[
  {
    "path": ".gitattributes",
    "content": "# Auto detect text files and perform LF normalization\n* text=auto\n*.min.js -diff\n*.min.css -diff"
  },
  {
    "path": ".github/workflows/docker-publish.yml",
    "content": "name: Build TheOneFile Verse\n\non:\n  push:\n    branches:\n      - main\n      - master\n    paths:\n      - 'theonefile_verse/**'\n      - '.github/workflows/docker-publish.yml'\n  create:\n    tags:\n      - 'v*'\n  release:\n    types: [published]\n  workflow_dispatch:\n\nenv:\n  REGISTRY: ghcr.io\n  IMAGE_NAME: ${{ github.repository_owner }}/theonefile_verse\n\njobs:\n  build-and-push:\n    runs-on: ubuntu-latest\n    permissions:\n      contents: read\n      packages: write\n\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v4\n\n      - name: Set up Docker Buildx\n        uses: docker/setup-buildx-action@v3\n\n      - name: Log in to GitHub Container Registry\n        uses: docker/login-action@v3\n        with:\n          registry: ${{ env.REGISTRY }}\n          username: ${{ github.actor }}\n          password: ${{ secrets.GITHUB_TOKEN }}\n\n      - name: Extract metadata (tags, labels)\n        id: meta\n        uses: docker/metadata-action@v5\n        with:\n          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}\n          tags: |\n            type=ref,event=branch\n            type=ref,event=tag\n            type=semver,pattern={{version}}\n            type=semver,pattern={{major}}.{{minor}}\n            type=sha,format=short\n            type=raw,value=latest,enable={{is_default_branch}}\n\n      - name: Build and push Docker image\n        uses: docker/build-push-action@v5\n        with:\n          context: ./theonefile_verse\n          push: true\n          tags: ${{ steps.meta.outputs.tags }}\n          labels: ${{ steps.meta.outputs.labels }}\n          cache-from: type=gha\n          cache-to: type=gha,mode=max\n          platforms: linux/amd64,linux/arm64\n"
  },
  {
    "path": "LICENSE",
    "content": "This is free and unencumbered software released into the public domain.\n\nAnyone is free to copy, modify, publish, use, compile, sell, or\ndistribute this software, either in source code form or as a compiled\nbinary, for any purpose, commercial or non-commercial, and by any\nmeans.\n\nIn jurisdictions that recognize copyright laws, the author or authors\nof this software dedicate any and all copyright interest in the\nsoftware to the public domain. We make this dedication for the benefit\nof the public at large and to the detriment of our heirs and\nsuccessors. We intend this dedication to be an overt act of\nrelinquishment in perpetuity of all present and future rights to this\nsoftware under copyright law.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\nIN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR\nOTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\nOTHER DEALINGS IN THE SOFTWARE.\n\nFor more information, please refer to <https://unlicense.org>\n\n- GelatineScreams\n"
  },
  {
    "path": "README.md",
    "content": "<p align=\"center\">\n  <img src=\"https://img.shields.io/badge/License-Unlicense-576169?style=for-the-badge&labelColor=01284b\" alt=\"License: Unlicense\">\n  <img src=\"https://img.shields.io/badge/TheOneFile-4.1.5-blue\" alt=\"TheOneFile 4.1.5\">\n  <a href=\"https://github.com/gelatinescreams/The-One-File/tree/main/theonefile_verse\">\n  <img src=\"https://img.shields.io/badge/TheOneFile_Verse-1.9.0-blue\" alt=\"TheOneFile_Verse Docker Version 1.9.0\">\n  </a>\n</p>\n\n![The One File](assets/theonefile.jpg)\n\nThere can only be One File. Map networks, smart homes, sports plays, mind maps, infrastructure or anything with nodes and connections. Animated zones, replay video export, enterprise encryption, full styling. Works offline, opens anywhere, saves into itself. \n\n*The Networkening edition adds live status monitoring and icons from [selfh.st/icons](https://selfh.st/icons/), [MDI](https://pictogrammers.com/library/mdi/), and [Simple Icons](https://simpleicons.org/).* Still one file. \n\n*TheOneFile_Verse adds an easily deployable, Docker based, real time collaboration wrapper that enables multiple users to create, edit and share together!\n\n![The One File corporate preview](assets/corporate-preview.gif) ![The One File routing preview](assets/routing-preview.gif) ![The One File homelab preview](assets/homelab-preview.gif) ![The One File mobile previews](assets/mobilepreviews.jpg)\n\n### Version 4.1.5 /\\ 3-26-26 : Enhanced 'Create', UI polish and squashing a sneaky bug and TheOneFile_Verse 1.8.6\n* **Enhanced Create**\n  * Added more options to the Create New Node & Rack modals.\n  * Added icon preview box to the Create New Node & Rack modals.\n\n* **Bug Fixes**\n* **Core Edition & Networkening**\n  * Fixed Assigned Rack dropdown snapping back to \"None\" after changing in the edit Node and Rack sidebar/footer\n  * See [Changelog](changelog.md) for full update details\n\n### TheOneFile_Verse /\\ 1.8.6 3-26-26 : Further improvements to Network Auto Discovery scanner/editor one page setup\n  * Very close to a stable 2.0\n  * See [TheOneFile_Verse changelog](theonefile_verse/changelog.md) for full 1.8.6 list\n\n<img width=\"1525\" height=\"770\" alt=\"Screenshot 2026-03-11 094906\" src=\"https://github.com/user-attachments/assets/43196f4e-f6c4-4d9d-aae0-d311b2bf94ec\" />\n<img width=\"1521\" height=\"770\" alt=\"Screenshot 2026-03-11 094937\" src=\"https://github.com/user-attachments/assets/c47e1cea-dd33-47ac-ac62-cef7fe92e3a3\" />  \n\n### Demos\n\n* **Core Edition**\n  * [Core: Corporation Demo](https://therecanonlybe.one/demos/?version=core&demo=corporate)\n  * [Core: Homelab Demo](https://therecanonlybe.one/demos/?version=core&demo=homelab)\n* **Networkening Edition**\n  * [Networkening: Corporate Demo](https://therecanonlybe.one/demos/?version=networkening&demo=corporate)\n  * [Networkening: Homelab Demo](https://therecanonlybe.one/demos/?version=networkening&demo=homelab)\n* **TheOneFile_Verse Edition** \n  * [TheOneFile_Verse online demo](https://multiverse.therecanonlybe.one/s/2ab95062-9d96-4e32-b373-d1994c210d82)\n  * *join from different browsers to see real time changes*\n  * [TheOneFile_Verse landing page](https://multiverse.therecanonlybe.one) \n  * [TheOneFile_Verse admin demo](https://therecanonlybe.one/theonefile_verse/demo-admin.html) \n\n### Download\n\n* [the-one-file.html](https://github.com/user-attachments/files/26288480/the-one-file.html)\n* [theonefile-networkening.html](https://github.com/user-attachments/files/26288482/theonefile-networkening.html)\n  \n### Core vs Networkening vs TheOneFile_Verse\n\n| Feature | theonefile.html | theonefile-networkening.html | TheOneFile_Verse |\n|---------|---------|--------|--------|\n| All core features | ✓ | ✓ | ✓ |\n| Create/edit/save topologies | ✓ | ✓ | ✓ |\n| Shapes, lines, styling | ✓ | ✓ | ✓ |\n| U slot rack topologies | ✓ | ✓ | ✓ |\n| **Add text labels anywhere** | ✓ | ✓ | ✓ |\n| **Draw rectangles/boxes** | ✓ | ✓ | ✓ |\n| **Free draw custom lines** | ✓ | ✓ | ✓ |\n| **Keyboard shortcuts** | ✓ | ✓ | ✓ |\n| Encryption, export | ✓ | ✓ | ✓ |\n| Bulk operations | ✓ | ✓ | ✓ |\n| Multi select | ✓ | ✓ | ✓ |\n| **AES 256 GCM encryption (OPTIONAL)** | ✓ | ✓ | ✓ |\n| Live node search | ✓ | ✓ | ✓ |\n| Per device styling | ✓ | ✓ | ✓ |\n| Mobile optimized | ✓ | ✓ | ✓ |\n| Offline only | ✓ |  | ✓ |\n| No dependencies | ✓ |  | ✓ |\n| **[MDI Icons](https://pictogrammers.com/library/mdi/)**       |  | ✓ | ✓ |\n| **[Simple Icons](https://simpleicons.org/?q=ping)**   |  | ✓ | ✓ |\n| **[selfh.st/icons](https://selfh.st/icons/)** |  | ✓ | ✓ |\n| **Auto Status Checking** |  | ✓ | ✓ |\n| **Live Ping/Health Status** |  | ✓ | ✓ |\n| **Real time, multi user collaboration** |  | ✓ | ✓ |\n\n### Docker & TheOneFile_Verse\n\n```bash\ndocker run -d -p 10101:10101 -v theonefile-data:/app/data ghcr.io/gelatinescreams/theonefile_verse:latest\n```\n\nOr with docker compose, create a `docker-compose.yml`:\n\n```yaml\nservices:\n  theonefile_verse:\n    image: ghcr.io/gelatinescreams/theonefile_verse:latest\n    ports:\n      - \"10101:10101\"\n    volumes:\n      - ./data:/app/data\n    restart: unless-stopped\n```\n\nThen run:\n```bash\ndocker compose up -d\n```\n\nOpen `http://localhost:10101`\n\n* [More information on TheOneFile_Verse](https://github.com/gelatinescreams/The-One-File/tree/main/theonefile_verse)\n\n### Core + Networkening Editions Download:\n\n* Core [the-one-file.html](https://github.com/user-attachments/files/24578723/the-one-file.html)\n* Networkening [theonefile-networkening.html](https://github.com/user-attachments/files/24578724/theonefile-networkening.html)\n\n### Features\n* Zero coding knowledge required    \n* Zero config files\n* Draw anywhere: add text labels, boxes, and custom lines to annotate your topology\n* Full keyboard shortcut support for power users\n* Advanced keyboard navigation with arrow keys, tab cycling, and focus controls\n* Node locking to prevent accidental changes to positioned elements\n* Node grouping to move related components together\n* Touch optimized mobile interface with gesture support\n\n### Canvas and Navigation\n* Large zoomable and pannable workspace  \n* Minimap with viewport tracking  \n* Works with touch and mobile  \n* Clear grid and boundary indicators\n* Precise zoom controls with level display\n* Right click context menu for quick actions\n* **Free Draw Mode** Draw custom polylines, rectangles, and text labels anywhere:\n  * Custom lines with points you place\n  * Rectangles (filled or outlined) for zones/boundaries\n  * Text labels with full styling (font, size, color, weight, alignment)\n  * Customizable colors, line styles (solid/dashed/dotted), and arrows\n* Keyboard shortcuts for power users (undo/redo, copy/paste, zoom controls, node navigation)\n* **Arrow key nudging** for pixel perfect positioning (1px or 10px with Shift)\n* **Tab cycling** to quickly navigate through all nodes\n* **Focus key** to auto zoom to selected nodes\n\n### Nodes\n* Multiple shapes for common devices including servers, routers, switches, firewalls, and clouds  \n* *Icon shapes from MDI, Simple Icons, and selfh.st/icons available in the theonefile-networkening.html version*\n* Editable name, IP, role, tags, and notes\n* *Editable icon shapes from MDI, Simple Icons, and selfh.st/icons available in the theonefile-networkening.html version*\n* Resizable with full styling controls  \n* Custom fonts, colors, and text offsets  \n* **Per breakpoint styling** for desktop, tablet, mobile, and fold layouts : customize appearance independently for each screen size\n* **Right click to clone** nodes with smart positioning\n* **Multi select support** with click drag or right click selection\n* **Lock nodes** to prevent accidental movement while editing\n* **Group nodes** to move multiple related nodes as a single unit\n* Visual indicators for locked and grouped nodes\n\n#### Racks\n* Create rack nodes to represent physical server racks\n* Enter rack view by double clicking a rack node (or long press on mobile)\n* Inside rack view, see a vertical rack with U unit markings\n* Assign devices to specific U positions within racks\n* Set device height in rack units (1U, 2U, 4U, etc.)\n* Visual rack capacity indicator shows available vs used space\n* Nodes assigned to racks appear in both topology and rack views\n* Quick navigation: double tap empty space on mobile to exit rack view\n* Automatic rack assignment when creating nodes while in rack view\n\n### Layer System\n* Choose between physical, logical, security and application layers for anything and easily toggle visibility between them\n* Toggle layer visibility to focus on specific aspects of your topology\n* Filter view to show only nodes and connections in selected layers\n* Simplifies complex topologies by letting you focus on one aspect at a time\n\n#### Lock and Group System\n* **Lock individual or multiple nodes** to prevent accidental movement\n  * Visual lock indicator appears on locked nodes\n  * Locked nodes cannot be moved by mouse, touch, or keyboard\n  * Keyboard shortcut (L) for quick lock/unlock\n  * Works with multi selection for batch locking\n  * Lock button in mobile multi select menu\n* **Group nodes to move together**\n  * Create groups of related nodes that move as a unit\n  * Visual group indicator (dashed outline) on grouped nodes\n  * Drag any member of a group to move all nodes in that group\n  * Keyboard shortcut (G) to create/dissolve groups\n  * Groups respect individual node lock status\n  * Group button in mobile multi select menu\n  * Perfect for logical grouping (clusters, zones, related infrastructure)\n\n### Bulk Operations\n* **Multi select nodes** for batch operations\n* **Bulk Align**: Align selected nodes left, right, top, or bottom\n* **Bulk Distribute**: Evenly space nodes horizontally or vertically\n* **Bulk Clone**: Duplicate multiple nodes at once\n* **Bulk Delete**: Remove multiple nodes simultaneously\n* **Bulk Lock**: Lock or unlock multiple nodes at once\n* **Bulk Group**: Create or dissolve node groups\n* **Desktop and mobile toolbars** optimized for each platform\n\n### Network Monitoring *(theonefile-networkening.html only)*\n* **Live status indicators** on nodes (online/offline/checking)\n* **Manual ping/status check** for individual nodes\n* **Auto Status Checking** with configurable intervals (5-3600 seconds)\n* Status check scheduling with next check timer\n* Last run timestamp tracking\n* Per node ping enable/disable settings\n* Visual ping indicators with color coding\n\n### Connections\n* Smart routed lines between nodes\n* Multiple links between the same devices  \n* Optional direction arrows  \n* Custom width, color, and labels  \n* Port labels (e.g., eth0, gi0/1)\n* Notes for VLANs, protocols, policies, and bandwidth  \n\n### Legend (Bottom left)\n* Can be hidden on both desktop and mobile\n* Automatically built from line colors in use  \n* Editable labels  \n* note: only shows up after first line is generated.\n\n### Save System\n* Exports a brand new updated HTML file  \n* All data is embedded in the file  \n* Optional AES 256 GCM encryption for sensitive information  \n* Browser native crypto only  \n* No servers involved  \n* *Version theonefile-networkening.html uses 3 server calls from cdn.jsdelivr.net to load icons*\n\n### Security & Encryption\n**Industry Standard Protection:**\n* **AES 256 GCM** encryption (authenticated encryption with 256 bit keys)\n* **PBKDF2** key derivation with:\n  * 200,000 iterations (protection against brute force attacks)\n  * SHA 256 hashing algorithm\n  * Cryptographically secure random 16 byte salt per file\n  * Unique 12 byte initialization vector (IV) per encryption\n* **Browser native Web Crypto API** no third party encryption libraries\n* **Zero server communication** all encryption/decryption happens locally in your browser\n* **Password confirmation** required before saving encrypted files\n* **Non recoverable** no backdoors, no password reset, no recovery options\n  * If you forget your password, your data is permanently inaccessible\n  * This is a feature, not a bug so your data stays protected\n* **3 attempt limit** on decryption to prevent brute force attempts\n* **Encrypted data format**: Salt + IV + ciphertext embedded directly in the HTML file\n\n### Mobile Experience\n* **Completely rewritten mobile UI** in version 3.0\n* **Resizable mobile footer** with drag handle for custom panel sizing\n* **Touch optimized controls** throughout the interface\n* **Mobile bulk operations modal** for efficient multi node editing with Lock and Group buttons\n* **Enhanced double tap gestures**:\n  * Double tap nodes to select multiple (equivalent to right click on desktop)\n  * Double tap to clone and align nodes\n  * **NEW: Double tap empty space in rack view to quickly exit to topology view**\n* Responsive layout that adapts to screen orientation\n* Optimized for phones, tablets, and foldable devices\n* Smart gesture detection prevents accidental actions during panning\n\n### Keyboard Shortcuts\n\n[keyboard-shortcuts.md](keyboard-shortcuts.md)\n\n| Shortcut | Action |\n|----------|--------|\n| `Arrow Keys` | Move selected node(s) 1 pixel |\n| `Shift + Arrow Keys` | Move selected node(s) 10 pixels |\n| `Tab` | Cycle to next node |\n| `Shift + Tab` | Cycle to previous node |\n| `F` | Focus camera on selected node(s) |\n| `L` | Lock/unlock selected node(s) |\n| `G` | Group/ungroup selected nodes (requires 2+) |\n| `Ctrl/Cmd + Z` | Undo |\n| `Ctrl/Cmd + Y` | Redo |\n| `Ctrl/Cmd + Shift + Z` | Redo (alternative) |\n| `Ctrl/Cmd + C` | Copy selected node |\n| `Ctrl/Cmd + V` | Paste node |\n| `Ctrl/Cmd + D` | Duplicate selected node |\n| `Ctrl/Cmd + A` | Select all nodes |\n| `Ctrl/Cmd + Plus` | Zoom in |\n| `Ctrl/Cmd + Minus` | Zoom out |\n| `Ctrl/Cmd + 0` | Reset view |\n| `Space + Drag` | Pan canvas |\n| `Escape` | Clear selection |\n| `Delete` | Delete selected item(s) |\n| `Scroll` | Zoom in/out |\n| `Shift + Click/Drag` | Multiple Select (marquee selection) |\n\n## Recording\n\n| Key | Action |\n|-----|--------|\n| R | Start/stop real time recording |\n| Shift+R | Start/stop step by step recording |\n| Space | Add step (step recording) / Play/Pause (playback) |\n| P | Play recording |\n\n### Mobile Gestures & Touch Controls\n\n[mobile-gestures.md](mobile-gestures.md)\n\n| Gesture | Action | Context |\n|---------|--------|---------|\n| **Double tap node** | Add to multi selection | Any view |\n| **Double tap empty space** | Exit to topology view | Rack view only |\n| **Long press rack node** | Enter rack view | Topology view |\n| **Pinch** | Zoom in/out | Any view |\n| **One finger drag (empty space)** | Pan canvas | Any view |\n| **One finger drag (node)** | Move node | Any view |\n| **Tap selected node** | Open properties panel | Any view |\n| **Drag footer handle** | Resize mobile panel | Mobile only |\n\n### Customization\n* 100% control theme editor\n* Per breakpoint node styling for responsive designs\n* Custom color schemes and backgrounds\n* Adjustable panel sizes and layouts\n\n### Supported Browsers\n\n* Chrome and Edge  \n* Firefox  \n* Safari desktop and mobile  \n* Modern Android and iOS browsers  \n\nIf the browser is reasonably modern, it should work.\n\n### Credits\n\nIcon support for theonefile-networkening.html version powered by:\n* [selfh.st/icons](https://selfh.st/icons/) : Popular self hosted icons\n* [Material Design Icons](https://pictogrammers.com/library/mdi/) : 7,000+ open source icons\n* [Simple Icons](https://simpleicons.org/) : Free SVG icons for popular brands\n\nThank you to all the icon creators and maintainers for making these resources freely available.\n"
  },
  {
    "path": "changelog.md",
    "content": "#### CHANGE LOG\n\n### Version 4.1.5 /\\ 3-26-26 Enhanced 'Create', UI polish and squashing a sneaky bug and TheOneFile_Verse 1.8.6\n* **Enhanced Create**\n  * Added more options to the Create New Node & Rack modals.\n  * Added icon preview box to the Create New Node & Rack modals.\n\n* **Bug Fixes**\n* **Core Edition & Networkening**\n  * Fixed Assigned Rack dropdown snapping back to \"None\" after changing in the edit Node and Rack sidebar/footer\n\n* **TheOneFile_Verse 1.8.6** Further improvements to Network Auto Discovery scanner/editor\n  * See [TheOneFile_Verse changelog](theonefile_verse/changelog.md) for full details\n\n### Version 4.1.4 /\\ 3-4-26 Testers finding the stragglers. Thank you to everyone!\n* Added custom port label logic to the canvas that allows port labels to find the next blank space automatically. (this is v1 and will likely be upgraded). Thanks to [jbr1989](https://github.com/jbr1989) [#44](https://github.com/gelatinescreams/The-One-File/issues/44)\n* Fixed an issue where port maps were not showing on the canvas. Thanks to [jbr1989](https://github.com/jbr1989) [#44](https://github.com/gelatinescreams/The-One-File/issues/44)\n* Fixed an issue where port maps were not displaying correctly in b&w print preview. Thanks to [jbr1989](https://github.com/jbr1989) [#44](https://github.com/gelatinescreams/The-One-File/issues/44)\n* Added version number to the bottom of settings modal for better versioning and TheOneFile_Verse tracking Thanks to [jbr1989](https://github.com/jbr1989) [#43](https://github.com/gelatinescreams/The-One-File/issues/43)\n\n* **Core Edition**\n  * Fixed an issue where connections dropdown were not displaying correctly in node and rack information panel(s)\n\n* **TheOneFile_Verse 1.8.0** Added a few settings, fixed some bugs, annoyances, security and production friendly hierarchical structure\n  * See [TheOneFile_Verse changelog](theonefile_verse/changelog.md) for full details\n  \n### Version 4.1.3 /\\ 2-14-26 Styles, bugs and TheOneFile_Verse 1.5.2\n* Fixed an issue where scrolling down on modals was not possible on some mobile environments\n* Fixed an issue where draw and add connection buttons would show at inverse times\n* Fixed an issue where the draw button would dissapear on mobile in some rare cases during import\n* Fixed an issue where JSON import was not working in networkening edition\n* Fixed one (1) missing and incorrect language keys from networkening edition\n* **New Demo**\n* **TheOneFile_Verse 1.5.2** Auth flow fixes, error logging, version tracking\n  * See [TheOneFile_Verse changelog](theonefile_verse/changelog.md) for full details\n\n### Version 4.1.2 /\\ 1-12-26 Image support, Notes Enhancements and Squashing Bugs\n* **Image System**\n  * New image upload icon added to toolbar\n  * Drag and drop images added to canvas\n  * Each image has their own customizable styles\n  * Images are compressed for the one file purpose. NOT FOR ARCHIVING!\n\n* **Notes System Enhancements**\n  * Added rich text editor to all notes\n  * Added notes linking to main notes hub with notes search\n  * Added search to all individual notes sections\n  \n* **Bug Fixes**\n  * Fixed outlined zones rendering on top of nodes\n  * Fixed z order issue where outlined zones appeared above nodes and racks\n  * Fixed wall style overwriting opacity with hardcoded value\n  * Fixed recordings not saving with HTML export in some rare cases\n  * Fixed and issue where zones and text labels not captured in recordings\n  \n* **Core Edition**\n  * Fixed errant translation keys for the language system\n \n* **Networkening Edition**\n  * Fixed Zone line style dropdown not working.  Big thank you to legendary tester [mohacc2008-ctrl](https://github.com/mohacc2008-ctrl)\n  * Fixed z order issue where outlined zones appeared above nodes and racks\n  * Fixed four (4) missing and incorrect language keys\n\n### Version 4.1.1 /\\ 1/12/26 Step by step recording\n* **Added step by step recording**\n  * Step by step recording mode (green ●+ button) for manual frame capture\n  * Each step plays for 1 second in playback\n  * Keyboard shortcuts for recording: R (real time), Shift+R (step), Space (add step/play/pause), P (play)\n\n### Version 4.1.0 /\\ 1/12/26 The Vision Converges\n* **Added custom language system**\n  * full GUI for language editing\n  * or import/export via json\n  * language system available in the main settings menu\n  * more translations are coming in the future\n* **Expanded Mapping Modes**\n  * added Sports, MindMap, Smart Home and Blueprint\n  * each with their own custom sports grid\n* **Expanded Grid System**\n  * new network grids available : dots, blueprint\n  * new sports grids available : basketball, american football, football(soccer), hockey, baseball, tennis\n* **Full motion recording system**\n  * record and play back different situations on your map type\n  * saves replays to your data and/or export them as movies (webm)\n* **Orthogonal Overhaul** Complete rewrite of the entire connections system. \n  * Create waypoints anywhere on the connection and drag it around.\n  * custom script that re evaluates routing in real time, no matter how many way points you need.\n  * double click on desktop and long press on mobile\n* **Added a new welcome modal to make map mode switching easier**  \n  * each tab has its own mapping type. unlimited options\n* **Tons of performance tweaks**\n  * updated preset themes\t\n* **Added new demo browser**  \n* **Tons Of visual fixes**\n* **Added new print color option**\n* **TheOneFile_Verse enhancements + fixes** [More information on TheOneFile_Verse](https://github.com/gelatinescreams/The-One-File/tree/main/theonefile_verse)\n\n### Version 4.0.0 /\\ 1/6/26 Stable + TheOneFile_Verse\n* **4.0.0 Stable! Thank you to everyone!**\n* **TheOneFile_Verse launch** An easily deployable, Docker based, real time collaboration wrapper that enables multiple users to work together!\n  * [TheOneFile_Verse online demo](https://multiverse.therecanonlybe.one/s/b208667b-7a9e-4a18-ac98-5cb6e73bb669)\n  * *join from different browsers to see real time changes*\n* [More information on TheOneFile_Verse](https://github.com/gelatinescreams/The-One-File/tree/main/theonefile_verse)\n* **Fixed an issue where nodes and racks did not display correctly in print export**\n* **I am working on new demos with all the included features**\n\n### Version 3.9.9.9 /\\ 12/29/25 Upgraded Animations, minimap and editable options\n* **Coverage zones now work on 11 device types**\n  * Camera, CCTV, Doorbell\n  * Motion Sensor, Smoke Detector\n  * Access Point, WiFi, Router\n  * Sensor, IoT, Sprinkler\n\n* **Added new editable Detection Zone properties**\n  * Inner Radius : Add inner dead spots to zones\n  * Opacity : Control zone fill transparency\n  * Gradient Fade : Zone fades from center to edge\n  * Border Color : Separate color for zone outline\n  * Border Width : Thickness of zone outline\n  * Border Style : Solid, dashed, or dotted outlines\n  * Border Opacity : Transparency of zone outline\n\n* **Added text labels to coverage zones**\n* **Added editable text labels properties to coverage zones**\n\n* **Added animation types**\n  * Sweep : Pan back and forth\n  * Pulse : Breathing/scale effect\n  * Rings : Expanding circles emanating outward\n  * Spin : Continuous rotation\n\n* **Added Zone Presets**\n  * Security Cam, PTZ Cam, Motion Detect\n  * WiFi Strong, WiFi Extended\n  * Smoke Alarm, Sprinkler Arc\n* **Save your own custom presets!**\n\n* **Added new Bulk Operations for zones**\n  * Bulk Copy : Copy zone style from first selected node\n  * Bulk Paste : Apply copied style to all selected nodes\n  * Bulk Toggle : Enable/disable zones on all selected nodes\n  \n* **Updated Line legend to includes zones**\n  * Auto generated legend showing zone colors\n  * Editable labels for each zone color\n  * Click color swatch to select nodes with that zone color for faster editing\n\n* **New Main Settings : Animations & Zones**\n  * Master toggles (all animations, all zones)\n  * Type toggles (sweep, pulse, rings, spin, connections)\n  * Category toggles (cameras, doorbells, motion sensors, smoke detectors, wifi/AP/router, sensors/IoT, sprinklers)\n\n* **Minimap Improvements**\n  * Removed all animations and coverage zones from minimap (performance improvemnet and useless visually)\n  * Simplified node shapes in minimap\n  * Circles for regular nodes\n  * Squares for racks\n  * Outlined diamonds for animatable devices\n  * Added wall rectangles from free draw to minimap\n  * Added orthogonal routing display to minimap\n  * Fixed: resizing nodes now updates minimap immediately\n\n* **Welcome Message addition**\n  * Click or tap anywhere to dismiss early.\n\n* **Improved: HTTP Ping / Status Monitoring for Networkening edition**\n  * More reliable detection that now uses image load method to verify server actually responds with valid content. Built in fetch fallback for edge cases.\n  * Added response time tracking (e.g., \"47ms\")\n  * Batched checking now checks nodes in groups of 5 for faster bulk checks without overwhelming the network\n  * Added advanced timeout handling\n  \n* **General Bug Fixes**  \n\n### Version 3.9.9.8 /\\ 12/22/25 Animations, walls, settings, themes and tidying up for 4.0\n* **Theme System Overhaul**\n  * Added 8 preset themes organized by category\n  * Save your own custom themes for easy tab theming\n* **Animated Connections with Flow**\n  * Show which way data is flowing for all, some, or any amount of custom connections\n  * Includes options for all the flow settings\n* **CCTV/Camera nodes now have FOV Cone options for both stationary and PTZ cameras**\n  * Cone can be animated and animation carry over to minimap\n* **Added 13 new device icons in a new \"Smart Home\" dropdown category:**\n  * Thermostat, Video Doorbell, Smart Lock, Smart Bulb, Smart Plug, Smart Speaker, Smart TV, Smart Hub, Smoke Detector, Motion Sensor, Garage Door, Sprinkler, Robot Vacuum\n* **Bug fixes for Core + Networkening Editions**\n  * Removed unused code\n  * Fixed rotate actions and added -360 degrees on canvas assets\n  * Added defensive checks to prevent potential very edge case crashes\n  * Replaced port map links with buttons for better UI on both desktop and mobile\n  * Camera FOV cones render on the minimap with synchronized animation\n  * Line (✏️), Rectangle (▭), and Text (T) tool buttons now pulse with glow animation when active to add visual feedback so users know to click \"Done\" to exit drawing mode\n  * Resize Handles now scale with border width(s)\n  * Fixed a memory leak where pressing Escape to close the Rack Unit or U Height editor could cause erratic behavior after repeated use\n  * Port Map Export: Fixed CSV export producing malformed files when device names contained quotes or special characters\n  * Undo now works after importing JSON files\n  * Markdown import now validates connections and invalid links are skipped instead of creating broken edges\n  * Welcome message no longer disappears after saving and refreshing\n* **Bug fixes for Networkening Edition**\n  * Removed localStorage icon caching\n  * Old exported cached icons are automatically cleaned up on first load\n\n### Version 3.9.9.7 /\\ 12/19/25 Import/export, saving and updates for editors\n* **Improved the import/export system**  [read more: import-export-save.md](import-export-save.md)\n* **Added markdown export and import**  Ala, Git versioning!\n  * Edit and create in your favorite markdown editor\n  * This also allows git versioning!\n* **Added csv export and import**  \n  * Edit and create in your favorite csv editor\n* **Added print option**  \n  * Removes the background and makes the entire canvas print friendly\n* **New import/Export mobile menu**  \n* **Bug Fixes**  \n  * Fixed minimap rendering nodes twice (performance)\n  * Fixed event listener memory leak in edge drag handlers\n  * Fixed Mac keyboard shortcuts (SORRY APPLE)\n\n### Version 3.9.9.6 /\\ 12/17/25 Stretching version numbers AND lines too!\n* **Big performance update**\n* **Orthogonal routing update** [changelog.md](changelog.md)\n  * New orthogonal routing option draws clean 90 degree angle connections\n  * Three routing styles available: Orthogonal, Curved, and Straight\n  * Change between all of them in the settings at any time OR mix and match them   \n* **Auto Recovery**\n  * If your browser crashes or you accidentally close the tab, your work is saved in your local browser\n  * If you want to clear this cache, use the Clear All option in settings\n* **Enhanced Search** : Search now zooms in and out of the canvas automatically to group found items\n* **Mobile Undo** : Add three finder tap anywhere on the canvas for undo on mobile devices\n* **Performance Under The Hood**\n  * Multiple rapid changes are now batched into single redraws\n  * Minimap updates are throttled during pan and zoom\n  * Drop shadows and pulse animations automatically disabled when zoomed out below 50 percent\n  * Mouse and touch pan handlers optimized to prevent layout thrashing\n  * CSS containment added to panels and viewport for faster rendering\n  * Undo performance updates\n  * Minimap performance updates\n  * Custom Orthogonal gap function : edges show gaps where they cross other orthogonal edges\n  * Refactored update/import function\n  * Refactored background grid for performance\n  \n### Version 3.9.9.5 /\\ 12/14/25 The ports enhancements update\n* **Port connections section in node/rack panel** : See all ports connected to a device at a glance\n* **Click to edit unassigned ports** : Assign ports directly from the devices\n* **Port JumpTO** : Click ↗ to jump to that connection on canvas and highlight it\n* **Edge color indicators** : Added color codes to the dedicated port view for easy reference\n* **Duplicate port warning** : Added a vert simply alert when assigning a port already in use on that device\n* **Squashed port bugs** : Squashed some small related ports bugs\n\n### Version 3.9.9.4 /\\ 12/14/25 Further fixes towards 4.0 Stable\n* Fixed an issue where entering rack view would freeze the canvas on mobile\n* Fixed an issue where node creation could be interrupted in rare cases\n* Fixed pedantic HTML sanitations for inputs\n* Added a function to sanitize inputs\n\n### Version 3.9.9.3 /\\ 12/13/25 Added Advanced Ports View\n* Fixed [#27](https://github.com/gelatinescreams/The-One-File/issues/27)** thanks to [@lehnerpat](https://github.com/lehnerpat) for debugging\n* Fixed an issue where modals were not closing correctly in the background\n\n### Version 3.9.9.2 /\\ 12/11/25 Added Advanced Ports View\n* **Added New Ports View [#25](https://github.com/gelatinescreams/The-One-File/issues/25)** thanks to [@mohacc2008-ctrl](https://github.com/mohacc2008-ctrl) for the request\n* **Squashed Bugs**\n* * Fixed a bug where deleted tabs were not leaving the old canvas view\n* * Fixed a bug where foldables and some tablets showed desktop elements\n* * Started to refactor a few core elements for future updates\n\n##### Version 3.9.9.1 /\\ 12/10/25 Getting close to 4.0 Stable!\n* **Fixed Search [#24](https://github.com/gelatinescreams/The-One-File/issues/24)**\n* **Upgraded and fixed the undo system**\n* * Edit node name/IP now undoable\n* * Edit/delete/add tags now undoable\n* * Edit/delete notes now undoable\n* * Resize node (slider + reset) now undoable\n* * Change shape now undoable\n* * Style changes (colors, fonts, borders) now undoable\n* * Edit width/color/direction/style now undoable\n* * Delete edge now undoable\n* * Edit/delete edge notes now undoable\n* * Drag custom edge points now undoable\n* * Edit color/border color/border width/style now undoable\n* * Delete zone now undoable\n* * Delete zone notes now undoable\n* * All 9 text properties (font size, color, weight, style, align, decoration, bg color, bg enabled, opacity) now undoable\n\n* **Squashed Bugs**\n* * On mobile/tablet, users could still drag canvas elements (nodes, rectangles) even when View Only mode is enabled.\n* * Ctrl+A (Select All) now shows correct total count (was only counting nodes)\n* * (Shift multi select) Marquee select no longer accumulates between selections\n* * Misc code refinement\n\n##### Version 3.9.9 /\\ 12/10/25 Hotfix release for desktop multi select\n* **Added back mobile core functions from updating the audit system\n\n##### Version 3.9.8.1 /\\ 12/10/25 Hotfix release for desktop multi select\n* **fixed a bug where mutli select with shift on desktop counted all entities it crossed\n\n##### Version 3.9.8 /\\ 12/10/25 View only mode + multi select for desktop\n\n* **Added View Only Mode in Settings** Disables all editing while keeping pan and zoom\n* * Click or tap any canvas item five times to inspect its details while in View Only Mode\n* **Upgraded and fixed the Audit log.** Moved it to the stored JSON. Audit log now transfers with the save!\n* * Added smart migration to merge old localStorage audit with new JSON storage\n* **Added new core shapes to both versions.**\n* **New desktop mutli entity select option added.** [#21](https://github.com/gelatinescreams/The-One-File/issues/21)  + more : Special thanks to [@mitchplze](https://github.com/mitchplze)\n* * Hold shift and click+drag on empty canvas to select multiple items at once\n* * Selection box colors can be customized in Settings\n\n##### Version 3.9.7 /\\ 12/9/25 Squashing Bugs\n\n* **Special thanks to @mitchplze for testing and reporting on some of these fixes**\n* **Bug Fixes**\n* * Fixed duplicate buttons appearing in saved files after multiple saves\n* * Fixed toolbars staying in desktop layout when resizing window to mobile size\n* * Fixed duplicate import button in Settings\n* * Removed unused code to reduce file size\n* * Zone line style (solid, dashed, dotted) can now be changed after creation. \n* **Squashing more bugs** [#21](https://github.com/gelatinescreams/The-One-File/issues/21)  + more : Special thanks to [@mitchplze](https://github.com/mitchplze)\n* **Networkening Edition Fixes:**\n* * Dropdown now shows \"Custom Icon\" when a web icon is selected. [#19](https://github.com/gelatinescreams/The-One-File/issues/19)\n* * Switching to any core shape automatically clears the web icon [#19](https://github.com/gelatinescreams/The-One-File/issues/19)\n* * Web icons keep their original colors until you change them [#19](https://github.com/gelatinescreams/The-One-File/issues/19)\n* * Fixed web icon colors persisting when selecting a new icon. New icons now display their natural colors instead of inheriting previous custom colors. [#19](https://github.com/gelatinescreams/The-One-File/issues/19)\n* * Fixed selfh.st icon search to only show icons that have SVG versions available. (for editability) [#19](https://github.com/gelatinescreams/The-One-File/issues/19)\n\n##### Version 3.9.6 /\\ 12/8/25 Rack canvas fixes\n\n* **Can now view and edit nodes that are inside a given rack just by selecting a rack and using the sidebar / mobile footer**\n* **Updated all keyboard shortcuts to work with new canvas draw tools**\n  * Fixed a bug where cloning racks with nodes inside would not clone the nodes\n  * Fixed a bug where deleting nodes and racks via the information panel was broken\n  * Fixed a bug where nodes were created at the bottom of canvas in rack view regardless of user view\n* **They will now generate in the center of users view**\n  * Fixed a bug where page titles were overriden by tab titles\n  * Fixed a bug where canvas information panel from previous tabs would stay selected after tab switch \n\n##### Version 3.9.5 /\\ 12/7/25 The Styles Update!\n\n* **Added all available styling options to settings**\n* **Canvas Styling** Added seperated styling for the main canvas and rack view canvases\n  * Added additional syling options to free draw tools\n\n##### Version 3.9.4 /\\ 12/6/25 Canvas bug fixes\n\n* **Help Tab** Added keyboard and mouse how:to in ?help\n* **Various bug fixes including**\n  * Added additional styling options for free draw tools\n  * Fixed a bug where cloned nodes inside racks would clone to main canvas\n  * Fixed a bug where free drawn rectangle zones would not allow outlined style\n  * Fixed a bug where free drawn lines were not draggable after creation\n  * Fixed a bug where free drawn lines were not editable after creation\n  * Fixed a bug where free drawn rectangle zones were not editable after creation\n  * Fixed a bug where free drawn rectangles zones had no custom styling inputs\n  * Fixed a bug where free drawn tools were not grouped correctly with nodes and racks\n  \n##### Version 3.9.3 /\\ 12/5/25 Mobile bug fixes\n\n* **NEW 3.9.3** Mobile fixes. \n* **Various bug fixes including**\n  * Rewrote every mobile touch event\n  * Cleaned mobile core\n  \n##### Version 3.9.2 /\\ 12/5/25 Bug fixes\n\n* **NEW 3.9.2** Styles per tab! You can now set different styles for each topology tab.\n* **Various bug fixes including**\n  * Fixed a bug where rack and node colors were not applying correctly\n  * Added node and rack border along with fill to icons\n  * Fixed a bug where rack view can get stuck\n  * Fixed a bug where rack height and rack unit got swapped causing drag errors in rack view\n  * Fixed a bug where lines could be added to a node when the node was inside a rack\n  * Fixed a bug where lines would persist after a node is added to a rack\n  * Fixed various dragging and dropping bugs\n  * Changed the icons to not animate on desktop (this was implemented in version .2, well before the style sidebar)\n\n##### Version 3.9 /\\ 12/4/25\n\n* **NEW 3.9** Tabs, snapshots, action audits and notes! (encrypted also)\n  * Tabs are now implemented. This allows unlimited locations of topologies, still one file!\n* **NEW 3.9** General topology notes are now implemented. You can also encrypt per note.\n* **NEW 3.9** Snapshots are now implemeted. Auto save and manual save is available.\n* **NEW 3.9** Action audits are now implemeted. This allows you to audit whether or not the file has been tampered in an easy to read log.\n  * *Note: Both snapshots and audits are local browser only and do not trasnfer to another device. This feature is coming in 4.0*\n* **NEW 3.7** The Rackening!!\n  * Add any rack size from 6-48 and open it as its own canvas with U slot placement of nodes, new and existing, enabling unlimited hierarchical rack layouts.\n* **NEW 3.7** Layers upon layers!!\n  * Choose between physical, logical, security and application layers for anything and easily toggle visibility between them.\n* **NEW 3.7** Added Racks and individual rack canvasses!\n* **NEW 3.7** *Major workflow enhancement release with full keyboard control and mobile optimization*\n* **NEW 3.7** The Rackening!!\n  * Add any rack size from 6-48 and open it as its own canvas with U slot placement of nodes, new and existing, enabling unlimited hierarchical rack layouts.\n* **NEW 3.7** Layers upon layers!!\n  * Choose between physical, logical, security and application layers for anything and easily toggle visibility between them.\n* **NEW 3.7** Added Racks and individual rack canvasses!\n* **NEW 3.7** *Major workflow enhancement release with full keyboard control and mobile optimization*\n\n##### Version 3.7 /\\ 12/3/25\n* **NEW 3.7 Advanced Keyboard Navigation**\n  * **Arrow Keys** Move selected nodes 1 pixel in any direction for precise positioning\n  * **Shift + Arrow Keys** Move selected nodes 10 pixels for faster adjustments\n  * **Tab** Cycle forward through all nodes in current view\n  * **Shift + Tab** Cycle backward through all nodes in current view\n  * **F** Focus camera on selected node(s) with automatic zoom and centering\n* **NEW 3.7 Node Lock System** Prevent accidental movement of positioned nodes\n  * Lock individual nodes or multiple nodes at once\n  * Visual lock indicator on locked nodes\n  * Locked nodes cannot be moved by dragging or keyboard\n  * Works with multi selection for batch operations\n  * Lock status persists in saved files\n* **NEW 3.7 Node Grouping System** Move related nodes together as a unit\n  * Group multiple nodes to move them as a single unit\n  * Visual group indicator (dashed outline) on grouped nodes\n  * Drag any group member and all nodes in the group move together\n  * Groups respect lock status (locked nodes stay in place)\n  * Create and dissolve groups dynamically with keyboard shortcut\n  * Group membership persists in saved files\n* **NEW 3.7 Enhanced Mobile Experience**\n  * **Double tap empty space to exit rack view** Quick navigation without button press\n  * **Lock Toggle button** in mobile multi select menu for easy locking on touch devices\n  * **Group Toggle button** in mobile multi select menu for easy grouping on mobile\n  * Haptic feedback (vibration) for double tap actions on supported devices\n  * Smart gesture detection (pan vs tap) prevents accidental actions\n* **NEW 3.7 Rack View Improvements**\n  * Nodes created while in rack view automatically assign to that rack\n  * Drawing tools (free draw, rectangles, text) properly disabled in rack view with clear feedback\n  * Improved workflow prevents accidentally placing elements in wrong view\n* **A more detailed list of changes is included below**\n  * [Change Log](changelog.md)\n\n##### Version 3.5.1 /\\ 12/2/25\n- **NEW 3.5.1** Small style fixes and mobile refactoring\n\n##### Version 3.5 /\\ 12/2/25\n- **NEW 3.5** *Another major release. Thank you to Discord testers!!*\n- **NEW 3.5 Add Text Labels Anywhere** Click the \"T\" button to place custom text annotations anywhere on your canvas with full styling control\n- **NEW 3.5 Draw Rectangles/Boxes** Create visual boundaries, zones, or highlighted areas with filled or outlined rectangles in any color\n* **NEW 3.5 Bulk Operations** Select multiple nodes at once with right click (or double tap on mobile) and perform batch operations:\n  * Align Left, Right, Top, or Bottom\n  * Distribute Horizontally or Vertically\n  * Clone all selected nodes\n  * Delete in bulk\n* **NEW 3.5 Keyboard Shortcuts** Power user controls:\n  * `Ctrl/Cmd + Z` Undo\n  * `Ctrl/Cmd + Y` or `Ctrl/Cmd + Shift + Z` Redo\n  * `Ctrl/Cmd + C` Copy node\n  * `Ctrl/Cmd + V` Paste node\n  * `Ctrl/Cmd + Plus` Zoom in\n  * `Ctrl/Cmd + Minus` Zoom out\n  * `Ctrl/Cmd + 0` Reset view\n  * `Space + Drag` Pan canvas\n- **NEW 3.5 Mobile Gestures** Touch optimized controls:\n  - **Double tap** to select multiple nodes\n  - **Double tap** to clone and align nodes\n  - Resizable mobile footer with drag handle\n  - Touch friendly bulk operations modal\n- **NEW 3.5 Per Breakpoint Styling** Customize node appearance independently for Desktop, Tablet, Mobile, and Fold layouts\n- **NEW 3.5 Live node search with visual highlighting**\n- **NEW 3.5 Added MAC field to node**\n- **NEW 3.5 Added Rack field to node**\n- **NEW 3.5 Live node search with visual highlighting**\n- **NEW 3.5 Upgraded Military Grade Encryption** AES 256 GCM encryption with PBKDF2 key derivation (200,000 iterations)\n  - Browser native encryption, zero server involvement\n  - Password protect sensitive network documentation\n  - Non recoverable (no backdoors, your data stays truly private)\n  - Perfect for break glass documentation with credentials\n- NEW 3.1 Live Status Monitoring *(networkening version only)*\n- NEW 3.1 Real time ping/status indicators on nodes *(networkening version only)*\n- NEW 3.1 Visual online/offline/checking indicators *(networkening version only)*\n\n##### Version 3.0 /\\ 12/1/25\n- NEW 3.0 Total mobile rewrite for core the-one-file.html\n- NEW 3.0 Total css rewrite for core the-one-file.html\n- NEW 3.0 : Ping/Live status for nodes added to theonefile-networkening.html\n- Click here for a full online demo of the theonefile-networkening.html\n- [Click here for a full online demo of the theonefile-networkening-corporate-demo.html](https://gelatinescreams.github.io/The-One-File/demos/theonefile-networkening-corporate-demo.html)\n- [Click here for a full online demo of the theonefile-networkening-homelab-demo.html](https://gelatinescreams.github.io/The-One-File/demos/theonefile-networkening-homelab-demo.html)\n- NEW 2.8 : Icon SEARCH with live preview\n- Seperate online features version to include icons - OPTIONAL\n- Use theonefile-networkening.html for this version\n\n\n##### Version 2.8 /\\ 11/29/25 The One File: The Networkening update!\n- Totally collapsible mobile interface\n- Mobile core added for future features\n- NEW 2.8 : Icon SEARCH with live preview\n- Seperate online features version to include web incons (OPTIONAL)\n- Use theonefile-networkening.html for this version\n- Icon support added (MDI, Simple Icons, Selfh.st)\n\n##### Version 2.5 /\\ 11/25/25 The One File: The Networkening update!\n- NEW 2.5 : Icon SEARCH with live preview"
  },
  {
    "path": "demos/csv-exports/the-one-file-corporate.csv",
    "content": "#THEONEFILE_CONFIG:{\"nodeData\":{\"core-router-1\":{\"shape\":\"router\",\"name\":\"Core Router 1\",\"ip\":\"10.0.0.1\",\"role\":\"Core Routing\",\"tags\":[\"core\",\"tier-1\",\"redundant\"],\"notes\":[\"Primary core router\",\"BGP peering enabled\"],\"mac\":\"00:1A:2B:3C:4D:01\",\"rackUnit\":\"\",\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"core-router-2\":{\"shape\":\"router\",\"name\":\"Core Router 2\",\"ip\":\"10.0.0.2\",\"role\":\"Core Routing\",\"tags\":[\"core\",\"tier-1\",\"redundant\"],\"notes\":[\"Secondary core router\",\"HSRP standby\"],\"mac\":\"00:1A:2B:3C:4D:02\",\"rackUnit\":\"\",\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null,\"ping\":{\"enabled\":true,\"protocol\":\"custom\",\"customUrl\":\"https://google.com\",\"timeout\":3000,\"status\":\"online\",\"lastCheck\":\"2025-12-09T00:15:04.343Z\"}},\"fw-external-1\":{\"shape\":\"firewall\",\"name\":\"External FW 1\",\"ip\":\"10.0.1.1\",\"role\":\"Perimeter Security\",\"tags\":[\"security\",\"perimeter\",\"ha-pair\"],\"notes\":[\"Palo Alto PA-5250\",\"Active node\"],\"mac\":\"00:1A:2B:3C:4D:10\",\"rackUnit\":\"\",\"uHeight\":\"2\",\"layer\":\"security\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"fw-external-2\":{\"shape\":\"firewall\",\"name\":\"External FW 2\",\"ip\":\"10.0.1.2\",\"role\":\"Perimeter Security\",\"tags\":[\"security\",\"perimeter\",\"ha-pair\"],\"notes\":[\"Palo Alto PA-5250\",\"Passive node\"],\"mac\":\"00:1A:2B:3C:4D:11\",\"rackUnit\":\"\",\"uHeight\":\"2\",\"layer\":\"security\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"fw-internal\":{\"shape\":\"firewall\",\"name\":\"Internal FW\",\"ip\":\"10.0.2.1\",\"role\":\"Internal Segmentation\",\"tags\":[\"security\",\"internal\"],\"notes\":[\"East-West traffic inspection\"],\"mac\":\"00:1A:2B:3C:4D:12\",\"rackUnit\":\"\",\"uHeight\":\"2\",\"layer\":\"security\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"core-switch-1\":{\"shape\":\"switch\",\"name\":\"Core Switch 1\",\"ip\":\"10.0.10.1\",\"role\":\"Core Switching\",\"tags\":[\"core\",\"layer3\",\"redundant\"],\"notes\":[\"Cisco Nexus 9000\",\"VPC Domain 1\"],\"mac\":\"00:1A:2B:3C:4D:20\",\"rackUnit\":\"\",\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"core-switch-2\":{\"shape\":\"switch\",\"name\":\"Core Switch 2\",\"ip\":\"10.0.10.2\",\"role\":\"Core Switching\",\"tags\":[\"core\",\"layer3\",\"redundant\"],\"notes\":[\"Cisco Nexus 9000\",\"VPC Domain 1\"],\"mac\":\"00:1A:2B:3C:4D:21\",\"rackUnit\":\"\",\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"dc-rack-a1\":{\"shape\":\"server\",\"name\":\"DC Rack A1\",\"ip\":\"10.10.0.0/24\",\"role\":\"Data Center Rack\",\"tags\":[\"datacenter\",\"row-a\",\"production\"],\"notes\":[\"Row A, Position 1\",\"Primary compute\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":true,\"locked\":false,\"groupId\":null},\"dc-rack-a2\":{\"shape\":\"server\",\"name\":\"DC Rack A2\",\"ip\":\"10.10.1.0/24\",\"role\":\"Data Center Rack\",\"tags\":[\"datacenter\",\"row-a\",\"production\"],\"notes\":[\"Row A, Position 2\",\"Primary compute\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":true,\"locked\":false,\"groupId\":null},\"dc-rack-b1\":{\"shape\":\"server\",\"name\":\"DC Rack B1\",\"ip\":\"10.10.2.0/24\",\"role\":\"Data Center Rack\",\"tags\":[\"datacenter\",\"row-b\",\"storage\"],\"notes\":[\"Row B, Position 1\",\"Storage systems\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":true,\"locked\":false,\"groupId\":null},\"dc-rack-b2\":{\"shape\":\"server\",\"name\":\"DC Rack B2\",\"ip\":\"10.10.3.0/24\",\"role\":\"Data Center Rack\",\"tags\":[\"datacenter\",\"row-b\",\"storage\"],\"notes\":[\"Row B, Position 2\",\"Storage systems\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":true,\"locked\":false,\"groupId\":null},\"dmz-rack\":{\"shape\":\"server\",\"name\":\"DMZ Rack\",\"ip\":\"172.16.0.0/24\",\"role\":\"DMZ Infrastructure\",\"tags\":[\"dmz\",\"security\",\"public-facing\",{\"type\":\"icon\",\"library\":\"selfhst\",\"name\":\"booklogr\"},{\"type\":\"icon\",\"library\":\"simple\",\"name\":\"gmail\"}],\"notes\":[\"Isolated DMZ zone\",\"Public-facing services\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"security\",\"assignedRack\":\"\",\"rackCapacity\":\"24\",\"isRack\":true,\"locked\":false,\"groupId\":null},\"mgmt-rack\":{\"shape\":\"server\",\"name\":\"Management Rack\",\"ip\":\"192.168.100.0/24\",\"role\":\"Management Infrastructure\",\"tags\":[\"management\",\"oob\",\"noc\"],\"notes\":[\"Out-of-band management\",\"NOC equipment\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"logical\",\"assignedRack\":\"\",\"rackCapacity\":\"24\",\"isRack\":true,\"locked\":false,\"groupId\":null},\"esxi-host-01\":{\"shape\":\"server\",\"name\":\"ESXi Host 01\",\"ip\":\"10.10.0.11\",\"role\":\"Hypervisor\",\"tags\":[\"vmware\",\"compute\",\"cluster-a\"],\"notes\":[\"Dell PowerEdge R750\",\"512GB RAM\",\"vSphere 8.0\"],\"mac\":\"00:50:56:AA:01:01\",\"rackUnit\":38,\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a1\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"esxi-host-02\":{\"shape\":\"server\",\"name\":\"ESXi Host 02\",\"ip\":\"10.10.0.12\",\"role\":\"Hypervisor\",\"tags\":[\"vmware\",\"compute\",\"cluster-a\"],\"notes\":[\"Dell PowerEdge R750\",\"512GB RAM\",\"vSphere 8.0\"],\"mac\":\"00:50:56:AA:01:02\",\"rackUnit\":35,\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a1\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"esxi-host-03\":{\"shape\":\"server\",\"name\":\"ESXi Host 03\",\"ip\":\"10.10.0.13\",\"role\":\"Hypervisor\",\"tags\":[\"vmware\",\"compute\",\"cluster-a\"],\"notes\":[\"Dell PowerEdge R750\",\"512GB RAM\",\"vSphere 8.0\"],\"mac\":\"00:50:56:AA:01:03\",\"rackUnit\":32,\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a1\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"esxi-host-04\":{\"shape\":\"server\",\"name\":\"ESXi Host 04\",\"ip\":\"10.10.0.14\",\"role\":\"Hypervisor\",\"tags\":[\"vmware\",\"compute\",\"cluster-a\"],\"notes\":[\"Dell PowerEdge R750\",\"512GB RAM\",\"vSphere 8.0\"],\"mac\":\"00:50:56:AA:01:04\",\"rackUnit\":29,\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a1\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"tor-switch-a1\":{\"shape\":\"switch\",\"name\":\"ToR Switch A1\",\"ip\":\"10.10.0.1\",\"role\":\"Top of Rack\",\"tags\":[\"tor\",\"access\",\"rack-a1\"],\"notes\":[\"Cisco Nexus 93180YC-FX\",\"48x25G ports\"],\"mac\":\"00:1A:2B:3C:5D:01\",\"rackUnit\":42,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a1\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"esxi-host-05\":{\"shape\":\"server\",\"name\":\"ESXi Host 05\",\"ip\":\"10.10.1.11\",\"role\":\"Hypervisor\",\"tags\":[\"vmware\",\"compute\",\"cluster-b\"],\"notes\":[\"Dell PowerEdge R750\",\"768GB RAM\",\"vSphere 8.0\"],\"mac\":\"00:50:56:AA:02:01\",\"rackUnit\":38,\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"esxi-host-06\":{\"shape\":\"server\",\"name\":\"ESXi Host 06\",\"ip\":\"10.10.1.12\",\"role\":\"Hypervisor\",\"tags\":[\"vmware\",\"compute\",\"cluster-b\"],\"notes\":[\"Dell PowerEdge R750\",\"768GB RAM\",\"vSphere 8.0\"],\"mac\":\"00:50:56:AA:02:02\",\"rackUnit\":35,\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"esxi-host-07\":{\"shape\":\"server\",\"name\":\"ESXi Host 07\",\"ip\":\"10.10.1.13\",\"role\":\"Hypervisor\",\"tags\":[\"vmware\",\"compute\",\"cluster-b\"],\"notes\":[\"Dell PowerEdge R750\",\"768GB RAM\",\"vSphere 8.0\"],\"mac\":\"00:50:56:AA:02:03\",\"rackUnit\":32,\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"esxi-host-08\":{\"shape\":\"server\",\"name\":\"ESXi Host 08\",\"ip\":\"10.10.1.14\",\"role\":\"Hypervisor\",\"tags\":[\"vmware\",\"compute\",\"cluster-b\"],\"notes\":[\"Dell PowerEdge R750\",\"768GB RAM\",\"vSphere 8.0\"],\"mac\":\"00:50:56:AA:02:04\",\"rackUnit\":29,\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"tor-switch-a2\":{\"shape\":\"switch\",\"name\":\"ToR Switch A2\",\"ip\":\"10.10.1.1\",\"role\":\"Top of Rack\",\"tags\":[\"tor\",\"access\",\"rack-a2\"],\"notes\":[\"Cisco Nexus 93180YC-FX\",\"48x25G ports\"],\"mac\":\"00:1A:2B:3C:5D:02\",\"rackUnit\":42,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"san-primary\":{\"shape\":\"database\",\"name\":\"SAN Primary\",\"ip\":\"10.10.2.10\",\"role\":\"Primary Storage\",\"tags\":[\"storage\",\"san\",\"netapp\"],\"notes\":[\"NetApp AFF A400\",\"500TB Raw\",\"FC 32Gb\"],\"mac\":\"00:A0:98:AA:01:01\",\"rackUnit\":36,\"uHeight\":\"6\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-b1\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"san-secondary\":{\"shape\":\"database\",\"name\":\"SAN Secondary\",\"ip\":\"10.10.2.11\",\"role\":\"Secondary Storage\",\"tags\":[\"storage\",\"san\",\"netapp\"],\"notes\":[\"NetApp AFF A400\",\"500TB Raw\",\"FC 32Gb\"],\"mac\":\"00:A0:98:AA:01:02\",\"rackUnit\":28,\"uHeight\":\"6\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-b1\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"fc-switch-1\":{\"shape\":\"switch\",\"name\":\"FC Switch 1\",\"ip\":\"10.10.2.1\",\"role\":\"Fibre Channel\",\"tags\":[\"storage\",\"fc\",\"fabric-a\"],\"notes\":[\"Brocade G620\",\"Fabric A\"],\"mac\":\"00:1A:2B:FC:01:01\",\"rackUnit\":42,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-b1\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"fc-switch-2\":{\"shape\":\"switch\",\"name\":\"FC Switch 2\",\"ip\":\"10.10.2.2\",\"role\":\"Fibre Channel\",\"tags\":[\"storage\",\"fc\",\"fabric-b\"],\"notes\":[\"Brocade G620\",\"Fabric B\"],\"mac\":\"00:1A:2B:FC:01:02\",\"rackUnit\":41,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-b1\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"backup-server-1\":{\"shape\":\"server\",\"name\":\"Backup Server 1\",\"ip\":\"10.10.3.10\",\"role\":\"Backup Infrastructure\",\"tags\":[\"backup\",\"veeam\",\"protection\"],\"notes\":[\"Veeam Backup Server\",\"Dell R740xd\",\"200TB\"],\"mac\":\"00:50:56:BB:01:01\",\"rackUnit\":36,\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-b2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"backup-server-2\":{\"shape\":\"server\",\"name\":\"Backup Server 2\",\"ip\":\"10.10.3.11\",\"role\":\"Backup Infrastructure\",\"tags\":[\"backup\",\"veeam\",\"protection\"],\"notes\":[\"Veeam Backup Server\",\"Dell R740xd\",\"200TB\"],\"mac\":\"00:50:56:BB:01:02\",\"rackUnit\":33,\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-b2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"tape-library\":{\"shape\":\"database\",\"name\":\"Tape Library\",\"ip\":\"10.10.3.20\",\"role\":\"Archival Storage\",\"tags\":[\"backup\",\"tape\",\"lto9\"],\"notes\":[\"IBM TS4500\",\"LTO-9\",\"Long-term archive\"],\"mac\":\"00:50:56:BB:02:01\",\"rackUnit\":20,\"uHeight\":\"10\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-b2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"tor-switch-b1\":{\"shape\":\"switch\",\"name\":\"ToR Switch B1\",\"ip\":\"10.10.2.3\",\"role\":\"Top of Rack\",\"tags\":[\"tor\",\"access\",\"rack-b1\"],\"notes\":[\"Cisco Nexus 93180YC-FX\"],\"mac\":\"00:1A:2B:3C:5D:03\",\"rackUnit\":40,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-b1\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"tor-switch-b2\":{\"shape\":\"switch\",\"name\":\"ToR Switch B2\",\"ip\":\"10.10.3.1\",\"role\":\"Top of Rack\",\"tags\":[\"tor\",\"access\",\"rack-b2\"],\"notes\":[\"Cisco Nexus 93180YC-FX\"],\"mac\":\"00:1A:2B:3C:5D:04\",\"rackUnit\":42,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-b2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"web-server-1\":{\"shape\":\"server\",\"name\":\"Web Server 1\",\"ip\":\"172.16.0.11\",\"role\":\"Web Frontend\",\"tags\":[\"dmz\",\"web\",\"nginx\"],\"notes\":[\"NGINX reverse proxy\",\"Public facing\"],\"mac\":\"00:50:56:CC:01:01\",\"rackUnit\":20,\"uHeight\":\"1\",\"layer\":\"security\",\"assignedRack\":\"dmz-rack\",\"rackCapacity\":\"24\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"web-server-2\":{\"shape\":\"server\",\"name\":\"Web Server 2\",\"ip\":\"172.16.0.12\",\"role\":\"Web Frontend\",\"tags\":[\"dmz\",\"web\",\"nginx\"],\"notes\":[\"NGINX reverse proxy\",\"Public facing\"],\"mac\":\"00:50:56:CC:01:02\",\"rackUnit\":18,\"uHeight\":\"1\",\"layer\":\"security\",\"assignedRack\":\"dmz-rack\",\"rackCapacity\":\"24\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"waf-1\":{\"shape\":\"firewall\",\"name\":\"WAF Appliance\",\"ip\":\"172.16.0.5\",\"role\":\"Web Application Firewall\",\"tags\":[\"dmz\",\"security\",\"waf\"],\"notes\":[\"F5 BIG-IP ASM\",\"OWASP protection\"],\"mac\":\"00:50:56:CC:02:01\",\"rackUnit\":22,\"uHeight\":\"2\",\"layer\":\"security\",\"assignedRack\":\"dmz-rack\",\"rackCapacity\":\"24\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"load-balancer-dmz\":{\"shape\":\"switch\",\"name\":\"DMZ Load Balancer\",\"ip\":\"172.16.0.3\",\"role\":\"Load Balancing\",\"tags\":[\"dmz\",\"lb\",\"f5\"],\"notes\":[\"F5 BIG-IP LTM\",\"VIP: 172.16.0.100\"],\"mac\":\"00:50:56:CC:03:01\",\"rackUnit\":16,\"uHeight\":\"2\",\"layer\":\"security\",\"assignedRack\":\"dmz-rack\",\"rackCapacity\":\"24\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"mail-gateway\":{\"shape\":\"server\",\"name\":\"Mail Gateway\",\"ip\":\"172.16.0.25\",\"role\":\"Email Security\",\"tags\":[\"dmz\",\"email\",\"security\"],\"notes\":[\"Proofpoint Email Gateway\",\"Spam/malware filtering\"],\"mac\":\"00:50:56:CC:04:01\",\"rackUnit\":14,\"uHeight\":\"1\",\"layer\":\"security\",\"assignedRack\":\"dmz-rack\",\"rackCapacity\":\"24\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"dns-external-1\":{\"shape\":\"circle\",\"name\":\"External DNS 1\",\"ip\":\"172.16.0.53\",\"role\":\"External DNS\",\"tags\":[\"dmz\",\"dns\",\"public\"],\"notes\":[\"BIND DNS\",\"Authoritative for corp.com\"],\"mac\":\"00:50:56:CC:05:01\",\"rackUnit\":12,\"uHeight\":\"1\",\"layer\":\"security\",\"assignedRack\":\"dmz-rack\",\"rackCapacity\":\"24\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"dns-external-2\":{\"shape\":\"circle\",\"name\":\"External DNS 2\",\"ip\":\"172.16.0.54\",\"role\":\"External DNS\",\"tags\":[\"dmz\",\"dns\",\"public\"],\"notes\":[\"BIND DNS\",\"Secondary for corp.com\"],\"mac\":\"00:50:56:CC:05:02\",\"rackUnit\":10,\"uHeight\":\"1\",\"layer\":\"security\",\"assignedRack\":\"dmz-rack\",\"rackCapacity\":\"24\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"vcenter\":{\"shape\":\"server\",\"name\":\"vCenter Server\",\"ip\":\"192.168.100.10\",\"role\":\"Virtualization Management\",\"tags\":[\"management\",\"vmware\",\"vcsa\"],\"notes\":[\"vCenter Server Appliance 8.0\",\"Single SSO domain\"],\"mac\":\"00:50:56:DD:01:01\",\"rackUnit\":20,\"uHeight\":\"2\",\"layer\":\"logical\",\"assignedRack\":\"mgmt-rack\",\"rackCapacity\":\"24\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"nsx-manager\":{\"shape\":\"server\",\"name\":\"NSX Manager\",\"ip\":\"192.168.100.15\",\"role\":\"Network Virtualization\",\"tags\":[\"management\",\"vmware\",\"nsx\"],\"notes\":[\"NSX-T 4.1 Manager Cluster\"],\"mac\":\"00:50:56:DD:02:01\",\"rackUnit\":17,\"uHeight\":\"2\",\"layer\":\"logical\",\"assignedRack\":\"mgmt-rack\",\"rackCapacity\":\"24\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"siem-server\":{\"shape\":\"server\",\"name\":\"SIEM Server\",\"ip\":\"192.168.100.50\",\"role\":\"Security Monitoring\",\"tags\":[\"management\",\"security\",\"splunk\"],\"notes\":[\"Splunk Enterprise\",\"Security monitoring\"],\"mac\":\"00:50:56:DD:03:01\",\"rackUnit\":14,\"uHeight\":\"2\",\"layer\":\"logical\",\"assignedRack\":\"mgmt-rack\",\"rackCapacity\":\"24\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"nms-server\":{\"shape\":\"server\",\"name\":\"Network Monitoring\",\"ip\":\"192.168.100.60\",\"role\":\"Network Management\",\"tags\":[\"management\",\"monitoring\",\"prtg\"],\"notes\":[\"PRTG Network Monitor\",\"5000 sensors\"],\"mac\":\"00:50:56:DD:04:01\",\"rackUnit\":11,\"uHeight\":\"1\",\"layer\":\"logical\",\"assignedRack\":\"mgmt-rack\",\"rackCapacity\":\"24\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"jump-server\":{\"shape\":\"server\",\"name\":\"Jump Server\",\"ip\":\"192.168.100.100\",\"role\":\"Bastion Host\",\"tags\":[\"management\",\"security\",\"bastion\"],\"notes\":[\"Windows Server 2022\",\"MFA enabled\"],\"mac\":\"00:50:56:DD:05:01\",\"rackUnit\":9,\"uHeight\":\"1\",\"layer\":\"logical\",\"assignedRack\":\"mgmt-rack\",\"rackCapacity\":\"24\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"ipam-server\":{\"shape\":\"server\",\"name\":\"IPAM/DDI\",\"ip\":\"192.168.100.70\",\"role\":\"IP Management\",\"tags\":[\"management\",\"dns\",\"dhcp\"],\"notes\":[\"Infoblox DDI\",\"DNS/DHCP/IPAM\"],\"mac\":\"00:50:56:DD:06:01\",\"rackUnit\":7,\"uHeight\":\"2\",\"layer\":\"logical\",\"assignedRack\":\"mgmt-rack\",\"rackCapacity\":\"24\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"wlc-primary\":{\"shape\":\"wifi\",\"name\":\"WLC Primary\",\"ip\":\"10.20.0.1\",\"role\":\"Wireless Controller\",\"tags\":[\"wireless\",\"cisco\",\"9800\"],\"notes\":[\"Cisco C9800-40\",\"Primary controller\"],\"mac\":\"00:1A:2B:WL:01:01\",\"rackUnit\":\"\",\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"wlc-secondary\":{\"shape\":\"wifi\",\"name\":\"WLC Secondary\",\"ip\":\"10.20.0.2\",\"role\":\"Wireless Controller\",\"tags\":[\"wireless\",\"cisco\",\"9800\"],\"notes\":[\"Cisco C9800-40\",\"HA Secondary\"],\"mac\":\"00:1A:2B:WL:01:02\",\"rackUnit\":\"\",\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"mobile-zone-hq\":{\"shape\":\"phone\",\"name\":\"HQ Mobile Zone\",\"ip\":\"10.20.10.0/24\",\"role\":\"Mobile Device Zone\",\"tags\":[\"wireless\",\"byod\",\"mobile\"],\"notes\":[\"Corporate BYOD\",\"MDM enrolled devices\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"mobile-zone-guest\":{\"shape\":\"phone\",\"name\":\"Guest WiFi Zone\",\"ip\":\"10.30.0.0/24\",\"role\":\"Guest Network\",\"tags\":[\"wireless\",\"guest\",\"isolated\"],\"notes\":[\"Captive portal\",\"Internet only\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"mobile-zone-iot\":{\"shape\":\"phone\",\"name\":\"IoT Device Zone\",\"ip\":\"10.40.0.0/24\",\"role\":\"IoT Network\",\"tags\":[\"wireless\",\"iot\",\"building\"],\"notes\":[\"Building automation\",\"Smart devices\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"branch-router-ny\":{\"shape\":\"router\",\"name\":\"NYC Branch Router\",\"ip\":\"10.100.0.1\",\"role\":\"Branch Gateway\",\"tags\":[\"branch\",\"nyc\",\"sd-wan\"],\"notes\":[\"Cisco Viptela vEdge\",\"SD-WAN enabled\"],\"mac\":\"00:1A:2B:BR:01:01\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"branch-router-la\":{\"shape\":\"router\",\"name\":\"LA Branch Router\",\"ip\":\"10.101.0.1\",\"role\":\"Branch Gateway\",\"tags\":[\"branch\",\"la\",\"sd-wan\"],\"notes\":[\"Cisco Viptela vEdge\",\"SD-WAN enabled\"],\"mac\":\"00:1A:2B:BR:02:01\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"branch-router-chi\":{\"shape\":\"router\",\"name\":\"Chicago Branch Router\",\"ip\":\"10.102.0.1\",\"role\":\"Branch Gateway\",\"tags\":[\"branch\",\"chicago\",\"sd-wan\"],\"notes\":[\"Cisco Viptela vEdge\",\"SD-WAN enabled\"],\"mac\":\"00:1A:2B:BR:03:01\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"branch-router-lon\":{\"shape\":\"router\",\"name\":\"London Branch Router\",\"ip\":\"10.200.0.1\",\"role\":\"Branch Gateway\",\"tags\":[\"branch\",\"london\",\"sd-wan\"],\"notes\":[\"Cisco Viptela vEdge\",\"EMEA region\"],\"mac\":\"00:1A:2B:BR:04:01\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"branch-router-tokyo\":{\"shape\":\"router\",\"name\":\"Tokyo Branch Router\",\"ip\":\"10.201.0.1\",\"role\":\"Branch Gateway\",\"tags\":[\"branch\",\"tokyo\",\"sd-wan\"],\"notes\":[\"Cisco Viptela vEdge\",\"APAC region\"],\"mac\":\"00:1A:2B:BR:05:01\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"cloud-aws\":{\"shape\":\"cloud\",\"name\":\"AWS Cloud\",\"ip\":\"vpc-0a1b2c3d\",\"role\":\"Public Cloud\",\"tags\":[\"cloud\",\"aws\",\"hybrid\"],\"notes\":[\"AWS US-East-1\",\"VPC peering to HQ\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"logical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"cloud-azure\":{\"shape\":\"cloud\",\"name\":\"Azure Cloud\",\"ip\":\"vnet-corp-prod\",\"role\":\"Public Cloud\",\"tags\":[\"cloud\",\"azure\",\"hybrid\"],\"notes\":[\"Azure East US 2\",\"ExpressRoute\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"logical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"cloud-gcp\":{\"shape\":\"cloud\",\"name\":\"GCP Cloud\",\"ip\":\"vpc-gcp-corp\",\"role\":\"Public Cloud\",\"tags\":[\"cloud\",\"gcp\",\"dev\"],\"notes\":[\"GCP us-central1\",\"Dev/Test workloads\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"logical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"isp-primary\":{\"shape\":\"globe\",\"name\":\"ISP Primary\",\"ip\":\"203.0.113.1\",\"role\":\"Internet Uplink\",\"tags\":[\"wan\",\"internet\",\"primary\"],\"notes\":[\"AT&T MPLS\",\"1 Gbps dedicated\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"isp-secondary\":{\"shape\":\"vm\",\"name\":\"ISP Secondary\",\"ip\":\"198.51.100.1\",\"role\":\"Internet Uplink\",\"tags\":[\"wan\",\"internet\",\"backup\"],\"notes\":[\"Verizon Business\",\"500 Mbps backup\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null,\"rotation\":-17},\"dc-internal-1\":{\"shape\":\"circle\",\"name\":\"DC1 Int DNS\",\"ip\":\"10.10.0.53\",\"role\":\"Internal DNS/AD\",\"tags\":[\"dns\",\"ad\",\"dc1\"],\"notes\":[\"Windows Server 2022\",\"Primary DC\"],\"mac\":\"00:50:56:AD:01:01\",\"rackUnit\":26,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a1\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"dc-internal-2\":{\"shape\":\"circle\",\"name\":\"DC2 Int DNS\",\"ip\":\"10.10.1.53\",\"role\":\"Internal DNS/AD\",\"tags\":[\"dns\",\"ad\",\"dc2\"],\"notes\":[\"Windows Server 2022\",\"Secondary DC\"],\"mac\":\"00:50:56:AD:01:02\",\"rackUnit\":26,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"app-server-1\":{\"shape\":\"server\",\"name\":\"App Server 01\",\"ip\":\"10.10.0.101\",\"role\":\"Application\",\"tags\":[\"app\",\"iis\",\"web\"],\"notes\":[\"Windows Server 2022\",\"IIS Application\"],\"mac\":\"00:50:56:AP:01:01\",\"rackUnit\":24,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a1\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"app-server-2\":{\"shape\":\"server\",\"name\":\"App Server 02\",\"ip\":\"10.10.0.102\",\"role\":\"Application\",\"tags\":[\"app\",\"iis\",\"web\"],\"notes\":[\"Windows Server 2022\",\"IIS Application\"],\"mac\":\"00:50:56:AP:01:02\",\"rackUnit\":22,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a1\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"db-server-1\":{\"shape\":\"database\",\"name\":\"SQL Server 01\",\"ip\":\"10.10.0.201\",\"role\":\"Database\",\"tags\":[\"db\",\"sql\",\"primary\"],\"notes\":[\"SQL Server 2022 Enterprise\",\"AlwaysOn Primary\"],\"mac\":\"00:50:56:DB:01:01\",\"rackUnit\":20,\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a1\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"db-server-2\":{\"shape\":\"database\",\"name\":\"SQL Server 02\",\"ip\":\"10.10.1.201\",\"role\":\"Database\",\"tags\":[\"db\",\"sql\",\"secondary\"],\"notes\":[\"SQL Server 2022 Enterprise\",\"AlwaysOn Secondary\"],\"mac\":\"00:50:56:DB:01:02\",\"rackUnit\":24,\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"k8s-master-1\":{\"shape\":\"hexagon\",\"name\":\"K8s Master 1\",\"ip\":\"10.10.1.50\",\"role\":\"Container Orchestration\",\"tags\":[\"kubernetes\",\"master\",\"container\"],\"notes\":[\"K8s Control Plane\",\"etcd member\"],\"mac\":\"00:50:56:K8:01:01\",\"rackUnit\":21,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"k8s-master-2\":{\"shape\":\"hexagon\",\"name\":\"K8s Master 2\",\"ip\":\"10.10.1.51\",\"role\":\"Container Orchestration\",\"tags\":[\"kubernetes\",\"master\",\"container\"],\"notes\":[\"K8s Control Plane\",\"etcd member\"],\"mac\":\"00:50:56:K8:01:02\",\"rackUnit\":19,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"k8s-master-3\":{\"shape\":\"hexagon\",\"name\":\"K8s Master 3\",\"ip\":\"10.10.1.52\",\"role\":\"Container Orchestration\",\"tags\":[\"kubernetes\",\"master\",\"container\"],\"notes\":[\"K8s Control Plane\",\"etcd member\"],\"mac\":\"00:50:56:K8:01:03\",\"rackUnit\":17,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"k8s-worker-1\":{\"shape\":\"server\",\"name\":\"K8s Worker 1\",\"ip\":\"10.10.1.60\",\"role\":\"Container Workload\",\"tags\":[\"kubernetes\",\"worker\",\"container\"],\"notes\":[\"K8s Worker Node\",\"64GB RAM\"],\"mac\":\"00:50:56:K8:02:01\",\"rackUnit\":15,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"k8s-worker-2\":{\"shape\":\"server\",\"name\":\"K8s Worker 2\",\"ip\":\"10.10.1.61\",\"role\":\"Container Workload\",\"tags\":[\"kubernetes\",\"worker\",\"container\"],\"notes\":[\"K8s Worker Node\",\"64GB RAM\"],\"mac\":\"00:50:56:K8:02:02\",\"rackUnit\":13,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"k8s-worker-3\":{\"shape\":\"server\",\"name\":\"K8s Worker 3\",\"ip\":\"10.10.1.62\",\"role\":\"Container Workload\",\"tags\":[\"kubernetes\",\"worker\",\"container\"],\"notes\":[\"K8s Worker Node\",\"64GB RAM\"],\"mac\":\"00:50:56:K8:02:03\",\"rackUnit\":11,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"k8s-worker-4\":{\"shape\":\"server\",\"name\":\"K8s Worker 4\",\"ip\":\"10.10.1.63\",\"role\":\"Container Workload\",\"tags\":[\"kubernetes\",\"worker\",\"container\"],\"notes\":[\"K8s Worker Node\",\"64GB RAM\"],\"mac\":\"00:50:56:K8:02:04\",\"rackUnit\":9,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"proxy-server-1\":{\"shape\":\"server\",\"name\":\"Proxy Server 1\",\"ip\":\"10.5.0.10\",\"role\":\"Web Proxy\",\"tags\":[\"proxy\",\"squid\",\"filtering\"],\"notes\":[\"Squid Proxy\",\"Content filtering\"],\"mac\":\"00:50:56:PX:01:01\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"security\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"proxy-server-2\":{\"shape\":\"server\",\"name\":\"Proxy Server 2\",\"ip\":\"10.5.0.11\",\"role\":\"Web Proxy\",\"tags\":[\"proxy\",\"squid\",\"filtering\"],\"notes\":[\"Squid Proxy\",\"HA pair\"],\"mac\":\"00:50:56:PX:01:02\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"security\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"vpn-concentrator\":{\"shape\":\"firewall\",\"name\":\"VPN Concentrator\",\"ip\":\"10.0.5.1\",\"role\":\"Remote Access VPN\",\"tags\":[\"vpn\",\"remote\",\"security\"],\"notes\":[\"Cisco ASA 5555-X\",\"AnyConnect SSL VPN\"],\"mac\":\"00:1A:2B:VP:01:01\",\"rackUnit\":\"\",\"uHeight\":\"2\",\"layer\":\"security\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"nac-server\":{\"shape\":\"server\",\"name\":\"NAC Server\",\"ip\":\"10.5.5.10\",\"role\":\"Network Access Control\",\"tags\":[\"nac\",\"ise\",\"802.1x\"],\"notes\":[\"Cisco ISE 3.1\",\"RADIUS/TACACS+\"],\"mac\":\"00:50:56:NA:01:01\",\"rackUnit\":\"\",\"uHeight\":\"2\",\"layer\":\"security\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"print-server\":{\"shape\":\"server\",\"name\":\"Print Server\",\"ip\":\"10.10.0.150\",\"role\":\"Print Services\",\"tags\":[\"print\",\"windows\",\"services\"],\"notes\":[\"Windows Print Server\",\"50+ printers\"],\"mac\":\"00:50:56:PR:01:01\",\"rackUnit\":18,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a1\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"file-server\":{\"shape\":\"database\",\"name\":\"File Server\",\"ip\":\"10.10.0.160\",\"role\":\"File Services\",\"tags\":[\"file\",\"smb\",\"dfs\"],\"notes\":[\"Windows File Server\",\"DFS namespace\"],\"mac\":\"00:50:56:FS:01:01\",\"rackUnit\":16,\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a1\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"ca-server\":{\"shape\":\"server\",\"name\":\"Certificate Authority\",\"ip\":\"192.168.100.80\",\"role\":\"PKI Infrastructure\",\"tags\":[\"pki\",\"ca\",\"security\"],\"notes\":[\"Windows CA\",\"Enterprise Root CA\"],\"mac\":\"00:50:56:CA:01:01\",\"rackUnit\":5,\"uHeight\":\"1\",\"layer\":\"logical\",\"assignedRack\":\"mgmt-rack\",\"rackCapacity\":\"24\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"sccm-server\":{\"shape\":\"server\",\"name\":\"SCCM Server\",\"ip\":\"192.168.100.90\",\"role\":\"Endpoint Management\",\"tags\":[\"sccm\",\"patching\",\"software\"],\"notes\":[\"MECM Primary Site\",\"Software deployment\"],\"mac\":\"00:50:56:SC:01:01\",\"rackUnit\":3,\"uHeight\":\"2\",\"layer\":\"logical\",\"assignedRack\":\"mgmt-rack\",\"rackCapacity\":\"24\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"voip-cluster\":{\"shape\":\"phone\",\"name\":\"VoIP Cluster\",\"ip\":\"10.50.0.0/24\",\"role\":\"Voice Services\",\"tags\":[\"voip\",\"cisco\",\"ucm\"],\"notes\":[\"Cisco UCM Cluster\",\"3000 endpoints\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"application\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"video-conf\":{\"shape\":\"laptop\",\"name\":\"Video Conference\",\"ip\":\"10.51.0.0/24\",\"role\":\"Video Services\",\"tags\":[\"video\",\"webex\",\"teams\"],\"notes\":[\"Webex/Teams integration\",\"Meeting rooms\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"application\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"security-cameras\":{\"shape\":\"camera\",\"name\":\"Security Cameras\",\"ip\":\"10.60.0.0/24\",\"role\":\"Physical Security\",\"tags\":[\"cctv\",\"surveillance\",\"security\"],\"notes\":[\"150+ IP cameras\",\"30-day retention\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"nvr-cluster\":{\"shape\":\"server\",\"name\":\"NVR Cluster\",\"ip\":\"10.60.0.10\",\"role\":\"Video Recording\",\"tags\":[\"nvr\",\"surveillance\",\"storage\"],\"notes\":[\"Milestone XProtect\",\"500TB storage\"],\"mac\":\"00:50:56:NV:01:01\",\"rackUnit\":15,\"uHeight\":\"4\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-b2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"dev-server-1\":{\"shape\":\"server\",\"name\":\"Dev Server 1\",\"ip\":\"10.80.0.10\",\"role\":\"Development\",\"tags\":[\"dev\",\"gitlab\",\"ci-cd\",{\"type\":\"icon\",\"library\":\"selfhst\",\"name\":\"dokku\"}],\"notes\":[\"GitLab Server\",\"CI/CD pipelines\"],\"mac\":\"00:50:56:DV:01:01\",\"rackUnit\":\"\",\"uHeight\":\"2\",\"layer\":\"application\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"dev-server-2\":{\"shape\":\"server\",\"name\":\"Dev Server 2\",\"ip\":\"10.80.0.11\",\"role\":\"Development\",\"tags\":[\"dev\",\"jenkins\",\"ci-cd\"],\"notes\":[\"Jenkins Server\",\"Build automation\"],\"mac\":\"00:50:56:DV:01:02\",\"rackUnit\":\"\",\"uHeight\":\"2\",\"layer\":\"application\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"test-environment\":{\"shape\":\"shield\",\"name\":\"Test Environment\",\"ip\":\"10.81.0.0/24\",\"role\":\"QA/Testing\",\"tags\":[\"test\",\"qa\",\"staging\"],\"notes\":[\"Staging environment\",\"Pre-prod validation\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"application\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null,\"rotation\":-36},\"erp-system\":{\"shape\":\"database\",\"name\":\"ERP System\",\"ip\":\"10.90.0.10\",\"role\":\"Business Application\",\"tags\":[\"erp\",\"sap\",\"business\"],\"notes\":[\"SAP S/4HANA\",\"Financial/HR systems\"],\"mac\":\"00:50:56:ER:01:01\",\"rackUnit\":\"\",\"uHeight\":\"4\",\"layer\":\"application\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"crm-system\":{\"shape\":\"database\",\"name\":\"CRM System\",\"ip\":\"10.91.0.10\",\"role\":\"Business Application\",\"tags\":[\"crm\",\"salesforce\",\"business\"],\"notes\":[\"Salesforce integration\",\"Sales/Marketing\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"application\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"endpoint-1000\":{\"shape\":\"laptop\",\"name\":\"Corporate Endpoints\",\"ip\":\"10.70.0.0/22\",\"role\":\"User Workstations\",\"tags\":[\"endpoints\",\"workstations\",\"users\"],\"notes\":[\"~1000 corporate laptops\",\"Windows 11\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"dist-switch-floor1\":{\"shape\":\"switch\",\"name\":\"Floor 1 Switch\",\"ip\":\"10.1.1.1\",\"role\":\"Distribution\",\"tags\":[\"distribution\",\"floor-1\",\"access\"],\"notes\":[\"Cisco C9300-48P\",\"PoE+ enabled\"],\"mac\":\"00:1A:2B:FL:01:01\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"dist-switch-floor2\":{\"shape\":\"switch\",\"name\":\"Floor 2 Switch\",\"ip\":\"10.1.2.1\",\"role\":\"Distribution\",\"tags\":[\"distribution\",\"floor-2\",\"access\"],\"notes\":[\"Cisco C9300-48P\",\"PoE+ enabled\"],\"mac\":\"00:1A:2B:FL:02:01\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"dist-switch-floor3\":{\"shape\":\"switch\",\"name\":\"Floor 3 Switch\",\"ip\":\"10.1.3.1\",\"role\":\"Distribution\",\"tags\":[\"distribution\",\"floor-3\",\"access\"],\"notes\":[\"Cisco C9300-48P\",\"PoE+ enabled\"],\"mac\":\"00:1A:2B:FL:03:01\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"dist-switch-floor4\":{\"shape\":\"switch\",\"name\":\"Floor 4 Switch\",\"ip\":\"10.1.4.1\",\"role\":\"Distribution\",\"tags\":[\"distribution\",\"floor-4\",\"access\"],\"notes\":[\"Cisco C9300-48P\",\"PoE+ enabled\"],\"mac\":\"00:1A:2B:FL:04:01\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"ap-floor1-zone1\":{\"shape\":\"wifi\",\"name\":\"AP Floor 1 Zone 1\",\"ip\":\"10.20.1.10\",\"role\":\"Wireless Access\",\"tags\":[\"wifi\",\"ap\",\"floor-1\"],\"notes\":[\"Cisco 9120AX\",\"Wi-Fi 6\"],\"mac\":\"00:1A:2B:AP:01:01\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"ap-floor2-zone1\":{\"shape\":\"wifi\",\"name\":\"AP Floor 2 Zone 1\",\"ip\":\"10.20.2.10\",\"role\":\"Wireless Access\",\"tags\":[\"wifi\",\"ap\",\"floor-2\"],\"notes\":[\"Cisco 9120AX\",\"Wi-Fi 6\"],\"mac\":\"00:1A:2B:AP:02:01\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"ap-floor3-zone1\":{\"shape\":\"wifi\",\"name\":\"AP Floor 3 Zone 1\",\"ip\":\"10.20.3.10\",\"role\":\"Wireless Access\",\"tags\":[\"wifi\",\"ap\",\"floor-3\"],\"notes\":[\"Cisco 9120AX\",\"Wi-Fi 6\"],\"mac\":\"00:1A:2B:AP:03:01\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"ap-floor4-zone1\":{\"shape\":\"wifi\",\"name\":\"AP Floor 4 Zone 1\",\"ip\":\"10.20.4.10\",\"role\":\"Wireless Access\",\"tags\":[\"wifi\",\"ap\",\"floor-4\"],\"notes\":[\"Cisco 9120AX\",\"Wi-Fi 6\"],\"mac\":\"00:1A:2B:AP:04:01\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"ups-dc-1\":{\"shape\":\"rectangle\",\"name\":\"UPS DC-1\",\"ip\":\"192.168.200.10\",\"role\":\"Power Management\",\"tags\":[\"power\",\"ups\",\"datacenter\"],\"notes\":[\"APC Symmetra\",\"80kVA\",\"30 min runtime\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"ups-dc-2\":{\"shape\":\"rectangle\",\"name\":\"UPS DC-2\",\"ip\":\"192.168.200.11\",\"role\":\"Power Management\",\"tags\":[\"power\",\"ups\",\"datacenter\"],\"notes\":[\"APC Symmetra\",\"80kVA\",\"Redundant\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"pdu-rack-a1\":{\"shape\":\"rectangle\",\"name\":\"PDU Rack A1\",\"ip\":\"192.168.200.21\",\"role\":\"Power Distribution\",\"tags\":[\"power\",\"pdu\",\"rack-a1\"],\"notes\":[\"APC Switched PDU\",\"Per-outlet metering\"],\"mac\":\"\",\"rackUnit\":1,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a1\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"pdu-rack-a2\":{\"shape\":\"rectangle\",\"name\":\"PDU Rack A2\",\"ip\":\"192.168.200.22\",\"role\":\"Power Distribution\",\"tags\":[\"power\",\"pdu\",\"rack-a2\"],\"notes\":[\"APC Switched PDU\",\"Per-outlet metering\"],\"mac\":\"\",\"rackUnit\":1,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"cooling-1\":{\"shape\":\"rectangle\",\"name\":\"CRAC Unit 1\",\"ip\":\"192.168.200.30\",\"role\":\"Cooling\",\"tags\":[\"cooling\",\"hvac\",\"datacenter\"],\"notes\":[\"Liebert CRV\",\"Row-based cooling\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"cooling-2\":{\"shape\":\"rectangle\",\"name\":\"CRAC Unit 2\",\"ip\":\"192.168.200.31\",\"role\":\"Cooling\",\"tags\":[\"cooling\",\"hvac\",\"datacenter\"],\"notes\":[\"Liebert CRV\",\"N+1 redundancy\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"camera-a\":{\"shape\":\"camera\",\"name\":\"camera A\",\"ip\":\"0.0.0.0\",\"role\":\"\",\"tags\":[],\"notes\":[],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"ping\":{\"enabled\":false,\"protocol\":\"http\",\"customUrl\":\"\",\"timeout\":3000,\"status\":\"unknown\",\"lastCheck\":null},\"locked\":false,\"groupId\":null,\"fovEnabled\":true,\"fovRotation\":104,\"fovDistance\":500,\"fovSweep\":60,\"fovSpeed\":10,\"fovAnimate\":true},\"camera-a-copy\":{\"shape\":\"camera\",\"name\":\"camera B\",\"ip\":\"0.0.0.0\",\"role\":\"\",\"tags\":[],\"notes\":[],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"ping\":{\"enabled\":false,\"protocol\":\"http\",\"customUrl\":\"\",\"timeout\":3000,\"status\":\"unknown\",\"lastCheck\":null},\"locked\":false,\"groupId\":null,\"fovEnabled\":true,\"fovRotation\":162,\"fovDistance\":500,\"fovSweep\":60,\"fovSpeed\":10,\"fovAnimate\":false}},\"edgeData\":{\"list\":[{\"id\":\"isp1-router1\",\"from\":\"isp-primary\",\"to\":\"core-router-1\",\"width\":6,\"color\":\"#10b981\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"Primary WAN link\"],\"fromPort\":\"Gi0/0\",\"toPort\":\"Gi1/0/1\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"isp2-router2\",\"from\":\"isp-secondary\",\"to\":\"core-router-2\",\"width\":6,\"color\":\"#10b981\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"Backup WAN link\"],\"fromPort\":\"Gi0/0\",\"toPort\":\"Gi1/0/1\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"router1-router2\",\"from\":\"core-router-1\",\"to\":\"core-router-2\",\"width\":4,\"color\":\"#f59e0b\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"HSRP Peering\"],\"fromPort\":\"Gi1/0/24\",\"toPort\":\"Gi1/0/24\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"router1-fw1\",\"from\":\"core-router-1\",\"to\":\"fw-external-1\",\"width\":4,\"color\":\"#ef4444\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"router2-fw2\",\"from\":\"core-router-2\",\"to\":\"fw-external-2\",\"width\":4,\"color\":\"#ef4444\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"fw1-fw2\",\"from\":\"fw-external-1\",\"to\":\"fw-external-2\",\"width\":3,\"color\":\"#f59e0b\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"HA heartbeat\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"dashed\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"fw1-coresw1\",\"from\":\"fw-external-1\",\"to\":\"core-switch-1\",\"width\":4,\"color\":\"#475569\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"fw2-coresw2\",\"from\":\"fw-external-2\",\"to\":\"core-switch-2\",\"width\":4,\"color\":\"#475569\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw1-coresw2\",\"from\":\"core-switch-1\",\"to\":\"core-switch-2\",\"width\":5,\"color\":\"#3b82f6\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"VPC peer-link\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw1-fwint\",\"from\":\"core-switch-1\",\"to\":\"fw-internal\",\"width\":3,\"color\":\"#ef4444\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw2-fwint\",\"from\":\"core-switch-2\",\"to\":\"fw-internal\",\"width\":3,\"color\":\"#ef4444\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw1-racka1\",\"from\":\"core-switch-1\",\"to\":\"dc-rack-a1\",\"width\":4,\"color\":\"#475569\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw2-racka1\",\"from\":\"core-switch-2\",\"to\":\"dc-rack-a1\",\"width\":4,\"color\":\"#475569\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw1-racka2\",\"from\":\"core-switch-1\",\"to\":\"dc-rack-a2\",\"width\":4,\"color\":\"#475569\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw2-racka2\",\"from\":\"core-switch-2\",\"to\":\"dc-rack-a2\",\"width\":4,\"color\":\"#475569\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw1-rackb1\",\"from\":\"core-switch-1\",\"to\":\"dc-rack-b1\",\"width\":4,\"color\":\"#475569\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw2-rackb1\",\"from\":\"core-switch-2\",\"to\":\"dc-rack-b1\",\"width\":4,\"color\":\"#475569\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw1-rackb2\",\"from\":\"core-switch-1\",\"to\":\"dc-rack-b2\",\"width\":4,\"color\":\"#475569\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw2-rackb2\",\"from\":\"core-switch-2\",\"to\":\"dc-rack-b2\",\"width\":4,\"color\":\"#475569\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"fw1-dmz\",\"from\":\"fw-external-1\",\"to\":\"dmz-rack\",\"width\":3,\"color\":\"#f59e0b\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"DMZ segment\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"fw2-dmz\",\"from\":\"fw-external-2\",\"to\":\"dmz-rack\",\"width\":3,\"color\":\"#f59e0b\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"DMZ segment\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw1-mgmt\",\"from\":\"core-switch-1\",\"to\":\"mgmt-rack\",\"width\":3,\"color\":\"#8b5cf6\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"OOB management\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw1-wlc1\",\"from\":\"core-switch-1\",\"to\":\"wlc-primary\",\"width\":3,\"color\":\"#06b6d4\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw2-wlc2\",\"from\":\"core-switch-2\",\"to\":\"wlc-secondary\",\"width\":3,\"color\":\"#06b6d4\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\",\"animate\":true},{\"id\":\"wlc1-wlc2\",\"from\":\"wlc-primary\",\"to\":\"wlc-secondary\",\"width\":2,\"color\":\"#f59e0b\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"HA pair\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"dashed\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"wlc1-mobile-hq\",\"from\":\"wlc-primary\",\"to\":\"mobile-zone-hq\",\"width\":2,\"color\":\"#06b6d4\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"wlc1-mobile-guest\",\"from\":\"wlc-primary\",\"to\":\"mobile-zone-guest\",\"width\":2,\"color\":\"#06b6d4\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"wlc1-mobile-iot\",\"from\":\"wlc-primary\",\"to\":\"mobile-zone-iot\",\"width\":2,\"color\":\"#06b6d4\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"router1-branch-ny\",\"from\":\"core-router-1\",\"to\":\"branch-router-ny\",\"width\":3,\"color\":\"#a855f7\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"SD-WAN tunnel\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"dashed\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"router1-branch-la\",\"from\":\"core-router-1\",\"to\":\"branch-router-la\",\"width\":3,\"color\":\"#a855f7\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"SD-WAN tunnel\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"dashed\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"router1-branch-chi\",\"from\":\"core-router-1\",\"to\":\"branch-router-chi\",\"width\":3,\"color\":\"#a855f7\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"SD-WAN tunnel\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"dashed\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"router1-branch-lon\",\"from\":\"core-router-1\",\"to\":\"branch-router-lon\",\"width\":3,\"color\":\"#a855f7\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"SD-WAN tunnel\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"dashed\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"router1-branch-tokyo\",\"from\":\"core-router-1\",\"to\":\"branch-router-tokyo\",\"width\":3,\"color\":\"#a855f7\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"SD-WAN tunnel\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"dashed\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"router1-aws\",\"from\":\"core-router-1\",\"to\":\"cloud-aws\",\"width\":3,\"color\":\"#f97316\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"Direct Connect\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"dashed\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"router2-azure\",\"from\":\"core-router-2\",\"to\":\"cloud-azure\",\"width\":3,\"color\":\"#0ea5e9\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"ExpressRoute\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"dashed\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"fwint-gcp\",\"from\":\"fw-internal\",\"to\":\"cloud-gcp\",\"width\":2,\"color\":\"#22c55e\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"VPN tunnel\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"dashed\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw1-floor1\",\"from\":\"core-switch-1\",\"to\":\"dist-switch-floor1\",\"width\":3,\"color\":\"#475569\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw1-floor2\",\"from\":\"core-switch-1\",\"to\":\"dist-switch-floor2\",\"width\":3,\"color\":\"#475569\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw2-floor3\",\"from\":\"core-switch-2\",\"to\":\"dist-switch-floor3\",\"width\":3,\"color\":\"#475569\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw2-floor4\",\"from\":\"core-switch-2\",\"to\":\"dist-switch-floor4\",\"width\":3,\"color\":\"#475569\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"floor1-endpoints\",\"from\":\"dist-switch-floor1\",\"to\":\"endpoint-1000\",\"width\":2,\"color\":\"#94a3b8\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"floor1-ap1\",\"from\":\"dist-switch-floor1\",\"to\":\"ap-floor1-zone1\",\"width\":2,\"color\":\"#06b6d4\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"floor2-ap2\",\"from\":\"dist-switch-floor2\",\"to\":\"ap-floor2-zone1\",\"width\":2,\"color\":\"#06b6d4\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"floor3-ap3\",\"from\":\"dist-switch-floor3\",\"to\":\"ap-floor3-zone1\",\"width\":2,\"color\":\"#06b6d4\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"floor4-ap4\",\"from\":\"dist-switch-floor4\",\"to\":\"ap-floor4-zone1\",\"width\":2,\"color\":\"#06b6d4\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"fwint-proxy1\",\"from\":\"fw-internal\",\"to\":\"proxy-server-1\",\"width\":2,\"color\":\"#ef4444\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"fwint-proxy2\",\"from\":\"fw-internal\",\"to\":\"proxy-server-2\",\"width\":2,\"color\":\"#ef4444\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"fwext1-vpn\",\"from\":\"fw-external-1\",\"to\":\"vpn-concentrator\",\"width\":3,\"color\":\"#8b5cf6\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw1-nac\",\"from\":\"core-switch-1\",\"to\":\"nac-server\",\"width\":2,\"color\":\"#f59e0b\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw1-voip\",\"from\":\"core-switch-1\",\"to\":\"voip-cluster\",\"width\":3,\"color\":\"#22c55e\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw2-video\",\"from\":\"core-switch-2\",\"to\":\"video-conf\",\"width\":3,\"color\":\"#22c55e\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw1-cameras\",\"from\":\"core-switch-1\",\"to\":\"security-cameras\",\"width\":2,\"color\":\"#94a3b8\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"fwint-dev1\",\"from\":\"fw-internal\",\"to\":\"dev-server-1\",\"width\":2,\"color\":\"#a855f7\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"fwint-dev2\",\"from\":\"fw-internal\",\"to\":\"dev-server-2\",\"width\":2,\"color\":\"#a855f7\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\",\"animate\":true,\"animationSpeed\":\"1.5\"},{\"id\":\"fwint-test\",\"from\":\"fw-internal\",\"to\":\"test-environment\",\"width\":2,\"color\":\"#a855f7\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw1-erp\",\"from\":\"core-switch-1\",\"to\":\"erp-system\",\"width\":3,\"color\":\"#f59e0b\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"fwext1-crm\",\"from\":\"fw-external-1\",\"to\":\"crm-system\",\"width\":2,\"color\":\"#f59e0b\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"Salesforce cloud\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"dashed\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"ups1-racka1\",\"from\":\"ups-dc-1\",\"to\":\"dc-rack-a1\",\"width\":2,\"color\":\"#fbbf24\",\"direction\":\"forward\",\"type\":\"main\",\"notes\":[\"Power feed A\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"ups2-racka2\",\"from\":\"ups-dc-2\",\"to\":\"dc-rack-a2\",\"width\":2,\"color\":\"#fbbf24\",\"direction\":\"forward\",\"type\":\"main\",\"notes\":[\"Power feed B\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"ups1-rackb1\",\"from\":\"ups-dc-1\",\"to\":\"dc-rack-b1\",\"width\":2,\"color\":\"#fbbf24\",\"direction\":\"forward\",\"type\":\"main\",\"notes\":[\"Power feed A\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\",\"animate\":true,\"animationSpeed\":\"4\"},{\"id\":\"ups2-rackb2\",\"from\":\"ups-dc-2\",\"to\":\"dc-rack-b2\",\"width\":2,\"color\":\"#fbbf24\",\"direction\":\"forward\",\"type\":\"main\",\"notes\":[\"Power feed B\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"cooling1-racka1\",\"from\":\"cooling-1\",\"to\":\"dc-rack-a1\",\"width\":2,\"color\":\"#38bdf8\",\"direction\":\"forward\",\"type\":\"main\",\"notes\":[\"Cooling zone\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"dotted\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"cooling2-rackb1\",\"from\":\"cooling-2\",\"to\":\"dc-rack-b1\",\"width\":2,\"color\":\"#38bdf8\",\"direction\":\"forward\",\"type\":\"main\",\"notes\":[\"Cooling zone\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"dotted\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"custom-1765237881452\",\"type\":\"custom\",\"color\":\"#c800ff\",\"width\":4,\"lineStyle\":\"solid\",\"direction\":\"forward\",\"points\":[{\"x\":3492.3994140625,\"y\":1526.9556884765625},{\"x\":3500.609619140625,\"y\":1830.7386474609375},{\"x\":3303.561279296875,\"y\":1732.2144775390625}],\"notes\":[],\"routing\":\"orthogonal\"}]},\"rectData\":{\"list\":[{\"id\":\"rect-1765237540610\",\"x\":2879.214599609375,\"y\":159.71981811523438,\"width\":992.196044921875,\"height\":538.8650817871094,\"color\":\"#f97316\",\"style\":\"filled\",\"lineStyle\":\"solid\",\"notes\":[]},{\"id\":\"rect-1765237681216\",\"x\":448.3926696777344,\"y\":1671.651123046875,\"width\":916.3436584472656,\"height\":924.27734375,\"color\":\"#c800ff\",\"style\":\"outlined\",\"lineStyle\":\"solid\",\"notes\":[]},{\"id\":\"rect-1766437913740\",\"x\":904.5889892578125,\"y\":115.40318298339844,\"width\":110.93878173828125,\"height\":919.6242218017578,\"color\":\"#5215f9\",\"style\":\"filled\",\"lineStyle\":\"wall\",\"notes\":[],\"borderWidth\":13},{\"id\":\"rect-1766437935414\",\"x\":130.93685150146484,\"y\":1072.3624877929688,\"width\":872.9131851196289,\"height\":99.260986328125,\"color\":\"#5215f9\",\"style\":\"filled\",\"lineStyle\":\"wall\",\"notes\":[],\"borderWidth\":13}]},\"textData\":{\"list\":[{\"id\":\"text-1765237828167\",\"x\":3411.458740234375,\"y\":1390.00439453125,\"content\":\"Double click on desktop\\nor long press on mobile\\nto enter rack canvas view\",\"fontSize\":46,\"color\":\"#e2e8f0\",\"fontWeight\":\"bold\",\"fontStyle\":\"italic\",\"textAlign\":\"middle\",\"textDecoration\":\"none\",\"bgColor\":\"#000000\",\"bgEnabled\":false,\"opacity\":1},{\"id\":\"text-1766446595277\",\"x\":654.3878479003906,\"y\":1367.7945556640625,\"content\":\"SITE A\",\"fontSize\":101,\"color\":\"#e2e8f0\",\"fontWeight\":\"normal\",\"fontStyle\":\"normal\",\"textAlign\":\"start\",\"textDecoration\":\"none\",\"bgColor\":\"#000000\",\"bgEnabled\":false,\"opacity\":1},{\"id\":\"text-1766446610211\",\"x\":180.63662719726562,\"y\":1128.822998046875,\"content\":\"SITE A\",\"fontSize\":101,\"color\":\"#e2e8f0\",\"fontWeight\":\"normal\",\"fontStyle\":\"normal\",\"textAlign\":\"start\",\"textDecoration\":\"none\",\"bgColor\":\"#000000\",\"bgEnabled\":false,\"opacity\":1},{\"id\":\"text-1766453024797\",\"x\":968.6458740234375,\"y\":1028.6621398925781,\"content\":\"SITE A\",\"fontSize\":101,\"color\":\"#e2e8f0\",\"fontWeight\":\"normal\",\"fontStyle\":\"normal\",\"textAlign\":\"start\",\"textDecoration\":\"none\",\"bgColor\":\"#000000\",\"bgEnabled\":false,\"opacity\":1,\"rotation\":-89,\"_dragStartX\":972.46826171875,\"_dragStartY\":1009.5499572753906},{\"id\":\"text-1766453070975\",\"x\":613.1589965820312,\"y\":1139.512939453125,\"content\":\"SITE A\",\"fontSize\":101,\"color\":\"#e2e8f0\",\"fontWeight\":\"normal\",\"fontStyle\":\"normal\",\"textAlign\":\"start\",\"textDecoration\":\"none\",\"bgColor\":\"#000000\",\"bgEnabled\":false,\"opacity\":1},{\"id\":\"text-1766453072857\",\"x\":968.64599609375,\"y\":474.40818786621094,\"content\":\"SITE A\",\"fontSize\":101,\"color\":\"#e2e8f0\",\"fontWeight\":\"normal\",\"fontStyle\":\"normal\",\"textAlign\":\"start\",\"textDecoration\":\"none\",\"bgColor\":\"#000000\",\"bgEnabled\":false,\"opacity\":1,\"rotation\":269,\"_dragStartX\":1480.85302734375,\"_dragStartY\":822.2503356933594},{\"id\":\"text-1766458222326\",\"x\":3167.812744140625,\"y\":2190.516357421875,\"content\":\"\",\"fontSize\":18,\"color\":\"#e2e8f0\",\"fontWeight\":\"normal\",\"fontStyle\":\"normal\",\"textAlign\":\"start\",\"textDecoration\":\"none\",\"bgColor\":\"#000000\",\"bgEnabled\":false,\"opacity\":1}]},\"edgeLegend\":{\"#10b981\":\"Trusted Lan\",\"#f59e0b\":\"Secure Lan\",\"#ef4444\":\"DMZ\",\"#475569\":\"Main ISP\",\"#3b82f6\":\"Alternate ISP\",\"#8b5cf6\":\"you can edit me too\",\"#06b6d4\":\"you can edit me too\",\"#a855f7\":\"you can edit me too\",\"#f97316\":\"you can edit me too\",\"#0ea5e9\":\"you can edit me too\",\"#22c55e\":\"you can edit me too\",\"#94a3b8\":\"you can edit me too\",\"#fbbf24\":\"you can edit me too\",\"#38bdf8\":\"you can edit me too\",\"#c800ff\":\"you can edit me too\"},\"nodePositions\":{\"core-router-1\":{\"x\":3720.166015625,\"y\":245.9932403564453},\"core-router-2\":{\"x\":2499.883407638303,\"y\":329.99503430389154},\"fw-external-1\":{\"x\":3221.7385182723783,\"y\":1016.1364499992887},\"fw-external-2\":{\"x\":1915.5213706410505,\"y\":224.43528858865443},\"fw-internal\":{\"x\":1746.9168185079352,\"y\":477.5300527221864},\"core-switch-1\":{\"x\":449.39860669455675,\"y\":384.4578707617695},\"core-switch-2\":{\"x\":761.1664921394672,\"y\":180.89283910873155},\"dc-rack-a1\":{\"x\":783.7017241128451,\"y\":647.4086870405963},\"dc-rack-a2\":{\"x\":209.25701628255229,\"y\":228.01593190351014},\"dc-rack-b1\":{\"x\":3184.3186625759854,\"y\":1627.4495531027196},\"dc-rack-b2\":{\"x\":245.37065918741246,\"y\":499.6191264194081},\"dmz-rack\":{\"x\":2176.4105289561007,\"y\":610.8312056412005},\"mgmt-rack\":{\"x\":1601.2987201807314,\"y\":1281.4753424975324},\"esxi-host-01\":{\"x\":2162.2166789540615,\"y\":2608.110619289529},\"esxi-host-02\":{\"x\":2205.94717202368,\"y\":2689.67539624076},\"esxi-host-03\":{\"x\":2154.6015436939074,\"y\":2771.203009774913},\"esxi-host-04\":{\"x\":2195.986926025096,\"y\":2845},\"tor-switch-a1\":{\"x\":2146.8943639962963,\"y\":2845},\"esxi-host-05\":{\"x\":2185.9099961569727,\"y\":2845},\"esxi-host-06\":{\"x\":2139.099728450725,\"y\":2845},\"esxi-host-07\":{\"x\":2175.7223818764883,\"y\":2845},\"esxi-host-08\":{\"x\":2131.2222777148922,\"y\":2845},\"tor-switch-a2\":{\"x\":2165.4301485385085,\"y\":2845},\"san-primary\":{\"x\":2123.2667017518106,\"y\":2845},\"san-secondary\":{\"x\":2155.0394237844876,\"y\":2845},\"fc-switch-1\":{\"x\":2115.2377370375634,\"y\":2845},\"fc-switch-2\":{\"x\":2144.5563938942755,\"y\":2845},\"backup-server-1\":{\"x\":2107.1401637413705,\"y\":2845},\"backup-server-2\":{\"x\":2133.987300103025,\"y\":2845},\"tape-library\":{\"x\":2098.9788028796397,\"y\":2845},\"tor-switch-b1\":{\"x\":2123.338434885373,\"y\":2845},\"tor-switch-b2\":{\"x\":2090.7585134456995,\"y\":2845},\"web-server-1\":{\"x\":2112.6161382091163,\"y\":2845},\"web-server-2\":{\"x\":2082.484189516922,\"y\":2845},\"waf-1\":{\"x\":2101.826793760617,\"y\":2845},\"load-balancer-dmz\":{\"x\":2074.1607573409574,\"y\":2845},\"mail-gateway\":{\"x\":2090.97682514417,\"y\":2845},\"dns-external-1\":{\"x\":2065.7931724028163,\"y\":2845},\"dns-external-2\":{\"x\":2080.0726920576153,\"y\":2845},\"vcenter\":{\"x\":2057.3864164745437,\"y\":2845},\"nsx-manager\":{\"x\":2069.1208864464534,\"y\":2845},\"siem-server\":{\"x\":2048.945494649244,\"y\":2845},\"nms-server\":{\"x\":2058.1279286387635,\"y\":2845},\"jump-server\":{\"x\":2040.4754323612206,\"y\":2845},\"ipam-server\":{\"x\":2047.1003634632284,\"y\":2845},\"wlc-primary\":{\"x\":1575.9723612611924,\"y\":2306.135986328125},\"wlc-secondary\":{\"x\":1468.1361870166274,\"y\":1563.733642578125},\"mobile-zone-hq\":{\"x\":2354.901177346808,\"y\":2806.0078125},\"mobile-zone-guest\":{\"x\":2307.6605605284435,\"y\":2611.047119140625},\"mobile-zone-iot\":{\"x\":2229.397686389302,\"y\":2299.110107421875},\"branch-router-ny\":{\"x\":3151.903101363964,\"y\":633.6580810546875},\"branch-router-la\":{\"x\":3083.8876194705945,\"y\":506.90625},\"branch-router-chi\":{\"x\":3355.02409980103,\"y\":393.1805725097656},\"branch-router-lon\":{\"x\":3113.609823320121,\"y\":260.4093322753906},\"branch-router-tokyo\":{\"x\":3699.3234994733834,\"y\":471.4241027832031},\"cloud-aws\":{\"x\":3436.528122523513,\"y\":545.9614868164062},\"cloud-azure\":{\"x\":2592.566210818907,\"y\":2724.068115234375},\"cloud-gcp\":{\"x\":2827.3183770424234,\"y\":2731.397216796875},\"isp-primary\":{\"x\":3712.192068081962,\"y\":615.64990234375},\"isp-secondary\":{\"x\":3253.9473366098055,\"y\":1993.2629089355469},\"dc-internal-1\":{\"x\":1958.4243458877936,\"y\":2845},\"dc-internal-2\":{\"x\":1963.768951182132,\"y\":2845},\"app-server-1\":{\"x\":1947.3819379304134,\"y\":2845},\"app-server-2\":{\"x\":1955.2862087394126,\"y\":2845},\"db-server-1\":{\"x\":1936.3708569559828,\"y\":2845},\"db-server-2\":{\"x\":1946.8300873488822,\"y\":2845},\"k8s-master-1\":{\"x\":1925.397658583093,\"y\":2845},\"k8s-master-2\":{\"x\":1938.405621494142,\"y\":2845},\"k8s-master-3\":{\"x\":1914.4688758763386,\"y\":2845},\"k8s-worker-1\":{\"x\":1930.017826812177,\"y\":2845},\"k8s-worker-2\":{\"x\":1903.5910154567553,\"y\":2845},\"k8s-worker-3\":{\"x\":1921.6716971072178,\"y\":2845},\"k8s-worker-4\":{\"x\":1892.7705536280016,\"y\":2845},\"proxy-server-1\":{\"x\":1806.1152433697903,\"y\":653.7529296875},\"proxy-server-2\":{\"x\":2937.4207928721535,\"y\":2628.7880859375},\"vpn-concentrator\":{\"x\":3642.252088474593,\"y\":946.7255249023438},\"nac-server\":{\"x\":1153.2626148502184,\"y\":1172.1895751953125},\"print-server\":{\"x\":1896.9328460745962,\"y\":2845},\"file-server\":{\"x\":1860.7177871362182,\"y\":2845},\"ca-server\":{\"x\":1888.8027739274805,\"y\":2845},\"sccm-server\":{\"x\":1850.1909418511675,\"y\":2845},\"voip-cluster\":{\"x\":1777.038465328039,\"y\":1616.8961181640625},\"video-conf\":{\"x\":1993.8373941679588,\"y\":2244.936309814453},\"security-cameras\":{\"x\":1674.413336949044,\"y\":2046.0380859375},\"nvr-cluster\":{\"x\":1829.4110389706402,\"y\":2845},\"dev-server-1\":{\"x\":2800.5894350649614,\"y\":1175.623291015625},\"dev-server-2\":{\"x\":1945.0822182484326,\"y\":1164.5184783935547},\"test-environment\":{\"x\":2932.0863047891075,\"y\":862.4592895507812},\"erp-system\":{\"x\":789.9880103985649,\"y\":473.7113342285156},\"crm-system\":{\"x\":3514.6003232048542,\"y\":1137.7720947265625},\"endpoint-1000\":{\"x\":991.6812012057328,\"y\":2284.42236328125},\"dist-switch-floor1\":{\"x\":654.2091033261356,\"y\":2020.0086669921875},\"dist-switch-floor2\":{\"x\":853.8845527112826,\"y\":1843.2872314453125},\"dist-switch-floor3\":{\"x\":1899.4353951584517,\"y\":1456.5068359375},\"dist-switch-floor4\":{\"x\":488.5289313756234,\"y\":181.47256469726562},\"ap-floor1-zone1\":{\"x\":1140.16846970184,\"y\":2070.2916259765625},\"ap-floor2-zone1\":{\"x\":688.1952143592268,\"y\":2384.4775390625},\"ap-floor3-zone1\":{\"x\":2145.3803027919676,\"y\":1890.2816162109375},\"ap-floor4-zone1\":{\"x\":517.646146409649,\"y\":565.59716796875},\"ups-dc-1\":{\"x\":771.1406786539856,\"y\":295.9266662597656},\"ups-dc-2\":{\"x\":216.2410855890687,\"y\":330.3345947265625},\"pdu-rack-a1\":{\"x\":1804.774444371901,\"y\":2845},\"pdu-rack-a2\":{\"x\":1741.6184034693686,\"y\":2845},\"cooling-1\":{\"x\":245.7080801919958,\"y\":626.1914672851562},\"cooling-2\":{\"x\":1603.293611085831,\"y\":981.0621185302734},\"camera-a\":{\"x\":166.57075412676295,\"y\":145},\"camera-a-copy\":{\"x\":1040.653076171875,\"y\":738.42822265625}},\"nodeSizes\":{\"isp-secondary\":139,\"test-environment\":121,\"dev-server-1\":128,\"core-router-2\":120,\"camera-a\":45,\"camera-a-copy\":45},\"nodeStyles\":{\"dc-rack-b2\":{\"all\":{\"circleColor\":\"#ff0000\"}},\"dc-rack-a1\":{\"all\":{\"circleColor\":\"#ff0000\"}},\"dc-rack-b1\":{\"all\":{\"circleColor\":\"#ff0000\",\"titleSize\":59}},\"isp-secondary\":{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"alist\"},\"circleColor\":\"#4d2c58\",\"circleBorder\":\"#000000\",\"titleColor\":\"#006eff\"}},\"core-router-2\":{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"actual-budget\"},\"pingOffsetX\":-15,\"pingOffsetY\":-38}},\"fw-external-1\":{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"anonaddy\"}}},\"cloud-aws\":{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"ansible\"}}},\"isp-primary\":{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"wikidocs\"}}},\"branch-router-tokyo\":{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"adguard-home\"}}},\"core-router-1\":{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"borg\"}}},\"test-environment\":{\"all\":{\"icon\":{\"library\":\"simple\",\"name\":\"apple\"}}},\"dev-server-1\":{\"all\":{\"icon\":{\"library\":\"simple\",\"name\":\"amazonwebservices\"}}}},\"page\":{\"title\":\"The One File Corporate\",\"background\":\"\",\"topbarBg\":\"rgba(9, 12, 20, 0.9)\",\"topbarBorder\":\"#1f2533\",\"panel\":\"#0b0e13\",\"panelAlt\":\"#10141b\",\"accent\":\"#4fd1c5\",\"sidebarBg\":\"#10141b\",\"btnBg\":\"#0b0e13\",\"btnText\":\"#e2e8f0\",\"tagFill\":\"#1e293b\",\"tagText\":\"#e2e8f0\",\"tagBorder\":\"#475569\",\"inputBg\":\"#0b0e13\",\"inputText\":\"#e2e8f0\",\"inputBorder\":\"#1f2937\",\"inputFont\":\"Inter, system-ui, sans-serif\",\"inputFontSize\":14,\"toolbarBg\":\"#0f172a\",\"toolbarBorder\":\"#1f2937\",\"toolbarText\":\"#94a3b8\",\"toolbarBtnBg\":\"#0b0e13\",\"toolbarBtnText\":\"#e2e8f0\",\"minimapDots\":\"#94a3b8\",\"canvasHintEnabled\":true,\"canvasHintText\":\"\",\"canvasHintBg\":\"#0f172a\",\"canvasHintColor\":\"#94a3b8\",\"danger\":\"#f56565\",\"textMain\":\"#e2e8f0\",\"textSoft\":\"#94a3b8\",\"topbarHeight\":103,\"sidebarWidth\":350,\"mobileFooterHeight\":40,\"sidebarCollapsed\":false,\"nodeFill\":\"#1e293b\",\"nodeStroke\":\"#475569\",\"nodeTitle\":\"#e2e8f0\",\"nodeSub\":\"#94a3b8\",\"nodeTitleSize\":41,\"nodeSubSize\":27,\"nodeFont\":\"monospace\",\"defaultEdge\":\"#475569\",\"selectionHandle\":\"#f59e0b\",\"selectionHandleSize\":8,\"groupIndicator\":\"#4fd1c5\",\"canvasGradientTop\":\"#1e2532\",\"canvasGradientBottom\":\"#050608\",\"canvasBorder\":\"#475569\",\"canvasGrid\":\"#475569\",\"canvasGridSize\":50,\"canvasGridEnabled\":true,\"rackFrameFill\":\"#0f172a\",\"rackGridEnabled\":true,\"rackFrameStroke\":\"#4fd1c5\",\"rackLineColor\":\"#475569\",\"rackTextColor\":\"#4fd1c5\",\"viewOnly\":false,\"defaultEdgeRouting\":\"orthogonal\",\"animateConnections\":false,\"animationStyle\":\"arrows\",\"animationDirection\":\"all\",\"animationSpeed\":4,\"autoPingEnabled\":false,\"autoPingInterval\":30},\"canvas\":{\"zoom\":0.8752084596859406,\"panX\":-191.26917538063572,\"panY\":-261.51832729948296},\"savedTopologyView\":{\"zoom\":0.9111098220009686,\"panX\":-207.26076103009882,\"panY\":-201.83911371533202},\"documentTabs\":[{\"id\":\"main\",\"name\":\"Corporate Site B\",\"nodes\":{\"core-router-1\":{\"shape\":\"router\",\"name\":\"Core Router 1\",\"ip\":\"10.0.0.1\",\"role\":\"Core Routing\",\"tags\":[\"core\",\"tier-1\",\"redundant\"],\"notes\":[\"Primary core router\",\"BGP peering enabled\"],\"mac\":\"00:1A:2B:3C:4D:01\",\"rackUnit\":\"\",\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"core-router-2\":{\"shape\":\"router\",\"name\":\"Core Router 2\",\"ip\":\"10.0.0.2\",\"role\":\"Core Routing\",\"tags\":[\"core\",\"tier-1\",\"redundant\"],\"notes\":[\"Secondary core router\",\"HSRP standby\"],\"mac\":\"00:1A:2B:3C:4D:02\",\"rackUnit\":\"\",\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null,\"ping\":{\"enabled\":true,\"protocol\":\"custom\",\"customUrl\":\"https://google.com\",\"timeout\":3000,\"status\":\"online\",\"lastCheck\":\"2025-12-09T00:15:04.343Z\"}},\"fw-external-1\":{\"shape\":\"firewall\",\"name\":\"External FW 1\",\"ip\":\"10.0.1.1\",\"role\":\"Perimeter Security\",\"tags\":[\"security\",\"perimeter\",\"ha-pair\"],\"notes\":[\"Palo Alto PA-5250\",\"Active node\"],\"mac\":\"00:1A:2B:3C:4D:10\",\"rackUnit\":\"\",\"uHeight\":\"2\",\"layer\":\"security\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"fw-external-2\":{\"shape\":\"firewall\",\"name\":\"External FW 2\",\"ip\":\"10.0.1.2\",\"role\":\"Perimeter Security\",\"tags\":[\"security\",\"perimeter\",\"ha-pair\"],\"notes\":[\"Palo Alto PA-5250\",\"Passive node\"],\"mac\":\"00:1A:2B:3C:4D:11\",\"rackUnit\":\"\",\"uHeight\":\"2\",\"layer\":\"security\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"fw-internal\":{\"shape\":\"firewall\",\"name\":\"Internal FW\",\"ip\":\"10.0.2.1\",\"role\":\"Internal Segmentation\",\"tags\":[\"security\",\"internal\"],\"notes\":[\"East-West traffic inspection\"],\"mac\":\"00:1A:2B:3C:4D:12\",\"rackUnit\":\"\",\"uHeight\":\"2\",\"layer\":\"security\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"core-switch-1\":{\"shape\":\"switch\",\"name\":\"Core Switch 1\",\"ip\":\"10.0.10.1\",\"role\":\"Core Switching\",\"tags\":[\"core\",\"layer3\",\"redundant\"],\"notes\":[\"Cisco Nexus 9000\",\"VPC Domain 1\"],\"mac\":\"00:1A:2B:3C:4D:20\",\"rackUnit\":\"\",\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"core-switch-2\":{\"shape\":\"switch\",\"name\":\"Core Switch 2\",\"ip\":\"10.0.10.2\",\"role\":\"Core Switching\",\"tags\":[\"core\",\"layer3\",\"redundant\"],\"notes\":[\"Cisco Nexus 9000\",\"VPC Domain 1\"],\"mac\":\"00:1A:2B:3C:4D:21\",\"rackUnit\":\"\",\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"dc-rack-a1\":{\"shape\":\"server\",\"name\":\"DC Rack A1\",\"ip\":\"10.10.0.0/24\",\"role\":\"Data Center Rack\",\"tags\":[\"datacenter\",\"row-a\",\"production\"],\"notes\":[\"Row A, Position 1\",\"Primary compute\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":true,\"locked\":false,\"groupId\":null},\"dc-rack-a2\":{\"shape\":\"server\",\"name\":\"DC Rack A2\",\"ip\":\"10.10.1.0/24\",\"role\":\"Data Center Rack\",\"tags\":[\"datacenter\",\"row-a\",\"production\"],\"notes\":[\"Row A, Position 2\",\"Primary compute\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":true,\"locked\":false,\"groupId\":null},\"dc-rack-b1\":{\"shape\":\"server\",\"name\":\"DC Rack B1\",\"ip\":\"10.10.2.0/24\",\"role\":\"Data Center Rack\",\"tags\":[\"datacenter\",\"row-b\",\"storage\"],\"notes\":[\"Row B, Position 1\",\"Storage systems\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":true,\"locked\":false,\"groupId\":null},\"dc-rack-b2\":{\"shape\":\"server\",\"name\":\"DC Rack B2\",\"ip\":\"10.10.3.0/24\",\"role\":\"Data Center Rack\",\"tags\":[\"datacenter\",\"row-b\",\"storage\"],\"notes\":[\"Row B, Position 2\",\"Storage systems\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":true,\"locked\":false,\"groupId\":null},\"dmz-rack\":{\"shape\":\"server\",\"name\":\"DMZ Rack\",\"ip\":\"172.16.0.0/24\",\"role\":\"DMZ Infrastructure\",\"tags\":[\"dmz\",\"security\",\"public-facing\",{\"type\":\"icon\",\"library\":\"selfhst\",\"name\":\"booklogr\"},{\"type\":\"icon\",\"library\":\"simple\",\"name\":\"gmail\"}],\"notes\":[\"Isolated DMZ zone\",\"Public-facing services\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"security\",\"assignedRack\":\"\",\"rackCapacity\":\"24\",\"isRack\":true,\"locked\":false,\"groupId\":null},\"mgmt-rack\":{\"shape\":\"server\",\"name\":\"Management Rack\",\"ip\":\"192.168.100.0/24\",\"role\":\"Management Infrastructure\",\"tags\":[\"management\",\"oob\",\"noc\"],\"notes\":[\"Out-of-band management\",\"NOC equipment\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"logical\",\"assignedRack\":\"\",\"rackCapacity\":\"24\",\"isRack\":true,\"locked\":false,\"groupId\":null},\"esxi-host-01\":{\"shape\":\"server\",\"name\":\"ESXi Host 01\",\"ip\":\"10.10.0.11\",\"role\":\"Hypervisor\",\"tags\":[\"vmware\",\"compute\",\"cluster-a\"],\"notes\":[\"Dell PowerEdge R750\",\"512GB RAM\",\"vSphere 8.0\"],\"mac\":\"00:50:56:AA:01:01\",\"rackUnit\":38,\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a1\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"esxi-host-02\":{\"shape\":\"server\",\"name\":\"ESXi Host 02\",\"ip\":\"10.10.0.12\",\"role\":\"Hypervisor\",\"tags\":[\"vmware\",\"compute\",\"cluster-a\"],\"notes\":[\"Dell PowerEdge R750\",\"512GB RAM\",\"vSphere 8.0\"],\"mac\":\"00:50:56:AA:01:02\",\"rackUnit\":35,\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a1\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"esxi-host-03\":{\"shape\":\"server\",\"name\":\"ESXi Host 03\",\"ip\":\"10.10.0.13\",\"role\":\"Hypervisor\",\"tags\":[\"vmware\",\"compute\",\"cluster-a\"],\"notes\":[\"Dell PowerEdge R750\",\"512GB RAM\",\"vSphere 8.0\"],\"mac\":\"00:50:56:AA:01:03\",\"rackUnit\":32,\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a1\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"esxi-host-04\":{\"shape\":\"server\",\"name\":\"ESXi Host 04\",\"ip\":\"10.10.0.14\",\"role\":\"Hypervisor\",\"tags\":[\"vmware\",\"compute\",\"cluster-a\"],\"notes\":[\"Dell PowerEdge R750\",\"512GB RAM\",\"vSphere 8.0\"],\"mac\":\"00:50:56:AA:01:04\",\"rackUnit\":29,\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a1\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"tor-switch-a1\":{\"shape\":\"switch\",\"name\":\"ToR Switch A1\",\"ip\":\"10.10.0.1\",\"role\":\"Top of Rack\",\"tags\":[\"tor\",\"access\",\"rack-a1\"],\"notes\":[\"Cisco Nexus 93180YC-FX\",\"48x25G ports\"],\"mac\":\"00:1A:2B:3C:5D:01\",\"rackUnit\":42,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a1\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"esxi-host-05\":{\"shape\":\"server\",\"name\":\"ESXi Host 05\",\"ip\":\"10.10.1.11\",\"role\":\"Hypervisor\",\"tags\":[\"vmware\",\"compute\",\"cluster-b\"],\"notes\":[\"Dell PowerEdge R750\",\"768GB RAM\",\"vSphere 8.0\"],\"mac\":\"00:50:56:AA:02:01\",\"rackUnit\":38,\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"esxi-host-06\":{\"shape\":\"server\",\"name\":\"ESXi Host 06\",\"ip\":\"10.10.1.12\",\"role\":\"Hypervisor\",\"tags\":[\"vmware\",\"compute\",\"cluster-b\"],\"notes\":[\"Dell PowerEdge R750\",\"768GB RAM\",\"vSphere 8.0\"],\"mac\":\"00:50:56:AA:02:02\",\"rackUnit\":35,\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"esxi-host-07\":{\"shape\":\"server\",\"name\":\"ESXi Host 07\",\"ip\":\"10.10.1.13\",\"role\":\"Hypervisor\",\"tags\":[\"vmware\",\"compute\",\"cluster-b\"],\"notes\":[\"Dell PowerEdge R750\",\"768GB RAM\",\"vSphere 8.0\"],\"mac\":\"00:50:56:AA:02:03\",\"rackUnit\":32,\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"esxi-host-08\":{\"shape\":\"server\",\"name\":\"ESXi Host 08\",\"ip\":\"10.10.1.14\",\"role\":\"Hypervisor\",\"tags\":[\"vmware\",\"compute\",\"cluster-b\"],\"notes\":[\"Dell PowerEdge R750\",\"768GB RAM\",\"vSphere 8.0\"],\"mac\":\"00:50:56:AA:02:04\",\"rackUnit\":29,\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"tor-switch-a2\":{\"shape\":\"switch\",\"name\":\"ToR Switch A2\",\"ip\":\"10.10.1.1\",\"role\":\"Top of Rack\",\"tags\":[\"tor\",\"access\",\"rack-a2\"],\"notes\":[\"Cisco Nexus 93180YC-FX\",\"48x25G ports\"],\"mac\":\"00:1A:2B:3C:5D:02\",\"rackUnit\":42,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"san-primary\":{\"shape\":\"database\",\"name\":\"SAN Primary\",\"ip\":\"10.10.2.10\",\"role\":\"Primary Storage\",\"tags\":[\"storage\",\"san\",\"netapp\"],\"notes\":[\"NetApp AFF A400\",\"500TB Raw\",\"FC 32Gb\"],\"mac\":\"00:A0:98:AA:01:01\",\"rackUnit\":36,\"uHeight\":\"6\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-b1\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"san-secondary\":{\"shape\":\"database\",\"name\":\"SAN Secondary\",\"ip\":\"10.10.2.11\",\"role\":\"Secondary Storage\",\"tags\":[\"storage\",\"san\",\"netapp\"],\"notes\":[\"NetApp AFF A400\",\"500TB Raw\",\"FC 32Gb\"],\"mac\":\"00:A0:98:AA:01:02\",\"rackUnit\":28,\"uHeight\":\"6\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-b1\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"fc-switch-1\":{\"shape\":\"switch\",\"name\":\"FC Switch 1\",\"ip\":\"10.10.2.1\",\"role\":\"Fibre Channel\",\"tags\":[\"storage\",\"fc\",\"fabric-a\"],\"notes\":[\"Brocade G620\",\"Fabric A\"],\"mac\":\"00:1A:2B:FC:01:01\",\"rackUnit\":42,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-b1\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"fc-switch-2\":{\"shape\":\"switch\",\"name\":\"FC Switch 2\",\"ip\":\"10.10.2.2\",\"role\":\"Fibre Channel\",\"tags\":[\"storage\",\"fc\",\"fabric-b\"],\"notes\":[\"Brocade G620\",\"Fabric B\"],\"mac\":\"00:1A:2B:FC:01:02\",\"rackUnit\":41,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-b1\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"backup-server-1\":{\"shape\":\"server\",\"name\":\"Backup Server 1\",\"ip\":\"10.10.3.10\",\"role\":\"Backup Infrastructure\",\"tags\":[\"backup\",\"veeam\",\"protection\"],\"notes\":[\"Veeam Backup Server\",\"Dell R740xd\",\"200TB\"],\"mac\":\"00:50:56:BB:01:01\",\"rackUnit\":36,\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-b2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"backup-server-2\":{\"shape\":\"server\",\"name\":\"Backup Server 2\",\"ip\":\"10.10.3.11\",\"role\":\"Backup Infrastructure\",\"tags\":[\"backup\",\"veeam\",\"protection\"],\"notes\":[\"Veeam Backup Server\",\"Dell R740xd\",\"200TB\"],\"mac\":\"00:50:56:BB:01:02\",\"rackUnit\":33,\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-b2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"tape-library\":{\"shape\":\"database\",\"name\":\"Tape Library\",\"ip\":\"10.10.3.20\",\"role\":\"Archival Storage\",\"tags\":[\"backup\",\"tape\",\"lto9\"],\"notes\":[\"IBM TS4500\",\"LTO-9\",\"Long-term archive\"],\"mac\":\"00:50:56:BB:02:01\",\"rackUnit\":20,\"uHeight\":\"10\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-b2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"tor-switch-b1\":{\"shape\":\"switch\",\"name\":\"ToR Switch B1\",\"ip\":\"10.10.2.3\",\"role\":\"Top of Rack\",\"tags\":[\"tor\",\"access\",\"rack-b1\"],\"notes\":[\"Cisco Nexus 93180YC-FX\"],\"mac\":\"00:1A:2B:3C:5D:03\",\"rackUnit\":40,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-b1\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"tor-switch-b2\":{\"shape\":\"switch\",\"name\":\"ToR Switch B2\",\"ip\":\"10.10.3.1\",\"role\":\"Top of Rack\",\"tags\":[\"tor\",\"access\",\"rack-b2\"],\"notes\":[\"Cisco Nexus 93180YC-FX\"],\"mac\":\"00:1A:2B:3C:5D:04\",\"rackUnit\":42,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-b2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"web-server-1\":{\"shape\":\"server\",\"name\":\"Web Server 1\",\"ip\":\"172.16.0.11\",\"role\":\"Web Frontend\",\"tags\":[\"dmz\",\"web\",\"nginx\"],\"notes\":[\"NGINX reverse proxy\",\"Public facing\"],\"mac\":\"00:50:56:CC:01:01\",\"rackUnit\":20,\"uHeight\":\"1\",\"layer\":\"security\",\"assignedRack\":\"dmz-rack\",\"rackCapacity\":\"24\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"web-server-2\":{\"shape\":\"server\",\"name\":\"Web Server 2\",\"ip\":\"172.16.0.12\",\"role\":\"Web Frontend\",\"tags\":[\"dmz\",\"web\",\"nginx\"],\"notes\":[\"NGINX reverse proxy\",\"Public facing\"],\"mac\":\"00:50:56:CC:01:02\",\"rackUnit\":18,\"uHeight\":\"1\",\"layer\":\"security\",\"assignedRack\":\"dmz-rack\",\"rackCapacity\":\"24\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"waf-1\":{\"shape\":\"firewall\",\"name\":\"WAF Appliance\",\"ip\":\"172.16.0.5\",\"role\":\"Web Application Firewall\",\"tags\":[\"dmz\",\"security\",\"waf\"],\"notes\":[\"F5 BIG-IP ASM\",\"OWASP protection\"],\"mac\":\"00:50:56:CC:02:01\",\"rackUnit\":22,\"uHeight\":\"2\",\"layer\":\"security\",\"assignedRack\":\"dmz-rack\",\"rackCapacity\":\"24\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"load-balancer-dmz\":{\"shape\":\"switch\",\"name\":\"DMZ Load Balancer\",\"ip\":\"172.16.0.3\",\"role\":\"Load Balancing\",\"tags\":[\"dmz\",\"lb\",\"f5\"],\"notes\":[\"F5 BIG-IP LTM\",\"VIP: 172.16.0.100\"],\"mac\":\"00:50:56:CC:03:01\",\"rackUnit\":16,\"uHeight\":\"2\",\"layer\":\"security\",\"assignedRack\":\"dmz-rack\",\"rackCapacity\":\"24\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"mail-gateway\":{\"shape\":\"server\",\"name\":\"Mail Gateway\",\"ip\":\"172.16.0.25\",\"role\":\"Email Security\",\"tags\":[\"dmz\",\"email\",\"security\"],\"notes\":[\"Proofpoint Email Gateway\",\"Spam/malware filtering\"],\"mac\":\"00:50:56:CC:04:01\",\"rackUnit\":14,\"uHeight\":\"1\",\"layer\":\"security\",\"assignedRack\":\"dmz-rack\",\"rackCapacity\":\"24\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"dns-external-1\":{\"shape\":\"circle\",\"name\":\"External DNS 1\",\"ip\":\"172.16.0.53\",\"role\":\"External DNS\",\"tags\":[\"dmz\",\"dns\",\"public\"],\"notes\":[\"BIND DNS\",\"Authoritative for corp.com\"],\"mac\":\"00:50:56:CC:05:01\",\"rackUnit\":12,\"uHeight\":\"1\",\"layer\":\"security\",\"assignedRack\":\"dmz-rack\",\"rackCapacity\":\"24\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"dns-external-2\":{\"shape\":\"circle\",\"name\":\"External DNS 2\",\"ip\":\"172.16.0.54\",\"role\":\"External DNS\",\"tags\":[\"dmz\",\"dns\",\"public\"],\"notes\":[\"BIND DNS\",\"Secondary for corp.com\"],\"mac\":\"00:50:56:CC:05:02\",\"rackUnit\":10,\"uHeight\":\"1\",\"layer\":\"security\",\"assignedRack\":\"dmz-rack\",\"rackCapacity\":\"24\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"vcenter\":{\"shape\":\"server\",\"name\":\"vCenter Server\",\"ip\":\"192.168.100.10\",\"role\":\"Virtualization Management\",\"tags\":[\"management\",\"vmware\",\"vcsa\"],\"notes\":[\"vCenter Server Appliance 8.0\",\"Single SSO domain\"],\"mac\":\"00:50:56:DD:01:01\",\"rackUnit\":20,\"uHeight\":\"2\",\"layer\":\"logical\",\"assignedRack\":\"mgmt-rack\",\"rackCapacity\":\"24\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"nsx-manager\":{\"shape\":\"server\",\"name\":\"NSX Manager\",\"ip\":\"192.168.100.15\",\"role\":\"Network Virtualization\",\"tags\":[\"management\",\"vmware\",\"nsx\"],\"notes\":[\"NSX-T 4.1 Manager Cluster\"],\"mac\":\"00:50:56:DD:02:01\",\"rackUnit\":17,\"uHeight\":\"2\",\"layer\":\"logical\",\"assignedRack\":\"mgmt-rack\",\"rackCapacity\":\"24\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"siem-server\":{\"shape\":\"server\",\"name\":\"SIEM Server\",\"ip\":\"192.168.100.50\",\"role\":\"Security Monitoring\",\"tags\":[\"management\",\"security\",\"splunk\"],\"notes\":[\"Splunk Enterprise\",\"Security monitoring\"],\"mac\":\"00:50:56:DD:03:01\",\"rackUnit\":14,\"uHeight\":\"2\",\"layer\":\"logical\",\"assignedRack\":\"mgmt-rack\",\"rackCapacity\":\"24\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"nms-server\":{\"shape\":\"server\",\"name\":\"Network Monitoring\",\"ip\":\"192.168.100.60\",\"role\":\"Network Management\",\"tags\":[\"management\",\"monitoring\",\"prtg\"],\"notes\":[\"PRTG Network Monitor\",\"5000 sensors\"],\"mac\":\"00:50:56:DD:04:01\",\"rackUnit\":11,\"uHeight\":\"1\",\"layer\":\"logical\",\"assignedRack\":\"mgmt-rack\",\"rackCapacity\":\"24\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"jump-server\":{\"shape\":\"server\",\"name\":\"Jump Server\",\"ip\":\"192.168.100.100\",\"role\":\"Bastion Host\",\"tags\":[\"management\",\"security\",\"bastion\"],\"notes\":[\"Windows Server 2022\",\"MFA enabled\"],\"mac\":\"00:50:56:DD:05:01\",\"rackUnit\":9,\"uHeight\":\"1\",\"layer\":\"logical\",\"assignedRack\":\"mgmt-rack\",\"rackCapacity\":\"24\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"ipam-server\":{\"shape\":\"server\",\"name\":\"IPAM/DDI\",\"ip\":\"192.168.100.70\",\"role\":\"IP Management\",\"tags\":[\"management\",\"dns\",\"dhcp\"],\"notes\":[\"Infoblox DDI\",\"DNS/DHCP/IPAM\"],\"mac\":\"00:50:56:DD:06:01\",\"rackUnit\":7,\"uHeight\":\"2\",\"layer\":\"logical\",\"assignedRack\":\"mgmt-rack\",\"rackCapacity\":\"24\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"wlc-primary\":{\"shape\":\"wifi\",\"name\":\"WLC Primary\",\"ip\":\"10.20.0.1\",\"role\":\"Wireless Controller\",\"tags\":[\"wireless\",\"cisco\",\"9800\"],\"notes\":[\"Cisco C9800-40\",\"Primary controller\"],\"mac\":\"00:1A:2B:WL:01:01\",\"rackUnit\":\"\",\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"wlc-secondary\":{\"shape\":\"wifi\",\"name\":\"WLC Secondary\",\"ip\":\"10.20.0.2\",\"role\":\"Wireless Controller\",\"tags\":[\"wireless\",\"cisco\",\"9800\"],\"notes\":[\"Cisco C9800-40\",\"HA Secondary\"],\"mac\":\"00:1A:2B:WL:01:02\",\"rackUnit\":\"\",\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"mobile-zone-hq\":{\"shape\":\"phone\",\"name\":\"HQ Mobile Zone\",\"ip\":\"10.20.10.0/24\",\"role\":\"Mobile Device Zone\",\"tags\":[\"wireless\",\"byod\",\"mobile\"],\"notes\":[\"Corporate BYOD\",\"MDM enrolled devices\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"mobile-zone-guest\":{\"shape\":\"phone\",\"name\":\"Guest WiFi Zone\",\"ip\":\"10.30.0.0/24\",\"role\":\"Guest Network\",\"tags\":[\"wireless\",\"guest\",\"isolated\"],\"notes\":[\"Captive portal\",\"Internet only\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"mobile-zone-iot\":{\"shape\":\"phone\",\"name\":\"IoT Device Zone\",\"ip\":\"10.40.0.0/24\",\"role\":\"IoT Network\",\"tags\":[\"wireless\",\"iot\",\"building\"],\"notes\":[\"Building automation\",\"Smart devices\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"branch-router-ny\":{\"shape\":\"router\",\"name\":\"NYC Branch Router\",\"ip\":\"10.100.0.1\",\"role\":\"Branch Gateway\",\"tags\":[\"branch\",\"nyc\",\"sd-wan\"],\"notes\":[\"Cisco Viptela vEdge\",\"SD-WAN enabled\"],\"mac\":\"00:1A:2B:BR:01:01\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"branch-router-la\":{\"shape\":\"router\",\"name\":\"LA Branch Router\",\"ip\":\"10.101.0.1\",\"role\":\"Branch Gateway\",\"tags\":[\"branch\",\"la\",\"sd-wan\"],\"notes\":[\"Cisco Viptela vEdge\",\"SD-WAN enabled\"],\"mac\":\"00:1A:2B:BR:02:01\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"branch-router-chi\":{\"shape\":\"router\",\"name\":\"Chicago Branch Router\",\"ip\":\"10.102.0.1\",\"role\":\"Branch Gateway\",\"tags\":[\"branch\",\"chicago\",\"sd-wan\"],\"notes\":[\"Cisco Viptela vEdge\",\"SD-WAN enabled\"],\"mac\":\"00:1A:2B:BR:03:01\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"branch-router-lon\":{\"shape\":\"router\",\"name\":\"London Branch Router\",\"ip\":\"10.200.0.1\",\"role\":\"Branch Gateway\",\"tags\":[\"branch\",\"london\",\"sd-wan\"],\"notes\":[\"Cisco Viptela vEdge\",\"EMEA region\"],\"mac\":\"00:1A:2B:BR:04:01\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"branch-router-tokyo\":{\"shape\":\"router\",\"name\":\"Tokyo Branch Router\",\"ip\":\"10.201.0.1\",\"role\":\"Branch Gateway\",\"tags\":[\"branch\",\"tokyo\",\"sd-wan\"],\"notes\":[\"Cisco Viptela vEdge\",\"APAC region\"],\"mac\":\"00:1A:2B:BR:05:01\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"cloud-aws\":{\"shape\":\"cloud\",\"name\":\"AWS Cloud\",\"ip\":\"vpc-0a1b2c3d\",\"role\":\"Public Cloud\",\"tags\":[\"cloud\",\"aws\",\"hybrid\"],\"notes\":[\"AWS US-East-1\",\"VPC peering to HQ\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"logical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"cloud-azure\":{\"shape\":\"cloud\",\"name\":\"Azure Cloud\",\"ip\":\"vnet-corp-prod\",\"role\":\"Public Cloud\",\"tags\":[\"cloud\",\"azure\",\"hybrid\"],\"notes\":[\"Azure East US 2\",\"ExpressRoute\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"logical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"cloud-gcp\":{\"shape\":\"cloud\",\"name\":\"GCP Cloud\",\"ip\":\"vpc-gcp-corp\",\"role\":\"Public Cloud\",\"tags\":[\"cloud\",\"gcp\",\"dev\"],\"notes\":[\"GCP us-central1\",\"Dev/Test workloads\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"logical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"isp-primary\":{\"shape\":\"globe\",\"name\":\"ISP Primary\",\"ip\":\"203.0.113.1\",\"role\":\"Internet Uplink\",\"tags\":[\"wan\",\"internet\",\"primary\"],\"notes\":[\"AT&T MPLS\",\"1 Gbps dedicated\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"isp-secondary\":{\"shape\":\"vm\",\"name\":\"ISP Secondary\",\"ip\":\"198.51.100.1\",\"role\":\"Internet Uplink\",\"tags\":[\"wan\",\"internet\",\"backup\"],\"notes\":[\"Verizon Business\",\"500 Mbps backup\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null,\"rotation\":-17},\"dc-internal-1\":{\"shape\":\"circle\",\"name\":\"DC1 Int DNS\",\"ip\":\"10.10.0.53\",\"role\":\"Internal DNS/AD\",\"tags\":[\"dns\",\"ad\",\"dc1\"],\"notes\":[\"Windows Server 2022\",\"Primary DC\"],\"mac\":\"00:50:56:AD:01:01\",\"rackUnit\":26,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a1\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"dc-internal-2\":{\"shape\":\"circle\",\"name\":\"DC2 Int DNS\",\"ip\":\"10.10.1.53\",\"role\":\"Internal DNS/AD\",\"tags\":[\"dns\",\"ad\",\"dc2\"],\"notes\":[\"Windows Server 2022\",\"Secondary DC\"],\"mac\":\"00:50:56:AD:01:02\",\"rackUnit\":26,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"app-server-1\":{\"shape\":\"server\",\"name\":\"App Server 01\",\"ip\":\"10.10.0.101\",\"role\":\"Application\",\"tags\":[\"app\",\"iis\",\"web\"],\"notes\":[\"Windows Server 2022\",\"IIS Application\"],\"mac\":\"00:50:56:AP:01:01\",\"rackUnit\":24,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a1\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"app-server-2\":{\"shape\":\"server\",\"name\":\"App Server 02\",\"ip\":\"10.10.0.102\",\"role\":\"Application\",\"tags\":[\"app\",\"iis\",\"web\"],\"notes\":[\"Windows Server 2022\",\"IIS Application\"],\"mac\":\"00:50:56:AP:01:02\",\"rackUnit\":22,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a1\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"db-server-1\":{\"shape\":\"database\",\"name\":\"SQL Server 01\",\"ip\":\"10.10.0.201\",\"role\":\"Database\",\"tags\":[\"db\",\"sql\",\"primary\"],\"notes\":[\"SQL Server 2022 Enterprise\",\"AlwaysOn Primary\"],\"mac\":\"00:50:56:DB:01:01\",\"rackUnit\":20,\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a1\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"db-server-2\":{\"shape\":\"database\",\"name\":\"SQL Server 02\",\"ip\":\"10.10.1.201\",\"role\":\"Database\",\"tags\":[\"db\",\"sql\",\"secondary\"],\"notes\":[\"SQL Server 2022 Enterprise\",\"AlwaysOn Secondary\"],\"mac\":\"00:50:56:DB:01:02\",\"rackUnit\":24,\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"k8s-master-1\":{\"shape\":\"hexagon\",\"name\":\"K8s Master 1\",\"ip\":\"10.10.1.50\",\"role\":\"Container Orchestration\",\"tags\":[\"kubernetes\",\"master\",\"container\"],\"notes\":[\"K8s Control Plane\",\"etcd member\"],\"mac\":\"00:50:56:K8:01:01\",\"rackUnit\":21,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"k8s-master-2\":{\"shape\":\"hexagon\",\"name\":\"K8s Master 2\",\"ip\":\"10.10.1.51\",\"role\":\"Container Orchestration\",\"tags\":[\"kubernetes\",\"master\",\"container\"],\"notes\":[\"K8s Control Plane\",\"etcd member\"],\"mac\":\"00:50:56:K8:01:02\",\"rackUnit\":19,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"k8s-master-3\":{\"shape\":\"hexagon\",\"name\":\"K8s Master 3\",\"ip\":\"10.10.1.52\",\"role\":\"Container Orchestration\",\"tags\":[\"kubernetes\",\"master\",\"container\"],\"notes\":[\"K8s Control Plane\",\"etcd member\"],\"mac\":\"00:50:56:K8:01:03\",\"rackUnit\":17,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"k8s-worker-1\":{\"shape\":\"server\",\"name\":\"K8s Worker 1\",\"ip\":\"10.10.1.60\",\"role\":\"Container Workload\",\"tags\":[\"kubernetes\",\"worker\",\"container\"],\"notes\":[\"K8s Worker Node\",\"64GB RAM\"],\"mac\":\"00:50:56:K8:02:01\",\"rackUnit\":15,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"k8s-worker-2\":{\"shape\":\"server\",\"name\":\"K8s Worker 2\",\"ip\":\"10.10.1.61\",\"role\":\"Container Workload\",\"tags\":[\"kubernetes\",\"worker\",\"container\"],\"notes\":[\"K8s Worker Node\",\"64GB RAM\"],\"mac\":\"00:50:56:K8:02:02\",\"rackUnit\":13,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"k8s-worker-3\":{\"shape\":\"server\",\"name\":\"K8s Worker 3\",\"ip\":\"10.10.1.62\",\"role\":\"Container Workload\",\"tags\":[\"kubernetes\",\"worker\",\"container\"],\"notes\":[\"K8s Worker Node\",\"64GB RAM\"],\"mac\":\"00:50:56:K8:02:03\",\"rackUnit\":11,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"k8s-worker-4\":{\"shape\":\"server\",\"name\":\"K8s Worker 4\",\"ip\":\"10.10.1.63\",\"role\":\"Container Workload\",\"tags\":[\"kubernetes\",\"worker\",\"container\"],\"notes\":[\"K8s Worker Node\",\"64GB RAM\"],\"mac\":\"00:50:56:K8:02:04\",\"rackUnit\":9,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"proxy-server-1\":{\"shape\":\"server\",\"name\":\"Proxy Server 1\",\"ip\":\"10.5.0.10\",\"role\":\"Web Proxy\",\"tags\":[\"proxy\",\"squid\",\"filtering\"],\"notes\":[\"Squid Proxy\",\"Content filtering\"],\"mac\":\"00:50:56:PX:01:01\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"security\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"proxy-server-2\":{\"shape\":\"server\",\"name\":\"Proxy Server 2\",\"ip\":\"10.5.0.11\",\"role\":\"Web Proxy\",\"tags\":[\"proxy\",\"squid\",\"filtering\"],\"notes\":[\"Squid Proxy\",\"HA pair\"],\"mac\":\"00:50:56:PX:01:02\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"security\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"vpn-concentrator\":{\"shape\":\"firewall\",\"name\":\"VPN Concentrator\",\"ip\":\"10.0.5.1\",\"role\":\"Remote Access VPN\",\"tags\":[\"vpn\",\"remote\",\"security\"],\"notes\":[\"Cisco ASA 5555-X\",\"AnyConnect SSL VPN\"],\"mac\":\"00:1A:2B:VP:01:01\",\"rackUnit\":\"\",\"uHeight\":\"2\",\"layer\":\"security\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"nac-server\":{\"shape\":\"server\",\"name\":\"NAC Server\",\"ip\":\"10.5.5.10\",\"role\":\"Network Access Control\",\"tags\":[\"nac\",\"ise\",\"802.1x\"],\"notes\":[\"Cisco ISE 3.1\",\"RADIUS/TACACS+\"],\"mac\":\"00:50:56:NA:01:01\",\"rackUnit\":\"\",\"uHeight\":\"2\",\"layer\":\"security\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"print-server\":{\"shape\":\"server\",\"name\":\"Print Server\",\"ip\":\"10.10.0.150\",\"role\":\"Print Services\",\"tags\":[\"print\",\"windows\",\"services\"],\"notes\":[\"Windows Print Server\",\"50+ printers\"],\"mac\":\"00:50:56:PR:01:01\",\"rackUnit\":18,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a1\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"file-server\":{\"shape\":\"database\",\"name\":\"File Server\",\"ip\":\"10.10.0.160\",\"role\":\"File Services\",\"tags\":[\"file\",\"smb\",\"dfs\"],\"notes\":[\"Windows File Server\",\"DFS namespace\"],\"mac\":\"00:50:56:FS:01:01\",\"rackUnit\":16,\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a1\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"ca-server\":{\"shape\":\"server\",\"name\":\"Certificate Authority\",\"ip\":\"192.168.100.80\",\"role\":\"PKI Infrastructure\",\"tags\":[\"pki\",\"ca\",\"security\"],\"notes\":[\"Windows CA\",\"Enterprise Root CA\"],\"mac\":\"00:50:56:CA:01:01\",\"rackUnit\":5,\"uHeight\":\"1\",\"layer\":\"logical\",\"assignedRack\":\"mgmt-rack\",\"rackCapacity\":\"24\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"sccm-server\":{\"shape\":\"server\",\"name\":\"SCCM Server\",\"ip\":\"192.168.100.90\",\"role\":\"Endpoint Management\",\"tags\":[\"sccm\",\"patching\",\"software\"],\"notes\":[\"MECM Primary Site\",\"Software deployment\"],\"mac\":\"00:50:56:SC:01:01\",\"rackUnit\":3,\"uHeight\":\"2\",\"layer\":\"logical\",\"assignedRack\":\"mgmt-rack\",\"rackCapacity\":\"24\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"voip-cluster\":{\"shape\":\"phone\",\"name\":\"VoIP Cluster\",\"ip\":\"10.50.0.0/24\",\"role\":\"Voice Services\",\"tags\":[\"voip\",\"cisco\",\"ucm\"],\"notes\":[\"Cisco UCM Cluster\",\"3000 endpoints\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"application\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"video-conf\":{\"shape\":\"laptop\",\"name\":\"Video Conference\",\"ip\":\"10.51.0.0/24\",\"role\":\"Video Services\",\"tags\":[\"video\",\"webex\",\"teams\"],\"notes\":[\"Webex/Teams integration\",\"Meeting rooms\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"application\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"security-cameras\":{\"shape\":\"camera\",\"name\":\"Security Cameras\",\"ip\":\"10.60.0.0/24\",\"role\":\"Physical Security\",\"tags\":[\"cctv\",\"surveillance\",\"security\"],\"notes\":[\"150+ IP cameras\",\"30-day retention\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"nvr-cluster\":{\"shape\":\"server\",\"name\":\"NVR Cluster\",\"ip\":\"10.60.0.10\",\"role\":\"Video Recording\",\"tags\":[\"nvr\",\"surveillance\",\"storage\"],\"notes\":[\"Milestone XProtect\",\"500TB storage\"],\"mac\":\"00:50:56:NV:01:01\",\"rackUnit\":15,\"uHeight\":\"4\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-b2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"dev-server-1\":{\"shape\":\"server\",\"name\":\"Dev Server 1\",\"ip\":\"10.80.0.10\",\"role\":\"Development\",\"tags\":[\"dev\",\"gitlab\",\"ci-cd\",{\"type\":\"icon\",\"library\":\"selfhst\",\"name\":\"dokku\"}],\"notes\":[\"GitLab Server\",\"CI/CD pipelines\"],\"mac\":\"00:50:56:DV:01:01\",\"rackUnit\":\"\",\"uHeight\":\"2\",\"layer\":\"application\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"dev-server-2\":{\"shape\":\"server\",\"name\":\"Dev Server 2\",\"ip\":\"10.80.0.11\",\"role\":\"Development\",\"tags\":[\"dev\",\"jenkins\",\"ci-cd\"],\"notes\":[\"Jenkins Server\",\"Build automation\"],\"mac\":\"00:50:56:DV:01:02\",\"rackUnit\":\"\",\"uHeight\":\"2\",\"layer\":\"application\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"test-environment\":{\"shape\":\"shield\",\"name\":\"Test Environment\",\"ip\":\"10.81.0.0/24\",\"role\":\"QA/Testing\",\"tags\":[\"test\",\"qa\",\"staging\"],\"notes\":[\"Staging environment\",\"Pre-prod validation\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"application\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null,\"rotation\":-36},\"erp-system\":{\"shape\":\"database\",\"name\":\"ERP System\",\"ip\":\"10.90.0.10\",\"role\":\"Business Application\",\"tags\":[\"erp\",\"sap\",\"business\"],\"notes\":[\"SAP S/4HANA\",\"Financial/HR systems\"],\"mac\":\"00:50:56:ER:01:01\",\"rackUnit\":\"\",\"uHeight\":\"4\",\"layer\":\"application\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"crm-system\":{\"shape\":\"database\",\"name\":\"CRM System\",\"ip\":\"10.91.0.10\",\"role\":\"Business Application\",\"tags\":[\"crm\",\"salesforce\",\"business\"],\"notes\":[\"Salesforce integration\",\"Sales/Marketing\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"application\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"endpoint-1000\":{\"shape\":\"laptop\",\"name\":\"Corporate Endpoints\",\"ip\":\"10.70.0.0/22\",\"role\":\"User Workstations\",\"tags\":[\"endpoints\",\"workstations\",\"users\"],\"notes\":[\"~1000 corporate laptops\",\"Windows 11\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"dist-switch-floor1\":{\"shape\":\"switch\",\"name\":\"Floor 1 Switch\",\"ip\":\"10.1.1.1\",\"role\":\"Distribution\",\"tags\":[\"distribution\",\"floor-1\",\"access\"],\"notes\":[\"Cisco C9300-48P\",\"PoE+ enabled\"],\"mac\":\"00:1A:2B:FL:01:01\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"dist-switch-floor2\":{\"shape\":\"switch\",\"name\":\"Floor 2 Switch\",\"ip\":\"10.1.2.1\",\"role\":\"Distribution\",\"tags\":[\"distribution\",\"floor-2\",\"access\"],\"notes\":[\"Cisco C9300-48P\",\"PoE+ enabled\"],\"mac\":\"00:1A:2B:FL:02:01\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"dist-switch-floor3\":{\"shape\":\"switch\",\"name\":\"Floor 3 Switch\",\"ip\":\"10.1.3.1\",\"role\":\"Distribution\",\"tags\":[\"distribution\",\"floor-3\",\"access\"],\"notes\":[\"Cisco C9300-48P\",\"PoE+ enabled\"],\"mac\":\"00:1A:2B:FL:03:01\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"dist-switch-floor4\":{\"shape\":\"switch\",\"name\":\"Floor 4 Switch\",\"ip\":\"10.1.4.1\",\"role\":\"Distribution\",\"tags\":[\"distribution\",\"floor-4\",\"access\"],\"notes\":[\"Cisco C9300-48P\",\"PoE+ enabled\"],\"mac\":\"00:1A:2B:FL:04:01\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"ap-floor1-zone1\":{\"shape\":\"wifi\",\"name\":\"AP Floor 1 Zone 1\",\"ip\":\"10.20.1.10\",\"role\":\"Wireless Access\",\"tags\":[\"wifi\",\"ap\",\"floor-1\"],\"notes\":[\"Cisco 9120AX\",\"Wi-Fi 6\"],\"mac\":\"00:1A:2B:AP:01:01\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"ap-floor2-zone1\":{\"shape\":\"wifi\",\"name\":\"AP Floor 2 Zone 1\",\"ip\":\"10.20.2.10\",\"role\":\"Wireless Access\",\"tags\":[\"wifi\",\"ap\",\"floor-2\"],\"notes\":[\"Cisco 9120AX\",\"Wi-Fi 6\"],\"mac\":\"00:1A:2B:AP:02:01\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"ap-floor3-zone1\":{\"shape\":\"wifi\",\"name\":\"AP Floor 3 Zone 1\",\"ip\":\"10.20.3.10\",\"role\":\"Wireless Access\",\"tags\":[\"wifi\",\"ap\",\"floor-3\"],\"notes\":[\"Cisco 9120AX\",\"Wi-Fi 6\"],\"mac\":\"00:1A:2B:AP:03:01\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"ap-floor4-zone1\":{\"shape\":\"wifi\",\"name\":\"AP Floor 4 Zone 1\",\"ip\":\"10.20.4.10\",\"role\":\"Wireless Access\",\"tags\":[\"wifi\",\"ap\",\"floor-4\"],\"notes\":[\"Cisco 9120AX\",\"Wi-Fi 6\"],\"mac\":\"00:1A:2B:AP:04:01\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"ups-dc-1\":{\"shape\":\"rectangle\",\"name\":\"UPS DC-1\",\"ip\":\"192.168.200.10\",\"role\":\"Power Management\",\"tags\":[\"power\",\"ups\",\"datacenter\"],\"notes\":[\"APC Symmetra\",\"80kVA\",\"30 min runtime\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"ups-dc-2\":{\"shape\":\"rectangle\",\"name\":\"UPS DC-2\",\"ip\":\"192.168.200.11\",\"role\":\"Power Management\",\"tags\":[\"power\",\"ups\",\"datacenter\"],\"notes\":[\"APC Symmetra\",\"80kVA\",\"Redundant\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"pdu-rack-a1\":{\"shape\":\"rectangle\",\"name\":\"PDU Rack A1\",\"ip\":\"192.168.200.21\",\"role\":\"Power Distribution\",\"tags\":[\"power\",\"pdu\",\"rack-a1\"],\"notes\":[\"APC Switched PDU\",\"Per-outlet metering\"],\"mac\":\"\",\"rackUnit\":1,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a1\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"pdu-rack-a2\":{\"shape\":\"rectangle\",\"name\":\"PDU Rack A2\",\"ip\":\"192.168.200.22\",\"role\":\"Power Distribution\",\"tags\":[\"power\",\"pdu\",\"rack-a2\"],\"notes\":[\"APC Switched PDU\",\"Per-outlet metering\"],\"mac\":\"\",\"rackUnit\":1,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"cooling-1\":{\"shape\":\"rectangle\",\"name\":\"CRAC Unit 1\",\"ip\":\"192.168.200.30\",\"role\":\"Cooling\",\"tags\":[\"cooling\",\"hvac\",\"datacenter\"],\"notes\":[\"Liebert CRV\",\"Row-based cooling\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"cooling-2\":{\"shape\":\"rectangle\",\"name\":\"CRAC Unit 2\",\"ip\":\"192.168.200.31\",\"role\":\"Cooling\",\"tags\":[\"cooling\",\"hvac\",\"datacenter\"],\"notes\":[\"Liebert CRV\",\"N+1 redundancy\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"camera-a\":{\"shape\":\"camera\",\"name\":\"camera A\",\"ip\":\"0.0.0.0\",\"role\":\"\",\"tags\":[],\"notes\":[],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"ping\":{\"enabled\":false,\"protocol\":\"http\",\"customUrl\":\"\",\"timeout\":3000,\"status\":\"unknown\",\"lastCheck\":null},\"locked\":false,\"groupId\":null,\"fovEnabled\":true,\"fovRotation\":104,\"fovDistance\":500,\"fovSweep\":60,\"fovSpeed\":10,\"fovAnimate\":true},\"camera-a-copy\":{\"shape\":\"camera\",\"name\":\"camera B\",\"ip\":\"0.0.0.0\",\"role\":\"\",\"tags\":[],\"notes\":[],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"ping\":{\"enabled\":false,\"protocol\":\"http\",\"customUrl\":\"\",\"timeout\":3000,\"status\":\"unknown\",\"lastCheck\":null},\"locked\":false,\"groupId\":null,\"fovEnabled\":true,\"fovRotation\":162,\"fovDistance\":500,\"fovSweep\":60,\"fovSpeed\":10,\"fovAnimate\":false}},\"edges\":{\"list\":[{\"id\":\"isp1-router1\",\"from\":\"isp-primary\",\"to\":\"core-router-1\",\"width\":6,\"color\":\"#10b981\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"Primary WAN link\"],\"fromPort\":\"Gi0/0\",\"toPort\":\"Gi1/0/1\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"isp2-router2\",\"from\":\"isp-secondary\",\"to\":\"core-router-2\",\"width\":6,\"color\":\"#10b981\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"Backup WAN link\"],\"fromPort\":\"Gi0/0\",\"toPort\":\"Gi1/0/1\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"router1-router2\",\"from\":\"core-router-1\",\"to\":\"core-router-2\",\"width\":4,\"color\":\"#f59e0b\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"HSRP Peering\"],\"fromPort\":\"Gi1/0/24\",\"toPort\":\"Gi1/0/24\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"router1-fw1\",\"from\":\"core-router-1\",\"to\":\"fw-external-1\",\"width\":4,\"color\":\"#ef4444\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"router2-fw2\",\"from\":\"core-router-2\",\"to\":\"fw-external-2\",\"width\":4,\"color\":\"#ef4444\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"fw1-fw2\",\"from\":\"fw-external-1\",\"to\":\"fw-external-2\",\"width\":3,\"color\":\"#f59e0b\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"HA heartbeat\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"dashed\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"fw1-coresw1\",\"from\":\"fw-external-1\",\"to\":\"core-switch-1\",\"width\":4,\"color\":\"#475569\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"fw2-coresw2\",\"from\":\"fw-external-2\",\"to\":\"core-switch-2\",\"width\":4,\"color\":\"#475569\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw1-coresw2\",\"from\":\"core-switch-1\",\"to\":\"core-switch-2\",\"width\":5,\"color\":\"#3b82f6\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"VPC peer-link\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw1-fwint\",\"from\":\"core-switch-1\",\"to\":\"fw-internal\",\"width\":3,\"color\":\"#ef4444\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw2-fwint\",\"from\":\"core-switch-2\",\"to\":\"fw-internal\",\"width\":3,\"color\":\"#ef4444\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw1-racka1\",\"from\":\"core-switch-1\",\"to\":\"dc-rack-a1\",\"width\":4,\"color\":\"#475569\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw2-racka1\",\"from\":\"core-switch-2\",\"to\":\"dc-rack-a1\",\"width\":4,\"color\":\"#475569\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw1-racka2\",\"from\":\"core-switch-1\",\"to\":\"dc-rack-a2\",\"width\":4,\"color\":\"#475569\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw2-racka2\",\"from\":\"core-switch-2\",\"to\":\"dc-rack-a2\",\"width\":4,\"color\":\"#475569\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw1-rackb1\",\"from\":\"core-switch-1\",\"to\":\"dc-rack-b1\",\"width\":4,\"color\":\"#475569\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw2-rackb1\",\"from\":\"core-switch-2\",\"to\":\"dc-rack-b1\",\"width\":4,\"color\":\"#475569\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw1-rackb2\",\"from\":\"core-switch-1\",\"to\":\"dc-rack-b2\",\"width\":4,\"color\":\"#475569\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw2-rackb2\",\"from\":\"core-switch-2\",\"to\":\"dc-rack-b2\",\"width\":4,\"color\":\"#475569\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"fw1-dmz\",\"from\":\"fw-external-1\",\"to\":\"dmz-rack\",\"width\":3,\"color\":\"#f59e0b\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"DMZ segment\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"fw2-dmz\",\"from\":\"fw-external-2\",\"to\":\"dmz-rack\",\"width\":3,\"color\":\"#f59e0b\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"DMZ segment\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw1-mgmt\",\"from\":\"core-switch-1\",\"to\":\"mgmt-rack\",\"width\":3,\"color\":\"#8b5cf6\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"OOB management\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw1-wlc1\",\"from\":\"core-switch-1\",\"to\":\"wlc-primary\",\"width\":3,\"color\":\"#06b6d4\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw2-wlc2\",\"from\":\"core-switch-2\",\"to\":\"wlc-secondary\",\"width\":3,\"color\":\"#06b6d4\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\",\"animate\":true},{\"id\":\"wlc1-wlc2\",\"from\":\"wlc-primary\",\"to\":\"wlc-secondary\",\"width\":2,\"color\":\"#f59e0b\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"HA pair\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"dashed\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"wlc1-mobile-hq\",\"from\":\"wlc-primary\",\"to\":\"mobile-zone-hq\",\"width\":2,\"color\":\"#06b6d4\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"wlc1-mobile-guest\",\"from\":\"wlc-primary\",\"to\":\"mobile-zone-guest\",\"width\":2,\"color\":\"#06b6d4\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"wlc1-mobile-iot\",\"from\":\"wlc-primary\",\"to\":\"mobile-zone-iot\",\"width\":2,\"color\":\"#06b6d4\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"router1-branch-ny\",\"from\":\"core-router-1\",\"to\":\"branch-router-ny\",\"width\":3,\"color\":\"#a855f7\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"SD-WAN tunnel\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"dashed\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"router1-branch-la\",\"from\":\"core-router-1\",\"to\":\"branch-router-la\",\"width\":3,\"color\":\"#a855f7\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"SD-WAN tunnel\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"dashed\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"router1-branch-chi\",\"from\":\"core-router-1\",\"to\":\"branch-router-chi\",\"width\":3,\"color\":\"#a855f7\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"SD-WAN tunnel\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"dashed\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"router1-branch-lon\",\"from\":\"core-router-1\",\"to\":\"branch-router-lon\",\"width\":3,\"color\":\"#a855f7\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"SD-WAN tunnel\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"dashed\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"router1-branch-tokyo\",\"from\":\"core-router-1\",\"to\":\"branch-router-tokyo\",\"width\":3,\"color\":\"#a855f7\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"SD-WAN tunnel\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"dashed\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"router1-aws\",\"from\":\"core-router-1\",\"to\":\"cloud-aws\",\"width\":3,\"color\":\"#f97316\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"Direct Connect\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"dashed\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"router2-azure\",\"from\":\"core-router-2\",\"to\":\"cloud-azure\",\"width\":3,\"color\":\"#0ea5e9\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"ExpressRoute\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"dashed\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"fwint-gcp\",\"from\":\"fw-internal\",\"to\":\"cloud-gcp\",\"width\":2,\"color\":\"#22c55e\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"VPN tunnel\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"dashed\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw1-floor1\",\"from\":\"core-switch-1\",\"to\":\"dist-switch-floor1\",\"width\":3,\"color\":\"#475569\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw1-floor2\",\"from\":\"core-switch-1\",\"to\":\"dist-switch-floor2\",\"width\":3,\"color\":\"#475569\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw2-floor3\",\"from\":\"core-switch-2\",\"to\":\"dist-switch-floor3\",\"width\":3,\"color\":\"#475569\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw2-floor4\",\"from\":\"core-switch-2\",\"to\":\"dist-switch-floor4\",\"width\":3,\"color\":\"#475569\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"floor1-endpoints\",\"from\":\"dist-switch-floor1\",\"to\":\"endpoint-1000\",\"width\":2,\"color\":\"#94a3b8\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"floor1-ap1\",\"from\":\"dist-switch-floor1\",\"to\":\"ap-floor1-zone1\",\"width\":2,\"color\":\"#06b6d4\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"floor2-ap2\",\"from\":\"dist-switch-floor2\",\"to\":\"ap-floor2-zone1\",\"width\":2,\"color\":\"#06b6d4\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"floor3-ap3\",\"from\":\"dist-switch-floor3\",\"to\":\"ap-floor3-zone1\",\"width\":2,\"color\":\"#06b6d4\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"floor4-ap4\",\"from\":\"dist-switch-floor4\",\"to\":\"ap-floor4-zone1\",\"width\":2,\"color\":\"#06b6d4\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"fwint-proxy1\",\"from\":\"fw-internal\",\"to\":\"proxy-server-1\",\"width\":2,\"color\":\"#ef4444\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"fwint-proxy2\",\"from\":\"fw-internal\",\"to\":\"proxy-server-2\",\"width\":2,\"color\":\"#ef4444\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"fwext1-vpn\",\"from\":\"fw-external-1\",\"to\":\"vpn-concentrator\",\"width\":3,\"color\":\"#8b5cf6\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw1-nac\",\"from\":\"core-switch-1\",\"to\":\"nac-server\",\"width\":2,\"color\":\"#f59e0b\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw1-voip\",\"from\":\"core-switch-1\",\"to\":\"voip-cluster\",\"width\":3,\"color\":\"#22c55e\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw2-video\",\"from\":\"core-switch-2\",\"to\":\"video-conf\",\"width\":3,\"color\":\"#22c55e\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw1-cameras\",\"from\":\"core-switch-1\",\"to\":\"security-cameras\",\"width\":2,\"color\":\"#94a3b8\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"fwint-dev1\",\"from\":\"fw-internal\",\"to\":\"dev-server-1\",\"width\":2,\"color\":\"#a855f7\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"fwint-dev2\",\"from\":\"fw-internal\",\"to\":\"dev-server-2\",\"width\":2,\"color\":\"#a855f7\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\",\"animate\":true,\"animationSpeed\":\"1.5\"},{\"id\":\"fwint-test\",\"from\":\"fw-internal\",\"to\":\"test-environment\",\"width\":2,\"color\":\"#a855f7\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw1-erp\",\"from\":\"core-switch-1\",\"to\":\"erp-system\",\"width\":3,\"color\":\"#f59e0b\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"fwext1-crm\",\"from\":\"fw-external-1\",\"to\":\"crm-system\",\"width\":2,\"color\":\"#f59e0b\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"Salesforce cloud\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"dashed\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"ups1-racka1\",\"from\":\"ups-dc-1\",\"to\":\"dc-rack-a1\",\"width\":2,\"color\":\"#fbbf24\",\"direction\":\"forward\",\"type\":\"main\",\"notes\":[\"Power feed A\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"ups2-racka2\",\"from\":\"ups-dc-2\",\"to\":\"dc-rack-a2\",\"width\":2,\"color\":\"#fbbf24\",\"direction\":\"forward\",\"type\":\"main\",\"notes\":[\"Power feed B\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"ups1-rackb1\",\"from\":\"ups-dc-1\",\"to\":\"dc-rack-b1\",\"width\":2,\"color\":\"#fbbf24\",\"direction\":\"forward\",\"type\":\"main\",\"notes\":[\"Power feed A\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\",\"animate\":true,\"animationSpeed\":\"4\"},{\"id\":\"ups2-rackb2\",\"from\":\"ups-dc-2\",\"to\":\"dc-rack-b2\",\"width\":2,\"color\":\"#fbbf24\",\"direction\":\"forward\",\"type\":\"main\",\"notes\":[\"Power feed B\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"cooling1-racka1\",\"from\":\"cooling-1\",\"to\":\"dc-rack-a1\",\"width\":2,\"color\":\"#38bdf8\",\"direction\":\"forward\",\"type\":\"main\",\"notes\":[\"Cooling zone\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"dotted\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"cooling2-rackb1\",\"from\":\"cooling-2\",\"to\":\"dc-rack-b1\",\"width\":2,\"color\":\"#38bdf8\",\"direction\":\"forward\",\"type\":\"main\",\"notes\":[\"Cooling zone\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"dotted\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"custom-1765237881452\",\"type\":\"custom\",\"color\":\"#c800ff\",\"width\":4,\"lineStyle\":\"solid\",\"direction\":\"forward\",\"points\":[{\"x\":3492.3994140625,\"y\":1526.9556884765625},{\"x\":3500.609619140625,\"y\":1830.7386474609375},{\"x\":3303.561279296875,\"y\":1732.2144775390625}],\"notes\":[],\"routing\":\"orthogonal\"}]},\"positions\":{\"core-router-1\":{\"x\":3720.166015625,\"y\":245.9932403564453},\"core-router-2\":{\"x\":2499.883407638303,\"y\":329.99503430389154},\"fw-external-1\":{\"x\":3221.7385182723783,\"y\":1016.1364499992887},\"fw-external-2\":{\"x\":1915.5213706410505,\"y\":224.43528858865443},\"fw-internal\":{\"x\":1746.9168185079352,\"y\":477.5300527221864},\"core-switch-1\":{\"x\":449.39860669455675,\"y\":384.4578707617695},\"core-switch-2\":{\"x\":761.1664921394672,\"y\":180.89283910873155},\"dc-rack-a1\":{\"x\":783.7017241128451,\"y\":647.4086870405963},\"dc-rack-a2\":{\"x\":209.25701628255229,\"y\":228.01593190351014},\"dc-rack-b1\":{\"x\":3184.3186625759854,\"y\":1627.4495531027196},\"dc-rack-b2\":{\"x\":245.37065918741246,\"y\":499.6191264194081},\"dmz-rack\":{\"x\":2176.4105289561007,\"y\":610.8312056412005},\"mgmt-rack\":{\"x\":1601.2987201807314,\"y\":1281.4753424975324},\"esxi-host-01\":{\"x\":2162.2166789540615,\"y\":2608.110619289529},\"esxi-host-02\":{\"x\":2205.94717202368,\"y\":2689.67539624076},\"esxi-host-03\":{\"x\":2154.6015436939074,\"y\":2771.203009774913},\"esxi-host-04\":{\"x\":2195.986926025096,\"y\":2845},\"tor-switch-a1\":{\"x\":2146.8943639962963,\"y\":2845},\"esxi-host-05\":{\"x\":2185.9099961569727,\"y\":2845},\"esxi-host-06\":{\"x\":2139.099728450725,\"y\":2845},\"esxi-host-07\":{\"x\":2175.7223818764883,\"y\":2845},\"esxi-host-08\":{\"x\":2131.2222777148922,\"y\":2845},\"tor-switch-a2\":{\"x\":2165.4301485385085,\"y\":2845},\"san-primary\":{\"x\":2123.2667017518106,\"y\":2845},\"san-secondary\":{\"x\":2155.0394237844876,\"y\":2845},\"fc-switch-1\":{\"x\":2115.2377370375634,\"y\":2845},\"fc-switch-2\":{\"x\":2144.5563938942755,\"y\":2845},\"backup-server-1\":{\"x\":2107.1401637413705,\"y\":2845},\"backup-server-2\":{\"x\":2133.987300103025,\"y\":2845},\"tape-library\":{\"x\":2098.9788028796397,\"y\":2845},\"tor-switch-b1\":{\"x\":2123.338434885373,\"y\":2845},\"tor-switch-b2\":{\"x\":2090.7585134456995,\"y\":2845},\"web-server-1\":{\"x\":2112.6161382091163,\"y\":2845},\"web-server-2\":{\"x\":2082.484189516922,\"y\":2845},\"waf-1\":{\"x\":2101.826793760617,\"y\":2845},\"load-balancer-dmz\":{\"x\":2074.1607573409574,\"y\":2845},\"mail-gateway\":{\"x\":2090.97682514417,\"y\":2845},\"dns-external-1\":{\"x\":2065.7931724028163,\"y\":2845},\"dns-external-2\":{\"x\":2080.0726920576153,\"y\":2845},\"vcenter\":{\"x\":2057.3864164745437,\"y\":2845},\"nsx-manager\":{\"x\":2069.1208864464534,\"y\":2845},\"siem-server\":{\"x\":2048.945494649244,\"y\":2845},\"nms-server\":{\"x\":2058.1279286387635,\"y\":2845},\"jump-server\":{\"x\":2040.4754323612206,\"y\":2845},\"ipam-server\":{\"x\":2047.1003634632284,\"y\":2845},\"wlc-primary\":{\"x\":1575.9723612611924,\"y\":2306.135986328125},\"wlc-secondary\":{\"x\":1468.1361870166274,\"y\":1563.733642578125},\"mobile-zone-hq\":{\"x\":2354.901177346808,\"y\":2806.0078125},\"mobile-zone-guest\":{\"x\":2307.6605605284435,\"y\":2611.047119140625},\"mobile-zone-iot\":{\"x\":2229.397686389302,\"y\":2299.110107421875},\"branch-router-ny\":{\"x\":3151.903101363964,\"y\":633.6580810546875},\"branch-router-la\":{\"x\":3083.8876194705945,\"y\":506.90625},\"branch-router-chi\":{\"x\":3355.02409980103,\"y\":393.1805725097656},\"branch-router-lon\":{\"x\":3113.609823320121,\"y\":260.4093322753906},\"branch-router-tokyo\":{\"x\":3699.3234994733834,\"y\":471.4241027832031},\"cloud-aws\":{\"x\":3436.528122523513,\"y\":545.9614868164062},\"cloud-azure\":{\"x\":2592.566210818907,\"y\":2724.068115234375},\"cloud-gcp\":{\"x\":2827.3183770424234,\"y\":2731.397216796875},\"isp-primary\":{\"x\":3712.192068081962,\"y\":615.64990234375},\"isp-secondary\":{\"x\":3253.9473366098055,\"y\":1993.2629089355469},\"dc-internal-1\":{\"x\":1958.4243458877936,\"y\":2845},\"dc-internal-2\":{\"x\":1963.768951182132,\"y\":2845},\"app-server-1\":{\"x\":1947.3819379304134,\"y\":2845},\"app-server-2\":{\"x\":1955.2862087394126,\"y\":2845},\"db-server-1\":{\"x\":1936.3708569559828,\"y\":2845},\"db-server-2\":{\"x\":1946.8300873488822,\"y\":2845},\"k8s-master-1\":{\"x\":1925.397658583093,\"y\":2845},\"k8s-master-2\":{\"x\":1938.405621494142,\"y\":2845},\"k8s-master-3\":{\"x\":1914.4688758763386,\"y\":2845},\"k8s-worker-1\":{\"x\":1930.017826812177,\"y\":2845},\"k8s-worker-2\":{\"x\":1903.5910154567553,\"y\":2845},\"k8s-worker-3\":{\"x\":1921.6716971072178,\"y\":2845},\"k8s-worker-4\":{\"x\":1892.7705536280016,\"y\":2845},\"proxy-server-1\":{\"x\":1806.1152433697903,\"y\":653.7529296875},\"proxy-server-2\":{\"x\":2937.4207928721535,\"y\":2628.7880859375},\"vpn-concentrator\":{\"x\":3642.252088474593,\"y\":946.7255249023438},\"nac-server\":{\"x\":1153.2626148502184,\"y\":1172.1895751953125},\"print-server\":{\"x\":1896.9328460745962,\"y\":2845},\"file-server\":{\"x\":1860.7177871362182,\"y\":2845},\"ca-server\":{\"x\":1888.8027739274805,\"y\":2845},\"sccm-server\":{\"x\":1850.1909418511675,\"y\":2845},\"voip-cluster\":{\"x\":1777.038465328039,\"y\":1616.8961181640625},\"video-conf\":{\"x\":1993.8373941679588,\"y\":2244.936309814453},\"security-cameras\":{\"x\":1674.413336949044,\"y\":2046.0380859375},\"nvr-cluster\":{\"x\":1829.4110389706402,\"y\":2845},\"dev-server-1\":{\"x\":2800.5894350649614,\"y\":1175.623291015625},\"dev-server-2\":{\"x\":1945.0822182484326,\"y\":1164.5184783935547},\"test-environment\":{\"x\":2932.0863047891075,\"y\":862.4592895507812},\"erp-system\":{\"x\":789.9880103985649,\"y\":473.7113342285156},\"crm-system\":{\"x\":3514.6003232048542,\"y\":1137.7720947265625},\"endpoint-1000\":{\"x\":991.6812012057328,\"y\":2284.42236328125},\"dist-switch-floor1\":{\"x\":654.2091033261356,\"y\":2020.0086669921875},\"dist-switch-floor2\":{\"x\":853.8845527112826,\"y\":1843.2872314453125},\"dist-switch-floor3\":{\"x\":1899.4353951584517,\"y\":1456.5068359375},\"dist-switch-floor4\":{\"x\":488.5289313756234,\"y\":181.47256469726562},\"ap-floor1-zone1\":{\"x\":1140.16846970184,\"y\":2070.2916259765625},\"ap-floor2-zone1\":{\"x\":688.1952143592268,\"y\":2384.4775390625},\"ap-floor3-zone1\":{\"x\":2145.3803027919676,\"y\":1890.2816162109375},\"ap-floor4-zone1\":{\"x\":517.646146409649,\"y\":565.59716796875},\"ups-dc-1\":{\"x\":771.1406786539856,\"y\":295.9266662597656},\"ups-dc-2\":{\"x\":216.2410855890687,\"y\":330.3345947265625},\"pdu-rack-a1\":{\"x\":1804.774444371901,\"y\":2845},\"pdu-rack-a2\":{\"x\":1741.6184034693686,\"y\":2845},\"cooling-1\":{\"x\":245.7080801919958,\"y\":626.1914672851562},\"cooling-2\":{\"x\":1603.293611085831,\"y\":981.0621185302734},\"camera-a\":{\"x\":166.57075412676295,\"y\":145},\"camera-a-copy\":{\"x\":1040.653076171875,\"y\":738.42822265625}},\"sizes\":{\"isp-secondary\":139,\"test-environment\":121,\"dev-server-1\":128,\"core-router-2\":120,\"camera-a\":45,\"camera-a-copy\":45},\"styles\":{\"dc-rack-b2\":{\"all\":{\"circleColor\":\"#ff0000\"}},\"dc-rack-a1\":{\"all\":{\"circleColor\":\"#ff0000\"}},\"dc-rack-b1\":{\"all\":{\"circleColor\":\"#ff0000\",\"titleSize\":59}},\"isp-secondary\":{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"alist\"},\"circleColor\":\"#4d2c58\",\"circleBorder\":\"#000000\",\"titleColor\":\"#006eff\"}},\"core-router-2\":{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"actual-budget\"},\"pingOffsetX\":-15,\"pingOffsetY\":-38}},\"fw-external-1\":{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"anonaddy\"}}},\"cloud-aws\":{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"ansible\"}}},\"isp-primary\":{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"wikidocs\"}}},\"branch-router-tokyo\":{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"adguard-home\"}}},\"core-router-1\":{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"borg\"}}},\"test-environment\":{\"all\":{\"icon\":{\"library\":\"simple\",\"name\":\"apple\"}}},\"dev-server-1\":{\"all\":{\"icon\":{\"library\":\"simple\",\"name\":\"amazonwebservices\"}}}},\"legend\":{\"#10b981\":\"Trusted Lan\",\"#f59e0b\":\"Secure Lan\",\"#ef4444\":\"DMZ\",\"#475569\":\"Main ISP\",\"#3b82f6\":\"Alternate ISP\",\"#8b5cf6\":\"you can edit me too\",\"#06b6d4\":\"you can edit me too\",\"#a855f7\":\"you can edit me too\",\"#f97316\":\"you can edit me too\",\"#0ea5e9\":\"you can edit me too\",\"#22c55e\":\"you can edit me too\",\"#94a3b8\":\"you can edit me too\",\"#fbbf24\":\"you can edit me too\",\"#38bdf8\":\"you can edit me too\",\"#c800ff\":\"you can edit me too\"},\"rects\":{\"list\":[{\"id\":\"rect-1765237540610\",\"x\":2879.214599609375,\"y\":159.71981811523438,\"width\":992.196044921875,\"height\":538.8650817871094,\"color\":\"#f97316\",\"style\":\"filled\",\"lineStyle\":\"solid\",\"notes\":[]},{\"id\":\"rect-1765237681216\",\"x\":448.3926696777344,\"y\":1671.651123046875,\"width\":916.3436584472656,\"height\":924.27734375,\"color\":\"#c800ff\",\"style\":\"outlined\",\"lineStyle\":\"solid\",\"notes\":[]},{\"id\":\"rect-1766437913740\",\"x\":904.5889892578125,\"y\":115.40318298339844,\"width\":110.93878173828125,\"height\":919.6242218017578,\"color\":\"#5215f9\",\"style\":\"filled\",\"lineStyle\":\"wall\",\"notes\":[],\"borderWidth\":13},{\"id\":\"rect-1766437935414\",\"x\":130.93685150146484,\"y\":1072.3624877929688,\"width\":872.9131851196289,\"height\":99.260986328125,\"color\":\"#5215f9\",\"style\":\"filled\",\"lineStyle\":\"wall\",\"notes\":[],\"borderWidth\":13}]},\"texts\":{\"list\":[{\"id\":\"text-1765237828167\",\"x\":3411.458740234375,\"y\":1390.00439453125,\"content\":\"Double click on desktop\\nor long press on mobile\\nto enter rack canvas view\",\"fontSize\":46,\"color\":\"#e2e8f0\",\"fontWeight\":\"bold\",\"fontStyle\":\"italic\",\"textAlign\":\"middle\",\"textDecoration\":\"none\",\"bgColor\":\"#000000\",\"bgEnabled\":false,\"opacity\":1},{\"id\":\"text-1766446595277\",\"x\":654.3878479003906,\"y\":1367.7945556640625,\"content\":\"SITE A\",\"fontSize\":101,\"color\":\"#e2e8f0\",\"fontWeight\":\"normal\",\"fontStyle\":\"normal\",\"textAlign\":\"start\",\"textDecoration\":\"none\",\"bgColor\":\"#000000\",\"bgEnabled\":false,\"opacity\":1},{\"id\":\"text-1766446610211\",\"x\":180.63662719726562,\"y\":1128.822998046875,\"content\":\"SITE A\",\"fontSize\":101,\"color\":\"#e2e8f0\",\"fontWeight\":\"normal\",\"fontStyle\":\"normal\",\"textAlign\":\"start\",\"textDecoration\":\"none\",\"bgColor\":\"#000000\",\"bgEnabled\":false,\"opacity\":1},{\"id\":\"text-1766453024797\",\"x\":968.6458740234375,\"y\":1028.6621398925781,\"content\":\"SITE A\",\"fontSize\":101,\"color\":\"#e2e8f0\",\"fontWeight\":\"normal\",\"fontStyle\":\"normal\",\"textAlign\":\"start\",\"textDecoration\":\"none\",\"bgColor\":\"#000000\",\"bgEnabled\":false,\"opacity\":1,\"rotation\":-89,\"_dragStartX\":972.46826171875,\"_dragStartY\":1009.5499572753906},{\"id\":\"text-1766453070975\",\"x\":613.1589965820312,\"y\":1139.512939453125,\"content\":\"SITE A\",\"fontSize\":101,\"color\":\"#e2e8f0\",\"fontWeight\":\"normal\",\"fontStyle\":\"normal\",\"textAlign\":\"start\",\"textDecoration\":\"none\",\"bgColor\":\"#000000\",\"bgEnabled\":false,\"opacity\":1},{\"id\":\"text-1766453072857\",\"x\":968.64599609375,\"y\":474.40818786621094,\"content\":\"SITE A\",\"fontSize\":101,\"color\":\"#e2e8f0\",\"fontWeight\":\"normal\",\"fontStyle\":\"normal\",\"textAlign\":\"start\",\"textDecoration\":\"none\",\"bgColor\":\"#000000\",\"bgEnabled\":false,\"opacity\":1,\"rotation\":269,\"_dragStartX\":1480.85302734375,\"_dragStartY\":822.2503356933594},{\"id\":\"text-1766458222326\",\"x\":3167.812744140625,\"y\":2190.516357421875,\"content\":\"\",\"fontSize\":18,\"color\":\"#e2e8f0\",\"fontWeight\":\"normal\",\"fontStyle\":\"normal\",\"textAlign\":\"start\",\"textDecoration\":\"none\",\"bgColor\":\"#000000\",\"bgEnabled\":false,\"opacity\":1}]},\"pageState\":{\"title\":\"The One File Corporate\",\"background\":\"\",\"topbarBg\":\"rgba(9, 12, 20, 0.9)\",\"topbarBorder\":\"#1f2533\",\"panel\":\"#0b0e13\",\"panelAlt\":\"#10141b\",\"accent\":\"#4fd1c5\",\"sidebarBg\":\"#10141b\",\"btnBg\":\"#0b0e13\",\"btnText\":\"#e2e8f0\",\"tagFill\":\"#1e293b\",\"tagText\":\"#e2e8f0\",\"tagBorder\":\"#475569\",\"inputBg\":\"#0b0e13\",\"inputText\":\"#e2e8f0\",\"inputBorder\":\"#1f2937\",\"inputFont\":\"Inter, system-ui, sans-serif\",\"inputFontSize\":14,\"toolbarBg\":\"#0f172a\",\"toolbarBorder\":\"#1f2937\",\"toolbarText\":\"#94a3b8\",\"toolbarBtnBg\":\"#0b0e13\",\"toolbarBtnText\":\"#e2e8f0\",\"minimapDots\":\"#94a3b8\",\"canvasHintEnabled\":true,\"canvasHintText\":\"\",\"canvasHintBg\":\"#0f172a\",\"canvasHintColor\":\"#94a3b8\",\"danger\":\"#f56565\",\"textMain\":\"#e2e8f0\",\"textSoft\":\"#94a3b8\",\"topbarHeight\":103,\"sidebarWidth\":350,\"mobileFooterHeight\":40,\"sidebarCollapsed\":false,\"nodeFill\":\"#1e293b\",\"nodeStroke\":\"#475569\",\"nodeTitle\":\"#e2e8f0\",\"nodeSub\":\"#94a3b8\",\"nodeTitleSize\":41,\"nodeSubSize\":27,\"nodeFont\":\"monospace\",\"defaultEdge\":\"#475569\",\"selectionHandle\":\"#f59e0b\",\"selectionHandleSize\":8,\"groupIndicator\":\"#4fd1c5\",\"canvasGradientTop\":\"#1e2532\",\"canvasGradientBottom\":\"#050608\",\"canvasBorder\":\"#475569\",\"canvasGrid\":\"#475569\",\"canvasGridSize\":50,\"canvasGridEnabled\":true,\"rackFrameFill\":\"#0f172a\",\"rackGridEnabled\":true,\"rackFrameStroke\":\"#4fd1c5\",\"rackLineColor\":\"#475569\",\"rackTextColor\":\"#4fd1c5\",\"viewOnly\":false,\"defaultEdgeRouting\":\"orthogonal\",\"animateConnections\":false,\"animationStyle\":\"arrows\",\"animationDirection\":\"all\",\"animationSpeed\":4,\"autoPingEnabled\":false,\"autoPingInterval\":30}},{\"id\":\"tab-1765235136918\",\"name\":\"Homelab 2\",\"nodes\":{\"internet\":{\"shape\":\"stop-sign\",\"name\":\"Internet\",\"ip\":\"0.0.0.0\",\"role\":\"\",\"tags\":[],\"notes\":[],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"internet-copy\":{\"shape\":\"firewall\",\"name\":\"OPNSENSE\",\"ip\":\"0.0.0.0\",\"role\":\"\",\"tags\":[],\"notes\":[],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"opnsense-copy\":{\"shape\":\"firewall\",\"name\":\"Docker\",\"ip\":\"0.0.0.0\",\"role\":\"\",\"tags\":[],\"notes\":[],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"docker-copy\":{\"shape\":\"firewall\",\"name\":\"Docker2\",\"ip\":\"0.0.0.0\",\"role\":\"\",\"tags\":[],\"notes\":[],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"docker-copy-1\":{\"shape\":\"firewall\",\"name\":\"Docker3\",\"ip\":\"0.0.0.0\",\"role\":\"\",\"tags\":[],\"notes\":[],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"docker-copy-2\":{\"shape\":\"firewall\",\"name\":\"Docker 4\",\"ip\":\"0.0.0.0\",\"role\":\"\",\"tags\":[{\"type\":\"icon\",\"library\":\"selfhst\",\"name\":\"docker\"},{\"type\":\"icon\",\"library\":\"selfhst\",\"name\":\"authentik\"},{\"type\":\"icon\",\"library\":\"selfhst\",\"name\":\"immich\"}],\"notes\":[],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"opnsense-copy-1\":{\"shape\":\"firewall\",\"name\":\"OPNSENSE GUEST\",\"ip\":\"0.0.0.0\",\"role\":\"\",\"tags\":[],\"notes\":[],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"phone\":{\"shape\":\"phone\",\"name\":\"Phone\",\"ip\":\"0.0.0.0\",\"role\":\"\",\"tags\":[],\"notes\":[],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"desktop\":{\"shape\":\"pc\",\"name\":\"Desktop\",\"ip\":\"0.0.0.0\",\"role\":\"\",\"tags\":[],\"notes\":[],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"dns\":{\"shape\":\"cloud\",\"name\":\"DNS\",\"ip\":\"0.0.0.0\",\"role\":\"\",\"tags\":[],\"notes\":[],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"racked\":{\"shape\":\"server\",\"name\":\"Racked\",\"ip\":\"\",\"role\":\"Rack\",\"tags\":[],\"notes\":[],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":true,\"locked\":false,\"groupId\":null},\"thermostat\":{\"shape\":\"thermostat\",\"name\":\"Thermostat\",\"ip\":\"0.0.0.0\",\"role\":\"\",\"tags\":[],\"notes\":[],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"video-doorbell\":{\"shape\":\"doorbell\",\"name\":\"Video Doorbell\",\"ip\":\"0.0.0.0\",\"role\":\"\",\"tags\":[],\"notes\":[],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"smart-lock\":{\"shape\":\"smart-lock\",\"name\":\"Smart Lock\",\"ip\":\"0.0.0.0\",\"role\":\"\",\"tags\":[],\"notes\":[],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"smart-bulb\":{\"shape\":\"smart-bulb\",\"name\":\"Smart Bulb\",\"ip\":\"0.0.0.0\",\"role\":\"\",\"tags\":[],\"notes\":[],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"robot-vacuum\":{\"shape\":\"vacuum\",\"name\":\"Robot Vacuum\",\"ip\":\"0.0.0.0\",\"role\":\"\",\"tags\":[],\"notes\":[],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null}},\"edges\":{\"list\":[{\"id\":\"internet-internet-copy-1765238145151\",\"from\":\"internet\",\"to\":\"internet-copy\",\"width\":4,\"color\":\"#55e208\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1},{\"id\":\"internet-copy-opnsense-copy-1765238187451\",\"from\":\"internet-copy\",\"to\":\"opnsense-copy\",\"width\":4,\"color\":\"#4c00ff\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1},{\"id\":\"internet-copy-docker-copy-1765238242477\",\"from\":\"internet-copy\",\"to\":\"docker-copy\",\"width\":4,\"color\":\"#4c00ff\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1},{\"id\":\"internet-copy-docker-copy-1-1765238244637\",\"from\":\"internet-copy\",\"to\":\"docker-copy-1\",\"width\":4,\"color\":\"#4c00ff\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1},{\"id\":\"internet-copy-docker-copy-2-1765238246233\",\"from\":\"internet-copy\",\"to\":\"docker-copy-2\",\"width\":4,\"color\":\"#4c00ff\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1},{\"id\":\"internet-opnsense-copy-1-1765238266117\",\"from\":\"internet\",\"to\":\"opnsense-copy-1\",\"width\":4,\"color\":\"#80ff00\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1},{\"id\":\"opnsense-copy-1-dns-1765238347996\",\"from\":\"opnsense-copy-1\",\"to\":\"dns\",\"width\":4,\"color\":\"#fb00ff\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1},{\"id\":\"dns-desktop-1765238386101\",\"from\":\"dns\",\"to\":\"desktop\",\"width\":4,\"color\":\"#ff00d0\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1},{\"id\":\"phone-dns-1765238391156\",\"from\":\"phone\",\"to\":\"dns\",\"width\":4,\"color\":\"#ff00d0\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1},{\"id\":\"custom-1765239449323\",\"type\":\"custom\",\"color\":\"#f97316\",\"width\":4,\"lineStyle\":\"solid\",\"direction\":\"forward\",\"points\":[{\"x\":2936.464111328125,\"y\":786.07958984375},{\"x\":3184.112060546875,\"y\":887.6153564453125},{\"x\":2763.110107421875,\"y\":981.7216796875}],\"notes\":[]}]},\"positions\":{\"internet\":{\"x\":1757.7735887323333,\"y\":298.77284240722656},\"internet-copy\":{\"x\":2066.9677515897347,\"y\":473.4119134177565},\"opnsense-copy\":{\"x\":1773.8400660428597,\"y\":666.5758233298659},\"docker-copy\":{\"x\":1931.1978950081452,\"y\":782.2775961320921},\"docker-copy-1\":{\"x\":2158.1262397347077,\"y\":767.7122274797483},\"docker-copy-2\":{\"x\":2342.2663764534577,\"y\":631.7681967180296},\"opnsense-copy-1\":{\"x\":2757.879480087803,\"y\":307.6117116091891},\"phone\":{\"x\":3312.857751572178,\"y\":502.58220111114224},\"desktop\":{\"x\":2971.700036728428,\"y\":480.7287465212985},\"dns\":{\"x\":3200.4643189549906,\"y\":320.469591247861},\"racked\":{\"x\":2645.5845448279656,\"y\":970.7820678889219},\"thermostat\":{\"x\":1323.0595481711202,\"y\":574.6132617105841},\"video-doorbell\":{\"x\":1188.4284446554952,\"y\":455.3684191812872},\"smart-lock\":{\"x\":1292.286782057839,\"y\":790.0231738199591},\"smart-bulb\":{\"x\":1496.156899245339,\"y\":716.9377246012091},\"robot-vacuum\":{\"x\":2288.5581443625265,\"y\":978.5069995035528}},\"sizes\":{\"core-router-1\":36,\"internet\":168,\"phone\":121,\"desktop\":147,\"racked\":137,\"docker-copy-2\":82},\"styles\":{\"internet\":{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"amazon-web-services\"},\"circleColor\":\"#db0000\",\"circleBorder\":\"#000000\",\"titleSize\":52,\"subSize\":46}},\"opnsense-copy-1\":{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"opnsense-v1\"}}},\"internet-copy\":{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"opnsense\"}}},\"docker-copy-2\":{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"docker\"}}},\"docker-copy-1\":{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"authportal\"}}},\"docker-copy\":{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"jotty\"}}},\"opnsense-copy\":{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"portainer\"}}},\"racked\":{\"all\":{\"icon\":{\"library\":\"mdi\",\"name\":\"server-security\"},\"circleColor\":\"#010813\",\"circleBorder\":\"#ffffff\"}}},\"legend\":{\"#475569\":\"you can edit me too\",\"#65758b\":\"you can edit me too\",\"#63748c\":\"you can edit me too\",\"#5e6f87\":\"you can edit me too\",\"#586a84\":\"you can edit me too\",\"#4f627d\":\"you can edit me too\",\"#455873\":\"you can edit me too\",\"#3d506c\":\"you can edit me too\",\"#354964\":\"you can edit me too\",\"#2e415c\":\"you can edit me too\",\"#293c56\":\"you can edit me too\",\"#273a53\":\"you can edit me too\",\"#253750\":\"you can edit me too\",\"#23354d\":\"you can edit me too\",\"#203046\":\"you can edit me too\",\"#1e2d43\":\"you can edit me too\",\"#1a283d\":\"you can edit me too\",\"#172435\":\"you can edit me too\",\"#141f2e\":\"you can edit me too\",\"#111a27\":\"you can edit me too\",\"#0f1824\":\"you can edit me too\",\"#0d1521\":\"you can edit me too\",\"#0c131d\":\"you can edit me too\",\"#0c1d1c\":\"you can edit me too\",\"#0c1c1d\":\"you can edit me too\",\"#0c191d\":\"you can edit me too\",\"#0c141d\":\"you can edit me too\",\"#0c0d1d\":\"you can edit me too\",\"#130c1d\":\"you can edit me too\",\"#1b0c1d\":\"you can edit me too\",\"#1d0c17\":\"you can edit me too\",\"#1d0c10\":\"you can edit me too\",\"#1d0c0c\":\"you can edit me too\",\"#3b1b1b\":\"you can edit me too\",\"#3c1a1a\":\"you can edit me too\",\"#3f1c1c\":\"you can edit me too\",\"#401c1c\":\"you can edit me too\",\"#451c1c\":\"you can edit me too\",\"#461b1b\":\"you can edit me too\",\"#4c1a1a\":\"you can edit me too\",\"#521919\":\"you can edit me too\",\"#571919\":\"you can edit me too\",\"#5d1818\":\"you can edit me too\",\"#631717\":\"you can edit me too\",\"#651515\":\"you can edit me too\",\"#6a1616\":\"you can edit me too\",\"#6f1515\":\"you can edit me too\",\"#711414\":\"you can edit me too\",\"#761414\":\"you can edit me too\",\"#771313\":\"you can edit me too\",\"#7c1313\":\"you can edit me too\",\"#811313\":\"you can edit me too\",\"#821212\":\"you can edit me too\",\"#871212\":\"you can edit me too\",\"#881111\":\"you can edit me too\",\"#8d1111\":\"you can edit me too\",\"#8e1010\":\"you can edit me too\",\"#8f0f0f\":\"you can edit me too\",\"#900e0e\":\"you can edit me too\",\"#8e0b0b\":\"you can edit me too\",\"#8c0d0d\":\"you can edit me too\",\"#880c0c\":\"you can edit me too\",\"#830c0c\":\"you can edit me too\",\"#7e0c0c\":\"you can edit me too\",\"#790c0c\":\"you can edit me too\",\"#730c0c\":\"you can edit me too\",\"#6f0b0b\":\"you can edit me too\",\"#0b6f64\":\"you can edit me too\",\"#0b6f5f\":\"you can edit me too\",\"#0b6f56\":\"you can edit me too\",\"#0b6f49\":\"you can edit me too\",\"#0b6f31\":\"you can edit me too\",\"#0b6f1f\":\"you can edit me too\",\"#0b6f0d\":\"you can edit me too\",\"#176f0b\":\"you can edit me too\",\"#266f0b\":\"you can edit me too\",\"#296f0b\":\"you can edit me too\",\"#2e6f0b\":\"you can edit me too\",\"#1a2d10\":\"you can edit me too\",\"#1c3111\":\"you can edit me too\",\"#213814\":\"you can edit me too\",\"#233c15\":\"you can edit me too\",\"#254017\":\"you can edit me too\",\"#294918\":\"you can edit me too\",\"#2b4d1a\":\"you can edit me too\",\"#2d511a\":\"you can edit me too\",\"#315a1b\":\"you can edit me too\",\"#35631c\":\"you can edit me too\",\"#37681d\":\"you can edit me too\",\"#3b721d\":\"you can edit me too\",\"#3f7b1e\":\"you can edit me too\",\"#42851e\":\"you can edit me too\",\"#46901d\":\"you can edit me too\",\"#499a1d\":\"you can edit me too\",\"#4b9f1d\":\"you can edit me too\",\"#4ca61c\":\"you can edit me too\",\"#50b01c\":\"you can edit me too\",\"#51b71a\":\"you can edit me too\",\"#50b918\":\"you can edit me too\",\"#51c115\":\"you can edit me too\",\"#53c615\":\"you can edit me too\",\"#53c814\":\"you can edit me too\",\"#52c913\":\"you can edit me too\",\"#54d011\":\"you can edit me too\",\"#53d110\":\"you can edit me too\",\"#55d510\":\"you can edit me too\",\"#55d70f\":\"you can edit me too\",\"#54d80e\":\"you can edit me too\",\"#54da0b\":\"you can edit me too\",\"#56df0c\":\"you can edit me too\",\"#53db0a\":\"you can edit me too\",\"#55e00b\":\"you can edit me too\",\"#55e109\":\"you can edit me too\",\"#55e208\":\"ISP LINE\",\"#4c00ff\":\"MY Guest NETWORK\",\"#80ff00\":\"you can edit me too\",\"#3b4234\":\"you can edit me too\",\"#3a3442\":\"you can edit me too\",\"#3b3442\":\"you can edit me too\",\"#3c3442\":\"you can edit me too\",\"#3d3442\":\"you can edit me too\",\"#3e3442\":\"you can edit me too\",\"#3f3442\":\"you can edit me too\",\"#403442\":\"you can edit me too\",\"#413442\":\"you can edit me too\",\"#653d66\":\"you can edit me too\",\"#683f69\":\"you can edit me too\",\"#6c416c\":\"you can edit me too\",\"#6f4370\":\"you can edit me too\",\"#704270\":\"you can edit me too\",\"#734474\":\"you can edit me too\",\"#784479\":\"you can edit me too\",\"#7d447e\":\"you can edit me too\",\"#7e437f\":\"you can edit me too\",\"#834384\":\"you can edit me too\",\"#844285\":\"you can edit me too\",\"#89418b\":\"you can edit me too\",\"#8e428f\":\"you can edit me too\",\"#904091\":\"you can edit me too\",\"#923e93\":\"you can edit me too\",\"#973e98\":\"you can edit me too\",\"#943c96\":\"you can edit me too\",\"#993c9a\":\"you can edit me too\",\"#963a98\":\"you can edit me too\",\"#973899\":\"you can edit me too\",\"#99369b\":\"you can edit me too\",\"#9a359c\":\"you can edit me too\",\"#9b349d\":\"you can edit me too\",\"#9d329f\":\"you can edit me too\",\"#9e31a0\":\"you can edit me too\",\"#a02fa2\":\"you can edit me too\",\"#9d2d9f\":\"you can edit me too\",\"#9f2ba1\":\"you can edit me too\",\"#a129a3\":\"you can edit me too\",\"#a327a5\":\"you can edit me too\",\"#a525a7\":\"you can edit me too\",\"#a723a9\":\"you can edit me too\",\"#a921ab\":\"you can edit me too\",\"#ab1fad\":\"you can edit me too\",\"#ad1daf\":\"you can edit me too\",\"#ae1cb0\":\"you can edit me too\",\"#b019b3\":\"you can edit me too\",\"#b118b4\":\"you can edit me too\",\"#b316b6\":\"you can edit me too\",\"#b816bb\":\"you can edit me too\",\"#b514b8\":\"you can edit me too\",\"#ba14bd\":\"you can edit me too\",\"#b712ba\":\"you can edit me too\",\"#bb13be\":\"you can edit me too\",\"#b811bb\":\"you can edit me too\",\"#be10c1\":\"you can edit me too\",\"#bb0ebe\":\"you can edit me too\",\"#bd0cc0\":\"you can edit me too\",\"#be0bc1\":\"you can edit me too\",\"#c108c4\":\"you can edit me too\",\"#be06c1\":\"you can edit me too\",\"#c103c4\":\"you can edit me too\",\"#c301c6\":\"you can edit me too\",\"#c400c7\":\"you can edit me too\",\"#c900cc\":\"you can edit me too\",\"#ce00d1\":\"you can edit me too\",\"#d300d6\":\"you can edit me too\",\"#d800db\":\"you can edit me too\",\"#dd00e0\":\"you can edit me too\",\"#e200e6\":\"you can edit me too\",\"#ec00f0\":\"you can edit me too\",\"#f100f5\":\"you can edit me too\",\"#f600fa\":\"you can edit me too\",\"#fb00ff\":\"you can edit me too\",\"#ff00d0\":\"iPhone (always guest iPhone)\",\"#f97316\":\"you can edit me too\"},\"rects\":{\"list\":[{\"id\":\"rect-1765238219615\",\"x\":2680.053955078125,\"y\":251.44879150390625,\"width\":814.10400390625,\"height\":389.26678466796875,\"color\":\"#ec0999\",\"style\":\"filled\",\"lineStyle\":\"solid\",\"notes\":[]}]},\"texts\":{\"list\":[{\"id\":\"text-1765238422602\",\"x\":2466.35986328125,\"y\":741.6801147460938,\"content\":\"Double click on desktop\\nor long press on mobile\\nto enter rack canvas view\",\"fontSize\":40,\"color\":\"#e2e8f0\",\"fontWeight\":\"normal\",\"fontStyle\":\"normal\",\"textAlign\":\"start\",\"textDecoration\":\"none\",\"bgColor\":\"#000000\",\"bgEnabled\":false,\"opacity\":1}]},\"pageState\":{\"title\":\"The One File\",\"background\":\"\",\"topbarBg\":\"rgba(9, 12, 20, 0.9)\",\"topbarBorder\":\"#1f2533\",\"panel\":\"#2f0e0e\",\"panelAlt\":\"#10141b\",\"accent\":\"#a75252\",\"sidebarBg\":\"#10141b\",\"btnBg\":\"#0b0e13\",\"btnText\":\"#e2e8f0\",\"tagFill\":\"#1e293b\",\"tagText\":\"#e2e8f0\",\"tagBorder\":\"#475569\",\"inputBg\":\"#0b0e13\",\"inputText\":\"#e2e8f0\",\"inputBorder\":\"#1f2937\",\"inputFont\":\"Inter, system-ui, sans-serif\",\"inputFontSize\":14,\"toolbarBg\":\"#441215\",\"toolbarBorder\":\"#1f2937\",\"toolbarText\":\"#94a3b8\",\"toolbarBtnBg\":\"#0b0e13\",\"toolbarBtnText\":\"#e2e8f0\",\"minimapDots\":\"#94a3b8\",\"canvasHintEnabled\":true,\"canvasHintText\":\"\",\"canvasHintBg\":\"#0f172a\",\"canvasHintColor\":\"#94a3b8\",\"danger\":\"#f56565\",\"textMain\":\"#e2e8f0\",\"textSoft\":\"#94a3b8\",\"topbarHeight\":112,\"sidebarWidth\":350,\"mobileFooterHeight\":40,\"sidebarCollapsed\":false,\"nodeFill\":\"#1e293b\",\"nodeStroke\":\"#475569\",\"nodeTitle\":\"#e2e8f0\",\"nodeSub\":\"#94a3b8\",\"nodeTitleSize\":18,\"nodeSubSize\":13,\"nodeFont\":\"Inter, system-ui, sans-serif\",\"defaultEdge\":\"#475569\",\"selectionHandle\":\"#f59e0b\",\"selectionHandleSize\":8,\"groupIndicator\":\"#4fd1c5\",\"canvasGradientTop\":\"#1e2532\",\"canvasGradientBottom\":\"#050608\",\"canvasBorder\":\"#475569\",\"canvasGrid\":\"#475569\",\"canvasGridSize\":50,\"canvasGridEnabled\":true,\"rackFrameFill\":\"#0f172a\",\"rackGridEnabled\":true,\"rackFrameStroke\":\"#4fd1c5\",\"rackLineColor\":\"#475569\",\"rackTextColor\":\"#4fd1c5\",\"viewOnly\":false,\"defaultEdgeRouting\":\"curved\",\"animateConnections\":false,\"animationStyle\":\"arrows\",\"animationDirection\":\"all\",\"animationSpeed\":1.5}}],\"currentTabIndex\":0,\"encryptedSections\":{},\"auditLog\":[{\"timestamp\":1766459400738,\"type\":\"tab\",\"description\":\"Switched to tab: Corporate Site B\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459392434,\"type\":\"export\",\"description\":\"Exported CSV: the-one-file.csv\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766459386369,\"type\":\"export\",\"description\":\"Exported Markdown: the-one-file.md\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766459379962,\"type\":\"export\",\"description\":\"Exported JSON: the-one-file.json\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766459374396,\"type\":\"save\",\"description\":\"File saved: the-one-file.html\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766459370112,\"type\":\"tab\",\"description\":\"Switched to tab: Homelab 2\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766459361896,\"type\":\"save\",\"description\":\"File saved: the-one-file-corporate.html\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459352785,\"type\":\"node\",\"description\":\"rotate node\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459352343,\"type\":\"node\",\"description\":\"rotate node\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459352224,\"type\":\"node\",\"description\":\"rotate node\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459351722,\"type\":\"node\",\"description\":\"rotate node\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459351541,\"type\":\"node\",\"description\":\"rotate node\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459350380,\"type\":\"node\",\"description\":\"resize node\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459350178,\"type\":\"node\",\"description\":\"resize node\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459350049,\"type\":\"node\",\"description\":\"resize node\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459346233,\"type\":\"node\",\"description\":\"change shape\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459335960,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459335846,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459335742,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459335630,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459335398,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459335292,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459335188,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459332894,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459332780,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459332661,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459332556,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459332450,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459332346,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459331643,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459331492,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459331378,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459331274,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459330996,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459330868,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459330764,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459330637,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459327262,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459327136,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459326544,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459326438,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459326334,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459326176,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459325232,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459325088,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459324279,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459323835,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459323732,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459323200,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459323093,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459322989,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459322883,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459322780,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459321176,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459321070,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459320748,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459320642,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459320492,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459319706,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459319600,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459319055,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459318467,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459318363,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459318258,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459317846,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459317742,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459317464,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459317314,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459313457,\"type\":\"node\",\"description\":\"change shape\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459310142,\"type\":\"node\",\"description\":\"change shape\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459306160,\"type\":\"node\",\"description\":\"rotate node\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459305289,\"type\":\"node\",\"description\":\"rotate node\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459305132,\"type\":\"node\",\"description\":\"rotate node\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459304675,\"type\":\"node\",\"description\":\"rotate node\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459304530,\"type\":\"node\",\"description\":\"rotate node\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459304396,\"type\":\"node\",\"description\":\"rotate node\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459304290,\"type\":\"node\",\"description\":\"rotate node\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459304157,\"type\":\"node\",\"description\":\"rotate node\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459303660,\"type\":\"node\",\"description\":\"rotate node\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459303534,\"type\":\"node\",\"description\":\"rotate node\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459303414,\"type\":\"node\",\"description\":\"rotate node\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459303247,\"type\":\"node\",\"description\":\"rotate node\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459303144,\"type\":\"node\",\"description\":\"rotate node\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459303002,\"type\":\"node\",\"description\":\"rotate node\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459302875,\"type\":\"node\",\"description\":\"rotate node\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459302725,\"type\":\"node\",\"description\":\"rotate node\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459302613,\"type\":\"node\",\"description\":\"rotate node\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459302507,\"type\":\"node\",\"description\":\"rotate node\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459301997,\"type\":\"node\",\"description\":\"rotate node\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459301893,\"type\":\"node\",\"description\":\"rotate node\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458459721,\"type\":\"tab\",\"description\":\"Switched to tab: Corporate Site B\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458438687,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766458438583,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766458438437,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766458438333,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766458438187,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766458438083,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766458437937,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766458437833,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766458437687,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766458437583,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766458437437,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766458437333,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766458437187,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766458436932,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766458435139,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766458434986,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766458434840,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766458434736,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766458434590,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766458434486,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766458434340,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766458434236,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766458434090,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766458433986,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766458433840,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766458433736,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766458433590,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766458433334,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766458429157,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766458429053,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766458428947,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766458426794,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766458426691,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766458426584,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766458426481,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766458423513,\"type\":\"node\",\"description\":\"change shape\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766458421278,\"type\":\"node\",\"description\":\"change shape\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766458416555,\"type\":\"node\",\"description\":\"change shape\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766458404891,\"type\":\"node\",\"description\":\"add node\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766458392272,\"type\":\"node\",\"description\":\"add node\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766458378068,\"type\":\"node\",\"description\":\"add node\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766458367460,\"type\":\"node\",\"description\":\"add node\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766458356226,\"type\":\"node\",\"description\":\"add node\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766458338198,\"type\":\"tab\",\"description\":\"Switched to tab: Homelab 2\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766458258865,\"type\":\"save\",\"description\":\"File saved: the-one-file-corporate.html\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458249051,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458248926,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458248793,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458248683,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458248556,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458248451,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458248325,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458248221,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458248092,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458247989,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458247885,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458247784,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458247284,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458246701,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458246523,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458246410,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458246129,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458245955,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458245737,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458245627,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458245425,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458245247,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458245133,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458244923,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458244741,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458244313,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458244198,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458244055,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458243873,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458243637,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458243399,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458243218,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458241018,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458237254,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458235033,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458234835,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458234694,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458234425,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458227773,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458227623,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458227441,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458227279,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458227155,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458226967,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458226847,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458226733,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458226563,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458226421,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458222326,\"type\":\"text\",\"description\":\"add text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458213989,\"type\":\"connection\",\"description\":\"delete edge\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458209437,\"type\":\"text\",\"description\":\"delete text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458195427,\"type\":\"import\",\"description\":\"Imported JSON: the-one-file-corporate.json (107 nodes, 65 connections)\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455847368,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455844534,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455844054,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455843762,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455843560,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455843371,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455843162,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455842852,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455842747,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455842601,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455842449,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455842348,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455842098,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455841678,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455841236,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455841053,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455840901,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455840650,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455839427,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455839234,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455839061,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455837247,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455837081,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455836893,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455836377,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455836198,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455835455,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455834630,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455831880,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455831676,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455831451,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455830817,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455830687,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455830176,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455830048,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455829944,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455829816,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455378795,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455378693,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455378459,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455378316,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455378180,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455378069,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455377956,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455377677,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455377558,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455377448,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455377318,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455377209,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453090534,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453090317,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453090213,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453090112,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453090009,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453089903,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453088895,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453088793,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453088689,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453088584,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453088480,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453088250,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453087236,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453086725,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453086485,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453086373,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453086142,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453086043,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453072857,\"type\":\"text\",\"description\":\"paste text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453070975,\"type\":\"text\",\"description\":\"paste text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453054439,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453053127,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453052450,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453052106,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453051948,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453051806,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453051334,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453050207,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453042725,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453042179,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453041797,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453041570,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453039703,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453039291,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453039168,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453039065,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453038481,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453038365,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453038237,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453038105,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453038001,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453037850,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453037745,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453037495,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453037378,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453037182,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453037078,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453036972,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453036860,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453036147,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453035945,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453035825,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453035720,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453035443,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453035337,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453035233,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453035127,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453035026,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453034917,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453031063,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453030955,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453030833,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453030732,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453030225,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453030104,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453029968,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453029796,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453029474,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453024797,\"type\":\"text\",\"description\":\"paste text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766451118553,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766450929324,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766450817210,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766450257424,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766450255024,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766450254395,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766450253241,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766450251598,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766450250392,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766450248756,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766450244072,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766450242166,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766450240998,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766450236492,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766450233672,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766450232384,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766450231012,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766450230254,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766450229302,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766450228132,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446610211,\"type\":\"text\",\"description\":\"paste text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446604849,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446604702,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446604550,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446604404,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446604305,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446604204,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446604099,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446603952,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446603849,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446603702,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446603599,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446603452,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446603348,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446603202,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446603099,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446602953,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446602850,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446602702,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446602600,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446602453,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446602349,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446602204,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446602101,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446602000,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446601848,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446601702,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446601601,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446601452,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446601301,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446601154,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446601049,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446600948,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446600802,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446600550,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446598595,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446598461,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446598171,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446598017,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446597219,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446595278,\"type\":\"text\",\"description\":\"add text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445633355,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445632515,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445631735,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445630757,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445627846,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445625085,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445618645,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445617784,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445608998,\"type\":\"edit\",\"description\":\"edit zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445608720,\"type\":\"edit\",\"description\":\"edit zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445608540,\"type\":\"edit\",\"description\":\"edit zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445608376,\"type\":\"edit\",\"description\":\"edit zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445608204,\"type\":\"edit\",\"description\":\"edit zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445608038,\"type\":\"edit\",\"description\":\"edit zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445607852,\"type\":\"edit\",\"description\":\"edit zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445607678,\"type\":\"edit\",\"description\":\"edit zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445607506,\"type\":\"edit\",\"description\":\"edit zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445607319,\"type\":\"edit\",\"description\":\"edit zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445607154,\"type\":\"edit\",\"description\":\"edit zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445604410,\"type\":\"edit\",\"description\":\"edit zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445604244,\"type\":\"edit\",\"description\":\"edit zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445604066,\"type\":\"edit\",\"description\":\"edit zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445603900,\"type\":\"edit\",\"description\":\"edit zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445603743,\"type\":\"edit\",\"description\":\"edit zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445603563,\"type\":\"edit\",\"description\":\"edit zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445603406,\"type\":\"edit\",\"description\":\"edit zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445603226,\"type\":\"edit\",\"description\":\"edit zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445603052,\"type\":\"edit\",\"description\":\"edit zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445602880,\"type\":\"edit\",\"description\":\"edit zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445602641,\"type\":\"edit\",\"description\":\"edit zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445576567,\"type\":\"edit\",\"description\":\"edit edge animation speed\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445570290,\"type\":\"edit\",\"description\":\"edit edge animation speed\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445567192,\"type\":\"edit\",\"description\":\"edit edge animate\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445566766,\"type\":\"edit\",\"description\":\"edit edge animate\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445565520,\"type\":\"edit\",\"description\":\"edit edge animate\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445398115,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445390895,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445385694,\"type\":\"edit\",\"description\":\"toggle fov animation\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445383241,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445382911,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445381695,\"type\":\"edit\",\"description\":\"edit node name\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445375383,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445374665,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445373273,\"type\":\"node\",\"description\":\"paste node\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445372205,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438157980,\"type\":\"edit\",\"description\":\"edit edge animate\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438157430,\"type\":\"edit\",\"description\":\"edit edge animation speed\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438152691,\"type\":\"edit\",\"description\":\"edit edge animate\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438151948,\"type\":\"edit\",\"description\":\"edit edge animate\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438151286,\"type\":\"edit\",\"description\":\"edit edge animate\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438146174,\"type\":\"edit\",\"description\":\"edit edge animate\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438145649,\"type\":\"edit\",\"description\":\"edit edge animate\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438144555,\"type\":\"edit\",\"description\":\"edit edge animate\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438143655,\"type\":\"edit\",\"description\":\"edit edge animate\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438142504,\"type\":\"edit\",\"description\":\"edit edge animation speed\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438130077,\"type\":\"edit\",\"description\":\"edit edge animate\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438129561,\"type\":\"edit\",\"description\":\"edit edge animate\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438128772,\"type\":\"edit\",\"description\":\"edit edge animate\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438128398,\"type\":\"edit\",\"description\":\"edit edge animate\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438122820,\"type\":\"edit\",\"description\":\"edit edge animate\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438122062,\"type\":\"edit\",\"description\":\"edit edge animate\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438119836,\"type\":\"edit\",\"description\":\"edit edge animate\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438119588,\"type\":\"edit\",\"description\":\"edit edge animate\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438095045,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438093965,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438062827,\"type\":\"edit\",\"description\":\"toggle fov animation\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438047679,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438044161,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438041852,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438039668,\"type\":\"edit\",\"description\":\"resize node\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438039562,\"type\":\"edit\",\"description\":\"resize node\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438039421,\"type\":\"edit\",\"description\":\"resize node\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438039260,\"type\":\"edit\",\"description\":\"resize node\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438039150,\"type\":\"edit\",\"description\":\"resize node\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438039039,\"type\":\"edit\",\"description\":\"resize node\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438028508,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438021410,\"type\":\"edit\",\"description\":\"toggle fov\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438019234,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438017562,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438014356,\"type\":\"node\",\"description\":\"add node\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766437981696,\"type\":\"edit\",\"description\":\"apply routing to all\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766437966551,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766437964879,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766437963627,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766437961813,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766437961193,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766437957989,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766437956467,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766437953437,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766437952239,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766437950807,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766437944990,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766437943699,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766437935414,\"type\":\"zone\",\"description\":\"draw zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766437919019,\"type\":\"zone\",\"description\":\"delete zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766437917758,\"type\":\"zone\",\"description\":\"draw zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766437913740,\"type\":\"zone\",\"description\":\"draw zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766437882832,\"type\":\"import\",\"description\":\"Imported JSON: theonefile-networkening-corporate-demo.json (105 nodes, 65 connections)\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766263279163,\"type\":\"tab\",\"description\":\"Switched to tab: Corporate Site B\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766263270414,\"type\":\"export\",\"description\":\"Exported JSON: the-one-file.json\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766263260682,\"type\":\"save\",\"description\":\"File saved: the-one-file.html\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766263259518,\"type\":\"tab\",\"description\":\"Switched to tab: Homelab 2\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766263249401,\"type\":\"save\",\"description\":\"File saved: the-one-file-corporate.html\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766263246362,\"type\":\"import\",\"description\":\"Imported JSON: theonefile-networkening-corporate-demo.json (105 nodes, 65 connections)\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766190721141,\"type\":\"save\",\"description\":\"File saved: the-one-file-corporate.html\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766190717499,\"type\":\"tab\",\"description\":\"Switched to tab: Corporate Site B\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766190710946,\"type\":\"save\",\"description\":\"File saved: the-one-file.html\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766190705273,\"type\":\"save\",\"description\":\"File saved: the-one-file.html\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766190703463,\"type\":\"tab\",\"description\":\"Switched to tab: Homelab 2\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766190695709,\"type\":\"save\",\"description\":\"File saved: the-one-file-corporate.html\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766190688417,\"type\":\"import\",\"description\":\"Imported JSON: theonefile-networkening-corporate-demo.json (105 nodes, 65 connections)\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1765402888416,\"type\":\"save\",\"description\":\"File saved: the-one-file-corporate.html\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1765402884873,\"type\":\"tab\",\"description\":\"Switched to tab: Corporate Site B\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1765402878108,\"type\":\"save\",\"description\":\"File saved: the-one-file.html\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1765402866440,\"type\":\"save\",\"description\":\"File saved: the-one-file.html\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1765402865008,\"type\":\"tab\",\"description\":\"Switched to tab: Homelab 2\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1765402860428,\"type\":\"save\",\"description\":\"File saved: the-one-file-corporate.html\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1765402858103,\"type\":\"import\",\"description\":\"Imported JSON: theonefile-networkening-corporate-demo.json (105 nodes, 65 connections)\",\"details\":{},\"tab\":\"Corporate Site B\"}],\"savedStyleSets\":[]}\n#\n# The One File Corporate - Node List\n# Exported from The One File on 2025-12-23T03:10:03.226Z\nname,ip,role,shape,tags,layer,mac,rackUnit,uHeight,assignedRack,rackCapacity,isRack,locked,groupId,x,y,size,notes,styles\nCore Router 1,10.0.0.1,Core Routing,router,core;tier-1;redundant,physical,00:1A:2B:3C:4D:01,,2,,42,false,false,,3720,246,50,Primary core router|BGP peering enabled,\"{\"\"all\"\":{\"\"icon\"\":{\"\"library\"\":\"\"selfhst\"\",\"\"name\"\":\"\"borg\"\"}}}\"\nCore Router 2,10.0.0.2,Core Routing,router,core;tier-1;redundant,physical,00:1A:2B:3C:4D:02,,2,,42,false,false,,2500,330,120,Secondary core router|HSRP standby,\"{\"\"all\"\":{\"\"icon\"\":{\"\"library\"\":\"\"selfhst\"\",\"\"name\"\":\"\"actual-budget\"\"},\"\"pingOffsetX\"\":-15,\"\"pingOffsetY\"\":-38}}\"\nExternal FW 1,10.0.1.1,Perimeter Security,firewall,security;perimeter;ha-pair,security,00:1A:2B:3C:4D:10,,2,,42,false,false,,3222,1016,50,Palo Alto PA-5250|Active node,\"{\"\"all\"\":{\"\"icon\"\":{\"\"library\"\":\"\"selfhst\"\",\"\"name\"\":\"\"anonaddy\"\"}}}\"\nExternal FW 2,10.0.1.2,Perimeter Security,firewall,security;perimeter;ha-pair,security,00:1A:2B:3C:4D:11,,2,,42,false,false,,1916,224,50,Palo Alto PA-5250|Passive node,\nInternal FW,10.0.2.1,Internal Segmentation,firewall,security;internal,security,00:1A:2B:3C:4D:12,,2,,42,false,false,,1747,478,50,East-West traffic inspection,\nCore Switch 1,10.0.10.1,Core Switching,switch,core;layer3;redundant,physical,00:1A:2B:3C:4D:20,,2,,42,false,false,,449,384,50,Cisco Nexus 9000|VPC Domain 1,\nCore Switch 2,10.0.10.2,Core Switching,switch,core;layer3;redundant,physical,00:1A:2B:3C:4D:21,,2,,42,false,false,,761,181,50,Cisco Nexus 9000|VPC Domain 1,\nDC Rack A1,10.10.0.0/24,Data Center Rack,server,datacenter;row-a;production,physical,,,1,,42,true,false,,784,647,50,\"Row A, Position 1|Primary compute\",\"{\"\"all\"\":{\"\"circleColor\"\":\"\"#ff0000\"\"}}\"\nDC Rack A2,10.10.1.0/24,Data Center Rack,server,datacenter;row-a;production,physical,,,1,,42,true,false,,209,228,50,\"Row A, Position 2|Primary compute\",\nDC Rack B1,10.10.2.0/24,Data Center Rack,server,datacenter;row-b;storage,physical,,,1,,42,true,false,,3184,1627,50,\"Row B, Position 1|Storage systems\",\"{\"\"all\"\":{\"\"circleColor\"\":\"\"#ff0000\"\",\"\"titleSize\"\":59}}\"\nDC Rack B2,10.10.3.0/24,Data Center Rack,server,datacenter;row-b;storage,physical,,,1,,42,true,false,,245,500,50,\"Row B, Position 2|Storage systems\",\"{\"\"all\"\":{\"\"circleColor\"\":\"\"#ff0000\"\"}}\"\nDMZ Rack,172.16.0.0/24,DMZ Infrastructure,server,dmz;security;public-facing;[object Object];[object Object],security,,,1,,24,true,false,,2176,611,50,Isolated DMZ zone|Public-facing services,\nManagement Rack,192.168.100.0/24,Management Infrastructure,server,management;oob;noc,logical,,,1,,24,true,false,,1601,1281,50,Out-of-band management|NOC equipment,\nESXi Host 01,10.10.0.11,Hypervisor,server,vmware;compute;cluster-a,physical,00:50:56:AA:01:01,38,2,dc-rack-a1,42,false,false,,2162,2608,50,Dell PowerEdge R750|512GB RAM|vSphere 8.0,\nESXi Host 02,10.10.0.12,Hypervisor,server,vmware;compute;cluster-a,physical,00:50:56:AA:01:02,35,2,dc-rack-a1,42,false,false,,2206,2690,50,Dell PowerEdge R750|512GB RAM|vSphere 8.0,\nESXi Host 03,10.10.0.13,Hypervisor,server,vmware;compute;cluster-a,physical,00:50:56:AA:01:03,32,2,dc-rack-a1,42,false,false,,2155,2771,50,Dell PowerEdge R750|512GB RAM|vSphere 8.0,\nESXi Host 04,10.10.0.14,Hypervisor,server,vmware;compute;cluster-a,physical,00:50:56:AA:01:04,29,2,dc-rack-a1,42,false,false,,2196,2845,50,Dell PowerEdge R750|512GB RAM|vSphere 8.0,\nToR Switch A1,10.10.0.1,Top of Rack,switch,tor;access;rack-a1,physical,00:1A:2B:3C:5D:01,42,1,dc-rack-a1,42,false,false,,2147,2845,50,Cisco Nexus 93180YC-FX|48x25G ports,\nESXi Host 05,10.10.1.11,Hypervisor,server,vmware;compute;cluster-b,physical,00:50:56:AA:02:01,38,2,dc-rack-a2,42,false,false,,2186,2845,50,Dell PowerEdge R750|768GB RAM|vSphere 8.0,\nESXi Host 06,10.10.1.12,Hypervisor,server,vmware;compute;cluster-b,physical,00:50:56:AA:02:02,35,2,dc-rack-a2,42,false,false,,2139,2845,50,Dell PowerEdge R750|768GB RAM|vSphere 8.0,\nESXi Host 07,10.10.1.13,Hypervisor,server,vmware;compute;cluster-b,physical,00:50:56:AA:02:03,32,2,dc-rack-a2,42,false,false,,2176,2845,50,Dell PowerEdge R750|768GB RAM|vSphere 8.0,\nESXi Host 08,10.10.1.14,Hypervisor,server,vmware;compute;cluster-b,physical,00:50:56:AA:02:04,29,2,dc-rack-a2,42,false,false,,2131,2845,50,Dell PowerEdge R750|768GB RAM|vSphere 8.0,\nToR Switch A2,10.10.1.1,Top of Rack,switch,tor;access;rack-a2,physical,00:1A:2B:3C:5D:02,42,1,dc-rack-a2,42,false,false,,2165,2845,50,Cisco Nexus 93180YC-FX|48x25G ports,\nSAN Primary,10.10.2.10,Primary Storage,database,storage;san;netapp,physical,00:A0:98:AA:01:01,36,6,dc-rack-b1,42,false,false,,2123,2845,50,NetApp AFF A400|500TB Raw|FC 32Gb,\nSAN Secondary,10.10.2.11,Secondary Storage,database,storage;san;netapp,physical,00:A0:98:AA:01:02,28,6,dc-rack-b1,42,false,false,,2155,2845,50,NetApp AFF A400|500TB Raw|FC 32Gb,\nFC Switch 1,10.10.2.1,Fibre Channel,switch,storage;fc;fabric-a,physical,00:1A:2B:FC:01:01,42,1,dc-rack-b1,42,false,false,,2115,2845,50,Brocade G620|Fabric A,\nFC Switch 2,10.10.2.2,Fibre Channel,switch,storage;fc;fabric-b,physical,00:1A:2B:FC:01:02,41,1,dc-rack-b1,42,false,false,,2145,2845,50,Brocade G620|Fabric B,\nBackup Server 1,10.10.3.10,Backup Infrastructure,server,backup;veeam;protection,physical,00:50:56:BB:01:01,36,2,dc-rack-b2,42,false,false,,2107,2845,50,Veeam Backup Server|Dell R740xd|200TB,\nBackup Server 2,10.10.3.11,Backup Infrastructure,server,backup;veeam;protection,physical,00:50:56:BB:01:02,33,2,dc-rack-b2,42,false,false,,2134,2845,50,Veeam Backup Server|Dell R740xd|200TB,\nTape Library,10.10.3.20,Archival Storage,database,backup;tape;lto9,physical,00:50:56:BB:02:01,20,10,dc-rack-b2,42,false,false,,2099,2845,50,IBM TS4500|LTO-9|Long-term archive,\nToR Switch B1,10.10.2.3,Top of Rack,switch,tor;access;rack-b1,physical,00:1A:2B:3C:5D:03,40,1,dc-rack-b1,42,false,false,,2123,2845,50,Cisco Nexus 93180YC-FX,\nToR Switch B2,10.10.3.1,Top of Rack,switch,tor;access;rack-b2,physical,00:1A:2B:3C:5D:04,42,1,dc-rack-b2,42,false,false,,2091,2845,50,Cisco Nexus 93180YC-FX,\nWeb Server 1,172.16.0.11,Web Frontend,server,dmz;web;nginx,security,00:50:56:CC:01:01,20,1,dmz-rack,24,false,false,,2113,2845,50,NGINX reverse proxy|Public facing,\nWeb Server 2,172.16.0.12,Web Frontend,server,dmz;web;nginx,security,00:50:56:CC:01:02,18,1,dmz-rack,24,false,false,,2082,2845,50,NGINX reverse proxy|Public facing,\nWAF Appliance,172.16.0.5,Web Application Firewall,firewall,dmz;security;waf,security,00:50:56:CC:02:01,22,2,dmz-rack,24,false,false,,2102,2845,50,F5 BIG-IP ASM|OWASP protection,\nDMZ Load Balancer,172.16.0.3,Load Balancing,switch,dmz;lb;f5,security,00:50:56:CC:03:01,16,2,dmz-rack,24,false,false,,2074,2845,50,F5 BIG-IP LTM|VIP: 172.16.0.100,\nMail Gateway,172.16.0.25,Email Security,server,dmz;email;security,security,00:50:56:CC:04:01,14,1,dmz-rack,24,false,false,,2091,2845,50,Proofpoint Email Gateway|Spam/malware filtering,\nExternal DNS 1,172.16.0.53,External DNS,circle,dmz;dns;public,security,00:50:56:CC:05:01,12,1,dmz-rack,24,false,false,,2066,2845,50,BIND DNS|Authoritative for corp.com,\nExternal DNS 2,172.16.0.54,External DNS,circle,dmz;dns;public,security,00:50:56:CC:05:02,10,1,dmz-rack,24,false,false,,2080,2845,50,BIND DNS|Secondary for corp.com,\nvCenter Server,192.168.100.10,Virtualization Management,server,management;vmware;vcsa,logical,00:50:56:DD:01:01,20,2,mgmt-rack,24,false,false,,2057,2845,50,vCenter Server Appliance 8.0|Single SSO domain,\nNSX Manager,192.168.100.15,Network Virtualization,server,management;vmware;nsx,logical,00:50:56:DD:02:01,17,2,mgmt-rack,24,false,false,,2069,2845,50,NSX-T 4.1 Manager Cluster,\nSIEM Server,192.168.100.50,Security Monitoring,server,management;security;splunk,logical,00:50:56:DD:03:01,14,2,mgmt-rack,24,false,false,,2049,2845,50,Splunk Enterprise|Security monitoring,\nNetwork Monitoring,192.168.100.60,Network Management,server,management;monitoring;prtg,logical,00:50:56:DD:04:01,11,1,mgmt-rack,24,false,false,,2058,2845,50,PRTG Network Monitor|5000 sensors,\nJump Server,192.168.100.100,Bastion Host,server,management;security;bastion,logical,00:50:56:DD:05:01,9,1,mgmt-rack,24,false,false,,2040,2845,50,Windows Server 2022|MFA enabled,\nIPAM/DDI,192.168.100.70,IP Management,server,management;dns;dhcp,logical,00:50:56:DD:06:01,7,2,mgmt-rack,24,false,false,,2047,2845,50,Infoblox DDI|DNS/DHCP/IPAM,\nWLC Primary,10.20.0.1,Wireless Controller,wifi,wireless;cisco;9800,physical,00:1A:2B:WL:01:01,,2,,42,false,false,,1576,2306,50,Cisco C9800-40|Primary controller,\nWLC Secondary,10.20.0.2,Wireless Controller,wifi,wireless;cisco;9800,physical,00:1A:2B:WL:01:02,,2,,42,false,false,,1468,1564,50,Cisco C9800-40|HA Secondary,\nHQ Mobile Zone,10.20.10.0/24,Mobile Device Zone,phone,wireless;byod;mobile,physical,,,1,,42,false,false,,2355,2806,50,Corporate BYOD|MDM enrolled devices,\nGuest WiFi Zone,10.30.0.0/24,Guest Network,phone,wireless;guest;isolated,physical,,,1,,42,false,false,,2308,2611,50,Captive portal|Internet only,\nIoT Device Zone,10.40.0.0/24,IoT Network,phone,wireless;iot;building,physical,,,1,,42,false,false,,2229,2299,50,Building automation|Smart devices,\nNYC Branch Router,10.100.0.1,Branch Gateway,router,branch;nyc;sd-wan,physical,00:1A:2B:BR:01:01,,1,,42,false,false,,3152,634,50,Cisco Viptela vEdge|SD-WAN enabled,\nLA Branch Router,10.101.0.1,Branch Gateway,router,branch;la;sd-wan,physical,00:1A:2B:BR:02:01,,1,,42,false,false,,3084,507,50,Cisco Viptela vEdge|SD-WAN enabled,\nChicago Branch Router,10.102.0.1,Branch Gateway,router,branch;chicago;sd-wan,physical,00:1A:2B:BR:03:01,,1,,42,false,false,,3355,393,50,Cisco Viptela vEdge|SD-WAN enabled,\nLondon Branch Router,10.200.0.1,Branch Gateway,router,branch;london;sd-wan,physical,00:1A:2B:BR:04:01,,1,,42,false,false,,3114,260,50,Cisco Viptela vEdge|EMEA region,\nTokyo Branch Router,10.201.0.1,Branch Gateway,router,branch;tokyo;sd-wan,physical,00:1A:2B:BR:05:01,,1,,42,false,false,,3699,471,50,Cisco Viptela vEdge|APAC region,\"{\"\"all\"\":{\"\"icon\"\":{\"\"library\"\":\"\"selfhst\"\",\"\"name\"\":\"\"adguard-home\"\"}}}\"\nAWS Cloud,vpc-0a1b2c3d,Public Cloud,cloud,cloud;aws;hybrid,logical,,,1,,42,false,false,,3437,546,50,AWS US-East-1|VPC peering to HQ,\"{\"\"all\"\":{\"\"icon\"\":{\"\"library\"\":\"\"selfhst\"\",\"\"name\"\":\"\"ansible\"\"}}}\"\nAzure Cloud,vnet-corp-prod,Public Cloud,cloud,cloud;azure;hybrid,logical,,,1,,42,false,false,,2593,2724,50,Azure East US 2|ExpressRoute,\nGCP Cloud,vpc-gcp-corp,Public Cloud,cloud,cloud;gcp;dev,logical,,,1,,42,false,false,,2827,2731,50,GCP us-central1|Dev/Test workloads,\nISP Primary,203.0.113.1,Internet Uplink,globe,wan;internet;primary,physical,,,1,,42,false,false,,3712,616,50,AT&T MPLS|1 Gbps dedicated,\"{\"\"all\"\":{\"\"icon\"\":{\"\"library\"\":\"\"selfhst\"\",\"\"name\"\":\"\"wikidocs\"\"}}}\"\nISP Secondary,198.51.100.1,Internet Uplink,vm,wan;internet;backup,physical,,,1,,42,false,false,,3254,1993,139,Verizon Business|500 Mbps backup,\"{\"\"all\"\":{\"\"icon\"\":{\"\"library\"\":\"\"selfhst\"\",\"\"name\"\":\"\"alist\"\"},\"\"circleColor\"\":\"\"#4d2c58\"\",\"\"circleBorder\"\":\"\"#000000\"\",\"\"titleColor\"\":\"\"#006eff\"\"}}\"\nDC1 Int DNS,10.10.0.53,Internal DNS/AD,circle,dns;ad;dc1,physical,00:50:56:AD:01:01,26,1,dc-rack-a1,42,false,false,,1958,2845,50,Windows Server 2022|Primary DC,\nDC2 Int DNS,10.10.1.53,Internal DNS/AD,circle,dns;ad;dc2,physical,00:50:56:AD:01:02,26,1,dc-rack-a2,42,false,false,,1964,2845,50,Windows Server 2022|Secondary DC,\nApp Server 01,10.10.0.101,Application,server,app;iis;web,physical,00:50:56:AP:01:01,24,1,dc-rack-a1,42,false,false,,1947,2845,50,Windows Server 2022|IIS Application,\nApp Server 02,10.10.0.102,Application,server,app;iis;web,physical,00:50:56:AP:01:02,22,1,dc-rack-a1,42,false,false,,1955,2845,50,Windows Server 2022|IIS Application,\nSQL Server 01,10.10.0.201,Database,database,db;sql;primary,physical,00:50:56:DB:01:01,20,2,dc-rack-a1,42,false,false,,1936,2845,50,SQL Server 2022 Enterprise|AlwaysOn Primary,\nSQL Server 02,10.10.1.201,Database,database,db;sql;secondary,physical,00:50:56:DB:01:02,24,2,dc-rack-a2,42,false,false,,1947,2845,50,SQL Server 2022 Enterprise|AlwaysOn Secondary,\nK8s Master 1,10.10.1.50,Container Orchestration,hexagon,kubernetes;master;container,physical,00:50:56:K8:01:01,21,1,dc-rack-a2,42,false,false,,1925,2845,50,K8s Control Plane|etcd member,\nK8s Master 2,10.10.1.51,Container Orchestration,hexagon,kubernetes;master;container,physical,00:50:56:K8:01:02,19,1,dc-rack-a2,42,false,false,,1938,2845,50,K8s Control Plane|etcd member,\nK8s Master 3,10.10.1.52,Container Orchestration,hexagon,kubernetes;master;container,physical,00:50:56:K8:01:03,17,1,dc-rack-a2,42,false,false,,1914,2845,50,K8s Control Plane|etcd member,\nK8s Worker 1,10.10.1.60,Container Workload,server,kubernetes;worker;container,physical,00:50:56:K8:02:01,15,1,dc-rack-a2,42,false,false,,1930,2845,50,K8s Worker Node|64GB RAM,\nK8s Worker 2,10.10.1.61,Container Workload,server,kubernetes;worker;container,physical,00:50:56:K8:02:02,13,1,dc-rack-a2,42,false,false,,1904,2845,50,K8s Worker Node|64GB RAM,\nK8s Worker 3,10.10.1.62,Container Workload,server,kubernetes;worker;container,physical,00:50:56:K8:02:03,11,1,dc-rack-a2,42,false,false,,1922,2845,50,K8s Worker Node|64GB RAM,\nK8s Worker 4,10.10.1.63,Container Workload,server,kubernetes;worker;container,physical,00:50:56:K8:02:04,9,1,dc-rack-a2,42,false,false,,1893,2845,50,K8s Worker Node|64GB RAM,\nProxy Server 1,10.5.0.10,Web Proxy,server,proxy;squid;filtering,security,00:50:56:PX:01:01,,1,,42,false,false,,1806,654,50,Squid Proxy|Content filtering,\nProxy Server 2,10.5.0.11,Web Proxy,server,proxy;squid;filtering,security,00:50:56:PX:01:02,,1,,42,false,false,,2937,2629,50,Squid Proxy|HA pair,\nVPN Concentrator,10.0.5.1,Remote Access VPN,firewall,vpn;remote;security,security,00:1A:2B:VP:01:01,,2,,42,false,false,,3642,947,50,Cisco ASA 5555-X|AnyConnect SSL VPN,\nNAC Server,10.5.5.10,Network Access Control,server,nac;ise;802.1x,security,00:50:56:NA:01:01,,2,,42,false,false,,1153,1172,50,Cisco ISE 3.1|RADIUS/TACACS+,\nPrint Server,10.10.0.150,Print Services,server,print;windows;services,physical,00:50:56:PR:01:01,18,1,dc-rack-a1,42,false,false,,1897,2845,50,Windows Print Server|50+ printers,\nFile Server,10.10.0.160,File Services,database,file;smb;dfs,physical,00:50:56:FS:01:01,16,2,dc-rack-a1,42,false,false,,1861,2845,50,Windows File Server|DFS namespace,\nCertificate Authority,192.168.100.80,PKI Infrastructure,server,pki;ca;security,logical,00:50:56:CA:01:01,5,1,mgmt-rack,24,false,false,,1889,2845,50,Windows CA|Enterprise Root CA,\nSCCM Server,192.168.100.90,Endpoint Management,server,sccm;patching;software,logical,00:50:56:SC:01:01,3,2,mgmt-rack,24,false,false,,1850,2845,50,MECM Primary Site|Software deployment,\nVoIP Cluster,10.50.0.0/24,Voice Services,phone,voip;cisco;ucm,application,,,1,,42,false,false,,1777,1617,50,Cisco UCM Cluster|3000 endpoints,\nVideo Conference,10.51.0.0/24,Video Services,laptop,video;webex;teams,application,,,1,,42,false,false,,1994,2245,50,Webex/Teams integration|Meeting rooms,\nSecurity Cameras,10.60.0.0/24,Physical Security,camera,cctv;surveillance;security,physical,,,1,,42,false,false,,1674,2046,50,150+ IP cameras|30-day retention,\nNVR Cluster,10.60.0.10,Video Recording,server,nvr;surveillance;storage,physical,00:50:56:NV:01:01,15,4,dc-rack-b2,42,false,false,,1829,2845,50,Milestone XProtect|500TB storage,\nDev Server 1,10.80.0.10,Development,server,dev;gitlab;ci-cd;[object Object],application,00:50:56:DV:01:01,,2,,42,false,false,,2801,1176,128,GitLab Server|CI/CD pipelines,\"{\"\"all\"\":{\"\"icon\"\":{\"\"library\"\":\"\"simple\"\",\"\"name\"\":\"\"amazonwebservices\"\"}}}\"\nDev Server 2,10.80.0.11,Development,server,dev;jenkins;ci-cd,application,00:50:56:DV:01:02,,2,,42,false,false,,1945,1165,50,Jenkins Server|Build automation,\nTest Environment,10.81.0.0/24,QA/Testing,shield,test;qa;staging,application,,,1,,42,false,false,,2932,862,121,Staging environment|Pre-prod validation,\"{\"\"all\"\":{\"\"icon\"\":{\"\"library\"\":\"\"simple\"\",\"\"name\"\":\"\"apple\"\"}}}\"\nERP System,10.90.0.10,Business Application,database,erp;sap;business,application,00:50:56:ER:01:01,,4,,42,false,false,,790,474,50,SAP S/4HANA|Financial/HR systems,\nCRM System,10.91.0.10,Business Application,database,crm;salesforce;business,application,,,1,,42,false,false,,3515,1138,50,Salesforce integration|Sales/Marketing,\nCorporate Endpoints,10.70.0.0/22,User Workstations,laptop,endpoints;workstations;users,physical,,,1,,42,false,false,,992,2284,50,~1000 corporate laptops|Windows 11,\nFloor 1 Switch,10.1.1.1,Distribution,switch,distribution;floor-1;access,physical,00:1A:2B:FL:01:01,,1,,42,false,false,,654,2020,50,Cisco C9300-48P|PoE+ enabled,\nFloor 2 Switch,10.1.2.1,Distribution,switch,distribution;floor-2;access,physical,00:1A:2B:FL:02:01,,1,,42,false,false,,854,1843,50,Cisco C9300-48P|PoE+ enabled,\nFloor 3 Switch,10.1.3.1,Distribution,switch,distribution;floor-3;access,physical,00:1A:2B:FL:03:01,,1,,42,false,false,,1899,1457,50,Cisco C9300-48P|PoE+ enabled,\nFloor 4 Switch,10.1.4.1,Distribution,switch,distribution;floor-4;access,physical,00:1A:2B:FL:04:01,,1,,42,false,false,,489,181,50,Cisco C9300-48P|PoE+ enabled,\nAP Floor 1 Zone 1,10.20.1.10,Wireless Access,wifi,wifi;ap;floor-1,physical,00:1A:2B:AP:01:01,,1,,42,false,false,,1140,2070,50,Cisco 9120AX|Wi-Fi 6,\nAP Floor 2 Zone 1,10.20.2.10,Wireless Access,wifi,wifi;ap;floor-2,physical,00:1A:2B:AP:02:01,,1,,42,false,false,,688,2384,50,Cisco 9120AX|Wi-Fi 6,\nAP Floor 3 Zone 1,10.20.3.10,Wireless Access,wifi,wifi;ap;floor-3,physical,00:1A:2B:AP:03:01,,1,,42,false,false,,2145,1890,50,Cisco 9120AX|Wi-Fi 6,\nAP Floor 4 Zone 1,10.20.4.10,Wireless Access,wifi,wifi;ap;floor-4,physical,00:1A:2B:AP:04:01,,1,,42,false,false,,518,566,50,Cisco 9120AX|Wi-Fi 6,\nUPS DC-1,192.168.200.10,Power Management,rectangle,power;ups;datacenter,physical,,,1,,42,false,false,,771,296,50,APC Symmetra|80kVA|30 min runtime,\nUPS DC-2,192.168.200.11,Power Management,rectangle,power;ups;datacenter,physical,,,1,,42,false,false,,216,330,50,APC Symmetra|80kVA|Redundant,\nPDU Rack A1,192.168.200.21,Power Distribution,rectangle,power;pdu;rack-a1,physical,,1,1,dc-rack-a1,42,false,false,,1805,2845,50,APC Switched PDU|Per-outlet metering,\nPDU Rack A2,192.168.200.22,Power Distribution,rectangle,power;pdu;rack-a2,physical,,1,1,dc-rack-a2,42,false,false,,1742,2845,50,APC Switched PDU|Per-outlet metering,\nCRAC Unit 1,192.168.200.30,Cooling,rectangle,cooling;hvac;datacenter,physical,,,1,,42,false,false,,246,626,50,Liebert CRV|Row-based cooling,\nCRAC Unit 2,192.168.200.31,Cooling,rectangle,cooling;hvac;datacenter,physical,,,1,,42,false,false,,1603,981,50,Liebert CRV|N+1 redundancy,\ncamera A,0.0.0.0,,camera,,physical,,,1,,,false,false,,167,145,45,,\ncamera B,0.0.0.0,,camera,,physical,,,1,,,false,false,,1041,738,45,,\n"
  },
  {
    "path": "demos/csv-exports/the-one-file-homelab.csv",
    "content": "#THEONEFILE_CONFIG:{\"nodeData\":{\"internet\":{\"shape\":\"stop-sign\",\"name\":\"Internet\",\"ip\":\"0.0.0.0\",\"role\":\"\",\"tags\":[],\"notes\":[],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"internet-copy\":{\"shape\":\"firewall\",\"name\":\"OPNSENSE\",\"ip\":\"0.0.0.0\",\"role\":\"\",\"tags\":[],\"notes\":[],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"opnsense-copy\":{\"shape\":\"firewall\",\"name\":\"Docker\",\"ip\":\"0.0.0.0\",\"role\":\"\",\"tags\":[],\"notes\":[],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"docker-copy\":{\"shape\":\"firewall\",\"name\":\"Docker2\",\"ip\":\"0.0.0.0\",\"role\":\"\",\"tags\":[],\"notes\":[],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"docker-copy-1\":{\"shape\":\"firewall\",\"name\":\"Docker3\",\"ip\":\"0.0.0.0\",\"role\":\"\",\"tags\":[],\"notes\":[],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"docker-copy-2\":{\"shape\":\"firewall\",\"name\":\"Docker 4\",\"ip\":\"0.0.0.0\",\"role\":\"\",\"tags\":[{\"type\":\"icon\",\"library\":\"selfhst\",\"name\":\"docker\"},{\"type\":\"icon\",\"library\":\"selfhst\",\"name\":\"authentik\"},{\"type\":\"icon\",\"library\":\"selfhst\",\"name\":\"immich\"}],\"notes\":[],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"opnsense-copy-1\":{\"shape\":\"firewall\",\"name\":\"OPNSENSE GUEST\",\"ip\":\"0.0.0.0\",\"role\":\"\",\"tags\":[],\"notes\":[],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"phone\":{\"shape\":\"phone\",\"name\":\"Phone\",\"ip\":\"0.0.0.0\",\"role\":\"\",\"tags\":[],\"notes\":[],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"desktop\":{\"shape\":\"pc\",\"name\":\"Desktop\",\"ip\":\"0.0.0.0\",\"role\":\"\",\"tags\":[],\"notes\":[],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"dns\":{\"shape\":\"cloud\",\"name\":\"DNS\",\"ip\":\"0.0.0.0\",\"role\":\"\",\"tags\":[],\"notes\":[],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"racked\":{\"shape\":\"server\",\"name\":\"Racked\",\"ip\":\"\",\"role\":\"Rack\",\"tags\":[],\"notes\":[],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":true,\"locked\":false,\"groupId\":null},\"thermostat\":{\"shape\":\"thermostat\",\"name\":\"Thermostat\",\"ip\":\"0.0.0.0\",\"role\":\"\",\"tags\":[],\"notes\":[],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"video-doorbell\":{\"shape\":\"doorbell\",\"name\":\"Video Doorbell\",\"ip\":\"0.0.0.0\",\"role\":\"\",\"tags\":[],\"notes\":[],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"smart-lock\":{\"shape\":\"smart-lock\",\"name\":\"Smart Lock\",\"ip\":\"0.0.0.0\",\"role\":\"\",\"tags\":[],\"notes\":[],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"smart-bulb\":{\"shape\":\"smart-bulb\",\"name\":\"Smart Bulb\",\"ip\":\"0.0.0.0\",\"role\":\"\",\"tags\":[],\"notes\":[],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"robot-vacuum\":{\"shape\":\"vacuum\",\"name\":\"Robot Vacuum\",\"ip\":\"0.0.0.0\",\"role\":\"\",\"tags\":[],\"notes\":[],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null}},\"edgeData\":{\"list\":[{\"id\":\"internet-internet-copy-1765238145151\",\"from\":\"internet\",\"to\":\"internet-copy\",\"width\":4,\"color\":\"#55e208\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1},{\"id\":\"internet-copy-opnsense-copy-1765238187451\",\"from\":\"internet-copy\",\"to\":\"opnsense-copy\",\"width\":4,\"color\":\"#4c00ff\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1},{\"id\":\"internet-copy-docker-copy-1765238242477\",\"from\":\"internet-copy\",\"to\":\"docker-copy\",\"width\":4,\"color\":\"#4c00ff\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1},{\"id\":\"internet-copy-docker-copy-1-1765238244637\",\"from\":\"internet-copy\",\"to\":\"docker-copy-1\",\"width\":4,\"color\":\"#4c00ff\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1},{\"id\":\"internet-copy-docker-copy-2-1765238246233\",\"from\":\"internet-copy\",\"to\":\"docker-copy-2\",\"width\":4,\"color\":\"#4c00ff\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1},{\"id\":\"internet-opnsense-copy-1-1765238266117\",\"from\":\"internet\",\"to\":\"opnsense-copy-1\",\"width\":4,\"color\":\"#80ff00\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1},{\"id\":\"opnsense-copy-1-dns-1765238347996\",\"from\":\"opnsense-copy-1\",\"to\":\"dns\",\"width\":4,\"color\":\"#fb00ff\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1},{\"id\":\"dns-desktop-1765238386101\",\"from\":\"dns\",\"to\":\"desktop\",\"width\":4,\"color\":\"#ff00d0\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1},{\"id\":\"phone-dns-1765238391156\",\"from\":\"phone\",\"to\":\"dns\",\"width\":4,\"color\":\"#ff00d0\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1},{\"id\":\"custom-1765239449323\",\"type\":\"custom\",\"color\":\"#f97316\",\"width\":4,\"lineStyle\":\"solid\",\"direction\":\"forward\",\"points\":[{\"x\":2936.464111328125,\"y\":786.07958984375},{\"x\":3184.112060546875,\"y\":887.6153564453125},{\"x\":2763.110107421875,\"y\":981.7216796875}],\"notes\":[]}]},\"rectData\":{\"list\":[{\"id\":\"rect-1765238219615\",\"x\":2680.053955078125,\"y\":251.44879150390625,\"width\":814.10400390625,\"height\":389.26678466796875,\"color\":\"#ec0999\",\"style\":\"filled\",\"lineStyle\":\"solid\",\"notes\":[]}]},\"textData\":{\"list\":[{\"id\":\"text-1765238422602\",\"x\":2466.35986328125,\"y\":741.6801147460938,\"content\":\"Double click on desktop\\nor long press on mobile\\nto enter rack canvas view\",\"fontSize\":40,\"color\":\"#e2e8f0\",\"fontWeight\":\"normal\",\"fontStyle\":\"normal\",\"textAlign\":\"start\",\"textDecoration\":\"none\",\"bgColor\":\"#000000\",\"bgEnabled\":false,\"opacity\":1}]},\"edgeLegend\":{\"#475569\":\"you can edit me too\",\"#65758b\":\"you can edit me too\",\"#63748c\":\"you can edit me too\",\"#5e6f87\":\"you can edit me too\",\"#586a84\":\"you can edit me too\",\"#4f627d\":\"you can edit me too\",\"#455873\":\"you can edit me too\",\"#3d506c\":\"you can edit me too\",\"#354964\":\"you can edit me too\",\"#2e415c\":\"you can edit me too\",\"#293c56\":\"you can edit me too\",\"#273a53\":\"you can edit me too\",\"#253750\":\"you can edit me too\",\"#23354d\":\"you can edit me too\",\"#203046\":\"you can edit me too\",\"#1e2d43\":\"you can edit me too\",\"#1a283d\":\"you can edit me too\",\"#172435\":\"you can edit me too\",\"#141f2e\":\"you can edit me too\",\"#111a27\":\"you can edit me too\",\"#0f1824\":\"you can edit me too\",\"#0d1521\":\"you can edit me too\",\"#0c131d\":\"you can edit me too\",\"#0c1d1c\":\"you can edit me too\",\"#0c1c1d\":\"you can edit me too\",\"#0c191d\":\"you can edit me too\",\"#0c141d\":\"you can edit me too\",\"#0c0d1d\":\"you can edit me too\",\"#130c1d\":\"you can edit me too\",\"#1b0c1d\":\"you can edit me too\",\"#1d0c17\":\"you can edit me too\",\"#1d0c10\":\"you can edit me too\",\"#1d0c0c\":\"you can edit me too\",\"#3b1b1b\":\"you can edit me too\",\"#3c1a1a\":\"you can edit me too\",\"#3f1c1c\":\"you can edit me too\",\"#401c1c\":\"you can edit me too\",\"#451c1c\":\"you can edit me too\",\"#461b1b\":\"you can edit me too\",\"#4c1a1a\":\"you can edit me too\",\"#521919\":\"you can edit me too\",\"#571919\":\"you can edit me too\",\"#5d1818\":\"you can edit me too\",\"#631717\":\"you can edit me too\",\"#651515\":\"you can edit me too\",\"#6a1616\":\"you can edit me too\",\"#6f1515\":\"you can edit me too\",\"#711414\":\"you can edit me too\",\"#761414\":\"you can edit me too\",\"#771313\":\"you can edit me too\",\"#7c1313\":\"you can edit me too\",\"#811313\":\"you can edit me too\",\"#821212\":\"you can edit me too\",\"#871212\":\"you can edit me too\",\"#881111\":\"you can edit me too\",\"#8d1111\":\"you can edit me too\",\"#8e1010\":\"you can edit me too\",\"#8f0f0f\":\"you can edit me too\",\"#900e0e\":\"you can edit me too\",\"#8e0b0b\":\"you can edit me too\",\"#8c0d0d\":\"you can edit me too\",\"#880c0c\":\"you can edit me too\",\"#830c0c\":\"you can edit me too\",\"#7e0c0c\":\"you can edit me too\",\"#790c0c\":\"you can edit me too\",\"#730c0c\":\"you can edit me too\",\"#6f0b0b\":\"you can edit me too\",\"#0b6f64\":\"you can edit me too\",\"#0b6f5f\":\"you can edit me too\",\"#0b6f56\":\"you can edit me too\",\"#0b6f49\":\"you can edit me too\",\"#0b6f31\":\"you can edit me too\",\"#0b6f1f\":\"you can edit me too\",\"#0b6f0d\":\"you can edit me too\",\"#176f0b\":\"you can edit me too\",\"#266f0b\":\"you can edit me too\",\"#296f0b\":\"you can edit me too\",\"#2e6f0b\":\"you can edit me too\",\"#1a2d10\":\"you can edit me too\",\"#1c3111\":\"you can edit me too\",\"#213814\":\"you can edit me too\",\"#233c15\":\"you can edit me too\",\"#254017\":\"you can edit me too\",\"#294918\":\"you can edit me too\",\"#2b4d1a\":\"you can edit me too\",\"#2d511a\":\"you can edit me too\",\"#315a1b\":\"you can edit me too\",\"#35631c\":\"you can edit me too\",\"#37681d\":\"you can edit me too\",\"#3b721d\":\"you can edit me too\",\"#3f7b1e\":\"you can edit me too\",\"#42851e\":\"you can edit me too\",\"#46901d\":\"you can edit me too\",\"#499a1d\":\"you can edit me too\",\"#4b9f1d\":\"you can edit me too\",\"#4ca61c\":\"you can edit me too\",\"#50b01c\":\"you can edit me too\",\"#51b71a\":\"you can edit me too\",\"#50b918\":\"you can edit me too\",\"#51c115\":\"you can edit me too\",\"#53c615\":\"you can edit me too\",\"#53c814\":\"you can edit me too\",\"#52c913\":\"you can edit me too\",\"#54d011\":\"you can edit me too\",\"#53d110\":\"you can edit me too\",\"#55d510\":\"you can edit me too\",\"#55d70f\":\"you can edit me too\",\"#54d80e\":\"you can edit me too\",\"#54da0b\":\"you can edit me too\",\"#56df0c\":\"you can edit me too\",\"#53db0a\":\"you can edit me too\",\"#55e00b\":\"you can edit me too\",\"#55e109\":\"you can edit me too\",\"#55e208\":\"ISP LINE\",\"#4c00ff\":\"MY Guest NETWORK\",\"#80ff00\":\"you can edit me too\",\"#3b4234\":\"you can edit me too\",\"#3a3442\":\"you can edit me too\",\"#3b3442\":\"you can edit me too\",\"#3c3442\":\"you can edit me too\",\"#3d3442\":\"you can edit me too\",\"#3e3442\":\"you can edit me too\",\"#3f3442\":\"you can edit me too\",\"#403442\":\"you can edit me too\",\"#413442\":\"you can edit me too\",\"#653d66\":\"you can edit me too\",\"#683f69\":\"you can edit me too\",\"#6c416c\":\"you can edit me too\",\"#6f4370\":\"you can edit me too\",\"#704270\":\"you can edit me too\",\"#734474\":\"you can edit me too\",\"#784479\":\"you can edit me too\",\"#7d447e\":\"you can edit me too\",\"#7e437f\":\"you can edit me too\",\"#834384\":\"you can edit me too\",\"#844285\":\"you can edit me too\",\"#89418b\":\"you can edit me too\",\"#8e428f\":\"you can edit me too\",\"#904091\":\"you can edit me too\",\"#923e93\":\"you can edit me too\",\"#973e98\":\"you can edit me too\",\"#943c96\":\"you can edit me too\",\"#993c9a\":\"you can edit me too\",\"#963a98\":\"you can edit me too\",\"#973899\":\"you can edit me too\",\"#99369b\":\"you can edit me too\",\"#9a359c\":\"you can edit me too\",\"#9b349d\":\"you can edit me too\",\"#9d329f\":\"you can edit me too\",\"#9e31a0\":\"you can edit me too\",\"#a02fa2\":\"you can edit me too\",\"#9d2d9f\":\"you can edit me too\",\"#9f2ba1\":\"you can edit me too\",\"#a129a3\":\"you can edit me too\",\"#a327a5\":\"you can edit me too\",\"#a525a7\":\"you can edit me too\",\"#a723a9\":\"you can edit me too\",\"#a921ab\":\"you can edit me too\",\"#ab1fad\":\"you can edit me too\",\"#ad1daf\":\"you can edit me too\",\"#ae1cb0\":\"you can edit me too\",\"#b019b3\":\"you can edit me too\",\"#b118b4\":\"you can edit me too\",\"#b316b6\":\"you can edit me too\",\"#b816bb\":\"you can edit me too\",\"#b514b8\":\"you can edit me too\",\"#ba14bd\":\"you can edit me too\",\"#b712ba\":\"you can edit me too\",\"#bb13be\":\"you can edit me too\",\"#b811bb\":\"you can edit me too\",\"#be10c1\":\"you can edit me too\",\"#bb0ebe\":\"you can edit me too\",\"#bd0cc0\":\"you can edit me too\",\"#be0bc1\":\"you can edit me too\",\"#c108c4\":\"you can edit me too\",\"#be06c1\":\"you can edit me too\",\"#c103c4\":\"you can edit me too\",\"#c301c6\":\"you can edit me too\",\"#c400c7\":\"you can edit me too\",\"#c900cc\":\"you can edit me too\",\"#ce00d1\":\"you can edit me too\",\"#d300d6\":\"you can edit me too\",\"#d800db\":\"you can edit me too\",\"#dd00e0\":\"you can edit me too\",\"#e200e6\":\"you can edit me too\",\"#ec00f0\":\"you can edit me too\",\"#f100f5\":\"you can edit me too\",\"#f600fa\":\"you can edit me too\",\"#fb00ff\":\"you can edit me too\",\"#ff00d0\":\"iPhone (always guest iPhone)\",\"#f97316\":\"you can edit me too\"},\"nodePositions\":{\"internet\":{\"x\":1757.7735887323333,\"y\":298.77284240722656},\"internet-copy\":{\"x\":2066.9677515897347,\"y\":473.4119134177565},\"opnsense-copy\":{\"x\":1773.8400660428597,\"y\":666.5758233298659},\"docker-copy\":{\"x\":1931.1978950081452,\"y\":782.2775961320921},\"docker-copy-1\":{\"x\":2158.1262397347077,\"y\":767.7122274797483},\"docker-copy-2\":{\"x\":2342.2663764534577,\"y\":631.7681967180296},\"opnsense-copy-1\":{\"x\":2757.879480087803,\"y\":307.6117116091891},\"phone\":{\"x\":3312.857751572178,\"y\":502.58220111114224},\"desktop\":{\"x\":2971.700036728428,\"y\":480.7287465212985},\"dns\":{\"x\":3200.4643189549906,\"y\":320.469591247861},\"racked\":{\"x\":2645.5845448279656,\"y\":970.7820678889219},\"thermostat\":{\"x\":1323.0595481711202,\"y\":574.6132617105841},\"video-doorbell\":{\"x\":1188.4284446554952,\"y\":455.3684191812872},\"smart-lock\":{\"x\":1292.286782057839,\"y\":790.0231738199591},\"smart-bulb\":{\"x\":1496.156899245339,\"y\":716.9377246012091},\"robot-vacuum\":{\"x\":2288.5581443625265,\"y\":978.5069995035528}},\"nodeSizes\":{\"core-router-1\":36,\"internet\":168,\"phone\":121,\"desktop\":147,\"racked\":137,\"docker-copy-2\":82},\"nodeStyles\":{\"internet\":{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"amazon-web-services\"},\"circleColor\":\"#db0000\",\"circleBorder\":\"#000000\",\"titleSize\":52,\"subSize\":46}},\"opnsense-copy-1\":{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"opnsense-v1\"}}},\"internet-copy\":{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"opnsense\"}}},\"docker-copy-2\":{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"docker\"}}},\"docker-copy-1\":{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"authportal\"}}},\"docker-copy\":{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"jotty\"}}},\"opnsense-copy\":{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"portainer\"}}},\"racked\":{\"all\":{\"icon\":{\"library\":\"mdi\",\"name\":\"server-security\"},\"circleColor\":\"#010813\",\"circleBorder\":\"#ffffff\"}}},\"page\":{\"title\":\"The One File\",\"background\":\"\",\"topbarBg\":\"rgba(9, 12, 20, 0.9)\",\"topbarBorder\":\"#1f2533\",\"panel\":\"#2f0e0e\",\"panelAlt\":\"#10141b\",\"accent\":\"#a75252\",\"sidebarBg\":\"#10141b\",\"btnBg\":\"#0b0e13\",\"btnText\":\"#e2e8f0\",\"tagFill\":\"#1e293b\",\"tagText\":\"#e2e8f0\",\"tagBorder\":\"#475569\",\"inputBg\":\"#0b0e13\",\"inputText\":\"#e2e8f0\",\"inputBorder\":\"#1f2937\",\"inputFont\":\"Inter, system-ui, sans-serif\",\"inputFontSize\":14,\"toolbarBg\":\"#441215\",\"toolbarBorder\":\"#1f2937\",\"toolbarText\":\"#94a3b8\",\"toolbarBtnBg\":\"#0b0e13\",\"toolbarBtnText\":\"#e2e8f0\",\"minimapDots\":\"#94a3b8\",\"canvasHintEnabled\":true,\"canvasHintText\":\"\",\"canvasHintBg\":\"#0f172a\",\"canvasHintColor\":\"#94a3b8\",\"danger\":\"#f56565\",\"textMain\":\"#e2e8f0\",\"textSoft\":\"#94a3b8\",\"topbarHeight\":112,\"sidebarWidth\":350,\"mobileFooterHeight\":40,\"sidebarCollapsed\":false,\"nodeFill\":\"#1e293b\",\"nodeStroke\":\"#475569\",\"nodeTitle\":\"#e2e8f0\",\"nodeSub\":\"#94a3b8\",\"nodeTitleSize\":18,\"nodeSubSize\":13,\"nodeFont\":\"Inter, system-ui, sans-serif\",\"defaultEdge\":\"#475569\",\"selectionHandle\":\"#f59e0b\",\"selectionHandleSize\":8,\"groupIndicator\":\"#4fd1c5\",\"canvasGradientTop\":\"#1e2532\",\"canvasGradientBottom\":\"#050608\",\"canvasBorder\":\"#475569\",\"canvasGrid\":\"#475569\",\"canvasGridSize\":50,\"canvasGridEnabled\":true,\"rackFrameFill\":\"#0f172a\",\"rackGridEnabled\":true,\"rackFrameStroke\":\"#4fd1c5\",\"rackLineColor\":\"#475569\",\"rackTextColor\":\"#4fd1c5\",\"viewOnly\":false,\"defaultEdgeRouting\":\"curved\",\"animateConnections\":false,\"animationStyle\":\"arrows\",\"animationDirection\":\"all\",\"animationSpeed\":1.5},\"canvas\":{\"zoom\":0.8752084596859406,\"panX\":-191.26917538063572,\"panY\":-261.51832729948296},\"savedTopologyView\":{\"zoom\":0.9111098220009686,\"panX\":-207.26076103009882,\"panY\":-201.83911371533202},\"documentTabs\":[{\"id\":\"main\",\"name\":\"Corporate Site B\",\"nodes\":{\"core-router-1\":{\"shape\":\"router\",\"name\":\"Core Router 1\",\"ip\":\"10.0.0.1\",\"role\":\"Core Routing\",\"tags\":[\"core\",\"tier-1\",\"redundant\"],\"notes\":[\"Primary core router\",\"BGP peering enabled\"],\"mac\":\"00:1A:2B:3C:4D:01\",\"rackUnit\":\"\",\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"core-router-2\":{\"shape\":\"router\",\"name\":\"Core Router 2\",\"ip\":\"10.0.0.2\",\"role\":\"Core Routing\",\"tags\":[\"core\",\"tier-1\",\"redundant\"],\"notes\":[\"Secondary core router\",\"HSRP standby\"],\"mac\":\"00:1A:2B:3C:4D:02\",\"rackUnit\":\"\",\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null,\"ping\":{\"enabled\":true,\"protocol\":\"custom\",\"customUrl\":\"https://google.com\",\"timeout\":3000,\"status\":\"online\",\"lastCheck\":\"2025-12-09T00:15:04.343Z\"}},\"fw-external-1\":{\"shape\":\"firewall\",\"name\":\"External FW 1\",\"ip\":\"10.0.1.1\",\"role\":\"Perimeter Security\",\"tags\":[\"security\",\"perimeter\",\"ha-pair\"],\"notes\":[\"Palo Alto PA-5250\",\"Active node\"],\"mac\":\"00:1A:2B:3C:4D:10\",\"rackUnit\":\"\",\"uHeight\":\"2\",\"layer\":\"security\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"fw-external-2\":{\"shape\":\"firewall\",\"name\":\"External FW 2\",\"ip\":\"10.0.1.2\",\"role\":\"Perimeter Security\",\"tags\":[\"security\",\"perimeter\",\"ha-pair\"],\"notes\":[\"Palo Alto PA-5250\",\"Passive node\"],\"mac\":\"00:1A:2B:3C:4D:11\",\"rackUnit\":\"\",\"uHeight\":\"2\",\"layer\":\"security\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"fw-internal\":{\"shape\":\"firewall\",\"name\":\"Internal FW\",\"ip\":\"10.0.2.1\",\"role\":\"Internal Segmentation\",\"tags\":[\"security\",\"internal\"],\"notes\":[\"East-West traffic inspection\"],\"mac\":\"00:1A:2B:3C:4D:12\",\"rackUnit\":\"\",\"uHeight\":\"2\",\"layer\":\"security\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"core-switch-1\":{\"shape\":\"switch\",\"name\":\"Core Switch 1\",\"ip\":\"10.0.10.1\",\"role\":\"Core Switching\",\"tags\":[\"core\",\"layer3\",\"redundant\"],\"notes\":[\"Cisco Nexus 9000\",\"VPC Domain 1\"],\"mac\":\"00:1A:2B:3C:4D:20\",\"rackUnit\":\"\",\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"core-switch-2\":{\"shape\":\"switch\",\"name\":\"Core Switch 2\",\"ip\":\"10.0.10.2\",\"role\":\"Core Switching\",\"tags\":[\"core\",\"layer3\",\"redundant\"],\"notes\":[\"Cisco Nexus 9000\",\"VPC Domain 1\"],\"mac\":\"00:1A:2B:3C:4D:21\",\"rackUnit\":\"\",\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"dc-rack-a1\":{\"shape\":\"server\",\"name\":\"DC Rack A1\",\"ip\":\"10.10.0.0/24\",\"role\":\"Data Center Rack\",\"tags\":[\"datacenter\",\"row-a\",\"production\"],\"notes\":[\"Row A, Position 1\",\"Primary compute\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":true,\"locked\":false,\"groupId\":null},\"dc-rack-a2\":{\"shape\":\"server\",\"name\":\"DC Rack A2\",\"ip\":\"10.10.1.0/24\",\"role\":\"Data Center Rack\",\"tags\":[\"datacenter\",\"row-a\",\"production\"],\"notes\":[\"Row A, Position 2\",\"Primary compute\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":true,\"locked\":false,\"groupId\":null},\"dc-rack-b1\":{\"shape\":\"server\",\"name\":\"DC Rack B1\",\"ip\":\"10.10.2.0/24\",\"role\":\"Data Center Rack\",\"tags\":[\"datacenter\",\"row-b\",\"storage\"],\"notes\":[\"Row B, Position 1\",\"Storage systems\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":true,\"locked\":false,\"groupId\":null},\"dc-rack-b2\":{\"shape\":\"server\",\"name\":\"DC Rack B2\",\"ip\":\"10.10.3.0/24\",\"role\":\"Data Center Rack\",\"tags\":[\"datacenter\",\"row-b\",\"storage\"],\"notes\":[\"Row B, Position 2\",\"Storage systems\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":true,\"locked\":false,\"groupId\":null},\"dmz-rack\":{\"shape\":\"server\",\"name\":\"DMZ Rack\",\"ip\":\"172.16.0.0/24\",\"role\":\"DMZ Infrastructure\",\"tags\":[\"dmz\",\"security\",\"public-facing\",{\"type\":\"icon\",\"library\":\"selfhst\",\"name\":\"booklogr\"},{\"type\":\"icon\",\"library\":\"simple\",\"name\":\"gmail\"}],\"notes\":[\"Isolated DMZ zone\",\"Public-facing services\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"security\",\"assignedRack\":\"\",\"rackCapacity\":\"24\",\"isRack\":true,\"locked\":false,\"groupId\":null},\"mgmt-rack\":{\"shape\":\"server\",\"name\":\"Management Rack\",\"ip\":\"192.168.100.0/24\",\"role\":\"Management Infrastructure\",\"tags\":[\"management\",\"oob\",\"noc\"],\"notes\":[\"Out-of-band management\",\"NOC equipment\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"logical\",\"assignedRack\":\"\",\"rackCapacity\":\"24\",\"isRack\":true,\"locked\":false,\"groupId\":null},\"esxi-host-01\":{\"shape\":\"server\",\"name\":\"ESXi Host 01\",\"ip\":\"10.10.0.11\",\"role\":\"Hypervisor\",\"tags\":[\"vmware\",\"compute\",\"cluster-a\"],\"notes\":[\"Dell PowerEdge R750\",\"512GB RAM\",\"vSphere 8.0\"],\"mac\":\"00:50:56:AA:01:01\",\"rackUnit\":38,\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a1\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"esxi-host-02\":{\"shape\":\"server\",\"name\":\"ESXi Host 02\",\"ip\":\"10.10.0.12\",\"role\":\"Hypervisor\",\"tags\":[\"vmware\",\"compute\",\"cluster-a\"],\"notes\":[\"Dell PowerEdge R750\",\"512GB RAM\",\"vSphere 8.0\"],\"mac\":\"00:50:56:AA:01:02\",\"rackUnit\":35,\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a1\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"esxi-host-03\":{\"shape\":\"server\",\"name\":\"ESXi Host 03\",\"ip\":\"10.10.0.13\",\"role\":\"Hypervisor\",\"tags\":[\"vmware\",\"compute\",\"cluster-a\"],\"notes\":[\"Dell PowerEdge R750\",\"512GB RAM\",\"vSphere 8.0\"],\"mac\":\"00:50:56:AA:01:03\",\"rackUnit\":32,\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a1\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"esxi-host-04\":{\"shape\":\"server\",\"name\":\"ESXi Host 04\",\"ip\":\"10.10.0.14\",\"role\":\"Hypervisor\",\"tags\":[\"vmware\",\"compute\",\"cluster-a\"],\"notes\":[\"Dell PowerEdge R750\",\"512GB RAM\",\"vSphere 8.0\"],\"mac\":\"00:50:56:AA:01:04\",\"rackUnit\":29,\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a1\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"tor-switch-a1\":{\"shape\":\"switch\",\"name\":\"ToR Switch A1\",\"ip\":\"10.10.0.1\",\"role\":\"Top of Rack\",\"tags\":[\"tor\",\"access\",\"rack-a1\"],\"notes\":[\"Cisco Nexus 93180YC-FX\",\"48x25G ports\"],\"mac\":\"00:1A:2B:3C:5D:01\",\"rackUnit\":42,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a1\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"esxi-host-05\":{\"shape\":\"server\",\"name\":\"ESXi Host 05\",\"ip\":\"10.10.1.11\",\"role\":\"Hypervisor\",\"tags\":[\"vmware\",\"compute\",\"cluster-b\"],\"notes\":[\"Dell PowerEdge R750\",\"768GB RAM\",\"vSphere 8.0\"],\"mac\":\"00:50:56:AA:02:01\",\"rackUnit\":38,\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"esxi-host-06\":{\"shape\":\"server\",\"name\":\"ESXi Host 06\",\"ip\":\"10.10.1.12\",\"role\":\"Hypervisor\",\"tags\":[\"vmware\",\"compute\",\"cluster-b\"],\"notes\":[\"Dell PowerEdge R750\",\"768GB RAM\",\"vSphere 8.0\"],\"mac\":\"00:50:56:AA:02:02\",\"rackUnit\":35,\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"esxi-host-07\":{\"shape\":\"server\",\"name\":\"ESXi Host 07\",\"ip\":\"10.10.1.13\",\"role\":\"Hypervisor\",\"tags\":[\"vmware\",\"compute\",\"cluster-b\"],\"notes\":[\"Dell PowerEdge R750\",\"768GB RAM\",\"vSphere 8.0\"],\"mac\":\"00:50:56:AA:02:03\",\"rackUnit\":32,\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"esxi-host-08\":{\"shape\":\"server\",\"name\":\"ESXi Host 08\",\"ip\":\"10.10.1.14\",\"role\":\"Hypervisor\",\"tags\":[\"vmware\",\"compute\",\"cluster-b\"],\"notes\":[\"Dell PowerEdge R750\",\"768GB RAM\",\"vSphere 8.0\"],\"mac\":\"00:50:56:AA:02:04\",\"rackUnit\":29,\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"tor-switch-a2\":{\"shape\":\"switch\",\"name\":\"ToR Switch A2\",\"ip\":\"10.10.1.1\",\"role\":\"Top of Rack\",\"tags\":[\"tor\",\"access\",\"rack-a2\"],\"notes\":[\"Cisco Nexus 93180YC-FX\",\"48x25G ports\"],\"mac\":\"00:1A:2B:3C:5D:02\",\"rackUnit\":42,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"san-primary\":{\"shape\":\"database\",\"name\":\"SAN Primary\",\"ip\":\"10.10.2.10\",\"role\":\"Primary Storage\",\"tags\":[\"storage\",\"san\",\"netapp\"],\"notes\":[\"NetApp AFF A400\",\"500TB Raw\",\"FC 32Gb\"],\"mac\":\"00:A0:98:AA:01:01\",\"rackUnit\":36,\"uHeight\":\"6\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-b1\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"san-secondary\":{\"shape\":\"database\",\"name\":\"SAN Secondary\",\"ip\":\"10.10.2.11\",\"role\":\"Secondary Storage\",\"tags\":[\"storage\",\"san\",\"netapp\"],\"notes\":[\"NetApp AFF A400\",\"500TB Raw\",\"FC 32Gb\"],\"mac\":\"00:A0:98:AA:01:02\",\"rackUnit\":28,\"uHeight\":\"6\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-b1\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"fc-switch-1\":{\"shape\":\"switch\",\"name\":\"FC Switch 1\",\"ip\":\"10.10.2.1\",\"role\":\"Fibre Channel\",\"tags\":[\"storage\",\"fc\",\"fabric-a\"],\"notes\":[\"Brocade G620\",\"Fabric A\"],\"mac\":\"00:1A:2B:FC:01:01\",\"rackUnit\":42,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-b1\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"fc-switch-2\":{\"shape\":\"switch\",\"name\":\"FC Switch 2\",\"ip\":\"10.10.2.2\",\"role\":\"Fibre Channel\",\"tags\":[\"storage\",\"fc\",\"fabric-b\"],\"notes\":[\"Brocade G620\",\"Fabric B\"],\"mac\":\"00:1A:2B:FC:01:02\",\"rackUnit\":41,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-b1\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"backup-server-1\":{\"shape\":\"server\",\"name\":\"Backup Server 1\",\"ip\":\"10.10.3.10\",\"role\":\"Backup Infrastructure\",\"tags\":[\"backup\",\"veeam\",\"protection\"],\"notes\":[\"Veeam Backup Server\",\"Dell R740xd\",\"200TB\"],\"mac\":\"00:50:56:BB:01:01\",\"rackUnit\":36,\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-b2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"backup-server-2\":{\"shape\":\"server\",\"name\":\"Backup Server 2\",\"ip\":\"10.10.3.11\",\"role\":\"Backup Infrastructure\",\"tags\":[\"backup\",\"veeam\",\"protection\"],\"notes\":[\"Veeam Backup Server\",\"Dell R740xd\",\"200TB\"],\"mac\":\"00:50:56:BB:01:02\",\"rackUnit\":33,\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-b2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"tape-library\":{\"shape\":\"database\",\"name\":\"Tape Library\",\"ip\":\"10.10.3.20\",\"role\":\"Archival Storage\",\"tags\":[\"backup\",\"tape\",\"lto9\"],\"notes\":[\"IBM TS4500\",\"LTO-9\",\"Long-term archive\"],\"mac\":\"00:50:56:BB:02:01\",\"rackUnit\":20,\"uHeight\":\"10\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-b2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"tor-switch-b1\":{\"shape\":\"switch\",\"name\":\"ToR Switch B1\",\"ip\":\"10.10.2.3\",\"role\":\"Top of Rack\",\"tags\":[\"tor\",\"access\",\"rack-b1\"],\"notes\":[\"Cisco Nexus 93180YC-FX\"],\"mac\":\"00:1A:2B:3C:5D:03\",\"rackUnit\":40,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-b1\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"tor-switch-b2\":{\"shape\":\"switch\",\"name\":\"ToR Switch B2\",\"ip\":\"10.10.3.1\",\"role\":\"Top of Rack\",\"tags\":[\"tor\",\"access\",\"rack-b2\"],\"notes\":[\"Cisco Nexus 93180YC-FX\"],\"mac\":\"00:1A:2B:3C:5D:04\",\"rackUnit\":42,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-b2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"web-server-1\":{\"shape\":\"server\",\"name\":\"Web Server 1\",\"ip\":\"172.16.0.11\",\"role\":\"Web Frontend\",\"tags\":[\"dmz\",\"web\",\"nginx\"],\"notes\":[\"NGINX reverse proxy\",\"Public facing\"],\"mac\":\"00:50:56:CC:01:01\",\"rackUnit\":20,\"uHeight\":\"1\",\"layer\":\"security\",\"assignedRack\":\"dmz-rack\",\"rackCapacity\":\"24\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"web-server-2\":{\"shape\":\"server\",\"name\":\"Web Server 2\",\"ip\":\"172.16.0.12\",\"role\":\"Web Frontend\",\"tags\":[\"dmz\",\"web\",\"nginx\"],\"notes\":[\"NGINX reverse proxy\",\"Public facing\"],\"mac\":\"00:50:56:CC:01:02\",\"rackUnit\":18,\"uHeight\":\"1\",\"layer\":\"security\",\"assignedRack\":\"dmz-rack\",\"rackCapacity\":\"24\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"waf-1\":{\"shape\":\"firewall\",\"name\":\"WAF Appliance\",\"ip\":\"172.16.0.5\",\"role\":\"Web Application Firewall\",\"tags\":[\"dmz\",\"security\",\"waf\"],\"notes\":[\"F5 BIG-IP ASM\",\"OWASP protection\"],\"mac\":\"00:50:56:CC:02:01\",\"rackUnit\":22,\"uHeight\":\"2\",\"layer\":\"security\",\"assignedRack\":\"dmz-rack\",\"rackCapacity\":\"24\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"load-balancer-dmz\":{\"shape\":\"switch\",\"name\":\"DMZ Load Balancer\",\"ip\":\"172.16.0.3\",\"role\":\"Load Balancing\",\"tags\":[\"dmz\",\"lb\",\"f5\"],\"notes\":[\"F5 BIG-IP LTM\",\"VIP: 172.16.0.100\"],\"mac\":\"00:50:56:CC:03:01\",\"rackUnit\":16,\"uHeight\":\"2\",\"layer\":\"security\",\"assignedRack\":\"dmz-rack\",\"rackCapacity\":\"24\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"mail-gateway\":{\"shape\":\"server\",\"name\":\"Mail Gateway\",\"ip\":\"172.16.0.25\",\"role\":\"Email Security\",\"tags\":[\"dmz\",\"email\",\"security\"],\"notes\":[\"Proofpoint Email Gateway\",\"Spam/malware filtering\"],\"mac\":\"00:50:56:CC:04:01\",\"rackUnit\":14,\"uHeight\":\"1\",\"layer\":\"security\",\"assignedRack\":\"dmz-rack\",\"rackCapacity\":\"24\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"dns-external-1\":{\"shape\":\"circle\",\"name\":\"External DNS 1\",\"ip\":\"172.16.0.53\",\"role\":\"External DNS\",\"tags\":[\"dmz\",\"dns\",\"public\"],\"notes\":[\"BIND DNS\",\"Authoritative for corp.com\"],\"mac\":\"00:50:56:CC:05:01\",\"rackUnit\":12,\"uHeight\":\"1\",\"layer\":\"security\",\"assignedRack\":\"dmz-rack\",\"rackCapacity\":\"24\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"dns-external-2\":{\"shape\":\"circle\",\"name\":\"External DNS 2\",\"ip\":\"172.16.0.54\",\"role\":\"External DNS\",\"tags\":[\"dmz\",\"dns\",\"public\"],\"notes\":[\"BIND DNS\",\"Secondary for corp.com\"],\"mac\":\"00:50:56:CC:05:02\",\"rackUnit\":10,\"uHeight\":\"1\",\"layer\":\"security\",\"assignedRack\":\"dmz-rack\",\"rackCapacity\":\"24\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"vcenter\":{\"shape\":\"server\",\"name\":\"vCenter Server\",\"ip\":\"192.168.100.10\",\"role\":\"Virtualization Management\",\"tags\":[\"management\",\"vmware\",\"vcsa\"],\"notes\":[\"vCenter Server Appliance 8.0\",\"Single SSO domain\"],\"mac\":\"00:50:56:DD:01:01\",\"rackUnit\":20,\"uHeight\":\"2\",\"layer\":\"logical\",\"assignedRack\":\"mgmt-rack\",\"rackCapacity\":\"24\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"nsx-manager\":{\"shape\":\"server\",\"name\":\"NSX Manager\",\"ip\":\"192.168.100.15\",\"role\":\"Network Virtualization\",\"tags\":[\"management\",\"vmware\",\"nsx\"],\"notes\":[\"NSX-T 4.1 Manager Cluster\"],\"mac\":\"00:50:56:DD:02:01\",\"rackUnit\":17,\"uHeight\":\"2\",\"layer\":\"logical\",\"assignedRack\":\"mgmt-rack\",\"rackCapacity\":\"24\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"siem-server\":{\"shape\":\"server\",\"name\":\"SIEM Server\",\"ip\":\"192.168.100.50\",\"role\":\"Security Monitoring\",\"tags\":[\"management\",\"security\",\"splunk\"],\"notes\":[\"Splunk Enterprise\",\"Security monitoring\"],\"mac\":\"00:50:56:DD:03:01\",\"rackUnit\":14,\"uHeight\":\"2\",\"layer\":\"logical\",\"assignedRack\":\"mgmt-rack\",\"rackCapacity\":\"24\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"nms-server\":{\"shape\":\"server\",\"name\":\"Network Monitoring\",\"ip\":\"192.168.100.60\",\"role\":\"Network Management\",\"tags\":[\"management\",\"monitoring\",\"prtg\"],\"notes\":[\"PRTG Network Monitor\",\"5000 sensors\"],\"mac\":\"00:50:56:DD:04:01\",\"rackUnit\":11,\"uHeight\":\"1\",\"layer\":\"logical\",\"assignedRack\":\"mgmt-rack\",\"rackCapacity\":\"24\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"jump-server\":{\"shape\":\"server\",\"name\":\"Jump Server\",\"ip\":\"192.168.100.100\",\"role\":\"Bastion Host\",\"tags\":[\"management\",\"security\",\"bastion\"],\"notes\":[\"Windows Server 2022\",\"MFA enabled\"],\"mac\":\"00:50:56:DD:05:01\",\"rackUnit\":9,\"uHeight\":\"1\",\"layer\":\"logical\",\"assignedRack\":\"mgmt-rack\",\"rackCapacity\":\"24\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"ipam-server\":{\"shape\":\"server\",\"name\":\"IPAM/DDI\",\"ip\":\"192.168.100.70\",\"role\":\"IP Management\",\"tags\":[\"management\",\"dns\",\"dhcp\"],\"notes\":[\"Infoblox DDI\",\"DNS/DHCP/IPAM\"],\"mac\":\"00:50:56:DD:06:01\",\"rackUnit\":7,\"uHeight\":\"2\",\"layer\":\"logical\",\"assignedRack\":\"mgmt-rack\",\"rackCapacity\":\"24\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"wlc-primary\":{\"shape\":\"wifi\",\"name\":\"WLC Primary\",\"ip\":\"10.20.0.1\",\"role\":\"Wireless Controller\",\"tags\":[\"wireless\",\"cisco\",\"9800\"],\"notes\":[\"Cisco C9800-40\",\"Primary controller\"],\"mac\":\"00:1A:2B:WL:01:01\",\"rackUnit\":\"\",\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"wlc-secondary\":{\"shape\":\"wifi\",\"name\":\"WLC Secondary\",\"ip\":\"10.20.0.2\",\"role\":\"Wireless Controller\",\"tags\":[\"wireless\",\"cisco\",\"9800\"],\"notes\":[\"Cisco C9800-40\",\"HA Secondary\"],\"mac\":\"00:1A:2B:WL:01:02\",\"rackUnit\":\"\",\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"mobile-zone-hq\":{\"shape\":\"phone\",\"name\":\"HQ Mobile Zone\",\"ip\":\"10.20.10.0/24\",\"role\":\"Mobile Device Zone\",\"tags\":[\"wireless\",\"byod\",\"mobile\"],\"notes\":[\"Corporate BYOD\",\"MDM enrolled devices\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"mobile-zone-guest\":{\"shape\":\"phone\",\"name\":\"Guest WiFi Zone\",\"ip\":\"10.30.0.0/24\",\"role\":\"Guest Network\",\"tags\":[\"wireless\",\"guest\",\"isolated\"],\"notes\":[\"Captive portal\",\"Internet only\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"mobile-zone-iot\":{\"shape\":\"phone\",\"name\":\"IoT Device Zone\",\"ip\":\"10.40.0.0/24\",\"role\":\"IoT Network\",\"tags\":[\"wireless\",\"iot\",\"building\"],\"notes\":[\"Building automation\",\"Smart devices\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"branch-router-ny\":{\"shape\":\"router\",\"name\":\"NYC Branch Router\",\"ip\":\"10.100.0.1\",\"role\":\"Branch Gateway\",\"tags\":[\"branch\",\"nyc\",\"sd-wan\"],\"notes\":[\"Cisco Viptela vEdge\",\"SD-WAN enabled\"],\"mac\":\"00:1A:2B:BR:01:01\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"branch-router-la\":{\"shape\":\"router\",\"name\":\"LA Branch Router\",\"ip\":\"10.101.0.1\",\"role\":\"Branch Gateway\",\"tags\":[\"branch\",\"la\",\"sd-wan\"],\"notes\":[\"Cisco Viptela vEdge\",\"SD-WAN enabled\"],\"mac\":\"00:1A:2B:BR:02:01\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"branch-router-chi\":{\"shape\":\"router\",\"name\":\"Chicago Branch Router\",\"ip\":\"10.102.0.1\",\"role\":\"Branch Gateway\",\"tags\":[\"branch\",\"chicago\",\"sd-wan\"],\"notes\":[\"Cisco Viptela vEdge\",\"SD-WAN enabled\"],\"mac\":\"00:1A:2B:BR:03:01\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"branch-router-lon\":{\"shape\":\"router\",\"name\":\"London Branch Router\",\"ip\":\"10.200.0.1\",\"role\":\"Branch Gateway\",\"tags\":[\"branch\",\"london\",\"sd-wan\"],\"notes\":[\"Cisco Viptela vEdge\",\"EMEA region\"],\"mac\":\"00:1A:2B:BR:04:01\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"branch-router-tokyo\":{\"shape\":\"router\",\"name\":\"Tokyo Branch Router\",\"ip\":\"10.201.0.1\",\"role\":\"Branch Gateway\",\"tags\":[\"branch\",\"tokyo\",\"sd-wan\"],\"notes\":[\"Cisco Viptela vEdge\",\"APAC region\"],\"mac\":\"00:1A:2B:BR:05:01\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"cloud-aws\":{\"shape\":\"cloud\",\"name\":\"AWS Cloud\",\"ip\":\"vpc-0a1b2c3d\",\"role\":\"Public Cloud\",\"tags\":[\"cloud\",\"aws\",\"hybrid\"],\"notes\":[\"AWS US-East-1\",\"VPC peering to HQ\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"logical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"cloud-azure\":{\"shape\":\"cloud\",\"name\":\"Azure Cloud\",\"ip\":\"vnet-corp-prod\",\"role\":\"Public Cloud\",\"tags\":[\"cloud\",\"azure\",\"hybrid\"],\"notes\":[\"Azure East US 2\",\"ExpressRoute\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"logical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"cloud-gcp\":{\"shape\":\"cloud\",\"name\":\"GCP Cloud\",\"ip\":\"vpc-gcp-corp\",\"role\":\"Public Cloud\",\"tags\":[\"cloud\",\"gcp\",\"dev\"],\"notes\":[\"GCP us-central1\",\"Dev/Test workloads\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"logical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"isp-primary\":{\"shape\":\"globe\",\"name\":\"ISP Primary\",\"ip\":\"203.0.113.1\",\"role\":\"Internet Uplink\",\"tags\":[\"wan\",\"internet\",\"primary\"],\"notes\":[\"AT&T MPLS\",\"1 Gbps dedicated\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"isp-secondary\":{\"shape\":\"vm\",\"name\":\"ISP Secondary\",\"ip\":\"198.51.100.1\",\"role\":\"Internet Uplink\",\"tags\":[\"wan\",\"internet\",\"backup\"],\"notes\":[\"Verizon Business\",\"500 Mbps backup\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null,\"rotation\":-17},\"dc-internal-1\":{\"shape\":\"circle\",\"name\":\"DC1 Int DNS\",\"ip\":\"10.10.0.53\",\"role\":\"Internal DNS/AD\",\"tags\":[\"dns\",\"ad\",\"dc1\"],\"notes\":[\"Windows Server 2022\",\"Primary DC\"],\"mac\":\"00:50:56:AD:01:01\",\"rackUnit\":26,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a1\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"dc-internal-2\":{\"shape\":\"circle\",\"name\":\"DC2 Int DNS\",\"ip\":\"10.10.1.53\",\"role\":\"Internal DNS/AD\",\"tags\":[\"dns\",\"ad\",\"dc2\"],\"notes\":[\"Windows Server 2022\",\"Secondary DC\"],\"mac\":\"00:50:56:AD:01:02\",\"rackUnit\":26,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"app-server-1\":{\"shape\":\"server\",\"name\":\"App Server 01\",\"ip\":\"10.10.0.101\",\"role\":\"Application\",\"tags\":[\"app\",\"iis\",\"web\"],\"notes\":[\"Windows Server 2022\",\"IIS Application\"],\"mac\":\"00:50:56:AP:01:01\",\"rackUnit\":24,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a1\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"app-server-2\":{\"shape\":\"server\",\"name\":\"App Server 02\",\"ip\":\"10.10.0.102\",\"role\":\"Application\",\"tags\":[\"app\",\"iis\",\"web\"],\"notes\":[\"Windows Server 2022\",\"IIS Application\"],\"mac\":\"00:50:56:AP:01:02\",\"rackUnit\":22,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a1\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"db-server-1\":{\"shape\":\"database\",\"name\":\"SQL Server 01\",\"ip\":\"10.10.0.201\",\"role\":\"Database\",\"tags\":[\"db\",\"sql\",\"primary\"],\"notes\":[\"SQL Server 2022 Enterprise\",\"AlwaysOn Primary\"],\"mac\":\"00:50:56:DB:01:01\",\"rackUnit\":20,\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a1\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"db-server-2\":{\"shape\":\"database\",\"name\":\"SQL Server 02\",\"ip\":\"10.10.1.201\",\"role\":\"Database\",\"tags\":[\"db\",\"sql\",\"secondary\"],\"notes\":[\"SQL Server 2022 Enterprise\",\"AlwaysOn Secondary\"],\"mac\":\"00:50:56:DB:01:02\",\"rackUnit\":24,\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"k8s-master-1\":{\"shape\":\"hexagon\",\"name\":\"K8s Master 1\",\"ip\":\"10.10.1.50\",\"role\":\"Container Orchestration\",\"tags\":[\"kubernetes\",\"master\",\"container\"],\"notes\":[\"K8s Control Plane\",\"etcd member\"],\"mac\":\"00:50:56:K8:01:01\",\"rackUnit\":21,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"k8s-master-2\":{\"shape\":\"hexagon\",\"name\":\"K8s Master 2\",\"ip\":\"10.10.1.51\",\"role\":\"Container Orchestration\",\"tags\":[\"kubernetes\",\"master\",\"container\"],\"notes\":[\"K8s Control Plane\",\"etcd member\"],\"mac\":\"00:50:56:K8:01:02\",\"rackUnit\":19,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"k8s-master-3\":{\"shape\":\"hexagon\",\"name\":\"K8s Master 3\",\"ip\":\"10.10.1.52\",\"role\":\"Container Orchestration\",\"tags\":[\"kubernetes\",\"master\",\"container\"],\"notes\":[\"K8s Control Plane\",\"etcd member\"],\"mac\":\"00:50:56:K8:01:03\",\"rackUnit\":17,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"k8s-worker-1\":{\"shape\":\"server\",\"name\":\"K8s Worker 1\",\"ip\":\"10.10.1.60\",\"role\":\"Container Workload\",\"tags\":[\"kubernetes\",\"worker\",\"container\"],\"notes\":[\"K8s Worker Node\",\"64GB RAM\"],\"mac\":\"00:50:56:K8:02:01\",\"rackUnit\":15,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"k8s-worker-2\":{\"shape\":\"server\",\"name\":\"K8s Worker 2\",\"ip\":\"10.10.1.61\",\"role\":\"Container Workload\",\"tags\":[\"kubernetes\",\"worker\",\"container\"],\"notes\":[\"K8s Worker Node\",\"64GB RAM\"],\"mac\":\"00:50:56:K8:02:02\",\"rackUnit\":13,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"k8s-worker-3\":{\"shape\":\"server\",\"name\":\"K8s Worker 3\",\"ip\":\"10.10.1.62\",\"role\":\"Container Workload\",\"tags\":[\"kubernetes\",\"worker\",\"container\"],\"notes\":[\"K8s Worker Node\",\"64GB RAM\"],\"mac\":\"00:50:56:K8:02:03\",\"rackUnit\":11,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"k8s-worker-4\":{\"shape\":\"server\",\"name\":\"K8s Worker 4\",\"ip\":\"10.10.1.63\",\"role\":\"Container Workload\",\"tags\":[\"kubernetes\",\"worker\",\"container\"],\"notes\":[\"K8s Worker Node\",\"64GB RAM\"],\"mac\":\"00:50:56:K8:02:04\",\"rackUnit\":9,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"proxy-server-1\":{\"shape\":\"server\",\"name\":\"Proxy Server 1\",\"ip\":\"10.5.0.10\",\"role\":\"Web Proxy\",\"tags\":[\"proxy\",\"squid\",\"filtering\"],\"notes\":[\"Squid Proxy\",\"Content filtering\"],\"mac\":\"00:50:56:PX:01:01\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"security\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"proxy-server-2\":{\"shape\":\"server\",\"name\":\"Proxy Server 2\",\"ip\":\"10.5.0.11\",\"role\":\"Web Proxy\",\"tags\":[\"proxy\",\"squid\",\"filtering\"],\"notes\":[\"Squid Proxy\",\"HA pair\"],\"mac\":\"00:50:56:PX:01:02\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"security\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"vpn-concentrator\":{\"shape\":\"firewall\",\"name\":\"VPN Concentrator\",\"ip\":\"10.0.5.1\",\"role\":\"Remote Access VPN\",\"tags\":[\"vpn\",\"remote\",\"security\"],\"notes\":[\"Cisco ASA 5555-X\",\"AnyConnect SSL VPN\"],\"mac\":\"00:1A:2B:VP:01:01\",\"rackUnit\":\"\",\"uHeight\":\"2\",\"layer\":\"security\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"nac-server\":{\"shape\":\"server\",\"name\":\"NAC Server\",\"ip\":\"10.5.5.10\",\"role\":\"Network Access Control\",\"tags\":[\"nac\",\"ise\",\"802.1x\"],\"notes\":[\"Cisco ISE 3.1\",\"RADIUS/TACACS+\"],\"mac\":\"00:50:56:NA:01:01\",\"rackUnit\":\"\",\"uHeight\":\"2\",\"layer\":\"security\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"print-server\":{\"shape\":\"server\",\"name\":\"Print Server\",\"ip\":\"10.10.0.150\",\"role\":\"Print Services\",\"tags\":[\"print\",\"windows\",\"services\"],\"notes\":[\"Windows Print Server\",\"50+ printers\"],\"mac\":\"00:50:56:PR:01:01\",\"rackUnit\":18,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a1\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"file-server\":{\"shape\":\"database\",\"name\":\"File Server\",\"ip\":\"10.10.0.160\",\"role\":\"File Services\",\"tags\":[\"file\",\"smb\",\"dfs\"],\"notes\":[\"Windows File Server\",\"DFS namespace\"],\"mac\":\"00:50:56:FS:01:01\",\"rackUnit\":16,\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a1\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"ca-server\":{\"shape\":\"server\",\"name\":\"Certificate Authority\",\"ip\":\"192.168.100.80\",\"role\":\"PKI Infrastructure\",\"tags\":[\"pki\",\"ca\",\"security\"],\"notes\":[\"Windows CA\",\"Enterprise Root CA\"],\"mac\":\"00:50:56:CA:01:01\",\"rackUnit\":5,\"uHeight\":\"1\",\"layer\":\"logical\",\"assignedRack\":\"mgmt-rack\",\"rackCapacity\":\"24\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"sccm-server\":{\"shape\":\"server\",\"name\":\"SCCM Server\",\"ip\":\"192.168.100.90\",\"role\":\"Endpoint Management\",\"tags\":[\"sccm\",\"patching\",\"software\"],\"notes\":[\"MECM Primary Site\",\"Software deployment\"],\"mac\":\"00:50:56:SC:01:01\",\"rackUnit\":3,\"uHeight\":\"2\",\"layer\":\"logical\",\"assignedRack\":\"mgmt-rack\",\"rackCapacity\":\"24\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"voip-cluster\":{\"shape\":\"phone\",\"name\":\"VoIP Cluster\",\"ip\":\"10.50.0.0/24\",\"role\":\"Voice Services\",\"tags\":[\"voip\",\"cisco\",\"ucm\"],\"notes\":[\"Cisco UCM Cluster\",\"3000 endpoints\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"application\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"video-conf\":{\"shape\":\"laptop\",\"name\":\"Video Conference\",\"ip\":\"10.51.0.0/24\",\"role\":\"Video Services\",\"tags\":[\"video\",\"webex\",\"teams\"],\"notes\":[\"Webex/Teams integration\",\"Meeting rooms\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"application\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"security-cameras\":{\"shape\":\"camera\",\"name\":\"Security Cameras\",\"ip\":\"10.60.0.0/24\",\"role\":\"Physical Security\",\"tags\":[\"cctv\",\"surveillance\",\"security\"],\"notes\":[\"150+ IP cameras\",\"30-day retention\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"nvr-cluster\":{\"shape\":\"server\",\"name\":\"NVR Cluster\",\"ip\":\"10.60.0.10\",\"role\":\"Video Recording\",\"tags\":[\"nvr\",\"surveillance\",\"storage\"],\"notes\":[\"Milestone XProtect\",\"500TB storage\"],\"mac\":\"00:50:56:NV:01:01\",\"rackUnit\":15,\"uHeight\":\"4\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-b2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"dev-server-1\":{\"shape\":\"server\",\"name\":\"Dev Server 1\",\"ip\":\"10.80.0.10\",\"role\":\"Development\",\"tags\":[\"dev\",\"gitlab\",\"ci-cd\",{\"type\":\"icon\",\"library\":\"selfhst\",\"name\":\"dokku\"}],\"notes\":[\"GitLab Server\",\"CI/CD pipelines\"],\"mac\":\"00:50:56:DV:01:01\",\"rackUnit\":\"\",\"uHeight\":\"2\",\"layer\":\"application\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"dev-server-2\":{\"shape\":\"server\",\"name\":\"Dev Server 2\",\"ip\":\"10.80.0.11\",\"role\":\"Development\",\"tags\":[\"dev\",\"jenkins\",\"ci-cd\"],\"notes\":[\"Jenkins Server\",\"Build automation\"],\"mac\":\"00:50:56:DV:01:02\",\"rackUnit\":\"\",\"uHeight\":\"2\",\"layer\":\"application\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"test-environment\":{\"shape\":\"shield\",\"name\":\"Test Environment\",\"ip\":\"10.81.0.0/24\",\"role\":\"QA/Testing\",\"tags\":[\"test\",\"qa\",\"staging\"],\"notes\":[\"Staging environment\",\"Pre-prod validation\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"application\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null,\"rotation\":-36},\"erp-system\":{\"shape\":\"database\",\"name\":\"ERP System\",\"ip\":\"10.90.0.10\",\"role\":\"Business Application\",\"tags\":[\"erp\",\"sap\",\"business\"],\"notes\":[\"SAP S/4HANA\",\"Financial/HR systems\"],\"mac\":\"00:50:56:ER:01:01\",\"rackUnit\":\"\",\"uHeight\":\"4\",\"layer\":\"application\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"crm-system\":{\"shape\":\"database\",\"name\":\"CRM System\",\"ip\":\"10.91.0.10\",\"role\":\"Business Application\",\"tags\":[\"crm\",\"salesforce\",\"business\"],\"notes\":[\"Salesforce integration\",\"Sales/Marketing\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"application\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"endpoint-1000\":{\"shape\":\"laptop\",\"name\":\"Corporate Endpoints\",\"ip\":\"10.70.0.0/22\",\"role\":\"User Workstations\",\"tags\":[\"endpoints\",\"workstations\",\"users\"],\"notes\":[\"~1000 corporate laptops\",\"Windows 11\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"dist-switch-floor1\":{\"shape\":\"switch\",\"name\":\"Floor 1 Switch\",\"ip\":\"10.1.1.1\",\"role\":\"Distribution\",\"tags\":[\"distribution\",\"floor-1\",\"access\"],\"notes\":[\"Cisco C9300-48P\",\"PoE+ enabled\"],\"mac\":\"00:1A:2B:FL:01:01\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"dist-switch-floor2\":{\"shape\":\"switch\",\"name\":\"Floor 2 Switch\",\"ip\":\"10.1.2.1\",\"role\":\"Distribution\",\"tags\":[\"distribution\",\"floor-2\",\"access\"],\"notes\":[\"Cisco C9300-48P\",\"PoE+ enabled\"],\"mac\":\"00:1A:2B:FL:02:01\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"dist-switch-floor3\":{\"shape\":\"switch\",\"name\":\"Floor 3 Switch\",\"ip\":\"10.1.3.1\",\"role\":\"Distribution\",\"tags\":[\"distribution\",\"floor-3\",\"access\"],\"notes\":[\"Cisco C9300-48P\",\"PoE+ enabled\"],\"mac\":\"00:1A:2B:FL:03:01\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"dist-switch-floor4\":{\"shape\":\"switch\",\"name\":\"Floor 4 Switch\",\"ip\":\"10.1.4.1\",\"role\":\"Distribution\",\"tags\":[\"distribution\",\"floor-4\",\"access\"],\"notes\":[\"Cisco C9300-48P\",\"PoE+ enabled\"],\"mac\":\"00:1A:2B:FL:04:01\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"ap-floor1-zone1\":{\"shape\":\"wifi\",\"name\":\"AP Floor 1 Zone 1\",\"ip\":\"10.20.1.10\",\"role\":\"Wireless Access\",\"tags\":[\"wifi\",\"ap\",\"floor-1\"],\"notes\":[\"Cisco 9120AX\",\"Wi-Fi 6\"],\"mac\":\"00:1A:2B:AP:01:01\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"ap-floor2-zone1\":{\"shape\":\"wifi\",\"name\":\"AP Floor 2 Zone 1\",\"ip\":\"10.20.2.10\",\"role\":\"Wireless Access\",\"tags\":[\"wifi\",\"ap\",\"floor-2\"],\"notes\":[\"Cisco 9120AX\",\"Wi-Fi 6\"],\"mac\":\"00:1A:2B:AP:02:01\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"ap-floor3-zone1\":{\"shape\":\"wifi\",\"name\":\"AP Floor 3 Zone 1\",\"ip\":\"10.20.3.10\",\"role\":\"Wireless Access\",\"tags\":[\"wifi\",\"ap\",\"floor-3\"],\"notes\":[\"Cisco 9120AX\",\"Wi-Fi 6\"],\"mac\":\"00:1A:2B:AP:03:01\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"ap-floor4-zone1\":{\"shape\":\"wifi\",\"name\":\"AP Floor 4 Zone 1\",\"ip\":\"10.20.4.10\",\"role\":\"Wireless Access\",\"tags\":[\"wifi\",\"ap\",\"floor-4\"],\"notes\":[\"Cisco 9120AX\",\"Wi-Fi 6\"],\"mac\":\"00:1A:2B:AP:04:01\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"ups-dc-1\":{\"shape\":\"rectangle\",\"name\":\"UPS DC-1\",\"ip\":\"192.168.200.10\",\"role\":\"Power Management\",\"tags\":[\"power\",\"ups\",\"datacenter\"],\"notes\":[\"APC Symmetra\",\"80kVA\",\"30 min runtime\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"ups-dc-2\":{\"shape\":\"rectangle\",\"name\":\"UPS DC-2\",\"ip\":\"192.168.200.11\",\"role\":\"Power Management\",\"tags\":[\"power\",\"ups\",\"datacenter\"],\"notes\":[\"APC Symmetra\",\"80kVA\",\"Redundant\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"pdu-rack-a1\":{\"shape\":\"rectangle\",\"name\":\"PDU Rack A1\",\"ip\":\"192.168.200.21\",\"role\":\"Power Distribution\",\"tags\":[\"power\",\"pdu\",\"rack-a1\"],\"notes\":[\"APC Switched PDU\",\"Per-outlet metering\"],\"mac\":\"\",\"rackUnit\":1,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a1\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"pdu-rack-a2\":{\"shape\":\"rectangle\",\"name\":\"PDU Rack A2\",\"ip\":\"192.168.200.22\",\"role\":\"Power Distribution\",\"tags\":[\"power\",\"pdu\",\"rack-a2\"],\"notes\":[\"APC Switched PDU\",\"Per-outlet metering\"],\"mac\":\"\",\"rackUnit\":1,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"cooling-1\":{\"shape\":\"rectangle\",\"name\":\"CRAC Unit 1\",\"ip\":\"192.168.200.30\",\"role\":\"Cooling\",\"tags\":[\"cooling\",\"hvac\",\"datacenter\"],\"notes\":[\"Liebert CRV\",\"Row-based cooling\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"cooling-2\":{\"shape\":\"rectangle\",\"name\":\"CRAC Unit 2\",\"ip\":\"192.168.200.31\",\"role\":\"Cooling\",\"tags\":[\"cooling\",\"hvac\",\"datacenter\"],\"notes\":[\"Liebert CRV\",\"N+1 redundancy\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"camera-a\":{\"shape\":\"camera\",\"name\":\"camera A\",\"ip\":\"0.0.0.0\",\"role\":\"\",\"tags\":[],\"notes\":[],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"ping\":{\"enabled\":false,\"protocol\":\"http\",\"customUrl\":\"\",\"timeout\":3000,\"status\":\"unknown\",\"lastCheck\":null},\"locked\":false,\"groupId\":null,\"fovEnabled\":true,\"fovRotation\":104,\"fovDistance\":500,\"fovSweep\":60,\"fovSpeed\":10,\"fovAnimate\":true},\"camera-a-copy\":{\"shape\":\"camera\",\"name\":\"camera B\",\"ip\":\"0.0.0.0\",\"role\":\"\",\"tags\":[],\"notes\":[],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"ping\":{\"enabled\":false,\"protocol\":\"http\",\"customUrl\":\"\",\"timeout\":3000,\"status\":\"unknown\",\"lastCheck\":null},\"locked\":false,\"groupId\":null,\"fovEnabled\":true,\"fovRotation\":162,\"fovDistance\":500,\"fovSweep\":60,\"fovSpeed\":10,\"fovAnimate\":false}},\"edges\":{\"list\":[{\"id\":\"isp1-router1\",\"from\":\"isp-primary\",\"to\":\"core-router-1\",\"width\":6,\"color\":\"#10b981\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"Primary WAN link\"],\"fromPort\":\"Gi0/0\",\"toPort\":\"Gi1/0/1\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"isp2-router2\",\"from\":\"isp-secondary\",\"to\":\"core-router-2\",\"width\":6,\"color\":\"#10b981\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"Backup WAN link\"],\"fromPort\":\"Gi0/0\",\"toPort\":\"Gi1/0/1\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"router1-router2\",\"from\":\"core-router-1\",\"to\":\"core-router-2\",\"width\":4,\"color\":\"#f59e0b\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"HSRP Peering\"],\"fromPort\":\"Gi1/0/24\",\"toPort\":\"Gi1/0/24\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"router1-fw1\",\"from\":\"core-router-1\",\"to\":\"fw-external-1\",\"width\":4,\"color\":\"#ef4444\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"router2-fw2\",\"from\":\"core-router-2\",\"to\":\"fw-external-2\",\"width\":4,\"color\":\"#ef4444\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"fw1-fw2\",\"from\":\"fw-external-1\",\"to\":\"fw-external-2\",\"width\":3,\"color\":\"#f59e0b\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"HA heartbeat\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"dashed\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"fw1-coresw1\",\"from\":\"fw-external-1\",\"to\":\"core-switch-1\",\"width\":4,\"color\":\"#475569\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"fw2-coresw2\",\"from\":\"fw-external-2\",\"to\":\"core-switch-2\",\"width\":4,\"color\":\"#475569\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw1-coresw2\",\"from\":\"core-switch-1\",\"to\":\"core-switch-2\",\"width\":5,\"color\":\"#3b82f6\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"VPC peer-link\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw1-fwint\",\"from\":\"core-switch-1\",\"to\":\"fw-internal\",\"width\":3,\"color\":\"#ef4444\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw2-fwint\",\"from\":\"core-switch-2\",\"to\":\"fw-internal\",\"width\":3,\"color\":\"#ef4444\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw1-racka1\",\"from\":\"core-switch-1\",\"to\":\"dc-rack-a1\",\"width\":4,\"color\":\"#475569\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw2-racka1\",\"from\":\"core-switch-2\",\"to\":\"dc-rack-a1\",\"width\":4,\"color\":\"#475569\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw1-racka2\",\"from\":\"core-switch-1\",\"to\":\"dc-rack-a2\",\"width\":4,\"color\":\"#475569\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw2-racka2\",\"from\":\"core-switch-2\",\"to\":\"dc-rack-a2\",\"width\":4,\"color\":\"#475569\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw1-rackb1\",\"from\":\"core-switch-1\",\"to\":\"dc-rack-b1\",\"width\":4,\"color\":\"#475569\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw2-rackb1\",\"from\":\"core-switch-2\",\"to\":\"dc-rack-b1\",\"width\":4,\"color\":\"#475569\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw1-rackb2\",\"from\":\"core-switch-1\",\"to\":\"dc-rack-b2\",\"width\":4,\"color\":\"#475569\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw2-rackb2\",\"from\":\"core-switch-2\",\"to\":\"dc-rack-b2\",\"width\":4,\"color\":\"#475569\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"fw1-dmz\",\"from\":\"fw-external-1\",\"to\":\"dmz-rack\",\"width\":3,\"color\":\"#f59e0b\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"DMZ segment\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"fw2-dmz\",\"from\":\"fw-external-2\",\"to\":\"dmz-rack\",\"width\":3,\"color\":\"#f59e0b\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"DMZ segment\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw1-mgmt\",\"from\":\"core-switch-1\",\"to\":\"mgmt-rack\",\"width\":3,\"color\":\"#8b5cf6\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"OOB management\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw1-wlc1\",\"from\":\"core-switch-1\",\"to\":\"wlc-primary\",\"width\":3,\"color\":\"#06b6d4\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw2-wlc2\",\"from\":\"core-switch-2\",\"to\":\"wlc-secondary\",\"width\":3,\"color\":\"#06b6d4\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\",\"animate\":true},{\"id\":\"wlc1-wlc2\",\"from\":\"wlc-primary\",\"to\":\"wlc-secondary\",\"width\":2,\"color\":\"#f59e0b\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"HA pair\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"dashed\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"wlc1-mobile-hq\",\"from\":\"wlc-primary\",\"to\":\"mobile-zone-hq\",\"width\":2,\"color\":\"#06b6d4\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"wlc1-mobile-guest\",\"from\":\"wlc-primary\",\"to\":\"mobile-zone-guest\",\"width\":2,\"color\":\"#06b6d4\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"wlc1-mobile-iot\",\"from\":\"wlc-primary\",\"to\":\"mobile-zone-iot\",\"width\":2,\"color\":\"#06b6d4\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"router1-branch-ny\",\"from\":\"core-router-1\",\"to\":\"branch-router-ny\",\"width\":3,\"color\":\"#a855f7\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"SD-WAN tunnel\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"dashed\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"router1-branch-la\",\"from\":\"core-router-1\",\"to\":\"branch-router-la\",\"width\":3,\"color\":\"#a855f7\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"SD-WAN tunnel\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"dashed\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"router1-branch-chi\",\"from\":\"core-router-1\",\"to\":\"branch-router-chi\",\"width\":3,\"color\":\"#a855f7\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"SD-WAN tunnel\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"dashed\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"router1-branch-lon\",\"from\":\"core-router-1\",\"to\":\"branch-router-lon\",\"width\":3,\"color\":\"#a855f7\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"SD-WAN tunnel\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"dashed\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"router1-branch-tokyo\",\"from\":\"core-router-1\",\"to\":\"branch-router-tokyo\",\"width\":3,\"color\":\"#a855f7\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"SD-WAN tunnel\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"dashed\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"router1-aws\",\"from\":\"core-router-1\",\"to\":\"cloud-aws\",\"width\":3,\"color\":\"#f97316\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"Direct Connect\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"dashed\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"router2-azure\",\"from\":\"core-router-2\",\"to\":\"cloud-azure\",\"width\":3,\"color\":\"#0ea5e9\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"ExpressRoute\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"dashed\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"fwint-gcp\",\"from\":\"fw-internal\",\"to\":\"cloud-gcp\",\"width\":2,\"color\":\"#22c55e\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"VPN tunnel\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"dashed\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw1-floor1\",\"from\":\"core-switch-1\",\"to\":\"dist-switch-floor1\",\"width\":3,\"color\":\"#475569\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw1-floor2\",\"from\":\"core-switch-1\",\"to\":\"dist-switch-floor2\",\"width\":3,\"color\":\"#475569\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw2-floor3\",\"from\":\"core-switch-2\",\"to\":\"dist-switch-floor3\",\"width\":3,\"color\":\"#475569\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw2-floor4\",\"from\":\"core-switch-2\",\"to\":\"dist-switch-floor4\",\"width\":3,\"color\":\"#475569\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"floor1-endpoints\",\"from\":\"dist-switch-floor1\",\"to\":\"endpoint-1000\",\"width\":2,\"color\":\"#94a3b8\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"floor1-ap1\",\"from\":\"dist-switch-floor1\",\"to\":\"ap-floor1-zone1\",\"width\":2,\"color\":\"#06b6d4\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"floor2-ap2\",\"from\":\"dist-switch-floor2\",\"to\":\"ap-floor2-zone1\",\"width\":2,\"color\":\"#06b6d4\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"floor3-ap3\",\"from\":\"dist-switch-floor3\",\"to\":\"ap-floor3-zone1\",\"width\":2,\"color\":\"#06b6d4\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"floor4-ap4\",\"from\":\"dist-switch-floor4\",\"to\":\"ap-floor4-zone1\",\"width\":2,\"color\":\"#06b6d4\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"fwint-proxy1\",\"from\":\"fw-internal\",\"to\":\"proxy-server-1\",\"width\":2,\"color\":\"#ef4444\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"fwint-proxy2\",\"from\":\"fw-internal\",\"to\":\"proxy-server-2\",\"width\":2,\"color\":\"#ef4444\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"fwext1-vpn\",\"from\":\"fw-external-1\",\"to\":\"vpn-concentrator\",\"width\":3,\"color\":\"#8b5cf6\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw1-nac\",\"from\":\"core-switch-1\",\"to\":\"nac-server\",\"width\":2,\"color\":\"#f59e0b\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw1-voip\",\"from\":\"core-switch-1\",\"to\":\"voip-cluster\",\"width\":3,\"color\":\"#22c55e\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw2-video\",\"from\":\"core-switch-2\",\"to\":\"video-conf\",\"width\":3,\"color\":\"#22c55e\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw1-cameras\",\"from\":\"core-switch-1\",\"to\":\"security-cameras\",\"width\":2,\"color\":\"#94a3b8\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"fwint-dev1\",\"from\":\"fw-internal\",\"to\":\"dev-server-1\",\"width\":2,\"color\":\"#a855f7\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"fwint-dev2\",\"from\":\"fw-internal\",\"to\":\"dev-server-2\",\"width\":2,\"color\":\"#a855f7\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\",\"animate\":true,\"animationSpeed\":\"1.5\"},{\"id\":\"fwint-test\",\"from\":\"fw-internal\",\"to\":\"test-environment\",\"width\":2,\"color\":\"#a855f7\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw1-erp\",\"from\":\"core-switch-1\",\"to\":\"erp-system\",\"width\":3,\"color\":\"#f59e0b\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"fwext1-crm\",\"from\":\"fw-external-1\",\"to\":\"crm-system\",\"width\":2,\"color\":\"#f59e0b\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"Salesforce cloud\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"dashed\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"ups1-racka1\",\"from\":\"ups-dc-1\",\"to\":\"dc-rack-a1\",\"width\":2,\"color\":\"#fbbf24\",\"direction\":\"forward\",\"type\":\"main\",\"notes\":[\"Power feed A\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"ups2-racka2\",\"from\":\"ups-dc-2\",\"to\":\"dc-rack-a2\",\"width\":2,\"color\":\"#fbbf24\",\"direction\":\"forward\",\"type\":\"main\",\"notes\":[\"Power feed B\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"ups1-rackb1\",\"from\":\"ups-dc-1\",\"to\":\"dc-rack-b1\",\"width\":2,\"color\":\"#fbbf24\",\"direction\":\"forward\",\"type\":\"main\",\"notes\":[\"Power feed A\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\",\"animate\":true,\"animationSpeed\":\"4\"},{\"id\":\"ups2-rackb2\",\"from\":\"ups-dc-2\",\"to\":\"dc-rack-b2\",\"width\":2,\"color\":\"#fbbf24\",\"direction\":\"forward\",\"type\":\"main\",\"notes\":[\"Power feed B\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"cooling1-racka1\",\"from\":\"cooling-1\",\"to\":\"dc-rack-a1\",\"width\":2,\"color\":\"#38bdf8\",\"direction\":\"forward\",\"type\":\"main\",\"notes\":[\"Cooling zone\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"dotted\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"cooling2-rackb1\",\"from\":\"cooling-2\",\"to\":\"dc-rack-b1\",\"width\":2,\"color\":\"#38bdf8\",\"direction\":\"forward\",\"type\":\"main\",\"notes\":[\"Cooling zone\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"dotted\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"custom-1765237881452\",\"type\":\"custom\",\"color\":\"#c800ff\",\"width\":4,\"lineStyle\":\"solid\",\"direction\":\"forward\",\"points\":[{\"x\":3492.3994140625,\"y\":1526.9556884765625},{\"x\":3500.609619140625,\"y\":1830.7386474609375},{\"x\":3303.561279296875,\"y\":1732.2144775390625}],\"notes\":[],\"routing\":\"orthogonal\"}]},\"positions\":{\"core-router-1\":{\"x\":3720.166015625,\"y\":245.9932403564453},\"core-router-2\":{\"x\":2499.883407638303,\"y\":329.99503430389154},\"fw-external-1\":{\"x\":3221.7385182723783,\"y\":1016.1364499992887},\"fw-external-2\":{\"x\":1915.5213706410505,\"y\":224.43528858865443},\"fw-internal\":{\"x\":1746.9168185079352,\"y\":477.5300527221864},\"core-switch-1\":{\"x\":449.39860669455675,\"y\":384.4578707617695},\"core-switch-2\":{\"x\":761.1664921394672,\"y\":180.89283910873155},\"dc-rack-a1\":{\"x\":783.7017241128451,\"y\":647.4086870405963},\"dc-rack-a2\":{\"x\":209.25701628255229,\"y\":228.01593190351014},\"dc-rack-b1\":{\"x\":3184.3186625759854,\"y\":1627.4495531027196},\"dc-rack-b2\":{\"x\":245.37065918741246,\"y\":499.6191264194081},\"dmz-rack\":{\"x\":2176.4105289561007,\"y\":610.8312056412005},\"mgmt-rack\":{\"x\":1601.2987201807314,\"y\":1281.4753424975324},\"esxi-host-01\":{\"x\":2162.2166789540615,\"y\":2608.110619289529},\"esxi-host-02\":{\"x\":2205.94717202368,\"y\":2689.67539624076},\"esxi-host-03\":{\"x\":2154.6015436939074,\"y\":2771.203009774913},\"esxi-host-04\":{\"x\":2195.986926025096,\"y\":2845},\"tor-switch-a1\":{\"x\":2146.8943639962963,\"y\":2845},\"esxi-host-05\":{\"x\":2185.9099961569727,\"y\":2845},\"esxi-host-06\":{\"x\":2139.099728450725,\"y\":2845},\"esxi-host-07\":{\"x\":2175.7223818764883,\"y\":2845},\"esxi-host-08\":{\"x\":2131.2222777148922,\"y\":2845},\"tor-switch-a2\":{\"x\":2165.4301485385085,\"y\":2845},\"san-primary\":{\"x\":2123.2667017518106,\"y\":2845},\"san-secondary\":{\"x\":2155.0394237844876,\"y\":2845},\"fc-switch-1\":{\"x\":2115.2377370375634,\"y\":2845},\"fc-switch-2\":{\"x\":2144.5563938942755,\"y\":2845},\"backup-server-1\":{\"x\":2107.1401637413705,\"y\":2845},\"backup-server-2\":{\"x\":2133.987300103025,\"y\":2845},\"tape-library\":{\"x\":2098.9788028796397,\"y\":2845},\"tor-switch-b1\":{\"x\":2123.338434885373,\"y\":2845},\"tor-switch-b2\":{\"x\":2090.7585134456995,\"y\":2845},\"web-server-1\":{\"x\":2112.6161382091163,\"y\":2845},\"web-server-2\":{\"x\":2082.484189516922,\"y\":2845},\"waf-1\":{\"x\":2101.826793760617,\"y\":2845},\"load-balancer-dmz\":{\"x\":2074.1607573409574,\"y\":2845},\"mail-gateway\":{\"x\":2090.97682514417,\"y\":2845},\"dns-external-1\":{\"x\":2065.7931724028163,\"y\":2845},\"dns-external-2\":{\"x\":2080.0726920576153,\"y\":2845},\"vcenter\":{\"x\":2057.3864164745437,\"y\":2845},\"nsx-manager\":{\"x\":2069.1208864464534,\"y\":2845},\"siem-server\":{\"x\":2048.945494649244,\"y\":2845},\"nms-server\":{\"x\":2058.1279286387635,\"y\":2845},\"jump-server\":{\"x\":2040.4754323612206,\"y\":2845},\"ipam-server\":{\"x\":2047.1003634632284,\"y\":2845},\"wlc-primary\":{\"x\":1575.9723612611924,\"y\":2306.135986328125},\"wlc-secondary\":{\"x\":1468.1361870166274,\"y\":1563.733642578125},\"mobile-zone-hq\":{\"x\":2354.901177346808,\"y\":2806.0078125},\"mobile-zone-guest\":{\"x\":2307.6605605284435,\"y\":2611.047119140625},\"mobile-zone-iot\":{\"x\":2229.397686389302,\"y\":2299.110107421875},\"branch-router-ny\":{\"x\":3151.903101363964,\"y\":633.6580810546875},\"branch-router-la\":{\"x\":3083.8876194705945,\"y\":506.90625},\"branch-router-chi\":{\"x\":3355.02409980103,\"y\":393.1805725097656},\"branch-router-lon\":{\"x\":3113.609823320121,\"y\":260.4093322753906},\"branch-router-tokyo\":{\"x\":3699.3234994733834,\"y\":471.4241027832031},\"cloud-aws\":{\"x\":3436.528122523513,\"y\":545.9614868164062},\"cloud-azure\":{\"x\":2592.566210818907,\"y\":2724.068115234375},\"cloud-gcp\":{\"x\":2827.3183770424234,\"y\":2731.397216796875},\"isp-primary\":{\"x\":3712.192068081962,\"y\":615.64990234375},\"isp-secondary\":{\"x\":3253.9473366098055,\"y\":1993.2629089355469},\"dc-internal-1\":{\"x\":1958.4243458877936,\"y\":2845},\"dc-internal-2\":{\"x\":1963.768951182132,\"y\":2845},\"app-server-1\":{\"x\":1947.3819379304134,\"y\":2845},\"app-server-2\":{\"x\":1955.2862087394126,\"y\":2845},\"db-server-1\":{\"x\":1936.3708569559828,\"y\":2845},\"db-server-2\":{\"x\":1946.8300873488822,\"y\":2845},\"k8s-master-1\":{\"x\":1925.397658583093,\"y\":2845},\"k8s-master-2\":{\"x\":1938.405621494142,\"y\":2845},\"k8s-master-3\":{\"x\":1914.4688758763386,\"y\":2845},\"k8s-worker-1\":{\"x\":1930.017826812177,\"y\":2845},\"k8s-worker-2\":{\"x\":1903.5910154567553,\"y\":2845},\"k8s-worker-3\":{\"x\":1921.6716971072178,\"y\":2845},\"k8s-worker-4\":{\"x\":1892.7705536280016,\"y\":2845},\"proxy-server-1\":{\"x\":1806.1152433697903,\"y\":653.7529296875},\"proxy-server-2\":{\"x\":2937.4207928721535,\"y\":2628.7880859375},\"vpn-concentrator\":{\"x\":3642.252088474593,\"y\":946.7255249023438},\"nac-server\":{\"x\":1153.2626148502184,\"y\":1172.1895751953125},\"print-server\":{\"x\":1896.9328460745962,\"y\":2845},\"file-server\":{\"x\":1860.7177871362182,\"y\":2845},\"ca-server\":{\"x\":1888.8027739274805,\"y\":2845},\"sccm-server\":{\"x\":1850.1909418511675,\"y\":2845},\"voip-cluster\":{\"x\":1777.038465328039,\"y\":1616.8961181640625},\"video-conf\":{\"x\":1993.8373941679588,\"y\":2244.936309814453},\"security-cameras\":{\"x\":1674.413336949044,\"y\":2046.0380859375},\"nvr-cluster\":{\"x\":1829.4110389706402,\"y\":2845},\"dev-server-1\":{\"x\":2800.5894350649614,\"y\":1175.623291015625},\"dev-server-2\":{\"x\":1945.0822182484326,\"y\":1164.5184783935547},\"test-environment\":{\"x\":2932.0863047891075,\"y\":862.4592895507812},\"erp-system\":{\"x\":789.9880103985649,\"y\":473.7113342285156},\"crm-system\":{\"x\":3514.6003232048542,\"y\":1137.7720947265625},\"endpoint-1000\":{\"x\":991.6812012057328,\"y\":2284.42236328125},\"dist-switch-floor1\":{\"x\":654.2091033261356,\"y\":2020.0086669921875},\"dist-switch-floor2\":{\"x\":853.8845527112826,\"y\":1843.2872314453125},\"dist-switch-floor3\":{\"x\":1899.4353951584517,\"y\":1456.5068359375},\"dist-switch-floor4\":{\"x\":488.5289313756234,\"y\":181.47256469726562},\"ap-floor1-zone1\":{\"x\":1140.16846970184,\"y\":2070.2916259765625},\"ap-floor2-zone1\":{\"x\":688.1952143592268,\"y\":2384.4775390625},\"ap-floor3-zone1\":{\"x\":2145.3803027919676,\"y\":1890.2816162109375},\"ap-floor4-zone1\":{\"x\":517.646146409649,\"y\":565.59716796875},\"ups-dc-1\":{\"x\":771.1406786539856,\"y\":295.9266662597656},\"ups-dc-2\":{\"x\":216.2410855890687,\"y\":330.3345947265625},\"pdu-rack-a1\":{\"x\":1804.774444371901,\"y\":2845},\"pdu-rack-a2\":{\"x\":1741.6184034693686,\"y\":2845},\"cooling-1\":{\"x\":245.7080801919958,\"y\":626.1914672851562},\"cooling-2\":{\"x\":1603.293611085831,\"y\":981.0621185302734},\"camera-a\":{\"x\":166.57075412676295,\"y\":145},\"camera-a-copy\":{\"x\":1040.653076171875,\"y\":738.42822265625}},\"sizes\":{\"isp-secondary\":139,\"test-environment\":121,\"dev-server-1\":128,\"core-router-2\":120,\"camera-a\":45,\"camera-a-copy\":45},\"styles\":{\"dc-rack-b2\":{\"all\":{\"circleColor\":\"#ff0000\"}},\"dc-rack-a1\":{\"all\":{\"circleColor\":\"#ff0000\"}},\"dc-rack-b1\":{\"all\":{\"circleColor\":\"#ff0000\",\"titleSize\":59}},\"isp-secondary\":{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"alist\"},\"circleColor\":\"#4d2c58\",\"circleBorder\":\"#000000\",\"titleColor\":\"#006eff\"}},\"core-router-2\":{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"actual-budget\"},\"pingOffsetX\":-15,\"pingOffsetY\":-38}},\"fw-external-1\":{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"anonaddy\"}}},\"cloud-aws\":{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"ansible\"}}},\"isp-primary\":{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"wikidocs\"}}},\"branch-router-tokyo\":{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"adguard-home\"}}},\"core-router-1\":{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"borg\"}}},\"test-environment\":{\"all\":{\"icon\":{\"library\":\"simple\",\"name\":\"apple\"}}},\"dev-server-1\":{\"all\":{\"icon\":{\"library\":\"simple\",\"name\":\"amazonwebservices\"}}}},\"legend\":{\"#10b981\":\"Trusted Lan\",\"#f59e0b\":\"Secure Lan\",\"#ef4444\":\"DMZ\",\"#475569\":\"Main ISP\",\"#3b82f6\":\"Alternate ISP\",\"#8b5cf6\":\"you can edit me too\",\"#06b6d4\":\"you can edit me too\",\"#a855f7\":\"you can edit me too\",\"#f97316\":\"you can edit me too\",\"#0ea5e9\":\"you can edit me too\",\"#22c55e\":\"you can edit me too\",\"#94a3b8\":\"you can edit me too\",\"#fbbf24\":\"you can edit me too\",\"#38bdf8\":\"you can edit me too\",\"#c800ff\":\"you can edit me too\"},\"rects\":{\"list\":[{\"id\":\"rect-1765237540610\",\"x\":2879.214599609375,\"y\":159.71981811523438,\"width\":992.196044921875,\"height\":538.8650817871094,\"color\":\"#f97316\",\"style\":\"filled\",\"lineStyle\":\"solid\",\"notes\":[]},{\"id\":\"rect-1765237681216\",\"x\":448.3926696777344,\"y\":1671.651123046875,\"width\":916.3436584472656,\"height\":924.27734375,\"color\":\"#c800ff\",\"style\":\"outlined\",\"lineStyle\":\"solid\",\"notes\":[]},{\"id\":\"rect-1766437913740\",\"x\":904.5889892578125,\"y\":115.40318298339844,\"width\":110.93878173828125,\"height\":919.6242218017578,\"color\":\"#5215f9\",\"style\":\"filled\",\"lineStyle\":\"wall\",\"notes\":[],\"borderWidth\":13},{\"id\":\"rect-1766437935414\",\"x\":130.93685150146484,\"y\":1072.3624877929688,\"width\":872.9131851196289,\"height\":99.260986328125,\"color\":\"#5215f9\",\"style\":\"filled\",\"lineStyle\":\"wall\",\"notes\":[],\"borderWidth\":13}]},\"texts\":{\"list\":[{\"id\":\"text-1765237828167\",\"x\":3411.458740234375,\"y\":1390.00439453125,\"content\":\"Double click on desktop\\nor long press on mobile\\nto enter rack canvas view\",\"fontSize\":46,\"color\":\"#e2e8f0\",\"fontWeight\":\"bold\",\"fontStyle\":\"italic\",\"textAlign\":\"middle\",\"textDecoration\":\"none\",\"bgColor\":\"#000000\",\"bgEnabled\":false,\"opacity\":1},{\"id\":\"text-1766446595277\",\"x\":654.3878479003906,\"y\":1367.7945556640625,\"content\":\"SITE A\",\"fontSize\":101,\"color\":\"#e2e8f0\",\"fontWeight\":\"normal\",\"fontStyle\":\"normal\",\"textAlign\":\"start\",\"textDecoration\":\"none\",\"bgColor\":\"#000000\",\"bgEnabled\":false,\"opacity\":1},{\"id\":\"text-1766446610211\",\"x\":180.63662719726562,\"y\":1128.822998046875,\"content\":\"SITE A\",\"fontSize\":101,\"color\":\"#e2e8f0\",\"fontWeight\":\"normal\",\"fontStyle\":\"normal\",\"textAlign\":\"start\",\"textDecoration\":\"none\",\"bgColor\":\"#000000\",\"bgEnabled\":false,\"opacity\":1},{\"id\":\"text-1766453024797\",\"x\":968.6458740234375,\"y\":1028.6621398925781,\"content\":\"SITE A\",\"fontSize\":101,\"color\":\"#e2e8f0\",\"fontWeight\":\"normal\",\"fontStyle\":\"normal\",\"textAlign\":\"start\",\"textDecoration\":\"none\",\"bgColor\":\"#000000\",\"bgEnabled\":false,\"opacity\":1,\"rotation\":-89,\"_dragStartX\":972.46826171875,\"_dragStartY\":1009.5499572753906},{\"id\":\"text-1766453070975\",\"x\":613.1589965820312,\"y\":1139.512939453125,\"content\":\"SITE A\",\"fontSize\":101,\"color\":\"#e2e8f0\",\"fontWeight\":\"normal\",\"fontStyle\":\"normal\",\"textAlign\":\"start\",\"textDecoration\":\"none\",\"bgColor\":\"#000000\",\"bgEnabled\":false,\"opacity\":1},{\"id\":\"text-1766453072857\",\"x\":968.64599609375,\"y\":474.40818786621094,\"content\":\"SITE A\",\"fontSize\":101,\"color\":\"#e2e8f0\",\"fontWeight\":\"normal\",\"fontStyle\":\"normal\",\"textAlign\":\"start\",\"textDecoration\":\"none\",\"bgColor\":\"#000000\",\"bgEnabled\":false,\"opacity\":1,\"rotation\":269,\"_dragStartX\":1480.85302734375,\"_dragStartY\":822.2503356933594},{\"id\":\"text-1766458222326\",\"x\":3167.812744140625,\"y\":2190.516357421875,\"content\":\"\",\"fontSize\":18,\"color\":\"#e2e8f0\",\"fontWeight\":\"normal\",\"fontStyle\":\"normal\",\"textAlign\":\"start\",\"textDecoration\":\"none\",\"bgColor\":\"#000000\",\"bgEnabled\":false,\"opacity\":1}]},\"pageState\":{\"title\":\"The One File Corporate\",\"background\":\"\",\"topbarBg\":\"rgba(9, 12, 20, 0.9)\",\"topbarBorder\":\"#1f2533\",\"panel\":\"#0b0e13\",\"panelAlt\":\"#10141b\",\"accent\":\"#4fd1c5\",\"sidebarBg\":\"#10141b\",\"btnBg\":\"#0b0e13\",\"btnText\":\"#e2e8f0\",\"tagFill\":\"#1e293b\",\"tagText\":\"#e2e8f0\",\"tagBorder\":\"#475569\",\"inputBg\":\"#0b0e13\",\"inputText\":\"#e2e8f0\",\"inputBorder\":\"#1f2937\",\"inputFont\":\"Inter, system-ui, sans-serif\",\"inputFontSize\":14,\"toolbarBg\":\"#0f172a\",\"toolbarBorder\":\"#1f2937\",\"toolbarText\":\"#94a3b8\",\"toolbarBtnBg\":\"#0b0e13\",\"toolbarBtnText\":\"#e2e8f0\",\"minimapDots\":\"#94a3b8\",\"canvasHintEnabled\":true,\"canvasHintText\":\"\",\"canvasHintBg\":\"#0f172a\",\"canvasHintColor\":\"#94a3b8\",\"danger\":\"#f56565\",\"textMain\":\"#e2e8f0\",\"textSoft\":\"#94a3b8\",\"topbarHeight\":103,\"sidebarWidth\":350,\"mobileFooterHeight\":40,\"sidebarCollapsed\":false,\"nodeFill\":\"#1e293b\",\"nodeStroke\":\"#475569\",\"nodeTitle\":\"#e2e8f0\",\"nodeSub\":\"#94a3b8\",\"nodeTitleSize\":41,\"nodeSubSize\":27,\"nodeFont\":\"monospace\",\"defaultEdge\":\"#475569\",\"selectionHandle\":\"#f59e0b\",\"selectionHandleSize\":8,\"groupIndicator\":\"#4fd1c5\",\"canvasGradientTop\":\"#1e2532\",\"canvasGradientBottom\":\"#050608\",\"canvasBorder\":\"#475569\",\"canvasGrid\":\"#475569\",\"canvasGridSize\":50,\"canvasGridEnabled\":true,\"rackFrameFill\":\"#0f172a\",\"rackGridEnabled\":true,\"rackFrameStroke\":\"#4fd1c5\",\"rackLineColor\":\"#475569\",\"rackTextColor\":\"#4fd1c5\",\"viewOnly\":false,\"defaultEdgeRouting\":\"orthogonal\",\"animateConnections\":false,\"animationStyle\":\"arrows\",\"animationDirection\":\"all\",\"animationSpeed\":4,\"autoPingEnabled\":false,\"autoPingInterval\":30}},{\"id\":\"tab-1765235136918\",\"name\":\"Homelab 2\",\"nodes\":{\"internet\":{\"shape\":\"stop-sign\",\"name\":\"Internet\",\"ip\":\"0.0.0.0\",\"role\":\"\",\"tags\":[],\"notes\":[],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"internet-copy\":{\"shape\":\"firewall\",\"name\":\"OPNSENSE\",\"ip\":\"0.0.0.0\",\"role\":\"\",\"tags\":[],\"notes\":[],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"opnsense-copy\":{\"shape\":\"firewall\",\"name\":\"Docker\",\"ip\":\"0.0.0.0\",\"role\":\"\",\"tags\":[],\"notes\":[],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"docker-copy\":{\"shape\":\"firewall\",\"name\":\"Docker2\",\"ip\":\"0.0.0.0\",\"role\":\"\",\"tags\":[],\"notes\":[],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"docker-copy-1\":{\"shape\":\"firewall\",\"name\":\"Docker3\",\"ip\":\"0.0.0.0\",\"role\":\"\",\"tags\":[],\"notes\":[],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"docker-copy-2\":{\"shape\":\"firewall\",\"name\":\"Docker 4\",\"ip\":\"0.0.0.0\",\"role\":\"\",\"tags\":[{\"type\":\"icon\",\"library\":\"selfhst\",\"name\":\"docker\"},{\"type\":\"icon\",\"library\":\"selfhst\",\"name\":\"authentik\"},{\"type\":\"icon\",\"library\":\"selfhst\",\"name\":\"immich\"}],\"notes\":[],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"opnsense-copy-1\":{\"shape\":\"firewall\",\"name\":\"OPNSENSE GUEST\",\"ip\":\"0.0.0.0\",\"role\":\"\",\"tags\":[],\"notes\":[],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"phone\":{\"shape\":\"phone\",\"name\":\"Phone\",\"ip\":\"0.0.0.0\",\"role\":\"\",\"tags\":[],\"notes\":[],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"desktop\":{\"shape\":\"pc\",\"name\":\"Desktop\",\"ip\":\"0.0.0.0\",\"role\":\"\",\"tags\":[],\"notes\":[],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"dns\":{\"shape\":\"cloud\",\"name\":\"DNS\",\"ip\":\"0.0.0.0\",\"role\":\"\",\"tags\":[],\"notes\":[],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"racked\":{\"shape\":\"server\",\"name\":\"Racked\",\"ip\":\"\",\"role\":\"Rack\",\"tags\":[],\"notes\":[],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":true,\"locked\":false,\"groupId\":null},\"thermostat\":{\"shape\":\"thermostat\",\"name\":\"Thermostat\",\"ip\":\"0.0.0.0\",\"role\":\"\",\"tags\":[],\"notes\":[],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"video-doorbell\":{\"shape\":\"doorbell\",\"name\":\"Video Doorbell\",\"ip\":\"0.0.0.0\",\"role\":\"\",\"tags\":[],\"notes\":[],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"smart-lock\":{\"shape\":\"smart-lock\",\"name\":\"Smart Lock\",\"ip\":\"0.0.0.0\",\"role\":\"\",\"tags\":[],\"notes\":[],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"smart-bulb\":{\"shape\":\"smart-bulb\",\"name\":\"Smart Bulb\",\"ip\":\"0.0.0.0\",\"role\":\"\",\"tags\":[],\"notes\":[],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"robot-vacuum\":{\"shape\":\"vacuum\",\"name\":\"Robot Vacuum\",\"ip\":\"0.0.0.0\",\"role\":\"\",\"tags\":[],\"notes\":[],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null}},\"edges\":{\"list\":[{\"id\":\"internet-internet-copy-1765238145151\",\"from\":\"internet\",\"to\":\"internet-copy\",\"width\":4,\"color\":\"#55e208\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1},{\"id\":\"internet-copy-opnsense-copy-1765238187451\",\"from\":\"internet-copy\",\"to\":\"opnsense-copy\",\"width\":4,\"color\":\"#4c00ff\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1},{\"id\":\"internet-copy-docker-copy-1765238242477\",\"from\":\"internet-copy\",\"to\":\"docker-copy\",\"width\":4,\"color\":\"#4c00ff\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1},{\"id\":\"internet-copy-docker-copy-1-1765238244637\",\"from\":\"internet-copy\",\"to\":\"docker-copy-1\",\"width\":4,\"color\":\"#4c00ff\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1},{\"id\":\"internet-copy-docker-copy-2-1765238246233\",\"from\":\"internet-copy\",\"to\":\"docker-copy-2\",\"width\":4,\"color\":\"#4c00ff\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1},{\"id\":\"internet-opnsense-copy-1-1765238266117\",\"from\":\"internet\",\"to\":\"opnsense-copy-1\",\"width\":4,\"color\":\"#80ff00\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1},{\"id\":\"opnsense-copy-1-dns-1765238347996\",\"from\":\"opnsense-copy-1\",\"to\":\"dns\",\"width\":4,\"color\":\"#fb00ff\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1},{\"id\":\"dns-desktop-1765238386101\",\"from\":\"dns\",\"to\":\"desktop\",\"width\":4,\"color\":\"#ff00d0\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1},{\"id\":\"phone-dns-1765238391156\",\"from\":\"phone\",\"to\":\"dns\",\"width\":4,\"color\":\"#ff00d0\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1},{\"id\":\"custom-1765239449323\",\"type\":\"custom\",\"color\":\"#f97316\",\"width\":4,\"lineStyle\":\"solid\",\"direction\":\"forward\",\"points\":[{\"x\":2936.464111328125,\"y\":786.07958984375},{\"x\":3184.112060546875,\"y\":887.6153564453125},{\"x\":2763.110107421875,\"y\":981.7216796875}],\"notes\":[]}]},\"positions\":{\"internet\":{\"x\":1757.7735887323333,\"y\":298.77284240722656},\"internet-copy\":{\"x\":2066.9677515897347,\"y\":473.4119134177565},\"opnsense-copy\":{\"x\":1773.8400660428597,\"y\":666.5758233298659},\"docker-copy\":{\"x\":1931.1978950081452,\"y\":782.2775961320921},\"docker-copy-1\":{\"x\":2158.1262397347077,\"y\":767.7122274797483},\"docker-copy-2\":{\"x\":2342.2663764534577,\"y\":631.7681967180296},\"opnsense-copy-1\":{\"x\":2757.879480087803,\"y\":307.6117116091891},\"phone\":{\"x\":3312.857751572178,\"y\":502.58220111114224},\"desktop\":{\"x\":2971.700036728428,\"y\":480.7287465212985},\"dns\":{\"x\":3200.4643189549906,\"y\":320.469591247861},\"racked\":{\"x\":2645.5845448279656,\"y\":970.7820678889219},\"thermostat\":{\"x\":1323.0595481711202,\"y\":574.6132617105841},\"video-doorbell\":{\"x\":1188.4284446554952,\"y\":455.3684191812872},\"smart-lock\":{\"x\":1292.286782057839,\"y\":790.0231738199591},\"smart-bulb\":{\"x\":1496.156899245339,\"y\":716.9377246012091},\"robot-vacuum\":{\"x\":2288.5581443625265,\"y\":978.5069995035528}},\"sizes\":{\"core-router-1\":36,\"internet\":168,\"phone\":121,\"desktop\":147,\"racked\":137,\"docker-copy-2\":82},\"styles\":{\"internet\":{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"amazon-web-services\"},\"circleColor\":\"#db0000\",\"circleBorder\":\"#000000\",\"titleSize\":52,\"subSize\":46}},\"opnsense-copy-1\":{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"opnsense-v1\"}}},\"internet-copy\":{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"opnsense\"}}},\"docker-copy-2\":{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"docker\"}}},\"docker-copy-1\":{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"authportal\"}}},\"docker-copy\":{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"jotty\"}}},\"opnsense-copy\":{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"portainer\"}}},\"racked\":{\"all\":{\"icon\":{\"library\":\"mdi\",\"name\":\"server-security\"},\"circleColor\":\"#010813\",\"circleBorder\":\"#ffffff\"}}},\"legend\":{\"#475569\":\"you can edit me too\",\"#65758b\":\"you can edit me too\",\"#63748c\":\"you can edit me too\",\"#5e6f87\":\"you can edit me too\",\"#586a84\":\"you can edit me too\",\"#4f627d\":\"you can edit me too\",\"#455873\":\"you can edit me too\",\"#3d506c\":\"you can edit me too\",\"#354964\":\"you can edit me too\",\"#2e415c\":\"you can edit me too\",\"#293c56\":\"you can edit me too\",\"#273a53\":\"you can edit me too\",\"#253750\":\"you can edit me too\",\"#23354d\":\"you can edit me too\",\"#203046\":\"you can edit me too\",\"#1e2d43\":\"you can edit me too\",\"#1a283d\":\"you can edit me too\",\"#172435\":\"you can edit me too\",\"#141f2e\":\"you can edit me too\",\"#111a27\":\"you can edit me too\",\"#0f1824\":\"you can edit me too\",\"#0d1521\":\"you can edit me too\",\"#0c131d\":\"you can edit me too\",\"#0c1d1c\":\"you can edit me too\",\"#0c1c1d\":\"you can edit me too\",\"#0c191d\":\"you can edit me too\",\"#0c141d\":\"you can edit me too\",\"#0c0d1d\":\"you can edit me too\",\"#130c1d\":\"you can edit me too\",\"#1b0c1d\":\"you can edit me too\",\"#1d0c17\":\"you can edit me too\",\"#1d0c10\":\"you can edit me too\",\"#1d0c0c\":\"you can edit me too\",\"#3b1b1b\":\"you can edit me too\",\"#3c1a1a\":\"you can edit me too\",\"#3f1c1c\":\"you can edit me too\",\"#401c1c\":\"you can edit me too\",\"#451c1c\":\"you can edit me too\",\"#461b1b\":\"you can edit me too\",\"#4c1a1a\":\"you can edit me too\",\"#521919\":\"you can edit me too\",\"#571919\":\"you can edit me too\",\"#5d1818\":\"you can edit me too\",\"#631717\":\"you can edit me too\",\"#651515\":\"you can edit me too\",\"#6a1616\":\"you can edit me too\",\"#6f1515\":\"you can edit me too\",\"#711414\":\"you can edit me too\",\"#761414\":\"you can edit me too\",\"#771313\":\"you can edit me too\",\"#7c1313\":\"you can edit me too\",\"#811313\":\"you can edit me too\",\"#821212\":\"you can edit me too\",\"#871212\":\"you can edit me too\",\"#881111\":\"you can edit me too\",\"#8d1111\":\"you can edit me too\",\"#8e1010\":\"you can edit me too\",\"#8f0f0f\":\"you can edit me too\",\"#900e0e\":\"you can edit me too\",\"#8e0b0b\":\"you can edit me too\",\"#8c0d0d\":\"you can edit me too\",\"#880c0c\":\"you can edit me too\",\"#830c0c\":\"you can edit me too\",\"#7e0c0c\":\"you can edit me too\",\"#790c0c\":\"you can edit me too\",\"#730c0c\":\"you can edit me too\",\"#6f0b0b\":\"you can edit me too\",\"#0b6f64\":\"you can edit me too\",\"#0b6f5f\":\"you can edit me too\",\"#0b6f56\":\"you can edit me too\",\"#0b6f49\":\"you can edit me too\",\"#0b6f31\":\"you can edit me too\",\"#0b6f1f\":\"you can edit me too\",\"#0b6f0d\":\"you can edit me too\",\"#176f0b\":\"you can edit me too\",\"#266f0b\":\"you can edit me too\",\"#296f0b\":\"you can edit me too\",\"#2e6f0b\":\"you can edit me too\",\"#1a2d10\":\"you can edit me too\",\"#1c3111\":\"you can edit me too\",\"#213814\":\"you can edit me too\",\"#233c15\":\"you can edit me too\",\"#254017\":\"you can edit me too\",\"#294918\":\"you can edit me too\",\"#2b4d1a\":\"you can edit me too\",\"#2d511a\":\"you can edit me too\",\"#315a1b\":\"you can edit me too\",\"#35631c\":\"you can edit me too\",\"#37681d\":\"you can edit me too\",\"#3b721d\":\"you can edit me too\",\"#3f7b1e\":\"you can edit me too\",\"#42851e\":\"you can edit me too\",\"#46901d\":\"you can edit me too\",\"#499a1d\":\"you can edit me too\",\"#4b9f1d\":\"you can edit me too\",\"#4ca61c\":\"you can edit me too\",\"#50b01c\":\"you can edit me too\",\"#51b71a\":\"you can edit me too\",\"#50b918\":\"you can edit me too\",\"#51c115\":\"you can edit me too\",\"#53c615\":\"you can edit me too\",\"#53c814\":\"you can edit me too\",\"#52c913\":\"you can edit me too\",\"#54d011\":\"you can edit me too\",\"#53d110\":\"you can edit me too\",\"#55d510\":\"you can edit me too\",\"#55d70f\":\"you can edit me too\",\"#54d80e\":\"you can edit me too\",\"#54da0b\":\"you can edit me too\",\"#56df0c\":\"you can edit me too\",\"#53db0a\":\"you can edit me too\",\"#55e00b\":\"you can edit me too\",\"#55e109\":\"you can edit me too\",\"#55e208\":\"ISP LINE\",\"#4c00ff\":\"MY Guest NETWORK\",\"#80ff00\":\"you can edit me too\",\"#3b4234\":\"you can edit me too\",\"#3a3442\":\"you can edit me too\",\"#3b3442\":\"you can edit me too\",\"#3c3442\":\"you can edit me too\",\"#3d3442\":\"you can edit me too\",\"#3e3442\":\"you can edit me too\",\"#3f3442\":\"you can edit me too\",\"#403442\":\"you can edit me too\",\"#413442\":\"you can edit me too\",\"#653d66\":\"you can edit me too\",\"#683f69\":\"you can edit me too\",\"#6c416c\":\"you can edit me too\",\"#6f4370\":\"you can edit me too\",\"#704270\":\"you can edit me too\",\"#734474\":\"you can edit me too\",\"#784479\":\"you can edit me too\",\"#7d447e\":\"you can edit me too\",\"#7e437f\":\"you can edit me too\",\"#834384\":\"you can edit me too\",\"#844285\":\"you can edit me too\",\"#89418b\":\"you can edit me too\",\"#8e428f\":\"you can edit me too\",\"#904091\":\"you can edit me too\",\"#923e93\":\"you can edit me too\",\"#973e98\":\"you can edit me too\",\"#943c96\":\"you can edit me too\",\"#993c9a\":\"you can edit me too\",\"#963a98\":\"you can edit me too\",\"#973899\":\"you can edit me too\",\"#99369b\":\"you can edit me too\",\"#9a359c\":\"you can edit me too\",\"#9b349d\":\"you can edit me too\",\"#9d329f\":\"you can edit me too\",\"#9e31a0\":\"you can edit me too\",\"#a02fa2\":\"you can edit me too\",\"#9d2d9f\":\"you can edit me too\",\"#9f2ba1\":\"you can edit me too\",\"#a129a3\":\"you can edit me too\",\"#a327a5\":\"you can edit me too\",\"#a525a7\":\"you can edit me too\",\"#a723a9\":\"you can edit me too\",\"#a921ab\":\"you can edit me too\",\"#ab1fad\":\"you can edit me too\",\"#ad1daf\":\"you can edit me too\",\"#ae1cb0\":\"you can edit me too\",\"#b019b3\":\"you can edit me too\",\"#b118b4\":\"you can edit me too\",\"#b316b6\":\"you can edit me too\",\"#b816bb\":\"you can edit me too\",\"#b514b8\":\"you can edit me too\",\"#ba14bd\":\"you can edit me too\",\"#b712ba\":\"you can edit me too\",\"#bb13be\":\"you can edit me too\",\"#b811bb\":\"you can edit me too\",\"#be10c1\":\"you can edit me too\",\"#bb0ebe\":\"you can edit me too\",\"#bd0cc0\":\"you can edit me too\",\"#be0bc1\":\"you can edit me too\",\"#c108c4\":\"you can edit me too\",\"#be06c1\":\"you can edit me too\",\"#c103c4\":\"you can edit me too\",\"#c301c6\":\"you can edit me too\",\"#c400c7\":\"you can edit me too\",\"#c900cc\":\"you can edit me too\",\"#ce00d1\":\"you can edit me too\",\"#d300d6\":\"you can edit me too\",\"#d800db\":\"you can edit me too\",\"#dd00e0\":\"you can edit me too\",\"#e200e6\":\"you can edit me too\",\"#ec00f0\":\"you can edit me too\",\"#f100f5\":\"you can edit me too\",\"#f600fa\":\"you can edit me too\",\"#fb00ff\":\"you can edit me too\",\"#ff00d0\":\"iPhone (always guest iPhone)\",\"#f97316\":\"you can edit me too\"},\"rects\":{\"list\":[{\"id\":\"rect-1765238219615\",\"x\":2680.053955078125,\"y\":251.44879150390625,\"width\":814.10400390625,\"height\":389.26678466796875,\"color\":\"#ec0999\",\"style\":\"filled\",\"lineStyle\":\"solid\",\"notes\":[]}]},\"texts\":{\"list\":[{\"id\":\"text-1765238422602\",\"x\":2466.35986328125,\"y\":741.6801147460938,\"content\":\"Double click on desktop\\nor long press on mobile\\nto enter rack canvas view\",\"fontSize\":40,\"color\":\"#e2e8f0\",\"fontWeight\":\"normal\",\"fontStyle\":\"normal\",\"textAlign\":\"start\",\"textDecoration\":\"none\",\"bgColor\":\"#000000\",\"bgEnabled\":false,\"opacity\":1}]},\"pageState\":{\"title\":\"The One File\",\"background\":\"\",\"topbarBg\":\"rgba(9, 12, 20, 0.9)\",\"topbarBorder\":\"#1f2533\",\"panel\":\"#2f0e0e\",\"panelAlt\":\"#10141b\",\"accent\":\"#a75252\",\"sidebarBg\":\"#10141b\",\"btnBg\":\"#0b0e13\",\"btnText\":\"#e2e8f0\",\"tagFill\":\"#1e293b\",\"tagText\":\"#e2e8f0\",\"tagBorder\":\"#475569\",\"inputBg\":\"#0b0e13\",\"inputText\":\"#e2e8f0\",\"inputBorder\":\"#1f2937\",\"inputFont\":\"Inter, system-ui, sans-serif\",\"inputFontSize\":14,\"toolbarBg\":\"#441215\",\"toolbarBorder\":\"#1f2937\",\"toolbarText\":\"#94a3b8\",\"toolbarBtnBg\":\"#0b0e13\",\"toolbarBtnText\":\"#e2e8f0\",\"minimapDots\":\"#94a3b8\",\"canvasHintEnabled\":true,\"canvasHintText\":\"\",\"canvasHintBg\":\"#0f172a\",\"canvasHintColor\":\"#94a3b8\",\"danger\":\"#f56565\",\"textMain\":\"#e2e8f0\",\"textSoft\":\"#94a3b8\",\"topbarHeight\":112,\"sidebarWidth\":350,\"mobileFooterHeight\":40,\"sidebarCollapsed\":false,\"nodeFill\":\"#1e293b\",\"nodeStroke\":\"#475569\",\"nodeTitle\":\"#e2e8f0\",\"nodeSub\":\"#94a3b8\",\"nodeTitleSize\":18,\"nodeSubSize\":13,\"nodeFont\":\"Inter, system-ui, sans-serif\",\"defaultEdge\":\"#475569\",\"selectionHandle\":\"#f59e0b\",\"selectionHandleSize\":8,\"groupIndicator\":\"#4fd1c5\",\"canvasGradientTop\":\"#1e2532\",\"canvasGradientBottom\":\"#050608\",\"canvasBorder\":\"#475569\",\"canvasGrid\":\"#475569\",\"canvasGridSize\":50,\"canvasGridEnabled\":true,\"rackFrameFill\":\"#0f172a\",\"rackGridEnabled\":true,\"rackFrameStroke\":\"#4fd1c5\",\"rackLineColor\":\"#475569\",\"rackTextColor\":\"#4fd1c5\",\"viewOnly\":false,\"defaultEdgeRouting\":\"curved\",\"animateConnections\":false,\"animationStyle\":\"arrows\",\"animationDirection\":\"all\",\"animationSpeed\":1.5}}],\"currentTabIndex\":1,\"encryptedSections\":{},\"auditLog\":[{\"timestamp\":1766459386369,\"type\":\"export\",\"description\":\"Exported Markdown: the-one-file.md\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766459379962,\"type\":\"export\",\"description\":\"Exported JSON: the-one-file.json\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766459374396,\"type\":\"save\",\"description\":\"File saved: the-one-file.html\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766459370112,\"type\":\"tab\",\"description\":\"Switched to tab: Homelab 2\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766459361896,\"type\":\"save\",\"description\":\"File saved: the-one-file-corporate.html\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459352785,\"type\":\"node\",\"description\":\"rotate node\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459352343,\"type\":\"node\",\"description\":\"rotate node\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459352224,\"type\":\"node\",\"description\":\"rotate node\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459351722,\"type\":\"node\",\"description\":\"rotate node\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459351541,\"type\":\"node\",\"description\":\"rotate node\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459350380,\"type\":\"node\",\"description\":\"resize node\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459350178,\"type\":\"node\",\"description\":\"resize node\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459350049,\"type\":\"node\",\"description\":\"resize node\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459346233,\"type\":\"node\",\"description\":\"change shape\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459335960,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459335846,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459335742,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459335630,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459335398,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459335292,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459335188,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459332894,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459332780,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459332661,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459332556,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459332450,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459332346,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459331643,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459331492,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459331378,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459331274,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459330996,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459330868,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459330764,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459330637,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459327262,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459327136,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459326544,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459326438,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459326334,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459326176,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459325232,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459325088,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459324279,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459323835,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459323732,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459323200,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459323093,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459322989,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459322883,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459322780,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459321176,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459321070,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459320748,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459320642,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459320492,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459319706,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459319600,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459319055,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459318467,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459318363,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459318258,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459317846,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459317742,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459317464,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459317314,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459313457,\"type\":\"node\",\"description\":\"change shape\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459310142,\"type\":\"node\",\"description\":\"change shape\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459306160,\"type\":\"node\",\"description\":\"rotate node\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459305289,\"type\":\"node\",\"description\":\"rotate node\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459305132,\"type\":\"node\",\"description\":\"rotate node\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459304675,\"type\":\"node\",\"description\":\"rotate node\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459304530,\"type\":\"node\",\"description\":\"rotate node\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459304396,\"type\":\"node\",\"description\":\"rotate node\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459304290,\"type\":\"node\",\"description\":\"rotate node\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459304157,\"type\":\"node\",\"description\":\"rotate node\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459303660,\"type\":\"node\",\"description\":\"rotate node\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459303534,\"type\":\"node\",\"description\":\"rotate node\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459303414,\"type\":\"node\",\"description\":\"rotate node\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459303247,\"type\":\"node\",\"description\":\"rotate node\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459303144,\"type\":\"node\",\"description\":\"rotate node\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459303002,\"type\":\"node\",\"description\":\"rotate node\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459302875,\"type\":\"node\",\"description\":\"rotate node\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459302725,\"type\":\"node\",\"description\":\"rotate node\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459302613,\"type\":\"node\",\"description\":\"rotate node\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459302507,\"type\":\"node\",\"description\":\"rotate node\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459301997,\"type\":\"node\",\"description\":\"rotate node\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459301893,\"type\":\"node\",\"description\":\"rotate node\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458459721,\"type\":\"tab\",\"description\":\"Switched to tab: Corporate Site B\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458438687,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766458438583,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766458438437,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766458438333,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766458438187,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766458438083,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766458437937,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766458437833,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766458437687,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766458437583,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766458437437,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766458437333,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766458437187,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766458436932,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766458435139,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766458434986,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766458434840,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766458434736,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766458434590,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766458434486,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766458434340,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766458434236,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766458434090,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766458433986,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766458433840,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766458433736,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766458433590,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766458433334,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766458429157,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766458429053,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766458428947,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766458426794,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766458426691,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766458426584,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766458426481,\"type\":\"style\",\"description\":\"style change\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766458423513,\"type\":\"node\",\"description\":\"change shape\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766458421278,\"type\":\"node\",\"description\":\"change shape\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766458416555,\"type\":\"node\",\"description\":\"change shape\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766458404891,\"type\":\"node\",\"description\":\"add node\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766458392272,\"type\":\"node\",\"description\":\"add node\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766458378068,\"type\":\"node\",\"description\":\"add node\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766458367460,\"type\":\"node\",\"description\":\"add node\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766458356226,\"type\":\"node\",\"description\":\"add node\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766458338198,\"type\":\"tab\",\"description\":\"Switched to tab: Homelab 2\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766458258865,\"type\":\"save\",\"description\":\"File saved: the-one-file-corporate.html\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458249051,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458248926,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458248793,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458248683,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458248556,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458248451,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458248325,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458248221,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458248092,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458247989,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458247885,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458247784,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458247284,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458246701,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458246523,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458246410,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458246129,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458245955,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458245737,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458245627,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458245425,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458245247,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458245133,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458244923,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458244741,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458244313,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458244198,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458244055,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458243873,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458243637,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458243399,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458243218,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458241018,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458237254,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458235033,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458234835,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458234694,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458234425,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458227773,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458227623,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458227441,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458227279,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458227155,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458226967,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458226847,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458226733,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458226563,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458226421,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458222326,\"type\":\"text\",\"description\":\"add text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458213989,\"type\":\"connection\",\"description\":\"delete edge\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458209437,\"type\":\"text\",\"description\":\"delete text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766458195427,\"type\":\"import\",\"description\":\"Imported JSON: the-one-file-corporate.json (107 nodes, 65 connections)\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455847368,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455844534,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455844054,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455843762,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455843560,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455843371,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455843162,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455842852,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455842747,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455842601,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455842449,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455842348,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455842098,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455841678,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455841236,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455841053,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455840901,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455840650,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455839427,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455839234,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455839061,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455837247,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455837081,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455836893,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455836377,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455836198,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455835455,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455834630,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455831880,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455831676,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455831451,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455830817,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455830687,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455830176,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455830048,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455829944,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455829816,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455378795,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455378693,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455378459,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455378316,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455378180,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455378069,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455377956,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455377677,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455377558,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455377448,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455377318,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455377209,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453090534,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453090317,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453090213,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453090112,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453090009,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453089903,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453088895,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453088793,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453088689,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453088584,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453088480,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453088250,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453087236,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453086725,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453086485,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453086373,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453086142,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453086043,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453072857,\"type\":\"text\",\"description\":\"paste text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453070975,\"type\":\"text\",\"description\":\"paste text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453054439,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453053127,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453052450,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453052106,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453051948,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453051806,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453051334,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453050207,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453042725,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453042179,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453041797,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453041570,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453039703,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453039291,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453039168,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453039065,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453038481,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453038365,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453038237,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453038105,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453038001,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453037850,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453037745,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453037495,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453037378,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453037182,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453037078,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453036972,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453036860,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453036147,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453035945,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453035825,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453035720,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453035443,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453035337,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453035233,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453035127,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453035026,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453034917,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453031063,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453030955,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453030833,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453030732,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453030225,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453030104,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453029968,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453029796,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453029474,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453024797,\"type\":\"text\",\"description\":\"paste text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766451118553,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766450929324,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766450817210,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766450257424,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766450255024,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766450254395,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766450253241,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766450251598,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766450250392,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766450248756,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766450244072,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766450242166,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766450240998,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766450236492,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766450233672,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766450232384,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766450231012,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766450230254,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766450229302,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766450228132,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446610211,\"type\":\"text\",\"description\":\"paste text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446604849,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446604702,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446604550,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446604404,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446604305,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446604204,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446604099,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446603952,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446603849,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446603702,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446603599,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446603452,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446603348,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446603202,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446603099,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446602953,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446602850,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446602702,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446602600,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446602453,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446602349,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446602204,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446602101,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446602000,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446601848,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446601702,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446601601,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446601452,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446601301,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446601154,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446601049,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446600948,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446600802,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446600550,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446598595,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446598461,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446598171,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446598017,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446597219,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446595278,\"type\":\"text\",\"description\":\"add text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445633355,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445632515,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445631735,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445630757,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445627846,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445625085,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445618645,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445617784,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445608998,\"type\":\"edit\",\"description\":\"edit zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445608720,\"type\":\"edit\",\"description\":\"edit zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445608540,\"type\":\"edit\",\"description\":\"edit zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445608376,\"type\":\"edit\",\"description\":\"edit zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445608204,\"type\":\"edit\",\"description\":\"edit zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445608038,\"type\":\"edit\",\"description\":\"edit zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445607852,\"type\":\"edit\",\"description\":\"edit zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445607678,\"type\":\"edit\",\"description\":\"edit zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445607506,\"type\":\"edit\",\"description\":\"edit zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445607319,\"type\":\"edit\",\"description\":\"edit zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445607154,\"type\":\"edit\",\"description\":\"edit zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445604410,\"type\":\"edit\",\"description\":\"edit zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445604244,\"type\":\"edit\",\"description\":\"edit zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445604066,\"type\":\"edit\",\"description\":\"edit zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445603900,\"type\":\"edit\",\"description\":\"edit zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445603743,\"type\":\"edit\",\"description\":\"edit zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445603563,\"type\":\"edit\",\"description\":\"edit zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445603406,\"type\":\"edit\",\"description\":\"edit zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445603226,\"type\":\"edit\",\"description\":\"edit zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445603052,\"type\":\"edit\",\"description\":\"edit zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445602880,\"type\":\"edit\",\"description\":\"edit zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445602641,\"type\":\"edit\",\"description\":\"edit zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445576567,\"type\":\"edit\",\"description\":\"edit edge animation speed\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445570290,\"type\":\"edit\",\"description\":\"edit edge animation speed\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445567192,\"type\":\"edit\",\"description\":\"edit edge animate\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445566766,\"type\":\"edit\",\"description\":\"edit edge animate\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445565520,\"type\":\"edit\",\"description\":\"edit edge animate\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445398115,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445390895,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445385694,\"type\":\"edit\",\"description\":\"toggle fov animation\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445383241,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445382911,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445381695,\"type\":\"edit\",\"description\":\"edit node name\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445375383,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445374665,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445373273,\"type\":\"node\",\"description\":\"paste node\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445372205,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438157980,\"type\":\"edit\",\"description\":\"edit edge animate\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438157430,\"type\":\"edit\",\"description\":\"edit edge animation speed\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438152691,\"type\":\"edit\",\"description\":\"edit edge animate\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438151948,\"type\":\"edit\",\"description\":\"edit edge animate\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438151286,\"type\":\"edit\",\"description\":\"edit edge animate\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438146174,\"type\":\"edit\",\"description\":\"edit edge animate\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438145649,\"type\":\"edit\",\"description\":\"edit edge animate\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438144555,\"type\":\"edit\",\"description\":\"edit edge animate\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438143655,\"type\":\"edit\",\"description\":\"edit edge animate\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438142504,\"type\":\"edit\",\"description\":\"edit edge animation speed\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438130077,\"type\":\"edit\",\"description\":\"edit edge animate\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438129561,\"type\":\"edit\",\"description\":\"edit edge animate\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438128772,\"type\":\"edit\",\"description\":\"edit edge animate\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438128398,\"type\":\"edit\",\"description\":\"edit edge animate\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438122820,\"type\":\"edit\",\"description\":\"edit edge animate\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438122062,\"type\":\"edit\",\"description\":\"edit edge animate\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438119836,\"type\":\"edit\",\"description\":\"edit edge animate\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438119588,\"type\":\"edit\",\"description\":\"edit edge animate\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438095045,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438093965,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438062827,\"type\":\"edit\",\"description\":\"toggle fov animation\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438047679,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438044161,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438041852,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438039668,\"type\":\"edit\",\"description\":\"resize node\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438039562,\"type\":\"edit\",\"description\":\"resize node\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438039421,\"type\":\"edit\",\"description\":\"resize node\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438039260,\"type\":\"edit\",\"description\":\"resize node\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438039150,\"type\":\"edit\",\"description\":\"resize node\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438039039,\"type\":\"edit\",\"description\":\"resize node\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438028508,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438021410,\"type\":\"edit\",\"description\":\"toggle fov\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438019234,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438017562,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438014356,\"type\":\"node\",\"description\":\"add node\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766437981696,\"type\":\"edit\",\"description\":\"apply routing to all\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766437966551,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766437964879,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766437963627,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766437961813,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766437961193,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766437957989,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766437956467,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766437953437,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766437952239,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766437950807,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766437944990,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766437943699,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766437935414,\"type\":\"zone\",\"description\":\"draw zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766437919019,\"type\":\"zone\",\"description\":\"delete zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766437917758,\"type\":\"zone\",\"description\":\"draw zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766437913740,\"type\":\"zone\",\"description\":\"draw zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766437882832,\"type\":\"import\",\"description\":\"Imported JSON: theonefile-networkening-corporate-demo.json (105 nodes, 65 connections)\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766263279163,\"type\":\"tab\",\"description\":\"Switched to tab: Corporate Site B\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766263270414,\"type\":\"export\",\"description\":\"Exported JSON: the-one-file.json\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766263260682,\"type\":\"save\",\"description\":\"File saved: the-one-file.html\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766263259518,\"type\":\"tab\",\"description\":\"Switched to tab: Homelab 2\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766263249401,\"type\":\"save\",\"description\":\"File saved: the-one-file-corporate.html\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766263246362,\"type\":\"import\",\"description\":\"Imported JSON: theonefile-networkening-corporate-demo.json (105 nodes, 65 connections)\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766190721141,\"type\":\"save\",\"description\":\"File saved: the-one-file-corporate.html\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766190717499,\"type\":\"tab\",\"description\":\"Switched to tab: Corporate Site B\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766190710946,\"type\":\"save\",\"description\":\"File saved: the-one-file.html\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766190705273,\"type\":\"save\",\"description\":\"File saved: the-one-file.html\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766190703463,\"type\":\"tab\",\"description\":\"Switched to tab: Homelab 2\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766190695709,\"type\":\"save\",\"description\":\"File saved: the-one-file-corporate.html\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766190688417,\"type\":\"import\",\"description\":\"Imported JSON: theonefile-networkening-corporate-demo.json (105 nodes, 65 connections)\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1765402888416,\"type\":\"save\",\"description\":\"File saved: the-one-file-corporate.html\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1765402884873,\"type\":\"tab\",\"description\":\"Switched to tab: Corporate Site B\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1765402878108,\"type\":\"save\",\"description\":\"File saved: the-one-file.html\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1765402866440,\"type\":\"save\",\"description\":\"File saved: the-one-file.html\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1765402865008,\"type\":\"tab\",\"description\":\"Switched to tab: Homelab 2\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1765402860428,\"type\":\"save\",\"description\":\"File saved: the-one-file-corporate.html\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1765402858103,\"type\":\"import\",\"description\":\"Imported JSON: theonefile-networkening-corporate-demo.json (105 nodes, 65 connections)\",\"details\":{},\"tab\":\"Corporate Site B\"}],\"savedStyleSets\":[]}\n#\n# The One File - Node List\n# Exported from The One File on 2025-12-23T03:09:52.432Z\nname,ip,role,shape,tags,layer,mac,rackUnit,uHeight,assignedRack,rackCapacity,isRack,locked,groupId,x,y,size,notes,styles\nInternet,0.0.0.0,,stop-sign,,physical,,,1,,42,false,false,,1758,299,168,,\"{\"\"all\"\":{\"\"icon\"\":{\"\"library\"\":\"\"selfhst\"\",\"\"name\"\":\"\"amazon-web-services\"\"},\"\"circleColor\"\":\"\"#db0000\"\",\"\"circleBorder\"\":\"\"#000000\"\",\"\"titleSize\"\":52,\"\"subSize\"\":46}}\"\nOPNSENSE,0.0.0.0,,firewall,,physical,,,1,,42,false,false,,2067,473,50,,\"{\"\"all\"\":{\"\"icon\"\":{\"\"library\"\":\"\"selfhst\"\",\"\"name\"\":\"\"opnsense\"\"}}}\"\nDocker,0.0.0.0,,firewall,,physical,,,1,,42,false,false,,1774,667,50,,\"{\"\"all\"\":{\"\"icon\"\":{\"\"library\"\":\"\"selfhst\"\",\"\"name\"\":\"\"portainer\"\"}}}\"\nDocker2,0.0.0.0,,firewall,,physical,,,1,,42,false,false,,1931,782,50,,\"{\"\"all\"\":{\"\"icon\"\":{\"\"library\"\":\"\"selfhst\"\",\"\"name\"\":\"\"jotty\"\"}}}\"\nDocker3,0.0.0.0,,firewall,,physical,,,1,,42,false,false,,2158,768,50,,\"{\"\"all\"\":{\"\"icon\"\":{\"\"library\"\":\"\"selfhst\"\",\"\"name\"\":\"\"authportal\"\"}}}\"\nDocker 4,0.0.0.0,,firewall,[object Object];[object Object];[object Object],physical,,,1,,42,false,false,,2342,632,82,,\"{\"\"all\"\":{\"\"icon\"\":{\"\"library\"\":\"\"selfhst\"\",\"\"name\"\":\"\"docker\"\"}}}\"\nOPNSENSE GUEST,0.0.0.0,,firewall,,physical,,,1,,42,false,false,,2758,308,50,,\"{\"\"all\"\":{\"\"icon\"\":{\"\"library\"\":\"\"selfhst\"\",\"\"name\"\":\"\"opnsense-v1\"\"}}}\"\nPhone,0.0.0.0,,phone,,physical,,,1,,42,false,false,,3313,503,121,,\nDesktop,0.0.0.0,,pc,,physical,,,1,,42,false,false,,2972,481,147,,\nDNS,0.0.0.0,,cloud,,physical,,,1,,42,false,false,,3200,320,50,,\nRacked,,Rack,server,,physical,,,1,,42,true,false,,2646,971,137,,\"{\"\"all\"\":{\"\"icon\"\":{\"\"library\"\":\"\"mdi\"\",\"\"name\"\":\"\"server-security\"\"},\"\"circleColor\"\":\"\"#010813\"\",\"\"circleBorder\"\":\"\"#ffffff\"\"}}\"\nThermostat,0.0.0.0,,thermostat,,physical,,,1,,42,false,false,,1323,575,50,,\nVideo Doorbell,0.0.0.0,,doorbell,,physical,,,1,,42,false,false,,1188,455,50,,\nSmart Lock,0.0.0.0,,smart-lock,,physical,,,1,,42,false,false,,1292,790,50,,\nSmart Bulb,0.0.0.0,,smart-bulb,,physical,,,1,,42,false,false,,1496,717,50,,\nRobot Vacuum,0.0.0.0,,vacuum,,physical,,,1,,42,false,false,,2289,979,50,,\n"
  },
  {
    "path": "demos/csv-exports/the-one-file-networkening-corporate.csv",
    "content": "#THEONEFILE_CONFIG:{\"nodeData\":{\"core-router-1\":{\"shape\":\"router\",\"name\":\"Core Router 1\",\"ip\":\"10.0.0.1\",\"role\":\"Core Routing\",\"tags\":[\"core\",\"tier-1\",\"redundant\"],\"notes\":[\"Primary core router\",\"BGP peering enabled\"],\"mac\":\"00:1A:2B:3C:4D:01\",\"rackUnit\":\"\",\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"core-router-2\":{\"shape\":\"router\",\"name\":\"Core Router 2\",\"ip\":\"10.0.0.2\",\"role\":\"Core Routing\",\"tags\":[\"core\",\"tier-1\",\"redundant\"],\"notes\":[\"Secondary core router\",\"HSRP standby\"],\"mac\":\"00:1A:2B:3C:4D:02\",\"rackUnit\":\"\",\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null,\"ping\":{\"enabled\":true,\"protocol\":\"custom\",\"customUrl\":\"https://google.com\",\"timeout\":3000,\"status\":\"online\",\"lastCheck\":\"2025-12-09T00:15:04.343Z\"}},\"fw-external-1\":{\"shape\":\"firewall\",\"name\":\"External FW 1\",\"ip\":\"10.0.1.1\",\"role\":\"Perimeter Security\",\"tags\":[\"security\",\"perimeter\",\"ha-pair\"],\"notes\":[\"Palo Alto PA-5250\",\"Active node\"],\"mac\":\"00:1A:2B:3C:4D:10\",\"rackUnit\":\"\",\"uHeight\":\"2\",\"layer\":\"security\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"fw-external-2\":{\"shape\":\"firewall\",\"name\":\"External FW 2\",\"ip\":\"10.0.1.2\",\"role\":\"Perimeter Security\",\"tags\":[\"security\",\"perimeter\",\"ha-pair\"],\"notes\":[\"Palo Alto PA-5250\",\"Passive node\"],\"mac\":\"00:1A:2B:3C:4D:11\",\"rackUnit\":\"\",\"uHeight\":\"2\",\"layer\":\"security\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"fw-internal\":{\"shape\":\"firewall\",\"name\":\"Internal FW\",\"ip\":\"10.0.2.1\",\"role\":\"Internal Segmentation\",\"tags\":[\"security\",\"internal\"],\"notes\":[\"East-West traffic inspection\"],\"mac\":\"00:1A:2B:3C:4D:12\",\"rackUnit\":\"\",\"uHeight\":\"2\",\"layer\":\"security\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"core-switch-1\":{\"shape\":\"switch\",\"name\":\"Core Switch 1\",\"ip\":\"10.0.10.1\",\"role\":\"Core Switching\",\"tags\":[\"core\",\"layer3\",\"redundant\"],\"notes\":[\"Cisco Nexus 9000\",\"VPC Domain 1\"],\"mac\":\"00:1A:2B:3C:4D:20\",\"rackUnit\":\"\",\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"core-switch-2\":{\"shape\":\"switch\",\"name\":\"Core Switch 2\",\"ip\":\"10.0.10.2\",\"role\":\"Core Switching\",\"tags\":[\"core\",\"layer3\",\"redundant\"],\"notes\":[\"Cisco Nexus 9000\",\"VPC Domain 1\"],\"mac\":\"00:1A:2B:3C:4D:21\",\"rackUnit\":\"\",\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"dc-rack-a1\":{\"shape\":\"server\",\"name\":\"DC Rack A1\",\"ip\":\"10.10.0.0/24\",\"role\":\"Data Center Rack\",\"tags\":[\"datacenter\",\"row-a\",\"production\"],\"notes\":[\"Row A, Position 1\",\"Primary compute\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":true,\"locked\":false,\"groupId\":null},\"dc-rack-a2\":{\"shape\":\"server\",\"name\":\"DC Rack A2\",\"ip\":\"10.10.1.0/24\",\"role\":\"Data Center Rack\",\"tags\":[\"datacenter\",\"row-a\",\"production\"],\"notes\":[\"Row A, Position 2\",\"Primary compute\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":true,\"locked\":false,\"groupId\":null},\"dc-rack-b1\":{\"shape\":\"server\",\"name\":\"DC Rack B1\",\"ip\":\"10.10.2.0/24\",\"role\":\"Data Center Rack\",\"tags\":[\"datacenter\",\"row-b\",\"storage\"],\"notes\":[\"Row B, Position 1\",\"Storage systems\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":true,\"locked\":false,\"groupId\":null},\"dc-rack-b2\":{\"shape\":\"server\",\"name\":\"DC Rack B2\",\"ip\":\"10.10.3.0/24\",\"role\":\"Data Center Rack\",\"tags\":[\"datacenter\",\"row-b\",\"storage\"],\"notes\":[\"Row B, Position 2\",\"Storage systems\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":true,\"locked\":false,\"groupId\":null},\"dmz-rack\":{\"shape\":\"server\",\"name\":\"DMZ Rack\",\"ip\":\"172.16.0.0/24\",\"role\":\"DMZ Infrastructure\",\"tags\":[\"dmz\",\"security\",\"public-facing\",{\"type\":\"icon\",\"library\":\"selfhst\",\"name\":\"booklogr\"},{\"type\":\"icon\",\"library\":\"simple\",\"name\":\"gmail\"}],\"notes\":[\"Isolated DMZ zone\",\"Public-facing services\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"security\",\"assignedRack\":\"\",\"rackCapacity\":\"24\",\"isRack\":true,\"locked\":false,\"groupId\":null},\"mgmt-rack\":{\"shape\":\"server\",\"name\":\"Management Rack\",\"ip\":\"192.168.100.0/24\",\"role\":\"Management Infrastructure\",\"tags\":[\"management\",\"oob\",\"noc\"],\"notes\":[\"Out-of-band management\",\"NOC equipment\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"logical\",\"assignedRack\":\"\",\"rackCapacity\":\"24\",\"isRack\":true,\"locked\":false,\"groupId\":null},\"esxi-host-01\":{\"shape\":\"server\",\"name\":\"ESXi Host 01\",\"ip\":\"10.10.0.11\",\"role\":\"Hypervisor\",\"tags\":[\"vmware\",\"compute\",\"cluster-a\"],\"notes\":[\"Dell PowerEdge R750\",\"512GB RAM\",\"vSphere 8.0\"],\"mac\":\"00:50:56:AA:01:01\",\"rackUnit\":38,\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a1\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"esxi-host-02\":{\"shape\":\"server\",\"name\":\"ESXi Host 02\",\"ip\":\"10.10.0.12\",\"role\":\"Hypervisor\",\"tags\":[\"vmware\",\"compute\",\"cluster-a\"],\"notes\":[\"Dell PowerEdge R750\",\"512GB RAM\",\"vSphere 8.0\"],\"mac\":\"00:50:56:AA:01:02\",\"rackUnit\":35,\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a1\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"esxi-host-03\":{\"shape\":\"server\",\"name\":\"ESXi Host 03\",\"ip\":\"10.10.0.13\",\"role\":\"Hypervisor\",\"tags\":[\"vmware\",\"compute\",\"cluster-a\"],\"notes\":[\"Dell PowerEdge R750\",\"512GB RAM\",\"vSphere 8.0\"],\"mac\":\"00:50:56:AA:01:03\",\"rackUnit\":32,\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a1\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"esxi-host-04\":{\"shape\":\"server\",\"name\":\"ESXi Host 04\",\"ip\":\"10.10.0.14\",\"role\":\"Hypervisor\",\"tags\":[\"vmware\",\"compute\",\"cluster-a\"],\"notes\":[\"Dell PowerEdge R750\",\"512GB RAM\",\"vSphere 8.0\"],\"mac\":\"00:50:56:AA:01:04\",\"rackUnit\":29,\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a1\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"tor-switch-a1\":{\"shape\":\"switch\",\"name\":\"ToR Switch A1\",\"ip\":\"10.10.0.1\",\"role\":\"Top of Rack\",\"tags\":[\"tor\",\"access\",\"rack-a1\"],\"notes\":[\"Cisco Nexus 93180YC-FX\",\"48x25G ports\"],\"mac\":\"00:1A:2B:3C:5D:01\",\"rackUnit\":42,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a1\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"esxi-host-05\":{\"shape\":\"server\",\"name\":\"ESXi Host 05\",\"ip\":\"10.10.1.11\",\"role\":\"Hypervisor\",\"tags\":[\"vmware\",\"compute\",\"cluster-b\"],\"notes\":[\"Dell PowerEdge R750\",\"768GB RAM\",\"vSphere 8.0\"],\"mac\":\"00:50:56:AA:02:01\",\"rackUnit\":38,\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"esxi-host-06\":{\"shape\":\"server\",\"name\":\"ESXi Host 06\",\"ip\":\"10.10.1.12\",\"role\":\"Hypervisor\",\"tags\":[\"vmware\",\"compute\",\"cluster-b\"],\"notes\":[\"Dell PowerEdge R750\",\"768GB RAM\",\"vSphere 8.0\"],\"mac\":\"00:50:56:AA:02:02\",\"rackUnit\":35,\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"esxi-host-07\":{\"shape\":\"server\",\"name\":\"ESXi Host 07\",\"ip\":\"10.10.1.13\",\"role\":\"Hypervisor\",\"tags\":[\"vmware\",\"compute\",\"cluster-b\"],\"notes\":[\"Dell PowerEdge R750\",\"768GB RAM\",\"vSphere 8.0\"],\"mac\":\"00:50:56:AA:02:03\",\"rackUnit\":32,\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"esxi-host-08\":{\"shape\":\"server\",\"name\":\"ESXi Host 08\",\"ip\":\"10.10.1.14\",\"role\":\"Hypervisor\",\"tags\":[\"vmware\",\"compute\",\"cluster-b\"],\"notes\":[\"Dell PowerEdge R750\",\"768GB RAM\",\"vSphere 8.0\"],\"mac\":\"00:50:56:AA:02:04\",\"rackUnit\":29,\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"tor-switch-a2\":{\"shape\":\"switch\",\"name\":\"ToR Switch A2\",\"ip\":\"10.10.1.1\",\"role\":\"Top of Rack\",\"tags\":[\"tor\",\"access\",\"rack-a2\"],\"notes\":[\"Cisco Nexus 93180YC-FX\",\"48x25G ports\"],\"mac\":\"00:1A:2B:3C:5D:02\",\"rackUnit\":42,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"san-primary\":{\"shape\":\"database\",\"name\":\"SAN Primary\",\"ip\":\"10.10.2.10\",\"role\":\"Primary Storage\",\"tags\":[\"storage\",\"san\",\"netapp\"],\"notes\":[\"NetApp AFF A400\",\"500TB Raw\",\"FC 32Gb\"],\"mac\":\"00:A0:98:AA:01:01\",\"rackUnit\":36,\"uHeight\":\"6\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-b1\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"san-secondary\":{\"shape\":\"database\",\"name\":\"SAN Secondary\",\"ip\":\"10.10.2.11\",\"role\":\"Secondary Storage\",\"tags\":[\"storage\",\"san\",\"netapp\"],\"notes\":[\"NetApp AFF A400\",\"500TB Raw\",\"FC 32Gb\"],\"mac\":\"00:A0:98:AA:01:02\",\"rackUnit\":28,\"uHeight\":\"6\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-b1\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"fc-switch-1\":{\"shape\":\"switch\",\"name\":\"FC Switch 1\",\"ip\":\"10.10.2.1\",\"role\":\"Fibre Channel\",\"tags\":[\"storage\",\"fc\",\"fabric-a\"],\"notes\":[\"Brocade G620\",\"Fabric A\"],\"mac\":\"00:1A:2B:FC:01:01\",\"rackUnit\":42,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-b1\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"fc-switch-2\":{\"shape\":\"switch\",\"name\":\"FC Switch 2\",\"ip\":\"10.10.2.2\",\"role\":\"Fibre Channel\",\"tags\":[\"storage\",\"fc\",\"fabric-b\"],\"notes\":[\"Brocade G620\",\"Fabric B\"],\"mac\":\"00:1A:2B:FC:01:02\",\"rackUnit\":41,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-b1\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"backup-server-1\":{\"shape\":\"server\",\"name\":\"Backup Server 1\",\"ip\":\"10.10.3.10\",\"role\":\"Backup Infrastructure\",\"tags\":[\"backup\",\"veeam\",\"protection\"],\"notes\":[\"Veeam Backup Server\",\"Dell R740xd\",\"200TB\"],\"mac\":\"00:50:56:BB:01:01\",\"rackUnit\":36,\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-b2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"backup-server-2\":{\"shape\":\"server\",\"name\":\"Backup Server 2\",\"ip\":\"10.10.3.11\",\"role\":\"Backup Infrastructure\",\"tags\":[\"backup\",\"veeam\",\"protection\"],\"notes\":[\"Veeam Backup Server\",\"Dell R740xd\",\"200TB\"],\"mac\":\"00:50:56:BB:01:02\",\"rackUnit\":33,\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-b2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"tape-library\":{\"shape\":\"database\",\"name\":\"Tape Library\",\"ip\":\"10.10.3.20\",\"role\":\"Archival Storage\",\"tags\":[\"backup\",\"tape\",\"lto9\"],\"notes\":[\"IBM TS4500\",\"LTO-9\",\"Long-term archive\"],\"mac\":\"00:50:56:BB:02:01\",\"rackUnit\":20,\"uHeight\":\"10\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-b2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"tor-switch-b1\":{\"shape\":\"switch\",\"name\":\"ToR Switch B1\",\"ip\":\"10.10.2.3\",\"role\":\"Top of Rack\",\"tags\":[\"tor\",\"access\",\"rack-b1\"],\"notes\":[\"Cisco Nexus 93180YC-FX\"],\"mac\":\"00:1A:2B:3C:5D:03\",\"rackUnit\":40,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-b1\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"tor-switch-b2\":{\"shape\":\"switch\",\"name\":\"ToR Switch B2\",\"ip\":\"10.10.3.1\",\"role\":\"Top of Rack\",\"tags\":[\"tor\",\"access\",\"rack-b2\"],\"notes\":[\"Cisco Nexus 93180YC-FX\"],\"mac\":\"00:1A:2B:3C:5D:04\",\"rackUnit\":42,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-b2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"web-server-1\":{\"shape\":\"server\",\"name\":\"Web Server 1\",\"ip\":\"172.16.0.11\",\"role\":\"Web Frontend\",\"tags\":[\"dmz\",\"web\",\"nginx\"],\"notes\":[\"NGINX reverse proxy\",\"Public facing\"],\"mac\":\"00:50:56:CC:01:01\",\"rackUnit\":20,\"uHeight\":\"1\",\"layer\":\"security\",\"assignedRack\":\"dmz-rack\",\"rackCapacity\":\"24\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"web-server-2\":{\"shape\":\"server\",\"name\":\"Web Server 2\",\"ip\":\"172.16.0.12\",\"role\":\"Web Frontend\",\"tags\":[\"dmz\",\"web\",\"nginx\"],\"notes\":[\"NGINX reverse proxy\",\"Public facing\"],\"mac\":\"00:50:56:CC:01:02\",\"rackUnit\":18,\"uHeight\":\"1\",\"layer\":\"security\",\"assignedRack\":\"dmz-rack\",\"rackCapacity\":\"24\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"waf-1\":{\"shape\":\"firewall\",\"name\":\"WAF Appliance\",\"ip\":\"172.16.0.5\",\"role\":\"Web Application Firewall\",\"tags\":[\"dmz\",\"security\",\"waf\"],\"notes\":[\"F5 BIG-IP ASM\",\"OWASP protection\"],\"mac\":\"00:50:56:CC:02:01\",\"rackUnit\":22,\"uHeight\":\"2\",\"layer\":\"security\",\"assignedRack\":\"dmz-rack\",\"rackCapacity\":\"24\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"load-balancer-dmz\":{\"shape\":\"switch\",\"name\":\"DMZ Load Balancer\",\"ip\":\"172.16.0.3\",\"role\":\"Load Balancing\",\"tags\":[\"dmz\",\"lb\",\"f5\"],\"notes\":[\"F5 BIG-IP LTM\",\"VIP: 172.16.0.100\"],\"mac\":\"00:50:56:CC:03:01\",\"rackUnit\":16,\"uHeight\":\"2\",\"layer\":\"security\",\"assignedRack\":\"dmz-rack\",\"rackCapacity\":\"24\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"mail-gateway\":{\"shape\":\"server\",\"name\":\"Mail Gateway\",\"ip\":\"172.16.0.25\",\"role\":\"Email Security\",\"tags\":[\"dmz\",\"email\",\"security\"],\"notes\":[\"Proofpoint Email Gateway\",\"Spam/malware filtering\"],\"mac\":\"00:50:56:CC:04:01\",\"rackUnit\":14,\"uHeight\":\"1\",\"layer\":\"security\",\"assignedRack\":\"dmz-rack\",\"rackCapacity\":\"24\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"dns-external-1\":{\"shape\":\"circle\",\"name\":\"External DNS 1\",\"ip\":\"172.16.0.53\",\"role\":\"External DNS\",\"tags\":[\"dmz\",\"dns\",\"public\"],\"notes\":[\"BIND DNS\",\"Authoritative for corp.com\"],\"mac\":\"00:50:56:CC:05:01\",\"rackUnit\":12,\"uHeight\":\"1\",\"layer\":\"security\",\"assignedRack\":\"dmz-rack\",\"rackCapacity\":\"24\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"dns-external-2\":{\"shape\":\"circle\",\"name\":\"External DNS 2\",\"ip\":\"172.16.0.54\",\"role\":\"External DNS\",\"tags\":[\"dmz\",\"dns\",\"public\"],\"notes\":[\"BIND DNS\",\"Secondary for corp.com\"],\"mac\":\"00:50:56:CC:05:02\",\"rackUnit\":10,\"uHeight\":\"1\",\"layer\":\"security\",\"assignedRack\":\"dmz-rack\",\"rackCapacity\":\"24\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"vcenter\":{\"shape\":\"server\",\"name\":\"vCenter Server\",\"ip\":\"192.168.100.10\",\"role\":\"Virtualization Management\",\"tags\":[\"management\",\"vmware\",\"vcsa\"],\"notes\":[\"vCenter Server Appliance 8.0\",\"Single SSO domain\"],\"mac\":\"00:50:56:DD:01:01\",\"rackUnit\":20,\"uHeight\":\"2\",\"layer\":\"logical\",\"assignedRack\":\"mgmt-rack\",\"rackCapacity\":\"24\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"nsx-manager\":{\"shape\":\"server\",\"name\":\"NSX Manager\",\"ip\":\"192.168.100.15\",\"role\":\"Network Virtualization\",\"tags\":[\"management\",\"vmware\",\"nsx\"],\"notes\":[\"NSX-T 4.1 Manager Cluster\"],\"mac\":\"00:50:56:DD:02:01\",\"rackUnit\":17,\"uHeight\":\"2\",\"layer\":\"logical\",\"assignedRack\":\"mgmt-rack\",\"rackCapacity\":\"24\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"siem-server\":{\"shape\":\"server\",\"name\":\"SIEM Server\",\"ip\":\"192.168.100.50\",\"role\":\"Security Monitoring\",\"tags\":[\"management\",\"security\",\"splunk\"],\"notes\":[\"Splunk Enterprise\",\"Security monitoring\"],\"mac\":\"00:50:56:DD:03:01\",\"rackUnit\":14,\"uHeight\":\"2\",\"layer\":\"logical\",\"assignedRack\":\"mgmt-rack\",\"rackCapacity\":\"24\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"nms-server\":{\"shape\":\"server\",\"name\":\"Network Monitoring\",\"ip\":\"192.168.100.60\",\"role\":\"Network Management\",\"tags\":[\"management\",\"monitoring\",\"prtg\"],\"notes\":[\"PRTG Network Monitor\",\"5000 sensors\"],\"mac\":\"00:50:56:DD:04:01\",\"rackUnit\":11,\"uHeight\":\"1\",\"layer\":\"logical\",\"assignedRack\":\"mgmt-rack\",\"rackCapacity\":\"24\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"jump-server\":{\"shape\":\"server\",\"name\":\"Jump Server\",\"ip\":\"192.168.100.100\",\"role\":\"Bastion Host\",\"tags\":[\"management\",\"security\",\"bastion\"],\"notes\":[\"Windows Server 2022\",\"MFA enabled\"],\"mac\":\"00:50:56:DD:05:01\",\"rackUnit\":9,\"uHeight\":\"1\",\"layer\":\"logical\",\"assignedRack\":\"mgmt-rack\",\"rackCapacity\":\"24\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"ipam-server\":{\"shape\":\"server\",\"name\":\"IPAM/DDI\",\"ip\":\"192.168.100.70\",\"role\":\"IP Management\",\"tags\":[\"management\",\"dns\",\"dhcp\"],\"notes\":[\"Infoblox DDI\",\"DNS/DHCP/IPAM\"],\"mac\":\"00:50:56:DD:06:01\",\"rackUnit\":7,\"uHeight\":\"2\",\"layer\":\"logical\",\"assignedRack\":\"mgmt-rack\",\"rackCapacity\":\"24\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"wlc-primary\":{\"shape\":\"wifi\",\"name\":\"WLC Primary\",\"ip\":\"10.20.0.1\",\"role\":\"Wireless Controller\",\"tags\":[\"wireless\",\"cisco\",\"9800\"],\"notes\":[\"Cisco C9800-40\",\"Primary controller\"],\"mac\":\"00:1A:2B:WL:01:01\",\"rackUnit\":\"\",\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"wlc-secondary\":{\"shape\":\"wifi\",\"name\":\"WLC Secondary\",\"ip\":\"10.20.0.2\",\"role\":\"Wireless Controller\",\"tags\":[\"wireless\",\"cisco\",\"9800\"],\"notes\":[\"Cisco C9800-40\",\"HA Secondary\"],\"mac\":\"00:1A:2B:WL:01:02\",\"rackUnit\":\"\",\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"mobile-zone-hq\":{\"shape\":\"phone\",\"name\":\"HQ Mobile Zone\",\"ip\":\"10.20.10.0/24\",\"role\":\"Mobile Device Zone\",\"tags\":[\"wireless\",\"byod\",\"mobile\"],\"notes\":[\"Corporate BYOD\",\"MDM enrolled devices\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"mobile-zone-guest\":{\"shape\":\"phone\",\"name\":\"Guest WiFi Zone\",\"ip\":\"10.30.0.0/24\",\"role\":\"Guest Network\",\"tags\":[\"wireless\",\"guest\",\"isolated\"],\"notes\":[\"Captive portal\",\"Internet only\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"mobile-zone-iot\":{\"shape\":\"phone\",\"name\":\"IoT Device Zone\",\"ip\":\"10.40.0.0/24\",\"role\":\"IoT Network\",\"tags\":[\"wireless\",\"iot\",\"building\"],\"notes\":[\"Building automation\",\"Smart devices\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"branch-router-ny\":{\"shape\":\"router\",\"name\":\"NYC Branch Router\",\"ip\":\"10.100.0.1\",\"role\":\"Branch Gateway\",\"tags\":[\"branch\",\"nyc\",\"sd-wan\"],\"notes\":[\"Cisco Viptela vEdge\",\"SD-WAN enabled\"],\"mac\":\"00:1A:2B:BR:01:01\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"branch-router-la\":{\"shape\":\"router\",\"name\":\"LA Branch Router\",\"ip\":\"10.101.0.1\",\"role\":\"Branch Gateway\",\"tags\":[\"branch\",\"la\",\"sd-wan\"],\"notes\":[\"Cisco Viptela vEdge\",\"SD-WAN enabled\"],\"mac\":\"00:1A:2B:BR:02:01\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"branch-router-chi\":{\"shape\":\"router\",\"name\":\"Chicago Branch Router\",\"ip\":\"10.102.0.1\",\"role\":\"Branch Gateway\",\"tags\":[\"branch\",\"chicago\",\"sd-wan\"],\"notes\":[\"Cisco Viptela vEdge\",\"SD-WAN enabled\"],\"mac\":\"00:1A:2B:BR:03:01\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"branch-router-lon\":{\"shape\":\"router\",\"name\":\"London Branch Router\",\"ip\":\"10.200.0.1\",\"role\":\"Branch Gateway\",\"tags\":[\"branch\",\"london\",\"sd-wan\"],\"notes\":[\"Cisco Viptela vEdge\",\"EMEA region\"],\"mac\":\"00:1A:2B:BR:04:01\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"branch-router-tokyo\":{\"shape\":\"router\",\"name\":\"Tokyo Branch Router\",\"ip\":\"10.201.0.1\",\"role\":\"Branch Gateway\",\"tags\":[\"branch\",\"tokyo\",\"sd-wan\"],\"notes\":[\"Cisco Viptela vEdge\",\"APAC region\"],\"mac\":\"00:1A:2B:BR:05:01\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"cloud-aws\":{\"shape\":\"cloud\",\"name\":\"AWS Cloud\",\"ip\":\"vpc-0a1b2c3d\",\"role\":\"Public Cloud\",\"tags\":[\"cloud\",\"aws\",\"hybrid\"],\"notes\":[\"AWS US-East-1\",\"VPC peering to HQ\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"logical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"cloud-azure\":{\"shape\":\"cloud\",\"name\":\"Azure Cloud\",\"ip\":\"vnet-corp-prod\",\"role\":\"Public Cloud\",\"tags\":[\"cloud\",\"azure\",\"hybrid\"],\"notes\":[\"Azure East US 2\",\"ExpressRoute\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"logical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"cloud-gcp\":{\"shape\":\"cloud\",\"name\":\"GCP Cloud\",\"ip\":\"vpc-gcp-corp\",\"role\":\"Public Cloud\",\"tags\":[\"cloud\",\"gcp\",\"dev\"],\"notes\":[\"GCP us-central1\",\"Dev/Test workloads\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"logical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"isp-primary\":{\"shape\":\"globe\",\"name\":\"ISP Primary\",\"ip\":\"203.0.113.1\",\"role\":\"Internet Uplink\",\"tags\":[\"wan\",\"internet\",\"primary\"],\"notes\":[\"AT&T MPLS\",\"1 Gbps dedicated\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"isp-secondary\":{\"shape\":\"globe\",\"name\":\"ISP Secondary\",\"ip\":\"198.51.100.1\",\"role\":\"Internet Uplink\",\"tags\":[\"wan\",\"internet\",\"backup\"],\"notes\":[\"Verizon Business\",\"500 Mbps backup\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"dc-internal-1\":{\"shape\":\"circle\",\"name\":\"DC1 Int DNS\",\"ip\":\"10.10.0.53\",\"role\":\"Internal DNS/AD\",\"tags\":[\"dns\",\"ad\",\"dc1\"],\"notes\":[\"Windows Server 2022\",\"Primary DC\"],\"mac\":\"00:50:56:AD:01:01\",\"rackUnit\":26,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a1\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"dc-internal-2\":{\"shape\":\"circle\",\"name\":\"DC2 Int DNS\",\"ip\":\"10.10.1.53\",\"role\":\"Internal DNS/AD\",\"tags\":[\"dns\",\"ad\",\"dc2\"],\"notes\":[\"Windows Server 2022\",\"Secondary DC\"],\"mac\":\"00:50:56:AD:01:02\",\"rackUnit\":26,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"app-server-1\":{\"shape\":\"server\",\"name\":\"App Server 01\",\"ip\":\"10.10.0.101\",\"role\":\"Application\",\"tags\":[\"app\",\"iis\",\"web\"],\"notes\":[\"Windows Server 2022\",\"IIS Application\"],\"mac\":\"00:50:56:AP:01:01\",\"rackUnit\":24,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a1\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"app-server-2\":{\"shape\":\"server\",\"name\":\"App Server 02\",\"ip\":\"10.10.0.102\",\"role\":\"Application\",\"tags\":[\"app\",\"iis\",\"web\"],\"notes\":[\"Windows Server 2022\",\"IIS Application\"],\"mac\":\"00:50:56:AP:01:02\",\"rackUnit\":22,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a1\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"db-server-1\":{\"shape\":\"database\",\"name\":\"SQL Server 01\",\"ip\":\"10.10.0.201\",\"role\":\"Database\",\"tags\":[\"db\",\"sql\",\"primary\"],\"notes\":[\"SQL Server 2022 Enterprise\",\"AlwaysOn Primary\"],\"mac\":\"00:50:56:DB:01:01\",\"rackUnit\":20,\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a1\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"db-server-2\":{\"shape\":\"database\",\"name\":\"SQL Server 02\",\"ip\":\"10.10.1.201\",\"role\":\"Database\",\"tags\":[\"db\",\"sql\",\"secondary\"],\"notes\":[\"SQL Server 2022 Enterprise\",\"AlwaysOn Secondary\"],\"mac\":\"00:50:56:DB:01:02\",\"rackUnit\":24,\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"k8s-master-1\":{\"shape\":\"hexagon\",\"name\":\"K8s Master 1\",\"ip\":\"10.10.1.50\",\"role\":\"Container Orchestration\",\"tags\":[\"kubernetes\",\"master\",\"container\"],\"notes\":[\"K8s Control Plane\",\"etcd member\"],\"mac\":\"00:50:56:K8:01:01\",\"rackUnit\":21,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"k8s-master-2\":{\"shape\":\"hexagon\",\"name\":\"K8s Master 2\",\"ip\":\"10.10.1.51\",\"role\":\"Container Orchestration\",\"tags\":[\"kubernetes\",\"master\",\"container\"],\"notes\":[\"K8s Control Plane\",\"etcd member\"],\"mac\":\"00:50:56:K8:01:02\",\"rackUnit\":19,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"k8s-master-3\":{\"shape\":\"hexagon\",\"name\":\"K8s Master 3\",\"ip\":\"10.10.1.52\",\"role\":\"Container Orchestration\",\"tags\":[\"kubernetes\",\"master\",\"container\"],\"notes\":[\"K8s Control Plane\",\"etcd member\"],\"mac\":\"00:50:56:K8:01:03\",\"rackUnit\":17,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"k8s-worker-1\":{\"shape\":\"server\",\"name\":\"K8s Worker 1\",\"ip\":\"10.10.1.60\",\"role\":\"Container Workload\",\"tags\":[\"kubernetes\",\"worker\",\"container\"],\"notes\":[\"K8s Worker Node\",\"64GB RAM\"],\"mac\":\"00:50:56:K8:02:01\",\"rackUnit\":15,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"k8s-worker-2\":{\"shape\":\"server\",\"name\":\"K8s Worker 2\",\"ip\":\"10.10.1.61\",\"role\":\"Container Workload\",\"tags\":[\"kubernetes\",\"worker\",\"container\"],\"notes\":[\"K8s Worker Node\",\"64GB RAM\"],\"mac\":\"00:50:56:K8:02:02\",\"rackUnit\":13,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"k8s-worker-3\":{\"shape\":\"server\",\"name\":\"K8s Worker 3\",\"ip\":\"10.10.1.62\",\"role\":\"Container Workload\",\"tags\":[\"kubernetes\",\"worker\",\"container\"],\"notes\":[\"K8s Worker Node\",\"64GB RAM\"],\"mac\":\"00:50:56:K8:02:03\",\"rackUnit\":11,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"k8s-worker-4\":{\"shape\":\"server\",\"name\":\"K8s Worker 4\",\"ip\":\"10.10.1.63\",\"role\":\"Container Workload\",\"tags\":[\"kubernetes\",\"worker\",\"container\"],\"notes\":[\"K8s Worker Node\",\"64GB RAM\"],\"mac\":\"00:50:56:K8:02:04\",\"rackUnit\":9,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"proxy-server-1\":{\"shape\":\"server\",\"name\":\"Proxy Server 1\",\"ip\":\"10.5.0.10\",\"role\":\"Web Proxy\",\"tags\":[\"proxy\",\"squid\",\"filtering\"],\"notes\":[\"Squid Proxy\",\"Content filtering\"],\"mac\":\"00:50:56:PX:01:01\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"security\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"proxy-server-2\":{\"shape\":\"server\",\"name\":\"Proxy Server 2\",\"ip\":\"10.5.0.11\",\"role\":\"Web Proxy\",\"tags\":[\"proxy\",\"squid\",\"filtering\"],\"notes\":[\"Squid Proxy\",\"HA pair\"],\"mac\":\"00:50:56:PX:01:02\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"security\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"vpn-concentrator\":{\"shape\":\"firewall\",\"name\":\"VPN Concentrator\",\"ip\":\"10.0.5.1\",\"role\":\"Remote Access VPN\",\"tags\":[\"vpn\",\"remote\",\"security\"],\"notes\":[\"Cisco ASA 5555-X\",\"AnyConnect SSL VPN\"],\"mac\":\"00:1A:2B:VP:01:01\",\"rackUnit\":\"\",\"uHeight\":\"2\",\"layer\":\"security\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"nac-server\":{\"shape\":\"server\",\"name\":\"NAC Server\",\"ip\":\"10.5.5.10\",\"role\":\"Network Access Control\",\"tags\":[\"nac\",\"ise\",\"802.1x\"],\"notes\":[\"Cisco ISE 3.1\",\"RADIUS/TACACS+\"],\"mac\":\"00:50:56:NA:01:01\",\"rackUnit\":\"\",\"uHeight\":\"2\",\"layer\":\"security\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"print-server\":{\"shape\":\"server\",\"name\":\"Print Server\",\"ip\":\"10.10.0.150\",\"role\":\"Print Services\",\"tags\":[\"print\",\"windows\",\"services\"],\"notes\":[\"Windows Print Server\",\"50+ printers\"],\"mac\":\"00:50:56:PR:01:01\",\"rackUnit\":18,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a1\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"file-server\":{\"shape\":\"database\",\"name\":\"File Server\",\"ip\":\"10.10.0.160\",\"role\":\"File Services\",\"tags\":[\"file\",\"smb\",\"dfs\"],\"notes\":[\"Windows File Server\",\"DFS namespace\"],\"mac\":\"00:50:56:FS:01:01\",\"rackUnit\":16,\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a1\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"ca-server\":{\"shape\":\"server\",\"name\":\"Certificate Authority\",\"ip\":\"192.168.100.80\",\"role\":\"PKI Infrastructure\",\"tags\":[\"pki\",\"ca\",\"security\"],\"notes\":[\"Windows CA\",\"Enterprise Root CA\"],\"mac\":\"00:50:56:CA:01:01\",\"rackUnit\":5,\"uHeight\":\"1\",\"layer\":\"logical\",\"assignedRack\":\"mgmt-rack\",\"rackCapacity\":\"24\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"sccm-server\":{\"shape\":\"server\",\"name\":\"SCCM Server\",\"ip\":\"192.168.100.90\",\"role\":\"Endpoint Management\",\"tags\":[\"sccm\",\"patching\",\"software\"],\"notes\":[\"MECM Primary Site\",\"Software deployment\"],\"mac\":\"00:50:56:SC:01:01\",\"rackUnit\":3,\"uHeight\":\"2\",\"layer\":\"logical\",\"assignedRack\":\"mgmt-rack\",\"rackCapacity\":\"24\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"voip-cluster\":{\"shape\":\"phone\",\"name\":\"VoIP Cluster\",\"ip\":\"10.50.0.0/24\",\"role\":\"Voice Services\",\"tags\":[\"voip\",\"cisco\",\"ucm\"],\"notes\":[\"Cisco UCM Cluster\",\"3000 endpoints\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"application\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"video-conf\":{\"shape\":\"laptop\",\"name\":\"Video Conference\",\"ip\":\"10.51.0.0/24\",\"role\":\"Video Services\",\"tags\":[\"video\",\"webex\",\"teams\"],\"notes\":[\"Webex/Teams integration\",\"Meeting rooms\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"application\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"security-cameras\":{\"shape\":\"camera\",\"name\":\"Security Cameras\",\"ip\":\"10.60.0.0/24\",\"role\":\"Physical Security\",\"tags\":[\"cctv\",\"surveillance\",\"security\"],\"notes\":[\"150+ IP cameras\",\"30-day retention\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"nvr-cluster\":{\"shape\":\"server\",\"name\":\"NVR Cluster\",\"ip\":\"10.60.0.10\",\"role\":\"Video Recording\",\"tags\":[\"nvr\",\"surveillance\",\"storage\"],\"notes\":[\"Milestone XProtect\",\"500TB storage\"],\"mac\":\"00:50:56:NV:01:01\",\"rackUnit\":15,\"uHeight\":\"4\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-b2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"dev-server-1\":{\"shape\":\"server\",\"name\":\"Dev Server 1\",\"ip\":\"10.80.0.10\",\"role\":\"Development\",\"tags\":[\"dev\",\"gitlab\",\"ci-cd\",{\"type\":\"icon\",\"library\":\"selfhst\",\"name\":\"dokku\"}],\"notes\":[\"GitLab Server\",\"CI/CD pipelines\"],\"mac\":\"00:50:56:DV:01:01\",\"rackUnit\":\"\",\"uHeight\":\"2\",\"layer\":\"application\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"dev-server-2\":{\"shape\":\"server\",\"name\":\"Dev Server 2\",\"ip\":\"10.80.0.11\",\"role\":\"Development\",\"tags\":[\"dev\",\"jenkins\",\"ci-cd\"],\"notes\":[\"Jenkins Server\",\"Build automation\"],\"mac\":\"00:50:56:DV:01:02\",\"rackUnit\":\"\",\"uHeight\":\"2\",\"layer\":\"application\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"test-environment\":{\"shape\":\"hexagon\",\"name\":\"Test Environment\",\"ip\":\"10.81.0.0/24\",\"role\":\"QA/Testing\",\"tags\":[\"test\",\"qa\",\"staging\"],\"notes\":[\"Staging environment\",\"Pre-prod validation\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"application\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"erp-system\":{\"shape\":\"database\",\"name\":\"ERP System\",\"ip\":\"10.90.0.10\",\"role\":\"Business Application\",\"tags\":[\"erp\",\"sap\",\"business\"],\"notes\":[\"SAP S/4HANA\",\"Financial/HR systems\"],\"mac\":\"00:50:56:ER:01:01\",\"rackUnit\":\"\",\"uHeight\":\"4\",\"layer\":\"application\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"crm-system\":{\"shape\":\"database\",\"name\":\"CRM System\",\"ip\":\"10.91.0.10\",\"role\":\"Business Application\",\"tags\":[\"crm\",\"salesforce\",\"business\"],\"notes\":[\"Salesforce integration\",\"Sales/Marketing\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"application\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"endpoint-1000\":{\"shape\":\"laptop\",\"name\":\"Corporate Endpoints\",\"ip\":\"10.70.0.0/22\",\"role\":\"User Workstations\",\"tags\":[\"endpoints\",\"workstations\",\"users\"],\"notes\":[\"~1000 corporate laptops\",\"Windows 11\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"dist-switch-floor1\":{\"shape\":\"switch\",\"name\":\"Floor 1 Switch\",\"ip\":\"10.1.1.1\",\"role\":\"Distribution\",\"tags\":[\"distribution\",\"floor-1\",\"access\"],\"notes\":[\"Cisco C9300-48P\",\"PoE+ enabled\"],\"mac\":\"00:1A:2B:FL:01:01\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"dist-switch-floor2\":{\"shape\":\"switch\",\"name\":\"Floor 2 Switch\",\"ip\":\"10.1.2.1\",\"role\":\"Distribution\",\"tags\":[\"distribution\",\"floor-2\",\"access\"],\"notes\":[\"Cisco C9300-48P\",\"PoE+ enabled\"],\"mac\":\"00:1A:2B:FL:02:01\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"dist-switch-floor3\":{\"shape\":\"switch\",\"name\":\"Floor 3 Switch\",\"ip\":\"10.1.3.1\",\"role\":\"Distribution\",\"tags\":[\"distribution\",\"floor-3\",\"access\"],\"notes\":[\"Cisco C9300-48P\",\"PoE+ enabled\"],\"mac\":\"00:1A:2B:FL:03:01\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"dist-switch-floor4\":{\"shape\":\"switch\",\"name\":\"Floor 4 Switch\",\"ip\":\"10.1.4.1\",\"role\":\"Distribution\",\"tags\":[\"distribution\",\"floor-4\",\"access\"],\"notes\":[\"Cisco C9300-48P\",\"PoE+ enabled\"],\"mac\":\"00:1A:2B:FL:04:01\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"ap-floor1-zone1\":{\"shape\":\"wifi\",\"name\":\"AP Floor 1 Zone 1\",\"ip\":\"10.20.1.10\",\"role\":\"Wireless Access\",\"tags\":[\"wifi\",\"ap\",\"floor-1\"],\"notes\":[\"Cisco 9120AX\",\"Wi-Fi 6\"],\"mac\":\"00:1A:2B:AP:01:01\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"ap-floor2-zone1\":{\"shape\":\"wifi\",\"name\":\"AP Floor 2 Zone 1\",\"ip\":\"10.20.2.10\",\"role\":\"Wireless Access\",\"tags\":[\"wifi\",\"ap\",\"floor-2\"],\"notes\":[\"Cisco 9120AX\",\"Wi-Fi 6\"],\"mac\":\"00:1A:2B:AP:02:01\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"ap-floor3-zone1\":{\"shape\":\"wifi\",\"name\":\"AP Floor 3 Zone 1\",\"ip\":\"10.20.3.10\",\"role\":\"Wireless Access\",\"tags\":[\"wifi\",\"ap\",\"floor-3\"],\"notes\":[\"Cisco 9120AX\",\"Wi-Fi 6\"],\"mac\":\"00:1A:2B:AP:03:01\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"ap-floor4-zone1\":{\"shape\":\"wifi\",\"name\":\"AP Floor 4 Zone 1\",\"ip\":\"10.20.4.10\",\"role\":\"Wireless Access\",\"tags\":[\"wifi\",\"ap\",\"floor-4\"],\"notes\":[\"Cisco 9120AX\",\"Wi-Fi 6\"],\"mac\":\"00:1A:2B:AP:04:01\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"ups-dc-1\":{\"shape\":\"rectangle\",\"name\":\"UPS DC-1\",\"ip\":\"192.168.200.10\",\"role\":\"Power Management\",\"tags\":[\"power\",\"ups\",\"datacenter\"],\"notes\":[\"APC Symmetra\",\"80kVA\",\"30 min runtime\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"ups-dc-2\":{\"shape\":\"rectangle\",\"name\":\"UPS DC-2\",\"ip\":\"192.168.200.11\",\"role\":\"Power Management\",\"tags\":[\"power\",\"ups\",\"datacenter\"],\"notes\":[\"APC Symmetra\",\"80kVA\",\"Redundant\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"pdu-rack-a1\":{\"shape\":\"rectangle\",\"name\":\"PDU Rack A1\",\"ip\":\"192.168.200.21\",\"role\":\"Power Distribution\",\"tags\":[\"power\",\"pdu\",\"rack-a1\"],\"notes\":[\"APC Switched PDU\",\"Per-outlet metering\"],\"mac\":\"\",\"rackUnit\":1,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a1\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"pdu-rack-a2\":{\"shape\":\"rectangle\",\"name\":\"PDU Rack A2\",\"ip\":\"192.168.200.22\",\"role\":\"Power Distribution\",\"tags\":[\"power\",\"pdu\",\"rack-a2\"],\"notes\":[\"APC Switched PDU\",\"Per-outlet metering\"],\"mac\":\"\",\"rackUnit\":1,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"cooling-1\":{\"shape\":\"rectangle\",\"name\":\"CRAC Unit 1\",\"ip\":\"192.168.200.30\",\"role\":\"Cooling\",\"tags\":[\"cooling\",\"hvac\",\"datacenter\"],\"notes\":[\"Liebert CRV\",\"Row-based cooling\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"cooling-2\":{\"shape\":\"rectangle\",\"name\":\"CRAC Unit 2\",\"ip\":\"192.168.200.31\",\"role\":\"Cooling\",\"tags\":[\"cooling\",\"hvac\",\"datacenter\"],\"notes\":[\"Liebert CRV\",\"N+1 redundancy\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"camera-a\":{\"shape\":\"camera\",\"name\":\"camera A\",\"ip\":\"0.0.0.0\",\"role\":\"\",\"tags\":[],\"notes\":[],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"ping\":{\"enabled\":false,\"protocol\":\"http\",\"customUrl\":\"\",\"timeout\":3000,\"status\":\"unknown\",\"lastCheck\":null},\"locked\":false,\"groupId\":null,\"fovEnabled\":true,\"fovRotation\":104,\"fovDistance\":500,\"fovSweep\":60,\"fovSpeed\":10,\"fovAnimate\":true},\"camera-a-copy\":{\"shape\":\"camera\",\"name\":\"camera B\",\"ip\":\"0.0.0.0\",\"role\":\"\",\"tags\":[],\"notes\":[],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"ping\":{\"enabled\":false,\"protocol\":\"http\",\"customUrl\":\"\",\"timeout\":3000,\"status\":\"unknown\",\"lastCheck\":null},\"locked\":false,\"groupId\":null,\"fovEnabled\":true,\"fovRotation\":162,\"fovDistance\":500,\"fovSweep\":60,\"fovSpeed\":10,\"fovAnimate\":false}},\"edgeData\":{\"list\":[{\"id\":\"isp1-router1\",\"from\":\"isp-primary\",\"to\":\"core-router-1\",\"width\":6,\"color\":\"#10b981\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"Primary WAN link\"],\"fromPort\":\"Gi0/0\",\"toPort\":\"Gi1/0/1\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"isp2-router2\",\"from\":\"isp-secondary\",\"to\":\"core-router-2\",\"width\":6,\"color\":\"#10b981\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"Backup WAN link\"],\"fromPort\":\"Gi0/0\",\"toPort\":\"Gi1/0/1\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"router1-router2\",\"from\":\"core-router-1\",\"to\":\"core-router-2\",\"width\":4,\"color\":\"#f59e0b\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"HSRP Peering\"],\"fromPort\":\"Gi1/0/24\",\"toPort\":\"Gi1/0/24\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"router1-fw1\",\"from\":\"core-router-1\",\"to\":\"fw-external-1\",\"width\":4,\"color\":\"#ef4444\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"router2-fw2\",\"from\":\"core-router-2\",\"to\":\"fw-external-2\",\"width\":4,\"color\":\"#ef4444\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"fw1-fw2\",\"from\":\"fw-external-1\",\"to\":\"fw-external-2\",\"width\":3,\"color\":\"#f59e0b\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"HA heartbeat\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"dashed\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"fw1-coresw1\",\"from\":\"fw-external-1\",\"to\":\"core-switch-1\",\"width\":4,\"color\":\"#475569\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"fw2-coresw2\",\"from\":\"fw-external-2\",\"to\":\"core-switch-2\",\"width\":4,\"color\":\"#475569\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw1-coresw2\",\"from\":\"core-switch-1\",\"to\":\"core-switch-2\",\"width\":5,\"color\":\"#3b82f6\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"VPC peer-link\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw1-fwint\",\"from\":\"core-switch-1\",\"to\":\"fw-internal\",\"width\":3,\"color\":\"#ef4444\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw2-fwint\",\"from\":\"core-switch-2\",\"to\":\"fw-internal\",\"width\":3,\"color\":\"#ef4444\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw1-racka1\",\"from\":\"core-switch-1\",\"to\":\"dc-rack-a1\",\"width\":4,\"color\":\"#475569\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw2-racka1\",\"from\":\"core-switch-2\",\"to\":\"dc-rack-a1\",\"width\":4,\"color\":\"#475569\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw1-racka2\",\"from\":\"core-switch-1\",\"to\":\"dc-rack-a2\",\"width\":4,\"color\":\"#475569\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw2-racka2\",\"from\":\"core-switch-2\",\"to\":\"dc-rack-a2\",\"width\":4,\"color\":\"#475569\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw1-rackb1\",\"from\":\"core-switch-1\",\"to\":\"dc-rack-b1\",\"width\":4,\"color\":\"#475569\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw2-rackb1\",\"from\":\"core-switch-2\",\"to\":\"dc-rack-b1\",\"width\":4,\"color\":\"#475569\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw1-rackb2\",\"from\":\"core-switch-1\",\"to\":\"dc-rack-b2\",\"width\":4,\"color\":\"#475569\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw2-rackb2\",\"from\":\"core-switch-2\",\"to\":\"dc-rack-b2\",\"width\":4,\"color\":\"#475569\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"fw1-dmz\",\"from\":\"fw-external-1\",\"to\":\"dmz-rack\",\"width\":3,\"color\":\"#f59e0b\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"DMZ segment\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"fw2-dmz\",\"from\":\"fw-external-2\",\"to\":\"dmz-rack\",\"width\":3,\"color\":\"#f59e0b\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"DMZ segment\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw1-mgmt\",\"from\":\"core-switch-1\",\"to\":\"mgmt-rack\",\"width\":3,\"color\":\"#8b5cf6\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"OOB management\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw1-wlc1\",\"from\":\"core-switch-1\",\"to\":\"wlc-primary\",\"width\":3,\"color\":\"#06b6d4\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw2-wlc2\",\"from\":\"core-switch-2\",\"to\":\"wlc-secondary\",\"width\":3,\"color\":\"#06b6d4\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\",\"animate\":true},{\"id\":\"wlc1-wlc2\",\"from\":\"wlc-primary\",\"to\":\"wlc-secondary\",\"width\":2,\"color\":\"#f59e0b\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"HA pair\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"dashed\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"wlc1-mobile-hq\",\"from\":\"wlc-primary\",\"to\":\"mobile-zone-hq\",\"width\":2,\"color\":\"#06b6d4\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"wlc1-mobile-guest\",\"from\":\"wlc-primary\",\"to\":\"mobile-zone-guest\",\"width\":2,\"color\":\"#06b6d4\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"wlc1-mobile-iot\",\"from\":\"wlc-primary\",\"to\":\"mobile-zone-iot\",\"width\":2,\"color\":\"#06b6d4\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"router1-branch-ny\",\"from\":\"core-router-1\",\"to\":\"branch-router-ny\",\"width\":3,\"color\":\"#a855f7\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"SD-WAN tunnel\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"dashed\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"router1-branch-la\",\"from\":\"core-router-1\",\"to\":\"branch-router-la\",\"width\":3,\"color\":\"#a855f7\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"SD-WAN tunnel\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"dashed\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"router1-branch-chi\",\"from\":\"core-router-1\",\"to\":\"branch-router-chi\",\"width\":3,\"color\":\"#a855f7\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"SD-WAN tunnel\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"dashed\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"router1-branch-lon\",\"from\":\"core-router-1\",\"to\":\"branch-router-lon\",\"width\":3,\"color\":\"#a855f7\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"SD-WAN tunnel\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"dashed\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"router1-branch-tokyo\",\"from\":\"core-router-1\",\"to\":\"branch-router-tokyo\",\"width\":3,\"color\":\"#a855f7\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"SD-WAN tunnel\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"dashed\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"router1-aws\",\"from\":\"core-router-1\",\"to\":\"cloud-aws\",\"width\":3,\"color\":\"#f97316\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"Direct Connect\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"dashed\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"router2-azure\",\"from\":\"core-router-2\",\"to\":\"cloud-azure\",\"width\":3,\"color\":\"#0ea5e9\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"ExpressRoute\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"dashed\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"fwint-gcp\",\"from\":\"fw-internal\",\"to\":\"cloud-gcp\",\"width\":2,\"color\":\"#22c55e\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"VPN tunnel\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"dashed\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw1-floor1\",\"from\":\"core-switch-1\",\"to\":\"dist-switch-floor1\",\"width\":3,\"color\":\"#475569\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw1-floor2\",\"from\":\"core-switch-1\",\"to\":\"dist-switch-floor2\",\"width\":3,\"color\":\"#475569\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw2-floor3\",\"from\":\"core-switch-2\",\"to\":\"dist-switch-floor3\",\"width\":3,\"color\":\"#475569\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw2-floor4\",\"from\":\"core-switch-2\",\"to\":\"dist-switch-floor4\",\"width\":3,\"color\":\"#475569\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"floor1-endpoints\",\"from\":\"dist-switch-floor1\",\"to\":\"endpoint-1000\",\"width\":2,\"color\":\"#94a3b8\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"floor1-ap1\",\"from\":\"dist-switch-floor1\",\"to\":\"ap-floor1-zone1\",\"width\":2,\"color\":\"#06b6d4\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"floor2-ap2\",\"from\":\"dist-switch-floor2\",\"to\":\"ap-floor2-zone1\",\"width\":2,\"color\":\"#06b6d4\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"floor3-ap3\",\"from\":\"dist-switch-floor3\",\"to\":\"ap-floor3-zone1\",\"width\":2,\"color\":\"#06b6d4\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"floor4-ap4\",\"from\":\"dist-switch-floor4\",\"to\":\"ap-floor4-zone1\",\"width\":2,\"color\":\"#06b6d4\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"fwint-proxy1\",\"from\":\"fw-internal\",\"to\":\"proxy-server-1\",\"width\":2,\"color\":\"#ef4444\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"fwint-proxy2\",\"from\":\"fw-internal\",\"to\":\"proxy-server-2\",\"width\":2,\"color\":\"#ef4444\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"fwext1-vpn\",\"from\":\"fw-external-1\",\"to\":\"vpn-concentrator\",\"width\":3,\"color\":\"#8b5cf6\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw1-nac\",\"from\":\"core-switch-1\",\"to\":\"nac-server\",\"width\":2,\"color\":\"#f59e0b\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw1-voip\",\"from\":\"core-switch-1\",\"to\":\"voip-cluster\",\"width\":3,\"color\":\"#22c55e\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw2-video\",\"from\":\"core-switch-2\",\"to\":\"video-conf\",\"width\":3,\"color\":\"#22c55e\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw1-cameras\",\"from\":\"core-switch-1\",\"to\":\"security-cameras\",\"width\":2,\"color\":\"#94a3b8\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"fwint-dev1\",\"from\":\"fw-internal\",\"to\":\"dev-server-1\",\"width\":2,\"color\":\"#a855f7\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"fwint-dev2\",\"from\":\"fw-internal\",\"to\":\"dev-server-2\",\"width\":2,\"color\":\"#a855f7\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\",\"animate\":true,\"animationSpeed\":\"1.5\"},{\"id\":\"fwint-test\",\"from\":\"fw-internal\",\"to\":\"test-environment\",\"width\":2,\"color\":\"#a855f7\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw1-erp\",\"from\":\"core-switch-1\",\"to\":\"erp-system\",\"width\":3,\"color\":\"#f59e0b\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"fwext1-crm\",\"from\":\"fw-external-1\",\"to\":\"crm-system\",\"width\":2,\"color\":\"#f59e0b\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"Salesforce cloud\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"dashed\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"ups1-racka1\",\"from\":\"ups-dc-1\",\"to\":\"dc-rack-a1\",\"width\":2,\"color\":\"#fbbf24\",\"direction\":\"forward\",\"type\":\"main\",\"notes\":[\"Power feed A\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"ups2-racka2\",\"from\":\"ups-dc-2\",\"to\":\"dc-rack-a2\",\"width\":2,\"color\":\"#fbbf24\",\"direction\":\"forward\",\"type\":\"main\",\"notes\":[\"Power feed B\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"ups1-rackb1\",\"from\":\"ups-dc-1\",\"to\":\"dc-rack-b1\",\"width\":2,\"color\":\"#fbbf24\",\"direction\":\"forward\",\"type\":\"main\",\"notes\":[\"Power feed A\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\",\"animate\":true,\"animationSpeed\":\"4\"},{\"id\":\"ups2-rackb2\",\"from\":\"ups-dc-2\",\"to\":\"dc-rack-b2\",\"width\":2,\"color\":\"#fbbf24\",\"direction\":\"forward\",\"type\":\"main\",\"notes\":[\"Power feed B\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"cooling1-racka1\",\"from\":\"cooling-1\",\"to\":\"dc-rack-a1\",\"width\":2,\"color\":\"#38bdf8\",\"direction\":\"forward\",\"type\":\"main\",\"notes\":[\"Cooling zone\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"dotted\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"cooling2-rackb1\",\"from\":\"cooling-2\",\"to\":\"dc-rack-b1\",\"width\":2,\"color\":\"#38bdf8\",\"direction\":\"forward\",\"type\":\"main\",\"notes\":[\"Cooling zone\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"dotted\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"custom-1765237881452\",\"type\":\"custom\",\"color\":\"#c800ff\",\"width\":4,\"lineStyle\":\"solid\",\"direction\":\"forward\",\"points\":[{\"x\":3492.3994140625,\"y\":1526.9556884765625},{\"x\":3500.609619140625,\"y\":1830.7386474609375},{\"x\":3303.561279296875,\"y\":1732.2144775390625}],\"notes\":[],\"routing\":\"orthogonal\"},{\"id\":\"custom-1765239355462\",\"type\":\"custom\",\"color\":\"#f97316\",\"width\":4,\"lineStyle\":\"solid\",\"direction\":\"forward\",\"points\":[{\"x\":2467.182861328125,\"y\":156.12173461914062},{\"x\":2146.36376953125,\"y\":146.32574462890625},{\"x\":2305.548828125,\"y\":244.28573608398438}],\"notes\":[],\"routing\":\"orthogonal\"}]},\"rectData\":{\"list\":[{\"id\":\"rect-1765237540610\",\"x\":2879.214599609375,\"y\":159.71981811523438,\"width\":992.196044921875,\"height\":538.8650817871094,\"color\":\"#f97316\",\"style\":\"filled\",\"lineStyle\":\"solid\",\"notes\":[]},{\"id\":\"rect-1765237681216\",\"x\":448.3926696777344,\"y\":1671.651123046875,\"width\":916.3436584472656,\"height\":924.27734375,\"color\":\"#c800ff\",\"style\":\"outlined\",\"lineStyle\":\"solid\",\"notes\":[]},{\"id\":\"rect-1766437913740\",\"x\":904.5889892578125,\"y\":115.40318298339844,\"width\":110.93878173828125,\"height\":919.6242218017578,\"color\":\"#5215f9\",\"style\":\"filled\",\"lineStyle\":\"wall\",\"notes\":[],\"borderWidth\":13},{\"id\":\"rect-1766437935414\",\"x\":130.93685150146484,\"y\":1072.3624877929688,\"width\":872.9131851196289,\"height\":99.260986328125,\"color\":\"#5215f9\",\"style\":\"filled\",\"lineStyle\":\"wall\",\"notes\":[],\"borderWidth\":13}]},\"textData\":{\"list\":[{\"id\":\"text-1765237828167\",\"x\":3411.458740234375,\"y\":1390.00439453125,\"content\":\"Double click on desktop\\nor long press on mobile\\nto enter rack canvas view\",\"fontSize\":46,\"color\":\"#e2e8f0\",\"fontWeight\":\"bold\",\"fontStyle\":\"italic\",\"textAlign\":\"middle\",\"textDecoration\":\"none\",\"bgColor\":\"#000000\",\"bgEnabled\":false,\"opacity\":1},{\"id\":\"text-1765239331126\",\"x\":2454.5615234375,\"y\":160.73322105407715,\"content\":\"Google is live!\",\"fontSize\":56,\"color\":\"#e2e8f0\",\"fontWeight\":\"bold\",\"fontStyle\":\"normal\",\"textAlign\":\"start\",\"textDecoration\":\"none\",\"bgColor\":\"#000000\",\"bgEnabled\":false,\"opacity\":1},{\"id\":\"text-1766446595277\",\"x\":654.3878479003906,\"y\":1367.7945556640625,\"content\":\"SITE A\",\"fontSize\":101,\"color\":\"#e2e8f0\",\"fontWeight\":\"normal\",\"fontStyle\":\"normal\",\"textAlign\":\"start\",\"textDecoration\":\"none\",\"bgColor\":\"#000000\",\"bgEnabled\":false,\"opacity\":1},{\"id\":\"text-1766446610211\",\"x\":180.63662719726562,\"y\":1128.822998046875,\"content\":\"SITE A\",\"fontSize\":101,\"color\":\"#e2e8f0\",\"fontWeight\":\"normal\",\"fontStyle\":\"normal\",\"textAlign\":\"start\",\"textDecoration\":\"none\",\"bgColor\":\"#000000\",\"bgEnabled\":false,\"opacity\":1},{\"id\":\"text-1766453024797\",\"x\":968.6458740234375,\"y\":1028.6621398925781,\"content\":\"SITE A\",\"fontSize\":101,\"color\":\"#e2e8f0\",\"fontWeight\":\"normal\",\"fontStyle\":\"normal\",\"textAlign\":\"start\",\"textDecoration\":\"none\",\"bgColor\":\"#000000\",\"bgEnabled\":false,\"opacity\":1,\"rotation\":-89,\"_dragStartX\":972.46826171875,\"_dragStartY\":1009.5499572753906},{\"id\":\"text-1766453070975\",\"x\":613.1589965820312,\"y\":1139.512939453125,\"content\":\"SITE A\",\"fontSize\":101,\"color\":\"#e2e8f0\",\"fontWeight\":\"normal\",\"fontStyle\":\"normal\",\"textAlign\":\"start\",\"textDecoration\":\"none\",\"bgColor\":\"#000000\",\"bgEnabled\":false,\"opacity\":1},{\"id\":\"text-1766453072857\",\"x\":968.64599609375,\"y\":474.40818786621094,\"content\":\"SITE A\",\"fontSize\":101,\"color\":\"#e2e8f0\",\"fontWeight\":\"normal\",\"fontStyle\":\"normal\",\"textAlign\":\"start\",\"textDecoration\":\"none\",\"bgColor\":\"#000000\",\"bgEnabled\":false,\"opacity\":1,\"rotation\":269,\"_dragStartX\":1480.85302734375,\"_dragStartY\":822.2503356933594}]},\"edgeLegend\":{\"#10b981\":\"Trusted Lan\",\"#f59e0b\":\"Secure Lan\",\"#ef4444\":\"DMZ\",\"#475569\":\"Main ISP\",\"#3b82f6\":\"Alternate ISP\",\"#8b5cf6\":\"you can edit me too\",\"#06b6d4\":\"you can edit me too\",\"#a855f7\":\"you can edit me too\",\"#f97316\":\"you can edit me too\",\"#0ea5e9\":\"you can edit me too\",\"#22c55e\":\"you can edit me too\",\"#94a3b8\":\"you can edit me too\",\"#fbbf24\":\"you can edit me too\",\"#38bdf8\":\"you can edit me too\",\"#c800ff\":\"you can edit me too\"},\"nodePositions\":{\"core-router-1\":{\"x\":3720.166015625,\"y\":245.9932403564453},\"core-router-2\":{\"x\":2499.883407638303,\"y\":329.99503430389154},\"fw-external-1\":{\"x\":3221.7385182723783,\"y\":1016.1364499992887},\"fw-external-2\":{\"x\":1915.5213706410505,\"y\":224.43528858865443},\"fw-internal\":{\"x\":1746.9168185079352,\"y\":477.5300527221864},\"core-switch-1\":{\"x\":449.39860669455675,\"y\":384.4578707617695},\"core-switch-2\":{\"x\":761.1664921394672,\"y\":180.89283910873155},\"dc-rack-a1\":{\"x\":783.7017241128451,\"y\":647.4086870405963},\"dc-rack-a2\":{\"x\":209.25701628255229,\"y\":228.01593190351014},\"dc-rack-b1\":{\"x\":3184.3186625759854,\"y\":1627.4495531027196},\"dc-rack-b2\":{\"x\":245.37065918741246,\"y\":499.6191264194081},\"dmz-rack\":{\"x\":2176.4105289561007,\"y\":610.8312056412005},\"mgmt-rack\":{\"x\":1601.2987201807314,\"y\":1281.4753424975324},\"esxi-host-01\":{\"x\":2162.2166789540615,\"y\":2608.110619289529},\"esxi-host-02\":{\"x\":2205.94717202368,\"y\":2689.67539624076},\"esxi-host-03\":{\"x\":2154.6015436939074,\"y\":2771.203009774913},\"esxi-host-04\":{\"x\":2195.986926025096,\"y\":2845},\"tor-switch-a1\":{\"x\":2146.8943639962963,\"y\":2845},\"esxi-host-05\":{\"x\":2185.9099961569727,\"y\":2845},\"esxi-host-06\":{\"x\":2139.099728450725,\"y\":2845},\"esxi-host-07\":{\"x\":2175.7223818764883,\"y\":2845},\"esxi-host-08\":{\"x\":2131.2222777148922,\"y\":2845},\"tor-switch-a2\":{\"x\":2165.4301485385085,\"y\":2845},\"san-primary\":{\"x\":2123.2667017518106,\"y\":2845},\"san-secondary\":{\"x\":2155.0394237844876,\"y\":2845},\"fc-switch-1\":{\"x\":2115.2377370375634,\"y\":2845},\"fc-switch-2\":{\"x\":2144.5563938942755,\"y\":2845},\"backup-server-1\":{\"x\":2107.1401637413705,\"y\":2845},\"backup-server-2\":{\"x\":2133.987300103025,\"y\":2845},\"tape-library\":{\"x\":2098.9788028796397,\"y\":2845},\"tor-switch-b1\":{\"x\":2123.338434885373,\"y\":2845},\"tor-switch-b2\":{\"x\":2090.7585134456995,\"y\":2845},\"web-server-1\":{\"x\":2112.6161382091163,\"y\":2845},\"web-server-2\":{\"x\":2082.484189516922,\"y\":2845},\"waf-1\":{\"x\":2101.826793760617,\"y\":2845},\"load-balancer-dmz\":{\"x\":2074.1607573409574,\"y\":2845},\"mail-gateway\":{\"x\":2090.97682514417,\"y\":2845},\"dns-external-1\":{\"x\":2065.7931724028163,\"y\":2845},\"dns-external-2\":{\"x\":2080.0726920576153,\"y\":2845},\"vcenter\":{\"x\":2057.3864164745437,\"y\":2845},\"nsx-manager\":{\"x\":2069.1208864464534,\"y\":2845},\"siem-server\":{\"x\":2048.945494649244,\"y\":2845},\"nms-server\":{\"x\":2058.1279286387635,\"y\":2845},\"jump-server\":{\"x\":2040.4754323612206,\"y\":2845},\"ipam-server\":{\"x\":2047.1003634632284,\"y\":2845},\"wlc-primary\":{\"x\":1575.9723612611924,\"y\":2306.135986328125},\"wlc-secondary\":{\"x\":1468.1361870166274,\"y\":1563.733642578125},\"mobile-zone-hq\":{\"x\":2354.901177346808,\"y\":2806.0078125},\"mobile-zone-guest\":{\"x\":2307.6605605284435,\"y\":2611.047119140625},\"mobile-zone-iot\":{\"x\":2229.397686389302,\"y\":2299.110107421875},\"branch-router-ny\":{\"x\":3151.903101363964,\"y\":633.6580810546875},\"branch-router-la\":{\"x\":3083.8876194705945,\"y\":506.90625},\"branch-router-chi\":{\"x\":3355.02409980103,\"y\":393.1805725097656},\"branch-router-lon\":{\"x\":3113.609823320121,\"y\":260.4093322753906},\"branch-router-tokyo\":{\"x\":3699.3234994733834,\"y\":471.4241027832031},\"cloud-aws\":{\"x\":3436.528122523513,\"y\":545.9614868164062},\"cloud-azure\":{\"x\":2592.566210818907,\"y\":2724.068115234375},\"cloud-gcp\":{\"x\":2827.3183770424234,\"y\":2731.397216796875},\"isp-primary\":{\"x\":3712.192068081962,\"y\":615.64990234375},\"isp-secondary\":{\"x\":2702.3789772348055,\"y\":467.890869140625},\"dc-internal-1\":{\"x\":1958.4243458877936,\"y\":2845},\"dc-internal-2\":{\"x\":1963.768951182132,\"y\":2845},\"app-server-1\":{\"x\":1947.3819379304134,\"y\":2845},\"app-server-2\":{\"x\":1955.2862087394126,\"y\":2845},\"db-server-1\":{\"x\":1936.3708569559828,\"y\":2845},\"db-server-2\":{\"x\":1946.8300873488822,\"y\":2845},\"k8s-master-1\":{\"x\":1925.397658583093,\"y\":2845},\"k8s-master-2\":{\"x\":1938.405621494142,\"y\":2845},\"k8s-master-3\":{\"x\":1914.4688758763386,\"y\":2845},\"k8s-worker-1\":{\"x\":1930.017826812177,\"y\":2845},\"k8s-worker-2\":{\"x\":1903.5910154567553,\"y\":2845},\"k8s-worker-3\":{\"x\":1921.6716971072178,\"y\":2845},\"k8s-worker-4\":{\"x\":1892.7705536280016,\"y\":2845},\"proxy-server-1\":{\"x\":1806.1152433697903,\"y\":653.7529296875},\"proxy-server-2\":{\"x\":2937.4207928721535,\"y\":2628.7880859375},\"vpn-concentrator\":{\"x\":3642.252088474593,\"y\":946.7255249023438},\"nac-server\":{\"x\":1153.2626148502184,\"y\":1172.1895751953125},\"print-server\":{\"x\":1896.9328460745962,\"y\":2845},\"file-server\":{\"x\":1860.7177871362182,\"y\":2845},\"ca-server\":{\"x\":1888.8027739274805,\"y\":2845},\"sccm-server\":{\"x\":1850.1909418511675,\"y\":2845},\"voip-cluster\":{\"x\":1777.038465328039,\"y\":1616.8961181640625},\"video-conf\":{\"x\":1993.8373941679588,\"y\":2244.936309814453},\"security-cameras\":{\"x\":1674.413336949044,\"y\":2046.0380859375},\"nvr-cluster\":{\"x\":1829.4110389706402,\"y\":2845},\"dev-server-1\":{\"x\":2800.5894350649614,\"y\":1175.623291015625},\"dev-server-2\":{\"x\":1945.0822182484326,\"y\":1164.5184783935547},\"test-environment\":{\"x\":2566.9100352578575,\"y\":885.2827758789062},\"erp-system\":{\"x\":789.9880103985649,\"y\":473.7113342285156},\"crm-system\":{\"x\":3514.6003232048542,\"y\":1137.7720947265625},\"endpoint-1000\":{\"x\":991.6812012057328,\"y\":2284.42236328125},\"dist-switch-floor1\":{\"x\":654.2091033261356,\"y\":2020.0086669921875},\"dist-switch-floor2\":{\"x\":853.8845527112826,\"y\":1843.2872314453125},\"dist-switch-floor3\":{\"x\":1899.4353951584517,\"y\":1456.5068359375},\"dist-switch-floor4\":{\"x\":488.5289313756234,\"y\":181.47256469726562},\"ap-floor1-zone1\":{\"x\":1140.16846970184,\"y\":2070.2916259765625},\"ap-floor2-zone1\":{\"x\":688.1952143592268,\"y\":2384.4775390625},\"ap-floor3-zone1\":{\"x\":2145.3803027919676,\"y\":1890.2816162109375},\"ap-floor4-zone1\":{\"x\":517.646146409649,\"y\":565.59716796875},\"ups-dc-1\":{\"x\":771.1406786539856,\"y\":295.9266662597656},\"ups-dc-2\":{\"x\":216.2410855890687,\"y\":330.3345947265625},\"pdu-rack-a1\":{\"x\":1804.774444371901,\"y\":2845},\"pdu-rack-a2\":{\"x\":1741.6184034693686,\"y\":2845},\"cooling-1\":{\"x\":245.7080801919958,\"y\":626.1914672851562},\"cooling-2\":{\"x\":1603.293611085831,\"y\":981.0621185302734},\"camera-a\":{\"x\":166.57075412676295,\"y\":145},\"camera-a-copy\":{\"x\":1040.653076171875,\"y\":738.42822265625}},\"nodeSizes\":{\"isp-secondary\":139,\"test-environment\":148,\"dev-server-1\":128,\"core-router-2\":120,\"camera-a\":45,\"camera-a-copy\":45},\"nodeStyles\":{\"dc-rack-b2\":{\"all\":{\"circleColor\":\"#ff0000\"}},\"dc-rack-a1\":{\"all\":{\"circleColor\":\"#ff0000\"}},\"dc-rack-b1\":{\"all\":{\"circleColor\":\"#ff0000\",\"titleSize\":59}},\"isp-secondary\":{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"alist\"}}},\"core-router-2\":{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"actual-budget\"},\"pingOffsetX\":-15,\"pingOffsetY\":-38}},\"fw-external-1\":{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"anonaddy\"}}},\"cloud-aws\":{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"ansible\"}}},\"isp-primary\":{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"wikidocs\"}}},\"branch-router-tokyo\":{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"adguard-home\"}}},\"core-router-1\":{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"borg\"}}},\"test-environment\":{\"all\":{\"icon\":{\"library\":\"simple\",\"name\":\"apple\"}}},\"dev-server-1\":{\"all\":{\"icon\":{\"library\":\"simple\",\"name\":\"amazonwebservices\"}}}},\"iconCache\":{\"selfhst-borg\":\"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" xml:space=\\\"preserve\\\" viewBox=\\\"0 0 512 512\\\"><path d=\\\"M331 102.2H204.5V0h197.9L505 102.2v104.2l-51.3 51.3L505 309v100.7L402.4 512H204.5V409.8H331V300.6H204.5v-89.1H331zM6.9 0h170.5v512H6.9z\\\" style=\\\"fill:#0d0\\\"/></svg>\",\"selfhst-actual-budget\":\"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" xml:space=\\\"preserve\\\" viewBox=\\\"0 0 512 512\\\"><path d=\\\"M378.5 512h-245C59.8 512 0 452.2 0 378.5v-245C0 59.8 59.8 0 133.5 0h245C452.2 0 512 59.8 512 133.5v245c0 73.7-59.8 133.5-133.5 133.5\\\" style=\\\"fill:#805ad5\\\"/><path d=\\\"m407.7 384.1-33.2-68.2 26.9-10.4c1.7-.6 2.6-2.6 1.9-4.3l-7.8-20.2c-.6-1.7-2.6-2.6-4.3-1.9L362 290.3 268.8 99.2c-.6-1.2-1.7-1.9-3-1.9h-6.3c-1.3 0-2.4.7-3 1.9l-95 201.6-55.2 20.3c-1.8.6-2.6 2.6-2 4.3l7.4 20.3c.6 1.8 2.6 2.6 4.3 2l28.4-10.4-32.2 68.3c-.3.8-.4 1.7 0 2.5l1.6 4.3v.1c.6 1.7 2.6 2.6 4.3 1.9l229.8-88.3 34.3 70.4c.8 1.7 2.8 2.4 4.5 1.6l19.5-9.5c1.6-.8 2.3-2.8 1.5-4.5M263 151.8l46.1 94.9L199.4 287zM161.5 367.4l20.7-44 139.4-51.1 13.7 28.3z\\\" style=\\\"fill:#fff\\\"/></svg>\",\"selfhst-anonaddy\":\"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" xml:space=\\\"preserve\\\" viewBox=\\\"0 0 512 512\\\"><linearGradient id=\\\"anonaddy_svg__a\\\" x1=\\\"44.378\\\" x2=\\\"447.022\\\" y1=\\\"386.378\\\" y2=\\\"789.022\\\" gradientTransform=\\\"translate(0 -278)\\\" gradientUnits=\\\"userSpaceOnUse\\\"><stop offset=\\\"0\\\" style=\\\"stop-color:#3ae7e1\\\"/><stop offset=\\\".948\\\" style=\\\"stop-color:#1993d2\\\"/></linearGradient><path d=\\\"M320 224H192c-17.7 0-32 14.3-32 32v128c0 17.7 14.3 32 32 32h128c17.7 0 32-14.3 32-32V256c0-17.7-14.3-32-32-32m-32 128h-64v-64h64z\\\" style=\\\"fill:url(#anonaddy_svg__a)\\\"/><linearGradient id=\\\"anonaddy_svg__b\\\" x1=\\\"44.337\\\" x2=\\\"446.981\\\" y1=\\\"386.418\\\" y2=\\\"789.062\\\" gradientTransform=\\\"translate(0 -278)\\\" gradientUnits=\\\"userSpaceOnUse\\\"><stop offset=\\\"0\\\" style=\\\"stop-color:#3ae7e1\\\"/><stop offset=\\\".948\\\" style=\\\"stop-color:#1993d2\\\"/></linearGradient><path d=\\\"M384 384c0 5.5-.8 10.9-2.1 16 39.4-21.8 66.1-63.8 66.1-112v-32c0-47.4-25.8-88.6-64-110.7-18.8-10.9-40.7-17.3-64-17.3H192c-23.3 0-45.2 6.4-64 17.3-38.2 22.1-64 63.3-64 110.7v128c0 70.7 57.3 128 128 128h128c0-35.3-28.7-64-64-64h-64c-35.3 0-64-28.7-64-64V256c0-35.3 28.7-64 64-64h128c35.3 0 64 28.7 64 64z\\\" style=\\\"fill:url(#anonaddy_svg__b)\\\"/><path d=\\\"M384 288c0 23.7-12.9 44.3-32 55.4V384c0 17.7-14.3 32-32 32 22.5 0 43.5-5.8 61.9-16 1.3-5.1 2.1-10.5 2.1-16zM128 145.3c18.8-10.9 40.7-17.3 64-17.3 0-11.7 3.2-22.6 8.7-32H192c-21.4 0-42.7 4.4-62.4 12.8-1 6.3-1.6 12.7-1.6 19.2zM311.3 96c5.5 9.4 8.7 20.3 8.7 32 23.3 0 45.2 6.4 64 17.3V128c0-6.5-.6-12.9-1.6-19.2-19.7-8.4-41-12.8-62.4-12.8z\\\" style=\\\"fill:#2d7aae\\\"/><linearGradient id=\\\"anonaddy_svg__c\\\" x1=\\\"158.782\\\" x2=\\\"561.426\\\" y1=\\\"271.974\\\" y2=\\\"674.618\\\" gradientTransform=\\\"translate(0 -278)\\\" gradientUnits=\\\"userSpaceOnUse\\\"><stop offset=\\\"0\\\" style=\\\"stop-color:#3ae7e1\\\"/><stop offset=\\\".948\\\" style=\\\"stop-color:#1993d2\\\"/></linearGradient><path d=\\\"M200.7 96c11.1-19.1 31.7-32 55.3-32s44.3 12.9 55.3 32h8.7c21.4 0 42.7 4.4 62.4 12.8C373.1 47.3 320 0 256 0S138.9 47.3 129.6 108.8c19.7-8.4 41-12.8 62.4-12.8z\\\" style=\\\"fill:url(#anonaddy_svg__c)\\\"/></svg>\",\"selfhst-adguard-home\":\"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" xml:space=\\\"preserve\\\" viewBox=\\\"0 0 512 512\\\"><path d=\\\"M505.8 58.8C428.2 18.4 334.1 0 256 0h-.3v511.8c-21.5-12.7-41.1-26-59.1-39.6 18 13.7 37.8 27 59.4 39.8C506.8 363.4 505.8 146 505.8 58.8\\\" style=\\\"fill-rule:evenodd;clip-rule:evenodd;fill:#68bc71\\\"/><path d=\\\"M255.7 511.8C5.2 363.3 6.2 146 6.2 58.8 83.7 18.4 177.7 0 255.7 0z\\\" style=\\\"fill-rule:evenodd;clip-rule:evenodd;fill:#67b279\\\"/><path d=\\\"m246.9 341.6 151-203.6c-11.1-8.9-20.8-2.6-26.1 2.2h-.2l-125.9 131-47.4-57.1c-22.6-26.2-53.4-6.2-60.6-.9z\\\" style=\\\"fill-rule:evenodd;clip-rule:evenodd;fill:#fff\\\"/></svg>\",\"selfhst-ansible\":\"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" xml:space=\\\"preserve\\\" viewBox=\\\"0 0 512 512\\\"><path d=\\\"M512 256c0 141.4-114.6 256-256 256S0 397.4 0 256 114.6 0 256 0s256 114.6 256 256\\\" style=\\\"fill:#c32214\\\"/><path d=\\\"m260.3 156.4 66.2 163.5-100.1-78.8zM378 357.6 276.1 112.3c-2.9-7.1-8.7-10.8-15.8-10.8s-13.3 3.7-16.2 10.8l-111.8 269h38.3l44.3-110.9L347 377.1c5.3 4.3 9.1 6.2 14.1 6.2 10 0 18.7-7.5 18.7-18.3-.1-1.7-.7-4.4-1.8-7.4\\\" style=\\\"fill:#fff\\\"/></svg>\",\"selfhst-wikidocs\":\"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" xml:space=\\\"preserve\\\" viewBox=\\\"0 0 512 512\\\"><path d=\\\"M488.8 72.1h-90.7c-8.4 0-15.1 6.7-15.1 15.1v206.4c0 8.4-6.7 15.1-15.1 15.1h-45.3c-8.4 0-15.1-6.7-15.1-15.1V87.2c0-8.4-6.7-15.1-15.1-15.1h-72.5c-8.4 0-15.1 6.7-15.1 15.1v206.4c0 8.4-6.7 15.1-15.1 15.1h-45.3c-8.4 0-15.1-6.7-15.1-15.1V87.2c0-8.4-6.7-15.1-15.1-15.1H23.4c-8.4 0-15.1 6.7-15.1 15.1v257.2c0 50.1 38.6 91.2 87.5 95.3v.3h392.9c8.4 0 15.1-6.7 15.1-15.1V87.2c0-8.4-6.7-15.1-15-15.1\\\" style=\\\"fill:#4caf50\\\"/><path d=\\\"M488.6 448.2H95.7c-1.1 0-2.1-.2-3.1-.6-24.9-2.7-48-14.2-65.1-32.8C9.8 395.6 0 370.5 0 344.3V87.2c0-12.9 10.5-23.4 23.4-23.4h90.9c12.9 0 23.4 10.5 23.4 23.4v206.4c0 3.8 3 6.8 6.8 6.8h45.3c3.8 0 6.8-3 6.8-6.8V87.2c0-12.9 10.5-23.4 23.4-23.4h72.5c12.9 0 23.4 10.5 23.4 23.4v206.4c0 3.8 3 6.8 6.8 6.8H368c3.8 0 6.8-3 6.8-6.8V87.2c0-12.9 10.5-23.4 23.4-23.4h90.7c6.3 0 12.2 2.5 16.6 6.9s6.7 10.3 6.6 16.6v337.6c-.1 12.8-10.6 23.3-23.5 23.3M98.1 431.7h390.6c3.8 0 6.8-3 6.8-6.8V87.2c0-1.9-.6-3.6-1.9-4.8-1.3-1.3-3-2-4.8-2h-90.7c-3.8 0-6.8 3-6.8 6.8v206.4c0 12.9-10.5 23.4-23.4 23.4h-45.3c-12.9 0-23.4-10.5-23.4-23.4V87.2c0-3.8-3-6.8-6.8-6.8h-72.5c-3.8 0-6.8 3-6.8 6.8v206.4c0 12.9-10.5 23.4-23.4 23.4h-45.3c-12.9 0-23.4-10.5-23.4-23.4V87.2c0-3.8-3-6.8-6.8-6.8H23.4c-3.8 0-6.8 3-6.8 6.8v257.2c0 45.1 35.1 83.3 79.9 87.1.5-.1 1 0 1.6.2\\\" style=\\\"fill:#fff\\\"/></svg>\",\"selfhst-alist\":\"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" xml:space=\\\"preserve\\\" viewBox=\\\"0 0 512 512\\\"><path d=\\\"M260.5 30.8c5.4-.6 11 .6 15.5 3.7 6.8 4.4 11.6 11.1 15.7 17.9 18.4 30.9 37 61.7 55.4 92.6 24.5 41.3 49.1 82.7 73.6 124q25.35 42.9 51 85.8c11.2 19.1 22.8 37.9 33.5 57.3 4.6 8.1 7.2 17.5 6.8 26.8-.3 7.2-1.6 14.7-5.4 20.9-5.3 8.8-14.3 15.1-23.9 18.3-5.2 1.9-10.6 3.1-16.1 3.1H286.3c-5.2-.1-10.4-.7-15.3-2.6-3.5-1.5-7-3.4-9.8-6.1-4.7-4.7-7.8-11.3-7-18.1.4-4.7 1.6-9.4 3.5-13.7 1.9-4.4 4.5-8.5 6.8-12.7 10.1-17.9 20.6-35.6 31.5-53 3.5-5.5 6.6-11.4 11.4-15.8 5.2-4.7 11.6-8.4 18.7-8.9 5.7-.2 11.3 1.7 16.1 4.8 5.4 3.1 9.6 8.4 11 14.5 2 7.7-.2 15.8-3.4 22.9-3.7 8-8.9 15.3-11.7 23.7-.5 1.9-1.2 4.3.2 6 1.4 1.4 3.5 1.9 5.3 2.4 5.5 1.2 11.1.9 16.6.9h61.3c4-.1 8.1.1 12.1-.1 2.9-.3 6.1-.8 8.4-2.9 1.5-1.3 1.7-3.6 1.1-5.4-1.2-4-3.4-7.7-5.5-11.3C383.2 312.9 329 219.7 274 126.9c-1.8-3-3.3-6.2-5.9-8.6-1.3-1.2-3.2-1.9-4.9-1.4-1.5.5-2.5 1.8-3.5 3-2.4 3.1-4.6 6.4-6.6 9.8-9.5 15.4-18.6 31.1-27.9 46.6-53.6 90.1-107.1 180.3-160.7 270.4-3.3 6.1-6.9 12.1-10.7 17.9-3.4 5-7.5 9.6-12.8 12.6-6.2 3.6-13.9 5-20.9 3-5.1-1.6-10-4.1-13.7-8.1-4.6-4.6-6.7-11.3-6.4-17.7.2-6.7 2.5-13.3 5.9-19.1C38.4 380.1 71.1 325 104 269.9c16.9-28.5 33.7-57.1 50.7-85.6 17.2-28.8 34.4-57.7 51.5-86.5 9.5-16.3 19.2-32.5 29.2-48.5 3.1-5.1 7.1-9.6 11.9-13.1 4-2.7 8.4-5 13.2-5.4\\\" style=\\\"fill:#70c6be\\\"/><path d=\\\"M257.8 244.3c8.1-1.3 16.7.6 23.4 5.4 5.1 3.9 8.6 9.8 9.7 16.2.7 4.9.2 9.9-1.6 14.5-1.4 4-3.3 7.7-5.5 11.3-31.2 52.5-62.5 105-93.7 157.5-4 6.6-7.6 13.4-12.2 19.6-3.3 4.1-7.5 7.7-12.4 9.9-10.2 5.4-23.5 2.1-31.3-6.1-3.9-3.9-6.2-9.4-6.5-14.9-.4-5.7.8-11.5 3.4-16.7 2.3-5 5-9.7 7.8-14.4 13.7-23.1 27.3-46.3 41-69.4 18.7-31.8 37.8-63.4 56.6-95.1 2-3.6 4.5-6.9 7.3-9.9 3.8-4 8.6-7.1 14-7.9\\\" style=\\\"fill:#1ba0d8\\\"/></svg>\",\"simple-amazonwebservices\":\"<svg role=\\\"img\\\" viewBox=\\\"0 0 24 24\\\" xmlns=\\\"http://www.w3.org/2000/svg\\\"><title>Amazon Web Services</title><path d=\\\"M6.763 10.036c0 .296.032.535.088.71.064.176.144.368.256.576.04.063.056.127.056.183 0 .08-.048.16-.152.24l-.503.335a.383.383 0 0 1-.208.072c-.08 0-.16-.04-.239-.112a2.47 2.47 0 0 1-.287-.375 6.18 6.18 0 0 1-.248-.471c-.622.734-1.405 1.101-2.347 1.101-.67 0-1.205-.191-1.596-.574-.391-.384-.59-.894-.59-1.533 0-.678.239-1.23.726-1.644.487-.415 1.133-.623 1.955-.623.272 0 .551.024.846.064.296.04.6.104.918.176v-.583c0-.607-.127-1.03-.375-1.277-.255-.248-.686-.367-1.3-.367-.28 0-.568.031-.863.103-.295.072-.583.16-.862.272a2.287 2.287 0 0 1-.28.104.488.488 0 0 1-.127.023c-.112 0-.168-.08-.168-.247v-.391c0-.128.016-.224.056-.28a.597.597 0 0 1 .224-.167c.279-.144.614-.264 1.005-.36a4.84 4.84 0 0 1 1.246-.151c.95 0 1.644.216 2.091.647.439.43.662 1.085.662 1.963v2.586zm-3.24 1.214c.263 0 .534-.048.822-.144.287-.096.543-.271.758-.51.128-.152.224-.32.272-.512.047-.191.08-.423.08-.694v-.335a6.66 6.66 0 0 0-.735-.136 6.02 6.02 0 0 0-.75-.048c-.535 0-.926.104-1.19.32-.263.215-.39.518-.39.917 0 .375.095.655.295.846.191.2.47.296.838.296zm6.41.862c-.144 0-.24-.024-.304-.08-.064-.048-.12-.16-.168-.311L7.586 5.55a1.398 1.398 0 0 1-.072-.32c0-.128.064-.2.191-.2h.783c.151 0 .255.025.31.08.065.048.113.16.16.312l1.342 5.284 1.245-5.284c.04-.16.088-.264.151-.312a.549.549 0 0 1 .32-.08h.638c.152 0 .256.025.32.08.063.048.12.16.151.312l1.261 5.348 1.381-5.348c.048-.16.104-.264.16-.312a.52.52 0 0 1 .311-.08h.743c.127 0 .2.065.2.2 0 .04-.009.08-.017.128a1.137 1.137 0 0 1-.056.2l-1.923 6.17c-.048.16-.104.263-.168.311a.51.51 0 0 1-.303.08h-.687c-.151 0-.255-.024-.32-.08-.063-.056-.119-.16-.15-.32l-1.238-5.148-1.23 5.14c-.04.16-.087.264-.15.32-.065.056-.177.08-.32.08zm10.256.215c-.415 0-.83-.048-1.229-.143-.399-.096-.71-.2-.918-.32-.128-.071-.215-.151-.247-.223a.563.563 0 0 1-.048-.224v-.407c0-.167.064-.247.183-.247.048 0 .096.008.144.024.048.016.12.048.2.08.271.12.566.215.878.279.319.064.63.096.95.096.502 0 .894-.088 1.165-.264a.86.86 0 0 0 .415-.758.777.777 0 0 0-.215-.559c-.144-.151-.416-.287-.807-.415l-1.157-.36c-.583-.183-1.014-.454-1.277-.813a1.902 1.902 0 0 1-.4-1.158c0-.335.073-.63.216-.886.144-.255.335-.479.575-.654.24-.184.51-.32.83-.415.32-.096.655-.136 1.006-.136.175 0 .359.008.535.032.183.024.35.056.518.088.16.04.312.08.455.127.144.048.256.096.336.144a.69.69 0 0 1 .24.2.43.43 0 0 1 .071.263v.375c0 .168-.064.256-.184.256a.83.83 0 0 1-.303-.096 3.652 3.652 0 0 0-1.532-.311c-.455 0-.815.071-1.062.223-.248.152-.375.383-.375.71 0 .224.08.416.24.567.159.152.454.304.877.44l1.134.358c.574.184.99.44 1.237.767.247.327.367.702.367 1.117 0 .343-.072.655-.207.926-.144.272-.336.511-.583.703-.248.2-.543.343-.886.447-.36.111-.734.167-1.142.167zM21.698 16.207c-2.626 1.94-6.442 2.969-9.722 2.969-4.598 0-8.74-1.7-11.87-4.526-.247-.223-.024-.527.272-.351 3.384 1.963 7.559 3.153 11.877 3.153 2.914 0 6.114-.607 9.06-1.852.439-.2.814.287.383.607zM22.792 14.961c-.336-.43-2.22-.207-3.074-.103-.255.032-.295-.192-.063-.36 1.5-1.053 3.967-.75 4.254-.399.287.36-.08 2.826-1.485 4.007-.215.184-.423.088-.327-.151.32-.79 1.03-2.57.695-2.994z\\\"/></svg>\",\"simple-apple\":\"<svg role=\\\"img\\\" viewBox=\\\"0 0 24 24\\\" xmlns=\\\"http://www.w3.org/2000/svg\\\"><title>Apple</title><path d=\\\"M12.152 6.896c-.948 0-2.415-1.078-3.96-1.04-2.04.027-3.91 1.183-4.961 3.014-2.117 3.675-.546 9.103 1.519 12.09 1.013 1.454 2.208 3.09 3.792 3.039 1.52-.065 2.09-.987 3.935-.987 1.831 0 2.35.987 3.96.948 1.637-.026 2.676-1.48 3.676-2.948 1.156-1.688 1.636-3.325 1.662-3.415-.039-.013-3.182-1.221-3.22-4.857-.026-3.04 2.48-4.494 2.597-4.559-1.429-2.09-3.623-2.324-4.39-2.376-2-.156-3.675 1.09-4.61 1.09zM15.53 3.83c.843-1.012 1.4-2.427 1.245-3.83-1.207.052-2.662.805-3.532 1.818-.78.896-1.454 2.338-1.273 3.714 1.338.104 2.715-.688 3.559-1.701\\\"/></svg>\",\"selfhst-amazon-web-services\":\"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" xml:space=\\\"preserve\\\" viewBox=\\\"0 0 512 512\\\"><path d=\\\"M144.3 214.1c0 6.3.7 11.4 1.9 15.2 1.4 3.7 3.1 7.8 5.4 12.3.9 1.4 1.2 2.7 1.2 3.9 0 1.7-1 3.4-3.2 5.1l-10.7 7.2c-1.5 1-3.1 1.5-4.4 1.5-1.7 0-3.4-.9-5.1-2.4-2.4-2.6-4.4-5.3-6.1-8-1.7-2.9-3.4-6.1-5.3-10-13.3 15.7-30 23.5-50.1 23.5-14.3 0-25.7-4.1-34.1-12.3-8.3-8.2-12.6-19.1-12.6-32.7 0-14.5 5.1-26.2 15.5-35.1S60.8 169 78.4 169c5.8 0 11.7.5 18.1 1.4s12.8 2.2 19.6 3.7v-12.4c0-12.9-2.7-22-8-27.2-5.4-5.3-14.6-7.8-27.8-7.8-6 0-12.1.7-18.4 2.2s-12.4 3.4-18.4 5.8c-2.7 1.2-4.8 1.9-6 2.2s-2 .5-2.7.5c-2.4 0-3.6-1.7-3.6-5.3v-8.3c0-2.7.3-4.8 1.2-6s2.4-2.4 4.8-3.6c6-3.1 13.1-5.6 21.5-7.7 8.3-2.2 17.2-3.2 26.6-3.2 20.3 0 35.1 4.6 44.6 13.8 9.4 9.2 14.1 23.2 14.1 41.9v55.2zM75.2 240c5.6 0 11.4-1 17.5-3.1 6.1-2 11.6-5.8 16.2-10.9 2.7-3.2 4.8-6.8 5.8-10.9s1.7-9 1.7-14.8v-7.2c-4.9-1.2-10.2-2.2-15.7-2.9-5.4-.7-10.7-1-16-1-11.4 0-19.8 2.2-25.4 6.8S51 207.1 51 215.6c0 8 2 14 6.3 18.1 4.1 4.2 10 6.3 17.9 6.3m136.7 18.4c-3.1 0-5.1-.5-6.5-1.7-1.4-1-2.6-3.4-3.6-6.6l-40-131.6c-1-3.4-1.5-5.6-1.5-6.8 0-2.7 1.4-4.3 4.1-4.3h16.7c3.2 0 5.4.5 6.6 1.7 1.4 1 2.4 3.4 3.4 6.6l28.6 112.7 26.6-112.7c.9-3.4 1.9-5.6 3.2-6.6 1.4-1 3.7-1.7 6.8-1.7H270c3.2 0 5.4.5 6.8 1.7 1.4 1 2.6 3.4 3.2 6.6l26.9 114.1 29.5-114.1c1-3.4 2.2-5.6 3.4-6.6 1.4-1 3.6-1.7 6.6-1.7h15.8c2.7 0 4.3 1.4 4.3 4.3 0 .9-.2 1.7-.3 2.7-.2 1-.5 2.4-1.2 4.3l-41 131.6q-1.5 5.1-3.6 6.6c-1.4 1-3.6 1.7-6.5 1.7h-14.6c-3.2 0-5.4-.5-6.8-1.7s-2.6-3.4-3.2-6.8l-26.4-109.8L236.7 250c-.9 3.4-1.9 5.6-3.2 6.8-1.4 1.2-3.7 1.7-6.8 1.7zm218.8 4.6c-8.9 0-17.7-1-26.2-3.1-8.5-2-15.2-4.3-19.6-6.8-2.7-1.5-4.6-3.2-5.3-4.8s-1-3.2-1-4.8v-8.7c0-3.6 1.4-5.3 3.9-5.3 1 0 2 .2 3.1.5 1 .3 2.6 1 4.3 1.7 5.8 2.6 12.1 4.6 18.7 6 6.8 1.4 13.5 2 20.3 2 10.7 0 19.1-1.9 24.9-5.6s8.9-9.2 8.9-16.2c0-4.8-1.5-8.7-4.6-11.9s-8.9-6.1-17.2-8.9l-24.7-7.7c-12.4-3.9-21.6-9.7-27.2-17.4-5.6-7.5-8.5-15.8-8.5-24.7 0-7.2 1.5-13.5 4.6-18.9s7.2-10.2 12.3-14c5.1-3.9 10.9-6.8 17.7-8.9 6.8-2 14-2.9 21.5-2.9 3.7 0 7.7.2 11.4.7 3.9.5 7.5 1.2 11.1 1.9 3.4.9 6.6 1.7 9.7 2.7s5.4 2 7.2 3.1c2.4 1.4 4.1 2.7 5.1 4.3 1 1.4 1.5 3.2 1.5 5.6v8c0 3.6-1.4 5.4-3.9 5.4-1.4 0-3.6-.7-6.5-2q-14.55-6.6-32.7-6.6c-9.7 0-17.4 1.5-22.6 4.8s-8 8.2-8 15.2c0 4.8 1.7 8.9 5.1 12.1s9.7 6.5 18.7 9.4l24.2 7.7c12.3 3.9 21.1 9.4 26.4 16.3s7.8 15 7.8 23.8c0 7.3-1.5 14-4.4 19.8-3.1 5.8-7.2 10.9-12.4 15-5.3 4.3-11.6 7.3-18.9 9.5-8 2.5-16 3.7-24.7 3.7\\\"/><path d=\\\"M462.9 345.7c-56 41.4-137.4 63.3-207.4 63.3-98.1 0-186.5-36.3-253.2-96.6-5.3-4.8-.5-11.2 5.8-7.5 72.2 41.9 161.3 67.3 253.4 67.3 62.2 0 130.4-12.9 193.3-39.5 9.3-4.2 17.3 6.2 8.1 13m23.3-26.5c-7.2-9.2-47.3-4.4-65.6-2.2-5.4.7-6.3-4.1-1.4-7.7 32-22.5 84.6-16 90.8-8.5 6.1 7.7-1.7 60.3-31.7 85.5-4.6 3.9-9 1.9-7-3.2 6.9-16.9 22.1-54.9 14.9-63.9\\\" style=\\\"fill-rule:evenodd;clip-rule:evenodd;fill:#f90\\\"/></svg>\",\"selfhst-opnsense\":\"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" xml:space=\\\"preserve\\\" viewBox=\\\"0 0 512 512\\\"><path d=\\\"M512 160.2V400H400V112H112V0h240.4zM112 112H0v240.4L160.2 512H400V400H112z\\\" style=\\\"fill:#de3c07\\\"/></svg>\",\"selfhst-portainer\":\"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" xml:space=\\\"preserve\\\" viewBox=\\\"0 0 512 512\\\"><path d=\\\"M462.2 512H49.8C22.3 512 0 489.7 0 462.2V49.8C0 22.3 22.3 0 49.8 0h412.4C489.7 0 512 22.3 512 49.8v412.4c0 27.5-22.3 49.8-49.8 49.8\\\" style=\\\"fill:#2e2f33\\\"/><path d=\\\"M108.2 63.9h140.9c97.2 0 154.6 30.4 154.6 129.5v3.4c0 99.4-57.2 129.5-154.5 129.5h-30.7V447H108.2zm134.5 177.8c30.1 0 46-11.2 46-44.6v-3.9c0-33.2-15.8-44.6-46-44.6h-24.2v93.1zM313 358h90.3v90.1H313z\\\" style=\\\"fill:#f7f6f3\\\"/></svg>\",\"selfhst-jotty\":\"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" xml:space=\\\"preserve\\\" viewBox=\\\"0 0 512 512\\\"><path d=\\\"M438.6 0H73.4C33.1 0 .3 32.8.3 73.1v365.7c0 40.3 32.8 73.1 73.1 73.1h365.3c40.3 0 73.1-32.8 73.1-73.1V73.1C511.7 32.8 478.9 0 438.6 0\\\" style=\\\"fill-rule:evenodd;clip-rule:evenodd;fill:#8b3bd0\\\"/><path d=\\\"M356.8 53.6v261.3c0 39.3-13.9 72.8-41.8 100.7s-61.4 41.8-100.7 41.8l-23.8-95h23.8c13 0 24.1-4.6 33.2-13.8 9.5-9.5 14.3-20.7 14.3-33.7V148.6H157.3v-95z\\\" style=\\\"fill:#fff\\\"/></svg>\",\"selfhst-authportal\":\"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" xml:space=\\\"preserve\\\" viewBox=\\\"0 0 512 512\\\"><path d=\\\"M0 0h512v512H0z\\\" style=\\\"fill:#111827\\\"/><path d=\\\"M170.7 85.3 369.8 256 170.7 426.7z\\\" style=\\\"fill:#f59e0b\\\"/></svg>\",\"selfhst-docker\":\"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" xml:space=\\\"preserve\\\" viewBox=\\\"0 0 512 512\\\"><path d=\\\"M501.4 212.3c-11.5-8-38-11-58.6-7-2.4-20-13.5-37.5-32.7-53l-11-8-7.7 11.5c-9.6 15-14.4 36-13 56 .5 7 2.9 19.5 10.1 30.5-6.7 4-20.7 9-38.9 9H2.3l-1 4c-3.4 20-3.4 82.5 36 130.5 29.8 36.5 74 55 132.1 55 125.9 0 219.1-60.5 262.8-170 17.3.5 54.3 0 73-37.5.5-1 1.4-3 4.8-10.5l1.9-4zM280 71.3h-52.8v50H280zm0 60h-52.8v50H280zm-62.5 0h-52.8v50h52.8zm-62.4 0h-52.8v50h52.8zm-62.5 60H39.8v50h52.8zm62.5 0h-52.8v50h52.8zm62.4 0h-52.8v50h52.8zm62.5 0h-52.8v50H280zm62.4 0h-52.8v50h52.8z\\\" style=\\\"fill:#2396ed\\\"/></svg>\",\"selfhst-opnsense-v1\":\"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" xml:space=\\\"preserve\\\" viewBox=\\\"0 0 512 512\\\"><path d=\\\"M503.1 465.4v2.1c-6 25.5-28.4 44.5-55 44.5H7.3v-46.6h38.3v7.3h403c1.1 0 2 0 2.9-.1 1.8-.1 3.6-.6 5.2-1.3 2.9-1.3 5.3-3.3 7-5.9zM63.9 0c-26.5 0-49 19-55 44.5V47H48c2.7-4.3 7.3-7.2 12.6-7.6 1-.1 2-.1 3.2-.1h402.6V47h38.3V0z\\\" style=\\\"fill:#898b8d\\\"/><path d=\\\"M466.1 157.7V197H319.5v-39.3zM45.9 315.4v39.3h146.6v-39.3zm0-157.7V197h146.6v-39.3zm273.6 157.7v39.3h146.6v-39.3z\\\" style=\\\"fill:#58595b\\\"/><path d=\\\"M83.8 78.6H428v39.3H83.8zm0 315.9H428v39.3H83.8z\\\" style=\\\"fill:#403f41\\\"/><linearGradient id=\\\"opnsense-v1_svg__a\\\" x1=\\\"-1460.617\\\" x2=\\\"-1375.11\\\" y1=\\\"1248.095\\\" y2=\\\"1248.095\\\" gradientTransform=\\\"matrix(.1853 .356 .6722 -.353 -90.66 993.802)\\\" gradientUnits=\\\"userSpaceOnUse\\\"><stop offset=\\\"0\\\" style=\\\"stop-color:#737373\\\"/><stop offset=\\\"1\\\" style=\\\"stop-color:#333\\\"/></linearGradient><path d=\\\"m466.5 78.6 38.2-20.5.1-39.5-38.3 20.7\\\" style=\\\"fill:url(#opnsense-v1_svg__a)\\\"/><linearGradient id=\\\"opnsense-v1_svg__b\\\" x1=\\\"4.634\\\" x2=\\\"50.301\\\" y1=\\\"462.6\\\" y2=\\\"462.6\\\" gradientTransform=\\\"matrix(1 0 0 -1 0 514)\\\" gradientUnits=\\\"userSpaceOnUse\\\"><stop offset=\\\"0\\\" style=\\\"stop-color:#737373\\\"/><stop offset=\\\"1\\\" style=\\\"stop-color:#333\\\"/></linearGradient><path d=\\\"M45.6 64.5v-8.9c0-5.3 2.6-10 6.5-13l-34-18.5c-6.7 9.5-10.8 21.3-10.9 34l38.3 20.6v-14c.1-.1.1-.2.1-.2\\\" style=\\\"fill:url(#opnsense-v1_svg__b)\\\"/><linearGradient id=\\\"opnsense-v1_svg__c\\\" x1=\\\"-1587.887\\\" x2=\\\"-1498.137\\\" y1=\\\"-2208.165\\\" y2=\\\"-2208.165\\\" gradientTransform=\\\"matrix(-.1853 -.356 -.6722 .353 -1743.836 694.098)\\\" gradientUnits=\\\"userSpaceOnUse\\\"><stop offset=\\\"0\\\" style=\\\"stop-color:#737373\\\"/><stop offset=\\\"1\\\" style=\\\"stop-color:#333\\\"/></linearGradient><path d=\\\"M45.6 472.7v-38.9L7.3 454.5v39.7l39.9-21.5z\\\" style=\\\"fill:url(#opnsense-v1_svg__c)\\\"/><linearGradient id=\\\"opnsense-v1_svg__d\\\" x1=\\\"460.439\\\" x2=\\\"504.436\\\" y1=\\\"53\\\" y2=\\\"53\\\" gradientTransform=\\\"matrix(1 0 0 -1 0 514)\\\" gradientUnits=\\\"userSpaceOnUse\\\"><stop offset=\\\"0\\\" style=\\\"stop-color:#737373\\\"/><stop offset=\\\"1\\\" style=\\\"stop-color:#333\\\"/></linearGradient><path d=\\\"M466.5 456.4c0 5.5-2.7 10.3-6.9 13.2l34.2 18.4c6.8-9.5 10.8-21.2 10.9-33.8L466.5 434v12.8\\\" style=\\\"fill:url(#opnsense-v1_svg__d)\\\"/><linearGradient id=\\\"opnsense-v1_svg__e\\\" x1=\\\"2521.902\\\" x2=\\\"2684.464\\\" y1=\\\"-2476.233\\\" y2=\\\"-2476.233\\\" gradientTransform=\\\"matrix(-1.508 -1.0166 -3.1265 -1.519 -3697.438 -937.952)\\\" gradientUnits=\\\"userSpaceOnUse\\\"><stop offset=\\\".107\\\" style=\\\"stop-color:#58595b\\\"/><stop offset=\\\"1\\\" style=\\\"stop-color:#000\\\"/></linearGradient><path d=\\\"M192.5 197v-.4l-72.4-38.9H45.9v.2l72.5 39.1z\\\" style=\\\"fill:url(#opnsense-v1_svg__e)\\\"/><linearGradient id=\\\"opnsense-v1_svg__f\\\" x1=\\\"-1594.953\\\" x2=\\\"-1432.391\\\" y1=\\\"278.458\\\" y2=\\\"278.458\\\" gradientTransform=\\\"matrix(1.508 -1.0166 3.1265 -1.519 1804.196 -937.952)\\\" gradientUnits=\\\"userSpaceOnUse\\\"><stop offset=\\\".107\\\" style=\\\"stop-color:#58595b\\\"/><stop offset=\\\"1\\\" style=\\\"stop-color:#000\\\"/></linearGradient><path d=\\\"M466.1 157.7h-74l-72.6 39.1v.2h74.2l72.4-38.9z\\\" style=\\\"fill:url(#opnsense-v1_svg__f)\\\"/><linearGradient id=\\\"opnsense-v1_svg__g\\\" x1=\\\"-4327.142\\\" x2=\\\"-4164.581\\\" y1=\\\"1572.387\\\" y2=\\\"1572.387\\\" gradientTransform=\\\"matrix(1.508 1.0166 3.1265 1.519 1863.938 2252.352)\\\" gradientUnits=\\\"userSpaceOnUse\\\"><stop offset=\\\".107\\\" style=\\\"stop-color:#58595b\\\"/><stop offset=\\\"1\\\" style=\\\"stop-color:#000\\\"/></linearGradient><path d=\\\"M466.1 354.7v-.4l-72.4-38.9h-74.2v.2l72.6 39.1z\\\" style=\\\"fill:url(#opnsense-v1_svg__g)\\\"/><linearGradient id=\\\"opnsense-v1_svg__h\\\" x1=\\\"-413.668\\\" x2=\\\"-246.99\\\" y1=\\\"-1046.618\\\" y2=\\\"-1046.618\\\" gradientTransform=\\\"matrix(-1.508 1.0166 -3.1265 1.519 -3638.692 2252.352)\\\" gradientUnits=\\\"userSpaceOnUse\\\"><stop offset=\\\".107\\\" style=\\\"stop-color:#58595b\\\"/><stop offset=\\\"1\\\" style=\\\"stop-color:#000\\\"/></linearGradient><path d=\\\"M192.5 315.4h-74.1l-72.5 39.1v.2h74.2l72.4-38.9z\\\" style=\\\"fill:url(#opnsense-v1_svg__h)\\\"/><linearGradient id=\\\"opnsense-v1_svg__i\\\" x1=\\\"74.725\\\" x2=\\\"261.062\\\" y1=\\\"132.65\\\" y2=\\\"132.65\\\" gradientTransform=\\\"matrix(.9914 0 0 -1 -66.782 260.2)\\\" gradientUnits=\\\"userSpaceOnUse\\\"><stop offset=\\\".237\\\" style=\\\"stop-color:#a6a8ab\\\"/><stop offset=\\\"1\\\" style=\\\"stop-color:#404040\\\"/></linearGradient><path d=\\\"M192.8 157.7V197L7.3 97.4V58.1z\\\" style=\\\"fill:url(#opnsense-v1_svg__i)\\\"/><linearGradient id=\\\"opnsense-v1_svg__j\\\" x1=\\\"-2461.056\\\" x2=\\\"-2274.718\\\" y1=\\\"132.65\\\" y2=\\\"132.65\\\" gradientTransform=\\\"matrix(-.9914 0 0 -1 -1935.19 260.2)\\\" gradientUnits=\\\"userSpaceOnUse\\\"><stop offset=\\\".237\\\" style=\\\"stop-color:#a6a8ab\\\"/><stop offset=\\\"1\\\" style=\\\"stop-color:#404040\\\"/></linearGradient><path d=\\\"M319.2 157.7V197l185.5-99.6V58.1z\\\" style=\\\"fill:url(#opnsense-v1_svg__j)\\\"/><linearGradient id=\\\"opnsense-v1_svg__k\\\" x1=\\\"-2291.845\\\" x2=\\\"-2105.508\\\" y1=\\\"-739.55\\\" y2=\\\"-739.55\\\" gradientTransform=\\\"matrix(-.9914 0 0 1 -1767.435 1124.6)\\\" gradientUnits=\\\"userSpaceOnUse\\\"><stop offset=\\\".237\\\" style=\\\"stop-color:#a6a8ab\\\"/><stop offset=\\\"1\\\" style=\\\"stop-color:#404040\\\"/></linearGradient><path d=\\\"M319.2 355.2v-39.8L504.7 415v39.7z\\\" style=\\\"fill:url(#opnsense-v1_svg__k)\\\"/><linearGradient id=\\\"opnsense-v1_svg__l\\\" x1=\\\"-94.103\\\" x2=\\\"91.934\\\" y1=\\\"-739.55\\\" y2=\\\"-739.55\\\" gradientTransform=\\\"matrix(.9914 0 0 1 100.993 1124.6)\\\" gradientUnits=\\\"userSpaceOnUse\\\"><stop offset=\\\".237\\\" style=\\\"stop-color:#a6a8ab\\\"/><stop offset=\\\"1\\\" style=\\\"stop-color:#404040\\\"/></linearGradient><path d=\\\"M192.8 355.2v-39.8L7.3 415v39.7z\\\" style=\\\"fill:url(#opnsense-v1_svg__l)\\\"/><path d=\\\"m319.2 276.1 108.9 58.5v-39.7l-35.3-18.8h111.9v-39.8H392.8l35.3-18.8v-39.3l-108.9 58.1zM83.8 334.6l109-58.5v-39.8l-109-58.1v39.3l35.7 18.8H7.3v39.8h111.9l-35.3 18.8v39.7z\\\" style=\\\"fill:#e24525\\\"/></svg>\",\"mdi-server-security\":\"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" id=\\\"mdi-server-security\\\" viewBox=\\\"0 0 24 24\\\"><path d=\\\"M3,1H19A1,1 0 0,1 20,2V6A1,1 0 0,1 19,7H3A1,1 0 0,1 2,6V2A1,1 0 0,1 3,1M3,9H19A1,1 0 0,1 20,10V10.67L17.5,9.56L11,12.44V15H3A1,1 0 0,1 2,14V10A1,1 0 0,1 3,9M3,17H11C11.06,19.25 12,21.4 13.46,23H3A1,1 0 0,1 2,22V18A1,1 0 0,1 3,17M8,5H9V3H8V5M8,13H9V11H8V13M8,21H9V19H8V21M4,3V5H6V3H4M4,11V13H6V11H4M4,19V21H6V19H4M17.5,12L22,14V17C22,19.78 20.08,22.37 17.5,23C14.92,22.37 13,19.78 13,17V14L17.5,12M17.5,13.94L15,15.06V17.72C15,19.26 16.07,20.7 17.5,21.06V13.94Z\\\" /></svg>\"},\"page\":{\"title\":\"The One File Corporate\",\"background\":\"\",\"topbarBg\":\"rgba(9, 12, 20, 0.9)\",\"topbarBorder\":\"#1f2533\",\"panel\":\"#0b0e13\",\"panelAlt\":\"#10141b\",\"accent\":\"#4fd1c5\",\"sidebarBg\":\"#10141b\",\"btnBg\":\"#0b0e13\",\"btnText\":\"#e2e8f0\",\"tagFill\":\"#1e293b\",\"tagText\":\"#e2e8f0\",\"tagBorder\":\"#475569\",\"inputBg\":\"#0b0e13\",\"inputText\":\"#e2e8f0\",\"inputBorder\":\"#1f2937\",\"inputFont\":\"Inter, system-ui, sans-serif\",\"inputFontSize\":14,\"toolbarBg\":\"#0f172a\",\"toolbarBorder\":\"#1f2937\",\"toolbarText\":\"#94a3b8\",\"toolbarBtnBg\":\"#0b0e13\",\"toolbarBtnText\":\"#e2e8f0\",\"minimapDots\":\"#94a3b8\",\"canvasHintEnabled\":true,\"canvasHintText\":\"\",\"canvasHintBg\":\"#0f172a\",\"canvasHintColor\":\"#94a3b8\",\"danger\":\"#f56565\",\"textMain\":\"#e2e8f0\",\"textSoft\":\"#94a3b8\",\"topbarHeight\":103,\"sidebarWidth\":350,\"mobileFooterHeight\":40,\"sidebarCollapsed\":false,\"nodeFill\":\"#1e293b\",\"nodeStroke\":\"#475569\",\"nodeTitle\":\"#e2e8f0\",\"nodeSub\":\"#94a3b8\",\"nodeTitleSize\":41,\"nodeSubSize\":27,\"nodeFont\":\"monospace\",\"defaultEdge\":\"#475569\",\"selectionHandle\":\"#f59e0b\",\"selectionHandleSize\":8,\"groupIndicator\":\"#4fd1c5\",\"canvasGradientTop\":\"#1e2532\",\"canvasGradientBottom\":\"#050608\",\"canvasBorder\":\"#475569\",\"canvasGrid\":\"#475569\",\"canvasGridSize\":50,\"canvasGridEnabled\":true,\"rackFrameFill\":\"#0f172a\",\"rackFrameStroke\":\"#4fd1c5\",\"rackLineColor\":\"#475569\",\"rackTextColor\":\"#4fd1c5\",\"rackGridEnabled\":true,\"viewOnly\":false,\"defaultEdgeRouting\":\"orthogonal\",\"animateConnections\":false,\"animationStyle\":\"arrows\",\"animationDirection\":\"all\",\"animationSpeed\":4,\"autoPingEnabled\":false,\"autoPingInterval\":30},\"autoPingEnabled\":false,\"autoPingInterval\":30,\"canvas\":{\"zoom\":0.9921985961590549,\"panX\":-5.584863670202822,\"panY\":-99.90831573327841},\"savedTopologyView\":{\"zoom\":0.9325110211947125,\"panX\":-563.7108933103631,\"panY\":-561.6887674556383},\"documentTabs\":[{\"id\":\"main\",\"name\":\"Corporate Site B\",\"nodes\":{\"core-router-1\":{\"shape\":\"router\",\"name\":\"Core Router 1\",\"ip\":\"10.0.0.1\",\"role\":\"Core Routing\",\"tags\":[\"core\",\"tier-1\",\"redundant\"],\"notes\":[\"Primary core router\",\"BGP peering enabled\"],\"mac\":\"00:1A:2B:3C:4D:01\",\"rackUnit\":\"\",\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"core-router-2\":{\"shape\":\"router\",\"name\":\"Core Router 2\",\"ip\":\"10.0.0.2\",\"role\":\"Core Routing\",\"tags\":[\"core\",\"tier-1\",\"redundant\"],\"notes\":[\"Secondary core router\",\"HSRP standby\"],\"mac\":\"00:1A:2B:3C:4D:02\",\"rackUnit\":\"\",\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null,\"ping\":{\"enabled\":true,\"protocol\":\"custom\",\"customUrl\":\"https://google.com\",\"timeout\":3000,\"status\":\"online\",\"lastCheck\":\"2025-12-09T00:15:04.343Z\"}},\"fw-external-1\":{\"shape\":\"firewall\",\"name\":\"External FW 1\",\"ip\":\"10.0.1.1\",\"role\":\"Perimeter Security\",\"tags\":[\"security\",\"perimeter\",\"ha-pair\"],\"notes\":[\"Palo Alto PA-5250\",\"Active node\"],\"mac\":\"00:1A:2B:3C:4D:10\",\"rackUnit\":\"\",\"uHeight\":\"2\",\"layer\":\"security\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"fw-external-2\":{\"shape\":\"firewall\",\"name\":\"External FW 2\",\"ip\":\"10.0.1.2\",\"role\":\"Perimeter Security\",\"tags\":[\"security\",\"perimeter\",\"ha-pair\"],\"notes\":[\"Palo Alto PA-5250\",\"Passive node\"],\"mac\":\"00:1A:2B:3C:4D:11\",\"rackUnit\":\"\",\"uHeight\":\"2\",\"layer\":\"security\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"fw-internal\":{\"shape\":\"firewall\",\"name\":\"Internal FW\",\"ip\":\"10.0.2.1\",\"role\":\"Internal Segmentation\",\"tags\":[\"security\",\"internal\"],\"notes\":[\"East-West traffic inspection\"],\"mac\":\"00:1A:2B:3C:4D:12\",\"rackUnit\":\"\",\"uHeight\":\"2\",\"layer\":\"security\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"core-switch-1\":{\"shape\":\"switch\",\"name\":\"Core Switch 1\",\"ip\":\"10.0.10.1\",\"role\":\"Core Switching\",\"tags\":[\"core\",\"layer3\",\"redundant\"],\"notes\":[\"Cisco Nexus 9000\",\"VPC Domain 1\"],\"mac\":\"00:1A:2B:3C:4D:20\",\"rackUnit\":\"\",\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"core-switch-2\":{\"shape\":\"switch\",\"name\":\"Core Switch 2\",\"ip\":\"10.0.10.2\",\"role\":\"Core Switching\",\"tags\":[\"core\",\"layer3\",\"redundant\"],\"notes\":[\"Cisco Nexus 9000\",\"VPC Domain 1\"],\"mac\":\"00:1A:2B:3C:4D:21\",\"rackUnit\":\"\",\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"dc-rack-a1\":{\"shape\":\"server\",\"name\":\"DC Rack A1\",\"ip\":\"10.10.0.0/24\",\"role\":\"Data Center Rack\",\"tags\":[\"datacenter\",\"row-a\",\"production\"],\"notes\":[\"Row A, Position 1\",\"Primary compute\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":true,\"locked\":false,\"groupId\":null},\"dc-rack-a2\":{\"shape\":\"server\",\"name\":\"DC Rack A2\",\"ip\":\"10.10.1.0/24\",\"role\":\"Data Center Rack\",\"tags\":[\"datacenter\",\"row-a\",\"production\"],\"notes\":[\"Row A, Position 2\",\"Primary compute\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":true,\"locked\":false,\"groupId\":null},\"dc-rack-b1\":{\"shape\":\"server\",\"name\":\"DC Rack B1\",\"ip\":\"10.10.2.0/24\",\"role\":\"Data Center Rack\",\"tags\":[\"datacenter\",\"row-b\",\"storage\"],\"notes\":[\"Row B, Position 1\",\"Storage systems\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":true,\"locked\":false,\"groupId\":null},\"dc-rack-b2\":{\"shape\":\"server\",\"name\":\"DC Rack B2\",\"ip\":\"10.10.3.0/24\",\"role\":\"Data Center Rack\",\"tags\":[\"datacenter\",\"row-b\",\"storage\"],\"notes\":[\"Row B, Position 2\",\"Storage systems\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":true,\"locked\":false,\"groupId\":null},\"dmz-rack\":{\"shape\":\"server\",\"name\":\"DMZ Rack\",\"ip\":\"172.16.0.0/24\",\"role\":\"DMZ Infrastructure\",\"tags\":[\"dmz\",\"security\",\"public-facing\",{\"type\":\"icon\",\"library\":\"selfhst\",\"name\":\"booklogr\"},{\"type\":\"icon\",\"library\":\"simple\",\"name\":\"gmail\"}],\"notes\":[\"Isolated DMZ zone\",\"Public-facing services\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"security\",\"assignedRack\":\"\",\"rackCapacity\":\"24\",\"isRack\":true,\"locked\":false,\"groupId\":null},\"mgmt-rack\":{\"shape\":\"server\",\"name\":\"Management Rack\",\"ip\":\"192.168.100.0/24\",\"role\":\"Management Infrastructure\",\"tags\":[\"management\",\"oob\",\"noc\"],\"notes\":[\"Out-of-band management\",\"NOC equipment\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"logical\",\"assignedRack\":\"\",\"rackCapacity\":\"24\",\"isRack\":true,\"locked\":false,\"groupId\":null},\"esxi-host-01\":{\"shape\":\"server\",\"name\":\"ESXi Host 01\",\"ip\":\"10.10.0.11\",\"role\":\"Hypervisor\",\"tags\":[\"vmware\",\"compute\",\"cluster-a\"],\"notes\":[\"Dell PowerEdge R750\",\"512GB RAM\",\"vSphere 8.0\"],\"mac\":\"00:50:56:AA:01:01\",\"rackUnit\":38,\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a1\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"esxi-host-02\":{\"shape\":\"server\",\"name\":\"ESXi Host 02\",\"ip\":\"10.10.0.12\",\"role\":\"Hypervisor\",\"tags\":[\"vmware\",\"compute\",\"cluster-a\"],\"notes\":[\"Dell PowerEdge R750\",\"512GB RAM\",\"vSphere 8.0\"],\"mac\":\"00:50:56:AA:01:02\",\"rackUnit\":35,\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a1\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"esxi-host-03\":{\"shape\":\"server\",\"name\":\"ESXi Host 03\",\"ip\":\"10.10.0.13\",\"role\":\"Hypervisor\",\"tags\":[\"vmware\",\"compute\",\"cluster-a\"],\"notes\":[\"Dell PowerEdge R750\",\"512GB RAM\",\"vSphere 8.0\"],\"mac\":\"00:50:56:AA:01:03\",\"rackUnit\":32,\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a1\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"esxi-host-04\":{\"shape\":\"server\",\"name\":\"ESXi Host 04\",\"ip\":\"10.10.0.14\",\"role\":\"Hypervisor\",\"tags\":[\"vmware\",\"compute\",\"cluster-a\"],\"notes\":[\"Dell PowerEdge R750\",\"512GB RAM\",\"vSphere 8.0\"],\"mac\":\"00:50:56:AA:01:04\",\"rackUnit\":29,\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a1\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"tor-switch-a1\":{\"shape\":\"switch\",\"name\":\"ToR Switch A1\",\"ip\":\"10.10.0.1\",\"role\":\"Top of Rack\",\"tags\":[\"tor\",\"access\",\"rack-a1\"],\"notes\":[\"Cisco Nexus 93180YC-FX\",\"48x25G ports\"],\"mac\":\"00:1A:2B:3C:5D:01\",\"rackUnit\":42,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a1\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"esxi-host-05\":{\"shape\":\"server\",\"name\":\"ESXi Host 05\",\"ip\":\"10.10.1.11\",\"role\":\"Hypervisor\",\"tags\":[\"vmware\",\"compute\",\"cluster-b\"],\"notes\":[\"Dell PowerEdge R750\",\"768GB RAM\",\"vSphere 8.0\"],\"mac\":\"00:50:56:AA:02:01\",\"rackUnit\":38,\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"esxi-host-06\":{\"shape\":\"server\",\"name\":\"ESXi Host 06\",\"ip\":\"10.10.1.12\",\"role\":\"Hypervisor\",\"tags\":[\"vmware\",\"compute\",\"cluster-b\"],\"notes\":[\"Dell PowerEdge R750\",\"768GB RAM\",\"vSphere 8.0\"],\"mac\":\"00:50:56:AA:02:02\",\"rackUnit\":35,\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"esxi-host-07\":{\"shape\":\"server\",\"name\":\"ESXi Host 07\",\"ip\":\"10.10.1.13\",\"role\":\"Hypervisor\",\"tags\":[\"vmware\",\"compute\",\"cluster-b\"],\"notes\":[\"Dell PowerEdge R750\",\"768GB RAM\",\"vSphere 8.0\"],\"mac\":\"00:50:56:AA:02:03\",\"rackUnit\":32,\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"esxi-host-08\":{\"shape\":\"server\",\"name\":\"ESXi Host 08\",\"ip\":\"10.10.1.14\",\"role\":\"Hypervisor\",\"tags\":[\"vmware\",\"compute\",\"cluster-b\"],\"notes\":[\"Dell PowerEdge R750\",\"768GB RAM\",\"vSphere 8.0\"],\"mac\":\"00:50:56:AA:02:04\",\"rackUnit\":29,\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"tor-switch-a2\":{\"shape\":\"switch\",\"name\":\"ToR Switch A2\",\"ip\":\"10.10.1.1\",\"role\":\"Top of Rack\",\"tags\":[\"tor\",\"access\",\"rack-a2\"],\"notes\":[\"Cisco Nexus 93180YC-FX\",\"48x25G ports\"],\"mac\":\"00:1A:2B:3C:5D:02\",\"rackUnit\":42,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"san-primary\":{\"shape\":\"database\",\"name\":\"SAN Primary\",\"ip\":\"10.10.2.10\",\"role\":\"Primary Storage\",\"tags\":[\"storage\",\"san\",\"netapp\"],\"notes\":[\"NetApp AFF A400\",\"500TB Raw\",\"FC 32Gb\"],\"mac\":\"00:A0:98:AA:01:01\",\"rackUnit\":36,\"uHeight\":\"6\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-b1\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"san-secondary\":{\"shape\":\"database\",\"name\":\"SAN Secondary\",\"ip\":\"10.10.2.11\",\"role\":\"Secondary Storage\",\"tags\":[\"storage\",\"san\",\"netapp\"],\"notes\":[\"NetApp AFF A400\",\"500TB Raw\",\"FC 32Gb\"],\"mac\":\"00:A0:98:AA:01:02\",\"rackUnit\":28,\"uHeight\":\"6\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-b1\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"fc-switch-1\":{\"shape\":\"switch\",\"name\":\"FC Switch 1\",\"ip\":\"10.10.2.1\",\"role\":\"Fibre Channel\",\"tags\":[\"storage\",\"fc\",\"fabric-a\"],\"notes\":[\"Brocade G620\",\"Fabric A\"],\"mac\":\"00:1A:2B:FC:01:01\",\"rackUnit\":42,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-b1\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"fc-switch-2\":{\"shape\":\"switch\",\"name\":\"FC Switch 2\",\"ip\":\"10.10.2.2\",\"role\":\"Fibre Channel\",\"tags\":[\"storage\",\"fc\",\"fabric-b\"],\"notes\":[\"Brocade G620\",\"Fabric B\"],\"mac\":\"00:1A:2B:FC:01:02\",\"rackUnit\":41,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-b1\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"backup-server-1\":{\"shape\":\"server\",\"name\":\"Backup Server 1\",\"ip\":\"10.10.3.10\",\"role\":\"Backup Infrastructure\",\"tags\":[\"backup\",\"veeam\",\"protection\"],\"notes\":[\"Veeam Backup Server\",\"Dell R740xd\",\"200TB\"],\"mac\":\"00:50:56:BB:01:01\",\"rackUnit\":36,\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-b2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"backup-server-2\":{\"shape\":\"server\",\"name\":\"Backup Server 2\",\"ip\":\"10.10.3.11\",\"role\":\"Backup Infrastructure\",\"tags\":[\"backup\",\"veeam\",\"protection\"],\"notes\":[\"Veeam Backup Server\",\"Dell R740xd\",\"200TB\"],\"mac\":\"00:50:56:BB:01:02\",\"rackUnit\":33,\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-b2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"tape-library\":{\"shape\":\"database\",\"name\":\"Tape Library\",\"ip\":\"10.10.3.20\",\"role\":\"Archival Storage\",\"tags\":[\"backup\",\"tape\",\"lto9\"],\"notes\":[\"IBM TS4500\",\"LTO-9\",\"Long-term archive\"],\"mac\":\"00:50:56:BB:02:01\",\"rackUnit\":20,\"uHeight\":\"10\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-b2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"tor-switch-b1\":{\"shape\":\"switch\",\"name\":\"ToR Switch B1\",\"ip\":\"10.10.2.3\",\"role\":\"Top of Rack\",\"tags\":[\"tor\",\"access\",\"rack-b1\"],\"notes\":[\"Cisco Nexus 93180YC-FX\"],\"mac\":\"00:1A:2B:3C:5D:03\",\"rackUnit\":40,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-b1\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"tor-switch-b2\":{\"shape\":\"switch\",\"name\":\"ToR Switch B2\",\"ip\":\"10.10.3.1\",\"role\":\"Top of Rack\",\"tags\":[\"tor\",\"access\",\"rack-b2\"],\"notes\":[\"Cisco Nexus 93180YC-FX\"],\"mac\":\"00:1A:2B:3C:5D:04\",\"rackUnit\":42,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-b2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"web-server-1\":{\"shape\":\"server\",\"name\":\"Web Server 1\",\"ip\":\"172.16.0.11\",\"role\":\"Web Frontend\",\"tags\":[\"dmz\",\"web\",\"nginx\"],\"notes\":[\"NGINX reverse proxy\",\"Public facing\"],\"mac\":\"00:50:56:CC:01:01\",\"rackUnit\":20,\"uHeight\":\"1\",\"layer\":\"security\",\"assignedRack\":\"dmz-rack\",\"rackCapacity\":\"24\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"web-server-2\":{\"shape\":\"server\",\"name\":\"Web Server 2\",\"ip\":\"172.16.0.12\",\"role\":\"Web Frontend\",\"tags\":[\"dmz\",\"web\",\"nginx\"],\"notes\":[\"NGINX reverse proxy\",\"Public facing\"],\"mac\":\"00:50:56:CC:01:02\",\"rackUnit\":18,\"uHeight\":\"1\",\"layer\":\"security\",\"assignedRack\":\"dmz-rack\",\"rackCapacity\":\"24\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"waf-1\":{\"shape\":\"firewall\",\"name\":\"WAF Appliance\",\"ip\":\"172.16.0.5\",\"role\":\"Web Application Firewall\",\"tags\":[\"dmz\",\"security\",\"waf\"],\"notes\":[\"F5 BIG-IP ASM\",\"OWASP protection\"],\"mac\":\"00:50:56:CC:02:01\",\"rackUnit\":22,\"uHeight\":\"2\",\"layer\":\"security\",\"assignedRack\":\"dmz-rack\",\"rackCapacity\":\"24\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"load-balancer-dmz\":{\"shape\":\"switch\",\"name\":\"DMZ Load Balancer\",\"ip\":\"172.16.0.3\",\"role\":\"Load Balancing\",\"tags\":[\"dmz\",\"lb\",\"f5\"],\"notes\":[\"F5 BIG-IP LTM\",\"VIP: 172.16.0.100\"],\"mac\":\"00:50:56:CC:03:01\",\"rackUnit\":16,\"uHeight\":\"2\",\"layer\":\"security\",\"assignedRack\":\"dmz-rack\",\"rackCapacity\":\"24\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"mail-gateway\":{\"shape\":\"server\",\"name\":\"Mail Gateway\",\"ip\":\"172.16.0.25\",\"role\":\"Email Security\",\"tags\":[\"dmz\",\"email\",\"security\"],\"notes\":[\"Proofpoint Email Gateway\",\"Spam/malware filtering\"],\"mac\":\"00:50:56:CC:04:01\",\"rackUnit\":14,\"uHeight\":\"1\",\"layer\":\"security\",\"assignedRack\":\"dmz-rack\",\"rackCapacity\":\"24\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"dns-external-1\":{\"shape\":\"circle\",\"name\":\"External DNS 1\",\"ip\":\"172.16.0.53\",\"role\":\"External DNS\",\"tags\":[\"dmz\",\"dns\",\"public\"],\"notes\":[\"BIND DNS\",\"Authoritative for corp.com\"],\"mac\":\"00:50:56:CC:05:01\",\"rackUnit\":12,\"uHeight\":\"1\",\"layer\":\"security\",\"assignedRack\":\"dmz-rack\",\"rackCapacity\":\"24\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"dns-external-2\":{\"shape\":\"circle\",\"name\":\"External DNS 2\",\"ip\":\"172.16.0.54\",\"role\":\"External DNS\",\"tags\":[\"dmz\",\"dns\",\"public\"],\"notes\":[\"BIND DNS\",\"Secondary for corp.com\"],\"mac\":\"00:50:56:CC:05:02\",\"rackUnit\":10,\"uHeight\":\"1\",\"layer\":\"security\",\"assignedRack\":\"dmz-rack\",\"rackCapacity\":\"24\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"vcenter\":{\"shape\":\"server\",\"name\":\"vCenter Server\",\"ip\":\"192.168.100.10\",\"role\":\"Virtualization Management\",\"tags\":[\"management\",\"vmware\",\"vcsa\"],\"notes\":[\"vCenter Server Appliance 8.0\",\"Single SSO domain\"],\"mac\":\"00:50:56:DD:01:01\",\"rackUnit\":20,\"uHeight\":\"2\",\"layer\":\"logical\",\"assignedRack\":\"mgmt-rack\",\"rackCapacity\":\"24\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"nsx-manager\":{\"shape\":\"server\",\"name\":\"NSX Manager\",\"ip\":\"192.168.100.15\",\"role\":\"Network Virtualization\",\"tags\":[\"management\",\"vmware\",\"nsx\"],\"notes\":[\"NSX-T 4.1 Manager Cluster\"],\"mac\":\"00:50:56:DD:02:01\",\"rackUnit\":17,\"uHeight\":\"2\",\"layer\":\"logical\",\"assignedRack\":\"mgmt-rack\",\"rackCapacity\":\"24\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"siem-server\":{\"shape\":\"server\",\"name\":\"SIEM Server\",\"ip\":\"192.168.100.50\",\"role\":\"Security Monitoring\",\"tags\":[\"management\",\"security\",\"splunk\"],\"notes\":[\"Splunk Enterprise\",\"Security monitoring\"],\"mac\":\"00:50:56:DD:03:01\",\"rackUnit\":14,\"uHeight\":\"2\",\"layer\":\"logical\",\"assignedRack\":\"mgmt-rack\",\"rackCapacity\":\"24\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"nms-server\":{\"shape\":\"server\",\"name\":\"Network Monitoring\",\"ip\":\"192.168.100.60\",\"role\":\"Network Management\",\"tags\":[\"management\",\"monitoring\",\"prtg\"],\"notes\":[\"PRTG Network Monitor\",\"5000 sensors\"],\"mac\":\"00:50:56:DD:04:01\",\"rackUnit\":11,\"uHeight\":\"1\",\"layer\":\"logical\",\"assignedRack\":\"mgmt-rack\",\"rackCapacity\":\"24\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"jump-server\":{\"shape\":\"server\",\"name\":\"Jump Server\",\"ip\":\"192.168.100.100\",\"role\":\"Bastion Host\",\"tags\":[\"management\",\"security\",\"bastion\"],\"notes\":[\"Windows Server 2022\",\"MFA enabled\"],\"mac\":\"00:50:56:DD:05:01\",\"rackUnit\":9,\"uHeight\":\"1\",\"layer\":\"logical\",\"assignedRack\":\"mgmt-rack\",\"rackCapacity\":\"24\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"ipam-server\":{\"shape\":\"server\",\"name\":\"IPAM/DDI\",\"ip\":\"192.168.100.70\",\"role\":\"IP Management\",\"tags\":[\"management\",\"dns\",\"dhcp\"],\"notes\":[\"Infoblox DDI\",\"DNS/DHCP/IPAM\"],\"mac\":\"00:50:56:DD:06:01\",\"rackUnit\":7,\"uHeight\":\"2\",\"layer\":\"logical\",\"assignedRack\":\"mgmt-rack\",\"rackCapacity\":\"24\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"wlc-primary\":{\"shape\":\"wifi\",\"name\":\"WLC Primary\",\"ip\":\"10.20.0.1\",\"role\":\"Wireless Controller\",\"tags\":[\"wireless\",\"cisco\",\"9800\"],\"notes\":[\"Cisco C9800-40\",\"Primary controller\"],\"mac\":\"00:1A:2B:WL:01:01\",\"rackUnit\":\"\",\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"wlc-secondary\":{\"shape\":\"wifi\",\"name\":\"WLC Secondary\",\"ip\":\"10.20.0.2\",\"role\":\"Wireless Controller\",\"tags\":[\"wireless\",\"cisco\",\"9800\"],\"notes\":[\"Cisco C9800-40\",\"HA Secondary\"],\"mac\":\"00:1A:2B:WL:01:02\",\"rackUnit\":\"\",\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"mobile-zone-hq\":{\"shape\":\"phone\",\"name\":\"HQ Mobile Zone\",\"ip\":\"10.20.10.0/24\",\"role\":\"Mobile Device Zone\",\"tags\":[\"wireless\",\"byod\",\"mobile\"],\"notes\":[\"Corporate BYOD\",\"MDM enrolled devices\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"mobile-zone-guest\":{\"shape\":\"phone\",\"name\":\"Guest WiFi Zone\",\"ip\":\"10.30.0.0/24\",\"role\":\"Guest Network\",\"tags\":[\"wireless\",\"guest\",\"isolated\"],\"notes\":[\"Captive portal\",\"Internet only\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"mobile-zone-iot\":{\"shape\":\"phone\",\"name\":\"IoT Device Zone\",\"ip\":\"10.40.0.0/24\",\"role\":\"IoT Network\",\"tags\":[\"wireless\",\"iot\",\"building\"],\"notes\":[\"Building automation\",\"Smart devices\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"branch-router-ny\":{\"shape\":\"router\",\"name\":\"NYC Branch Router\",\"ip\":\"10.100.0.1\",\"role\":\"Branch Gateway\",\"tags\":[\"branch\",\"nyc\",\"sd-wan\"],\"notes\":[\"Cisco Viptela vEdge\",\"SD-WAN enabled\"],\"mac\":\"00:1A:2B:BR:01:01\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"branch-router-la\":{\"shape\":\"router\",\"name\":\"LA Branch Router\",\"ip\":\"10.101.0.1\",\"role\":\"Branch Gateway\",\"tags\":[\"branch\",\"la\",\"sd-wan\"],\"notes\":[\"Cisco Viptela vEdge\",\"SD-WAN enabled\"],\"mac\":\"00:1A:2B:BR:02:01\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"branch-router-chi\":{\"shape\":\"router\",\"name\":\"Chicago Branch Router\",\"ip\":\"10.102.0.1\",\"role\":\"Branch Gateway\",\"tags\":[\"branch\",\"chicago\",\"sd-wan\"],\"notes\":[\"Cisco Viptela vEdge\",\"SD-WAN enabled\"],\"mac\":\"00:1A:2B:BR:03:01\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"branch-router-lon\":{\"shape\":\"router\",\"name\":\"London Branch Router\",\"ip\":\"10.200.0.1\",\"role\":\"Branch Gateway\",\"tags\":[\"branch\",\"london\",\"sd-wan\"],\"notes\":[\"Cisco Viptela vEdge\",\"EMEA region\"],\"mac\":\"00:1A:2B:BR:04:01\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"branch-router-tokyo\":{\"shape\":\"router\",\"name\":\"Tokyo Branch Router\",\"ip\":\"10.201.0.1\",\"role\":\"Branch Gateway\",\"tags\":[\"branch\",\"tokyo\",\"sd-wan\"],\"notes\":[\"Cisco Viptela vEdge\",\"APAC region\"],\"mac\":\"00:1A:2B:BR:05:01\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"cloud-aws\":{\"shape\":\"cloud\",\"name\":\"AWS Cloud\",\"ip\":\"vpc-0a1b2c3d\",\"role\":\"Public Cloud\",\"tags\":[\"cloud\",\"aws\",\"hybrid\"],\"notes\":[\"AWS US-East-1\",\"VPC peering to HQ\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"logical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"cloud-azure\":{\"shape\":\"cloud\",\"name\":\"Azure Cloud\",\"ip\":\"vnet-corp-prod\",\"role\":\"Public Cloud\",\"tags\":[\"cloud\",\"azure\",\"hybrid\"],\"notes\":[\"Azure East US 2\",\"ExpressRoute\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"logical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"cloud-gcp\":{\"shape\":\"cloud\",\"name\":\"GCP Cloud\",\"ip\":\"vpc-gcp-corp\",\"role\":\"Public Cloud\",\"tags\":[\"cloud\",\"gcp\",\"dev\"],\"notes\":[\"GCP us-central1\",\"Dev/Test workloads\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"logical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"isp-primary\":{\"shape\":\"globe\",\"name\":\"ISP Primary\",\"ip\":\"203.0.113.1\",\"role\":\"Internet Uplink\",\"tags\":[\"wan\",\"internet\",\"primary\"],\"notes\":[\"AT&T MPLS\",\"1 Gbps dedicated\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"isp-secondary\":{\"shape\":\"globe\",\"name\":\"ISP Secondary\",\"ip\":\"198.51.100.1\",\"role\":\"Internet Uplink\",\"tags\":[\"wan\",\"internet\",\"backup\"],\"notes\":[\"Verizon Business\",\"500 Mbps backup\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"dc-internal-1\":{\"shape\":\"circle\",\"name\":\"DC1 Int DNS\",\"ip\":\"10.10.0.53\",\"role\":\"Internal DNS/AD\",\"tags\":[\"dns\",\"ad\",\"dc1\"],\"notes\":[\"Windows Server 2022\",\"Primary DC\"],\"mac\":\"00:50:56:AD:01:01\",\"rackUnit\":26,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a1\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"dc-internal-2\":{\"shape\":\"circle\",\"name\":\"DC2 Int DNS\",\"ip\":\"10.10.1.53\",\"role\":\"Internal DNS/AD\",\"tags\":[\"dns\",\"ad\",\"dc2\"],\"notes\":[\"Windows Server 2022\",\"Secondary DC\"],\"mac\":\"00:50:56:AD:01:02\",\"rackUnit\":26,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"app-server-1\":{\"shape\":\"server\",\"name\":\"App Server 01\",\"ip\":\"10.10.0.101\",\"role\":\"Application\",\"tags\":[\"app\",\"iis\",\"web\"],\"notes\":[\"Windows Server 2022\",\"IIS Application\"],\"mac\":\"00:50:56:AP:01:01\",\"rackUnit\":24,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a1\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"app-server-2\":{\"shape\":\"server\",\"name\":\"App Server 02\",\"ip\":\"10.10.0.102\",\"role\":\"Application\",\"tags\":[\"app\",\"iis\",\"web\"],\"notes\":[\"Windows Server 2022\",\"IIS Application\"],\"mac\":\"00:50:56:AP:01:02\",\"rackUnit\":22,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a1\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"db-server-1\":{\"shape\":\"database\",\"name\":\"SQL Server 01\",\"ip\":\"10.10.0.201\",\"role\":\"Database\",\"tags\":[\"db\",\"sql\",\"primary\"],\"notes\":[\"SQL Server 2022 Enterprise\",\"AlwaysOn Primary\"],\"mac\":\"00:50:56:DB:01:01\",\"rackUnit\":20,\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a1\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"db-server-2\":{\"shape\":\"database\",\"name\":\"SQL Server 02\",\"ip\":\"10.10.1.201\",\"role\":\"Database\",\"tags\":[\"db\",\"sql\",\"secondary\"],\"notes\":[\"SQL Server 2022 Enterprise\",\"AlwaysOn Secondary\"],\"mac\":\"00:50:56:DB:01:02\",\"rackUnit\":24,\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"k8s-master-1\":{\"shape\":\"hexagon\",\"name\":\"K8s Master 1\",\"ip\":\"10.10.1.50\",\"role\":\"Container Orchestration\",\"tags\":[\"kubernetes\",\"master\",\"container\"],\"notes\":[\"K8s Control Plane\",\"etcd member\"],\"mac\":\"00:50:56:K8:01:01\",\"rackUnit\":21,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"k8s-master-2\":{\"shape\":\"hexagon\",\"name\":\"K8s Master 2\",\"ip\":\"10.10.1.51\",\"role\":\"Container Orchestration\",\"tags\":[\"kubernetes\",\"master\",\"container\"],\"notes\":[\"K8s Control Plane\",\"etcd member\"],\"mac\":\"00:50:56:K8:01:02\",\"rackUnit\":19,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"k8s-master-3\":{\"shape\":\"hexagon\",\"name\":\"K8s Master 3\",\"ip\":\"10.10.1.52\",\"role\":\"Container Orchestration\",\"tags\":[\"kubernetes\",\"master\",\"container\"],\"notes\":[\"K8s Control Plane\",\"etcd member\"],\"mac\":\"00:50:56:K8:01:03\",\"rackUnit\":17,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"k8s-worker-1\":{\"shape\":\"server\",\"name\":\"K8s Worker 1\",\"ip\":\"10.10.1.60\",\"role\":\"Container Workload\",\"tags\":[\"kubernetes\",\"worker\",\"container\"],\"notes\":[\"K8s Worker Node\",\"64GB RAM\"],\"mac\":\"00:50:56:K8:02:01\",\"rackUnit\":15,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"k8s-worker-2\":{\"shape\":\"server\",\"name\":\"K8s Worker 2\",\"ip\":\"10.10.1.61\",\"role\":\"Container Workload\",\"tags\":[\"kubernetes\",\"worker\",\"container\"],\"notes\":[\"K8s Worker Node\",\"64GB RAM\"],\"mac\":\"00:50:56:K8:02:02\",\"rackUnit\":13,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"k8s-worker-3\":{\"shape\":\"server\",\"name\":\"K8s Worker 3\",\"ip\":\"10.10.1.62\",\"role\":\"Container Workload\",\"tags\":[\"kubernetes\",\"worker\",\"container\"],\"notes\":[\"K8s Worker Node\",\"64GB RAM\"],\"mac\":\"00:50:56:K8:02:03\",\"rackUnit\":11,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"k8s-worker-4\":{\"shape\":\"server\",\"name\":\"K8s Worker 4\",\"ip\":\"10.10.1.63\",\"role\":\"Container Workload\",\"tags\":[\"kubernetes\",\"worker\",\"container\"],\"notes\":[\"K8s Worker Node\",\"64GB RAM\"],\"mac\":\"00:50:56:K8:02:04\",\"rackUnit\":9,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"proxy-server-1\":{\"shape\":\"server\",\"name\":\"Proxy Server 1\",\"ip\":\"10.5.0.10\",\"role\":\"Web Proxy\",\"tags\":[\"proxy\",\"squid\",\"filtering\"],\"notes\":[\"Squid Proxy\",\"Content filtering\"],\"mac\":\"00:50:56:PX:01:01\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"security\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"proxy-server-2\":{\"shape\":\"server\",\"name\":\"Proxy Server 2\",\"ip\":\"10.5.0.11\",\"role\":\"Web Proxy\",\"tags\":[\"proxy\",\"squid\",\"filtering\"],\"notes\":[\"Squid Proxy\",\"HA pair\"],\"mac\":\"00:50:56:PX:01:02\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"security\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"vpn-concentrator\":{\"shape\":\"firewall\",\"name\":\"VPN Concentrator\",\"ip\":\"10.0.5.1\",\"role\":\"Remote Access VPN\",\"tags\":[\"vpn\",\"remote\",\"security\"],\"notes\":[\"Cisco ASA 5555-X\",\"AnyConnect SSL VPN\"],\"mac\":\"00:1A:2B:VP:01:01\",\"rackUnit\":\"\",\"uHeight\":\"2\",\"layer\":\"security\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"nac-server\":{\"shape\":\"server\",\"name\":\"NAC Server\",\"ip\":\"10.5.5.10\",\"role\":\"Network Access Control\",\"tags\":[\"nac\",\"ise\",\"802.1x\"],\"notes\":[\"Cisco ISE 3.1\",\"RADIUS/TACACS+\"],\"mac\":\"00:50:56:NA:01:01\",\"rackUnit\":\"\",\"uHeight\":\"2\",\"layer\":\"security\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"print-server\":{\"shape\":\"server\",\"name\":\"Print Server\",\"ip\":\"10.10.0.150\",\"role\":\"Print Services\",\"tags\":[\"print\",\"windows\",\"services\"],\"notes\":[\"Windows Print Server\",\"50+ printers\"],\"mac\":\"00:50:56:PR:01:01\",\"rackUnit\":18,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a1\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"file-server\":{\"shape\":\"database\",\"name\":\"File Server\",\"ip\":\"10.10.0.160\",\"role\":\"File Services\",\"tags\":[\"file\",\"smb\",\"dfs\"],\"notes\":[\"Windows File Server\",\"DFS namespace\"],\"mac\":\"00:50:56:FS:01:01\",\"rackUnit\":16,\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a1\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"ca-server\":{\"shape\":\"server\",\"name\":\"Certificate Authority\",\"ip\":\"192.168.100.80\",\"role\":\"PKI Infrastructure\",\"tags\":[\"pki\",\"ca\",\"security\"],\"notes\":[\"Windows CA\",\"Enterprise Root CA\"],\"mac\":\"00:50:56:CA:01:01\",\"rackUnit\":5,\"uHeight\":\"1\",\"layer\":\"logical\",\"assignedRack\":\"mgmt-rack\",\"rackCapacity\":\"24\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"sccm-server\":{\"shape\":\"server\",\"name\":\"SCCM Server\",\"ip\":\"192.168.100.90\",\"role\":\"Endpoint Management\",\"tags\":[\"sccm\",\"patching\",\"software\"],\"notes\":[\"MECM Primary Site\",\"Software deployment\"],\"mac\":\"00:50:56:SC:01:01\",\"rackUnit\":3,\"uHeight\":\"2\",\"layer\":\"logical\",\"assignedRack\":\"mgmt-rack\",\"rackCapacity\":\"24\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"voip-cluster\":{\"shape\":\"phone\",\"name\":\"VoIP Cluster\",\"ip\":\"10.50.0.0/24\",\"role\":\"Voice Services\",\"tags\":[\"voip\",\"cisco\",\"ucm\"],\"notes\":[\"Cisco UCM Cluster\",\"3000 endpoints\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"application\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"video-conf\":{\"shape\":\"laptop\",\"name\":\"Video Conference\",\"ip\":\"10.51.0.0/24\",\"role\":\"Video Services\",\"tags\":[\"video\",\"webex\",\"teams\"],\"notes\":[\"Webex/Teams integration\",\"Meeting rooms\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"application\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"security-cameras\":{\"shape\":\"camera\",\"name\":\"Security Cameras\",\"ip\":\"10.60.0.0/24\",\"role\":\"Physical Security\",\"tags\":[\"cctv\",\"surveillance\",\"security\"],\"notes\":[\"150+ IP cameras\",\"30-day retention\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"nvr-cluster\":{\"shape\":\"server\",\"name\":\"NVR Cluster\",\"ip\":\"10.60.0.10\",\"role\":\"Video Recording\",\"tags\":[\"nvr\",\"surveillance\",\"storage\"],\"notes\":[\"Milestone XProtect\",\"500TB storage\"],\"mac\":\"00:50:56:NV:01:01\",\"rackUnit\":15,\"uHeight\":\"4\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-b2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"dev-server-1\":{\"shape\":\"server\",\"name\":\"Dev Server 1\",\"ip\":\"10.80.0.10\",\"role\":\"Development\",\"tags\":[\"dev\",\"gitlab\",\"ci-cd\",{\"type\":\"icon\",\"library\":\"selfhst\",\"name\":\"dokku\"}],\"notes\":[\"GitLab Server\",\"CI/CD pipelines\"],\"mac\":\"00:50:56:DV:01:01\",\"rackUnit\":\"\",\"uHeight\":\"2\",\"layer\":\"application\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"dev-server-2\":{\"shape\":\"server\",\"name\":\"Dev Server 2\",\"ip\":\"10.80.0.11\",\"role\":\"Development\",\"tags\":[\"dev\",\"jenkins\",\"ci-cd\"],\"notes\":[\"Jenkins Server\",\"Build automation\"],\"mac\":\"00:50:56:DV:01:02\",\"rackUnit\":\"\",\"uHeight\":\"2\",\"layer\":\"application\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"test-environment\":{\"shape\":\"hexagon\",\"name\":\"Test Environment\",\"ip\":\"10.81.0.0/24\",\"role\":\"QA/Testing\",\"tags\":[\"test\",\"qa\",\"staging\"],\"notes\":[\"Staging environment\",\"Pre-prod validation\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"application\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"erp-system\":{\"shape\":\"database\",\"name\":\"ERP System\",\"ip\":\"10.90.0.10\",\"role\":\"Business Application\",\"tags\":[\"erp\",\"sap\",\"business\"],\"notes\":[\"SAP S/4HANA\",\"Financial/HR systems\"],\"mac\":\"00:50:56:ER:01:01\",\"rackUnit\":\"\",\"uHeight\":\"4\",\"layer\":\"application\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"crm-system\":{\"shape\":\"database\",\"name\":\"CRM System\",\"ip\":\"10.91.0.10\",\"role\":\"Business Application\",\"tags\":[\"crm\",\"salesforce\",\"business\"],\"notes\":[\"Salesforce integration\",\"Sales/Marketing\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"application\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"endpoint-1000\":{\"shape\":\"laptop\",\"name\":\"Corporate Endpoints\",\"ip\":\"10.70.0.0/22\",\"role\":\"User Workstations\",\"tags\":[\"endpoints\",\"workstations\",\"users\"],\"notes\":[\"~1000 corporate laptops\",\"Windows 11\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"dist-switch-floor1\":{\"shape\":\"switch\",\"name\":\"Floor 1 Switch\",\"ip\":\"10.1.1.1\",\"role\":\"Distribution\",\"tags\":[\"distribution\",\"floor-1\",\"access\"],\"notes\":[\"Cisco C9300-48P\",\"PoE+ enabled\"],\"mac\":\"00:1A:2B:FL:01:01\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"dist-switch-floor2\":{\"shape\":\"switch\",\"name\":\"Floor 2 Switch\",\"ip\":\"10.1.2.1\",\"role\":\"Distribution\",\"tags\":[\"distribution\",\"floor-2\",\"access\"],\"notes\":[\"Cisco C9300-48P\",\"PoE+ enabled\"],\"mac\":\"00:1A:2B:FL:02:01\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"dist-switch-floor3\":{\"shape\":\"switch\",\"name\":\"Floor 3 Switch\",\"ip\":\"10.1.3.1\",\"role\":\"Distribution\",\"tags\":[\"distribution\",\"floor-3\",\"access\"],\"notes\":[\"Cisco C9300-48P\",\"PoE+ enabled\"],\"mac\":\"00:1A:2B:FL:03:01\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"dist-switch-floor4\":{\"shape\":\"switch\",\"name\":\"Floor 4 Switch\",\"ip\":\"10.1.4.1\",\"role\":\"Distribution\",\"tags\":[\"distribution\",\"floor-4\",\"access\"],\"notes\":[\"Cisco C9300-48P\",\"PoE+ enabled\"],\"mac\":\"00:1A:2B:FL:04:01\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"ap-floor1-zone1\":{\"shape\":\"wifi\",\"name\":\"AP Floor 1 Zone 1\",\"ip\":\"10.20.1.10\",\"role\":\"Wireless Access\",\"tags\":[\"wifi\",\"ap\",\"floor-1\"],\"notes\":[\"Cisco 9120AX\",\"Wi-Fi 6\"],\"mac\":\"00:1A:2B:AP:01:01\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"ap-floor2-zone1\":{\"shape\":\"wifi\",\"name\":\"AP Floor 2 Zone 1\",\"ip\":\"10.20.2.10\",\"role\":\"Wireless Access\",\"tags\":[\"wifi\",\"ap\",\"floor-2\"],\"notes\":[\"Cisco 9120AX\",\"Wi-Fi 6\"],\"mac\":\"00:1A:2B:AP:02:01\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"ap-floor3-zone1\":{\"shape\":\"wifi\",\"name\":\"AP Floor 3 Zone 1\",\"ip\":\"10.20.3.10\",\"role\":\"Wireless Access\",\"tags\":[\"wifi\",\"ap\",\"floor-3\"],\"notes\":[\"Cisco 9120AX\",\"Wi-Fi 6\"],\"mac\":\"00:1A:2B:AP:03:01\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"ap-floor4-zone1\":{\"shape\":\"wifi\",\"name\":\"AP Floor 4 Zone 1\",\"ip\":\"10.20.4.10\",\"role\":\"Wireless Access\",\"tags\":[\"wifi\",\"ap\",\"floor-4\"],\"notes\":[\"Cisco 9120AX\",\"Wi-Fi 6\"],\"mac\":\"00:1A:2B:AP:04:01\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"ups-dc-1\":{\"shape\":\"rectangle\",\"name\":\"UPS DC-1\",\"ip\":\"192.168.200.10\",\"role\":\"Power Management\",\"tags\":[\"power\",\"ups\",\"datacenter\"],\"notes\":[\"APC Symmetra\",\"80kVA\",\"30 min runtime\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"ups-dc-2\":{\"shape\":\"rectangle\",\"name\":\"UPS DC-2\",\"ip\":\"192.168.200.11\",\"role\":\"Power Management\",\"tags\":[\"power\",\"ups\",\"datacenter\"],\"notes\":[\"APC Symmetra\",\"80kVA\",\"Redundant\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"pdu-rack-a1\":{\"shape\":\"rectangle\",\"name\":\"PDU Rack A1\",\"ip\":\"192.168.200.21\",\"role\":\"Power Distribution\",\"tags\":[\"power\",\"pdu\",\"rack-a1\"],\"notes\":[\"APC Switched PDU\",\"Per-outlet metering\"],\"mac\":\"\",\"rackUnit\":1,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a1\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"pdu-rack-a2\":{\"shape\":\"rectangle\",\"name\":\"PDU Rack A2\",\"ip\":\"192.168.200.22\",\"role\":\"Power Distribution\",\"tags\":[\"power\",\"pdu\",\"rack-a2\"],\"notes\":[\"APC Switched PDU\",\"Per-outlet metering\"],\"mac\":\"\",\"rackUnit\":1,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"cooling-1\":{\"shape\":\"rectangle\",\"name\":\"CRAC Unit 1\",\"ip\":\"192.168.200.30\",\"role\":\"Cooling\",\"tags\":[\"cooling\",\"hvac\",\"datacenter\"],\"notes\":[\"Liebert CRV\",\"Row-based cooling\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"cooling-2\":{\"shape\":\"rectangle\",\"name\":\"CRAC Unit 2\",\"ip\":\"192.168.200.31\",\"role\":\"Cooling\",\"tags\":[\"cooling\",\"hvac\",\"datacenter\"],\"notes\":[\"Liebert CRV\",\"N+1 redundancy\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"camera-a\":{\"shape\":\"camera\",\"name\":\"camera A\",\"ip\":\"0.0.0.0\",\"role\":\"\",\"tags\":[],\"notes\":[],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"ping\":{\"enabled\":false,\"protocol\":\"http\",\"customUrl\":\"\",\"timeout\":3000,\"status\":\"unknown\",\"lastCheck\":null},\"locked\":false,\"groupId\":null,\"fovEnabled\":true,\"fovRotation\":104,\"fovDistance\":500,\"fovSweep\":60,\"fovSpeed\":10,\"fovAnimate\":true},\"camera-a-copy\":{\"shape\":\"camera\",\"name\":\"camera B\",\"ip\":\"0.0.0.0\",\"role\":\"\",\"tags\":[],\"notes\":[],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"ping\":{\"enabled\":false,\"protocol\":\"http\",\"customUrl\":\"\",\"timeout\":3000,\"status\":\"unknown\",\"lastCheck\":null},\"locked\":false,\"groupId\":null,\"fovEnabled\":true,\"fovRotation\":162,\"fovDistance\":500,\"fovSweep\":60,\"fovSpeed\":10,\"fovAnimate\":false}},\"edges\":{\"list\":[{\"id\":\"isp1-router1\",\"from\":\"isp-primary\",\"to\":\"core-router-1\",\"width\":6,\"color\":\"#10b981\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"Primary WAN link\"],\"fromPort\":\"Gi0/0\",\"toPort\":\"Gi1/0/1\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"isp2-router2\",\"from\":\"isp-secondary\",\"to\":\"core-router-2\",\"width\":6,\"color\":\"#10b981\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"Backup WAN link\"],\"fromPort\":\"Gi0/0\",\"toPort\":\"Gi1/0/1\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"router1-router2\",\"from\":\"core-router-1\",\"to\":\"core-router-2\",\"width\":4,\"color\":\"#f59e0b\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"HSRP Peering\"],\"fromPort\":\"Gi1/0/24\",\"toPort\":\"Gi1/0/24\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"router1-fw1\",\"from\":\"core-router-1\",\"to\":\"fw-external-1\",\"width\":4,\"color\":\"#ef4444\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"router2-fw2\",\"from\":\"core-router-2\",\"to\":\"fw-external-2\",\"width\":4,\"color\":\"#ef4444\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"fw1-fw2\",\"from\":\"fw-external-1\",\"to\":\"fw-external-2\",\"width\":3,\"color\":\"#f59e0b\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"HA heartbeat\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"dashed\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"fw1-coresw1\",\"from\":\"fw-external-1\",\"to\":\"core-switch-1\",\"width\":4,\"color\":\"#475569\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"fw2-coresw2\",\"from\":\"fw-external-2\",\"to\":\"core-switch-2\",\"width\":4,\"color\":\"#475569\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw1-coresw2\",\"from\":\"core-switch-1\",\"to\":\"core-switch-2\",\"width\":5,\"color\":\"#3b82f6\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"VPC peer-link\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw1-fwint\",\"from\":\"core-switch-1\",\"to\":\"fw-internal\",\"width\":3,\"color\":\"#ef4444\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw2-fwint\",\"from\":\"core-switch-2\",\"to\":\"fw-internal\",\"width\":3,\"color\":\"#ef4444\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw1-racka1\",\"from\":\"core-switch-1\",\"to\":\"dc-rack-a1\",\"width\":4,\"color\":\"#475569\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw2-racka1\",\"from\":\"core-switch-2\",\"to\":\"dc-rack-a1\",\"width\":4,\"color\":\"#475569\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw1-racka2\",\"from\":\"core-switch-1\",\"to\":\"dc-rack-a2\",\"width\":4,\"color\":\"#475569\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw2-racka2\",\"from\":\"core-switch-2\",\"to\":\"dc-rack-a2\",\"width\":4,\"color\":\"#475569\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw1-rackb1\",\"from\":\"core-switch-1\",\"to\":\"dc-rack-b1\",\"width\":4,\"color\":\"#475569\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw2-rackb1\",\"from\":\"core-switch-2\",\"to\":\"dc-rack-b1\",\"width\":4,\"color\":\"#475569\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw1-rackb2\",\"from\":\"core-switch-1\",\"to\":\"dc-rack-b2\",\"width\":4,\"color\":\"#475569\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw2-rackb2\",\"from\":\"core-switch-2\",\"to\":\"dc-rack-b2\",\"width\":4,\"color\":\"#475569\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"fw1-dmz\",\"from\":\"fw-external-1\",\"to\":\"dmz-rack\",\"width\":3,\"color\":\"#f59e0b\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"DMZ segment\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"fw2-dmz\",\"from\":\"fw-external-2\",\"to\":\"dmz-rack\",\"width\":3,\"color\":\"#f59e0b\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"DMZ segment\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw1-mgmt\",\"from\":\"core-switch-1\",\"to\":\"mgmt-rack\",\"width\":3,\"color\":\"#8b5cf6\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"OOB management\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw1-wlc1\",\"from\":\"core-switch-1\",\"to\":\"wlc-primary\",\"width\":3,\"color\":\"#06b6d4\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw2-wlc2\",\"from\":\"core-switch-2\",\"to\":\"wlc-secondary\",\"width\":3,\"color\":\"#06b6d4\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\",\"animate\":true},{\"id\":\"wlc1-wlc2\",\"from\":\"wlc-primary\",\"to\":\"wlc-secondary\",\"width\":2,\"color\":\"#f59e0b\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"HA pair\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"dashed\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"wlc1-mobile-hq\",\"from\":\"wlc-primary\",\"to\":\"mobile-zone-hq\",\"width\":2,\"color\":\"#06b6d4\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"wlc1-mobile-guest\",\"from\":\"wlc-primary\",\"to\":\"mobile-zone-guest\",\"width\":2,\"color\":\"#06b6d4\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"wlc1-mobile-iot\",\"from\":\"wlc-primary\",\"to\":\"mobile-zone-iot\",\"width\":2,\"color\":\"#06b6d4\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"router1-branch-ny\",\"from\":\"core-router-1\",\"to\":\"branch-router-ny\",\"width\":3,\"color\":\"#a855f7\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"SD-WAN tunnel\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"dashed\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"router1-branch-la\",\"from\":\"core-router-1\",\"to\":\"branch-router-la\",\"width\":3,\"color\":\"#a855f7\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"SD-WAN tunnel\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"dashed\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"router1-branch-chi\",\"from\":\"core-router-1\",\"to\":\"branch-router-chi\",\"width\":3,\"color\":\"#a855f7\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"SD-WAN tunnel\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"dashed\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"router1-branch-lon\",\"from\":\"core-router-1\",\"to\":\"branch-router-lon\",\"width\":3,\"color\":\"#a855f7\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"SD-WAN tunnel\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"dashed\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"router1-branch-tokyo\",\"from\":\"core-router-1\",\"to\":\"branch-router-tokyo\",\"width\":3,\"color\":\"#a855f7\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"SD-WAN tunnel\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"dashed\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"router1-aws\",\"from\":\"core-router-1\",\"to\":\"cloud-aws\",\"width\":3,\"color\":\"#f97316\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"Direct Connect\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"dashed\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"router2-azure\",\"from\":\"core-router-2\",\"to\":\"cloud-azure\",\"width\":3,\"color\":\"#0ea5e9\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"ExpressRoute\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"dashed\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"fwint-gcp\",\"from\":\"fw-internal\",\"to\":\"cloud-gcp\",\"width\":2,\"color\":\"#22c55e\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"VPN tunnel\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"dashed\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw1-floor1\",\"from\":\"core-switch-1\",\"to\":\"dist-switch-floor1\",\"width\":3,\"color\":\"#475569\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw1-floor2\",\"from\":\"core-switch-1\",\"to\":\"dist-switch-floor2\",\"width\":3,\"color\":\"#475569\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw2-floor3\",\"from\":\"core-switch-2\",\"to\":\"dist-switch-floor3\",\"width\":3,\"color\":\"#475569\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw2-floor4\",\"from\":\"core-switch-2\",\"to\":\"dist-switch-floor4\",\"width\":3,\"color\":\"#475569\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"floor1-endpoints\",\"from\":\"dist-switch-floor1\",\"to\":\"endpoint-1000\",\"width\":2,\"color\":\"#94a3b8\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"floor1-ap1\",\"from\":\"dist-switch-floor1\",\"to\":\"ap-floor1-zone1\",\"width\":2,\"color\":\"#06b6d4\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"floor2-ap2\",\"from\":\"dist-switch-floor2\",\"to\":\"ap-floor2-zone1\",\"width\":2,\"color\":\"#06b6d4\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"floor3-ap3\",\"from\":\"dist-switch-floor3\",\"to\":\"ap-floor3-zone1\",\"width\":2,\"color\":\"#06b6d4\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"floor4-ap4\",\"from\":\"dist-switch-floor4\",\"to\":\"ap-floor4-zone1\",\"width\":2,\"color\":\"#06b6d4\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"fwint-proxy1\",\"from\":\"fw-internal\",\"to\":\"proxy-server-1\",\"width\":2,\"color\":\"#ef4444\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"fwint-proxy2\",\"from\":\"fw-internal\",\"to\":\"proxy-server-2\",\"width\":2,\"color\":\"#ef4444\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"fwext1-vpn\",\"from\":\"fw-external-1\",\"to\":\"vpn-concentrator\",\"width\":3,\"color\":\"#8b5cf6\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw1-nac\",\"from\":\"core-switch-1\",\"to\":\"nac-server\",\"width\":2,\"color\":\"#f59e0b\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw1-voip\",\"from\":\"core-switch-1\",\"to\":\"voip-cluster\",\"width\":3,\"color\":\"#22c55e\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw2-video\",\"from\":\"core-switch-2\",\"to\":\"video-conf\",\"width\":3,\"color\":\"#22c55e\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw1-cameras\",\"from\":\"core-switch-1\",\"to\":\"security-cameras\",\"width\":2,\"color\":\"#94a3b8\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"fwint-dev1\",\"from\":\"fw-internal\",\"to\":\"dev-server-1\",\"width\":2,\"color\":\"#a855f7\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"fwint-dev2\",\"from\":\"fw-internal\",\"to\":\"dev-server-2\",\"width\":2,\"color\":\"#a855f7\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\",\"animate\":true,\"animationSpeed\":\"1.5\"},{\"id\":\"fwint-test\",\"from\":\"fw-internal\",\"to\":\"test-environment\",\"width\":2,\"color\":\"#a855f7\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw1-erp\",\"from\":\"core-switch-1\",\"to\":\"erp-system\",\"width\":3,\"color\":\"#f59e0b\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"fwext1-crm\",\"from\":\"fw-external-1\",\"to\":\"crm-system\",\"width\":2,\"color\":\"#f59e0b\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"Salesforce cloud\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"dashed\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"ups1-racka1\",\"from\":\"ups-dc-1\",\"to\":\"dc-rack-a1\",\"width\":2,\"color\":\"#fbbf24\",\"direction\":\"forward\",\"type\":\"main\",\"notes\":[\"Power feed A\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"ups2-racka2\",\"from\":\"ups-dc-2\",\"to\":\"dc-rack-a2\",\"width\":2,\"color\":\"#fbbf24\",\"direction\":\"forward\",\"type\":\"main\",\"notes\":[\"Power feed B\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"ups1-rackb1\",\"from\":\"ups-dc-1\",\"to\":\"dc-rack-b1\",\"width\":2,\"color\":\"#fbbf24\",\"direction\":\"forward\",\"type\":\"main\",\"notes\":[\"Power feed A\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\",\"animate\":true,\"animationSpeed\":\"4\"},{\"id\":\"ups2-rackb2\",\"from\":\"ups-dc-2\",\"to\":\"dc-rack-b2\",\"width\":2,\"color\":\"#fbbf24\",\"direction\":\"forward\",\"type\":\"main\",\"notes\":[\"Power feed B\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"cooling1-racka1\",\"from\":\"cooling-1\",\"to\":\"dc-rack-a1\",\"width\":2,\"color\":\"#38bdf8\",\"direction\":\"forward\",\"type\":\"main\",\"notes\":[\"Cooling zone\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"dotted\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"cooling2-rackb1\",\"from\":\"cooling-2\",\"to\":\"dc-rack-b1\",\"width\":2,\"color\":\"#38bdf8\",\"direction\":\"forward\",\"type\":\"main\",\"notes\":[\"Cooling zone\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"dotted\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"custom-1765237881452\",\"type\":\"custom\",\"color\":\"#c800ff\",\"width\":4,\"lineStyle\":\"solid\",\"direction\":\"forward\",\"points\":[{\"x\":3492.3994140625,\"y\":1526.9556884765625},{\"x\":3500.609619140625,\"y\":1830.7386474609375},{\"x\":3303.561279296875,\"y\":1732.2144775390625}],\"notes\":[],\"routing\":\"orthogonal\"},{\"id\":\"custom-1765239355462\",\"type\":\"custom\",\"color\":\"#f97316\",\"width\":4,\"lineStyle\":\"solid\",\"direction\":\"forward\",\"points\":[{\"x\":2467.182861328125,\"y\":156.12173461914062},{\"x\":2146.36376953125,\"y\":146.32574462890625},{\"x\":2305.548828125,\"y\":244.28573608398438}],\"notes\":[],\"routing\":\"orthogonal\"}]},\"positions\":{\"core-router-1\":{\"x\":3720.166015625,\"y\":245.9932403564453},\"core-router-2\":{\"x\":2499.883407638303,\"y\":329.99503430389154},\"fw-external-1\":{\"x\":3221.7385182723783,\"y\":1016.1364499992887},\"fw-external-2\":{\"x\":1915.5213706410505,\"y\":224.43528858865443},\"fw-internal\":{\"x\":1746.9168185079352,\"y\":477.5300527221864},\"core-switch-1\":{\"x\":449.39860669455675,\"y\":384.4578707617695},\"core-switch-2\":{\"x\":761.1664921394672,\"y\":180.89283910873155},\"dc-rack-a1\":{\"x\":783.7017241128451,\"y\":647.4086870405963},\"dc-rack-a2\":{\"x\":209.25701628255229,\"y\":228.01593190351014},\"dc-rack-b1\":{\"x\":3184.3186625759854,\"y\":1627.4495531027196},\"dc-rack-b2\":{\"x\":245.37065918741246,\"y\":499.6191264194081},\"dmz-rack\":{\"x\":2176.4105289561007,\"y\":610.8312056412005},\"mgmt-rack\":{\"x\":1601.2987201807314,\"y\":1281.4753424975324},\"esxi-host-01\":{\"x\":2162.2166789540615,\"y\":2608.110619289529},\"esxi-host-02\":{\"x\":2205.94717202368,\"y\":2689.67539624076},\"esxi-host-03\":{\"x\":2154.6015436939074,\"y\":2771.203009774913},\"esxi-host-04\":{\"x\":2195.986926025096,\"y\":2845},\"tor-switch-a1\":{\"x\":2146.8943639962963,\"y\":2845},\"esxi-host-05\":{\"x\":2185.9099961569727,\"y\":2845},\"esxi-host-06\":{\"x\":2139.099728450725,\"y\":2845},\"esxi-host-07\":{\"x\":2175.7223818764883,\"y\":2845},\"esxi-host-08\":{\"x\":2131.2222777148922,\"y\":2845},\"tor-switch-a2\":{\"x\":2165.4301485385085,\"y\":2845},\"san-primary\":{\"x\":2123.2667017518106,\"y\":2845},\"san-secondary\":{\"x\":2155.0394237844876,\"y\":2845},\"fc-switch-1\":{\"x\":2115.2377370375634,\"y\":2845},\"fc-switch-2\":{\"x\":2144.5563938942755,\"y\":2845},\"backup-server-1\":{\"x\":2107.1401637413705,\"y\":2845},\"backup-server-2\":{\"x\":2133.987300103025,\"y\":2845},\"tape-library\":{\"x\":2098.9788028796397,\"y\":2845},\"tor-switch-b1\":{\"x\":2123.338434885373,\"y\":2845},\"tor-switch-b2\":{\"x\":2090.7585134456995,\"y\":2845},\"web-server-1\":{\"x\":2112.6161382091163,\"y\":2845},\"web-server-2\":{\"x\":2082.484189516922,\"y\":2845},\"waf-1\":{\"x\":2101.826793760617,\"y\":2845},\"load-balancer-dmz\":{\"x\":2074.1607573409574,\"y\":2845},\"mail-gateway\":{\"x\":2090.97682514417,\"y\":2845},\"dns-external-1\":{\"x\":2065.7931724028163,\"y\":2845},\"dns-external-2\":{\"x\":2080.0726920576153,\"y\":2845},\"vcenter\":{\"x\":2057.3864164745437,\"y\":2845},\"nsx-manager\":{\"x\":2069.1208864464534,\"y\":2845},\"siem-server\":{\"x\":2048.945494649244,\"y\":2845},\"nms-server\":{\"x\":2058.1279286387635,\"y\":2845},\"jump-server\":{\"x\":2040.4754323612206,\"y\":2845},\"ipam-server\":{\"x\":2047.1003634632284,\"y\":2845},\"wlc-primary\":{\"x\":1575.9723612611924,\"y\":2306.135986328125},\"wlc-secondary\":{\"x\":1468.1361870166274,\"y\":1563.733642578125},\"mobile-zone-hq\":{\"x\":2354.901177346808,\"y\":2806.0078125},\"mobile-zone-guest\":{\"x\":2307.6605605284435,\"y\":2611.047119140625},\"mobile-zone-iot\":{\"x\":2229.397686389302,\"y\":2299.110107421875},\"branch-router-ny\":{\"x\":3151.903101363964,\"y\":633.6580810546875},\"branch-router-la\":{\"x\":3083.8876194705945,\"y\":506.90625},\"branch-router-chi\":{\"x\":3355.02409980103,\"y\":393.1805725097656},\"branch-router-lon\":{\"x\":3113.609823320121,\"y\":260.4093322753906},\"branch-router-tokyo\":{\"x\":3699.3234994733834,\"y\":471.4241027832031},\"cloud-aws\":{\"x\":3436.528122523513,\"y\":545.9614868164062},\"cloud-azure\":{\"x\":2592.566210818907,\"y\":2724.068115234375},\"cloud-gcp\":{\"x\":2827.3183770424234,\"y\":2731.397216796875},\"isp-primary\":{\"x\":3712.192068081962,\"y\":615.64990234375},\"isp-secondary\":{\"x\":2702.3789772348055,\"y\":467.890869140625},\"dc-internal-1\":{\"x\":1958.4243458877936,\"y\":2845},\"dc-internal-2\":{\"x\":1963.768951182132,\"y\":2845},\"app-server-1\":{\"x\":1947.3819379304134,\"y\":2845},\"app-server-2\":{\"x\":1955.2862087394126,\"y\":2845},\"db-server-1\":{\"x\":1936.3708569559828,\"y\":2845},\"db-server-2\":{\"x\":1946.8300873488822,\"y\":2845},\"k8s-master-1\":{\"x\":1925.397658583093,\"y\":2845},\"k8s-master-2\":{\"x\":1938.405621494142,\"y\":2845},\"k8s-master-3\":{\"x\":1914.4688758763386,\"y\":2845},\"k8s-worker-1\":{\"x\":1930.017826812177,\"y\":2845},\"k8s-worker-2\":{\"x\":1903.5910154567553,\"y\":2845},\"k8s-worker-3\":{\"x\":1921.6716971072178,\"y\":2845},\"k8s-worker-4\":{\"x\":1892.7705536280016,\"y\":2845},\"proxy-server-1\":{\"x\":1806.1152433697903,\"y\":653.7529296875},\"proxy-server-2\":{\"x\":2937.4207928721535,\"y\":2628.7880859375},\"vpn-concentrator\":{\"x\":3642.252088474593,\"y\":946.7255249023438},\"nac-server\":{\"x\":1153.2626148502184,\"y\":1172.1895751953125},\"print-server\":{\"x\":1896.9328460745962,\"y\":2845},\"file-server\":{\"x\":1860.7177871362182,\"y\":2845},\"ca-server\":{\"x\":1888.8027739274805,\"y\":2845},\"sccm-server\":{\"x\":1850.1909418511675,\"y\":2845},\"voip-cluster\":{\"x\":1777.038465328039,\"y\":1616.8961181640625},\"video-conf\":{\"x\":1993.8373941679588,\"y\":2244.936309814453},\"security-cameras\":{\"x\":1674.413336949044,\"y\":2046.0380859375},\"nvr-cluster\":{\"x\":1829.4110389706402,\"y\":2845},\"dev-server-1\":{\"x\":2800.5894350649614,\"y\":1175.623291015625},\"dev-server-2\":{\"x\":1945.0822182484326,\"y\":1164.5184783935547},\"test-environment\":{\"x\":2566.9100352578575,\"y\":885.2827758789062},\"erp-system\":{\"x\":789.9880103985649,\"y\":473.7113342285156},\"crm-system\":{\"x\":3514.6003232048542,\"y\":1137.7720947265625},\"endpoint-1000\":{\"x\":991.6812012057328,\"y\":2284.42236328125},\"dist-switch-floor1\":{\"x\":654.2091033261356,\"y\":2020.0086669921875},\"dist-switch-floor2\":{\"x\":853.8845527112826,\"y\":1843.2872314453125},\"dist-switch-floor3\":{\"x\":1899.4353951584517,\"y\":1456.5068359375},\"dist-switch-floor4\":{\"x\":488.5289313756234,\"y\":181.47256469726562},\"ap-floor1-zone1\":{\"x\":1140.16846970184,\"y\":2070.2916259765625},\"ap-floor2-zone1\":{\"x\":688.1952143592268,\"y\":2384.4775390625},\"ap-floor3-zone1\":{\"x\":2145.3803027919676,\"y\":1890.2816162109375},\"ap-floor4-zone1\":{\"x\":517.646146409649,\"y\":565.59716796875},\"ups-dc-1\":{\"x\":771.1406786539856,\"y\":295.9266662597656},\"ups-dc-2\":{\"x\":216.2410855890687,\"y\":330.3345947265625},\"pdu-rack-a1\":{\"x\":1804.774444371901,\"y\":2845},\"pdu-rack-a2\":{\"x\":1741.6184034693686,\"y\":2845},\"cooling-1\":{\"x\":245.7080801919958,\"y\":626.1914672851562},\"cooling-2\":{\"x\":1603.293611085831,\"y\":981.0621185302734},\"camera-a\":{\"x\":166.57075412676295,\"y\":145},\"camera-a-copy\":{\"x\":1040.653076171875,\"y\":738.42822265625}},\"sizes\":{\"isp-secondary\":139,\"test-environment\":148,\"dev-server-1\":128,\"core-router-2\":120,\"camera-a\":45,\"camera-a-copy\":45},\"styles\":{\"dc-rack-b2\":{\"all\":{\"circleColor\":\"#ff0000\"}},\"dc-rack-a1\":{\"all\":{\"circleColor\":\"#ff0000\"}},\"dc-rack-b1\":{\"all\":{\"circleColor\":\"#ff0000\",\"titleSize\":59}},\"isp-secondary\":{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"alist\"}}},\"core-router-2\":{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"actual-budget\"},\"pingOffsetX\":-15,\"pingOffsetY\":-38}},\"fw-external-1\":{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"anonaddy\"}}},\"cloud-aws\":{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"ansible\"}}},\"isp-primary\":{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"wikidocs\"}}},\"branch-router-tokyo\":{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"adguard-home\"}}},\"core-router-1\":{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"borg\"}}},\"test-environment\":{\"all\":{\"icon\":{\"library\":\"simple\",\"name\":\"apple\"}}},\"dev-server-1\":{\"all\":{\"icon\":{\"library\":\"simple\",\"name\":\"amazonwebservices\"}}}},\"legend\":{\"#10b981\":\"Trusted Lan\",\"#f59e0b\":\"Secure Lan\",\"#ef4444\":\"DMZ\",\"#475569\":\"Main ISP\",\"#3b82f6\":\"Alternate ISP\",\"#8b5cf6\":\"you can edit me too\",\"#06b6d4\":\"you can edit me too\",\"#a855f7\":\"you can edit me too\",\"#f97316\":\"you can edit me too\",\"#0ea5e9\":\"you can edit me too\",\"#22c55e\":\"you can edit me too\",\"#94a3b8\":\"you can edit me too\",\"#fbbf24\":\"you can edit me too\",\"#38bdf8\":\"you can edit me too\",\"#c800ff\":\"you can edit me too\"},\"rects\":{\"list\":[{\"id\":\"rect-1765237540610\",\"x\":2879.214599609375,\"y\":159.71981811523438,\"width\":992.196044921875,\"height\":538.8650817871094,\"color\":\"#f97316\",\"style\":\"filled\",\"lineStyle\":\"solid\",\"notes\":[]},{\"id\":\"rect-1765237681216\",\"x\":448.3926696777344,\"y\":1671.651123046875,\"width\":916.3436584472656,\"height\":924.27734375,\"color\":\"#c800ff\",\"style\":\"outlined\",\"lineStyle\":\"solid\",\"notes\":[]},{\"id\":\"rect-1766437913740\",\"x\":904.5889892578125,\"y\":115.40318298339844,\"width\":110.93878173828125,\"height\":919.6242218017578,\"color\":\"#5215f9\",\"style\":\"filled\",\"lineStyle\":\"wall\",\"notes\":[],\"borderWidth\":13},{\"id\":\"rect-1766437935414\",\"x\":130.93685150146484,\"y\":1072.3624877929688,\"width\":872.9131851196289,\"height\":99.260986328125,\"color\":\"#5215f9\",\"style\":\"filled\",\"lineStyle\":\"wall\",\"notes\":[],\"borderWidth\":13}]},\"texts\":{\"list\":[{\"id\":\"text-1765237828167\",\"x\":3411.458740234375,\"y\":1390.00439453125,\"content\":\"Double click on desktop\\nor long press on mobile\\nto enter rack canvas view\",\"fontSize\":46,\"color\":\"#e2e8f0\",\"fontWeight\":\"bold\",\"fontStyle\":\"italic\",\"textAlign\":\"middle\",\"textDecoration\":\"none\",\"bgColor\":\"#000000\",\"bgEnabled\":false,\"opacity\":1},{\"id\":\"text-1765239331126\",\"x\":2454.5615234375,\"y\":160.73322105407715,\"content\":\"Google is live!\",\"fontSize\":56,\"color\":\"#e2e8f0\",\"fontWeight\":\"bold\",\"fontStyle\":\"normal\",\"textAlign\":\"start\",\"textDecoration\":\"none\",\"bgColor\":\"#000000\",\"bgEnabled\":false,\"opacity\":1},{\"id\":\"text-1766446595277\",\"x\":654.3878479003906,\"y\":1367.7945556640625,\"content\":\"SITE A\",\"fontSize\":101,\"color\":\"#e2e8f0\",\"fontWeight\":\"normal\",\"fontStyle\":\"normal\",\"textAlign\":\"start\",\"textDecoration\":\"none\",\"bgColor\":\"#000000\",\"bgEnabled\":false,\"opacity\":1},{\"id\":\"text-1766446610211\",\"x\":180.63662719726562,\"y\":1128.822998046875,\"content\":\"SITE A\",\"fontSize\":101,\"color\":\"#e2e8f0\",\"fontWeight\":\"normal\",\"fontStyle\":\"normal\",\"textAlign\":\"start\",\"textDecoration\":\"none\",\"bgColor\":\"#000000\",\"bgEnabled\":false,\"opacity\":1},{\"id\":\"text-1766453024797\",\"x\":968.6458740234375,\"y\":1028.6621398925781,\"content\":\"SITE A\",\"fontSize\":101,\"color\":\"#e2e8f0\",\"fontWeight\":\"normal\",\"fontStyle\":\"normal\",\"textAlign\":\"start\",\"textDecoration\":\"none\",\"bgColor\":\"#000000\",\"bgEnabled\":false,\"opacity\":1,\"rotation\":-89,\"_dragStartX\":972.46826171875,\"_dragStartY\":1009.5499572753906},{\"id\":\"text-1766453070975\",\"x\":613.1589965820312,\"y\":1139.512939453125,\"content\":\"SITE A\",\"fontSize\":101,\"color\":\"#e2e8f0\",\"fontWeight\":\"normal\",\"fontStyle\":\"normal\",\"textAlign\":\"start\",\"textDecoration\":\"none\",\"bgColor\":\"#000000\",\"bgEnabled\":false,\"opacity\":1},{\"id\":\"text-1766453072857\",\"x\":968.64599609375,\"y\":474.40818786621094,\"content\":\"SITE A\",\"fontSize\":101,\"color\":\"#e2e8f0\",\"fontWeight\":\"normal\",\"fontStyle\":\"normal\",\"textAlign\":\"start\",\"textDecoration\":\"none\",\"bgColor\":\"#000000\",\"bgEnabled\":false,\"opacity\":1,\"rotation\":269,\"_dragStartX\":1480.85302734375,\"_dragStartY\":822.2503356933594}]},\"pageState\":{\"title\":\"The One File Corporate\",\"background\":\"\",\"topbarBg\":\"rgba(9, 12, 20, 0.9)\",\"topbarBorder\":\"#1f2533\",\"panel\":\"#0b0e13\",\"panelAlt\":\"#10141b\",\"accent\":\"#4fd1c5\",\"sidebarBg\":\"#10141b\",\"btnBg\":\"#0b0e13\",\"btnText\":\"#e2e8f0\",\"tagFill\":\"#1e293b\",\"tagText\":\"#e2e8f0\",\"tagBorder\":\"#475569\",\"inputBg\":\"#0b0e13\",\"inputText\":\"#e2e8f0\",\"inputBorder\":\"#1f2937\",\"inputFont\":\"Inter, system-ui, sans-serif\",\"inputFontSize\":14,\"toolbarBg\":\"#0f172a\",\"toolbarBorder\":\"#1f2937\",\"toolbarText\":\"#94a3b8\",\"toolbarBtnBg\":\"#0b0e13\",\"toolbarBtnText\":\"#e2e8f0\",\"minimapDots\":\"#94a3b8\",\"canvasHintEnabled\":true,\"canvasHintText\":\"\",\"canvasHintBg\":\"#0f172a\",\"canvasHintColor\":\"#94a3b8\",\"danger\":\"#f56565\",\"textMain\":\"#e2e8f0\",\"textSoft\":\"#94a3b8\",\"topbarHeight\":103,\"sidebarWidth\":350,\"mobileFooterHeight\":40,\"sidebarCollapsed\":false,\"nodeFill\":\"#1e293b\",\"nodeStroke\":\"#475569\",\"nodeTitle\":\"#e2e8f0\",\"nodeSub\":\"#94a3b8\",\"nodeTitleSize\":41,\"nodeSubSize\":27,\"nodeFont\":\"monospace\",\"defaultEdge\":\"#475569\",\"selectionHandle\":\"#f59e0b\",\"selectionHandleSize\":8,\"groupIndicator\":\"#4fd1c5\",\"canvasGradientTop\":\"#1e2532\",\"canvasGradientBottom\":\"#050608\",\"canvasBorder\":\"#475569\",\"canvasGrid\":\"#475569\",\"canvasGridSize\":50,\"canvasGridEnabled\":true,\"rackFrameFill\":\"#0f172a\",\"rackFrameStroke\":\"#4fd1c5\",\"rackLineColor\":\"#475569\",\"rackTextColor\":\"#4fd1c5\",\"rackGridEnabled\":true,\"viewOnly\":false,\"defaultEdgeRouting\":\"orthogonal\",\"animateConnections\":false,\"animationStyle\":\"arrows\",\"animationDirection\":\"all\",\"animationSpeed\":4,\"autoPingEnabled\":false,\"autoPingInterval\":30}},{\"id\":\"tab-1765235136918\",\"name\":\"Homelab 2\",\"nodes\":{\"internet\":{\"shape\":\"square\",\"name\":\"Internet\",\"ip\":\"0.0.0.0\",\"role\":\"\",\"tags\":[],\"notes\":[],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"internet-copy\":{\"shape\":\"firewall\",\"name\":\"OPNSENSE\",\"ip\":\"0.0.0.0\",\"role\":\"\",\"tags\":[],\"notes\":[],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"opnsense-copy\":{\"shape\":\"firewall\",\"name\":\"Docker\",\"ip\":\"0.0.0.0\",\"role\":\"\",\"tags\":[],\"notes\":[],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"docker-copy\":{\"shape\":\"firewall\",\"name\":\"Docker2\",\"ip\":\"0.0.0.0\",\"role\":\"\",\"tags\":[],\"notes\":[],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"docker-copy-1\":{\"shape\":\"firewall\",\"name\":\"Docker3\",\"ip\":\"0.0.0.0\",\"role\":\"\",\"tags\":[],\"notes\":[],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"docker-copy-2\":{\"shape\":\"firewall\",\"name\":\"Docker 4\",\"ip\":\"0.0.0.0\",\"role\":\"\",\"tags\":[{\"type\":\"icon\",\"library\":\"selfhst\",\"name\":\"docker\"},{\"type\":\"icon\",\"library\":\"selfhst\",\"name\":\"authentik\"},{\"type\":\"icon\",\"library\":\"selfhst\",\"name\":\"immich\"}],\"notes\":[],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"opnsense-copy-1\":{\"shape\":\"firewall\",\"name\":\"OPNSENSE GUEST\",\"ip\":\"0.0.0.0\",\"role\":\"\",\"tags\":[],\"notes\":[],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"phone\":{\"shape\":\"phone\",\"name\":\"Phone\",\"ip\":\"0.0.0.0\",\"role\":\"\",\"tags\":[],\"notes\":[],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"desktop\":{\"shape\":\"pc\",\"name\":\"Desktop\",\"ip\":\"0.0.0.0\",\"role\":\"\",\"tags\":[],\"notes\":[],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"dns\":{\"shape\":\"cloud\",\"name\":\"DNS\",\"ip\":\"0.0.0.0\",\"role\":\"\",\"tags\":[],\"notes\":[],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"racked\":{\"shape\":\"server\",\"name\":\"Racked\",\"ip\":\"\",\"role\":\"Rack\",\"tags\":[],\"notes\":[],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":true,\"locked\":false,\"groupId\":null}},\"edges\":{\"list\":[{\"id\":\"internet-internet-copy-1765238145151\",\"from\":\"internet\",\"to\":\"internet-copy\",\"width\":4,\"color\":\"#55e208\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1},{\"id\":\"internet-copy-opnsense-copy-1765238187451\",\"from\":\"internet-copy\",\"to\":\"opnsense-copy\",\"width\":4,\"color\":\"#4c00ff\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1},{\"id\":\"internet-copy-docker-copy-1765238242477\",\"from\":\"internet-copy\",\"to\":\"docker-copy\",\"width\":4,\"color\":\"#4c00ff\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1},{\"id\":\"internet-copy-docker-copy-1-1765238244637\",\"from\":\"internet-copy\",\"to\":\"docker-copy-1\",\"width\":4,\"color\":\"#4c00ff\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1},{\"id\":\"internet-copy-docker-copy-2-1765238246233\",\"from\":\"internet-copy\",\"to\":\"docker-copy-2\",\"width\":4,\"color\":\"#4c00ff\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1},{\"id\":\"internet-opnsense-copy-1-1765238266117\",\"from\":\"internet\",\"to\":\"opnsense-copy-1\",\"width\":4,\"color\":\"#80ff00\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1},{\"id\":\"opnsense-copy-1-dns-1765238347996\",\"from\":\"opnsense-copy-1\",\"to\":\"dns\",\"width\":4,\"color\":\"#fb00ff\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1},{\"id\":\"dns-desktop-1765238386101\",\"from\":\"dns\",\"to\":\"desktop\",\"width\":4,\"color\":\"#ff00d0\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1},{\"id\":\"phone-dns-1765238391156\",\"from\":\"phone\",\"to\":\"dns\",\"width\":4,\"color\":\"#ff00d0\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1},{\"id\":\"custom-1765239449323\",\"type\":\"custom\",\"color\":\"#f97316\",\"width\":4,\"lineStyle\":\"solid\",\"direction\":\"forward\",\"points\":[{\"x\":2936.464111328125,\"y\":786.07958984375},{\"x\":3184.112060546875,\"y\":887.6153564453125},{\"x\":2763.110107421875,\"y\":981.7216796875}],\"notes\":[]}]},\"positions\":{\"internet\":{\"x\":2103.968290880771,\"y\":268},\"internet-copy\":{\"x\":2066.9677515897347,\"y\":473.4119134177565},\"opnsense-copy\":{\"x\":1773.8400660428597,\"y\":666.5758233298659},\"docker-copy\":{\"x\":1931.1978950081452,\"y\":782.2775961320921},\"docker-copy-1\":{\"x\":2158.1262397347077,\"y\":767.7122274797483},\"docker-copy-2\":{\"x\":2342.2663764534577,\"y\":631.7681967180296},\"opnsense-copy-1\":{\"x\":2757.879480087803,\"y\":307.6117116091891},\"phone\":{\"x\":3312.857751572178,\"y\":502.58220111114224},\"desktop\":{\"x\":2971.700036728428,\"y\":480.7287465212985},\"dns\":{\"x\":3200.4643189549906,\"y\":320.469591247861},\"racked\":{\"x\":2645.5845448279656,\"y\":970.7820678889219}},\"sizes\":{\"core-router-1\":36,\"internet\":168,\"phone\":121,\"desktop\":147,\"racked\":137,\"docker-copy-2\":82},\"styles\":{\"internet\":{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"amazon-web-services\"},\"circleColor\":\"#ffffff\",\"circleBorder\":\"#ffffff\"}},\"opnsense-copy-1\":{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"opnsense-v1\"}}},\"internet-copy\":{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"opnsense\"}}},\"docker-copy-2\":{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"docker\"}}},\"docker-copy-1\":{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"authportal\"}}},\"docker-copy\":{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"jotty\"}}},\"opnsense-copy\":{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"portainer\"}}},\"racked\":{\"all\":{\"icon\":{\"library\":\"mdi\",\"name\":\"server-security\"},\"circleColor\":\"#010813\",\"circleBorder\":\"#ffffff\"}}},\"legend\":{\"#475569\":\"you can edit me too\",\"#65758b\":\"you can edit me too\",\"#63748c\":\"you can edit me too\",\"#5e6f87\":\"you can edit me too\",\"#586a84\":\"you can edit me too\",\"#4f627d\":\"you can edit me too\",\"#455873\":\"you can edit me too\",\"#3d506c\":\"you can edit me too\",\"#354964\":\"you can edit me too\",\"#2e415c\":\"you can edit me too\",\"#293c56\":\"you can edit me too\",\"#273a53\":\"you can edit me too\",\"#253750\":\"you can edit me too\",\"#23354d\":\"you can edit me too\",\"#203046\":\"you can edit me too\",\"#1e2d43\":\"you can edit me too\",\"#1a283d\":\"you can edit me too\",\"#172435\":\"you can edit me too\",\"#141f2e\":\"you can edit me too\",\"#111a27\":\"you can edit me too\",\"#0f1824\":\"you can edit me too\",\"#0d1521\":\"you can edit me too\",\"#0c131d\":\"you can edit me too\",\"#0c1d1c\":\"you can edit me too\",\"#0c1c1d\":\"you can edit me too\",\"#0c191d\":\"you can edit me too\",\"#0c141d\":\"you can edit me too\",\"#0c0d1d\":\"you can edit me too\",\"#130c1d\":\"you can edit me too\",\"#1b0c1d\":\"you can edit me too\",\"#1d0c17\":\"you can edit me too\",\"#1d0c10\":\"you can edit me too\",\"#1d0c0c\":\"you can edit me too\",\"#3b1b1b\":\"you can edit me too\",\"#3c1a1a\":\"you can edit me too\",\"#3f1c1c\":\"you can edit me too\",\"#401c1c\":\"you can edit me too\",\"#451c1c\":\"you can edit me too\",\"#461b1b\":\"you can edit me too\",\"#4c1a1a\":\"you can edit me too\",\"#521919\":\"you can edit me too\",\"#571919\":\"you can edit me too\",\"#5d1818\":\"you can edit me too\",\"#631717\":\"you can edit me too\",\"#651515\":\"you can edit me too\",\"#6a1616\":\"you can edit me too\",\"#6f1515\":\"you can edit me too\",\"#711414\":\"you can edit me too\",\"#761414\":\"you can edit me too\",\"#771313\":\"you can edit me too\",\"#7c1313\":\"you can edit me too\",\"#811313\":\"you can edit me too\",\"#821212\":\"you can edit me too\",\"#871212\":\"you can edit me too\",\"#881111\":\"you can edit me too\",\"#8d1111\":\"you can edit me too\",\"#8e1010\":\"you can edit me too\",\"#8f0f0f\":\"you can edit me too\",\"#900e0e\":\"you can edit me too\",\"#8e0b0b\":\"you can edit me too\",\"#8c0d0d\":\"you can edit me too\",\"#880c0c\":\"you can edit me too\",\"#830c0c\":\"you can edit me too\",\"#7e0c0c\":\"you can edit me too\",\"#790c0c\":\"you can edit me too\",\"#730c0c\":\"you can edit me too\",\"#6f0b0b\":\"you can edit me too\",\"#0b6f64\":\"you can edit me too\",\"#0b6f5f\":\"you can edit me too\",\"#0b6f56\":\"you can edit me too\",\"#0b6f49\":\"you can edit me too\",\"#0b6f31\":\"you can edit me too\",\"#0b6f1f\":\"you can edit me too\",\"#0b6f0d\":\"you can edit me too\",\"#176f0b\":\"you can edit me too\",\"#266f0b\":\"you can edit me too\",\"#296f0b\":\"you can edit me too\",\"#2e6f0b\":\"you can edit me too\",\"#1a2d10\":\"you can edit me too\",\"#1c3111\":\"you can edit me too\",\"#213814\":\"you can edit me too\",\"#233c15\":\"you can edit me too\",\"#254017\":\"you can edit me too\",\"#294918\":\"you can edit me too\",\"#2b4d1a\":\"you can edit me too\",\"#2d511a\":\"you can edit me too\",\"#315a1b\":\"you can edit me too\",\"#35631c\":\"you can edit me too\",\"#37681d\":\"you can edit me too\",\"#3b721d\":\"you can edit me too\",\"#3f7b1e\":\"you can edit me too\",\"#42851e\":\"you can edit me too\",\"#46901d\":\"you can edit me too\",\"#499a1d\":\"you can edit me too\",\"#4b9f1d\":\"you can edit me too\",\"#4ca61c\":\"you can edit me too\",\"#50b01c\":\"you can edit me too\",\"#51b71a\":\"you can edit me too\",\"#50b918\":\"you can edit me too\",\"#51c115\":\"you can edit me too\",\"#53c615\":\"you can edit me too\",\"#53c814\":\"you can edit me too\",\"#52c913\":\"you can edit me too\",\"#54d011\":\"you can edit me too\",\"#53d110\":\"you can edit me too\",\"#55d510\":\"you can edit me too\",\"#55d70f\":\"you can edit me too\",\"#54d80e\":\"you can edit me too\",\"#54da0b\":\"you can edit me too\",\"#56df0c\":\"you can edit me too\",\"#53db0a\":\"you can edit me too\",\"#55e00b\":\"you can edit me too\",\"#55e109\":\"you can edit me too\",\"#55e208\":\"ISP LINE\",\"#4c00ff\":\"MY Guest NETWORK\",\"#80ff00\":\"you can edit me too\",\"#3b4234\":\"you can edit me too\",\"#3a3442\":\"you can edit me too\",\"#3b3442\":\"you can edit me too\",\"#3c3442\":\"you can edit me too\",\"#3d3442\":\"you can edit me too\",\"#3e3442\":\"you can edit me too\",\"#3f3442\":\"you can edit me too\",\"#403442\":\"you can edit me too\",\"#413442\":\"you can edit me too\",\"#653d66\":\"you can edit me too\",\"#683f69\":\"you can edit me too\",\"#6c416c\":\"you can edit me too\",\"#6f4370\":\"you can edit me too\",\"#704270\":\"you can edit me too\",\"#734474\":\"you can edit me too\",\"#784479\":\"you can edit me too\",\"#7d447e\":\"you can edit me too\",\"#7e437f\":\"you can edit me too\",\"#834384\":\"you can edit me too\",\"#844285\":\"you can edit me too\",\"#89418b\":\"you can edit me too\",\"#8e428f\":\"you can edit me too\",\"#904091\":\"you can edit me too\",\"#923e93\":\"you can edit me too\",\"#973e98\":\"you can edit me too\",\"#943c96\":\"you can edit me too\",\"#993c9a\":\"you can edit me too\",\"#963a98\":\"you can edit me too\",\"#973899\":\"you can edit me too\",\"#99369b\":\"you can edit me too\",\"#9a359c\":\"you can edit me too\",\"#9b349d\":\"you can edit me too\",\"#9d329f\":\"you can edit me too\",\"#9e31a0\":\"you can edit me too\",\"#a02fa2\":\"you can edit me too\",\"#9d2d9f\":\"you can edit me too\",\"#9f2ba1\":\"you can edit me too\",\"#a129a3\":\"you can edit me too\",\"#a327a5\":\"you can edit me too\",\"#a525a7\":\"you can edit me too\",\"#a723a9\":\"you can edit me too\",\"#a921ab\":\"you can edit me too\",\"#ab1fad\":\"you can edit me too\",\"#ad1daf\":\"you can edit me too\",\"#ae1cb0\":\"you can edit me too\",\"#b019b3\":\"you can edit me too\",\"#b118b4\":\"you can edit me too\",\"#b316b6\":\"you can edit me too\",\"#b816bb\":\"you can edit me too\",\"#b514b8\":\"you can edit me too\",\"#ba14bd\":\"you can edit me too\",\"#b712ba\":\"you can edit me too\",\"#bb13be\":\"you can edit me too\",\"#b811bb\":\"you can edit me too\",\"#be10c1\":\"you can edit me too\",\"#bb0ebe\":\"you can edit me too\",\"#bd0cc0\":\"you can edit me too\",\"#be0bc1\":\"you can edit me too\",\"#c108c4\":\"you can edit me too\",\"#be06c1\":\"you can edit me too\",\"#c103c4\":\"you can edit me too\",\"#c301c6\":\"you can edit me too\",\"#c400c7\":\"you can edit me too\",\"#c900cc\":\"you can edit me too\",\"#ce00d1\":\"you can edit me too\",\"#d300d6\":\"you can edit me too\",\"#d800db\":\"you can edit me too\",\"#dd00e0\":\"you can edit me too\",\"#e200e6\":\"you can edit me too\",\"#ec00f0\":\"you can edit me too\",\"#f100f5\":\"you can edit me too\",\"#f600fa\":\"you can edit me too\",\"#fb00ff\":\"you can edit me too\",\"#ff00d0\":\"iPhone (always guest iPhone)\",\"#f97316\":\"you can edit me too\"},\"rects\":{\"list\":[{\"id\":\"rect-1765238219615\",\"x\":2680.053955078125,\"y\":251.44879150390625,\"width\":814.10400390625,\"height\":389.26678466796875,\"color\":\"#ec0999\",\"style\":\"filled\",\"lineStyle\":\"solid\",\"notes\":[]}]},\"texts\":{\"list\":[{\"id\":\"text-1765238422602\",\"x\":2466.35986328125,\"y\":741.6801147460938,\"content\":\"Double click on desktop\\nor long press on mobile\\nto enter rack canvas view\",\"fontSize\":40,\"color\":\"#e2e8f0\",\"fontWeight\":\"normal\",\"fontStyle\":\"normal\",\"textAlign\":\"start\",\"textDecoration\":\"none\",\"bgColor\":\"#000000\",\"bgEnabled\":false,\"opacity\":1}]},\"pageState\":{\"title\":\"The One File\",\"background\":\"\",\"topbarBg\":\"rgba(9, 12, 20, 0.9)\",\"topbarBorder\":\"#1f2533\",\"panel\":\"#2f0e0e\",\"panelAlt\":\"#10141b\",\"accent\":\"#a75252\",\"sidebarBg\":\"#10141b\",\"btnBg\":\"#0b0e13\",\"btnText\":\"#e2e8f0\",\"tagFill\":\"#1e293b\",\"tagText\":\"#e2e8f0\",\"tagBorder\":\"#475569\",\"inputBg\":\"#0b0e13\",\"inputText\":\"#e2e8f0\",\"inputBorder\":\"#1f2937\",\"inputFont\":\"Inter, system-ui, sans-serif\",\"inputFontSize\":14,\"toolbarBg\":\"#441215\",\"toolbarBorder\":\"#1f2937\",\"toolbarText\":\"#94a3b8\",\"toolbarBtnBg\":\"#0b0e13\",\"toolbarBtnText\":\"#e2e8f0\",\"minimapDots\":\"#94a3b8\",\"canvasHintEnabled\":true,\"canvasHintText\":\"\",\"canvasHintBg\":\"#0f172a\",\"canvasHintColor\":\"#94a3b8\",\"danger\":\"#f56565\",\"textMain\":\"#e2e8f0\",\"textSoft\":\"#94a3b8\",\"topbarHeight\":112,\"sidebarWidth\":350,\"mobileFooterHeight\":40,\"sidebarCollapsed\":false,\"nodeFill\":\"#1e293b\",\"nodeStroke\":\"#475569\",\"nodeTitle\":\"#e2e8f0\",\"nodeSub\":\"#94a3b8\",\"nodeTitleSize\":18,\"nodeSubSize\":13,\"nodeFont\":\"Inter, system-ui, sans-serif\",\"defaultEdge\":\"#475569\",\"selectionHandle\":\"#f59e0b\",\"selectionHandleSize\":8,\"groupIndicator\":\"#4fd1c5\",\"canvasGradientTop\":\"#1e2532\",\"canvasGradientBottom\":\"#050608\",\"canvasBorder\":\"#475569\",\"canvasGrid\":\"#475569\",\"canvasGridSize\":50,\"canvasGridEnabled\":true,\"rackFrameFill\":\"#0f172a\",\"rackFrameStroke\":\"#4fd1c5\",\"rackLineColor\":\"#475569\",\"rackTextColor\":\"#4fd1c5\",\"rackGridEnabled\":true,\"viewOnly\":false,\"defaultEdgeRouting\":\"curved\",\"animateConnections\":false,\"animationStyle\":\"arrows\",\"animationDirection\":\"all\",\"animationSpeed\":1.5}}],\"currentTabIndex\":0,\"encryptedSections\":{},\"auditLog\":[{\"timestamp\":1766459536815,\"type\":\"export\",\"description\":\"Exported JSON: the-one-file-corporate.json\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459534490,\"type\":\"tab\",\"description\":\"Switched to tab: Corporate Site B\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459525616,\"type\":\"export\",\"description\":\"Exported CSV: the-one-file.csv\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766459518367,\"type\":\"export\",\"description\":\"Exported Markdown: the-one-file.md\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766459511746,\"type\":\"export\",\"description\":\"Exported JSON: the-one-file.json\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766459504374,\"type\":\"save\",\"description\":\"File saved: the-one-file.html\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766459500911,\"type\":\"save\",\"description\":\"File saved: the-one-file.html\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766459497380,\"type\":\"tab\",\"description\":\"Switched to tab: Homelab 2\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766459491436,\"type\":\"save\",\"description\":\"File saved: the-one-file-corporate.html\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459483682,\"type\":\"save\",\"description\":\"File saved: the-one-file-corporate.html\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459477676,\"type\":\"save\",\"description\":\"File saved: the-one-file-corporate.html\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766457578277,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766457564726,\"type\":\"tab\",\"description\":\"Switched to tab: Corporate Site B\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766457564253,\"type\":\"tab\",\"description\":\"Switched to tab: Homelab 2\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766457560309,\"type\":\"import\",\"description\":\"Imported JSON: the-one-file-corporate.json (107 nodes, 65 connections)\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455847368,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455844534,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455844054,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455843762,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455843560,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455843371,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455843162,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455842852,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455842747,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455842601,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455842449,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455842348,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455842098,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455841678,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455841236,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455841053,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455840901,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455840650,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455839427,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455839234,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455839061,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455837247,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455837081,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455836893,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455836377,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455836198,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455835455,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455834630,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455831880,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455831676,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455831451,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455830817,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455830687,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455830176,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455830048,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455829944,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455829816,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455378795,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455378693,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455378459,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455378316,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455378180,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455378069,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455377956,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455377677,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455377558,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455377448,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455377318,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455377209,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453090534,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453090317,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453090213,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453090112,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453090009,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453089903,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453088895,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453088793,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453088689,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453088584,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453088480,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453088250,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453087236,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453086725,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453086485,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453086373,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453086142,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453086043,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453072857,\"type\":\"text\",\"description\":\"paste text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453070975,\"type\":\"text\",\"description\":\"paste text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453054439,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453053127,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453052450,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453052106,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453051948,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453051806,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453051334,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453050207,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453042725,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453042179,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453041797,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453041570,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453039703,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453039291,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453039168,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453039065,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453038481,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453038365,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453038237,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453038105,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453038001,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453037850,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453037745,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453037495,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453037378,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453037182,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453037078,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453036972,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453036860,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453036147,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453035945,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453035825,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453035720,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453035443,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453035337,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453035233,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453035127,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453035026,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453034917,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453031063,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453030955,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453030833,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453030732,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453030225,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453030104,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453029968,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453029796,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453029474,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453024797,\"type\":\"text\",\"description\":\"paste text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766451118553,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766450929324,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766450817210,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766450257424,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766450255024,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766450254395,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766450253241,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766450251598,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766450250392,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766450248756,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766450244072,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766450242166,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766450240998,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766450236492,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766450233672,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766450232384,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766450231012,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766450230254,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766450229302,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766450228132,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446610211,\"type\":\"text\",\"description\":\"paste text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446604849,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446604702,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446604550,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446604404,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446604305,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446604204,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446604099,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446603952,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446603849,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446603702,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446603599,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446603452,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446603348,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446603202,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446603099,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446602953,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446602850,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446602702,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446602600,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446602453,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446602349,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446602204,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446602101,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446602000,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446601848,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446601702,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446601601,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446601452,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446601301,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446601154,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446601049,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446600948,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446600802,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446600550,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446598595,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446598461,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446598171,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446598017,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446597219,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446595278,\"type\":\"text\",\"description\":\"add text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445633355,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445632515,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445631735,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445630757,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445627846,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445625085,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445618645,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445617784,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445608998,\"type\":\"edit\",\"description\":\"edit zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445608720,\"type\":\"edit\",\"description\":\"edit zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445608540,\"type\":\"edit\",\"description\":\"edit zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445608376,\"type\":\"edit\",\"description\":\"edit zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445608204,\"type\":\"edit\",\"description\":\"edit zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445608038,\"type\":\"edit\",\"description\":\"edit zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445607852,\"type\":\"edit\",\"description\":\"edit zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445607678,\"type\":\"edit\",\"description\":\"edit zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445607506,\"type\":\"edit\",\"description\":\"edit zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445607319,\"type\":\"edit\",\"description\":\"edit zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445607154,\"type\":\"edit\",\"description\":\"edit zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445604410,\"type\":\"edit\",\"description\":\"edit zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445604244,\"type\":\"edit\",\"description\":\"edit zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445604066,\"type\":\"edit\",\"description\":\"edit zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445603900,\"type\":\"edit\",\"description\":\"edit zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445603743,\"type\":\"edit\",\"description\":\"edit zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445603563,\"type\":\"edit\",\"description\":\"edit zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445603406,\"type\":\"edit\",\"description\":\"edit zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445603226,\"type\":\"edit\",\"description\":\"edit zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445603052,\"type\":\"edit\",\"description\":\"edit zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445602880,\"type\":\"edit\",\"description\":\"edit zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445602641,\"type\":\"edit\",\"description\":\"edit zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445576567,\"type\":\"edit\",\"description\":\"edit edge animation speed\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445570290,\"type\":\"edit\",\"description\":\"edit edge animation speed\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445567192,\"type\":\"edit\",\"description\":\"edit edge animate\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445566766,\"type\":\"edit\",\"description\":\"edit edge animate\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445565520,\"type\":\"edit\",\"description\":\"edit edge animate\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445398115,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445390895,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445385694,\"type\":\"edit\",\"description\":\"toggle fov animation\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445383241,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445382911,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445381695,\"type\":\"edit\",\"description\":\"edit node name\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445375383,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445374665,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445373273,\"type\":\"node\",\"description\":\"paste node\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445372205,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438157980,\"type\":\"edit\",\"description\":\"edit edge animate\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438157430,\"type\":\"edit\",\"description\":\"edit edge animation speed\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438152691,\"type\":\"edit\",\"description\":\"edit edge animate\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438151948,\"type\":\"edit\",\"description\":\"edit edge animate\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438151286,\"type\":\"edit\",\"description\":\"edit edge animate\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438146174,\"type\":\"edit\",\"description\":\"edit edge animate\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438145649,\"type\":\"edit\",\"description\":\"edit edge animate\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438144555,\"type\":\"edit\",\"description\":\"edit edge animate\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438143655,\"type\":\"edit\",\"description\":\"edit edge animate\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438142504,\"type\":\"edit\",\"description\":\"edit edge animation speed\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438130077,\"type\":\"edit\",\"description\":\"edit edge animate\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438129561,\"type\":\"edit\",\"description\":\"edit edge animate\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438128772,\"type\":\"edit\",\"description\":\"edit edge animate\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438128398,\"type\":\"edit\",\"description\":\"edit edge animate\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438122820,\"type\":\"edit\",\"description\":\"edit edge animate\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438122062,\"type\":\"edit\",\"description\":\"edit edge animate\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438119836,\"type\":\"edit\",\"description\":\"edit edge animate\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438119588,\"type\":\"edit\",\"description\":\"edit edge animate\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438095045,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438093965,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438062827,\"type\":\"edit\",\"description\":\"toggle fov animation\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438047679,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438044161,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438041852,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438039668,\"type\":\"edit\",\"description\":\"resize node\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438039562,\"type\":\"edit\",\"description\":\"resize node\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438039421,\"type\":\"edit\",\"description\":\"resize node\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438039260,\"type\":\"edit\",\"description\":\"resize node\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438039150,\"type\":\"edit\",\"description\":\"resize node\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438039039,\"type\":\"edit\",\"description\":\"resize node\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438028508,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438021410,\"type\":\"edit\",\"description\":\"toggle fov\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438019234,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438017562,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438014356,\"type\":\"node\",\"description\":\"add node\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766437981696,\"type\":\"edit\",\"description\":\"apply routing to all\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766437966551,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766437964879,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766437963627,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766437961813,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766437961193,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766437957989,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766437956467,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766437953437,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766437952239,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766437950807,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766437944990,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766437943699,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766437935414,\"type\":\"zone\",\"description\":\"draw zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766437919019,\"type\":\"zone\",\"description\":\"delete zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766437917758,\"type\":\"zone\",\"description\":\"draw zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766437913740,\"type\":\"zone\",\"description\":\"draw zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766437882832,\"type\":\"import\",\"description\":\"Imported JSON: theonefile-networkening-corporate-demo.json (105 nodes, 65 connections)\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766263279163,\"type\":\"tab\",\"description\":\"Switched to tab: Corporate Site B\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766263270414,\"type\":\"export\",\"description\":\"Exported JSON: the-one-file.json\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766263260682,\"type\":\"save\",\"description\":\"File saved: the-one-file.html\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766263259518,\"type\":\"tab\",\"description\":\"Switched to tab: Homelab 2\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766263249401,\"type\":\"save\",\"description\":\"File saved: the-one-file-corporate.html\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766263246362,\"type\":\"import\",\"description\":\"Imported JSON: theonefile-networkening-corporate-demo.json (105 nodes, 65 connections)\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766190721141,\"type\":\"save\",\"description\":\"File saved: the-one-file-corporate.html\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766190717499,\"type\":\"tab\",\"description\":\"Switched to tab: Corporate Site B\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766190710946,\"type\":\"save\",\"description\":\"File saved: the-one-file.html\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766190705273,\"type\":\"save\",\"description\":\"File saved: the-one-file.html\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766190703463,\"type\":\"tab\",\"description\":\"Switched to tab: Homelab 2\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766190695709,\"type\":\"save\",\"description\":\"File saved: the-one-file-corporate.html\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766190688417,\"type\":\"import\",\"description\":\"Imported JSON: theonefile-networkening-corporate-demo.json (105 nodes, 65 connections)\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1765402888416,\"type\":\"save\",\"description\":\"File saved: the-one-file-corporate.html\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1765402884873,\"type\":\"tab\",\"description\":\"Switched to tab: Corporate Site B\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1765402878108,\"type\":\"save\",\"description\":\"File saved: the-one-file.html\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1765402866440,\"type\":\"save\",\"description\":\"File saved: the-one-file.html\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1765402865008,\"type\":\"tab\",\"description\":\"Switched to tab: Homelab 2\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1765402860428,\"type\":\"save\",\"description\":\"File saved: the-one-file-corporate.html\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1765402858103,\"type\":\"import\",\"description\":\"Imported JSON: theonefile-networkening-corporate-demo.json (105 nodes, 65 connections)\",\"details\":{},\"tab\":\"Corporate Site B\"}],\"savedStyleSets\":[]}\n#\n# The One File Corporate - Node List\n# Exported from The One File on 2025-12-23T03:12:20.239Z\nname,ip,role,shape,tags,layer,mac,rackUnit,uHeight,assignedRack,rackCapacity,isRack,locked,groupId,x,y,size,notes,styles\nCore Router 1,10.0.0.1,Core Routing,router,core;tier-1;redundant,physical,00:1A:2B:3C:4D:01,,2,,42,false,false,,3720,246,50,Primary core router|BGP peering enabled,\"{\"\"all\"\":{\"\"icon\"\":{\"\"library\"\":\"\"selfhst\"\",\"\"name\"\":\"\"borg\"\"}}}\"\nCore Router 2,10.0.0.2,Core Routing,router,core;tier-1;redundant,physical,00:1A:2B:3C:4D:02,,2,,42,false,false,,2500,330,120,Secondary core router|HSRP standby,\"{\"\"all\"\":{\"\"icon\"\":{\"\"library\"\":\"\"selfhst\"\",\"\"name\"\":\"\"actual-budget\"\"},\"\"pingOffsetX\"\":-15,\"\"pingOffsetY\"\":-38}}\"\nExternal FW 1,10.0.1.1,Perimeter Security,firewall,security;perimeter;ha-pair,security,00:1A:2B:3C:4D:10,,2,,42,false,false,,3222,1016,50,Palo Alto PA-5250|Active node,\"{\"\"all\"\":{\"\"icon\"\":{\"\"library\"\":\"\"selfhst\"\",\"\"name\"\":\"\"anonaddy\"\"}}}\"\nExternal FW 2,10.0.1.2,Perimeter Security,firewall,security;perimeter;ha-pair,security,00:1A:2B:3C:4D:11,,2,,42,false,false,,1916,224,50,Palo Alto PA-5250|Passive node,\nInternal FW,10.0.2.1,Internal Segmentation,firewall,security;internal,security,00:1A:2B:3C:4D:12,,2,,42,false,false,,1747,478,50,East-West traffic inspection,\nCore Switch 1,10.0.10.1,Core Switching,switch,core;layer3;redundant,physical,00:1A:2B:3C:4D:20,,2,,42,false,false,,449,384,50,Cisco Nexus 9000|VPC Domain 1,\nCore Switch 2,10.0.10.2,Core Switching,switch,core;layer3;redundant,physical,00:1A:2B:3C:4D:21,,2,,42,false,false,,761,181,50,Cisco Nexus 9000|VPC Domain 1,\nDC Rack A1,10.10.0.0/24,Data Center Rack,server,datacenter;row-a;production,physical,,,1,,42,true,false,,784,647,50,\"Row A, Position 1|Primary compute\",\"{\"\"all\"\":{\"\"circleColor\"\":\"\"#ff0000\"\"}}\"\nDC Rack A2,10.10.1.0/24,Data Center Rack,server,datacenter;row-a;production,physical,,,1,,42,true,false,,209,228,50,\"Row A, Position 2|Primary compute\",\nDC Rack B1,10.10.2.0/24,Data Center Rack,server,datacenter;row-b;storage,physical,,,1,,42,true,false,,3184,1627,50,\"Row B, Position 1|Storage systems\",\"{\"\"all\"\":{\"\"circleColor\"\":\"\"#ff0000\"\",\"\"titleSize\"\":59}}\"\nDC Rack B2,10.10.3.0/24,Data Center Rack,server,datacenter;row-b;storage,physical,,,1,,42,true,false,,245,500,50,\"Row B, Position 2|Storage systems\",\"{\"\"all\"\":{\"\"circleColor\"\":\"\"#ff0000\"\"}}\"\nDMZ Rack,172.16.0.0/24,DMZ Infrastructure,server,dmz;security;public-facing;[object Object];[object Object],security,,,1,,24,true,false,,2176,611,50,Isolated DMZ zone|Public-facing services,\nManagement Rack,192.168.100.0/24,Management Infrastructure,server,management;oob;noc,logical,,,1,,24,true,false,,1601,1281,50,Out-of-band management|NOC equipment,\nESXi Host 01,10.10.0.11,Hypervisor,server,vmware;compute;cluster-a,physical,00:50:56:AA:01:01,38,2,dc-rack-a1,42,false,false,,2162,2608,50,Dell PowerEdge R750|512GB RAM|vSphere 8.0,\nESXi Host 02,10.10.0.12,Hypervisor,server,vmware;compute;cluster-a,physical,00:50:56:AA:01:02,35,2,dc-rack-a1,42,false,false,,2206,2690,50,Dell PowerEdge R750|512GB RAM|vSphere 8.0,\nESXi Host 03,10.10.0.13,Hypervisor,server,vmware;compute;cluster-a,physical,00:50:56:AA:01:03,32,2,dc-rack-a1,42,false,false,,2155,2771,50,Dell PowerEdge R750|512GB RAM|vSphere 8.0,\nESXi Host 04,10.10.0.14,Hypervisor,server,vmware;compute;cluster-a,physical,00:50:56:AA:01:04,29,2,dc-rack-a1,42,false,false,,2196,2845,50,Dell PowerEdge R750|512GB RAM|vSphere 8.0,\nToR Switch A1,10.10.0.1,Top of Rack,switch,tor;access;rack-a1,physical,00:1A:2B:3C:5D:01,42,1,dc-rack-a1,42,false,false,,2147,2845,50,Cisco Nexus 93180YC-FX|48x25G ports,\nESXi Host 05,10.10.1.11,Hypervisor,server,vmware;compute;cluster-b,physical,00:50:56:AA:02:01,38,2,dc-rack-a2,42,false,false,,2186,2845,50,Dell PowerEdge R750|768GB RAM|vSphere 8.0,\nESXi Host 06,10.10.1.12,Hypervisor,server,vmware;compute;cluster-b,physical,00:50:56:AA:02:02,35,2,dc-rack-a2,42,false,false,,2139,2845,50,Dell PowerEdge R750|768GB RAM|vSphere 8.0,\nESXi Host 07,10.10.1.13,Hypervisor,server,vmware;compute;cluster-b,physical,00:50:56:AA:02:03,32,2,dc-rack-a2,42,false,false,,2176,2845,50,Dell PowerEdge R750|768GB RAM|vSphere 8.0,\nESXi Host 08,10.10.1.14,Hypervisor,server,vmware;compute;cluster-b,physical,00:50:56:AA:02:04,29,2,dc-rack-a2,42,false,false,,2131,2845,50,Dell PowerEdge R750|768GB RAM|vSphere 8.0,\nToR Switch A2,10.10.1.1,Top of Rack,switch,tor;access;rack-a2,physical,00:1A:2B:3C:5D:02,42,1,dc-rack-a2,42,false,false,,2165,2845,50,Cisco Nexus 93180YC-FX|48x25G ports,\nSAN Primary,10.10.2.10,Primary Storage,database,storage;san;netapp,physical,00:A0:98:AA:01:01,36,6,dc-rack-b1,42,false,false,,2123,2845,50,NetApp AFF A400|500TB Raw|FC 32Gb,\nSAN Secondary,10.10.2.11,Secondary Storage,database,storage;san;netapp,physical,00:A0:98:AA:01:02,28,6,dc-rack-b1,42,false,false,,2155,2845,50,NetApp AFF A400|500TB Raw|FC 32Gb,\nFC Switch 1,10.10.2.1,Fibre Channel,switch,storage;fc;fabric-a,physical,00:1A:2B:FC:01:01,42,1,dc-rack-b1,42,false,false,,2115,2845,50,Brocade G620|Fabric A,\nFC Switch 2,10.10.2.2,Fibre Channel,switch,storage;fc;fabric-b,physical,00:1A:2B:FC:01:02,41,1,dc-rack-b1,42,false,false,,2145,2845,50,Brocade G620|Fabric B,\nBackup Server 1,10.10.3.10,Backup Infrastructure,server,backup;veeam;protection,physical,00:50:56:BB:01:01,36,2,dc-rack-b2,42,false,false,,2107,2845,50,Veeam Backup Server|Dell R740xd|200TB,\nBackup Server 2,10.10.3.11,Backup Infrastructure,server,backup;veeam;protection,physical,00:50:56:BB:01:02,33,2,dc-rack-b2,42,false,false,,2134,2845,50,Veeam Backup Server|Dell R740xd|200TB,\nTape Library,10.10.3.20,Archival Storage,database,backup;tape;lto9,physical,00:50:56:BB:02:01,20,10,dc-rack-b2,42,false,false,,2099,2845,50,IBM TS4500|LTO-9|Long-term archive,\nToR Switch B1,10.10.2.3,Top of Rack,switch,tor;access;rack-b1,physical,00:1A:2B:3C:5D:03,40,1,dc-rack-b1,42,false,false,,2123,2845,50,Cisco Nexus 93180YC-FX,\nToR Switch B2,10.10.3.1,Top of Rack,switch,tor;access;rack-b2,physical,00:1A:2B:3C:5D:04,42,1,dc-rack-b2,42,false,false,,2091,2845,50,Cisco Nexus 93180YC-FX,\nWeb Server 1,172.16.0.11,Web Frontend,server,dmz;web;nginx,security,00:50:56:CC:01:01,20,1,dmz-rack,24,false,false,,2113,2845,50,NGINX reverse proxy|Public facing,\nWeb Server 2,172.16.0.12,Web Frontend,server,dmz;web;nginx,security,00:50:56:CC:01:02,18,1,dmz-rack,24,false,false,,2082,2845,50,NGINX reverse proxy|Public facing,\nWAF Appliance,172.16.0.5,Web Application Firewall,firewall,dmz;security;waf,security,00:50:56:CC:02:01,22,2,dmz-rack,24,false,false,,2102,2845,50,F5 BIG-IP ASM|OWASP protection,\nDMZ Load Balancer,172.16.0.3,Load Balancing,switch,dmz;lb;f5,security,00:50:56:CC:03:01,16,2,dmz-rack,24,false,false,,2074,2845,50,F5 BIG-IP LTM|VIP: 172.16.0.100,\nMail Gateway,172.16.0.25,Email Security,server,dmz;email;security,security,00:50:56:CC:04:01,14,1,dmz-rack,24,false,false,,2091,2845,50,Proofpoint Email Gateway|Spam/malware filtering,\nExternal DNS 1,172.16.0.53,External DNS,circle,dmz;dns;public,security,00:50:56:CC:05:01,12,1,dmz-rack,24,false,false,,2066,2845,50,BIND DNS|Authoritative for corp.com,\nExternal DNS 2,172.16.0.54,External DNS,circle,dmz;dns;public,security,00:50:56:CC:05:02,10,1,dmz-rack,24,false,false,,2080,2845,50,BIND DNS|Secondary for corp.com,\nvCenter Server,192.168.100.10,Virtualization Management,server,management;vmware;vcsa,logical,00:50:56:DD:01:01,20,2,mgmt-rack,24,false,false,,2057,2845,50,vCenter Server Appliance 8.0|Single SSO domain,\nNSX Manager,192.168.100.15,Network Virtualization,server,management;vmware;nsx,logical,00:50:56:DD:02:01,17,2,mgmt-rack,24,false,false,,2069,2845,50,NSX-T 4.1 Manager Cluster,\nSIEM Server,192.168.100.50,Security Monitoring,server,management;security;splunk,logical,00:50:56:DD:03:01,14,2,mgmt-rack,24,false,false,,2049,2845,50,Splunk Enterprise|Security monitoring,\nNetwork Monitoring,192.168.100.60,Network Management,server,management;monitoring;prtg,logical,00:50:56:DD:04:01,11,1,mgmt-rack,24,false,false,,2058,2845,50,PRTG Network Monitor|5000 sensors,\nJump Server,192.168.100.100,Bastion Host,server,management;security;bastion,logical,00:50:56:DD:05:01,9,1,mgmt-rack,24,false,false,,2040,2845,50,Windows Server 2022|MFA enabled,\nIPAM/DDI,192.168.100.70,IP Management,server,management;dns;dhcp,logical,00:50:56:DD:06:01,7,2,mgmt-rack,24,false,false,,2047,2845,50,Infoblox DDI|DNS/DHCP/IPAM,\nWLC Primary,10.20.0.1,Wireless Controller,wifi,wireless;cisco;9800,physical,00:1A:2B:WL:01:01,,2,,42,false,false,,1576,2306,50,Cisco C9800-40|Primary controller,\nWLC Secondary,10.20.0.2,Wireless Controller,wifi,wireless;cisco;9800,physical,00:1A:2B:WL:01:02,,2,,42,false,false,,1468,1564,50,Cisco C9800-40|HA Secondary,\nHQ Mobile Zone,10.20.10.0/24,Mobile Device Zone,phone,wireless;byod;mobile,physical,,,1,,42,false,false,,2355,2806,50,Corporate BYOD|MDM enrolled devices,\nGuest WiFi Zone,10.30.0.0/24,Guest Network,phone,wireless;guest;isolated,physical,,,1,,42,false,false,,2308,2611,50,Captive portal|Internet only,\nIoT Device Zone,10.40.0.0/24,IoT Network,phone,wireless;iot;building,physical,,,1,,42,false,false,,2229,2299,50,Building automation|Smart devices,\nNYC Branch Router,10.100.0.1,Branch Gateway,router,branch;nyc;sd-wan,physical,00:1A:2B:BR:01:01,,1,,42,false,false,,3152,634,50,Cisco Viptela vEdge|SD-WAN enabled,\nLA Branch Router,10.101.0.1,Branch Gateway,router,branch;la;sd-wan,physical,00:1A:2B:BR:02:01,,1,,42,false,false,,3084,507,50,Cisco Viptela vEdge|SD-WAN enabled,\nChicago Branch Router,10.102.0.1,Branch Gateway,router,branch;chicago;sd-wan,physical,00:1A:2B:BR:03:01,,1,,42,false,false,,3355,393,50,Cisco Viptela vEdge|SD-WAN enabled,\nLondon Branch Router,10.200.0.1,Branch Gateway,router,branch;london;sd-wan,physical,00:1A:2B:BR:04:01,,1,,42,false,false,,3114,260,50,Cisco Viptela vEdge|EMEA region,\nTokyo Branch Router,10.201.0.1,Branch Gateway,router,branch;tokyo;sd-wan,physical,00:1A:2B:BR:05:01,,1,,42,false,false,,3699,471,50,Cisco Viptela vEdge|APAC region,\"{\"\"all\"\":{\"\"icon\"\":{\"\"library\"\":\"\"selfhst\"\",\"\"name\"\":\"\"adguard-home\"\"}}}\"\nAWS Cloud,vpc-0a1b2c3d,Public Cloud,cloud,cloud;aws;hybrid,logical,,,1,,42,false,false,,3437,546,50,AWS US-East-1|VPC peering to HQ,\"{\"\"all\"\":{\"\"icon\"\":{\"\"library\"\":\"\"selfhst\"\",\"\"name\"\":\"\"ansible\"\"}}}\"\nAzure Cloud,vnet-corp-prod,Public Cloud,cloud,cloud;azure;hybrid,logical,,,1,,42,false,false,,2593,2724,50,Azure East US 2|ExpressRoute,\nGCP Cloud,vpc-gcp-corp,Public Cloud,cloud,cloud;gcp;dev,logical,,,1,,42,false,false,,2827,2731,50,GCP us-central1|Dev/Test workloads,\nISP Primary,203.0.113.1,Internet Uplink,globe,wan;internet;primary,physical,,,1,,42,false,false,,3712,616,50,AT&T MPLS|1 Gbps dedicated,\"{\"\"all\"\":{\"\"icon\"\":{\"\"library\"\":\"\"selfhst\"\",\"\"name\"\":\"\"wikidocs\"\"}}}\"\nISP Secondary,198.51.100.1,Internet Uplink,globe,wan;internet;backup,physical,,,1,,42,false,false,,2702,468,139,Verizon Business|500 Mbps backup,\"{\"\"all\"\":{\"\"icon\"\":{\"\"library\"\":\"\"selfhst\"\",\"\"name\"\":\"\"alist\"\"}}}\"\nDC1 Int DNS,10.10.0.53,Internal DNS/AD,circle,dns;ad;dc1,physical,00:50:56:AD:01:01,26,1,dc-rack-a1,42,false,false,,1958,2845,50,Windows Server 2022|Primary DC,\nDC2 Int DNS,10.10.1.53,Internal DNS/AD,circle,dns;ad;dc2,physical,00:50:56:AD:01:02,26,1,dc-rack-a2,42,false,false,,1964,2845,50,Windows Server 2022|Secondary DC,\nApp Server 01,10.10.0.101,Application,server,app;iis;web,physical,00:50:56:AP:01:01,24,1,dc-rack-a1,42,false,false,,1947,2845,50,Windows Server 2022|IIS Application,\nApp Server 02,10.10.0.102,Application,server,app;iis;web,physical,00:50:56:AP:01:02,22,1,dc-rack-a1,42,false,false,,1955,2845,50,Windows Server 2022|IIS Application,\nSQL Server 01,10.10.0.201,Database,database,db;sql;primary,physical,00:50:56:DB:01:01,20,2,dc-rack-a1,42,false,false,,1936,2845,50,SQL Server 2022 Enterprise|AlwaysOn Primary,\nSQL Server 02,10.10.1.201,Database,database,db;sql;secondary,physical,00:50:56:DB:01:02,24,2,dc-rack-a2,42,false,false,,1947,2845,50,SQL Server 2022 Enterprise|AlwaysOn Secondary,\nK8s Master 1,10.10.1.50,Container Orchestration,hexagon,kubernetes;master;container,physical,00:50:56:K8:01:01,21,1,dc-rack-a2,42,false,false,,1925,2845,50,K8s Control Plane|etcd member,\nK8s Master 2,10.10.1.51,Container Orchestration,hexagon,kubernetes;master;container,physical,00:50:56:K8:01:02,19,1,dc-rack-a2,42,false,false,,1938,2845,50,K8s Control Plane|etcd member,\nK8s Master 3,10.10.1.52,Container Orchestration,hexagon,kubernetes;master;container,physical,00:50:56:K8:01:03,17,1,dc-rack-a2,42,false,false,,1914,2845,50,K8s Control Plane|etcd member,\nK8s Worker 1,10.10.1.60,Container Workload,server,kubernetes;worker;container,physical,00:50:56:K8:02:01,15,1,dc-rack-a2,42,false,false,,1930,2845,50,K8s Worker Node|64GB RAM,\nK8s Worker 2,10.10.1.61,Container Workload,server,kubernetes;worker;container,physical,00:50:56:K8:02:02,13,1,dc-rack-a2,42,false,false,,1904,2845,50,K8s Worker Node|64GB RAM,\nK8s Worker 3,10.10.1.62,Container Workload,server,kubernetes;worker;container,physical,00:50:56:K8:02:03,11,1,dc-rack-a2,42,false,false,,1922,2845,50,K8s Worker Node|64GB RAM,\nK8s Worker 4,10.10.1.63,Container Workload,server,kubernetes;worker;container,physical,00:50:56:K8:02:04,9,1,dc-rack-a2,42,false,false,,1893,2845,50,K8s Worker Node|64GB RAM,\nProxy Server 1,10.5.0.10,Web Proxy,server,proxy;squid;filtering,security,00:50:56:PX:01:01,,1,,42,false,false,,1806,654,50,Squid Proxy|Content filtering,\nProxy Server 2,10.5.0.11,Web Proxy,server,proxy;squid;filtering,security,00:50:56:PX:01:02,,1,,42,false,false,,2937,2629,50,Squid Proxy|HA pair,\nVPN Concentrator,10.0.5.1,Remote Access VPN,firewall,vpn;remote;security,security,00:1A:2B:VP:01:01,,2,,42,false,false,,3642,947,50,Cisco ASA 5555-X|AnyConnect SSL VPN,\nNAC Server,10.5.5.10,Network Access Control,server,nac;ise;802.1x,security,00:50:56:NA:01:01,,2,,42,false,false,,1153,1172,50,Cisco ISE 3.1|RADIUS/TACACS+,\nPrint Server,10.10.0.150,Print Services,server,print;windows;services,physical,00:50:56:PR:01:01,18,1,dc-rack-a1,42,false,false,,1897,2845,50,Windows Print Server|50+ printers,\nFile Server,10.10.0.160,File Services,database,file;smb;dfs,physical,00:50:56:FS:01:01,16,2,dc-rack-a1,42,false,false,,1861,2845,50,Windows File Server|DFS namespace,\nCertificate Authority,192.168.100.80,PKI Infrastructure,server,pki;ca;security,logical,00:50:56:CA:01:01,5,1,mgmt-rack,24,false,false,,1889,2845,50,Windows CA|Enterprise Root CA,\nSCCM Server,192.168.100.90,Endpoint Management,server,sccm;patching;software,logical,00:50:56:SC:01:01,3,2,mgmt-rack,24,false,false,,1850,2845,50,MECM Primary Site|Software deployment,\nVoIP Cluster,10.50.0.0/24,Voice Services,phone,voip;cisco;ucm,application,,,1,,42,false,false,,1777,1617,50,Cisco UCM Cluster|3000 endpoints,\nVideo Conference,10.51.0.0/24,Video Services,laptop,video;webex;teams,application,,,1,,42,false,false,,1994,2245,50,Webex/Teams integration|Meeting rooms,\nSecurity Cameras,10.60.0.0/24,Physical Security,camera,cctv;surveillance;security,physical,,,1,,42,false,false,,1674,2046,50,150+ IP cameras|30-day retention,\nNVR Cluster,10.60.0.10,Video Recording,server,nvr;surveillance;storage,physical,00:50:56:NV:01:01,15,4,dc-rack-b2,42,false,false,,1829,2845,50,Milestone XProtect|500TB storage,\nDev Server 1,10.80.0.10,Development,server,dev;gitlab;ci-cd;[object Object],application,00:50:56:DV:01:01,,2,,42,false,false,,2801,1176,128,GitLab Server|CI/CD pipelines,\"{\"\"all\"\":{\"\"icon\"\":{\"\"library\"\":\"\"simple\"\",\"\"name\"\":\"\"amazonwebservices\"\"}}}\"\nDev Server 2,10.80.0.11,Development,server,dev;jenkins;ci-cd,application,00:50:56:DV:01:02,,2,,42,false,false,,1945,1165,50,Jenkins Server|Build automation,\nTest Environment,10.81.0.0/24,QA/Testing,hexagon,test;qa;staging,application,,,1,,42,false,false,,2567,885,148,Staging environment|Pre-prod validation,\"{\"\"all\"\":{\"\"icon\"\":{\"\"library\"\":\"\"simple\"\",\"\"name\"\":\"\"apple\"\"}}}\"\nERP System,10.90.0.10,Business Application,database,erp;sap;business,application,00:50:56:ER:01:01,,4,,42,false,false,,790,474,50,SAP S/4HANA|Financial/HR systems,\nCRM System,10.91.0.10,Business Application,database,crm;salesforce;business,application,,,1,,42,false,false,,3515,1138,50,Salesforce integration|Sales/Marketing,\nCorporate Endpoints,10.70.0.0/22,User Workstations,laptop,endpoints;workstations;users,physical,,,1,,42,false,false,,992,2284,50,~1000 corporate laptops|Windows 11,\nFloor 1 Switch,10.1.1.1,Distribution,switch,distribution;floor-1;access,physical,00:1A:2B:FL:01:01,,1,,42,false,false,,654,2020,50,Cisco C9300-48P|PoE+ enabled,\nFloor 2 Switch,10.1.2.1,Distribution,switch,distribution;floor-2;access,physical,00:1A:2B:FL:02:01,,1,,42,false,false,,854,1843,50,Cisco C9300-48P|PoE+ enabled,\nFloor 3 Switch,10.1.3.1,Distribution,switch,distribution;floor-3;access,physical,00:1A:2B:FL:03:01,,1,,42,false,false,,1899,1457,50,Cisco C9300-48P|PoE+ enabled,\nFloor 4 Switch,10.1.4.1,Distribution,switch,distribution;floor-4;access,physical,00:1A:2B:FL:04:01,,1,,42,false,false,,489,181,50,Cisco C9300-48P|PoE+ enabled,\nAP Floor 1 Zone 1,10.20.1.10,Wireless Access,wifi,wifi;ap;floor-1,physical,00:1A:2B:AP:01:01,,1,,42,false,false,,1140,2070,50,Cisco 9120AX|Wi-Fi 6,\nAP Floor 2 Zone 1,10.20.2.10,Wireless Access,wifi,wifi;ap;floor-2,physical,00:1A:2B:AP:02:01,,1,,42,false,false,,688,2384,50,Cisco 9120AX|Wi-Fi 6,\nAP Floor 3 Zone 1,10.20.3.10,Wireless Access,wifi,wifi;ap;floor-3,physical,00:1A:2B:AP:03:01,,1,,42,false,false,,2145,1890,50,Cisco 9120AX|Wi-Fi 6,\nAP Floor 4 Zone 1,10.20.4.10,Wireless Access,wifi,wifi;ap;floor-4,physical,00:1A:2B:AP:04:01,,1,,42,false,false,,518,566,50,Cisco 9120AX|Wi-Fi 6,\nUPS DC-1,192.168.200.10,Power Management,rectangle,power;ups;datacenter,physical,,,1,,42,false,false,,771,296,50,APC Symmetra|80kVA|30 min runtime,\nUPS DC-2,192.168.200.11,Power Management,rectangle,power;ups;datacenter,physical,,,1,,42,false,false,,216,330,50,APC Symmetra|80kVA|Redundant,\nPDU Rack A1,192.168.200.21,Power Distribution,rectangle,power;pdu;rack-a1,physical,,1,1,dc-rack-a1,42,false,false,,1805,2845,50,APC Switched PDU|Per-outlet metering,\nPDU Rack A2,192.168.200.22,Power Distribution,rectangle,power;pdu;rack-a2,physical,,1,1,dc-rack-a2,42,false,false,,1742,2845,50,APC Switched PDU|Per-outlet metering,\nCRAC Unit 1,192.168.200.30,Cooling,rectangle,cooling;hvac;datacenter,physical,,,1,,42,false,false,,246,626,50,Liebert CRV|Row-based cooling,\nCRAC Unit 2,192.168.200.31,Cooling,rectangle,cooling;hvac;datacenter,physical,,,1,,42,false,false,,1603,981,50,Liebert CRV|N+1 redundancy,\ncamera A,0.0.0.0,,camera,,physical,,,1,,,false,false,,167,145,45,,\ncamera B,0.0.0.0,,camera,,physical,,,1,,,false,false,,1041,738,45,,\n"
  },
  {
    "path": "demos/csv-exports/the-one-file-networkening-homelab.csv",
    "content": "#THEONEFILE_CONFIG:{\"nodeData\":{\"internet\":{\"shape\":\"square\",\"name\":\"Internet\",\"ip\":\"0.0.0.0\",\"role\":\"\",\"tags\":[],\"notes\":[],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"internet-copy\":{\"shape\":\"firewall\",\"name\":\"OPNSENSE\",\"ip\":\"0.0.0.0\",\"role\":\"\",\"tags\":[],\"notes\":[],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"opnsense-copy\":{\"shape\":\"firewall\",\"name\":\"Docker\",\"ip\":\"0.0.0.0\",\"role\":\"\",\"tags\":[],\"notes\":[],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"docker-copy\":{\"shape\":\"firewall\",\"name\":\"Docker2\",\"ip\":\"0.0.0.0\",\"role\":\"\",\"tags\":[],\"notes\":[],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"docker-copy-1\":{\"shape\":\"firewall\",\"name\":\"Docker3\",\"ip\":\"0.0.0.0\",\"role\":\"\",\"tags\":[],\"notes\":[],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"docker-copy-2\":{\"shape\":\"firewall\",\"name\":\"Docker 4\",\"ip\":\"0.0.0.0\",\"role\":\"\",\"tags\":[{\"type\":\"icon\",\"library\":\"selfhst\",\"name\":\"docker\"},{\"type\":\"icon\",\"library\":\"selfhst\",\"name\":\"authentik\"},{\"type\":\"icon\",\"library\":\"selfhst\",\"name\":\"immich\"}],\"notes\":[],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"opnsense-copy-1\":{\"shape\":\"firewall\",\"name\":\"OPNSENSE GUEST\",\"ip\":\"0.0.0.0\",\"role\":\"\",\"tags\":[],\"notes\":[],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"phone\":{\"shape\":\"phone\",\"name\":\"Phone\",\"ip\":\"0.0.0.0\",\"role\":\"\",\"tags\":[],\"notes\":[],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"desktop\":{\"shape\":\"pc\",\"name\":\"Desktop\",\"ip\":\"0.0.0.0\",\"role\":\"\",\"tags\":[],\"notes\":[],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"dns\":{\"shape\":\"cloud\",\"name\":\"DNS\",\"ip\":\"0.0.0.0\",\"role\":\"\",\"tags\":[],\"notes\":[],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"racked\":{\"shape\":\"server\",\"name\":\"Racked\",\"ip\":\"\",\"role\":\"Rack\",\"tags\":[],\"notes\":[],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":true,\"locked\":false,\"groupId\":null}},\"edgeData\":{\"list\":[{\"id\":\"internet-internet-copy-1765238145151\",\"from\":\"internet\",\"to\":\"internet-copy\",\"width\":4,\"color\":\"#55e208\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1},{\"id\":\"internet-copy-opnsense-copy-1765238187451\",\"from\":\"internet-copy\",\"to\":\"opnsense-copy\",\"width\":4,\"color\":\"#4c00ff\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1},{\"id\":\"internet-copy-docker-copy-1765238242477\",\"from\":\"internet-copy\",\"to\":\"docker-copy\",\"width\":4,\"color\":\"#4c00ff\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1},{\"id\":\"internet-copy-docker-copy-1-1765238244637\",\"from\":\"internet-copy\",\"to\":\"docker-copy-1\",\"width\":4,\"color\":\"#4c00ff\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1},{\"id\":\"internet-copy-docker-copy-2-1765238246233\",\"from\":\"internet-copy\",\"to\":\"docker-copy-2\",\"width\":4,\"color\":\"#4c00ff\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1},{\"id\":\"internet-opnsense-copy-1-1765238266117\",\"from\":\"internet\",\"to\":\"opnsense-copy-1\",\"width\":4,\"color\":\"#80ff00\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1},{\"id\":\"opnsense-copy-1-dns-1765238347996\",\"from\":\"opnsense-copy-1\",\"to\":\"dns\",\"width\":4,\"color\":\"#fb00ff\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1},{\"id\":\"dns-desktop-1765238386101\",\"from\":\"dns\",\"to\":\"desktop\",\"width\":4,\"color\":\"#ff00d0\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1},{\"id\":\"phone-dns-1765238391156\",\"from\":\"phone\",\"to\":\"dns\",\"width\":4,\"color\":\"#ff00d0\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1},{\"id\":\"custom-1765239449323\",\"type\":\"custom\",\"color\":\"#f97316\",\"width\":4,\"lineStyle\":\"solid\",\"direction\":\"forward\",\"points\":[{\"x\":2936.464111328125,\"y\":786.07958984375},{\"x\":3184.112060546875,\"y\":887.6153564453125},{\"x\":2763.110107421875,\"y\":981.7216796875}],\"notes\":[]}]},\"rectData\":{\"list\":[{\"id\":\"rect-1765238219615\",\"x\":2680.053955078125,\"y\":251.44879150390625,\"width\":814.10400390625,\"height\":389.26678466796875,\"color\":\"#ec0999\",\"style\":\"filled\",\"lineStyle\":\"solid\",\"notes\":[]}]},\"textData\":{\"list\":[{\"id\":\"text-1765238422602\",\"x\":2466.35986328125,\"y\":741.6801147460938,\"content\":\"Double click on desktop\\nor long press on mobile\\nto enter rack canvas view\",\"fontSize\":40,\"color\":\"#e2e8f0\",\"fontWeight\":\"normal\",\"fontStyle\":\"normal\",\"textAlign\":\"start\",\"textDecoration\":\"none\",\"bgColor\":\"#000000\",\"bgEnabled\":false,\"opacity\":1}]},\"edgeLegend\":{\"#475569\":\"you can edit me too\",\"#65758b\":\"you can edit me too\",\"#63748c\":\"you can edit me too\",\"#5e6f87\":\"you can edit me too\",\"#586a84\":\"you can edit me too\",\"#4f627d\":\"you can edit me too\",\"#455873\":\"you can edit me too\",\"#3d506c\":\"you can edit me too\",\"#354964\":\"you can edit me too\",\"#2e415c\":\"you can edit me too\",\"#293c56\":\"you can edit me too\",\"#273a53\":\"you can edit me too\",\"#253750\":\"you can edit me too\",\"#23354d\":\"you can edit me too\",\"#203046\":\"you can edit me too\",\"#1e2d43\":\"you can edit me too\",\"#1a283d\":\"you can edit me too\",\"#172435\":\"you can edit me too\",\"#141f2e\":\"you can edit me too\",\"#111a27\":\"you can edit me too\",\"#0f1824\":\"you can edit me too\",\"#0d1521\":\"you can edit me too\",\"#0c131d\":\"you can edit me too\",\"#0c1d1c\":\"you can edit me too\",\"#0c1c1d\":\"you can edit me too\",\"#0c191d\":\"you can edit me too\",\"#0c141d\":\"you can edit me too\",\"#0c0d1d\":\"you can edit me too\",\"#130c1d\":\"you can edit me too\",\"#1b0c1d\":\"you can edit me too\",\"#1d0c17\":\"you can edit me too\",\"#1d0c10\":\"you can edit me too\",\"#1d0c0c\":\"you can edit me too\",\"#3b1b1b\":\"you can edit me too\",\"#3c1a1a\":\"you can edit me too\",\"#3f1c1c\":\"you can edit me too\",\"#401c1c\":\"you can edit me too\",\"#451c1c\":\"you can edit me too\",\"#461b1b\":\"you can edit me too\",\"#4c1a1a\":\"you can edit me too\",\"#521919\":\"you can edit me too\",\"#571919\":\"you can edit me too\",\"#5d1818\":\"you can edit me too\",\"#631717\":\"you can edit me too\",\"#651515\":\"you can edit me too\",\"#6a1616\":\"you can edit me too\",\"#6f1515\":\"you can edit me too\",\"#711414\":\"you can edit me too\",\"#761414\":\"you can edit me too\",\"#771313\":\"you can edit me too\",\"#7c1313\":\"you can edit me too\",\"#811313\":\"you can edit me too\",\"#821212\":\"you can edit me too\",\"#871212\":\"you can edit me too\",\"#881111\":\"you can edit me too\",\"#8d1111\":\"you can edit me too\",\"#8e1010\":\"you can edit me too\",\"#8f0f0f\":\"you can edit me too\",\"#900e0e\":\"you can edit me too\",\"#8e0b0b\":\"you can edit me too\",\"#8c0d0d\":\"you can edit me too\",\"#880c0c\":\"you can edit me too\",\"#830c0c\":\"you can edit me too\",\"#7e0c0c\":\"you can edit me too\",\"#790c0c\":\"you can edit me too\",\"#730c0c\":\"you can edit me too\",\"#6f0b0b\":\"you can edit me too\",\"#0b6f64\":\"you can edit me too\",\"#0b6f5f\":\"you can edit me too\",\"#0b6f56\":\"you can edit me too\",\"#0b6f49\":\"you can edit me too\",\"#0b6f31\":\"you can edit me too\",\"#0b6f1f\":\"you can edit me too\",\"#0b6f0d\":\"you can edit me too\",\"#176f0b\":\"you can edit me too\",\"#266f0b\":\"you can edit me too\",\"#296f0b\":\"you can edit me too\",\"#2e6f0b\":\"you can edit me too\",\"#1a2d10\":\"you can edit me too\",\"#1c3111\":\"you can edit me too\",\"#213814\":\"you can edit me too\",\"#233c15\":\"you can edit me too\",\"#254017\":\"you can edit me too\",\"#294918\":\"you can edit me too\",\"#2b4d1a\":\"you can edit me too\",\"#2d511a\":\"you can edit me too\",\"#315a1b\":\"you can edit me too\",\"#35631c\":\"you can edit me too\",\"#37681d\":\"you can edit me too\",\"#3b721d\":\"you can edit me too\",\"#3f7b1e\":\"you can edit me too\",\"#42851e\":\"you can edit me too\",\"#46901d\":\"you can edit me too\",\"#499a1d\":\"you can edit me too\",\"#4b9f1d\":\"you can edit me too\",\"#4ca61c\":\"you can edit me too\",\"#50b01c\":\"you can edit me too\",\"#51b71a\":\"you can edit me too\",\"#50b918\":\"you can edit me too\",\"#51c115\":\"you can edit me too\",\"#53c615\":\"you can edit me too\",\"#53c814\":\"you can edit me too\",\"#52c913\":\"you can edit me too\",\"#54d011\":\"you can edit me too\",\"#53d110\":\"you can edit me too\",\"#55d510\":\"you can edit me too\",\"#55d70f\":\"you can edit me too\",\"#54d80e\":\"you can edit me too\",\"#54da0b\":\"you can edit me too\",\"#56df0c\":\"you can edit me too\",\"#53db0a\":\"you can edit me too\",\"#55e00b\":\"you can edit me too\",\"#55e109\":\"you can edit me too\",\"#55e208\":\"ISP LINE\",\"#4c00ff\":\"MY Guest NETWORK\",\"#80ff00\":\"you can edit me too\",\"#3b4234\":\"you can edit me too\",\"#3a3442\":\"you can edit me too\",\"#3b3442\":\"you can edit me too\",\"#3c3442\":\"you can edit me too\",\"#3d3442\":\"you can edit me too\",\"#3e3442\":\"you can edit me too\",\"#3f3442\":\"you can edit me too\",\"#403442\":\"you can edit me too\",\"#413442\":\"you can edit me too\",\"#653d66\":\"you can edit me too\",\"#683f69\":\"you can edit me too\",\"#6c416c\":\"you can edit me too\",\"#6f4370\":\"you can edit me too\",\"#704270\":\"you can edit me too\",\"#734474\":\"you can edit me too\",\"#784479\":\"you can edit me too\",\"#7d447e\":\"you can edit me too\",\"#7e437f\":\"you can edit me too\",\"#834384\":\"you can edit me too\",\"#844285\":\"you can edit me too\",\"#89418b\":\"you can edit me too\",\"#8e428f\":\"you can edit me too\",\"#904091\":\"you can edit me too\",\"#923e93\":\"you can edit me too\",\"#973e98\":\"you can edit me too\",\"#943c96\":\"you can edit me too\",\"#993c9a\":\"you can edit me too\",\"#963a98\":\"you can edit me too\",\"#973899\":\"you can edit me too\",\"#99369b\":\"you can edit me too\",\"#9a359c\":\"you can edit me too\",\"#9b349d\":\"you can edit me too\",\"#9d329f\":\"you can edit me too\",\"#9e31a0\":\"you can edit me too\",\"#a02fa2\":\"you can edit me too\",\"#9d2d9f\":\"you can edit me too\",\"#9f2ba1\":\"you can edit me too\",\"#a129a3\":\"you can edit me too\",\"#a327a5\":\"you can edit me too\",\"#a525a7\":\"you can edit me too\",\"#a723a9\":\"you can edit me too\",\"#a921ab\":\"you can edit me too\",\"#ab1fad\":\"you can edit me too\",\"#ad1daf\":\"you can edit me too\",\"#ae1cb0\":\"you can edit me too\",\"#b019b3\":\"you can edit me too\",\"#b118b4\":\"you can edit me too\",\"#b316b6\":\"you can edit me too\",\"#b816bb\":\"you can edit me too\",\"#b514b8\":\"you can edit me too\",\"#ba14bd\":\"you can edit me too\",\"#b712ba\":\"you can edit me too\",\"#bb13be\":\"you can edit me too\",\"#b811bb\":\"you can edit me too\",\"#be10c1\":\"you can edit me too\",\"#bb0ebe\":\"you can edit me too\",\"#bd0cc0\":\"you can edit me too\",\"#be0bc1\":\"you can edit me too\",\"#c108c4\":\"you can edit me too\",\"#be06c1\":\"you can edit me too\",\"#c103c4\":\"you can edit me too\",\"#c301c6\":\"you can edit me too\",\"#c400c7\":\"you can edit me too\",\"#c900cc\":\"you can edit me too\",\"#ce00d1\":\"you can edit me too\",\"#d300d6\":\"you can edit me too\",\"#d800db\":\"you can edit me too\",\"#dd00e0\":\"you can edit me too\",\"#e200e6\":\"you can edit me too\",\"#ec00f0\":\"you can edit me too\",\"#f100f5\":\"you can edit me too\",\"#f600fa\":\"you can edit me too\",\"#fb00ff\":\"you can edit me too\",\"#ff00d0\":\"iPhone (always guest iPhone)\",\"#f97316\":\"you can edit me too\"},\"nodePositions\":{\"internet\":{\"x\":2103.968290880771,\"y\":268},\"internet-copy\":{\"x\":2066.9677515897347,\"y\":473.4119134177565},\"opnsense-copy\":{\"x\":1773.8400660428597,\"y\":666.5758233298659},\"docker-copy\":{\"x\":1931.1978950081452,\"y\":782.2775961320921},\"docker-copy-1\":{\"x\":2158.1262397347077,\"y\":767.7122274797483},\"docker-copy-2\":{\"x\":2342.2663764534577,\"y\":631.7681967180296},\"opnsense-copy-1\":{\"x\":2757.879480087803,\"y\":307.6117116091891},\"phone\":{\"x\":3312.857751572178,\"y\":502.58220111114224},\"desktop\":{\"x\":2971.700036728428,\"y\":480.7287465212985},\"dns\":{\"x\":3200.4643189549906,\"y\":320.469591247861},\"racked\":{\"x\":2645.5845448279656,\"y\":970.7820678889219}},\"nodeSizes\":{\"core-router-1\":36,\"internet\":168,\"phone\":121,\"desktop\":147,\"racked\":137,\"docker-copy-2\":82},\"nodeStyles\":{\"internet\":{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"amazon-web-services\"},\"circleColor\":\"#ffffff\",\"circleBorder\":\"#ffffff\"}},\"opnsense-copy-1\":{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"opnsense-v1\"}}},\"internet-copy\":{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"opnsense\"}}},\"docker-copy-2\":{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"docker\"}}},\"docker-copy-1\":{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"authportal\"}}},\"docker-copy\":{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"jotty\"}}},\"opnsense-copy\":{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"portainer\"}}},\"racked\":{\"all\":{\"icon\":{\"library\":\"mdi\",\"name\":\"server-security\"},\"circleColor\":\"#010813\",\"circleBorder\":\"#ffffff\"}}},\"iconCache\":{\"selfhst-borg\":\"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" xml:space=\\\"preserve\\\" viewBox=\\\"0 0 512 512\\\"><path d=\\\"M331 102.2H204.5V0h197.9L505 102.2v104.2l-51.3 51.3L505 309v100.7L402.4 512H204.5V409.8H331V300.6H204.5v-89.1H331zM6.9 0h170.5v512H6.9z\\\" style=\\\"fill:#0d0\\\"/></svg>\",\"selfhst-actual-budget\":\"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" xml:space=\\\"preserve\\\" viewBox=\\\"0 0 512 512\\\"><path d=\\\"M378.5 512h-245C59.8 512 0 452.2 0 378.5v-245C0 59.8 59.8 0 133.5 0h245C452.2 0 512 59.8 512 133.5v245c0 73.7-59.8 133.5-133.5 133.5\\\" style=\\\"fill:#805ad5\\\"/><path d=\\\"m407.7 384.1-33.2-68.2 26.9-10.4c1.7-.6 2.6-2.6 1.9-4.3l-7.8-20.2c-.6-1.7-2.6-2.6-4.3-1.9L362 290.3 268.8 99.2c-.6-1.2-1.7-1.9-3-1.9h-6.3c-1.3 0-2.4.7-3 1.9l-95 201.6-55.2 20.3c-1.8.6-2.6 2.6-2 4.3l7.4 20.3c.6 1.8 2.6 2.6 4.3 2l28.4-10.4-32.2 68.3c-.3.8-.4 1.7 0 2.5l1.6 4.3v.1c.6 1.7 2.6 2.6 4.3 1.9l229.8-88.3 34.3 70.4c.8 1.7 2.8 2.4 4.5 1.6l19.5-9.5c1.6-.8 2.3-2.8 1.5-4.5M263 151.8l46.1 94.9L199.4 287zM161.5 367.4l20.7-44 139.4-51.1 13.7 28.3z\\\" style=\\\"fill:#fff\\\"/></svg>\",\"selfhst-anonaddy\":\"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" xml:space=\\\"preserve\\\" viewBox=\\\"0 0 512 512\\\"><linearGradient id=\\\"anonaddy_svg__a\\\" x1=\\\"44.378\\\" x2=\\\"447.022\\\" y1=\\\"386.378\\\" y2=\\\"789.022\\\" gradientTransform=\\\"translate(0 -278)\\\" gradientUnits=\\\"userSpaceOnUse\\\"><stop offset=\\\"0\\\" style=\\\"stop-color:#3ae7e1\\\"/><stop offset=\\\".948\\\" style=\\\"stop-color:#1993d2\\\"/></linearGradient><path d=\\\"M320 224H192c-17.7 0-32 14.3-32 32v128c0 17.7 14.3 32 32 32h128c17.7 0 32-14.3 32-32V256c0-17.7-14.3-32-32-32m-32 128h-64v-64h64z\\\" style=\\\"fill:url(#anonaddy_svg__a)\\\"/><linearGradient id=\\\"anonaddy_svg__b\\\" x1=\\\"44.337\\\" x2=\\\"446.981\\\" y1=\\\"386.418\\\" y2=\\\"789.062\\\" gradientTransform=\\\"translate(0 -278)\\\" gradientUnits=\\\"userSpaceOnUse\\\"><stop offset=\\\"0\\\" style=\\\"stop-color:#3ae7e1\\\"/><stop offset=\\\".948\\\" style=\\\"stop-color:#1993d2\\\"/></linearGradient><path d=\\\"M384 384c0 5.5-.8 10.9-2.1 16 39.4-21.8 66.1-63.8 66.1-112v-32c0-47.4-25.8-88.6-64-110.7-18.8-10.9-40.7-17.3-64-17.3H192c-23.3 0-45.2 6.4-64 17.3-38.2 22.1-64 63.3-64 110.7v128c0 70.7 57.3 128 128 128h128c0-35.3-28.7-64-64-64h-64c-35.3 0-64-28.7-64-64V256c0-35.3 28.7-64 64-64h128c35.3 0 64 28.7 64 64z\\\" style=\\\"fill:url(#anonaddy_svg__b)\\\"/><path d=\\\"M384 288c0 23.7-12.9 44.3-32 55.4V384c0 17.7-14.3 32-32 32 22.5 0 43.5-5.8 61.9-16 1.3-5.1 2.1-10.5 2.1-16zM128 145.3c18.8-10.9 40.7-17.3 64-17.3 0-11.7 3.2-22.6 8.7-32H192c-21.4 0-42.7 4.4-62.4 12.8-1 6.3-1.6 12.7-1.6 19.2zM311.3 96c5.5 9.4 8.7 20.3 8.7 32 23.3 0 45.2 6.4 64 17.3V128c0-6.5-.6-12.9-1.6-19.2-19.7-8.4-41-12.8-62.4-12.8z\\\" style=\\\"fill:#2d7aae\\\"/><linearGradient id=\\\"anonaddy_svg__c\\\" x1=\\\"158.782\\\" x2=\\\"561.426\\\" y1=\\\"271.974\\\" y2=\\\"674.618\\\" gradientTransform=\\\"translate(0 -278)\\\" gradientUnits=\\\"userSpaceOnUse\\\"><stop offset=\\\"0\\\" style=\\\"stop-color:#3ae7e1\\\"/><stop offset=\\\".948\\\" style=\\\"stop-color:#1993d2\\\"/></linearGradient><path d=\\\"M200.7 96c11.1-19.1 31.7-32 55.3-32s44.3 12.9 55.3 32h8.7c21.4 0 42.7 4.4 62.4 12.8C373.1 47.3 320 0 256 0S138.9 47.3 129.6 108.8c19.7-8.4 41-12.8 62.4-12.8z\\\" style=\\\"fill:url(#anonaddy_svg__c)\\\"/></svg>\",\"selfhst-adguard-home\":\"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" xml:space=\\\"preserve\\\" viewBox=\\\"0 0 512 512\\\"><path d=\\\"M505.8 58.8C428.2 18.4 334.1 0 256 0h-.3v511.8c-21.5-12.7-41.1-26-59.1-39.6 18 13.7 37.8 27 59.4 39.8C506.8 363.4 505.8 146 505.8 58.8\\\" style=\\\"fill-rule:evenodd;clip-rule:evenodd;fill:#68bc71\\\"/><path d=\\\"M255.7 511.8C5.2 363.3 6.2 146 6.2 58.8 83.7 18.4 177.7 0 255.7 0z\\\" style=\\\"fill-rule:evenodd;clip-rule:evenodd;fill:#67b279\\\"/><path d=\\\"m246.9 341.6 151-203.6c-11.1-8.9-20.8-2.6-26.1 2.2h-.2l-125.9 131-47.4-57.1c-22.6-26.2-53.4-6.2-60.6-.9z\\\" style=\\\"fill-rule:evenodd;clip-rule:evenodd;fill:#fff\\\"/></svg>\",\"selfhst-ansible\":\"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" xml:space=\\\"preserve\\\" viewBox=\\\"0 0 512 512\\\"><path d=\\\"M512 256c0 141.4-114.6 256-256 256S0 397.4 0 256 114.6 0 256 0s256 114.6 256 256\\\" style=\\\"fill:#c32214\\\"/><path d=\\\"m260.3 156.4 66.2 163.5-100.1-78.8zM378 357.6 276.1 112.3c-2.9-7.1-8.7-10.8-15.8-10.8s-13.3 3.7-16.2 10.8l-111.8 269h38.3l44.3-110.9L347 377.1c5.3 4.3 9.1 6.2 14.1 6.2 10 0 18.7-7.5 18.7-18.3-.1-1.7-.7-4.4-1.8-7.4\\\" style=\\\"fill:#fff\\\"/></svg>\",\"selfhst-wikidocs\":\"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" xml:space=\\\"preserve\\\" viewBox=\\\"0 0 512 512\\\"><path d=\\\"M488.8 72.1h-90.7c-8.4 0-15.1 6.7-15.1 15.1v206.4c0 8.4-6.7 15.1-15.1 15.1h-45.3c-8.4 0-15.1-6.7-15.1-15.1V87.2c0-8.4-6.7-15.1-15.1-15.1h-72.5c-8.4 0-15.1 6.7-15.1 15.1v206.4c0 8.4-6.7 15.1-15.1 15.1h-45.3c-8.4 0-15.1-6.7-15.1-15.1V87.2c0-8.4-6.7-15.1-15.1-15.1H23.4c-8.4 0-15.1 6.7-15.1 15.1v257.2c0 50.1 38.6 91.2 87.5 95.3v.3h392.9c8.4 0 15.1-6.7 15.1-15.1V87.2c0-8.4-6.7-15.1-15-15.1\\\" style=\\\"fill:#4caf50\\\"/><path d=\\\"M488.6 448.2H95.7c-1.1 0-2.1-.2-3.1-.6-24.9-2.7-48-14.2-65.1-32.8C9.8 395.6 0 370.5 0 344.3V87.2c0-12.9 10.5-23.4 23.4-23.4h90.9c12.9 0 23.4 10.5 23.4 23.4v206.4c0 3.8 3 6.8 6.8 6.8h45.3c3.8 0 6.8-3 6.8-6.8V87.2c0-12.9 10.5-23.4 23.4-23.4h72.5c12.9 0 23.4 10.5 23.4 23.4v206.4c0 3.8 3 6.8 6.8 6.8H368c3.8 0 6.8-3 6.8-6.8V87.2c0-12.9 10.5-23.4 23.4-23.4h90.7c6.3 0 12.2 2.5 16.6 6.9s6.7 10.3 6.6 16.6v337.6c-.1 12.8-10.6 23.3-23.5 23.3M98.1 431.7h390.6c3.8 0 6.8-3 6.8-6.8V87.2c0-1.9-.6-3.6-1.9-4.8-1.3-1.3-3-2-4.8-2h-90.7c-3.8 0-6.8 3-6.8 6.8v206.4c0 12.9-10.5 23.4-23.4 23.4h-45.3c-12.9 0-23.4-10.5-23.4-23.4V87.2c0-3.8-3-6.8-6.8-6.8h-72.5c-3.8 0-6.8 3-6.8 6.8v206.4c0 12.9-10.5 23.4-23.4 23.4h-45.3c-12.9 0-23.4-10.5-23.4-23.4V87.2c0-3.8-3-6.8-6.8-6.8H23.4c-3.8 0-6.8 3-6.8 6.8v257.2c0 45.1 35.1 83.3 79.9 87.1.5-.1 1 0 1.6.2\\\" style=\\\"fill:#fff\\\"/></svg>\",\"selfhst-alist\":\"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" xml:space=\\\"preserve\\\" viewBox=\\\"0 0 512 512\\\"><path d=\\\"M260.5 30.8c5.4-.6 11 .6 15.5 3.7 6.8 4.4 11.6 11.1 15.7 17.9 18.4 30.9 37 61.7 55.4 92.6 24.5 41.3 49.1 82.7 73.6 124q25.35 42.9 51 85.8c11.2 19.1 22.8 37.9 33.5 57.3 4.6 8.1 7.2 17.5 6.8 26.8-.3 7.2-1.6 14.7-5.4 20.9-5.3 8.8-14.3 15.1-23.9 18.3-5.2 1.9-10.6 3.1-16.1 3.1H286.3c-5.2-.1-10.4-.7-15.3-2.6-3.5-1.5-7-3.4-9.8-6.1-4.7-4.7-7.8-11.3-7-18.1.4-4.7 1.6-9.4 3.5-13.7 1.9-4.4 4.5-8.5 6.8-12.7 10.1-17.9 20.6-35.6 31.5-53 3.5-5.5 6.6-11.4 11.4-15.8 5.2-4.7 11.6-8.4 18.7-8.9 5.7-.2 11.3 1.7 16.1 4.8 5.4 3.1 9.6 8.4 11 14.5 2 7.7-.2 15.8-3.4 22.9-3.7 8-8.9 15.3-11.7 23.7-.5 1.9-1.2 4.3.2 6 1.4 1.4 3.5 1.9 5.3 2.4 5.5 1.2 11.1.9 16.6.9h61.3c4-.1 8.1.1 12.1-.1 2.9-.3 6.1-.8 8.4-2.9 1.5-1.3 1.7-3.6 1.1-5.4-1.2-4-3.4-7.7-5.5-11.3C383.2 312.9 329 219.7 274 126.9c-1.8-3-3.3-6.2-5.9-8.6-1.3-1.2-3.2-1.9-4.9-1.4-1.5.5-2.5 1.8-3.5 3-2.4 3.1-4.6 6.4-6.6 9.8-9.5 15.4-18.6 31.1-27.9 46.6-53.6 90.1-107.1 180.3-160.7 270.4-3.3 6.1-6.9 12.1-10.7 17.9-3.4 5-7.5 9.6-12.8 12.6-6.2 3.6-13.9 5-20.9 3-5.1-1.6-10-4.1-13.7-8.1-4.6-4.6-6.7-11.3-6.4-17.7.2-6.7 2.5-13.3 5.9-19.1C38.4 380.1 71.1 325 104 269.9c16.9-28.5 33.7-57.1 50.7-85.6 17.2-28.8 34.4-57.7 51.5-86.5 9.5-16.3 19.2-32.5 29.2-48.5 3.1-5.1 7.1-9.6 11.9-13.1 4-2.7 8.4-5 13.2-5.4\\\" style=\\\"fill:#70c6be\\\"/><path d=\\\"M257.8 244.3c8.1-1.3 16.7.6 23.4 5.4 5.1 3.9 8.6 9.8 9.7 16.2.7 4.9.2 9.9-1.6 14.5-1.4 4-3.3 7.7-5.5 11.3-31.2 52.5-62.5 105-93.7 157.5-4 6.6-7.6 13.4-12.2 19.6-3.3 4.1-7.5 7.7-12.4 9.9-10.2 5.4-23.5 2.1-31.3-6.1-3.9-3.9-6.2-9.4-6.5-14.9-.4-5.7.8-11.5 3.4-16.7 2.3-5 5-9.7 7.8-14.4 13.7-23.1 27.3-46.3 41-69.4 18.7-31.8 37.8-63.4 56.6-95.1 2-3.6 4.5-6.9 7.3-9.9 3.8-4 8.6-7.1 14-7.9\\\" style=\\\"fill:#1ba0d8\\\"/></svg>\",\"simple-amazonwebservices\":\"<svg role=\\\"img\\\" viewBox=\\\"0 0 24 24\\\" xmlns=\\\"http://www.w3.org/2000/svg\\\"><title>Amazon Web Services</title><path d=\\\"M6.763 10.036c0 .296.032.535.088.71.064.176.144.368.256.576.04.063.056.127.056.183 0 .08-.048.16-.152.24l-.503.335a.383.383 0 0 1-.208.072c-.08 0-.16-.04-.239-.112a2.47 2.47 0 0 1-.287-.375 6.18 6.18 0 0 1-.248-.471c-.622.734-1.405 1.101-2.347 1.101-.67 0-1.205-.191-1.596-.574-.391-.384-.59-.894-.59-1.533 0-.678.239-1.23.726-1.644.487-.415 1.133-.623 1.955-.623.272 0 .551.024.846.064.296.04.6.104.918.176v-.583c0-.607-.127-1.03-.375-1.277-.255-.248-.686-.367-1.3-.367-.28 0-.568.031-.863.103-.295.072-.583.16-.862.272a2.287 2.287 0 0 1-.28.104.488.488 0 0 1-.127.023c-.112 0-.168-.08-.168-.247v-.391c0-.128.016-.224.056-.28a.597.597 0 0 1 .224-.167c.279-.144.614-.264 1.005-.36a4.84 4.84 0 0 1 1.246-.151c.95 0 1.644.216 2.091.647.439.43.662 1.085.662 1.963v2.586zm-3.24 1.214c.263 0 .534-.048.822-.144.287-.096.543-.271.758-.51.128-.152.224-.32.272-.512.047-.191.08-.423.08-.694v-.335a6.66 6.66 0 0 0-.735-.136 6.02 6.02 0 0 0-.75-.048c-.535 0-.926.104-1.19.32-.263.215-.39.518-.39.917 0 .375.095.655.295.846.191.2.47.296.838.296zm6.41.862c-.144 0-.24-.024-.304-.08-.064-.048-.12-.16-.168-.311L7.586 5.55a1.398 1.398 0 0 1-.072-.32c0-.128.064-.2.191-.2h.783c.151 0 .255.025.31.08.065.048.113.16.16.312l1.342 5.284 1.245-5.284c.04-.16.088-.264.151-.312a.549.549 0 0 1 .32-.08h.638c.152 0 .256.025.32.08.063.048.12.16.151.312l1.261 5.348 1.381-5.348c.048-.16.104-.264.16-.312a.52.52 0 0 1 .311-.08h.743c.127 0 .2.065.2.2 0 .04-.009.08-.017.128a1.137 1.137 0 0 1-.056.2l-1.923 6.17c-.048.16-.104.263-.168.311a.51.51 0 0 1-.303.08h-.687c-.151 0-.255-.024-.32-.08-.063-.056-.119-.16-.15-.32l-1.238-5.148-1.23 5.14c-.04.16-.087.264-.15.32-.065.056-.177.08-.32.08zm10.256.215c-.415 0-.83-.048-1.229-.143-.399-.096-.71-.2-.918-.32-.128-.071-.215-.151-.247-.223a.563.563 0 0 1-.048-.224v-.407c0-.167.064-.247.183-.247.048 0 .096.008.144.024.048.016.12.048.2.08.271.12.566.215.878.279.319.064.63.096.95.096.502 0 .894-.088 1.165-.264a.86.86 0 0 0 .415-.758.777.777 0 0 0-.215-.559c-.144-.151-.416-.287-.807-.415l-1.157-.36c-.583-.183-1.014-.454-1.277-.813a1.902 1.902 0 0 1-.4-1.158c0-.335.073-.63.216-.886.144-.255.335-.479.575-.654.24-.184.51-.32.83-.415.32-.096.655-.136 1.006-.136.175 0 .359.008.535.032.183.024.35.056.518.088.16.04.312.08.455.127.144.048.256.096.336.144a.69.69 0 0 1 .24.2.43.43 0 0 1 .071.263v.375c0 .168-.064.256-.184.256a.83.83 0 0 1-.303-.096 3.652 3.652 0 0 0-1.532-.311c-.455 0-.815.071-1.062.223-.248.152-.375.383-.375.71 0 .224.08.416.24.567.159.152.454.304.877.44l1.134.358c.574.184.99.44 1.237.767.247.327.367.702.367 1.117 0 .343-.072.655-.207.926-.144.272-.336.511-.583.703-.248.2-.543.343-.886.447-.36.111-.734.167-1.142.167zM21.698 16.207c-2.626 1.94-6.442 2.969-9.722 2.969-4.598 0-8.74-1.7-11.87-4.526-.247-.223-.024-.527.272-.351 3.384 1.963 7.559 3.153 11.877 3.153 2.914 0 6.114-.607 9.06-1.852.439-.2.814.287.383.607zM22.792 14.961c-.336-.43-2.22-.207-3.074-.103-.255.032-.295-.192-.063-.36 1.5-1.053 3.967-.75 4.254-.399.287.36-.08 2.826-1.485 4.007-.215.184-.423.088-.327-.151.32-.79 1.03-2.57.695-2.994z\\\"/></svg>\",\"simple-apple\":\"<svg role=\\\"img\\\" viewBox=\\\"0 0 24 24\\\" xmlns=\\\"http://www.w3.org/2000/svg\\\"><title>Apple</title><path d=\\\"M12.152 6.896c-.948 0-2.415-1.078-3.96-1.04-2.04.027-3.91 1.183-4.961 3.014-2.117 3.675-.546 9.103 1.519 12.09 1.013 1.454 2.208 3.09 3.792 3.039 1.52-.065 2.09-.987 3.935-.987 1.831 0 2.35.987 3.96.948 1.637-.026 2.676-1.48 3.676-2.948 1.156-1.688 1.636-3.325 1.662-3.415-.039-.013-3.182-1.221-3.22-4.857-.026-3.04 2.48-4.494 2.597-4.559-1.429-2.09-3.623-2.324-4.39-2.376-2-.156-3.675 1.09-4.61 1.09zM15.53 3.83c.843-1.012 1.4-2.427 1.245-3.83-1.207.052-2.662.805-3.532 1.818-.78.896-1.454 2.338-1.273 3.714 1.338.104 2.715-.688 3.559-1.701\\\"/></svg>\",\"selfhst-amazon-web-services\":\"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" xml:space=\\\"preserve\\\" viewBox=\\\"0 0 512 512\\\"><path d=\\\"M144.3 214.1c0 6.3.7 11.4 1.9 15.2 1.4 3.7 3.1 7.8 5.4 12.3.9 1.4 1.2 2.7 1.2 3.9 0 1.7-1 3.4-3.2 5.1l-10.7 7.2c-1.5 1-3.1 1.5-4.4 1.5-1.7 0-3.4-.9-5.1-2.4-2.4-2.6-4.4-5.3-6.1-8-1.7-2.9-3.4-6.1-5.3-10-13.3 15.7-30 23.5-50.1 23.5-14.3 0-25.7-4.1-34.1-12.3-8.3-8.2-12.6-19.1-12.6-32.7 0-14.5 5.1-26.2 15.5-35.1S60.8 169 78.4 169c5.8 0 11.7.5 18.1 1.4s12.8 2.2 19.6 3.7v-12.4c0-12.9-2.7-22-8-27.2-5.4-5.3-14.6-7.8-27.8-7.8-6 0-12.1.7-18.4 2.2s-12.4 3.4-18.4 5.8c-2.7 1.2-4.8 1.9-6 2.2s-2 .5-2.7.5c-2.4 0-3.6-1.7-3.6-5.3v-8.3c0-2.7.3-4.8 1.2-6s2.4-2.4 4.8-3.6c6-3.1 13.1-5.6 21.5-7.7 8.3-2.2 17.2-3.2 26.6-3.2 20.3 0 35.1 4.6 44.6 13.8 9.4 9.2 14.1 23.2 14.1 41.9v55.2zM75.2 240c5.6 0 11.4-1 17.5-3.1 6.1-2 11.6-5.8 16.2-10.9 2.7-3.2 4.8-6.8 5.8-10.9s1.7-9 1.7-14.8v-7.2c-4.9-1.2-10.2-2.2-15.7-2.9-5.4-.7-10.7-1-16-1-11.4 0-19.8 2.2-25.4 6.8S51 207.1 51 215.6c0 8 2 14 6.3 18.1 4.1 4.2 10 6.3 17.9 6.3m136.7 18.4c-3.1 0-5.1-.5-6.5-1.7-1.4-1-2.6-3.4-3.6-6.6l-40-131.6c-1-3.4-1.5-5.6-1.5-6.8 0-2.7 1.4-4.3 4.1-4.3h16.7c3.2 0 5.4.5 6.6 1.7 1.4 1 2.4 3.4 3.4 6.6l28.6 112.7 26.6-112.7c.9-3.4 1.9-5.6 3.2-6.6 1.4-1 3.7-1.7 6.8-1.7H270c3.2 0 5.4.5 6.8 1.7 1.4 1 2.6 3.4 3.2 6.6l26.9 114.1 29.5-114.1c1-3.4 2.2-5.6 3.4-6.6 1.4-1 3.6-1.7 6.6-1.7h15.8c2.7 0 4.3 1.4 4.3 4.3 0 .9-.2 1.7-.3 2.7-.2 1-.5 2.4-1.2 4.3l-41 131.6q-1.5 5.1-3.6 6.6c-1.4 1-3.6 1.7-6.5 1.7h-14.6c-3.2 0-5.4-.5-6.8-1.7s-2.6-3.4-3.2-6.8l-26.4-109.8L236.7 250c-.9 3.4-1.9 5.6-3.2 6.8-1.4 1.2-3.7 1.7-6.8 1.7zm218.8 4.6c-8.9 0-17.7-1-26.2-3.1-8.5-2-15.2-4.3-19.6-6.8-2.7-1.5-4.6-3.2-5.3-4.8s-1-3.2-1-4.8v-8.7c0-3.6 1.4-5.3 3.9-5.3 1 0 2 .2 3.1.5 1 .3 2.6 1 4.3 1.7 5.8 2.6 12.1 4.6 18.7 6 6.8 1.4 13.5 2 20.3 2 10.7 0 19.1-1.9 24.9-5.6s8.9-9.2 8.9-16.2c0-4.8-1.5-8.7-4.6-11.9s-8.9-6.1-17.2-8.9l-24.7-7.7c-12.4-3.9-21.6-9.7-27.2-17.4-5.6-7.5-8.5-15.8-8.5-24.7 0-7.2 1.5-13.5 4.6-18.9s7.2-10.2 12.3-14c5.1-3.9 10.9-6.8 17.7-8.9 6.8-2 14-2.9 21.5-2.9 3.7 0 7.7.2 11.4.7 3.9.5 7.5 1.2 11.1 1.9 3.4.9 6.6 1.7 9.7 2.7s5.4 2 7.2 3.1c2.4 1.4 4.1 2.7 5.1 4.3 1 1.4 1.5 3.2 1.5 5.6v8c0 3.6-1.4 5.4-3.9 5.4-1.4 0-3.6-.7-6.5-2q-14.55-6.6-32.7-6.6c-9.7 0-17.4 1.5-22.6 4.8s-8 8.2-8 15.2c0 4.8 1.7 8.9 5.1 12.1s9.7 6.5 18.7 9.4l24.2 7.7c12.3 3.9 21.1 9.4 26.4 16.3s7.8 15 7.8 23.8c0 7.3-1.5 14-4.4 19.8-3.1 5.8-7.2 10.9-12.4 15-5.3 4.3-11.6 7.3-18.9 9.5-8 2.5-16 3.7-24.7 3.7\\\"/><path d=\\\"M462.9 345.7c-56 41.4-137.4 63.3-207.4 63.3-98.1 0-186.5-36.3-253.2-96.6-5.3-4.8-.5-11.2 5.8-7.5 72.2 41.9 161.3 67.3 253.4 67.3 62.2 0 130.4-12.9 193.3-39.5 9.3-4.2 17.3 6.2 8.1 13m23.3-26.5c-7.2-9.2-47.3-4.4-65.6-2.2-5.4.7-6.3-4.1-1.4-7.7 32-22.5 84.6-16 90.8-8.5 6.1 7.7-1.7 60.3-31.7 85.5-4.6 3.9-9 1.9-7-3.2 6.9-16.9 22.1-54.9 14.9-63.9\\\" style=\\\"fill-rule:evenodd;clip-rule:evenodd;fill:#f90\\\"/></svg>\",\"selfhst-opnsense\":\"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" xml:space=\\\"preserve\\\" viewBox=\\\"0 0 512 512\\\"><path d=\\\"M512 160.2V400H400V112H112V0h240.4zM112 112H0v240.4L160.2 512H400V400H112z\\\" style=\\\"fill:#de3c07\\\"/></svg>\",\"selfhst-portainer\":\"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" xml:space=\\\"preserve\\\" viewBox=\\\"0 0 512 512\\\"><path d=\\\"M462.2 512H49.8C22.3 512 0 489.7 0 462.2V49.8C0 22.3 22.3 0 49.8 0h412.4C489.7 0 512 22.3 512 49.8v412.4c0 27.5-22.3 49.8-49.8 49.8\\\" style=\\\"fill:#2e2f33\\\"/><path d=\\\"M108.2 63.9h140.9c97.2 0 154.6 30.4 154.6 129.5v3.4c0 99.4-57.2 129.5-154.5 129.5h-30.7V447H108.2zm134.5 177.8c30.1 0 46-11.2 46-44.6v-3.9c0-33.2-15.8-44.6-46-44.6h-24.2v93.1zM313 358h90.3v90.1H313z\\\" style=\\\"fill:#f7f6f3\\\"/></svg>\",\"selfhst-jotty\":\"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" xml:space=\\\"preserve\\\" viewBox=\\\"0 0 512 512\\\"><path d=\\\"M438.6 0H73.4C33.1 0 .3 32.8.3 73.1v365.7c0 40.3 32.8 73.1 73.1 73.1h365.3c40.3 0 73.1-32.8 73.1-73.1V73.1C511.7 32.8 478.9 0 438.6 0\\\" style=\\\"fill-rule:evenodd;clip-rule:evenodd;fill:#8b3bd0\\\"/><path d=\\\"M356.8 53.6v261.3c0 39.3-13.9 72.8-41.8 100.7s-61.4 41.8-100.7 41.8l-23.8-95h23.8c13 0 24.1-4.6 33.2-13.8 9.5-9.5 14.3-20.7 14.3-33.7V148.6H157.3v-95z\\\" style=\\\"fill:#fff\\\"/></svg>\",\"selfhst-authportal\":\"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" xml:space=\\\"preserve\\\" viewBox=\\\"0 0 512 512\\\"><path d=\\\"M0 0h512v512H0z\\\" style=\\\"fill:#111827\\\"/><path d=\\\"M170.7 85.3 369.8 256 170.7 426.7z\\\" style=\\\"fill:#f59e0b\\\"/></svg>\",\"selfhst-docker\":\"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" xml:space=\\\"preserve\\\" viewBox=\\\"0 0 512 512\\\"><path d=\\\"M501.4 212.3c-11.5-8-38-11-58.6-7-2.4-20-13.5-37.5-32.7-53l-11-8-7.7 11.5c-9.6 15-14.4 36-13 56 .5 7 2.9 19.5 10.1 30.5-6.7 4-20.7 9-38.9 9H2.3l-1 4c-3.4 20-3.4 82.5 36 130.5 29.8 36.5 74 55 132.1 55 125.9 0 219.1-60.5 262.8-170 17.3.5 54.3 0 73-37.5.5-1 1.4-3 4.8-10.5l1.9-4zM280 71.3h-52.8v50H280zm0 60h-52.8v50H280zm-62.5 0h-52.8v50h52.8zm-62.4 0h-52.8v50h52.8zm-62.5 60H39.8v50h52.8zm62.5 0h-52.8v50h52.8zm62.4 0h-52.8v50h52.8zm62.5 0h-52.8v50H280zm62.4 0h-52.8v50h52.8z\\\" style=\\\"fill:#2396ed\\\"/></svg>\",\"selfhst-opnsense-v1\":\"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" xml:space=\\\"preserve\\\" viewBox=\\\"0 0 512 512\\\"><path d=\\\"M503.1 465.4v2.1c-6 25.5-28.4 44.5-55 44.5H7.3v-46.6h38.3v7.3h403c1.1 0 2 0 2.9-.1 1.8-.1 3.6-.6 5.2-1.3 2.9-1.3 5.3-3.3 7-5.9zM63.9 0c-26.5 0-49 19-55 44.5V47H48c2.7-4.3 7.3-7.2 12.6-7.6 1-.1 2-.1 3.2-.1h402.6V47h38.3V0z\\\" style=\\\"fill:#898b8d\\\"/><path d=\\\"M466.1 157.7V197H319.5v-39.3zM45.9 315.4v39.3h146.6v-39.3zm0-157.7V197h146.6v-39.3zm273.6 157.7v39.3h146.6v-39.3z\\\" style=\\\"fill:#58595b\\\"/><path d=\\\"M83.8 78.6H428v39.3H83.8zm0 315.9H428v39.3H83.8z\\\" style=\\\"fill:#403f41\\\"/><linearGradient id=\\\"opnsense-v1_svg__a\\\" x1=\\\"-1460.617\\\" x2=\\\"-1375.11\\\" y1=\\\"1248.095\\\" y2=\\\"1248.095\\\" gradientTransform=\\\"matrix(.1853 .356 .6722 -.353 -90.66 993.802)\\\" gradientUnits=\\\"userSpaceOnUse\\\"><stop offset=\\\"0\\\" style=\\\"stop-color:#737373\\\"/><stop offset=\\\"1\\\" style=\\\"stop-color:#333\\\"/></linearGradient><path d=\\\"m466.5 78.6 38.2-20.5.1-39.5-38.3 20.7\\\" style=\\\"fill:url(#opnsense-v1_svg__a)\\\"/><linearGradient id=\\\"opnsense-v1_svg__b\\\" x1=\\\"4.634\\\" x2=\\\"50.301\\\" y1=\\\"462.6\\\" y2=\\\"462.6\\\" gradientTransform=\\\"matrix(1 0 0 -1 0 514)\\\" gradientUnits=\\\"userSpaceOnUse\\\"><stop offset=\\\"0\\\" style=\\\"stop-color:#737373\\\"/><stop offset=\\\"1\\\" style=\\\"stop-color:#333\\\"/></linearGradient><path d=\\\"M45.6 64.5v-8.9c0-5.3 2.6-10 6.5-13l-34-18.5c-6.7 9.5-10.8 21.3-10.9 34l38.3 20.6v-14c.1-.1.1-.2.1-.2\\\" style=\\\"fill:url(#opnsense-v1_svg__b)\\\"/><linearGradient id=\\\"opnsense-v1_svg__c\\\" x1=\\\"-1587.887\\\" x2=\\\"-1498.137\\\" y1=\\\"-2208.165\\\" y2=\\\"-2208.165\\\" gradientTransform=\\\"matrix(-.1853 -.356 -.6722 .353 -1743.836 694.098)\\\" gradientUnits=\\\"userSpaceOnUse\\\"><stop offset=\\\"0\\\" style=\\\"stop-color:#737373\\\"/><stop offset=\\\"1\\\" style=\\\"stop-color:#333\\\"/></linearGradient><path d=\\\"M45.6 472.7v-38.9L7.3 454.5v39.7l39.9-21.5z\\\" style=\\\"fill:url(#opnsense-v1_svg__c)\\\"/><linearGradient id=\\\"opnsense-v1_svg__d\\\" x1=\\\"460.439\\\" x2=\\\"504.436\\\" y1=\\\"53\\\" y2=\\\"53\\\" gradientTransform=\\\"matrix(1 0 0 -1 0 514)\\\" gradientUnits=\\\"userSpaceOnUse\\\"><stop offset=\\\"0\\\" style=\\\"stop-color:#737373\\\"/><stop offset=\\\"1\\\" style=\\\"stop-color:#333\\\"/></linearGradient><path d=\\\"M466.5 456.4c0 5.5-2.7 10.3-6.9 13.2l34.2 18.4c6.8-9.5 10.8-21.2 10.9-33.8L466.5 434v12.8\\\" style=\\\"fill:url(#opnsense-v1_svg__d)\\\"/><linearGradient id=\\\"opnsense-v1_svg__e\\\" x1=\\\"2521.902\\\" x2=\\\"2684.464\\\" y1=\\\"-2476.233\\\" y2=\\\"-2476.233\\\" gradientTransform=\\\"matrix(-1.508 -1.0166 -3.1265 -1.519 -3697.438 -937.952)\\\" gradientUnits=\\\"userSpaceOnUse\\\"><stop offset=\\\".107\\\" style=\\\"stop-color:#58595b\\\"/><stop offset=\\\"1\\\" style=\\\"stop-color:#000\\\"/></linearGradient><path d=\\\"M192.5 197v-.4l-72.4-38.9H45.9v.2l72.5 39.1z\\\" style=\\\"fill:url(#opnsense-v1_svg__e)\\\"/><linearGradient id=\\\"opnsense-v1_svg__f\\\" x1=\\\"-1594.953\\\" x2=\\\"-1432.391\\\" y1=\\\"278.458\\\" y2=\\\"278.458\\\" gradientTransform=\\\"matrix(1.508 -1.0166 3.1265 -1.519 1804.196 -937.952)\\\" gradientUnits=\\\"userSpaceOnUse\\\"><stop offset=\\\".107\\\" style=\\\"stop-color:#58595b\\\"/><stop offset=\\\"1\\\" style=\\\"stop-color:#000\\\"/></linearGradient><path d=\\\"M466.1 157.7h-74l-72.6 39.1v.2h74.2l72.4-38.9z\\\" style=\\\"fill:url(#opnsense-v1_svg__f)\\\"/><linearGradient id=\\\"opnsense-v1_svg__g\\\" x1=\\\"-4327.142\\\" x2=\\\"-4164.581\\\" y1=\\\"1572.387\\\" y2=\\\"1572.387\\\" gradientTransform=\\\"matrix(1.508 1.0166 3.1265 1.519 1863.938 2252.352)\\\" gradientUnits=\\\"userSpaceOnUse\\\"><stop offset=\\\".107\\\" style=\\\"stop-color:#58595b\\\"/><stop offset=\\\"1\\\" style=\\\"stop-color:#000\\\"/></linearGradient><path d=\\\"M466.1 354.7v-.4l-72.4-38.9h-74.2v.2l72.6 39.1z\\\" style=\\\"fill:url(#opnsense-v1_svg__g)\\\"/><linearGradient id=\\\"opnsense-v1_svg__h\\\" x1=\\\"-413.668\\\" x2=\\\"-246.99\\\" y1=\\\"-1046.618\\\" y2=\\\"-1046.618\\\" gradientTransform=\\\"matrix(-1.508 1.0166 -3.1265 1.519 -3638.692 2252.352)\\\" gradientUnits=\\\"userSpaceOnUse\\\"><stop offset=\\\".107\\\" style=\\\"stop-color:#58595b\\\"/><stop offset=\\\"1\\\" style=\\\"stop-color:#000\\\"/></linearGradient><path d=\\\"M192.5 315.4h-74.1l-72.5 39.1v.2h74.2l72.4-38.9z\\\" style=\\\"fill:url(#opnsense-v1_svg__h)\\\"/><linearGradient id=\\\"opnsense-v1_svg__i\\\" x1=\\\"74.725\\\" x2=\\\"261.062\\\" y1=\\\"132.65\\\" y2=\\\"132.65\\\" gradientTransform=\\\"matrix(.9914 0 0 -1 -66.782 260.2)\\\" gradientUnits=\\\"userSpaceOnUse\\\"><stop offset=\\\".237\\\" style=\\\"stop-color:#a6a8ab\\\"/><stop offset=\\\"1\\\" style=\\\"stop-color:#404040\\\"/></linearGradient><path d=\\\"M192.8 157.7V197L7.3 97.4V58.1z\\\" style=\\\"fill:url(#opnsense-v1_svg__i)\\\"/><linearGradient id=\\\"opnsense-v1_svg__j\\\" x1=\\\"-2461.056\\\" x2=\\\"-2274.718\\\" y1=\\\"132.65\\\" y2=\\\"132.65\\\" gradientTransform=\\\"matrix(-.9914 0 0 -1 -1935.19 260.2)\\\" gradientUnits=\\\"userSpaceOnUse\\\"><stop offset=\\\".237\\\" style=\\\"stop-color:#a6a8ab\\\"/><stop offset=\\\"1\\\" style=\\\"stop-color:#404040\\\"/></linearGradient><path d=\\\"M319.2 157.7V197l185.5-99.6V58.1z\\\" style=\\\"fill:url(#opnsense-v1_svg__j)\\\"/><linearGradient id=\\\"opnsense-v1_svg__k\\\" x1=\\\"-2291.845\\\" x2=\\\"-2105.508\\\" y1=\\\"-739.55\\\" y2=\\\"-739.55\\\" gradientTransform=\\\"matrix(-.9914 0 0 1 -1767.435 1124.6)\\\" gradientUnits=\\\"userSpaceOnUse\\\"><stop offset=\\\".237\\\" style=\\\"stop-color:#a6a8ab\\\"/><stop offset=\\\"1\\\" style=\\\"stop-color:#404040\\\"/></linearGradient><path d=\\\"M319.2 355.2v-39.8L504.7 415v39.7z\\\" style=\\\"fill:url(#opnsense-v1_svg__k)\\\"/><linearGradient id=\\\"opnsense-v1_svg__l\\\" x1=\\\"-94.103\\\" x2=\\\"91.934\\\" y1=\\\"-739.55\\\" y2=\\\"-739.55\\\" gradientTransform=\\\"matrix(.9914 0 0 1 100.993 1124.6)\\\" gradientUnits=\\\"userSpaceOnUse\\\"><stop offset=\\\".237\\\" style=\\\"stop-color:#a6a8ab\\\"/><stop offset=\\\"1\\\" style=\\\"stop-color:#404040\\\"/></linearGradient><path d=\\\"M192.8 355.2v-39.8L7.3 415v39.7z\\\" style=\\\"fill:url(#opnsense-v1_svg__l)\\\"/><path d=\\\"m319.2 276.1 108.9 58.5v-39.7l-35.3-18.8h111.9v-39.8H392.8l35.3-18.8v-39.3l-108.9 58.1zM83.8 334.6l109-58.5v-39.8l-109-58.1v39.3l35.7 18.8H7.3v39.8h111.9l-35.3 18.8v39.7z\\\" style=\\\"fill:#e24525\\\"/></svg>\",\"mdi-server-security\":\"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" id=\\\"mdi-server-security\\\" viewBox=\\\"0 0 24 24\\\"><path d=\\\"M3,1H19A1,1 0 0,1 20,2V6A1,1 0 0,1 19,7H3A1,1 0 0,1 2,6V2A1,1 0 0,1 3,1M3,9H19A1,1 0 0,1 20,10V10.67L17.5,9.56L11,12.44V15H3A1,1 0 0,1 2,14V10A1,1 0 0,1 3,9M3,17H11C11.06,19.25 12,21.4 13.46,23H3A1,1 0 0,1 2,22V18A1,1 0 0,1 3,17M8,5H9V3H8V5M8,13H9V11H8V13M8,21H9V19H8V21M4,3V5H6V3H4M4,11V13H6V11H4M4,19V21H6V19H4M17.5,12L22,14V17C22,19.78 20.08,22.37 17.5,23C14.92,22.37 13,19.78 13,17V14L17.5,12M17.5,13.94L15,15.06V17.72C15,19.26 16.07,20.7 17.5,21.06V13.94Z\\\" /></svg>\"},\"page\":{\"title\":\"The One File\",\"background\":\"\",\"topbarBg\":\"rgba(9, 12, 20, 0.9)\",\"topbarBorder\":\"#1f2533\",\"panel\":\"#2f0e0e\",\"panelAlt\":\"#10141b\",\"accent\":\"#a75252\",\"sidebarBg\":\"#10141b\",\"btnBg\":\"#0b0e13\",\"btnText\":\"#e2e8f0\",\"tagFill\":\"#1e293b\",\"tagText\":\"#e2e8f0\",\"tagBorder\":\"#475569\",\"inputBg\":\"#0b0e13\",\"inputText\":\"#e2e8f0\",\"inputBorder\":\"#1f2937\",\"inputFont\":\"Inter, system-ui, sans-serif\",\"inputFontSize\":14,\"toolbarBg\":\"#441215\",\"toolbarBorder\":\"#1f2937\",\"toolbarText\":\"#94a3b8\",\"toolbarBtnBg\":\"#0b0e13\",\"toolbarBtnText\":\"#e2e8f0\",\"minimapDots\":\"#94a3b8\",\"canvasHintEnabled\":true,\"canvasHintText\":\"\",\"canvasHintBg\":\"#0f172a\",\"canvasHintColor\":\"#94a3b8\",\"danger\":\"#f56565\",\"textMain\":\"#e2e8f0\",\"textSoft\":\"#94a3b8\",\"topbarHeight\":112,\"sidebarWidth\":350,\"mobileFooterHeight\":40,\"sidebarCollapsed\":false,\"nodeFill\":\"#1e293b\",\"nodeStroke\":\"#475569\",\"nodeTitle\":\"#e2e8f0\",\"nodeSub\":\"#94a3b8\",\"nodeTitleSize\":18,\"nodeSubSize\":13,\"nodeFont\":\"Inter, system-ui, sans-serif\",\"defaultEdge\":\"#475569\",\"selectionHandle\":\"#f59e0b\",\"selectionHandleSize\":8,\"groupIndicator\":\"#4fd1c5\",\"canvasGradientTop\":\"#1e2532\",\"canvasGradientBottom\":\"#050608\",\"canvasBorder\":\"#475569\",\"canvasGrid\":\"#475569\",\"canvasGridSize\":50,\"canvasGridEnabled\":true,\"rackFrameFill\":\"#0f172a\",\"rackFrameStroke\":\"#4fd1c5\",\"rackLineColor\":\"#475569\",\"rackTextColor\":\"#4fd1c5\",\"rackGridEnabled\":true,\"viewOnly\":false,\"defaultEdgeRouting\":\"curved\",\"animateConnections\":false,\"animationStyle\":\"arrows\",\"animationDirection\":\"all\",\"animationSpeed\":1.5},\"autoPingEnabled\":false,\"autoPingInterval\":30,\"canvas\":{\"zoom\":0.9921985961590549,\"panX\":-5.584863670202822,\"panY\":-99.90831573327841},\"savedTopologyView\":{\"zoom\":0.9325110211947125,\"panX\":-563.7108933103631,\"panY\":-561.6887674556383},\"documentTabs\":[{\"id\":\"main\",\"name\":\"Corporate Site B\",\"nodes\":{\"core-router-1\":{\"shape\":\"router\",\"name\":\"Core Router 1\",\"ip\":\"10.0.0.1\",\"role\":\"Core Routing\",\"tags\":[\"core\",\"tier-1\",\"redundant\"],\"notes\":[\"Primary core router\",\"BGP peering enabled\"],\"mac\":\"00:1A:2B:3C:4D:01\",\"rackUnit\":\"\",\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"core-router-2\":{\"shape\":\"router\",\"name\":\"Core Router 2\",\"ip\":\"10.0.0.2\",\"role\":\"Core Routing\",\"tags\":[\"core\",\"tier-1\",\"redundant\"],\"notes\":[\"Secondary core router\",\"HSRP standby\"],\"mac\":\"00:1A:2B:3C:4D:02\",\"rackUnit\":\"\",\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null,\"ping\":{\"enabled\":true,\"protocol\":\"custom\",\"customUrl\":\"https://google.com\",\"timeout\":3000,\"status\":\"online\",\"lastCheck\":\"2025-12-09T00:15:04.343Z\"}},\"fw-external-1\":{\"shape\":\"firewall\",\"name\":\"External FW 1\",\"ip\":\"10.0.1.1\",\"role\":\"Perimeter Security\",\"tags\":[\"security\",\"perimeter\",\"ha-pair\"],\"notes\":[\"Palo Alto PA-5250\",\"Active node\"],\"mac\":\"00:1A:2B:3C:4D:10\",\"rackUnit\":\"\",\"uHeight\":\"2\",\"layer\":\"security\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"fw-external-2\":{\"shape\":\"firewall\",\"name\":\"External FW 2\",\"ip\":\"10.0.1.2\",\"role\":\"Perimeter Security\",\"tags\":[\"security\",\"perimeter\",\"ha-pair\"],\"notes\":[\"Palo Alto PA-5250\",\"Passive node\"],\"mac\":\"00:1A:2B:3C:4D:11\",\"rackUnit\":\"\",\"uHeight\":\"2\",\"layer\":\"security\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"fw-internal\":{\"shape\":\"firewall\",\"name\":\"Internal FW\",\"ip\":\"10.0.2.1\",\"role\":\"Internal Segmentation\",\"tags\":[\"security\",\"internal\"],\"notes\":[\"East-West traffic inspection\"],\"mac\":\"00:1A:2B:3C:4D:12\",\"rackUnit\":\"\",\"uHeight\":\"2\",\"layer\":\"security\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"core-switch-1\":{\"shape\":\"switch\",\"name\":\"Core Switch 1\",\"ip\":\"10.0.10.1\",\"role\":\"Core Switching\",\"tags\":[\"core\",\"layer3\",\"redundant\"],\"notes\":[\"Cisco Nexus 9000\",\"VPC Domain 1\"],\"mac\":\"00:1A:2B:3C:4D:20\",\"rackUnit\":\"\",\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"core-switch-2\":{\"shape\":\"switch\",\"name\":\"Core Switch 2\",\"ip\":\"10.0.10.2\",\"role\":\"Core Switching\",\"tags\":[\"core\",\"layer3\",\"redundant\"],\"notes\":[\"Cisco Nexus 9000\",\"VPC Domain 1\"],\"mac\":\"00:1A:2B:3C:4D:21\",\"rackUnit\":\"\",\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"dc-rack-a1\":{\"shape\":\"server\",\"name\":\"DC Rack A1\",\"ip\":\"10.10.0.0/24\",\"role\":\"Data Center Rack\",\"tags\":[\"datacenter\",\"row-a\",\"production\"],\"notes\":[\"Row A, Position 1\",\"Primary compute\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":true,\"locked\":false,\"groupId\":null},\"dc-rack-a2\":{\"shape\":\"server\",\"name\":\"DC Rack A2\",\"ip\":\"10.10.1.0/24\",\"role\":\"Data Center Rack\",\"tags\":[\"datacenter\",\"row-a\",\"production\"],\"notes\":[\"Row A, Position 2\",\"Primary compute\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":true,\"locked\":false,\"groupId\":null},\"dc-rack-b1\":{\"shape\":\"server\",\"name\":\"DC Rack B1\",\"ip\":\"10.10.2.0/24\",\"role\":\"Data Center Rack\",\"tags\":[\"datacenter\",\"row-b\",\"storage\"],\"notes\":[\"Row B, Position 1\",\"Storage systems\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":true,\"locked\":false,\"groupId\":null},\"dc-rack-b2\":{\"shape\":\"server\",\"name\":\"DC Rack B2\",\"ip\":\"10.10.3.0/24\",\"role\":\"Data Center Rack\",\"tags\":[\"datacenter\",\"row-b\",\"storage\"],\"notes\":[\"Row B, Position 2\",\"Storage systems\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":true,\"locked\":false,\"groupId\":null},\"dmz-rack\":{\"shape\":\"server\",\"name\":\"DMZ Rack\",\"ip\":\"172.16.0.0/24\",\"role\":\"DMZ Infrastructure\",\"tags\":[\"dmz\",\"security\",\"public-facing\",{\"type\":\"icon\",\"library\":\"selfhst\",\"name\":\"booklogr\"},{\"type\":\"icon\",\"library\":\"simple\",\"name\":\"gmail\"}],\"notes\":[\"Isolated DMZ zone\",\"Public-facing services\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"security\",\"assignedRack\":\"\",\"rackCapacity\":\"24\",\"isRack\":true,\"locked\":false,\"groupId\":null},\"mgmt-rack\":{\"shape\":\"server\",\"name\":\"Management Rack\",\"ip\":\"192.168.100.0/24\",\"role\":\"Management Infrastructure\",\"tags\":[\"management\",\"oob\",\"noc\"],\"notes\":[\"Out-of-band management\",\"NOC equipment\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"logical\",\"assignedRack\":\"\",\"rackCapacity\":\"24\",\"isRack\":true,\"locked\":false,\"groupId\":null},\"esxi-host-01\":{\"shape\":\"server\",\"name\":\"ESXi Host 01\",\"ip\":\"10.10.0.11\",\"role\":\"Hypervisor\",\"tags\":[\"vmware\",\"compute\",\"cluster-a\"],\"notes\":[\"Dell PowerEdge R750\",\"512GB RAM\",\"vSphere 8.0\"],\"mac\":\"00:50:56:AA:01:01\",\"rackUnit\":38,\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a1\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"esxi-host-02\":{\"shape\":\"server\",\"name\":\"ESXi Host 02\",\"ip\":\"10.10.0.12\",\"role\":\"Hypervisor\",\"tags\":[\"vmware\",\"compute\",\"cluster-a\"],\"notes\":[\"Dell PowerEdge R750\",\"512GB RAM\",\"vSphere 8.0\"],\"mac\":\"00:50:56:AA:01:02\",\"rackUnit\":35,\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a1\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"esxi-host-03\":{\"shape\":\"server\",\"name\":\"ESXi Host 03\",\"ip\":\"10.10.0.13\",\"role\":\"Hypervisor\",\"tags\":[\"vmware\",\"compute\",\"cluster-a\"],\"notes\":[\"Dell PowerEdge R750\",\"512GB RAM\",\"vSphere 8.0\"],\"mac\":\"00:50:56:AA:01:03\",\"rackUnit\":32,\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a1\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"esxi-host-04\":{\"shape\":\"server\",\"name\":\"ESXi Host 04\",\"ip\":\"10.10.0.14\",\"role\":\"Hypervisor\",\"tags\":[\"vmware\",\"compute\",\"cluster-a\"],\"notes\":[\"Dell PowerEdge R750\",\"512GB RAM\",\"vSphere 8.0\"],\"mac\":\"00:50:56:AA:01:04\",\"rackUnit\":29,\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a1\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"tor-switch-a1\":{\"shape\":\"switch\",\"name\":\"ToR Switch A1\",\"ip\":\"10.10.0.1\",\"role\":\"Top of Rack\",\"tags\":[\"tor\",\"access\",\"rack-a1\"],\"notes\":[\"Cisco Nexus 93180YC-FX\",\"48x25G ports\"],\"mac\":\"00:1A:2B:3C:5D:01\",\"rackUnit\":42,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a1\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"esxi-host-05\":{\"shape\":\"server\",\"name\":\"ESXi Host 05\",\"ip\":\"10.10.1.11\",\"role\":\"Hypervisor\",\"tags\":[\"vmware\",\"compute\",\"cluster-b\"],\"notes\":[\"Dell PowerEdge R750\",\"768GB RAM\",\"vSphere 8.0\"],\"mac\":\"00:50:56:AA:02:01\",\"rackUnit\":38,\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"esxi-host-06\":{\"shape\":\"server\",\"name\":\"ESXi Host 06\",\"ip\":\"10.10.1.12\",\"role\":\"Hypervisor\",\"tags\":[\"vmware\",\"compute\",\"cluster-b\"],\"notes\":[\"Dell PowerEdge R750\",\"768GB RAM\",\"vSphere 8.0\"],\"mac\":\"00:50:56:AA:02:02\",\"rackUnit\":35,\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"esxi-host-07\":{\"shape\":\"server\",\"name\":\"ESXi Host 07\",\"ip\":\"10.10.1.13\",\"role\":\"Hypervisor\",\"tags\":[\"vmware\",\"compute\",\"cluster-b\"],\"notes\":[\"Dell PowerEdge R750\",\"768GB RAM\",\"vSphere 8.0\"],\"mac\":\"00:50:56:AA:02:03\",\"rackUnit\":32,\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"esxi-host-08\":{\"shape\":\"server\",\"name\":\"ESXi Host 08\",\"ip\":\"10.10.1.14\",\"role\":\"Hypervisor\",\"tags\":[\"vmware\",\"compute\",\"cluster-b\"],\"notes\":[\"Dell PowerEdge R750\",\"768GB RAM\",\"vSphere 8.0\"],\"mac\":\"00:50:56:AA:02:04\",\"rackUnit\":29,\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"tor-switch-a2\":{\"shape\":\"switch\",\"name\":\"ToR Switch A2\",\"ip\":\"10.10.1.1\",\"role\":\"Top of Rack\",\"tags\":[\"tor\",\"access\",\"rack-a2\"],\"notes\":[\"Cisco Nexus 93180YC-FX\",\"48x25G ports\"],\"mac\":\"00:1A:2B:3C:5D:02\",\"rackUnit\":42,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"san-primary\":{\"shape\":\"database\",\"name\":\"SAN Primary\",\"ip\":\"10.10.2.10\",\"role\":\"Primary Storage\",\"tags\":[\"storage\",\"san\",\"netapp\"],\"notes\":[\"NetApp AFF A400\",\"500TB Raw\",\"FC 32Gb\"],\"mac\":\"00:A0:98:AA:01:01\",\"rackUnit\":36,\"uHeight\":\"6\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-b1\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"san-secondary\":{\"shape\":\"database\",\"name\":\"SAN Secondary\",\"ip\":\"10.10.2.11\",\"role\":\"Secondary Storage\",\"tags\":[\"storage\",\"san\",\"netapp\"],\"notes\":[\"NetApp AFF A400\",\"500TB Raw\",\"FC 32Gb\"],\"mac\":\"00:A0:98:AA:01:02\",\"rackUnit\":28,\"uHeight\":\"6\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-b1\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"fc-switch-1\":{\"shape\":\"switch\",\"name\":\"FC Switch 1\",\"ip\":\"10.10.2.1\",\"role\":\"Fibre Channel\",\"tags\":[\"storage\",\"fc\",\"fabric-a\"],\"notes\":[\"Brocade G620\",\"Fabric A\"],\"mac\":\"00:1A:2B:FC:01:01\",\"rackUnit\":42,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-b1\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"fc-switch-2\":{\"shape\":\"switch\",\"name\":\"FC Switch 2\",\"ip\":\"10.10.2.2\",\"role\":\"Fibre Channel\",\"tags\":[\"storage\",\"fc\",\"fabric-b\"],\"notes\":[\"Brocade G620\",\"Fabric B\"],\"mac\":\"00:1A:2B:FC:01:02\",\"rackUnit\":41,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-b1\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"backup-server-1\":{\"shape\":\"server\",\"name\":\"Backup Server 1\",\"ip\":\"10.10.3.10\",\"role\":\"Backup Infrastructure\",\"tags\":[\"backup\",\"veeam\",\"protection\"],\"notes\":[\"Veeam Backup Server\",\"Dell R740xd\",\"200TB\"],\"mac\":\"00:50:56:BB:01:01\",\"rackUnit\":36,\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-b2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"backup-server-2\":{\"shape\":\"server\",\"name\":\"Backup Server 2\",\"ip\":\"10.10.3.11\",\"role\":\"Backup Infrastructure\",\"tags\":[\"backup\",\"veeam\",\"protection\"],\"notes\":[\"Veeam Backup Server\",\"Dell R740xd\",\"200TB\"],\"mac\":\"00:50:56:BB:01:02\",\"rackUnit\":33,\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-b2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"tape-library\":{\"shape\":\"database\",\"name\":\"Tape Library\",\"ip\":\"10.10.3.20\",\"role\":\"Archival Storage\",\"tags\":[\"backup\",\"tape\",\"lto9\"],\"notes\":[\"IBM TS4500\",\"LTO-9\",\"Long-term archive\"],\"mac\":\"00:50:56:BB:02:01\",\"rackUnit\":20,\"uHeight\":\"10\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-b2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"tor-switch-b1\":{\"shape\":\"switch\",\"name\":\"ToR Switch B1\",\"ip\":\"10.10.2.3\",\"role\":\"Top of Rack\",\"tags\":[\"tor\",\"access\",\"rack-b1\"],\"notes\":[\"Cisco Nexus 93180YC-FX\"],\"mac\":\"00:1A:2B:3C:5D:03\",\"rackUnit\":40,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-b1\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"tor-switch-b2\":{\"shape\":\"switch\",\"name\":\"ToR Switch B2\",\"ip\":\"10.10.3.1\",\"role\":\"Top of Rack\",\"tags\":[\"tor\",\"access\",\"rack-b2\"],\"notes\":[\"Cisco Nexus 93180YC-FX\"],\"mac\":\"00:1A:2B:3C:5D:04\",\"rackUnit\":42,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-b2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"web-server-1\":{\"shape\":\"server\",\"name\":\"Web Server 1\",\"ip\":\"172.16.0.11\",\"role\":\"Web Frontend\",\"tags\":[\"dmz\",\"web\",\"nginx\"],\"notes\":[\"NGINX reverse proxy\",\"Public facing\"],\"mac\":\"00:50:56:CC:01:01\",\"rackUnit\":20,\"uHeight\":\"1\",\"layer\":\"security\",\"assignedRack\":\"dmz-rack\",\"rackCapacity\":\"24\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"web-server-2\":{\"shape\":\"server\",\"name\":\"Web Server 2\",\"ip\":\"172.16.0.12\",\"role\":\"Web Frontend\",\"tags\":[\"dmz\",\"web\",\"nginx\"],\"notes\":[\"NGINX reverse proxy\",\"Public facing\"],\"mac\":\"00:50:56:CC:01:02\",\"rackUnit\":18,\"uHeight\":\"1\",\"layer\":\"security\",\"assignedRack\":\"dmz-rack\",\"rackCapacity\":\"24\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"waf-1\":{\"shape\":\"firewall\",\"name\":\"WAF Appliance\",\"ip\":\"172.16.0.5\",\"role\":\"Web Application Firewall\",\"tags\":[\"dmz\",\"security\",\"waf\"],\"notes\":[\"F5 BIG-IP ASM\",\"OWASP protection\"],\"mac\":\"00:50:56:CC:02:01\",\"rackUnit\":22,\"uHeight\":\"2\",\"layer\":\"security\",\"assignedRack\":\"dmz-rack\",\"rackCapacity\":\"24\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"load-balancer-dmz\":{\"shape\":\"switch\",\"name\":\"DMZ Load Balancer\",\"ip\":\"172.16.0.3\",\"role\":\"Load Balancing\",\"tags\":[\"dmz\",\"lb\",\"f5\"],\"notes\":[\"F5 BIG-IP LTM\",\"VIP: 172.16.0.100\"],\"mac\":\"00:50:56:CC:03:01\",\"rackUnit\":16,\"uHeight\":\"2\",\"layer\":\"security\",\"assignedRack\":\"dmz-rack\",\"rackCapacity\":\"24\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"mail-gateway\":{\"shape\":\"server\",\"name\":\"Mail Gateway\",\"ip\":\"172.16.0.25\",\"role\":\"Email Security\",\"tags\":[\"dmz\",\"email\",\"security\"],\"notes\":[\"Proofpoint Email Gateway\",\"Spam/malware filtering\"],\"mac\":\"00:50:56:CC:04:01\",\"rackUnit\":14,\"uHeight\":\"1\",\"layer\":\"security\",\"assignedRack\":\"dmz-rack\",\"rackCapacity\":\"24\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"dns-external-1\":{\"shape\":\"circle\",\"name\":\"External DNS 1\",\"ip\":\"172.16.0.53\",\"role\":\"External DNS\",\"tags\":[\"dmz\",\"dns\",\"public\"],\"notes\":[\"BIND DNS\",\"Authoritative for corp.com\"],\"mac\":\"00:50:56:CC:05:01\",\"rackUnit\":12,\"uHeight\":\"1\",\"layer\":\"security\",\"assignedRack\":\"dmz-rack\",\"rackCapacity\":\"24\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"dns-external-2\":{\"shape\":\"circle\",\"name\":\"External DNS 2\",\"ip\":\"172.16.0.54\",\"role\":\"External DNS\",\"tags\":[\"dmz\",\"dns\",\"public\"],\"notes\":[\"BIND DNS\",\"Secondary for corp.com\"],\"mac\":\"00:50:56:CC:05:02\",\"rackUnit\":10,\"uHeight\":\"1\",\"layer\":\"security\",\"assignedRack\":\"dmz-rack\",\"rackCapacity\":\"24\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"vcenter\":{\"shape\":\"server\",\"name\":\"vCenter Server\",\"ip\":\"192.168.100.10\",\"role\":\"Virtualization Management\",\"tags\":[\"management\",\"vmware\",\"vcsa\"],\"notes\":[\"vCenter Server Appliance 8.0\",\"Single SSO domain\"],\"mac\":\"00:50:56:DD:01:01\",\"rackUnit\":20,\"uHeight\":\"2\",\"layer\":\"logical\",\"assignedRack\":\"mgmt-rack\",\"rackCapacity\":\"24\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"nsx-manager\":{\"shape\":\"server\",\"name\":\"NSX Manager\",\"ip\":\"192.168.100.15\",\"role\":\"Network Virtualization\",\"tags\":[\"management\",\"vmware\",\"nsx\"],\"notes\":[\"NSX-T 4.1 Manager Cluster\"],\"mac\":\"00:50:56:DD:02:01\",\"rackUnit\":17,\"uHeight\":\"2\",\"layer\":\"logical\",\"assignedRack\":\"mgmt-rack\",\"rackCapacity\":\"24\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"siem-server\":{\"shape\":\"server\",\"name\":\"SIEM Server\",\"ip\":\"192.168.100.50\",\"role\":\"Security Monitoring\",\"tags\":[\"management\",\"security\",\"splunk\"],\"notes\":[\"Splunk Enterprise\",\"Security monitoring\"],\"mac\":\"00:50:56:DD:03:01\",\"rackUnit\":14,\"uHeight\":\"2\",\"layer\":\"logical\",\"assignedRack\":\"mgmt-rack\",\"rackCapacity\":\"24\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"nms-server\":{\"shape\":\"server\",\"name\":\"Network Monitoring\",\"ip\":\"192.168.100.60\",\"role\":\"Network Management\",\"tags\":[\"management\",\"monitoring\",\"prtg\"],\"notes\":[\"PRTG Network Monitor\",\"5000 sensors\"],\"mac\":\"00:50:56:DD:04:01\",\"rackUnit\":11,\"uHeight\":\"1\",\"layer\":\"logical\",\"assignedRack\":\"mgmt-rack\",\"rackCapacity\":\"24\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"jump-server\":{\"shape\":\"server\",\"name\":\"Jump Server\",\"ip\":\"192.168.100.100\",\"role\":\"Bastion Host\",\"tags\":[\"management\",\"security\",\"bastion\"],\"notes\":[\"Windows Server 2022\",\"MFA enabled\"],\"mac\":\"00:50:56:DD:05:01\",\"rackUnit\":9,\"uHeight\":\"1\",\"layer\":\"logical\",\"assignedRack\":\"mgmt-rack\",\"rackCapacity\":\"24\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"ipam-server\":{\"shape\":\"server\",\"name\":\"IPAM/DDI\",\"ip\":\"192.168.100.70\",\"role\":\"IP Management\",\"tags\":[\"management\",\"dns\",\"dhcp\"],\"notes\":[\"Infoblox DDI\",\"DNS/DHCP/IPAM\"],\"mac\":\"00:50:56:DD:06:01\",\"rackUnit\":7,\"uHeight\":\"2\",\"layer\":\"logical\",\"assignedRack\":\"mgmt-rack\",\"rackCapacity\":\"24\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"wlc-primary\":{\"shape\":\"wifi\",\"name\":\"WLC Primary\",\"ip\":\"10.20.0.1\",\"role\":\"Wireless Controller\",\"tags\":[\"wireless\",\"cisco\",\"9800\"],\"notes\":[\"Cisco C9800-40\",\"Primary controller\"],\"mac\":\"00:1A:2B:WL:01:01\",\"rackUnit\":\"\",\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"wlc-secondary\":{\"shape\":\"wifi\",\"name\":\"WLC Secondary\",\"ip\":\"10.20.0.2\",\"role\":\"Wireless Controller\",\"tags\":[\"wireless\",\"cisco\",\"9800\"],\"notes\":[\"Cisco C9800-40\",\"HA Secondary\"],\"mac\":\"00:1A:2B:WL:01:02\",\"rackUnit\":\"\",\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"mobile-zone-hq\":{\"shape\":\"phone\",\"name\":\"HQ Mobile Zone\",\"ip\":\"10.20.10.0/24\",\"role\":\"Mobile Device Zone\",\"tags\":[\"wireless\",\"byod\",\"mobile\"],\"notes\":[\"Corporate BYOD\",\"MDM enrolled devices\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"mobile-zone-guest\":{\"shape\":\"phone\",\"name\":\"Guest WiFi Zone\",\"ip\":\"10.30.0.0/24\",\"role\":\"Guest Network\",\"tags\":[\"wireless\",\"guest\",\"isolated\"],\"notes\":[\"Captive portal\",\"Internet only\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"mobile-zone-iot\":{\"shape\":\"phone\",\"name\":\"IoT Device Zone\",\"ip\":\"10.40.0.0/24\",\"role\":\"IoT Network\",\"tags\":[\"wireless\",\"iot\",\"building\"],\"notes\":[\"Building automation\",\"Smart devices\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"branch-router-ny\":{\"shape\":\"router\",\"name\":\"NYC Branch Router\",\"ip\":\"10.100.0.1\",\"role\":\"Branch Gateway\",\"tags\":[\"branch\",\"nyc\",\"sd-wan\"],\"notes\":[\"Cisco Viptela vEdge\",\"SD-WAN enabled\"],\"mac\":\"00:1A:2B:BR:01:01\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"branch-router-la\":{\"shape\":\"router\",\"name\":\"LA Branch Router\",\"ip\":\"10.101.0.1\",\"role\":\"Branch Gateway\",\"tags\":[\"branch\",\"la\",\"sd-wan\"],\"notes\":[\"Cisco Viptela vEdge\",\"SD-WAN enabled\"],\"mac\":\"00:1A:2B:BR:02:01\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"branch-router-chi\":{\"shape\":\"router\",\"name\":\"Chicago Branch Router\",\"ip\":\"10.102.0.1\",\"role\":\"Branch Gateway\",\"tags\":[\"branch\",\"chicago\",\"sd-wan\"],\"notes\":[\"Cisco Viptela vEdge\",\"SD-WAN enabled\"],\"mac\":\"00:1A:2B:BR:03:01\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"branch-router-lon\":{\"shape\":\"router\",\"name\":\"London Branch Router\",\"ip\":\"10.200.0.1\",\"role\":\"Branch Gateway\",\"tags\":[\"branch\",\"london\",\"sd-wan\"],\"notes\":[\"Cisco Viptela vEdge\",\"EMEA region\"],\"mac\":\"00:1A:2B:BR:04:01\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"branch-router-tokyo\":{\"shape\":\"router\",\"name\":\"Tokyo Branch Router\",\"ip\":\"10.201.0.1\",\"role\":\"Branch Gateway\",\"tags\":[\"branch\",\"tokyo\",\"sd-wan\"],\"notes\":[\"Cisco Viptela vEdge\",\"APAC region\"],\"mac\":\"00:1A:2B:BR:05:01\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"cloud-aws\":{\"shape\":\"cloud\",\"name\":\"AWS Cloud\",\"ip\":\"vpc-0a1b2c3d\",\"role\":\"Public Cloud\",\"tags\":[\"cloud\",\"aws\",\"hybrid\"],\"notes\":[\"AWS US-East-1\",\"VPC peering to HQ\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"logical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"cloud-azure\":{\"shape\":\"cloud\",\"name\":\"Azure Cloud\",\"ip\":\"vnet-corp-prod\",\"role\":\"Public Cloud\",\"tags\":[\"cloud\",\"azure\",\"hybrid\"],\"notes\":[\"Azure East US 2\",\"ExpressRoute\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"logical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"cloud-gcp\":{\"shape\":\"cloud\",\"name\":\"GCP Cloud\",\"ip\":\"vpc-gcp-corp\",\"role\":\"Public Cloud\",\"tags\":[\"cloud\",\"gcp\",\"dev\"],\"notes\":[\"GCP us-central1\",\"Dev/Test workloads\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"logical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"isp-primary\":{\"shape\":\"globe\",\"name\":\"ISP Primary\",\"ip\":\"203.0.113.1\",\"role\":\"Internet Uplink\",\"tags\":[\"wan\",\"internet\",\"primary\"],\"notes\":[\"AT&T MPLS\",\"1 Gbps dedicated\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"isp-secondary\":{\"shape\":\"globe\",\"name\":\"ISP Secondary\",\"ip\":\"198.51.100.1\",\"role\":\"Internet Uplink\",\"tags\":[\"wan\",\"internet\",\"backup\"],\"notes\":[\"Verizon Business\",\"500 Mbps backup\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"dc-internal-1\":{\"shape\":\"circle\",\"name\":\"DC1 Int DNS\",\"ip\":\"10.10.0.53\",\"role\":\"Internal DNS/AD\",\"tags\":[\"dns\",\"ad\",\"dc1\"],\"notes\":[\"Windows Server 2022\",\"Primary DC\"],\"mac\":\"00:50:56:AD:01:01\",\"rackUnit\":26,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a1\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"dc-internal-2\":{\"shape\":\"circle\",\"name\":\"DC2 Int DNS\",\"ip\":\"10.10.1.53\",\"role\":\"Internal DNS/AD\",\"tags\":[\"dns\",\"ad\",\"dc2\"],\"notes\":[\"Windows Server 2022\",\"Secondary DC\"],\"mac\":\"00:50:56:AD:01:02\",\"rackUnit\":26,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"app-server-1\":{\"shape\":\"server\",\"name\":\"App Server 01\",\"ip\":\"10.10.0.101\",\"role\":\"Application\",\"tags\":[\"app\",\"iis\",\"web\"],\"notes\":[\"Windows Server 2022\",\"IIS Application\"],\"mac\":\"00:50:56:AP:01:01\",\"rackUnit\":24,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a1\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"app-server-2\":{\"shape\":\"server\",\"name\":\"App Server 02\",\"ip\":\"10.10.0.102\",\"role\":\"Application\",\"tags\":[\"app\",\"iis\",\"web\"],\"notes\":[\"Windows Server 2022\",\"IIS Application\"],\"mac\":\"00:50:56:AP:01:02\",\"rackUnit\":22,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a1\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"db-server-1\":{\"shape\":\"database\",\"name\":\"SQL Server 01\",\"ip\":\"10.10.0.201\",\"role\":\"Database\",\"tags\":[\"db\",\"sql\",\"primary\"],\"notes\":[\"SQL Server 2022 Enterprise\",\"AlwaysOn Primary\"],\"mac\":\"00:50:56:DB:01:01\",\"rackUnit\":20,\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a1\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"db-server-2\":{\"shape\":\"database\",\"name\":\"SQL Server 02\",\"ip\":\"10.10.1.201\",\"role\":\"Database\",\"tags\":[\"db\",\"sql\",\"secondary\"],\"notes\":[\"SQL Server 2022 Enterprise\",\"AlwaysOn Secondary\"],\"mac\":\"00:50:56:DB:01:02\",\"rackUnit\":24,\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"k8s-master-1\":{\"shape\":\"hexagon\",\"name\":\"K8s Master 1\",\"ip\":\"10.10.1.50\",\"role\":\"Container Orchestration\",\"tags\":[\"kubernetes\",\"master\",\"container\"],\"notes\":[\"K8s Control Plane\",\"etcd member\"],\"mac\":\"00:50:56:K8:01:01\",\"rackUnit\":21,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"k8s-master-2\":{\"shape\":\"hexagon\",\"name\":\"K8s Master 2\",\"ip\":\"10.10.1.51\",\"role\":\"Container Orchestration\",\"tags\":[\"kubernetes\",\"master\",\"container\"],\"notes\":[\"K8s Control Plane\",\"etcd member\"],\"mac\":\"00:50:56:K8:01:02\",\"rackUnit\":19,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"k8s-master-3\":{\"shape\":\"hexagon\",\"name\":\"K8s Master 3\",\"ip\":\"10.10.1.52\",\"role\":\"Container Orchestration\",\"tags\":[\"kubernetes\",\"master\",\"container\"],\"notes\":[\"K8s Control Plane\",\"etcd member\"],\"mac\":\"00:50:56:K8:01:03\",\"rackUnit\":17,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"k8s-worker-1\":{\"shape\":\"server\",\"name\":\"K8s Worker 1\",\"ip\":\"10.10.1.60\",\"role\":\"Container Workload\",\"tags\":[\"kubernetes\",\"worker\",\"container\"],\"notes\":[\"K8s Worker Node\",\"64GB RAM\"],\"mac\":\"00:50:56:K8:02:01\",\"rackUnit\":15,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"k8s-worker-2\":{\"shape\":\"server\",\"name\":\"K8s Worker 2\",\"ip\":\"10.10.1.61\",\"role\":\"Container Workload\",\"tags\":[\"kubernetes\",\"worker\",\"container\"],\"notes\":[\"K8s Worker Node\",\"64GB RAM\"],\"mac\":\"00:50:56:K8:02:02\",\"rackUnit\":13,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"k8s-worker-3\":{\"shape\":\"server\",\"name\":\"K8s Worker 3\",\"ip\":\"10.10.1.62\",\"role\":\"Container Workload\",\"tags\":[\"kubernetes\",\"worker\",\"container\"],\"notes\":[\"K8s Worker Node\",\"64GB RAM\"],\"mac\":\"00:50:56:K8:02:03\",\"rackUnit\":11,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"k8s-worker-4\":{\"shape\":\"server\",\"name\":\"K8s Worker 4\",\"ip\":\"10.10.1.63\",\"role\":\"Container Workload\",\"tags\":[\"kubernetes\",\"worker\",\"container\"],\"notes\":[\"K8s Worker Node\",\"64GB RAM\"],\"mac\":\"00:50:56:K8:02:04\",\"rackUnit\":9,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"proxy-server-1\":{\"shape\":\"server\",\"name\":\"Proxy Server 1\",\"ip\":\"10.5.0.10\",\"role\":\"Web Proxy\",\"tags\":[\"proxy\",\"squid\",\"filtering\"],\"notes\":[\"Squid Proxy\",\"Content filtering\"],\"mac\":\"00:50:56:PX:01:01\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"security\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"proxy-server-2\":{\"shape\":\"server\",\"name\":\"Proxy Server 2\",\"ip\":\"10.5.0.11\",\"role\":\"Web Proxy\",\"tags\":[\"proxy\",\"squid\",\"filtering\"],\"notes\":[\"Squid Proxy\",\"HA pair\"],\"mac\":\"00:50:56:PX:01:02\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"security\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"vpn-concentrator\":{\"shape\":\"firewall\",\"name\":\"VPN Concentrator\",\"ip\":\"10.0.5.1\",\"role\":\"Remote Access VPN\",\"tags\":[\"vpn\",\"remote\",\"security\"],\"notes\":[\"Cisco ASA 5555-X\",\"AnyConnect SSL VPN\"],\"mac\":\"00:1A:2B:VP:01:01\",\"rackUnit\":\"\",\"uHeight\":\"2\",\"layer\":\"security\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"nac-server\":{\"shape\":\"server\",\"name\":\"NAC Server\",\"ip\":\"10.5.5.10\",\"role\":\"Network Access Control\",\"tags\":[\"nac\",\"ise\",\"802.1x\"],\"notes\":[\"Cisco ISE 3.1\",\"RADIUS/TACACS+\"],\"mac\":\"00:50:56:NA:01:01\",\"rackUnit\":\"\",\"uHeight\":\"2\",\"layer\":\"security\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"print-server\":{\"shape\":\"server\",\"name\":\"Print Server\",\"ip\":\"10.10.0.150\",\"role\":\"Print Services\",\"tags\":[\"print\",\"windows\",\"services\"],\"notes\":[\"Windows Print Server\",\"50+ printers\"],\"mac\":\"00:50:56:PR:01:01\",\"rackUnit\":18,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a1\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"file-server\":{\"shape\":\"database\",\"name\":\"File Server\",\"ip\":\"10.10.0.160\",\"role\":\"File Services\",\"tags\":[\"file\",\"smb\",\"dfs\"],\"notes\":[\"Windows File Server\",\"DFS namespace\"],\"mac\":\"00:50:56:FS:01:01\",\"rackUnit\":16,\"uHeight\":\"2\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a1\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"ca-server\":{\"shape\":\"server\",\"name\":\"Certificate Authority\",\"ip\":\"192.168.100.80\",\"role\":\"PKI Infrastructure\",\"tags\":[\"pki\",\"ca\",\"security\"],\"notes\":[\"Windows CA\",\"Enterprise Root CA\"],\"mac\":\"00:50:56:CA:01:01\",\"rackUnit\":5,\"uHeight\":\"1\",\"layer\":\"logical\",\"assignedRack\":\"mgmt-rack\",\"rackCapacity\":\"24\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"sccm-server\":{\"shape\":\"server\",\"name\":\"SCCM Server\",\"ip\":\"192.168.100.90\",\"role\":\"Endpoint Management\",\"tags\":[\"sccm\",\"patching\",\"software\"],\"notes\":[\"MECM Primary Site\",\"Software deployment\"],\"mac\":\"00:50:56:SC:01:01\",\"rackUnit\":3,\"uHeight\":\"2\",\"layer\":\"logical\",\"assignedRack\":\"mgmt-rack\",\"rackCapacity\":\"24\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"voip-cluster\":{\"shape\":\"phone\",\"name\":\"VoIP Cluster\",\"ip\":\"10.50.0.0/24\",\"role\":\"Voice Services\",\"tags\":[\"voip\",\"cisco\",\"ucm\"],\"notes\":[\"Cisco UCM Cluster\",\"3000 endpoints\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"application\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"video-conf\":{\"shape\":\"laptop\",\"name\":\"Video Conference\",\"ip\":\"10.51.0.0/24\",\"role\":\"Video Services\",\"tags\":[\"video\",\"webex\",\"teams\"],\"notes\":[\"Webex/Teams integration\",\"Meeting rooms\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"application\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"security-cameras\":{\"shape\":\"camera\",\"name\":\"Security Cameras\",\"ip\":\"10.60.0.0/24\",\"role\":\"Physical Security\",\"tags\":[\"cctv\",\"surveillance\",\"security\"],\"notes\":[\"150+ IP cameras\",\"30-day retention\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"nvr-cluster\":{\"shape\":\"server\",\"name\":\"NVR Cluster\",\"ip\":\"10.60.0.10\",\"role\":\"Video Recording\",\"tags\":[\"nvr\",\"surveillance\",\"storage\"],\"notes\":[\"Milestone XProtect\",\"500TB storage\"],\"mac\":\"00:50:56:NV:01:01\",\"rackUnit\":15,\"uHeight\":\"4\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-b2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"dev-server-1\":{\"shape\":\"server\",\"name\":\"Dev Server 1\",\"ip\":\"10.80.0.10\",\"role\":\"Development\",\"tags\":[\"dev\",\"gitlab\",\"ci-cd\",{\"type\":\"icon\",\"library\":\"selfhst\",\"name\":\"dokku\"}],\"notes\":[\"GitLab Server\",\"CI/CD pipelines\"],\"mac\":\"00:50:56:DV:01:01\",\"rackUnit\":\"\",\"uHeight\":\"2\",\"layer\":\"application\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"dev-server-2\":{\"shape\":\"server\",\"name\":\"Dev Server 2\",\"ip\":\"10.80.0.11\",\"role\":\"Development\",\"tags\":[\"dev\",\"jenkins\",\"ci-cd\"],\"notes\":[\"Jenkins Server\",\"Build automation\"],\"mac\":\"00:50:56:DV:01:02\",\"rackUnit\":\"\",\"uHeight\":\"2\",\"layer\":\"application\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"test-environment\":{\"shape\":\"hexagon\",\"name\":\"Test Environment\",\"ip\":\"10.81.0.0/24\",\"role\":\"QA/Testing\",\"tags\":[\"test\",\"qa\",\"staging\"],\"notes\":[\"Staging environment\",\"Pre-prod validation\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"application\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"erp-system\":{\"shape\":\"database\",\"name\":\"ERP System\",\"ip\":\"10.90.0.10\",\"role\":\"Business Application\",\"tags\":[\"erp\",\"sap\",\"business\"],\"notes\":[\"SAP S/4HANA\",\"Financial/HR systems\"],\"mac\":\"00:50:56:ER:01:01\",\"rackUnit\":\"\",\"uHeight\":\"4\",\"layer\":\"application\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"crm-system\":{\"shape\":\"database\",\"name\":\"CRM System\",\"ip\":\"10.91.0.10\",\"role\":\"Business Application\",\"tags\":[\"crm\",\"salesforce\",\"business\"],\"notes\":[\"Salesforce integration\",\"Sales/Marketing\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"application\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"endpoint-1000\":{\"shape\":\"laptop\",\"name\":\"Corporate Endpoints\",\"ip\":\"10.70.0.0/22\",\"role\":\"User Workstations\",\"tags\":[\"endpoints\",\"workstations\",\"users\"],\"notes\":[\"~1000 corporate laptops\",\"Windows 11\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"dist-switch-floor1\":{\"shape\":\"switch\",\"name\":\"Floor 1 Switch\",\"ip\":\"10.1.1.1\",\"role\":\"Distribution\",\"tags\":[\"distribution\",\"floor-1\",\"access\"],\"notes\":[\"Cisco C9300-48P\",\"PoE+ enabled\"],\"mac\":\"00:1A:2B:FL:01:01\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"dist-switch-floor2\":{\"shape\":\"switch\",\"name\":\"Floor 2 Switch\",\"ip\":\"10.1.2.1\",\"role\":\"Distribution\",\"tags\":[\"distribution\",\"floor-2\",\"access\"],\"notes\":[\"Cisco C9300-48P\",\"PoE+ enabled\"],\"mac\":\"00:1A:2B:FL:02:01\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"dist-switch-floor3\":{\"shape\":\"switch\",\"name\":\"Floor 3 Switch\",\"ip\":\"10.1.3.1\",\"role\":\"Distribution\",\"tags\":[\"distribution\",\"floor-3\",\"access\"],\"notes\":[\"Cisco C9300-48P\",\"PoE+ enabled\"],\"mac\":\"00:1A:2B:FL:03:01\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"dist-switch-floor4\":{\"shape\":\"switch\",\"name\":\"Floor 4 Switch\",\"ip\":\"10.1.4.1\",\"role\":\"Distribution\",\"tags\":[\"distribution\",\"floor-4\",\"access\"],\"notes\":[\"Cisco C9300-48P\",\"PoE+ enabled\"],\"mac\":\"00:1A:2B:FL:04:01\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"ap-floor1-zone1\":{\"shape\":\"wifi\",\"name\":\"AP Floor 1 Zone 1\",\"ip\":\"10.20.1.10\",\"role\":\"Wireless Access\",\"tags\":[\"wifi\",\"ap\",\"floor-1\"],\"notes\":[\"Cisco 9120AX\",\"Wi-Fi 6\"],\"mac\":\"00:1A:2B:AP:01:01\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"ap-floor2-zone1\":{\"shape\":\"wifi\",\"name\":\"AP Floor 2 Zone 1\",\"ip\":\"10.20.2.10\",\"role\":\"Wireless Access\",\"tags\":[\"wifi\",\"ap\",\"floor-2\"],\"notes\":[\"Cisco 9120AX\",\"Wi-Fi 6\"],\"mac\":\"00:1A:2B:AP:02:01\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"ap-floor3-zone1\":{\"shape\":\"wifi\",\"name\":\"AP Floor 3 Zone 1\",\"ip\":\"10.20.3.10\",\"role\":\"Wireless Access\",\"tags\":[\"wifi\",\"ap\",\"floor-3\"],\"notes\":[\"Cisco 9120AX\",\"Wi-Fi 6\"],\"mac\":\"00:1A:2B:AP:03:01\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"ap-floor4-zone1\":{\"shape\":\"wifi\",\"name\":\"AP Floor 4 Zone 1\",\"ip\":\"10.20.4.10\",\"role\":\"Wireless Access\",\"tags\":[\"wifi\",\"ap\",\"floor-4\"],\"notes\":[\"Cisco 9120AX\",\"Wi-Fi 6\"],\"mac\":\"00:1A:2B:AP:04:01\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"ups-dc-1\":{\"shape\":\"rectangle\",\"name\":\"UPS DC-1\",\"ip\":\"192.168.200.10\",\"role\":\"Power Management\",\"tags\":[\"power\",\"ups\",\"datacenter\"],\"notes\":[\"APC Symmetra\",\"80kVA\",\"30 min runtime\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"ups-dc-2\":{\"shape\":\"rectangle\",\"name\":\"UPS DC-2\",\"ip\":\"192.168.200.11\",\"role\":\"Power Management\",\"tags\":[\"power\",\"ups\",\"datacenter\"],\"notes\":[\"APC Symmetra\",\"80kVA\",\"Redundant\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"pdu-rack-a1\":{\"shape\":\"rectangle\",\"name\":\"PDU Rack A1\",\"ip\":\"192.168.200.21\",\"role\":\"Power Distribution\",\"tags\":[\"power\",\"pdu\",\"rack-a1\"],\"notes\":[\"APC Switched PDU\",\"Per-outlet metering\"],\"mac\":\"\",\"rackUnit\":1,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a1\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"pdu-rack-a2\":{\"shape\":\"rectangle\",\"name\":\"PDU Rack A2\",\"ip\":\"192.168.200.22\",\"role\":\"Power Distribution\",\"tags\":[\"power\",\"pdu\",\"rack-a2\"],\"notes\":[\"APC Switched PDU\",\"Per-outlet metering\"],\"mac\":\"\",\"rackUnit\":1,\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"dc-rack-a2\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"cooling-1\":{\"shape\":\"rectangle\",\"name\":\"CRAC Unit 1\",\"ip\":\"192.168.200.30\",\"role\":\"Cooling\",\"tags\":[\"cooling\",\"hvac\",\"datacenter\"],\"notes\":[\"Liebert CRV\",\"Row-based cooling\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"cooling-2\":{\"shape\":\"rectangle\",\"name\":\"CRAC Unit 2\",\"ip\":\"192.168.200.31\",\"role\":\"Cooling\",\"tags\":[\"cooling\",\"hvac\",\"datacenter\"],\"notes\":[\"Liebert CRV\",\"N+1 redundancy\"],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"camera-a\":{\"shape\":\"camera\",\"name\":\"camera A\",\"ip\":\"0.0.0.0\",\"role\":\"\",\"tags\":[],\"notes\":[],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"ping\":{\"enabled\":false,\"protocol\":\"http\",\"customUrl\":\"\",\"timeout\":3000,\"status\":\"unknown\",\"lastCheck\":null},\"locked\":false,\"groupId\":null,\"fovEnabled\":true,\"fovRotation\":104,\"fovDistance\":500,\"fovSweep\":60,\"fovSpeed\":10,\"fovAnimate\":true},\"camera-a-copy\":{\"shape\":\"camera\",\"name\":\"camera B\",\"ip\":\"0.0.0.0\",\"role\":\"\",\"tags\":[],\"notes\":[],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"ping\":{\"enabled\":false,\"protocol\":\"http\",\"customUrl\":\"\",\"timeout\":3000,\"status\":\"unknown\",\"lastCheck\":null},\"locked\":false,\"groupId\":null,\"fovEnabled\":true,\"fovRotation\":162,\"fovDistance\":500,\"fovSweep\":60,\"fovSpeed\":10,\"fovAnimate\":false}},\"edges\":{\"list\":[{\"id\":\"isp1-router1\",\"from\":\"isp-primary\",\"to\":\"core-router-1\",\"width\":6,\"color\":\"#10b981\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"Primary WAN link\"],\"fromPort\":\"Gi0/0\",\"toPort\":\"Gi1/0/1\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"isp2-router2\",\"from\":\"isp-secondary\",\"to\":\"core-router-2\",\"width\":6,\"color\":\"#10b981\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"Backup WAN link\"],\"fromPort\":\"Gi0/0\",\"toPort\":\"Gi1/0/1\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"router1-router2\",\"from\":\"core-router-1\",\"to\":\"core-router-2\",\"width\":4,\"color\":\"#f59e0b\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"HSRP Peering\"],\"fromPort\":\"Gi1/0/24\",\"toPort\":\"Gi1/0/24\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"router1-fw1\",\"from\":\"core-router-1\",\"to\":\"fw-external-1\",\"width\":4,\"color\":\"#ef4444\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"router2-fw2\",\"from\":\"core-router-2\",\"to\":\"fw-external-2\",\"width\":4,\"color\":\"#ef4444\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"fw1-fw2\",\"from\":\"fw-external-1\",\"to\":\"fw-external-2\",\"width\":3,\"color\":\"#f59e0b\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"HA heartbeat\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"dashed\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"fw1-coresw1\",\"from\":\"fw-external-1\",\"to\":\"core-switch-1\",\"width\":4,\"color\":\"#475569\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"fw2-coresw2\",\"from\":\"fw-external-2\",\"to\":\"core-switch-2\",\"width\":4,\"color\":\"#475569\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw1-coresw2\",\"from\":\"core-switch-1\",\"to\":\"core-switch-2\",\"width\":5,\"color\":\"#3b82f6\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"VPC peer-link\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw1-fwint\",\"from\":\"core-switch-1\",\"to\":\"fw-internal\",\"width\":3,\"color\":\"#ef4444\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw2-fwint\",\"from\":\"core-switch-2\",\"to\":\"fw-internal\",\"width\":3,\"color\":\"#ef4444\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw1-racka1\",\"from\":\"core-switch-1\",\"to\":\"dc-rack-a1\",\"width\":4,\"color\":\"#475569\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw2-racka1\",\"from\":\"core-switch-2\",\"to\":\"dc-rack-a1\",\"width\":4,\"color\":\"#475569\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw1-racka2\",\"from\":\"core-switch-1\",\"to\":\"dc-rack-a2\",\"width\":4,\"color\":\"#475569\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw2-racka2\",\"from\":\"core-switch-2\",\"to\":\"dc-rack-a2\",\"width\":4,\"color\":\"#475569\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw1-rackb1\",\"from\":\"core-switch-1\",\"to\":\"dc-rack-b1\",\"width\":4,\"color\":\"#475569\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw2-rackb1\",\"from\":\"core-switch-2\",\"to\":\"dc-rack-b1\",\"width\":4,\"color\":\"#475569\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw1-rackb2\",\"from\":\"core-switch-1\",\"to\":\"dc-rack-b2\",\"width\":4,\"color\":\"#475569\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw2-rackb2\",\"from\":\"core-switch-2\",\"to\":\"dc-rack-b2\",\"width\":4,\"color\":\"#475569\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"fw1-dmz\",\"from\":\"fw-external-1\",\"to\":\"dmz-rack\",\"width\":3,\"color\":\"#f59e0b\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"DMZ segment\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"fw2-dmz\",\"from\":\"fw-external-2\",\"to\":\"dmz-rack\",\"width\":3,\"color\":\"#f59e0b\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"DMZ segment\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw1-mgmt\",\"from\":\"core-switch-1\",\"to\":\"mgmt-rack\",\"width\":3,\"color\":\"#8b5cf6\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"OOB management\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw1-wlc1\",\"from\":\"core-switch-1\",\"to\":\"wlc-primary\",\"width\":3,\"color\":\"#06b6d4\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw2-wlc2\",\"from\":\"core-switch-2\",\"to\":\"wlc-secondary\",\"width\":3,\"color\":\"#06b6d4\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\",\"animate\":true},{\"id\":\"wlc1-wlc2\",\"from\":\"wlc-primary\",\"to\":\"wlc-secondary\",\"width\":2,\"color\":\"#f59e0b\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"HA pair\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"dashed\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"wlc1-mobile-hq\",\"from\":\"wlc-primary\",\"to\":\"mobile-zone-hq\",\"width\":2,\"color\":\"#06b6d4\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"wlc1-mobile-guest\",\"from\":\"wlc-primary\",\"to\":\"mobile-zone-guest\",\"width\":2,\"color\":\"#06b6d4\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"wlc1-mobile-iot\",\"from\":\"wlc-primary\",\"to\":\"mobile-zone-iot\",\"width\":2,\"color\":\"#06b6d4\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"router1-branch-ny\",\"from\":\"core-router-1\",\"to\":\"branch-router-ny\",\"width\":3,\"color\":\"#a855f7\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"SD-WAN tunnel\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"dashed\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"router1-branch-la\",\"from\":\"core-router-1\",\"to\":\"branch-router-la\",\"width\":3,\"color\":\"#a855f7\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"SD-WAN tunnel\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"dashed\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"router1-branch-chi\",\"from\":\"core-router-1\",\"to\":\"branch-router-chi\",\"width\":3,\"color\":\"#a855f7\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"SD-WAN tunnel\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"dashed\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"router1-branch-lon\",\"from\":\"core-router-1\",\"to\":\"branch-router-lon\",\"width\":3,\"color\":\"#a855f7\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"SD-WAN tunnel\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"dashed\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"router1-branch-tokyo\",\"from\":\"core-router-1\",\"to\":\"branch-router-tokyo\",\"width\":3,\"color\":\"#a855f7\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"SD-WAN tunnel\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"dashed\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"router1-aws\",\"from\":\"core-router-1\",\"to\":\"cloud-aws\",\"width\":3,\"color\":\"#f97316\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"Direct Connect\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"dashed\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"router2-azure\",\"from\":\"core-router-2\",\"to\":\"cloud-azure\",\"width\":3,\"color\":\"#0ea5e9\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"ExpressRoute\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"dashed\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"fwint-gcp\",\"from\":\"fw-internal\",\"to\":\"cloud-gcp\",\"width\":2,\"color\":\"#22c55e\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"VPN tunnel\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"dashed\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw1-floor1\",\"from\":\"core-switch-1\",\"to\":\"dist-switch-floor1\",\"width\":3,\"color\":\"#475569\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw1-floor2\",\"from\":\"core-switch-1\",\"to\":\"dist-switch-floor2\",\"width\":3,\"color\":\"#475569\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw2-floor3\",\"from\":\"core-switch-2\",\"to\":\"dist-switch-floor3\",\"width\":3,\"color\":\"#475569\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw2-floor4\",\"from\":\"core-switch-2\",\"to\":\"dist-switch-floor4\",\"width\":3,\"color\":\"#475569\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"floor1-endpoints\",\"from\":\"dist-switch-floor1\",\"to\":\"endpoint-1000\",\"width\":2,\"color\":\"#94a3b8\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"floor1-ap1\",\"from\":\"dist-switch-floor1\",\"to\":\"ap-floor1-zone1\",\"width\":2,\"color\":\"#06b6d4\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"floor2-ap2\",\"from\":\"dist-switch-floor2\",\"to\":\"ap-floor2-zone1\",\"width\":2,\"color\":\"#06b6d4\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"floor3-ap3\",\"from\":\"dist-switch-floor3\",\"to\":\"ap-floor3-zone1\",\"width\":2,\"color\":\"#06b6d4\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"floor4-ap4\",\"from\":\"dist-switch-floor4\",\"to\":\"ap-floor4-zone1\",\"width\":2,\"color\":\"#06b6d4\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"fwint-proxy1\",\"from\":\"fw-internal\",\"to\":\"proxy-server-1\",\"width\":2,\"color\":\"#ef4444\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"fwint-proxy2\",\"from\":\"fw-internal\",\"to\":\"proxy-server-2\",\"width\":2,\"color\":\"#ef4444\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"fwext1-vpn\",\"from\":\"fw-external-1\",\"to\":\"vpn-concentrator\",\"width\":3,\"color\":\"#8b5cf6\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw1-nac\",\"from\":\"core-switch-1\",\"to\":\"nac-server\",\"width\":2,\"color\":\"#f59e0b\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw1-voip\",\"from\":\"core-switch-1\",\"to\":\"voip-cluster\",\"width\":3,\"color\":\"#22c55e\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw2-video\",\"from\":\"core-switch-2\",\"to\":\"video-conf\",\"width\":3,\"color\":\"#22c55e\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw1-cameras\",\"from\":\"core-switch-1\",\"to\":\"security-cameras\",\"width\":2,\"color\":\"#94a3b8\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"fwint-dev1\",\"from\":\"fw-internal\",\"to\":\"dev-server-1\",\"width\":2,\"color\":\"#a855f7\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"fwint-dev2\",\"from\":\"fw-internal\",\"to\":\"dev-server-2\",\"width\":2,\"color\":\"#a855f7\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\",\"animate\":true,\"animationSpeed\":\"1.5\"},{\"id\":\"fwint-test\",\"from\":\"fw-internal\",\"to\":\"test-environment\",\"width\":2,\"color\":\"#a855f7\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"coresw1-erp\",\"from\":\"core-switch-1\",\"to\":\"erp-system\",\"width\":3,\"color\":\"#f59e0b\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"fwext1-crm\",\"from\":\"fw-external-1\",\"to\":\"crm-system\",\"width\":2,\"color\":\"#f59e0b\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[\"Salesforce cloud\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"dashed\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"ups1-racka1\",\"from\":\"ups-dc-1\",\"to\":\"dc-rack-a1\",\"width\":2,\"color\":\"#fbbf24\",\"direction\":\"forward\",\"type\":\"main\",\"notes\":[\"Power feed A\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"ups2-racka2\",\"from\":\"ups-dc-2\",\"to\":\"dc-rack-a2\",\"width\":2,\"color\":\"#fbbf24\",\"direction\":\"forward\",\"type\":\"main\",\"notes\":[\"Power feed B\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"ups1-rackb1\",\"from\":\"ups-dc-1\",\"to\":\"dc-rack-b1\",\"width\":2,\"color\":\"#fbbf24\",\"direction\":\"forward\",\"type\":\"main\",\"notes\":[\"Power feed A\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\",\"animate\":true,\"animationSpeed\":\"4\"},{\"id\":\"ups2-rackb2\",\"from\":\"ups-dc-2\",\"to\":\"dc-rack-b2\",\"width\":2,\"color\":\"#fbbf24\",\"direction\":\"forward\",\"type\":\"main\",\"notes\":[\"Power feed B\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"cooling1-racka1\",\"from\":\"cooling-1\",\"to\":\"dc-rack-a1\",\"width\":2,\"color\":\"#38bdf8\",\"direction\":\"forward\",\"type\":\"main\",\"notes\":[\"Cooling zone\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"dotted\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"cooling2-rackb1\",\"from\":\"cooling-2\",\"to\":\"dc-rack-b1\",\"width\":2,\"color\":\"#38bdf8\",\"direction\":\"forward\",\"type\":\"main\",\"notes\":[\"Cooling zone\"],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"dotted\",\"_pairIndex\":0,\"_pairTotal\":1,\"routing\":\"orthogonal\"},{\"id\":\"custom-1765237881452\",\"type\":\"custom\",\"color\":\"#c800ff\",\"width\":4,\"lineStyle\":\"solid\",\"direction\":\"forward\",\"points\":[{\"x\":3492.3994140625,\"y\":1526.9556884765625},{\"x\":3500.609619140625,\"y\":1830.7386474609375},{\"x\":3303.561279296875,\"y\":1732.2144775390625}],\"notes\":[],\"routing\":\"orthogonal\"},{\"id\":\"custom-1765239355462\",\"type\":\"custom\",\"color\":\"#f97316\",\"width\":4,\"lineStyle\":\"solid\",\"direction\":\"forward\",\"points\":[{\"x\":2467.182861328125,\"y\":156.12173461914062},{\"x\":2146.36376953125,\"y\":146.32574462890625},{\"x\":2305.548828125,\"y\":244.28573608398438}],\"notes\":[],\"routing\":\"orthogonal\"}]},\"positions\":{\"core-router-1\":{\"x\":3720.166015625,\"y\":245.9932403564453},\"core-router-2\":{\"x\":2499.883407638303,\"y\":329.99503430389154},\"fw-external-1\":{\"x\":3221.7385182723783,\"y\":1016.1364499992887},\"fw-external-2\":{\"x\":1915.5213706410505,\"y\":224.43528858865443},\"fw-internal\":{\"x\":1746.9168185079352,\"y\":477.5300527221864},\"core-switch-1\":{\"x\":449.39860669455675,\"y\":384.4578707617695},\"core-switch-2\":{\"x\":761.1664921394672,\"y\":180.89283910873155},\"dc-rack-a1\":{\"x\":783.7017241128451,\"y\":647.4086870405963},\"dc-rack-a2\":{\"x\":209.25701628255229,\"y\":228.01593190351014},\"dc-rack-b1\":{\"x\":3184.3186625759854,\"y\":1627.4495531027196},\"dc-rack-b2\":{\"x\":245.37065918741246,\"y\":499.6191264194081},\"dmz-rack\":{\"x\":2176.4105289561007,\"y\":610.8312056412005},\"mgmt-rack\":{\"x\":1601.2987201807314,\"y\":1281.4753424975324},\"esxi-host-01\":{\"x\":2162.2166789540615,\"y\":2608.110619289529},\"esxi-host-02\":{\"x\":2205.94717202368,\"y\":2689.67539624076},\"esxi-host-03\":{\"x\":2154.6015436939074,\"y\":2771.203009774913},\"esxi-host-04\":{\"x\":2195.986926025096,\"y\":2845},\"tor-switch-a1\":{\"x\":2146.8943639962963,\"y\":2845},\"esxi-host-05\":{\"x\":2185.9099961569727,\"y\":2845},\"esxi-host-06\":{\"x\":2139.099728450725,\"y\":2845},\"esxi-host-07\":{\"x\":2175.7223818764883,\"y\":2845},\"esxi-host-08\":{\"x\":2131.2222777148922,\"y\":2845},\"tor-switch-a2\":{\"x\":2165.4301485385085,\"y\":2845},\"san-primary\":{\"x\":2123.2667017518106,\"y\":2845},\"san-secondary\":{\"x\":2155.0394237844876,\"y\":2845},\"fc-switch-1\":{\"x\":2115.2377370375634,\"y\":2845},\"fc-switch-2\":{\"x\":2144.5563938942755,\"y\":2845},\"backup-server-1\":{\"x\":2107.1401637413705,\"y\":2845},\"backup-server-2\":{\"x\":2133.987300103025,\"y\":2845},\"tape-library\":{\"x\":2098.9788028796397,\"y\":2845},\"tor-switch-b1\":{\"x\":2123.338434885373,\"y\":2845},\"tor-switch-b2\":{\"x\":2090.7585134456995,\"y\":2845},\"web-server-1\":{\"x\":2112.6161382091163,\"y\":2845},\"web-server-2\":{\"x\":2082.484189516922,\"y\":2845},\"waf-1\":{\"x\":2101.826793760617,\"y\":2845},\"load-balancer-dmz\":{\"x\":2074.1607573409574,\"y\":2845},\"mail-gateway\":{\"x\":2090.97682514417,\"y\":2845},\"dns-external-1\":{\"x\":2065.7931724028163,\"y\":2845},\"dns-external-2\":{\"x\":2080.0726920576153,\"y\":2845},\"vcenter\":{\"x\":2057.3864164745437,\"y\":2845},\"nsx-manager\":{\"x\":2069.1208864464534,\"y\":2845},\"siem-server\":{\"x\":2048.945494649244,\"y\":2845},\"nms-server\":{\"x\":2058.1279286387635,\"y\":2845},\"jump-server\":{\"x\":2040.4754323612206,\"y\":2845},\"ipam-server\":{\"x\":2047.1003634632284,\"y\":2845},\"wlc-primary\":{\"x\":1575.9723612611924,\"y\":2306.135986328125},\"wlc-secondary\":{\"x\":1468.1361870166274,\"y\":1563.733642578125},\"mobile-zone-hq\":{\"x\":2354.901177346808,\"y\":2806.0078125},\"mobile-zone-guest\":{\"x\":2307.6605605284435,\"y\":2611.047119140625},\"mobile-zone-iot\":{\"x\":2229.397686389302,\"y\":2299.110107421875},\"branch-router-ny\":{\"x\":3151.903101363964,\"y\":633.6580810546875},\"branch-router-la\":{\"x\":3083.8876194705945,\"y\":506.90625},\"branch-router-chi\":{\"x\":3355.02409980103,\"y\":393.1805725097656},\"branch-router-lon\":{\"x\":3113.609823320121,\"y\":260.4093322753906},\"branch-router-tokyo\":{\"x\":3699.3234994733834,\"y\":471.4241027832031},\"cloud-aws\":{\"x\":3436.528122523513,\"y\":545.9614868164062},\"cloud-azure\":{\"x\":2592.566210818907,\"y\":2724.068115234375},\"cloud-gcp\":{\"x\":2827.3183770424234,\"y\":2731.397216796875},\"isp-primary\":{\"x\":3712.192068081962,\"y\":615.64990234375},\"isp-secondary\":{\"x\":2702.3789772348055,\"y\":467.890869140625},\"dc-internal-1\":{\"x\":1958.4243458877936,\"y\":2845},\"dc-internal-2\":{\"x\":1963.768951182132,\"y\":2845},\"app-server-1\":{\"x\":1947.3819379304134,\"y\":2845},\"app-server-2\":{\"x\":1955.2862087394126,\"y\":2845},\"db-server-1\":{\"x\":1936.3708569559828,\"y\":2845},\"db-server-2\":{\"x\":1946.8300873488822,\"y\":2845},\"k8s-master-1\":{\"x\":1925.397658583093,\"y\":2845},\"k8s-master-2\":{\"x\":1938.405621494142,\"y\":2845},\"k8s-master-3\":{\"x\":1914.4688758763386,\"y\":2845},\"k8s-worker-1\":{\"x\":1930.017826812177,\"y\":2845},\"k8s-worker-2\":{\"x\":1903.5910154567553,\"y\":2845},\"k8s-worker-3\":{\"x\":1921.6716971072178,\"y\":2845},\"k8s-worker-4\":{\"x\":1892.7705536280016,\"y\":2845},\"proxy-server-1\":{\"x\":1806.1152433697903,\"y\":653.7529296875},\"proxy-server-2\":{\"x\":2937.4207928721535,\"y\":2628.7880859375},\"vpn-concentrator\":{\"x\":3642.252088474593,\"y\":946.7255249023438},\"nac-server\":{\"x\":1153.2626148502184,\"y\":1172.1895751953125},\"print-server\":{\"x\":1896.9328460745962,\"y\":2845},\"file-server\":{\"x\":1860.7177871362182,\"y\":2845},\"ca-server\":{\"x\":1888.8027739274805,\"y\":2845},\"sccm-server\":{\"x\":1850.1909418511675,\"y\":2845},\"voip-cluster\":{\"x\":1777.038465328039,\"y\":1616.8961181640625},\"video-conf\":{\"x\":1993.8373941679588,\"y\":2244.936309814453},\"security-cameras\":{\"x\":1674.413336949044,\"y\":2046.0380859375},\"nvr-cluster\":{\"x\":1829.4110389706402,\"y\":2845},\"dev-server-1\":{\"x\":2800.5894350649614,\"y\":1175.623291015625},\"dev-server-2\":{\"x\":1945.0822182484326,\"y\":1164.5184783935547},\"test-environment\":{\"x\":2566.9100352578575,\"y\":885.2827758789062},\"erp-system\":{\"x\":789.9880103985649,\"y\":473.7113342285156},\"crm-system\":{\"x\":3514.6003232048542,\"y\":1137.7720947265625},\"endpoint-1000\":{\"x\":991.6812012057328,\"y\":2284.42236328125},\"dist-switch-floor1\":{\"x\":654.2091033261356,\"y\":2020.0086669921875},\"dist-switch-floor2\":{\"x\":853.8845527112826,\"y\":1843.2872314453125},\"dist-switch-floor3\":{\"x\":1899.4353951584517,\"y\":1456.5068359375},\"dist-switch-floor4\":{\"x\":488.5289313756234,\"y\":181.47256469726562},\"ap-floor1-zone1\":{\"x\":1140.16846970184,\"y\":2070.2916259765625},\"ap-floor2-zone1\":{\"x\":688.1952143592268,\"y\":2384.4775390625},\"ap-floor3-zone1\":{\"x\":2145.3803027919676,\"y\":1890.2816162109375},\"ap-floor4-zone1\":{\"x\":517.646146409649,\"y\":565.59716796875},\"ups-dc-1\":{\"x\":771.1406786539856,\"y\":295.9266662597656},\"ups-dc-2\":{\"x\":216.2410855890687,\"y\":330.3345947265625},\"pdu-rack-a1\":{\"x\":1804.774444371901,\"y\":2845},\"pdu-rack-a2\":{\"x\":1741.6184034693686,\"y\":2845},\"cooling-1\":{\"x\":245.7080801919958,\"y\":626.1914672851562},\"cooling-2\":{\"x\":1603.293611085831,\"y\":981.0621185302734},\"camera-a\":{\"x\":166.57075412676295,\"y\":145},\"camera-a-copy\":{\"x\":1040.653076171875,\"y\":738.42822265625}},\"sizes\":{\"isp-secondary\":139,\"test-environment\":148,\"dev-server-1\":128,\"core-router-2\":120,\"camera-a\":45,\"camera-a-copy\":45},\"styles\":{\"dc-rack-b2\":{\"all\":{\"circleColor\":\"#ff0000\"}},\"dc-rack-a1\":{\"all\":{\"circleColor\":\"#ff0000\"}},\"dc-rack-b1\":{\"all\":{\"circleColor\":\"#ff0000\",\"titleSize\":59}},\"isp-secondary\":{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"alist\"}}},\"core-router-2\":{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"actual-budget\"},\"pingOffsetX\":-15,\"pingOffsetY\":-38}},\"fw-external-1\":{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"anonaddy\"}}},\"cloud-aws\":{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"ansible\"}}},\"isp-primary\":{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"wikidocs\"}}},\"branch-router-tokyo\":{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"adguard-home\"}}},\"core-router-1\":{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"borg\"}}},\"test-environment\":{\"all\":{\"icon\":{\"library\":\"simple\",\"name\":\"apple\"}}},\"dev-server-1\":{\"all\":{\"icon\":{\"library\":\"simple\",\"name\":\"amazonwebservices\"}}}},\"legend\":{\"#10b981\":\"Trusted Lan\",\"#f59e0b\":\"Secure Lan\",\"#ef4444\":\"DMZ\",\"#475569\":\"Main ISP\",\"#3b82f6\":\"Alternate ISP\",\"#8b5cf6\":\"you can edit me too\",\"#06b6d4\":\"you can edit me too\",\"#a855f7\":\"you can edit me too\",\"#f97316\":\"you can edit me too\",\"#0ea5e9\":\"you can edit me too\",\"#22c55e\":\"you can edit me too\",\"#94a3b8\":\"you can edit me too\",\"#fbbf24\":\"you can edit me too\",\"#38bdf8\":\"you can edit me too\",\"#c800ff\":\"you can edit me too\"},\"rects\":{\"list\":[{\"id\":\"rect-1765237540610\",\"x\":2879.214599609375,\"y\":159.71981811523438,\"width\":992.196044921875,\"height\":538.8650817871094,\"color\":\"#f97316\",\"style\":\"filled\",\"lineStyle\":\"solid\",\"notes\":[]},{\"id\":\"rect-1765237681216\",\"x\":448.3926696777344,\"y\":1671.651123046875,\"width\":916.3436584472656,\"height\":924.27734375,\"color\":\"#c800ff\",\"style\":\"outlined\",\"lineStyle\":\"solid\",\"notes\":[]},{\"id\":\"rect-1766437913740\",\"x\":904.5889892578125,\"y\":115.40318298339844,\"width\":110.93878173828125,\"height\":919.6242218017578,\"color\":\"#5215f9\",\"style\":\"filled\",\"lineStyle\":\"wall\",\"notes\":[],\"borderWidth\":13},{\"id\":\"rect-1766437935414\",\"x\":130.93685150146484,\"y\":1072.3624877929688,\"width\":872.9131851196289,\"height\":99.260986328125,\"color\":\"#5215f9\",\"style\":\"filled\",\"lineStyle\":\"wall\",\"notes\":[],\"borderWidth\":13}]},\"texts\":{\"list\":[{\"id\":\"text-1765237828167\",\"x\":3411.458740234375,\"y\":1390.00439453125,\"content\":\"Double click on desktop\\nor long press on mobile\\nto enter rack canvas view\",\"fontSize\":46,\"color\":\"#e2e8f0\",\"fontWeight\":\"bold\",\"fontStyle\":\"italic\",\"textAlign\":\"middle\",\"textDecoration\":\"none\",\"bgColor\":\"#000000\",\"bgEnabled\":false,\"opacity\":1},{\"id\":\"text-1765239331126\",\"x\":2454.5615234375,\"y\":160.73322105407715,\"content\":\"Google is live!\",\"fontSize\":56,\"color\":\"#e2e8f0\",\"fontWeight\":\"bold\",\"fontStyle\":\"normal\",\"textAlign\":\"start\",\"textDecoration\":\"none\",\"bgColor\":\"#000000\",\"bgEnabled\":false,\"opacity\":1},{\"id\":\"text-1766446595277\",\"x\":654.3878479003906,\"y\":1367.7945556640625,\"content\":\"SITE A\",\"fontSize\":101,\"color\":\"#e2e8f0\",\"fontWeight\":\"normal\",\"fontStyle\":\"normal\",\"textAlign\":\"start\",\"textDecoration\":\"none\",\"bgColor\":\"#000000\",\"bgEnabled\":false,\"opacity\":1},{\"id\":\"text-1766446610211\",\"x\":180.63662719726562,\"y\":1128.822998046875,\"content\":\"SITE A\",\"fontSize\":101,\"color\":\"#e2e8f0\",\"fontWeight\":\"normal\",\"fontStyle\":\"normal\",\"textAlign\":\"start\",\"textDecoration\":\"none\",\"bgColor\":\"#000000\",\"bgEnabled\":false,\"opacity\":1},{\"id\":\"text-1766453024797\",\"x\":968.6458740234375,\"y\":1028.6621398925781,\"content\":\"SITE A\",\"fontSize\":101,\"color\":\"#e2e8f0\",\"fontWeight\":\"normal\",\"fontStyle\":\"normal\",\"textAlign\":\"start\",\"textDecoration\":\"none\",\"bgColor\":\"#000000\",\"bgEnabled\":false,\"opacity\":1,\"rotation\":-89,\"_dragStartX\":972.46826171875,\"_dragStartY\":1009.5499572753906},{\"id\":\"text-1766453070975\",\"x\":613.1589965820312,\"y\":1139.512939453125,\"content\":\"SITE A\",\"fontSize\":101,\"color\":\"#e2e8f0\",\"fontWeight\":\"normal\",\"fontStyle\":\"normal\",\"textAlign\":\"start\",\"textDecoration\":\"none\",\"bgColor\":\"#000000\",\"bgEnabled\":false,\"opacity\":1},{\"id\":\"text-1766453072857\",\"x\":968.64599609375,\"y\":474.40818786621094,\"content\":\"SITE A\",\"fontSize\":101,\"color\":\"#e2e8f0\",\"fontWeight\":\"normal\",\"fontStyle\":\"normal\",\"textAlign\":\"start\",\"textDecoration\":\"none\",\"bgColor\":\"#000000\",\"bgEnabled\":false,\"opacity\":1,\"rotation\":269,\"_dragStartX\":1480.85302734375,\"_dragStartY\":822.2503356933594}]},\"pageState\":{\"title\":\"The One File Corporate\",\"background\":\"\",\"topbarBg\":\"rgba(9, 12, 20, 0.9)\",\"topbarBorder\":\"#1f2533\",\"panel\":\"#0b0e13\",\"panelAlt\":\"#10141b\",\"accent\":\"#4fd1c5\",\"sidebarBg\":\"#10141b\",\"btnBg\":\"#0b0e13\",\"btnText\":\"#e2e8f0\",\"tagFill\":\"#1e293b\",\"tagText\":\"#e2e8f0\",\"tagBorder\":\"#475569\",\"inputBg\":\"#0b0e13\",\"inputText\":\"#e2e8f0\",\"inputBorder\":\"#1f2937\",\"inputFont\":\"Inter, system-ui, sans-serif\",\"inputFontSize\":14,\"toolbarBg\":\"#0f172a\",\"toolbarBorder\":\"#1f2937\",\"toolbarText\":\"#94a3b8\",\"toolbarBtnBg\":\"#0b0e13\",\"toolbarBtnText\":\"#e2e8f0\",\"minimapDots\":\"#94a3b8\",\"canvasHintEnabled\":true,\"canvasHintText\":\"\",\"canvasHintBg\":\"#0f172a\",\"canvasHintColor\":\"#94a3b8\",\"danger\":\"#f56565\",\"textMain\":\"#e2e8f0\",\"textSoft\":\"#94a3b8\",\"topbarHeight\":103,\"sidebarWidth\":350,\"mobileFooterHeight\":40,\"sidebarCollapsed\":false,\"nodeFill\":\"#1e293b\",\"nodeStroke\":\"#475569\",\"nodeTitle\":\"#e2e8f0\",\"nodeSub\":\"#94a3b8\",\"nodeTitleSize\":41,\"nodeSubSize\":27,\"nodeFont\":\"monospace\",\"defaultEdge\":\"#475569\",\"selectionHandle\":\"#f59e0b\",\"selectionHandleSize\":8,\"groupIndicator\":\"#4fd1c5\",\"canvasGradientTop\":\"#1e2532\",\"canvasGradientBottom\":\"#050608\",\"canvasBorder\":\"#475569\",\"canvasGrid\":\"#475569\",\"canvasGridSize\":50,\"canvasGridEnabled\":true,\"rackFrameFill\":\"#0f172a\",\"rackFrameStroke\":\"#4fd1c5\",\"rackLineColor\":\"#475569\",\"rackTextColor\":\"#4fd1c5\",\"rackGridEnabled\":true,\"viewOnly\":false,\"defaultEdgeRouting\":\"orthogonal\",\"animateConnections\":false,\"animationStyle\":\"arrows\",\"animationDirection\":\"all\",\"animationSpeed\":4,\"autoPingEnabled\":false,\"autoPingInterval\":30}},{\"id\":\"tab-1765235136918\",\"name\":\"Homelab 2\",\"nodes\":{\"internet\":{\"shape\":\"square\",\"name\":\"Internet\",\"ip\":\"0.0.0.0\",\"role\":\"\",\"tags\":[],\"notes\":[],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"internet-copy\":{\"shape\":\"firewall\",\"name\":\"OPNSENSE\",\"ip\":\"0.0.0.0\",\"role\":\"\",\"tags\":[],\"notes\":[],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"opnsense-copy\":{\"shape\":\"firewall\",\"name\":\"Docker\",\"ip\":\"0.0.0.0\",\"role\":\"\",\"tags\":[],\"notes\":[],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"docker-copy\":{\"shape\":\"firewall\",\"name\":\"Docker2\",\"ip\":\"0.0.0.0\",\"role\":\"\",\"tags\":[],\"notes\":[],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"docker-copy-1\":{\"shape\":\"firewall\",\"name\":\"Docker3\",\"ip\":\"0.0.0.0\",\"role\":\"\",\"tags\":[],\"notes\":[],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"docker-copy-2\":{\"shape\":\"firewall\",\"name\":\"Docker 4\",\"ip\":\"0.0.0.0\",\"role\":\"\",\"tags\":[{\"type\":\"icon\",\"library\":\"selfhst\",\"name\":\"docker\"},{\"type\":\"icon\",\"library\":\"selfhst\",\"name\":\"authentik\"},{\"type\":\"icon\",\"library\":\"selfhst\",\"name\":\"immich\"}],\"notes\":[],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"opnsense-copy-1\":{\"shape\":\"firewall\",\"name\":\"OPNSENSE GUEST\",\"ip\":\"0.0.0.0\",\"role\":\"\",\"tags\":[],\"notes\":[],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"phone\":{\"shape\":\"phone\",\"name\":\"Phone\",\"ip\":\"0.0.0.0\",\"role\":\"\",\"tags\":[],\"notes\":[],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"desktop\":{\"shape\":\"pc\",\"name\":\"Desktop\",\"ip\":\"0.0.0.0\",\"role\":\"\",\"tags\":[],\"notes\":[],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"dns\":{\"shape\":\"cloud\",\"name\":\"DNS\",\"ip\":\"0.0.0.0\",\"role\":\"\",\"tags\":[],\"notes\":[],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":false,\"locked\":false,\"groupId\":null},\"racked\":{\"shape\":\"server\",\"name\":\"Racked\",\"ip\":\"\",\"role\":\"Rack\",\"tags\":[],\"notes\":[],\"mac\":\"\",\"rackUnit\":\"\",\"uHeight\":\"1\",\"layer\":\"physical\",\"assignedRack\":\"\",\"rackCapacity\":\"42\",\"isRack\":true,\"locked\":false,\"groupId\":null}},\"edges\":{\"list\":[{\"id\":\"internet-internet-copy-1765238145151\",\"from\":\"internet\",\"to\":\"internet-copy\",\"width\":4,\"color\":\"#55e208\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1},{\"id\":\"internet-copy-opnsense-copy-1765238187451\",\"from\":\"internet-copy\",\"to\":\"opnsense-copy\",\"width\":4,\"color\":\"#4c00ff\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1},{\"id\":\"internet-copy-docker-copy-1765238242477\",\"from\":\"internet-copy\",\"to\":\"docker-copy\",\"width\":4,\"color\":\"#4c00ff\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1},{\"id\":\"internet-copy-docker-copy-1-1765238244637\",\"from\":\"internet-copy\",\"to\":\"docker-copy-1\",\"width\":4,\"color\":\"#4c00ff\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1},{\"id\":\"internet-copy-docker-copy-2-1765238246233\",\"from\":\"internet-copy\",\"to\":\"docker-copy-2\",\"width\":4,\"color\":\"#4c00ff\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1},{\"id\":\"internet-opnsense-copy-1-1765238266117\",\"from\":\"internet\",\"to\":\"opnsense-copy-1\",\"width\":4,\"color\":\"#80ff00\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1},{\"id\":\"opnsense-copy-1-dns-1765238347996\",\"from\":\"opnsense-copy-1\",\"to\":\"dns\",\"width\":4,\"color\":\"#fb00ff\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1},{\"id\":\"dns-desktop-1765238386101\",\"from\":\"dns\",\"to\":\"desktop\",\"width\":4,\"color\":\"#ff00d0\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1},{\"id\":\"phone-dns-1765238391156\",\"from\":\"phone\",\"to\":\"dns\",\"width\":4,\"color\":\"#ff00d0\",\"direction\":\"both\",\"type\":\"main\",\"notes\":[],\"fromPort\":\"\",\"toPort\":\"\",\"lineStyle\":\"solid\",\"_pairIndex\":0,\"_pairTotal\":1},{\"id\":\"custom-1765239449323\",\"type\":\"custom\",\"color\":\"#f97316\",\"width\":4,\"lineStyle\":\"solid\",\"direction\":\"forward\",\"points\":[{\"x\":2936.464111328125,\"y\":786.07958984375},{\"x\":3184.112060546875,\"y\":887.6153564453125},{\"x\":2763.110107421875,\"y\":981.7216796875}],\"notes\":[]}]},\"positions\":{\"internet\":{\"x\":2103.968290880771,\"y\":268},\"internet-copy\":{\"x\":2066.9677515897347,\"y\":473.4119134177565},\"opnsense-copy\":{\"x\":1773.8400660428597,\"y\":666.5758233298659},\"docker-copy\":{\"x\":1931.1978950081452,\"y\":782.2775961320921},\"docker-copy-1\":{\"x\":2158.1262397347077,\"y\":767.7122274797483},\"docker-copy-2\":{\"x\":2342.2663764534577,\"y\":631.7681967180296},\"opnsense-copy-1\":{\"x\":2757.879480087803,\"y\":307.6117116091891},\"phone\":{\"x\":3312.857751572178,\"y\":502.58220111114224},\"desktop\":{\"x\":2971.700036728428,\"y\":480.7287465212985},\"dns\":{\"x\":3200.4643189549906,\"y\":320.469591247861},\"racked\":{\"x\":2645.5845448279656,\"y\":970.7820678889219}},\"sizes\":{\"core-router-1\":36,\"internet\":168,\"phone\":121,\"desktop\":147,\"racked\":137,\"docker-copy-2\":82},\"styles\":{\"internet\":{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"amazon-web-services\"},\"circleColor\":\"#ffffff\",\"circleBorder\":\"#ffffff\"}},\"opnsense-copy-1\":{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"opnsense-v1\"}}},\"internet-copy\":{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"opnsense\"}}},\"docker-copy-2\":{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"docker\"}}},\"docker-copy-1\":{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"authportal\"}}},\"docker-copy\":{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"jotty\"}}},\"opnsense-copy\":{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"portainer\"}}},\"racked\":{\"all\":{\"icon\":{\"library\":\"mdi\",\"name\":\"server-security\"},\"circleColor\":\"#010813\",\"circleBorder\":\"#ffffff\"}}},\"legend\":{\"#475569\":\"you can edit me too\",\"#65758b\":\"you can edit me too\",\"#63748c\":\"you can edit me too\",\"#5e6f87\":\"you can edit me too\",\"#586a84\":\"you can edit me too\",\"#4f627d\":\"you can edit me too\",\"#455873\":\"you can edit me too\",\"#3d506c\":\"you can edit me too\",\"#354964\":\"you can edit me too\",\"#2e415c\":\"you can edit me too\",\"#293c56\":\"you can edit me too\",\"#273a53\":\"you can edit me too\",\"#253750\":\"you can edit me too\",\"#23354d\":\"you can edit me too\",\"#203046\":\"you can edit me too\",\"#1e2d43\":\"you can edit me too\",\"#1a283d\":\"you can edit me too\",\"#172435\":\"you can edit me too\",\"#141f2e\":\"you can edit me too\",\"#111a27\":\"you can edit me too\",\"#0f1824\":\"you can edit me too\",\"#0d1521\":\"you can edit me too\",\"#0c131d\":\"you can edit me too\",\"#0c1d1c\":\"you can edit me too\",\"#0c1c1d\":\"you can edit me too\",\"#0c191d\":\"you can edit me too\",\"#0c141d\":\"you can edit me too\",\"#0c0d1d\":\"you can edit me too\",\"#130c1d\":\"you can edit me too\",\"#1b0c1d\":\"you can edit me too\",\"#1d0c17\":\"you can edit me too\",\"#1d0c10\":\"you can edit me too\",\"#1d0c0c\":\"you can edit me too\",\"#3b1b1b\":\"you can edit me too\",\"#3c1a1a\":\"you can edit me too\",\"#3f1c1c\":\"you can edit me too\",\"#401c1c\":\"you can edit me too\",\"#451c1c\":\"you can edit me too\",\"#461b1b\":\"you can edit me too\",\"#4c1a1a\":\"you can edit me too\",\"#521919\":\"you can edit me too\",\"#571919\":\"you can edit me too\",\"#5d1818\":\"you can edit me too\",\"#631717\":\"you can edit me too\",\"#651515\":\"you can edit me too\",\"#6a1616\":\"you can edit me too\",\"#6f1515\":\"you can edit me too\",\"#711414\":\"you can edit me too\",\"#761414\":\"you can edit me too\",\"#771313\":\"you can edit me too\",\"#7c1313\":\"you can edit me too\",\"#811313\":\"you can edit me too\",\"#821212\":\"you can edit me too\",\"#871212\":\"you can edit me too\",\"#881111\":\"you can edit me too\",\"#8d1111\":\"you can edit me too\",\"#8e1010\":\"you can edit me too\",\"#8f0f0f\":\"you can edit me too\",\"#900e0e\":\"you can edit me too\",\"#8e0b0b\":\"you can edit me too\",\"#8c0d0d\":\"you can edit me too\",\"#880c0c\":\"you can edit me too\",\"#830c0c\":\"you can edit me too\",\"#7e0c0c\":\"you can edit me too\",\"#790c0c\":\"you can edit me too\",\"#730c0c\":\"you can edit me too\",\"#6f0b0b\":\"you can edit me too\",\"#0b6f64\":\"you can edit me too\",\"#0b6f5f\":\"you can edit me too\",\"#0b6f56\":\"you can edit me too\",\"#0b6f49\":\"you can edit me too\",\"#0b6f31\":\"you can edit me too\",\"#0b6f1f\":\"you can edit me too\",\"#0b6f0d\":\"you can edit me too\",\"#176f0b\":\"you can edit me too\",\"#266f0b\":\"you can edit me too\",\"#296f0b\":\"you can edit me too\",\"#2e6f0b\":\"you can edit me too\",\"#1a2d10\":\"you can edit me too\",\"#1c3111\":\"you can edit me too\",\"#213814\":\"you can edit me too\",\"#233c15\":\"you can edit me too\",\"#254017\":\"you can edit me too\",\"#294918\":\"you can edit me too\",\"#2b4d1a\":\"you can edit me too\",\"#2d511a\":\"you can edit me too\",\"#315a1b\":\"you can edit me too\",\"#35631c\":\"you can edit me too\",\"#37681d\":\"you can edit me too\",\"#3b721d\":\"you can edit me too\",\"#3f7b1e\":\"you can edit me too\",\"#42851e\":\"you can edit me too\",\"#46901d\":\"you can edit me too\",\"#499a1d\":\"you can edit me too\",\"#4b9f1d\":\"you can edit me too\",\"#4ca61c\":\"you can edit me too\",\"#50b01c\":\"you can edit me too\",\"#51b71a\":\"you can edit me too\",\"#50b918\":\"you can edit me too\",\"#51c115\":\"you can edit me too\",\"#53c615\":\"you can edit me too\",\"#53c814\":\"you can edit me too\",\"#52c913\":\"you can edit me too\",\"#54d011\":\"you can edit me too\",\"#53d110\":\"you can edit me too\",\"#55d510\":\"you can edit me too\",\"#55d70f\":\"you can edit me too\",\"#54d80e\":\"you can edit me too\",\"#54da0b\":\"you can edit me too\",\"#56df0c\":\"you can edit me too\",\"#53db0a\":\"you can edit me too\",\"#55e00b\":\"you can edit me too\",\"#55e109\":\"you can edit me too\",\"#55e208\":\"ISP LINE\",\"#4c00ff\":\"MY Guest NETWORK\",\"#80ff00\":\"you can edit me too\",\"#3b4234\":\"you can edit me too\",\"#3a3442\":\"you can edit me too\",\"#3b3442\":\"you can edit me too\",\"#3c3442\":\"you can edit me too\",\"#3d3442\":\"you can edit me too\",\"#3e3442\":\"you can edit me too\",\"#3f3442\":\"you can edit me too\",\"#403442\":\"you can edit me too\",\"#413442\":\"you can edit me too\",\"#653d66\":\"you can edit me too\",\"#683f69\":\"you can edit me too\",\"#6c416c\":\"you can edit me too\",\"#6f4370\":\"you can edit me too\",\"#704270\":\"you can edit me too\",\"#734474\":\"you can edit me too\",\"#784479\":\"you can edit me too\",\"#7d447e\":\"you can edit me too\",\"#7e437f\":\"you can edit me too\",\"#834384\":\"you can edit me too\",\"#844285\":\"you can edit me too\",\"#89418b\":\"you can edit me too\",\"#8e428f\":\"you can edit me too\",\"#904091\":\"you can edit me too\",\"#923e93\":\"you can edit me too\",\"#973e98\":\"you can edit me too\",\"#943c96\":\"you can edit me too\",\"#993c9a\":\"you can edit me too\",\"#963a98\":\"you can edit me too\",\"#973899\":\"you can edit me too\",\"#99369b\":\"you can edit me too\",\"#9a359c\":\"you can edit me too\",\"#9b349d\":\"you can edit me too\",\"#9d329f\":\"you can edit me too\",\"#9e31a0\":\"you can edit me too\",\"#a02fa2\":\"you can edit me too\",\"#9d2d9f\":\"you can edit me too\",\"#9f2ba1\":\"you can edit me too\",\"#a129a3\":\"you can edit me too\",\"#a327a5\":\"you can edit me too\",\"#a525a7\":\"you can edit me too\",\"#a723a9\":\"you can edit me too\",\"#a921ab\":\"you can edit me too\",\"#ab1fad\":\"you can edit me too\",\"#ad1daf\":\"you can edit me too\",\"#ae1cb0\":\"you can edit me too\",\"#b019b3\":\"you can edit me too\",\"#b118b4\":\"you can edit me too\",\"#b316b6\":\"you can edit me too\",\"#b816bb\":\"you can edit me too\",\"#b514b8\":\"you can edit me too\",\"#ba14bd\":\"you can edit me too\",\"#b712ba\":\"you can edit me too\",\"#bb13be\":\"you can edit me too\",\"#b811bb\":\"you can edit me too\",\"#be10c1\":\"you can edit me too\",\"#bb0ebe\":\"you can edit me too\",\"#bd0cc0\":\"you can edit me too\",\"#be0bc1\":\"you can edit me too\",\"#c108c4\":\"you can edit me too\",\"#be06c1\":\"you can edit me too\",\"#c103c4\":\"you can edit me too\",\"#c301c6\":\"you can edit me too\",\"#c400c7\":\"you can edit me too\",\"#c900cc\":\"you can edit me too\",\"#ce00d1\":\"you can edit me too\",\"#d300d6\":\"you can edit me too\",\"#d800db\":\"you can edit me too\",\"#dd00e0\":\"you can edit me too\",\"#e200e6\":\"you can edit me too\",\"#ec00f0\":\"you can edit me too\",\"#f100f5\":\"you can edit me too\",\"#f600fa\":\"you can edit me too\",\"#fb00ff\":\"you can edit me too\",\"#ff00d0\":\"iPhone (always guest iPhone)\",\"#f97316\":\"you can edit me too\"},\"rects\":{\"list\":[{\"id\":\"rect-1765238219615\",\"x\":2680.053955078125,\"y\":251.44879150390625,\"width\":814.10400390625,\"height\":389.26678466796875,\"color\":\"#ec0999\",\"style\":\"filled\",\"lineStyle\":\"solid\",\"notes\":[]}]},\"texts\":{\"list\":[{\"id\":\"text-1765238422602\",\"x\":2466.35986328125,\"y\":741.6801147460938,\"content\":\"Double click on desktop\\nor long press on mobile\\nto enter rack canvas view\",\"fontSize\":40,\"color\":\"#e2e8f0\",\"fontWeight\":\"normal\",\"fontStyle\":\"normal\",\"textAlign\":\"start\",\"textDecoration\":\"none\",\"bgColor\":\"#000000\",\"bgEnabled\":false,\"opacity\":1}]},\"pageState\":{\"title\":\"The One File\",\"background\":\"\",\"topbarBg\":\"rgba(9, 12, 20, 0.9)\",\"topbarBorder\":\"#1f2533\",\"panel\":\"#2f0e0e\",\"panelAlt\":\"#10141b\",\"accent\":\"#a75252\",\"sidebarBg\":\"#10141b\",\"btnBg\":\"#0b0e13\",\"btnText\":\"#e2e8f0\",\"tagFill\":\"#1e293b\",\"tagText\":\"#e2e8f0\",\"tagBorder\":\"#475569\",\"inputBg\":\"#0b0e13\",\"inputText\":\"#e2e8f0\",\"inputBorder\":\"#1f2937\",\"inputFont\":\"Inter, system-ui, sans-serif\",\"inputFontSize\":14,\"toolbarBg\":\"#441215\",\"toolbarBorder\":\"#1f2937\",\"toolbarText\":\"#94a3b8\",\"toolbarBtnBg\":\"#0b0e13\",\"toolbarBtnText\":\"#e2e8f0\",\"minimapDots\":\"#94a3b8\",\"canvasHintEnabled\":true,\"canvasHintText\":\"\",\"canvasHintBg\":\"#0f172a\",\"canvasHintColor\":\"#94a3b8\",\"danger\":\"#f56565\",\"textMain\":\"#e2e8f0\",\"textSoft\":\"#94a3b8\",\"topbarHeight\":112,\"sidebarWidth\":350,\"mobileFooterHeight\":40,\"sidebarCollapsed\":false,\"nodeFill\":\"#1e293b\",\"nodeStroke\":\"#475569\",\"nodeTitle\":\"#e2e8f0\",\"nodeSub\":\"#94a3b8\",\"nodeTitleSize\":18,\"nodeSubSize\":13,\"nodeFont\":\"Inter, system-ui, sans-serif\",\"defaultEdge\":\"#475569\",\"selectionHandle\":\"#f59e0b\",\"selectionHandleSize\":8,\"groupIndicator\":\"#4fd1c5\",\"canvasGradientTop\":\"#1e2532\",\"canvasGradientBottom\":\"#050608\",\"canvasBorder\":\"#475569\",\"canvasGrid\":\"#475569\",\"canvasGridSize\":50,\"canvasGridEnabled\":true,\"rackFrameFill\":\"#0f172a\",\"rackFrameStroke\":\"#4fd1c5\",\"rackLineColor\":\"#475569\",\"rackTextColor\":\"#4fd1c5\",\"rackGridEnabled\":true,\"viewOnly\":false,\"defaultEdgeRouting\":\"curved\",\"animateConnections\":false,\"animationStyle\":\"arrows\",\"animationDirection\":\"all\",\"animationSpeed\":1.5}}],\"currentTabIndex\":1,\"encryptedSections\":{},\"auditLog\":[{\"timestamp\":1766459518367,\"type\":\"export\",\"description\":\"Exported Markdown: the-one-file.md\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766459511746,\"type\":\"export\",\"description\":\"Exported JSON: the-one-file.json\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766459504374,\"type\":\"save\",\"description\":\"File saved: the-one-file.html\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766459500911,\"type\":\"save\",\"description\":\"File saved: the-one-file.html\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766459497380,\"type\":\"tab\",\"description\":\"Switched to tab: Homelab 2\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766459491436,\"type\":\"save\",\"description\":\"File saved: the-one-file-corporate.html\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459483682,\"type\":\"save\",\"description\":\"File saved: the-one-file-corporate.html\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766459477676,\"type\":\"save\",\"description\":\"File saved: the-one-file-corporate.html\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766457578277,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766457564726,\"type\":\"tab\",\"description\":\"Switched to tab: Corporate Site B\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766457564253,\"type\":\"tab\",\"description\":\"Switched to tab: Homelab 2\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766457560309,\"type\":\"import\",\"description\":\"Imported JSON: the-one-file-corporate.json (107 nodes, 65 connections)\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455847368,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455844534,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455844054,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455843762,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455843560,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455843371,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455843162,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455842852,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455842747,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455842601,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455842449,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455842348,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455842098,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455841678,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455841236,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455841053,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455840901,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455840650,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455839427,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455839234,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455839061,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455837247,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455837081,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455836893,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455836377,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455836198,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455835455,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455834630,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455831880,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455831676,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455831451,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455830817,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455830687,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455830176,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455830048,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455829944,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455829816,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455378795,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455378693,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455378459,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455378316,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455378180,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455378069,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455377956,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455377677,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455377558,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455377448,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455377318,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766455377209,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453090534,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453090317,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453090213,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453090112,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453090009,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453089903,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453088895,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453088793,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453088689,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453088584,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453088480,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453088250,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453087236,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453086725,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453086485,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453086373,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453086142,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453086043,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453072857,\"type\":\"text\",\"description\":\"paste text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453070975,\"type\":\"text\",\"description\":\"paste text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453054439,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453053127,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453052450,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453052106,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453051948,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453051806,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453051334,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453050207,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453042725,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453042179,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453041797,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453041570,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453039703,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453039291,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453039168,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453039065,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453038481,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453038365,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453038237,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453038105,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453038001,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453037850,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453037745,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453037495,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453037378,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453037182,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453037078,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453036972,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453036860,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453036147,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453035945,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453035825,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453035720,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453035443,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453035337,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453035233,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453035127,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453035026,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453034917,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453031063,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453030955,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453030833,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453030732,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453030225,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453030104,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453029968,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453029796,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453029474,\"type\":\"edit\",\"description\":\"rotate text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766453024797,\"type\":\"text\",\"description\":\"paste text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766451118553,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766450929324,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766450817210,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766450257424,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766450255024,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766450254395,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766450253241,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766450251598,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766450250392,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766450248756,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766450244072,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766450242166,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766450240998,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766450236492,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766450233672,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766450232384,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766450231012,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766450230254,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766450229302,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766450228132,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446610211,\"type\":\"text\",\"description\":\"paste text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446604849,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446604702,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446604550,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446604404,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446604305,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446604204,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446604099,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446603952,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446603849,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446603702,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446603599,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446603452,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446603348,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446603202,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446603099,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446602953,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446602850,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446602702,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446602600,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446602453,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446602349,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446602204,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446602101,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446602000,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446601848,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446601702,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446601601,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446601452,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446601301,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446601154,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446601049,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446600948,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446600802,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446600550,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446598595,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446598461,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446598171,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446598017,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446597219,\"type\":\"text\",\"description\":\"edit text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766446595278,\"type\":\"text\",\"description\":\"add text\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445633355,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445632515,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445631735,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445630757,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445627846,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445625085,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445618645,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445617784,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445608998,\"type\":\"edit\",\"description\":\"edit zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445608720,\"type\":\"edit\",\"description\":\"edit zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445608540,\"type\":\"edit\",\"description\":\"edit zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445608376,\"type\":\"edit\",\"description\":\"edit zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445608204,\"type\":\"edit\",\"description\":\"edit zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445608038,\"type\":\"edit\",\"description\":\"edit zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445607852,\"type\":\"edit\",\"description\":\"edit zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445607678,\"type\":\"edit\",\"description\":\"edit zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445607506,\"type\":\"edit\",\"description\":\"edit zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445607319,\"type\":\"edit\",\"description\":\"edit zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445607154,\"type\":\"edit\",\"description\":\"edit zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445604410,\"type\":\"edit\",\"description\":\"edit zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445604244,\"type\":\"edit\",\"description\":\"edit zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445604066,\"type\":\"edit\",\"description\":\"edit zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445603900,\"type\":\"edit\",\"description\":\"edit zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445603743,\"type\":\"edit\",\"description\":\"edit zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445603563,\"type\":\"edit\",\"description\":\"edit zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445603406,\"type\":\"edit\",\"description\":\"edit zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445603226,\"type\":\"edit\",\"description\":\"edit zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445603052,\"type\":\"edit\",\"description\":\"edit zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445602880,\"type\":\"edit\",\"description\":\"edit zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445602641,\"type\":\"edit\",\"description\":\"edit zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445576567,\"type\":\"edit\",\"description\":\"edit edge animation speed\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445570290,\"type\":\"edit\",\"description\":\"edit edge animation speed\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445567192,\"type\":\"edit\",\"description\":\"edit edge animate\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445566766,\"type\":\"edit\",\"description\":\"edit edge animate\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445565520,\"type\":\"edit\",\"description\":\"edit edge animate\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445398115,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445390895,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445385694,\"type\":\"edit\",\"description\":\"toggle fov animation\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445383241,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445382911,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445381695,\"type\":\"edit\",\"description\":\"edit node name\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445375383,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445374665,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445373273,\"type\":\"node\",\"description\":\"paste node\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766445372205,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438157980,\"type\":\"edit\",\"description\":\"edit edge animate\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438157430,\"type\":\"edit\",\"description\":\"edit edge animation speed\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438152691,\"type\":\"edit\",\"description\":\"edit edge animate\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438151948,\"type\":\"edit\",\"description\":\"edit edge animate\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438151286,\"type\":\"edit\",\"description\":\"edit edge animate\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438146174,\"type\":\"edit\",\"description\":\"edit edge animate\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438145649,\"type\":\"edit\",\"description\":\"edit edge animate\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438144555,\"type\":\"edit\",\"description\":\"edit edge animate\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438143655,\"type\":\"edit\",\"description\":\"edit edge animate\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438142504,\"type\":\"edit\",\"description\":\"edit edge animation speed\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438130077,\"type\":\"edit\",\"description\":\"edit edge animate\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438129561,\"type\":\"edit\",\"description\":\"edit edge animate\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438128772,\"type\":\"edit\",\"description\":\"edit edge animate\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438128398,\"type\":\"edit\",\"description\":\"edit edge animate\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438122820,\"type\":\"edit\",\"description\":\"edit edge animate\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438122062,\"type\":\"edit\",\"description\":\"edit edge animate\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438119836,\"type\":\"edit\",\"description\":\"edit edge animate\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438119588,\"type\":\"edit\",\"description\":\"edit edge animate\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438095045,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438093965,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438062827,\"type\":\"edit\",\"description\":\"toggle fov animation\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438047679,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438044161,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438041852,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438039668,\"type\":\"edit\",\"description\":\"resize node\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438039562,\"type\":\"edit\",\"description\":\"resize node\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438039421,\"type\":\"edit\",\"description\":\"resize node\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438039260,\"type\":\"edit\",\"description\":\"resize node\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438039150,\"type\":\"edit\",\"description\":\"resize node\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438039039,\"type\":\"edit\",\"description\":\"resize node\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438028508,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438021410,\"type\":\"edit\",\"description\":\"toggle fov\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438019234,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438017562,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766438014356,\"type\":\"node\",\"description\":\"add node\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766437981696,\"type\":\"edit\",\"description\":\"apply routing to all\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766437966551,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766437964879,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766437963627,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766437961813,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766437961193,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766437957989,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766437956467,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766437953437,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766437952239,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766437950807,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766437944990,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766437943699,\"type\":\"node\",\"description\":\"move nodes\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766437935414,\"type\":\"zone\",\"description\":\"draw zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766437919019,\"type\":\"zone\",\"description\":\"delete zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766437917758,\"type\":\"zone\",\"description\":\"draw zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766437913740,\"type\":\"zone\",\"description\":\"draw zone\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766437882832,\"type\":\"import\",\"description\":\"Imported JSON: theonefile-networkening-corporate-demo.json (105 nodes, 65 connections)\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766263279163,\"type\":\"tab\",\"description\":\"Switched to tab: Corporate Site B\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766263270414,\"type\":\"export\",\"description\":\"Exported JSON: the-one-file.json\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766263260682,\"type\":\"save\",\"description\":\"File saved: the-one-file.html\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766263259518,\"type\":\"tab\",\"description\":\"Switched to tab: Homelab 2\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766263249401,\"type\":\"save\",\"description\":\"File saved: the-one-file-corporate.html\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766263246362,\"type\":\"import\",\"description\":\"Imported JSON: theonefile-networkening-corporate-demo.json (105 nodes, 65 connections)\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766190721141,\"type\":\"save\",\"description\":\"File saved: the-one-file-corporate.html\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766190717499,\"type\":\"tab\",\"description\":\"Switched to tab: Corporate Site B\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766190710946,\"type\":\"save\",\"description\":\"File saved: the-one-file.html\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766190705273,\"type\":\"save\",\"description\":\"File saved: the-one-file.html\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766190703463,\"type\":\"tab\",\"description\":\"Switched to tab: Homelab 2\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1766190695709,\"type\":\"save\",\"description\":\"File saved: the-one-file-corporate.html\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1766190688417,\"type\":\"import\",\"description\":\"Imported JSON: theonefile-networkening-corporate-demo.json (105 nodes, 65 connections)\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1765402888416,\"type\":\"save\",\"description\":\"File saved: the-one-file-corporate.html\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1765402884873,\"type\":\"tab\",\"description\":\"Switched to tab: Corporate Site B\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1765402878108,\"type\":\"save\",\"description\":\"File saved: the-one-file.html\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1765402866440,\"type\":\"save\",\"description\":\"File saved: the-one-file.html\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1765402865008,\"type\":\"tab\",\"description\":\"Switched to tab: Homelab 2\",\"details\":{},\"tab\":\"Homelab 2\"},{\"timestamp\":1765402860428,\"type\":\"save\",\"description\":\"File saved: the-one-file-corporate.html\",\"details\":{},\"tab\":\"Corporate Site B\"},{\"timestamp\":1765402858103,\"type\":\"import\",\"description\":\"Imported JSON: theonefile-networkening-corporate-demo.json (105 nodes, 65 connections)\",\"details\":{},\"tab\":\"Corporate Site B\"}],\"savedStyleSets\":[]}\n#\n# The One File - Node List\n# Exported from The One File on 2025-12-23T03:12:05.614Z\nname,ip,role,shape,tags,layer,mac,rackUnit,uHeight,assignedRack,rackCapacity,isRack,locked,groupId,x,y,size,notes,styles\nInternet,0.0.0.0,,square,,physical,,,1,,42,false,false,,2104,268,168,,\"{\"\"all\"\":{\"\"icon\"\":{\"\"library\"\":\"\"selfhst\"\",\"\"name\"\":\"\"amazon-web-services\"\"},\"\"circleColor\"\":\"\"#ffffff\"\",\"\"circleBorder\"\":\"\"#ffffff\"\"}}\"\nOPNSENSE,0.0.0.0,,firewall,,physical,,,1,,42,false,false,,2067,473,50,,\"{\"\"all\"\":{\"\"icon\"\":{\"\"library\"\":\"\"selfhst\"\",\"\"name\"\":\"\"opnsense\"\"}}}\"\nDocker,0.0.0.0,,firewall,,physical,,,1,,42,false,false,,1774,667,50,,\"{\"\"all\"\":{\"\"icon\"\":{\"\"library\"\":\"\"selfhst\"\",\"\"name\"\":\"\"portainer\"\"}}}\"\nDocker2,0.0.0.0,,firewall,,physical,,,1,,42,false,false,,1931,782,50,,\"{\"\"all\"\":{\"\"icon\"\":{\"\"library\"\":\"\"selfhst\"\",\"\"name\"\":\"\"jotty\"\"}}}\"\nDocker3,0.0.0.0,,firewall,,physical,,,1,,42,false,false,,2158,768,50,,\"{\"\"all\"\":{\"\"icon\"\":{\"\"library\"\":\"\"selfhst\"\",\"\"name\"\":\"\"authportal\"\"}}}\"\nDocker 4,0.0.0.0,,firewall,[object Object];[object Object];[object Object],physical,,,1,,42,false,false,,2342,632,82,,\"{\"\"all\"\":{\"\"icon\"\":{\"\"library\"\":\"\"selfhst\"\",\"\"name\"\":\"\"docker\"\"}}}\"\nOPNSENSE GUEST,0.0.0.0,,firewall,,physical,,,1,,42,false,false,,2758,308,50,,\"{\"\"all\"\":{\"\"icon\"\":{\"\"library\"\":\"\"selfhst\"\",\"\"name\"\":\"\"opnsense-v1\"\"}}}\"\nPhone,0.0.0.0,,phone,,physical,,,1,,42,false,false,,3313,503,121,,\nDesktop,0.0.0.0,,pc,,physical,,,1,,42,false,false,,2972,481,147,,\nDNS,0.0.0.0,,cloud,,physical,,,1,,42,false,false,,3200,320,50,,\nRacked,,Rack,server,,physical,,,1,,42,true,false,,2646,971,137,,\"{\"\"all\"\":{\"\"icon\"\":{\"\"library\"\":\"\"mdi\"\",\"\"name\"\":\"\"server-security\"\"},\"\"circleColor\"\":\"\"#010813\"\",\"\"circleBorder\"\":\"\"#ffffff\"\"}}\"\n"
  },
  {
    "path": "demos/index.html",
    "content": "<!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\">\n  <title>The One File : Demo Browser</title>\n  <meta name=\"description\" content=\"Interactive demos for The One File. Map networks, smart homes, sports plays, mind maps, infrastructure or anything with nodes and connections.\">\n  <meta name=\"theme-color\" content=\"#0a0a0a\">\n  <meta property=\"og:title\" content=\"The One File : Demo Browser\">\n  <meta property=\"og:description\" content=\"Interactive demos for The One File. Map networks, smart homes, sports plays, mind maps, infrastructure or anything with nodes and connections.\">\n  <meta property=\"og:type\" content=\"website\">\n  <meta property=\"og:url\" content=\"https://therecanonlybe.one/demos/\">\n  <meta property=\"og:image\" content=\"https://raw.githubusercontent.com/gelatinescreams/The-One-File/main/assets/theonefile.jpg\">\n  <meta property=\"og:site_name\" content=\"The One File\">\n  <meta name=\"twitter:card\" content=\"summary_large_image\">\n  <meta name=\"twitter:title\" content=\"The One File : Demo Browser\">\n  <meta name=\"twitter:description\" content=\"Interactive demos for The One File. Map networks, smart homes, sports plays, mind maps, infrastructure or anything with nodes and connections.\">\n  <meta name=\"twitter:image\" content=\"https://raw.githubusercontent.com/gelatinescreams/The-One-File/main/assets/theonefile.jpg\">\n  <style>\n    * { margin: 0; padding: 0; box-sizing: border-box; }\n    body { font-family: system-ui, -apple-system, sans-serif; background: #0a0a0a; color: #e5e5e5; min-height: 100vh; display: flex; align-items: center; justify-content: center; }\n    .selector { text-align: center; max-width: 420px; width: 100%; padding: 20px; }\n    .selector h1 { font-size: 24px; font-weight: 700; margin-bottom: 8px; }\n    .selector p { color: #aaa; font-size: 14px; margin-bottom: 32px; }\n    .form-row { display: flex; align-items: center; gap: 12px; margin-bottom: 16px; justify-content: center; }\n    .form-row label { color: #aaa; font-size: 13px; min-width: 60px; text-align: right; }\n    .form-row select {\n      padding: 10px 14px;\n      background: #0f3460;\n      color: #eee;\n      border: 1px solid #e5e5e5;\n      border-radius: 6px;\n      font-size: 14px;\n      cursor: pointer;\n      min-width: 180px;\n    }\n    .form-row select:hover { background: #1a4a7a; }\n    .load-btn {\n      margin-top: 24px;\n      padding: 14px 40px;\n      background: #e94560;\n      color: #fff;\n      border: none;\n      border-radius: 8px;\n      font-size: 16px;\n      font-weight: 600;\n      cursor: pointer;\n    }\n    .load-btn:hover { background: #d13550; }\n    .load-btn:disabled { opacity: 0.5; cursor: not-allowed; }\n    .github-btn {\n      display: inline-block;\n      margin-top: 12px;\n      padding: 10px 24px;\n      background: transparent;\n      color: #aaa;\n      border: 1px solid #444;\n      border-radius: 8px;\n      font-size: 14px;\n      text-decoration: none;\n      transition: border-color 0.2s, color 0.2s;\n    }\n    .github-btn:hover { border-color: #e5e5e5; color: #e5e5e5; }\n    .loading { display: none; align-items: center; justify-content: center; gap: 12px; margin-top: 24px; color: #e94560; }\n    .loading.active { display: flex; }\n    .spinner {\n      width: 28px; height: 28px;\n      border: 3px solid #1a1a2e;\n      border-top-color: #e94560;\n      border-radius: 50%;\n      animation: spin 1s linear infinite;\n    }\n    @keyframes spin { to { transform: rotate(360deg); } }\n    .error { color: #e94560; margin-top: 16px; display: none; }\n    .error.active { display: block; }\n  </style>\n</head>\n<body>\n  <div class=\"selector\">\n    <h1>The One File : Demo Browser</h1>\n    <p>Select a version and demo to load</p>\n    <div class=\"form-row\">\n      <label>Version:</label>\n      <select id=\"version-select\">\n        <option value=\"networkening\">Networkening</option>\n        <option value=\"core\">Core</option>\n      </select>\n    </div>\n    <div class=\"form-row\">\n      <label>Demo:</label>\n      <select id=\"demo-select\">\n        <option value=\"corporate\">Corporate</option>\n        <option value=\"homelab\">Homelab</option>\n        <option value=\"sports\">Sports</option>\n      </select>\n    </div>\n    <button class=\"load-btn\" id=\"load-btn\" onclick=\"loadDemo()\">Load Demo</button>\n    <div class=\"loading\" id=\"loading\">\n      <div class=\"spinner\"></div>\n      Loading demo...\n    </div>\n    <div class=\"error\" id=\"error\"></div>\n    <div style=\"margin-top: 32px;\">\n      <a class=\"github-btn\" href=\"https://github.com/gelatinescreams/The-One-File\" target=\"_blank\" rel=\"noopener\">View on GitHub</a>\n    </div>\n  </div>\n\n  <script>\n    var DEMOS = {\n      core: {\n        corporate: 'json-exports/the-one-file-corporate-demo.json',\n        homelab: 'json-exports/the-one-file-homelab-demo.json'\n      },\n      networkening: {\n        corporate: 'json-exports/theonefile-networkening-corporate-demo.json',\n        homelab: 'json-exports/theonefile-networkening-homelab-demo.json',\n        sports: 'json-exports/theonefile-networkening-sports-demo.json'\n      }\n    };\n\n    var BASE_HTML = {\n      core: '../the-one-file.html',\n      networkening: '../theonefile-networkening.html'\n    };\n\n    var DEMO_LABELS = {\n      core: { corporate: 'Core / Corporate', homelab: 'Core / Homelab' },\n      networkening: { corporate: 'Networkening / Corporate', homelab: 'Networkening / Homelab', sports: 'Networkening / Sports' }\n    };\n\n    var DEMO_NAMES = {\n      corporate: 'Corporate',\n      homelab: 'Homelab',\n      sports: 'Sports'\n    };\n\n    var PAGE_BASE_URL = window.location.href.replace(/[?#].*$/, '').replace(/[^/]*$/, '');\n    var PAGE_ORIGIN = window.location.origin + window.location.pathname;\n\n    var versionSelect = document.getElementById('version-select');\n    var demoSelect = document.getElementById('demo-select');\n    var params = new URLSearchParams(window.location.search);\n\n    if (params.get('version') && DEMOS[params.get('version')]) {\n      versionSelect.value = params.get('version');\n    }\n    if (params.get('demo') && DEMOS[versionSelect.value][params.get('demo')]) {\n      demoSelect.value = params.get('demo');\n    }\n\n    if (params.get('autoload') === '1') {\n      loadDemo();\n    }\n\n    function showError(msg) {\n      var el = document.getElementById('error');\n      el.textContent = msg;\n      el.classList.add('active');\n    }\n\n    async function loadDemo() {\n      var version = versionSelect.value;\n      var demo = demoSelect.value;\n\n      if (!DEMOS[version] || !DEMOS[version][demo]) {\n        showError('Invalid demo selection');\n        return;\n      }\n\n      document.getElementById('load-btn').disabled = true;\n      document.getElementById('loading').classList.add('active');\n      document.getElementById('error').classList.remove('active');\n\n      try {\n        var responses = await Promise.all([\n          fetch(BASE_HTML[version]),\n          fetch(DEMOS[version][demo])\n        ]);\n\n        if (!responses[0].ok || !responses[1].ok) throw new Error('Failed to load files');\n\n        var html = await responses[0].text();\n        var demoData = await responses[1].json();\n\n        var versionDemos = DEMOS[version];\n        var versionLabels = {};\n        for (var k in versionDemos) {\n          versionLabels[k] = DEMO_NAMES[k] || k;\n        }\n\n        var demoLabel = (DEMO_LABELS[version] && DEMO_LABELS[version][demo]) || (version + ' / ' + demo);\n\n        var selectOptions = '';\n        for (var demoKey in versionDemos) {\n          var sel = demoKey === demo ? ' selected' : '';\n          selectOptions += '<option value=\"' + demoKey + '\"' + sel + '>' + (versionLabels[demoKey] || demoKey) + '</option>';\n        }\n\n        var absoluteDemoUrls = {};\n        for (var dk in versionDemos) {\n          absoluteDemoUrls[dk] = PAGE_BASE_URL + versionDemos[dk];\n        }\n\n        var selectorUrl = PAGE_ORIGIN;\n\n        var toolbarHtml = '<div id=\"demo-toolbar\" style=\"' +\n          'position:fixed;top:0;left:0;right:0;height:44px;' +\n          'background:linear-gradient(135deg,#3e3e59 0%,#14151c 100%);' +\n          'border-bottom:2px solid #0f3460;' +\n          'display:flex;align-items:center;padding:0 20px;gap:12px;' +\n          'z-index:999999;box-shadow:0 2px 10px rgba(0,0,0,0.5);font-family:system-ui,sans-serif;' +\n          '\">' +\n          '<span id=\"demo-version-label\" style=\"color:#e5e5e5;font-size:14px;font-weight:700;white-space:nowrap\">' + (version === 'core' ? 'Core' : 'Networkening') + '</span>' +\n          '<select id=\"demo-switcher\" style=\"' +\n          'padding:5px 10px;background:#0f3460;color:#eee;border:1px solid #e94560;' +\n          'border-radius:5px;font-size:13px;cursor:pointer;outline:none;' +\n          '\">' + selectOptions + '</select>' +\n          '<span id=\"demo-loading-indicator\" style=\"color:#e94560;font-size:12px;display:none\">Loading...</span>' +\n          '<a href=\"' + selectorUrl + '\" style=\"' +\n          'color:#888;font-size:12px;text-decoration:none;white-space:nowrap;' +\n          '\">All Demos</a>' +\n          '<a href=\"https://github.com/gelatinescreams/The-One-File\" target=\"_blank\" rel=\"noopener\" style=\"' +\n          'color:#aaa;font-size:13px;text-decoration:none;padding:6px 14px;' +\n          'border:1px solid #444;border-radius:5px;white-space:nowrap;margin-left:auto;' +\n          '\">GitHub</a>' +\n          '</div>';\n\n        var toolbarPadding = '<st' + 'yle>body{padding-top:44px !important}</st' + 'yle>';\n\n        var autosaveBlocker = '<scr' + 'ipt>' +\n          '(function(){' +\n          'window.DEMO_MODE=true;' +\n          'var blocked=[\"topology\",\"autosave\",\"savedState\",\"nodeData\",\"edgeData\",\"canvasState\",\"lastState\",\"PAGE_STATE\",\"theonefile\"];' +\n          'function isBlocked(k){if(!k)return false;var l=k.toLowerCase();for(var i=0;i<blocked.length;i++){if(l.indexOf(blocked[i].toLowerCase())!==-1)return true;}return false;}' +\n          'var origGet=localStorage.getItem.bind(localStorage);' +\n          'var origSet=localStorage.setItem.bind(localStorage);' +\n          'var origRem=localStorage.removeItem.bind(localStorage);' +\n          'localStorage.getItem=function(k){if(isBlocked(k))return null;return origGet(k);};' +\n          'localStorage.setItem=function(k,v){if(isBlocked(k))return;return origSet(k,v);};' +\n          'localStorage.removeItem=function(k){if(isBlocked(k))return;return origRem(k);};' +\n          'var origOpen=indexedDB.open.bind(indexedDB);' +\n          'indexedDB.open=function(name){' +\n          'if(name&&name.toLowerCase().indexOf(\"theonefile\")!==-1){' +\n          'var fr={result:null,error:null,onsuccess:null,onerror:null,onupgradeneeded:null,onblocked:null,readyState:\"done\",transaction:null,source:null};' +\n          'setTimeout(function(){if(fr.onerror)fr.onerror(new Event(\"error\"));},0);return fr;}' +\n          'return origOpen.apply(indexedDB,arguments);};' +\n          '})();' +\n          '</scr' + 'ipt>';\n\n        var switcherScript = '<scr' + 'ipt>' +\n          'window.__DEMO_URLS=' + JSON.stringify(absoluteDemoUrls) + ';' +\n          '(function(){' +\n          'function applyDemoData(d){' +\n          'NODE_DATA=d.nodeData||{};' +\n          'EDGE_DATA=d.edgeData||{list:[]};' +\n          'EDGE_LEGEND=d.edgeLegend||{};' +\n          'if(typeof RECT_DATA!==\"undefined\")RECT_DATA=d.rectData||{list:[]};' +\n          'if(typeof TEXT_DATA!==\"undefined\")TEXT_DATA=d.textData||{list:[]};' +\n          'savedPositions=d.nodePositions||{};' +\n          'savedSizes=d.nodeSizes||{};' +\n          'savedStyles=d.nodeStyles||{};' +\n          'if(d.page&&typeof PAGE_STATE!==\"undefined\"&&typeof DEFAULT_PAGE_STATE!==\"undefined\"){' +\n          'PAGE_STATE=Object.assign({},DEFAULT_PAGE_STATE,d.page);' +\n          'if(typeof wieldThePower===\"function\")wieldThePower();}' +\n          'if(d.canvas&&typeof canvasState!==\"undefined\"){' +\n          'canvasState.zoom=d.canvas.zoom||1;canvasState.panX=d.canvas.panX||0;canvasState.panY=d.canvas.panY||0;}' +\n          'if(d.documentTabs&&typeof documentTabs!==\"undefined\"){documentTabs=d.documentTabs;currentTabIndex=d.currentTabIndex||0;}' +\n          'if(d.page&&d.page.title){document.title=d.page.title;' +\n          'var te=document.querySelector(\".editable-page-title\");if(te)te.textContent=d.page.title;}' +\n          'var lm={\"physical\":\"layer1\",\"logical\":\"layer2\",\"security\":\"layer3\",\"application\":\"layer4\"};' +\n          'Object.values(NODE_DATA).forEach(function(n){if(!n.tags)n.tags=[];if(!n.notes)n.notes=[];if(!n.layer||lm[n.layer])n.layer=lm[n.layer]||\"layer1\";});' +\n          'forgeTheTopology();' +\n          'if(typeof updateViewBox===\"function\")updateViewBox();' +\n          '}' +\n          'function waitForReady(cb){' +\n          'if(typeof NODE_DATA!==\"undefined\"&&typeof forgeTheTopology===\"function\"){cb();return;}' +\n          'setTimeout(function(){waitForReady(cb);},50);' +\n          '}' +\n          'waitForReady(function(){' +\n          'var sel=document.getElementById(\"demo-switcher\");' +\n          'if(!sel)return;' +\n          'sel.addEventListener(\"change\",function(){' +\n          'var key=sel.value;var url=window.__DEMO_URLS[key];' +\n          'if(!url)return;' +\n          'var indicator=document.getElementById(\"demo-loading-indicator\");' +\n          'if(indicator)indicator.style.display=\"inline\";' +\n          'sel.disabled=true;' +\n          'fetch(url).then(function(r){' +\n          'if(!r.ok)throw new Error(\"Failed to load\");return r.json();' +\n          '}).then(function(d){' +\n          'applyDemoData(d);' +\n          'if(indicator)indicator.style.display=\"none\";' +\n          'sel.disabled=false;' +\n          '}).catch(function(e){' +\n          'console.error(\"Demo switch error:\",e);' +\n          'if(indicator){indicator.style.display=\"inline\";indicator.textContent=\"Error loading\";}' +\n          'sel.disabled=false;' +\n          'setTimeout(function(){if(indicator){indicator.style.display=\"none\";indicator.textContent=\"Loading...\";}},3000);' +\n          '});' +\n          '});' +\n          '});' +\n          '})();' +\n          '</scr' + 'ipt>';\n\n        var dataLoaderScript = '<scr' + 'ipt>' +\n          'window.__DEMO_DATA=' + JSON.stringify(demoData) + ';' +\n          '(function(){' +\n          'function applyData(){' +\n          'if(typeof NODE_DATA===\"undefined\"||typeof forgeTheTopology===\"undefined\"){setTimeout(applyData,50);return;}' +\n          'var d=window.__DEMO_DATA;' +\n          'NODE_DATA=d.nodeData||{};' +\n          'EDGE_DATA=d.edgeData||{list:[]};' +\n          'EDGE_LEGEND=d.edgeLegend||{};' +\n          'if(typeof RECT_DATA!==\"undefined\")RECT_DATA=d.rectData||{list:[]};' +\n          'if(typeof TEXT_DATA!==\"undefined\")TEXT_DATA=d.textData||{list:[]};' +\n          'savedPositions=d.nodePositions||{};' +\n          'savedSizes=d.nodeSizes||{};' +\n          'savedStyles=d.nodeStyles||{};' +\n          'if(d.page&&typeof PAGE_STATE!==\"undefined\"&&typeof DEFAULT_PAGE_STATE!==\"undefined\"){' +\n          'PAGE_STATE=Object.assign({},DEFAULT_PAGE_STATE,d.page);' +\n          'if(typeof wieldThePower===\"function\")wieldThePower();}' +\n          'if(d.canvas&&typeof canvasState!==\"undefined\"){' +\n          'canvasState.zoom=d.canvas.zoom||1;canvasState.panX=d.canvas.panX||0;canvasState.panY=d.canvas.panY||0;}' +\n          'if(d.documentTabs&&typeof documentTabs!==\"undefined\"){documentTabs=d.documentTabs;currentTabIndex=d.currentTabIndex||0;}' +\n          'if(d.page&&d.page.title){document.title=d.page.title;' +\n          'var te=document.querySelector(\".editable-page-title\");if(te)te.textContent=d.page.title;}' +\n          'var lm={\"physical\":\"layer1\",\"logical\":\"layer2\",\"security\":\"layer3\",\"application\":\"layer4\"};' +\n          'Object.values(NODE_DATA).forEach(function(n){if(!n.tags)n.tags=[];if(!n.notes)n.notes=[];if(!n.layer||lm[n.layer])n.layer=lm[n.layer]||\"layer1\";});' +\n          'forgeTheTopology();' +\n          'if(typeof updateViewBox===\"function\")updateViewBox();' +\n          'if(typeof showWelcomeModal===\"function\")showWelcomeModal();' +\n          'delete window.__DEMO_DATA;' +\n          '}' +\n          'applyData();' +\n          '})();' +\n          '</scr' + 'ipt>';\n\n        html = html.replace(/<head[^>]*>/i, '$&' + autosaveBlocker + toolbarPadding);\n        html = html.replace(/<\\/head>/i, switcherScript + dataLoaderScript + '</head>');\n        html = html.replace(/<body[^>]*>/i, '$&' + toolbarHtml);\n\n        var blob = new Blob([html], { type: 'text/html' });\n        var blobUrl = URL.createObjectURL(blob);\n        window.location.href = blobUrl;\n\n      } catch (error) {\n        console.error('Error loading demo:', error);\n        document.getElementById('load-btn').disabled = false;\n        document.getElementById('loading').classList.remove('active');\n        showError('Failed to load demo. Please try again.');\n      }\n    }\n  </script>\n</body>\n</html>\n"
  },
  {
    "path": "demos/json-exports/the-one-file-corporate-demo.json",
    "content": "{\n  \"nodeData\": {\n    \"core-router-1\": {\n      \"shape\": \"router\",\n      \"name\": \"Core Router 1\",\n      \"ip\": \"10.0.0.1\",\n      \"role\": \"Core Routing\",\n      \"tags\": [\n        \"core\",\n        \"tier-1\",\n        \"redundant\"\n      ],\n      \"notes\": [\n        \"Primary core router\",\n        \"BGP peering enabled\"\n      ],\n      \"mac\": \"00:1A:2B:3C:4D:01\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"2\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"core-router-2\": {\n      \"shape\": \"router\",\n      \"name\": \"Core Router 2\",\n      \"ip\": \"10.0.0.2\",\n      \"role\": \"Core Routing\",\n      \"tags\": [\n        \"core\",\n        \"tier-1\",\n        \"redundant\"\n      ],\n      \"notes\": [\n        \"Secondary core router\",\n        \"HSRP standby\"\n      ],\n      \"mac\": \"00:1A:2B:3C:4D:02\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"2\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null,\n      \"ping\": {\n        \"enabled\": true,\n        \"protocol\": \"custom\",\n        \"customUrl\": \"https://google.com\",\n        \"timeout\": 3000,\n        \"status\": \"online\",\n        \"lastCheck\": \"2025-12-09T00:15:04.343Z\"\n      }\n    },\n    \"fw-external-1\": {\n      \"shape\": \"firewall\",\n      \"name\": \"External FW 1\",\n      \"ip\": \"10.0.1.1\",\n      \"role\": \"Perimeter Security\",\n      \"tags\": [\n        \"security\",\n        \"perimeter\",\n        \"ha-pair\"\n      ],\n      \"notes\": [\n        \"Palo Alto PA-5250\",\n        \"Active node\"\n      ],\n      \"mac\": \"00:1A:2B:3C:4D:10\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"2\",\n      \"layer\": \"security\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"fw-external-2\": {\n      \"shape\": \"firewall\",\n      \"name\": \"External FW 2\",\n      \"ip\": \"10.0.1.2\",\n      \"role\": \"Perimeter Security\",\n      \"tags\": [\n        \"security\",\n        \"perimeter\",\n        \"ha-pair\"\n      ],\n      \"notes\": [\n        \"Palo Alto PA-5250\",\n        \"Passive node\"\n      ],\n      \"mac\": \"00:1A:2B:3C:4D:11\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"2\",\n      \"layer\": \"security\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"fw-internal\": {\n      \"shape\": \"firewall\",\n      \"name\": \"Internal FW\",\n      \"ip\": \"10.0.2.1\",\n      \"role\": \"Internal Segmentation\",\n      \"tags\": [\n        \"security\",\n        \"internal\"\n      ],\n      \"notes\": [\n        \"East-West traffic inspection\"\n      ],\n      \"mac\": \"00:1A:2B:3C:4D:12\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"2\",\n      \"layer\": \"security\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"core-switch-1\": {\n      \"shape\": \"switch\",\n      \"name\": \"Core Switch 1\",\n      \"ip\": \"10.0.10.1\",\n      \"role\": \"Core Switching\",\n      \"tags\": [\n        \"core\",\n        \"layer3\",\n        \"redundant\"\n      ],\n      \"notes\": [\n        \"Cisco Nexus 9000\",\n        \"VPC Domain 1\"\n      ],\n      \"mac\": \"00:1A:2B:3C:4D:20\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"2\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"core-switch-2\": {\n      \"shape\": \"switch\",\n      \"name\": \"Core Switch 2\",\n      \"ip\": \"10.0.10.2\",\n      \"role\": \"Core Switching\",\n      \"tags\": [\n        \"core\",\n        \"layer3\",\n        \"redundant\"\n      ],\n      \"notes\": [\n        \"Cisco Nexus 9000\",\n        \"VPC Domain 1\"\n      ],\n      \"mac\": \"00:1A:2B:3C:4D:21\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"2\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"dc-rack-a1\": {\n      \"shape\": \"server\",\n      \"name\": \"DC Rack A1\",\n      \"ip\": \"10.10.0.0/24\",\n      \"role\": \"Data Center Rack\",\n      \"tags\": [\n        \"datacenter\",\n        \"row-a\",\n        \"production\"\n      ],\n      \"notes\": [\n        \"Row A, Position 1\",\n        \"Primary compute\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": true,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"dc-rack-a2\": {\n      \"shape\": \"server\",\n      \"name\": \"DC Rack A2\",\n      \"ip\": \"10.10.1.0/24\",\n      \"role\": \"Data Center Rack\",\n      \"tags\": [\n        \"datacenter\",\n        \"row-a\",\n        \"production\"\n      ],\n      \"notes\": [\n        \"Row A, Position 2\",\n        \"Primary compute\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": true,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"dc-rack-b1\": {\n      \"shape\": \"server\",\n      \"name\": \"DC Rack B1\",\n      \"ip\": \"10.10.2.0/24\",\n      \"role\": \"Data Center Rack\",\n      \"tags\": [\n        \"datacenter\",\n        \"row-b\",\n        \"storage\"\n      ],\n      \"notes\": [\n        \"Row B, Position 1\",\n        \"Storage systems\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": true,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"dc-rack-b2\": {\n      \"shape\": \"server\",\n      \"name\": \"DC Rack B2\",\n      \"ip\": \"10.10.3.0/24\",\n      \"role\": \"Data Center Rack\",\n      \"tags\": [\n        \"datacenter\",\n        \"row-b\",\n        \"storage\"\n      ],\n      \"notes\": [\n        \"Row B, Position 2\",\n        \"Storage systems\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": true,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"dmz-rack\": {\n      \"shape\": \"server\",\n      \"name\": \"DMZ Rack\",\n      \"ip\": \"172.16.0.0/24\",\n      \"role\": \"DMZ Infrastructure\",\n      \"tags\": [\n        \"dmz\",\n        \"security\",\n        \"public-facing\",\n        {\n          \"type\": \"icon\",\n          \"library\": \"selfhst\",\n          \"name\": \"booklogr\"\n        },\n        {\n          \"type\": \"icon\",\n          \"library\": \"simple\",\n          \"name\": \"gmail\"\n        }\n      ],\n      \"notes\": [\n        \"Isolated DMZ zone\",\n        \"Public-facing services\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"security\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"24\",\n      \"isRack\": true,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"mgmt-rack\": {\n      \"shape\": \"server\",\n      \"name\": \"Management Rack\",\n      \"ip\": \"192.168.100.0/24\",\n      \"role\": \"Management Infrastructure\",\n      \"tags\": [\n        \"management\",\n        \"oob\",\n        \"noc\"\n      ],\n      \"notes\": [\n        \"Out-of-band management\",\n        \"NOC equipment\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"logical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"24\",\n      \"isRack\": true,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"esxi-host-01\": {\n      \"shape\": \"server\",\n      \"name\": \"ESXi Host 01\",\n      \"ip\": \"10.10.0.11\",\n      \"role\": \"Hypervisor\",\n      \"tags\": [\n        \"vmware\",\n        \"compute\",\n        \"cluster-a\"\n      ],\n      \"notes\": [\n        \"Dell PowerEdge R750\",\n        \"512GB RAM\",\n        \"vSphere 8.0\"\n      ],\n      \"mac\": \"00:50:56:AA:01:01\",\n      \"rackUnit\": 38,\n      \"uHeight\": \"2\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a1\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"esxi-host-02\": {\n      \"shape\": \"server\",\n      \"name\": \"ESXi Host 02\",\n      \"ip\": \"10.10.0.12\",\n      \"role\": \"Hypervisor\",\n      \"tags\": [\n        \"vmware\",\n        \"compute\",\n        \"cluster-a\"\n      ],\n      \"notes\": [\n        \"Dell PowerEdge R750\",\n        \"512GB RAM\",\n        \"vSphere 8.0\"\n      ],\n      \"mac\": \"00:50:56:AA:01:02\",\n      \"rackUnit\": 35,\n      \"uHeight\": \"2\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a1\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"esxi-host-03\": {\n      \"shape\": \"server\",\n      \"name\": \"ESXi Host 03\",\n      \"ip\": \"10.10.0.13\",\n      \"role\": \"Hypervisor\",\n      \"tags\": [\n        \"vmware\",\n        \"compute\",\n        \"cluster-a\"\n      ],\n      \"notes\": [\n        \"Dell PowerEdge R750\",\n        \"512GB RAM\",\n        \"vSphere 8.0\"\n      ],\n      \"mac\": \"00:50:56:AA:01:03\",\n      \"rackUnit\": 32,\n      \"uHeight\": \"2\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a1\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"esxi-host-04\": {\n      \"shape\": \"server\",\n      \"name\": \"ESXi Host 04\",\n      \"ip\": \"10.10.0.14\",\n      \"role\": \"Hypervisor\",\n      \"tags\": [\n        \"vmware\",\n        \"compute\",\n        \"cluster-a\"\n      ],\n      \"notes\": [\n        \"Dell PowerEdge R750\",\n        \"512GB RAM\",\n        \"vSphere 8.0\"\n      ],\n      \"mac\": \"00:50:56:AA:01:04\",\n      \"rackUnit\": 29,\n      \"uHeight\": \"2\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a1\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"tor-switch-a1\": {\n      \"shape\": \"switch\",\n      \"name\": \"ToR Switch A1\",\n      \"ip\": \"10.10.0.1\",\n      \"role\": \"Top of Rack\",\n      \"tags\": [\n        \"tor\",\n        \"access\",\n        \"rack-a1\"\n      ],\n      \"notes\": [\n        \"Cisco Nexus 93180YC-FX\",\n        \"48x25G ports\"\n      ],\n      \"mac\": \"00:1A:2B:3C:5D:01\",\n      \"rackUnit\": 42,\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a1\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"esxi-host-05\": {\n      \"shape\": \"server\",\n      \"name\": \"ESXi Host 05\",\n      \"ip\": \"10.10.1.11\",\n      \"role\": \"Hypervisor\",\n      \"tags\": [\n        \"vmware\",\n        \"compute\",\n        \"cluster-b\"\n      ],\n      \"notes\": [\n        \"Dell PowerEdge R750\",\n        \"768GB RAM\",\n        \"vSphere 8.0\"\n      ],\n      \"mac\": \"00:50:56:AA:02:01\",\n      \"rackUnit\": 38,\n      \"uHeight\": \"2\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a2\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"esxi-host-06\": {\n      \"shape\": \"server\",\n      \"name\": \"ESXi Host 06\",\n      \"ip\": \"10.10.1.12\",\n      \"role\": \"Hypervisor\",\n      \"tags\": [\n        \"vmware\",\n        \"compute\",\n        \"cluster-b\"\n      ],\n      \"notes\": [\n        \"Dell PowerEdge R750\",\n        \"768GB RAM\",\n        \"vSphere 8.0\"\n      ],\n      \"mac\": \"00:50:56:AA:02:02\",\n      \"rackUnit\": 35,\n      \"uHeight\": \"2\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a2\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"esxi-host-07\": {\n      \"shape\": \"server\",\n      \"name\": \"ESXi Host 07\",\n      \"ip\": \"10.10.1.13\",\n      \"role\": \"Hypervisor\",\n      \"tags\": [\n        \"vmware\",\n        \"compute\",\n        \"cluster-b\"\n      ],\n      \"notes\": [\n        \"Dell PowerEdge R750\",\n        \"768GB RAM\",\n        \"vSphere 8.0\"\n      ],\n      \"mac\": \"00:50:56:AA:02:03\",\n      \"rackUnit\": 32,\n      \"uHeight\": \"2\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a2\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"esxi-host-08\": {\n      \"shape\": \"server\",\n      \"name\": \"ESXi Host 08\",\n      \"ip\": \"10.10.1.14\",\n      \"role\": \"Hypervisor\",\n      \"tags\": [\n        \"vmware\",\n        \"compute\",\n        \"cluster-b\"\n      ],\n      \"notes\": [\n        \"Dell PowerEdge R750\",\n        \"768GB RAM\",\n        \"vSphere 8.0\"\n      ],\n      \"mac\": \"00:50:56:AA:02:04\",\n      \"rackUnit\": 29,\n      \"uHeight\": \"2\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a2\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"tor-switch-a2\": {\n      \"shape\": \"switch\",\n      \"name\": \"ToR Switch A2\",\n      \"ip\": \"10.10.1.1\",\n      \"role\": \"Top of Rack\",\n      \"tags\": [\n        \"tor\",\n        \"access\",\n        \"rack-a2\"\n      ],\n      \"notes\": [\n        \"Cisco Nexus 93180YC-FX\",\n        \"48x25G ports\"\n      ],\n      \"mac\": \"00:1A:2B:3C:5D:02\",\n      \"rackUnit\": 42,\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a2\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"san-primary\": {\n      \"shape\": \"database\",\n      \"name\": \"SAN Primary\",\n      \"ip\": \"10.10.2.10\",\n      \"role\": \"Primary Storage\",\n      \"tags\": [\n        \"storage\",\n        \"san\",\n        \"netapp\"\n      ],\n      \"notes\": [\n        \"NetApp AFF A400\",\n        \"500TB Raw\",\n        \"FC 32Gb\"\n      ],\n      \"mac\": \"00:A0:98:AA:01:01\",\n      \"rackUnit\": 36,\n      \"uHeight\": \"6\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-b1\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"san-secondary\": {\n      \"shape\": \"database\",\n      \"name\": \"SAN Secondary\",\n      \"ip\": \"10.10.2.11\",\n      \"role\": \"Secondary Storage\",\n      \"tags\": [\n        \"storage\",\n        \"san\",\n        \"netapp\"\n      ],\n      \"notes\": [\n        \"NetApp AFF A400\",\n        \"500TB Raw\",\n        \"FC 32Gb\"\n      ],\n      \"mac\": \"00:A0:98:AA:01:02\",\n      \"rackUnit\": 28,\n      \"uHeight\": \"6\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-b1\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"fc-switch-1\": {\n      \"shape\": \"switch\",\n      \"name\": \"FC Switch 1\",\n      \"ip\": \"10.10.2.1\",\n      \"role\": \"Fibre Channel\",\n      \"tags\": [\n        \"storage\",\n        \"fc\",\n        \"fabric-a\"\n      ],\n      \"notes\": [\n        \"Brocade G620\",\n        \"Fabric A\"\n      ],\n      \"mac\": \"00:1A:2B:FC:01:01\",\n      \"rackUnit\": 42,\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-b1\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"fc-switch-2\": {\n      \"shape\": \"switch\",\n      \"name\": \"FC Switch 2\",\n      \"ip\": \"10.10.2.2\",\n      \"role\": \"Fibre Channel\",\n      \"tags\": [\n        \"storage\",\n        \"fc\",\n        \"fabric-b\"\n      ],\n      \"notes\": [\n        \"Brocade G620\",\n        \"Fabric B\"\n      ],\n      \"mac\": \"00:1A:2B:FC:01:02\",\n      \"rackUnit\": 41,\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-b1\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"backup-server-1\": {\n      \"shape\": \"server\",\n      \"name\": \"Backup Server 1\",\n      \"ip\": \"10.10.3.10\",\n      \"role\": \"Backup Infrastructure\",\n      \"tags\": [\n        \"backup\",\n        \"veeam\",\n        \"protection\"\n      ],\n      \"notes\": [\n        \"Veeam Backup Server\",\n        \"Dell R740xd\",\n        \"200TB\"\n      ],\n      \"mac\": \"00:50:56:BB:01:01\",\n      \"rackUnit\": 36,\n      \"uHeight\": \"2\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-b2\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"backup-server-2\": {\n      \"shape\": \"server\",\n      \"name\": \"Backup Server 2\",\n      \"ip\": \"10.10.3.11\",\n      \"role\": \"Backup Infrastructure\",\n      \"tags\": [\n        \"backup\",\n        \"veeam\",\n        \"protection\"\n      ],\n      \"notes\": [\n        \"Veeam Backup Server\",\n        \"Dell R740xd\",\n        \"200TB\"\n      ],\n      \"mac\": \"00:50:56:BB:01:02\",\n      \"rackUnit\": 33,\n      \"uHeight\": \"2\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-b2\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"tape-library\": {\n      \"shape\": \"database\",\n      \"name\": \"Tape Library\",\n      \"ip\": \"10.10.3.20\",\n      \"role\": \"Archival Storage\",\n      \"tags\": [\n        \"backup\",\n        \"tape\",\n        \"lto9\"\n      ],\n      \"notes\": [\n        \"IBM TS4500\",\n        \"LTO-9\",\n        \"Long-term archive\"\n      ],\n      \"mac\": \"00:50:56:BB:02:01\",\n      \"rackUnit\": 20,\n      \"uHeight\": \"10\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-b2\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"tor-switch-b1\": {\n      \"shape\": \"switch\",\n      \"name\": \"ToR Switch B1\",\n      \"ip\": \"10.10.2.3\",\n      \"role\": \"Top of Rack\",\n      \"tags\": [\n        \"tor\",\n        \"access\",\n        \"rack-b1\"\n      ],\n      \"notes\": [\n        \"Cisco Nexus 93180YC-FX\"\n      ],\n      \"mac\": \"00:1A:2B:3C:5D:03\",\n      \"rackUnit\": 40,\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-b1\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"tor-switch-b2\": {\n      \"shape\": \"switch\",\n      \"name\": \"ToR Switch B2\",\n      \"ip\": \"10.10.3.1\",\n      \"role\": \"Top of Rack\",\n      \"tags\": [\n        \"tor\",\n        \"access\",\n        \"rack-b2\"\n      ],\n      \"notes\": [\n        \"Cisco Nexus 93180YC-FX\"\n      ],\n      \"mac\": \"00:1A:2B:3C:5D:04\",\n      \"rackUnit\": 42,\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-b2\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"web-server-1\": {\n      \"shape\": \"server\",\n      \"name\": \"Web Server 1\",\n      \"ip\": \"172.16.0.11\",\n      \"role\": \"Web Frontend\",\n      \"tags\": [\n        \"dmz\",\n        \"web\",\n        \"nginx\"\n      ],\n      \"notes\": [\n        \"NGINX reverse proxy\",\n        \"Public facing\"\n      ],\n      \"mac\": \"00:50:56:CC:01:01\",\n      \"rackUnit\": 20,\n      \"uHeight\": \"1\",\n      \"layer\": \"security\",\n      \"assignedRack\": \"dmz-rack\",\n      \"rackCapacity\": \"24\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"web-server-2\": {\n      \"shape\": \"server\",\n      \"name\": \"Web Server 2\",\n      \"ip\": \"172.16.0.12\",\n      \"role\": \"Web Frontend\",\n      \"tags\": [\n        \"dmz\",\n        \"web\",\n        \"nginx\"\n      ],\n      \"notes\": [\n        \"NGINX reverse proxy\",\n        \"Public facing\"\n      ],\n      \"mac\": \"00:50:56:CC:01:02\",\n      \"rackUnit\": 18,\n      \"uHeight\": \"1\",\n      \"layer\": \"security\",\n      \"assignedRack\": \"dmz-rack\",\n      \"rackCapacity\": \"24\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"waf-1\": {\n      \"shape\": \"firewall\",\n      \"name\": \"WAF Appliance\",\n      \"ip\": \"172.16.0.5\",\n      \"role\": \"Web Application Firewall\",\n      \"tags\": [\n        \"dmz\",\n        \"security\",\n        \"waf\"\n      ],\n      \"notes\": [\n        \"F5 BIG-IP ASM\",\n        \"OWASP protection\"\n      ],\n      \"mac\": \"00:50:56:CC:02:01\",\n      \"rackUnit\": 22,\n      \"uHeight\": \"2\",\n      \"layer\": \"security\",\n      \"assignedRack\": \"dmz-rack\",\n      \"rackCapacity\": \"24\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"load-balancer-dmz\": {\n      \"shape\": \"switch\",\n      \"name\": \"DMZ Load Balancer\",\n      \"ip\": \"172.16.0.3\",\n      \"role\": \"Load Balancing\",\n      \"tags\": [\n        \"dmz\",\n        \"lb\",\n        \"f5\"\n      ],\n      \"notes\": [\n        \"F5 BIG-IP LTM\",\n        \"VIP: 172.16.0.100\"\n      ],\n      \"mac\": \"00:50:56:CC:03:01\",\n      \"rackUnit\": 16,\n      \"uHeight\": \"2\",\n      \"layer\": \"security\",\n      \"assignedRack\": \"dmz-rack\",\n      \"rackCapacity\": \"24\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"mail-gateway\": {\n      \"shape\": \"server\",\n      \"name\": \"Mail Gateway\",\n      \"ip\": \"172.16.0.25\",\n      \"role\": \"Email Security\",\n      \"tags\": [\n        \"dmz\",\n        \"email\",\n        \"security\"\n      ],\n      \"notes\": [\n        \"Proofpoint Email Gateway\",\n        \"Spam/malware filtering\"\n      ],\n      \"mac\": \"00:50:56:CC:04:01\",\n      \"rackUnit\": 14,\n      \"uHeight\": \"1\",\n      \"layer\": \"security\",\n      \"assignedRack\": \"dmz-rack\",\n      \"rackCapacity\": \"24\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"dns-external-1\": {\n      \"shape\": \"circle\",\n      \"name\": \"External DNS 1\",\n      \"ip\": \"172.16.0.53\",\n      \"role\": \"External DNS\",\n      \"tags\": [\n        \"dmz\",\n        \"dns\",\n        \"public\"\n      ],\n      \"notes\": [\n        \"BIND DNS\",\n        \"Authoritative for corp.com\"\n      ],\n      \"mac\": \"00:50:56:CC:05:01\",\n      \"rackUnit\": 12,\n      \"uHeight\": \"1\",\n      \"layer\": \"security\",\n      \"assignedRack\": \"dmz-rack\",\n      \"rackCapacity\": \"24\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"dns-external-2\": {\n      \"shape\": \"circle\",\n      \"name\": \"External DNS 2\",\n      \"ip\": \"172.16.0.54\",\n      \"role\": \"External DNS\",\n      \"tags\": [\n        \"dmz\",\n        \"dns\",\n        \"public\"\n      ],\n      \"notes\": [\n        \"BIND DNS\",\n        \"Secondary for corp.com\"\n      ],\n      \"mac\": \"00:50:56:CC:05:02\",\n      \"rackUnit\": 10,\n      \"uHeight\": \"1\",\n      \"layer\": \"security\",\n      \"assignedRack\": \"dmz-rack\",\n      \"rackCapacity\": \"24\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"vcenter\": {\n      \"shape\": \"server\",\n      \"name\": \"vCenter Server\",\n      \"ip\": \"192.168.100.10\",\n      \"role\": \"Virtualization Management\",\n      \"tags\": [\n        \"management\",\n        \"vmware\",\n        \"vcsa\"\n      ],\n      \"notes\": [\n        \"vCenter Server Appliance 8.0\",\n        \"Single SSO domain\"\n      ],\n      \"mac\": \"00:50:56:DD:01:01\",\n      \"rackUnit\": 20,\n      \"uHeight\": \"2\",\n      \"layer\": \"logical\",\n      \"assignedRack\": \"mgmt-rack\",\n      \"rackCapacity\": \"24\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"nsx-manager\": {\n      \"shape\": \"server\",\n      \"name\": \"NSX Manager\",\n      \"ip\": \"192.168.100.15\",\n      \"role\": \"Network Virtualization\",\n      \"tags\": [\n        \"management\",\n        \"vmware\",\n        \"nsx\"\n      ],\n      \"notes\": [\n        \"NSX-T 4.1 Manager Cluster\"\n      ],\n      \"mac\": \"00:50:56:DD:02:01\",\n      \"rackUnit\": 17,\n      \"uHeight\": \"2\",\n      \"layer\": \"logical\",\n      \"assignedRack\": \"mgmt-rack\",\n      \"rackCapacity\": \"24\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"siem-server\": {\n      \"shape\": \"server\",\n      \"name\": \"SIEM Server\",\n      \"ip\": \"192.168.100.50\",\n      \"role\": \"Security Monitoring\",\n      \"tags\": [\n        \"management\",\n        \"security\",\n        \"splunk\"\n      ],\n      \"notes\": [\n        \"Splunk Enterprise\",\n        \"Security monitoring\"\n      ],\n      \"mac\": \"00:50:56:DD:03:01\",\n      \"rackUnit\": 14,\n      \"uHeight\": \"2\",\n      \"layer\": \"logical\",\n      \"assignedRack\": \"mgmt-rack\",\n      \"rackCapacity\": \"24\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"nms-server\": {\n      \"shape\": \"server\",\n      \"name\": \"Network Monitoring\",\n      \"ip\": \"192.168.100.60\",\n      \"role\": \"Network Management\",\n      \"tags\": [\n        \"management\",\n        \"monitoring\",\n        \"prtg\"\n      ],\n      \"notes\": [\n        \"PRTG Network Monitor\",\n        \"5000 sensors\"\n      ],\n      \"mac\": \"00:50:56:DD:04:01\",\n      \"rackUnit\": 11,\n      \"uHeight\": \"1\",\n      \"layer\": \"logical\",\n      \"assignedRack\": \"mgmt-rack\",\n      \"rackCapacity\": \"24\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"jump-server\": {\n      \"shape\": \"server\",\n      \"name\": \"Jump Server\",\n      \"ip\": \"192.168.100.100\",\n      \"role\": \"Bastion Host\",\n      \"tags\": [\n        \"management\",\n        \"security\",\n        \"bastion\"\n      ],\n      \"notes\": [\n        \"Windows Server 2022\",\n        \"MFA enabled\"\n      ],\n      \"mac\": \"00:50:56:DD:05:01\",\n      \"rackUnit\": 9,\n      \"uHeight\": \"1\",\n      \"layer\": \"logical\",\n      \"assignedRack\": \"mgmt-rack\",\n      \"rackCapacity\": \"24\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"ipam-server\": {\n      \"shape\": \"server\",\n      \"name\": \"IPAM/DDI\",\n      \"ip\": \"192.168.100.70\",\n      \"role\": \"IP Management\",\n      \"tags\": [\n        \"management\",\n        \"dns\",\n        \"dhcp\"\n      ],\n      \"notes\": [\n        \"Infoblox DDI\",\n        \"DNS/DHCP/IPAM\"\n      ],\n      \"mac\": \"00:50:56:DD:06:01\",\n      \"rackUnit\": 7,\n      \"uHeight\": \"2\",\n      \"layer\": \"logical\",\n      \"assignedRack\": \"mgmt-rack\",\n      \"rackCapacity\": \"24\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"wlc-primary\": {\n      \"shape\": \"wifi\",\n      \"name\": \"WLC Primary\",\n      \"ip\": \"10.20.0.1\",\n      \"role\": \"Wireless Controller\",\n      \"tags\": [\n        \"wireless\",\n        \"cisco\",\n        \"9800\"\n      ],\n      \"notes\": [\n        \"Cisco C9800-40\",\n        \"Primary controller\"\n      ],\n      \"mac\": \"00:1A:2B:WL:01:01\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"2\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"wlc-secondary\": {\n      \"shape\": \"wifi\",\n      \"name\": \"WLC Secondary\",\n      \"ip\": \"10.20.0.2\",\n      \"role\": \"Wireless Controller\",\n      \"tags\": [\n        \"wireless\",\n        \"cisco\",\n        \"9800\"\n      ],\n      \"notes\": [\n        \"Cisco C9800-40\",\n        \"HA Secondary\"\n      ],\n      \"mac\": \"00:1A:2B:WL:01:02\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"2\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"mobile-zone-hq\": {\n      \"shape\": \"phone\",\n      \"name\": \"HQ Mobile Zone\",\n      \"ip\": \"10.20.10.0/24\",\n      \"role\": \"Mobile Device Zone\",\n      \"tags\": [\n        \"wireless\",\n        \"byod\",\n        \"mobile\"\n      ],\n      \"notes\": [\n        \"Corporate BYOD\",\n        \"MDM enrolled devices\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"mobile-zone-guest\": {\n      \"shape\": \"phone\",\n      \"name\": \"Guest WiFi Zone\",\n      \"ip\": \"10.30.0.0/24\",\n      \"role\": \"Guest Network\",\n      \"tags\": [\n        \"wireless\",\n        \"guest\",\n        \"isolated\"\n      ],\n      \"notes\": [\n        \"Captive portal\",\n        \"Internet only\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"mobile-zone-iot\": {\n      \"shape\": \"phone\",\n      \"name\": \"IoT Device Zone\",\n      \"ip\": \"10.40.0.0/24\",\n      \"role\": \"IoT Network\",\n      \"tags\": [\n        \"wireless\",\n        \"iot\",\n        \"building\"\n      ],\n      \"notes\": [\n        \"Building automation\",\n        \"Smart devices\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"branch-router-ny\": {\n      \"shape\": \"router\",\n      \"name\": \"NYC Branch Router\",\n      \"ip\": \"10.100.0.1\",\n      \"role\": \"Branch Gateway\",\n      \"tags\": [\n        \"branch\",\n        \"nyc\",\n        \"sd-wan\"\n      ],\n      \"notes\": [\n        \"Cisco Viptela vEdge\",\n        \"SD-WAN enabled\"\n      ],\n      \"mac\": \"00:1A:2B:BR:01:01\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"branch-router-la\": {\n      \"shape\": \"router\",\n      \"name\": \"LA Branch Router\",\n      \"ip\": \"10.101.0.1\",\n      \"role\": \"Branch Gateway\",\n      \"tags\": [\n        \"branch\",\n        \"la\",\n        \"sd-wan\"\n      ],\n      \"notes\": [\n        \"Cisco Viptela vEdge\",\n        \"SD-WAN enabled\"\n      ],\n      \"mac\": \"00:1A:2B:BR:02:01\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"branch-router-chi\": {\n      \"shape\": \"router\",\n      \"name\": \"Chicago Branch Router\",\n      \"ip\": \"10.102.0.1\",\n      \"role\": \"Branch Gateway\",\n      \"tags\": [\n        \"branch\",\n        \"chicago\",\n        \"sd-wan\"\n      ],\n      \"notes\": [\n        \"Cisco Viptela vEdge\",\n        \"SD-WAN enabled\"\n      ],\n      \"mac\": \"00:1A:2B:BR:03:01\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"branch-router-lon\": {\n      \"shape\": \"router\",\n      \"name\": \"London Branch Router\",\n      \"ip\": \"10.200.0.1\",\n      \"role\": \"Branch Gateway\",\n      \"tags\": [\n        \"branch\",\n        \"london\",\n        \"sd-wan\"\n      ],\n      \"notes\": [\n        \"Cisco Viptela vEdge\",\n        \"EMEA region\"\n      ],\n      \"mac\": \"00:1A:2B:BR:04:01\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"branch-router-tokyo\": {\n      \"shape\": \"router\",\n      \"name\": \"Tokyo Branch Router\",\n      \"ip\": \"10.201.0.1\",\n      \"role\": \"Branch Gateway\",\n      \"tags\": [\n        \"branch\",\n        \"tokyo\",\n        \"sd-wan\"\n      ],\n      \"notes\": [\n        \"Cisco Viptela vEdge\",\n        \"APAC region\"\n      ],\n      \"mac\": \"00:1A:2B:BR:05:01\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"cloud-aws\": {\n      \"shape\": \"cloud\",\n      \"name\": \"AWS Cloud\",\n      \"ip\": \"vpc-0a1b2c3d\",\n      \"role\": \"Public Cloud\",\n      \"tags\": [\n        \"cloud\",\n        \"aws\",\n        \"hybrid\"\n      ],\n      \"notes\": [\n        \"AWS US-East-1\",\n        \"VPC peering to HQ\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"logical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"cloud-azure\": {\n      \"shape\": \"cloud\",\n      \"name\": \"Azure Cloud\",\n      \"ip\": \"vnet-corp-prod\",\n      \"role\": \"Public Cloud\",\n      \"tags\": [\n        \"cloud\",\n        \"azure\",\n        \"hybrid\"\n      ],\n      \"notes\": [\n        \"Azure East US 2\",\n        \"ExpressRoute\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"logical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"cloud-gcp\": {\n      \"shape\": \"cloud\",\n      \"name\": \"GCP Cloud\",\n      \"ip\": \"vpc-gcp-corp\",\n      \"role\": \"Public Cloud\",\n      \"tags\": [\n        \"cloud\",\n        \"gcp\",\n        \"dev\"\n      ],\n      \"notes\": [\n        \"GCP us-central1\",\n        \"Dev/Test workloads\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"logical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"isp-primary\": {\n      \"shape\": \"globe\",\n      \"name\": \"ISP Primary\",\n      \"ip\": \"203.0.113.1\",\n      \"role\": \"Internet Uplink\",\n      \"tags\": [\n        \"wan\",\n        \"internet\",\n        \"primary\"\n      ],\n      \"notes\": [\n        \"AT&T MPLS\",\n        \"1 Gbps dedicated\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"isp-secondary\": {\n      \"shape\": \"vm\",\n      \"name\": \"ISP Secondary\",\n      \"ip\": \"198.51.100.1\",\n      \"role\": \"Internet Uplink\",\n      \"tags\": [\n        \"wan\",\n        \"internet\",\n        \"backup\"\n      ],\n      \"notes\": [\n        \"Verizon Business\",\n        \"500 Mbps backup\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null,\n      \"rotation\": -17\n    },\n    \"dc-internal-1\": {\n      \"shape\": \"circle\",\n      \"name\": \"DC1 Int DNS\",\n      \"ip\": \"10.10.0.53\",\n      \"role\": \"Internal DNS/AD\",\n      \"tags\": [\n        \"dns\",\n        \"ad\",\n        \"dc1\"\n      ],\n      \"notes\": [\n        \"Windows Server 2022\",\n        \"Primary DC\"\n      ],\n      \"mac\": \"00:50:56:AD:01:01\",\n      \"rackUnit\": 26,\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a1\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"dc-internal-2\": {\n      \"shape\": \"circle\",\n      \"name\": \"DC2 Int DNS\",\n      \"ip\": \"10.10.1.53\",\n      \"role\": \"Internal DNS/AD\",\n      \"tags\": [\n        \"dns\",\n        \"ad\",\n        \"dc2\"\n      ],\n      \"notes\": [\n        \"Windows Server 2022\",\n        \"Secondary DC\"\n      ],\n      \"mac\": \"00:50:56:AD:01:02\",\n      \"rackUnit\": 26,\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a2\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"app-server-1\": {\n      \"shape\": \"server\",\n      \"name\": \"App Server 01\",\n      \"ip\": \"10.10.0.101\",\n      \"role\": \"Application\",\n      \"tags\": [\n        \"app\",\n        \"iis\",\n        \"web\"\n      ],\n      \"notes\": [\n        \"Windows Server 2022\",\n        \"IIS Application\"\n      ],\n      \"mac\": \"00:50:56:AP:01:01\",\n      \"rackUnit\": 24,\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a1\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"app-server-2\": {\n      \"shape\": \"server\",\n      \"name\": \"App Server 02\",\n      \"ip\": \"10.10.0.102\",\n      \"role\": \"Application\",\n      \"tags\": [\n        \"app\",\n        \"iis\",\n        \"web\"\n      ],\n      \"notes\": [\n        \"Windows Server 2022\",\n        \"IIS Application\"\n      ],\n      \"mac\": \"00:50:56:AP:01:02\",\n      \"rackUnit\": 22,\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a1\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"db-server-1\": {\n      \"shape\": \"database\",\n      \"name\": \"SQL Server 01\",\n      \"ip\": \"10.10.0.201\",\n      \"role\": \"Database\",\n      \"tags\": [\n        \"db\",\n        \"sql\",\n        \"primary\"\n      ],\n      \"notes\": [\n        \"SQL Server 2022 Enterprise\",\n        \"AlwaysOn Primary\"\n      ],\n      \"mac\": \"00:50:56:DB:01:01\",\n      \"rackUnit\": 20,\n      \"uHeight\": \"2\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a1\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"db-server-2\": {\n      \"shape\": \"database\",\n      \"name\": \"SQL Server 02\",\n      \"ip\": \"10.10.1.201\",\n      \"role\": \"Database\",\n      \"tags\": [\n        \"db\",\n        \"sql\",\n        \"secondary\"\n      ],\n      \"notes\": [\n        \"SQL Server 2022 Enterprise\",\n        \"AlwaysOn Secondary\"\n      ],\n      \"mac\": \"00:50:56:DB:01:02\",\n      \"rackUnit\": 24,\n      \"uHeight\": \"2\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a2\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"k8s-master-1\": {\n      \"shape\": \"hexagon\",\n      \"name\": \"K8s Master 1\",\n      \"ip\": \"10.10.1.50\",\n      \"role\": \"Container Orchestration\",\n      \"tags\": [\n        \"kubernetes\",\n        \"master\",\n        \"container\"\n      ],\n      \"notes\": [\n        \"K8s Control Plane\",\n        \"etcd member\"\n      ],\n      \"mac\": \"00:50:56:K8:01:01\",\n      \"rackUnit\": 21,\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a2\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"k8s-master-2\": {\n      \"shape\": \"hexagon\",\n      \"name\": \"K8s Master 2\",\n      \"ip\": \"10.10.1.51\",\n      \"role\": \"Container Orchestration\",\n      \"tags\": [\n        \"kubernetes\",\n        \"master\",\n        \"container\"\n      ],\n      \"notes\": [\n        \"K8s Control Plane\",\n        \"etcd member\"\n      ],\n      \"mac\": \"00:50:56:K8:01:02\",\n      \"rackUnit\": 19,\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a2\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"k8s-master-3\": {\n      \"shape\": \"hexagon\",\n      \"name\": \"K8s Master 3\",\n      \"ip\": \"10.10.1.52\",\n      \"role\": \"Container Orchestration\",\n      \"tags\": [\n        \"kubernetes\",\n        \"master\",\n        \"container\"\n      ],\n      \"notes\": [\n        \"K8s Control Plane\",\n        \"etcd member\"\n      ],\n      \"mac\": \"00:50:56:K8:01:03\",\n      \"rackUnit\": 17,\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a2\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"k8s-worker-1\": {\n      \"shape\": \"server\",\n      \"name\": \"K8s Worker 1\",\n      \"ip\": \"10.10.1.60\",\n      \"role\": \"Container Workload\",\n      \"tags\": [\n        \"kubernetes\",\n        \"worker\",\n        \"container\"\n      ],\n      \"notes\": [\n        \"K8s Worker Node\",\n        \"64GB RAM\"\n      ],\n      \"mac\": \"00:50:56:K8:02:01\",\n      \"rackUnit\": 15,\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a2\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"k8s-worker-2\": {\n      \"shape\": \"server\",\n      \"name\": \"K8s Worker 2\",\n      \"ip\": \"10.10.1.61\",\n      \"role\": \"Container Workload\",\n      \"tags\": [\n        \"kubernetes\",\n        \"worker\",\n        \"container\"\n      ],\n      \"notes\": [\n        \"K8s Worker Node\",\n        \"64GB RAM\"\n      ],\n      \"mac\": \"00:50:56:K8:02:02\",\n      \"rackUnit\": 13,\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a2\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"k8s-worker-3\": {\n      \"shape\": \"server\",\n      \"name\": \"K8s Worker 3\",\n      \"ip\": \"10.10.1.62\",\n      \"role\": \"Container Workload\",\n      \"tags\": [\n        \"kubernetes\",\n        \"worker\",\n        \"container\"\n      ],\n      \"notes\": [\n        \"K8s Worker Node\",\n        \"64GB RAM\"\n      ],\n      \"mac\": \"00:50:56:K8:02:03\",\n      \"rackUnit\": 11,\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a2\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"k8s-worker-4\": {\n      \"shape\": \"server\",\n      \"name\": \"K8s Worker 4\",\n      \"ip\": \"10.10.1.63\",\n      \"role\": \"Container Workload\",\n      \"tags\": [\n        \"kubernetes\",\n        \"worker\",\n        \"container\"\n      ],\n      \"notes\": [\n        \"K8s Worker Node\",\n        \"64GB RAM\"\n      ],\n      \"mac\": \"00:50:56:K8:02:04\",\n      \"rackUnit\": 9,\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a2\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"proxy-server-1\": {\n      \"shape\": \"server\",\n      \"name\": \"Proxy Server 1\",\n      \"ip\": \"10.5.0.10\",\n      \"role\": \"Web Proxy\",\n      \"tags\": [\n        \"proxy\",\n        \"squid\",\n        \"filtering\"\n      ],\n      \"notes\": [\n        \"Squid Proxy\",\n        \"Content filtering\"\n      ],\n      \"mac\": \"00:50:56:PX:01:01\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"security\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"proxy-server-2\": {\n      \"shape\": \"server\",\n      \"name\": \"Proxy Server 2\",\n      \"ip\": \"10.5.0.11\",\n      \"role\": \"Web Proxy\",\n      \"tags\": [\n        \"proxy\",\n        \"squid\",\n        \"filtering\"\n      ],\n      \"notes\": [\n        \"Squid Proxy\",\n        \"HA pair\"\n      ],\n      \"mac\": \"00:50:56:PX:01:02\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"security\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"vpn-concentrator\": {\n      \"shape\": \"firewall\",\n      \"name\": \"VPN Concentrator\",\n      \"ip\": \"10.0.5.1\",\n      \"role\": \"Remote Access VPN\",\n      \"tags\": [\n        \"vpn\",\n        \"remote\",\n        \"security\"\n      ],\n      \"notes\": [\n        \"Cisco ASA 5555-X\",\n        \"AnyConnect SSL VPN\"\n      ],\n      \"mac\": \"00:1A:2B:VP:01:01\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"2\",\n      \"layer\": \"security\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"nac-server\": {\n      \"shape\": \"server\",\n      \"name\": \"NAC Server\",\n      \"ip\": \"10.5.5.10\",\n      \"role\": \"Network Access Control\",\n      \"tags\": [\n        \"nac\",\n        \"ise\",\n        \"802.1x\"\n      ],\n      \"notes\": [\n        \"Cisco ISE 3.1\",\n        \"RADIUS/TACACS+\"\n      ],\n      \"mac\": \"00:50:56:NA:01:01\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"2\",\n      \"layer\": \"security\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"print-server\": {\n      \"shape\": \"server\",\n      \"name\": \"Print Server\",\n      \"ip\": \"10.10.0.150\",\n      \"role\": \"Print Services\",\n      \"tags\": [\n        \"print\",\n        \"windows\",\n        \"services\"\n      ],\n      \"notes\": [\n        \"Windows Print Server\",\n        \"50+ printers\"\n      ],\n      \"mac\": \"00:50:56:PR:01:01\",\n      \"rackUnit\": 18,\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a1\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"file-server\": {\n      \"shape\": \"database\",\n      \"name\": \"File Server\",\n      \"ip\": \"10.10.0.160\",\n      \"role\": \"File Services\",\n      \"tags\": [\n        \"file\",\n        \"smb\",\n        \"dfs\"\n      ],\n      \"notes\": [\n        \"Windows File Server\",\n        \"DFS namespace\"\n      ],\n      \"mac\": \"00:50:56:FS:01:01\",\n      \"rackUnit\": 16,\n      \"uHeight\": \"2\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a1\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"ca-server\": {\n      \"shape\": \"server\",\n      \"name\": \"Certificate Authority\",\n      \"ip\": \"192.168.100.80\",\n      \"role\": \"PKI Infrastructure\",\n      \"tags\": [\n        \"pki\",\n        \"ca\",\n        \"security\"\n      ],\n      \"notes\": [\n        \"Windows CA\",\n        \"Enterprise Root CA\"\n      ],\n      \"mac\": \"00:50:56:CA:01:01\",\n      \"rackUnit\": 5,\n      \"uHeight\": \"1\",\n      \"layer\": \"logical\",\n      \"assignedRack\": \"mgmt-rack\",\n      \"rackCapacity\": \"24\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"sccm-server\": {\n      \"shape\": \"server\",\n      \"name\": \"SCCM Server\",\n      \"ip\": \"192.168.100.90\",\n      \"role\": \"Endpoint Management\",\n      \"tags\": [\n        \"sccm\",\n        \"patching\",\n        \"software\"\n      ],\n      \"notes\": [\n        \"MECM Primary Site\",\n        \"Software deployment\"\n      ],\n      \"mac\": \"00:50:56:SC:01:01\",\n      \"rackUnit\": 3,\n      \"uHeight\": \"2\",\n      \"layer\": \"logical\",\n      \"assignedRack\": \"mgmt-rack\",\n      \"rackCapacity\": \"24\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"voip-cluster\": {\n      \"shape\": \"phone\",\n      \"name\": \"VoIP Cluster\",\n      \"ip\": \"10.50.0.0/24\",\n      \"role\": \"Voice Services\",\n      \"tags\": [\n        \"voip\",\n        \"cisco\",\n        \"ucm\"\n      ],\n      \"notes\": [\n        \"Cisco UCM Cluster\",\n        \"3000 endpoints\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"application\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"video-conf\": {\n      \"shape\": \"laptop\",\n      \"name\": \"Video Conference\",\n      \"ip\": \"10.51.0.0/24\",\n      \"role\": \"Video Services\",\n      \"tags\": [\n        \"video\",\n        \"webex\",\n        \"teams\"\n      ],\n      \"notes\": [\n        \"Webex/Teams integration\",\n        \"Meeting rooms\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"application\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"security-cameras\": {\n      \"shape\": \"camera\",\n      \"name\": \"Security Cameras\",\n      \"ip\": \"10.60.0.0/24\",\n      \"role\": \"Physical Security\",\n      \"tags\": [\n        \"cctv\",\n        \"surveillance\",\n        \"security\"\n      ],\n      \"notes\": [\n        \"150+ IP cameras\",\n        \"30-day retention\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"nvr-cluster\": {\n      \"shape\": \"server\",\n      \"name\": \"NVR Cluster\",\n      \"ip\": \"10.60.0.10\",\n      \"role\": \"Video Recording\",\n      \"tags\": [\n        \"nvr\",\n        \"surveillance\",\n        \"storage\"\n      ],\n      \"notes\": [\n        \"Milestone XProtect\",\n        \"500TB storage\"\n      ],\n      \"mac\": \"00:50:56:NV:01:01\",\n      \"rackUnit\": 15,\n      \"uHeight\": \"4\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-b2\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"dev-server-1\": {\n      \"shape\": \"server\",\n      \"name\": \"Dev Server 1\",\n      \"ip\": \"10.80.0.10\",\n      \"role\": \"Development\",\n      \"tags\": [\n        \"dev\",\n        \"gitlab\",\n        \"ci-cd\",\n        {\n          \"type\": \"icon\",\n          \"library\": \"selfhst\",\n          \"name\": \"dokku\"\n        }\n      ],\n      \"notes\": [\n        \"GitLab Server\",\n        \"CI/CD pipelines\"\n      ],\n      \"mac\": \"00:50:56:DV:01:01\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"2\",\n      \"layer\": \"application\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"dev-server-2\": {\n      \"shape\": \"server\",\n      \"name\": \"Dev Server 2\",\n      \"ip\": \"10.80.0.11\",\n      \"role\": \"Development\",\n      \"tags\": [\n        \"dev\",\n        \"jenkins\",\n        \"ci-cd\"\n      ],\n      \"notes\": [\n        \"Jenkins Server\",\n        \"Build automation\"\n      ],\n      \"mac\": \"00:50:56:DV:01:02\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"2\",\n      \"layer\": \"application\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"test-environment\": {\n      \"shape\": \"shield\",\n      \"name\": \"Test Environment\",\n      \"ip\": \"10.81.0.0/24\",\n      \"role\": \"QA/Testing\",\n      \"tags\": [\n        \"test\",\n        \"qa\",\n        \"staging\"\n      ],\n      \"notes\": [\n        \"Staging environment\",\n        \"Pre-prod validation\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"application\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null,\n      \"rotation\": -36\n    },\n    \"erp-system\": {\n      \"shape\": \"database\",\n      \"name\": \"ERP System\",\n      \"ip\": \"10.90.0.10\",\n      \"role\": \"Business Application\",\n      \"tags\": [\n        \"erp\",\n        \"sap\",\n        \"business\"\n      ],\n      \"notes\": [\n        \"SAP S/4HANA\",\n        \"Financial/HR systems\"\n      ],\n      \"mac\": \"00:50:56:ER:01:01\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"4\",\n      \"layer\": \"application\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"crm-system\": {\n      \"shape\": \"database\",\n      \"name\": \"CRM System\",\n      \"ip\": \"10.91.0.10\",\n      \"role\": \"Business Application\",\n      \"tags\": [\n        \"crm\",\n        \"salesforce\",\n        \"business\"\n      ],\n      \"notes\": [\n        \"Salesforce integration\",\n        \"Sales/Marketing\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"application\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"endpoint-1000\": {\n      \"shape\": \"laptop\",\n      \"name\": \"Corporate Endpoints\",\n      \"ip\": \"10.70.0.0/22\",\n      \"role\": \"User Workstations\",\n      \"tags\": [\n        \"endpoints\",\n        \"workstations\",\n        \"users\"\n      ],\n      \"notes\": [\n        \"~1000 corporate laptops\",\n        \"Windows 11\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"dist-switch-floor1\": {\n      \"shape\": \"switch\",\n      \"name\": \"Floor 1 Switch\",\n      \"ip\": \"10.1.1.1\",\n      \"role\": \"Distribution\",\n      \"tags\": [\n        \"distribution\",\n        \"floor-1\",\n        \"access\"\n      ],\n      \"notes\": [\n        \"Cisco C9300-48P\",\n        \"PoE+ enabled\"\n      ],\n      \"mac\": \"00:1A:2B:FL:01:01\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"dist-switch-floor2\": {\n      \"shape\": \"switch\",\n      \"name\": \"Floor 2 Switch\",\n      \"ip\": \"10.1.2.1\",\n      \"role\": \"Distribution\",\n      \"tags\": [\n        \"distribution\",\n        \"floor-2\",\n        \"access\"\n      ],\n      \"notes\": [\n        \"Cisco C9300-48P\",\n        \"PoE+ enabled\"\n      ],\n      \"mac\": \"00:1A:2B:FL:02:01\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"dist-switch-floor3\": {\n      \"shape\": \"switch\",\n      \"name\": \"Floor 3 Switch\",\n      \"ip\": \"10.1.3.1\",\n      \"role\": \"Distribution\",\n      \"tags\": [\n        \"distribution\",\n        \"floor-3\",\n        \"access\"\n      ],\n      \"notes\": [\n        \"Cisco C9300-48P\",\n        \"PoE+ enabled\"\n      ],\n      \"mac\": \"00:1A:2B:FL:03:01\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"dist-switch-floor4\": {\n      \"shape\": \"switch\",\n      \"name\": \"Floor 4 Switch\",\n      \"ip\": \"10.1.4.1\",\n      \"role\": \"Distribution\",\n      \"tags\": [\n        \"distribution\",\n        \"floor-4\",\n        \"access\"\n      ],\n      \"notes\": [\n        \"Cisco C9300-48P\",\n        \"PoE+ enabled\"\n      ],\n      \"mac\": \"00:1A:2B:FL:04:01\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"ap-floor1-zone1\": {\n      \"shape\": \"wifi\",\n      \"name\": \"AP Floor 1 Zone 1\",\n      \"ip\": \"10.20.1.10\",\n      \"role\": \"Wireless Access\",\n      \"tags\": [\n        \"wifi\",\n        \"ap\",\n        \"floor-1\"\n      ],\n      \"notes\": [\n        \"Cisco 9120AX\",\n        \"Wi-Fi 6\"\n      ],\n      \"mac\": \"00:1A:2B:AP:01:01\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"ap-floor2-zone1\": {\n      \"shape\": \"wifi\",\n      \"name\": \"AP Floor 2 Zone 1\",\n      \"ip\": \"10.20.2.10\",\n      \"role\": \"Wireless Access\",\n      \"tags\": [\n        \"wifi\",\n        \"ap\",\n        \"floor-2\"\n      ],\n      \"notes\": [\n        \"Cisco 9120AX\",\n        \"Wi-Fi 6\"\n      ],\n      \"mac\": \"00:1A:2B:AP:02:01\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"ap-floor3-zone1\": {\n      \"shape\": \"wifi\",\n      \"name\": \"AP Floor 3 Zone 1\",\n      \"ip\": \"10.20.3.10\",\n      \"role\": \"Wireless Access\",\n      \"tags\": [\n        \"wifi\",\n        \"ap\",\n        \"floor-3\"\n      ],\n      \"notes\": [\n        \"Cisco 9120AX\",\n        \"Wi-Fi 6\"\n      ],\n      \"mac\": \"00:1A:2B:AP:03:01\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"ap-floor4-zone1\": {\n      \"shape\": \"wifi\",\n      \"name\": \"AP Floor 4 Zone 1\",\n      \"ip\": \"10.20.4.10\",\n      \"role\": \"Wireless Access\",\n      \"tags\": [\n        \"wifi\",\n        \"ap\",\n        \"floor-4\"\n      ],\n      \"notes\": [\n        \"Cisco 9120AX\",\n        \"Wi-Fi 6\"\n      ],\n      \"mac\": \"00:1A:2B:AP:04:01\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"ups-dc-1\": {\n      \"shape\": \"rectangle\",\n      \"name\": \"UPS DC-1\",\n      \"ip\": \"192.168.200.10\",\n      \"role\": \"Power Management\",\n      \"tags\": [\n        \"power\",\n        \"ups\",\n        \"datacenter\"\n      ],\n      \"notes\": [\n        \"APC Symmetra\",\n        \"80kVA\",\n        \"30 min runtime\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"ups-dc-2\": {\n      \"shape\": \"rectangle\",\n      \"name\": \"UPS DC-2\",\n      \"ip\": \"192.168.200.11\",\n      \"role\": \"Power Management\",\n      \"tags\": [\n        \"power\",\n        \"ups\",\n        \"datacenter\"\n      ],\n      \"notes\": [\n        \"APC Symmetra\",\n        \"80kVA\",\n        \"Redundant\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"pdu-rack-a1\": {\n      \"shape\": \"rectangle\",\n      \"name\": \"PDU Rack A1\",\n      \"ip\": \"192.168.200.21\",\n      \"role\": \"Power Distribution\",\n      \"tags\": [\n        \"power\",\n        \"pdu\",\n        \"rack-a1\"\n      ],\n      \"notes\": [\n        \"APC Switched PDU\",\n        \"Per-outlet metering\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": 1,\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a1\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"pdu-rack-a2\": {\n      \"shape\": \"rectangle\",\n      \"name\": \"PDU Rack A2\",\n      \"ip\": \"192.168.200.22\",\n      \"role\": \"Power Distribution\",\n      \"tags\": [\n        \"power\",\n        \"pdu\",\n        \"rack-a2\"\n      ],\n      \"notes\": [\n        \"APC Switched PDU\",\n        \"Per-outlet metering\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": 1,\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a2\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"cooling-1\": {\n      \"shape\": \"rectangle\",\n      \"name\": \"CRAC Unit 1\",\n      \"ip\": \"192.168.200.30\",\n      \"role\": \"Cooling\",\n      \"tags\": [\n        \"cooling\",\n        \"hvac\",\n        \"datacenter\"\n      ],\n      \"notes\": [\n        \"Liebert CRV\",\n        \"Row-based cooling\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"cooling-2\": {\n      \"shape\": \"rectangle\",\n      \"name\": \"CRAC Unit 2\",\n      \"ip\": \"192.168.200.31\",\n      \"role\": \"Cooling\",\n      \"tags\": [\n        \"cooling\",\n        \"hvac\",\n        \"datacenter\"\n      ],\n      \"notes\": [\n        \"Liebert CRV\",\n        \"N+1 redundancy\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"camera-a\": {\n      \"shape\": \"camera\",\n      \"name\": \"camera A\",\n      \"ip\": \"0.0.0.0\",\n      \"role\": \"\",\n      \"tags\": [],\n      \"notes\": [],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"ping\": {\n        \"enabled\": false,\n        \"protocol\": \"http\",\n        \"customUrl\": \"\",\n        \"timeout\": 3000,\n        \"status\": \"unknown\",\n        \"lastCheck\": null\n      },\n      \"locked\": false,\n      \"groupId\": null,\n      \"fovEnabled\": true,\n      \"fovRotation\": 104,\n      \"fovDistance\": 500,\n      \"fovSweep\": 60,\n      \"fovSpeed\": 10,\n      \"fovAnimate\": true\n    },\n    \"camera-a-copy\": {\n      \"shape\": \"camera\",\n      \"name\": \"camera B\",\n      \"ip\": \"0.0.0.0\",\n      \"role\": \"\",\n      \"tags\": [],\n      \"notes\": [],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"ping\": {\n        \"enabled\": false,\n        \"protocol\": \"http\",\n        \"customUrl\": \"\",\n        \"timeout\": 3000,\n        \"status\": \"unknown\",\n        \"lastCheck\": null\n      },\n      \"locked\": false,\n      \"groupId\": null,\n      \"fovEnabled\": true,\n      \"fovRotation\": 162,\n      \"fovDistance\": 500,\n      \"fovSweep\": 60,\n      \"fovSpeed\": 10,\n      \"fovAnimate\": false\n    }\n  },\n  \"edgeData\": {\n    \"list\": [\n      {\n        \"id\": \"isp1-router1\",\n        \"from\": \"isp-primary\",\n        \"to\": \"core-router-1\",\n        \"width\": 6,\n        \"color\": \"#10b981\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [\n          \"Primary WAN link\"\n        ],\n        \"fromPort\": \"Gi0/0\",\n        \"toPort\": \"Gi1/0/1\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"isp2-router2\",\n        \"from\": \"isp-secondary\",\n        \"to\": \"core-router-2\",\n        \"width\": 6,\n        \"color\": \"#10b981\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [\n          \"Backup WAN link\"\n        ],\n        \"fromPort\": \"Gi0/0\",\n        \"toPort\": \"Gi1/0/1\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"router1-router2\",\n        \"from\": \"core-router-1\",\n        \"to\": \"core-router-2\",\n        \"width\": 4,\n        \"color\": \"#f59e0b\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [\n          \"HSRP Peering\"\n        ],\n        \"fromPort\": \"Gi1/0/24\",\n        \"toPort\": \"Gi1/0/24\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"router1-fw1\",\n        \"from\": \"core-router-1\",\n        \"to\": \"fw-external-1\",\n        \"width\": 4,\n        \"color\": \"#ef4444\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"router2-fw2\",\n        \"from\": \"core-router-2\",\n        \"to\": \"fw-external-2\",\n        \"width\": 4,\n        \"color\": \"#ef4444\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"fw1-fw2\",\n        \"from\": \"fw-external-1\",\n        \"to\": \"fw-external-2\",\n        \"width\": 3,\n        \"color\": \"#f59e0b\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [\n          \"HA heartbeat\"\n        ],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"dashed\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"fw1-coresw1\",\n        \"from\": \"fw-external-1\",\n        \"to\": \"core-switch-1\",\n        \"width\": 4,\n        \"color\": \"#475569\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"fw2-coresw2\",\n        \"from\": \"fw-external-2\",\n        \"to\": \"core-switch-2\",\n        \"width\": 4,\n        \"color\": \"#475569\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"coresw1-coresw2\",\n        \"from\": \"core-switch-1\",\n        \"to\": \"core-switch-2\",\n        \"width\": 5,\n        \"color\": \"#3b82f6\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [\n          \"VPC peer-link\"\n        ],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"coresw1-fwint\",\n        \"from\": \"core-switch-1\",\n        \"to\": \"fw-internal\",\n        \"width\": 3,\n        \"color\": \"#ef4444\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"coresw2-fwint\",\n        \"from\": \"core-switch-2\",\n        \"to\": \"fw-internal\",\n        \"width\": 3,\n        \"color\": \"#ef4444\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"coresw1-racka1\",\n        \"from\": \"core-switch-1\",\n        \"to\": \"dc-rack-a1\",\n        \"width\": 4,\n        \"color\": \"#475569\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"coresw2-racka1\",\n        \"from\": \"core-switch-2\",\n        \"to\": \"dc-rack-a1\",\n        \"width\": 4,\n        \"color\": \"#475569\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"coresw1-racka2\",\n        \"from\": \"core-switch-1\",\n        \"to\": \"dc-rack-a2\",\n        \"width\": 4,\n        \"color\": \"#475569\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"coresw2-racka2\",\n        \"from\": \"core-switch-2\",\n        \"to\": \"dc-rack-a2\",\n        \"width\": 4,\n        \"color\": \"#475569\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"coresw1-rackb1\",\n        \"from\": \"core-switch-1\",\n        \"to\": \"dc-rack-b1\",\n        \"width\": 4,\n        \"color\": \"#475569\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"coresw2-rackb1\",\n        \"from\": \"core-switch-2\",\n        \"to\": \"dc-rack-b1\",\n        \"width\": 4,\n        \"color\": \"#475569\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"coresw1-rackb2\",\n        \"from\": \"core-switch-1\",\n        \"to\": \"dc-rack-b2\",\n        \"width\": 4,\n        \"color\": \"#475569\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"coresw2-rackb2\",\n        \"from\": \"core-switch-2\",\n        \"to\": \"dc-rack-b2\",\n        \"width\": 4,\n        \"color\": \"#475569\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"fw1-dmz\",\n        \"from\": \"fw-external-1\",\n        \"to\": \"dmz-rack\",\n        \"width\": 3,\n        \"color\": \"#f59e0b\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [\n          \"DMZ segment\"\n        ],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"fw2-dmz\",\n        \"from\": \"fw-external-2\",\n        \"to\": \"dmz-rack\",\n        \"width\": 3,\n        \"color\": \"#f59e0b\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [\n          \"DMZ segment\"\n        ],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"coresw1-mgmt\",\n        \"from\": \"core-switch-1\",\n        \"to\": \"mgmt-rack\",\n        \"width\": 3,\n        \"color\": \"#8b5cf6\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [\n          \"OOB management\"\n        ],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"coresw1-wlc1\",\n        \"from\": \"core-switch-1\",\n        \"to\": \"wlc-primary\",\n        \"width\": 3,\n        \"color\": \"#06b6d4\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"coresw2-wlc2\",\n        \"from\": \"core-switch-2\",\n        \"to\": \"wlc-secondary\",\n        \"width\": 3,\n        \"color\": \"#06b6d4\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\",\n        \"animate\": true\n      },\n      {\n        \"id\": \"wlc1-wlc2\",\n        \"from\": \"wlc-primary\",\n        \"to\": \"wlc-secondary\",\n        \"width\": 2,\n        \"color\": \"#f59e0b\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [\n          \"HA pair\"\n        ],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"dashed\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"wlc1-mobile-hq\",\n        \"from\": \"wlc-primary\",\n        \"to\": \"mobile-zone-hq\",\n        \"width\": 2,\n        \"color\": \"#06b6d4\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"wlc1-mobile-guest\",\n        \"from\": \"wlc-primary\",\n        \"to\": \"mobile-zone-guest\",\n        \"width\": 2,\n        \"color\": \"#06b6d4\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"wlc1-mobile-iot\",\n        \"from\": \"wlc-primary\",\n        \"to\": \"mobile-zone-iot\",\n        \"width\": 2,\n        \"color\": \"#06b6d4\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"router1-branch-ny\",\n        \"from\": \"core-router-1\",\n        \"to\": \"branch-router-ny\",\n        \"width\": 3,\n        \"color\": \"#a855f7\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [\n          \"SD-WAN tunnel\"\n        ],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"dashed\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"router1-branch-la\",\n        \"from\": \"core-router-1\",\n        \"to\": \"branch-router-la\",\n        \"width\": 3,\n        \"color\": \"#a855f7\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [\n          \"SD-WAN tunnel\"\n        ],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"dashed\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"router1-branch-chi\",\n        \"from\": \"core-router-1\",\n        \"to\": \"branch-router-chi\",\n        \"width\": 3,\n        \"color\": \"#a855f7\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [\n          \"SD-WAN tunnel\"\n        ],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"dashed\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"router1-branch-lon\",\n        \"from\": \"core-router-1\",\n        \"to\": \"branch-router-lon\",\n        \"width\": 3,\n        \"color\": \"#a855f7\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [\n          \"SD-WAN tunnel\"\n        ],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"dashed\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"router1-branch-tokyo\",\n        \"from\": \"core-router-1\",\n        \"to\": \"branch-router-tokyo\",\n        \"width\": 3,\n        \"color\": \"#a855f7\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [\n          \"SD-WAN tunnel\"\n        ],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"dashed\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"router1-aws\",\n        \"from\": \"core-router-1\",\n        \"to\": \"cloud-aws\",\n        \"width\": 3,\n        \"color\": \"#f97316\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [\n          \"Direct Connect\"\n        ],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"dashed\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"router2-azure\",\n        \"from\": \"core-router-2\",\n        \"to\": \"cloud-azure\",\n        \"width\": 3,\n        \"color\": \"#0ea5e9\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [\n          \"ExpressRoute\"\n        ],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"dashed\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"fwint-gcp\",\n        \"from\": \"fw-internal\",\n        \"to\": \"cloud-gcp\",\n        \"width\": 2,\n        \"color\": \"#22c55e\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [\n          \"VPN tunnel\"\n        ],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"dashed\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"coresw1-floor1\",\n        \"from\": \"core-switch-1\",\n        \"to\": \"dist-switch-floor1\",\n        \"width\": 3,\n        \"color\": \"#475569\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"coresw1-floor2\",\n        \"from\": \"core-switch-1\",\n        \"to\": \"dist-switch-floor2\",\n        \"width\": 3,\n        \"color\": \"#475569\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"coresw2-floor3\",\n        \"from\": \"core-switch-2\",\n        \"to\": \"dist-switch-floor3\",\n        \"width\": 3,\n        \"color\": \"#475569\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"coresw2-floor4\",\n        \"from\": \"core-switch-2\",\n        \"to\": \"dist-switch-floor4\",\n        \"width\": 3,\n        \"color\": \"#475569\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"floor1-endpoints\",\n        \"from\": \"dist-switch-floor1\",\n        \"to\": \"endpoint-1000\",\n        \"width\": 2,\n        \"color\": \"#94a3b8\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"floor1-ap1\",\n        \"from\": \"dist-switch-floor1\",\n        \"to\": \"ap-floor1-zone1\",\n        \"width\": 2,\n        \"color\": \"#06b6d4\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"floor2-ap2\",\n        \"from\": \"dist-switch-floor2\",\n        \"to\": \"ap-floor2-zone1\",\n        \"width\": 2,\n        \"color\": \"#06b6d4\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"floor3-ap3\",\n        \"from\": \"dist-switch-floor3\",\n        \"to\": \"ap-floor3-zone1\",\n        \"width\": 2,\n        \"color\": \"#06b6d4\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"floor4-ap4\",\n        \"from\": \"dist-switch-floor4\",\n        \"to\": \"ap-floor4-zone1\",\n        \"width\": 2,\n        \"color\": \"#06b6d4\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"fwint-proxy1\",\n        \"from\": \"fw-internal\",\n        \"to\": \"proxy-server-1\",\n        \"width\": 2,\n        \"color\": \"#ef4444\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"fwint-proxy2\",\n        \"from\": \"fw-internal\",\n        \"to\": \"proxy-server-2\",\n        \"width\": 2,\n        \"color\": \"#ef4444\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"fwext1-vpn\",\n        \"from\": \"fw-external-1\",\n        \"to\": \"vpn-concentrator\",\n        \"width\": 3,\n        \"color\": \"#8b5cf6\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"coresw1-nac\",\n        \"from\": \"core-switch-1\",\n        \"to\": \"nac-server\",\n        \"width\": 2,\n        \"color\": \"#f59e0b\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"coresw1-voip\",\n        \"from\": \"core-switch-1\",\n        \"to\": \"voip-cluster\",\n        \"width\": 3,\n        \"color\": \"#22c55e\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"coresw2-video\",\n        \"from\": \"core-switch-2\",\n        \"to\": \"video-conf\",\n        \"width\": 3,\n        \"color\": \"#22c55e\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"coresw1-cameras\",\n        \"from\": \"core-switch-1\",\n        \"to\": \"security-cameras\",\n        \"width\": 2,\n        \"color\": \"#94a3b8\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"fwint-dev1\",\n        \"from\": \"fw-internal\",\n        \"to\": \"dev-server-1\",\n        \"width\": 2,\n        \"color\": \"#a855f7\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"fwint-dev2\",\n        \"from\": \"fw-internal\",\n        \"to\": \"dev-server-2\",\n        \"width\": 2,\n        \"color\": \"#a855f7\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\",\n        \"animate\": true,\n        \"animationSpeed\": \"1.5\"\n      },\n      {\n        \"id\": \"fwint-test\",\n        \"from\": \"fw-internal\",\n        \"to\": \"test-environment\",\n        \"width\": 2,\n        \"color\": \"#a855f7\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"coresw1-erp\",\n        \"from\": \"core-switch-1\",\n        \"to\": \"erp-system\",\n        \"width\": 3,\n        \"color\": \"#f59e0b\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"fwext1-crm\",\n        \"from\": \"fw-external-1\",\n        \"to\": \"crm-system\",\n        \"width\": 2,\n        \"color\": \"#f59e0b\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [\n          \"Salesforce cloud\"\n        ],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"dashed\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"ups1-racka1\",\n        \"from\": \"ups-dc-1\",\n        \"to\": \"dc-rack-a1\",\n        \"width\": 2,\n        \"color\": \"#fbbf24\",\n        \"direction\": \"forward\",\n        \"type\": \"main\",\n        \"notes\": [\n          \"Power feed A\"\n        ],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"ups2-racka2\",\n        \"from\": \"ups-dc-2\",\n        \"to\": \"dc-rack-a2\",\n        \"width\": 2,\n        \"color\": \"#fbbf24\",\n        \"direction\": \"forward\",\n        \"type\": \"main\",\n        \"notes\": [\n          \"Power feed B\"\n        ],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"ups1-rackb1\",\n        \"from\": \"ups-dc-1\",\n        \"to\": \"dc-rack-b1\",\n        \"width\": 2,\n        \"color\": \"#fbbf24\",\n        \"direction\": \"forward\",\n        \"type\": \"main\",\n        \"notes\": [\n          \"Power feed A\"\n        ],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\",\n        \"animate\": true,\n        \"animationSpeed\": \"4\"\n      },\n      {\n        \"id\": \"ups2-rackb2\",\n        \"from\": \"ups-dc-2\",\n        \"to\": \"dc-rack-b2\",\n        \"width\": 2,\n        \"color\": \"#fbbf24\",\n        \"direction\": \"forward\",\n        \"type\": \"main\",\n        \"notes\": [\n          \"Power feed B\"\n        ],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"cooling1-racka1\",\n        \"from\": \"cooling-1\",\n        \"to\": \"dc-rack-a1\",\n        \"width\": 2,\n        \"color\": \"#38bdf8\",\n        \"direction\": \"forward\",\n        \"type\": \"main\",\n        \"notes\": [\n          \"Cooling zone\"\n        ],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"dotted\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"cooling2-rackb1\",\n        \"from\": \"cooling-2\",\n        \"to\": \"dc-rack-b1\",\n        \"width\": 2,\n        \"color\": \"#38bdf8\",\n        \"direction\": \"forward\",\n        \"type\": \"main\",\n        \"notes\": [\n          \"Cooling zone\"\n        ],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"dotted\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"custom-1765237881452\",\n        \"type\": \"custom\",\n        \"color\": \"#c800ff\",\n        \"width\": 4,\n        \"lineStyle\": \"solid\",\n        \"direction\": \"forward\",\n        \"points\": [\n          {\n            \"x\": 3492.3994140625,\n            \"y\": 1526.9556884765625\n          },\n          {\n            \"x\": 3500.609619140625,\n            \"y\": 1830.7386474609375\n          },\n          {\n            \"x\": 3303.561279296875,\n            \"y\": 1732.2144775390625\n          }\n        ],\n        \"notes\": [],\n        \"routing\": \"orthogonal\"\n      }\n    ]\n  },\n  \"rectData\": {\n    \"list\": [\n      {\n        \"id\": \"rect-1765237540610\",\n        \"x\": 2879.214599609375,\n        \"y\": 159.71981811523438,\n        \"width\": 992.196044921875,\n        \"height\": 538.8650817871094,\n        \"color\": \"#f97316\",\n        \"style\": \"filled\",\n        \"lineStyle\": \"solid\",\n        \"notes\": []\n      },\n      {\n        \"id\": \"rect-1765237681216\",\n        \"x\": 448.3926696777344,\n        \"y\": 1671.651123046875,\n        \"width\": 916.3436584472656,\n        \"height\": 924.27734375,\n        \"color\": \"#c800ff\",\n        \"style\": \"outlined\",\n        \"lineStyle\": \"solid\",\n        \"notes\": []\n      },\n      {\n        \"id\": \"rect-1766437913740\",\n        \"x\": 904.5889892578125,\n        \"y\": 115.40318298339844,\n        \"width\": 110.93878173828125,\n        \"height\": 919.6242218017578,\n        \"color\": \"#5215f9\",\n        \"style\": \"filled\",\n        \"lineStyle\": \"wall\",\n        \"notes\": [],\n        \"borderWidth\": 13\n      },\n      {\n        \"id\": \"rect-1766437935414\",\n        \"x\": 130.93685150146484,\n        \"y\": 1072.3624877929688,\n        \"width\": 872.9131851196289,\n        \"height\": 99.260986328125,\n        \"color\": \"#5215f9\",\n        \"style\": \"filled\",\n        \"lineStyle\": \"wall\",\n        \"notes\": [],\n        \"borderWidth\": 13\n      }\n    ]\n  },\n  \"textData\": {\n    \"list\": [\n      {\n        \"id\": \"text-1765237828167\",\n        \"x\": 3411.458740234375,\n        \"y\": 1390.00439453125,\n        \"content\": \"Double click on desktop\\nor long press on mobile\\nto enter rack canvas view\",\n        \"fontSize\": 46,\n        \"color\": \"#e2e8f0\",\n        \"fontWeight\": \"bold\",\n        \"fontStyle\": \"italic\",\n        \"textAlign\": \"middle\",\n        \"textDecoration\": \"none\",\n        \"bgColor\": \"#000000\",\n        \"bgEnabled\": false,\n        \"opacity\": 1\n      },\n      {\n        \"id\": \"text-1766446595277\",\n        \"x\": 654.3878479003906,\n        \"y\": 1367.7945556640625,\n        \"content\": \"SITE A\",\n        \"fontSize\": 101,\n        \"color\": \"#e2e8f0\",\n        \"fontWeight\": \"normal\",\n        \"fontStyle\": \"normal\",\n        \"textAlign\": \"start\",\n        \"textDecoration\": \"none\",\n        \"bgColor\": \"#000000\",\n        \"bgEnabled\": false,\n        \"opacity\": 1\n      },\n      {\n        \"id\": \"text-1766446610211\",\n        \"x\": 180.63662719726562,\n        \"y\": 1128.822998046875,\n        \"content\": \"SITE A\",\n        \"fontSize\": 101,\n        \"color\": \"#e2e8f0\",\n        \"fontWeight\": \"normal\",\n        \"fontStyle\": \"normal\",\n        \"textAlign\": \"start\",\n        \"textDecoration\": \"none\",\n        \"bgColor\": \"#000000\",\n        \"bgEnabled\": false,\n        \"opacity\": 1\n      },\n      {\n        \"id\": \"text-1766453024797\",\n        \"x\": 968.6458740234375,\n        \"y\": 1028.6621398925781,\n        \"content\": \"SITE A\",\n        \"fontSize\": 101,\n        \"color\": \"#e2e8f0\",\n        \"fontWeight\": \"normal\",\n        \"fontStyle\": \"normal\",\n        \"textAlign\": \"start\",\n        \"textDecoration\": \"none\",\n        \"bgColor\": \"#000000\",\n        \"bgEnabled\": false,\n        \"opacity\": 1,\n        \"rotation\": -89,\n        \"_dragStartX\": 972.46826171875,\n        \"_dragStartY\": 1009.5499572753906\n      },\n      {\n        \"id\": \"text-1766453070975\",\n        \"x\": 613.1589965820312,\n        \"y\": 1139.512939453125,\n        \"content\": \"SITE A\",\n        \"fontSize\": 101,\n        \"color\": \"#e2e8f0\",\n        \"fontWeight\": \"normal\",\n        \"fontStyle\": \"normal\",\n        \"textAlign\": \"start\",\n        \"textDecoration\": \"none\",\n        \"bgColor\": \"#000000\",\n        \"bgEnabled\": false,\n        \"opacity\": 1\n      },\n      {\n        \"id\": \"text-1766453072857\",\n        \"x\": 968.64599609375,\n        \"y\": 474.40818786621094,\n        \"content\": \"SITE A\",\n        \"fontSize\": 101,\n        \"color\": \"#e2e8f0\",\n        \"fontWeight\": \"normal\",\n        \"fontStyle\": \"normal\",\n        \"textAlign\": \"start\",\n        \"textDecoration\": \"none\",\n        \"bgColor\": \"#000000\",\n        \"bgEnabled\": false,\n        \"opacity\": 1,\n        \"rotation\": 269,\n        \"_dragStartX\": 1480.85302734375,\n        \"_dragStartY\": 822.2503356933594\n      },\n      {\n        \"id\": \"text-1766458222326\",\n        \"x\": 3167.812744140625,\n        \"y\": 2190.516357421875,\n        \"content\": \"\",\n        \"fontSize\": 18,\n        \"color\": \"#e2e8f0\",\n        \"fontWeight\": \"normal\",\n        \"fontStyle\": \"normal\",\n        \"textAlign\": \"start\",\n        \"textDecoration\": \"none\",\n        \"bgColor\": \"#000000\",\n        \"bgEnabled\": false,\n        \"opacity\": 1\n      }\n    ]\n  },\n  \"edgeLegend\": {\n    \"#10b981\": \"Trusted Lan\",\n    \"#f59e0b\": \"Secure Lan\",\n    \"#ef4444\": \"DMZ\",\n    \"#475569\": \"Main ISP\",\n    \"#3b82f6\": \"Alternate ISP\",\n    \"#8b5cf6\": \"you can edit me too\",\n    \"#06b6d4\": \"you can edit me too\",\n    \"#a855f7\": \"you can edit me too\",\n    \"#f97316\": \"you can edit me too\",\n    \"#0ea5e9\": \"you can edit me too\",\n    \"#22c55e\": \"you can edit me too\",\n    \"#94a3b8\": \"you can edit me too\",\n    \"#fbbf24\": \"you can edit me too\",\n    \"#38bdf8\": \"you can edit me too\",\n    \"#c800ff\": \"you can edit me too\"\n  },\n  \"nodePositions\": {\n    \"core-router-1\": {\n      \"x\": 3720.166015625,\n      \"y\": 245.9932403564453\n    },\n    \"core-router-2\": {\n      \"x\": 2499.883407638303,\n      \"y\": 329.99503430389154\n    },\n    \"fw-external-1\": {\n      \"x\": 3221.7385182723783,\n      \"y\": 1016.1364499992887\n    },\n    \"fw-external-2\": {\n      \"x\": 1915.5213706410505,\n      \"y\": 224.43528858865443\n    },\n    \"fw-internal\": {\n      \"x\": 1746.9168185079352,\n      \"y\": 477.5300527221864\n    },\n    \"core-switch-1\": {\n      \"x\": 449.39860669455675,\n      \"y\": 384.4578707617695\n    },\n    \"core-switch-2\": {\n      \"x\": 761.1664921394672,\n      \"y\": 180.89283910873155\n    },\n    \"dc-rack-a1\": {\n      \"x\": 783.7017241128451,\n      \"y\": 647.4086870405963\n    },\n    \"dc-rack-a2\": {\n      \"x\": 209.25701628255229,\n      \"y\": 228.01593190351014\n    },\n    \"dc-rack-b1\": {\n      \"x\": 3184.3186625759854,\n      \"y\": 1627.4495531027196\n    },\n    \"dc-rack-b2\": {\n      \"x\": 245.37065918741246,\n      \"y\": 499.6191264194081\n    },\n    \"dmz-rack\": {\n      \"x\": 2176.4105289561007,\n      \"y\": 610.8312056412005\n    },\n    \"mgmt-rack\": {\n      \"x\": 1601.2987201807314,\n      \"y\": 1281.4753424975324\n    },\n    \"esxi-host-01\": {\n      \"x\": 2162.2166789540615,\n      \"y\": 2608.110619289529\n    },\n    \"esxi-host-02\": {\n      \"x\": 2205.94717202368,\n      \"y\": 2689.67539624076\n    },\n    \"esxi-host-03\": {\n      \"x\": 2154.6015436939074,\n      \"y\": 2771.203009774913\n    },\n    \"esxi-host-04\": {\n      \"x\": 2195.986926025096,\n      \"y\": 2845\n    },\n    \"tor-switch-a1\": {\n      \"x\": 2146.8943639962963,\n      \"y\": 2845\n    },\n    \"esxi-host-05\": {\n      \"x\": 2185.9099961569727,\n      \"y\": 2845\n    },\n    \"esxi-host-06\": {\n      \"x\": 2139.099728450725,\n      \"y\": 2845\n    },\n    \"esxi-host-07\": {\n      \"x\": 2175.7223818764883,\n      \"y\": 2845\n    },\n    \"esxi-host-08\": {\n      \"x\": 2131.2222777148922,\n      \"y\": 2845\n    },\n    \"tor-switch-a2\": {\n      \"x\": 2165.4301485385085,\n      \"y\": 2845\n    },\n    \"san-primary\": {\n      \"x\": 2123.2667017518106,\n      \"y\": 2845\n    },\n    \"san-secondary\": {\n      \"x\": 2155.0394237844876,\n      \"y\": 2845\n    },\n    \"fc-switch-1\": {\n      \"x\": 2115.2377370375634,\n      \"y\": 2845\n    },\n    \"fc-switch-2\": {\n      \"x\": 2144.5563938942755,\n      \"y\": 2845\n    },\n    \"backup-server-1\": {\n      \"x\": 2107.1401637413705,\n      \"y\": 2845\n    },\n    \"backup-server-2\": {\n      \"x\": 2133.987300103025,\n      \"y\": 2845\n    },\n    \"tape-library\": {\n      \"x\": 2098.9788028796397,\n      \"y\": 2845\n    },\n    \"tor-switch-b1\": {\n      \"x\": 2123.338434885373,\n      \"y\": 2845\n    },\n    \"tor-switch-b2\": {\n      \"x\": 2090.7585134456995,\n      \"y\": 2845\n    },\n    \"web-server-1\": {\n      \"x\": 2112.6161382091163,\n      \"y\": 2845\n    },\n    \"web-server-2\": {\n      \"x\": 2082.484189516922,\n      \"y\": 2845\n    },\n    \"waf-1\": {\n      \"x\": 2101.826793760617,\n      \"y\": 2845\n    },\n    \"load-balancer-dmz\": {\n      \"x\": 2074.1607573409574,\n      \"y\": 2845\n    },\n    \"mail-gateway\": {\n      \"x\": 2090.97682514417,\n      \"y\": 2845\n    },\n    \"dns-external-1\": {\n      \"x\": 2065.7931724028163,\n      \"y\": 2845\n    },\n    \"dns-external-2\": {\n      \"x\": 2080.0726920576153,\n      \"y\": 2845\n    },\n    \"vcenter\": {\n      \"x\": 2057.3864164745437,\n      \"y\": 2845\n    },\n    \"nsx-manager\": {\n      \"x\": 2069.1208864464534,\n      \"y\": 2845\n    },\n    \"siem-server\": {\n      \"x\": 2048.945494649244,\n      \"y\": 2845\n    },\n    \"nms-server\": {\n      \"x\": 2058.1279286387635,\n      \"y\": 2845\n    },\n    \"jump-server\": {\n      \"x\": 2040.4754323612206,\n      \"y\": 2845\n    },\n    \"ipam-server\": {\n      \"x\": 2047.1003634632284,\n      \"y\": 2845\n    },\n    \"wlc-primary\": {\n      \"x\": 1575.9723612611924,\n      \"y\": 2306.135986328125\n    },\n    \"wlc-secondary\": {\n      \"x\": 1468.1361870166274,\n      \"y\": 1563.733642578125\n    },\n    \"mobile-zone-hq\": {\n      \"x\": 2354.901177346808,\n      \"y\": 2806.0078125\n    },\n    \"mobile-zone-guest\": {\n      \"x\": 2307.6605605284435,\n      \"y\": 2611.047119140625\n    },\n    \"mobile-zone-iot\": {\n      \"x\": 2229.397686389302,\n      \"y\": 2299.110107421875\n    },\n    \"branch-router-ny\": {\n      \"x\": 3151.903101363964,\n      \"y\": 633.6580810546875\n    },\n    \"branch-router-la\": {\n      \"x\": 3083.8876194705945,\n      \"y\": 506.90625\n    },\n    \"branch-router-chi\": {\n      \"x\": 3355.02409980103,\n      \"y\": 393.1805725097656\n    },\n    \"branch-router-lon\": {\n      \"x\": 3113.609823320121,\n      \"y\": 260.4093322753906\n    },\n    \"branch-router-tokyo\": {\n      \"x\": 3699.3234994733834,\n      \"y\": 471.4241027832031\n    },\n    \"cloud-aws\": {\n      \"x\": 3436.528122523513,\n      \"y\": 545.9614868164062\n    },\n    \"cloud-azure\": {\n      \"x\": 2592.566210818907,\n      \"y\": 2724.068115234375\n    },\n    \"cloud-gcp\": {\n      \"x\": 2827.3183770424234,\n      \"y\": 2731.397216796875\n    },\n    \"isp-primary\": {\n      \"x\": 3712.192068081962,\n      \"y\": 615.64990234375\n    },\n    \"isp-secondary\": {\n      \"x\": 3253.9473366098055,\n      \"y\": 1993.2629089355469\n    },\n    \"dc-internal-1\": {\n      \"x\": 1958.4243458877936,\n      \"y\": 2845\n    },\n    \"dc-internal-2\": {\n      \"x\": 1963.768951182132,\n      \"y\": 2845\n    },\n    \"app-server-1\": {\n      \"x\": 1947.3819379304134,\n      \"y\": 2845\n    },\n    \"app-server-2\": {\n      \"x\": 1955.2862087394126,\n      \"y\": 2845\n    },\n    \"db-server-1\": {\n      \"x\": 1936.3708569559828,\n      \"y\": 2845\n    },\n    \"db-server-2\": {\n      \"x\": 1946.8300873488822,\n      \"y\": 2845\n    },\n    \"k8s-master-1\": {\n      \"x\": 1925.397658583093,\n      \"y\": 2845\n    },\n    \"k8s-master-2\": {\n      \"x\": 1938.405621494142,\n      \"y\": 2845\n    },\n    \"k8s-master-3\": {\n      \"x\": 1914.4688758763386,\n      \"y\": 2845\n    },\n    \"k8s-worker-1\": {\n      \"x\": 1930.017826812177,\n      \"y\": 2845\n    },\n    \"k8s-worker-2\": {\n      \"x\": 1903.5910154567553,\n      \"y\": 2845\n    },\n    \"k8s-worker-3\": {\n      \"x\": 1921.6716971072178,\n      \"y\": 2845\n    },\n    \"k8s-worker-4\": {\n      \"x\": 1892.7705536280016,\n      \"y\": 2845\n    },\n    \"proxy-server-1\": {\n      \"x\": 1806.1152433697903,\n      \"y\": 653.7529296875\n    },\n    \"proxy-server-2\": {\n      \"x\": 2937.4207928721535,\n      \"y\": 2628.7880859375\n    },\n    \"vpn-concentrator\": {\n      \"x\": 3642.252088474593,\n      \"y\": 946.7255249023438\n    },\n    \"nac-server\": {\n      \"x\": 1153.2626148502184,\n      \"y\": 1172.1895751953125\n    },\n    \"print-server\": {\n      \"x\": 1896.9328460745962,\n      \"y\": 2845\n    },\n    \"file-server\": {\n      \"x\": 1860.7177871362182,\n      \"y\": 2845\n    },\n    \"ca-server\": {\n      \"x\": 1888.8027739274805,\n      \"y\": 2845\n    },\n    \"sccm-server\": {\n      \"x\": 1850.1909418511675,\n      \"y\": 2845\n    },\n    \"voip-cluster\": {\n      \"x\": 1777.038465328039,\n      \"y\": 1616.8961181640625\n    },\n    \"video-conf\": {\n      \"x\": 1993.8373941679588,\n      \"y\": 2244.936309814453\n    },\n    \"security-cameras\": {\n      \"x\": 1674.413336949044,\n      \"y\": 2046.0380859375\n    },\n    \"nvr-cluster\": {\n      \"x\": 1829.4110389706402,\n      \"y\": 2845\n    },\n    \"dev-server-1\": {\n      \"x\": 2800.5894350649614,\n      \"y\": 1175.623291015625\n    },\n    \"dev-server-2\": {\n      \"x\": 1945.0822182484326,\n      \"y\": 1164.5184783935547\n    },\n    \"test-environment\": {\n      \"x\": 2932.0863047891075,\n      \"y\": 862.4592895507812\n    },\n    \"erp-system\": {\n      \"x\": 789.9880103985649,\n      \"y\": 473.7113342285156\n    },\n    \"crm-system\": {\n      \"x\": 3514.6003232048542,\n      \"y\": 1137.7720947265625\n    },\n    \"endpoint-1000\": {\n      \"x\": 991.6812012057328,\n      \"y\": 2284.42236328125\n    },\n    \"dist-switch-floor1\": {\n      \"x\": 654.2091033261356,\n      \"y\": 2020.0086669921875\n    },\n    \"dist-switch-floor2\": {\n      \"x\": 853.8845527112826,\n      \"y\": 1843.2872314453125\n    },\n    \"dist-switch-floor3\": {\n      \"x\": 1899.4353951584517,\n      \"y\": 1456.5068359375\n    },\n    \"dist-switch-floor4\": {\n      \"x\": 488.5289313756234,\n      \"y\": 181.47256469726562\n    },\n    \"ap-floor1-zone1\": {\n      \"x\": 1140.16846970184,\n      \"y\": 2070.2916259765625\n    },\n    \"ap-floor2-zone1\": {\n      \"x\": 688.1952143592268,\n      \"y\": 2384.4775390625\n    },\n    \"ap-floor3-zone1\": {\n      \"x\": 2145.3803027919676,\n      \"y\": 1890.2816162109375\n    },\n    \"ap-floor4-zone1\": {\n      \"x\": 517.646146409649,\n      \"y\": 565.59716796875\n    },\n    \"ups-dc-1\": {\n      \"x\": 771.1406786539856,\n      \"y\": 295.9266662597656\n    },\n    \"ups-dc-2\": {\n      \"x\": 216.2410855890687,\n      \"y\": 330.3345947265625\n    },\n    \"pdu-rack-a1\": {\n      \"x\": 1804.774444371901,\n      \"y\": 2845\n    },\n    \"pdu-rack-a2\": {\n      \"x\": 1741.6184034693686,\n      \"y\": 2845\n    },\n    \"cooling-1\": {\n      \"x\": 245.7080801919958,\n      \"y\": 626.1914672851562\n    },\n    \"cooling-2\": {\n      \"x\": 1603.293611085831,\n      \"y\": 981.0621185302734\n    },\n    \"camera-a\": {\n      \"x\": 166.57075412676295,\n      \"y\": 145\n    },\n    \"camera-a-copy\": {\n      \"x\": 1040.653076171875,\n      \"y\": 738.42822265625\n    }\n  },\n  \"nodeSizes\": {\n    \"isp-secondary\": 139,\n    \"test-environment\": 121,\n    \"dev-server-1\": 128,\n    \"core-router-2\": 120,\n    \"camera-a\": 45,\n    \"camera-a-copy\": 45\n  },\n  \"nodeStyles\": {\n    \"dc-rack-b2\": {\n      \"all\": {\n        \"circleColor\": \"#ff0000\"\n      }\n    },\n    \"dc-rack-a1\": {\n      \"all\": {\n        \"circleColor\": \"#ff0000\"\n      }\n    },\n    \"dc-rack-b1\": {\n      \"all\": {\n        \"circleColor\": \"#ff0000\",\n        \"titleSize\": 59\n      }\n    },\n    \"isp-secondary\": {\n      \"all\": {\n        \"icon\": {\n          \"library\": \"selfhst\",\n          \"name\": \"alist\"\n        },\n        \"circleColor\": \"#4d2c58\",\n        \"circleBorder\": \"#000000\",\n        \"titleColor\": \"#006eff\"\n      }\n    },\n    \"core-router-2\": {\n      \"all\": {\n        \"icon\": {\n          \"library\": \"selfhst\",\n          \"name\": \"actual-budget\"\n        },\n        \"pingOffsetX\": -15,\n        \"pingOffsetY\": -38\n      }\n    },\n    \"fw-external-1\": {\n      \"all\": {\n        \"icon\": {\n          \"library\": \"selfhst\",\n          \"name\": \"anonaddy\"\n        }\n      }\n    },\n    \"cloud-aws\": {\n      \"all\": {\n        \"icon\": {\n          \"library\": \"selfhst\",\n          \"name\": \"ansible\"\n        }\n      }\n    },\n    \"isp-primary\": {\n      \"all\": {\n        \"icon\": {\n          \"library\": \"selfhst\",\n          \"name\": \"wikidocs\"\n        }\n      }\n    },\n    \"branch-router-tokyo\": {\n      \"all\": {\n        \"icon\": {\n          \"library\": \"selfhst\",\n          \"name\": \"adguard-home\"\n        }\n      }\n    },\n    \"core-router-1\": {\n      \"all\": {\n        \"icon\": {\n          \"library\": \"selfhst\",\n          \"name\": \"borg\"\n        }\n      }\n    },\n    \"test-environment\": {\n      \"all\": {\n        \"icon\": {\n          \"library\": \"simple\",\n          \"name\": \"apple\"\n        }\n      }\n    },\n    \"dev-server-1\": {\n      \"all\": {\n        \"icon\": {\n          \"library\": \"simple\",\n          \"name\": \"amazonwebservices\"\n        }\n      }\n    }\n  },\n  \"page\": {\n    \"title\": \"The One File Corporate\",\n    \"background\": \"\",\n    \"topbarBg\": \"rgba(9, 12, 20, 0.9)\",\n    \"topbarBorder\": \"#1f2533\",\n    \"panel\": \"#0b0e13\",\n    \"panelAlt\": \"#10141b\",\n    \"accent\": \"#4fd1c5\",\n    \"sidebarBg\": \"#10141b\",\n    \"btnBg\": \"#0b0e13\",\n    \"btnText\": \"#e2e8f0\",\n    \"tagFill\": \"#1e293b\",\n    \"tagText\": \"#e2e8f0\",\n    \"tagBorder\": \"#475569\",\n    \"inputBg\": \"#0b0e13\",\n    \"inputText\": \"#e2e8f0\",\n    \"inputBorder\": \"#1f2937\",\n    \"inputFont\": \"Inter, system-ui, sans-serif\",\n    \"inputFontSize\": 14,\n    \"toolbarBg\": \"#0f172a\",\n    \"toolbarBorder\": \"#1f2937\",\n    \"toolbarText\": \"#94a3b8\",\n    \"toolbarBtnBg\": \"#0b0e13\",\n    \"toolbarBtnText\": \"#e2e8f0\",\n    \"minimapDots\": \"#94a3b8\",\n    \"canvasHintEnabled\": true,\n    \"canvasHintText\": \"\",\n    \"canvasHintBg\": \"#0f172a\",\n    \"canvasHintColor\": \"#94a3b8\",\n    \"danger\": \"#f56565\",\n    \"textMain\": \"#e2e8f0\",\n    \"textSoft\": \"#94a3b8\",\n    \"topbarHeight\": 103,\n    \"sidebarWidth\": 350,\n    \"mobileFooterHeight\": 40,\n    \"sidebarCollapsed\": false,\n    \"nodeFill\": \"#1e293b\",\n    \"nodeStroke\": \"#475569\",\n    \"nodeTitle\": \"#e2e8f0\",\n    \"nodeSub\": \"#94a3b8\",\n    \"nodeTitleSize\": 41,\n    \"nodeSubSize\": 27,\n    \"nodeFont\": \"monospace\",\n    \"defaultEdge\": \"#475569\",\n    \"selectionHandle\": \"#f59e0b\",\n    \"selectionHandleSize\": 8,\n    \"groupIndicator\": \"#4fd1c5\",\n    \"canvasGradientTop\": \"#1e2532\",\n    \"canvasGradientBottom\": \"#050608\",\n    \"canvasBorder\": \"#475569\",\n    \"canvasGrid\": \"#475569\",\n    \"canvasGridSize\": 50,\n    \"canvasGridEnabled\": true,\n    \"rackFrameFill\": \"#0f172a\",\n    \"rackGridEnabled\": true,\n    \"rackFrameStroke\": \"#4fd1c5\",\n    \"rackLineColor\": \"#475569\",\n    \"rackTextColor\": \"#4fd1c5\",\n    \"viewOnly\": false,\n    \"defaultEdgeRouting\": \"orthogonal\",\n    \"animateConnections\": false,\n    \"animationStyle\": \"arrows\",\n    \"animationDirection\": \"all\",\n    \"animationSpeed\": 4,\n    \"autoPingEnabled\": false,\n    \"autoPingInterval\": 30\n  },\n  \"canvas\": {\n    \"zoom\": 0.8752084596859406,\n    \"panX\": -191.26917538063572,\n    \"panY\": -261.51832729948296\n  },\n  \"savedTopologyView\": {\n    \"zoom\": 0.9111098220009686,\n    \"panX\": -207.26076103009882,\n    \"panY\": -201.83911371533202\n  },\n  \"documentTabs\": [\n    {\n      \"id\": \"main\",\n      \"name\": \"Corporate Site B\",\n      \"nodes\": {\n        \"core-router-1\": {\n          \"shape\": \"router\",\n          \"name\": \"Core Router 1\",\n          \"ip\": \"10.0.0.1\",\n          \"role\": \"Core Routing\",\n          \"tags\": [\n            \"core\",\n            \"tier-1\",\n            \"redundant\"\n          ],\n          \"notes\": [\n            \"Primary core router\",\n            \"BGP peering enabled\"\n          ],\n          \"mac\": \"00:1A:2B:3C:4D:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"core-router-2\": {\n          \"shape\": \"router\",\n          \"name\": \"Core Router 2\",\n          \"ip\": \"10.0.0.2\",\n          \"role\": \"Core Routing\",\n          \"tags\": [\n            \"core\",\n            \"tier-1\",\n            \"redundant\"\n          ],\n          \"notes\": [\n            \"Secondary core router\",\n            \"HSRP standby\"\n          ],\n          \"mac\": \"00:1A:2B:3C:4D:02\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null,\n          \"ping\": {\n            \"enabled\": true,\n            \"protocol\": \"custom\",\n            \"customUrl\": \"https://google.com\",\n            \"timeout\": 3000,\n            \"status\": \"online\",\n            \"lastCheck\": \"2025-12-09T00:15:04.343Z\"\n          }\n        },\n        \"fw-external-1\": {\n          \"shape\": \"firewall\",\n          \"name\": \"External FW 1\",\n          \"ip\": \"10.0.1.1\",\n          \"role\": \"Perimeter Security\",\n          \"tags\": [\n            \"security\",\n            \"perimeter\",\n            \"ha-pair\"\n          ],\n          \"notes\": [\n            \"Palo Alto PA-5250\",\n            \"Active node\"\n          ],\n          \"mac\": \"00:1A:2B:3C:4D:10\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"fw-external-2\": {\n          \"shape\": \"firewall\",\n          \"name\": \"External FW 2\",\n          \"ip\": \"10.0.1.2\",\n          \"role\": \"Perimeter Security\",\n          \"tags\": [\n            \"security\",\n            \"perimeter\",\n            \"ha-pair\"\n          ],\n          \"notes\": [\n            \"Palo Alto PA-5250\",\n            \"Passive node\"\n          ],\n          \"mac\": \"00:1A:2B:3C:4D:11\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"fw-internal\": {\n          \"shape\": \"firewall\",\n          \"name\": \"Internal FW\",\n          \"ip\": \"10.0.2.1\",\n          \"role\": \"Internal Segmentation\",\n          \"tags\": [\n            \"security\",\n            \"internal\"\n          ],\n          \"notes\": [\n            \"East-West traffic inspection\"\n          ],\n          \"mac\": \"00:1A:2B:3C:4D:12\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"core-switch-1\": {\n          \"shape\": \"switch\",\n          \"name\": \"Core Switch 1\",\n          \"ip\": \"10.0.10.1\",\n          \"role\": \"Core Switching\",\n          \"tags\": [\n            \"core\",\n            \"layer3\",\n            \"redundant\"\n          ],\n          \"notes\": [\n            \"Cisco Nexus 9000\",\n            \"VPC Domain 1\"\n          ],\n          \"mac\": \"00:1A:2B:3C:4D:20\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"core-switch-2\": {\n          \"shape\": \"switch\",\n          \"name\": \"Core Switch 2\",\n          \"ip\": \"10.0.10.2\",\n          \"role\": \"Core Switching\",\n          \"tags\": [\n            \"core\",\n            \"layer3\",\n            \"redundant\"\n          ],\n          \"notes\": [\n            \"Cisco Nexus 9000\",\n            \"VPC Domain 1\"\n          ],\n          \"mac\": \"00:1A:2B:3C:4D:21\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dc-rack-a1\": {\n          \"shape\": \"server\",\n          \"name\": \"DC Rack A1\",\n          \"ip\": \"10.10.0.0/24\",\n          \"role\": \"Data Center Rack\",\n          \"tags\": [\n            \"datacenter\",\n            \"row-a\",\n            \"production\"\n          ],\n          \"notes\": [\n            \"Row A, Position 1\",\n            \"Primary compute\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": true,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dc-rack-a2\": {\n          \"shape\": \"server\",\n          \"name\": \"DC Rack A2\",\n          \"ip\": \"10.10.1.0/24\",\n          \"role\": \"Data Center Rack\",\n          \"tags\": [\n            \"datacenter\",\n            \"row-a\",\n            \"production\"\n          ],\n          \"notes\": [\n            \"Row A, Position 2\",\n            \"Primary compute\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": true,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dc-rack-b1\": {\n          \"shape\": \"server\",\n          \"name\": \"DC Rack B1\",\n          \"ip\": \"10.10.2.0/24\",\n          \"role\": \"Data Center Rack\",\n          \"tags\": [\n            \"datacenter\",\n            \"row-b\",\n            \"storage\"\n          ],\n          \"notes\": [\n            \"Row B, Position 1\",\n            \"Storage systems\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": true,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dc-rack-b2\": {\n          \"shape\": \"server\",\n          \"name\": \"DC Rack B2\",\n          \"ip\": \"10.10.3.0/24\",\n          \"role\": \"Data Center Rack\",\n          \"tags\": [\n            \"datacenter\",\n            \"row-b\",\n            \"storage\"\n          ],\n          \"notes\": [\n            \"Row B, Position 2\",\n            \"Storage systems\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": true,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dmz-rack\": {\n          \"shape\": \"server\",\n          \"name\": \"DMZ Rack\",\n          \"ip\": \"172.16.0.0/24\",\n          \"role\": \"DMZ Infrastructure\",\n          \"tags\": [\n            \"dmz\",\n            \"security\",\n            \"public-facing\",\n            {\n              \"type\": \"icon\",\n              \"library\": \"selfhst\",\n              \"name\": \"booklogr\"\n            },\n            {\n              \"type\": \"icon\",\n              \"library\": \"simple\",\n              \"name\": \"gmail\"\n            }\n          ],\n          \"notes\": [\n            \"Isolated DMZ zone\",\n            \"Public-facing services\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": true,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"mgmt-rack\": {\n          \"shape\": \"server\",\n          \"name\": \"Management Rack\",\n          \"ip\": \"192.168.100.0/24\",\n          \"role\": \"Management Infrastructure\",\n          \"tags\": [\n            \"management\",\n            \"oob\",\n            \"noc\"\n          ],\n          \"notes\": [\n            \"Out-of-band management\",\n            \"NOC equipment\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"logical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": true,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"esxi-host-01\": {\n          \"shape\": \"server\",\n          \"name\": \"ESXi Host 01\",\n          \"ip\": \"10.10.0.11\",\n          \"role\": \"Hypervisor\",\n          \"tags\": [\n            \"vmware\",\n            \"compute\",\n            \"cluster-a\"\n          ],\n          \"notes\": [\n            \"Dell PowerEdge R750\",\n            \"512GB RAM\",\n            \"vSphere 8.0\"\n          ],\n          \"mac\": \"00:50:56:AA:01:01\",\n          \"rackUnit\": 38,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"esxi-host-02\": {\n          \"shape\": \"server\",\n          \"name\": \"ESXi Host 02\",\n          \"ip\": \"10.10.0.12\",\n          \"role\": \"Hypervisor\",\n          \"tags\": [\n            \"vmware\",\n            \"compute\",\n            \"cluster-a\"\n          ],\n          \"notes\": [\n            \"Dell PowerEdge R750\",\n            \"512GB RAM\",\n            \"vSphere 8.0\"\n          ],\n          \"mac\": \"00:50:56:AA:01:02\",\n          \"rackUnit\": 35,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"esxi-host-03\": {\n          \"shape\": \"server\",\n          \"name\": \"ESXi Host 03\",\n          \"ip\": \"10.10.0.13\",\n          \"role\": \"Hypervisor\",\n          \"tags\": [\n            \"vmware\",\n            \"compute\",\n            \"cluster-a\"\n          ],\n          \"notes\": [\n            \"Dell PowerEdge R750\",\n            \"512GB RAM\",\n            \"vSphere 8.0\"\n          ],\n          \"mac\": \"00:50:56:AA:01:03\",\n          \"rackUnit\": 32,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"esxi-host-04\": {\n          \"shape\": \"server\",\n          \"name\": \"ESXi Host 04\",\n          \"ip\": \"10.10.0.14\",\n          \"role\": \"Hypervisor\",\n          \"tags\": [\n            \"vmware\",\n            \"compute\",\n            \"cluster-a\"\n          ],\n          \"notes\": [\n            \"Dell PowerEdge R750\",\n            \"512GB RAM\",\n            \"vSphere 8.0\"\n          ],\n          \"mac\": \"00:50:56:AA:01:04\",\n          \"rackUnit\": 29,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"tor-switch-a1\": {\n          \"shape\": \"switch\",\n          \"name\": \"ToR Switch A1\",\n          \"ip\": \"10.10.0.1\",\n          \"role\": \"Top of Rack\",\n          \"tags\": [\n            \"tor\",\n            \"access\",\n            \"rack-a1\"\n          ],\n          \"notes\": [\n            \"Cisco Nexus 93180YC-FX\",\n            \"48x25G ports\"\n          ],\n          \"mac\": \"00:1A:2B:3C:5D:01\",\n          \"rackUnit\": 42,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"esxi-host-05\": {\n          \"shape\": \"server\",\n          \"name\": \"ESXi Host 05\",\n          \"ip\": \"10.10.1.11\",\n          \"role\": \"Hypervisor\",\n          \"tags\": [\n            \"vmware\",\n            \"compute\",\n            \"cluster-b\"\n          ],\n          \"notes\": [\n            \"Dell PowerEdge R750\",\n            \"768GB RAM\",\n            \"vSphere 8.0\"\n          ],\n          \"mac\": \"00:50:56:AA:02:01\",\n          \"rackUnit\": 38,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"esxi-host-06\": {\n          \"shape\": \"server\",\n          \"name\": \"ESXi Host 06\",\n          \"ip\": \"10.10.1.12\",\n          \"role\": \"Hypervisor\",\n          \"tags\": [\n            \"vmware\",\n            \"compute\",\n            \"cluster-b\"\n          ],\n          \"notes\": [\n            \"Dell PowerEdge R750\",\n            \"768GB RAM\",\n            \"vSphere 8.0\"\n          ],\n          \"mac\": \"00:50:56:AA:02:02\",\n          \"rackUnit\": 35,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"esxi-host-07\": {\n          \"shape\": \"server\",\n          \"name\": \"ESXi Host 07\",\n          \"ip\": \"10.10.1.13\",\n          \"role\": \"Hypervisor\",\n          \"tags\": [\n            \"vmware\",\n            \"compute\",\n            \"cluster-b\"\n          ],\n          \"notes\": [\n            \"Dell PowerEdge R750\",\n            \"768GB RAM\",\n            \"vSphere 8.0\"\n          ],\n          \"mac\": \"00:50:56:AA:02:03\",\n          \"rackUnit\": 32,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"esxi-host-08\": {\n          \"shape\": \"server\",\n          \"name\": \"ESXi Host 08\",\n          \"ip\": \"10.10.1.14\",\n          \"role\": \"Hypervisor\",\n          \"tags\": [\n            \"vmware\",\n            \"compute\",\n            \"cluster-b\"\n          ],\n          \"notes\": [\n            \"Dell PowerEdge R750\",\n            \"768GB RAM\",\n            \"vSphere 8.0\"\n          ],\n          \"mac\": \"00:50:56:AA:02:04\",\n          \"rackUnit\": 29,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"tor-switch-a2\": {\n          \"shape\": \"switch\",\n          \"name\": \"ToR Switch A2\",\n          \"ip\": \"10.10.1.1\",\n          \"role\": \"Top of Rack\",\n          \"tags\": [\n            \"tor\",\n            \"access\",\n            \"rack-a2\"\n          ],\n          \"notes\": [\n            \"Cisco Nexus 93180YC-FX\",\n            \"48x25G ports\"\n          ],\n          \"mac\": \"00:1A:2B:3C:5D:02\",\n          \"rackUnit\": 42,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"san-primary\": {\n          \"shape\": \"database\",\n          \"name\": \"SAN Primary\",\n          \"ip\": \"10.10.2.10\",\n          \"role\": \"Primary Storage\",\n          \"tags\": [\n            \"storage\",\n            \"san\",\n            \"netapp\"\n          ],\n          \"notes\": [\n            \"NetApp AFF A400\",\n            \"500TB Raw\",\n            \"FC 32Gb\"\n          ],\n          \"mac\": \"00:A0:98:AA:01:01\",\n          \"rackUnit\": 36,\n          \"uHeight\": \"6\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-b1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"san-secondary\": {\n          \"shape\": \"database\",\n          \"name\": \"SAN Secondary\",\n          \"ip\": \"10.10.2.11\",\n          \"role\": \"Secondary Storage\",\n          \"tags\": [\n            \"storage\",\n            \"san\",\n            \"netapp\"\n          ],\n          \"notes\": [\n            \"NetApp AFF A400\",\n            \"500TB Raw\",\n            \"FC 32Gb\"\n          ],\n          \"mac\": \"00:A0:98:AA:01:02\",\n          \"rackUnit\": 28,\n          \"uHeight\": \"6\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-b1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"fc-switch-1\": {\n          \"shape\": \"switch\",\n          \"name\": \"FC Switch 1\",\n          \"ip\": \"10.10.2.1\",\n          \"role\": \"Fibre Channel\",\n          \"tags\": [\n            \"storage\",\n            \"fc\",\n            \"fabric-a\"\n          ],\n          \"notes\": [\n            \"Brocade G620\",\n            \"Fabric A\"\n          ],\n          \"mac\": \"00:1A:2B:FC:01:01\",\n          \"rackUnit\": 42,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-b1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"fc-switch-2\": {\n          \"shape\": \"switch\",\n          \"name\": \"FC Switch 2\",\n          \"ip\": \"10.10.2.2\",\n          \"role\": \"Fibre Channel\",\n          \"tags\": [\n            \"storage\",\n            \"fc\",\n            \"fabric-b\"\n          ],\n          \"notes\": [\n            \"Brocade G620\",\n            \"Fabric B\"\n          ],\n          \"mac\": \"00:1A:2B:FC:01:02\",\n          \"rackUnit\": 41,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-b1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"backup-server-1\": {\n          \"shape\": \"server\",\n          \"name\": \"Backup Server 1\",\n          \"ip\": \"10.10.3.10\",\n          \"role\": \"Backup Infrastructure\",\n          \"tags\": [\n            \"backup\",\n            \"veeam\",\n            \"protection\"\n          ],\n          \"notes\": [\n            \"Veeam Backup Server\",\n            \"Dell R740xd\",\n            \"200TB\"\n          ],\n          \"mac\": \"00:50:56:BB:01:01\",\n          \"rackUnit\": 36,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-b2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"backup-server-2\": {\n          \"shape\": \"server\",\n          \"name\": \"Backup Server 2\",\n          \"ip\": \"10.10.3.11\",\n          \"role\": \"Backup Infrastructure\",\n          \"tags\": [\n            \"backup\",\n            \"veeam\",\n            \"protection\"\n          ],\n          \"notes\": [\n            \"Veeam Backup Server\",\n            \"Dell R740xd\",\n            \"200TB\"\n          ],\n          \"mac\": \"00:50:56:BB:01:02\",\n          \"rackUnit\": 33,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-b2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"tape-library\": {\n          \"shape\": \"database\",\n          \"name\": \"Tape Library\",\n          \"ip\": \"10.10.3.20\",\n          \"role\": \"Archival Storage\",\n          \"tags\": [\n            \"backup\",\n            \"tape\",\n            \"lto9\"\n          ],\n          \"notes\": [\n            \"IBM TS4500\",\n            \"LTO-9\",\n            \"Long-term archive\"\n          ],\n          \"mac\": \"00:50:56:BB:02:01\",\n          \"rackUnit\": 20,\n          \"uHeight\": \"10\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-b2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"tor-switch-b1\": {\n          \"shape\": \"switch\",\n          \"name\": \"ToR Switch B1\",\n          \"ip\": \"10.10.2.3\",\n          \"role\": \"Top of Rack\",\n          \"tags\": [\n            \"tor\",\n            \"access\",\n            \"rack-b1\"\n          ],\n          \"notes\": [\n            \"Cisco Nexus 93180YC-FX\"\n          ],\n          \"mac\": \"00:1A:2B:3C:5D:03\",\n          \"rackUnit\": 40,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-b1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"tor-switch-b2\": {\n          \"shape\": \"switch\",\n          \"name\": \"ToR Switch B2\",\n          \"ip\": \"10.10.3.1\",\n          \"role\": \"Top of Rack\",\n          \"tags\": [\n            \"tor\",\n            \"access\",\n            \"rack-b2\"\n          ],\n          \"notes\": [\n            \"Cisco Nexus 93180YC-FX\"\n          ],\n          \"mac\": \"00:1A:2B:3C:5D:04\",\n          \"rackUnit\": 42,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-b2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"web-server-1\": {\n          \"shape\": \"server\",\n          \"name\": \"Web Server 1\",\n          \"ip\": \"172.16.0.11\",\n          \"role\": \"Web Frontend\",\n          \"tags\": [\n            \"dmz\",\n            \"web\",\n            \"nginx\"\n          ],\n          \"notes\": [\n            \"NGINX reverse proxy\",\n            \"Public facing\"\n          ],\n          \"mac\": \"00:50:56:CC:01:01\",\n          \"rackUnit\": 20,\n          \"uHeight\": \"1\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"dmz-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"web-server-2\": {\n          \"shape\": \"server\",\n          \"name\": \"Web Server 2\",\n          \"ip\": \"172.16.0.12\",\n          \"role\": \"Web Frontend\",\n          \"tags\": [\n            \"dmz\",\n            \"web\",\n            \"nginx\"\n          ],\n          \"notes\": [\n            \"NGINX reverse proxy\",\n            \"Public facing\"\n          ],\n          \"mac\": \"00:50:56:CC:01:02\",\n          \"rackUnit\": 18,\n          \"uHeight\": \"1\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"dmz-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"waf-1\": {\n          \"shape\": \"firewall\",\n          \"name\": \"WAF Appliance\",\n          \"ip\": \"172.16.0.5\",\n          \"role\": \"Web Application Firewall\",\n          \"tags\": [\n            \"dmz\",\n            \"security\",\n            \"waf\"\n          ],\n          \"notes\": [\n            \"F5 BIG-IP ASM\",\n            \"OWASP protection\"\n          ],\n          \"mac\": \"00:50:56:CC:02:01\",\n          \"rackUnit\": 22,\n          \"uHeight\": \"2\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"dmz-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"load-balancer-dmz\": {\n          \"shape\": \"switch\",\n          \"name\": \"DMZ Load Balancer\",\n          \"ip\": \"172.16.0.3\",\n          \"role\": \"Load Balancing\",\n          \"tags\": [\n            \"dmz\",\n            \"lb\",\n            \"f5\"\n          ],\n          \"notes\": [\n            \"F5 BIG-IP LTM\",\n            \"VIP: 172.16.0.100\"\n          ],\n          \"mac\": \"00:50:56:CC:03:01\",\n          \"rackUnit\": 16,\n          \"uHeight\": \"2\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"dmz-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"mail-gateway\": {\n          \"shape\": \"server\",\n          \"name\": \"Mail Gateway\",\n          \"ip\": \"172.16.0.25\",\n          \"role\": \"Email Security\",\n          \"tags\": [\n            \"dmz\",\n            \"email\",\n            \"security\"\n          ],\n          \"notes\": [\n            \"Proofpoint Email Gateway\",\n            \"Spam/malware filtering\"\n          ],\n          \"mac\": \"00:50:56:CC:04:01\",\n          \"rackUnit\": 14,\n          \"uHeight\": \"1\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"dmz-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dns-external-1\": {\n          \"shape\": \"circle\",\n          \"name\": \"External DNS 1\",\n          \"ip\": \"172.16.0.53\",\n          \"role\": \"External DNS\",\n          \"tags\": [\n            \"dmz\",\n            \"dns\",\n            \"public\"\n          ],\n          \"notes\": [\n            \"BIND DNS\",\n            \"Authoritative for corp.com\"\n          ],\n          \"mac\": \"00:50:56:CC:05:01\",\n          \"rackUnit\": 12,\n          \"uHeight\": \"1\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"dmz-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dns-external-2\": {\n          \"shape\": \"circle\",\n          \"name\": \"External DNS 2\",\n          \"ip\": \"172.16.0.54\",\n          \"role\": \"External DNS\",\n          \"tags\": [\n            \"dmz\",\n            \"dns\",\n            \"public\"\n          ],\n          \"notes\": [\n            \"BIND DNS\",\n            \"Secondary for corp.com\"\n          ],\n          \"mac\": \"00:50:56:CC:05:02\",\n          \"rackUnit\": 10,\n          \"uHeight\": \"1\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"dmz-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"vcenter\": {\n          \"shape\": \"server\",\n          \"name\": \"vCenter Server\",\n          \"ip\": \"192.168.100.10\",\n          \"role\": \"Virtualization Management\",\n          \"tags\": [\n            \"management\",\n            \"vmware\",\n            \"vcsa\"\n          ],\n          \"notes\": [\n            \"vCenter Server Appliance 8.0\",\n            \"Single SSO domain\"\n          ],\n          \"mac\": \"00:50:56:DD:01:01\",\n          \"rackUnit\": 20,\n          \"uHeight\": \"2\",\n          \"layer\": \"logical\",\n          \"assignedRack\": \"mgmt-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"nsx-manager\": {\n          \"shape\": \"server\",\n          \"name\": \"NSX Manager\",\n          \"ip\": \"192.168.100.15\",\n          \"role\": \"Network Virtualization\",\n          \"tags\": [\n            \"management\",\n            \"vmware\",\n            \"nsx\"\n          ],\n          \"notes\": [\n            \"NSX-T 4.1 Manager Cluster\"\n          ],\n          \"mac\": \"00:50:56:DD:02:01\",\n          \"rackUnit\": 17,\n          \"uHeight\": \"2\",\n          \"layer\": \"logical\",\n          \"assignedRack\": \"mgmt-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"siem-server\": {\n          \"shape\": \"server\",\n          \"name\": \"SIEM Server\",\n          \"ip\": \"192.168.100.50\",\n          \"role\": \"Security Monitoring\",\n          \"tags\": [\n            \"management\",\n            \"security\",\n            \"splunk\"\n          ],\n          \"notes\": [\n            \"Splunk Enterprise\",\n            \"Security monitoring\"\n          ],\n          \"mac\": \"00:50:56:DD:03:01\",\n          \"rackUnit\": 14,\n          \"uHeight\": \"2\",\n          \"layer\": \"logical\",\n          \"assignedRack\": \"mgmt-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"nms-server\": {\n          \"shape\": \"server\",\n          \"name\": \"Network Monitoring\",\n          \"ip\": \"192.168.100.60\",\n          \"role\": \"Network Management\",\n          \"tags\": [\n            \"management\",\n            \"monitoring\",\n            \"prtg\"\n          ],\n          \"notes\": [\n            \"PRTG Network Monitor\",\n            \"5000 sensors\"\n          ],\n          \"mac\": \"00:50:56:DD:04:01\",\n          \"rackUnit\": 11,\n          \"uHeight\": \"1\",\n          \"layer\": \"logical\",\n          \"assignedRack\": \"mgmt-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"jump-server\": {\n          \"shape\": \"server\",\n          \"name\": \"Jump Server\",\n          \"ip\": \"192.168.100.100\",\n          \"role\": \"Bastion Host\",\n          \"tags\": [\n            \"management\",\n            \"security\",\n            \"bastion\"\n          ],\n          \"notes\": [\n            \"Windows Server 2022\",\n            \"MFA enabled\"\n          ],\n          \"mac\": \"00:50:56:DD:05:01\",\n          \"rackUnit\": 9,\n          \"uHeight\": \"1\",\n          \"layer\": \"logical\",\n          \"assignedRack\": \"mgmt-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"ipam-server\": {\n          \"shape\": \"server\",\n          \"name\": \"IPAM/DDI\",\n          \"ip\": \"192.168.100.70\",\n          \"role\": \"IP Management\",\n          \"tags\": [\n            \"management\",\n            \"dns\",\n            \"dhcp\"\n          ],\n          \"notes\": [\n            \"Infoblox DDI\",\n            \"DNS/DHCP/IPAM\"\n          ],\n          \"mac\": \"00:50:56:DD:06:01\",\n          \"rackUnit\": 7,\n          \"uHeight\": \"2\",\n          \"layer\": \"logical\",\n          \"assignedRack\": \"mgmt-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"wlc-primary\": {\n          \"shape\": \"wifi\",\n          \"name\": \"WLC Primary\",\n          \"ip\": \"10.20.0.1\",\n          \"role\": \"Wireless Controller\",\n          \"tags\": [\n            \"wireless\",\n            \"cisco\",\n            \"9800\"\n          ],\n          \"notes\": [\n            \"Cisco C9800-40\",\n            \"Primary controller\"\n          ],\n          \"mac\": \"00:1A:2B:WL:01:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"wlc-secondary\": {\n          \"shape\": \"wifi\",\n          \"name\": \"WLC Secondary\",\n          \"ip\": \"10.20.0.2\",\n          \"role\": \"Wireless Controller\",\n          \"tags\": [\n            \"wireless\",\n            \"cisco\",\n            \"9800\"\n          ],\n          \"notes\": [\n            \"Cisco C9800-40\",\n            \"HA Secondary\"\n          ],\n          \"mac\": \"00:1A:2B:WL:01:02\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"mobile-zone-hq\": {\n          \"shape\": \"phone\",\n          \"name\": \"HQ Mobile Zone\",\n          \"ip\": \"10.20.10.0/24\",\n          \"role\": \"Mobile Device Zone\",\n          \"tags\": [\n            \"wireless\",\n            \"byod\",\n            \"mobile\"\n          ],\n          \"notes\": [\n            \"Corporate BYOD\",\n            \"MDM enrolled devices\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"mobile-zone-guest\": {\n          \"shape\": \"phone\",\n          \"name\": \"Guest WiFi Zone\",\n          \"ip\": \"10.30.0.0/24\",\n          \"role\": \"Guest Network\",\n          \"tags\": [\n            \"wireless\",\n            \"guest\",\n            \"isolated\"\n          ],\n          \"notes\": [\n            \"Captive portal\",\n            \"Internet only\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"mobile-zone-iot\": {\n          \"shape\": \"phone\",\n          \"name\": \"IoT Device Zone\",\n          \"ip\": \"10.40.0.0/24\",\n          \"role\": \"IoT Network\",\n          \"tags\": [\n            \"wireless\",\n            \"iot\",\n            \"building\"\n          ],\n          \"notes\": [\n            \"Building automation\",\n            \"Smart devices\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"branch-router-ny\": {\n          \"shape\": \"router\",\n          \"name\": \"NYC Branch Router\",\n          \"ip\": \"10.100.0.1\",\n          \"role\": \"Branch Gateway\",\n          \"tags\": [\n            \"branch\",\n            \"nyc\",\n            \"sd-wan\"\n          ],\n          \"notes\": [\n            \"Cisco Viptela vEdge\",\n            \"SD-WAN enabled\"\n          ],\n          \"mac\": \"00:1A:2B:BR:01:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"branch-router-la\": {\n          \"shape\": \"router\",\n          \"name\": \"LA Branch Router\",\n          \"ip\": \"10.101.0.1\",\n          \"role\": \"Branch Gateway\",\n          \"tags\": [\n            \"branch\",\n            \"la\",\n            \"sd-wan\"\n          ],\n          \"notes\": [\n            \"Cisco Viptela vEdge\",\n            \"SD-WAN enabled\"\n          ],\n          \"mac\": \"00:1A:2B:BR:02:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"branch-router-chi\": {\n          \"shape\": \"router\",\n          \"name\": \"Chicago Branch Router\",\n          \"ip\": \"10.102.0.1\",\n          \"role\": \"Branch Gateway\",\n          \"tags\": [\n            \"branch\",\n            \"chicago\",\n            \"sd-wan\"\n          ],\n          \"notes\": [\n            \"Cisco Viptela vEdge\",\n            \"SD-WAN enabled\"\n          ],\n          \"mac\": \"00:1A:2B:BR:03:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"branch-router-lon\": {\n          \"shape\": \"router\",\n          \"name\": \"London Branch Router\",\n          \"ip\": \"10.200.0.1\",\n          \"role\": \"Branch Gateway\",\n          \"tags\": [\n            \"branch\",\n            \"london\",\n            \"sd-wan\"\n          ],\n          \"notes\": [\n            \"Cisco Viptela vEdge\",\n            \"EMEA region\"\n          ],\n          \"mac\": \"00:1A:2B:BR:04:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"branch-router-tokyo\": {\n          \"shape\": \"router\",\n          \"name\": \"Tokyo Branch Router\",\n          \"ip\": \"10.201.0.1\",\n          \"role\": \"Branch Gateway\",\n          \"tags\": [\n            \"branch\",\n            \"tokyo\",\n            \"sd-wan\"\n          ],\n          \"notes\": [\n            \"Cisco Viptela vEdge\",\n            \"APAC region\"\n          ],\n          \"mac\": \"00:1A:2B:BR:05:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"cloud-aws\": {\n          \"shape\": \"cloud\",\n          \"name\": \"AWS Cloud\",\n          \"ip\": \"vpc-0a1b2c3d\",\n          \"role\": \"Public Cloud\",\n          \"tags\": [\n            \"cloud\",\n            \"aws\",\n            \"hybrid\"\n          ],\n          \"notes\": [\n            \"AWS US-East-1\",\n            \"VPC peering to HQ\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"logical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"cloud-azure\": {\n          \"shape\": \"cloud\",\n          \"name\": \"Azure Cloud\",\n          \"ip\": \"vnet-corp-prod\",\n          \"role\": \"Public Cloud\",\n          \"tags\": [\n            \"cloud\",\n            \"azure\",\n            \"hybrid\"\n          ],\n          \"notes\": [\n            \"Azure East US 2\",\n            \"ExpressRoute\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"logical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"cloud-gcp\": {\n          \"shape\": \"cloud\",\n          \"name\": \"GCP Cloud\",\n          \"ip\": \"vpc-gcp-corp\",\n          \"role\": \"Public Cloud\",\n          \"tags\": [\n            \"cloud\",\n            \"gcp\",\n            \"dev\"\n          ],\n          \"notes\": [\n            \"GCP us-central1\",\n            \"Dev/Test workloads\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"logical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"isp-primary\": {\n          \"shape\": \"globe\",\n          \"name\": \"ISP Primary\",\n          \"ip\": \"203.0.113.1\",\n          \"role\": \"Internet Uplink\",\n          \"tags\": [\n            \"wan\",\n            \"internet\",\n            \"primary\"\n          ],\n          \"notes\": [\n            \"AT&T MPLS\",\n            \"1 Gbps dedicated\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"isp-secondary\": {\n          \"shape\": \"vm\",\n          \"name\": \"ISP Secondary\",\n          \"ip\": \"198.51.100.1\",\n          \"role\": \"Internet Uplink\",\n          \"tags\": [\n            \"wan\",\n            \"internet\",\n            \"backup\"\n          ],\n          \"notes\": [\n            \"Verizon Business\",\n            \"500 Mbps backup\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null,\n          \"rotation\": -17\n        },\n        \"dc-internal-1\": {\n          \"shape\": \"circle\",\n          \"name\": \"DC1 Int DNS\",\n          \"ip\": \"10.10.0.53\",\n          \"role\": \"Internal DNS/AD\",\n          \"tags\": [\n            \"dns\",\n            \"ad\",\n            \"dc1\"\n          ],\n          \"notes\": [\n            \"Windows Server 2022\",\n            \"Primary DC\"\n          ],\n          \"mac\": \"00:50:56:AD:01:01\",\n          \"rackUnit\": 26,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dc-internal-2\": {\n          \"shape\": \"circle\",\n          \"name\": \"DC2 Int DNS\",\n          \"ip\": \"10.10.1.53\",\n          \"role\": \"Internal DNS/AD\",\n          \"tags\": [\n            \"dns\",\n            \"ad\",\n            \"dc2\"\n          ],\n          \"notes\": [\n            \"Windows Server 2022\",\n            \"Secondary DC\"\n          ],\n          \"mac\": \"00:50:56:AD:01:02\",\n          \"rackUnit\": 26,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"app-server-1\": {\n          \"shape\": \"server\",\n          \"name\": \"App Server 01\",\n          \"ip\": \"10.10.0.101\",\n          \"role\": \"Application\",\n          \"tags\": [\n            \"app\",\n            \"iis\",\n            \"web\"\n          ],\n          \"notes\": [\n            \"Windows Server 2022\",\n            \"IIS Application\"\n          ],\n          \"mac\": \"00:50:56:AP:01:01\",\n          \"rackUnit\": 24,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"app-server-2\": {\n          \"shape\": \"server\",\n          \"name\": \"App Server 02\",\n          \"ip\": \"10.10.0.102\",\n          \"role\": \"Application\",\n          \"tags\": [\n            \"app\",\n            \"iis\",\n            \"web\"\n          ],\n          \"notes\": [\n            \"Windows Server 2022\",\n            \"IIS Application\"\n          ],\n          \"mac\": \"00:50:56:AP:01:02\",\n          \"rackUnit\": 22,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"db-server-1\": {\n          \"shape\": \"database\",\n          \"name\": \"SQL Server 01\",\n          \"ip\": \"10.10.0.201\",\n          \"role\": \"Database\",\n          \"tags\": [\n            \"db\",\n            \"sql\",\n            \"primary\"\n          ],\n          \"notes\": [\n            \"SQL Server 2022 Enterprise\",\n            \"AlwaysOn Primary\"\n          ],\n          \"mac\": \"00:50:56:DB:01:01\",\n          \"rackUnit\": 20,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"db-server-2\": {\n          \"shape\": \"database\",\n          \"name\": \"SQL Server 02\",\n          \"ip\": \"10.10.1.201\",\n          \"role\": \"Database\",\n          \"tags\": [\n            \"db\",\n            \"sql\",\n            \"secondary\"\n          ],\n          \"notes\": [\n            \"SQL Server 2022 Enterprise\",\n            \"AlwaysOn Secondary\"\n          ],\n          \"mac\": \"00:50:56:DB:01:02\",\n          \"rackUnit\": 24,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"k8s-master-1\": {\n          \"shape\": \"hexagon\",\n          \"name\": \"K8s Master 1\",\n          \"ip\": \"10.10.1.50\",\n          \"role\": \"Container Orchestration\",\n          \"tags\": [\n            \"kubernetes\",\n            \"master\",\n            \"container\"\n          ],\n          \"notes\": [\n            \"K8s Control Plane\",\n            \"etcd member\"\n          ],\n          \"mac\": \"00:50:56:K8:01:01\",\n          \"rackUnit\": 21,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"k8s-master-2\": {\n          \"shape\": \"hexagon\",\n          \"name\": \"K8s Master 2\",\n          \"ip\": \"10.10.1.51\",\n          \"role\": \"Container Orchestration\",\n          \"tags\": [\n            \"kubernetes\",\n            \"master\",\n            \"container\"\n          ],\n          \"notes\": [\n            \"K8s Control Plane\",\n            \"etcd member\"\n          ],\n          \"mac\": \"00:50:56:K8:01:02\",\n          \"rackUnit\": 19,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"k8s-master-3\": {\n          \"shape\": \"hexagon\",\n          \"name\": \"K8s Master 3\",\n          \"ip\": \"10.10.1.52\",\n          \"role\": \"Container Orchestration\",\n          \"tags\": [\n            \"kubernetes\",\n            \"master\",\n            \"container\"\n          ],\n          \"notes\": [\n            \"K8s Control Plane\",\n            \"etcd member\"\n          ],\n          \"mac\": \"00:50:56:K8:01:03\",\n          \"rackUnit\": 17,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"k8s-worker-1\": {\n          \"shape\": \"server\",\n          \"name\": \"K8s Worker 1\",\n          \"ip\": \"10.10.1.60\",\n          \"role\": \"Container Workload\",\n          \"tags\": [\n            \"kubernetes\",\n            \"worker\",\n            \"container\"\n          ],\n          \"notes\": [\n            \"K8s Worker Node\",\n            \"64GB RAM\"\n          ],\n          \"mac\": \"00:50:56:K8:02:01\",\n          \"rackUnit\": 15,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"k8s-worker-2\": {\n          \"shape\": \"server\",\n          \"name\": \"K8s Worker 2\",\n          \"ip\": \"10.10.1.61\",\n          \"role\": \"Container Workload\",\n          \"tags\": [\n            \"kubernetes\",\n            \"worker\",\n            \"container\"\n          ],\n          \"notes\": [\n            \"K8s Worker Node\",\n            \"64GB RAM\"\n          ],\n          \"mac\": \"00:50:56:K8:02:02\",\n          \"rackUnit\": 13,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"k8s-worker-3\": {\n          \"shape\": \"server\",\n          \"name\": \"K8s Worker 3\",\n          \"ip\": \"10.10.1.62\",\n          \"role\": \"Container Workload\",\n          \"tags\": [\n            \"kubernetes\",\n            \"worker\",\n            \"container\"\n          ],\n          \"notes\": [\n            \"K8s Worker Node\",\n            \"64GB RAM\"\n          ],\n          \"mac\": \"00:50:56:K8:02:03\",\n          \"rackUnit\": 11,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"k8s-worker-4\": {\n          \"shape\": \"server\",\n          \"name\": \"K8s Worker 4\",\n          \"ip\": \"10.10.1.63\",\n          \"role\": \"Container Workload\",\n          \"tags\": [\n            \"kubernetes\",\n            \"worker\",\n            \"container\"\n          ],\n          \"notes\": [\n            \"K8s Worker Node\",\n            \"64GB RAM\"\n          ],\n          \"mac\": \"00:50:56:K8:02:04\",\n          \"rackUnit\": 9,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"proxy-server-1\": {\n          \"shape\": \"server\",\n          \"name\": \"Proxy Server 1\",\n          \"ip\": \"10.5.0.10\",\n          \"role\": \"Web Proxy\",\n          \"tags\": [\n            \"proxy\",\n            \"squid\",\n            \"filtering\"\n          ],\n          \"notes\": [\n            \"Squid Proxy\",\n            \"Content filtering\"\n          ],\n          \"mac\": \"00:50:56:PX:01:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"proxy-server-2\": {\n          \"shape\": \"server\",\n          \"name\": \"Proxy Server 2\",\n          \"ip\": \"10.5.0.11\",\n          \"role\": \"Web Proxy\",\n          \"tags\": [\n            \"proxy\",\n            \"squid\",\n            \"filtering\"\n          ],\n          \"notes\": [\n            \"Squid Proxy\",\n            \"HA pair\"\n          ],\n          \"mac\": \"00:50:56:PX:01:02\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"vpn-concentrator\": {\n          \"shape\": \"firewall\",\n          \"name\": \"VPN Concentrator\",\n          \"ip\": \"10.0.5.1\",\n          \"role\": \"Remote Access VPN\",\n          \"tags\": [\n            \"vpn\",\n            \"remote\",\n            \"security\"\n          ],\n          \"notes\": [\n            \"Cisco ASA 5555-X\",\n            \"AnyConnect SSL VPN\"\n          ],\n          \"mac\": \"00:1A:2B:VP:01:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"nac-server\": {\n          \"shape\": \"server\",\n          \"name\": \"NAC Server\",\n          \"ip\": \"10.5.5.10\",\n          \"role\": \"Network Access Control\",\n          \"tags\": [\n            \"nac\",\n            \"ise\",\n            \"802.1x\"\n          ],\n          \"notes\": [\n            \"Cisco ISE 3.1\",\n            \"RADIUS/TACACS+\"\n          ],\n          \"mac\": \"00:50:56:NA:01:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"print-server\": {\n          \"shape\": \"server\",\n          \"name\": \"Print Server\",\n          \"ip\": \"10.10.0.150\",\n          \"role\": \"Print Services\",\n          \"tags\": [\n            \"print\",\n            \"windows\",\n            \"services\"\n          ],\n          \"notes\": [\n            \"Windows Print Server\",\n            \"50+ printers\"\n          ],\n          \"mac\": \"00:50:56:PR:01:01\",\n          \"rackUnit\": 18,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"file-server\": {\n          \"shape\": \"database\",\n          \"name\": \"File Server\",\n          \"ip\": \"10.10.0.160\",\n          \"role\": \"File Services\",\n          \"tags\": [\n            \"file\",\n            \"smb\",\n            \"dfs\"\n          ],\n          \"notes\": [\n            \"Windows File Server\",\n            \"DFS namespace\"\n          ],\n          \"mac\": \"00:50:56:FS:01:01\",\n          \"rackUnit\": 16,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"ca-server\": {\n          \"shape\": \"server\",\n          \"name\": \"Certificate Authority\",\n          \"ip\": \"192.168.100.80\",\n          \"role\": \"PKI Infrastructure\",\n          \"tags\": [\n            \"pki\",\n            \"ca\",\n            \"security\"\n          ],\n          \"notes\": [\n            \"Windows CA\",\n            \"Enterprise Root CA\"\n          ],\n          \"mac\": \"00:50:56:CA:01:01\",\n          \"rackUnit\": 5,\n          \"uHeight\": \"1\",\n          \"layer\": \"logical\",\n          \"assignedRack\": \"mgmt-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"sccm-server\": {\n          \"shape\": \"server\",\n          \"name\": \"SCCM Server\",\n          \"ip\": \"192.168.100.90\",\n          \"role\": \"Endpoint Management\",\n          \"tags\": [\n            \"sccm\",\n            \"patching\",\n            \"software\"\n          ],\n          \"notes\": [\n            \"MECM Primary Site\",\n            \"Software deployment\"\n          ],\n          \"mac\": \"00:50:56:SC:01:01\",\n          \"rackUnit\": 3,\n          \"uHeight\": \"2\",\n          \"layer\": \"logical\",\n          \"assignedRack\": \"mgmt-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"voip-cluster\": {\n          \"shape\": \"phone\",\n          \"name\": \"VoIP Cluster\",\n          \"ip\": \"10.50.0.0/24\",\n          \"role\": \"Voice Services\",\n          \"tags\": [\n            \"voip\",\n            \"cisco\",\n            \"ucm\"\n          ],\n          \"notes\": [\n            \"Cisco UCM Cluster\",\n            \"3000 endpoints\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"application\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"video-conf\": {\n          \"shape\": \"laptop\",\n          \"name\": \"Video Conference\",\n          \"ip\": \"10.51.0.0/24\",\n          \"role\": \"Video Services\",\n          \"tags\": [\n            \"video\",\n            \"webex\",\n            \"teams\"\n          ],\n          \"notes\": [\n            \"Webex/Teams integration\",\n            \"Meeting rooms\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"application\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"security-cameras\": {\n          \"shape\": \"camera\",\n          \"name\": \"Security Cameras\",\n          \"ip\": \"10.60.0.0/24\",\n          \"role\": \"Physical Security\",\n          \"tags\": [\n            \"cctv\",\n            \"surveillance\",\n            \"security\"\n          ],\n          \"notes\": [\n            \"150+ IP cameras\",\n            \"30-day retention\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"nvr-cluster\": {\n          \"shape\": \"server\",\n          \"name\": \"NVR Cluster\",\n          \"ip\": \"10.60.0.10\",\n          \"role\": \"Video Recording\",\n          \"tags\": [\n            \"nvr\",\n            \"surveillance\",\n            \"storage\"\n          ],\n          \"notes\": [\n            \"Milestone XProtect\",\n            \"500TB storage\"\n          ],\n          \"mac\": \"00:50:56:NV:01:01\",\n          \"rackUnit\": 15,\n          \"uHeight\": \"4\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-b2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dev-server-1\": {\n          \"shape\": \"server\",\n          \"name\": \"Dev Server 1\",\n          \"ip\": \"10.80.0.10\",\n          \"role\": \"Development\",\n          \"tags\": [\n            \"dev\",\n            \"gitlab\",\n            \"ci-cd\",\n            {\n              \"type\": \"icon\",\n              \"library\": \"selfhst\",\n              \"name\": \"dokku\"\n            }\n          ],\n          \"notes\": [\n            \"GitLab Server\",\n            \"CI/CD pipelines\"\n          ],\n          \"mac\": \"00:50:56:DV:01:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"application\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dev-server-2\": {\n          \"shape\": \"server\",\n          \"name\": \"Dev Server 2\",\n          \"ip\": \"10.80.0.11\",\n          \"role\": \"Development\",\n          \"tags\": [\n            \"dev\",\n            \"jenkins\",\n            \"ci-cd\"\n          ],\n          \"notes\": [\n            \"Jenkins Server\",\n            \"Build automation\"\n          ],\n          \"mac\": \"00:50:56:DV:01:02\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"application\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"test-environment\": {\n          \"shape\": \"shield\",\n          \"name\": \"Test Environment\",\n          \"ip\": \"10.81.0.0/24\",\n          \"role\": \"QA/Testing\",\n          \"tags\": [\n            \"test\",\n            \"qa\",\n            \"staging\"\n          ],\n          \"notes\": [\n            \"Staging environment\",\n            \"Pre-prod validation\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"application\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null,\n          \"rotation\": -36\n        },\n        \"erp-system\": {\n          \"shape\": \"database\",\n          \"name\": \"ERP System\",\n          \"ip\": \"10.90.0.10\",\n          \"role\": \"Business Application\",\n          \"tags\": [\n            \"erp\",\n            \"sap\",\n            \"business\"\n          ],\n          \"notes\": [\n            \"SAP S/4HANA\",\n            \"Financial/HR systems\"\n          ],\n          \"mac\": \"00:50:56:ER:01:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"4\",\n          \"layer\": \"application\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"crm-system\": {\n          \"shape\": \"database\",\n          \"name\": \"CRM System\",\n          \"ip\": \"10.91.0.10\",\n          \"role\": \"Business Application\",\n          \"tags\": [\n            \"crm\",\n            \"salesforce\",\n            \"business\"\n          ],\n          \"notes\": [\n            \"Salesforce integration\",\n            \"Sales/Marketing\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"application\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"endpoint-1000\": {\n          \"shape\": \"laptop\",\n          \"name\": \"Corporate Endpoints\",\n          \"ip\": \"10.70.0.0/22\",\n          \"role\": \"User Workstations\",\n          \"tags\": [\n            \"endpoints\",\n            \"workstations\",\n            \"users\"\n          ],\n          \"notes\": [\n            \"~1000 corporate laptops\",\n            \"Windows 11\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dist-switch-floor1\": {\n          \"shape\": \"switch\",\n          \"name\": \"Floor 1 Switch\",\n          \"ip\": \"10.1.1.1\",\n          \"role\": \"Distribution\",\n          \"tags\": [\n            \"distribution\",\n            \"floor-1\",\n            \"access\"\n          ],\n          \"notes\": [\n            \"Cisco C9300-48P\",\n            \"PoE+ enabled\"\n          ],\n          \"mac\": \"00:1A:2B:FL:01:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dist-switch-floor2\": {\n          \"shape\": \"switch\",\n          \"name\": \"Floor 2 Switch\",\n          \"ip\": \"10.1.2.1\",\n          \"role\": \"Distribution\",\n          \"tags\": [\n            \"distribution\",\n            \"floor-2\",\n            \"access\"\n          ],\n          \"notes\": [\n            \"Cisco C9300-48P\",\n            \"PoE+ enabled\"\n          ],\n          \"mac\": \"00:1A:2B:FL:02:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dist-switch-floor3\": {\n          \"shape\": \"switch\",\n          \"name\": \"Floor 3 Switch\",\n          \"ip\": \"10.1.3.1\",\n          \"role\": \"Distribution\",\n          \"tags\": [\n            \"distribution\",\n            \"floor-3\",\n            \"access\"\n          ],\n          \"notes\": [\n            \"Cisco C9300-48P\",\n            \"PoE+ enabled\"\n          ],\n          \"mac\": \"00:1A:2B:FL:03:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dist-switch-floor4\": {\n          \"shape\": \"switch\",\n          \"name\": \"Floor 4 Switch\",\n          \"ip\": \"10.1.4.1\",\n          \"role\": \"Distribution\",\n          \"tags\": [\n            \"distribution\",\n            \"floor-4\",\n            \"access\"\n          ],\n          \"notes\": [\n            \"Cisco C9300-48P\",\n            \"PoE+ enabled\"\n          ],\n          \"mac\": \"00:1A:2B:FL:04:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"ap-floor1-zone1\": {\n          \"shape\": \"wifi\",\n          \"name\": \"AP Floor 1 Zone 1\",\n          \"ip\": \"10.20.1.10\",\n          \"role\": \"Wireless Access\",\n          \"tags\": [\n            \"wifi\",\n            \"ap\",\n            \"floor-1\"\n          ],\n          \"notes\": [\n            \"Cisco 9120AX\",\n            \"Wi-Fi 6\"\n          ],\n          \"mac\": \"00:1A:2B:AP:01:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"ap-floor2-zone1\": {\n          \"shape\": \"wifi\",\n          \"name\": \"AP Floor 2 Zone 1\",\n          \"ip\": \"10.20.2.10\",\n          \"role\": \"Wireless Access\",\n          \"tags\": [\n            \"wifi\",\n            \"ap\",\n            \"floor-2\"\n          ],\n          \"notes\": [\n            \"Cisco 9120AX\",\n            \"Wi-Fi 6\"\n          ],\n          \"mac\": \"00:1A:2B:AP:02:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"ap-floor3-zone1\": {\n          \"shape\": \"wifi\",\n          \"name\": \"AP Floor 3 Zone 1\",\n          \"ip\": \"10.20.3.10\",\n          \"role\": \"Wireless Access\",\n          \"tags\": [\n            \"wifi\",\n            \"ap\",\n            \"floor-3\"\n          ],\n          \"notes\": [\n            \"Cisco 9120AX\",\n            \"Wi-Fi 6\"\n          ],\n          \"mac\": \"00:1A:2B:AP:03:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"ap-floor4-zone1\": {\n          \"shape\": \"wifi\",\n          \"name\": \"AP Floor 4 Zone 1\",\n          \"ip\": \"10.20.4.10\",\n          \"role\": \"Wireless Access\",\n          \"tags\": [\n            \"wifi\",\n            \"ap\",\n            \"floor-4\"\n          ],\n          \"notes\": [\n            \"Cisco 9120AX\",\n            \"Wi-Fi 6\"\n          ],\n          \"mac\": \"00:1A:2B:AP:04:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"ups-dc-1\": {\n          \"shape\": \"rectangle\",\n          \"name\": \"UPS DC-1\",\n          \"ip\": \"192.168.200.10\",\n          \"role\": \"Power Management\",\n          \"tags\": [\n            \"power\",\n            \"ups\",\n            \"datacenter\"\n          ],\n          \"notes\": [\n            \"APC Symmetra\",\n            \"80kVA\",\n            \"30 min runtime\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"ups-dc-2\": {\n          \"shape\": \"rectangle\",\n          \"name\": \"UPS DC-2\",\n          \"ip\": \"192.168.200.11\",\n          \"role\": \"Power Management\",\n          \"tags\": [\n            \"power\",\n            \"ups\",\n            \"datacenter\"\n          ],\n          \"notes\": [\n            \"APC Symmetra\",\n            \"80kVA\",\n            \"Redundant\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"pdu-rack-a1\": {\n          \"shape\": \"rectangle\",\n          \"name\": \"PDU Rack A1\",\n          \"ip\": \"192.168.200.21\",\n          \"role\": \"Power Distribution\",\n          \"tags\": [\n            \"power\",\n            \"pdu\",\n            \"rack-a1\"\n          ],\n          \"notes\": [\n            \"APC Switched PDU\",\n            \"Per-outlet metering\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": 1,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"pdu-rack-a2\": {\n          \"shape\": \"rectangle\",\n          \"name\": \"PDU Rack A2\",\n          \"ip\": \"192.168.200.22\",\n          \"role\": \"Power Distribution\",\n          \"tags\": [\n            \"power\",\n            \"pdu\",\n            \"rack-a2\"\n          ],\n          \"notes\": [\n            \"APC Switched PDU\",\n            \"Per-outlet metering\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": 1,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"cooling-1\": {\n          \"shape\": \"rectangle\",\n          \"name\": \"CRAC Unit 1\",\n          \"ip\": \"192.168.200.30\",\n          \"role\": \"Cooling\",\n          \"tags\": [\n            \"cooling\",\n            \"hvac\",\n            \"datacenter\"\n          ],\n          \"notes\": [\n            \"Liebert CRV\",\n            \"Row-based cooling\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"cooling-2\": {\n          \"shape\": \"rectangle\",\n          \"name\": \"CRAC Unit 2\",\n          \"ip\": \"192.168.200.31\",\n          \"role\": \"Cooling\",\n          \"tags\": [\n            \"cooling\",\n            \"hvac\",\n            \"datacenter\"\n          ],\n          \"notes\": [\n            \"Liebert CRV\",\n            \"N+1 redundancy\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"camera-a\": {\n          \"shape\": \"camera\",\n          \"name\": \"camera A\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"ping\": {\n            \"enabled\": false,\n            \"protocol\": \"http\",\n            \"customUrl\": \"\",\n            \"timeout\": 3000,\n            \"status\": \"unknown\",\n            \"lastCheck\": null\n          },\n          \"locked\": false,\n          \"groupId\": null,\n          \"fovEnabled\": true,\n          \"fovRotation\": 104,\n          \"fovDistance\": 500,\n          \"fovSweep\": 60,\n          \"fovSpeed\": 10,\n          \"fovAnimate\": true\n        },\n        \"camera-a-copy\": {\n          \"shape\": \"camera\",\n          \"name\": \"camera B\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"ping\": {\n            \"enabled\": false,\n            \"protocol\": \"http\",\n            \"customUrl\": \"\",\n            \"timeout\": 3000,\n            \"status\": \"unknown\",\n            \"lastCheck\": null\n          },\n          \"locked\": false,\n          \"groupId\": null,\n          \"fovEnabled\": true,\n          \"fovRotation\": 162,\n          \"fovDistance\": 500,\n          \"fovSweep\": 60,\n          \"fovSpeed\": 10,\n          \"fovAnimate\": false\n        }\n      },\n      \"edges\": {\n        \"list\": [\n          {\n            \"id\": \"isp1-router1\",\n            \"from\": \"isp-primary\",\n            \"to\": \"core-router-1\",\n            \"width\": 6,\n            \"color\": \"#10b981\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"Primary WAN link\"\n            ],\n            \"fromPort\": \"Gi0/0\",\n            \"toPort\": \"Gi1/0/1\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"isp2-router2\",\n            \"from\": \"isp-secondary\",\n            \"to\": \"core-router-2\",\n            \"width\": 6,\n            \"color\": \"#10b981\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"Backup WAN link\"\n            ],\n            \"fromPort\": \"Gi0/0\",\n            \"toPort\": \"Gi1/0/1\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"router1-router2\",\n            \"from\": \"core-router-1\",\n            \"to\": \"core-router-2\",\n            \"width\": 4,\n            \"color\": \"#f59e0b\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"HSRP Peering\"\n            ],\n            \"fromPort\": \"Gi1/0/24\",\n            \"toPort\": \"Gi1/0/24\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"router1-fw1\",\n            \"from\": \"core-router-1\",\n            \"to\": \"fw-external-1\",\n            \"width\": 4,\n            \"color\": \"#ef4444\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"router2-fw2\",\n            \"from\": \"core-router-2\",\n            \"to\": \"fw-external-2\",\n            \"width\": 4,\n            \"color\": \"#ef4444\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"fw1-fw2\",\n            \"from\": \"fw-external-1\",\n            \"to\": \"fw-external-2\",\n            \"width\": 3,\n            \"color\": \"#f59e0b\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"HA heartbeat\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dashed\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"fw1-coresw1\",\n            \"from\": \"fw-external-1\",\n            \"to\": \"core-switch-1\",\n            \"width\": 4,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"fw2-coresw2\",\n            \"from\": \"fw-external-2\",\n            \"to\": \"core-switch-2\",\n            \"width\": 4,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-coresw2\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"core-switch-2\",\n            \"width\": 5,\n            \"color\": \"#3b82f6\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"VPC peer-link\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-fwint\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"fw-internal\",\n            \"width\": 3,\n            \"color\": \"#ef4444\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw2-fwint\",\n            \"from\": \"core-switch-2\",\n            \"to\": \"fw-internal\",\n            \"width\": 3,\n            \"color\": \"#ef4444\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-racka1\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"dc-rack-a1\",\n            \"width\": 4,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw2-racka1\",\n            \"from\": \"core-switch-2\",\n            \"to\": \"dc-rack-a1\",\n            \"width\": 4,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-racka2\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"dc-rack-a2\",\n            \"width\": 4,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw2-racka2\",\n            \"from\": \"core-switch-2\",\n            \"to\": \"dc-rack-a2\",\n            \"width\": 4,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-rackb1\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"dc-rack-b1\",\n            \"width\": 4,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw2-rackb1\",\n            \"from\": \"core-switch-2\",\n            \"to\": \"dc-rack-b1\",\n            \"width\": 4,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-rackb2\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"dc-rack-b2\",\n            \"width\": 4,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw2-rackb2\",\n            \"from\": \"core-switch-2\",\n            \"to\": \"dc-rack-b2\",\n            \"width\": 4,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"fw1-dmz\",\n            \"from\": \"fw-external-1\",\n            \"to\": \"dmz-rack\",\n            \"width\": 3,\n            \"color\": \"#f59e0b\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"DMZ segment\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"fw2-dmz\",\n            \"from\": \"fw-external-2\",\n            \"to\": \"dmz-rack\",\n            \"width\": 3,\n            \"color\": \"#f59e0b\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"DMZ segment\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-mgmt\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"mgmt-rack\",\n            \"width\": 3,\n            \"color\": \"#8b5cf6\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"OOB management\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-wlc1\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"wlc-primary\",\n            \"width\": 3,\n            \"color\": \"#06b6d4\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw2-wlc2\",\n            \"from\": \"core-switch-2\",\n            \"to\": \"wlc-secondary\",\n            \"width\": 3,\n            \"color\": \"#06b6d4\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\",\n            \"animate\": true\n          },\n          {\n            \"id\": \"wlc1-wlc2\",\n            \"from\": \"wlc-primary\",\n            \"to\": \"wlc-secondary\",\n            \"width\": 2,\n            \"color\": \"#f59e0b\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"HA pair\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dashed\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"wlc1-mobile-hq\",\n            \"from\": \"wlc-primary\",\n            \"to\": \"mobile-zone-hq\",\n            \"width\": 2,\n            \"color\": \"#06b6d4\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"wlc1-mobile-guest\",\n            \"from\": \"wlc-primary\",\n            \"to\": \"mobile-zone-guest\",\n            \"width\": 2,\n            \"color\": \"#06b6d4\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"wlc1-mobile-iot\",\n            \"from\": \"wlc-primary\",\n            \"to\": \"mobile-zone-iot\",\n            \"width\": 2,\n            \"color\": \"#06b6d4\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"router1-branch-ny\",\n            \"from\": \"core-router-1\",\n            \"to\": \"branch-router-ny\",\n            \"width\": 3,\n            \"color\": \"#a855f7\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"SD-WAN tunnel\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dashed\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"router1-branch-la\",\n            \"from\": \"core-router-1\",\n            \"to\": \"branch-router-la\",\n            \"width\": 3,\n            \"color\": \"#a855f7\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"SD-WAN tunnel\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dashed\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"router1-branch-chi\",\n            \"from\": \"core-router-1\",\n            \"to\": \"branch-router-chi\",\n            \"width\": 3,\n            \"color\": \"#a855f7\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"SD-WAN tunnel\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dashed\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"router1-branch-lon\",\n            \"from\": \"core-router-1\",\n            \"to\": \"branch-router-lon\",\n            \"width\": 3,\n            \"color\": \"#a855f7\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"SD-WAN tunnel\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dashed\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"router1-branch-tokyo\",\n            \"from\": \"core-router-1\",\n            \"to\": \"branch-router-tokyo\",\n            \"width\": 3,\n            \"color\": \"#a855f7\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"SD-WAN tunnel\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dashed\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"router1-aws\",\n            \"from\": \"core-router-1\",\n            \"to\": \"cloud-aws\",\n            \"width\": 3,\n            \"color\": \"#f97316\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"Direct Connect\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dashed\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"router2-azure\",\n            \"from\": \"core-router-2\",\n            \"to\": \"cloud-azure\",\n            \"width\": 3,\n            \"color\": \"#0ea5e9\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"ExpressRoute\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dashed\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"fwint-gcp\",\n            \"from\": \"fw-internal\",\n            \"to\": \"cloud-gcp\",\n            \"width\": 2,\n            \"color\": \"#22c55e\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"VPN tunnel\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dashed\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-floor1\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"dist-switch-floor1\",\n            \"width\": 3,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-floor2\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"dist-switch-floor2\",\n            \"width\": 3,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw2-floor3\",\n            \"from\": \"core-switch-2\",\n            \"to\": \"dist-switch-floor3\",\n            \"width\": 3,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw2-floor4\",\n            \"from\": \"core-switch-2\",\n            \"to\": \"dist-switch-floor4\",\n            \"width\": 3,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"floor1-endpoints\",\n            \"from\": \"dist-switch-floor1\",\n            \"to\": \"endpoint-1000\",\n            \"width\": 2,\n            \"color\": \"#94a3b8\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"floor1-ap1\",\n            \"from\": \"dist-switch-floor1\",\n            \"to\": \"ap-floor1-zone1\",\n            \"width\": 2,\n            \"color\": \"#06b6d4\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"floor2-ap2\",\n            \"from\": \"dist-switch-floor2\",\n            \"to\": \"ap-floor2-zone1\",\n            \"width\": 2,\n            \"color\": \"#06b6d4\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"floor3-ap3\",\n            \"from\": \"dist-switch-floor3\",\n            \"to\": \"ap-floor3-zone1\",\n            \"width\": 2,\n            \"color\": \"#06b6d4\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"floor4-ap4\",\n            \"from\": \"dist-switch-floor4\",\n            \"to\": \"ap-floor4-zone1\",\n            \"width\": 2,\n            \"color\": \"#06b6d4\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"fwint-proxy1\",\n            \"from\": \"fw-internal\",\n            \"to\": \"proxy-server-1\",\n            \"width\": 2,\n            \"color\": \"#ef4444\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"fwint-proxy2\",\n            \"from\": \"fw-internal\",\n            \"to\": \"proxy-server-2\",\n            \"width\": 2,\n            \"color\": \"#ef4444\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"fwext1-vpn\",\n            \"from\": \"fw-external-1\",\n            \"to\": \"vpn-concentrator\",\n            \"width\": 3,\n            \"color\": \"#8b5cf6\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-nac\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"nac-server\",\n            \"width\": 2,\n            \"color\": \"#f59e0b\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-voip\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"voip-cluster\",\n            \"width\": 3,\n            \"color\": \"#22c55e\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw2-video\",\n            \"from\": \"core-switch-2\",\n            \"to\": \"video-conf\",\n            \"width\": 3,\n            \"color\": \"#22c55e\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-cameras\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"security-cameras\",\n            \"width\": 2,\n            \"color\": \"#94a3b8\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"fwint-dev1\",\n            \"from\": \"fw-internal\",\n            \"to\": \"dev-server-1\",\n            \"width\": 2,\n            \"color\": \"#a855f7\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"fwint-dev2\",\n            \"from\": \"fw-internal\",\n            \"to\": \"dev-server-2\",\n            \"width\": 2,\n            \"color\": \"#a855f7\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\",\n            \"animate\": true,\n            \"animationSpeed\": \"1.5\"\n          },\n          {\n            \"id\": \"fwint-test\",\n            \"from\": \"fw-internal\",\n            \"to\": \"test-environment\",\n            \"width\": 2,\n            \"color\": \"#a855f7\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-erp\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"erp-system\",\n            \"width\": 3,\n            \"color\": \"#f59e0b\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"fwext1-crm\",\n            \"from\": \"fw-external-1\",\n            \"to\": \"crm-system\",\n            \"width\": 2,\n            \"color\": \"#f59e0b\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"Salesforce cloud\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dashed\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"ups1-racka1\",\n            \"from\": \"ups-dc-1\",\n            \"to\": \"dc-rack-a1\",\n            \"width\": 2,\n            \"color\": \"#fbbf24\",\n            \"direction\": \"forward\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"Power feed A\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"ups2-racka2\",\n            \"from\": \"ups-dc-2\",\n            \"to\": \"dc-rack-a2\",\n            \"width\": 2,\n            \"color\": \"#fbbf24\",\n            \"direction\": \"forward\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"Power feed B\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"ups1-rackb1\",\n            \"from\": \"ups-dc-1\",\n            \"to\": \"dc-rack-b1\",\n            \"width\": 2,\n            \"color\": \"#fbbf24\",\n            \"direction\": \"forward\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"Power feed A\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\",\n            \"animate\": true,\n            \"animationSpeed\": \"4\"\n          },\n          {\n            \"id\": \"ups2-rackb2\",\n            \"from\": \"ups-dc-2\",\n            \"to\": \"dc-rack-b2\",\n            \"width\": 2,\n            \"color\": \"#fbbf24\",\n            \"direction\": \"forward\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"Power feed B\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"cooling1-racka1\",\n            \"from\": \"cooling-1\",\n            \"to\": \"dc-rack-a1\",\n            \"width\": 2,\n            \"color\": \"#38bdf8\",\n            \"direction\": \"forward\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"Cooling zone\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dotted\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"cooling2-rackb1\",\n            \"from\": \"cooling-2\",\n            \"to\": \"dc-rack-b1\",\n            \"width\": 2,\n            \"color\": \"#38bdf8\",\n            \"direction\": \"forward\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"Cooling zone\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dotted\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"custom-1765237881452\",\n            \"type\": \"custom\",\n            \"color\": \"#c800ff\",\n            \"width\": 4,\n            \"lineStyle\": \"solid\",\n            \"direction\": \"forward\",\n            \"points\": [\n              {\n                \"x\": 3492.3994140625,\n                \"y\": 1526.9556884765625\n              },\n              {\n                \"x\": 3500.609619140625,\n                \"y\": 1830.7386474609375\n              },\n              {\n                \"x\": 3303.561279296875,\n                \"y\": 1732.2144775390625\n              }\n            ],\n            \"notes\": [],\n            \"routing\": \"orthogonal\"\n          }\n        ]\n      },\n      \"positions\": {\n        \"core-router-1\": {\n          \"x\": 3720.166015625,\n          \"y\": 245.9932403564453\n        },\n        \"core-router-2\": {\n          \"x\": 2499.883407638303,\n          \"y\": 329.99503430389154\n        },\n        \"fw-external-1\": {\n          \"x\": 3221.7385182723783,\n          \"y\": 1016.1364499992887\n        },\n        \"fw-external-2\": {\n          \"x\": 1915.5213706410505,\n          \"y\": 224.43528858865443\n        },\n        \"fw-internal\": {\n          \"x\": 1746.9168185079352,\n          \"y\": 477.5300527221864\n        },\n        \"core-switch-1\": {\n          \"x\": 449.39860669455675,\n          \"y\": 384.4578707617695\n        },\n        \"core-switch-2\": {\n          \"x\": 761.1664921394672,\n          \"y\": 180.89283910873155\n        },\n        \"dc-rack-a1\": {\n          \"x\": 783.7017241128451,\n          \"y\": 647.4086870405963\n        },\n        \"dc-rack-a2\": {\n          \"x\": 209.25701628255229,\n          \"y\": 228.01593190351014\n        },\n        \"dc-rack-b1\": {\n          \"x\": 3184.3186625759854,\n          \"y\": 1627.4495531027196\n        },\n        \"dc-rack-b2\": {\n          \"x\": 245.37065918741246,\n          \"y\": 499.6191264194081\n        },\n        \"dmz-rack\": {\n          \"x\": 2176.4105289561007,\n          \"y\": 610.8312056412005\n        },\n        \"mgmt-rack\": {\n          \"x\": 1601.2987201807314,\n          \"y\": 1281.4753424975324\n        },\n        \"esxi-host-01\": {\n          \"x\": 2162.2166789540615,\n          \"y\": 2608.110619289529\n        },\n        \"esxi-host-02\": {\n          \"x\": 2205.94717202368,\n          \"y\": 2689.67539624076\n        },\n        \"esxi-host-03\": {\n          \"x\": 2154.6015436939074,\n          \"y\": 2771.203009774913\n        },\n        \"esxi-host-04\": {\n          \"x\": 2195.986926025096,\n          \"y\": 2845\n        },\n        \"tor-switch-a1\": {\n          \"x\": 2146.8943639962963,\n          \"y\": 2845\n        },\n        \"esxi-host-05\": {\n          \"x\": 2185.9099961569727,\n          \"y\": 2845\n        },\n        \"esxi-host-06\": {\n          \"x\": 2139.099728450725,\n          \"y\": 2845\n        },\n        \"esxi-host-07\": {\n          \"x\": 2175.7223818764883,\n          \"y\": 2845\n        },\n        \"esxi-host-08\": {\n          \"x\": 2131.2222777148922,\n          \"y\": 2845\n        },\n        \"tor-switch-a2\": {\n          \"x\": 2165.4301485385085,\n          \"y\": 2845\n        },\n        \"san-primary\": {\n          \"x\": 2123.2667017518106,\n          \"y\": 2845\n        },\n        \"san-secondary\": {\n          \"x\": 2155.0394237844876,\n          \"y\": 2845\n        },\n        \"fc-switch-1\": {\n          \"x\": 2115.2377370375634,\n          \"y\": 2845\n        },\n        \"fc-switch-2\": {\n          \"x\": 2144.5563938942755,\n          \"y\": 2845\n        },\n        \"backup-server-1\": {\n          \"x\": 2107.1401637413705,\n          \"y\": 2845\n        },\n        \"backup-server-2\": {\n          \"x\": 2133.987300103025,\n          \"y\": 2845\n        },\n        \"tape-library\": {\n          \"x\": 2098.9788028796397,\n          \"y\": 2845\n        },\n        \"tor-switch-b1\": {\n          \"x\": 2123.338434885373,\n          \"y\": 2845\n        },\n        \"tor-switch-b2\": {\n          \"x\": 2090.7585134456995,\n          \"y\": 2845\n        },\n        \"web-server-1\": {\n          \"x\": 2112.6161382091163,\n          \"y\": 2845\n        },\n        \"web-server-2\": {\n          \"x\": 2082.484189516922,\n          \"y\": 2845\n        },\n        \"waf-1\": {\n          \"x\": 2101.826793760617,\n          \"y\": 2845\n        },\n        \"load-balancer-dmz\": {\n          \"x\": 2074.1607573409574,\n          \"y\": 2845\n        },\n        \"mail-gateway\": {\n          \"x\": 2090.97682514417,\n          \"y\": 2845\n        },\n        \"dns-external-1\": {\n          \"x\": 2065.7931724028163,\n          \"y\": 2845\n        },\n        \"dns-external-2\": {\n          \"x\": 2080.0726920576153,\n          \"y\": 2845\n        },\n        \"vcenter\": {\n          \"x\": 2057.3864164745437,\n          \"y\": 2845\n        },\n        \"nsx-manager\": {\n          \"x\": 2069.1208864464534,\n          \"y\": 2845\n        },\n        \"siem-server\": {\n          \"x\": 2048.945494649244,\n          \"y\": 2845\n        },\n        \"nms-server\": {\n          \"x\": 2058.1279286387635,\n          \"y\": 2845\n        },\n        \"jump-server\": {\n          \"x\": 2040.4754323612206,\n          \"y\": 2845\n        },\n        \"ipam-server\": {\n          \"x\": 2047.1003634632284,\n          \"y\": 2845\n        },\n        \"wlc-primary\": {\n          \"x\": 1575.9723612611924,\n          \"y\": 2306.135986328125\n        },\n        \"wlc-secondary\": {\n          \"x\": 1468.1361870166274,\n          \"y\": 1563.733642578125\n        },\n        \"mobile-zone-hq\": {\n          \"x\": 2354.901177346808,\n          \"y\": 2806.0078125\n        },\n        \"mobile-zone-guest\": {\n          \"x\": 2307.6605605284435,\n          \"y\": 2611.047119140625\n        },\n        \"mobile-zone-iot\": {\n          \"x\": 2229.397686389302,\n          \"y\": 2299.110107421875\n        },\n        \"branch-router-ny\": {\n          \"x\": 3151.903101363964,\n          \"y\": 633.6580810546875\n        },\n        \"branch-router-la\": {\n          \"x\": 3083.8876194705945,\n          \"y\": 506.90625\n        },\n        \"branch-router-chi\": {\n          \"x\": 3355.02409980103,\n          \"y\": 393.1805725097656\n        },\n        \"branch-router-lon\": {\n          \"x\": 3113.609823320121,\n          \"y\": 260.4093322753906\n        },\n        \"branch-router-tokyo\": {\n          \"x\": 3699.3234994733834,\n          \"y\": 471.4241027832031\n        },\n        \"cloud-aws\": {\n          \"x\": 3436.528122523513,\n          \"y\": 545.9614868164062\n        },\n        \"cloud-azure\": {\n          \"x\": 2592.566210818907,\n          \"y\": 2724.068115234375\n        },\n        \"cloud-gcp\": {\n          \"x\": 2827.3183770424234,\n          \"y\": 2731.397216796875\n        },\n        \"isp-primary\": {\n          \"x\": 3712.192068081962,\n          \"y\": 615.64990234375\n        },\n        \"isp-secondary\": {\n          \"x\": 3253.9473366098055,\n          \"y\": 1993.2629089355469\n        },\n        \"dc-internal-1\": {\n          \"x\": 1958.4243458877936,\n          \"y\": 2845\n        },\n        \"dc-internal-2\": {\n          \"x\": 1963.768951182132,\n          \"y\": 2845\n        },\n        \"app-server-1\": {\n          \"x\": 1947.3819379304134,\n          \"y\": 2845\n        },\n        \"app-server-2\": {\n          \"x\": 1955.2862087394126,\n          \"y\": 2845\n        },\n        \"db-server-1\": {\n          \"x\": 1936.3708569559828,\n          \"y\": 2845\n        },\n        \"db-server-2\": {\n          \"x\": 1946.8300873488822,\n          \"y\": 2845\n        },\n        \"k8s-master-1\": {\n          \"x\": 1925.397658583093,\n          \"y\": 2845\n        },\n        \"k8s-master-2\": {\n          \"x\": 1938.405621494142,\n          \"y\": 2845\n        },\n        \"k8s-master-3\": {\n          \"x\": 1914.4688758763386,\n          \"y\": 2845\n        },\n        \"k8s-worker-1\": {\n          \"x\": 1930.017826812177,\n          \"y\": 2845\n        },\n        \"k8s-worker-2\": {\n          \"x\": 1903.5910154567553,\n          \"y\": 2845\n        },\n        \"k8s-worker-3\": {\n          \"x\": 1921.6716971072178,\n          \"y\": 2845\n        },\n        \"k8s-worker-4\": {\n          \"x\": 1892.7705536280016,\n          \"y\": 2845\n        },\n        \"proxy-server-1\": {\n          \"x\": 1806.1152433697903,\n          \"y\": 653.7529296875\n        },\n        \"proxy-server-2\": {\n          \"x\": 2937.4207928721535,\n          \"y\": 2628.7880859375\n        },\n        \"vpn-concentrator\": {\n          \"x\": 3642.252088474593,\n          \"y\": 946.7255249023438\n        },\n        \"nac-server\": {\n          \"x\": 1153.2626148502184,\n          \"y\": 1172.1895751953125\n        },\n        \"print-server\": {\n          \"x\": 1896.9328460745962,\n          \"y\": 2845\n        },\n        \"file-server\": {\n          \"x\": 1860.7177871362182,\n          \"y\": 2845\n        },\n        \"ca-server\": {\n          \"x\": 1888.8027739274805,\n          \"y\": 2845\n        },\n        \"sccm-server\": {\n          \"x\": 1850.1909418511675,\n          \"y\": 2845\n        },\n        \"voip-cluster\": {\n          \"x\": 1777.038465328039,\n          \"y\": 1616.8961181640625\n        },\n        \"video-conf\": {\n          \"x\": 1993.8373941679588,\n          \"y\": 2244.936309814453\n        },\n        \"security-cameras\": {\n          \"x\": 1674.413336949044,\n          \"y\": 2046.0380859375\n        },\n        \"nvr-cluster\": {\n          \"x\": 1829.4110389706402,\n          \"y\": 2845\n        },\n        \"dev-server-1\": {\n          \"x\": 2800.5894350649614,\n          \"y\": 1175.623291015625\n        },\n        \"dev-server-2\": {\n          \"x\": 1945.0822182484326,\n          \"y\": 1164.5184783935547\n        },\n        \"test-environment\": {\n          \"x\": 2932.0863047891075,\n          \"y\": 862.4592895507812\n        },\n        \"erp-system\": {\n          \"x\": 789.9880103985649,\n          \"y\": 473.7113342285156\n        },\n        \"crm-system\": {\n          \"x\": 3514.6003232048542,\n          \"y\": 1137.7720947265625\n        },\n        \"endpoint-1000\": {\n          \"x\": 991.6812012057328,\n          \"y\": 2284.42236328125\n        },\n        \"dist-switch-floor1\": {\n          \"x\": 654.2091033261356,\n          \"y\": 2020.0086669921875\n        },\n        \"dist-switch-floor2\": {\n          \"x\": 853.8845527112826,\n          \"y\": 1843.2872314453125\n        },\n        \"dist-switch-floor3\": {\n          \"x\": 1899.4353951584517,\n          \"y\": 1456.5068359375\n        },\n        \"dist-switch-floor4\": {\n          \"x\": 488.5289313756234,\n          \"y\": 181.47256469726562\n        },\n        \"ap-floor1-zone1\": {\n          \"x\": 1140.16846970184,\n          \"y\": 2070.2916259765625\n        },\n        \"ap-floor2-zone1\": {\n          \"x\": 688.1952143592268,\n          \"y\": 2384.4775390625\n        },\n        \"ap-floor3-zone1\": {\n          \"x\": 2145.3803027919676,\n          \"y\": 1890.2816162109375\n        },\n        \"ap-floor4-zone1\": {\n          \"x\": 517.646146409649,\n          \"y\": 565.59716796875\n        },\n        \"ups-dc-1\": {\n          \"x\": 771.1406786539856,\n          \"y\": 295.9266662597656\n        },\n        \"ups-dc-2\": {\n          \"x\": 216.2410855890687,\n          \"y\": 330.3345947265625\n        },\n        \"pdu-rack-a1\": {\n          \"x\": 1804.774444371901,\n          \"y\": 2845\n        },\n        \"pdu-rack-a2\": {\n          \"x\": 1741.6184034693686,\n          \"y\": 2845\n        },\n        \"cooling-1\": {\n          \"x\": 245.7080801919958,\n          \"y\": 626.1914672851562\n        },\n        \"cooling-2\": {\n          \"x\": 1603.293611085831,\n          \"y\": 981.0621185302734\n        },\n        \"camera-a\": {\n          \"x\": 166.57075412676295,\n          \"y\": 145\n        },\n        \"camera-a-copy\": {\n          \"x\": 1040.653076171875,\n          \"y\": 738.42822265625\n        }\n      },\n      \"sizes\": {\n        \"isp-secondary\": 139,\n        \"test-environment\": 121,\n        \"dev-server-1\": 128,\n        \"core-router-2\": 120,\n        \"camera-a\": 45,\n        \"camera-a-copy\": 45\n      },\n      \"styles\": {\n        \"dc-rack-b2\": {\n          \"all\": {\n            \"circleColor\": \"#ff0000\"\n          }\n        },\n        \"dc-rack-a1\": {\n          \"all\": {\n            \"circleColor\": \"#ff0000\"\n          }\n        },\n        \"dc-rack-b1\": {\n          \"all\": {\n            \"circleColor\": \"#ff0000\",\n            \"titleSize\": 59\n          }\n        },\n        \"isp-secondary\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"alist\"\n            },\n            \"circleColor\": \"#4d2c58\",\n            \"circleBorder\": \"#000000\",\n            \"titleColor\": \"#006eff\"\n          }\n        },\n        \"core-router-2\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"actual-budget\"\n            },\n            \"pingOffsetX\": -15,\n            \"pingOffsetY\": -38\n          }\n        },\n        \"fw-external-1\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"anonaddy\"\n            }\n          }\n        },\n        \"cloud-aws\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"ansible\"\n            }\n          }\n        },\n        \"isp-primary\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"wikidocs\"\n            }\n          }\n        },\n        \"branch-router-tokyo\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"adguard-home\"\n            }\n          }\n        },\n        \"core-router-1\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"borg\"\n            }\n          }\n        },\n        \"test-environment\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"simple\",\n              \"name\": \"apple\"\n            }\n          }\n        },\n        \"dev-server-1\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"simple\",\n              \"name\": \"amazonwebservices\"\n            }\n          }\n        }\n      },\n      \"legend\": {\n        \"#10b981\": \"Trusted Lan\",\n        \"#f59e0b\": \"Secure Lan\",\n        \"#ef4444\": \"DMZ\",\n        \"#475569\": \"Main ISP\",\n        \"#3b82f6\": \"Alternate ISP\",\n        \"#8b5cf6\": \"you can edit me too\",\n        \"#06b6d4\": \"you can edit me too\",\n        \"#a855f7\": \"you can edit me too\",\n        \"#f97316\": \"you can edit me too\",\n        \"#0ea5e9\": \"you can edit me too\",\n        \"#22c55e\": \"you can edit me too\",\n        \"#94a3b8\": \"you can edit me too\",\n        \"#fbbf24\": \"you can edit me too\",\n        \"#38bdf8\": \"you can edit me too\",\n        \"#c800ff\": \"you can edit me too\"\n      },\n      \"rects\": {\n        \"list\": [\n          {\n            \"id\": \"rect-1765237540610\",\n            \"x\": 2879.214599609375,\n            \"y\": 159.71981811523438,\n            \"width\": 992.196044921875,\n            \"height\": 538.8650817871094,\n            \"color\": \"#f97316\",\n            \"style\": \"filled\",\n            \"lineStyle\": \"solid\",\n            \"notes\": []\n          },\n          {\n            \"id\": \"rect-1765237681216\",\n            \"x\": 448.3926696777344,\n            \"y\": 1671.651123046875,\n            \"width\": 916.3436584472656,\n            \"height\": 924.27734375,\n            \"color\": \"#c800ff\",\n            \"style\": \"outlined\",\n            \"lineStyle\": \"solid\",\n            \"notes\": []\n          },\n          {\n            \"id\": \"rect-1766437913740\",\n            \"x\": 904.5889892578125,\n            \"y\": 115.40318298339844,\n            \"width\": 110.93878173828125,\n            \"height\": 919.6242218017578,\n            \"color\": \"#5215f9\",\n            \"style\": \"filled\",\n            \"lineStyle\": \"wall\",\n            \"notes\": [],\n            \"borderWidth\": 13\n          },\n          {\n            \"id\": \"rect-1766437935414\",\n            \"x\": 130.93685150146484,\n            \"y\": 1072.3624877929688,\n            \"width\": 872.9131851196289,\n            \"height\": 99.260986328125,\n            \"color\": \"#5215f9\",\n            \"style\": \"filled\",\n            \"lineStyle\": \"wall\",\n            \"notes\": [],\n            \"borderWidth\": 13\n          }\n        ]\n      },\n      \"texts\": {\n        \"list\": [\n          {\n            \"id\": \"text-1765237828167\",\n            \"x\": 3411.458740234375,\n            \"y\": 1390.00439453125,\n            \"content\": \"Double click on desktop\\nor long press on mobile\\nto enter rack canvas view\",\n            \"fontSize\": 46,\n            \"color\": \"#e2e8f0\",\n            \"fontWeight\": \"bold\",\n            \"fontStyle\": \"italic\",\n            \"textAlign\": \"middle\",\n            \"textDecoration\": \"none\",\n            \"bgColor\": \"#000000\",\n            \"bgEnabled\": false,\n            \"opacity\": 1\n          },\n          {\n            \"id\": \"text-1766446595277\",\n            \"x\": 654.3878479003906,\n            \"y\": 1367.7945556640625,\n            \"content\": \"SITE A\",\n            \"fontSize\": 101,\n            \"color\": \"#e2e8f0\",\n            \"fontWeight\": \"normal\",\n            \"fontStyle\": \"normal\",\n            \"textAlign\": \"start\",\n            \"textDecoration\": \"none\",\n            \"bgColor\": \"#000000\",\n            \"bgEnabled\": false,\n            \"opacity\": 1\n          },\n          {\n            \"id\": \"text-1766446610211\",\n            \"x\": 180.63662719726562,\n            \"y\": 1128.822998046875,\n            \"content\": \"SITE A\",\n            \"fontSize\": 101,\n            \"color\": \"#e2e8f0\",\n            \"fontWeight\": \"normal\",\n            \"fontStyle\": \"normal\",\n            \"textAlign\": \"start\",\n            \"textDecoration\": \"none\",\n            \"bgColor\": \"#000000\",\n            \"bgEnabled\": false,\n            \"opacity\": 1\n          },\n          {\n            \"id\": \"text-1766453024797\",\n            \"x\": 968.6458740234375,\n            \"y\": 1028.6621398925781,\n            \"content\": \"SITE A\",\n            \"fontSize\": 101,\n            \"color\": \"#e2e8f0\",\n            \"fontWeight\": \"normal\",\n            \"fontStyle\": \"normal\",\n            \"textAlign\": \"start\",\n            \"textDecoration\": \"none\",\n            \"bgColor\": \"#000000\",\n            \"bgEnabled\": false,\n            \"opacity\": 1,\n            \"rotation\": -89,\n            \"_dragStartX\": 972.46826171875,\n            \"_dragStartY\": 1009.5499572753906\n          },\n          {\n            \"id\": \"text-1766453070975\",\n            \"x\": 613.1589965820312,\n            \"y\": 1139.512939453125,\n            \"content\": \"SITE A\",\n            \"fontSize\": 101,\n            \"color\": \"#e2e8f0\",\n            \"fontWeight\": \"normal\",\n            \"fontStyle\": \"normal\",\n            \"textAlign\": \"start\",\n            \"textDecoration\": \"none\",\n            \"bgColor\": \"#000000\",\n            \"bgEnabled\": false,\n            \"opacity\": 1\n          },\n          {\n            \"id\": \"text-1766453072857\",\n            \"x\": 968.64599609375,\n            \"y\": 474.40818786621094,\n            \"content\": \"SITE A\",\n            \"fontSize\": 101,\n            \"color\": \"#e2e8f0\",\n            \"fontWeight\": \"normal\",\n            \"fontStyle\": \"normal\",\n            \"textAlign\": \"start\",\n            \"textDecoration\": \"none\",\n            \"bgColor\": \"#000000\",\n            \"bgEnabled\": false,\n            \"opacity\": 1,\n            \"rotation\": 269,\n            \"_dragStartX\": 1480.85302734375,\n            \"_dragStartY\": 822.2503356933594\n          },\n          {\n            \"id\": \"text-1766458222326\",\n            \"x\": 3167.812744140625,\n            \"y\": 2190.516357421875,\n            \"content\": \"\",\n            \"fontSize\": 18,\n            \"color\": \"#e2e8f0\",\n            \"fontWeight\": \"normal\",\n            \"fontStyle\": \"normal\",\n            \"textAlign\": \"start\",\n            \"textDecoration\": \"none\",\n            \"bgColor\": \"#000000\",\n            \"bgEnabled\": false,\n            \"opacity\": 1\n          }\n        ]\n      },\n      \"pageState\": {\n        \"title\": \"The One File Corporate\",\n        \"background\": \"\",\n        \"topbarBg\": \"rgba(9, 12, 20, 0.9)\",\n        \"topbarBorder\": \"#1f2533\",\n        \"panel\": \"#0b0e13\",\n        \"panelAlt\": \"#10141b\",\n        \"accent\": \"#4fd1c5\",\n        \"sidebarBg\": \"#10141b\",\n        \"btnBg\": \"#0b0e13\",\n        \"btnText\": \"#e2e8f0\",\n        \"tagFill\": \"#1e293b\",\n        \"tagText\": \"#e2e8f0\",\n        \"tagBorder\": \"#475569\",\n        \"inputBg\": \"#0b0e13\",\n        \"inputText\": \"#e2e8f0\",\n        \"inputBorder\": \"#1f2937\",\n        \"inputFont\": \"Inter, system-ui, sans-serif\",\n        \"inputFontSize\": 14,\n        \"toolbarBg\": \"#0f172a\",\n        \"toolbarBorder\": \"#1f2937\",\n        \"toolbarText\": \"#94a3b8\",\n        \"toolbarBtnBg\": \"#0b0e13\",\n        \"toolbarBtnText\": \"#e2e8f0\",\n        \"minimapDots\": \"#94a3b8\",\n        \"canvasHintEnabled\": true,\n        \"canvasHintText\": \"\",\n        \"canvasHintBg\": \"#0f172a\",\n        \"canvasHintColor\": \"#94a3b8\",\n        \"danger\": \"#f56565\",\n        \"textMain\": \"#e2e8f0\",\n        \"textSoft\": \"#94a3b8\",\n        \"topbarHeight\": 103,\n        \"sidebarWidth\": 350,\n        \"mobileFooterHeight\": 40,\n        \"sidebarCollapsed\": false,\n        \"nodeFill\": \"#1e293b\",\n        \"nodeStroke\": \"#475569\",\n        \"nodeTitle\": \"#e2e8f0\",\n        \"nodeSub\": \"#94a3b8\",\n        \"nodeTitleSize\": 41,\n        \"nodeSubSize\": 27,\n        \"nodeFont\": \"monospace\",\n        \"defaultEdge\": \"#475569\",\n        \"selectionHandle\": \"#f59e0b\",\n        \"selectionHandleSize\": 8,\n        \"groupIndicator\": \"#4fd1c5\",\n        \"canvasGradientTop\": \"#1e2532\",\n        \"canvasGradientBottom\": \"#050608\",\n        \"canvasBorder\": \"#475569\",\n        \"canvasGrid\": \"#475569\",\n        \"canvasGridSize\": 50,\n        \"canvasGridEnabled\": true,\n        \"rackFrameFill\": \"#0f172a\",\n        \"rackGridEnabled\": true,\n        \"rackFrameStroke\": \"#4fd1c5\",\n        \"rackLineColor\": \"#475569\",\n        \"rackTextColor\": \"#4fd1c5\",\n        \"viewOnly\": false,\n        \"defaultEdgeRouting\": \"orthogonal\",\n        \"animateConnections\": false,\n        \"animationStyle\": \"arrows\",\n        \"animationDirection\": \"all\",\n        \"animationSpeed\": 4,\n        \"autoPingEnabled\": false,\n        \"autoPingInterval\": 30\n      }\n    },\n    {\n      \"id\": \"tab-1765235136918\",\n      \"name\": \"Homelab 2\",\n      \"nodes\": {\n        \"internet\": {\n          \"shape\": \"stop-sign\",\n          \"name\": \"Internet\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"internet-copy\": {\n          \"shape\": \"firewall\",\n          \"name\": \"OPNSENSE\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"opnsense-copy\": {\n          \"shape\": \"firewall\",\n          \"name\": \"Docker\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"docker-copy\": {\n          \"shape\": \"firewall\",\n          \"name\": \"Docker2\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"docker-copy-1\": {\n          \"shape\": \"firewall\",\n          \"name\": \"Docker3\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"docker-copy-2\": {\n          \"shape\": \"firewall\",\n          \"name\": \"Docker 4\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [\n            {\n              \"type\": \"icon\",\n              \"library\": \"selfhst\",\n              \"name\": \"docker\"\n            },\n            {\n              \"type\": \"icon\",\n              \"library\": \"selfhst\",\n              \"name\": \"authentik\"\n            },\n            {\n              \"type\": \"icon\",\n              \"library\": \"selfhst\",\n              \"name\": \"immich\"\n            }\n          ],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"opnsense-copy-1\": {\n          \"shape\": \"firewall\",\n          \"name\": \"OPNSENSE GUEST\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"phone\": {\n          \"shape\": \"phone\",\n          \"name\": \"Phone\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"desktop\": {\n          \"shape\": \"pc\",\n          \"name\": \"Desktop\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dns\": {\n          \"shape\": \"cloud\",\n          \"name\": \"DNS\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"racked\": {\n          \"shape\": \"server\",\n          \"name\": \"Racked\",\n          \"ip\": \"\",\n          \"role\": \"Rack\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": true,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"thermostat\": {\n          \"shape\": \"thermostat\",\n          \"name\": \"Thermostat\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"video-doorbell\": {\n          \"shape\": \"doorbell\",\n          \"name\": \"Video Doorbell\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"smart-lock\": {\n          \"shape\": \"smart-lock\",\n          \"name\": \"Smart Lock\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"smart-bulb\": {\n          \"shape\": \"smart-bulb\",\n          \"name\": \"Smart Bulb\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"robot-vacuum\": {\n          \"shape\": \"vacuum\",\n          \"name\": \"Robot Vacuum\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        }\n      },\n      \"edges\": {\n        \"list\": [\n          {\n            \"id\": \"internet-internet-copy-1765238145151\",\n            \"from\": \"internet\",\n            \"to\": \"internet-copy\",\n            \"width\": 4,\n            \"color\": \"#55e208\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1\n          },\n          {\n            \"id\": \"internet-copy-opnsense-copy-1765238187451\",\n            \"from\": \"internet-copy\",\n            \"to\": \"opnsense-copy\",\n            \"width\": 4,\n            \"color\": \"#4c00ff\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1\n          },\n          {\n            \"id\": \"internet-copy-docker-copy-1765238242477\",\n            \"from\": \"internet-copy\",\n            \"to\": \"docker-copy\",\n            \"width\": 4,\n            \"color\": \"#4c00ff\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1\n          },\n          {\n            \"id\": \"internet-copy-docker-copy-1-1765238244637\",\n            \"from\": \"internet-copy\",\n            \"to\": \"docker-copy-1\",\n            \"width\": 4,\n            \"color\": \"#4c00ff\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1\n          },\n          {\n            \"id\": \"internet-copy-docker-copy-2-1765238246233\",\n            \"from\": \"internet-copy\",\n            \"to\": \"docker-copy-2\",\n            \"width\": 4,\n            \"color\": \"#4c00ff\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1\n          },\n          {\n            \"id\": \"internet-opnsense-copy-1-1765238266117\",\n            \"from\": \"internet\",\n            \"to\": \"opnsense-copy-1\",\n            \"width\": 4,\n            \"color\": \"#80ff00\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1\n          },\n          {\n            \"id\": \"opnsense-copy-1-dns-1765238347996\",\n            \"from\": \"opnsense-copy-1\",\n            \"to\": \"dns\",\n            \"width\": 4,\n            \"color\": \"#fb00ff\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1\n          },\n          {\n            \"id\": \"dns-desktop-1765238386101\",\n            \"from\": \"dns\",\n            \"to\": \"desktop\",\n            \"width\": 4,\n            \"color\": \"#ff00d0\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1\n          },\n          {\n            \"id\": \"phone-dns-1765238391156\",\n            \"from\": \"phone\",\n            \"to\": \"dns\",\n            \"width\": 4,\n            \"color\": \"#ff00d0\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1\n          },\n          {\n            \"id\": \"custom-1765239449323\",\n            \"type\": \"custom\",\n            \"color\": \"#f97316\",\n            \"width\": 4,\n            \"lineStyle\": \"solid\",\n            \"direction\": \"forward\",\n            \"points\": [\n              {\n                \"x\": 2936.464111328125,\n                \"y\": 786.07958984375\n              },\n              {\n                \"x\": 3184.112060546875,\n                \"y\": 887.6153564453125\n              },\n              {\n                \"x\": 2763.110107421875,\n                \"y\": 981.7216796875\n              }\n            ],\n            \"notes\": []\n          }\n        ]\n      },\n      \"positions\": {\n        \"internet\": {\n          \"x\": 1757.7735887323333,\n          \"y\": 298.77284240722656\n        },\n        \"internet-copy\": {\n          \"x\": 2066.9677515897347,\n          \"y\": 473.4119134177565\n        },\n        \"opnsense-copy\": {\n          \"x\": 1773.8400660428597,\n          \"y\": 666.5758233298659\n        },\n        \"docker-copy\": {\n          \"x\": 1931.1978950081452,\n          \"y\": 782.2775961320921\n        },\n        \"docker-copy-1\": {\n          \"x\": 2158.1262397347077,\n          \"y\": 767.7122274797483\n        },\n        \"docker-copy-2\": {\n          \"x\": 2342.2663764534577,\n          \"y\": 631.7681967180296\n        },\n        \"opnsense-copy-1\": {\n          \"x\": 2757.879480087803,\n          \"y\": 307.6117116091891\n        },\n        \"phone\": {\n          \"x\": 3312.857751572178,\n          \"y\": 502.58220111114224\n        },\n        \"desktop\": {\n          \"x\": 2971.700036728428,\n          \"y\": 480.7287465212985\n        },\n        \"dns\": {\n          \"x\": 3200.4643189549906,\n          \"y\": 320.469591247861\n        },\n        \"racked\": {\n          \"x\": 2645.5845448279656,\n          \"y\": 970.7820678889219\n        },\n        \"thermostat\": {\n          \"x\": 1323.0595481711202,\n          \"y\": 574.6132617105841\n        },\n        \"video-doorbell\": {\n          \"x\": 1188.4284446554952,\n          \"y\": 455.3684191812872\n        },\n        \"smart-lock\": {\n          \"x\": 1292.286782057839,\n          \"y\": 790.0231738199591\n        },\n        \"smart-bulb\": {\n          \"x\": 1496.156899245339,\n          \"y\": 716.9377246012091\n        },\n        \"robot-vacuum\": {\n          \"x\": 2288.5581443625265,\n          \"y\": 978.5069995035528\n        }\n      },\n      \"sizes\": {\n        \"core-router-1\": 36,\n        \"internet\": 168,\n        \"phone\": 121,\n        \"desktop\": 147,\n        \"racked\": 137,\n        \"docker-copy-2\": 82\n      },\n      \"styles\": {\n        \"internet\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"amazon-web-services\"\n            },\n            \"circleColor\": \"#db0000\",\n            \"circleBorder\": \"#000000\",\n            \"titleSize\": 52,\n            \"subSize\": 46\n          }\n        },\n        \"opnsense-copy-1\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"opnsense-v1\"\n            }\n          }\n        },\n        \"internet-copy\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"opnsense\"\n            }\n          }\n        },\n        \"docker-copy-2\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"docker\"\n            }\n          }\n        },\n        \"docker-copy-1\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"authportal\"\n            }\n          }\n        },\n        \"docker-copy\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"jotty\"\n            }\n          }\n        },\n        \"opnsense-copy\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"portainer\"\n            }\n          }\n        },\n        \"racked\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"mdi\",\n              \"name\": \"server-security\"\n            },\n            \"circleColor\": \"#010813\",\n            \"circleBorder\": \"#ffffff\"\n          }\n        }\n      },\n      \"legend\": {\n        \"#475569\": \"you can edit me too\",\n        \"#65758b\": \"you can edit me too\",\n        \"#63748c\": \"you can edit me too\",\n        \"#5e6f87\": \"you can edit me too\",\n        \"#586a84\": \"you can edit me too\",\n        \"#4f627d\": \"you can edit me too\",\n        \"#455873\": \"you can edit me too\",\n        \"#3d506c\": \"you can edit me too\",\n        \"#354964\": \"you can edit me too\",\n        \"#2e415c\": \"you can edit me too\",\n        \"#293c56\": \"you can edit me too\",\n        \"#273a53\": \"you can edit me too\",\n        \"#253750\": \"you can edit me too\",\n        \"#23354d\": \"you can edit me too\",\n        \"#203046\": \"you can edit me too\",\n        \"#1e2d43\": \"you can edit me too\",\n        \"#1a283d\": \"you can edit me too\",\n        \"#172435\": \"you can edit me too\",\n        \"#141f2e\": \"you can edit me too\",\n        \"#111a27\": \"you can edit me too\",\n        \"#0f1824\": \"you can edit me too\",\n        \"#0d1521\": \"you can edit me too\",\n        \"#0c131d\": \"you can edit me too\",\n        \"#0c1d1c\": \"you can edit me too\",\n        \"#0c1c1d\": \"you can edit me too\",\n        \"#0c191d\": \"you can edit me too\",\n        \"#0c141d\": \"you can edit me too\",\n        \"#0c0d1d\": \"you can edit me too\",\n        \"#130c1d\": \"you can edit me too\",\n        \"#1b0c1d\": \"you can edit me too\",\n        \"#1d0c17\": \"you can edit me too\",\n        \"#1d0c10\": \"you can edit me too\",\n        \"#1d0c0c\": \"you can edit me too\",\n        \"#3b1b1b\": \"you can edit me too\",\n        \"#3c1a1a\": \"you can edit me too\",\n        \"#3f1c1c\": \"you can edit me too\",\n        \"#401c1c\": \"you can edit me too\",\n        \"#451c1c\": \"you can edit me too\",\n        \"#461b1b\": \"you can edit me too\",\n        \"#4c1a1a\": \"you can edit me too\",\n        \"#521919\": \"you can edit me too\",\n        \"#571919\": \"you can edit me too\",\n        \"#5d1818\": \"you can edit me too\",\n        \"#631717\": \"you can edit me too\",\n        \"#651515\": \"you can edit me too\",\n        \"#6a1616\": \"you can edit me too\",\n        \"#6f1515\": \"you can edit me too\",\n        \"#711414\": \"you can edit me too\",\n        \"#761414\": \"you can edit me too\",\n        \"#771313\": \"you can edit me too\",\n        \"#7c1313\": \"you can edit me too\",\n        \"#811313\": \"you can edit me too\",\n        \"#821212\": \"you can edit me too\",\n        \"#871212\": \"you can edit me too\",\n        \"#881111\": \"you can edit me too\",\n        \"#8d1111\": \"you can edit me too\",\n        \"#8e1010\": \"you can edit me too\",\n        \"#8f0f0f\": \"you can edit me too\",\n        \"#900e0e\": \"you can edit me too\",\n        \"#8e0b0b\": \"you can edit me too\",\n        \"#8c0d0d\": \"you can edit me too\",\n        \"#880c0c\": \"you can edit me too\",\n        \"#830c0c\": \"you can edit me too\",\n        \"#7e0c0c\": \"you can edit me too\",\n        \"#790c0c\": \"you can edit me too\",\n        \"#730c0c\": \"you can edit me too\",\n        \"#6f0b0b\": \"you can edit me too\",\n        \"#0b6f64\": \"you can edit me too\",\n        \"#0b6f5f\": \"you can edit me too\",\n        \"#0b6f56\": \"you can edit me too\",\n        \"#0b6f49\": \"you can edit me too\",\n        \"#0b6f31\": \"you can edit me too\",\n        \"#0b6f1f\": \"you can edit me too\",\n        \"#0b6f0d\": \"you can edit me too\",\n        \"#176f0b\": \"you can edit me too\",\n        \"#266f0b\": \"you can edit me too\",\n        \"#296f0b\": \"you can edit me too\",\n        \"#2e6f0b\": \"you can edit me too\",\n        \"#1a2d10\": \"you can edit me too\",\n        \"#1c3111\": \"you can edit me too\",\n        \"#213814\": \"you can edit me too\",\n        \"#233c15\": \"you can edit me too\",\n        \"#254017\": \"you can edit me too\",\n        \"#294918\": \"you can edit me too\",\n        \"#2b4d1a\": \"you can edit me too\",\n        \"#2d511a\": \"you can edit me too\",\n        \"#315a1b\": \"you can edit me too\",\n        \"#35631c\": \"you can edit me too\",\n        \"#37681d\": \"you can edit me too\",\n        \"#3b721d\": \"you can edit me too\",\n        \"#3f7b1e\": \"you can edit me too\",\n        \"#42851e\": \"you can edit me too\",\n        \"#46901d\": \"you can edit me too\",\n        \"#499a1d\": \"you can edit me too\",\n        \"#4b9f1d\": \"you can edit me too\",\n        \"#4ca61c\": \"you can edit me too\",\n        \"#50b01c\": \"you can edit me too\",\n        \"#51b71a\": \"you can edit me too\",\n        \"#50b918\": \"you can edit me too\",\n        \"#51c115\": \"you can edit me too\",\n        \"#53c615\": \"you can edit me too\",\n        \"#53c814\": \"you can edit me too\",\n        \"#52c913\": \"you can edit me too\",\n        \"#54d011\": \"you can edit me too\",\n        \"#53d110\": \"you can edit me too\",\n        \"#55d510\": \"you can edit me too\",\n        \"#55d70f\": \"you can edit me too\",\n        \"#54d80e\": \"you can edit me too\",\n        \"#54da0b\": \"you can edit me too\",\n        \"#56df0c\": \"you can edit me too\",\n        \"#53db0a\": \"you can edit me too\",\n        \"#55e00b\": \"you can edit me too\",\n        \"#55e109\": \"you can edit me too\",\n        \"#55e208\": \"ISP LINE\",\n        \"#4c00ff\": \"MY Guest NETWORK\",\n        \"#80ff00\": \"you can edit me too\",\n        \"#3b4234\": \"you can edit me too\",\n        \"#3a3442\": \"you can edit me too\",\n        \"#3b3442\": \"you can edit me too\",\n        \"#3c3442\": \"you can edit me too\",\n        \"#3d3442\": \"you can edit me too\",\n        \"#3e3442\": \"you can edit me too\",\n        \"#3f3442\": \"you can edit me too\",\n        \"#403442\": \"you can edit me too\",\n        \"#413442\": \"you can edit me too\",\n        \"#653d66\": \"you can edit me too\",\n        \"#683f69\": \"you can edit me too\",\n        \"#6c416c\": \"you can edit me too\",\n        \"#6f4370\": \"you can edit me too\",\n        \"#704270\": \"you can edit me too\",\n        \"#734474\": \"you can edit me too\",\n        \"#784479\": \"you can edit me too\",\n        \"#7d447e\": \"you can edit me too\",\n        \"#7e437f\": \"you can edit me too\",\n        \"#834384\": \"you can edit me too\",\n        \"#844285\": \"you can edit me too\",\n        \"#89418b\": \"you can edit me too\",\n        \"#8e428f\": \"you can edit me too\",\n        \"#904091\": \"you can edit me too\",\n        \"#923e93\": \"you can edit me too\",\n        \"#973e98\": \"you can edit me too\",\n        \"#943c96\": \"you can edit me too\",\n        \"#993c9a\": \"you can edit me too\",\n        \"#963a98\": \"you can edit me too\",\n        \"#973899\": \"you can edit me too\",\n        \"#99369b\": \"you can edit me too\",\n        \"#9a359c\": \"you can edit me too\",\n        \"#9b349d\": \"you can edit me too\",\n        \"#9d329f\": \"you can edit me too\",\n        \"#9e31a0\": \"you can edit me too\",\n        \"#a02fa2\": \"you can edit me too\",\n        \"#9d2d9f\": \"you can edit me too\",\n        \"#9f2ba1\": \"you can edit me too\",\n        \"#a129a3\": \"you can edit me too\",\n        \"#a327a5\": \"you can edit me too\",\n        \"#a525a7\": \"you can edit me too\",\n        \"#a723a9\": \"you can edit me too\",\n        \"#a921ab\": \"you can edit me too\",\n        \"#ab1fad\": \"you can edit me too\",\n        \"#ad1daf\": \"you can edit me too\",\n        \"#ae1cb0\": \"you can edit me too\",\n        \"#b019b3\": \"you can edit me too\",\n        \"#b118b4\": \"you can edit me too\",\n        \"#b316b6\": \"you can edit me too\",\n        \"#b816bb\": \"you can edit me too\",\n        \"#b514b8\": \"you can edit me too\",\n        \"#ba14bd\": \"you can edit me too\",\n        \"#b712ba\": \"you can edit me too\",\n        \"#bb13be\": \"you can edit me too\",\n        \"#b811bb\": \"you can edit me too\",\n        \"#be10c1\": \"you can edit me too\",\n        \"#bb0ebe\": \"you can edit me too\",\n        \"#bd0cc0\": \"you can edit me too\",\n        \"#be0bc1\": \"you can edit me too\",\n        \"#c108c4\": \"you can edit me too\",\n        \"#be06c1\": \"you can edit me too\",\n        \"#c103c4\": \"you can edit me too\",\n        \"#c301c6\": \"you can edit me too\",\n        \"#c400c7\": \"you can edit me too\",\n        \"#c900cc\": \"you can edit me too\",\n        \"#ce00d1\": \"you can edit me too\",\n        \"#d300d6\": \"you can edit me too\",\n        \"#d800db\": \"you can edit me too\",\n        \"#dd00e0\": \"you can edit me too\",\n        \"#e200e6\": \"you can edit me too\",\n        \"#ec00f0\": \"you can edit me too\",\n        \"#f100f5\": \"you can edit me too\",\n        \"#f600fa\": \"you can edit me too\",\n        \"#fb00ff\": \"you can edit me too\",\n        \"#ff00d0\": \"iPhone (always guest iPhone)\",\n        \"#f97316\": \"you can edit me too\"\n      },\n      \"rects\": {\n        \"list\": [\n          {\n            \"id\": \"rect-1765238219615\",\n            \"x\": 2680.053955078125,\n            \"y\": 251.44879150390625,\n            \"width\": 814.10400390625,\n            \"height\": 389.26678466796875,\n            \"color\": \"#ec0999\",\n            \"style\": \"filled\",\n            \"lineStyle\": \"solid\",\n            \"notes\": []\n          }\n        ]\n      },\n      \"texts\": {\n        \"list\": [\n          {\n            \"id\": \"text-1765238422602\",\n            \"x\": 2466.35986328125,\n            \"y\": 741.6801147460938,\n            \"content\": \"Double click on desktop\\nor long press on mobile\\nto enter rack canvas view\",\n            \"fontSize\": 40,\n            \"color\": \"#e2e8f0\",\n            \"fontWeight\": \"normal\",\n            \"fontStyle\": \"normal\",\n            \"textAlign\": \"start\",\n            \"textDecoration\": \"none\",\n            \"bgColor\": \"#000000\",\n            \"bgEnabled\": false,\n            \"opacity\": 1\n          }\n        ]\n      },\n      \"pageState\": {\n        \"title\": \"The One File\",\n        \"background\": \"\",\n        \"topbarBg\": \"rgba(9, 12, 20, 0.9)\",\n        \"topbarBorder\": \"#1f2533\",\n        \"panel\": \"#2f0e0e\",\n        \"panelAlt\": \"#10141b\",\n        \"accent\": \"#a75252\",\n        \"sidebarBg\": \"#10141b\",\n        \"btnBg\": \"#0b0e13\",\n        \"btnText\": \"#e2e8f0\",\n        \"tagFill\": \"#1e293b\",\n        \"tagText\": \"#e2e8f0\",\n        \"tagBorder\": \"#475569\",\n        \"inputBg\": \"#0b0e13\",\n        \"inputText\": \"#e2e8f0\",\n        \"inputBorder\": \"#1f2937\",\n        \"inputFont\": \"Inter, system-ui, sans-serif\",\n        \"inputFontSize\": 14,\n        \"toolbarBg\": \"#441215\",\n        \"toolbarBorder\": \"#1f2937\",\n        \"toolbarText\": \"#94a3b8\",\n        \"toolbarBtnBg\": \"#0b0e13\",\n        \"toolbarBtnText\": \"#e2e8f0\",\n        \"minimapDots\": \"#94a3b8\",\n        \"canvasHintEnabled\": true,\n        \"canvasHintText\": \"\",\n        \"canvasHintBg\": \"#0f172a\",\n        \"canvasHintColor\": \"#94a3b8\",\n        \"danger\": \"#f56565\",\n        \"textMain\": \"#e2e8f0\",\n        \"textSoft\": \"#94a3b8\",\n        \"topbarHeight\": 112,\n        \"sidebarWidth\": 350,\n        \"mobileFooterHeight\": 40,\n        \"sidebarCollapsed\": false,\n        \"nodeFill\": \"#1e293b\",\n        \"nodeStroke\": \"#475569\",\n        \"nodeTitle\": \"#e2e8f0\",\n        \"nodeSub\": \"#94a3b8\",\n        \"nodeTitleSize\": 18,\n        \"nodeSubSize\": 13,\n        \"nodeFont\": \"Inter, system-ui, sans-serif\",\n        \"defaultEdge\": \"#475569\",\n        \"selectionHandle\": \"#f59e0b\",\n        \"selectionHandleSize\": 8,\n        \"groupIndicator\": \"#4fd1c5\",\n        \"canvasGradientTop\": \"#1e2532\",\n        \"canvasGradientBottom\": \"#050608\",\n        \"canvasBorder\": \"#475569\",\n        \"canvasGrid\": \"#475569\",\n        \"canvasGridSize\": 50,\n        \"canvasGridEnabled\": true,\n        \"rackFrameFill\": \"#0f172a\",\n        \"rackGridEnabled\": true,\n        \"rackFrameStroke\": \"#4fd1c5\",\n        \"rackLineColor\": \"#475569\",\n        \"rackTextColor\": \"#4fd1c5\",\n        \"viewOnly\": false,\n        \"defaultEdgeRouting\": \"curved\",\n        \"animateConnections\": false,\n        \"animationStyle\": \"arrows\",\n        \"animationDirection\": \"all\",\n        \"animationSpeed\": 1.5\n      }\n    }\n  ],\n  \"currentTabIndex\": 0,\n  \"encryptedSections\": {},\n  \"auditLog\": [\n    {\n      \"timestamp\": 1766459410068,\n      \"type\": \"export\",\n      \"description\": \"Exported Markdown: the-one-file-corporate.md\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459403229,\n      \"type\": \"export\",\n      \"description\": \"Exported CSV: the-one-file-corporate.csv\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459400738,\n      \"type\": \"tab\",\n      \"description\": \"Switched to tab: Corporate Site B\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459392434,\n      \"type\": \"export\",\n      \"description\": \"Exported CSV: the-one-file.csv\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766459386369,\n      \"type\": \"export\",\n      \"description\": \"Exported Markdown: the-one-file.md\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766459379962,\n      \"type\": \"export\",\n      \"description\": \"Exported JSON: the-one-file.json\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766459374396,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file.html\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766459370112,\n      \"type\": \"tab\",\n      \"description\": \"Switched to tab: Homelab 2\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766459361896,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file-corporate.html\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459352785,\n      \"type\": \"node\",\n      \"description\": \"rotate node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459352343,\n      \"type\": \"node\",\n      \"description\": \"rotate node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459352224,\n      \"type\": \"node\",\n      \"description\": \"rotate node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459351722,\n      \"type\": \"node\",\n      \"description\": \"rotate node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459351541,\n      \"type\": \"node\",\n      \"description\": \"rotate node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459350380,\n      \"type\": \"node\",\n      \"description\": \"resize node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459350178,\n      \"type\": \"node\",\n      \"description\": \"resize node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459350049,\n      \"type\": \"node\",\n      \"description\": \"resize node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459346233,\n      \"type\": \"node\",\n      \"description\": \"change shape\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459335960,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459335846,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459335742,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459335630,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459335398,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459335292,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459335188,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459332894,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459332780,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459332661,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459332556,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459332450,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459332346,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459331643,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459331492,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459331378,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459331274,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459330996,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459330868,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459330764,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459330637,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459327262,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459327136,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459326544,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459326438,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459326334,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459326176,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459325232,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459325088,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459324279,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459323835,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459323732,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459323200,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459323093,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459322989,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459322883,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459322780,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459321176,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459321070,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459320748,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459320642,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459320492,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459319706,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459319600,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459319055,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459318467,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459318363,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459318258,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459317846,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459317742,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459317464,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459317314,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459313457,\n      \"type\": \"node\",\n      \"description\": \"change shape\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459310142,\n      \"type\": \"node\",\n      \"description\": \"change shape\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459306160,\n      \"type\": \"node\",\n      \"description\": \"rotate node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459305289,\n      \"type\": \"node\",\n      \"description\": \"rotate node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459305132,\n      \"type\": \"node\",\n      \"description\": \"rotate node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459304675,\n      \"type\": \"node\",\n      \"description\": \"rotate node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459304530,\n      \"type\": \"node\",\n      \"description\": \"rotate node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459304396,\n      \"type\": \"node\",\n      \"description\": \"rotate node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459304290,\n      \"type\": \"node\",\n      \"description\": \"rotate node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459304157,\n      \"type\": \"node\",\n      \"description\": \"rotate node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459303660,\n      \"type\": \"node\",\n      \"description\": \"rotate node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459303534,\n      \"type\": \"node\",\n      \"description\": \"rotate node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459303414,\n      \"type\": \"node\",\n      \"description\": \"rotate node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459303247,\n      \"type\": \"node\",\n      \"description\": \"rotate node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459303144,\n      \"type\": \"node\",\n      \"description\": \"rotate node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459303002,\n      \"type\": \"node\",\n      \"description\": \"rotate node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459302875,\n      \"type\": \"node\",\n      \"description\": \"rotate node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459302725,\n      \"type\": \"node\",\n      \"description\": \"rotate node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459302613,\n      \"type\": \"node\",\n      \"description\": \"rotate node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459302507,\n      \"type\": \"node\",\n      \"description\": \"rotate node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459301997,\n      \"type\": \"node\",\n      \"description\": \"rotate node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459301893,\n      \"type\": \"node\",\n      \"description\": \"rotate node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458459721,\n      \"type\": \"tab\",\n      \"description\": \"Switched to tab: Corporate Site B\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458438687,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458438583,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458438437,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458438333,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458438187,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458438083,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458437937,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458437833,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458437687,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458437583,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458437437,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458437333,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458437187,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458436932,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458435139,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458434986,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458434840,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458434736,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458434590,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458434486,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458434340,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458434236,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458434090,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458433986,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458433840,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458433736,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458433590,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458433334,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458429157,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458429053,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458428947,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458426794,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458426691,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458426584,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458426481,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458423513,\n      \"type\": \"node\",\n      \"description\": \"change shape\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458421278,\n      \"type\": \"node\",\n      \"description\": \"change shape\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458416555,\n      \"type\": \"node\",\n      \"description\": \"change shape\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458404891,\n      \"type\": \"node\",\n      \"description\": \"add node\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458392272,\n      \"type\": \"node\",\n      \"description\": \"add node\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458378068,\n      \"type\": \"node\",\n      \"description\": \"add node\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458367460,\n      \"type\": \"node\",\n      \"description\": \"add node\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458356226,\n      \"type\": \"node\",\n      \"description\": \"add node\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458338198,\n      \"type\": \"tab\",\n      \"description\": \"Switched to tab: Homelab 2\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458258865,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file-corporate.html\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458249051,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458248926,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458248793,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458248683,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458248556,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458248451,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458248325,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458248221,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458248092,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458247989,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458247885,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458247784,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458247284,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458246701,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458246523,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458246410,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458246129,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458245955,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458245737,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458245627,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458245425,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458245247,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458245133,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458244923,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458244741,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458244313,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458244198,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458244055,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458243873,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458243637,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458243399,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458243218,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458241018,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458237254,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458235033,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458234835,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458234694,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458234425,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458227773,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458227623,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458227441,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458227279,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458227155,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458226967,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458226847,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458226733,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458226563,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458226421,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458222326,\n      \"type\": \"text\",\n      \"description\": \"add text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458213989,\n      \"type\": \"connection\",\n      \"description\": \"delete edge\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458209437,\n      \"type\": \"text\",\n      \"description\": \"delete text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458195427,\n      \"type\": \"import\",\n      \"description\": \"Imported JSON: the-one-file-corporate.json (107 nodes, 65 connections)\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455847368,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455844534,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455844054,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455843762,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455843560,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455843371,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455843162,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455842852,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455842747,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455842601,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455842449,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455842348,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455842098,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455841678,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455841236,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455841053,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455840901,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455840650,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455839427,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455839234,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455839061,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455837247,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455837081,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455836893,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455836377,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455836198,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455835455,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455834630,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455831880,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455831676,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455831451,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455830817,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455830687,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455830176,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455830048,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455829944,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455829816,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455378795,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455378693,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455378459,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455378316,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455378180,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455378069,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455377956,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455377677,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455377558,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455377448,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455377318,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455377209,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453090534,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453090317,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453090213,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453090112,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453090009,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453089903,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453088895,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453088793,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453088689,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453088584,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453088480,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453088250,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453087236,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453086725,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453086485,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453086373,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453086142,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453086043,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453072857,\n      \"type\": \"text\",\n      \"description\": \"paste text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453070975,\n      \"type\": \"text\",\n      \"description\": \"paste text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453054439,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453053127,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453052450,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453052106,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453051948,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453051806,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453051334,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453050207,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453042725,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453042179,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453041797,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453041570,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453039703,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453039291,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453039168,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453039065,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453038481,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453038365,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453038237,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453038105,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453038001,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453037850,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453037745,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453037495,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453037378,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453037182,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453037078,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453036972,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453036860,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453036147,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453035945,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453035825,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453035720,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453035443,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453035337,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453035233,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453035127,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453035026,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453034917,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453031063,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453030955,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453030833,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453030732,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453030225,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453030104,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453029968,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453029796,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453029474,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453024797,\n      \"type\": \"text\",\n      \"description\": \"paste text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766451118553,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450929324,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450817210,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450257424,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450255024,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450254395,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450253241,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450251598,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450250392,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450248756,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450244072,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450242166,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450240998,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450236492,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450233672,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450232384,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450231012,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450230254,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450229302,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450228132,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446610211,\n      \"type\": \"text\",\n      \"description\": \"paste text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446604849,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446604702,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446604550,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446604404,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446604305,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446604204,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446604099,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446603952,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446603849,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446603702,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446603599,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446603452,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446603348,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446603202,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446603099,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446602953,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446602850,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446602702,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446602600,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446602453,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446602349,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446602204,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446602101,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446602000,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446601848,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446601702,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446601601,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446601452,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446601301,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446601154,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446601049,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446600948,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446600802,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446600550,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446598595,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446598461,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446598171,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446598017,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446597219,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446595278,\n      \"type\": \"text\",\n      \"description\": \"add text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445633355,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445632515,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445631735,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445630757,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445627846,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445625085,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445618645,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445617784,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445608998,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445608720,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445608540,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445608376,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445608204,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445608038,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445607852,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445607678,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445607506,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445607319,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445607154,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445604410,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445604244,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445604066,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445603900,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445603743,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445603563,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445603406,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445603226,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445603052,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445602880,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445602641,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445576567,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animation speed\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445570290,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animation speed\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445567192,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445566766,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445565520,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445398115,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445390895,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445385694,\n      \"type\": \"edit\",\n      \"description\": \"toggle fov animation\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445383241,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445382911,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445381695,\n      \"type\": \"edit\",\n      \"description\": \"edit node name\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445375383,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445374665,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445373273,\n      \"type\": \"node\",\n      \"description\": \"paste node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445372205,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438157980,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438157430,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animation speed\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438152691,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438151948,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438151286,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438146174,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438145649,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438144555,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438143655,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438142504,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animation speed\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438130077,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438129561,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438128772,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438128398,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438122820,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438122062,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438119836,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438119588,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438095045,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438093965,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438062827,\n      \"type\": \"edit\",\n      \"description\": \"toggle fov animation\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438047679,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438044161,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438041852,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438039668,\n      \"type\": \"edit\",\n      \"description\": \"resize node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438039562,\n      \"type\": \"edit\",\n      \"description\": \"resize node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438039421,\n      \"type\": \"edit\",\n      \"description\": \"resize node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438039260,\n      \"type\": \"edit\",\n      \"description\": \"resize node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438039150,\n      \"type\": \"edit\",\n      \"description\": \"resize node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438039039,\n      \"type\": \"edit\",\n      \"description\": \"resize node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438028508,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438021410,\n      \"type\": \"edit\",\n      \"description\": \"toggle fov\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438019234,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438017562,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438014356,\n      \"type\": \"node\",\n      \"description\": \"add node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437981696,\n      \"type\": \"edit\",\n      \"description\": \"apply routing to all\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437966551,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437964879,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437963627,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437961813,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437961193,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437957989,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437956467,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437953437,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437952239,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437950807,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437944990,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437943699,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437935414,\n      \"type\": \"zone\",\n      \"description\": \"draw zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437919019,\n      \"type\": \"zone\",\n      \"description\": \"delete zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437917758,\n      \"type\": \"zone\",\n      \"description\": \"draw zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437913740,\n      \"type\": \"zone\",\n      \"description\": \"draw zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437882832,\n      \"type\": \"import\",\n      \"description\": \"Imported JSON: theonefile-networkening-corporate-demo.json (105 nodes, 65 connections)\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766263279163,\n      \"type\": \"tab\",\n      \"description\": \"Switched to tab: Corporate Site B\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766263270414,\n      \"type\": \"export\",\n      \"description\": \"Exported JSON: the-one-file.json\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766263260682,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file.html\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766263259518,\n      \"type\": \"tab\",\n      \"description\": \"Switched to tab: Homelab 2\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766263249401,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file-corporate.html\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766263246362,\n      \"type\": \"import\",\n      \"description\": \"Imported JSON: theonefile-networkening-corporate-demo.json (105 nodes, 65 connections)\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766190721141,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file-corporate.html\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766190717499,\n      \"type\": \"tab\",\n      \"description\": \"Switched to tab: Corporate Site B\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766190710946,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file.html\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766190705273,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file.html\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766190703463,\n      \"type\": \"tab\",\n      \"description\": \"Switched to tab: Homelab 2\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766190695709,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file-corporate.html\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766190688417,\n      \"type\": \"import\",\n      \"description\": \"Imported JSON: theonefile-networkening-corporate-demo.json (105 nodes, 65 connections)\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1765402888416,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file-corporate.html\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1765402884873,\n      \"type\": \"tab\",\n      \"description\": \"Switched to tab: Corporate Site B\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1765402878108,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file.html\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1765402866440,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file.html\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1765402865008,\n      \"type\": \"tab\",\n      \"description\": \"Switched to tab: Homelab 2\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1765402860428,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file-corporate.html\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1765402858103,\n      \"type\": \"import\",\n      \"description\": \"Imported JSON: theonefile-networkening-corporate-demo.json (105 nodes, 65 connections)\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    }\n  ],\n  \"savedStyleSets\": []\n}"
  },
  {
    "path": "demos/json-exports/the-one-file-homelab-demo.json",
    "content": "{\n  \"nodeData\": {\n    \"internet\": {\n      \"shape\": \"stop-sign\",\n      \"name\": \"Internet\",\n      \"ip\": \"0.0.0.0\",\n      \"role\": \"\",\n      \"tags\": [],\n      \"notes\": [],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"internet-copy\": {\n      \"shape\": \"firewall\",\n      \"name\": \"OPNSENSE\",\n      \"ip\": \"0.0.0.0\",\n      \"role\": \"\",\n      \"tags\": [],\n      \"notes\": [],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"opnsense-copy\": {\n      \"shape\": \"firewall\",\n      \"name\": \"Docker\",\n      \"ip\": \"0.0.0.0\",\n      \"role\": \"\",\n      \"tags\": [],\n      \"notes\": [],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"docker-copy\": {\n      \"shape\": \"firewall\",\n      \"name\": \"Docker2\",\n      \"ip\": \"0.0.0.0\",\n      \"role\": \"\",\n      \"tags\": [],\n      \"notes\": [],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"docker-copy-1\": {\n      \"shape\": \"firewall\",\n      \"name\": \"Docker3\",\n      \"ip\": \"0.0.0.0\",\n      \"role\": \"\",\n      \"tags\": [],\n      \"notes\": [],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"docker-copy-2\": {\n      \"shape\": \"firewall\",\n      \"name\": \"Docker 4\",\n      \"ip\": \"0.0.0.0\",\n      \"role\": \"\",\n      \"tags\": [\n        {\n          \"type\": \"icon\",\n          \"library\": \"selfhst\",\n          \"name\": \"docker\"\n        },\n        {\n          \"type\": \"icon\",\n          \"library\": \"selfhst\",\n          \"name\": \"authentik\"\n        },\n        {\n          \"type\": \"icon\",\n          \"library\": \"selfhst\",\n          \"name\": \"immich\"\n        }\n      ],\n      \"notes\": [],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"opnsense-copy-1\": {\n      \"shape\": \"firewall\",\n      \"name\": \"OPNSENSE GUEST\",\n      \"ip\": \"0.0.0.0\",\n      \"role\": \"\",\n      \"tags\": [],\n      \"notes\": [],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"phone\": {\n      \"shape\": \"phone\",\n      \"name\": \"Phone\",\n      \"ip\": \"0.0.0.0\",\n      \"role\": \"\",\n      \"tags\": [],\n      \"notes\": [],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"desktop\": {\n      \"shape\": \"pc\",\n      \"name\": \"Desktop\",\n      \"ip\": \"0.0.0.0\",\n      \"role\": \"\",\n      \"tags\": [],\n      \"notes\": [],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"dns\": {\n      \"shape\": \"cloud\",\n      \"name\": \"DNS\",\n      \"ip\": \"0.0.0.0\",\n      \"role\": \"\",\n      \"tags\": [],\n      \"notes\": [],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"racked\": {\n      \"shape\": \"server\",\n      \"name\": \"Racked\",\n      \"ip\": \"\",\n      \"role\": \"Rack\",\n      \"tags\": [],\n      \"notes\": [],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": true,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"thermostat\": {\n      \"shape\": \"thermostat\",\n      \"name\": \"Thermostat\",\n      \"ip\": \"0.0.0.0\",\n      \"role\": \"\",\n      \"tags\": [],\n      \"notes\": [],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"video-doorbell\": {\n      \"shape\": \"doorbell\",\n      \"name\": \"Video Doorbell\",\n      \"ip\": \"0.0.0.0\",\n      \"role\": \"\",\n      \"tags\": [],\n      \"notes\": [],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"smart-lock\": {\n      \"shape\": \"smart-lock\",\n      \"name\": \"Smart Lock\",\n      \"ip\": \"0.0.0.0\",\n      \"role\": \"\",\n      \"tags\": [],\n      \"notes\": [],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"smart-bulb\": {\n      \"shape\": \"smart-bulb\",\n      \"name\": \"Smart Bulb\",\n      \"ip\": \"0.0.0.0\",\n      \"role\": \"\",\n      \"tags\": [],\n      \"notes\": [],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"robot-vacuum\": {\n      \"shape\": \"vacuum\",\n      \"name\": \"Robot Vacuum\",\n      \"ip\": \"0.0.0.0\",\n      \"role\": \"\",\n      \"tags\": [],\n      \"notes\": [],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    }\n  },\n  \"edgeData\": {\n    \"list\": [\n      {\n        \"id\": \"internet-internet-copy-1765238145151\",\n        \"from\": \"internet\",\n        \"to\": \"internet-copy\",\n        \"width\": 4,\n        \"color\": \"#55e208\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1\n      },\n      {\n        \"id\": \"internet-copy-opnsense-copy-1765238187451\",\n        \"from\": \"internet-copy\",\n        \"to\": \"opnsense-copy\",\n        \"width\": 4,\n        \"color\": \"#4c00ff\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1\n      },\n      {\n        \"id\": \"internet-copy-docker-copy-1765238242477\",\n        \"from\": \"internet-copy\",\n        \"to\": \"docker-copy\",\n        \"width\": 4,\n        \"color\": \"#4c00ff\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1\n      },\n      {\n        \"id\": \"internet-copy-docker-copy-1-1765238244637\",\n        \"from\": \"internet-copy\",\n        \"to\": \"docker-copy-1\",\n        \"width\": 4,\n        \"color\": \"#4c00ff\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1\n      },\n      {\n        \"id\": \"internet-copy-docker-copy-2-1765238246233\",\n        \"from\": \"internet-copy\",\n        \"to\": \"docker-copy-2\",\n        \"width\": 4,\n        \"color\": \"#4c00ff\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1\n      },\n      {\n        \"id\": \"internet-opnsense-copy-1-1765238266117\",\n        \"from\": \"internet\",\n        \"to\": \"opnsense-copy-1\",\n        \"width\": 4,\n        \"color\": \"#80ff00\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1\n      },\n      {\n        \"id\": \"opnsense-copy-1-dns-1765238347996\",\n        \"from\": \"opnsense-copy-1\",\n        \"to\": \"dns\",\n        \"width\": 4,\n        \"color\": \"#fb00ff\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1\n      },\n      {\n        \"id\": \"dns-desktop-1765238386101\",\n        \"from\": \"dns\",\n        \"to\": \"desktop\",\n        \"width\": 4,\n        \"color\": \"#ff00d0\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1\n      },\n      {\n        \"id\": \"phone-dns-1765238391156\",\n        \"from\": \"phone\",\n        \"to\": \"dns\",\n        \"width\": 4,\n        \"color\": \"#ff00d0\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1\n      },\n      {\n        \"id\": \"custom-1765239449323\",\n        \"type\": \"custom\",\n        \"color\": \"#f97316\",\n        \"width\": 4,\n        \"lineStyle\": \"solid\",\n        \"direction\": \"forward\",\n        \"points\": [\n          {\n            \"x\": 2936.464111328125,\n            \"y\": 786.07958984375\n          },\n          {\n            \"x\": 3184.112060546875,\n            \"y\": 887.6153564453125\n          },\n          {\n            \"x\": 2763.110107421875,\n            \"y\": 981.7216796875\n          }\n        ],\n        \"notes\": []\n      }\n    ]\n  },\n  \"rectData\": {\n    \"list\": [\n      {\n        \"id\": \"rect-1765238219615\",\n        \"x\": 2680.053955078125,\n        \"y\": 251.44879150390625,\n        \"width\": 814.10400390625,\n        \"height\": 389.26678466796875,\n        \"color\": \"#ec0999\",\n        \"style\": \"filled\",\n        \"lineStyle\": \"solid\",\n        \"notes\": []\n      }\n    ]\n  },\n  \"textData\": {\n    \"list\": [\n      {\n        \"id\": \"text-1765238422602\",\n        \"x\": 2466.35986328125,\n        \"y\": 741.6801147460938,\n        \"content\": \"Double click on desktop\\nor long press on mobile\\nto enter rack canvas view\",\n        \"fontSize\": 40,\n        \"color\": \"#e2e8f0\",\n        \"fontWeight\": \"normal\",\n        \"fontStyle\": \"normal\",\n        \"textAlign\": \"start\",\n        \"textDecoration\": \"none\",\n        \"bgColor\": \"#000000\",\n        \"bgEnabled\": false,\n        \"opacity\": 1\n      }\n    ]\n  },\n  \"edgeLegend\": {\n    \"#475569\": \"you can edit me too\",\n    \"#65758b\": \"you can edit me too\",\n    \"#63748c\": \"you can edit me too\",\n    \"#5e6f87\": \"you can edit me too\",\n    \"#586a84\": \"you can edit me too\",\n    \"#4f627d\": \"you can edit me too\",\n    \"#455873\": \"you can edit me too\",\n    \"#3d506c\": \"you can edit me too\",\n    \"#354964\": \"you can edit me too\",\n    \"#2e415c\": \"you can edit me too\",\n    \"#293c56\": \"you can edit me too\",\n    \"#273a53\": \"you can edit me too\",\n    \"#253750\": \"you can edit me too\",\n    \"#23354d\": \"you can edit me too\",\n    \"#203046\": \"you can edit me too\",\n    \"#1e2d43\": \"you can edit me too\",\n    \"#1a283d\": \"you can edit me too\",\n    \"#172435\": \"you can edit me too\",\n    \"#141f2e\": \"you can edit me too\",\n    \"#111a27\": \"you can edit me too\",\n    \"#0f1824\": \"you can edit me too\",\n    \"#0d1521\": \"you can edit me too\",\n    \"#0c131d\": \"you can edit me too\",\n    \"#0c1d1c\": \"you can edit me too\",\n    \"#0c1c1d\": \"you can edit me too\",\n    \"#0c191d\": \"you can edit me too\",\n    \"#0c141d\": \"you can edit me too\",\n    \"#0c0d1d\": \"you can edit me too\",\n    \"#130c1d\": \"you can edit me too\",\n    \"#1b0c1d\": \"you can edit me too\",\n    \"#1d0c17\": \"you can edit me too\",\n    \"#1d0c10\": \"you can edit me too\",\n    \"#1d0c0c\": \"you can edit me too\",\n    \"#3b1b1b\": \"you can edit me too\",\n    \"#3c1a1a\": \"you can edit me too\",\n    \"#3f1c1c\": \"you can edit me too\",\n    \"#401c1c\": \"you can edit me too\",\n    \"#451c1c\": \"you can edit me too\",\n    \"#461b1b\": \"you can edit me too\",\n    \"#4c1a1a\": \"you can edit me too\",\n    \"#521919\": \"you can edit me too\",\n    \"#571919\": \"you can edit me too\",\n    \"#5d1818\": \"you can edit me too\",\n    \"#631717\": \"you can edit me too\",\n    \"#651515\": \"you can edit me too\",\n    \"#6a1616\": \"you can edit me too\",\n    \"#6f1515\": \"you can edit me too\",\n    \"#711414\": \"you can edit me too\",\n    \"#761414\": \"you can edit me too\",\n    \"#771313\": \"you can edit me too\",\n    \"#7c1313\": \"you can edit me too\",\n    \"#811313\": \"you can edit me too\",\n    \"#821212\": \"you can edit me too\",\n    \"#871212\": \"you can edit me too\",\n    \"#881111\": \"you can edit me too\",\n    \"#8d1111\": \"you can edit me too\",\n    \"#8e1010\": \"you can edit me too\",\n    \"#8f0f0f\": \"you can edit me too\",\n    \"#900e0e\": \"you can edit me too\",\n    \"#8e0b0b\": \"you can edit me too\",\n    \"#8c0d0d\": \"you can edit me too\",\n    \"#880c0c\": \"you can edit me too\",\n    \"#830c0c\": \"you can edit me too\",\n    \"#7e0c0c\": \"you can edit me too\",\n    \"#790c0c\": \"you can edit me too\",\n    \"#730c0c\": \"you can edit me too\",\n    \"#6f0b0b\": \"you can edit me too\",\n    \"#0b6f64\": \"you can edit me too\",\n    \"#0b6f5f\": \"you can edit me too\",\n    \"#0b6f56\": \"you can edit me too\",\n    \"#0b6f49\": \"you can edit me too\",\n    \"#0b6f31\": \"you can edit me too\",\n    \"#0b6f1f\": \"you can edit me too\",\n    \"#0b6f0d\": \"you can edit me too\",\n    \"#176f0b\": \"you can edit me too\",\n    \"#266f0b\": \"you can edit me too\",\n    \"#296f0b\": \"you can edit me too\",\n    \"#2e6f0b\": \"you can edit me too\",\n    \"#1a2d10\": \"you can edit me too\",\n    \"#1c3111\": \"you can edit me too\",\n    \"#213814\": \"you can edit me too\",\n    \"#233c15\": \"you can edit me too\",\n    \"#254017\": \"you can edit me too\",\n    \"#294918\": \"you can edit me too\",\n    \"#2b4d1a\": \"you can edit me too\",\n    \"#2d511a\": \"you can edit me too\",\n    \"#315a1b\": \"you can edit me too\",\n    \"#35631c\": \"you can edit me too\",\n    \"#37681d\": \"you can edit me too\",\n    \"#3b721d\": \"you can edit me too\",\n    \"#3f7b1e\": \"you can edit me too\",\n    \"#42851e\": \"you can edit me too\",\n    \"#46901d\": \"you can edit me too\",\n    \"#499a1d\": \"you can edit me too\",\n    \"#4b9f1d\": \"you can edit me too\",\n    \"#4ca61c\": \"you can edit me too\",\n    \"#50b01c\": \"you can edit me too\",\n    \"#51b71a\": \"you can edit me too\",\n    \"#50b918\": \"you can edit me too\",\n    \"#51c115\": \"you can edit me too\",\n    \"#53c615\": \"you can edit me too\",\n    \"#53c814\": \"you can edit me too\",\n    \"#52c913\": \"you can edit me too\",\n    \"#54d011\": \"you can edit me too\",\n    \"#53d110\": \"you can edit me too\",\n    \"#55d510\": \"you can edit me too\",\n    \"#55d70f\": \"you can edit me too\",\n    \"#54d80e\": \"you can edit me too\",\n    \"#54da0b\": \"you can edit me too\",\n    \"#56df0c\": \"you can edit me too\",\n    \"#53db0a\": \"you can edit me too\",\n    \"#55e00b\": \"you can edit me too\",\n    \"#55e109\": \"you can edit me too\",\n    \"#55e208\": \"ISP LINE\",\n    \"#4c00ff\": \"MY Guest NETWORK\",\n    \"#80ff00\": \"you can edit me too\",\n    \"#3b4234\": \"you can edit me too\",\n    \"#3a3442\": \"you can edit me too\",\n    \"#3b3442\": \"you can edit me too\",\n    \"#3c3442\": \"you can edit me too\",\n    \"#3d3442\": \"you can edit me too\",\n    \"#3e3442\": \"you can edit me too\",\n    \"#3f3442\": \"you can edit me too\",\n    \"#403442\": \"you can edit me too\",\n    \"#413442\": \"you can edit me too\",\n    \"#653d66\": \"you can edit me too\",\n    \"#683f69\": \"you can edit me too\",\n    \"#6c416c\": \"you can edit me too\",\n    \"#6f4370\": \"you can edit me too\",\n    \"#704270\": \"you can edit me too\",\n    \"#734474\": \"you can edit me too\",\n    \"#784479\": \"you can edit me too\",\n    \"#7d447e\": \"you can edit me too\",\n    \"#7e437f\": \"you can edit me too\",\n    \"#834384\": \"you can edit me too\",\n    \"#844285\": \"you can edit me too\",\n    \"#89418b\": \"you can edit me too\",\n    \"#8e428f\": \"you can edit me too\",\n    \"#904091\": \"you can edit me too\",\n    \"#923e93\": \"you can edit me too\",\n    \"#973e98\": \"you can edit me too\",\n    \"#943c96\": \"you can edit me too\",\n    \"#993c9a\": \"you can edit me too\",\n    \"#963a98\": \"you can edit me too\",\n    \"#973899\": \"you can edit me too\",\n    \"#99369b\": \"you can edit me too\",\n    \"#9a359c\": \"you can edit me too\",\n    \"#9b349d\": \"you can edit me too\",\n    \"#9d329f\": \"you can edit me too\",\n    \"#9e31a0\": \"you can edit me too\",\n    \"#a02fa2\": \"you can edit me too\",\n    \"#9d2d9f\": \"you can edit me too\",\n    \"#9f2ba1\": \"you can edit me too\",\n    \"#a129a3\": \"you can edit me too\",\n    \"#a327a5\": \"you can edit me too\",\n    \"#a525a7\": \"you can edit me too\",\n    \"#a723a9\": \"you can edit me too\",\n    \"#a921ab\": \"you can edit me too\",\n    \"#ab1fad\": \"you can edit me too\",\n    \"#ad1daf\": \"you can edit me too\",\n    \"#ae1cb0\": \"you can edit me too\",\n    \"#b019b3\": \"you can edit me too\",\n    \"#b118b4\": \"you can edit me too\",\n    \"#b316b6\": \"you can edit me too\",\n    \"#b816bb\": \"you can edit me too\",\n    \"#b514b8\": \"you can edit me too\",\n    \"#ba14bd\": \"you can edit me too\",\n    \"#b712ba\": \"you can edit me too\",\n    \"#bb13be\": \"you can edit me too\",\n    \"#b811bb\": \"you can edit me too\",\n    \"#be10c1\": \"you can edit me too\",\n    \"#bb0ebe\": \"you can edit me too\",\n    \"#bd0cc0\": \"you can edit me too\",\n    \"#be0bc1\": \"you can edit me too\",\n    \"#c108c4\": \"you can edit me too\",\n    \"#be06c1\": \"you can edit me too\",\n    \"#c103c4\": \"you can edit me too\",\n    \"#c301c6\": \"you can edit me too\",\n    \"#c400c7\": \"you can edit me too\",\n    \"#c900cc\": \"you can edit me too\",\n    \"#ce00d1\": \"you can edit me too\",\n    \"#d300d6\": \"you can edit me too\",\n    \"#d800db\": \"you can edit me too\",\n    \"#dd00e0\": \"you can edit me too\",\n    \"#e200e6\": \"you can edit me too\",\n    \"#ec00f0\": \"you can edit me too\",\n    \"#f100f5\": \"you can edit me too\",\n    \"#f600fa\": \"you can edit me too\",\n    \"#fb00ff\": \"you can edit me too\",\n    \"#ff00d0\": \"iPhone (always guest iPhone)\",\n    \"#f97316\": \"you can edit me too\"\n  },\n  \"nodePositions\": {\n    \"internet\": {\n      \"x\": 1757.7735887323333,\n      \"y\": 298.77284240722656\n    },\n    \"internet-copy\": {\n      \"x\": 2066.9677515897347,\n      \"y\": 473.4119134177565\n    },\n    \"opnsense-copy\": {\n      \"x\": 1773.8400660428597,\n      \"y\": 666.5758233298659\n    },\n    \"docker-copy\": {\n      \"x\": 1931.1978950081452,\n      \"y\": 782.2775961320921\n    },\n    \"docker-copy-1\": {\n      \"x\": 2158.1262397347077,\n      \"y\": 767.7122274797483\n    },\n    \"docker-copy-2\": {\n      \"x\": 2342.2663764534577,\n      \"y\": 631.7681967180296\n    },\n    \"opnsense-copy-1\": {\n      \"x\": 2757.879480087803,\n      \"y\": 307.6117116091891\n    },\n    \"phone\": {\n      \"x\": 3312.857751572178,\n      \"y\": 502.58220111114224\n    },\n    \"desktop\": {\n      \"x\": 2971.700036728428,\n      \"y\": 480.7287465212985\n    },\n    \"dns\": {\n      \"x\": 3200.4643189549906,\n      \"y\": 320.469591247861\n    },\n    \"racked\": {\n      \"x\": 2645.5845448279656,\n      \"y\": 970.7820678889219\n    },\n    \"thermostat\": {\n      \"x\": 1323.0595481711202,\n      \"y\": 574.6132617105841\n    },\n    \"video-doorbell\": {\n      \"x\": 1188.4284446554952,\n      \"y\": 455.3684191812872\n    },\n    \"smart-lock\": {\n      \"x\": 1292.286782057839,\n      \"y\": 790.0231738199591\n    },\n    \"smart-bulb\": {\n      \"x\": 1496.156899245339,\n      \"y\": 716.9377246012091\n    },\n    \"robot-vacuum\": {\n      \"x\": 2288.5581443625265,\n      \"y\": 978.5069995035528\n    }\n  },\n  \"nodeSizes\": {\n    \"core-router-1\": 36,\n    \"internet\": 168,\n    \"phone\": 121,\n    \"desktop\": 147,\n    \"racked\": 137,\n    \"docker-copy-2\": 82\n  },\n  \"nodeStyles\": {\n    \"internet\": {\n      \"all\": {\n        \"icon\": {\n          \"library\": \"selfhst\",\n          \"name\": \"amazon-web-services\"\n        },\n        \"circleColor\": \"#db0000\",\n        \"circleBorder\": \"#000000\",\n        \"titleSize\": 52,\n        \"subSize\": 46\n      }\n    },\n    \"opnsense-copy-1\": {\n      \"all\": {\n        \"icon\": {\n          \"library\": \"selfhst\",\n          \"name\": \"opnsense-v1\"\n        }\n      }\n    },\n    \"internet-copy\": {\n      \"all\": {\n        \"icon\": {\n          \"library\": \"selfhst\",\n          \"name\": \"opnsense\"\n        }\n      }\n    },\n    \"docker-copy-2\": {\n      \"all\": {\n        \"icon\": {\n          \"library\": \"selfhst\",\n          \"name\": \"docker\"\n        }\n      }\n    },\n    \"docker-copy-1\": {\n      \"all\": {\n        \"icon\": {\n          \"library\": \"selfhst\",\n          \"name\": \"authportal\"\n        }\n      }\n    },\n    \"docker-copy\": {\n      \"all\": {\n        \"icon\": {\n          \"library\": \"selfhst\",\n          \"name\": \"jotty\"\n        }\n      }\n    },\n    \"opnsense-copy\": {\n      \"all\": {\n        \"icon\": {\n          \"library\": \"selfhst\",\n          \"name\": \"portainer\"\n        }\n      }\n    },\n    \"racked\": {\n      \"all\": {\n        \"icon\": {\n          \"library\": \"mdi\",\n          \"name\": \"server-security\"\n        },\n        \"circleColor\": \"#010813\",\n        \"circleBorder\": \"#ffffff\"\n      }\n    }\n  },\n  \"page\": {\n    \"title\": \"The One File\",\n    \"background\": \"\",\n    \"topbarBg\": \"rgba(9, 12, 20, 0.9)\",\n    \"topbarBorder\": \"#1f2533\",\n    \"panel\": \"#2f0e0e\",\n    \"panelAlt\": \"#10141b\",\n    \"accent\": \"#a75252\",\n    \"sidebarBg\": \"#10141b\",\n    \"btnBg\": \"#0b0e13\",\n    \"btnText\": \"#e2e8f0\",\n    \"tagFill\": \"#1e293b\",\n    \"tagText\": \"#e2e8f0\",\n    \"tagBorder\": \"#475569\",\n    \"inputBg\": \"#0b0e13\",\n    \"inputText\": \"#e2e8f0\",\n    \"inputBorder\": \"#1f2937\",\n    \"inputFont\": \"Inter, system-ui, sans-serif\",\n    \"inputFontSize\": 14,\n    \"toolbarBg\": \"#441215\",\n    \"toolbarBorder\": \"#1f2937\",\n    \"toolbarText\": \"#94a3b8\",\n    \"toolbarBtnBg\": \"#0b0e13\",\n    \"toolbarBtnText\": \"#e2e8f0\",\n    \"minimapDots\": \"#94a3b8\",\n    \"canvasHintEnabled\": true,\n    \"canvasHintText\": \"\",\n    \"canvasHintBg\": \"#0f172a\",\n    \"canvasHintColor\": \"#94a3b8\",\n    \"danger\": \"#f56565\",\n    \"textMain\": \"#e2e8f0\",\n    \"textSoft\": \"#94a3b8\",\n    \"topbarHeight\": 112,\n    \"sidebarWidth\": 350,\n    \"mobileFooterHeight\": 40,\n    \"sidebarCollapsed\": false,\n    \"nodeFill\": \"#1e293b\",\n    \"nodeStroke\": \"#475569\",\n    \"nodeTitle\": \"#e2e8f0\",\n    \"nodeSub\": \"#94a3b8\",\n    \"nodeTitleSize\": 18,\n    \"nodeSubSize\": 13,\n    \"nodeFont\": \"Inter, system-ui, sans-serif\",\n    \"defaultEdge\": \"#475569\",\n    \"selectionHandle\": \"#f59e0b\",\n    \"selectionHandleSize\": 8,\n    \"groupIndicator\": \"#4fd1c5\",\n    \"canvasGradientTop\": \"#1e2532\",\n    \"canvasGradientBottom\": \"#050608\",\n    \"canvasBorder\": \"#475569\",\n    \"canvasGrid\": \"#475569\",\n    \"canvasGridSize\": 50,\n    \"canvasGridEnabled\": true,\n    \"rackFrameFill\": \"#0f172a\",\n    \"rackGridEnabled\": true,\n    \"rackFrameStroke\": \"#4fd1c5\",\n    \"rackLineColor\": \"#475569\",\n    \"rackTextColor\": \"#4fd1c5\",\n    \"viewOnly\": false,\n    \"defaultEdgeRouting\": \"curved\",\n    \"animateConnections\": false,\n    \"animationStyle\": \"arrows\",\n    \"animationDirection\": \"all\",\n    \"animationSpeed\": 1.5\n  },\n  \"canvas\": {\n    \"zoom\": 0.8752084596859406,\n    \"panX\": -191.26917538063572,\n    \"panY\": -261.51832729948296\n  },\n  \"savedTopologyView\": {\n    \"zoom\": 0.9111098220009686,\n    \"panX\": -207.26076103009882,\n    \"panY\": -201.83911371533202\n  },\n  \"documentTabs\": [\n    {\n      \"id\": \"main\",\n      \"name\": \"Corporate Site B\",\n      \"nodes\": {\n        \"core-router-1\": {\n          \"shape\": \"router\",\n          \"name\": \"Core Router 1\",\n          \"ip\": \"10.0.0.1\",\n          \"role\": \"Core Routing\",\n          \"tags\": [\n            \"core\",\n            \"tier-1\",\n            \"redundant\"\n          ],\n          \"notes\": [\n            \"Primary core router\",\n            \"BGP peering enabled\"\n          ],\n          \"mac\": \"00:1A:2B:3C:4D:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"core-router-2\": {\n          \"shape\": \"router\",\n          \"name\": \"Core Router 2\",\n          \"ip\": \"10.0.0.2\",\n          \"role\": \"Core Routing\",\n          \"tags\": [\n            \"core\",\n            \"tier-1\",\n            \"redundant\"\n          ],\n          \"notes\": [\n            \"Secondary core router\",\n            \"HSRP standby\"\n          ],\n          \"mac\": \"00:1A:2B:3C:4D:02\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null,\n          \"ping\": {\n            \"enabled\": true,\n            \"protocol\": \"custom\",\n            \"customUrl\": \"https://google.com\",\n            \"timeout\": 3000,\n            \"status\": \"online\",\n            \"lastCheck\": \"2025-12-09T00:15:04.343Z\"\n          }\n        },\n        \"fw-external-1\": {\n          \"shape\": \"firewall\",\n          \"name\": \"External FW 1\",\n          \"ip\": \"10.0.1.1\",\n          \"role\": \"Perimeter Security\",\n          \"tags\": [\n            \"security\",\n            \"perimeter\",\n            \"ha-pair\"\n          ],\n          \"notes\": [\n            \"Palo Alto PA-5250\",\n            \"Active node\"\n          ],\n          \"mac\": \"00:1A:2B:3C:4D:10\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"fw-external-2\": {\n          \"shape\": \"firewall\",\n          \"name\": \"External FW 2\",\n          \"ip\": \"10.0.1.2\",\n          \"role\": \"Perimeter Security\",\n          \"tags\": [\n            \"security\",\n            \"perimeter\",\n            \"ha-pair\"\n          ],\n          \"notes\": [\n            \"Palo Alto PA-5250\",\n            \"Passive node\"\n          ],\n          \"mac\": \"00:1A:2B:3C:4D:11\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"fw-internal\": {\n          \"shape\": \"firewall\",\n          \"name\": \"Internal FW\",\n          \"ip\": \"10.0.2.1\",\n          \"role\": \"Internal Segmentation\",\n          \"tags\": [\n            \"security\",\n            \"internal\"\n          ],\n          \"notes\": [\n            \"East-West traffic inspection\"\n          ],\n          \"mac\": \"00:1A:2B:3C:4D:12\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"core-switch-1\": {\n          \"shape\": \"switch\",\n          \"name\": \"Core Switch 1\",\n          \"ip\": \"10.0.10.1\",\n          \"role\": \"Core Switching\",\n          \"tags\": [\n            \"core\",\n            \"layer3\",\n            \"redundant\"\n          ],\n          \"notes\": [\n            \"Cisco Nexus 9000\",\n            \"VPC Domain 1\"\n          ],\n          \"mac\": \"00:1A:2B:3C:4D:20\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"core-switch-2\": {\n          \"shape\": \"switch\",\n          \"name\": \"Core Switch 2\",\n          \"ip\": \"10.0.10.2\",\n          \"role\": \"Core Switching\",\n          \"tags\": [\n            \"core\",\n            \"layer3\",\n            \"redundant\"\n          ],\n          \"notes\": [\n            \"Cisco Nexus 9000\",\n            \"VPC Domain 1\"\n          ],\n          \"mac\": \"00:1A:2B:3C:4D:21\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dc-rack-a1\": {\n          \"shape\": \"server\",\n          \"name\": \"DC Rack A1\",\n          \"ip\": \"10.10.0.0/24\",\n          \"role\": \"Data Center Rack\",\n          \"tags\": [\n            \"datacenter\",\n            \"row-a\",\n            \"production\"\n          ],\n          \"notes\": [\n            \"Row A, Position 1\",\n            \"Primary compute\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": true,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dc-rack-a2\": {\n          \"shape\": \"server\",\n          \"name\": \"DC Rack A2\",\n          \"ip\": \"10.10.1.0/24\",\n          \"role\": \"Data Center Rack\",\n          \"tags\": [\n            \"datacenter\",\n            \"row-a\",\n            \"production\"\n          ],\n          \"notes\": [\n            \"Row A, Position 2\",\n            \"Primary compute\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": true,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dc-rack-b1\": {\n          \"shape\": \"server\",\n          \"name\": \"DC Rack B1\",\n          \"ip\": \"10.10.2.0/24\",\n          \"role\": \"Data Center Rack\",\n          \"tags\": [\n            \"datacenter\",\n            \"row-b\",\n            \"storage\"\n          ],\n          \"notes\": [\n            \"Row B, Position 1\",\n            \"Storage systems\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": true,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dc-rack-b2\": {\n          \"shape\": \"server\",\n          \"name\": \"DC Rack B2\",\n          \"ip\": \"10.10.3.0/24\",\n          \"role\": \"Data Center Rack\",\n          \"tags\": [\n            \"datacenter\",\n            \"row-b\",\n            \"storage\"\n          ],\n          \"notes\": [\n            \"Row B, Position 2\",\n            \"Storage systems\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": true,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dmz-rack\": {\n          \"shape\": \"server\",\n          \"name\": \"DMZ Rack\",\n          \"ip\": \"172.16.0.0/24\",\n          \"role\": \"DMZ Infrastructure\",\n          \"tags\": [\n            \"dmz\",\n            \"security\",\n            \"public-facing\",\n            {\n              \"type\": \"icon\",\n              \"library\": \"selfhst\",\n              \"name\": \"booklogr\"\n            },\n            {\n              \"type\": \"icon\",\n              \"library\": \"simple\",\n              \"name\": \"gmail\"\n            }\n          ],\n          \"notes\": [\n            \"Isolated DMZ zone\",\n            \"Public-facing services\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": true,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"mgmt-rack\": {\n          \"shape\": \"server\",\n          \"name\": \"Management Rack\",\n          \"ip\": \"192.168.100.0/24\",\n          \"role\": \"Management Infrastructure\",\n          \"tags\": [\n            \"management\",\n            \"oob\",\n            \"noc\"\n          ],\n          \"notes\": [\n            \"Out-of-band management\",\n            \"NOC equipment\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"logical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": true,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"esxi-host-01\": {\n          \"shape\": \"server\",\n          \"name\": \"ESXi Host 01\",\n          \"ip\": \"10.10.0.11\",\n          \"role\": \"Hypervisor\",\n          \"tags\": [\n            \"vmware\",\n            \"compute\",\n            \"cluster-a\"\n          ],\n          \"notes\": [\n            \"Dell PowerEdge R750\",\n            \"512GB RAM\",\n            \"vSphere 8.0\"\n          ],\n          \"mac\": \"00:50:56:AA:01:01\",\n          \"rackUnit\": 38,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"esxi-host-02\": {\n          \"shape\": \"server\",\n          \"name\": \"ESXi Host 02\",\n          \"ip\": \"10.10.0.12\",\n          \"role\": \"Hypervisor\",\n          \"tags\": [\n            \"vmware\",\n            \"compute\",\n            \"cluster-a\"\n          ],\n          \"notes\": [\n            \"Dell PowerEdge R750\",\n            \"512GB RAM\",\n            \"vSphere 8.0\"\n          ],\n          \"mac\": \"00:50:56:AA:01:02\",\n          \"rackUnit\": 35,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"esxi-host-03\": {\n          \"shape\": \"server\",\n          \"name\": \"ESXi Host 03\",\n          \"ip\": \"10.10.0.13\",\n          \"role\": \"Hypervisor\",\n          \"tags\": [\n            \"vmware\",\n            \"compute\",\n            \"cluster-a\"\n          ],\n          \"notes\": [\n            \"Dell PowerEdge R750\",\n            \"512GB RAM\",\n            \"vSphere 8.0\"\n          ],\n          \"mac\": \"00:50:56:AA:01:03\",\n          \"rackUnit\": 32,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"esxi-host-04\": {\n          \"shape\": \"server\",\n          \"name\": \"ESXi Host 04\",\n          \"ip\": \"10.10.0.14\",\n          \"role\": \"Hypervisor\",\n          \"tags\": [\n            \"vmware\",\n            \"compute\",\n            \"cluster-a\"\n          ],\n          \"notes\": [\n            \"Dell PowerEdge R750\",\n            \"512GB RAM\",\n            \"vSphere 8.0\"\n          ],\n          \"mac\": \"00:50:56:AA:01:04\",\n          \"rackUnit\": 29,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"tor-switch-a1\": {\n          \"shape\": \"switch\",\n          \"name\": \"ToR Switch A1\",\n          \"ip\": \"10.10.0.1\",\n          \"role\": \"Top of Rack\",\n          \"tags\": [\n            \"tor\",\n            \"access\",\n            \"rack-a1\"\n          ],\n          \"notes\": [\n            \"Cisco Nexus 93180YC-FX\",\n            \"48x25G ports\"\n          ],\n          \"mac\": \"00:1A:2B:3C:5D:01\",\n          \"rackUnit\": 42,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"esxi-host-05\": {\n          \"shape\": \"server\",\n          \"name\": \"ESXi Host 05\",\n          \"ip\": \"10.10.1.11\",\n          \"role\": \"Hypervisor\",\n          \"tags\": [\n            \"vmware\",\n            \"compute\",\n            \"cluster-b\"\n          ],\n          \"notes\": [\n            \"Dell PowerEdge R750\",\n            \"768GB RAM\",\n            \"vSphere 8.0\"\n          ],\n          \"mac\": \"00:50:56:AA:02:01\",\n          \"rackUnit\": 38,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"esxi-host-06\": {\n          \"shape\": \"server\",\n          \"name\": \"ESXi Host 06\",\n          \"ip\": \"10.10.1.12\",\n          \"role\": \"Hypervisor\",\n          \"tags\": [\n            \"vmware\",\n            \"compute\",\n            \"cluster-b\"\n          ],\n          \"notes\": [\n            \"Dell PowerEdge R750\",\n            \"768GB RAM\",\n            \"vSphere 8.0\"\n          ],\n          \"mac\": \"00:50:56:AA:02:02\",\n          \"rackUnit\": 35,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"esxi-host-07\": {\n          \"shape\": \"server\",\n          \"name\": \"ESXi Host 07\",\n          \"ip\": \"10.10.1.13\",\n          \"role\": \"Hypervisor\",\n          \"tags\": [\n            \"vmware\",\n            \"compute\",\n            \"cluster-b\"\n          ],\n          \"notes\": [\n            \"Dell PowerEdge R750\",\n            \"768GB RAM\",\n            \"vSphere 8.0\"\n          ],\n          \"mac\": \"00:50:56:AA:02:03\",\n          \"rackUnit\": 32,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"esxi-host-08\": {\n          \"shape\": \"server\",\n          \"name\": \"ESXi Host 08\",\n          \"ip\": \"10.10.1.14\",\n          \"role\": \"Hypervisor\",\n          \"tags\": [\n            \"vmware\",\n            \"compute\",\n            \"cluster-b\"\n          ],\n          \"notes\": [\n            \"Dell PowerEdge R750\",\n            \"768GB RAM\",\n            \"vSphere 8.0\"\n          ],\n          \"mac\": \"00:50:56:AA:02:04\",\n          \"rackUnit\": 29,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"tor-switch-a2\": {\n          \"shape\": \"switch\",\n          \"name\": \"ToR Switch A2\",\n          \"ip\": \"10.10.1.1\",\n          \"role\": \"Top of Rack\",\n          \"tags\": [\n            \"tor\",\n            \"access\",\n            \"rack-a2\"\n          ],\n          \"notes\": [\n            \"Cisco Nexus 93180YC-FX\",\n            \"48x25G ports\"\n          ],\n          \"mac\": \"00:1A:2B:3C:5D:02\",\n          \"rackUnit\": 42,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"san-primary\": {\n          \"shape\": \"database\",\n          \"name\": \"SAN Primary\",\n          \"ip\": \"10.10.2.10\",\n          \"role\": \"Primary Storage\",\n          \"tags\": [\n            \"storage\",\n            \"san\",\n            \"netapp\"\n          ],\n          \"notes\": [\n            \"NetApp AFF A400\",\n            \"500TB Raw\",\n            \"FC 32Gb\"\n          ],\n          \"mac\": \"00:A0:98:AA:01:01\",\n          \"rackUnit\": 36,\n          \"uHeight\": \"6\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-b1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"san-secondary\": {\n          \"shape\": \"database\",\n          \"name\": \"SAN Secondary\",\n          \"ip\": \"10.10.2.11\",\n          \"role\": \"Secondary Storage\",\n          \"tags\": [\n            \"storage\",\n            \"san\",\n            \"netapp\"\n          ],\n          \"notes\": [\n            \"NetApp AFF A400\",\n            \"500TB Raw\",\n            \"FC 32Gb\"\n          ],\n          \"mac\": \"00:A0:98:AA:01:02\",\n          \"rackUnit\": 28,\n          \"uHeight\": \"6\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-b1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"fc-switch-1\": {\n          \"shape\": \"switch\",\n          \"name\": \"FC Switch 1\",\n          \"ip\": \"10.10.2.1\",\n          \"role\": \"Fibre Channel\",\n          \"tags\": [\n            \"storage\",\n            \"fc\",\n            \"fabric-a\"\n          ],\n          \"notes\": [\n            \"Brocade G620\",\n            \"Fabric A\"\n          ],\n          \"mac\": \"00:1A:2B:FC:01:01\",\n          \"rackUnit\": 42,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-b1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"fc-switch-2\": {\n          \"shape\": \"switch\",\n          \"name\": \"FC Switch 2\",\n          \"ip\": \"10.10.2.2\",\n          \"role\": \"Fibre Channel\",\n          \"tags\": [\n            \"storage\",\n            \"fc\",\n            \"fabric-b\"\n          ],\n          \"notes\": [\n            \"Brocade G620\",\n            \"Fabric B\"\n          ],\n          \"mac\": \"00:1A:2B:FC:01:02\",\n          \"rackUnit\": 41,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-b1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"backup-server-1\": {\n          \"shape\": \"server\",\n          \"name\": \"Backup Server 1\",\n          \"ip\": \"10.10.3.10\",\n          \"role\": \"Backup Infrastructure\",\n          \"tags\": [\n            \"backup\",\n            \"veeam\",\n            \"protection\"\n          ],\n          \"notes\": [\n            \"Veeam Backup Server\",\n            \"Dell R740xd\",\n            \"200TB\"\n          ],\n          \"mac\": \"00:50:56:BB:01:01\",\n          \"rackUnit\": 36,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-b2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"backup-server-2\": {\n          \"shape\": \"server\",\n          \"name\": \"Backup Server 2\",\n          \"ip\": \"10.10.3.11\",\n          \"role\": \"Backup Infrastructure\",\n          \"tags\": [\n            \"backup\",\n            \"veeam\",\n            \"protection\"\n          ],\n          \"notes\": [\n            \"Veeam Backup Server\",\n            \"Dell R740xd\",\n            \"200TB\"\n          ],\n          \"mac\": \"00:50:56:BB:01:02\",\n          \"rackUnit\": 33,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-b2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"tape-library\": {\n          \"shape\": \"database\",\n          \"name\": \"Tape Library\",\n          \"ip\": \"10.10.3.20\",\n          \"role\": \"Archival Storage\",\n          \"tags\": [\n            \"backup\",\n            \"tape\",\n            \"lto9\"\n          ],\n          \"notes\": [\n            \"IBM TS4500\",\n            \"LTO-9\",\n            \"Long-term archive\"\n          ],\n          \"mac\": \"00:50:56:BB:02:01\",\n          \"rackUnit\": 20,\n          \"uHeight\": \"10\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-b2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"tor-switch-b1\": {\n          \"shape\": \"switch\",\n          \"name\": \"ToR Switch B1\",\n          \"ip\": \"10.10.2.3\",\n          \"role\": \"Top of Rack\",\n          \"tags\": [\n            \"tor\",\n            \"access\",\n            \"rack-b1\"\n          ],\n          \"notes\": [\n            \"Cisco Nexus 93180YC-FX\"\n          ],\n          \"mac\": \"00:1A:2B:3C:5D:03\",\n          \"rackUnit\": 40,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-b1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"tor-switch-b2\": {\n          \"shape\": \"switch\",\n          \"name\": \"ToR Switch B2\",\n          \"ip\": \"10.10.3.1\",\n          \"role\": \"Top of Rack\",\n          \"tags\": [\n            \"tor\",\n            \"access\",\n            \"rack-b2\"\n          ],\n          \"notes\": [\n            \"Cisco Nexus 93180YC-FX\"\n          ],\n          \"mac\": \"00:1A:2B:3C:5D:04\",\n          \"rackUnit\": 42,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-b2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"web-server-1\": {\n          \"shape\": \"server\",\n          \"name\": \"Web Server 1\",\n          \"ip\": \"172.16.0.11\",\n          \"role\": \"Web Frontend\",\n          \"tags\": [\n            \"dmz\",\n            \"web\",\n            \"nginx\"\n          ],\n          \"notes\": [\n            \"NGINX reverse proxy\",\n            \"Public facing\"\n          ],\n          \"mac\": \"00:50:56:CC:01:01\",\n          \"rackUnit\": 20,\n          \"uHeight\": \"1\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"dmz-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"web-server-2\": {\n          \"shape\": \"server\",\n          \"name\": \"Web Server 2\",\n          \"ip\": \"172.16.0.12\",\n          \"role\": \"Web Frontend\",\n          \"tags\": [\n            \"dmz\",\n            \"web\",\n            \"nginx\"\n          ],\n          \"notes\": [\n            \"NGINX reverse proxy\",\n            \"Public facing\"\n          ],\n          \"mac\": \"00:50:56:CC:01:02\",\n          \"rackUnit\": 18,\n          \"uHeight\": \"1\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"dmz-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"waf-1\": {\n          \"shape\": \"firewall\",\n          \"name\": \"WAF Appliance\",\n          \"ip\": \"172.16.0.5\",\n          \"role\": \"Web Application Firewall\",\n          \"tags\": [\n            \"dmz\",\n            \"security\",\n            \"waf\"\n          ],\n          \"notes\": [\n            \"F5 BIG-IP ASM\",\n            \"OWASP protection\"\n          ],\n          \"mac\": \"00:50:56:CC:02:01\",\n          \"rackUnit\": 22,\n          \"uHeight\": \"2\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"dmz-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"load-balancer-dmz\": {\n          \"shape\": \"switch\",\n          \"name\": \"DMZ Load Balancer\",\n          \"ip\": \"172.16.0.3\",\n          \"role\": \"Load Balancing\",\n          \"tags\": [\n            \"dmz\",\n            \"lb\",\n            \"f5\"\n          ],\n          \"notes\": [\n            \"F5 BIG-IP LTM\",\n            \"VIP: 172.16.0.100\"\n          ],\n          \"mac\": \"00:50:56:CC:03:01\",\n          \"rackUnit\": 16,\n          \"uHeight\": \"2\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"dmz-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"mail-gateway\": {\n          \"shape\": \"server\",\n          \"name\": \"Mail Gateway\",\n          \"ip\": \"172.16.0.25\",\n          \"role\": \"Email Security\",\n          \"tags\": [\n            \"dmz\",\n            \"email\",\n            \"security\"\n          ],\n          \"notes\": [\n            \"Proofpoint Email Gateway\",\n            \"Spam/malware filtering\"\n          ],\n          \"mac\": \"00:50:56:CC:04:01\",\n          \"rackUnit\": 14,\n          \"uHeight\": \"1\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"dmz-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dns-external-1\": {\n          \"shape\": \"circle\",\n          \"name\": \"External DNS 1\",\n          \"ip\": \"172.16.0.53\",\n          \"role\": \"External DNS\",\n          \"tags\": [\n            \"dmz\",\n            \"dns\",\n            \"public\"\n          ],\n          \"notes\": [\n            \"BIND DNS\",\n            \"Authoritative for corp.com\"\n          ],\n          \"mac\": \"00:50:56:CC:05:01\",\n          \"rackUnit\": 12,\n          \"uHeight\": \"1\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"dmz-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dns-external-2\": {\n          \"shape\": \"circle\",\n          \"name\": \"External DNS 2\",\n          \"ip\": \"172.16.0.54\",\n          \"role\": \"External DNS\",\n          \"tags\": [\n            \"dmz\",\n            \"dns\",\n            \"public\"\n          ],\n          \"notes\": [\n            \"BIND DNS\",\n            \"Secondary for corp.com\"\n          ],\n          \"mac\": \"00:50:56:CC:05:02\",\n          \"rackUnit\": 10,\n          \"uHeight\": \"1\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"dmz-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"vcenter\": {\n          \"shape\": \"server\",\n          \"name\": \"vCenter Server\",\n          \"ip\": \"192.168.100.10\",\n          \"role\": \"Virtualization Management\",\n          \"tags\": [\n            \"management\",\n            \"vmware\",\n            \"vcsa\"\n          ],\n          \"notes\": [\n            \"vCenter Server Appliance 8.0\",\n            \"Single SSO domain\"\n          ],\n          \"mac\": \"00:50:56:DD:01:01\",\n          \"rackUnit\": 20,\n          \"uHeight\": \"2\",\n          \"layer\": \"logical\",\n          \"assignedRack\": \"mgmt-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"nsx-manager\": {\n          \"shape\": \"server\",\n          \"name\": \"NSX Manager\",\n          \"ip\": \"192.168.100.15\",\n          \"role\": \"Network Virtualization\",\n          \"tags\": [\n            \"management\",\n            \"vmware\",\n            \"nsx\"\n          ],\n          \"notes\": [\n            \"NSX-T 4.1 Manager Cluster\"\n          ],\n          \"mac\": \"00:50:56:DD:02:01\",\n          \"rackUnit\": 17,\n          \"uHeight\": \"2\",\n          \"layer\": \"logical\",\n          \"assignedRack\": \"mgmt-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"siem-server\": {\n          \"shape\": \"server\",\n          \"name\": \"SIEM Server\",\n          \"ip\": \"192.168.100.50\",\n          \"role\": \"Security Monitoring\",\n          \"tags\": [\n            \"management\",\n            \"security\",\n            \"splunk\"\n          ],\n          \"notes\": [\n            \"Splunk Enterprise\",\n            \"Security monitoring\"\n          ],\n          \"mac\": \"00:50:56:DD:03:01\",\n          \"rackUnit\": 14,\n          \"uHeight\": \"2\",\n          \"layer\": \"logical\",\n          \"assignedRack\": \"mgmt-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"nms-server\": {\n          \"shape\": \"server\",\n          \"name\": \"Network Monitoring\",\n          \"ip\": \"192.168.100.60\",\n          \"role\": \"Network Management\",\n          \"tags\": [\n            \"management\",\n            \"monitoring\",\n            \"prtg\"\n          ],\n          \"notes\": [\n            \"PRTG Network Monitor\",\n            \"5000 sensors\"\n          ],\n          \"mac\": \"00:50:56:DD:04:01\",\n          \"rackUnit\": 11,\n          \"uHeight\": \"1\",\n          \"layer\": \"logical\",\n          \"assignedRack\": \"mgmt-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"jump-server\": {\n          \"shape\": \"server\",\n          \"name\": \"Jump Server\",\n          \"ip\": \"192.168.100.100\",\n          \"role\": \"Bastion Host\",\n          \"tags\": [\n            \"management\",\n            \"security\",\n            \"bastion\"\n          ],\n          \"notes\": [\n            \"Windows Server 2022\",\n            \"MFA enabled\"\n          ],\n          \"mac\": \"00:50:56:DD:05:01\",\n          \"rackUnit\": 9,\n          \"uHeight\": \"1\",\n          \"layer\": \"logical\",\n          \"assignedRack\": \"mgmt-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"ipam-server\": {\n          \"shape\": \"server\",\n          \"name\": \"IPAM/DDI\",\n          \"ip\": \"192.168.100.70\",\n          \"role\": \"IP Management\",\n          \"tags\": [\n            \"management\",\n            \"dns\",\n            \"dhcp\"\n          ],\n          \"notes\": [\n            \"Infoblox DDI\",\n            \"DNS/DHCP/IPAM\"\n          ],\n          \"mac\": \"00:50:56:DD:06:01\",\n          \"rackUnit\": 7,\n          \"uHeight\": \"2\",\n          \"layer\": \"logical\",\n          \"assignedRack\": \"mgmt-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"wlc-primary\": {\n          \"shape\": \"wifi\",\n          \"name\": \"WLC Primary\",\n          \"ip\": \"10.20.0.1\",\n          \"role\": \"Wireless Controller\",\n          \"tags\": [\n            \"wireless\",\n            \"cisco\",\n            \"9800\"\n          ],\n          \"notes\": [\n            \"Cisco C9800-40\",\n            \"Primary controller\"\n          ],\n          \"mac\": \"00:1A:2B:WL:01:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"wlc-secondary\": {\n          \"shape\": \"wifi\",\n          \"name\": \"WLC Secondary\",\n          \"ip\": \"10.20.0.2\",\n          \"role\": \"Wireless Controller\",\n          \"tags\": [\n            \"wireless\",\n            \"cisco\",\n            \"9800\"\n          ],\n          \"notes\": [\n            \"Cisco C9800-40\",\n            \"HA Secondary\"\n          ],\n          \"mac\": \"00:1A:2B:WL:01:02\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"mobile-zone-hq\": {\n          \"shape\": \"phone\",\n          \"name\": \"HQ Mobile Zone\",\n          \"ip\": \"10.20.10.0/24\",\n          \"role\": \"Mobile Device Zone\",\n          \"tags\": [\n            \"wireless\",\n            \"byod\",\n            \"mobile\"\n          ],\n          \"notes\": [\n            \"Corporate BYOD\",\n            \"MDM enrolled devices\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"mobile-zone-guest\": {\n          \"shape\": \"phone\",\n          \"name\": \"Guest WiFi Zone\",\n          \"ip\": \"10.30.0.0/24\",\n          \"role\": \"Guest Network\",\n          \"tags\": [\n            \"wireless\",\n            \"guest\",\n            \"isolated\"\n          ],\n          \"notes\": [\n            \"Captive portal\",\n            \"Internet only\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"mobile-zone-iot\": {\n          \"shape\": \"phone\",\n          \"name\": \"IoT Device Zone\",\n          \"ip\": \"10.40.0.0/24\",\n          \"role\": \"IoT Network\",\n          \"tags\": [\n            \"wireless\",\n            \"iot\",\n            \"building\"\n          ],\n          \"notes\": [\n            \"Building automation\",\n            \"Smart devices\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"branch-router-ny\": {\n          \"shape\": \"router\",\n          \"name\": \"NYC Branch Router\",\n          \"ip\": \"10.100.0.1\",\n          \"role\": \"Branch Gateway\",\n          \"tags\": [\n            \"branch\",\n            \"nyc\",\n            \"sd-wan\"\n          ],\n          \"notes\": [\n            \"Cisco Viptela vEdge\",\n            \"SD-WAN enabled\"\n          ],\n          \"mac\": \"00:1A:2B:BR:01:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"branch-router-la\": {\n          \"shape\": \"router\",\n          \"name\": \"LA Branch Router\",\n          \"ip\": \"10.101.0.1\",\n          \"role\": \"Branch Gateway\",\n          \"tags\": [\n            \"branch\",\n            \"la\",\n            \"sd-wan\"\n          ],\n          \"notes\": [\n            \"Cisco Viptela vEdge\",\n            \"SD-WAN enabled\"\n          ],\n          \"mac\": \"00:1A:2B:BR:02:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"branch-router-chi\": {\n          \"shape\": \"router\",\n          \"name\": \"Chicago Branch Router\",\n          \"ip\": \"10.102.0.1\",\n          \"role\": \"Branch Gateway\",\n          \"tags\": [\n            \"branch\",\n            \"chicago\",\n            \"sd-wan\"\n          ],\n          \"notes\": [\n            \"Cisco Viptela vEdge\",\n            \"SD-WAN enabled\"\n          ],\n          \"mac\": \"00:1A:2B:BR:03:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"branch-router-lon\": {\n          \"shape\": \"router\",\n          \"name\": \"London Branch Router\",\n          \"ip\": \"10.200.0.1\",\n          \"role\": \"Branch Gateway\",\n          \"tags\": [\n            \"branch\",\n            \"london\",\n            \"sd-wan\"\n          ],\n          \"notes\": [\n            \"Cisco Viptela vEdge\",\n            \"EMEA region\"\n          ],\n          \"mac\": \"00:1A:2B:BR:04:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"branch-router-tokyo\": {\n          \"shape\": \"router\",\n          \"name\": \"Tokyo Branch Router\",\n          \"ip\": \"10.201.0.1\",\n          \"role\": \"Branch Gateway\",\n          \"tags\": [\n            \"branch\",\n            \"tokyo\",\n            \"sd-wan\"\n          ],\n          \"notes\": [\n            \"Cisco Viptela vEdge\",\n            \"APAC region\"\n          ],\n          \"mac\": \"00:1A:2B:BR:05:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"cloud-aws\": {\n          \"shape\": \"cloud\",\n          \"name\": \"AWS Cloud\",\n          \"ip\": \"vpc-0a1b2c3d\",\n          \"role\": \"Public Cloud\",\n          \"tags\": [\n            \"cloud\",\n            \"aws\",\n            \"hybrid\"\n          ],\n          \"notes\": [\n            \"AWS US-East-1\",\n            \"VPC peering to HQ\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"logical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"cloud-azure\": {\n          \"shape\": \"cloud\",\n          \"name\": \"Azure Cloud\",\n          \"ip\": \"vnet-corp-prod\",\n          \"role\": \"Public Cloud\",\n          \"tags\": [\n            \"cloud\",\n            \"azure\",\n            \"hybrid\"\n          ],\n          \"notes\": [\n            \"Azure East US 2\",\n            \"ExpressRoute\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"logical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"cloud-gcp\": {\n          \"shape\": \"cloud\",\n          \"name\": \"GCP Cloud\",\n          \"ip\": \"vpc-gcp-corp\",\n          \"role\": \"Public Cloud\",\n          \"tags\": [\n            \"cloud\",\n            \"gcp\",\n            \"dev\"\n          ],\n          \"notes\": [\n            \"GCP us-central1\",\n            \"Dev/Test workloads\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"logical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"isp-primary\": {\n          \"shape\": \"globe\",\n          \"name\": \"ISP Primary\",\n          \"ip\": \"203.0.113.1\",\n          \"role\": \"Internet Uplink\",\n          \"tags\": [\n            \"wan\",\n            \"internet\",\n            \"primary\"\n          ],\n          \"notes\": [\n            \"AT&T MPLS\",\n            \"1 Gbps dedicated\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"isp-secondary\": {\n          \"shape\": \"vm\",\n          \"name\": \"ISP Secondary\",\n          \"ip\": \"198.51.100.1\",\n          \"role\": \"Internet Uplink\",\n          \"tags\": [\n            \"wan\",\n            \"internet\",\n            \"backup\"\n          ],\n          \"notes\": [\n            \"Verizon Business\",\n            \"500 Mbps backup\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null,\n          \"rotation\": -17\n        },\n        \"dc-internal-1\": {\n          \"shape\": \"circle\",\n          \"name\": \"DC1 Int DNS\",\n          \"ip\": \"10.10.0.53\",\n          \"role\": \"Internal DNS/AD\",\n          \"tags\": [\n            \"dns\",\n            \"ad\",\n            \"dc1\"\n          ],\n          \"notes\": [\n            \"Windows Server 2022\",\n            \"Primary DC\"\n          ],\n          \"mac\": \"00:50:56:AD:01:01\",\n          \"rackUnit\": 26,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dc-internal-2\": {\n          \"shape\": \"circle\",\n          \"name\": \"DC2 Int DNS\",\n          \"ip\": \"10.10.1.53\",\n          \"role\": \"Internal DNS/AD\",\n          \"tags\": [\n            \"dns\",\n            \"ad\",\n            \"dc2\"\n          ],\n          \"notes\": [\n            \"Windows Server 2022\",\n            \"Secondary DC\"\n          ],\n          \"mac\": \"00:50:56:AD:01:02\",\n          \"rackUnit\": 26,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"app-server-1\": {\n          \"shape\": \"server\",\n          \"name\": \"App Server 01\",\n          \"ip\": \"10.10.0.101\",\n          \"role\": \"Application\",\n          \"tags\": [\n            \"app\",\n            \"iis\",\n            \"web\"\n          ],\n          \"notes\": [\n            \"Windows Server 2022\",\n            \"IIS Application\"\n          ],\n          \"mac\": \"00:50:56:AP:01:01\",\n          \"rackUnit\": 24,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"app-server-2\": {\n          \"shape\": \"server\",\n          \"name\": \"App Server 02\",\n          \"ip\": \"10.10.0.102\",\n          \"role\": \"Application\",\n          \"tags\": [\n            \"app\",\n            \"iis\",\n            \"web\"\n          ],\n          \"notes\": [\n            \"Windows Server 2022\",\n            \"IIS Application\"\n          ],\n          \"mac\": \"00:50:56:AP:01:02\",\n          \"rackUnit\": 22,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"db-server-1\": {\n          \"shape\": \"database\",\n          \"name\": \"SQL Server 01\",\n          \"ip\": \"10.10.0.201\",\n          \"role\": \"Database\",\n          \"tags\": [\n            \"db\",\n            \"sql\",\n            \"primary\"\n          ],\n          \"notes\": [\n            \"SQL Server 2022 Enterprise\",\n            \"AlwaysOn Primary\"\n          ],\n          \"mac\": \"00:50:56:DB:01:01\",\n          \"rackUnit\": 20,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"db-server-2\": {\n          \"shape\": \"database\",\n          \"name\": \"SQL Server 02\",\n          \"ip\": \"10.10.1.201\",\n          \"role\": \"Database\",\n          \"tags\": [\n            \"db\",\n            \"sql\",\n            \"secondary\"\n          ],\n          \"notes\": [\n            \"SQL Server 2022 Enterprise\",\n            \"AlwaysOn Secondary\"\n          ],\n          \"mac\": \"00:50:56:DB:01:02\",\n          \"rackUnit\": 24,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"k8s-master-1\": {\n          \"shape\": \"hexagon\",\n          \"name\": \"K8s Master 1\",\n          \"ip\": \"10.10.1.50\",\n          \"role\": \"Container Orchestration\",\n          \"tags\": [\n            \"kubernetes\",\n            \"master\",\n            \"container\"\n          ],\n          \"notes\": [\n            \"K8s Control Plane\",\n            \"etcd member\"\n          ],\n          \"mac\": \"00:50:56:K8:01:01\",\n          \"rackUnit\": 21,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"k8s-master-2\": {\n          \"shape\": \"hexagon\",\n          \"name\": \"K8s Master 2\",\n          \"ip\": \"10.10.1.51\",\n          \"role\": \"Container Orchestration\",\n          \"tags\": [\n            \"kubernetes\",\n            \"master\",\n            \"container\"\n          ],\n          \"notes\": [\n            \"K8s Control Plane\",\n            \"etcd member\"\n          ],\n          \"mac\": \"00:50:56:K8:01:02\",\n          \"rackUnit\": 19,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"k8s-master-3\": {\n          \"shape\": \"hexagon\",\n          \"name\": \"K8s Master 3\",\n          \"ip\": \"10.10.1.52\",\n          \"role\": \"Container Orchestration\",\n          \"tags\": [\n            \"kubernetes\",\n            \"master\",\n            \"container\"\n          ],\n          \"notes\": [\n            \"K8s Control Plane\",\n            \"etcd member\"\n          ],\n          \"mac\": \"00:50:56:K8:01:03\",\n          \"rackUnit\": 17,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"k8s-worker-1\": {\n          \"shape\": \"server\",\n          \"name\": \"K8s Worker 1\",\n          \"ip\": \"10.10.1.60\",\n          \"role\": \"Container Workload\",\n          \"tags\": [\n            \"kubernetes\",\n            \"worker\",\n            \"container\"\n          ],\n          \"notes\": [\n            \"K8s Worker Node\",\n            \"64GB RAM\"\n          ],\n          \"mac\": \"00:50:56:K8:02:01\",\n          \"rackUnit\": 15,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"k8s-worker-2\": {\n          \"shape\": \"server\",\n          \"name\": \"K8s Worker 2\",\n          \"ip\": \"10.10.1.61\",\n          \"role\": \"Container Workload\",\n          \"tags\": [\n            \"kubernetes\",\n            \"worker\",\n            \"container\"\n          ],\n          \"notes\": [\n            \"K8s Worker Node\",\n            \"64GB RAM\"\n          ],\n          \"mac\": \"00:50:56:K8:02:02\",\n          \"rackUnit\": 13,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"k8s-worker-3\": {\n          \"shape\": \"server\",\n          \"name\": \"K8s Worker 3\",\n          \"ip\": \"10.10.1.62\",\n          \"role\": \"Container Workload\",\n          \"tags\": [\n            \"kubernetes\",\n            \"worker\",\n            \"container\"\n          ],\n          \"notes\": [\n            \"K8s Worker Node\",\n            \"64GB RAM\"\n          ],\n          \"mac\": \"00:50:56:K8:02:03\",\n          \"rackUnit\": 11,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"k8s-worker-4\": {\n          \"shape\": \"server\",\n          \"name\": \"K8s Worker 4\",\n          \"ip\": \"10.10.1.63\",\n          \"role\": \"Container Workload\",\n          \"tags\": [\n            \"kubernetes\",\n            \"worker\",\n            \"container\"\n          ],\n          \"notes\": [\n            \"K8s Worker Node\",\n            \"64GB RAM\"\n          ],\n          \"mac\": \"00:50:56:K8:02:04\",\n          \"rackUnit\": 9,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"proxy-server-1\": {\n          \"shape\": \"server\",\n          \"name\": \"Proxy Server 1\",\n          \"ip\": \"10.5.0.10\",\n          \"role\": \"Web Proxy\",\n          \"tags\": [\n            \"proxy\",\n            \"squid\",\n            \"filtering\"\n          ],\n          \"notes\": [\n            \"Squid Proxy\",\n            \"Content filtering\"\n          ],\n          \"mac\": \"00:50:56:PX:01:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"proxy-server-2\": {\n          \"shape\": \"server\",\n          \"name\": \"Proxy Server 2\",\n          \"ip\": \"10.5.0.11\",\n          \"role\": \"Web Proxy\",\n          \"tags\": [\n            \"proxy\",\n            \"squid\",\n            \"filtering\"\n          ],\n          \"notes\": [\n            \"Squid Proxy\",\n            \"HA pair\"\n          ],\n          \"mac\": \"00:50:56:PX:01:02\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"vpn-concentrator\": {\n          \"shape\": \"firewall\",\n          \"name\": \"VPN Concentrator\",\n          \"ip\": \"10.0.5.1\",\n          \"role\": \"Remote Access VPN\",\n          \"tags\": [\n            \"vpn\",\n            \"remote\",\n            \"security\"\n          ],\n          \"notes\": [\n            \"Cisco ASA 5555-X\",\n            \"AnyConnect SSL VPN\"\n          ],\n          \"mac\": \"00:1A:2B:VP:01:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"nac-server\": {\n          \"shape\": \"server\",\n          \"name\": \"NAC Server\",\n          \"ip\": \"10.5.5.10\",\n          \"role\": \"Network Access Control\",\n          \"tags\": [\n            \"nac\",\n            \"ise\",\n            \"802.1x\"\n          ],\n          \"notes\": [\n            \"Cisco ISE 3.1\",\n            \"RADIUS/TACACS+\"\n          ],\n          \"mac\": \"00:50:56:NA:01:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"print-server\": {\n          \"shape\": \"server\",\n          \"name\": \"Print Server\",\n          \"ip\": \"10.10.0.150\",\n          \"role\": \"Print Services\",\n          \"tags\": [\n            \"print\",\n            \"windows\",\n            \"services\"\n          ],\n          \"notes\": [\n            \"Windows Print Server\",\n            \"50+ printers\"\n          ],\n          \"mac\": \"00:50:56:PR:01:01\",\n          \"rackUnit\": 18,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"file-server\": {\n          \"shape\": \"database\",\n          \"name\": \"File Server\",\n          \"ip\": \"10.10.0.160\",\n          \"role\": \"File Services\",\n          \"tags\": [\n            \"file\",\n            \"smb\",\n            \"dfs\"\n          ],\n          \"notes\": [\n            \"Windows File Server\",\n            \"DFS namespace\"\n          ],\n          \"mac\": \"00:50:56:FS:01:01\",\n          \"rackUnit\": 16,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"ca-server\": {\n          \"shape\": \"server\",\n          \"name\": \"Certificate Authority\",\n          \"ip\": \"192.168.100.80\",\n          \"role\": \"PKI Infrastructure\",\n          \"tags\": [\n            \"pki\",\n            \"ca\",\n            \"security\"\n          ],\n          \"notes\": [\n            \"Windows CA\",\n            \"Enterprise Root CA\"\n          ],\n          \"mac\": \"00:50:56:CA:01:01\",\n          \"rackUnit\": 5,\n          \"uHeight\": \"1\",\n          \"layer\": \"logical\",\n          \"assignedRack\": \"mgmt-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"sccm-server\": {\n          \"shape\": \"server\",\n          \"name\": \"SCCM Server\",\n          \"ip\": \"192.168.100.90\",\n          \"role\": \"Endpoint Management\",\n          \"tags\": [\n            \"sccm\",\n            \"patching\",\n            \"software\"\n          ],\n          \"notes\": [\n            \"MECM Primary Site\",\n            \"Software deployment\"\n          ],\n          \"mac\": \"00:50:56:SC:01:01\",\n          \"rackUnit\": 3,\n          \"uHeight\": \"2\",\n          \"layer\": \"logical\",\n          \"assignedRack\": \"mgmt-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"voip-cluster\": {\n          \"shape\": \"phone\",\n          \"name\": \"VoIP Cluster\",\n          \"ip\": \"10.50.0.0/24\",\n          \"role\": \"Voice Services\",\n          \"tags\": [\n            \"voip\",\n            \"cisco\",\n            \"ucm\"\n          ],\n          \"notes\": [\n            \"Cisco UCM Cluster\",\n            \"3000 endpoints\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"application\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"video-conf\": {\n          \"shape\": \"laptop\",\n          \"name\": \"Video Conference\",\n          \"ip\": \"10.51.0.0/24\",\n          \"role\": \"Video Services\",\n          \"tags\": [\n            \"video\",\n            \"webex\",\n            \"teams\"\n          ],\n          \"notes\": [\n            \"Webex/Teams integration\",\n            \"Meeting rooms\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"application\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"security-cameras\": {\n          \"shape\": \"camera\",\n          \"name\": \"Security Cameras\",\n          \"ip\": \"10.60.0.0/24\",\n          \"role\": \"Physical Security\",\n          \"tags\": [\n            \"cctv\",\n            \"surveillance\",\n            \"security\"\n          ],\n          \"notes\": [\n            \"150+ IP cameras\",\n            \"30-day retention\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"nvr-cluster\": {\n          \"shape\": \"server\",\n          \"name\": \"NVR Cluster\",\n          \"ip\": \"10.60.0.10\",\n          \"role\": \"Video Recording\",\n          \"tags\": [\n            \"nvr\",\n            \"surveillance\",\n            \"storage\"\n          ],\n          \"notes\": [\n            \"Milestone XProtect\",\n            \"500TB storage\"\n          ],\n          \"mac\": \"00:50:56:NV:01:01\",\n          \"rackUnit\": 15,\n          \"uHeight\": \"4\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-b2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dev-server-1\": {\n          \"shape\": \"server\",\n          \"name\": \"Dev Server 1\",\n          \"ip\": \"10.80.0.10\",\n          \"role\": \"Development\",\n          \"tags\": [\n            \"dev\",\n            \"gitlab\",\n            \"ci-cd\",\n            {\n              \"type\": \"icon\",\n              \"library\": \"selfhst\",\n              \"name\": \"dokku\"\n            }\n          ],\n          \"notes\": [\n            \"GitLab Server\",\n            \"CI/CD pipelines\"\n          ],\n          \"mac\": \"00:50:56:DV:01:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"application\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dev-server-2\": {\n          \"shape\": \"server\",\n          \"name\": \"Dev Server 2\",\n          \"ip\": \"10.80.0.11\",\n          \"role\": \"Development\",\n          \"tags\": [\n            \"dev\",\n            \"jenkins\",\n            \"ci-cd\"\n          ],\n          \"notes\": [\n            \"Jenkins Server\",\n            \"Build automation\"\n          ],\n          \"mac\": \"00:50:56:DV:01:02\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"application\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"test-environment\": {\n          \"shape\": \"shield\",\n          \"name\": \"Test Environment\",\n          \"ip\": \"10.81.0.0/24\",\n          \"role\": \"QA/Testing\",\n          \"tags\": [\n            \"test\",\n            \"qa\",\n            \"staging\"\n          ],\n          \"notes\": [\n            \"Staging environment\",\n            \"Pre-prod validation\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"application\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null,\n          \"rotation\": -36\n        },\n        \"erp-system\": {\n          \"shape\": \"database\",\n          \"name\": \"ERP System\",\n          \"ip\": \"10.90.0.10\",\n          \"role\": \"Business Application\",\n          \"tags\": [\n            \"erp\",\n            \"sap\",\n            \"business\"\n          ],\n          \"notes\": [\n            \"SAP S/4HANA\",\n            \"Financial/HR systems\"\n          ],\n          \"mac\": \"00:50:56:ER:01:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"4\",\n          \"layer\": \"application\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"crm-system\": {\n          \"shape\": \"database\",\n          \"name\": \"CRM System\",\n          \"ip\": \"10.91.0.10\",\n          \"role\": \"Business Application\",\n          \"tags\": [\n            \"crm\",\n            \"salesforce\",\n            \"business\"\n          ],\n          \"notes\": [\n            \"Salesforce integration\",\n            \"Sales/Marketing\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"application\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"endpoint-1000\": {\n          \"shape\": \"laptop\",\n          \"name\": \"Corporate Endpoints\",\n          \"ip\": \"10.70.0.0/22\",\n          \"role\": \"User Workstations\",\n          \"tags\": [\n            \"endpoints\",\n            \"workstations\",\n            \"users\"\n          ],\n          \"notes\": [\n            \"~1000 corporate laptops\",\n            \"Windows 11\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dist-switch-floor1\": {\n          \"shape\": \"switch\",\n          \"name\": \"Floor 1 Switch\",\n          \"ip\": \"10.1.1.1\",\n          \"role\": \"Distribution\",\n          \"tags\": [\n            \"distribution\",\n            \"floor-1\",\n            \"access\"\n          ],\n          \"notes\": [\n            \"Cisco C9300-48P\",\n            \"PoE+ enabled\"\n          ],\n          \"mac\": \"00:1A:2B:FL:01:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dist-switch-floor2\": {\n          \"shape\": \"switch\",\n          \"name\": \"Floor 2 Switch\",\n          \"ip\": \"10.1.2.1\",\n          \"role\": \"Distribution\",\n          \"tags\": [\n            \"distribution\",\n            \"floor-2\",\n            \"access\"\n          ],\n          \"notes\": [\n            \"Cisco C9300-48P\",\n            \"PoE+ enabled\"\n          ],\n          \"mac\": \"00:1A:2B:FL:02:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dist-switch-floor3\": {\n          \"shape\": \"switch\",\n          \"name\": \"Floor 3 Switch\",\n          \"ip\": \"10.1.3.1\",\n          \"role\": \"Distribution\",\n          \"tags\": [\n            \"distribution\",\n            \"floor-3\",\n            \"access\"\n          ],\n          \"notes\": [\n            \"Cisco C9300-48P\",\n            \"PoE+ enabled\"\n          ],\n          \"mac\": \"00:1A:2B:FL:03:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dist-switch-floor4\": {\n          \"shape\": \"switch\",\n          \"name\": \"Floor 4 Switch\",\n          \"ip\": \"10.1.4.1\",\n          \"role\": \"Distribution\",\n          \"tags\": [\n            \"distribution\",\n            \"floor-4\",\n            \"access\"\n          ],\n          \"notes\": [\n            \"Cisco C9300-48P\",\n            \"PoE+ enabled\"\n          ],\n          \"mac\": \"00:1A:2B:FL:04:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"ap-floor1-zone1\": {\n          \"shape\": \"wifi\",\n          \"name\": \"AP Floor 1 Zone 1\",\n          \"ip\": \"10.20.1.10\",\n          \"role\": \"Wireless Access\",\n          \"tags\": [\n            \"wifi\",\n            \"ap\",\n            \"floor-1\"\n          ],\n          \"notes\": [\n            \"Cisco 9120AX\",\n            \"Wi-Fi 6\"\n          ],\n          \"mac\": \"00:1A:2B:AP:01:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"ap-floor2-zone1\": {\n          \"shape\": \"wifi\",\n          \"name\": \"AP Floor 2 Zone 1\",\n          \"ip\": \"10.20.2.10\",\n          \"role\": \"Wireless Access\",\n          \"tags\": [\n            \"wifi\",\n            \"ap\",\n            \"floor-2\"\n          ],\n          \"notes\": [\n            \"Cisco 9120AX\",\n            \"Wi-Fi 6\"\n          ],\n          \"mac\": \"00:1A:2B:AP:02:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"ap-floor3-zone1\": {\n          \"shape\": \"wifi\",\n          \"name\": \"AP Floor 3 Zone 1\",\n          \"ip\": \"10.20.3.10\",\n          \"role\": \"Wireless Access\",\n          \"tags\": [\n            \"wifi\",\n            \"ap\",\n            \"floor-3\"\n          ],\n          \"notes\": [\n            \"Cisco 9120AX\",\n            \"Wi-Fi 6\"\n          ],\n          \"mac\": \"00:1A:2B:AP:03:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"ap-floor4-zone1\": {\n          \"shape\": \"wifi\",\n          \"name\": \"AP Floor 4 Zone 1\",\n          \"ip\": \"10.20.4.10\",\n          \"role\": \"Wireless Access\",\n          \"tags\": [\n            \"wifi\",\n            \"ap\",\n            \"floor-4\"\n          ],\n          \"notes\": [\n            \"Cisco 9120AX\",\n            \"Wi-Fi 6\"\n          ],\n          \"mac\": \"00:1A:2B:AP:04:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"ups-dc-1\": {\n          \"shape\": \"rectangle\",\n          \"name\": \"UPS DC-1\",\n          \"ip\": \"192.168.200.10\",\n          \"role\": \"Power Management\",\n          \"tags\": [\n            \"power\",\n            \"ups\",\n            \"datacenter\"\n          ],\n          \"notes\": [\n            \"APC Symmetra\",\n            \"80kVA\",\n            \"30 min runtime\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"ups-dc-2\": {\n          \"shape\": \"rectangle\",\n          \"name\": \"UPS DC-2\",\n          \"ip\": \"192.168.200.11\",\n          \"role\": \"Power Management\",\n          \"tags\": [\n            \"power\",\n            \"ups\",\n            \"datacenter\"\n          ],\n          \"notes\": [\n            \"APC Symmetra\",\n            \"80kVA\",\n            \"Redundant\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"pdu-rack-a1\": {\n          \"shape\": \"rectangle\",\n          \"name\": \"PDU Rack A1\",\n          \"ip\": \"192.168.200.21\",\n          \"role\": \"Power Distribution\",\n          \"tags\": [\n            \"power\",\n            \"pdu\",\n            \"rack-a1\"\n          ],\n          \"notes\": [\n            \"APC Switched PDU\",\n            \"Per-outlet metering\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": 1,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"pdu-rack-a2\": {\n          \"shape\": \"rectangle\",\n          \"name\": \"PDU Rack A2\",\n          \"ip\": \"192.168.200.22\",\n          \"role\": \"Power Distribution\",\n          \"tags\": [\n            \"power\",\n            \"pdu\",\n            \"rack-a2\"\n          ],\n          \"notes\": [\n            \"APC Switched PDU\",\n            \"Per-outlet metering\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": 1,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"cooling-1\": {\n          \"shape\": \"rectangle\",\n          \"name\": \"CRAC Unit 1\",\n          \"ip\": \"192.168.200.30\",\n          \"role\": \"Cooling\",\n          \"tags\": [\n            \"cooling\",\n            \"hvac\",\n            \"datacenter\"\n          ],\n          \"notes\": [\n            \"Liebert CRV\",\n            \"Row-based cooling\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"cooling-2\": {\n          \"shape\": \"rectangle\",\n          \"name\": \"CRAC Unit 2\",\n          \"ip\": \"192.168.200.31\",\n          \"role\": \"Cooling\",\n          \"tags\": [\n            \"cooling\",\n            \"hvac\",\n            \"datacenter\"\n          ],\n          \"notes\": [\n            \"Liebert CRV\",\n            \"N+1 redundancy\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"camera-a\": {\n          \"shape\": \"camera\",\n          \"name\": \"camera A\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"ping\": {\n            \"enabled\": false,\n            \"protocol\": \"http\",\n            \"customUrl\": \"\",\n            \"timeout\": 3000,\n            \"status\": \"unknown\",\n            \"lastCheck\": null\n          },\n          \"locked\": false,\n          \"groupId\": null,\n          \"fovEnabled\": true,\n          \"fovRotation\": 104,\n          \"fovDistance\": 500,\n          \"fovSweep\": 60,\n          \"fovSpeed\": 10,\n          \"fovAnimate\": true\n        },\n        \"camera-a-copy\": {\n          \"shape\": \"camera\",\n          \"name\": \"camera B\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"ping\": {\n            \"enabled\": false,\n            \"protocol\": \"http\",\n            \"customUrl\": \"\",\n            \"timeout\": 3000,\n            \"status\": \"unknown\",\n            \"lastCheck\": null\n          },\n          \"locked\": false,\n          \"groupId\": null,\n          \"fovEnabled\": true,\n          \"fovRotation\": 162,\n          \"fovDistance\": 500,\n          \"fovSweep\": 60,\n          \"fovSpeed\": 10,\n          \"fovAnimate\": false\n        }\n      },\n      \"edges\": {\n        \"list\": [\n          {\n            \"id\": \"isp1-router1\",\n            \"from\": \"isp-primary\",\n            \"to\": \"core-router-1\",\n            \"width\": 6,\n            \"color\": \"#10b981\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"Primary WAN link\"\n            ],\n            \"fromPort\": \"Gi0/0\",\n            \"toPort\": \"Gi1/0/1\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"isp2-router2\",\n            \"from\": \"isp-secondary\",\n            \"to\": \"core-router-2\",\n            \"width\": 6,\n            \"color\": \"#10b981\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"Backup WAN link\"\n            ],\n            \"fromPort\": \"Gi0/0\",\n            \"toPort\": \"Gi1/0/1\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"router1-router2\",\n            \"from\": \"core-router-1\",\n            \"to\": \"core-router-2\",\n            \"width\": 4,\n            \"color\": \"#f59e0b\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"HSRP Peering\"\n            ],\n            \"fromPort\": \"Gi1/0/24\",\n            \"toPort\": \"Gi1/0/24\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"router1-fw1\",\n            \"from\": \"core-router-1\",\n            \"to\": \"fw-external-1\",\n            \"width\": 4,\n            \"color\": \"#ef4444\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"router2-fw2\",\n            \"from\": \"core-router-2\",\n            \"to\": \"fw-external-2\",\n            \"width\": 4,\n            \"color\": \"#ef4444\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"fw1-fw2\",\n            \"from\": \"fw-external-1\",\n            \"to\": \"fw-external-2\",\n            \"width\": 3,\n            \"color\": \"#f59e0b\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"HA heartbeat\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dashed\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"fw1-coresw1\",\n            \"from\": \"fw-external-1\",\n            \"to\": \"core-switch-1\",\n            \"width\": 4,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"fw2-coresw2\",\n            \"from\": \"fw-external-2\",\n            \"to\": \"core-switch-2\",\n            \"width\": 4,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-coresw2\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"core-switch-2\",\n            \"width\": 5,\n            \"color\": \"#3b82f6\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"VPC peer-link\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-fwint\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"fw-internal\",\n            \"width\": 3,\n            \"color\": \"#ef4444\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw2-fwint\",\n            \"from\": \"core-switch-2\",\n            \"to\": \"fw-internal\",\n            \"width\": 3,\n            \"color\": \"#ef4444\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-racka1\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"dc-rack-a1\",\n            \"width\": 4,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw2-racka1\",\n            \"from\": \"core-switch-2\",\n            \"to\": \"dc-rack-a1\",\n            \"width\": 4,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-racka2\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"dc-rack-a2\",\n            \"width\": 4,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw2-racka2\",\n            \"from\": \"core-switch-2\",\n            \"to\": \"dc-rack-a2\",\n            \"width\": 4,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-rackb1\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"dc-rack-b1\",\n            \"width\": 4,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw2-rackb1\",\n            \"from\": \"core-switch-2\",\n            \"to\": \"dc-rack-b1\",\n            \"width\": 4,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-rackb2\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"dc-rack-b2\",\n            \"width\": 4,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw2-rackb2\",\n            \"from\": \"core-switch-2\",\n            \"to\": \"dc-rack-b2\",\n            \"width\": 4,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"fw1-dmz\",\n            \"from\": \"fw-external-1\",\n            \"to\": \"dmz-rack\",\n            \"width\": 3,\n            \"color\": \"#f59e0b\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"DMZ segment\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"fw2-dmz\",\n            \"from\": \"fw-external-2\",\n            \"to\": \"dmz-rack\",\n            \"width\": 3,\n            \"color\": \"#f59e0b\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"DMZ segment\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-mgmt\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"mgmt-rack\",\n            \"width\": 3,\n            \"color\": \"#8b5cf6\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"OOB management\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-wlc1\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"wlc-primary\",\n            \"width\": 3,\n            \"color\": \"#06b6d4\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw2-wlc2\",\n            \"from\": \"core-switch-2\",\n            \"to\": \"wlc-secondary\",\n            \"width\": 3,\n            \"color\": \"#06b6d4\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\",\n            \"animate\": true\n          },\n          {\n            \"id\": \"wlc1-wlc2\",\n            \"from\": \"wlc-primary\",\n            \"to\": \"wlc-secondary\",\n            \"width\": 2,\n            \"color\": \"#f59e0b\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"HA pair\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dashed\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"wlc1-mobile-hq\",\n            \"from\": \"wlc-primary\",\n            \"to\": \"mobile-zone-hq\",\n            \"width\": 2,\n            \"color\": \"#06b6d4\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"wlc1-mobile-guest\",\n            \"from\": \"wlc-primary\",\n            \"to\": \"mobile-zone-guest\",\n            \"width\": 2,\n            \"color\": \"#06b6d4\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"wlc1-mobile-iot\",\n            \"from\": \"wlc-primary\",\n            \"to\": \"mobile-zone-iot\",\n            \"width\": 2,\n            \"color\": \"#06b6d4\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"router1-branch-ny\",\n            \"from\": \"core-router-1\",\n            \"to\": \"branch-router-ny\",\n            \"width\": 3,\n            \"color\": \"#a855f7\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"SD-WAN tunnel\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dashed\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"router1-branch-la\",\n            \"from\": \"core-router-1\",\n            \"to\": \"branch-router-la\",\n            \"width\": 3,\n            \"color\": \"#a855f7\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"SD-WAN tunnel\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dashed\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"router1-branch-chi\",\n            \"from\": \"core-router-1\",\n            \"to\": \"branch-router-chi\",\n            \"width\": 3,\n            \"color\": \"#a855f7\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"SD-WAN tunnel\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dashed\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"router1-branch-lon\",\n            \"from\": \"core-router-1\",\n            \"to\": \"branch-router-lon\",\n            \"width\": 3,\n            \"color\": \"#a855f7\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"SD-WAN tunnel\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dashed\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"router1-branch-tokyo\",\n            \"from\": \"core-router-1\",\n            \"to\": \"branch-router-tokyo\",\n            \"width\": 3,\n            \"color\": \"#a855f7\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"SD-WAN tunnel\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dashed\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"router1-aws\",\n            \"from\": \"core-router-1\",\n            \"to\": \"cloud-aws\",\n            \"width\": 3,\n            \"color\": \"#f97316\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"Direct Connect\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dashed\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"router2-azure\",\n            \"from\": \"core-router-2\",\n            \"to\": \"cloud-azure\",\n            \"width\": 3,\n            \"color\": \"#0ea5e9\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"ExpressRoute\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dashed\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"fwint-gcp\",\n            \"from\": \"fw-internal\",\n            \"to\": \"cloud-gcp\",\n            \"width\": 2,\n            \"color\": \"#22c55e\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"VPN tunnel\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dashed\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-floor1\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"dist-switch-floor1\",\n            \"width\": 3,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-floor2\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"dist-switch-floor2\",\n            \"width\": 3,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw2-floor3\",\n            \"from\": \"core-switch-2\",\n            \"to\": \"dist-switch-floor3\",\n            \"width\": 3,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw2-floor4\",\n            \"from\": \"core-switch-2\",\n            \"to\": \"dist-switch-floor4\",\n            \"width\": 3,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"floor1-endpoints\",\n            \"from\": \"dist-switch-floor1\",\n            \"to\": \"endpoint-1000\",\n            \"width\": 2,\n            \"color\": \"#94a3b8\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"floor1-ap1\",\n            \"from\": \"dist-switch-floor1\",\n            \"to\": \"ap-floor1-zone1\",\n            \"width\": 2,\n            \"color\": \"#06b6d4\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"floor2-ap2\",\n            \"from\": \"dist-switch-floor2\",\n            \"to\": \"ap-floor2-zone1\",\n            \"width\": 2,\n            \"color\": \"#06b6d4\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"floor3-ap3\",\n            \"from\": \"dist-switch-floor3\",\n            \"to\": \"ap-floor3-zone1\",\n            \"width\": 2,\n            \"color\": \"#06b6d4\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"floor4-ap4\",\n            \"from\": \"dist-switch-floor4\",\n            \"to\": \"ap-floor4-zone1\",\n            \"width\": 2,\n            \"color\": \"#06b6d4\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"fwint-proxy1\",\n            \"from\": \"fw-internal\",\n            \"to\": \"proxy-server-1\",\n            \"width\": 2,\n            \"color\": \"#ef4444\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"fwint-proxy2\",\n            \"from\": \"fw-internal\",\n            \"to\": \"proxy-server-2\",\n            \"width\": 2,\n            \"color\": \"#ef4444\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"fwext1-vpn\",\n            \"from\": \"fw-external-1\",\n            \"to\": \"vpn-concentrator\",\n            \"width\": 3,\n            \"color\": \"#8b5cf6\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-nac\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"nac-server\",\n            \"width\": 2,\n            \"color\": \"#f59e0b\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-voip\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"voip-cluster\",\n            \"width\": 3,\n            \"color\": \"#22c55e\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw2-video\",\n            \"from\": \"core-switch-2\",\n            \"to\": \"video-conf\",\n            \"width\": 3,\n            \"color\": \"#22c55e\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-cameras\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"security-cameras\",\n            \"width\": 2,\n            \"color\": \"#94a3b8\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"fwint-dev1\",\n            \"from\": \"fw-internal\",\n            \"to\": \"dev-server-1\",\n            \"width\": 2,\n            \"color\": \"#a855f7\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"fwint-dev2\",\n            \"from\": \"fw-internal\",\n            \"to\": \"dev-server-2\",\n            \"width\": 2,\n            \"color\": \"#a855f7\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\",\n            \"animate\": true,\n            \"animationSpeed\": \"1.5\"\n          },\n          {\n            \"id\": \"fwint-test\",\n            \"from\": \"fw-internal\",\n            \"to\": \"test-environment\",\n            \"width\": 2,\n            \"color\": \"#a855f7\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-erp\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"erp-system\",\n            \"width\": 3,\n            \"color\": \"#f59e0b\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"fwext1-crm\",\n            \"from\": \"fw-external-1\",\n            \"to\": \"crm-system\",\n            \"width\": 2,\n            \"color\": \"#f59e0b\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"Salesforce cloud\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dashed\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"ups1-racka1\",\n            \"from\": \"ups-dc-1\",\n            \"to\": \"dc-rack-a1\",\n            \"width\": 2,\n            \"color\": \"#fbbf24\",\n            \"direction\": \"forward\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"Power feed A\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"ups2-racka2\",\n            \"from\": \"ups-dc-2\",\n            \"to\": \"dc-rack-a2\",\n            \"width\": 2,\n            \"color\": \"#fbbf24\",\n            \"direction\": \"forward\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"Power feed B\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"ups1-rackb1\",\n            \"from\": \"ups-dc-1\",\n            \"to\": \"dc-rack-b1\",\n            \"width\": 2,\n            \"color\": \"#fbbf24\",\n            \"direction\": \"forward\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"Power feed A\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\",\n            \"animate\": true,\n            \"animationSpeed\": \"4\"\n          },\n          {\n            \"id\": \"ups2-rackb2\",\n            \"from\": \"ups-dc-2\",\n            \"to\": \"dc-rack-b2\",\n            \"width\": 2,\n            \"color\": \"#fbbf24\",\n            \"direction\": \"forward\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"Power feed B\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"cooling1-racka1\",\n            \"from\": \"cooling-1\",\n            \"to\": \"dc-rack-a1\",\n            \"width\": 2,\n            \"color\": \"#38bdf8\",\n            \"direction\": \"forward\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"Cooling zone\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dotted\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"cooling2-rackb1\",\n            \"from\": \"cooling-2\",\n            \"to\": \"dc-rack-b1\",\n            \"width\": 2,\n            \"color\": \"#38bdf8\",\n            \"direction\": \"forward\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"Cooling zone\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dotted\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"custom-1765237881452\",\n            \"type\": \"custom\",\n            \"color\": \"#c800ff\",\n            \"width\": 4,\n            \"lineStyle\": \"solid\",\n            \"direction\": \"forward\",\n            \"points\": [\n              {\n                \"x\": 3492.3994140625,\n                \"y\": 1526.9556884765625\n              },\n              {\n                \"x\": 3500.609619140625,\n                \"y\": 1830.7386474609375\n              },\n              {\n                \"x\": 3303.561279296875,\n                \"y\": 1732.2144775390625\n              }\n            ],\n            \"notes\": [],\n            \"routing\": \"orthogonal\"\n          }\n        ]\n      },\n      \"positions\": {\n        \"core-router-1\": {\n          \"x\": 3720.166015625,\n          \"y\": 245.9932403564453\n        },\n        \"core-router-2\": {\n          \"x\": 2499.883407638303,\n          \"y\": 329.99503430389154\n        },\n        \"fw-external-1\": {\n          \"x\": 3221.7385182723783,\n          \"y\": 1016.1364499992887\n        },\n        \"fw-external-2\": {\n          \"x\": 1915.5213706410505,\n          \"y\": 224.43528858865443\n        },\n        \"fw-internal\": {\n          \"x\": 1746.9168185079352,\n          \"y\": 477.5300527221864\n        },\n        \"core-switch-1\": {\n          \"x\": 449.39860669455675,\n          \"y\": 384.4578707617695\n        },\n        \"core-switch-2\": {\n          \"x\": 761.1664921394672,\n          \"y\": 180.89283910873155\n        },\n        \"dc-rack-a1\": {\n          \"x\": 783.7017241128451,\n          \"y\": 647.4086870405963\n        },\n        \"dc-rack-a2\": {\n          \"x\": 209.25701628255229,\n          \"y\": 228.01593190351014\n        },\n        \"dc-rack-b1\": {\n          \"x\": 3184.3186625759854,\n          \"y\": 1627.4495531027196\n        },\n        \"dc-rack-b2\": {\n          \"x\": 245.37065918741246,\n          \"y\": 499.6191264194081\n        },\n        \"dmz-rack\": {\n          \"x\": 2176.4105289561007,\n          \"y\": 610.8312056412005\n        },\n        \"mgmt-rack\": {\n          \"x\": 1601.2987201807314,\n          \"y\": 1281.4753424975324\n        },\n        \"esxi-host-01\": {\n          \"x\": 2162.2166789540615,\n          \"y\": 2608.110619289529\n        },\n        \"esxi-host-02\": {\n          \"x\": 2205.94717202368,\n          \"y\": 2689.67539624076\n        },\n        \"esxi-host-03\": {\n          \"x\": 2154.6015436939074,\n          \"y\": 2771.203009774913\n        },\n        \"esxi-host-04\": {\n          \"x\": 2195.986926025096,\n          \"y\": 2845\n        },\n        \"tor-switch-a1\": {\n          \"x\": 2146.8943639962963,\n          \"y\": 2845\n        },\n        \"esxi-host-05\": {\n          \"x\": 2185.9099961569727,\n          \"y\": 2845\n        },\n        \"esxi-host-06\": {\n          \"x\": 2139.099728450725,\n          \"y\": 2845\n        },\n        \"esxi-host-07\": {\n          \"x\": 2175.7223818764883,\n          \"y\": 2845\n        },\n        \"esxi-host-08\": {\n          \"x\": 2131.2222777148922,\n          \"y\": 2845\n        },\n        \"tor-switch-a2\": {\n          \"x\": 2165.4301485385085,\n          \"y\": 2845\n        },\n        \"san-primary\": {\n          \"x\": 2123.2667017518106,\n          \"y\": 2845\n        },\n        \"san-secondary\": {\n          \"x\": 2155.0394237844876,\n          \"y\": 2845\n        },\n        \"fc-switch-1\": {\n          \"x\": 2115.2377370375634,\n          \"y\": 2845\n        },\n        \"fc-switch-2\": {\n          \"x\": 2144.5563938942755,\n          \"y\": 2845\n        },\n        \"backup-server-1\": {\n          \"x\": 2107.1401637413705,\n          \"y\": 2845\n        },\n        \"backup-server-2\": {\n          \"x\": 2133.987300103025,\n          \"y\": 2845\n        },\n        \"tape-library\": {\n          \"x\": 2098.9788028796397,\n          \"y\": 2845\n        },\n        \"tor-switch-b1\": {\n          \"x\": 2123.338434885373,\n          \"y\": 2845\n        },\n        \"tor-switch-b2\": {\n          \"x\": 2090.7585134456995,\n          \"y\": 2845\n        },\n        \"web-server-1\": {\n          \"x\": 2112.6161382091163,\n          \"y\": 2845\n        },\n        \"web-server-2\": {\n          \"x\": 2082.484189516922,\n          \"y\": 2845\n        },\n        \"waf-1\": {\n          \"x\": 2101.826793760617,\n          \"y\": 2845\n        },\n        \"load-balancer-dmz\": {\n          \"x\": 2074.1607573409574,\n          \"y\": 2845\n        },\n        \"mail-gateway\": {\n          \"x\": 2090.97682514417,\n          \"y\": 2845\n        },\n        \"dns-external-1\": {\n          \"x\": 2065.7931724028163,\n          \"y\": 2845\n        },\n        \"dns-external-2\": {\n          \"x\": 2080.0726920576153,\n          \"y\": 2845\n        },\n        \"vcenter\": {\n          \"x\": 2057.3864164745437,\n          \"y\": 2845\n        },\n        \"nsx-manager\": {\n          \"x\": 2069.1208864464534,\n          \"y\": 2845\n        },\n        \"siem-server\": {\n          \"x\": 2048.945494649244,\n          \"y\": 2845\n        },\n        \"nms-server\": {\n          \"x\": 2058.1279286387635,\n          \"y\": 2845\n        },\n        \"jump-server\": {\n          \"x\": 2040.4754323612206,\n          \"y\": 2845\n        },\n        \"ipam-server\": {\n          \"x\": 2047.1003634632284,\n          \"y\": 2845\n        },\n        \"wlc-primary\": {\n          \"x\": 1575.9723612611924,\n          \"y\": 2306.135986328125\n        },\n        \"wlc-secondary\": {\n          \"x\": 1468.1361870166274,\n          \"y\": 1563.733642578125\n        },\n        \"mobile-zone-hq\": {\n          \"x\": 2354.901177346808,\n          \"y\": 2806.0078125\n        },\n        \"mobile-zone-guest\": {\n          \"x\": 2307.6605605284435,\n          \"y\": 2611.047119140625\n        },\n        \"mobile-zone-iot\": {\n          \"x\": 2229.397686389302,\n          \"y\": 2299.110107421875\n        },\n        \"branch-router-ny\": {\n          \"x\": 3151.903101363964,\n          \"y\": 633.6580810546875\n        },\n        \"branch-router-la\": {\n          \"x\": 3083.8876194705945,\n          \"y\": 506.90625\n        },\n        \"branch-router-chi\": {\n          \"x\": 3355.02409980103,\n          \"y\": 393.1805725097656\n        },\n        \"branch-router-lon\": {\n          \"x\": 3113.609823320121,\n          \"y\": 260.4093322753906\n        },\n        \"branch-router-tokyo\": {\n          \"x\": 3699.3234994733834,\n          \"y\": 471.4241027832031\n        },\n        \"cloud-aws\": {\n          \"x\": 3436.528122523513,\n          \"y\": 545.9614868164062\n        },\n        \"cloud-azure\": {\n          \"x\": 2592.566210818907,\n          \"y\": 2724.068115234375\n        },\n        \"cloud-gcp\": {\n          \"x\": 2827.3183770424234,\n          \"y\": 2731.397216796875\n        },\n        \"isp-primary\": {\n          \"x\": 3712.192068081962,\n          \"y\": 615.64990234375\n        },\n        \"isp-secondary\": {\n          \"x\": 3253.9473366098055,\n          \"y\": 1993.2629089355469\n        },\n        \"dc-internal-1\": {\n          \"x\": 1958.4243458877936,\n          \"y\": 2845\n        },\n        \"dc-internal-2\": {\n          \"x\": 1963.768951182132,\n          \"y\": 2845\n        },\n        \"app-server-1\": {\n          \"x\": 1947.3819379304134,\n          \"y\": 2845\n        },\n        \"app-server-2\": {\n          \"x\": 1955.2862087394126,\n          \"y\": 2845\n        },\n        \"db-server-1\": {\n          \"x\": 1936.3708569559828,\n          \"y\": 2845\n        },\n        \"db-server-2\": {\n          \"x\": 1946.8300873488822,\n          \"y\": 2845\n        },\n        \"k8s-master-1\": {\n          \"x\": 1925.397658583093,\n          \"y\": 2845\n        },\n        \"k8s-master-2\": {\n          \"x\": 1938.405621494142,\n          \"y\": 2845\n        },\n        \"k8s-master-3\": {\n          \"x\": 1914.4688758763386,\n          \"y\": 2845\n        },\n        \"k8s-worker-1\": {\n          \"x\": 1930.017826812177,\n          \"y\": 2845\n        },\n        \"k8s-worker-2\": {\n          \"x\": 1903.5910154567553,\n          \"y\": 2845\n        },\n        \"k8s-worker-3\": {\n          \"x\": 1921.6716971072178,\n          \"y\": 2845\n        },\n        \"k8s-worker-4\": {\n          \"x\": 1892.7705536280016,\n          \"y\": 2845\n        },\n        \"proxy-server-1\": {\n          \"x\": 1806.1152433697903,\n          \"y\": 653.7529296875\n        },\n        \"proxy-server-2\": {\n          \"x\": 2937.4207928721535,\n          \"y\": 2628.7880859375\n        },\n        \"vpn-concentrator\": {\n          \"x\": 3642.252088474593,\n          \"y\": 946.7255249023438\n        },\n        \"nac-server\": {\n          \"x\": 1153.2626148502184,\n          \"y\": 1172.1895751953125\n        },\n        \"print-server\": {\n          \"x\": 1896.9328460745962,\n          \"y\": 2845\n        },\n        \"file-server\": {\n          \"x\": 1860.7177871362182,\n          \"y\": 2845\n        },\n        \"ca-server\": {\n          \"x\": 1888.8027739274805,\n          \"y\": 2845\n        },\n        \"sccm-server\": {\n          \"x\": 1850.1909418511675,\n          \"y\": 2845\n        },\n        \"voip-cluster\": {\n          \"x\": 1777.038465328039,\n          \"y\": 1616.8961181640625\n        },\n        \"video-conf\": {\n          \"x\": 1993.8373941679588,\n          \"y\": 2244.936309814453\n        },\n        \"security-cameras\": {\n          \"x\": 1674.413336949044,\n          \"y\": 2046.0380859375\n        },\n        \"nvr-cluster\": {\n          \"x\": 1829.4110389706402,\n          \"y\": 2845\n        },\n        \"dev-server-1\": {\n          \"x\": 2800.5894350649614,\n          \"y\": 1175.623291015625\n        },\n        \"dev-server-2\": {\n          \"x\": 1945.0822182484326,\n          \"y\": 1164.5184783935547\n        },\n        \"test-environment\": {\n          \"x\": 2932.0863047891075,\n          \"y\": 862.4592895507812\n        },\n        \"erp-system\": {\n          \"x\": 789.9880103985649,\n          \"y\": 473.7113342285156\n        },\n        \"crm-system\": {\n          \"x\": 3514.6003232048542,\n          \"y\": 1137.7720947265625\n        },\n        \"endpoint-1000\": {\n          \"x\": 991.6812012057328,\n          \"y\": 2284.42236328125\n        },\n        \"dist-switch-floor1\": {\n          \"x\": 654.2091033261356,\n          \"y\": 2020.0086669921875\n        },\n        \"dist-switch-floor2\": {\n          \"x\": 853.8845527112826,\n          \"y\": 1843.2872314453125\n        },\n        \"dist-switch-floor3\": {\n          \"x\": 1899.4353951584517,\n          \"y\": 1456.5068359375\n        },\n        \"dist-switch-floor4\": {\n          \"x\": 488.5289313756234,\n          \"y\": 181.47256469726562\n        },\n        \"ap-floor1-zone1\": {\n          \"x\": 1140.16846970184,\n          \"y\": 2070.2916259765625\n        },\n        \"ap-floor2-zone1\": {\n          \"x\": 688.1952143592268,\n          \"y\": 2384.4775390625\n        },\n        \"ap-floor3-zone1\": {\n          \"x\": 2145.3803027919676,\n          \"y\": 1890.2816162109375\n        },\n        \"ap-floor4-zone1\": {\n          \"x\": 517.646146409649,\n          \"y\": 565.59716796875\n        },\n        \"ups-dc-1\": {\n          \"x\": 771.1406786539856,\n          \"y\": 295.9266662597656\n        },\n        \"ups-dc-2\": {\n          \"x\": 216.2410855890687,\n          \"y\": 330.3345947265625\n        },\n        \"pdu-rack-a1\": {\n          \"x\": 1804.774444371901,\n          \"y\": 2845\n        },\n        \"pdu-rack-a2\": {\n          \"x\": 1741.6184034693686,\n          \"y\": 2845\n        },\n        \"cooling-1\": {\n          \"x\": 245.7080801919958,\n          \"y\": 626.1914672851562\n        },\n        \"cooling-2\": {\n          \"x\": 1603.293611085831,\n          \"y\": 981.0621185302734\n        },\n        \"camera-a\": {\n          \"x\": 166.57075412676295,\n          \"y\": 145\n        },\n        \"camera-a-copy\": {\n          \"x\": 1040.653076171875,\n          \"y\": 738.42822265625\n        }\n      },\n      \"sizes\": {\n        \"isp-secondary\": 139,\n        \"test-environment\": 121,\n        \"dev-server-1\": 128,\n        \"core-router-2\": 120,\n        \"camera-a\": 45,\n        \"camera-a-copy\": 45\n      },\n      \"styles\": {\n        \"dc-rack-b2\": {\n          \"all\": {\n            \"circleColor\": \"#ff0000\"\n          }\n        },\n        \"dc-rack-a1\": {\n          \"all\": {\n            \"circleColor\": \"#ff0000\"\n          }\n        },\n        \"dc-rack-b1\": {\n          \"all\": {\n            \"circleColor\": \"#ff0000\",\n            \"titleSize\": 59\n          }\n        },\n        \"isp-secondary\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"alist\"\n            },\n            \"circleColor\": \"#4d2c58\",\n            \"circleBorder\": \"#000000\",\n            \"titleColor\": \"#006eff\"\n          }\n        },\n        \"core-router-2\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"actual-budget\"\n            },\n            \"pingOffsetX\": -15,\n            \"pingOffsetY\": -38\n          }\n        },\n        \"fw-external-1\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"anonaddy\"\n            }\n          }\n        },\n        \"cloud-aws\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"ansible\"\n            }\n          }\n        },\n        \"isp-primary\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"wikidocs\"\n            }\n          }\n        },\n        \"branch-router-tokyo\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"adguard-home\"\n            }\n          }\n        },\n        \"core-router-1\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"borg\"\n            }\n          }\n        },\n        \"test-environment\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"simple\",\n              \"name\": \"apple\"\n            }\n          }\n        },\n        \"dev-server-1\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"simple\",\n              \"name\": \"amazonwebservices\"\n            }\n          }\n        }\n      },\n      \"legend\": {\n        \"#10b981\": \"Trusted Lan\",\n        \"#f59e0b\": \"Secure Lan\",\n        \"#ef4444\": \"DMZ\",\n        \"#475569\": \"Main ISP\",\n        \"#3b82f6\": \"Alternate ISP\",\n        \"#8b5cf6\": \"you can edit me too\",\n        \"#06b6d4\": \"you can edit me too\",\n        \"#a855f7\": \"you can edit me too\",\n        \"#f97316\": \"you can edit me too\",\n        \"#0ea5e9\": \"you can edit me too\",\n        \"#22c55e\": \"you can edit me too\",\n        \"#94a3b8\": \"you can edit me too\",\n        \"#fbbf24\": \"you can edit me too\",\n        \"#38bdf8\": \"you can edit me too\",\n        \"#c800ff\": \"you can edit me too\"\n      },\n      \"rects\": {\n        \"list\": [\n          {\n            \"id\": \"rect-1765237540610\",\n            \"x\": 2879.214599609375,\n            \"y\": 159.71981811523438,\n            \"width\": 992.196044921875,\n            \"height\": 538.8650817871094,\n            \"color\": \"#f97316\",\n            \"style\": \"filled\",\n            \"lineStyle\": \"solid\",\n            \"notes\": []\n          },\n          {\n            \"id\": \"rect-1765237681216\",\n            \"x\": 448.3926696777344,\n            \"y\": 1671.651123046875,\n            \"width\": 916.3436584472656,\n            \"height\": 924.27734375,\n            \"color\": \"#c800ff\",\n            \"style\": \"outlined\",\n            \"lineStyle\": \"solid\",\n            \"notes\": []\n          },\n          {\n            \"id\": \"rect-1766437913740\",\n            \"x\": 904.5889892578125,\n            \"y\": 115.40318298339844,\n            \"width\": 110.93878173828125,\n            \"height\": 919.6242218017578,\n            \"color\": \"#5215f9\",\n            \"style\": \"filled\",\n            \"lineStyle\": \"wall\",\n            \"notes\": [],\n            \"borderWidth\": 13\n          },\n          {\n            \"id\": \"rect-1766437935414\",\n            \"x\": 130.93685150146484,\n            \"y\": 1072.3624877929688,\n            \"width\": 872.9131851196289,\n            \"height\": 99.260986328125,\n            \"color\": \"#5215f9\",\n            \"style\": \"filled\",\n            \"lineStyle\": \"wall\",\n            \"notes\": [],\n            \"borderWidth\": 13\n          }\n        ]\n      },\n      \"texts\": {\n        \"list\": [\n          {\n            \"id\": \"text-1765237828167\",\n            \"x\": 3411.458740234375,\n            \"y\": 1390.00439453125,\n            \"content\": \"Double click on desktop\\nor long press on mobile\\nto enter rack canvas view\",\n            \"fontSize\": 46,\n            \"color\": \"#e2e8f0\",\n            \"fontWeight\": \"bold\",\n            \"fontStyle\": \"italic\",\n            \"textAlign\": \"middle\",\n            \"textDecoration\": \"none\",\n            \"bgColor\": \"#000000\",\n            \"bgEnabled\": false,\n            \"opacity\": 1\n          },\n          {\n            \"id\": \"text-1766446595277\",\n            \"x\": 654.3878479003906,\n            \"y\": 1367.7945556640625,\n            \"content\": \"SITE A\",\n            \"fontSize\": 101,\n            \"color\": \"#e2e8f0\",\n            \"fontWeight\": \"normal\",\n            \"fontStyle\": \"normal\",\n            \"textAlign\": \"start\",\n            \"textDecoration\": \"none\",\n            \"bgColor\": \"#000000\",\n            \"bgEnabled\": false,\n            \"opacity\": 1\n          },\n          {\n            \"id\": \"text-1766446610211\",\n            \"x\": 180.63662719726562,\n            \"y\": 1128.822998046875,\n            \"content\": \"SITE A\",\n            \"fontSize\": 101,\n            \"color\": \"#e2e8f0\",\n            \"fontWeight\": \"normal\",\n            \"fontStyle\": \"normal\",\n            \"textAlign\": \"start\",\n            \"textDecoration\": \"none\",\n            \"bgColor\": \"#000000\",\n            \"bgEnabled\": false,\n            \"opacity\": 1\n          },\n          {\n            \"id\": \"text-1766453024797\",\n            \"x\": 968.6458740234375,\n            \"y\": 1028.6621398925781,\n            \"content\": \"SITE A\",\n            \"fontSize\": 101,\n            \"color\": \"#e2e8f0\",\n            \"fontWeight\": \"normal\",\n            \"fontStyle\": \"normal\",\n            \"textAlign\": \"start\",\n            \"textDecoration\": \"none\",\n            \"bgColor\": \"#000000\",\n            \"bgEnabled\": false,\n            \"opacity\": 1,\n            \"rotation\": -89,\n            \"_dragStartX\": 972.46826171875,\n            \"_dragStartY\": 1009.5499572753906\n          },\n          {\n            \"id\": \"text-1766453070975\",\n            \"x\": 613.1589965820312,\n            \"y\": 1139.512939453125,\n            \"content\": \"SITE A\",\n            \"fontSize\": 101,\n            \"color\": \"#e2e8f0\",\n            \"fontWeight\": \"normal\",\n            \"fontStyle\": \"normal\",\n            \"textAlign\": \"start\",\n            \"textDecoration\": \"none\",\n            \"bgColor\": \"#000000\",\n            \"bgEnabled\": false,\n            \"opacity\": 1\n          },\n          {\n            \"id\": \"text-1766453072857\",\n            \"x\": 968.64599609375,\n            \"y\": 474.40818786621094,\n            \"content\": \"SITE A\",\n            \"fontSize\": 101,\n            \"color\": \"#e2e8f0\",\n            \"fontWeight\": \"normal\",\n            \"fontStyle\": \"normal\",\n            \"textAlign\": \"start\",\n            \"textDecoration\": \"none\",\n            \"bgColor\": \"#000000\",\n            \"bgEnabled\": false,\n            \"opacity\": 1,\n            \"rotation\": 269,\n            \"_dragStartX\": 1480.85302734375,\n            \"_dragStartY\": 822.2503356933594\n          },\n          {\n            \"id\": \"text-1766458222326\",\n            \"x\": 3167.812744140625,\n            \"y\": 2190.516357421875,\n            \"content\": \"\",\n            \"fontSize\": 18,\n            \"color\": \"#e2e8f0\",\n            \"fontWeight\": \"normal\",\n            \"fontStyle\": \"normal\",\n            \"textAlign\": \"start\",\n            \"textDecoration\": \"none\",\n            \"bgColor\": \"#000000\",\n            \"bgEnabled\": false,\n            \"opacity\": 1\n          }\n        ]\n      },\n      \"pageState\": {\n        \"title\": \"The One File Corporate\",\n        \"background\": \"\",\n        \"topbarBg\": \"rgba(9, 12, 20, 0.9)\",\n        \"topbarBorder\": \"#1f2533\",\n        \"panel\": \"#0b0e13\",\n        \"panelAlt\": \"#10141b\",\n        \"accent\": \"#4fd1c5\",\n        \"sidebarBg\": \"#10141b\",\n        \"btnBg\": \"#0b0e13\",\n        \"btnText\": \"#e2e8f0\",\n        \"tagFill\": \"#1e293b\",\n        \"tagText\": \"#e2e8f0\",\n        \"tagBorder\": \"#475569\",\n        \"inputBg\": \"#0b0e13\",\n        \"inputText\": \"#e2e8f0\",\n        \"inputBorder\": \"#1f2937\",\n        \"inputFont\": \"Inter, system-ui, sans-serif\",\n        \"inputFontSize\": 14,\n        \"toolbarBg\": \"#0f172a\",\n        \"toolbarBorder\": \"#1f2937\",\n        \"toolbarText\": \"#94a3b8\",\n        \"toolbarBtnBg\": \"#0b0e13\",\n        \"toolbarBtnText\": \"#e2e8f0\",\n        \"minimapDots\": \"#94a3b8\",\n        \"canvasHintEnabled\": true,\n        \"canvasHintText\": \"\",\n        \"canvasHintBg\": \"#0f172a\",\n        \"canvasHintColor\": \"#94a3b8\",\n        \"danger\": \"#f56565\",\n        \"textMain\": \"#e2e8f0\",\n        \"textSoft\": \"#94a3b8\",\n        \"topbarHeight\": 103,\n        \"sidebarWidth\": 350,\n        \"mobileFooterHeight\": 40,\n        \"sidebarCollapsed\": false,\n        \"nodeFill\": \"#1e293b\",\n        \"nodeStroke\": \"#475569\",\n        \"nodeTitle\": \"#e2e8f0\",\n        \"nodeSub\": \"#94a3b8\",\n        \"nodeTitleSize\": 41,\n        \"nodeSubSize\": 27,\n        \"nodeFont\": \"monospace\",\n        \"defaultEdge\": \"#475569\",\n        \"selectionHandle\": \"#f59e0b\",\n        \"selectionHandleSize\": 8,\n        \"groupIndicator\": \"#4fd1c5\",\n        \"canvasGradientTop\": \"#1e2532\",\n        \"canvasGradientBottom\": \"#050608\",\n        \"canvasBorder\": \"#475569\",\n        \"canvasGrid\": \"#475569\",\n        \"canvasGridSize\": 50,\n        \"canvasGridEnabled\": true,\n        \"rackFrameFill\": \"#0f172a\",\n        \"rackGridEnabled\": true,\n        \"rackFrameStroke\": \"#4fd1c5\",\n        \"rackLineColor\": \"#475569\",\n        \"rackTextColor\": \"#4fd1c5\",\n        \"viewOnly\": false,\n        \"defaultEdgeRouting\": \"orthogonal\",\n        \"animateConnections\": false,\n        \"animationStyle\": \"arrows\",\n        \"animationDirection\": \"all\",\n        \"animationSpeed\": 4,\n        \"autoPingEnabled\": false,\n        \"autoPingInterval\": 30\n      }\n    },\n    {\n      \"id\": \"tab-1765235136918\",\n      \"name\": \"Homelab 2\",\n      \"nodes\": {\n        \"internet\": {\n          \"shape\": \"stop-sign\",\n          \"name\": \"Internet\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"internet-copy\": {\n          \"shape\": \"firewall\",\n          \"name\": \"OPNSENSE\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"opnsense-copy\": {\n          \"shape\": \"firewall\",\n          \"name\": \"Docker\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"docker-copy\": {\n          \"shape\": \"firewall\",\n          \"name\": \"Docker2\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"docker-copy-1\": {\n          \"shape\": \"firewall\",\n          \"name\": \"Docker3\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"docker-copy-2\": {\n          \"shape\": \"firewall\",\n          \"name\": \"Docker 4\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [\n            {\n              \"type\": \"icon\",\n              \"library\": \"selfhst\",\n              \"name\": \"docker\"\n            },\n            {\n              \"type\": \"icon\",\n              \"library\": \"selfhst\",\n              \"name\": \"authentik\"\n            },\n            {\n              \"type\": \"icon\",\n              \"library\": \"selfhst\",\n              \"name\": \"immich\"\n            }\n          ],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"opnsense-copy-1\": {\n          \"shape\": \"firewall\",\n          \"name\": \"OPNSENSE GUEST\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"phone\": {\n          \"shape\": \"phone\",\n          \"name\": \"Phone\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"desktop\": {\n          \"shape\": \"pc\",\n          \"name\": \"Desktop\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dns\": {\n          \"shape\": \"cloud\",\n          \"name\": \"DNS\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"racked\": {\n          \"shape\": \"server\",\n          \"name\": \"Racked\",\n          \"ip\": \"\",\n          \"role\": \"Rack\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": true,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"thermostat\": {\n          \"shape\": \"thermostat\",\n          \"name\": \"Thermostat\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"video-doorbell\": {\n          \"shape\": \"doorbell\",\n          \"name\": \"Video Doorbell\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"smart-lock\": {\n          \"shape\": \"smart-lock\",\n          \"name\": \"Smart Lock\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"smart-bulb\": {\n          \"shape\": \"smart-bulb\",\n          \"name\": \"Smart Bulb\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"robot-vacuum\": {\n          \"shape\": \"vacuum\",\n          \"name\": \"Robot Vacuum\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        }\n      },\n      \"edges\": {\n        \"list\": [\n          {\n            \"id\": \"internet-internet-copy-1765238145151\",\n            \"from\": \"internet\",\n            \"to\": \"internet-copy\",\n            \"width\": 4,\n            \"color\": \"#55e208\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1\n          },\n          {\n            \"id\": \"internet-copy-opnsense-copy-1765238187451\",\n            \"from\": \"internet-copy\",\n            \"to\": \"opnsense-copy\",\n            \"width\": 4,\n            \"color\": \"#4c00ff\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1\n          },\n          {\n            \"id\": \"internet-copy-docker-copy-1765238242477\",\n            \"from\": \"internet-copy\",\n            \"to\": \"docker-copy\",\n            \"width\": 4,\n            \"color\": \"#4c00ff\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1\n          },\n          {\n            \"id\": \"internet-copy-docker-copy-1-1765238244637\",\n            \"from\": \"internet-copy\",\n            \"to\": \"docker-copy-1\",\n            \"width\": 4,\n            \"color\": \"#4c00ff\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1\n          },\n          {\n            \"id\": \"internet-copy-docker-copy-2-1765238246233\",\n            \"from\": \"internet-copy\",\n            \"to\": \"docker-copy-2\",\n            \"width\": 4,\n            \"color\": \"#4c00ff\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1\n          },\n          {\n            \"id\": \"internet-opnsense-copy-1-1765238266117\",\n            \"from\": \"internet\",\n            \"to\": \"opnsense-copy-1\",\n            \"width\": 4,\n            \"color\": \"#80ff00\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1\n          },\n          {\n            \"id\": \"opnsense-copy-1-dns-1765238347996\",\n            \"from\": \"opnsense-copy-1\",\n            \"to\": \"dns\",\n            \"width\": 4,\n            \"color\": \"#fb00ff\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1\n          },\n          {\n            \"id\": \"dns-desktop-1765238386101\",\n            \"from\": \"dns\",\n            \"to\": \"desktop\",\n            \"width\": 4,\n            \"color\": \"#ff00d0\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1\n          },\n          {\n            \"id\": \"phone-dns-1765238391156\",\n            \"from\": \"phone\",\n            \"to\": \"dns\",\n            \"width\": 4,\n            \"color\": \"#ff00d0\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1\n          },\n          {\n            \"id\": \"custom-1765239449323\",\n            \"type\": \"custom\",\n            \"color\": \"#f97316\",\n            \"width\": 4,\n            \"lineStyle\": \"solid\",\n            \"direction\": \"forward\",\n            \"points\": [\n              {\n                \"x\": 2936.464111328125,\n                \"y\": 786.07958984375\n              },\n              {\n                \"x\": 3184.112060546875,\n                \"y\": 887.6153564453125\n              },\n              {\n                \"x\": 2763.110107421875,\n                \"y\": 981.7216796875\n              }\n            ],\n            \"notes\": []\n          }\n        ]\n      },\n      \"positions\": {\n        \"internet\": {\n          \"x\": 1757.7735887323333,\n          \"y\": 298.77284240722656\n        },\n        \"internet-copy\": {\n          \"x\": 2066.9677515897347,\n          \"y\": 473.4119134177565\n        },\n        \"opnsense-copy\": {\n          \"x\": 1773.8400660428597,\n          \"y\": 666.5758233298659\n        },\n        \"docker-copy\": {\n          \"x\": 1931.1978950081452,\n          \"y\": 782.2775961320921\n        },\n        \"docker-copy-1\": {\n          \"x\": 2158.1262397347077,\n          \"y\": 767.7122274797483\n        },\n        \"docker-copy-2\": {\n          \"x\": 2342.2663764534577,\n          \"y\": 631.7681967180296\n        },\n        \"opnsense-copy-1\": {\n          \"x\": 2757.879480087803,\n          \"y\": 307.6117116091891\n        },\n        \"phone\": {\n          \"x\": 3312.857751572178,\n          \"y\": 502.58220111114224\n        },\n        \"desktop\": {\n          \"x\": 2971.700036728428,\n          \"y\": 480.7287465212985\n        },\n        \"dns\": {\n          \"x\": 3200.4643189549906,\n          \"y\": 320.469591247861\n        },\n        \"racked\": {\n          \"x\": 2645.5845448279656,\n          \"y\": 970.7820678889219\n        },\n        \"thermostat\": {\n          \"x\": 1323.0595481711202,\n          \"y\": 574.6132617105841\n        },\n        \"video-doorbell\": {\n          \"x\": 1188.4284446554952,\n          \"y\": 455.3684191812872\n        },\n        \"smart-lock\": {\n          \"x\": 1292.286782057839,\n          \"y\": 790.0231738199591\n        },\n        \"smart-bulb\": {\n          \"x\": 1496.156899245339,\n          \"y\": 716.9377246012091\n        },\n        \"robot-vacuum\": {\n          \"x\": 2288.5581443625265,\n          \"y\": 978.5069995035528\n        }\n      },\n      \"sizes\": {\n        \"core-router-1\": 36,\n        \"internet\": 168,\n        \"phone\": 121,\n        \"desktop\": 147,\n        \"racked\": 137,\n        \"docker-copy-2\": 82\n      },\n      \"styles\": {\n        \"internet\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"amazon-web-services\"\n            },\n            \"circleColor\": \"#db0000\",\n            \"circleBorder\": \"#000000\",\n            \"titleSize\": 52,\n            \"subSize\": 46\n          }\n        },\n        \"opnsense-copy-1\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"opnsense-v1\"\n            }\n          }\n        },\n        \"internet-copy\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"opnsense\"\n            }\n          }\n        },\n        \"docker-copy-2\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"docker\"\n            }\n          }\n        },\n        \"docker-copy-1\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"authportal\"\n            }\n          }\n        },\n        \"docker-copy\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"jotty\"\n            }\n          }\n        },\n        \"opnsense-copy\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"portainer\"\n            }\n          }\n        },\n        \"racked\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"mdi\",\n              \"name\": \"server-security\"\n            },\n            \"circleColor\": \"#010813\",\n            \"circleBorder\": \"#ffffff\"\n          }\n        }\n      },\n      \"legend\": {\n        \"#475569\": \"you can edit me too\",\n        \"#65758b\": \"you can edit me too\",\n        \"#63748c\": \"you can edit me too\",\n        \"#5e6f87\": \"you can edit me too\",\n        \"#586a84\": \"you can edit me too\",\n        \"#4f627d\": \"you can edit me too\",\n        \"#455873\": \"you can edit me too\",\n        \"#3d506c\": \"you can edit me too\",\n        \"#354964\": \"you can edit me too\",\n        \"#2e415c\": \"you can edit me too\",\n        \"#293c56\": \"you can edit me too\",\n        \"#273a53\": \"you can edit me too\",\n        \"#253750\": \"you can edit me too\",\n        \"#23354d\": \"you can edit me too\",\n        \"#203046\": \"you can edit me too\",\n        \"#1e2d43\": \"you can edit me too\",\n        \"#1a283d\": \"you can edit me too\",\n        \"#172435\": \"you can edit me too\",\n        \"#141f2e\": \"you can edit me too\",\n        \"#111a27\": \"you can edit me too\",\n        \"#0f1824\": \"you can edit me too\",\n        \"#0d1521\": \"you can edit me too\",\n        \"#0c131d\": \"you can edit me too\",\n        \"#0c1d1c\": \"you can edit me too\",\n        \"#0c1c1d\": \"you can edit me too\",\n        \"#0c191d\": \"you can edit me too\",\n        \"#0c141d\": \"you can edit me too\",\n        \"#0c0d1d\": \"you can edit me too\",\n        \"#130c1d\": \"you can edit me too\",\n        \"#1b0c1d\": \"you can edit me too\",\n        \"#1d0c17\": \"you can edit me too\",\n        \"#1d0c10\": \"you can edit me too\",\n        \"#1d0c0c\": \"you can edit me too\",\n        \"#3b1b1b\": \"you can edit me too\",\n        \"#3c1a1a\": \"you can edit me too\",\n        \"#3f1c1c\": \"you can edit me too\",\n        \"#401c1c\": \"you can edit me too\",\n        \"#451c1c\": \"you can edit me too\",\n        \"#461b1b\": \"you can edit me too\",\n        \"#4c1a1a\": \"you can edit me too\",\n        \"#521919\": \"you can edit me too\",\n        \"#571919\": \"you can edit me too\",\n        \"#5d1818\": \"you can edit me too\",\n        \"#631717\": \"you can edit me too\",\n        \"#651515\": \"you can edit me too\",\n        \"#6a1616\": \"you can edit me too\",\n        \"#6f1515\": \"you can edit me too\",\n        \"#711414\": \"you can edit me too\",\n        \"#761414\": \"you can edit me too\",\n        \"#771313\": \"you can edit me too\",\n        \"#7c1313\": \"you can edit me too\",\n        \"#811313\": \"you can edit me too\",\n        \"#821212\": \"you can edit me too\",\n        \"#871212\": \"you can edit me too\",\n        \"#881111\": \"you can edit me too\",\n        \"#8d1111\": \"you can edit me too\",\n        \"#8e1010\": \"you can edit me too\",\n        \"#8f0f0f\": \"you can edit me too\",\n        \"#900e0e\": \"you can edit me too\",\n        \"#8e0b0b\": \"you can edit me too\",\n        \"#8c0d0d\": \"you can edit me too\",\n        \"#880c0c\": \"you can edit me too\",\n        \"#830c0c\": \"you can edit me too\",\n        \"#7e0c0c\": \"you can edit me too\",\n        \"#790c0c\": \"you can edit me too\",\n        \"#730c0c\": \"you can edit me too\",\n        \"#6f0b0b\": \"you can edit me too\",\n        \"#0b6f64\": \"you can edit me too\",\n        \"#0b6f5f\": \"you can edit me too\",\n        \"#0b6f56\": \"you can edit me too\",\n        \"#0b6f49\": \"you can edit me too\",\n        \"#0b6f31\": \"you can edit me too\",\n        \"#0b6f1f\": \"you can edit me too\",\n        \"#0b6f0d\": \"you can edit me too\",\n        \"#176f0b\": \"you can edit me too\",\n        \"#266f0b\": \"you can edit me too\",\n        \"#296f0b\": \"you can edit me too\",\n        \"#2e6f0b\": \"you can edit me too\",\n        \"#1a2d10\": \"you can edit me too\",\n        \"#1c3111\": \"you can edit me too\",\n        \"#213814\": \"you can edit me too\",\n        \"#233c15\": \"you can edit me too\",\n        \"#254017\": \"you can edit me too\",\n        \"#294918\": \"you can edit me too\",\n        \"#2b4d1a\": \"you can edit me too\",\n        \"#2d511a\": \"you can edit me too\",\n        \"#315a1b\": \"you can edit me too\",\n        \"#35631c\": \"you can edit me too\",\n        \"#37681d\": \"you can edit me too\",\n        \"#3b721d\": \"you can edit me too\",\n        \"#3f7b1e\": \"you can edit me too\",\n        \"#42851e\": \"you can edit me too\",\n        \"#46901d\": \"you can edit me too\",\n        \"#499a1d\": \"you can edit me too\",\n        \"#4b9f1d\": \"you can edit me too\",\n        \"#4ca61c\": \"you can edit me too\",\n        \"#50b01c\": \"you can edit me too\",\n        \"#51b71a\": \"you can edit me too\",\n        \"#50b918\": \"you can edit me too\",\n        \"#51c115\": \"you can edit me too\",\n        \"#53c615\": \"you can edit me too\",\n        \"#53c814\": \"you can edit me too\",\n        \"#52c913\": \"you can edit me too\",\n        \"#54d011\": \"you can edit me too\",\n        \"#53d110\": \"you can edit me too\",\n        \"#55d510\": \"you can edit me too\",\n        \"#55d70f\": \"you can edit me too\",\n        \"#54d80e\": \"you can edit me too\",\n        \"#54da0b\": \"you can edit me too\",\n        \"#56df0c\": \"you can edit me too\",\n        \"#53db0a\": \"you can edit me too\",\n        \"#55e00b\": \"you can edit me too\",\n        \"#55e109\": \"you can edit me too\",\n        \"#55e208\": \"ISP LINE\",\n        \"#4c00ff\": \"MY Guest NETWORK\",\n        \"#80ff00\": \"you can edit me too\",\n        \"#3b4234\": \"you can edit me too\",\n        \"#3a3442\": \"you can edit me too\",\n        \"#3b3442\": \"you can edit me too\",\n        \"#3c3442\": \"you can edit me too\",\n        \"#3d3442\": \"you can edit me too\",\n        \"#3e3442\": \"you can edit me too\",\n        \"#3f3442\": \"you can edit me too\",\n        \"#403442\": \"you can edit me too\",\n        \"#413442\": \"you can edit me too\",\n        \"#653d66\": \"you can edit me too\",\n        \"#683f69\": \"you can edit me too\",\n        \"#6c416c\": \"you can edit me too\",\n        \"#6f4370\": \"you can edit me too\",\n        \"#704270\": \"you can edit me too\",\n        \"#734474\": \"you can edit me too\",\n        \"#784479\": \"you can edit me too\",\n        \"#7d447e\": \"you can edit me too\",\n        \"#7e437f\": \"you can edit me too\",\n        \"#834384\": \"you can edit me too\",\n        \"#844285\": \"you can edit me too\",\n        \"#89418b\": \"you can edit me too\",\n        \"#8e428f\": \"you can edit me too\",\n        \"#904091\": \"you can edit me too\",\n        \"#923e93\": \"you can edit me too\",\n        \"#973e98\": \"you can edit me too\",\n        \"#943c96\": \"you can edit me too\",\n        \"#993c9a\": \"you can edit me too\",\n        \"#963a98\": \"you can edit me too\",\n        \"#973899\": \"you can edit me too\",\n        \"#99369b\": \"you can edit me too\",\n        \"#9a359c\": \"you can edit me too\",\n        \"#9b349d\": \"you can edit me too\",\n        \"#9d329f\": \"you can edit me too\",\n        \"#9e31a0\": \"you can edit me too\",\n        \"#a02fa2\": \"you can edit me too\",\n        \"#9d2d9f\": \"you can edit me too\",\n        \"#9f2ba1\": \"you can edit me too\",\n        \"#a129a3\": \"you can edit me too\",\n        \"#a327a5\": \"you can edit me too\",\n        \"#a525a7\": \"you can edit me too\",\n        \"#a723a9\": \"you can edit me too\",\n        \"#a921ab\": \"you can edit me too\",\n        \"#ab1fad\": \"you can edit me too\",\n        \"#ad1daf\": \"you can edit me too\",\n        \"#ae1cb0\": \"you can edit me too\",\n        \"#b019b3\": \"you can edit me too\",\n        \"#b118b4\": \"you can edit me too\",\n        \"#b316b6\": \"you can edit me too\",\n        \"#b816bb\": \"you can edit me too\",\n        \"#b514b8\": \"you can edit me too\",\n        \"#ba14bd\": \"you can edit me too\",\n        \"#b712ba\": \"you can edit me too\",\n        \"#bb13be\": \"you can edit me too\",\n        \"#b811bb\": \"you can edit me too\",\n        \"#be10c1\": \"you can edit me too\",\n        \"#bb0ebe\": \"you can edit me too\",\n        \"#bd0cc0\": \"you can edit me too\",\n        \"#be0bc1\": \"you can edit me too\",\n        \"#c108c4\": \"you can edit me too\",\n        \"#be06c1\": \"you can edit me too\",\n        \"#c103c4\": \"you can edit me too\",\n        \"#c301c6\": \"you can edit me too\",\n        \"#c400c7\": \"you can edit me too\",\n        \"#c900cc\": \"you can edit me too\",\n        \"#ce00d1\": \"you can edit me too\",\n        \"#d300d6\": \"you can edit me too\",\n        \"#d800db\": \"you can edit me too\",\n        \"#dd00e0\": \"you can edit me too\",\n        \"#e200e6\": \"you can edit me too\",\n        \"#ec00f0\": \"you can edit me too\",\n        \"#f100f5\": \"you can edit me too\",\n        \"#f600fa\": \"you can edit me too\",\n        \"#fb00ff\": \"you can edit me too\",\n        \"#ff00d0\": \"iPhone (always guest iPhone)\",\n        \"#f97316\": \"you can edit me too\"\n      },\n      \"rects\": {\n        \"list\": [\n          {\n            \"id\": \"rect-1765238219615\",\n            \"x\": 2680.053955078125,\n            \"y\": 251.44879150390625,\n            \"width\": 814.10400390625,\n            \"height\": 389.26678466796875,\n            \"color\": \"#ec0999\",\n            \"style\": \"filled\",\n            \"lineStyle\": \"solid\",\n            \"notes\": []\n          }\n        ]\n      },\n      \"texts\": {\n        \"list\": [\n          {\n            \"id\": \"text-1765238422602\",\n            \"x\": 2466.35986328125,\n            \"y\": 741.6801147460938,\n            \"content\": \"Double click on desktop\\nor long press on mobile\\nto enter rack canvas view\",\n            \"fontSize\": 40,\n            \"color\": \"#e2e8f0\",\n            \"fontWeight\": \"normal\",\n            \"fontStyle\": \"normal\",\n            \"textAlign\": \"start\",\n            \"textDecoration\": \"none\",\n            \"bgColor\": \"#000000\",\n            \"bgEnabled\": false,\n            \"opacity\": 1\n          }\n        ]\n      },\n      \"pageState\": {\n        \"title\": \"The One File\",\n        \"background\": \"\",\n        \"topbarBg\": \"rgba(9, 12, 20, 0.9)\",\n        \"topbarBorder\": \"#1f2533\",\n        \"panel\": \"#2f0e0e\",\n        \"panelAlt\": \"#10141b\",\n        \"accent\": \"#a75252\",\n        \"sidebarBg\": \"#10141b\",\n        \"btnBg\": \"#0b0e13\",\n        \"btnText\": \"#e2e8f0\",\n        \"tagFill\": \"#1e293b\",\n        \"tagText\": \"#e2e8f0\",\n        \"tagBorder\": \"#475569\",\n        \"inputBg\": \"#0b0e13\",\n        \"inputText\": \"#e2e8f0\",\n        \"inputBorder\": \"#1f2937\",\n        \"inputFont\": \"Inter, system-ui, sans-serif\",\n        \"inputFontSize\": 14,\n        \"toolbarBg\": \"#441215\",\n        \"toolbarBorder\": \"#1f2937\",\n        \"toolbarText\": \"#94a3b8\",\n        \"toolbarBtnBg\": \"#0b0e13\",\n        \"toolbarBtnText\": \"#e2e8f0\",\n        \"minimapDots\": \"#94a3b8\",\n        \"canvasHintEnabled\": true,\n        \"canvasHintText\": \"\",\n        \"canvasHintBg\": \"#0f172a\",\n        \"canvasHintColor\": \"#94a3b8\",\n        \"danger\": \"#f56565\",\n        \"textMain\": \"#e2e8f0\",\n        \"textSoft\": \"#94a3b8\",\n        \"topbarHeight\": 112,\n        \"sidebarWidth\": 350,\n        \"mobileFooterHeight\": 40,\n        \"sidebarCollapsed\": false,\n        \"nodeFill\": \"#1e293b\",\n        \"nodeStroke\": \"#475569\",\n        \"nodeTitle\": \"#e2e8f0\",\n        \"nodeSub\": \"#94a3b8\",\n        \"nodeTitleSize\": 18,\n        \"nodeSubSize\": 13,\n        \"nodeFont\": \"Inter, system-ui, sans-serif\",\n        \"defaultEdge\": \"#475569\",\n        \"selectionHandle\": \"#f59e0b\",\n        \"selectionHandleSize\": 8,\n        \"groupIndicator\": \"#4fd1c5\",\n        \"canvasGradientTop\": \"#1e2532\",\n        \"canvasGradientBottom\": \"#050608\",\n        \"canvasBorder\": \"#475569\",\n        \"canvasGrid\": \"#475569\",\n        \"canvasGridSize\": 50,\n        \"canvasGridEnabled\": true,\n        \"rackFrameFill\": \"#0f172a\",\n        \"rackGridEnabled\": true,\n        \"rackFrameStroke\": \"#4fd1c5\",\n        \"rackLineColor\": \"#475569\",\n        \"rackTextColor\": \"#4fd1c5\",\n        \"viewOnly\": false,\n        \"defaultEdgeRouting\": \"curved\",\n        \"animateConnections\": false,\n        \"animationStyle\": \"arrows\",\n        \"animationDirection\": \"all\",\n        \"animationSpeed\": 1.5\n      }\n    }\n  ],\n  \"currentTabIndex\": 1,\n  \"encryptedSections\": {},\n  \"auditLog\": [\n    {\n      \"timestamp\": 1766459374396,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file.html\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766459370112,\n      \"type\": \"tab\",\n      \"description\": \"Switched to tab: Homelab 2\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766459361896,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file-corporate.html\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459352785,\n      \"type\": \"node\",\n      \"description\": \"rotate node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459352343,\n      \"type\": \"node\",\n      \"description\": \"rotate node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459352224,\n      \"type\": \"node\",\n      \"description\": \"rotate node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459351722,\n      \"type\": \"node\",\n      \"description\": \"rotate node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459351541,\n      \"type\": \"node\",\n      \"description\": \"rotate node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459350380,\n      \"type\": \"node\",\n      \"description\": \"resize node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459350178,\n      \"type\": \"node\",\n      \"description\": \"resize node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459350049,\n      \"type\": \"node\",\n      \"description\": \"resize node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459346233,\n      \"type\": \"node\",\n      \"description\": \"change shape\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459335960,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459335846,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459335742,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459335630,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459335398,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459335292,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459335188,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459332894,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459332780,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459332661,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459332556,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459332450,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459332346,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459331643,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459331492,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459331378,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459331274,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459330996,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459330868,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459330764,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459330637,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459327262,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459327136,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459326544,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459326438,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459326334,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459326176,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459325232,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459325088,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459324279,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459323835,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459323732,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459323200,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459323093,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459322989,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459322883,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459322780,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459321176,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459321070,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459320748,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459320642,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459320492,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459319706,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459319600,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459319055,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459318467,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459318363,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459318258,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459317846,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459317742,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459317464,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459317314,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459313457,\n      \"type\": \"node\",\n      \"description\": \"change shape\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459310142,\n      \"type\": \"node\",\n      \"description\": \"change shape\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459306160,\n      \"type\": \"node\",\n      \"description\": \"rotate node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459305289,\n      \"type\": \"node\",\n      \"description\": \"rotate node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459305132,\n      \"type\": \"node\",\n      \"description\": \"rotate node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459304675,\n      \"type\": \"node\",\n      \"description\": \"rotate node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459304530,\n      \"type\": \"node\",\n      \"description\": \"rotate node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459304396,\n      \"type\": \"node\",\n      \"description\": \"rotate node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459304290,\n      \"type\": \"node\",\n      \"description\": \"rotate node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459304157,\n      \"type\": \"node\",\n      \"description\": \"rotate node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459303660,\n      \"type\": \"node\",\n      \"description\": \"rotate node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459303534,\n      \"type\": \"node\",\n      \"description\": \"rotate node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459303414,\n      \"type\": \"node\",\n      \"description\": \"rotate node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459303247,\n      \"type\": \"node\",\n      \"description\": \"rotate node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459303144,\n      \"type\": \"node\",\n      \"description\": \"rotate node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459303002,\n      \"type\": \"node\",\n      \"description\": \"rotate node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459302875,\n      \"type\": \"node\",\n      \"description\": \"rotate node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459302725,\n      \"type\": \"node\",\n      \"description\": \"rotate node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459302613,\n      \"type\": \"node\",\n      \"description\": \"rotate node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459302507,\n      \"type\": \"node\",\n      \"description\": \"rotate node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459301997,\n      \"type\": \"node\",\n      \"description\": \"rotate node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459301893,\n      \"type\": \"node\",\n      \"description\": \"rotate node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458459721,\n      \"type\": \"tab\",\n      \"description\": \"Switched to tab: Corporate Site B\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458438687,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458438583,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458438437,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458438333,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458438187,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458438083,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458437937,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458437833,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458437687,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458437583,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458437437,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458437333,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458437187,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458436932,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458435139,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458434986,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458434840,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458434736,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458434590,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458434486,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458434340,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458434236,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458434090,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458433986,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458433840,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458433736,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458433590,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458433334,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458429157,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458429053,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458428947,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458426794,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458426691,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458426584,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458426481,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458423513,\n      \"type\": \"node\",\n      \"description\": \"change shape\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458421278,\n      \"type\": \"node\",\n      \"description\": \"change shape\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458416555,\n      \"type\": \"node\",\n      \"description\": \"change shape\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458404891,\n      \"type\": \"node\",\n      \"description\": \"add node\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458392272,\n      \"type\": \"node\",\n      \"description\": \"add node\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458378068,\n      \"type\": \"node\",\n      \"description\": \"add node\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458367460,\n      \"type\": \"node\",\n      \"description\": \"add node\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458356226,\n      \"type\": \"node\",\n      \"description\": \"add node\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458338198,\n      \"type\": \"tab\",\n      \"description\": \"Switched to tab: Homelab 2\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458258865,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file-corporate.html\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458249051,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458248926,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458248793,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458248683,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458248556,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458248451,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458248325,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458248221,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458248092,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458247989,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458247885,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458247784,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458247284,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458246701,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458246523,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458246410,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458246129,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458245955,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458245737,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458245627,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458245425,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458245247,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458245133,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458244923,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458244741,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458244313,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458244198,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458244055,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458243873,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458243637,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458243399,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458243218,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458241018,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458237254,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458235033,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458234835,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458234694,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458234425,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458227773,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458227623,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458227441,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458227279,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458227155,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458226967,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458226847,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458226733,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458226563,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458226421,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458222326,\n      \"type\": \"text\",\n      \"description\": \"add text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458213989,\n      \"type\": \"connection\",\n      \"description\": \"delete edge\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458209437,\n      \"type\": \"text\",\n      \"description\": \"delete text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458195427,\n      \"type\": \"import\",\n      \"description\": \"Imported JSON: the-one-file-corporate.json (107 nodes, 65 connections)\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455847368,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455844534,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455844054,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455843762,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455843560,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455843371,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455843162,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455842852,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455842747,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455842601,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455842449,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455842348,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455842098,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455841678,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455841236,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455841053,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455840901,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455840650,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455839427,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455839234,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455839061,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455837247,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455837081,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455836893,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455836377,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455836198,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455835455,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455834630,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455831880,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455831676,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455831451,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455830817,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455830687,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455830176,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455830048,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455829944,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455829816,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455378795,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455378693,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455378459,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455378316,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455378180,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455378069,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455377956,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455377677,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455377558,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455377448,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455377318,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455377209,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453090534,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453090317,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453090213,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453090112,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453090009,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453089903,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453088895,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453088793,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453088689,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453088584,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453088480,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453088250,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453087236,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453086725,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453086485,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453086373,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453086142,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453086043,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453072857,\n      \"type\": \"text\",\n      \"description\": \"paste text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453070975,\n      \"type\": \"text\",\n      \"description\": \"paste text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453054439,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453053127,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453052450,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453052106,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453051948,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453051806,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453051334,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453050207,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453042725,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453042179,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453041797,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453041570,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453039703,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453039291,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453039168,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453039065,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453038481,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453038365,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453038237,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453038105,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453038001,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453037850,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453037745,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453037495,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453037378,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453037182,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453037078,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453036972,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453036860,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453036147,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453035945,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453035825,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453035720,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453035443,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453035337,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453035233,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453035127,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453035026,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453034917,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453031063,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453030955,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453030833,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453030732,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453030225,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453030104,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453029968,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453029796,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453029474,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453024797,\n      \"type\": \"text\",\n      \"description\": \"paste text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766451118553,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450929324,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450817210,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450257424,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450255024,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450254395,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450253241,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450251598,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450250392,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450248756,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450244072,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450242166,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450240998,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450236492,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450233672,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450232384,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450231012,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450230254,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450229302,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450228132,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446610211,\n      \"type\": \"text\",\n      \"description\": \"paste text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446604849,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446604702,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446604550,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446604404,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446604305,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446604204,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446604099,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446603952,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446603849,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446603702,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446603599,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446603452,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446603348,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446603202,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446603099,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446602953,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446602850,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446602702,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446602600,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446602453,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446602349,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446602204,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446602101,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446602000,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446601848,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446601702,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446601601,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446601452,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446601301,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446601154,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446601049,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446600948,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446600802,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446600550,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446598595,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446598461,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446598171,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446598017,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446597219,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446595278,\n      \"type\": \"text\",\n      \"description\": \"add text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445633355,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445632515,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445631735,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445630757,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445627846,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445625085,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445618645,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445617784,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445608998,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445608720,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445608540,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445608376,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445608204,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445608038,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445607852,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445607678,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445607506,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445607319,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445607154,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445604410,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445604244,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445604066,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445603900,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445603743,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445603563,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445603406,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445603226,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445603052,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445602880,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445602641,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445576567,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animation speed\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445570290,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animation speed\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445567192,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445566766,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445565520,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445398115,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445390895,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445385694,\n      \"type\": \"edit\",\n      \"description\": \"toggle fov animation\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445383241,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445382911,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445381695,\n      \"type\": \"edit\",\n      \"description\": \"edit node name\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445375383,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445374665,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445373273,\n      \"type\": \"node\",\n      \"description\": \"paste node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445372205,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438157980,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438157430,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animation speed\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438152691,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438151948,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438151286,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438146174,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438145649,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438144555,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438143655,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438142504,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animation speed\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438130077,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438129561,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438128772,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438128398,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438122820,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438122062,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438119836,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438119588,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438095045,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438093965,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438062827,\n      \"type\": \"edit\",\n      \"description\": \"toggle fov animation\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438047679,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438044161,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438041852,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438039668,\n      \"type\": \"edit\",\n      \"description\": \"resize node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438039562,\n      \"type\": \"edit\",\n      \"description\": \"resize node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438039421,\n      \"type\": \"edit\",\n      \"description\": \"resize node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438039260,\n      \"type\": \"edit\",\n      \"description\": \"resize node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438039150,\n      \"type\": \"edit\",\n      \"description\": \"resize node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438039039,\n      \"type\": \"edit\",\n      \"description\": \"resize node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438028508,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438021410,\n      \"type\": \"edit\",\n      \"description\": \"toggle fov\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438019234,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438017562,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438014356,\n      \"type\": \"node\",\n      \"description\": \"add node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437981696,\n      \"type\": \"edit\",\n      \"description\": \"apply routing to all\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437966551,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437964879,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437963627,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437961813,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437961193,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437957989,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437956467,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437953437,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437952239,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437950807,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437944990,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437943699,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437935414,\n      \"type\": \"zone\",\n      \"description\": \"draw zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437919019,\n      \"type\": \"zone\",\n      \"description\": \"delete zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437917758,\n      \"type\": \"zone\",\n      \"description\": \"draw zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437913740,\n      \"type\": \"zone\",\n      \"description\": \"draw zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437882832,\n      \"type\": \"import\",\n      \"description\": \"Imported JSON: theonefile-networkening-corporate-demo.json (105 nodes, 65 connections)\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766263279163,\n      \"type\": \"tab\",\n      \"description\": \"Switched to tab: Corporate Site B\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766263270414,\n      \"type\": \"export\",\n      \"description\": \"Exported JSON: the-one-file.json\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766263260682,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file.html\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766263259518,\n      \"type\": \"tab\",\n      \"description\": \"Switched to tab: Homelab 2\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766263249401,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file-corporate.html\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766263246362,\n      \"type\": \"import\",\n      \"description\": \"Imported JSON: theonefile-networkening-corporate-demo.json (105 nodes, 65 connections)\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766190721141,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file-corporate.html\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766190717499,\n      \"type\": \"tab\",\n      \"description\": \"Switched to tab: Corporate Site B\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766190710946,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file.html\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766190705273,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file.html\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766190703463,\n      \"type\": \"tab\",\n      \"description\": \"Switched to tab: Homelab 2\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766190695709,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file-corporate.html\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766190688417,\n      \"type\": \"import\",\n      \"description\": \"Imported JSON: theonefile-networkening-corporate-demo.json (105 nodes, 65 connections)\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1765402888416,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file-corporate.html\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1765402884873,\n      \"type\": \"tab\",\n      \"description\": \"Switched to tab: Corporate Site B\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1765402878108,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file.html\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1765402866440,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file.html\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1765402865008,\n      \"type\": \"tab\",\n      \"description\": \"Switched to tab: Homelab 2\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1765402860428,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file-corporate.html\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1765402858103,\n      \"type\": \"import\",\n      \"description\": \"Imported JSON: theonefile-networkening-corporate-demo.json (105 nodes, 65 connections)\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    }\n  ],\n  \"savedStyleSets\": []\n}"
  },
  {
    "path": "demos/json-exports/theonefile-networkening-corporate-demo.json",
    "content": "{\n  \"nodeData\": {\n    \"core-router-1\": {\n      \"shape\": \"router\",\n      \"name\": \"Core Router 1\",\n      \"ip\": \"10.0.0.1\",\n      \"role\": \"Core Routing\",\n      \"tags\": [\n        \"core\",\n        \"tier-1\",\n        \"redundant\"\n      ],\n      \"notes\": [\n        \"Primary core router\",\n        \"BGP peering enabled\"\n      ],\n      \"mac\": \"00:1A:2B:3C:4D:01\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"2\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"core-router-2\": {\n      \"shape\": \"router\",\n      \"name\": \"Core Router 2\",\n      \"ip\": \"10.0.0.2\",\n      \"role\": \"Core Routing\",\n      \"tags\": [\n        \"core\",\n        \"tier-1\",\n        \"redundant\"\n      ],\n      \"notes\": [\n        \"Secondary core router\",\n        \"HSRP standby\"\n      ],\n      \"mac\": \"00:1A:2B:3C:4D:02\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"2\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null,\n      \"ping\": {\n        \"enabled\": true,\n        \"protocol\": \"custom\",\n        \"customUrl\": \"https://google.com\",\n        \"timeout\": 3000,\n        \"status\": \"online\",\n        \"lastCheck\": \"2025-12-09T00:15:04.343Z\"\n      }\n    },\n    \"fw-external-1\": {\n      \"shape\": \"firewall\",\n      \"name\": \"External FW 1\",\n      \"ip\": \"10.0.1.1\",\n      \"role\": \"Perimeter Security\",\n      \"tags\": [\n        \"security\",\n        \"perimeter\",\n        \"ha-pair\"\n      ],\n      \"notes\": [\n        \"Palo Alto PA-5250\",\n        \"Active node\"\n      ],\n      \"mac\": \"00:1A:2B:3C:4D:10\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"2\",\n      \"layer\": \"security\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"fw-external-2\": {\n      \"shape\": \"firewall\",\n      \"name\": \"External FW 2\",\n      \"ip\": \"10.0.1.2\",\n      \"role\": \"Perimeter Security\",\n      \"tags\": [\n        \"security\",\n        \"perimeter\",\n        \"ha-pair\"\n      ],\n      \"notes\": [\n        \"Palo Alto PA-5250\",\n        \"Passive node\"\n      ],\n      \"mac\": \"00:1A:2B:3C:4D:11\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"2\",\n      \"layer\": \"security\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"fw-internal\": {\n      \"shape\": \"firewall\",\n      \"name\": \"Internal FW\",\n      \"ip\": \"10.0.2.1\",\n      \"role\": \"Internal Segmentation\",\n      \"tags\": [\n        \"security\",\n        \"internal\"\n      ],\n      \"notes\": [\n        \"East-West traffic inspection\"\n      ],\n      \"mac\": \"00:1A:2B:3C:4D:12\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"2\",\n      \"layer\": \"security\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"core-switch-1\": {\n      \"shape\": \"switch\",\n      \"name\": \"Core Switch 1\",\n      \"ip\": \"10.0.10.1\",\n      \"role\": \"Core Switching\",\n      \"tags\": [\n        \"core\",\n        \"layer3\",\n        \"redundant\"\n      ],\n      \"notes\": [\n        \"Cisco Nexus 9000\",\n        \"VPC Domain 1\"\n      ],\n      \"mac\": \"00:1A:2B:3C:4D:20\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"2\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"core-switch-2\": {\n      \"shape\": \"switch\",\n      \"name\": \"Core Switch 2\",\n      \"ip\": \"10.0.10.2\",\n      \"role\": \"Core Switching\",\n      \"tags\": [\n        \"core\",\n        \"layer3\",\n        \"redundant\"\n      ],\n      \"notes\": [\n        \"Cisco Nexus 9000\",\n        \"VPC Domain 1\"\n      ],\n      \"mac\": \"00:1A:2B:3C:4D:21\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"2\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"dc-rack-a1\": {\n      \"shape\": \"server\",\n      \"name\": \"DC Rack A1\",\n      \"ip\": \"10.10.0.0/24\",\n      \"role\": \"Data Center Rack\",\n      \"tags\": [\n        \"datacenter\",\n        \"row-a\",\n        \"production\"\n      ],\n      \"notes\": [\n        \"Row A, Position 1\",\n        \"Primary compute\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": true,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"dc-rack-a2\": {\n      \"shape\": \"server\",\n      \"name\": \"DC Rack A2\",\n      \"ip\": \"10.10.1.0/24\",\n      \"role\": \"Data Center Rack\",\n      \"tags\": [\n        \"datacenter\",\n        \"row-a\",\n        \"production\"\n      ],\n      \"notes\": [\n        \"Row A, Position 2\",\n        \"Primary compute\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": true,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"dc-rack-b1\": {\n      \"shape\": \"server\",\n      \"name\": \"DC Rack B1\",\n      \"ip\": \"10.10.2.0/24\",\n      \"role\": \"Data Center Rack\",\n      \"tags\": [\n        \"datacenter\",\n        \"row-b\",\n        \"storage\"\n      ],\n      \"notes\": [\n        \"Row B, Position 1\",\n        \"Storage systems\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": true,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"dc-rack-b2\": {\n      \"shape\": \"server\",\n      \"name\": \"DC Rack B2\",\n      \"ip\": \"10.10.3.0/24\",\n      \"role\": \"Data Center Rack\",\n      \"tags\": [\n        \"datacenter\",\n        \"row-b\",\n        \"storage\"\n      ],\n      \"notes\": [\n        \"Row B, Position 2\",\n        \"Storage systems\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": true,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"dmz-rack\": {\n      \"shape\": \"server\",\n      \"name\": \"DMZ Rack\",\n      \"ip\": \"172.16.0.0/24\",\n      \"role\": \"DMZ Infrastructure\",\n      \"tags\": [\n        \"dmz\",\n        \"security\",\n        \"public-facing\",\n        {\n          \"type\": \"icon\",\n          \"library\": \"selfhst\",\n          \"name\": \"booklogr\"\n        },\n        {\n          \"type\": \"icon\",\n          \"library\": \"simple\",\n          \"name\": \"gmail\"\n        }\n      ],\n      \"notes\": [\n        \"Isolated DMZ zone\",\n        \"Public-facing services\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"security\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"24\",\n      \"isRack\": true,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"mgmt-rack\": {\n      \"shape\": \"server\",\n      \"name\": \"Management Rack\",\n      \"ip\": \"192.168.100.0/24\",\n      \"role\": \"Management Infrastructure\",\n      \"tags\": [\n        \"management\",\n        \"oob\",\n        \"noc\"\n      ],\n      \"notes\": [\n        \"Out-of-band management\",\n        \"NOC equipment\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"logical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"24\",\n      \"isRack\": true,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"esxi-host-01\": {\n      \"shape\": \"server\",\n      \"name\": \"ESXi Host 01\",\n      \"ip\": \"10.10.0.11\",\n      \"role\": \"Hypervisor\",\n      \"tags\": [\n        \"vmware\",\n        \"compute\",\n        \"cluster-a\"\n      ],\n      \"notes\": [\n        \"Dell PowerEdge R750\",\n        \"512GB RAM\",\n        \"vSphere 8.0\"\n      ],\n      \"mac\": \"00:50:56:AA:01:01\",\n      \"rackUnit\": 38,\n      \"uHeight\": \"2\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a1\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"esxi-host-02\": {\n      \"shape\": \"server\",\n      \"name\": \"ESXi Host 02\",\n      \"ip\": \"10.10.0.12\",\n      \"role\": \"Hypervisor\",\n      \"tags\": [\n        \"vmware\",\n        \"compute\",\n        \"cluster-a\"\n      ],\n      \"notes\": [\n        \"Dell PowerEdge R750\",\n        \"512GB RAM\",\n        \"vSphere 8.0\"\n      ],\n      \"mac\": \"00:50:56:AA:01:02\",\n      \"rackUnit\": 35,\n      \"uHeight\": \"2\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a1\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"esxi-host-03\": {\n      \"shape\": \"server\",\n      \"name\": \"ESXi Host 03\",\n      \"ip\": \"10.10.0.13\",\n      \"role\": \"Hypervisor\",\n      \"tags\": [\n        \"vmware\",\n        \"compute\",\n        \"cluster-a\"\n      ],\n      \"notes\": [\n        \"Dell PowerEdge R750\",\n        \"512GB RAM\",\n        \"vSphere 8.0\"\n      ],\n      \"mac\": \"00:50:56:AA:01:03\",\n      \"rackUnit\": 32,\n      \"uHeight\": \"2\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a1\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"esxi-host-04\": {\n      \"shape\": \"server\",\n      \"name\": \"ESXi Host 04\",\n      \"ip\": \"10.10.0.14\",\n      \"role\": \"Hypervisor\",\n      \"tags\": [\n        \"vmware\",\n        \"compute\",\n        \"cluster-a\"\n      ],\n      \"notes\": [\n        \"Dell PowerEdge R750\",\n        \"512GB RAM\",\n        \"vSphere 8.0\"\n      ],\n      \"mac\": \"00:50:56:AA:01:04\",\n      \"rackUnit\": 29,\n      \"uHeight\": \"2\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a1\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"tor-switch-a1\": {\n      \"shape\": \"switch\",\n      \"name\": \"ToR Switch A1\",\n      \"ip\": \"10.10.0.1\",\n      \"role\": \"Top of Rack\",\n      \"tags\": [\n        \"tor\",\n        \"access\",\n        \"rack-a1\"\n      ],\n      \"notes\": [\n        \"Cisco Nexus 93180YC-FX\",\n        \"48x25G ports\"\n      ],\n      \"mac\": \"00:1A:2B:3C:5D:01\",\n      \"rackUnit\": 42,\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a1\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"esxi-host-05\": {\n      \"shape\": \"server\",\n      \"name\": \"ESXi Host 05\",\n      \"ip\": \"10.10.1.11\",\n      \"role\": \"Hypervisor\",\n      \"tags\": [\n        \"vmware\",\n        \"compute\",\n        \"cluster-b\"\n      ],\n      \"notes\": [\n        \"Dell PowerEdge R750\",\n        \"768GB RAM\",\n        \"vSphere 8.0\"\n      ],\n      \"mac\": \"00:50:56:AA:02:01\",\n      \"rackUnit\": 38,\n      \"uHeight\": \"2\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a2\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"esxi-host-06\": {\n      \"shape\": \"server\",\n      \"name\": \"ESXi Host 06\",\n      \"ip\": \"10.10.1.12\",\n      \"role\": \"Hypervisor\",\n      \"tags\": [\n        \"vmware\",\n        \"compute\",\n        \"cluster-b\"\n      ],\n      \"notes\": [\n        \"Dell PowerEdge R750\",\n        \"768GB RAM\",\n        \"vSphere 8.0\"\n      ],\n      \"mac\": \"00:50:56:AA:02:02\",\n      \"rackUnit\": 35,\n      \"uHeight\": \"2\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a2\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"esxi-host-07\": {\n      \"shape\": \"server\",\n      \"name\": \"ESXi Host 07\",\n      \"ip\": \"10.10.1.13\",\n      \"role\": \"Hypervisor\",\n      \"tags\": [\n        \"vmware\",\n        \"compute\",\n        \"cluster-b\"\n      ],\n      \"notes\": [\n        \"Dell PowerEdge R750\",\n        \"768GB RAM\",\n        \"vSphere 8.0\"\n      ],\n      \"mac\": \"00:50:56:AA:02:03\",\n      \"rackUnit\": 32,\n      \"uHeight\": \"2\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a2\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"esxi-host-08\": {\n      \"shape\": \"server\",\n      \"name\": \"ESXi Host 08\",\n      \"ip\": \"10.10.1.14\",\n      \"role\": \"Hypervisor\",\n      \"tags\": [\n        \"vmware\",\n        \"compute\",\n        \"cluster-b\"\n      ],\n      \"notes\": [\n        \"Dell PowerEdge R750\",\n        \"768GB RAM\",\n        \"vSphere 8.0\"\n      ],\n      \"mac\": \"00:50:56:AA:02:04\",\n      \"rackUnit\": 29,\n      \"uHeight\": \"2\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a2\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"tor-switch-a2\": {\n      \"shape\": \"switch\",\n      \"name\": \"ToR Switch A2\",\n      \"ip\": \"10.10.1.1\",\n      \"role\": \"Top of Rack\",\n      \"tags\": [\n        \"tor\",\n        \"access\",\n        \"rack-a2\"\n      ],\n      \"notes\": [\n        \"Cisco Nexus 93180YC-FX\",\n        \"48x25G ports\"\n      ],\n      \"mac\": \"00:1A:2B:3C:5D:02\",\n      \"rackUnit\": 42,\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a2\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"san-primary\": {\n      \"shape\": \"database\",\n      \"name\": \"SAN Primary\",\n      \"ip\": \"10.10.2.10\",\n      \"role\": \"Primary Storage\",\n      \"tags\": [\n        \"storage\",\n        \"san\",\n        \"netapp\"\n      ],\n      \"notes\": [\n        \"NetApp AFF A400\",\n        \"500TB Raw\",\n        \"FC 32Gb\"\n      ],\n      \"mac\": \"00:A0:98:AA:01:01\",\n      \"rackUnit\": 36,\n      \"uHeight\": \"6\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-b1\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"san-secondary\": {\n      \"shape\": \"database\",\n      \"name\": \"SAN Secondary\",\n      \"ip\": \"10.10.2.11\",\n      \"role\": \"Secondary Storage\",\n      \"tags\": [\n        \"storage\",\n        \"san\",\n        \"netapp\"\n      ],\n      \"notes\": [\n        \"NetApp AFF A400\",\n        \"500TB Raw\",\n        \"FC 32Gb\"\n      ],\n      \"mac\": \"00:A0:98:AA:01:02\",\n      \"rackUnit\": 28,\n      \"uHeight\": \"6\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-b1\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"fc-switch-1\": {\n      \"shape\": \"switch\",\n      \"name\": \"FC Switch 1\",\n      \"ip\": \"10.10.2.1\",\n      \"role\": \"Fibre Channel\",\n      \"tags\": [\n        \"storage\",\n        \"fc\",\n        \"fabric-a\"\n      ],\n      \"notes\": [\n        \"Brocade G620\",\n        \"Fabric A\"\n      ],\n      \"mac\": \"00:1A:2B:FC:01:01\",\n      \"rackUnit\": 42,\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-b1\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"fc-switch-2\": {\n      \"shape\": \"switch\",\n      \"name\": \"FC Switch 2\",\n      \"ip\": \"10.10.2.2\",\n      \"role\": \"Fibre Channel\",\n      \"tags\": [\n        \"storage\",\n        \"fc\",\n        \"fabric-b\"\n      ],\n      \"notes\": [\n        \"Brocade G620\",\n        \"Fabric B\"\n      ],\n      \"mac\": \"00:1A:2B:FC:01:02\",\n      \"rackUnit\": 41,\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-b1\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"backup-server-1\": {\n      \"shape\": \"server\",\n      \"name\": \"Backup Server 1\",\n      \"ip\": \"10.10.3.10\",\n      \"role\": \"Backup Infrastructure\",\n      \"tags\": [\n        \"backup\",\n        \"veeam\",\n        \"protection\"\n      ],\n      \"notes\": [\n        \"Veeam Backup Server\",\n        \"Dell R740xd\",\n        \"200TB\"\n      ],\n      \"mac\": \"00:50:56:BB:01:01\",\n      \"rackUnit\": 36,\n      \"uHeight\": \"2\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-b2\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"backup-server-2\": {\n      \"shape\": \"server\",\n      \"name\": \"Backup Server 2\",\n      \"ip\": \"10.10.3.11\",\n      \"role\": \"Backup Infrastructure\",\n      \"tags\": [\n        \"backup\",\n        \"veeam\",\n        \"protection\"\n      ],\n      \"notes\": [\n        \"Veeam Backup Server\",\n        \"Dell R740xd\",\n        \"200TB\"\n      ],\n      \"mac\": \"00:50:56:BB:01:02\",\n      \"rackUnit\": 33,\n      \"uHeight\": \"2\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-b2\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"tape-library\": {\n      \"shape\": \"database\",\n      \"name\": \"Tape Library\",\n      \"ip\": \"10.10.3.20\",\n      \"role\": \"Archival Storage\",\n      \"tags\": [\n        \"backup\",\n        \"tape\",\n        \"lto9\"\n      ],\n      \"notes\": [\n        \"IBM TS4500\",\n        \"LTO-9\",\n        \"Long-term archive\"\n      ],\n      \"mac\": \"00:50:56:BB:02:01\",\n      \"rackUnit\": 20,\n      \"uHeight\": \"10\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-b2\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"tor-switch-b1\": {\n      \"shape\": \"switch\",\n      \"name\": \"ToR Switch B1\",\n      \"ip\": \"10.10.2.3\",\n      \"role\": \"Top of Rack\",\n      \"tags\": [\n        \"tor\",\n        \"access\",\n        \"rack-b1\"\n      ],\n      \"notes\": [\n        \"Cisco Nexus 93180YC-FX\"\n      ],\n      \"mac\": \"00:1A:2B:3C:5D:03\",\n      \"rackUnit\": 40,\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-b1\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"tor-switch-b2\": {\n      \"shape\": \"switch\",\n      \"name\": \"ToR Switch B2\",\n      \"ip\": \"10.10.3.1\",\n      \"role\": \"Top of Rack\",\n      \"tags\": [\n        \"tor\",\n        \"access\",\n        \"rack-b2\"\n      ],\n      \"notes\": [\n        \"Cisco Nexus 93180YC-FX\"\n      ],\n      \"mac\": \"00:1A:2B:3C:5D:04\",\n      \"rackUnit\": 42,\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-b2\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"web-server-1\": {\n      \"shape\": \"server\",\n      \"name\": \"Web Server 1\",\n      \"ip\": \"172.16.0.11\",\n      \"role\": \"Web Frontend\",\n      \"tags\": [\n        \"dmz\",\n        \"web\",\n        \"nginx\"\n      ],\n      \"notes\": [\n        \"NGINX reverse proxy\",\n        \"Public facing\"\n      ],\n      \"mac\": \"00:50:56:CC:01:01\",\n      \"rackUnit\": 20,\n      \"uHeight\": \"1\",\n      \"layer\": \"security\",\n      \"assignedRack\": \"dmz-rack\",\n      \"rackCapacity\": \"24\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"web-server-2\": {\n      \"shape\": \"server\",\n      \"name\": \"Web Server 2\",\n      \"ip\": \"172.16.0.12\",\n      \"role\": \"Web Frontend\",\n      \"tags\": [\n        \"dmz\",\n        \"web\",\n        \"nginx\"\n      ],\n      \"notes\": [\n        \"NGINX reverse proxy\",\n        \"Public facing\"\n      ],\n      \"mac\": \"00:50:56:CC:01:02\",\n      \"rackUnit\": 18,\n      \"uHeight\": \"1\",\n      \"layer\": \"security\",\n      \"assignedRack\": \"dmz-rack\",\n      \"rackCapacity\": \"24\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"waf-1\": {\n      \"shape\": \"firewall\",\n      \"name\": \"WAF Appliance\",\n      \"ip\": \"172.16.0.5\",\n      \"role\": \"Web Application Firewall\",\n      \"tags\": [\n        \"dmz\",\n        \"security\",\n        \"waf\"\n      ],\n      \"notes\": [\n        \"F5 BIG-IP ASM\",\n        \"OWASP protection\"\n      ],\n      \"mac\": \"00:50:56:CC:02:01\",\n      \"rackUnit\": 22,\n      \"uHeight\": \"2\",\n      \"layer\": \"security\",\n      \"assignedRack\": \"dmz-rack\",\n      \"rackCapacity\": \"24\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"load-balancer-dmz\": {\n      \"shape\": \"switch\",\n      \"name\": \"DMZ Load Balancer\",\n      \"ip\": \"172.16.0.3\",\n      \"role\": \"Load Balancing\",\n      \"tags\": [\n        \"dmz\",\n        \"lb\",\n        \"f5\"\n      ],\n      \"notes\": [\n        \"F5 BIG-IP LTM\",\n        \"VIP: 172.16.0.100\"\n      ],\n      \"mac\": \"00:50:56:CC:03:01\",\n      \"rackUnit\": 16,\n      \"uHeight\": \"2\",\n      \"layer\": \"security\",\n      \"assignedRack\": \"dmz-rack\",\n      \"rackCapacity\": \"24\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"mail-gateway\": {\n      \"shape\": \"server\",\n      \"name\": \"Mail Gateway\",\n      \"ip\": \"172.16.0.25\",\n      \"role\": \"Email Security\",\n      \"tags\": [\n        \"dmz\",\n        \"email\",\n        \"security\"\n      ],\n      \"notes\": [\n        \"Proofpoint Email Gateway\",\n        \"Spam/malware filtering\"\n      ],\n      \"mac\": \"00:50:56:CC:04:01\",\n      \"rackUnit\": 14,\n      \"uHeight\": \"1\",\n      \"layer\": \"security\",\n      \"assignedRack\": \"dmz-rack\",\n      \"rackCapacity\": \"24\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"dns-external-1\": {\n      \"shape\": \"circle\",\n      \"name\": \"External DNS 1\",\n      \"ip\": \"172.16.0.53\",\n      \"role\": \"External DNS\",\n      \"tags\": [\n        \"dmz\",\n        \"dns\",\n        \"public\"\n      ],\n      \"notes\": [\n        \"BIND DNS\",\n        \"Authoritative for corp.com\"\n      ],\n      \"mac\": \"00:50:56:CC:05:01\",\n      \"rackUnit\": 12,\n      \"uHeight\": \"1\",\n      \"layer\": \"security\",\n      \"assignedRack\": \"dmz-rack\",\n      \"rackCapacity\": \"24\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"dns-external-2\": {\n      \"shape\": \"circle\",\n      \"name\": \"External DNS 2\",\n      \"ip\": \"172.16.0.54\",\n      \"role\": \"External DNS\",\n      \"tags\": [\n        \"dmz\",\n        \"dns\",\n        \"public\"\n      ],\n      \"notes\": [\n        \"BIND DNS\",\n        \"Secondary for corp.com\"\n      ],\n      \"mac\": \"00:50:56:CC:05:02\",\n      \"rackUnit\": 10,\n      \"uHeight\": \"1\",\n      \"layer\": \"security\",\n      \"assignedRack\": \"dmz-rack\",\n      \"rackCapacity\": \"24\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"vcenter\": {\n      \"shape\": \"server\",\n      \"name\": \"vCenter Server\",\n      \"ip\": \"192.168.100.10\",\n      \"role\": \"Virtualization Management\",\n      \"tags\": [\n        \"management\",\n        \"vmware\",\n        \"vcsa\"\n      ],\n      \"notes\": [\n        \"vCenter Server Appliance 8.0\",\n        \"Single SSO domain\"\n      ],\n      \"mac\": \"00:50:56:DD:01:01\",\n      \"rackUnit\": 20,\n      \"uHeight\": \"2\",\n      \"layer\": \"logical\",\n      \"assignedRack\": \"mgmt-rack\",\n      \"rackCapacity\": \"24\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"nsx-manager\": {\n      \"shape\": \"server\",\n      \"name\": \"NSX Manager\",\n      \"ip\": \"192.168.100.15\",\n      \"role\": \"Network Virtualization\",\n      \"tags\": [\n        \"management\",\n        \"vmware\",\n        \"nsx\"\n      ],\n      \"notes\": [\n        \"NSX-T 4.1 Manager Cluster\"\n      ],\n      \"mac\": \"00:50:56:DD:02:01\",\n      \"rackUnit\": 17,\n      \"uHeight\": \"2\",\n      \"layer\": \"logical\",\n      \"assignedRack\": \"mgmt-rack\",\n      \"rackCapacity\": \"24\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"siem-server\": {\n      \"shape\": \"server\",\n      \"name\": \"SIEM Server\",\n      \"ip\": \"192.168.100.50\",\n      \"role\": \"Security Monitoring\",\n      \"tags\": [\n        \"management\",\n        \"security\",\n        \"splunk\"\n      ],\n      \"notes\": [\n        \"Splunk Enterprise\",\n        \"Security monitoring\"\n      ],\n      \"mac\": \"00:50:56:DD:03:01\",\n      \"rackUnit\": 14,\n      \"uHeight\": \"2\",\n      \"layer\": \"logical\",\n      \"assignedRack\": \"mgmt-rack\",\n      \"rackCapacity\": \"24\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"nms-server\": {\n      \"shape\": \"server\",\n      \"name\": \"Network Monitoring\",\n      \"ip\": \"192.168.100.60\",\n      \"role\": \"Network Management\",\n      \"tags\": [\n        \"management\",\n        \"monitoring\",\n        \"prtg\"\n      ],\n      \"notes\": [\n        \"PRTG Network Monitor\",\n        \"5000 sensors\"\n      ],\n      \"mac\": \"00:50:56:DD:04:01\",\n      \"rackUnit\": 11,\n      \"uHeight\": \"1\",\n      \"layer\": \"logical\",\n      \"assignedRack\": \"mgmt-rack\",\n      \"rackCapacity\": \"24\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"jump-server\": {\n      \"shape\": \"server\",\n      \"name\": \"Jump Server\",\n      \"ip\": \"192.168.100.100\",\n      \"role\": \"Bastion Host\",\n      \"tags\": [\n        \"management\",\n        \"security\",\n        \"bastion\"\n      ],\n      \"notes\": [\n        \"Windows Server 2022\",\n        \"MFA enabled\"\n      ],\n      \"mac\": \"00:50:56:DD:05:01\",\n      \"rackUnit\": 9,\n      \"uHeight\": \"1\",\n      \"layer\": \"logical\",\n      \"assignedRack\": \"mgmt-rack\",\n      \"rackCapacity\": \"24\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"ipam-server\": {\n      \"shape\": \"server\",\n      \"name\": \"IPAM/DDI\",\n      \"ip\": \"192.168.100.70\",\n      \"role\": \"IP Management\",\n      \"tags\": [\n        \"management\",\n        \"dns\",\n        \"dhcp\"\n      ],\n      \"notes\": [\n        \"Infoblox DDI\",\n        \"DNS/DHCP/IPAM\"\n      ],\n      \"mac\": \"00:50:56:DD:06:01\",\n      \"rackUnit\": 7,\n      \"uHeight\": \"2\",\n      \"layer\": \"logical\",\n      \"assignedRack\": \"mgmt-rack\",\n      \"rackCapacity\": \"24\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"wlc-primary\": {\n      \"shape\": \"wifi\",\n      \"name\": \"WLC Primary\",\n      \"ip\": \"10.20.0.1\",\n      \"role\": \"Wireless Controller\",\n      \"tags\": [\n        \"wireless\",\n        \"cisco\",\n        \"9800\"\n      ],\n      \"notes\": [\n        \"Cisco C9800-40\",\n        \"Primary controller\"\n      ],\n      \"mac\": \"00:1A:2B:WL:01:01\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"2\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"wlc-secondary\": {\n      \"shape\": \"wifi\",\n      \"name\": \"WLC Secondary\",\n      \"ip\": \"10.20.0.2\",\n      \"role\": \"Wireless Controller\",\n      \"tags\": [\n        \"wireless\",\n        \"cisco\",\n        \"9800\"\n      ],\n      \"notes\": [\n        \"Cisco C9800-40\",\n        \"HA Secondary\"\n      ],\n      \"mac\": \"00:1A:2B:WL:01:02\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"2\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"mobile-zone-hq\": {\n      \"shape\": \"phone\",\n      \"name\": \"HQ Mobile Zone\",\n      \"ip\": \"10.20.10.0/24\",\n      \"role\": \"Mobile Device Zone\",\n      \"tags\": [\n        \"wireless\",\n        \"byod\",\n        \"mobile\"\n      ],\n      \"notes\": [\n        \"Corporate BYOD\",\n        \"MDM enrolled devices\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"mobile-zone-guest\": {\n      \"shape\": \"phone\",\n      \"name\": \"Guest WiFi Zone\",\n      \"ip\": \"10.30.0.0/24\",\n      \"role\": \"Guest Network\",\n      \"tags\": [\n        \"wireless\",\n        \"guest\",\n        \"isolated\"\n      ],\n      \"notes\": [\n        \"Captive portal\",\n        \"Internet only\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"mobile-zone-iot\": {\n      \"shape\": \"phone\",\n      \"name\": \"IoT Device Zone\",\n      \"ip\": \"10.40.0.0/24\",\n      \"role\": \"IoT Network\",\n      \"tags\": [\n        \"wireless\",\n        \"iot\",\n        \"building\"\n      ],\n      \"notes\": [\n        \"Building automation\",\n        \"Smart devices\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"branch-router-ny\": {\n      \"shape\": \"router\",\n      \"name\": \"NYC Branch Router\",\n      \"ip\": \"10.100.0.1\",\n      \"role\": \"Branch Gateway\",\n      \"tags\": [\n        \"branch\",\n        \"nyc\",\n        \"sd-wan\"\n      ],\n      \"notes\": [\n        \"Cisco Viptela vEdge\",\n        \"SD-WAN enabled\"\n      ],\n      \"mac\": \"00:1A:2B:BR:01:01\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"branch-router-la\": {\n      \"shape\": \"router\",\n      \"name\": \"LA Branch Router\",\n      \"ip\": \"10.101.0.1\",\n      \"role\": \"Branch Gateway\",\n      \"tags\": [\n        \"branch\",\n        \"la\",\n        \"sd-wan\"\n      ],\n      \"notes\": [\n        \"Cisco Viptela vEdge\",\n        \"SD-WAN enabled\"\n      ],\n      \"mac\": \"00:1A:2B:BR:02:01\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"branch-router-chi\": {\n      \"shape\": \"router\",\n      \"name\": \"Chicago Branch Router\",\n      \"ip\": \"10.102.0.1\",\n      \"role\": \"Branch Gateway\",\n      \"tags\": [\n        \"branch\",\n        \"chicago\",\n        \"sd-wan\"\n      ],\n      \"notes\": [\n        \"Cisco Viptela vEdge\",\n        \"SD-WAN enabled\"\n      ],\n      \"mac\": \"00:1A:2B:BR:03:01\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"branch-router-lon\": {\n      \"shape\": \"router\",\n      \"name\": \"London Branch Router\",\n      \"ip\": \"10.200.0.1\",\n      \"role\": \"Branch Gateway\",\n      \"tags\": [\n        \"branch\",\n        \"london\",\n        \"sd-wan\"\n      ],\n      \"notes\": [\n        \"Cisco Viptela vEdge\",\n        \"EMEA region\"\n      ],\n      \"mac\": \"00:1A:2B:BR:04:01\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"branch-router-tokyo\": {\n      \"shape\": \"router\",\n      \"name\": \"Tokyo Branch Router\",\n      \"ip\": \"10.201.0.1\",\n      \"role\": \"Branch Gateway\",\n      \"tags\": [\n        \"branch\",\n        \"tokyo\",\n        \"sd-wan\"\n      ],\n      \"notes\": [\n        \"Cisco Viptela vEdge\",\n        \"APAC region\"\n      ],\n      \"mac\": \"00:1A:2B:BR:05:01\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"cloud-aws\": {\n      \"shape\": \"cloud\",\n      \"name\": \"AWS Cloud\",\n      \"ip\": \"vpc-0a1b2c3d\",\n      \"role\": \"Public Cloud\",\n      \"tags\": [\n        \"cloud\",\n        \"aws\",\n        \"hybrid\"\n      ],\n      \"notes\": [\n        \"AWS US-East-1\",\n        \"VPC peering to HQ\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"logical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"cloud-azure\": {\n      \"shape\": \"cloud\",\n      \"name\": \"Azure Cloud\",\n      \"ip\": \"vnet-corp-prod\",\n      \"role\": \"Public Cloud\",\n      \"tags\": [\n        \"cloud\",\n        \"azure\",\n        \"hybrid\"\n      ],\n      \"notes\": [\n        \"Azure East US 2\",\n        \"ExpressRoute\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"logical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"cloud-gcp\": {\n      \"shape\": \"cloud\",\n      \"name\": \"GCP Cloud\",\n      \"ip\": \"vpc-gcp-corp\",\n      \"role\": \"Public Cloud\",\n      \"tags\": [\n        \"cloud\",\n        \"gcp\",\n        \"dev\"\n      ],\n      \"notes\": [\n        \"GCP us-central1\",\n        \"Dev/Test workloads\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"logical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"isp-primary\": {\n      \"shape\": \"globe\",\n      \"name\": \"ISP Primary\",\n      \"ip\": \"203.0.113.1\",\n      \"role\": \"Internet Uplink\",\n      \"tags\": [\n        \"wan\",\n        \"internet\",\n        \"primary\"\n      ],\n      \"notes\": [\n        \"AT&T MPLS\",\n        \"1 Gbps dedicated\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"isp-secondary\": {\n      \"shape\": \"globe\",\n      \"name\": \"ISP Secondary\",\n      \"ip\": \"198.51.100.1\",\n      \"role\": \"Internet Uplink\",\n      \"tags\": [\n        \"wan\",\n        \"internet\",\n        \"backup\"\n      ],\n      \"notes\": [\n        \"Verizon Business\",\n        \"500 Mbps backup\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"dc-internal-1\": {\n      \"shape\": \"circle\",\n      \"name\": \"DC1 Int DNS\",\n      \"ip\": \"10.10.0.53\",\n      \"role\": \"Internal DNS/AD\",\n      \"tags\": [\n        \"dns\",\n        \"ad\",\n        \"dc1\"\n      ],\n      \"notes\": [\n        \"Windows Server 2022\",\n        \"Primary DC\"\n      ],\n      \"mac\": \"00:50:56:AD:01:01\",\n      \"rackUnit\": 26,\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a1\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"dc-internal-2\": {\n      \"shape\": \"circle\",\n      \"name\": \"DC2 Int DNS\",\n      \"ip\": \"10.10.1.53\",\n      \"role\": \"Internal DNS/AD\",\n      \"tags\": [\n        \"dns\",\n        \"ad\",\n        \"dc2\"\n      ],\n      \"notes\": [\n        \"Windows Server 2022\",\n        \"Secondary DC\"\n      ],\n      \"mac\": \"00:50:56:AD:01:02\",\n      \"rackUnit\": 26,\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a2\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"app-server-1\": {\n      \"shape\": \"server\",\n      \"name\": \"App Server 01\",\n      \"ip\": \"10.10.0.101\",\n      \"role\": \"Application\",\n      \"tags\": [\n        \"app\",\n        \"iis\",\n        \"web\"\n      ],\n      \"notes\": [\n        \"Windows Server 2022\",\n        \"IIS Application\"\n      ],\n      \"mac\": \"00:50:56:AP:01:01\",\n      \"rackUnit\": 24,\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a1\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"app-server-2\": {\n      \"shape\": \"server\",\n      \"name\": \"App Server 02\",\n      \"ip\": \"10.10.0.102\",\n      \"role\": \"Application\",\n      \"tags\": [\n        \"app\",\n        \"iis\",\n        \"web\"\n      ],\n      \"notes\": [\n        \"Windows Server 2022\",\n        \"IIS Application\"\n      ],\n      \"mac\": \"00:50:56:AP:01:02\",\n      \"rackUnit\": 22,\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a1\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"db-server-1\": {\n      \"shape\": \"database\",\n      \"name\": \"SQL Server 01\",\n      \"ip\": \"10.10.0.201\",\n      \"role\": \"Database\",\n      \"tags\": [\n        \"db\",\n        \"sql\",\n        \"primary\"\n      ],\n      \"notes\": [\n        \"SQL Server 2022 Enterprise\",\n        \"AlwaysOn Primary\"\n      ],\n      \"mac\": \"00:50:56:DB:01:01\",\n      \"rackUnit\": 20,\n      \"uHeight\": \"2\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a1\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"db-server-2\": {\n      \"shape\": \"database\",\n      \"name\": \"SQL Server 02\",\n      \"ip\": \"10.10.1.201\",\n      \"role\": \"Database\",\n      \"tags\": [\n        \"db\",\n        \"sql\",\n        \"secondary\"\n      ],\n      \"notes\": [\n        \"SQL Server 2022 Enterprise\",\n        \"AlwaysOn Secondary\"\n      ],\n      \"mac\": \"00:50:56:DB:01:02\",\n      \"rackUnit\": 24,\n      \"uHeight\": \"2\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a2\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"k8s-master-1\": {\n      \"shape\": \"hexagon\",\n      \"name\": \"K8s Master 1\",\n      \"ip\": \"10.10.1.50\",\n      \"role\": \"Container Orchestration\",\n      \"tags\": [\n        \"kubernetes\",\n        \"master\",\n        \"container\"\n      ],\n      \"notes\": [\n        \"K8s Control Plane\",\n        \"etcd member\"\n      ],\n      \"mac\": \"00:50:56:K8:01:01\",\n      \"rackUnit\": 21,\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a2\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"k8s-master-2\": {\n      \"shape\": \"hexagon\",\n      \"name\": \"K8s Master 2\",\n      \"ip\": \"10.10.1.51\",\n      \"role\": \"Container Orchestration\",\n      \"tags\": [\n        \"kubernetes\",\n        \"master\",\n        \"container\"\n      ],\n      \"notes\": [\n        \"K8s Control Plane\",\n        \"etcd member\"\n      ],\n      \"mac\": \"00:50:56:K8:01:02\",\n      \"rackUnit\": 19,\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a2\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"k8s-master-3\": {\n      \"shape\": \"hexagon\",\n      \"name\": \"K8s Master 3\",\n      \"ip\": \"10.10.1.52\",\n      \"role\": \"Container Orchestration\",\n      \"tags\": [\n        \"kubernetes\",\n        \"master\",\n        \"container\"\n      ],\n      \"notes\": [\n        \"K8s Control Plane\",\n        \"etcd member\"\n      ],\n      \"mac\": \"00:50:56:K8:01:03\",\n      \"rackUnit\": 17,\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a2\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"k8s-worker-1\": {\n      \"shape\": \"server\",\n      \"name\": \"K8s Worker 1\",\n      \"ip\": \"10.10.1.60\",\n      \"role\": \"Container Workload\",\n      \"tags\": [\n        \"kubernetes\",\n        \"worker\",\n        \"container\"\n      ],\n      \"notes\": [\n        \"K8s Worker Node\",\n        \"64GB RAM\"\n      ],\n      \"mac\": \"00:50:56:K8:02:01\",\n      \"rackUnit\": 15,\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a2\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"k8s-worker-2\": {\n      \"shape\": \"server\",\n      \"name\": \"K8s Worker 2\",\n      \"ip\": \"10.10.1.61\",\n      \"role\": \"Container Workload\",\n      \"tags\": [\n        \"kubernetes\",\n        \"worker\",\n        \"container\"\n      ],\n      \"notes\": [\n        \"K8s Worker Node\",\n        \"64GB RAM\"\n      ],\n      \"mac\": \"00:50:56:K8:02:02\",\n      \"rackUnit\": 13,\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a2\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"k8s-worker-3\": {\n      \"shape\": \"server\",\n      \"name\": \"K8s Worker 3\",\n      \"ip\": \"10.10.1.62\",\n      \"role\": \"Container Workload\",\n      \"tags\": [\n        \"kubernetes\",\n        \"worker\",\n        \"container\"\n      ],\n      \"notes\": [\n        \"K8s Worker Node\",\n        \"64GB RAM\"\n      ],\n      \"mac\": \"00:50:56:K8:02:03\",\n      \"rackUnit\": 11,\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a2\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"k8s-worker-4\": {\n      \"shape\": \"server\",\n      \"name\": \"K8s Worker 4\",\n      \"ip\": \"10.10.1.63\",\n      \"role\": \"Container Workload\",\n      \"tags\": [\n        \"kubernetes\",\n        \"worker\",\n        \"container\"\n      ],\n      \"notes\": [\n        \"K8s Worker Node\",\n        \"64GB RAM\"\n      ],\n      \"mac\": \"00:50:56:K8:02:04\",\n      \"rackUnit\": 9,\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a2\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"proxy-server-1\": {\n      \"shape\": \"server\",\n      \"name\": \"Proxy Server 1\",\n      \"ip\": \"10.5.0.10\",\n      \"role\": \"Web Proxy\",\n      \"tags\": [\n        \"proxy\",\n        \"squid\",\n        \"filtering\"\n      ],\n      \"notes\": [\n        \"Squid Proxy\",\n        \"Content filtering\"\n      ],\n      \"mac\": \"00:50:56:PX:01:01\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"security\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"proxy-server-2\": {\n      \"shape\": \"server\",\n      \"name\": \"Proxy Server 2\",\n      \"ip\": \"10.5.0.11\",\n      \"role\": \"Web Proxy\",\n      \"tags\": [\n        \"proxy\",\n        \"squid\",\n        \"filtering\"\n      ],\n      \"notes\": [\n        \"Squid Proxy\",\n        \"HA pair\"\n      ],\n      \"mac\": \"00:50:56:PX:01:02\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"security\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"vpn-concentrator\": {\n      \"shape\": \"firewall\",\n      \"name\": \"VPN Concentrator\",\n      \"ip\": \"10.0.5.1\",\n      \"role\": \"Remote Access VPN\",\n      \"tags\": [\n        \"vpn\",\n        \"remote\",\n        \"security\"\n      ],\n      \"notes\": [\n        \"Cisco ASA 5555-X\",\n        \"AnyConnect SSL VPN\"\n      ],\n      \"mac\": \"00:1A:2B:VP:01:01\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"2\",\n      \"layer\": \"security\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"nac-server\": {\n      \"shape\": \"server\",\n      \"name\": \"NAC Server\",\n      \"ip\": \"10.5.5.10\",\n      \"role\": \"Network Access Control\",\n      \"tags\": [\n        \"nac\",\n        \"ise\",\n        \"802.1x\"\n      ],\n      \"notes\": [\n        \"Cisco ISE 3.1\",\n        \"RADIUS/TACACS+\"\n      ],\n      \"mac\": \"00:50:56:NA:01:01\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"2\",\n      \"layer\": \"security\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"print-server\": {\n      \"shape\": \"server\",\n      \"name\": \"Print Server\",\n      \"ip\": \"10.10.0.150\",\n      \"role\": \"Print Services\",\n      \"tags\": [\n        \"print\",\n        \"windows\",\n        \"services\"\n      ],\n      \"notes\": [\n        \"Windows Print Server\",\n        \"50+ printers\"\n      ],\n      \"mac\": \"00:50:56:PR:01:01\",\n      \"rackUnit\": 18,\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a1\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"file-server\": {\n      \"shape\": \"database\",\n      \"name\": \"File Server\",\n      \"ip\": \"10.10.0.160\",\n      \"role\": \"File Services\",\n      \"tags\": [\n        \"file\",\n        \"smb\",\n        \"dfs\"\n      ],\n      \"notes\": [\n        \"Windows File Server\",\n        \"DFS namespace\"\n      ],\n      \"mac\": \"00:50:56:FS:01:01\",\n      \"rackUnit\": 16,\n      \"uHeight\": \"2\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a1\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"ca-server\": {\n      \"shape\": \"server\",\n      \"name\": \"Certificate Authority\",\n      \"ip\": \"192.168.100.80\",\n      \"role\": \"PKI Infrastructure\",\n      \"tags\": [\n        \"pki\",\n        \"ca\",\n        \"security\"\n      ],\n      \"notes\": [\n        \"Windows CA\",\n        \"Enterprise Root CA\"\n      ],\n      \"mac\": \"00:50:56:CA:01:01\",\n      \"rackUnit\": 5,\n      \"uHeight\": \"1\",\n      \"layer\": \"logical\",\n      \"assignedRack\": \"mgmt-rack\",\n      \"rackCapacity\": \"24\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"sccm-server\": {\n      \"shape\": \"server\",\n      \"name\": \"SCCM Server\",\n      \"ip\": \"192.168.100.90\",\n      \"role\": \"Endpoint Management\",\n      \"tags\": [\n        \"sccm\",\n        \"patching\",\n        \"software\"\n      ],\n      \"notes\": [\n        \"MECM Primary Site\",\n        \"Software deployment\"\n      ],\n      \"mac\": \"00:50:56:SC:01:01\",\n      \"rackUnit\": 3,\n      \"uHeight\": \"2\",\n      \"layer\": \"logical\",\n      \"assignedRack\": \"mgmt-rack\",\n      \"rackCapacity\": \"24\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"voip-cluster\": {\n      \"shape\": \"phone\",\n      \"name\": \"VoIP Cluster\",\n      \"ip\": \"10.50.0.0/24\",\n      \"role\": \"Voice Services\",\n      \"tags\": [\n        \"voip\",\n        \"cisco\",\n        \"ucm\"\n      ],\n      \"notes\": [\n        \"Cisco UCM Cluster\",\n        \"3000 endpoints\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"application\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"video-conf\": {\n      \"shape\": \"laptop\",\n      \"name\": \"Video Conference\",\n      \"ip\": \"10.51.0.0/24\",\n      \"role\": \"Video Services\",\n      \"tags\": [\n        \"video\",\n        \"webex\",\n        \"teams\"\n      ],\n      \"notes\": [\n        \"Webex/Teams integration\",\n        \"Meeting rooms\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"application\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"security-cameras\": {\n      \"shape\": \"camera\",\n      \"name\": \"Security Cameras\",\n      \"ip\": \"10.60.0.0/24\",\n      \"role\": \"Physical Security\",\n      \"tags\": [\n        \"cctv\",\n        \"surveillance\",\n        \"security\"\n      ],\n      \"notes\": [\n        \"150+ IP cameras\",\n        \"30-day retention\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"nvr-cluster\": {\n      \"shape\": \"server\",\n      \"name\": \"NVR Cluster\",\n      \"ip\": \"10.60.0.10\",\n      \"role\": \"Video Recording\",\n      \"tags\": [\n        \"nvr\",\n        \"surveillance\",\n        \"storage\"\n      ],\n      \"notes\": [\n        \"Milestone XProtect\",\n        \"500TB storage\"\n      ],\n      \"mac\": \"00:50:56:NV:01:01\",\n      \"rackUnit\": 15,\n      \"uHeight\": \"4\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-b2\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"dev-server-1\": {\n      \"shape\": \"server\",\n      \"name\": \"Dev Server 1\",\n      \"ip\": \"10.80.0.10\",\n      \"role\": \"Development\",\n      \"tags\": [\n        \"dev\",\n        \"gitlab\",\n        \"ci-cd\",\n        {\n          \"type\": \"icon\",\n          \"library\": \"selfhst\",\n          \"name\": \"dokku\"\n        }\n      ],\n      \"notes\": [\n        \"GitLab Server\",\n        \"CI/CD pipelines\"\n      ],\n      \"mac\": \"00:50:56:DV:01:01\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"2\",\n      \"layer\": \"application\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"dev-server-2\": {\n      \"shape\": \"server\",\n      \"name\": \"Dev Server 2\",\n      \"ip\": \"10.80.0.11\",\n      \"role\": \"Development\",\n      \"tags\": [\n        \"dev\",\n        \"jenkins\",\n        \"ci-cd\"\n      ],\n      \"notes\": [\n        \"Jenkins Server\",\n        \"Build automation\"\n      ],\n      \"mac\": \"00:50:56:DV:01:02\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"2\",\n      \"layer\": \"application\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"test-environment\": {\n      \"shape\": \"hexagon\",\n      \"name\": \"Test Environment\",\n      \"ip\": \"10.81.0.0/24\",\n      \"role\": \"QA/Testing\",\n      \"tags\": [\n        \"test\",\n        \"qa\",\n        \"staging\"\n      ],\n      \"notes\": [\n        \"Staging environment\",\n        \"Pre-prod validation\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"application\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"erp-system\": {\n      \"shape\": \"database\",\n      \"name\": \"ERP System\",\n      \"ip\": \"10.90.0.10\",\n      \"role\": \"Business Application\",\n      \"tags\": [\n        \"erp\",\n        \"sap\",\n        \"business\"\n      ],\n      \"notes\": [\n        \"SAP S/4HANA\",\n        \"Financial/HR systems\"\n      ],\n      \"mac\": \"00:50:56:ER:01:01\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"4\",\n      \"layer\": \"application\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"crm-system\": {\n      \"shape\": \"database\",\n      \"name\": \"CRM System\",\n      \"ip\": \"10.91.0.10\",\n      \"role\": \"Business Application\",\n      \"tags\": [\n        \"crm\",\n        \"salesforce\",\n        \"business\"\n      ],\n      \"notes\": [\n        \"Salesforce integration\",\n        \"Sales/Marketing\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"application\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"endpoint-1000\": {\n      \"shape\": \"laptop\",\n      \"name\": \"Corporate Endpoints\",\n      \"ip\": \"10.70.0.0/22\",\n      \"role\": \"User Workstations\",\n      \"tags\": [\n        \"endpoints\",\n        \"workstations\",\n        \"users\"\n      ],\n      \"notes\": [\n        \"~1000 corporate laptops\",\n        \"Windows 11\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"dist-switch-floor1\": {\n      \"shape\": \"switch\",\n      \"name\": \"Floor 1 Switch\",\n      \"ip\": \"10.1.1.1\",\n      \"role\": \"Distribution\",\n      \"tags\": [\n        \"distribution\",\n        \"floor-1\",\n        \"access\"\n      ],\n      \"notes\": [\n        \"Cisco C9300-48P\",\n        \"PoE+ enabled\"\n      ],\n      \"mac\": \"00:1A:2B:FL:01:01\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"dist-switch-floor2\": {\n      \"shape\": \"switch\",\n      \"name\": \"Floor 2 Switch\",\n      \"ip\": \"10.1.2.1\",\n      \"role\": \"Distribution\",\n      \"tags\": [\n        \"distribution\",\n        \"floor-2\",\n        \"access\"\n      ],\n      \"notes\": [\n        \"Cisco C9300-48P\",\n        \"PoE+ enabled\"\n      ],\n      \"mac\": \"00:1A:2B:FL:02:01\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"dist-switch-floor3\": {\n      \"shape\": \"switch\",\n      \"name\": \"Floor 3 Switch\",\n      \"ip\": \"10.1.3.1\",\n      \"role\": \"Distribution\",\n      \"tags\": [\n        \"distribution\",\n        \"floor-3\",\n        \"access\"\n      ],\n      \"notes\": [\n        \"Cisco C9300-48P\",\n        \"PoE+ enabled\"\n      ],\n      \"mac\": \"00:1A:2B:FL:03:01\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"dist-switch-floor4\": {\n      \"shape\": \"switch\",\n      \"name\": \"Floor 4 Switch\",\n      \"ip\": \"10.1.4.1\",\n      \"role\": \"Distribution\",\n      \"tags\": [\n        \"distribution\",\n        \"floor-4\",\n        \"access\"\n      ],\n      \"notes\": [\n        \"Cisco C9300-48P\",\n        \"PoE+ enabled\"\n      ],\n      \"mac\": \"00:1A:2B:FL:04:01\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"ap-floor1-zone1\": {\n      \"shape\": \"wifi\",\n      \"name\": \"AP Floor 1 Zone 1\",\n      \"ip\": \"10.20.1.10\",\n      \"role\": \"Wireless Access\",\n      \"tags\": [\n        \"wifi\",\n        \"ap\",\n        \"floor-1\"\n      ],\n      \"notes\": [\n        \"Cisco 9120AX\",\n        \"Wi-Fi 6\"\n      ],\n      \"mac\": \"00:1A:2B:AP:01:01\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"ap-floor2-zone1\": {\n      \"shape\": \"wifi\",\n      \"name\": \"AP Floor 2 Zone 1\",\n      \"ip\": \"10.20.2.10\",\n      \"role\": \"Wireless Access\",\n      \"tags\": [\n        \"wifi\",\n        \"ap\",\n        \"floor-2\"\n      ],\n      \"notes\": [\n        \"Cisco 9120AX\",\n        \"Wi-Fi 6\"\n      ],\n      \"mac\": \"00:1A:2B:AP:02:01\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"ap-floor3-zone1\": {\n      \"shape\": \"wifi\",\n      \"name\": \"AP Floor 3 Zone 1\",\n      \"ip\": \"10.20.3.10\",\n      \"role\": \"Wireless Access\",\n      \"tags\": [\n        \"wifi\",\n        \"ap\",\n        \"floor-3\"\n      ],\n      \"notes\": [\n        \"Cisco 9120AX\",\n        \"Wi-Fi 6\"\n      ],\n      \"mac\": \"00:1A:2B:AP:03:01\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"ap-floor4-zone1\": {\n      \"shape\": \"wifi\",\n      \"name\": \"AP Floor 4 Zone 1\",\n      \"ip\": \"10.20.4.10\",\n      \"role\": \"Wireless Access\",\n      \"tags\": [\n        \"wifi\",\n        \"ap\",\n        \"floor-4\"\n      ],\n      \"notes\": [\n        \"Cisco 9120AX\",\n        \"Wi-Fi 6\"\n      ],\n      \"mac\": \"00:1A:2B:AP:04:01\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"ups-dc-1\": {\n      \"shape\": \"rectangle\",\n      \"name\": \"UPS DC-1\",\n      \"ip\": \"192.168.200.10\",\n      \"role\": \"Power Management\",\n      \"tags\": [\n        \"power\",\n        \"ups\",\n        \"datacenter\"\n      ],\n      \"notes\": [\n        \"APC Symmetra\",\n        \"80kVA\",\n        \"30 min runtime\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"ups-dc-2\": {\n      \"shape\": \"rectangle\",\n      \"name\": \"UPS DC-2\",\n      \"ip\": \"192.168.200.11\",\n      \"role\": \"Power Management\",\n      \"tags\": [\n        \"power\",\n        \"ups\",\n        \"datacenter\"\n      ],\n      \"notes\": [\n        \"APC Symmetra\",\n        \"80kVA\",\n        \"Redundant\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"pdu-rack-a1\": {\n      \"shape\": \"rectangle\",\n      \"name\": \"PDU Rack A1\",\n      \"ip\": \"192.168.200.21\",\n      \"role\": \"Power Distribution\",\n      \"tags\": [\n        \"power\",\n        \"pdu\",\n        \"rack-a1\"\n      ],\n      \"notes\": [\n        \"APC Switched PDU\",\n        \"Per-outlet metering\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": 1,\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a1\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"pdu-rack-a2\": {\n      \"shape\": \"rectangle\",\n      \"name\": \"PDU Rack A2\",\n      \"ip\": \"192.168.200.22\",\n      \"role\": \"Power Distribution\",\n      \"tags\": [\n        \"power\",\n        \"pdu\",\n        \"rack-a2\"\n      ],\n      \"notes\": [\n        \"APC Switched PDU\",\n        \"Per-outlet metering\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": 1,\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a2\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"cooling-1\": {\n      \"shape\": \"rectangle\",\n      \"name\": \"CRAC Unit 1\",\n      \"ip\": \"192.168.200.30\",\n      \"role\": \"Cooling\",\n      \"tags\": [\n        \"cooling\",\n        \"hvac\",\n        \"datacenter\"\n      ],\n      \"notes\": [\n        \"Liebert CRV\",\n        \"Row-based cooling\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"cooling-2\": {\n      \"shape\": \"rectangle\",\n      \"name\": \"CRAC Unit 2\",\n      \"ip\": \"192.168.200.31\",\n      \"role\": \"Cooling\",\n      \"tags\": [\n        \"cooling\",\n        \"hvac\",\n        \"datacenter\"\n      ],\n      \"notes\": [\n        \"Liebert CRV\",\n        \"N+1 redundancy\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"camera-a\": {\n      \"shape\": \"camera\",\n      \"name\": \"camera A\",\n      \"ip\": \"0.0.0.0\",\n      \"role\": \"\",\n      \"tags\": [],\n      \"notes\": [],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"ping\": {\n        \"enabled\": false,\n        \"protocol\": \"http\",\n        \"customUrl\": \"\",\n        \"timeout\": 3000,\n        \"status\": \"unknown\",\n        \"lastCheck\": null\n      },\n      \"locked\": false,\n      \"groupId\": null,\n      \"fovEnabled\": true,\n      \"fovRotation\": 104,\n      \"fovDistance\": 500,\n      \"fovSweep\": 60,\n      \"fovSpeed\": 10,\n      \"fovAnimate\": true\n    },\n    \"camera-a-copy\": {\n      \"shape\": \"camera\",\n      \"name\": \"camera B\",\n      \"ip\": \"0.0.0.0\",\n      \"role\": \"\",\n      \"tags\": [],\n      \"notes\": [],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"ping\": {\n        \"enabled\": false,\n        \"protocol\": \"http\",\n        \"customUrl\": \"\",\n        \"timeout\": 3000,\n        \"status\": \"unknown\",\n        \"lastCheck\": null\n      },\n      \"locked\": false,\n      \"groupId\": null,\n      \"fovEnabled\": true,\n      \"fovRotation\": 162,\n      \"fovDistance\": 500,\n      \"fovSweep\": 60,\n      \"fovSpeed\": 10,\n      \"fovAnimate\": false\n    }\n  },\n  \"edgeData\": {\n    \"list\": [\n      {\n        \"id\": \"isp1-router1\",\n        \"from\": \"isp-primary\",\n        \"to\": \"core-router-1\",\n        \"width\": 6,\n        \"color\": \"#10b981\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [\n          \"Primary WAN link\"\n        ],\n        \"fromPort\": \"Gi0/0\",\n        \"toPort\": \"Gi1/0/1\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"isp2-router2\",\n        \"from\": \"isp-secondary\",\n        \"to\": \"core-router-2\",\n        \"width\": 6,\n        \"color\": \"#10b981\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [\n          \"Backup WAN link\"\n        ],\n        \"fromPort\": \"Gi0/0\",\n        \"toPort\": \"Gi1/0/1\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"router1-router2\",\n        \"from\": \"core-router-1\",\n        \"to\": \"core-router-2\",\n        \"width\": 4,\n        \"color\": \"#f59e0b\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [\n          \"HSRP Peering\"\n        ],\n        \"fromPort\": \"Gi1/0/24\",\n        \"toPort\": \"Gi1/0/24\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"router1-fw1\",\n        \"from\": \"core-router-1\",\n        \"to\": \"fw-external-1\",\n        \"width\": 4,\n        \"color\": \"#ef4444\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"router2-fw2\",\n        \"from\": \"core-router-2\",\n        \"to\": \"fw-external-2\",\n        \"width\": 4,\n        \"color\": \"#ef4444\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"fw1-fw2\",\n        \"from\": \"fw-external-1\",\n        \"to\": \"fw-external-2\",\n        \"width\": 3,\n        \"color\": \"#f59e0b\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [\n          \"HA heartbeat\"\n        ],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"dashed\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"fw1-coresw1\",\n        \"from\": \"fw-external-1\",\n        \"to\": \"core-switch-1\",\n        \"width\": 4,\n        \"color\": \"#475569\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"fw2-coresw2\",\n        \"from\": \"fw-external-2\",\n        \"to\": \"core-switch-2\",\n        \"width\": 4,\n        \"color\": \"#475569\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"coresw1-coresw2\",\n        \"from\": \"core-switch-1\",\n        \"to\": \"core-switch-2\",\n        \"width\": 5,\n        \"color\": \"#3b82f6\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [\n          \"VPC peer-link\"\n        ],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"coresw1-fwint\",\n        \"from\": \"core-switch-1\",\n        \"to\": \"fw-internal\",\n        \"width\": 3,\n        \"color\": \"#ef4444\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"coresw2-fwint\",\n        \"from\": \"core-switch-2\",\n        \"to\": \"fw-internal\",\n        \"width\": 3,\n        \"color\": \"#ef4444\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"coresw1-racka1\",\n        \"from\": \"core-switch-1\",\n        \"to\": \"dc-rack-a1\",\n        \"width\": 4,\n        \"color\": \"#475569\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"coresw2-racka1\",\n        \"from\": \"core-switch-2\",\n        \"to\": \"dc-rack-a1\",\n        \"width\": 4,\n        \"color\": \"#475569\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"coresw1-racka2\",\n        \"from\": \"core-switch-1\",\n        \"to\": \"dc-rack-a2\",\n        \"width\": 4,\n        \"color\": \"#475569\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"coresw2-racka2\",\n        \"from\": \"core-switch-2\",\n        \"to\": \"dc-rack-a2\",\n        \"width\": 4,\n        \"color\": \"#475569\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"coresw1-rackb1\",\n        \"from\": \"core-switch-1\",\n        \"to\": \"dc-rack-b1\",\n        \"width\": 4,\n        \"color\": \"#475569\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"coresw2-rackb1\",\n        \"from\": \"core-switch-2\",\n        \"to\": \"dc-rack-b1\",\n        \"width\": 4,\n        \"color\": \"#475569\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"coresw1-rackb2\",\n        \"from\": \"core-switch-1\",\n        \"to\": \"dc-rack-b2\",\n        \"width\": 4,\n        \"color\": \"#475569\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"coresw2-rackb2\",\n        \"from\": \"core-switch-2\",\n        \"to\": \"dc-rack-b2\",\n        \"width\": 4,\n        \"color\": \"#475569\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"fw1-dmz\",\n        \"from\": \"fw-external-1\",\n        \"to\": \"dmz-rack\",\n        \"width\": 3,\n        \"color\": \"#f59e0b\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [\n          \"DMZ segment\"\n        ],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"fw2-dmz\",\n        \"from\": \"fw-external-2\",\n        \"to\": \"dmz-rack\",\n        \"width\": 3,\n        \"color\": \"#f59e0b\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [\n          \"DMZ segment\"\n        ],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"coresw1-mgmt\",\n        \"from\": \"core-switch-1\",\n        \"to\": \"mgmt-rack\",\n        \"width\": 3,\n        \"color\": \"#8b5cf6\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [\n          \"OOB management\"\n        ],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"coresw1-wlc1\",\n        \"from\": \"core-switch-1\",\n        \"to\": \"wlc-primary\",\n        \"width\": 3,\n        \"color\": \"#06b6d4\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"coresw2-wlc2\",\n        \"from\": \"core-switch-2\",\n        \"to\": \"wlc-secondary\",\n        \"width\": 3,\n        \"color\": \"#06b6d4\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\",\n        \"animate\": true\n      },\n      {\n        \"id\": \"wlc1-wlc2\",\n        \"from\": \"wlc-primary\",\n        \"to\": \"wlc-secondary\",\n        \"width\": 2,\n        \"color\": \"#f59e0b\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [\n          \"HA pair\"\n        ],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"dashed\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"wlc1-mobile-hq\",\n        \"from\": \"wlc-primary\",\n        \"to\": \"mobile-zone-hq\",\n        \"width\": 2,\n        \"color\": \"#06b6d4\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"wlc1-mobile-guest\",\n        \"from\": \"wlc-primary\",\n        \"to\": \"mobile-zone-guest\",\n        \"width\": 2,\n        \"color\": \"#06b6d4\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"wlc1-mobile-iot\",\n        \"from\": \"wlc-primary\",\n        \"to\": \"mobile-zone-iot\",\n        \"width\": 2,\n        \"color\": \"#06b6d4\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"router1-branch-ny\",\n        \"from\": \"core-router-1\",\n        \"to\": \"branch-router-ny\",\n        \"width\": 3,\n        \"color\": \"#a855f7\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [\n          \"SD-WAN tunnel\"\n        ],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"dashed\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"router1-branch-la\",\n        \"from\": \"core-router-1\",\n        \"to\": \"branch-router-la\",\n        \"width\": 3,\n        \"color\": \"#a855f7\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [\n          \"SD-WAN tunnel\"\n        ],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"dashed\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"router1-branch-chi\",\n        \"from\": \"core-router-1\",\n        \"to\": \"branch-router-chi\",\n        \"width\": 3,\n        \"color\": \"#a855f7\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [\n          \"SD-WAN tunnel\"\n        ],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"dashed\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"router1-branch-lon\",\n        \"from\": \"core-router-1\",\n        \"to\": \"branch-router-lon\",\n        \"width\": 3,\n        \"color\": \"#a855f7\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [\n          \"SD-WAN tunnel\"\n        ],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"dashed\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"router1-branch-tokyo\",\n        \"from\": \"core-router-1\",\n        \"to\": \"branch-router-tokyo\",\n        \"width\": 3,\n        \"color\": \"#a855f7\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [\n          \"SD-WAN tunnel\"\n        ],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"dashed\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"router1-aws\",\n        \"from\": \"core-router-1\",\n        \"to\": \"cloud-aws\",\n        \"width\": 3,\n        \"color\": \"#f97316\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [\n          \"Direct Connect\"\n        ],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"dashed\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"router2-azure\",\n        \"from\": \"core-router-2\",\n        \"to\": \"cloud-azure\",\n        \"width\": 3,\n        \"color\": \"#0ea5e9\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [\n          \"ExpressRoute\"\n        ],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"dashed\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"fwint-gcp\",\n        \"from\": \"fw-internal\",\n        \"to\": \"cloud-gcp\",\n        \"width\": 2,\n        \"color\": \"#22c55e\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [\n          \"VPN tunnel\"\n        ],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"dashed\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"coresw1-floor1\",\n        \"from\": \"core-switch-1\",\n        \"to\": \"dist-switch-floor1\",\n        \"width\": 3,\n        \"color\": \"#475569\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"coresw1-floor2\",\n        \"from\": \"core-switch-1\",\n        \"to\": \"dist-switch-floor2\",\n        \"width\": 3,\n        \"color\": \"#475569\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"coresw2-floor3\",\n        \"from\": \"core-switch-2\",\n        \"to\": \"dist-switch-floor3\",\n        \"width\": 3,\n        \"color\": \"#475569\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"coresw2-floor4\",\n        \"from\": \"core-switch-2\",\n        \"to\": \"dist-switch-floor4\",\n        \"width\": 3,\n        \"color\": \"#475569\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"floor1-endpoints\",\n        \"from\": \"dist-switch-floor1\",\n        \"to\": \"endpoint-1000\",\n        \"width\": 2,\n        \"color\": \"#94a3b8\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"floor1-ap1\",\n        \"from\": \"dist-switch-floor1\",\n        \"to\": \"ap-floor1-zone1\",\n        \"width\": 2,\n        \"color\": \"#06b6d4\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"floor2-ap2\",\n        \"from\": \"dist-switch-floor2\",\n        \"to\": \"ap-floor2-zone1\",\n        \"width\": 2,\n        \"color\": \"#06b6d4\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"floor3-ap3\",\n        \"from\": \"dist-switch-floor3\",\n        \"to\": \"ap-floor3-zone1\",\n        \"width\": 2,\n        \"color\": \"#06b6d4\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"floor4-ap4\",\n        \"from\": \"dist-switch-floor4\",\n        \"to\": \"ap-floor4-zone1\",\n        \"width\": 2,\n        \"color\": \"#06b6d4\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"fwint-proxy1\",\n        \"from\": \"fw-internal\",\n        \"to\": \"proxy-server-1\",\n        \"width\": 2,\n        \"color\": \"#ef4444\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"fwint-proxy2\",\n        \"from\": \"fw-internal\",\n        \"to\": \"proxy-server-2\",\n        \"width\": 2,\n        \"color\": \"#ef4444\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"fwext1-vpn\",\n        \"from\": \"fw-external-1\",\n        \"to\": \"vpn-concentrator\",\n        \"width\": 3,\n        \"color\": \"#8b5cf6\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"coresw1-nac\",\n        \"from\": \"core-switch-1\",\n        \"to\": \"nac-server\",\n        \"width\": 2,\n        \"color\": \"#f59e0b\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"coresw1-voip\",\n        \"from\": \"core-switch-1\",\n        \"to\": \"voip-cluster\",\n        \"width\": 3,\n        \"color\": \"#22c55e\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"coresw2-video\",\n        \"from\": \"core-switch-2\",\n        \"to\": \"video-conf\",\n        \"width\": 3,\n        \"color\": \"#22c55e\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"coresw1-cameras\",\n        \"from\": \"core-switch-1\",\n        \"to\": \"security-cameras\",\n        \"width\": 2,\n        \"color\": \"#94a3b8\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"fwint-dev1\",\n        \"from\": \"fw-internal\",\n        \"to\": \"dev-server-1\",\n        \"width\": 2,\n        \"color\": \"#a855f7\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"fwint-dev2\",\n        \"from\": \"fw-internal\",\n        \"to\": \"dev-server-2\",\n        \"width\": 2,\n        \"color\": \"#a855f7\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\",\n        \"animate\": true,\n        \"animationSpeed\": \"1.5\"\n      },\n      {\n        \"id\": \"fwint-test\",\n        \"from\": \"fw-internal\",\n        \"to\": \"test-environment\",\n        \"width\": 2,\n        \"color\": \"#a855f7\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"coresw1-erp\",\n        \"from\": \"core-switch-1\",\n        \"to\": \"erp-system\",\n        \"width\": 3,\n        \"color\": \"#f59e0b\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"fwext1-crm\",\n        \"from\": \"fw-external-1\",\n        \"to\": \"crm-system\",\n        \"width\": 2,\n        \"color\": \"#f59e0b\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [\n          \"Salesforce cloud\"\n        ],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"dashed\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"ups1-racka1\",\n        \"from\": \"ups-dc-1\",\n        \"to\": \"dc-rack-a1\",\n        \"width\": 2,\n        \"color\": \"#fbbf24\",\n        \"direction\": \"forward\",\n        \"type\": \"main\",\n        \"notes\": [\n          \"Power feed A\"\n        ],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"ups2-racka2\",\n        \"from\": \"ups-dc-2\",\n        \"to\": \"dc-rack-a2\",\n        \"width\": 2,\n        \"color\": \"#fbbf24\",\n        \"direction\": \"forward\",\n        \"type\": \"main\",\n        \"notes\": [\n          \"Power feed B\"\n        ],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"ups1-rackb1\",\n        \"from\": \"ups-dc-1\",\n        \"to\": \"dc-rack-b1\",\n        \"width\": 2,\n        \"color\": \"#fbbf24\",\n        \"direction\": \"forward\",\n        \"type\": \"main\",\n        \"notes\": [\n          \"Power feed A\"\n        ],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\",\n        \"animate\": true,\n        \"animationSpeed\": \"4\"\n      },\n      {\n        \"id\": \"ups2-rackb2\",\n        \"from\": \"ups-dc-2\",\n        \"to\": \"dc-rack-b2\",\n        \"width\": 2,\n        \"color\": \"#fbbf24\",\n        \"direction\": \"forward\",\n        \"type\": \"main\",\n        \"notes\": [\n          \"Power feed B\"\n        ],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"cooling1-racka1\",\n        \"from\": \"cooling-1\",\n        \"to\": \"dc-rack-a1\",\n        \"width\": 2,\n        \"color\": \"#38bdf8\",\n        \"direction\": \"forward\",\n        \"type\": \"main\",\n        \"notes\": [\n          \"Cooling zone\"\n        ],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"dotted\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"cooling2-rackb1\",\n        \"from\": \"cooling-2\",\n        \"to\": \"dc-rack-b1\",\n        \"width\": 2,\n        \"color\": \"#38bdf8\",\n        \"direction\": \"forward\",\n        \"type\": \"main\",\n        \"notes\": [\n          \"Cooling zone\"\n        ],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"dotted\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"custom-1765237881452\",\n        \"type\": \"custom\",\n        \"color\": \"#c800ff\",\n        \"width\": 4,\n        \"lineStyle\": \"solid\",\n        \"direction\": \"forward\",\n        \"points\": [\n          {\n            \"x\": 3492.3994140625,\n            \"y\": 1526.9556884765625\n          },\n          {\n            \"x\": 3500.609619140625,\n            \"y\": 1830.7386474609375\n          },\n          {\n            \"x\": 3303.561279296875,\n            \"y\": 1732.2144775390625\n          }\n        ],\n        \"notes\": [],\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"custom-1765239355462\",\n        \"type\": \"custom\",\n        \"color\": \"#f97316\",\n        \"width\": 4,\n        \"lineStyle\": \"solid\",\n        \"direction\": \"forward\",\n        \"points\": [\n          {\n            \"x\": 2467.182861328125,\n            \"y\": 156.12173461914062\n          },\n          {\n            \"x\": 2146.36376953125,\n            \"y\": 146.32574462890625\n          },\n          {\n            \"x\": 2305.548828125,\n            \"y\": 244.28573608398438\n          }\n        ],\n        \"notes\": [],\n        \"routing\": \"orthogonal\"\n      }\n    ]\n  },\n  \"rectData\": {\n    \"list\": [\n      {\n        \"id\": \"rect-1765237540610\",\n        \"x\": 2879.214599609375,\n        \"y\": 159.71981811523438,\n        \"width\": 992.196044921875,\n        \"height\": 538.8650817871094,\n        \"color\": \"#f97316\",\n        \"style\": \"filled\",\n        \"lineStyle\": \"solid\",\n        \"notes\": []\n      },\n      {\n        \"id\": \"rect-1765237681216\",\n        \"x\": 448.3926696777344,\n        \"y\": 1671.651123046875,\n        \"width\": 916.3436584472656,\n        \"height\": 924.27734375,\n        \"color\": \"#c800ff\",\n        \"style\": \"outlined\",\n        \"lineStyle\": \"solid\",\n        \"notes\": []\n      },\n      {\n        \"id\": \"rect-1766437913740\",\n        \"x\": 904.5889892578125,\n        \"y\": 115.40318298339844,\n        \"width\": 110.93878173828125,\n        \"height\": 919.6242218017578,\n        \"color\": \"#5215f9\",\n        \"style\": \"filled\",\n        \"lineStyle\": \"wall\",\n        \"notes\": [],\n        \"borderWidth\": 13\n      },\n      {\n        \"id\": \"rect-1766437935414\",\n        \"x\": 130.93685150146484,\n        \"y\": 1072.3624877929688,\n        \"width\": 872.9131851196289,\n        \"height\": 99.260986328125,\n        \"color\": \"#5215f9\",\n        \"style\": \"filled\",\n        \"lineStyle\": \"wall\",\n        \"notes\": [],\n        \"borderWidth\": 13\n      }\n    ]\n  },\n  \"textData\": {\n    \"list\": [\n      {\n        \"id\": \"text-1765237828167\",\n        \"x\": 3411.458740234375,\n        \"y\": 1390.00439453125,\n        \"content\": \"Double click on desktop\\nor long press on mobile\\nto enter rack canvas view\",\n        \"fontSize\": 46,\n        \"color\": \"#e2e8f0\",\n        \"fontWeight\": \"bold\",\n        \"fontStyle\": \"italic\",\n        \"textAlign\": \"middle\",\n        \"textDecoration\": \"none\",\n        \"bgColor\": \"#000000\",\n        \"bgEnabled\": false,\n        \"opacity\": 1\n      },\n      {\n        \"id\": \"text-1765239331126\",\n        \"x\": 2454.5615234375,\n        \"y\": 160.73322105407715,\n        \"content\": \"Google is live!\",\n        \"fontSize\": 56,\n        \"color\": \"#e2e8f0\",\n        \"fontWeight\": \"bold\",\n        \"fontStyle\": \"normal\",\n        \"textAlign\": \"start\",\n        \"textDecoration\": \"none\",\n        \"bgColor\": \"#000000\",\n        \"bgEnabled\": false,\n        \"opacity\": 1\n      },\n      {\n        \"id\": \"text-1766446595277\",\n        \"x\": 654.3878479003906,\n        \"y\": 1367.7945556640625,\n        \"content\": \"SITE A\",\n        \"fontSize\": 101,\n        \"color\": \"#e2e8f0\",\n        \"fontWeight\": \"normal\",\n        \"fontStyle\": \"normal\",\n        \"textAlign\": \"start\",\n        \"textDecoration\": \"none\",\n        \"bgColor\": \"#000000\",\n        \"bgEnabled\": false,\n        \"opacity\": 1\n      },\n      {\n        \"id\": \"text-1766446610211\",\n        \"x\": 180.63662719726562,\n        \"y\": 1128.822998046875,\n        \"content\": \"SITE A\",\n        \"fontSize\": 101,\n        \"color\": \"#e2e8f0\",\n        \"fontWeight\": \"normal\",\n        \"fontStyle\": \"normal\",\n        \"textAlign\": \"start\",\n        \"textDecoration\": \"none\",\n        \"bgColor\": \"#000000\",\n        \"bgEnabled\": false,\n        \"opacity\": 1\n      },\n      {\n        \"id\": \"text-1766453024797\",\n        \"x\": 968.6458740234375,\n        \"y\": 1028.6621398925781,\n        \"content\": \"SITE A\",\n        \"fontSize\": 101,\n        \"color\": \"#e2e8f0\",\n        \"fontWeight\": \"normal\",\n        \"fontStyle\": \"normal\",\n        \"textAlign\": \"start\",\n        \"textDecoration\": \"none\",\n        \"bgColor\": \"#000000\",\n        \"bgEnabled\": false,\n        \"opacity\": 1,\n        \"rotation\": -89,\n        \"_dragStartX\": 972.46826171875,\n        \"_dragStartY\": 1009.5499572753906\n      },\n      {\n        \"id\": \"text-1766453070975\",\n        \"x\": 613.1589965820312,\n        \"y\": 1139.512939453125,\n        \"content\": \"SITE A\",\n        \"fontSize\": 101,\n        \"color\": \"#e2e8f0\",\n        \"fontWeight\": \"normal\",\n        \"fontStyle\": \"normal\",\n        \"textAlign\": \"start\",\n        \"textDecoration\": \"none\",\n        \"bgColor\": \"#000000\",\n        \"bgEnabled\": false,\n        \"opacity\": 1\n      },\n      {\n        \"id\": \"text-1766453072857\",\n        \"x\": 968.64599609375,\n        \"y\": 474.40818786621094,\n        \"content\": \"SITE A\",\n        \"fontSize\": 101,\n        \"color\": \"#e2e8f0\",\n        \"fontWeight\": \"normal\",\n        \"fontStyle\": \"normal\",\n        \"textAlign\": \"start\",\n        \"textDecoration\": \"none\",\n        \"bgColor\": \"#000000\",\n        \"bgEnabled\": false,\n        \"opacity\": 1,\n        \"rotation\": 269,\n        \"_dragStartX\": 1480.85302734375,\n        \"_dragStartY\": 822.2503356933594\n      }\n    ]\n  },\n  \"edgeLegend\": {\n    \"#10b981\": \"Trusted Lan\",\n    \"#f59e0b\": \"Secure Lan\",\n    \"#ef4444\": \"DMZ\",\n    \"#475569\": \"Main ISP\",\n    \"#3b82f6\": \"Alternate ISP\",\n    \"#8b5cf6\": \"you can edit me too\",\n    \"#06b6d4\": \"you can edit me too\",\n    \"#a855f7\": \"you can edit me too\",\n    \"#f97316\": \"you can edit me too\",\n    \"#0ea5e9\": \"you can edit me too\",\n    \"#22c55e\": \"you can edit me too\",\n    \"#94a3b8\": \"you can edit me too\",\n    \"#fbbf24\": \"you can edit me too\",\n    \"#38bdf8\": \"you can edit me too\",\n    \"#c800ff\": \"you can edit me too\"\n  },\n  \"nodePositions\": {\n    \"core-router-1\": {\n      \"x\": 3720.166015625,\n      \"y\": 245.9932403564453\n    },\n    \"core-router-2\": {\n      \"x\": 2499.883407638303,\n      \"y\": 329.99503430389154\n    },\n    \"fw-external-1\": {\n      \"x\": 3221.7385182723783,\n      \"y\": 1016.1364499992887\n    },\n    \"fw-external-2\": {\n      \"x\": 1915.5213706410505,\n      \"y\": 224.43528858865443\n    },\n    \"fw-internal\": {\n      \"x\": 1746.9168185079352,\n      \"y\": 477.5300527221864\n    },\n    \"core-switch-1\": {\n      \"x\": 449.39860669455675,\n      \"y\": 384.4578707617695\n    },\n    \"core-switch-2\": {\n      \"x\": 761.1664921394672,\n      \"y\": 180.89283910873155\n    },\n    \"dc-rack-a1\": {\n      \"x\": 783.7017241128451,\n      \"y\": 647.4086870405963\n    },\n    \"dc-rack-a2\": {\n      \"x\": 209.25701628255229,\n      \"y\": 228.01593190351014\n    },\n    \"dc-rack-b1\": {\n      \"x\": 3184.3186625759854,\n      \"y\": 1627.4495531027196\n    },\n    \"dc-rack-b2\": {\n      \"x\": 245.37065918741246,\n      \"y\": 499.6191264194081\n    },\n    \"dmz-rack\": {\n      \"x\": 2176.4105289561007,\n      \"y\": 610.8312056412005\n    },\n    \"mgmt-rack\": {\n      \"x\": 1601.2987201807314,\n      \"y\": 1281.4753424975324\n    },\n    \"esxi-host-01\": {\n      \"x\": 2162.2166789540615,\n      \"y\": 2608.110619289529\n    },\n    \"esxi-host-02\": {\n      \"x\": 2205.94717202368,\n      \"y\": 2689.67539624076\n    },\n    \"esxi-host-03\": {\n      \"x\": 2154.6015436939074,\n      \"y\": 2771.203009774913\n    },\n    \"esxi-host-04\": {\n      \"x\": 2195.986926025096,\n      \"y\": 2845\n    },\n    \"tor-switch-a1\": {\n      \"x\": 2146.8943639962963,\n      \"y\": 2845\n    },\n    \"esxi-host-05\": {\n      \"x\": 2185.9099961569727,\n      \"y\": 2845\n    },\n    \"esxi-host-06\": {\n      \"x\": 2139.099728450725,\n      \"y\": 2845\n    },\n    \"esxi-host-07\": {\n      \"x\": 2175.7223818764883,\n      \"y\": 2845\n    },\n    \"esxi-host-08\": {\n      \"x\": 2131.2222777148922,\n      \"y\": 2845\n    },\n    \"tor-switch-a2\": {\n      \"x\": 2165.4301485385085,\n      \"y\": 2845\n    },\n    \"san-primary\": {\n      \"x\": 2123.2667017518106,\n      \"y\": 2845\n    },\n    \"san-secondary\": {\n      \"x\": 2155.0394237844876,\n      \"y\": 2845\n    },\n    \"fc-switch-1\": {\n      \"x\": 2115.2377370375634,\n      \"y\": 2845\n    },\n    \"fc-switch-2\": {\n      \"x\": 2144.5563938942755,\n      \"y\": 2845\n    },\n    \"backup-server-1\": {\n      \"x\": 2107.1401637413705,\n      \"y\": 2845\n    },\n    \"backup-server-2\": {\n      \"x\": 2133.987300103025,\n      \"y\": 2845\n    },\n    \"tape-library\": {\n      \"x\": 2098.9788028796397,\n      \"y\": 2845\n    },\n    \"tor-switch-b1\": {\n      \"x\": 2123.338434885373,\n      \"y\": 2845\n    },\n    \"tor-switch-b2\": {\n      \"x\": 2090.7585134456995,\n      \"y\": 2845\n    },\n    \"web-server-1\": {\n      \"x\": 2112.6161382091163,\n      \"y\": 2845\n    },\n    \"web-server-2\": {\n      \"x\": 2082.484189516922,\n      \"y\": 2845\n    },\n    \"waf-1\": {\n      \"x\": 2101.826793760617,\n      \"y\": 2845\n    },\n    \"load-balancer-dmz\": {\n      \"x\": 2074.1607573409574,\n      \"y\": 2845\n    },\n    \"mail-gateway\": {\n      \"x\": 2090.97682514417,\n      \"y\": 2845\n    },\n    \"dns-external-1\": {\n      \"x\": 2065.7931724028163,\n      \"y\": 2845\n    },\n    \"dns-external-2\": {\n      \"x\": 2080.0726920576153,\n      \"y\": 2845\n    },\n    \"vcenter\": {\n      \"x\": 2057.3864164745437,\n      \"y\": 2845\n    },\n    \"nsx-manager\": {\n      \"x\": 2069.1208864464534,\n      \"y\": 2845\n    },\n    \"siem-server\": {\n      \"x\": 2048.945494649244,\n      \"y\": 2845\n    },\n    \"nms-server\": {\n      \"x\": 2058.1279286387635,\n      \"y\": 2845\n    },\n    \"jump-server\": {\n      \"x\": 2040.4754323612206,\n      \"y\": 2845\n    },\n    \"ipam-server\": {\n      \"x\": 2047.1003634632284,\n      \"y\": 2845\n    },\n    \"wlc-primary\": {\n      \"x\": 1575.9723612611924,\n      \"y\": 2306.135986328125\n    },\n    \"wlc-secondary\": {\n      \"x\": 1468.1361870166274,\n      \"y\": 1563.733642578125\n    },\n    \"mobile-zone-hq\": {\n      \"x\": 2354.901177346808,\n      \"y\": 2806.0078125\n    },\n    \"mobile-zone-guest\": {\n      \"x\": 2307.6605605284435,\n      \"y\": 2611.047119140625\n    },\n    \"mobile-zone-iot\": {\n      \"x\": 2229.397686389302,\n      \"y\": 2299.110107421875\n    },\n    \"branch-router-ny\": {\n      \"x\": 3151.903101363964,\n      \"y\": 633.6580810546875\n    },\n    \"branch-router-la\": {\n      \"x\": 3083.8876194705945,\n      \"y\": 506.90625\n    },\n    \"branch-router-chi\": {\n      \"x\": 3355.02409980103,\n      \"y\": 393.1805725097656\n    },\n    \"branch-router-lon\": {\n      \"x\": 3113.609823320121,\n      \"y\": 260.4093322753906\n    },\n    \"branch-router-tokyo\": {\n      \"x\": 3699.3234994733834,\n      \"y\": 471.4241027832031\n    },\n    \"cloud-aws\": {\n      \"x\": 3436.528122523513,\n      \"y\": 545.9614868164062\n    },\n    \"cloud-azure\": {\n      \"x\": 2592.566210818907,\n      \"y\": 2724.068115234375\n    },\n    \"cloud-gcp\": {\n      \"x\": 2827.3183770424234,\n      \"y\": 2731.397216796875\n    },\n    \"isp-primary\": {\n      \"x\": 3712.192068081962,\n      \"y\": 615.64990234375\n    },\n    \"isp-secondary\": {\n      \"x\": 2702.3789772348055,\n      \"y\": 467.890869140625\n    },\n    \"dc-internal-1\": {\n      \"x\": 1958.4243458877936,\n      \"y\": 2845\n    },\n    \"dc-internal-2\": {\n      \"x\": 1963.768951182132,\n      \"y\": 2845\n    },\n    \"app-server-1\": {\n      \"x\": 1947.3819379304134,\n      \"y\": 2845\n    },\n    \"app-server-2\": {\n      \"x\": 1955.2862087394126,\n      \"y\": 2845\n    },\n    \"db-server-1\": {\n      \"x\": 1936.3708569559828,\n      \"y\": 2845\n    },\n    \"db-server-2\": {\n      \"x\": 1946.8300873488822,\n      \"y\": 2845\n    },\n    \"k8s-master-1\": {\n      \"x\": 1925.397658583093,\n      \"y\": 2845\n    },\n    \"k8s-master-2\": {\n      \"x\": 1938.405621494142,\n      \"y\": 2845\n    },\n    \"k8s-master-3\": {\n      \"x\": 1914.4688758763386,\n      \"y\": 2845\n    },\n    \"k8s-worker-1\": {\n      \"x\": 1930.017826812177,\n      \"y\": 2845\n    },\n    \"k8s-worker-2\": {\n      \"x\": 1903.5910154567553,\n      \"y\": 2845\n    },\n    \"k8s-worker-3\": {\n      \"x\": 1921.6716971072178,\n      \"y\": 2845\n    },\n    \"k8s-worker-4\": {\n      \"x\": 1892.7705536280016,\n      \"y\": 2845\n    },\n    \"proxy-server-1\": {\n      \"x\": 1806.1152433697903,\n      \"y\": 653.7529296875\n    },\n    \"proxy-server-2\": {\n      \"x\": 2937.4207928721535,\n      \"y\": 2628.7880859375\n    },\n    \"vpn-concentrator\": {\n      \"x\": 3642.252088474593,\n      \"y\": 946.7255249023438\n    },\n    \"nac-server\": {\n      \"x\": 1153.2626148502184,\n      \"y\": 1172.1895751953125\n    },\n    \"print-server\": {\n      \"x\": 1896.9328460745962,\n      \"y\": 2845\n    },\n    \"file-server\": {\n      \"x\": 1860.7177871362182,\n      \"y\": 2845\n    },\n    \"ca-server\": {\n      \"x\": 1888.8027739274805,\n      \"y\": 2845\n    },\n    \"sccm-server\": {\n      \"x\": 1850.1909418511675,\n      \"y\": 2845\n    },\n    \"voip-cluster\": {\n      \"x\": 1777.038465328039,\n      \"y\": 1616.8961181640625\n    },\n    \"video-conf\": {\n      \"x\": 1993.8373941679588,\n      \"y\": 2244.936309814453\n    },\n    \"security-cameras\": {\n      \"x\": 1674.413336949044,\n      \"y\": 2046.0380859375\n    },\n    \"nvr-cluster\": {\n      \"x\": 1829.4110389706402,\n      \"y\": 2845\n    },\n    \"dev-server-1\": {\n      \"x\": 2800.5894350649614,\n      \"y\": 1175.623291015625\n    },\n    \"dev-server-2\": {\n      \"x\": 1945.0822182484326,\n      \"y\": 1164.5184783935547\n    },\n    \"test-environment\": {\n      \"x\": 2566.9100352578575,\n      \"y\": 885.2827758789062\n    },\n    \"erp-system\": {\n      \"x\": 789.9880103985649,\n      \"y\": 473.7113342285156\n    },\n    \"crm-system\": {\n      \"x\": 3514.6003232048542,\n      \"y\": 1137.7720947265625\n    },\n    \"endpoint-1000\": {\n      \"x\": 991.6812012057328,\n      \"y\": 2284.42236328125\n    },\n    \"dist-switch-floor1\": {\n      \"x\": 654.2091033261356,\n      \"y\": 2020.0086669921875\n    },\n    \"dist-switch-floor2\": {\n      \"x\": 853.8845527112826,\n      \"y\": 1843.2872314453125\n    },\n    \"dist-switch-floor3\": {\n      \"x\": 1899.4353951584517,\n      \"y\": 1456.5068359375\n    },\n    \"dist-switch-floor4\": {\n      \"x\": 488.5289313756234,\n      \"y\": 181.47256469726562\n    },\n    \"ap-floor1-zone1\": {\n      \"x\": 1140.16846970184,\n      \"y\": 2070.2916259765625\n    },\n    \"ap-floor2-zone1\": {\n      \"x\": 688.1952143592268,\n      \"y\": 2384.4775390625\n    },\n    \"ap-floor3-zone1\": {\n      \"x\": 2145.3803027919676,\n      \"y\": 1890.2816162109375\n    },\n    \"ap-floor4-zone1\": {\n      \"x\": 517.646146409649,\n      \"y\": 565.59716796875\n    },\n    \"ups-dc-1\": {\n      \"x\": 771.1406786539856,\n      \"y\": 295.9266662597656\n    },\n    \"ups-dc-2\": {\n      \"x\": 216.2410855890687,\n      \"y\": 330.3345947265625\n    },\n    \"pdu-rack-a1\": {\n      \"x\": 1804.774444371901,\n      \"y\": 2845\n    },\n    \"pdu-rack-a2\": {\n      \"x\": 1741.6184034693686,\n      \"y\": 2845\n    },\n    \"cooling-1\": {\n      \"x\": 245.7080801919958,\n      \"y\": 626.1914672851562\n    },\n    \"cooling-2\": {\n      \"x\": 1603.293611085831,\n      \"y\": 981.0621185302734\n    },\n    \"camera-a\": {\n      \"x\": 166.57075412676295,\n      \"y\": 145\n    },\n    \"camera-a-copy\": {\n      \"x\": 1040.653076171875,\n      \"y\": 738.42822265625\n    }\n  },\n  \"nodeSizes\": {\n    \"isp-secondary\": 139,\n    \"test-environment\": 148,\n    \"dev-server-1\": 128,\n    \"core-router-2\": 120,\n    \"camera-a\": 45,\n    \"camera-a-copy\": 45\n  },\n  \"nodeStyles\": {\n    \"dc-rack-b2\": {\n      \"all\": {\n        \"circleColor\": \"#ff0000\"\n      }\n    },\n    \"dc-rack-a1\": {\n      \"all\": {\n        \"circleColor\": \"#ff0000\"\n      }\n    },\n    \"dc-rack-b1\": {\n      \"all\": {\n        \"circleColor\": \"#ff0000\",\n        \"titleSize\": 59\n      }\n    },\n    \"isp-secondary\": {\n      \"all\": {\n        \"icon\": {\n          \"library\": \"selfhst\",\n          \"name\": \"alist\"\n        }\n      }\n    },\n    \"core-router-2\": {\n      \"all\": {\n        \"icon\": {\n          \"library\": \"selfhst\",\n          \"name\": \"actual-budget\"\n        },\n        \"pingOffsetX\": -15,\n        \"pingOffsetY\": -38\n      }\n    },\n    \"fw-external-1\": {\n      \"all\": {\n        \"icon\": {\n          \"library\": \"selfhst\",\n          \"name\": \"anonaddy\"\n        }\n      }\n    },\n    \"cloud-aws\": {\n      \"all\": {\n        \"icon\": {\n          \"library\": \"selfhst\",\n          \"name\": \"ansible\"\n        }\n      }\n    },\n    \"isp-primary\": {\n      \"all\": {\n        \"icon\": {\n          \"library\": \"selfhst\",\n          \"name\": \"wikidocs\"\n        }\n      }\n    },\n    \"branch-router-tokyo\": {\n      \"all\": {\n        \"icon\": {\n          \"library\": \"selfhst\",\n          \"name\": \"adguard-home\"\n        }\n      }\n    },\n    \"core-router-1\": {\n      \"all\": {\n        \"icon\": {\n          \"library\": \"selfhst\",\n          \"name\": \"borg\"\n        }\n      }\n    },\n    \"test-environment\": {\n      \"all\": {\n        \"icon\": {\n          \"library\": \"simple\",\n          \"name\": \"apple\"\n        }\n      }\n    },\n    \"dev-server-1\": {\n      \"all\": {\n        \"icon\": {\n          \"library\": \"simple\",\n          \"name\": \"amazonwebservices\"\n        }\n      }\n    }\n  },\n  \"iconCache\": {\n    \"selfhst-borg\": \"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" xml:space=\\\"preserve\\\" viewBox=\\\"0 0 512 512\\\"><path d=\\\"M331 102.2H204.5V0h197.9L505 102.2v104.2l-51.3 51.3L505 309v100.7L402.4 512H204.5V409.8H331V300.6H204.5v-89.1H331zM6.9 0h170.5v512H6.9z\\\" style=\\\"fill:#0d0\\\"/></svg>\",\n    \"selfhst-actual-budget\": \"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" xml:space=\\\"preserve\\\" viewBox=\\\"0 0 512 512\\\"><path d=\\\"M378.5 512h-245C59.8 512 0 452.2 0 378.5v-245C0 59.8 59.8 0 133.5 0h245C452.2 0 512 59.8 512 133.5v245c0 73.7-59.8 133.5-133.5 133.5\\\" style=\\\"fill:#805ad5\\\"/><path d=\\\"m407.7 384.1-33.2-68.2 26.9-10.4c1.7-.6 2.6-2.6 1.9-4.3l-7.8-20.2c-.6-1.7-2.6-2.6-4.3-1.9L362 290.3 268.8 99.2c-.6-1.2-1.7-1.9-3-1.9h-6.3c-1.3 0-2.4.7-3 1.9l-95 201.6-55.2 20.3c-1.8.6-2.6 2.6-2 4.3l7.4 20.3c.6 1.8 2.6 2.6 4.3 2l28.4-10.4-32.2 68.3c-.3.8-.4 1.7 0 2.5l1.6 4.3v.1c.6 1.7 2.6 2.6 4.3 1.9l229.8-88.3 34.3 70.4c.8 1.7 2.8 2.4 4.5 1.6l19.5-9.5c1.6-.8 2.3-2.8 1.5-4.5M263 151.8l46.1 94.9L199.4 287zM161.5 367.4l20.7-44 139.4-51.1 13.7 28.3z\\\" style=\\\"fill:#fff\\\"/></svg>\",\n    \"selfhst-anonaddy\": \"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" xml:space=\\\"preserve\\\" viewBox=\\\"0 0 512 512\\\"><linearGradient id=\\\"anonaddy_svg__a\\\" x1=\\\"44.378\\\" x2=\\\"447.022\\\" y1=\\\"386.378\\\" y2=\\\"789.022\\\" gradientTransform=\\\"translate(0 -278)\\\" gradientUnits=\\\"userSpaceOnUse\\\"><stop offset=\\\"0\\\" style=\\\"stop-color:#3ae7e1\\\"/><stop offset=\\\".948\\\" style=\\\"stop-color:#1993d2\\\"/></linearGradient><path d=\\\"M320 224H192c-17.7 0-32 14.3-32 32v128c0 17.7 14.3 32 32 32h128c17.7 0 32-14.3 32-32V256c0-17.7-14.3-32-32-32m-32 128h-64v-64h64z\\\" style=\\\"fill:url(#anonaddy_svg__a)\\\"/><linearGradient id=\\\"anonaddy_svg__b\\\" x1=\\\"44.337\\\" x2=\\\"446.981\\\" y1=\\\"386.418\\\" y2=\\\"789.062\\\" gradientTransform=\\\"translate(0 -278)\\\" gradientUnits=\\\"userSpaceOnUse\\\"><stop offset=\\\"0\\\" style=\\\"stop-color:#3ae7e1\\\"/><stop offset=\\\".948\\\" style=\\\"stop-color:#1993d2\\\"/></linearGradient><path d=\\\"M384 384c0 5.5-.8 10.9-2.1 16 39.4-21.8 66.1-63.8 66.1-112v-32c0-47.4-25.8-88.6-64-110.7-18.8-10.9-40.7-17.3-64-17.3H192c-23.3 0-45.2 6.4-64 17.3-38.2 22.1-64 63.3-64 110.7v128c0 70.7 57.3 128 128 128h128c0-35.3-28.7-64-64-64h-64c-35.3 0-64-28.7-64-64V256c0-35.3 28.7-64 64-64h128c35.3 0 64 28.7 64 64z\\\" style=\\\"fill:url(#anonaddy_svg__b)\\\"/><path d=\\\"M384 288c0 23.7-12.9 44.3-32 55.4V384c0 17.7-14.3 32-32 32 22.5 0 43.5-5.8 61.9-16 1.3-5.1 2.1-10.5 2.1-16zM128 145.3c18.8-10.9 40.7-17.3 64-17.3 0-11.7 3.2-22.6 8.7-32H192c-21.4 0-42.7 4.4-62.4 12.8-1 6.3-1.6 12.7-1.6 19.2zM311.3 96c5.5 9.4 8.7 20.3 8.7 32 23.3 0 45.2 6.4 64 17.3V128c0-6.5-.6-12.9-1.6-19.2-19.7-8.4-41-12.8-62.4-12.8z\\\" style=\\\"fill:#2d7aae\\\"/><linearGradient id=\\\"anonaddy_svg__c\\\" x1=\\\"158.782\\\" x2=\\\"561.426\\\" y1=\\\"271.974\\\" y2=\\\"674.618\\\" gradientTransform=\\\"translate(0 -278)\\\" gradientUnits=\\\"userSpaceOnUse\\\"><stop offset=\\\"0\\\" style=\\\"stop-color:#3ae7e1\\\"/><stop offset=\\\".948\\\" style=\\\"stop-color:#1993d2\\\"/></linearGradient><path d=\\\"M200.7 96c11.1-19.1 31.7-32 55.3-32s44.3 12.9 55.3 32h8.7c21.4 0 42.7 4.4 62.4 12.8C373.1 47.3 320 0 256 0S138.9 47.3 129.6 108.8c19.7-8.4 41-12.8 62.4-12.8z\\\" style=\\\"fill:url(#anonaddy_svg__c)\\\"/></svg>\",\n    \"selfhst-adguard-home\": \"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" xml:space=\\\"preserve\\\" viewBox=\\\"0 0 512 512\\\"><path d=\\\"M505.8 58.8C428.2 18.4 334.1 0 256 0h-.3v511.8c-21.5-12.7-41.1-26-59.1-39.6 18 13.7 37.8 27 59.4 39.8C506.8 363.4 505.8 146 505.8 58.8\\\" style=\\\"fill-rule:evenodd;clip-rule:evenodd;fill:#68bc71\\\"/><path d=\\\"M255.7 511.8C5.2 363.3 6.2 146 6.2 58.8 83.7 18.4 177.7 0 255.7 0z\\\" style=\\\"fill-rule:evenodd;clip-rule:evenodd;fill:#67b279\\\"/><path d=\\\"m246.9 341.6 151-203.6c-11.1-8.9-20.8-2.6-26.1 2.2h-.2l-125.9 131-47.4-57.1c-22.6-26.2-53.4-6.2-60.6-.9z\\\" style=\\\"fill-rule:evenodd;clip-rule:evenodd;fill:#fff\\\"/></svg>\",\n    \"selfhst-ansible\": \"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" xml:space=\\\"preserve\\\" viewBox=\\\"0 0 512 512\\\"><path d=\\\"M512 256c0 141.4-114.6 256-256 256S0 397.4 0 256 114.6 0 256 0s256 114.6 256 256\\\" style=\\\"fill:#c32214\\\"/><path d=\\\"m260.3 156.4 66.2 163.5-100.1-78.8zM378 357.6 276.1 112.3c-2.9-7.1-8.7-10.8-15.8-10.8s-13.3 3.7-16.2 10.8l-111.8 269h38.3l44.3-110.9L347 377.1c5.3 4.3 9.1 6.2 14.1 6.2 10 0 18.7-7.5 18.7-18.3-.1-1.7-.7-4.4-1.8-7.4\\\" style=\\\"fill:#fff\\\"/></svg>\",\n    \"selfhst-wikidocs\": \"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" xml:space=\\\"preserve\\\" viewBox=\\\"0 0 512 512\\\"><path d=\\\"M488.8 72.1h-90.7c-8.4 0-15.1 6.7-15.1 15.1v206.4c0 8.4-6.7 15.1-15.1 15.1h-45.3c-8.4 0-15.1-6.7-15.1-15.1V87.2c0-8.4-6.7-15.1-15.1-15.1h-72.5c-8.4 0-15.1 6.7-15.1 15.1v206.4c0 8.4-6.7 15.1-15.1 15.1h-45.3c-8.4 0-15.1-6.7-15.1-15.1V87.2c0-8.4-6.7-15.1-15.1-15.1H23.4c-8.4 0-15.1 6.7-15.1 15.1v257.2c0 50.1 38.6 91.2 87.5 95.3v.3h392.9c8.4 0 15.1-6.7 15.1-15.1V87.2c0-8.4-6.7-15.1-15-15.1\\\" style=\\\"fill:#4caf50\\\"/><path d=\\\"M488.6 448.2H95.7c-1.1 0-2.1-.2-3.1-.6-24.9-2.7-48-14.2-65.1-32.8C9.8 395.6 0 370.5 0 344.3V87.2c0-12.9 10.5-23.4 23.4-23.4h90.9c12.9 0 23.4 10.5 23.4 23.4v206.4c0 3.8 3 6.8 6.8 6.8h45.3c3.8 0 6.8-3 6.8-6.8V87.2c0-12.9 10.5-23.4 23.4-23.4h72.5c12.9 0 23.4 10.5 23.4 23.4v206.4c0 3.8 3 6.8 6.8 6.8H368c3.8 0 6.8-3 6.8-6.8V87.2c0-12.9 10.5-23.4 23.4-23.4h90.7c6.3 0 12.2 2.5 16.6 6.9s6.7 10.3 6.6 16.6v337.6c-.1 12.8-10.6 23.3-23.5 23.3M98.1 431.7h390.6c3.8 0 6.8-3 6.8-6.8V87.2c0-1.9-.6-3.6-1.9-4.8-1.3-1.3-3-2-4.8-2h-90.7c-3.8 0-6.8 3-6.8 6.8v206.4c0 12.9-10.5 23.4-23.4 23.4h-45.3c-12.9 0-23.4-10.5-23.4-23.4V87.2c0-3.8-3-6.8-6.8-6.8h-72.5c-3.8 0-6.8 3-6.8 6.8v206.4c0 12.9-10.5 23.4-23.4 23.4h-45.3c-12.9 0-23.4-10.5-23.4-23.4V87.2c0-3.8-3-6.8-6.8-6.8H23.4c-3.8 0-6.8 3-6.8 6.8v257.2c0 45.1 35.1 83.3 79.9 87.1.5-.1 1 0 1.6.2\\\" style=\\\"fill:#fff\\\"/></svg>\",\n    \"selfhst-alist\": \"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" xml:space=\\\"preserve\\\" viewBox=\\\"0 0 512 512\\\"><path d=\\\"M260.5 30.8c5.4-.6 11 .6 15.5 3.7 6.8 4.4 11.6 11.1 15.7 17.9 18.4 30.9 37 61.7 55.4 92.6 24.5 41.3 49.1 82.7 73.6 124q25.35 42.9 51 85.8c11.2 19.1 22.8 37.9 33.5 57.3 4.6 8.1 7.2 17.5 6.8 26.8-.3 7.2-1.6 14.7-5.4 20.9-5.3 8.8-14.3 15.1-23.9 18.3-5.2 1.9-10.6 3.1-16.1 3.1H286.3c-5.2-.1-10.4-.7-15.3-2.6-3.5-1.5-7-3.4-9.8-6.1-4.7-4.7-7.8-11.3-7-18.1.4-4.7 1.6-9.4 3.5-13.7 1.9-4.4 4.5-8.5 6.8-12.7 10.1-17.9 20.6-35.6 31.5-53 3.5-5.5 6.6-11.4 11.4-15.8 5.2-4.7 11.6-8.4 18.7-8.9 5.7-.2 11.3 1.7 16.1 4.8 5.4 3.1 9.6 8.4 11 14.5 2 7.7-.2 15.8-3.4 22.9-3.7 8-8.9 15.3-11.7 23.7-.5 1.9-1.2 4.3.2 6 1.4 1.4 3.5 1.9 5.3 2.4 5.5 1.2 11.1.9 16.6.9h61.3c4-.1 8.1.1 12.1-.1 2.9-.3 6.1-.8 8.4-2.9 1.5-1.3 1.7-3.6 1.1-5.4-1.2-4-3.4-7.7-5.5-11.3C383.2 312.9 329 219.7 274 126.9c-1.8-3-3.3-6.2-5.9-8.6-1.3-1.2-3.2-1.9-4.9-1.4-1.5.5-2.5 1.8-3.5 3-2.4 3.1-4.6 6.4-6.6 9.8-9.5 15.4-18.6 31.1-27.9 46.6-53.6 90.1-107.1 180.3-160.7 270.4-3.3 6.1-6.9 12.1-10.7 17.9-3.4 5-7.5 9.6-12.8 12.6-6.2 3.6-13.9 5-20.9 3-5.1-1.6-10-4.1-13.7-8.1-4.6-4.6-6.7-11.3-6.4-17.7.2-6.7 2.5-13.3 5.9-19.1C38.4 380.1 71.1 325 104 269.9c16.9-28.5 33.7-57.1 50.7-85.6 17.2-28.8 34.4-57.7 51.5-86.5 9.5-16.3 19.2-32.5 29.2-48.5 3.1-5.1 7.1-9.6 11.9-13.1 4-2.7 8.4-5 13.2-5.4\\\" style=\\\"fill:#70c6be\\\"/><path d=\\\"M257.8 244.3c8.1-1.3 16.7.6 23.4 5.4 5.1 3.9 8.6 9.8 9.7 16.2.7 4.9.2 9.9-1.6 14.5-1.4 4-3.3 7.7-5.5 11.3-31.2 52.5-62.5 105-93.7 157.5-4 6.6-7.6 13.4-12.2 19.6-3.3 4.1-7.5 7.7-12.4 9.9-10.2 5.4-23.5 2.1-31.3-6.1-3.9-3.9-6.2-9.4-6.5-14.9-.4-5.7.8-11.5 3.4-16.7 2.3-5 5-9.7 7.8-14.4 13.7-23.1 27.3-46.3 41-69.4 18.7-31.8 37.8-63.4 56.6-95.1 2-3.6 4.5-6.9 7.3-9.9 3.8-4 8.6-7.1 14-7.9\\\" style=\\\"fill:#1ba0d8\\\"/></svg>\",\n    \"simple-amazonwebservices\": \"<svg role=\\\"img\\\" viewBox=\\\"0 0 24 24\\\" xmlns=\\\"http://www.w3.org/2000/svg\\\"><title>Amazon Web Services</title><path d=\\\"M6.763 10.036c0 .296.032.535.088.71.064.176.144.368.256.576.04.063.056.127.056.183 0 .08-.048.16-.152.24l-.503.335a.383.383 0 0 1-.208.072c-.08 0-.16-.04-.239-.112a2.47 2.47 0 0 1-.287-.375 6.18 6.18 0 0 1-.248-.471c-.622.734-1.405 1.101-2.347 1.101-.67 0-1.205-.191-1.596-.574-.391-.384-.59-.894-.59-1.533 0-.678.239-1.23.726-1.644.487-.415 1.133-.623 1.955-.623.272 0 .551.024.846.064.296.04.6.104.918.176v-.583c0-.607-.127-1.03-.375-1.277-.255-.248-.686-.367-1.3-.367-.28 0-.568.031-.863.103-.295.072-.583.16-.862.272a2.287 2.287 0 0 1-.28.104.488.488 0 0 1-.127.023c-.112 0-.168-.08-.168-.247v-.391c0-.128.016-.224.056-.28a.597.597 0 0 1 .224-.167c.279-.144.614-.264 1.005-.36a4.84 4.84 0 0 1 1.246-.151c.95 0 1.644.216 2.091.647.439.43.662 1.085.662 1.963v2.586zm-3.24 1.214c.263 0 .534-.048.822-.144.287-.096.543-.271.758-.51.128-.152.224-.32.272-.512.047-.191.08-.423.08-.694v-.335a6.66 6.66 0 0 0-.735-.136 6.02 6.02 0 0 0-.75-.048c-.535 0-.926.104-1.19.32-.263.215-.39.518-.39.917 0 .375.095.655.295.846.191.2.47.296.838.296zm6.41.862c-.144 0-.24-.024-.304-.08-.064-.048-.12-.16-.168-.311L7.586 5.55a1.398 1.398 0 0 1-.072-.32c0-.128.064-.2.191-.2h.783c.151 0 .255.025.31.08.065.048.113.16.16.312l1.342 5.284 1.245-5.284c.04-.16.088-.264.151-.312a.549.549 0 0 1 .32-.08h.638c.152 0 .256.025.32.08.063.048.12.16.151.312l1.261 5.348 1.381-5.348c.048-.16.104-.264.16-.312a.52.52 0 0 1 .311-.08h.743c.127 0 .2.065.2.2 0 .04-.009.08-.017.128a1.137 1.137 0 0 1-.056.2l-1.923 6.17c-.048.16-.104.263-.168.311a.51.51 0 0 1-.303.08h-.687c-.151 0-.255-.024-.32-.08-.063-.056-.119-.16-.15-.32l-1.238-5.148-1.23 5.14c-.04.16-.087.264-.15.32-.065.056-.177.08-.32.08zm10.256.215c-.415 0-.83-.048-1.229-.143-.399-.096-.71-.2-.918-.32-.128-.071-.215-.151-.247-.223a.563.563 0 0 1-.048-.224v-.407c0-.167.064-.247.183-.247.048 0 .096.008.144.024.048.016.12.048.2.08.271.12.566.215.878.279.319.064.63.096.95.096.502 0 .894-.088 1.165-.264a.86.86 0 0 0 .415-.758.777.777 0 0 0-.215-.559c-.144-.151-.416-.287-.807-.415l-1.157-.36c-.583-.183-1.014-.454-1.277-.813a1.902 1.902 0 0 1-.4-1.158c0-.335.073-.63.216-.886.144-.255.335-.479.575-.654.24-.184.51-.32.83-.415.32-.096.655-.136 1.006-.136.175 0 .359.008.535.032.183.024.35.056.518.088.16.04.312.08.455.127.144.048.256.096.336.144a.69.69 0 0 1 .24.2.43.43 0 0 1 .071.263v.375c0 .168-.064.256-.184.256a.83.83 0 0 1-.303-.096 3.652 3.652 0 0 0-1.532-.311c-.455 0-.815.071-1.062.223-.248.152-.375.383-.375.71 0 .224.08.416.24.567.159.152.454.304.877.44l1.134.358c.574.184.99.44 1.237.767.247.327.367.702.367 1.117 0 .343-.072.655-.207.926-.144.272-.336.511-.583.703-.248.2-.543.343-.886.447-.36.111-.734.167-1.142.167zM21.698 16.207c-2.626 1.94-6.442 2.969-9.722 2.969-4.598 0-8.74-1.7-11.87-4.526-.247-.223-.024-.527.272-.351 3.384 1.963 7.559 3.153 11.877 3.153 2.914 0 6.114-.607 9.06-1.852.439-.2.814.287.383.607zM22.792 14.961c-.336-.43-2.22-.207-3.074-.103-.255.032-.295-.192-.063-.36 1.5-1.053 3.967-.75 4.254-.399.287.36-.08 2.826-1.485 4.007-.215.184-.423.088-.327-.151.32-.79 1.03-2.57.695-2.994z\\\"/></svg>\",\n    \"simple-apple\": \"<svg role=\\\"img\\\" viewBox=\\\"0 0 24 24\\\" xmlns=\\\"http://www.w3.org/2000/svg\\\"><title>Apple</title><path d=\\\"M12.152 6.896c-.948 0-2.415-1.078-3.96-1.04-2.04.027-3.91 1.183-4.961 3.014-2.117 3.675-.546 9.103 1.519 12.09 1.013 1.454 2.208 3.09 3.792 3.039 1.52-.065 2.09-.987 3.935-.987 1.831 0 2.35.987 3.96.948 1.637-.026 2.676-1.48 3.676-2.948 1.156-1.688 1.636-3.325 1.662-3.415-.039-.013-3.182-1.221-3.22-4.857-.026-3.04 2.48-4.494 2.597-4.559-1.429-2.09-3.623-2.324-4.39-2.376-2-.156-3.675 1.09-4.61 1.09zM15.53 3.83c.843-1.012 1.4-2.427 1.245-3.83-1.207.052-2.662.805-3.532 1.818-.78.896-1.454 2.338-1.273 3.714 1.338.104 2.715-.688 3.559-1.701\\\"/></svg>\",\n    \"selfhst-amazon-web-services\": \"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" xml:space=\\\"preserve\\\" viewBox=\\\"0 0 512 512\\\"><path d=\\\"M144.3 214.1c0 6.3.7 11.4 1.9 15.2 1.4 3.7 3.1 7.8 5.4 12.3.9 1.4 1.2 2.7 1.2 3.9 0 1.7-1 3.4-3.2 5.1l-10.7 7.2c-1.5 1-3.1 1.5-4.4 1.5-1.7 0-3.4-.9-5.1-2.4-2.4-2.6-4.4-5.3-6.1-8-1.7-2.9-3.4-6.1-5.3-10-13.3 15.7-30 23.5-50.1 23.5-14.3 0-25.7-4.1-34.1-12.3-8.3-8.2-12.6-19.1-12.6-32.7 0-14.5 5.1-26.2 15.5-35.1S60.8 169 78.4 169c5.8 0 11.7.5 18.1 1.4s12.8 2.2 19.6 3.7v-12.4c0-12.9-2.7-22-8-27.2-5.4-5.3-14.6-7.8-27.8-7.8-6 0-12.1.7-18.4 2.2s-12.4 3.4-18.4 5.8c-2.7 1.2-4.8 1.9-6 2.2s-2 .5-2.7.5c-2.4 0-3.6-1.7-3.6-5.3v-8.3c0-2.7.3-4.8 1.2-6s2.4-2.4 4.8-3.6c6-3.1 13.1-5.6 21.5-7.7 8.3-2.2 17.2-3.2 26.6-3.2 20.3 0 35.1 4.6 44.6 13.8 9.4 9.2 14.1 23.2 14.1 41.9v55.2zM75.2 240c5.6 0 11.4-1 17.5-3.1 6.1-2 11.6-5.8 16.2-10.9 2.7-3.2 4.8-6.8 5.8-10.9s1.7-9 1.7-14.8v-7.2c-4.9-1.2-10.2-2.2-15.7-2.9-5.4-.7-10.7-1-16-1-11.4 0-19.8 2.2-25.4 6.8S51 207.1 51 215.6c0 8 2 14 6.3 18.1 4.1 4.2 10 6.3 17.9 6.3m136.7 18.4c-3.1 0-5.1-.5-6.5-1.7-1.4-1-2.6-3.4-3.6-6.6l-40-131.6c-1-3.4-1.5-5.6-1.5-6.8 0-2.7 1.4-4.3 4.1-4.3h16.7c3.2 0 5.4.5 6.6 1.7 1.4 1 2.4 3.4 3.4 6.6l28.6 112.7 26.6-112.7c.9-3.4 1.9-5.6 3.2-6.6 1.4-1 3.7-1.7 6.8-1.7H270c3.2 0 5.4.5 6.8 1.7 1.4 1 2.6 3.4 3.2 6.6l26.9 114.1 29.5-114.1c1-3.4 2.2-5.6 3.4-6.6 1.4-1 3.6-1.7 6.6-1.7h15.8c2.7 0 4.3 1.4 4.3 4.3 0 .9-.2 1.7-.3 2.7-.2 1-.5 2.4-1.2 4.3l-41 131.6q-1.5 5.1-3.6 6.6c-1.4 1-3.6 1.7-6.5 1.7h-14.6c-3.2 0-5.4-.5-6.8-1.7s-2.6-3.4-3.2-6.8l-26.4-109.8L236.7 250c-.9 3.4-1.9 5.6-3.2 6.8-1.4 1.2-3.7 1.7-6.8 1.7zm218.8 4.6c-8.9 0-17.7-1-26.2-3.1-8.5-2-15.2-4.3-19.6-6.8-2.7-1.5-4.6-3.2-5.3-4.8s-1-3.2-1-4.8v-8.7c0-3.6 1.4-5.3 3.9-5.3 1 0 2 .2 3.1.5 1 .3 2.6 1 4.3 1.7 5.8 2.6 12.1 4.6 18.7 6 6.8 1.4 13.5 2 20.3 2 10.7 0 19.1-1.9 24.9-5.6s8.9-9.2 8.9-16.2c0-4.8-1.5-8.7-4.6-11.9s-8.9-6.1-17.2-8.9l-24.7-7.7c-12.4-3.9-21.6-9.7-27.2-17.4-5.6-7.5-8.5-15.8-8.5-24.7 0-7.2 1.5-13.5 4.6-18.9s7.2-10.2 12.3-14c5.1-3.9 10.9-6.8 17.7-8.9 6.8-2 14-2.9 21.5-2.9 3.7 0 7.7.2 11.4.7 3.9.5 7.5 1.2 11.1 1.9 3.4.9 6.6 1.7 9.7 2.7s5.4 2 7.2 3.1c2.4 1.4 4.1 2.7 5.1 4.3 1 1.4 1.5 3.2 1.5 5.6v8c0 3.6-1.4 5.4-3.9 5.4-1.4 0-3.6-.7-6.5-2q-14.55-6.6-32.7-6.6c-9.7 0-17.4 1.5-22.6 4.8s-8 8.2-8 15.2c0 4.8 1.7 8.9 5.1 12.1s9.7 6.5 18.7 9.4l24.2 7.7c12.3 3.9 21.1 9.4 26.4 16.3s7.8 15 7.8 23.8c0 7.3-1.5 14-4.4 19.8-3.1 5.8-7.2 10.9-12.4 15-5.3 4.3-11.6 7.3-18.9 9.5-8 2.5-16 3.7-24.7 3.7\\\"/><path d=\\\"M462.9 345.7c-56 41.4-137.4 63.3-207.4 63.3-98.1 0-186.5-36.3-253.2-96.6-5.3-4.8-.5-11.2 5.8-7.5 72.2 41.9 161.3 67.3 253.4 67.3 62.2 0 130.4-12.9 193.3-39.5 9.3-4.2 17.3 6.2 8.1 13m23.3-26.5c-7.2-9.2-47.3-4.4-65.6-2.2-5.4.7-6.3-4.1-1.4-7.7 32-22.5 84.6-16 90.8-8.5 6.1 7.7-1.7 60.3-31.7 85.5-4.6 3.9-9 1.9-7-3.2 6.9-16.9 22.1-54.9 14.9-63.9\\\" style=\\\"fill-rule:evenodd;clip-rule:evenodd;fill:#f90\\\"/></svg>\",\n    \"selfhst-opnsense\": \"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" xml:space=\\\"preserve\\\" viewBox=\\\"0 0 512 512\\\"><path d=\\\"M512 160.2V400H400V112H112V0h240.4zM112 112H0v240.4L160.2 512H400V400H112z\\\" style=\\\"fill:#de3c07\\\"/></svg>\",\n    \"selfhst-portainer\": \"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" xml:space=\\\"preserve\\\" viewBox=\\\"0 0 512 512\\\"><path d=\\\"M462.2 512H49.8C22.3 512 0 489.7 0 462.2V49.8C0 22.3 22.3 0 49.8 0h412.4C489.7 0 512 22.3 512 49.8v412.4c0 27.5-22.3 49.8-49.8 49.8\\\" style=\\\"fill:#2e2f33\\\"/><path d=\\\"M108.2 63.9h140.9c97.2 0 154.6 30.4 154.6 129.5v3.4c0 99.4-57.2 129.5-154.5 129.5h-30.7V447H108.2zm134.5 177.8c30.1 0 46-11.2 46-44.6v-3.9c0-33.2-15.8-44.6-46-44.6h-24.2v93.1zM313 358h90.3v90.1H313z\\\" style=\\\"fill:#f7f6f3\\\"/></svg>\",\n    \"selfhst-jotty\": \"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" xml:space=\\\"preserve\\\" viewBox=\\\"0 0 512 512\\\"><path d=\\\"M438.6 0H73.4C33.1 0 .3 32.8.3 73.1v365.7c0 40.3 32.8 73.1 73.1 73.1h365.3c40.3 0 73.1-32.8 73.1-73.1V73.1C511.7 32.8 478.9 0 438.6 0\\\" style=\\\"fill-rule:evenodd;clip-rule:evenodd;fill:#8b3bd0\\\"/><path d=\\\"M356.8 53.6v261.3c0 39.3-13.9 72.8-41.8 100.7s-61.4 41.8-100.7 41.8l-23.8-95h23.8c13 0 24.1-4.6 33.2-13.8 9.5-9.5 14.3-20.7 14.3-33.7V148.6H157.3v-95z\\\" style=\\\"fill:#fff\\\"/></svg>\",\n    \"selfhst-authportal\": \"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" xml:space=\\\"preserve\\\" viewBox=\\\"0 0 512 512\\\"><path d=\\\"M0 0h512v512H0z\\\" style=\\\"fill:#111827\\\"/><path d=\\\"M170.7 85.3 369.8 256 170.7 426.7z\\\" style=\\\"fill:#f59e0b\\\"/></svg>\",\n    \"selfhst-docker\": \"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" xml:space=\\\"preserve\\\" viewBox=\\\"0 0 512 512\\\"><path d=\\\"M501.4 212.3c-11.5-8-38-11-58.6-7-2.4-20-13.5-37.5-32.7-53l-11-8-7.7 11.5c-9.6 15-14.4 36-13 56 .5 7 2.9 19.5 10.1 30.5-6.7 4-20.7 9-38.9 9H2.3l-1 4c-3.4 20-3.4 82.5 36 130.5 29.8 36.5 74 55 132.1 55 125.9 0 219.1-60.5 262.8-170 17.3.5 54.3 0 73-37.5.5-1 1.4-3 4.8-10.5l1.9-4zM280 71.3h-52.8v50H280zm0 60h-52.8v50H280zm-62.5 0h-52.8v50h52.8zm-62.4 0h-52.8v50h52.8zm-62.5 60H39.8v50h52.8zm62.5 0h-52.8v50h52.8zm62.4 0h-52.8v50h52.8zm62.5 0h-52.8v50H280zm62.4 0h-52.8v50h52.8z\\\" style=\\\"fill:#2396ed\\\"/></svg>\",\n    \"selfhst-opnsense-v1\": \"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" xml:space=\\\"preserve\\\" viewBox=\\\"0 0 512 512\\\"><path d=\\\"M503.1 465.4v2.1c-6 25.5-28.4 44.5-55 44.5H7.3v-46.6h38.3v7.3h403c1.1 0 2 0 2.9-.1 1.8-.1 3.6-.6 5.2-1.3 2.9-1.3 5.3-3.3 7-5.9zM63.9 0c-26.5 0-49 19-55 44.5V47H48c2.7-4.3 7.3-7.2 12.6-7.6 1-.1 2-.1 3.2-.1h402.6V47h38.3V0z\\\" style=\\\"fill:#898b8d\\\"/><path d=\\\"M466.1 157.7V197H319.5v-39.3zM45.9 315.4v39.3h146.6v-39.3zm0-157.7V197h146.6v-39.3zm273.6 157.7v39.3h146.6v-39.3z\\\" style=\\\"fill:#58595b\\\"/><path d=\\\"M83.8 78.6H428v39.3H83.8zm0 315.9H428v39.3H83.8z\\\" style=\\\"fill:#403f41\\\"/><linearGradient id=\\\"opnsense-v1_svg__a\\\" x1=\\\"-1460.617\\\" x2=\\\"-1375.11\\\" y1=\\\"1248.095\\\" y2=\\\"1248.095\\\" gradientTransform=\\\"matrix(.1853 .356 .6722 -.353 -90.66 993.802)\\\" gradientUnits=\\\"userSpaceOnUse\\\"><stop offset=\\\"0\\\" style=\\\"stop-color:#737373\\\"/><stop offset=\\\"1\\\" style=\\\"stop-color:#333\\\"/></linearGradient><path d=\\\"m466.5 78.6 38.2-20.5.1-39.5-38.3 20.7\\\" style=\\\"fill:url(#opnsense-v1_svg__a)\\\"/><linearGradient id=\\\"opnsense-v1_svg__b\\\" x1=\\\"4.634\\\" x2=\\\"50.301\\\" y1=\\\"462.6\\\" y2=\\\"462.6\\\" gradientTransform=\\\"matrix(1 0 0 -1 0 514)\\\" gradientUnits=\\\"userSpaceOnUse\\\"><stop offset=\\\"0\\\" style=\\\"stop-color:#737373\\\"/><stop offset=\\\"1\\\" style=\\\"stop-color:#333\\\"/></linearGradient><path d=\\\"M45.6 64.5v-8.9c0-5.3 2.6-10 6.5-13l-34-18.5c-6.7 9.5-10.8 21.3-10.9 34l38.3 20.6v-14c.1-.1.1-.2.1-.2\\\" style=\\\"fill:url(#opnsense-v1_svg__b)\\\"/><linearGradient id=\\\"opnsense-v1_svg__c\\\" x1=\\\"-1587.887\\\" x2=\\\"-1498.137\\\" y1=\\\"-2208.165\\\" y2=\\\"-2208.165\\\" gradientTransform=\\\"matrix(-.1853 -.356 -.6722 .353 -1743.836 694.098)\\\" gradientUnits=\\\"userSpaceOnUse\\\"><stop offset=\\\"0\\\" style=\\\"stop-color:#737373\\\"/><stop offset=\\\"1\\\" style=\\\"stop-color:#333\\\"/></linearGradient><path d=\\\"M45.6 472.7v-38.9L7.3 454.5v39.7l39.9-21.5z\\\" style=\\\"fill:url(#opnsense-v1_svg__c)\\\"/><linearGradient id=\\\"opnsense-v1_svg__d\\\" x1=\\\"460.439\\\" x2=\\\"504.436\\\" y1=\\\"53\\\" y2=\\\"53\\\" gradientTransform=\\\"matrix(1 0 0 -1 0 514)\\\" gradientUnits=\\\"userSpaceOnUse\\\"><stop offset=\\\"0\\\" style=\\\"stop-color:#737373\\\"/><stop offset=\\\"1\\\" style=\\\"stop-color:#333\\\"/></linearGradient><path d=\\\"M466.5 456.4c0 5.5-2.7 10.3-6.9 13.2l34.2 18.4c6.8-9.5 10.8-21.2 10.9-33.8L466.5 434v12.8\\\" style=\\\"fill:url(#opnsense-v1_svg__d)\\\"/><linearGradient id=\\\"opnsense-v1_svg__e\\\" x1=\\\"2521.902\\\" x2=\\\"2684.464\\\" y1=\\\"-2476.233\\\" y2=\\\"-2476.233\\\" gradientTransform=\\\"matrix(-1.508 -1.0166 -3.1265 -1.519 -3697.438 -937.952)\\\" gradientUnits=\\\"userSpaceOnUse\\\"><stop offset=\\\".107\\\" style=\\\"stop-color:#58595b\\\"/><stop offset=\\\"1\\\" style=\\\"stop-color:#000\\\"/></linearGradient><path d=\\\"M192.5 197v-.4l-72.4-38.9H45.9v.2l72.5 39.1z\\\" style=\\\"fill:url(#opnsense-v1_svg__e)\\\"/><linearGradient id=\\\"opnsense-v1_svg__f\\\" x1=\\\"-1594.953\\\" x2=\\\"-1432.391\\\" y1=\\\"278.458\\\" y2=\\\"278.458\\\" gradientTransform=\\\"matrix(1.508 -1.0166 3.1265 -1.519 1804.196 -937.952)\\\" gradientUnits=\\\"userSpaceOnUse\\\"><stop offset=\\\".107\\\" style=\\\"stop-color:#58595b\\\"/><stop offset=\\\"1\\\" style=\\\"stop-color:#000\\\"/></linearGradient><path d=\\\"M466.1 157.7h-74l-72.6 39.1v.2h74.2l72.4-38.9z\\\" style=\\\"fill:url(#opnsense-v1_svg__f)\\\"/><linearGradient id=\\\"opnsense-v1_svg__g\\\" x1=\\\"-4327.142\\\" x2=\\\"-4164.581\\\" y1=\\\"1572.387\\\" y2=\\\"1572.387\\\" gradientTransform=\\\"matrix(1.508 1.0166 3.1265 1.519 1863.938 2252.352)\\\" gradientUnits=\\\"userSpaceOnUse\\\"><stop offset=\\\".107\\\" style=\\\"stop-color:#58595b\\\"/><stop offset=\\\"1\\\" style=\\\"stop-color:#000\\\"/></linearGradient><path d=\\\"M466.1 354.7v-.4l-72.4-38.9h-74.2v.2l72.6 39.1z\\\" style=\\\"fill:url(#opnsense-v1_svg__g)\\\"/><linearGradient id=\\\"opnsense-v1_svg__h\\\" x1=\\\"-413.668\\\" x2=\\\"-246.99\\\" y1=\\\"-1046.618\\\" y2=\\\"-1046.618\\\" gradientTransform=\\\"matrix(-1.508 1.0166 -3.1265 1.519 -3638.692 2252.352)\\\" gradientUnits=\\\"userSpaceOnUse\\\"><stop offset=\\\".107\\\" style=\\\"stop-color:#58595b\\\"/><stop offset=\\\"1\\\" style=\\\"stop-color:#000\\\"/></linearGradient><path d=\\\"M192.5 315.4h-74.1l-72.5 39.1v.2h74.2l72.4-38.9z\\\" style=\\\"fill:url(#opnsense-v1_svg__h)\\\"/><linearGradient id=\\\"opnsense-v1_svg__i\\\" x1=\\\"74.725\\\" x2=\\\"261.062\\\" y1=\\\"132.65\\\" y2=\\\"132.65\\\" gradientTransform=\\\"matrix(.9914 0 0 -1 -66.782 260.2)\\\" gradientUnits=\\\"userSpaceOnUse\\\"><stop offset=\\\".237\\\" style=\\\"stop-color:#a6a8ab\\\"/><stop offset=\\\"1\\\" style=\\\"stop-color:#404040\\\"/></linearGradient><path d=\\\"M192.8 157.7V197L7.3 97.4V58.1z\\\" style=\\\"fill:url(#opnsense-v1_svg__i)\\\"/><linearGradient id=\\\"opnsense-v1_svg__j\\\" x1=\\\"-2461.056\\\" x2=\\\"-2274.718\\\" y1=\\\"132.65\\\" y2=\\\"132.65\\\" gradientTransform=\\\"matrix(-.9914 0 0 -1 -1935.19 260.2)\\\" gradientUnits=\\\"userSpaceOnUse\\\"><stop offset=\\\".237\\\" style=\\\"stop-color:#a6a8ab\\\"/><stop offset=\\\"1\\\" style=\\\"stop-color:#404040\\\"/></linearGradient><path d=\\\"M319.2 157.7V197l185.5-99.6V58.1z\\\" style=\\\"fill:url(#opnsense-v1_svg__j)\\\"/><linearGradient id=\\\"opnsense-v1_svg__k\\\" x1=\\\"-2291.845\\\" x2=\\\"-2105.508\\\" y1=\\\"-739.55\\\" y2=\\\"-739.55\\\" gradientTransform=\\\"matrix(-.9914 0 0 1 -1767.435 1124.6)\\\" gradientUnits=\\\"userSpaceOnUse\\\"><stop offset=\\\".237\\\" style=\\\"stop-color:#a6a8ab\\\"/><stop offset=\\\"1\\\" style=\\\"stop-color:#404040\\\"/></linearGradient><path d=\\\"M319.2 355.2v-39.8L504.7 415v39.7z\\\" style=\\\"fill:url(#opnsense-v1_svg__k)\\\"/><linearGradient id=\\\"opnsense-v1_svg__l\\\" x1=\\\"-94.103\\\" x2=\\\"91.934\\\" y1=\\\"-739.55\\\" y2=\\\"-739.55\\\" gradientTransform=\\\"matrix(.9914 0 0 1 100.993 1124.6)\\\" gradientUnits=\\\"userSpaceOnUse\\\"><stop offset=\\\".237\\\" style=\\\"stop-color:#a6a8ab\\\"/><stop offset=\\\"1\\\" style=\\\"stop-color:#404040\\\"/></linearGradient><path d=\\\"M192.8 355.2v-39.8L7.3 415v39.7z\\\" style=\\\"fill:url(#opnsense-v1_svg__l)\\\"/><path d=\\\"m319.2 276.1 108.9 58.5v-39.7l-35.3-18.8h111.9v-39.8H392.8l35.3-18.8v-39.3l-108.9 58.1zM83.8 334.6l109-58.5v-39.8l-109-58.1v39.3l35.7 18.8H7.3v39.8h111.9l-35.3 18.8v39.7z\\\" style=\\\"fill:#e24525\\\"/></svg>\",\n    \"mdi-server-security\": \"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" id=\\\"mdi-server-security\\\" viewBox=\\\"0 0 24 24\\\"><path d=\\\"M3,1H19A1,1 0 0,1 20,2V6A1,1 0 0,1 19,7H3A1,1 0 0,1 2,6V2A1,1 0 0,1 3,1M3,9H19A1,1 0 0,1 20,10V10.67L17.5,9.56L11,12.44V15H3A1,1 0 0,1 2,14V10A1,1 0 0,1 3,9M3,17H11C11.06,19.25 12,21.4 13.46,23H3A1,1 0 0,1 2,22V18A1,1 0 0,1 3,17M8,5H9V3H8V5M8,13H9V11H8V13M8,21H9V19H8V21M4,3V5H6V3H4M4,11V13H6V11H4M4,19V21H6V19H4M17.5,12L22,14V17C22,19.78 20.08,22.37 17.5,23C14.92,22.37 13,19.78 13,17V14L17.5,12M17.5,13.94L15,15.06V17.72C15,19.26 16.07,20.7 17.5,21.06V13.94Z\\\" /></svg>\"\n  },\n  \"page\": {\n    \"title\": \"The One File Corporate\",\n    \"background\": \"\",\n    \"topbarBg\": \"rgba(9, 12, 20, 0.9)\",\n    \"topbarBorder\": \"#1f2533\",\n    \"panel\": \"#0b0e13\",\n    \"panelAlt\": \"#10141b\",\n    \"accent\": \"#4fd1c5\",\n    \"sidebarBg\": \"#10141b\",\n    \"btnBg\": \"#0b0e13\",\n    \"btnText\": \"#e2e8f0\",\n    \"tagFill\": \"#1e293b\",\n    \"tagText\": \"#e2e8f0\",\n    \"tagBorder\": \"#475569\",\n    \"inputBg\": \"#0b0e13\",\n    \"inputText\": \"#e2e8f0\",\n    \"inputBorder\": \"#1f2937\",\n    \"inputFont\": \"Inter, system-ui, sans-serif\",\n    \"inputFontSize\": 14,\n    \"toolbarBg\": \"#0f172a\",\n    \"toolbarBorder\": \"#1f2937\",\n    \"toolbarText\": \"#94a3b8\",\n    \"toolbarBtnBg\": \"#0b0e13\",\n    \"toolbarBtnText\": \"#e2e8f0\",\n    \"minimapDots\": \"#94a3b8\",\n    \"canvasHintEnabled\": true,\n    \"canvasHintText\": \"\",\n    \"canvasHintBg\": \"#0f172a\",\n    \"canvasHintColor\": \"#94a3b8\",\n    \"danger\": \"#f56565\",\n    \"textMain\": \"#e2e8f0\",\n    \"textSoft\": \"#94a3b8\",\n    \"topbarHeight\": 103,\n    \"sidebarWidth\": 350,\n    \"mobileFooterHeight\": 40,\n    \"sidebarCollapsed\": false,\n    \"nodeFill\": \"#1e293b\",\n    \"nodeStroke\": \"#475569\",\n    \"nodeTitle\": \"#e2e8f0\",\n    \"nodeSub\": \"#94a3b8\",\n    \"nodeTitleSize\": 41,\n    \"nodeSubSize\": 27,\n    \"nodeFont\": \"monospace\",\n    \"defaultEdge\": \"#475569\",\n    \"selectionHandle\": \"#f59e0b\",\n    \"selectionHandleSize\": 8,\n    \"groupIndicator\": \"#4fd1c5\",\n    \"canvasGradientTop\": \"#1e2532\",\n    \"canvasGradientBottom\": \"#050608\",\n    \"canvasBorder\": \"#475569\",\n    \"canvasGrid\": \"#475569\",\n    \"canvasGridSize\": 50,\n    \"canvasGridEnabled\": true,\n    \"rackFrameFill\": \"#0f172a\",\n    \"rackFrameStroke\": \"#4fd1c5\",\n    \"rackLineColor\": \"#475569\",\n    \"rackTextColor\": \"#4fd1c5\",\n    \"rackGridEnabled\": true,\n    \"viewOnly\": false,\n    \"defaultEdgeRouting\": \"orthogonal\",\n    \"animateConnections\": false,\n    \"animationStyle\": \"arrows\",\n    \"animationDirection\": \"all\",\n    \"animationSpeed\": 4,\n    \"autoPingEnabled\": false,\n    \"autoPingInterval\": 30\n  },\n  \"autoPingEnabled\": false,\n  \"autoPingInterval\": 30,\n  \"canvas\": {\n    \"zoom\": 0.9921985961590549,\n    \"panX\": -5.584863670202822,\n    \"panY\": -99.90831573327841\n  },\n  \"savedTopologyView\": {\n    \"zoom\": 0.9325110211947125,\n    \"panX\": -563.7108933103631,\n    \"panY\": -561.6887674556383\n  },\n  \"documentTabs\": [\n    {\n      \"id\": \"main\",\n      \"name\": \"Corporate Site B\",\n      \"nodes\": {\n        \"core-router-1\": {\n          \"shape\": \"router\",\n          \"name\": \"Core Router 1\",\n          \"ip\": \"10.0.0.1\",\n          \"role\": \"Core Routing\",\n          \"tags\": [\n            \"core\",\n            \"tier-1\",\n            \"redundant\"\n          ],\n          \"notes\": [\n            \"Primary core router\",\n            \"BGP peering enabled\"\n          ],\n          \"mac\": \"00:1A:2B:3C:4D:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"core-router-2\": {\n          \"shape\": \"router\",\n          \"name\": \"Core Router 2\",\n          \"ip\": \"10.0.0.2\",\n          \"role\": \"Core Routing\",\n          \"tags\": [\n            \"core\",\n            \"tier-1\",\n            \"redundant\"\n          ],\n          \"notes\": [\n            \"Secondary core router\",\n            \"HSRP standby\"\n          ],\n          \"mac\": \"00:1A:2B:3C:4D:02\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null,\n          \"ping\": {\n            \"enabled\": true,\n            \"protocol\": \"custom\",\n            \"customUrl\": \"https://google.com\",\n            \"timeout\": 3000,\n            \"status\": \"online\",\n            \"lastCheck\": \"2025-12-09T00:15:04.343Z\"\n          }\n        },\n        \"fw-external-1\": {\n          \"shape\": \"firewall\",\n          \"name\": \"External FW 1\",\n          \"ip\": \"10.0.1.1\",\n          \"role\": \"Perimeter Security\",\n          \"tags\": [\n            \"security\",\n            \"perimeter\",\n            \"ha-pair\"\n          ],\n          \"notes\": [\n            \"Palo Alto PA-5250\",\n            \"Active node\"\n          ],\n          \"mac\": \"00:1A:2B:3C:4D:10\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"fw-external-2\": {\n          \"shape\": \"firewall\",\n          \"name\": \"External FW 2\",\n          \"ip\": \"10.0.1.2\",\n          \"role\": \"Perimeter Security\",\n          \"tags\": [\n            \"security\",\n            \"perimeter\",\n            \"ha-pair\"\n          ],\n          \"notes\": [\n            \"Palo Alto PA-5250\",\n            \"Passive node\"\n          ],\n          \"mac\": \"00:1A:2B:3C:4D:11\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"fw-internal\": {\n          \"shape\": \"firewall\",\n          \"name\": \"Internal FW\",\n          \"ip\": \"10.0.2.1\",\n          \"role\": \"Internal Segmentation\",\n          \"tags\": [\n            \"security\",\n            \"internal\"\n          ],\n          \"notes\": [\n            \"East-West traffic inspection\"\n          ],\n          \"mac\": \"00:1A:2B:3C:4D:12\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"core-switch-1\": {\n          \"shape\": \"switch\",\n          \"name\": \"Core Switch 1\",\n          \"ip\": \"10.0.10.1\",\n          \"role\": \"Core Switching\",\n          \"tags\": [\n            \"core\",\n            \"layer3\",\n            \"redundant\"\n          ],\n          \"notes\": [\n            \"Cisco Nexus 9000\",\n            \"VPC Domain 1\"\n          ],\n          \"mac\": \"00:1A:2B:3C:4D:20\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"core-switch-2\": {\n          \"shape\": \"switch\",\n          \"name\": \"Core Switch 2\",\n          \"ip\": \"10.0.10.2\",\n          \"role\": \"Core Switching\",\n          \"tags\": [\n            \"core\",\n            \"layer3\",\n            \"redundant\"\n          ],\n          \"notes\": [\n            \"Cisco Nexus 9000\",\n            \"VPC Domain 1\"\n          ],\n          \"mac\": \"00:1A:2B:3C:4D:21\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dc-rack-a1\": {\n          \"shape\": \"server\",\n          \"name\": \"DC Rack A1\",\n          \"ip\": \"10.10.0.0/24\",\n          \"role\": \"Data Center Rack\",\n          \"tags\": [\n            \"datacenter\",\n            \"row-a\",\n            \"production\"\n          ],\n          \"notes\": [\n            \"Row A, Position 1\",\n            \"Primary compute\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": true,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dc-rack-a2\": {\n          \"shape\": \"server\",\n          \"name\": \"DC Rack A2\",\n          \"ip\": \"10.10.1.0/24\",\n          \"role\": \"Data Center Rack\",\n          \"tags\": [\n            \"datacenter\",\n            \"row-a\",\n            \"production\"\n          ],\n          \"notes\": [\n            \"Row A, Position 2\",\n            \"Primary compute\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": true,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dc-rack-b1\": {\n          \"shape\": \"server\",\n          \"name\": \"DC Rack B1\",\n          \"ip\": \"10.10.2.0/24\",\n          \"role\": \"Data Center Rack\",\n          \"tags\": [\n            \"datacenter\",\n            \"row-b\",\n            \"storage\"\n          ],\n          \"notes\": [\n            \"Row B, Position 1\",\n            \"Storage systems\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": true,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dc-rack-b2\": {\n          \"shape\": \"server\",\n          \"name\": \"DC Rack B2\",\n          \"ip\": \"10.10.3.0/24\",\n          \"role\": \"Data Center Rack\",\n          \"tags\": [\n            \"datacenter\",\n            \"row-b\",\n            \"storage\"\n          ],\n          \"notes\": [\n            \"Row B, Position 2\",\n            \"Storage systems\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": true,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dmz-rack\": {\n          \"shape\": \"server\",\n          \"name\": \"DMZ Rack\",\n          \"ip\": \"172.16.0.0/24\",\n          \"role\": \"DMZ Infrastructure\",\n          \"tags\": [\n            \"dmz\",\n            \"security\",\n            \"public-facing\",\n            {\n              \"type\": \"icon\",\n              \"library\": \"selfhst\",\n              \"name\": \"booklogr\"\n            },\n            {\n              \"type\": \"icon\",\n              \"library\": \"simple\",\n              \"name\": \"gmail\"\n            }\n          ],\n          \"notes\": [\n            \"Isolated DMZ zone\",\n            \"Public-facing services\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": true,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"mgmt-rack\": {\n          \"shape\": \"server\",\n          \"name\": \"Management Rack\",\n          \"ip\": \"192.168.100.0/24\",\n          \"role\": \"Management Infrastructure\",\n          \"tags\": [\n            \"management\",\n            \"oob\",\n            \"noc\"\n          ],\n          \"notes\": [\n            \"Out-of-band management\",\n            \"NOC equipment\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"logical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": true,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"esxi-host-01\": {\n          \"shape\": \"server\",\n          \"name\": \"ESXi Host 01\",\n          \"ip\": \"10.10.0.11\",\n          \"role\": \"Hypervisor\",\n          \"tags\": [\n            \"vmware\",\n            \"compute\",\n            \"cluster-a\"\n          ],\n          \"notes\": [\n            \"Dell PowerEdge R750\",\n            \"512GB RAM\",\n            \"vSphere 8.0\"\n          ],\n          \"mac\": \"00:50:56:AA:01:01\",\n          \"rackUnit\": 38,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"esxi-host-02\": {\n          \"shape\": \"server\",\n          \"name\": \"ESXi Host 02\",\n          \"ip\": \"10.10.0.12\",\n          \"role\": \"Hypervisor\",\n          \"tags\": [\n            \"vmware\",\n            \"compute\",\n            \"cluster-a\"\n          ],\n          \"notes\": [\n            \"Dell PowerEdge R750\",\n            \"512GB RAM\",\n            \"vSphere 8.0\"\n          ],\n          \"mac\": \"00:50:56:AA:01:02\",\n          \"rackUnit\": 35,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"esxi-host-03\": {\n          \"shape\": \"server\",\n          \"name\": \"ESXi Host 03\",\n          \"ip\": \"10.10.0.13\",\n          \"role\": \"Hypervisor\",\n          \"tags\": [\n            \"vmware\",\n            \"compute\",\n            \"cluster-a\"\n          ],\n          \"notes\": [\n            \"Dell PowerEdge R750\",\n            \"512GB RAM\",\n            \"vSphere 8.0\"\n          ],\n          \"mac\": \"00:50:56:AA:01:03\",\n          \"rackUnit\": 32,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"esxi-host-04\": {\n          \"shape\": \"server\",\n          \"name\": \"ESXi Host 04\",\n          \"ip\": \"10.10.0.14\",\n          \"role\": \"Hypervisor\",\n          \"tags\": [\n            \"vmware\",\n            \"compute\",\n            \"cluster-a\"\n          ],\n          \"notes\": [\n            \"Dell PowerEdge R750\",\n            \"512GB RAM\",\n            \"vSphere 8.0\"\n          ],\n          \"mac\": \"00:50:56:AA:01:04\",\n          \"rackUnit\": 29,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"tor-switch-a1\": {\n          \"shape\": \"switch\",\n          \"name\": \"ToR Switch A1\",\n          \"ip\": \"10.10.0.1\",\n          \"role\": \"Top of Rack\",\n          \"tags\": [\n            \"tor\",\n            \"access\",\n            \"rack-a1\"\n          ],\n          \"notes\": [\n            \"Cisco Nexus 93180YC-FX\",\n            \"48x25G ports\"\n          ],\n          \"mac\": \"00:1A:2B:3C:5D:01\",\n          \"rackUnit\": 42,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"esxi-host-05\": {\n          \"shape\": \"server\",\n          \"name\": \"ESXi Host 05\",\n          \"ip\": \"10.10.1.11\",\n          \"role\": \"Hypervisor\",\n          \"tags\": [\n            \"vmware\",\n            \"compute\",\n            \"cluster-b\"\n          ],\n          \"notes\": [\n            \"Dell PowerEdge R750\",\n            \"768GB RAM\",\n            \"vSphere 8.0\"\n          ],\n          \"mac\": \"00:50:56:AA:02:01\",\n          \"rackUnit\": 38,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"esxi-host-06\": {\n          \"shape\": \"server\",\n          \"name\": \"ESXi Host 06\",\n          \"ip\": \"10.10.1.12\",\n          \"role\": \"Hypervisor\",\n          \"tags\": [\n            \"vmware\",\n            \"compute\",\n            \"cluster-b\"\n          ],\n          \"notes\": [\n            \"Dell PowerEdge R750\",\n            \"768GB RAM\",\n            \"vSphere 8.0\"\n          ],\n          \"mac\": \"00:50:56:AA:02:02\",\n          \"rackUnit\": 35,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"esxi-host-07\": {\n          \"shape\": \"server\",\n          \"name\": \"ESXi Host 07\",\n          \"ip\": \"10.10.1.13\",\n          \"role\": \"Hypervisor\",\n          \"tags\": [\n            \"vmware\",\n            \"compute\",\n            \"cluster-b\"\n          ],\n          \"notes\": [\n            \"Dell PowerEdge R750\",\n            \"768GB RAM\",\n            \"vSphere 8.0\"\n          ],\n          \"mac\": \"00:50:56:AA:02:03\",\n          \"rackUnit\": 32,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"esxi-host-08\": {\n          \"shape\": \"server\",\n          \"name\": \"ESXi Host 08\",\n          \"ip\": \"10.10.1.14\",\n          \"role\": \"Hypervisor\",\n          \"tags\": [\n            \"vmware\",\n            \"compute\",\n            \"cluster-b\"\n          ],\n          \"notes\": [\n            \"Dell PowerEdge R750\",\n            \"768GB RAM\",\n            \"vSphere 8.0\"\n          ],\n          \"mac\": \"00:50:56:AA:02:04\",\n          \"rackUnit\": 29,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"tor-switch-a2\": {\n          \"shape\": \"switch\",\n          \"name\": \"ToR Switch A2\",\n          \"ip\": \"10.10.1.1\",\n          \"role\": \"Top of Rack\",\n          \"tags\": [\n            \"tor\",\n            \"access\",\n            \"rack-a2\"\n          ],\n          \"notes\": [\n            \"Cisco Nexus 93180YC-FX\",\n            \"48x25G ports\"\n          ],\n          \"mac\": \"00:1A:2B:3C:5D:02\",\n          \"rackUnit\": 42,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"san-primary\": {\n          \"shape\": \"database\",\n          \"name\": \"SAN Primary\",\n          \"ip\": \"10.10.2.10\",\n          \"role\": \"Primary Storage\",\n          \"tags\": [\n            \"storage\",\n            \"san\",\n            \"netapp\"\n          ],\n          \"notes\": [\n            \"NetApp AFF A400\",\n            \"500TB Raw\",\n            \"FC 32Gb\"\n          ],\n          \"mac\": \"00:A0:98:AA:01:01\",\n          \"rackUnit\": 36,\n          \"uHeight\": \"6\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-b1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"san-secondary\": {\n          \"shape\": \"database\",\n          \"name\": \"SAN Secondary\",\n          \"ip\": \"10.10.2.11\",\n          \"role\": \"Secondary Storage\",\n          \"tags\": [\n            \"storage\",\n            \"san\",\n            \"netapp\"\n          ],\n          \"notes\": [\n            \"NetApp AFF A400\",\n            \"500TB Raw\",\n            \"FC 32Gb\"\n          ],\n          \"mac\": \"00:A0:98:AA:01:02\",\n          \"rackUnit\": 28,\n          \"uHeight\": \"6\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-b1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"fc-switch-1\": {\n          \"shape\": \"switch\",\n          \"name\": \"FC Switch 1\",\n          \"ip\": \"10.10.2.1\",\n          \"role\": \"Fibre Channel\",\n          \"tags\": [\n            \"storage\",\n            \"fc\",\n            \"fabric-a\"\n          ],\n          \"notes\": [\n            \"Brocade G620\",\n            \"Fabric A\"\n          ],\n          \"mac\": \"00:1A:2B:FC:01:01\",\n          \"rackUnit\": 42,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-b1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"fc-switch-2\": {\n          \"shape\": \"switch\",\n          \"name\": \"FC Switch 2\",\n          \"ip\": \"10.10.2.2\",\n          \"role\": \"Fibre Channel\",\n          \"tags\": [\n            \"storage\",\n            \"fc\",\n            \"fabric-b\"\n          ],\n          \"notes\": [\n            \"Brocade G620\",\n            \"Fabric B\"\n          ],\n          \"mac\": \"00:1A:2B:FC:01:02\",\n          \"rackUnit\": 41,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-b1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"backup-server-1\": {\n          \"shape\": \"server\",\n          \"name\": \"Backup Server 1\",\n          \"ip\": \"10.10.3.10\",\n          \"role\": \"Backup Infrastructure\",\n          \"tags\": [\n            \"backup\",\n            \"veeam\",\n            \"protection\"\n          ],\n          \"notes\": [\n            \"Veeam Backup Server\",\n            \"Dell R740xd\",\n            \"200TB\"\n          ],\n          \"mac\": \"00:50:56:BB:01:01\",\n          \"rackUnit\": 36,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-b2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"backup-server-2\": {\n          \"shape\": \"server\",\n          \"name\": \"Backup Server 2\",\n          \"ip\": \"10.10.3.11\",\n          \"role\": \"Backup Infrastructure\",\n          \"tags\": [\n            \"backup\",\n            \"veeam\",\n            \"protection\"\n          ],\n          \"notes\": [\n            \"Veeam Backup Server\",\n            \"Dell R740xd\",\n            \"200TB\"\n          ],\n          \"mac\": \"00:50:56:BB:01:02\",\n          \"rackUnit\": 33,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-b2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"tape-library\": {\n          \"shape\": \"database\",\n          \"name\": \"Tape Library\",\n          \"ip\": \"10.10.3.20\",\n          \"role\": \"Archival Storage\",\n          \"tags\": [\n            \"backup\",\n            \"tape\",\n            \"lto9\"\n          ],\n          \"notes\": [\n            \"IBM TS4500\",\n            \"LTO-9\",\n            \"Long-term archive\"\n          ],\n          \"mac\": \"00:50:56:BB:02:01\",\n          \"rackUnit\": 20,\n          \"uHeight\": \"10\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-b2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"tor-switch-b1\": {\n          \"shape\": \"switch\",\n          \"name\": \"ToR Switch B1\",\n          \"ip\": \"10.10.2.3\",\n          \"role\": \"Top of Rack\",\n          \"tags\": [\n            \"tor\",\n            \"access\",\n            \"rack-b1\"\n          ],\n          \"notes\": [\n            \"Cisco Nexus 93180YC-FX\"\n          ],\n          \"mac\": \"00:1A:2B:3C:5D:03\",\n          \"rackUnit\": 40,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-b1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"tor-switch-b2\": {\n          \"shape\": \"switch\",\n          \"name\": \"ToR Switch B2\",\n          \"ip\": \"10.10.3.1\",\n          \"role\": \"Top of Rack\",\n          \"tags\": [\n            \"tor\",\n            \"access\",\n            \"rack-b2\"\n          ],\n          \"notes\": [\n            \"Cisco Nexus 93180YC-FX\"\n          ],\n          \"mac\": \"00:1A:2B:3C:5D:04\",\n          \"rackUnit\": 42,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-b2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"web-server-1\": {\n          \"shape\": \"server\",\n          \"name\": \"Web Server 1\",\n          \"ip\": \"172.16.0.11\",\n          \"role\": \"Web Frontend\",\n          \"tags\": [\n            \"dmz\",\n            \"web\",\n            \"nginx\"\n          ],\n          \"notes\": [\n            \"NGINX reverse proxy\",\n            \"Public facing\"\n          ],\n          \"mac\": \"00:50:56:CC:01:01\",\n          \"rackUnit\": 20,\n          \"uHeight\": \"1\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"dmz-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"web-server-2\": {\n          \"shape\": \"server\",\n          \"name\": \"Web Server 2\",\n          \"ip\": \"172.16.0.12\",\n          \"role\": \"Web Frontend\",\n          \"tags\": [\n            \"dmz\",\n            \"web\",\n            \"nginx\"\n          ],\n          \"notes\": [\n            \"NGINX reverse proxy\",\n            \"Public facing\"\n          ],\n          \"mac\": \"00:50:56:CC:01:02\",\n          \"rackUnit\": 18,\n          \"uHeight\": \"1\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"dmz-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"waf-1\": {\n          \"shape\": \"firewall\",\n          \"name\": \"WAF Appliance\",\n          \"ip\": \"172.16.0.5\",\n          \"role\": \"Web Application Firewall\",\n          \"tags\": [\n            \"dmz\",\n            \"security\",\n            \"waf\"\n          ],\n          \"notes\": [\n            \"F5 BIG-IP ASM\",\n            \"OWASP protection\"\n          ],\n          \"mac\": \"00:50:56:CC:02:01\",\n          \"rackUnit\": 22,\n          \"uHeight\": \"2\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"dmz-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"load-balancer-dmz\": {\n          \"shape\": \"switch\",\n          \"name\": \"DMZ Load Balancer\",\n          \"ip\": \"172.16.0.3\",\n          \"role\": \"Load Balancing\",\n          \"tags\": [\n            \"dmz\",\n            \"lb\",\n            \"f5\"\n          ],\n          \"notes\": [\n            \"F5 BIG-IP LTM\",\n            \"VIP: 172.16.0.100\"\n          ],\n          \"mac\": \"00:50:56:CC:03:01\",\n          \"rackUnit\": 16,\n          \"uHeight\": \"2\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"dmz-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"mail-gateway\": {\n          \"shape\": \"server\",\n          \"name\": \"Mail Gateway\",\n          \"ip\": \"172.16.0.25\",\n          \"role\": \"Email Security\",\n          \"tags\": [\n            \"dmz\",\n            \"email\",\n            \"security\"\n          ],\n          \"notes\": [\n            \"Proofpoint Email Gateway\",\n            \"Spam/malware filtering\"\n          ],\n          \"mac\": \"00:50:56:CC:04:01\",\n          \"rackUnit\": 14,\n          \"uHeight\": \"1\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"dmz-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dns-external-1\": {\n          \"shape\": \"circle\",\n          \"name\": \"External DNS 1\",\n          \"ip\": \"172.16.0.53\",\n          \"role\": \"External DNS\",\n          \"tags\": [\n            \"dmz\",\n            \"dns\",\n            \"public\"\n          ],\n          \"notes\": [\n            \"BIND DNS\",\n            \"Authoritative for corp.com\"\n          ],\n          \"mac\": \"00:50:56:CC:05:01\",\n          \"rackUnit\": 12,\n          \"uHeight\": \"1\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"dmz-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dns-external-2\": {\n          \"shape\": \"circle\",\n          \"name\": \"External DNS 2\",\n          \"ip\": \"172.16.0.54\",\n          \"role\": \"External DNS\",\n          \"tags\": [\n            \"dmz\",\n            \"dns\",\n            \"public\"\n          ],\n          \"notes\": [\n            \"BIND DNS\",\n            \"Secondary for corp.com\"\n          ],\n          \"mac\": \"00:50:56:CC:05:02\",\n          \"rackUnit\": 10,\n          \"uHeight\": \"1\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"dmz-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"vcenter\": {\n          \"shape\": \"server\",\n          \"name\": \"vCenter Server\",\n          \"ip\": \"192.168.100.10\",\n          \"role\": \"Virtualization Management\",\n          \"tags\": [\n            \"management\",\n            \"vmware\",\n            \"vcsa\"\n          ],\n          \"notes\": [\n            \"vCenter Server Appliance 8.0\",\n            \"Single SSO domain\"\n          ],\n          \"mac\": \"00:50:56:DD:01:01\",\n          \"rackUnit\": 20,\n          \"uHeight\": \"2\",\n          \"layer\": \"logical\",\n          \"assignedRack\": \"mgmt-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"nsx-manager\": {\n          \"shape\": \"server\",\n          \"name\": \"NSX Manager\",\n          \"ip\": \"192.168.100.15\",\n          \"role\": \"Network Virtualization\",\n          \"tags\": [\n            \"management\",\n            \"vmware\",\n            \"nsx\"\n          ],\n          \"notes\": [\n            \"NSX-T 4.1 Manager Cluster\"\n          ],\n          \"mac\": \"00:50:56:DD:02:01\",\n          \"rackUnit\": 17,\n          \"uHeight\": \"2\",\n          \"layer\": \"logical\",\n          \"assignedRack\": \"mgmt-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"siem-server\": {\n          \"shape\": \"server\",\n          \"name\": \"SIEM Server\",\n          \"ip\": \"192.168.100.50\",\n          \"role\": \"Security Monitoring\",\n          \"tags\": [\n            \"management\",\n            \"security\",\n            \"splunk\"\n          ],\n          \"notes\": [\n            \"Splunk Enterprise\",\n            \"Security monitoring\"\n          ],\n          \"mac\": \"00:50:56:DD:03:01\",\n          \"rackUnit\": 14,\n          \"uHeight\": \"2\",\n          \"layer\": \"logical\",\n          \"assignedRack\": \"mgmt-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"nms-server\": {\n          \"shape\": \"server\",\n          \"name\": \"Network Monitoring\",\n          \"ip\": \"192.168.100.60\",\n          \"role\": \"Network Management\",\n          \"tags\": [\n            \"management\",\n            \"monitoring\",\n            \"prtg\"\n          ],\n          \"notes\": [\n            \"PRTG Network Monitor\",\n            \"5000 sensors\"\n          ],\n          \"mac\": \"00:50:56:DD:04:01\",\n          \"rackUnit\": 11,\n          \"uHeight\": \"1\",\n          \"layer\": \"logical\",\n          \"assignedRack\": \"mgmt-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"jump-server\": {\n          \"shape\": \"server\",\n          \"name\": \"Jump Server\",\n          \"ip\": \"192.168.100.100\",\n          \"role\": \"Bastion Host\",\n          \"tags\": [\n            \"management\",\n            \"security\",\n            \"bastion\"\n          ],\n          \"notes\": [\n            \"Windows Server 2022\",\n            \"MFA enabled\"\n          ],\n          \"mac\": \"00:50:56:DD:05:01\",\n          \"rackUnit\": 9,\n          \"uHeight\": \"1\",\n          \"layer\": \"logical\",\n          \"assignedRack\": \"mgmt-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"ipam-server\": {\n          \"shape\": \"server\",\n          \"name\": \"IPAM/DDI\",\n          \"ip\": \"192.168.100.70\",\n          \"role\": \"IP Management\",\n          \"tags\": [\n            \"management\",\n            \"dns\",\n            \"dhcp\"\n          ],\n          \"notes\": [\n            \"Infoblox DDI\",\n            \"DNS/DHCP/IPAM\"\n          ],\n          \"mac\": \"00:50:56:DD:06:01\",\n          \"rackUnit\": 7,\n          \"uHeight\": \"2\",\n          \"layer\": \"logical\",\n          \"assignedRack\": \"mgmt-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"wlc-primary\": {\n          \"shape\": \"wifi\",\n          \"name\": \"WLC Primary\",\n          \"ip\": \"10.20.0.1\",\n          \"role\": \"Wireless Controller\",\n          \"tags\": [\n            \"wireless\",\n            \"cisco\",\n            \"9800\"\n          ],\n          \"notes\": [\n            \"Cisco C9800-40\",\n            \"Primary controller\"\n          ],\n          \"mac\": \"00:1A:2B:WL:01:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"wlc-secondary\": {\n          \"shape\": \"wifi\",\n          \"name\": \"WLC Secondary\",\n          \"ip\": \"10.20.0.2\",\n          \"role\": \"Wireless Controller\",\n          \"tags\": [\n            \"wireless\",\n            \"cisco\",\n            \"9800\"\n          ],\n          \"notes\": [\n            \"Cisco C9800-40\",\n            \"HA Secondary\"\n          ],\n          \"mac\": \"00:1A:2B:WL:01:02\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"mobile-zone-hq\": {\n          \"shape\": \"phone\",\n          \"name\": \"HQ Mobile Zone\",\n          \"ip\": \"10.20.10.0/24\",\n          \"role\": \"Mobile Device Zone\",\n          \"tags\": [\n            \"wireless\",\n            \"byod\",\n            \"mobile\"\n          ],\n          \"notes\": [\n            \"Corporate BYOD\",\n            \"MDM enrolled devices\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"mobile-zone-guest\": {\n          \"shape\": \"phone\",\n          \"name\": \"Guest WiFi Zone\",\n          \"ip\": \"10.30.0.0/24\",\n          \"role\": \"Guest Network\",\n          \"tags\": [\n            \"wireless\",\n            \"guest\",\n            \"isolated\"\n          ],\n          \"notes\": [\n            \"Captive portal\",\n            \"Internet only\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"mobile-zone-iot\": {\n          \"shape\": \"phone\",\n          \"name\": \"IoT Device Zone\",\n          \"ip\": \"10.40.0.0/24\",\n          \"role\": \"IoT Network\",\n          \"tags\": [\n            \"wireless\",\n            \"iot\",\n            \"building\"\n          ],\n          \"notes\": [\n            \"Building automation\",\n            \"Smart devices\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"branch-router-ny\": {\n          \"shape\": \"router\",\n          \"name\": \"NYC Branch Router\",\n          \"ip\": \"10.100.0.1\",\n          \"role\": \"Branch Gateway\",\n          \"tags\": [\n            \"branch\",\n            \"nyc\",\n            \"sd-wan\"\n          ],\n          \"notes\": [\n            \"Cisco Viptela vEdge\",\n            \"SD-WAN enabled\"\n          ],\n          \"mac\": \"00:1A:2B:BR:01:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"branch-router-la\": {\n          \"shape\": \"router\",\n          \"name\": \"LA Branch Router\",\n          \"ip\": \"10.101.0.1\",\n          \"role\": \"Branch Gateway\",\n          \"tags\": [\n            \"branch\",\n            \"la\",\n            \"sd-wan\"\n          ],\n          \"notes\": [\n            \"Cisco Viptela vEdge\",\n            \"SD-WAN enabled\"\n          ],\n          \"mac\": \"00:1A:2B:BR:02:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"branch-router-chi\": {\n          \"shape\": \"router\",\n          \"name\": \"Chicago Branch Router\",\n          \"ip\": \"10.102.0.1\",\n          \"role\": \"Branch Gateway\",\n          \"tags\": [\n            \"branch\",\n            \"chicago\",\n            \"sd-wan\"\n          ],\n          \"notes\": [\n            \"Cisco Viptela vEdge\",\n            \"SD-WAN enabled\"\n          ],\n          \"mac\": \"00:1A:2B:BR:03:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"branch-router-lon\": {\n          \"shape\": \"router\",\n          \"name\": \"London Branch Router\",\n          \"ip\": \"10.200.0.1\",\n          \"role\": \"Branch Gateway\",\n          \"tags\": [\n            \"branch\",\n            \"london\",\n            \"sd-wan\"\n          ],\n          \"notes\": [\n            \"Cisco Viptela vEdge\",\n            \"EMEA region\"\n          ],\n          \"mac\": \"00:1A:2B:BR:04:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"branch-router-tokyo\": {\n          \"shape\": \"router\",\n          \"name\": \"Tokyo Branch Router\",\n          \"ip\": \"10.201.0.1\",\n          \"role\": \"Branch Gateway\",\n          \"tags\": [\n            \"branch\",\n            \"tokyo\",\n            \"sd-wan\"\n          ],\n          \"notes\": [\n            \"Cisco Viptela vEdge\",\n            \"APAC region\"\n          ],\n          \"mac\": \"00:1A:2B:BR:05:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"cloud-aws\": {\n          \"shape\": \"cloud\",\n          \"name\": \"AWS Cloud\",\n          \"ip\": \"vpc-0a1b2c3d\",\n          \"role\": \"Public Cloud\",\n          \"tags\": [\n            \"cloud\",\n            \"aws\",\n            \"hybrid\"\n          ],\n          \"notes\": [\n            \"AWS US-East-1\",\n            \"VPC peering to HQ\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"logical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"cloud-azure\": {\n          \"shape\": \"cloud\",\n          \"name\": \"Azure Cloud\",\n          \"ip\": \"vnet-corp-prod\",\n          \"role\": \"Public Cloud\",\n          \"tags\": [\n            \"cloud\",\n            \"azure\",\n            \"hybrid\"\n          ],\n          \"notes\": [\n            \"Azure East US 2\",\n            \"ExpressRoute\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"logical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"cloud-gcp\": {\n          \"shape\": \"cloud\",\n          \"name\": \"GCP Cloud\",\n          \"ip\": \"vpc-gcp-corp\",\n          \"role\": \"Public Cloud\",\n          \"tags\": [\n            \"cloud\",\n            \"gcp\",\n            \"dev\"\n          ],\n          \"notes\": [\n            \"GCP us-central1\",\n            \"Dev/Test workloads\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"logical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"isp-primary\": {\n          \"shape\": \"globe\",\n          \"name\": \"ISP Primary\",\n          \"ip\": \"203.0.113.1\",\n          \"role\": \"Internet Uplink\",\n          \"tags\": [\n            \"wan\",\n            \"internet\",\n            \"primary\"\n          ],\n          \"notes\": [\n            \"AT&T MPLS\",\n            \"1 Gbps dedicated\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"isp-secondary\": {\n          \"shape\": \"globe\",\n          \"name\": \"ISP Secondary\",\n          \"ip\": \"198.51.100.1\",\n          \"role\": \"Internet Uplink\",\n          \"tags\": [\n            \"wan\",\n            \"internet\",\n            \"backup\"\n          ],\n          \"notes\": [\n            \"Verizon Business\",\n            \"500 Mbps backup\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dc-internal-1\": {\n          \"shape\": \"circle\",\n          \"name\": \"DC1 Int DNS\",\n          \"ip\": \"10.10.0.53\",\n          \"role\": \"Internal DNS/AD\",\n          \"tags\": [\n            \"dns\",\n            \"ad\",\n            \"dc1\"\n          ],\n          \"notes\": [\n            \"Windows Server 2022\",\n            \"Primary DC\"\n          ],\n          \"mac\": \"00:50:56:AD:01:01\",\n          \"rackUnit\": 26,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dc-internal-2\": {\n          \"shape\": \"circle\",\n          \"name\": \"DC2 Int DNS\",\n          \"ip\": \"10.10.1.53\",\n          \"role\": \"Internal DNS/AD\",\n          \"tags\": [\n            \"dns\",\n            \"ad\",\n            \"dc2\"\n          ],\n          \"notes\": [\n            \"Windows Server 2022\",\n            \"Secondary DC\"\n          ],\n          \"mac\": \"00:50:56:AD:01:02\",\n          \"rackUnit\": 26,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"app-server-1\": {\n          \"shape\": \"server\",\n          \"name\": \"App Server 01\",\n          \"ip\": \"10.10.0.101\",\n          \"role\": \"Application\",\n          \"tags\": [\n            \"app\",\n            \"iis\",\n            \"web\"\n          ],\n          \"notes\": [\n            \"Windows Server 2022\",\n            \"IIS Application\"\n          ],\n          \"mac\": \"00:50:56:AP:01:01\",\n          \"rackUnit\": 24,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"app-server-2\": {\n          \"shape\": \"server\",\n          \"name\": \"App Server 02\",\n          \"ip\": \"10.10.0.102\",\n          \"role\": \"Application\",\n          \"tags\": [\n            \"app\",\n            \"iis\",\n            \"web\"\n          ],\n          \"notes\": [\n            \"Windows Server 2022\",\n            \"IIS Application\"\n          ],\n          \"mac\": \"00:50:56:AP:01:02\",\n          \"rackUnit\": 22,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"db-server-1\": {\n          \"shape\": \"database\",\n          \"name\": \"SQL Server 01\",\n          \"ip\": \"10.10.0.201\",\n          \"role\": \"Database\",\n          \"tags\": [\n            \"db\",\n            \"sql\",\n            \"primary\"\n          ],\n          \"notes\": [\n            \"SQL Server 2022 Enterprise\",\n            \"AlwaysOn Primary\"\n          ],\n          \"mac\": \"00:50:56:DB:01:01\",\n          \"rackUnit\": 20,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"db-server-2\": {\n          \"shape\": \"database\",\n          \"name\": \"SQL Server 02\",\n          \"ip\": \"10.10.1.201\",\n          \"role\": \"Database\",\n          \"tags\": [\n            \"db\",\n            \"sql\",\n            \"secondary\"\n          ],\n          \"notes\": [\n            \"SQL Server 2022 Enterprise\",\n            \"AlwaysOn Secondary\"\n          ],\n          \"mac\": \"00:50:56:DB:01:02\",\n          \"rackUnit\": 24,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"k8s-master-1\": {\n          \"shape\": \"hexagon\",\n          \"name\": \"K8s Master 1\",\n          \"ip\": \"10.10.1.50\",\n          \"role\": \"Container Orchestration\",\n          \"tags\": [\n            \"kubernetes\",\n            \"master\",\n            \"container\"\n          ],\n          \"notes\": [\n            \"K8s Control Plane\",\n            \"etcd member\"\n          ],\n          \"mac\": \"00:50:56:K8:01:01\",\n          \"rackUnit\": 21,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"k8s-master-2\": {\n          \"shape\": \"hexagon\",\n          \"name\": \"K8s Master 2\",\n          \"ip\": \"10.10.1.51\",\n          \"role\": \"Container Orchestration\",\n          \"tags\": [\n            \"kubernetes\",\n            \"master\",\n            \"container\"\n          ],\n          \"notes\": [\n            \"K8s Control Plane\",\n            \"etcd member\"\n          ],\n          \"mac\": \"00:50:56:K8:01:02\",\n          \"rackUnit\": 19,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"k8s-master-3\": {\n          \"shape\": \"hexagon\",\n          \"name\": \"K8s Master 3\",\n          \"ip\": \"10.10.1.52\",\n          \"role\": \"Container Orchestration\",\n          \"tags\": [\n            \"kubernetes\",\n            \"master\",\n            \"container\"\n          ],\n          \"notes\": [\n            \"K8s Control Plane\",\n            \"etcd member\"\n          ],\n          \"mac\": \"00:50:56:K8:01:03\",\n          \"rackUnit\": 17,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"k8s-worker-1\": {\n          \"shape\": \"server\",\n          \"name\": \"K8s Worker 1\",\n          \"ip\": \"10.10.1.60\",\n          \"role\": \"Container Workload\",\n          \"tags\": [\n            \"kubernetes\",\n            \"worker\",\n            \"container\"\n          ],\n          \"notes\": [\n            \"K8s Worker Node\",\n            \"64GB RAM\"\n          ],\n          \"mac\": \"00:50:56:K8:02:01\",\n          \"rackUnit\": 15,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"k8s-worker-2\": {\n          \"shape\": \"server\",\n          \"name\": \"K8s Worker 2\",\n          \"ip\": \"10.10.1.61\",\n          \"role\": \"Container Workload\",\n          \"tags\": [\n            \"kubernetes\",\n            \"worker\",\n            \"container\"\n          ],\n          \"notes\": [\n            \"K8s Worker Node\",\n            \"64GB RAM\"\n          ],\n          \"mac\": \"00:50:56:K8:02:02\",\n          \"rackUnit\": 13,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"k8s-worker-3\": {\n          \"shape\": \"server\",\n          \"name\": \"K8s Worker 3\",\n          \"ip\": \"10.10.1.62\",\n          \"role\": \"Container Workload\",\n          \"tags\": [\n            \"kubernetes\",\n            \"worker\",\n            \"container\"\n          ],\n          \"notes\": [\n            \"K8s Worker Node\",\n            \"64GB RAM\"\n          ],\n          \"mac\": \"00:50:56:K8:02:03\",\n          \"rackUnit\": 11,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"k8s-worker-4\": {\n          \"shape\": \"server\",\n          \"name\": \"K8s Worker 4\",\n          \"ip\": \"10.10.1.63\",\n          \"role\": \"Container Workload\",\n          \"tags\": [\n            \"kubernetes\",\n            \"worker\",\n            \"container\"\n          ],\n          \"notes\": [\n            \"K8s Worker Node\",\n            \"64GB RAM\"\n          ],\n          \"mac\": \"00:50:56:K8:02:04\",\n          \"rackUnit\": 9,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"proxy-server-1\": {\n          \"shape\": \"server\",\n          \"name\": \"Proxy Server 1\",\n          \"ip\": \"10.5.0.10\",\n          \"role\": \"Web Proxy\",\n          \"tags\": [\n            \"proxy\",\n            \"squid\",\n            \"filtering\"\n          ],\n          \"notes\": [\n            \"Squid Proxy\",\n            \"Content filtering\"\n          ],\n          \"mac\": \"00:50:56:PX:01:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"proxy-server-2\": {\n          \"shape\": \"server\",\n          \"name\": \"Proxy Server 2\",\n          \"ip\": \"10.5.0.11\",\n          \"role\": \"Web Proxy\",\n          \"tags\": [\n            \"proxy\",\n            \"squid\",\n            \"filtering\"\n          ],\n          \"notes\": [\n            \"Squid Proxy\",\n            \"HA pair\"\n          ],\n          \"mac\": \"00:50:56:PX:01:02\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"vpn-concentrator\": {\n          \"shape\": \"firewall\",\n          \"name\": \"VPN Concentrator\",\n          \"ip\": \"10.0.5.1\",\n          \"role\": \"Remote Access VPN\",\n          \"tags\": [\n            \"vpn\",\n            \"remote\",\n            \"security\"\n          ],\n          \"notes\": [\n            \"Cisco ASA 5555-X\",\n            \"AnyConnect SSL VPN\"\n          ],\n          \"mac\": \"00:1A:2B:VP:01:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"nac-server\": {\n          \"shape\": \"server\",\n          \"name\": \"NAC Server\",\n          \"ip\": \"10.5.5.10\",\n          \"role\": \"Network Access Control\",\n          \"tags\": [\n            \"nac\",\n            \"ise\",\n            \"802.1x\"\n          ],\n          \"notes\": [\n            \"Cisco ISE 3.1\",\n            \"RADIUS/TACACS+\"\n          ],\n          \"mac\": \"00:50:56:NA:01:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"print-server\": {\n          \"shape\": \"server\",\n          \"name\": \"Print Server\",\n          \"ip\": \"10.10.0.150\",\n          \"role\": \"Print Services\",\n          \"tags\": [\n            \"print\",\n            \"windows\",\n            \"services\"\n          ],\n          \"notes\": [\n            \"Windows Print Server\",\n            \"50+ printers\"\n          ],\n          \"mac\": \"00:50:56:PR:01:01\",\n          \"rackUnit\": 18,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"file-server\": {\n          \"shape\": \"database\",\n          \"name\": \"File Server\",\n          \"ip\": \"10.10.0.160\",\n          \"role\": \"File Services\",\n          \"tags\": [\n            \"file\",\n            \"smb\",\n            \"dfs\"\n          ],\n          \"notes\": [\n            \"Windows File Server\",\n            \"DFS namespace\"\n          ],\n          \"mac\": \"00:50:56:FS:01:01\",\n          \"rackUnit\": 16,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"ca-server\": {\n          \"shape\": \"server\",\n          \"name\": \"Certificate Authority\",\n          \"ip\": \"192.168.100.80\",\n          \"role\": \"PKI Infrastructure\",\n          \"tags\": [\n            \"pki\",\n            \"ca\",\n            \"security\"\n          ],\n          \"notes\": [\n            \"Windows CA\",\n            \"Enterprise Root CA\"\n          ],\n          \"mac\": \"00:50:56:CA:01:01\",\n          \"rackUnit\": 5,\n          \"uHeight\": \"1\",\n          \"layer\": \"logical\",\n          \"assignedRack\": \"mgmt-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"sccm-server\": {\n          \"shape\": \"server\",\n          \"name\": \"SCCM Server\",\n          \"ip\": \"192.168.100.90\",\n          \"role\": \"Endpoint Management\",\n          \"tags\": [\n            \"sccm\",\n            \"patching\",\n            \"software\"\n          ],\n          \"notes\": [\n            \"MECM Primary Site\",\n            \"Software deployment\"\n          ],\n          \"mac\": \"00:50:56:SC:01:01\",\n          \"rackUnit\": 3,\n          \"uHeight\": \"2\",\n          \"layer\": \"logical\",\n          \"assignedRack\": \"mgmt-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"voip-cluster\": {\n          \"shape\": \"phone\",\n          \"name\": \"VoIP Cluster\",\n          \"ip\": \"10.50.0.0/24\",\n          \"role\": \"Voice Services\",\n          \"tags\": [\n            \"voip\",\n            \"cisco\",\n            \"ucm\"\n          ],\n          \"notes\": [\n            \"Cisco UCM Cluster\",\n            \"3000 endpoints\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"application\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"video-conf\": {\n          \"shape\": \"laptop\",\n          \"name\": \"Video Conference\",\n          \"ip\": \"10.51.0.0/24\",\n          \"role\": \"Video Services\",\n          \"tags\": [\n            \"video\",\n            \"webex\",\n            \"teams\"\n          ],\n          \"notes\": [\n            \"Webex/Teams integration\",\n            \"Meeting rooms\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"application\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"security-cameras\": {\n          \"shape\": \"camera\",\n          \"name\": \"Security Cameras\",\n          \"ip\": \"10.60.0.0/24\",\n          \"role\": \"Physical Security\",\n          \"tags\": [\n            \"cctv\",\n            \"surveillance\",\n            \"security\"\n          ],\n          \"notes\": [\n            \"150+ IP cameras\",\n            \"30-day retention\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"nvr-cluster\": {\n          \"shape\": \"server\",\n          \"name\": \"NVR Cluster\",\n          \"ip\": \"10.60.0.10\",\n          \"role\": \"Video Recording\",\n          \"tags\": [\n            \"nvr\",\n            \"surveillance\",\n            \"storage\"\n          ],\n          \"notes\": [\n            \"Milestone XProtect\",\n            \"500TB storage\"\n          ],\n          \"mac\": \"00:50:56:NV:01:01\",\n          \"rackUnit\": 15,\n          \"uHeight\": \"4\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-b2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dev-server-1\": {\n          \"shape\": \"server\",\n          \"name\": \"Dev Server 1\",\n          \"ip\": \"10.80.0.10\",\n          \"role\": \"Development\",\n          \"tags\": [\n            \"dev\",\n            \"gitlab\",\n            \"ci-cd\",\n            {\n              \"type\": \"icon\",\n              \"library\": \"selfhst\",\n              \"name\": \"dokku\"\n            }\n          ],\n          \"notes\": [\n            \"GitLab Server\",\n            \"CI/CD pipelines\"\n          ],\n          \"mac\": \"00:50:56:DV:01:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"application\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dev-server-2\": {\n          \"shape\": \"server\",\n          \"name\": \"Dev Server 2\",\n          \"ip\": \"10.80.0.11\",\n          \"role\": \"Development\",\n          \"tags\": [\n            \"dev\",\n            \"jenkins\",\n            \"ci-cd\"\n          ],\n          \"notes\": [\n            \"Jenkins Server\",\n            \"Build automation\"\n          ],\n          \"mac\": \"00:50:56:DV:01:02\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"application\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"test-environment\": {\n          \"shape\": \"hexagon\",\n          \"name\": \"Test Environment\",\n          \"ip\": \"10.81.0.0/24\",\n          \"role\": \"QA/Testing\",\n          \"tags\": [\n            \"test\",\n            \"qa\",\n            \"staging\"\n          ],\n          \"notes\": [\n            \"Staging environment\",\n            \"Pre-prod validation\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"application\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"erp-system\": {\n          \"shape\": \"database\",\n          \"name\": \"ERP System\",\n          \"ip\": \"10.90.0.10\",\n          \"role\": \"Business Application\",\n          \"tags\": [\n            \"erp\",\n            \"sap\",\n            \"business\"\n          ],\n          \"notes\": [\n            \"SAP S/4HANA\",\n            \"Financial/HR systems\"\n          ],\n          \"mac\": \"00:50:56:ER:01:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"4\",\n          \"layer\": \"application\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"crm-system\": {\n          \"shape\": \"database\",\n          \"name\": \"CRM System\",\n          \"ip\": \"10.91.0.10\",\n          \"role\": \"Business Application\",\n          \"tags\": [\n            \"crm\",\n            \"salesforce\",\n            \"business\"\n          ],\n          \"notes\": [\n            \"Salesforce integration\",\n            \"Sales/Marketing\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"application\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"endpoint-1000\": {\n          \"shape\": \"laptop\",\n          \"name\": \"Corporate Endpoints\",\n          \"ip\": \"10.70.0.0/22\",\n          \"role\": \"User Workstations\",\n          \"tags\": [\n            \"endpoints\",\n            \"workstations\",\n            \"users\"\n          ],\n          \"notes\": [\n            \"~1000 corporate laptops\",\n            \"Windows 11\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dist-switch-floor1\": {\n          \"shape\": \"switch\",\n          \"name\": \"Floor 1 Switch\",\n          \"ip\": \"10.1.1.1\",\n          \"role\": \"Distribution\",\n          \"tags\": [\n            \"distribution\",\n            \"floor-1\",\n            \"access\"\n          ],\n          \"notes\": [\n            \"Cisco C9300-48P\",\n            \"PoE+ enabled\"\n          ],\n          \"mac\": \"00:1A:2B:FL:01:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dist-switch-floor2\": {\n          \"shape\": \"switch\",\n          \"name\": \"Floor 2 Switch\",\n          \"ip\": \"10.1.2.1\",\n          \"role\": \"Distribution\",\n          \"tags\": [\n            \"distribution\",\n            \"floor-2\",\n            \"access\"\n          ],\n          \"notes\": [\n            \"Cisco C9300-48P\",\n            \"PoE+ enabled\"\n          ],\n          \"mac\": \"00:1A:2B:FL:02:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dist-switch-floor3\": {\n          \"shape\": \"switch\",\n          \"name\": \"Floor 3 Switch\",\n          \"ip\": \"10.1.3.1\",\n          \"role\": \"Distribution\",\n          \"tags\": [\n            \"distribution\",\n            \"floor-3\",\n            \"access\"\n          ],\n          \"notes\": [\n            \"Cisco C9300-48P\",\n            \"PoE+ enabled\"\n          ],\n          \"mac\": \"00:1A:2B:FL:03:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dist-switch-floor4\": {\n          \"shape\": \"switch\",\n          \"name\": \"Floor 4 Switch\",\n          \"ip\": \"10.1.4.1\",\n          \"role\": \"Distribution\",\n          \"tags\": [\n            \"distribution\",\n            \"floor-4\",\n            \"access\"\n          ],\n          \"notes\": [\n            \"Cisco C9300-48P\",\n            \"PoE+ enabled\"\n          ],\n          \"mac\": \"00:1A:2B:FL:04:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"ap-floor1-zone1\": {\n          \"shape\": \"wifi\",\n          \"name\": \"AP Floor 1 Zone 1\",\n          \"ip\": \"10.20.1.10\",\n          \"role\": \"Wireless Access\",\n          \"tags\": [\n            \"wifi\",\n            \"ap\",\n            \"floor-1\"\n          ],\n          \"notes\": [\n            \"Cisco 9120AX\",\n            \"Wi-Fi 6\"\n          ],\n          \"mac\": \"00:1A:2B:AP:01:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"ap-floor2-zone1\": {\n          \"shape\": \"wifi\",\n          \"name\": \"AP Floor 2 Zone 1\",\n          \"ip\": \"10.20.2.10\",\n          \"role\": \"Wireless Access\",\n          \"tags\": [\n            \"wifi\",\n            \"ap\",\n            \"floor-2\"\n          ],\n          \"notes\": [\n            \"Cisco 9120AX\",\n            \"Wi-Fi 6\"\n          ],\n          \"mac\": \"00:1A:2B:AP:02:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"ap-floor3-zone1\": {\n          \"shape\": \"wifi\",\n          \"name\": \"AP Floor 3 Zone 1\",\n          \"ip\": \"10.20.3.10\",\n          \"role\": \"Wireless Access\",\n          \"tags\": [\n            \"wifi\",\n            \"ap\",\n            \"floor-3\"\n          ],\n          \"notes\": [\n            \"Cisco 9120AX\",\n            \"Wi-Fi 6\"\n          ],\n          \"mac\": \"00:1A:2B:AP:03:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"ap-floor4-zone1\": {\n          \"shape\": \"wifi\",\n          \"name\": \"AP Floor 4 Zone 1\",\n          \"ip\": \"10.20.4.10\",\n          \"role\": \"Wireless Access\",\n          \"tags\": [\n            \"wifi\",\n            \"ap\",\n            \"floor-4\"\n          ],\n          \"notes\": [\n            \"Cisco 9120AX\",\n            \"Wi-Fi 6\"\n          ],\n          \"mac\": \"00:1A:2B:AP:04:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"ups-dc-1\": {\n          \"shape\": \"rectangle\",\n          \"name\": \"UPS DC-1\",\n          \"ip\": \"192.168.200.10\",\n          \"role\": \"Power Management\",\n          \"tags\": [\n            \"power\",\n            \"ups\",\n            \"datacenter\"\n          ],\n          \"notes\": [\n            \"APC Symmetra\",\n            \"80kVA\",\n            \"30 min runtime\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"ups-dc-2\": {\n          \"shape\": \"rectangle\",\n          \"name\": \"UPS DC-2\",\n          \"ip\": \"192.168.200.11\",\n          \"role\": \"Power Management\",\n          \"tags\": [\n            \"power\",\n            \"ups\",\n            \"datacenter\"\n          ],\n          \"notes\": [\n            \"APC Symmetra\",\n            \"80kVA\",\n            \"Redundant\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"pdu-rack-a1\": {\n          \"shape\": \"rectangle\",\n          \"name\": \"PDU Rack A1\",\n          \"ip\": \"192.168.200.21\",\n          \"role\": \"Power Distribution\",\n          \"tags\": [\n            \"power\",\n            \"pdu\",\n            \"rack-a1\"\n          ],\n          \"notes\": [\n            \"APC Switched PDU\",\n            \"Per-outlet metering\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": 1,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"pdu-rack-a2\": {\n          \"shape\": \"rectangle\",\n          \"name\": \"PDU Rack A2\",\n          \"ip\": \"192.168.200.22\",\n          \"role\": \"Power Distribution\",\n          \"tags\": [\n            \"power\",\n            \"pdu\",\n            \"rack-a2\"\n          ],\n          \"notes\": [\n            \"APC Switched PDU\",\n            \"Per-outlet metering\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": 1,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"cooling-1\": {\n          \"shape\": \"rectangle\",\n          \"name\": \"CRAC Unit 1\",\n          \"ip\": \"192.168.200.30\",\n          \"role\": \"Cooling\",\n          \"tags\": [\n            \"cooling\",\n            \"hvac\",\n            \"datacenter\"\n          ],\n          \"notes\": [\n            \"Liebert CRV\",\n            \"Row-based cooling\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"cooling-2\": {\n          \"shape\": \"rectangle\",\n          \"name\": \"CRAC Unit 2\",\n          \"ip\": \"192.168.200.31\",\n          \"role\": \"Cooling\",\n          \"tags\": [\n            \"cooling\",\n            \"hvac\",\n            \"datacenter\"\n          ],\n          \"notes\": [\n            \"Liebert CRV\",\n            \"N+1 redundancy\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"camera-a\": {\n          \"shape\": \"camera\",\n          \"name\": \"camera A\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"ping\": {\n            \"enabled\": false,\n            \"protocol\": \"http\",\n            \"customUrl\": \"\",\n            \"timeout\": 3000,\n            \"status\": \"unknown\",\n            \"lastCheck\": null\n          },\n          \"locked\": false,\n          \"groupId\": null,\n          \"fovEnabled\": true,\n          \"fovRotation\": 104,\n          \"fovDistance\": 500,\n          \"fovSweep\": 60,\n          \"fovSpeed\": 10,\n          \"fovAnimate\": true\n        },\n        \"camera-a-copy\": {\n          \"shape\": \"camera\",\n          \"name\": \"camera B\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"ping\": {\n            \"enabled\": false,\n            \"protocol\": \"http\",\n            \"customUrl\": \"\",\n            \"timeout\": 3000,\n            \"status\": \"unknown\",\n            \"lastCheck\": null\n          },\n          \"locked\": false,\n          \"groupId\": null,\n          \"fovEnabled\": true,\n          \"fovRotation\": 162,\n          \"fovDistance\": 500,\n          \"fovSweep\": 60,\n          \"fovSpeed\": 10,\n          \"fovAnimate\": false\n        }\n      },\n      \"edges\": {\n        \"list\": [\n          {\n            \"id\": \"isp1-router1\",\n            \"from\": \"isp-primary\",\n            \"to\": \"core-router-1\",\n            \"width\": 6,\n            \"color\": \"#10b981\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"Primary WAN link\"\n            ],\n            \"fromPort\": \"Gi0/0\",\n            \"toPort\": \"Gi1/0/1\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"isp2-router2\",\n            \"from\": \"isp-secondary\",\n            \"to\": \"core-router-2\",\n            \"width\": 6,\n            \"color\": \"#10b981\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"Backup WAN link\"\n            ],\n            \"fromPort\": \"Gi0/0\",\n            \"toPort\": \"Gi1/0/1\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"router1-router2\",\n            \"from\": \"core-router-1\",\n            \"to\": \"core-router-2\",\n            \"width\": 4,\n            \"color\": \"#f59e0b\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"HSRP Peering\"\n            ],\n            \"fromPort\": \"Gi1/0/24\",\n            \"toPort\": \"Gi1/0/24\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"router1-fw1\",\n            \"from\": \"core-router-1\",\n            \"to\": \"fw-external-1\",\n            \"width\": 4,\n            \"color\": \"#ef4444\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"router2-fw2\",\n            \"from\": \"core-router-2\",\n            \"to\": \"fw-external-2\",\n            \"width\": 4,\n            \"color\": \"#ef4444\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"fw1-fw2\",\n            \"from\": \"fw-external-1\",\n            \"to\": \"fw-external-2\",\n            \"width\": 3,\n            \"color\": \"#f59e0b\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"HA heartbeat\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dashed\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"fw1-coresw1\",\n            \"from\": \"fw-external-1\",\n            \"to\": \"core-switch-1\",\n            \"width\": 4,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"fw2-coresw2\",\n            \"from\": \"fw-external-2\",\n            \"to\": \"core-switch-2\",\n            \"width\": 4,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-coresw2\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"core-switch-2\",\n            \"width\": 5,\n            \"color\": \"#3b82f6\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"VPC peer-link\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-fwint\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"fw-internal\",\n            \"width\": 3,\n            \"color\": \"#ef4444\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw2-fwint\",\n            \"from\": \"core-switch-2\",\n            \"to\": \"fw-internal\",\n            \"width\": 3,\n            \"color\": \"#ef4444\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-racka1\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"dc-rack-a1\",\n            \"width\": 4,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw2-racka1\",\n            \"from\": \"core-switch-2\",\n            \"to\": \"dc-rack-a1\",\n            \"width\": 4,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-racka2\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"dc-rack-a2\",\n            \"width\": 4,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw2-racka2\",\n            \"from\": \"core-switch-2\",\n            \"to\": \"dc-rack-a2\",\n            \"width\": 4,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-rackb1\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"dc-rack-b1\",\n            \"width\": 4,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw2-rackb1\",\n            \"from\": \"core-switch-2\",\n            \"to\": \"dc-rack-b1\",\n            \"width\": 4,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-rackb2\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"dc-rack-b2\",\n            \"width\": 4,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw2-rackb2\",\n            \"from\": \"core-switch-2\",\n            \"to\": \"dc-rack-b2\",\n            \"width\": 4,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"fw1-dmz\",\n            \"from\": \"fw-external-1\",\n            \"to\": \"dmz-rack\",\n            \"width\": 3,\n            \"color\": \"#f59e0b\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"DMZ segment\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"fw2-dmz\",\n            \"from\": \"fw-external-2\",\n            \"to\": \"dmz-rack\",\n            \"width\": 3,\n            \"color\": \"#f59e0b\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"DMZ segment\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-mgmt\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"mgmt-rack\",\n            \"width\": 3,\n            \"color\": \"#8b5cf6\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"OOB management\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-wlc1\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"wlc-primary\",\n            \"width\": 3,\n            \"color\": \"#06b6d4\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw2-wlc2\",\n            \"from\": \"core-switch-2\",\n            \"to\": \"wlc-secondary\",\n            \"width\": 3,\n            \"color\": \"#06b6d4\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\",\n            \"animate\": true\n          },\n          {\n            \"id\": \"wlc1-wlc2\",\n            \"from\": \"wlc-primary\",\n            \"to\": \"wlc-secondary\",\n            \"width\": 2,\n            \"color\": \"#f59e0b\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"HA pair\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dashed\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"wlc1-mobile-hq\",\n            \"from\": \"wlc-primary\",\n            \"to\": \"mobile-zone-hq\",\n            \"width\": 2,\n            \"color\": \"#06b6d4\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"wlc1-mobile-guest\",\n            \"from\": \"wlc-primary\",\n            \"to\": \"mobile-zone-guest\",\n            \"width\": 2,\n            \"color\": \"#06b6d4\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"wlc1-mobile-iot\",\n            \"from\": \"wlc-primary\",\n            \"to\": \"mobile-zone-iot\",\n            \"width\": 2,\n            \"color\": \"#06b6d4\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"router1-branch-ny\",\n            \"from\": \"core-router-1\",\n            \"to\": \"branch-router-ny\",\n            \"width\": 3,\n            \"color\": \"#a855f7\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"SD-WAN tunnel\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dashed\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"router1-branch-la\",\n            \"from\": \"core-router-1\",\n            \"to\": \"branch-router-la\",\n            \"width\": 3,\n            \"color\": \"#a855f7\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"SD-WAN tunnel\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dashed\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"router1-branch-chi\",\n            \"from\": \"core-router-1\",\n            \"to\": \"branch-router-chi\",\n            \"width\": 3,\n            \"color\": \"#a855f7\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"SD-WAN tunnel\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dashed\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"router1-branch-lon\",\n            \"from\": \"core-router-1\",\n            \"to\": \"branch-router-lon\",\n            \"width\": 3,\n            \"color\": \"#a855f7\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"SD-WAN tunnel\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dashed\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"router1-branch-tokyo\",\n            \"from\": \"core-router-1\",\n            \"to\": \"branch-router-tokyo\",\n            \"width\": 3,\n            \"color\": \"#a855f7\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"SD-WAN tunnel\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dashed\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"router1-aws\",\n            \"from\": \"core-router-1\",\n            \"to\": \"cloud-aws\",\n            \"width\": 3,\n            \"color\": \"#f97316\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"Direct Connect\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dashed\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"router2-azure\",\n            \"from\": \"core-router-2\",\n            \"to\": \"cloud-azure\",\n            \"width\": 3,\n            \"color\": \"#0ea5e9\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"ExpressRoute\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dashed\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"fwint-gcp\",\n            \"from\": \"fw-internal\",\n            \"to\": \"cloud-gcp\",\n            \"width\": 2,\n            \"color\": \"#22c55e\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"VPN tunnel\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dashed\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-floor1\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"dist-switch-floor1\",\n            \"width\": 3,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-floor2\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"dist-switch-floor2\",\n            \"width\": 3,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw2-floor3\",\n            \"from\": \"core-switch-2\",\n            \"to\": \"dist-switch-floor3\",\n            \"width\": 3,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw2-floor4\",\n            \"from\": \"core-switch-2\",\n            \"to\": \"dist-switch-floor4\",\n            \"width\": 3,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"floor1-endpoints\",\n            \"from\": \"dist-switch-floor1\",\n            \"to\": \"endpoint-1000\",\n            \"width\": 2,\n            \"color\": \"#94a3b8\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"floor1-ap1\",\n            \"from\": \"dist-switch-floor1\",\n            \"to\": \"ap-floor1-zone1\",\n            \"width\": 2,\n            \"color\": \"#06b6d4\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"floor2-ap2\",\n            \"from\": \"dist-switch-floor2\",\n            \"to\": \"ap-floor2-zone1\",\n            \"width\": 2,\n            \"color\": \"#06b6d4\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"floor3-ap3\",\n            \"from\": \"dist-switch-floor3\",\n            \"to\": \"ap-floor3-zone1\",\n            \"width\": 2,\n            \"color\": \"#06b6d4\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"floor4-ap4\",\n            \"from\": \"dist-switch-floor4\",\n            \"to\": \"ap-floor4-zone1\",\n            \"width\": 2,\n            \"color\": \"#06b6d4\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"fwint-proxy1\",\n            \"from\": \"fw-internal\",\n            \"to\": \"proxy-server-1\",\n            \"width\": 2,\n            \"color\": \"#ef4444\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"fwint-proxy2\",\n            \"from\": \"fw-internal\",\n            \"to\": \"proxy-server-2\",\n            \"width\": 2,\n            \"color\": \"#ef4444\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"fwext1-vpn\",\n            \"from\": \"fw-external-1\",\n            \"to\": \"vpn-concentrator\",\n            \"width\": 3,\n            \"color\": \"#8b5cf6\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-nac\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"nac-server\",\n            \"width\": 2,\n            \"color\": \"#f59e0b\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-voip\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"voip-cluster\",\n            \"width\": 3,\n            \"color\": \"#22c55e\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw2-video\",\n            \"from\": \"core-switch-2\",\n            \"to\": \"video-conf\",\n            \"width\": 3,\n            \"color\": \"#22c55e\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-cameras\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"security-cameras\",\n            \"width\": 2,\n            \"color\": \"#94a3b8\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"fwint-dev1\",\n            \"from\": \"fw-internal\",\n            \"to\": \"dev-server-1\",\n            \"width\": 2,\n            \"color\": \"#a855f7\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"fwint-dev2\",\n            \"from\": \"fw-internal\",\n            \"to\": \"dev-server-2\",\n            \"width\": 2,\n            \"color\": \"#a855f7\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\",\n            \"animate\": true,\n            \"animationSpeed\": \"1.5\"\n          },\n          {\n            \"id\": \"fwint-test\",\n            \"from\": \"fw-internal\",\n            \"to\": \"test-environment\",\n            \"width\": 2,\n            \"color\": \"#a855f7\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-erp\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"erp-system\",\n            \"width\": 3,\n            \"color\": \"#f59e0b\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"fwext1-crm\",\n            \"from\": \"fw-external-1\",\n            \"to\": \"crm-system\",\n            \"width\": 2,\n            \"color\": \"#f59e0b\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"Salesforce cloud\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dashed\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"ups1-racka1\",\n            \"from\": \"ups-dc-1\",\n            \"to\": \"dc-rack-a1\",\n            \"width\": 2,\n            \"color\": \"#fbbf24\",\n            \"direction\": \"forward\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"Power feed A\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"ups2-racka2\",\n            \"from\": \"ups-dc-2\",\n            \"to\": \"dc-rack-a2\",\n            \"width\": 2,\n            \"color\": \"#fbbf24\",\n            \"direction\": \"forward\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"Power feed B\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"ups1-rackb1\",\n            \"from\": \"ups-dc-1\",\n            \"to\": \"dc-rack-b1\",\n            \"width\": 2,\n            \"color\": \"#fbbf24\",\n            \"direction\": \"forward\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"Power feed A\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\",\n            \"animate\": true,\n            \"animationSpeed\": \"4\"\n          },\n          {\n            \"id\": \"ups2-rackb2\",\n            \"from\": \"ups-dc-2\",\n            \"to\": \"dc-rack-b2\",\n            \"width\": 2,\n            \"color\": \"#fbbf24\",\n            \"direction\": \"forward\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"Power feed B\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"cooling1-racka1\",\n            \"from\": \"cooling-1\",\n            \"to\": \"dc-rack-a1\",\n            \"width\": 2,\n            \"color\": \"#38bdf8\",\n            \"direction\": \"forward\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"Cooling zone\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dotted\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"cooling2-rackb1\",\n            \"from\": \"cooling-2\",\n            \"to\": \"dc-rack-b1\",\n            \"width\": 2,\n            \"color\": \"#38bdf8\",\n            \"direction\": \"forward\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"Cooling zone\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dotted\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"custom-1765237881452\",\n            \"type\": \"custom\",\n            \"color\": \"#c800ff\",\n            \"width\": 4,\n            \"lineStyle\": \"solid\",\n            \"direction\": \"forward\",\n            \"points\": [\n              {\n                \"x\": 3492.3994140625,\n                \"y\": 1526.9556884765625\n              },\n              {\n                \"x\": 3500.609619140625,\n                \"y\": 1830.7386474609375\n              },\n              {\n                \"x\": 3303.561279296875,\n                \"y\": 1732.2144775390625\n              }\n            ],\n            \"notes\": [],\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"custom-1765239355462\",\n            \"type\": \"custom\",\n            \"color\": \"#f97316\",\n            \"width\": 4,\n            \"lineStyle\": \"solid\",\n            \"direction\": \"forward\",\n            \"points\": [\n              {\n                \"x\": 2467.182861328125,\n                \"y\": 156.12173461914062\n              },\n              {\n                \"x\": 2146.36376953125,\n                \"y\": 146.32574462890625\n              },\n              {\n                \"x\": 2305.548828125,\n                \"y\": 244.28573608398438\n              }\n            ],\n            \"notes\": [],\n            \"routing\": \"orthogonal\"\n          }\n        ]\n      },\n      \"positions\": {\n        \"core-router-1\": {\n          \"x\": 3720.166015625,\n          \"y\": 245.9932403564453\n        },\n        \"core-router-2\": {\n          \"x\": 2499.883407638303,\n          \"y\": 329.99503430389154\n        },\n        \"fw-external-1\": {\n          \"x\": 3221.7385182723783,\n          \"y\": 1016.1364499992887\n        },\n        \"fw-external-2\": {\n          \"x\": 1915.5213706410505,\n          \"y\": 224.43528858865443\n        },\n        \"fw-internal\": {\n          \"x\": 1746.9168185079352,\n          \"y\": 477.5300527221864\n        },\n        \"core-switch-1\": {\n          \"x\": 449.39860669455675,\n          \"y\": 384.4578707617695\n        },\n        \"core-switch-2\": {\n          \"x\": 761.1664921394672,\n          \"y\": 180.89283910873155\n        },\n        \"dc-rack-a1\": {\n          \"x\": 783.7017241128451,\n          \"y\": 647.4086870405963\n        },\n        \"dc-rack-a2\": {\n          \"x\": 209.25701628255229,\n          \"y\": 228.01593190351014\n        },\n        \"dc-rack-b1\": {\n          \"x\": 3184.3186625759854,\n          \"y\": 1627.4495531027196\n        },\n        \"dc-rack-b2\": {\n          \"x\": 245.37065918741246,\n          \"y\": 499.6191264194081\n        },\n        \"dmz-rack\": {\n          \"x\": 2176.4105289561007,\n          \"y\": 610.8312056412005\n        },\n        \"mgmt-rack\": {\n          \"x\": 1601.2987201807314,\n          \"y\": 1281.4753424975324\n        },\n        \"esxi-host-01\": {\n          \"x\": 2162.2166789540615,\n          \"y\": 2608.110619289529\n        },\n        \"esxi-host-02\": {\n          \"x\": 2205.94717202368,\n          \"y\": 2689.67539624076\n        },\n        \"esxi-host-03\": {\n          \"x\": 2154.6015436939074,\n          \"y\": 2771.203009774913\n        },\n        \"esxi-host-04\": {\n          \"x\": 2195.986926025096,\n          \"y\": 2845\n        },\n        \"tor-switch-a1\": {\n          \"x\": 2146.8943639962963,\n          \"y\": 2845\n        },\n        \"esxi-host-05\": {\n          \"x\": 2185.9099961569727,\n          \"y\": 2845\n        },\n        \"esxi-host-06\": {\n          \"x\": 2139.099728450725,\n          \"y\": 2845\n        },\n        \"esxi-host-07\": {\n          \"x\": 2175.7223818764883,\n          \"y\": 2845\n        },\n        \"esxi-host-08\": {\n          \"x\": 2131.2222777148922,\n          \"y\": 2845\n        },\n        \"tor-switch-a2\": {\n          \"x\": 2165.4301485385085,\n          \"y\": 2845\n        },\n        \"san-primary\": {\n          \"x\": 2123.2667017518106,\n          \"y\": 2845\n        },\n        \"san-secondary\": {\n          \"x\": 2155.0394237844876,\n          \"y\": 2845\n        },\n        \"fc-switch-1\": {\n          \"x\": 2115.2377370375634,\n          \"y\": 2845\n        },\n        \"fc-switch-2\": {\n          \"x\": 2144.5563938942755,\n          \"y\": 2845\n        },\n        \"backup-server-1\": {\n          \"x\": 2107.1401637413705,\n          \"y\": 2845\n        },\n        \"backup-server-2\": {\n          \"x\": 2133.987300103025,\n          \"y\": 2845\n        },\n        \"tape-library\": {\n          \"x\": 2098.9788028796397,\n          \"y\": 2845\n        },\n        \"tor-switch-b1\": {\n          \"x\": 2123.338434885373,\n          \"y\": 2845\n        },\n        \"tor-switch-b2\": {\n          \"x\": 2090.7585134456995,\n          \"y\": 2845\n        },\n        \"web-server-1\": {\n          \"x\": 2112.6161382091163,\n          \"y\": 2845\n        },\n        \"web-server-2\": {\n          \"x\": 2082.484189516922,\n          \"y\": 2845\n        },\n        \"waf-1\": {\n          \"x\": 2101.826793760617,\n          \"y\": 2845\n        },\n        \"load-balancer-dmz\": {\n          \"x\": 2074.1607573409574,\n          \"y\": 2845\n        },\n        \"mail-gateway\": {\n          \"x\": 2090.97682514417,\n          \"y\": 2845\n        },\n        \"dns-external-1\": {\n          \"x\": 2065.7931724028163,\n          \"y\": 2845\n        },\n        \"dns-external-2\": {\n          \"x\": 2080.0726920576153,\n          \"y\": 2845\n        },\n        \"vcenter\": {\n          \"x\": 2057.3864164745437,\n          \"y\": 2845\n        },\n        \"nsx-manager\": {\n          \"x\": 2069.1208864464534,\n          \"y\": 2845\n        },\n        \"siem-server\": {\n          \"x\": 2048.945494649244,\n          \"y\": 2845\n        },\n        \"nms-server\": {\n          \"x\": 2058.1279286387635,\n          \"y\": 2845\n        },\n        \"jump-server\": {\n          \"x\": 2040.4754323612206,\n          \"y\": 2845\n        },\n        \"ipam-server\": {\n          \"x\": 2047.1003634632284,\n          \"y\": 2845\n        },\n        \"wlc-primary\": {\n          \"x\": 1575.9723612611924,\n          \"y\": 2306.135986328125\n        },\n        \"wlc-secondary\": {\n          \"x\": 1468.1361870166274,\n          \"y\": 1563.733642578125\n        },\n        \"mobile-zone-hq\": {\n          \"x\": 2354.901177346808,\n          \"y\": 2806.0078125\n        },\n        \"mobile-zone-guest\": {\n          \"x\": 2307.6605605284435,\n          \"y\": 2611.047119140625\n        },\n        \"mobile-zone-iot\": {\n          \"x\": 2229.397686389302,\n          \"y\": 2299.110107421875\n        },\n        \"branch-router-ny\": {\n          \"x\": 3151.903101363964,\n          \"y\": 633.6580810546875\n        },\n        \"branch-router-la\": {\n          \"x\": 3083.8876194705945,\n          \"y\": 506.90625\n        },\n        \"branch-router-chi\": {\n          \"x\": 3355.02409980103,\n          \"y\": 393.1805725097656\n        },\n        \"branch-router-lon\": {\n          \"x\": 3113.609823320121,\n          \"y\": 260.4093322753906\n        },\n        \"branch-router-tokyo\": {\n          \"x\": 3699.3234994733834,\n          \"y\": 471.4241027832031\n        },\n        \"cloud-aws\": {\n          \"x\": 3436.528122523513,\n          \"y\": 545.9614868164062\n        },\n        \"cloud-azure\": {\n          \"x\": 2592.566210818907,\n          \"y\": 2724.068115234375\n        },\n        \"cloud-gcp\": {\n          \"x\": 2827.3183770424234,\n          \"y\": 2731.397216796875\n        },\n        \"isp-primary\": {\n          \"x\": 3712.192068081962,\n          \"y\": 615.64990234375\n        },\n        \"isp-secondary\": {\n          \"x\": 2702.3789772348055,\n          \"y\": 467.890869140625\n        },\n        \"dc-internal-1\": {\n          \"x\": 1958.4243458877936,\n          \"y\": 2845\n        },\n        \"dc-internal-2\": {\n          \"x\": 1963.768951182132,\n          \"y\": 2845\n        },\n        \"app-server-1\": {\n          \"x\": 1947.3819379304134,\n          \"y\": 2845\n        },\n        \"app-server-2\": {\n          \"x\": 1955.2862087394126,\n          \"y\": 2845\n        },\n        \"db-server-1\": {\n          \"x\": 1936.3708569559828,\n          \"y\": 2845\n        },\n        \"db-server-2\": {\n          \"x\": 1946.8300873488822,\n          \"y\": 2845\n        },\n        \"k8s-master-1\": {\n          \"x\": 1925.397658583093,\n          \"y\": 2845\n        },\n        \"k8s-master-2\": {\n          \"x\": 1938.405621494142,\n          \"y\": 2845\n        },\n        \"k8s-master-3\": {\n          \"x\": 1914.4688758763386,\n          \"y\": 2845\n        },\n        \"k8s-worker-1\": {\n          \"x\": 1930.017826812177,\n          \"y\": 2845\n        },\n        \"k8s-worker-2\": {\n          \"x\": 1903.5910154567553,\n          \"y\": 2845\n        },\n        \"k8s-worker-3\": {\n          \"x\": 1921.6716971072178,\n          \"y\": 2845\n        },\n        \"k8s-worker-4\": {\n          \"x\": 1892.7705536280016,\n          \"y\": 2845\n        },\n        \"proxy-server-1\": {\n          \"x\": 1806.1152433697903,\n          \"y\": 653.7529296875\n        },\n        \"proxy-server-2\": {\n          \"x\": 2937.4207928721535,\n          \"y\": 2628.7880859375\n        },\n        \"vpn-concentrator\": {\n          \"x\": 3642.252088474593,\n          \"y\": 946.7255249023438\n        },\n        \"nac-server\": {\n          \"x\": 1153.2626148502184,\n          \"y\": 1172.1895751953125\n        },\n        \"print-server\": {\n          \"x\": 1896.9328460745962,\n          \"y\": 2845\n        },\n        \"file-server\": {\n          \"x\": 1860.7177871362182,\n          \"y\": 2845\n        },\n        \"ca-server\": {\n          \"x\": 1888.8027739274805,\n          \"y\": 2845\n        },\n        \"sccm-server\": {\n          \"x\": 1850.1909418511675,\n          \"y\": 2845\n        },\n        \"voip-cluster\": {\n          \"x\": 1777.038465328039,\n          \"y\": 1616.8961181640625\n        },\n        \"video-conf\": {\n          \"x\": 1993.8373941679588,\n          \"y\": 2244.936309814453\n        },\n        \"security-cameras\": {\n          \"x\": 1674.413336949044,\n          \"y\": 2046.0380859375\n        },\n        \"nvr-cluster\": {\n          \"x\": 1829.4110389706402,\n          \"y\": 2845\n        },\n        \"dev-server-1\": {\n          \"x\": 2800.5894350649614,\n          \"y\": 1175.623291015625\n        },\n        \"dev-server-2\": {\n          \"x\": 1945.0822182484326,\n          \"y\": 1164.5184783935547\n        },\n        \"test-environment\": {\n          \"x\": 2566.9100352578575,\n          \"y\": 885.2827758789062\n        },\n        \"erp-system\": {\n          \"x\": 789.9880103985649,\n          \"y\": 473.7113342285156\n        },\n        \"crm-system\": {\n          \"x\": 3514.6003232048542,\n          \"y\": 1137.7720947265625\n        },\n        \"endpoint-1000\": {\n          \"x\": 991.6812012057328,\n          \"y\": 2284.42236328125\n        },\n        \"dist-switch-floor1\": {\n          \"x\": 654.2091033261356,\n          \"y\": 2020.0086669921875\n        },\n        \"dist-switch-floor2\": {\n          \"x\": 853.8845527112826,\n          \"y\": 1843.2872314453125\n        },\n        \"dist-switch-floor3\": {\n          \"x\": 1899.4353951584517,\n          \"y\": 1456.5068359375\n        },\n        \"dist-switch-floor4\": {\n          \"x\": 488.5289313756234,\n          \"y\": 181.47256469726562\n        },\n        \"ap-floor1-zone1\": {\n          \"x\": 1140.16846970184,\n          \"y\": 2070.2916259765625\n        },\n        \"ap-floor2-zone1\": {\n          \"x\": 688.1952143592268,\n          \"y\": 2384.4775390625\n        },\n        \"ap-floor3-zone1\": {\n          \"x\": 2145.3803027919676,\n          \"y\": 1890.2816162109375\n        },\n        \"ap-floor4-zone1\": {\n          \"x\": 517.646146409649,\n          \"y\": 565.59716796875\n        },\n        \"ups-dc-1\": {\n          \"x\": 771.1406786539856,\n          \"y\": 295.9266662597656\n        },\n        \"ups-dc-2\": {\n          \"x\": 216.2410855890687,\n          \"y\": 330.3345947265625\n        },\n        \"pdu-rack-a1\": {\n          \"x\": 1804.774444371901,\n          \"y\": 2845\n        },\n        \"pdu-rack-a2\": {\n          \"x\": 1741.6184034693686,\n          \"y\": 2845\n        },\n        \"cooling-1\": {\n          \"x\": 245.7080801919958,\n          \"y\": 626.1914672851562\n        },\n        \"cooling-2\": {\n          \"x\": 1603.293611085831,\n          \"y\": 981.0621185302734\n        },\n        \"camera-a\": {\n          \"x\": 166.57075412676295,\n          \"y\": 145\n        },\n        \"camera-a-copy\": {\n          \"x\": 1040.653076171875,\n          \"y\": 738.42822265625\n        }\n      },\n      \"sizes\": {\n        \"isp-secondary\": 139,\n        \"test-environment\": 148,\n        \"dev-server-1\": 128,\n        \"core-router-2\": 120,\n        \"camera-a\": 45,\n        \"camera-a-copy\": 45\n      },\n      \"styles\": {\n        \"dc-rack-b2\": {\n          \"all\": {\n            \"circleColor\": \"#ff0000\"\n          }\n        },\n        \"dc-rack-a1\": {\n          \"all\": {\n            \"circleColor\": \"#ff0000\"\n          }\n        },\n        \"dc-rack-b1\": {\n          \"all\": {\n            \"circleColor\": \"#ff0000\",\n            \"titleSize\": 59\n          }\n        },\n        \"isp-secondary\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"alist\"\n            }\n          }\n        },\n        \"core-router-2\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"actual-budget\"\n            },\n            \"pingOffsetX\": -15,\n            \"pingOffsetY\": -38\n          }\n        },\n        \"fw-external-1\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"anonaddy\"\n            }\n          }\n        },\n        \"cloud-aws\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"ansible\"\n            }\n          }\n        },\n        \"isp-primary\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"wikidocs\"\n            }\n          }\n        },\n        \"branch-router-tokyo\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"adguard-home\"\n            }\n          }\n        },\n        \"core-router-1\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"borg\"\n            }\n          }\n        },\n        \"test-environment\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"simple\",\n              \"name\": \"apple\"\n            }\n          }\n        },\n        \"dev-server-1\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"simple\",\n              \"name\": \"amazonwebservices\"\n            }\n          }\n        }\n      },\n      \"legend\": {\n        \"#10b981\": \"Trusted Lan\",\n        \"#f59e0b\": \"Secure Lan\",\n        \"#ef4444\": \"DMZ\",\n        \"#475569\": \"Main ISP\",\n        \"#3b82f6\": \"Alternate ISP\",\n        \"#8b5cf6\": \"you can edit me too\",\n        \"#06b6d4\": \"you can edit me too\",\n        \"#a855f7\": \"you can edit me too\",\n        \"#f97316\": \"you can edit me too\",\n        \"#0ea5e9\": \"you can edit me too\",\n        \"#22c55e\": \"you can edit me too\",\n        \"#94a3b8\": \"you can edit me too\",\n        \"#fbbf24\": \"you can edit me too\",\n        \"#38bdf8\": \"you can edit me too\",\n        \"#c800ff\": \"you can edit me too\"\n      },\n      \"rects\": {\n        \"list\": [\n          {\n            \"id\": \"rect-1765237540610\",\n            \"x\": 2879.214599609375,\n            \"y\": 159.71981811523438,\n            \"width\": 992.196044921875,\n            \"height\": 538.8650817871094,\n            \"color\": \"#f97316\",\n            \"style\": \"filled\",\n            \"lineStyle\": \"solid\",\n            \"notes\": []\n          },\n          {\n            \"id\": \"rect-1765237681216\",\n            \"x\": 448.3926696777344,\n            \"y\": 1671.651123046875,\n            \"width\": 916.3436584472656,\n            \"height\": 924.27734375,\n            \"color\": \"#c800ff\",\n            \"style\": \"outlined\",\n            \"lineStyle\": \"solid\",\n            \"notes\": []\n          },\n          {\n            \"id\": \"rect-1766437913740\",\n            \"x\": 904.5889892578125,\n            \"y\": 115.40318298339844,\n            \"width\": 110.93878173828125,\n            \"height\": 919.6242218017578,\n            \"color\": \"#5215f9\",\n            \"style\": \"filled\",\n            \"lineStyle\": \"wall\",\n            \"notes\": [],\n            \"borderWidth\": 13\n          },\n          {\n            \"id\": \"rect-1766437935414\",\n            \"x\": 130.93685150146484,\n            \"y\": 1072.3624877929688,\n            \"width\": 872.9131851196289,\n            \"height\": 99.260986328125,\n            \"color\": \"#5215f9\",\n            \"style\": \"filled\",\n            \"lineStyle\": \"wall\",\n            \"notes\": [],\n            \"borderWidth\": 13\n          }\n        ]\n      },\n      \"texts\": {\n        \"list\": [\n          {\n            \"id\": \"text-1765237828167\",\n            \"x\": 3411.458740234375,\n            \"y\": 1390.00439453125,\n            \"content\": \"Double click on desktop\\nor long press on mobile\\nto enter rack canvas view\",\n            \"fontSize\": 46,\n            \"color\": \"#e2e8f0\",\n            \"fontWeight\": \"bold\",\n            \"fontStyle\": \"italic\",\n            \"textAlign\": \"middle\",\n            \"textDecoration\": \"none\",\n            \"bgColor\": \"#000000\",\n            \"bgEnabled\": false,\n            \"opacity\": 1\n          },\n          {\n            \"id\": \"text-1765239331126\",\n            \"x\": 2454.5615234375,\n            \"y\": 160.73322105407715,\n            \"content\": \"Google is live!\",\n            \"fontSize\": 56,\n            \"color\": \"#e2e8f0\",\n            \"fontWeight\": \"bold\",\n            \"fontStyle\": \"normal\",\n            \"textAlign\": \"start\",\n            \"textDecoration\": \"none\",\n            \"bgColor\": \"#000000\",\n            \"bgEnabled\": false,\n            \"opacity\": 1\n          },\n          {\n            \"id\": \"text-1766446595277\",\n            \"x\": 654.3878479003906,\n            \"y\": 1367.7945556640625,\n            \"content\": \"SITE A\",\n            \"fontSize\": 101,\n            \"color\": \"#e2e8f0\",\n            \"fontWeight\": \"normal\",\n            \"fontStyle\": \"normal\",\n            \"textAlign\": \"start\",\n            \"textDecoration\": \"none\",\n            \"bgColor\": \"#000000\",\n            \"bgEnabled\": false,\n            \"opacity\": 1\n          },\n          {\n            \"id\": \"text-1766446610211\",\n            \"x\": 180.63662719726562,\n            \"y\": 1128.822998046875,\n            \"content\": \"SITE A\",\n            \"fontSize\": 101,\n            \"color\": \"#e2e8f0\",\n            \"fontWeight\": \"normal\",\n            \"fontStyle\": \"normal\",\n            \"textAlign\": \"start\",\n            \"textDecoration\": \"none\",\n            \"bgColor\": \"#000000\",\n            \"bgEnabled\": false,\n            \"opacity\": 1\n          },\n          {\n            \"id\": \"text-1766453024797\",\n            \"x\": 968.6458740234375,\n            \"y\": 1028.6621398925781,\n            \"content\": \"SITE A\",\n            \"fontSize\": 101,\n            \"color\": \"#e2e8f0\",\n            \"fontWeight\": \"normal\",\n            \"fontStyle\": \"normal\",\n            \"textAlign\": \"start\",\n            \"textDecoration\": \"none\",\n            \"bgColor\": \"#000000\",\n            \"bgEnabled\": false,\n            \"opacity\": 1,\n            \"rotation\": -89,\n            \"_dragStartX\": 972.46826171875,\n            \"_dragStartY\": 1009.5499572753906\n          },\n          {\n            \"id\": \"text-1766453070975\",\n            \"x\": 613.1589965820312,\n            \"y\": 1139.512939453125,\n            \"content\": \"SITE A\",\n            \"fontSize\": 101,\n            \"color\": \"#e2e8f0\",\n            \"fontWeight\": \"normal\",\n            \"fontStyle\": \"normal\",\n            \"textAlign\": \"start\",\n            \"textDecoration\": \"none\",\n            \"bgColor\": \"#000000\",\n            \"bgEnabled\": false,\n            \"opacity\": 1\n          },\n          {\n            \"id\": \"text-1766453072857\",\n            \"x\": 968.64599609375,\n            \"y\": 474.40818786621094,\n            \"content\": \"SITE A\",\n            \"fontSize\": 101,\n            \"color\": \"#e2e8f0\",\n            \"fontWeight\": \"normal\",\n            \"fontStyle\": \"normal\",\n            \"textAlign\": \"start\",\n            \"textDecoration\": \"none\",\n            \"bgColor\": \"#000000\",\n            \"bgEnabled\": false,\n            \"opacity\": 1,\n            \"rotation\": 269,\n            \"_dragStartX\": 1480.85302734375,\n            \"_dragStartY\": 822.2503356933594\n          }\n        ]\n      },\n      \"pageState\": {\n        \"title\": \"The One File Corporate\",\n        \"background\": \"\",\n        \"topbarBg\": \"rgba(9, 12, 20, 0.9)\",\n        \"topbarBorder\": \"#1f2533\",\n        \"panel\": \"#0b0e13\",\n        \"panelAlt\": \"#10141b\",\n        \"accent\": \"#4fd1c5\",\n        \"sidebarBg\": \"#10141b\",\n        \"btnBg\": \"#0b0e13\",\n        \"btnText\": \"#e2e8f0\",\n        \"tagFill\": \"#1e293b\",\n        \"tagText\": \"#e2e8f0\",\n        \"tagBorder\": \"#475569\",\n        \"inputBg\": \"#0b0e13\",\n        \"inputText\": \"#e2e8f0\",\n        \"inputBorder\": \"#1f2937\",\n        \"inputFont\": \"Inter, system-ui, sans-serif\",\n        \"inputFontSize\": 14,\n        \"toolbarBg\": \"#0f172a\",\n        \"toolbarBorder\": \"#1f2937\",\n        \"toolbarText\": \"#94a3b8\",\n        \"toolbarBtnBg\": \"#0b0e13\",\n        \"toolbarBtnText\": \"#e2e8f0\",\n        \"minimapDots\": \"#94a3b8\",\n        \"canvasHintEnabled\": true,\n        \"canvasHintText\": \"\",\n        \"canvasHintBg\": \"#0f172a\",\n        \"canvasHintColor\": \"#94a3b8\",\n        \"danger\": \"#f56565\",\n        \"textMain\": \"#e2e8f0\",\n        \"textSoft\": \"#94a3b8\",\n        \"topbarHeight\": 103,\n        \"sidebarWidth\": 350,\n        \"mobileFooterHeight\": 40,\n        \"sidebarCollapsed\": false,\n        \"nodeFill\": \"#1e293b\",\n        \"nodeStroke\": \"#475569\",\n        \"nodeTitle\": \"#e2e8f0\",\n        \"nodeSub\": \"#94a3b8\",\n        \"nodeTitleSize\": 41,\n        \"nodeSubSize\": 27,\n        \"nodeFont\": \"monospace\",\n        \"defaultEdge\": \"#475569\",\n        \"selectionHandle\": \"#f59e0b\",\n        \"selectionHandleSize\": 8,\n        \"groupIndicator\": \"#4fd1c5\",\n        \"canvasGradientTop\": \"#1e2532\",\n        \"canvasGradientBottom\": \"#050608\",\n        \"canvasBorder\": \"#475569\",\n        \"canvasGrid\": \"#475569\",\n        \"canvasGridSize\": 50,\n        \"canvasGridEnabled\": true,\n        \"rackFrameFill\": \"#0f172a\",\n        \"rackFrameStroke\": \"#4fd1c5\",\n        \"rackLineColor\": \"#475569\",\n        \"rackTextColor\": \"#4fd1c5\",\n        \"rackGridEnabled\": true,\n        \"viewOnly\": false,\n        \"defaultEdgeRouting\": \"orthogonal\",\n        \"animateConnections\": false,\n        \"animationStyle\": \"arrows\",\n        \"animationDirection\": \"all\",\n        \"animationSpeed\": 4,\n        \"autoPingEnabled\": false,\n        \"autoPingInterval\": 30\n      }\n    },\n    {\n      \"id\": \"tab-1765235136918\",\n      \"name\": \"Homelab 2\",\n      \"nodes\": {\n        \"internet\": {\n          \"shape\": \"square\",\n          \"name\": \"Internet\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"internet-copy\": {\n          \"shape\": \"firewall\",\n          \"name\": \"OPNSENSE\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"opnsense-copy\": {\n          \"shape\": \"firewall\",\n          \"name\": \"Docker\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"docker-copy\": {\n          \"shape\": \"firewall\",\n          \"name\": \"Docker2\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"docker-copy-1\": {\n          \"shape\": \"firewall\",\n          \"name\": \"Docker3\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"docker-copy-2\": {\n          \"shape\": \"firewall\",\n          \"name\": \"Docker 4\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [\n            {\n              \"type\": \"icon\",\n              \"library\": \"selfhst\",\n              \"name\": \"docker\"\n            },\n            {\n              \"type\": \"icon\",\n              \"library\": \"selfhst\",\n              \"name\": \"authentik\"\n            },\n            {\n              \"type\": \"icon\",\n              \"library\": \"selfhst\",\n              \"name\": \"immich\"\n            }\n          ],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"opnsense-copy-1\": {\n          \"shape\": \"firewall\",\n          \"name\": \"OPNSENSE GUEST\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"phone\": {\n          \"shape\": \"phone\",\n          \"name\": \"Phone\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"desktop\": {\n          \"shape\": \"pc\",\n          \"name\": \"Desktop\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dns\": {\n          \"shape\": \"cloud\",\n          \"name\": \"DNS\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"racked\": {\n          \"shape\": \"server\",\n          \"name\": \"Racked\",\n          \"ip\": \"\",\n          \"role\": \"Rack\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": true,\n          \"locked\": false,\n          \"groupId\": null\n        }\n      },\n      \"edges\": {\n        \"list\": [\n          {\n            \"id\": \"internet-internet-copy-1765238145151\",\n            \"from\": \"internet\",\n            \"to\": \"internet-copy\",\n            \"width\": 4,\n            \"color\": \"#55e208\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1\n          },\n          {\n            \"id\": \"internet-copy-opnsense-copy-1765238187451\",\n            \"from\": \"internet-copy\",\n            \"to\": \"opnsense-copy\",\n            \"width\": 4,\n            \"color\": \"#4c00ff\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1\n          },\n          {\n            \"id\": \"internet-copy-docker-copy-1765238242477\",\n            \"from\": \"internet-copy\",\n            \"to\": \"docker-copy\",\n            \"width\": 4,\n            \"color\": \"#4c00ff\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1\n          },\n          {\n            \"id\": \"internet-copy-docker-copy-1-1765238244637\",\n            \"from\": \"internet-copy\",\n            \"to\": \"docker-copy-1\",\n            \"width\": 4,\n            \"color\": \"#4c00ff\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1\n          },\n          {\n            \"id\": \"internet-copy-docker-copy-2-1765238246233\",\n            \"from\": \"internet-copy\",\n            \"to\": \"docker-copy-2\",\n            \"width\": 4,\n            \"color\": \"#4c00ff\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1\n          },\n          {\n            \"id\": \"internet-opnsense-copy-1-1765238266117\",\n            \"from\": \"internet\",\n            \"to\": \"opnsense-copy-1\",\n            \"width\": 4,\n            \"color\": \"#80ff00\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1\n          },\n          {\n            \"id\": \"opnsense-copy-1-dns-1765238347996\",\n            \"from\": \"opnsense-copy-1\",\n            \"to\": \"dns\",\n            \"width\": 4,\n            \"color\": \"#fb00ff\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1\n          },\n          {\n            \"id\": \"dns-desktop-1765238386101\",\n            \"from\": \"dns\",\n            \"to\": \"desktop\",\n            \"width\": 4,\n            \"color\": \"#ff00d0\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1\n          },\n          {\n            \"id\": \"phone-dns-1765238391156\",\n            \"from\": \"phone\",\n            \"to\": \"dns\",\n            \"width\": 4,\n            \"color\": \"#ff00d0\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1\n          },\n          {\n            \"id\": \"custom-1765239449323\",\n            \"type\": \"custom\",\n            \"color\": \"#f97316\",\n            \"width\": 4,\n            \"lineStyle\": \"solid\",\n            \"direction\": \"forward\",\n            \"points\": [\n              {\n                \"x\": 2936.464111328125,\n                \"y\": 786.07958984375\n              },\n              {\n                \"x\": 3184.112060546875,\n                \"y\": 887.6153564453125\n              },\n              {\n                \"x\": 2763.110107421875,\n                \"y\": 981.7216796875\n              }\n            ],\n            \"notes\": []\n          }\n        ]\n      },\n      \"positions\": {\n        \"internet\": {\n          \"x\": 2103.968290880771,\n          \"y\": 268\n        },\n        \"internet-copy\": {\n          \"x\": 2066.9677515897347,\n          \"y\": 473.4119134177565\n        },\n        \"opnsense-copy\": {\n          \"x\": 1773.8400660428597,\n          \"y\": 666.5758233298659\n        },\n        \"docker-copy\": {\n          \"x\": 1931.1978950081452,\n          \"y\": 782.2775961320921\n        },\n        \"docker-copy-1\": {\n          \"x\": 2158.1262397347077,\n          \"y\": 767.7122274797483\n        },\n        \"docker-copy-2\": {\n          \"x\": 2342.2663764534577,\n          \"y\": 631.7681967180296\n        },\n        \"opnsense-copy-1\": {\n          \"x\": 2757.879480087803,\n          \"y\": 307.6117116091891\n        },\n        \"phone\": {\n          \"x\": 3312.857751572178,\n          \"y\": 502.58220111114224\n        },\n        \"desktop\": {\n          \"x\": 2971.700036728428,\n          \"y\": 480.7287465212985\n        },\n        \"dns\": {\n          \"x\": 3200.4643189549906,\n          \"y\": 320.469591247861\n        },\n        \"racked\": {\n          \"x\": 2645.5845448279656,\n          \"y\": 970.7820678889219\n        }\n      },\n      \"sizes\": {\n        \"core-router-1\": 36,\n        \"internet\": 168,\n        \"phone\": 121,\n        \"desktop\": 147,\n        \"racked\": 137,\n        \"docker-copy-2\": 82\n      },\n      \"styles\": {\n        \"internet\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"amazon-web-services\"\n            },\n            \"circleColor\": \"#ffffff\",\n            \"circleBorder\": \"#ffffff\"\n          }\n        },\n        \"opnsense-copy-1\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"opnsense-v1\"\n            }\n          }\n        },\n        \"internet-copy\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"opnsense\"\n            }\n          }\n        },\n        \"docker-copy-2\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"docker\"\n            }\n          }\n        },\n        \"docker-copy-1\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"authportal\"\n            }\n          }\n        },\n        \"docker-copy\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"jotty\"\n            }\n          }\n        },\n        \"opnsense-copy\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"portainer\"\n            }\n          }\n        },\n        \"racked\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"mdi\",\n              \"name\": \"server-security\"\n            },\n            \"circleColor\": \"#010813\",\n            \"circleBorder\": \"#ffffff\"\n          }\n        }\n      },\n      \"legend\": {\n        \"#475569\": \"you can edit me too\",\n        \"#65758b\": \"you can edit me too\",\n        \"#63748c\": \"you can edit me too\",\n        \"#5e6f87\": \"you can edit me too\",\n        \"#586a84\": \"you can edit me too\",\n        \"#4f627d\": \"you can edit me too\",\n        \"#455873\": \"you can edit me too\",\n        \"#3d506c\": \"you can edit me too\",\n        \"#354964\": \"you can edit me too\",\n        \"#2e415c\": \"you can edit me too\",\n        \"#293c56\": \"you can edit me too\",\n        \"#273a53\": \"you can edit me too\",\n        \"#253750\": \"you can edit me too\",\n        \"#23354d\": \"you can edit me too\",\n        \"#203046\": \"you can edit me too\",\n        \"#1e2d43\": \"you can edit me too\",\n        \"#1a283d\": \"you can edit me too\",\n        \"#172435\": \"you can edit me too\",\n        \"#141f2e\": \"you can edit me too\",\n        \"#111a27\": \"you can edit me too\",\n        \"#0f1824\": \"you can edit me too\",\n        \"#0d1521\": \"you can edit me too\",\n        \"#0c131d\": \"you can edit me too\",\n        \"#0c1d1c\": \"you can edit me too\",\n        \"#0c1c1d\": \"you can edit me too\",\n        \"#0c191d\": \"you can edit me too\",\n        \"#0c141d\": \"you can edit me too\",\n        \"#0c0d1d\": \"you can edit me too\",\n        \"#130c1d\": \"you can edit me too\",\n        \"#1b0c1d\": \"you can edit me too\",\n        \"#1d0c17\": \"you can edit me too\",\n        \"#1d0c10\": \"you can edit me too\",\n        \"#1d0c0c\": \"you can edit me too\",\n        \"#3b1b1b\": \"you can edit me too\",\n        \"#3c1a1a\": \"you can edit me too\",\n        \"#3f1c1c\": \"you can edit me too\",\n        \"#401c1c\": \"you can edit me too\",\n        \"#451c1c\": \"you can edit me too\",\n        \"#461b1b\": \"you can edit me too\",\n        \"#4c1a1a\": \"you can edit me too\",\n        \"#521919\": \"you can edit me too\",\n        \"#571919\": \"you can edit me too\",\n        \"#5d1818\": \"you can edit me too\",\n        \"#631717\": \"you can edit me too\",\n        \"#651515\": \"you can edit me too\",\n        \"#6a1616\": \"you can edit me too\",\n        \"#6f1515\": \"you can edit me too\",\n        \"#711414\": \"you can edit me too\",\n        \"#761414\": \"you can edit me too\",\n        \"#771313\": \"you can edit me too\",\n        \"#7c1313\": \"you can edit me too\",\n        \"#811313\": \"you can edit me too\",\n        \"#821212\": \"you can edit me too\",\n        \"#871212\": \"you can edit me too\",\n        \"#881111\": \"you can edit me too\",\n        \"#8d1111\": \"you can edit me too\",\n        \"#8e1010\": \"you can edit me too\",\n        \"#8f0f0f\": \"you can edit me too\",\n        \"#900e0e\": \"you can edit me too\",\n        \"#8e0b0b\": \"you can edit me too\",\n        \"#8c0d0d\": \"you can edit me too\",\n        \"#880c0c\": \"you can edit me too\",\n        \"#830c0c\": \"you can edit me too\",\n        \"#7e0c0c\": \"you can edit me too\",\n        \"#790c0c\": \"you can edit me too\",\n        \"#730c0c\": \"you can edit me too\",\n        \"#6f0b0b\": \"you can edit me too\",\n        \"#0b6f64\": \"you can edit me too\",\n        \"#0b6f5f\": \"you can edit me too\",\n        \"#0b6f56\": \"you can edit me too\",\n        \"#0b6f49\": \"you can edit me too\",\n        \"#0b6f31\": \"you can edit me too\",\n        \"#0b6f1f\": \"you can edit me too\",\n        \"#0b6f0d\": \"you can edit me too\",\n        \"#176f0b\": \"you can edit me too\",\n        \"#266f0b\": \"you can edit me too\",\n        \"#296f0b\": \"you can edit me too\",\n        \"#2e6f0b\": \"you can edit me too\",\n        \"#1a2d10\": \"you can edit me too\",\n        \"#1c3111\": \"you can edit me too\",\n        \"#213814\": \"you can edit me too\",\n        \"#233c15\": \"you can edit me too\",\n        \"#254017\": \"you can edit me too\",\n        \"#294918\": \"you can edit me too\",\n        \"#2b4d1a\": \"you can edit me too\",\n        \"#2d511a\": \"you can edit me too\",\n        \"#315a1b\": \"you can edit me too\",\n        \"#35631c\": \"you can edit me too\",\n        \"#37681d\": \"you can edit me too\",\n        \"#3b721d\": \"you can edit me too\",\n        \"#3f7b1e\": \"you can edit me too\",\n        \"#42851e\": \"you can edit me too\",\n        \"#46901d\": \"you can edit me too\",\n        \"#499a1d\": \"you can edit me too\",\n        \"#4b9f1d\": \"you can edit me too\",\n        \"#4ca61c\": \"you can edit me too\",\n        \"#50b01c\": \"you can edit me too\",\n        \"#51b71a\": \"you can edit me too\",\n        \"#50b918\": \"you can edit me too\",\n        \"#51c115\": \"you can edit me too\",\n        \"#53c615\": \"you can edit me too\",\n        \"#53c814\": \"you can edit me too\",\n        \"#52c913\": \"you can edit me too\",\n        \"#54d011\": \"you can edit me too\",\n        \"#53d110\": \"you can edit me too\",\n        \"#55d510\": \"you can edit me too\",\n        \"#55d70f\": \"you can edit me too\",\n        \"#54d80e\": \"you can edit me too\",\n        \"#54da0b\": \"you can edit me too\",\n        \"#56df0c\": \"you can edit me too\",\n        \"#53db0a\": \"you can edit me too\",\n        \"#55e00b\": \"you can edit me too\",\n        \"#55e109\": \"you can edit me too\",\n        \"#55e208\": \"ISP LINE\",\n        \"#4c00ff\": \"MY Guest NETWORK\",\n        \"#80ff00\": \"you can edit me too\",\n        \"#3b4234\": \"you can edit me too\",\n        \"#3a3442\": \"you can edit me too\",\n        \"#3b3442\": \"you can edit me too\",\n        \"#3c3442\": \"you can edit me too\",\n        \"#3d3442\": \"you can edit me too\",\n        \"#3e3442\": \"you can edit me too\",\n        \"#3f3442\": \"you can edit me too\",\n        \"#403442\": \"you can edit me too\",\n        \"#413442\": \"you can edit me too\",\n        \"#653d66\": \"you can edit me too\",\n        \"#683f69\": \"you can edit me too\",\n        \"#6c416c\": \"you can edit me too\",\n        \"#6f4370\": \"you can edit me too\",\n        \"#704270\": \"you can edit me too\",\n        \"#734474\": \"you can edit me too\",\n        \"#784479\": \"you can edit me too\",\n        \"#7d447e\": \"you can edit me too\",\n        \"#7e437f\": \"you can edit me too\",\n        \"#834384\": \"you can edit me too\",\n        \"#844285\": \"you can edit me too\",\n        \"#89418b\": \"you can edit me too\",\n        \"#8e428f\": \"you can edit me too\",\n        \"#904091\": \"you can edit me too\",\n        \"#923e93\": \"you can edit me too\",\n        \"#973e98\": \"you can edit me too\",\n        \"#943c96\": \"you can edit me too\",\n        \"#993c9a\": \"you can edit me too\",\n        \"#963a98\": \"you can edit me too\",\n        \"#973899\": \"you can edit me too\",\n        \"#99369b\": \"you can edit me too\",\n        \"#9a359c\": \"you can edit me too\",\n        \"#9b349d\": \"you can edit me too\",\n        \"#9d329f\": \"you can edit me too\",\n        \"#9e31a0\": \"you can edit me too\",\n        \"#a02fa2\": \"you can edit me too\",\n        \"#9d2d9f\": \"you can edit me too\",\n        \"#9f2ba1\": \"you can edit me too\",\n        \"#a129a3\": \"you can edit me too\",\n        \"#a327a5\": \"you can edit me too\",\n        \"#a525a7\": \"you can edit me too\",\n        \"#a723a9\": \"you can edit me too\",\n        \"#a921ab\": \"you can edit me too\",\n        \"#ab1fad\": \"you can edit me too\",\n        \"#ad1daf\": \"you can edit me too\",\n        \"#ae1cb0\": \"you can edit me too\",\n        \"#b019b3\": \"you can edit me too\",\n        \"#b118b4\": \"you can edit me too\",\n        \"#b316b6\": \"you can edit me too\",\n        \"#b816bb\": \"you can edit me too\",\n        \"#b514b8\": \"you can edit me too\",\n        \"#ba14bd\": \"you can edit me too\",\n        \"#b712ba\": \"you can edit me too\",\n        \"#bb13be\": \"you can edit me too\",\n        \"#b811bb\": \"you can edit me too\",\n        \"#be10c1\": \"you can edit me too\",\n        \"#bb0ebe\": \"you can edit me too\",\n        \"#bd0cc0\": \"you can edit me too\",\n        \"#be0bc1\": \"you can edit me too\",\n        \"#c108c4\": \"you can edit me too\",\n        \"#be06c1\": \"you can edit me too\",\n        \"#c103c4\": \"you can edit me too\",\n        \"#c301c6\": \"you can edit me too\",\n        \"#c400c7\": \"you can edit me too\",\n        \"#c900cc\": \"you can edit me too\",\n        \"#ce00d1\": \"you can edit me too\",\n        \"#d300d6\": \"you can edit me too\",\n        \"#d800db\": \"you can edit me too\",\n        \"#dd00e0\": \"you can edit me too\",\n        \"#e200e6\": \"you can edit me too\",\n        \"#ec00f0\": \"you can edit me too\",\n        \"#f100f5\": \"you can edit me too\",\n        \"#f600fa\": \"you can edit me too\",\n        \"#fb00ff\": \"you can edit me too\",\n        \"#ff00d0\": \"iPhone (always guest iPhone)\",\n        \"#f97316\": \"you can edit me too\"\n      },\n      \"rects\": {\n        \"list\": [\n          {\n            \"id\": \"rect-1765238219615\",\n            \"x\": 2680.053955078125,\n            \"y\": 251.44879150390625,\n            \"width\": 814.10400390625,\n            \"height\": 389.26678466796875,\n            \"color\": \"#ec0999\",\n            \"style\": \"filled\",\n            \"lineStyle\": \"solid\",\n            \"notes\": []\n          }\n        ]\n      },\n      \"texts\": {\n        \"list\": [\n          {\n            \"id\": \"text-1765238422602\",\n            \"x\": 2466.35986328125,\n            \"y\": 741.6801147460938,\n            \"content\": \"Double click on desktop\\nor long press on mobile\\nto enter rack canvas view\",\n            \"fontSize\": 40,\n            \"color\": \"#e2e8f0\",\n            \"fontWeight\": \"normal\",\n            \"fontStyle\": \"normal\",\n            \"textAlign\": \"start\",\n            \"textDecoration\": \"none\",\n            \"bgColor\": \"#000000\",\n            \"bgEnabled\": false,\n            \"opacity\": 1\n          }\n        ]\n      },\n      \"pageState\": {\n        \"title\": \"The One File\",\n        \"background\": \"\",\n        \"topbarBg\": \"rgba(9, 12, 20, 0.9)\",\n        \"topbarBorder\": \"#1f2533\",\n        \"panel\": \"#2f0e0e\",\n        \"panelAlt\": \"#10141b\",\n        \"accent\": \"#a75252\",\n        \"sidebarBg\": \"#10141b\",\n        \"btnBg\": \"#0b0e13\",\n        \"btnText\": \"#e2e8f0\",\n        \"tagFill\": \"#1e293b\",\n        \"tagText\": \"#e2e8f0\",\n        \"tagBorder\": \"#475569\",\n        \"inputBg\": \"#0b0e13\",\n        \"inputText\": \"#e2e8f0\",\n        \"inputBorder\": \"#1f2937\",\n        \"inputFont\": \"Inter, system-ui, sans-serif\",\n        \"inputFontSize\": 14,\n        \"toolbarBg\": \"#441215\",\n        \"toolbarBorder\": \"#1f2937\",\n        \"toolbarText\": \"#94a3b8\",\n        \"toolbarBtnBg\": \"#0b0e13\",\n        \"toolbarBtnText\": \"#e2e8f0\",\n        \"minimapDots\": \"#94a3b8\",\n        \"canvasHintEnabled\": true,\n        \"canvasHintText\": \"\",\n        \"canvasHintBg\": \"#0f172a\",\n        \"canvasHintColor\": \"#94a3b8\",\n        \"danger\": \"#f56565\",\n        \"textMain\": \"#e2e8f0\",\n        \"textSoft\": \"#94a3b8\",\n        \"topbarHeight\": 112,\n        \"sidebarWidth\": 350,\n        \"mobileFooterHeight\": 40,\n        \"sidebarCollapsed\": false,\n        \"nodeFill\": \"#1e293b\",\n        \"nodeStroke\": \"#475569\",\n        \"nodeTitle\": \"#e2e8f0\",\n        \"nodeSub\": \"#94a3b8\",\n        \"nodeTitleSize\": 18,\n        \"nodeSubSize\": 13,\n        \"nodeFont\": \"Inter, system-ui, sans-serif\",\n        \"defaultEdge\": \"#475569\",\n        \"selectionHandle\": \"#f59e0b\",\n        \"selectionHandleSize\": 8,\n        \"groupIndicator\": \"#4fd1c5\",\n        \"canvasGradientTop\": \"#1e2532\",\n        \"canvasGradientBottom\": \"#050608\",\n        \"canvasBorder\": \"#475569\",\n        \"canvasGrid\": \"#475569\",\n        \"canvasGridSize\": 50,\n        \"canvasGridEnabled\": true,\n        \"rackFrameFill\": \"#0f172a\",\n        \"rackFrameStroke\": \"#4fd1c5\",\n        \"rackLineColor\": \"#475569\",\n        \"rackTextColor\": \"#4fd1c5\",\n        \"rackGridEnabled\": true,\n        \"viewOnly\": false,\n        \"defaultEdgeRouting\": \"curved\",\n        \"animateConnections\": false,\n        \"animationStyle\": \"arrows\",\n        \"animationDirection\": \"all\",\n        \"animationSpeed\": 1.5\n      }\n    }\n  ],\n  \"currentTabIndex\": 0,\n  \"encryptedSections\": {},\n  \"auditLog\": [\n    {\n      \"timestamp\": 1766459550267,\n      \"type\": \"export\",\n      \"description\": \"Exported JSON: the-one-file-corporate.json\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459544255,\n      \"type\": \"export\",\n      \"description\": \"Exported Markdown: the-one-file-corporate.md\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459540242,\n      \"type\": \"export\",\n      \"description\": \"Exported CSV: the-one-file-corporate.csv\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459536815,\n      \"type\": \"export\",\n      \"description\": \"Exported JSON: the-one-file-corporate.json\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459534490,\n      \"type\": \"tab\",\n      \"description\": \"Switched to tab: Corporate Site B\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459525616,\n      \"type\": \"export\",\n      \"description\": \"Exported CSV: the-one-file.csv\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766459518367,\n      \"type\": \"export\",\n      \"description\": \"Exported Markdown: the-one-file.md\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766459511746,\n      \"type\": \"export\",\n      \"description\": \"Exported JSON: the-one-file.json\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766459504374,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file.html\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766459500911,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file.html\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766459497380,\n      \"type\": \"tab\",\n      \"description\": \"Switched to tab: Homelab 2\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766459491436,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file-corporate.html\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459483682,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file-corporate.html\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459477676,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file-corporate.html\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766457578277,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766457564726,\n      \"type\": \"tab\",\n      \"description\": \"Switched to tab: Corporate Site B\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766457564253,\n      \"type\": \"tab\",\n      \"description\": \"Switched to tab: Homelab 2\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766457560309,\n      \"type\": \"import\",\n      \"description\": \"Imported JSON: the-one-file-corporate.json (107 nodes, 65 connections)\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455847368,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455844534,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455844054,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455843762,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455843560,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455843371,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455843162,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455842852,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455842747,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455842601,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455842449,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455842348,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455842098,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455841678,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455841236,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455841053,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455840901,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455840650,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455839427,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455839234,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455839061,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455837247,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455837081,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455836893,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455836377,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455836198,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455835455,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455834630,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455831880,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455831676,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455831451,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455830817,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455830687,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455830176,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455830048,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455829944,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455829816,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455378795,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455378693,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455378459,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455378316,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455378180,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455378069,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455377956,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455377677,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455377558,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455377448,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455377318,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455377209,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453090534,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453090317,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453090213,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453090112,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453090009,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453089903,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453088895,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453088793,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453088689,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453088584,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453088480,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453088250,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453087236,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453086725,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453086485,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453086373,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453086142,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453086043,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453072857,\n      \"type\": \"text\",\n      \"description\": \"paste text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453070975,\n      \"type\": \"text\",\n      \"description\": \"paste text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453054439,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453053127,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453052450,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453052106,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453051948,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453051806,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453051334,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453050207,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453042725,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453042179,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453041797,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453041570,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453039703,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453039291,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453039168,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453039065,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453038481,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453038365,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453038237,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453038105,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453038001,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453037850,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453037745,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453037495,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453037378,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453037182,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453037078,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453036972,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453036860,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453036147,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453035945,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453035825,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453035720,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453035443,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453035337,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453035233,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453035127,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453035026,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453034917,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453031063,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453030955,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453030833,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453030732,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453030225,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453030104,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453029968,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453029796,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453029474,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453024797,\n      \"type\": \"text\",\n      \"description\": \"paste text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766451118553,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450929324,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450817210,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450257424,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450255024,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450254395,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450253241,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450251598,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450250392,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450248756,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450244072,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450242166,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450240998,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450236492,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450233672,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450232384,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450231012,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450230254,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450229302,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450228132,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446610211,\n      \"type\": \"text\",\n      \"description\": \"paste text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446604849,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446604702,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446604550,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446604404,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446604305,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446604204,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446604099,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446603952,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446603849,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446603702,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446603599,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446603452,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446603348,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446603202,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446603099,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446602953,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446602850,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446602702,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446602600,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446602453,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446602349,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446602204,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446602101,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446602000,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446601848,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446601702,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446601601,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446601452,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446601301,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446601154,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446601049,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446600948,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446600802,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446600550,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446598595,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446598461,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446598171,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446598017,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446597219,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446595278,\n      \"type\": \"text\",\n      \"description\": \"add text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445633355,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445632515,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445631735,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445630757,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445627846,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445625085,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445618645,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445617784,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445608998,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445608720,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445608540,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445608376,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445608204,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445608038,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445607852,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445607678,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445607506,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445607319,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445607154,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445604410,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445604244,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445604066,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445603900,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445603743,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445603563,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445603406,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445603226,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445603052,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445602880,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445602641,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445576567,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animation speed\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445570290,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animation speed\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445567192,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445566766,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445565520,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445398115,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445390895,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445385694,\n      \"type\": \"edit\",\n      \"description\": \"toggle fov animation\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445383241,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445382911,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445381695,\n      \"type\": \"edit\",\n      \"description\": \"edit node name\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445375383,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445374665,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445373273,\n      \"type\": \"node\",\n      \"description\": \"paste node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445372205,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438157980,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438157430,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animation speed\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438152691,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438151948,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438151286,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438146174,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438145649,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438144555,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438143655,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438142504,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animation speed\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438130077,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438129561,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438128772,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438128398,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438122820,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438122062,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438119836,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438119588,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438095045,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438093965,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438062827,\n      \"type\": \"edit\",\n      \"description\": \"toggle fov animation\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438047679,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438044161,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438041852,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438039668,\n      \"type\": \"edit\",\n      \"description\": \"resize node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438039562,\n      \"type\": \"edit\",\n      \"description\": \"resize node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438039421,\n      \"type\": \"edit\",\n      \"description\": \"resize node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438039260,\n      \"type\": \"edit\",\n      \"description\": \"resize node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438039150,\n      \"type\": \"edit\",\n      \"description\": \"resize node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438039039,\n      \"type\": \"edit\",\n      \"description\": \"resize node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438028508,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438021410,\n      \"type\": \"edit\",\n      \"description\": \"toggle fov\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438019234,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438017562,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438014356,\n      \"type\": \"node\",\n      \"description\": \"add node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437981696,\n      \"type\": \"edit\",\n      \"description\": \"apply routing to all\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437966551,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437964879,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437963627,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437961813,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437961193,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437957989,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437956467,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437953437,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437952239,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437950807,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437944990,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437943699,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437935414,\n      \"type\": \"zone\",\n      \"description\": \"draw zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437919019,\n      \"type\": \"zone\",\n      \"description\": \"delete zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437917758,\n      \"type\": \"zone\",\n      \"description\": \"draw zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437913740,\n      \"type\": \"zone\",\n      \"description\": \"draw zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437882832,\n      \"type\": \"import\",\n      \"description\": \"Imported JSON: theonefile-networkening-corporate-demo.json (105 nodes, 65 connections)\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766263279163,\n      \"type\": \"tab\",\n      \"description\": \"Switched to tab: Corporate Site B\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766263270414,\n      \"type\": \"export\",\n      \"description\": \"Exported JSON: the-one-file.json\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766263260682,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file.html\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766263259518,\n      \"type\": \"tab\",\n      \"description\": \"Switched to tab: Homelab 2\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766263249401,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file-corporate.html\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766263246362,\n      \"type\": \"import\",\n      \"description\": \"Imported JSON: theonefile-networkening-corporate-demo.json (105 nodes, 65 connections)\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766190721141,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file-corporate.html\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766190717499,\n      \"type\": \"tab\",\n      \"description\": \"Switched to tab: Corporate Site B\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766190710946,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file.html\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766190705273,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file.html\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766190703463,\n      \"type\": \"tab\",\n      \"description\": \"Switched to tab: Homelab 2\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766190695709,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file-corporate.html\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766190688417,\n      \"type\": \"import\",\n      \"description\": \"Imported JSON: theonefile-networkening-corporate-demo.json (105 nodes, 65 connections)\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1765402888416,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file-corporate.html\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1765402884873,\n      \"type\": \"tab\",\n      \"description\": \"Switched to tab: Corporate Site B\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1765402878108,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file.html\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1765402866440,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file.html\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1765402865008,\n      \"type\": \"tab\",\n      \"description\": \"Switched to tab: Homelab 2\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1765402860428,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file-corporate.html\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1765402858103,\n      \"type\": \"import\",\n      \"description\": \"Imported JSON: theonefile-networkening-corporate-demo.json (105 nodes, 65 connections)\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    }\n  ],\n  \"savedStyleSets\": []\n}"
  },
  {
    "path": "demos/json-exports/theonefile-networkening-homelab-demo.json",
    "content": "{\n  \"nodeData\": {\n    \"internet\": {\n      \"shape\": \"square\",\n      \"name\": \"Internet\",\n      \"ip\": \"0.0.0.0\",\n      \"role\": \"\",\n      \"tags\": [],\n      \"notes\": [],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"internet-copy\": {\n      \"shape\": \"firewall\",\n      \"name\": \"OPNSENSE\",\n      \"ip\": \"0.0.0.0\",\n      \"role\": \"\",\n      \"tags\": [],\n      \"notes\": [],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"opnsense-copy\": {\n      \"shape\": \"firewall\",\n      \"name\": \"Docker\",\n      \"ip\": \"0.0.0.0\",\n      \"role\": \"\",\n      \"tags\": [],\n      \"notes\": [],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"docker-copy\": {\n      \"shape\": \"firewall\",\n      \"name\": \"Docker2\",\n      \"ip\": \"0.0.0.0\",\n      \"role\": \"\",\n      \"tags\": [],\n      \"notes\": [],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"docker-copy-1\": {\n      \"shape\": \"firewall\",\n      \"name\": \"Docker3\",\n      \"ip\": \"0.0.0.0\",\n      \"role\": \"\",\n      \"tags\": [],\n      \"notes\": [],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"docker-copy-2\": {\n      \"shape\": \"firewall\",\n      \"name\": \"Docker 4\",\n      \"ip\": \"0.0.0.0\",\n      \"role\": \"\",\n      \"tags\": [\n        {\n          \"type\": \"icon\",\n          \"library\": \"selfhst\",\n          \"name\": \"docker\"\n        },\n        {\n          \"type\": \"icon\",\n          \"library\": \"selfhst\",\n          \"name\": \"authentik\"\n        },\n        {\n          \"type\": \"icon\",\n          \"library\": \"selfhst\",\n          \"name\": \"immich\"\n        }\n      ],\n      \"notes\": [],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"opnsense-copy-1\": {\n      \"shape\": \"firewall\",\n      \"name\": \"OPNSENSE GUEST\",\n      \"ip\": \"0.0.0.0\",\n      \"role\": \"\",\n      \"tags\": [],\n      \"notes\": [],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"phone\": {\n      \"shape\": \"phone\",\n      \"name\": \"Phone\",\n      \"ip\": \"0.0.0.0\",\n      \"role\": \"\",\n      \"tags\": [],\n      \"notes\": [],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"desktop\": {\n      \"shape\": \"pc\",\n      \"name\": \"Desktop\",\n      \"ip\": \"0.0.0.0\",\n      \"role\": \"\",\n      \"tags\": [],\n      \"notes\": [],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"dns\": {\n      \"shape\": \"cloud\",\n      \"name\": \"DNS\",\n      \"ip\": \"0.0.0.0\",\n      \"role\": \"\",\n      \"tags\": [],\n      \"notes\": [],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"racked\": {\n      \"shape\": \"server\",\n      \"name\": \"Racked\",\n      \"ip\": \"\",\n      \"role\": \"Rack\",\n      \"tags\": [],\n      \"notes\": [],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": true,\n      \"locked\": false,\n      \"groupId\": null\n    }\n  },\n  \"edgeData\": {\n    \"list\": [\n      {\n        \"id\": \"internet-internet-copy-1765238145151\",\n        \"from\": \"internet\",\n        \"to\": \"internet-copy\",\n        \"width\": 4,\n        \"color\": \"#55e208\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1\n      },\n      {\n        \"id\": \"internet-copy-opnsense-copy-1765238187451\",\n        \"from\": \"internet-copy\",\n        \"to\": \"opnsense-copy\",\n        \"width\": 4,\n        \"color\": \"#4c00ff\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1\n      },\n      {\n        \"id\": \"internet-copy-docker-copy-1765238242477\",\n        \"from\": \"internet-copy\",\n        \"to\": \"docker-copy\",\n        \"width\": 4,\n        \"color\": \"#4c00ff\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1\n      },\n      {\n        \"id\": \"internet-copy-docker-copy-1-1765238244637\",\n        \"from\": \"internet-copy\",\n        \"to\": \"docker-copy-1\",\n        \"width\": 4,\n        \"color\": \"#4c00ff\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1\n      },\n      {\n        \"id\": \"internet-copy-docker-copy-2-1765238246233\",\n        \"from\": \"internet-copy\",\n        \"to\": \"docker-copy-2\",\n        \"width\": 4,\n        \"color\": \"#4c00ff\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1\n      },\n      {\n        \"id\": \"internet-opnsense-copy-1-1765238266117\",\n        \"from\": \"internet\",\n        \"to\": \"opnsense-copy-1\",\n        \"width\": 4,\n        \"color\": \"#80ff00\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1\n      },\n      {\n        \"id\": \"opnsense-copy-1-dns-1765238347996\",\n        \"from\": \"opnsense-copy-1\",\n        \"to\": \"dns\",\n        \"width\": 4,\n        \"color\": \"#fb00ff\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1\n      },\n      {\n        \"id\": \"dns-desktop-1765238386101\",\n        \"from\": \"dns\",\n        \"to\": \"desktop\",\n        \"width\": 4,\n        \"color\": \"#ff00d0\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1\n      },\n      {\n        \"id\": \"phone-dns-1765238391156\",\n        \"from\": \"phone\",\n        \"to\": \"dns\",\n        \"width\": 4,\n        \"color\": \"#ff00d0\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1\n      },\n      {\n        \"id\": \"custom-1765239449323\",\n        \"type\": \"custom\",\n        \"color\": \"#f97316\",\n        \"width\": 4,\n        \"lineStyle\": \"solid\",\n        \"direction\": \"forward\",\n        \"points\": [\n          {\n            \"x\": 2936.464111328125,\n            \"y\": 786.07958984375\n          },\n          {\n            \"x\": 3184.112060546875,\n            \"y\": 887.6153564453125\n          },\n          {\n            \"x\": 2763.110107421875,\n            \"y\": 981.7216796875\n          }\n        ],\n        \"notes\": []\n      }\n    ]\n  },\n  \"rectData\": {\n    \"list\": [\n      {\n        \"id\": \"rect-1765238219615\",\n        \"x\": 2680.053955078125,\n        \"y\": 251.44879150390625,\n        \"width\": 814.10400390625,\n        \"height\": 389.26678466796875,\n        \"color\": \"#ec0999\",\n        \"style\": \"filled\",\n        \"lineStyle\": \"solid\",\n        \"notes\": []\n      }\n    ]\n  },\n  \"textData\": {\n    \"list\": [\n      {\n        \"id\": \"text-1765238422602\",\n        \"x\": 2466.35986328125,\n        \"y\": 741.6801147460938,\n        \"content\": \"Double click on desktop\\nor long press on mobile\\nto enter rack canvas view\",\n        \"fontSize\": 40,\n        \"color\": \"#e2e8f0\",\n        \"fontWeight\": \"normal\",\n        \"fontStyle\": \"normal\",\n        \"textAlign\": \"start\",\n        \"textDecoration\": \"none\",\n        \"bgColor\": \"#000000\",\n        \"bgEnabled\": false,\n        \"opacity\": 1\n      }\n    ]\n  },\n  \"edgeLegend\": {\n    \"#475569\": \"you can edit me too\",\n    \"#65758b\": \"you can edit me too\",\n    \"#63748c\": \"you can edit me too\",\n    \"#5e6f87\": \"you can edit me too\",\n    \"#586a84\": \"you can edit me too\",\n    \"#4f627d\": \"you can edit me too\",\n    \"#455873\": \"you can edit me too\",\n    \"#3d506c\": \"you can edit me too\",\n    \"#354964\": \"you can edit me too\",\n    \"#2e415c\": \"you can edit me too\",\n    \"#293c56\": \"you can edit me too\",\n    \"#273a53\": \"you can edit me too\",\n    \"#253750\": \"you can edit me too\",\n    \"#23354d\": \"you can edit me too\",\n    \"#203046\": \"you can edit me too\",\n    \"#1e2d43\": \"you can edit me too\",\n    \"#1a283d\": \"you can edit me too\",\n    \"#172435\": \"you can edit me too\",\n    \"#141f2e\": \"you can edit me too\",\n    \"#111a27\": \"you can edit me too\",\n    \"#0f1824\": \"you can edit me too\",\n    \"#0d1521\": \"you can edit me too\",\n    \"#0c131d\": \"you can edit me too\",\n    \"#0c1d1c\": \"you can edit me too\",\n    \"#0c1c1d\": \"you can edit me too\",\n    \"#0c191d\": \"you can edit me too\",\n    \"#0c141d\": \"you can edit me too\",\n    \"#0c0d1d\": \"you can edit me too\",\n    \"#130c1d\": \"you can edit me too\",\n    \"#1b0c1d\": \"you can edit me too\",\n    \"#1d0c17\": \"you can edit me too\",\n    \"#1d0c10\": \"you can edit me too\",\n    \"#1d0c0c\": \"you can edit me too\",\n    \"#3b1b1b\": \"you can edit me too\",\n    \"#3c1a1a\": \"you can edit me too\",\n    \"#3f1c1c\": \"you can edit me too\",\n    \"#401c1c\": \"you can edit me too\",\n    \"#451c1c\": \"you can edit me too\",\n    \"#461b1b\": \"you can edit me too\",\n    \"#4c1a1a\": \"you can edit me too\",\n    \"#521919\": \"you can edit me too\",\n    \"#571919\": \"you can edit me too\",\n    \"#5d1818\": \"you can edit me too\",\n    \"#631717\": \"you can edit me too\",\n    \"#651515\": \"you can edit me too\",\n    \"#6a1616\": \"you can edit me too\",\n    \"#6f1515\": \"you can edit me too\",\n    \"#711414\": \"you can edit me too\",\n    \"#761414\": \"you can edit me too\",\n    \"#771313\": \"you can edit me too\",\n    \"#7c1313\": \"you can edit me too\",\n    \"#811313\": \"you can edit me too\",\n    \"#821212\": \"you can edit me too\",\n    \"#871212\": \"you can edit me too\",\n    \"#881111\": \"you can edit me too\",\n    \"#8d1111\": \"you can edit me too\",\n    \"#8e1010\": \"you can edit me too\",\n    \"#8f0f0f\": \"you can edit me too\",\n    \"#900e0e\": \"you can edit me too\",\n    \"#8e0b0b\": \"you can edit me too\",\n    \"#8c0d0d\": \"you can edit me too\",\n    \"#880c0c\": \"you can edit me too\",\n    \"#830c0c\": \"you can edit me too\",\n    \"#7e0c0c\": \"you can edit me too\",\n    \"#790c0c\": \"you can edit me too\",\n    \"#730c0c\": \"you can edit me too\",\n    \"#6f0b0b\": \"you can edit me too\",\n    \"#0b6f64\": \"you can edit me too\",\n    \"#0b6f5f\": \"you can edit me too\",\n    \"#0b6f56\": \"you can edit me too\",\n    \"#0b6f49\": \"you can edit me too\",\n    \"#0b6f31\": \"you can edit me too\",\n    \"#0b6f1f\": \"you can edit me too\",\n    \"#0b6f0d\": \"you can edit me too\",\n    \"#176f0b\": \"you can edit me too\",\n    \"#266f0b\": \"you can edit me too\",\n    \"#296f0b\": \"you can edit me too\",\n    \"#2e6f0b\": \"you can edit me too\",\n    \"#1a2d10\": \"you can edit me too\",\n    \"#1c3111\": \"you can edit me too\",\n    \"#213814\": \"you can edit me too\",\n    \"#233c15\": \"you can edit me too\",\n    \"#254017\": \"you can edit me too\",\n    \"#294918\": \"you can edit me too\",\n    \"#2b4d1a\": \"you can edit me too\",\n    \"#2d511a\": \"you can edit me too\",\n    \"#315a1b\": \"you can edit me too\",\n    \"#35631c\": \"you can edit me too\",\n    \"#37681d\": \"you can edit me too\",\n    \"#3b721d\": \"you can edit me too\",\n    \"#3f7b1e\": \"you can edit me too\",\n    \"#42851e\": \"you can edit me too\",\n    \"#46901d\": \"you can edit me too\",\n    \"#499a1d\": \"you can edit me too\",\n    \"#4b9f1d\": \"you can edit me too\",\n    \"#4ca61c\": \"you can edit me too\",\n    \"#50b01c\": \"you can edit me too\",\n    \"#51b71a\": \"you can edit me too\",\n    \"#50b918\": \"you can edit me too\",\n    \"#51c115\": \"you can edit me too\",\n    \"#53c615\": \"you can edit me too\",\n    \"#53c814\": \"you can edit me too\",\n    \"#52c913\": \"you can edit me too\",\n    \"#54d011\": \"you can edit me too\",\n    \"#53d110\": \"you can edit me too\",\n    \"#55d510\": \"you can edit me too\",\n    \"#55d70f\": \"you can edit me too\",\n    \"#54d80e\": \"you can edit me too\",\n    \"#54da0b\": \"you can edit me too\",\n    \"#56df0c\": \"you can edit me too\",\n    \"#53db0a\": \"you can edit me too\",\n    \"#55e00b\": \"you can edit me too\",\n    \"#55e109\": \"you can edit me too\",\n    \"#55e208\": \"ISP LINE\",\n    \"#4c00ff\": \"MY Guest NETWORK\",\n    \"#80ff00\": \"you can edit me too\",\n    \"#3b4234\": \"you can edit me too\",\n    \"#3a3442\": \"you can edit me too\",\n    \"#3b3442\": \"you can edit me too\",\n    \"#3c3442\": \"you can edit me too\",\n    \"#3d3442\": \"you can edit me too\",\n    \"#3e3442\": \"you can edit me too\",\n    \"#3f3442\": \"you can edit me too\",\n    \"#403442\": \"you can edit me too\",\n    \"#413442\": \"you can edit me too\",\n    \"#653d66\": \"you can edit me too\",\n    \"#683f69\": \"you can edit me too\",\n    \"#6c416c\": \"you can edit me too\",\n    \"#6f4370\": \"you can edit me too\",\n    \"#704270\": \"you can edit me too\",\n    \"#734474\": \"you can edit me too\",\n    \"#784479\": \"you can edit me too\",\n    \"#7d447e\": \"you can edit me too\",\n    \"#7e437f\": \"you can edit me too\",\n    \"#834384\": \"you can edit me too\",\n    \"#844285\": \"you can edit me too\",\n    \"#89418b\": \"you can edit me too\",\n    \"#8e428f\": \"you can edit me too\",\n    \"#904091\": \"you can edit me too\",\n    \"#923e93\": \"you can edit me too\",\n    \"#973e98\": \"you can edit me too\",\n    \"#943c96\": \"you can edit me too\",\n    \"#993c9a\": \"you can edit me too\",\n    \"#963a98\": \"you can edit me too\",\n    \"#973899\": \"you can edit me too\",\n    \"#99369b\": \"you can edit me too\",\n    \"#9a359c\": \"you can edit me too\",\n    \"#9b349d\": \"you can edit me too\",\n    \"#9d329f\": \"you can edit me too\",\n    \"#9e31a0\": \"you can edit me too\",\n    \"#a02fa2\": \"you can edit me too\",\n    \"#9d2d9f\": \"you can edit me too\",\n    \"#9f2ba1\": \"you can edit me too\",\n    \"#a129a3\": \"you can edit me too\",\n    \"#a327a5\": \"you can edit me too\",\n    \"#a525a7\": \"you can edit me too\",\n    \"#a723a9\": \"you can edit me too\",\n    \"#a921ab\": \"you can edit me too\",\n    \"#ab1fad\": \"you can edit me too\",\n    \"#ad1daf\": \"you can edit me too\",\n    \"#ae1cb0\": \"you can edit me too\",\n    \"#b019b3\": \"you can edit me too\",\n    \"#b118b4\": \"you can edit me too\",\n    \"#b316b6\": \"you can edit me too\",\n    \"#b816bb\": \"you can edit me too\",\n    \"#b514b8\": \"you can edit me too\",\n    \"#ba14bd\": \"you can edit me too\",\n    \"#b712ba\": \"you can edit me too\",\n    \"#bb13be\": \"you can edit me too\",\n    \"#b811bb\": \"you can edit me too\",\n    \"#be10c1\": \"you can edit me too\",\n    \"#bb0ebe\": \"you can edit me too\",\n    \"#bd0cc0\": \"you can edit me too\",\n    \"#be0bc1\": \"you can edit me too\",\n    \"#c108c4\": \"you can edit me too\",\n    \"#be06c1\": \"you can edit me too\",\n    \"#c103c4\": \"you can edit me too\",\n    \"#c301c6\": \"you can edit me too\",\n    \"#c400c7\": \"you can edit me too\",\n    \"#c900cc\": \"you can edit me too\",\n    \"#ce00d1\": \"you can edit me too\",\n    \"#d300d6\": \"you can edit me too\",\n    \"#d800db\": \"you can edit me too\",\n    \"#dd00e0\": \"you can edit me too\",\n    \"#e200e6\": \"you can edit me too\",\n    \"#ec00f0\": \"you can edit me too\",\n    \"#f100f5\": \"you can edit me too\",\n    \"#f600fa\": \"you can edit me too\",\n    \"#fb00ff\": \"you can edit me too\",\n    \"#ff00d0\": \"iPhone (always guest iPhone)\",\n    \"#f97316\": \"you can edit me too\"\n  },\n  \"nodePositions\": {\n    \"internet\": {\n      \"x\": 2103.968290880771,\n      \"y\": 268\n    },\n    \"internet-copy\": {\n      \"x\": 2066.9677515897347,\n      \"y\": 473.4119134177565\n    },\n    \"opnsense-copy\": {\n      \"x\": 1773.8400660428597,\n      \"y\": 666.5758233298659\n    },\n    \"docker-copy\": {\n      \"x\": 1931.1978950081452,\n      \"y\": 782.2775961320921\n    },\n    \"docker-copy-1\": {\n      \"x\": 2158.1262397347077,\n      \"y\": 767.7122274797483\n    },\n    \"docker-copy-2\": {\n      \"x\": 2342.2663764534577,\n      \"y\": 631.7681967180296\n    },\n    \"opnsense-copy-1\": {\n      \"x\": 2757.879480087803,\n      \"y\": 307.6117116091891\n    },\n    \"phone\": {\n      \"x\": 3312.857751572178,\n      \"y\": 502.58220111114224\n    },\n    \"desktop\": {\n      \"x\": 2971.700036728428,\n      \"y\": 480.7287465212985\n    },\n    \"dns\": {\n      \"x\": 3200.4643189549906,\n      \"y\": 320.469591247861\n    },\n    \"racked\": {\n      \"x\": 2645.5845448279656,\n      \"y\": 970.7820678889219\n    }\n  },\n  \"nodeSizes\": {\n    \"core-router-1\": 36,\n    \"internet\": 168,\n    \"phone\": 121,\n    \"desktop\": 147,\n    \"racked\": 137,\n    \"docker-copy-2\": 82\n  },\n  \"nodeStyles\": {\n    \"internet\": {\n      \"all\": {\n        \"icon\": {\n          \"library\": \"selfhst\",\n          \"name\": \"amazon-web-services\"\n        },\n        \"circleColor\": \"#ffffff\",\n        \"circleBorder\": \"#ffffff\"\n      }\n    },\n    \"opnsense-copy-1\": {\n      \"all\": {\n        \"icon\": {\n          \"library\": \"selfhst\",\n          \"name\": \"opnsense-v1\"\n        }\n      }\n    },\n    \"internet-copy\": {\n      \"all\": {\n        \"icon\": {\n          \"library\": \"selfhst\",\n          \"name\": \"opnsense\"\n        }\n      }\n    },\n    \"docker-copy-2\": {\n      \"all\": {\n        \"icon\": {\n          \"library\": \"selfhst\",\n          \"name\": \"docker\"\n        }\n      }\n    },\n    \"docker-copy-1\": {\n      \"all\": {\n        \"icon\": {\n          \"library\": \"selfhst\",\n          \"name\": \"authportal\"\n        }\n      }\n    },\n    \"docker-copy\": {\n      \"all\": {\n        \"icon\": {\n          \"library\": \"selfhst\",\n          \"name\": \"jotty\"\n        }\n      }\n    },\n    \"opnsense-copy\": {\n      \"all\": {\n        \"icon\": {\n          \"library\": \"selfhst\",\n          \"name\": \"portainer\"\n        }\n      }\n    },\n    \"racked\": {\n      \"all\": {\n        \"icon\": {\n          \"library\": \"mdi\",\n          \"name\": \"server-security\"\n        },\n        \"circleColor\": \"#010813\",\n        \"circleBorder\": \"#ffffff\"\n      }\n    }\n  },\n  \"iconCache\": {\n    \"selfhst-borg\": \"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" xml:space=\\\"preserve\\\" viewBox=\\\"0 0 512 512\\\"><path d=\\\"M331 102.2H204.5V0h197.9L505 102.2v104.2l-51.3 51.3L505 309v100.7L402.4 512H204.5V409.8H331V300.6H204.5v-89.1H331zM6.9 0h170.5v512H6.9z\\\" style=\\\"fill:#0d0\\\"/></svg>\",\n    \"selfhst-actual-budget\": \"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" xml:space=\\\"preserve\\\" viewBox=\\\"0 0 512 512\\\"><path d=\\\"M378.5 512h-245C59.8 512 0 452.2 0 378.5v-245C0 59.8 59.8 0 133.5 0h245C452.2 0 512 59.8 512 133.5v245c0 73.7-59.8 133.5-133.5 133.5\\\" style=\\\"fill:#805ad5\\\"/><path d=\\\"m407.7 384.1-33.2-68.2 26.9-10.4c1.7-.6 2.6-2.6 1.9-4.3l-7.8-20.2c-.6-1.7-2.6-2.6-4.3-1.9L362 290.3 268.8 99.2c-.6-1.2-1.7-1.9-3-1.9h-6.3c-1.3 0-2.4.7-3 1.9l-95 201.6-55.2 20.3c-1.8.6-2.6 2.6-2 4.3l7.4 20.3c.6 1.8 2.6 2.6 4.3 2l28.4-10.4-32.2 68.3c-.3.8-.4 1.7 0 2.5l1.6 4.3v.1c.6 1.7 2.6 2.6 4.3 1.9l229.8-88.3 34.3 70.4c.8 1.7 2.8 2.4 4.5 1.6l19.5-9.5c1.6-.8 2.3-2.8 1.5-4.5M263 151.8l46.1 94.9L199.4 287zM161.5 367.4l20.7-44 139.4-51.1 13.7 28.3z\\\" style=\\\"fill:#fff\\\"/></svg>\",\n    \"selfhst-anonaddy\": \"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" xml:space=\\\"preserve\\\" viewBox=\\\"0 0 512 512\\\"><linearGradient id=\\\"anonaddy_svg__a\\\" x1=\\\"44.378\\\" x2=\\\"447.022\\\" y1=\\\"386.378\\\" y2=\\\"789.022\\\" gradientTransform=\\\"translate(0 -278)\\\" gradientUnits=\\\"userSpaceOnUse\\\"><stop offset=\\\"0\\\" style=\\\"stop-color:#3ae7e1\\\"/><stop offset=\\\".948\\\" style=\\\"stop-color:#1993d2\\\"/></linearGradient><path d=\\\"M320 224H192c-17.7 0-32 14.3-32 32v128c0 17.7 14.3 32 32 32h128c17.7 0 32-14.3 32-32V256c0-17.7-14.3-32-32-32m-32 128h-64v-64h64z\\\" style=\\\"fill:url(#anonaddy_svg__a)\\\"/><linearGradient id=\\\"anonaddy_svg__b\\\" x1=\\\"44.337\\\" x2=\\\"446.981\\\" y1=\\\"386.418\\\" y2=\\\"789.062\\\" gradientTransform=\\\"translate(0 -278)\\\" gradientUnits=\\\"userSpaceOnUse\\\"><stop offset=\\\"0\\\" style=\\\"stop-color:#3ae7e1\\\"/><stop offset=\\\".948\\\" style=\\\"stop-color:#1993d2\\\"/></linearGradient><path d=\\\"M384 384c0 5.5-.8 10.9-2.1 16 39.4-21.8 66.1-63.8 66.1-112v-32c0-47.4-25.8-88.6-64-110.7-18.8-10.9-40.7-17.3-64-17.3H192c-23.3 0-45.2 6.4-64 17.3-38.2 22.1-64 63.3-64 110.7v128c0 70.7 57.3 128 128 128h128c0-35.3-28.7-64-64-64h-64c-35.3 0-64-28.7-64-64V256c0-35.3 28.7-64 64-64h128c35.3 0 64 28.7 64 64z\\\" style=\\\"fill:url(#anonaddy_svg__b)\\\"/><path d=\\\"M384 288c0 23.7-12.9 44.3-32 55.4V384c0 17.7-14.3 32-32 32 22.5 0 43.5-5.8 61.9-16 1.3-5.1 2.1-10.5 2.1-16zM128 145.3c18.8-10.9 40.7-17.3 64-17.3 0-11.7 3.2-22.6 8.7-32H192c-21.4 0-42.7 4.4-62.4 12.8-1 6.3-1.6 12.7-1.6 19.2zM311.3 96c5.5 9.4 8.7 20.3 8.7 32 23.3 0 45.2 6.4 64 17.3V128c0-6.5-.6-12.9-1.6-19.2-19.7-8.4-41-12.8-62.4-12.8z\\\" style=\\\"fill:#2d7aae\\\"/><linearGradient id=\\\"anonaddy_svg__c\\\" x1=\\\"158.782\\\" x2=\\\"561.426\\\" y1=\\\"271.974\\\" y2=\\\"674.618\\\" gradientTransform=\\\"translate(0 -278)\\\" gradientUnits=\\\"userSpaceOnUse\\\"><stop offset=\\\"0\\\" style=\\\"stop-color:#3ae7e1\\\"/><stop offset=\\\".948\\\" style=\\\"stop-color:#1993d2\\\"/></linearGradient><path d=\\\"M200.7 96c11.1-19.1 31.7-32 55.3-32s44.3 12.9 55.3 32h8.7c21.4 0 42.7 4.4 62.4 12.8C373.1 47.3 320 0 256 0S138.9 47.3 129.6 108.8c19.7-8.4 41-12.8 62.4-12.8z\\\" style=\\\"fill:url(#anonaddy_svg__c)\\\"/></svg>\",\n    \"selfhst-adguard-home\": \"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" xml:space=\\\"preserve\\\" viewBox=\\\"0 0 512 512\\\"><path d=\\\"M505.8 58.8C428.2 18.4 334.1 0 256 0h-.3v511.8c-21.5-12.7-41.1-26-59.1-39.6 18 13.7 37.8 27 59.4 39.8C506.8 363.4 505.8 146 505.8 58.8\\\" style=\\\"fill-rule:evenodd;clip-rule:evenodd;fill:#68bc71\\\"/><path d=\\\"M255.7 511.8C5.2 363.3 6.2 146 6.2 58.8 83.7 18.4 177.7 0 255.7 0z\\\" style=\\\"fill-rule:evenodd;clip-rule:evenodd;fill:#67b279\\\"/><path d=\\\"m246.9 341.6 151-203.6c-11.1-8.9-20.8-2.6-26.1 2.2h-.2l-125.9 131-47.4-57.1c-22.6-26.2-53.4-6.2-60.6-.9z\\\" style=\\\"fill-rule:evenodd;clip-rule:evenodd;fill:#fff\\\"/></svg>\",\n    \"selfhst-ansible\": \"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" xml:space=\\\"preserve\\\" viewBox=\\\"0 0 512 512\\\"><path d=\\\"M512 256c0 141.4-114.6 256-256 256S0 397.4 0 256 114.6 0 256 0s256 114.6 256 256\\\" style=\\\"fill:#c32214\\\"/><path d=\\\"m260.3 156.4 66.2 163.5-100.1-78.8zM378 357.6 276.1 112.3c-2.9-7.1-8.7-10.8-15.8-10.8s-13.3 3.7-16.2 10.8l-111.8 269h38.3l44.3-110.9L347 377.1c5.3 4.3 9.1 6.2 14.1 6.2 10 0 18.7-7.5 18.7-18.3-.1-1.7-.7-4.4-1.8-7.4\\\" style=\\\"fill:#fff\\\"/></svg>\",\n    \"selfhst-wikidocs\": \"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" xml:space=\\\"preserve\\\" viewBox=\\\"0 0 512 512\\\"><path d=\\\"M488.8 72.1h-90.7c-8.4 0-15.1 6.7-15.1 15.1v206.4c0 8.4-6.7 15.1-15.1 15.1h-45.3c-8.4 0-15.1-6.7-15.1-15.1V87.2c0-8.4-6.7-15.1-15.1-15.1h-72.5c-8.4 0-15.1 6.7-15.1 15.1v206.4c0 8.4-6.7 15.1-15.1 15.1h-45.3c-8.4 0-15.1-6.7-15.1-15.1V87.2c0-8.4-6.7-15.1-15.1-15.1H23.4c-8.4 0-15.1 6.7-15.1 15.1v257.2c0 50.1 38.6 91.2 87.5 95.3v.3h392.9c8.4 0 15.1-6.7 15.1-15.1V87.2c0-8.4-6.7-15.1-15-15.1\\\" style=\\\"fill:#4caf50\\\"/><path d=\\\"M488.6 448.2H95.7c-1.1 0-2.1-.2-3.1-.6-24.9-2.7-48-14.2-65.1-32.8C9.8 395.6 0 370.5 0 344.3V87.2c0-12.9 10.5-23.4 23.4-23.4h90.9c12.9 0 23.4 10.5 23.4 23.4v206.4c0 3.8 3 6.8 6.8 6.8h45.3c3.8 0 6.8-3 6.8-6.8V87.2c0-12.9 10.5-23.4 23.4-23.4h72.5c12.9 0 23.4 10.5 23.4 23.4v206.4c0 3.8 3 6.8 6.8 6.8H368c3.8 0 6.8-3 6.8-6.8V87.2c0-12.9 10.5-23.4 23.4-23.4h90.7c6.3 0 12.2 2.5 16.6 6.9s6.7 10.3 6.6 16.6v337.6c-.1 12.8-10.6 23.3-23.5 23.3M98.1 431.7h390.6c3.8 0 6.8-3 6.8-6.8V87.2c0-1.9-.6-3.6-1.9-4.8-1.3-1.3-3-2-4.8-2h-90.7c-3.8 0-6.8 3-6.8 6.8v206.4c0 12.9-10.5 23.4-23.4 23.4h-45.3c-12.9 0-23.4-10.5-23.4-23.4V87.2c0-3.8-3-6.8-6.8-6.8h-72.5c-3.8 0-6.8 3-6.8 6.8v206.4c0 12.9-10.5 23.4-23.4 23.4h-45.3c-12.9 0-23.4-10.5-23.4-23.4V87.2c0-3.8-3-6.8-6.8-6.8H23.4c-3.8 0-6.8 3-6.8 6.8v257.2c0 45.1 35.1 83.3 79.9 87.1.5-.1 1 0 1.6.2\\\" style=\\\"fill:#fff\\\"/></svg>\",\n    \"selfhst-alist\": \"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" xml:space=\\\"preserve\\\" viewBox=\\\"0 0 512 512\\\"><path d=\\\"M260.5 30.8c5.4-.6 11 .6 15.5 3.7 6.8 4.4 11.6 11.1 15.7 17.9 18.4 30.9 37 61.7 55.4 92.6 24.5 41.3 49.1 82.7 73.6 124q25.35 42.9 51 85.8c11.2 19.1 22.8 37.9 33.5 57.3 4.6 8.1 7.2 17.5 6.8 26.8-.3 7.2-1.6 14.7-5.4 20.9-5.3 8.8-14.3 15.1-23.9 18.3-5.2 1.9-10.6 3.1-16.1 3.1H286.3c-5.2-.1-10.4-.7-15.3-2.6-3.5-1.5-7-3.4-9.8-6.1-4.7-4.7-7.8-11.3-7-18.1.4-4.7 1.6-9.4 3.5-13.7 1.9-4.4 4.5-8.5 6.8-12.7 10.1-17.9 20.6-35.6 31.5-53 3.5-5.5 6.6-11.4 11.4-15.8 5.2-4.7 11.6-8.4 18.7-8.9 5.7-.2 11.3 1.7 16.1 4.8 5.4 3.1 9.6 8.4 11 14.5 2 7.7-.2 15.8-3.4 22.9-3.7 8-8.9 15.3-11.7 23.7-.5 1.9-1.2 4.3.2 6 1.4 1.4 3.5 1.9 5.3 2.4 5.5 1.2 11.1.9 16.6.9h61.3c4-.1 8.1.1 12.1-.1 2.9-.3 6.1-.8 8.4-2.9 1.5-1.3 1.7-3.6 1.1-5.4-1.2-4-3.4-7.7-5.5-11.3C383.2 312.9 329 219.7 274 126.9c-1.8-3-3.3-6.2-5.9-8.6-1.3-1.2-3.2-1.9-4.9-1.4-1.5.5-2.5 1.8-3.5 3-2.4 3.1-4.6 6.4-6.6 9.8-9.5 15.4-18.6 31.1-27.9 46.6-53.6 90.1-107.1 180.3-160.7 270.4-3.3 6.1-6.9 12.1-10.7 17.9-3.4 5-7.5 9.6-12.8 12.6-6.2 3.6-13.9 5-20.9 3-5.1-1.6-10-4.1-13.7-8.1-4.6-4.6-6.7-11.3-6.4-17.7.2-6.7 2.5-13.3 5.9-19.1C38.4 380.1 71.1 325 104 269.9c16.9-28.5 33.7-57.1 50.7-85.6 17.2-28.8 34.4-57.7 51.5-86.5 9.5-16.3 19.2-32.5 29.2-48.5 3.1-5.1 7.1-9.6 11.9-13.1 4-2.7 8.4-5 13.2-5.4\\\" style=\\\"fill:#70c6be\\\"/><path d=\\\"M257.8 244.3c8.1-1.3 16.7.6 23.4 5.4 5.1 3.9 8.6 9.8 9.7 16.2.7 4.9.2 9.9-1.6 14.5-1.4 4-3.3 7.7-5.5 11.3-31.2 52.5-62.5 105-93.7 157.5-4 6.6-7.6 13.4-12.2 19.6-3.3 4.1-7.5 7.7-12.4 9.9-10.2 5.4-23.5 2.1-31.3-6.1-3.9-3.9-6.2-9.4-6.5-14.9-.4-5.7.8-11.5 3.4-16.7 2.3-5 5-9.7 7.8-14.4 13.7-23.1 27.3-46.3 41-69.4 18.7-31.8 37.8-63.4 56.6-95.1 2-3.6 4.5-6.9 7.3-9.9 3.8-4 8.6-7.1 14-7.9\\\" style=\\\"fill:#1ba0d8\\\"/></svg>\",\n    \"simple-amazonwebservices\": \"<svg role=\\\"img\\\" viewBox=\\\"0 0 24 24\\\" xmlns=\\\"http://www.w3.org/2000/svg\\\"><title>Amazon Web Services</title><path d=\\\"M6.763 10.036c0 .296.032.535.088.71.064.176.144.368.256.576.04.063.056.127.056.183 0 .08-.048.16-.152.24l-.503.335a.383.383 0 0 1-.208.072c-.08 0-.16-.04-.239-.112a2.47 2.47 0 0 1-.287-.375 6.18 6.18 0 0 1-.248-.471c-.622.734-1.405 1.101-2.347 1.101-.67 0-1.205-.191-1.596-.574-.391-.384-.59-.894-.59-1.533 0-.678.239-1.23.726-1.644.487-.415 1.133-.623 1.955-.623.272 0 .551.024.846.064.296.04.6.104.918.176v-.583c0-.607-.127-1.03-.375-1.277-.255-.248-.686-.367-1.3-.367-.28 0-.568.031-.863.103-.295.072-.583.16-.862.272a2.287 2.287 0 0 1-.28.104.488.488 0 0 1-.127.023c-.112 0-.168-.08-.168-.247v-.391c0-.128.016-.224.056-.28a.597.597 0 0 1 .224-.167c.279-.144.614-.264 1.005-.36a4.84 4.84 0 0 1 1.246-.151c.95 0 1.644.216 2.091.647.439.43.662 1.085.662 1.963v2.586zm-3.24 1.214c.263 0 .534-.048.822-.144.287-.096.543-.271.758-.51.128-.152.224-.32.272-.512.047-.191.08-.423.08-.694v-.335a6.66 6.66 0 0 0-.735-.136 6.02 6.02 0 0 0-.75-.048c-.535 0-.926.104-1.19.32-.263.215-.39.518-.39.917 0 .375.095.655.295.846.191.2.47.296.838.296zm6.41.862c-.144 0-.24-.024-.304-.08-.064-.048-.12-.16-.168-.311L7.586 5.55a1.398 1.398 0 0 1-.072-.32c0-.128.064-.2.191-.2h.783c.151 0 .255.025.31.08.065.048.113.16.16.312l1.342 5.284 1.245-5.284c.04-.16.088-.264.151-.312a.549.549 0 0 1 .32-.08h.638c.152 0 .256.025.32.08.063.048.12.16.151.312l1.261 5.348 1.381-5.348c.048-.16.104-.264.16-.312a.52.52 0 0 1 .311-.08h.743c.127 0 .2.065.2.2 0 .04-.009.08-.017.128a1.137 1.137 0 0 1-.056.2l-1.923 6.17c-.048.16-.104.263-.168.311a.51.51 0 0 1-.303.08h-.687c-.151 0-.255-.024-.32-.08-.063-.056-.119-.16-.15-.32l-1.238-5.148-1.23 5.14c-.04.16-.087.264-.15.32-.065.056-.177.08-.32.08zm10.256.215c-.415 0-.83-.048-1.229-.143-.399-.096-.71-.2-.918-.32-.128-.071-.215-.151-.247-.223a.563.563 0 0 1-.048-.224v-.407c0-.167.064-.247.183-.247.048 0 .096.008.144.024.048.016.12.048.2.08.271.12.566.215.878.279.319.064.63.096.95.096.502 0 .894-.088 1.165-.264a.86.86 0 0 0 .415-.758.777.777 0 0 0-.215-.559c-.144-.151-.416-.287-.807-.415l-1.157-.36c-.583-.183-1.014-.454-1.277-.813a1.902 1.902 0 0 1-.4-1.158c0-.335.073-.63.216-.886.144-.255.335-.479.575-.654.24-.184.51-.32.83-.415.32-.096.655-.136 1.006-.136.175 0 .359.008.535.032.183.024.35.056.518.088.16.04.312.08.455.127.144.048.256.096.336.144a.69.69 0 0 1 .24.2.43.43 0 0 1 .071.263v.375c0 .168-.064.256-.184.256a.83.83 0 0 1-.303-.096 3.652 3.652 0 0 0-1.532-.311c-.455 0-.815.071-1.062.223-.248.152-.375.383-.375.71 0 .224.08.416.24.567.159.152.454.304.877.44l1.134.358c.574.184.99.44 1.237.767.247.327.367.702.367 1.117 0 .343-.072.655-.207.926-.144.272-.336.511-.583.703-.248.2-.543.343-.886.447-.36.111-.734.167-1.142.167zM21.698 16.207c-2.626 1.94-6.442 2.969-9.722 2.969-4.598 0-8.74-1.7-11.87-4.526-.247-.223-.024-.527.272-.351 3.384 1.963 7.559 3.153 11.877 3.153 2.914 0 6.114-.607 9.06-1.852.439-.2.814.287.383.607zM22.792 14.961c-.336-.43-2.22-.207-3.074-.103-.255.032-.295-.192-.063-.36 1.5-1.053 3.967-.75 4.254-.399.287.36-.08 2.826-1.485 4.007-.215.184-.423.088-.327-.151.32-.79 1.03-2.57.695-2.994z\\\"/></svg>\",\n    \"simple-apple\": \"<svg role=\\\"img\\\" viewBox=\\\"0 0 24 24\\\" xmlns=\\\"http://www.w3.org/2000/svg\\\"><title>Apple</title><path d=\\\"M12.152 6.896c-.948 0-2.415-1.078-3.96-1.04-2.04.027-3.91 1.183-4.961 3.014-2.117 3.675-.546 9.103 1.519 12.09 1.013 1.454 2.208 3.09 3.792 3.039 1.52-.065 2.09-.987 3.935-.987 1.831 0 2.35.987 3.96.948 1.637-.026 2.676-1.48 3.676-2.948 1.156-1.688 1.636-3.325 1.662-3.415-.039-.013-3.182-1.221-3.22-4.857-.026-3.04 2.48-4.494 2.597-4.559-1.429-2.09-3.623-2.324-4.39-2.376-2-.156-3.675 1.09-4.61 1.09zM15.53 3.83c.843-1.012 1.4-2.427 1.245-3.83-1.207.052-2.662.805-3.532 1.818-.78.896-1.454 2.338-1.273 3.714 1.338.104 2.715-.688 3.559-1.701\\\"/></svg>\",\n    \"selfhst-amazon-web-services\": \"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" xml:space=\\\"preserve\\\" viewBox=\\\"0 0 512 512\\\"><path d=\\\"M144.3 214.1c0 6.3.7 11.4 1.9 15.2 1.4 3.7 3.1 7.8 5.4 12.3.9 1.4 1.2 2.7 1.2 3.9 0 1.7-1 3.4-3.2 5.1l-10.7 7.2c-1.5 1-3.1 1.5-4.4 1.5-1.7 0-3.4-.9-5.1-2.4-2.4-2.6-4.4-5.3-6.1-8-1.7-2.9-3.4-6.1-5.3-10-13.3 15.7-30 23.5-50.1 23.5-14.3 0-25.7-4.1-34.1-12.3-8.3-8.2-12.6-19.1-12.6-32.7 0-14.5 5.1-26.2 15.5-35.1S60.8 169 78.4 169c5.8 0 11.7.5 18.1 1.4s12.8 2.2 19.6 3.7v-12.4c0-12.9-2.7-22-8-27.2-5.4-5.3-14.6-7.8-27.8-7.8-6 0-12.1.7-18.4 2.2s-12.4 3.4-18.4 5.8c-2.7 1.2-4.8 1.9-6 2.2s-2 .5-2.7.5c-2.4 0-3.6-1.7-3.6-5.3v-8.3c0-2.7.3-4.8 1.2-6s2.4-2.4 4.8-3.6c6-3.1 13.1-5.6 21.5-7.7 8.3-2.2 17.2-3.2 26.6-3.2 20.3 0 35.1 4.6 44.6 13.8 9.4 9.2 14.1 23.2 14.1 41.9v55.2zM75.2 240c5.6 0 11.4-1 17.5-3.1 6.1-2 11.6-5.8 16.2-10.9 2.7-3.2 4.8-6.8 5.8-10.9s1.7-9 1.7-14.8v-7.2c-4.9-1.2-10.2-2.2-15.7-2.9-5.4-.7-10.7-1-16-1-11.4 0-19.8 2.2-25.4 6.8S51 207.1 51 215.6c0 8 2 14 6.3 18.1 4.1 4.2 10 6.3 17.9 6.3m136.7 18.4c-3.1 0-5.1-.5-6.5-1.7-1.4-1-2.6-3.4-3.6-6.6l-40-131.6c-1-3.4-1.5-5.6-1.5-6.8 0-2.7 1.4-4.3 4.1-4.3h16.7c3.2 0 5.4.5 6.6 1.7 1.4 1 2.4 3.4 3.4 6.6l28.6 112.7 26.6-112.7c.9-3.4 1.9-5.6 3.2-6.6 1.4-1 3.7-1.7 6.8-1.7H270c3.2 0 5.4.5 6.8 1.7 1.4 1 2.6 3.4 3.2 6.6l26.9 114.1 29.5-114.1c1-3.4 2.2-5.6 3.4-6.6 1.4-1 3.6-1.7 6.6-1.7h15.8c2.7 0 4.3 1.4 4.3 4.3 0 .9-.2 1.7-.3 2.7-.2 1-.5 2.4-1.2 4.3l-41 131.6q-1.5 5.1-3.6 6.6c-1.4 1-3.6 1.7-6.5 1.7h-14.6c-3.2 0-5.4-.5-6.8-1.7s-2.6-3.4-3.2-6.8l-26.4-109.8L236.7 250c-.9 3.4-1.9 5.6-3.2 6.8-1.4 1.2-3.7 1.7-6.8 1.7zm218.8 4.6c-8.9 0-17.7-1-26.2-3.1-8.5-2-15.2-4.3-19.6-6.8-2.7-1.5-4.6-3.2-5.3-4.8s-1-3.2-1-4.8v-8.7c0-3.6 1.4-5.3 3.9-5.3 1 0 2 .2 3.1.5 1 .3 2.6 1 4.3 1.7 5.8 2.6 12.1 4.6 18.7 6 6.8 1.4 13.5 2 20.3 2 10.7 0 19.1-1.9 24.9-5.6s8.9-9.2 8.9-16.2c0-4.8-1.5-8.7-4.6-11.9s-8.9-6.1-17.2-8.9l-24.7-7.7c-12.4-3.9-21.6-9.7-27.2-17.4-5.6-7.5-8.5-15.8-8.5-24.7 0-7.2 1.5-13.5 4.6-18.9s7.2-10.2 12.3-14c5.1-3.9 10.9-6.8 17.7-8.9 6.8-2 14-2.9 21.5-2.9 3.7 0 7.7.2 11.4.7 3.9.5 7.5 1.2 11.1 1.9 3.4.9 6.6 1.7 9.7 2.7s5.4 2 7.2 3.1c2.4 1.4 4.1 2.7 5.1 4.3 1 1.4 1.5 3.2 1.5 5.6v8c0 3.6-1.4 5.4-3.9 5.4-1.4 0-3.6-.7-6.5-2q-14.55-6.6-32.7-6.6c-9.7 0-17.4 1.5-22.6 4.8s-8 8.2-8 15.2c0 4.8 1.7 8.9 5.1 12.1s9.7 6.5 18.7 9.4l24.2 7.7c12.3 3.9 21.1 9.4 26.4 16.3s7.8 15 7.8 23.8c0 7.3-1.5 14-4.4 19.8-3.1 5.8-7.2 10.9-12.4 15-5.3 4.3-11.6 7.3-18.9 9.5-8 2.5-16 3.7-24.7 3.7\\\"/><path d=\\\"M462.9 345.7c-56 41.4-137.4 63.3-207.4 63.3-98.1 0-186.5-36.3-253.2-96.6-5.3-4.8-.5-11.2 5.8-7.5 72.2 41.9 161.3 67.3 253.4 67.3 62.2 0 130.4-12.9 193.3-39.5 9.3-4.2 17.3 6.2 8.1 13m23.3-26.5c-7.2-9.2-47.3-4.4-65.6-2.2-5.4.7-6.3-4.1-1.4-7.7 32-22.5 84.6-16 90.8-8.5 6.1 7.7-1.7 60.3-31.7 85.5-4.6 3.9-9 1.9-7-3.2 6.9-16.9 22.1-54.9 14.9-63.9\\\" style=\\\"fill-rule:evenodd;clip-rule:evenodd;fill:#f90\\\"/></svg>\",\n    \"selfhst-opnsense\": \"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" xml:space=\\\"preserve\\\" viewBox=\\\"0 0 512 512\\\"><path d=\\\"M512 160.2V400H400V112H112V0h240.4zM112 112H0v240.4L160.2 512H400V400H112z\\\" style=\\\"fill:#de3c07\\\"/></svg>\",\n    \"selfhst-portainer\": \"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" xml:space=\\\"preserve\\\" viewBox=\\\"0 0 512 512\\\"><path d=\\\"M462.2 512H49.8C22.3 512 0 489.7 0 462.2V49.8C0 22.3 22.3 0 49.8 0h412.4C489.7 0 512 22.3 512 49.8v412.4c0 27.5-22.3 49.8-49.8 49.8\\\" style=\\\"fill:#2e2f33\\\"/><path d=\\\"M108.2 63.9h140.9c97.2 0 154.6 30.4 154.6 129.5v3.4c0 99.4-57.2 129.5-154.5 129.5h-30.7V447H108.2zm134.5 177.8c30.1 0 46-11.2 46-44.6v-3.9c0-33.2-15.8-44.6-46-44.6h-24.2v93.1zM313 358h90.3v90.1H313z\\\" style=\\\"fill:#f7f6f3\\\"/></svg>\",\n    \"selfhst-jotty\": \"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" xml:space=\\\"preserve\\\" viewBox=\\\"0 0 512 512\\\"><path d=\\\"M438.6 0H73.4C33.1 0 .3 32.8.3 73.1v365.7c0 40.3 32.8 73.1 73.1 73.1h365.3c40.3 0 73.1-32.8 73.1-73.1V73.1C511.7 32.8 478.9 0 438.6 0\\\" style=\\\"fill-rule:evenodd;clip-rule:evenodd;fill:#8b3bd0\\\"/><path d=\\\"M356.8 53.6v261.3c0 39.3-13.9 72.8-41.8 100.7s-61.4 41.8-100.7 41.8l-23.8-95h23.8c13 0 24.1-4.6 33.2-13.8 9.5-9.5 14.3-20.7 14.3-33.7V148.6H157.3v-95z\\\" style=\\\"fill:#fff\\\"/></svg>\",\n    \"selfhst-authportal\": \"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" xml:space=\\\"preserve\\\" viewBox=\\\"0 0 512 512\\\"><path d=\\\"M0 0h512v512H0z\\\" style=\\\"fill:#111827\\\"/><path d=\\\"M170.7 85.3 369.8 256 170.7 426.7z\\\" style=\\\"fill:#f59e0b\\\"/></svg>\",\n    \"selfhst-docker\": \"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" xml:space=\\\"preserve\\\" viewBox=\\\"0 0 512 512\\\"><path d=\\\"M501.4 212.3c-11.5-8-38-11-58.6-7-2.4-20-13.5-37.5-32.7-53l-11-8-7.7 11.5c-9.6 15-14.4 36-13 56 .5 7 2.9 19.5 10.1 30.5-6.7 4-20.7 9-38.9 9H2.3l-1 4c-3.4 20-3.4 82.5 36 130.5 29.8 36.5 74 55 132.1 55 125.9 0 219.1-60.5 262.8-170 17.3.5 54.3 0 73-37.5.5-1 1.4-3 4.8-10.5l1.9-4zM280 71.3h-52.8v50H280zm0 60h-52.8v50H280zm-62.5 0h-52.8v50h52.8zm-62.4 0h-52.8v50h52.8zm-62.5 60H39.8v50h52.8zm62.5 0h-52.8v50h52.8zm62.4 0h-52.8v50h52.8zm62.5 0h-52.8v50H280zm62.4 0h-52.8v50h52.8z\\\" style=\\\"fill:#2396ed\\\"/></svg>\",\n    \"selfhst-opnsense-v1\": \"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" xml:space=\\\"preserve\\\" viewBox=\\\"0 0 512 512\\\"><path d=\\\"M503.1 465.4v2.1c-6 25.5-28.4 44.5-55 44.5H7.3v-46.6h38.3v7.3h403c1.1 0 2 0 2.9-.1 1.8-.1 3.6-.6 5.2-1.3 2.9-1.3 5.3-3.3 7-5.9zM63.9 0c-26.5 0-49 19-55 44.5V47H48c2.7-4.3 7.3-7.2 12.6-7.6 1-.1 2-.1 3.2-.1h402.6V47h38.3V0z\\\" style=\\\"fill:#898b8d\\\"/><path d=\\\"M466.1 157.7V197H319.5v-39.3zM45.9 315.4v39.3h146.6v-39.3zm0-157.7V197h146.6v-39.3zm273.6 157.7v39.3h146.6v-39.3z\\\" style=\\\"fill:#58595b\\\"/><path d=\\\"M83.8 78.6H428v39.3H83.8zm0 315.9H428v39.3H83.8z\\\" style=\\\"fill:#403f41\\\"/><linearGradient id=\\\"opnsense-v1_svg__a\\\" x1=\\\"-1460.617\\\" x2=\\\"-1375.11\\\" y1=\\\"1248.095\\\" y2=\\\"1248.095\\\" gradientTransform=\\\"matrix(.1853 .356 .6722 -.353 -90.66 993.802)\\\" gradientUnits=\\\"userSpaceOnUse\\\"><stop offset=\\\"0\\\" style=\\\"stop-color:#737373\\\"/><stop offset=\\\"1\\\" style=\\\"stop-color:#333\\\"/></linearGradient><path d=\\\"m466.5 78.6 38.2-20.5.1-39.5-38.3 20.7\\\" style=\\\"fill:url(#opnsense-v1_svg__a)\\\"/><linearGradient id=\\\"opnsense-v1_svg__b\\\" x1=\\\"4.634\\\" x2=\\\"50.301\\\" y1=\\\"462.6\\\" y2=\\\"462.6\\\" gradientTransform=\\\"matrix(1 0 0 -1 0 514)\\\" gradientUnits=\\\"userSpaceOnUse\\\"><stop offset=\\\"0\\\" style=\\\"stop-color:#737373\\\"/><stop offset=\\\"1\\\" style=\\\"stop-color:#333\\\"/></linearGradient><path d=\\\"M45.6 64.5v-8.9c0-5.3 2.6-10 6.5-13l-34-18.5c-6.7 9.5-10.8 21.3-10.9 34l38.3 20.6v-14c.1-.1.1-.2.1-.2\\\" style=\\\"fill:url(#opnsense-v1_svg__b)\\\"/><linearGradient id=\\\"opnsense-v1_svg__c\\\" x1=\\\"-1587.887\\\" x2=\\\"-1498.137\\\" y1=\\\"-2208.165\\\" y2=\\\"-2208.165\\\" gradientTransform=\\\"matrix(-.1853 -.356 -.6722 .353 -1743.836 694.098)\\\" gradientUnits=\\\"userSpaceOnUse\\\"><stop offset=\\\"0\\\" style=\\\"stop-color:#737373\\\"/><stop offset=\\\"1\\\" style=\\\"stop-color:#333\\\"/></linearGradient><path d=\\\"M45.6 472.7v-38.9L7.3 454.5v39.7l39.9-21.5z\\\" style=\\\"fill:url(#opnsense-v1_svg__c)\\\"/><linearGradient id=\\\"opnsense-v1_svg__d\\\" x1=\\\"460.439\\\" x2=\\\"504.436\\\" y1=\\\"53\\\" y2=\\\"53\\\" gradientTransform=\\\"matrix(1 0 0 -1 0 514)\\\" gradientUnits=\\\"userSpaceOnUse\\\"><stop offset=\\\"0\\\" style=\\\"stop-color:#737373\\\"/><stop offset=\\\"1\\\" style=\\\"stop-color:#333\\\"/></linearGradient><path d=\\\"M466.5 456.4c0 5.5-2.7 10.3-6.9 13.2l34.2 18.4c6.8-9.5 10.8-21.2 10.9-33.8L466.5 434v12.8\\\" style=\\\"fill:url(#opnsense-v1_svg__d)\\\"/><linearGradient id=\\\"opnsense-v1_svg__e\\\" x1=\\\"2521.902\\\" x2=\\\"2684.464\\\" y1=\\\"-2476.233\\\" y2=\\\"-2476.233\\\" gradientTransform=\\\"matrix(-1.508 -1.0166 -3.1265 -1.519 -3697.438 -937.952)\\\" gradientUnits=\\\"userSpaceOnUse\\\"><stop offset=\\\".107\\\" style=\\\"stop-color:#58595b\\\"/><stop offset=\\\"1\\\" style=\\\"stop-color:#000\\\"/></linearGradient><path d=\\\"M192.5 197v-.4l-72.4-38.9H45.9v.2l72.5 39.1z\\\" style=\\\"fill:url(#opnsense-v1_svg__e)\\\"/><linearGradient id=\\\"opnsense-v1_svg__f\\\" x1=\\\"-1594.953\\\" x2=\\\"-1432.391\\\" y1=\\\"278.458\\\" y2=\\\"278.458\\\" gradientTransform=\\\"matrix(1.508 -1.0166 3.1265 -1.519 1804.196 -937.952)\\\" gradientUnits=\\\"userSpaceOnUse\\\"><stop offset=\\\".107\\\" style=\\\"stop-color:#58595b\\\"/><stop offset=\\\"1\\\" style=\\\"stop-color:#000\\\"/></linearGradient><path d=\\\"M466.1 157.7h-74l-72.6 39.1v.2h74.2l72.4-38.9z\\\" style=\\\"fill:url(#opnsense-v1_svg__f)\\\"/><linearGradient id=\\\"opnsense-v1_svg__g\\\" x1=\\\"-4327.142\\\" x2=\\\"-4164.581\\\" y1=\\\"1572.387\\\" y2=\\\"1572.387\\\" gradientTransform=\\\"matrix(1.508 1.0166 3.1265 1.519 1863.938 2252.352)\\\" gradientUnits=\\\"userSpaceOnUse\\\"><stop offset=\\\".107\\\" style=\\\"stop-color:#58595b\\\"/><stop offset=\\\"1\\\" style=\\\"stop-color:#000\\\"/></linearGradient><path d=\\\"M466.1 354.7v-.4l-72.4-38.9h-74.2v.2l72.6 39.1z\\\" style=\\\"fill:url(#opnsense-v1_svg__g)\\\"/><linearGradient id=\\\"opnsense-v1_svg__h\\\" x1=\\\"-413.668\\\" x2=\\\"-246.99\\\" y1=\\\"-1046.618\\\" y2=\\\"-1046.618\\\" gradientTransform=\\\"matrix(-1.508 1.0166 -3.1265 1.519 -3638.692 2252.352)\\\" gradientUnits=\\\"userSpaceOnUse\\\"><stop offset=\\\".107\\\" style=\\\"stop-color:#58595b\\\"/><stop offset=\\\"1\\\" style=\\\"stop-color:#000\\\"/></linearGradient><path d=\\\"M192.5 315.4h-74.1l-72.5 39.1v.2h74.2l72.4-38.9z\\\" style=\\\"fill:url(#opnsense-v1_svg__h)\\\"/><linearGradient id=\\\"opnsense-v1_svg__i\\\" x1=\\\"74.725\\\" x2=\\\"261.062\\\" y1=\\\"132.65\\\" y2=\\\"132.65\\\" gradientTransform=\\\"matrix(.9914 0 0 -1 -66.782 260.2)\\\" gradientUnits=\\\"userSpaceOnUse\\\"><stop offset=\\\".237\\\" style=\\\"stop-color:#a6a8ab\\\"/><stop offset=\\\"1\\\" style=\\\"stop-color:#404040\\\"/></linearGradient><path d=\\\"M192.8 157.7V197L7.3 97.4V58.1z\\\" style=\\\"fill:url(#opnsense-v1_svg__i)\\\"/><linearGradient id=\\\"opnsense-v1_svg__j\\\" x1=\\\"-2461.056\\\" x2=\\\"-2274.718\\\" y1=\\\"132.65\\\" y2=\\\"132.65\\\" gradientTransform=\\\"matrix(-.9914 0 0 -1 -1935.19 260.2)\\\" gradientUnits=\\\"userSpaceOnUse\\\"><stop offset=\\\".237\\\" style=\\\"stop-color:#a6a8ab\\\"/><stop offset=\\\"1\\\" style=\\\"stop-color:#404040\\\"/></linearGradient><path d=\\\"M319.2 157.7V197l185.5-99.6V58.1z\\\" style=\\\"fill:url(#opnsense-v1_svg__j)\\\"/><linearGradient id=\\\"opnsense-v1_svg__k\\\" x1=\\\"-2291.845\\\" x2=\\\"-2105.508\\\" y1=\\\"-739.55\\\" y2=\\\"-739.55\\\" gradientTransform=\\\"matrix(-.9914 0 0 1 -1767.435 1124.6)\\\" gradientUnits=\\\"userSpaceOnUse\\\"><stop offset=\\\".237\\\" style=\\\"stop-color:#a6a8ab\\\"/><stop offset=\\\"1\\\" style=\\\"stop-color:#404040\\\"/></linearGradient><path d=\\\"M319.2 355.2v-39.8L504.7 415v39.7z\\\" style=\\\"fill:url(#opnsense-v1_svg__k)\\\"/><linearGradient id=\\\"opnsense-v1_svg__l\\\" x1=\\\"-94.103\\\" x2=\\\"91.934\\\" y1=\\\"-739.55\\\" y2=\\\"-739.55\\\" gradientTransform=\\\"matrix(.9914 0 0 1 100.993 1124.6)\\\" gradientUnits=\\\"userSpaceOnUse\\\"><stop offset=\\\".237\\\" style=\\\"stop-color:#a6a8ab\\\"/><stop offset=\\\"1\\\" style=\\\"stop-color:#404040\\\"/></linearGradient><path d=\\\"M192.8 355.2v-39.8L7.3 415v39.7z\\\" style=\\\"fill:url(#opnsense-v1_svg__l)\\\"/><path d=\\\"m319.2 276.1 108.9 58.5v-39.7l-35.3-18.8h111.9v-39.8H392.8l35.3-18.8v-39.3l-108.9 58.1zM83.8 334.6l109-58.5v-39.8l-109-58.1v39.3l35.7 18.8H7.3v39.8h111.9l-35.3 18.8v39.7z\\\" style=\\\"fill:#e24525\\\"/></svg>\",\n    \"mdi-server-security\": \"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" id=\\\"mdi-server-security\\\" viewBox=\\\"0 0 24 24\\\"><path d=\\\"M3,1H19A1,1 0 0,1 20,2V6A1,1 0 0,1 19,7H3A1,1 0 0,1 2,6V2A1,1 0 0,1 3,1M3,9H19A1,1 0 0,1 20,10V10.67L17.5,9.56L11,12.44V15H3A1,1 0 0,1 2,14V10A1,1 0 0,1 3,9M3,17H11C11.06,19.25 12,21.4 13.46,23H3A1,1 0 0,1 2,22V18A1,1 0 0,1 3,17M8,5H9V3H8V5M8,13H9V11H8V13M8,21H9V19H8V21M4,3V5H6V3H4M4,11V13H6V11H4M4,19V21H6V19H4M17.5,12L22,14V17C22,19.78 20.08,22.37 17.5,23C14.92,22.37 13,19.78 13,17V14L17.5,12M17.5,13.94L15,15.06V17.72C15,19.26 16.07,20.7 17.5,21.06V13.94Z\\\" /></svg>\"\n  },\n  \"page\": {\n    \"title\": \"The One File\",\n    \"background\": \"\",\n    \"topbarBg\": \"rgba(9, 12, 20, 0.9)\",\n    \"topbarBorder\": \"#1f2533\",\n    \"panel\": \"#2f0e0e\",\n    \"panelAlt\": \"#10141b\",\n    \"accent\": \"#a75252\",\n    \"sidebarBg\": \"#10141b\",\n    \"btnBg\": \"#0b0e13\",\n    \"btnText\": \"#e2e8f0\",\n    \"tagFill\": \"#1e293b\",\n    \"tagText\": \"#e2e8f0\",\n    \"tagBorder\": \"#475569\",\n    \"inputBg\": \"#0b0e13\",\n    \"inputText\": \"#e2e8f0\",\n    \"inputBorder\": \"#1f2937\",\n    \"inputFont\": \"Inter, system-ui, sans-serif\",\n    \"inputFontSize\": 14,\n    \"toolbarBg\": \"#441215\",\n    \"toolbarBorder\": \"#1f2937\",\n    \"toolbarText\": \"#94a3b8\",\n    \"toolbarBtnBg\": \"#0b0e13\",\n    \"toolbarBtnText\": \"#e2e8f0\",\n    \"minimapDots\": \"#94a3b8\",\n    \"canvasHintEnabled\": true,\n    \"canvasHintText\": \"\",\n    \"canvasHintBg\": \"#0f172a\",\n    \"canvasHintColor\": \"#94a3b8\",\n    \"danger\": \"#f56565\",\n    \"textMain\": \"#e2e8f0\",\n    \"textSoft\": \"#94a3b8\",\n    \"topbarHeight\": 112,\n    \"sidebarWidth\": 350,\n    \"mobileFooterHeight\": 40,\n    \"sidebarCollapsed\": false,\n    \"nodeFill\": \"#1e293b\",\n    \"nodeStroke\": \"#475569\",\n    \"nodeTitle\": \"#e2e8f0\",\n    \"nodeSub\": \"#94a3b8\",\n    \"nodeTitleSize\": 18,\n    \"nodeSubSize\": 13,\n    \"nodeFont\": \"Inter, system-ui, sans-serif\",\n    \"defaultEdge\": \"#475569\",\n    \"selectionHandle\": \"#f59e0b\",\n    \"selectionHandleSize\": 8,\n    \"groupIndicator\": \"#4fd1c5\",\n    \"canvasGradientTop\": \"#1e2532\",\n    \"canvasGradientBottom\": \"#050608\",\n    \"canvasBorder\": \"#475569\",\n    \"canvasGrid\": \"#475569\",\n    \"canvasGridSize\": 50,\n    \"canvasGridEnabled\": true,\n    \"rackFrameFill\": \"#0f172a\",\n    \"rackFrameStroke\": \"#4fd1c5\",\n    \"rackLineColor\": \"#475569\",\n    \"rackTextColor\": \"#4fd1c5\",\n    \"rackGridEnabled\": true,\n    \"viewOnly\": false,\n    \"defaultEdgeRouting\": \"curved\",\n    \"animateConnections\": false,\n    \"animationStyle\": \"arrows\",\n    \"animationDirection\": \"all\",\n    \"animationSpeed\": 1.5\n  },\n  \"autoPingEnabled\": false,\n  \"autoPingInterval\": 30,\n  \"canvas\": {\n    \"zoom\": 0.9921985961590549,\n    \"panX\": -5.584863670202822,\n    \"panY\": -99.90831573327841\n  },\n  \"savedTopologyView\": {\n    \"zoom\": 0.9325110211947125,\n    \"panX\": -563.7108933103631,\n    \"panY\": -561.6887674556383\n  },\n  \"documentTabs\": [\n    {\n      \"id\": \"main\",\n      \"name\": \"Corporate Site B\",\n      \"nodes\": {\n        \"core-router-1\": {\n          \"shape\": \"router\",\n          \"name\": \"Core Router 1\",\n          \"ip\": \"10.0.0.1\",\n          \"role\": \"Core Routing\",\n          \"tags\": [\n            \"core\",\n            \"tier-1\",\n            \"redundant\"\n          ],\n          \"notes\": [\n            \"Primary core router\",\n            \"BGP peering enabled\"\n          ],\n          \"mac\": \"00:1A:2B:3C:4D:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"core-router-2\": {\n          \"shape\": \"router\",\n          \"name\": \"Core Router 2\",\n          \"ip\": \"10.0.0.2\",\n          \"role\": \"Core Routing\",\n          \"tags\": [\n            \"core\",\n            \"tier-1\",\n            \"redundant\"\n          ],\n          \"notes\": [\n            \"Secondary core router\",\n            \"HSRP standby\"\n          ],\n          \"mac\": \"00:1A:2B:3C:4D:02\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null,\n          \"ping\": {\n            \"enabled\": true,\n            \"protocol\": \"custom\",\n            \"customUrl\": \"https://google.com\",\n            \"timeout\": 3000,\n            \"status\": \"online\",\n            \"lastCheck\": \"2025-12-09T00:15:04.343Z\"\n          }\n        },\n        \"fw-external-1\": {\n          \"shape\": \"firewall\",\n          \"name\": \"External FW 1\",\n          \"ip\": \"10.0.1.1\",\n          \"role\": \"Perimeter Security\",\n          \"tags\": [\n            \"security\",\n            \"perimeter\",\n            \"ha-pair\"\n          ],\n          \"notes\": [\n            \"Palo Alto PA-5250\",\n            \"Active node\"\n          ],\n          \"mac\": \"00:1A:2B:3C:4D:10\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"fw-external-2\": {\n          \"shape\": \"firewall\",\n          \"name\": \"External FW 2\",\n          \"ip\": \"10.0.1.2\",\n          \"role\": \"Perimeter Security\",\n          \"tags\": [\n            \"security\",\n            \"perimeter\",\n            \"ha-pair\"\n          ],\n          \"notes\": [\n            \"Palo Alto PA-5250\",\n            \"Passive node\"\n          ],\n          \"mac\": \"00:1A:2B:3C:4D:11\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"fw-internal\": {\n          \"shape\": \"firewall\",\n          \"name\": \"Internal FW\",\n          \"ip\": \"10.0.2.1\",\n          \"role\": \"Internal Segmentation\",\n          \"tags\": [\n            \"security\",\n            \"internal\"\n          ],\n          \"notes\": [\n            \"East-West traffic inspection\"\n          ],\n          \"mac\": \"00:1A:2B:3C:4D:12\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"core-switch-1\": {\n          \"shape\": \"switch\",\n          \"name\": \"Core Switch 1\",\n          \"ip\": \"10.0.10.1\",\n          \"role\": \"Core Switching\",\n          \"tags\": [\n            \"core\",\n            \"layer3\",\n            \"redundant\"\n          ],\n          \"notes\": [\n            \"Cisco Nexus 9000\",\n            \"VPC Domain 1\"\n          ],\n          \"mac\": \"00:1A:2B:3C:4D:20\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"core-switch-2\": {\n          \"shape\": \"switch\",\n          \"name\": \"Core Switch 2\",\n          \"ip\": \"10.0.10.2\",\n          \"role\": \"Core Switching\",\n          \"tags\": [\n            \"core\",\n            \"layer3\",\n            \"redundant\"\n          ],\n          \"notes\": [\n            \"Cisco Nexus 9000\",\n            \"VPC Domain 1\"\n          ],\n          \"mac\": \"00:1A:2B:3C:4D:21\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dc-rack-a1\": {\n          \"shape\": \"server\",\n          \"name\": \"DC Rack A1\",\n          \"ip\": \"10.10.0.0/24\",\n          \"role\": \"Data Center Rack\",\n          \"tags\": [\n            \"datacenter\",\n            \"row-a\",\n            \"production\"\n          ],\n          \"notes\": [\n            \"Row A, Position 1\",\n            \"Primary compute\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": true,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dc-rack-a2\": {\n          \"shape\": \"server\",\n          \"name\": \"DC Rack A2\",\n          \"ip\": \"10.10.1.0/24\",\n          \"role\": \"Data Center Rack\",\n          \"tags\": [\n            \"datacenter\",\n            \"row-a\",\n            \"production\"\n          ],\n          \"notes\": [\n            \"Row A, Position 2\",\n            \"Primary compute\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": true,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dc-rack-b1\": {\n          \"shape\": \"server\",\n          \"name\": \"DC Rack B1\",\n          \"ip\": \"10.10.2.0/24\",\n          \"role\": \"Data Center Rack\",\n          \"tags\": [\n            \"datacenter\",\n            \"row-b\",\n            \"storage\"\n          ],\n          \"notes\": [\n            \"Row B, Position 1\",\n            \"Storage systems\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": true,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dc-rack-b2\": {\n          \"shape\": \"server\",\n          \"name\": \"DC Rack B2\",\n          \"ip\": \"10.10.3.0/24\",\n          \"role\": \"Data Center Rack\",\n          \"tags\": [\n            \"datacenter\",\n            \"row-b\",\n            \"storage\"\n          ],\n          \"notes\": [\n            \"Row B, Position 2\",\n            \"Storage systems\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": true,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dmz-rack\": {\n          \"shape\": \"server\",\n          \"name\": \"DMZ Rack\",\n          \"ip\": \"172.16.0.0/24\",\n          \"role\": \"DMZ Infrastructure\",\n          \"tags\": [\n            \"dmz\",\n            \"security\",\n            \"public-facing\",\n            {\n              \"type\": \"icon\",\n              \"library\": \"selfhst\",\n              \"name\": \"booklogr\"\n            },\n            {\n              \"type\": \"icon\",\n              \"library\": \"simple\",\n              \"name\": \"gmail\"\n            }\n          ],\n          \"notes\": [\n            \"Isolated DMZ zone\",\n            \"Public-facing services\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": true,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"mgmt-rack\": {\n          \"shape\": \"server\",\n          \"name\": \"Management Rack\",\n          \"ip\": \"192.168.100.0/24\",\n          \"role\": \"Management Infrastructure\",\n          \"tags\": [\n            \"management\",\n            \"oob\",\n            \"noc\"\n          ],\n          \"notes\": [\n            \"Out-of-band management\",\n            \"NOC equipment\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"logical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": true,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"esxi-host-01\": {\n          \"shape\": \"server\",\n          \"name\": \"ESXi Host 01\",\n          \"ip\": \"10.10.0.11\",\n          \"role\": \"Hypervisor\",\n          \"tags\": [\n            \"vmware\",\n            \"compute\",\n            \"cluster-a\"\n          ],\n          \"notes\": [\n            \"Dell PowerEdge R750\",\n            \"512GB RAM\",\n            \"vSphere 8.0\"\n          ],\n          \"mac\": \"00:50:56:AA:01:01\",\n          \"rackUnit\": 38,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"esxi-host-02\": {\n          \"shape\": \"server\",\n          \"name\": \"ESXi Host 02\",\n          \"ip\": \"10.10.0.12\",\n          \"role\": \"Hypervisor\",\n          \"tags\": [\n            \"vmware\",\n            \"compute\",\n            \"cluster-a\"\n          ],\n          \"notes\": [\n            \"Dell PowerEdge R750\",\n            \"512GB RAM\",\n            \"vSphere 8.0\"\n          ],\n          \"mac\": \"00:50:56:AA:01:02\",\n          \"rackUnit\": 35,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"esxi-host-03\": {\n          \"shape\": \"server\",\n          \"name\": \"ESXi Host 03\",\n          \"ip\": \"10.10.0.13\",\n          \"role\": \"Hypervisor\",\n          \"tags\": [\n            \"vmware\",\n            \"compute\",\n            \"cluster-a\"\n          ],\n          \"notes\": [\n            \"Dell PowerEdge R750\",\n            \"512GB RAM\",\n            \"vSphere 8.0\"\n          ],\n          \"mac\": \"00:50:56:AA:01:03\",\n          \"rackUnit\": 32,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"esxi-host-04\": {\n          \"shape\": \"server\",\n          \"name\": \"ESXi Host 04\",\n          \"ip\": \"10.10.0.14\",\n          \"role\": \"Hypervisor\",\n          \"tags\": [\n            \"vmware\",\n            \"compute\",\n            \"cluster-a\"\n          ],\n          \"notes\": [\n            \"Dell PowerEdge R750\",\n            \"512GB RAM\",\n            \"vSphere 8.0\"\n          ],\n          \"mac\": \"00:50:56:AA:01:04\",\n          \"rackUnit\": 29,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"tor-switch-a1\": {\n          \"shape\": \"switch\",\n          \"name\": \"ToR Switch A1\",\n          \"ip\": \"10.10.0.1\",\n          \"role\": \"Top of Rack\",\n          \"tags\": [\n            \"tor\",\n            \"access\",\n            \"rack-a1\"\n          ],\n          \"notes\": [\n            \"Cisco Nexus 93180YC-FX\",\n            \"48x25G ports\"\n          ],\n          \"mac\": \"00:1A:2B:3C:5D:01\",\n          \"rackUnit\": 42,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"esxi-host-05\": {\n          \"shape\": \"server\",\n          \"name\": \"ESXi Host 05\",\n          \"ip\": \"10.10.1.11\",\n          \"role\": \"Hypervisor\",\n          \"tags\": [\n            \"vmware\",\n            \"compute\",\n            \"cluster-b\"\n          ],\n          \"notes\": [\n            \"Dell PowerEdge R750\",\n            \"768GB RAM\",\n            \"vSphere 8.0\"\n          ],\n          \"mac\": \"00:50:56:AA:02:01\",\n          \"rackUnit\": 38,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"esxi-host-06\": {\n          \"shape\": \"server\",\n          \"name\": \"ESXi Host 06\",\n          \"ip\": \"10.10.1.12\",\n          \"role\": \"Hypervisor\",\n          \"tags\": [\n            \"vmware\",\n            \"compute\",\n            \"cluster-b\"\n          ],\n          \"notes\": [\n            \"Dell PowerEdge R750\",\n            \"768GB RAM\",\n            \"vSphere 8.0\"\n          ],\n          \"mac\": \"00:50:56:AA:02:02\",\n          \"rackUnit\": 35,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"esxi-host-07\": {\n          \"shape\": \"server\",\n          \"name\": \"ESXi Host 07\",\n          \"ip\": \"10.10.1.13\",\n          \"role\": \"Hypervisor\",\n          \"tags\": [\n            \"vmware\",\n            \"compute\",\n            \"cluster-b\"\n          ],\n          \"notes\": [\n            \"Dell PowerEdge R750\",\n            \"768GB RAM\",\n            \"vSphere 8.0\"\n          ],\n          \"mac\": \"00:50:56:AA:02:03\",\n          \"rackUnit\": 32,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"esxi-host-08\": {\n          \"shape\": \"server\",\n          \"name\": \"ESXi Host 08\",\n          \"ip\": \"10.10.1.14\",\n          \"role\": \"Hypervisor\",\n          \"tags\": [\n            \"vmware\",\n            \"compute\",\n            \"cluster-b\"\n          ],\n          \"notes\": [\n            \"Dell PowerEdge R750\",\n            \"768GB RAM\",\n            \"vSphere 8.0\"\n          ],\n          \"mac\": \"00:50:56:AA:02:04\",\n          \"rackUnit\": 29,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"tor-switch-a2\": {\n          \"shape\": \"switch\",\n          \"name\": \"ToR Switch A2\",\n          \"ip\": \"10.10.1.1\",\n          \"role\": \"Top of Rack\",\n          \"tags\": [\n            \"tor\",\n            \"access\",\n            \"rack-a2\"\n          ],\n          \"notes\": [\n            \"Cisco Nexus 93180YC-FX\",\n            \"48x25G ports\"\n          ],\n          \"mac\": \"00:1A:2B:3C:5D:02\",\n          \"rackUnit\": 42,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"san-primary\": {\n          \"shape\": \"database\",\n          \"name\": \"SAN Primary\",\n          \"ip\": \"10.10.2.10\",\n          \"role\": \"Primary Storage\",\n          \"tags\": [\n            \"storage\",\n            \"san\",\n            \"netapp\"\n          ],\n          \"notes\": [\n            \"NetApp AFF A400\",\n            \"500TB Raw\",\n            \"FC 32Gb\"\n          ],\n          \"mac\": \"00:A0:98:AA:01:01\",\n          \"rackUnit\": 36,\n          \"uHeight\": \"6\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-b1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"san-secondary\": {\n          \"shape\": \"database\",\n          \"name\": \"SAN Secondary\",\n          \"ip\": \"10.10.2.11\",\n          \"role\": \"Secondary Storage\",\n          \"tags\": [\n            \"storage\",\n            \"san\",\n            \"netapp\"\n          ],\n          \"notes\": [\n            \"NetApp AFF A400\",\n            \"500TB Raw\",\n            \"FC 32Gb\"\n          ],\n          \"mac\": \"00:A0:98:AA:01:02\",\n          \"rackUnit\": 28,\n          \"uHeight\": \"6\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-b1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"fc-switch-1\": {\n          \"shape\": \"switch\",\n          \"name\": \"FC Switch 1\",\n          \"ip\": \"10.10.2.1\",\n          \"role\": \"Fibre Channel\",\n          \"tags\": [\n            \"storage\",\n            \"fc\",\n            \"fabric-a\"\n          ],\n          \"notes\": [\n            \"Brocade G620\",\n            \"Fabric A\"\n          ],\n          \"mac\": \"00:1A:2B:FC:01:01\",\n          \"rackUnit\": 42,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-b1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"fc-switch-2\": {\n          \"shape\": \"switch\",\n          \"name\": \"FC Switch 2\",\n          \"ip\": \"10.10.2.2\",\n          \"role\": \"Fibre Channel\",\n          \"tags\": [\n            \"storage\",\n            \"fc\",\n            \"fabric-b\"\n          ],\n          \"notes\": [\n            \"Brocade G620\",\n            \"Fabric B\"\n          ],\n          \"mac\": \"00:1A:2B:FC:01:02\",\n          \"rackUnit\": 41,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-b1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"backup-server-1\": {\n          \"shape\": \"server\",\n          \"name\": \"Backup Server 1\",\n          \"ip\": \"10.10.3.10\",\n          \"role\": \"Backup Infrastructure\",\n          \"tags\": [\n            \"backup\",\n            \"veeam\",\n            \"protection\"\n          ],\n          \"notes\": [\n            \"Veeam Backup Server\",\n            \"Dell R740xd\",\n            \"200TB\"\n          ],\n          \"mac\": \"00:50:56:BB:01:01\",\n          \"rackUnit\": 36,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-b2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"backup-server-2\": {\n          \"shape\": \"server\",\n          \"name\": \"Backup Server 2\",\n          \"ip\": \"10.10.3.11\",\n          \"role\": \"Backup Infrastructure\",\n          \"tags\": [\n            \"backup\",\n            \"veeam\",\n            \"protection\"\n          ],\n          \"notes\": [\n            \"Veeam Backup Server\",\n            \"Dell R740xd\",\n            \"200TB\"\n          ],\n          \"mac\": \"00:50:56:BB:01:02\",\n          \"rackUnit\": 33,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-b2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"tape-library\": {\n          \"shape\": \"database\",\n          \"name\": \"Tape Library\",\n          \"ip\": \"10.10.3.20\",\n          \"role\": \"Archival Storage\",\n          \"tags\": [\n            \"backup\",\n            \"tape\",\n            \"lto9\"\n          ],\n          \"notes\": [\n            \"IBM TS4500\",\n            \"LTO-9\",\n            \"Long-term archive\"\n          ],\n          \"mac\": \"00:50:56:BB:02:01\",\n          \"rackUnit\": 20,\n          \"uHeight\": \"10\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-b2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"tor-switch-b1\": {\n          \"shape\": \"switch\",\n          \"name\": \"ToR Switch B1\",\n          \"ip\": \"10.10.2.3\",\n          \"role\": \"Top of Rack\",\n          \"tags\": [\n            \"tor\",\n            \"access\",\n            \"rack-b1\"\n          ],\n          \"notes\": [\n            \"Cisco Nexus 93180YC-FX\"\n          ],\n          \"mac\": \"00:1A:2B:3C:5D:03\",\n          \"rackUnit\": 40,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-b1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"tor-switch-b2\": {\n          \"shape\": \"switch\",\n          \"name\": \"ToR Switch B2\",\n          \"ip\": \"10.10.3.1\",\n          \"role\": \"Top of Rack\",\n          \"tags\": [\n            \"tor\",\n            \"access\",\n            \"rack-b2\"\n          ],\n          \"notes\": [\n            \"Cisco Nexus 93180YC-FX\"\n          ],\n          \"mac\": \"00:1A:2B:3C:5D:04\",\n          \"rackUnit\": 42,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-b2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"web-server-1\": {\n          \"shape\": \"server\",\n          \"name\": \"Web Server 1\",\n          \"ip\": \"172.16.0.11\",\n          \"role\": \"Web Frontend\",\n          \"tags\": [\n            \"dmz\",\n            \"web\",\n            \"nginx\"\n          ],\n          \"notes\": [\n            \"NGINX reverse proxy\",\n            \"Public facing\"\n          ],\n          \"mac\": \"00:50:56:CC:01:01\",\n          \"rackUnit\": 20,\n          \"uHeight\": \"1\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"dmz-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"web-server-2\": {\n          \"shape\": \"server\",\n          \"name\": \"Web Server 2\",\n          \"ip\": \"172.16.0.12\",\n          \"role\": \"Web Frontend\",\n          \"tags\": [\n            \"dmz\",\n            \"web\",\n            \"nginx\"\n          ],\n          \"notes\": [\n            \"NGINX reverse proxy\",\n            \"Public facing\"\n          ],\n          \"mac\": \"00:50:56:CC:01:02\",\n          \"rackUnit\": 18,\n          \"uHeight\": \"1\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"dmz-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"waf-1\": {\n          \"shape\": \"firewall\",\n          \"name\": \"WAF Appliance\",\n          \"ip\": \"172.16.0.5\",\n          \"role\": \"Web Application Firewall\",\n          \"tags\": [\n            \"dmz\",\n            \"security\",\n            \"waf\"\n          ],\n          \"notes\": [\n            \"F5 BIG-IP ASM\",\n            \"OWASP protection\"\n          ],\n          \"mac\": \"00:50:56:CC:02:01\",\n          \"rackUnit\": 22,\n          \"uHeight\": \"2\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"dmz-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"load-balancer-dmz\": {\n          \"shape\": \"switch\",\n          \"name\": \"DMZ Load Balancer\",\n          \"ip\": \"172.16.0.3\",\n          \"role\": \"Load Balancing\",\n          \"tags\": [\n            \"dmz\",\n            \"lb\",\n            \"f5\"\n          ],\n          \"notes\": [\n            \"F5 BIG-IP LTM\",\n            \"VIP: 172.16.0.100\"\n          ],\n          \"mac\": \"00:50:56:CC:03:01\",\n          \"rackUnit\": 16,\n          \"uHeight\": \"2\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"dmz-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"mail-gateway\": {\n          \"shape\": \"server\",\n          \"name\": \"Mail Gateway\",\n          \"ip\": \"172.16.0.25\",\n          \"role\": \"Email Security\",\n          \"tags\": [\n            \"dmz\",\n            \"email\",\n            \"security\"\n          ],\n          \"notes\": [\n            \"Proofpoint Email Gateway\",\n            \"Spam/malware filtering\"\n          ],\n          \"mac\": \"00:50:56:CC:04:01\",\n          \"rackUnit\": 14,\n          \"uHeight\": \"1\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"dmz-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dns-external-1\": {\n          \"shape\": \"circle\",\n          \"name\": \"External DNS 1\",\n          \"ip\": \"172.16.0.53\",\n          \"role\": \"External DNS\",\n          \"tags\": [\n            \"dmz\",\n            \"dns\",\n            \"public\"\n          ],\n          \"notes\": [\n            \"BIND DNS\",\n            \"Authoritative for corp.com\"\n          ],\n          \"mac\": \"00:50:56:CC:05:01\",\n          \"rackUnit\": 12,\n          \"uHeight\": \"1\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"dmz-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dns-external-2\": {\n          \"shape\": \"circle\",\n          \"name\": \"External DNS 2\",\n          \"ip\": \"172.16.0.54\",\n          \"role\": \"External DNS\",\n          \"tags\": [\n            \"dmz\",\n            \"dns\",\n            \"public\"\n          ],\n          \"notes\": [\n            \"BIND DNS\",\n            \"Secondary for corp.com\"\n          ],\n          \"mac\": \"00:50:56:CC:05:02\",\n          \"rackUnit\": 10,\n          \"uHeight\": \"1\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"dmz-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"vcenter\": {\n          \"shape\": \"server\",\n          \"name\": \"vCenter Server\",\n          \"ip\": \"192.168.100.10\",\n          \"role\": \"Virtualization Management\",\n          \"tags\": [\n            \"management\",\n            \"vmware\",\n            \"vcsa\"\n          ],\n          \"notes\": [\n            \"vCenter Server Appliance 8.0\",\n            \"Single SSO domain\"\n          ],\n          \"mac\": \"00:50:56:DD:01:01\",\n          \"rackUnit\": 20,\n          \"uHeight\": \"2\",\n          \"layer\": \"logical\",\n          \"assignedRack\": \"mgmt-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"nsx-manager\": {\n          \"shape\": \"server\",\n          \"name\": \"NSX Manager\",\n          \"ip\": \"192.168.100.15\",\n          \"role\": \"Network Virtualization\",\n          \"tags\": [\n            \"management\",\n            \"vmware\",\n            \"nsx\"\n          ],\n          \"notes\": [\n            \"NSX-T 4.1 Manager Cluster\"\n          ],\n          \"mac\": \"00:50:56:DD:02:01\",\n          \"rackUnit\": 17,\n          \"uHeight\": \"2\",\n          \"layer\": \"logical\",\n          \"assignedRack\": \"mgmt-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"siem-server\": {\n          \"shape\": \"server\",\n          \"name\": \"SIEM Server\",\n          \"ip\": \"192.168.100.50\",\n          \"role\": \"Security Monitoring\",\n          \"tags\": [\n            \"management\",\n            \"security\",\n            \"splunk\"\n          ],\n          \"notes\": [\n            \"Splunk Enterprise\",\n            \"Security monitoring\"\n          ],\n          \"mac\": \"00:50:56:DD:03:01\",\n          \"rackUnit\": 14,\n          \"uHeight\": \"2\",\n          \"layer\": \"logical\",\n          \"assignedRack\": \"mgmt-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"nms-server\": {\n          \"shape\": \"server\",\n          \"name\": \"Network Monitoring\",\n          \"ip\": \"192.168.100.60\",\n          \"role\": \"Network Management\",\n          \"tags\": [\n            \"management\",\n            \"monitoring\",\n            \"prtg\"\n          ],\n          \"notes\": [\n            \"PRTG Network Monitor\",\n            \"5000 sensors\"\n          ],\n          \"mac\": \"00:50:56:DD:04:01\",\n          \"rackUnit\": 11,\n          \"uHeight\": \"1\",\n          \"layer\": \"logical\",\n          \"assignedRack\": \"mgmt-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"jump-server\": {\n          \"shape\": \"server\",\n          \"name\": \"Jump Server\",\n          \"ip\": \"192.168.100.100\",\n          \"role\": \"Bastion Host\",\n          \"tags\": [\n            \"management\",\n            \"security\",\n            \"bastion\"\n          ],\n          \"notes\": [\n            \"Windows Server 2022\",\n            \"MFA enabled\"\n          ],\n          \"mac\": \"00:50:56:DD:05:01\",\n          \"rackUnit\": 9,\n          \"uHeight\": \"1\",\n          \"layer\": \"logical\",\n          \"assignedRack\": \"mgmt-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"ipam-server\": {\n          \"shape\": \"server\",\n          \"name\": \"IPAM/DDI\",\n          \"ip\": \"192.168.100.70\",\n          \"role\": \"IP Management\",\n          \"tags\": [\n            \"management\",\n            \"dns\",\n            \"dhcp\"\n          ],\n          \"notes\": [\n            \"Infoblox DDI\",\n            \"DNS/DHCP/IPAM\"\n          ],\n          \"mac\": \"00:50:56:DD:06:01\",\n          \"rackUnit\": 7,\n          \"uHeight\": \"2\",\n          \"layer\": \"logical\",\n          \"assignedRack\": \"mgmt-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"wlc-primary\": {\n          \"shape\": \"wifi\",\n          \"name\": \"WLC Primary\",\n          \"ip\": \"10.20.0.1\",\n          \"role\": \"Wireless Controller\",\n          \"tags\": [\n            \"wireless\",\n            \"cisco\",\n            \"9800\"\n          ],\n          \"notes\": [\n            \"Cisco C9800-40\",\n            \"Primary controller\"\n          ],\n          \"mac\": \"00:1A:2B:WL:01:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"wlc-secondary\": {\n          \"shape\": \"wifi\",\n          \"name\": \"WLC Secondary\",\n          \"ip\": \"10.20.0.2\",\n          \"role\": \"Wireless Controller\",\n          \"tags\": [\n            \"wireless\",\n            \"cisco\",\n            \"9800\"\n          ],\n          \"notes\": [\n            \"Cisco C9800-40\",\n            \"HA Secondary\"\n          ],\n          \"mac\": \"00:1A:2B:WL:01:02\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"mobile-zone-hq\": {\n          \"shape\": \"phone\",\n          \"name\": \"HQ Mobile Zone\",\n          \"ip\": \"10.20.10.0/24\",\n          \"role\": \"Mobile Device Zone\",\n          \"tags\": [\n            \"wireless\",\n            \"byod\",\n            \"mobile\"\n          ],\n          \"notes\": [\n            \"Corporate BYOD\",\n            \"MDM enrolled devices\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"mobile-zone-guest\": {\n          \"shape\": \"phone\",\n          \"name\": \"Guest WiFi Zone\",\n          \"ip\": \"10.30.0.0/24\",\n          \"role\": \"Guest Network\",\n          \"tags\": [\n            \"wireless\",\n            \"guest\",\n            \"isolated\"\n          ],\n          \"notes\": [\n            \"Captive portal\",\n            \"Internet only\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"mobile-zone-iot\": {\n          \"shape\": \"phone\",\n          \"name\": \"IoT Device Zone\",\n          \"ip\": \"10.40.0.0/24\",\n          \"role\": \"IoT Network\",\n          \"tags\": [\n            \"wireless\",\n            \"iot\",\n            \"building\"\n          ],\n          \"notes\": [\n            \"Building automation\",\n            \"Smart devices\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"branch-router-ny\": {\n          \"shape\": \"router\",\n          \"name\": \"NYC Branch Router\",\n          \"ip\": \"10.100.0.1\",\n          \"role\": \"Branch Gateway\",\n          \"tags\": [\n            \"branch\",\n            \"nyc\",\n            \"sd-wan\"\n          ],\n          \"notes\": [\n            \"Cisco Viptela vEdge\",\n            \"SD-WAN enabled\"\n          ],\n          \"mac\": \"00:1A:2B:BR:01:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"branch-router-la\": {\n          \"shape\": \"router\",\n          \"name\": \"LA Branch Router\",\n          \"ip\": \"10.101.0.1\",\n          \"role\": \"Branch Gateway\",\n          \"tags\": [\n            \"branch\",\n            \"la\",\n            \"sd-wan\"\n          ],\n          \"notes\": [\n            \"Cisco Viptela vEdge\",\n            \"SD-WAN enabled\"\n          ],\n          \"mac\": \"00:1A:2B:BR:02:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"branch-router-chi\": {\n          \"shape\": \"router\",\n          \"name\": \"Chicago Branch Router\",\n          \"ip\": \"10.102.0.1\",\n          \"role\": \"Branch Gateway\",\n          \"tags\": [\n            \"branch\",\n            \"chicago\",\n            \"sd-wan\"\n          ],\n          \"notes\": [\n            \"Cisco Viptela vEdge\",\n            \"SD-WAN enabled\"\n          ],\n          \"mac\": \"00:1A:2B:BR:03:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"branch-router-lon\": {\n          \"shape\": \"router\",\n          \"name\": \"London Branch Router\",\n          \"ip\": \"10.200.0.1\",\n          \"role\": \"Branch Gateway\",\n          \"tags\": [\n            \"branch\",\n            \"london\",\n            \"sd-wan\"\n          ],\n          \"notes\": [\n            \"Cisco Viptela vEdge\",\n            \"EMEA region\"\n          ],\n          \"mac\": \"00:1A:2B:BR:04:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"branch-router-tokyo\": {\n          \"shape\": \"router\",\n          \"name\": \"Tokyo Branch Router\",\n          \"ip\": \"10.201.0.1\",\n          \"role\": \"Branch Gateway\",\n          \"tags\": [\n            \"branch\",\n            \"tokyo\",\n            \"sd-wan\"\n          ],\n          \"notes\": [\n            \"Cisco Viptela vEdge\",\n            \"APAC region\"\n          ],\n          \"mac\": \"00:1A:2B:BR:05:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"cloud-aws\": {\n          \"shape\": \"cloud\",\n          \"name\": \"AWS Cloud\",\n          \"ip\": \"vpc-0a1b2c3d\",\n          \"role\": \"Public Cloud\",\n          \"tags\": [\n            \"cloud\",\n            \"aws\",\n            \"hybrid\"\n          ],\n          \"notes\": [\n            \"AWS US-East-1\",\n            \"VPC peering to HQ\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"logical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"cloud-azure\": {\n          \"shape\": \"cloud\",\n          \"name\": \"Azure Cloud\",\n          \"ip\": \"vnet-corp-prod\",\n          \"role\": \"Public Cloud\",\n          \"tags\": [\n            \"cloud\",\n            \"azure\",\n            \"hybrid\"\n          ],\n          \"notes\": [\n            \"Azure East US 2\",\n            \"ExpressRoute\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"logical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"cloud-gcp\": {\n          \"shape\": \"cloud\",\n          \"name\": \"GCP Cloud\",\n          \"ip\": \"vpc-gcp-corp\",\n          \"role\": \"Public Cloud\",\n          \"tags\": [\n            \"cloud\",\n            \"gcp\",\n            \"dev\"\n          ],\n          \"notes\": [\n            \"GCP us-central1\",\n            \"Dev/Test workloads\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"logical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"isp-primary\": {\n          \"shape\": \"globe\",\n          \"name\": \"ISP Primary\",\n          \"ip\": \"203.0.113.1\",\n          \"role\": \"Internet Uplink\",\n          \"tags\": [\n            \"wan\",\n            \"internet\",\n            \"primary\"\n          ],\n          \"notes\": [\n            \"AT&T MPLS\",\n            \"1 Gbps dedicated\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"isp-secondary\": {\n          \"shape\": \"globe\",\n          \"name\": \"ISP Secondary\",\n          \"ip\": \"198.51.100.1\",\n          \"role\": \"Internet Uplink\",\n          \"tags\": [\n            \"wan\",\n            \"internet\",\n            \"backup\"\n          ],\n          \"notes\": [\n            \"Verizon Business\",\n            \"500 Mbps backup\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dc-internal-1\": {\n          \"shape\": \"circle\",\n          \"name\": \"DC1 Int DNS\",\n          \"ip\": \"10.10.0.53\",\n          \"role\": \"Internal DNS/AD\",\n          \"tags\": [\n            \"dns\",\n            \"ad\",\n            \"dc1\"\n          ],\n          \"notes\": [\n            \"Windows Server 2022\",\n            \"Primary DC\"\n          ],\n          \"mac\": \"00:50:56:AD:01:01\",\n          \"rackUnit\": 26,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dc-internal-2\": {\n          \"shape\": \"circle\",\n          \"name\": \"DC2 Int DNS\",\n          \"ip\": \"10.10.1.53\",\n          \"role\": \"Internal DNS/AD\",\n          \"tags\": [\n            \"dns\",\n            \"ad\",\n            \"dc2\"\n          ],\n          \"notes\": [\n            \"Windows Server 2022\",\n            \"Secondary DC\"\n          ],\n          \"mac\": \"00:50:56:AD:01:02\",\n          \"rackUnit\": 26,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"app-server-1\": {\n          \"shape\": \"server\",\n          \"name\": \"App Server 01\",\n          \"ip\": \"10.10.0.101\",\n          \"role\": \"Application\",\n          \"tags\": [\n            \"app\",\n            \"iis\",\n            \"web\"\n          ],\n          \"notes\": [\n            \"Windows Server 2022\",\n            \"IIS Application\"\n          ],\n          \"mac\": \"00:50:56:AP:01:01\",\n          \"rackUnit\": 24,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"app-server-2\": {\n          \"shape\": \"server\",\n          \"name\": \"App Server 02\",\n          \"ip\": \"10.10.0.102\",\n          \"role\": \"Application\",\n          \"tags\": [\n            \"app\",\n            \"iis\",\n            \"web\"\n          ],\n          \"notes\": [\n            \"Windows Server 2022\",\n            \"IIS Application\"\n          ],\n          \"mac\": \"00:50:56:AP:01:02\",\n          \"rackUnit\": 22,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"db-server-1\": {\n          \"shape\": \"database\",\n          \"name\": \"SQL Server 01\",\n          \"ip\": \"10.10.0.201\",\n          \"role\": \"Database\",\n          \"tags\": [\n            \"db\",\n            \"sql\",\n            \"primary\"\n          ],\n          \"notes\": [\n            \"SQL Server 2022 Enterprise\",\n            \"AlwaysOn Primary\"\n          ],\n          \"mac\": \"00:50:56:DB:01:01\",\n          \"rackUnit\": 20,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"db-server-2\": {\n          \"shape\": \"database\",\n          \"name\": \"SQL Server 02\",\n          \"ip\": \"10.10.1.201\",\n          \"role\": \"Database\",\n          \"tags\": [\n            \"db\",\n            \"sql\",\n            \"secondary\"\n          ],\n          \"notes\": [\n            \"SQL Server 2022 Enterprise\",\n            \"AlwaysOn Secondary\"\n          ],\n          \"mac\": \"00:50:56:DB:01:02\",\n          \"rackUnit\": 24,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"k8s-master-1\": {\n          \"shape\": \"hexagon\",\n          \"name\": \"K8s Master 1\",\n          \"ip\": \"10.10.1.50\",\n          \"role\": \"Container Orchestration\",\n          \"tags\": [\n            \"kubernetes\",\n            \"master\",\n            \"container\"\n          ],\n          \"notes\": [\n            \"K8s Control Plane\",\n            \"etcd member\"\n          ],\n          \"mac\": \"00:50:56:K8:01:01\",\n          \"rackUnit\": 21,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"k8s-master-2\": {\n          \"shape\": \"hexagon\",\n          \"name\": \"K8s Master 2\",\n          \"ip\": \"10.10.1.51\",\n          \"role\": \"Container Orchestration\",\n          \"tags\": [\n            \"kubernetes\",\n            \"master\",\n            \"container\"\n          ],\n          \"notes\": [\n            \"K8s Control Plane\",\n            \"etcd member\"\n          ],\n          \"mac\": \"00:50:56:K8:01:02\",\n          \"rackUnit\": 19,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"k8s-master-3\": {\n          \"shape\": \"hexagon\",\n          \"name\": \"K8s Master 3\",\n          \"ip\": \"10.10.1.52\",\n          \"role\": \"Container Orchestration\",\n          \"tags\": [\n            \"kubernetes\",\n            \"master\",\n            \"container\"\n          ],\n          \"notes\": [\n            \"K8s Control Plane\",\n            \"etcd member\"\n          ],\n          \"mac\": \"00:50:56:K8:01:03\",\n          \"rackUnit\": 17,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"k8s-worker-1\": {\n          \"shape\": \"server\",\n          \"name\": \"K8s Worker 1\",\n          \"ip\": \"10.10.1.60\",\n          \"role\": \"Container Workload\",\n          \"tags\": [\n            \"kubernetes\",\n            \"worker\",\n            \"container\"\n          ],\n          \"notes\": [\n            \"K8s Worker Node\",\n            \"64GB RAM\"\n          ],\n          \"mac\": \"00:50:56:K8:02:01\",\n          \"rackUnit\": 15,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"k8s-worker-2\": {\n          \"shape\": \"server\",\n          \"name\": \"K8s Worker 2\",\n          \"ip\": \"10.10.1.61\",\n          \"role\": \"Container Workload\",\n          \"tags\": [\n            \"kubernetes\",\n            \"worker\",\n            \"container\"\n          ],\n          \"notes\": [\n            \"K8s Worker Node\",\n            \"64GB RAM\"\n          ],\n          \"mac\": \"00:50:56:K8:02:02\",\n          \"rackUnit\": 13,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"k8s-worker-3\": {\n          \"shape\": \"server\",\n          \"name\": \"K8s Worker 3\",\n          \"ip\": \"10.10.1.62\",\n          \"role\": \"Container Workload\",\n          \"tags\": [\n            \"kubernetes\",\n            \"worker\",\n            \"container\"\n          ],\n          \"notes\": [\n            \"K8s Worker Node\",\n            \"64GB RAM\"\n          ],\n          \"mac\": \"00:50:56:K8:02:03\",\n          \"rackUnit\": 11,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"k8s-worker-4\": {\n          \"shape\": \"server\",\n          \"name\": \"K8s Worker 4\",\n          \"ip\": \"10.10.1.63\",\n          \"role\": \"Container Workload\",\n          \"tags\": [\n            \"kubernetes\",\n            \"worker\",\n            \"container\"\n          ],\n          \"notes\": [\n            \"K8s Worker Node\",\n            \"64GB RAM\"\n          ],\n          \"mac\": \"00:50:56:K8:02:04\",\n          \"rackUnit\": 9,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"proxy-server-1\": {\n          \"shape\": \"server\",\n          \"name\": \"Proxy Server 1\",\n          \"ip\": \"10.5.0.10\",\n          \"role\": \"Web Proxy\",\n          \"tags\": [\n            \"proxy\",\n            \"squid\",\n            \"filtering\"\n          ],\n          \"notes\": [\n            \"Squid Proxy\",\n            \"Content filtering\"\n          ],\n          \"mac\": \"00:50:56:PX:01:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"proxy-server-2\": {\n          \"shape\": \"server\",\n          \"name\": \"Proxy Server 2\",\n          \"ip\": \"10.5.0.11\",\n          \"role\": \"Web Proxy\",\n          \"tags\": [\n            \"proxy\",\n            \"squid\",\n            \"filtering\"\n          ],\n          \"notes\": [\n            \"Squid Proxy\",\n            \"HA pair\"\n          ],\n          \"mac\": \"00:50:56:PX:01:02\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"vpn-concentrator\": {\n          \"shape\": \"firewall\",\n          \"name\": \"VPN Concentrator\",\n          \"ip\": \"10.0.5.1\",\n          \"role\": \"Remote Access VPN\",\n          \"tags\": [\n            \"vpn\",\n            \"remote\",\n            \"security\"\n          ],\n          \"notes\": [\n            \"Cisco ASA 5555-X\",\n            \"AnyConnect SSL VPN\"\n          ],\n          \"mac\": \"00:1A:2B:VP:01:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"nac-server\": {\n          \"shape\": \"server\",\n          \"name\": \"NAC Server\",\n          \"ip\": \"10.5.5.10\",\n          \"role\": \"Network Access Control\",\n          \"tags\": [\n            \"nac\",\n            \"ise\",\n            \"802.1x\"\n          ],\n          \"notes\": [\n            \"Cisco ISE 3.1\",\n            \"RADIUS/TACACS+\"\n          ],\n          \"mac\": \"00:50:56:NA:01:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"print-server\": {\n          \"shape\": \"server\",\n          \"name\": \"Print Server\",\n          \"ip\": \"10.10.0.150\",\n          \"role\": \"Print Services\",\n          \"tags\": [\n            \"print\",\n            \"windows\",\n            \"services\"\n          ],\n          \"notes\": [\n            \"Windows Print Server\",\n            \"50+ printers\"\n          ],\n          \"mac\": \"00:50:56:PR:01:01\",\n          \"rackUnit\": 18,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"file-server\": {\n          \"shape\": \"database\",\n          \"name\": \"File Server\",\n          \"ip\": \"10.10.0.160\",\n          \"role\": \"File Services\",\n          \"tags\": [\n            \"file\",\n            \"smb\",\n            \"dfs\"\n          ],\n          \"notes\": [\n            \"Windows File Server\",\n            \"DFS namespace\"\n          ],\n          \"mac\": \"00:50:56:FS:01:01\",\n          \"rackUnit\": 16,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"ca-server\": {\n          \"shape\": \"server\",\n          \"name\": \"Certificate Authority\",\n          \"ip\": \"192.168.100.80\",\n          \"role\": \"PKI Infrastructure\",\n          \"tags\": [\n            \"pki\",\n            \"ca\",\n            \"security\"\n          ],\n          \"notes\": [\n            \"Windows CA\",\n            \"Enterprise Root CA\"\n          ],\n          \"mac\": \"00:50:56:CA:01:01\",\n          \"rackUnit\": 5,\n          \"uHeight\": \"1\",\n          \"layer\": \"logical\",\n          \"assignedRack\": \"mgmt-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"sccm-server\": {\n          \"shape\": \"server\",\n          \"name\": \"SCCM Server\",\n          \"ip\": \"192.168.100.90\",\n          \"role\": \"Endpoint Management\",\n          \"tags\": [\n            \"sccm\",\n            \"patching\",\n            \"software\"\n          ],\n          \"notes\": [\n            \"MECM Primary Site\",\n            \"Software deployment\"\n          ],\n          \"mac\": \"00:50:56:SC:01:01\",\n          \"rackUnit\": 3,\n          \"uHeight\": \"2\",\n          \"layer\": \"logical\",\n          \"assignedRack\": \"mgmt-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"voip-cluster\": {\n          \"shape\": \"phone\",\n          \"name\": \"VoIP Cluster\",\n          \"ip\": \"10.50.0.0/24\",\n          \"role\": \"Voice Services\",\n          \"tags\": [\n            \"voip\",\n            \"cisco\",\n            \"ucm\"\n          ],\n          \"notes\": [\n            \"Cisco UCM Cluster\",\n            \"3000 endpoints\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"application\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"video-conf\": {\n          \"shape\": \"laptop\",\n          \"name\": \"Video Conference\",\n          \"ip\": \"10.51.0.0/24\",\n          \"role\": \"Video Services\",\n          \"tags\": [\n            \"video\",\n            \"webex\",\n            \"teams\"\n          ],\n          \"notes\": [\n            \"Webex/Teams integration\",\n            \"Meeting rooms\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"application\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"security-cameras\": {\n          \"shape\": \"camera\",\n          \"name\": \"Security Cameras\",\n          \"ip\": \"10.60.0.0/24\",\n          \"role\": \"Physical Security\",\n          \"tags\": [\n            \"cctv\",\n            \"surveillance\",\n            \"security\"\n          ],\n          \"notes\": [\n            \"150+ IP cameras\",\n            \"30-day retention\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"nvr-cluster\": {\n          \"shape\": \"server\",\n          \"name\": \"NVR Cluster\",\n          \"ip\": \"10.60.0.10\",\n          \"role\": \"Video Recording\",\n          \"tags\": [\n            \"nvr\",\n            \"surveillance\",\n            \"storage\"\n          ],\n          \"notes\": [\n            \"Milestone XProtect\",\n            \"500TB storage\"\n          ],\n          \"mac\": \"00:50:56:NV:01:01\",\n          \"rackUnit\": 15,\n          \"uHeight\": \"4\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-b2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dev-server-1\": {\n          \"shape\": \"server\",\n          \"name\": \"Dev Server 1\",\n          \"ip\": \"10.80.0.10\",\n          \"role\": \"Development\",\n          \"tags\": [\n            \"dev\",\n            \"gitlab\",\n            \"ci-cd\",\n            {\n              \"type\": \"icon\",\n              \"library\": \"selfhst\",\n              \"name\": \"dokku\"\n            }\n          ],\n          \"notes\": [\n            \"GitLab Server\",\n            \"CI/CD pipelines\"\n          ],\n          \"mac\": \"00:50:56:DV:01:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"application\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dev-server-2\": {\n          \"shape\": \"server\",\n          \"name\": \"Dev Server 2\",\n          \"ip\": \"10.80.0.11\",\n          \"role\": \"Development\",\n          \"tags\": [\n            \"dev\",\n            \"jenkins\",\n            \"ci-cd\"\n          ],\n          \"notes\": [\n            \"Jenkins Server\",\n            \"Build automation\"\n          ],\n          \"mac\": \"00:50:56:DV:01:02\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"application\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"test-environment\": {\n          \"shape\": \"hexagon\",\n          \"name\": \"Test Environment\",\n          \"ip\": \"10.81.0.0/24\",\n          \"role\": \"QA/Testing\",\n          \"tags\": [\n            \"test\",\n            \"qa\",\n            \"staging\"\n          ],\n          \"notes\": [\n            \"Staging environment\",\n            \"Pre-prod validation\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"application\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"erp-system\": {\n          \"shape\": \"database\",\n          \"name\": \"ERP System\",\n          \"ip\": \"10.90.0.10\",\n          \"role\": \"Business Application\",\n          \"tags\": [\n            \"erp\",\n            \"sap\",\n            \"business\"\n          ],\n          \"notes\": [\n            \"SAP S/4HANA\",\n            \"Financial/HR systems\"\n          ],\n          \"mac\": \"00:50:56:ER:01:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"4\",\n          \"layer\": \"application\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"crm-system\": {\n          \"shape\": \"database\",\n          \"name\": \"CRM System\",\n          \"ip\": \"10.91.0.10\",\n          \"role\": \"Business Application\",\n          \"tags\": [\n            \"crm\",\n            \"salesforce\",\n            \"business\"\n          ],\n          \"notes\": [\n            \"Salesforce integration\",\n            \"Sales/Marketing\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"application\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"endpoint-1000\": {\n          \"shape\": \"laptop\",\n          \"name\": \"Corporate Endpoints\",\n          \"ip\": \"10.70.0.0/22\",\n          \"role\": \"User Workstations\",\n          \"tags\": [\n            \"endpoints\",\n            \"workstations\",\n            \"users\"\n          ],\n          \"notes\": [\n            \"~1000 corporate laptops\",\n            \"Windows 11\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dist-switch-floor1\": {\n          \"shape\": \"switch\",\n          \"name\": \"Floor 1 Switch\",\n          \"ip\": \"10.1.1.1\",\n          \"role\": \"Distribution\",\n          \"tags\": [\n            \"distribution\",\n            \"floor-1\",\n            \"access\"\n          ],\n          \"notes\": [\n            \"Cisco C9300-48P\",\n            \"PoE+ enabled\"\n          ],\n          \"mac\": \"00:1A:2B:FL:01:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dist-switch-floor2\": {\n          \"shape\": \"switch\",\n          \"name\": \"Floor 2 Switch\",\n          \"ip\": \"10.1.2.1\",\n          \"role\": \"Distribution\",\n          \"tags\": [\n            \"distribution\",\n            \"floor-2\",\n            \"access\"\n          ],\n          \"notes\": [\n            \"Cisco C9300-48P\",\n            \"PoE+ enabled\"\n          ],\n          \"mac\": \"00:1A:2B:FL:02:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dist-switch-floor3\": {\n          \"shape\": \"switch\",\n          \"name\": \"Floor 3 Switch\",\n          \"ip\": \"10.1.3.1\",\n          \"role\": \"Distribution\",\n          \"tags\": [\n            \"distribution\",\n            \"floor-3\",\n            \"access\"\n          ],\n          \"notes\": [\n            \"Cisco C9300-48P\",\n            \"PoE+ enabled\"\n          ],\n          \"mac\": \"00:1A:2B:FL:03:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dist-switch-floor4\": {\n          \"shape\": \"switch\",\n          \"name\": \"Floor 4 Switch\",\n          \"ip\": \"10.1.4.1\",\n          \"role\": \"Distribution\",\n          \"tags\": [\n            \"distribution\",\n            \"floor-4\",\n            \"access\"\n          ],\n          \"notes\": [\n            \"Cisco C9300-48P\",\n            \"PoE+ enabled\"\n          ],\n          \"mac\": \"00:1A:2B:FL:04:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"ap-floor1-zone1\": {\n          \"shape\": \"wifi\",\n          \"name\": \"AP Floor 1 Zone 1\",\n          \"ip\": \"10.20.1.10\",\n          \"role\": \"Wireless Access\",\n          \"tags\": [\n            \"wifi\",\n            \"ap\",\n            \"floor-1\"\n          ],\n          \"notes\": [\n            \"Cisco 9120AX\",\n            \"Wi-Fi 6\"\n          ],\n          \"mac\": \"00:1A:2B:AP:01:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"ap-floor2-zone1\": {\n          \"shape\": \"wifi\",\n          \"name\": \"AP Floor 2 Zone 1\",\n          \"ip\": \"10.20.2.10\",\n          \"role\": \"Wireless Access\",\n          \"tags\": [\n            \"wifi\",\n            \"ap\",\n            \"floor-2\"\n          ],\n          \"notes\": [\n            \"Cisco 9120AX\",\n            \"Wi-Fi 6\"\n          ],\n          \"mac\": \"00:1A:2B:AP:02:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"ap-floor3-zone1\": {\n          \"shape\": \"wifi\",\n          \"name\": \"AP Floor 3 Zone 1\",\n          \"ip\": \"10.20.3.10\",\n          \"role\": \"Wireless Access\",\n          \"tags\": [\n            \"wifi\",\n            \"ap\",\n            \"floor-3\"\n          ],\n          \"notes\": [\n            \"Cisco 9120AX\",\n            \"Wi-Fi 6\"\n          ],\n          \"mac\": \"00:1A:2B:AP:03:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"ap-floor4-zone1\": {\n          \"shape\": \"wifi\",\n          \"name\": \"AP Floor 4 Zone 1\",\n          \"ip\": \"10.20.4.10\",\n          \"role\": \"Wireless Access\",\n          \"tags\": [\n            \"wifi\",\n            \"ap\",\n            \"floor-4\"\n          ],\n          \"notes\": [\n            \"Cisco 9120AX\",\n            \"Wi-Fi 6\"\n          ],\n          \"mac\": \"00:1A:2B:AP:04:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"ups-dc-1\": {\n          \"shape\": \"rectangle\",\n          \"name\": \"UPS DC-1\",\n          \"ip\": \"192.168.200.10\",\n          \"role\": \"Power Management\",\n          \"tags\": [\n            \"power\",\n            \"ups\",\n            \"datacenter\"\n          ],\n          \"notes\": [\n            \"APC Symmetra\",\n            \"80kVA\",\n            \"30 min runtime\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"ups-dc-2\": {\n          \"shape\": \"rectangle\",\n          \"name\": \"UPS DC-2\",\n          \"ip\": \"192.168.200.11\",\n          \"role\": \"Power Management\",\n          \"tags\": [\n            \"power\",\n            \"ups\",\n            \"datacenter\"\n          ],\n          \"notes\": [\n            \"APC Symmetra\",\n            \"80kVA\",\n            \"Redundant\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"pdu-rack-a1\": {\n          \"shape\": \"rectangle\",\n          \"name\": \"PDU Rack A1\",\n          \"ip\": \"192.168.200.21\",\n          \"role\": \"Power Distribution\",\n          \"tags\": [\n            \"power\",\n            \"pdu\",\n            \"rack-a1\"\n          ],\n          \"notes\": [\n            \"APC Switched PDU\",\n            \"Per-outlet metering\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": 1,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"pdu-rack-a2\": {\n          \"shape\": \"rectangle\",\n          \"name\": \"PDU Rack A2\",\n          \"ip\": \"192.168.200.22\",\n          \"role\": \"Power Distribution\",\n          \"tags\": [\n            \"power\",\n            \"pdu\",\n            \"rack-a2\"\n          ],\n          \"notes\": [\n            \"APC Switched PDU\",\n            \"Per-outlet metering\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": 1,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"cooling-1\": {\n          \"shape\": \"rectangle\",\n          \"name\": \"CRAC Unit 1\",\n          \"ip\": \"192.168.200.30\",\n          \"role\": \"Cooling\",\n          \"tags\": [\n            \"cooling\",\n            \"hvac\",\n            \"datacenter\"\n          ],\n          \"notes\": [\n            \"Liebert CRV\",\n            \"Row-based cooling\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"cooling-2\": {\n          \"shape\": \"rectangle\",\n          \"name\": \"CRAC Unit 2\",\n          \"ip\": \"192.168.200.31\",\n          \"role\": \"Cooling\",\n          \"tags\": [\n            \"cooling\",\n            \"hvac\",\n            \"datacenter\"\n          ],\n          \"notes\": [\n            \"Liebert CRV\",\n            \"N+1 redundancy\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"camera-a\": {\n          \"shape\": \"camera\",\n          \"name\": \"camera A\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"ping\": {\n            \"enabled\": false,\n            \"protocol\": \"http\",\n            \"customUrl\": \"\",\n            \"timeout\": 3000,\n            \"status\": \"unknown\",\n            \"lastCheck\": null\n          },\n          \"locked\": false,\n          \"groupId\": null,\n          \"fovEnabled\": true,\n          \"fovRotation\": 104,\n          \"fovDistance\": 500,\n          \"fovSweep\": 60,\n          \"fovSpeed\": 10,\n          \"fovAnimate\": true\n        },\n        \"camera-a-copy\": {\n          \"shape\": \"camera\",\n          \"name\": \"camera B\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"ping\": {\n            \"enabled\": false,\n            \"protocol\": \"http\",\n            \"customUrl\": \"\",\n            \"timeout\": 3000,\n            \"status\": \"unknown\",\n            \"lastCheck\": null\n          },\n          \"locked\": false,\n          \"groupId\": null,\n          \"fovEnabled\": true,\n          \"fovRotation\": 162,\n          \"fovDistance\": 500,\n          \"fovSweep\": 60,\n          \"fovSpeed\": 10,\n          \"fovAnimate\": false\n        }\n      },\n      \"edges\": {\n        \"list\": [\n          {\n            \"id\": \"isp1-router1\",\n            \"from\": \"isp-primary\",\n            \"to\": \"core-router-1\",\n            \"width\": 6,\n            \"color\": \"#10b981\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"Primary WAN link\"\n            ],\n            \"fromPort\": \"Gi0/0\",\n            \"toPort\": \"Gi1/0/1\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"isp2-router2\",\n            \"from\": \"isp-secondary\",\n            \"to\": \"core-router-2\",\n            \"width\": 6,\n            \"color\": \"#10b981\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"Backup WAN link\"\n            ],\n            \"fromPort\": \"Gi0/0\",\n            \"toPort\": \"Gi1/0/1\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"router1-router2\",\n            \"from\": \"core-router-1\",\n            \"to\": \"core-router-2\",\n            \"width\": 4,\n            \"color\": \"#f59e0b\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"HSRP Peering\"\n            ],\n            \"fromPort\": \"Gi1/0/24\",\n            \"toPort\": \"Gi1/0/24\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"router1-fw1\",\n            \"from\": \"core-router-1\",\n            \"to\": \"fw-external-1\",\n            \"width\": 4,\n            \"color\": \"#ef4444\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"router2-fw2\",\n            \"from\": \"core-router-2\",\n            \"to\": \"fw-external-2\",\n            \"width\": 4,\n            \"color\": \"#ef4444\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"fw1-fw2\",\n            \"from\": \"fw-external-1\",\n            \"to\": \"fw-external-2\",\n            \"width\": 3,\n            \"color\": \"#f59e0b\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"HA heartbeat\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dashed\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"fw1-coresw1\",\n            \"from\": \"fw-external-1\",\n            \"to\": \"core-switch-1\",\n            \"width\": 4,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"fw2-coresw2\",\n            \"from\": \"fw-external-2\",\n            \"to\": \"core-switch-2\",\n            \"width\": 4,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-coresw2\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"core-switch-2\",\n            \"width\": 5,\n            \"color\": \"#3b82f6\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"VPC peer-link\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-fwint\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"fw-internal\",\n            \"width\": 3,\n            \"color\": \"#ef4444\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw2-fwint\",\n            \"from\": \"core-switch-2\",\n            \"to\": \"fw-internal\",\n            \"width\": 3,\n            \"color\": \"#ef4444\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-racka1\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"dc-rack-a1\",\n            \"width\": 4,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw2-racka1\",\n            \"from\": \"core-switch-2\",\n            \"to\": \"dc-rack-a1\",\n            \"width\": 4,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-racka2\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"dc-rack-a2\",\n            \"width\": 4,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw2-racka2\",\n            \"from\": \"core-switch-2\",\n            \"to\": \"dc-rack-a2\",\n            \"width\": 4,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-rackb1\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"dc-rack-b1\",\n            \"width\": 4,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw2-rackb1\",\n            \"from\": \"core-switch-2\",\n            \"to\": \"dc-rack-b1\",\n            \"width\": 4,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-rackb2\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"dc-rack-b2\",\n            \"width\": 4,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw2-rackb2\",\n            \"from\": \"core-switch-2\",\n            \"to\": \"dc-rack-b2\",\n            \"width\": 4,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"fw1-dmz\",\n            \"from\": \"fw-external-1\",\n            \"to\": \"dmz-rack\",\n            \"width\": 3,\n            \"color\": \"#f59e0b\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"DMZ segment\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"fw2-dmz\",\n            \"from\": \"fw-external-2\",\n            \"to\": \"dmz-rack\",\n            \"width\": 3,\n            \"color\": \"#f59e0b\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"DMZ segment\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-mgmt\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"mgmt-rack\",\n            \"width\": 3,\n            \"color\": \"#8b5cf6\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"OOB management\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-wlc1\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"wlc-primary\",\n            \"width\": 3,\n            \"color\": \"#06b6d4\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw2-wlc2\",\n            \"from\": \"core-switch-2\",\n            \"to\": \"wlc-secondary\",\n            \"width\": 3,\n            \"color\": \"#06b6d4\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\",\n            \"animate\": true\n          },\n          {\n            \"id\": \"wlc1-wlc2\",\n            \"from\": \"wlc-primary\",\n            \"to\": \"wlc-secondary\",\n            \"width\": 2,\n            \"color\": \"#f59e0b\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"HA pair\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dashed\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"wlc1-mobile-hq\",\n            \"from\": \"wlc-primary\",\n            \"to\": \"mobile-zone-hq\",\n            \"width\": 2,\n            \"color\": \"#06b6d4\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"wlc1-mobile-guest\",\n            \"from\": \"wlc-primary\",\n            \"to\": \"mobile-zone-guest\",\n            \"width\": 2,\n            \"color\": \"#06b6d4\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"wlc1-mobile-iot\",\n            \"from\": \"wlc-primary\",\n            \"to\": \"mobile-zone-iot\",\n            \"width\": 2,\n            \"color\": \"#06b6d4\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"router1-branch-ny\",\n            \"from\": \"core-router-1\",\n            \"to\": \"branch-router-ny\",\n            \"width\": 3,\n            \"color\": \"#a855f7\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"SD-WAN tunnel\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dashed\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"router1-branch-la\",\n            \"from\": \"core-router-1\",\n            \"to\": \"branch-router-la\",\n            \"width\": 3,\n            \"color\": \"#a855f7\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"SD-WAN tunnel\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dashed\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"router1-branch-chi\",\n            \"from\": \"core-router-1\",\n            \"to\": \"branch-router-chi\",\n            \"width\": 3,\n            \"color\": \"#a855f7\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"SD-WAN tunnel\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dashed\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"router1-branch-lon\",\n            \"from\": \"core-router-1\",\n            \"to\": \"branch-router-lon\",\n            \"width\": 3,\n            \"color\": \"#a855f7\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"SD-WAN tunnel\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dashed\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"router1-branch-tokyo\",\n            \"from\": \"core-router-1\",\n            \"to\": \"branch-router-tokyo\",\n            \"width\": 3,\n            \"color\": \"#a855f7\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"SD-WAN tunnel\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dashed\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"router1-aws\",\n            \"from\": \"core-router-1\",\n            \"to\": \"cloud-aws\",\n            \"width\": 3,\n            \"color\": \"#f97316\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"Direct Connect\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dashed\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"router2-azure\",\n            \"from\": \"core-router-2\",\n            \"to\": \"cloud-azure\",\n            \"width\": 3,\n            \"color\": \"#0ea5e9\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"ExpressRoute\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dashed\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"fwint-gcp\",\n            \"from\": \"fw-internal\",\n            \"to\": \"cloud-gcp\",\n            \"width\": 2,\n            \"color\": \"#22c55e\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"VPN tunnel\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dashed\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-floor1\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"dist-switch-floor1\",\n            \"width\": 3,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-floor2\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"dist-switch-floor2\",\n            \"width\": 3,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw2-floor3\",\n            \"from\": \"core-switch-2\",\n            \"to\": \"dist-switch-floor3\",\n            \"width\": 3,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw2-floor4\",\n            \"from\": \"core-switch-2\",\n            \"to\": \"dist-switch-floor4\",\n            \"width\": 3,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"floor1-endpoints\",\n            \"from\": \"dist-switch-floor1\",\n            \"to\": \"endpoint-1000\",\n            \"width\": 2,\n            \"color\": \"#94a3b8\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"floor1-ap1\",\n            \"from\": \"dist-switch-floor1\",\n            \"to\": \"ap-floor1-zone1\",\n            \"width\": 2,\n            \"color\": \"#06b6d4\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"floor2-ap2\",\n            \"from\": \"dist-switch-floor2\",\n            \"to\": \"ap-floor2-zone1\",\n            \"width\": 2,\n            \"color\": \"#06b6d4\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"floor3-ap3\",\n            \"from\": \"dist-switch-floor3\",\n            \"to\": \"ap-floor3-zone1\",\n            \"width\": 2,\n            \"color\": \"#06b6d4\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"floor4-ap4\",\n            \"from\": \"dist-switch-floor4\",\n            \"to\": \"ap-floor4-zone1\",\n            \"width\": 2,\n            \"color\": \"#06b6d4\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"fwint-proxy1\",\n            \"from\": \"fw-internal\",\n            \"to\": \"proxy-server-1\",\n            \"width\": 2,\n            \"color\": \"#ef4444\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"fwint-proxy2\",\n            \"from\": \"fw-internal\",\n            \"to\": \"proxy-server-2\",\n            \"width\": 2,\n            \"color\": \"#ef4444\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"fwext1-vpn\",\n            \"from\": \"fw-external-1\",\n            \"to\": \"vpn-concentrator\",\n            \"width\": 3,\n            \"color\": \"#8b5cf6\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-nac\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"nac-server\",\n            \"width\": 2,\n            \"color\": \"#f59e0b\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-voip\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"voip-cluster\",\n            \"width\": 3,\n            \"color\": \"#22c55e\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw2-video\",\n            \"from\": \"core-switch-2\",\n            \"to\": \"video-conf\",\n            \"width\": 3,\n            \"color\": \"#22c55e\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-cameras\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"security-cameras\",\n            \"width\": 2,\n            \"color\": \"#94a3b8\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"fwint-dev1\",\n            \"from\": \"fw-internal\",\n            \"to\": \"dev-server-1\",\n            \"width\": 2,\n            \"color\": \"#a855f7\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"fwint-dev2\",\n            \"from\": \"fw-internal\",\n            \"to\": \"dev-server-2\",\n            \"width\": 2,\n            \"color\": \"#a855f7\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\",\n            \"animate\": true,\n            \"animationSpeed\": \"1.5\"\n          },\n          {\n            \"id\": \"fwint-test\",\n            \"from\": \"fw-internal\",\n            \"to\": \"test-environment\",\n            \"width\": 2,\n            \"color\": \"#a855f7\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-erp\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"erp-system\",\n            \"width\": 3,\n            \"color\": \"#f59e0b\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"fwext1-crm\",\n            \"from\": \"fw-external-1\",\n            \"to\": \"crm-system\",\n            \"width\": 2,\n            \"color\": \"#f59e0b\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"Salesforce cloud\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dashed\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"ups1-racka1\",\n            \"from\": \"ups-dc-1\",\n            \"to\": \"dc-rack-a1\",\n            \"width\": 2,\n            \"color\": \"#fbbf24\",\n            \"direction\": \"forward\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"Power feed A\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"ups2-racka2\",\n            \"from\": \"ups-dc-2\",\n            \"to\": \"dc-rack-a2\",\n            \"width\": 2,\n            \"color\": \"#fbbf24\",\n            \"direction\": \"forward\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"Power feed B\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"ups1-rackb1\",\n            \"from\": \"ups-dc-1\",\n            \"to\": \"dc-rack-b1\",\n            \"width\": 2,\n            \"color\": \"#fbbf24\",\n            \"direction\": \"forward\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"Power feed A\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\",\n            \"animate\": true,\n            \"animationSpeed\": \"4\"\n          },\n          {\n            \"id\": \"ups2-rackb2\",\n            \"from\": \"ups-dc-2\",\n            \"to\": \"dc-rack-b2\",\n            \"width\": 2,\n            \"color\": \"#fbbf24\",\n            \"direction\": \"forward\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"Power feed B\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"cooling1-racka1\",\n            \"from\": \"cooling-1\",\n            \"to\": \"dc-rack-a1\",\n            \"width\": 2,\n            \"color\": \"#38bdf8\",\n            \"direction\": \"forward\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"Cooling zone\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dotted\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"cooling2-rackb1\",\n            \"from\": \"cooling-2\",\n            \"to\": \"dc-rack-b1\",\n            \"width\": 2,\n            \"color\": \"#38bdf8\",\n            \"direction\": \"forward\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"Cooling zone\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dotted\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"custom-1765237881452\",\n            \"type\": \"custom\",\n            \"color\": \"#c800ff\",\n            \"width\": 4,\n            \"lineStyle\": \"solid\",\n            \"direction\": \"forward\",\n            \"points\": [\n              {\n                \"x\": 3492.3994140625,\n                \"y\": 1526.9556884765625\n              },\n              {\n                \"x\": 3500.609619140625,\n                \"y\": 1830.7386474609375\n              },\n              {\n                \"x\": 3303.561279296875,\n                \"y\": 1732.2144775390625\n              }\n            ],\n            \"notes\": [],\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"custom-1765239355462\",\n            \"type\": \"custom\",\n            \"color\": \"#f97316\",\n            \"width\": 4,\n            \"lineStyle\": \"solid\",\n            \"direction\": \"forward\",\n            \"points\": [\n              {\n                \"x\": 2467.182861328125,\n                \"y\": 156.12173461914062\n              },\n              {\n                \"x\": 2146.36376953125,\n                \"y\": 146.32574462890625\n              },\n              {\n                \"x\": 2305.548828125,\n                \"y\": 244.28573608398438\n              }\n            ],\n            \"notes\": [],\n            \"routing\": \"orthogonal\"\n          }\n        ]\n      },\n      \"positions\": {\n        \"core-router-1\": {\n          \"x\": 3720.166015625,\n          \"y\": 245.9932403564453\n        },\n        \"core-router-2\": {\n          \"x\": 2499.883407638303,\n          \"y\": 329.99503430389154\n        },\n        \"fw-external-1\": {\n          \"x\": 3221.7385182723783,\n          \"y\": 1016.1364499992887\n        },\n        \"fw-external-2\": {\n          \"x\": 1915.5213706410505,\n          \"y\": 224.43528858865443\n        },\n        \"fw-internal\": {\n          \"x\": 1746.9168185079352,\n          \"y\": 477.5300527221864\n        },\n        \"core-switch-1\": {\n          \"x\": 449.39860669455675,\n          \"y\": 384.4578707617695\n        },\n        \"core-switch-2\": {\n          \"x\": 761.1664921394672,\n          \"y\": 180.89283910873155\n        },\n        \"dc-rack-a1\": {\n          \"x\": 783.7017241128451,\n          \"y\": 647.4086870405963\n        },\n        \"dc-rack-a2\": {\n          \"x\": 209.25701628255229,\n          \"y\": 228.01593190351014\n        },\n        \"dc-rack-b1\": {\n          \"x\": 3184.3186625759854,\n          \"y\": 1627.4495531027196\n        },\n        \"dc-rack-b2\": {\n          \"x\": 245.37065918741246,\n          \"y\": 499.6191264194081\n        },\n        \"dmz-rack\": {\n          \"x\": 2176.4105289561007,\n          \"y\": 610.8312056412005\n        },\n        \"mgmt-rack\": {\n          \"x\": 1601.2987201807314,\n          \"y\": 1281.4753424975324\n        },\n        \"esxi-host-01\": {\n          \"x\": 2162.2166789540615,\n          \"y\": 2608.110619289529\n        },\n        \"esxi-host-02\": {\n          \"x\": 2205.94717202368,\n          \"y\": 2689.67539624076\n        },\n        \"esxi-host-03\": {\n          \"x\": 2154.6015436939074,\n          \"y\": 2771.203009774913\n        },\n        \"esxi-host-04\": {\n          \"x\": 2195.986926025096,\n          \"y\": 2845\n        },\n        \"tor-switch-a1\": {\n          \"x\": 2146.8943639962963,\n          \"y\": 2845\n        },\n        \"esxi-host-05\": {\n          \"x\": 2185.9099961569727,\n          \"y\": 2845\n        },\n        \"esxi-host-06\": {\n          \"x\": 2139.099728450725,\n          \"y\": 2845\n        },\n        \"esxi-host-07\": {\n          \"x\": 2175.7223818764883,\n          \"y\": 2845\n        },\n        \"esxi-host-08\": {\n          \"x\": 2131.2222777148922,\n          \"y\": 2845\n        },\n        \"tor-switch-a2\": {\n          \"x\": 2165.4301485385085,\n          \"y\": 2845\n        },\n        \"san-primary\": {\n          \"x\": 2123.2667017518106,\n          \"y\": 2845\n        },\n        \"san-secondary\": {\n          \"x\": 2155.0394237844876,\n          \"y\": 2845\n        },\n        \"fc-switch-1\": {\n          \"x\": 2115.2377370375634,\n          \"y\": 2845\n        },\n        \"fc-switch-2\": {\n          \"x\": 2144.5563938942755,\n          \"y\": 2845\n        },\n        \"backup-server-1\": {\n          \"x\": 2107.1401637413705,\n          \"y\": 2845\n        },\n        \"backup-server-2\": {\n          \"x\": 2133.987300103025,\n          \"y\": 2845\n        },\n        \"tape-library\": {\n          \"x\": 2098.9788028796397,\n          \"y\": 2845\n        },\n        \"tor-switch-b1\": {\n          \"x\": 2123.338434885373,\n          \"y\": 2845\n        },\n        \"tor-switch-b2\": {\n          \"x\": 2090.7585134456995,\n          \"y\": 2845\n        },\n        \"web-server-1\": {\n          \"x\": 2112.6161382091163,\n          \"y\": 2845\n        },\n        \"web-server-2\": {\n          \"x\": 2082.484189516922,\n          \"y\": 2845\n        },\n        \"waf-1\": {\n          \"x\": 2101.826793760617,\n          \"y\": 2845\n        },\n        \"load-balancer-dmz\": {\n          \"x\": 2074.1607573409574,\n          \"y\": 2845\n        },\n        \"mail-gateway\": {\n          \"x\": 2090.97682514417,\n          \"y\": 2845\n        },\n        \"dns-external-1\": {\n          \"x\": 2065.7931724028163,\n          \"y\": 2845\n        },\n        \"dns-external-2\": {\n          \"x\": 2080.0726920576153,\n          \"y\": 2845\n        },\n        \"vcenter\": {\n          \"x\": 2057.3864164745437,\n          \"y\": 2845\n        },\n        \"nsx-manager\": {\n          \"x\": 2069.1208864464534,\n          \"y\": 2845\n        },\n        \"siem-server\": {\n          \"x\": 2048.945494649244,\n          \"y\": 2845\n        },\n        \"nms-server\": {\n          \"x\": 2058.1279286387635,\n          \"y\": 2845\n        },\n        \"jump-server\": {\n          \"x\": 2040.4754323612206,\n          \"y\": 2845\n        },\n        \"ipam-server\": {\n          \"x\": 2047.1003634632284,\n          \"y\": 2845\n        },\n        \"wlc-primary\": {\n          \"x\": 1575.9723612611924,\n          \"y\": 2306.135986328125\n        },\n        \"wlc-secondary\": {\n          \"x\": 1468.1361870166274,\n          \"y\": 1563.733642578125\n        },\n        \"mobile-zone-hq\": {\n          \"x\": 2354.901177346808,\n          \"y\": 2806.0078125\n        },\n        \"mobile-zone-guest\": {\n          \"x\": 2307.6605605284435,\n          \"y\": 2611.047119140625\n        },\n        \"mobile-zone-iot\": {\n          \"x\": 2229.397686389302,\n          \"y\": 2299.110107421875\n        },\n        \"branch-router-ny\": {\n          \"x\": 3151.903101363964,\n          \"y\": 633.6580810546875\n        },\n        \"branch-router-la\": {\n          \"x\": 3083.8876194705945,\n          \"y\": 506.90625\n        },\n        \"branch-router-chi\": {\n          \"x\": 3355.02409980103,\n          \"y\": 393.1805725097656\n        },\n        \"branch-router-lon\": {\n          \"x\": 3113.609823320121,\n          \"y\": 260.4093322753906\n        },\n        \"branch-router-tokyo\": {\n          \"x\": 3699.3234994733834,\n          \"y\": 471.4241027832031\n        },\n        \"cloud-aws\": {\n          \"x\": 3436.528122523513,\n          \"y\": 545.9614868164062\n        },\n        \"cloud-azure\": {\n          \"x\": 2592.566210818907,\n          \"y\": 2724.068115234375\n        },\n        \"cloud-gcp\": {\n          \"x\": 2827.3183770424234,\n          \"y\": 2731.397216796875\n        },\n        \"isp-primary\": {\n          \"x\": 3712.192068081962,\n          \"y\": 615.64990234375\n        },\n        \"isp-secondary\": {\n          \"x\": 2702.3789772348055,\n          \"y\": 467.890869140625\n        },\n        \"dc-internal-1\": {\n          \"x\": 1958.4243458877936,\n          \"y\": 2845\n        },\n        \"dc-internal-2\": {\n          \"x\": 1963.768951182132,\n          \"y\": 2845\n        },\n        \"app-server-1\": {\n          \"x\": 1947.3819379304134,\n          \"y\": 2845\n        },\n        \"app-server-2\": {\n          \"x\": 1955.2862087394126,\n          \"y\": 2845\n        },\n        \"db-server-1\": {\n          \"x\": 1936.3708569559828,\n          \"y\": 2845\n        },\n        \"db-server-2\": {\n          \"x\": 1946.8300873488822,\n          \"y\": 2845\n        },\n        \"k8s-master-1\": {\n          \"x\": 1925.397658583093,\n          \"y\": 2845\n        },\n        \"k8s-master-2\": {\n          \"x\": 1938.405621494142,\n          \"y\": 2845\n        },\n        \"k8s-master-3\": {\n          \"x\": 1914.4688758763386,\n          \"y\": 2845\n        },\n        \"k8s-worker-1\": {\n          \"x\": 1930.017826812177,\n          \"y\": 2845\n        },\n        \"k8s-worker-2\": {\n          \"x\": 1903.5910154567553,\n          \"y\": 2845\n        },\n        \"k8s-worker-3\": {\n          \"x\": 1921.6716971072178,\n          \"y\": 2845\n        },\n        \"k8s-worker-4\": {\n          \"x\": 1892.7705536280016,\n          \"y\": 2845\n        },\n        \"proxy-server-1\": {\n          \"x\": 1806.1152433697903,\n          \"y\": 653.7529296875\n        },\n        \"proxy-server-2\": {\n          \"x\": 2937.4207928721535,\n          \"y\": 2628.7880859375\n        },\n        \"vpn-concentrator\": {\n          \"x\": 3642.252088474593,\n          \"y\": 946.7255249023438\n        },\n        \"nac-server\": {\n          \"x\": 1153.2626148502184,\n          \"y\": 1172.1895751953125\n        },\n        \"print-server\": {\n          \"x\": 1896.9328460745962,\n          \"y\": 2845\n        },\n        \"file-server\": {\n          \"x\": 1860.7177871362182,\n          \"y\": 2845\n        },\n        \"ca-server\": {\n          \"x\": 1888.8027739274805,\n          \"y\": 2845\n        },\n        \"sccm-server\": {\n          \"x\": 1850.1909418511675,\n          \"y\": 2845\n        },\n        \"voip-cluster\": {\n          \"x\": 1777.038465328039,\n          \"y\": 1616.8961181640625\n        },\n        \"video-conf\": {\n          \"x\": 1993.8373941679588,\n          \"y\": 2244.936309814453\n        },\n        \"security-cameras\": {\n          \"x\": 1674.413336949044,\n          \"y\": 2046.0380859375\n        },\n        \"nvr-cluster\": {\n          \"x\": 1829.4110389706402,\n          \"y\": 2845\n        },\n        \"dev-server-1\": {\n          \"x\": 2800.5894350649614,\n          \"y\": 1175.623291015625\n        },\n        \"dev-server-2\": {\n          \"x\": 1945.0822182484326,\n          \"y\": 1164.5184783935547\n        },\n        \"test-environment\": {\n          \"x\": 2566.9100352578575,\n          \"y\": 885.2827758789062\n        },\n        \"erp-system\": {\n          \"x\": 789.9880103985649,\n          \"y\": 473.7113342285156\n        },\n        \"crm-system\": {\n          \"x\": 3514.6003232048542,\n          \"y\": 1137.7720947265625\n        },\n        \"endpoint-1000\": {\n          \"x\": 991.6812012057328,\n          \"y\": 2284.42236328125\n        },\n        \"dist-switch-floor1\": {\n          \"x\": 654.2091033261356,\n          \"y\": 2020.0086669921875\n        },\n        \"dist-switch-floor2\": {\n          \"x\": 853.8845527112826,\n          \"y\": 1843.2872314453125\n        },\n        \"dist-switch-floor3\": {\n          \"x\": 1899.4353951584517,\n          \"y\": 1456.5068359375\n        },\n        \"dist-switch-floor4\": {\n          \"x\": 488.5289313756234,\n          \"y\": 181.47256469726562\n        },\n        \"ap-floor1-zone1\": {\n          \"x\": 1140.16846970184,\n          \"y\": 2070.2916259765625\n        },\n        \"ap-floor2-zone1\": {\n          \"x\": 688.1952143592268,\n          \"y\": 2384.4775390625\n        },\n        \"ap-floor3-zone1\": {\n          \"x\": 2145.3803027919676,\n          \"y\": 1890.2816162109375\n        },\n        \"ap-floor4-zone1\": {\n          \"x\": 517.646146409649,\n          \"y\": 565.59716796875\n        },\n        \"ups-dc-1\": {\n          \"x\": 771.1406786539856,\n          \"y\": 295.9266662597656\n        },\n        \"ups-dc-2\": {\n          \"x\": 216.2410855890687,\n          \"y\": 330.3345947265625\n        },\n        \"pdu-rack-a1\": {\n          \"x\": 1804.774444371901,\n          \"y\": 2845\n        },\n        \"pdu-rack-a2\": {\n          \"x\": 1741.6184034693686,\n          \"y\": 2845\n        },\n        \"cooling-1\": {\n          \"x\": 245.7080801919958,\n          \"y\": 626.1914672851562\n        },\n        \"cooling-2\": {\n          \"x\": 1603.293611085831,\n          \"y\": 981.0621185302734\n        },\n        \"camera-a\": {\n          \"x\": 166.57075412676295,\n          \"y\": 145\n        },\n        \"camera-a-copy\": {\n          \"x\": 1040.653076171875,\n          \"y\": 738.42822265625\n        }\n      },\n      \"sizes\": {\n        \"isp-secondary\": 139,\n        \"test-environment\": 148,\n        \"dev-server-1\": 128,\n        \"core-router-2\": 120,\n        \"camera-a\": 45,\n        \"camera-a-copy\": 45\n      },\n      \"styles\": {\n        \"dc-rack-b2\": {\n          \"all\": {\n            \"circleColor\": \"#ff0000\"\n          }\n        },\n        \"dc-rack-a1\": {\n          \"all\": {\n            \"circleColor\": \"#ff0000\"\n          }\n        },\n        \"dc-rack-b1\": {\n          \"all\": {\n            \"circleColor\": \"#ff0000\",\n            \"titleSize\": 59\n          }\n        },\n        \"isp-secondary\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"alist\"\n            }\n          }\n        },\n        \"core-router-2\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"actual-budget\"\n            },\n            \"pingOffsetX\": -15,\n            \"pingOffsetY\": -38\n          }\n        },\n        \"fw-external-1\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"anonaddy\"\n            }\n          }\n        },\n        \"cloud-aws\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"ansible\"\n            }\n          }\n        },\n        \"isp-primary\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"wikidocs\"\n            }\n          }\n        },\n        \"branch-router-tokyo\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"adguard-home\"\n            }\n          }\n        },\n        \"core-router-1\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"borg\"\n            }\n          }\n        },\n        \"test-environment\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"simple\",\n              \"name\": \"apple\"\n            }\n          }\n        },\n        \"dev-server-1\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"simple\",\n              \"name\": \"amazonwebservices\"\n            }\n          }\n        }\n      },\n      \"legend\": {\n        \"#10b981\": \"Trusted Lan\",\n        \"#f59e0b\": \"Secure Lan\",\n        \"#ef4444\": \"DMZ\",\n        \"#475569\": \"Main ISP\",\n        \"#3b82f6\": \"Alternate ISP\",\n        \"#8b5cf6\": \"you can edit me too\",\n        \"#06b6d4\": \"you can edit me too\",\n        \"#a855f7\": \"you can edit me too\",\n        \"#f97316\": \"you can edit me too\",\n        \"#0ea5e9\": \"you can edit me too\",\n        \"#22c55e\": \"you can edit me too\",\n        \"#94a3b8\": \"you can edit me too\",\n        \"#fbbf24\": \"you can edit me too\",\n        \"#38bdf8\": \"you can edit me too\",\n        \"#c800ff\": \"you can edit me too\"\n      },\n      \"rects\": {\n        \"list\": [\n          {\n            \"id\": \"rect-1765237540610\",\n            \"x\": 2879.214599609375,\n            \"y\": 159.71981811523438,\n            \"width\": 992.196044921875,\n            \"height\": 538.8650817871094,\n            \"color\": \"#f97316\",\n            \"style\": \"filled\",\n            \"lineStyle\": \"solid\",\n            \"notes\": []\n          },\n          {\n            \"id\": \"rect-1765237681216\",\n            \"x\": 448.3926696777344,\n            \"y\": 1671.651123046875,\n            \"width\": 916.3436584472656,\n            \"height\": 924.27734375,\n            \"color\": \"#c800ff\",\n            \"style\": \"outlined\",\n            \"lineStyle\": \"solid\",\n            \"notes\": []\n          },\n          {\n            \"id\": \"rect-1766437913740\",\n            \"x\": 904.5889892578125,\n            \"y\": 115.40318298339844,\n            \"width\": 110.93878173828125,\n            \"height\": 919.6242218017578,\n            \"color\": \"#5215f9\",\n            \"style\": \"filled\",\n            \"lineStyle\": \"wall\",\n            \"notes\": [],\n            \"borderWidth\": 13\n          },\n          {\n            \"id\": \"rect-1766437935414\",\n            \"x\": 130.93685150146484,\n            \"y\": 1072.3624877929688,\n            \"width\": 872.9131851196289,\n            \"height\": 99.260986328125,\n            \"color\": \"#5215f9\",\n            \"style\": \"filled\",\n            \"lineStyle\": \"wall\",\n            \"notes\": [],\n            \"borderWidth\": 13\n          }\n        ]\n      },\n      \"texts\": {\n        \"list\": [\n          {\n            \"id\": \"text-1765237828167\",\n            \"x\": 3411.458740234375,\n            \"y\": 1390.00439453125,\n            \"content\": \"Double click on desktop\\nor long press on mobile\\nto enter rack canvas view\",\n            \"fontSize\": 46,\n            \"color\": \"#e2e8f0\",\n            \"fontWeight\": \"bold\",\n            \"fontStyle\": \"italic\",\n            \"textAlign\": \"middle\",\n            \"textDecoration\": \"none\",\n            \"bgColor\": \"#000000\",\n            \"bgEnabled\": false,\n            \"opacity\": 1\n          },\n          {\n            \"id\": \"text-1765239331126\",\n            \"x\": 2454.5615234375,\n            \"y\": 160.73322105407715,\n            \"content\": \"Google is live!\",\n            \"fontSize\": 56,\n            \"color\": \"#e2e8f0\",\n            \"fontWeight\": \"bold\",\n            \"fontStyle\": \"normal\",\n            \"textAlign\": \"start\",\n            \"textDecoration\": \"none\",\n            \"bgColor\": \"#000000\",\n            \"bgEnabled\": false,\n            \"opacity\": 1\n          },\n          {\n            \"id\": \"text-1766446595277\",\n            \"x\": 654.3878479003906,\n            \"y\": 1367.7945556640625,\n            \"content\": \"SITE A\",\n            \"fontSize\": 101,\n            \"color\": \"#e2e8f0\",\n            \"fontWeight\": \"normal\",\n            \"fontStyle\": \"normal\",\n            \"textAlign\": \"start\",\n            \"textDecoration\": \"none\",\n            \"bgColor\": \"#000000\",\n            \"bgEnabled\": false,\n            \"opacity\": 1\n          },\n          {\n            \"id\": \"text-1766446610211\",\n            \"x\": 180.63662719726562,\n            \"y\": 1128.822998046875,\n            \"content\": \"SITE A\",\n            \"fontSize\": 101,\n            \"color\": \"#e2e8f0\",\n            \"fontWeight\": \"normal\",\n            \"fontStyle\": \"normal\",\n            \"textAlign\": \"start\",\n            \"textDecoration\": \"none\",\n            \"bgColor\": \"#000000\",\n            \"bgEnabled\": false,\n            \"opacity\": 1\n          },\n          {\n            \"id\": \"text-1766453024797\",\n            \"x\": 968.6458740234375,\n            \"y\": 1028.6621398925781,\n            \"content\": \"SITE A\",\n            \"fontSize\": 101,\n            \"color\": \"#e2e8f0\",\n            \"fontWeight\": \"normal\",\n            \"fontStyle\": \"normal\",\n            \"textAlign\": \"start\",\n            \"textDecoration\": \"none\",\n            \"bgColor\": \"#000000\",\n            \"bgEnabled\": false,\n            \"opacity\": 1,\n            \"rotation\": -89,\n            \"_dragStartX\": 972.46826171875,\n            \"_dragStartY\": 1009.5499572753906\n          },\n          {\n            \"id\": \"text-1766453070975\",\n            \"x\": 613.1589965820312,\n            \"y\": 1139.512939453125,\n            \"content\": \"SITE A\",\n            \"fontSize\": 101,\n            \"color\": \"#e2e8f0\",\n            \"fontWeight\": \"normal\",\n            \"fontStyle\": \"normal\",\n            \"textAlign\": \"start\",\n            \"textDecoration\": \"none\",\n            \"bgColor\": \"#000000\",\n            \"bgEnabled\": false,\n            \"opacity\": 1\n          },\n          {\n            \"id\": \"text-1766453072857\",\n            \"x\": 968.64599609375,\n            \"y\": 474.40818786621094,\n            \"content\": \"SITE A\",\n            \"fontSize\": 101,\n            \"color\": \"#e2e8f0\",\n            \"fontWeight\": \"normal\",\n            \"fontStyle\": \"normal\",\n            \"textAlign\": \"start\",\n            \"textDecoration\": \"none\",\n            \"bgColor\": \"#000000\",\n            \"bgEnabled\": false,\n            \"opacity\": 1,\n            \"rotation\": 269,\n            \"_dragStartX\": 1480.85302734375,\n            \"_dragStartY\": 822.2503356933594\n          }\n        ]\n      },\n      \"pageState\": {\n        \"title\": \"The One File Corporate\",\n        \"background\": \"\",\n        \"topbarBg\": \"rgba(9, 12, 20, 0.9)\",\n        \"topbarBorder\": \"#1f2533\",\n        \"panel\": \"#0b0e13\",\n        \"panelAlt\": \"#10141b\",\n        \"accent\": \"#4fd1c5\",\n        \"sidebarBg\": \"#10141b\",\n        \"btnBg\": \"#0b0e13\",\n        \"btnText\": \"#e2e8f0\",\n        \"tagFill\": \"#1e293b\",\n        \"tagText\": \"#e2e8f0\",\n        \"tagBorder\": \"#475569\",\n        \"inputBg\": \"#0b0e13\",\n        \"inputText\": \"#e2e8f0\",\n        \"inputBorder\": \"#1f2937\",\n        \"inputFont\": \"Inter, system-ui, sans-serif\",\n        \"inputFontSize\": 14,\n        \"toolbarBg\": \"#0f172a\",\n        \"toolbarBorder\": \"#1f2937\",\n        \"toolbarText\": \"#94a3b8\",\n        \"toolbarBtnBg\": \"#0b0e13\",\n        \"toolbarBtnText\": \"#e2e8f0\",\n        \"minimapDots\": \"#94a3b8\",\n        \"canvasHintEnabled\": true,\n        \"canvasHintText\": \"\",\n        \"canvasHintBg\": \"#0f172a\",\n        \"canvasHintColor\": \"#94a3b8\",\n        \"danger\": \"#f56565\",\n        \"textMain\": \"#e2e8f0\",\n        \"textSoft\": \"#94a3b8\",\n        \"topbarHeight\": 103,\n        \"sidebarWidth\": 350,\n        \"mobileFooterHeight\": 40,\n        \"sidebarCollapsed\": false,\n        \"nodeFill\": \"#1e293b\",\n        \"nodeStroke\": \"#475569\",\n        \"nodeTitle\": \"#e2e8f0\",\n        \"nodeSub\": \"#94a3b8\",\n        \"nodeTitleSize\": 41,\n        \"nodeSubSize\": 27,\n        \"nodeFont\": \"monospace\",\n        \"defaultEdge\": \"#475569\",\n        \"selectionHandle\": \"#f59e0b\",\n        \"selectionHandleSize\": 8,\n        \"groupIndicator\": \"#4fd1c5\",\n        \"canvasGradientTop\": \"#1e2532\",\n        \"canvasGradientBottom\": \"#050608\",\n        \"canvasBorder\": \"#475569\",\n        \"canvasGrid\": \"#475569\",\n        \"canvasGridSize\": 50,\n        \"canvasGridEnabled\": true,\n        \"rackFrameFill\": \"#0f172a\",\n        \"rackFrameStroke\": \"#4fd1c5\",\n        \"rackLineColor\": \"#475569\",\n        \"rackTextColor\": \"#4fd1c5\",\n        \"rackGridEnabled\": true,\n        \"viewOnly\": false,\n        \"defaultEdgeRouting\": \"orthogonal\",\n        \"animateConnections\": false,\n        \"animationStyle\": \"arrows\",\n        \"animationDirection\": \"all\",\n        \"animationSpeed\": 4,\n        \"autoPingEnabled\": false,\n        \"autoPingInterval\": 30\n      }\n    },\n    {\n      \"id\": \"tab-1765235136918\",\n      \"name\": \"Homelab 2\",\n      \"nodes\": {\n        \"internet\": {\n          \"shape\": \"square\",\n          \"name\": \"Internet\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"internet-copy\": {\n          \"shape\": \"firewall\",\n          \"name\": \"OPNSENSE\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"opnsense-copy\": {\n          \"shape\": \"firewall\",\n          \"name\": \"Docker\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"docker-copy\": {\n          \"shape\": \"firewall\",\n          \"name\": \"Docker2\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"docker-copy-1\": {\n          \"shape\": \"firewall\",\n          \"name\": \"Docker3\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"docker-copy-2\": {\n          \"shape\": \"firewall\",\n          \"name\": \"Docker 4\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [\n            {\n              \"type\": \"icon\",\n              \"library\": \"selfhst\",\n              \"name\": \"docker\"\n            },\n            {\n              \"type\": \"icon\",\n              \"library\": \"selfhst\",\n              \"name\": \"authentik\"\n            },\n            {\n              \"type\": \"icon\",\n              \"library\": \"selfhst\",\n              \"name\": \"immich\"\n            }\n          ],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"opnsense-copy-1\": {\n          \"shape\": \"firewall\",\n          \"name\": \"OPNSENSE GUEST\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"phone\": {\n          \"shape\": \"phone\",\n          \"name\": \"Phone\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"desktop\": {\n          \"shape\": \"pc\",\n          \"name\": \"Desktop\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dns\": {\n          \"shape\": \"cloud\",\n          \"name\": \"DNS\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"racked\": {\n          \"shape\": \"server\",\n          \"name\": \"Racked\",\n          \"ip\": \"\",\n          \"role\": \"Rack\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": true,\n          \"locked\": false,\n          \"groupId\": null\n        }\n      },\n      \"edges\": {\n        \"list\": [\n          {\n            \"id\": \"internet-internet-copy-1765238145151\",\n            \"from\": \"internet\",\n            \"to\": \"internet-copy\",\n            \"width\": 4,\n            \"color\": \"#55e208\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1\n          },\n          {\n            \"id\": \"internet-copy-opnsense-copy-1765238187451\",\n            \"from\": \"internet-copy\",\n            \"to\": \"opnsense-copy\",\n            \"width\": 4,\n            \"color\": \"#4c00ff\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1\n          },\n          {\n            \"id\": \"internet-copy-docker-copy-1765238242477\",\n            \"from\": \"internet-copy\",\n            \"to\": \"docker-copy\",\n            \"width\": 4,\n            \"color\": \"#4c00ff\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1\n          },\n          {\n            \"id\": \"internet-copy-docker-copy-1-1765238244637\",\n            \"from\": \"internet-copy\",\n            \"to\": \"docker-copy-1\",\n            \"width\": 4,\n            \"color\": \"#4c00ff\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1\n          },\n          {\n            \"id\": \"internet-copy-docker-copy-2-1765238246233\",\n            \"from\": \"internet-copy\",\n            \"to\": \"docker-copy-2\",\n            \"width\": 4,\n            \"color\": \"#4c00ff\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1\n          },\n          {\n            \"id\": \"internet-opnsense-copy-1-1765238266117\",\n            \"from\": \"internet\",\n            \"to\": \"opnsense-copy-1\",\n            \"width\": 4,\n            \"color\": \"#80ff00\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1\n          },\n          {\n            \"id\": \"opnsense-copy-1-dns-1765238347996\",\n            \"from\": \"opnsense-copy-1\",\n            \"to\": \"dns\",\n            \"width\": 4,\n            \"color\": \"#fb00ff\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1\n          },\n          {\n            \"id\": \"dns-desktop-1765238386101\",\n            \"from\": \"dns\",\n            \"to\": \"desktop\",\n            \"width\": 4,\n            \"color\": \"#ff00d0\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1\n          },\n          {\n            \"id\": \"phone-dns-1765238391156\",\n            \"from\": \"phone\",\n            \"to\": \"dns\",\n            \"width\": 4,\n            \"color\": \"#ff00d0\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1\n          },\n          {\n            \"id\": \"custom-1765239449323\",\n            \"type\": \"custom\",\n            \"color\": \"#f97316\",\n            \"width\": 4,\n            \"lineStyle\": \"solid\",\n            \"direction\": \"forward\",\n            \"points\": [\n              {\n                \"x\": 2936.464111328125,\n                \"y\": 786.07958984375\n              },\n              {\n                \"x\": 3184.112060546875,\n                \"y\": 887.6153564453125\n              },\n              {\n                \"x\": 2763.110107421875,\n                \"y\": 981.7216796875\n              }\n            ],\n            \"notes\": []\n          }\n        ]\n      },\n      \"positions\": {\n        \"internet\": {\n          \"x\": 2103.968290880771,\n          \"y\": 268\n        },\n        \"internet-copy\": {\n          \"x\": 2066.9677515897347,\n          \"y\": 473.4119134177565\n        },\n        \"opnsense-copy\": {\n          \"x\": 1773.8400660428597,\n          \"y\": 666.5758233298659\n        },\n        \"docker-copy\": {\n          \"x\": 1931.1978950081452,\n          \"y\": 782.2775961320921\n        },\n        \"docker-copy-1\": {\n          \"x\": 2158.1262397347077,\n          \"y\": 767.7122274797483\n        },\n        \"docker-copy-2\": {\n          \"x\": 2342.2663764534577,\n          \"y\": 631.7681967180296\n        },\n        \"opnsense-copy-1\": {\n          \"x\": 2757.879480087803,\n          \"y\": 307.6117116091891\n        },\n        \"phone\": {\n          \"x\": 3312.857751572178,\n          \"y\": 502.58220111114224\n        },\n        \"desktop\": {\n          \"x\": 2971.700036728428,\n          \"y\": 480.7287465212985\n        },\n        \"dns\": {\n          \"x\": 3200.4643189549906,\n          \"y\": 320.469591247861\n        },\n        \"racked\": {\n          \"x\": 2645.5845448279656,\n          \"y\": 970.7820678889219\n        }\n      },\n      \"sizes\": {\n        \"core-router-1\": 36,\n        \"internet\": 168,\n        \"phone\": 121,\n        \"desktop\": 147,\n        \"racked\": 137,\n        \"docker-copy-2\": 82\n      },\n      \"styles\": {\n        \"internet\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"amazon-web-services\"\n            },\n            \"circleColor\": \"#ffffff\",\n            \"circleBorder\": \"#ffffff\"\n          }\n        },\n        \"opnsense-copy-1\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"opnsense-v1\"\n            }\n          }\n        },\n        \"internet-copy\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"opnsense\"\n            }\n          }\n        },\n        \"docker-copy-2\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"docker\"\n            }\n          }\n        },\n        \"docker-copy-1\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"authportal\"\n            }\n          }\n        },\n        \"docker-copy\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"jotty\"\n            }\n          }\n        },\n        \"opnsense-copy\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"portainer\"\n            }\n          }\n        },\n        \"racked\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"mdi\",\n              \"name\": \"server-security\"\n            },\n            \"circleColor\": \"#010813\",\n            \"circleBorder\": \"#ffffff\"\n          }\n        }\n      },\n      \"legend\": {\n        \"#475569\": \"you can edit me too\",\n        \"#65758b\": \"you can edit me too\",\n        \"#63748c\": \"you can edit me too\",\n        \"#5e6f87\": \"you can edit me too\",\n        \"#586a84\": \"you can edit me too\",\n        \"#4f627d\": \"you can edit me too\",\n        \"#455873\": \"you can edit me too\",\n        \"#3d506c\": \"you can edit me too\",\n        \"#354964\": \"you can edit me too\",\n        \"#2e415c\": \"you can edit me too\",\n        \"#293c56\": \"you can edit me too\",\n        \"#273a53\": \"you can edit me too\",\n        \"#253750\": \"you can edit me too\",\n        \"#23354d\": \"you can edit me too\",\n        \"#203046\": \"you can edit me too\",\n        \"#1e2d43\": \"you can edit me too\",\n        \"#1a283d\": \"you can edit me too\",\n        \"#172435\": \"you can edit me too\",\n        \"#141f2e\": \"you can edit me too\",\n        \"#111a27\": \"you can edit me too\",\n        \"#0f1824\": \"you can edit me too\",\n        \"#0d1521\": \"you can edit me too\",\n        \"#0c131d\": \"you can edit me too\",\n        \"#0c1d1c\": \"you can edit me too\",\n        \"#0c1c1d\": \"you can edit me too\",\n        \"#0c191d\": \"you can edit me too\",\n        \"#0c141d\": \"you can edit me too\",\n        \"#0c0d1d\": \"you can edit me too\",\n        \"#130c1d\": \"you can edit me too\",\n        \"#1b0c1d\": \"you can edit me too\",\n        \"#1d0c17\": \"you can edit me too\",\n        \"#1d0c10\": \"you can edit me too\",\n        \"#1d0c0c\": \"you can edit me too\",\n        \"#3b1b1b\": \"you can edit me too\",\n        \"#3c1a1a\": \"you can edit me too\",\n        \"#3f1c1c\": \"you can edit me too\",\n        \"#401c1c\": \"you can edit me too\",\n        \"#451c1c\": \"you can edit me too\",\n        \"#461b1b\": \"you can edit me too\",\n        \"#4c1a1a\": \"you can edit me too\",\n        \"#521919\": \"you can edit me too\",\n        \"#571919\": \"you can edit me too\",\n        \"#5d1818\": \"you can edit me too\",\n        \"#631717\": \"you can edit me too\",\n        \"#651515\": \"you can edit me too\",\n        \"#6a1616\": \"you can edit me too\",\n        \"#6f1515\": \"you can edit me too\",\n        \"#711414\": \"you can edit me too\",\n        \"#761414\": \"you can edit me too\",\n        \"#771313\": \"you can edit me too\",\n        \"#7c1313\": \"you can edit me too\",\n        \"#811313\": \"you can edit me too\",\n        \"#821212\": \"you can edit me too\",\n        \"#871212\": \"you can edit me too\",\n        \"#881111\": \"you can edit me too\",\n        \"#8d1111\": \"you can edit me too\",\n        \"#8e1010\": \"you can edit me too\",\n        \"#8f0f0f\": \"you can edit me too\",\n        \"#900e0e\": \"you can edit me too\",\n        \"#8e0b0b\": \"you can edit me too\",\n        \"#8c0d0d\": \"you can edit me too\",\n        \"#880c0c\": \"you can edit me too\",\n        \"#830c0c\": \"you can edit me too\",\n        \"#7e0c0c\": \"you can edit me too\",\n        \"#790c0c\": \"you can edit me too\",\n        \"#730c0c\": \"you can edit me too\",\n        \"#6f0b0b\": \"you can edit me too\",\n        \"#0b6f64\": \"you can edit me too\",\n        \"#0b6f5f\": \"you can edit me too\",\n        \"#0b6f56\": \"you can edit me too\",\n        \"#0b6f49\": \"you can edit me too\",\n        \"#0b6f31\": \"you can edit me too\",\n        \"#0b6f1f\": \"you can edit me too\",\n        \"#0b6f0d\": \"you can edit me too\",\n        \"#176f0b\": \"you can edit me too\",\n        \"#266f0b\": \"you can edit me too\",\n        \"#296f0b\": \"you can edit me too\",\n        \"#2e6f0b\": \"you can edit me too\",\n        \"#1a2d10\": \"you can edit me too\",\n        \"#1c3111\": \"you can edit me too\",\n        \"#213814\": \"you can edit me too\",\n        \"#233c15\": \"you can edit me too\",\n        \"#254017\": \"you can edit me too\",\n        \"#294918\": \"you can edit me too\",\n        \"#2b4d1a\": \"you can edit me too\",\n        \"#2d511a\": \"you can edit me too\",\n        \"#315a1b\": \"you can edit me too\",\n        \"#35631c\": \"you can edit me too\",\n        \"#37681d\": \"you can edit me too\",\n        \"#3b721d\": \"you can edit me too\",\n        \"#3f7b1e\": \"you can edit me too\",\n        \"#42851e\": \"you can edit me too\",\n        \"#46901d\": \"you can edit me too\",\n        \"#499a1d\": \"you can edit me too\",\n        \"#4b9f1d\": \"you can edit me too\",\n        \"#4ca61c\": \"you can edit me too\",\n        \"#50b01c\": \"you can edit me too\",\n        \"#51b71a\": \"you can edit me too\",\n        \"#50b918\": \"you can edit me too\",\n        \"#51c115\": \"you can edit me too\",\n        \"#53c615\": \"you can edit me too\",\n        \"#53c814\": \"you can edit me too\",\n        \"#52c913\": \"you can edit me too\",\n        \"#54d011\": \"you can edit me too\",\n        \"#53d110\": \"you can edit me too\",\n        \"#55d510\": \"you can edit me too\",\n        \"#55d70f\": \"you can edit me too\",\n        \"#54d80e\": \"you can edit me too\",\n        \"#54da0b\": \"you can edit me too\",\n        \"#56df0c\": \"you can edit me too\",\n        \"#53db0a\": \"you can edit me too\",\n        \"#55e00b\": \"you can edit me too\",\n        \"#55e109\": \"you can edit me too\",\n        \"#55e208\": \"ISP LINE\",\n        \"#4c00ff\": \"MY Guest NETWORK\",\n        \"#80ff00\": \"you can edit me too\",\n        \"#3b4234\": \"you can edit me too\",\n        \"#3a3442\": \"you can edit me too\",\n        \"#3b3442\": \"you can edit me too\",\n        \"#3c3442\": \"you can edit me too\",\n        \"#3d3442\": \"you can edit me too\",\n        \"#3e3442\": \"you can edit me too\",\n        \"#3f3442\": \"you can edit me too\",\n        \"#403442\": \"you can edit me too\",\n        \"#413442\": \"you can edit me too\",\n        \"#653d66\": \"you can edit me too\",\n        \"#683f69\": \"you can edit me too\",\n        \"#6c416c\": \"you can edit me too\",\n        \"#6f4370\": \"you can edit me too\",\n        \"#704270\": \"you can edit me too\",\n        \"#734474\": \"you can edit me too\",\n        \"#784479\": \"you can edit me too\",\n        \"#7d447e\": \"you can edit me too\",\n        \"#7e437f\": \"you can edit me too\",\n        \"#834384\": \"you can edit me too\",\n        \"#844285\": \"you can edit me too\",\n        \"#89418b\": \"you can edit me too\",\n        \"#8e428f\": \"you can edit me too\",\n        \"#904091\": \"you can edit me too\",\n        \"#923e93\": \"you can edit me too\",\n        \"#973e98\": \"you can edit me too\",\n        \"#943c96\": \"you can edit me too\",\n        \"#993c9a\": \"you can edit me too\",\n        \"#963a98\": \"you can edit me too\",\n        \"#973899\": \"you can edit me too\",\n        \"#99369b\": \"you can edit me too\",\n        \"#9a359c\": \"you can edit me too\",\n        \"#9b349d\": \"you can edit me too\",\n        \"#9d329f\": \"you can edit me too\",\n        \"#9e31a0\": \"you can edit me too\",\n        \"#a02fa2\": \"you can edit me too\",\n        \"#9d2d9f\": \"you can edit me too\",\n        \"#9f2ba1\": \"you can edit me too\",\n        \"#a129a3\": \"you can edit me too\",\n        \"#a327a5\": \"you can edit me too\",\n        \"#a525a7\": \"you can edit me too\",\n        \"#a723a9\": \"you can edit me too\",\n        \"#a921ab\": \"you can edit me too\",\n        \"#ab1fad\": \"you can edit me too\",\n        \"#ad1daf\": \"you can edit me too\",\n        \"#ae1cb0\": \"you can edit me too\",\n        \"#b019b3\": \"you can edit me too\",\n        \"#b118b4\": \"you can edit me too\",\n        \"#b316b6\": \"you can edit me too\",\n        \"#b816bb\": \"you can edit me too\",\n        \"#b514b8\": \"you can edit me too\",\n        \"#ba14bd\": \"you can edit me too\",\n        \"#b712ba\": \"you can edit me too\",\n        \"#bb13be\": \"you can edit me too\",\n        \"#b811bb\": \"you can edit me too\",\n        \"#be10c1\": \"you can edit me too\",\n        \"#bb0ebe\": \"you can edit me too\",\n        \"#bd0cc0\": \"you can edit me too\",\n        \"#be0bc1\": \"you can edit me too\",\n        \"#c108c4\": \"you can edit me too\",\n        \"#be06c1\": \"you can edit me too\",\n        \"#c103c4\": \"you can edit me too\",\n        \"#c301c6\": \"you can edit me too\",\n        \"#c400c7\": \"you can edit me too\",\n        \"#c900cc\": \"you can edit me too\",\n        \"#ce00d1\": \"you can edit me too\",\n        \"#d300d6\": \"you can edit me too\",\n        \"#d800db\": \"you can edit me too\",\n        \"#dd00e0\": \"you can edit me too\",\n        \"#e200e6\": \"you can edit me too\",\n        \"#ec00f0\": \"you can edit me too\",\n        \"#f100f5\": \"you can edit me too\",\n        \"#f600fa\": \"you can edit me too\",\n        \"#fb00ff\": \"you can edit me too\",\n        \"#ff00d0\": \"iPhone (always guest iPhone)\",\n        \"#f97316\": \"you can edit me too\"\n      },\n      \"rects\": {\n        \"list\": [\n          {\n            \"id\": \"rect-1765238219615\",\n            \"x\": 2680.053955078125,\n            \"y\": 251.44879150390625,\n            \"width\": 814.10400390625,\n            \"height\": 389.26678466796875,\n            \"color\": \"#ec0999\",\n            \"style\": \"filled\",\n            \"lineStyle\": \"solid\",\n            \"notes\": []\n          }\n        ]\n      },\n      \"texts\": {\n        \"list\": [\n          {\n            \"id\": \"text-1765238422602\",\n            \"x\": 2466.35986328125,\n            \"y\": 741.6801147460938,\n            \"content\": \"Double click on desktop\\nor long press on mobile\\nto enter rack canvas view\",\n            \"fontSize\": 40,\n            \"color\": \"#e2e8f0\",\n            \"fontWeight\": \"normal\",\n            \"fontStyle\": \"normal\",\n            \"textAlign\": \"start\",\n            \"textDecoration\": \"none\",\n            \"bgColor\": \"#000000\",\n            \"bgEnabled\": false,\n            \"opacity\": 1\n          }\n        ]\n      },\n      \"pageState\": {\n        \"title\": \"The One File\",\n        \"background\": \"\",\n        \"topbarBg\": \"rgba(9, 12, 20, 0.9)\",\n        \"topbarBorder\": \"#1f2533\",\n        \"panel\": \"#2f0e0e\",\n        \"panelAlt\": \"#10141b\",\n        \"accent\": \"#a75252\",\n        \"sidebarBg\": \"#10141b\",\n        \"btnBg\": \"#0b0e13\",\n        \"btnText\": \"#e2e8f0\",\n        \"tagFill\": \"#1e293b\",\n        \"tagText\": \"#e2e8f0\",\n        \"tagBorder\": \"#475569\",\n        \"inputBg\": \"#0b0e13\",\n        \"inputText\": \"#e2e8f0\",\n        \"inputBorder\": \"#1f2937\",\n        \"inputFont\": \"Inter, system-ui, sans-serif\",\n        \"inputFontSize\": 14,\n        \"toolbarBg\": \"#441215\",\n        \"toolbarBorder\": \"#1f2937\",\n        \"toolbarText\": \"#94a3b8\",\n        \"toolbarBtnBg\": \"#0b0e13\",\n        \"toolbarBtnText\": \"#e2e8f0\",\n        \"minimapDots\": \"#94a3b8\",\n        \"canvasHintEnabled\": true,\n        \"canvasHintText\": \"\",\n        \"canvasHintBg\": \"#0f172a\",\n        \"canvasHintColor\": \"#94a3b8\",\n        \"danger\": \"#f56565\",\n        \"textMain\": \"#e2e8f0\",\n        \"textSoft\": \"#94a3b8\",\n        \"topbarHeight\": 112,\n        \"sidebarWidth\": 350,\n        \"mobileFooterHeight\": 40,\n        \"sidebarCollapsed\": false,\n        \"nodeFill\": \"#1e293b\",\n        \"nodeStroke\": \"#475569\",\n        \"nodeTitle\": \"#e2e8f0\",\n        \"nodeSub\": \"#94a3b8\",\n        \"nodeTitleSize\": 18,\n        \"nodeSubSize\": 13,\n        \"nodeFont\": \"Inter, system-ui, sans-serif\",\n        \"defaultEdge\": \"#475569\",\n        \"selectionHandle\": \"#f59e0b\",\n        \"selectionHandleSize\": 8,\n        \"groupIndicator\": \"#4fd1c5\",\n        \"canvasGradientTop\": \"#1e2532\",\n        \"canvasGradientBottom\": \"#050608\",\n        \"canvasBorder\": \"#475569\",\n        \"canvasGrid\": \"#475569\",\n        \"canvasGridSize\": 50,\n        \"canvasGridEnabled\": true,\n        \"rackFrameFill\": \"#0f172a\",\n        \"rackFrameStroke\": \"#4fd1c5\",\n        \"rackLineColor\": \"#475569\",\n        \"rackTextColor\": \"#4fd1c5\",\n        \"rackGridEnabled\": true,\n        \"viewOnly\": false,\n        \"defaultEdgeRouting\": \"curved\",\n        \"animateConnections\": false,\n        \"animationStyle\": \"arrows\",\n        \"animationDirection\": \"all\",\n        \"animationSpeed\": 1.5\n      }\n    }\n  ],\n  \"currentTabIndex\": 1,\n  \"encryptedSections\": {},\n  \"auditLog\": [\n    {\n      \"timestamp\": 1766459504374,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file.html\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766459500911,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file.html\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766459497380,\n      \"type\": \"tab\",\n      \"description\": \"Switched to tab: Homelab 2\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766459491436,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file-corporate.html\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459483682,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file-corporate.html\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459477676,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file-corporate.html\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766457578277,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766457564726,\n      \"type\": \"tab\",\n      \"description\": \"Switched to tab: Corporate Site B\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766457564253,\n      \"type\": \"tab\",\n      \"description\": \"Switched to tab: Homelab 2\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766457560309,\n      \"type\": \"import\",\n      \"description\": \"Imported JSON: the-one-file-corporate.json (107 nodes, 65 connections)\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455847368,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455844534,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455844054,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455843762,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455843560,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455843371,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455843162,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455842852,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455842747,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455842601,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455842449,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455842348,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455842098,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455841678,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455841236,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455841053,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455840901,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455840650,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455839427,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455839234,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455839061,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455837247,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455837081,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455836893,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455836377,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455836198,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455835455,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455834630,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455831880,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455831676,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455831451,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455830817,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455830687,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455830176,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455830048,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455829944,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455829816,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455378795,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455378693,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455378459,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455378316,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455378180,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455378069,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455377956,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455377677,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455377558,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455377448,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455377318,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455377209,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453090534,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453090317,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453090213,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453090112,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453090009,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453089903,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453088895,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453088793,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453088689,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453088584,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453088480,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453088250,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453087236,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453086725,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453086485,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453086373,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453086142,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453086043,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453072857,\n      \"type\": \"text\",\n      \"description\": \"paste text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453070975,\n      \"type\": \"text\",\n      \"description\": \"paste text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453054439,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453053127,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453052450,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453052106,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453051948,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453051806,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453051334,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453050207,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453042725,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453042179,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453041797,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453041570,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453039703,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453039291,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453039168,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453039065,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453038481,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453038365,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453038237,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453038105,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453038001,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453037850,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453037745,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453037495,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453037378,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453037182,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453037078,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453036972,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453036860,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453036147,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453035945,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453035825,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453035720,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453035443,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453035337,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453035233,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453035127,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453035026,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453034917,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453031063,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453030955,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453030833,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453030732,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453030225,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453030104,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453029968,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453029796,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453029474,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453024797,\n      \"type\": \"text\",\n      \"description\": \"paste text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766451118553,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450929324,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450817210,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450257424,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450255024,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450254395,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450253241,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450251598,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450250392,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450248756,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450244072,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450242166,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450240998,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450236492,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450233672,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450232384,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450231012,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450230254,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450229302,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450228132,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446610211,\n      \"type\": \"text\",\n      \"description\": \"paste text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446604849,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446604702,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446604550,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446604404,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446604305,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446604204,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446604099,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446603952,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446603849,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446603702,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446603599,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446603452,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446603348,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446603202,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446603099,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446602953,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446602850,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446602702,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446602600,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446602453,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446602349,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446602204,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446602101,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446602000,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446601848,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446601702,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446601601,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446601452,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446601301,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446601154,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446601049,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446600948,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446600802,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446600550,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446598595,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446598461,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446598171,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446598017,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446597219,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446595278,\n      \"type\": \"text\",\n      \"description\": \"add text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445633355,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445632515,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445631735,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445630757,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445627846,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445625085,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445618645,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445617784,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445608998,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445608720,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445608540,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445608376,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445608204,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445608038,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445607852,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445607678,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445607506,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445607319,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445607154,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445604410,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445604244,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445604066,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445603900,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445603743,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445603563,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445603406,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445603226,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445603052,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445602880,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445602641,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445576567,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animation speed\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445570290,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animation speed\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445567192,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445566766,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445565520,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445398115,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445390895,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445385694,\n      \"type\": \"edit\",\n      \"description\": \"toggle fov animation\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445383241,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445382911,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445381695,\n      \"type\": \"edit\",\n      \"description\": \"edit node name\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445375383,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445374665,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445373273,\n      \"type\": \"node\",\n      \"description\": \"paste node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445372205,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438157980,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438157430,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animation speed\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438152691,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438151948,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438151286,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438146174,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438145649,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438144555,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438143655,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438142504,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animation speed\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438130077,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438129561,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438128772,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438128398,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438122820,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438122062,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438119836,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438119588,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438095045,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438093965,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438062827,\n      \"type\": \"edit\",\n      \"description\": \"toggle fov animation\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438047679,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438044161,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438041852,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438039668,\n      \"type\": \"edit\",\n      \"description\": \"resize node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438039562,\n      \"type\": \"edit\",\n      \"description\": \"resize node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438039421,\n      \"type\": \"edit\",\n      \"description\": \"resize node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438039260,\n      \"type\": \"edit\",\n      \"description\": \"resize node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438039150,\n      \"type\": \"edit\",\n      \"description\": \"resize node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438039039,\n      \"type\": \"edit\",\n      \"description\": \"resize node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438028508,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438021410,\n      \"type\": \"edit\",\n      \"description\": \"toggle fov\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438019234,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438017562,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438014356,\n      \"type\": \"node\",\n      \"description\": \"add node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437981696,\n      \"type\": \"edit\",\n      \"description\": \"apply routing to all\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437966551,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437964879,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437963627,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437961813,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437961193,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437957989,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437956467,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437953437,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437952239,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437950807,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437944990,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437943699,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437935414,\n      \"type\": \"zone\",\n      \"description\": \"draw zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437919019,\n      \"type\": \"zone\",\n      \"description\": \"delete zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437917758,\n      \"type\": \"zone\",\n      \"description\": \"draw zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437913740,\n      \"type\": \"zone\",\n      \"description\": \"draw zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437882832,\n      \"type\": \"import\",\n      \"description\": \"Imported JSON: theonefile-networkening-corporate-demo.json (105 nodes, 65 connections)\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766263279163,\n      \"type\": \"tab\",\n      \"description\": \"Switched to tab: Corporate Site B\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766263270414,\n      \"type\": \"export\",\n      \"description\": \"Exported JSON: the-one-file.json\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766263260682,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file.html\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766263259518,\n      \"type\": \"tab\",\n      \"description\": \"Switched to tab: Homelab 2\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766263249401,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file-corporate.html\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766263246362,\n      \"type\": \"import\",\n      \"description\": \"Imported JSON: theonefile-networkening-corporate-demo.json (105 nodes, 65 connections)\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766190721141,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file-corporate.html\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766190717499,\n      \"type\": \"tab\",\n      \"description\": \"Switched to tab: Corporate Site B\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766190710946,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file.html\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766190705273,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file.html\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766190703463,\n      \"type\": \"tab\",\n      \"description\": \"Switched to tab: Homelab 2\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766190695709,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file-corporate.html\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766190688417,\n      \"type\": \"import\",\n      \"description\": \"Imported JSON: theonefile-networkening-corporate-demo.json (105 nodes, 65 connections)\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1765402888416,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file-corporate.html\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1765402884873,\n      \"type\": \"tab\",\n      \"description\": \"Switched to tab: Corporate Site B\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1765402878108,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file.html\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1765402866440,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file.html\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1765402865008,\n      \"type\": \"tab\",\n      \"description\": \"Switched to tab: Homelab 2\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1765402860428,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file-corporate.html\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1765402858103,\n      \"type\": \"import\",\n      \"description\": \"Imported JSON: theonefile-networkening-corporate-demo.json (105 nodes, 65 connections)\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    }\n  ],\n  \"savedStyleSets\": []\n}"
  },
  {
    "path": "demos/markdown-exports/the-one-file-corporate.md",
    "content": "<!--THEONEFILE_CONFIG\n{\n  \"nodeData\": {\n    \"core-router-1\": {\n      \"shape\": \"router\",\n      \"name\": \"Core Router 1\",\n      \"ip\": \"10.0.0.1\",\n      \"role\": \"Core Routing\",\n      \"tags\": [\n        \"core\",\n        \"tier-1\",\n        \"redundant\"\n      ],\n      \"notes\": [\n        \"Primary core router\",\n        \"BGP peering enabled\"\n      ],\n      \"mac\": \"00:1A:2B:3C:4D:01\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"2\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"core-router-2\": {\n      \"shape\": \"router\",\n      \"name\": \"Core Router 2\",\n      \"ip\": \"10.0.0.2\",\n      \"role\": \"Core Routing\",\n      \"tags\": [\n        \"core\",\n        \"tier-1\",\n        \"redundant\"\n      ],\n      \"notes\": [\n        \"Secondary core router\",\n        \"HSRP standby\"\n      ],\n      \"mac\": \"00:1A:2B:3C:4D:02\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"2\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null,\n      \"ping\": {\n        \"enabled\": true,\n        \"protocol\": \"custom\",\n        \"customUrl\": \"https://google.com\",\n        \"timeout\": 3000,\n        \"status\": \"online\",\n        \"lastCheck\": \"2025-12-09T00:15:04.343Z\"\n      }\n    },\n    \"fw-external-1\": {\n      \"shape\": \"firewall\",\n      \"name\": \"External FW 1\",\n      \"ip\": \"10.0.1.1\",\n      \"role\": \"Perimeter Security\",\n      \"tags\": [\n        \"security\",\n        \"perimeter\",\n        \"ha-pair\"\n      ],\n      \"notes\": [\n        \"Palo Alto PA-5250\",\n        \"Active node\"\n      ],\n      \"mac\": \"00:1A:2B:3C:4D:10\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"2\",\n      \"layer\": \"security\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"fw-external-2\": {\n      \"shape\": \"firewall\",\n      \"name\": \"External FW 2\",\n      \"ip\": \"10.0.1.2\",\n      \"role\": \"Perimeter Security\",\n      \"tags\": [\n        \"security\",\n        \"perimeter\",\n        \"ha-pair\"\n      ],\n      \"notes\": [\n        \"Palo Alto PA-5250\",\n        \"Passive node\"\n      ],\n      \"mac\": \"00:1A:2B:3C:4D:11\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"2\",\n      \"layer\": \"security\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"fw-internal\": {\n      \"shape\": \"firewall\",\n      \"name\": \"Internal FW\",\n      \"ip\": \"10.0.2.1\",\n      \"role\": \"Internal Segmentation\",\n      \"tags\": [\n        \"security\",\n        \"internal\"\n      ],\n      \"notes\": [\n        \"East-West traffic inspection\"\n      ],\n      \"mac\": \"00:1A:2B:3C:4D:12\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"2\",\n      \"layer\": \"security\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"core-switch-1\": {\n      \"shape\": \"switch\",\n      \"name\": \"Core Switch 1\",\n      \"ip\": \"10.0.10.1\",\n      \"role\": \"Core Switching\",\n      \"tags\": [\n        \"core\",\n        \"layer3\",\n        \"redundant\"\n      ],\n      \"notes\": [\n        \"Cisco Nexus 9000\",\n        \"VPC Domain 1\"\n      ],\n      \"mac\": \"00:1A:2B:3C:4D:20\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"2\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"core-switch-2\": {\n      \"shape\": \"switch\",\n      \"name\": \"Core Switch 2\",\n      \"ip\": \"10.0.10.2\",\n      \"role\": \"Core Switching\",\n      \"tags\": [\n        \"core\",\n        \"layer3\",\n        \"redundant\"\n      ],\n      \"notes\": [\n        \"Cisco Nexus 9000\",\n        \"VPC Domain 1\"\n      ],\n      \"mac\": \"00:1A:2B:3C:4D:21\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"2\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"dc-rack-a1\": {\n      \"shape\": \"server\",\n      \"name\": \"DC Rack A1\",\n      \"ip\": \"10.10.0.0/24\",\n      \"role\": \"Data Center Rack\",\n      \"tags\": [\n        \"datacenter\",\n        \"row-a\",\n        \"production\"\n      ],\n      \"notes\": [\n        \"Row A, Position 1\",\n        \"Primary compute\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": true,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"dc-rack-a2\": {\n      \"shape\": \"server\",\n      \"name\": \"DC Rack A2\",\n      \"ip\": \"10.10.1.0/24\",\n      \"role\": \"Data Center Rack\",\n      \"tags\": [\n        \"datacenter\",\n        \"row-a\",\n        \"production\"\n      ],\n      \"notes\": [\n        \"Row A, Position 2\",\n        \"Primary compute\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": true,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"dc-rack-b1\": {\n      \"shape\": \"server\",\n      \"name\": \"DC Rack B1\",\n      \"ip\": \"10.10.2.0/24\",\n      \"role\": \"Data Center Rack\",\n      \"tags\": [\n        \"datacenter\",\n        \"row-b\",\n        \"storage\"\n      ],\n      \"notes\": [\n        \"Row B, Position 1\",\n        \"Storage systems\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": true,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"dc-rack-b2\": {\n      \"shape\": \"server\",\n      \"name\": \"DC Rack B2\",\n      \"ip\": \"10.10.3.0/24\",\n      \"role\": \"Data Center Rack\",\n      \"tags\": [\n        \"datacenter\",\n        \"row-b\",\n        \"storage\"\n      ],\n      \"notes\": [\n        \"Row B, Position 2\",\n        \"Storage systems\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": true,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"dmz-rack\": {\n      \"shape\": \"server\",\n      \"name\": \"DMZ Rack\",\n      \"ip\": \"172.16.0.0/24\",\n      \"role\": \"DMZ Infrastructure\",\n      \"tags\": [\n        \"dmz\",\n        \"security\",\n        \"public-facing\",\n        {\n          \"type\": \"icon\",\n          \"library\": \"selfhst\",\n          \"name\": \"booklogr\"\n        },\n        {\n          \"type\": \"icon\",\n          \"library\": \"simple\",\n          \"name\": \"gmail\"\n        }\n      ],\n      \"notes\": [\n        \"Isolated DMZ zone\",\n        \"Public-facing services\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"security\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"24\",\n      \"isRack\": true,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"mgmt-rack\": {\n      \"shape\": \"server\",\n      \"name\": \"Management Rack\",\n      \"ip\": \"192.168.100.0/24\",\n      \"role\": \"Management Infrastructure\",\n      \"tags\": [\n        \"management\",\n        \"oob\",\n        \"noc\"\n      ],\n      \"notes\": [\n        \"Out-of-band management\",\n        \"NOC equipment\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"logical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"24\",\n      \"isRack\": true,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"esxi-host-01\": {\n      \"shape\": \"server\",\n      \"name\": \"ESXi Host 01\",\n      \"ip\": \"10.10.0.11\",\n      \"role\": \"Hypervisor\",\n      \"tags\": [\n        \"vmware\",\n        \"compute\",\n        \"cluster-a\"\n      ],\n      \"notes\": [\n        \"Dell PowerEdge R750\",\n        \"512GB RAM\",\n        \"vSphere 8.0\"\n      ],\n      \"mac\": \"00:50:56:AA:01:01\",\n      \"rackUnit\": 38,\n      \"uHeight\": \"2\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a1\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"esxi-host-02\": {\n      \"shape\": \"server\",\n      \"name\": \"ESXi Host 02\",\n      \"ip\": \"10.10.0.12\",\n      \"role\": \"Hypervisor\",\n      \"tags\": [\n        \"vmware\",\n        \"compute\",\n        \"cluster-a\"\n      ],\n      \"notes\": [\n        \"Dell PowerEdge R750\",\n        \"512GB RAM\",\n        \"vSphere 8.0\"\n      ],\n      \"mac\": \"00:50:56:AA:01:02\",\n      \"rackUnit\": 35,\n      \"uHeight\": \"2\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a1\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"esxi-host-03\": {\n      \"shape\": \"server\",\n      \"name\": \"ESXi Host 03\",\n      \"ip\": \"10.10.0.13\",\n      \"role\": \"Hypervisor\",\n      \"tags\": [\n        \"vmware\",\n        \"compute\",\n        \"cluster-a\"\n      ],\n      \"notes\": [\n        \"Dell PowerEdge R750\",\n        \"512GB RAM\",\n        \"vSphere 8.0\"\n      ],\n      \"mac\": \"00:50:56:AA:01:03\",\n      \"rackUnit\": 32,\n      \"uHeight\": \"2\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a1\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"esxi-host-04\": {\n      \"shape\": \"server\",\n      \"name\": \"ESXi Host 04\",\n      \"ip\": \"10.10.0.14\",\n      \"role\": \"Hypervisor\",\n      \"tags\": [\n        \"vmware\",\n        \"compute\",\n        \"cluster-a\"\n      ],\n      \"notes\": [\n        \"Dell PowerEdge R750\",\n        \"512GB RAM\",\n        \"vSphere 8.0\"\n      ],\n      \"mac\": \"00:50:56:AA:01:04\",\n      \"rackUnit\": 29,\n      \"uHeight\": \"2\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a1\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"tor-switch-a1\": {\n      \"shape\": \"switch\",\n      \"name\": \"ToR Switch A1\",\n      \"ip\": \"10.10.0.1\",\n      \"role\": \"Top of Rack\",\n      \"tags\": [\n        \"tor\",\n        \"access\",\n        \"rack-a1\"\n      ],\n      \"notes\": [\n        \"Cisco Nexus 93180YC-FX\",\n        \"48x25G ports\"\n      ],\n      \"mac\": \"00:1A:2B:3C:5D:01\",\n      \"rackUnit\": 42,\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a1\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"esxi-host-05\": {\n      \"shape\": \"server\",\n      \"name\": \"ESXi Host 05\",\n      \"ip\": \"10.10.1.11\",\n      \"role\": \"Hypervisor\",\n      \"tags\": [\n        \"vmware\",\n        \"compute\",\n        \"cluster-b\"\n      ],\n      \"notes\": [\n        \"Dell PowerEdge R750\",\n        \"768GB RAM\",\n        \"vSphere 8.0\"\n      ],\n      \"mac\": \"00:50:56:AA:02:01\",\n      \"rackUnit\": 38,\n      \"uHeight\": \"2\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a2\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"esxi-host-06\": {\n      \"shape\": \"server\",\n      \"name\": \"ESXi Host 06\",\n      \"ip\": \"10.10.1.12\",\n      \"role\": \"Hypervisor\",\n      \"tags\": [\n        \"vmware\",\n        \"compute\",\n        \"cluster-b\"\n      ],\n      \"notes\": [\n        \"Dell PowerEdge R750\",\n        \"768GB RAM\",\n        \"vSphere 8.0\"\n      ],\n      \"mac\": \"00:50:56:AA:02:02\",\n      \"rackUnit\": 35,\n      \"uHeight\": \"2\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a2\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"esxi-host-07\": {\n      \"shape\": \"server\",\n      \"name\": \"ESXi Host 07\",\n      \"ip\": \"10.10.1.13\",\n      \"role\": \"Hypervisor\",\n      \"tags\": [\n        \"vmware\",\n        \"compute\",\n        \"cluster-b\"\n      ],\n      \"notes\": [\n        \"Dell PowerEdge R750\",\n        \"768GB RAM\",\n        \"vSphere 8.0\"\n      ],\n      \"mac\": \"00:50:56:AA:02:03\",\n      \"rackUnit\": 32,\n      \"uHeight\": \"2\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a2\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"esxi-host-08\": {\n      \"shape\": \"server\",\n      \"name\": \"ESXi Host 08\",\n      \"ip\": \"10.10.1.14\",\n      \"role\": \"Hypervisor\",\n      \"tags\": [\n        \"vmware\",\n        \"compute\",\n        \"cluster-b\"\n      ],\n      \"notes\": [\n        \"Dell PowerEdge R750\",\n        \"768GB RAM\",\n        \"vSphere 8.0\"\n      ],\n      \"mac\": \"00:50:56:AA:02:04\",\n      \"rackUnit\": 29,\n      \"uHeight\": \"2\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a2\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"tor-switch-a2\": {\n      \"shape\": \"switch\",\n      \"name\": \"ToR Switch A2\",\n      \"ip\": \"10.10.1.1\",\n      \"role\": \"Top of Rack\",\n      \"tags\": [\n        \"tor\",\n        \"access\",\n        \"rack-a2\"\n      ],\n      \"notes\": [\n        \"Cisco Nexus 93180YC-FX\",\n        \"48x25G ports\"\n      ],\n      \"mac\": \"00:1A:2B:3C:5D:02\",\n      \"rackUnit\": 42,\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a2\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"san-primary\": {\n      \"shape\": \"database\",\n      \"name\": \"SAN Primary\",\n      \"ip\": \"10.10.2.10\",\n      \"role\": \"Primary Storage\",\n      \"tags\": [\n        \"storage\",\n        \"san\",\n        \"netapp\"\n      ],\n      \"notes\": [\n        \"NetApp AFF A400\",\n        \"500TB Raw\",\n        \"FC 32Gb\"\n      ],\n      \"mac\": \"00:A0:98:AA:01:01\",\n      \"rackUnit\": 36,\n      \"uHeight\": \"6\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-b1\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"san-secondary\": {\n      \"shape\": \"database\",\n      \"name\": \"SAN Secondary\",\n      \"ip\": \"10.10.2.11\",\n      \"role\": \"Secondary Storage\",\n      \"tags\": [\n        \"storage\",\n        \"san\",\n        \"netapp\"\n      ],\n      \"notes\": [\n        \"NetApp AFF A400\",\n        \"500TB Raw\",\n        \"FC 32Gb\"\n      ],\n      \"mac\": \"00:A0:98:AA:01:02\",\n      \"rackUnit\": 28,\n      \"uHeight\": \"6\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-b1\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"fc-switch-1\": {\n      \"shape\": \"switch\",\n      \"name\": \"FC Switch 1\",\n      \"ip\": \"10.10.2.1\",\n      \"role\": \"Fibre Channel\",\n      \"tags\": [\n        \"storage\",\n        \"fc\",\n        \"fabric-a\"\n      ],\n      \"notes\": [\n        \"Brocade G620\",\n        \"Fabric A\"\n      ],\n      \"mac\": \"00:1A:2B:FC:01:01\",\n      \"rackUnit\": 42,\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-b1\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"fc-switch-2\": {\n      \"shape\": \"switch\",\n      \"name\": \"FC Switch 2\",\n      \"ip\": \"10.10.2.2\",\n      \"role\": \"Fibre Channel\",\n      \"tags\": [\n        \"storage\",\n        \"fc\",\n        \"fabric-b\"\n      ],\n      \"notes\": [\n        \"Brocade G620\",\n        \"Fabric B\"\n      ],\n      \"mac\": \"00:1A:2B:FC:01:02\",\n      \"rackUnit\": 41,\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-b1\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"backup-server-1\": {\n      \"shape\": \"server\",\n      \"name\": \"Backup Server 1\",\n      \"ip\": \"10.10.3.10\",\n      \"role\": \"Backup Infrastructure\",\n      \"tags\": [\n        \"backup\",\n        \"veeam\",\n        \"protection\"\n      ],\n      \"notes\": [\n        \"Veeam Backup Server\",\n        \"Dell R740xd\",\n        \"200TB\"\n      ],\n      \"mac\": \"00:50:56:BB:01:01\",\n      \"rackUnit\": 36,\n      \"uHeight\": \"2\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-b2\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"backup-server-2\": {\n      \"shape\": \"server\",\n      \"name\": \"Backup Server 2\",\n      \"ip\": \"10.10.3.11\",\n      \"role\": \"Backup Infrastructure\",\n      \"tags\": [\n        \"backup\",\n        \"veeam\",\n        \"protection\"\n      ],\n      \"notes\": [\n        \"Veeam Backup Server\",\n        \"Dell R740xd\",\n        \"200TB\"\n      ],\n      \"mac\": \"00:50:56:BB:01:02\",\n      \"rackUnit\": 33,\n      \"uHeight\": \"2\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-b2\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"tape-library\": {\n      \"shape\": \"database\",\n      \"name\": \"Tape Library\",\n      \"ip\": \"10.10.3.20\",\n      \"role\": \"Archival Storage\",\n      \"tags\": [\n        \"backup\",\n        \"tape\",\n        \"lto9\"\n      ],\n      \"notes\": [\n        \"IBM TS4500\",\n        \"LTO-9\",\n        \"Long-term archive\"\n      ],\n      \"mac\": \"00:50:56:BB:02:01\",\n      \"rackUnit\": 20,\n      \"uHeight\": \"10\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-b2\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"tor-switch-b1\": {\n      \"shape\": \"switch\",\n      \"name\": \"ToR Switch B1\",\n      \"ip\": \"10.10.2.3\",\n      \"role\": \"Top of Rack\",\n      \"tags\": [\n        \"tor\",\n        \"access\",\n        \"rack-b1\"\n      ],\n      \"notes\": [\n        \"Cisco Nexus 93180YC-FX\"\n      ],\n      \"mac\": \"00:1A:2B:3C:5D:03\",\n      \"rackUnit\": 40,\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-b1\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"tor-switch-b2\": {\n      \"shape\": \"switch\",\n      \"name\": \"ToR Switch B2\",\n      \"ip\": \"10.10.3.1\",\n      \"role\": \"Top of Rack\",\n      \"tags\": [\n        \"tor\",\n        \"access\",\n        \"rack-b2\"\n      ],\n      \"notes\": [\n        \"Cisco Nexus 93180YC-FX\"\n      ],\n      \"mac\": \"00:1A:2B:3C:5D:04\",\n      \"rackUnit\": 42,\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-b2\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"web-server-1\": {\n      \"shape\": \"server\",\n      \"name\": \"Web Server 1\",\n      \"ip\": \"172.16.0.11\",\n      \"role\": \"Web Frontend\",\n      \"tags\": [\n        \"dmz\",\n        \"web\",\n        \"nginx\"\n      ],\n      \"notes\": [\n        \"NGINX reverse proxy\",\n        \"Public facing\"\n      ],\n      \"mac\": \"00:50:56:CC:01:01\",\n      \"rackUnit\": 20,\n      \"uHeight\": \"1\",\n      \"layer\": \"security\",\n      \"assignedRack\": \"dmz-rack\",\n      \"rackCapacity\": \"24\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"web-server-2\": {\n      \"shape\": \"server\",\n      \"name\": \"Web Server 2\",\n      \"ip\": \"172.16.0.12\",\n      \"role\": \"Web Frontend\",\n      \"tags\": [\n        \"dmz\",\n        \"web\",\n        \"nginx\"\n      ],\n      \"notes\": [\n        \"NGINX reverse proxy\",\n        \"Public facing\"\n      ],\n      \"mac\": \"00:50:56:CC:01:02\",\n      \"rackUnit\": 18,\n      \"uHeight\": \"1\",\n      \"layer\": \"security\",\n      \"assignedRack\": \"dmz-rack\",\n      \"rackCapacity\": \"24\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"waf-1\": {\n      \"shape\": \"firewall\",\n      \"name\": \"WAF Appliance\",\n      \"ip\": \"172.16.0.5\",\n      \"role\": \"Web Application Firewall\",\n      \"tags\": [\n        \"dmz\",\n        \"security\",\n        \"waf\"\n      ],\n      \"notes\": [\n        \"F5 BIG-IP ASM\",\n        \"OWASP protection\"\n      ],\n      \"mac\": \"00:50:56:CC:02:01\",\n      \"rackUnit\": 22,\n      \"uHeight\": \"2\",\n      \"layer\": \"security\",\n      \"assignedRack\": \"dmz-rack\",\n      \"rackCapacity\": \"24\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"load-balancer-dmz\": {\n      \"shape\": \"switch\",\n      \"name\": \"DMZ Load Balancer\",\n      \"ip\": \"172.16.0.3\",\n      \"role\": \"Load Balancing\",\n      \"tags\": [\n        \"dmz\",\n        \"lb\",\n        \"f5\"\n      ],\n      \"notes\": [\n        \"F5 BIG-IP LTM\",\n        \"VIP: 172.16.0.100\"\n      ],\n      \"mac\": \"00:50:56:CC:03:01\",\n      \"rackUnit\": 16,\n      \"uHeight\": \"2\",\n      \"layer\": \"security\",\n      \"assignedRack\": \"dmz-rack\",\n      \"rackCapacity\": \"24\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"mail-gateway\": {\n      \"shape\": \"server\",\n      \"name\": \"Mail Gateway\",\n      \"ip\": \"172.16.0.25\",\n      \"role\": \"Email Security\",\n      \"tags\": [\n        \"dmz\",\n        \"email\",\n        \"security\"\n      ],\n      \"notes\": [\n        \"Proofpoint Email Gateway\",\n        \"Spam/malware filtering\"\n      ],\n      \"mac\": \"00:50:56:CC:04:01\",\n      \"rackUnit\": 14,\n      \"uHeight\": \"1\",\n      \"layer\": \"security\",\n      \"assignedRack\": \"dmz-rack\",\n      \"rackCapacity\": \"24\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"dns-external-1\": {\n      \"shape\": \"circle\",\n      \"name\": \"External DNS 1\",\n      \"ip\": \"172.16.0.53\",\n      \"role\": \"External DNS\",\n      \"tags\": [\n        \"dmz\",\n        \"dns\",\n        \"public\"\n      ],\n      \"notes\": [\n        \"BIND DNS\",\n        \"Authoritative for corp.com\"\n      ],\n      \"mac\": \"00:50:56:CC:05:01\",\n      \"rackUnit\": 12,\n      \"uHeight\": \"1\",\n      \"layer\": \"security\",\n      \"assignedRack\": \"dmz-rack\",\n      \"rackCapacity\": \"24\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"dns-external-2\": {\n      \"shape\": \"circle\",\n      \"name\": \"External DNS 2\",\n      \"ip\": \"172.16.0.54\",\n      \"role\": \"External DNS\",\n      \"tags\": [\n        \"dmz\",\n        \"dns\",\n        \"public\"\n      ],\n      \"notes\": [\n        \"BIND DNS\",\n        \"Secondary for corp.com\"\n      ],\n      \"mac\": \"00:50:56:CC:05:02\",\n      \"rackUnit\": 10,\n      \"uHeight\": \"1\",\n      \"layer\": \"security\",\n      \"assignedRack\": \"dmz-rack\",\n      \"rackCapacity\": \"24\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"vcenter\": {\n      \"shape\": \"server\",\n      \"name\": \"vCenter Server\",\n      \"ip\": \"192.168.100.10\",\n      \"role\": \"Virtualization Management\",\n      \"tags\": [\n        \"management\",\n        \"vmware\",\n        \"vcsa\"\n      ],\n      \"notes\": [\n        \"vCenter Server Appliance 8.0\",\n        \"Single SSO domain\"\n      ],\n      \"mac\": \"00:50:56:DD:01:01\",\n      \"rackUnit\": 20,\n      \"uHeight\": \"2\",\n      \"layer\": \"logical\",\n      \"assignedRack\": \"mgmt-rack\",\n      \"rackCapacity\": \"24\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"nsx-manager\": {\n      \"shape\": \"server\",\n      \"name\": \"NSX Manager\",\n      \"ip\": \"192.168.100.15\",\n      \"role\": \"Network Virtualization\",\n      \"tags\": [\n        \"management\",\n        \"vmware\",\n        \"nsx\"\n      ],\n      \"notes\": [\n        \"NSX-T 4.1 Manager Cluster\"\n      ],\n      \"mac\": \"00:50:56:DD:02:01\",\n      \"rackUnit\": 17,\n      \"uHeight\": \"2\",\n      \"layer\": \"logical\",\n      \"assignedRack\": \"mgmt-rack\",\n      \"rackCapacity\": \"24\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"siem-server\": {\n      \"shape\": \"server\",\n      \"name\": \"SIEM Server\",\n      \"ip\": \"192.168.100.50\",\n      \"role\": \"Security Monitoring\",\n      \"tags\": [\n        \"management\",\n        \"security\",\n        \"splunk\"\n      ],\n      \"notes\": [\n        \"Splunk Enterprise\",\n        \"Security monitoring\"\n      ],\n      \"mac\": \"00:50:56:DD:03:01\",\n      \"rackUnit\": 14,\n      \"uHeight\": \"2\",\n      \"layer\": \"logical\",\n      \"assignedRack\": \"mgmt-rack\",\n      \"rackCapacity\": \"24\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"nms-server\": {\n      \"shape\": \"server\",\n      \"name\": \"Network Monitoring\",\n      \"ip\": \"192.168.100.60\",\n      \"role\": \"Network Management\",\n      \"tags\": [\n        \"management\",\n        \"monitoring\",\n        \"prtg\"\n      ],\n      \"notes\": [\n        \"PRTG Network Monitor\",\n        \"5000 sensors\"\n      ],\n      \"mac\": \"00:50:56:DD:04:01\",\n      \"rackUnit\": 11,\n      \"uHeight\": \"1\",\n      \"layer\": \"logical\",\n      \"assignedRack\": \"mgmt-rack\",\n      \"rackCapacity\": \"24\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"jump-server\": {\n      \"shape\": \"server\",\n      \"name\": \"Jump Server\",\n      \"ip\": \"192.168.100.100\",\n      \"role\": \"Bastion Host\",\n      \"tags\": [\n        \"management\",\n        \"security\",\n        \"bastion\"\n      ],\n      \"notes\": [\n        \"Windows Server 2022\",\n        \"MFA enabled\"\n      ],\n      \"mac\": \"00:50:56:DD:05:01\",\n      \"rackUnit\": 9,\n      \"uHeight\": \"1\",\n      \"layer\": \"logical\",\n      \"assignedRack\": \"mgmt-rack\",\n      \"rackCapacity\": \"24\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"ipam-server\": {\n      \"shape\": \"server\",\n      \"name\": \"IPAM/DDI\",\n      \"ip\": \"192.168.100.70\",\n      \"role\": \"IP Management\",\n      \"tags\": [\n        \"management\",\n        \"dns\",\n        \"dhcp\"\n      ],\n      \"notes\": [\n        \"Infoblox DDI\",\n        \"DNS/DHCP/IPAM\"\n      ],\n      \"mac\": \"00:50:56:DD:06:01\",\n      \"rackUnit\": 7,\n      \"uHeight\": \"2\",\n      \"layer\": \"logical\",\n      \"assignedRack\": \"mgmt-rack\",\n      \"rackCapacity\": \"24\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"wlc-primary\": {\n      \"shape\": \"wifi\",\n      \"name\": \"WLC Primary\",\n      \"ip\": \"10.20.0.1\",\n      \"role\": \"Wireless Controller\",\n      \"tags\": [\n        \"wireless\",\n        \"cisco\",\n        \"9800\"\n      ],\n      \"notes\": [\n        \"Cisco C9800-40\",\n        \"Primary controller\"\n      ],\n      \"mac\": \"00:1A:2B:WL:01:01\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"2\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"wlc-secondary\": {\n      \"shape\": \"wifi\",\n      \"name\": \"WLC Secondary\",\n      \"ip\": \"10.20.0.2\",\n      \"role\": \"Wireless Controller\",\n      \"tags\": [\n        \"wireless\",\n        \"cisco\",\n        \"9800\"\n      ],\n      \"notes\": [\n        \"Cisco C9800-40\",\n        \"HA Secondary\"\n      ],\n      \"mac\": \"00:1A:2B:WL:01:02\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"2\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"mobile-zone-hq\": {\n      \"shape\": \"phone\",\n      \"name\": \"HQ Mobile Zone\",\n      \"ip\": \"10.20.10.0/24\",\n      \"role\": \"Mobile Device Zone\",\n      \"tags\": [\n        \"wireless\",\n        \"byod\",\n        \"mobile\"\n      ],\n      \"notes\": [\n        \"Corporate BYOD\",\n        \"MDM enrolled devices\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"mobile-zone-guest\": {\n      \"shape\": \"phone\",\n      \"name\": \"Guest WiFi Zone\",\n      \"ip\": \"10.30.0.0/24\",\n      \"role\": \"Guest Network\",\n      \"tags\": [\n        \"wireless\",\n        \"guest\",\n        \"isolated\"\n      ],\n      \"notes\": [\n        \"Captive portal\",\n        \"Internet only\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"mobile-zone-iot\": {\n      \"shape\": \"phone\",\n      \"name\": \"IoT Device Zone\",\n      \"ip\": \"10.40.0.0/24\",\n      \"role\": \"IoT Network\",\n      \"tags\": [\n        \"wireless\",\n        \"iot\",\n        \"building\"\n      ],\n      \"notes\": [\n        \"Building automation\",\n        \"Smart devices\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"branch-router-ny\": {\n      \"shape\": \"router\",\n      \"name\": \"NYC Branch Router\",\n      \"ip\": \"10.100.0.1\",\n      \"role\": \"Branch Gateway\",\n      \"tags\": [\n        \"branch\",\n        \"nyc\",\n        \"sd-wan\"\n      ],\n      \"notes\": [\n        \"Cisco Viptela vEdge\",\n        \"SD-WAN enabled\"\n      ],\n      \"mac\": \"00:1A:2B:BR:01:01\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"branch-router-la\": {\n      \"shape\": \"router\",\n      \"name\": \"LA Branch Router\",\n      \"ip\": \"10.101.0.1\",\n      \"role\": \"Branch Gateway\",\n      \"tags\": [\n        \"branch\",\n        \"la\",\n        \"sd-wan\"\n      ],\n      \"notes\": [\n        \"Cisco Viptela vEdge\",\n        \"SD-WAN enabled\"\n      ],\n      \"mac\": \"00:1A:2B:BR:02:01\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"branch-router-chi\": {\n      \"shape\": \"router\",\n      \"name\": \"Chicago Branch Router\",\n      \"ip\": \"10.102.0.1\",\n      \"role\": \"Branch Gateway\",\n      \"tags\": [\n        \"branch\",\n        \"chicago\",\n        \"sd-wan\"\n      ],\n      \"notes\": [\n        \"Cisco Viptela vEdge\",\n        \"SD-WAN enabled\"\n      ],\n      \"mac\": \"00:1A:2B:BR:03:01\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"branch-router-lon\": {\n      \"shape\": \"router\",\n      \"name\": \"London Branch Router\",\n      \"ip\": \"10.200.0.1\",\n      \"role\": \"Branch Gateway\",\n      \"tags\": [\n        \"branch\",\n        \"london\",\n        \"sd-wan\"\n      ],\n      \"notes\": [\n        \"Cisco Viptela vEdge\",\n        \"EMEA region\"\n      ],\n      \"mac\": \"00:1A:2B:BR:04:01\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"branch-router-tokyo\": {\n      \"shape\": \"router\",\n      \"name\": \"Tokyo Branch Router\",\n      \"ip\": \"10.201.0.1\",\n      \"role\": \"Branch Gateway\",\n      \"tags\": [\n        \"branch\",\n        \"tokyo\",\n        \"sd-wan\"\n      ],\n      \"notes\": [\n        \"Cisco Viptela vEdge\",\n        \"APAC region\"\n      ],\n      \"mac\": \"00:1A:2B:BR:05:01\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"cloud-aws\": {\n      \"shape\": \"cloud\",\n      \"name\": \"AWS Cloud\",\n      \"ip\": \"vpc-0a1b2c3d\",\n      \"role\": \"Public Cloud\",\n      \"tags\": [\n        \"cloud\",\n        \"aws\",\n        \"hybrid\"\n      ],\n      \"notes\": [\n        \"AWS US-East-1\",\n        \"VPC peering to HQ\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"logical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"cloud-azure\": {\n      \"shape\": \"cloud\",\n      \"name\": \"Azure Cloud\",\n      \"ip\": \"vnet-corp-prod\",\n      \"role\": \"Public Cloud\",\n      \"tags\": [\n        \"cloud\",\n        \"azure\",\n        \"hybrid\"\n      ],\n      \"notes\": [\n        \"Azure East US 2\",\n        \"ExpressRoute\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"logical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"cloud-gcp\": {\n      \"shape\": \"cloud\",\n      \"name\": \"GCP Cloud\",\n      \"ip\": \"vpc-gcp-corp\",\n      \"role\": \"Public Cloud\",\n      \"tags\": [\n        \"cloud\",\n        \"gcp\",\n        \"dev\"\n      ],\n      \"notes\": [\n        \"GCP us-central1\",\n        \"Dev/Test workloads\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"logical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"isp-primary\": {\n      \"shape\": \"globe\",\n      \"name\": \"ISP Primary\",\n      \"ip\": \"203.0.113.1\",\n      \"role\": \"Internet Uplink\",\n      \"tags\": [\n        \"wan\",\n        \"internet\",\n        \"primary\"\n      ],\n      \"notes\": [\n        \"AT&T MPLS\",\n        \"1 Gbps dedicated\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"isp-secondary\": {\n      \"shape\": \"vm\",\n      \"name\": \"ISP Secondary\",\n      \"ip\": \"198.51.100.1\",\n      \"role\": \"Internet Uplink\",\n      \"tags\": [\n        \"wan\",\n        \"internet\",\n        \"backup\"\n      ],\n      \"notes\": [\n        \"Verizon Business\",\n        \"500 Mbps backup\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null,\n      \"rotation\": -17\n    },\n    \"dc-internal-1\": {\n      \"shape\": \"circle\",\n      \"name\": \"DC1 Int DNS\",\n      \"ip\": \"10.10.0.53\",\n      \"role\": \"Internal DNS/AD\",\n      \"tags\": [\n        \"dns\",\n        \"ad\",\n        \"dc1\"\n      ],\n      \"notes\": [\n        \"Windows Server 2022\",\n        \"Primary DC\"\n      ],\n      \"mac\": \"00:50:56:AD:01:01\",\n      \"rackUnit\": 26,\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a1\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"dc-internal-2\": {\n      \"shape\": \"circle\",\n      \"name\": \"DC2 Int DNS\",\n      \"ip\": \"10.10.1.53\",\n      \"role\": \"Internal DNS/AD\",\n      \"tags\": [\n        \"dns\",\n        \"ad\",\n        \"dc2\"\n      ],\n      \"notes\": [\n        \"Windows Server 2022\",\n        \"Secondary DC\"\n      ],\n      \"mac\": \"00:50:56:AD:01:02\",\n      \"rackUnit\": 26,\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a2\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"app-server-1\": {\n      \"shape\": \"server\",\n      \"name\": \"App Server 01\",\n      \"ip\": \"10.10.0.101\",\n      \"role\": \"Application\",\n      \"tags\": [\n        \"app\",\n        \"iis\",\n        \"web\"\n      ],\n      \"notes\": [\n        \"Windows Server 2022\",\n        \"IIS Application\"\n      ],\n      \"mac\": \"00:50:56:AP:01:01\",\n      \"rackUnit\": 24,\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a1\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"app-server-2\": {\n      \"shape\": \"server\",\n      \"name\": \"App Server 02\",\n      \"ip\": \"10.10.0.102\",\n      \"role\": \"Application\",\n      \"tags\": [\n        \"app\",\n        \"iis\",\n        \"web\"\n      ],\n      \"notes\": [\n        \"Windows Server 2022\",\n        \"IIS Application\"\n      ],\n      \"mac\": \"00:50:56:AP:01:02\",\n      \"rackUnit\": 22,\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a1\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"db-server-1\": {\n      \"shape\": \"database\",\n      \"name\": \"SQL Server 01\",\n      \"ip\": \"10.10.0.201\",\n      \"role\": \"Database\",\n      \"tags\": [\n        \"db\",\n        \"sql\",\n        \"primary\"\n      ],\n      \"notes\": [\n        \"SQL Server 2022 Enterprise\",\n        \"AlwaysOn Primary\"\n      ],\n      \"mac\": \"00:50:56:DB:01:01\",\n      \"rackUnit\": 20,\n      \"uHeight\": \"2\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a1\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"db-server-2\": {\n      \"shape\": \"database\",\n      \"name\": \"SQL Server 02\",\n      \"ip\": \"10.10.1.201\",\n      \"role\": \"Database\",\n      \"tags\": [\n        \"db\",\n        \"sql\",\n        \"secondary\"\n      ],\n      \"notes\": [\n        \"SQL Server 2022 Enterprise\",\n        \"AlwaysOn Secondary\"\n      ],\n      \"mac\": \"00:50:56:DB:01:02\",\n      \"rackUnit\": 24,\n      \"uHeight\": \"2\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a2\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"k8s-master-1\": {\n      \"shape\": \"hexagon\",\n      \"name\": \"K8s Master 1\",\n      \"ip\": \"10.10.1.50\",\n      \"role\": \"Container Orchestration\",\n      \"tags\": [\n        \"kubernetes\",\n        \"master\",\n        \"container\"\n      ],\n      \"notes\": [\n        \"K8s Control Plane\",\n        \"etcd member\"\n      ],\n      \"mac\": \"00:50:56:K8:01:01\",\n      \"rackUnit\": 21,\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a2\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"k8s-master-2\": {\n      \"shape\": \"hexagon\",\n      \"name\": \"K8s Master 2\",\n      \"ip\": \"10.10.1.51\",\n      \"role\": \"Container Orchestration\",\n      \"tags\": [\n        \"kubernetes\",\n        \"master\",\n        \"container\"\n      ],\n      \"notes\": [\n        \"K8s Control Plane\",\n        \"etcd member\"\n      ],\n      \"mac\": \"00:50:56:K8:01:02\",\n      \"rackUnit\": 19,\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a2\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"k8s-master-3\": {\n      \"shape\": \"hexagon\",\n      \"name\": \"K8s Master 3\",\n      \"ip\": \"10.10.1.52\",\n      \"role\": \"Container Orchestration\",\n      \"tags\": [\n        \"kubernetes\",\n        \"master\",\n        \"container\"\n      ],\n      \"notes\": [\n        \"K8s Control Plane\",\n        \"etcd member\"\n      ],\n      \"mac\": \"00:50:56:K8:01:03\",\n      \"rackUnit\": 17,\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a2\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"k8s-worker-1\": {\n      \"shape\": \"server\",\n      \"name\": \"K8s Worker 1\",\n      \"ip\": \"10.10.1.60\",\n      \"role\": \"Container Workload\",\n      \"tags\": [\n        \"kubernetes\",\n        \"worker\",\n        \"container\"\n      ],\n      \"notes\": [\n        \"K8s Worker Node\",\n        \"64GB RAM\"\n      ],\n      \"mac\": \"00:50:56:K8:02:01\",\n      \"rackUnit\": 15,\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a2\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"k8s-worker-2\": {\n      \"shape\": \"server\",\n      \"name\": \"K8s Worker 2\",\n      \"ip\": \"10.10.1.61\",\n      \"role\": \"Container Workload\",\n      \"tags\": [\n        \"kubernetes\",\n        \"worker\",\n        \"container\"\n      ],\n      \"notes\": [\n        \"K8s Worker Node\",\n        \"64GB RAM\"\n      ],\n      \"mac\": \"00:50:56:K8:02:02\",\n      \"rackUnit\": 13,\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a2\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"k8s-worker-3\": {\n      \"shape\": \"server\",\n      \"name\": \"K8s Worker 3\",\n      \"ip\": \"10.10.1.62\",\n      \"role\": \"Container Workload\",\n      \"tags\": [\n        \"kubernetes\",\n        \"worker\",\n        \"container\"\n      ],\n      \"notes\": [\n        \"K8s Worker Node\",\n        \"64GB RAM\"\n      ],\n      \"mac\": \"00:50:56:K8:02:03\",\n      \"rackUnit\": 11,\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a2\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"k8s-worker-4\": {\n      \"shape\": \"server\",\n      \"name\": \"K8s Worker 4\",\n      \"ip\": \"10.10.1.63\",\n      \"role\": \"Container Workload\",\n      \"tags\": [\n        \"kubernetes\",\n        \"worker\",\n        \"container\"\n      ],\n      \"notes\": [\n        \"K8s Worker Node\",\n        \"64GB RAM\"\n      ],\n      \"mac\": \"00:50:56:K8:02:04\",\n      \"rackUnit\": 9,\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a2\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"proxy-server-1\": {\n      \"shape\": \"server\",\n      \"name\": \"Proxy Server 1\",\n      \"ip\": \"10.5.0.10\",\n      \"role\": \"Web Proxy\",\n      \"tags\": [\n        \"proxy\",\n        \"squid\",\n        \"filtering\"\n      ],\n      \"notes\": [\n        \"Squid Proxy\",\n        \"Content filtering\"\n      ],\n      \"mac\": \"00:50:56:PX:01:01\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"security\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"proxy-server-2\": {\n      \"shape\": \"server\",\n      \"name\": \"Proxy Server 2\",\n      \"ip\": \"10.5.0.11\",\n      \"role\": \"Web Proxy\",\n      \"tags\": [\n        \"proxy\",\n        \"squid\",\n        \"filtering\"\n      ],\n      \"notes\": [\n        \"Squid Proxy\",\n        \"HA pair\"\n      ],\n      \"mac\": \"00:50:56:PX:01:02\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"security\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"vpn-concentrator\": {\n      \"shape\": \"firewall\",\n      \"name\": \"VPN Concentrator\",\n      \"ip\": \"10.0.5.1\",\n      \"role\": \"Remote Access VPN\",\n      \"tags\": [\n        \"vpn\",\n        \"remote\",\n        \"security\"\n      ],\n      \"notes\": [\n        \"Cisco ASA 5555-X\",\n        \"AnyConnect SSL VPN\"\n      ],\n      \"mac\": \"00:1A:2B:VP:01:01\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"2\",\n      \"layer\": \"security\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"nac-server\": {\n      \"shape\": \"server\",\n      \"name\": \"NAC Server\",\n      \"ip\": \"10.5.5.10\",\n      \"role\": \"Network Access Control\",\n      \"tags\": [\n        \"nac\",\n        \"ise\",\n        \"802.1x\"\n      ],\n      \"notes\": [\n        \"Cisco ISE 3.1\",\n        \"RADIUS/TACACS+\"\n      ],\n      \"mac\": \"00:50:56:NA:01:01\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"2\",\n      \"layer\": \"security\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"print-server\": {\n      \"shape\": \"server\",\n      \"name\": \"Print Server\",\n      \"ip\": \"10.10.0.150\",\n      \"role\": \"Print Services\",\n      \"tags\": [\n        \"print\",\n        \"windows\",\n        \"services\"\n      ],\n      \"notes\": [\n        \"Windows Print Server\",\n        \"50+ printers\"\n      ],\n      \"mac\": \"00:50:56:PR:01:01\",\n      \"rackUnit\": 18,\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a1\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"file-server\": {\n      \"shape\": \"database\",\n      \"name\": \"File Server\",\n      \"ip\": \"10.10.0.160\",\n      \"role\": \"File Services\",\n      \"tags\": [\n        \"file\",\n        \"smb\",\n        \"dfs\"\n      ],\n      \"notes\": [\n        \"Windows File Server\",\n        \"DFS namespace\"\n      ],\n      \"mac\": \"00:50:56:FS:01:01\",\n      \"rackUnit\": 16,\n      \"uHeight\": \"2\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a1\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"ca-server\": {\n      \"shape\": \"server\",\n      \"name\": \"Certificate Authority\",\n      \"ip\": \"192.168.100.80\",\n      \"role\": \"PKI Infrastructure\",\n      \"tags\": [\n        \"pki\",\n        \"ca\",\n        \"security\"\n      ],\n      \"notes\": [\n        \"Windows CA\",\n        \"Enterprise Root CA\"\n      ],\n      \"mac\": \"00:50:56:CA:01:01\",\n      \"rackUnit\": 5,\n      \"uHeight\": \"1\",\n      \"layer\": \"logical\",\n      \"assignedRack\": \"mgmt-rack\",\n      \"rackCapacity\": \"24\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"sccm-server\": {\n      \"shape\": \"server\",\n      \"name\": \"SCCM Server\",\n      \"ip\": \"192.168.100.90\",\n      \"role\": \"Endpoint Management\",\n      \"tags\": [\n        \"sccm\",\n        \"patching\",\n        \"software\"\n      ],\n      \"notes\": [\n        \"MECM Primary Site\",\n        \"Software deployment\"\n      ],\n      \"mac\": \"00:50:56:SC:01:01\",\n      \"rackUnit\": 3,\n      \"uHeight\": \"2\",\n      \"layer\": \"logical\",\n      \"assignedRack\": \"mgmt-rack\",\n      \"rackCapacity\": \"24\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"voip-cluster\": {\n      \"shape\": \"phone\",\n      \"name\": \"VoIP Cluster\",\n      \"ip\": \"10.50.0.0/24\",\n      \"role\": \"Voice Services\",\n      \"tags\": [\n        \"voip\",\n        \"cisco\",\n        \"ucm\"\n      ],\n      \"notes\": [\n        \"Cisco UCM Cluster\",\n        \"3000 endpoints\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"application\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"video-conf\": {\n      \"shape\": \"laptop\",\n      \"name\": \"Video Conference\",\n      \"ip\": \"10.51.0.0/24\",\n      \"role\": \"Video Services\",\n      \"tags\": [\n        \"video\",\n        \"webex\",\n        \"teams\"\n      ],\n      \"notes\": [\n        \"Webex/Teams integration\",\n        \"Meeting rooms\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"application\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"security-cameras\": {\n      \"shape\": \"camera\",\n      \"name\": \"Security Cameras\",\n      \"ip\": \"10.60.0.0/24\",\n      \"role\": \"Physical Security\",\n      \"tags\": [\n        \"cctv\",\n        \"surveillance\",\n        \"security\"\n      ],\n      \"notes\": [\n        \"150+ IP cameras\",\n        \"30-day retention\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"nvr-cluster\": {\n      \"shape\": \"server\",\n      \"name\": \"NVR Cluster\",\n      \"ip\": \"10.60.0.10\",\n      \"role\": \"Video Recording\",\n      \"tags\": [\n        \"nvr\",\n        \"surveillance\",\n        \"storage\"\n      ],\n      \"notes\": [\n        \"Milestone XProtect\",\n        \"500TB storage\"\n      ],\n      \"mac\": \"00:50:56:NV:01:01\",\n      \"rackUnit\": 15,\n      \"uHeight\": \"4\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-b2\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"dev-server-1\": {\n      \"shape\": \"server\",\n      \"name\": \"Dev Server 1\",\n      \"ip\": \"10.80.0.10\",\n      \"role\": \"Development\",\n      \"tags\": [\n        \"dev\",\n        \"gitlab\",\n        \"ci-cd\",\n        {\n          \"type\": \"icon\",\n          \"library\": \"selfhst\",\n          \"name\": \"dokku\"\n        }\n      ],\n      \"notes\": [\n        \"GitLab Server\",\n        \"CI/CD pipelines\"\n      ],\n      \"mac\": \"00:50:56:DV:01:01\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"2\",\n      \"layer\": \"application\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"dev-server-2\": {\n      \"shape\": \"server\",\n      \"name\": \"Dev Server 2\",\n      \"ip\": \"10.80.0.11\",\n      \"role\": \"Development\",\n      \"tags\": [\n        \"dev\",\n        \"jenkins\",\n        \"ci-cd\"\n      ],\n      \"notes\": [\n        \"Jenkins Server\",\n        \"Build automation\"\n      ],\n      \"mac\": \"00:50:56:DV:01:02\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"2\",\n      \"layer\": \"application\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"test-environment\": {\n      \"shape\": \"shield\",\n      \"name\": \"Test Environment\",\n      \"ip\": \"10.81.0.0/24\",\n      \"role\": \"QA/Testing\",\n      \"tags\": [\n        \"test\",\n        \"qa\",\n        \"staging\"\n      ],\n      \"notes\": [\n        \"Staging environment\",\n        \"Pre-prod validation\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"application\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null,\n      \"rotation\": -36\n    },\n    \"erp-system\": {\n      \"shape\": \"database\",\n      \"name\": \"ERP System\",\n      \"ip\": \"10.90.0.10\",\n      \"role\": \"Business Application\",\n      \"tags\": [\n        \"erp\",\n        \"sap\",\n        \"business\"\n      ],\n      \"notes\": [\n        \"SAP S/4HANA\",\n        \"Financial/HR systems\"\n      ],\n      \"mac\": \"00:50:56:ER:01:01\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"4\",\n      \"layer\": \"application\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"crm-system\": {\n      \"shape\": \"database\",\n      \"name\": \"CRM System\",\n      \"ip\": \"10.91.0.10\",\n      \"role\": \"Business Application\",\n      \"tags\": [\n        \"crm\",\n        \"salesforce\",\n        \"business\"\n      ],\n      \"notes\": [\n        \"Salesforce integration\",\n        \"Sales/Marketing\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"application\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"endpoint-1000\": {\n      \"shape\": \"laptop\",\n      \"name\": \"Corporate Endpoints\",\n      \"ip\": \"10.70.0.0/22\",\n      \"role\": \"User Workstations\",\n      \"tags\": [\n        \"endpoints\",\n        \"workstations\",\n        \"users\"\n      ],\n      \"notes\": [\n        \"~1000 corporate laptops\",\n        \"Windows 11\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"dist-switch-floor1\": {\n      \"shape\": \"switch\",\n      \"name\": \"Floor 1 Switch\",\n      \"ip\": \"10.1.1.1\",\n      \"role\": \"Distribution\",\n      \"tags\": [\n        \"distribution\",\n        \"floor-1\",\n        \"access\"\n      ],\n      \"notes\": [\n        \"Cisco C9300-48P\",\n        \"PoE+ enabled\"\n      ],\n      \"mac\": \"00:1A:2B:FL:01:01\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"dist-switch-floor2\": {\n      \"shape\": \"switch\",\n      \"name\": \"Floor 2 Switch\",\n      \"ip\": \"10.1.2.1\",\n      \"role\": \"Distribution\",\n      \"tags\": [\n        \"distribution\",\n        \"floor-2\",\n        \"access\"\n      ],\n      \"notes\": [\n        \"Cisco C9300-48P\",\n        \"PoE+ enabled\"\n      ],\n      \"mac\": \"00:1A:2B:FL:02:01\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"dist-switch-floor3\": {\n      \"shape\": \"switch\",\n      \"name\": \"Floor 3 Switch\",\n      \"ip\": \"10.1.3.1\",\n      \"role\": \"Distribution\",\n      \"tags\": [\n        \"distribution\",\n        \"floor-3\",\n        \"access\"\n      ],\n      \"notes\": [\n        \"Cisco C9300-48P\",\n        \"PoE+ enabled\"\n      ],\n      \"mac\": \"00:1A:2B:FL:03:01\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"dist-switch-floor4\": {\n      \"shape\": \"switch\",\n      \"name\": \"Floor 4 Switch\",\n      \"ip\": \"10.1.4.1\",\n      \"role\": \"Distribution\",\n      \"tags\": [\n        \"distribution\",\n        \"floor-4\",\n        \"access\"\n      ],\n      \"notes\": [\n        \"Cisco C9300-48P\",\n        \"PoE+ enabled\"\n      ],\n      \"mac\": \"00:1A:2B:FL:04:01\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"ap-floor1-zone1\": {\n      \"shape\": \"wifi\",\n      \"name\": \"AP Floor 1 Zone 1\",\n      \"ip\": \"10.20.1.10\",\n      \"role\": \"Wireless Access\",\n      \"tags\": [\n        \"wifi\",\n        \"ap\",\n        \"floor-1\"\n      ],\n      \"notes\": [\n        \"Cisco 9120AX\",\n        \"Wi-Fi 6\"\n      ],\n      \"mac\": \"00:1A:2B:AP:01:01\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"ap-floor2-zone1\": {\n      \"shape\": \"wifi\",\n      \"name\": \"AP Floor 2 Zone 1\",\n      \"ip\": \"10.20.2.10\",\n      \"role\": \"Wireless Access\",\n      \"tags\": [\n        \"wifi\",\n        \"ap\",\n        \"floor-2\"\n      ],\n      \"notes\": [\n        \"Cisco 9120AX\",\n        \"Wi-Fi 6\"\n      ],\n      \"mac\": \"00:1A:2B:AP:02:01\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"ap-floor3-zone1\": {\n      \"shape\": \"wifi\",\n      \"name\": \"AP Floor 3 Zone 1\",\n      \"ip\": \"10.20.3.10\",\n      \"role\": \"Wireless Access\",\n      \"tags\": [\n        \"wifi\",\n        \"ap\",\n        \"floor-3\"\n      ],\n      \"notes\": [\n        \"Cisco 9120AX\",\n        \"Wi-Fi 6\"\n      ],\n      \"mac\": \"00:1A:2B:AP:03:01\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"ap-floor4-zone1\": {\n      \"shape\": \"wifi\",\n      \"name\": \"AP Floor 4 Zone 1\",\n      \"ip\": \"10.20.4.10\",\n      \"role\": \"Wireless Access\",\n      \"tags\": [\n        \"wifi\",\n        \"ap\",\n        \"floor-4\"\n      ],\n      \"notes\": [\n        \"Cisco 9120AX\",\n        \"Wi-Fi 6\"\n      ],\n      \"mac\": \"00:1A:2B:AP:04:01\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"ups-dc-1\": {\n      \"shape\": \"rectangle\",\n      \"name\": \"UPS DC-1\",\n      \"ip\": \"192.168.200.10\",\n      \"role\": \"Power Management\",\n      \"tags\": [\n        \"power\",\n        \"ups\",\n        \"datacenter\"\n      ],\n      \"notes\": [\n        \"APC Symmetra\",\n        \"80kVA\",\n        \"30 min runtime\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"ups-dc-2\": {\n      \"shape\": \"rectangle\",\n      \"name\": \"UPS DC-2\",\n      \"ip\": \"192.168.200.11\",\n      \"role\": \"Power Management\",\n      \"tags\": [\n        \"power\",\n        \"ups\",\n        \"datacenter\"\n      ],\n      \"notes\": [\n        \"APC Symmetra\",\n        \"80kVA\",\n        \"Redundant\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"pdu-rack-a1\": {\n      \"shape\": \"rectangle\",\n      \"name\": \"PDU Rack A1\",\n      \"ip\": \"192.168.200.21\",\n      \"role\": \"Power Distribution\",\n      \"tags\": [\n        \"power\",\n        \"pdu\",\n        \"rack-a1\"\n      ],\n      \"notes\": [\n        \"APC Switched PDU\",\n        \"Per-outlet metering\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": 1,\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a1\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"pdu-rack-a2\": {\n      \"shape\": \"rectangle\",\n      \"name\": \"PDU Rack A2\",\n      \"ip\": \"192.168.200.22\",\n      \"role\": \"Power Distribution\",\n      \"tags\": [\n        \"power\",\n        \"pdu\",\n        \"rack-a2\"\n      ],\n      \"notes\": [\n        \"APC Switched PDU\",\n        \"Per-outlet metering\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": 1,\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a2\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"cooling-1\": {\n      \"shape\": \"rectangle\",\n      \"name\": \"CRAC Unit 1\",\n      \"ip\": \"192.168.200.30\",\n      \"role\": \"Cooling\",\n      \"tags\": [\n        \"cooling\",\n        \"hvac\",\n        \"datacenter\"\n      ],\n      \"notes\": [\n        \"Liebert CRV\",\n        \"Row-based cooling\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"cooling-2\": {\n      \"shape\": \"rectangle\",\n      \"name\": \"CRAC Unit 2\",\n      \"ip\": \"192.168.200.31\",\n      \"role\": \"Cooling\",\n      \"tags\": [\n        \"cooling\",\n        \"hvac\",\n        \"datacenter\"\n      ],\n      \"notes\": [\n        \"Liebert CRV\",\n        \"N+1 redundancy\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"camera-a\": {\n      \"shape\": \"camera\",\n      \"name\": \"camera A\",\n      \"ip\": \"0.0.0.0\",\n      \"role\": \"\",\n      \"tags\": [],\n      \"notes\": [],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"ping\": {\n        \"enabled\": false,\n        \"protocol\": \"http\",\n        \"customUrl\": \"\",\n        \"timeout\": 3000,\n        \"status\": \"unknown\",\n        \"lastCheck\": null\n      },\n      \"locked\": false,\n      \"groupId\": null,\n      \"fovEnabled\": true,\n      \"fovRotation\": 104,\n      \"fovDistance\": 500,\n      \"fovSweep\": 60,\n      \"fovSpeed\": 10,\n      \"fovAnimate\": true\n    },\n    \"camera-a-copy\": {\n      \"shape\": \"camera\",\n      \"name\": \"camera B\",\n      \"ip\": \"0.0.0.0\",\n      \"role\": \"\",\n      \"tags\": [],\n      \"notes\": [],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"ping\": {\n        \"enabled\": false,\n        \"protocol\": \"http\",\n        \"customUrl\": \"\",\n        \"timeout\": 3000,\n        \"status\": \"unknown\",\n        \"lastCheck\": null\n      },\n      \"locked\": false,\n      \"groupId\": null,\n      \"fovEnabled\": true,\n      \"fovRotation\": 162,\n      \"fovDistance\": 500,\n      \"fovSweep\": 60,\n      \"fovSpeed\": 10,\n      \"fovAnimate\": false\n    }\n  },\n  \"edgeData\": {\n    \"list\": [\n      {\n        \"id\": \"isp1-router1\",\n        \"from\": \"isp-primary\",\n        \"to\": \"core-router-1\",\n        \"width\": 6,\n        \"color\": \"#10b981\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [\n          \"Primary WAN link\"\n        ],\n        \"fromPort\": \"Gi0/0\",\n        \"toPort\": \"Gi1/0/1\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"isp2-router2\",\n        \"from\": \"isp-secondary\",\n        \"to\": \"core-router-2\",\n        \"width\": 6,\n        \"color\": \"#10b981\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [\n          \"Backup WAN link\"\n        ],\n        \"fromPort\": \"Gi0/0\",\n        \"toPort\": \"Gi1/0/1\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"router1-router2\",\n        \"from\": \"core-router-1\",\n        \"to\": \"core-router-2\",\n        \"width\": 4,\n        \"color\": \"#f59e0b\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [\n          \"HSRP Peering\"\n        ],\n        \"fromPort\": \"Gi1/0/24\",\n        \"toPort\": \"Gi1/0/24\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"router1-fw1\",\n        \"from\": \"core-router-1\",\n        \"to\": \"fw-external-1\",\n        \"width\": 4,\n        \"color\": \"#ef4444\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"router2-fw2\",\n        \"from\": \"core-router-2\",\n        \"to\": \"fw-external-2\",\n        \"width\": 4,\n        \"color\": \"#ef4444\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"fw1-fw2\",\n        \"from\": \"fw-external-1\",\n        \"to\": \"fw-external-2\",\n        \"width\": 3,\n        \"color\": \"#f59e0b\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [\n          \"HA heartbeat\"\n        ],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"dashed\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"fw1-coresw1\",\n        \"from\": \"fw-external-1\",\n        \"to\": \"core-switch-1\",\n        \"width\": 4,\n        \"color\": \"#475569\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"fw2-coresw2\",\n        \"from\": \"fw-external-2\",\n        \"to\": \"core-switch-2\",\n        \"width\": 4,\n        \"color\": \"#475569\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"coresw1-coresw2\",\n        \"from\": \"core-switch-1\",\n        \"to\": \"core-switch-2\",\n        \"width\": 5,\n        \"color\": \"#3b82f6\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [\n          \"VPC peer-link\"\n        ],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"coresw1-fwint\",\n        \"from\": \"core-switch-1\",\n        \"to\": \"fw-internal\",\n        \"width\": 3,\n        \"color\": \"#ef4444\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"coresw2-fwint\",\n        \"from\": \"core-switch-2\",\n        \"to\": \"fw-internal\",\n        \"width\": 3,\n        \"color\": \"#ef4444\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"coresw1-racka1\",\n        \"from\": \"core-switch-1\",\n        \"to\": \"dc-rack-a1\",\n        \"width\": 4,\n        \"color\": \"#475569\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"coresw2-racka1\",\n        \"from\": \"core-switch-2\",\n        \"to\": \"dc-rack-a1\",\n        \"width\": 4,\n        \"color\": \"#475569\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"coresw1-racka2\",\n        \"from\": \"core-switch-1\",\n        \"to\": \"dc-rack-a2\",\n        \"width\": 4,\n        \"color\": \"#475569\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"coresw2-racka2\",\n        \"from\": \"core-switch-2\",\n        \"to\": \"dc-rack-a2\",\n        \"width\": 4,\n        \"color\": \"#475569\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"coresw1-rackb1\",\n        \"from\": \"core-switch-1\",\n        \"to\": \"dc-rack-b1\",\n        \"width\": 4,\n        \"color\": \"#475569\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"coresw2-rackb1\",\n        \"from\": \"core-switch-2\",\n        \"to\": \"dc-rack-b1\",\n        \"width\": 4,\n        \"color\": \"#475569\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"coresw1-rackb2\",\n        \"from\": \"core-switch-1\",\n        \"to\": \"dc-rack-b2\",\n        \"width\": 4,\n        \"color\": \"#475569\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"coresw2-rackb2\",\n        \"from\": \"core-switch-2\",\n        \"to\": \"dc-rack-b2\",\n        \"width\": 4,\n        \"color\": \"#475569\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"fw1-dmz\",\n        \"from\": \"fw-external-1\",\n        \"to\": \"dmz-rack\",\n        \"width\": 3,\n        \"color\": \"#f59e0b\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [\n          \"DMZ segment\"\n        ],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"fw2-dmz\",\n        \"from\": \"fw-external-2\",\n        \"to\": \"dmz-rack\",\n        \"width\": 3,\n        \"color\": \"#f59e0b\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [\n          \"DMZ segment\"\n        ],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"coresw1-mgmt\",\n        \"from\": \"core-switch-1\",\n        \"to\": \"mgmt-rack\",\n        \"width\": 3,\n        \"color\": \"#8b5cf6\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [\n          \"OOB management\"\n        ],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"coresw1-wlc1\",\n        \"from\": \"core-switch-1\",\n        \"to\": \"wlc-primary\",\n        \"width\": 3,\n        \"color\": \"#06b6d4\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"coresw2-wlc2\",\n        \"from\": \"core-switch-2\",\n        \"to\": \"wlc-secondary\",\n        \"width\": 3,\n        \"color\": \"#06b6d4\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\",\n        \"animate\": true\n      },\n      {\n        \"id\": \"wlc1-wlc2\",\n        \"from\": \"wlc-primary\",\n        \"to\": \"wlc-secondary\",\n        \"width\": 2,\n        \"color\": \"#f59e0b\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [\n          \"HA pair\"\n        ],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"dashed\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"wlc1-mobile-hq\",\n        \"from\": \"wlc-primary\",\n        \"to\": \"mobile-zone-hq\",\n        \"width\": 2,\n        \"color\": \"#06b6d4\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"wlc1-mobile-guest\",\n        \"from\": \"wlc-primary\",\n        \"to\": \"mobile-zone-guest\",\n        \"width\": 2,\n        \"color\": \"#06b6d4\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"wlc1-mobile-iot\",\n        \"from\": \"wlc-primary\",\n        \"to\": \"mobile-zone-iot\",\n        \"width\": 2,\n        \"color\": \"#06b6d4\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"router1-branch-ny\",\n        \"from\": \"core-router-1\",\n        \"to\": \"branch-router-ny\",\n        \"width\": 3,\n        \"color\": \"#a855f7\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [\n          \"SD-WAN tunnel\"\n        ],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"dashed\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"router1-branch-la\",\n        \"from\": \"core-router-1\",\n        \"to\": \"branch-router-la\",\n        \"width\": 3,\n        \"color\": \"#a855f7\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [\n          \"SD-WAN tunnel\"\n        ],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"dashed\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"router1-branch-chi\",\n        \"from\": \"core-router-1\",\n        \"to\": \"branch-router-chi\",\n        \"width\": 3,\n        \"color\": \"#a855f7\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [\n          \"SD-WAN tunnel\"\n        ],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"dashed\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"router1-branch-lon\",\n        \"from\": \"core-router-1\",\n        \"to\": \"branch-router-lon\",\n        \"width\": 3,\n        \"color\": \"#a855f7\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [\n          \"SD-WAN tunnel\"\n        ],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"dashed\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"router1-branch-tokyo\",\n        \"from\": \"core-router-1\",\n        \"to\": \"branch-router-tokyo\",\n        \"width\": 3,\n        \"color\": \"#a855f7\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [\n          \"SD-WAN tunnel\"\n        ],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"dashed\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"router1-aws\",\n        \"from\": \"core-router-1\",\n        \"to\": \"cloud-aws\",\n        \"width\": 3,\n        \"color\": \"#f97316\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [\n          \"Direct Connect\"\n        ],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"dashed\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"router2-azure\",\n        \"from\": \"core-router-2\",\n        \"to\": \"cloud-azure\",\n        \"width\": 3,\n        \"color\": \"#0ea5e9\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [\n          \"ExpressRoute\"\n        ],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"dashed\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"fwint-gcp\",\n        \"from\": \"fw-internal\",\n        \"to\": \"cloud-gcp\",\n        \"width\": 2,\n        \"color\": \"#22c55e\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [\n          \"VPN tunnel\"\n        ],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"dashed\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"coresw1-floor1\",\n        \"from\": \"core-switch-1\",\n        \"to\": \"dist-switch-floor1\",\n        \"width\": 3,\n        \"color\": \"#475569\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"coresw1-floor2\",\n        \"from\": \"core-switch-1\",\n        \"to\": \"dist-switch-floor2\",\n        \"width\": 3,\n        \"color\": \"#475569\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"coresw2-floor3\",\n        \"from\": \"core-switch-2\",\n        \"to\": \"dist-switch-floor3\",\n        \"width\": 3,\n        \"color\": \"#475569\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"coresw2-floor4\",\n        \"from\": \"core-switch-2\",\n        \"to\": \"dist-switch-floor4\",\n        \"width\": 3,\n        \"color\": \"#475569\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"floor1-endpoints\",\n        \"from\": \"dist-switch-floor1\",\n        \"to\": \"endpoint-1000\",\n        \"width\": 2,\n        \"color\": \"#94a3b8\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"floor1-ap1\",\n        \"from\": \"dist-switch-floor1\",\n        \"to\": \"ap-floor1-zone1\",\n        \"width\": 2,\n        \"color\": \"#06b6d4\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"floor2-ap2\",\n        \"from\": \"dist-switch-floor2\",\n        \"to\": \"ap-floor2-zone1\",\n        \"width\": 2,\n        \"color\": \"#06b6d4\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"floor3-ap3\",\n        \"from\": \"dist-switch-floor3\",\n        \"to\": \"ap-floor3-zone1\",\n        \"width\": 2,\n        \"color\": \"#06b6d4\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"floor4-ap4\",\n        \"from\": \"dist-switch-floor4\",\n        \"to\": \"ap-floor4-zone1\",\n        \"width\": 2,\n        \"color\": \"#06b6d4\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"fwint-proxy1\",\n        \"from\": \"fw-internal\",\n        \"to\": \"proxy-server-1\",\n        \"width\": 2,\n        \"color\": \"#ef4444\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"fwint-proxy2\",\n        \"from\": \"fw-internal\",\n        \"to\": \"proxy-server-2\",\n        \"width\": 2,\n        \"color\": \"#ef4444\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"fwext1-vpn\",\n        \"from\": \"fw-external-1\",\n        \"to\": \"vpn-concentrator\",\n        \"width\": 3,\n        \"color\": \"#8b5cf6\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"coresw1-nac\",\n        \"from\": \"core-switch-1\",\n        \"to\": \"nac-server\",\n        \"width\": 2,\n        \"color\": \"#f59e0b\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"coresw1-voip\",\n        \"from\": \"core-switch-1\",\n        \"to\": \"voip-cluster\",\n        \"width\": 3,\n        \"color\": \"#22c55e\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"coresw2-video\",\n        \"from\": \"core-switch-2\",\n        \"to\": \"video-conf\",\n        \"width\": 3,\n        \"color\": \"#22c55e\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"coresw1-cameras\",\n        \"from\": \"core-switch-1\",\n        \"to\": \"security-cameras\",\n        \"width\": 2,\n        \"color\": \"#94a3b8\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"fwint-dev1\",\n        \"from\": \"fw-internal\",\n        \"to\": \"dev-server-1\",\n        \"width\": 2,\n        \"color\": \"#a855f7\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"fwint-dev2\",\n        \"from\": \"fw-internal\",\n        \"to\": \"dev-server-2\",\n        \"width\": 2,\n        \"color\": \"#a855f7\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\",\n        \"animate\": true,\n        \"animationSpeed\": \"1.5\"\n      },\n      {\n        \"id\": \"fwint-test\",\n        \"from\": \"fw-internal\",\n        \"to\": \"test-environment\",\n        \"width\": 2,\n        \"color\": \"#a855f7\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"coresw1-erp\",\n        \"from\": \"core-switch-1\",\n        \"to\": \"erp-system\",\n        \"width\": 3,\n        \"color\": \"#f59e0b\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"fwext1-crm\",\n        \"from\": \"fw-external-1\",\n        \"to\": \"crm-system\",\n        \"width\": 2,\n        \"color\": \"#f59e0b\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [\n          \"Salesforce cloud\"\n        ],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"dashed\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"ups1-racka1\",\n        \"from\": \"ups-dc-1\",\n        \"to\": \"dc-rack-a1\",\n        \"width\": 2,\n        \"color\": \"#fbbf24\",\n        \"direction\": \"forward\",\n        \"type\": \"main\",\n        \"notes\": [\n          \"Power feed A\"\n        ],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"ups2-racka2\",\n        \"from\": \"ups-dc-2\",\n        \"to\": \"dc-rack-a2\",\n        \"width\": 2,\n        \"color\": \"#fbbf24\",\n        \"direction\": \"forward\",\n        \"type\": \"main\",\n        \"notes\": [\n          \"Power feed B\"\n        ],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"ups1-rackb1\",\n        \"from\": \"ups-dc-1\",\n        \"to\": \"dc-rack-b1\",\n        \"width\": 2,\n        \"color\": \"#fbbf24\",\n        \"direction\": \"forward\",\n        \"type\": \"main\",\n        \"notes\": [\n          \"Power feed A\"\n        ],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\",\n        \"animate\": true,\n        \"animationSpeed\": \"4\"\n      },\n      {\n        \"id\": \"ups2-rackb2\",\n        \"from\": \"ups-dc-2\",\n        \"to\": \"dc-rack-b2\",\n        \"width\": 2,\n        \"color\": \"#fbbf24\",\n        \"direction\": \"forward\",\n        \"type\": \"main\",\n        \"notes\": [\n          \"Power feed B\"\n        ],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"cooling1-racka1\",\n        \"from\": \"cooling-1\",\n        \"to\": \"dc-rack-a1\",\n        \"width\": 2,\n        \"color\": \"#38bdf8\",\n        \"direction\": \"forward\",\n        \"type\": \"main\",\n        \"notes\": [\n          \"Cooling zone\"\n        ],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"dotted\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"cooling2-rackb1\",\n        \"from\": \"cooling-2\",\n        \"to\": \"dc-rack-b1\",\n        \"width\": 2,\n        \"color\": \"#38bdf8\",\n        \"direction\": \"forward\",\n        \"type\": \"main\",\n        \"notes\": [\n          \"Cooling zone\"\n        ],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"dotted\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"custom-1765237881452\",\n        \"type\": \"custom\",\n        \"color\": \"#c800ff\",\n        \"width\": 4,\n        \"lineStyle\": \"solid\",\n        \"direction\": \"forward\",\n        \"points\": [\n          {\n            \"x\": 3492.3994140625,\n            \"y\": 1526.9556884765625\n          },\n          {\n            \"x\": 3500.609619140625,\n            \"y\": 1830.7386474609375\n          },\n          {\n            \"x\": 3303.561279296875,\n            \"y\": 1732.2144775390625\n          }\n        ],\n        \"notes\": [],\n        \"routing\": \"orthogonal\"\n      }\n    ]\n  },\n  \"rectData\": {\n    \"list\": [\n      {\n        \"id\": \"rect-1765237540610\",\n        \"x\": 2879.214599609375,\n        \"y\": 159.71981811523438,\n        \"width\": 992.196044921875,\n        \"height\": 538.8650817871094,\n        \"color\": \"#f97316\",\n        \"style\": \"filled\",\n        \"lineStyle\": \"solid\",\n        \"notes\": []\n      },\n      {\n        \"id\": \"rect-1765237681216\",\n        \"x\": 448.3926696777344,\n        \"y\": 1671.651123046875,\n        \"width\": 916.3436584472656,\n        \"height\": 924.27734375,\n        \"color\": \"#c800ff\",\n        \"style\": \"outlined\",\n        \"lineStyle\": \"solid\",\n        \"notes\": []\n      },\n      {\n        \"id\": \"rect-1766437913740\",\n        \"x\": 904.5889892578125,\n        \"y\": 115.40318298339844,\n        \"width\": 110.93878173828125,\n        \"height\": 919.6242218017578,\n        \"color\": \"#5215f9\",\n        \"style\": \"filled\",\n        \"lineStyle\": \"wall\",\n        \"notes\": [],\n        \"borderWidth\": 13\n      },\n      {\n        \"id\": \"rect-1766437935414\",\n        \"x\": 130.93685150146484,\n        \"y\": 1072.3624877929688,\n        \"width\": 872.9131851196289,\n        \"height\": 99.260986328125,\n        \"color\": \"#5215f9\",\n        \"style\": \"filled\",\n        \"lineStyle\": \"wall\",\n        \"notes\": [],\n        \"borderWidth\": 13\n      }\n    ]\n  },\n  \"textData\": {\n    \"list\": [\n      {\n        \"id\": \"text-1765237828167\",\n        \"x\": 3411.458740234375,\n        \"y\": 1390.00439453125,\n        \"content\": \"Double click on desktop\\nor long press on mobile\\nto enter rack canvas view\",\n        \"fontSize\": 46,\n        \"color\": \"#e2e8f0\",\n        \"fontWeight\": \"bold\",\n        \"fontStyle\": \"italic\",\n        \"textAlign\": \"middle\",\n        \"textDecoration\": \"none\",\n        \"bgColor\": \"#000000\",\n        \"bgEnabled\": false,\n        \"opacity\": 1\n      },\n      {\n        \"id\": \"text-1766446595277\",\n        \"x\": 654.3878479003906,\n        \"y\": 1367.7945556640625,\n        \"content\": \"SITE A\",\n        \"fontSize\": 101,\n        \"color\": \"#e2e8f0\",\n        \"fontWeight\": \"normal\",\n        \"fontStyle\": \"normal\",\n        \"textAlign\": \"start\",\n        \"textDecoration\": \"none\",\n        \"bgColor\": \"#000000\",\n        \"bgEnabled\": false,\n        \"opacity\": 1\n      },\n      {\n        \"id\": \"text-1766446610211\",\n        \"x\": 180.63662719726562,\n        \"y\": 1128.822998046875,\n        \"content\": \"SITE A\",\n        \"fontSize\": 101,\n        \"color\": \"#e2e8f0\",\n        \"fontWeight\": \"normal\",\n        \"fontStyle\": \"normal\",\n        \"textAlign\": \"start\",\n        \"textDecoration\": \"none\",\n        \"bgColor\": \"#000000\",\n        \"bgEnabled\": false,\n        \"opacity\": 1\n      },\n      {\n        \"id\": \"text-1766453024797\",\n        \"x\": 968.6458740234375,\n        \"y\": 1028.6621398925781,\n        \"content\": \"SITE A\",\n        \"fontSize\": 101,\n        \"color\": \"#e2e8f0\",\n        \"fontWeight\": \"normal\",\n        \"fontStyle\": \"normal\",\n        \"textAlign\": \"start\",\n        \"textDecoration\": \"none\",\n        \"bgColor\": \"#000000\",\n        \"bgEnabled\": false,\n        \"opacity\": 1,\n        \"rotation\": -89,\n        \"_dragStartX\": 972.46826171875,\n        \"_dragStartY\": 1009.5499572753906\n      },\n      {\n        \"id\": \"text-1766453070975\",\n        \"x\": 613.1589965820312,\n        \"y\": 1139.512939453125,\n        \"content\": \"SITE A\",\n        \"fontSize\": 101,\n        \"color\": \"#e2e8f0\",\n        \"fontWeight\": \"normal\",\n        \"fontStyle\": \"normal\",\n        \"textAlign\": \"start\",\n        \"textDecoration\": \"none\",\n        \"bgColor\": \"#000000\",\n        \"bgEnabled\": false,\n        \"opacity\": 1\n      },\n      {\n        \"id\": \"text-1766453072857\",\n        \"x\": 968.64599609375,\n        \"y\": 474.40818786621094,\n        \"content\": \"SITE A\",\n        \"fontSize\": 101,\n        \"color\": \"#e2e8f0\",\n        \"fontWeight\": \"normal\",\n        \"fontStyle\": \"normal\",\n        \"textAlign\": \"start\",\n        \"textDecoration\": \"none\",\n        \"bgColor\": \"#000000\",\n        \"bgEnabled\": false,\n        \"opacity\": 1,\n        \"rotation\": 269,\n        \"_dragStartX\": 1480.85302734375,\n        \"_dragStartY\": 822.2503356933594\n      },\n      {\n        \"id\": \"text-1766458222326\",\n        \"x\": 3167.812744140625,\n        \"y\": 2190.516357421875,\n        \"content\": \"\",\n        \"fontSize\": 18,\n        \"color\": \"#e2e8f0\",\n        \"fontWeight\": \"normal\",\n        \"fontStyle\": \"normal\",\n        \"textAlign\": \"start\",\n        \"textDecoration\": \"none\",\n        \"bgColor\": \"#000000\",\n        \"bgEnabled\": false,\n        \"opacity\": 1\n      }\n    ]\n  },\n  \"edgeLegend\": {\n    \"#10b981\": \"Trusted Lan\",\n    \"#f59e0b\": \"Secure Lan\",\n    \"#ef4444\": \"DMZ\",\n    \"#475569\": \"Main ISP\",\n    \"#3b82f6\": \"Alternate ISP\",\n    \"#8b5cf6\": \"you can edit me too\",\n    \"#06b6d4\": \"you can edit me too\",\n    \"#a855f7\": \"you can edit me too\",\n    \"#f97316\": \"you can edit me too\",\n    \"#0ea5e9\": \"you can edit me too\",\n    \"#22c55e\": \"you can edit me too\",\n    \"#94a3b8\": \"you can edit me too\",\n    \"#fbbf24\": \"you can edit me too\",\n    \"#38bdf8\": \"you can edit me too\",\n    \"#c800ff\": \"you can edit me too\"\n  },\n  \"nodePositions\": {\n    \"core-router-1\": {\n      \"x\": 3720.166015625,\n      \"y\": 245.9932403564453\n    },\n    \"core-router-2\": {\n      \"x\": 2499.883407638303,\n      \"y\": 329.99503430389154\n    },\n    \"fw-external-1\": {\n      \"x\": 3221.7385182723783,\n      \"y\": 1016.1364499992887\n    },\n    \"fw-external-2\": {\n      \"x\": 1915.5213706410505,\n      \"y\": 224.43528858865443\n    },\n    \"fw-internal\": {\n      \"x\": 1746.9168185079352,\n      \"y\": 477.5300527221864\n    },\n    \"core-switch-1\": {\n      \"x\": 449.39860669455675,\n      \"y\": 384.4578707617695\n    },\n    \"core-switch-2\": {\n      \"x\": 761.1664921394672,\n      \"y\": 180.89283910873155\n    },\n    \"dc-rack-a1\": {\n      \"x\": 783.7017241128451,\n      \"y\": 647.4086870405963\n    },\n    \"dc-rack-a2\": {\n      \"x\": 209.25701628255229,\n      \"y\": 228.01593190351014\n    },\n    \"dc-rack-b1\": {\n      \"x\": 3184.3186625759854,\n      \"y\": 1627.4495531027196\n    },\n    \"dc-rack-b2\": {\n      \"x\": 245.37065918741246,\n      \"y\": 499.6191264194081\n    },\n    \"dmz-rack\": {\n      \"x\": 2176.4105289561007,\n      \"y\": 610.8312056412005\n    },\n    \"mgmt-rack\": {\n      \"x\": 1601.2987201807314,\n      \"y\": 1281.4753424975324\n    },\n    \"esxi-host-01\": {\n      \"x\": 2162.2166789540615,\n      \"y\": 2608.110619289529\n    },\n    \"esxi-host-02\": {\n      \"x\": 2205.94717202368,\n      \"y\": 2689.67539624076\n    },\n    \"esxi-host-03\": {\n      \"x\": 2154.6015436939074,\n      \"y\": 2771.203009774913\n    },\n    \"esxi-host-04\": {\n      \"x\": 2195.986926025096,\n      \"y\": 2845\n    },\n    \"tor-switch-a1\": {\n      \"x\": 2146.8943639962963,\n      \"y\": 2845\n    },\n    \"esxi-host-05\": {\n      \"x\": 2185.9099961569727,\n      \"y\": 2845\n    },\n    \"esxi-host-06\": {\n      \"x\": 2139.099728450725,\n      \"y\": 2845\n    },\n    \"esxi-host-07\": {\n      \"x\": 2175.7223818764883,\n      \"y\": 2845\n    },\n    \"esxi-host-08\": {\n      \"x\": 2131.2222777148922,\n      \"y\": 2845\n    },\n    \"tor-switch-a2\": {\n      \"x\": 2165.4301485385085,\n      \"y\": 2845\n    },\n    \"san-primary\": {\n      \"x\": 2123.2667017518106,\n      \"y\": 2845\n    },\n    \"san-secondary\": {\n      \"x\": 2155.0394237844876,\n      \"y\": 2845\n    },\n    \"fc-switch-1\": {\n      \"x\": 2115.2377370375634,\n      \"y\": 2845\n    },\n    \"fc-switch-2\": {\n      \"x\": 2144.5563938942755,\n      \"y\": 2845\n    },\n    \"backup-server-1\": {\n      \"x\": 2107.1401637413705,\n      \"y\": 2845\n    },\n    \"backup-server-2\": {\n      \"x\": 2133.987300103025,\n      \"y\": 2845\n    },\n    \"tape-library\": {\n      \"x\": 2098.9788028796397,\n      \"y\": 2845\n    },\n    \"tor-switch-b1\": {\n      \"x\": 2123.338434885373,\n      \"y\": 2845\n    },\n    \"tor-switch-b2\": {\n      \"x\": 2090.7585134456995,\n      \"y\": 2845\n    },\n    \"web-server-1\": {\n      \"x\": 2112.6161382091163,\n      \"y\": 2845\n    },\n    \"web-server-2\": {\n      \"x\": 2082.484189516922,\n      \"y\": 2845\n    },\n    \"waf-1\": {\n      \"x\": 2101.826793760617,\n      \"y\": 2845\n    },\n    \"load-balancer-dmz\": {\n      \"x\": 2074.1607573409574,\n      \"y\": 2845\n    },\n    \"mail-gateway\": {\n      \"x\": 2090.97682514417,\n      \"y\": 2845\n    },\n    \"dns-external-1\": {\n      \"x\": 2065.7931724028163,\n      \"y\": 2845\n    },\n    \"dns-external-2\": {\n      \"x\": 2080.0726920576153,\n      \"y\": 2845\n    },\n    \"vcenter\": {\n      \"x\": 2057.3864164745437,\n      \"y\": 2845\n    },\n    \"nsx-manager\": {\n      \"x\": 2069.1208864464534,\n      \"y\": 2845\n    },\n    \"siem-server\": {\n      \"x\": 2048.945494649244,\n      \"y\": 2845\n    },\n    \"nms-server\": {\n      \"x\": 2058.1279286387635,\n      \"y\": 2845\n    },\n    \"jump-server\": {\n      \"x\": 2040.4754323612206,\n      \"y\": 2845\n    },\n    \"ipam-server\": {\n      \"x\": 2047.1003634632284,\n      \"y\": 2845\n    },\n    \"wlc-primary\": {\n      \"x\": 1575.9723612611924,\n      \"y\": 2306.135986328125\n    },\n    \"wlc-secondary\": {\n      \"x\": 1468.1361870166274,\n      \"y\": 1563.733642578125\n    },\n    \"mobile-zone-hq\": {\n      \"x\": 2354.901177346808,\n      \"y\": 2806.0078125\n    },\n    \"mobile-zone-guest\": {\n      \"x\": 2307.6605605284435,\n      \"y\": 2611.047119140625\n    },\n    \"mobile-zone-iot\": {\n      \"x\": 2229.397686389302,\n      \"y\": 2299.110107421875\n    },\n    \"branch-router-ny\": {\n      \"x\": 3151.903101363964,\n      \"y\": 633.6580810546875\n    },\n    \"branch-router-la\": {\n      \"x\": 3083.8876194705945,\n      \"y\": 506.90625\n    },\n    \"branch-router-chi\": {\n      \"x\": 3355.02409980103,\n      \"y\": 393.1805725097656\n    },\n    \"branch-router-lon\": {\n      \"x\": 3113.609823320121,\n      \"y\": 260.4093322753906\n    },\n    \"branch-router-tokyo\": {\n      \"x\": 3699.3234994733834,\n      \"y\": 471.4241027832031\n    },\n    \"cloud-aws\": {\n      \"x\": 3436.528122523513,\n      \"y\": 545.9614868164062\n    },\n    \"cloud-azure\": {\n      \"x\": 2592.566210818907,\n      \"y\": 2724.068115234375\n    },\n    \"cloud-gcp\": {\n      \"x\": 2827.3183770424234,\n      \"y\": 2731.397216796875\n    },\n    \"isp-primary\": {\n      \"x\": 3712.192068081962,\n      \"y\": 615.64990234375\n    },\n    \"isp-secondary\": {\n      \"x\": 3253.9473366098055,\n      \"y\": 1993.2629089355469\n    },\n    \"dc-internal-1\": {\n      \"x\": 1958.4243458877936,\n      \"y\": 2845\n    },\n    \"dc-internal-2\": {\n      \"x\": 1963.768951182132,\n      \"y\": 2845\n    },\n    \"app-server-1\": {\n      \"x\": 1947.3819379304134,\n      \"y\": 2845\n    },\n    \"app-server-2\": {\n      \"x\": 1955.2862087394126,\n      \"y\": 2845\n    },\n    \"db-server-1\": {\n      \"x\": 1936.3708569559828,\n      \"y\": 2845\n    },\n    \"db-server-2\": {\n      \"x\": 1946.8300873488822,\n      \"y\": 2845\n    },\n    \"k8s-master-1\": {\n      \"x\": 1925.397658583093,\n      \"y\": 2845\n    },\n    \"k8s-master-2\": {\n      \"x\": 1938.405621494142,\n      \"y\": 2845\n    },\n    \"k8s-master-3\": {\n      \"x\": 1914.4688758763386,\n      \"y\": 2845\n    },\n    \"k8s-worker-1\": {\n      \"x\": 1930.017826812177,\n      \"y\": 2845\n    },\n    \"k8s-worker-2\": {\n      \"x\": 1903.5910154567553,\n      \"y\": 2845\n    },\n    \"k8s-worker-3\": {\n      \"x\": 1921.6716971072178,\n      \"y\": 2845\n    },\n    \"k8s-worker-4\": {\n      \"x\": 1892.7705536280016,\n      \"y\": 2845\n    },\n    \"proxy-server-1\": {\n      \"x\": 1806.1152433697903,\n      \"y\": 653.7529296875\n    },\n    \"proxy-server-2\": {\n      \"x\": 2937.4207928721535,\n      \"y\": 2628.7880859375\n    },\n    \"vpn-concentrator\": {\n      \"x\": 3642.252088474593,\n      \"y\": 946.7255249023438\n    },\n    \"nac-server\": {\n      \"x\": 1153.2626148502184,\n      \"y\": 1172.1895751953125\n    },\n    \"print-server\": {\n      \"x\": 1896.9328460745962,\n      \"y\": 2845\n    },\n    \"file-server\": {\n      \"x\": 1860.7177871362182,\n      \"y\": 2845\n    },\n    \"ca-server\": {\n      \"x\": 1888.8027739274805,\n      \"y\": 2845\n    },\n    \"sccm-server\": {\n      \"x\": 1850.1909418511675,\n      \"y\": 2845\n    },\n    \"voip-cluster\": {\n      \"x\": 1777.038465328039,\n      \"y\": 1616.8961181640625\n    },\n    \"video-conf\": {\n      \"x\": 1993.8373941679588,\n      \"y\": 2244.936309814453\n    },\n    \"security-cameras\": {\n      \"x\": 1674.413336949044,\n      \"y\": 2046.0380859375\n    },\n    \"nvr-cluster\": {\n      \"x\": 1829.4110389706402,\n      \"y\": 2845\n    },\n    \"dev-server-1\": {\n      \"x\": 2800.5894350649614,\n      \"y\": 1175.623291015625\n    },\n    \"dev-server-2\": {\n      \"x\": 1945.0822182484326,\n      \"y\": 1164.5184783935547\n    },\n    \"test-environment\": {\n      \"x\": 2932.0863047891075,\n      \"y\": 862.4592895507812\n    },\n    \"erp-system\": {\n      \"x\": 789.9880103985649,\n      \"y\": 473.7113342285156\n    },\n    \"crm-system\": {\n      \"x\": 3514.6003232048542,\n      \"y\": 1137.7720947265625\n    },\n    \"endpoint-1000\": {\n      \"x\": 991.6812012057328,\n      \"y\": 2284.42236328125\n    },\n    \"dist-switch-floor1\": {\n      \"x\": 654.2091033261356,\n      \"y\": 2020.0086669921875\n    },\n    \"dist-switch-floor2\": {\n      \"x\": 853.8845527112826,\n      \"y\": 1843.2872314453125\n    },\n    \"dist-switch-floor3\": {\n      \"x\": 1899.4353951584517,\n      \"y\": 1456.5068359375\n    },\n    \"dist-switch-floor4\": {\n      \"x\": 488.5289313756234,\n      \"y\": 181.47256469726562\n    },\n    \"ap-floor1-zone1\": {\n      \"x\": 1140.16846970184,\n      \"y\": 2070.2916259765625\n    },\n    \"ap-floor2-zone1\": {\n      \"x\": 688.1952143592268,\n      \"y\": 2384.4775390625\n    },\n    \"ap-floor3-zone1\": {\n      \"x\": 2145.3803027919676,\n      \"y\": 1890.2816162109375\n    },\n    \"ap-floor4-zone1\": {\n      \"x\": 517.646146409649,\n      \"y\": 565.59716796875\n    },\n    \"ups-dc-1\": {\n      \"x\": 771.1406786539856,\n      \"y\": 295.9266662597656\n    },\n    \"ups-dc-2\": {\n      \"x\": 216.2410855890687,\n      \"y\": 330.3345947265625\n    },\n    \"pdu-rack-a1\": {\n      \"x\": 1804.774444371901,\n      \"y\": 2845\n    },\n    \"pdu-rack-a2\": {\n      \"x\": 1741.6184034693686,\n      \"y\": 2845\n    },\n    \"cooling-1\": {\n      \"x\": 245.7080801919958,\n      \"y\": 626.1914672851562\n    },\n    \"cooling-2\": {\n      \"x\": 1603.293611085831,\n      \"y\": 981.0621185302734\n    },\n    \"camera-a\": {\n      \"x\": 166.57075412676295,\n      \"y\": 145\n    },\n    \"camera-a-copy\": {\n      \"x\": 1040.653076171875,\n      \"y\": 738.42822265625\n    }\n  },\n  \"nodeSizes\": {\n    \"isp-secondary\": 139,\n    \"test-environment\": 121,\n    \"dev-server-1\": 128,\n    \"core-router-2\": 120,\n    \"camera-a\": 45,\n    \"camera-a-copy\": 45\n  },\n  \"nodeStyles\": {\n    \"dc-rack-b2\": {\n      \"all\": {\n        \"circleColor\": \"#ff0000\"\n      }\n    },\n    \"dc-rack-a1\": {\n      \"all\": {\n        \"circleColor\": \"#ff0000\"\n      }\n    },\n    \"dc-rack-b1\": {\n      \"all\": {\n        \"circleColor\": \"#ff0000\",\n        \"titleSize\": 59\n      }\n    },\n    \"isp-secondary\": {\n      \"all\": {\n        \"icon\": {\n          \"library\": \"selfhst\",\n          \"name\": \"alist\"\n        },\n        \"circleColor\": \"#4d2c58\",\n        \"circleBorder\": \"#000000\",\n        \"titleColor\": \"#006eff\"\n      }\n    },\n    \"core-router-2\": {\n      \"all\": {\n        \"icon\": {\n          \"library\": \"selfhst\",\n          \"name\": \"actual-budget\"\n        },\n        \"pingOffsetX\": -15,\n        \"pingOffsetY\": -38\n      }\n    },\n    \"fw-external-1\": {\n      \"all\": {\n        \"icon\": {\n          \"library\": \"selfhst\",\n          \"name\": \"anonaddy\"\n        }\n      }\n    },\n    \"cloud-aws\": {\n      \"all\": {\n        \"icon\": {\n          \"library\": \"selfhst\",\n          \"name\": \"ansible\"\n        }\n      }\n    },\n    \"isp-primary\": {\n      \"all\": {\n        \"icon\": {\n          \"library\": \"selfhst\",\n          \"name\": \"wikidocs\"\n        }\n      }\n    },\n    \"branch-router-tokyo\": {\n      \"all\": {\n        \"icon\": {\n          \"library\": \"selfhst\",\n          \"name\": \"adguard-home\"\n        }\n      }\n    },\n    \"core-router-1\": {\n      \"all\": {\n        \"icon\": {\n          \"library\": \"selfhst\",\n          \"name\": \"borg\"\n        }\n      }\n    },\n    \"test-environment\": {\n      \"all\": {\n        \"icon\": {\n          \"library\": \"simple\",\n          \"name\": \"apple\"\n        }\n      }\n    },\n    \"dev-server-1\": {\n      \"all\": {\n        \"icon\": {\n          \"library\": \"simple\",\n          \"name\": \"amazonwebservices\"\n        }\n      }\n    }\n  },\n  \"page\": {\n    \"title\": \"The One File Corporate\",\n    \"background\": \"\",\n    \"topbarBg\": \"rgba(9, 12, 20, 0.9)\",\n    \"topbarBorder\": \"#1f2533\",\n    \"panel\": \"#0b0e13\",\n    \"panelAlt\": \"#10141b\",\n    \"accent\": \"#4fd1c5\",\n    \"sidebarBg\": \"#10141b\",\n    \"btnBg\": \"#0b0e13\",\n    \"btnText\": \"#e2e8f0\",\n    \"tagFill\": \"#1e293b\",\n    \"tagText\": \"#e2e8f0\",\n    \"tagBorder\": \"#475569\",\n    \"inputBg\": \"#0b0e13\",\n    \"inputText\": \"#e2e8f0\",\n    \"inputBorder\": \"#1f2937\",\n    \"inputFont\": \"Inter, system-ui, sans-serif\",\n    \"inputFontSize\": 14,\n    \"toolbarBg\": \"#0f172a\",\n    \"toolbarBorder\": \"#1f2937\",\n    \"toolbarText\": \"#94a3b8\",\n    \"toolbarBtnBg\": \"#0b0e13\",\n    \"toolbarBtnText\": \"#e2e8f0\",\n    \"minimapDots\": \"#94a3b8\",\n    \"canvasHintEnabled\": true,\n    \"canvasHintText\": \"\",\n    \"canvasHintBg\": \"#0f172a\",\n    \"canvasHintColor\": \"#94a3b8\",\n    \"danger\": \"#f56565\",\n    \"textMain\": \"#e2e8f0\",\n    \"textSoft\": \"#94a3b8\",\n    \"topbarHeight\": 103,\n    \"sidebarWidth\": 350,\n    \"mobileFooterHeight\": 40,\n    \"sidebarCollapsed\": false,\n    \"nodeFill\": \"#1e293b\",\n    \"nodeStroke\": \"#475569\",\n    \"nodeTitle\": \"#e2e8f0\",\n    \"nodeSub\": \"#94a3b8\",\n    \"nodeTitleSize\": 41,\n    \"nodeSubSize\": 27,\n    \"nodeFont\": \"monospace\",\n    \"defaultEdge\": \"#475569\",\n    \"selectionHandle\": \"#f59e0b\",\n    \"selectionHandleSize\": 8,\n    \"groupIndicator\": \"#4fd1c5\",\n    \"canvasGradientTop\": \"#1e2532\",\n    \"canvasGradientBottom\": \"#050608\",\n    \"canvasBorder\": \"#475569\",\n    \"canvasGrid\": \"#475569\",\n    \"canvasGridSize\": 50,\n    \"canvasGridEnabled\": true,\n    \"rackFrameFill\": \"#0f172a\",\n    \"rackGridEnabled\": true,\n    \"rackFrameStroke\": \"#4fd1c5\",\n    \"rackLineColor\": \"#475569\",\n    \"rackTextColor\": \"#4fd1c5\",\n    \"viewOnly\": false,\n    \"defaultEdgeRouting\": \"orthogonal\",\n    \"animateConnections\": false,\n    \"animationStyle\": \"arrows\",\n    \"animationDirection\": \"all\",\n    \"animationSpeed\": 4,\n    \"autoPingEnabled\": false,\n    \"autoPingInterval\": 30\n  },\n  \"canvas\": {\n    \"zoom\": 0.8752084596859406,\n    \"panX\": -191.26917538063572,\n    \"panY\": -261.51832729948296\n  },\n  \"savedTopologyView\": {\n    \"zoom\": 0.9111098220009686,\n    \"panX\": -207.26076103009882,\n    \"panY\": -201.83911371533202\n  },\n  \"documentTabs\": [\n    {\n      \"id\": \"main\",\n      \"name\": \"Corporate Site B\",\n      \"nodes\": {\n        \"core-router-1\": {\n          \"shape\": \"router\",\n          \"name\": \"Core Router 1\",\n          \"ip\": \"10.0.0.1\",\n          \"role\": \"Core Routing\",\n          \"tags\": [\n            \"core\",\n            \"tier-1\",\n            \"redundant\"\n          ],\n          \"notes\": [\n            \"Primary core router\",\n            \"BGP peering enabled\"\n          ],\n          \"mac\": \"00:1A:2B:3C:4D:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"core-router-2\": {\n          \"shape\": \"router\",\n          \"name\": \"Core Router 2\",\n          \"ip\": \"10.0.0.2\",\n          \"role\": \"Core Routing\",\n          \"tags\": [\n            \"core\",\n            \"tier-1\",\n            \"redundant\"\n          ],\n          \"notes\": [\n            \"Secondary core router\",\n            \"HSRP standby\"\n          ],\n          \"mac\": \"00:1A:2B:3C:4D:02\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null,\n          \"ping\": {\n            \"enabled\": true,\n            \"protocol\": \"custom\",\n            \"customUrl\": \"https://google.com\",\n            \"timeout\": 3000,\n            \"status\": \"online\",\n            \"lastCheck\": \"2025-12-09T00:15:04.343Z\"\n          }\n        },\n        \"fw-external-1\": {\n          \"shape\": \"firewall\",\n          \"name\": \"External FW 1\",\n          \"ip\": \"10.0.1.1\",\n          \"role\": \"Perimeter Security\",\n          \"tags\": [\n            \"security\",\n            \"perimeter\",\n            \"ha-pair\"\n          ],\n          \"notes\": [\n            \"Palo Alto PA-5250\",\n            \"Active node\"\n          ],\n          \"mac\": \"00:1A:2B:3C:4D:10\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"fw-external-2\": {\n          \"shape\": \"firewall\",\n          \"name\": \"External FW 2\",\n          \"ip\": \"10.0.1.2\",\n          \"role\": \"Perimeter Security\",\n          \"tags\": [\n            \"security\",\n            \"perimeter\",\n            \"ha-pair\"\n          ],\n          \"notes\": [\n            \"Palo Alto PA-5250\",\n            \"Passive node\"\n          ],\n          \"mac\": \"00:1A:2B:3C:4D:11\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"fw-internal\": {\n          \"shape\": \"firewall\",\n          \"name\": \"Internal FW\",\n          \"ip\": \"10.0.2.1\",\n          \"role\": \"Internal Segmentation\",\n          \"tags\": [\n            \"security\",\n            \"internal\"\n          ],\n          \"notes\": [\n            \"East-West traffic inspection\"\n          ],\n          \"mac\": \"00:1A:2B:3C:4D:12\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"core-switch-1\": {\n          \"shape\": \"switch\",\n          \"name\": \"Core Switch 1\",\n          \"ip\": \"10.0.10.1\",\n          \"role\": \"Core Switching\",\n          \"tags\": [\n            \"core\",\n            \"layer3\",\n            \"redundant\"\n          ],\n          \"notes\": [\n            \"Cisco Nexus 9000\",\n            \"VPC Domain 1\"\n          ],\n          \"mac\": \"00:1A:2B:3C:4D:20\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"core-switch-2\": {\n          \"shape\": \"switch\",\n          \"name\": \"Core Switch 2\",\n          \"ip\": \"10.0.10.2\",\n          \"role\": \"Core Switching\",\n          \"tags\": [\n            \"core\",\n            \"layer3\",\n            \"redundant\"\n          ],\n          \"notes\": [\n            \"Cisco Nexus 9000\",\n            \"VPC Domain 1\"\n          ],\n          \"mac\": \"00:1A:2B:3C:4D:21\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dc-rack-a1\": {\n          \"shape\": \"server\",\n          \"name\": \"DC Rack A1\",\n          \"ip\": \"10.10.0.0/24\",\n          \"role\": \"Data Center Rack\",\n          \"tags\": [\n            \"datacenter\",\n            \"row-a\",\n            \"production\"\n          ],\n          \"notes\": [\n            \"Row A, Position 1\",\n            \"Primary compute\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": true,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dc-rack-a2\": {\n          \"shape\": \"server\",\n          \"name\": \"DC Rack A2\",\n          \"ip\": \"10.10.1.0/24\",\n          \"role\": \"Data Center Rack\",\n          \"tags\": [\n            \"datacenter\",\n            \"row-a\",\n            \"production\"\n          ],\n          \"notes\": [\n            \"Row A, Position 2\",\n            \"Primary compute\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": true,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dc-rack-b1\": {\n          \"shape\": \"server\",\n          \"name\": \"DC Rack B1\",\n          \"ip\": \"10.10.2.0/24\",\n          \"role\": \"Data Center Rack\",\n          \"tags\": [\n            \"datacenter\",\n            \"row-b\",\n            \"storage\"\n          ],\n          \"notes\": [\n            \"Row B, Position 1\",\n            \"Storage systems\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": true,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dc-rack-b2\": {\n          \"shape\": \"server\",\n          \"name\": \"DC Rack B2\",\n          \"ip\": \"10.10.3.0/24\",\n          \"role\": \"Data Center Rack\",\n          \"tags\": [\n            \"datacenter\",\n            \"row-b\",\n            \"storage\"\n          ],\n          \"notes\": [\n            \"Row B, Position 2\",\n            \"Storage systems\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": true,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dmz-rack\": {\n          \"shape\": \"server\",\n          \"name\": \"DMZ Rack\",\n          \"ip\": \"172.16.0.0/24\",\n          \"role\": \"DMZ Infrastructure\",\n          \"tags\": [\n            \"dmz\",\n            \"security\",\n            \"public-facing\",\n            {\n              \"type\": \"icon\",\n              \"library\": \"selfhst\",\n              \"name\": \"booklogr\"\n            },\n            {\n              \"type\": \"icon\",\n              \"library\": \"simple\",\n              \"name\": \"gmail\"\n            }\n          ],\n          \"notes\": [\n            \"Isolated DMZ zone\",\n            \"Public-facing services\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": true,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"mgmt-rack\": {\n          \"shape\": \"server\",\n          \"name\": \"Management Rack\",\n          \"ip\": \"192.168.100.0/24\",\n          \"role\": \"Management Infrastructure\",\n          \"tags\": [\n            \"management\",\n            \"oob\",\n            \"noc\"\n          ],\n          \"notes\": [\n            \"Out-of-band management\",\n            \"NOC equipment\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"logical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": true,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"esxi-host-01\": {\n          \"shape\": \"server\",\n          \"name\": \"ESXi Host 01\",\n          \"ip\": \"10.10.0.11\",\n          \"role\": \"Hypervisor\",\n          \"tags\": [\n            \"vmware\",\n            \"compute\",\n            \"cluster-a\"\n          ],\n          \"notes\": [\n            \"Dell PowerEdge R750\",\n            \"512GB RAM\",\n            \"vSphere 8.0\"\n          ],\n          \"mac\": \"00:50:56:AA:01:01\",\n          \"rackUnit\": 38,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"esxi-host-02\": {\n          \"shape\": \"server\",\n          \"name\": \"ESXi Host 02\",\n          \"ip\": \"10.10.0.12\",\n          \"role\": \"Hypervisor\",\n          \"tags\": [\n            \"vmware\",\n            \"compute\",\n            \"cluster-a\"\n          ],\n          \"notes\": [\n            \"Dell PowerEdge R750\",\n            \"512GB RAM\",\n            \"vSphere 8.0\"\n          ],\n          \"mac\": \"00:50:56:AA:01:02\",\n          \"rackUnit\": 35,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"esxi-host-03\": {\n          \"shape\": \"server\",\n          \"name\": \"ESXi Host 03\",\n          \"ip\": \"10.10.0.13\",\n          \"role\": \"Hypervisor\",\n          \"tags\": [\n            \"vmware\",\n            \"compute\",\n            \"cluster-a\"\n          ],\n          \"notes\": [\n            \"Dell PowerEdge R750\",\n            \"512GB RAM\",\n            \"vSphere 8.0\"\n          ],\n          \"mac\": \"00:50:56:AA:01:03\",\n          \"rackUnit\": 32,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"esxi-host-04\": {\n          \"shape\": \"server\",\n          \"name\": \"ESXi Host 04\",\n          \"ip\": \"10.10.0.14\",\n          \"role\": \"Hypervisor\",\n          \"tags\": [\n            \"vmware\",\n            \"compute\",\n            \"cluster-a\"\n          ],\n          \"notes\": [\n            \"Dell PowerEdge R750\",\n            \"512GB RAM\",\n            \"vSphere 8.0\"\n          ],\n          \"mac\": \"00:50:56:AA:01:04\",\n          \"rackUnit\": 29,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"tor-switch-a1\": {\n          \"shape\": \"switch\",\n          \"name\": \"ToR Switch A1\",\n          \"ip\": \"10.10.0.1\",\n          \"role\": \"Top of Rack\",\n          \"tags\": [\n            \"tor\",\n            \"access\",\n            \"rack-a1\"\n          ],\n          \"notes\": [\n            \"Cisco Nexus 93180YC-FX\",\n            \"48x25G ports\"\n          ],\n          \"mac\": \"00:1A:2B:3C:5D:01\",\n          \"rackUnit\": 42,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"esxi-host-05\": {\n          \"shape\": \"server\",\n          \"name\": \"ESXi Host 05\",\n          \"ip\": \"10.10.1.11\",\n          \"role\": \"Hypervisor\",\n          \"tags\": [\n            \"vmware\",\n            \"compute\",\n            \"cluster-b\"\n          ],\n          \"notes\": [\n            \"Dell PowerEdge R750\",\n            \"768GB RAM\",\n            \"vSphere 8.0\"\n          ],\n          \"mac\": \"00:50:56:AA:02:01\",\n          \"rackUnit\": 38,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"esxi-host-06\": {\n          \"shape\": \"server\",\n          \"name\": \"ESXi Host 06\",\n          \"ip\": \"10.10.1.12\",\n          \"role\": \"Hypervisor\",\n          \"tags\": [\n            \"vmware\",\n            \"compute\",\n            \"cluster-b\"\n          ],\n          \"notes\": [\n            \"Dell PowerEdge R750\",\n            \"768GB RAM\",\n            \"vSphere 8.0\"\n          ],\n          \"mac\": \"00:50:56:AA:02:02\",\n          \"rackUnit\": 35,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"esxi-host-07\": {\n          \"shape\": \"server\",\n          \"name\": \"ESXi Host 07\",\n          \"ip\": \"10.10.1.13\",\n          \"role\": \"Hypervisor\",\n          \"tags\": [\n            \"vmware\",\n            \"compute\",\n            \"cluster-b\"\n          ],\n          \"notes\": [\n            \"Dell PowerEdge R750\",\n            \"768GB RAM\",\n            \"vSphere 8.0\"\n          ],\n          \"mac\": \"00:50:56:AA:02:03\",\n          \"rackUnit\": 32,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"esxi-host-08\": {\n          \"shape\": \"server\",\n          \"name\": \"ESXi Host 08\",\n          \"ip\": \"10.10.1.14\",\n          \"role\": \"Hypervisor\",\n          \"tags\": [\n            \"vmware\",\n            \"compute\",\n            \"cluster-b\"\n          ],\n          \"notes\": [\n            \"Dell PowerEdge R750\",\n            \"768GB RAM\",\n            \"vSphere 8.0\"\n          ],\n          \"mac\": \"00:50:56:AA:02:04\",\n          \"rackUnit\": 29,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"tor-switch-a2\": {\n          \"shape\": \"switch\",\n          \"name\": \"ToR Switch A2\",\n          \"ip\": \"10.10.1.1\",\n          \"role\": \"Top of Rack\",\n          \"tags\": [\n            \"tor\",\n            \"access\",\n            \"rack-a2\"\n          ],\n          \"notes\": [\n            \"Cisco Nexus 93180YC-FX\",\n            \"48x25G ports\"\n          ],\n          \"mac\": \"00:1A:2B:3C:5D:02\",\n          \"rackUnit\": 42,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"san-primary\": {\n          \"shape\": \"database\",\n          \"name\": \"SAN Primary\",\n          \"ip\": \"10.10.2.10\",\n          \"role\": \"Primary Storage\",\n          \"tags\": [\n            \"storage\",\n            \"san\",\n            \"netapp\"\n          ],\n          \"notes\": [\n            \"NetApp AFF A400\",\n            \"500TB Raw\",\n            \"FC 32Gb\"\n          ],\n          \"mac\": \"00:A0:98:AA:01:01\",\n          \"rackUnit\": 36,\n          \"uHeight\": \"6\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-b1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"san-secondary\": {\n          \"shape\": \"database\",\n          \"name\": \"SAN Secondary\",\n          \"ip\": \"10.10.2.11\",\n          \"role\": \"Secondary Storage\",\n          \"tags\": [\n            \"storage\",\n            \"san\",\n            \"netapp\"\n          ],\n          \"notes\": [\n            \"NetApp AFF A400\",\n            \"500TB Raw\",\n            \"FC 32Gb\"\n          ],\n          \"mac\": \"00:A0:98:AA:01:02\",\n          \"rackUnit\": 28,\n          \"uHeight\": \"6\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-b1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"fc-switch-1\": {\n          \"shape\": \"switch\",\n          \"name\": \"FC Switch 1\",\n          \"ip\": \"10.10.2.1\",\n          \"role\": \"Fibre Channel\",\n          \"tags\": [\n            \"storage\",\n            \"fc\",\n            \"fabric-a\"\n          ],\n          \"notes\": [\n            \"Brocade G620\",\n            \"Fabric A\"\n          ],\n          \"mac\": \"00:1A:2B:FC:01:01\",\n          \"rackUnit\": 42,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-b1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"fc-switch-2\": {\n          \"shape\": \"switch\",\n          \"name\": \"FC Switch 2\",\n          \"ip\": \"10.10.2.2\",\n          \"role\": \"Fibre Channel\",\n          \"tags\": [\n            \"storage\",\n            \"fc\",\n            \"fabric-b\"\n          ],\n          \"notes\": [\n            \"Brocade G620\",\n            \"Fabric B\"\n          ],\n          \"mac\": \"00:1A:2B:FC:01:02\",\n          \"rackUnit\": 41,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-b1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"backup-server-1\": {\n          \"shape\": \"server\",\n          \"name\": \"Backup Server 1\",\n          \"ip\": \"10.10.3.10\",\n          \"role\": \"Backup Infrastructure\",\n          \"tags\": [\n            \"backup\",\n            \"veeam\",\n            \"protection\"\n          ],\n          \"notes\": [\n            \"Veeam Backup Server\",\n            \"Dell R740xd\",\n            \"200TB\"\n          ],\n          \"mac\": \"00:50:56:BB:01:01\",\n          \"rackUnit\": 36,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-b2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"backup-server-2\": {\n          \"shape\": \"server\",\n          \"name\": \"Backup Server 2\",\n          \"ip\": \"10.10.3.11\",\n          \"role\": \"Backup Infrastructure\",\n          \"tags\": [\n            \"backup\",\n            \"veeam\",\n            \"protection\"\n          ],\n          \"notes\": [\n            \"Veeam Backup Server\",\n            \"Dell R740xd\",\n            \"200TB\"\n          ],\n          \"mac\": \"00:50:56:BB:01:02\",\n          \"rackUnit\": 33,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-b2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"tape-library\": {\n          \"shape\": \"database\",\n          \"name\": \"Tape Library\",\n          \"ip\": \"10.10.3.20\",\n          \"role\": \"Archival Storage\",\n          \"tags\": [\n            \"backup\",\n            \"tape\",\n            \"lto9\"\n          ],\n          \"notes\": [\n            \"IBM TS4500\",\n            \"LTO-9\",\n            \"Long-term archive\"\n          ],\n          \"mac\": \"00:50:56:BB:02:01\",\n          \"rackUnit\": 20,\n          \"uHeight\": \"10\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-b2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"tor-switch-b1\": {\n          \"shape\": \"switch\",\n          \"name\": \"ToR Switch B1\",\n          \"ip\": \"10.10.2.3\",\n          \"role\": \"Top of Rack\",\n          \"tags\": [\n            \"tor\",\n            \"access\",\n            \"rack-b1\"\n          ],\n          \"notes\": [\n            \"Cisco Nexus 93180YC-FX\"\n          ],\n          \"mac\": \"00:1A:2B:3C:5D:03\",\n          \"rackUnit\": 40,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-b1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"tor-switch-b2\": {\n          \"shape\": \"switch\",\n          \"name\": \"ToR Switch B2\",\n          \"ip\": \"10.10.3.1\",\n          \"role\": \"Top of Rack\",\n          \"tags\": [\n            \"tor\",\n            \"access\",\n            \"rack-b2\"\n          ],\n          \"notes\": [\n            \"Cisco Nexus 93180YC-FX\"\n          ],\n          \"mac\": \"00:1A:2B:3C:5D:04\",\n          \"rackUnit\": 42,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-b2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"web-server-1\": {\n          \"shape\": \"server\",\n          \"name\": \"Web Server 1\",\n          \"ip\": \"172.16.0.11\",\n          \"role\": \"Web Frontend\",\n          \"tags\": [\n            \"dmz\",\n            \"web\",\n            \"nginx\"\n          ],\n          \"notes\": [\n            \"NGINX reverse proxy\",\n            \"Public facing\"\n          ],\n          \"mac\": \"00:50:56:CC:01:01\",\n          \"rackUnit\": 20,\n          \"uHeight\": \"1\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"dmz-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"web-server-2\": {\n          \"shape\": \"server\",\n          \"name\": \"Web Server 2\",\n          \"ip\": \"172.16.0.12\",\n          \"role\": \"Web Frontend\",\n          \"tags\": [\n            \"dmz\",\n            \"web\",\n            \"nginx\"\n          ],\n          \"notes\": [\n            \"NGINX reverse proxy\",\n            \"Public facing\"\n          ],\n          \"mac\": \"00:50:56:CC:01:02\",\n          \"rackUnit\": 18,\n          \"uHeight\": \"1\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"dmz-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"waf-1\": {\n          \"shape\": \"firewall\",\n          \"name\": \"WAF Appliance\",\n          \"ip\": \"172.16.0.5\",\n          \"role\": \"Web Application Firewall\",\n          \"tags\": [\n            \"dmz\",\n            \"security\",\n            \"waf\"\n          ],\n          \"notes\": [\n            \"F5 BIG-IP ASM\",\n            \"OWASP protection\"\n          ],\n          \"mac\": \"00:50:56:CC:02:01\",\n          \"rackUnit\": 22,\n          \"uHeight\": \"2\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"dmz-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"load-balancer-dmz\": {\n          \"shape\": \"switch\",\n          \"name\": \"DMZ Load Balancer\",\n          \"ip\": \"172.16.0.3\",\n          \"role\": \"Load Balancing\",\n          \"tags\": [\n            \"dmz\",\n            \"lb\",\n            \"f5\"\n          ],\n          \"notes\": [\n            \"F5 BIG-IP LTM\",\n            \"VIP: 172.16.0.100\"\n          ],\n          \"mac\": \"00:50:56:CC:03:01\",\n          \"rackUnit\": 16,\n          \"uHeight\": \"2\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"dmz-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"mail-gateway\": {\n          \"shape\": \"server\",\n          \"name\": \"Mail Gateway\",\n          \"ip\": \"172.16.0.25\",\n          \"role\": \"Email Security\",\n          \"tags\": [\n            \"dmz\",\n            \"email\",\n            \"security\"\n          ],\n          \"notes\": [\n            \"Proofpoint Email Gateway\",\n            \"Spam/malware filtering\"\n          ],\n          \"mac\": \"00:50:56:CC:04:01\",\n          \"rackUnit\": 14,\n          \"uHeight\": \"1\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"dmz-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dns-external-1\": {\n          \"shape\": \"circle\",\n          \"name\": \"External DNS 1\",\n          \"ip\": \"172.16.0.53\",\n          \"role\": \"External DNS\",\n          \"tags\": [\n            \"dmz\",\n            \"dns\",\n            \"public\"\n          ],\n          \"notes\": [\n            \"BIND DNS\",\n            \"Authoritative for corp.com\"\n          ],\n          \"mac\": \"00:50:56:CC:05:01\",\n          \"rackUnit\": 12,\n          \"uHeight\": \"1\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"dmz-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dns-external-2\": {\n          \"shape\": \"circle\",\n          \"name\": \"External DNS 2\",\n          \"ip\": \"172.16.0.54\",\n          \"role\": \"External DNS\",\n          \"tags\": [\n            \"dmz\",\n            \"dns\",\n            \"public\"\n          ],\n          \"notes\": [\n            \"BIND DNS\",\n            \"Secondary for corp.com\"\n          ],\n          \"mac\": \"00:50:56:CC:05:02\",\n          \"rackUnit\": 10,\n          \"uHeight\": \"1\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"dmz-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"vcenter\": {\n          \"shape\": \"server\",\n          \"name\": \"vCenter Server\",\n          \"ip\": \"192.168.100.10\",\n          \"role\": \"Virtualization Management\",\n          \"tags\": [\n            \"management\",\n            \"vmware\",\n            \"vcsa\"\n          ],\n          \"notes\": [\n            \"vCenter Server Appliance 8.0\",\n            \"Single SSO domain\"\n          ],\n          \"mac\": \"00:50:56:DD:01:01\",\n          \"rackUnit\": 20,\n          \"uHeight\": \"2\",\n          \"layer\": \"logical\",\n          \"assignedRack\": \"mgmt-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"nsx-manager\": {\n          \"shape\": \"server\",\n          \"name\": \"NSX Manager\",\n          \"ip\": \"192.168.100.15\",\n          \"role\": \"Network Virtualization\",\n          \"tags\": [\n            \"management\",\n            \"vmware\",\n            \"nsx\"\n          ],\n          \"notes\": [\n            \"NSX-T 4.1 Manager Cluster\"\n          ],\n          \"mac\": \"00:50:56:DD:02:01\",\n          \"rackUnit\": 17,\n          \"uHeight\": \"2\",\n          \"layer\": \"logical\",\n          \"assignedRack\": \"mgmt-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"siem-server\": {\n          \"shape\": \"server\",\n          \"name\": \"SIEM Server\",\n          \"ip\": \"192.168.100.50\",\n          \"role\": \"Security Monitoring\",\n          \"tags\": [\n            \"management\",\n            \"security\",\n            \"splunk\"\n          ],\n          \"notes\": [\n            \"Splunk Enterprise\",\n            \"Security monitoring\"\n          ],\n          \"mac\": \"00:50:56:DD:03:01\",\n          \"rackUnit\": 14,\n          \"uHeight\": \"2\",\n          \"layer\": \"logical\",\n          \"assignedRack\": \"mgmt-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"nms-server\": {\n          \"shape\": \"server\",\n          \"name\": \"Network Monitoring\",\n          \"ip\": \"192.168.100.60\",\n          \"role\": \"Network Management\",\n          \"tags\": [\n            \"management\",\n            \"monitoring\",\n            \"prtg\"\n          ],\n          \"notes\": [\n            \"PRTG Network Monitor\",\n            \"5000 sensors\"\n          ],\n          \"mac\": \"00:50:56:DD:04:01\",\n          \"rackUnit\": 11,\n          \"uHeight\": \"1\",\n          \"layer\": \"logical\",\n          \"assignedRack\": \"mgmt-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"jump-server\": {\n          \"shape\": \"server\",\n          \"name\": \"Jump Server\",\n          \"ip\": \"192.168.100.100\",\n          \"role\": \"Bastion Host\",\n          \"tags\": [\n            \"management\",\n            \"security\",\n            \"bastion\"\n          ],\n          \"notes\": [\n            \"Windows Server 2022\",\n            \"MFA enabled\"\n          ],\n          \"mac\": \"00:50:56:DD:05:01\",\n          \"rackUnit\": 9,\n          \"uHeight\": \"1\",\n          \"layer\": \"logical\",\n          \"assignedRack\": \"mgmt-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"ipam-server\": {\n          \"shape\": \"server\",\n          \"name\": \"IPAM/DDI\",\n          \"ip\": \"192.168.100.70\",\n          \"role\": \"IP Management\",\n          \"tags\": [\n            \"management\",\n            \"dns\",\n            \"dhcp\"\n          ],\n          \"notes\": [\n            \"Infoblox DDI\",\n            \"DNS/DHCP/IPAM\"\n          ],\n          \"mac\": \"00:50:56:DD:06:01\",\n          \"rackUnit\": 7,\n          \"uHeight\": \"2\",\n          \"layer\": \"logical\",\n          \"assignedRack\": \"mgmt-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"wlc-primary\": {\n          \"shape\": \"wifi\",\n          \"name\": \"WLC Primary\",\n          \"ip\": \"10.20.0.1\",\n          \"role\": \"Wireless Controller\",\n          \"tags\": [\n            \"wireless\",\n            \"cisco\",\n            \"9800\"\n          ],\n          \"notes\": [\n            \"Cisco C9800-40\",\n            \"Primary controller\"\n          ],\n          \"mac\": \"00:1A:2B:WL:01:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"wlc-secondary\": {\n          \"shape\": \"wifi\",\n          \"name\": \"WLC Secondary\",\n          \"ip\": \"10.20.0.2\",\n          \"role\": \"Wireless Controller\",\n          \"tags\": [\n            \"wireless\",\n            \"cisco\",\n            \"9800\"\n          ],\n          \"notes\": [\n            \"Cisco C9800-40\",\n            \"HA Secondary\"\n          ],\n          \"mac\": \"00:1A:2B:WL:01:02\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"mobile-zone-hq\": {\n          \"shape\": \"phone\",\n          \"name\": \"HQ Mobile Zone\",\n          \"ip\": \"10.20.10.0/24\",\n          \"role\": \"Mobile Device Zone\",\n          \"tags\": [\n            \"wireless\",\n            \"byod\",\n            \"mobile\"\n          ],\n          \"notes\": [\n            \"Corporate BYOD\",\n            \"MDM enrolled devices\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"mobile-zone-guest\": {\n          \"shape\": \"phone\",\n          \"name\": \"Guest WiFi Zone\",\n          \"ip\": \"10.30.0.0/24\",\n          \"role\": \"Guest Network\",\n          \"tags\": [\n            \"wireless\",\n            \"guest\",\n            \"isolated\"\n          ],\n          \"notes\": [\n            \"Captive portal\",\n            \"Internet only\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"mobile-zone-iot\": {\n          \"shape\": \"phone\",\n          \"name\": \"IoT Device Zone\",\n          \"ip\": \"10.40.0.0/24\",\n          \"role\": \"IoT Network\",\n          \"tags\": [\n            \"wireless\",\n            \"iot\",\n            \"building\"\n          ],\n          \"notes\": [\n            \"Building automation\",\n            \"Smart devices\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"branch-router-ny\": {\n          \"shape\": \"router\",\n          \"name\": \"NYC Branch Router\",\n          \"ip\": \"10.100.0.1\",\n          \"role\": \"Branch Gateway\",\n          \"tags\": [\n            \"branch\",\n            \"nyc\",\n            \"sd-wan\"\n          ],\n          \"notes\": [\n            \"Cisco Viptela vEdge\",\n            \"SD-WAN enabled\"\n          ],\n          \"mac\": \"00:1A:2B:BR:01:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"branch-router-la\": {\n          \"shape\": \"router\",\n          \"name\": \"LA Branch Router\",\n          \"ip\": \"10.101.0.1\",\n          \"role\": \"Branch Gateway\",\n          \"tags\": [\n            \"branch\",\n            \"la\",\n            \"sd-wan\"\n          ],\n          \"notes\": [\n            \"Cisco Viptela vEdge\",\n            \"SD-WAN enabled\"\n          ],\n          \"mac\": \"00:1A:2B:BR:02:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"branch-router-chi\": {\n          \"shape\": \"router\",\n          \"name\": \"Chicago Branch Router\",\n          \"ip\": \"10.102.0.1\",\n          \"role\": \"Branch Gateway\",\n          \"tags\": [\n            \"branch\",\n            \"chicago\",\n            \"sd-wan\"\n          ],\n          \"notes\": [\n            \"Cisco Viptela vEdge\",\n            \"SD-WAN enabled\"\n          ],\n          \"mac\": \"00:1A:2B:BR:03:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"branch-router-lon\": {\n          \"shape\": \"router\",\n          \"name\": \"London Branch Router\",\n          \"ip\": \"10.200.0.1\",\n          \"role\": \"Branch Gateway\",\n          \"tags\": [\n            \"branch\",\n            \"london\",\n            \"sd-wan\"\n          ],\n          \"notes\": [\n            \"Cisco Viptela vEdge\",\n            \"EMEA region\"\n          ],\n          \"mac\": \"00:1A:2B:BR:04:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"branch-router-tokyo\": {\n          \"shape\": \"router\",\n          \"name\": \"Tokyo Branch Router\",\n          \"ip\": \"10.201.0.1\",\n          \"role\": \"Branch Gateway\",\n          \"tags\": [\n            \"branch\",\n            \"tokyo\",\n            \"sd-wan\"\n          ],\n          \"notes\": [\n            \"Cisco Viptela vEdge\",\n            \"APAC region\"\n          ],\n          \"mac\": \"00:1A:2B:BR:05:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"cloud-aws\": {\n          \"shape\": \"cloud\",\n          \"name\": \"AWS Cloud\",\n          \"ip\": \"vpc-0a1b2c3d\",\n          \"role\": \"Public Cloud\",\n          \"tags\": [\n            \"cloud\",\n            \"aws\",\n            \"hybrid\"\n          ],\n          \"notes\": [\n            \"AWS US-East-1\",\n            \"VPC peering to HQ\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"logical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"cloud-azure\": {\n          \"shape\": \"cloud\",\n          \"name\": \"Azure Cloud\",\n          \"ip\": \"vnet-corp-prod\",\n          \"role\": \"Public Cloud\",\n          \"tags\": [\n            \"cloud\",\n            \"azure\",\n            \"hybrid\"\n          ],\n          \"notes\": [\n            \"Azure East US 2\",\n            \"ExpressRoute\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"logical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"cloud-gcp\": {\n          \"shape\": \"cloud\",\n          \"name\": \"GCP Cloud\",\n          \"ip\": \"vpc-gcp-corp\",\n          \"role\": \"Public Cloud\",\n          \"tags\": [\n            \"cloud\",\n            \"gcp\",\n            \"dev\"\n          ],\n          \"notes\": [\n            \"GCP us-central1\",\n            \"Dev/Test workloads\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"logical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"isp-primary\": {\n          \"shape\": \"globe\",\n          \"name\": \"ISP Primary\",\n          \"ip\": \"203.0.113.1\",\n          \"role\": \"Internet Uplink\",\n          \"tags\": [\n            \"wan\",\n            \"internet\",\n            \"primary\"\n          ],\n          \"notes\": [\n            \"AT&T MPLS\",\n            \"1 Gbps dedicated\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"isp-secondary\": {\n          \"shape\": \"vm\",\n          \"name\": \"ISP Secondary\",\n          \"ip\": \"198.51.100.1\",\n          \"role\": \"Internet Uplink\",\n          \"tags\": [\n            \"wan\",\n            \"internet\",\n            \"backup\"\n          ],\n          \"notes\": [\n            \"Verizon Business\",\n            \"500 Mbps backup\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null,\n          \"rotation\": -17\n        },\n        \"dc-internal-1\": {\n          \"shape\": \"circle\",\n          \"name\": \"DC1 Int DNS\",\n          \"ip\": \"10.10.0.53\",\n          \"role\": \"Internal DNS/AD\",\n          \"tags\": [\n            \"dns\",\n            \"ad\",\n            \"dc1\"\n          ],\n          \"notes\": [\n            \"Windows Server 2022\",\n            \"Primary DC\"\n          ],\n          \"mac\": \"00:50:56:AD:01:01\",\n          \"rackUnit\": 26,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dc-internal-2\": {\n          \"shape\": \"circle\",\n          \"name\": \"DC2 Int DNS\",\n          \"ip\": \"10.10.1.53\",\n          \"role\": \"Internal DNS/AD\",\n          \"tags\": [\n            \"dns\",\n            \"ad\",\n            \"dc2\"\n          ],\n          \"notes\": [\n            \"Windows Server 2022\",\n            \"Secondary DC\"\n          ],\n          \"mac\": \"00:50:56:AD:01:02\",\n          \"rackUnit\": 26,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"app-server-1\": {\n          \"shape\": \"server\",\n          \"name\": \"App Server 01\",\n          \"ip\": \"10.10.0.101\",\n          \"role\": \"Application\",\n          \"tags\": [\n            \"app\",\n            \"iis\",\n            \"web\"\n          ],\n          \"notes\": [\n            \"Windows Server 2022\",\n            \"IIS Application\"\n          ],\n          \"mac\": \"00:50:56:AP:01:01\",\n          \"rackUnit\": 24,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"app-server-2\": {\n          \"shape\": \"server\",\n          \"name\": \"App Server 02\",\n          \"ip\": \"10.10.0.102\",\n          \"role\": \"Application\",\n          \"tags\": [\n            \"app\",\n            \"iis\",\n            \"web\"\n          ],\n          \"notes\": [\n            \"Windows Server 2022\",\n            \"IIS Application\"\n          ],\n          \"mac\": \"00:50:56:AP:01:02\",\n          \"rackUnit\": 22,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"db-server-1\": {\n          \"shape\": \"database\",\n          \"name\": \"SQL Server 01\",\n          \"ip\": \"10.10.0.201\",\n          \"role\": \"Database\",\n          \"tags\": [\n            \"db\",\n            \"sql\",\n            \"primary\"\n          ],\n          \"notes\": [\n            \"SQL Server 2022 Enterprise\",\n            \"AlwaysOn Primary\"\n          ],\n          \"mac\": \"00:50:56:DB:01:01\",\n          \"rackUnit\": 20,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"db-server-2\": {\n          \"shape\": \"database\",\n          \"name\": \"SQL Server 02\",\n          \"ip\": \"10.10.1.201\",\n          \"role\": \"Database\",\n          \"tags\": [\n            \"db\",\n            \"sql\",\n            \"secondary\"\n          ],\n          \"notes\": [\n            \"SQL Server 2022 Enterprise\",\n            \"AlwaysOn Secondary\"\n          ],\n          \"mac\": \"00:50:56:DB:01:02\",\n          \"rackUnit\": 24,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"k8s-master-1\": {\n          \"shape\": \"hexagon\",\n          \"name\": \"K8s Master 1\",\n          \"ip\": \"10.10.1.50\",\n          \"role\": \"Container Orchestration\",\n          \"tags\": [\n            \"kubernetes\",\n            \"master\",\n            \"container\"\n          ],\n          \"notes\": [\n            \"K8s Control Plane\",\n            \"etcd member\"\n          ],\n          \"mac\": \"00:50:56:K8:01:01\",\n          \"rackUnit\": 21,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"k8s-master-2\": {\n          \"shape\": \"hexagon\",\n          \"name\": \"K8s Master 2\",\n          \"ip\": \"10.10.1.51\",\n          \"role\": \"Container Orchestration\",\n          \"tags\": [\n            \"kubernetes\",\n            \"master\",\n            \"container\"\n          ],\n          \"notes\": [\n            \"K8s Control Plane\",\n            \"etcd member\"\n          ],\n          \"mac\": \"00:50:56:K8:01:02\",\n          \"rackUnit\": 19,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"k8s-master-3\": {\n          \"shape\": \"hexagon\",\n          \"name\": \"K8s Master 3\",\n          \"ip\": \"10.10.1.52\",\n          \"role\": \"Container Orchestration\",\n          \"tags\": [\n            \"kubernetes\",\n            \"master\",\n            \"container\"\n          ],\n          \"notes\": [\n            \"K8s Control Plane\",\n            \"etcd member\"\n          ],\n          \"mac\": \"00:50:56:K8:01:03\",\n          \"rackUnit\": 17,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"k8s-worker-1\": {\n          \"shape\": \"server\",\n          \"name\": \"K8s Worker 1\",\n          \"ip\": \"10.10.1.60\",\n          \"role\": \"Container Workload\",\n          \"tags\": [\n            \"kubernetes\",\n            \"worker\",\n            \"container\"\n          ],\n          \"notes\": [\n            \"K8s Worker Node\",\n            \"64GB RAM\"\n          ],\n          \"mac\": \"00:50:56:K8:02:01\",\n          \"rackUnit\": 15,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"k8s-worker-2\": {\n          \"shape\": \"server\",\n          \"name\": \"K8s Worker 2\",\n          \"ip\": \"10.10.1.61\",\n          \"role\": \"Container Workload\",\n          \"tags\": [\n            \"kubernetes\",\n            \"worker\",\n            \"container\"\n          ],\n          \"notes\": [\n            \"K8s Worker Node\",\n            \"64GB RAM\"\n          ],\n          \"mac\": \"00:50:56:K8:02:02\",\n          \"rackUnit\": 13,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"k8s-worker-3\": {\n          \"shape\": \"server\",\n          \"name\": \"K8s Worker 3\",\n          \"ip\": \"10.10.1.62\",\n          \"role\": \"Container Workload\",\n          \"tags\": [\n            \"kubernetes\",\n            \"worker\",\n            \"container\"\n          ],\n          \"notes\": [\n            \"K8s Worker Node\",\n            \"64GB RAM\"\n          ],\n          \"mac\": \"00:50:56:K8:02:03\",\n          \"rackUnit\": 11,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"k8s-worker-4\": {\n          \"shape\": \"server\",\n          \"name\": \"K8s Worker 4\",\n          \"ip\": \"10.10.1.63\",\n          \"role\": \"Container Workload\",\n          \"tags\": [\n            \"kubernetes\",\n            \"worker\",\n            \"container\"\n          ],\n          \"notes\": [\n            \"K8s Worker Node\",\n            \"64GB RAM\"\n          ],\n          \"mac\": \"00:50:56:K8:02:04\",\n          \"rackUnit\": 9,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"proxy-server-1\": {\n          \"shape\": \"server\",\n          \"name\": \"Proxy Server 1\",\n          \"ip\": \"10.5.0.10\",\n          \"role\": \"Web Proxy\",\n          \"tags\": [\n            \"proxy\",\n            \"squid\",\n            \"filtering\"\n          ],\n          \"notes\": [\n            \"Squid Proxy\",\n            \"Content filtering\"\n          ],\n          \"mac\": \"00:50:56:PX:01:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"proxy-server-2\": {\n          \"shape\": \"server\",\n          \"name\": \"Proxy Server 2\",\n          \"ip\": \"10.5.0.11\",\n          \"role\": \"Web Proxy\",\n          \"tags\": [\n            \"proxy\",\n            \"squid\",\n            \"filtering\"\n          ],\n          \"notes\": [\n            \"Squid Proxy\",\n            \"HA pair\"\n          ],\n          \"mac\": \"00:50:56:PX:01:02\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"vpn-concentrator\": {\n          \"shape\": \"firewall\",\n          \"name\": \"VPN Concentrator\",\n          \"ip\": \"10.0.5.1\",\n          \"role\": \"Remote Access VPN\",\n          \"tags\": [\n            \"vpn\",\n            \"remote\",\n            \"security\"\n          ],\n          \"notes\": [\n            \"Cisco ASA 5555-X\",\n            \"AnyConnect SSL VPN\"\n          ],\n          \"mac\": \"00:1A:2B:VP:01:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"nac-server\": {\n          \"shape\": \"server\",\n          \"name\": \"NAC Server\",\n          \"ip\": \"10.5.5.10\",\n          \"role\": \"Network Access Control\",\n          \"tags\": [\n            \"nac\",\n            \"ise\",\n            \"802.1x\"\n          ],\n          \"notes\": [\n            \"Cisco ISE 3.1\",\n            \"RADIUS/TACACS+\"\n          ],\n          \"mac\": \"00:50:56:NA:01:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"print-server\": {\n          \"shape\": \"server\",\n          \"name\": \"Print Server\",\n          \"ip\": \"10.10.0.150\",\n          \"role\": \"Print Services\",\n          \"tags\": [\n            \"print\",\n            \"windows\",\n            \"services\"\n          ],\n          \"notes\": [\n            \"Windows Print Server\",\n            \"50+ printers\"\n          ],\n          \"mac\": \"00:50:56:PR:01:01\",\n          \"rackUnit\": 18,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"file-server\": {\n          \"shape\": \"database\",\n          \"name\": \"File Server\",\n          \"ip\": \"10.10.0.160\",\n          \"role\": \"File Services\",\n          \"tags\": [\n            \"file\",\n            \"smb\",\n            \"dfs\"\n          ],\n          \"notes\": [\n            \"Windows File Server\",\n            \"DFS namespace\"\n          ],\n          \"mac\": \"00:50:56:FS:01:01\",\n          \"rackUnit\": 16,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"ca-server\": {\n          \"shape\": \"server\",\n          \"name\": \"Certificate Authority\",\n          \"ip\": \"192.168.100.80\",\n          \"role\": \"PKI Infrastructure\",\n          \"tags\": [\n            \"pki\",\n            \"ca\",\n            \"security\"\n          ],\n          \"notes\": [\n            \"Windows CA\",\n            \"Enterprise Root CA\"\n          ],\n          \"mac\": \"00:50:56:CA:01:01\",\n          \"rackUnit\": 5,\n          \"uHeight\": \"1\",\n          \"layer\": \"logical\",\n          \"assignedRack\": \"mgmt-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"sccm-server\": {\n          \"shape\": \"server\",\n          \"name\": \"SCCM Server\",\n          \"ip\": \"192.168.100.90\",\n          \"role\": \"Endpoint Management\",\n          \"tags\": [\n            \"sccm\",\n            \"patching\",\n            \"software\"\n          ],\n          \"notes\": [\n            \"MECM Primary Site\",\n            \"Software deployment\"\n          ],\n          \"mac\": \"00:50:56:SC:01:01\",\n          \"rackUnit\": 3,\n          \"uHeight\": \"2\",\n          \"layer\": \"logical\",\n          \"assignedRack\": \"mgmt-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"voip-cluster\": {\n          \"shape\": \"phone\",\n          \"name\": \"VoIP Cluster\",\n          \"ip\": \"10.50.0.0/24\",\n          \"role\": \"Voice Services\",\n          \"tags\": [\n            \"voip\",\n            \"cisco\",\n            \"ucm\"\n          ],\n          \"notes\": [\n            \"Cisco UCM Cluster\",\n            \"3000 endpoints\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"application\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"video-conf\": {\n          \"shape\": \"laptop\",\n          \"name\": \"Video Conference\",\n          \"ip\": \"10.51.0.0/24\",\n          \"role\": \"Video Services\",\n          \"tags\": [\n            \"video\",\n            \"webex\",\n            \"teams\"\n          ],\n          \"notes\": [\n            \"Webex/Teams integration\",\n            \"Meeting rooms\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"application\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"security-cameras\": {\n          \"shape\": \"camera\",\n          \"name\": \"Security Cameras\",\n          \"ip\": \"10.60.0.0/24\",\n          \"role\": \"Physical Security\",\n          \"tags\": [\n            \"cctv\",\n            \"surveillance\",\n            \"security\"\n          ],\n          \"notes\": [\n            \"150+ IP cameras\",\n            \"30-day retention\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"nvr-cluster\": {\n          \"shape\": \"server\",\n          \"name\": \"NVR Cluster\",\n          \"ip\": \"10.60.0.10\",\n          \"role\": \"Video Recording\",\n          \"tags\": [\n            \"nvr\",\n            \"surveillance\",\n            \"storage\"\n          ],\n          \"notes\": [\n            \"Milestone XProtect\",\n            \"500TB storage\"\n          ],\n          \"mac\": \"00:50:56:NV:01:01\",\n          \"rackUnit\": 15,\n          \"uHeight\": \"4\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-b2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dev-server-1\": {\n          \"shape\": \"server\",\n          \"name\": \"Dev Server 1\",\n          \"ip\": \"10.80.0.10\",\n          \"role\": \"Development\",\n          \"tags\": [\n            \"dev\",\n            \"gitlab\",\n            \"ci-cd\",\n            {\n              \"type\": \"icon\",\n              \"library\": \"selfhst\",\n              \"name\": \"dokku\"\n            }\n          ],\n          \"notes\": [\n            \"GitLab Server\",\n            \"CI/CD pipelines\"\n          ],\n          \"mac\": \"00:50:56:DV:01:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"application\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dev-server-2\": {\n          \"shape\": \"server\",\n          \"name\": \"Dev Server 2\",\n          \"ip\": \"10.80.0.11\",\n          \"role\": \"Development\",\n          \"tags\": [\n            \"dev\",\n            \"jenkins\",\n            \"ci-cd\"\n          ],\n          \"notes\": [\n            \"Jenkins Server\",\n            \"Build automation\"\n          ],\n          \"mac\": \"00:50:56:DV:01:02\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"application\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"test-environment\": {\n          \"shape\": \"shield\",\n          \"name\": \"Test Environment\",\n          \"ip\": \"10.81.0.0/24\",\n          \"role\": \"QA/Testing\",\n          \"tags\": [\n            \"test\",\n            \"qa\",\n            \"staging\"\n          ],\n          \"notes\": [\n            \"Staging environment\",\n            \"Pre-prod validation\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"application\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null,\n          \"rotation\": -36\n        },\n        \"erp-system\": {\n          \"shape\": \"database\",\n          \"name\": \"ERP System\",\n          \"ip\": \"10.90.0.10\",\n          \"role\": \"Business Application\",\n          \"tags\": [\n            \"erp\",\n            \"sap\",\n            \"business\"\n          ],\n          \"notes\": [\n            \"SAP S/4HANA\",\n            \"Financial/HR systems\"\n          ],\n          \"mac\": \"00:50:56:ER:01:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"4\",\n          \"layer\": \"application\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"crm-system\": {\n          \"shape\": \"database\",\n          \"name\": \"CRM System\",\n          \"ip\": \"10.91.0.10\",\n          \"role\": \"Business Application\",\n          \"tags\": [\n            \"crm\",\n            \"salesforce\",\n            \"business\"\n          ],\n          \"notes\": [\n            \"Salesforce integration\",\n            \"Sales/Marketing\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"application\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"endpoint-1000\": {\n          \"shape\": \"laptop\",\n          \"name\": \"Corporate Endpoints\",\n          \"ip\": \"10.70.0.0/22\",\n          \"role\": \"User Workstations\",\n          \"tags\": [\n            \"endpoints\",\n            \"workstations\",\n            \"users\"\n          ],\n          \"notes\": [\n            \"~1000 corporate laptops\",\n            \"Windows 11\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dist-switch-floor1\": {\n          \"shape\": \"switch\",\n          \"name\": \"Floor 1 Switch\",\n          \"ip\": \"10.1.1.1\",\n          \"role\": \"Distribution\",\n          \"tags\": [\n            \"distribution\",\n            \"floor-1\",\n            \"access\"\n          ],\n          \"notes\": [\n            \"Cisco C9300-48P\",\n            \"PoE+ enabled\"\n          ],\n          \"mac\": \"00:1A:2B:FL:01:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dist-switch-floor2\": {\n          \"shape\": \"switch\",\n          \"name\": \"Floor 2 Switch\",\n          \"ip\": \"10.1.2.1\",\n          \"role\": \"Distribution\",\n          \"tags\": [\n            \"distribution\",\n            \"floor-2\",\n            \"access\"\n          ],\n          \"notes\": [\n            \"Cisco C9300-48P\",\n            \"PoE+ enabled\"\n          ],\n          \"mac\": \"00:1A:2B:FL:02:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dist-switch-floor3\": {\n          \"shape\": \"switch\",\n          \"name\": \"Floor 3 Switch\",\n          \"ip\": \"10.1.3.1\",\n          \"role\": \"Distribution\",\n          \"tags\": [\n            \"distribution\",\n            \"floor-3\",\n            \"access\"\n          ],\n          \"notes\": [\n            \"Cisco C9300-48P\",\n            \"PoE+ enabled\"\n          ],\n          \"mac\": \"00:1A:2B:FL:03:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dist-switch-floor4\": {\n          \"shape\": \"switch\",\n          \"name\": \"Floor 4 Switch\",\n          \"ip\": \"10.1.4.1\",\n          \"role\": \"Distribution\",\n          \"tags\": [\n            \"distribution\",\n            \"floor-4\",\n            \"access\"\n          ],\n          \"notes\": [\n            \"Cisco C9300-48P\",\n            \"PoE+ enabled\"\n          ],\n          \"mac\": \"00:1A:2B:FL:04:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"ap-floor1-zone1\": {\n          \"shape\": \"wifi\",\n          \"name\": \"AP Floor 1 Zone 1\",\n          \"ip\": \"10.20.1.10\",\n          \"role\": \"Wireless Access\",\n          \"tags\": [\n            \"wifi\",\n            \"ap\",\n            \"floor-1\"\n          ],\n          \"notes\": [\n            \"Cisco 9120AX\",\n            \"Wi-Fi 6\"\n          ],\n          \"mac\": \"00:1A:2B:AP:01:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"ap-floor2-zone1\": {\n          \"shape\": \"wifi\",\n          \"name\": \"AP Floor 2 Zone 1\",\n          \"ip\": \"10.20.2.10\",\n          \"role\": \"Wireless Access\",\n          \"tags\": [\n            \"wifi\",\n            \"ap\",\n            \"floor-2\"\n          ],\n          \"notes\": [\n            \"Cisco 9120AX\",\n            \"Wi-Fi 6\"\n          ],\n          \"mac\": \"00:1A:2B:AP:02:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"ap-floor3-zone1\": {\n          \"shape\": \"wifi\",\n          \"name\": \"AP Floor 3 Zone 1\",\n          \"ip\": \"10.20.3.10\",\n          \"role\": \"Wireless Access\",\n          \"tags\": [\n            \"wifi\",\n            \"ap\",\n            \"floor-3\"\n          ],\n          \"notes\": [\n            \"Cisco 9120AX\",\n            \"Wi-Fi 6\"\n          ],\n          \"mac\": \"00:1A:2B:AP:03:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"ap-floor4-zone1\": {\n          \"shape\": \"wifi\",\n          \"name\": \"AP Floor 4 Zone 1\",\n          \"ip\": \"10.20.4.10\",\n          \"role\": \"Wireless Access\",\n          \"tags\": [\n            \"wifi\",\n            \"ap\",\n            \"floor-4\"\n          ],\n          \"notes\": [\n            \"Cisco 9120AX\",\n            \"Wi-Fi 6\"\n          ],\n          \"mac\": \"00:1A:2B:AP:04:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"ups-dc-1\": {\n          \"shape\": \"rectangle\",\n          \"name\": \"UPS DC-1\",\n          \"ip\": \"192.168.200.10\",\n          \"role\": \"Power Management\",\n          \"tags\": [\n            \"power\",\n            \"ups\",\n            \"datacenter\"\n          ],\n          \"notes\": [\n            \"APC Symmetra\",\n            \"80kVA\",\n            \"30 min runtime\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"ups-dc-2\": {\n          \"shape\": \"rectangle\",\n          \"name\": \"UPS DC-2\",\n          \"ip\": \"192.168.200.11\",\n          \"role\": \"Power Management\",\n          \"tags\": [\n            \"power\",\n            \"ups\",\n            \"datacenter\"\n          ],\n          \"notes\": [\n            \"APC Symmetra\",\n            \"80kVA\",\n            \"Redundant\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"pdu-rack-a1\": {\n          \"shape\": \"rectangle\",\n          \"name\": \"PDU Rack A1\",\n          \"ip\": \"192.168.200.21\",\n          \"role\": \"Power Distribution\",\n          \"tags\": [\n            \"power\",\n            \"pdu\",\n            \"rack-a1\"\n          ],\n          \"notes\": [\n            \"APC Switched PDU\",\n            \"Per-outlet metering\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": 1,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"pdu-rack-a2\": {\n          \"shape\": \"rectangle\",\n          \"name\": \"PDU Rack A2\",\n          \"ip\": \"192.168.200.22\",\n          \"role\": \"Power Distribution\",\n          \"tags\": [\n            \"power\",\n            \"pdu\",\n            \"rack-a2\"\n          ],\n          \"notes\": [\n            \"APC Switched PDU\",\n            \"Per-outlet metering\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": 1,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"cooling-1\": {\n          \"shape\": \"rectangle\",\n          \"name\": \"CRAC Unit 1\",\n          \"ip\": \"192.168.200.30\",\n          \"role\": \"Cooling\",\n          \"tags\": [\n            \"cooling\",\n            \"hvac\",\n            \"datacenter\"\n          ],\n          \"notes\": [\n            \"Liebert CRV\",\n            \"Row-based cooling\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"cooling-2\": {\n          \"shape\": \"rectangle\",\n          \"name\": \"CRAC Unit 2\",\n          \"ip\": \"192.168.200.31\",\n          \"role\": \"Cooling\",\n          \"tags\": [\n            \"cooling\",\n            \"hvac\",\n            \"datacenter\"\n          ],\n          \"notes\": [\n            \"Liebert CRV\",\n            \"N+1 redundancy\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"camera-a\": {\n          \"shape\": \"camera\",\n          \"name\": \"camera A\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"ping\": {\n            \"enabled\": false,\n            \"protocol\": \"http\",\n            \"customUrl\": \"\",\n            \"timeout\": 3000,\n            \"status\": \"unknown\",\n            \"lastCheck\": null\n          },\n          \"locked\": false,\n          \"groupId\": null,\n          \"fovEnabled\": true,\n          \"fovRotation\": 104,\n          \"fovDistance\": 500,\n          \"fovSweep\": 60,\n          \"fovSpeed\": 10,\n          \"fovAnimate\": true\n        },\n        \"camera-a-copy\": {\n          \"shape\": \"camera\",\n          \"name\": \"camera B\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"ping\": {\n            \"enabled\": false,\n            \"protocol\": \"http\",\n            \"customUrl\": \"\",\n            \"timeout\": 3000,\n            \"status\": \"unknown\",\n            \"lastCheck\": null\n          },\n          \"locked\": false,\n          \"groupId\": null,\n          \"fovEnabled\": true,\n          \"fovRotation\": 162,\n          \"fovDistance\": 500,\n          \"fovSweep\": 60,\n          \"fovSpeed\": 10,\n          \"fovAnimate\": false\n        }\n      },\n      \"edges\": {\n        \"list\": [\n          {\n            \"id\": \"isp1-router1\",\n            \"from\": \"isp-primary\",\n            \"to\": \"core-router-1\",\n            \"width\": 6,\n            \"color\": \"#10b981\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"Primary WAN link\"\n            ],\n            \"fromPort\": \"Gi0/0\",\n            \"toPort\": \"Gi1/0/1\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"isp2-router2\",\n            \"from\": \"isp-secondary\",\n            \"to\": \"core-router-2\",\n            \"width\": 6,\n            \"color\": \"#10b981\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"Backup WAN link\"\n            ],\n            \"fromPort\": \"Gi0/0\",\n            \"toPort\": \"Gi1/0/1\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"router1-router2\",\n            \"from\": \"core-router-1\",\n            \"to\": \"core-router-2\",\n            \"width\": 4,\n            \"color\": \"#f59e0b\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"HSRP Peering\"\n            ],\n            \"fromPort\": \"Gi1/0/24\",\n            \"toPort\": \"Gi1/0/24\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"router1-fw1\",\n            \"from\": \"core-router-1\",\n            \"to\": \"fw-external-1\",\n            \"width\": 4,\n            \"color\": \"#ef4444\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"router2-fw2\",\n            \"from\": \"core-router-2\",\n            \"to\": \"fw-external-2\",\n            \"width\": 4,\n            \"color\": \"#ef4444\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"fw1-fw2\",\n            \"from\": \"fw-external-1\",\n            \"to\": \"fw-external-2\",\n            \"width\": 3,\n            \"color\": \"#f59e0b\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"HA heartbeat\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dashed\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"fw1-coresw1\",\n            \"from\": \"fw-external-1\",\n            \"to\": \"core-switch-1\",\n            \"width\": 4,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"fw2-coresw2\",\n            \"from\": \"fw-external-2\",\n            \"to\": \"core-switch-2\",\n            \"width\": 4,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-coresw2\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"core-switch-2\",\n            \"width\": 5,\n            \"color\": \"#3b82f6\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"VPC peer-link\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-fwint\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"fw-internal\",\n            \"width\": 3,\n            \"color\": \"#ef4444\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw2-fwint\",\n            \"from\": \"core-switch-2\",\n            \"to\": \"fw-internal\",\n            \"width\": 3,\n            \"color\": \"#ef4444\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-racka1\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"dc-rack-a1\",\n            \"width\": 4,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw2-racka1\",\n            \"from\": \"core-switch-2\",\n            \"to\": \"dc-rack-a1\",\n            \"width\": 4,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-racka2\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"dc-rack-a2\",\n            \"width\": 4,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw2-racka2\",\n            \"from\": \"core-switch-2\",\n            \"to\": \"dc-rack-a2\",\n            \"width\": 4,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-rackb1\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"dc-rack-b1\",\n            \"width\": 4,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw2-rackb1\",\n            \"from\": \"core-switch-2\",\n            \"to\": \"dc-rack-b1\",\n            \"width\": 4,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-rackb2\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"dc-rack-b2\",\n            \"width\": 4,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw2-rackb2\",\n            \"from\": \"core-switch-2\",\n            \"to\": \"dc-rack-b2\",\n            \"width\": 4,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"fw1-dmz\",\n            \"from\": \"fw-external-1\",\n            \"to\": \"dmz-rack\",\n            \"width\": 3,\n            \"color\": \"#f59e0b\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"DMZ segment\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"fw2-dmz\",\n            \"from\": \"fw-external-2\",\n            \"to\": \"dmz-rack\",\n            \"width\": 3,\n            \"color\": \"#f59e0b\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"DMZ segment\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-mgmt\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"mgmt-rack\",\n            \"width\": 3,\n            \"color\": \"#8b5cf6\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"OOB management\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-wlc1\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"wlc-primary\",\n            \"width\": 3,\n            \"color\": \"#06b6d4\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw2-wlc2\",\n            \"from\": \"core-switch-2\",\n            \"to\": \"wlc-secondary\",\n            \"width\": 3,\n            \"color\": \"#06b6d4\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\",\n            \"animate\": true\n          },\n          {\n            \"id\": \"wlc1-wlc2\",\n            \"from\": \"wlc-primary\",\n            \"to\": \"wlc-secondary\",\n            \"width\": 2,\n            \"color\": \"#f59e0b\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"HA pair\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dashed\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"wlc1-mobile-hq\",\n            \"from\": \"wlc-primary\",\n            \"to\": \"mobile-zone-hq\",\n            \"width\": 2,\n            \"color\": \"#06b6d4\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"wlc1-mobile-guest\",\n            \"from\": \"wlc-primary\",\n            \"to\": \"mobile-zone-guest\",\n            \"width\": 2,\n            \"color\": \"#06b6d4\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"wlc1-mobile-iot\",\n            \"from\": \"wlc-primary\",\n            \"to\": \"mobile-zone-iot\",\n            \"width\": 2,\n            \"color\": \"#06b6d4\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"router1-branch-ny\",\n            \"from\": \"core-router-1\",\n            \"to\": \"branch-router-ny\",\n            \"width\": 3,\n            \"color\": \"#a855f7\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"SD-WAN tunnel\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dashed\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"router1-branch-la\",\n            \"from\": \"core-router-1\",\n            \"to\": \"branch-router-la\",\n            \"width\": 3,\n            \"color\": \"#a855f7\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"SD-WAN tunnel\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dashed\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"router1-branch-chi\",\n            \"from\": \"core-router-1\",\n            \"to\": \"branch-router-chi\",\n            \"width\": 3,\n            \"color\": \"#a855f7\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"SD-WAN tunnel\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dashed\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"router1-branch-lon\",\n            \"from\": \"core-router-1\",\n            \"to\": \"branch-router-lon\",\n            \"width\": 3,\n            \"color\": \"#a855f7\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"SD-WAN tunnel\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dashed\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"router1-branch-tokyo\",\n            \"from\": \"core-router-1\",\n            \"to\": \"branch-router-tokyo\",\n            \"width\": 3,\n            \"color\": \"#a855f7\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"SD-WAN tunnel\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dashed\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"router1-aws\",\n            \"from\": \"core-router-1\",\n            \"to\": \"cloud-aws\",\n            \"width\": 3,\n            \"color\": \"#f97316\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"Direct Connect\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dashed\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"router2-azure\",\n            \"from\": \"core-router-2\",\n            \"to\": \"cloud-azure\",\n            \"width\": 3,\n            \"color\": \"#0ea5e9\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"ExpressRoute\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dashed\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"fwint-gcp\",\n            \"from\": \"fw-internal\",\n            \"to\": \"cloud-gcp\",\n            \"width\": 2,\n            \"color\": \"#22c55e\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"VPN tunnel\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dashed\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-floor1\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"dist-switch-floor1\",\n            \"width\": 3,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-floor2\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"dist-switch-floor2\",\n            \"width\": 3,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw2-floor3\",\n            \"from\": \"core-switch-2\",\n            \"to\": \"dist-switch-floor3\",\n            \"width\": 3,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw2-floor4\",\n            \"from\": \"core-switch-2\",\n            \"to\": \"dist-switch-floor4\",\n            \"width\": 3,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"floor1-endpoints\",\n            \"from\": \"dist-switch-floor1\",\n            \"to\": \"endpoint-1000\",\n            \"width\": 2,\n            \"color\": \"#94a3b8\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"floor1-ap1\",\n            \"from\": \"dist-switch-floor1\",\n            \"to\": \"ap-floor1-zone1\",\n            \"width\": 2,\n            \"color\": \"#06b6d4\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"floor2-ap2\",\n            \"from\": \"dist-switch-floor2\",\n            \"to\": \"ap-floor2-zone1\",\n            \"width\": 2,\n            \"color\": \"#06b6d4\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"floor3-ap3\",\n            \"from\": \"dist-switch-floor3\",\n            \"to\": \"ap-floor3-zone1\",\n            \"width\": 2,\n            \"color\": \"#06b6d4\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"floor4-ap4\",\n            \"from\": \"dist-switch-floor4\",\n            \"to\": \"ap-floor4-zone1\",\n            \"width\": 2,\n            \"color\": \"#06b6d4\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"fwint-proxy1\",\n            \"from\": \"fw-internal\",\n            \"to\": \"proxy-server-1\",\n            \"width\": 2,\n            \"color\": \"#ef4444\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"fwint-proxy2\",\n            \"from\": \"fw-internal\",\n            \"to\": \"proxy-server-2\",\n            \"width\": 2,\n            \"color\": \"#ef4444\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"fwext1-vpn\",\n            \"from\": \"fw-external-1\",\n            \"to\": \"vpn-concentrator\",\n            \"width\": 3,\n            \"color\": \"#8b5cf6\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-nac\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"nac-server\",\n            \"width\": 2,\n            \"color\": \"#f59e0b\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-voip\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"voip-cluster\",\n            \"width\": 3,\n            \"color\": \"#22c55e\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw2-video\",\n            \"from\": \"core-switch-2\",\n            \"to\": \"video-conf\",\n            \"width\": 3,\n            \"color\": \"#22c55e\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-cameras\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"security-cameras\",\n            \"width\": 2,\n            \"color\": \"#94a3b8\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"fwint-dev1\",\n            \"from\": \"fw-internal\",\n            \"to\": \"dev-server-1\",\n            \"width\": 2,\n            \"color\": \"#a855f7\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"fwint-dev2\",\n            \"from\": \"fw-internal\",\n            \"to\": \"dev-server-2\",\n            \"width\": 2,\n            \"color\": \"#a855f7\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\",\n            \"animate\": true,\n            \"animationSpeed\": \"1.5\"\n          },\n          {\n            \"id\": \"fwint-test\",\n            \"from\": \"fw-internal\",\n            \"to\": \"test-environment\",\n            \"width\": 2,\n            \"color\": \"#a855f7\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-erp\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"erp-system\",\n            \"width\": 3,\n            \"color\": \"#f59e0b\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"fwext1-crm\",\n            \"from\": \"fw-external-1\",\n            \"to\": \"crm-system\",\n            \"width\": 2,\n            \"color\": \"#f59e0b\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"Salesforce cloud\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dashed\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"ups1-racka1\",\n            \"from\": \"ups-dc-1\",\n            \"to\": \"dc-rack-a1\",\n            \"width\": 2,\n            \"color\": \"#fbbf24\",\n            \"direction\": \"forward\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"Power feed A\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"ups2-racka2\",\n            \"from\": \"ups-dc-2\",\n            \"to\": \"dc-rack-a2\",\n            \"width\": 2,\n            \"color\": \"#fbbf24\",\n            \"direction\": \"forward\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"Power feed B\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"ups1-rackb1\",\n            \"from\": \"ups-dc-1\",\n            \"to\": \"dc-rack-b1\",\n            \"width\": 2,\n            \"color\": \"#fbbf24\",\n            \"direction\": \"forward\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"Power feed A\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\",\n            \"animate\": true,\n            \"animationSpeed\": \"4\"\n          },\n          {\n            \"id\": \"ups2-rackb2\",\n            \"from\": \"ups-dc-2\",\n            \"to\": \"dc-rack-b2\",\n            \"width\": 2,\n            \"color\": \"#fbbf24\",\n            \"direction\": \"forward\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"Power feed B\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"cooling1-racka1\",\n            \"from\": \"cooling-1\",\n            \"to\": \"dc-rack-a1\",\n            \"width\": 2,\n            \"color\": \"#38bdf8\",\n            \"direction\": \"forward\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"Cooling zone\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dotted\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"cooling2-rackb1\",\n            \"from\": \"cooling-2\",\n            \"to\": \"dc-rack-b1\",\n            \"width\": 2,\n            \"color\": \"#38bdf8\",\n            \"direction\": \"forward\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"Cooling zone\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dotted\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"custom-1765237881452\",\n            \"type\": \"custom\",\n            \"color\": \"#c800ff\",\n            \"width\": 4,\n            \"lineStyle\": \"solid\",\n            \"direction\": \"forward\",\n            \"points\": [\n              {\n                \"x\": 3492.3994140625,\n                \"y\": 1526.9556884765625\n              },\n              {\n                \"x\": 3500.609619140625,\n                \"y\": 1830.7386474609375\n              },\n              {\n                \"x\": 3303.561279296875,\n                \"y\": 1732.2144775390625\n              }\n            ],\n            \"notes\": [],\n            \"routing\": \"orthogonal\"\n          }\n        ]\n      },\n      \"positions\": {\n        \"core-router-1\": {\n          \"x\": 3720.166015625,\n          \"y\": 245.9932403564453\n        },\n        \"core-router-2\": {\n          \"x\": 2499.883407638303,\n          \"y\": 329.99503430389154\n        },\n        \"fw-external-1\": {\n          \"x\": 3221.7385182723783,\n          \"y\": 1016.1364499992887\n        },\n        \"fw-external-2\": {\n          \"x\": 1915.5213706410505,\n          \"y\": 224.43528858865443\n        },\n        \"fw-internal\": {\n          \"x\": 1746.9168185079352,\n          \"y\": 477.5300527221864\n        },\n        \"core-switch-1\": {\n          \"x\": 449.39860669455675,\n          \"y\": 384.4578707617695\n        },\n        \"core-switch-2\": {\n          \"x\": 761.1664921394672,\n          \"y\": 180.89283910873155\n        },\n        \"dc-rack-a1\": {\n          \"x\": 783.7017241128451,\n          \"y\": 647.4086870405963\n        },\n        \"dc-rack-a2\": {\n          \"x\": 209.25701628255229,\n          \"y\": 228.01593190351014\n        },\n        \"dc-rack-b1\": {\n          \"x\": 3184.3186625759854,\n          \"y\": 1627.4495531027196\n        },\n        \"dc-rack-b2\": {\n          \"x\": 245.37065918741246,\n          \"y\": 499.6191264194081\n        },\n        \"dmz-rack\": {\n          \"x\": 2176.4105289561007,\n          \"y\": 610.8312056412005\n        },\n        \"mgmt-rack\": {\n          \"x\": 1601.2987201807314,\n          \"y\": 1281.4753424975324\n        },\n        \"esxi-host-01\": {\n          \"x\": 2162.2166789540615,\n          \"y\": 2608.110619289529\n        },\n        \"esxi-host-02\": {\n          \"x\": 2205.94717202368,\n          \"y\": 2689.67539624076\n        },\n        \"esxi-host-03\": {\n          \"x\": 2154.6015436939074,\n          \"y\": 2771.203009774913\n        },\n        \"esxi-host-04\": {\n          \"x\": 2195.986926025096,\n          \"y\": 2845\n        },\n        \"tor-switch-a1\": {\n          \"x\": 2146.8943639962963,\n          \"y\": 2845\n        },\n        \"esxi-host-05\": {\n          \"x\": 2185.9099961569727,\n          \"y\": 2845\n        },\n        \"esxi-host-06\": {\n          \"x\": 2139.099728450725,\n          \"y\": 2845\n        },\n        \"esxi-host-07\": {\n          \"x\": 2175.7223818764883,\n          \"y\": 2845\n        },\n        \"esxi-host-08\": {\n          \"x\": 2131.2222777148922,\n          \"y\": 2845\n        },\n        \"tor-switch-a2\": {\n          \"x\": 2165.4301485385085,\n          \"y\": 2845\n        },\n        \"san-primary\": {\n          \"x\": 2123.2667017518106,\n          \"y\": 2845\n        },\n        \"san-secondary\": {\n          \"x\": 2155.0394237844876,\n          \"y\": 2845\n        },\n        \"fc-switch-1\": {\n          \"x\": 2115.2377370375634,\n          \"y\": 2845\n        },\n        \"fc-switch-2\": {\n          \"x\": 2144.5563938942755,\n          \"y\": 2845\n        },\n        \"backup-server-1\": {\n          \"x\": 2107.1401637413705,\n          \"y\": 2845\n        },\n        \"backup-server-2\": {\n          \"x\": 2133.987300103025,\n          \"y\": 2845\n        },\n        \"tape-library\": {\n          \"x\": 2098.9788028796397,\n          \"y\": 2845\n        },\n        \"tor-switch-b1\": {\n          \"x\": 2123.338434885373,\n          \"y\": 2845\n        },\n        \"tor-switch-b2\": {\n          \"x\": 2090.7585134456995,\n          \"y\": 2845\n        },\n        \"web-server-1\": {\n          \"x\": 2112.6161382091163,\n          \"y\": 2845\n        },\n        \"web-server-2\": {\n          \"x\": 2082.484189516922,\n          \"y\": 2845\n        },\n        \"waf-1\": {\n          \"x\": 2101.826793760617,\n          \"y\": 2845\n        },\n        \"load-balancer-dmz\": {\n          \"x\": 2074.1607573409574,\n          \"y\": 2845\n        },\n        \"mail-gateway\": {\n          \"x\": 2090.97682514417,\n          \"y\": 2845\n        },\n        \"dns-external-1\": {\n          \"x\": 2065.7931724028163,\n          \"y\": 2845\n        },\n        \"dns-external-2\": {\n          \"x\": 2080.0726920576153,\n          \"y\": 2845\n        },\n        \"vcenter\": {\n          \"x\": 2057.3864164745437,\n          \"y\": 2845\n        },\n        \"nsx-manager\": {\n          \"x\": 2069.1208864464534,\n          \"y\": 2845\n        },\n        \"siem-server\": {\n          \"x\": 2048.945494649244,\n          \"y\": 2845\n        },\n        \"nms-server\": {\n          \"x\": 2058.1279286387635,\n          \"y\": 2845\n        },\n        \"jump-server\": {\n          \"x\": 2040.4754323612206,\n          \"y\": 2845\n        },\n        \"ipam-server\": {\n          \"x\": 2047.1003634632284,\n          \"y\": 2845\n        },\n        \"wlc-primary\": {\n          \"x\": 1575.9723612611924,\n          \"y\": 2306.135986328125\n        },\n        \"wlc-secondary\": {\n          \"x\": 1468.1361870166274,\n          \"y\": 1563.733642578125\n        },\n        \"mobile-zone-hq\": {\n          \"x\": 2354.901177346808,\n          \"y\": 2806.0078125\n        },\n        \"mobile-zone-guest\": {\n          \"x\": 2307.6605605284435,\n          \"y\": 2611.047119140625\n        },\n        \"mobile-zone-iot\": {\n          \"x\": 2229.397686389302,\n          \"y\": 2299.110107421875\n        },\n        \"branch-router-ny\": {\n          \"x\": 3151.903101363964,\n          \"y\": 633.6580810546875\n        },\n        \"branch-router-la\": {\n          \"x\": 3083.8876194705945,\n          \"y\": 506.90625\n        },\n        \"branch-router-chi\": {\n          \"x\": 3355.02409980103,\n          \"y\": 393.1805725097656\n        },\n        \"branch-router-lon\": {\n          \"x\": 3113.609823320121,\n          \"y\": 260.4093322753906\n        },\n        \"branch-router-tokyo\": {\n          \"x\": 3699.3234994733834,\n          \"y\": 471.4241027832031\n        },\n        \"cloud-aws\": {\n          \"x\": 3436.528122523513,\n          \"y\": 545.9614868164062\n        },\n        \"cloud-azure\": {\n          \"x\": 2592.566210818907,\n          \"y\": 2724.068115234375\n        },\n        \"cloud-gcp\": {\n          \"x\": 2827.3183770424234,\n          \"y\": 2731.397216796875\n        },\n        \"isp-primary\": {\n          \"x\": 3712.192068081962,\n          \"y\": 615.64990234375\n        },\n        \"isp-secondary\": {\n          \"x\": 3253.9473366098055,\n          \"y\": 1993.2629089355469\n        },\n        \"dc-internal-1\": {\n          \"x\": 1958.4243458877936,\n          \"y\": 2845\n        },\n        \"dc-internal-2\": {\n          \"x\": 1963.768951182132,\n          \"y\": 2845\n        },\n        \"app-server-1\": {\n          \"x\": 1947.3819379304134,\n          \"y\": 2845\n        },\n        \"app-server-2\": {\n          \"x\": 1955.2862087394126,\n          \"y\": 2845\n        },\n        \"db-server-1\": {\n          \"x\": 1936.3708569559828,\n          \"y\": 2845\n        },\n        \"db-server-2\": {\n          \"x\": 1946.8300873488822,\n          \"y\": 2845\n        },\n        \"k8s-master-1\": {\n          \"x\": 1925.397658583093,\n          \"y\": 2845\n        },\n        \"k8s-master-2\": {\n          \"x\": 1938.405621494142,\n          \"y\": 2845\n        },\n        \"k8s-master-3\": {\n          \"x\": 1914.4688758763386,\n          \"y\": 2845\n        },\n        \"k8s-worker-1\": {\n          \"x\": 1930.017826812177,\n          \"y\": 2845\n        },\n        \"k8s-worker-2\": {\n          \"x\": 1903.5910154567553,\n          \"y\": 2845\n        },\n        \"k8s-worker-3\": {\n          \"x\": 1921.6716971072178,\n          \"y\": 2845\n        },\n        \"k8s-worker-4\": {\n          \"x\": 1892.7705536280016,\n          \"y\": 2845\n        },\n        \"proxy-server-1\": {\n          \"x\": 1806.1152433697903,\n          \"y\": 653.7529296875\n        },\n        \"proxy-server-2\": {\n          \"x\": 2937.4207928721535,\n          \"y\": 2628.7880859375\n        },\n        \"vpn-concentrator\": {\n          \"x\": 3642.252088474593,\n          \"y\": 946.7255249023438\n        },\n        \"nac-server\": {\n          \"x\": 1153.2626148502184,\n          \"y\": 1172.1895751953125\n        },\n        \"print-server\": {\n          \"x\": 1896.9328460745962,\n          \"y\": 2845\n        },\n        \"file-server\": {\n          \"x\": 1860.7177871362182,\n          \"y\": 2845\n        },\n        \"ca-server\": {\n          \"x\": 1888.8027739274805,\n          \"y\": 2845\n        },\n        \"sccm-server\": {\n          \"x\": 1850.1909418511675,\n          \"y\": 2845\n        },\n        \"voip-cluster\": {\n          \"x\": 1777.038465328039,\n          \"y\": 1616.8961181640625\n        },\n        \"video-conf\": {\n          \"x\": 1993.8373941679588,\n          \"y\": 2244.936309814453\n        },\n        \"security-cameras\": {\n          \"x\": 1674.413336949044,\n          \"y\": 2046.0380859375\n        },\n        \"nvr-cluster\": {\n          \"x\": 1829.4110389706402,\n          \"y\": 2845\n        },\n        \"dev-server-1\": {\n          \"x\": 2800.5894350649614,\n          \"y\": 1175.623291015625\n        },\n        \"dev-server-2\": {\n          \"x\": 1945.0822182484326,\n          \"y\": 1164.5184783935547\n        },\n        \"test-environment\": {\n          \"x\": 2932.0863047891075,\n          \"y\": 862.4592895507812\n        },\n        \"erp-system\": {\n          \"x\": 789.9880103985649,\n          \"y\": 473.7113342285156\n        },\n        \"crm-system\": {\n          \"x\": 3514.6003232048542,\n          \"y\": 1137.7720947265625\n        },\n        \"endpoint-1000\": {\n          \"x\": 991.6812012057328,\n          \"y\": 2284.42236328125\n        },\n        \"dist-switch-floor1\": {\n          \"x\": 654.2091033261356,\n          \"y\": 2020.0086669921875\n        },\n        \"dist-switch-floor2\": {\n          \"x\": 853.8845527112826,\n          \"y\": 1843.2872314453125\n        },\n        \"dist-switch-floor3\": {\n          \"x\": 1899.4353951584517,\n          \"y\": 1456.5068359375\n        },\n        \"dist-switch-floor4\": {\n          \"x\": 488.5289313756234,\n          \"y\": 181.47256469726562\n        },\n        \"ap-floor1-zone1\": {\n          \"x\": 1140.16846970184,\n          \"y\": 2070.2916259765625\n        },\n        \"ap-floor2-zone1\": {\n          \"x\": 688.1952143592268,\n          \"y\": 2384.4775390625\n        },\n        \"ap-floor3-zone1\": {\n          \"x\": 2145.3803027919676,\n          \"y\": 1890.2816162109375\n        },\n        \"ap-floor4-zone1\": {\n          \"x\": 517.646146409649,\n          \"y\": 565.59716796875\n        },\n        \"ups-dc-1\": {\n          \"x\": 771.1406786539856,\n          \"y\": 295.9266662597656\n        },\n        \"ups-dc-2\": {\n          \"x\": 216.2410855890687,\n          \"y\": 330.3345947265625\n        },\n        \"pdu-rack-a1\": {\n          \"x\": 1804.774444371901,\n          \"y\": 2845\n        },\n        \"pdu-rack-a2\": {\n          \"x\": 1741.6184034693686,\n          \"y\": 2845\n        },\n        \"cooling-1\": {\n          \"x\": 245.7080801919958,\n          \"y\": 626.1914672851562\n        },\n        \"cooling-2\": {\n          \"x\": 1603.293611085831,\n          \"y\": 981.0621185302734\n        },\n        \"camera-a\": {\n          \"x\": 166.57075412676295,\n          \"y\": 145\n        },\n        \"camera-a-copy\": {\n          \"x\": 1040.653076171875,\n          \"y\": 738.42822265625\n        }\n      },\n      \"sizes\": {\n        \"isp-secondary\": 139,\n        \"test-environment\": 121,\n        \"dev-server-1\": 128,\n        \"core-router-2\": 120,\n        \"camera-a\": 45,\n        \"camera-a-copy\": 45\n      },\n      \"styles\": {\n        \"dc-rack-b2\": {\n          \"all\": {\n            \"circleColor\": \"#ff0000\"\n          }\n        },\n        \"dc-rack-a1\": {\n          \"all\": {\n            \"circleColor\": \"#ff0000\"\n          }\n        },\n        \"dc-rack-b1\": {\n          \"all\": {\n            \"circleColor\": \"#ff0000\",\n            \"titleSize\": 59\n          }\n        },\n        \"isp-secondary\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"alist\"\n            },\n            \"circleColor\": \"#4d2c58\",\n            \"circleBorder\": \"#000000\",\n            \"titleColor\": \"#006eff\"\n          }\n        },\n        \"core-router-2\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"actual-budget\"\n            },\n            \"pingOffsetX\": -15,\n            \"pingOffsetY\": -38\n          }\n        },\n        \"fw-external-1\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"anonaddy\"\n            }\n          }\n        },\n        \"cloud-aws\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"ansible\"\n            }\n          }\n        },\n        \"isp-primary\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"wikidocs\"\n            }\n          }\n        },\n        \"branch-router-tokyo\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"adguard-home\"\n            }\n          }\n        },\n        \"core-router-1\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"borg\"\n            }\n          }\n        },\n        \"test-environment\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"simple\",\n              \"name\": \"apple\"\n            }\n          }\n        },\n        \"dev-server-1\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"simple\",\n              \"name\": \"amazonwebservices\"\n            }\n          }\n        }\n      },\n      \"legend\": {\n        \"#10b981\": \"Trusted Lan\",\n        \"#f59e0b\": \"Secure Lan\",\n        \"#ef4444\": \"DMZ\",\n        \"#475569\": \"Main ISP\",\n        \"#3b82f6\": \"Alternate ISP\",\n        \"#8b5cf6\": \"you can edit me too\",\n        \"#06b6d4\": \"you can edit me too\",\n        \"#a855f7\": \"you can edit me too\",\n        \"#f97316\": \"you can edit me too\",\n        \"#0ea5e9\": \"you can edit me too\",\n        \"#22c55e\": \"you can edit me too\",\n        \"#94a3b8\": \"you can edit me too\",\n        \"#fbbf24\": \"you can edit me too\",\n        \"#38bdf8\": \"you can edit me too\",\n        \"#c800ff\": \"you can edit me too\"\n      },\n      \"rects\": {\n        \"list\": [\n          {\n            \"id\": \"rect-1765237540610\",\n            \"x\": 2879.214599609375,\n            \"y\": 159.71981811523438,\n            \"width\": 992.196044921875,\n            \"height\": 538.8650817871094,\n            \"color\": \"#f97316\",\n            \"style\": \"filled\",\n            \"lineStyle\": \"solid\",\n            \"notes\": []\n          },\n          {\n            \"id\": \"rect-1765237681216\",\n            \"x\": 448.3926696777344,\n            \"y\": 1671.651123046875,\n            \"width\": 916.3436584472656,\n            \"height\": 924.27734375,\n            \"color\": \"#c800ff\",\n            \"style\": \"outlined\",\n            \"lineStyle\": \"solid\",\n            \"notes\": []\n          },\n          {\n            \"id\": \"rect-1766437913740\",\n            \"x\": 904.5889892578125,\n            \"y\": 115.40318298339844,\n            \"width\": 110.93878173828125,\n            \"height\": 919.6242218017578,\n            \"color\": \"#5215f9\",\n            \"style\": \"filled\",\n            \"lineStyle\": \"wall\",\n            \"notes\": [],\n            \"borderWidth\": 13\n          },\n          {\n            \"id\": \"rect-1766437935414\",\n            \"x\": 130.93685150146484,\n            \"y\": 1072.3624877929688,\n            \"width\": 872.9131851196289,\n            \"height\": 99.260986328125,\n            \"color\": \"#5215f9\",\n            \"style\": \"filled\",\n            \"lineStyle\": \"wall\",\n            \"notes\": [],\n            \"borderWidth\": 13\n          }\n        ]\n      },\n      \"texts\": {\n        \"list\": [\n          {\n            \"id\": \"text-1765237828167\",\n            \"x\": 3411.458740234375,\n            \"y\": 1390.00439453125,\n            \"content\": \"Double click on desktop\\nor long press on mobile\\nto enter rack canvas view\",\n            \"fontSize\": 46,\n            \"color\": \"#e2e8f0\",\n            \"fontWeight\": \"bold\",\n            \"fontStyle\": \"italic\",\n            \"textAlign\": \"middle\",\n            \"textDecoration\": \"none\",\n            \"bgColor\": \"#000000\",\n            \"bgEnabled\": false,\n            \"opacity\": 1\n          },\n          {\n            \"id\": \"text-1766446595277\",\n            \"x\": 654.3878479003906,\n            \"y\": 1367.7945556640625,\n            \"content\": \"SITE A\",\n            \"fontSize\": 101,\n            \"color\": \"#e2e8f0\",\n            \"fontWeight\": \"normal\",\n            \"fontStyle\": \"normal\",\n            \"textAlign\": \"start\",\n            \"textDecoration\": \"none\",\n            \"bgColor\": \"#000000\",\n            \"bgEnabled\": false,\n            \"opacity\": 1\n          },\n          {\n            \"id\": \"text-1766446610211\",\n            \"x\": 180.63662719726562,\n            \"y\": 1128.822998046875,\n            \"content\": \"SITE A\",\n            \"fontSize\": 101,\n            \"color\": \"#e2e8f0\",\n            \"fontWeight\": \"normal\",\n            \"fontStyle\": \"normal\",\n            \"textAlign\": \"start\",\n            \"textDecoration\": \"none\",\n            \"bgColor\": \"#000000\",\n            \"bgEnabled\": false,\n            \"opacity\": 1\n          },\n          {\n            \"id\": \"text-1766453024797\",\n            \"x\": 968.6458740234375,\n            \"y\": 1028.6621398925781,\n            \"content\": \"SITE A\",\n            \"fontSize\": 101,\n            \"color\": \"#e2e8f0\",\n            \"fontWeight\": \"normal\",\n            \"fontStyle\": \"normal\",\n            \"textAlign\": \"start\",\n            \"textDecoration\": \"none\",\n            \"bgColor\": \"#000000\",\n            \"bgEnabled\": false,\n            \"opacity\": 1,\n            \"rotation\": -89,\n            \"_dragStartX\": 972.46826171875,\n            \"_dragStartY\": 1009.5499572753906\n          },\n          {\n            \"id\": \"text-1766453070975\",\n            \"x\": 613.1589965820312,\n            \"y\": 1139.512939453125,\n            \"content\": \"SITE A\",\n            \"fontSize\": 101,\n            \"color\": \"#e2e8f0\",\n            \"fontWeight\": \"normal\",\n            \"fontStyle\": \"normal\",\n            \"textAlign\": \"start\",\n            \"textDecoration\": \"none\",\n            \"bgColor\": \"#000000\",\n            \"bgEnabled\": false,\n            \"opacity\": 1\n          },\n          {\n            \"id\": \"text-1766453072857\",\n            \"x\": 968.64599609375,\n            \"y\": 474.40818786621094,\n            \"content\": \"SITE A\",\n            \"fontSize\": 101,\n            \"color\": \"#e2e8f0\",\n            \"fontWeight\": \"normal\",\n            \"fontStyle\": \"normal\",\n            \"textAlign\": \"start\",\n            \"textDecoration\": \"none\",\n            \"bgColor\": \"#000000\",\n            \"bgEnabled\": false,\n            \"opacity\": 1,\n            \"rotation\": 269,\n            \"_dragStartX\": 1480.85302734375,\n            \"_dragStartY\": 822.2503356933594\n          },\n          {\n            \"id\": \"text-1766458222326\",\n            \"x\": 3167.812744140625,\n            \"y\": 2190.516357421875,\n            \"content\": \"\",\n            \"fontSize\": 18,\n            \"color\": \"#e2e8f0\",\n            \"fontWeight\": \"normal\",\n            \"fontStyle\": \"normal\",\n            \"textAlign\": \"start\",\n            \"textDecoration\": \"none\",\n            \"bgColor\": \"#000000\",\n            \"bgEnabled\": false,\n            \"opacity\": 1\n          }\n        ]\n      },\n      \"pageState\": {\n        \"title\": \"The One File Corporate\",\n        \"background\": \"\",\n        \"topbarBg\": \"rgba(9, 12, 20, 0.9)\",\n        \"topbarBorder\": \"#1f2533\",\n        \"panel\": \"#0b0e13\",\n        \"panelAlt\": \"#10141b\",\n        \"accent\": \"#4fd1c5\",\n        \"sidebarBg\": \"#10141b\",\n        \"btnBg\": \"#0b0e13\",\n        \"btnText\": \"#e2e8f0\",\n        \"tagFill\": \"#1e293b\",\n        \"tagText\": \"#e2e8f0\",\n        \"tagBorder\": \"#475569\",\n        \"inputBg\": \"#0b0e13\",\n        \"inputText\": \"#e2e8f0\",\n        \"inputBorder\": \"#1f2937\",\n        \"inputFont\": \"Inter, system-ui, sans-serif\",\n        \"inputFontSize\": 14,\n        \"toolbarBg\": \"#0f172a\",\n        \"toolbarBorder\": \"#1f2937\",\n        \"toolbarText\": \"#94a3b8\",\n        \"toolbarBtnBg\": \"#0b0e13\",\n        \"toolbarBtnText\": \"#e2e8f0\",\n        \"minimapDots\": \"#94a3b8\",\n        \"canvasHintEnabled\": true,\n        \"canvasHintText\": \"\",\n        \"canvasHintBg\": \"#0f172a\",\n        \"canvasHintColor\": \"#94a3b8\",\n        \"danger\": \"#f56565\",\n        \"textMain\": \"#e2e8f0\",\n        \"textSoft\": \"#94a3b8\",\n        \"topbarHeight\": 103,\n        \"sidebarWidth\": 350,\n        \"mobileFooterHeight\": 40,\n        \"sidebarCollapsed\": false,\n        \"nodeFill\": \"#1e293b\",\n        \"nodeStroke\": \"#475569\",\n        \"nodeTitle\": \"#e2e8f0\",\n        \"nodeSub\": \"#94a3b8\",\n        \"nodeTitleSize\": 41,\n        \"nodeSubSize\": 27,\n        \"nodeFont\": \"monospace\",\n        \"defaultEdge\": \"#475569\",\n        \"selectionHandle\": \"#f59e0b\",\n        \"selectionHandleSize\": 8,\n        \"groupIndicator\": \"#4fd1c5\",\n        \"canvasGradientTop\": \"#1e2532\",\n        \"canvasGradientBottom\": \"#050608\",\n        \"canvasBorder\": \"#475569\",\n        \"canvasGrid\": \"#475569\",\n        \"canvasGridSize\": 50,\n        \"canvasGridEnabled\": true,\n        \"rackFrameFill\": \"#0f172a\",\n        \"rackGridEnabled\": true,\n        \"rackFrameStroke\": \"#4fd1c5\",\n        \"rackLineColor\": \"#475569\",\n        \"rackTextColor\": \"#4fd1c5\",\n        \"viewOnly\": false,\n        \"defaultEdgeRouting\": \"orthogonal\",\n        \"animateConnections\": false,\n        \"animationStyle\": \"arrows\",\n        \"animationDirection\": \"all\",\n        \"animationSpeed\": 4,\n        \"autoPingEnabled\": false,\n        \"autoPingInterval\": 30\n      }\n    },\n    {\n      \"id\": \"tab-1765235136918\",\n      \"name\": \"Homelab 2\",\n      \"nodes\": {\n        \"internet\": {\n          \"shape\": \"stop-sign\",\n          \"name\": \"Internet\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"internet-copy\": {\n          \"shape\": \"firewall\",\n          \"name\": \"OPNSENSE\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"opnsense-copy\": {\n          \"shape\": \"firewall\",\n          \"name\": \"Docker\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"docker-copy\": {\n          \"shape\": \"firewall\",\n          \"name\": \"Docker2\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"docker-copy-1\": {\n          \"shape\": \"firewall\",\n          \"name\": \"Docker3\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"docker-copy-2\": {\n          \"shape\": \"firewall\",\n          \"name\": \"Docker 4\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [\n            {\n              \"type\": \"icon\",\n              \"library\": \"selfhst\",\n              \"name\": \"docker\"\n            },\n            {\n              \"type\": \"icon\",\n              \"library\": \"selfhst\",\n              \"name\": \"authentik\"\n            },\n            {\n              \"type\": \"icon\",\n              \"library\": \"selfhst\",\n              \"name\": \"immich\"\n            }\n          ],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"opnsense-copy-1\": {\n          \"shape\": \"firewall\",\n          \"name\": \"OPNSENSE GUEST\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"phone\": {\n          \"shape\": \"phone\",\n          \"name\": \"Phone\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"desktop\": {\n          \"shape\": \"pc\",\n          \"name\": \"Desktop\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dns\": {\n          \"shape\": \"cloud\",\n          \"name\": \"DNS\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"racked\": {\n          \"shape\": \"server\",\n          \"name\": \"Racked\",\n          \"ip\": \"\",\n          \"role\": \"Rack\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": true,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"thermostat\": {\n          \"shape\": \"thermostat\",\n          \"name\": \"Thermostat\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"video-doorbell\": {\n          \"shape\": \"doorbell\",\n          \"name\": \"Video Doorbell\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"smart-lock\": {\n          \"shape\": \"smart-lock\",\n          \"name\": \"Smart Lock\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"smart-bulb\": {\n          \"shape\": \"smart-bulb\",\n          \"name\": \"Smart Bulb\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"robot-vacuum\": {\n          \"shape\": \"vacuum\",\n          \"name\": \"Robot Vacuum\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        }\n      },\n      \"edges\": {\n        \"list\": [\n          {\n            \"id\": \"internet-internet-copy-1765238145151\",\n            \"from\": \"internet\",\n            \"to\": \"internet-copy\",\n            \"width\": 4,\n            \"color\": \"#55e208\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1\n          },\n          {\n            \"id\": \"internet-copy-opnsense-copy-1765238187451\",\n            \"from\": \"internet-copy\",\n            \"to\": \"opnsense-copy\",\n            \"width\": 4,\n            \"color\": \"#4c00ff\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1\n          },\n          {\n            \"id\": \"internet-copy-docker-copy-1765238242477\",\n            \"from\": \"internet-copy\",\n            \"to\": \"docker-copy\",\n            \"width\": 4,\n            \"color\": \"#4c00ff\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1\n          },\n          {\n            \"id\": \"internet-copy-docker-copy-1-1765238244637\",\n            \"from\": \"internet-copy\",\n            \"to\": \"docker-copy-1\",\n            \"width\": 4,\n            \"color\": \"#4c00ff\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1\n          },\n          {\n            \"id\": \"internet-copy-docker-copy-2-1765238246233\",\n            \"from\": \"internet-copy\",\n            \"to\": \"docker-copy-2\",\n            \"width\": 4,\n            \"color\": \"#4c00ff\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1\n          },\n          {\n            \"id\": \"internet-opnsense-copy-1-1765238266117\",\n            \"from\": \"internet\",\n            \"to\": \"opnsense-copy-1\",\n            \"width\": 4,\n            \"color\": \"#80ff00\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1\n          },\n          {\n            \"id\": \"opnsense-copy-1-dns-1765238347996\",\n            \"from\": \"opnsense-copy-1\",\n            \"to\": \"dns\",\n            \"width\": 4,\n            \"color\": \"#fb00ff\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1\n          },\n          {\n            \"id\": \"dns-desktop-1765238386101\",\n            \"from\": \"dns\",\n            \"to\": \"desktop\",\n            \"width\": 4,\n            \"color\": \"#ff00d0\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1\n          },\n          {\n            \"id\": \"phone-dns-1765238391156\",\n            \"from\": \"phone\",\n            \"to\": \"dns\",\n            \"width\": 4,\n            \"color\": \"#ff00d0\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1\n          },\n          {\n            \"id\": \"custom-1765239449323\",\n            \"type\": \"custom\",\n            \"color\": \"#f97316\",\n            \"width\": 4,\n            \"lineStyle\": \"solid\",\n            \"direction\": \"forward\",\n            \"points\": [\n              {\n                \"x\": 2936.464111328125,\n                \"y\": 786.07958984375\n              },\n              {\n                \"x\": 3184.112060546875,\n                \"y\": 887.6153564453125\n              },\n              {\n                \"x\": 2763.110107421875,\n                \"y\": 981.7216796875\n              }\n            ],\n            \"notes\": []\n          }\n        ]\n      },\n      \"positions\": {\n        \"internet\": {\n          \"x\": 1757.7735887323333,\n          \"y\": 298.77284240722656\n        },\n        \"internet-copy\": {\n          \"x\": 2066.9677515897347,\n          \"y\": 473.4119134177565\n        },\n        \"opnsense-copy\": {\n          \"x\": 1773.8400660428597,\n          \"y\": 666.5758233298659\n        },\n        \"docker-copy\": {\n          \"x\": 1931.1978950081452,\n          \"y\": 782.2775961320921\n        },\n        \"docker-copy-1\": {\n          \"x\": 2158.1262397347077,\n          \"y\": 767.7122274797483\n        },\n        \"docker-copy-2\": {\n          \"x\": 2342.2663764534577,\n          \"y\": 631.7681967180296\n        },\n        \"opnsense-copy-1\": {\n          \"x\": 2757.879480087803,\n          \"y\": 307.6117116091891\n        },\n        \"phone\": {\n          \"x\": 3312.857751572178,\n          \"y\": 502.58220111114224\n        },\n        \"desktop\": {\n          \"x\": 2971.700036728428,\n          \"y\": 480.7287465212985\n        },\n        \"dns\": {\n          \"x\": 3200.4643189549906,\n          \"y\": 320.469591247861\n        },\n        \"racked\": {\n          \"x\": 2645.5845448279656,\n          \"y\": 970.7820678889219\n        },\n        \"thermostat\": {\n          \"x\": 1323.0595481711202,\n          \"y\": 574.6132617105841\n        },\n        \"video-doorbell\": {\n          \"x\": 1188.4284446554952,\n          \"y\": 455.3684191812872\n        },\n        \"smart-lock\": {\n          \"x\": 1292.286782057839,\n          \"y\": 790.0231738199591\n        },\n        \"smart-bulb\": {\n          \"x\": 1496.156899245339,\n          \"y\": 716.9377246012091\n        },\n        \"robot-vacuum\": {\n          \"x\": 2288.5581443625265,\n          \"y\": 978.5069995035528\n        }\n      },\n      \"sizes\": {\n        \"core-router-1\": 36,\n        \"internet\": 168,\n        \"phone\": 121,\n        \"desktop\": 147,\n        \"racked\": 137,\n        \"docker-copy-2\": 82\n      },\n      \"styles\": {\n        \"internet\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"amazon-web-services\"\n            },\n            \"circleColor\": \"#db0000\",\n            \"circleBorder\": \"#000000\",\n            \"titleSize\": 52,\n            \"subSize\": 46\n          }\n        },\n        \"opnsense-copy-1\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"opnsense-v1\"\n            }\n          }\n        },\n        \"internet-copy\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"opnsense\"\n            }\n          }\n        },\n        \"docker-copy-2\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"docker\"\n            }\n          }\n        },\n        \"docker-copy-1\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"authportal\"\n            }\n          }\n        },\n        \"docker-copy\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"jotty\"\n            }\n          }\n        },\n        \"opnsense-copy\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"portainer\"\n            }\n          }\n        },\n        \"racked\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"mdi\",\n              \"name\": \"server-security\"\n            },\n            \"circleColor\": \"#010813\",\n            \"circleBorder\": \"#ffffff\"\n          }\n        }\n      },\n      \"legend\": {\n        \"#475569\": \"you can edit me too\",\n        \"#65758b\": \"you can edit me too\",\n        \"#63748c\": \"you can edit me too\",\n        \"#5e6f87\": \"you can edit me too\",\n        \"#586a84\": \"you can edit me too\",\n        \"#4f627d\": \"you can edit me too\",\n        \"#455873\": \"you can edit me too\",\n        \"#3d506c\": \"you can edit me too\",\n        \"#354964\": \"you can edit me too\",\n        \"#2e415c\": \"you can edit me too\",\n        \"#293c56\": \"you can edit me too\",\n        \"#273a53\": \"you can edit me too\",\n        \"#253750\": \"you can edit me too\",\n        \"#23354d\": \"you can edit me too\",\n        \"#203046\": \"you can edit me too\",\n        \"#1e2d43\": \"you can edit me too\",\n        \"#1a283d\": \"you can edit me too\",\n        \"#172435\": \"you can edit me too\",\n        \"#141f2e\": \"you can edit me too\",\n        \"#111a27\": \"you can edit me too\",\n        \"#0f1824\": \"you can edit me too\",\n        \"#0d1521\": \"you can edit me too\",\n        \"#0c131d\": \"you can edit me too\",\n        \"#0c1d1c\": \"you can edit me too\",\n        \"#0c1c1d\": \"you can edit me too\",\n        \"#0c191d\": \"you can edit me too\",\n        \"#0c141d\": \"you can edit me too\",\n        \"#0c0d1d\": \"you can edit me too\",\n        \"#130c1d\": \"you can edit me too\",\n        \"#1b0c1d\": \"you can edit me too\",\n        \"#1d0c17\": \"you can edit me too\",\n        \"#1d0c10\": \"you can edit me too\",\n        \"#1d0c0c\": \"you can edit me too\",\n        \"#3b1b1b\": \"you can edit me too\",\n        \"#3c1a1a\": \"you can edit me too\",\n        \"#3f1c1c\": \"you can edit me too\",\n        \"#401c1c\": \"you can edit me too\",\n        \"#451c1c\": \"you can edit me too\",\n        \"#461b1b\": \"you can edit me too\",\n        \"#4c1a1a\": \"you can edit me too\",\n        \"#521919\": \"you can edit me too\",\n        \"#571919\": \"you can edit me too\",\n        \"#5d1818\": \"you can edit me too\",\n        \"#631717\": \"you can edit me too\",\n        \"#651515\": \"you can edit me too\",\n        \"#6a1616\": \"you can edit me too\",\n        \"#6f1515\": \"you can edit me too\",\n        \"#711414\": \"you can edit me too\",\n        \"#761414\": \"you can edit me too\",\n        \"#771313\": \"you can edit me too\",\n        \"#7c1313\": \"you can edit me too\",\n        \"#811313\": \"you can edit me too\",\n        \"#821212\": \"you can edit me too\",\n        \"#871212\": \"you can edit me too\",\n        \"#881111\": \"you can edit me too\",\n        \"#8d1111\": \"you can edit me too\",\n        \"#8e1010\": \"you can edit me too\",\n        \"#8f0f0f\": \"you can edit me too\",\n        \"#900e0e\": \"you can edit me too\",\n        \"#8e0b0b\": \"you can edit me too\",\n        \"#8c0d0d\": \"you can edit me too\",\n        \"#880c0c\": \"you can edit me too\",\n        \"#830c0c\": \"you can edit me too\",\n        \"#7e0c0c\": \"you can edit me too\",\n        \"#790c0c\": \"you can edit me too\",\n        \"#730c0c\": \"you can edit me too\",\n        \"#6f0b0b\": \"you can edit me too\",\n        \"#0b6f64\": \"you can edit me too\",\n        \"#0b6f5f\": \"you can edit me too\",\n        \"#0b6f56\": \"you can edit me too\",\n        \"#0b6f49\": \"you can edit me too\",\n        \"#0b6f31\": \"you can edit me too\",\n        \"#0b6f1f\": \"you can edit me too\",\n        \"#0b6f0d\": \"you can edit me too\",\n        \"#176f0b\": \"you can edit me too\",\n        \"#266f0b\": \"you can edit me too\",\n        \"#296f0b\": \"you can edit me too\",\n        \"#2e6f0b\": \"you can edit me too\",\n        \"#1a2d10\": \"you can edit me too\",\n        \"#1c3111\": \"you can edit me too\",\n        \"#213814\": \"you can edit me too\",\n        \"#233c15\": \"you can edit me too\",\n        \"#254017\": \"you can edit me too\",\n        \"#294918\": \"you can edit me too\",\n        \"#2b4d1a\": \"you can edit me too\",\n        \"#2d511a\": \"you can edit me too\",\n        \"#315a1b\": \"you can edit me too\",\n        \"#35631c\": \"you can edit me too\",\n        \"#37681d\": \"you can edit me too\",\n        \"#3b721d\": \"you can edit me too\",\n        \"#3f7b1e\": \"you can edit me too\",\n        \"#42851e\": \"you can edit me too\",\n        \"#46901d\": \"you can edit me too\",\n        \"#499a1d\": \"you can edit me too\",\n        \"#4b9f1d\": \"you can edit me too\",\n        \"#4ca61c\": \"you can edit me too\",\n        \"#50b01c\": \"you can edit me too\",\n        \"#51b71a\": \"you can edit me too\",\n        \"#50b918\": \"you can edit me too\",\n        \"#51c115\": \"you can edit me too\",\n        \"#53c615\": \"you can edit me too\",\n        \"#53c814\": \"you can edit me too\",\n        \"#52c913\": \"you can edit me too\",\n        \"#54d011\": \"you can edit me too\",\n        \"#53d110\": \"you can edit me too\",\n        \"#55d510\": \"you can edit me too\",\n        \"#55d70f\": \"you can edit me too\",\n        \"#54d80e\": \"you can edit me too\",\n        \"#54da0b\": \"you can edit me too\",\n        \"#56df0c\": \"you can edit me too\",\n        \"#53db0a\": \"you can edit me too\",\n        \"#55e00b\": \"you can edit me too\",\n        \"#55e109\": \"you can edit me too\",\n        \"#55e208\": \"ISP LINE\",\n        \"#4c00ff\": \"MY Guest NETWORK\",\n        \"#80ff00\": \"you can edit me too\",\n        \"#3b4234\": \"you can edit me too\",\n        \"#3a3442\": \"you can edit me too\",\n        \"#3b3442\": \"you can edit me too\",\n        \"#3c3442\": \"you can edit me too\",\n        \"#3d3442\": \"you can edit me too\",\n        \"#3e3442\": \"you can edit me too\",\n        \"#3f3442\": \"you can edit me too\",\n        \"#403442\": \"you can edit me too\",\n        \"#413442\": \"you can edit me too\",\n        \"#653d66\": \"you can edit me too\",\n        \"#683f69\": \"you can edit me too\",\n        \"#6c416c\": \"you can edit me too\",\n        \"#6f4370\": \"you can edit me too\",\n        \"#704270\": \"you can edit me too\",\n        \"#734474\": \"you can edit me too\",\n        \"#784479\": \"you can edit me too\",\n        \"#7d447e\": \"you can edit me too\",\n        \"#7e437f\": \"you can edit me too\",\n        \"#834384\": \"you can edit me too\",\n        \"#844285\": \"you can edit me too\",\n        \"#89418b\": \"you can edit me too\",\n        \"#8e428f\": \"you can edit me too\",\n        \"#904091\": \"you can edit me too\",\n        \"#923e93\": \"you can edit me too\",\n        \"#973e98\": \"you can edit me too\",\n        \"#943c96\": \"you can edit me too\",\n        \"#993c9a\": \"you can edit me too\",\n        \"#963a98\": \"you can edit me too\",\n        \"#973899\": \"you can edit me too\",\n        \"#99369b\": \"you can edit me too\",\n        \"#9a359c\": \"you can edit me too\",\n        \"#9b349d\": \"you can edit me too\",\n        \"#9d329f\": \"you can edit me too\",\n        \"#9e31a0\": \"you can edit me too\",\n        \"#a02fa2\": \"you can edit me too\",\n        \"#9d2d9f\": \"you can edit me too\",\n        \"#9f2ba1\": \"you can edit me too\",\n        \"#a129a3\": \"you can edit me too\",\n        \"#a327a5\": \"you can edit me too\",\n        \"#a525a7\": \"you can edit me too\",\n        \"#a723a9\": \"you can edit me too\",\n        \"#a921ab\": \"you can edit me too\",\n        \"#ab1fad\": \"you can edit me too\",\n        \"#ad1daf\": \"you can edit me too\",\n        \"#ae1cb0\": \"you can edit me too\",\n        \"#b019b3\": \"you can edit me too\",\n        \"#b118b4\": \"you can edit me too\",\n        \"#b316b6\": \"you can edit me too\",\n        \"#b816bb\": \"you can edit me too\",\n        \"#b514b8\": \"you can edit me too\",\n        \"#ba14bd\": \"you can edit me too\",\n        \"#b712ba\": \"you can edit me too\",\n        \"#bb13be\": \"you can edit me too\",\n        \"#b811bb\": \"you can edit me too\",\n        \"#be10c1\": \"you can edit me too\",\n        \"#bb0ebe\": \"you can edit me too\",\n        \"#bd0cc0\": \"you can edit me too\",\n        \"#be0bc1\": \"you can edit me too\",\n        \"#c108c4\": \"you can edit me too\",\n        \"#be06c1\": \"you can edit me too\",\n        \"#c103c4\": \"you can edit me too\",\n        \"#c301c6\": \"you can edit me too\",\n        \"#c400c7\": \"you can edit me too\",\n        \"#c900cc\": \"you can edit me too\",\n        \"#ce00d1\": \"you can edit me too\",\n        \"#d300d6\": \"you can edit me too\",\n        \"#d800db\": \"you can edit me too\",\n        \"#dd00e0\": \"you can edit me too\",\n        \"#e200e6\": \"you can edit me too\",\n        \"#ec00f0\": \"you can edit me too\",\n        \"#f100f5\": \"you can edit me too\",\n        \"#f600fa\": \"you can edit me too\",\n        \"#fb00ff\": \"you can edit me too\",\n        \"#ff00d0\": \"iPhone (always guest iPhone)\",\n        \"#f97316\": \"you can edit me too\"\n      },\n      \"rects\": {\n        \"list\": [\n          {\n            \"id\": \"rect-1765238219615\",\n            \"x\": 2680.053955078125,\n            \"y\": 251.44879150390625,\n            \"width\": 814.10400390625,\n            \"height\": 389.26678466796875,\n            \"color\": \"#ec0999\",\n            \"style\": \"filled\",\n            \"lineStyle\": \"solid\",\n            \"notes\": []\n          }\n        ]\n      },\n      \"texts\": {\n        \"list\": [\n          {\n            \"id\": \"text-1765238422602\",\n            \"x\": 2466.35986328125,\n            \"y\": 741.6801147460938,\n            \"content\": \"Double click on desktop\\nor long press on mobile\\nto enter rack canvas view\",\n            \"fontSize\": 40,\n            \"color\": \"#e2e8f0\",\n            \"fontWeight\": \"normal\",\n            \"fontStyle\": \"normal\",\n            \"textAlign\": \"start\",\n            \"textDecoration\": \"none\",\n            \"bgColor\": \"#000000\",\n            \"bgEnabled\": false,\n            \"opacity\": 1\n          }\n        ]\n      },\n      \"pageState\": {\n        \"title\": \"The One File\",\n        \"background\": \"\",\n        \"topbarBg\": \"rgba(9, 12, 20, 0.9)\",\n        \"topbarBorder\": \"#1f2533\",\n        \"panel\": \"#2f0e0e\",\n        \"panelAlt\": \"#10141b\",\n        \"accent\": \"#a75252\",\n        \"sidebarBg\": \"#10141b\",\n        \"btnBg\": \"#0b0e13\",\n        \"btnText\": \"#e2e8f0\",\n        \"tagFill\": \"#1e293b\",\n        \"tagText\": \"#e2e8f0\",\n        \"tagBorder\": \"#475569\",\n        \"inputBg\": \"#0b0e13\",\n        \"inputText\": \"#e2e8f0\",\n        \"inputBorder\": \"#1f2937\",\n        \"inputFont\": \"Inter, system-ui, sans-serif\",\n        \"inputFontSize\": 14,\n        \"toolbarBg\": \"#441215\",\n        \"toolbarBorder\": \"#1f2937\",\n        \"toolbarText\": \"#94a3b8\",\n        \"toolbarBtnBg\": \"#0b0e13\",\n        \"toolbarBtnText\": \"#e2e8f0\",\n        \"minimapDots\": \"#94a3b8\",\n        \"canvasHintEnabled\": true,\n        \"canvasHintText\": \"\",\n        \"canvasHintBg\": \"#0f172a\",\n        \"canvasHintColor\": \"#94a3b8\",\n        \"danger\": \"#f56565\",\n        \"textMain\": \"#e2e8f0\",\n        \"textSoft\": \"#94a3b8\",\n        \"topbarHeight\": 112,\n        \"sidebarWidth\": 350,\n        \"mobileFooterHeight\": 40,\n        \"sidebarCollapsed\": false,\n        \"nodeFill\": \"#1e293b\",\n        \"nodeStroke\": \"#475569\",\n        \"nodeTitle\": \"#e2e8f0\",\n        \"nodeSub\": \"#94a3b8\",\n        \"nodeTitleSize\": 18,\n        \"nodeSubSize\": 13,\n        \"nodeFont\": \"Inter, system-ui, sans-serif\",\n        \"defaultEdge\": \"#475569\",\n        \"selectionHandle\": \"#f59e0b\",\n        \"selectionHandleSize\": 8,\n        \"groupIndicator\": \"#4fd1c5\",\n        \"canvasGradientTop\": \"#1e2532\",\n        \"canvasGradientBottom\": \"#050608\",\n        \"canvasBorder\": \"#475569\",\n        \"canvasGrid\": \"#475569\",\n        \"canvasGridSize\": 50,\n        \"canvasGridEnabled\": true,\n        \"rackFrameFill\": \"#0f172a\",\n        \"rackGridEnabled\": true,\n        \"rackFrameStroke\": \"#4fd1c5\",\n        \"rackLineColor\": \"#475569\",\n        \"rackTextColor\": \"#4fd1c5\",\n        \"viewOnly\": false,\n        \"defaultEdgeRouting\": \"curved\",\n        \"animateConnections\": false,\n        \"animationStyle\": \"arrows\",\n        \"animationDirection\": \"all\",\n        \"animationSpeed\": 1.5\n      }\n    }\n  ],\n  \"currentTabIndex\": 0,\n  \"encryptedSections\": {},\n  \"auditLog\": [\n    {\n      \"timestamp\": 1766459403229,\n      \"type\": \"export\",\n      \"description\": \"Exported CSV: the-one-file-corporate.csv\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459400738,\n      \"type\": \"tab\",\n      \"description\": \"Switched to tab: Corporate Site B\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459392434,\n      \"type\": \"export\",\n      \"description\": \"Exported CSV: the-one-file.csv\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766459386369,\n      \"type\": \"export\",\n      \"description\": \"Exported Markdown: the-one-file.md\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766459379962,\n      \"type\": \"export\",\n      \"description\": \"Exported JSON: the-one-file.json\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766459374396,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file.html\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766459370112,\n      \"type\": \"tab\",\n      \"description\": \"Switched to tab: Homelab 2\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766459361896,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file-corporate.html\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459352785,\n      \"type\": \"node\",\n      \"description\": \"rotate node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459352343,\n      \"type\": \"node\",\n      \"description\": \"rotate node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459352224,\n      \"type\": \"node\",\n      \"description\": \"rotate node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459351722,\n      \"type\": \"node\",\n      \"description\": \"rotate node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459351541,\n      \"type\": \"node\",\n      \"description\": \"rotate node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459350380,\n      \"type\": \"node\",\n      \"description\": \"resize node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459350178,\n      \"type\": \"node\",\n      \"description\": \"resize node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459350049,\n      \"type\": \"node\",\n      \"description\": \"resize node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459346233,\n      \"type\": \"node\",\n      \"description\": \"change shape\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459335960,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459335846,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459335742,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459335630,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459335398,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459335292,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459335188,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459332894,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459332780,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459332661,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459332556,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459332450,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459332346,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459331643,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459331492,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459331378,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459331274,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459330996,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459330868,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459330764,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459330637,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459327262,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459327136,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459326544,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459326438,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459326334,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459326176,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459325232,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459325088,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459324279,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459323835,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459323732,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459323200,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459323093,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459322989,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459322883,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459322780,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459321176,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459321070,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459320748,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459320642,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459320492,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459319706,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459319600,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459319055,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459318467,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459318363,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459318258,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459317846,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459317742,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459317464,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459317314,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459313457,\n      \"type\": \"node\",\n      \"description\": \"change shape\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459310142,\n      \"type\": \"node\",\n      \"description\": \"change shape\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459306160,\n      \"type\": \"node\",\n      \"description\": \"rotate node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459305289,\n      \"type\": \"node\",\n      \"description\": \"rotate node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459305132,\n      \"type\": \"node\",\n      \"description\": \"rotate node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459304675,\n      \"type\": \"node\",\n      \"description\": \"rotate node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459304530,\n      \"type\": \"node\",\n      \"description\": \"rotate node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459304396,\n      \"type\": \"node\",\n      \"description\": \"rotate node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459304290,\n      \"type\": \"node\",\n      \"description\": \"rotate node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459304157,\n      \"type\": \"node\",\n      \"description\": \"rotate node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459303660,\n      \"type\": \"node\",\n      \"description\": \"rotate node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459303534,\n      \"type\": \"node\",\n      \"description\": \"rotate node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459303414,\n      \"type\": \"node\",\n      \"description\": \"rotate node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459303247,\n      \"type\": \"node\",\n      \"description\": \"rotate node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459303144,\n      \"type\": \"node\",\n      \"description\": \"rotate node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459303002,\n      \"type\": \"node\",\n      \"description\": \"rotate node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459302875,\n      \"type\": \"node\",\n      \"description\": \"rotate node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459302725,\n      \"type\": \"node\",\n      \"description\": \"rotate node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459302613,\n      \"type\": \"node\",\n      \"description\": \"rotate node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459302507,\n      \"type\": \"node\",\n      \"description\": \"rotate node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459301997,\n      \"type\": \"node\",\n      \"description\": \"rotate node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459301893,\n      \"type\": \"node\",\n      \"description\": \"rotate node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458459721,\n      \"type\": \"tab\",\n      \"description\": \"Switched to tab: Corporate Site B\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458438687,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458438583,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458438437,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458438333,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458438187,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458438083,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458437937,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458437833,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458437687,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458437583,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458437437,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458437333,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458437187,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458436932,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458435139,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458434986,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458434840,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458434736,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458434590,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458434486,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458434340,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458434236,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458434090,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458433986,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458433840,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458433736,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458433590,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458433334,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458429157,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458429053,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458428947,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458426794,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458426691,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458426584,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458426481,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458423513,\n      \"type\": \"node\",\n      \"description\": \"change shape\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458421278,\n      \"type\": \"node\",\n      \"description\": \"change shape\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458416555,\n      \"type\": \"node\",\n      \"description\": \"change shape\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458404891,\n      \"type\": \"node\",\n      \"description\": \"add node\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458392272,\n      \"type\": \"node\",\n      \"description\": \"add node\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458378068,\n      \"type\": \"node\",\n      \"description\": \"add node\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458367460,\n      \"type\": \"node\",\n      \"description\": \"add node\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458356226,\n      \"type\": \"node\",\n      \"description\": \"add node\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458338198,\n      \"type\": \"tab\",\n      \"description\": \"Switched to tab: Homelab 2\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458258865,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file-corporate.html\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458249051,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458248926,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458248793,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458248683,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458248556,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458248451,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458248325,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458248221,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458248092,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458247989,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458247885,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458247784,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458247284,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458246701,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458246523,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458246410,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458246129,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458245955,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458245737,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458245627,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458245425,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458245247,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458245133,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458244923,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458244741,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458244313,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458244198,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458244055,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458243873,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458243637,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458243399,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458243218,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458241018,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458237254,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458235033,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458234835,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458234694,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458234425,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458227773,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458227623,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458227441,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458227279,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458227155,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458226967,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458226847,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458226733,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458226563,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458226421,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458222326,\n      \"type\": \"text\",\n      \"description\": \"add text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458213989,\n      \"type\": \"connection\",\n      \"description\": \"delete edge\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458209437,\n      \"type\": \"text\",\n      \"description\": \"delete text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458195427,\n      \"type\": \"import\",\n      \"description\": \"Imported JSON: the-one-file-corporate.json (107 nodes, 65 connections)\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455847368,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455844534,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455844054,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455843762,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455843560,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455843371,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455843162,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455842852,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455842747,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455842601,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455842449,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455842348,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455842098,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455841678,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455841236,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455841053,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455840901,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455840650,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455839427,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455839234,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455839061,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455837247,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455837081,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455836893,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455836377,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455836198,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455835455,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455834630,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455831880,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455831676,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455831451,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455830817,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455830687,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455830176,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455830048,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455829944,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455829816,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455378795,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455378693,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455378459,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455378316,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455378180,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455378069,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455377956,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455377677,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455377558,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455377448,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455377318,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455377209,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453090534,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453090317,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453090213,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453090112,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453090009,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453089903,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453088895,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453088793,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453088689,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453088584,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453088480,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453088250,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453087236,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453086725,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453086485,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453086373,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453086142,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453086043,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453072857,\n      \"type\": \"text\",\n      \"description\": \"paste text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453070975,\n      \"type\": \"text\",\n      \"description\": \"paste text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453054439,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453053127,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453052450,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453052106,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453051948,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453051806,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453051334,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453050207,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453042725,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453042179,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453041797,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453041570,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453039703,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453039291,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453039168,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453039065,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453038481,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453038365,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453038237,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453038105,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453038001,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453037850,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453037745,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453037495,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453037378,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453037182,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453037078,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453036972,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453036860,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453036147,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453035945,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453035825,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453035720,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453035443,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453035337,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453035233,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453035127,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453035026,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453034917,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453031063,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453030955,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453030833,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453030732,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453030225,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453030104,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453029968,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453029796,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453029474,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453024797,\n      \"type\": \"text\",\n      \"description\": \"paste text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766451118553,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450929324,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450817210,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450257424,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450255024,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450254395,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450253241,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450251598,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450250392,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450248756,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450244072,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450242166,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450240998,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450236492,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450233672,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450232384,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450231012,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450230254,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450229302,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450228132,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446610211,\n      \"type\": \"text\",\n      \"description\": \"paste text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446604849,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446604702,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446604550,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446604404,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446604305,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446604204,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446604099,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446603952,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446603849,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446603702,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446603599,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446603452,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446603348,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446603202,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446603099,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446602953,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446602850,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446602702,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446602600,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446602453,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446602349,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446602204,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446602101,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446602000,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446601848,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446601702,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446601601,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446601452,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446601301,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446601154,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446601049,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446600948,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446600802,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446600550,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446598595,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446598461,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446598171,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446598017,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446597219,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446595278,\n      \"type\": \"text\",\n      \"description\": \"add text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445633355,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445632515,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445631735,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445630757,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445627846,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445625085,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445618645,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445617784,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445608998,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445608720,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445608540,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445608376,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445608204,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445608038,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445607852,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445607678,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445607506,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445607319,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445607154,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445604410,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445604244,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445604066,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445603900,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445603743,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445603563,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445603406,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445603226,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445603052,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445602880,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445602641,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445576567,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animation speed\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445570290,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animation speed\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445567192,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445566766,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445565520,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445398115,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445390895,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445385694,\n      \"type\": \"edit\",\n      \"description\": \"toggle fov animation\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445383241,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445382911,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445381695,\n      \"type\": \"edit\",\n      \"description\": \"edit node name\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445375383,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445374665,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445373273,\n      \"type\": \"node\",\n      \"description\": \"paste node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445372205,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438157980,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438157430,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animation speed\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438152691,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438151948,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438151286,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438146174,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438145649,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438144555,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438143655,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438142504,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animation speed\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438130077,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438129561,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438128772,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438128398,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438122820,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438122062,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438119836,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438119588,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438095045,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438093965,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438062827,\n      \"type\": \"edit\",\n      \"description\": \"toggle fov animation\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438047679,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438044161,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438041852,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438039668,\n      \"type\": \"edit\",\n      \"description\": \"resize node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438039562,\n      \"type\": \"edit\",\n      \"description\": \"resize node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438039421,\n      \"type\": \"edit\",\n      \"description\": \"resize node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438039260,\n      \"type\": \"edit\",\n      \"description\": \"resize node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438039150,\n      \"type\": \"edit\",\n      \"description\": \"resize node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438039039,\n      \"type\": \"edit\",\n      \"description\": \"resize node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438028508,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438021410,\n      \"type\": \"edit\",\n      \"description\": \"toggle fov\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438019234,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438017562,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438014356,\n      \"type\": \"node\",\n      \"description\": \"add node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437981696,\n      \"type\": \"edit\",\n      \"description\": \"apply routing to all\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437966551,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437964879,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437963627,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437961813,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437961193,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437957989,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437956467,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437953437,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437952239,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437950807,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437944990,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437943699,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437935414,\n      \"type\": \"zone\",\n      \"description\": \"draw zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437919019,\n      \"type\": \"zone\",\n      \"description\": \"delete zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437917758,\n      \"type\": \"zone\",\n      \"description\": \"draw zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437913740,\n      \"type\": \"zone\",\n      \"description\": \"draw zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437882832,\n      \"type\": \"import\",\n      \"description\": \"Imported JSON: theonefile-networkening-corporate-demo.json (105 nodes, 65 connections)\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766263279163,\n      \"type\": \"tab\",\n      \"description\": \"Switched to tab: Corporate Site B\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766263270414,\n      \"type\": \"export\",\n      \"description\": \"Exported JSON: the-one-file.json\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766263260682,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file.html\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766263259518,\n      \"type\": \"tab\",\n      \"description\": \"Switched to tab: Homelab 2\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766263249401,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file-corporate.html\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766263246362,\n      \"type\": \"import\",\n      \"description\": \"Imported JSON: theonefile-networkening-corporate-demo.json (105 nodes, 65 connections)\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766190721141,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file-corporate.html\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766190717499,\n      \"type\": \"tab\",\n      \"description\": \"Switched to tab: Corporate Site B\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766190710946,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file.html\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766190705273,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file.html\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766190703463,\n      \"type\": \"tab\",\n      \"description\": \"Switched to tab: Homelab 2\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766190695709,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file-corporate.html\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766190688417,\n      \"type\": \"import\",\n      \"description\": \"Imported JSON: theonefile-networkening-corporate-demo.json (105 nodes, 65 connections)\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1765402888416,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file-corporate.html\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1765402884873,\n      \"type\": \"tab\",\n      \"description\": \"Switched to tab: Corporate Site B\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1765402878108,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file.html\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1765402866440,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file.html\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1765402865008,\n      \"type\": \"tab\",\n      \"description\": \"Switched to tab: Homelab 2\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1765402860428,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file-corporate.html\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1765402858103,\n      \"type\": \"import\",\n      \"description\": \"Imported JSON: theonefile-networkening-corporate-demo.json (105 nodes, 65 connections)\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    }\n  ],\n  \"savedStyleSets\": []\n}\nTHEONEFILE_CONFIG-->\n\n# The One File Corporate\n\n> Exported from The One File on 2025-12-23T03:10:10.065Z\n\n## Legend\n\n- #10b981: Trusted Lan\n- #f59e0b: Secure Lan\n- #ef4444: DMZ\n- #475569: Main ISP\n- #3b82f6: Alternate ISP\n- #8b5cf6: you can edit me too\n- #06b6d4: you can edit me too\n- #a855f7: you can edit me too\n- #f97316: you can edit me too\n- #0ea5e9: you can edit me too\n- #22c55e: you can edit me too\n- #94a3b8: you can edit me too\n- #fbbf24: you can edit me too\n- #38bdf8: you can edit me too\n- #c800ff: you can edit me too\n\n## Nodes\n\n### core-router-1\n- **Name:** Core Router 1\n- **IP:** 10.0.0.1\n- **Role:** Core Routing\n- **Shape:** router\n- **Tags:** core; tier-1; redundant\n- **Layer:** physical\n- **MAC:** 00:1A:2B:3C:4D:01\n- **Rack Unit:** \n- **U Height:** 2\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 3720, 246\n- **Size:** 50\n- **Notes:**\n  - Primary core router\n  - BGP peering enabled\n- **Styles:** `{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"borg\"}}}`\n\n### core-router-2\n- **Name:** Core Router 2\n- **IP:** 10.0.0.2\n- **Role:** Core Routing\n- **Shape:** router\n- **Tags:** core; tier-1; redundant\n- **Layer:** physical\n- **MAC:** 00:1A:2B:3C:4D:02\n- **Rack Unit:** \n- **U Height:** 2\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 2500, 330\n- **Size:** 120\n- **Notes:**\n  - Secondary core router\n  - HSRP standby\n- **Styles:** `{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"actual-budget\"},\"pingOffsetX\":-15,\"pingOffsetY\":-38}}`\n\n### fw-external-1\n- **Name:** External FW 1\n- **IP:** 10.0.1.1\n- **Role:** Perimeter Security\n- **Shape:** firewall\n- **Tags:** security; perimeter; ha-pair\n- **Layer:** security\n- **MAC:** 00:1A:2B:3C:4D:10\n- **Rack Unit:** \n- **U Height:** 2\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 3222, 1016\n- **Size:** 50\n- **Notes:**\n  - Palo Alto PA-5250\n  - Active node\n- **Styles:** `{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"anonaddy\"}}}`\n\n### fw-external-2\n- **Name:** External FW 2\n- **IP:** 10.0.1.2\n- **Role:** Perimeter Security\n- **Shape:** firewall\n- **Tags:** security; perimeter; ha-pair\n- **Layer:** security\n- **MAC:** 00:1A:2B:3C:4D:11\n- **Rack Unit:** \n- **U Height:** 2\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 1916, 224\n- **Size:** 50\n- **Notes:**\n  - Palo Alto PA-5250\n  - Passive node\n\n### fw-internal\n- **Name:** Internal FW\n- **IP:** 10.0.2.1\n- **Role:** Internal Segmentation\n- **Shape:** firewall\n- **Tags:** security; internal\n- **Layer:** security\n- **MAC:** 00:1A:2B:3C:4D:12\n- **Rack Unit:** \n- **U Height:** 2\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 1747, 478\n- **Size:** 50\n- **Notes:**\n  - East-West traffic inspection\n\n### core-switch-1\n- **Name:** Core Switch 1\n- **IP:** 10.0.10.1\n- **Role:** Core Switching\n- **Shape:** switch\n- **Tags:** core; layer3; redundant\n- **Layer:** physical\n- **MAC:** 00:1A:2B:3C:4D:20\n- **Rack Unit:** \n- **U Height:** 2\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 449, 384\n- **Size:** 50\n- **Notes:**\n  - Cisco Nexus 9000\n  - VPC Domain 1\n\n### core-switch-2\n- **Name:** Core Switch 2\n- **IP:** 10.0.10.2\n- **Role:** Core Switching\n- **Shape:** switch\n- **Tags:** core; layer3; redundant\n- **Layer:** physical\n- **MAC:** 00:1A:2B:3C:4D:21\n- **Rack Unit:** \n- **U Height:** 2\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 761, 181\n- **Size:** 50\n- **Notes:**\n  - Cisco Nexus 9000\n  - VPC Domain 1\n\n### dc-rack-a1\n- **Name:** DC Rack A1\n- **IP:** 10.10.0.0/24\n- **Role:** Data Center Rack\n- **Shape:** server\n- **Tags:** datacenter; row-a; production\n- **Layer:** physical\n- **MAC:** \n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** true\n- **Locked:** false\n- **Group ID:** \n- **Position:** 784, 647\n- **Size:** 50\n- **Notes:**\n  - Row A, Position 1\n  - Primary compute\n- **Styles:** `{\"all\":{\"circleColor\":\"#ff0000\"}}`\n\n### dc-rack-a2\n- **Name:** DC Rack A2\n- **IP:** 10.10.1.0/24\n- **Role:** Data Center Rack\n- **Shape:** server\n- **Tags:** datacenter; row-a; production\n- **Layer:** physical\n- **MAC:** \n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** true\n- **Locked:** false\n- **Group ID:** \n- **Position:** 209, 228\n- **Size:** 50\n- **Notes:**\n  - Row A, Position 2\n  - Primary compute\n\n### dc-rack-b1\n- **Name:** DC Rack B1\n- **IP:** 10.10.2.0/24\n- **Role:** Data Center Rack\n- **Shape:** server\n- **Tags:** datacenter; row-b; storage\n- **Layer:** physical\n- **MAC:** \n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** true\n- **Locked:** false\n- **Group ID:** \n- **Position:** 3184, 1627\n- **Size:** 50\n- **Notes:**\n  - Row B, Position 1\n  - Storage systems\n- **Styles:** `{\"all\":{\"circleColor\":\"#ff0000\",\"titleSize\":59}}`\n\n### dc-rack-b2\n- **Name:** DC Rack B2\n- **IP:** 10.10.3.0/24\n- **Role:** Data Center Rack\n- **Shape:** server\n- **Tags:** datacenter; row-b; storage\n- **Layer:** physical\n- **MAC:** \n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** true\n- **Locked:** false\n- **Group ID:** \n- **Position:** 245, 500\n- **Size:** 50\n- **Notes:**\n  - Row B, Position 2\n  - Storage systems\n- **Styles:** `{\"all\":{\"circleColor\":\"#ff0000\"}}`\n\n### dmz-rack\n- **Name:** DMZ Rack\n- **IP:** 172.16.0.0/24\n- **Role:** DMZ Infrastructure\n- **Shape:** server\n- **Tags:** dmz; security; public-facing; [object Object]; [object Object]\n- **Layer:** security\n- **MAC:** \n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 24\n- **Is Rack:** true\n- **Locked:** false\n- **Group ID:** \n- **Position:** 2176, 611\n- **Size:** 50\n- **Notes:**\n  - Isolated DMZ zone\n  - Public-facing services\n\n### mgmt-rack\n- **Name:** Management Rack\n- **IP:** 192.168.100.0/24\n- **Role:** Management Infrastructure\n- **Shape:** server\n- **Tags:** management; oob; noc\n- **Layer:** logical\n- **MAC:** \n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 24\n- **Is Rack:** true\n- **Locked:** false\n- **Group ID:** \n- **Position:** 1601, 1281\n- **Size:** 50\n- **Notes:**\n  - Out-of-band management\n  - NOC equipment\n\n### esxi-host-01\n- **Name:** ESXi Host 01\n- **IP:** 10.10.0.11\n- **Role:** Hypervisor\n- **Shape:** server\n- **Tags:** vmware; compute; cluster-a\n- **Layer:** physical\n- **MAC:** 00:50:56:AA:01:01\n- **Rack Unit:** 38\n- **U Height:** 2\n- **Assigned Rack:** dc-rack-a1\n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 2162, 2608\n- **Size:** 50\n- **Notes:**\n  - Dell PowerEdge R750\n  - 512GB RAM\n  - vSphere 8.0\n\n### esxi-host-02\n- **Name:** ESXi Host 02\n- **IP:** 10.10.0.12\n- **Role:** Hypervisor\n- **Shape:** server\n- **Tags:** vmware; compute; cluster-a\n- **Layer:** physical\n- **MAC:** 00:50:56:AA:01:02\n- **Rack Unit:** 35\n- **U Height:** 2\n- **Assigned Rack:** dc-rack-a1\n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 2206, 2690\n- **Size:** 50\n- **Notes:**\n  - Dell PowerEdge R750\n  - 512GB RAM\n  - vSphere 8.0\n\n### esxi-host-03\n- **Name:** ESXi Host 03\n- **IP:** 10.10.0.13\n- **Role:** Hypervisor\n- **Shape:** server\n- **Tags:** vmware; compute; cluster-a\n- **Layer:** physical\n- **MAC:** 00:50:56:AA:01:03\n- **Rack Unit:** 32\n- **U Height:** 2\n- **Assigned Rack:** dc-rack-a1\n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 2155, 2771\n- **Size:** 50\n- **Notes:**\n  - Dell PowerEdge R750\n  - 512GB RAM\n  - vSphere 8.0\n\n### esxi-host-04\n- **Name:** ESXi Host 04\n- **IP:** 10.10.0.14\n- **Role:** Hypervisor\n- **Shape:** server\n- **Tags:** vmware; compute; cluster-a\n- **Layer:** physical\n- **MAC:** 00:50:56:AA:01:04\n- **Rack Unit:** 29\n- **U Height:** 2\n- **Assigned Rack:** dc-rack-a1\n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 2196, 2845\n- **Size:** 50\n- **Notes:**\n  - Dell PowerEdge R750\n  - 512GB RAM\n  - vSphere 8.0\n\n### tor-switch-a1\n- **Name:** ToR Switch A1\n- **IP:** 10.10.0.1\n- **Role:** Top of Rack\n- **Shape:** switch\n- **Tags:** tor; access; rack-a1\n- **Layer:** physical\n- **MAC:** 00:1A:2B:3C:5D:01\n- **Rack Unit:** 42\n- **U Height:** 1\n- **Assigned Rack:** dc-rack-a1\n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 2147, 2845\n- **Size:** 50\n- **Notes:**\n  - Cisco Nexus 93180YC-FX\n  - 48x25G ports\n\n### esxi-host-05\n- **Name:** ESXi Host 05\n- **IP:** 10.10.1.11\n- **Role:** Hypervisor\n- **Shape:** server\n- **Tags:** vmware; compute; cluster-b\n- **Layer:** physical\n- **MAC:** 00:50:56:AA:02:01\n- **Rack Unit:** 38\n- **U Height:** 2\n- **Assigned Rack:** dc-rack-a2\n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 2186, 2845\n- **Size:** 50\n- **Notes:**\n  - Dell PowerEdge R750\n  - 768GB RAM\n  - vSphere 8.0\n\n### esxi-host-06\n- **Name:** ESXi Host 06\n- **IP:** 10.10.1.12\n- **Role:** Hypervisor\n- **Shape:** server\n- **Tags:** vmware; compute; cluster-b\n- **Layer:** physical\n- **MAC:** 00:50:56:AA:02:02\n- **Rack Unit:** 35\n- **U Height:** 2\n- **Assigned Rack:** dc-rack-a2\n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 2139, 2845\n- **Size:** 50\n- **Notes:**\n  - Dell PowerEdge R750\n  - 768GB RAM\n  - vSphere 8.0\n\n### esxi-host-07\n- **Name:** ESXi Host 07\n- **IP:** 10.10.1.13\n- **Role:** Hypervisor\n- **Shape:** server\n- **Tags:** vmware; compute; cluster-b\n- **Layer:** physical\n- **MAC:** 00:50:56:AA:02:03\n- **Rack Unit:** 32\n- **U Height:** 2\n- **Assigned Rack:** dc-rack-a2\n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 2176, 2845\n- **Size:** 50\n- **Notes:**\n  - Dell PowerEdge R750\n  - 768GB RAM\n  - vSphere 8.0\n\n### esxi-host-08\n- **Name:** ESXi Host 08\n- **IP:** 10.10.1.14\n- **Role:** Hypervisor\n- **Shape:** server\n- **Tags:** vmware; compute; cluster-b\n- **Layer:** physical\n- **MAC:** 00:50:56:AA:02:04\n- **Rack Unit:** 29\n- **U Height:** 2\n- **Assigned Rack:** dc-rack-a2\n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 2131, 2845\n- **Size:** 50\n- **Notes:**\n  - Dell PowerEdge R750\n  - 768GB RAM\n  - vSphere 8.0\n\n### tor-switch-a2\n- **Name:** ToR Switch A2\n- **IP:** 10.10.1.1\n- **Role:** Top of Rack\n- **Shape:** switch\n- **Tags:** tor; access; rack-a2\n- **Layer:** physical\n- **MAC:** 00:1A:2B:3C:5D:02\n- **Rack Unit:** 42\n- **U Height:** 1\n- **Assigned Rack:** dc-rack-a2\n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 2165, 2845\n- **Size:** 50\n- **Notes:**\n  - Cisco Nexus 93180YC-FX\n  - 48x25G ports\n\n### san-primary\n- **Name:** SAN Primary\n- **IP:** 10.10.2.10\n- **Role:** Primary Storage\n- **Shape:** database\n- **Tags:** storage; san; netapp\n- **Layer:** physical\n- **MAC:** 00:A0:98:AA:01:01\n- **Rack Unit:** 36\n- **U Height:** 6\n- **Assigned Rack:** dc-rack-b1\n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 2123, 2845\n- **Size:** 50\n- **Notes:**\n  - NetApp AFF A400\n  - 500TB Raw\n  - FC 32Gb\n\n### san-secondary\n- **Name:** SAN Secondary\n- **IP:** 10.10.2.11\n- **Role:** Secondary Storage\n- **Shape:** database\n- **Tags:** storage; san; netapp\n- **Layer:** physical\n- **MAC:** 00:A0:98:AA:01:02\n- **Rack Unit:** 28\n- **U Height:** 6\n- **Assigned Rack:** dc-rack-b1\n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 2155, 2845\n- **Size:** 50\n- **Notes:**\n  - NetApp AFF A400\n  - 500TB Raw\n  - FC 32Gb\n\n### fc-switch-1\n- **Name:** FC Switch 1\n- **IP:** 10.10.2.1\n- **Role:** Fibre Channel\n- **Shape:** switch\n- **Tags:** storage; fc; fabric-a\n- **Layer:** physical\n- **MAC:** 00:1A:2B:FC:01:01\n- **Rack Unit:** 42\n- **U Height:** 1\n- **Assigned Rack:** dc-rack-b1\n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 2115, 2845\n- **Size:** 50\n- **Notes:**\n  - Brocade G620\n  - Fabric A\n\n### fc-switch-2\n- **Name:** FC Switch 2\n- **IP:** 10.10.2.2\n- **Role:** Fibre Channel\n- **Shape:** switch\n- **Tags:** storage; fc; fabric-b\n- **Layer:** physical\n- **MAC:** 00:1A:2B:FC:01:02\n- **Rack Unit:** 41\n- **U Height:** 1\n- **Assigned Rack:** dc-rack-b1\n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 2145, 2845\n- **Size:** 50\n- **Notes:**\n  - Brocade G620\n  - Fabric B\n\n### backup-server-1\n- **Name:** Backup Server 1\n- **IP:** 10.10.3.10\n- **Role:** Backup Infrastructure\n- **Shape:** server\n- **Tags:** backup; veeam; protection\n- **Layer:** physical\n- **MAC:** 00:50:56:BB:01:01\n- **Rack Unit:** 36\n- **U Height:** 2\n- **Assigned Rack:** dc-rack-b2\n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 2107, 2845\n- **Size:** 50\n- **Notes:**\n  - Veeam Backup Server\n  - Dell R740xd\n  - 200TB\n\n### backup-server-2\n- **Name:** Backup Server 2\n- **IP:** 10.10.3.11\n- **Role:** Backup Infrastructure\n- **Shape:** server\n- **Tags:** backup; veeam; protection\n- **Layer:** physical\n- **MAC:** 00:50:56:BB:01:02\n- **Rack Unit:** 33\n- **U Height:** 2\n- **Assigned Rack:** dc-rack-b2\n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 2134, 2845\n- **Size:** 50\n- **Notes:**\n  - Veeam Backup Server\n  - Dell R740xd\n  - 200TB\n\n### tape-library\n- **Name:** Tape Library\n- **IP:** 10.10.3.20\n- **Role:** Archival Storage\n- **Shape:** database\n- **Tags:** backup; tape; lto9\n- **Layer:** physical\n- **MAC:** 00:50:56:BB:02:01\n- **Rack Unit:** 20\n- **U Height:** 10\n- **Assigned Rack:** dc-rack-b2\n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 2099, 2845\n- **Size:** 50\n- **Notes:**\n  - IBM TS4500\n  - LTO-9\n  - Long-term archive\n\n### tor-switch-b1\n- **Name:** ToR Switch B1\n- **IP:** 10.10.2.3\n- **Role:** Top of Rack\n- **Shape:** switch\n- **Tags:** tor; access; rack-b1\n- **Layer:** physical\n- **MAC:** 00:1A:2B:3C:5D:03\n- **Rack Unit:** 40\n- **U Height:** 1\n- **Assigned Rack:** dc-rack-b1\n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 2123, 2845\n- **Size:** 50\n- **Notes:**\n  - Cisco Nexus 93180YC-FX\n\n### tor-switch-b2\n- **Name:** ToR Switch B2\n- **IP:** 10.10.3.1\n- **Role:** Top of Rack\n- **Shape:** switch\n- **Tags:** tor; access; rack-b2\n- **Layer:** physical\n- **MAC:** 00:1A:2B:3C:5D:04\n- **Rack Unit:** 42\n- **U Height:** 1\n- **Assigned Rack:** dc-rack-b2\n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 2091, 2845\n- **Size:** 50\n- **Notes:**\n  - Cisco Nexus 93180YC-FX\n\n### web-server-1\n- **Name:** Web Server 1\n- **IP:** 172.16.0.11\n- **Role:** Web Frontend\n- **Shape:** server\n- **Tags:** dmz; web; nginx\n- **Layer:** security\n- **MAC:** 00:50:56:CC:01:01\n- **Rack Unit:** 20\n- **U Height:** 1\n- **Assigned Rack:** dmz-rack\n- **Rack Capacity:** 24\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 2113, 2845\n- **Size:** 50\n- **Notes:**\n  - NGINX reverse proxy\n  - Public facing\n\n### web-server-2\n- **Name:** Web Server 2\n- **IP:** 172.16.0.12\n- **Role:** Web Frontend\n- **Shape:** server\n- **Tags:** dmz; web; nginx\n- **Layer:** security\n- **MAC:** 00:50:56:CC:01:02\n- **Rack Unit:** 18\n- **U Height:** 1\n- **Assigned Rack:** dmz-rack\n- **Rack Capacity:** 24\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 2082, 2845\n- **Size:** 50\n- **Notes:**\n  - NGINX reverse proxy\n  - Public facing\n\n### waf-1\n- **Name:** WAF Appliance\n- **IP:** 172.16.0.5\n- **Role:** Web Application Firewall\n- **Shape:** firewall\n- **Tags:** dmz; security; waf\n- **Layer:** security\n- **MAC:** 00:50:56:CC:02:01\n- **Rack Unit:** 22\n- **U Height:** 2\n- **Assigned Rack:** dmz-rack\n- **Rack Capacity:** 24\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 2102, 2845\n- **Size:** 50\n- **Notes:**\n  - F5 BIG-IP ASM\n  - OWASP protection\n\n### load-balancer-dmz\n- **Name:** DMZ Load Balancer\n- **IP:** 172.16.0.3\n- **Role:** Load Balancing\n- **Shape:** switch\n- **Tags:** dmz; lb; f5\n- **Layer:** security\n- **MAC:** 00:50:56:CC:03:01\n- **Rack Unit:** 16\n- **U Height:** 2\n- **Assigned Rack:** dmz-rack\n- **Rack Capacity:** 24\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 2074, 2845\n- **Size:** 50\n- **Notes:**\n  - F5 BIG-IP LTM\n  - VIP: 172.16.0.100\n\n### mail-gateway\n- **Name:** Mail Gateway\n- **IP:** 172.16.0.25\n- **Role:** Email Security\n- **Shape:** server\n- **Tags:** dmz; email; security\n- **Layer:** security\n- **MAC:** 00:50:56:CC:04:01\n- **Rack Unit:** 14\n- **U Height:** 1\n- **Assigned Rack:** dmz-rack\n- **Rack Capacity:** 24\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 2091, 2845\n- **Size:** 50\n- **Notes:**\n  - Proofpoint Email Gateway\n  - Spam/malware filtering\n\n### dns-external-1\n- **Name:** External DNS 1\n- **IP:** 172.16.0.53\n- **Role:** External DNS\n- **Shape:** circle\n- **Tags:** dmz; dns; public\n- **Layer:** security\n- **MAC:** 00:50:56:CC:05:01\n- **Rack Unit:** 12\n- **U Height:** 1\n- **Assigned Rack:** dmz-rack\n- **Rack Capacity:** 24\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 2066, 2845\n- **Size:** 50\n- **Notes:**\n  - BIND DNS\n  - Authoritative for corp.com\n\n### dns-external-2\n- **Name:** External DNS 2\n- **IP:** 172.16.0.54\n- **Role:** External DNS\n- **Shape:** circle\n- **Tags:** dmz; dns; public\n- **Layer:** security\n- **MAC:** 00:50:56:CC:05:02\n- **Rack Unit:** 10\n- **U Height:** 1\n- **Assigned Rack:** dmz-rack\n- **Rack Capacity:** 24\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 2080, 2845\n- **Size:** 50\n- **Notes:**\n  - BIND DNS\n  - Secondary for corp.com\n\n### vcenter\n- **Name:** vCenter Server\n- **IP:** 192.168.100.10\n- **Role:** Virtualization Management\n- **Shape:** server\n- **Tags:** management; vmware; vcsa\n- **Layer:** logical\n- **MAC:** 00:50:56:DD:01:01\n- **Rack Unit:** 20\n- **U Height:** 2\n- **Assigned Rack:** mgmt-rack\n- **Rack Capacity:** 24\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 2057, 2845\n- **Size:** 50\n- **Notes:**\n  - vCenter Server Appliance 8.0\n  - Single SSO domain\n\n### nsx-manager\n- **Name:** NSX Manager\n- **IP:** 192.168.100.15\n- **Role:** Network Virtualization\n- **Shape:** server\n- **Tags:** management; vmware; nsx\n- **Layer:** logical\n- **MAC:** 00:50:56:DD:02:01\n- **Rack Unit:** 17\n- **U Height:** 2\n- **Assigned Rack:** mgmt-rack\n- **Rack Capacity:** 24\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 2069, 2845\n- **Size:** 50\n- **Notes:**\n  - NSX-T 4.1 Manager Cluster\n\n### siem-server\n- **Name:** SIEM Server\n- **IP:** 192.168.100.50\n- **Role:** Security Monitoring\n- **Shape:** server\n- **Tags:** management; security; splunk\n- **Layer:** logical\n- **MAC:** 00:50:56:DD:03:01\n- **Rack Unit:** 14\n- **U Height:** 2\n- **Assigned Rack:** mgmt-rack\n- **Rack Capacity:** 24\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 2049, 2845\n- **Size:** 50\n- **Notes:**\n  - Splunk Enterprise\n  - Security monitoring\n\n### nms-server\n- **Name:** Network Monitoring\n- **IP:** 192.168.100.60\n- **Role:** Network Management\n- **Shape:** server\n- **Tags:** management; monitoring; prtg\n- **Layer:** logical\n- **MAC:** 00:50:56:DD:04:01\n- **Rack Unit:** 11\n- **U Height:** 1\n- **Assigned Rack:** mgmt-rack\n- **Rack Capacity:** 24\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 2058, 2845\n- **Size:** 50\n- **Notes:**\n  - PRTG Network Monitor\n  - 5000 sensors\n\n### jump-server\n- **Name:** Jump Server\n- **IP:** 192.168.100.100\n- **Role:** Bastion Host\n- **Shape:** server\n- **Tags:** management; security; bastion\n- **Layer:** logical\n- **MAC:** 00:50:56:DD:05:01\n- **Rack Unit:** 9\n- **U Height:** 1\n- **Assigned Rack:** mgmt-rack\n- **Rack Capacity:** 24\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 2040, 2845\n- **Size:** 50\n- **Notes:**\n  - Windows Server 2022\n  - MFA enabled\n\n### ipam-server\n- **Name:** IPAM/DDI\n- **IP:** 192.168.100.70\n- **Role:** IP Management\n- **Shape:** server\n- **Tags:** management; dns; dhcp\n- **Layer:** logical\n- **MAC:** 00:50:56:DD:06:01\n- **Rack Unit:** 7\n- **U Height:** 2\n- **Assigned Rack:** mgmt-rack\n- **Rack Capacity:** 24\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 2047, 2845\n- **Size:** 50\n- **Notes:**\n  - Infoblox DDI\n  - DNS/DHCP/IPAM\n\n### wlc-primary\n- **Name:** WLC Primary\n- **IP:** 10.20.0.1\n- **Role:** Wireless Controller\n- **Shape:** wifi\n- **Tags:** wireless; cisco; 9800\n- **Layer:** physical\n- **MAC:** 00:1A:2B:WL:01:01\n- **Rack Unit:** \n- **U Height:** 2\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 1576, 2306\n- **Size:** 50\n- **Notes:**\n  - Cisco C9800-40\n  - Primary controller\n\n### wlc-secondary\n- **Name:** WLC Secondary\n- **IP:** 10.20.0.2\n- **Role:** Wireless Controller\n- **Shape:** wifi\n- **Tags:** wireless; cisco; 9800\n- **Layer:** physical\n- **MAC:** 00:1A:2B:WL:01:02\n- **Rack Unit:** \n- **U Height:** 2\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 1468, 1564\n- **Size:** 50\n- **Notes:**\n  - Cisco C9800-40\n  - HA Secondary\n\n### mobile-zone-hq\n- **Name:** HQ Mobile Zone\n- **IP:** 10.20.10.0/24\n- **Role:** Mobile Device Zone\n- **Shape:** phone\n- **Tags:** wireless; byod; mobile\n- **Layer:** physical\n- **MAC:** \n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 2355, 2806\n- **Size:** 50\n- **Notes:**\n  - Corporate BYOD\n  - MDM enrolled devices\n\n### mobile-zone-guest\n- **Name:** Guest WiFi Zone\n- **IP:** 10.30.0.0/24\n- **Role:** Guest Network\n- **Shape:** phone\n- **Tags:** wireless; guest; isolated\n- **Layer:** physical\n- **MAC:** \n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 2308, 2611\n- **Size:** 50\n- **Notes:**\n  - Captive portal\n  - Internet only\n\n### mobile-zone-iot\n- **Name:** IoT Device Zone\n- **IP:** 10.40.0.0/24\n- **Role:** IoT Network\n- **Shape:** phone\n- **Tags:** wireless; iot; building\n- **Layer:** physical\n- **MAC:** \n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 2229, 2299\n- **Size:** 50\n- **Notes:**\n  - Building automation\n  - Smart devices\n\n### branch-router-ny\n- **Name:** NYC Branch Router\n- **IP:** 10.100.0.1\n- **Role:** Branch Gateway\n- **Shape:** router\n- **Tags:** branch; nyc; sd-wan\n- **Layer:** physical\n- **MAC:** 00:1A:2B:BR:01:01\n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 3152, 634\n- **Size:** 50\n- **Notes:**\n  - Cisco Viptela vEdge\n  - SD-WAN enabled\n\n### branch-router-la\n- **Name:** LA Branch Router\n- **IP:** 10.101.0.1\n- **Role:** Branch Gateway\n- **Shape:** router\n- **Tags:** branch; la; sd-wan\n- **Layer:** physical\n- **MAC:** 00:1A:2B:BR:02:01\n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 3084, 507\n- **Size:** 50\n- **Notes:**\n  - Cisco Viptela vEdge\n  - SD-WAN enabled\n\n### branch-router-chi\n- **Name:** Chicago Branch Router\n- **IP:** 10.102.0.1\n- **Role:** Branch Gateway\n- **Shape:** router\n- **Tags:** branch; chicago; sd-wan\n- **Layer:** physical\n- **MAC:** 00:1A:2B:BR:03:01\n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 3355, 393\n- **Size:** 50\n- **Notes:**\n  - Cisco Viptela vEdge\n  - SD-WAN enabled\n\n### branch-router-lon\n- **Name:** London Branch Router\n- **IP:** 10.200.0.1\n- **Role:** Branch Gateway\n- **Shape:** router\n- **Tags:** branch; london; sd-wan\n- **Layer:** physical\n- **MAC:** 00:1A:2B:BR:04:01\n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 3114, 260\n- **Size:** 50\n- **Notes:**\n  - Cisco Viptela vEdge\n  - EMEA region\n\n### branch-router-tokyo\n- **Name:** Tokyo Branch Router\n- **IP:** 10.201.0.1\n- **Role:** Branch Gateway\n- **Shape:** router\n- **Tags:** branch; tokyo; sd-wan\n- **Layer:** physical\n- **MAC:** 00:1A:2B:BR:05:01\n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 3699, 471\n- **Size:** 50\n- **Notes:**\n  - Cisco Viptela vEdge\n  - APAC region\n- **Styles:** `{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"adguard-home\"}}}`\n\n### cloud-aws\n- **Name:** AWS Cloud\n- **IP:** vpc-0a1b2c3d\n- **Role:** Public Cloud\n- **Shape:** cloud\n- **Tags:** cloud; aws; hybrid\n- **Layer:** logical\n- **MAC:** \n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 3437, 546\n- **Size:** 50\n- **Notes:**\n  - AWS US-East-1\n  - VPC peering to HQ\n- **Styles:** `{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"ansible\"}}}`\n\n### cloud-azure\n- **Name:** Azure Cloud\n- **IP:** vnet-corp-prod\n- **Role:** Public Cloud\n- **Shape:** cloud\n- **Tags:** cloud; azure; hybrid\n- **Layer:** logical\n- **MAC:** \n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 2593, 2724\n- **Size:** 50\n- **Notes:**\n  - Azure East US 2\n  - ExpressRoute\n\n### cloud-gcp\n- **Name:** GCP Cloud\n- **IP:** vpc-gcp-corp\n- **Role:** Public Cloud\n- **Shape:** cloud\n- **Tags:** cloud; gcp; dev\n- **Layer:** logical\n- **MAC:** \n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 2827, 2731\n- **Size:** 50\n- **Notes:**\n  - GCP us-central1\n  - Dev/Test workloads\n\n### isp-primary\n- **Name:** ISP Primary\n- **IP:** 203.0.113.1\n- **Role:** Internet Uplink\n- **Shape:** globe\n- **Tags:** wan; internet; primary\n- **Layer:** physical\n- **MAC:** \n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 3712, 616\n- **Size:** 50\n- **Notes:**\n  - AT&T MPLS\n  - 1 Gbps dedicated\n- **Styles:** `{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"wikidocs\"}}}`\n\n### isp-secondary\n- **Name:** ISP Secondary\n- **IP:** 198.51.100.1\n- **Role:** Internet Uplink\n- **Shape:** vm\n- **Tags:** wan; internet; backup\n- **Layer:** physical\n- **MAC:** \n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 3254, 1993\n- **Size:** 139\n- **Notes:**\n  - Verizon Business\n  - 500 Mbps backup\n- **Styles:** `{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"alist\"},\"circleColor\":\"#4d2c58\",\"circleBorder\":\"#000000\",\"titleColor\":\"#006eff\"}}`\n\n### dc-internal-1\n- **Name:** DC1 Int DNS\n- **IP:** 10.10.0.53\n- **Role:** Internal DNS/AD\n- **Shape:** circle\n- **Tags:** dns; ad; dc1\n- **Layer:** physical\n- **MAC:** 00:50:56:AD:01:01\n- **Rack Unit:** 26\n- **U Height:** 1\n- **Assigned Rack:** dc-rack-a1\n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 1958, 2845\n- **Size:** 50\n- **Notes:**\n  - Windows Server 2022\n  - Primary DC\n\n### dc-internal-2\n- **Name:** DC2 Int DNS\n- **IP:** 10.10.1.53\n- **Role:** Internal DNS/AD\n- **Shape:** circle\n- **Tags:** dns; ad; dc2\n- **Layer:** physical\n- **MAC:** 00:50:56:AD:01:02\n- **Rack Unit:** 26\n- **U Height:** 1\n- **Assigned Rack:** dc-rack-a2\n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 1964, 2845\n- **Size:** 50\n- **Notes:**\n  - Windows Server 2022\n  - Secondary DC\n\n### app-server-1\n- **Name:** App Server 01\n- **IP:** 10.10.0.101\n- **Role:** Application\n- **Shape:** server\n- **Tags:** app; iis; web\n- **Layer:** physical\n- **MAC:** 00:50:56:AP:01:01\n- **Rack Unit:** 24\n- **U Height:** 1\n- **Assigned Rack:** dc-rack-a1\n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 1947, 2845\n- **Size:** 50\n- **Notes:**\n  - Windows Server 2022\n  - IIS Application\n\n### app-server-2\n- **Name:** App Server 02\n- **IP:** 10.10.0.102\n- **Role:** Application\n- **Shape:** server\n- **Tags:** app; iis; web\n- **Layer:** physical\n- **MAC:** 00:50:56:AP:01:02\n- **Rack Unit:** 22\n- **U Height:** 1\n- **Assigned Rack:** dc-rack-a1\n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 1955, 2845\n- **Size:** 50\n- **Notes:**\n  - Windows Server 2022\n  - IIS Application\n\n### db-server-1\n- **Name:** SQL Server 01\n- **IP:** 10.10.0.201\n- **Role:** Database\n- **Shape:** database\n- **Tags:** db; sql; primary\n- **Layer:** physical\n- **MAC:** 00:50:56:DB:01:01\n- **Rack Unit:** 20\n- **U Height:** 2\n- **Assigned Rack:** dc-rack-a1\n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 1936, 2845\n- **Size:** 50\n- **Notes:**\n  - SQL Server 2022 Enterprise\n  - AlwaysOn Primary\n\n### db-server-2\n- **Name:** SQL Server 02\n- **IP:** 10.10.1.201\n- **Role:** Database\n- **Shape:** database\n- **Tags:** db; sql; secondary\n- **Layer:** physical\n- **MAC:** 00:50:56:DB:01:02\n- **Rack Unit:** 24\n- **U Height:** 2\n- **Assigned Rack:** dc-rack-a2\n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 1947, 2845\n- **Size:** 50\n- **Notes:**\n  - SQL Server 2022 Enterprise\n  - AlwaysOn Secondary\n\n### k8s-master-1\n- **Name:** K8s Master 1\n- **IP:** 10.10.1.50\n- **Role:** Container Orchestration\n- **Shape:** hexagon\n- **Tags:** kubernetes; master; container\n- **Layer:** physical\n- **MAC:** 00:50:56:K8:01:01\n- **Rack Unit:** 21\n- **U Height:** 1\n- **Assigned Rack:** dc-rack-a2\n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 1925, 2845\n- **Size:** 50\n- **Notes:**\n  - K8s Control Plane\n  - etcd member\n\n### k8s-master-2\n- **Name:** K8s Master 2\n- **IP:** 10.10.1.51\n- **Role:** Container Orchestration\n- **Shape:** hexagon\n- **Tags:** kubernetes; master; container\n- **Layer:** physical\n- **MAC:** 00:50:56:K8:01:02\n- **Rack Unit:** 19\n- **U Height:** 1\n- **Assigned Rack:** dc-rack-a2\n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 1938, 2845\n- **Size:** 50\n- **Notes:**\n  - K8s Control Plane\n  - etcd member\n\n### k8s-master-3\n- **Name:** K8s Master 3\n- **IP:** 10.10.1.52\n- **Role:** Container Orchestration\n- **Shape:** hexagon\n- **Tags:** kubernetes; master; container\n- **Layer:** physical\n- **MAC:** 00:50:56:K8:01:03\n- **Rack Unit:** 17\n- **U Height:** 1\n- **Assigned Rack:** dc-rack-a2\n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 1914, 2845\n- **Size:** 50\n- **Notes:**\n  - K8s Control Plane\n  - etcd member\n\n### k8s-worker-1\n- **Name:** K8s Worker 1\n- **IP:** 10.10.1.60\n- **Role:** Container Workload\n- **Shape:** server\n- **Tags:** kubernetes; worker; container\n- **Layer:** physical\n- **MAC:** 00:50:56:K8:02:01\n- **Rack Unit:** 15\n- **U Height:** 1\n- **Assigned Rack:** dc-rack-a2\n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 1930, 2845\n- **Size:** 50\n- **Notes:**\n  - K8s Worker Node\n  - 64GB RAM\n\n### k8s-worker-2\n- **Name:** K8s Worker 2\n- **IP:** 10.10.1.61\n- **Role:** Container Workload\n- **Shape:** server\n- **Tags:** kubernetes; worker; container\n- **Layer:** physical\n- **MAC:** 00:50:56:K8:02:02\n- **Rack Unit:** 13\n- **U Height:** 1\n- **Assigned Rack:** dc-rack-a2\n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 1904, 2845\n- **Size:** 50\n- **Notes:**\n  - K8s Worker Node\n  - 64GB RAM\n\n### k8s-worker-3\n- **Name:** K8s Worker 3\n- **IP:** 10.10.1.62\n- **Role:** Container Workload\n- **Shape:** server\n- **Tags:** kubernetes; worker; container\n- **Layer:** physical\n- **MAC:** 00:50:56:K8:02:03\n- **Rack Unit:** 11\n- **U Height:** 1\n- **Assigned Rack:** dc-rack-a2\n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 1922, 2845\n- **Size:** 50\n- **Notes:**\n  - K8s Worker Node\n  - 64GB RAM\n\n### k8s-worker-4\n- **Name:** K8s Worker 4\n- **IP:** 10.10.1.63\n- **Role:** Container Workload\n- **Shape:** server\n- **Tags:** kubernetes; worker; container\n- **Layer:** physical\n- **MAC:** 00:50:56:K8:02:04\n- **Rack Unit:** 9\n- **U Height:** 1\n- **Assigned Rack:** dc-rack-a2\n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 1893, 2845\n- **Size:** 50\n- **Notes:**\n  - K8s Worker Node\n  - 64GB RAM\n\n### proxy-server-1\n- **Name:** Proxy Server 1\n- **IP:** 10.5.0.10\n- **Role:** Web Proxy\n- **Shape:** server\n- **Tags:** proxy; squid; filtering\n- **Layer:** security\n- **MAC:** 00:50:56:PX:01:01\n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 1806, 654\n- **Size:** 50\n- **Notes:**\n  - Squid Proxy\n  - Content filtering\n\n### proxy-server-2\n- **Name:** Proxy Server 2\n- **IP:** 10.5.0.11\n- **Role:** Web Proxy\n- **Shape:** server\n- **Tags:** proxy; squid; filtering\n- **Layer:** security\n- **MAC:** 00:50:56:PX:01:02\n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 2937, 2629\n- **Size:** 50\n- **Notes:**\n  - Squid Proxy\n  - HA pair\n\n### vpn-concentrator\n- **Name:** VPN Concentrator\n- **IP:** 10.0.5.1\n- **Role:** Remote Access VPN\n- **Shape:** firewall\n- **Tags:** vpn; remote; security\n- **Layer:** security\n- **MAC:** 00:1A:2B:VP:01:01\n- **Rack Unit:** \n- **U Height:** 2\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 3642, 947\n- **Size:** 50\n- **Notes:**\n  - Cisco ASA 5555-X\n  - AnyConnect SSL VPN\n\n### nac-server\n- **Name:** NAC Server\n- **IP:** 10.5.5.10\n- **Role:** Network Access Control\n- **Shape:** server\n- **Tags:** nac; ise; 802.1x\n- **Layer:** security\n- **MAC:** 00:50:56:NA:01:01\n- **Rack Unit:** \n- **U Height:** 2\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 1153, 1172\n- **Size:** 50\n- **Notes:**\n  - Cisco ISE 3.1\n  - RADIUS/TACACS+\n\n### print-server\n- **Name:** Print Server\n- **IP:** 10.10.0.150\n- **Role:** Print Services\n- **Shape:** server\n- **Tags:** print; windows; services\n- **Layer:** physical\n- **MAC:** 00:50:56:PR:01:01\n- **Rack Unit:** 18\n- **U Height:** 1\n- **Assigned Rack:** dc-rack-a1\n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 1897, 2845\n- **Size:** 50\n- **Notes:**\n  - Windows Print Server\n  - 50+ printers\n\n### file-server\n- **Name:** File Server\n- **IP:** 10.10.0.160\n- **Role:** File Services\n- **Shape:** database\n- **Tags:** file; smb; dfs\n- **Layer:** physical\n- **MAC:** 00:50:56:FS:01:01\n- **Rack Unit:** 16\n- **U Height:** 2\n- **Assigned Rack:** dc-rack-a1\n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 1861, 2845\n- **Size:** 50\n- **Notes:**\n  - Windows File Server\n  - DFS namespace\n\n### ca-server\n- **Name:** Certificate Authority\n- **IP:** 192.168.100.80\n- **Role:** PKI Infrastructure\n- **Shape:** server\n- **Tags:** pki; ca; security\n- **Layer:** logical\n- **MAC:** 00:50:56:CA:01:01\n- **Rack Unit:** 5\n- **U Height:** 1\n- **Assigned Rack:** mgmt-rack\n- **Rack Capacity:** 24\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 1889, 2845\n- **Size:** 50\n- **Notes:**\n  - Windows CA\n  - Enterprise Root CA\n\n### sccm-server\n- **Name:** SCCM Server\n- **IP:** 192.168.100.90\n- **Role:** Endpoint Management\n- **Shape:** server\n- **Tags:** sccm; patching; software\n- **Layer:** logical\n- **MAC:** 00:50:56:SC:01:01\n- **Rack Unit:** 3\n- **U Height:** 2\n- **Assigned Rack:** mgmt-rack\n- **Rack Capacity:** 24\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 1850, 2845\n- **Size:** 50\n- **Notes:**\n  - MECM Primary Site\n  - Software deployment\n\n### voip-cluster\n- **Name:** VoIP Cluster\n- **IP:** 10.50.0.0/24\n- **Role:** Voice Services\n- **Shape:** phone\n- **Tags:** voip; cisco; ucm\n- **Layer:** application\n- **MAC:** \n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 1777, 1617\n- **Size:** 50\n- **Notes:**\n  - Cisco UCM Cluster\n  - 3000 endpoints\n\n### video-conf\n- **Name:** Video Conference\n- **IP:** 10.51.0.0/24\n- **Role:** Video Services\n- **Shape:** laptop\n- **Tags:** video; webex; teams\n- **Layer:** application\n- **MAC:** \n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 1994, 2245\n- **Size:** 50\n- **Notes:**\n  - Webex/Teams integration\n  - Meeting rooms\n\n### security-cameras\n- **Name:** Security Cameras\n- **IP:** 10.60.0.0/24\n- **Role:** Physical Security\n- **Shape:** camera\n- **Tags:** cctv; surveillance; security\n- **Layer:** physical\n- **MAC:** \n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 1674, 2046\n- **Size:** 50\n- **Notes:**\n  - 150+ IP cameras\n  - 30-day retention\n\n### nvr-cluster\n- **Name:** NVR Cluster\n- **IP:** 10.60.0.10\n- **Role:** Video Recording\n- **Shape:** server\n- **Tags:** nvr; surveillance; storage\n- **Layer:** physical\n- **MAC:** 00:50:56:NV:01:01\n- **Rack Unit:** 15\n- **U Height:** 4\n- **Assigned Rack:** dc-rack-b2\n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 1829, 2845\n- **Size:** 50\n- **Notes:**\n  - Milestone XProtect\n  - 500TB storage\n\n### dev-server-1\n- **Name:** Dev Server 1\n- **IP:** 10.80.0.10\n- **Role:** Development\n- **Shape:** server\n- **Tags:** dev; gitlab; ci-cd; [object Object]\n- **Layer:** application\n- **MAC:** 00:50:56:DV:01:01\n- **Rack Unit:** \n- **U Height:** 2\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 2801, 1176\n- **Size:** 128\n- **Notes:**\n  - GitLab Server\n  - CI/CD pipelines\n- **Styles:** `{\"all\":{\"icon\":{\"library\":\"simple\",\"name\":\"amazonwebservices\"}}}`\n\n### dev-server-2\n- **Name:** Dev Server 2\n- **IP:** 10.80.0.11\n- **Role:** Development\n- **Shape:** server\n- **Tags:** dev; jenkins; ci-cd\n- **Layer:** application\n- **MAC:** 00:50:56:DV:01:02\n- **Rack Unit:** \n- **U Height:** 2\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 1945, 1165\n- **Size:** 50\n- **Notes:**\n  - Jenkins Server\n  - Build automation\n\n### test-environment\n- **Name:** Test Environment\n- **IP:** 10.81.0.0/24\n- **Role:** QA/Testing\n- **Shape:** shield\n- **Tags:** test; qa; staging\n- **Layer:** application\n- **MAC:** \n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 2932, 862\n- **Size:** 121\n- **Notes:**\n  - Staging environment\n  - Pre-prod validation\n- **Styles:** `{\"all\":{\"icon\":{\"library\":\"simple\",\"name\":\"apple\"}}}`\n\n### erp-system\n- **Name:** ERP System\n- **IP:** 10.90.0.10\n- **Role:** Business Application\n- **Shape:** database\n- **Tags:** erp; sap; business\n- **Layer:** application\n- **MAC:** 00:50:56:ER:01:01\n- **Rack Unit:** \n- **U Height:** 4\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 790, 474\n- **Size:** 50\n- **Notes:**\n  - SAP S/4HANA\n  - Financial/HR systems\n\n### crm-system\n- **Name:** CRM System\n- **IP:** 10.91.0.10\n- **Role:** Business Application\n- **Shape:** database\n- **Tags:** crm; salesforce; business\n- **Layer:** application\n- **MAC:** \n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 3515, 1138\n- **Size:** 50\n- **Notes:**\n  - Salesforce integration\n  - Sales/Marketing\n\n### endpoint-1000\n- **Name:** Corporate Endpoints\n- **IP:** 10.70.0.0/22\n- **Role:** User Workstations\n- **Shape:** laptop\n- **Tags:** endpoints; workstations; users\n- **Layer:** physical\n- **MAC:** \n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 992, 2284\n- **Size:** 50\n- **Notes:**\n  - ~1000 corporate laptops\n  - Windows 11\n\n### dist-switch-floor1\n- **Name:** Floor 1 Switch\n- **IP:** 10.1.1.1\n- **Role:** Distribution\n- **Shape:** switch\n- **Tags:** distribution; floor-1; access\n- **Layer:** physical\n- **MAC:** 00:1A:2B:FL:01:01\n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 654, 2020\n- **Size:** 50\n- **Notes:**\n  - Cisco C9300-48P\n  - PoE+ enabled\n\n### dist-switch-floor2\n- **Name:** Floor 2 Switch\n- **IP:** 10.1.2.1\n- **Role:** Distribution\n- **Shape:** switch\n- **Tags:** distribution; floor-2; access\n- **Layer:** physical\n- **MAC:** 00:1A:2B:FL:02:01\n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 854, 1843\n- **Size:** 50\n- **Notes:**\n  - Cisco C9300-48P\n  - PoE+ enabled\n\n### dist-switch-floor3\n- **Name:** Floor 3 Switch\n- **IP:** 10.1.3.1\n- **Role:** Distribution\n- **Shape:** switch\n- **Tags:** distribution; floor-3; access\n- **Layer:** physical\n- **MAC:** 00:1A:2B:FL:03:01\n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 1899, 1457\n- **Size:** 50\n- **Notes:**\n  - Cisco C9300-48P\n  - PoE+ enabled\n\n### dist-switch-floor4\n- **Name:** Floor 4 Switch\n- **IP:** 10.1.4.1\n- **Role:** Distribution\n- **Shape:** switch\n- **Tags:** distribution; floor-4; access\n- **Layer:** physical\n- **MAC:** 00:1A:2B:FL:04:01\n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 489, 181\n- **Size:** 50\n- **Notes:**\n  - Cisco C9300-48P\n  - PoE+ enabled\n\n### ap-floor1-zone1\n- **Name:** AP Floor 1 Zone 1\n- **IP:** 10.20.1.10\n- **Role:** Wireless Access\n- **Shape:** wifi\n- **Tags:** wifi; ap; floor-1\n- **Layer:** physical\n- **MAC:** 00:1A:2B:AP:01:01\n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 1140, 2070\n- **Size:** 50\n- **Notes:**\n  - Cisco 9120AX\n  - Wi-Fi 6\n\n### ap-floor2-zone1\n- **Name:** AP Floor 2 Zone 1\n- **IP:** 10.20.2.10\n- **Role:** Wireless Access\n- **Shape:** wifi\n- **Tags:** wifi; ap; floor-2\n- **Layer:** physical\n- **MAC:** 00:1A:2B:AP:02:01\n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 688, 2384\n- **Size:** 50\n- **Notes:**\n  - Cisco 9120AX\n  - Wi-Fi 6\n\n### ap-floor3-zone1\n- **Name:** AP Floor 3 Zone 1\n- **IP:** 10.20.3.10\n- **Role:** Wireless Access\n- **Shape:** wifi\n- **Tags:** wifi; ap; floor-3\n- **Layer:** physical\n- **MAC:** 00:1A:2B:AP:03:01\n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 2145, 1890\n- **Size:** 50\n- **Notes:**\n  - Cisco 9120AX\n  - Wi-Fi 6\n\n### ap-floor4-zone1\n- **Name:** AP Floor 4 Zone 1\n- **IP:** 10.20.4.10\n- **Role:** Wireless Access\n- **Shape:** wifi\n- **Tags:** wifi; ap; floor-4\n- **Layer:** physical\n- **MAC:** 00:1A:2B:AP:04:01\n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 518, 566\n- **Size:** 50\n- **Notes:**\n  - Cisco 9120AX\n  - Wi-Fi 6\n\n### ups-dc-1\n- **Name:** UPS DC-1\n- **IP:** 192.168.200.10\n- **Role:** Power Management\n- **Shape:** rectangle\n- **Tags:** power; ups; datacenter\n- **Layer:** physical\n- **MAC:** \n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 771, 296\n- **Size:** 50\n- **Notes:**\n  - APC Symmetra\n  - 80kVA\n  - 30 min runtime\n\n### ups-dc-2\n- **Name:** UPS DC-2\n- **IP:** 192.168.200.11\n- **Role:** Power Management\n- **Shape:** rectangle\n- **Tags:** power; ups; datacenter\n- **Layer:** physical\n- **MAC:** \n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 216, 330\n- **Size:** 50\n- **Notes:**\n  - APC Symmetra\n  - 80kVA\n  - Redundant\n\n### pdu-rack-a1\n- **Name:** PDU Rack A1\n- **IP:** 192.168.200.21\n- **Role:** Power Distribution\n- **Shape:** rectangle\n- **Tags:** power; pdu; rack-a1\n- **Layer:** physical\n- **MAC:** \n- **Rack Unit:** 1\n- **U Height:** 1\n- **Assigned Rack:** dc-rack-a1\n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 1805, 2845\n- **Size:** 50\n- **Notes:**\n  - APC Switched PDU\n  - Per-outlet metering\n\n### pdu-rack-a2\n- **Name:** PDU Rack A2\n- **IP:** 192.168.200.22\n- **Role:** Power Distribution\n- **Shape:** rectangle\n- **Tags:** power; pdu; rack-a2\n- **Layer:** physical\n- **MAC:** \n- **Rack Unit:** 1\n- **U Height:** 1\n- **Assigned Rack:** dc-rack-a2\n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 1742, 2845\n- **Size:** 50\n- **Notes:**\n  - APC Switched PDU\n  - Per-outlet metering\n\n### cooling-1\n- **Name:** CRAC Unit 1\n- **IP:** 192.168.200.30\n- **Role:** Cooling\n- **Shape:** rectangle\n- **Tags:** cooling; hvac; datacenter\n- **Layer:** physical\n- **MAC:** \n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 246, 626\n- **Size:** 50\n- **Notes:**\n  - Liebert CRV\n  - Row-based cooling\n\n### cooling-2\n- **Name:** CRAC Unit 2\n- **IP:** 192.168.200.31\n- **Role:** Cooling\n- **Shape:** rectangle\n- **Tags:** cooling; hvac; datacenter\n- **Layer:** physical\n- **MAC:** \n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 1603, 981\n- **Size:** 50\n- **Notes:**\n  - Liebert CRV\n  - N+1 redundancy\n\n### camera-a\n- **Name:** camera A\n- **IP:** 0.0.0.0\n- **Role:** \n- **Shape:** camera\n- **Tags:** _none_\n- **Layer:** physical\n- **MAC:** \n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** \n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 167, 145\n- **Size:** 45\n\n### camera-a-copy\n- **Name:** camera B\n- **IP:** 0.0.0.0\n- **Role:** \n- **Shape:** camera\n- **Tags:** _none_\n- **Layer:** physical\n- **MAC:** \n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** \n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 1041, 738\n- **Size:** 45\n\n## Connections\n\n- isp-primary (Gi0/0) --> core-router-1 (Gi1/0/1)\n  - **ID:** isp1-router1\n  - **Label:** \n  - **Color:** #10b981\n  - **Width:** 6\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n  - **Notes:**\n    - Primary WAN link\n\n- isp-secondary (Gi0/0) --> core-router-2 (Gi1/0/1)\n  - **ID:** isp2-router2\n  - **Label:** \n  - **Color:** #10b981\n  - **Width:** 6\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n  - **Notes:**\n    - Backup WAN link\n\n- core-router-1 (Gi1/0/24) --> core-router-2 (Gi1/0/24)\n  - **ID:** router1-router2\n  - **Label:** \n  - **Color:** #f59e0b\n  - **Width:** 4\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n  - **Notes:**\n    - HSRP Peering\n\n- core-router-1 --> fw-external-1\n  - **ID:** router1-fw1\n  - **Label:** \n  - **Color:** #ef4444\n  - **Width:** 4\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n\n- core-router-2 --> fw-external-2\n  - **ID:** router2-fw2\n  - **Label:** \n  - **Color:** #ef4444\n  - **Width:** 4\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n\n- fw-external-1 --> fw-external-2\n  - **ID:** fw1-fw2\n  - **Label:** \n  - **Color:** #f59e0b\n  - **Width:** 3\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** dashed\n  - **Group ID:** \n  - **Notes:**\n    - HA heartbeat\n\n- fw-external-1 --> core-switch-1\n  - **ID:** fw1-coresw1\n  - **Label:** \n  - **Color:** #475569\n  - **Width:** 4\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n\n- fw-external-2 --> core-switch-2\n  - **ID:** fw2-coresw2\n  - **Label:** \n  - **Color:** #475569\n  - **Width:** 4\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n\n- core-switch-1 --> core-switch-2\n  - **ID:** coresw1-coresw2\n  - **Label:** \n  - **Color:** #3b82f6\n  - **Width:** 5\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n  - **Notes:**\n    - VPC peer-link\n\n- core-switch-1 --> fw-internal\n  - **ID:** coresw1-fwint\n  - **Label:** \n  - **Color:** #ef4444\n  - **Width:** 3\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n\n- core-switch-2 --> fw-internal\n  - **ID:** coresw2-fwint\n  - **Label:** \n  - **Color:** #ef4444\n  - **Width:** 3\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n\n- core-switch-1 --> dc-rack-a1\n  - **ID:** coresw1-racka1\n  - **Label:** \n  - **Color:** #475569\n  - **Width:** 4\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n\n- core-switch-2 --> dc-rack-a1\n  - **ID:** coresw2-racka1\n  - **Label:** \n  - **Color:** #475569\n  - **Width:** 4\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n\n- core-switch-1 --> dc-rack-a2\n  - **ID:** coresw1-racka2\n  - **Label:** \n  - **Color:** #475569\n  - **Width:** 4\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n\n- core-switch-2 --> dc-rack-a2\n  - **ID:** coresw2-racka2\n  - **Label:** \n  - **Color:** #475569\n  - **Width:** 4\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n\n- core-switch-1 --> dc-rack-b1\n  - **ID:** coresw1-rackb1\n  - **Label:** \n  - **Color:** #475569\n  - **Width:** 4\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n\n- core-switch-2 --> dc-rack-b1\n  - **ID:** coresw2-rackb1\n  - **Label:** \n  - **Color:** #475569\n  - **Width:** 4\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n\n- core-switch-1 --> dc-rack-b2\n  - **ID:** coresw1-rackb2\n  - **Label:** \n  - **Color:** #475569\n  - **Width:** 4\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n\n- core-switch-2 --> dc-rack-b2\n  - **ID:** coresw2-rackb2\n  - **Label:** \n  - **Color:** #475569\n  - **Width:** 4\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n\n- fw-external-1 --> dmz-rack\n  - **ID:** fw1-dmz\n  - **Label:** \n  - **Color:** #f59e0b\n  - **Width:** 3\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n  - **Notes:**\n    - DMZ segment\n\n- fw-external-2 --> dmz-rack\n  - **ID:** fw2-dmz\n  - **Label:** \n  - **Color:** #f59e0b\n  - **Width:** 3\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n  - **Notes:**\n    - DMZ segment\n\n- core-switch-1 --> mgmt-rack\n  - **ID:** coresw1-mgmt\n  - **Label:** \n  - **Color:** #8b5cf6\n  - **Width:** 3\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n  - **Notes:**\n    - OOB management\n\n- core-switch-1 --> wlc-primary\n  - **ID:** coresw1-wlc1\n  - **Label:** \n  - **Color:** #06b6d4\n  - **Width:** 3\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n\n- core-switch-2 --> wlc-secondary\n  - **ID:** coresw2-wlc2\n  - **Label:** \n  - **Color:** #06b6d4\n  - **Width:** 3\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n\n- wlc-primary --> wlc-secondary\n  - **ID:** wlc1-wlc2\n  - **Label:** \n  - **Color:** #f59e0b\n  - **Width:** 2\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** dashed\n  - **Group ID:** \n  - **Notes:**\n    - HA pair\n\n- wlc-primary --> mobile-zone-hq\n  - **ID:** wlc1-mobile-hq\n  - **Label:** \n  - **Color:** #06b6d4\n  - **Width:** 2\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n\n- wlc-primary --> mobile-zone-guest\n  - **ID:** wlc1-mobile-guest\n  - **Label:** \n  - **Color:** #06b6d4\n  - **Width:** 2\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n\n- wlc-primary --> mobile-zone-iot\n  - **ID:** wlc1-mobile-iot\n  - **Label:** \n  - **Color:** #06b6d4\n  - **Width:** 2\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n\n- core-router-1 --> branch-router-ny\n  - **ID:** router1-branch-ny\n  - **Label:** \n  - **Color:** #a855f7\n  - **Width:** 3\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** dashed\n  - **Group ID:** \n  - **Notes:**\n    - SD-WAN tunnel\n\n- core-router-1 --> branch-router-la\n  - **ID:** router1-branch-la\n  - **Label:** \n  - **Color:** #a855f7\n  - **Width:** 3\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** dashed\n  - **Group ID:** \n  - **Notes:**\n    - SD-WAN tunnel\n\n- core-router-1 --> branch-router-chi\n  - **ID:** router1-branch-chi\n  - **Label:** \n  - **Color:** #a855f7\n  - **Width:** 3\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** dashed\n  - **Group ID:** \n  - **Notes:**\n    - SD-WAN tunnel\n\n- core-router-1 --> branch-router-lon\n  - **ID:** router1-branch-lon\n  - **Label:** \n  - **Color:** #a855f7\n  - **Width:** 3\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** dashed\n  - **Group ID:** \n  - **Notes:**\n    - SD-WAN tunnel\n\n- core-router-1 --> branch-router-tokyo\n  - **ID:** router1-branch-tokyo\n  - **Label:** \n  - **Color:** #a855f7\n  - **Width:** 3\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** dashed\n  - **Group ID:** \n  - **Notes:**\n    - SD-WAN tunnel\n\n- core-router-1 --> cloud-aws\n  - **ID:** router1-aws\n  - **Label:** \n  - **Color:** #f97316\n  - **Width:** 3\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** dashed\n  - **Group ID:** \n  - **Notes:**\n    - Direct Connect\n\n- core-router-2 --> cloud-azure\n  - **ID:** router2-azure\n  - **Label:** \n  - **Color:** #0ea5e9\n  - **Width:** 3\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** dashed\n  - **Group ID:** \n  - **Notes:**\n    - ExpressRoute\n\n- fw-internal --> cloud-gcp\n  - **ID:** fwint-gcp\n  - **Label:** \n  - **Color:** #22c55e\n  - **Width:** 2\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** dashed\n  - **Group ID:** \n  - **Notes:**\n    - VPN tunnel\n\n- core-switch-1 --> dist-switch-floor1\n  - **ID:** coresw1-floor1\n  - **Label:** \n  - **Color:** #475569\n  - **Width:** 3\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n\n- core-switch-1 --> dist-switch-floor2\n  - **ID:** coresw1-floor2\n  - **Label:** \n  - **Color:** #475569\n  - **Width:** 3\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n\n- core-switch-2 --> dist-switch-floor3\n  - **ID:** coresw2-floor3\n  - **Label:** \n  - **Color:** #475569\n  - **Width:** 3\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n\n- core-switch-2 --> dist-switch-floor4\n  - **ID:** coresw2-floor4\n  - **Label:** \n  - **Color:** #475569\n  - **Width:** 3\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n\n- dist-switch-floor1 --> endpoint-1000\n  - **ID:** floor1-endpoints\n  - **Label:** \n  - **Color:** #94a3b8\n  - **Width:** 2\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n\n- dist-switch-floor1 --> ap-floor1-zone1\n  - **ID:** floor1-ap1\n  - **Label:** \n  - **Color:** #06b6d4\n  - **Width:** 2\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n\n- dist-switch-floor2 --> ap-floor2-zone1\n  - **ID:** floor2-ap2\n  - **Label:** \n  - **Color:** #06b6d4\n  - **Width:** 2\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n\n- dist-switch-floor3 --> ap-floor3-zone1\n  - **ID:** floor3-ap3\n  - **Label:** \n  - **Color:** #06b6d4\n  - **Width:** 2\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n\n- dist-switch-floor4 --> ap-floor4-zone1\n  - **ID:** floor4-ap4\n  - **Label:** \n  - **Color:** #06b6d4\n  - **Width:** 2\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n\n- fw-internal --> proxy-server-1\n  - **ID:** fwint-proxy1\n  - **Label:** \n  - **Color:** #ef4444\n  - **Width:** 2\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n\n- fw-internal --> proxy-server-2\n  - **ID:** fwint-proxy2\n  - **Label:** \n  - **Color:** #ef4444\n  - **Width:** 2\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n\n- fw-external-1 --> vpn-concentrator\n  - **ID:** fwext1-vpn\n  - **Label:** \n  - **Color:** #8b5cf6\n  - **Width:** 3\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n\n- core-switch-1 --> nac-server\n  - **ID:** coresw1-nac\n  - **Label:** \n  - **Color:** #f59e0b\n  - **Width:** 2\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n\n- core-switch-1 --> voip-cluster\n  - **ID:** coresw1-voip\n  - **Label:** \n  - **Color:** #22c55e\n  - **Width:** 3\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n\n- core-switch-2 --> video-conf\n  - **ID:** coresw2-video\n  - **Label:** \n  - **Color:** #22c55e\n  - **Width:** 3\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n\n- core-switch-1 --> security-cameras\n  - **ID:** coresw1-cameras\n  - **Label:** \n  - **Color:** #94a3b8\n  - **Width:** 2\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n\n- fw-internal --> dev-server-1\n  - **ID:** fwint-dev1\n  - **Label:** \n  - **Color:** #a855f7\n  - **Width:** 2\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n\n- fw-internal --> dev-server-2\n  - **ID:** fwint-dev2\n  - **Label:** \n  - **Color:** #a855f7\n  - **Width:** 2\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n\n- fw-internal --> test-environment\n  - **ID:** fwint-test\n  - **Label:** \n  - **Color:** #a855f7\n  - **Width:** 2\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n\n- core-switch-1 --> erp-system\n  - **ID:** coresw1-erp\n  - **Label:** \n  - **Color:** #f59e0b\n  - **Width:** 3\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n\n- fw-external-1 --> crm-system\n  - **ID:** fwext1-crm\n  - **Label:** \n  - **Color:** #f59e0b\n  - **Width:** 2\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** dashed\n  - **Group ID:** \n  - **Notes:**\n    - Salesforce cloud\n\n- ups-dc-1 --> dc-rack-a1\n  - **ID:** ups1-racka1\n  - **Label:** \n  - **Color:** #fbbf24\n  - **Width:** 2\n  - **Direction:** forward\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n  - **Notes:**\n    - Power feed A\n\n- ups-dc-2 --> dc-rack-a2\n  - **ID:** ups2-racka2\n  - **Label:** \n  - **Color:** #fbbf24\n  - **Width:** 2\n  - **Direction:** forward\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n  - **Notes:**\n    - Power feed B\n\n- ups-dc-1 --> dc-rack-b1\n  - **ID:** ups1-rackb1\n  - **Label:** \n  - **Color:** #fbbf24\n  - **Width:** 2\n  - **Direction:** forward\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n  - **Notes:**\n    - Power feed A\n\n- ups-dc-2 --> dc-rack-b2\n  - **ID:** ups2-rackb2\n  - **Label:** \n  - **Color:** #fbbf24\n  - **Width:** 2\n  - **Direction:** forward\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n  - **Notes:**\n    - Power feed B\n\n- cooling-1 --> dc-rack-a1\n  - **ID:** cooling1-racka1\n  - **Label:** \n  - **Color:** #38bdf8\n  - **Width:** 2\n  - **Direction:** forward\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** dotted\n  - **Group ID:** \n  - **Notes:**\n    - Cooling zone\n\n- cooling-2 --> dc-rack-b1\n  - **ID:** cooling2-rackb1\n  - **Label:** \n  - **Color:** #38bdf8\n  - **Width:** 2\n  - **Direction:** forward\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** dotted\n  - **Group ID:** \n  - **Notes:**\n    - Cooling zone\n\n- undefined --> undefined\n  - **ID:** custom-1765237881452\n  - **Label:** \n  - **Color:** #c800ff\n  - **Width:** 4\n  - **Direction:** forward\n  - **Routing:** orthogonal\n  - **Type:** custom\n  - **Line Style:** solid\n  - **Group ID:** \n  - **Points:** 3492,1527 3501,1831 3304,1732\n\n## Zones\n\n### rect-1765237540610\n- **Position:** 2879, 160\n- **Size:** 992 x 539\n- **Color:** #f97316\n- **Style:** filled\n- **Line Style:** solid\n- **Border Color:** \n- **Border Width:** 2\n\n### rect-1765237681216\n- **Position:** 448, 1672\n- **Size:** 916 x 924\n- **Color:** #c800ff\n- **Style:** outlined\n- **Line Style:** solid\n- **Border Color:** \n- **Border Width:** 2\n\n### rect-1766437913740\n- **Position:** 905, 115\n- **Size:** 111 x 920\n- **Color:** #5215f9\n- **Style:** filled\n- **Line Style:** wall\n- **Border Color:** \n- **Border Width:** 13\n\n### rect-1766437935414\n- **Position:** 131, 1072\n- **Size:** 873 x 99\n- **Color:** #5215f9\n- **Style:** filled\n- **Line Style:** wall\n- **Border Color:** \n- **Border Width:** 13\n\n## Text Labels\n\n### text-1765237828167\n- **Content:** Double click on desktop<br>or long press on mobile<br>to enter rack canvas view\n- **Position:** 3411, 1390\n- **Font Size:** 46\n- **Color:** #e2e8f0\n- **Font Weight:** bold\n- **Font Style:** italic\n- **Text Align:** middle\n- **Text Decoration:** none\n- **Background Color:** #000000\n- **Background Enabled:** false\n- **Opacity:** 1\n\n### text-1766446595277\n- **Content:** SITE A\n- **Position:** 654, 1368\n- **Font Size:** 101\n- **Color:** #e2e8f0\n- **Font Weight:** normal\n- **Font Style:** normal\n- **Text Align:** start\n- **Text Decoration:** none\n- **Background Color:** #000000\n- **Background Enabled:** false\n- **Opacity:** 1\n\n### text-1766446610211\n- **Content:** SITE A\n- **Position:** 181, 1129\n- **Font Size:** 101\n- **Color:** #e2e8f0\n- **Font Weight:** normal\n- **Font Style:** normal\n- **Text Align:** start\n- **Text Decoration:** none\n- **Background Color:** #000000\n- **Background Enabled:** false\n- **Opacity:** 1\n\n### text-1766453024797\n- **Content:** SITE A\n- **Position:** 969, 1029\n- **Font Size:** 101\n- **Color:** #e2e8f0\n- **Font Weight:** normal\n- **Font Style:** normal\n- **Text Align:** start\n- **Text Decoration:** none\n- **Background Color:** #000000\n- **Background Enabled:** false\n- **Opacity:** 1\n\n### text-1766453070975\n- **Content:** SITE A\n- **Position:** 613, 1140\n- **Font Size:** 101\n- **Color:** #e2e8f0\n- **Font Weight:** normal\n- **Font Style:** normal\n- **Text Align:** start\n- **Text Decoration:** none\n- **Background Color:** #000000\n- **Background Enabled:** false\n- **Opacity:** 1\n\n### text-1766453072857\n- **Content:** SITE A\n- **Position:** 969, 474\n- **Font Size:** 101\n- **Color:** #e2e8f0\n- **Font Weight:** normal\n- **Font Style:** normal\n- **Text Align:** start\n- **Text Decoration:** none\n- **Background Color:** #000000\n- **Background Enabled:** false\n- **Opacity:** 1\n\n### text-1766458222326\n- **Content:** \n- **Position:** 3168, 2191\n- **Font Size:** 18\n- **Color:** #e2e8f0\n- **Font Weight:** normal\n- **Font Style:** normal\n- **Text Align:** start\n- **Text Decoration:** none\n- **Background Color:** #000000\n- **Background Enabled:** false\n- **Opacity:** 1\n\n"
  },
  {
    "path": "demos/markdown-exports/the-one-file-homelab.md",
    "content": "<!--THEONEFILE_CONFIG\n{\n  \"nodeData\": {\n    \"internet\": {\n      \"shape\": \"stop-sign\",\n      \"name\": \"Internet\",\n      \"ip\": \"0.0.0.0\",\n      \"role\": \"\",\n      \"tags\": [],\n      \"notes\": [],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"internet-copy\": {\n      \"shape\": \"firewall\",\n      \"name\": \"OPNSENSE\",\n      \"ip\": \"0.0.0.0\",\n      \"role\": \"\",\n      \"tags\": [],\n      \"notes\": [],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"opnsense-copy\": {\n      \"shape\": \"firewall\",\n      \"name\": \"Docker\",\n      \"ip\": \"0.0.0.0\",\n      \"role\": \"\",\n      \"tags\": [],\n      \"notes\": [],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"docker-copy\": {\n      \"shape\": \"firewall\",\n      \"name\": \"Docker2\",\n      \"ip\": \"0.0.0.0\",\n      \"role\": \"\",\n      \"tags\": [],\n      \"notes\": [],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"docker-copy-1\": {\n      \"shape\": \"firewall\",\n      \"name\": \"Docker3\",\n      \"ip\": \"0.0.0.0\",\n      \"role\": \"\",\n      \"tags\": [],\n      \"notes\": [],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"docker-copy-2\": {\n      \"shape\": \"firewall\",\n      \"name\": \"Docker 4\",\n      \"ip\": \"0.0.0.0\",\n      \"role\": \"\",\n      \"tags\": [\n        {\n          \"type\": \"icon\",\n          \"library\": \"selfhst\",\n          \"name\": \"docker\"\n        },\n        {\n          \"type\": \"icon\",\n          \"library\": \"selfhst\",\n          \"name\": \"authentik\"\n        },\n        {\n          \"type\": \"icon\",\n          \"library\": \"selfhst\",\n          \"name\": \"immich\"\n        }\n      ],\n      \"notes\": [],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"opnsense-copy-1\": {\n      \"shape\": \"firewall\",\n      \"name\": \"OPNSENSE GUEST\",\n      \"ip\": \"0.0.0.0\",\n      \"role\": \"\",\n      \"tags\": [],\n      \"notes\": [],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"phone\": {\n      \"shape\": \"phone\",\n      \"name\": \"Phone\",\n      \"ip\": \"0.0.0.0\",\n      \"role\": \"\",\n      \"tags\": [],\n      \"notes\": [],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"desktop\": {\n      \"shape\": \"pc\",\n      \"name\": \"Desktop\",\n      \"ip\": \"0.0.0.0\",\n      \"role\": \"\",\n      \"tags\": [],\n      \"notes\": [],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"dns\": {\n      \"shape\": \"cloud\",\n      \"name\": \"DNS\",\n      \"ip\": \"0.0.0.0\",\n      \"role\": \"\",\n      \"tags\": [],\n      \"notes\": [],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"racked\": {\n      \"shape\": \"server\",\n      \"name\": \"Racked\",\n      \"ip\": \"\",\n      \"role\": \"Rack\",\n      \"tags\": [],\n      \"notes\": [],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": true,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"thermostat\": {\n      \"shape\": \"thermostat\",\n      \"name\": \"Thermostat\",\n      \"ip\": \"0.0.0.0\",\n      \"role\": \"\",\n      \"tags\": [],\n      \"notes\": [],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"video-doorbell\": {\n      \"shape\": \"doorbell\",\n      \"name\": \"Video Doorbell\",\n      \"ip\": \"0.0.0.0\",\n      \"role\": \"\",\n      \"tags\": [],\n      \"notes\": [],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"smart-lock\": {\n      \"shape\": \"smart-lock\",\n      \"name\": \"Smart Lock\",\n      \"ip\": \"0.0.0.0\",\n      \"role\": \"\",\n      \"tags\": [],\n      \"notes\": [],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"smart-bulb\": {\n      \"shape\": \"smart-bulb\",\n      \"name\": \"Smart Bulb\",\n      \"ip\": \"0.0.0.0\",\n      \"role\": \"\",\n      \"tags\": [],\n      \"notes\": [],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"robot-vacuum\": {\n      \"shape\": \"vacuum\",\n      \"name\": \"Robot Vacuum\",\n      \"ip\": \"0.0.0.0\",\n      \"role\": \"\",\n      \"tags\": [],\n      \"notes\": [],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    }\n  },\n  \"edgeData\": {\n    \"list\": [\n      {\n        \"id\": \"internet-internet-copy-1765238145151\",\n        \"from\": \"internet\",\n        \"to\": \"internet-copy\",\n        \"width\": 4,\n        \"color\": \"#55e208\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1\n      },\n      {\n        \"id\": \"internet-copy-opnsense-copy-1765238187451\",\n        \"from\": \"internet-copy\",\n        \"to\": \"opnsense-copy\",\n        \"width\": 4,\n        \"color\": \"#4c00ff\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1\n      },\n      {\n        \"id\": \"internet-copy-docker-copy-1765238242477\",\n        \"from\": \"internet-copy\",\n        \"to\": \"docker-copy\",\n        \"width\": 4,\n        \"color\": \"#4c00ff\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1\n      },\n      {\n        \"id\": \"internet-copy-docker-copy-1-1765238244637\",\n        \"from\": \"internet-copy\",\n        \"to\": \"docker-copy-1\",\n        \"width\": 4,\n        \"color\": \"#4c00ff\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1\n      },\n      {\n        \"id\": \"internet-copy-docker-copy-2-1765238246233\",\n        \"from\": \"internet-copy\",\n        \"to\": \"docker-copy-2\",\n        \"width\": 4,\n        \"color\": \"#4c00ff\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1\n      },\n      {\n        \"id\": \"internet-opnsense-copy-1-1765238266117\",\n        \"from\": \"internet\",\n        \"to\": \"opnsense-copy-1\",\n        \"width\": 4,\n        \"color\": \"#80ff00\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1\n      },\n      {\n        \"id\": \"opnsense-copy-1-dns-1765238347996\",\n        \"from\": \"opnsense-copy-1\",\n        \"to\": \"dns\",\n        \"width\": 4,\n        \"color\": \"#fb00ff\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1\n      },\n      {\n        \"id\": \"dns-desktop-1765238386101\",\n        \"from\": \"dns\",\n        \"to\": \"desktop\",\n        \"width\": 4,\n        \"color\": \"#ff00d0\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1\n      },\n      {\n        \"id\": \"phone-dns-1765238391156\",\n        \"from\": \"phone\",\n        \"to\": \"dns\",\n        \"width\": 4,\n        \"color\": \"#ff00d0\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1\n      },\n      {\n        \"id\": \"custom-1765239449323\",\n        \"type\": \"custom\",\n        \"color\": \"#f97316\",\n        \"width\": 4,\n        \"lineStyle\": \"solid\",\n        \"direction\": \"forward\",\n        \"points\": [\n          {\n            \"x\": 2936.464111328125,\n            \"y\": 786.07958984375\n          },\n          {\n            \"x\": 3184.112060546875,\n            \"y\": 887.6153564453125\n          },\n          {\n            \"x\": 2763.110107421875,\n            \"y\": 981.7216796875\n          }\n        ],\n        \"notes\": []\n      }\n    ]\n  },\n  \"rectData\": {\n    \"list\": [\n      {\n        \"id\": \"rect-1765238219615\",\n        \"x\": 2680.053955078125,\n        \"y\": 251.44879150390625,\n        \"width\": 814.10400390625,\n        \"height\": 389.26678466796875,\n        \"color\": \"#ec0999\",\n        \"style\": \"filled\",\n        \"lineStyle\": \"solid\",\n        \"notes\": []\n      }\n    ]\n  },\n  \"textData\": {\n    \"list\": [\n      {\n        \"id\": \"text-1765238422602\",\n        \"x\": 2466.35986328125,\n        \"y\": 741.6801147460938,\n        \"content\": \"Double click on desktop\\nor long press on mobile\\nto enter rack canvas view\",\n        \"fontSize\": 40,\n        \"color\": \"#e2e8f0\",\n        \"fontWeight\": \"normal\",\n        \"fontStyle\": \"normal\",\n        \"textAlign\": \"start\",\n        \"textDecoration\": \"none\",\n        \"bgColor\": \"#000000\",\n        \"bgEnabled\": false,\n        \"opacity\": 1\n      }\n    ]\n  },\n  \"edgeLegend\": {\n    \"#475569\": \"you can edit me too\",\n    \"#65758b\": \"you can edit me too\",\n    \"#63748c\": \"you can edit me too\",\n    \"#5e6f87\": \"you can edit me too\",\n    \"#586a84\": \"you can edit me too\",\n    \"#4f627d\": \"you can edit me too\",\n    \"#455873\": \"you can edit me too\",\n    \"#3d506c\": \"you can edit me too\",\n    \"#354964\": \"you can edit me too\",\n    \"#2e415c\": \"you can edit me too\",\n    \"#293c56\": \"you can edit me too\",\n    \"#273a53\": \"you can edit me too\",\n    \"#253750\": \"you can edit me too\",\n    \"#23354d\": \"you can edit me too\",\n    \"#203046\": \"you can edit me too\",\n    \"#1e2d43\": \"you can edit me too\",\n    \"#1a283d\": \"you can edit me too\",\n    \"#172435\": \"you can edit me too\",\n    \"#141f2e\": \"you can edit me too\",\n    \"#111a27\": \"you can edit me too\",\n    \"#0f1824\": \"you can edit me too\",\n    \"#0d1521\": \"you can edit me too\",\n    \"#0c131d\": \"you can edit me too\",\n    \"#0c1d1c\": \"you can edit me too\",\n    \"#0c1c1d\": \"you can edit me too\",\n    \"#0c191d\": \"you can edit me too\",\n    \"#0c141d\": \"you can edit me too\",\n    \"#0c0d1d\": \"you can edit me too\",\n    \"#130c1d\": \"you can edit me too\",\n    \"#1b0c1d\": \"you can edit me too\",\n    \"#1d0c17\": \"you can edit me too\",\n    \"#1d0c10\": \"you can edit me too\",\n    \"#1d0c0c\": \"you can edit me too\",\n    \"#3b1b1b\": \"you can edit me too\",\n    \"#3c1a1a\": \"you can edit me too\",\n    \"#3f1c1c\": \"you can edit me too\",\n    \"#401c1c\": \"you can edit me too\",\n    \"#451c1c\": \"you can edit me too\",\n    \"#461b1b\": \"you can edit me too\",\n    \"#4c1a1a\": \"you can edit me too\",\n    \"#521919\": \"you can edit me too\",\n    \"#571919\": \"you can edit me too\",\n    \"#5d1818\": \"you can edit me too\",\n    \"#631717\": \"you can edit me too\",\n    \"#651515\": \"you can edit me too\",\n    \"#6a1616\": \"you can edit me too\",\n    \"#6f1515\": \"you can edit me too\",\n    \"#711414\": \"you can edit me too\",\n    \"#761414\": \"you can edit me too\",\n    \"#771313\": \"you can edit me too\",\n    \"#7c1313\": \"you can edit me too\",\n    \"#811313\": \"you can edit me too\",\n    \"#821212\": \"you can edit me too\",\n    \"#871212\": \"you can edit me too\",\n    \"#881111\": \"you can edit me too\",\n    \"#8d1111\": \"you can edit me too\",\n    \"#8e1010\": \"you can edit me too\",\n    \"#8f0f0f\": \"you can edit me too\",\n    \"#900e0e\": \"you can edit me too\",\n    \"#8e0b0b\": \"you can edit me too\",\n    \"#8c0d0d\": \"you can edit me too\",\n    \"#880c0c\": \"you can edit me too\",\n    \"#830c0c\": \"you can edit me too\",\n    \"#7e0c0c\": \"you can edit me too\",\n    \"#790c0c\": \"you can edit me too\",\n    \"#730c0c\": \"you can edit me too\",\n    \"#6f0b0b\": \"you can edit me too\",\n    \"#0b6f64\": \"you can edit me too\",\n    \"#0b6f5f\": \"you can edit me too\",\n    \"#0b6f56\": \"you can edit me too\",\n    \"#0b6f49\": \"you can edit me too\",\n    \"#0b6f31\": \"you can edit me too\",\n    \"#0b6f1f\": \"you can edit me too\",\n    \"#0b6f0d\": \"you can edit me too\",\n    \"#176f0b\": \"you can edit me too\",\n    \"#266f0b\": \"you can edit me too\",\n    \"#296f0b\": \"you can edit me too\",\n    \"#2e6f0b\": \"you can edit me too\",\n    \"#1a2d10\": \"you can edit me too\",\n    \"#1c3111\": \"you can edit me too\",\n    \"#213814\": \"you can edit me too\",\n    \"#233c15\": \"you can edit me too\",\n    \"#254017\": \"you can edit me too\",\n    \"#294918\": \"you can edit me too\",\n    \"#2b4d1a\": \"you can edit me too\",\n    \"#2d511a\": \"you can edit me too\",\n    \"#315a1b\": \"you can edit me too\",\n    \"#35631c\": \"you can edit me too\",\n    \"#37681d\": \"you can edit me too\",\n    \"#3b721d\": \"you can edit me too\",\n    \"#3f7b1e\": \"you can edit me too\",\n    \"#42851e\": \"you can edit me too\",\n    \"#46901d\": \"you can edit me too\",\n    \"#499a1d\": \"you can edit me too\",\n    \"#4b9f1d\": \"you can edit me too\",\n    \"#4ca61c\": \"you can edit me too\",\n    \"#50b01c\": \"you can edit me too\",\n    \"#51b71a\": \"you can edit me too\",\n    \"#50b918\": \"you can edit me too\",\n    \"#51c115\": \"you can edit me too\",\n    \"#53c615\": \"you can edit me too\",\n    \"#53c814\": \"you can edit me too\",\n    \"#52c913\": \"you can edit me too\",\n    \"#54d011\": \"you can edit me too\",\n    \"#53d110\": \"you can edit me too\",\n    \"#55d510\": \"you can edit me too\",\n    \"#55d70f\": \"you can edit me too\",\n    \"#54d80e\": \"you can edit me too\",\n    \"#54da0b\": \"you can edit me too\",\n    \"#56df0c\": \"you can edit me too\",\n    \"#53db0a\": \"you can edit me too\",\n    \"#55e00b\": \"you can edit me too\",\n    \"#55e109\": \"you can edit me too\",\n    \"#55e208\": \"ISP LINE\",\n    \"#4c00ff\": \"MY Guest NETWORK\",\n    \"#80ff00\": \"you can edit me too\",\n    \"#3b4234\": \"you can edit me too\",\n    \"#3a3442\": \"you can edit me too\",\n    \"#3b3442\": \"you can edit me too\",\n    \"#3c3442\": \"you can edit me too\",\n    \"#3d3442\": \"you can edit me too\",\n    \"#3e3442\": \"you can edit me too\",\n    \"#3f3442\": \"you can edit me too\",\n    \"#403442\": \"you can edit me too\",\n    \"#413442\": \"you can edit me too\",\n    \"#653d66\": \"you can edit me too\",\n    \"#683f69\": \"you can edit me too\",\n    \"#6c416c\": \"you can edit me too\",\n    \"#6f4370\": \"you can edit me too\",\n    \"#704270\": \"you can edit me too\",\n    \"#734474\": \"you can edit me too\",\n    \"#784479\": \"you can edit me too\",\n    \"#7d447e\": \"you can edit me too\",\n    \"#7e437f\": \"you can edit me too\",\n    \"#834384\": \"you can edit me too\",\n    \"#844285\": \"you can edit me too\",\n    \"#89418b\": \"you can edit me too\",\n    \"#8e428f\": \"you can edit me too\",\n    \"#904091\": \"you can edit me too\",\n    \"#923e93\": \"you can edit me too\",\n    \"#973e98\": \"you can edit me too\",\n    \"#943c96\": \"you can edit me too\",\n    \"#993c9a\": \"you can edit me too\",\n    \"#963a98\": \"you can edit me too\",\n    \"#973899\": \"you can edit me too\",\n    \"#99369b\": \"you can edit me too\",\n    \"#9a359c\": \"you can edit me too\",\n    \"#9b349d\": \"you can edit me too\",\n    \"#9d329f\": \"you can edit me too\",\n    \"#9e31a0\": \"you can edit me too\",\n    \"#a02fa2\": \"you can edit me too\",\n    \"#9d2d9f\": \"you can edit me too\",\n    \"#9f2ba1\": \"you can edit me too\",\n    \"#a129a3\": \"you can edit me too\",\n    \"#a327a5\": \"you can edit me too\",\n    \"#a525a7\": \"you can edit me too\",\n    \"#a723a9\": \"you can edit me too\",\n    \"#a921ab\": \"you can edit me too\",\n    \"#ab1fad\": \"you can edit me too\",\n    \"#ad1daf\": \"you can edit me too\",\n    \"#ae1cb0\": \"you can edit me too\",\n    \"#b019b3\": \"you can edit me too\",\n    \"#b118b4\": \"you can edit me too\",\n    \"#b316b6\": \"you can edit me too\",\n    \"#b816bb\": \"you can edit me too\",\n    \"#b514b8\": \"you can edit me too\",\n    \"#ba14bd\": \"you can edit me too\",\n    \"#b712ba\": \"you can edit me too\",\n    \"#bb13be\": \"you can edit me too\",\n    \"#b811bb\": \"you can edit me too\",\n    \"#be10c1\": \"you can edit me too\",\n    \"#bb0ebe\": \"you can edit me too\",\n    \"#bd0cc0\": \"you can edit me too\",\n    \"#be0bc1\": \"you can edit me too\",\n    \"#c108c4\": \"you can edit me too\",\n    \"#be06c1\": \"you can edit me too\",\n    \"#c103c4\": \"you can edit me too\",\n    \"#c301c6\": \"you can edit me too\",\n    \"#c400c7\": \"you can edit me too\",\n    \"#c900cc\": \"you can edit me too\",\n    \"#ce00d1\": \"you can edit me too\",\n    \"#d300d6\": \"you can edit me too\",\n    \"#d800db\": \"you can edit me too\",\n    \"#dd00e0\": \"you can edit me too\",\n    \"#e200e6\": \"you can edit me too\",\n    \"#ec00f0\": \"you can edit me too\",\n    \"#f100f5\": \"you can edit me too\",\n    \"#f600fa\": \"you can edit me too\",\n    \"#fb00ff\": \"you can edit me too\",\n    \"#ff00d0\": \"iPhone (always guest iPhone)\",\n    \"#f97316\": \"you can edit me too\"\n  },\n  \"nodePositions\": {\n    \"internet\": {\n      \"x\": 1757.7735887323333,\n      \"y\": 298.77284240722656\n    },\n    \"internet-copy\": {\n      \"x\": 2066.9677515897347,\n      \"y\": 473.4119134177565\n    },\n    \"opnsense-copy\": {\n      \"x\": 1773.8400660428597,\n      \"y\": 666.5758233298659\n    },\n    \"docker-copy\": {\n      \"x\": 1931.1978950081452,\n      \"y\": 782.2775961320921\n    },\n    \"docker-copy-1\": {\n      \"x\": 2158.1262397347077,\n      \"y\": 767.7122274797483\n    },\n    \"docker-copy-2\": {\n      \"x\": 2342.2663764534577,\n      \"y\": 631.7681967180296\n    },\n    \"opnsense-copy-1\": {\n      \"x\": 2757.879480087803,\n      \"y\": 307.6117116091891\n    },\n    \"phone\": {\n      \"x\": 3312.857751572178,\n      \"y\": 502.58220111114224\n    },\n    \"desktop\": {\n      \"x\": 2971.700036728428,\n      \"y\": 480.7287465212985\n    },\n    \"dns\": {\n      \"x\": 3200.4643189549906,\n      \"y\": 320.469591247861\n    },\n    \"racked\": {\n      \"x\": 2645.5845448279656,\n      \"y\": 970.7820678889219\n    },\n    \"thermostat\": {\n      \"x\": 1323.0595481711202,\n      \"y\": 574.6132617105841\n    },\n    \"video-doorbell\": {\n      \"x\": 1188.4284446554952,\n      \"y\": 455.3684191812872\n    },\n    \"smart-lock\": {\n      \"x\": 1292.286782057839,\n      \"y\": 790.0231738199591\n    },\n    \"smart-bulb\": {\n      \"x\": 1496.156899245339,\n      \"y\": 716.9377246012091\n    },\n    \"robot-vacuum\": {\n      \"x\": 2288.5581443625265,\n      \"y\": 978.5069995035528\n    }\n  },\n  \"nodeSizes\": {\n    \"core-router-1\": 36,\n    \"internet\": 168,\n    \"phone\": 121,\n    \"desktop\": 147,\n    \"racked\": 137,\n    \"docker-copy-2\": 82\n  },\n  \"nodeStyles\": {\n    \"internet\": {\n      \"all\": {\n        \"icon\": {\n          \"library\": \"selfhst\",\n          \"name\": \"amazon-web-services\"\n        },\n        \"circleColor\": \"#db0000\",\n        \"circleBorder\": \"#000000\",\n        \"titleSize\": 52,\n        \"subSize\": 46\n      }\n    },\n    \"opnsense-copy-1\": {\n      \"all\": {\n        \"icon\": {\n          \"library\": \"selfhst\",\n          \"name\": \"opnsense-v1\"\n        }\n      }\n    },\n    \"internet-copy\": {\n      \"all\": {\n        \"icon\": {\n          \"library\": \"selfhst\",\n          \"name\": \"opnsense\"\n        }\n      }\n    },\n    \"docker-copy-2\": {\n      \"all\": {\n        \"icon\": {\n          \"library\": \"selfhst\",\n          \"name\": \"docker\"\n        }\n      }\n    },\n    \"docker-copy-1\": {\n      \"all\": {\n        \"icon\": {\n          \"library\": \"selfhst\",\n          \"name\": \"authportal\"\n        }\n      }\n    },\n    \"docker-copy\": {\n      \"all\": {\n        \"icon\": {\n          \"library\": \"selfhst\",\n          \"name\": \"jotty\"\n        }\n      }\n    },\n    \"opnsense-copy\": {\n      \"all\": {\n        \"icon\": {\n          \"library\": \"selfhst\",\n          \"name\": \"portainer\"\n        }\n      }\n    },\n    \"racked\": {\n      \"all\": {\n        \"icon\": {\n          \"library\": \"mdi\",\n          \"name\": \"server-security\"\n        },\n        \"circleColor\": \"#010813\",\n        \"circleBorder\": \"#ffffff\"\n      }\n    }\n  },\n  \"page\": {\n    \"title\": \"The One File\",\n    \"background\": \"\",\n    \"topbarBg\": \"rgba(9, 12, 20, 0.9)\",\n    \"topbarBorder\": \"#1f2533\",\n    \"panel\": \"#2f0e0e\",\n    \"panelAlt\": \"#10141b\",\n    \"accent\": \"#a75252\",\n    \"sidebarBg\": \"#10141b\",\n    \"btnBg\": \"#0b0e13\",\n    \"btnText\": \"#e2e8f0\",\n    \"tagFill\": \"#1e293b\",\n    \"tagText\": \"#e2e8f0\",\n    \"tagBorder\": \"#475569\",\n    \"inputBg\": \"#0b0e13\",\n    \"inputText\": \"#e2e8f0\",\n    \"inputBorder\": \"#1f2937\",\n    \"inputFont\": \"Inter, system-ui, sans-serif\",\n    \"inputFontSize\": 14,\n    \"toolbarBg\": \"#441215\",\n    \"toolbarBorder\": \"#1f2937\",\n    \"toolbarText\": \"#94a3b8\",\n    \"toolbarBtnBg\": \"#0b0e13\",\n    \"toolbarBtnText\": \"#e2e8f0\",\n    \"minimapDots\": \"#94a3b8\",\n    \"canvasHintEnabled\": true,\n    \"canvasHintText\": \"\",\n    \"canvasHintBg\": \"#0f172a\",\n    \"canvasHintColor\": \"#94a3b8\",\n    \"danger\": \"#f56565\",\n    \"textMain\": \"#e2e8f0\",\n    \"textSoft\": \"#94a3b8\",\n    \"topbarHeight\": 112,\n    \"sidebarWidth\": 350,\n    \"mobileFooterHeight\": 40,\n    \"sidebarCollapsed\": false,\n    \"nodeFill\": \"#1e293b\",\n    \"nodeStroke\": \"#475569\",\n    \"nodeTitle\": \"#e2e8f0\",\n    \"nodeSub\": \"#94a3b8\",\n    \"nodeTitleSize\": 18,\n    \"nodeSubSize\": 13,\n    \"nodeFont\": \"Inter, system-ui, sans-serif\",\n    \"defaultEdge\": \"#475569\",\n    \"selectionHandle\": \"#f59e0b\",\n    \"selectionHandleSize\": 8,\n    \"groupIndicator\": \"#4fd1c5\",\n    \"canvasGradientTop\": \"#1e2532\",\n    \"canvasGradientBottom\": \"#050608\",\n    \"canvasBorder\": \"#475569\",\n    \"canvasGrid\": \"#475569\",\n    \"canvasGridSize\": 50,\n    \"canvasGridEnabled\": true,\n    \"rackFrameFill\": \"#0f172a\",\n    \"rackGridEnabled\": true,\n    \"rackFrameStroke\": \"#4fd1c5\",\n    \"rackLineColor\": \"#475569\",\n    \"rackTextColor\": \"#4fd1c5\",\n    \"viewOnly\": false,\n    \"defaultEdgeRouting\": \"curved\",\n    \"animateConnections\": false,\n    \"animationStyle\": \"arrows\",\n    \"animationDirection\": \"all\",\n    \"animationSpeed\": 1.5\n  },\n  \"canvas\": {\n    \"zoom\": 0.8752084596859406,\n    \"panX\": -191.26917538063572,\n    \"panY\": -261.51832729948296\n  },\n  \"savedTopologyView\": {\n    \"zoom\": 0.9111098220009686,\n    \"panX\": -207.26076103009882,\n    \"panY\": -201.83911371533202\n  },\n  \"documentTabs\": [\n    {\n      \"id\": \"main\",\n      \"name\": \"Corporate Site B\",\n      \"nodes\": {\n        \"core-router-1\": {\n          \"shape\": \"router\",\n          \"name\": \"Core Router 1\",\n          \"ip\": \"10.0.0.1\",\n          \"role\": \"Core Routing\",\n          \"tags\": [\n            \"core\",\n            \"tier-1\",\n            \"redundant\"\n          ],\n          \"notes\": [\n            \"Primary core router\",\n            \"BGP peering enabled\"\n          ],\n          \"mac\": \"00:1A:2B:3C:4D:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"core-router-2\": {\n          \"shape\": \"router\",\n          \"name\": \"Core Router 2\",\n          \"ip\": \"10.0.0.2\",\n          \"role\": \"Core Routing\",\n          \"tags\": [\n            \"core\",\n            \"tier-1\",\n            \"redundant\"\n          ],\n          \"notes\": [\n            \"Secondary core router\",\n            \"HSRP standby\"\n          ],\n          \"mac\": \"00:1A:2B:3C:4D:02\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null,\n          \"ping\": {\n            \"enabled\": true,\n            \"protocol\": \"custom\",\n            \"customUrl\": \"https://google.com\",\n            \"timeout\": 3000,\n            \"status\": \"online\",\n            \"lastCheck\": \"2025-12-09T00:15:04.343Z\"\n          }\n        },\n        \"fw-external-1\": {\n          \"shape\": \"firewall\",\n          \"name\": \"External FW 1\",\n          \"ip\": \"10.0.1.1\",\n          \"role\": \"Perimeter Security\",\n          \"tags\": [\n            \"security\",\n            \"perimeter\",\n            \"ha-pair\"\n          ],\n          \"notes\": [\n            \"Palo Alto PA-5250\",\n            \"Active node\"\n          ],\n          \"mac\": \"00:1A:2B:3C:4D:10\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"fw-external-2\": {\n          \"shape\": \"firewall\",\n          \"name\": \"External FW 2\",\n          \"ip\": \"10.0.1.2\",\n          \"role\": \"Perimeter Security\",\n          \"tags\": [\n            \"security\",\n            \"perimeter\",\n            \"ha-pair\"\n          ],\n          \"notes\": [\n            \"Palo Alto PA-5250\",\n            \"Passive node\"\n          ],\n          \"mac\": \"00:1A:2B:3C:4D:11\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"fw-internal\": {\n          \"shape\": \"firewall\",\n          \"name\": \"Internal FW\",\n          \"ip\": \"10.0.2.1\",\n          \"role\": \"Internal Segmentation\",\n          \"tags\": [\n            \"security\",\n            \"internal\"\n          ],\n          \"notes\": [\n            \"East-West traffic inspection\"\n          ],\n          \"mac\": \"00:1A:2B:3C:4D:12\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"core-switch-1\": {\n          \"shape\": \"switch\",\n          \"name\": \"Core Switch 1\",\n          \"ip\": \"10.0.10.1\",\n          \"role\": \"Core Switching\",\n          \"tags\": [\n            \"core\",\n            \"layer3\",\n            \"redundant\"\n          ],\n          \"notes\": [\n            \"Cisco Nexus 9000\",\n            \"VPC Domain 1\"\n          ],\n          \"mac\": \"00:1A:2B:3C:4D:20\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"core-switch-2\": {\n          \"shape\": \"switch\",\n          \"name\": \"Core Switch 2\",\n          \"ip\": \"10.0.10.2\",\n          \"role\": \"Core Switching\",\n          \"tags\": [\n            \"core\",\n            \"layer3\",\n            \"redundant\"\n          ],\n          \"notes\": [\n            \"Cisco Nexus 9000\",\n            \"VPC Domain 1\"\n          ],\n          \"mac\": \"00:1A:2B:3C:4D:21\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dc-rack-a1\": {\n          \"shape\": \"server\",\n          \"name\": \"DC Rack A1\",\n          \"ip\": \"10.10.0.0/24\",\n          \"role\": \"Data Center Rack\",\n          \"tags\": [\n            \"datacenter\",\n            \"row-a\",\n            \"production\"\n          ],\n          \"notes\": [\n            \"Row A, Position 1\",\n            \"Primary compute\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": true,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dc-rack-a2\": {\n          \"shape\": \"server\",\n          \"name\": \"DC Rack A2\",\n          \"ip\": \"10.10.1.0/24\",\n          \"role\": \"Data Center Rack\",\n          \"tags\": [\n            \"datacenter\",\n            \"row-a\",\n            \"production\"\n          ],\n          \"notes\": [\n            \"Row A, Position 2\",\n            \"Primary compute\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": true,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dc-rack-b1\": {\n          \"shape\": \"server\",\n          \"name\": \"DC Rack B1\",\n          \"ip\": \"10.10.2.0/24\",\n          \"role\": \"Data Center Rack\",\n          \"tags\": [\n            \"datacenter\",\n            \"row-b\",\n            \"storage\"\n          ],\n          \"notes\": [\n            \"Row B, Position 1\",\n            \"Storage systems\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": true,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dc-rack-b2\": {\n          \"shape\": \"server\",\n          \"name\": \"DC Rack B2\",\n          \"ip\": \"10.10.3.0/24\",\n          \"role\": \"Data Center Rack\",\n          \"tags\": [\n            \"datacenter\",\n            \"row-b\",\n            \"storage\"\n          ],\n          \"notes\": [\n            \"Row B, Position 2\",\n            \"Storage systems\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": true,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dmz-rack\": {\n          \"shape\": \"server\",\n          \"name\": \"DMZ Rack\",\n          \"ip\": \"172.16.0.0/24\",\n          \"role\": \"DMZ Infrastructure\",\n          \"tags\": [\n            \"dmz\",\n            \"security\",\n            \"public-facing\",\n            {\n              \"type\": \"icon\",\n              \"library\": \"selfhst\",\n              \"name\": \"booklogr\"\n            },\n            {\n              \"type\": \"icon\",\n              \"library\": \"simple\",\n              \"name\": \"gmail\"\n            }\n          ],\n          \"notes\": [\n            \"Isolated DMZ zone\",\n            \"Public-facing services\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": true,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"mgmt-rack\": {\n          \"shape\": \"server\",\n          \"name\": \"Management Rack\",\n          \"ip\": \"192.168.100.0/24\",\n          \"role\": \"Management Infrastructure\",\n          \"tags\": [\n            \"management\",\n            \"oob\",\n            \"noc\"\n          ],\n          \"notes\": [\n            \"Out-of-band management\",\n            \"NOC equipment\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"logical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": true,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"esxi-host-01\": {\n          \"shape\": \"server\",\n          \"name\": \"ESXi Host 01\",\n          \"ip\": \"10.10.0.11\",\n          \"role\": \"Hypervisor\",\n          \"tags\": [\n            \"vmware\",\n            \"compute\",\n            \"cluster-a\"\n          ],\n          \"notes\": [\n            \"Dell PowerEdge R750\",\n            \"512GB RAM\",\n            \"vSphere 8.0\"\n          ],\n          \"mac\": \"00:50:56:AA:01:01\",\n          \"rackUnit\": 38,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"esxi-host-02\": {\n          \"shape\": \"server\",\n          \"name\": \"ESXi Host 02\",\n          \"ip\": \"10.10.0.12\",\n          \"role\": \"Hypervisor\",\n          \"tags\": [\n            \"vmware\",\n            \"compute\",\n            \"cluster-a\"\n          ],\n          \"notes\": [\n            \"Dell PowerEdge R750\",\n            \"512GB RAM\",\n            \"vSphere 8.0\"\n          ],\n          \"mac\": \"00:50:56:AA:01:02\",\n          \"rackUnit\": 35,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"esxi-host-03\": {\n          \"shape\": \"server\",\n          \"name\": \"ESXi Host 03\",\n          \"ip\": \"10.10.0.13\",\n          \"role\": \"Hypervisor\",\n          \"tags\": [\n            \"vmware\",\n            \"compute\",\n            \"cluster-a\"\n          ],\n          \"notes\": [\n            \"Dell PowerEdge R750\",\n            \"512GB RAM\",\n            \"vSphere 8.0\"\n          ],\n          \"mac\": \"00:50:56:AA:01:03\",\n          \"rackUnit\": 32,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"esxi-host-04\": {\n          \"shape\": \"server\",\n          \"name\": \"ESXi Host 04\",\n          \"ip\": \"10.10.0.14\",\n          \"role\": \"Hypervisor\",\n          \"tags\": [\n            \"vmware\",\n            \"compute\",\n            \"cluster-a\"\n          ],\n          \"notes\": [\n            \"Dell PowerEdge R750\",\n            \"512GB RAM\",\n            \"vSphere 8.0\"\n          ],\n          \"mac\": \"00:50:56:AA:01:04\",\n          \"rackUnit\": 29,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"tor-switch-a1\": {\n          \"shape\": \"switch\",\n          \"name\": \"ToR Switch A1\",\n          \"ip\": \"10.10.0.1\",\n          \"role\": \"Top of Rack\",\n          \"tags\": [\n            \"tor\",\n            \"access\",\n            \"rack-a1\"\n          ],\n          \"notes\": [\n            \"Cisco Nexus 93180YC-FX\",\n            \"48x25G ports\"\n          ],\n          \"mac\": \"00:1A:2B:3C:5D:01\",\n          \"rackUnit\": 42,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"esxi-host-05\": {\n          \"shape\": \"server\",\n          \"name\": \"ESXi Host 05\",\n          \"ip\": \"10.10.1.11\",\n          \"role\": \"Hypervisor\",\n          \"tags\": [\n            \"vmware\",\n            \"compute\",\n            \"cluster-b\"\n          ],\n          \"notes\": [\n            \"Dell PowerEdge R750\",\n            \"768GB RAM\",\n            \"vSphere 8.0\"\n          ],\n          \"mac\": \"00:50:56:AA:02:01\",\n          \"rackUnit\": 38,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"esxi-host-06\": {\n          \"shape\": \"server\",\n          \"name\": \"ESXi Host 06\",\n          \"ip\": \"10.10.1.12\",\n          \"role\": \"Hypervisor\",\n          \"tags\": [\n            \"vmware\",\n            \"compute\",\n            \"cluster-b\"\n          ],\n          \"notes\": [\n            \"Dell PowerEdge R750\",\n            \"768GB RAM\",\n            \"vSphere 8.0\"\n          ],\n          \"mac\": \"00:50:56:AA:02:02\",\n          \"rackUnit\": 35,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"esxi-host-07\": {\n          \"shape\": \"server\",\n          \"name\": \"ESXi Host 07\",\n          \"ip\": \"10.10.1.13\",\n          \"role\": \"Hypervisor\",\n          \"tags\": [\n            \"vmware\",\n            \"compute\",\n            \"cluster-b\"\n          ],\n          \"notes\": [\n            \"Dell PowerEdge R750\",\n            \"768GB RAM\",\n            \"vSphere 8.0\"\n          ],\n          \"mac\": \"00:50:56:AA:02:03\",\n          \"rackUnit\": 32,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"esxi-host-08\": {\n          \"shape\": \"server\",\n          \"name\": \"ESXi Host 08\",\n          \"ip\": \"10.10.1.14\",\n          \"role\": \"Hypervisor\",\n          \"tags\": [\n            \"vmware\",\n            \"compute\",\n            \"cluster-b\"\n          ],\n          \"notes\": [\n            \"Dell PowerEdge R750\",\n            \"768GB RAM\",\n            \"vSphere 8.0\"\n          ],\n          \"mac\": \"00:50:56:AA:02:04\",\n          \"rackUnit\": 29,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"tor-switch-a2\": {\n          \"shape\": \"switch\",\n          \"name\": \"ToR Switch A2\",\n          \"ip\": \"10.10.1.1\",\n          \"role\": \"Top of Rack\",\n          \"tags\": [\n            \"tor\",\n            \"access\",\n            \"rack-a2\"\n          ],\n          \"notes\": [\n            \"Cisco Nexus 93180YC-FX\",\n            \"48x25G ports\"\n          ],\n          \"mac\": \"00:1A:2B:3C:5D:02\",\n          \"rackUnit\": 42,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"san-primary\": {\n          \"shape\": \"database\",\n          \"name\": \"SAN Primary\",\n          \"ip\": \"10.10.2.10\",\n          \"role\": \"Primary Storage\",\n          \"tags\": [\n            \"storage\",\n            \"san\",\n            \"netapp\"\n          ],\n          \"notes\": [\n            \"NetApp AFF A400\",\n            \"500TB Raw\",\n            \"FC 32Gb\"\n          ],\n          \"mac\": \"00:A0:98:AA:01:01\",\n          \"rackUnit\": 36,\n          \"uHeight\": \"6\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-b1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"san-secondary\": {\n          \"shape\": \"database\",\n          \"name\": \"SAN Secondary\",\n          \"ip\": \"10.10.2.11\",\n          \"role\": \"Secondary Storage\",\n          \"tags\": [\n            \"storage\",\n            \"san\",\n            \"netapp\"\n          ],\n          \"notes\": [\n            \"NetApp AFF A400\",\n            \"500TB Raw\",\n            \"FC 32Gb\"\n          ],\n          \"mac\": \"00:A0:98:AA:01:02\",\n          \"rackUnit\": 28,\n          \"uHeight\": \"6\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-b1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"fc-switch-1\": {\n          \"shape\": \"switch\",\n          \"name\": \"FC Switch 1\",\n          \"ip\": \"10.10.2.1\",\n          \"role\": \"Fibre Channel\",\n          \"tags\": [\n            \"storage\",\n            \"fc\",\n            \"fabric-a\"\n          ],\n          \"notes\": [\n            \"Brocade G620\",\n            \"Fabric A\"\n          ],\n          \"mac\": \"00:1A:2B:FC:01:01\",\n          \"rackUnit\": 42,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-b1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"fc-switch-2\": {\n          \"shape\": \"switch\",\n          \"name\": \"FC Switch 2\",\n          \"ip\": \"10.10.2.2\",\n          \"role\": \"Fibre Channel\",\n          \"tags\": [\n            \"storage\",\n            \"fc\",\n            \"fabric-b\"\n          ],\n          \"notes\": [\n            \"Brocade G620\",\n            \"Fabric B\"\n          ],\n          \"mac\": \"00:1A:2B:FC:01:02\",\n          \"rackUnit\": 41,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-b1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"backup-server-1\": {\n          \"shape\": \"server\",\n          \"name\": \"Backup Server 1\",\n          \"ip\": \"10.10.3.10\",\n          \"role\": \"Backup Infrastructure\",\n          \"tags\": [\n            \"backup\",\n            \"veeam\",\n            \"protection\"\n          ],\n          \"notes\": [\n            \"Veeam Backup Server\",\n            \"Dell R740xd\",\n            \"200TB\"\n          ],\n          \"mac\": \"00:50:56:BB:01:01\",\n          \"rackUnit\": 36,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-b2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"backup-server-2\": {\n          \"shape\": \"server\",\n          \"name\": \"Backup Server 2\",\n          \"ip\": \"10.10.3.11\",\n          \"role\": \"Backup Infrastructure\",\n          \"tags\": [\n            \"backup\",\n            \"veeam\",\n            \"protection\"\n          ],\n          \"notes\": [\n            \"Veeam Backup Server\",\n            \"Dell R740xd\",\n            \"200TB\"\n          ],\n          \"mac\": \"00:50:56:BB:01:02\",\n          \"rackUnit\": 33,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-b2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"tape-library\": {\n          \"shape\": \"database\",\n          \"name\": \"Tape Library\",\n          \"ip\": \"10.10.3.20\",\n          \"role\": \"Archival Storage\",\n          \"tags\": [\n            \"backup\",\n            \"tape\",\n            \"lto9\"\n          ],\n          \"notes\": [\n            \"IBM TS4500\",\n            \"LTO-9\",\n            \"Long-term archive\"\n          ],\n          \"mac\": \"00:50:56:BB:02:01\",\n          \"rackUnit\": 20,\n          \"uHeight\": \"10\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-b2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"tor-switch-b1\": {\n          \"shape\": \"switch\",\n          \"name\": \"ToR Switch B1\",\n          \"ip\": \"10.10.2.3\",\n          \"role\": \"Top of Rack\",\n          \"tags\": [\n            \"tor\",\n            \"access\",\n            \"rack-b1\"\n          ],\n          \"notes\": [\n            \"Cisco Nexus 93180YC-FX\"\n          ],\n          \"mac\": \"00:1A:2B:3C:5D:03\",\n          \"rackUnit\": 40,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-b1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"tor-switch-b2\": {\n          \"shape\": \"switch\",\n          \"name\": \"ToR Switch B2\",\n          \"ip\": \"10.10.3.1\",\n          \"role\": \"Top of Rack\",\n          \"tags\": [\n            \"tor\",\n            \"access\",\n            \"rack-b2\"\n          ],\n          \"notes\": [\n            \"Cisco Nexus 93180YC-FX\"\n          ],\n          \"mac\": \"00:1A:2B:3C:5D:04\",\n          \"rackUnit\": 42,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-b2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"web-server-1\": {\n          \"shape\": \"server\",\n          \"name\": \"Web Server 1\",\n          \"ip\": \"172.16.0.11\",\n          \"role\": \"Web Frontend\",\n          \"tags\": [\n            \"dmz\",\n            \"web\",\n            \"nginx\"\n          ],\n          \"notes\": [\n            \"NGINX reverse proxy\",\n            \"Public facing\"\n          ],\n          \"mac\": \"00:50:56:CC:01:01\",\n          \"rackUnit\": 20,\n          \"uHeight\": \"1\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"dmz-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"web-server-2\": {\n          \"shape\": \"server\",\n          \"name\": \"Web Server 2\",\n          \"ip\": \"172.16.0.12\",\n          \"role\": \"Web Frontend\",\n          \"tags\": [\n            \"dmz\",\n            \"web\",\n            \"nginx\"\n          ],\n          \"notes\": [\n            \"NGINX reverse proxy\",\n            \"Public facing\"\n          ],\n          \"mac\": \"00:50:56:CC:01:02\",\n          \"rackUnit\": 18,\n          \"uHeight\": \"1\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"dmz-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"waf-1\": {\n          \"shape\": \"firewall\",\n          \"name\": \"WAF Appliance\",\n          \"ip\": \"172.16.0.5\",\n          \"role\": \"Web Application Firewall\",\n          \"tags\": [\n            \"dmz\",\n            \"security\",\n            \"waf\"\n          ],\n          \"notes\": [\n            \"F5 BIG-IP ASM\",\n            \"OWASP protection\"\n          ],\n          \"mac\": \"00:50:56:CC:02:01\",\n          \"rackUnit\": 22,\n          \"uHeight\": \"2\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"dmz-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"load-balancer-dmz\": {\n          \"shape\": \"switch\",\n          \"name\": \"DMZ Load Balancer\",\n          \"ip\": \"172.16.0.3\",\n          \"role\": \"Load Balancing\",\n          \"tags\": [\n            \"dmz\",\n            \"lb\",\n            \"f5\"\n          ],\n          \"notes\": [\n            \"F5 BIG-IP LTM\",\n            \"VIP: 172.16.0.100\"\n          ],\n          \"mac\": \"00:50:56:CC:03:01\",\n          \"rackUnit\": 16,\n          \"uHeight\": \"2\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"dmz-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"mail-gateway\": {\n          \"shape\": \"server\",\n          \"name\": \"Mail Gateway\",\n          \"ip\": \"172.16.0.25\",\n          \"role\": \"Email Security\",\n          \"tags\": [\n            \"dmz\",\n            \"email\",\n            \"security\"\n          ],\n          \"notes\": [\n            \"Proofpoint Email Gateway\",\n            \"Spam/malware filtering\"\n          ],\n          \"mac\": \"00:50:56:CC:04:01\",\n          \"rackUnit\": 14,\n          \"uHeight\": \"1\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"dmz-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dns-external-1\": {\n          \"shape\": \"circle\",\n          \"name\": \"External DNS 1\",\n          \"ip\": \"172.16.0.53\",\n          \"role\": \"External DNS\",\n          \"tags\": [\n            \"dmz\",\n            \"dns\",\n            \"public\"\n          ],\n          \"notes\": [\n            \"BIND DNS\",\n            \"Authoritative for corp.com\"\n          ],\n          \"mac\": \"00:50:56:CC:05:01\",\n          \"rackUnit\": 12,\n          \"uHeight\": \"1\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"dmz-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dns-external-2\": {\n          \"shape\": \"circle\",\n          \"name\": \"External DNS 2\",\n          \"ip\": \"172.16.0.54\",\n          \"role\": \"External DNS\",\n          \"tags\": [\n            \"dmz\",\n            \"dns\",\n            \"public\"\n          ],\n          \"notes\": [\n            \"BIND DNS\",\n            \"Secondary for corp.com\"\n          ],\n          \"mac\": \"00:50:56:CC:05:02\",\n          \"rackUnit\": 10,\n          \"uHeight\": \"1\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"dmz-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"vcenter\": {\n          \"shape\": \"server\",\n          \"name\": \"vCenter Server\",\n          \"ip\": \"192.168.100.10\",\n          \"role\": \"Virtualization Management\",\n          \"tags\": [\n            \"management\",\n            \"vmware\",\n            \"vcsa\"\n          ],\n          \"notes\": [\n            \"vCenter Server Appliance 8.0\",\n            \"Single SSO domain\"\n          ],\n          \"mac\": \"00:50:56:DD:01:01\",\n          \"rackUnit\": 20,\n          \"uHeight\": \"2\",\n          \"layer\": \"logical\",\n          \"assignedRack\": \"mgmt-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"nsx-manager\": {\n          \"shape\": \"server\",\n          \"name\": \"NSX Manager\",\n          \"ip\": \"192.168.100.15\",\n          \"role\": \"Network Virtualization\",\n          \"tags\": [\n            \"management\",\n            \"vmware\",\n            \"nsx\"\n          ],\n          \"notes\": [\n            \"NSX-T 4.1 Manager Cluster\"\n          ],\n          \"mac\": \"00:50:56:DD:02:01\",\n          \"rackUnit\": 17,\n          \"uHeight\": \"2\",\n          \"layer\": \"logical\",\n          \"assignedRack\": \"mgmt-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"siem-server\": {\n          \"shape\": \"server\",\n          \"name\": \"SIEM Server\",\n          \"ip\": \"192.168.100.50\",\n          \"role\": \"Security Monitoring\",\n          \"tags\": [\n            \"management\",\n            \"security\",\n            \"splunk\"\n          ],\n          \"notes\": [\n            \"Splunk Enterprise\",\n            \"Security monitoring\"\n          ],\n          \"mac\": \"00:50:56:DD:03:01\",\n          \"rackUnit\": 14,\n          \"uHeight\": \"2\",\n          \"layer\": \"logical\",\n          \"assignedRack\": \"mgmt-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"nms-server\": {\n          \"shape\": \"server\",\n          \"name\": \"Network Monitoring\",\n          \"ip\": \"192.168.100.60\",\n          \"role\": \"Network Management\",\n          \"tags\": [\n            \"management\",\n            \"monitoring\",\n            \"prtg\"\n          ],\n          \"notes\": [\n            \"PRTG Network Monitor\",\n            \"5000 sensors\"\n          ],\n          \"mac\": \"00:50:56:DD:04:01\",\n          \"rackUnit\": 11,\n          \"uHeight\": \"1\",\n          \"layer\": \"logical\",\n          \"assignedRack\": \"mgmt-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"jump-server\": {\n          \"shape\": \"server\",\n          \"name\": \"Jump Server\",\n          \"ip\": \"192.168.100.100\",\n          \"role\": \"Bastion Host\",\n          \"tags\": [\n            \"management\",\n            \"security\",\n            \"bastion\"\n          ],\n          \"notes\": [\n            \"Windows Server 2022\",\n            \"MFA enabled\"\n          ],\n          \"mac\": \"00:50:56:DD:05:01\",\n          \"rackUnit\": 9,\n          \"uHeight\": \"1\",\n          \"layer\": \"logical\",\n          \"assignedRack\": \"mgmt-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"ipam-server\": {\n          \"shape\": \"server\",\n          \"name\": \"IPAM/DDI\",\n          \"ip\": \"192.168.100.70\",\n          \"role\": \"IP Management\",\n          \"tags\": [\n            \"management\",\n            \"dns\",\n            \"dhcp\"\n          ],\n          \"notes\": [\n            \"Infoblox DDI\",\n            \"DNS/DHCP/IPAM\"\n          ],\n          \"mac\": \"00:50:56:DD:06:01\",\n          \"rackUnit\": 7,\n          \"uHeight\": \"2\",\n          \"layer\": \"logical\",\n          \"assignedRack\": \"mgmt-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"wlc-primary\": {\n          \"shape\": \"wifi\",\n          \"name\": \"WLC Primary\",\n          \"ip\": \"10.20.0.1\",\n          \"role\": \"Wireless Controller\",\n          \"tags\": [\n            \"wireless\",\n            \"cisco\",\n            \"9800\"\n          ],\n          \"notes\": [\n            \"Cisco C9800-40\",\n            \"Primary controller\"\n          ],\n          \"mac\": \"00:1A:2B:WL:01:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"wlc-secondary\": {\n          \"shape\": \"wifi\",\n          \"name\": \"WLC Secondary\",\n          \"ip\": \"10.20.0.2\",\n          \"role\": \"Wireless Controller\",\n          \"tags\": [\n            \"wireless\",\n            \"cisco\",\n            \"9800\"\n          ],\n          \"notes\": [\n            \"Cisco C9800-40\",\n            \"HA Secondary\"\n          ],\n          \"mac\": \"00:1A:2B:WL:01:02\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"mobile-zone-hq\": {\n          \"shape\": \"phone\",\n          \"name\": \"HQ Mobile Zone\",\n          \"ip\": \"10.20.10.0/24\",\n          \"role\": \"Mobile Device Zone\",\n          \"tags\": [\n            \"wireless\",\n            \"byod\",\n            \"mobile\"\n          ],\n          \"notes\": [\n            \"Corporate BYOD\",\n            \"MDM enrolled devices\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"mobile-zone-guest\": {\n          \"shape\": \"phone\",\n          \"name\": \"Guest WiFi Zone\",\n          \"ip\": \"10.30.0.0/24\",\n          \"role\": \"Guest Network\",\n          \"tags\": [\n            \"wireless\",\n            \"guest\",\n            \"isolated\"\n          ],\n          \"notes\": [\n            \"Captive portal\",\n            \"Internet only\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"mobile-zone-iot\": {\n          \"shape\": \"phone\",\n          \"name\": \"IoT Device Zone\",\n          \"ip\": \"10.40.0.0/24\",\n          \"role\": \"IoT Network\",\n          \"tags\": [\n            \"wireless\",\n            \"iot\",\n            \"building\"\n          ],\n          \"notes\": [\n            \"Building automation\",\n            \"Smart devices\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"branch-router-ny\": {\n          \"shape\": \"router\",\n          \"name\": \"NYC Branch Router\",\n          \"ip\": \"10.100.0.1\",\n          \"role\": \"Branch Gateway\",\n          \"tags\": [\n            \"branch\",\n            \"nyc\",\n            \"sd-wan\"\n          ],\n          \"notes\": [\n            \"Cisco Viptela vEdge\",\n            \"SD-WAN enabled\"\n          ],\n          \"mac\": \"00:1A:2B:BR:01:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"branch-router-la\": {\n          \"shape\": \"router\",\n          \"name\": \"LA Branch Router\",\n          \"ip\": \"10.101.0.1\",\n          \"role\": \"Branch Gateway\",\n          \"tags\": [\n            \"branch\",\n            \"la\",\n            \"sd-wan\"\n          ],\n          \"notes\": [\n            \"Cisco Viptela vEdge\",\n            \"SD-WAN enabled\"\n          ],\n          \"mac\": \"00:1A:2B:BR:02:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"branch-router-chi\": {\n          \"shape\": \"router\",\n          \"name\": \"Chicago Branch Router\",\n          \"ip\": \"10.102.0.1\",\n          \"role\": \"Branch Gateway\",\n          \"tags\": [\n            \"branch\",\n            \"chicago\",\n            \"sd-wan\"\n          ],\n          \"notes\": [\n            \"Cisco Viptela vEdge\",\n            \"SD-WAN enabled\"\n          ],\n          \"mac\": \"00:1A:2B:BR:03:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"branch-router-lon\": {\n          \"shape\": \"router\",\n          \"name\": \"London Branch Router\",\n          \"ip\": \"10.200.0.1\",\n          \"role\": \"Branch Gateway\",\n          \"tags\": [\n            \"branch\",\n            \"london\",\n            \"sd-wan\"\n          ],\n          \"notes\": [\n            \"Cisco Viptela vEdge\",\n            \"EMEA region\"\n          ],\n          \"mac\": \"00:1A:2B:BR:04:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"branch-router-tokyo\": {\n          \"shape\": \"router\",\n          \"name\": \"Tokyo Branch Router\",\n          \"ip\": \"10.201.0.1\",\n          \"role\": \"Branch Gateway\",\n          \"tags\": [\n            \"branch\",\n            \"tokyo\",\n            \"sd-wan\"\n          ],\n          \"notes\": [\n            \"Cisco Viptela vEdge\",\n            \"APAC region\"\n          ],\n          \"mac\": \"00:1A:2B:BR:05:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"cloud-aws\": {\n          \"shape\": \"cloud\",\n          \"name\": \"AWS Cloud\",\n          \"ip\": \"vpc-0a1b2c3d\",\n          \"role\": \"Public Cloud\",\n          \"tags\": [\n            \"cloud\",\n            \"aws\",\n            \"hybrid\"\n          ],\n          \"notes\": [\n            \"AWS US-East-1\",\n            \"VPC peering to HQ\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"logical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"cloud-azure\": {\n          \"shape\": \"cloud\",\n          \"name\": \"Azure Cloud\",\n          \"ip\": \"vnet-corp-prod\",\n          \"role\": \"Public Cloud\",\n          \"tags\": [\n            \"cloud\",\n            \"azure\",\n            \"hybrid\"\n          ],\n          \"notes\": [\n            \"Azure East US 2\",\n            \"ExpressRoute\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"logical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"cloud-gcp\": {\n          \"shape\": \"cloud\",\n          \"name\": \"GCP Cloud\",\n          \"ip\": \"vpc-gcp-corp\",\n          \"role\": \"Public Cloud\",\n          \"tags\": [\n            \"cloud\",\n            \"gcp\",\n            \"dev\"\n          ],\n          \"notes\": [\n            \"GCP us-central1\",\n            \"Dev/Test workloads\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"logical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"isp-primary\": {\n          \"shape\": \"globe\",\n          \"name\": \"ISP Primary\",\n          \"ip\": \"203.0.113.1\",\n          \"role\": \"Internet Uplink\",\n          \"tags\": [\n            \"wan\",\n            \"internet\",\n            \"primary\"\n          ],\n          \"notes\": [\n            \"AT&T MPLS\",\n            \"1 Gbps dedicated\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"isp-secondary\": {\n          \"shape\": \"vm\",\n          \"name\": \"ISP Secondary\",\n          \"ip\": \"198.51.100.1\",\n          \"role\": \"Internet Uplink\",\n          \"tags\": [\n            \"wan\",\n            \"internet\",\n            \"backup\"\n          ],\n          \"notes\": [\n            \"Verizon Business\",\n            \"500 Mbps backup\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null,\n          \"rotation\": -17\n        },\n        \"dc-internal-1\": {\n          \"shape\": \"circle\",\n          \"name\": \"DC1 Int DNS\",\n          \"ip\": \"10.10.0.53\",\n          \"role\": \"Internal DNS/AD\",\n          \"tags\": [\n            \"dns\",\n            \"ad\",\n            \"dc1\"\n          ],\n          \"notes\": [\n            \"Windows Server 2022\",\n            \"Primary DC\"\n          ],\n          \"mac\": \"00:50:56:AD:01:01\",\n          \"rackUnit\": 26,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dc-internal-2\": {\n          \"shape\": \"circle\",\n          \"name\": \"DC2 Int DNS\",\n          \"ip\": \"10.10.1.53\",\n          \"role\": \"Internal DNS/AD\",\n          \"tags\": [\n            \"dns\",\n            \"ad\",\n            \"dc2\"\n          ],\n          \"notes\": [\n            \"Windows Server 2022\",\n            \"Secondary DC\"\n          ],\n          \"mac\": \"00:50:56:AD:01:02\",\n          \"rackUnit\": 26,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"app-server-1\": {\n          \"shape\": \"server\",\n          \"name\": \"App Server 01\",\n          \"ip\": \"10.10.0.101\",\n          \"role\": \"Application\",\n          \"tags\": [\n            \"app\",\n            \"iis\",\n            \"web\"\n          ],\n          \"notes\": [\n            \"Windows Server 2022\",\n            \"IIS Application\"\n          ],\n          \"mac\": \"00:50:56:AP:01:01\",\n          \"rackUnit\": 24,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"app-server-2\": {\n          \"shape\": \"server\",\n          \"name\": \"App Server 02\",\n          \"ip\": \"10.10.0.102\",\n          \"role\": \"Application\",\n          \"tags\": [\n            \"app\",\n            \"iis\",\n            \"web\"\n          ],\n          \"notes\": [\n            \"Windows Server 2022\",\n            \"IIS Application\"\n          ],\n          \"mac\": \"00:50:56:AP:01:02\",\n          \"rackUnit\": 22,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"db-server-1\": {\n          \"shape\": \"database\",\n          \"name\": \"SQL Server 01\",\n          \"ip\": \"10.10.0.201\",\n          \"role\": \"Database\",\n          \"tags\": [\n            \"db\",\n            \"sql\",\n            \"primary\"\n          ],\n          \"notes\": [\n            \"SQL Server 2022 Enterprise\",\n            \"AlwaysOn Primary\"\n          ],\n          \"mac\": \"00:50:56:DB:01:01\",\n          \"rackUnit\": 20,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"db-server-2\": {\n          \"shape\": \"database\",\n          \"name\": \"SQL Server 02\",\n          \"ip\": \"10.10.1.201\",\n          \"role\": \"Database\",\n          \"tags\": [\n            \"db\",\n            \"sql\",\n            \"secondary\"\n          ],\n          \"notes\": [\n            \"SQL Server 2022 Enterprise\",\n            \"AlwaysOn Secondary\"\n          ],\n          \"mac\": \"00:50:56:DB:01:02\",\n          \"rackUnit\": 24,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"k8s-master-1\": {\n          \"shape\": \"hexagon\",\n          \"name\": \"K8s Master 1\",\n          \"ip\": \"10.10.1.50\",\n          \"role\": \"Container Orchestration\",\n          \"tags\": [\n            \"kubernetes\",\n            \"master\",\n            \"container\"\n          ],\n          \"notes\": [\n            \"K8s Control Plane\",\n            \"etcd member\"\n          ],\n          \"mac\": \"00:50:56:K8:01:01\",\n          \"rackUnit\": 21,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"k8s-master-2\": {\n          \"shape\": \"hexagon\",\n          \"name\": \"K8s Master 2\",\n          \"ip\": \"10.10.1.51\",\n          \"role\": \"Container Orchestration\",\n          \"tags\": [\n            \"kubernetes\",\n            \"master\",\n            \"container\"\n          ],\n          \"notes\": [\n            \"K8s Control Plane\",\n            \"etcd member\"\n          ],\n          \"mac\": \"00:50:56:K8:01:02\",\n          \"rackUnit\": 19,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"k8s-master-3\": {\n          \"shape\": \"hexagon\",\n          \"name\": \"K8s Master 3\",\n          \"ip\": \"10.10.1.52\",\n          \"role\": \"Container Orchestration\",\n          \"tags\": [\n            \"kubernetes\",\n            \"master\",\n            \"container\"\n          ],\n          \"notes\": [\n            \"K8s Control Plane\",\n            \"etcd member\"\n          ],\n          \"mac\": \"00:50:56:K8:01:03\",\n          \"rackUnit\": 17,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"k8s-worker-1\": {\n          \"shape\": \"server\",\n          \"name\": \"K8s Worker 1\",\n          \"ip\": \"10.10.1.60\",\n          \"role\": \"Container Workload\",\n          \"tags\": [\n            \"kubernetes\",\n            \"worker\",\n            \"container\"\n          ],\n          \"notes\": [\n            \"K8s Worker Node\",\n            \"64GB RAM\"\n          ],\n          \"mac\": \"00:50:56:K8:02:01\",\n          \"rackUnit\": 15,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"k8s-worker-2\": {\n          \"shape\": \"server\",\n          \"name\": \"K8s Worker 2\",\n          \"ip\": \"10.10.1.61\",\n          \"role\": \"Container Workload\",\n          \"tags\": [\n            \"kubernetes\",\n            \"worker\",\n            \"container\"\n          ],\n          \"notes\": [\n            \"K8s Worker Node\",\n            \"64GB RAM\"\n          ],\n          \"mac\": \"00:50:56:K8:02:02\",\n          \"rackUnit\": 13,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"k8s-worker-3\": {\n          \"shape\": \"server\",\n          \"name\": \"K8s Worker 3\",\n          \"ip\": \"10.10.1.62\",\n          \"role\": \"Container Workload\",\n          \"tags\": [\n            \"kubernetes\",\n            \"worker\",\n            \"container\"\n          ],\n          \"notes\": [\n            \"K8s Worker Node\",\n            \"64GB RAM\"\n          ],\n          \"mac\": \"00:50:56:K8:02:03\",\n          \"rackUnit\": 11,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"k8s-worker-4\": {\n          \"shape\": \"server\",\n          \"name\": \"K8s Worker 4\",\n          \"ip\": \"10.10.1.63\",\n          \"role\": \"Container Workload\",\n          \"tags\": [\n            \"kubernetes\",\n            \"worker\",\n            \"container\"\n          ],\n          \"notes\": [\n            \"K8s Worker Node\",\n            \"64GB RAM\"\n          ],\n          \"mac\": \"00:50:56:K8:02:04\",\n          \"rackUnit\": 9,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"proxy-server-1\": {\n          \"shape\": \"server\",\n          \"name\": \"Proxy Server 1\",\n          \"ip\": \"10.5.0.10\",\n          \"role\": \"Web Proxy\",\n          \"tags\": [\n            \"proxy\",\n            \"squid\",\n            \"filtering\"\n          ],\n          \"notes\": [\n            \"Squid Proxy\",\n            \"Content filtering\"\n          ],\n          \"mac\": \"00:50:56:PX:01:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"proxy-server-2\": {\n          \"shape\": \"server\",\n          \"name\": \"Proxy Server 2\",\n          \"ip\": \"10.5.0.11\",\n          \"role\": \"Web Proxy\",\n          \"tags\": [\n            \"proxy\",\n            \"squid\",\n            \"filtering\"\n          ],\n          \"notes\": [\n            \"Squid Proxy\",\n            \"HA pair\"\n          ],\n          \"mac\": \"00:50:56:PX:01:02\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"vpn-concentrator\": {\n          \"shape\": \"firewall\",\n          \"name\": \"VPN Concentrator\",\n          \"ip\": \"10.0.5.1\",\n          \"role\": \"Remote Access VPN\",\n          \"tags\": [\n            \"vpn\",\n            \"remote\",\n            \"security\"\n          ],\n          \"notes\": [\n            \"Cisco ASA 5555-X\",\n            \"AnyConnect SSL VPN\"\n          ],\n          \"mac\": \"00:1A:2B:VP:01:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"nac-server\": {\n          \"shape\": \"server\",\n          \"name\": \"NAC Server\",\n          \"ip\": \"10.5.5.10\",\n          \"role\": \"Network Access Control\",\n          \"tags\": [\n            \"nac\",\n            \"ise\",\n            \"802.1x\"\n          ],\n          \"notes\": [\n            \"Cisco ISE 3.1\",\n            \"RADIUS/TACACS+\"\n          ],\n          \"mac\": \"00:50:56:NA:01:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"print-server\": {\n          \"shape\": \"server\",\n          \"name\": \"Print Server\",\n          \"ip\": \"10.10.0.150\",\n          \"role\": \"Print Services\",\n          \"tags\": [\n            \"print\",\n            \"windows\",\n            \"services\"\n          ],\n          \"notes\": [\n            \"Windows Print Server\",\n            \"50+ printers\"\n          ],\n          \"mac\": \"00:50:56:PR:01:01\",\n          \"rackUnit\": 18,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"file-server\": {\n          \"shape\": \"database\",\n          \"name\": \"File Server\",\n          \"ip\": \"10.10.0.160\",\n          \"role\": \"File Services\",\n          \"tags\": [\n            \"file\",\n            \"smb\",\n            \"dfs\"\n          ],\n          \"notes\": [\n            \"Windows File Server\",\n            \"DFS namespace\"\n          ],\n          \"mac\": \"00:50:56:FS:01:01\",\n          \"rackUnit\": 16,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"ca-server\": {\n          \"shape\": \"server\",\n          \"name\": \"Certificate Authority\",\n          \"ip\": \"192.168.100.80\",\n          \"role\": \"PKI Infrastructure\",\n          \"tags\": [\n            \"pki\",\n            \"ca\",\n            \"security\"\n          ],\n          \"notes\": [\n            \"Windows CA\",\n            \"Enterprise Root CA\"\n          ],\n          \"mac\": \"00:50:56:CA:01:01\",\n          \"rackUnit\": 5,\n          \"uHeight\": \"1\",\n          \"layer\": \"logical\",\n          \"assignedRack\": \"mgmt-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"sccm-server\": {\n          \"shape\": \"server\",\n          \"name\": \"SCCM Server\",\n          \"ip\": \"192.168.100.90\",\n          \"role\": \"Endpoint Management\",\n          \"tags\": [\n            \"sccm\",\n            \"patching\",\n            \"software\"\n          ],\n          \"notes\": [\n            \"MECM Primary Site\",\n            \"Software deployment\"\n          ],\n          \"mac\": \"00:50:56:SC:01:01\",\n          \"rackUnit\": 3,\n          \"uHeight\": \"2\",\n          \"layer\": \"logical\",\n          \"assignedRack\": \"mgmt-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"voip-cluster\": {\n          \"shape\": \"phone\",\n          \"name\": \"VoIP Cluster\",\n          \"ip\": \"10.50.0.0/24\",\n          \"role\": \"Voice Services\",\n          \"tags\": [\n            \"voip\",\n            \"cisco\",\n            \"ucm\"\n          ],\n          \"notes\": [\n            \"Cisco UCM Cluster\",\n            \"3000 endpoints\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"application\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"video-conf\": {\n          \"shape\": \"laptop\",\n          \"name\": \"Video Conference\",\n          \"ip\": \"10.51.0.0/24\",\n          \"role\": \"Video Services\",\n          \"tags\": [\n            \"video\",\n            \"webex\",\n            \"teams\"\n          ],\n          \"notes\": [\n            \"Webex/Teams integration\",\n            \"Meeting rooms\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"application\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"security-cameras\": {\n          \"shape\": \"camera\",\n          \"name\": \"Security Cameras\",\n          \"ip\": \"10.60.0.0/24\",\n          \"role\": \"Physical Security\",\n          \"tags\": [\n            \"cctv\",\n            \"surveillance\",\n            \"security\"\n          ],\n          \"notes\": [\n            \"150+ IP cameras\",\n            \"30-day retention\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"nvr-cluster\": {\n          \"shape\": \"server\",\n          \"name\": \"NVR Cluster\",\n          \"ip\": \"10.60.0.10\",\n          \"role\": \"Video Recording\",\n          \"tags\": [\n            \"nvr\",\n            \"surveillance\",\n            \"storage\"\n          ],\n          \"notes\": [\n            \"Milestone XProtect\",\n            \"500TB storage\"\n          ],\n          \"mac\": \"00:50:56:NV:01:01\",\n          \"rackUnit\": 15,\n          \"uHeight\": \"4\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-b2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dev-server-1\": {\n          \"shape\": \"server\",\n          \"name\": \"Dev Server 1\",\n          \"ip\": \"10.80.0.10\",\n          \"role\": \"Development\",\n          \"tags\": [\n            \"dev\",\n            \"gitlab\",\n            \"ci-cd\",\n            {\n              \"type\": \"icon\",\n              \"library\": \"selfhst\",\n              \"name\": \"dokku\"\n            }\n          ],\n          \"notes\": [\n            \"GitLab Server\",\n            \"CI/CD pipelines\"\n          ],\n          \"mac\": \"00:50:56:DV:01:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"application\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dev-server-2\": {\n          \"shape\": \"server\",\n          \"name\": \"Dev Server 2\",\n          \"ip\": \"10.80.0.11\",\n          \"role\": \"Development\",\n          \"tags\": [\n            \"dev\",\n            \"jenkins\",\n            \"ci-cd\"\n          ],\n          \"notes\": [\n            \"Jenkins Server\",\n            \"Build automation\"\n          ],\n          \"mac\": \"00:50:56:DV:01:02\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"application\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"test-environment\": {\n          \"shape\": \"shield\",\n          \"name\": \"Test Environment\",\n          \"ip\": \"10.81.0.0/24\",\n          \"role\": \"QA/Testing\",\n          \"tags\": [\n            \"test\",\n            \"qa\",\n            \"staging\"\n          ],\n          \"notes\": [\n            \"Staging environment\",\n            \"Pre-prod validation\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"application\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null,\n          \"rotation\": -36\n        },\n        \"erp-system\": {\n          \"shape\": \"database\",\n          \"name\": \"ERP System\",\n          \"ip\": \"10.90.0.10\",\n          \"role\": \"Business Application\",\n          \"tags\": [\n            \"erp\",\n            \"sap\",\n            \"business\"\n          ],\n          \"notes\": [\n            \"SAP S/4HANA\",\n            \"Financial/HR systems\"\n          ],\n          \"mac\": \"00:50:56:ER:01:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"4\",\n          \"layer\": \"application\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"crm-system\": {\n          \"shape\": \"database\",\n          \"name\": \"CRM System\",\n          \"ip\": \"10.91.0.10\",\n          \"role\": \"Business Application\",\n          \"tags\": [\n            \"crm\",\n            \"salesforce\",\n            \"business\"\n          ],\n          \"notes\": [\n            \"Salesforce integration\",\n            \"Sales/Marketing\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"application\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"endpoint-1000\": {\n          \"shape\": \"laptop\",\n          \"name\": \"Corporate Endpoints\",\n          \"ip\": \"10.70.0.0/22\",\n          \"role\": \"User Workstations\",\n          \"tags\": [\n            \"endpoints\",\n            \"workstations\",\n            \"users\"\n          ],\n          \"notes\": [\n            \"~1000 corporate laptops\",\n            \"Windows 11\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dist-switch-floor1\": {\n          \"shape\": \"switch\",\n          \"name\": \"Floor 1 Switch\",\n          \"ip\": \"10.1.1.1\",\n          \"role\": \"Distribution\",\n          \"tags\": [\n            \"distribution\",\n            \"floor-1\",\n            \"access\"\n          ],\n          \"notes\": [\n            \"Cisco C9300-48P\",\n            \"PoE+ enabled\"\n          ],\n          \"mac\": \"00:1A:2B:FL:01:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dist-switch-floor2\": {\n          \"shape\": \"switch\",\n          \"name\": \"Floor 2 Switch\",\n          \"ip\": \"10.1.2.1\",\n          \"role\": \"Distribution\",\n          \"tags\": [\n            \"distribution\",\n            \"floor-2\",\n            \"access\"\n          ],\n          \"notes\": [\n            \"Cisco C9300-48P\",\n            \"PoE+ enabled\"\n          ],\n          \"mac\": \"00:1A:2B:FL:02:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dist-switch-floor3\": {\n          \"shape\": \"switch\",\n          \"name\": \"Floor 3 Switch\",\n          \"ip\": \"10.1.3.1\",\n          \"role\": \"Distribution\",\n          \"tags\": [\n            \"distribution\",\n            \"floor-3\",\n            \"access\"\n          ],\n          \"notes\": [\n            \"Cisco C9300-48P\",\n            \"PoE+ enabled\"\n          ],\n          \"mac\": \"00:1A:2B:FL:03:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dist-switch-floor4\": {\n          \"shape\": \"switch\",\n          \"name\": \"Floor 4 Switch\",\n          \"ip\": \"10.1.4.1\",\n          \"role\": \"Distribution\",\n          \"tags\": [\n            \"distribution\",\n            \"floor-4\",\n            \"access\"\n          ],\n          \"notes\": [\n            \"Cisco C9300-48P\",\n            \"PoE+ enabled\"\n          ],\n          \"mac\": \"00:1A:2B:FL:04:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"ap-floor1-zone1\": {\n          \"shape\": \"wifi\",\n          \"name\": \"AP Floor 1 Zone 1\",\n          \"ip\": \"10.20.1.10\",\n          \"role\": \"Wireless Access\",\n          \"tags\": [\n            \"wifi\",\n            \"ap\",\n            \"floor-1\"\n          ],\n          \"notes\": [\n            \"Cisco 9120AX\",\n            \"Wi-Fi 6\"\n          ],\n          \"mac\": \"00:1A:2B:AP:01:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"ap-floor2-zone1\": {\n          \"shape\": \"wifi\",\n          \"name\": \"AP Floor 2 Zone 1\",\n          \"ip\": \"10.20.2.10\",\n          \"role\": \"Wireless Access\",\n          \"tags\": [\n            \"wifi\",\n            \"ap\",\n            \"floor-2\"\n          ],\n          \"notes\": [\n            \"Cisco 9120AX\",\n            \"Wi-Fi 6\"\n          ],\n          \"mac\": \"00:1A:2B:AP:02:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"ap-floor3-zone1\": {\n          \"shape\": \"wifi\",\n          \"name\": \"AP Floor 3 Zone 1\",\n          \"ip\": \"10.20.3.10\",\n          \"role\": \"Wireless Access\",\n          \"tags\": [\n            \"wifi\",\n            \"ap\",\n            \"floor-3\"\n          ],\n          \"notes\": [\n            \"Cisco 9120AX\",\n            \"Wi-Fi 6\"\n          ],\n          \"mac\": \"00:1A:2B:AP:03:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"ap-floor4-zone1\": {\n          \"shape\": \"wifi\",\n          \"name\": \"AP Floor 4 Zone 1\",\n          \"ip\": \"10.20.4.10\",\n          \"role\": \"Wireless Access\",\n          \"tags\": [\n            \"wifi\",\n            \"ap\",\n            \"floor-4\"\n          ],\n          \"notes\": [\n            \"Cisco 9120AX\",\n            \"Wi-Fi 6\"\n          ],\n          \"mac\": \"00:1A:2B:AP:04:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"ups-dc-1\": {\n          \"shape\": \"rectangle\",\n          \"name\": \"UPS DC-1\",\n          \"ip\": \"192.168.200.10\",\n          \"role\": \"Power Management\",\n          \"tags\": [\n            \"power\",\n            \"ups\",\n            \"datacenter\"\n          ],\n          \"notes\": [\n            \"APC Symmetra\",\n            \"80kVA\",\n            \"30 min runtime\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"ups-dc-2\": {\n          \"shape\": \"rectangle\",\n          \"name\": \"UPS DC-2\",\n          \"ip\": \"192.168.200.11\",\n          \"role\": \"Power Management\",\n          \"tags\": [\n            \"power\",\n            \"ups\",\n            \"datacenter\"\n          ],\n          \"notes\": [\n            \"APC Symmetra\",\n            \"80kVA\",\n            \"Redundant\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"pdu-rack-a1\": {\n          \"shape\": \"rectangle\",\n          \"name\": \"PDU Rack A1\",\n          \"ip\": \"192.168.200.21\",\n          \"role\": \"Power Distribution\",\n          \"tags\": [\n            \"power\",\n            \"pdu\",\n            \"rack-a1\"\n          ],\n          \"notes\": [\n            \"APC Switched PDU\",\n            \"Per-outlet metering\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": 1,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"pdu-rack-a2\": {\n          \"shape\": \"rectangle\",\n          \"name\": \"PDU Rack A2\",\n          \"ip\": \"192.168.200.22\",\n          \"role\": \"Power Distribution\",\n          \"tags\": [\n            \"power\",\n            \"pdu\",\n            \"rack-a2\"\n          ],\n          \"notes\": [\n            \"APC Switched PDU\",\n            \"Per-outlet metering\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": 1,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"cooling-1\": {\n          \"shape\": \"rectangle\",\n          \"name\": \"CRAC Unit 1\",\n          \"ip\": \"192.168.200.30\",\n          \"role\": \"Cooling\",\n          \"tags\": [\n            \"cooling\",\n            \"hvac\",\n            \"datacenter\"\n          ],\n          \"notes\": [\n            \"Liebert CRV\",\n            \"Row-based cooling\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"cooling-2\": {\n          \"shape\": \"rectangle\",\n          \"name\": \"CRAC Unit 2\",\n          \"ip\": \"192.168.200.31\",\n          \"role\": \"Cooling\",\n          \"tags\": [\n            \"cooling\",\n            \"hvac\",\n            \"datacenter\"\n          ],\n          \"notes\": [\n            \"Liebert CRV\",\n            \"N+1 redundancy\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"camera-a\": {\n          \"shape\": \"camera\",\n          \"name\": \"camera A\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"ping\": {\n            \"enabled\": false,\n            \"protocol\": \"http\",\n            \"customUrl\": \"\",\n            \"timeout\": 3000,\n            \"status\": \"unknown\",\n            \"lastCheck\": null\n          },\n          \"locked\": false,\n          \"groupId\": null,\n          \"fovEnabled\": true,\n          \"fovRotation\": 104,\n          \"fovDistance\": 500,\n          \"fovSweep\": 60,\n          \"fovSpeed\": 10,\n          \"fovAnimate\": true\n        },\n        \"camera-a-copy\": {\n          \"shape\": \"camera\",\n          \"name\": \"camera B\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"ping\": {\n            \"enabled\": false,\n            \"protocol\": \"http\",\n            \"customUrl\": \"\",\n            \"timeout\": 3000,\n            \"status\": \"unknown\",\n            \"lastCheck\": null\n          },\n          \"locked\": false,\n          \"groupId\": null,\n          \"fovEnabled\": true,\n          \"fovRotation\": 162,\n          \"fovDistance\": 500,\n          \"fovSweep\": 60,\n          \"fovSpeed\": 10,\n          \"fovAnimate\": false\n        }\n      },\n      \"edges\": {\n        \"list\": [\n          {\n            \"id\": \"isp1-router1\",\n            \"from\": \"isp-primary\",\n            \"to\": \"core-router-1\",\n            \"width\": 6,\n            \"color\": \"#10b981\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"Primary WAN link\"\n            ],\n            \"fromPort\": \"Gi0/0\",\n            \"toPort\": \"Gi1/0/1\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"isp2-router2\",\n            \"from\": \"isp-secondary\",\n            \"to\": \"core-router-2\",\n            \"width\": 6,\n            \"color\": \"#10b981\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"Backup WAN link\"\n            ],\n            \"fromPort\": \"Gi0/0\",\n            \"toPort\": \"Gi1/0/1\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"router1-router2\",\n            \"from\": \"core-router-1\",\n            \"to\": \"core-router-2\",\n            \"width\": 4,\n            \"color\": \"#f59e0b\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"HSRP Peering\"\n            ],\n            \"fromPort\": \"Gi1/0/24\",\n            \"toPort\": \"Gi1/0/24\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"router1-fw1\",\n            \"from\": \"core-router-1\",\n            \"to\": \"fw-external-1\",\n            \"width\": 4,\n            \"color\": \"#ef4444\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"router2-fw2\",\n            \"from\": \"core-router-2\",\n            \"to\": \"fw-external-2\",\n            \"width\": 4,\n            \"color\": \"#ef4444\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"fw1-fw2\",\n            \"from\": \"fw-external-1\",\n            \"to\": \"fw-external-2\",\n            \"width\": 3,\n            \"color\": \"#f59e0b\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"HA heartbeat\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dashed\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"fw1-coresw1\",\n            \"from\": \"fw-external-1\",\n            \"to\": \"core-switch-1\",\n            \"width\": 4,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"fw2-coresw2\",\n            \"from\": \"fw-external-2\",\n            \"to\": \"core-switch-2\",\n            \"width\": 4,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-coresw2\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"core-switch-2\",\n            \"width\": 5,\n            \"color\": \"#3b82f6\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"VPC peer-link\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-fwint\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"fw-internal\",\n            \"width\": 3,\n            \"color\": \"#ef4444\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw2-fwint\",\n            \"from\": \"core-switch-2\",\n            \"to\": \"fw-internal\",\n            \"width\": 3,\n            \"color\": \"#ef4444\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-racka1\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"dc-rack-a1\",\n            \"width\": 4,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw2-racka1\",\n            \"from\": \"core-switch-2\",\n            \"to\": \"dc-rack-a1\",\n            \"width\": 4,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-racka2\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"dc-rack-a2\",\n            \"width\": 4,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw2-racka2\",\n            \"from\": \"core-switch-2\",\n            \"to\": \"dc-rack-a2\",\n            \"width\": 4,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-rackb1\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"dc-rack-b1\",\n            \"width\": 4,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw2-rackb1\",\n            \"from\": \"core-switch-2\",\n            \"to\": \"dc-rack-b1\",\n            \"width\": 4,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-rackb2\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"dc-rack-b2\",\n            \"width\": 4,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw2-rackb2\",\n            \"from\": \"core-switch-2\",\n            \"to\": \"dc-rack-b2\",\n            \"width\": 4,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"fw1-dmz\",\n            \"from\": \"fw-external-1\",\n            \"to\": \"dmz-rack\",\n            \"width\": 3,\n            \"color\": \"#f59e0b\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"DMZ segment\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"fw2-dmz\",\n            \"from\": \"fw-external-2\",\n            \"to\": \"dmz-rack\",\n            \"width\": 3,\n            \"color\": \"#f59e0b\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"DMZ segment\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-mgmt\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"mgmt-rack\",\n            \"width\": 3,\n            \"color\": \"#8b5cf6\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"OOB management\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-wlc1\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"wlc-primary\",\n            \"width\": 3,\n            \"color\": \"#06b6d4\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw2-wlc2\",\n            \"from\": \"core-switch-2\",\n            \"to\": \"wlc-secondary\",\n            \"width\": 3,\n            \"color\": \"#06b6d4\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\",\n            \"animate\": true\n          },\n          {\n            \"id\": \"wlc1-wlc2\",\n            \"from\": \"wlc-primary\",\n            \"to\": \"wlc-secondary\",\n            \"width\": 2,\n            \"color\": \"#f59e0b\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"HA pair\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dashed\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"wlc1-mobile-hq\",\n            \"from\": \"wlc-primary\",\n            \"to\": \"mobile-zone-hq\",\n            \"width\": 2,\n            \"color\": \"#06b6d4\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"wlc1-mobile-guest\",\n            \"from\": \"wlc-primary\",\n            \"to\": \"mobile-zone-guest\",\n            \"width\": 2,\n            \"color\": \"#06b6d4\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"wlc1-mobile-iot\",\n            \"from\": \"wlc-primary\",\n            \"to\": \"mobile-zone-iot\",\n            \"width\": 2,\n            \"color\": \"#06b6d4\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"router1-branch-ny\",\n            \"from\": \"core-router-1\",\n            \"to\": \"branch-router-ny\",\n            \"width\": 3,\n            \"color\": \"#a855f7\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"SD-WAN tunnel\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dashed\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"router1-branch-la\",\n            \"from\": \"core-router-1\",\n            \"to\": \"branch-router-la\",\n            \"width\": 3,\n            \"color\": \"#a855f7\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"SD-WAN tunnel\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dashed\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"router1-branch-chi\",\n            \"from\": \"core-router-1\",\n            \"to\": \"branch-router-chi\",\n            \"width\": 3,\n            \"color\": \"#a855f7\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"SD-WAN tunnel\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dashed\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"router1-branch-lon\",\n            \"from\": \"core-router-1\",\n            \"to\": \"branch-router-lon\",\n            \"width\": 3,\n            \"color\": \"#a855f7\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"SD-WAN tunnel\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dashed\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"router1-branch-tokyo\",\n            \"from\": \"core-router-1\",\n            \"to\": \"branch-router-tokyo\",\n            \"width\": 3,\n            \"color\": \"#a855f7\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"SD-WAN tunnel\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dashed\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"router1-aws\",\n            \"from\": \"core-router-1\",\n            \"to\": \"cloud-aws\",\n            \"width\": 3,\n            \"color\": \"#f97316\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"Direct Connect\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dashed\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"router2-azure\",\n            \"from\": \"core-router-2\",\n            \"to\": \"cloud-azure\",\n            \"width\": 3,\n            \"color\": \"#0ea5e9\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"ExpressRoute\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dashed\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"fwint-gcp\",\n            \"from\": \"fw-internal\",\n            \"to\": \"cloud-gcp\",\n            \"width\": 2,\n            \"color\": \"#22c55e\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"VPN tunnel\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dashed\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-floor1\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"dist-switch-floor1\",\n            \"width\": 3,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-floor2\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"dist-switch-floor2\",\n            \"width\": 3,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw2-floor3\",\n            \"from\": \"core-switch-2\",\n            \"to\": \"dist-switch-floor3\",\n            \"width\": 3,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw2-floor4\",\n            \"from\": \"core-switch-2\",\n            \"to\": \"dist-switch-floor4\",\n            \"width\": 3,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"floor1-endpoints\",\n            \"from\": \"dist-switch-floor1\",\n            \"to\": \"endpoint-1000\",\n            \"width\": 2,\n            \"color\": \"#94a3b8\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"floor1-ap1\",\n            \"from\": \"dist-switch-floor1\",\n            \"to\": \"ap-floor1-zone1\",\n            \"width\": 2,\n            \"color\": \"#06b6d4\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"floor2-ap2\",\n            \"from\": \"dist-switch-floor2\",\n            \"to\": \"ap-floor2-zone1\",\n            \"width\": 2,\n            \"color\": \"#06b6d4\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"floor3-ap3\",\n            \"from\": \"dist-switch-floor3\",\n            \"to\": \"ap-floor3-zone1\",\n            \"width\": 2,\n            \"color\": \"#06b6d4\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"floor4-ap4\",\n            \"from\": \"dist-switch-floor4\",\n            \"to\": \"ap-floor4-zone1\",\n            \"width\": 2,\n            \"color\": \"#06b6d4\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"fwint-proxy1\",\n            \"from\": \"fw-internal\",\n            \"to\": \"proxy-server-1\",\n            \"width\": 2,\n            \"color\": \"#ef4444\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"fwint-proxy2\",\n            \"from\": \"fw-internal\",\n            \"to\": \"proxy-server-2\",\n            \"width\": 2,\n            \"color\": \"#ef4444\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"fwext1-vpn\",\n            \"from\": \"fw-external-1\",\n            \"to\": \"vpn-concentrator\",\n            \"width\": 3,\n            \"color\": \"#8b5cf6\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-nac\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"nac-server\",\n            \"width\": 2,\n            \"color\": \"#f59e0b\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-voip\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"voip-cluster\",\n            \"width\": 3,\n            \"color\": \"#22c55e\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw2-video\",\n            \"from\": \"core-switch-2\",\n            \"to\": \"video-conf\",\n            \"width\": 3,\n            \"color\": \"#22c55e\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-cameras\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"security-cameras\",\n            \"width\": 2,\n            \"color\": \"#94a3b8\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"fwint-dev1\",\n            \"from\": \"fw-internal\",\n            \"to\": \"dev-server-1\",\n            \"width\": 2,\n            \"color\": \"#a855f7\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"fwint-dev2\",\n            \"from\": \"fw-internal\",\n            \"to\": \"dev-server-2\",\n            \"width\": 2,\n            \"color\": \"#a855f7\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\",\n            \"animate\": true,\n            \"animationSpeed\": \"1.5\"\n          },\n          {\n            \"id\": \"fwint-test\",\n            \"from\": \"fw-internal\",\n            \"to\": \"test-environment\",\n            \"width\": 2,\n            \"color\": \"#a855f7\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-erp\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"erp-system\",\n            \"width\": 3,\n            \"color\": \"#f59e0b\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"fwext1-crm\",\n            \"from\": \"fw-external-1\",\n            \"to\": \"crm-system\",\n            \"width\": 2,\n            \"color\": \"#f59e0b\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"Salesforce cloud\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dashed\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"ups1-racka1\",\n            \"from\": \"ups-dc-1\",\n            \"to\": \"dc-rack-a1\",\n            \"width\": 2,\n            \"color\": \"#fbbf24\",\n            \"direction\": \"forward\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"Power feed A\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"ups2-racka2\",\n            \"from\": \"ups-dc-2\",\n            \"to\": \"dc-rack-a2\",\n            \"width\": 2,\n            \"color\": \"#fbbf24\",\n            \"direction\": \"forward\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"Power feed B\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"ups1-rackb1\",\n            \"from\": \"ups-dc-1\",\n            \"to\": \"dc-rack-b1\",\n            \"width\": 2,\n            \"color\": \"#fbbf24\",\n            \"direction\": \"forward\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"Power feed A\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\",\n            \"animate\": true,\n            \"animationSpeed\": \"4\"\n          },\n          {\n            \"id\": \"ups2-rackb2\",\n            \"from\": \"ups-dc-2\",\n            \"to\": \"dc-rack-b2\",\n            \"width\": 2,\n            \"color\": \"#fbbf24\",\n            \"direction\": \"forward\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"Power feed B\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"cooling1-racka1\",\n            \"from\": \"cooling-1\",\n            \"to\": \"dc-rack-a1\",\n            \"width\": 2,\n            \"color\": \"#38bdf8\",\n            \"direction\": \"forward\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"Cooling zone\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dotted\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"cooling2-rackb1\",\n            \"from\": \"cooling-2\",\n            \"to\": \"dc-rack-b1\",\n            \"width\": 2,\n            \"color\": \"#38bdf8\",\n            \"direction\": \"forward\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"Cooling zone\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dotted\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"custom-1765237881452\",\n            \"type\": \"custom\",\n            \"color\": \"#c800ff\",\n            \"width\": 4,\n            \"lineStyle\": \"solid\",\n            \"direction\": \"forward\",\n            \"points\": [\n              {\n                \"x\": 3492.3994140625,\n                \"y\": 1526.9556884765625\n              },\n              {\n                \"x\": 3500.609619140625,\n                \"y\": 1830.7386474609375\n              },\n              {\n                \"x\": 3303.561279296875,\n                \"y\": 1732.2144775390625\n              }\n            ],\n            \"notes\": [],\n            \"routing\": \"orthogonal\"\n          }\n        ]\n      },\n      \"positions\": {\n        \"core-router-1\": {\n          \"x\": 3720.166015625,\n          \"y\": 245.9932403564453\n        },\n        \"core-router-2\": {\n          \"x\": 2499.883407638303,\n          \"y\": 329.99503430389154\n        },\n        \"fw-external-1\": {\n          \"x\": 3221.7385182723783,\n          \"y\": 1016.1364499992887\n        },\n        \"fw-external-2\": {\n          \"x\": 1915.5213706410505,\n          \"y\": 224.43528858865443\n        },\n        \"fw-internal\": {\n          \"x\": 1746.9168185079352,\n          \"y\": 477.5300527221864\n        },\n        \"core-switch-1\": {\n          \"x\": 449.39860669455675,\n          \"y\": 384.4578707617695\n        },\n        \"core-switch-2\": {\n          \"x\": 761.1664921394672,\n          \"y\": 180.89283910873155\n        },\n        \"dc-rack-a1\": {\n          \"x\": 783.7017241128451,\n          \"y\": 647.4086870405963\n        },\n        \"dc-rack-a2\": {\n          \"x\": 209.25701628255229,\n          \"y\": 228.01593190351014\n        },\n        \"dc-rack-b1\": {\n          \"x\": 3184.3186625759854,\n          \"y\": 1627.4495531027196\n        },\n        \"dc-rack-b2\": {\n          \"x\": 245.37065918741246,\n          \"y\": 499.6191264194081\n        },\n        \"dmz-rack\": {\n          \"x\": 2176.4105289561007,\n          \"y\": 610.8312056412005\n        },\n        \"mgmt-rack\": {\n          \"x\": 1601.2987201807314,\n          \"y\": 1281.4753424975324\n        },\n        \"esxi-host-01\": {\n          \"x\": 2162.2166789540615,\n          \"y\": 2608.110619289529\n        },\n        \"esxi-host-02\": {\n          \"x\": 2205.94717202368,\n          \"y\": 2689.67539624076\n        },\n        \"esxi-host-03\": {\n          \"x\": 2154.6015436939074,\n          \"y\": 2771.203009774913\n        },\n        \"esxi-host-04\": {\n          \"x\": 2195.986926025096,\n          \"y\": 2845\n        },\n        \"tor-switch-a1\": {\n          \"x\": 2146.8943639962963,\n          \"y\": 2845\n        },\n        \"esxi-host-05\": {\n          \"x\": 2185.9099961569727,\n          \"y\": 2845\n        },\n        \"esxi-host-06\": {\n          \"x\": 2139.099728450725,\n          \"y\": 2845\n        },\n        \"esxi-host-07\": {\n          \"x\": 2175.7223818764883,\n          \"y\": 2845\n        },\n        \"esxi-host-08\": {\n          \"x\": 2131.2222777148922,\n          \"y\": 2845\n        },\n        \"tor-switch-a2\": {\n          \"x\": 2165.4301485385085,\n          \"y\": 2845\n        },\n        \"san-primary\": {\n          \"x\": 2123.2667017518106,\n          \"y\": 2845\n        },\n        \"san-secondary\": {\n          \"x\": 2155.0394237844876,\n          \"y\": 2845\n        },\n        \"fc-switch-1\": {\n          \"x\": 2115.2377370375634,\n          \"y\": 2845\n        },\n        \"fc-switch-2\": {\n          \"x\": 2144.5563938942755,\n          \"y\": 2845\n        },\n        \"backup-server-1\": {\n          \"x\": 2107.1401637413705,\n          \"y\": 2845\n        },\n        \"backup-server-2\": {\n          \"x\": 2133.987300103025,\n          \"y\": 2845\n        },\n        \"tape-library\": {\n          \"x\": 2098.9788028796397,\n          \"y\": 2845\n        },\n        \"tor-switch-b1\": {\n          \"x\": 2123.338434885373,\n          \"y\": 2845\n        },\n        \"tor-switch-b2\": {\n          \"x\": 2090.7585134456995,\n          \"y\": 2845\n        },\n        \"web-server-1\": {\n          \"x\": 2112.6161382091163,\n          \"y\": 2845\n        },\n        \"web-server-2\": {\n          \"x\": 2082.484189516922,\n          \"y\": 2845\n        },\n        \"waf-1\": {\n          \"x\": 2101.826793760617,\n          \"y\": 2845\n        },\n        \"load-balancer-dmz\": {\n          \"x\": 2074.1607573409574,\n          \"y\": 2845\n        },\n        \"mail-gateway\": {\n          \"x\": 2090.97682514417,\n          \"y\": 2845\n        },\n        \"dns-external-1\": {\n          \"x\": 2065.7931724028163,\n          \"y\": 2845\n        },\n        \"dns-external-2\": {\n          \"x\": 2080.0726920576153,\n          \"y\": 2845\n        },\n        \"vcenter\": {\n          \"x\": 2057.3864164745437,\n          \"y\": 2845\n        },\n        \"nsx-manager\": {\n          \"x\": 2069.1208864464534,\n          \"y\": 2845\n        },\n        \"siem-server\": {\n          \"x\": 2048.945494649244,\n          \"y\": 2845\n        },\n        \"nms-server\": {\n          \"x\": 2058.1279286387635,\n          \"y\": 2845\n        },\n        \"jump-server\": {\n          \"x\": 2040.4754323612206,\n          \"y\": 2845\n        },\n        \"ipam-server\": {\n          \"x\": 2047.1003634632284,\n          \"y\": 2845\n        },\n        \"wlc-primary\": {\n          \"x\": 1575.9723612611924,\n          \"y\": 2306.135986328125\n        },\n        \"wlc-secondary\": {\n          \"x\": 1468.1361870166274,\n          \"y\": 1563.733642578125\n        },\n        \"mobile-zone-hq\": {\n          \"x\": 2354.901177346808,\n          \"y\": 2806.0078125\n        },\n        \"mobile-zone-guest\": {\n          \"x\": 2307.6605605284435,\n          \"y\": 2611.047119140625\n        },\n        \"mobile-zone-iot\": {\n          \"x\": 2229.397686389302,\n          \"y\": 2299.110107421875\n        },\n        \"branch-router-ny\": {\n          \"x\": 3151.903101363964,\n          \"y\": 633.6580810546875\n        },\n        \"branch-router-la\": {\n          \"x\": 3083.8876194705945,\n          \"y\": 506.90625\n        },\n        \"branch-router-chi\": {\n          \"x\": 3355.02409980103,\n          \"y\": 393.1805725097656\n        },\n        \"branch-router-lon\": {\n          \"x\": 3113.609823320121,\n          \"y\": 260.4093322753906\n        },\n        \"branch-router-tokyo\": {\n          \"x\": 3699.3234994733834,\n          \"y\": 471.4241027832031\n        },\n        \"cloud-aws\": {\n          \"x\": 3436.528122523513,\n          \"y\": 545.9614868164062\n        },\n        \"cloud-azure\": {\n          \"x\": 2592.566210818907,\n          \"y\": 2724.068115234375\n        },\n        \"cloud-gcp\": {\n          \"x\": 2827.3183770424234,\n          \"y\": 2731.397216796875\n        },\n        \"isp-primary\": {\n          \"x\": 3712.192068081962,\n          \"y\": 615.64990234375\n        },\n        \"isp-secondary\": {\n          \"x\": 3253.9473366098055,\n          \"y\": 1993.2629089355469\n        },\n        \"dc-internal-1\": {\n          \"x\": 1958.4243458877936,\n          \"y\": 2845\n        },\n        \"dc-internal-2\": {\n          \"x\": 1963.768951182132,\n          \"y\": 2845\n        },\n        \"app-server-1\": {\n          \"x\": 1947.3819379304134,\n          \"y\": 2845\n        },\n        \"app-server-2\": {\n          \"x\": 1955.2862087394126,\n          \"y\": 2845\n        },\n        \"db-server-1\": {\n          \"x\": 1936.3708569559828,\n          \"y\": 2845\n        },\n        \"db-server-2\": {\n          \"x\": 1946.8300873488822,\n          \"y\": 2845\n        },\n        \"k8s-master-1\": {\n          \"x\": 1925.397658583093,\n          \"y\": 2845\n        },\n        \"k8s-master-2\": {\n          \"x\": 1938.405621494142,\n          \"y\": 2845\n        },\n        \"k8s-master-3\": {\n          \"x\": 1914.4688758763386,\n          \"y\": 2845\n        },\n        \"k8s-worker-1\": {\n          \"x\": 1930.017826812177,\n          \"y\": 2845\n        },\n        \"k8s-worker-2\": {\n          \"x\": 1903.5910154567553,\n          \"y\": 2845\n        },\n        \"k8s-worker-3\": {\n          \"x\": 1921.6716971072178,\n          \"y\": 2845\n        },\n        \"k8s-worker-4\": {\n          \"x\": 1892.7705536280016,\n          \"y\": 2845\n        },\n        \"proxy-server-1\": {\n          \"x\": 1806.1152433697903,\n          \"y\": 653.7529296875\n        },\n        \"proxy-server-2\": {\n          \"x\": 2937.4207928721535,\n          \"y\": 2628.7880859375\n        },\n        \"vpn-concentrator\": {\n          \"x\": 3642.252088474593,\n          \"y\": 946.7255249023438\n        },\n        \"nac-server\": {\n          \"x\": 1153.2626148502184,\n          \"y\": 1172.1895751953125\n        },\n        \"print-server\": {\n          \"x\": 1896.9328460745962,\n          \"y\": 2845\n        },\n        \"file-server\": {\n          \"x\": 1860.7177871362182,\n          \"y\": 2845\n        },\n        \"ca-server\": {\n          \"x\": 1888.8027739274805,\n          \"y\": 2845\n        },\n        \"sccm-server\": {\n          \"x\": 1850.1909418511675,\n          \"y\": 2845\n        },\n        \"voip-cluster\": {\n          \"x\": 1777.038465328039,\n          \"y\": 1616.8961181640625\n        },\n        \"video-conf\": {\n          \"x\": 1993.8373941679588,\n          \"y\": 2244.936309814453\n        },\n        \"security-cameras\": {\n          \"x\": 1674.413336949044,\n          \"y\": 2046.0380859375\n        },\n        \"nvr-cluster\": {\n          \"x\": 1829.4110389706402,\n          \"y\": 2845\n        },\n        \"dev-server-1\": {\n          \"x\": 2800.5894350649614,\n          \"y\": 1175.623291015625\n        },\n        \"dev-server-2\": {\n          \"x\": 1945.0822182484326,\n          \"y\": 1164.5184783935547\n        },\n        \"test-environment\": {\n          \"x\": 2932.0863047891075,\n          \"y\": 862.4592895507812\n        },\n        \"erp-system\": {\n          \"x\": 789.9880103985649,\n          \"y\": 473.7113342285156\n        },\n        \"crm-system\": {\n          \"x\": 3514.6003232048542,\n          \"y\": 1137.7720947265625\n        },\n        \"endpoint-1000\": {\n          \"x\": 991.6812012057328,\n          \"y\": 2284.42236328125\n        },\n        \"dist-switch-floor1\": {\n          \"x\": 654.2091033261356,\n          \"y\": 2020.0086669921875\n        },\n        \"dist-switch-floor2\": {\n          \"x\": 853.8845527112826,\n          \"y\": 1843.2872314453125\n        },\n        \"dist-switch-floor3\": {\n          \"x\": 1899.4353951584517,\n          \"y\": 1456.5068359375\n        },\n        \"dist-switch-floor4\": {\n          \"x\": 488.5289313756234,\n          \"y\": 181.47256469726562\n        },\n        \"ap-floor1-zone1\": {\n          \"x\": 1140.16846970184,\n          \"y\": 2070.2916259765625\n        },\n        \"ap-floor2-zone1\": {\n          \"x\": 688.1952143592268,\n          \"y\": 2384.4775390625\n        },\n        \"ap-floor3-zone1\": {\n          \"x\": 2145.3803027919676,\n          \"y\": 1890.2816162109375\n        },\n        \"ap-floor4-zone1\": {\n          \"x\": 517.646146409649,\n          \"y\": 565.59716796875\n        },\n        \"ups-dc-1\": {\n          \"x\": 771.1406786539856,\n          \"y\": 295.9266662597656\n        },\n        \"ups-dc-2\": {\n          \"x\": 216.2410855890687,\n          \"y\": 330.3345947265625\n        },\n        \"pdu-rack-a1\": {\n          \"x\": 1804.774444371901,\n          \"y\": 2845\n        },\n        \"pdu-rack-a2\": {\n          \"x\": 1741.6184034693686,\n          \"y\": 2845\n        },\n        \"cooling-1\": {\n          \"x\": 245.7080801919958,\n          \"y\": 626.1914672851562\n        },\n        \"cooling-2\": {\n          \"x\": 1603.293611085831,\n          \"y\": 981.0621185302734\n        },\n        \"camera-a\": {\n          \"x\": 166.57075412676295,\n          \"y\": 145\n        },\n        \"camera-a-copy\": {\n          \"x\": 1040.653076171875,\n          \"y\": 738.42822265625\n        }\n      },\n      \"sizes\": {\n        \"isp-secondary\": 139,\n        \"test-environment\": 121,\n        \"dev-server-1\": 128,\n        \"core-router-2\": 120,\n        \"camera-a\": 45,\n        \"camera-a-copy\": 45\n      },\n      \"styles\": {\n        \"dc-rack-b2\": {\n          \"all\": {\n            \"circleColor\": \"#ff0000\"\n          }\n        },\n        \"dc-rack-a1\": {\n          \"all\": {\n            \"circleColor\": \"#ff0000\"\n          }\n        },\n        \"dc-rack-b1\": {\n          \"all\": {\n            \"circleColor\": \"#ff0000\",\n            \"titleSize\": 59\n          }\n        },\n        \"isp-secondary\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"alist\"\n            },\n            \"circleColor\": \"#4d2c58\",\n            \"circleBorder\": \"#000000\",\n            \"titleColor\": \"#006eff\"\n          }\n        },\n        \"core-router-2\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"actual-budget\"\n            },\n            \"pingOffsetX\": -15,\n            \"pingOffsetY\": -38\n          }\n        },\n        \"fw-external-1\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"anonaddy\"\n            }\n          }\n        },\n        \"cloud-aws\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"ansible\"\n            }\n          }\n        },\n        \"isp-primary\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"wikidocs\"\n            }\n          }\n        },\n        \"branch-router-tokyo\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"adguard-home\"\n            }\n          }\n        },\n        \"core-router-1\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"borg\"\n            }\n          }\n        },\n        \"test-environment\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"simple\",\n              \"name\": \"apple\"\n            }\n          }\n        },\n        \"dev-server-1\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"simple\",\n              \"name\": \"amazonwebservices\"\n            }\n          }\n        }\n      },\n      \"legend\": {\n        \"#10b981\": \"Trusted Lan\",\n        \"#f59e0b\": \"Secure Lan\",\n        \"#ef4444\": \"DMZ\",\n        \"#475569\": \"Main ISP\",\n        \"#3b82f6\": \"Alternate ISP\",\n        \"#8b5cf6\": \"you can edit me too\",\n        \"#06b6d4\": \"you can edit me too\",\n        \"#a855f7\": \"you can edit me too\",\n        \"#f97316\": \"you can edit me too\",\n        \"#0ea5e9\": \"you can edit me too\",\n        \"#22c55e\": \"you can edit me too\",\n        \"#94a3b8\": \"you can edit me too\",\n        \"#fbbf24\": \"you can edit me too\",\n        \"#38bdf8\": \"you can edit me too\",\n        \"#c800ff\": \"you can edit me too\"\n      },\n      \"rects\": {\n        \"list\": [\n          {\n            \"id\": \"rect-1765237540610\",\n            \"x\": 2879.214599609375,\n            \"y\": 159.71981811523438,\n            \"width\": 992.196044921875,\n            \"height\": 538.8650817871094,\n            \"color\": \"#f97316\",\n            \"style\": \"filled\",\n            \"lineStyle\": \"solid\",\n            \"notes\": []\n          },\n          {\n            \"id\": \"rect-1765237681216\",\n            \"x\": 448.3926696777344,\n            \"y\": 1671.651123046875,\n            \"width\": 916.3436584472656,\n            \"height\": 924.27734375,\n            \"color\": \"#c800ff\",\n            \"style\": \"outlined\",\n            \"lineStyle\": \"solid\",\n            \"notes\": []\n          },\n          {\n            \"id\": \"rect-1766437913740\",\n            \"x\": 904.5889892578125,\n            \"y\": 115.40318298339844,\n            \"width\": 110.93878173828125,\n            \"height\": 919.6242218017578,\n            \"color\": \"#5215f9\",\n            \"style\": \"filled\",\n            \"lineStyle\": \"wall\",\n            \"notes\": [],\n            \"borderWidth\": 13\n          },\n          {\n            \"id\": \"rect-1766437935414\",\n            \"x\": 130.93685150146484,\n            \"y\": 1072.3624877929688,\n            \"width\": 872.9131851196289,\n            \"height\": 99.260986328125,\n            \"color\": \"#5215f9\",\n            \"style\": \"filled\",\n            \"lineStyle\": \"wall\",\n            \"notes\": [],\n            \"borderWidth\": 13\n          }\n        ]\n      },\n      \"texts\": {\n        \"list\": [\n          {\n            \"id\": \"text-1765237828167\",\n            \"x\": 3411.458740234375,\n            \"y\": 1390.00439453125,\n            \"content\": \"Double click on desktop\\nor long press on mobile\\nto enter rack canvas view\",\n            \"fontSize\": 46,\n            \"color\": \"#e2e8f0\",\n            \"fontWeight\": \"bold\",\n            \"fontStyle\": \"italic\",\n            \"textAlign\": \"middle\",\n            \"textDecoration\": \"none\",\n            \"bgColor\": \"#000000\",\n            \"bgEnabled\": false,\n            \"opacity\": 1\n          },\n          {\n            \"id\": \"text-1766446595277\",\n            \"x\": 654.3878479003906,\n            \"y\": 1367.7945556640625,\n            \"content\": \"SITE A\",\n            \"fontSize\": 101,\n            \"color\": \"#e2e8f0\",\n            \"fontWeight\": \"normal\",\n            \"fontStyle\": \"normal\",\n            \"textAlign\": \"start\",\n            \"textDecoration\": \"none\",\n            \"bgColor\": \"#000000\",\n            \"bgEnabled\": false,\n            \"opacity\": 1\n          },\n          {\n            \"id\": \"text-1766446610211\",\n            \"x\": 180.63662719726562,\n            \"y\": 1128.822998046875,\n            \"content\": \"SITE A\",\n            \"fontSize\": 101,\n            \"color\": \"#e2e8f0\",\n            \"fontWeight\": \"normal\",\n            \"fontStyle\": \"normal\",\n            \"textAlign\": \"start\",\n            \"textDecoration\": \"none\",\n            \"bgColor\": \"#000000\",\n            \"bgEnabled\": false,\n            \"opacity\": 1\n          },\n          {\n            \"id\": \"text-1766453024797\",\n            \"x\": 968.6458740234375,\n            \"y\": 1028.6621398925781,\n            \"content\": \"SITE A\",\n            \"fontSize\": 101,\n            \"color\": \"#e2e8f0\",\n            \"fontWeight\": \"normal\",\n            \"fontStyle\": \"normal\",\n            \"textAlign\": \"start\",\n            \"textDecoration\": \"none\",\n            \"bgColor\": \"#000000\",\n            \"bgEnabled\": false,\n            \"opacity\": 1,\n            \"rotation\": -89,\n            \"_dragStartX\": 972.46826171875,\n            \"_dragStartY\": 1009.5499572753906\n          },\n          {\n            \"id\": \"text-1766453070975\",\n            \"x\": 613.1589965820312,\n            \"y\": 1139.512939453125,\n            \"content\": \"SITE A\",\n            \"fontSize\": 101,\n            \"color\": \"#e2e8f0\",\n            \"fontWeight\": \"normal\",\n            \"fontStyle\": \"normal\",\n            \"textAlign\": \"start\",\n            \"textDecoration\": \"none\",\n            \"bgColor\": \"#000000\",\n            \"bgEnabled\": false,\n            \"opacity\": 1\n          },\n          {\n            \"id\": \"text-1766453072857\",\n            \"x\": 968.64599609375,\n            \"y\": 474.40818786621094,\n            \"content\": \"SITE A\",\n            \"fontSize\": 101,\n            \"color\": \"#e2e8f0\",\n            \"fontWeight\": \"normal\",\n            \"fontStyle\": \"normal\",\n            \"textAlign\": \"start\",\n            \"textDecoration\": \"none\",\n            \"bgColor\": \"#000000\",\n            \"bgEnabled\": false,\n            \"opacity\": 1,\n            \"rotation\": 269,\n            \"_dragStartX\": 1480.85302734375,\n            \"_dragStartY\": 822.2503356933594\n          },\n          {\n            \"id\": \"text-1766458222326\",\n            \"x\": 3167.812744140625,\n            \"y\": 2190.516357421875,\n            \"content\": \"\",\n            \"fontSize\": 18,\n            \"color\": \"#e2e8f0\",\n            \"fontWeight\": \"normal\",\n            \"fontStyle\": \"normal\",\n            \"textAlign\": \"start\",\n            \"textDecoration\": \"none\",\n            \"bgColor\": \"#000000\",\n            \"bgEnabled\": false,\n            \"opacity\": 1\n          }\n        ]\n      },\n      \"pageState\": {\n        \"title\": \"The One File Corporate\",\n        \"background\": \"\",\n        \"topbarBg\": \"rgba(9, 12, 20, 0.9)\",\n        \"topbarBorder\": \"#1f2533\",\n        \"panel\": \"#0b0e13\",\n        \"panelAlt\": \"#10141b\",\n        \"accent\": \"#4fd1c5\",\n        \"sidebarBg\": \"#10141b\",\n        \"btnBg\": \"#0b0e13\",\n        \"btnText\": \"#e2e8f0\",\n        \"tagFill\": \"#1e293b\",\n        \"tagText\": \"#e2e8f0\",\n        \"tagBorder\": \"#475569\",\n        \"inputBg\": \"#0b0e13\",\n        \"inputText\": \"#e2e8f0\",\n        \"inputBorder\": \"#1f2937\",\n        \"inputFont\": \"Inter, system-ui, sans-serif\",\n        \"inputFontSize\": 14,\n        \"toolbarBg\": \"#0f172a\",\n        \"toolbarBorder\": \"#1f2937\",\n        \"toolbarText\": \"#94a3b8\",\n        \"toolbarBtnBg\": \"#0b0e13\",\n        \"toolbarBtnText\": \"#e2e8f0\",\n        \"minimapDots\": \"#94a3b8\",\n        \"canvasHintEnabled\": true,\n        \"canvasHintText\": \"\",\n        \"canvasHintBg\": \"#0f172a\",\n        \"canvasHintColor\": \"#94a3b8\",\n        \"danger\": \"#f56565\",\n        \"textMain\": \"#e2e8f0\",\n        \"textSoft\": \"#94a3b8\",\n        \"topbarHeight\": 103,\n        \"sidebarWidth\": 350,\n        \"mobileFooterHeight\": 40,\n        \"sidebarCollapsed\": false,\n        \"nodeFill\": \"#1e293b\",\n        \"nodeStroke\": \"#475569\",\n        \"nodeTitle\": \"#e2e8f0\",\n        \"nodeSub\": \"#94a3b8\",\n        \"nodeTitleSize\": 41,\n        \"nodeSubSize\": 27,\n        \"nodeFont\": \"monospace\",\n        \"defaultEdge\": \"#475569\",\n        \"selectionHandle\": \"#f59e0b\",\n        \"selectionHandleSize\": 8,\n        \"groupIndicator\": \"#4fd1c5\",\n        \"canvasGradientTop\": \"#1e2532\",\n        \"canvasGradientBottom\": \"#050608\",\n        \"canvasBorder\": \"#475569\",\n        \"canvasGrid\": \"#475569\",\n        \"canvasGridSize\": 50,\n        \"canvasGridEnabled\": true,\n        \"rackFrameFill\": \"#0f172a\",\n        \"rackGridEnabled\": true,\n        \"rackFrameStroke\": \"#4fd1c5\",\n        \"rackLineColor\": \"#475569\",\n        \"rackTextColor\": \"#4fd1c5\",\n        \"viewOnly\": false,\n        \"defaultEdgeRouting\": \"orthogonal\",\n        \"animateConnections\": false,\n        \"animationStyle\": \"arrows\",\n        \"animationDirection\": \"all\",\n        \"animationSpeed\": 4,\n        \"autoPingEnabled\": false,\n        \"autoPingInterval\": 30\n      }\n    },\n    {\n      \"id\": \"tab-1765235136918\",\n      \"name\": \"Homelab 2\",\n      \"nodes\": {\n        \"internet\": {\n          \"shape\": \"stop-sign\",\n          \"name\": \"Internet\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"internet-copy\": {\n          \"shape\": \"firewall\",\n          \"name\": \"OPNSENSE\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"opnsense-copy\": {\n          \"shape\": \"firewall\",\n          \"name\": \"Docker\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"docker-copy\": {\n          \"shape\": \"firewall\",\n          \"name\": \"Docker2\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"docker-copy-1\": {\n          \"shape\": \"firewall\",\n          \"name\": \"Docker3\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"docker-copy-2\": {\n          \"shape\": \"firewall\",\n          \"name\": \"Docker 4\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [\n            {\n              \"type\": \"icon\",\n              \"library\": \"selfhst\",\n              \"name\": \"docker\"\n            },\n            {\n              \"type\": \"icon\",\n              \"library\": \"selfhst\",\n              \"name\": \"authentik\"\n            },\n            {\n              \"type\": \"icon\",\n              \"library\": \"selfhst\",\n              \"name\": \"immich\"\n            }\n          ],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"opnsense-copy-1\": {\n          \"shape\": \"firewall\",\n          \"name\": \"OPNSENSE GUEST\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"phone\": {\n          \"shape\": \"phone\",\n          \"name\": \"Phone\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"desktop\": {\n          \"shape\": \"pc\",\n          \"name\": \"Desktop\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dns\": {\n          \"shape\": \"cloud\",\n          \"name\": \"DNS\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"racked\": {\n          \"shape\": \"server\",\n          \"name\": \"Racked\",\n          \"ip\": \"\",\n          \"role\": \"Rack\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": true,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"thermostat\": {\n          \"shape\": \"thermostat\",\n          \"name\": \"Thermostat\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"video-doorbell\": {\n          \"shape\": \"doorbell\",\n          \"name\": \"Video Doorbell\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"smart-lock\": {\n          \"shape\": \"smart-lock\",\n          \"name\": \"Smart Lock\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"smart-bulb\": {\n          \"shape\": \"smart-bulb\",\n          \"name\": \"Smart Bulb\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"robot-vacuum\": {\n          \"shape\": \"vacuum\",\n          \"name\": \"Robot Vacuum\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        }\n      },\n      \"edges\": {\n        \"list\": [\n          {\n            \"id\": \"internet-internet-copy-1765238145151\",\n            \"from\": \"internet\",\n            \"to\": \"internet-copy\",\n            \"width\": 4,\n            \"color\": \"#55e208\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1\n          },\n          {\n            \"id\": \"internet-copy-opnsense-copy-1765238187451\",\n            \"from\": \"internet-copy\",\n            \"to\": \"opnsense-copy\",\n            \"width\": 4,\n            \"color\": \"#4c00ff\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1\n          },\n          {\n            \"id\": \"internet-copy-docker-copy-1765238242477\",\n            \"from\": \"internet-copy\",\n            \"to\": \"docker-copy\",\n            \"width\": 4,\n            \"color\": \"#4c00ff\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1\n          },\n          {\n            \"id\": \"internet-copy-docker-copy-1-1765238244637\",\n            \"from\": \"internet-copy\",\n            \"to\": \"docker-copy-1\",\n            \"width\": 4,\n            \"color\": \"#4c00ff\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1\n          },\n          {\n            \"id\": \"internet-copy-docker-copy-2-1765238246233\",\n            \"from\": \"internet-copy\",\n            \"to\": \"docker-copy-2\",\n            \"width\": 4,\n            \"color\": \"#4c00ff\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1\n          },\n          {\n            \"id\": \"internet-opnsense-copy-1-1765238266117\",\n            \"from\": \"internet\",\n            \"to\": \"opnsense-copy-1\",\n            \"width\": 4,\n            \"color\": \"#80ff00\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1\n          },\n          {\n            \"id\": \"opnsense-copy-1-dns-1765238347996\",\n            \"from\": \"opnsense-copy-1\",\n            \"to\": \"dns\",\n            \"width\": 4,\n            \"color\": \"#fb00ff\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1\n          },\n          {\n            \"id\": \"dns-desktop-1765238386101\",\n            \"from\": \"dns\",\n            \"to\": \"desktop\",\n            \"width\": 4,\n            \"color\": \"#ff00d0\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1\n          },\n          {\n            \"id\": \"phone-dns-1765238391156\",\n            \"from\": \"phone\",\n            \"to\": \"dns\",\n            \"width\": 4,\n            \"color\": \"#ff00d0\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1\n          },\n          {\n            \"id\": \"custom-1765239449323\",\n            \"type\": \"custom\",\n            \"color\": \"#f97316\",\n            \"width\": 4,\n            \"lineStyle\": \"solid\",\n            \"direction\": \"forward\",\n            \"points\": [\n              {\n                \"x\": 2936.464111328125,\n                \"y\": 786.07958984375\n              },\n              {\n                \"x\": 3184.112060546875,\n                \"y\": 887.6153564453125\n              },\n              {\n                \"x\": 2763.110107421875,\n                \"y\": 981.7216796875\n              }\n            ],\n            \"notes\": []\n          }\n        ]\n      },\n      \"positions\": {\n        \"internet\": {\n          \"x\": 1757.7735887323333,\n          \"y\": 298.77284240722656\n        },\n        \"internet-copy\": {\n          \"x\": 2066.9677515897347,\n          \"y\": 473.4119134177565\n        },\n        \"opnsense-copy\": {\n          \"x\": 1773.8400660428597,\n          \"y\": 666.5758233298659\n        },\n        \"docker-copy\": {\n          \"x\": 1931.1978950081452,\n          \"y\": 782.2775961320921\n        },\n        \"docker-copy-1\": {\n          \"x\": 2158.1262397347077,\n          \"y\": 767.7122274797483\n        },\n        \"docker-copy-2\": {\n          \"x\": 2342.2663764534577,\n          \"y\": 631.7681967180296\n        },\n        \"opnsense-copy-1\": {\n          \"x\": 2757.879480087803,\n          \"y\": 307.6117116091891\n        },\n        \"phone\": {\n          \"x\": 3312.857751572178,\n          \"y\": 502.58220111114224\n        },\n        \"desktop\": {\n          \"x\": 2971.700036728428,\n          \"y\": 480.7287465212985\n        },\n        \"dns\": {\n          \"x\": 3200.4643189549906,\n          \"y\": 320.469591247861\n        },\n        \"racked\": {\n          \"x\": 2645.5845448279656,\n          \"y\": 970.7820678889219\n        },\n        \"thermostat\": {\n          \"x\": 1323.0595481711202,\n          \"y\": 574.6132617105841\n        },\n        \"video-doorbell\": {\n          \"x\": 1188.4284446554952,\n          \"y\": 455.3684191812872\n        },\n        \"smart-lock\": {\n          \"x\": 1292.286782057839,\n          \"y\": 790.0231738199591\n        },\n        \"smart-bulb\": {\n          \"x\": 1496.156899245339,\n          \"y\": 716.9377246012091\n        },\n        \"robot-vacuum\": {\n          \"x\": 2288.5581443625265,\n          \"y\": 978.5069995035528\n        }\n      },\n      \"sizes\": {\n        \"core-router-1\": 36,\n        \"internet\": 168,\n        \"phone\": 121,\n        \"desktop\": 147,\n        \"racked\": 137,\n        \"docker-copy-2\": 82\n      },\n      \"styles\": {\n        \"internet\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"amazon-web-services\"\n            },\n            \"circleColor\": \"#db0000\",\n            \"circleBorder\": \"#000000\",\n            \"titleSize\": 52,\n            \"subSize\": 46\n          }\n        },\n        \"opnsense-copy-1\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"opnsense-v1\"\n            }\n          }\n        },\n        \"internet-copy\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"opnsense\"\n            }\n          }\n        },\n        \"docker-copy-2\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"docker\"\n            }\n          }\n        },\n        \"docker-copy-1\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"authportal\"\n            }\n          }\n        },\n        \"docker-copy\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"jotty\"\n            }\n          }\n        },\n        \"opnsense-copy\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"portainer\"\n            }\n          }\n        },\n        \"racked\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"mdi\",\n              \"name\": \"server-security\"\n            },\n            \"circleColor\": \"#010813\",\n            \"circleBorder\": \"#ffffff\"\n          }\n        }\n      },\n      \"legend\": {\n        \"#475569\": \"you can edit me too\",\n        \"#65758b\": \"you can edit me too\",\n        \"#63748c\": \"you can edit me too\",\n        \"#5e6f87\": \"you can edit me too\",\n        \"#586a84\": \"you can edit me too\",\n        \"#4f627d\": \"you can edit me too\",\n        \"#455873\": \"you can edit me too\",\n        \"#3d506c\": \"you can edit me too\",\n        \"#354964\": \"you can edit me too\",\n        \"#2e415c\": \"you can edit me too\",\n        \"#293c56\": \"you can edit me too\",\n        \"#273a53\": \"you can edit me too\",\n        \"#253750\": \"you can edit me too\",\n        \"#23354d\": \"you can edit me too\",\n        \"#203046\": \"you can edit me too\",\n        \"#1e2d43\": \"you can edit me too\",\n        \"#1a283d\": \"you can edit me too\",\n        \"#172435\": \"you can edit me too\",\n        \"#141f2e\": \"you can edit me too\",\n        \"#111a27\": \"you can edit me too\",\n        \"#0f1824\": \"you can edit me too\",\n        \"#0d1521\": \"you can edit me too\",\n        \"#0c131d\": \"you can edit me too\",\n        \"#0c1d1c\": \"you can edit me too\",\n        \"#0c1c1d\": \"you can edit me too\",\n        \"#0c191d\": \"you can edit me too\",\n        \"#0c141d\": \"you can edit me too\",\n        \"#0c0d1d\": \"you can edit me too\",\n        \"#130c1d\": \"you can edit me too\",\n        \"#1b0c1d\": \"you can edit me too\",\n        \"#1d0c17\": \"you can edit me too\",\n        \"#1d0c10\": \"you can edit me too\",\n        \"#1d0c0c\": \"you can edit me too\",\n        \"#3b1b1b\": \"you can edit me too\",\n        \"#3c1a1a\": \"you can edit me too\",\n        \"#3f1c1c\": \"you can edit me too\",\n        \"#401c1c\": \"you can edit me too\",\n        \"#451c1c\": \"you can edit me too\",\n        \"#461b1b\": \"you can edit me too\",\n        \"#4c1a1a\": \"you can edit me too\",\n        \"#521919\": \"you can edit me too\",\n        \"#571919\": \"you can edit me too\",\n        \"#5d1818\": \"you can edit me too\",\n        \"#631717\": \"you can edit me too\",\n        \"#651515\": \"you can edit me too\",\n        \"#6a1616\": \"you can edit me too\",\n        \"#6f1515\": \"you can edit me too\",\n        \"#711414\": \"you can edit me too\",\n        \"#761414\": \"you can edit me too\",\n        \"#771313\": \"you can edit me too\",\n        \"#7c1313\": \"you can edit me too\",\n        \"#811313\": \"you can edit me too\",\n        \"#821212\": \"you can edit me too\",\n        \"#871212\": \"you can edit me too\",\n        \"#881111\": \"you can edit me too\",\n        \"#8d1111\": \"you can edit me too\",\n        \"#8e1010\": \"you can edit me too\",\n        \"#8f0f0f\": \"you can edit me too\",\n        \"#900e0e\": \"you can edit me too\",\n        \"#8e0b0b\": \"you can edit me too\",\n        \"#8c0d0d\": \"you can edit me too\",\n        \"#880c0c\": \"you can edit me too\",\n        \"#830c0c\": \"you can edit me too\",\n        \"#7e0c0c\": \"you can edit me too\",\n        \"#790c0c\": \"you can edit me too\",\n        \"#730c0c\": \"you can edit me too\",\n        \"#6f0b0b\": \"you can edit me too\",\n        \"#0b6f64\": \"you can edit me too\",\n        \"#0b6f5f\": \"you can edit me too\",\n        \"#0b6f56\": \"you can edit me too\",\n        \"#0b6f49\": \"you can edit me too\",\n        \"#0b6f31\": \"you can edit me too\",\n        \"#0b6f1f\": \"you can edit me too\",\n        \"#0b6f0d\": \"you can edit me too\",\n        \"#176f0b\": \"you can edit me too\",\n        \"#266f0b\": \"you can edit me too\",\n        \"#296f0b\": \"you can edit me too\",\n        \"#2e6f0b\": \"you can edit me too\",\n        \"#1a2d10\": \"you can edit me too\",\n        \"#1c3111\": \"you can edit me too\",\n        \"#213814\": \"you can edit me too\",\n        \"#233c15\": \"you can edit me too\",\n        \"#254017\": \"you can edit me too\",\n        \"#294918\": \"you can edit me too\",\n        \"#2b4d1a\": \"you can edit me too\",\n        \"#2d511a\": \"you can edit me too\",\n        \"#315a1b\": \"you can edit me too\",\n        \"#35631c\": \"you can edit me too\",\n        \"#37681d\": \"you can edit me too\",\n        \"#3b721d\": \"you can edit me too\",\n        \"#3f7b1e\": \"you can edit me too\",\n        \"#42851e\": \"you can edit me too\",\n        \"#46901d\": \"you can edit me too\",\n        \"#499a1d\": \"you can edit me too\",\n        \"#4b9f1d\": \"you can edit me too\",\n        \"#4ca61c\": \"you can edit me too\",\n        \"#50b01c\": \"you can edit me too\",\n        \"#51b71a\": \"you can edit me too\",\n        \"#50b918\": \"you can edit me too\",\n        \"#51c115\": \"you can edit me too\",\n        \"#53c615\": \"you can edit me too\",\n        \"#53c814\": \"you can edit me too\",\n        \"#52c913\": \"you can edit me too\",\n        \"#54d011\": \"you can edit me too\",\n        \"#53d110\": \"you can edit me too\",\n        \"#55d510\": \"you can edit me too\",\n        \"#55d70f\": \"you can edit me too\",\n        \"#54d80e\": \"you can edit me too\",\n        \"#54da0b\": \"you can edit me too\",\n        \"#56df0c\": \"you can edit me too\",\n        \"#53db0a\": \"you can edit me too\",\n        \"#55e00b\": \"you can edit me too\",\n        \"#55e109\": \"you can edit me too\",\n        \"#55e208\": \"ISP LINE\",\n        \"#4c00ff\": \"MY Guest NETWORK\",\n        \"#80ff00\": \"you can edit me too\",\n        \"#3b4234\": \"you can edit me too\",\n        \"#3a3442\": \"you can edit me too\",\n        \"#3b3442\": \"you can edit me too\",\n        \"#3c3442\": \"you can edit me too\",\n        \"#3d3442\": \"you can edit me too\",\n        \"#3e3442\": \"you can edit me too\",\n        \"#3f3442\": \"you can edit me too\",\n        \"#403442\": \"you can edit me too\",\n        \"#413442\": \"you can edit me too\",\n        \"#653d66\": \"you can edit me too\",\n        \"#683f69\": \"you can edit me too\",\n        \"#6c416c\": \"you can edit me too\",\n        \"#6f4370\": \"you can edit me too\",\n        \"#704270\": \"you can edit me too\",\n        \"#734474\": \"you can edit me too\",\n        \"#784479\": \"you can edit me too\",\n        \"#7d447e\": \"you can edit me too\",\n        \"#7e437f\": \"you can edit me too\",\n        \"#834384\": \"you can edit me too\",\n        \"#844285\": \"you can edit me too\",\n        \"#89418b\": \"you can edit me too\",\n        \"#8e428f\": \"you can edit me too\",\n        \"#904091\": \"you can edit me too\",\n        \"#923e93\": \"you can edit me too\",\n        \"#973e98\": \"you can edit me too\",\n        \"#943c96\": \"you can edit me too\",\n        \"#993c9a\": \"you can edit me too\",\n        \"#963a98\": \"you can edit me too\",\n        \"#973899\": \"you can edit me too\",\n        \"#99369b\": \"you can edit me too\",\n        \"#9a359c\": \"you can edit me too\",\n        \"#9b349d\": \"you can edit me too\",\n        \"#9d329f\": \"you can edit me too\",\n        \"#9e31a0\": \"you can edit me too\",\n        \"#a02fa2\": \"you can edit me too\",\n        \"#9d2d9f\": \"you can edit me too\",\n        \"#9f2ba1\": \"you can edit me too\",\n        \"#a129a3\": \"you can edit me too\",\n        \"#a327a5\": \"you can edit me too\",\n        \"#a525a7\": \"you can edit me too\",\n        \"#a723a9\": \"you can edit me too\",\n        \"#a921ab\": \"you can edit me too\",\n        \"#ab1fad\": \"you can edit me too\",\n        \"#ad1daf\": \"you can edit me too\",\n        \"#ae1cb0\": \"you can edit me too\",\n        \"#b019b3\": \"you can edit me too\",\n        \"#b118b4\": \"you can edit me too\",\n        \"#b316b6\": \"you can edit me too\",\n        \"#b816bb\": \"you can edit me too\",\n        \"#b514b8\": \"you can edit me too\",\n        \"#ba14bd\": \"you can edit me too\",\n        \"#b712ba\": \"you can edit me too\",\n        \"#bb13be\": \"you can edit me too\",\n        \"#b811bb\": \"you can edit me too\",\n        \"#be10c1\": \"you can edit me too\",\n        \"#bb0ebe\": \"you can edit me too\",\n        \"#bd0cc0\": \"you can edit me too\",\n        \"#be0bc1\": \"you can edit me too\",\n        \"#c108c4\": \"you can edit me too\",\n        \"#be06c1\": \"you can edit me too\",\n        \"#c103c4\": \"you can edit me too\",\n        \"#c301c6\": \"you can edit me too\",\n        \"#c400c7\": \"you can edit me too\",\n        \"#c900cc\": \"you can edit me too\",\n        \"#ce00d1\": \"you can edit me too\",\n        \"#d300d6\": \"you can edit me too\",\n        \"#d800db\": \"you can edit me too\",\n        \"#dd00e0\": \"you can edit me too\",\n        \"#e200e6\": \"you can edit me too\",\n        \"#ec00f0\": \"you can edit me too\",\n        \"#f100f5\": \"you can edit me too\",\n        \"#f600fa\": \"you can edit me too\",\n        \"#fb00ff\": \"you can edit me too\",\n        \"#ff00d0\": \"iPhone (always guest iPhone)\",\n        \"#f97316\": \"you can edit me too\"\n      },\n      \"rects\": {\n        \"list\": [\n          {\n            \"id\": \"rect-1765238219615\",\n            \"x\": 2680.053955078125,\n            \"y\": 251.44879150390625,\n            \"width\": 814.10400390625,\n            \"height\": 389.26678466796875,\n            \"color\": \"#ec0999\",\n            \"style\": \"filled\",\n            \"lineStyle\": \"solid\",\n            \"notes\": []\n          }\n        ]\n      },\n      \"texts\": {\n        \"list\": [\n          {\n            \"id\": \"text-1765238422602\",\n            \"x\": 2466.35986328125,\n            \"y\": 741.6801147460938,\n            \"content\": \"Double click on desktop\\nor long press on mobile\\nto enter rack canvas view\",\n            \"fontSize\": 40,\n            \"color\": \"#e2e8f0\",\n            \"fontWeight\": \"normal\",\n            \"fontStyle\": \"normal\",\n            \"textAlign\": \"start\",\n            \"textDecoration\": \"none\",\n            \"bgColor\": \"#000000\",\n            \"bgEnabled\": false,\n            \"opacity\": 1\n          }\n        ]\n      },\n      \"pageState\": {\n        \"title\": \"The One File\",\n        \"background\": \"\",\n        \"topbarBg\": \"rgba(9, 12, 20, 0.9)\",\n        \"topbarBorder\": \"#1f2533\",\n        \"panel\": \"#2f0e0e\",\n        \"panelAlt\": \"#10141b\",\n        \"accent\": \"#a75252\",\n        \"sidebarBg\": \"#10141b\",\n        \"btnBg\": \"#0b0e13\",\n        \"btnText\": \"#e2e8f0\",\n        \"tagFill\": \"#1e293b\",\n        \"tagText\": \"#e2e8f0\",\n        \"tagBorder\": \"#475569\",\n        \"inputBg\": \"#0b0e13\",\n        \"inputText\": \"#e2e8f0\",\n        \"inputBorder\": \"#1f2937\",\n        \"inputFont\": \"Inter, system-ui, sans-serif\",\n        \"inputFontSize\": 14,\n        \"toolbarBg\": \"#441215\",\n        \"toolbarBorder\": \"#1f2937\",\n        \"toolbarText\": \"#94a3b8\",\n        \"toolbarBtnBg\": \"#0b0e13\",\n        \"toolbarBtnText\": \"#e2e8f0\",\n        \"minimapDots\": \"#94a3b8\",\n        \"canvasHintEnabled\": true,\n        \"canvasHintText\": \"\",\n        \"canvasHintBg\": \"#0f172a\",\n        \"canvasHintColor\": \"#94a3b8\",\n        \"danger\": \"#f56565\",\n        \"textMain\": \"#e2e8f0\",\n        \"textSoft\": \"#94a3b8\",\n        \"topbarHeight\": 112,\n        \"sidebarWidth\": 350,\n        \"mobileFooterHeight\": 40,\n        \"sidebarCollapsed\": false,\n        \"nodeFill\": \"#1e293b\",\n        \"nodeStroke\": \"#475569\",\n        \"nodeTitle\": \"#e2e8f0\",\n        \"nodeSub\": \"#94a3b8\",\n        \"nodeTitleSize\": 18,\n        \"nodeSubSize\": 13,\n        \"nodeFont\": \"Inter, system-ui, sans-serif\",\n        \"defaultEdge\": \"#475569\",\n        \"selectionHandle\": \"#f59e0b\",\n        \"selectionHandleSize\": 8,\n        \"groupIndicator\": \"#4fd1c5\",\n        \"canvasGradientTop\": \"#1e2532\",\n        \"canvasGradientBottom\": \"#050608\",\n        \"canvasBorder\": \"#475569\",\n        \"canvasGrid\": \"#475569\",\n        \"canvasGridSize\": 50,\n        \"canvasGridEnabled\": true,\n        \"rackFrameFill\": \"#0f172a\",\n        \"rackGridEnabled\": true,\n        \"rackFrameStroke\": \"#4fd1c5\",\n        \"rackLineColor\": \"#475569\",\n        \"rackTextColor\": \"#4fd1c5\",\n        \"viewOnly\": false,\n        \"defaultEdgeRouting\": \"curved\",\n        \"animateConnections\": false,\n        \"animationStyle\": \"arrows\",\n        \"animationDirection\": \"all\",\n        \"animationSpeed\": 1.5\n      }\n    }\n  ],\n  \"currentTabIndex\": 1,\n  \"encryptedSections\": {},\n  \"auditLog\": [\n    {\n      \"timestamp\": 1766459379962,\n      \"type\": \"export\",\n      \"description\": \"Exported JSON: the-one-file.json\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766459374396,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file.html\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766459370112,\n      \"type\": \"tab\",\n      \"description\": \"Switched to tab: Homelab 2\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766459361896,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file-corporate.html\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459352785,\n      \"type\": \"node\",\n      \"description\": \"rotate node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459352343,\n      \"type\": \"node\",\n      \"description\": \"rotate node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459352224,\n      \"type\": \"node\",\n      \"description\": \"rotate node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459351722,\n      \"type\": \"node\",\n      \"description\": \"rotate node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459351541,\n      \"type\": \"node\",\n      \"description\": \"rotate node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459350380,\n      \"type\": \"node\",\n      \"description\": \"resize node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459350178,\n      \"type\": \"node\",\n      \"description\": \"resize node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459350049,\n      \"type\": \"node\",\n      \"description\": \"resize node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459346233,\n      \"type\": \"node\",\n      \"description\": \"change shape\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459335960,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459335846,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459335742,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459335630,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459335398,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459335292,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459335188,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459332894,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459332780,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459332661,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459332556,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459332450,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459332346,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459331643,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459331492,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459331378,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459331274,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459330996,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459330868,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459330764,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459330637,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459327262,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459327136,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459326544,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459326438,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459326334,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459326176,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459325232,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459325088,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459324279,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459323835,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459323732,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459323200,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459323093,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459322989,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459322883,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459322780,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459321176,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459321070,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459320748,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459320642,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459320492,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459319706,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459319600,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459319055,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459318467,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459318363,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459318258,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459317846,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459317742,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459317464,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459317314,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459313457,\n      \"type\": \"node\",\n      \"description\": \"change shape\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459310142,\n      \"type\": \"node\",\n      \"description\": \"change shape\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459306160,\n      \"type\": \"node\",\n      \"description\": \"rotate node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459305289,\n      \"type\": \"node\",\n      \"description\": \"rotate node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459305132,\n      \"type\": \"node\",\n      \"description\": \"rotate node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459304675,\n      \"type\": \"node\",\n      \"description\": \"rotate node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459304530,\n      \"type\": \"node\",\n      \"description\": \"rotate node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459304396,\n      \"type\": \"node\",\n      \"description\": \"rotate node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459304290,\n      \"type\": \"node\",\n      \"description\": \"rotate node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459304157,\n      \"type\": \"node\",\n      \"description\": \"rotate node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459303660,\n      \"type\": \"node\",\n      \"description\": \"rotate node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459303534,\n      \"type\": \"node\",\n      \"description\": \"rotate node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459303414,\n      \"type\": \"node\",\n      \"description\": \"rotate node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459303247,\n      \"type\": \"node\",\n      \"description\": \"rotate node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459303144,\n      \"type\": \"node\",\n      \"description\": \"rotate node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459303002,\n      \"type\": \"node\",\n      \"description\": \"rotate node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459302875,\n      \"type\": \"node\",\n      \"description\": \"rotate node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459302725,\n      \"type\": \"node\",\n      \"description\": \"rotate node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459302613,\n      \"type\": \"node\",\n      \"description\": \"rotate node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459302507,\n      \"type\": \"node\",\n      \"description\": \"rotate node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459301997,\n      \"type\": \"node\",\n      \"description\": \"rotate node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459301893,\n      \"type\": \"node\",\n      \"description\": \"rotate node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458459721,\n      \"type\": \"tab\",\n      \"description\": \"Switched to tab: Corporate Site B\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458438687,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458438583,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458438437,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458438333,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458438187,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458438083,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458437937,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458437833,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458437687,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458437583,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458437437,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458437333,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458437187,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458436932,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458435139,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458434986,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458434840,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458434736,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458434590,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458434486,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458434340,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458434236,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458434090,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458433986,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458433840,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458433736,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458433590,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458433334,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458429157,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458429053,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458428947,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458426794,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458426691,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458426584,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458426481,\n      \"type\": \"style\",\n      \"description\": \"style change\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458423513,\n      \"type\": \"node\",\n      \"description\": \"change shape\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458421278,\n      \"type\": \"node\",\n      \"description\": \"change shape\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458416555,\n      \"type\": \"node\",\n      \"description\": \"change shape\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458404891,\n      \"type\": \"node\",\n      \"description\": \"add node\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458392272,\n      \"type\": \"node\",\n      \"description\": \"add node\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458378068,\n      \"type\": \"node\",\n      \"description\": \"add node\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458367460,\n      \"type\": \"node\",\n      \"description\": \"add node\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458356226,\n      \"type\": \"node\",\n      \"description\": \"add node\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458338198,\n      \"type\": \"tab\",\n      \"description\": \"Switched to tab: Homelab 2\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766458258865,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file-corporate.html\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458249051,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458248926,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458248793,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458248683,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458248556,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458248451,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458248325,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458248221,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458248092,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458247989,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458247885,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458247784,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458247284,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458246701,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458246523,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458246410,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458246129,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458245955,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458245737,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458245627,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458245425,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458245247,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458245133,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458244923,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458244741,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458244313,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458244198,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458244055,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458243873,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458243637,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458243399,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458243218,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458241018,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458237254,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458235033,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458234835,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458234694,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458234425,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458227773,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458227623,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458227441,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458227279,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458227155,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458226967,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458226847,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458226733,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458226563,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458226421,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458222326,\n      \"type\": \"text\",\n      \"description\": \"add text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458213989,\n      \"type\": \"connection\",\n      \"description\": \"delete edge\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458209437,\n      \"type\": \"text\",\n      \"description\": \"delete text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766458195427,\n      \"type\": \"import\",\n      \"description\": \"Imported JSON: the-one-file-corporate.json (107 nodes, 65 connections)\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455847368,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455844534,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455844054,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455843762,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455843560,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455843371,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455843162,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455842852,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455842747,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455842601,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455842449,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455842348,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455842098,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455841678,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455841236,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455841053,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455840901,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455840650,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455839427,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455839234,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455839061,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455837247,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455837081,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455836893,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455836377,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455836198,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455835455,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455834630,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455831880,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455831676,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455831451,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455830817,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455830687,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455830176,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455830048,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455829944,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455829816,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455378795,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455378693,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455378459,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455378316,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455378180,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455378069,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455377956,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455377677,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455377558,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455377448,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455377318,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455377209,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453090534,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453090317,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453090213,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453090112,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453090009,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453089903,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453088895,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453088793,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453088689,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453088584,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453088480,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453088250,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453087236,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453086725,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453086485,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453086373,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453086142,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453086043,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453072857,\n      \"type\": \"text\",\n      \"description\": \"paste text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453070975,\n      \"type\": \"text\",\n      \"description\": \"paste text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453054439,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453053127,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453052450,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453052106,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453051948,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453051806,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453051334,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453050207,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453042725,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453042179,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453041797,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453041570,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453039703,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453039291,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453039168,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453039065,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453038481,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453038365,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453038237,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453038105,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453038001,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453037850,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453037745,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453037495,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453037378,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453037182,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453037078,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453036972,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453036860,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453036147,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453035945,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453035825,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453035720,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453035443,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453035337,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453035233,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453035127,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453035026,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453034917,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453031063,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453030955,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453030833,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453030732,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453030225,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453030104,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453029968,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453029796,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453029474,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453024797,\n      \"type\": \"text\",\n      \"description\": \"paste text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766451118553,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450929324,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450817210,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450257424,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450255024,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450254395,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450253241,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450251598,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450250392,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450248756,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450244072,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450242166,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450240998,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450236492,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450233672,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450232384,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450231012,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450230254,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450229302,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450228132,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446610211,\n      \"type\": \"text\",\n      \"description\": \"paste text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446604849,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446604702,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446604550,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446604404,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446604305,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446604204,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446604099,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446603952,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446603849,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446603702,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446603599,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446603452,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446603348,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446603202,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446603099,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446602953,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446602850,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446602702,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446602600,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446602453,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446602349,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446602204,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446602101,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446602000,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446601848,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446601702,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446601601,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446601452,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446601301,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446601154,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446601049,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446600948,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446600802,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446600550,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446598595,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446598461,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446598171,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446598017,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446597219,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446595278,\n      \"type\": \"text\",\n      \"description\": \"add text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445633355,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445632515,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445631735,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445630757,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445627846,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445625085,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445618645,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445617784,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445608998,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445608720,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445608540,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445608376,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445608204,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445608038,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445607852,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445607678,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445607506,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445607319,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445607154,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445604410,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445604244,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445604066,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445603900,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445603743,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445603563,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445603406,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445603226,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445603052,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445602880,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445602641,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445576567,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animation speed\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445570290,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animation speed\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445567192,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445566766,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445565520,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445398115,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445390895,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445385694,\n      \"type\": \"edit\",\n      \"description\": \"toggle fov animation\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445383241,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445382911,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445381695,\n      \"type\": \"edit\",\n      \"description\": \"edit node name\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445375383,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445374665,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445373273,\n      \"type\": \"node\",\n      \"description\": \"paste node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445372205,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438157980,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438157430,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animation speed\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438152691,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438151948,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438151286,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438146174,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438145649,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438144555,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438143655,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438142504,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animation speed\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438130077,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438129561,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438128772,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438128398,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438122820,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438122062,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438119836,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438119588,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438095045,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438093965,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438062827,\n      \"type\": \"edit\",\n      \"description\": \"toggle fov animation\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438047679,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438044161,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438041852,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438039668,\n      \"type\": \"edit\",\n      \"description\": \"resize node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438039562,\n      \"type\": \"edit\",\n      \"description\": \"resize node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438039421,\n      \"type\": \"edit\",\n      \"description\": \"resize node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438039260,\n      \"type\": \"edit\",\n      \"description\": \"resize node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438039150,\n      \"type\": \"edit\",\n      \"description\": \"resize node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438039039,\n      \"type\": \"edit\",\n      \"description\": \"resize node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438028508,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438021410,\n      \"type\": \"edit\",\n      \"description\": \"toggle fov\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438019234,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438017562,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438014356,\n      \"type\": \"node\",\n      \"description\": \"add node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437981696,\n      \"type\": \"edit\",\n      \"description\": \"apply routing to all\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437966551,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437964879,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437963627,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437961813,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437961193,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437957989,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437956467,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437953437,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437952239,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437950807,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437944990,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437943699,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437935414,\n      \"type\": \"zone\",\n      \"description\": \"draw zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437919019,\n      \"type\": \"zone\",\n      \"description\": \"delete zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437917758,\n      \"type\": \"zone\",\n      \"description\": \"draw zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437913740,\n      \"type\": \"zone\",\n      \"description\": \"draw zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437882832,\n      \"type\": \"import\",\n      \"description\": \"Imported JSON: theonefile-networkening-corporate-demo.json (105 nodes, 65 connections)\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766263279163,\n      \"type\": \"tab\",\n      \"description\": \"Switched to tab: Corporate Site B\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766263270414,\n      \"type\": \"export\",\n      \"description\": \"Exported JSON: the-one-file.json\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766263260682,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file.html\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766263259518,\n      \"type\": \"tab\",\n      \"description\": \"Switched to tab: Homelab 2\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766263249401,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file-corporate.html\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766263246362,\n      \"type\": \"import\",\n      \"description\": \"Imported JSON: theonefile-networkening-corporate-demo.json (105 nodes, 65 connections)\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766190721141,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file-corporate.html\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766190717499,\n      \"type\": \"tab\",\n      \"description\": \"Switched to tab: Corporate Site B\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766190710946,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file.html\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766190705273,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file.html\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766190703463,\n      \"type\": \"tab\",\n      \"description\": \"Switched to tab: Homelab 2\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766190695709,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file-corporate.html\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766190688417,\n      \"type\": \"import\",\n      \"description\": \"Imported JSON: theonefile-networkening-corporate-demo.json (105 nodes, 65 connections)\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1765402888416,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file-corporate.html\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1765402884873,\n      \"type\": \"tab\",\n      \"description\": \"Switched to tab: Corporate Site B\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1765402878108,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file.html\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1765402866440,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file.html\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1765402865008,\n      \"type\": \"tab\",\n      \"description\": \"Switched to tab: Homelab 2\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1765402860428,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file-corporate.html\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1765402858103,\n      \"type\": \"import\",\n      \"description\": \"Imported JSON: theonefile-networkening-corporate-demo.json (105 nodes, 65 connections)\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    }\n  ],\n  \"savedStyleSets\": []\n}\nTHEONEFILE_CONFIG-->\n\n# The One File\n\n> Exported from The One File on 2025-12-23T03:09:46.367Z\n\n## Legend\n\n- #475569: you can edit me too\n- #65758b: you can edit me too\n- #63748c: you can edit me too\n- #5e6f87: you can edit me too\n- #586a84: you can edit me too\n- #4f627d: you can edit me too\n- #455873: you can edit me too\n- #3d506c: you can edit me too\n- #354964: you can edit me too\n- #2e415c: you can edit me too\n- #293c56: you can edit me too\n- #273a53: you can edit me too\n- #253750: you can edit me too\n- #23354d: you can edit me too\n- #203046: you can edit me too\n- #1e2d43: you can edit me too\n- #1a283d: you can edit me too\n- #172435: you can edit me too\n- #141f2e: you can edit me too\n- #111a27: you can edit me too\n- #0f1824: you can edit me too\n- #0d1521: you can edit me too\n- #0c131d: you can edit me too\n- #0c1d1c: you can edit me too\n- #0c1c1d: you can edit me too\n- #0c191d: you can edit me too\n- #0c141d: you can edit me too\n- #0c0d1d: you can edit me too\n- #130c1d: you can edit me too\n- #1b0c1d: you can edit me too\n- #1d0c17: you can edit me too\n- #1d0c10: you can edit me too\n- #1d0c0c: you can edit me too\n- #3b1b1b: you can edit me too\n- #3c1a1a: you can edit me too\n- #3f1c1c: you can edit me too\n- #401c1c: you can edit me too\n- #451c1c: you can edit me too\n- #461b1b: you can edit me too\n- #4c1a1a: you can edit me too\n- #521919: you can edit me too\n- #571919: you can edit me too\n- #5d1818: you can edit me too\n- #631717: you can edit me too\n- #651515: you can edit me too\n- #6a1616: you can edit me too\n- #6f1515: you can edit me too\n- #711414: you can edit me too\n- #761414: you can edit me too\n- #771313: you can edit me too\n- #7c1313: you can edit me too\n- #811313: you can edit me too\n- #821212: you can edit me too\n- #871212: you can edit me too\n- #881111: you can edit me too\n- #8d1111: you can edit me too\n- #8e1010: you can edit me too\n- #8f0f0f: you can edit me too\n- #900e0e: you can edit me too\n- #8e0b0b: you can edit me too\n- #8c0d0d: you can edit me too\n- #880c0c: you can edit me too\n- #830c0c: you can edit me too\n- #7e0c0c: you can edit me too\n- #790c0c: you can edit me too\n- #730c0c: you can edit me too\n- #6f0b0b: you can edit me too\n- #0b6f64: you can edit me too\n- #0b6f5f: you can edit me too\n- #0b6f56: you can edit me too\n- #0b6f49: you can edit me too\n- #0b6f31: you can edit me too\n- #0b6f1f: you can edit me too\n- #0b6f0d: you can edit me too\n- #176f0b: you can edit me too\n- #266f0b: you can edit me too\n- #296f0b: you can edit me too\n- #2e6f0b: you can edit me too\n- #1a2d10: you can edit me too\n- #1c3111: you can edit me too\n- #213814: you can edit me too\n- #233c15: you can edit me too\n- #254017: you can edit me too\n- #294918: you can edit me too\n- #2b4d1a: you can edit me too\n- #2d511a: you can edit me too\n- #315a1b: you can edit me too\n- #35631c: you can edit me too\n- #37681d: you can edit me too\n- #3b721d: you can edit me too\n- #3f7b1e: you can edit me too\n- #42851e: you can edit me too\n- #46901d: you can edit me too\n- #499a1d: you can edit me too\n- #4b9f1d: you can edit me too\n- #4ca61c: you can edit me too\n- #50b01c: you can edit me too\n- #51b71a: you can edit me too\n- #50b918: you can edit me too\n- #51c115: you can edit me too\n- #53c615: you can edit me too\n- #53c814: you can edit me too\n- #52c913: you can edit me too\n- #54d011: you can edit me too\n- #53d110: you can edit me too\n- #55d510: you can edit me too\n- #55d70f: you can edit me too\n- #54d80e: you can edit me too\n- #54da0b: you can edit me too\n- #56df0c: you can edit me too\n- #53db0a: you can edit me too\n- #55e00b: you can edit me too\n- #55e109: you can edit me too\n- #55e208: ISP LINE\n- #4c00ff: MY Guest NETWORK\n- #80ff00: you can edit me too\n- #3b4234: you can edit me too\n- #3a3442: you can edit me too\n- #3b3442: you can edit me too\n- #3c3442: you can edit me too\n- #3d3442: you can edit me too\n- #3e3442: you can edit me too\n- #3f3442: you can edit me too\n- #403442: you can edit me too\n- #413442: you can edit me too\n- #653d66: you can edit me too\n- #683f69: you can edit me too\n- #6c416c: you can edit me too\n- #6f4370: you can edit me too\n- #704270: you can edit me too\n- #734474: you can edit me too\n- #784479: you can edit me too\n- #7d447e: you can edit me too\n- #7e437f: you can edit me too\n- #834384: you can edit me too\n- #844285: you can edit me too\n- #89418b: you can edit me too\n- #8e428f: you can edit me too\n- #904091: you can edit me too\n- #923e93: you can edit me too\n- #973e98: you can edit me too\n- #943c96: you can edit me too\n- #993c9a: you can edit me too\n- #963a98: you can edit me too\n- #973899: you can edit me too\n- #99369b: you can edit me too\n- #9a359c: you can edit me too\n- #9b349d: you can edit me too\n- #9d329f: you can edit me too\n- #9e31a0: you can edit me too\n- #a02fa2: you can edit me too\n- #9d2d9f: you can edit me too\n- #9f2ba1: you can edit me too\n- #a129a3: you can edit me too\n- #a327a5: you can edit me too\n- #a525a7: you can edit me too\n- #a723a9: you can edit me too\n- #a921ab: you can edit me too\n- #ab1fad: you can edit me too\n- #ad1daf: you can edit me too\n- #ae1cb0: you can edit me too\n- #b019b3: you can edit me too\n- #b118b4: you can edit me too\n- #b316b6: you can edit me too\n- #b816bb: you can edit me too\n- #b514b8: you can edit me too\n- #ba14bd: you can edit me too\n- #b712ba: you can edit me too\n- #bb13be: you can edit me too\n- #b811bb: you can edit me too\n- #be10c1: you can edit me too\n- #bb0ebe: you can edit me too\n- #bd0cc0: you can edit me too\n- #be0bc1: you can edit me too\n- #c108c4: you can edit me too\n- #be06c1: you can edit me too\n- #c103c4: you can edit me too\n- #c301c6: you can edit me too\n- #c400c7: you can edit me too\n- #c900cc: you can edit me too\n- #ce00d1: you can edit me too\n- #d300d6: you can edit me too\n- #d800db: you can edit me too\n- #dd00e0: you can edit me too\n- #e200e6: you can edit me too\n- #ec00f0: you can edit me too\n- #f100f5: you can edit me too\n- #f600fa: you can edit me too\n- #fb00ff: you can edit me too\n- #ff00d0: iPhone (always guest iPhone)\n- #f97316: you can edit me too\n\n## Nodes\n\n### internet\n- **Name:** Internet\n- **IP:** 0.0.0.0\n- **Role:** \n- **Shape:** stop-sign\n- **Tags:** _none_\n- **Layer:** physical\n- **MAC:** \n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 1758, 299\n- **Size:** 168\n- **Styles:** `{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"amazon-web-services\"},\"circleColor\":\"#db0000\",\"circleBorder\":\"#000000\",\"titleSize\":52,\"subSize\":46}}`\n\n### internet-copy\n- **Name:** OPNSENSE\n- **IP:** 0.0.0.0\n- **Role:** \n- **Shape:** firewall\n- **Tags:** _none_\n- **Layer:** physical\n- **MAC:** \n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 2067, 473\n- **Size:** 50\n- **Styles:** `{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"opnsense\"}}}`\n\n### opnsense-copy\n- **Name:** Docker\n- **IP:** 0.0.0.0\n- **Role:** \n- **Shape:** firewall\n- **Tags:** _none_\n- **Layer:** physical\n- **MAC:** \n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 1774, 667\n- **Size:** 50\n- **Styles:** `{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"portainer\"}}}`\n\n### docker-copy\n- **Name:** Docker2\n- **IP:** 0.0.0.0\n- **Role:** \n- **Shape:** firewall\n- **Tags:** _none_\n- **Layer:** physical\n- **MAC:** \n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 1931, 782\n- **Size:** 50\n- **Styles:** `{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"jotty\"}}}`\n\n### docker-copy-1\n- **Name:** Docker3\n- **IP:** 0.0.0.0\n- **Role:** \n- **Shape:** firewall\n- **Tags:** _none_\n- **Layer:** physical\n- **MAC:** \n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 2158, 768\n- **Size:** 50\n- **Styles:** `{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"authportal\"}}}`\n\n### docker-copy-2\n- **Name:** Docker 4\n- **IP:** 0.0.0.0\n- **Role:** \n- **Shape:** firewall\n- **Tags:** [object Object]; [object Object]; [object Object]\n- **Layer:** physical\n- **MAC:** \n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 2342, 632\n- **Size:** 82\n- **Styles:** `{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"docker\"}}}`\n\n### opnsense-copy-1\n- **Name:** OPNSENSE GUEST\n- **IP:** 0.0.0.0\n- **Role:** \n- **Shape:** firewall\n- **Tags:** _none_\n- **Layer:** physical\n- **MAC:** \n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 2758, 308\n- **Size:** 50\n- **Styles:** `{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"opnsense-v1\"}}}`\n\n### phone\n- **Name:** Phone\n- **IP:** 0.0.0.0\n- **Role:** \n- **Shape:** phone\n- **Tags:** _none_\n- **Layer:** physical\n- **MAC:** \n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 3313, 503\n- **Size:** 121\n\n### desktop\n- **Name:** Desktop\n- **IP:** 0.0.0.0\n- **Role:** \n- **Shape:** pc\n- **Tags:** _none_\n- **Layer:** physical\n- **MAC:** \n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 2972, 481\n- **Size:** 147\n\n### dns\n- **Name:** DNS\n- **IP:** 0.0.0.0\n- **Role:** \n- **Shape:** cloud\n- **Tags:** _none_\n- **Layer:** physical\n- **MAC:** \n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 3200, 320\n- **Size:** 50\n\n### racked\n- **Name:** Racked\n- **IP:** \n- **Role:** Rack\n- **Shape:** server\n- **Tags:** _none_\n- **Layer:** physical\n- **MAC:** \n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** true\n- **Locked:** false\n- **Group ID:** \n- **Position:** 2646, 971\n- **Size:** 137\n- **Styles:** `{\"all\":{\"icon\":{\"library\":\"mdi\",\"name\":\"server-security\"},\"circleColor\":\"#010813\",\"circleBorder\":\"#ffffff\"}}`\n\n### thermostat\n- **Name:** Thermostat\n- **IP:** 0.0.0.0\n- **Role:** \n- **Shape:** thermostat\n- **Tags:** _none_\n- **Layer:** physical\n- **MAC:** \n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 1323, 575\n- **Size:** 50\n\n### video-doorbell\n- **Name:** Video Doorbell\n- **IP:** 0.0.0.0\n- **Role:** \n- **Shape:** doorbell\n- **Tags:** _none_\n- **Layer:** physical\n- **MAC:** \n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 1188, 455\n- **Size:** 50\n\n### smart-lock\n- **Name:** Smart Lock\n- **IP:** 0.0.0.0\n- **Role:** \n- **Shape:** smart-lock\n- **Tags:** _none_\n- **Layer:** physical\n- **MAC:** \n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 1292, 790\n- **Size:** 50\n\n### smart-bulb\n- **Name:** Smart Bulb\n- **IP:** 0.0.0.0\n- **Role:** \n- **Shape:** smart-bulb\n- **Tags:** _none_\n- **Layer:** physical\n- **MAC:** \n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 1496, 717\n- **Size:** 50\n\n### robot-vacuum\n- **Name:** Robot Vacuum\n- **IP:** 0.0.0.0\n- **Role:** \n- **Shape:** vacuum\n- **Tags:** _none_\n- **Layer:** physical\n- **MAC:** \n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 2289, 979\n- **Size:** 50\n\n## Connections\n\n- internet --> internet-copy\n  - **ID:** internet-internet-copy-1765238145151\n  - **Label:** \n  - **Color:** #55e208\n  - **Width:** 4\n  - **Direction:** both\n  - **Routing:** curved\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n\n- internet-copy --> opnsense-copy\n  - **ID:** internet-copy-opnsense-copy-1765238187451\n  - **Label:** \n  - **Color:** #4c00ff\n  - **Width:** 4\n  - **Direction:** both\n  - **Routing:** curved\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n\n- internet-copy --> docker-copy\n  - **ID:** internet-copy-docker-copy-1765238242477\n  - **Label:** \n  - **Color:** #4c00ff\n  - **Width:** 4\n  - **Direction:** both\n  - **Routing:** curved\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n\n- internet-copy --> docker-copy-1\n  - **ID:** internet-copy-docker-copy-1-1765238244637\n  - **Label:** \n  - **Color:** #4c00ff\n  - **Width:** 4\n  - **Direction:** both\n  - **Routing:** curved\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n\n- internet-copy --> docker-copy-2\n  - **ID:** internet-copy-docker-copy-2-1765238246233\n  - **Label:** \n  - **Color:** #4c00ff\n  - **Width:** 4\n  - **Direction:** both\n  - **Routing:** curved\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n\n- internet --> opnsense-copy-1\n  - **ID:** internet-opnsense-copy-1-1765238266117\n  - **Label:** \n  - **Color:** #80ff00\n  - **Width:** 4\n  - **Direction:** both\n  - **Routing:** curved\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n\n- opnsense-copy-1 --> dns\n  - **ID:** opnsense-copy-1-dns-1765238347996\n  - **Label:** \n  - **Color:** #fb00ff\n  - **Width:** 4\n  - **Direction:** both\n  - **Routing:** curved\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n\n- dns --> desktop\n  - **ID:** dns-desktop-1765238386101\n  - **Label:** \n  - **Color:** #ff00d0\n  - **Width:** 4\n  - **Direction:** both\n  - **Routing:** curved\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n\n- phone --> dns\n  - **ID:** phone-dns-1765238391156\n  - **Label:** \n  - **Color:** #ff00d0\n  - **Width:** 4\n  - **Direction:** both\n  - **Routing:** curved\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n\n- undefined --> undefined\n  - **ID:** custom-1765239449323\n  - **Label:** \n  - **Color:** #f97316\n  - **Width:** 4\n  - **Direction:** forward\n  - **Routing:** curved\n  - **Type:** custom\n  - **Line Style:** solid\n  - **Group ID:** \n  - **Points:** 2936,786 3184,888 2763,982\n\n## Zones\n\n### rect-1765238219615\n- **Position:** 2680, 251\n- **Size:** 814 x 389\n- **Color:** #ec0999\n- **Style:** filled\n- **Line Style:** solid\n- **Border Color:** \n- **Border Width:** 2\n\n## Text Labels\n\n### text-1765238422602\n- **Content:** Double click on desktop<br>or long press on mobile<br>to enter rack canvas view\n- **Position:** 2466, 742\n- **Font Size:** 40\n- **Color:** #e2e8f0\n- **Font Weight:** normal\n- **Font Style:** normal\n- **Text Align:** start\n- **Text Decoration:** none\n- **Background Color:** #000000\n- **Background Enabled:** false\n- **Opacity:** 1\n\n"
  },
  {
    "path": "demos/markdown-exports/the-one-file-networkening-corporate.md",
    "content": "<!--THEONEFILE_CONFIG\n{\n  \"nodeData\": {\n    \"core-router-1\": {\n      \"shape\": \"router\",\n      \"name\": \"Core Router 1\",\n      \"ip\": \"10.0.0.1\",\n      \"role\": \"Core Routing\",\n      \"tags\": [\n        \"core\",\n        \"tier-1\",\n        \"redundant\"\n      ],\n      \"notes\": [\n        \"Primary core router\",\n        \"BGP peering enabled\"\n      ],\n      \"mac\": \"00:1A:2B:3C:4D:01\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"2\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"core-router-2\": {\n      \"shape\": \"router\",\n      \"name\": \"Core Router 2\",\n      \"ip\": \"10.0.0.2\",\n      \"role\": \"Core Routing\",\n      \"tags\": [\n        \"core\",\n        \"tier-1\",\n        \"redundant\"\n      ],\n      \"notes\": [\n        \"Secondary core router\",\n        \"HSRP standby\"\n      ],\n      \"mac\": \"00:1A:2B:3C:4D:02\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"2\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null,\n      \"ping\": {\n        \"enabled\": true,\n        \"protocol\": \"custom\",\n        \"customUrl\": \"https://google.com\",\n        \"timeout\": 3000,\n        \"status\": \"online\",\n        \"lastCheck\": \"2025-12-09T00:15:04.343Z\"\n      }\n    },\n    \"fw-external-1\": {\n      \"shape\": \"firewall\",\n      \"name\": \"External FW 1\",\n      \"ip\": \"10.0.1.1\",\n      \"role\": \"Perimeter Security\",\n      \"tags\": [\n        \"security\",\n        \"perimeter\",\n        \"ha-pair\"\n      ],\n      \"notes\": [\n        \"Palo Alto PA-5250\",\n        \"Active node\"\n      ],\n      \"mac\": \"00:1A:2B:3C:4D:10\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"2\",\n      \"layer\": \"security\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"fw-external-2\": {\n      \"shape\": \"firewall\",\n      \"name\": \"External FW 2\",\n      \"ip\": \"10.0.1.2\",\n      \"role\": \"Perimeter Security\",\n      \"tags\": [\n        \"security\",\n        \"perimeter\",\n        \"ha-pair\"\n      ],\n      \"notes\": [\n        \"Palo Alto PA-5250\",\n        \"Passive node\"\n      ],\n      \"mac\": \"00:1A:2B:3C:4D:11\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"2\",\n      \"layer\": \"security\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"fw-internal\": {\n      \"shape\": \"firewall\",\n      \"name\": \"Internal FW\",\n      \"ip\": \"10.0.2.1\",\n      \"role\": \"Internal Segmentation\",\n      \"tags\": [\n        \"security\",\n        \"internal\"\n      ],\n      \"notes\": [\n        \"East-West traffic inspection\"\n      ],\n      \"mac\": \"00:1A:2B:3C:4D:12\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"2\",\n      \"layer\": \"security\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"core-switch-1\": {\n      \"shape\": \"switch\",\n      \"name\": \"Core Switch 1\",\n      \"ip\": \"10.0.10.1\",\n      \"role\": \"Core Switching\",\n      \"tags\": [\n        \"core\",\n        \"layer3\",\n        \"redundant\"\n      ],\n      \"notes\": [\n        \"Cisco Nexus 9000\",\n        \"VPC Domain 1\"\n      ],\n      \"mac\": \"00:1A:2B:3C:4D:20\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"2\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"core-switch-2\": {\n      \"shape\": \"switch\",\n      \"name\": \"Core Switch 2\",\n      \"ip\": \"10.0.10.2\",\n      \"role\": \"Core Switching\",\n      \"tags\": [\n        \"core\",\n        \"layer3\",\n        \"redundant\"\n      ],\n      \"notes\": [\n        \"Cisco Nexus 9000\",\n        \"VPC Domain 1\"\n      ],\n      \"mac\": \"00:1A:2B:3C:4D:21\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"2\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"dc-rack-a1\": {\n      \"shape\": \"server\",\n      \"name\": \"DC Rack A1\",\n      \"ip\": \"10.10.0.0/24\",\n      \"role\": \"Data Center Rack\",\n      \"tags\": [\n        \"datacenter\",\n        \"row-a\",\n        \"production\"\n      ],\n      \"notes\": [\n        \"Row A, Position 1\",\n        \"Primary compute\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": true,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"dc-rack-a2\": {\n      \"shape\": \"server\",\n      \"name\": \"DC Rack A2\",\n      \"ip\": \"10.10.1.0/24\",\n      \"role\": \"Data Center Rack\",\n      \"tags\": [\n        \"datacenter\",\n        \"row-a\",\n        \"production\"\n      ],\n      \"notes\": [\n        \"Row A, Position 2\",\n        \"Primary compute\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": true,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"dc-rack-b1\": {\n      \"shape\": \"server\",\n      \"name\": \"DC Rack B1\",\n      \"ip\": \"10.10.2.0/24\",\n      \"role\": \"Data Center Rack\",\n      \"tags\": [\n        \"datacenter\",\n        \"row-b\",\n        \"storage\"\n      ],\n      \"notes\": [\n        \"Row B, Position 1\",\n        \"Storage systems\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": true,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"dc-rack-b2\": {\n      \"shape\": \"server\",\n      \"name\": \"DC Rack B2\",\n      \"ip\": \"10.10.3.0/24\",\n      \"role\": \"Data Center Rack\",\n      \"tags\": [\n        \"datacenter\",\n        \"row-b\",\n        \"storage\"\n      ],\n      \"notes\": [\n        \"Row B, Position 2\",\n        \"Storage systems\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": true,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"dmz-rack\": {\n      \"shape\": \"server\",\n      \"name\": \"DMZ Rack\",\n      \"ip\": \"172.16.0.0/24\",\n      \"role\": \"DMZ Infrastructure\",\n      \"tags\": [\n        \"dmz\",\n        \"security\",\n        \"public-facing\",\n        {\n          \"type\": \"icon\",\n          \"library\": \"selfhst\",\n          \"name\": \"booklogr\"\n        },\n        {\n          \"type\": \"icon\",\n          \"library\": \"simple\",\n          \"name\": \"gmail\"\n        }\n      ],\n      \"notes\": [\n        \"Isolated DMZ zone\",\n        \"Public-facing services\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"security\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"24\",\n      \"isRack\": true,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"mgmt-rack\": {\n      \"shape\": \"server\",\n      \"name\": \"Management Rack\",\n      \"ip\": \"192.168.100.0/24\",\n      \"role\": \"Management Infrastructure\",\n      \"tags\": [\n        \"management\",\n        \"oob\",\n        \"noc\"\n      ],\n      \"notes\": [\n        \"Out-of-band management\",\n        \"NOC equipment\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"logical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"24\",\n      \"isRack\": true,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"esxi-host-01\": {\n      \"shape\": \"server\",\n      \"name\": \"ESXi Host 01\",\n      \"ip\": \"10.10.0.11\",\n      \"role\": \"Hypervisor\",\n      \"tags\": [\n        \"vmware\",\n        \"compute\",\n        \"cluster-a\"\n      ],\n      \"notes\": [\n        \"Dell PowerEdge R750\",\n        \"512GB RAM\",\n        \"vSphere 8.0\"\n      ],\n      \"mac\": \"00:50:56:AA:01:01\",\n      \"rackUnit\": 38,\n      \"uHeight\": \"2\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a1\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"esxi-host-02\": {\n      \"shape\": \"server\",\n      \"name\": \"ESXi Host 02\",\n      \"ip\": \"10.10.0.12\",\n      \"role\": \"Hypervisor\",\n      \"tags\": [\n        \"vmware\",\n        \"compute\",\n        \"cluster-a\"\n      ],\n      \"notes\": [\n        \"Dell PowerEdge R750\",\n        \"512GB RAM\",\n        \"vSphere 8.0\"\n      ],\n      \"mac\": \"00:50:56:AA:01:02\",\n      \"rackUnit\": 35,\n      \"uHeight\": \"2\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a1\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"esxi-host-03\": {\n      \"shape\": \"server\",\n      \"name\": \"ESXi Host 03\",\n      \"ip\": \"10.10.0.13\",\n      \"role\": \"Hypervisor\",\n      \"tags\": [\n        \"vmware\",\n        \"compute\",\n        \"cluster-a\"\n      ],\n      \"notes\": [\n        \"Dell PowerEdge R750\",\n        \"512GB RAM\",\n        \"vSphere 8.0\"\n      ],\n      \"mac\": \"00:50:56:AA:01:03\",\n      \"rackUnit\": 32,\n      \"uHeight\": \"2\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a1\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"esxi-host-04\": {\n      \"shape\": \"server\",\n      \"name\": \"ESXi Host 04\",\n      \"ip\": \"10.10.0.14\",\n      \"role\": \"Hypervisor\",\n      \"tags\": [\n        \"vmware\",\n        \"compute\",\n        \"cluster-a\"\n      ],\n      \"notes\": [\n        \"Dell PowerEdge R750\",\n        \"512GB RAM\",\n        \"vSphere 8.0\"\n      ],\n      \"mac\": \"00:50:56:AA:01:04\",\n      \"rackUnit\": 29,\n      \"uHeight\": \"2\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a1\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"tor-switch-a1\": {\n      \"shape\": \"switch\",\n      \"name\": \"ToR Switch A1\",\n      \"ip\": \"10.10.0.1\",\n      \"role\": \"Top of Rack\",\n      \"tags\": [\n        \"tor\",\n        \"access\",\n        \"rack-a1\"\n      ],\n      \"notes\": [\n        \"Cisco Nexus 93180YC-FX\",\n        \"48x25G ports\"\n      ],\n      \"mac\": \"00:1A:2B:3C:5D:01\",\n      \"rackUnit\": 42,\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a1\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"esxi-host-05\": {\n      \"shape\": \"server\",\n      \"name\": \"ESXi Host 05\",\n      \"ip\": \"10.10.1.11\",\n      \"role\": \"Hypervisor\",\n      \"tags\": [\n        \"vmware\",\n        \"compute\",\n        \"cluster-b\"\n      ],\n      \"notes\": [\n        \"Dell PowerEdge R750\",\n        \"768GB RAM\",\n        \"vSphere 8.0\"\n      ],\n      \"mac\": \"00:50:56:AA:02:01\",\n      \"rackUnit\": 38,\n      \"uHeight\": \"2\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a2\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"esxi-host-06\": {\n      \"shape\": \"server\",\n      \"name\": \"ESXi Host 06\",\n      \"ip\": \"10.10.1.12\",\n      \"role\": \"Hypervisor\",\n      \"tags\": [\n        \"vmware\",\n        \"compute\",\n        \"cluster-b\"\n      ],\n      \"notes\": [\n        \"Dell PowerEdge R750\",\n        \"768GB RAM\",\n        \"vSphere 8.0\"\n      ],\n      \"mac\": \"00:50:56:AA:02:02\",\n      \"rackUnit\": 35,\n      \"uHeight\": \"2\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a2\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"esxi-host-07\": {\n      \"shape\": \"server\",\n      \"name\": \"ESXi Host 07\",\n      \"ip\": \"10.10.1.13\",\n      \"role\": \"Hypervisor\",\n      \"tags\": [\n        \"vmware\",\n        \"compute\",\n        \"cluster-b\"\n      ],\n      \"notes\": [\n        \"Dell PowerEdge R750\",\n        \"768GB RAM\",\n        \"vSphere 8.0\"\n      ],\n      \"mac\": \"00:50:56:AA:02:03\",\n      \"rackUnit\": 32,\n      \"uHeight\": \"2\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a2\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"esxi-host-08\": {\n      \"shape\": \"server\",\n      \"name\": \"ESXi Host 08\",\n      \"ip\": \"10.10.1.14\",\n      \"role\": \"Hypervisor\",\n      \"tags\": [\n        \"vmware\",\n        \"compute\",\n        \"cluster-b\"\n      ],\n      \"notes\": [\n        \"Dell PowerEdge R750\",\n        \"768GB RAM\",\n        \"vSphere 8.0\"\n      ],\n      \"mac\": \"00:50:56:AA:02:04\",\n      \"rackUnit\": 29,\n      \"uHeight\": \"2\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a2\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"tor-switch-a2\": {\n      \"shape\": \"switch\",\n      \"name\": \"ToR Switch A2\",\n      \"ip\": \"10.10.1.1\",\n      \"role\": \"Top of Rack\",\n      \"tags\": [\n        \"tor\",\n        \"access\",\n        \"rack-a2\"\n      ],\n      \"notes\": [\n        \"Cisco Nexus 93180YC-FX\",\n        \"48x25G ports\"\n      ],\n      \"mac\": \"00:1A:2B:3C:5D:02\",\n      \"rackUnit\": 42,\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a2\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"san-primary\": {\n      \"shape\": \"database\",\n      \"name\": \"SAN Primary\",\n      \"ip\": \"10.10.2.10\",\n      \"role\": \"Primary Storage\",\n      \"tags\": [\n        \"storage\",\n        \"san\",\n        \"netapp\"\n      ],\n      \"notes\": [\n        \"NetApp AFF A400\",\n        \"500TB Raw\",\n        \"FC 32Gb\"\n      ],\n      \"mac\": \"00:A0:98:AA:01:01\",\n      \"rackUnit\": 36,\n      \"uHeight\": \"6\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-b1\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"san-secondary\": {\n      \"shape\": \"database\",\n      \"name\": \"SAN Secondary\",\n      \"ip\": \"10.10.2.11\",\n      \"role\": \"Secondary Storage\",\n      \"tags\": [\n        \"storage\",\n        \"san\",\n        \"netapp\"\n      ],\n      \"notes\": [\n        \"NetApp AFF A400\",\n        \"500TB Raw\",\n        \"FC 32Gb\"\n      ],\n      \"mac\": \"00:A0:98:AA:01:02\",\n      \"rackUnit\": 28,\n      \"uHeight\": \"6\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-b1\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"fc-switch-1\": {\n      \"shape\": \"switch\",\n      \"name\": \"FC Switch 1\",\n      \"ip\": \"10.10.2.1\",\n      \"role\": \"Fibre Channel\",\n      \"tags\": [\n        \"storage\",\n        \"fc\",\n        \"fabric-a\"\n      ],\n      \"notes\": [\n        \"Brocade G620\",\n        \"Fabric A\"\n      ],\n      \"mac\": \"00:1A:2B:FC:01:01\",\n      \"rackUnit\": 42,\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-b1\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"fc-switch-2\": {\n      \"shape\": \"switch\",\n      \"name\": \"FC Switch 2\",\n      \"ip\": \"10.10.2.2\",\n      \"role\": \"Fibre Channel\",\n      \"tags\": [\n        \"storage\",\n        \"fc\",\n        \"fabric-b\"\n      ],\n      \"notes\": [\n        \"Brocade G620\",\n        \"Fabric B\"\n      ],\n      \"mac\": \"00:1A:2B:FC:01:02\",\n      \"rackUnit\": 41,\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-b1\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"backup-server-1\": {\n      \"shape\": \"server\",\n      \"name\": \"Backup Server 1\",\n      \"ip\": \"10.10.3.10\",\n      \"role\": \"Backup Infrastructure\",\n      \"tags\": [\n        \"backup\",\n        \"veeam\",\n        \"protection\"\n      ],\n      \"notes\": [\n        \"Veeam Backup Server\",\n        \"Dell R740xd\",\n        \"200TB\"\n      ],\n      \"mac\": \"00:50:56:BB:01:01\",\n      \"rackUnit\": 36,\n      \"uHeight\": \"2\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-b2\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"backup-server-2\": {\n      \"shape\": \"server\",\n      \"name\": \"Backup Server 2\",\n      \"ip\": \"10.10.3.11\",\n      \"role\": \"Backup Infrastructure\",\n      \"tags\": [\n        \"backup\",\n        \"veeam\",\n        \"protection\"\n      ],\n      \"notes\": [\n        \"Veeam Backup Server\",\n        \"Dell R740xd\",\n        \"200TB\"\n      ],\n      \"mac\": \"00:50:56:BB:01:02\",\n      \"rackUnit\": 33,\n      \"uHeight\": \"2\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-b2\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"tape-library\": {\n      \"shape\": \"database\",\n      \"name\": \"Tape Library\",\n      \"ip\": \"10.10.3.20\",\n      \"role\": \"Archival Storage\",\n      \"tags\": [\n        \"backup\",\n        \"tape\",\n        \"lto9\"\n      ],\n      \"notes\": [\n        \"IBM TS4500\",\n        \"LTO-9\",\n        \"Long-term archive\"\n      ],\n      \"mac\": \"00:50:56:BB:02:01\",\n      \"rackUnit\": 20,\n      \"uHeight\": \"10\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-b2\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"tor-switch-b1\": {\n      \"shape\": \"switch\",\n      \"name\": \"ToR Switch B1\",\n      \"ip\": \"10.10.2.3\",\n      \"role\": \"Top of Rack\",\n      \"tags\": [\n        \"tor\",\n        \"access\",\n        \"rack-b1\"\n      ],\n      \"notes\": [\n        \"Cisco Nexus 93180YC-FX\"\n      ],\n      \"mac\": \"00:1A:2B:3C:5D:03\",\n      \"rackUnit\": 40,\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-b1\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"tor-switch-b2\": {\n      \"shape\": \"switch\",\n      \"name\": \"ToR Switch B2\",\n      \"ip\": \"10.10.3.1\",\n      \"role\": \"Top of Rack\",\n      \"tags\": [\n        \"tor\",\n        \"access\",\n        \"rack-b2\"\n      ],\n      \"notes\": [\n        \"Cisco Nexus 93180YC-FX\"\n      ],\n      \"mac\": \"00:1A:2B:3C:5D:04\",\n      \"rackUnit\": 42,\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-b2\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"web-server-1\": {\n      \"shape\": \"server\",\n      \"name\": \"Web Server 1\",\n      \"ip\": \"172.16.0.11\",\n      \"role\": \"Web Frontend\",\n      \"tags\": [\n        \"dmz\",\n        \"web\",\n        \"nginx\"\n      ],\n      \"notes\": [\n        \"NGINX reverse proxy\",\n        \"Public facing\"\n      ],\n      \"mac\": \"00:50:56:CC:01:01\",\n      \"rackUnit\": 20,\n      \"uHeight\": \"1\",\n      \"layer\": \"security\",\n      \"assignedRack\": \"dmz-rack\",\n      \"rackCapacity\": \"24\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"web-server-2\": {\n      \"shape\": \"server\",\n      \"name\": \"Web Server 2\",\n      \"ip\": \"172.16.0.12\",\n      \"role\": \"Web Frontend\",\n      \"tags\": [\n        \"dmz\",\n        \"web\",\n        \"nginx\"\n      ],\n      \"notes\": [\n        \"NGINX reverse proxy\",\n        \"Public facing\"\n      ],\n      \"mac\": \"00:50:56:CC:01:02\",\n      \"rackUnit\": 18,\n      \"uHeight\": \"1\",\n      \"layer\": \"security\",\n      \"assignedRack\": \"dmz-rack\",\n      \"rackCapacity\": \"24\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"waf-1\": {\n      \"shape\": \"firewall\",\n      \"name\": \"WAF Appliance\",\n      \"ip\": \"172.16.0.5\",\n      \"role\": \"Web Application Firewall\",\n      \"tags\": [\n        \"dmz\",\n        \"security\",\n        \"waf\"\n      ],\n      \"notes\": [\n        \"F5 BIG-IP ASM\",\n        \"OWASP protection\"\n      ],\n      \"mac\": \"00:50:56:CC:02:01\",\n      \"rackUnit\": 22,\n      \"uHeight\": \"2\",\n      \"layer\": \"security\",\n      \"assignedRack\": \"dmz-rack\",\n      \"rackCapacity\": \"24\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"load-balancer-dmz\": {\n      \"shape\": \"switch\",\n      \"name\": \"DMZ Load Balancer\",\n      \"ip\": \"172.16.0.3\",\n      \"role\": \"Load Balancing\",\n      \"tags\": [\n        \"dmz\",\n        \"lb\",\n        \"f5\"\n      ],\n      \"notes\": [\n        \"F5 BIG-IP LTM\",\n        \"VIP: 172.16.0.100\"\n      ],\n      \"mac\": \"00:50:56:CC:03:01\",\n      \"rackUnit\": 16,\n      \"uHeight\": \"2\",\n      \"layer\": \"security\",\n      \"assignedRack\": \"dmz-rack\",\n      \"rackCapacity\": \"24\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"mail-gateway\": {\n      \"shape\": \"server\",\n      \"name\": \"Mail Gateway\",\n      \"ip\": \"172.16.0.25\",\n      \"role\": \"Email Security\",\n      \"tags\": [\n        \"dmz\",\n        \"email\",\n        \"security\"\n      ],\n      \"notes\": [\n        \"Proofpoint Email Gateway\",\n        \"Spam/malware filtering\"\n      ],\n      \"mac\": \"00:50:56:CC:04:01\",\n      \"rackUnit\": 14,\n      \"uHeight\": \"1\",\n      \"layer\": \"security\",\n      \"assignedRack\": \"dmz-rack\",\n      \"rackCapacity\": \"24\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"dns-external-1\": {\n      \"shape\": \"circle\",\n      \"name\": \"External DNS 1\",\n      \"ip\": \"172.16.0.53\",\n      \"role\": \"External DNS\",\n      \"tags\": [\n        \"dmz\",\n        \"dns\",\n        \"public\"\n      ],\n      \"notes\": [\n        \"BIND DNS\",\n        \"Authoritative for corp.com\"\n      ],\n      \"mac\": \"00:50:56:CC:05:01\",\n      \"rackUnit\": 12,\n      \"uHeight\": \"1\",\n      \"layer\": \"security\",\n      \"assignedRack\": \"dmz-rack\",\n      \"rackCapacity\": \"24\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"dns-external-2\": {\n      \"shape\": \"circle\",\n      \"name\": \"External DNS 2\",\n      \"ip\": \"172.16.0.54\",\n      \"role\": \"External DNS\",\n      \"tags\": [\n        \"dmz\",\n        \"dns\",\n        \"public\"\n      ],\n      \"notes\": [\n        \"BIND DNS\",\n        \"Secondary for corp.com\"\n      ],\n      \"mac\": \"00:50:56:CC:05:02\",\n      \"rackUnit\": 10,\n      \"uHeight\": \"1\",\n      \"layer\": \"security\",\n      \"assignedRack\": \"dmz-rack\",\n      \"rackCapacity\": \"24\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"vcenter\": {\n      \"shape\": \"server\",\n      \"name\": \"vCenter Server\",\n      \"ip\": \"192.168.100.10\",\n      \"role\": \"Virtualization Management\",\n      \"tags\": [\n        \"management\",\n        \"vmware\",\n        \"vcsa\"\n      ],\n      \"notes\": [\n        \"vCenter Server Appliance 8.0\",\n        \"Single SSO domain\"\n      ],\n      \"mac\": \"00:50:56:DD:01:01\",\n      \"rackUnit\": 20,\n      \"uHeight\": \"2\",\n      \"layer\": \"logical\",\n      \"assignedRack\": \"mgmt-rack\",\n      \"rackCapacity\": \"24\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"nsx-manager\": {\n      \"shape\": \"server\",\n      \"name\": \"NSX Manager\",\n      \"ip\": \"192.168.100.15\",\n      \"role\": \"Network Virtualization\",\n      \"tags\": [\n        \"management\",\n        \"vmware\",\n        \"nsx\"\n      ],\n      \"notes\": [\n        \"NSX-T 4.1 Manager Cluster\"\n      ],\n      \"mac\": \"00:50:56:DD:02:01\",\n      \"rackUnit\": 17,\n      \"uHeight\": \"2\",\n      \"layer\": \"logical\",\n      \"assignedRack\": \"mgmt-rack\",\n      \"rackCapacity\": \"24\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"siem-server\": {\n      \"shape\": \"server\",\n      \"name\": \"SIEM Server\",\n      \"ip\": \"192.168.100.50\",\n      \"role\": \"Security Monitoring\",\n      \"tags\": [\n        \"management\",\n        \"security\",\n        \"splunk\"\n      ],\n      \"notes\": [\n        \"Splunk Enterprise\",\n        \"Security monitoring\"\n      ],\n      \"mac\": \"00:50:56:DD:03:01\",\n      \"rackUnit\": 14,\n      \"uHeight\": \"2\",\n      \"layer\": \"logical\",\n      \"assignedRack\": \"mgmt-rack\",\n      \"rackCapacity\": \"24\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"nms-server\": {\n      \"shape\": \"server\",\n      \"name\": \"Network Monitoring\",\n      \"ip\": \"192.168.100.60\",\n      \"role\": \"Network Management\",\n      \"tags\": [\n        \"management\",\n        \"monitoring\",\n        \"prtg\"\n      ],\n      \"notes\": [\n        \"PRTG Network Monitor\",\n        \"5000 sensors\"\n      ],\n      \"mac\": \"00:50:56:DD:04:01\",\n      \"rackUnit\": 11,\n      \"uHeight\": \"1\",\n      \"layer\": \"logical\",\n      \"assignedRack\": \"mgmt-rack\",\n      \"rackCapacity\": \"24\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"jump-server\": {\n      \"shape\": \"server\",\n      \"name\": \"Jump Server\",\n      \"ip\": \"192.168.100.100\",\n      \"role\": \"Bastion Host\",\n      \"tags\": [\n        \"management\",\n        \"security\",\n        \"bastion\"\n      ],\n      \"notes\": [\n        \"Windows Server 2022\",\n        \"MFA enabled\"\n      ],\n      \"mac\": \"00:50:56:DD:05:01\",\n      \"rackUnit\": 9,\n      \"uHeight\": \"1\",\n      \"layer\": \"logical\",\n      \"assignedRack\": \"mgmt-rack\",\n      \"rackCapacity\": \"24\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"ipam-server\": {\n      \"shape\": \"server\",\n      \"name\": \"IPAM/DDI\",\n      \"ip\": \"192.168.100.70\",\n      \"role\": \"IP Management\",\n      \"tags\": [\n        \"management\",\n        \"dns\",\n        \"dhcp\"\n      ],\n      \"notes\": [\n        \"Infoblox DDI\",\n        \"DNS/DHCP/IPAM\"\n      ],\n      \"mac\": \"00:50:56:DD:06:01\",\n      \"rackUnit\": 7,\n      \"uHeight\": \"2\",\n      \"layer\": \"logical\",\n      \"assignedRack\": \"mgmt-rack\",\n      \"rackCapacity\": \"24\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"wlc-primary\": {\n      \"shape\": \"wifi\",\n      \"name\": \"WLC Primary\",\n      \"ip\": \"10.20.0.1\",\n      \"role\": \"Wireless Controller\",\n      \"tags\": [\n        \"wireless\",\n        \"cisco\",\n        \"9800\"\n      ],\n      \"notes\": [\n        \"Cisco C9800-40\",\n        \"Primary controller\"\n      ],\n      \"mac\": \"00:1A:2B:WL:01:01\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"2\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"wlc-secondary\": {\n      \"shape\": \"wifi\",\n      \"name\": \"WLC Secondary\",\n      \"ip\": \"10.20.0.2\",\n      \"role\": \"Wireless Controller\",\n      \"tags\": [\n        \"wireless\",\n        \"cisco\",\n        \"9800\"\n      ],\n      \"notes\": [\n        \"Cisco C9800-40\",\n        \"HA Secondary\"\n      ],\n      \"mac\": \"00:1A:2B:WL:01:02\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"2\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"mobile-zone-hq\": {\n      \"shape\": \"phone\",\n      \"name\": \"HQ Mobile Zone\",\n      \"ip\": \"10.20.10.0/24\",\n      \"role\": \"Mobile Device Zone\",\n      \"tags\": [\n        \"wireless\",\n        \"byod\",\n        \"mobile\"\n      ],\n      \"notes\": [\n        \"Corporate BYOD\",\n        \"MDM enrolled devices\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"mobile-zone-guest\": {\n      \"shape\": \"phone\",\n      \"name\": \"Guest WiFi Zone\",\n      \"ip\": \"10.30.0.0/24\",\n      \"role\": \"Guest Network\",\n      \"tags\": [\n        \"wireless\",\n        \"guest\",\n        \"isolated\"\n      ],\n      \"notes\": [\n        \"Captive portal\",\n        \"Internet only\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"mobile-zone-iot\": {\n      \"shape\": \"phone\",\n      \"name\": \"IoT Device Zone\",\n      \"ip\": \"10.40.0.0/24\",\n      \"role\": \"IoT Network\",\n      \"tags\": [\n        \"wireless\",\n        \"iot\",\n        \"building\"\n      ],\n      \"notes\": [\n        \"Building automation\",\n        \"Smart devices\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"branch-router-ny\": {\n      \"shape\": \"router\",\n      \"name\": \"NYC Branch Router\",\n      \"ip\": \"10.100.0.1\",\n      \"role\": \"Branch Gateway\",\n      \"tags\": [\n        \"branch\",\n        \"nyc\",\n        \"sd-wan\"\n      ],\n      \"notes\": [\n        \"Cisco Viptela vEdge\",\n        \"SD-WAN enabled\"\n      ],\n      \"mac\": \"00:1A:2B:BR:01:01\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"branch-router-la\": {\n      \"shape\": \"router\",\n      \"name\": \"LA Branch Router\",\n      \"ip\": \"10.101.0.1\",\n      \"role\": \"Branch Gateway\",\n      \"tags\": [\n        \"branch\",\n        \"la\",\n        \"sd-wan\"\n      ],\n      \"notes\": [\n        \"Cisco Viptela vEdge\",\n        \"SD-WAN enabled\"\n      ],\n      \"mac\": \"00:1A:2B:BR:02:01\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"branch-router-chi\": {\n      \"shape\": \"router\",\n      \"name\": \"Chicago Branch Router\",\n      \"ip\": \"10.102.0.1\",\n      \"role\": \"Branch Gateway\",\n      \"tags\": [\n        \"branch\",\n        \"chicago\",\n        \"sd-wan\"\n      ],\n      \"notes\": [\n        \"Cisco Viptela vEdge\",\n        \"SD-WAN enabled\"\n      ],\n      \"mac\": \"00:1A:2B:BR:03:01\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"branch-router-lon\": {\n      \"shape\": \"router\",\n      \"name\": \"London Branch Router\",\n      \"ip\": \"10.200.0.1\",\n      \"role\": \"Branch Gateway\",\n      \"tags\": [\n        \"branch\",\n        \"london\",\n        \"sd-wan\"\n      ],\n      \"notes\": [\n        \"Cisco Viptela vEdge\",\n        \"EMEA region\"\n      ],\n      \"mac\": \"00:1A:2B:BR:04:01\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"branch-router-tokyo\": {\n      \"shape\": \"router\",\n      \"name\": \"Tokyo Branch Router\",\n      \"ip\": \"10.201.0.1\",\n      \"role\": \"Branch Gateway\",\n      \"tags\": [\n        \"branch\",\n        \"tokyo\",\n        \"sd-wan\"\n      ],\n      \"notes\": [\n        \"Cisco Viptela vEdge\",\n        \"APAC region\"\n      ],\n      \"mac\": \"00:1A:2B:BR:05:01\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"cloud-aws\": {\n      \"shape\": \"cloud\",\n      \"name\": \"AWS Cloud\",\n      \"ip\": \"vpc-0a1b2c3d\",\n      \"role\": \"Public Cloud\",\n      \"tags\": [\n        \"cloud\",\n        \"aws\",\n        \"hybrid\"\n      ],\n      \"notes\": [\n        \"AWS US-East-1\",\n        \"VPC peering to HQ\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"logical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"cloud-azure\": {\n      \"shape\": \"cloud\",\n      \"name\": \"Azure Cloud\",\n      \"ip\": \"vnet-corp-prod\",\n      \"role\": \"Public Cloud\",\n      \"tags\": [\n        \"cloud\",\n        \"azure\",\n        \"hybrid\"\n      ],\n      \"notes\": [\n        \"Azure East US 2\",\n        \"ExpressRoute\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"logical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"cloud-gcp\": {\n      \"shape\": \"cloud\",\n      \"name\": \"GCP Cloud\",\n      \"ip\": \"vpc-gcp-corp\",\n      \"role\": \"Public Cloud\",\n      \"tags\": [\n        \"cloud\",\n        \"gcp\",\n        \"dev\"\n      ],\n      \"notes\": [\n        \"GCP us-central1\",\n        \"Dev/Test workloads\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"logical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"isp-primary\": {\n      \"shape\": \"globe\",\n      \"name\": \"ISP Primary\",\n      \"ip\": \"203.0.113.1\",\n      \"role\": \"Internet Uplink\",\n      \"tags\": [\n        \"wan\",\n        \"internet\",\n        \"primary\"\n      ],\n      \"notes\": [\n        \"AT&T MPLS\",\n        \"1 Gbps dedicated\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"isp-secondary\": {\n      \"shape\": \"globe\",\n      \"name\": \"ISP Secondary\",\n      \"ip\": \"198.51.100.1\",\n      \"role\": \"Internet Uplink\",\n      \"tags\": [\n        \"wan\",\n        \"internet\",\n        \"backup\"\n      ],\n      \"notes\": [\n        \"Verizon Business\",\n        \"500 Mbps backup\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"dc-internal-1\": {\n      \"shape\": \"circle\",\n      \"name\": \"DC1 Int DNS\",\n      \"ip\": \"10.10.0.53\",\n      \"role\": \"Internal DNS/AD\",\n      \"tags\": [\n        \"dns\",\n        \"ad\",\n        \"dc1\"\n      ],\n      \"notes\": [\n        \"Windows Server 2022\",\n        \"Primary DC\"\n      ],\n      \"mac\": \"00:50:56:AD:01:01\",\n      \"rackUnit\": 26,\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a1\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"dc-internal-2\": {\n      \"shape\": \"circle\",\n      \"name\": \"DC2 Int DNS\",\n      \"ip\": \"10.10.1.53\",\n      \"role\": \"Internal DNS/AD\",\n      \"tags\": [\n        \"dns\",\n        \"ad\",\n        \"dc2\"\n      ],\n      \"notes\": [\n        \"Windows Server 2022\",\n        \"Secondary DC\"\n      ],\n      \"mac\": \"00:50:56:AD:01:02\",\n      \"rackUnit\": 26,\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a2\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"app-server-1\": {\n      \"shape\": \"server\",\n      \"name\": \"App Server 01\",\n      \"ip\": \"10.10.0.101\",\n      \"role\": \"Application\",\n      \"tags\": [\n        \"app\",\n        \"iis\",\n        \"web\"\n      ],\n      \"notes\": [\n        \"Windows Server 2022\",\n        \"IIS Application\"\n      ],\n      \"mac\": \"00:50:56:AP:01:01\",\n      \"rackUnit\": 24,\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a1\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"app-server-2\": {\n      \"shape\": \"server\",\n      \"name\": \"App Server 02\",\n      \"ip\": \"10.10.0.102\",\n      \"role\": \"Application\",\n      \"tags\": [\n        \"app\",\n        \"iis\",\n        \"web\"\n      ],\n      \"notes\": [\n        \"Windows Server 2022\",\n        \"IIS Application\"\n      ],\n      \"mac\": \"00:50:56:AP:01:02\",\n      \"rackUnit\": 22,\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a1\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"db-server-1\": {\n      \"shape\": \"database\",\n      \"name\": \"SQL Server 01\",\n      \"ip\": \"10.10.0.201\",\n      \"role\": \"Database\",\n      \"tags\": [\n        \"db\",\n        \"sql\",\n        \"primary\"\n      ],\n      \"notes\": [\n        \"SQL Server 2022 Enterprise\",\n        \"AlwaysOn Primary\"\n      ],\n      \"mac\": \"00:50:56:DB:01:01\",\n      \"rackUnit\": 20,\n      \"uHeight\": \"2\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a1\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"db-server-2\": {\n      \"shape\": \"database\",\n      \"name\": \"SQL Server 02\",\n      \"ip\": \"10.10.1.201\",\n      \"role\": \"Database\",\n      \"tags\": [\n        \"db\",\n        \"sql\",\n        \"secondary\"\n      ],\n      \"notes\": [\n        \"SQL Server 2022 Enterprise\",\n        \"AlwaysOn Secondary\"\n      ],\n      \"mac\": \"00:50:56:DB:01:02\",\n      \"rackUnit\": 24,\n      \"uHeight\": \"2\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a2\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"k8s-master-1\": {\n      \"shape\": \"hexagon\",\n      \"name\": \"K8s Master 1\",\n      \"ip\": \"10.10.1.50\",\n      \"role\": \"Container Orchestration\",\n      \"tags\": [\n        \"kubernetes\",\n        \"master\",\n        \"container\"\n      ],\n      \"notes\": [\n        \"K8s Control Plane\",\n        \"etcd member\"\n      ],\n      \"mac\": \"00:50:56:K8:01:01\",\n      \"rackUnit\": 21,\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a2\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"k8s-master-2\": {\n      \"shape\": \"hexagon\",\n      \"name\": \"K8s Master 2\",\n      \"ip\": \"10.10.1.51\",\n      \"role\": \"Container Orchestration\",\n      \"tags\": [\n        \"kubernetes\",\n        \"master\",\n        \"container\"\n      ],\n      \"notes\": [\n        \"K8s Control Plane\",\n        \"etcd member\"\n      ],\n      \"mac\": \"00:50:56:K8:01:02\",\n      \"rackUnit\": 19,\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a2\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"k8s-master-3\": {\n      \"shape\": \"hexagon\",\n      \"name\": \"K8s Master 3\",\n      \"ip\": \"10.10.1.52\",\n      \"role\": \"Container Orchestration\",\n      \"tags\": [\n        \"kubernetes\",\n        \"master\",\n        \"container\"\n      ],\n      \"notes\": [\n        \"K8s Control Plane\",\n        \"etcd member\"\n      ],\n      \"mac\": \"00:50:56:K8:01:03\",\n      \"rackUnit\": 17,\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a2\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"k8s-worker-1\": {\n      \"shape\": \"server\",\n      \"name\": \"K8s Worker 1\",\n      \"ip\": \"10.10.1.60\",\n      \"role\": \"Container Workload\",\n      \"tags\": [\n        \"kubernetes\",\n        \"worker\",\n        \"container\"\n      ],\n      \"notes\": [\n        \"K8s Worker Node\",\n        \"64GB RAM\"\n      ],\n      \"mac\": \"00:50:56:K8:02:01\",\n      \"rackUnit\": 15,\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a2\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"k8s-worker-2\": {\n      \"shape\": \"server\",\n      \"name\": \"K8s Worker 2\",\n      \"ip\": \"10.10.1.61\",\n      \"role\": \"Container Workload\",\n      \"tags\": [\n        \"kubernetes\",\n        \"worker\",\n        \"container\"\n      ],\n      \"notes\": [\n        \"K8s Worker Node\",\n        \"64GB RAM\"\n      ],\n      \"mac\": \"00:50:56:K8:02:02\",\n      \"rackUnit\": 13,\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a2\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"k8s-worker-3\": {\n      \"shape\": \"server\",\n      \"name\": \"K8s Worker 3\",\n      \"ip\": \"10.10.1.62\",\n      \"role\": \"Container Workload\",\n      \"tags\": [\n        \"kubernetes\",\n        \"worker\",\n        \"container\"\n      ],\n      \"notes\": [\n        \"K8s Worker Node\",\n        \"64GB RAM\"\n      ],\n      \"mac\": \"00:50:56:K8:02:03\",\n      \"rackUnit\": 11,\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a2\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"k8s-worker-4\": {\n      \"shape\": \"server\",\n      \"name\": \"K8s Worker 4\",\n      \"ip\": \"10.10.1.63\",\n      \"role\": \"Container Workload\",\n      \"tags\": [\n        \"kubernetes\",\n        \"worker\",\n        \"container\"\n      ],\n      \"notes\": [\n        \"K8s Worker Node\",\n        \"64GB RAM\"\n      ],\n      \"mac\": \"00:50:56:K8:02:04\",\n      \"rackUnit\": 9,\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a2\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"proxy-server-1\": {\n      \"shape\": \"server\",\n      \"name\": \"Proxy Server 1\",\n      \"ip\": \"10.5.0.10\",\n      \"role\": \"Web Proxy\",\n      \"tags\": [\n        \"proxy\",\n        \"squid\",\n        \"filtering\"\n      ],\n      \"notes\": [\n        \"Squid Proxy\",\n        \"Content filtering\"\n      ],\n      \"mac\": \"00:50:56:PX:01:01\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"security\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"proxy-server-2\": {\n      \"shape\": \"server\",\n      \"name\": \"Proxy Server 2\",\n      \"ip\": \"10.5.0.11\",\n      \"role\": \"Web Proxy\",\n      \"tags\": [\n        \"proxy\",\n        \"squid\",\n        \"filtering\"\n      ],\n      \"notes\": [\n        \"Squid Proxy\",\n        \"HA pair\"\n      ],\n      \"mac\": \"00:50:56:PX:01:02\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"security\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"vpn-concentrator\": {\n      \"shape\": \"firewall\",\n      \"name\": \"VPN Concentrator\",\n      \"ip\": \"10.0.5.1\",\n      \"role\": \"Remote Access VPN\",\n      \"tags\": [\n        \"vpn\",\n        \"remote\",\n        \"security\"\n      ],\n      \"notes\": [\n        \"Cisco ASA 5555-X\",\n        \"AnyConnect SSL VPN\"\n      ],\n      \"mac\": \"00:1A:2B:VP:01:01\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"2\",\n      \"layer\": \"security\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"nac-server\": {\n      \"shape\": \"server\",\n      \"name\": \"NAC Server\",\n      \"ip\": \"10.5.5.10\",\n      \"role\": \"Network Access Control\",\n      \"tags\": [\n        \"nac\",\n        \"ise\",\n        \"802.1x\"\n      ],\n      \"notes\": [\n        \"Cisco ISE 3.1\",\n        \"RADIUS/TACACS+\"\n      ],\n      \"mac\": \"00:50:56:NA:01:01\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"2\",\n      \"layer\": \"security\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"print-server\": {\n      \"shape\": \"server\",\n      \"name\": \"Print Server\",\n      \"ip\": \"10.10.0.150\",\n      \"role\": \"Print Services\",\n      \"tags\": [\n        \"print\",\n        \"windows\",\n        \"services\"\n      ],\n      \"notes\": [\n        \"Windows Print Server\",\n        \"50+ printers\"\n      ],\n      \"mac\": \"00:50:56:PR:01:01\",\n      \"rackUnit\": 18,\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a1\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"file-server\": {\n      \"shape\": \"database\",\n      \"name\": \"File Server\",\n      \"ip\": \"10.10.0.160\",\n      \"role\": \"File Services\",\n      \"tags\": [\n        \"file\",\n        \"smb\",\n        \"dfs\"\n      ],\n      \"notes\": [\n        \"Windows File Server\",\n        \"DFS namespace\"\n      ],\n      \"mac\": \"00:50:56:FS:01:01\",\n      \"rackUnit\": 16,\n      \"uHeight\": \"2\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a1\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"ca-server\": {\n      \"shape\": \"server\",\n      \"name\": \"Certificate Authority\",\n      \"ip\": \"192.168.100.80\",\n      \"role\": \"PKI Infrastructure\",\n      \"tags\": [\n        \"pki\",\n        \"ca\",\n        \"security\"\n      ],\n      \"notes\": [\n        \"Windows CA\",\n        \"Enterprise Root CA\"\n      ],\n      \"mac\": \"00:50:56:CA:01:01\",\n      \"rackUnit\": 5,\n      \"uHeight\": \"1\",\n      \"layer\": \"logical\",\n      \"assignedRack\": \"mgmt-rack\",\n      \"rackCapacity\": \"24\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"sccm-server\": {\n      \"shape\": \"server\",\n      \"name\": \"SCCM Server\",\n      \"ip\": \"192.168.100.90\",\n      \"role\": \"Endpoint Management\",\n      \"tags\": [\n        \"sccm\",\n        \"patching\",\n        \"software\"\n      ],\n      \"notes\": [\n        \"MECM Primary Site\",\n        \"Software deployment\"\n      ],\n      \"mac\": \"00:50:56:SC:01:01\",\n      \"rackUnit\": 3,\n      \"uHeight\": \"2\",\n      \"layer\": \"logical\",\n      \"assignedRack\": \"mgmt-rack\",\n      \"rackCapacity\": \"24\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"voip-cluster\": {\n      \"shape\": \"phone\",\n      \"name\": \"VoIP Cluster\",\n      \"ip\": \"10.50.0.0/24\",\n      \"role\": \"Voice Services\",\n      \"tags\": [\n        \"voip\",\n        \"cisco\",\n        \"ucm\"\n      ],\n      \"notes\": [\n        \"Cisco UCM Cluster\",\n        \"3000 endpoints\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"application\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"video-conf\": {\n      \"shape\": \"laptop\",\n      \"name\": \"Video Conference\",\n      \"ip\": \"10.51.0.0/24\",\n      \"role\": \"Video Services\",\n      \"tags\": [\n        \"video\",\n        \"webex\",\n        \"teams\"\n      ],\n      \"notes\": [\n        \"Webex/Teams integration\",\n        \"Meeting rooms\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"application\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"security-cameras\": {\n      \"shape\": \"camera\",\n      \"name\": \"Security Cameras\",\n      \"ip\": \"10.60.0.0/24\",\n      \"role\": \"Physical Security\",\n      \"tags\": [\n        \"cctv\",\n        \"surveillance\",\n        \"security\"\n      ],\n      \"notes\": [\n        \"150+ IP cameras\",\n        \"30-day retention\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"nvr-cluster\": {\n      \"shape\": \"server\",\n      \"name\": \"NVR Cluster\",\n      \"ip\": \"10.60.0.10\",\n      \"role\": \"Video Recording\",\n      \"tags\": [\n        \"nvr\",\n        \"surveillance\",\n        \"storage\"\n      ],\n      \"notes\": [\n        \"Milestone XProtect\",\n        \"500TB storage\"\n      ],\n      \"mac\": \"00:50:56:NV:01:01\",\n      \"rackUnit\": 15,\n      \"uHeight\": \"4\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-b2\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"dev-server-1\": {\n      \"shape\": \"server\",\n      \"name\": \"Dev Server 1\",\n      \"ip\": \"10.80.0.10\",\n      \"role\": \"Development\",\n      \"tags\": [\n        \"dev\",\n        \"gitlab\",\n        \"ci-cd\",\n        {\n          \"type\": \"icon\",\n          \"library\": \"selfhst\",\n          \"name\": \"dokku\"\n        }\n      ],\n      \"notes\": [\n        \"GitLab Server\",\n        \"CI/CD pipelines\"\n      ],\n      \"mac\": \"00:50:56:DV:01:01\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"2\",\n      \"layer\": \"application\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"dev-server-2\": {\n      \"shape\": \"server\",\n      \"name\": \"Dev Server 2\",\n      \"ip\": \"10.80.0.11\",\n      \"role\": \"Development\",\n      \"tags\": [\n        \"dev\",\n        \"jenkins\",\n        \"ci-cd\"\n      ],\n      \"notes\": [\n        \"Jenkins Server\",\n        \"Build automation\"\n      ],\n      \"mac\": \"00:50:56:DV:01:02\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"2\",\n      \"layer\": \"application\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"test-environment\": {\n      \"shape\": \"hexagon\",\n      \"name\": \"Test Environment\",\n      \"ip\": \"10.81.0.0/24\",\n      \"role\": \"QA/Testing\",\n      \"tags\": [\n        \"test\",\n        \"qa\",\n        \"staging\"\n      ],\n      \"notes\": [\n        \"Staging environment\",\n        \"Pre-prod validation\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"application\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"erp-system\": {\n      \"shape\": \"database\",\n      \"name\": \"ERP System\",\n      \"ip\": \"10.90.0.10\",\n      \"role\": \"Business Application\",\n      \"tags\": [\n        \"erp\",\n        \"sap\",\n        \"business\"\n      ],\n      \"notes\": [\n        \"SAP S/4HANA\",\n        \"Financial/HR systems\"\n      ],\n      \"mac\": \"00:50:56:ER:01:01\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"4\",\n      \"layer\": \"application\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"crm-system\": {\n      \"shape\": \"database\",\n      \"name\": \"CRM System\",\n      \"ip\": \"10.91.0.10\",\n      \"role\": \"Business Application\",\n      \"tags\": [\n        \"crm\",\n        \"salesforce\",\n        \"business\"\n      ],\n      \"notes\": [\n        \"Salesforce integration\",\n        \"Sales/Marketing\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"application\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"endpoint-1000\": {\n      \"shape\": \"laptop\",\n      \"name\": \"Corporate Endpoints\",\n      \"ip\": \"10.70.0.0/22\",\n      \"role\": \"User Workstations\",\n      \"tags\": [\n        \"endpoints\",\n        \"workstations\",\n        \"users\"\n      ],\n      \"notes\": [\n        \"~1000 corporate laptops\",\n        \"Windows 11\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"dist-switch-floor1\": {\n      \"shape\": \"switch\",\n      \"name\": \"Floor 1 Switch\",\n      \"ip\": \"10.1.1.1\",\n      \"role\": \"Distribution\",\n      \"tags\": [\n        \"distribution\",\n        \"floor-1\",\n        \"access\"\n      ],\n      \"notes\": [\n        \"Cisco C9300-48P\",\n        \"PoE+ enabled\"\n      ],\n      \"mac\": \"00:1A:2B:FL:01:01\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"dist-switch-floor2\": {\n      \"shape\": \"switch\",\n      \"name\": \"Floor 2 Switch\",\n      \"ip\": \"10.1.2.1\",\n      \"role\": \"Distribution\",\n      \"tags\": [\n        \"distribution\",\n        \"floor-2\",\n        \"access\"\n      ],\n      \"notes\": [\n        \"Cisco C9300-48P\",\n        \"PoE+ enabled\"\n      ],\n      \"mac\": \"00:1A:2B:FL:02:01\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"dist-switch-floor3\": {\n      \"shape\": \"switch\",\n      \"name\": \"Floor 3 Switch\",\n      \"ip\": \"10.1.3.1\",\n      \"role\": \"Distribution\",\n      \"tags\": [\n        \"distribution\",\n        \"floor-3\",\n        \"access\"\n      ],\n      \"notes\": [\n        \"Cisco C9300-48P\",\n        \"PoE+ enabled\"\n      ],\n      \"mac\": \"00:1A:2B:FL:03:01\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"dist-switch-floor4\": {\n      \"shape\": \"switch\",\n      \"name\": \"Floor 4 Switch\",\n      \"ip\": \"10.1.4.1\",\n      \"role\": \"Distribution\",\n      \"tags\": [\n        \"distribution\",\n        \"floor-4\",\n        \"access\"\n      ],\n      \"notes\": [\n        \"Cisco C9300-48P\",\n        \"PoE+ enabled\"\n      ],\n      \"mac\": \"00:1A:2B:FL:04:01\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"ap-floor1-zone1\": {\n      \"shape\": \"wifi\",\n      \"name\": \"AP Floor 1 Zone 1\",\n      \"ip\": \"10.20.1.10\",\n      \"role\": \"Wireless Access\",\n      \"tags\": [\n        \"wifi\",\n        \"ap\",\n        \"floor-1\"\n      ],\n      \"notes\": [\n        \"Cisco 9120AX\",\n        \"Wi-Fi 6\"\n      ],\n      \"mac\": \"00:1A:2B:AP:01:01\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"ap-floor2-zone1\": {\n      \"shape\": \"wifi\",\n      \"name\": \"AP Floor 2 Zone 1\",\n      \"ip\": \"10.20.2.10\",\n      \"role\": \"Wireless Access\",\n      \"tags\": [\n        \"wifi\",\n        \"ap\",\n        \"floor-2\"\n      ],\n      \"notes\": [\n        \"Cisco 9120AX\",\n        \"Wi-Fi 6\"\n      ],\n      \"mac\": \"00:1A:2B:AP:02:01\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"ap-floor3-zone1\": {\n      \"shape\": \"wifi\",\n      \"name\": \"AP Floor 3 Zone 1\",\n      \"ip\": \"10.20.3.10\",\n      \"role\": \"Wireless Access\",\n      \"tags\": [\n        \"wifi\",\n        \"ap\",\n        \"floor-3\"\n      ],\n      \"notes\": [\n        \"Cisco 9120AX\",\n        \"Wi-Fi 6\"\n      ],\n      \"mac\": \"00:1A:2B:AP:03:01\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"ap-floor4-zone1\": {\n      \"shape\": \"wifi\",\n      \"name\": \"AP Floor 4 Zone 1\",\n      \"ip\": \"10.20.4.10\",\n      \"role\": \"Wireless Access\",\n      \"tags\": [\n        \"wifi\",\n        \"ap\",\n        \"floor-4\"\n      ],\n      \"notes\": [\n        \"Cisco 9120AX\",\n        \"Wi-Fi 6\"\n      ],\n      \"mac\": \"00:1A:2B:AP:04:01\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"ups-dc-1\": {\n      \"shape\": \"rectangle\",\n      \"name\": \"UPS DC-1\",\n      \"ip\": \"192.168.200.10\",\n      \"role\": \"Power Management\",\n      \"tags\": [\n        \"power\",\n        \"ups\",\n        \"datacenter\"\n      ],\n      \"notes\": [\n        \"APC Symmetra\",\n        \"80kVA\",\n        \"30 min runtime\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"ups-dc-2\": {\n      \"shape\": \"rectangle\",\n      \"name\": \"UPS DC-2\",\n      \"ip\": \"192.168.200.11\",\n      \"role\": \"Power Management\",\n      \"tags\": [\n        \"power\",\n        \"ups\",\n        \"datacenter\"\n      ],\n      \"notes\": [\n        \"APC Symmetra\",\n        \"80kVA\",\n        \"Redundant\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"pdu-rack-a1\": {\n      \"shape\": \"rectangle\",\n      \"name\": \"PDU Rack A1\",\n      \"ip\": \"192.168.200.21\",\n      \"role\": \"Power Distribution\",\n      \"tags\": [\n        \"power\",\n        \"pdu\",\n        \"rack-a1\"\n      ],\n      \"notes\": [\n        \"APC Switched PDU\",\n        \"Per-outlet metering\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": 1,\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a1\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"pdu-rack-a2\": {\n      \"shape\": \"rectangle\",\n      \"name\": \"PDU Rack A2\",\n      \"ip\": \"192.168.200.22\",\n      \"role\": \"Power Distribution\",\n      \"tags\": [\n        \"power\",\n        \"pdu\",\n        \"rack-a2\"\n      ],\n      \"notes\": [\n        \"APC Switched PDU\",\n        \"Per-outlet metering\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": 1,\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"dc-rack-a2\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"cooling-1\": {\n      \"shape\": \"rectangle\",\n      \"name\": \"CRAC Unit 1\",\n      \"ip\": \"192.168.200.30\",\n      \"role\": \"Cooling\",\n      \"tags\": [\n        \"cooling\",\n        \"hvac\",\n        \"datacenter\"\n      ],\n      \"notes\": [\n        \"Liebert CRV\",\n        \"Row-based cooling\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"cooling-2\": {\n      \"shape\": \"rectangle\",\n      \"name\": \"CRAC Unit 2\",\n      \"ip\": \"192.168.200.31\",\n      \"role\": \"Cooling\",\n      \"tags\": [\n        \"cooling\",\n        \"hvac\",\n        \"datacenter\"\n      ],\n      \"notes\": [\n        \"Liebert CRV\",\n        \"N+1 redundancy\"\n      ],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"camera-a\": {\n      \"shape\": \"camera\",\n      \"name\": \"camera A\",\n      \"ip\": \"0.0.0.0\",\n      \"role\": \"\",\n      \"tags\": [],\n      \"notes\": [],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"ping\": {\n        \"enabled\": false,\n        \"protocol\": \"http\",\n        \"customUrl\": \"\",\n        \"timeout\": 3000,\n        \"status\": \"unknown\",\n        \"lastCheck\": null\n      },\n      \"locked\": false,\n      \"groupId\": null,\n      \"fovEnabled\": true,\n      \"fovRotation\": 104,\n      \"fovDistance\": 500,\n      \"fovSweep\": 60,\n      \"fovSpeed\": 10,\n      \"fovAnimate\": true\n    },\n    \"camera-a-copy\": {\n      \"shape\": \"camera\",\n      \"name\": \"camera B\",\n      \"ip\": \"0.0.0.0\",\n      \"role\": \"\",\n      \"tags\": [],\n      \"notes\": [],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"ping\": {\n        \"enabled\": false,\n        \"protocol\": \"http\",\n        \"customUrl\": \"\",\n        \"timeout\": 3000,\n        \"status\": \"unknown\",\n        \"lastCheck\": null\n      },\n      \"locked\": false,\n      \"groupId\": null,\n      \"fovEnabled\": true,\n      \"fovRotation\": 162,\n      \"fovDistance\": 500,\n      \"fovSweep\": 60,\n      \"fovSpeed\": 10,\n      \"fovAnimate\": false\n    }\n  },\n  \"edgeData\": {\n    \"list\": [\n      {\n        \"id\": \"isp1-router1\",\n        \"from\": \"isp-primary\",\n        \"to\": \"core-router-1\",\n        \"width\": 6,\n        \"color\": \"#10b981\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [\n          \"Primary WAN link\"\n        ],\n        \"fromPort\": \"Gi0/0\",\n        \"toPort\": \"Gi1/0/1\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"isp2-router2\",\n        \"from\": \"isp-secondary\",\n        \"to\": \"core-router-2\",\n        \"width\": 6,\n        \"color\": \"#10b981\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [\n          \"Backup WAN link\"\n        ],\n        \"fromPort\": \"Gi0/0\",\n        \"toPort\": \"Gi1/0/1\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"router1-router2\",\n        \"from\": \"core-router-1\",\n        \"to\": \"core-router-2\",\n        \"width\": 4,\n        \"color\": \"#f59e0b\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [\n          \"HSRP Peering\"\n        ],\n        \"fromPort\": \"Gi1/0/24\",\n        \"toPort\": \"Gi1/0/24\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"router1-fw1\",\n        \"from\": \"core-router-1\",\n        \"to\": \"fw-external-1\",\n        \"width\": 4,\n        \"color\": \"#ef4444\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"router2-fw2\",\n        \"from\": \"core-router-2\",\n        \"to\": \"fw-external-2\",\n        \"width\": 4,\n        \"color\": \"#ef4444\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"fw1-fw2\",\n        \"from\": \"fw-external-1\",\n        \"to\": \"fw-external-2\",\n        \"width\": 3,\n        \"color\": \"#f59e0b\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [\n          \"HA heartbeat\"\n        ],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"dashed\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"fw1-coresw1\",\n        \"from\": \"fw-external-1\",\n        \"to\": \"core-switch-1\",\n        \"width\": 4,\n        \"color\": \"#475569\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"fw2-coresw2\",\n        \"from\": \"fw-external-2\",\n        \"to\": \"core-switch-2\",\n        \"width\": 4,\n        \"color\": \"#475569\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"coresw1-coresw2\",\n        \"from\": \"core-switch-1\",\n        \"to\": \"core-switch-2\",\n        \"width\": 5,\n        \"color\": \"#3b82f6\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [\n          \"VPC peer-link\"\n        ],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"coresw1-fwint\",\n        \"from\": \"core-switch-1\",\n        \"to\": \"fw-internal\",\n        \"width\": 3,\n        \"color\": \"#ef4444\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"coresw2-fwint\",\n        \"from\": \"core-switch-2\",\n        \"to\": \"fw-internal\",\n        \"width\": 3,\n        \"color\": \"#ef4444\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"coresw1-racka1\",\n        \"from\": \"core-switch-1\",\n        \"to\": \"dc-rack-a1\",\n        \"width\": 4,\n        \"color\": \"#475569\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"coresw2-racka1\",\n        \"from\": \"core-switch-2\",\n        \"to\": \"dc-rack-a1\",\n        \"width\": 4,\n        \"color\": \"#475569\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"coresw1-racka2\",\n        \"from\": \"core-switch-1\",\n        \"to\": \"dc-rack-a2\",\n        \"width\": 4,\n        \"color\": \"#475569\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"coresw2-racka2\",\n        \"from\": \"core-switch-2\",\n        \"to\": \"dc-rack-a2\",\n        \"width\": 4,\n        \"color\": \"#475569\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"coresw1-rackb1\",\n        \"from\": \"core-switch-1\",\n        \"to\": \"dc-rack-b1\",\n        \"width\": 4,\n        \"color\": \"#475569\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"coresw2-rackb1\",\n        \"from\": \"core-switch-2\",\n        \"to\": \"dc-rack-b1\",\n        \"width\": 4,\n        \"color\": \"#475569\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"coresw1-rackb2\",\n        \"from\": \"core-switch-1\",\n        \"to\": \"dc-rack-b2\",\n        \"width\": 4,\n        \"color\": \"#475569\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"coresw2-rackb2\",\n        \"from\": \"core-switch-2\",\n        \"to\": \"dc-rack-b2\",\n        \"width\": 4,\n        \"color\": \"#475569\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"fw1-dmz\",\n        \"from\": \"fw-external-1\",\n        \"to\": \"dmz-rack\",\n        \"width\": 3,\n        \"color\": \"#f59e0b\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [\n          \"DMZ segment\"\n        ],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"fw2-dmz\",\n        \"from\": \"fw-external-2\",\n        \"to\": \"dmz-rack\",\n        \"width\": 3,\n        \"color\": \"#f59e0b\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [\n          \"DMZ segment\"\n        ],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"coresw1-mgmt\",\n        \"from\": \"core-switch-1\",\n        \"to\": \"mgmt-rack\",\n        \"width\": 3,\n        \"color\": \"#8b5cf6\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [\n          \"OOB management\"\n        ],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"coresw1-wlc1\",\n        \"from\": \"core-switch-1\",\n        \"to\": \"wlc-primary\",\n        \"width\": 3,\n        \"color\": \"#06b6d4\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"coresw2-wlc2\",\n        \"from\": \"core-switch-2\",\n        \"to\": \"wlc-secondary\",\n        \"width\": 3,\n        \"color\": \"#06b6d4\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\",\n        \"animate\": true\n      },\n      {\n        \"id\": \"wlc1-wlc2\",\n        \"from\": \"wlc-primary\",\n        \"to\": \"wlc-secondary\",\n        \"width\": 2,\n        \"color\": \"#f59e0b\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [\n          \"HA pair\"\n        ],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"dashed\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"wlc1-mobile-hq\",\n        \"from\": \"wlc-primary\",\n        \"to\": \"mobile-zone-hq\",\n        \"width\": 2,\n        \"color\": \"#06b6d4\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"wlc1-mobile-guest\",\n        \"from\": \"wlc-primary\",\n        \"to\": \"mobile-zone-guest\",\n        \"width\": 2,\n        \"color\": \"#06b6d4\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"wlc1-mobile-iot\",\n        \"from\": \"wlc-primary\",\n        \"to\": \"mobile-zone-iot\",\n        \"width\": 2,\n        \"color\": \"#06b6d4\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"router1-branch-ny\",\n        \"from\": \"core-router-1\",\n        \"to\": \"branch-router-ny\",\n        \"width\": 3,\n        \"color\": \"#a855f7\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [\n          \"SD-WAN tunnel\"\n        ],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"dashed\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"router1-branch-la\",\n        \"from\": \"core-router-1\",\n        \"to\": \"branch-router-la\",\n        \"width\": 3,\n        \"color\": \"#a855f7\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [\n          \"SD-WAN tunnel\"\n        ],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"dashed\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"router1-branch-chi\",\n        \"from\": \"core-router-1\",\n        \"to\": \"branch-router-chi\",\n        \"width\": 3,\n        \"color\": \"#a855f7\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [\n          \"SD-WAN tunnel\"\n        ],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"dashed\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"router1-branch-lon\",\n        \"from\": \"core-router-1\",\n        \"to\": \"branch-router-lon\",\n        \"width\": 3,\n        \"color\": \"#a855f7\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [\n          \"SD-WAN tunnel\"\n        ],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"dashed\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"router1-branch-tokyo\",\n        \"from\": \"core-router-1\",\n        \"to\": \"branch-router-tokyo\",\n        \"width\": 3,\n        \"color\": \"#a855f7\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [\n          \"SD-WAN tunnel\"\n        ],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"dashed\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"router1-aws\",\n        \"from\": \"core-router-1\",\n        \"to\": \"cloud-aws\",\n        \"width\": 3,\n        \"color\": \"#f97316\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [\n          \"Direct Connect\"\n        ],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"dashed\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"router2-azure\",\n        \"from\": \"core-router-2\",\n        \"to\": \"cloud-azure\",\n        \"width\": 3,\n        \"color\": \"#0ea5e9\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [\n          \"ExpressRoute\"\n        ],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"dashed\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"fwint-gcp\",\n        \"from\": \"fw-internal\",\n        \"to\": \"cloud-gcp\",\n        \"width\": 2,\n        \"color\": \"#22c55e\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [\n          \"VPN tunnel\"\n        ],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"dashed\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"coresw1-floor1\",\n        \"from\": \"core-switch-1\",\n        \"to\": \"dist-switch-floor1\",\n        \"width\": 3,\n        \"color\": \"#475569\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"coresw1-floor2\",\n        \"from\": \"core-switch-1\",\n        \"to\": \"dist-switch-floor2\",\n        \"width\": 3,\n        \"color\": \"#475569\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"coresw2-floor3\",\n        \"from\": \"core-switch-2\",\n        \"to\": \"dist-switch-floor3\",\n        \"width\": 3,\n        \"color\": \"#475569\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"coresw2-floor4\",\n        \"from\": \"core-switch-2\",\n        \"to\": \"dist-switch-floor4\",\n        \"width\": 3,\n        \"color\": \"#475569\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"floor1-endpoints\",\n        \"from\": \"dist-switch-floor1\",\n        \"to\": \"endpoint-1000\",\n        \"width\": 2,\n        \"color\": \"#94a3b8\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"floor1-ap1\",\n        \"from\": \"dist-switch-floor1\",\n        \"to\": \"ap-floor1-zone1\",\n        \"width\": 2,\n        \"color\": \"#06b6d4\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"floor2-ap2\",\n        \"from\": \"dist-switch-floor2\",\n        \"to\": \"ap-floor2-zone1\",\n        \"width\": 2,\n        \"color\": \"#06b6d4\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"floor3-ap3\",\n        \"from\": \"dist-switch-floor3\",\n        \"to\": \"ap-floor3-zone1\",\n        \"width\": 2,\n        \"color\": \"#06b6d4\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"floor4-ap4\",\n        \"from\": \"dist-switch-floor4\",\n        \"to\": \"ap-floor4-zone1\",\n        \"width\": 2,\n        \"color\": \"#06b6d4\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"fwint-proxy1\",\n        \"from\": \"fw-internal\",\n        \"to\": \"proxy-server-1\",\n        \"width\": 2,\n        \"color\": \"#ef4444\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"fwint-proxy2\",\n        \"from\": \"fw-internal\",\n        \"to\": \"proxy-server-2\",\n        \"width\": 2,\n        \"color\": \"#ef4444\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"fwext1-vpn\",\n        \"from\": \"fw-external-1\",\n        \"to\": \"vpn-concentrator\",\n        \"width\": 3,\n        \"color\": \"#8b5cf6\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"coresw1-nac\",\n        \"from\": \"core-switch-1\",\n        \"to\": \"nac-server\",\n        \"width\": 2,\n        \"color\": \"#f59e0b\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"coresw1-voip\",\n        \"from\": \"core-switch-1\",\n        \"to\": \"voip-cluster\",\n        \"width\": 3,\n        \"color\": \"#22c55e\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"coresw2-video\",\n        \"from\": \"core-switch-2\",\n        \"to\": \"video-conf\",\n        \"width\": 3,\n        \"color\": \"#22c55e\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"coresw1-cameras\",\n        \"from\": \"core-switch-1\",\n        \"to\": \"security-cameras\",\n        \"width\": 2,\n        \"color\": \"#94a3b8\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"fwint-dev1\",\n        \"from\": \"fw-internal\",\n        \"to\": \"dev-server-1\",\n        \"width\": 2,\n        \"color\": \"#a855f7\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"fwint-dev2\",\n        \"from\": \"fw-internal\",\n        \"to\": \"dev-server-2\",\n        \"width\": 2,\n        \"color\": \"#a855f7\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\",\n        \"animate\": true,\n        \"animationSpeed\": \"1.5\"\n      },\n      {\n        \"id\": \"fwint-test\",\n        \"from\": \"fw-internal\",\n        \"to\": \"test-environment\",\n        \"width\": 2,\n        \"color\": \"#a855f7\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"coresw1-erp\",\n        \"from\": \"core-switch-1\",\n        \"to\": \"erp-system\",\n        \"width\": 3,\n        \"color\": \"#f59e0b\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"fwext1-crm\",\n        \"from\": \"fw-external-1\",\n        \"to\": \"crm-system\",\n        \"width\": 2,\n        \"color\": \"#f59e0b\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [\n          \"Salesforce cloud\"\n        ],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"dashed\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"ups1-racka1\",\n        \"from\": \"ups-dc-1\",\n        \"to\": \"dc-rack-a1\",\n        \"width\": 2,\n        \"color\": \"#fbbf24\",\n        \"direction\": \"forward\",\n        \"type\": \"main\",\n        \"notes\": [\n          \"Power feed A\"\n        ],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"ups2-racka2\",\n        \"from\": \"ups-dc-2\",\n        \"to\": \"dc-rack-a2\",\n        \"width\": 2,\n        \"color\": \"#fbbf24\",\n        \"direction\": \"forward\",\n        \"type\": \"main\",\n        \"notes\": [\n          \"Power feed B\"\n        ],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"ups1-rackb1\",\n        \"from\": \"ups-dc-1\",\n        \"to\": \"dc-rack-b1\",\n        \"width\": 2,\n        \"color\": \"#fbbf24\",\n        \"direction\": \"forward\",\n        \"type\": \"main\",\n        \"notes\": [\n          \"Power feed A\"\n        ],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\",\n        \"animate\": true,\n        \"animationSpeed\": \"4\"\n      },\n      {\n        \"id\": \"ups2-rackb2\",\n        \"from\": \"ups-dc-2\",\n        \"to\": \"dc-rack-b2\",\n        \"width\": 2,\n        \"color\": \"#fbbf24\",\n        \"direction\": \"forward\",\n        \"type\": \"main\",\n        \"notes\": [\n          \"Power feed B\"\n        ],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"cooling1-racka1\",\n        \"from\": \"cooling-1\",\n        \"to\": \"dc-rack-a1\",\n        \"width\": 2,\n        \"color\": \"#38bdf8\",\n        \"direction\": \"forward\",\n        \"type\": \"main\",\n        \"notes\": [\n          \"Cooling zone\"\n        ],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"dotted\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"cooling2-rackb1\",\n        \"from\": \"cooling-2\",\n        \"to\": \"dc-rack-b1\",\n        \"width\": 2,\n        \"color\": \"#38bdf8\",\n        \"direction\": \"forward\",\n        \"type\": \"main\",\n        \"notes\": [\n          \"Cooling zone\"\n        ],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"dotted\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1,\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"custom-1765237881452\",\n        \"type\": \"custom\",\n        \"color\": \"#c800ff\",\n        \"width\": 4,\n        \"lineStyle\": \"solid\",\n        \"direction\": \"forward\",\n        \"points\": [\n          {\n            \"x\": 3492.3994140625,\n            \"y\": 1526.9556884765625\n          },\n          {\n            \"x\": 3500.609619140625,\n            \"y\": 1830.7386474609375\n          },\n          {\n            \"x\": 3303.561279296875,\n            \"y\": 1732.2144775390625\n          }\n        ],\n        \"notes\": [],\n        \"routing\": \"orthogonal\"\n      },\n      {\n        \"id\": \"custom-1765239355462\",\n        \"type\": \"custom\",\n        \"color\": \"#f97316\",\n        \"width\": 4,\n        \"lineStyle\": \"solid\",\n        \"direction\": \"forward\",\n        \"points\": [\n          {\n            \"x\": 2467.182861328125,\n            \"y\": 156.12173461914062\n          },\n          {\n            \"x\": 2146.36376953125,\n            \"y\": 146.32574462890625\n          },\n          {\n            \"x\": 2305.548828125,\n            \"y\": 244.28573608398438\n          }\n        ],\n        \"notes\": [],\n        \"routing\": \"orthogonal\"\n      }\n    ]\n  },\n  \"rectData\": {\n    \"list\": [\n      {\n        \"id\": \"rect-1765237540610\",\n        \"x\": 2879.214599609375,\n        \"y\": 159.71981811523438,\n        \"width\": 992.196044921875,\n        \"height\": 538.8650817871094,\n        \"color\": \"#f97316\",\n        \"style\": \"filled\",\n        \"lineStyle\": \"solid\",\n        \"notes\": []\n      },\n      {\n        \"id\": \"rect-1765237681216\",\n        \"x\": 448.3926696777344,\n        \"y\": 1671.651123046875,\n        \"width\": 916.3436584472656,\n        \"height\": 924.27734375,\n        \"color\": \"#c800ff\",\n        \"style\": \"outlined\",\n        \"lineStyle\": \"solid\",\n        \"notes\": []\n      },\n      {\n        \"id\": \"rect-1766437913740\",\n        \"x\": 904.5889892578125,\n        \"y\": 115.40318298339844,\n        \"width\": 110.93878173828125,\n        \"height\": 919.6242218017578,\n        \"color\": \"#5215f9\",\n        \"style\": \"filled\",\n        \"lineStyle\": \"wall\",\n        \"notes\": [],\n        \"borderWidth\": 13\n      },\n      {\n        \"id\": \"rect-1766437935414\",\n        \"x\": 130.93685150146484,\n        \"y\": 1072.3624877929688,\n        \"width\": 872.9131851196289,\n        \"height\": 99.260986328125,\n        \"color\": \"#5215f9\",\n        \"style\": \"filled\",\n        \"lineStyle\": \"wall\",\n        \"notes\": [],\n        \"borderWidth\": 13\n      }\n    ]\n  },\n  \"textData\": {\n    \"list\": [\n      {\n        \"id\": \"text-1765237828167\",\n        \"x\": 3411.458740234375,\n        \"y\": 1390.00439453125,\n        \"content\": \"Double click on desktop\\nor long press on mobile\\nto enter rack canvas view\",\n        \"fontSize\": 46,\n        \"color\": \"#e2e8f0\",\n        \"fontWeight\": \"bold\",\n        \"fontStyle\": \"italic\",\n        \"textAlign\": \"middle\",\n        \"textDecoration\": \"none\",\n        \"bgColor\": \"#000000\",\n        \"bgEnabled\": false,\n        \"opacity\": 1\n      },\n      {\n        \"id\": \"text-1765239331126\",\n        \"x\": 2454.5615234375,\n        \"y\": 160.73322105407715,\n        \"content\": \"Google is live!\",\n        \"fontSize\": 56,\n        \"color\": \"#e2e8f0\",\n        \"fontWeight\": \"bold\",\n        \"fontStyle\": \"normal\",\n        \"textAlign\": \"start\",\n        \"textDecoration\": \"none\",\n        \"bgColor\": \"#000000\",\n        \"bgEnabled\": false,\n        \"opacity\": 1\n      },\n      {\n        \"id\": \"text-1766446595277\",\n        \"x\": 654.3878479003906,\n        \"y\": 1367.7945556640625,\n        \"content\": \"SITE A\",\n        \"fontSize\": 101,\n        \"color\": \"#e2e8f0\",\n        \"fontWeight\": \"normal\",\n        \"fontStyle\": \"normal\",\n        \"textAlign\": \"start\",\n        \"textDecoration\": \"none\",\n        \"bgColor\": \"#000000\",\n        \"bgEnabled\": false,\n        \"opacity\": 1\n      },\n      {\n        \"id\": \"text-1766446610211\",\n        \"x\": 180.63662719726562,\n        \"y\": 1128.822998046875,\n        \"content\": \"SITE A\",\n        \"fontSize\": 101,\n        \"color\": \"#e2e8f0\",\n        \"fontWeight\": \"normal\",\n        \"fontStyle\": \"normal\",\n        \"textAlign\": \"start\",\n        \"textDecoration\": \"none\",\n        \"bgColor\": \"#000000\",\n        \"bgEnabled\": false,\n        \"opacity\": 1\n      },\n      {\n        \"id\": \"text-1766453024797\",\n        \"x\": 968.6458740234375,\n        \"y\": 1028.6621398925781,\n        \"content\": \"SITE A\",\n        \"fontSize\": 101,\n        \"color\": \"#e2e8f0\",\n        \"fontWeight\": \"normal\",\n        \"fontStyle\": \"normal\",\n        \"textAlign\": \"start\",\n        \"textDecoration\": \"none\",\n        \"bgColor\": \"#000000\",\n        \"bgEnabled\": false,\n        \"opacity\": 1,\n        \"rotation\": -89,\n        \"_dragStartX\": 972.46826171875,\n        \"_dragStartY\": 1009.5499572753906\n      },\n      {\n        \"id\": \"text-1766453070975\",\n        \"x\": 613.1589965820312,\n        \"y\": 1139.512939453125,\n        \"content\": \"SITE A\",\n        \"fontSize\": 101,\n        \"color\": \"#e2e8f0\",\n        \"fontWeight\": \"normal\",\n        \"fontStyle\": \"normal\",\n        \"textAlign\": \"start\",\n        \"textDecoration\": \"none\",\n        \"bgColor\": \"#000000\",\n        \"bgEnabled\": false,\n        \"opacity\": 1\n      },\n      {\n        \"id\": \"text-1766453072857\",\n        \"x\": 968.64599609375,\n        \"y\": 474.40818786621094,\n        \"content\": \"SITE A\",\n        \"fontSize\": 101,\n        \"color\": \"#e2e8f0\",\n        \"fontWeight\": \"normal\",\n        \"fontStyle\": \"normal\",\n        \"textAlign\": \"start\",\n        \"textDecoration\": \"none\",\n        \"bgColor\": \"#000000\",\n        \"bgEnabled\": false,\n        \"opacity\": 1,\n        \"rotation\": 269,\n        \"_dragStartX\": 1480.85302734375,\n        \"_dragStartY\": 822.2503356933594\n      }\n    ]\n  },\n  \"edgeLegend\": {\n    \"#10b981\": \"Trusted Lan\",\n    \"#f59e0b\": \"Secure Lan\",\n    \"#ef4444\": \"DMZ\",\n    \"#475569\": \"Main ISP\",\n    \"#3b82f6\": \"Alternate ISP\",\n    \"#8b5cf6\": \"you can edit me too\",\n    \"#06b6d4\": \"you can edit me too\",\n    \"#a855f7\": \"you can edit me too\",\n    \"#f97316\": \"you can edit me too\",\n    \"#0ea5e9\": \"you can edit me too\",\n    \"#22c55e\": \"you can edit me too\",\n    \"#94a3b8\": \"you can edit me too\",\n    \"#fbbf24\": \"you can edit me too\",\n    \"#38bdf8\": \"you can edit me too\",\n    \"#c800ff\": \"you can edit me too\"\n  },\n  \"nodePositions\": {\n    \"core-router-1\": {\n      \"x\": 3720.166015625,\n      \"y\": 245.9932403564453\n    },\n    \"core-router-2\": {\n      \"x\": 2499.883407638303,\n      \"y\": 329.99503430389154\n    },\n    \"fw-external-1\": {\n      \"x\": 3221.7385182723783,\n      \"y\": 1016.1364499992887\n    },\n    \"fw-external-2\": {\n      \"x\": 1915.5213706410505,\n      \"y\": 224.43528858865443\n    },\n    \"fw-internal\": {\n      \"x\": 1746.9168185079352,\n      \"y\": 477.5300527221864\n    },\n    \"core-switch-1\": {\n      \"x\": 449.39860669455675,\n      \"y\": 384.4578707617695\n    },\n    \"core-switch-2\": {\n      \"x\": 761.1664921394672,\n      \"y\": 180.89283910873155\n    },\n    \"dc-rack-a1\": {\n      \"x\": 783.7017241128451,\n      \"y\": 647.4086870405963\n    },\n    \"dc-rack-a2\": {\n      \"x\": 209.25701628255229,\n      \"y\": 228.01593190351014\n    },\n    \"dc-rack-b1\": {\n      \"x\": 3184.3186625759854,\n      \"y\": 1627.4495531027196\n    },\n    \"dc-rack-b2\": {\n      \"x\": 245.37065918741246,\n      \"y\": 499.6191264194081\n    },\n    \"dmz-rack\": {\n      \"x\": 2176.4105289561007,\n      \"y\": 610.8312056412005\n    },\n    \"mgmt-rack\": {\n      \"x\": 1601.2987201807314,\n      \"y\": 1281.4753424975324\n    },\n    \"esxi-host-01\": {\n      \"x\": 2162.2166789540615,\n      \"y\": 2608.110619289529\n    },\n    \"esxi-host-02\": {\n      \"x\": 2205.94717202368,\n      \"y\": 2689.67539624076\n    },\n    \"esxi-host-03\": {\n      \"x\": 2154.6015436939074,\n      \"y\": 2771.203009774913\n    },\n    \"esxi-host-04\": {\n      \"x\": 2195.986926025096,\n      \"y\": 2845\n    },\n    \"tor-switch-a1\": {\n      \"x\": 2146.8943639962963,\n      \"y\": 2845\n    },\n    \"esxi-host-05\": {\n      \"x\": 2185.9099961569727,\n      \"y\": 2845\n    },\n    \"esxi-host-06\": {\n      \"x\": 2139.099728450725,\n      \"y\": 2845\n    },\n    \"esxi-host-07\": {\n      \"x\": 2175.7223818764883,\n      \"y\": 2845\n    },\n    \"esxi-host-08\": {\n      \"x\": 2131.2222777148922,\n      \"y\": 2845\n    },\n    \"tor-switch-a2\": {\n      \"x\": 2165.4301485385085,\n      \"y\": 2845\n    },\n    \"san-primary\": {\n      \"x\": 2123.2667017518106,\n      \"y\": 2845\n    },\n    \"san-secondary\": {\n      \"x\": 2155.0394237844876,\n      \"y\": 2845\n    },\n    \"fc-switch-1\": {\n      \"x\": 2115.2377370375634,\n      \"y\": 2845\n    },\n    \"fc-switch-2\": {\n      \"x\": 2144.5563938942755,\n      \"y\": 2845\n    },\n    \"backup-server-1\": {\n      \"x\": 2107.1401637413705,\n      \"y\": 2845\n    },\n    \"backup-server-2\": {\n      \"x\": 2133.987300103025,\n      \"y\": 2845\n    },\n    \"tape-library\": {\n      \"x\": 2098.9788028796397,\n      \"y\": 2845\n    },\n    \"tor-switch-b1\": {\n      \"x\": 2123.338434885373,\n      \"y\": 2845\n    },\n    \"tor-switch-b2\": {\n      \"x\": 2090.7585134456995,\n      \"y\": 2845\n    },\n    \"web-server-1\": {\n      \"x\": 2112.6161382091163,\n      \"y\": 2845\n    },\n    \"web-server-2\": {\n      \"x\": 2082.484189516922,\n      \"y\": 2845\n    },\n    \"waf-1\": {\n      \"x\": 2101.826793760617,\n      \"y\": 2845\n    },\n    \"load-balancer-dmz\": {\n      \"x\": 2074.1607573409574,\n      \"y\": 2845\n    },\n    \"mail-gateway\": {\n      \"x\": 2090.97682514417,\n      \"y\": 2845\n    },\n    \"dns-external-1\": {\n      \"x\": 2065.7931724028163,\n      \"y\": 2845\n    },\n    \"dns-external-2\": {\n      \"x\": 2080.0726920576153,\n      \"y\": 2845\n    },\n    \"vcenter\": {\n      \"x\": 2057.3864164745437,\n      \"y\": 2845\n    },\n    \"nsx-manager\": {\n      \"x\": 2069.1208864464534,\n      \"y\": 2845\n    },\n    \"siem-server\": {\n      \"x\": 2048.945494649244,\n      \"y\": 2845\n    },\n    \"nms-server\": {\n      \"x\": 2058.1279286387635,\n      \"y\": 2845\n    },\n    \"jump-server\": {\n      \"x\": 2040.4754323612206,\n      \"y\": 2845\n    },\n    \"ipam-server\": {\n      \"x\": 2047.1003634632284,\n      \"y\": 2845\n    },\n    \"wlc-primary\": {\n      \"x\": 1575.9723612611924,\n      \"y\": 2306.135986328125\n    },\n    \"wlc-secondary\": {\n      \"x\": 1468.1361870166274,\n      \"y\": 1563.733642578125\n    },\n    \"mobile-zone-hq\": {\n      \"x\": 2354.901177346808,\n      \"y\": 2806.0078125\n    },\n    \"mobile-zone-guest\": {\n      \"x\": 2307.6605605284435,\n      \"y\": 2611.047119140625\n    },\n    \"mobile-zone-iot\": {\n      \"x\": 2229.397686389302,\n      \"y\": 2299.110107421875\n    },\n    \"branch-router-ny\": {\n      \"x\": 3151.903101363964,\n      \"y\": 633.6580810546875\n    },\n    \"branch-router-la\": {\n      \"x\": 3083.8876194705945,\n      \"y\": 506.90625\n    },\n    \"branch-router-chi\": {\n      \"x\": 3355.02409980103,\n      \"y\": 393.1805725097656\n    },\n    \"branch-router-lon\": {\n      \"x\": 3113.609823320121,\n      \"y\": 260.4093322753906\n    },\n    \"branch-router-tokyo\": {\n      \"x\": 3699.3234994733834,\n      \"y\": 471.4241027832031\n    },\n    \"cloud-aws\": {\n      \"x\": 3436.528122523513,\n      \"y\": 545.9614868164062\n    },\n    \"cloud-azure\": {\n      \"x\": 2592.566210818907,\n      \"y\": 2724.068115234375\n    },\n    \"cloud-gcp\": {\n      \"x\": 2827.3183770424234,\n      \"y\": 2731.397216796875\n    },\n    \"isp-primary\": {\n      \"x\": 3712.192068081962,\n      \"y\": 615.64990234375\n    },\n    \"isp-secondary\": {\n      \"x\": 2702.3789772348055,\n      \"y\": 467.890869140625\n    },\n    \"dc-internal-1\": {\n      \"x\": 1958.4243458877936,\n      \"y\": 2845\n    },\n    \"dc-internal-2\": {\n      \"x\": 1963.768951182132,\n      \"y\": 2845\n    },\n    \"app-server-1\": {\n      \"x\": 1947.3819379304134,\n      \"y\": 2845\n    },\n    \"app-server-2\": {\n      \"x\": 1955.2862087394126,\n      \"y\": 2845\n    },\n    \"db-server-1\": {\n      \"x\": 1936.3708569559828,\n      \"y\": 2845\n    },\n    \"db-server-2\": {\n      \"x\": 1946.8300873488822,\n      \"y\": 2845\n    },\n    \"k8s-master-1\": {\n      \"x\": 1925.397658583093,\n      \"y\": 2845\n    },\n    \"k8s-master-2\": {\n      \"x\": 1938.405621494142,\n      \"y\": 2845\n    },\n    \"k8s-master-3\": {\n      \"x\": 1914.4688758763386,\n      \"y\": 2845\n    },\n    \"k8s-worker-1\": {\n      \"x\": 1930.017826812177,\n      \"y\": 2845\n    },\n    \"k8s-worker-2\": {\n      \"x\": 1903.5910154567553,\n      \"y\": 2845\n    },\n    \"k8s-worker-3\": {\n      \"x\": 1921.6716971072178,\n      \"y\": 2845\n    },\n    \"k8s-worker-4\": {\n      \"x\": 1892.7705536280016,\n      \"y\": 2845\n    },\n    \"proxy-server-1\": {\n      \"x\": 1806.1152433697903,\n      \"y\": 653.7529296875\n    },\n    \"proxy-server-2\": {\n      \"x\": 2937.4207928721535,\n      \"y\": 2628.7880859375\n    },\n    \"vpn-concentrator\": {\n      \"x\": 3642.252088474593,\n      \"y\": 946.7255249023438\n    },\n    \"nac-server\": {\n      \"x\": 1153.2626148502184,\n      \"y\": 1172.1895751953125\n    },\n    \"print-server\": {\n      \"x\": 1896.9328460745962,\n      \"y\": 2845\n    },\n    \"file-server\": {\n      \"x\": 1860.7177871362182,\n      \"y\": 2845\n    },\n    \"ca-server\": {\n      \"x\": 1888.8027739274805,\n      \"y\": 2845\n    },\n    \"sccm-server\": {\n      \"x\": 1850.1909418511675,\n      \"y\": 2845\n    },\n    \"voip-cluster\": {\n      \"x\": 1777.038465328039,\n      \"y\": 1616.8961181640625\n    },\n    \"video-conf\": {\n      \"x\": 1993.8373941679588,\n      \"y\": 2244.936309814453\n    },\n    \"security-cameras\": {\n      \"x\": 1674.413336949044,\n      \"y\": 2046.0380859375\n    },\n    \"nvr-cluster\": {\n      \"x\": 1829.4110389706402,\n      \"y\": 2845\n    },\n    \"dev-server-1\": {\n      \"x\": 2800.5894350649614,\n      \"y\": 1175.623291015625\n    },\n    \"dev-server-2\": {\n      \"x\": 1945.0822182484326,\n      \"y\": 1164.5184783935547\n    },\n    \"test-environment\": {\n      \"x\": 2566.9100352578575,\n      \"y\": 885.2827758789062\n    },\n    \"erp-system\": {\n      \"x\": 789.9880103985649,\n      \"y\": 473.7113342285156\n    },\n    \"crm-system\": {\n      \"x\": 3514.6003232048542,\n      \"y\": 1137.7720947265625\n    },\n    \"endpoint-1000\": {\n      \"x\": 991.6812012057328,\n      \"y\": 2284.42236328125\n    },\n    \"dist-switch-floor1\": {\n      \"x\": 654.2091033261356,\n      \"y\": 2020.0086669921875\n    },\n    \"dist-switch-floor2\": {\n      \"x\": 853.8845527112826,\n      \"y\": 1843.2872314453125\n    },\n    \"dist-switch-floor3\": {\n      \"x\": 1899.4353951584517,\n      \"y\": 1456.5068359375\n    },\n    \"dist-switch-floor4\": {\n      \"x\": 488.5289313756234,\n      \"y\": 181.47256469726562\n    },\n    \"ap-floor1-zone1\": {\n      \"x\": 1140.16846970184,\n      \"y\": 2070.2916259765625\n    },\n    \"ap-floor2-zone1\": {\n      \"x\": 688.1952143592268,\n      \"y\": 2384.4775390625\n    },\n    \"ap-floor3-zone1\": {\n      \"x\": 2145.3803027919676,\n      \"y\": 1890.2816162109375\n    },\n    \"ap-floor4-zone1\": {\n      \"x\": 517.646146409649,\n      \"y\": 565.59716796875\n    },\n    \"ups-dc-1\": {\n      \"x\": 771.1406786539856,\n      \"y\": 295.9266662597656\n    },\n    \"ups-dc-2\": {\n      \"x\": 216.2410855890687,\n      \"y\": 330.3345947265625\n    },\n    \"pdu-rack-a1\": {\n      \"x\": 1804.774444371901,\n      \"y\": 2845\n    },\n    \"pdu-rack-a2\": {\n      \"x\": 1741.6184034693686,\n      \"y\": 2845\n    },\n    \"cooling-1\": {\n      \"x\": 245.7080801919958,\n      \"y\": 626.1914672851562\n    },\n    \"cooling-2\": {\n      \"x\": 1603.293611085831,\n      \"y\": 981.0621185302734\n    },\n    \"camera-a\": {\n      \"x\": 166.57075412676295,\n      \"y\": 145\n    },\n    \"camera-a-copy\": {\n      \"x\": 1040.653076171875,\n      \"y\": 738.42822265625\n    }\n  },\n  \"nodeSizes\": {\n    \"isp-secondary\": 139,\n    \"test-environment\": 148,\n    \"dev-server-1\": 128,\n    \"core-router-2\": 120,\n    \"camera-a\": 45,\n    \"camera-a-copy\": 45\n  },\n  \"nodeStyles\": {\n    \"dc-rack-b2\": {\n      \"all\": {\n        \"circleColor\": \"#ff0000\"\n      }\n    },\n    \"dc-rack-a1\": {\n      \"all\": {\n        \"circleColor\": \"#ff0000\"\n      }\n    },\n    \"dc-rack-b1\": {\n      \"all\": {\n        \"circleColor\": \"#ff0000\",\n        \"titleSize\": 59\n      }\n    },\n    \"isp-secondary\": {\n      \"all\": {\n        \"icon\": {\n          \"library\": \"selfhst\",\n          \"name\": \"alist\"\n        }\n      }\n    },\n    \"core-router-2\": {\n      \"all\": {\n        \"icon\": {\n          \"library\": \"selfhst\",\n          \"name\": \"actual-budget\"\n        },\n        \"pingOffsetX\": -15,\n        \"pingOffsetY\": -38\n      }\n    },\n    \"fw-external-1\": {\n      \"all\": {\n        \"icon\": {\n          \"library\": \"selfhst\",\n          \"name\": \"anonaddy\"\n        }\n      }\n    },\n    \"cloud-aws\": {\n      \"all\": {\n        \"icon\": {\n          \"library\": \"selfhst\",\n          \"name\": \"ansible\"\n        }\n      }\n    },\n    \"isp-primary\": {\n      \"all\": {\n        \"icon\": {\n          \"library\": \"selfhst\",\n          \"name\": \"wikidocs\"\n        }\n      }\n    },\n    \"branch-router-tokyo\": {\n      \"all\": {\n        \"icon\": {\n          \"library\": \"selfhst\",\n          \"name\": \"adguard-home\"\n        }\n      }\n    },\n    \"core-router-1\": {\n      \"all\": {\n        \"icon\": {\n          \"library\": \"selfhst\",\n          \"name\": \"borg\"\n        }\n      }\n    },\n    \"test-environment\": {\n      \"all\": {\n        \"icon\": {\n          \"library\": \"simple\",\n          \"name\": \"apple\"\n        }\n      }\n    },\n    \"dev-server-1\": {\n      \"all\": {\n        \"icon\": {\n          \"library\": \"simple\",\n          \"name\": \"amazonwebservices\"\n        }\n      }\n    }\n  },\n  \"iconCache\": {\n    \"selfhst-borg\": \"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" xml:space=\\\"preserve\\\" viewBox=\\\"0 0 512 512\\\"><path d=\\\"M331 102.2H204.5V0h197.9L505 102.2v104.2l-51.3 51.3L505 309v100.7L402.4 512H204.5V409.8H331V300.6H204.5v-89.1H331zM6.9 0h170.5v512H6.9z\\\" style=\\\"fill:#0d0\\\"/></svg>\",\n    \"selfhst-actual-budget\": \"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" xml:space=\\\"preserve\\\" viewBox=\\\"0 0 512 512\\\"><path d=\\\"M378.5 512h-245C59.8 512 0 452.2 0 378.5v-245C0 59.8 59.8 0 133.5 0h245C452.2 0 512 59.8 512 133.5v245c0 73.7-59.8 133.5-133.5 133.5\\\" style=\\\"fill:#805ad5\\\"/><path d=\\\"m407.7 384.1-33.2-68.2 26.9-10.4c1.7-.6 2.6-2.6 1.9-4.3l-7.8-20.2c-.6-1.7-2.6-2.6-4.3-1.9L362 290.3 268.8 99.2c-.6-1.2-1.7-1.9-3-1.9h-6.3c-1.3 0-2.4.7-3 1.9l-95 201.6-55.2 20.3c-1.8.6-2.6 2.6-2 4.3l7.4 20.3c.6 1.8 2.6 2.6 4.3 2l28.4-10.4-32.2 68.3c-.3.8-.4 1.7 0 2.5l1.6 4.3v.1c.6 1.7 2.6 2.6 4.3 1.9l229.8-88.3 34.3 70.4c.8 1.7 2.8 2.4 4.5 1.6l19.5-9.5c1.6-.8 2.3-2.8 1.5-4.5M263 151.8l46.1 94.9L199.4 287zM161.5 367.4l20.7-44 139.4-51.1 13.7 28.3z\\\" style=\\\"fill:#fff\\\"/></svg>\",\n    \"selfhst-anonaddy\": \"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" xml:space=\\\"preserve\\\" viewBox=\\\"0 0 512 512\\\"><linearGradient id=\\\"anonaddy_svg__a\\\" x1=\\\"44.378\\\" x2=\\\"447.022\\\" y1=\\\"386.378\\\" y2=\\\"789.022\\\" gradientTransform=\\\"translate(0 -278)\\\" gradientUnits=\\\"userSpaceOnUse\\\"><stop offset=\\\"0\\\" style=\\\"stop-color:#3ae7e1\\\"/><stop offset=\\\".948\\\" style=\\\"stop-color:#1993d2\\\"/></linearGradient><path d=\\\"M320 224H192c-17.7 0-32 14.3-32 32v128c0 17.7 14.3 32 32 32h128c17.7 0 32-14.3 32-32V256c0-17.7-14.3-32-32-32m-32 128h-64v-64h64z\\\" style=\\\"fill:url(#anonaddy_svg__a)\\\"/><linearGradient id=\\\"anonaddy_svg__b\\\" x1=\\\"44.337\\\" x2=\\\"446.981\\\" y1=\\\"386.418\\\" y2=\\\"789.062\\\" gradientTransform=\\\"translate(0 -278)\\\" gradientUnits=\\\"userSpaceOnUse\\\"><stop offset=\\\"0\\\" style=\\\"stop-color:#3ae7e1\\\"/><stop offset=\\\".948\\\" style=\\\"stop-color:#1993d2\\\"/></linearGradient><path d=\\\"M384 384c0 5.5-.8 10.9-2.1 16 39.4-21.8 66.1-63.8 66.1-112v-32c0-47.4-25.8-88.6-64-110.7-18.8-10.9-40.7-17.3-64-17.3H192c-23.3 0-45.2 6.4-64 17.3-38.2 22.1-64 63.3-64 110.7v128c0 70.7 57.3 128 128 128h128c0-35.3-28.7-64-64-64h-64c-35.3 0-64-28.7-64-64V256c0-35.3 28.7-64 64-64h128c35.3 0 64 28.7 64 64z\\\" style=\\\"fill:url(#anonaddy_svg__b)\\\"/><path d=\\\"M384 288c0 23.7-12.9 44.3-32 55.4V384c0 17.7-14.3 32-32 32 22.5 0 43.5-5.8 61.9-16 1.3-5.1 2.1-10.5 2.1-16zM128 145.3c18.8-10.9 40.7-17.3 64-17.3 0-11.7 3.2-22.6 8.7-32H192c-21.4 0-42.7 4.4-62.4 12.8-1 6.3-1.6 12.7-1.6 19.2zM311.3 96c5.5 9.4 8.7 20.3 8.7 32 23.3 0 45.2 6.4 64 17.3V128c0-6.5-.6-12.9-1.6-19.2-19.7-8.4-41-12.8-62.4-12.8z\\\" style=\\\"fill:#2d7aae\\\"/><linearGradient id=\\\"anonaddy_svg__c\\\" x1=\\\"158.782\\\" x2=\\\"561.426\\\" y1=\\\"271.974\\\" y2=\\\"674.618\\\" gradientTransform=\\\"translate(0 -278)\\\" gradientUnits=\\\"userSpaceOnUse\\\"><stop offset=\\\"0\\\" style=\\\"stop-color:#3ae7e1\\\"/><stop offset=\\\".948\\\" style=\\\"stop-color:#1993d2\\\"/></linearGradient><path d=\\\"M200.7 96c11.1-19.1 31.7-32 55.3-32s44.3 12.9 55.3 32h8.7c21.4 0 42.7 4.4 62.4 12.8C373.1 47.3 320 0 256 0S138.9 47.3 129.6 108.8c19.7-8.4 41-12.8 62.4-12.8z\\\" style=\\\"fill:url(#anonaddy_svg__c)\\\"/></svg>\",\n    \"selfhst-adguard-home\": \"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" xml:space=\\\"preserve\\\" viewBox=\\\"0 0 512 512\\\"><path d=\\\"M505.8 58.8C428.2 18.4 334.1 0 256 0h-.3v511.8c-21.5-12.7-41.1-26-59.1-39.6 18 13.7 37.8 27 59.4 39.8C506.8 363.4 505.8 146 505.8 58.8\\\" style=\\\"fill-rule:evenodd;clip-rule:evenodd;fill:#68bc71\\\"/><path d=\\\"M255.7 511.8C5.2 363.3 6.2 146 6.2 58.8 83.7 18.4 177.7 0 255.7 0z\\\" style=\\\"fill-rule:evenodd;clip-rule:evenodd;fill:#67b279\\\"/><path d=\\\"m246.9 341.6 151-203.6c-11.1-8.9-20.8-2.6-26.1 2.2h-.2l-125.9 131-47.4-57.1c-22.6-26.2-53.4-6.2-60.6-.9z\\\" style=\\\"fill-rule:evenodd;clip-rule:evenodd;fill:#fff\\\"/></svg>\",\n    \"selfhst-ansible\": \"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" xml:space=\\\"preserve\\\" viewBox=\\\"0 0 512 512\\\"><path d=\\\"M512 256c0 141.4-114.6 256-256 256S0 397.4 0 256 114.6 0 256 0s256 114.6 256 256\\\" style=\\\"fill:#c32214\\\"/><path d=\\\"m260.3 156.4 66.2 163.5-100.1-78.8zM378 357.6 276.1 112.3c-2.9-7.1-8.7-10.8-15.8-10.8s-13.3 3.7-16.2 10.8l-111.8 269h38.3l44.3-110.9L347 377.1c5.3 4.3 9.1 6.2 14.1 6.2 10 0 18.7-7.5 18.7-18.3-.1-1.7-.7-4.4-1.8-7.4\\\" style=\\\"fill:#fff\\\"/></svg>\",\n    \"selfhst-wikidocs\": \"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" xml:space=\\\"preserve\\\" viewBox=\\\"0 0 512 512\\\"><path d=\\\"M488.8 72.1h-90.7c-8.4 0-15.1 6.7-15.1 15.1v206.4c0 8.4-6.7 15.1-15.1 15.1h-45.3c-8.4 0-15.1-6.7-15.1-15.1V87.2c0-8.4-6.7-15.1-15.1-15.1h-72.5c-8.4 0-15.1 6.7-15.1 15.1v206.4c0 8.4-6.7 15.1-15.1 15.1h-45.3c-8.4 0-15.1-6.7-15.1-15.1V87.2c0-8.4-6.7-15.1-15.1-15.1H23.4c-8.4 0-15.1 6.7-15.1 15.1v257.2c0 50.1 38.6 91.2 87.5 95.3v.3h392.9c8.4 0 15.1-6.7 15.1-15.1V87.2c0-8.4-6.7-15.1-15-15.1\\\" style=\\\"fill:#4caf50\\\"/><path d=\\\"M488.6 448.2H95.7c-1.1 0-2.1-.2-3.1-.6-24.9-2.7-48-14.2-65.1-32.8C9.8 395.6 0 370.5 0 344.3V87.2c0-12.9 10.5-23.4 23.4-23.4h90.9c12.9 0 23.4 10.5 23.4 23.4v206.4c0 3.8 3 6.8 6.8 6.8h45.3c3.8 0 6.8-3 6.8-6.8V87.2c0-12.9 10.5-23.4 23.4-23.4h72.5c12.9 0 23.4 10.5 23.4 23.4v206.4c0 3.8 3 6.8 6.8 6.8H368c3.8 0 6.8-3 6.8-6.8V87.2c0-12.9 10.5-23.4 23.4-23.4h90.7c6.3 0 12.2 2.5 16.6 6.9s6.7 10.3 6.6 16.6v337.6c-.1 12.8-10.6 23.3-23.5 23.3M98.1 431.7h390.6c3.8 0 6.8-3 6.8-6.8V87.2c0-1.9-.6-3.6-1.9-4.8-1.3-1.3-3-2-4.8-2h-90.7c-3.8 0-6.8 3-6.8 6.8v206.4c0 12.9-10.5 23.4-23.4 23.4h-45.3c-12.9 0-23.4-10.5-23.4-23.4V87.2c0-3.8-3-6.8-6.8-6.8h-72.5c-3.8 0-6.8 3-6.8 6.8v206.4c0 12.9-10.5 23.4-23.4 23.4h-45.3c-12.9 0-23.4-10.5-23.4-23.4V87.2c0-3.8-3-6.8-6.8-6.8H23.4c-3.8 0-6.8 3-6.8 6.8v257.2c0 45.1 35.1 83.3 79.9 87.1.5-.1 1 0 1.6.2\\\" style=\\\"fill:#fff\\\"/></svg>\",\n    \"selfhst-alist\": \"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" xml:space=\\\"preserve\\\" viewBox=\\\"0 0 512 512\\\"><path d=\\\"M260.5 30.8c5.4-.6 11 .6 15.5 3.7 6.8 4.4 11.6 11.1 15.7 17.9 18.4 30.9 37 61.7 55.4 92.6 24.5 41.3 49.1 82.7 73.6 124q25.35 42.9 51 85.8c11.2 19.1 22.8 37.9 33.5 57.3 4.6 8.1 7.2 17.5 6.8 26.8-.3 7.2-1.6 14.7-5.4 20.9-5.3 8.8-14.3 15.1-23.9 18.3-5.2 1.9-10.6 3.1-16.1 3.1H286.3c-5.2-.1-10.4-.7-15.3-2.6-3.5-1.5-7-3.4-9.8-6.1-4.7-4.7-7.8-11.3-7-18.1.4-4.7 1.6-9.4 3.5-13.7 1.9-4.4 4.5-8.5 6.8-12.7 10.1-17.9 20.6-35.6 31.5-53 3.5-5.5 6.6-11.4 11.4-15.8 5.2-4.7 11.6-8.4 18.7-8.9 5.7-.2 11.3 1.7 16.1 4.8 5.4 3.1 9.6 8.4 11 14.5 2 7.7-.2 15.8-3.4 22.9-3.7 8-8.9 15.3-11.7 23.7-.5 1.9-1.2 4.3.2 6 1.4 1.4 3.5 1.9 5.3 2.4 5.5 1.2 11.1.9 16.6.9h61.3c4-.1 8.1.1 12.1-.1 2.9-.3 6.1-.8 8.4-2.9 1.5-1.3 1.7-3.6 1.1-5.4-1.2-4-3.4-7.7-5.5-11.3C383.2 312.9 329 219.7 274 126.9c-1.8-3-3.3-6.2-5.9-8.6-1.3-1.2-3.2-1.9-4.9-1.4-1.5.5-2.5 1.8-3.5 3-2.4 3.1-4.6 6.4-6.6 9.8-9.5 15.4-18.6 31.1-27.9 46.6-53.6 90.1-107.1 180.3-160.7 270.4-3.3 6.1-6.9 12.1-10.7 17.9-3.4 5-7.5 9.6-12.8 12.6-6.2 3.6-13.9 5-20.9 3-5.1-1.6-10-4.1-13.7-8.1-4.6-4.6-6.7-11.3-6.4-17.7.2-6.7 2.5-13.3 5.9-19.1C38.4 380.1 71.1 325 104 269.9c16.9-28.5 33.7-57.1 50.7-85.6 17.2-28.8 34.4-57.7 51.5-86.5 9.5-16.3 19.2-32.5 29.2-48.5 3.1-5.1 7.1-9.6 11.9-13.1 4-2.7 8.4-5 13.2-5.4\\\" style=\\\"fill:#70c6be\\\"/><path d=\\\"M257.8 244.3c8.1-1.3 16.7.6 23.4 5.4 5.1 3.9 8.6 9.8 9.7 16.2.7 4.9.2 9.9-1.6 14.5-1.4 4-3.3 7.7-5.5 11.3-31.2 52.5-62.5 105-93.7 157.5-4 6.6-7.6 13.4-12.2 19.6-3.3 4.1-7.5 7.7-12.4 9.9-10.2 5.4-23.5 2.1-31.3-6.1-3.9-3.9-6.2-9.4-6.5-14.9-.4-5.7.8-11.5 3.4-16.7 2.3-5 5-9.7 7.8-14.4 13.7-23.1 27.3-46.3 41-69.4 18.7-31.8 37.8-63.4 56.6-95.1 2-3.6 4.5-6.9 7.3-9.9 3.8-4 8.6-7.1 14-7.9\\\" style=\\\"fill:#1ba0d8\\\"/></svg>\",\n    \"simple-amazonwebservices\": \"<svg role=\\\"img\\\" viewBox=\\\"0 0 24 24\\\" xmlns=\\\"http://www.w3.org/2000/svg\\\"><title>Amazon Web Services</title><path d=\\\"M6.763 10.036c0 .296.032.535.088.71.064.176.144.368.256.576.04.063.056.127.056.183 0 .08-.048.16-.152.24l-.503.335a.383.383 0 0 1-.208.072c-.08 0-.16-.04-.239-.112a2.47 2.47 0 0 1-.287-.375 6.18 6.18 0 0 1-.248-.471c-.622.734-1.405 1.101-2.347 1.101-.67 0-1.205-.191-1.596-.574-.391-.384-.59-.894-.59-1.533 0-.678.239-1.23.726-1.644.487-.415 1.133-.623 1.955-.623.272 0 .551.024.846.064.296.04.6.104.918.176v-.583c0-.607-.127-1.03-.375-1.277-.255-.248-.686-.367-1.3-.367-.28 0-.568.031-.863.103-.295.072-.583.16-.862.272a2.287 2.287 0 0 1-.28.104.488.488 0 0 1-.127.023c-.112 0-.168-.08-.168-.247v-.391c0-.128.016-.224.056-.28a.597.597 0 0 1 .224-.167c.279-.144.614-.264 1.005-.36a4.84 4.84 0 0 1 1.246-.151c.95 0 1.644.216 2.091.647.439.43.662 1.085.662 1.963v2.586zm-3.24 1.214c.263 0 .534-.048.822-.144.287-.096.543-.271.758-.51.128-.152.224-.32.272-.512.047-.191.08-.423.08-.694v-.335a6.66 6.66 0 0 0-.735-.136 6.02 6.02 0 0 0-.75-.048c-.535 0-.926.104-1.19.32-.263.215-.39.518-.39.917 0 .375.095.655.295.846.191.2.47.296.838.296zm6.41.862c-.144 0-.24-.024-.304-.08-.064-.048-.12-.16-.168-.311L7.586 5.55a1.398 1.398 0 0 1-.072-.32c0-.128.064-.2.191-.2h.783c.151 0 .255.025.31.08.065.048.113.16.16.312l1.342 5.284 1.245-5.284c.04-.16.088-.264.151-.312a.549.549 0 0 1 .32-.08h.638c.152 0 .256.025.32.08.063.048.12.16.151.312l1.261 5.348 1.381-5.348c.048-.16.104-.264.16-.312a.52.52 0 0 1 .311-.08h.743c.127 0 .2.065.2.2 0 .04-.009.08-.017.128a1.137 1.137 0 0 1-.056.2l-1.923 6.17c-.048.16-.104.263-.168.311a.51.51 0 0 1-.303.08h-.687c-.151 0-.255-.024-.32-.08-.063-.056-.119-.16-.15-.32l-1.238-5.148-1.23 5.14c-.04.16-.087.264-.15.32-.065.056-.177.08-.32.08zm10.256.215c-.415 0-.83-.048-1.229-.143-.399-.096-.71-.2-.918-.32-.128-.071-.215-.151-.247-.223a.563.563 0 0 1-.048-.224v-.407c0-.167.064-.247.183-.247.048 0 .096.008.144.024.048.016.12.048.2.08.271.12.566.215.878.279.319.064.63.096.95.096.502 0 .894-.088 1.165-.264a.86.86 0 0 0 .415-.758.777.777 0 0 0-.215-.559c-.144-.151-.416-.287-.807-.415l-1.157-.36c-.583-.183-1.014-.454-1.277-.813a1.902 1.902 0 0 1-.4-1.158c0-.335.073-.63.216-.886.144-.255.335-.479.575-.654.24-.184.51-.32.83-.415.32-.096.655-.136 1.006-.136.175 0 .359.008.535.032.183.024.35.056.518.088.16.04.312.08.455.127.144.048.256.096.336.144a.69.69 0 0 1 .24.2.43.43 0 0 1 .071.263v.375c0 .168-.064.256-.184.256a.83.83 0 0 1-.303-.096 3.652 3.652 0 0 0-1.532-.311c-.455 0-.815.071-1.062.223-.248.152-.375.383-.375.71 0 .224.08.416.24.567.159.152.454.304.877.44l1.134.358c.574.184.99.44 1.237.767.247.327.367.702.367 1.117 0 .343-.072.655-.207.926-.144.272-.336.511-.583.703-.248.2-.543.343-.886.447-.36.111-.734.167-1.142.167zM21.698 16.207c-2.626 1.94-6.442 2.969-9.722 2.969-4.598 0-8.74-1.7-11.87-4.526-.247-.223-.024-.527.272-.351 3.384 1.963 7.559 3.153 11.877 3.153 2.914 0 6.114-.607 9.06-1.852.439-.2.814.287.383.607zM22.792 14.961c-.336-.43-2.22-.207-3.074-.103-.255.032-.295-.192-.063-.36 1.5-1.053 3.967-.75 4.254-.399.287.36-.08 2.826-1.485 4.007-.215.184-.423.088-.327-.151.32-.79 1.03-2.57.695-2.994z\\\"/></svg>\",\n    \"simple-apple\": \"<svg role=\\\"img\\\" viewBox=\\\"0 0 24 24\\\" xmlns=\\\"http://www.w3.org/2000/svg\\\"><title>Apple</title><path d=\\\"M12.152 6.896c-.948 0-2.415-1.078-3.96-1.04-2.04.027-3.91 1.183-4.961 3.014-2.117 3.675-.546 9.103 1.519 12.09 1.013 1.454 2.208 3.09 3.792 3.039 1.52-.065 2.09-.987 3.935-.987 1.831 0 2.35.987 3.96.948 1.637-.026 2.676-1.48 3.676-2.948 1.156-1.688 1.636-3.325 1.662-3.415-.039-.013-3.182-1.221-3.22-4.857-.026-3.04 2.48-4.494 2.597-4.559-1.429-2.09-3.623-2.324-4.39-2.376-2-.156-3.675 1.09-4.61 1.09zM15.53 3.83c.843-1.012 1.4-2.427 1.245-3.83-1.207.052-2.662.805-3.532 1.818-.78.896-1.454 2.338-1.273 3.714 1.338.104 2.715-.688 3.559-1.701\\\"/></svg>\",\n    \"selfhst-amazon-web-services\": \"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" xml:space=\\\"preserve\\\" viewBox=\\\"0 0 512 512\\\"><path d=\\\"M144.3 214.1c0 6.3.7 11.4 1.9 15.2 1.4 3.7 3.1 7.8 5.4 12.3.9 1.4 1.2 2.7 1.2 3.9 0 1.7-1 3.4-3.2 5.1l-10.7 7.2c-1.5 1-3.1 1.5-4.4 1.5-1.7 0-3.4-.9-5.1-2.4-2.4-2.6-4.4-5.3-6.1-8-1.7-2.9-3.4-6.1-5.3-10-13.3 15.7-30 23.5-50.1 23.5-14.3 0-25.7-4.1-34.1-12.3-8.3-8.2-12.6-19.1-12.6-32.7 0-14.5 5.1-26.2 15.5-35.1S60.8 169 78.4 169c5.8 0 11.7.5 18.1 1.4s12.8 2.2 19.6 3.7v-12.4c0-12.9-2.7-22-8-27.2-5.4-5.3-14.6-7.8-27.8-7.8-6 0-12.1.7-18.4 2.2s-12.4 3.4-18.4 5.8c-2.7 1.2-4.8 1.9-6 2.2s-2 .5-2.7.5c-2.4 0-3.6-1.7-3.6-5.3v-8.3c0-2.7.3-4.8 1.2-6s2.4-2.4 4.8-3.6c6-3.1 13.1-5.6 21.5-7.7 8.3-2.2 17.2-3.2 26.6-3.2 20.3 0 35.1 4.6 44.6 13.8 9.4 9.2 14.1 23.2 14.1 41.9v55.2zM75.2 240c5.6 0 11.4-1 17.5-3.1 6.1-2 11.6-5.8 16.2-10.9 2.7-3.2 4.8-6.8 5.8-10.9s1.7-9 1.7-14.8v-7.2c-4.9-1.2-10.2-2.2-15.7-2.9-5.4-.7-10.7-1-16-1-11.4 0-19.8 2.2-25.4 6.8S51 207.1 51 215.6c0 8 2 14 6.3 18.1 4.1 4.2 10 6.3 17.9 6.3m136.7 18.4c-3.1 0-5.1-.5-6.5-1.7-1.4-1-2.6-3.4-3.6-6.6l-40-131.6c-1-3.4-1.5-5.6-1.5-6.8 0-2.7 1.4-4.3 4.1-4.3h16.7c3.2 0 5.4.5 6.6 1.7 1.4 1 2.4 3.4 3.4 6.6l28.6 112.7 26.6-112.7c.9-3.4 1.9-5.6 3.2-6.6 1.4-1 3.7-1.7 6.8-1.7H270c3.2 0 5.4.5 6.8 1.7 1.4 1 2.6 3.4 3.2 6.6l26.9 114.1 29.5-114.1c1-3.4 2.2-5.6 3.4-6.6 1.4-1 3.6-1.7 6.6-1.7h15.8c2.7 0 4.3 1.4 4.3 4.3 0 .9-.2 1.7-.3 2.7-.2 1-.5 2.4-1.2 4.3l-41 131.6q-1.5 5.1-3.6 6.6c-1.4 1-3.6 1.7-6.5 1.7h-14.6c-3.2 0-5.4-.5-6.8-1.7s-2.6-3.4-3.2-6.8l-26.4-109.8L236.7 250c-.9 3.4-1.9 5.6-3.2 6.8-1.4 1.2-3.7 1.7-6.8 1.7zm218.8 4.6c-8.9 0-17.7-1-26.2-3.1-8.5-2-15.2-4.3-19.6-6.8-2.7-1.5-4.6-3.2-5.3-4.8s-1-3.2-1-4.8v-8.7c0-3.6 1.4-5.3 3.9-5.3 1 0 2 .2 3.1.5 1 .3 2.6 1 4.3 1.7 5.8 2.6 12.1 4.6 18.7 6 6.8 1.4 13.5 2 20.3 2 10.7 0 19.1-1.9 24.9-5.6s8.9-9.2 8.9-16.2c0-4.8-1.5-8.7-4.6-11.9s-8.9-6.1-17.2-8.9l-24.7-7.7c-12.4-3.9-21.6-9.7-27.2-17.4-5.6-7.5-8.5-15.8-8.5-24.7 0-7.2 1.5-13.5 4.6-18.9s7.2-10.2 12.3-14c5.1-3.9 10.9-6.8 17.7-8.9 6.8-2 14-2.9 21.5-2.9 3.7 0 7.7.2 11.4.7 3.9.5 7.5 1.2 11.1 1.9 3.4.9 6.6 1.7 9.7 2.7s5.4 2 7.2 3.1c2.4 1.4 4.1 2.7 5.1 4.3 1 1.4 1.5 3.2 1.5 5.6v8c0 3.6-1.4 5.4-3.9 5.4-1.4 0-3.6-.7-6.5-2q-14.55-6.6-32.7-6.6c-9.7 0-17.4 1.5-22.6 4.8s-8 8.2-8 15.2c0 4.8 1.7 8.9 5.1 12.1s9.7 6.5 18.7 9.4l24.2 7.7c12.3 3.9 21.1 9.4 26.4 16.3s7.8 15 7.8 23.8c0 7.3-1.5 14-4.4 19.8-3.1 5.8-7.2 10.9-12.4 15-5.3 4.3-11.6 7.3-18.9 9.5-8 2.5-16 3.7-24.7 3.7\\\"/><path d=\\\"M462.9 345.7c-56 41.4-137.4 63.3-207.4 63.3-98.1 0-186.5-36.3-253.2-96.6-5.3-4.8-.5-11.2 5.8-7.5 72.2 41.9 161.3 67.3 253.4 67.3 62.2 0 130.4-12.9 193.3-39.5 9.3-4.2 17.3 6.2 8.1 13m23.3-26.5c-7.2-9.2-47.3-4.4-65.6-2.2-5.4.7-6.3-4.1-1.4-7.7 32-22.5 84.6-16 90.8-8.5 6.1 7.7-1.7 60.3-31.7 85.5-4.6 3.9-9 1.9-7-3.2 6.9-16.9 22.1-54.9 14.9-63.9\\\" style=\\\"fill-rule:evenodd;clip-rule:evenodd;fill:#f90\\\"/></svg>\",\n    \"selfhst-opnsense\": \"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" xml:space=\\\"preserve\\\" viewBox=\\\"0 0 512 512\\\"><path d=\\\"M512 160.2V400H400V112H112V0h240.4zM112 112H0v240.4L160.2 512H400V400H112z\\\" style=\\\"fill:#de3c07\\\"/></svg>\",\n    \"selfhst-portainer\": \"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" xml:space=\\\"preserve\\\" viewBox=\\\"0 0 512 512\\\"><path d=\\\"M462.2 512H49.8C22.3 512 0 489.7 0 462.2V49.8C0 22.3 22.3 0 49.8 0h412.4C489.7 0 512 22.3 512 49.8v412.4c0 27.5-22.3 49.8-49.8 49.8\\\" style=\\\"fill:#2e2f33\\\"/><path d=\\\"M108.2 63.9h140.9c97.2 0 154.6 30.4 154.6 129.5v3.4c0 99.4-57.2 129.5-154.5 129.5h-30.7V447H108.2zm134.5 177.8c30.1 0 46-11.2 46-44.6v-3.9c0-33.2-15.8-44.6-46-44.6h-24.2v93.1zM313 358h90.3v90.1H313z\\\" style=\\\"fill:#f7f6f3\\\"/></svg>\",\n    \"selfhst-jotty\": \"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" xml:space=\\\"preserve\\\" viewBox=\\\"0 0 512 512\\\"><path d=\\\"M438.6 0H73.4C33.1 0 .3 32.8.3 73.1v365.7c0 40.3 32.8 73.1 73.1 73.1h365.3c40.3 0 73.1-32.8 73.1-73.1V73.1C511.7 32.8 478.9 0 438.6 0\\\" style=\\\"fill-rule:evenodd;clip-rule:evenodd;fill:#8b3bd0\\\"/><path d=\\\"M356.8 53.6v261.3c0 39.3-13.9 72.8-41.8 100.7s-61.4 41.8-100.7 41.8l-23.8-95h23.8c13 0 24.1-4.6 33.2-13.8 9.5-9.5 14.3-20.7 14.3-33.7V148.6H157.3v-95z\\\" style=\\\"fill:#fff\\\"/></svg>\",\n    \"selfhst-authportal\": \"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" xml:space=\\\"preserve\\\" viewBox=\\\"0 0 512 512\\\"><path d=\\\"M0 0h512v512H0z\\\" style=\\\"fill:#111827\\\"/><path d=\\\"M170.7 85.3 369.8 256 170.7 426.7z\\\" style=\\\"fill:#f59e0b\\\"/></svg>\",\n    \"selfhst-docker\": \"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" xml:space=\\\"preserve\\\" viewBox=\\\"0 0 512 512\\\"><path d=\\\"M501.4 212.3c-11.5-8-38-11-58.6-7-2.4-20-13.5-37.5-32.7-53l-11-8-7.7 11.5c-9.6 15-14.4 36-13 56 .5 7 2.9 19.5 10.1 30.5-6.7 4-20.7 9-38.9 9H2.3l-1 4c-3.4 20-3.4 82.5 36 130.5 29.8 36.5 74 55 132.1 55 125.9 0 219.1-60.5 262.8-170 17.3.5 54.3 0 73-37.5.5-1 1.4-3 4.8-10.5l1.9-4zM280 71.3h-52.8v50H280zm0 60h-52.8v50H280zm-62.5 0h-52.8v50h52.8zm-62.4 0h-52.8v50h52.8zm-62.5 60H39.8v50h52.8zm62.5 0h-52.8v50h52.8zm62.4 0h-52.8v50h52.8zm62.5 0h-52.8v50H280zm62.4 0h-52.8v50h52.8z\\\" style=\\\"fill:#2396ed\\\"/></svg>\",\n    \"selfhst-opnsense-v1\": \"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" xml:space=\\\"preserve\\\" viewBox=\\\"0 0 512 512\\\"><path d=\\\"M503.1 465.4v2.1c-6 25.5-28.4 44.5-55 44.5H7.3v-46.6h38.3v7.3h403c1.1 0 2 0 2.9-.1 1.8-.1 3.6-.6 5.2-1.3 2.9-1.3 5.3-3.3 7-5.9zM63.9 0c-26.5 0-49 19-55 44.5V47H48c2.7-4.3 7.3-7.2 12.6-7.6 1-.1 2-.1 3.2-.1h402.6V47h38.3V0z\\\" style=\\\"fill:#898b8d\\\"/><path d=\\\"M466.1 157.7V197H319.5v-39.3zM45.9 315.4v39.3h146.6v-39.3zm0-157.7V197h146.6v-39.3zm273.6 157.7v39.3h146.6v-39.3z\\\" style=\\\"fill:#58595b\\\"/><path d=\\\"M83.8 78.6H428v39.3H83.8zm0 315.9H428v39.3H83.8z\\\" style=\\\"fill:#403f41\\\"/><linearGradient id=\\\"opnsense-v1_svg__a\\\" x1=\\\"-1460.617\\\" x2=\\\"-1375.11\\\" y1=\\\"1248.095\\\" y2=\\\"1248.095\\\" gradientTransform=\\\"matrix(.1853 .356 .6722 -.353 -90.66 993.802)\\\" gradientUnits=\\\"userSpaceOnUse\\\"><stop offset=\\\"0\\\" style=\\\"stop-color:#737373\\\"/><stop offset=\\\"1\\\" style=\\\"stop-color:#333\\\"/></linearGradient><path d=\\\"m466.5 78.6 38.2-20.5.1-39.5-38.3 20.7\\\" style=\\\"fill:url(#opnsense-v1_svg__a)\\\"/><linearGradient id=\\\"opnsense-v1_svg__b\\\" x1=\\\"4.634\\\" x2=\\\"50.301\\\" y1=\\\"462.6\\\" y2=\\\"462.6\\\" gradientTransform=\\\"matrix(1 0 0 -1 0 514)\\\" gradientUnits=\\\"userSpaceOnUse\\\"><stop offset=\\\"0\\\" style=\\\"stop-color:#737373\\\"/><stop offset=\\\"1\\\" style=\\\"stop-color:#333\\\"/></linearGradient><path d=\\\"M45.6 64.5v-8.9c0-5.3 2.6-10 6.5-13l-34-18.5c-6.7 9.5-10.8 21.3-10.9 34l38.3 20.6v-14c.1-.1.1-.2.1-.2\\\" style=\\\"fill:url(#opnsense-v1_svg__b)\\\"/><linearGradient id=\\\"opnsense-v1_svg__c\\\" x1=\\\"-1587.887\\\" x2=\\\"-1498.137\\\" y1=\\\"-2208.165\\\" y2=\\\"-2208.165\\\" gradientTransform=\\\"matrix(-.1853 -.356 -.6722 .353 -1743.836 694.098)\\\" gradientUnits=\\\"userSpaceOnUse\\\"><stop offset=\\\"0\\\" style=\\\"stop-color:#737373\\\"/><stop offset=\\\"1\\\" style=\\\"stop-color:#333\\\"/></linearGradient><path d=\\\"M45.6 472.7v-38.9L7.3 454.5v39.7l39.9-21.5z\\\" style=\\\"fill:url(#opnsense-v1_svg__c)\\\"/><linearGradient id=\\\"opnsense-v1_svg__d\\\" x1=\\\"460.439\\\" x2=\\\"504.436\\\" y1=\\\"53\\\" y2=\\\"53\\\" gradientTransform=\\\"matrix(1 0 0 -1 0 514)\\\" gradientUnits=\\\"userSpaceOnUse\\\"><stop offset=\\\"0\\\" style=\\\"stop-color:#737373\\\"/><stop offset=\\\"1\\\" style=\\\"stop-color:#333\\\"/></linearGradient><path d=\\\"M466.5 456.4c0 5.5-2.7 10.3-6.9 13.2l34.2 18.4c6.8-9.5 10.8-21.2 10.9-33.8L466.5 434v12.8\\\" style=\\\"fill:url(#opnsense-v1_svg__d)\\\"/><linearGradient id=\\\"opnsense-v1_svg__e\\\" x1=\\\"2521.902\\\" x2=\\\"2684.464\\\" y1=\\\"-2476.233\\\" y2=\\\"-2476.233\\\" gradientTransform=\\\"matrix(-1.508 -1.0166 -3.1265 -1.519 -3697.438 -937.952)\\\" gradientUnits=\\\"userSpaceOnUse\\\"><stop offset=\\\".107\\\" style=\\\"stop-color:#58595b\\\"/><stop offset=\\\"1\\\" style=\\\"stop-color:#000\\\"/></linearGradient><path d=\\\"M192.5 197v-.4l-72.4-38.9H45.9v.2l72.5 39.1z\\\" style=\\\"fill:url(#opnsense-v1_svg__e)\\\"/><linearGradient id=\\\"opnsense-v1_svg__f\\\" x1=\\\"-1594.953\\\" x2=\\\"-1432.391\\\" y1=\\\"278.458\\\" y2=\\\"278.458\\\" gradientTransform=\\\"matrix(1.508 -1.0166 3.1265 -1.519 1804.196 -937.952)\\\" gradientUnits=\\\"userSpaceOnUse\\\"><stop offset=\\\".107\\\" style=\\\"stop-color:#58595b\\\"/><stop offset=\\\"1\\\" style=\\\"stop-color:#000\\\"/></linearGradient><path d=\\\"M466.1 157.7h-74l-72.6 39.1v.2h74.2l72.4-38.9z\\\" style=\\\"fill:url(#opnsense-v1_svg__f)\\\"/><linearGradient id=\\\"opnsense-v1_svg__g\\\" x1=\\\"-4327.142\\\" x2=\\\"-4164.581\\\" y1=\\\"1572.387\\\" y2=\\\"1572.387\\\" gradientTransform=\\\"matrix(1.508 1.0166 3.1265 1.519 1863.938 2252.352)\\\" gradientUnits=\\\"userSpaceOnUse\\\"><stop offset=\\\".107\\\" style=\\\"stop-color:#58595b\\\"/><stop offset=\\\"1\\\" style=\\\"stop-color:#000\\\"/></linearGradient><path d=\\\"M466.1 354.7v-.4l-72.4-38.9h-74.2v.2l72.6 39.1z\\\" style=\\\"fill:url(#opnsense-v1_svg__g)\\\"/><linearGradient id=\\\"opnsense-v1_svg__h\\\" x1=\\\"-413.668\\\" x2=\\\"-246.99\\\" y1=\\\"-1046.618\\\" y2=\\\"-1046.618\\\" gradientTransform=\\\"matrix(-1.508 1.0166 -3.1265 1.519 -3638.692 2252.352)\\\" gradientUnits=\\\"userSpaceOnUse\\\"><stop offset=\\\".107\\\" style=\\\"stop-color:#58595b\\\"/><stop offset=\\\"1\\\" style=\\\"stop-color:#000\\\"/></linearGradient><path d=\\\"M192.5 315.4h-74.1l-72.5 39.1v.2h74.2l72.4-38.9z\\\" style=\\\"fill:url(#opnsense-v1_svg__h)\\\"/><linearGradient id=\\\"opnsense-v1_svg__i\\\" x1=\\\"74.725\\\" x2=\\\"261.062\\\" y1=\\\"132.65\\\" y2=\\\"132.65\\\" gradientTransform=\\\"matrix(.9914 0 0 -1 -66.782 260.2)\\\" gradientUnits=\\\"userSpaceOnUse\\\"><stop offset=\\\".237\\\" style=\\\"stop-color:#a6a8ab\\\"/><stop offset=\\\"1\\\" style=\\\"stop-color:#404040\\\"/></linearGradient><path d=\\\"M192.8 157.7V197L7.3 97.4V58.1z\\\" style=\\\"fill:url(#opnsense-v1_svg__i)\\\"/><linearGradient id=\\\"opnsense-v1_svg__j\\\" x1=\\\"-2461.056\\\" x2=\\\"-2274.718\\\" y1=\\\"132.65\\\" y2=\\\"132.65\\\" gradientTransform=\\\"matrix(-.9914 0 0 -1 -1935.19 260.2)\\\" gradientUnits=\\\"userSpaceOnUse\\\"><stop offset=\\\".237\\\" style=\\\"stop-color:#a6a8ab\\\"/><stop offset=\\\"1\\\" style=\\\"stop-color:#404040\\\"/></linearGradient><path d=\\\"M319.2 157.7V197l185.5-99.6V58.1z\\\" style=\\\"fill:url(#opnsense-v1_svg__j)\\\"/><linearGradient id=\\\"opnsense-v1_svg__k\\\" x1=\\\"-2291.845\\\" x2=\\\"-2105.508\\\" y1=\\\"-739.55\\\" y2=\\\"-739.55\\\" gradientTransform=\\\"matrix(-.9914 0 0 1 -1767.435 1124.6)\\\" gradientUnits=\\\"userSpaceOnUse\\\"><stop offset=\\\".237\\\" style=\\\"stop-color:#a6a8ab\\\"/><stop offset=\\\"1\\\" style=\\\"stop-color:#404040\\\"/></linearGradient><path d=\\\"M319.2 355.2v-39.8L504.7 415v39.7z\\\" style=\\\"fill:url(#opnsense-v1_svg__k)\\\"/><linearGradient id=\\\"opnsense-v1_svg__l\\\" x1=\\\"-94.103\\\" x2=\\\"91.934\\\" y1=\\\"-739.55\\\" y2=\\\"-739.55\\\" gradientTransform=\\\"matrix(.9914 0 0 1 100.993 1124.6)\\\" gradientUnits=\\\"userSpaceOnUse\\\"><stop offset=\\\".237\\\" style=\\\"stop-color:#a6a8ab\\\"/><stop offset=\\\"1\\\" style=\\\"stop-color:#404040\\\"/></linearGradient><path d=\\\"M192.8 355.2v-39.8L7.3 415v39.7z\\\" style=\\\"fill:url(#opnsense-v1_svg__l)\\\"/><path d=\\\"m319.2 276.1 108.9 58.5v-39.7l-35.3-18.8h111.9v-39.8H392.8l35.3-18.8v-39.3l-108.9 58.1zM83.8 334.6l109-58.5v-39.8l-109-58.1v39.3l35.7 18.8H7.3v39.8h111.9l-35.3 18.8v39.7z\\\" style=\\\"fill:#e24525\\\"/></svg>\",\n    \"mdi-server-security\": \"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" id=\\\"mdi-server-security\\\" viewBox=\\\"0 0 24 24\\\"><path d=\\\"M3,1H19A1,1 0 0,1 20,2V6A1,1 0 0,1 19,7H3A1,1 0 0,1 2,6V2A1,1 0 0,1 3,1M3,9H19A1,1 0 0,1 20,10V10.67L17.5,9.56L11,12.44V15H3A1,1 0 0,1 2,14V10A1,1 0 0,1 3,9M3,17H11C11.06,19.25 12,21.4 13.46,23H3A1,1 0 0,1 2,22V18A1,1 0 0,1 3,17M8,5H9V3H8V5M8,13H9V11H8V13M8,21H9V19H8V21M4,3V5H6V3H4M4,11V13H6V11H4M4,19V21H6V19H4M17.5,12L22,14V17C22,19.78 20.08,22.37 17.5,23C14.92,22.37 13,19.78 13,17V14L17.5,12M17.5,13.94L15,15.06V17.72C15,19.26 16.07,20.7 17.5,21.06V13.94Z\\\" /></svg>\"\n  },\n  \"page\": {\n    \"title\": \"The One File Corporate\",\n    \"background\": \"\",\n    \"topbarBg\": \"rgba(9, 12, 20, 0.9)\",\n    \"topbarBorder\": \"#1f2533\",\n    \"panel\": \"#0b0e13\",\n    \"panelAlt\": \"#10141b\",\n    \"accent\": \"#4fd1c5\",\n    \"sidebarBg\": \"#10141b\",\n    \"btnBg\": \"#0b0e13\",\n    \"btnText\": \"#e2e8f0\",\n    \"tagFill\": \"#1e293b\",\n    \"tagText\": \"#e2e8f0\",\n    \"tagBorder\": \"#475569\",\n    \"inputBg\": \"#0b0e13\",\n    \"inputText\": \"#e2e8f0\",\n    \"inputBorder\": \"#1f2937\",\n    \"inputFont\": \"Inter, system-ui, sans-serif\",\n    \"inputFontSize\": 14,\n    \"toolbarBg\": \"#0f172a\",\n    \"toolbarBorder\": \"#1f2937\",\n    \"toolbarText\": \"#94a3b8\",\n    \"toolbarBtnBg\": \"#0b0e13\",\n    \"toolbarBtnText\": \"#e2e8f0\",\n    \"minimapDots\": \"#94a3b8\",\n    \"canvasHintEnabled\": true,\n    \"canvasHintText\": \"\",\n    \"canvasHintBg\": \"#0f172a\",\n    \"canvasHintColor\": \"#94a3b8\",\n    \"danger\": \"#f56565\",\n    \"textMain\": \"#e2e8f0\",\n    \"textSoft\": \"#94a3b8\",\n    \"topbarHeight\": 103,\n    \"sidebarWidth\": 350,\n    \"mobileFooterHeight\": 40,\n    \"sidebarCollapsed\": false,\n    \"nodeFill\": \"#1e293b\",\n    \"nodeStroke\": \"#475569\",\n    \"nodeTitle\": \"#e2e8f0\",\n    \"nodeSub\": \"#94a3b8\",\n    \"nodeTitleSize\": 41,\n    \"nodeSubSize\": 27,\n    \"nodeFont\": \"monospace\",\n    \"defaultEdge\": \"#475569\",\n    \"selectionHandle\": \"#f59e0b\",\n    \"selectionHandleSize\": 8,\n    \"groupIndicator\": \"#4fd1c5\",\n    \"canvasGradientTop\": \"#1e2532\",\n    \"canvasGradientBottom\": \"#050608\",\n    \"canvasBorder\": \"#475569\",\n    \"canvasGrid\": \"#475569\",\n    \"canvasGridSize\": 50,\n    \"canvasGridEnabled\": true,\n    \"rackFrameFill\": \"#0f172a\",\n    \"rackFrameStroke\": \"#4fd1c5\",\n    \"rackLineColor\": \"#475569\",\n    \"rackTextColor\": \"#4fd1c5\",\n    \"rackGridEnabled\": true,\n    \"viewOnly\": false,\n    \"defaultEdgeRouting\": \"orthogonal\",\n    \"animateConnections\": false,\n    \"animationStyle\": \"arrows\",\n    \"animationDirection\": \"all\",\n    \"animationSpeed\": 4,\n    \"autoPingEnabled\": false,\n    \"autoPingInterval\": 30\n  },\n  \"autoPingEnabled\": false,\n  \"autoPingInterval\": 30,\n  \"canvas\": {\n    \"zoom\": 0.9921985961590549,\n    \"panX\": -5.584863670202822,\n    \"panY\": -99.90831573327841\n  },\n  \"savedTopologyView\": {\n    \"zoom\": 0.9325110211947125,\n    \"panX\": -563.7108933103631,\n    \"panY\": -561.6887674556383\n  },\n  \"documentTabs\": [\n    {\n      \"id\": \"main\",\n      \"name\": \"Corporate Site B\",\n      \"nodes\": {\n        \"core-router-1\": {\n          \"shape\": \"router\",\n          \"name\": \"Core Router 1\",\n          \"ip\": \"10.0.0.1\",\n          \"role\": \"Core Routing\",\n          \"tags\": [\n            \"core\",\n            \"tier-1\",\n            \"redundant\"\n          ],\n          \"notes\": [\n            \"Primary core router\",\n            \"BGP peering enabled\"\n          ],\n          \"mac\": \"00:1A:2B:3C:4D:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"core-router-2\": {\n          \"shape\": \"router\",\n          \"name\": \"Core Router 2\",\n          \"ip\": \"10.0.0.2\",\n          \"role\": \"Core Routing\",\n          \"tags\": [\n            \"core\",\n            \"tier-1\",\n            \"redundant\"\n          ],\n          \"notes\": [\n            \"Secondary core router\",\n            \"HSRP standby\"\n          ],\n          \"mac\": \"00:1A:2B:3C:4D:02\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null,\n          \"ping\": {\n            \"enabled\": true,\n            \"protocol\": \"custom\",\n            \"customUrl\": \"https://google.com\",\n            \"timeout\": 3000,\n            \"status\": \"online\",\n            \"lastCheck\": \"2025-12-09T00:15:04.343Z\"\n          }\n        },\n        \"fw-external-1\": {\n          \"shape\": \"firewall\",\n          \"name\": \"External FW 1\",\n          \"ip\": \"10.0.1.1\",\n          \"role\": \"Perimeter Security\",\n          \"tags\": [\n            \"security\",\n            \"perimeter\",\n            \"ha-pair\"\n          ],\n          \"notes\": [\n            \"Palo Alto PA-5250\",\n            \"Active node\"\n          ],\n          \"mac\": \"00:1A:2B:3C:4D:10\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"fw-external-2\": {\n          \"shape\": \"firewall\",\n          \"name\": \"External FW 2\",\n          \"ip\": \"10.0.1.2\",\n          \"role\": \"Perimeter Security\",\n          \"tags\": [\n            \"security\",\n            \"perimeter\",\n            \"ha-pair\"\n          ],\n          \"notes\": [\n            \"Palo Alto PA-5250\",\n            \"Passive node\"\n          ],\n          \"mac\": \"00:1A:2B:3C:4D:11\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"fw-internal\": {\n          \"shape\": \"firewall\",\n          \"name\": \"Internal FW\",\n          \"ip\": \"10.0.2.1\",\n          \"role\": \"Internal Segmentation\",\n          \"tags\": [\n            \"security\",\n            \"internal\"\n          ],\n          \"notes\": [\n            \"East-West traffic inspection\"\n          ],\n          \"mac\": \"00:1A:2B:3C:4D:12\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"core-switch-1\": {\n          \"shape\": \"switch\",\n          \"name\": \"Core Switch 1\",\n          \"ip\": \"10.0.10.1\",\n          \"role\": \"Core Switching\",\n          \"tags\": [\n            \"core\",\n            \"layer3\",\n            \"redundant\"\n          ],\n          \"notes\": [\n            \"Cisco Nexus 9000\",\n            \"VPC Domain 1\"\n          ],\n          \"mac\": \"00:1A:2B:3C:4D:20\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"core-switch-2\": {\n          \"shape\": \"switch\",\n          \"name\": \"Core Switch 2\",\n          \"ip\": \"10.0.10.2\",\n          \"role\": \"Core Switching\",\n          \"tags\": [\n            \"core\",\n            \"layer3\",\n            \"redundant\"\n          ],\n          \"notes\": [\n            \"Cisco Nexus 9000\",\n            \"VPC Domain 1\"\n          ],\n          \"mac\": \"00:1A:2B:3C:4D:21\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dc-rack-a1\": {\n          \"shape\": \"server\",\n          \"name\": \"DC Rack A1\",\n          \"ip\": \"10.10.0.0/24\",\n          \"role\": \"Data Center Rack\",\n          \"tags\": [\n            \"datacenter\",\n            \"row-a\",\n            \"production\"\n          ],\n          \"notes\": [\n            \"Row A, Position 1\",\n            \"Primary compute\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": true,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dc-rack-a2\": {\n          \"shape\": \"server\",\n          \"name\": \"DC Rack A2\",\n          \"ip\": \"10.10.1.0/24\",\n          \"role\": \"Data Center Rack\",\n          \"tags\": [\n            \"datacenter\",\n            \"row-a\",\n            \"production\"\n          ],\n          \"notes\": [\n            \"Row A, Position 2\",\n            \"Primary compute\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": true,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dc-rack-b1\": {\n          \"shape\": \"server\",\n          \"name\": \"DC Rack B1\",\n          \"ip\": \"10.10.2.0/24\",\n          \"role\": \"Data Center Rack\",\n          \"tags\": [\n            \"datacenter\",\n            \"row-b\",\n            \"storage\"\n          ],\n          \"notes\": [\n            \"Row B, Position 1\",\n            \"Storage systems\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": true,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dc-rack-b2\": {\n          \"shape\": \"server\",\n          \"name\": \"DC Rack B2\",\n          \"ip\": \"10.10.3.0/24\",\n          \"role\": \"Data Center Rack\",\n          \"tags\": [\n            \"datacenter\",\n            \"row-b\",\n            \"storage\"\n          ],\n          \"notes\": [\n            \"Row B, Position 2\",\n            \"Storage systems\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": true,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dmz-rack\": {\n          \"shape\": \"server\",\n          \"name\": \"DMZ Rack\",\n          \"ip\": \"172.16.0.0/24\",\n          \"role\": \"DMZ Infrastructure\",\n          \"tags\": [\n            \"dmz\",\n            \"security\",\n            \"public-facing\",\n            {\n              \"type\": \"icon\",\n              \"library\": \"selfhst\",\n              \"name\": \"booklogr\"\n            },\n            {\n              \"type\": \"icon\",\n              \"library\": \"simple\",\n              \"name\": \"gmail\"\n            }\n          ],\n          \"notes\": [\n            \"Isolated DMZ zone\",\n            \"Public-facing services\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": true,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"mgmt-rack\": {\n          \"shape\": \"server\",\n          \"name\": \"Management Rack\",\n          \"ip\": \"192.168.100.0/24\",\n          \"role\": \"Management Infrastructure\",\n          \"tags\": [\n            \"management\",\n            \"oob\",\n            \"noc\"\n          ],\n          \"notes\": [\n            \"Out-of-band management\",\n            \"NOC equipment\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"logical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": true,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"esxi-host-01\": {\n          \"shape\": \"server\",\n          \"name\": \"ESXi Host 01\",\n          \"ip\": \"10.10.0.11\",\n          \"role\": \"Hypervisor\",\n          \"tags\": [\n            \"vmware\",\n            \"compute\",\n            \"cluster-a\"\n          ],\n          \"notes\": [\n            \"Dell PowerEdge R750\",\n            \"512GB RAM\",\n            \"vSphere 8.0\"\n          ],\n          \"mac\": \"00:50:56:AA:01:01\",\n          \"rackUnit\": 38,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"esxi-host-02\": {\n          \"shape\": \"server\",\n          \"name\": \"ESXi Host 02\",\n          \"ip\": \"10.10.0.12\",\n          \"role\": \"Hypervisor\",\n          \"tags\": [\n            \"vmware\",\n            \"compute\",\n            \"cluster-a\"\n          ],\n          \"notes\": [\n            \"Dell PowerEdge R750\",\n            \"512GB RAM\",\n            \"vSphere 8.0\"\n          ],\n          \"mac\": \"00:50:56:AA:01:02\",\n          \"rackUnit\": 35,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"esxi-host-03\": {\n          \"shape\": \"server\",\n          \"name\": \"ESXi Host 03\",\n          \"ip\": \"10.10.0.13\",\n          \"role\": \"Hypervisor\",\n          \"tags\": [\n            \"vmware\",\n            \"compute\",\n            \"cluster-a\"\n          ],\n          \"notes\": [\n            \"Dell PowerEdge R750\",\n            \"512GB RAM\",\n            \"vSphere 8.0\"\n          ],\n          \"mac\": \"00:50:56:AA:01:03\",\n          \"rackUnit\": 32,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"esxi-host-04\": {\n          \"shape\": \"server\",\n          \"name\": \"ESXi Host 04\",\n          \"ip\": \"10.10.0.14\",\n          \"role\": \"Hypervisor\",\n          \"tags\": [\n            \"vmware\",\n            \"compute\",\n            \"cluster-a\"\n          ],\n          \"notes\": [\n            \"Dell PowerEdge R750\",\n            \"512GB RAM\",\n            \"vSphere 8.0\"\n          ],\n          \"mac\": \"00:50:56:AA:01:04\",\n          \"rackUnit\": 29,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"tor-switch-a1\": {\n          \"shape\": \"switch\",\n          \"name\": \"ToR Switch A1\",\n          \"ip\": \"10.10.0.1\",\n          \"role\": \"Top of Rack\",\n          \"tags\": [\n            \"tor\",\n            \"access\",\n            \"rack-a1\"\n          ],\n          \"notes\": [\n            \"Cisco Nexus 93180YC-FX\",\n            \"48x25G ports\"\n          ],\n          \"mac\": \"00:1A:2B:3C:5D:01\",\n          \"rackUnit\": 42,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"esxi-host-05\": {\n          \"shape\": \"server\",\n          \"name\": \"ESXi Host 05\",\n          \"ip\": \"10.10.1.11\",\n          \"role\": \"Hypervisor\",\n          \"tags\": [\n            \"vmware\",\n            \"compute\",\n            \"cluster-b\"\n          ],\n          \"notes\": [\n            \"Dell PowerEdge R750\",\n            \"768GB RAM\",\n            \"vSphere 8.0\"\n          ],\n          \"mac\": \"00:50:56:AA:02:01\",\n          \"rackUnit\": 38,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"esxi-host-06\": {\n          \"shape\": \"server\",\n          \"name\": \"ESXi Host 06\",\n          \"ip\": \"10.10.1.12\",\n          \"role\": \"Hypervisor\",\n          \"tags\": [\n            \"vmware\",\n            \"compute\",\n            \"cluster-b\"\n          ],\n          \"notes\": [\n            \"Dell PowerEdge R750\",\n            \"768GB RAM\",\n            \"vSphere 8.0\"\n          ],\n          \"mac\": \"00:50:56:AA:02:02\",\n          \"rackUnit\": 35,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"esxi-host-07\": {\n          \"shape\": \"server\",\n          \"name\": \"ESXi Host 07\",\n          \"ip\": \"10.10.1.13\",\n          \"role\": \"Hypervisor\",\n          \"tags\": [\n            \"vmware\",\n            \"compute\",\n            \"cluster-b\"\n          ],\n          \"notes\": [\n            \"Dell PowerEdge R750\",\n            \"768GB RAM\",\n            \"vSphere 8.0\"\n          ],\n          \"mac\": \"00:50:56:AA:02:03\",\n          \"rackUnit\": 32,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"esxi-host-08\": {\n          \"shape\": \"server\",\n          \"name\": \"ESXi Host 08\",\n          \"ip\": \"10.10.1.14\",\n          \"role\": \"Hypervisor\",\n          \"tags\": [\n            \"vmware\",\n            \"compute\",\n            \"cluster-b\"\n          ],\n          \"notes\": [\n            \"Dell PowerEdge R750\",\n            \"768GB RAM\",\n            \"vSphere 8.0\"\n          ],\n          \"mac\": \"00:50:56:AA:02:04\",\n          \"rackUnit\": 29,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"tor-switch-a2\": {\n          \"shape\": \"switch\",\n          \"name\": \"ToR Switch A2\",\n          \"ip\": \"10.10.1.1\",\n          \"role\": \"Top of Rack\",\n          \"tags\": [\n            \"tor\",\n            \"access\",\n            \"rack-a2\"\n          ],\n          \"notes\": [\n            \"Cisco Nexus 93180YC-FX\",\n            \"48x25G ports\"\n          ],\n          \"mac\": \"00:1A:2B:3C:5D:02\",\n          \"rackUnit\": 42,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"san-primary\": {\n          \"shape\": \"database\",\n          \"name\": \"SAN Primary\",\n          \"ip\": \"10.10.2.10\",\n          \"role\": \"Primary Storage\",\n          \"tags\": [\n            \"storage\",\n            \"san\",\n            \"netapp\"\n          ],\n          \"notes\": [\n            \"NetApp AFF A400\",\n            \"500TB Raw\",\n            \"FC 32Gb\"\n          ],\n          \"mac\": \"00:A0:98:AA:01:01\",\n          \"rackUnit\": 36,\n          \"uHeight\": \"6\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-b1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"san-secondary\": {\n          \"shape\": \"database\",\n          \"name\": \"SAN Secondary\",\n          \"ip\": \"10.10.2.11\",\n          \"role\": \"Secondary Storage\",\n          \"tags\": [\n            \"storage\",\n            \"san\",\n            \"netapp\"\n          ],\n          \"notes\": [\n            \"NetApp AFF A400\",\n            \"500TB Raw\",\n            \"FC 32Gb\"\n          ],\n          \"mac\": \"00:A0:98:AA:01:02\",\n          \"rackUnit\": 28,\n          \"uHeight\": \"6\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-b1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"fc-switch-1\": {\n          \"shape\": \"switch\",\n          \"name\": \"FC Switch 1\",\n          \"ip\": \"10.10.2.1\",\n          \"role\": \"Fibre Channel\",\n          \"tags\": [\n            \"storage\",\n            \"fc\",\n            \"fabric-a\"\n          ],\n          \"notes\": [\n            \"Brocade G620\",\n            \"Fabric A\"\n          ],\n          \"mac\": \"00:1A:2B:FC:01:01\",\n          \"rackUnit\": 42,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-b1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"fc-switch-2\": {\n          \"shape\": \"switch\",\n          \"name\": \"FC Switch 2\",\n          \"ip\": \"10.10.2.2\",\n          \"role\": \"Fibre Channel\",\n          \"tags\": [\n            \"storage\",\n            \"fc\",\n            \"fabric-b\"\n          ],\n          \"notes\": [\n            \"Brocade G620\",\n            \"Fabric B\"\n          ],\n          \"mac\": \"00:1A:2B:FC:01:02\",\n          \"rackUnit\": 41,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-b1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"backup-server-1\": {\n          \"shape\": \"server\",\n          \"name\": \"Backup Server 1\",\n          \"ip\": \"10.10.3.10\",\n          \"role\": \"Backup Infrastructure\",\n          \"tags\": [\n            \"backup\",\n            \"veeam\",\n            \"protection\"\n          ],\n          \"notes\": [\n            \"Veeam Backup Server\",\n            \"Dell R740xd\",\n            \"200TB\"\n          ],\n          \"mac\": \"00:50:56:BB:01:01\",\n          \"rackUnit\": 36,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-b2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"backup-server-2\": {\n          \"shape\": \"server\",\n          \"name\": \"Backup Server 2\",\n          \"ip\": \"10.10.3.11\",\n          \"role\": \"Backup Infrastructure\",\n          \"tags\": [\n            \"backup\",\n            \"veeam\",\n            \"protection\"\n          ],\n          \"notes\": [\n            \"Veeam Backup Server\",\n            \"Dell R740xd\",\n            \"200TB\"\n          ],\n          \"mac\": \"00:50:56:BB:01:02\",\n          \"rackUnit\": 33,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-b2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"tape-library\": {\n          \"shape\": \"database\",\n          \"name\": \"Tape Library\",\n          \"ip\": \"10.10.3.20\",\n          \"role\": \"Archival Storage\",\n          \"tags\": [\n            \"backup\",\n            \"tape\",\n            \"lto9\"\n          ],\n          \"notes\": [\n            \"IBM TS4500\",\n            \"LTO-9\",\n            \"Long-term archive\"\n          ],\n          \"mac\": \"00:50:56:BB:02:01\",\n          \"rackUnit\": 20,\n          \"uHeight\": \"10\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-b2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"tor-switch-b1\": {\n          \"shape\": \"switch\",\n          \"name\": \"ToR Switch B1\",\n          \"ip\": \"10.10.2.3\",\n          \"role\": \"Top of Rack\",\n          \"tags\": [\n            \"tor\",\n            \"access\",\n            \"rack-b1\"\n          ],\n          \"notes\": [\n            \"Cisco Nexus 93180YC-FX\"\n          ],\n          \"mac\": \"00:1A:2B:3C:5D:03\",\n          \"rackUnit\": 40,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-b1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"tor-switch-b2\": {\n          \"shape\": \"switch\",\n          \"name\": \"ToR Switch B2\",\n          \"ip\": \"10.10.3.1\",\n          \"role\": \"Top of Rack\",\n          \"tags\": [\n            \"tor\",\n            \"access\",\n            \"rack-b2\"\n          ],\n          \"notes\": [\n            \"Cisco Nexus 93180YC-FX\"\n          ],\n          \"mac\": \"00:1A:2B:3C:5D:04\",\n          \"rackUnit\": 42,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-b2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"web-server-1\": {\n          \"shape\": \"server\",\n          \"name\": \"Web Server 1\",\n          \"ip\": \"172.16.0.11\",\n          \"role\": \"Web Frontend\",\n          \"tags\": [\n            \"dmz\",\n            \"web\",\n            \"nginx\"\n          ],\n          \"notes\": [\n            \"NGINX reverse proxy\",\n            \"Public facing\"\n          ],\n          \"mac\": \"00:50:56:CC:01:01\",\n          \"rackUnit\": 20,\n          \"uHeight\": \"1\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"dmz-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"web-server-2\": {\n          \"shape\": \"server\",\n          \"name\": \"Web Server 2\",\n          \"ip\": \"172.16.0.12\",\n          \"role\": \"Web Frontend\",\n          \"tags\": [\n            \"dmz\",\n            \"web\",\n            \"nginx\"\n          ],\n          \"notes\": [\n            \"NGINX reverse proxy\",\n            \"Public facing\"\n          ],\n          \"mac\": \"00:50:56:CC:01:02\",\n          \"rackUnit\": 18,\n          \"uHeight\": \"1\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"dmz-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"waf-1\": {\n          \"shape\": \"firewall\",\n          \"name\": \"WAF Appliance\",\n          \"ip\": \"172.16.0.5\",\n          \"role\": \"Web Application Firewall\",\n          \"tags\": [\n            \"dmz\",\n            \"security\",\n            \"waf\"\n          ],\n          \"notes\": [\n            \"F5 BIG-IP ASM\",\n            \"OWASP protection\"\n          ],\n          \"mac\": \"00:50:56:CC:02:01\",\n          \"rackUnit\": 22,\n          \"uHeight\": \"2\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"dmz-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"load-balancer-dmz\": {\n          \"shape\": \"switch\",\n          \"name\": \"DMZ Load Balancer\",\n          \"ip\": \"172.16.0.3\",\n          \"role\": \"Load Balancing\",\n          \"tags\": [\n            \"dmz\",\n            \"lb\",\n            \"f5\"\n          ],\n          \"notes\": [\n            \"F5 BIG-IP LTM\",\n            \"VIP: 172.16.0.100\"\n          ],\n          \"mac\": \"00:50:56:CC:03:01\",\n          \"rackUnit\": 16,\n          \"uHeight\": \"2\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"dmz-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"mail-gateway\": {\n          \"shape\": \"server\",\n          \"name\": \"Mail Gateway\",\n          \"ip\": \"172.16.0.25\",\n          \"role\": \"Email Security\",\n          \"tags\": [\n            \"dmz\",\n            \"email\",\n            \"security\"\n          ],\n          \"notes\": [\n            \"Proofpoint Email Gateway\",\n            \"Spam/malware filtering\"\n          ],\n          \"mac\": \"00:50:56:CC:04:01\",\n          \"rackUnit\": 14,\n          \"uHeight\": \"1\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"dmz-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dns-external-1\": {\n          \"shape\": \"circle\",\n          \"name\": \"External DNS 1\",\n          \"ip\": \"172.16.0.53\",\n          \"role\": \"External DNS\",\n          \"tags\": [\n            \"dmz\",\n            \"dns\",\n            \"public\"\n          ],\n          \"notes\": [\n            \"BIND DNS\",\n            \"Authoritative for corp.com\"\n          ],\n          \"mac\": \"00:50:56:CC:05:01\",\n          \"rackUnit\": 12,\n          \"uHeight\": \"1\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"dmz-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dns-external-2\": {\n          \"shape\": \"circle\",\n          \"name\": \"External DNS 2\",\n          \"ip\": \"172.16.0.54\",\n          \"role\": \"External DNS\",\n          \"tags\": [\n            \"dmz\",\n            \"dns\",\n            \"public\"\n          ],\n          \"notes\": [\n            \"BIND DNS\",\n            \"Secondary for corp.com\"\n          ],\n          \"mac\": \"00:50:56:CC:05:02\",\n          \"rackUnit\": 10,\n          \"uHeight\": \"1\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"dmz-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"vcenter\": {\n          \"shape\": \"server\",\n          \"name\": \"vCenter Server\",\n          \"ip\": \"192.168.100.10\",\n          \"role\": \"Virtualization Management\",\n          \"tags\": [\n            \"management\",\n            \"vmware\",\n            \"vcsa\"\n          ],\n          \"notes\": [\n            \"vCenter Server Appliance 8.0\",\n            \"Single SSO domain\"\n          ],\n          \"mac\": \"00:50:56:DD:01:01\",\n          \"rackUnit\": 20,\n          \"uHeight\": \"2\",\n          \"layer\": \"logical\",\n          \"assignedRack\": \"mgmt-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"nsx-manager\": {\n          \"shape\": \"server\",\n          \"name\": \"NSX Manager\",\n          \"ip\": \"192.168.100.15\",\n          \"role\": \"Network Virtualization\",\n          \"tags\": [\n            \"management\",\n            \"vmware\",\n            \"nsx\"\n          ],\n          \"notes\": [\n            \"NSX-T 4.1 Manager Cluster\"\n          ],\n          \"mac\": \"00:50:56:DD:02:01\",\n          \"rackUnit\": 17,\n          \"uHeight\": \"2\",\n          \"layer\": \"logical\",\n          \"assignedRack\": \"mgmt-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"siem-server\": {\n          \"shape\": \"server\",\n          \"name\": \"SIEM Server\",\n          \"ip\": \"192.168.100.50\",\n          \"role\": \"Security Monitoring\",\n          \"tags\": [\n            \"management\",\n            \"security\",\n            \"splunk\"\n          ],\n          \"notes\": [\n            \"Splunk Enterprise\",\n            \"Security monitoring\"\n          ],\n          \"mac\": \"00:50:56:DD:03:01\",\n          \"rackUnit\": 14,\n          \"uHeight\": \"2\",\n          \"layer\": \"logical\",\n          \"assignedRack\": \"mgmt-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"nms-server\": {\n          \"shape\": \"server\",\n          \"name\": \"Network Monitoring\",\n          \"ip\": \"192.168.100.60\",\n          \"role\": \"Network Management\",\n          \"tags\": [\n            \"management\",\n            \"monitoring\",\n            \"prtg\"\n          ],\n          \"notes\": [\n            \"PRTG Network Monitor\",\n            \"5000 sensors\"\n          ],\n          \"mac\": \"00:50:56:DD:04:01\",\n          \"rackUnit\": 11,\n          \"uHeight\": \"1\",\n          \"layer\": \"logical\",\n          \"assignedRack\": \"mgmt-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"jump-server\": {\n          \"shape\": \"server\",\n          \"name\": \"Jump Server\",\n          \"ip\": \"192.168.100.100\",\n          \"role\": \"Bastion Host\",\n          \"tags\": [\n            \"management\",\n            \"security\",\n            \"bastion\"\n          ],\n          \"notes\": [\n            \"Windows Server 2022\",\n            \"MFA enabled\"\n          ],\n          \"mac\": \"00:50:56:DD:05:01\",\n          \"rackUnit\": 9,\n          \"uHeight\": \"1\",\n          \"layer\": \"logical\",\n          \"assignedRack\": \"mgmt-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"ipam-server\": {\n          \"shape\": \"server\",\n          \"name\": \"IPAM/DDI\",\n          \"ip\": \"192.168.100.70\",\n          \"role\": \"IP Management\",\n          \"tags\": [\n            \"management\",\n            \"dns\",\n            \"dhcp\"\n          ],\n          \"notes\": [\n            \"Infoblox DDI\",\n            \"DNS/DHCP/IPAM\"\n          ],\n          \"mac\": \"00:50:56:DD:06:01\",\n          \"rackUnit\": 7,\n          \"uHeight\": \"2\",\n          \"layer\": \"logical\",\n          \"assignedRack\": \"mgmt-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"wlc-primary\": {\n          \"shape\": \"wifi\",\n          \"name\": \"WLC Primary\",\n          \"ip\": \"10.20.0.1\",\n          \"role\": \"Wireless Controller\",\n          \"tags\": [\n            \"wireless\",\n            \"cisco\",\n            \"9800\"\n          ],\n          \"notes\": [\n            \"Cisco C9800-40\",\n            \"Primary controller\"\n          ],\n          \"mac\": \"00:1A:2B:WL:01:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"wlc-secondary\": {\n          \"shape\": \"wifi\",\n          \"name\": \"WLC Secondary\",\n          \"ip\": \"10.20.0.2\",\n          \"role\": \"Wireless Controller\",\n          \"tags\": [\n            \"wireless\",\n            \"cisco\",\n            \"9800\"\n          ],\n          \"notes\": [\n            \"Cisco C9800-40\",\n            \"HA Secondary\"\n          ],\n          \"mac\": \"00:1A:2B:WL:01:02\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"mobile-zone-hq\": {\n          \"shape\": \"phone\",\n          \"name\": \"HQ Mobile Zone\",\n          \"ip\": \"10.20.10.0/24\",\n          \"role\": \"Mobile Device Zone\",\n          \"tags\": [\n            \"wireless\",\n            \"byod\",\n            \"mobile\"\n          ],\n          \"notes\": [\n            \"Corporate BYOD\",\n            \"MDM enrolled devices\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"mobile-zone-guest\": {\n          \"shape\": \"phone\",\n          \"name\": \"Guest WiFi Zone\",\n          \"ip\": \"10.30.0.0/24\",\n          \"role\": \"Guest Network\",\n          \"tags\": [\n            \"wireless\",\n            \"guest\",\n            \"isolated\"\n          ],\n          \"notes\": [\n            \"Captive portal\",\n            \"Internet only\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"mobile-zone-iot\": {\n          \"shape\": \"phone\",\n          \"name\": \"IoT Device Zone\",\n          \"ip\": \"10.40.0.0/24\",\n          \"role\": \"IoT Network\",\n          \"tags\": [\n            \"wireless\",\n            \"iot\",\n            \"building\"\n          ],\n          \"notes\": [\n            \"Building automation\",\n            \"Smart devices\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"branch-router-ny\": {\n          \"shape\": \"router\",\n          \"name\": \"NYC Branch Router\",\n          \"ip\": \"10.100.0.1\",\n          \"role\": \"Branch Gateway\",\n          \"tags\": [\n            \"branch\",\n            \"nyc\",\n            \"sd-wan\"\n          ],\n          \"notes\": [\n            \"Cisco Viptela vEdge\",\n            \"SD-WAN enabled\"\n          ],\n          \"mac\": \"00:1A:2B:BR:01:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"branch-router-la\": {\n          \"shape\": \"router\",\n          \"name\": \"LA Branch Router\",\n          \"ip\": \"10.101.0.1\",\n          \"role\": \"Branch Gateway\",\n          \"tags\": [\n            \"branch\",\n            \"la\",\n            \"sd-wan\"\n          ],\n          \"notes\": [\n            \"Cisco Viptela vEdge\",\n            \"SD-WAN enabled\"\n          ],\n          \"mac\": \"00:1A:2B:BR:02:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"branch-router-chi\": {\n          \"shape\": \"router\",\n          \"name\": \"Chicago Branch Router\",\n          \"ip\": \"10.102.0.1\",\n          \"role\": \"Branch Gateway\",\n          \"tags\": [\n            \"branch\",\n            \"chicago\",\n            \"sd-wan\"\n          ],\n          \"notes\": [\n            \"Cisco Viptela vEdge\",\n            \"SD-WAN enabled\"\n          ],\n          \"mac\": \"00:1A:2B:BR:03:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"branch-router-lon\": {\n          \"shape\": \"router\",\n          \"name\": \"London Branch Router\",\n          \"ip\": \"10.200.0.1\",\n          \"role\": \"Branch Gateway\",\n          \"tags\": [\n            \"branch\",\n            \"london\",\n            \"sd-wan\"\n          ],\n          \"notes\": [\n            \"Cisco Viptela vEdge\",\n            \"EMEA region\"\n          ],\n          \"mac\": \"00:1A:2B:BR:04:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"branch-router-tokyo\": {\n          \"shape\": \"router\",\n          \"name\": \"Tokyo Branch Router\",\n          \"ip\": \"10.201.0.1\",\n          \"role\": \"Branch Gateway\",\n          \"tags\": [\n            \"branch\",\n            \"tokyo\",\n            \"sd-wan\"\n          ],\n          \"notes\": [\n            \"Cisco Viptela vEdge\",\n            \"APAC region\"\n          ],\n          \"mac\": \"00:1A:2B:BR:05:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"cloud-aws\": {\n          \"shape\": \"cloud\",\n          \"name\": \"AWS Cloud\",\n          \"ip\": \"vpc-0a1b2c3d\",\n          \"role\": \"Public Cloud\",\n          \"tags\": [\n            \"cloud\",\n            \"aws\",\n            \"hybrid\"\n          ],\n          \"notes\": [\n            \"AWS US-East-1\",\n            \"VPC peering to HQ\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"logical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"cloud-azure\": {\n          \"shape\": \"cloud\",\n          \"name\": \"Azure Cloud\",\n          \"ip\": \"vnet-corp-prod\",\n          \"role\": \"Public Cloud\",\n          \"tags\": [\n            \"cloud\",\n            \"azure\",\n            \"hybrid\"\n          ],\n          \"notes\": [\n            \"Azure East US 2\",\n            \"ExpressRoute\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"logical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"cloud-gcp\": {\n          \"shape\": \"cloud\",\n          \"name\": \"GCP Cloud\",\n          \"ip\": \"vpc-gcp-corp\",\n          \"role\": \"Public Cloud\",\n          \"tags\": [\n            \"cloud\",\n            \"gcp\",\n            \"dev\"\n          ],\n          \"notes\": [\n            \"GCP us-central1\",\n            \"Dev/Test workloads\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"logical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"isp-primary\": {\n          \"shape\": \"globe\",\n          \"name\": \"ISP Primary\",\n          \"ip\": \"203.0.113.1\",\n          \"role\": \"Internet Uplink\",\n          \"tags\": [\n            \"wan\",\n            \"internet\",\n            \"primary\"\n          ],\n          \"notes\": [\n            \"AT&T MPLS\",\n            \"1 Gbps dedicated\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"isp-secondary\": {\n          \"shape\": \"globe\",\n          \"name\": \"ISP Secondary\",\n          \"ip\": \"198.51.100.1\",\n          \"role\": \"Internet Uplink\",\n          \"tags\": [\n            \"wan\",\n            \"internet\",\n            \"backup\"\n          ],\n          \"notes\": [\n            \"Verizon Business\",\n            \"500 Mbps backup\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dc-internal-1\": {\n          \"shape\": \"circle\",\n          \"name\": \"DC1 Int DNS\",\n          \"ip\": \"10.10.0.53\",\n          \"role\": \"Internal DNS/AD\",\n          \"tags\": [\n            \"dns\",\n            \"ad\",\n            \"dc1\"\n          ],\n          \"notes\": [\n            \"Windows Server 2022\",\n            \"Primary DC\"\n          ],\n          \"mac\": \"00:50:56:AD:01:01\",\n          \"rackUnit\": 26,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dc-internal-2\": {\n          \"shape\": \"circle\",\n          \"name\": \"DC2 Int DNS\",\n          \"ip\": \"10.10.1.53\",\n          \"role\": \"Internal DNS/AD\",\n          \"tags\": [\n            \"dns\",\n            \"ad\",\n            \"dc2\"\n          ],\n          \"notes\": [\n            \"Windows Server 2022\",\n            \"Secondary DC\"\n          ],\n          \"mac\": \"00:50:56:AD:01:02\",\n          \"rackUnit\": 26,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"app-server-1\": {\n          \"shape\": \"server\",\n          \"name\": \"App Server 01\",\n          \"ip\": \"10.10.0.101\",\n          \"role\": \"Application\",\n          \"tags\": [\n            \"app\",\n            \"iis\",\n            \"web\"\n          ],\n          \"notes\": [\n            \"Windows Server 2022\",\n            \"IIS Application\"\n          ],\n          \"mac\": \"00:50:56:AP:01:01\",\n          \"rackUnit\": 24,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"app-server-2\": {\n          \"shape\": \"server\",\n          \"name\": \"App Server 02\",\n          \"ip\": \"10.10.0.102\",\n          \"role\": \"Application\",\n          \"tags\": [\n            \"app\",\n            \"iis\",\n            \"web\"\n          ],\n          \"notes\": [\n            \"Windows Server 2022\",\n            \"IIS Application\"\n          ],\n          \"mac\": \"00:50:56:AP:01:02\",\n          \"rackUnit\": 22,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"db-server-1\": {\n          \"shape\": \"database\",\n          \"name\": \"SQL Server 01\",\n          \"ip\": \"10.10.0.201\",\n          \"role\": \"Database\",\n          \"tags\": [\n            \"db\",\n            \"sql\",\n            \"primary\"\n          ],\n          \"notes\": [\n            \"SQL Server 2022 Enterprise\",\n            \"AlwaysOn Primary\"\n          ],\n          \"mac\": \"00:50:56:DB:01:01\",\n          \"rackUnit\": 20,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"db-server-2\": {\n          \"shape\": \"database\",\n          \"name\": \"SQL Server 02\",\n          \"ip\": \"10.10.1.201\",\n          \"role\": \"Database\",\n          \"tags\": [\n            \"db\",\n            \"sql\",\n            \"secondary\"\n          ],\n          \"notes\": [\n            \"SQL Server 2022 Enterprise\",\n            \"AlwaysOn Secondary\"\n          ],\n          \"mac\": \"00:50:56:DB:01:02\",\n          \"rackUnit\": 24,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"k8s-master-1\": {\n          \"shape\": \"hexagon\",\n          \"name\": \"K8s Master 1\",\n          \"ip\": \"10.10.1.50\",\n          \"role\": \"Container Orchestration\",\n          \"tags\": [\n            \"kubernetes\",\n            \"master\",\n            \"container\"\n          ],\n          \"notes\": [\n            \"K8s Control Plane\",\n            \"etcd member\"\n          ],\n          \"mac\": \"00:50:56:K8:01:01\",\n          \"rackUnit\": 21,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"k8s-master-2\": {\n          \"shape\": \"hexagon\",\n          \"name\": \"K8s Master 2\",\n          \"ip\": \"10.10.1.51\",\n          \"role\": \"Container Orchestration\",\n          \"tags\": [\n            \"kubernetes\",\n            \"master\",\n            \"container\"\n          ],\n          \"notes\": [\n            \"K8s Control Plane\",\n            \"etcd member\"\n          ],\n          \"mac\": \"00:50:56:K8:01:02\",\n          \"rackUnit\": 19,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"k8s-master-3\": {\n          \"shape\": \"hexagon\",\n          \"name\": \"K8s Master 3\",\n          \"ip\": \"10.10.1.52\",\n          \"role\": \"Container Orchestration\",\n          \"tags\": [\n            \"kubernetes\",\n            \"master\",\n            \"container\"\n          ],\n          \"notes\": [\n            \"K8s Control Plane\",\n            \"etcd member\"\n          ],\n          \"mac\": \"00:50:56:K8:01:03\",\n          \"rackUnit\": 17,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"k8s-worker-1\": {\n          \"shape\": \"server\",\n          \"name\": \"K8s Worker 1\",\n          \"ip\": \"10.10.1.60\",\n          \"role\": \"Container Workload\",\n          \"tags\": [\n            \"kubernetes\",\n            \"worker\",\n            \"container\"\n          ],\n          \"notes\": [\n            \"K8s Worker Node\",\n            \"64GB RAM\"\n          ],\n          \"mac\": \"00:50:56:K8:02:01\",\n          \"rackUnit\": 15,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"k8s-worker-2\": {\n          \"shape\": \"server\",\n          \"name\": \"K8s Worker 2\",\n          \"ip\": \"10.10.1.61\",\n          \"role\": \"Container Workload\",\n          \"tags\": [\n            \"kubernetes\",\n            \"worker\",\n            \"container\"\n          ],\n          \"notes\": [\n            \"K8s Worker Node\",\n            \"64GB RAM\"\n          ],\n          \"mac\": \"00:50:56:K8:02:02\",\n          \"rackUnit\": 13,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"k8s-worker-3\": {\n          \"shape\": \"server\",\n          \"name\": \"K8s Worker 3\",\n          \"ip\": \"10.10.1.62\",\n          \"role\": \"Container Workload\",\n          \"tags\": [\n            \"kubernetes\",\n            \"worker\",\n            \"container\"\n          ],\n          \"notes\": [\n            \"K8s Worker Node\",\n            \"64GB RAM\"\n          ],\n          \"mac\": \"00:50:56:K8:02:03\",\n          \"rackUnit\": 11,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"k8s-worker-4\": {\n          \"shape\": \"server\",\n          \"name\": \"K8s Worker 4\",\n          \"ip\": \"10.10.1.63\",\n          \"role\": \"Container Workload\",\n          \"tags\": [\n            \"kubernetes\",\n            \"worker\",\n            \"container\"\n          ],\n          \"notes\": [\n            \"K8s Worker Node\",\n            \"64GB RAM\"\n          ],\n          \"mac\": \"00:50:56:K8:02:04\",\n          \"rackUnit\": 9,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"proxy-server-1\": {\n          \"shape\": \"server\",\n          \"name\": \"Proxy Server 1\",\n          \"ip\": \"10.5.0.10\",\n          \"role\": \"Web Proxy\",\n          \"tags\": [\n            \"proxy\",\n            \"squid\",\n            \"filtering\"\n          ],\n          \"notes\": [\n            \"Squid Proxy\",\n            \"Content filtering\"\n          ],\n          \"mac\": \"00:50:56:PX:01:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"proxy-server-2\": {\n          \"shape\": \"server\",\n          \"name\": \"Proxy Server 2\",\n          \"ip\": \"10.5.0.11\",\n          \"role\": \"Web Proxy\",\n          \"tags\": [\n            \"proxy\",\n            \"squid\",\n            \"filtering\"\n          ],\n          \"notes\": [\n            \"Squid Proxy\",\n            \"HA pair\"\n          ],\n          \"mac\": \"00:50:56:PX:01:02\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"vpn-concentrator\": {\n          \"shape\": \"firewall\",\n          \"name\": \"VPN Concentrator\",\n          \"ip\": \"10.0.5.1\",\n          \"role\": \"Remote Access VPN\",\n          \"tags\": [\n            \"vpn\",\n            \"remote\",\n            \"security\"\n          ],\n          \"notes\": [\n            \"Cisco ASA 5555-X\",\n            \"AnyConnect SSL VPN\"\n          ],\n          \"mac\": \"00:1A:2B:VP:01:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"nac-server\": {\n          \"shape\": \"server\",\n          \"name\": \"NAC Server\",\n          \"ip\": \"10.5.5.10\",\n          \"role\": \"Network Access Control\",\n          \"tags\": [\n            \"nac\",\n            \"ise\",\n            \"802.1x\"\n          ],\n          \"notes\": [\n            \"Cisco ISE 3.1\",\n            \"RADIUS/TACACS+\"\n          ],\n          \"mac\": \"00:50:56:NA:01:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"print-server\": {\n          \"shape\": \"server\",\n          \"name\": \"Print Server\",\n          \"ip\": \"10.10.0.150\",\n          \"role\": \"Print Services\",\n          \"tags\": [\n            \"print\",\n            \"windows\",\n            \"services\"\n          ],\n          \"notes\": [\n            \"Windows Print Server\",\n            \"50+ printers\"\n          ],\n          \"mac\": \"00:50:56:PR:01:01\",\n          \"rackUnit\": 18,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"file-server\": {\n          \"shape\": \"database\",\n          \"name\": \"File Server\",\n          \"ip\": \"10.10.0.160\",\n          \"role\": \"File Services\",\n          \"tags\": [\n            \"file\",\n            \"smb\",\n            \"dfs\"\n          ],\n          \"notes\": [\n            \"Windows File Server\",\n            \"DFS namespace\"\n          ],\n          \"mac\": \"00:50:56:FS:01:01\",\n          \"rackUnit\": 16,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"ca-server\": {\n          \"shape\": \"server\",\n          \"name\": \"Certificate Authority\",\n          \"ip\": \"192.168.100.80\",\n          \"role\": \"PKI Infrastructure\",\n          \"tags\": [\n            \"pki\",\n            \"ca\",\n            \"security\"\n          ],\n          \"notes\": [\n            \"Windows CA\",\n            \"Enterprise Root CA\"\n          ],\n          \"mac\": \"00:50:56:CA:01:01\",\n          \"rackUnit\": 5,\n          \"uHeight\": \"1\",\n          \"layer\": \"logical\",\n          \"assignedRack\": \"mgmt-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"sccm-server\": {\n          \"shape\": \"server\",\n          \"name\": \"SCCM Server\",\n          \"ip\": \"192.168.100.90\",\n          \"role\": \"Endpoint Management\",\n          \"tags\": [\n            \"sccm\",\n            \"patching\",\n            \"software\"\n          ],\n          \"notes\": [\n            \"MECM Primary Site\",\n            \"Software deployment\"\n          ],\n          \"mac\": \"00:50:56:SC:01:01\",\n          \"rackUnit\": 3,\n          \"uHeight\": \"2\",\n          \"layer\": \"logical\",\n          \"assignedRack\": \"mgmt-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"voip-cluster\": {\n          \"shape\": \"phone\",\n          \"name\": \"VoIP Cluster\",\n          \"ip\": \"10.50.0.0/24\",\n          \"role\": \"Voice Services\",\n          \"tags\": [\n            \"voip\",\n            \"cisco\",\n            \"ucm\"\n          ],\n          \"notes\": [\n            \"Cisco UCM Cluster\",\n            \"3000 endpoints\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"application\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"video-conf\": {\n          \"shape\": \"laptop\",\n          \"name\": \"Video Conference\",\n          \"ip\": \"10.51.0.0/24\",\n          \"role\": \"Video Services\",\n          \"tags\": [\n            \"video\",\n            \"webex\",\n            \"teams\"\n          ],\n          \"notes\": [\n            \"Webex/Teams integration\",\n            \"Meeting rooms\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"application\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"security-cameras\": {\n          \"shape\": \"camera\",\n          \"name\": \"Security Cameras\",\n          \"ip\": \"10.60.0.0/24\",\n          \"role\": \"Physical Security\",\n          \"tags\": [\n            \"cctv\",\n            \"surveillance\",\n            \"security\"\n          ],\n          \"notes\": [\n            \"150+ IP cameras\",\n            \"30-day retention\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"nvr-cluster\": {\n          \"shape\": \"server\",\n          \"name\": \"NVR Cluster\",\n          \"ip\": \"10.60.0.10\",\n          \"role\": \"Video Recording\",\n          \"tags\": [\n            \"nvr\",\n            \"surveillance\",\n            \"storage\"\n          ],\n          \"notes\": [\n            \"Milestone XProtect\",\n            \"500TB storage\"\n          ],\n          \"mac\": \"00:50:56:NV:01:01\",\n          \"rackUnit\": 15,\n          \"uHeight\": \"4\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-b2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dev-server-1\": {\n          \"shape\": \"server\",\n          \"name\": \"Dev Server 1\",\n          \"ip\": \"10.80.0.10\",\n          \"role\": \"Development\",\n          \"tags\": [\n            \"dev\",\n            \"gitlab\",\n            \"ci-cd\",\n            {\n              \"type\": \"icon\",\n              \"library\": \"selfhst\",\n              \"name\": \"dokku\"\n            }\n          ],\n          \"notes\": [\n            \"GitLab Server\",\n            \"CI/CD pipelines\"\n          ],\n          \"mac\": \"00:50:56:DV:01:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"application\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dev-server-2\": {\n          \"shape\": \"server\",\n          \"name\": \"Dev Server 2\",\n          \"ip\": \"10.80.0.11\",\n          \"role\": \"Development\",\n          \"tags\": [\n            \"dev\",\n            \"jenkins\",\n            \"ci-cd\"\n          ],\n          \"notes\": [\n            \"Jenkins Server\",\n            \"Build automation\"\n          ],\n          \"mac\": \"00:50:56:DV:01:02\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"application\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"test-environment\": {\n          \"shape\": \"hexagon\",\n          \"name\": \"Test Environment\",\n          \"ip\": \"10.81.0.0/24\",\n          \"role\": \"QA/Testing\",\n          \"tags\": [\n            \"test\",\n            \"qa\",\n            \"staging\"\n          ],\n          \"notes\": [\n            \"Staging environment\",\n            \"Pre-prod validation\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"application\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"erp-system\": {\n          \"shape\": \"database\",\n          \"name\": \"ERP System\",\n          \"ip\": \"10.90.0.10\",\n          \"role\": \"Business Application\",\n          \"tags\": [\n            \"erp\",\n            \"sap\",\n            \"business\"\n          ],\n          \"notes\": [\n            \"SAP S/4HANA\",\n            \"Financial/HR systems\"\n          ],\n          \"mac\": \"00:50:56:ER:01:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"4\",\n          \"layer\": \"application\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"crm-system\": {\n          \"shape\": \"database\",\n          \"name\": \"CRM System\",\n          \"ip\": \"10.91.0.10\",\n          \"role\": \"Business Application\",\n          \"tags\": [\n            \"crm\",\n            \"salesforce\",\n            \"business\"\n          ],\n          \"notes\": [\n            \"Salesforce integration\",\n            \"Sales/Marketing\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"application\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"endpoint-1000\": {\n          \"shape\": \"laptop\",\n          \"name\": \"Corporate Endpoints\",\n          \"ip\": \"10.70.0.0/22\",\n          \"role\": \"User Workstations\",\n          \"tags\": [\n            \"endpoints\",\n            \"workstations\",\n            \"users\"\n          ],\n          \"notes\": [\n            \"~1000 corporate laptops\",\n            \"Windows 11\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dist-switch-floor1\": {\n          \"shape\": \"switch\",\n          \"name\": \"Floor 1 Switch\",\n          \"ip\": \"10.1.1.1\",\n          \"role\": \"Distribution\",\n          \"tags\": [\n            \"distribution\",\n            \"floor-1\",\n            \"access\"\n          ],\n          \"notes\": [\n            \"Cisco C9300-48P\",\n            \"PoE+ enabled\"\n          ],\n          \"mac\": \"00:1A:2B:FL:01:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dist-switch-floor2\": {\n          \"shape\": \"switch\",\n          \"name\": \"Floor 2 Switch\",\n          \"ip\": \"10.1.2.1\",\n          \"role\": \"Distribution\",\n          \"tags\": [\n            \"distribution\",\n            \"floor-2\",\n            \"access\"\n          ],\n          \"notes\": [\n            \"Cisco C9300-48P\",\n            \"PoE+ enabled\"\n          ],\n          \"mac\": \"00:1A:2B:FL:02:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dist-switch-floor3\": {\n          \"shape\": \"switch\",\n          \"name\": \"Floor 3 Switch\",\n          \"ip\": \"10.1.3.1\",\n          \"role\": \"Distribution\",\n          \"tags\": [\n            \"distribution\",\n            \"floor-3\",\n            \"access\"\n          ],\n          \"notes\": [\n            \"Cisco C9300-48P\",\n            \"PoE+ enabled\"\n          ],\n          \"mac\": \"00:1A:2B:FL:03:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dist-switch-floor4\": {\n          \"shape\": \"switch\",\n          \"name\": \"Floor 4 Switch\",\n          \"ip\": \"10.1.4.1\",\n          \"role\": \"Distribution\",\n          \"tags\": [\n            \"distribution\",\n            \"floor-4\",\n            \"access\"\n          ],\n          \"notes\": [\n            \"Cisco C9300-48P\",\n            \"PoE+ enabled\"\n          ],\n          \"mac\": \"00:1A:2B:FL:04:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"ap-floor1-zone1\": {\n          \"shape\": \"wifi\",\n          \"name\": \"AP Floor 1 Zone 1\",\n          \"ip\": \"10.20.1.10\",\n          \"role\": \"Wireless Access\",\n          \"tags\": [\n            \"wifi\",\n            \"ap\",\n            \"floor-1\"\n          ],\n          \"notes\": [\n            \"Cisco 9120AX\",\n            \"Wi-Fi 6\"\n          ],\n          \"mac\": \"00:1A:2B:AP:01:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"ap-floor2-zone1\": {\n          \"shape\": \"wifi\",\n          \"name\": \"AP Floor 2 Zone 1\",\n          \"ip\": \"10.20.2.10\",\n          \"role\": \"Wireless Access\",\n          \"tags\": [\n            \"wifi\",\n            \"ap\",\n            \"floor-2\"\n          ],\n          \"notes\": [\n            \"Cisco 9120AX\",\n            \"Wi-Fi 6\"\n          ],\n          \"mac\": \"00:1A:2B:AP:02:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"ap-floor3-zone1\": {\n          \"shape\": \"wifi\",\n          \"name\": \"AP Floor 3 Zone 1\",\n          \"ip\": \"10.20.3.10\",\n          \"role\": \"Wireless Access\",\n          \"tags\": [\n            \"wifi\",\n            \"ap\",\n            \"floor-3\"\n          ],\n          \"notes\": [\n            \"Cisco 9120AX\",\n            \"Wi-Fi 6\"\n          ],\n          \"mac\": \"00:1A:2B:AP:03:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"ap-floor4-zone1\": {\n          \"shape\": \"wifi\",\n          \"name\": \"AP Floor 4 Zone 1\",\n          \"ip\": \"10.20.4.10\",\n          \"role\": \"Wireless Access\",\n          \"tags\": [\n            \"wifi\",\n            \"ap\",\n            \"floor-4\"\n          ],\n          \"notes\": [\n            \"Cisco 9120AX\",\n            \"Wi-Fi 6\"\n          ],\n          \"mac\": \"00:1A:2B:AP:04:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"ups-dc-1\": {\n          \"shape\": \"rectangle\",\n          \"name\": \"UPS DC-1\",\n          \"ip\": \"192.168.200.10\",\n          \"role\": \"Power Management\",\n          \"tags\": [\n            \"power\",\n            \"ups\",\n            \"datacenter\"\n          ],\n          \"notes\": [\n            \"APC Symmetra\",\n            \"80kVA\",\n            \"30 min runtime\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"ups-dc-2\": {\n          \"shape\": \"rectangle\",\n          \"name\": \"UPS DC-2\",\n          \"ip\": \"192.168.200.11\",\n          \"role\": \"Power Management\",\n          \"tags\": [\n            \"power\",\n            \"ups\",\n            \"datacenter\"\n          ],\n          \"notes\": [\n            \"APC Symmetra\",\n            \"80kVA\",\n            \"Redundant\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"pdu-rack-a1\": {\n          \"shape\": \"rectangle\",\n          \"name\": \"PDU Rack A1\",\n          \"ip\": \"192.168.200.21\",\n          \"role\": \"Power Distribution\",\n          \"tags\": [\n            \"power\",\n            \"pdu\",\n            \"rack-a1\"\n          ],\n          \"notes\": [\n            \"APC Switched PDU\",\n            \"Per-outlet metering\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": 1,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"pdu-rack-a2\": {\n          \"shape\": \"rectangle\",\n          \"name\": \"PDU Rack A2\",\n          \"ip\": \"192.168.200.22\",\n          \"role\": \"Power Distribution\",\n          \"tags\": [\n            \"power\",\n            \"pdu\",\n            \"rack-a2\"\n          ],\n          \"notes\": [\n            \"APC Switched PDU\",\n            \"Per-outlet metering\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": 1,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"cooling-1\": {\n          \"shape\": \"rectangle\",\n          \"name\": \"CRAC Unit 1\",\n          \"ip\": \"192.168.200.30\",\n          \"role\": \"Cooling\",\n          \"tags\": [\n            \"cooling\",\n            \"hvac\",\n            \"datacenter\"\n          ],\n          \"notes\": [\n            \"Liebert CRV\",\n            \"Row-based cooling\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"cooling-2\": {\n          \"shape\": \"rectangle\",\n          \"name\": \"CRAC Unit 2\",\n          \"ip\": \"192.168.200.31\",\n          \"role\": \"Cooling\",\n          \"tags\": [\n            \"cooling\",\n            \"hvac\",\n            \"datacenter\"\n          ],\n          \"notes\": [\n            \"Liebert CRV\",\n            \"N+1 redundancy\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"camera-a\": {\n          \"shape\": \"camera\",\n          \"name\": \"camera A\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"ping\": {\n            \"enabled\": false,\n            \"protocol\": \"http\",\n            \"customUrl\": \"\",\n            \"timeout\": 3000,\n            \"status\": \"unknown\",\n            \"lastCheck\": null\n          },\n          \"locked\": false,\n          \"groupId\": null,\n          \"fovEnabled\": true,\n          \"fovRotation\": 104,\n          \"fovDistance\": 500,\n          \"fovSweep\": 60,\n          \"fovSpeed\": 10,\n          \"fovAnimate\": true\n        },\n        \"camera-a-copy\": {\n          \"shape\": \"camera\",\n          \"name\": \"camera B\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"ping\": {\n            \"enabled\": false,\n            \"protocol\": \"http\",\n            \"customUrl\": \"\",\n            \"timeout\": 3000,\n            \"status\": \"unknown\",\n            \"lastCheck\": null\n          },\n          \"locked\": false,\n          \"groupId\": null,\n          \"fovEnabled\": true,\n          \"fovRotation\": 162,\n          \"fovDistance\": 500,\n          \"fovSweep\": 60,\n          \"fovSpeed\": 10,\n          \"fovAnimate\": false\n        }\n      },\n      \"edges\": {\n        \"list\": [\n          {\n            \"id\": \"isp1-router1\",\n            \"from\": \"isp-primary\",\n            \"to\": \"core-router-1\",\n            \"width\": 6,\n            \"color\": \"#10b981\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"Primary WAN link\"\n            ],\n            \"fromPort\": \"Gi0/0\",\n            \"toPort\": \"Gi1/0/1\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"isp2-router2\",\n            \"from\": \"isp-secondary\",\n            \"to\": \"core-router-2\",\n            \"width\": 6,\n            \"color\": \"#10b981\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"Backup WAN link\"\n            ],\n            \"fromPort\": \"Gi0/0\",\n            \"toPort\": \"Gi1/0/1\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"router1-router2\",\n            \"from\": \"core-router-1\",\n            \"to\": \"core-router-2\",\n            \"width\": 4,\n            \"color\": \"#f59e0b\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"HSRP Peering\"\n            ],\n            \"fromPort\": \"Gi1/0/24\",\n            \"toPort\": \"Gi1/0/24\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"router1-fw1\",\n            \"from\": \"core-router-1\",\n            \"to\": \"fw-external-1\",\n            \"width\": 4,\n            \"color\": \"#ef4444\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"router2-fw2\",\n            \"from\": \"core-router-2\",\n            \"to\": \"fw-external-2\",\n            \"width\": 4,\n            \"color\": \"#ef4444\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"fw1-fw2\",\n            \"from\": \"fw-external-1\",\n            \"to\": \"fw-external-2\",\n            \"width\": 3,\n            \"color\": \"#f59e0b\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"HA heartbeat\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dashed\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"fw1-coresw1\",\n            \"from\": \"fw-external-1\",\n            \"to\": \"core-switch-1\",\n            \"width\": 4,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"fw2-coresw2\",\n            \"from\": \"fw-external-2\",\n            \"to\": \"core-switch-2\",\n            \"width\": 4,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-coresw2\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"core-switch-2\",\n            \"width\": 5,\n            \"color\": \"#3b82f6\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"VPC peer-link\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-fwint\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"fw-internal\",\n            \"width\": 3,\n            \"color\": \"#ef4444\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw2-fwint\",\n            \"from\": \"core-switch-2\",\n            \"to\": \"fw-internal\",\n            \"width\": 3,\n            \"color\": \"#ef4444\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-racka1\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"dc-rack-a1\",\n            \"width\": 4,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw2-racka1\",\n            \"from\": \"core-switch-2\",\n            \"to\": \"dc-rack-a1\",\n            \"width\": 4,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-racka2\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"dc-rack-a2\",\n            \"width\": 4,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw2-racka2\",\n            \"from\": \"core-switch-2\",\n            \"to\": \"dc-rack-a2\",\n            \"width\": 4,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-rackb1\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"dc-rack-b1\",\n            \"width\": 4,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw2-rackb1\",\n            \"from\": \"core-switch-2\",\n            \"to\": \"dc-rack-b1\",\n            \"width\": 4,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-rackb2\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"dc-rack-b2\",\n            \"width\": 4,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw2-rackb2\",\n            \"from\": \"core-switch-2\",\n            \"to\": \"dc-rack-b2\",\n            \"width\": 4,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"fw1-dmz\",\n            \"from\": \"fw-external-1\",\n            \"to\": \"dmz-rack\",\n            \"width\": 3,\n            \"color\": \"#f59e0b\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"DMZ segment\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"fw2-dmz\",\n            \"from\": \"fw-external-2\",\n            \"to\": \"dmz-rack\",\n            \"width\": 3,\n            \"color\": \"#f59e0b\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"DMZ segment\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-mgmt\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"mgmt-rack\",\n            \"width\": 3,\n            \"color\": \"#8b5cf6\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"OOB management\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-wlc1\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"wlc-primary\",\n            \"width\": 3,\n            \"color\": \"#06b6d4\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw2-wlc2\",\n            \"from\": \"core-switch-2\",\n            \"to\": \"wlc-secondary\",\n            \"width\": 3,\n            \"color\": \"#06b6d4\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\",\n            \"animate\": true\n          },\n          {\n            \"id\": \"wlc1-wlc2\",\n            \"from\": \"wlc-primary\",\n            \"to\": \"wlc-secondary\",\n            \"width\": 2,\n            \"color\": \"#f59e0b\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"HA pair\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dashed\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"wlc1-mobile-hq\",\n            \"from\": \"wlc-primary\",\n            \"to\": \"mobile-zone-hq\",\n            \"width\": 2,\n            \"color\": \"#06b6d4\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"wlc1-mobile-guest\",\n            \"from\": \"wlc-primary\",\n            \"to\": \"mobile-zone-guest\",\n            \"width\": 2,\n            \"color\": \"#06b6d4\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"wlc1-mobile-iot\",\n            \"from\": \"wlc-primary\",\n            \"to\": \"mobile-zone-iot\",\n            \"width\": 2,\n            \"color\": \"#06b6d4\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"router1-branch-ny\",\n            \"from\": \"core-router-1\",\n            \"to\": \"branch-router-ny\",\n            \"width\": 3,\n            \"color\": \"#a855f7\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"SD-WAN tunnel\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dashed\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"router1-branch-la\",\n            \"from\": \"core-router-1\",\n            \"to\": \"branch-router-la\",\n            \"width\": 3,\n            \"color\": \"#a855f7\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"SD-WAN tunnel\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dashed\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"router1-branch-chi\",\n            \"from\": \"core-router-1\",\n            \"to\": \"branch-router-chi\",\n            \"width\": 3,\n            \"color\": \"#a855f7\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"SD-WAN tunnel\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dashed\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"router1-branch-lon\",\n            \"from\": \"core-router-1\",\n            \"to\": \"branch-router-lon\",\n            \"width\": 3,\n            \"color\": \"#a855f7\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"SD-WAN tunnel\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dashed\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"router1-branch-tokyo\",\n            \"from\": \"core-router-1\",\n            \"to\": \"branch-router-tokyo\",\n            \"width\": 3,\n            \"color\": \"#a855f7\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"SD-WAN tunnel\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dashed\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"router1-aws\",\n            \"from\": \"core-router-1\",\n            \"to\": \"cloud-aws\",\n            \"width\": 3,\n            \"color\": \"#f97316\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"Direct Connect\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dashed\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"router2-azure\",\n            \"from\": \"core-router-2\",\n            \"to\": \"cloud-azure\",\n            \"width\": 3,\n            \"color\": \"#0ea5e9\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"ExpressRoute\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dashed\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"fwint-gcp\",\n            \"from\": \"fw-internal\",\n            \"to\": \"cloud-gcp\",\n            \"width\": 2,\n            \"color\": \"#22c55e\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"VPN tunnel\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dashed\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-floor1\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"dist-switch-floor1\",\n            \"width\": 3,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-floor2\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"dist-switch-floor2\",\n            \"width\": 3,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw2-floor3\",\n            \"from\": \"core-switch-2\",\n            \"to\": \"dist-switch-floor3\",\n            \"width\": 3,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw2-floor4\",\n            \"from\": \"core-switch-2\",\n            \"to\": \"dist-switch-floor4\",\n            \"width\": 3,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"floor1-endpoints\",\n            \"from\": \"dist-switch-floor1\",\n            \"to\": \"endpoint-1000\",\n            \"width\": 2,\n            \"color\": \"#94a3b8\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"floor1-ap1\",\n            \"from\": \"dist-switch-floor1\",\n            \"to\": \"ap-floor1-zone1\",\n            \"width\": 2,\n            \"color\": \"#06b6d4\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"floor2-ap2\",\n            \"from\": \"dist-switch-floor2\",\n            \"to\": \"ap-floor2-zone1\",\n            \"width\": 2,\n            \"color\": \"#06b6d4\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"floor3-ap3\",\n            \"from\": \"dist-switch-floor3\",\n            \"to\": \"ap-floor3-zone1\",\n            \"width\": 2,\n            \"color\": \"#06b6d4\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"floor4-ap4\",\n            \"from\": \"dist-switch-floor4\",\n            \"to\": \"ap-floor4-zone1\",\n            \"width\": 2,\n            \"color\": \"#06b6d4\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"fwint-proxy1\",\n            \"from\": \"fw-internal\",\n            \"to\": \"proxy-server-1\",\n            \"width\": 2,\n            \"color\": \"#ef4444\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"fwint-proxy2\",\n            \"from\": \"fw-internal\",\n            \"to\": \"proxy-server-2\",\n            \"width\": 2,\n            \"color\": \"#ef4444\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"fwext1-vpn\",\n            \"from\": \"fw-external-1\",\n            \"to\": \"vpn-concentrator\",\n            \"width\": 3,\n            \"color\": \"#8b5cf6\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-nac\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"nac-server\",\n            \"width\": 2,\n            \"color\": \"#f59e0b\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-voip\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"voip-cluster\",\n            \"width\": 3,\n            \"color\": \"#22c55e\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw2-video\",\n            \"from\": \"core-switch-2\",\n            \"to\": \"video-conf\",\n            \"width\": 3,\n            \"color\": \"#22c55e\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-cameras\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"security-cameras\",\n            \"width\": 2,\n            \"color\": \"#94a3b8\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"fwint-dev1\",\n            \"from\": \"fw-internal\",\n            \"to\": \"dev-server-1\",\n            \"width\": 2,\n            \"color\": \"#a855f7\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"fwint-dev2\",\n            \"from\": \"fw-internal\",\n            \"to\": \"dev-server-2\",\n            \"width\": 2,\n            \"color\": \"#a855f7\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\",\n            \"animate\": true,\n            \"animationSpeed\": \"1.5\"\n          },\n          {\n            \"id\": \"fwint-test\",\n            \"from\": \"fw-internal\",\n            \"to\": \"test-environment\",\n            \"width\": 2,\n            \"color\": \"#a855f7\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-erp\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"erp-system\",\n            \"width\": 3,\n            \"color\": \"#f59e0b\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"fwext1-crm\",\n            \"from\": \"fw-external-1\",\n            \"to\": \"crm-system\",\n            \"width\": 2,\n            \"color\": \"#f59e0b\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"Salesforce cloud\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dashed\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"ups1-racka1\",\n            \"from\": \"ups-dc-1\",\n            \"to\": \"dc-rack-a1\",\n            \"width\": 2,\n            \"color\": \"#fbbf24\",\n            \"direction\": \"forward\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"Power feed A\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"ups2-racka2\",\n            \"from\": \"ups-dc-2\",\n            \"to\": \"dc-rack-a2\",\n            \"width\": 2,\n            \"color\": \"#fbbf24\",\n            \"direction\": \"forward\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"Power feed B\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"ups1-rackb1\",\n            \"from\": \"ups-dc-1\",\n            \"to\": \"dc-rack-b1\",\n            \"width\": 2,\n            \"color\": \"#fbbf24\",\n            \"direction\": \"forward\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"Power feed A\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\",\n            \"animate\": true,\n            \"animationSpeed\": \"4\"\n          },\n          {\n            \"id\": \"ups2-rackb2\",\n            \"from\": \"ups-dc-2\",\n            \"to\": \"dc-rack-b2\",\n            \"width\": 2,\n            \"color\": \"#fbbf24\",\n            \"direction\": \"forward\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"Power feed B\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"cooling1-racka1\",\n            \"from\": \"cooling-1\",\n            \"to\": \"dc-rack-a1\",\n            \"width\": 2,\n            \"color\": \"#38bdf8\",\n            \"direction\": \"forward\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"Cooling zone\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dotted\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"cooling2-rackb1\",\n            \"from\": \"cooling-2\",\n            \"to\": \"dc-rack-b1\",\n            \"width\": 2,\n            \"color\": \"#38bdf8\",\n            \"direction\": \"forward\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"Cooling zone\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dotted\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"custom-1765237881452\",\n            \"type\": \"custom\",\n            \"color\": \"#c800ff\",\n            \"width\": 4,\n            \"lineStyle\": \"solid\",\n            \"direction\": \"forward\",\n            \"points\": [\n              {\n                \"x\": 3492.3994140625,\n                \"y\": 1526.9556884765625\n              },\n              {\n                \"x\": 3500.609619140625,\n                \"y\": 1830.7386474609375\n              },\n              {\n                \"x\": 3303.561279296875,\n                \"y\": 1732.2144775390625\n              }\n            ],\n            \"notes\": [],\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"custom-1765239355462\",\n            \"type\": \"custom\",\n            \"color\": \"#f97316\",\n            \"width\": 4,\n            \"lineStyle\": \"solid\",\n            \"direction\": \"forward\",\n            \"points\": [\n              {\n                \"x\": 2467.182861328125,\n                \"y\": 156.12173461914062\n              },\n              {\n                \"x\": 2146.36376953125,\n                \"y\": 146.32574462890625\n              },\n              {\n                \"x\": 2305.548828125,\n                \"y\": 244.28573608398438\n              }\n            ],\n            \"notes\": [],\n            \"routing\": \"orthogonal\"\n          }\n        ]\n      },\n      \"positions\": {\n        \"core-router-1\": {\n          \"x\": 3720.166015625,\n          \"y\": 245.9932403564453\n        },\n        \"core-router-2\": {\n          \"x\": 2499.883407638303,\n          \"y\": 329.99503430389154\n        },\n        \"fw-external-1\": {\n          \"x\": 3221.7385182723783,\n          \"y\": 1016.1364499992887\n        },\n        \"fw-external-2\": {\n          \"x\": 1915.5213706410505,\n          \"y\": 224.43528858865443\n        },\n        \"fw-internal\": {\n          \"x\": 1746.9168185079352,\n          \"y\": 477.5300527221864\n        },\n        \"core-switch-1\": {\n          \"x\": 449.39860669455675,\n          \"y\": 384.4578707617695\n        },\n        \"core-switch-2\": {\n          \"x\": 761.1664921394672,\n          \"y\": 180.89283910873155\n        },\n        \"dc-rack-a1\": {\n          \"x\": 783.7017241128451,\n          \"y\": 647.4086870405963\n        },\n        \"dc-rack-a2\": {\n          \"x\": 209.25701628255229,\n          \"y\": 228.01593190351014\n        },\n        \"dc-rack-b1\": {\n          \"x\": 3184.3186625759854,\n          \"y\": 1627.4495531027196\n        },\n        \"dc-rack-b2\": {\n          \"x\": 245.37065918741246,\n          \"y\": 499.6191264194081\n        },\n        \"dmz-rack\": {\n          \"x\": 2176.4105289561007,\n          \"y\": 610.8312056412005\n        },\n        \"mgmt-rack\": {\n          \"x\": 1601.2987201807314,\n          \"y\": 1281.4753424975324\n        },\n        \"esxi-host-01\": {\n          \"x\": 2162.2166789540615,\n          \"y\": 2608.110619289529\n        },\n        \"esxi-host-02\": {\n          \"x\": 2205.94717202368,\n          \"y\": 2689.67539624076\n        },\n        \"esxi-host-03\": {\n          \"x\": 2154.6015436939074,\n          \"y\": 2771.203009774913\n        },\n        \"esxi-host-04\": {\n          \"x\": 2195.986926025096,\n          \"y\": 2845\n        },\n        \"tor-switch-a1\": {\n          \"x\": 2146.8943639962963,\n          \"y\": 2845\n        },\n        \"esxi-host-05\": {\n          \"x\": 2185.9099961569727,\n          \"y\": 2845\n        },\n        \"esxi-host-06\": {\n          \"x\": 2139.099728450725,\n          \"y\": 2845\n        },\n        \"esxi-host-07\": {\n          \"x\": 2175.7223818764883,\n          \"y\": 2845\n        },\n        \"esxi-host-08\": {\n          \"x\": 2131.2222777148922,\n          \"y\": 2845\n        },\n        \"tor-switch-a2\": {\n          \"x\": 2165.4301485385085,\n          \"y\": 2845\n        },\n        \"san-primary\": {\n          \"x\": 2123.2667017518106,\n          \"y\": 2845\n        },\n        \"san-secondary\": {\n          \"x\": 2155.0394237844876,\n          \"y\": 2845\n        },\n        \"fc-switch-1\": {\n          \"x\": 2115.2377370375634,\n          \"y\": 2845\n        },\n        \"fc-switch-2\": {\n          \"x\": 2144.5563938942755,\n          \"y\": 2845\n        },\n        \"backup-server-1\": {\n          \"x\": 2107.1401637413705,\n          \"y\": 2845\n        },\n        \"backup-server-2\": {\n          \"x\": 2133.987300103025,\n          \"y\": 2845\n        },\n        \"tape-library\": {\n          \"x\": 2098.9788028796397,\n          \"y\": 2845\n        },\n        \"tor-switch-b1\": {\n          \"x\": 2123.338434885373,\n          \"y\": 2845\n        },\n        \"tor-switch-b2\": {\n          \"x\": 2090.7585134456995,\n          \"y\": 2845\n        },\n        \"web-server-1\": {\n          \"x\": 2112.6161382091163,\n          \"y\": 2845\n        },\n        \"web-server-2\": {\n          \"x\": 2082.484189516922,\n          \"y\": 2845\n        },\n        \"waf-1\": {\n          \"x\": 2101.826793760617,\n          \"y\": 2845\n        },\n        \"load-balancer-dmz\": {\n          \"x\": 2074.1607573409574,\n          \"y\": 2845\n        },\n        \"mail-gateway\": {\n          \"x\": 2090.97682514417,\n          \"y\": 2845\n        },\n        \"dns-external-1\": {\n          \"x\": 2065.7931724028163,\n          \"y\": 2845\n        },\n        \"dns-external-2\": {\n          \"x\": 2080.0726920576153,\n          \"y\": 2845\n        },\n        \"vcenter\": {\n          \"x\": 2057.3864164745437,\n          \"y\": 2845\n        },\n        \"nsx-manager\": {\n          \"x\": 2069.1208864464534,\n          \"y\": 2845\n        },\n        \"siem-server\": {\n          \"x\": 2048.945494649244,\n          \"y\": 2845\n        },\n        \"nms-server\": {\n          \"x\": 2058.1279286387635,\n          \"y\": 2845\n        },\n        \"jump-server\": {\n          \"x\": 2040.4754323612206,\n          \"y\": 2845\n        },\n        \"ipam-server\": {\n          \"x\": 2047.1003634632284,\n          \"y\": 2845\n        },\n        \"wlc-primary\": {\n          \"x\": 1575.9723612611924,\n          \"y\": 2306.135986328125\n        },\n        \"wlc-secondary\": {\n          \"x\": 1468.1361870166274,\n          \"y\": 1563.733642578125\n        },\n        \"mobile-zone-hq\": {\n          \"x\": 2354.901177346808,\n          \"y\": 2806.0078125\n        },\n        \"mobile-zone-guest\": {\n          \"x\": 2307.6605605284435,\n          \"y\": 2611.047119140625\n        },\n        \"mobile-zone-iot\": {\n          \"x\": 2229.397686389302,\n          \"y\": 2299.110107421875\n        },\n        \"branch-router-ny\": {\n          \"x\": 3151.903101363964,\n          \"y\": 633.6580810546875\n        },\n        \"branch-router-la\": {\n          \"x\": 3083.8876194705945,\n          \"y\": 506.90625\n        },\n        \"branch-router-chi\": {\n          \"x\": 3355.02409980103,\n          \"y\": 393.1805725097656\n        },\n        \"branch-router-lon\": {\n          \"x\": 3113.609823320121,\n          \"y\": 260.4093322753906\n        },\n        \"branch-router-tokyo\": {\n          \"x\": 3699.3234994733834,\n          \"y\": 471.4241027832031\n        },\n        \"cloud-aws\": {\n          \"x\": 3436.528122523513,\n          \"y\": 545.9614868164062\n        },\n        \"cloud-azure\": {\n          \"x\": 2592.566210818907,\n          \"y\": 2724.068115234375\n        },\n        \"cloud-gcp\": {\n          \"x\": 2827.3183770424234,\n          \"y\": 2731.397216796875\n        },\n        \"isp-primary\": {\n          \"x\": 3712.192068081962,\n          \"y\": 615.64990234375\n        },\n        \"isp-secondary\": {\n          \"x\": 2702.3789772348055,\n          \"y\": 467.890869140625\n        },\n        \"dc-internal-1\": {\n          \"x\": 1958.4243458877936,\n          \"y\": 2845\n        },\n        \"dc-internal-2\": {\n          \"x\": 1963.768951182132,\n          \"y\": 2845\n        },\n        \"app-server-1\": {\n          \"x\": 1947.3819379304134,\n          \"y\": 2845\n        },\n        \"app-server-2\": {\n          \"x\": 1955.2862087394126,\n          \"y\": 2845\n        },\n        \"db-server-1\": {\n          \"x\": 1936.3708569559828,\n          \"y\": 2845\n        },\n        \"db-server-2\": {\n          \"x\": 1946.8300873488822,\n          \"y\": 2845\n        },\n        \"k8s-master-1\": {\n          \"x\": 1925.397658583093,\n          \"y\": 2845\n        },\n        \"k8s-master-2\": {\n          \"x\": 1938.405621494142,\n          \"y\": 2845\n        },\n        \"k8s-master-3\": {\n          \"x\": 1914.4688758763386,\n          \"y\": 2845\n        },\n        \"k8s-worker-1\": {\n          \"x\": 1930.017826812177,\n          \"y\": 2845\n        },\n        \"k8s-worker-2\": {\n          \"x\": 1903.5910154567553,\n          \"y\": 2845\n        },\n        \"k8s-worker-3\": {\n          \"x\": 1921.6716971072178,\n          \"y\": 2845\n        },\n        \"k8s-worker-4\": {\n          \"x\": 1892.7705536280016,\n          \"y\": 2845\n        },\n        \"proxy-server-1\": {\n          \"x\": 1806.1152433697903,\n          \"y\": 653.7529296875\n        },\n        \"proxy-server-2\": {\n          \"x\": 2937.4207928721535,\n          \"y\": 2628.7880859375\n        },\n        \"vpn-concentrator\": {\n          \"x\": 3642.252088474593,\n          \"y\": 946.7255249023438\n        },\n        \"nac-server\": {\n          \"x\": 1153.2626148502184,\n          \"y\": 1172.1895751953125\n        },\n        \"print-server\": {\n          \"x\": 1896.9328460745962,\n          \"y\": 2845\n        },\n        \"file-server\": {\n          \"x\": 1860.7177871362182,\n          \"y\": 2845\n        },\n        \"ca-server\": {\n          \"x\": 1888.8027739274805,\n          \"y\": 2845\n        },\n        \"sccm-server\": {\n          \"x\": 1850.1909418511675,\n          \"y\": 2845\n        },\n        \"voip-cluster\": {\n          \"x\": 1777.038465328039,\n          \"y\": 1616.8961181640625\n        },\n        \"video-conf\": {\n          \"x\": 1993.8373941679588,\n          \"y\": 2244.936309814453\n        },\n        \"security-cameras\": {\n          \"x\": 1674.413336949044,\n          \"y\": 2046.0380859375\n        },\n        \"nvr-cluster\": {\n          \"x\": 1829.4110389706402,\n          \"y\": 2845\n        },\n        \"dev-server-1\": {\n          \"x\": 2800.5894350649614,\n          \"y\": 1175.623291015625\n        },\n        \"dev-server-2\": {\n          \"x\": 1945.0822182484326,\n          \"y\": 1164.5184783935547\n        },\n        \"test-environment\": {\n          \"x\": 2566.9100352578575,\n          \"y\": 885.2827758789062\n        },\n        \"erp-system\": {\n          \"x\": 789.9880103985649,\n          \"y\": 473.7113342285156\n        },\n        \"crm-system\": {\n          \"x\": 3514.6003232048542,\n          \"y\": 1137.7720947265625\n        },\n        \"endpoint-1000\": {\n          \"x\": 991.6812012057328,\n          \"y\": 2284.42236328125\n        },\n        \"dist-switch-floor1\": {\n          \"x\": 654.2091033261356,\n          \"y\": 2020.0086669921875\n        },\n        \"dist-switch-floor2\": {\n          \"x\": 853.8845527112826,\n          \"y\": 1843.2872314453125\n        },\n        \"dist-switch-floor3\": {\n          \"x\": 1899.4353951584517,\n          \"y\": 1456.5068359375\n        },\n        \"dist-switch-floor4\": {\n          \"x\": 488.5289313756234,\n          \"y\": 181.47256469726562\n        },\n        \"ap-floor1-zone1\": {\n          \"x\": 1140.16846970184,\n          \"y\": 2070.2916259765625\n        },\n        \"ap-floor2-zone1\": {\n          \"x\": 688.1952143592268,\n          \"y\": 2384.4775390625\n        },\n        \"ap-floor3-zone1\": {\n          \"x\": 2145.3803027919676,\n          \"y\": 1890.2816162109375\n        },\n        \"ap-floor4-zone1\": {\n          \"x\": 517.646146409649,\n          \"y\": 565.59716796875\n        },\n        \"ups-dc-1\": {\n          \"x\": 771.1406786539856,\n          \"y\": 295.9266662597656\n        },\n        \"ups-dc-2\": {\n          \"x\": 216.2410855890687,\n          \"y\": 330.3345947265625\n        },\n        \"pdu-rack-a1\": {\n          \"x\": 1804.774444371901,\n          \"y\": 2845\n        },\n        \"pdu-rack-a2\": {\n          \"x\": 1741.6184034693686,\n          \"y\": 2845\n        },\n        \"cooling-1\": {\n          \"x\": 245.7080801919958,\n          \"y\": 626.1914672851562\n        },\n        \"cooling-2\": {\n          \"x\": 1603.293611085831,\n          \"y\": 981.0621185302734\n        },\n        \"camera-a\": {\n          \"x\": 166.57075412676295,\n          \"y\": 145\n        },\n        \"camera-a-copy\": {\n          \"x\": 1040.653076171875,\n          \"y\": 738.42822265625\n        }\n      },\n      \"sizes\": {\n        \"isp-secondary\": 139,\n        \"test-environment\": 148,\n        \"dev-server-1\": 128,\n        \"core-router-2\": 120,\n        \"camera-a\": 45,\n        \"camera-a-copy\": 45\n      },\n      \"styles\": {\n        \"dc-rack-b2\": {\n          \"all\": {\n            \"circleColor\": \"#ff0000\"\n          }\n        },\n        \"dc-rack-a1\": {\n          \"all\": {\n            \"circleColor\": \"#ff0000\"\n          }\n        },\n        \"dc-rack-b1\": {\n          \"all\": {\n            \"circleColor\": \"#ff0000\",\n            \"titleSize\": 59\n          }\n        },\n        \"isp-secondary\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"alist\"\n            }\n          }\n        },\n        \"core-router-2\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"actual-budget\"\n            },\n            \"pingOffsetX\": -15,\n            \"pingOffsetY\": -38\n          }\n        },\n        \"fw-external-1\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"anonaddy\"\n            }\n          }\n        },\n        \"cloud-aws\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"ansible\"\n            }\n          }\n        },\n        \"isp-primary\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"wikidocs\"\n            }\n          }\n        },\n        \"branch-router-tokyo\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"adguard-home\"\n            }\n          }\n        },\n        \"core-router-1\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"borg\"\n            }\n          }\n        },\n        \"test-environment\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"simple\",\n              \"name\": \"apple\"\n            }\n          }\n        },\n        \"dev-server-1\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"simple\",\n              \"name\": \"amazonwebservices\"\n            }\n          }\n        }\n      },\n      \"legend\": {\n        \"#10b981\": \"Trusted Lan\",\n        \"#f59e0b\": \"Secure Lan\",\n        \"#ef4444\": \"DMZ\",\n        \"#475569\": \"Main ISP\",\n        \"#3b82f6\": \"Alternate ISP\",\n        \"#8b5cf6\": \"you can edit me too\",\n        \"#06b6d4\": \"you can edit me too\",\n        \"#a855f7\": \"you can edit me too\",\n        \"#f97316\": \"you can edit me too\",\n        \"#0ea5e9\": \"you can edit me too\",\n        \"#22c55e\": \"you can edit me too\",\n        \"#94a3b8\": \"you can edit me too\",\n        \"#fbbf24\": \"you can edit me too\",\n        \"#38bdf8\": \"you can edit me too\",\n        \"#c800ff\": \"you can edit me too\"\n      },\n      \"rects\": {\n        \"list\": [\n          {\n            \"id\": \"rect-1765237540610\",\n            \"x\": 2879.214599609375,\n            \"y\": 159.71981811523438,\n            \"width\": 992.196044921875,\n            \"height\": 538.8650817871094,\n            \"color\": \"#f97316\",\n            \"style\": \"filled\",\n            \"lineStyle\": \"solid\",\n            \"notes\": []\n          },\n          {\n            \"id\": \"rect-1765237681216\",\n            \"x\": 448.3926696777344,\n            \"y\": 1671.651123046875,\n            \"width\": 916.3436584472656,\n            \"height\": 924.27734375,\n            \"color\": \"#c800ff\",\n            \"style\": \"outlined\",\n            \"lineStyle\": \"solid\",\n            \"notes\": []\n          },\n          {\n            \"id\": \"rect-1766437913740\",\n            \"x\": 904.5889892578125,\n            \"y\": 115.40318298339844,\n            \"width\": 110.93878173828125,\n            \"height\": 919.6242218017578,\n            \"color\": \"#5215f9\",\n            \"style\": \"filled\",\n            \"lineStyle\": \"wall\",\n            \"notes\": [],\n            \"borderWidth\": 13\n          },\n          {\n            \"id\": \"rect-1766437935414\",\n            \"x\": 130.93685150146484,\n            \"y\": 1072.3624877929688,\n            \"width\": 872.9131851196289,\n            \"height\": 99.260986328125,\n            \"color\": \"#5215f9\",\n            \"style\": \"filled\",\n            \"lineStyle\": \"wall\",\n            \"notes\": [],\n            \"borderWidth\": 13\n          }\n        ]\n      },\n      \"texts\": {\n        \"list\": [\n          {\n            \"id\": \"text-1765237828167\",\n            \"x\": 3411.458740234375,\n            \"y\": 1390.00439453125,\n            \"content\": \"Double click on desktop\\nor long press on mobile\\nto enter rack canvas view\",\n            \"fontSize\": 46,\n            \"color\": \"#e2e8f0\",\n            \"fontWeight\": \"bold\",\n            \"fontStyle\": \"italic\",\n            \"textAlign\": \"middle\",\n            \"textDecoration\": \"none\",\n            \"bgColor\": \"#000000\",\n            \"bgEnabled\": false,\n            \"opacity\": 1\n          },\n          {\n            \"id\": \"text-1765239331126\",\n            \"x\": 2454.5615234375,\n            \"y\": 160.73322105407715,\n            \"content\": \"Google is live!\",\n            \"fontSize\": 56,\n            \"color\": \"#e2e8f0\",\n            \"fontWeight\": \"bold\",\n            \"fontStyle\": \"normal\",\n            \"textAlign\": \"start\",\n            \"textDecoration\": \"none\",\n            \"bgColor\": \"#000000\",\n            \"bgEnabled\": false,\n            \"opacity\": 1\n          },\n          {\n            \"id\": \"text-1766446595277\",\n            \"x\": 654.3878479003906,\n            \"y\": 1367.7945556640625,\n            \"content\": \"SITE A\",\n            \"fontSize\": 101,\n            \"color\": \"#e2e8f0\",\n            \"fontWeight\": \"normal\",\n            \"fontStyle\": \"normal\",\n            \"textAlign\": \"start\",\n            \"textDecoration\": \"none\",\n            \"bgColor\": \"#000000\",\n            \"bgEnabled\": false,\n            \"opacity\": 1\n          },\n          {\n            \"id\": \"text-1766446610211\",\n            \"x\": 180.63662719726562,\n            \"y\": 1128.822998046875,\n            \"content\": \"SITE A\",\n            \"fontSize\": 101,\n            \"color\": \"#e2e8f0\",\n            \"fontWeight\": \"normal\",\n            \"fontStyle\": \"normal\",\n            \"textAlign\": \"start\",\n            \"textDecoration\": \"none\",\n            \"bgColor\": \"#000000\",\n            \"bgEnabled\": false,\n            \"opacity\": 1\n          },\n          {\n            \"id\": \"text-1766453024797\",\n            \"x\": 968.6458740234375,\n            \"y\": 1028.6621398925781,\n            \"content\": \"SITE A\",\n            \"fontSize\": 101,\n            \"color\": \"#e2e8f0\",\n            \"fontWeight\": \"normal\",\n            \"fontStyle\": \"normal\",\n            \"textAlign\": \"start\",\n            \"textDecoration\": \"none\",\n            \"bgColor\": \"#000000\",\n            \"bgEnabled\": false,\n            \"opacity\": 1,\n            \"rotation\": -89,\n            \"_dragStartX\": 972.46826171875,\n            \"_dragStartY\": 1009.5499572753906\n          },\n          {\n            \"id\": \"text-1766453070975\",\n            \"x\": 613.1589965820312,\n            \"y\": 1139.512939453125,\n            \"content\": \"SITE A\",\n            \"fontSize\": 101,\n            \"color\": \"#e2e8f0\",\n            \"fontWeight\": \"normal\",\n            \"fontStyle\": \"normal\",\n            \"textAlign\": \"start\",\n            \"textDecoration\": \"none\",\n            \"bgColor\": \"#000000\",\n            \"bgEnabled\": false,\n            \"opacity\": 1\n          },\n          {\n            \"id\": \"text-1766453072857\",\n            \"x\": 968.64599609375,\n            \"y\": 474.40818786621094,\n            \"content\": \"SITE A\",\n            \"fontSize\": 101,\n            \"color\": \"#e2e8f0\",\n            \"fontWeight\": \"normal\",\n            \"fontStyle\": \"normal\",\n            \"textAlign\": \"start\",\n            \"textDecoration\": \"none\",\n            \"bgColor\": \"#000000\",\n            \"bgEnabled\": false,\n            \"opacity\": 1,\n            \"rotation\": 269,\n            \"_dragStartX\": 1480.85302734375,\n            \"_dragStartY\": 822.2503356933594\n          }\n        ]\n      },\n      \"pageState\": {\n        \"title\": \"The One File Corporate\",\n        \"background\": \"\",\n        \"topbarBg\": \"rgba(9, 12, 20, 0.9)\",\n        \"topbarBorder\": \"#1f2533\",\n        \"panel\": \"#0b0e13\",\n        \"panelAlt\": \"#10141b\",\n        \"accent\": \"#4fd1c5\",\n        \"sidebarBg\": \"#10141b\",\n        \"btnBg\": \"#0b0e13\",\n        \"btnText\": \"#e2e8f0\",\n        \"tagFill\": \"#1e293b\",\n        \"tagText\": \"#e2e8f0\",\n        \"tagBorder\": \"#475569\",\n        \"inputBg\": \"#0b0e13\",\n        \"inputText\": \"#e2e8f0\",\n        \"inputBorder\": \"#1f2937\",\n        \"inputFont\": \"Inter, system-ui, sans-serif\",\n        \"inputFontSize\": 14,\n        \"toolbarBg\": \"#0f172a\",\n        \"toolbarBorder\": \"#1f2937\",\n        \"toolbarText\": \"#94a3b8\",\n        \"toolbarBtnBg\": \"#0b0e13\",\n        \"toolbarBtnText\": \"#e2e8f0\",\n        \"minimapDots\": \"#94a3b8\",\n        \"canvasHintEnabled\": true,\n        \"canvasHintText\": \"\",\n        \"canvasHintBg\": \"#0f172a\",\n        \"canvasHintColor\": \"#94a3b8\",\n        \"danger\": \"#f56565\",\n        \"textMain\": \"#e2e8f0\",\n        \"textSoft\": \"#94a3b8\",\n        \"topbarHeight\": 103,\n        \"sidebarWidth\": 350,\n        \"mobileFooterHeight\": 40,\n        \"sidebarCollapsed\": false,\n        \"nodeFill\": \"#1e293b\",\n        \"nodeStroke\": \"#475569\",\n        \"nodeTitle\": \"#e2e8f0\",\n        \"nodeSub\": \"#94a3b8\",\n        \"nodeTitleSize\": 41,\n        \"nodeSubSize\": 27,\n        \"nodeFont\": \"monospace\",\n        \"defaultEdge\": \"#475569\",\n        \"selectionHandle\": \"#f59e0b\",\n        \"selectionHandleSize\": 8,\n        \"groupIndicator\": \"#4fd1c5\",\n        \"canvasGradientTop\": \"#1e2532\",\n        \"canvasGradientBottom\": \"#050608\",\n        \"canvasBorder\": \"#475569\",\n        \"canvasGrid\": \"#475569\",\n        \"canvasGridSize\": 50,\n        \"canvasGridEnabled\": true,\n        \"rackFrameFill\": \"#0f172a\",\n        \"rackFrameStroke\": \"#4fd1c5\",\n        \"rackLineColor\": \"#475569\",\n        \"rackTextColor\": \"#4fd1c5\",\n        \"rackGridEnabled\": true,\n        \"viewOnly\": false,\n        \"defaultEdgeRouting\": \"orthogonal\",\n        \"animateConnections\": false,\n        \"animationStyle\": \"arrows\",\n        \"animationDirection\": \"all\",\n        \"animationSpeed\": 4,\n        \"autoPingEnabled\": false,\n        \"autoPingInterval\": 30\n      }\n    },\n    {\n      \"id\": \"tab-1765235136918\",\n      \"name\": \"Homelab 2\",\n      \"nodes\": {\n        \"internet\": {\n          \"shape\": \"square\",\n          \"name\": \"Internet\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"internet-copy\": {\n          \"shape\": \"firewall\",\n          \"name\": \"OPNSENSE\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"opnsense-copy\": {\n          \"shape\": \"firewall\",\n          \"name\": \"Docker\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"docker-copy\": {\n          \"shape\": \"firewall\",\n          \"name\": \"Docker2\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"docker-copy-1\": {\n          \"shape\": \"firewall\",\n          \"name\": \"Docker3\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"docker-copy-2\": {\n          \"shape\": \"firewall\",\n          \"name\": \"Docker 4\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [\n            {\n              \"type\": \"icon\",\n              \"library\": \"selfhst\",\n              \"name\": \"docker\"\n            },\n            {\n              \"type\": \"icon\",\n              \"library\": \"selfhst\",\n              \"name\": \"authentik\"\n            },\n            {\n              \"type\": \"icon\",\n              \"library\": \"selfhst\",\n              \"name\": \"immich\"\n            }\n          ],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"opnsense-copy-1\": {\n          \"shape\": \"firewall\",\n          \"name\": \"OPNSENSE GUEST\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"phone\": {\n          \"shape\": \"phone\",\n          \"name\": \"Phone\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"desktop\": {\n          \"shape\": \"pc\",\n          \"name\": \"Desktop\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dns\": {\n          \"shape\": \"cloud\",\n          \"name\": \"DNS\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"racked\": {\n          \"shape\": \"server\",\n          \"name\": \"Racked\",\n          \"ip\": \"\",\n          \"role\": \"Rack\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": true,\n          \"locked\": false,\n          \"groupId\": null\n        }\n      },\n      \"edges\": {\n        \"list\": [\n          {\n            \"id\": \"internet-internet-copy-1765238145151\",\n            \"from\": \"internet\",\n            \"to\": \"internet-copy\",\n            \"width\": 4,\n            \"color\": \"#55e208\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1\n          },\n          {\n            \"id\": \"internet-copy-opnsense-copy-1765238187451\",\n            \"from\": \"internet-copy\",\n            \"to\": \"opnsense-copy\",\n            \"width\": 4,\n            \"color\": \"#4c00ff\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1\n          },\n          {\n            \"id\": \"internet-copy-docker-copy-1765238242477\",\n            \"from\": \"internet-copy\",\n            \"to\": \"docker-copy\",\n            \"width\": 4,\n            \"color\": \"#4c00ff\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1\n          },\n          {\n            \"id\": \"internet-copy-docker-copy-1-1765238244637\",\n            \"from\": \"internet-copy\",\n            \"to\": \"docker-copy-1\",\n            \"width\": 4,\n            \"color\": \"#4c00ff\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1\n          },\n          {\n            \"id\": \"internet-copy-docker-copy-2-1765238246233\",\n            \"from\": \"internet-copy\",\n            \"to\": \"docker-copy-2\",\n            \"width\": 4,\n            \"color\": \"#4c00ff\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1\n          },\n          {\n            \"id\": \"internet-opnsense-copy-1-1765238266117\",\n            \"from\": \"internet\",\n            \"to\": \"opnsense-copy-1\",\n            \"width\": 4,\n            \"color\": \"#80ff00\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1\n          },\n          {\n            \"id\": \"opnsense-copy-1-dns-1765238347996\",\n            \"from\": \"opnsense-copy-1\",\n            \"to\": \"dns\",\n            \"width\": 4,\n            \"color\": \"#fb00ff\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1\n          },\n          {\n            \"id\": \"dns-desktop-1765238386101\",\n            \"from\": \"dns\",\n            \"to\": \"desktop\",\n            \"width\": 4,\n            \"color\": \"#ff00d0\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1\n          },\n          {\n            \"id\": \"phone-dns-1765238391156\",\n            \"from\": \"phone\",\n            \"to\": \"dns\",\n            \"width\": 4,\n            \"color\": \"#ff00d0\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1\n          },\n          {\n            \"id\": \"custom-1765239449323\",\n            \"type\": \"custom\",\n            \"color\": \"#f97316\",\n            \"width\": 4,\n            \"lineStyle\": \"solid\",\n            \"direction\": \"forward\",\n            \"points\": [\n              {\n                \"x\": 2936.464111328125,\n                \"y\": 786.07958984375\n              },\n              {\n                \"x\": 3184.112060546875,\n                \"y\": 887.6153564453125\n              },\n              {\n                \"x\": 2763.110107421875,\n                \"y\": 981.7216796875\n              }\n            ],\n            \"notes\": []\n          }\n        ]\n      },\n      \"positions\": {\n        \"internet\": {\n          \"x\": 2103.968290880771,\n          \"y\": 268\n        },\n        \"internet-copy\": {\n          \"x\": 2066.9677515897347,\n          \"y\": 473.4119134177565\n        },\n        \"opnsense-copy\": {\n          \"x\": 1773.8400660428597,\n          \"y\": 666.5758233298659\n        },\n        \"docker-copy\": {\n          \"x\": 1931.1978950081452,\n          \"y\": 782.2775961320921\n        },\n        \"docker-copy-1\": {\n          \"x\": 2158.1262397347077,\n          \"y\": 767.7122274797483\n        },\n        \"docker-copy-2\": {\n          \"x\": 2342.2663764534577,\n          \"y\": 631.7681967180296\n        },\n        \"opnsense-copy-1\": {\n          \"x\": 2757.879480087803,\n          \"y\": 307.6117116091891\n        },\n        \"phone\": {\n          \"x\": 3312.857751572178,\n          \"y\": 502.58220111114224\n        },\n        \"desktop\": {\n          \"x\": 2971.700036728428,\n          \"y\": 480.7287465212985\n        },\n        \"dns\": {\n          \"x\": 3200.4643189549906,\n          \"y\": 320.469591247861\n        },\n        \"racked\": {\n          \"x\": 2645.5845448279656,\n          \"y\": 970.7820678889219\n        }\n      },\n      \"sizes\": {\n        \"core-router-1\": 36,\n        \"internet\": 168,\n        \"phone\": 121,\n        \"desktop\": 147,\n        \"racked\": 137,\n        \"docker-copy-2\": 82\n      },\n      \"styles\": {\n        \"internet\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"amazon-web-services\"\n            },\n            \"circleColor\": \"#ffffff\",\n            \"circleBorder\": \"#ffffff\"\n          }\n        },\n        \"opnsense-copy-1\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"opnsense-v1\"\n            }\n          }\n        },\n        \"internet-copy\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"opnsense\"\n            }\n          }\n        },\n        \"docker-copy-2\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"docker\"\n            }\n          }\n        },\n        \"docker-copy-1\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"authportal\"\n            }\n          }\n        },\n        \"docker-copy\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"jotty\"\n            }\n          }\n        },\n        \"opnsense-copy\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"portainer\"\n            }\n          }\n        },\n        \"racked\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"mdi\",\n              \"name\": \"server-security\"\n            },\n            \"circleColor\": \"#010813\",\n            \"circleBorder\": \"#ffffff\"\n          }\n        }\n      },\n      \"legend\": {\n        \"#475569\": \"you can edit me too\",\n        \"#65758b\": \"you can edit me too\",\n        \"#63748c\": \"you can edit me too\",\n        \"#5e6f87\": \"you can edit me too\",\n        \"#586a84\": \"you can edit me too\",\n        \"#4f627d\": \"you can edit me too\",\n        \"#455873\": \"you can edit me too\",\n        \"#3d506c\": \"you can edit me too\",\n        \"#354964\": \"you can edit me too\",\n        \"#2e415c\": \"you can edit me too\",\n        \"#293c56\": \"you can edit me too\",\n        \"#273a53\": \"you can edit me too\",\n        \"#253750\": \"you can edit me too\",\n        \"#23354d\": \"you can edit me too\",\n        \"#203046\": \"you can edit me too\",\n        \"#1e2d43\": \"you can edit me too\",\n        \"#1a283d\": \"you can edit me too\",\n        \"#172435\": \"you can edit me too\",\n        \"#141f2e\": \"you can edit me too\",\n        \"#111a27\": \"you can edit me too\",\n        \"#0f1824\": \"you can edit me too\",\n        \"#0d1521\": \"you can edit me too\",\n        \"#0c131d\": \"you can edit me too\",\n        \"#0c1d1c\": \"you can edit me too\",\n        \"#0c1c1d\": \"you can edit me too\",\n        \"#0c191d\": \"you can edit me too\",\n        \"#0c141d\": \"you can edit me too\",\n        \"#0c0d1d\": \"you can edit me too\",\n        \"#130c1d\": \"you can edit me too\",\n        \"#1b0c1d\": \"you can edit me too\",\n        \"#1d0c17\": \"you can edit me too\",\n        \"#1d0c10\": \"you can edit me too\",\n        \"#1d0c0c\": \"you can edit me too\",\n        \"#3b1b1b\": \"you can edit me too\",\n        \"#3c1a1a\": \"you can edit me too\",\n        \"#3f1c1c\": \"you can edit me too\",\n        \"#401c1c\": \"you can edit me too\",\n        \"#451c1c\": \"you can edit me too\",\n        \"#461b1b\": \"you can edit me too\",\n        \"#4c1a1a\": \"you can edit me too\",\n        \"#521919\": \"you can edit me too\",\n        \"#571919\": \"you can edit me too\",\n        \"#5d1818\": \"you can edit me too\",\n        \"#631717\": \"you can edit me too\",\n        \"#651515\": \"you can edit me too\",\n        \"#6a1616\": \"you can edit me too\",\n        \"#6f1515\": \"you can edit me too\",\n        \"#711414\": \"you can edit me too\",\n        \"#761414\": \"you can edit me too\",\n        \"#771313\": \"you can edit me too\",\n        \"#7c1313\": \"you can edit me too\",\n        \"#811313\": \"you can edit me too\",\n        \"#821212\": \"you can edit me too\",\n        \"#871212\": \"you can edit me too\",\n        \"#881111\": \"you can edit me too\",\n        \"#8d1111\": \"you can edit me too\",\n        \"#8e1010\": \"you can edit me too\",\n        \"#8f0f0f\": \"you can edit me too\",\n        \"#900e0e\": \"you can edit me too\",\n        \"#8e0b0b\": \"you can edit me too\",\n        \"#8c0d0d\": \"you can edit me too\",\n        \"#880c0c\": \"you can edit me too\",\n        \"#830c0c\": \"you can edit me too\",\n        \"#7e0c0c\": \"you can edit me too\",\n        \"#790c0c\": \"you can edit me too\",\n        \"#730c0c\": \"you can edit me too\",\n        \"#6f0b0b\": \"you can edit me too\",\n        \"#0b6f64\": \"you can edit me too\",\n        \"#0b6f5f\": \"you can edit me too\",\n        \"#0b6f56\": \"you can edit me too\",\n        \"#0b6f49\": \"you can edit me too\",\n        \"#0b6f31\": \"you can edit me too\",\n        \"#0b6f1f\": \"you can edit me too\",\n        \"#0b6f0d\": \"you can edit me too\",\n        \"#176f0b\": \"you can edit me too\",\n        \"#266f0b\": \"you can edit me too\",\n        \"#296f0b\": \"you can edit me too\",\n        \"#2e6f0b\": \"you can edit me too\",\n        \"#1a2d10\": \"you can edit me too\",\n        \"#1c3111\": \"you can edit me too\",\n        \"#213814\": \"you can edit me too\",\n        \"#233c15\": \"you can edit me too\",\n        \"#254017\": \"you can edit me too\",\n        \"#294918\": \"you can edit me too\",\n        \"#2b4d1a\": \"you can edit me too\",\n        \"#2d511a\": \"you can edit me too\",\n        \"#315a1b\": \"you can edit me too\",\n        \"#35631c\": \"you can edit me too\",\n        \"#37681d\": \"you can edit me too\",\n        \"#3b721d\": \"you can edit me too\",\n        \"#3f7b1e\": \"you can edit me too\",\n        \"#42851e\": \"you can edit me too\",\n        \"#46901d\": \"you can edit me too\",\n        \"#499a1d\": \"you can edit me too\",\n        \"#4b9f1d\": \"you can edit me too\",\n        \"#4ca61c\": \"you can edit me too\",\n        \"#50b01c\": \"you can edit me too\",\n        \"#51b71a\": \"you can edit me too\",\n        \"#50b918\": \"you can edit me too\",\n        \"#51c115\": \"you can edit me too\",\n        \"#53c615\": \"you can edit me too\",\n        \"#53c814\": \"you can edit me too\",\n        \"#52c913\": \"you can edit me too\",\n        \"#54d011\": \"you can edit me too\",\n        \"#53d110\": \"you can edit me too\",\n        \"#55d510\": \"you can edit me too\",\n        \"#55d70f\": \"you can edit me too\",\n        \"#54d80e\": \"you can edit me too\",\n        \"#54da0b\": \"you can edit me too\",\n        \"#56df0c\": \"you can edit me too\",\n        \"#53db0a\": \"you can edit me too\",\n        \"#55e00b\": \"you can edit me too\",\n        \"#55e109\": \"you can edit me too\",\n        \"#55e208\": \"ISP LINE\",\n        \"#4c00ff\": \"MY Guest NETWORK\",\n        \"#80ff00\": \"you can edit me too\",\n        \"#3b4234\": \"you can edit me too\",\n        \"#3a3442\": \"you can edit me too\",\n        \"#3b3442\": \"you can edit me too\",\n        \"#3c3442\": \"you can edit me too\",\n        \"#3d3442\": \"you can edit me too\",\n        \"#3e3442\": \"you can edit me too\",\n        \"#3f3442\": \"you can edit me too\",\n        \"#403442\": \"you can edit me too\",\n        \"#413442\": \"you can edit me too\",\n        \"#653d66\": \"you can edit me too\",\n        \"#683f69\": \"you can edit me too\",\n        \"#6c416c\": \"you can edit me too\",\n        \"#6f4370\": \"you can edit me too\",\n        \"#704270\": \"you can edit me too\",\n        \"#734474\": \"you can edit me too\",\n        \"#784479\": \"you can edit me too\",\n        \"#7d447e\": \"you can edit me too\",\n        \"#7e437f\": \"you can edit me too\",\n        \"#834384\": \"you can edit me too\",\n        \"#844285\": \"you can edit me too\",\n        \"#89418b\": \"you can edit me too\",\n        \"#8e428f\": \"you can edit me too\",\n        \"#904091\": \"you can edit me too\",\n        \"#923e93\": \"you can edit me too\",\n        \"#973e98\": \"you can edit me too\",\n        \"#943c96\": \"you can edit me too\",\n        \"#993c9a\": \"you can edit me too\",\n        \"#963a98\": \"you can edit me too\",\n        \"#973899\": \"you can edit me too\",\n        \"#99369b\": \"you can edit me too\",\n        \"#9a359c\": \"you can edit me too\",\n        \"#9b349d\": \"you can edit me too\",\n        \"#9d329f\": \"you can edit me too\",\n        \"#9e31a0\": \"you can edit me too\",\n        \"#a02fa2\": \"you can edit me too\",\n        \"#9d2d9f\": \"you can edit me too\",\n        \"#9f2ba1\": \"you can edit me too\",\n        \"#a129a3\": \"you can edit me too\",\n        \"#a327a5\": \"you can edit me too\",\n        \"#a525a7\": \"you can edit me too\",\n        \"#a723a9\": \"you can edit me too\",\n        \"#a921ab\": \"you can edit me too\",\n        \"#ab1fad\": \"you can edit me too\",\n        \"#ad1daf\": \"you can edit me too\",\n        \"#ae1cb0\": \"you can edit me too\",\n        \"#b019b3\": \"you can edit me too\",\n        \"#b118b4\": \"you can edit me too\",\n        \"#b316b6\": \"you can edit me too\",\n        \"#b816bb\": \"you can edit me too\",\n        \"#b514b8\": \"you can edit me too\",\n        \"#ba14bd\": \"you can edit me too\",\n        \"#b712ba\": \"you can edit me too\",\n        \"#bb13be\": \"you can edit me too\",\n        \"#b811bb\": \"you can edit me too\",\n        \"#be10c1\": \"you can edit me too\",\n        \"#bb0ebe\": \"you can edit me too\",\n        \"#bd0cc0\": \"you can edit me too\",\n        \"#be0bc1\": \"you can edit me too\",\n        \"#c108c4\": \"you can edit me too\",\n        \"#be06c1\": \"you can edit me too\",\n        \"#c103c4\": \"you can edit me too\",\n        \"#c301c6\": \"you can edit me too\",\n        \"#c400c7\": \"you can edit me too\",\n        \"#c900cc\": \"you can edit me too\",\n        \"#ce00d1\": \"you can edit me too\",\n        \"#d300d6\": \"you can edit me too\",\n        \"#d800db\": \"you can edit me too\",\n        \"#dd00e0\": \"you can edit me too\",\n        \"#e200e6\": \"you can edit me too\",\n        \"#ec00f0\": \"you can edit me too\",\n        \"#f100f5\": \"you can edit me too\",\n        \"#f600fa\": \"you can edit me too\",\n        \"#fb00ff\": \"you can edit me too\",\n        \"#ff00d0\": \"iPhone (always guest iPhone)\",\n        \"#f97316\": \"you can edit me too\"\n      },\n      \"rects\": {\n        \"list\": [\n          {\n            \"id\": \"rect-1765238219615\",\n            \"x\": 2680.053955078125,\n            \"y\": 251.44879150390625,\n            \"width\": 814.10400390625,\n            \"height\": 389.26678466796875,\n            \"color\": \"#ec0999\",\n            \"style\": \"filled\",\n            \"lineStyle\": \"solid\",\n            \"notes\": []\n          }\n        ]\n      },\n      \"texts\": {\n        \"list\": [\n          {\n            \"id\": \"text-1765238422602\",\n            \"x\": 2466.35986328125,\n            \"y\": 741.6801147460938,\n            \"content\": \"Double click on desktop\\nor long press on mobile\\nto enter rack canvas view\",\n            \"fontSize\": 40,\n            \"color\": \"#e2e8f0\",\n            \"fontWeight\": \"normal\",\n            \"fontStyle\": \"normal\",\n            \"textAlign\": \"start\",\n            \"textDecoration\": \"none\",\n            \"bgColor\": \"#000000\",\n            \"bgEnabled\": false,\n            \"opacity\": 1\n          }\n        ]\n      },\n      \"pageState\": {\n        \"title\": \"The One File\",\n        \"background\": \"\",\n        \"topbarBg\": \"rgba(9, 12, 20, 0.9)\",\n        \"topbarBorder\": \"#1f2533\",\n        \"panel\": \"#2f0e0e\",\n        \"panelAlt\": \"#10141b\",\n        \"accent\": \"#a75252\",\n        \"sidebarBg\": \"#10141b\",\n        \"btnBg\": \"#0b0e13\",\n        \"btnText\": \"#e2e8f0\",\n        \"tagFill\": \"#1e293b\",\n        \"tagText\": \"#e2e8f0\",\n        \"tagBorder\": \"#475569\",\n        \"inputBg\": \"#0b0e13\",\n        \"inputText\": \"#e2e8f0\",\n        \"inputBorder\": \"#1f2937\",\n        \"inputFont\": \"Inter, system-ui, sans-serif\",\n        \"inputFontSize\": 14,\n        \"toolbarBg\": \"#441215\",\n        \"toolbarBorder\": \"#1f2937\",\n        \"toolbarText\": \"#94a3b8\",\n        \"toolbarBtnBg\": \"#0b0e13\",\n        \"toolbarBtnText\": \"#e2e8f0\",\n        \"minimapDots\": \"#94a3b8\",\n        \"canvasHintEnabled\": true,\n        \"canvasHintText\": \"\",\n        \"canvasHintBg\": \"#0f172a\",\n        \"canvasHintColor\": \"#94a3b8\",\n        \"danger\": \"#f56565\",\n        \"textMain\": \"#e2e8f0\",\n        \"textSoft\": \"#94a3b8\",\n        \"topbarHeight\": 112,\n        \"sidebarWidth\": 350,\n        \"mobileFooterHeight\": 40,\n        \"sidebarCollapsed\": false,\n        \"nodeFill\": \"#1e293b\",\n        \"nodeStroke\": \"#475569\",\n        \"nodeTitle\": \"#e2e8f0\",\n        \"nodeSub\": \"#94a3b8\",\n        \"nodeTitleSize\": 18,\n        \"nodeSubSize\": 13,\n        \"nodeFont\": \"Inter, system-ui, sans-serif\",\n        \"defaultEdge\": \"#475569\",\n        \"selectionHandle\": \"#f59e0b\",\n        \"selectionHandleSize\": 8,\n        \"groupIndicator\": \"#4fd1c5\",\n        \"canvasGradientTop\": \"#1e2532\",\n        \"canvasGradientBottom\": \"#050608\",\n        \"canvasBorder\": \"#475569\",\n        \"canvasGrid\": \"#475569\",\n        \"canvasGridSize\": 50,\n        \"canvasGridEnabled\": true,\n        \"rackFrameFill\": \"#0f172a\",\n        \"rackFrameStroke\": \"#4fd1c5\",\n        \"rackLineColor\": \"#475569\",\n        \"rackTextColor\": \"#4fd1c5\",\n        \"rackGridEnabled\": true,\n        \"viewOnly\": false,\n        \"defaultEdgeRouting\": \"curved\",\n        \"animateConnections\": false,\n        \"animationStyle\": \"arrows\",\n        \"animationDirection\": \"all\",\n        \"animationSpeed\": 1.5\n      }\n    }\n  ],\n  \"currentTabIndex\": 0,\n  \"encryptedSections\": {},\n  \"auditLog\": [\n    {\n      \"timestamp\": 1766459540242,\n      \"type\": \"export\",\n      \"description\": \"Exported CSV: the-one-file-corporate.csv\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459536815,\n      \"type\": \"export\",\n      \"description\": \"Exported JSON: the-one-file-corporate.json\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459534490,\n      \"type\": \"tab\",\n      \"description\": \"Switched to tab: Corporate Site B\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459525616,\n      \"type\": \"export\",\n      \"description\": \"Exported CSV: the-one-file.csv\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766459518367,\n      \"type\": \"export\",\n      \"description\": \"Exported Markdown: the-one-file.md\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766459511746,\n      \"type\": \"export\",\n      \"description\": \"Exported JSON: the-one-file.json\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766459504374,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file.html\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766459500911,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file.html\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766459497380,\n      \"type\": \"tab\",\n      \"description\": \"Switched to tab: Homelab 2\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766459491436,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file-corporate.html\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459483682,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file-corporate.html\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459477676,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file-corporate.html\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766457578277,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766457564726,\n      \"type\": \"tab\",\n      \"description\": \"Switched to tab: Corporate Site B\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766457564253,\n      \"type\": \"tab\",\n      \"description\": \"Switched to tab: Homelab 2\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766457560309,\n      \"type\": \"import\",\n      \"description\": \"Imported JSON: the-one-file-corporate.json (107 nodes, 65 connections)\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455847368,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455844534,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455844054,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455843762,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455843560,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455843371,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455843162,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455842852,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455842747,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455842601,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455842449,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455842348,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455842098,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455841678,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455841236,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455841053,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455840901,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455840650,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455839427,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455839234,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455839061,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455837247,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455837081,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455836893,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455836377,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455836198,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455835455,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455834630,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455831880,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455831676,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455831451,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455830817,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455830687,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455830176,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455830048,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455829944,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455829816,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455378795,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455378693,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455378459,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455378316,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455378180,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455378069,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455377956,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455377677,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455377558,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455377448,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455377318,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455377209,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453090534,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453090317,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453090213,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453090112,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453090009,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453089903,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453088895,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453088793,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453088689,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453088584,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453088480,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453088250,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453087236,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453086725,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453086485,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453086373,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453086142,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453086043,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453072857,\n      \"type\": \"text\",\n      \"description\": \"paste text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453070975,\n      \"type\": \"text\",\n      \"description\": \"paste text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453054439,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453053127,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453052450,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453052106,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453051948,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453051806,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453051334,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453050207,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453042725,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453042179,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453041797,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453041570,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453039703,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453039291,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453039168,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453039065,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453038481,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453038365,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453038237,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453038105,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453038001,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453037850,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453037745,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453037495,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453037378,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453037182,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453037078,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453036972,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453036860,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453036147,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453035945,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453035825,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453035720,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453035443,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453035337,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453035233,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453035127,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453035026,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453034917,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453031063,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453030955,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453030833,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453030732,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453030225,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453030104,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453029968,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453029796,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453029474,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453024797,\n      \"type\": \"text\",\n      \"description\": \"paste text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766451118553,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450929324,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450817210,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450257424,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450255024,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450254395,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450253241,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450251598,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450250392,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450248756,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450244072,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450242166,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450240998,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450236492,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450233672,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450232384,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450231012,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450230254,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450229302,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450228132,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446610211,\n      \"type\": \"text\",\n      \"description\": \"paste text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446604849,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446604702,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446604550,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446604404,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446604305,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446604204,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446604099,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446603952,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446603849,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446603702,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446603599,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446603452,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446603348,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446603202,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446603099,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446602953,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446602850,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446602702,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446602600,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446602453,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446602349,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446602204,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446602101,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446602000,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446601848,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446601702,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446601601,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446601452,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446601301,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446601154,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446601049,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446600948,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446600802,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446600550,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446598595,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446598461,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446598171,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446598017,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446597219,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446595278,\n      \"type\": \"text\",\n      \"description\": \"add text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445633355,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445632515,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445631735,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445630757,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445627846,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445625085,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445618645,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445617784,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445608998,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445608720,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445608540,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445608376,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445608204,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445608038,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445607852,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445607678,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445607506,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445607319,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445607154,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445604410,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445604244,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445604066,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445603900,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445603743,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445603563,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445603406,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445603226,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445603052,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445602880,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445602641,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445576567,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animation speed\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445570290,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animation speed\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445567192,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445566766,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445565520,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445398115,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445390895,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445385694,\n      \"type\": \"edit\",\n      \"description\": \"toggle fov animation\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445383241,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445382911,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445381695,\n      \"type\": \"edit\",\n      \"description\": \"edit node name\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445375383,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445374665,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445373273,\n      \"type\": \"node\",\n      \"description\": \"paste node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445372205,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438157980,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438157430,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animation speed\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438152691,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438151948,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438151286,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438146174,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438145649,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438144555,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438143655,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438142504,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animation speed\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438130077,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438129561,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438128772,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438128398,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438122820,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438122062,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438119836,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438119588,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438095045,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438093965,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438062827,\n      \"type\": \"edit\",\n      \"description\": \"toggle fov animation\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438047679,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438044161,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438041852,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438039668,\n      \"type\": \"edit\",\n      \"description\": \"resize node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438039562,\n      \"type\": \"edit\",\n      \"description\": \"resize node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438039421,\n      \"type\": \"edit\",\n      \"description\": \"resize node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438039260,\n      \"type\": \"edit\",\n      \"description\": \"resize node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438039150,\n      \"type\": \"edit\",\n      \"description\": \"resize node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438039039,\n      \"type\": \"edit\",\n      \"description\": \"resize node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438028508,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438021410,\n      \"type\": \"edit\",\n      \"description\": \"toggle fov\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438019234,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438017562,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438014356,\n      \"type\": \"node\",\n      \"description\": \"add node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437981696,\n      \"type\": \"edit\",\n      \"description\": \"apply routing to all\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437966551,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437964879,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437963627,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437961813,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437961193,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437957989,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437956467,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437953437,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437952239,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437950807,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437944990,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437943699,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437935414,\n      \"type\": \"zone\",\n      \"description\": \"draw zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437919019,\n      \"type\": \"zone\",\n      \"description\": \"delete zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437917758,\n      \"type\": \"zone\",\n      \"description\": \"draw zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437913740,\n      \"type\": \"zone\",\n      \"description\": \"draw zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437882832,\n      \"type\": \"import\",\n      \"description\": \"Imported JSON: theonefile-networkening-corporate-demo.json (105 nodes, 65 connections)\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766263279163,\n      \"type\": \"tab\",\n      \"description\": \"Switched to tab: Corporate Site B\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766263270414,\n      \"type\": \"export\",\n      \"description\": \"Exported JSON: the-one-file.json\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766263260682,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file.html\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766263259518,\n      \"type\": \"tab\",\n      \"description\": \"Switched to tab: Homelab 2\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766263249401,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file-corporate.html\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766263246362,\n      \"type\": \"import\",\n      \"description\": \"Imported JSON: theonefile-networkening-corporate-demo.json (105 nodes, 65 connections)\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766190721141,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file-corporate.html\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766190717499,\n      \"type\": \"tab\",\n      \"description\": \"Switched to tab: Corporate Site B\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766190710946,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file.html\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766190705273,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file.html\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766190703463,\n      \"type\": \"tab\",\n      \"description\": \"Switched to tab: Homelab 2\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766190695709,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file-corporate.html\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766190688417,\n      \"type\": \"import\",\n      \"description\": \"Imported JSON: theonefile-networkening-corporate-demo.json (105 nodes, 65 connections)\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1765402888416,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file-corporate.html\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1765402884873,\n      \"type\": \"tab\",\n      \"description\": \"Switched to tab: Corporate Site B\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1765402878108,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file.html\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1765402866440,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file.html\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1765402865008,\n      \"type\": \"tab\",\n      \"description\": \"Switched to tab: Homelab 2\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1765402860428,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file-corporate.html\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1765402858103,\n      \"type\": \"import\",\n      \"description\": \"Imported JSON: theonefile-networkening-corporate-demo.json (105 nodes, 65 connections)\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    }\n  ],\n  \"savedStyleSets\": []\n}\nTHEONEFILE_CONFIG-->\n\n# The One File Corporate\n\n> Exported from The One File on 2025-12-23T03:12:24.252Z\n\n## Legend\n\n- #10b981: Trusted Lan\n- #f59e0b: Secure Lan\n- #ef4444: DMZ\n- #475569: Main ISP\n- #3b82f6: Alternate ISP\n- #8b5cf6: you can edit me too\n- #06b6d4: you can edit me too\n- #a855f7: you can edit me too\n- #f97316: you can edit me too\n- #0ea5e9: you can edit me too\n- #22c55e: you can edit me too\n- #94a3b8: you can edit me too\n- #fbbf24: you can edit me too\n- #38bdf8: you can edit me too\n- #c800ff: you can edit me too\n\n## Nodes\n\n### core-router-1\n- **Name:** Core Router 1\n- **IP:** 10.0.0.1\n- **Role:** Core Routing\n- **Shape:** router\n- **Tags:** core; tier-1; redundant\n- **Layer:** physical\n- **MAC:** 00:1A:2B:3C:4D:01\n- **Rack Unit:** \n- **U Height:** 2\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 3720, 246\n- **Size:** 50\n- **Notes:**\n  - Primary core router\n  - BGP peering enabled\n- **Styles:** `{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"borg\"}}}`\n\n### core-router-2\n- **Name:** Core Router 2\n- **IP:** 10.0.0.2\n- **Role:** Core Routing\n- **Shape:** router\n- **Tags:** core; tier-1; redundant\n- **Layer:** physical\n- **MAC:** 00:1A:2B:3C:4D:02\n- **Rack Unit:** \n- **U Height:** 2\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 2500, 330\n- **Size:** 120\n- **Notes:**\n  - Secondary core router\n  - HSRP standby\n- **Styles:** `{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"actual-budget\"},\"pingOffsetX\":-15,\"pingOffsetY\":-38}}`\n\n### fw-external-1\n- **Name:** External FW 1\n- **IP:** 10.0.1.1\n- **Role:** Perimeter Security\n- **Shape:** firewall\n- **Tags:** security; perimeter; ha-pair\n- **Layer:** security\n- **MAC:** 00:1A:2B:3C:4D:10\n- **Rack Unit:** \n- **U Height:** 2\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 3222, 1016\n- **Size:** 50\n- **Notes:**\n  - Palo Alto PA-5250\n  - Active node\n- **Styles:** `{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"anonaddy\"}}}`\n\n### fw-external-2\n- **Name:** External FW 2\n- **IP:** 10.0.1.2\n- **Role:** Perimeter Security\n- **Shape:** firewall\n- **Tags:** security; perimeter; ha-pair\n- **Layer:** security\n- **MAC:** 00:1A:2B:3C:4D:11\n- **Rack Unit:** \n- **U Height:** 2\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 1916, 224\n- **Size:** 50\n- **Notes:**\n  - Palo Alto PA-5250\n  - Passive node\n\n### fw-internal\n- **Name:** Internal FW\n- **IP:** 10.0.2.1\n- **Role:** Internal Segmentation\n- **Shape:** firewall\n- **Tags:** security; internal\n- **Layer:** security\n- **MAC:** 00:1A:2B:3C:4D:12\n- **Rack Unit:** \n- **U Height:** 2\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 1747, 478\n- **Size:** 50\n- **Notes:**\n  - East-West traffic inspection\n\n### core-switch-1\n- **Name:** Core Switch 1\n- **IP:** 10.0.10.1\n- **Role:** Core Switching\n- **Shape:** switch\n- **Tags:** core; layer3; redundant\n- **Layer:** physical\n- **MAC:** 00:1A:2B:3C:4D:20\n- **Rack Unit:** \n- **U Height:** 2\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 449, 384\n- **Size:** 50\n- **Notes:**\n  - Cisco Nexus 9000\n  - VPC Domain 1\n\n### core-switch-2\n- **Name:** Core Switch 2\n- **IP:** 10.0.10.2\n- **Role:** Core Switching\n- **Shape:** switch\n- **Tags:** core; layer3; redundant\n- **Layer:** physical\n- **MAC:** 00:1A:2B:3C:4D:21\n- **Rack Unit:** \n- **U Height:** 2\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 761, 181\n- **Size:** 50\n- **Notes:**\n  - Cisco Nexus 9000\n  - VPC Domain 1\n\n### dc-rack-a1\n- **Name:** DC Rack A1\n- **IP:** 10.10.0.0/24\n- **Role:** Data Center Rack\n- **Shape:** server\n- **Tags:** datacenter; row-a; production\n- **Layer:** physical\n- **MAC:** \n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** true\n- **Locked:** false\n- **Group ID:** \n- **Position:** 784, 647\n- **Size:** 50\n- **Notes:**\n  - Row A, Position 1\n  - Primary compute\n- **Styles:** `{\"all\":{\"circleColor\":\"#ff0000\"}}`\n\n### dc-rack-a2\n- **Name:** DC Rack A2\n- **IP:** 10.10.1.0/24\n- **Role:** Data Center Rack\n- **Shape:** server\n- **Tags:** datacenter; row-a; production\n- **Layer:** physical\n- **MAC:** \n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** true\n- **Locked:** false\n- **Group ID:** \n- **Position:** 209, 228\n- **Size:** 50\n- **Notes:**\n  - Row A, Position 2\n  - Primary compute\n\n### dc-rack-b1\n- **Name:** DC Rack B1\n- **IP:** 10.10.2.0/24\n- **Role:** Data Center Rack\n- **Shape:** server\n- **Tags:** datacenter; row-b; storage\n- **Layer:** physical\n- **MAC:** \n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** true\n- **Locked:** false\n- **Group ID:** \n- **Position:** 3184, 1627\n- **Size:** 50\n- **Notes:**\n  - Row B, Position 1\n  - Storage systems\n- **Styles:** `{\"all\":{\"circleColor\":\"#ff0000\",\"titleSize\":59}}`\n\n### dc-rack-b2\n- **Name:** DC Rack B2\n- **IP:** 10.10.3.0/24\n- **Role:** Data Center Rack\n- **Shape:** server\n- **Tags:** datacenter; row-b; storage\n- **Layer:** physical\n- **MAC:** \n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** true\n- **Locked:** false\n- **Group ID:** \n- **Position:** 245, 500\n- **Size:** 50\n- **Notes:**\n  - Row B, Position 2\n  - Storage systems\n- **Styles:** `{\"all\":{\"circleColor\":\"#ff0000\"}}`\n\n### dmz-rack\n- **Name:** DMZ Rack\n- **IP:** 172.16.0.0/24\n- **Role:** DMZ Infrastructure\n- **Shape:** server\n- **Tags:** dmz; security; public-facing; [object Object]; [object Object]\n- **Layer:** security\n- **MAC:** \n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 24\n- **Is Rack:** true\n- **Locked:** false\n- **Group ID:** \n- **Position:** 2176, 611\n- **Size:** 50\n- **Notes:**\n  - Isolated DMZ zone\n  - Public-facing services\n\n### mgmt-rack\n- **Name:** Management Rack\n- **IP:** 192.168.100.0/24\n- **Role:** Management Infrastructure\n- **Shape:** server\n- **Tags:** management; oob; noc\n- **Layer:** logical\n- **MAC:** \n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 24\n- **Is Rack:** true\n- **Locked:** false\n- **Group ID:** \n- **Position:** 1601, 1281\n- **Size:** 50\n- **Notes:**\n  - Out-of-band management\n  - NOC equipment\n\n### esxi-host-01\n- **Name:** ESXi Host 01\n- **IP:** 10.10.0.11\n- **Role:** Hypervisor\n- **Shape:** server\n- **Tags:** vmware; compute; cluster-a\n- **Layer:** physical\n- **MAC:** 00:50:56:AA:01:01\n- **Rack Unit:** 38\n- **U Height:** 2\n- **Assigned Rack:** dc-rack-a1\n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 2162, 2608\n- **Size:** 50\n- **Notes:**\n  - Dell PowerEdge R750\n  - 512GB RAM\n  - vSphere 8.0\n\n### esxi-host-02\n- **Name:** ESXi Host 02\n- **IP:** 10.10.0.12\n- **Role:** Hypervisor\n- **Shape:** server\n- **Tags:** vmware; compute; cluster-a\n- **Layer:** physical\n- **MAC:** 00:50:56:AA:01:02\n- **Rack Unit:** 35\n- **U Height:** 2\n- **Assigned Rack:** dc-rack-a1\n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 2206, 2690\n- **Size:** 50\n- **Notes:**\n  - Dell PowerEdge R750\n  - 512GB RAM\n  - vSphere 8.0\n\n### esxi-host-03\n- **Name:** ESXi Host 03\n- **IP:** 10.10.0.13\n- **Role:** Hypervisor\n- **Shape:** server\n- **Tags:** vmware; compute; cluster-a\n- **Layer:** physical\n- **MAC:** 00:50:56:AA:01:03\n- **Rack Unit:** 32\n- **U Height:** 2\n- **Assigned Rack:** dc-rack-a1\n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 2155, 2771\n- **Size:** 50\n- **Notes:**\n  - Dell PowerEdge R750\n  - 512GB RAM\n  - vSphere 8.0\n\n### esxi-host-04\n- **Name:** ESXi Host 04\n- **IP:** 10.10.0.14\n- **Role:** Hypervisor\n- **Shape:** server\n- **Tags:** vmware; compute; cluster-a\n- **Layer:** physical\n- **MAC:** 00:50:56:AA:01:04\n- **Rack Unit:** 29\n- **U Height:** 2\n- **Assigned Rack:** dc-rack-a1\n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 2196, 2845\n- **Size:** 50\n- **Notes:**\n  - Dell PowerEdge R750\n  - 512GB RAM\n  - vSphere 8.0\n\n### tor-switch-a1\n- **Name:** ToR Switch A1\n- **IP:** 10.10.0.1\n- **Role:** Top of Rack\n- **Shape:** switch\n- **Tags:** tor; access; rack-a1\n- **Layer:** physical\n- **MAC:** 00:1A:2B:3C:5D:01\n- **Rack Unit:** 42\n- **U Height:** 1\n- **Assigned Rack:** dc-rack-a1\n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 2147, 2845\n- **Size:** 50\n- **Notes:**\n  - Cisco Nexus 93180YC-FX\n  - 48x25G ports\n\n### esxi-host-05\n- **Name:** ESXi Host 05\n- **IP:** 10.10.1.11\n- **Role:** Hypervisor\n- **Shape:** server\n- **Tags:** vmware; compute; cluster-b\n- **Layer:** physical\n- **MAC:** 00:50:56:AA:02:01\n- **Rack Unit:** 38\n- **U Height:** 2\n- **Assigned Rack:** dc-rack-a2\n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 2186, 2845\n- **Size:** 50\n- **Notes:**\n  - Dell PowerEdge R750\n  - 768GB RAM\n  - vSphere 8.0\n\n### esxi-host-06\n- **Name:** ESXi Host 06\n- **IP:** 10.10.1.12\n- **Role:** Hypervisor\n- **Shape:** server\n- **Tags:** vmware; compute; cluster-b\n- **Layer:** physical\n- **MAC:** 00:50:56:AA:02:02\n- **Rack Unit:** 35\n- **U Height:** 2\n- **Assigned Rack:** dc-rack-a2\n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 2139, 2845\n- **Size:** 50\n- **Notes:**\n  - Dell PowerEdge R750\n  - 768GB RAM\n  - vSphere 8.0\n\n### esxi-host-07\n- **Name:** ESXi Host 07\n- **IP:** 10.10.1.13\n- **Role:** Hypervisor\n- **Shape:** server\n- **Tags:** vmware; compute; cluster-b\n- **Layer:** physical\n- **MAC:** 00:50:56:AA:02:03\n- **Rack Unit:** 32\n- **U Height:** 2\n- **Assigned Rack:** dc-rack-a2\n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 2176, 2845\n- **Size:** 50\n- **Notes:**\n  - Dell PowerEdge R750\n  - 768GB RAM\n  - vSphere 8.0\n\n### esxi-host-08\n- **Name:** ESXi Host 08\n- **IP:** 10.10.1.14\n- **Role:** Hypervisor\n- **Shape:** server\n- **Tags:** vmware; compute; cluster-b\n- **Layer:** physical\n- **MAC:** 00:50:56:AA:02:04\n- **Rack Unit:** 29\n- **U Height:** 2\n- **Assigned Rack:** dc-rack-a2\n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 2131, 2845\n- **Size:** 50\n- **Notes:**\n  - Dell PowerEdge R750\n  - 768GB RAM\n  - vSphere 8.0\n\n### tor-switch-a2\n- **Name:** ToR Switch A2\n- **IP:** 10.10.1.1\n- **Role:** Top of Rack\n- **Shape:** switch\n- **Tags:** tor; access; rack-a2\n- **Layer:** physical\n- **MAC:** 00:1A:2B:3C:5D:02\n- **Rack Unit:** 42\n- **U Height:** 1\n- **Assigned Rack:** dc-rack-a2\n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 2165, 2845\n- **Size:** 50\n- **Notes:**\n  - Cisco Nexus 93180YC-FX\n  - 48x25G ports\n\n### san-primary\n- **Name:** SAN Primary\n- **IP:** 10.10.2.10\n- **Role:** Primary Storage\n- **Shape:** database\n- **Tags:** storage; san; netapp\n- **Layer:** physical\n- **MAC:** 00:A0:98:AA:01:01\n- **Rack Unit:** 36\n- **U Height:** 6\n- **Assigned Rack:** dc-rack-b1\n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 2123, 2845\n- **Size:** 50\n- **Notes:**\n  - NetApp AFF A400\n  - 500TB Raw\n  - FC 32Gb\n\n### san-secondary\n- **Name:** SAN Secondary\n- **IP:** 10.10.2.11\n- **Role:** Secondary Storage\n- **Shape:** database\n- **Tags:** storage; san; netapp\n- **Layer:** physical\n- **MAC:** 00:A0:98:AA:01:02\n- **Rack Unit:** 28\n- **U Height:** 6\n- **Assigned Rack:** dc-rack-b1\n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 2155, 2845\n- **Size:** 50\n- **Notes:**\n  - NetApp AFF A400\n  - 500TB Raw\n  - FC 32Gb\n\n### fc-switch-1\n- **Name:** FC Switch 1\n- **IP:** 10.10.2.1\n- **Role:** Fibre Channel\n- **Shape:** switch\n- **Tags:** storage; fc; fabric-a\n- **Layer:** physical\n- **MAC:** 00:1A:2B:FC:01:01\n- **Rack Unit:** 42\n- **U Height:** 1\n- **Assigned Rack:** dc-rack-b1\n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 2115, 2845\n- **Size:** 50\n- **Notes:**\n  - Brocade G620\n  - Fabric A\n\n### fc-switch-2\n- **Name:** FC Switch 2\n- **IP:** 10.10.2.2\n- **Role:** Fibre Channel\n- **Shape:** switch\n- **Tags:** storage; fc; fabric-b\n- **Layer:** physical\n- **MAC:** 00:1A:2B:FC:01:02\n- **Rack Unit:** 41\n- **U Height:** 1\n- **Assigned Rack:** dc-rack-b1\n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 2145, 2845\n- **Size:** 50\n- **Notes:**\n  - Brocade G620\n  - Fabric B\n\n### backup-server-1\n- **Name:** Backup Server 1\n- **IP:** 10.10.3.10\n- **Role:** Backup Infrastructure\n- **Shape:** server\n- **Tags:** backup; veeam; protection\n- **Layer:** physical\n- **MAC:** 00:50:56:BB:01:01\n- **Rack Unit:** 36\n- **U Height:** 2\n- **Assigned Rack:** dc-rack-b2\n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 2107, 2845\n- **Size:** 50\n- **Notes:**\n  - Veeam Backup Server\n  - Dell R740xd\n  - 200TB\n\n### backup-server-2\n- **Name:** Backup Server 2\n- **IP:** 10.10.3.11\n- **Role:** Backup Infrastructure\n- **Shape:** server\n- **Tags:** backup; veeam; protection\n- **Layer:** physical\n- **MAC:** 00:50:56:BB:01:02\n- **Rack Unit:** 33\n- **U Height:** 2\n- **Assigned Rack:** dc-rack-b2\n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 2134, 2845\n- **Size:** 50\n- **Notes:**\n  - Veeam Backup Server\n  - Dell R740xd\n  - 200TB\n\n### tape-library\n- **Name:** Tape Library\n- **IP:** 10.10.3.20\n- **Role:** Archival Storage\n- **Shape:** database\n- **Tags:** backup; tape; lto9\n- **Layer:** physical\n- **MAC:** 00:50:56:BB:02:01\n- **Rack Unit:** 20\n- **U Height:** 10\n- **Assigned Rack:** dc-rack-b2\n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 2099, 2845\n- **Size:** 50\n- **Notes:**\n  - IBM TS4500\n  - LTO-9\n  - Long-term archive\n\n### tor-switch-b1\n- **Name:** ToR Switch B1\n- **IP:** 10.10.2.3\n- **Role:** Top of Rack\n- **Shape:** switch\n- **Tags:** tor; access; rack-b1\n- **Layer:** physical\n- **MAC:** 00:1A:2B:3C:5D:03\n- **Rack Unit:** 40\n- **U Height:** 1\n- **Assigned Rack:** dc-rack-b1\n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 2123, 2845\n- **Size:** 50\n- **Notes:**\n  - Cisco Nexus 93180YC-FX\n\n### tor-switch-b2\n- **Name:** ToR Switch B2\n- **IP:** 10.10.3.1\n- **Role:** Top of Rack\n- **Shape:** switch\n- **Tags:** tor; access; rack-b2\n- **Layer:** physical\n- **MAC:** 00:1A:2B:3C:5D:04\n- **Rack Unit:** 42\n- **U Height:** 1\n- **Assigned Rack:** dc-rack-b2\n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 2091, 2845\n- **Size:** 50\n- **Notes:**\n  - Cisco Nexus 93180YC-FX\n\n### web-server-1\n- **Name:** Web Server 1\n- **IP:** 172.16.0.11\n- **Role:** Web Frontend\n- **Shape:** server\n- **Tags:** dmz; web; nginx\n- **Layer:** security\n- **MAC:** 00:50:56:CC:01:01\n- **Rack Unit:** 20\n- **U Height:** 1\n- **Assigned Rack:** dmz-rack\n- **Rack Capacity:** 24\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 2113, 2845\n- **Size:** 50\n- **Notes:**\n  - NGINX reverse proxy\n  - Public facing\n\n### web-server-2\n- **Name:** Web Server 2\n- **IP:** 172.16.0.12\n- **Role:** Web Frontend\n- **Shape:** server\n- **Tags:** dmz; web; nginx\n- **Layer:** security\n- **MAC:** 00:50:56:CC:01:02\n- **Rack Unit:** 18\n- **U Height:** 1\n- **Assigned Rack:** dmz-rack\n- **Rack Capacity:** 24\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 2082, 2845\n- **Size:** 50\n- **Notes:**\n  - NGINX reverse proxy\n  - Public facing\n\n### waf-1\n- **Name:** WAF Appliance\n- **IP:** 172.16.0.5\n- **Role:** Web Application Firewall\n- **Shape:** firewall\n- **Tags:** dmz; security; waf\n- **Layer:** security\n- **MAC:** 00:50:56:CC:02:01\n- **Rack Unit:** 22\n- **U Height:** 2\n- **Assigned Rack:** dmz-rack\n- **Rack Capacity:** 24\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 2102, 2845\n- **Size:** 50\n- **Notes:**\n  - F5 BIG-IP ASM\n  - OWASP protection\n\n### load-balancer-dmz\n- **Name:** DMZ Load Balancer\n- **IP:** 172.16.0.3\n- **Role:** Load Balancing\n- **Shape:** switch\n- **Tags:** dmz; lb; f5\n- **Layer:** security\n- **MAC:** 00:50:56:CC:03:01\n- **Rack Unit:** 16\n- **U Height:** 2\n- **Assigned Rack:** dmz-rack\n- **Rack Capacity:** 24\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 2074, 2845\n- **Size:** 50\n- **Notes:**\n  - F5 BIG-IP LTM\n  - VIP: 172.16.0.100\n\n### mail-gateway\n- **Name:** Mail Gateway\n- **IP:** 172.16.0.25\n- **Role:** Email Security\n- **Shape:** server\n- **Tags:** dmz; email; security\n- **Layer:** security\n- **MAC:** 00:50:56:CC:04:01\n- **Rack Unit:** 14\n- **U Height:** 1\n- **Assigned Rack:** dmz-rack\n- **Rack Capacity:** 24\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 2091, 2845\n- **Size:** 50\n- **Notes:**\n  - Proofpoint Email Gateway\n  - Spam/malware filtering\n\n### dns-external-1\n- **Name:** External DNS 1\n- **IP:** 172.16.0.53\n- **Role:** External DNS\n- **Shape:** circle\n- **Tags:** dmz; dns; public\n- **Layer:** security\n- **MAC:** 00:50:56:CC:05:01\n- **Rack Unit:** 12\n- **U Height:** 1\n- **Assigned Rack:** dmz-rack\n- **Rack Capacity:** 24\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 2066, 2845\n- **Size:** 50\n- **Notes:**\n  - BIND DNS\n  - Authoritative for corp.com\n\n### dns-external-2\n- **Name:** External DNS 2\n- **IP:** 172.16.0.54\n- **Role:** External DNS\n- **Shape:** circle\n- **Tags:** dmz; dns; public\n- **Layer:** security\n- **MAC:** 00:50:56:CC:05:02\n- **Rack Unit:** 10\n- **U Height:** 1\n- **Assigned Rack:** dmz-rack\n- **Rack Capacity:** 24\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 2080, 2845\n- **Size:** 50\n- **Notes:**\n  - BIND DNS\n  - Secondary for corp.com\n\n### vcenter\n- **Name:** vCenter Server\n- **IP:** 192.168.100.10\n- **Role:** Virtualization Management\n- **Shape:** server\n- **Tags:** management; vmware; vcsa\n- **Layer:** logical\n- **MAC:** 00:50:56:DD:01:01\n- **Rack Unit:** 20\n- **U Height:** 2\n- **Assigned Rack:** mgmt-rack\n- **Rack Capacity:** 24\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 2057, 2845\n- **Size:** 50\n- **Notes:**\n  - vCenter Server Appliance 8.0\n  - Single SSO domain\n\n### nsx-manager\n- **Name:** NSX Manager\n- **IP:** 192.168.100.15\n- **Role:** Network Virtualization\n- **Shape:** server\n- **Tags:** management; vmware; nsx\n- **Layer:** logical\n- **MAC:** 00:50:56:DD:02:01\n- **Rack Unit:** 17\n- **U Height:** 2\n- **Assigned Rack:** mgmt-rack\n- **Rack Capacity:** 24\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 2069, 2845\n- **Size:** 50\n- **Notes:**\n  - NSX-T 4.1 Manager Cluster\n\n### siem-server\n- **Name:** SIEM Server\n- **IP:** 192.168.100.50\n- **Role:** Security Monitoring\n- **Shape:** server\n- **Tags:** management; security; splunk\n- **Layer:** logical\n- **MAC:** 00:50:56:DD:03:01\n- **Rack Unit:** 14\n- **U Height:** 2\n- **Assigned Rack:** mgmt-rack\n- **Rack Capacity:** 24\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 2049, 2845\n- **Size:** 50\n- **Notes:**\n  - Splunk Enterprise\n  - Security monitoring\n\n### nms-server\n- **Name:** Network Monitoring\n- **IP:** 192.168.100.60\n- **Role:** Network Management\n- **Shape:** server\n- **Tags:** management; monitoring; prtg\n- **Layer:** logical\n- **MAC:** 00:50:56:DD:04:01\n- **Rack Unit:** 11\n- **U Height:** 1\n- **Assigned Rack:** mgmt-rack\n- **Rack Capacity:** 24\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 2058, 2845\n- **Size:** 50\n- **Notes:**\n  - PRTG Network Monitor\n  - 5000 sensors\n\n### jump-server\n- **Name:** Jump Server\n- **IP:** 192.168.100.100\n- **Role:** Bastion Host\n- **Shape:** server\n- **Tags:** management; security; bastion\n- **Layer:** logical\n- **MAC:** 00:50:56:DD:05:01\n- **Rack Unit:** 9\n- **U Height:** 1\n- **Assigned Rack:** mgmt-rack\n- **Rack Capacity:** 24\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 2040, 2845\n- **Size:** 50\n- **Notes:**\n  - Windows Server 2022\n  - MFA enabled\n\n### ipam-server\n- **Name:** IPAM/DDI\n- **IP:** 192.168.100.70\n- **Role:** IP Management\n- **Shape:** server\n- **Tags:** management; dns; dhcp\n- **Layer:** logical\n- **MAC:** 00:50:56:DD:06:01\n- **Rack Unit:** 7\n- **U Height:** 2\n- **Assigned Rack:** mgmt-rack\n- **Rack Capacity:** 24\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 2047, 2845\n- **Size:** 50\n- **Notes:**\n  - Infoblox DDI\n  - DNS/DHCP/IPAM\n\n### wlc-primary\n- **Name:** WLC Primary\n- **IP:** 10.20.0.1\n- **Role:** Wireless Controller\n- **Shape:** wifi\n- **Tags:** wireless; cisco; 9800\n- **Layer:** physical\n- **MAC:** 00:1A:2B:WL:01:01\n- **Rack Unit:** \n- **U Height:** 2\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 1576, 2306\n- **Size:** 50\n- **Notes:**\n  - Cisco C9800-40\n  - Primary controller\n\n### wlc-secondary\n- **Name:** WLC Secondary\n- **IP:** 10.20.0.2\n- **Role:** Wireless Controller\n- **Shape:** wifi\n- **Tags:** wireless; cisco; 9800\n- **Layer:** physical\n- **MAC:** 00:1A:2B:WL:01:02\n- **Rack Unit:** \n- **U Height:** 2\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 1468, 1564\n- **Size:** 50\n- **Notes:**\n  - Cisco C9800-40\n  - HA Secondary\n\n### mobile-zone-hq\n- **Name:** HQ Mobile Zone\n- **IP:** 10.20.10.0/24\n- **Role:** Mobile Device Zone\n- **Shape:** phone\n- **Tags:** wireless; byod; mobile\n- **Layer:** physical\n- **MAC:** \n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 2355, 2806\n- **Size:** 50\n- **Notes:**\n  - Corporate BYOD\n  - MDM enrolled devices\n\n### mobile-zone-guest\n- **Name:** Guest WiFi Zone\n- **IP:** 10.30.0.0/24\n- **Role:** Guest Network\n- **Shape:** phone\n- **Tags:** wireless; guest; isolated\n- **Layer:** physical\n- **MAC:** \n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 2308, 2611\n- **Size:** 50\n- **Notes:**\n  - Captive portal\n  - Internet only\n\n### mobile-zone-iot\n- **Name:** IoT Device Zone\n- **IP:** 10.40.0.0/24\n- **Role:** IoT Network\n- **Shape:** phone\n- **Tags:** wireless; iot; building\n- **Layer:** physical\n- **MAC:** \n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 2229, 2299\n- **Size:** 50\n- **Notes:**\n  - Building automation\n  - Smart devices\n\n### branch-router-ny\n- **Name:** NYC Branch Router\n- **IP:** 10.100.0.1\n- **Role:** Branch Gateway\n- **Shape:** router\n- **Tags:** branch; nyc; sd-wan\n- **Layer:** physical\n- **MAC:** 00:1A:2B:BR:01:01\n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 3152, 634\n- **Size:** 50\n- **Notes:**\n  - Cisco Viptela vEdge\n  - SD-WAN enabled\n\n### branch-router-la\n- **Name:** LA Branch Router\n- **IP:** 10.101.0.1\n- **Role:** Branch Gateway\n- **Shape:** router\n- **Tags:** branch; la; sd-wan\n- **Layer:** physical\n- **MAC:** 00:1A:2B:BR:02:01\n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 3084, 507\n- **Size:** 50\n- **Notes:**\n  - Cisco Viptela vEdge\n  - SD-WAN enabled\n\n### branch-router-chi\n- **Name:** Chicago Branch Router\n- **IP:** 10.102.0.1\n- **Role:** Branch Gateway\n- **Shape:** router\n- **Tags:** branch; chicago; sd-wan\n- **Layer:** physical\n- **MAC:** 00:1A:2B:BR:03:01\n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 3355, 393\n- **Size:** 50\n- **Notes:**\n  - Cisco Viptela vEdge\n  - SD-WAN enabled\n\n### branch-router-lon\n- **Name:** London Branch Router\n- **IP:** 10.200.0.1\n- **Role:** Branch Gateway\n- **Shape:** router\n- **Tags:** branch; london; sd-wan\n- **Layer:** physical\n- **MAC:** 00:1A:2B:BR:04:01\n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 3114, 260\n- **Size:** 50\n- **Notes:**\n  - Cisco Viptela vEdge\n  - EMEA region\n\n### branch-router-tokyo\n- **Name:** Tokyo Branch Router\n- **IP:** 10.201.0.1\n- **Role:** Branch Gateway\n- **Shape:** router\n- **Tags:** branch; tokyo; sd-wan\n- **Layer:** physical\n- **MAC:** 00:1A:2B:BR:05:01\n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 3699, 471\n- **Size:** 50\n- **Notes:**\n  - Cisco Viptela vEdge\n  - APAC region\n- **Styles:** `{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"adguard-home\"}}}`\n\n### cloud-aws\n- **Name:** AWS Cloud\n- **IP:** vpc-0a1b2c3d\n- **Role:** Public Cloud\n- **Shape:** cloud\n- **Tags:** cloud; aws; hybrid\n- **Layer:** logical\n- **MAC:** \n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 3437, 546\n- **Size:** 50\n- **Notes:**\n  - AWS US-East-1\n  - VPC peering to HQ\n- **Styles:** `{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"ansible\"}}}`\n\n### cloud-azure\n- **Name:** Azure Cloud\n- **IP:** vnet-corp-prod\n- **Role:** Public Cloud\n- **Shape:** cloud\n- **Tags:** cloud; azure; hybrid\n- **Layer:** logical\n- **MAC:** \n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 2593, 2724\n- **Size:** 50\n- **Notes:**\n  - Azure East US 2\n  - ExpressRoute\n\n### cloud-gcp\n- **Name:** GCP Cloud\n- **IP:** vpc-gcp-corp\n- **Role:** Public Cloud\n- **Shape:** cloud\n- **Tags:** cloud; gcp; dev\n- **Layer:** logical\n- **MAC:** \n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 2827, 2731\n- **Size:** 50\n- **Notes:**\n  - GCP us-central1\n  - Dev/Test workloads\n\n### isp-primary\n- **Name:** ISP Primary\n- **IP:** 203.0.113.1\n- **Role:** Internet Uplink\n- **Shape:** globe\n- **Tags:** wan; internet; primary\n- **Layer:** physical\n- **MAC:** \n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 3712, 616\n- **Size:** 50\n- **Notes:**\n  - AT&T MPLS\n  - 1 Gbps dedicated\n- **Styles:** `{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"wikidocs\"}}}`\n\n### isp-secondary\n- **Name:** ISP Secondary\n- **IP:** 198.51.100.1\n- **Role:** Internet Uplink\n- **Shape:** globe\n- **Tags:** wan; internet; backup\n- **Layer:** physical\n- **MAC:** \n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 2702, 468\n- **Size:** 139\n- **Notes:**\n  - Verizon Business\n  - 500 Mbps backup\n- **Styles:** `{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"alist\"}}}`\n\n### dc-internal-1\n- **Name:** DC1 Int DNS\n- **IP:** 10.10.0.53\n- **Role:** Internal DNS/AD\n- **Shape:** circle\n- **Tags:** dns; ad; dc1\n- **Layer:** physical\n- **MAC:** 00:50:56:AD:01:01\n- **Rack Unit:** 26\n- **U Height:** 1\n- **Assigned Rack:** dc-rack-a1\n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 1958, 2845\n- **Size:** 50\n- **Notes:**\n  - Windows Server 2022\n  - Primary DC\n\n### dc-internal-2\n- **Name:** DC2 Int DNS\n- **IP:** 10.10.1.53\n- **Role:** Internal DNS/AD\n- **Shape:** circle\n- **Tags:** dns; ad; dc2\n- **Layer:** physical\n- **MAC:** 00:50:56:AD:01:02\n- **Rack Unit:** 26\n- **U Height:** 1\n- **Assigned Rack:** dc-rack-a2\n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 1964, 2845\n- **Size:** 50\n- **Notes:**\n  - Windows Server 2022\n  - Secondary DC\n\n### app-server-1\n- **Name:** App Server 01\n- **IP:** 10.10.0.101\n- **Role:** Application\n- **Shape:** server\n- **Tags:** app; iis; web\n- **Layer:** physical\n- **MAC:** 00:50:56:AP:01:01\n- **Rack Unit:** 24\n- **U Height:** 1\n- **Assigned Rack:** dc-rack-a1\n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 1947, 2845\n- **Size:** 50\n- **Notes:**\n  - Windows Server 2022\n  - IIS Application\n\n### app-server-2\n- **Name:** App Server 02\n- **IP:** 10.10.0.102\n- **Role:** Application\n- **Shape:** server\n- **Tags:** app; iis; web\n- **Layer:** physical\n- **MAC:** 00:50:56:AP:01:02\n- **Rack Unit:** 22\n- **U Height:** 1\n- **Assigned Rack:** dc-rack-a1\n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 1955, 2845\n- **Size:** 50\n- **Notes:**\n  - Windows Server 2022\n  - IIS Application\n\n### db-server-1\n- **Name:** SQL Server 01\n- **IP:** 10.10.0.201\n- **Role:** Database\n- **Shape:** database\n- **Tags:** db; sql; primary\n- **Layer:** physical\n- **MAC:** 00:50:56:DB:01:01\n- **Rack Unit:** 20\n- **U Height:** 2\n- **Assigned Rack:** dc-rack-a1\n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 1936, 2845\n- **Size:** 50\n- **Notes:**\n  - SQL Server 2022 Enterprise\n  - AlwaysOn Primary\n\n### db-server-2\n- **Name:** SQL Server 02\n- **IP:** 10.10.1.201\n- **Role:** Database\n- **Shape:** database\n- **Tags:** db; sql; secondary\n- **Layer:** physical\n- **MAC:** 00:50:56:DB:01:02\n- **Rack Unit:** 24\n- **U Height:** 2\n- **Assigned Rack:** dc-rack-a2\n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 1947, 2845\n- **Size:** 50\n- **Notes:**\n  - SQL Server 2022 Enterprise\n  - AlwaysOn Secondary\n\n### k8s-master-1\n- **Name:** K8s Master 1\n- **IP:** 10.10.1.50\n- **Role:** Container Orchestration\n- **Shape:** hexagon\n- **Tags:** kubernetes; master; container\n- **Layer:** physical\n- **MAC:** 00:50:56:K8:01:01\n- **Rack Unit:** 21\n- **U Height:** 1\n- **Assigned Rack:** dc-rack-a2\n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 1925, 2845\n- **Size:** 50\n- **Notes:**\n  - K8s Control Plane\n  - etcd member\n\n### k8s-master-2\n- **Name:** K8s Master 2\n- **IP:** 10.10.1.51\n- **Role:** Container Orchestration\n- **Shape:** hexagon\n- **Tags:** kubernetes; master; container\n- **Layer:** physical\n- **MAC:** 00:50:56:K8:01:02\n- **Rack Unit:** 19\n- **U Height:** 1\n- **Assigned Rack:** dc-rack-a2\n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 1938, 2845\n- **Size:** 50\n- **Notes:**\n  - K8s Control Plane\n  - etcd member\n\n### k8s-master-3\n- **Name:** K8s Master 3\n- **IP:** 10.10.1.52\n- **Role:** Container Orchestration\n- **Shape:** hexagon\n- **Tags:** kubernetes; master; container\n- **Layer:** physical\n- **MAC:** 00:50:56:K8:01:03\n- **Rack Unit:** 17\n- **U Height:** 1\n- **Assigned Rack:** dc-rack-a2\n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 1914, 2845\n- **Size:** 50\n- **Notes:**\n  - K8s Control Plane\n  - etcd member\n\n### k8s-worker-1\n- **Name:** K8s Worker 1\n- **IP:** 10.10.1.60\n- **Role:** Container Workload\n- **Shape:** server\n- **Tags:** kubernetes; worker; container\n- **Layer:** physical\n- **MAC:** 00:50:56:K8:02:01\n- **Rack Unit:** 15\n- **U Height:** 1\n- **Assigned Rack:** dc-rack-a2\n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 1930, 2845\n- **Size:** 50\n- **Notes:**\n  - K8s Worker Node\n  - 64GB RAM\n\n### k8s-worker-2\n- **Name:** K8s Worker 2\n- **IP:** 10.10.1.61\n- **Role:** Container Workload\n- **Shape:** server\n- **Tags:** kubernetes; worker; container\n- **Layer:** physical\n- **MAC:** 00:50:56:K8:02:02\n- **Rack Unit:** 13\n- **U Height:** 1\n- **Assigned Rack:** dc-rack-a2\n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 1904, 2845\n- **Size:** 50\n- **Notes:**\n  - K8s Worker Node\n  - 64GB RAM\n\n### k8s-worker-3\n- **Name:** K8s Worker 3\n- **IP:** 10.10.1.62\n- **Role:** Container Workload\n- **Shape:** server\n- **Tags:** kubernetes; worker; container\n- **Layer:** physical\n- **MAC:** 00:50:56:K8:02:03\n- **Rack Unit:** 11\n- **U Height:** 1\n- **Assigned Rack:** dc-rack-a2\n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 1922, 2845\n- **Size:** 50\n- **Notes:**\n  - K8s Worker Node\n  - 64GB RAM\n\n### k8s-worker-4\n- **Name:** K8s Worker 4\n- **IP:** 10.10.1.63\n- **Role:** Container Workload\n- **Shape:** server\n- **Tags:** kubernetes; worker; container\n- **Layer:** physical\n- **MAC:** 00:50:56:K8:02:04\n- **Rack Unit:** 9\n- **U Height:** 1\n- **Assigned Rack:** dc-rack-a2\n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 1893, 2845\n- **Size:** 50\n- **Notes:**\n  - K8s Worker Node\n  - 64GB RAM\n\n### proxy-server-1\n- **Name:** Proxy Server 1\n- **IP:** 10.5.0.10\n- **Role:** Web Proxy\n- **Shape:** server\n- **Tags:** proxy; squid; filtering\n- **Layer:** security\n- **MAC:** 00:50:56:PX:01:01\n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 1806, 654\n- **Size:** 50\n- **Notes:**\n  - Squid Proxy\n  - Content filtering\n\n### proxy-server-2\n- **Name:** Proxy Server 2\n- **IP:** 10.5.0.11\n- **Role:** Web Proxy\n- **Shape:** server\n- **Tags:** proxy; squid; filtering\n- **Layer:** security\n- **MAC:** 00:50:56:PX:01:02\n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 2937, 2629\n- **Size:** 50\n- **Notes:**\n  - Squid Proxy\n  - HA pair\n\n### vpn-concentrator\n- **Name:** VPN Concentrator\n- **IP:** 10.0.5.1\n- **Role:** Remote Access VPN\n- **Shape:** firewall\n- **Tags:** vpn; remote; security\n- **Layer:** security\n- **MAC:** 00:1A:2B:VP:01:01\n- **Rack Unit:** \n- **U Height:** 2\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 3642, 947\n- **Size:** 50\n- **Notes:**\n  - Cisco ASA 5555-X\n  - AnyConnect SSL VPN\n\n### nac-server\n- **Name:** NAC Server\n- **IP:** 10.5.5.10\n- **Role:** Network Access Control\n- **Shape:** server\n- **Tags:** nac; ise; 802.1x\n- **Layer:** security\n- **MAC:** 00:50:56:NA:01:01\n- **Rack Unit:** \n- **U Height:** 2\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 1153, 1172\n- **Size:** 50\n- **Notes:**\n  - Cisco ISE 3.1\n  - RADIUS/TACACS+\n\n### print-server\n- **Name:** Print Server\n- **IP:** 10.10.0.150\n- **Role:** Print Services\n- **Shape:** server\n- **Tags:** print; windows; services\n- **Layer:** physical\n- **MAC:** 00:50:56:PR:01:01\n- **Rack Unit:** 18\n- **U Height:** 1\n- **Assigned Rack:** dc-rack-a1\n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 1897, 2845\n- **Size:** 50\n- **Notes:**\n  - Windows Print Server\n  - 50+ printers\n\n### file-server\n- **Name:** File Server\n- **IP:** 10.10.0.160\n- **Role:** File Services\n- **Shape:** database\n- **Tags:** file; smb; dfs\n- **Layer:** physical\n- **MAC:** 00:50:56:FS:01:01\n- **Rack Unit:** 16\n- **U Height:** 2\n- **Assigned Rack:** dc-rack-a1\n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 1861, 2845\n- **Size:** 50\n- **Notes:**\n  - Windows File Server\n  - DFS namespace\n\n### ca-server\n- **Name:** Certificate Authority\n- **IP:** 192.168.100.80\n- **Role:** PKI Infrastructure\n- **Shape:** server\n- **Tags:** pki; ca; security\n- **Layer:** logical\n- **MAC:** 00:50:56:CA:01:01\n- **Rack Unit:** 5\n- **U Height:** 1\n- **Assigned Rack:** mgmt-rack\n- **Rack Capacity:** 24\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 1889, 2845\n- **Size:** 50\n- **Notes:**\n  - Windows CA\n  - Enterprise Root CA\n\n### sccm-server\n- **Name:** SCCM Server\n- **IP:** 192.168.100.90\n- **Role:** Endpoint Management\n- **Shape:** server\n- **Tags:** sccm; patching; software\n- **Layer:** logical\n- **MAC:** 00:50:56:SC:01:01\n- **Rack Unit:** 3\n- **U Height:** 2\n- **Assigned Rack:** mgmt-rack\n- **Rack Capacity:** 24\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 1850, 2845\n- **Size:** 50\n- **Notes:**\n  - MECM Primary Site\n  - Software deployment\n\n### voip-cluster\n- **Name:** VoIP Cluster\n- **IP:** 10.50.0.0/24\n- **Role:** Voice Services\n- **Shape:** phone\n- **Tags:** voip; cisco; ucm\n- **Layer:** application\n- **MAC:** \n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 1777, 1617\n- **Size:** 50\n- **Notes:**\n  - Cisco UCM Cluster\n  - 3000 endpoints\n\n### video-conf\n- **Name:** Video Conference\n- **IP:** 10.51.0.0/24\n- **Role:** Video Services\n- **Shape:** laptop\n- **Tags:** video; webex; teams\n- **Layer:** application\n- **MAC:** \n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 1994, 2245\n- **Size:** 50\n- **Notes:**\n  - Webex/Teams integration\n  - Meeting rooms\n\n### security-cameras\n- **Name:** Security Cameras\n- **IP:** 10.60.0.0/24\n- **Role:** Physical Security\n- **Shape:** camera\n- **Tags:** cctv; surveillance; security\n- **Layer:** physical\n- **MAC:** \n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 1674, 2046\n- **Size:** 50\n- **Notes:**\n  - 150+ IP cameras\n  - 30-day retention\n\n### nvr-cluster\n- **Name:** NVR Cluster\n- **IP:** 10.60.0.10\n- **Role:** Video Recording\n- **Shape:** server\n- **Tags:** nvr; surveillance; storage\n- **Layer:** physical\n- **MAC:** 00:50:56:NV:01:01\n- **Rack Unit:** 15\n- **U Height:** 4\n- **Assigned Rack:** dc-rack-b2\n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 1829, 2845\n- **Size:** 50\n- **Notes:**\n  - Milestone XProtect\n  - 500TB storage\n\n### dev-server-1\n- **Name:** Dev Server 1\n- **IP:** 10.80.0.10\n- **Role:** Development\n- **Shape:** server\n- **Tags:** dev; gitlab; ci-cd; [object Object]\n- **Layer:** application\n- **MAC:** 00:50:56:DV:01:01\n- **Rack Unit:** \n- **U Height:** 2\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 2801, 1176\n- **Size:** 128\n- **Notes:**\n  - GitLab Server\n  - CI/CD pipelines\n- **Styles:** `{\"all\":{\"icon\":{\"library\":\"simple\",\"name\":\"amazonwebservices\"}}}`\n\n### dev-server-2\n- **Name:** Dev Server 2\n- **IP:** 10.80.0.11\n- **Role:** Development\n- **Shape:** server\n- **Tags:** dev; jenkins; ci-cd\n- **Layer:** application\n- **MAC:** 00:50:56:DV:01:02\n- **Rack Unit:** \n- **U Height:** 2\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 1945, 1165\n- **Size:** 50\n- **Notes:**\n  - Jenkins Server\n  - Build automation\n\n### test-environment\n- **Name:** Test Environment\n- **IP:** 10.81.0.0/24\n- **Role:** QA/Testing\n- **Shape:** hexagon\n- **Tags:** test; qa; staging\n- **Layer:** application\n- **MAC:** \n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 2567, 885\n- **Size:** 148\n- **Notes:**\n  - Staging environment\n  - Pre-prod validation\n- **Styles:** `{\"all\":{\"icon\":{\"library\":\"simple\",\"name\":\"apple\"}}}`\n\n### erp-system\n- **Name:** ERP System\n- **IP:** 10.90.0.10\n- **Role:** Business Application\n- **Shape:** database\n- **Tags:** erp; sap; business\n- **Layer:** application\n- **MAC:** 00:50:56:ER:01:01\n- **Rack Unit:** \n- **U Height:** 4\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 790, 474\n- **Size:** 50\n- **Notes:**\n  - SAP S/4HANA\n  - Financial/HR systems\n\n### crm-system\n- **Name:** CRM System\n- **IP:** 10.91.0.10\n- **Role:** Business Application\n- **Shape:** database\n- **Tags:** crm; salesforce; business\n- **Layer:** application\n- **MAC:** \n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 3515, 1138\n- **Size:** 50\n- **Notes:**\n  - Salesforce integration\n  - Sales/Marketing\n\n### endpoint-1000\n- **Name:** Corporate Endpoints\n- **IP:** 10.70.0.0/22\n- **Role:** User Workstations\n- **Shape:** laptop\n- **Tags:** endpoints; workstations; users\n- **Layer:** physical\n- **MAC:** \n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 992, 2284\n- **Size:** 50\n- **Notes:**\n  - ~1000 corporate laptops\n  - Windows 11\n\n### dist-switch-floor1\n- **Name:** Floor 1 Switch\n- **IP:** 10.1.1.1\n- **Role:** Distribution\n- **Shape:** switch\n- **Tags:** distribution; floor-1; access\n- **Layer:** physical\n- **MAC:** 00:1A:2B:FL:01:01\n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 654, 2020\n- **Size:** 50\n- **Notes:**\n  - Cisco C9300-48P\n  - PoE+ enabled\n\n### dist-switch-floor2\n- **Name:** Floor 2 Switch\n- **IP:** 10.1.2.1\n- **Role:** Distribution\n- **Shape:** switch\n- **Tags:** distribution; floor-2; access\n- **Layer:** physical\n- **MAC:** 00:1A:2B:FL:02:01\n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 854, 1843\n- **Size:** 50\n- **Notes:**\n  - Cisco C9300-48P\n  - PoE+ enabled\n\n### dist-switch-floor3\n- **Name:** Floor 3 Switch\n- **IP:** 10.1.3.1\n- **Role:** Distribution\n- **Shape:** switch\n- **Tags:** distribution; floor-3; access\n- **Layer:** physical\n- **MAC:** 00:1A:2B:FL:03:01\n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 1899, 1457\n- **Size:** 50\n- **Notes:**\n  - Cisco C9300-48P\n  - PoE+ enabled\n\n### dist-switch-floor4\n- **Name:** Floor 4 Switch\n- **IP:** 10.1.4.1\n- **Role:** Distribution\n- **Shape:** switch\n- **Tags:** distribution; floor-4; access\n- **Layer:** physical\n- **MAC:** 00:1A:2B:FL:04:01\n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 489, 181\n- **Size:** 50\n- **Notes:**\n  - Cisco C9300-48P\n  - PoE+ enabled\n\n### ap-floor1-zone1\n- **Name:** AP Floor 1 Zone 1\n- **IP:** 10.20.1.10\n- **Role:** Wireless Access\n- **Shape:** wifi\n- **Tags:** wifi; ap; floor-1\n- **Layer:** physical\n- **MAC:** 00:1A:2B:AP:01:01\n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 1140, 2070\n- **Size:** 50\n- **Notes:**\n  - Cisco 9120AX\n  - Wi-Fi 6\n\n### ap-floor2-zone1\n- **Name:** AP Floor 2 Zone 1\n- **IP:** 10.20.2.10\n- **Role:** Wireless Access\n- **Shape:** wifi\n- **Tags:** wifi; ap; floor-2\n- **Layer:** physical\n- **MAC:** 00:1A:2B:AP:02:01\n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 688, 2384\n- **Size:** 50\n- **Notes:**\n  - Cisco 9120AX\n  - Wi-Fi 6\n\n### ap-floor3-zone1\n- **Name:** AP Floor 3 Zone 1\n- **IP:** 10.20.3.10\n- **Role:** Wireless Access\n- **Shape:** wifi\n- **Tags:** wifi; ap; floor-3\n- **Layer:** physical\n- **MAC:** 00:1A:2B:AP:03:01\n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 2145, 1890\n- **Size:** 50\n- **Notes:**\n  - Cisco 9120AX\n  - Wi-Fi 6\n\n### ap-floor4-zone1\n- **Name:** AP Floor 4 Zone 1\n- **IP:** 10.20.4.10\n- **Role:** Wireless Access\n- **Shape:** wifi\n- **Tags:** wifi; ap; floor-4\n- **Layer:** physical\n- **MAC:** 00:1A:2B:AP:04:01\n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 518, 566\n- **Size:** 50\n- **Notes:**\n  - Cisco 9120AX\n  - Wi-Fi 6\n\n### ups-dc-1\n- **Name:** UPS DC-1\n- **IP:** 192.168.200.10\n- **Role:** Power Management\n- **Shape:** rectangle\n- **Tags:** power; ups; datacenter\n- **Layer:** physical\n- **MAC:** \n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 771, 296\n- **Size:** 50\n- **Notes:**\n  - APC Symmetra\n  - 80kVA\n  - 30 min runtime\n\n### ups-dc-2\n- **Name:** UPS DC-2\n- **IP:** 192.168.200.11\n- **Role:** Power Management\n- **Shape:** rectangle\n- **Tags:** power; ups; datacenter\n- **Layer:** physical\n- **MAC:** \n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 216, 330\n- **Size:** 50\n- **Notes:**\n  - APC Symmetra\n  - 80kVA\n  - Redundant\n\n### pdu-rack-a1\n- **Name:** PDU Rack A1\n- **IP:** 192.168.200.21\n- **Role:** Power Distribution\n- **Shape:** rectangle\n- **Tags:** power; pdu; rack-a1\n- **Layer:** physical\n- **MAC:** \n- **Rack Unit:** 1\n- **U Height:** 1\n- **Assigned Rack:** dc-rack-a1\n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 1805, 2845\n- **Size:** 50\n- **Notes:**\n  - APC Switched PDU\n  - Per-outlet metering\n\n### pdu-rack-a2\n- **Name:** PDU Rack A2\n- **IP:** 192.168.200.22\n- **Role:** Power Distribution\n- **Shape:** rectangle\n- **Tags:** power; pdu; rack-a2\n- **Layer:** physical\n- **MAC:** \n- **Rack Unit:** 1\n- **U Height:** 1\n- **Assigned Rack:** dc-rack-a2\n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 1742, 2845\n- **Size:** 50\n- **Notes:**\n  - APC Switched PDU\n  - Per-outlet metering\n\n### cooling-1\n- **Name:** CRAC Unit 1\n- **IP:** 192.168.200.30\n- **Role:** Cooling\n- **Shape:** rectangle\n- **Tags:** cooling; hvac; datacenter\n- **Layer:** physical\n- **MAC:** \n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 246, 626\n- **Size:** 50\n- **Notes:**\n  - Liebert CRV\n  - Row-based cooling\n\n### cooling-2\n- **Name:** CRAC Unit 2\n- **IP:** 192.168.200.31\n- **Role:** Cooling\n- **Shape:** rectangle\n- **Tags:** cooling; hvac; datacenter\n- **Layer:** physical\n- **MAC:** \n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 1603, 981\n- **Size:** 50\n- **Notes:**\n  - Liebert CRV\n  - N+1 redundancy\n\n### camera-a\n- **Name:** camera A\n- **IP:** 0.0.0.0\n- **Role:** \n- **Shape:** camera\n- **Tags:** _none_\n- **Layer:** physical\n- **MAC:** \n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** \n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 167, 145\n- **Size:** 45\n\n### camera-a-copy\n- **Name:** camera B\n- **IP:** 0.0.0.0\n- **Role:** \n- **Shape:** camera\n- **Tags:** _none_\n- **Layer:** physical\n- **MAC:** \n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** \n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 1041, 738\n- **Size:** 45\n\n## Connections\n\n- isp-primary (Gi0/0) --> core-router-1 (Gi1/0/1)\n  - **ID:** isp1-router1\n  - **Label:** \n  - **Color:** #10b981\n  - **Width:** 6\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n  - **Notes:**\n    - Primary WAN link\n\n- isp-secondary (Gi0/0) --> core-router-2 (Gi1/0/1)\n  - **ID:** isp2-router2\n  - **Label:** \n  - **Color:** #10b981\n  - **Width:** 6\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n  - **Notes:**\n    - Backup WAN link\n\n- core-router-1 (Gi1/0/24) --> core-router-2 (Gi1/0/24)\n  - **ID:** router1-router2\n  - **Label:** \n  - **Color:** #f59e0b\n  - **Width:** 4\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n  - **Notes:**\n    - HSRP Peering\n\n- core-router-1 --> fw-external-1\n  - **ID:** router1-fw1\n  - **Label:** \n  - **Color:** #ef4444\n  - **Width:** 4\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n\n- core-router-2 --> fw-external-2\n  - **ID:** router2-fw2\n  - **Label:** \n  - **Color:** #ef4444\n  - **Width:** 4\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n\n- fw-external-1 --> fw-external-2\n  - **ID:** fw1-fw2\n  - **Label:** \n  - **Color:** #f59e0b\n  - **Width:** 3\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** dashed\n  - **Group ID:** \n  - **Notes:**\n    - HA heartbeat\n\n- fw-external-1 --> core-switch-1\n  - **ID:** fw1-coresw1\n  - **Label:** \n  - **Color:** #475569\n  - **Width:** 4\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n\n- fw-external-2 --> core-switch-2\n  - **ID:** fw2-coresw2\n  - **Label:** \n  - **Color:** #475569\n  - **Width:** 4\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n\n- core-switch-1 --> core-switch-2\n  - **ID:** coresw1-coresw2\n  - **Label:** \n  - **Color:** #3b82f6\n  - **Width:** 5\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n  - **Notes:**\n    - VPC peer-link\n\n- core-switch-1 --> fw-internal\n  - **ID:** coresw1-fwint\n  - **Label:** \n  - **Color:** #ef4444\n  - **Width:** 3\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n\n- core-switch-2 --> fw-internal\n  - **ID:** coresw2-fwint\n  - **Label:** \n  - **Color:** #ef4444\n  - **Width:** 3\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n\n- core-switch-1 --> dc-rack-a1\n  - **ID:** coresw1-racka1\n  - **Label:** \n  - **Color:** #475569\n  - **Width:** 4\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n\n- core-switch-2 --> dc-rack-a1\n  - **ID:** coresw2-racka1\n  - **Label:** \n  - **Color:** #475569\n  - **Width:** 4\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n\n- core-switch-1 --> dc-rack-a2\n  - **ID:** coresw1-racka2\n  - **Label:** \n  - **Color:** #475569\n  - **Width:** 4\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n\n- core-switch-2 --> dc-rack-a2\n  - **ID:** coresw2-racka2\n  - **Label:** \n  - **Color:** #475569\n  - **Width:** 4\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n\n- core-switch-1 --> dc-rack-b1\n  - **ID:** coresw1-rackb1\n  - **Label:** \n  - **Color:** #475569\n  - **Width:** 4\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n\n- core-switch-2 --> dc-rack-b1\n  - **ID:** coresw2-rackb1\n  - **Label:** \n  - **Color:** #475569\n  - **Width:** 4\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n\n- core-switch-1 --> dc-rack-b2\n  - **ID:** coresw1-rackb2\n  - **Label:** \n  - **Color:** #475569\n  - **Width:** 4\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n\n- core-switch-2 --> dc-rack-b2\n  - **ID:** coresw2-rackb2\n  - **Label:** \n  - **Color:** #475569\n  - **Width:** 4\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n\n- fw-external-1 --> dmz-rack\n  - **ID:** fw1-dmz\n  - **Label:** \n  - **Color:** #f59e0b\n  - **Width:** 3\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n  - **Notes:**\n    - DMZ segment\n\n- fw-external-2 --> dmz-rack\n  - **ID:** fw2-dmz\n  - **Label:** \n  - **Color:** #f59e0b\n  - **Width:** 3\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n  - **Notes:**\n    - DMZ segment\n\n- core-switch-1 --> mgmt-rack\n  - **ID:** coresw1-mgmt\n  - **Label:** \n  - **Color:** #8b5cf6\n  - **Width:** 3\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n  - **Notes:**\n    - OOB management\n\n- core-switch-1 --> wlc-primary\n  - **ID:** coresw1-wlc1\n  - **Label:** \n  - **Color:** #06b6d4\n  - **Width:** 3\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n\n- core-switch-2 --> wlc-secondary\n  - **ID:** coresw2-wlc2\n  - **Label:** \n  - **Color:** #06b6d4\n  - **Width:** 3\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n\n- wlc-primary --> wlc-secondary\n  - **ID:** wlc1-wlc2\n  - **Label:** \n  - **Color:** #f59e0b\n  - **Width:** 2\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** dashed\n  - **Group ID:** \n  - **Notes:**\n    - HA pair\n\n- wlc-primary --> mobile-zone-hq\n  - **ID:** wlc1-mobile-hq\n  - **Label:** \n  - **Color:** #06b6d4\n  - **Width:** 2\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n\n- wlc-primary --> mobile-zone-guest\n  - **ID:** wlc1-mobile-guest\n  - **Label:** \n  - **Color:** #06b6d4\n  - **Width:** 2\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n\n- wlc-primary --> mobile-zone-iot\n  - **ID:** wlc1-mobile-iot\n  - **Label:** \n  - **Color:** #06b6d4\n  - **Width:** 2\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n\n- core-router-1 --> branch-router-ny\n  - **ID:** router1-branch-ny\n  - **Label:** \n  - **Color:** #a855f7\n  - **Width:** 3\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** dashed\n  - **Group ID:** \n  - **Notes:**\n    - SD-WAN tunnel\n\n- core-router-1 --> branch-router-la\n  - **ID:** router1-branch-la\n  - **Label:** \n  - **Color:** #a855f7\n  - **Width:** 3\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** dashed\n  - **Group ID:** \n  - **Notes:**\n    - SD-WAN tunnel\n\n- core-router-1 --> branch-router-chi\n  - **ID:** router1-branch-chi\n  - **Label:** \n  - **Color:** #a855f7\n  - **Width:** 3\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** dashed\n  - **Group ID:** \n  - **Notes:**\n    - SD-WAN tunnel\n\n- core-router-1 --> branch-router-lon\n  - **ID:** router1-branch-lon\n  - **Label:** \n  - **Color:** #a855f7\n  - **Width:** 3\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** dashed\n  - **Group ID:** \n  - **Notes:**\n    - SD-WAN tunnel\n\n- core-router-1 --> branch-router-tokyo\n  - **ID:** router1-branch-tokyo\n  - **Label:** \n  - **Color:** #a855f7\n  - **Width:** 3\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** dashed\n  - **Group ID:** \n  - **Notes:**\n    - SD-WAN tunnel\n\n- core-router-1 --> cloud-aws\n  - **ID:** router1-aws\n  - **Label:** \n  - **Color:** #f97316\n  - **Width:** 3\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** dashed\n  - **Group ID:** \n  - **Notes:**\n    - Direct Connect\n\n- core-router-2 --> cloud-azure\n  - **ID:** router2-azure\n  - **Label:** \n  - **Color:** #0ea5e9\n  - **Width:** 3\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** dashed\n  - **Group ID:** \n  - **Notes:**\n    - ExpressRoute\n\n- fw-internal --> cloud-gcp\n  - **ID:** fwint-gcp\n  - **Label:** \n  - **Color:** #22c55e\n  - **Width:** 2\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** dashed\n  - **Group ID:** \n  - **Notes:**\n    - VPN tunnel\n\n- core-switch-1 --> dist-switch-floor1\n  - **ID:** coresw1-floor1\n  - **Label:** \n  - **Color:** #475569\n  - **Width:** 3\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n\n- core-switch-1 --> dist-switch-floor2\n  - **ID:** coresw1-floor2\n  - **Label:** \n  - **Color:** #475569\n  - **Width:** 3\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n\n- core-switch-2 --> dist-switch-floor3\n  - **ID:** coresw2-floor3\n  - **Label:** \n  - **Color:** #475569\n  - **Width:** 3\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n\n- core-switch-2 --> dist-switch-floor4\n  - **ID:** coresw2-floor4\n  - **Label:** \n  - **Color:** #475569\n  - **Width:** 3\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n\n- dist-switch-floor1 --> endpoint-1000\n  - **ID:** floor1-endpoints\n  - **Label:** \n  - **Color:** #94a3b8\n  - **Width:** 2\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n\n- dist-switch-floor1 --> ap-floor1-zone1\n  - **ID:** floor1-ap1\n  - **Label:** \n  - **Color:** #06b6d4\n  - **Width:** 2\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n\n- dist-switch-floor2 --> ap-floor2-zone1\n  - **ID:** floor2-ap2\n  - **Label:** \n  - **Color:** #06b6d4\n  - **Width:** 2\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n\n- dist-switch-floor3 --> ap-floor3-zone1\n  - **ID:** floor3-ap3\n  - **Label:** \n  - **Color:** #06b6d4\n  - **Width:** 2\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n\n- dist-switch-floor4 --> ap-floor4-zone1\n  - **ID:** floor4-ap4\n  - **Label:** \n  - **Color:** #06b6d4\n  - **Width:** 2\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n\n- fw-internal --> proxy-server-1\n  - **ID:** fwint-proxy1\n  - **Label:** \n  - **Color:** #ef4444\n  - **Width:** 2\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n\n- fw-internal --> proxy-server-2\n  - **ID:** fwint-proxy2\n  - **Label:** \n  - **Color:** #ef4444\n  - **Width:** 2\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n\n- fw-external-1 --> vpn-concentrator\n  - **ID:** fwext1-vpn\n  - **Label:** \n  - **Color:** #8b5cf6\n  - **Width:** 3\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n\n- core-switch-1 --> nac-server\n  - **ID:** coresw1-nac\n  - **Label:** \n  - **Color:** #f59e0b\n  - **Width:** 2\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n\n- core-switch-1 --> voip-cluster\n  - **ID:** coresw1-voip\n  - **Label:** \n  - **Color:** #22c55e\n  - **Width:** 3\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n\n- core-switch-2 --> video-conf\n  - **ID:** coresw2-video\n  - **Label:** \n  - **Color:** #22c55e\n  - **Width:** 3\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n\n- core-switch-1 --> security-cameras\n  - **ID:** coresw1-cameras\n  - **Label:** \n  - **Color:** #94a3b8\n  - **Width:** 2\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n\n- fw-internal --> dev-server-1\n  - **ID:** fwint-dev1\n  - **Label:** \n  - **Color:** #a855f7\n  - **Width:** 2\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n\n- fw-internal --> dev-server-2\n  - **ID:** fwint-dev2\n  - **Label:** \n  - **Color:** #a855f7\n  - **Width:** 2\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n\n- fw-internal --> test-environment\n  - **ID:** fwint-test\n  - **Label:** \n  - **Color:** #a855f7\n  - **Width:** 2\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n\n- core-switch-1 --> erp-system\n  - **ID:** coresw1-erp\n  - **Label:** \n  - **Color:** #f59e0b\n  - **Width:** 3\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n\n- fw-external-1 --> crm-system\n  - **ID:** fwext1-crm\n  - **Label:** \n  - **Color:** #f59e0b\n  - **Width:** 2\n  - **Direction:** both\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** dashed\n  - **Group ID:** \n  - **Notes:**\n    - Salesforce cloud\n\n- ups-dc-1 --> dc-rack-a1\n  - **ID:** ups1-racka1\n  - **Label:** \n  - **Color:** #fbbf24\n  - **Width:** 2\n  - **Direction:** forward\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n  - **Notes:**\n    - Power feed A\n\n- ups-dc-2 --> dc-rack-a2\n  - **ID:** ups2-racka2\n  - **Label:** \n  - **Color:** #fbbf24\n  - **Width:** 2\n  - **Direction:** forward\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n  - **Notes:**\n    - Power feed B\n\n- ups-dc-1 --> dc-rack-b1\n  - **ID:** ups1-rackb1\n  - **Label:** \n  - **Color:** #fbbf24\n  - **Width:** 2\n  - **Direction:** forward\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n  - **Notes:**\n    - Power feed A\n\n- ups-dc-2 --> dc-rack-b2\n  - **ID:** ups2-rackb2\n  - **Label:** \n  - **Color:** #fbbf24\n  - **Width:** 2\n  - **Direction:** forward\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n  - **Notes:**\n    - Power feed B\n\n- cooling-1 --> dc-rack-a1\n  - **ID:** cooling1-racka1\n  - **Label:** \n  - **Color:** #38bdf8\n  - **Width:** 2\n  - **Direction:** forward\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** dotted\n  - **Group ID:** \n  - **Notes:**\n    - Cooling zone\n\n- cooling-2 --> dc-rack-b1\n  - **ID:** cooling2-rackb1\n  - **Label:** \n  - **Color:** #38bdf8\n  - **Width:** 2\n  - **Direction:** forward\n  - **Routing:** orthogonal\n  - **Type:** main\n  - **Line Style:** dotted\n  - **Group ID:** \n  - **Notes:**\n    - Cooling zone\n\n- undefined --> undefined\n  - **ID:** custom-1765237881452\n  - **Label:** \n  - **Color:** #c800ff\n  - **Width:** 4\n  - **Direction:** forward\n  - **Routing:** orthogonal\n  - **Type:** custom\n  - **Line Style:** solid\n  - **Group ID:** \n  - **Points:** 3492,1527 3501,1831 3304,1732\n\n- undefined --> undefined\n  - **ID:** custom-1765239355462\n  - **Label:** \n  - **Color:** #f97316\n  - **Width:** 4\n  - **Direction:** forward\n  - **Routing:** orthogonal\n  - **Type:** custom\n  - **Line Style:** solid\n  - **Group ID:** \n  - **Points:** 2467,156 2146,146 2306,244\n\n## Zones\n\n### rect-1765237540610\n- **Position:** 2879, 160\n- **Size:** 992 x 539\n- **Color:** #f97316\n- **Style:** filled\n- **Line Style:** solid\n- **Border Color:** \n- **Border Width:** 2\n\n### rect-1765237681216\n- **Position:** 448, 1672\n- **Size:** 916 x 924\n- **Color:** #c800ff\n- **Style:** outlined\n- **Line Style:** solid\n- **Border Color:** \n- **Border Width:** 2\n\n### rect-1766437913740\n- **Position:** 905, 115\n- **Size:** 111 x 920\n- **Color:** #5215f9\n- **Style:** filled\n- **Line Style:** wall\n- **Border Color:** \n- **Border Width:** 13\n\n### rect-1766437935414\n- **Position:** 131, 1072\n- **Size:** 873 x 99\n- **Color:** #5215f9\n- **Style:** filled\n- **Line Style:** wall\n- **Border Color:** \n- **Border Width:** 13\n\n## Text Labels\n\n### text-1765237828167\n- **Content:** Double click on desktop<br>or long press on mobile<br>to enter rack canvas view\n- **Position:** 3411, 1390\n- **Font Size:** 46\n- **Color:** #e2e8f0\n- **Font Weight:** bold\n- **Font Style:** italic\n- **Text Align:** middle\n- **Text Decoration:** none\n- **Background Color:** #000000\n- **Background Enabled:** false\n- **Opacity:** 1\n\n### text-1765239331126\n- **Content:** Google is live!\n- **Position:** 2455, 161\n- **Font Size:** 56\n- **Color:** #e2e8f0\n- **Font Weight:** bold\n- **Font Style:** normal\n- **Text Align:** start\n- **Text Decoration:** none\n- **Background Color:** #000000\n- **Background Enabled:** false\n- **Opacity:** 1\n\n### text-1766446595277\n- **Content:** SITE A\n- **Position:** 654, 1368\n- **Font Size:** 101\n- **Color:** #e2e8f0\n- **Font Weight:** normal\n- **Font Style:** normal\n- **Text Align:** start\n- **Text Decoration:** none\n- **Background Color:** #000000\n- **Background Enabled:** false\n- **Opacity:** 1\n\n### text-1766446610211\n- **Content:** SITE A\n- **Position:** 181, 1129\n- **Font Size:** 101\n- **Color:** #e2e8f0\n- **Font Weight:** normal\n- **Font Style:** normal\n- **Text Align:** start\n- **Text Decoration:** none\n- **Background Color:** #000000\n- **Background Enabled:** false\n- **Opacity:** 1\n\n### text-1766453024797\n- **Content:** SITE A\n- **Position:** 969, 1029\n- **Font Size:** 101\n- **Color:** #e2e8f0\n- **Font Weight:** normal\n- **Font Style:** normal\n- **Text Align:** start\n- **Text Decoration:** none\n- **Background Color:** #000000\n- **Background Enabled:** false\n- **Opacity:** 1\n\n### text-1766453070975\n- **Content:** SITE A\n- **Position:** 613, 1140\n- **Font Size:** 101\n- **Color:** #e2e8f0\n- **Font Weight:** normal\n- **Font Style:** normal\n- **Text Align:** start\n- **Text Decoration:** none\n- **Background Color:** #000000\n- **Background Enabled:** false\n- **Opacity:** 1\n\n### text-1766453072857\n- **Content:** SITE A\n- **Position:** 969, 474\n- **Font Size:** 101\n- **Color:** #e2e8f0\n- **Font Weight:** normal\n- **Font Style:** normal\n- **Text Align:** start\n- **Text Decoration:** none\n- **Background Color:** #000000\n- **Background Enabled:** false\n- **Opacity:** 1\n\n"
  },
  {
    "path": "demos/markdown-exports/the-one-file-networkening-homelab.md",
    "content": "<!--THEONEFILE_CONFIG\n{\n  \"nodeData\": {\n    \"internet\": {\n      \"shape\": \"square\",\n      \"name\": \"Internet\",\n      \"ip\": \"0.0.0.0\",\n      \"role\": \"\",\n      \"tags\": [],\n      \"notes\": [],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"internet-copy\": {\n      \"shape\": \"firewall\",\n      \"name\": \"OPNSENSE\",\n      \"ip\": \"0.0.0.0\",\n      \"role\": \"\",\n      \"tags\": [],\n      \"notes\": [],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"opnsense-copy\": {\n      \"shape\": \"firewall\",\n      \"name\": \"Docker\",\n      \"ip\": \"0.0.0.0\",\n      \"role\": \"\",\n      \"tags\": [],\n      \"notes\": [],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"docker-copy\": {\n      \"shape\": \"firewall\",\n      \"name\": \"Docker2\",\n      \"ip\": \"0.0.0.0\",\n      \"role\": \"\",\n      \"tags\": [],\n      \"notes\": [],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"docker-copy-1\": {\n      \"shape\": \"firewall\",\n      \"name\": \"Docker3\",\n      \"ip\": \"0.0.0.0\",\n      \"role\": \"\",\n      \"tags\": [],\n      \"notes\": [],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"docker-copy-2\": {\n      \"shape\": \"firewall\",\n      \"name\": \"Docker 4\",\n      \"ip\": \"0.0.0.0\",\n      \"role\": \"\",\n      \"tags\": [\n        {\n          \"type\": \"icon\",\n          \"library\": \"selfhst\",\n          \"name\": \"docker\"\n        },\n        {\n          \"type\": \"icon\",\n          \"library\": \"selfhst\",\n          \"name\": \"authentik\"\n        },\n        {\n          \"type\": \"icon\",\n          \"library\": \"selfhst\",\n          \"name\": \"immich\"\n        }\n      ],\n      \"notes\": [],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"opnsense-copy-1\": {\n      \"shape\": \"firewall\",\n      \"name\": \"OPNSENSE GUEST\",\n      \"ip\": \"0.0.0.0\",\n      \"role\": \"\",\n      \"tags\": [],\n      \"notes\": [],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"phone\": {\n      \"shape\": \"phone\",\n      \"name\": \"Phone\",\n      \"ip\": \"0.0.0.0\",\n      \"role\": \"\",\n      \"tags\": [],\n      \"notes\": [],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"desktop\": {\n      \"shape\": \"pc\",\n      \"name\": \"Desktop\",\n      \"ip\": \"0.0.0.0\",\n      \"role\": \"\",\n      \"tags\": [],\n      \"notes\": [],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"dns\": {\n      \"shape\": \"cloud\",\n      \"name\": \"DNS\",\n      \"ip\": \"0.0.0.0\",\n      \"role\": \"\",\n      \"tags\": [],\n      \"notes\": [],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": false,\n      \"locked\": false,\n      \"groupId\": null\n    },\n    \"racked\": {\n      \"shape\": \"server\",\n      \"name\": \"Racked\",\n      \"ip\": \"\",\n      \"role\": \"Rack\",\n      \"tags\": [],\n      \"notes\": [],\n      \"mac\": \"\",\n      \"rackUnit\": \"\",\n      \"uHeight\": \"1\",\n      \"layer\": \"physical\",\n      \"assignedRack\": \"\",\n      \"rackCapacity\": \"42\",\n      \"isRack\": true,\n      \"locked\": false,\n      \"groupId\": null\n    }\n  },\n  \"edgeData\": {\n    \"list\": [\n      {\n        \"id\": \"internet-internet-copy-1765238145151\",\n        \"from\": \"internet\",\n        \"to\": \"internet-copy\",\n        \"width\": 4,\n        \"color\": \"#55e208\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1\n      },\n      {\n        \"id\": \"internet-copy-opnsense-copy-1765238187451\",\n        \"from\": \"internet-copy\",\n        \"to\": \"opnsense-copy\",\n        \"width\": 4,\n        \"color\": \"#4c00ff\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1\n      },\n      {\n        \"id\": \"internet-copy-docker-copy-1765238242477\",\n        \"from\": \"internet-copy\",\n        \"to\": \"docker-copy\",\n        \"width\": 4,\n        \"color\": \"#4c00ff\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1\n      },\n      {\n        \"id\": \"internet-copy-docker-copy-1-1765238244637\",\n        \"from\": \"internet-copy\",\n        \"to\": \"docker-copy-1\",\n        \"width\": 4,\n        \"color\": \"#4c00ff\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1\n      },\n      {\n        \"id\": \"internet-copy-docker-copy-2-1765238246233\",\n        \"from\": \"internet-copy\",\n        \"to\": \"docker-copy-2\",\n        \"width\": 4,\n        \"color\": \"#4c00ff\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1\n      },\n      {\n        \"id\": \"internet-opnsense-copy-1-1765238266117\",\n        \"from\": \"internet\",\n        \"to\": \"opnsense-copy-1\",\n        \"width\": 4,\n        \"color\": \"#80ff00\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1\n      },\n      {\n        \"id\": \"opnsense-copy-1-dns-1765238347996\",\n        \"from\": \"opnsense-copy-1\",\n        \"to\": \"dns\",\n        \"width\": 4,\n        \"color\": \"#fb00ff\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1\n      },\n      {\n        \"id\": \"dns-desktop-1765238386101\",\n        \"from\": \"dns\",\n        \"to\": \"desktop\",\n        \"width\": 4,\n        \"color\": \"#ff00d0\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1\n      },\n      {\n        \"id\": \"phone-dns-1765238391156\",\n        \"from\": \"phone\",\n        \"to\": \"dns\",\n        \"width\": 4,\n        \"color\": \"#ff00d0\",\n        \"direction\": \"both\",\n        \"type\": \"main\",\n        \"notes\": [],\n        \"fromPort\": \"\",\n        \"toPort\": \"\",\n        \"lineStyle\": \"solid\",\n        \"_pairIndex\": 0,\n        \"_pairTotal\": 1\n      },\n      {\n        \"id\": \"custom-1765239449323\",\n        \"type\": \"custom\",\n        \"color\": \"#f97316\",\n        \"width\": 4,\n        \"lineStyle\": \"solid\",\n        \"direction\": \"forward\",\n        \"points\": [\n          {\n            \"x\": 2936.464111328125,\n            \"y\": 786.07958984375\n          },\n          {\n            \"x\": 3184.112060546875,\n            \"y\": 887.6153564453125\n          },\n          {\n            \"x\": 2763.110107421875,\n            \"y\": 981.7216796875\n          }\n        ],\n        \"notes\": []\n      }\n    ]\n  },\n  \"rectData\": {\n    \"list\": [\n      {\n        \"id\": \"rect-1765238219615\",\n        \"x\": 2680.053955078125,\n        \"y\": 251.44879150390625,\n        \"width\": 814.10400390625,\n        \"height\": 389.26678466796875,\n        \"color\": \"#ec0999\",\n        \"style\": \"filled\",\n        \"lineStyle\": \"solid\",\n        \"notes\": []\n      }\n    ]\n  },\n  \"textData\": {\n    \"list\": [\n      {\n        \"id\": \"text-1765238422602\",\n        \"x\": 2466.35986328125,\n        \"y\": 741.6801147460938,\n        \"content\": \"Double click on desktop\\nor long press on mobile\\nto enter rack canvas view\",\n        \"fontSize\": 40,\n        \"color\": \"#e2e8f0\",\n        \"fontWeight\": \"normal\",\n        \"fontStyle\": \"normal\",\n        \"textAlign\": \"start\",\n        \"textDecoration\": \"none\",\n        \"bgColor\": \"#000000\",\n        \"bgEnabled\": false,\n        \"opacity\": 1\n      }\n    ]\n  },\n  \"edgeLegend\": {\n    \"#475569\": \"you can edit me too\",\n    \"#65758b\": \"you can edit me too\",\n    \"#63748c\": \"you can edit me too\",\n    \"#5e6f87\": \"you can edit me too\",\n    \"#586a84\": \"you can edit me too\",\n    \"#4f627d\": \"you can edit me too\",\n    \"#455873\": \"you can edit me too\",\n    \"#3d506c\": \"you can edit me too\",\n    \"#354964\": \"you can edit me too\",\n    \"#2e415c\": \"you can edit me too\",\n    \"#293c56\": \"you can edit me too\",\n    \"#273a53\": \"you can edit me too\",\n    \"#253750\": \"you can edit me too\",\n    \"#23354d\": \"you can edit me too\",\n    \"#203046\": \"you can edit me too\",\n    \"#1e2d43\": \"you can edit me too\",\n    \"#1a283d\": \"you can edit me too\",\n    \"#172435\": \"you can edit me too\",\n    \"#141f2e\": \"you can edit me too\",\n    \"#111a27\": \"you can edit me too\",\n    \"#0f1824\": \"you can edit me too\",\n    \"#0d1521\": \"you can edit me too\",\n    \"#0c131d\": \"you can edit me too\",\n    \"#0c1d1c\": \"you can edit me too\",\n    \"#0c1c1d\": \"you can edit me too\",\n    \"#0c191d\": \"you can edit me too\",\n    \"#0c141d\": \"you can edit me too\",\n    \"#0c0d1d\": \"you can edit me too\",\n    \"#130c1d\": \"you can edit me too\",\n    \"#1b0c1d\": \"you can edit me too\",\n    \"#1d0c17\": \"you can edit me too\",\n    \"#1d0c10\": \"you can edit me too\",\n    \"#1d0c0c\": \"you can edit me too\",\n    \"#3b1b1b\": \"you can edit me too\",\n    \"#3c1a1a\": \"you can edit me too\",\n    \"#3f1c1c\": \"you can edit me too\",\n    \"#401c1c\": \"you can edit me too\",\n    \"#451c1c\": \"you can edit me too\",\n    \"#461b1b\": \"you can edit me too\",\n    \"#4c1a1a\": \"you can edit me too\",\n    \"#521919\": \"you can edit me too\",\n    \"#571919\": \"you can edit me too\",\n    \"#5d1818\": \"you can edit me too\",\n    \"#631717\": \"you can edit me too\",\n    \"#651515\": \"you can edit me too\",\n    \"#6a1616\": \"you can edit me too\",\n    \"#6f1515\": \"you can edit me too\",\n    \"#711414\": \"you can edit me too\",\n    \"#761414\": \"you can edit me too\",\n    \"#771313\": \"you can edit me too\",\n    \"#7c1313\": \"you can edit me too\",\n    \"#811313\": \"you can edit me too\",\n    \"#821212\": \"you can edit me too\",\n    \"#871212\": \"you can edit me too\",\n    \"#881111\": \"you can edit me too\",\n    \"#8d1111\": \"you can edit me too\",\n    \"#8e1010\": \"you can edit me too\",\n    \"#8f0f0f\": \"you can edit me too\",\n    \"#900e0e\": \"you can edit me too\",\n    \"#8e0b0b\": \"you can edit me too\",\n    \"#8c0d0d\": \"you can edit me too\",\n    \"#880c0c\": \"you can edit me too\",\n    \"#830c0c\": \"you can edit me too\",\n    \"#7e0c0c\": \"you can edit me too\",\n    \"#790c0c\": \"you can edit me too\",\n    \"#730c0c\": \"you can edit me too\",\n    \"#6f0b0b\": \"you can edit me too\",\n    \"#0b6f64\": \"you can edit me too\",\n    \"#0b6f5f\": \"you can edit me too\",\n    \"#0b6f56\": \"you can edit me too\",\n    \"#0b6f49\": \"you can edit me too\",\n    \"#0b6f31\": \"you can edit me too\",\n    \"#0b6f1f\": \"you can edit me too\",\n    \"#0b6f0d\": \"you can edit me too\",\n    \"#176f0b\": \"you can edit me too\",\n    \"#266f0b\": \"you can edit me too\",\n    \"#296f0b\": \"you can edit me too\",\n    \"#2e6f0b\": \"you can edit me too\",\n    \"#1a2d10\": \"you can edit me too\",\n    \"#1c3111\": \"you can edit me too\",\n    \"#213814\": \"you can edit me too\",\n    \"#233c15\": \"you can edit me too\",\n    \"#254017\": \"you can edit me too\",\n    \"#294918\": \"you can edit me too\",\n    \"#2b4d1a\": \"you can edit me too\",\n    \"#2d511a\": \"you can edit me too\",\n    \"#315a1b\": \"you can edit me too\",\n    \"#35631c\": \"you can edit me too\",\n    \"#37681d\": \"you can edit me too\",\n    \"#3b721d\": \"you can edit me too\",\n    \"#3f7b1e\": \"you can edit me too\",\n    \"#42851e\": \"you can edit me too\",\n    \"#46901d\": \"you can edit me too\",\n    \"#499a1d\": \"you can edit me too\",\n    \"#4b9f1d\": \"you can edit me too\",\n    \"#4ca61c\": \"you can edit me too\",\n    \"#50b01c\": \"you can edit me too\",\n    \"#51b71a\": \"you can edit me too\",\n    \"#50b918\": \"you can edit me too\",\n    \"#51c115\": \"you can edit me too\",\n    \"#53c615\": \"you can edit me too\",\n    \"#53c814\": \"you can edit me too\",\n    \"#52c913\": \"you can edit me too\",\n    \"#54d011\": \"you can edit me too\",\n    \"#53d110\": \"you can edit me too\",\n    \"#55d510\": \"you can edit me too\",\n    \"#55d70f\": \"you can edit me too\",\n    \"#54d80e\": \"you can edit me too\",\n    \"#54da0b\": \"you can edit me too\",\n    \"#56df0c\": \"you can edit me too\",\n    \"#53db0a\": \"you can edit me too\",\n    \"#55e00b\": \"you can edit me too\",\n    \"#55e109\": \"you can edit me too\",\n    \"#55e208\": \"ISP LINE\",\n    \"#4c00ff\": \"MY Guest NETWORK\",\n    \"#80ff00\": \"you can edit me too\",\n    \"#3b4234\": \"you can edit me too\",\n    \"#3a3442\": \"you can edit me too\",\n    \"#3b3442\": \"you can edit me too\",\n    \"#3c3442\": \"you can edit me too\",\n    \"#3d3442\": \"you can edit me too\",\n    \"#3e3442\": \"you can edit me too\",\n    \"#3f3442\": \"you can edit me too\",\n    \"#403442\": \"you can edit me too\",\n    \"#413442\": \"you can edit me too\",\n    \"#653d66\": \"you can edit me too\",\n    \"#683f69\": \"you can edit me too\",\n    \"#6c416c\": \"you can edit me too\",\n    \"#6f4370\": \"you can edit me too\",\n    \"#704270\": \"you can edit me too\",\n    \"#734474\": \"you can edit me too\",\n    \"#784479\": \"you can edit me too\",\n    \"#7d447e\": \"you can edit me too\",\n    \"#7e437f\": \"you can edit me too\",\n    \"#834384\": \"you can edit me too\",\n    \"#844285\": \"you can edit me too\",\n    \"#89418b\": \"you can edit me too\",\n    \"#8e428f\": \"you can edit me too\",\n    \"#904091\": \"you can edit me too\",\n    \"#923e93\": \"you can edit me too\",\n    \"#973e98\": \"you can edit me too\",\n    \"#943c96\": \"you can edit me too\",\n    \"#993c9a\": \"you can edit me too\",\n    \"#963a98\": \"you can edit me too\",\n    \"#973899\": \"you can edit me too\",\n    \"#99369b\": \"you can edit me too\",\n    \"#9a359c\": \"you can edit me too\",\n    \"#9b349d\": \"you can edit me too\",\n    \"#9d329f\": \"you can edit me too\",\n    \"#9e31a0\": \"you can edit me too\",\n    \"#a02fa2\": \"you can edit me too\",\n    \"#9d2d9f\": \"you can edit me too\",\n    \"#9f2ba1\": \"you can edit me too\",\n    \"#a129a3\": \"you can edit me too\",\n    \"#a327a5\": \"you can edit me too\",\n    \"#a525a7\": \"you can edit me too\",\n    \"#a723a9\": \"you can edit me too\",\n    \"#a921ab\": \"you can edit me too\",\n    \"#ab1fad\": \"you can edit me too\",\n    \"#ad1daf\": \"you can edit me too\",\n    \"#ae1cb0\": \"you can edit me too\",\n    \"#b019b3\": \"you can edit me too\",\n    \"#b118b4\": \"you can edit me too\",\n    \"#b316b6\": \"you can edit me too\",\n    \"#b816bb\": \"you can edit me too\",\n    \"#b514b8\": \"you can edit me too\",\n    \"#ba14bd\": \"you can edit me too\",\n    \"#b712ba\": \"you can edit me too\",\n    \"#bb13be\": \"you can edit me too\",\n    \"#b811bb\": \"you can edit me too\",\n    \"#be10c1\": \"you can edit me too\",\n    \"#bb0ebe\": \"you can edit me too\",\n    \"#bd0cc0\": \"you can edit me too\",\n    \"#be0bc1\": \"you can edit me too\",\n    \"#c108c4\": \"you can edit me too\",\n    \"#be06c1\": \"you can edit me too\",\n    \"#c103c4\": \"you can edit me too\",\n    \"#c301c6\": \"you can edit me too\",\n    \"#c400c7\": \"you can edit me too\",\n    \"#c900cc\": \"you can edit me too\",\n    \"#ce00d1\": \"you can edit me too\",\n    \"#d300d6\": \"you can edit me too\",\n    \"#d800db\": \"you can edit me too\",\n    \"#dd00e0\": \"you can edit me too\",\n    \"#e200e6\": \"you can edit me too\",\n    \"#ec00f0\": \"you can edit me too\",\n    \"#f100f5\": \"you can edit me too\",\n    \"#f600fa\": \"you can edit me too\",\n    \"#fb00ff\": \"you can edit me too\",\n    \"#ff00d0\": \"iPhone (always guest iPhone)\",\n    \"#f97316\": \"you can edit me too\"\n  },\n  \"nodePositions\": {\n    \"internet\": {\n      \"x\": 2103.968290880771,\n      \"y\": 268\n    },\n    \"internet-copy\": {\n      \"x\": 2066.9677515897347,\n      \"y\": 473.4119134177565\n    },\n    \"opnsense-copy\": {\n      \"x\": 1773.8400660428597,\n      \"y\": 666.5758233298659\n    },\n    \"docker-copy\": {\n      \"x\": 1931.1978950081452,\n      \"y\": 782.2775961320921\n    },\n    \"docker-copy-1\": {\n      \"x\": 2158.1262397347077,\n      \"y\": 767.7122274797483\n    },\n    \"docker-copy-2\": {\n      \"x\": 2342.2663764534577,\n      \"y\": 631.7681967180296\n    },\n    \"opnsense-copy-1\": {\n      \"x\": 2757.879480087803,\n      \"y\": 307.6117116091891\n    },\n    \"phone\": {\n      \"x\": 3312.857751572178,\n      \"y\": 502.58220111114224\n    },\n    \"desktop\": {\n      \"x\": 2971.700036728428,\n      \"y\": 480.7287465212985\n    },\n    \"dns\": {\n      \"x\": 3200.4643189549906,\n      \"y\": 320.469591247861\n    },\n    \"racked\": {\n      \"x\": 2645.5845448279656,\n      \"y\": 970.7820678889219\n    }\n  },\n  \"nodeSizes\": {\n    \"core-router-1\": 36,\n    \"internet\": 168,\n    \"phone\": 121,\n    \"desktop\": 147,\n    \"racked\": 137,\n    \"docker-copy-2\": 82\n  },\n  \"nodeStyles\": {\n    \"internet\": {\n      \"all\": {\n        \"icon\": {\n          \"library\": \"selfhst\",\n          \"name\": \"amazon-web-services\"\n        },\n        \"circleColor\": \"#ffffff\",\n        \"circleBorder\": \"#ffffff\"\n      }\n    },\n    \"opnsense-copy-1\": {\n      \"all\": {\n        \"icon\": {\n          \"library\": \"selfhst\",\n          \"name\": \"opnsense-v1\"\n        }\n      }\n    },\n    \"internet-copy\": {\n      \"all\": {\n        \"icon\": {\n          \"library\": \"selfhst\",\n          \"name\": \"opnsense\"\n        }\n      }\n    },\n    \"docker-copy-2\": {\n      \"all\": {\n        \"icon\": {\n          \"library\": \"selfhst\",\n          \"name\": \"docker\"\n        }\n      }\n    },\n    \"docker-copy-1\": {\n      \"all\": {\n        \"icon\": {\n          \"library\": \"selfhst\",\n          \"name\": \"authportal\"\n        }\n      }\n    },\n    \"docker-copy\": {\n      \"all\": {\n        \"icon\": {\n          \"library\": \"selfhst\",\n          \"name\": \"jotty\"\n        }\n      }\n    },\n    \"opnsense-copy\": {\n      \"all\": {\n        \"icon\": {\n          \"library\": \"selfhst\",\n          \"name\": \"portainer\"\n        }\n      }\n    },\n    \"racked\": {\n      \"all\": {\n        \"icon\": {\n          \"library\": \"mdi\",\n          \"name\": \"server-security\"\n        },\n        \"circleColor\": \"#010813\",\n        \"circleBorder\": \"#ffffff\"\n      }\n    }\n  },\n  \"iconCache\": {\n    \"selfhst-borg\": \"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" xml:space=\\\"preserve\\\" viewBox=\\\"0 0 512 512\\\"><path d=\\\"M331 102.2H204.5V0h197.9L505 102.2v104.2l-51.3 51.3L505 309v100.7L402.4 512H204.5V409.8H331V300.6H204.5v-89.1H331zM6.9 0h170.5v512H6.9z\\\" style=\\\"fill:#0d0\\\"/></svg>\",\n    \"selfhst-actual-budget\": \"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" xml:space=\\\"preserve\\\" viewBox=\\\"0 0 512 512\\\"><path d=\\\"M378.5 512h-245C59.8 512 0 452.2 0 378.5v-245C0 59.8 59.8 0 133.5 0h245C452.2 0 512 59.8 512 133.5v245c0 73.7-59.8 133.5-133.5 133.5\\\" style=\\\"fill:#805ad5\\\"/><path d=\\\"m407.7 384.1-33.2-68.2 26.9-10.4c1.7-.6 2.6-2.6 1.9-4.3l-7.8-20.2c-.6-1.7-2.6-2.6-4.3-1.9L362 290.3 268.8 99.2c-.6-1.2-1.7-1.9-3-1.9h-6.3c-1.3 0-2.4.7-3 1.9l-95 201.6-55.2 20.3c-1.8.6-2.6 2.6-2 4.3l7.4 20.3c.6 1.8 2.6 2.6 4.3 2l28.4-10.4-32.2 68.3c-.3.8-.4 1.7 0 2.5l1.6 4.3v.1c.6 1.7 2.6 2.6 4.3 1.9l229.8-88.3 34.3 70.4c.8 1.7 2.8 2.4 4.5 1.6l19.5-9.5c1.6-.8 2.3-2.8 1.5-4.5M263 151.8l46.1 94.9L199.4 287zM161.5 367.4l20.7-44 139.4-51.1 13.7 28.3z\\\" style=\\\"fill:#fff\\\"/></svg>\",\n    \"selfhst-anonaddy\": \"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" xml:space=\\\"preserve\\\" viewBox=\\\"0 0 512 512\\\"><linearGradient id=\\\"anonaddy_svg__a\\\" x1=\\\"44.378\\\" x2=\\\"447.022\\\" y1=\\\"386.378\\\" y2=\\\"789.022\\\" gradientTransform=\\\"translate(0 -278)\\\" gradientUnits=\\\"userSpaceOnUse\\\"><stop offset=\\\"0\\\" style=\\\"stop-color:#3ae7e1\\\"/><stop offset=\\\".948\\\" style=\\\"stop-color:#1993d2\\\"/></linearGradient><path d=\\\"M320 224H192c-17.7 0-32 14.3-32 32v128c0 17.7 14.3 32 32 32h128c17.7 0 32-14.3 32-32V256c0-17.7-14.3-32-32-32m-32 128h-64v-64h64z\\\" style=\\\"fill:url(#anonaddy_svg__a)\\\"/><linearGradient id=\\\"anonaddy_svg__b\\\" x1=\\\"44.337\\\" x2=\\\"446.981\\\" y1=\\\"386.418\\\" y2=\\\"789.062\\\" gradientTransform=\\\"translate(0 -278)\\\" gradientUnits=\\\"userSpaceOnUse\\\"><stop offset=\\\"0\\\" style=\\\"stop-color:#3ae7e1\\\"/><stop offset=\\\".948\\\" style=\\\"stop-color:#1993d2\\\"/></linearGradient><path d=\\\"M384 384c0 5.5-.8 10.9-2.1 16 39.4-21.8 66.1-63.8 66.1-112v-32c0-47.4-25.8-88.6-64-110.7-18.8-10.9-40.7-17.3-64-17.3H192c-23.3 0-45.2 6.4-64 17.3-38.2 22.1-64 63.3-64 110.7v128c0 70.7 57.3 128 128 128h128c0-35.3-28.7-64-64-64h-64c-35.3 0-64-28.7-64-64V256c0-35.3 28.7-64 64-64h128c35.3 0 64 28.7 64 64z\\\" style=\\\"fill:url(#anonaddy_svg__b)\\\"/><path d=\\\"M384 288c0 23.7-12.9 44.3-32 55.4V384c0 17.7-14.3 32-32 32 22.5 0 43.5-5.8 61.9-16 1.3-5.1 2.1-10.5 2.1-16zM128 145.3c18.8-10.9 40.7-17.3 64-17.3 0-11.7 3.2-22.6 8.7-32H192c-21.4 0-42.7 4.4-62.4 12.8-1 6.3-1.6 12.7-1.6 19.2zM311.3 96c5.5 9.4 8.7 20.3 8.7 32 23.3 0 45.2 6.4 64 17.3V128c0-6.5-.6-12.9-1.6-19.2-19.7-8.4-41-12.8-62.4-12.8z\\\" style=\\\"fill:#2d7aae\\\"/><linearGradient id=\\\"anonaddy_svg__c\\\" x1=\\\"158.782\\\" x2=\\\"561.426\\\" y1=\\\"271.974\\\" y2=\\\"674.618\\\" gradientTransform=\\\"translate(0 -278)\\\" gradientUnits=\\\"userSpaceOnUse\\\"><stop offset=\\\"0\\\" style=\\\"stop-color:#3ae7e1\\\"/><stop offset=\\\".948\\\" style=\\\"stop-color:#1993d2\\\"/></linearGradient><path d=\\\"M200.7 96c11.1-19.1 31.7-32 55.3-32s44.3 12.9 55.3 32h8.7c21.4 0 42.7 4.4 62.4 12.8C373.1 47.3 320 0 256 0S138.9 47.3 129.6 108.8c19.7-8.4 41-12.8 62.4-12.8z\\\" style=\\\"fill:url(#anonaddy_svg__c)\\\"/></svg>\",\n    \"selfhst-adguard-home\": \"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" xml:space=\\\"preserve\\\" viewBox=\\\"0 0 512 512\\\"><path d=\\\"M505.8 58.8C428.2 18.4 334.1 0 256 0h-.3v511.8c-21.5-12.7-41.1-26-59.1-39.6 18 13.7 37.8 27 59.4 39.8C506.8 363.4 505.8 146 505.8 58.8\\\" style=\\\"fill-rule:evenodd;clip-rule:evenodd;fill:#68bc71\\\"/><path d=\\\"M255.7 511.8C5.2 363.3 6.2 146 6.2 58.8 83.7 18.4 177.7 0 255.7 0z\\\" style=\\\"fill-rule:evenodd;clip-rule:evenodd;fill:#67b279\\\"/><path d=\\\"m246.9 341.6 151-203.6c-11.1-8.9-20.8-2.6-26.1 2.2h-.2l-125.9 131-47.4-57.1c-22.6-26.2-53.4-6.2-60.6-.9z\\\" style=\\\"fill-rule:evenodd;clip-rule:evenodd;fill:#fff\\\"/></svg>\",\n    \"selfhst-ansible\": \"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" xml:space=\\\"preserve\\\" viewBox=\\\"0 0 512 512\\\"><path d=\\\"M512 256c0 141.4-114.6 256-256 256S0 397.4 0 256 114.6 0 256 0s256 114.6 256 256\\\" style=\\\"fill:#c32214\\\"/><path d=\\\"m260.3 156.4 66.2 163.5-100.1-78.8zM378 357.6 276.1 112.3c-2.9-7.1-8.7-10.8-15.8-10.8s-13.3 3.7-16.2 10.8l-111.8 269h38.3l44.3-110.9L347 377.1c5.3 4.3 9.1 6.2 14.1 6.2 10 0 18.7-7.5 18.7-18.3-.1-1.7-.7-4.4-1.8-7.4\\\" style=\\\"fill:#fff\\\"/></svg>\",\n    \"selfhst-wikidocs\": \"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" xml:space=\\\"preserve\\\" viewBox=\\\"0 0 512 512\\\"><path d=\\\"M488.8 72.1h-90.7c-8.4 0-15.1 6.7-15.1 15.1v206.4c0 8.4-6.7 15.1-15.1 15.1h-45.3c-8.4 0-15.1-6.7-15.1-15.1V87.2c0-8.4-6.7-15.1-15.1-15.1h-72.5c-8.4 0-15.1 6.7-15.1 15.1v206.4c0 8.4-6.7 15.1-15.1 15.1h-45.3c-8.4 0-15.1-6.7-15.1-15.1V87.2c0-8.4-6.7-15.1-15.1-15.1H23.4c-8.4 0-15.1 6.7-15.1 15.1v257.2c0 50.1 38.6 91.2 87.5 95.3v.3h392.9c8.4 0 15.1-6.7 15.1-15.1V87.2c0-8.4-6.7-15.1-15-15.1\\\" style=\\\"fill:#4caf50\\\"/><path d=\\\"M488.6 448.2H95.7c-1.1 0-2.1-.2-3.1-.6-24.9-2.7-48-14.2-65.1-32.8C9.8 395.6 0 370.5 0 344.3V87.2c0-12.9 10.5-23.4 23.4-23.4h90.9c12.9 0 23.4 10.5 23.4 23.4v206.4c0 3.8 3 6.8 6.8 6.8h45.3c3.8 0 6.8-3 6.8-6.8V87.2c0-12.9 10.5-23.4 23.4-23.4h72.5c12.9 0 23.4 10.5 23.4 23.4v206.4c0 3.8 3 6.8 6.8 6.8H368c3.8 0 6.8-3 6.8-6.8V87.2c0-12.9 10.5-23.4 23.4-23.4h90.7c6.3 0 12.2 2.5 16.6 6.9s6.7 10.3 6.6 16.6v337.6c-.1 12.8-10.6 23.3-23.5 23.3M98.1 431.7h390.6c3.8 0 6.8-3 6.8-6.8V87.2c0-1.9-.6-3.6-1.9-4.8-1.3-1.3-3-2-4.8-2h-90.7c-3.8 0-6.8 3-6.8 6.8v206.4c0 12.9-10.5 23.4-23.4 23.4h-45.3c-12.9 0-23.4-10.5-23.4-23.4V87.2c0-3.8-3-6.8-6.8-6.8h-72.5c-3.8 0-6.8 3-6.8 6.8v206.4c0 12.9-10.5 23.4-23.4 23.4h-45.3c-12.9 0-23.4-10.5-23.4-23.4V87.2c0-3.8-3-6.8-6.8-6.8H23.4c-3.8 0-6.8 3-6.8 6.8v257.2c0 45.1 35.1 83.3 79.9 87.1.5-.1 1 0 1.6.2\\\" style=\\\"fill:#fff\\\"/></svg>\",\n    \"selfhst-alist\": \"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" xml:space=\\\"preserve\\\" viewBox=\\\"0 0 512 512\\\"><path d=\\\"M260.5 30.8c5.4-.6 11 .6 15.5 3.7 6.8 4.4 11.6 11.1 15.7 17.9 18.4 30.9 37 61.7 55.4 92.6 24.5 41.3 49.1 82.7 73.6 124q25.35 42.9 51 85.8c11.2 19.1 22.8 37.9 33.5 57.3 4.6 8.1 7.2 17.5 6.8 26.8-.3 7.2-1.6 14.7-5.4 20.9-5.3 8.8-14.3 15.1-23.9 18.3-5.2 1.9-10.6 3.1-16.1 3.1H286.3c-5.2-.1-10.4-.7-15.3-2.6-3.5-1.5-7-3.4-9.8-6.1-4.7-4.7-7.8-11.3-7-18.1.4-4.7 1.6-9.4 3.5-13.7 1.9-4.4 4.5-8.5 6.8-12.7 10.1-17.9 20.6-35.6 31.5-53 3.5-5.5 6.6-11.4 11.4-15.8 5.2-4.7 11.6-8.4 18.7-8.9 5.7-.2 11.3 1.7 16.1 4.8 5.4 3.1 9.6 8.4 11 14.5 2 7.7-.2 15.8-3.4 22.9-3.7 8-8.9 15.3-11.7 23.7-.5 1.9-1.2 4.3.2 6 1.4 1.4 3.5 1.9 5.3 2.4 5.5 1.2 11.1.9 16.6.9h61.3c4-.1 8.1.1 12.1-.1 2.9-.3 6.1-.8 8.4-2.9 1.5-1.3 1.7-3.6 1.1-5.4-1.2-4-3.4-7.7-5.5-11.3C383.2 312.9 329 219.7 274 126.9c-1.8-3-3.3-6.2-5.9-8.6-1.3-1.2-3.2-1.9-4.9-1.4-1.5.5-2.5 1.8-3.5 3-2.4 3.1-4.6 6.4-6.6 9.8-9.5 15.4-18.6 31.1-27.9 46.6-53.6 90.1-107.1 180.3-160.7 270.4-3.3 6.1-6.9 12.1-10.7 17.9-3.4 5-7.5 9.6-12.8 12.6-6.2 3.6-13.9 5-20.9 3-5.1-1.6-10-4.1-13.7-8.1-4.6-4.6-6.7-11.3-6.4-17.7.2-6.7 2.5-13.3 5.9-19.1C38.4 380.1 71.1 325 104 269.9c16.9-28.5 33.7-57.1 50.7-85.6 17.2-28.8 34.4-57.7 51.5-86.5 9.5-16.3 19.2-32.5 29.2-48.5 3.1-5.1 7.1-9.6 11.9-13.1 4-2.7 8.4-5 13.2-5.4\\\" style=\\\"fill:#70c6be\\\"/><path d=\\\"M257.8 244.3c8.1-1.3 16.7.6 23.4 5.4 5.1 3.9 8.6 9.8 9.7 16.2.7 4.9.2 9.9-1.6 14.5-1.4 4-3.3 7.7-5.5 11.3-31.2 52.5-62.5 105-93.7 157.5-4 6.6-7.6 13.4-12.2 19.6-3.3 4.1-7.5 7.7-12.4 9.9-10.2 5.4-23.5 2.1-31.3-6.1-3.9-3.9-6.2-9.4-6.5-14.9-.4-5.7.8-11.5 3.4-16.7 2.3-5 5-9.7 7.8-14.4 13.7-23.1 27.3-46.3 41-69.4 18.7-31.8 37.8-63.4 56.6-95.1 2-3.6 4.5-6.9 7.3-9.9 3.8-4 8.6-7.1 14-7.9\\\" style=\\\"fill:#1ba0d8\\\"/></svg>\",\n    \"simple-amazonwebservices\": \"<svg role=\\\"img\\\" viewBox=\\\"0 0 24 24\\\" xmlns=\\\"http://www.w3.org/2000/svg\\\"><title>Amazon Web Services</title><path d=\\\"M6.763 10.036c0 .296.032.535.088.71.064.176.144.368.256.576.04.063.056.127.056.183 0 .08-.048.16-.152.24l-.503.335a.383.383 0 0 1-.208.072c-.08 0-.16-.04-.239-.112a2.47 2.47 0 0 1-.287-.375 6.18 6.18 0 0 1-.248-.471c-.622.734-1.405 1.101-2.347 1.101-.67 0-1.205-.191-1.596-.574-.391-.384-.59-.894-.59-1.533 0-.678.239-1.23.726-1.644.487-.415 1.133-.623 1.955-.623.272 0 .551.024.846.064.296.04.6.104.918.176v-.583c0-.607-.127-1.03-.375-1.277-.255-.248-.686-.367-1.3-.367-.28 0-.568.031-.863.103-.295.072-.583.16-.862.272a2.287 2.287 0 0 1-.28.104.488.488 0 0 1-.127.023c-.112 0-.168-.08-.168-.247v-.391c0-.128.016-.224.056-.28a.597.597 0 0 1 .224-.167c.279-.144.614-.264 1.005-.36a4.84 4.84 0 0 1 1.246-.151c.95 0 1.644.216 2.091.647.439.43.662 1.085.662 1.963v2.586zm-3.24 1.214c.263 0 .534-.048.822-.144.287-.096.543-.271.758-.51.128-.152.224-.32.272-.512.047-.191.08-.423.08-.694v-.335a6.66 6.66 0 0 0-.735-.136 6.02 6.02 0 0 0-.75-.048c-.535 0-.926.104-1.19.32-.263.215-.39.518-.39.917 0 .375.095.655.295.846.191.2.47.296.838.296zm6.41.862c-.144 0-.24-.024-.304-.08-.064-.048-.12-.16-.168-.311L7.586 5.55a1.398 1.398 0 0 1-.072-.32c0-.128.064-.2.191-.2h.783c.151 0 .255.025.31.08.065.048.113.16.16.312l1.342 5.284 1.245-5.284c.04-.16.088-.264.151-.312a.549.549 0 0 1 .32-.08h.638c.152 0 .256.025.32.08.063.048.12.16.151.312l1.261 5.348 1.381-5.348c.048-.16.104-.264.16-.312a.52.52 0 0 1 .311-.08h.743c.127 0 .2.065.2.2 0 .04-.009.08-.017.128a1.137 1.137 0 0 1-.056.2l-1.923 6.17c-.048.16-.104.263-.168.311a.51.51 0 0 1-.303.08h-.687c-.151 0-.255-.024-.32-.08-.063-.056-.119-.16-.15-.32l-1.238-5.148-1.23 5.14c-.04.16-.087.264-.15.32-.065.056-.177.08-.32.08zm10.256.215c-.415 0-.83-.048-1.229-.143-.399-.096-.71-.2-.918-.32-.128-.071-.215-.151-.247-.223a.563.563 0 0 1-.048-.224v-.407c0-.167.064-.247.183-.247.048 0 .096.008.144.024.048.016.12.048.2.08.271.12.566.215.878.279.319.064.63.096.95.096.502 0 .894-.088 1.165-.264a.86.86 0 0 0 .415-.758.777.777 0 0 0-.215-.559c-.144-.151-.416-.287-.807-.415l-1.157-.36c-.583-.183-1.014-.454-1.277-.813a1.902 1.902 0 0 1-.4-1.158c0-.335.073-.63.216-.886.144-.255.335-.479.575-.654.24-.184.51-.32.83-.415.32-.096.655-.136 1.006-.136.175 0 .359.008.535.032.183.024.35.056.518.088.16.04.312.08.455.127.144.048.256.096.336.144a.69.69 0 0 1 .24.2.43.43 0 0 1 .071.263v.375c0 .168-.064.256-.184.256a.83.83 0 0 1-.303-.096 3.652 3.652 0 0 0-1.532-.311c-.455 0-.815.071-1.062.223-.248.152-.375.383-.375.71 0 .224.08.416.24.567.159.152.454.304.877.44l1.134.358c.574.184.99.44 1.237.767.247.327.367.702.367 1.117 0 .343-.072.655-.207.926-.144.272-.336.511-.583.703-.248.2-.543.343-.886.447-.36.111-.734.167-1.142.167zM21.698 16.207c-2.626 1.94-6.442 2.969-9.722 2.969-4.598 0-8.74-1.7-11.87-4.526-.247-.223-.024-.527.272-.351 3.384 1.963 7.559 3.153 11.877 3.153 2.914 0 6.114-.607 9.06-1.852.439-.2.814.287.383.607zM22.792 14.961c-.336-.43-2.22-.207-3.074-.103-.255.032-.295-.192-.063-.36 1.5-1.053 3.967-.75 4.254-.399.287.36-.08 2.826-1.485 4.007-.215.184-.423.088-.327-.151.32-.79 1.03-2.57.695-2.994z\\\"/></svg>\",\n    \"simple-apple\": \"<svg role=\\\"img\\\" viewBox=\\\"0 0 24 24\\\" xmlns=\\\"http://www.w3.org/2000/svg\\\"><title>Apple</title><path d=\\\"M12.152 6.896c-.948 0-2.415-1.078-3.96-1.04-2.04.027-3.91 1.183-4.961 3.014-2.117 3.675-.546 9.103 1.519 12.09 1.013 1.454 2.208 3.09 3.792 3.039 1.52-.065 2.09-.987 3.935-.987 1.831 0 2.35.987 3.96.948 1.637-.026 2.676-1.48 3.676-2.948 1.156-1.688 1.636-3.325 1.662-3.415-.039-.013-3.182-1.221-3.22-4.857-.026-3.04 2.48-4.494 2.597-4.559-1.429-2.09-3.623-2.324-4.39-2.376-2-.156-3.675 1.09-4.61 1.09zM15.53 3.83c.843-1.012 1.4-2.427 1.245-3.83-1.207.052-2.662.805-3.532 1.818-.78.896-1.454 2.338-1.273 3.714 1.338.104 2.715-.688 3.559-1.701\\\"/></svg>\",\n    \"selfhst-amazon-web-services\": \"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" xml:space=\\\"preserve\\\" viewBox=\\\"0 0 512 512\\\"><path d=\\\"M144.3 214.1c0 6.3.7 11.4 1.9 15.2 1.4 3.7 3.1 7.8 5.4 12.3.9 1.4 1.2 2.7 1.2 3.9 0 1.7-1 3.4-3.2 5.1l-10.7 7.2c-1.5 1-3.1 1.5-4.4 1.5-1.7 0-3.4-.9-5.1-2.4-2.4-2.6-4.4-5.3-6.1-8-1.7-2.9-3.4-6.1-5.3-10-13.3 15.7-30 23.5-50.1 23.5-14.3 0-25.7-4.1-34.1-12.3-8.3-8.2-12.6-19.1-12.6-32.7 0-14.5 5.1-26.2 15.5-35.1S60.8 169 78.4 169c5.8 0 11.7.5 18.1 1.4s12.8 2.2 19.6 3.7v-12.4c0-12.9-2.7-22-8-27.2-5.4-5.3-14.6-7.8-27.8-7.8-6 0-12.1.7-18.4 2.2s-12.4 3.4-18.4 5.8c-2.7 1.2-4.8 1.9-6 2.2s-2 .5-2.7.5c-2.4 0-3.6-1.7-3.6-5.3v-8.3c0-2.7.3-4.8 1.2-6s2.4-2.4 4.8-3.6c6-3.1 13.1-5.6 21.5-7.7 8.3-2.2 17.2-3.2 26.6-3.2 20.3 0 35.1 4.6 44.6 13.8 9.4 9.2 14.1 23.2 14.1 41.9v55.2zM75.2 240c5.6 0 11.4-1 17.5-3.1 6.1-2 11.6-5.8 16.2-10.9 2.7-3.2 4.8-6.8 5.8-10.9s1.7-9 1.7-14.8v-7.2c-4.9-1.2-10.2-2.2-15.7-2.9-5.4-.7-10.7-1-16-1-11.4 0-19.8 2.2-25.4 6.8S51 207.1 51 215.6c0 8 2 14 6.3 18.1 4.1 4.2 10 6.3 17.9 6.3m136.7 18.4c-3.1 0-5.1-.5-6.5-1.7-1.4-1-2.6-3.4-3.6-6.6l-40-131.6c-1-3.4-1.5-5.6-1.5-6.8 0-2.7 1.4-4.3 4.1-4.3h16.7c3.2 0 5.4.5 6.6 1.7 1.4 1 2.4 3.4 3.4 6.6l28.6 112.7 26.6-112.7c.9-3.4 1.9-5.6 3.2-6.6 1.4-1 3.7-1.7 6.8-1.7H270c3.2 0 5.4.5 6.8 1.7 1.4 1 2.6 3.4 3.2 6.6l26.9 114.1 29.5-114.1c1-3.4 2.2-5.6 3.4-6.6 1.4-1 3.6-1.7 6.6-1.7h15.8c2.7 0 4.3 1.4 4.3 4.3 0 .9-.2 1.7-.3 2.7-.2 1-.5 2.4-1.2 4.3l-41 131.6q-1.5 5.1-3.6 6.6c-1.4 1-3.6 1.7-6.5 1.7h-14.6c-3.2 0-5.4-.5-6.8-1.7s-2.6-3.4-3.2-6.8l-26.4-109.8L236.7 250c-.9 3.4-1.9 5.6-3.2 6.8-1.4 1.2-3.7 1.7-6.8 1.7zm218.8 4.6c-8.9 0-17.7-1-26.2-3.1-8.5-2-15.2-4.3-19.6-6.8-2.7-1.5-4.6-3.2-5.3-4.8s-1-3.2-1-4.8v-8.7c0-3.6 1.4-5.3 3.9-5.3 1 0 2 .2 3.1.5 1 .3 2.6 1 4.3 1.7 5.8 2.6 12.1 4.6 18.7 6 6.8 1.4 13.5 2 20.3 2 10.7 0 19.1-1.9 24.9-5.6s8.9-9.2 8.9-16.2c0-4.8-1.5-8.7-4.6-11.9s-8.9-6.1-17.2-8.9l-24.7-7.7c-12.4-3.9-21.6-9.7-27.2-17.4-5.6-7.5-8.5-15.8-8.5-24.7 0-7.2 1.5-13.5 4.6-18.9s7.2-10.2 12.3-14c5.1-3.9 10.9-6.8 17.7-8.9 6.8-2 14-2.9 21.5-2.9 3.7 0 7.7.2 11.4.7 3.9.5 7.5 1.2 11.1 1.9 3.4.9 6.6 1.7 9.7 2.7s5.4 2 7.2 3.1c2.4 1.4 4.1 2.7 5.1 4.3 1 1.4 1.5 3.2 1.5 5.6v8c0 3.6-1.4 5.4-3.9 5.4-1.4 0-3.6-.7-6.5-2q-14.55-6.6-32.7-6.6c-9.7 0-17.4 1.5-22.6 4.8s-8 8.2-8 15.2c0 4.8 1.7 8.9 5.1 12.1s9.7 6.5 18.7 9.4l24.2 7.7c12.3 3.9 21.1 9.4 26.4 16.3s7.8 15 7.8 23.8c0 7.3-1.5 14-4.4 19.8-3.1 5.8-7.2 10.9-12.4 15-5.3 4.3-11.6 7.3-18.9 9.5-8 2.5-16 3.7-24.7 3.7\\\"/><path d=\\\"M462.9 345.7c-56 41.4-137.4 63.3-207.4 63.3-98.1 0-186.5-36.3-253.2-96.6-5.3-4.8-.5-11.2 5.8-7.5 72.2 41.9 161.3 67.3 253.4 67.3 62.2 0 130.4-12.9 193.3-39.5 9.3-4.2 17.3 6.2 8.1 13m23.3-26.5c-7.2-9.2-47.3-4.4-65.6-2.2-5.4.7-6.3-4.1-1.4-7.7 32-22.5 84.6-16 90.8-8.5 6.1 7.7-1.7 60.3-31.7 85.5-4.6 3.9-9 1.9-7-3.2 6.9-16.9 22.1-54.9 14.9-63.9\\\" style=\\\"fill-rule:evenodd;clip-rule:evenodd;fill:#f90\\\"/></svg>\",\n    \"selfhst-opnsense\": \"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" xml:space=\\\"preserve\\\" viewBox=\\\"0 0 512 512\\\"><path d=\\\"M512 160.2V400H400V112H112V0h240.4zM112 112H0v240.4L160.2 512H400V400H112z\\\" style=\\\"fill:#de3c07\\\"/></svg>\",\n    \"selfhst-portainer\": \"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" xml:space=\\\"preserve\\\" viewBox=\\\"0 0 512 512\\\"><path d=\\\"M462.2 512H49.8C22.3 512 0 489.7 0 462.2V49.8C0 22.3 22.3 0 49.8 0h412.4C489.7 0 512 22.3 512 49.8v412.4c0 27.5-22.3 49.8-49.8 49.8\\\" style=\\\"fill:#2e2f33\\\"/><path d=\\\"M108.2 63.9h140.9c97.2 0 154.6 30.4 154.6 129.5v3.4c0 99.4-57.2 129.5-154.5 129.5h-30.7V447H108.2zm134.5 177.8c30.1 0 46-11.2 46-44.6v-3.9c0-33.2-15.8-44.6-46-44.6h-24.2v93.1zM313 358h90.3v90.1H313z\\\" style=\\\"fill:#f7f6f3\\\"/></svg>\",\n    \"selfhst-jotty\": \"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" xml:space=\\\"preserve\\\" viewBox=\\\"0 0 512 512\\\"><path d=\\\"M438.6 0H73.4C33.1 0 .3 32.8.3 73.1v365.7c0 40.3 32.8 73.1 73.1 73.1h365.3c40.3 0 73.1-32.8 73.1-73.1V73.1C511.7 32.8 478.9 0 438.6 0\\\" style=\\\"fill-rule:evenodd;clip-rule:evenodd;fill:#8b3bd0\\\"/><path d=\\\"M356.8 53.6v261.3c0 39.3-13.9 72.8-41.8 100.7s-61.4 41.8-100.7 41.8l-23.8-95h23.8c13 0 24.1-4.6 33.2-13.8 9.5-9.5 14.3-20.7 14.3-33.7V148.6H157.3v-95z\\\" style=\\\"fill:#fff\\\"/></svg>\",\n    \"selfhst-authportal\": \"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" xml:space=\\\"preserve\\\" viewBox=\\\"0 0 512 512\\\"><path d=\\\"M0 0h512v512H0z\\\" style=\\\"fill:#111827\\\"/><path d=\\\"M170.7 85.3 369.8 256 170.7 426.7z\\\" style=\\\"fill:#f59e0b\\\"/></svg>\",\n    \"selfhst-docker\": \"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" xml:space=\\\"preserve\\\" viewBox=\\\"0 0 512 512\\\"><path d=\\\"M501.4 212.3c-11.5-8-38-11-58.6-7-2.4-20-13.5-37.5-32.7-53l-11-8-7.7 11.5c-9.6 15-14.4 36-13 56 .5 7 2.9 19.5 10.1 30.5-6.7 4-20.7 9-38.9 9H2.3l-1 4c-3.4 20-3.4 82.5 36 130.5 29.8 36.5 74 55 132.1 55 125.9 0 219.1-60.5 262.8-170 17.3.5 54.3 0 73-37.5.5-1 1.4-3 4.8-10.5l1.9-4zM280 71.3h-52.8v50H280zm0 60h-52.8v50H280zm-62.5 0h-52.8v50h52.8zm-62.4 0h-52.8v50h52.8zm-62.5 60H39.8v50h52.8zm62.5 0h-52.8v50h52.8zm62.4 0h-52.8v50h52.8zm62.5 0h-52.8v50H280zm62.4 0h-52.8v50h52.8z\\\" style=\\\"fill:#2396ed\\\"/></svg>\",\n    \"selfhst-opnsense-v1\": \"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" xml:space=\\\"preserve\\\" viewBox=\\\"0 0 512 512\\\"><path d=\\\"M503.1 465.4v2.1c-6 25.5-28.4 44.5-55 44.5H7.3v-46.6h38.3v7.3h403c1.1 0 2 0 2.9-.1 1.8-.1 3.6-.6 5.2-1.3 2.9-1.3 5.3-3.3 7-5.9zM63.9 0c-26.5 0-49 19-55 44.5V47H48c2.7-4.3 7.3-7.2 12.6-7.6 1-.1 2-.1 3.2-.1h402.6V47h38.3V0z\\\" style=\\\"fill:#898b8d\\\"/><path d=\\\"M466.1 157.7V197H319.5v-39.3zM45.9 315.4v39.3h146.6v-39.3zm0-157.7V197h146.6v-39.3zm273.6 157.7v39.3h146.6v-39.3z\\\" style=\\\"fill:#58595b\\\"/><path d=\\\"M83.8 78.6H428v39.3H83.8zm0 315.9H428v39.3H83.8z\\\" style=\\\"fill:#403f41\\\"/><linearGradient id=\\\"opnsense-v1_svg__a\\\" x1=\\\"-1460.617\\\" x2=\\\"-1375.11\\\" y1=\\\"1248.095\\\" y2=\\\"1248.095\\\" gradientTransform=\\\"matrix(.1853 .356 .6722 -.353 -90.66 993.802)\\\" gradientUnits=\\\"userSpaceOnUse\\\"><stop offset=\\\"0\\\" style=\\\"stop-color:#737373\\\"/><stop offset=\\\"1\\\" style=\\\"stop-color:#333\\\"/></linearGradient><path d=\\\"m466.5 78.6 38.2-20.5.1-39.5-38.3 20.7\\\" style=\\\"fill:url(#opnsense-v1_svg__a)\\\"/><linearGradient id=\\\"opnsense-v1_svg__b\\\" x1=\\\"4.634\\\" x2=\\\"50.301\\\" y1=\\\"462.6\\\" y2=\\\"462.6\\\" gradientTransform=\\\"matrix(1 0 0 -1 0 514)\\\" gradientUnits=\\\"userSpaceOnUse\\\"><stop offset=\\\"0\\\" style=\\\"stop-color:#737373\\\"/><stop offset=\\\"1\\\" style=\\\"stop-color:#333\\\"/></linearGradient><path d=\\\"M45.6 64.5v-8.9c0-5.3 2.6-10 6.5-13l-34-18.5c-6.7 9.5-10.8 21.3-10.9 34l38.3 20.6v-14c.1-.1.1-.2.1-.2\\\" style=\\\"fill:url(#opnsense-v1_svg__b)\\\"/><linearGradient id=\\\"opnsense-v1_svg__c\\\" x1=\\\"-1587.887\\\" x2=\\\"-1498.137\\\" y1=\\\"-2208.165\\\" y2=\\\"-2208.165\\\" gradientTransform=\\\"matrix(-.1853 -.356 -.6722 .353 -1743.836 694.098)\\\" gradientUnits=\\\"userSpaceOnUse\\\"><stop offset=\\\"0\\\" style=\\\"stop-color:#737373\\\"/><stop offset=\\\"1\\\" style=\\\"stop-color:#333\\\"/></linearGradient><path d=\\\"M45.6 472.7v-38.9L7.3 454.5v39.7l39.9-21.5z\\\" style=\\\"fill:url(#opnsense-v1_svg__c)\\\"/><linearGradient id=\\\"opnsense-v1_svg__d\\\" x1=\\\"460.439\\\" x2=\\\"504.436\\\" y1=\\\"53\\\" y2=\\\"53\\\" gradientTransform=\\\"matrix(1 0 0 -1 0 514)\\\" gradientUnits=\\\"userSpaceOnUse\\\"><stop offset=\\\"0\\\" style=\\\"stop-color:#737373\\\"/><stop offset=\\\"1\\\" style=\\\"stop-color:#333\\\"/></linearGradient><path d=\\\"M466.5 456.4c0 5.5-2.7 10.3-6.9 13.2l34.2 18.4c6.8-9.5 10.8-21.2 10.9-33.8L466.5 434v12.8\\\" style=\\\"fill:url(#opnsense-v1_svg__d)\\\"/><linearGradient id=\\\"opnsense-v1_svg__e\\\" x1=\\\"2521.902\\\" x2=\\\"2684.464\\\" y1=\\\"-2476.233\\\" y2=\\\"-2476.233\\\" gradientTransform=\\\"matrix(-1.508 -1.0166 -3.1265 -1.519 -3697.438 -937.952)\\\" gradientUnits=\\\"userSpaceOnUse\\\"><stop offset=\\\".107\\\" style=\\\"stop-color:#58595b\\\"/><stop offset=\\\"1\\\" style=\\\"stop-color:#000\\\"/></linearGradient><path d=\\\"M192.5 197v-.4l-72.4-38.9H45.9v.2l72.5 39.1z\\\" style=\\\"fill:url(#opnsense-v1_svg__e)\\\"/><linearGradient id=\\\"opnsense-v1_svg__f\\\" x1=\\\"-1594.953\\\" x2=\\\"-1432.391\\\" y1=\\\"278.458\\\" y2=\\\"278.458\\\" gradientTransform=\\\"matrix(1.508 -1.0166 3.1265 -1.519 1804.196 -937.952)\\\" gradientUnits=\\\"userSpaceOnUse\\\"><stop offset=\\\".107\\\" style=\\\"stop-color:#58595b\\\"/><stop offset=\\\"1\\\" style=\\\"stop-color:#000\\\"/></linearGradient><path d=\\\"M466.1 157.7h-74l-72.6 39.1v.2h74.2l72.4-38.9z\\\" style=\\\"fill:url(#opnsense-v1_svg__f)\\\"/><linearGradient id=\\\"opnsense-v1_svg__g\\\" x1=\\\"-4327.142\\\" x2=\\\"-4164.581\\\" y1=\\\"1572.387\\\" y2=\\\"1572.387\\\" gradientTransform=\\\"matrix(1.508 1.0166 3.1265 1.519 1863.938 2252.352)\\\" gradientUnits=\\\"userSpaceOnUse\\\"><stop offset=\\\".107\\\" style=\\\"stop-color:#58595b\\\"/><stop offset=\\\"1\\\" style=\\\"stop-color:#000\\\"/></linearGradient><path d=\\\"M466.1 354.7v-.4l-72.4-38.9h-74.2v.2l72.6 39.1z\\\" style=\\\"fill:url(#opnsense-v1_svg__g)\\\"/><linearGradient id=\\\"opnsense-v1_svg__h\\\" x1=\\\"-413.668\\\" x2=\\\"-246.99\\\" y1=\\\"-1046.618\\\" y2=\\\"-1046.618\\\" gradientTransform=\\\"matrix(-1.508 1.0166 -3.1265 1.519 -3638.692 2252.352)\\\" gradientUnits=\\\"userSpaceOnUse\\\"><stop offset=\\\".107\\\" style=\\\"stop-color:#58595b\\\"/><stop offset=\\\"1\\\" style=\\\"stop-color:#000\\\"/></linearGradient><path d=\\\"M192.5 315.4h-74.1l-72.5 39.1v.2h74.2l72.4-38.9z\\\" style=\\\"fill:url(#opnsense-v1_svg__h)\\\"/><linearGradient id=\\\"opnsense-v1_svg__i\\\" x1=\\\"74.725\\\" x2=\\\"261.062\\\" y1=\\\"132.65\\\" y2=\\\"132.65\\\" gradientTransform=\\\"matrix(.9914 0 0 -1 -66.782 260.2)\\\" gradientUnits=\\\"userSpaceOnUse\\\"><stop offset=\\\".237\\\" style=\\\"stop-color:#a6a8ab\\\"/><stop offset=\\\"1\\\" style=\\\"stop-color:#404040\\\"/></linearGradient><path d=\\\"M192.8 157.7V197L7.3 97.4V58.1z\\\" style=\\\"fill:url(#opnsense-v1_svg__i)\\\"/><linearGradient id=\\\"opnsense-v1_svg__j\\\" x1=\\\"-2461.056\\\" x2=\\\"-2274.718\\\" y1=\\\"132.65\\\" y2=\\\"132.65\\\" gradientTransform=\\\"matrix(-.9914 0 0 -1 -1935.19 260.2)\\\" gradientUnits=\\\"userSpaceOnUse\\\"><stop offset=\\\".237\\\" style=\\\"stop-color:#a6a8ab\\\"/><stop offset=\\\"1\\\" style=\\\"stop-color:#404040\\\"/></linearGradient><path d=\\\"M319.2 157.7V197l185.5-99.6V58.1z\\\" style=\\\"fill:url(#opnsense-v1_svg__j)\\\"/><linearGradient id=\\\"opnsense-v1_svg__k\\\" x1=\\\"-2291.845\\\" x2=\\\"-2105.508\\\" y1=\\\"-739.55\\\" y2=\\\"-739.55\\\" gradientTransform=\\\"matrix(-.9914 0 0 1 -1767.435 1124.6)\\\" gradientUnits=\\\"userSpaceOnUse\\\"><stop offset=\\\".237\\\" style=\\\"stop-color:#a6a8ab\\\"/><stop offset=\\\"1\\\" style=\\\"stop-color:#404040\\\"/></linearGradient><path d=\\\"M319.2 355.2v-39.8L504.7 415v39.7z\\\" style=\\\"fill:url(#opnsense-v1_svg__k)\\\"/><linearGradient id=\\\"opnsense-v1_svg__l\\\" x1=\\\"-94.103\\\" x2=\\\"91.934\\\" y1=\\\"-739.55\\\" y2=\\\"-739.55\\\" gradientTransform=\\\"matrix(.9914 0 0 1 100.993 1124.6)\\\" gradientUnits=\\\"userSpaceOnUse\\\"><stop offset=\\\".237\\\" style=\\\"stop-color:#a6a8ab\\\"/><stop offset=\\\"1\\\" style=\\\"stop-color:#404040\\\"/></linearGradient><path d=\\\"M192.8 355.2v-39.8L7.3 415v39.7z\\\" style=\\\"fill:url(#opnsense-v1_svg__l)\\\"/><path d=\\\"m319.2 276.1 108.9 58.5v-39.7l-35.3-18.8h111.9v-39.8H392.8l35.3-18.8v-39.3l-108.9 58.1zM83.8 334.6l109-58.5v-39.8l-109-58.1v39.3l35.7 18.8H7.3v39.8h111.9l-35.3 18.8v39.7z\\\" style=\\\"fill:#e24525\\\"/></svg>\",\n    \"mdi-server-security\": \"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" id=\\\"mdi-server-security\\\" viewBox=\\\"0 0 24 24\\\"><path d=\\\"M3,1H19A1,1 0 0,1 20,2V6A1,1 0 0,1 19,7H3A1,1 0 0,1 2,6V2A1,1 0 0,1 3,1M3,9H19A1,1 0 0,1 20,10V10.67L17.5,9.56L11,12.44V15H3A1,1 0 0,1 2,14V10A1,1 0 0,1 3,9M3,17H11C11.06,19.25 12,21.4 13.46,23H3A1,1 0 0,1 2,22V18A1,1 0 0,1 3,17M8,5H9V3H8V5M8,13H9V11H8V13M8,21H9V19H8V21M4,3V5H6V3H4M4,11V13H6V11H4M4,19V21H6V19H4M17.5,12L22,14V17C22,19.78 20.08,22.37 17.5,23C14.92,22.37 13,19.78 13,17V14L17.5,12M17.5,13.94L15,15.06V17.72C15,19.26 16.07,20.7 17.5,21.06V13.94Z\\\" /></svg>\"\n  },\n  \"page\": {\n    \"title\": \"The One File\",\n    \"background\": \"\",\n    \"topbarBg\": \"rgba(9, 12, 20, 0.9)\",\n    \"topbarBorder\": \"#1f2533\",\n    \"panel\": \"#2f0e0e\",\n    \"panelAlt\": \"#10141b\",\n    \"accent\": \"#a75252\",\n    \"sidebarBg\": \"#10141b\",\n    \"btnBg\": \"#0b0e13\",\n    \"btnText\": \"#e2e8f0\",\n    \"tagFill\": \"#1e293b\",\n    \"tagText\": \"#e2e8f0\",\n    \"tagBorder\": \"#475569\",\n    \"inputBg\": \"#0b0e13\",\n    \"inputText\": \"#e2e8f0\",\n    \"inputBorder\": \"#1f2937\",\n    \"inputFont\": \"Inter, system-ui, sans-serif\",\n    \"inputFontSize\": 14,\n    \"toolbarBg\": \"#441215\",\n    \"toolbarBorder\": \"#1f2937\",\n    \"toolbarText\": \"#94a3b8\",\n    \"toolbarBtnBg\": \"#0b0e13\",\n    \"toolbarBtnText\": \"#e2e8f0\",\n    \"minimapDots\": \"#94a3b8\",\n    \"canvasHintEnabled\": true,\n    \"canvasHintText\": \"\",\n    \"canvasHintBg\": \"#0f172a\",\n    \"canvasHintColor\": \"#94a3b8\",\n    \"danger\": \"#f56565\",\n    \"textMain\": \"#e2e8f0\",\n    \"textSoft\": \"#94a3b8\",\n    \"topbarHeight\": 112,\n    \"sidebarWidth\": 350,\n    \"mobileFooterHeight\": 40,\n    \"sidebarCollapsed\": false,\n    \"nodeFill\": \"#1e293b\",\n    \"nodeStroke\": \"#475569\",\n    \"nodeTitle\": \"#e2e8f0\",\n    \"nodeSub\": \"#94a3b8\",\n    \"nodeTitleSize\": 18,\n    \"nodeSubSize\": 13,\n    \"nodeFont\": \"Inter, system-ui, sans-serif\",\n    \"defaultEdge\": \"#475569\",\n    \"selectionHandle\": \"#f59e0b\",\n    \"selectionHandleSize\": 8,\n    \"groupIndicator\": \"#4fd1c5\",\n    \"canvasGradientTop\": \"#1e2532\",\n    \"canvasGradientBottom\": \"#050608\",\n    \"canvasBorder\": \"#475569\",\n    \"canvasGrid\": \"#475569\",\n    \"canvasGridSize\": 50,\n    \"canvasGridEnabled\": true,\n    \"rackFrameFill\": \"#0f172a\",\n    \"rackFrameStroke\": \"#4fd1c5\",\n    \"rackLineColor\": \"#475569\",\n    \"rackTextColor\": \"#4fd1c5\",\n    \"rackGridEnabled\": true,\n    \"viewOnly\": false,\n    \"defaultEdgeRouting\": \"curved\",\n    \"animateConnections\": false,\n    \"animationStyle\": \"arrows\",\n    \"animationDirection\": \"all\",\n    \"animationSpeed\": 1.5\n  },\n  \"autoPingEnabled\": false,\n  \"autoPingInterval\": 30,\n  \"canvas\": {\n    \"zoom\": 0.9921985961590549,\n    \"panX\": -5.584863670202822,\n    \"panY\": -99.90831573327841\n  },\n  \"savedTopologyView\": {\n    \"zoom\": 0.9325110211947125,\n    \"panX\": -563.7108933103631,\n    \"panY\": -561.6887674556383\n  },\n  \"documentTabs\": [\n    {\n      \"id\": \"main\",\n      \"name\": \"Corporate Site B\",\n      \"nodes\": {\n        \"core-router-1\": {\n          \"shape\": \"router\",\n          \"name\": \"Core Router 1\",\n          \"ip\": \"10.0.0.1\",\n          \"role\": \"Core Routing\",\n          \"tags\": [\n            \"core\",\n            \"tier-1\",\n            \"redundant\"\n          ],\n          \"notes\": [\n            \"Primary core router\",\n            \"BGP peering enabled\"\n          ],\n          \"mac\": \"00:1A:2B:3C:4D:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"core-router-2\": {\n          \"shape\": \"router\",\n          \"name\": \"Core Router 2\",\n          \"ip\": \"10.0.0.2\",\n          \"role\": \"Core Routing\",\n          \"tags\": [\n            \"core\",\n            \"tier-1\",\n            \"redundant\"\n          ],\n          \"notes\": [\n            \"Secondary core router\",\n            \"HSRP standby\"\n          ],\n          \"mac\": \"00:1A:2B:3C:4D:02\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null,\n          \"ping\": {\n            \"enabled\": true,\n            \"protocol\": \"custom\",\n            \"customUrl\": \"https://google.com\",\n            \"timeout\": 3000,\n            \"status\": \"online\",\n            \"lastCheck\": \"2025-12-09T00:15:04.343Z\"\n          }\n        },\n        \"fw-external-1\": {\n          \"shape\": \"firewall\",\n          \"name\": \"External FW 1\",\n          \"ip\": \"10.0.1.1\",\n          \"role\": \"Perimeter Security\",\n          \"tags\": [\n            \"security\",\n            \"perimeter\",\n            \"ha-pair\"\n          ],\n          \"notes\": [\n            \"Palo Alto PA-5250\",\n            \"Active node\"\n          ],\n          \"mac\": \"00:1A:2B:3C:4D:10\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"fw-external-2\": {\n          \"shape\": \"firewall\",\n          \"name\": \"External FW 2\",\n          \"ip\": \"10.0.1.2\",\n          \"role\": \"Perimeter Security\",\n          \"tags\": [\n            \"security\",\n            \"perimeter\",\n            \"ha-pair\"\n          ],\n          \"notes\": [\n            \"Palo Alto PA-5250\",\n            \"Passive node\"\n          ],\n          \"mac\": \"00:1A:2B:3C:4D:11\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"fw-internal\": {\n          \"shape\": \"firewall\",\n          \"name\": \"Internal FW\",\n          \"ip\": \"10.0.2.1\",\n          \"role\": \"Internal Segmentation\",\n          \"tags\": [\n            \"security\",\n            \"internal\"\n          ],\n          \"notes\": [\n            \"East-West traffic inspection\"\n          ],\n          \"mac\": \"00:1A:2B:3C:4D:12\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"core-switch-1\": {\n          \"shape\": \"switch\",\n          \"name\": \"Core Switch 1\",\n          \"ip\": \"10.0.10.1\",\n          \"role\": \"Core Switching\",\n          \"tags\": [\n            \"core\",\n            \"layer3\",\n            \"redundant\"\n          ],\n          \"notes\": [\n            \"Cisco Nexus 9000\",\n            \"VPC Domain 1\"\n          ],\n          \"mac\": \"00:1A:2B:3C:4D:20\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"core-switch-2\": {\n          \"shape\": \"switch\",\n          \"name\": \"Core Switch 2\",\n          \"ip\": \"10.0.10.2\",\n          \"role\": \"Core Switching\",\n          \"tags\": [\n            \"core\",\n            \"layer3\",\n            \"redundant\"\n          ],\n          \"notes\": [\n            \"Cisco Nexus 9000\",\n            \"VPC Domain 1\"\n          ],\n          \"mac\": \"00:1A:2B:3C:4D:21\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dc-rack-a1\": {\n          \"shape\": \"server\",\n          \"name\": \"DC Rack A1\",\n          \"ip\": \"10.10.0.0/24\",\n          \"role\": \"Data Center Rack\",\n          \"tags\": [\n            \"datacenter\",\n            \"row-a\",\n            \"production\"\n          ],\n          \"notes\": [\n            \"Row A, Position 1\",\n            \"Primary compute\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": true,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dc-rack-a2\": {\n          \"shape\": \"server\",\n          \"name\": \"DC Rack A2\",\n          \"ip\": \"10.10.1.0/24\",\n          \"role\": \"Data Center Rack\",\n          \"tags\": [\n            \"datacenter\",\n            \"row-a\",\n            \"production\"\n          ],\n          \"notes\": [\n            \"Row A, Position 2\",\n            \"Primary compute\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": true,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dc-rack-b1\": {\n          \"shape\": \"server\",\n          \"name\": \"DC Rack B1\",\n          \"ip\": \"10.10.2.0/24\",\n          \"role\": \"Data Center Rack\",\n          \"tags\": [\n            \"datacenter\",\n            \"row-b\",\n            \"storage\"\n          ],\n          \"notes\": [\n            \"Row B, Position 1\",\n            \"Storage systems\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": true,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dc-rack-b2\": {\n          \"shape\": \"server\",\n          \"name\": \"DC Rack B2\",\n          \"ip\": \"10.10.3.0/24\",\n          \"role\": \"Data Center Rack\",\n          \"tags\": [\n            \"datacenter\",\n            \"row-b\",\n            \"storage\"\n          ],\n          \"notes\": [\n            \"Row B, Position 2\",\n            \"Storage systems\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": true,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dmz-rack\": {\n          \"shape\": \"server\",\n          \"name\": \"DMZ Rack\",\n          \"ip\": \"172.16.0.0/24\",\n          \"role\": \"DMZ Infrastructure\",\n          \"tags\": [\n            \"dmz\",\n            \"security\",\n            \"public-facing\",\n            {\n              \"type\": \"icon\",\n              \"library\": \"selfhst\",\n              \"name\": \"booklogr\"\n            },\n            {\n              \"type\": \"icon\",\n              \"library\": \"simple\",\n              \"name\": \"gmail\"\n            }\n          ],\n          \"notes\": [\n            \"Isolated DMZ zone\",\n            \"Public-facing services\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": true,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"mgmt-rack\": {\n          \"shape\": \"server\",\n          \"name\": \"Management Rack\",\n          \"ip\": \"192.168.100.0/24\",\n          \"role\": \"Management Infrastructure\",\n          \"tags\": [\n            \"management\",\n            \"oob\",\n            \"noc\"\n          ],\n          \"notes\": [\n            \"Out-of-band management\",\n            \"NOC equipment\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"logical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": true,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"esxi-host-01\": {\n          \"shape\": \"server\",\n          \"name\": \"ESXi Host 01\",\n          \"ip\": \"10.10.0.11\",\n          \"role\": \"Hypervisor\",\n          \"tags\": [\n            \"vmware\",\n            \"compute\",\n            \"cluster-a\"\n          ],\n          \"notes\": [\n            \"Dell PowerEdge R750\",\n            \"512GB RAM\",\n            \"vSphere 8.0\"\n          ],\n          \"mac\": \"00:50:56:AA:01:01\",\n          \"rackUnit\": 38,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"esxi-host-02\": {\n          \"shape\": \"server\",\n          \"name\": \"ESXi Host 02\",\n          \"ip\": \"10.10.0.12\",\n          \"role\": \"Hypervisor\",\n          \"tags\": [\n            \"vmware\",\n            \"compute\",\n            \"cluster-a\"\n          ],\n          \"notes\": [\n            \"Dell PowerEdge R750\",\n            \"512GB RAM\",\n            \"vSphere 8.0\"\n          ],\n          \"mac\": \"00:50:56:AA:01:02\",\n          \"rackUnit\": 35,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"esxi-host-03\": {\n          \"shape\": \"server\",\n          \"name\": \"ESXi Host 03\",\n          \"ip\": \"10.10.0.13\",\n          \"role\": \"Hypervisor\",\n          \"tags\": [\n            \"vmware\",\n            \"compute\",\n            \"cluster-a\"\n          ],\n          \"notes\": [\n            \"Dell PowerEdge R750\",\n            \"512GB RAM\",\n            \"vSphere 8.0\"\n          ],\n          \"mac\": \"00:50:56:AA:01:03\",\n          \"rackUnit\": 32,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"esxi-host-04\": {\n          \"shape\": \"server\",\n          \"name\": \"ESXi Host 04\",\n          \"ip\": \"10.10.0.14\",\n          \"role\": \"Hypervisor\",\n          \"tags\": [\n            \"vmware\",\n            \"compute\",\n            \"cluster-a\"\n          ],\n          \"notes\": [\n            \"Dell PowerEdge R750\",\n            \"512GB RAM\",\n            \"vSphere 8.0\"\n          ],\n          \"mac\": \"00:50:56:AA:01:04\",\n          \"rackUnit\": 29,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"tor-switch-a1\": {\n          \"shape\": \"switch\",\n          \"name\": \"ToR Switch A1\",\n          \"ip\": \"10.10.0.1\",\n          \"role\": \"Top of Rack\",\n          \"tags\": [\n            \"tor\",\n            \"access\",\n            \"rack-a1\"\n          ],\n          \"notes\": [\n            \"Cisco Nexus 93180YC-FX\",\n            \"48x25G ports\"\n          ],\n          \"mac\": \"00:1A:2B:3C:5D:01\",\n          \"rackUnit\": 42,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"esxi-host-05\": {\n          \"shape\": \"server\",\n          \"name\": \"ESXi Host 05\",\n          \"ip\": \"10.10.1.11\",\n          \"role\": \"Hypervisor\",\n          \"tags\": [\n            \"vmware\",\n            \"compute\",\n            \"cluster-b\"\n          ],\n          \"notes\": [\n            \"Dell PowerEdge R750\",\n            \"768GB RAM\",\n            \"vSphere 8.0\"\n          ],\n          \"mac\": \"00:50:56:AA:02:01\",\n          \"rackUnit\": 38,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"esxi-host-06\": {\n          \"shape\": \"server\",\n          \"name\": \"ESXi Host 06\",\n          \"ip\": \"10.10.1.12\",\n          \"role\": \"Hypervisor\",\n          \"tags\": [\n            \"vmware\",\n            \"compute\",\n            \"cluster-b\"\n          ],\n          \"notes\": [\n            \"Dell PowerEdge R750\",\n            \"768GB RAM\",\n            \"vSphere 8.0\"\n          ],\n          \"mac\": \"00:50:56:AA:02:02\",\n          \"rackUnit\": 35,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"esxi-host-07\": {\n          \"shape\": \"server\",\n          \"name\": \"ESXi Host 07\",\n          \"ip\": \"10.10.1.13\",\n          \"role\": \"Hypervisor\",\n          \"tags\": [\n            \"vmware\",\n            \"compute\",\n            \"cluster-b\"\n          ],\n          \"notes\": [\n            \"Dell PowerEdge R750\",\n            \"768GB RAM\",\n            \"vSphere 8.0\"\n          ],\n          \"mac\": \"00:50:56:AA:02:03\",\n          \"rackUnit\": 32,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"esxi-host-08\": {\n          \"shape\": \"server\",\n          \"name\": \"ESXi Host 08\",\n          \"ip\": \"10.10.1.14\",\n          \"role\": \"Hypervisor\",\n          \"tags\": [\n            \"vmware\",\n            \"compute\",\n            \"cluster-b\"\n          ],\n          \"notes\": [\n            \"Dell PowerEdge R750\",\n            \"768GB RAM\",\n            \"vSphere 8.0\"\n          ],\n          \"mac\": \"00:50:56:AA:02:04\",\n          \"rackUnit\": 29,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"tor-switch-a2\": {\n          \"shape\": \"switch\",\n          \"name\": \"ToR Switch A2\",\n          \"ip\": \"10.10.1.1\",\n          \"role\": \"Top of Rack\",\n          \"tags\": [\n            \"tor\",\n            \"access\",\n            \"rack-a2\"\n          ],\n          \"notes\": [\n            \"Cisco Nexus 93180YC-FX\",\n            \"48x25G ports\"\n          ],\n          \"mac\": \"00:1A:2B:3C:5D:02\",\n          \"rackUnit\": 42,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"san-primary\": {\n          \"shape\": \"database\",\n          \"name\": \"SAN Primary\",\n          \"ip\": \"10.10.2.10\",\n          \"role\": \"Primary Storage\",\n          \"tags\": [\n            \"storage\",\n            \"san\",\n            \"netapp\"\n          ],\n          \"notes\": [\n            \"NetApp AFF A400\",\n            \"500TB Raw\",\n            \"FC 32Gb\"\n          ],\n          \"mac\": \"00:A0:98:AA:01:01\",\n          \"rackUnit\": 36,\n          \"uHeight\": \"6\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-b1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"san-secondary\": {\n          \"shape\": \"database\",\n          \"name\": \"SAN Secondary\",\n          \"ip\": \"10.10.2.11\",\n          \"role\": \"Secondary Storage\",\n          \"tags\": [\n            \"storage\",\n            \"san\",\n            \"netapp\"\n          ],\n          \"notes\": [\n            \"NetApp AFF A400\",\n            \"500TB Raw\",\n            \"FC 32Gb\"\n          ],\n          \"mac\": \"00:A0:98:AA:01:02\",\n          \"rackUnit\": 28,\n          \"uHeight\": \"6\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-b1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"fc-switch-1\": {\n          \"shape\": \"switch\",\n          \"name\": \"FC Switch 1\",\n          \"ip\": \"10.10.2.1\",\n          \"role\": \"Fibre Channel\",\n          \"tags\": [\n            \"storage\",\n            \"fc\",\n            \"fabric-a\"\n          ],\n          \"notes\": [\n            \"Brocade G620\",\n            \"Fabric A\"\n          ],\n          \"mac\": \"00:1A:2B:FC:01:01\",\n          \"rackUnit\": 42,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-b1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"fc-switch-2\": {\n          \"shape\": \"switch\",\n          \"name\": \"FC Switch 2\",\n          \"ip\": \"10.10.2.2\",\n          \"role\": \"Fibre Channel\",\n          \"tags\": [\n            \"storage\",\n            \"fc\",\n            \"fabric-b\"\n          ],\n          \"notes\": [\n            \"Brocade G620\",\n            \"Fabric B\"\n          ],\n          \"mac\": \"00:1A:2B:FC:01:02\",\n          \"rackUnit\": 41,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-b1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"backup-server-1\": {\n          \"shape\": \"server\",\n          \"name\": \"Backup Server 1\",\n          \"ip\": \"10.10.3.10\",\n          \"role\": \"Backup Infrastructure\",\n          \"tags\": [\n            \"backup\",\n            \"veeam\",\n            \"protection\"\n          ],\n          \"notes\": [\n            \"Veeam Backup Server\",\n            \"Dell R740xd\",\n            \"200TB\"\n          ],\n          \"mac\": \"00:50:56:BB:01:01\",\n          \"rackUnit\": 36,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-b2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"backup-server-2\": {\n          \"shape\": \"server\",\n          \"name\": \"Backup Server 2\",\n          \"ip\": \"10.10.3.11\",\n          \"role\": \"Backup Infrastructure\",\n          \"tags\": [\n            \"backup\",\n            \"veeam\",\n            \"protection\"\n          ],\n          \"notes\": [\n            \"Veeam Backup Server\",\n            \"Dell R740xd\",\n            \"200TB\"\n          ],\n          \"mac\": \"00:50:56:BB:01:02\",\n          \"rackUnit\": 33,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-b2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"tape-library\": {\n          \"shape\": \"database\",\n          \"name\": \"Tape Library\",\n          \"ip\": \"10.10.3.20\",\n          \"role\": \"Archival Storage\",\n          \"tags\": [\n            \"backup\",\n            \"tape\",\n            \"lto9\"\n          ],\n          \"notes\": [\n            \"IBM TS4500\",\n            \"LTO-9\",\n            \"Long-term archive\"\n          ],\n          \"mac\": \"00:50:56:BB:02:01\",\n          \"rackUnit\": 20,\n          \"uHeight\": \"10\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-b2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"tor-switch-b1\": {\n          \"shape\": \"switch\",\n          \"name\": \"ToR Switch B1\",\n          \"ip\": \"10.10.2.3\",\n          \"role\": \"Top of Rack\",\n          \"tags\": [\n            \"tor\",\n            \"access\",\n            \"rack-b1\"\n          ],\n          \"notes\": [\n            \"Cisco Nexus 93180YC-FX\"\n          ],\n          \"mac\": \"00:1A:2B:3C:5D:03\",\n          \"rackUnit\": 40,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-b1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"tor-switch-b2\": {\n          \"shape\": \"switch\",\n          \"name\": \"ToR Switch B2\",\n          \"ip\": \"10.10.3.1\",\n          \"role\": \"Top of Rack\",\n          \"tags\": [\n            \"tor\",\n            \"access\",\n            \"rack-b2\"\n          ],\n          \"notes\": [\n            \"Cisco Nexus 93180YC-FX\"\n          ],\n          \"mac\": \"00:1A:2B:3C:5D:04\",\n          \"rackUnit\": 42,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-b2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"web-server-1\": {\n          \"shape\": \"server\",\n          \"name\": \"Web Server 1\",\n          \"ip\": \"172.16.0.11\",\n          \"role\": \"Web Frontend\",\n          \"tags\": [\n            \"dmz\",\n            \"web\",\n            \"nginx\"\n          ],\n          \"notes\": [\n            \"NGINX reverse proxy\",\n            \"Public facing\"\n          ],\n          \"mac\": \"00:50:56:CC:01:01\",\n          \"rackUnit\": 20,\n          \"uHeight\": \"1\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"dmz-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"web-server-2\": {\n          \"shape\": \"server\",\n          \"name\": \"Web Server 2\",\n          \"ip\": \"172.16.0.12\",\n          \"role\": \"Web Frontend\",\n          \"tags\": [\n            \"dmz\",\n            \"web\",\n            \"nginx\"\n          ],\n          \"notes\": [\n            \"NGINX reverse proxy\",\n            \"Public facing\"\n          ],\n          \"mac\": \"00:50:56:CC:01:02\",\n          \"rackUnit\": 18,\n          \"uHeight\": \"1\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"dmz-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"waf-1\": {\n          \"shape\": \"firewall\",\n          \"name\": \"WAF Appliance\",\n          \"ip\": \"172.16.0.5\",\n          \"role\": \"Web Application Firewall\",\n          \"tags\": [\n            \"dmz\",\n            \"security\",\n            \"waf\"\n          ],\n          \"notes\": [\n            \"F5 BIG-IP ASM\",\n            \"OWASP protection\"\n          ],\n          \"mac\": \"00:50:56:CC:02:01\",\n          \"rackUnit\": 22,\n          \"uHeight\": \"2\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"dmz-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"load-balancer-dmz\": {\n          \"shape\": \"switch\",\n          \"name\": \"DMZ Load Balancer\",\n          \"ip\": \"172.16.0.3\",\n          \"role\": \"Load Balancing\",\n          \"tags\": [\n            \"dmz\",\n            \"lb\",\n            \"f5\"\n          ],\n          \"notes\": [\n            \"F5 BIG-IP LTM\",\n            \"VIP: 172.16.0.100\"\n          ],\n          \"mac\": \"00:50:56:CC:03:01\",\n          \"rackUnit\": 16,\n          \"uHeight\": \"2\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"dmz-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"mail-gateway\": {\n          \"shape\": \"server\",\n          \"name\": \"Mail Gateway\",\n          \"ip\": \"172.16.0.25\",\n          \"role\": \"Email Security\",\n          \"tags\": [\n            \"dmz\",\n            \"email\",\n            \"security\"\n          ],\n          \"notes\": [\n            \"Proofpoint Email Gateway\",\n            \"Spam/malware filtering\"\n          ],\n          \"mac\": \"00:50:56:CC:04:01\",\n          \"rackUnit\": 14,\n          \"uHeight\": \"1\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"dmz-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dns-external-1\": {\n          \"shape\": \"circle\",\n          \"name\": \"External DNS 1\",\n          \"ip\": \"172.16.0.53\",\n          \"role\": \"External DNS\",\n          \"tags\": [\n            \"dmz\",\n            \"dns\",\n            \"public\"\n          ],\n          \"notes\": [\n            \"BIND DNS\",\n            \"Authoritative for corp.com\"\n          ],\n          \"mac\": \"00:50:56:CC:05:01\",\n          \"rackUnit\": 12,\n          \"uHeight\": \"1\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"dmz-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dns-external-2\": {\n          \"shape\": \"circle\",\n          \"name\": \"External DNS 2\",\n          \"ip\": \"172.16.0.54\",\n          \"role\": \"External DNS\",\n          \"tags\": [\n            \"dmz\",\n            \"dns\",\n            \"public\"\n          ],\n          \"notes\": [\n            \"BIND DNS\",\n            \"Secondary for corp.com\"\n          ],\n          \"mac\": \"00:50:56:CC:05:02\",\n          \"rackUnit\": 10,\n          \"uHeight\": \"1\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"dmz-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"vcenter\": {\n          \"shape\": \"server\",\n          \"name\": \"vCenter Server\",\n          \"ip\": \"192.168.100.10\",\n          \"role\": \"Virtualization Management\",\n          \"tags\": [\n            \"management\",\n            \"vmware\",\n            \"vcsa\"\n          ],\n          \"notes\": [\n            \"vCenter Server Appliance 8.0\",\n            \"Single SSO domain\"\n          ],\n          \"mac\": \"00:50:56:DD:01:01\",\n          \"rackUnit\": 20,\n          \"uHeight\": \"2\",\n          \"layer\": \"logical\",\n          \"assignedRack\": \"mgmt-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"nsx-manager\": {\n          \"shape\": \"server\",\n          \"name\": \"NSX Manager\",\n          \"ip\": \"192.168.100.15\",\n          \"role\": \"Network Virtualization\",\n          \"tags\": [\n            \"management\",\n            \"vmware\",\n            \"nsx\"\n          ],\n          \"notes\": [\n            \"NSX-T 4.1 Manager Cluster\"\n          ],\n          \"mac\": \"00:50:56:DD:02:01\",\n          \"rackUnit\": 17,\n          \"uHeight\": \"2\",\n          \"layer\": \"logical\",\n          \"assignedRack\": \"mgmt-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"siem-server\": {\n          \"shape\": \"server\",\n          \"name\": \"SIEM Server\",\n          \"ip\": \"192.168.100.50\",\n          \"role\": \"Security Monitoring\",\n          \"tags\": [\n            \"management\",\n            \"security\",\n            \"splunk\"\n          ],\n          \"notes\": [\n            \"Splunk Enterprise\",\n            \"Security monitoring\"\n          ],\n          \"mac\": \"00:50:56:DD:03:01\",\n          \"rackUnit\": 14,\n          \"uHeight\": \"2\",\n          \"layer\": \"logical\",\n          \"assignedRack\": \"mgmt-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"nms-server\": {\n          \"shape\": \"server\",\n          \"name\": \"Network Monitoring\",\n          \"ip\": \"192.168.100.60\",\n          \"role\": \"Network Management\",\n          \"tags\": [\n            \"management\",\n            \"monitoring\",\n            \"prtg\"\n          ],\n          \"notes\": [\n            \"PRTG Network Monitor\",\n            \"5000 sensors\"\n          ],\n          \"mac\": \"00:50:56:DD:04:01\",\n          \"rackUnit\": 11,\n          \"uHeight\": \"1\",\n          \"layer\": \"logical\",\n          \"assignedRack\": \"mgmt-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"jump-server\": {\n          \"shape\": \"server\",\n          \"name\": \"Jump Server\",\n          \"ip\": \"192.168.100.100\",\n          \"role\": \"Bastion Host\",\n          \"tags\": [\n            \"management\",\n            \"security\",\n            \"bastion\"\n          ],\n          \"notes\": [\n            \"Windows Server 2022\",\n            \"MFA enabled\"\n          ],\n          \"mac\": \"00:50:56:DD:05:01\",\n          \"rackUnit\": 9,\n          \"uHeight\": \"1\",\n          \"layer\": \"logical\",\n          \"assignedRack\": \"mgmt-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"ipam-server\": {\n          \"shape\": \"server\",\n          \"name\": \"IPAM/DDI\",\n          \"ip\": \"192.168.100.70\",\n          \"role\": \"IP Management\",\n          \"tags\": [\n            \"management\",\n            \"dns\",\n            \"dhcp\"\n          ],\n          \"notes\": [\n            \"Infoblox DDI\",\n            \"DNS/DHCP/IPAM\"\n          ],\n          \"mac\": \"00:50:56:DD:06:01\",\n          \"rackUnit\": 7,\n          \"uHeight\": \"2\",\n          \"layer\": \"logical\",\n          \"assignedRack\": \"mgmt-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"wlc-primary\": {\n          \"shape\": \"wifi\",\n          \"name\": \"WLC Primary\",\n          \"ip\": \"10.20.0.1\",\n          \"role\": \"Wireless Controller\",\n          \"tags\": [\n            \"wireless\",\n            \"cisco\",\n            \"9800\"\n          ],\n          \"notes\": [\n            \"Cisco C9800-40\",\n            \"Primary controller\"\n          ],\n          \"mac\": \"00:1A:2B:WL:01:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"wlc-secondary\": {\n          \"shape\": \"wifi\",\n          \"name\": \"WLC Secondary\",\n          \"ip\": \"10.20.0.2\",\n          \"role\": \"Wireless Controller\",\n          \"tags\": [\n            \"wireless\",\n            \"cisco\",\n            \"9800\"\n          ],\n          \"notes\": [\n            \"Cisco C9800-40\",\n            \"HA Secondary\"\n          ],\n          \"mac\": \"00:1A:2B:WL:01:02\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"mobile-zone-hq\": {\n          \"shape\": \"phone\",\n          \"name\": \"HQ Mobile Zone\",\n          \"ip\": \"10.20.10.0/24\",\n          \"role\": \"Mobile Device Zone\",\n          \"tags\": [\n            \"wireless\",\n            \"byod\",\n            \"mobile\"\n          ],\n          \"notes\": [\n            \"Corporate BYOD\",\n            \"MDM enrolled devices\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"mobile-zone-guest\": {\n          \"shape\": \"phone\",\n          \"name\": \"Guest WiFi Zone\",\n          \"ip\": \"10.30.0.0/24\",\n          \"role\": \"Guest Network\",\n          \"tags\": [\n            \"wireless\",\n            \"guest\",\n            \"isolated\"\n          ],\n          \"notes\": [\n            \"Captive portal\",\n            \"Internet only\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"mobile-zone-iot\": {\n          \"shape\": \"phone\",\n          \"name\": \"IoT Device Zone\",\n          \"ip\": \"10.40.0.0/24\",\n          \"role\": \"IoT Network\",\n          \"tags\": [\n            \"wireless\",\n            \"iot\",\n            \"building\"\n          ],\n          \"notes\": [\n            \"Building automation\",\n            \"Smart devices\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"branch-router-ny\": {\n          \"shape\": \"router\",\n          \"name\": \"NYC Branch Router\",\n          \"ip\": \"10.100.0.1\",\n          \"role\": \"Branch Gateway\",\n          \"tags\": [\n            \"branch\",\n            \"nyc\",\n            \"sd-wan\"\n          ],\n          \"notes\": [\n            \"Cisco Viptela vEdge\",\n            \"SD-WAN enabled\"\n          ],\n          \"mac\": \"00:1A:2B:BR:01:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"branch-router-la\": {\n          \"shape\": \"router\",\n          \"name\": \"LA Branch Router\",\n          \"ip\": \"10.101.0.1\",\n          \"role\": \"Branch Gateway\",\n          \"tags\": [\n            \"branch\",\n            \"la\",\n            \"sd-wan\"\n          ],\n          \"notes\": [\n            \"Cisco Viptela vEdge\",\n            \"SD-WAN enabled\"\n          ],\n          \"mac\": \"00:1A:2B:BR:02:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"branch-router-chi\": {\n          \"shape\": \"router\",\n          \"name\": \"Chicago Branch Router\",\n          \"ip\": \"10.102.0.1\",\n          \"role\": \"Branch Gateway\",\n          \"tags\": [\n            \"branch\",\n            \"chicago\",\n            \"sd-wan\"\n          ],\n          \"notes\": [\n            \"Cisco Viptela vEdge\",\n            \"SD-WAN enabled\"\n          ],\n          \"mac\": \"00:1A:2B:BR:03:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"branch-router-lon\": {\n          \"shape\": \"router\",\n          \"name\": \"London Branch Router\",\n          \"ip\": \"10.200.0.1\",\n          \"role\": \"Branch Gateway\",\n          \"tags\": [\n            \"branch\",\n            \"london\",\n            \"sd-wan\"\n          ],\n          \"notes\": [\n            \"Cisco Viptela vEdge\",\n            \"EMEA region\"\n          ],\n          \"mac\": \"00:1A:2B:BR:04:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"branch-router-tokyo\": {\n          \"shape\": \"router\",\n          \"name\": \"Tokyo Branch Router\",\n          \"ip\": \"10.201.0.1\",\n          \"role\": \"Branch Gateway\",\n          \"tags\": [\n            \"branch\",\n            \"tokyo\",\n            \"sd-wan\"\n          ],\n          \"notes\": [\n            \"Cisco Viptela vEdge\",\n            \"APAC region\"\n          ],\n          \"mac\": \"00:1A:2B:BR:05:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"cloud-aws\": {\n          \"shape\": \"cloud\",\n          \"name\": \"AWS Cloud\",\n          \"ip\": \"vpc-0a1b2c3d\",\n          \"role\": \"Public Cloud\",\n          \"tags\": [\n            \"cloud\",\n            \"aws\",\n            \"hybrid\"\n          ],\n          \"notes\": [\n            \"AWS US-East-1\",\n            \"VPC peering to HQ\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"logical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"cloud-azure\": {\n          \"shape\": \"cloud\",\n          \"name\": \"Azure Cloud\",\n          \"ip\": \"vnet-corp-prod\",\n          \"role\": \"Public Cloud\",\n          \"tags\": [\n            \"cloud\",\n            \"azure\",\n            \"hybrid\"\n          ],\n          \"notes\": [\n            \"Azure East US 2\",\n            \"ExpressRoute\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"logical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"cloud-gcp\": {\n          \"shape\": \"cloud\",\n          \"name\": \"GCP Cloud\",\n          \"ip\": \"vpc-gcp-corp\",\n          \"role\": \"Public Cloud\",\n          \"tags\": [\n            \"cloud\",\n            \"gcp\",\n            \"dev\"\n          ],\n          \"notes\": [\n            \"GCP us-central1\",\n            \"Dev/Test workloads\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"logical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"isp-primary\": {\n          \"shape\": \"globe\",\n          \"name\": \"ISP Primary\",\n          \"ip\": \"203.0.113.1\",\n          \"role\": \"Internet Uplink\",\n          \"tags\": [\n            \"wan\",\n            \"internet\",\n            \"primary\"\n          ],\n          \"notes\": [\n            \"AT&T MPLS\",\n            \"1 Gbps dedicated\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"isp-secondary\": {\n          \"shape\": \"globe\",\n          \"name\": \"ISP Secondary\",\n          \"ip\": \"198.51.100.1\",\n          \"role\": \"Internet Uplink\",\n          \"tags\": [\n            \"wan\",\n            \"internet\",\n            \"backup\"\n          ],\n          \"notes\": [\n            \"Verizon Business\",\n            \"500 Mbps backup\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dc-internal-1\": {\n          \"shape\": \"circle\",\n          \"name\": \"DC1 Int DNS\",\n          \"ip\": \"10.10.0.53\",\n          \"role\": \"Internal DNS/AD\",\n          \"tags\": [\n            \"dns\",\n            \"ad\",\n            \"dc1\"\n          ],\n          \"notes\": [\n            \"Windows Server 2022\",\n            \"Primary DC\"\n          ],\n          \"mac\": \"00:50:56:AD:01:01\",\n          \"rackUnit\": 26,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dc-internal-2\": {\n          \"shape\": \"circle\",\n          \"name\": \"DC2 Int DNS\",\n          \"ip\": \"10.10.1.53\",\n          \"role\": \"Internal DNS/AD\",\n          \"tags\": [\n            \"dns\",\n            \"ad\",\n            \"dc2\"\n          ],\n          \"notes\": [\n            \"Windows Server 2022\",\n            \"Secondary DC\"\n          ],\n          \"mac\": \"00:50:56:AD:01:02\",\n          \"rackUnit\": 26,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"app-server-1\": {\n          \"shape\": \"server\",\n          \"name\": \"App Server 01\",\n          \"ip\": \"10.10.0.101\",\n          \"role\": \"Application\",\n          \"tags\": [\n            \"app\",\n            \"iis\",\n            \"web\"\n          ],\n          \"notes\": [\n            \"Windows Server 2022\",\n            \"IIS Application\"\n          ],\n          \"mac\": \"00:50:56:AP:01:01\",\n          \"rackUnit\": 24,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"app-server-2\": {\n          \"shape\": \"server\",\n          \"name\": \"App Server 02\",\n          \"ip\": \"10.10.0.102\",\n          \"role\": \"Application\",\n          \"tags\": [\n            \"app\",\n            \"iis\",\n            \"web\"\n          ],\n          \"notes\": [\n            \"Windows Server 2022\",\n            \"IIS Application\"\n          ],\n          \"mac\": \"00:50:56:AP:01:02\",\n          \"rackUnit\": 22,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"db-server-1\": {\n          \"shape\": \"database\",\n          \"name\": \"SQL Server 01\",\n          \"ip\": \"10.10.0.201\",\n          \"role\": \"Database\",\n          \"tags\": [\n            \"db\",\n            \"sql\",\n            \"primary\"\n          ],\n          \"notes\": [\n            \"SQL Server 2022 Enterprise\",\n            \"AlwaysOn Primary\"\n          ],\n          \"mac\": \"00:50:56:DB:01:01\",\n          \"rackUnit\": 20,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"db-server-2\": {\n          \"shape\": \"database\",\n          \"name\": \"SQL Server 02\",\n          \"ip\": \"10.10.1.201\",\n          \"role\": \"Database\",\n          \"tags\": [\n            \"db\",\n            \"sql\",\n            \"secondary\"\n          ],\n          \"notes\": [\n            \"SQL Server 2022 Enterprise\",\n            \"AlwaysOn Secondary\"\n          ],\n          \"mac\": \"00:50:56:DB:01:02\",\n          \"rackUnit\": 24,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"k8s-master-1\": {\n          \"shape\": \"hexagon\",\n          \"name\": \"K8s Master 1\",\n          \"ip\": \"10.10.1.50\",\n          \"role\": \"Container Orchestration\",\n          \"tags\": [\n            \"kubernetes\",\n            \"master\",\n            \"container\"\n          ],\n          \"notes\": [\n            \"K8s Control Plane\",\n            \"etcd member\"\n          ],\n          \"mac\": \"00:50:56:K8:01:01\",\n          \"rackUnit\": 21,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"k8s-master-2\": {\n          \"shape\": \"hexagon\",\n          \"name\": \"K8s Master 2\",\n          \"ip\": \"10.10.1.51\",\n          \"role\": \"Container Orchestration\",\n          \"tags\": [\n            \"kubernetes\",\n            \"master\",\n            \"container\"\n          ],\n          \"notes\": [\n            \"K8s Control Plane\",\n            \"etcd member\"\n          ],\n          \"mac\": \"00:50:56:K8:01:02\",\n          \"rackUnit\": 19,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"k8s-master-3\": {\n          \"shape\": \"hexagon\",\n          \"name\": \"K8s Master 3\",\n          \"ip\": \"10.10.1.52\",\n          \"role\": \"Container Orchestration\",\n          \"tags\": [\n            \"kubernetes\",\n            \"master\",\n            \"container\"\n          ],\n          \"notes\": [\n            \"K8s Control Plane\",\n            \"etcd member\"\n          ],\n          \"mac\": \"00:50:56:K8:01:03\",\n          \"rackUnit\": 17,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"k8s-worker-1\": {\n          \"shape\": \"server\",\n          \"name\": \"K8s Worker 1\",\n          \"ip\": \"10.10.1.60\",\n          \"role\": \"Container Workload\",\n          \"tags\": [\n            \"kubernetes\",\n            \"worker\",\n            \"container\"\n          ],\n          \"notes\": [\n            \"K8s Worker Node\",\n            \"64GB RAM\"\n          ],\n          \"mac\": \"00:50:56:K8:02:01\",\n          \"rackUnit\": 15,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"k8s-worker-2\": {\n          \"shape\": \"server\",\n          \"name\": \"K8s Worker 2\",\n          \"ip\": \"10.10.1.61\",\n          \"role\": \"Container Workload\",\n          \"tags\": [\n            \"kubernetes\",\n            \"worker\",\n            \"container\"\n          ],\n          \"notes\": [\n            \"K8s Worker Node\",\n            \"64GB RAM\"\n          ],\n          \"mac\": \"00:50:56:K8:02:02\",\n          \"rackUnit\": 13,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"k8s-worker-3\": {\n          \"shape\": \"server\",\n          \"name\": \"K8s Worker 3\",\n          \"ip\": \"10.10.1.62\",\n          \"role\": \"Container Workload\",\n          \"tags\": [\n            \"kubernetes\",\n            \"worker\",\n            \"container\"\n          ],\n          \"notes\": [\n            \"K8s Worker Node\",\n            \"64GB RAM\"\n          ],\n          \"mac\": \"00:50:56:K8:02:03\",\n          \"rackUnit\": 11,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"k8s-worker-4\": {\n          \"shape\": \"server\",\n          \"name\": \"K8s Worker 4\",\n          \"ip\": \"10.10.1.63\",\n          \"role\": \"Container Workload\",\n          \"tags\": [\n            \"kubernetes\",\n            \"worker\",\n            \"container\"\n          ],\n          \"notes\": [\n            \"K8s Worker Node\",\n            \"64GB RAM\"\n          ],\n          \"mac\": \"00:50:56:K8:02:04\",\n          \"rackUnit\": 9,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"proxy-server-1\": {\n          \"shape\": \"server\",\n          \"name\": \"Proxy Server 1\",\n          \"ip\": \"10.5.0.10\",\n          \"role\": \"Web Proxy\",\n          \"tags\": [\n            \"proxy\",\n            \"squid\",\n            \"filtering\"\n          ],\n          \"notes\": [\n            \"Squid Proxy\",\n            \"Content filtering\"\n          ],\n          \"mac\": \"00:50:56:PX:01:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"proxy-server-2\": {\n          \"shape\": \"server\",\n          \"name\": \"Proxy Server 2\",\n          \"ip\": \"10.5.0.11\",\n          \"role\": \"Web Proxy\",\n          \"tags\": [\n            \"proxy\",\n            \"squid\",\n            \"filtering\"\n          ],\n          \"notes\": [\n            \"Squid Proxy\",\n            \"HA pair\"\n          ],\n          \"mac\": \"00:50:56:PX:01:02\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"vpn-concentrator\": {\n          \"shape\": \"firewall\",\n          \"name\": \"VPN Concentrator\",\n          \"ip\": \"10.0.5.1\",\n          \"role\": \"Remote Access VPN\",\n          \"tags\": [\n            \"vpn\",\n            \"remote\",\n            \"security\"\n          ],\n          \"notes\": [\n            \"Cisco ASA 5555-X\",\n            \"AnyConnect SSL VPN\"\n          ],\n          \"mac\": \"00:1A:2B:VP:01:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"nac-server\": {\n          \"shape\": \"server\",\n          \"name\": \"NAC Server\",\n          \"ip\": \"10.5.5.10\",\n          \"role\": \"Network Access Control\",\n          \"tags\": [\n            \"nac\",\n            \"ise\",\n            \"802.1x\"\n          ],\n          \"notes\": [\n            \"Cisco ISE 3.1\",\n            \"RADIUS/TACACS+\"\n          ],\n          \"mac\": \"00:50:56:NA:01:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"security\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"print-server\": {\n          \"shape\": \"server\",\n          \"name\": \"Print Server\",\n          \"ip\": \"10.10.0.150\",\n          \"role\": \"Print Services\",\n          \"tags\": [\n            \"print\",\n            \"windows\",\n            \"services\"\n          ],\n          \"notes\": [\n            \"Windows Print Server\",\n            \"50+ printers\"\n          ],\n          \"mac\": \"00:50:56:PR:01:01\",\n          \"rackUnit\": 18,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"file-server\": {\n          \"shape\": \"database\",\n          \"name\": \"File Server\",\n          \"ip\": \"10.10.0.160\",\n          \"role\": \"File Services\",\n          \"tags\": [\n            \"file\",\n            \"smb\",\n            \"dfs\"\n          ],\n          \"notes\": [\n            \"Windows File Server\",\n            \"DFS namespace\"\n          ],\n          \"mac\": \"00:50:56:FS:01:01\",\n          \"rackUnit\": 16,\n          \"uHeight\": \"2\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"ca-server\": {\n          \"shape\": \"server\",\n          \"name\": \"Certificate Authority\",\n          \"ip\": \"192.168.100.80\",\n          \"role\": \"PKI Infrastructure\",\n          \"tags\": [\n            \"pki\",\n            \"ca\",\n            \"security\"\n          ],\n          \"notes\": [\n            \"Windows CA\",\n            \"Enterprise Root CA\"\n          ],\n          \"mac\": \"00:50:56:CA:01:01\",\n          \"rackUnit\": 5,\n          \"uHeight\": \"1\",\n          \"layer\": \"logical\",\n          \"assignedRack\": \"mgmt-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"sccm-server\": {\n          \"shape\": \"server\",\n          \"name\": \"SCCM Server\",\n          \"ip\": \"192.168.100.90\",\n          \"role\": \"Endpoint Management\",\n          \"tags\": [\n            \"sccm\",\n            \"patching\",\n            \"software\"\n          ],\n          \"notes\": [\n            \"MECM Primary Site\",\n            \"Software deployment\"\n          ],\n          \"mac\": \"00:50:56:SC:01:01\",\n          \"rackUnit\": 3,\n          \"uHeight\": \"2\",\n          \"layer\": \"logical\",\n          \"assignedRack\": \"mgmt-rack\",\n          \"rackCapacity\": \"24\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"voip-cluster\": {\n          \"shape\": \"phone\",\n          \"name\": \"VoIP Cluster\",\n          \"ip\": \"10.50.0.0/24\",\n          \"role\": \"Voice Services\",\n          \"tags\": [\n            \"voip\",\n            \"cisco\",\n            \"ucm\"\n          ],\n          \"notes\": [\n            \"Cisco UCM Cluster\",\n            \"3000 endpoints\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"application\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"video-conf\": {\n          \"shape\": \"laptop\",\n          \"name\": \"Video Conference\",\n          \"ip\": \"10.51.0.0/24\",\n          \"role\": \"Video Services\",\n          \"tags\": [\n            \"video\",\n            \"webex\",\n            \"teams\"\n          ],\n          \"notes\": [\n            \"Webex/Teams integration\",\n            \"Meeting rooms\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"application\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"security-cameras\": {\n          \"shape\": \"camera\",\n          \"name\": \"Security Cameras\",\n          \"ip\": \"10.60.0.0/24\",\n          \"role\": \"Physical Security\",\n          \"tags\": [\n            \"cctv\",\n            \"surveillance\",\n            \"security\"\n          ],\n          \"notes\": [\n            \"150+ IP cameras\",\n            \"30-day retention\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"nvr-cluster\": {\n          \"shape\": \"server\",\n          \"name\": \"NVR Cluster\",\n          \"ip\": \"10.60.0.10\",\n          \"role\": \"Video Recording\",\n          \"tags\": [\n            \"nvr\",\n            \"surveillance\",\n            \"storage\"\n          ],\n          \"notes\": [\n            \"Milestone XProtect\",\n            \"500TB storage\"\n          ],\n          \"mac\": \"00:50:56:NV:01:01\",\n          \"rackUnit\": 15,\n          \"uHeight\": \"4\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-b2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dev-server-1\": {\n          \"shape\": \"server\",\n          \"name\": \"Dev Server 1\",\n          \"ip\": \"10.80.0.10\",\n          \"role\": \"Development\",\n          \"tags\": [\n            \"dev\",\n            \"gitlab\",\n            \"ci-cd\",\n            {\n              \"type\": \"icon\",\n              \"library\": \"selfhst\",\n              \"name\": \"dokku\"\n            }\n          ],\n          \"notes\": [\n            \"GitLab Server\",\n            \"CI/CD pipelines\"\n          ],\n          \"mac\": \"00:50:56:DV:01:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"application\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dev-server-2\": {\n          \"shape\": \"server\",\n          \"name\": \"Dev Server 2\",\n          \"ip\": \"10.80.0.11\",\n          \"role\": \"Development\",\n          \"tags\": [\n            \"dev\",\n            \"jenkins\",\n            \"ci-cd\"\n          ],\n          \"notes\": [\n            \"Jenkins Server\",\n            \"Build automation\"\n          ],\n          \"mac\": \"00:50:56:DV:01:02\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"2\",\n          \"layer\": \"application\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"test-environment\": {\n          \"shape\": \"hexagon\",\n          \"name\": \"Test Environment\",\n          \"ip\": \"10.81.0.0/24\",\n          \"role\": \"QA/Testing\",\n          \"tags\": [\n            \"test\",\n            \"qa\",\n            \"staging\"\n          ],\n          \"notes\": [\n            \"Staging environment\",\n            \"Pre-prod validation\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"application\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"erp-system\": {\n          \"shape\": \"database\",\n          \"name\": \"ERP System\",\n          \"ip\": \"10.90.0.10\",\n          \"role\": \"Business Application\",\n          \"tags\": [\n            \"erp\",\n            \"sap\",\n            \"business\"\n          ],\n          \"notes\": [\n            \"SAP S/4HANA\",\n            \"Financial/HR systems\"\n          ],\n          \"mac\": \"00:50:56:ER:01:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"4\",\n          \"layer\": \"application\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"crm-system\": {\n          \"shape\": \"database\",\n          \"name\": \"CRM System\",\n          \"ip\": \"10.91.0.10\",\n          \"role\": \"Business Application\",\n          \"tags\": [\n            \"crm\",\n            \"salesforce\",\n            \"business\"\n          ],\n          \"notes\": [\n            \"Salesforce integration\",\n            \"Sales/Marketing\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"application\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"endpoint-1000\": {\n          \"shape\": \"laptop\",\n          \"name\": \"Corporate Endpoints\",\n          \"ip\": \"10.70.0.0/22\",\n          \"role\": \"User Workstations\",\n          \"tags\": [\n            \"endpoints\",\n            \"workstations\",\n            \"users\"\n          ],\n          \"notes\": [\n            \"~1000 corporate laptops\",\n            \"Windows 11\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dist-switch-floor1\": {\n          \"shape\": \"switch\",\n          \"name\": \"Floor 1 Switch\",\n          \"ip\": \"10.1.1.1\",\n          \"role\": \"Distribution\",\n          \"tags\": [\n            \"distribution\",\n            \"floor-1\",\n            \"access\"\n          ],\n          \"notes\": [\n            \"Cisco C9300-48P\",\n            \"PoE+ enabled\"\n          ],\n          \"mac\": \"00:1A:2B:FL:01:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dist-switch-floor2\": {\n          \"shape\": \"switch\",\n          \"name\": \"Floor 2 Switch\",\n          \"ip\": \"10.1.2.1\",\n          \"role\": \"Distribution\",\n          \"tags\": [\n            \"distribution\",\n            \"floor-2\",\n            \"access\"\n          ],\n          \"notes\": [\n            \"Cisco C9300-48P\",\n            \"PoE+ enabled\"\n          ],\n          \"mac\": \"00:1A:2B:FL:02:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dist-switch-floor3\": {\n          \"shape\": \"switch\",\n          \"name\": \"Floor 3 Switch\",\n          \"ip\": \"10.1.3.1\",\n          \"role\": \"Distribution\",\n          \"tags\": [\n            \"distribution\",\n            \"floor-3\",\n            \"access\"\n          ],\n          \"notes\": [\n            \"Cisco C9300-48P\",\n            \"PoE+ enabled\"\n          ],\n          \"mac\": \"00:1A:2B:FL:03:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dist-switch-floor4\": {\n          \"shape\": \"switch\",\n          \"name\": \"Floor 4 Switch\",\n          \"ip\": \"10.1.4.1\",\n          \"role\": \"Distribution\",\n          \"tags\": [\n            \"distribution\",\n            \"floor-4\",\n            \"access\"\n          ],\n          \"notes\": [\n            \"Cisco C9300-48P\",\n            \"PoE+ enabled\"\n          ],\n          \"mac\": \"00:1A:2B:FL:04:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"ap-floor1-zone1\": {\n          \"shape\": \"wifi\",\n          \"name\": \"AP Floor 1 Zone 1\",\n          \"ip\": \"10.20.1.10\",\n          \"role\": \"Wireless Access\",\n          \"tags\": [\n            \"wifi\",\n            \"ap\",\n            \"floor-1\"\n          ],\n          \"notes\": [\n            \"Cisco 9120AX\",\n            \"Wi-Fi 6\"\n          ],\n          \"mac\": \"00:1A:2B:AP:01:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"ap-floor2-zone1\": {\n          \"shape\": \"wifi\",\n          \"name\": \"AP Floor 2 Zone 1\",\n          \"ip\": \"10.20.2.10\",\n          \"role\": \"Wireless Access\",\n          \"tags\": [\n            \"wifi\",\n            \"ap\",\n            \"floor-2\"\n          ],\n          \"notes\": [\n            \"Cisco 9120AX\",\n            \"Wi-Fi 6\"\n          ],\n          \"mac\": \"00:1A:2B:AP:02:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"ap-floor3-zone1\": {\n          \"shape\": \"wifi\",\n          \"name\": \"AP Floor 3 Zone 1\",\n          \"ip\": \"10.20.3.10\",\n          \"role\": \"Wireless Access\",\n          \"tags\": [\n            \"wifi\",\n            \"ap\",\n            \"floor-3\"\n          ],\n          \"notes\": [\n            \"Cisco 9120AX\",\n            \"Wi-Fi 6\"\n          ],\n          \"mac\": \"00:1A:2B:AP:03:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"ap-floor4-zone1\": {\n          \"shape\": \"wifi\",\n          \"name\": \"AP Floor 4 Zone 1\",\n          \"ip\": \"10.20.4.10\",\n          \"role\": \"Wireless Access\",\n          \"tags\": [\n            \"wifi\",\n            \"ap\",\n            \"floor-4\"\n          ],\n          \"notes\": [\n            \"Cisco 9120AX\",\n            \"Wi-Fi 6\"\n          ],\n          \"mac\": \"00:1A:2B:AP:04:01\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"ups-dc-1\": {\n          \"shape\": \"rectangle\",\n          \"name\": \"UPS DC-1\",\n          \"ip\": \"192.168.200.10\",\n          \"role\": \"Power Management\",\n          \"tags\": [\n            \"power\",\n            \"ups\",\n            \"datacenter\"\n          ],\n          \"notes\": [\n            \"APC Symmetra\",\n            \"80kVA\",\n            \"30 min runtime\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"ups-dc-2\": {\n          \"shape\": \"rectangle\",\n          \"name\": \"UPS DC-2\",\n          \"ip\": \"192.168.200.11\",\n          \"role\": \"Power Management\",\n          \"tags\": [\n            \"power\",\n            \"ups\",\n            \"datacenter\"\n          ],\n          \"notes\": [\n            \"APC Symmetra\",\n            \"80kVA\",\n            \"Redundant\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"pdu-rack-a1\": {\n          \"shape\": \"rectangle\",\n          \"name\": \"PDU Rack A1\",\n          \"ip\": \"192.168.200.21\",\n          \"role\": \"Power Distribution\",\n          \"tags\": [\n            \"power\",\n            \"pdu\",\n            \"rack-a1\"\n          ],\n          \"notes\": [\n            \"APC Switched PDU\",\n            \"Per-outlet metering\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": 1,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a1\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"pdu-rack-a2\": {\n          \"shape\": \"rectangle\",\n          \"name\": \"PDU Rack A2\",\n          \"ip\": \"192.168.200.22\",\n          \"role\": \"Power Distribution\",\n          \"tags\": [\n            \"power\",\n            \"pdu\",\n            \"rack-a2\"\n          ],\n          \"notes\": [\n            \"APC Switched PDU\",\n            \"Per-outlet metering\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": 1,\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"dc-rack-a2\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"cooling-1\": {\n          \"shape\": \"rectangle\",\n          \"name\": \"CRAC Unit 1\",\n          \"ip\": \"192.168.200.30\",\n          \"role\": \"Cooling\",\n          \"tags\": [\n            \"cooling\",\n            \"hvac\",\n            \"datacenter\"\n          ],\n          \"notes\": [\n            \"Liebert CRV\",\n            \"Row-based cooling\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"cooling-2\": {\n          \"shape\": \"rectangle\",\n          \"name\": \"CRAC Unit 2\",\n          \"ip\": \"192.168.200.31\",\n          \"role\": \"Cooling\",\n          \"tags\": [\n            \"cooling\",\n            \"hvac\",\n            \"datacenter\"\n          ],\n          \"notes\": [\n            \"Liebert CRV\",\n            \"N+1 redundancy\"\n          ],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"camera-a\": {\n          \"shape\": \"camera\",\n          \"name\": \"camera A\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"ping\": {\n            \"enabled\": false,\n            \"protocol\": \"http\",\n            \"customUrl\": \"\",\n            \"timeout\": 3000,\n            \"status\": \"unknown\",\n            \"lastCheck\": null\n          },\n          \"locked\": false,\n          \"groupId\": null,\n          \"fovEnabled\": true,\n          \"fovRotation\": 104,\n          \"fovDistance\": 500,\n          \"fovSweep\": 60,\n          \"fovSpeed\": 10,\n          \"fovAnimate\": true\n        },\n        \"camera-a-copy\": {\n          \"shape\": \"camera\",\n          \"name\": \"camera B\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"ping\": {\n            \"enabled\": false,\n            \"protocol\": \"http\",\n            \"customUrl\": \"\",\n            \"timeout\": 3000,\n            \"status\": \"unknown\",\n            \"lastCheck\": null\n          },\n          \"locked\": false,\n          \"groupId\": null,\n          \"fovEnabled\": true,\n          \"fovRotation\": 162,\n          \"fovDistance\": 500,\n          \"fovSweep\": 60,\n          \"fovSpeed\": 10,\n          \"fovAnimate\": false\n        }\n      },\n      \"edges\": {\n        \"list\": [\n          {\n            \"id\": \"isp1-router1\",\n            \"from\": \"isp-primary\",\n            \"to\": \"core-router-1\",\n            \"width\": 6,\n            \"color\": \"#10b981\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"Primary WAN link\"\n            ],\n            \"fromPort\": \"Gi0/0\",\n            \"toPort\": \"Gi1/0/1\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"isp2-router2\",\n            \"from\": \"isp-secondary\",\n            \"to\": \"core-router-2\",\n            \"width\": 6,\n            \"color\": \"#10b981\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"Backup WAN link\"\n            ],\n            \"fromPort\": \"Gi0/0\",\n            \"toPort\": \"Gi1/0/1\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"router1-router2\",\n            \"from\": \"core-router-1\",\n            \"to\": \"core-router-2\",\n            \"width\": 4,\n            \"color\": \"#f59e0b\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"HSRP Peering\"\n            ],\n            \"fromPort\": \"Gi1/0/24\",\n            \"toPort\": \"Gi1/0/24\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"router1-fw1\",\n            \"from\": \"core-router-1\",\n            \"to\": \"fw-external-1\",\n            \"width\": 4,\n            \"color\": \"#ef4444\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"router2-fw2\",\n            \"from\": \"core-router-2\",\n            \"to\": \"fw-external-2\",\n            \"width\": 4,\n            \"color\": \"#ef4444\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"fw1-fw2\",\n            \"from\": \"fw-external-1\",\n            \"to\": \"fw-external-2\",\n            \"width\": 3,\n            \"color\": \"#f59e0b\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"HA heartbeat\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dashed\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"fw1-coresw1\",\n            \"from\": \"fw-external-1\",\n            \"to\": \"core-switch-1\",\n            \"width\": 4,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"fw2-coresw2\",\n            \"from\": \"fw-external-2\",\n            \"to\": \"core-switch-2\",\n            \"width\": 4,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-coresw2\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"core-switch-2\",\n            \"width\": 5,\n            \"color\": \"#3b82f6\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"VPC peer-link\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-fwint\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"fw-internal\",\n            \"width\": 3,\n            \"color\": \"#ef4444\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw2-fwint\",\n            \"from\": \"core-switch-2\",\n            \"to\": \"fw-internal\",\n            \"width\": 3,\n            \"color\": \"#ef4444\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-racka1\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"dc-rack-a1\",\n            \"width\": 4,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw2-racka1\",\n            \"from\": \"core-switch-2\",\n            \"to\": \"dc-rack-a1\",\n            \"width\": 4,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-racka2\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"dc-rack-a2\",\n            \"width\": 4,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw2-racka2\",\n            \"from\": \"core-switch-2\",\n            \"to\": \"dc-rack-a2\",\n            \"width\": 4,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-rackb1\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"dc-rack-b1\",\n            \"width\": 4,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw2-rackb1\",\n            \"from\": \"core-switch-2\",\n            \"to\": \"dc-rack-b1\",\n            \"width\": 4,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-rackb2\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"dc-rack-b2\",\n            \"width\": 4,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw2-rackb2\",\n            \"from\": \"core-switch-2\",\n            \"to\": \"dc-rack-b2\",\n            \"width\": 4,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"fw1-dmz\",\n            \"from\": \"fw-external-1\",\n            \"to\": \"dmz-rack\",\n            \"width\": 3,\n            \"color\": \"#f59e0b\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"DMZ segment\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"fw2-dmz\",\n            \"from\": \"fw-external-2\",\n            \"to\": \"dmz-rack\",\n            \"width\": 3,\n            \"color\": \"#f59e0b\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"DMZ segment\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-mgmt\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"mgmt-rack\",\n            \"width\": 3,\n            \"color\": \"#8b5cf6\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"OOB management\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-wlc1\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"wlc-primary\",\n            \"width\": 3,\n            \"color\": \"#06b6d4\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw2-wlc2\",\n            \"from\": \"core-switch-2\",\n            \"to\": \"wlc-secondary\",\n            \"width\": 3,\n            \"color\": \"#06b6d4\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\",\n            \"animate\": true\n          },\n          {\n            \"id\": \"wlc1-wlc2\",\n            \"from\": \"wlc-primary\",\n            \"to\": \"wlc-secondary\",\n            \"width\": 2,\n            \"color\": \"#f59e0b\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"HA pair\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dashed\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"wlc1-mobile-hq\",\n            \"from\": \"wlc-primary\",\n            \"to\": \"mobile-zone-hq\",\n            \"width\": 2,\n            \"color\": \"#06b6d4\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"wlc1-mobile-guest\",\n            \"from\": \"wlc-primary\",\n            \"to\": \"mobile-zone-guest\",\n            \"width\": 2,\n            \"color\": \"#06b6d4\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"wlc1-mobile-iot\",\n            \"from\": \"wlc-primary\",\n            \"to\": \"mobile-zone-iot\",\n            \"width\": 2,\n            \"color\": \"#06b6d4\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"router1-branch-ny\",\n            \"from\": \"core-router-1\",\n            \"to\": \"branch-router-ny\",\n            \"width\": 3,\n            \"color\": \"#a855f7\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"SD-WAN tunnel\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dashed\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"router1-branch-la\",\n            \"from\": \"core-router-1\",\n            \"to\": \"branch-router-la\",\n            \"width\": 3,\n            \"color\": \"#a855f7\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"SD-WAN tunnel\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dashed\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"router1-branch-chi\",\n            \"from\": \"core-router-1\",\n            \"to\": \"branch-router-chi\",\n            \"width\": 3,\n            \"color\": \"#a855f7\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"SD-WAN tunnel\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dashed\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"router1-branch-lon\",\n            \"from\": \"core-router-1\",\n            \"to\": \"branch-router-lon\",\n            \"width\": 3,\n            \"color\": \"#a855f7\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"SD-WAN tunnel\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dashed\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"router1-branch-tokyo\",\n            \"from\": \"core-router-1\",\n            \"to\": \"branch-router-tokyo\",\n            \"width\": 3,\n            \"color\": \"#a855f7\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"SD-WAN tunnel\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dashed\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"router1-aws\",\n            \"from\": \"core-router-1\",\n            \"to\": \"cloud-aws\",\n            \"width\": 3,\n            \"color\": \"#f97316\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"Direct Connect\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dashed\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"router2-azure\",\n            \"from\": \"core-router-2\",\n            \"to\": \"cloud-azure\",\n            \"width\": 3,\n            \"color\": \"#0ea5e9\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"ExpressRoute\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dashed\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"fwint-gcp\",\n            \"from\": \"fw-internal\",\n            \"to\": \"cloud-gcp\",\n            \"width\": 2,\n            \"color\": \"#22c55e\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"VPN tunnel\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dashed\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-floor1\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"dist-switch-floor1\",\n            \"width\": 3,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-floor2\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"dist-switch-floor2\",\n            \"width\": 3,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw2-floor3\",\n            \"from\": \"core-switch-2\",\n            \"to\": \"dist-switch-floor3\",\n            \"width\": 3,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw2-floor4\",\n            \"from\": \"core-switch-2\",\n            \"to\": \"dist-switch-floor4\",\n            \"width\": 3,\n            \"color\": \"#475569\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"floor1-endpoints\",\n            \"from\": \"dist-switch-floor1\",\n            \"to\": \"endpoint-1000\",\n            \"width\": 2,\n            \"color\": \"#94a3b8\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"floor1-ap1\",\n            \"from\": \"dist-switch-floor1\",\n            \"to\": \"ap-floor1-zone1\",\n            \"width\": 2,\n            \"color\": \"#06b6d4\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"floor2-ap2\",\n            \"from\": \"dist-switch-floor2\",\n            \"to\": \"ap-floor2-zone1\",\n            \"width\": 2,\n            \"color\": \"#06b6d4\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"floor3-ap3\",\n            \"from\": \"dist-switch-floor3\",\n            \"to\": \"ap-floor3-zone1\",\n            \"width\": 2,\n            \"color\": \"#06b6d4\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"floor4-ap4\",\n            \"from\": \"dist-switch-floor4\",\n            \"to\": \"ap-floor4-zone1\",\n            \"width\": 2,\n            \"color\": \"#06b6d4\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"fwint-proxy1\",\n            \"from\": \"fw-internal\",\n            \"to\": \"proxy-server-1\",\n            \"width\": 2,\n            \"color\": \"#ef4444\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"fwint-proxy2\",\n            \"from\": \"fw-internal\",\n            \"to\": \"proxy-server-2\",\n            \"width\": 2,\n            \"color\": \"#ef4444\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"fwext1-vpn\",\n            \"from\": \"fw-external-1\",\n            \"to\": \"vpn-concentrator\",\n            \"width\": 3,\n            \"color\": \"#8b5cf6\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-nac\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"nac-server\",\n            \"width\": 2,\n            \"color\": \"#f59e0b\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-voip\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"voip-cluster\",\n            \"width\": 3,\n            \"color\": \"#22c55e\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw2-video\",\n            \"from\": \"core-switch-2\",\n            \"to\": \"video-conf\",\n            \"width\": 3,\n            \"color\": \"#22c55e\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-cameras\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"security-cameras\",\n            \"width\": 2,\n            \"color\": \"#94a3b8\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"fwint-dev1\",\n            \"from\": \"fw-internal\",\n            \"to\": \"dev-server-1\",\n            \"width\": 2,\n            \"color\": \"#a855f7\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"fwint-dev2\",\n            \"from\": \"fw-internal\",\n            \"to\": \"dev-server-2\",\n            \"width\": 2,\n            \"color\": \"#a855f7\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\",\n            \"animate\": true,\n            \"animationSpeed\": \"1.5\"\n          },\n          {\n            \"id\": \"fwint-test\",\n            \"from\": \"fw-internal\",\n            \"to\": \"test-environment\",\n            \"width\": 2,\n            \"color\": \"#a855f7\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"coresw1-erp\",\n            \"from\": \"core-switch-1\",\n            \"to\": \"erp-system\",\n            \"width\": 3,\n            \"color\": \"#f59e0b\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"fwext1-crm\",\n            \"from\": \"fw-external-1\",\n            \"to\": \"crm-system\",\n            \"width\": 2,\n            \"color\": \"#f59e0b\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"Salesforce cloud\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dashed\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"ups1-racka1\",\n            \"from\": \"ups-dc-1\",\n            \"to\": \"dc-rack-a1\",\n            \"width\": 2,\n            \"color\": \"#fbbf24\",\n            \"direction\": \"forward\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"Power feed A\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"ups2-racka2\",\n            \"from\": \"ups-dc-2\",\n            \"to\": \"dc-rack-a2\",\n            \"width\": 2,\n            \"color\": \"#fbbf24\",\n            \"direction\": \"forward\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"Power feed B\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"ups1-rackb1\",\n            \"from\": \"ups-dc-1\",\n            \"to\": \"dc-rack-b1\",\n            \"width\": 2,\n            \"color\": \"#fbbf24\",\n            \"direction\": \"forward\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"Power feed A\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\",\n            \"animate\": true,\n            \"animationSpeed\": \"4\"\n          },\n          {\n            \"id\": \"ups2-rackb2\",\n            \"from\": \"ups-dc-2\",\n            \"to\": \"dc-rack-b2\",\n            \"width\": 2,\n            \"color\": \"#fbbf24\",\n            \"direction\": \"forward\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"Power feed B\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"cooling1-racka1\",\n            \"from\": \"cooling-1\",\n            \"to\": \"dc-rack-a1\",\n            \"width\": 2,\n            \"color\": \"#38bdf8\",\n            \"direction\": \"forward\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"Cooling zone\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dotted\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"cooling2-rackb1\",\n            \"from\": \"cooling-2\",\n            \"to\": \"dc-rack-b1\",\n            \"width\": 2,\n            \"color\": \"#38bdf8\",\n            \"direction\": \"forward\",\n            \"type\": \"main\",\n            \"notes\": [\n              \"Cooling zone\"\n            ],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"dotted\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1,\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"custom-1765237881452\",\n            \"type\": \"custom\",\n            \"color\": \"#c800ff\",\n            \"width\": 4,\n            \"lineStyle\": \"solid\",\n            \"direction\": \"forward\",\n            \"points\": [\n              {\n                \"x\": 3492.3994140625,\n                \"y\": 1526.9556884765625\n              },\n              {\n                \"x\": 3500.609619140625,\n                \"y\": 1830.7386474609375\n              },\n              {\n                \"x\": 3303.561279296875,\n                \"y\": 1732.2144775390625\n              }\n            ],\n            \"notes\": [],\n            \"routing\": \"orthogonal\"\n          },\n          {\n            \"id\": \"custom-1765239355462\",\n            \"type\": \"custom\",\n            \"color\": \"#f97316\",\n            \"width\": 4,\n            \"lineStyle\": \"solid\",\n            \"direction\": \"forward\",\n            \"points\": [\n              {\n                \"x\": 2467.182861328125,\n                \"y\": 156.12173461914062\n              },\n              {\n                \"x\": 2146.36376953125,\n                \"y\": 146.32574462890625\n              },\n              {\n                \"x\": 2305.548828125,\n                \"y\": 244.28573608398438\n              }\n            ],\n            \"notes\": [],\n            \"routing\": \"orthogonal\"\n          }\n        ]\n      },\n      \"positions\": {\n        \"core-router-1\": {\n          \"x\": 3720.166015625,\n          \"y\": 245.9932403564453\n        },\n        \"core-router-2\": {\n          \"x\": 2499.883407638303,\n          \"y\": 329.99503430389154\n        },\n        \"fw-external-1\": {\n          \"x\": 3221.7385182723783,\n          \"y\": 1016.1364499992887\n        },\n        \"fw-external-2\": {\n          \"x\": 1915.5213706410505,\n          \"y\": 224.43528858865443\n        },\n        \"fw-internal\": {\n          \"x\": 1746.9168185079352,\n          \"y\": 477.5300527221864\n        },\n        \"core-switch-1\": {\n          \"x\": 449.39860669455675,\n          \"y\": 384.4578707617695\n        },\n        \"core-switch-2\": {\n          \"x\": 761.1664921394672,\n          \"y\": 180.89283910873155\n        },\n        \"dc-rack-a1\": {\n          \"x\": 783.7017241128451,\n          \"y\": 647.4086870405963\n        },\n        \"dc-rack-a2\": {\n          \"x\": 209.25701628255229,\n          \"y\": 228.01593190351014\n        },\n        \"dc-rack-b1\": {\n          \"x\": 3184.3186625759854,\n          \"y\": 1627.4495531027196\n        },\n        \"dc-rack-b2\": {\n          \"x\": 245.37065918741246,\n          \"y\": 499.6191264194081\n        },\n        \"dmz-rack\": {\n          \"x\": 2176.4105289561007,\n          \"y\": 610.8312056412005\n        },\n        \"mgmt-rack\": {\n          \"x\": 1601.2987201807314,\n          \"y\": 1281.4753424975324\n        },\n        \"esxi-host-01\": {\n          \"x\": 2162.2166789540615,\n          \"y\": 2608.110619289529\n        },\n        \"esxi-host-02\": {\n          \"x\": 2205.94717202368,\n          \"y\": 2689.67539624076\n        },\n        \"esxi-host-03\": {\n          \"x\": 2154.6015436939074,\n          \"y\": 2771.203009774913\n        },\n        \"esxi-host-04\": {\n          \"x\": 2195.986926025096,\n          \"y\": 2845\n        },\n        \"tor-switch-a1\": {\n          \"x\": 2146.8943639962963,\n          \"y\": 2845\n        },\n        \"esxi-host-05\": {\n          \"x\": 2185.9099961569727,\n          \"y\": 2845\n        },\n        \"esxi-host-06\": {\n          \"x\": 2139.099728450725,\n          \"y\": 2845\n        },\n        \"esxi-host-07\": {\n          \"x\": 2175.7223818764883,\n          \"y\": 2845\n        },\n        \"esxi-host-08\": {\n          \"x\": 2131.2222777148922,\n          \"y\": 2845\n        },\n        \"tor-switch-a2\": {\n          \"x\": 2165.4301485385085,\n          \"y\": 2845\n        },\n        \"san-primary\": {\n          \"x\": 2123.2667017518106,\n          \"y\": 2845\n        },\n        \"san-secondary\": {\n          \"x\": 2155.0394237844876,\n          \"y\": 2845\n        },\n        \"fc-switch-1\": {\n          \"x\": 2115.2377370375634,\n          \"y\": 2845\n        },\n        \"fc-switch-2\": {\n          \"x\": 2144.5563938942755,\n          \"y\": 2845\n        },\n        \"backup-server-1\": {\n          \"x\": 2107.1401637413705,\n          \"y\": 2845\n        },\n        \"backup-server-2\": {\n          \"x\": 2133.987300103025,\n          \"y\": 2845\n        },\n        \"tape-library\": {\n          \"x\": 2098.9788028796397,\n          \"y\": 2845\n        },\n        \"tor-switch-b1\": {\n          \"x\": 2123.338434885373,\n          \"y\": 2845\n        },\n        \"tor-switch-b2\": {\n          \"x\": 2090.7585134456995,\n          \"y\": 2845\n        },\n        \"web-server-1\": {\n          \"x\": 2112.6161382091163,\n          \"y\": 2845\n        },\n        \"web-server-2\": {\n          \"x\": 2082.484189516922,\n          \"y\": 2845\n        },\n        \"waf-1\": {\n          \"x\": 2101.826793760617,\n          \"y\": 2845\n        },\n        \"load-balancer-dmz\": {\n          \"x\": 2074.1607573409574,\n          \"y\": 2845\n        },\n        \"mail-gateway\": {\n          \"x\": 2090.97682514417,\n          \"y\": 2845\n        },\n        \"dns-external-1\": {\n          \"x\": 2065.7931724028163,\n          \"y\": 2845\n        },\n        \"dns-external-2\": {\n          \"x\": 2080.0726920576153,\n          \"y\": 2845\n        },\n        \"vcenter\": {\n          \"x\": 2057.3864164745437,\n          \"y\": 2845\n        },\n        \"nsx-manager\": {\n          \"x\": 2069.1208864464534,\n          \"y\": 2845\n        },\n        \"siem-server\": {\n          \"x\": 2048.945494649244,\n          \"y\": 2845\n        },\n        \"nms-server\": {\n          \"x\": 2058.1279286387635,\n          \"y\": 2845\n        },\n        \"jump-server\": {\n          \"x\": 2040.4754323612206,\n          \"y\": 2845\n        },\n        \"ipam-server\": {\n          \"x\": 2047.1003634632284,\n          \"y\": 2845\n        },\n        \"wlc-primary\": {\n          \"x\": 1575.9723612611924,\n          \"y\": 2306.135986328125\n        },\n        \"wlc-secondary\": {\n          \"x\": 1468.1361870166274,\n          \"y\": 1563.733642578125\n        },\n        \"mobile-zone-hq\": {\n          \"x\": 2354.901177346808,\n          \"y\": 2806.0078125\n        },\n        \"mobile-zone-guest\": {\n          \"x\": 2307.6605605284435,\n          \"y\": 2611.047119140625\n        },\n        \"mobile-zone-iot\": {\n          \"x\": 2229.397686389302,\n          \"y\": 2299.110107421875\n        },\n        \"branch-router-ny\": {\n          \"x\": 3151.903101363964,\n          \"y\": 633.6580810546875\n        },\n        \"branch-router-la\": {\n          \"x\": 3083.8876194705945,\n          \"y\": 506.90625\n        },\n        \"branch-router-chi\": {\n          \"x\": 3355.02409980103,\n          \"y\": 393.1805725097656\n        },\n        \"branch-router-lon\": {\n          \"x\": 3113.609823320121,\n          \"y\": 260.4093322753906\n        },\n        \"branch-router-tokyo\": {\n          \"x\": 3699.3234994733834,\n          \"y\": 471.4241027832031\n        },\n        \"cloud-aws\": {\n          \"x\": 3436.528122523513,\n          \"y\": 545.9614868164062\n        },\n        \"cloud-azure\": {\n          \"x\": 2592.566210818907,\n          \"y\": 2724.068115234375\n        },\n        \"cloud-gcp\": {\n          \"x\": 2827.3183770424234,\n          \"y\": 2731.397216796875\n        },\n        \"isp-primary\": {\n          \"x\": 3712.192068081962,\n          \"y\": 615.64990234375\n        },\n        \"isp-secondary\": {\n          \"x\": 2702.3789772348055,\n          \"y\": 467.890869140625\n        },\n        \"dc-internal-1\": {\n          \"x\": 1958.4243458877936,\n          \"y\": 2845\n        },\n        \"dc-internal-2\": {\n          \"x\": 1963.768951182132,\n          \"y\": 2845\n        },\n        \"app-server-1\": {\n          \"x\": 1947.3819379304134,\n          \"y\": 2845\n        },\n        \"app-server-2\": {\n          \"x\": 1955.2862087394126,\n          \"y\": 2845\n        },\n        \"db-server-1\": {\n          \"x\": 1936.3708569559828,\n          \"y\": 2845\n        },\n        \"db-server-2\": {\n          \"x\": 1946.8300873488822,\n          \"y\": 2845\n        },\n        \"k8s-master-1\": {\n          \"x\": 1925.397658583093,\n          \"y\": 2845\n        },\n        \"k8s-master-2\": {\n          \"x\": 1938.405621494142,\n          \"y\": 2845\n        },\n        \"k8s-master-3\": {\n          \"x\": 1914.4688758763386,\n          \"y\": 2845\n        },\n        \"k8s-worker-1\": {\n          \"x\": 1930.017826812177,\n          \"y\": 2845\n        },\n        \"k8s-worker-2\": {\n          \"x\": 1903.5910154567553,\n          \"y\": 2845\n        },\n        \"k8s-worker-3\": {\n          \"x\": 1921.6716971072178,\n          \"y\": 2845\n        },\n        \"k8s-worker-4\": {\n          \"x\": 1892.7705536280016,\n          \"y\": 2845\n        },\n        \"proxy-server-1\": {\n          \"x\": 1806.1152433697903,\n          \"y\": 653.7529296875\n        },\n        \"proxy-server-2\": {\n          \"x\": 2937.4207928721535,\n          \"y\": 2628.7880859375\n        },\n        \"vpn-concentrator\": {\n          \"x\": 3642.252088474593,\n          \"y\": 946.7255249023438\n        },\n        \"nac-server\": {\n          \"x\": 1153.2626148502184,\n          \"y\": 1172.1895751953125\n        },\n        \"print-server\": {\n          \"x\": 1896.9328460745962,\n          \"y\": 2845\n        },\n        \"file-server\": {\n          \"x\": 1860.7177871362182,\n          \"y\": 2845\n        },\n        \"ca-server\": {\n          \"x\": 1888.8027739274805,\n          \"y\": 2845\n        },\n        \"sccm-server\": {\n          \"x\": 1850.1909418511675,\n          \"y\": 2845\n        },\n        \"voip-cluster\": {\n          \"x\": 1777.038465328039,\n          \"y\": 1616.8961181640625\n        },\n        \"video-conf\": {\n          \"x\": 1993.8373941679588,\n          \"y\": 2244.936309814453\n        },\n        \"security-cameras\": {\n          \"x\": 1674.413336949044,\n          \"y\": 2046.0380859375\n        },\n        \"nvr-cluster\": {\n          \"x\": 1829.4110389706402,\n          \"y\": 2845\n        },\n        \"dev-server-1\": {\n          \"x\": 2800.5894350649614,\n          \"y\": 1175.623291015625\n        },\n        \"dev-server-2\": {\n          \"x\": 1945.0822182484326,\n          \"y\": 1164.5184783935547\n        },\n        \"test-environment\": {\n          \"x\": 2566.9100352578575,\n          \"y\": 885.2827758789062\n        },\n        \"erp-system\": {\n          \"x\": 789.9880103985649,\n          \"y\": 473.7113342285156\n        },\n        \"crm-system\": {\n          \"x\": 3514.6003232048542,\n          \"y\": 1137.7720947265625\n        },\n        \"endpoint-1000\": {\n          \"x\": 991.6812012057328,\n          \"y\": 2284.42236328125\n        },\n        \"dist-switch-floor1\": {\n          \"x\": 654.2091033261356,\n          \"y\": 2020.0086669921875\n        },\n        \"dist-switch-floor2\": {\n          \"x\": 853.8845527112826,\n          \"y\": 1843.2872314453125\n        },\n        \"dist-switch-floor3\": {\n          \"x\": 1899.4353951584517,\n          \"y\": 1456.5068359375\n        },\n        \"dist-switch-floor4\": {\n          \"x\": 488.5289313756234,\n          \"y\": 181.47256469726562\n        },\n        \"ap-floor1-zone1\": {\n          \"x\": 1140.16846970184,\n          \"y\": 2070.2916259765625\n        },\n        \"ap-floor2-zone1\": {\n          \"x\": 688.1952143592268,\n          \"y\": 2384.4775390625\n        },\n        \"ap-floor3-zone1\": {\n          \"x\": 2145.3803027919676,\n          \"y\": 1890.2816162109375\n        },\n        \"ap-floor4-zone1\": {\n          \"x\": 517.646146409649,\n          \"y\": 565.59716796875\n        },\n        \"ups-dc-1\": {\n          \"x\": 771.1406786539856,\n          \"y\": 295.9266662597656\n        },\n        \"ups-dc-2\": {\n          \"x\": 216.2410855890687,\n          \"y\": 330.3345947265625\n        },\n        \"pdu-rack-a1\": {\n          \"x\": 1804.774444371901,\n          \"y\": 2845\n        },\n        \"pdu-rack-a2\": {\n          \"x\": 1741.6184034693686,\n          \"y\": 2845\n        },\n        \"cooling-1\": {\n          \"x\": 245.7080801919958,\n          \"y\": 626.1914672851562\n        },\n        \"cooling-2\": {\n          \"x\": 1603.293611085831,\n          \"y\": 981.0621185302734\n        },\n        \"camera-a\": {\n          \"x\": 166.57075412676295,\n          \"y\": 145\n        },\n        \"camera-a-copy\": {\n          \"x\": 1040.653076171875,\n          \"y\": 738.42822265625\n        }\n      },\n      \"sizes\": {\n        \"isp-secondary\": 139,\n        \"test-environment\": 148,\n        \"dev-server-1\": 128,\n        \"core-router-2\": 120,\n        \"camera-a\": 45,\n        \"camera-a-copy\": 45\n      },\n      \"styles\": {\n        \"dc-rack-b2\": {\n          \"all\": {\n            \"circleColor\": \"#ff0000\"\n          }\n        },\n        \"dc-rack-a1\": {\n          \"all\": {\n            \"circleColor\": \"#ff0000\"\n          }\n        },\n        \"dc-rack-b1\": {\n          \"all\": {\n            \"circleColor\": \"#ff0000\",\n            \"titleSize\": 59\n          }\n        },\n        \"isp-secondary\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"alist\"\n            }\n          }\n        },\n        \"core-router-2\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"actual-budget\"\n            },\n            \"pingOffsetX\": -15,\n            \"pingOffsetY\": -38\n          }\n        },\n        \"fw-external-1\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"anonaddy\"\n            }\n          }\n        },\n        \"cloud-aws\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"ansible\"\n            }\n          }\n        },\n        \"isp-primary\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"wikidocs\"\n            }\n          }\n        },\n        \"branch-router-tokyo\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"adguard-home\"\n            }\n          }\n        },\n        \"core-router-1\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"borg\"\n            }\n          }\n        },\n        \"test-environment\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"simple\",\n              \"name\": \"apple\"\n            }\n          }\n        },\n        \"dev-server-1\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"simple\",\n              \"name\": \"amazonwebservices\"\n            }\n          }\n        }\n      },\n      \"legend\": {\n        \"#10b981\": \"Trusted Lan\",\n        \"#f59e0b\": \"Secure Lan\",\n        \"#ef4444\": \"DMZ\",\n        \"#475569\": \"Main ISP\",\n        \"#3b82f6\": \"Alternate ISP\",\n        \"#8b5cf6\": \"you can edit me too\",\n        \"#06b6d4\": \"you can edit me too\",\n        \"#a855f7\": \"you can edit me too\",\n        \"#f97316\": \"you can edit me too\",\n        \"#0ea5e9\": \"you can edit me too\",\n        \"#22c55e\": \"you can edit me too\",\n        \"#94a3b8\": \"you can edit me too\",\n        \"#fbbf24\": \"you can edit me too\",\n        \"#38bdf8\": \"you can edit me too\",\n        \"#c800ff\": \"you can edit me too\"\n      },\n      \"rects\": {\n        \"list\": [\n          {\n            \"id\": \"rect-1765237540610\",\n            \"x\": 2879.214599609375,\n            \"y\": 159.71981811523438,\n            \"width\": 992.196044921875,\n            \"height\": 538.8650817871094,\n            \"color\": \"#f97316\",\n            \"style\": \"filled\",\n            \"lineStyle\": \"solid\",\n            \"notes\": []\n          },\n          {\n            \"id\": \"rect-1765237681216\",\n            \"x\": 448.3926696777344,\n            \"y\": 1671.651123046875,\n            \"width\": 916.3436584472656,\n            \"height\": 924.27734375,\n            \"color\": \"#c800ff\",\n            \"style\": \"outlined\",\n            \"lineStyle\": \"solid\",\n            \"notes\": []\n          },\n          {\n            \"id\": \"rect-1766437913740\",\n            \"x\": 904.5889892578125,\n            \"y\": 115.40318298339844,\n            \"width\": 110.93878173828125,\n            \"height\": 919.6242218017578,\n            \"color\": \"#5215f9\",\n            \"style\": \"filled\",\n            \"lineStyle\": \"wall\",\n            \"notes\": [],\n            \"borderWidth\": 13\n          },\n          {\n            \"id\": \"rect-1766437935414\",\n            \"x\": 130.93685150146484,\n            \"y\": 1072.3624877929688,\n            \"width\": 872.9131851196289,\n            \"height\": 99.260986328125,\n            \"color\": \"#5215f9\",\n            \"style\": \"filled\",\n            \"lineStyle\": \"wall\",\n            \"notes\": [],\n            \"borderWidth\": 13\n          }\n        ]\n      },\n      \"texts\": {\n        \"list\": [\n          {\n            \"id\": \"text-1765237828167\",\n            \"x\": 3411.458740234375,\n            \"y\": 1390.00439453125,\n            \"content\": \"Double click on desktop\\nor long press on mobile\\nto enter rack canvas view\",\n            \"fontSize\": 46,\n            \"color\": \"#e2e8f0\",\n            \"fontWeight\": \"bold\",\n            \"fontStyle\": \"italic\",\n            \"textAlign\": \"middle\",\n            \"textDecoration\": \"none\",\n            \"bgColor\": \"#000000\",\n            \"bgEnabled\": false,\n            \"opacity\": 1\n          },\n          {\n            \"id\": \"text-1765239331126\",\n            \"x\": 2454.5615234375,\n            \"y\": 160.73322105407715,\n            \"content\": \"Google is live!\",\n            \"fontSize\": 56,\n            \"color\": \"#e2e8f0\",\n            \"fontWeight\": \"bold\",\n            \"fontStyle\": \"normal\",\n            \"textAlign\": \"start\",\n            \"textDecoration\": \"none\",\n            \"bgColor\": \"#000000\",\n            \"bgEnabled\": false,\n            \"opacity\": 1\n          },\n          {\n            \"id\": \"text-1766446595277\",\n            \"x\": 654.3878479003906,\n            \"y\": 1367.7945556640625,\n            \"content\": \"SITE A\",\n            \"fontSize\": 101,\n            \"color\": \"#e2e8f0\",\n            \"fontWeight\": \"normal\",\n            \"fontStyle\": \"normal\",\n            \"textAlign\": \"start\",\n            \"textDecoration\": \"none\",\n            \"bgColor\": \"#000000\",\n            \"bgEnabled\": false,\n            \"opacity\": 1\n          },\n          {\n            \"id\": \"text-1766446610211\",\n            \"x\": 180.63662719726562,\n            \"y\": 1128.822998046875,\n            \"content\": \"SITE A\",\n            \"fontSize\": 101,\n            \"color\": \"#e2e8f0\",\n            \"fontWeight\": \"normal\",\n            \"fontStyle\": \"normal\",\n            \"textAlign\": \"start\",\n            \"textDecoration\": \"none\",\n            \"bgColor\": \"#000000\",\n            \"bgEnabled\": false,\n            \"opacity\": 1\n          },\n          {\n            \"id\": \"text-1766453024797\",\n            \"x\": 968.6458740234375,\n            \"y\": 1028.6621398925781,\n            \"content\": \"SITE A\",\n            \"fontSize\": 101,\n            \"color\": \"#e2e8f0\",\n            \"fontWeight\": \"normal\",\n            \"fontStyle\": \"normal\",\n            \"textAlign\": \"start\",\n            \"textDecoration\": \"none\",\n            \"bgColor\": \"#000000\",\n            \"bgEnabled\": false,\n            \"opacity\": 1,\n            \"rotation\": -89,\n            \"_dragStartX\": 972.46826171875,\n            \"_dragStartY\": 1009.5499572753906\n          },\n          {\n            \"id\": \"text-1766453070975\",\n            \"x\": 613.1589965820312,\n            \"y\": 1139.512939453125,\n            \"content\": \"SITE A\",\n            \"fontSize\": 101,\n            \"color\": \"#e2e8f0\",\n            \"fontWeight\": \"normal\",\n            \"fontStyle\": \"normal\",\n            \"textAlign\": \"start\",\n            \"textDecoration\": \"none\",\n            \"bgColor\": \"#000000\",\n            \"bgEnabled\": false,\n            \"opacity\": 1\n          },\n          {\n            \"id\": \"text-1766453072857\",\n            \"x\": 968.64599609375,\n            \"y\": 474.40818786621094,\n            \"content\": \"SITE A\",\n            \"fontSize\": 101,\n            \"color\": \"#e2e8f0\",\n            \"fontWeight\": \"normal\",\n            \"fontStyle\": \"normal\",\n            \"textAlign\": \"start\",\n            \"textDecoration\": \"none\",\n            \"bgColor\": \"#000000\",\n            \"bgEnabled\": false,\n            \"opacity\": 1,\n            \"rotation\": 269,\n            \"_dragStartX\": 1480.85302734375,\n            \"_dragStartY\": 822.2503356933594\n          }\n        ]\n      },\n      \"pageState\": {\n        \"title\": \"The One File Corporate\",\n        \"background\": \"\",\n        \"topbarBg\": \"rgba(9, 12, 20, 0.9)\",\n        \"topbarBorder\": \"#1f2533\",\n        \"panel\": \"#0b0e13\",\n        \"panelAlt\": \"#10141b\",\n        \"accent\": \"#4fd1c5\",\n        \"sidebarBg\": \"#10141b\",\n        \"btnBg\": \"#0b0e13\",\n        \"btnText\": \"#e2e8f0\",\n        \"tagFill\": \"#1e293b\",\n        \"tagText\": \"#e2e8f0\",\n        \"tagBorder\": \"#475569\",\n        \"inputBg\": \"#0b0e13\",\n        \"inputText\": \"#e2e8f0\",\n        \"inputBorder\": \"#1f2937\",\n        \"inputFont\": \"Inter, system-ui, sans-serif\",\n        \"inputFontSize\": 14,\n        \"toolbarBg\": \"#0f172a\",\n        \"toolbarBorder\": \"#1f2937\",\n        \"toolbarText\": \"#94a3b8\",\n        \"toolbarBtnBg\": \"#0b0e13\",\n        \"toolbarBtnText\": \"#e2e8f0\",\n        \"minimapDots\": \"#94a3b8\",\n        \"canvasHintEnabled\": true,\n        \"canvasHintText\": \"\",\n        \"canvasHintBg\": \"#0f172a\",\n        \"canvasHintColor\": \"#94a3b8\",\n        \"danger\": \"#f56565\",\n        \"textMain\": \"#e2e8f0\",\n        \"textSoft\": \"#94a3b8\",\n        \"topbarHeight\": 103,\n        \"sidebarWidth\": 350,\n        \"mobileFooterHeight\": 40,\n        \"sidebarCollapsed\": false,\n        \"nodeFill\": \"#1e293b\",\n        \"nodeStroke\": \"#475569\",\n        \"nodeTitle\": \"#e2e8f0\",\n        \"nodeSub\": \"#94a3b8\",\n        \"nodeTitleSize\": 41,\n        \"nodeSubSize\": 27,\n        \"nodeFont\": \"monospace\",\n        \"defaultEdge\": \"#475569\",\n        \"selectionHandle\": \"#f59e0b\",\n        \"selectionHandleSize\": 8,\n        \"groupIndicator\": \"#4fd1c5\",\n        \"canvasGradientTop\": \"#1e2532\",\n        \"canvasGradientBottom\": \"#050608\",\n        \"canvasBorder\": \"#475569\",\n        \"canvasGrid\": \"#475569\",\n        \"canvasGridSize\": 50,\n        \"canvasGridEnabled\": true,\n        \"rackFrameFill\": \"#0f172a\",\n        \"rackFrameStroke\": \"#4fd1c5\",\n        \"rackLineColor\": \"#475569\",\n        \"rackTextColor\": \"#4fd1c5\",\n        \"rackGridEnabled\": true,\n        \"viewOnly\": false,\n        \"defaultEdgeRouting\": \"orthogonal\",\n        \"animateConnections\": false,\n        \"animationStyle\": \"arrows\",\n        \"animationDirection\": \"all\",\n        \"animationSpeed\": 4,\n        \"autoPingEnabled\": false,\n        \"autoPingInterval\": 30\n      }\n    },\n    {\n      \"id\": \"tab-1765235136918\",\n      \"name\": \"Homelab 2\",\n      \"nodes\": {\n        \"internet\": {\n          \"shape\": \"square\",\n          \"name\": \"Internet\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"internet-copy\": {\n          \"shape\": \"firewall\",\n          \"name\": \"OPNSENSE\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"opnsense-copy\": {\n          \"shape\": \"firewall\",\n          \"name\": \"Docker\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"docker-copy\": {\n          \"shape\": \"firewall\",\n          \"name\": \"Docker2\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"docker-copy-1\": {\n          \"shape\": \"firewall\",\n          \"name\": \"Docker3\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"docker-copy-2\": {\n          \"shape\": \"firewall\",\n          \"name\": \"Docker 4\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [\n            {\n              \"type\": \"icon\",\n              \"library\": \"selfhst\",\n              \"name\": \"docker\"\n            },\n            {\n              \"type\": \"icon\",\n              \"library\": \"selfhst\",\n              \"name\": \"authentik\"\n            },\n            {\n              \"type\": \"icon\",\n              \"library\": \"selfhst\",\n              \"name\": \"immich\"\n            }\n          ],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"opnsense-copy-1\": {\n          \"shape\": \"firewall\",\n          \"name\": \"OPNSENSE GUEST\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"phone\": {\n          \"shape\": \"phone\",\n          \"name\": \"Phone\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"desktop\": {\n          \"shape\": \"pc\",\n          \"name\": \"Desktop\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"dns\": {\n          \"shape\": \"cloud\",\n          \"name\": \"DNS\",\n          \"ip\": \"0.0.0.0\",\n          \"role\": \"\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": false,\n          \"locked\": false,\n          \"groupId\": null\n        },\n        \"racked\": {\n          \"shape\": \"server\",\n          \"name\": \"Racked\",\n          \"ip\": \"\",\n          \"role\": \"Rack\",\n          \"tags\": [],\n          \"notes\": [],\n          \"mac\": \"\",\n          \"rackUnit\": \"\",\n          \"uHeight\": \"1\",\n          \"layer\": \"physical\",\n          \"assignedRack\": \"\",\n          \"rackCapacity\": \"42\",\n          \"isRack\": true,\n          \"locked\": false,\n          \"groupId\": null\n        }\n      },\n      \"edges\": {\n        \"list\": [\n          {\n            \"id\": \"internet-internet-copy-1765238145151\",\n            \"from\": \"internet\",\n            \"to\": \"internet-copy\",\n            \"width\": 4,\n            \"color\": \"#55e208\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1\n          },\n          {\n            \"id\": \"internet-copy-opnsense-copy-1765238187451\",\n            \"from\": \"internet-copy\",\n            \"to\": \"opnsense-copy\",\n            \"width\": 4,\n            \"color\": \"#4c00ff\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1\n          },\n          {\n            \"id\": \"internet-copy-docker-copy-1765238242477\",\n            \"from\": \"internet-copy\",\n            \"to\": \"docker-copy\",\n            \"width\": 4,\n            \"color\": \"#4c00ff\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1\n          },\n          {\n            \"id\": \"internet-copy-docker-copy-1-1765238244637\",\n            \"from\": \"internet-copy\",\n            \"to\": \"docker-copy-1\",\n            \"width\": 4,\n            \"color\": \"#4c00ff\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1\n          },\n          {\n            \"id\": \"internet-copy-docker-copy-2-1765238246233\",\n            \"from\": \"internet-copy\",\n            \"to\": \"docker-copy-2\",\n            \"width\": 4,\n            \"color\": \"#4c00ff\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1\n          },\n          {\n            \"id\": \"internet-opnsense-copy-1-1765238266117\",\n            \"from\": \"internet\",\n            \"to\": \"opnsense-copy-1\",\n            \"width\": 4,\n            \"color\": \"#80ff00\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1\n          },\n          {\n            \"id\": \"opnsense-copy-1-dns-1765238347996\",\n            \"from\": \"opnsense-copy-1\",\n            \"to\": \"dns\",\n            \"width\": 4,\n            \"color\": \"#fb00ff\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1\n          },\n          {\n            \"id\": \"dns-desktop-1765238386101\",\n            \"from\": \"dns\",\n            \"to\": \"desktop\",\n            \"width\": 4,\n            \"color\": \"#ff00d0\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1\n          },\n          {\n            \"id\": \"phone-dns-1765238391156\",\n            \"from\": \"phone\",\n            \"to\": \"dns\",\n            \"width\": 4,\n            \"color\": \"#ff00d0\",\n            \"direction\": \"both\",\n            \"type\": \"main\",\n            \"notes\": [],\n            \"fromPort\": \"\",\n            \"toPort\": \"\",\n            \"lineStyle\": \"solid\",\n            \"_pairIndex\": 0,\n            \"_pairTotal\": 1\n          },\n          {\n            \"id\": \"custom-1765239449323\",\n            \"type\": \"custom\",\n            \"color\": \"#f97316\",\n            \"width\": 4,\n            \"lineStyle\": \"solid\",\n            \"direction\": \"forward\",\n            \"points\": [\n              {\n                \"x\": 2936.464111328125,\n                \"y\": 786.07958984375\n              },\n              {\n                \"x\": 3184.112060546875,\n                \"y\": 887.6153564453125\n              },\n              {\n                \"x\": 2763.110107421875,\n                \"y\": 981.7216796875\n              }\n            ],\n            \"notes\": []\n          }\n        ]\n      },\n      \"positions\": {\n        \"internet\": {\n          \"x\": 2103.968290880771,\n          \"y\": 268\n        },\n        \"internet-copy\": {\n          \"x\": 2066.9677515897347,\n          \"y\": 473.4119134177565\n        },\n        \"opnsense-copy\": {\n          \"x\": 1773.8400660428597,\n          \"y\": 666.5758233298659\n        },\n        \"docker-copy\": {\n          \"x\": 1931.1978950081452,\n          \"y\": 782.2775961320921\n        },\n        \"docker-copy-1\": {\n          \"x\": 2158.1262397347077,\n          \"y\": 767.7122274797483\n        },\n        \"docker-copy-2\": {\n          \"x\": 2342.2663764534577,\n          \"y\": 631.7681967180296\n        },\n        \"opnsense-copy-1\": {\n          \"x\": 2757.879480087803,\n          \"y\": 307.6117116091891\n        },\n        \"phone\": {\n          \"x\": 3312.857751572178,\n          \"y\": 502.58220111114224\n        },\n        \"desktop\": {\n          \"x\": 2971.700036728428,\n          \"y\": 480.7287465212985\n        },\n        \"dns\": {\n          \"x\": 3200.4643189549906,\n          \"y\": 320.469591247861\n        },\n        \"racked\": {\n          \"x\": 2645.5845448279656,\n          \"y\": 970.7820678889219\n        }\n      },\n      \"sizes\": {\n        \"core-router-1\": 36,\n        \"internet\": 168,\n        \"phone\": 121,\n        \"desktop\": 147,\n        \"racked\": 137,\n        \"docker-copy-2\": 82\n      },\n      \"styles\": {\n        \"internet\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"amazon-web-services\"\n            },\n            \"circleColor\": \"#ffffff\",\n            \"circleBorder\": \"#ffffff\"\n          }\n        },\n        \"opnsense-copy-1\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"opnsense-v1\"\n            }\n          }\n        },\n        \"internet-copy\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"opnsense\"\n            }\n          }\n        },\n        \"docker-copy-2\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"docker\"\n            }\n          }\n        },\n        \"docker-copy-1\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"authportal\"\n            }\n          }\n        },\n        \"docker-copy\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"jotty\"\n            }\n          }\n        },\n        \"opnsense-copy\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"selfhst\",\n              \"name\": \"portainer\"\n            }\n          }\n        },\n        \"racked\": {\n          \"all\": {\n            \"icon\": {\n              \"library\": \"mdi\",\n              \"name\": \"server-security\"\n            },\n            \"circleColor\": \"#010813\",\n            \"circleBorder\": \"#ffffff\"\n          }\n        }\n      },\n      \"legend\": {\n        \"#475569\": \"you can edit me too\",\n        \"#65758b\": \"you can edit me too\",\n        \"#63748c\": \"you can edit me too\",\n        \"#5e6f87\": \"you can edit me too\",\n        \"#586a84\": \"you can edit me too\",\n        \"#4f627d\": \"you can edit me too\",\n        \"#455873\": \"you can edit me too\",\n        \"#3d506c\": \"you can edit me too\",\n        \"#354964\": \"you can edit me too\",\n        \"#2e415c\": \"you can edit me too\",\n        \"#293c56\": \"you can edit me too\",\n        \"#273a53\": \"you can edit me too\",\n        \"#253750\": \"you can edit me too\",\n        \"#23354d\": \"you can edit me too\",\n        \"#203046\": \"you can edit me too\",\n        \"#1e2d43\": \"you can edit me too\",\n        \"#1a283d\": \"you can edit me too\",\n        \"#172435\": \"you can edit me too\",\n        \"#141f2e\": \"you can edit me too\",\n        \"#111a27\": \"you can edit me too\",\n        \"#0f1824\": \"you can edit me too\",\n        \"#0d1521\": \"you can edit me too\",\n        \"#0c131d\": \"you can edit me too\",\n        \"#0c1d1c\": \"you can edit me too\",\n        \"#0c1c1d\": \"you can edit me too\",\n        \"#0c191d\": \"you can edit me too\",\n        \"#0c141d\": \"you can edit me too\",\n        \"#0c0d1d\": \"you can edit me too\",\n        \"#130c1d\": \"you can edit me too\",\n        \"#1b0c1d\": \"you can edit me too\",\n        \"#1d0c17\": \"you can edit me too\",\n        \"#1d0c10\": \"you can edit me too\",\n        \"#1d0c0c\": \"you can edit me too\",\n        \"#3b1b1b\": \"you can edit me too\",\n        \"#3c1a1a\": \"you can edit me too\",\n        \"#3f1c1c\": \"you can edit me too\",\n        \"#401c1c\": \"you can edit me too\",\n        \"#451c1c\": \"you can edit me too\",\n        \"#461b1b\": \"you can edit me too\",\n        \"#4c1a1a\": \"you can edit me too\",\n        \"#521919\": \"you can edit me too\",\n        \"#571919\": \"you can edit me too\",\n        \"#5d1818\": \"you can edit me too\",\n        \"#631717\": \"you can edit me too\",\n        \"#651515\": \"you can edit me too\",\n        \"#6a1616\": \"you can edit me too\",\n        \"#6f1515\": \"you can edit me too\",\n        \"#711414\": \"you can edit me too\",\n        \"#761414\": \"you can edit me too\",\n        \"#771313\": \"you can edit me too\",\n        \"#7c1313\": \"you can edit me too\",\n        \"#811313\": \"you can edit me too\",\n        \"#821212\": \"you can edit me too\",\n        \"#871212\": \"you can edit me too\",\n        \"#881111\": \"you can edit me too\",\n        \"#8d1111\": \"you can edit me too\",\n        \"#8e1010\": \"you can edit me too\",\n        \"#8f0f0f\": \"you can edit me too\",\n        \"#900e0e\": \"you can edit me too\",\n        \"#8e0b0b\": \"you can edit me too\",\n        \"#8c0d0d\": \"you can edit me too\",\n        \"#880c0c\": \"you can edit me too\",\n        \"#830c0c\": \"you can edit me too\",\n        \"#7e0c0c\": \"you can edit me too\",\n        \"#790c0c\": \"you can edit me too\",\n        \"#730c0c\": \"you can edit me too\",\n        \"#6f0b0b\": \"you can edit me too\",\n        \"#0b6f64\": \"you can edit me too\",\n        \"#0b6f5f\": \"you can edit me too\",\n        \"#0b6f56\": \"you can edit me too\",\n        \"#0b6f49\": \"you can edit me too\",\n        \"#0b6f31\": \"you can edit me too\",\n        \"#0b6f1f\": \"you can edit me too\",\n        \"#0b6f0d\": \"you can edit me too\",\n        \"#176f0b\": \"you can edit me too\",\n        \"#266f0b\": \"you can edit me too\",\n        \"#296f0b\": \"you can edit me too\",\n        \"#2e6f0b\": \"you can edit me too\",\n        \"#1a2d10\": \"you can edit me too\",\n        \"#1c3111\": \"you can edit me too\",\n        \"#213814\": \"you can edit me too\",\n        \"#233c15\": \"you can edit me too\",\n        \"#254017\": \"you can edit me too\",\n        \"#294918\": \"you can edit me too\",\n        \"#2b4d1a\": \"you can edit me too\",\n        \"#2d511a\": \"you can edit me too\",\n        \"#315a1b\": \"you can edit me too\",\n        \"#35631c\": \"you can edit me too\",\n        \"#37681d\": \"you can edit me too\",\n        \"#3b721d\": \"you can edit me too\",\n        \"#3f7b1e\": \"you can edit me too\",\n        \"#42851e\": \"you can edit me too\",\n        \"#46901d\": \"you can edit me too\",\n        \"#499a1d\": \"you can edit me too\",\n        \"#4b9f1d\": \"you can edit me too\",\n        \"#4ca61c\": \"you can edit me too\",\n        \"#50b01c\": \"you can edit me too\",\n        \"#51b71a\": \"you can edit me too\",\n        \"#50b918\": \"you can edit me too\",\n        \"#51c115\": \"you can edit me too\",\n        \"#53c615\": \"you can edit me too\",\n        \"#53c814\": \"you can edit me too\",\n        \"#52c913\": \"you can edit me too\",\n        \"#54d011\": \"you can edit me too\",\n        \"#53d110\": \"you can edit me too\",\n        \"#55d510\": \"you can edit me too\",\n        \"#55d70f\": \"you can edit me too\",\n        \"#54d80e\": \"you can edit me too\",\n        \"#54da0b\": \"you can edit me too\",\n        \"#56df0c\": \"you can edit me too\",\n        \"#53db0a\": \"you can edit me too\",\n        \"#55e00b\": \"you can edit me too\",\n        \"#55e109\": \"you can edit me too\",\n        \"#55e208\": \"ISP LINE\",\n        \"#4c00ff\": \"MY Guest NETWORK\",\n        \"#80ff00\": \"you can edit me too\",\n        \"#3b4234\": \"you can edit me too\",\n        \"#3a3442\": \"you can edit me too\",\n        \"#3b3442\": \"you can edit me too\",\n        \"#3c3442\": \"you can edit me too\",\n        \"#3d3442\": \"you can edit me too\",\n        \"#3e3442\": \"you can edit me too\",\n        \"#3f3442\": \"you can edit me too\",\n        \"#403442\": \"you can edit me too\",\n        \"#413442\": \"you can edit me too\",\n        \"#653d66\": \"you can edit me too\",\n        \"#683f69\": \"you can edit me too\",\n        \"#6c416c\": \"you can edit me too\",\n        \"#6f4370\": \"you can edit me too\",\n        \"#704270\": \"you can edit me too\",\n        \"#734474\": \"you can edit me too\",\n        \"#784479\": \"you can edit me too\",\n        \"#7d447e\": \"you can edit me too\",\n        \"#7e437f\": \"you can edit me too\",\n        \"#834384\": \"you can edit me too\",\n        \"#844285\": \"you can edit me too\",\n        \"#89418b\": \"you can edit me too\",\n        \"#8e428f\": \"you can edit me too\",\n        \"#904091\": \"you can edit me too\",\n        \"#923e93\": \"you can edit me too\",\n        \"#973e98\": \"you can edit me too\",\n        \"#943c96\": \"you can edit me too\",\n        \"#993c9a\": \"you can edit me too\",\n        \"#963a98\": \"you can edit me too\",\n        \"#973899\": \"you can edit me too\",\n        \"#99369b\": \"you can edit me too\",\n        \"#9a359c\": \"you can edit me too\",\n        \"#9b349d\": \"you can edit me too\",\n        \"#9d329f\": \"you can edit me too\",\n        \"#9e31a0\": \"you can edit me too\",\n        \"#a02fa2\": \"you can edit me too\",\n        \"#9d2d9f\": \"you can edit me too\",\n        \"#9f2ba1\": \"you can edit me too\",\n        \"#a129a3\": \"you can edit me too\",\n        \"#a327a5\": \"you can edit me too\",\n        \"#a525a7\": \"you can edit me too\",\n        \"#a723a9\": \"you can edit me too\",\n        \"#a921ab\": \"you can edit me too\",\n        \"#ab1fad\": \"you can edit me too\",\n        \"#ad1daf\": \"you can edit me too\",\n        \"#ae1cb0\": \"you can edit me too\",\n        \"#b019b3\": \"you can edit me too\",\n        \"#b118b4\": \"you can edit me too\",\n        \"#b316b6\": \"you can edit me too\",\n        \"#b816bb\": \"you can edit me too\",\n        \"#b514b8\": \"you can edit me too\",\n        \"#ba14bd\": \"you can edit me too\",\n        \"#b712ba\": \"you can edit me too\",\n        \"#bb13be\": \"you can edit me too\",\n        \"#b811bb\": \"you can edit me too\",\n        \"#be10c1\": \"you can edit me too\",\n        \"#bb0ebe\": \"you can edit me too\",\n        \"#bd0cc0\": \"you can edit me too\",\n        \"#be0bc1\": \"you can edit me too\",\n        \"#c108c4\": \"you can edit me too\",\n        \"#be06c1\": \"you can edit me too\",\n        \"#c103c4\": \"you can edit me too\",\n        \"#c301c6\": \"you can edit me too\",\n        \"#c400c7\": \"you can edit me too\",\n        \"#c900cc\": \"you can edit me too\",\n        \"#ce00d1\": \"you can edit me too\",\n        \"#d300d6\": \"you can edit me too\",\n        \"#d800db\": \"you can edit me too\",\n        \"#dd00e0\": \"you can edit me too\",\n        \"#e200e6\": \"you can edit me too\",\n        \"#ec00f0\": \"you can edit me too\",\n        \"#f100f5\": \"you can edit me too\",\n        \"#f600fa\": \"you can edit me too\",\n        \"#fb00ff\": \"you can edit me too\",\n        \"#ff00d0\": \"iPhone (always guest iPhone)\",\n        \"#f97316\": \"you can edit me too\"\n      },\n      \"rects\": {\n        \"list\": [\n          {\n            \"id\": \"rect-1765238219615\",\n            \"x\": 2680.053955078125,\n            \"y\": 251.44879150390625,\n            \"width\": 814.10400390625,\n            \"height\": 389.26678466796875,\n            \"color\": \"#ec0999\",\n            \"style\": \"filled\",\n            \"lineStyle\": \"solid\",\n            \"notes\": []\n          }\n        ]\n      },\n      \"texts\": {\n        \"list\": [\n          {\n            \"id\": \"text-1765238422602\",\n            \"x\": 2466.35986328125,\n            \"y\": 741.6801147460938,\n            \"content\": \"Double click on desktop\\nor long press on mobile\\nto enter rack canvas view\",\n            \"fontSize\": 40,\n            \"color\": \"#e2e8f0\",\n            \"fontWeight\": \"normal\",\n            \"fontStyle\": \"normal\",\n            \"textAlign\": \"start\",\n            \"textDecoration\": \"none\",\n            \"bgColor\": \"#000000\",\n            \"bgEnabled\": false,\n            \"opacity\": 1\n          }\n        ]\n      },\n      \"pageState\": {\n        \"title\": \"The One File\",\n        \"background\": \"\",\n        \"topbarBg\": \"rgba(9, 12, 20, 0.9)\",\n        \"topbarBorder\": \"#1f2533\",\n        \"panel\": \"#2f0e0e\",\n        \"panelAlt\": \"#10141b\",\n        \"accent\": \"#a75252\",\n        \"sidebarBg\": \"#10141b\",\n        \"btnBg\": \"#0b0e13\",\n        \"btnText\": \"#e2e8f0\",\n        \"tagFill\": \"#1e293b\",\n        \"tagText\": \"#e2e8f0\",\n        \"tagBorder\": \"#475569\",\n        \"inputBg\": \"#0b0e13\",\n        \"inputText\": \"#e2e8f0\",\n        \"inputBorder\": \"#1f2937\",\n        \"inputFont\": \"Inter, system-ui, sans-serif\",\n        \"inputFontSize\": 14,\n        \"toolbarBg\": \"#441215\",\n        \"toolbarBorder\": \"#1f2937\",\n        \"toolbarText\": \"#94a3b8\",\n        \"toolbarBtnBg\": \"#0b0e13\",\n        \"toolbarBtnText\": \"#e2e8f0\",\n        \"minimapDots\": \"#94a3b8\",\n        \"canvasHintEnabled\": true,\n        \"canvasHintText\": \"\",\n        \"canvasHintBg\": \"#0f172a\",\n        \"canvasHintColor\": \"#94a3b8\",\n        \"danger\": \"#f56565\",\n        \"textMain\": \"#e2e8f0\",\n        \"textSoft\": \"#94a3b8\",\n        \"topbarHeight\": 112,\n        \"sidebarWidth\": 350,\n        \"mobileFooterHeight\": 40,\n        \"sidebarCollapsed\": false,\n        \"nodeFill\": \"#1e293b\",\n        \"nodeStroke\": \"#475569\",\n        \"nodeTitle\": \"#e2e8f0\",\n        \"nodeSub\": \"#94a3b8\",\n        \"nodeTitleSize\": 18,\n        \"nodeSubSize\": 13,\n        \"nodeFont\": \"Inter, system-ui, sans-serif\",\n        \"defaultEdge\": \"#475569\",\n        \"selectionHandle\": \"#f59e0b\",\n        \"selectionHandleSize\": 8,\n        \"groupIndicator\": \"#4fd1c5\",\n        \"canvasGradientTop\": \"#1e2532\",\n        \"canvasGradientBottom\": \"#050608\",\n        \"canvasBorder\": \"#475569\",\n        \"canvasGrid\": \"#475569\",\n        \"canvasGridSize\": 50,\n        \"canvasGridEnabled\": true,\n        \"rackFrameFill\": \"#0f172a\",\n        \"rackFrameStroke\": \"#4fd1c5\",\n        \"rackLineColor\": \"#475569\",\n        \"rackTextColor\": \"#4fd1c5\",\n        \"rackGridEnabled\": true,\n        \"viewOnly\": false,\n        \"defaultEdgeRouting\": \"curved\",\n        \"animateConnections\": false,\n        \"animationStyle\": \"arrows\",\n        \"animationDirection\": \"all\",\n        \"animationSpeed\": 1.5\n      }\n    }\n  ],\n  \"currentTabIndex\": 1,\n  \"encryptedSections\": {},\n  \"auditLog\": [\n    {\n      \"timestamp\": 1766459511746,\n      \"type\": \"export\",\n      \"description\": \"Exported JSON: the-one-file.json\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766459504374,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file.html\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766459500911,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file.html\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766459497380,\n      \"type\": \"tab\",\n      \"description\": \"Switched to tab: Homelab 2\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766459491436,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file-corporate.html\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459483682,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file-corporate.html\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766459477676,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file-corporate.html\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766457578277,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766457564726,\n      \"type\": \"tab\",\n      \"description\": \"Switched to tab: Corporate Site B\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766457564253,\n      \"type\": \"tab\",\n      \"description\": \"Switched to tab: Homelab 2\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766457560309,\n      \"type\": \"import\",\n      \"description\": \"Imported JSON: the-one-file-corporate.json (107 nodes, 65 connections)\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455847368,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455844534,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455844054,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455843762,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455843560,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455843371,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455843162,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455842852,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455842747,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455842601,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455842449,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455842348,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455842098,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455841678,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455841236,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455841053,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455840901,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455840650,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455839427,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455839234,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455839061,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455837247,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455837081,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455836893,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455836377,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455836198,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455835455,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455834630,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455831880,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455831676,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455831451,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455830817,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455830687,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455830176,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455830048,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455829944,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455829816,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455378795,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455378693,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455378459,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455378316,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455378180,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455378069,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455377956,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455377677,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455377558,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455377448,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455377318,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766455377209,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453090534,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453090317,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453090213,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453090112,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453090009,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453089903,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453088895,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453088793,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453088689,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453088584,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453088480,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453088250,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453087236,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453086725,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453086485,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453086373,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453086142,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453086043,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453072857,\n      \"type\": \"text\",\n      \"description\": \"paste text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453070975,\n      \"type\": \"text\",\n      \"description\": \"paste text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453054439,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453053127,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453052450,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453052106,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453051948,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453051806,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453051334,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453050207,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453042725,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453042179,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453041797,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453041570,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453039703,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453039291,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453039168,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453039065,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453038481,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453038365,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453038237,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453038105,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453038001,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453037850,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453037745,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453037495,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453037378,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453037182,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453037078,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453036972,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453036860,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453036147,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453035945,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453035825,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453035720,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453035443,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453035337,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453035233,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453035127,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453035026,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453034917,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453031063,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453030955,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453030833,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453030732,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453030225,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453030104,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453029968,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453029796,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453029474,\n      \"type\": \"edit\",\n      \"description\": \"rotate text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766453024797,\n      \"type\": \"text\",\n      \"description\": \"paste text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766451118553,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450929324,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450817210,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450257424,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450255024,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450254395,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450253241,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450251598,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450250392,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450248756,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450244072,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450242166,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450240998,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450236492,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450233672,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450232384,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450231012,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450230254,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450229302,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766450228132,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446610211,\n      \"type\": \"text\",\n      \"description\": \"paste text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446604849,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446604702,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446604550,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446604404,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446604305,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446604204,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446604099,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446603952,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446603849,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446603702,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446603599,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446603452,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446603348,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446603202,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446603099,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446602953,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446602850,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446602702,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446602600,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446602453,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446602349,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446602204,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446602101,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446602000,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446601848,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446601702,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446601601,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446601452,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446601301,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446601154,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446601049,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446600948,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446600802,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446600550,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446598595,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446598461,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446598171,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446598017,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446597219,\n      \"type\": \"text\",\n      \"description\": \"edit text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766446595278,\n      \"type\": \"text\",\n      \"description\": \"add text\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445633355,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445632515,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445631735,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445630757,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445627846,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445625085,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445618645,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445617784,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445608998,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445608720,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445608540,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445608376,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445608204,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445608038,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445607852,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445607678,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445607506,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445607319,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445607154,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445604410,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445604244,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445604066,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445603900,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445603743,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445603563,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445603406,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445603226,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445603052,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445602880,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445602641,\n      \"type\": \"edit\",\n      \"description\": \"edit zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445576567,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animation speed\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445570290,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animation speed\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445567192,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445566766,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445565520,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445398115,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445390895,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445385694,\n      \"type\": \"edit\",\n      \"description\": \"toggle fov animation\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445383241,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445382911,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445381695,\n      \"type\": \"edit\",\n      \"description\": \"edit node name\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445375383,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445374665,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445373273,\n      \"type\": \"node\",\n      \"description\": \"paste node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766445372205,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438157980,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438157430,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animation speed\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438152691,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438151948,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438151286,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438146174,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438145649,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438144555,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438143655,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438142504,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animation speed\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438130077,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438129561,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438128772,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438128398,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438122820,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438122062,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438119836,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438119588,\n      \"type\": \"edit\",\n      \"description\": \"edit edge animate\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438095045,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438093965,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438062827,\n      \"type\": \"edit\",\n      \"description\": \"toggle fov animation\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438047679,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438044161,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438041852,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438039668,\n      \"type\": \"edit\",\n      \"description\": \"resize node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438039562,\n      \"type\": \"edit\",\n      \"description\": \"resize node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438039421,\n      \"type\": \"edit\",\n      \"description\": \"resize node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438039260,\n      \"type\": \"edit\",\n      \"description\": \"resize node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438039150,\n      \"type\": \"edit\",\n      \"description\": \"resize node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438039039,\n      \"type\": \"edit\",\n      \"description\": \"resize node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438028508,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438021410,\n      \"type\": \"edit\",\n      \"description\": \"toggle fov\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438019234,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438017562,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766438014356,\n      \"type\": \"node\",\n      \"description\": \"add node\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437981696,\n      \"type\": \"edit\",\n      \"description\": \"apply routing to all\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437966551,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437964879,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437963627,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437961813,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437961193,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437957989,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437956467,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437953437,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437952239,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437950807,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437944990,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437943699,\n      \"type\": \"node\",\n      \"description\": \"move nodes\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437935414,\n      \"type\": \"zone\",\n      \"description\": \"draw zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437919019,\n      \"type\": \"zone\",\n      \"description\": \"delete zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437917758,\n      \"type\": \"zone\",\n      \"description\": \"draw zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437913740,\n      \"type\": \"zone\",\n      \"description\": \"draw zone\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766437882832,\n      \"type\": \"import\",\n      \"description\": \"Imported JSON: theonefile-networkening-corporate-demo.json (105 nodes, 65 connections)\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766263279163,\n      \"type\": \"tab\",\n      \"description\": \"Switched to tab: Corporate Site B\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766263270414,\n      \"type\": \"export\",\n      \"description\": \"Exported JSON: the-one-file.json\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766263260682,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file.html\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766263259518,\n      \"type\": \"tab\",\n      \"description\": \"Switched to tab: Homelab 2\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766263249401,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file-corporate.html\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766263246362,\n      \"type\": \"import\",\n      \"description\": \"Imported JSON: theonefile-networkening-corporate-demo.json (105 nodes, 65 connections)\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766190721141,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file-corporate.html\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766190717499,\n      \"type\": \"tab\",\n      \"description\": \"Switched to tab: Corporate Site B\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766190710946,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file.html\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766190705273,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file.html\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766190703463,\n      \"type\": \"tab\",\n      \"description\": \"Switched to tab: Homelab 2\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1766190695709,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file-corporate.html\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1766190688417,\n      \"type\": \"import\",\n      \"description\": \"Imported JSON: theonefile-networkening-corporate-demo.json (105 nodes, 65 connections)\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1765402888416,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file-corporate.html\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1765402884873,\n      \"type\": \"tab\",\n      \"description\": \"Switched to tab: Corporate Site B\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1765402878108,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file.html\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1765402866440,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file.html\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1765402865008,\n      \"type\": \"tab\",\n      \"description\": \"Switched to tab: Homelab 2\",\n      \"details\": {},\n      \"tab\": \"Homelab 2\"\n    },\n    {\n      \"timestamp\": 1765402860428,\n      \"type\": \"save\",\n      \"description\": \"File saved: the-one-file-corporate.html\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    },\n    {\n      \"timestamp\": 1765402858103,\n      \"type\": \"import\",\n      \"description\": \"Imported JSON: theonefile-networkening-corporate-demo.json (105 nodes, 65 connections)\",\n      \"details\": {},\n      \"tab\": \"Corporate Site B\"\n    }\n  ],\n  \"savedStyleSets\": []\n}\nTHEONEFILE_CONFIG-->\n\n# The One File\n\n> Exported from The One File on 2025-12-23T03:11:58.364Z\n\n## Legend\n\n- #475569: you can edit me too\n- #65758b: you can edit me too\n- #63748c: you can edit me too\n- #5e6f87: you can edit me too\n- #586a84: you can edit me too\n- #4f627d: you can edit me too\n- #455873: you can edit me too\n- #3d506c: you can edit me too\n- #354964: you can edit me too\n- #2e415c: you can edit me too\n- #293c56: you can edit me too\n- #273a53: you can edit me too\n- #253750: you can edit me too\n- #23354d: you can edit me too\n- #203046: you can edit me too\n- #1e2d43: you can edit me too\n- #1a283d: you can edit me too\n- #172435: you can edit me too\n- #141f2e: you can edit me too\n- #111a27: you can edit me too\n- #0f1824: you can edit me too\n- #0d1521: you can edit me too\n- #0c131d: you can edit me too\n- #0c1d1c: you can edit me too\n- #0c1c1d: you can edit me too\n- #0c191d: you can edit me too\n- #0c141d: you can edit me too\n- #0c0d1d: you can edit me too\n- #130c1d: you can edit me too\n- #1b0c1d: you can edit me too\n- #1d0c17: you can edit me too\n- #1d0c10: you can edit me too\n- #1d0c0c: you can edit me too\n- #3b1b1b: you can edit me too\n- #3c1a1a: you can edit me too\n- #3f1c1c: you can edit me too\n- #401c1c: you can edit me too\n- #451c1c: you can edit me too\n- #461b1b: you can edit me too\n- #4c1a1a: you can edit me too\n- #521919: you can edit me too\n- #571919: you can edit me too\n- #5d1818: you can edit me too\n- #631717: you can edit me too\n- #651515: you can edit me too\n- #6a1616: you can edit me too\n- #6f1515: you can edit me too\n- #711414: you can edit me too\n- #761414: you can edit me too\n- #771313: you can edit me too\n- #7c1313: you can edit me too\n- #811313: you can edit me too\n- #821212: you can edit me too\n- #871212: you can edit me too\n- #881111: you can edit me too\n- #8d1111: you can edit me too\n- #8e1010: you can edit me too\n- #8f0f0f: you can edit me too\n- #900e0e: you can edit me too\n- #8e0b0b: you can edit me too\n- #8c0d0d: you can edit me too\n- #880c0c: you can edit me too\n- #830c0c: you can edit me too\n- #7e0c0c: you can edit me too\n- #790c0c: you can edit me too\n- #730c0c: you can edit me too\n- #6f0b0b: you can edit me too\n- #0b6f64: you can edit me too\n- #0b6f5f: you can edit me too\n- #0b6f56: you can edit me too\n- #0b6f49: you can edit me too\n- #0b6f31: you can edit me too\n- #0b6f1f: you can edit me too\n- #0b6f0d: you can edit me too\n- #176f0b: you can edit me too\n- #266f0b: you can edit me too\n- #296f0b: you can edit me too\n- #2e6f0b: you can edit me too\n- #1a2d10: you can edit me too\n- #1c3111: you can edit me too\n- #213814: you can edit me too\n- #233c15: you can edit me too\n- #254017: you can edit me too\n- #294918: you can edit me too\n- #2b4d1a: you can edit me too\n- #2d511a: you can edit me too\n- #315a1b: you can edit me too\n- #35631c: you can edit me too\n- #37681d: you can edit me too\n- #3b721d: you can edit me too\n- #3f7b1e: you can edit me too\n- #42851e: you can edit me too\n- #46901d: you can edit me too\n- #499a1d: you can edit me too\n- #4b9f1d: you can edit me too\n- #4ca61c: you can edit me too\n- #50b01c: you can edit me too\n- #51b71a: you can edit me too\n- #50b918: you can edit me too\n- #51c115: you can edit me too\n- #53c615: you can edit me too\n- #53c814: you can edit me too\n- #52c913: you can edit me too\n- #54d011: you can edit me too\n- #53d110: you can edit me too\n- #55d510: you can edit me too\n- #55d70f: you can edit me too\n- #54d80e: you can edit me too\n- #54da0b: you can edit me too\n- #56df0c: you can edit me too\n- #53db0a: you can edit me too\n- #55e00b: you can edit me too\n- #55e109: you can edit me too\n- #55e208: ISP LINE\n- #4c00ff: MY Guest NETWORK\n- #80ff00: you can edit me too\n- #3b4234: you can edit me too\n- #3a3442: you can edit me too\n- #3b3442: you can edit me too\n- #3c3442: you can edit me too\n- #3d3442: you can edit me too\n- #3e3442: you can edit me too\n- #3f3442: you can edit me too\n- #403442: you can edit me too\n- #413442: you can edit me too\n- #653d66: you can edit me too\n- #683f69: you can edit me too\n- #6c416c: you can edit me too\n- #6f4370: you can edit me too\n- #704270: you can edit me too\n- #734474: you can edit me too\n- #784479: you can edit me too\n- #7d447e: you can edit me too\n- #7e437f: you can edit me too\n- #834384: you can edit me too\n- #844285: you can edit me too\n- #89418b: you can edit me too\n- #8e428f: you can edit me too\n- #904091: you can edit me too\n- #923e93: you can edit me too\n- #973e98: you can edit me too\n- #943c96: you can edit me too\n- #993c9a: you can edit me too\n- #963a98: you can edit me too\n- #973899: you can edit me too\n- #99369b: you can edit me too\n- #9a359c: you can edit me too\n- #9b349d: you can edit me too\n- #9d329f: you can edit me too\n- #9e31a0: you can edit me too\n- #a02fa2: you can edit me too\n- #9d2d9f: you can edit me too\n- #9f2ba1: you can edit me too\n- #a129a3: you can edit me too\n- #a327a5: you can edit me too\n- #a525a7: you can edit me too\n- #a723a9: you can edit me too\n- #a921ab: you can edit me too\n- #ab1fad: you can edit me too\n- #ad1daf: you can edit me too\n- #ae1cb0: you can edit me too\n- #b019b3: you can edit me too\n- #b118b4: you can edit me too\n- #b316b6: you can edit me too\n- #b816bb: you can edit me too\n- #b514b8: you can edit me too\n- #ba14bd: you can edit me too\n- #b712ba: you can edit me too\n- #bb13be: you can edit me too\n- #b811bb: you can edit me too\n- #be10c1: you can edit me too\n- #bb0ebe: you can edit me too\n- #bd0cc0: you can edit me too\n- #be0bc1: you can edit me too\n- #c108c4: you can edit me too\n- #be06c1: you can edit me too\n- #c103c4: you can edit me too\n- #c301c6: you can edit me too\n- #c400c7: you can edit me too\n- #c900cc: you can edit me too\n- #ce00d1: you can edit me too\n- #d300d6: you can edit me too\n- #d800db: you can edit me too\n- #dd00e0: you can edit me too\n- #e200e6: you can edit me too\n- #ec00f0: you can edit me too\n- #f100f5: you can edit me too\n- #f600fa: you can edit me too\n- #fb00ff: you can edit me too\n- #ff00d0: iPhone (always guest iPhone)\n- #f97316: you can edit me too\n\n## Nodes\n\n### internet\n- **Name:** Internet\n- **IP:** 0.0.0.0\n- **Role:** \n- **Shape:** square\n- **Tags:** _none_\n- **Layer:** physical\n- **MAC:** \n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 2104, 268\n- **Size:** 168\n- **Styles:** `{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"amazon-web-services\"},\"circleColor\":\"#ffffff\",\"circleBorder\":\"#ffffff\"}}`\n\n### internet-copy\n- **Name:** OPNSENSE\n- **IP:** 0.0.0.0\n- **Role:** \n- **Shape:** firewall\n- **Tags:** _none_\n- **Layer:** physical\n- **MAC:** \n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 2067, 473\n- **Size:** 50\n- **Styles:** `{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"opnsense\"}}}`\n\n### opnsense-copy\n- **Name:** Docker\n- **IP:** 0.0.0.0\n- **Role:** \n- **Shape:** firewall\n- **Tags:** _none_\n- **Layer:** physical\n- **MAC:** \n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 1774, 667\n- **Size:** 50\n- **Styles:** `{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"portainer\"}}}`\n\n### docker-copy\n- **Name:** Docker2\n- **IP:** 0.0.0.0\n- **Role:** \n- **Shape:** firewall\n- **Tags:** _none_\n- **Layer:** physical\n- **MAC:** \n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 1931, 782\n- **Size:** 50\n- **Styles:** `{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"jotty\"}}}`\n\n### docker-copy-1\n- **Name:** Docker3\n- **IP:** 0.0.0.0\n- **Role:** \n- **Shape:** firewall\n- **Tags:** _none_\n- **Layer:** physical\n- **MAC:** \n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 2158, 768\n- **Size:** 50\n- **Styles:** `{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"authportal\"}}}`\n\n### docker-copy-2\n- **Name:** Docker 4\n- **IP:** 0.0.0.0\n- **Role:** \n- **Shape:** firewall\n- **Tags:** [object Object]; [object Object]; [object Object]\n- **Layer:** physical\n- **MAC:** \n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 2342, 632\n- **Size:** 82\n- **Styles:** `{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"docker\"}}}`\n\n### opnsense-copy-1\n- **Name:** OPNSENSE GUEST\n- **IP:** 0.0.0.0\n- **Role:** \n- **Shape:** firewall\n- **Tags:** _none_\n- **Layer:** physical\n- **MAC:** \n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 2758, 308\n- **Size:** 50\n- **Styles:** `{\"all\":{\"icon\":{\"library\":\"selfhst\",\"name\":\"opnsense-v1\"}}}`\n\n### phone\n- **Name:** Phone\n- **IP:** 0.0.0.0\n- **Role:** \n- **Shape:** phone\n- **Tags:** _none_\n- **Layer:** physical\n- **MAC:** \n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 3313, 503\n- **Size:** 121\n\n### desktop\n- **Name:** Desktop\n- **IP:** 0.0.0.0\n- **Role:** \n- **Shape:** pc\n- **Tags:** _none_\n- **Layer:** physical\n- **MAC:** \n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 2972, 481\n- **Size:** 147\n\n### dns\n- **Name:** DNS\n- **IP:** 0.0.0.0\n- **Role:** \n- **Shape:** cloud\n- **Tags:** _none_\n- **Layer:** physical\n- **MAC:** \n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 3200, 320\n- **Size:** 50\n\n### racked\n- **Name:** Racked\n- **IP:** \n- **Role:** Rack\n- **Shape:** server\n- **Tags:** _none_\n- **Layer:** physical\n- **MAC:** \n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** 42\n- **Is Rack:** true\n- **Locked:** false\n- **Group ID:** \n- **Position:** 2646, 971\n- **Size:** 137\n- **Styles:** `{\"all\":{\"icon\":{\"library\":\"mdi\",\"name\":\"server-security\"},\"circleColor\":\"#010813\",\"circleBorder\":\"#ffffff\"}}`\n\n## Connections\n\n- internet --> internet-copy\n  - **ID:** internet-internet-copy-1765238145151\n  - **Label:** \n  - **Color:** #55e208\n  - **Width:** 4\n  - **Direction:** both\n  - **Routing:** curved\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n\n- internet-copy --> opnsense-copy\n  - **ID:** internet-copy-opnsense-copy-1765238187451\n  - **Label:** \n  - **Color:** #4c00ff\n  - **Width:** 4\n  - **Direction:** both\n  - **Routing:** curved\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n\n- internet-copy --> docker-copy\n  - **ID:** internet-copy-docker-copy-1765238242477\n  - **Label:** \n  - **Color:** #4c00ff\n  - **Width:** 4\n  - **Direction:** both\n  - **Routing:** curved\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n\n- internet-copy --> docker-copy-1\n  - **ID:** internet-copy-docker-copy-1-1765238244637\n  - **Label:** \n  - **Color:** #4c00ff\n  - **Width:** 4\n  - **Direction:** both\n  - **Routing:** curved\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n\n- internet-copy --> docker-copy-2\n  - **ID:** internet-copy-docker-copy-2-1765238246233\n  - **Label:** \n  - **Color:** #4c00ff\n  - **Width:** 4\n  - **Direction:** both\n  - **Routing:** curved\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n\n- internet --> opnsense-copy-1\n  - **ID:** internet-opnsense-copy-1-1765238266117\n  - **Label:** \n  - **Color:** #80ff00\n  - **Width:** 4\n  - **Direction:** both\n  - **Routing:** curved\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n\n- opnsense-copy-1 --> dns\n  - **ID:** opnsense-copy-1-dns-1765238347996\n  - **Label:** \n  - **Color:** #fb00ff\n  - **Width:** 4\n  - **Direction:** both\n  - **Routing:** curved\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n\n- dns --> desktop\n  - **ID:** dns-desktop-1765238386101\n  - **Label:** \n  - **Color:** #ff00d0\n  - **Width:** 4\n  - **Direction:** both\n  - **Routing:** curved\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n\n- phone --> dns\n  - **ID:** phone-dns-1765238391156\n  - **Label:** \n  - **Color:** #ff00d0\n  - **Width:** 4\n  - **Direction:** both\n  - **Routing:** curved\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n\n- undefined --> undefined\n  - **ID:** custom-1765239449323\n  - **Label:** \n  - **Color:** #f97316\n  - **Width:** 4\n  - **Direction:** forward\n  - **Routing:** curved\n  - **Type:** custom\n  - **Line Style:** solid\n  - **Group ID:** \n  - **Points:** 2936,786 3184,888 2763,982\n\n## Zones\n\n### rect-1765238219615\n- **Position:** 2680, 251\n- **Size:** 814 x 389\n- **Color:** #ec0999\n- **Style:** filled\n- **Line Style:** solid\n- **Border Color:** \n- **Border Width:** 2\n\n## Text Labels\n\n### text-1765238422602\n- **Content:** Double click on desktop<br>or long press on mobile<br>to enter rack canvas view\n- **Position:** 2466, 742\n- **Font Size:** 40\n- **Color:** #e2e8f0\n- **Font Weight:** normal\n- **Font Style:** normal\n- **Text Align:** start\n- **Text Decoration:** none\n- **Background Color:** #000000\n- **Background Enabled:** false\n- **Opacity:** 1\n\n"
  },
  {
    "path": "demos/password-protected/password.txt",
    "content": "lambert"
  },
  {
    "path": "demos/password-protected/the-one-file-corporate-demo.html",
    "content": "<!DOCTYPE html> \n <html lang=\"en\" style=\"--panel: #0b0e13; --panel-alt: #10141b; --sidebar-bg: #10141b; --btn-bg: #0b0e13; --accent: #4fd1c5; --danger: #f56565; --text-main: #e2e8f0; --text-soft: #94a3b8; --topbar-bg: rgba(9, 12, 20, 0.9); --topbar-border: #1f2533; --topbar-height: 103px; --sidebar-width: 350px; --mobile-footer-height: 40vh; --draw-toolbar-height: 0px; --btn-text: #e2e8f0; --tag-fill: #1e293b; --tag-text: #e2e8f0; --tag-border: #475569; --input-bg: #0b0e13; --input-text: #e2e8f0; --input-border: #1f2937; --input-font: Inter, system-ui, sans-serif; --input-font-size: 14px; --toolbar-bg: #0f172a; --toolbar-border: #1f2937; --toolbar-text: #94a3b8; --toolbar-btn-bg: #0b0e13; --toolbar-btn-text: #e2e8f0; --minimap-dots: #94a3b8; --canvas-hint-bg: #0f172a; --canvas-hint-color: #94a3b8; --node-fill: #1e293b; --node-stroke: #475569; --node-title: #e2e8f0; --node-sub: #94a3b8; --node-title-size: 41px; --node-sub-size: 27px; --node-font: monospace; --default-edge: #475569; --selection-handle: #f59e0b; --selection-handle-size: 8px; --group-indicator: #4fd1c5;\"><head>\n    <meta charset=\"UTF-8\">\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>The One File Corporate</title>\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <!--\n      * ====================================================================================\n      * THE ONE FILE\n      * \"There can be only one\". A all in one file topology maker for enterprise or homelab\n      *\n      * This is your last backup when all others fail. A completely self contained\n      * network topology visualization tool that works as a single HTML file.\n      * Open it anywhere, anytime and the idea lives forever.\n      * ====================================================================================\n      -->\n<style>\n      :root {\n      color-scheme: dark;\n      --bg: #050608;\n      --panel: #0b0e13;\n      --panel-alt: #10141b;\n      --accent: #4fd1c5;\n      --danger: #f56565;\n      --text-main: #e2e8f0;\n      --text-soft: #94a3b8;\n      --edge-main: #475569;\n      --node-min: 35px;\n      --node-max: 70px;\n      --topbar-bg: rgba(9, 12, 20, 0.9);\n      --topbar-border: #1f2533;\n      }\n\t  html, body, svg, .map-container {\n      touch-action: none;\n      }\n      * {\n      box-sizing: border-box;\n      user-select: none;\n      }\n      input,\n      textarea,\n      [contenteditable=\"true\"] {\n      user-select: text;\n      -webkit-user-select: text;\n      }\n      body {\n      margin: 0;\n      font-family: system-ui, sans-serif;\n      background: radial-gradient(circle at top, #1e2532 0, #050608 70%);\n      color: var(--text-main);\n      display: flex;\n      flex-direction: column;\n      height: 100vh;\n      overflow: hidden;\n      }\n      header {\n      padding: 0 20px;\n      height: var(--topbar-height, 52px);\n      min-height: var(--topbar-height, 52px);\n      background: var(--topbar-bg);\n      backdrop-filter: blur(6px);\n      display: flex;\n      justify-content: space-between;\n      align-items: center;\n      border-bottom: 1px solid var(--topbar-border);\n      gap: 16px;\n      }\n      .title-block {\n      display: flex;\n      flex-direction: column;\n      gap: 6px;\n      min-width: 0;\n      }\n      header h1 {\n      font-size: clamp(22px, 3vw, 32px);\n      margin: 0;\n      white-space: nowrap;\n      text-overflow: ellipsis;\n      overflow: hidden;\n      }\n      .editable-page-title {\n      cursor: pointer;\n      transition: opacity 0.2s;\n      }\n      .editable-page-title:hover {\n      opacity: 0.7;\n      }\n      .save-row {\n      display: flex;\n      align-items: center;\n      gap: 8px;\n      flex-wrap: wrap;\n      }\n\t  .toggle-switch{position:relative;display:inline-block;min-width:44px !important;height:24px;flex-shrink:0;vertical-align:middle;}\n\t\t.toggle-switch input{opacity:0;width:0;height:0;position:absolute;}\n\t\t.toggle-slider{position:absolute;cursor:pointer;top:0;left:0;right:0;bottom:0;background:#475569;transition:.25s;border-radius:24px;}\n\t\t.toggle-slider:before{position:absolute;content:\"\";height:18px;width:18px;left:3px;bottom:3px;background:#e2e8f0;transition:.25s;border-radius:50%;}\n\t\t.toggle-switch input:checked+.toggle-slider{background:var(--accent);}\n\t\t.toggle-switch input:checked+.toggle-slider:before{transform:translateX(20px);}\n\t\t.anim-zone-row{display:flex;justify-content:space-between;align-items:center;padding:6px 0;}\n\t\t.anim-zone-row label{color:var(--text-main);font-size:14px;}\n\t\t.anim-zone-section{font-size:11px;color:var(--text-soft);margin:12px 0 6px;text-transform:uppercase;letter-spacing:0.05em;border-bottom:1px solid var(--edge-main);padding-bottom:4px;}\n\t\t.anim-zone-header{font-size:12px;color:var(--accent);margin-bottom:10px;text-transform:uppercase;letter-spacing:0.05em;font-weight:600;}\n      .save-btn {\n      padding: 6px 12px;\n      background: var(--accent);\n      color: var(--bg);\n      border: none;\n      border-radius: 6px;\n      cursor: pointer;\n      font-size: 14px;\n      font-weight: 600;\n      white-space: nowrap;\n      }\n      .save-btn:hover {\n      opacity: 0.9;\n      }\n      .help-icon {\n      width: 22px;\n      height: 22px;\n      border-radius: 50%;\n      border: 1px solid var(--edge-main);\n      display: inline-flex;\n      align-items: center;\n      justify-content: center;\n      font-size: 14px;\n      cursor: pointer;\n      color: var(--text-soft);\n      background: rgba(15, 23, 42, 0.9);\n      flex-shrink: 0;\n      }\n      .help-icon:hover {\n      color: var(--accent);\n      border-color: var(--accent);\n      }\n      #settings-btn {\n      background: var(--btn-bg, var(--panel));\n      color: var(--btn-text, var(--text-main));\n      border: 1px solid var(--edge-main);\n      padding: 6px 12px;\n      border-radius: 6px;\n      cursor: pointer;\n      font-size: 16px;\n      flex-shrink: 0;\n      }\n      #settings-btn:hover {\n      background: var(--accent);\n      color: var(--bg);\n      }\n      .header-resizer {\n      position: absolute;\n      bottom: 0;\n      left: 0;\n      right: 0;\n      height: 6px;\n      background: transparent;\n      cursor: row-resize;\n      z-index: 1000;\n      transition: background 0.2s;\n      }\n      .header-resizer:hover {\n      background: rgba(79, 209, 197, 0.3);\n      }\n      .header-resizer.resizing {\n      background: rgba(79, 209, 197, 0.5);\n      }\n      .sidebar-resizer {\n      position: absolute;\n      left: 0;\n      top: 0;\n      bottom: 0;\n      width: 6px;\n      background: transparent;\n      cursor: col-resize;\n      z-index: 1000;\n      transition: background 0.2s;\n      }\n      .sidebar-resizer:hover {\n      background: rgba(79, 209, 197, 0.3);\n      }\n      .sidebar-resizer.resizing {\n      background: rgba(79, 209, 197, 0.5);\n      }\n      .mobile-footer-resizer {\n      position: absolute;\n      top: 0;\n      left: 0;\n      right: 0;\n      height: 6px;\n      background: transparent;\n      cursor: row-resize;\n      z-index: 1000;\n      transition: background 0.2s;\n      display: none;\n      }\n      .mobile-footer-resizer:hover {\n      background: rgba(79, 209, 197, 0.3);\n      }\n      .mobile-footer-resizer.resizing {\n      background: rgba(79, 209, 197, 0.5);\n      }\n      @media (max-width: 900px) {\n      .mobile-footer-resizer {\n      display: block;\n      height: 12px;\n      }\n      .sidebar-resizer {\n      display: none;\n      }\n      .header-resizer {\n      height: 12px;\n      }\n      }\n      @media (pointer: coarse) {\n      .header-resizer {\n      height: 16px;\n      }\n      .mobile-footer-resizer {\n      height: 16px;\n      }\n      .sidebar-resizer {\n      width: 16px;\n      }\n      }\n      .resizer-icon {\n      position: absolute;\n      opacity: 0.5;\n      transition: opacity 0.2s, transform 0.2s;\n      pointer-events: none;\n      fill: var(--accent);\n      }\n      .header-resizer .resizer-icon {\n      left: 50%;\n      top: 50%;\n      transform: translate(-50%, -50%);\n      }\n      .sidebar-resizer .resizer-icon {\n      left: 50%;\n      top: 50%;\n      transform: translate(-50%, -50%);\n      }\n      .mobile-footer-resizer .resizer-icon {\n      left: 50%;\n      top: 50%;\n      transform: translate(-50%, -50%);\n      }\n      .header-resizer:hover .resizer-icon,\n      .sidebar-resizer:hover .resizer-icon,\n      .mobile-footer-resizer:hover .resizer-icon {\n      opacity: 1;\n      }\n      .header-resizer.resizing .resizer-icon,\n      .sidebar-resizer.resizing .resizer-icon,\n      .mobile-footer-resizer.resizing .resizer-icon {\n      opacity: 1;\n      transform: translate(-50%, -50%) scale(1.2);\n      }\n      body.resizing {\n      user-select: none;\n      }\n      body.resizing * {\n      cursor: inherit !important;\n      pointer-events: none;\n      }\n      header {\n      position: relative;\n      }\n      .details-panel {\n      position: relative;\n      }\n      main {\n      display: grid;\n      grid-template-columns: 1fr var(--sidebar-width, 350px);\n      flex: 1;\n      }\n      main.sidebar-collapsed {\n      grid-template-columns: 1fr 0;\n      }\n      @media (max-width: 900px) {\n      main {\n      grid-template-columns: 1fr;\n      grid-template-rows: calc(100vh - var(--topbar-height, 52px) - var(--mobile-footer-height, 40vh)) var(--mobile-footer-height, 40vh);\n      }\n      main.sidebar-collapsed {\n      grid-template-rows: 1fr 0;\n      }\n      .details-panel {\n      max-height: var(--mobile-footer-height, 40vh);\n      height: 100%;\n      }\n      }\n      .topology-panel {\n      background: var(--panel);\n      border-right: 1px solid #111827;\n      position: relative;\n      overflow: hidden;\n      }\n      .topology-toolbar {\n      position: absolute;\n      top: 10px;\n      right: 10px;\n      display: none;\n      align-items: center;\n      gap: 8px;\n      padding: 6px 10px;\n      background: var(--toolbar-bg, rgba(15, 23, 42, 0.92));\n      border: 1px solid var(--toolbar-border, #1f2937);\n      border-radius: 6px;\n      z-index: 99;\n      font-size: 13px;\n      color: var(--toolbar-text, #94a3b8);\n      }\n      .topology-toolbar label {\n      color: var(--toolbar-text, var(--text-soft));\n      }\n      .topology-toolbar select {\n      padding: 4px 6px;\n      background: var(--toolbar-btn-bg, var(--panel));\n      border: 1px solid var(--toolbar-border, var(--edge-main));\n      border-radius: 4px;\n      color: var(--toolbar-btn-text, var(--text-main));\n      font-size: 13px;\n      cursor: pointer;\n      }\n      .topology-toolbar button {\n      padding: 4px 10px;\n      background: var(--toolbar-btn-bg, var(--panel));\n      color: var(--toolbar-btn-text, var(--text-main));\n      border: 1px solid var(--toolbar-border, var(--edge-main));\n      border-radius: 4px;\n      cursor: pointer;\n      font-size: 13px;\n      font-weight: 600;\n      }\n      .topology-toolbar button:hover {\n      background: var(--accent);\n      color: var(--bg);\n      }\n      .bulk-toolbar-desktop,\n      .bulk-toolbar-mobile {\n      position: fixed;\n      bottom: 20px;\n      left: 50%;\n      transform: translateX(-50%);\n      z-index: 9999;\n      }\n      .bulk-toolbar-desktop {\n      position: absolute;\n      top: 10px;\n      right: 10px;\n      bottom: auto;\n      left: auto;\n      transform: none;\n      }\n      @media (min-width: 768px) {\n      .bulk-toolbar-mobile {\n      display: none !important;\n      }\n      }\n      .bulk-action-btn {\n      padding: 16px;\n      background: var(--panel-main);\n      border: 1px solid var(--edge-main);\n      border-radius: 8px;\n      color: var(--text-main);\n      font-size: 24px;\n      cursor: pointer;\n      text-align: center;\n      line-height: 1.2;\n      }\n      .bulk-action-btn:active {\n      transform: scale(0.95);\n      background: var(--accent);\n      }\n      .draw-toolbar {\n      position: absolute;\n      top: 10px;\n      left: 10px;\n      display: flex;\n      align-items: center;\n      gap: 6px;\n      padding: 6px 8px;\n      background: var(--toolbar-bg, rgba(15, 23, 42, 0.92));\n      border: 1px solid var(--toolbar-border, #1f2937);\n      border-radius: 6px;\n      z-index: 99;\n      font-size: 13px;\n      pointer-events: auto;\n      color: var(--toolbar-text, #94a3b8);\n      }\n      .draw-toolbar button {\n      padding: 4px 8px;\n      background: var(--toolbar-btn-bg, var(--panel));\n      color: var(--toolbar-btn-text, var(--text-main));\n      border: 1px solid var(--toolbar-border, var(--edge-main));\n      border-radius: 4px;\n      cursor: pointer;\n      font-size: 13px;\n      }\n      .draw-toolbar button:hover {\n      background: var(--accent);\n      color: var(--bg);\n      }\n      .draw-toolbar input[type=\"color\"] {\n      width: 30px;\n      height: 22px;\n      border-radius: 4px;\n      border: 1px solid var(--edge-main);\n      padding: 0;\n      background: transparent;\n      cursor: pointer;\n      }\n      .draw-toolbar select {\n      padding: 4px 6px;\n      background: var(--toolbar-btn-bg, var(--panel));\n      border: 1px solid var(--toolbar-border, var(--edge-main));\n      border-radius: 4px;\n      color: var(--toolbar-btn-text, var(--text-main));\n      font-size: 13px;\n      cursor: pointer;\n      }\n      .legend-container {\n      position: absolute;\n      left: 10px;\n      bottom: 10px;\n      display: flex;\n      flex-direction: column;\n      gap: 6px;\n      padding: 8px 10px;\n      background: var(--toolbar-bg, rgba(15, 23, 42, 0.92));\n      border: 1px solid var(--toolbar-border, #1f2937);\n      border-radius: 6px;\n      font-size: 12px;\n      z-index: 20;\n      max-width: 260px;\n      pointer-events: auto;\n      color: var(--toolbar-text, #94a3b8);\n      }\n      .legend-container {\n      padding-right: 22px;\n      }\n      @media (max-width: 900px) {\n      .legend-container {\n      height:250px;\n      overflow-y: auto;\n      z-index:99;\n      }\n      }\n      @media (max-width: 900px) {\n      .bulk-toolbar-desktop,\n      .topology-toolbar {\n      display: none !important;\n      }\n      }\n      .legend-close-btn {\n      position: absolute;\n      top: 5px;\n      right: 5px;\n      width: 18px;\n      height: 18px;\n      border-radius: 999px;\n      border: 1px solid var(--edge-main);\n      background: var(--panel-alt);\n      color: var(--text-soft);\n      font-size: 11px;\n      display: flex;\n      align-items: center;\n      justify-content: center;\n      cursor: pointer;\n      }\n      .legend-close-btn:hover {\n      background: var(--danger);\n      color: #fff;\n      }\n      .legend-mini-btn {\n      position: absolute;\n      left: 10px;\n      padding: 4px 8px;\n      border-radius: 4px;\n      border: 1px solid var(--toolbar-border, #1f2937);\n      background: var(--toolbar-bg, rgba(15, 23, 42, 0.92));\n      color: var(--toolbar-btn-text, var(--text-main));\n      font-size: 11px;\n      cursor: pointer;\n      z-index: 20;\n      display: none;\n      transition: all 0.2s;\n      }\n      .legend-mini-btn:hover,\n      .legend-mini-btn:active {\n      background: var(--accent);\n      color: var(--bg);\n      border-color: var(--accent);\n      }\n      .legend-title {\n      font-size: 11px;\n      text-transform: uppercase;\n      letter-spacing: 0.06em;\n      color: var(--toolbar-text, var(--text-soft));\n      margin-bottom: 2px;\n      }\n\t  .fov-group {\n        transition: opacity 0.3s ease;\n      }\n      g[data-node-id]:not(:hover) .fov-group {\n        opacity: 0.7;\n      }\n      g[data-node-id]:hover .fov-group {\n        opacity: 1;\n      }\n      .legend-item {\n      display: flex;\n      align-items: center;\n      gap: 8px;\n      }\n      .legend-swatch {\n      width: 14px;\n      height: 14px;\n      border-radius: 3px;\n      border: 1px solid #020617;\n      flex-shrink: 0;\n      }\n      .legend-label {\n      outline: none;\n      cursor: text;\n      flex: 1;\n      min-width: 60px;\n      user-select: text;\n      -webkit-user-select: text;\n      }\n      .legend-label.editing {\n      border-bottom: 1px dashed var(--accent);\n      }\n      .canvas-viewport {\n      position: absolute;\n      top: 0;\n      left: 0;\n      right: 0;\n      bottom: 0;\n      overflow: hidden;\n      }\n      .canvas-viewport.panning {\n      cursor: grabbing !important;\n      }\n      .canvas-viewport.panning * {\n      cursor: grabbing !important;\n      }\n      .canvas-viewport svg {\n      width: 100%;\n      height: 100%;\n      display: block; \n      }\n      .zoom-toolbar {\n      display: flex;\n      gap: 4px;\n      padding: 6px;\n      background: var(--toolbar-bg, rgba(15, 23, 42, 0.92));\n      border: 1px solid var(--toolbar-border, #1f2937);\n      border-radius: 6px;\n      }\n      .zoom-toolbar button {\n      width: 32px;\n      height: 32px;\n      padding: 0;\n      background: var(--toolbar-btn-bg, var(--panel));\n      color: var(--toolbar-btn-text, var(--text-main));\n      border: 1px solid var(--toolbar-border, var(--edge-main));\n      border-radius: 4px;\n      cursor: pointer;\n      font-size: 18px;\n      font-weight: bold;\n      display: flex;\n      align-items: center;\n      justify-content: center;\n      transition: all 0.15s;\n      }\n      .zoom-toolbar button:hover {\n      background: var(--accent);\n      color: var(--bg);\n      }\n      .zoom-toolbar .zoom-level {\n      font-size: 11px;\n      color: var(--toolbar-text, var(--text-soft));\n      text-align: center;\n      padding: 2px 0;\n      min-width: 32px;\n      }\n      .zoom-toolbar .divider {\n      height: 1px;\n      background: var(--toolbar-border, var(--edge-main));\n      margin: 2px 0;\n      }\n      .minimap-zoom-wrapper {\n      position: absolute;\n      bottom: 10px;\n      right: 10px;\n      z-index: 99;\n      }\n      .minimap-container {\n      background: var(--toolbar-bg, rgba(15, 23, 42, 0.92));\n      border: 1px solid var(--toolbar-border, #1f2937);\n      border-radius: 6px;\n      overflow: hidden;\n      margin-bottom: 4px;\n      }\n      .minimap-container svg {\n      width: 100%;\n      height: 100%;\n      }\n\t.minimap-container,\n\t.node-panel,\n\t.edge-panel,\n\t#canvas-viewport {\n\t  contain: layout paint;\n\t}\t  \n\t.low-zoom .node-group .node-circle,\n\t.low-zoom .node-group.active .node-circle,\n\t.low-zoom .node-group.selected .node-circle {\n\t  filter: none !important;\n\t  animation: none !important;\n\t}\t\n      .minimap-viewport {\n      fill: rgba(79, 209, 197, 0.2);\n      stroke: var(--accent);\n      stroke-width: 2;\n      cursor: move;\n      }\n      .minimap-node {\n      fill: var(--minimap-dots, var(--text-soft));\n      }\n\t  .minimap-edge {\n      stroke: var(--edge-main);\n      stroke-width: 8px;\n      fill: none;\n      }\n\t  .minimap-wall {\n      pointer-events: none;\n      }\n      .minimap-rect {\n      pointer-events: none;\n      }\t  \n      .minimap-close-btn {\n      position: absolute;\n      top: 2px;\n      right: 2px;\n      width: 18px;\n      height: 18px;\n      background: var(--toolbar-btn-bg, rgba(15, 23, 42, 0.9));\n      border: 1px solid var(--toolbar-border, var(--edge-main));\n      border-radius: 3px;\n      color: var(--toolbar-text, var(--text-soft));\n      font-size: 11px;\n      cursor: pointer;\n      display: flex;\n      align-items: center;\n      justify-content: center;\n      z-index: 10;\n      padding: 0;\n      line-height: 1;\n      }\n      .minimap-close-btn:hover {\n      background: var(--danger);\n      color: white;\n      border-color: var(--danger);\n      }\n      .toolbar-close-btn {\n      width: 24px;\n      height: 24px;\n      background: var(--toolbar-btn-bg, rgba(15, 23, 42, 0.9));\n      border: 1px solid var(--toolbar-border, var(--edge-main));\n      border-radius: 4px;\n      color: var(--toolbar-text, var(--text-soft));\n      font-size: 12px;\n      cursor: pointer;\n      display: flex;\n      align-items: center;\n      justify-content: center;\n      padding: 0;\n      line-height: 1;\n      flex-shrink: 0;\n      }\n      .toolbar-close-btn:hover {\n      background: var(--danger);\n      color: white;\n      border-color: var(--danger);\n      }\n      .canvas-hint {\n      position: absolute;\n      top: 50px;\n      left: 50%;\n      transform: translateX(-50%);\n      padding: 8px 16px;\n      background: rgba(15, 23, 42, 0.92);\n      border: 1px solid #1f2937;\n      border-radius: 6px;\n      font-size: 12px;\n      color: var(--text-soft);\n      z-index: 18;\n      pointer-events: none;\n      opacity: 0;\n      transition: opacity 0.3s;\n      }\n      .canvas-hint.visible {\n      opacity: 1;\n      pointer-events: auto;\n      }\n      .edge {\n      stroke: var(--edge-main);\n      stroke-width: 4;\n      opacity: 0.75;\n      transition: 0.25s ease-in-out;\n      cursor: pointer;\n      }\n      .edge.backup {\n      stroke: var(--danger);\n      stroke-width: 5;\n      }\n      .edge.active {\n      opacity: 1;\n      stroke-width: 7;\n      filter: drop-shadow(0 0 8px var(--accent, #4fd1c5));\n      }\n      .rect-group.active .rect-shape {\n      filter: drop-shadow(0 0 10px var(--accent, #4fd1c5));\n      animation: pulse 1.2s infinite ease-in-out;\n      }\n      .text-element.active {\n      filter: drop-shadow(0 0 10px var(--accent, #4fd1c5));\n      animation: pulse 1.2s infinite ease-in-out;\n      }\n      .free-preview {\n      fill: none;\n      stroke-dasharray: 4 4;\n      pointer-events: none;\n      }\n@keyframes edge-flow-arrow {\n  0% { offset-distance: 0%; }\n  100% { offset-distance: 100%; }\n}\n@keyframes edge-flow-arrow-reverse {\n  0% { offset-distance: 100%; }\n  100% { offset-distance: 0%; }\n}\n.edge-arrow-forward {\n  offset-rotate: auto;\n  animation: edge-flow-arrow 1.5s linear infinite;\n}\n.edge-arrow-backward {\n  offset-rotate: auto 180deg;\n  animation: edge-flow-arrow-reverse 1.5s linear infinite;\n}\n      .free-point {\n      fill: #e5e7eb;\n      stroke: #0f172a;\n      stroke-width: 1.5;\n      cursor: grab;\n      }\n      .node-circle {\n      fill: var(--node-fill, #1e293b);\n      stroke: var(--node-stroke, #475569);\n      stroke-width: 2;\n      transition: 0.25s ease;\n      transform-origin: center center;\n      }\n      .node-hit-area {\n      cursor: grab;\n      pointer-events: all;\n      }\n      .node-group:hover .node-circle {\n      filter: drop-shadow(0 0 10px rgba(79, 209, 197, 0.45));\n      }\n      .node-group.active .node-circle {\n      stroke: var(--accent);\n      animation: pulse 1.2s infinite ease-in-out;\n      }\n      .node-group.selected .node-circle {\n      stroke: var(--selection-handle, #f59e0b);\n      stroke-width: 3;\n      }\n      .node-group.search-highlight .node-circle {\n      stroke: #10b981;\n      stroke-width: 3;\n      filter: drop-shadow(0 0 8px #10b981);\n      }\n\t  .node-group.search-faded {\n\t\topacity: 0.15;\n\t\tpointer-events: none;\n\t\t}\n\t\t.edge-group.search-faded {\n\t\topacity: 0.1;\n\t\t}\n\t  .node-group.search-faded {\n      opacity: 0.15;\n      pointer-events: none;\n      }\n      .edge-group.search-faded {\n      opacity: 0.1;\n      }\n      @keyframes pulse {\n      0% {\n      filter: drop-shadow(0 0 4px #4fd1c5);\n      }\n      50% {\n      filter: drop-shadow(0 0 14px #4fd1c5);\n      }\n      100% {\n      filter: drop-shadow(0 0 4px #4fd1c5);\n      }\n      }\n      @keyframes done-pulse {\n        0%, 100% {\n          transform: scale(1);\n          box-shadow: 0 0 0 0 rgba(79, 209, 197, 0.7);\n        }\n        50% {\n          transform: scale(1.05);\n          box-shadow: 0 0 0 8px rgba(79, 209, 197, 0);\n        }\n      }\n      .done-btn-active {\n        animation: done-pulse 1.5s ease-in-out infinite;\n        background: var(--accent) !important;\n        color: var(--bg) !important;\n      }\n      .node-label {\n      fill: var(--text-main);\n      font-size: 18px;\n      text-anchor: middle;\n      font-weight: 600;\n      }\n      .node-sub {\n      fill: var(--text-soft);\n      font-size: 13px;\n      text-anchor: middle;\n      }\n      @media (max-width: 1024px) {\n      .node-label {\n      font-size: 28px;\n      }\n      .node-sub {\n      font-size: 20px;\n      }\n      }\n      @media (max-width: 768px) {\n      .node-label {\n      font-size: 70px;\n      }\n      .node-sub {\n      font-size: 50px;\n      }\n      }\n      @media (max-width: 380px) {\n      .node-label {\n      font-size: 60px;\n      }\n      .node-sub {\n      font-size: 42px;\n      }\n      }\n      .details-panel {\n      background: var(--sidebar-bg, var(--panel-alt));\n      padding: 22px;\n      padding-bottom: 80px;\n      overflow-y: auto;\n      display: flex;\n      flex-direction: column;\n      gap: 15px;\n      position: relative;\n      transition:\n      width 0.3s ease,\n      min-width 0.3s ease,\n      padding 0.3s ease,\n      opacity 0.3s ease;\n      }\n      .details-panel {\n      min-width: 260px !important;\n      }\n      .details-panel {\n      overflow-y: auto !important;\n      overflow-x: hidden;\n      }\n      .details-panel.collapsed {\n      width: 0 !important;\n      min-width: 0 !important;\n      padding: 0 !important;\n      overflow: hidden;\n      opacity: 0;\n      }\n      body {\n      overflow-x: hidden;\n      overflow-y: hidden;\n      }\n      main {\n      overflow-x: hidden;\n      overflow-y: visible;\n      }\n      .details-panel {\n      overflow-y: auto !important;\n      overflow-x: hidden;\n      }\n      .details-panel.collapsed {\n      min-width: 0 !important;\n      }\n      .sidebar-toggle {\n      position: absolute;\n      left: -16px;\n      top: 50%;\n      transform: translateY(-50%);\n      width: 16px;\n      height: 60px;\n      background: var(--sidebar-bg, var(--panel-alt));\n      border: 1px solid var(--edge-main);\n      border-right: none;\n      border-radius: 8px 0 0 8px;\n      cursor: pointer;\n      display: flex;\n      align-items: center;\n      justify-content: center;\n      color: var(--text-soft);\n      font-size: 10px;\n      z-index: 25;\n      transition:\n      background 0.2s,\n      color 0.2s;\n      }\n      .sidebar-toggle:hover {\n      background: var(--accent);\n      color: var(--bg);\n      }\n      .sidebar-toggle.collapsed {\n      left: 0;\n      border-right: 1px solid var(--edge-main);\n      border-radius: 0 8px 8px 0;\n      position: fixed;\n      right: 0;\n      left: auto;\n      }\n      .details-name {\n      font-size: clamp(22px, 2.5vw, 30px);\n      font-weight: 700;\n      }\n      .details-ip {\n      font-size: clamp(16px, 2vw, 22px);\n      color: var(--text-soft);\n      }\n      .details-role {\n      font-size: clamp(16px, 2vw, 22px);\n      color: var(--accent);\n      }\n      .details-info-row {\n      display: flex;\n      align-items: center;\n      margin: 6px 0;\n      }\n      .size-controls {\n      display: flex;\n      gap: 10px;\n      align-items: center;\n      margin-top: 10px;\n      }\n      .size-controls label {\n      font-size: clamp(14px, 1.6vw, 18px);\n      color: var(--text-soft);\n      }\n      .size-controls input[type=\"range\"] {\n      flex: 1;\n      accent-color: var(--accent);\n      }\n      .size-controls button {\n      padding: 6px 12px;\n      background: var(--panel);\n      color: var(--text-main);\n      border: 1px solid var(--edge-main);\n      border-radius: 4px;\n      cursor: pointer;\n      font-size: clamp(12px, 1.4vw, 16px);\n      }\n      .size-controls button:hover {\n      background: var(--accent);\n      color: var(--bg);\n      }\n      .style-section {\n      margin-top: 15px;\n      padding-top: 15px;\n      border-top: 1px solid var(--edge-main);\n      }\n      .style-section summary {\n      cursor: pointer;\n      font-size: clamp(14px, 1.6vw, 20px);\n      text-transform: uppercase;\n      color: var(--text-soft);\n      list-style: none;\n      display: flex;\n      justify-content: space-between;\n      align-items: center;\n      user-select: none;\n      }\n      .style-section summary::-webkit-details-marker {\n      display: none;\n      }\n      .password-overlay {\n      position: fixed;\n      top: 0;\n      left: 0;\n      width: 100%;\n      height: 100%;\n      background: #ffffff;\n      z-index: 9999;\n      }\n      .style-section summary::after {\n      content: \"▼\";\n      transition: transform 0.2s;\n      }\n      .style-section[open] summary::after {\n      transform: rotate(180deg);\n      }\n.style-content {\n  display: grid;\n  grid-template-columns: 1fr auto;\n  gap: 8px 12px;\n  align-items: center;\n  justify-items: end;\n}\n.style-content label {\n  justify-self: start;\n}\n.style-row {\n  display: contents;\n}\n#edge-panel .style-row,\n#rect-panel .style-row,\n#text-panel .style-row {\n  display: flex;\n  align-items: center;\n  gap: 12px;\n  margin-bottom: 8px;\n}\n#edge-panel .style-row label,\n#rect-panel .style-row label,\n#text-panel .style-row label {\n  min-width: 80px;\n}\nbutton:disabled {\n  opacity: 0.5;\n  cursor: not-allowed;\n}\n      .style-row label {\n      font-size: clamp(13px, 1.5vw, 17px);\n      color: var(--text-soft);\n      min-width: 80px;\n      }\n      .style-row input[type=\"color\"] {\n      width: 50px;\n      height: 30px;\n      border: 1px solid var(--edge-main);\n      border-radius: 4px;\n      cursor: pointer;\n      background: transparent;\n      }\n      .style-row input[type=\"number\"] {\n      width: 70px;\n      padding: 4px 8px;\n      background: var(--panel);\n      border: 1px solid var(--edge-main);\n      border-radius: 4px;\n      color: var(--text-main);\n      font-size: clamp(12px, 1.4vw, 16px);\n      }\n      .style-row select {\n      flex: 1;\n      padding: 4px 8px;\n      background: var(--panel);\n      border: 1px solid var(--edge-main);\n      border-radius: 4px;\n      color: var(--text-main);\n      font-size: clamp(12px, 1.4vw, 16px);\n      cursor: pointer;\n      }\n      .editable-text {\n      cursor: pointer;\n      transition: opacity 0.2s;\n      }\n      .editable-text:hover {\n      opacity: 0.7;\n      }\n      .modal {\n\t  display: none;\n\t  position: fixed;\n\t  top: 0;\n\t  left: 0;\n\t  width: 100%;\n\t  height: 100%;\n\t  z-index: 999999;\n\t  justify-content: center;\n\t  align-items: center;\n\t  overflow: auto;\n\t  }\n\t  .modal.active {\n\t  display: inline-grid;\n\t  }\n\t  .modal-content {\n\t  background: var(--panel-alt);\n\t  padding: 25px;\n\t  border-radius: 8px;\n\t  border: 1px solid var(--edge-main);\n\t  min-width: 300px;\n\t  max-width: 90%;\n\t  }\n      .modal-content h3 {\n      margin: 0 0 15px 0;\n      color: var(--text-main);\n      font-size: clamp(18px, 2vw, 24px);\n      }\n      .modal-content p {\n      color: var(--text-soft);\n      font-size: clamp(14px, 1.6vw, 18px);\n      margin: 0 0 20px 0;\n      }\n      .modal-content input:not([type=\"color\"]),\n      .modal-content select {\n      width: 100%;\n      padding: 10px;\n      background: var(--panel);\n      border: 1px solid var(--edge-main);\n      border-radius: 4px;\n      color: var(--text-main);\n      font-size: clamp(14px, 1.6vw, 18px);\n      margin-bottom: 15px;\n      user-select: text;\n      -webkit-user-select: text;\n      }\n      .modal-content input[type=\"color\"] {\n      width: 60px;\n      height: 36px;\n      padding: 2px;\n      border: 2px solid var(--edge-main);\n      border-radius: 6px;\n      cursor: pointer;\n      background: none;\n      -webkit-appearance: none;\n      appearance: none;\n      }\n      .modal-content input[type=\"color\"]::-webkit-color-swatch-wrapper {\n      padding: 0;\n      }\n      .modal-content input[type=\"color\"]::-webkit-color-swatch {\n      border: none;\n      border-radius: 4px;\n      }\n      .modal-buttons {\n      display: flex;\n      gap: 10px;\n      justify-content: flex-end;\n      margin-top: 10px;\n      }\n      .modal-buttons button {\n      padding: 8px 16px;\n      border: none;\n      border-radius: 4px;\n      cursor: pointer;\n      font-size: clamp(14px, 1.6vw, 18px);\n      font-weight: 600;\n      }\n      .modal-buttons .btn-cancel {\n      background: var(--panel);\n      color: var(--text-main);\n      border: 1px solid var(--edge-main);\n      }\n      .modal-buttons .btn-save {\n      background: var(--accent);\n      color: var(--bg);\n      }\n      .modal-buttons .btn-delete {\n      background: var(--danger);\n      color: white;\n      }\n      .confirm-modal p {\n      color: var(--text-soft);\n      font-size: clamp(14px, 1.6vw, 18px);\n      margin: 0 0 20px 0;\n      }\n      .context-menu-item {\n      transition: background 0.15s;\n      }\n      .context-menu-item:hover {\n      background: var(--panel);\n      }\n      .badge-row {\n      display: flex;\n      flex-wrap: wrap;\n      gap: 8px;\n      }\n      .badge {\n      background: var(--tag-fill, #1e293b);\n      color: var(--tag-text, #e2e8f0);\n      border: 1px solid var(--tag-border, var(--edge-main));\n      padding: 5px 12px;\n      border-radius: 22px;\n      font-size: clamp(12px, 1.4vw, 18px);\n      }\n      .badge.wg {\n      color: var(--accent);\n      border-color: var(--accent);\n      }\n      input[type=\"text\"],\n      input[type=\"number\"],\n      input[type=\"password\"],\n      textarea,\n      select {\n      background: var(--input-bg, #0b0e13) !important;\n      color: var(--input-text, #e2e8f0) !important;\n      border-color: var(--input-border, #1f2937) !important;\n      font-family: var(--input-font, Inter, system-ui, sans-serif) !important;\n      font-size: var(--input-font-size, 14px) !important;\n      }\n      .section-label {\n      margin-top: 8px;\n      font-size: clamp(14px, 1.6vw, 20px);\n      text-transform: uppercase;\n      color: var(--text-soft);\n      }\n      .list li {\n      margin: 6px 0;\n      font-size: clamp(14px, 1.6vw, 20px);\n      cursor: pointer;\n      display: flex;\n      justify-content: space-between;\n      align-items: center;\n      gap: 10px;\n      }\n      .list li:hover {\n      color: var(--accent);\n      }\n      .delete-note {\n      color: var(--danger);\n      font-size: 18px;\n      cursor: pointer;\n      opacity: 0;\n      transition: opacity 0.2s;\n      flex-shrink: 0;\n      }\n      .list li:hover .delete-note {\n      opacity: 1;\n      }\n      .editing {\n      outline: 2px solid var(--accent);\n      background: #0d141f;\n      padding: 4px;\n      border-radius: 6px;\n      user-select: text;\n      -webkit-user-select: text;\n      }\n\t  .fov-group {\n  transition: opacity 0.3s ease;\n}\ng[data-node-id]:not(:hover) .fov-group {\n  opacity: 0.6;\n}\ng[data-node-id]:hover .fov-group {\n  opacity: 1;\n}\n      .version-item {\n      display: flex;\n      justify-content: space-between;\n      align-items: center;\n      padding: 12px;\n      background: var(--panel);\n      border: 1px solid var(--edge-main);\n      border-radius: 6px;\n      cursor: pointer;\n      transition: all 0.2s;\n      }\n      .version-item:hover {\n      background: var(--panel-alt);\n      border-color: var(--accent);\n      }\n      .version-item.current {\n      border-color: var(--accent);\n      background: rgba(79, 209, 197, 0.1);\n      }\n      .version-info {\n      flex: 1;\n      }\n      .version-info .timestamp {\n      font-size: 13px;\n      color: var(--text-main);\n      font-weight: 600;\n      }\n      .version-info .details {\n      font-size: 11px;\n      color: var(--text-soft);\n      margin-top: 4px;\n      }\n      .version-actions {\n      display: flex;\n      gap: 6px;\n      }\n      .audit-entry {\n      padding: 8px 12px;\n      background: var(--panel);\n      border-left: 3px solid var(--edge-main);\n      border-radius: 4px;\n      font-size: 12px;\n      }\n      .audit-entry.node { border-left-color: #4fd1c5; }\n      .audit-entry.connection { border-left-color: #9f7aea; }\n      .audit-entry.style { border-left-color: #ed8936; }\n      .audit-entry.rack { border-left-color: #48bb78; }\n      .audit-entry.layer { border-left-color: #4299e1; }\n      .audit-entry .time {\n      color: var(--text-soft);\n      font-size: 10px;\n      }\n      .audit-entry .action {\n      color: var(--text-main);\n      font-weight: 500;\n      }\n      .tab-item {\n      display: flex;\n      justify-content: space-between;\n      align-items: center;\n      padding: 12px;\n      background: var(--panel);\n      border: 1px solid var(--edge-main);\n      border-radius: 6px;\n      cursor: pointer;\n      transition: all 0.2s;\n      }\n      .tab-item:hover {\n      background: var(--panel-alt);\n      }\n      .tab-item.active {\n      border-color: var(--accent);\n      background: rgba(79, 209, 197, 0.1);\n      }\n      .tab-item .tab-name {\n      font-weight: 600;\n      color: var(--text-main);\n      flex: 1;\n      }\n      .tab-item .tab-stats {\n      font-size: 11px;\n      color: var(--text-soft);\n      margin-right: 10px;\n      }\n      .tab-actions {\n      display: flex;\n      gap: 6px;\n      }\n      .secret-item {\n      display: flex;\n      justify-content: space-between;\n      align-items: center;\n      padding: 12px;\n      background: var(--panel);\n      border: 1px solid var(--edge-main);\n      border-radius: 6px;\n      border-left: 3px solid var(--danger);\n      }\n      .secret-item .secret-name {\n      font-weight: 600;\n      color: var(--text-main);\n      flex: 1;\n      }\n      .secret-item .secret-status {\n      font-size: 11px;\n      color: var(--text-soft);\n      margin-right: 10px;\n      }\n      .mobile-menu-btn {\n      display: none;\n      background: var(--panel);\n      border: 1px solid var(--edge-main);\n      color: var(--text-main);\n      font-size: 22px;\n      padding: 6px 12px;\n      border-radius: 6px;\n      cursor: pointer;\n      }\n      #topbar-menu {\n      gap: 8px;\n      }\n      @media (min-width: 721px) {\n      #topbar-menu {\n      display: flex !important;\n      }\n      }\n      @media (max-width: 720px) {\n      #topbar-menu {\n      display: none;\n      }\n      #topbar-menu.open {\n      display: flex;\n      }\n      }\n      @media (max-width: 720px) {\n      .mobile-menu-btn {\n      display: block;\n      }\n      #topbar-menu {\n      position: absolute;\n      top: var(--topbar-height);\n      right: 0;\n      background: var(--panel-alt);\n      border-left: 1px solid var(--topbar-border);\n      border-bottom: 1px solid var(--topbar-border);\n      padding: 12px;\n      display: none;\n      flex-direction: column;\n      width: 180px;\n      z-index: 999;\n      }\n      #topbar-menu.open {\n      display: flex;\n      }\n      header {\n      position: relative;\n      z-index: 9999;\n      }\n      }\n      @media (max-width: 720px) {\n      .draw-toolbar {\n      position: absolute;\n      top: auto;\n      bottom: auto;\n      left: 10px !important;\n      right: auto !important;\n      width: auto;\n      gap: 6px;\n      padding: 8px;\n      }\n      .topology-toolbar {\n      position: absolute;\n      top: auto;\n      bottom: auto;\n      width: auto;\n      gap: 6px;\n      padding: 8px;\n      }\n      .draw-toolbar {\n      top: 10px !important;\n      z-index:99;\n      }\n      .topology-toolbar {\n      z-index:99;\n      top: calc(10px + var(--draw-toolbar-height, 50px)) !important;\n      }\n      .canvas-hint {\n      top: calc(10px + 120px);\n      }\n      #search-input {\n      width: 100%;\n      }\n      }\n\t  body.view-only-mode:not(.view-only-inspect) #node-panel,\n      body.view-only-mode:not(.view-only-inspect) #edge-panel,\n      body.view-only-mode:not(.view-only-inspect) #text-panel,\n      body.view-only-mode:not(.view-only-inspect) #rect-panel {\n        display: none !important;\n      }\n      body.view-only-mode .node-group,\n      body.view-only-mode .edge,\n      body.view-only-mode .rect-group,\n      body.view-only-mode .text-element {\n        cursor: default !important;\n      }\n      body.view-only-mode #bulk-toolbar,\n      body.view-only-mode #bulk-toolbar-mobile,\n      body.view-only-mode #bulk-actions-modal {\n        display: none !important;\n      }\n      body.view-only-mode::after {\n        content: \"VIEW ONLY • tap 5× to inspect\";\n        position: fixed;\n        bottom: 20px;\n        left: 50%;\n        transform: translateX(-50%);\n        background: rgba(245, 101, 101, 0.9);\n        color: white;\n        padding: 6px 16px;\n        border-radius: 20px;\n        font-size: 12px;\n        font-weight: 600;\n        letter-spacing: 1px;\n        z-index: 9999;\n        pointer-events: none;\n      }\n.dropdown {\n  position: relative;\n  display: inline-block;\n}\n.dropdown-btn {\n  padding: 6px 12px;\n  background: var(--btn-bg, var(--panel));\n  color: var(--btn-text, var(--text-main));\n  border: 1px solid var(--edge-main);\n  border-radius: 6px;\n  cursor: pointer;\n  font-size: 14px;\n  font-weight: 600;\n  white-space: nowrap;\n}\n.dropdown-btn:hover {\n  background: var(--accent);\n  color: var(--bg);\n}\n.dropdown-menu {\n  display: none;\n  position: absolute;\n  top: 100%;\n  left: 0;\n  min-width: 180px;\n  background: var(--panel);\n  border: 1px solid var(--edge-main);\n  border-radius: 6px;\n  box-shadow: 0 4px 20px rgba(0,0,0,0.4);\n  z-index: 100000;\n  margin-top: 4px;\n  overflow: hidden;\n}\nheader {\n  z-index: 99999;\n  position: relative;\n}\n.dropdown-menu.open {\n  display: block;\n}\n.dropdown-menu button {\n  display: block;\n  width: 100%;\n  padding: 10px 14px;\n  background: none;\n  border: none;\n  color: var(--text-main);\n  text-align: left;\n  cursor: pointer;\n  font-size: 14px;\n}\n.dropdown-menu button:hover {\n  background: var(--panel-alt);\n  color: var(--accent);\n}\n.dropdown-divider {\n  height: 1px;\n  background: var(--edge-main);\n  margin: 4px 0;\n}\n@media (max-width: 900px) {\n  .save-row .dropdown {\n    display: none !important;\n  }\n}\n\t@media print {\n\t  @page {\n\t\tsize: landscape;\n\t\tmargin: 0.5cm;\n\t  }\n\t  html, body {\n\t\twidth: 100%;\n\t\theight: 100%;\n\t\tmargin: 0;\n\t\tpadding: 0;\n\t\toverflow: visible !important;\n\t  }\n\t  body * {\n\t\tvisibility: hidden;\n\t  }\n\t  #canvas-viewport,\n\t  #canvas-viewport *,\n\t  #map,\n\t  #map * {\n\t\tvisibility: visible;\n\t  }\n\t  #canvas-viewport {\n\t\tposition: absolute !important;\n\t\tleft: 0 !important;\n\t\ttop: 0 !important;\n\t\tright: 0 !important;\n\t\tbottom: 0 !important;\n\t\twidth: 100vw !important;\n\t\theight: 100vh !important;\n\t\toverflow: visible !important;\n\t  }\n\t  #map {\n\t\tposition: absolute !important;\n\t\tleft: 0 !important;\n\t\ttop: 0 !important;\n\t\twidth: 100% !important;\n\t\theight: 100% !important;\n\t\tbackground: white !important;\n\t\tbackground-image: none !important;\n\t  }\n\t  #canvas-grid {\n\t\tdisplay: none !important;\n\t  }\n\t  main, .topology-panel {\n\t\tdisplay: block !important;\n\t\tposition: static !important;\n\t\toverflow: visible !important;\n\t  }\n\n\t  #map .node-hit-area,\n\t  #map .group-indicator,\n\t  #map .lock-indicator,\n\t  #map .fov-group,\n\t  #map .edge-arrow-forward,\n\t  #map .edge-arrow-backward {\n\t\tdisplay: none !important;\n\t  }\n\n\t  #map .node-circle,\n\t  #map .node-shape {\n\t\tfill: white !important;\n\t\tstroke: #000 !important;\n\t\tstroke-width: 2px !important;\n\t  }\n\n\t  #map text {\n\t\tfill: #000 !important;\n\t\tstroke: none !important;\n\t  }\n\n\t  #map .edge,\n\t  #map polyline,\n\t  #map line:not([class*=\"grid\"]) {\n\t\tstroke: #333 !important;\n\t  }\n\n  #map .rect-group rect {\n    stroke: #333 !important;\n  }\n\t  header, .sidebar, .mobile-footer, .minimap-zoom-wrapper,\n\t  .draw-toolbar, .topology-toolbar, .legend-container,\n\t  .bulk-toolbar, #bulk-toolbar-mobile, .dropdown-menu,\n\t  #canvas-hint, .node-panel, .edge-panel, .text-panel, .rect-panel {\n\t\tdisplay: none !important;\n\t  }\n\t}\n\t#mobile-export-btn,\n#mobile-import-btn {\n  display: none;\n}\n@media (max-width: 900px) {\n  #mobile-export-btn,\n  #mobile-import-btn {\n    display: inline-block;\n  }\n}\n    </style>\n  </head>\n  <body style=\"background: radial-gradient(circle at center top, rgb(30, 37, 50) 0px, rgb(5, 6, 8) 70%);\" class=\"\">\n    <div class=\"modal\" id=\"edit-modal\">\n      <div class=\"modal-content\">\n        <h3 id=\"modal-title\">Edit Title</h3>\n        <input type=\"text\" id=\"modal-input\">\n        <div class=\"modal-buttons\">\n          <button class=\"btn-cancel\" id=\"modal-cancel\">Cancel</button>\n          <button class=\"btn-save\" id=\"modal-save\">Save</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal confirm-modal\" id=\"confirm-modal\">\n      <div class=\"modal-content\">\n        <h3>Confirm</h3>\n        <p id=\"confirm-message\"> Are you sure you want to delete this line? </p>\n        <div class=\"modal-buttons\">\n          <button class=\"btn-cancel\" id=\"confirm-cancel\">Cancel</button>\n          <button class=\"btn-delete\" id=\"confirm-delete\">Delete</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"save-info-modal\">\n      <div class=\"modal-content\" style=\"max-width: 700px; max-height: 80vh; overflow-y: auto;\">\n        <h3 style=\"margin-bottom: 16px;\">Help</h3>\n        <div style=\"display: flex; gap: 8px; margin-bottom: 16px; flex-wrap: wrap;\">\n          <button class=\"help-tab active\" data-tab=\"general\" style=\"padding: 8px 16px; border: 1px solid var(--edge-main); background: var(--accent); color: var(--bg); border-radius: 6px; cursor: pointer; font-weight: 600;\">General</button>\n          <button class=\"help-tab\" data-tab=\"desktop\" style=\"padding: 8px 16px; border: 1px solid var(--edge-main); background: var(--panel); color: var(--text-main); border-radius: 6px; cursor: pointer; font-weight: 600;\">Desktop</button>\n          <button class=\"help-tab\" data-tab=\"mobile\" style=\"padding: 8px 16px; border: 1px solid var(--edge-main); background: var(--panel); color: var(--text-main); border-radius: 6px; cursor: pointer; font-weight: 600;\">Mobile</button>\n\t\t  <button class=\"help-tab\" data-tab=\"formats\" style=\"padding: 8px 16px; border: 1px solid var(--edge-main); background: var(--panel); color: var(--text-main); border-radius: 6px; cursor: pointer; font-weight: 600;\">Import/Export</button>\n        </div>\n        <div id=\"help-tab-general\" class=\"help-tab-content\" style=\"display: block;\">\n          <ul style=\"margin-left: 20px; margin-bottom: 12px; line-height: 1.8;\">\n            <li><strong>Add Nodes:</strong> Click \"+ Node\" or \"+ Rack\" in the top menu</li>\n            <li><strong>Connect Nodes:</strong> Select a node, then use \"Add Connection\" in the panel</li>\n            <li><strong>Move Nodes:</strong> Drag nodes to reposition them</li>\n            <li><strong>Enter Rack View:</strong> Double click on desktop or Long press on mobile</li>\n            <li><strong>Multi Select:</strong> Right click (desktop) or double tap (mobile)</li>\n            <li><strong>Pan Canvas:</strong> Drag empty space or hold Space + drag</li>\n            <li><strong>Zoom:</strong> Scroll wheel or pinch gesture</li>\n            <li><strong>Free Draw:</strong> Use draw toolbar for drawing lines, boxes/zones , and text</li>\n          </ul>\n          <h4 style=\"color: var(--accent); margin-bottom: 8px;\">Saving &amp; Encryption</h4>\n          <p style=\"margin-bottom: 8px;\">Browsers cannot overwrite local files. Click <strong>Save File</strong> to download an updated HTML with all changes. Replace your old file to keep edits.</p>\n          <p style=\"margin-bottom: 8px;\"><strong>Encryption of data:</strong> Check \"Encrypt\" before saving to password protect your data. Beware! No recovery possible without password!!</p>\n          <p><strong>Decryption of data:</strong> Export via JSON in top settings menu after successful password validation to decrypt your data. You can then reimport back into the same file and save!</p>\n        </div>\n        <div id=\"help-tab-desktop\" class=\"help-tab-content\" style=\"display: none;\">\n          <h4 style=\"color: var(--accent); margin-bottom: 12px;\">Navigation</h4>\n          <table style=\"width: 100%; border-collapse: collapse; margin-bottom: 16px; font-size: 14px;\">\n            <tbody><tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\">Arrow Keys</code></td>\n              <td style=\"padding: 8px;\">Move selected 1px</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\">Shift + Arrows</code></td>\n              <td style=\"padding: 8px;\">Move selected 10px</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\">Tab / Shift+Tab</code></td>\n              <td style=\"padding: 8px;\">Cycle through nodes</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\">F</code></td>\n              <td style=\"padding: 8px;\">Focus on selected</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\">Space + Drag</code></td>\n              <td style=\"padding: 8px;\">Pan canvas</td>\n            </tr>\n          </tbody></table>\n          <h4 style=\"color: var(--accent); margin-bottom: 12px;\">Management</h4>\n          <table style=\"width: 100%; border-collapse: collapse; margin-bottom: 16px; font-size: 14px;\">\n            <tbody><tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\">L</code></td>\n              <td style=\"padding: 8px;\">Lock/unlock selected</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\">G</code></td>\n              <td style=\"padding: 8px;\">Group/ungroup selected</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\">Ctrl+C / Ctrl+V</code></td>\n              <td style=\"padding: 8px;\">Copy / Paste</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\">Ctrl+D</code></td>\n              <td style=\"padding: 8px;\">Duplicate</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\">Ctrl+A</code></td>\n              <td style=\"padding: 8px;\">Select all</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\">Delete</code></td>\n              <td style=\"padding: 8px;\">Delete selected</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\">Escape</code></td>\n              <td style=\"padding: 8px;\">Clear selection</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\">Ctrl+Z / Ctrl+Y</code></td>\n              <td style=\"padding: 8px;\">Undo / Redo</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\">Right Click</code></td>\n              <td style=\"padding: 8px;\">Multi select toggle</td>\n            </tr>\n          </tbody></table>\n        </div>\n        <div id=\"help-tab-mobile\" class=\"help-tab-content\" style=\"display: none;\">\n          <h4 style=\"color: var(--accent); margin-bottom: 12px;\">Basic Gestures</h4>\n          <table style=\"width: 100%; border-collapse: collapse; margin-bottom: 16px; font-size: 14px;\">\n            <tbody><tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><strong>Tap node</strong></td>\n              <td style=\"padding: 8px;\">Select &amp; open properties</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><strong>Tap empty</strong></td>\n              <td style=\"padding: 8px;\">Deselect all</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><strong>Drag node</strong></td>\n              <td style=\"padding: 8px;\">Move node</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><strong>Drag empty</strong></td>\n              <td style=\"padding: 8px;\">Pan canvas</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><strong>Pinch</strong></td>\n              <td style=\"padding: 8px;\">Zoom in/out</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><strong>Double tap node</strong></td>\n              <td style=\"padding: 8px;\">Add/remove from selection</td>\n            </tr>\n          </tbody></table>\n          <h4 style=\"color: var(--accent); margin-bottom: 12px;\">Rack View</h4>\n          <table style=\"width: 100%; border-collapse: collapse; margin-bottom: 16px; font-size: 14px;\">\n            <tbody><tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><strong>Long press rack</strong></td>\n              <td style=\"padding: 8px;\">Enter rack view</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><strong>Double tap empty</strong></td>\n              <td style=\"padding: 8px;\">Exit rack view</td>\n            </tr>\n          </tbody></table>\n        </div>\n\t\t<div id=\"help-tab-formats\" class=\"help-tab-content\" style=\"display: none;\">\n          <h4 style=\"color: var(--accent); margin-bottom: 12px;\">Export Help</h4>\n          <table style=\"width: 100%; border-collapse: collapse; margin-bottom: 16px; font-size: 14px;\">\n            <tbody>\n              <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n                <td style=\"padding: 8px;\"><strong>HTML</strong></td>\n                <td style=\"padding: 8px;\">Complete self contained file and the default save method. When in doubt, choose this. Use the Save HTML button for this</td>\n              </tr>\n              <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n                <td style=\"padding: 8px;\"><strong>JSON</strong></td>\n                <td style=\"padding: 8px;\">Full backup. Editable in your favorite text editor</td>\n              </tr>\n              <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n                <td style=\"padding: 8px;\"><strong>Markdown</strong></td>\n                <td style=\"padding: 8px;\">Full backup. Editable in your favorite text editor</td>\n              </tr>\n              <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n                <td style=\"padding: 8px;\"><strong>CSV</strong></td>\n                <td style=\"padding: 8px;\">Full backup in header, nodes in spreadsheet rows</td>\n              </tr>\n              <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n                <td style=\"padding: 8px;\"><strong>PNG</strong></td>\n                <td style=\"padding: 8px;\">Standard image of current canvas tab</td>\n              </tr>\n              <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n                <td style=\"padding: 8px;\"><strong>SVG</strong></td>\n                <td style=\"padding: 8px;\">Vector image, scalable and editable image of current canvas tab</td>\n              </tr>\n            </tbody>\n          </table>\n          <h4 style=\"color: var(--accent); margin-bottom: 12px;\">Import Help</h4>\n          <table style=\"width: 100%; border-collapse: collapse; margin-bottom: 16px; font-size: 14px;\">\n            <tbody>\n              <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n                <td style=\"padding: 8px;\"><strong>JSON</strong></td>\n                <td style=\"padding: 8px;\">Replaces all data</td>\n              </tr>\n              <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n                <td style=\"padding: 8px;\"><strong>Markdown</strong></td>\n                <td style=\"padding: 8px;\">Replaces all data</td>\n              </tr>\n              <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n                <td style=\"padding: 8px;\"><strong>CSV</strong></td>\n                <td style=\"padding: 8px;\">Choose full restore or add nodes only. Allows quickly creating nodes with a spreadsheet and importing into an existing save.</td>\n              </tr>\n            </tbody>\n          </table>\n        </div>\n        <div class=\"modal-buttons\" style=\"margin-top: 16px;\">\n          <button class=\"btn-cancel\" id=\"save-info-close\">Close</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"settings-modal\">\n      <div class=\"modal-content\">\n        <h2>Settings</h2>\n\t\t<details class=\"style-section\">\n          <summary>View Only Mode</summary>\n          <div class=\"style-content\">\n            <div class=\"style-row\">\n              <label>View Only (disable editing)</label>\n              <input type=\"checkbox\" id=\"view-only-mode\" style=\"width:auto;\">\n            </div>\n            <p style=\"margin-top:4px;font-size:12px;color:var(--text-soft);\">When enabled, all editing of the canvas is disabled. Pan and zoom still work.</p>\n          </div>\n        </details>\n        <details class=\"style-section\">\n          <summary>General Theme</summary>\n          <div class=\"style-content\">\n\t\t    <div class=\"style-row\">\n              <label>Theme Preset</label>\n              <div style=\"display:flex;gap:6px;flex:1;\">\n                <select id=\"theme-preset\" style=\"flex:1;padding:4px 8px;background:var(--input-bg);color:var(--input-text);border:1px solid var(--input-border);border-radius:4px;\">\n                 <option value=\"defaulted\">Default</option>\n                 <option value=\"\">Custom</option>\n                  <optgroup label=\"Corporate\">\n                    <option value=\"slate\">Slate</option>\n                    <option value=\"graphite\">Graphite</option>\n                    <option value=\"frost\">Frost (Light)</option>\n                  </optgroup>\n                  <optgroup label=\"Homelab\">\n                    <option value=\"synthwave\">Synthwave</option>\n                    <option value=\"terminal\">Terminal</option>\n                  </optgroup>\n                  <optgroup label=\"Dev\">\n                    <option value=\"dracula\">Dracula</option>\n                    <option value=\"cobalt\">Cobalt</option>\n                    <option value=\"solarized\">Solarized</option>\n                  </optgroup>\n                  <optgroup id=\"my-themes-group\" label=\"My Themes\"></optgroup>\n                </select>\n              </div>\n            </div>\n\t\t\t<div class=\"style-row\">\n\t\t\t <label>  \n\t\t\t  <button onclick=\"saveCurrentTheme()\" style=\"padding:4px 8px;background:var(--accent);color:var(--bg);border:none;border-radius:4px;cursor:pointer;font-size:12px;white-space:nowrap;\">Save Custom Theme</button>\n\t\t\t </label>\n\t\t\t  <button id=\"delete-theme-btn\" onclick=\"deleteCurrentTheme()\" style=\"padding: 4px 8px; background: var(--danger); color: rgb(255, 255, 255); border: none; border-radius: 4px; cursor: pointer; font-size: 12px; display: block;\" disabled=\"\">Delete Custom Theme</button>\n\t\t\t</div>\t\n            <div class=\"style-row\">\n              <label>Main Background</label>\n              <input type=\"color\" id=\"panel-color\" value=\"#0b0e13\">\n            </div>\n            <div class=\"style-row\">\n              <label>Sidebar / Mobile Footer</label>\n              <input type=\"color\" id=\"sidebar-bg-color\" value=\"#10141b\">\n            </div>\n            <div class=\"style-row\">\n              <label>Modal Window (popup) Background</label>\n              <input type=\"color\" id=\"panel-alt-color\" value=\"#10141b\">\n            </div>\n            <div class=\"style-row\">\n              <label>Accent Buttons</label>\n              <input type=\"color\" id=\"accent-color\" value=\"#4fd1c5\">\n            </div>\n            <div class=\"style-row\">\n              <label>Danger Buttons</label>\n              <input type=\"color\" id=\"danger-color\" value=\"#f56565\">\n            </div>\n            <div class=\"style-row\">\n              <label>Primary Text</label>\n              <input type=\"color\" id=\"text-main-color\" value=\"#e2e8f0\">\n            </div>\n            <div class=\"style-row\">\n              <label>Secondary Text</label>\n              <input type=\"color\" id=\"text-soft-color\" value=\"#94a3b8\">\n            </div>\n\t\t\t<div class=\"style-row\">\n              <label>Tag(s) Fill</label>\n              <input type=\"color\" id=\"tag-fill-color\" value=\"#1e293b\">\n            </div>\n            <div class=\"style-row\">\n              <label>Tag(s) Text</label>\n              <input type=\"color\" id=\"tag-text-color\" value=\"#e2e8f0\">\n            </div>\n            <div class=\"style-row\">\n              <label>Tag(s) Border</label>\n              <input type=\"color\" id=\"tag-border-color\" value=\"#475569\">\n            </div>\t\t\t\n          </div>\n        </details>\n\t\t<details class=\"style-section\">\n          <summary>Top Bar</summary>\n          <div class=\"style-content\">\n            <div class=\"style-row\">\n              <label>Background</label>\n              <input type=\"color\" id=\"topbar-bg-color\" value=\"#0b0e13\">\n            </div>\n            <div class=\"style-row\">\n              <label>Border</label>\n              <input type=\"color\" id=\"topbar-border-color\" value=\"#1f2533\">\n            </div>\n          </div>\n\t\t  <div class=\"style-content\">\n            <div class=\"style-row\">\n              <label>Button Fill</label>\n              <input type=\"color\" id=\"btn-bg-color\" value=\"#0b0e13\">\n            </div>\n            <div class=\"style-row\">\n              <label>Button Text</label>\n              <input type=\"color\" id=\"btn-text-color\" value=\"#e2e8f0\">\n            </div>\n          </div>\n        </details>\n        <details class=\"style-section\">\n          <summary>Main Canvas</summary>\n          <div class=\"style-content\">\n            <div class=\"style-row\">\n              <label>Border</label>\n              <input type=\"color\" id=\"canvas-border-color\" value=\"#475569\">\n            </div>\n            <div class=\"style-row\">\n              <label>Grid Lines</label>\n              <input type=\"color\" id=\"canvas-grid-color\" value=\"#475569\">\n            </div>\n            <div class=\"style-row\">\n              <label>Grid Size</label>\n              <input type=\"number\" id=\"canvas-grid-size\" value=\"50\" min=\"20\" max=\"200\" style=\"width:60px;\">\n            </div>\n\t\t\t<div class=\"style-row\">\n\t\t\t<label>Show Grid</label>\n\t\t\t<input type=\"checkbox\" id=\"canvas-grid-enabled\" checked=\"\">\n\t\t\t</div>\n\t\t\t<div class=\"style-row\">\n              <label>Background Top</label>\n              <input type=\"color\" id=\"canvas-gradient-top\" value=\"#1e2532\">\n            </div>\n            <div class=\"style-row\">\n              <label>Background Bottom</label>\n              <input type=\"color\" id=\"canvas-gradient-bottom\" value=\"#050608\">\n            </div>\n            <div class=\"style-row\">\n              <label>Solid Background</label>\n              <input type=\"color\" id=\"page-bg-color\" value=\"#050608\">\n            </div>\n            <p style=\"margin-top:4px;font-size:12px;color:var(--text-soft);\">Set solid background to override gradient.</p>\n          </div>\n\t\t\t</details>\n\t\t  <details class=\"style-section\">\n          <summary>Rack Canvas</summary>\n          <div class=\"style-content\">\n            <div class=\"style-row\">\n              <label>Frame Fill</label>\n              <input type=\"color\" id=\"rack-frame-fill\" value=\"#0f172a\">\n            </div>\n            <div class=\"style-row\">\n              <label>Frame Border</label>\n              <input type=\"color\" id=\"rack-frame-stroke\" value=\"#4fd1c5\">\n            </div>\n            <div class=\"style-row\">\n              <label>Grid Lines</label>\n              <input type=\"color\" id=\"rack-line-color\" value=\"#475569\">\n            </div>\n\t\t\t<div class=\"style-row\">\n\t\t\t  <label>Show Grid</label>\n\t\t\t  <input type=\"checkbox\" id=\"rack-grid-enabled\" checked=\"\">\n\t\t\t</div>\n            <div class=\"style-row\">\n              <label>U Labels</label>\n              <input type=\"color\" id=\"rack-text-color\" value=\"#4fd1c5\">\n            </div>\n          </div>\n        </details>\n\t\t        <details class=\"style-section\">\n          <summary>Canvas Toolbars</summary>\n          <div class=\"style-content\">\n            <div class=\"style-row\">\n              <label>Background</label>\n              <input type=\"color\" id=\"toolbar-bg-color\" value=\"#0f172a\">\n            </div>\n            <div class=\"style-row\">\n              <label>Border</label>\n              <input type=\"color\" id=\"toolbar-border-color\" value=\"#1f2937\">\n            </div>\n            <div class=\"style-row\">\n              <label>Text</label>\n              <input type=\"color\" id=\"toolbar-text-color\" value=\"#94a3b8\">\n            </div>\n            <div class=\"style-row\">\n              <label>Button Fill</label>\n              <input type=\"color\" id=\"toolbar-btn-bg-color\" value=\"#0b0e13\">\n            </div>\n            <div class=\"style-row\">\n              <label>Button Text</label>\n              <input type=\"color\" id=\"toolbar-btn-text-color\" value=\"#e2e8f0\">\n            </div>\n            <div class=\"style-row\">\n              <label>Minimap Dots</label>\n              <input type=\"color\" id=\"minimap-dots-color\" value=\"#94a3b8\">\n            </div>\n          </div>\n        </details>\n        <details class=\"style-section\">\n          <summary>Nodes</summary>\n          <div class=\"style-content\">\n            <div class=\"style-row\">\n              <label>Fill</label>\n              <input type=\"color\" id=\"node-fill-color\" value=\"#1e293b\">\n            </div>\n            <div class=\"style-row\">\n              <label>Border</label>\n              <input type=\"color\" id=\"node-stroke-color\" value=\"#475569\">\n            </div>\n            <div class=\"style-row\">\n              <label>Title Color</label>\n              <input type=\"color\" id=\"node-title-color\" value=\"#e2e8f0\">\n            </div>\n            <div class=\"style-row\">\n              <label>Title Size</label>\n              <input type=\"number\" id=\"node-title-size\" value=\"18\" min=\"8\" max=\"48\" style=\"width:60px;\">\n            </div>\n            <div class=\"style-row\">\n              <label>Subtitle Color</label>\n              <input type=\"color\" id=\"node-sub-color\" value=\"#94a3b8\">\n            </div>\n            <div class=\"style-row\">\n              <label>Subtitle Size</label>\n              <input type=\"number\" id=\"node-sub-size\" value=\"13\" min=\"8\" max=\"48\" style=\"width:60px;\">\n            </div>\n            <div class=\"style-row\">\n              <label>Font</label>\n              <select id=\"node-font-family\" style=\"flex:1;padding:4px;background:var(--panel);color:var(--text-main);border:1px solid var(--topbar-border);border-radius:4px;\">\n                <option value=\"Inter, system-ui, sans-serif\">Inter</option>\n                <option value=\"Arial, sans-serif\">Arial</option>\n                <option value=\"Helvetica, sans-serif\">Helvetica</option>\n                <option value=\"Georgia, serif\">Georgia</option>\n                <option value=\"monospace\">Monospace</option>\n              </select>\n            </div>\n          </div>\n        </details>\n        <details class=\"style-section\">\n          <summary>Connections</summary>\n          <div class=\"style-content\">\n            <div class=\"style-row\">\n              <label>Default Color</label>\n              <input type=\"color\" id=\"default-edge-color\" value=\"#475569\">\n            </div>\n            <div class=\"style-row\">\n              <label>Default Routing</label>\n              <select id=\"default-edge-routing\">\n                <option value=\"orthogonal\">Orthogonal (90°)</option>\n                <option value=\"curved\">Curved</option>\n                <option value=\"straight\">Straight</option>\n              </select>\n            </div>\n            <div class=\"style-row\">\n              <label>Animation Style</label>\n              <select id=\"animation-style-select\">\n                <option value=\"arrows\">Flowing Arrows</option>\n                <option value=\"dots\">Dot Arrows</option>\n              </select>\n            </div>\n            <div class=\"style-row\">\n              <label>Animate Directions</label>\n              <select id=\"animation-direction-select\">\n                <option value=\"all\">All Directions</option>\n                <option value=\"forward\">Forward Only</option>\n                <option value=\"backward\">Backward Only</option>\n                <option value=\"both\">Bidirectional Only</option>\n              </select>\n            </div>\n            <div class=\"style-row\">\n              <label>Animation Speed</label>\n              <select id=\"animation-speed-select\">\n                <option value=\"0.5\">Very Fast</option>\n                <option value=\"1\">Fast</option>\n                <option value=\"1.5\">Normal</option>\n                <option value=\"2.5\">Slow</option>\n                <option value=\"4\">Very Slow</option>\n              </select>\n            </div>\n            <div class=\"style-row\" style=\"grid-column: 1 / -1;\">\n              <button id=\"apply-routing-all\" style=\"width:100%;padding:8px;background:var(--accent);color:var(--bg);border:none;border-radius:6px;cursor:pointer;font-weight:600;\">Apply Routing to All Connections</button>\n            </div>\n          </div>\n        </details>\n\t\t<details class=\"style-section\">\n          <summary>Animations &amp; Zones</summary>\n          <div class=\"style-content\">\n            <div class=\"style-row\"><label>All Animations</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"anim-master\" checked=\"\"><span class=\"toggle-slider\"></span></label></div>\n            <div style=\"grid-column:1/-1;font-size:11px;color:var(--text-soft);text-transform:uppercase;border-bottom:1px solid var(--edge-main);padding-bottom:4px;margin-top:8px;\">By Type</div>\n            <div class=\"style-row\"><label>Sweep (Pan)</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"anim-type-sweep\" checked=\"\"><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label>Pulse (Breathe)</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"anim-type-pulse\" checked=\"\"><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label>Rings (Emanate)</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"anim-type-rings\" checked=\"\"><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label>Spin (Rotate)</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"anim-type-spin\" checked=\"\"><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label>Connections (Flow)</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"anim-type-connections\" checked=\"\"><span class=\"toggle-slider\"></span></label></div>\n            <div style=\"grid-column:1/-1;font-size:11px;color:var(--text-soft);text-transform:uppercase;border-bottom:1px solid var(--edge-main);padding-bottom:4px;margin-top:8px;\">By Category</div>\n            <div class=\"style-row\"><label>Cameras</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"anim-cat-camera\" checked=\"\"><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label>Doorbells</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"anim-cat-doorbell\" checked=\"\"><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label>Motion Sensors</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"anim-cat-motion\" checked=\"\"><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label>Smoke Detectors</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"anim-cat-smoke\" checked=\"\"><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label>WiFi/AP/Router</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"anim-cat-wifi\" checked=\"\"><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label>Sensors/IoT</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"anim-cat-sensor\" checked=\"\"><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label>Sprinklers</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"anim-cat-sprinkler\" checked=\"\"><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label>Connections</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"anim-cat-connections\" checked=\"\"><span class=\"toggle-slider\"></span></label></div>\n            <div style=\"grid-column:1/-1;font-size:12px;color:var(--accent);text-transform:uppercase;letter-spacing:0.05em;font-weight:600;margin-top:16px;padding-top:12px;border-top:1px solid var(--edge-main);\">Zone (Animation Cone) Setings</div>\n            <div class=\"style-row\"><label>All Zones</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"zone-master\" checked=\"\"><span class=\"toggle-slider\"></span></label></div>\n            <div style=\"grid-column:1/-1;font-size:11px;color:var(--text-soft);text-transform:uppercase;border-bottom:1px solid var(--edge-main);padding-bottom:4px;margin-top:8px;\">By Category</div>\n            <div class=\"style-row\"><label>Cameras</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"zone-cat-camera\" checked=\"\"><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label>Doorbells</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"zone-cat-doorbell\" checked=\"\"><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label>Motion Sensors</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"zone-cat-motion\" checked=\"\"><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label>Smoke Detectors</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"zone-cat-smoke\" checked=\"\"><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label>WiFi/AP/Router</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"zone-cat-wifi\" checked=\"\"><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label>Sensors/IoT</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"zone-cat-sensor\" checked=\"\"><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label>Sprinklers</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"zone-cat-sprinkler\" checked=\"\"><span class=\"toggle-slider\"></span></label></div>\n          </div>\n        </details>\n        <details class=\"style-section\">\n          <summary>Groups &amp; Editing</summary>\n          <div class=\"style-content\">\n            <div class=\"style-row\">\n              <label>Resize handle Color</label>\n              <input type=\"color\" id=\"selection-handle-color\" value=\"#f59e0b\">\n            </div>\n            <div class=\"style-row\">\n              <label>Resize Handle Size</label>\n              <input type=\"number\" id=\"selection-handle-size\" value=\"8\" min=\"4\" max=\"16\" style=\"width:60px;\">\n            </div>\n            <div class=\"style-row\">\n              <label>Grouped Icon Outline</label>\n              <input type=\"color\" id=\"group-indicator-color\" value=\"#4fd1c5\">\n            </div>\n          </div>\n\t\t   <div class=\"style-content\">\n            <div class=\"style-row\">\n              <label>Multiselect Fill Color (Desktop)</label>\n              <input type=\"color\" id=\"selection-fill-color\" value=\"#4fd1c5\">\n            </div>\n            <div class=\"style-row\">\n              <label>Multiselect Fill Opacity (Desktop)</label>\n              <div style=\"display:flex;align-items:center;gap:8px;\">\n                <input type=\"range\" id=\"selection-fill-opacity\" min=\"0\" max=\"1\" step=\"0.05\" value=\"0.1\">\n                <span id=\"selection-fill-opacity-val\" style=\"min-width:35px;text-align:right;\">10%</span>\n              </div>\n            </div>\n            <div class=\"style-row\">\n              <label>Multiselect Border Color (Desktop)</label>\n              <input type=\"color\" id=\"selection-stroke-color\" value=\"#4fd1c5\">\n            </div>\n            <div class=\"style-row\">\n              <label>Multiselect Border Width (Desktop)</label>\n              <input type=\"number\" id=\"selection-stroke-width\" min=\"1\" max=\"10\" value=\"2\">\n            </div>\n            <div class=\"style-row\">\n              <label>Multiselect Border Style (Desktop)</label>\n              <select id=\"selection-stroke-style\">\n                <option value=\"5,5\">Dashed</option>\n                <option value=\"2,4\">Dotted</option>\n                <option value=\"none\">Solid</option>\n              </select>\n            </div>\n          </div>\n        </details>\n        <details class=\"style-section\">\n          <summary>Inputs &amp; Dropdowns</summary>\n          <div class=\"style-content\">\n            <div class=\"style-row\">\n              <label>Background</label>\n              <input type=\"color\" id=\"input-bg-color\" value=\"#0b0e13\">\n            </div>\n            <div class=\"style-row\">\n              <label>Text</label>\n              <input type=\"color\" id=\"input-text-color\" value=\"#e2e8f0\">\n            </div>\n            <div class=\"style-row\">\n              <label>Border</label>\n              <input type=\"color\" id=\"input-border-color\" value=\"#1f2937\">\n            </div>\n            <div class=\"style-row\">\n              <label>Font</label>\n              <select id=\"input-font-family\" style=\"flex:1;padding:4px;background:var(--panel);color:var(--text-main);border:1px solid var(--topbar-border);border-radius:4px;\">\n                <option value=\"Inter, system-ui, sans-serif\">Inter</option>\n                <option value=\"Arial, sans-serif\">Arial</option>\n                <option value=\"Helvetica, sans-serif\">Helvetica</option>\n                <option value=\"Georgia, serif\">Georgia</option>\n                <option value=\"monospace\">Monospace</option>\n              </select>\n            </div>\n            <div class=\"style-row\">\n              <label>Font Size</label>\n              <input type=\"number\" id=\"input-font-size\" value=\"14\" min=\"10\" max=\"24\" style=\"width:60px;\">\n            </div>\n          </div>\n        </details>\n        <details class=\"style-section\">\n          <summary>Welcome Message</summary>\n          <div class=\"style-content\">\n            <div class=\"style-row\">\n              <label>Enabled</label>\n              <input type=\"checkbox\" id=\"canvas-hint-enabled\" checked=\"\" style=\"width:auto;\">\n            </div>\n            <div class=\"style-row\">\n              <label>Background</label>\n              <input type=\"color\" id=\"canvas-hint-bg-color\" value=\"#0f172a\">\n            </div>\n            <div class=\"style-row\">\n              <label>Text Color</label>\n              <input type=\"color\" id=\"canvas-hint-text-color\" value=\"#94a3b8\">\n            </div>\n            <div class=\"style-row\" style=\"flex-direction:column;align-items:stretch;\">\n              <label>Custom Text (HTML)</label>\n              <textarea id=\"canvas-hint-text\" rows=\"4\" style=\"width:100%;margin-top:4px;padding:6px;background:var(--panel);color:var(--text-main);border:1px solid var(--topbar-border);border-radius:4px;font-size:12px;resize:vertical;\" placeholder=\"Leave empty for default\"></textarea>\n            </div>\n          </div>\n        </details>\n        <details class=\"style-section\" open=\"\">\n          <summary>Danger Zone</summary>\n          <div class=\"style-content\">\n            <p style=\"margin-bottom:12px;font-size:13px;color:var(--text-soft);\">Permanently delete everything on the canvas.</p>\n            <button id=\"clear-all-btn\" title=\"Clear all nodes\" style=\"padding:6px 12px;background:var(--danger);color:#fff;border:none;border-radius:6px;cursor:pointer;font-size:14px;font-weight:600;\">Clear All</button>\n            <input type=\"file\" id=\"import-data-file\" accept=\".json\" style=\"display:none\">\n          </div>\n        </details>\n        <div class=\"modal-buttons\">\n          <button class=\"btn-cancel\" id=\"settings-close\">Close</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"add-node-modal\">\n      <div class=\"modal-content\">\n        <h3>Add New Node</h3>\n        <label style=\"display: block;margin-bottom: 4px;color: var(--text-soft);font-size: 13px;\">Name</label>\n        <input type=\"text\" id=\"new-node-name\" placeholder=\"e.g. web-server\">\n        <label style=\"display: block;margin-bottom: 4px;color: var(--text-soft);font-size: 13px;\">IP / Subtitle</label>\n        <input type=\"text\" id=\"new-node-ip\" placeholder=\"e.g. 192.168.1.100\">\n        <label style=\"display: block;margin-bottom: 4px;color: var(--text-soft);font-size: 13px;\">Tags (comma separated)</label>\n        <input type=\"text\" id=\"new-node-tags\" placeholder=\"e.g. Docker, nginx, WG: vpn\">\n        <label style=\"display: block;margin-bottom: 4px;color: var(--text-soft);font-size: 13px;\">Shape</label>\n        <select id=\"new-node-shape\">\n                  <optgroup label=\"Basic Shapes\">\n\t\t\t\t  <option value=\"circle\">Circle</option>\n\t\t\t\t  <option value=\"square\">Square</option>\n\t\t\t\t  <option value=\"rectangle\">Rectangle</option>\n\t\t\t\t  <option value=\"triangle\">Triangle</option>\n\t\t\t\t  <option value=\"hexagon\">Hexagon</option>\n\t\t\t\t  <option value=\"diamond\">Diamond</option>\n\t\t\t\t  <option value=\"star\">Star</option>\n\t\t\t\t  <option value=\"stop-sign\">Stop Sign</option>\n\t\t\t\t  <option value=\"octagon\">Octagon</option>\n\t\t\t\t  <option value=\"pentagon\">Pentagon</option>\n\t\t\t\t  <option value=\"cross\">Cross</option>\n\t\t\t\t  <option value=\"rounded-square\">Rounded Square</option>\n\t\t\t\t  <option value=\"pill\">Pill</option>\n\t\t\t\t  <option value=\"parallelogram\">Parallelogram</option>\n\t\t\t\t  <option value=\"trapezoid\">Trapezoid</option>\n\t\t\t\t</optgroup>\n\t\t\t  <optgroup label=\"Computers &amp; Devices\">\n\t\t\t      <option value=\"server\">Server</option>\n\t\t\t\t  <option value=\"pc\">PC / Desktop</option>\n\t\t\t\t  <option value=\"laptop\">Laptop</option>\n\t\t\t\t  <option value=\"phone\">Phone / Mobile</option>\n\t\t\t\t  <option value=\"printer\">Printer</option>\n\t\t\t\t  <option value=\"pi\">Raspberry Pi</option>\n\t\t\t\t  <option value=\"sensor\">Sensor / IoT</option>\n\t\t\t  </optgroup>\n\t\t\t  <optgroup label=\"Network Equipment\">\n\t\t\t\t  <option value=\"router\">Router</option>\n\t\t\t\t  <option value=\"switch\">Switch</option>\n\t\t\t\t  <option value=\"firewall\">Firewall</option>\n\t\t\t\t  <option value=\"access-point\">Access Point</option>\n\t\t\t\t  <option value=\"load-balancer\">Load Balancer</option>\n\t\t\t\t  <option value=\"gateway\">Gateway</option>\n\t\t\t\t  <option value=\"vpn\">VPN / Tunnel</option>\n\t\t\t\t  <option value=\"nas\">NAS / Storage</option>\n\t\t\t  </optgroup>\n\t\t\t  <optgroup label=\"Cloud &amp; Services\">\n\t\t\t      <option value=\"cloud\">Cloud</option>\n\t\t\t\t  <option value=\"database\">Database</option>\n\t\t\t\t  <option value=\"docker\">Docker</option>\n\t\t\t\t  <option value=\"container\">Container</option>\n\t\t\t\t  <option value=\"vm\">Virtual Machine</option>\n\t\t\t\t  <option value=\"kubernetes\">Kubernetes</option>\n\t\t\t\t  <option value=\"api\">API / Endpoint</option>\n\t\t\t\t  <option value=\"queue\">Queue / Message</option>\n\t\t\t\t  <option value=\"lambda\">Lambda / Function</option>\n\t\t\t\t  <option value=\"bucket\">Bucket / S3</option>\n\t\t\t  </optgroup>\n\t\t\t  <optgroup label=\"Security &amp; Monitoring\">\n\t\t\t\t  <option value=\"shield\">Shield</option>\n\t\t\t\t  <option value=\"camera\">Camera / CCTV</option>\n\t\t\t\t  <option value=\"monitor\">Monitor / Dashboard</option>\n\t\t\t  </optgroup>\n\t\t\t  <optgroup label=\"Smart Home\">\n\t\t\t\t  <option value=\"thermostat\">Thermostat</option>\n\t\t\t\t  <option value=\"doorbell\">Video Doorbell</option>\n\t\t\t\t  <option value=\"smart-lock\">Smart Lock</option>\n\t\t\t\t  <option value=\"smart-bulb\">Smart Bulb</option>\n\t\t\t\t  <option value=\"smart-plug\">Smart Plug</option>\n\t\t\t\t  <option value=\"smart-speaker\">Smart Speaker</option>\n\t\t\t\t  <option value=\"smart-tv\">Smart TV</option>\n\t\t\t\t  <option value=\"hub\">Smart Hub</option>\n\t\t\t\t  <option value=\"smoke-detector\">Smoke Detector</option>\n\t\t\t\t  <option value=\"motion-sensor\">Motion Sensor</option>\n\t\t\t\t  <option value=\"garage\">Garage Door</option>\n\t\t\t\t  <option value=\"sprinkler\">Sprinkler</option>\n\t\t\t\t  <option value=\"vacuum\">Robot Vacuum</option>\n\t\t\t  </optgroup>\n             </select>\n        <div class=\"modal-buttons\">\n          <button class=\"btn-cancel\" id=\"add-node-cancel\">Cancel</button>\n          <button class=\"btn-save\" id=\"add-node-save\">Add Node</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"add-rack-modal\">\n      <div class=\"modal-content\">\n        <h3>Add New Rack</h3>\n        <label style=\"display: block;margin-bottom: 4px;color: var(--text-soft);font-size: 13px;\">Rack Name</label>\n        <input type=\"text\" id=\"new-rack-name\" placeholder=\"e.g. Rack-A, DC1-R01\">\n        <label style=\"display: block;margin-bottom: 4px;color: var(--text-soft);font-size: 13px;\">IP / Network Range (optional)</label>\n        <input type=\"text\" id=\"new-rack-ip\" placeholder=\"e.g. 192.168.1.0/24\">\n        <label style=\"display: block;margin-bottom: 4px;color: var(--text-soft);font-size: 13px;\">Tags (comma separated)</label>\n        <input type=\"text\" id=\"new-rack-tags\" placeholder=\"e.g. Production, Data Center 1\">\n        <label style=\"display: block;margin-bottom: 4px;color: var(--text-soft);font-size: 13px;\">Icon / Shape</label>\n        <select id=\"new-rack-shape\">\n                  <optgroup label=\"Basic Shapes\">\n\t\t\t\t  <option value=\"circle\">Circle</option>\n\t\t\t\t  <option value=\"square\">Square</option>\n\t\t\t\t  <option value=\"rectangle\">Rectangle</option>\n\t\t\t\t  <option value=\"triangle\">Triangle</option>\n\t\t\t\t  <option value=\"hexagon\">Hexagon</option>\n\t\t\t\t  <option value=\"diamond\">Diamond</option>\n\t\t\t\t  <option value=\"star\">Star</option>\n\t\t\t\t  <option value=\"stop-sign\">Stop Sign</option>\n\t\t\t\t  <option value=\"octagon\">Octagon</option>\n\t\t\t\t  <option value=\"pentagon\">Pentagon</option>\n\t\t\t\t  <option value=\"cross\">Cross</option>\n\t\t\t\t  <option value=\"rounded-square\">Rounded Square</option>\n\t\t\t\t  <option value=\"pill\">Pill</option>\n\t\t\t\t  <option value=\"parallelogram\">Parallelogram</option>\n\t\t\t\t  <option value=\"trapezoid\">Trapezoid</option>\n\t\t\t\t</optgroup>\n\t\t\t  <optgroup label=\"Computers &amp; Devices\">\n\t\t\t      <option value=\"server\">Server</option>\n\t\t\t\t  <option value=\"pc\">PC / Desktop</option>\n\t\t\t\t  <option value=\"laptop\">Laptop</option>\n\t\t\t\t  <option value=\"phone\">Phone / Mobile</option>\n\t\t\t\t  <option value=\"printer\">Printer</option>\n\t\t\t\t  <option value=\"pi\">Raspberry Pi</option>\n\t\t\t\t  <option value=\"sensor\">Sensor / IoT</option>\n\t\t\t  </optgroup>\n\t\t\t  <optgroup label=\"Network Equipment\">\n\t\t\t\t  <option value=\"router\">Router</option>\n\t\t\t\t  <option value=\"switch\">Switch</option>\n\t\t\t\t  <option value=\"firewall\">Firewall</option>\n\t\t\t\t  <option value=\"access-point\">Access Point</option>\n\t\t\t\t  <option value=\"load-balancer\">Load Balancer</option>\n\t\t\t\t  <option value=\"gateway\">Gateway</option>\n\t\t\t\t  <option value=\"vpn\">VPN / Tunnel</option>\n\t\t\t\t  <option value=\"nas\">NAS / Storage</option>\n\t\t\t  </optgroup>\n\t\t\t  <optgroup label=\"Cloud &amp; Services\">\n\t\t\t      <option value=\"cloud\">Cloud</option>\n\t\t\t\t  <option value=\"database\">Database</option>\n\t\t\t\t  <option value=\"docker\">Docker</option>\n\t\t\t\t  <option value=\"container\">Container</option>\n\t\t\t\t  <option value=\"vm\">Virtual Machine</option>\n\t\t\t\t  <option value=\"kubernetes\">Kubernetes</option>\n\t\t\t\t  <option value=\"api\">API / Endpoint</option>\n\t\t\t\t  <option value=\"queue\">Queue / Message</option>\n\t\t\t\t  <option value=\"lambda\">Lambda / Function</option>\n\t\t\t\t  <option value=\"bucket\">Bucket / S3</option>\n\t\t\t  </optgroup>\n\t\t\t  <optgroup label=\"Security &amp; Monitoring\">\n\t\t\t\t  <option value=\"shield\">Shield</option>\n\t\t\t\t  <option value=\"camera\">Camera / CCTV</option>\n\t\t\t\t  <option value=\"monitor\">Monitor / Dashboard</option>\n\t\t\t  </optgroup>\n\t\t\t  <optgroup label=\"Smart Home\">\n\t\t\t\t  <option value=\"thermostat\">Thermostat</option>\n\t\t\t\t  <option value=\"doorbell\">Video Doorbell</option>\n\t\t\t\t  <option value=\"smart-lock\">Smart Lock</option>\n\t\t\t\t  <option value=\"smart-bulb\">Smart Bulb</option>\n\t\t\t\t  <option value=\"smart-plug\">Smart Plug</option>\n\t\t\t\t  <option value=\"smart-speaker\">Smart Speaker</option>\n\t\t\t\t  <option value=\"smart-tv\">Smart TV</option>\n\t\t\t\t  <option value=\"hub\">Smart Hub</option>\n\t\t\t\t  <option value=\"smoke-detector\">Smoke Detector</option>\n\t\t\t\t  <option value=\"motion-sensor\">Motion Sensor</option>\n\t\t\t\t  <option value=\"garage\">Garage Door</option>\n\t\t\t\t  <option value=\"sprinkler\">Sprinkler</option>\n\t\t\t\t  <option value=\"vacuum\">Robot Vacuum</option>\n\t\t\t  </optgroup>\n             </select>\n        <label style=\"display: block;margin-bottom: 4px;color: var(--text-soft);font-size: 13px;\">Rack Capacity</label>\n        <select id=\"new-rack-capacity\">\n          <option value=\"42\" selected=\"\">42U (Standard Full Rack)</option>\n          <option value=\"48\">48U (Large Rack)</option>\n          <option value=\"24\">24U (Half Rack)</option>\n          <option value=\"12\">12U (Small/Wall Mount)</option>\n          <option value=\"6\">6U (Mini Rack)</option>\n        </select>\n        <div class=\"modal-buttons\">\n          <button class=\"btn-cancel\" id=\"add-rack-cancel\">Cancel</button>\n          <button class=\"btn-save\" id=\"add-rack-save\">Add Rack</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal confirm-modal\" id=\"clear-all-modal\">\n      <div class=\"modal-content\">\n        <h3>Clear All Nodes</h3>\n        <p> This will remove ALL nodes and connections. This cannot be undone until you reload without saving. Are you sure? </p>\n        <div class=\"modal-buttons\">\n          <button class=\"btn-cancel\" id=\"clear-all-cancel\">Cancel</button>\n          <button class=\"btn-delete\" id=\"clear-all-confirm\">Clear Everything</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"layer-modal\">\n      <div class=\"modal-content\" style=\"max-width: 400px;\">\n        <h3>Layer Visibility</h3>\n        <p style=\"font-size: 13px; color: var(--text-soft); margin-bottom: 15px;\">Toggle which layers are visible on the canvas</p>\n        <div style=\"display: flex; flex-direction: column; gap: 10px;\">\n          <label style=\"display: flex; align-items: center; gap: 8px; cursor: pointer; padding: 8px; background: var(--panel-alt); border-radius: 6px;\">\n          <input type=\"checkbox\" id=\"layer-physical\" checked=\"\" style=\"cursor: pointer; width: 18px; height: 18px;\">\n          <span style=\"flex: 1; font-weight: 500;\">Physical Layer</span>\n          </label>\n          <label style=\"display: flex; align-items: center; gap: 8px; cursor: pointer; padding: 8px; background: var(--panel-alt); border-radius: 6px;\">\n          <input type=\"checkbox\" id=\"layer-logical\" checked=\"\" style=\"cursor: pointer; width: 18px; height: 18px;\">\n          <span style=\"flex: 1; font-weight: 500;\">Logical Layer</span>\n          </label>\n          <label style=\"display: flex; align-items: center; gap: 8px; cursor: pointer; padding: 8px; background: var(--panel-alt); border-radius: 6px;\">\n          <input type=\"checkbox\" id=\"layer-security\" checked=\"\" style=\"cursor: pointer; width: 18px; height: 18px;\">\n          <span style=\"flex: 1; font-weight: 500;\">Security Layer</span>\n          </label>\n          <label style=\"display: flex; align-items: center; gap: 8px; cursor: pointer; padding: 8px; background: var(--panel-alt); border-radius: 6px;\">\n          <input type=\"checkbox\" id=\"layer-application\" checked=\"\" style=\"cursor: pointer; width: 18px; height: 18px;\">\n          <span style=\"flex: 1; font-weight: 500;\">Application Layer</span>\n          </label>\n        </div>\n        <div style=\"margin-top: 15px; display: flex; gap: 8px;\">\n          <button class=\"btn-cancel\" style=\"flex: 1;\" onclick=\"document.querySelectorAll('#layer-modal input[type=checkbox]').forEach(cb =&gt; cb.checked = false); applyLayerFilter();\">Hide All</button>\n          <button class=\"btn-save\" style=\"flex: 1;\" onclick=\"document.querySelectorAll('#layer-modal input[type=checkbox]').forEach(cb =&gt; cb.checked = true); applyLayerFilter();\">Show All</button>\n        </div>\n        <div class=\"modal-buttons\" style=\"margin-top: 10px;\">\n          <button class=\"btn-cancel\" id=\"layer-modal-close\">Close</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"tabs-modal\">\n      <div class=\"modal-content\" style=\"max-width: 600px;\">\n        <p style=\"font-size: 13px; color: var(--text-soft); margin-bottom: 15px;\">Manage multiple topologies</p>\n        <div id=\"tabs-list\" style=\"display: flex; flex-direction: column; gap: 8px; max-height: 400px; overflow-y: auto; margin-bottom: 15px;\">\n            <div class=\"tab-item active\" onclick=\"switchTab(0)\">\n              <div class=\"tab-name\">Corporate Site B</div>\n              <div class=\"tab-stats\">107 nodes • 64 connections</div>\n              <div class=\"tab-actions\">\n                <button class=\"btn-cancel\" onclick=\"event.stopPropagation(); renameTab(0)\" title=\"Rename tab\">✏️</button>\n                <button class=\"btn-cancel\" onclick=\"event.stopPropagation(); deleteTab(0)\" title=\"Delete tab\">🗑️</button>\n              </div>\n            </div>\n          \n            <div class=\"tab-item \" onclick=\"switchTab(1)\">\n              <div class=\"tab-name\">Homelab 2</div>\n              <div class=\"tab-stats\">16 nodes • 10 connections</div>\n              <div class=\"tab-actions\">\n                <button class=\"btn-cancel\" onclick=\"event.stopPropagation(); renameTab(1)\" title=\"Rename tab\">✏️</button>\n                <button class=\"btn-cancel\" onclick=\"event.stopPropagation(); deleteTab(1)\" title=\"Delete tab\">🗑️</button>\n              </div>\n            </div>\n          </div>\n        <div style=\"display: flex; gap: 8px; margin-bottom: 10px;\">\n          <input type=\"text\" id=\"new-tab-name\" placeholder=\"New tab name...\" style=\"flex: 1; padding: 8px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 6px; color: var(--text-main);\">\n          <button class=\"btn-save\" onclick=\"createNewTab()\">+ Add Tab</button>\n        </div>\n        <div class=\"modal-buttons\">\n          <button class=\"btn-cancel\" id=\"tabs-modal-close\">Close</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"rollback-modal\">\n      <div class=\"modal-content\" style=\"max-width: 700px;\">\n        <h3>Version History</h3>\n        <p style=\"font-size: 13px; color: var(--text-soft); margin-bottom: 15px;\">Limit: Snapshots</p>\n        <div id=\"rollback-list\" style=\"display: flex; flex-direction: column; gap: 8px; max-height: 450px; overflow-y: auto; margin-bottom: 15px;\"></div>\n        <div style=\"display: flex; gap: 8px; margin-bottom: 10px;\">\n          <button class=\"btn-cancel\" onclick=\"clearRollbackHistory()\">Clear History</button>\n          <button class=\"btn-save\" onclick=\"createManualSnapshot()\">Create Snapshot</button>\n        </div>\n        <div class=\"modal-buttons\">\n          <button class=\"btn-cancel\" id=\"rollback-modal-close\">Close</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"audit-log-modal\">\n      <div class=\"modal-content\" style=\"max-width: 800px;\">\n        <h3>Audit Log</h3>\n        <p style=\"font-size: 13px; color: var(--text-soft); margin-bottom: 15px;\">Track all changes made to your topology</p>\n        <div style=\"display: flex; gap: 8px; margin-bottom: 10px;\">\n          <select id=\"audit-filter\" style=\"padding: 8px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 6px; color: var(--text-main);\">\n            <option value=\"all\">All Events</option>\n            <option value=\"node\">Node Operations</option>\n            <option value=\"connection\">Connections</option>\n            <option value=\"style\">Style Changes</option>\n            <option value=\"rack\">Rack Operations</option>\n            <option value=\"layer\">Layer Changes</option>\n          </select>\n          <button class=\"btn-save\" onclick=\"exportAuditLog()\">Export</button>\n        </div>\n        <div id=\"audit-log-list\" style=\"display: flex; flex-direction: column; gap: 4px; max-height: 450px; overflow-y: auto; margin-bottom: 15px; font-family: monospace; font-size: 12px;\"></div>\n        <div style=\"display: flex; gap: 8px;\">\n          <button class=\"btn-cancel\" onclick=\"clearAuditLog()\">Clear Log</button>\n          <button class=\"btn-cancel\" id=\"audit-log-modal-close\">Close</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"port-map-modal\">\n      <div class=\"modal-content\" style=\"max-width: 900px;\">\n        <h3>Port Map</h3>\n        <p style=\"font-size: 13px; color: var(--text-soft); margin-bottom: 15px;\">All connections with port assignments</p>\n        <div style=\"display: flex; gap: 8px; margin-bottom: 10px;\">\n          <input type=\"text\" id=\"port-map-search\" placeholder=\"Search devices or ports...\" style=\"flex: 1; min-width: 200px; padding: 8px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 6px; color: var(--text-main);\">\n          <select id=\"port-map-filter\" style=\"padding: 8px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 6px; color: var(--text-main);\">\n            <option value=\"all\">All Connections</option>\n            <option value=\"with-ports\">With Ports Only</option>\n            <option value=\"without-ports\">Missing Ports</option>\n          </select>\n        </div>\n        <div id=\"port-map-table\" style=\"max-height: 450px; overflow-y: auto; margin-bottom: 15px;\"><div style=\"text-align: center; padding: 40px; color: var(--text-soft);\">No connections found</div></div>\n        <div style=\"display: flex; gap: 8px;\">\n          <button class=\"btn-save\" onclick=\"exportPortMap()\">Export CSV</button>\n          <button class=\"btn-cancel\" id=\"port-map-modal-close\">Close</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"secrets-modal\">\n      <div class=\"modal-content\" style=\"max-width: 600px;\">\n        <h3>Notes</h3>\n        <p style=\"font-size: 13px; color: var(--text-soft); margin-bottom: 15px;\">Notes can also be stored with AES 256 encryption</p>\n        <div id=\"secrets-list\" style=\"display: flex; flex-direction: column; gap: 8px; max-height: 350px; overflow-y: auto; margin-bottom: 15px;\"><div style=\"text-align: center; padding: 40px; color: var(--text-soft);\">No notes yet</div></div>\n        <div style=\"display: flex; gap: 8px; margin-bottom: 10px;\">\n          <input type=\"text\" id=\"new-secret-name\" placeholder=\"Note name (e.g., 'Root Passwords')...\" style=\"flex: 1; padding: 8px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 6px; color: var(--text-main);\">\n          <button class=\"btn-save\" onclick=\"createNewSecret()\">+ Add Note</button>\n        </div>\n        <div class=\"modal-buttons\">\n          <button class=\"btn-cancel\" id=\"secrets-modal-close\">Close</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"secret-editor-modal\">\n      <div class=\"modal-content\" style=\"max-width: 600px;\">\n        <h3 id=\"secret-editor-title\">Edit Note</h3>\n        <textarea id=\"secret-editor-content\" placeholder=\"Enter sensitive information here...\" style=\"width: 100%; height: 200px; padding: 12px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 6px; color: var(--text-main); font-family: monospace; resize: vertical; margin-bottom: 15px;\"></textarea>\n        <div style=\"margin-bottom: 15px;\">\n          <label style=\"display: flex; align-items: center; gap: 8px; cursor: pointer;\">\n          <input type=\"checkbox\" id=\"secret-auto-encrypt\" checked=\"\" style=\"cursor: pointer;\">\n          <span>Encrypt Note</span>\n          </label>\n        </div>\n        <div class=\"modal-buttons\">\n          <button class=\"btn-cancel\" onclick=\"closeSecretEditor()\">Cancel</button>\n          <button class=\"btn-save\" onclick=\"saveSecret()\">Save</button>\n        </div>\n      </div>\n    </div>\n\t\n\t<div class=\"modal\" id=\"mobile-export-modal\">\n      <div class=\"modal-content\" style=\"max-width: 320px;\">\n        <h3 style=\"margin: 0 0 20px 0;\">Export</h3>\n        <div style=\"display: flex; flex-direction: column; gap: 12px;\">\n          <button onclick=\"screenshotCanvas(); document.getElementById('mobile-export-modal').classList.remove('active');\" style=\"padding: 14px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; color: var(--text-main); font-size: 16px; cursor: pointer;\">PNG Image</button>\n          <button onclick=\"exportCanvasSVG(); document.getElementById('mobile-export-modal').classList.remove('active');\" style=\"padding: 14px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; color: var(--text-main); font-size: 16px; cursor: pointer;\">SVG Vector</button>\n          <button onclick=\"exportJSONFile(); document.getElementById('mobile-export-modal').classList.remove('active');\" style=\"padding: 14px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; color: var(--text-main); font-size: 16px; cursor: pointer;\">JSON (Full Backup)</button>\n          <button onclick=\"exportMarkdown(); document.getElementById('mobile-export-modal').classList.remove('active');\" style=\"padding: 14px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; color: var(--text-main); font-size: 16px; cursor: pointer;\">Markdown</button>\n          <button onclick=\"exportCSV(); document.getElementById('mobile-export-modal').classList.remove('active');\" style=\"padding: 14px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; color: var(--text-main); font-size: 16px; cursor: pointer;\">CSV</button>\n          <button onclick=\"printTopology(); document.getElementById('mobile-export-modal').classList.remove('active');\" style=\"padding: 14px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; color: var(--text-main); font-size: 16px; cursor: pointer;\">Print...</button>\n\t\t  <button onclick=\"showFormatHelp(); document.getElementById('mobile-import-modal').classList.remove('active');\" style=\"padding: 14px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; color: var(--text-main); font-size: 16px; cursor: pointer;\">Export Help</button>\n        </div>\n        <button onclick=\"document.getElementById('mobile-export-modal').classList.remove('active');\" style=\"margin-top: 20px; padding: 12px; background: var(--danger); border: none; border-radius: 8px; color: white; font-size: 16px; cursor: pointer; width: 100%;\">Cancel</button>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"mobile-import-modal\">\n      <div class=\"modal-content\" style=\"max-width: 320px;\">\n        <h3 style=\"margin: 0 0 20px 0;\">Import</h3>\n        <div style=\"display: flex; flex-direction: column; gap: 12px;\">\n          <button onclick=\"document.getElementById('import-json-file').click(); document.getElementById('mobile-import-modal').classList.remove('active');\" style=\"padding: 14px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; color: var(--text-main); font-size: 16px; cursor: pointer;\">JSON</button>\n          <button onclick=\"document.getElementById('import-markdown-file').click(); document.getElementById('mobile-import-modal').classList.remove('active');\" style=\"padding: 14px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; color: var(--text-main); font-size: 16px; cursor: pointer;\">Markdown</button>\n          <button onclick=\"document.getElementById('import-csv-file').click(); document.getElementById('mobile-import-modal').classList.remove('active');\" style=\"padding: 14px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; color: var(--text-main); font-size: 16px; cursor: pointer;\">CSV</button>\n          <button onclick=\"showFormatHelp(); document.getElementById('mobile-import-modal').classList.remove('active');\" style=\"padding: 14px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; color: var(--text-main); font-size: 16px; cursor: pointer;\">Import Help</button>\n        </div>\n        <button onclick=\"document.getElementById('mobile-import-modal').classList.remove('active');\" style=\"margin-top: 20px; padding: 12px; background: var(--danger); border: none; border-radius: 8px; color: white; font-size: 16px; cursor: pointer; width: 100%;\">Cancel</button>\n      </div>\n    </div>\n    <div id=\"context-menu\" style=\"display: none !important;\">\n    </div>\n    <header>\n      <div class=\"title-block\">\n        <h1 id=\"page-title\" class=\"editable-page-title\">The One File Corporate</h1>\n        <div class=\"save-row\">\n          <button id=\"save-file-btn\" class=\"save-btn\" type=\"button\">Save HTML</button>\n          <label style=\"display: flex;align-items: center;gap: 4px;font-size: 12px;color: var(--text-soft);cursor: pointer;user-select: none;\">\n            <input type=\"checkbox\" id=\"encrypt-toggle\" style=\"cursor: pointer\">\n            <span title=\"Encrypt data with password\">🔒</span>\n          </label>\n          <div class=\"dropdown\" id=\"export-dropdown\">\n            <button class=\"dropdown-btn\" type=\"button\">Export ▾</button>\n            <div class=\"dropdown-menu\">\n              <button type=\"button\" onclick=\"screenshotCanvas()\">PNG Image</button>\n              <button type=\"button\" onclick=\"exportCanvasSVG()\">SVG Vector</button>\n              <div class=\"dropdown-divider\"></div>\n              <button type=\"button\" onclick=\"exportJSONFile()\">JSON</button>\n              <button type=\"button\" onclick=\"exportMarkdown()\">Markdown</button>\n              <button type=\"button\" onclick=\"exportCSV()\">CSV</button>\n              <div class=\"dropdown-divider\"></div>\n              <button type=\"button\" onclick=\"printTopology()\">Print...</button>\n\t\t\t  <div class=\"dropdown-divider\"></div>\n\t\t\t  <button type=\"button\" onclick=\"showFormatHelp()\">Export Help</button>\n            </div>\n          </div>\n          <div class=\"dropdown\" id=\"import-dropdown\">\n            <button class=\"dropdown-btn\" type=\"button\">Import ▾</button>\n            <div class=\"dropdown-menu\">\n              <button type=\"button\" onclick=\"document.getElementById('import-json-file').click()\">JSON</button>\n              <button type=\"button\" onclick=\"document.getElementById('import-markdown-file').click()\">Markdown</button>\n              <button type=\"button\" onclick=\"document.getElementById('import-csv-file').click()\">CSV</button>\n              <div class=\"dropdown-divider\"></div>\n              <button type=\"button\" onclick=\"showFormatHelp()\">Import Help</button>\n            </div>\n          </div>\n          <input type=\"file\" id=\"import-json-file\" accept=\".json\" style=\"display:none\">\n          <input type=\"file\" id=\"import-markdown-file\" accept=\".md,.markdown,.txt\" style=\"display:none\">\n          <input type=\"file\" id=\"import-csv-file\" accept=\".csv,.txt\" style=\"display:none\">\n          <span id=\"save-help-btn\" class=\"help-icon\" title=\"Why save?\">?</span>\n        </div>\n      </div>\n      <div id=\"topbar-menu\">\n        <button id=\"back-to-topology-btn\" title=\"Exit rack view and return to topology\" style=\"padding: 6px 12px;background: var(--accent);color: var(--bg);border: none;border-radius: 6px;cursor: pointer;font-size: 14px;font-weight: 600;display: none;\">← Back to Topology</button>\n        <button id=\"add-node-btn\" class=\"save-btn\" title=\"Add new node\" style=\"background: var(--accent)\" draggable=\"true\">+ Node</button>\n        <button id=\"add-rack-btn\" class=\"save-btn\" title=\"Add new rack\" style=\"background: var(--accent); margin-left: 8px;\" draggable=\"true\">+ Rack</button>\n        <button id=\"undo-btn\" title=\"Undo (Ctrl+Z)\" style=\"padding: 6px 12px; background: var(--btn-bg); color: var(--btn-text); border: 1px solid var(--edge-main); border-radius: 6px; cursor: pointer; font-size: 14px; font-weight: 600; opacity: 1;\">↶</button>\n        <button id=\"redo-btn\" title=\"Redo (Ctrl+Y)\" style=\"padding: 6px 12px; background: var(--btn-bg); color: var(--btn-text); border: 1px solid var(--edge-main); border-radius: 6px; cursor: pointer; font-size: 14px; font-weight: 600; opacity: 0.5;\" disabled=\"\">↷</button>\n        <input type=\"text\" id=\"search-nodes\" placeholder=\"Search nodes...\" title=\"Search by name, IP, MAC, role, or tag\" style=\"padding: 6px 12px;background: var(--input-bg);color: var(--input-text);border: 1px solid var(--input-border);border-radius: 6px;font-family: var(--input-font);font-size: var(--input-font-size);width: 180px;\">\n        <button id=\"layers-btn\" title=\"Toggle layer visibility\" style=\"padding: 6px 12px;background: var(--btn-bg);color: var(--btn-text);border: 1px solid var(--edge-main);border-radius: 6px;cursor: pointer;font-size: 14px;font-weight: 600;\">Layers</button>\n        <button id=\"tabs-btn\" title=\"Manage document tabs\" style=\"padding: 6px 12px;background: var(--btn-bg);color: var(--btn-text);border: 1px solid var(--edge-main);border-radius: 6px;cursor: pointer;font-size: 14px;font-weight: 600;\">Tabs</button>\n        <button id=\"rollback-btn\" title=\"View version history\" style=\"padding: 6px 12px;background: var(--btn-bg);color: var(--btn-text);border: 1px solid var(--edge-main);border-radius: 6px;cursor: pointer;font-size: 14px;font-weight: 600;\">Snapshots</button>\n        <button id=\"audit-log-btn\" title=\"View audit log\" style=\"padding: 6px 12px;background: var(--btn-bg);color: var(--btn-text);border: 1px solid var(--edge-main);border-radius: 6px;cursor: pointer;font-size: 14px;font-weight: 600;\">Audit</button>\n        <button id=\"port-map-btn\" title=\"View all port connections\" style=\"padding: 6px 12px;background: var(--btn-bg);color: var(--btn-text);border: 1px solid var(--edge-main);border-radius: 6px;cursor: pointer;font-size: 14px;font-weight: 600;\">Ports</button>\n        <button id=\"secrets-btn\" title=\"Manage encrypted notes\" style=\"padding: 6px 12px;background: var(--btn-bg);color: var(--btn-text);border: 1px solid var(--edge-main);border-radius: 6px;cursor: pointer;font-size: 14px;font-weight: 600;\">Notes</button>\n\t\t<button id=\"mobile-export-btn\" title=\"Export topology\" style=\"padding: 6px 12px;background: var(--btn-bg);color: var(--btn-text);border: 1px solid var(--edge-main);border-radius: 6px;cursor: pointer;font-size: 14px;font-weight: 600;\">Export</button>\n        <button id=\"mobile-import-btn\" title=\"Import topology\" style=\"padding: 6px 12px;background: var(--btn-bg);color: var(--btn-text);border: 1px solid var(--edge-main);border-radius: 6px;cursor: pointer;font-size: 14px;font-weight: 600;\">Import</button>\n        <button id=\"settings-btn\" title=\"Page settings\">⚙️</button>\n      </div>\n      <button id=\"mobile-menu-toggle\" class=\"mobile-menu-btn\">☰</button>\n      <div class=\"header-resizer\" id=\"header-resizer\">\n        <svg class=\"resizer-icon\" width=\"40\" height=\"6\" viewBox=\"0 0 40 6\" xmlns=\"http://www.w3.org/2000/svg\">\n          <circle cx=\"14\" cy=\"3\" r=\"2\"></circle>\n          <circle cx=\"20\" cy=\"3\" r=\"2\"></circle>\n          <circle cx=\"26\" cy=\"3\" r=\"2\"></circle>\n        </svg>\n      </div>\n    </header>\n    <main>\n      <section class=\"topology-panel\">\n        <div class=\"draw-toolbar\" id=\"draw-toolbar\" style=\"display: flex !important;\">\n          <button type=\"button\" class=\"toolbar-close-btn\" id=\"draw-toolbar-close-btn\" title=\"Hide draw toolbar\">✕</button>\n          <button id=\"draw-toggle\" title=\"Draw custom line\">✏️</button>\n          <button id=\"rect-toggle\" title=\"Draw zone\">▭</button>\n          <button id=\"text-toggle\" title=\"Add text\">T</button>\n          <span style=\"border-left: 1px solid var(--edge-main); height: 20px; margin: 0 4px;\"></span>\n          <select id=\"rect-style\" title=\"zone style\">\n            <option value=\"filled\">Filled</option>\n            <option value=\"outlined\">Outlined</option>\n          </select>\n          <input type=\"color\" id=\"draw-color\" value=\"#f97316\" title=\"Line color\">\n          <select id=\"draw-style\" title=\"Line style\">\n  <option value=\"solid\">Solid</option>\n  <option value=\"dashed\">Dashed</option>\n  <option value=\"dotted\">Dotted</option>\n  <option value=\"wall\">Wall</option>\n</select>\n          <select id=\"draw-arrow\" title=\"Arrow direction\">\n            <option value=\"none\">No arrows</option>\n            <option value=\"forward\">→ Right</option>\n            <option value=\"backward\">← Left</option>\n            <option value=\"both\">↔ Both</option>\n          </select>\n          <button id=\"draw-undo\" style=\"display: none\" title=\"Undo last point\">Undo</button>\n        </div>\n        <div class=\"topology-toolbar\" id=\"topology-toolbar\" style=\"display: flex !important;\">\n          <button type=\"button\" class=\"toolbar-close-btn\" id=\"topology-toolbar-close-btn\" title=\"Hide add line toolbar\">✕</button>\n          <label for=\"add-line-select\">Add line to:</label>\n          <select id=\"add-line-select\"></select>\n          <input type=\"color\" id=\"add-line-color\" value=\"#475569\" title=\"Line color\" style=\"width: 30px;height: 24px;border: 1px solid var(--toolbar-border);border-radius: 4px;cursor: pointer;background: transparent;padding: 0;\">\n          <select id=\"add-line-direction\" title=\"Line direction\">\n            <option value=\"none\">No arrows</option>\n            <option value=\"forward\">→ Forward</option>\n            <option value=\"backward\">← Backward</option>\n            <option value=\"both\">↔ Both</option>\n          </select>\n          <select id=\"add-line-routing\" title=\"Line routing\">\n            <option value=\"orthogonal\">Orthogonal</option>\n            <option value=\"curved\">Curved</option>\n            <option value=\"straight\">Straight</option>\n          </select>\n          <button id=\"add-line-btn\">Add</button>\n        </div>\n        <div class=\"topology-toolbar bulk-toolbar-desktop\" id=\"bulk-toolbar\" style=\"display: none\">\n          <button type=\"button\" class=\"toolbar-close-btn\" id=\"bulk-toolbar-close\" title=\"Clear selection\">✕</button>\n          <label style=\"font-weight: 600; color: var(--accent)\">Selected: <span id=\"bulk-count\">0</span></label>\n          <button id=\"bulk-align-left\" title=\"Align left\">⬅ Left</button>\n          <button id=\"bulk-align-right\" title=\"Align right\">➡ Right</button>\n          <button id=\"bulk-align-top\" title=\"Align top\">⬆ Top</button>\n          <button id=\"bulk-align-bottom\" title=\"Align bottom\">⬇ Bottom</button>\n          <button id=\"bulk-distribute-h\" title=\"Distribute horizontally\">↔ Distribute H</button>\n          <button id=\"bulk-distribute-v\" title=\"Distribute vertically\">↕ Distribute V</button>\n          <button id=\"bulk-clone\" title=\"Clone selected\">📋 Clone</button>\n          <button id=\"bulk-zone-copy\" title=\"Copy zone style\">📡 Copy Zone</button>\n          <button id=\"bulk-zone-paste\" title=\"Paste zone style\">📡 Paste Zone</button>\n          <button id=\"bulk-zone-toggle\" title=\"Toggle zones on selected\">📡 Toggle Zones</button>\n          <button id=\"bulk-delete\" title=\"Delete selected\" style=\"background: var(--danger); color: white;\">Delete</button>\n        </div>\n        <div class=\"bulk-toolbar-mobile\" id=\"bulk-toolbar-mobile\" style=\"\">\n          <button type=\"button\" id=\"bulk-mobile-btn\" style=\"background: var(--accent);color: white;padding: 12px 20px;border-radius: 25px;font-weight: 600;font-size: 16px;box-shadow: 0 4px 12px rgba(0,0,0,0.3);border: none;cursor: pointer;display: flex;align-items: center;gap: 8px;\">\n          <span id=\"bulk-count-mobile\">0</span>Selected</button>\n        </div>\n        <div id=\"bulk-actions-modal\" style=\"display: none;position: fixed;bottom: 0;left: 0;right: 0;background: var(--panel-alt);border-top-left-radius: 20px;border-top-right-radius: 20px;padding: 20px;padding-bottom: env(safe-area-inset-bottom, 20px);box-shadow: 0 -4px 20px rgba(0,0,0,0.5);z-index: 10000;max-height: calc(100vh - 80px);overflow-y: auto;\">\n          <div style=\"display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;\">\n            <h3 style=\"margin: 0; color: var(--accent); font-size: 20px;\">\n              <span id=\"bulk-count-modal\">0</span> Nodes Selected\n            </h3>\n            <button id=\"bulk-modal-close\" style=\"background: none;border: none;font-size: 24px;cursor: pointer;color: var(--text-main);padding: 0;width: 32px;height: 32px;\">✕</button>\n          </div>\n          <div style=\"display: grid; grid-template-columns: 1fr 1fr; gap: 12px;\">\n            <button id=\"bulk-align-left-mobile\" class=\"bulk-action-btn\">⬅<br><span style=\"font-size: 12px;\">Align Left</span></button>\n            <button id=\"bulk-align-right-mobile\" class=\"bulk-action-btn\">➡<br><span style=\"font-size: 12px;\">Align Right</span></button>\n            <button id=\"bulk-align-top-mobile\" class=\"bulk-action-btn\">⬆<br><span style=\"font-size: 12px;\">Align Top</span></button>\n            <button id=\"bulk-align-bottom-mobile\" class=\"bulk-action-btn\">⬇<br><span style=\"font-size: 12px;\">Align Bottom</span></button>\n            <button id=\"bulk-distribute-h-mobile\" class=\"bulk-action-btn\">↔<br><span style=\"font-size: 12px;\">Distribute H</span></button>\n            <button id=\"bulk-distribute-v-mobile\" class=\"bulk-action-btn\">↕<br><span style=\"font-size: 12px;\">Distribute V</span></button>\n            <button id=\"bulk-lock-mobile\" class=\"bulk-action-btn\">🔒<br><span style=\"font-size: 12px;\">Lock Toggle</span></button>\n            <button id=\"bulk-group-mobile\" class=\"bulk-action-btn\">⭕<br><span style=\"font-size: 12px;\">Group Toggle</span></button>\n            <button id=\"bulk-clone-mobile\" class=\"bulk-action-btn\">📋<br><span style=\"font-size: 12px;\">Clone All</span></button>\n            <button id=\"bulk-delete-mobile\" class=\"bulk-action-btn\" style=\"background: var(--danger); color: white;\">🗑<br><span style=\"font-size: 12px;\">Delete All</span></button>\n          </div>\n          <div style=\"margin-top: 16px; padding-top: 16px; border-top: 1px solid var(--edge-main);\">\n            <div style=\"font-size: 12px; color: var(--text-soft); margin-bottom: 12px; text-transform: uppercase; letter-spacing: 0.05em;\">Coverage Zones</div>\n            <div style=\"display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 12px;\">\n              <button id=\"bulk-zone-copy-mobile\" class=\"bulk-action-btn\">📡<br><span style=\"font-size: 12px;\">Copy Zone</span></button>\n              <button id=\"bulk-zone-paste-mobile\" class=\"bulk-action-btn\">📡<br><span style=\"font-size: 12px;\">Paste Zone</span></button>\n              <button id=\"bulk-zone-toggle-mobile\" class=\"bulk-action-btn\">📡<br><span style=\"font-size: 12px;\">Toggle Zones</span></button>\n            </div>\n          </div>\n          <button id=\"bulk-clear-mobile\" style=\"width: 100%;margin-top: 16px;padding: 14px;background: var(--panel-main);border: 1px solid var(--edge-main);border-radius: 8px;color: var(--text-main);font-size: 16px;cursor: pointer;\">Clear Selection</button>\n        </div>\n        <div class=\"canvas-hint\" id=\"canvas-hint\" style=\"cursor: pointer;\">\n          <ul>\n            <li>Scroll to zoom</li>\n            <li>Drag to pan</li>\n            <li>Right click to clone and align</li>\n            <li>Right click to select multiple</li>\n\t\t\t<li>Hold Shift + drag mouse for marquee selection</li>\n            <li>You have the power</li>\n            <li>Your time is NOW!</li>\n          </ul>\n        </div>\n        <div class=\"legend-container\" id=\"edge-legend\" style=\"display: flex;\"><div class=\"legend-title\">Line Legend</div><button type=\"button\" class=\"legend-close-btn\">✕</button><div class=\"legend-item\"><span class=\"legend-swatch\" style=\"background-color: rgb(16, 185, 129); cursor: pointer;\"></span><span class=\"legend-label\" contenteditable=\"true\">Trusted Lan</span></div><div class=\"legend-item\"><span class=\"legend-swatch\" style=\"background-color: rgb(245, 158, 11); cursor: pointer;\"></span><span class=\"legend-label\" contenteditable=\"true\">Secure Lan</span></div><div class=\"legend-item\"><span class=\"legend-swatch\" style=\"background-color: rgb(239, 68, 68); cursor: pointer;\"></span><span class=\"legend-label\" contenteditable=\"true\">DMZ</span></div><div class=\"legend-item\"><span class=\"legend-swatch\" style=\"background-color: rgb(71, 85, 105); cursor: pointer;\"></span><span class=\"legend-label\" contenteditable=\"true\">Main ISP</span></div><div class=\"legend-item\"><span class=\"legend-swatch\" style=\"background-color: rgb(59, 130, 246); cursor: pointer;\"></span><span class=\"legend-label\" contenteditable=\"true\">Alternate ISP</span></div><div class=\"legend-item\"><span class=\"legend-swatch\" style=\"background-color: rgb(139, 92, 246); cursor: pointer;\"></span><span class=\"legend-label\" contenteditable=\"true\">you can edit me too</span></div><div class=\"legend-item\"><span class=\"legend-swatch\" style=\"background-color: rgb(6, 182, 212); cursor: pointer;\"></span><span class=\"legend-label\" contenteditable=\"true\">you can edit me too</span></div><div class=\"legend-item\"><span class=\"legend-swatch\" style=\"background-color: rgb(168, 85, 247); cursor: pointer;\"></span><span class=\"legend-label\" contenteditable=\"true\">you can edit me too</span></div><div class=\"legend-item\"><span class=\"legend-swatch\" style=\"background-color: rgb(249, 115, 22); cursor: pointer;\"></span><span class=\"legend-label\" contenteditable=\"true\">you can edit me too</span></div><div class=\"legend-item\"><span class=\"legend-swatch\" style=\"background-color: rgb(14, 165, 233); cursor: pointer;\"></span><span class=\"legend-label\" contenteditable=\"true\">you can edit me too</span></div><div class=\"legend-item\"><span class=\"legend-swatch\" style=\"background-color: rgb(34, 197, 94); cursor: pointer;\"></span><span class=\"legend-label\" contenteditable=\"true\">you can edit me too</span></div><div class=\"legend-item\"><span class=\"legend-swatch\" style=\"background-color: rgb(148, 163, 184); cursor: pointer;\"></span><span class=\"legend-label\" contenteditable=\"true\">you can edit me too</span></div><div class=\"legend-item\"><span class=\"legend-swatch\" style=\"background-color: rgb(251, 191, 36); cursor: pointer;\"></span><span class=\"legend-label\" contenteditable=\"true\">you can edit me too</span></div><div class=\"legend-item\"><span class=\"legend-swatch\" style=\"background-color: rgb(56, 189, 248); cursor: pointer;\"></span><span class=\"legend-label\" contenteditable=\"true\">you can edit me too</span></div><div class=\"legend-item\"><span class=\"legend-swatch\" style=\"background-color: rgb(200, 0, 255); cursor: pointer;\"></span><span class=\"legend-label\" contenteditable=\"true\">you can edit me too</span></div></div>\n        <div class=\"canvas-viewport\" id=\"canvas-viewport\">\n          <svg id=\"map\" viewBox=\"-191.26917538063572 -261.51832729948296 4570.339735330435 3427.7548014978265\" style=\"\"></svg>\n        </div>\n        <div class=\"minimap-zoom-wrapper\" id=\"minimap-zoom-wrapper\" style=\"display: block;\">\n          <button type=\"button\" class=\"minimap-close-btn\" id=\"minimap-close-btn\" title=\"Hide map &amp; zoom\">✕</button>\n          <div class=\"minimap-container\" id=\"minimap-container\">\n            <svg id=\"minimap\" viewBox=\"0 0 4000 3000\">\n              <rect x=\"904.5889892578125\" y=\"115.40318298339844\" width=\"110.93878173828125\" height=\"919.6242218017578\" class=\"minimap-wall\" style=\"fill: rgb(82, 21, 249); fill-opacity: 0.6; stroke: rgb(82, 21, 249); stroke-width: 4;\"></rect><rect x=\"130.93685150146484\" y=\"1072.3624877929688\" width=\"872.9131851196289\" height=\"99.260986328125\" class=\"minimap-wall\" style=\"fill: rgb(82, 21, 249); fill-opacity: 0.6; stroke: rgb(82, 21, 249); stroke-width: 4;\"></rect><rect class=\"minimap-viewport\" id=\"minimap-viewport\" x=\"-191.26917538063572\" y=\"-261.51832729948296\" width=\"4570.339735330435\" height=\"3427.7548014978265\"></rect>\n            </svg>\n          </div>\n          <div class=\"zoom-toolbar\" id=\"zoom-toolbar\">\n            <button id=\"zoom-in-btn\" title=\"Zoom in\">+</button>\n            <div class=\"zoom-level\" id=\"zoom-level\">88%</div>\n            <button id=\"zoom-out-btn\" title=\"Zoom out\">-</button>\n            <div class=\"divider\"></div>\n            <button id=\"zoom-fit-btn\" title=\"Fit to view\">[ ]</button>\n            <button id=\"zoom-reset-btn\" title=\"Reset view\">R</button>\n          </div>\n        </div>\n        <button type=\"button\" id=\"edge-legend-mini\" class=\"legend-mini-btn\" style=\"bottom: 10px; display: none;\">Connection &amp; Zone Legend</button>\n        <button type=\"button\" id=\"minimap-mini\" class=\"legend-mini-btn\" style=\"right: 10px; left: auto; bottom: 10px; display: none;\">Map</button>\n        <button type=\"button\" id=\"draw-toolbar-mini\" class=\"legend-mini-btn\" style=\"top: 10px; left: 10px; right: auto;\">Draw</button>\n        <button type=\"button\" id=\"topology-toolbar-mini\" class=\"legend-mini-btn\" style=\"top: 10px; left: auto; right: 40px;\">Add Connection</button></section>\n      <aside class=\"details-panel\" id=\"details-panel\">\n        <div class=\"sidebar-resizer\" id=\"sidebar-resizer\">\n          <svg class=\"resizer-icon\" width=\"6\" height=\"40\" viewBox=\"0 0 6 40\" xmlns=\"http://www.w3.org/2000/svg\">\n            <circle cx=\"3\" cy=\"14\" r=\"2\"></circle>\n            <circle cx=\"3\" cy=\"20\" r=\"2\"></circle>\n            <circle cx=\"3\" cy=\"26\" r=\"2\"></circle>\n          </svg>\n        </div>\n        <div class=\"mobile-footer-resizer\" id=\"mobile-footer-resizer\">\n          <svg class=\"resizer-icon\" width=\"40\" height=\"6\" viewBox=\"0 0 40 6\" xmlns=\"http://www.w3.org/2000/svg\">\n            <circle cx=\"14\" cy=\"3\" r=\"2\"></circle>\n            <circle cx=\"20\" cy=\"3\" r=\"2\"></circle>\n            <circle cx=\"26\" cy=\"3\" r=\"2\"></circle>\n          </svg>\n        </div>\n        <div id=\"node-panel\" style=\"display: none;\">\n          <div class=\"details-name editable-text\" id=\"node-name\">Core Router 1</div>\n          <div class=\"details-ip editable-text\" id=\"node-ip\">10.0.0.1</div>\n          <div class=\"details-role\" id=\"node-role\">Core Routing</div>\n          <div class=\"details-info-row\">\n            <span style=\"color: var(--text-soft); font-size: 14px;\">MAC:</span>\n            <span class=\"editable-text\" id=\"node-mac\" style=\"margin-left: 8px; font-size: 14px;\">00:1A:2B:3C:4D:01</span>\n          </div>\n          <div class=\"details-info-row\">\n            <span style=\"color: var(--text-soft); font-size: 14px;\">Rack Unit:</span>\n            <span class=\"editable-text\" id=\"node-rack\" style=\"margin-left: 8px; font-size: 14px;\">--</span>\n          </div>\n          <div class=\"details-info-row\" id=\"uheight-row\" style=\"display: none;\">\n            <span style=\"color: var(--text-soft); font-size: 14px;\">U Height:</span>\n            <span class=\"editable-text\" id=\"node-uheight\" style=\"margin-left: 8px; font-size: 14px;\">2U</span>\n          </div>\n          <div class=\"details-info-row\">\n            <span style=\"color: var(--text-soft); font-size: 14px;\">Layer:</span>\n            <select id=\"node-layer\" style=\"margin-left: 8px; padding: 4px 8px; background: var(--panel); color: var(--text-main); border: 1px solid var(--edge-main); border-radius: 4px; cursor: pointer; font-size: 14px;\">\n              <option value=\"physical\">Physical</option>\n              <option value=\"logical\">Logical</option>\n              <option value=\"security\">Security</option>\n              <option value=\"application\">Application</option>\n            </select>\n          </div>\n          <div class=\"details-info-row\" id=\"assigned-rack-row\" style=\"display: flex;\">\n            <span style=\"color: var(--text-soft); font-size: 14px;\">Assigned Rack:</span>\n            <select id=\"node-assigned-rack\" style=\"margin-left: 8px; padding: 4px 8px; background: var(--panel); color: var(--text-main); border: 1px solid var(--edge-main); border-radius: 4px; cursor: pointer; font-size: 14px;\"><option value=\"\">None</option><option value=\"dc-rack-a1\">DC Rack A1</option><option value=\"dc-rack-a2\">DC Rack A2</option><option value=\"dc-rack-b1\">DC Rack B1</option><option value=\"dc-rack-b2\">DC Rack B2</option><option value=\"dmz-rack\">DMZ Rack</option><option value=\"mgmt-rack\">Management Rack</option></select>\n          </div>\n          <div class=\"details-info-row\" id=\"rack-capacity-row\" style=\"display: none;\">\n            <span style=\"color: var(--text-soft); font-size: 14px;\">Rack Capacity:</span>\n            <select id=\"node-rack-capacity\" style=\"margin-left: 8px; padding: 4px 8px; background: var(--panel); color: var(--text-main); border: 1px solid var(--edge-main); border-radius: 4px; cursor: pointer; font-size: 14px;\">\n              <option value=\"42\">42U</option>\n              <option value=\"48\">48U</option>\n              <option value=\"24\">24U</option>\n              <option value=\"12\">12U</option>\n\t\t\t  <option value=\"6\">6U</option>\n            </select>\n          </div>\n\t\t  <details id=\"rack-contents-section\" style=\"display: none; margin-top: 12px; margin-bottom: 12px; padding: 10px; background: var(--panel-alt); border-radius: 6px;\" open=\"\">\n            <summary style=\"color: var(--accent); font-weight: 600; font-size: 14px; cursor: pointer;\">Nodes in Rack (<span id=\"rack-contents-count\">0</span>)</summary>\n            <div id=\"rack-contents-list\" style=\"max-height: 200px; overflow-y: auto; margin-top: 8px;\"></div>\n          </details>\n\t\t  \n          <details id=\"fov-section\" style=\"display: block; margin-top: 12px; margin-bottom: 12px; padding: 10px; background: var(--panel-alt); border-radius: 6px;\">\n            <summary style=\"color: var(--accent); font-weight: 600; font-size: 14px; cursor: pointer;\">Coverage Zone</summary>\n            <div style=\"padding: 10px 0;\">\n              <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px; gap: 8px;\">\n                <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Preset:</label>\n                <select id=\"fov-preset\" style=\"flex: 1; padding: 4px 8px; background: var(--panel); border: 1px solid var(--edge-main); border-radius: 4px; color: var(--text-main);\">\n                  <option value=\"\">-- Apply Preset --</option>\n                  <option value=\"security-cam\">Security Camera</option>\n                  <option value=\"ptz-cam\">PTZ Camera</option>\n                  <option value=\"motion-detect\">Motion Detector</option>\n                  <option value=\"wifi-strong\">WiFi</option>\n                  <option value=\"wifi-extended\">WiFi Extender</option>\n                  <option value=\"smoke-alarm\">Smoke Alarm</option>\n                  <option value=\"sprinkler-arc\">Sprinkler Arc</option>\n                </select>\n                <button id=\"fov-save-preset\" title=\"Save as preset\" style=\"padding: 4px 8px; background: var(--panel); border: 1px solid var(--edge-main); border-radius: 4px; color: var(--text-main); cursor: pointer;\">💾</button>\n                <button id=\"fov-copy-style\" title=\"Copy zone style\" style=\"padding: 4px 8px; background: var(--panel); border: 1px solid var(--edge-main); border-radius: 4px; color: var(--text-main); cursor: pointer;\">📋</button>\n                <button id=\"fov-paste-style\" title=\"Paste zone style\" style=\"padding: 4px 8px; background: var(--panel); border: 1px solid var(--edge-main); border-radius: 4px; color: var(--text-main); cursor: pointer;\">📥</button>\n              </div>\n              <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Show Zone:</label>\n                <input type=\"checkbox\" id=\"fov-enabled\" style=\"width: 18px; height: 18px; cursor: pointer;\">\n              </div>\n              <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Angle:</label>\n                <input type=\"range\" id=\"fov-angle\" min=\"10\" max=\"360\" value=\"90\" style=\"flex: 1;\">\n                <span id=\"fov-angle-value\" style=\"min-width: 40px; text-align: right; color: var(--text-main);\">360°</span>\n              </div>\n              <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Distance:</label>\n                <input type=\"range\" id=\"fov-distance\" min=\"50\" max=\"500\" value=\"150\" style=\"flex: 1;\">\n                <span id=\"fov-distance-value\" style=\"min-width: 40px; text-align: right; color: var(--text-main);\">200</span>\n              </div>\n              <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Inner Radius:</label>\n                <input type=\"range\" id=\"fov-inner-radius\" min=\"0\" max=\"200\" value=\"0\" style=\"flex: 1;\">\n                <span id=\"fov-inner-radius-value\" style=\"min-width: 40px; text-align: right; color: var(--text-main);\">0</span>\n              </div>\n              <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Rotation:</label>\n                <input type=\"range\" id=\"fov-rotation\" min=\"0\" max=\"360\" value=\"0\" style=\"flex: 1;\">\n                <span id=\"fov-rotation-value\" style=\"min-width: 40px; text-align: right; color: var(--text-main);\">0°</span>\n              </div>\n              <div style=\"border-top: 1px solid var(--edge-main); margin-top: 10px; padding-top: 10px;\">\n                <div style=\"font-size: 12px; color: var(--text-soft); margin-bottom: 8px; text-transform: uppercase; letter-spacing: 0.05em;\">Fill</div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Color:</label>\n                  <input type=\"color\" id=\"fov-color\" value=\"#f59e0b\" style=\"width: 50px; height: 30px; border: 1px solid var(--edge-main); border-radius: 4px; cursor: pointer;\">\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Opacity:</label>\n                  <input type=\"range\" id=\"fov-opacity\" min=\"5\" max=\"80\" value=\"20\" style=\"flex: 1;\">\n                  <span id=\"fov-opacity-value\" style=\"min-width: 40px; text-align: right; color: var(--text-main);\">20%</span>\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Gradient:</label>\n                  <input type=\"checkbox\" id=\"fov-gradient\" style=\"width: 18px; height: 18px; cursor: pointer;\">\n                  <span style=\"color: var(--text-soft); font-size: 12px; margin-left: 8px;\">Fade toward edge</span>\n                </div>\n              </div>\n              <div style=\"border-top: 1px solid var(--edge-main); margin-top: 10px; padding-top: 10px;\">\n                <div style=\"font-size: 12px; color: var(--text-soft); margin-bottom: 8px; text-transform: uppercase; letter-spacing: 0.05em;\">Border</div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Color:</label>\n                  <input type=\"color\" id=\"fov-border-color\" value=\"#f59e0b\" style=\"width: 50px; height: 30px; border: 1px solid var(--edge-main); border-radius: 4px; cursor: pointer;\">\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Width:</label>\n                  <input type=\"range\" id=\"fov-border-width\" min=\"0\" max=\"10\" value=\"2\" style=\"flex: 1;\">\n                  <span id=\"fov-border-width-value\" style=\"min-width: 40px; text-align: right; color: var(--text-main);\">2</span>\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Style:</label>\n                  <select id=\"fov-border-style\" style=\"flex: 1; padding: 4px 8px; background: var(--panel); border: 1px solid var(--edge-main); border-radius: 4px; color: var(--text-main);\">\n                    <option value=\"solid\">Solid</option>\n                    <option value=\"dashed\">Dashed</option>\n                    <option value=\"dotted\">Dotted</option>\n                  </select>\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Opacity:</label>\n                  <input type=\"range\" id=\"fov-border-opacity\" min=\"0\" max=\"100\" value=\"100\" style=\"flex: 1;\">\n                  <span id=\"fov-border-opacity-value\" style=\"min-width: 40px; text-align: right; color: var(--text-main);\">100%</span>\n                </div>\n              </div>\n              <div style=\"border-top: 1px solid var(--edge-main); margin-top: 10px; padding-top: 10px;\">\n                <div style=\"font-size: 12px; color: var(--text-soft); margin-bottom: 8px; text-transform: uppercase; letter-spacing: 0.05em;\">Label</div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Text:</label>\n                  <input type=\"text\" id=\"fov-label\" placeholder=\"e.g. Detection Zone\" style=\"flex: 1; padding: 4px 8px; background: var(--panel); border: 1px solid var(--edge-main); border-radius: 4px; color: var(--text-main);\">\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Position:</label>\n                  <select id=\"fov-label-position\" style=\"flex: 1; padding: 4px 8px; background: var(--panel); border: 1px solid var(--edge-main); border-radius: 4px; color: var(--text-main);\">\n                    <option value=\"center\">Center</option>\n                    <option value=\"edge\">Edge</option>\n                    <option value=\"outside\">Outside</option>\n                  </select>\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Font Size:</label>\n                  <input type=\"range\" id=\"fov-label-size\" min=\"8\" max=\"32\" value=\"14\" style=\"flex: 1;\">\n                  <span id=\"fov-label-size-value\" style=\"min-width: 40px; text-align: right; color: var(--text-main);\">14px</span>\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Font Color:</label>\n                  <input type=\"color\" id=\"fov-label-color\" value=\"#ffffff\" style=\"width: 50px; height: 30px; border: 1px solid var(--edge-main); border-radius: 4px; cursor: pointer;\">\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Bold:</label>\n                  <input type=\"checkbox\" id=\"fov-label-bold\" style=\"width: 18px; height: 18px; cursor: pointer;\">\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Background:</label>\n                  <input type=\"checkbox\" id=\"fov-label-bg\" style=\"width: 18px; height: 18px; cursor: pointer;\">\n                  <input type=\"color\" id=\"fov-label-bg-color\" value=\"#000000\" style=\"width: 50px; height: 30px; border: 1px solid var(--edge-main); border-radius: 4px; cursor: pointer; margin-left: 8px;\">\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Offset X:</label>\n                  <input type=\"range\" id=\"fov-label-offset-x\" min=\"-200\" max=\"200\" value=\"0\" style=\"flex: 1;\">\n                  <span id=\"fov-label-offset-x-value\" style=\"min-width: 40px; text-align: right; color: var(--text-main);\">0</span>\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Offset Y:</label>\n                  <input type=\"range\" id=\"fov-label-offset-y\" min=\"-200\" max=\"200\" value=\"0\" style=\"flex: 1;\">\n                  <span id=\"fov-label-offset-y-value\" style=\"min-width: 40px; text-align: right; color: var(--text-main);\">0</span>\n                </div>\n              </div>\n              <div style=\"border-top: 1px solid var(--edge-main); margin-top: 10px; padding-top: 10px;\">\n                <div style=\"font-size: 12px; color: var(--text-soft); margin-bottom: 8px; text-transform: uppercase; letter-spacing: 0.05em;\">Animation</div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Animate:</label>\n                  <input type=\"checkbox\" id=\"fov-animate\" style=\"width: 18px; height: 18px; cursor: pointer;\">\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Type:</label>\n                  <select id=\"fov-animation-type\" style=\"flex: 1; padding: 4px 8px; background: var(--panel); border: 1px solid var(--edge-main); border-radius: 4px; color: var(--text-main);\">\n                    <option value=\"sweep\">Sweep (Pan)</option>\n                    <option value=\"pulse\">Pulse (Breathe)</option>\n                    <option value=\"rings\">Rings (Emanate)</option>\n                    <option value=\"spin\">Spin (Rotate)</option>\n                  </select>\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Sweep:</label>\n                  <input type=\"range\" id=\"fov-sweep\" min=\"30\" max=\"360\" value=\"120\" style=\"flex: 1;\">\n                  <span id=\"fov-sweep-value\" style=\"min-width: 40px; text-align: right; color: var(--text-main);\">120°</span>\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Speed:</label>\n                  <input type=\"range\" id=\"fov-speed\" min=\"1\" max=\"60\" value=\"4\" style=\"flex: 1;\">\n                  <span id=\"fov-speed-value\" style=\"min-width: 40px; text-align: right; color: var(--text-main);\">4s</span>\n                </div>\n              </div>\n            </div>\n          </details>\n          <details id=\"node-connections-section\" ...=\"\" <details=\"\" style=\"margin-top: 12px; margin-bottom: 12px; padding: 10px; background: var(--panel-alt); border-radius: 6px; display: block;\">\n            <summary style=\"color: var(--accent); font-weight: 600; font-size: 14px; cursor: pointer;\">Connections (<span id=\"node-connections-count\">9</span>)</summary>\n            <div id=\"node-connections-list\" style=\"max-height: 200px; overflow-y: auto; margin-top: 8px;\"><div style=\"padding: 6px 0px; border-bottom: 1px solid var(--edge-main); display: flex; align-items: center; font-size: 13px;\"><span title=\"Click to view connection\" style=\"color: var(--accent); font-family: monospace; cursor: pointer;\">Gi1/0/1</span><span style=\"color: var(--text-soft); margin: 0px 8px;\">↔</span><span title=\"Click to view connection\" style=\"color: var(--text-main); cursor: pointer;\">ISP Primary</span><span title=\"Click to view connection\" style=\"color: var(--accent); font-family: monospace; margin-left: 6px; cursor: pointer;\">(Gi0/0)</span></div><div style=\"padding: 6px 0px; border-bottom: 1px solid var(--edge-main); display: flex; align-items: center; font-size: 13px;\"><span title=\"Click to view connection\" style=\"color: var(--accent); font-family: monospace; cursor: pointer;\">Gi1/0/24</span><span style=\"color: var(--text-soft); margin: 0px 8px;\">↔</span><span title=\"Click to view connection\" style=\"color: var(--text-main); cursor: pointer;\">Core Router 2</span><span title=\"Click to view connection\" style=\"color: var(--accent); font-family: monospace; margin-left: 6px; cursor: pointer;\">(Gi1/0/24)</span></div><div style=\"padding: 6px 0px; border-bottom: 1px solid var(--edge-main); display: flex; align-items: center; font-size: 13px;\"><span title=\"Click to set port\" style=\"color: var(--text-soft); cursor: pointer;\">-</span><span style=\"color: var(--text-soft); margin: 0px 8px;\">↔</span><span title=\"Click to view connection\" style=\"color: var(--text-main); cursor: pointer;\">External FW 1</span><span title=\"Click to set port\" style=\"color: var(--text-soft); margin-left: 6px; cursor: pointer;\">(-)</span></div><div style=\"padding: 6px 0px; border-bottom: 1px solid var(--edge-main); display: flex; align-items: center; font-size: 13px;\"><span title=\"Click to set port\" style=\"color: var(--text-soft); cursor: pointer;\">-</span><span style=\"color: var(--text-soft); margin: 0px 8px;\">↔</span><span title=\"Click to view connection\" style=\"color: var(--text-main); cursor: pointer;\">NYC Branch Router</span><span title=\"Click to set port\" style=\"color: var(--text-soft); margin-left: 6px; cursor: pointer;\">(-)</span></div><div style=\"padding: 6px 0px; border-bottom: 1px solid var(--edge-main); display: flex; align-items: center; font-size: 13px;\"><span title=\"Click to set port\" style=\"color: var(--text-soft); cursor: pointer;\">-</span><span style=\"color: var(--text-soft); margin: 0px 8px;\">↔</span><span title=\"Click to view connection\" style=\"color: var(--text-main); cursor: pointer;\">LA Branch Router</span><span title=\"Click to set port\" style=\"color: var(--text-soft); margin-left: 6px; cursor: pointer;\">(-)</span></div><div style=\"padding: 6px 0px; border-bottom: 1px solid var(--edge-main); display: flex; align-items: center; font-size: 13px;\"><span title=\"Click to set port\" style=\"color: var(--text-soft); cursor: pointer;\">-</span><span style=\"color: var(--text-soft); margin: 0px 8px;\">↔</span><span title=\"Click to view connection\" style=\"color: var(--text-main); cursor: pointer;\">Chicago Branch Router</span><span title=\"Click to set port\" style=\"color: var(--text-soft); margin-left: 6px; cursor: pointer;\">(-)</span></div><div style=\"padding: 6px 0px; border-bottom: 1px solid var(--edge-main); display: flex; align-items: center; font-size: 13px;\"><span title=\"Click to set port\" style=\"color: var(--text-soft); cursor: pointer;\">-</span><span style=\"color: var(--text-soft); margin: 0px 8px;\">↔</span><span title=\"Click to view connection\" style=\"color: var(--text-main); cursor: pointer;\">London Branch Router</span><span title=\"Click to set port\" style=\"color: var(--text-soft); margin-left: 6px; cursor: pointer;\">(-)</span></div><div style=\"padding: 6px 0px; border-bottom: 1px solid var(--edge-main); display: flex; align-items: center; font-size: 13px;\"><span title=\"Click to set port\" style=\"color: var(--text-soft); cursor: pointer;\">-</span><span style=\"color: var(--text-soft); margin: 0px 8px;\">↔</span><span title=\"Click to view connection\" style=\"color: var(--text-main); cursor: pointer;\">Tokyo Branch Router</span><span title=\"Click to set port\" style=\"color: var(--text-soft); margin-left: 6px; cursor: pointer;\">(-)</span></div><div style=\"padding: 6px 0px; border-bottom: 1px solid var(--edge-main); display: flex; align-items: center; font-size: 13px;\"><span title=\"Click to set port\" style=\"color: var(--text-soft); cursor: pointer;\">-</span><span style=\"color: var(--text-soft); margin: 0px 8px;\">↔</span><span title=\"Click to view connection\" style=\"color: var(--text-main); cursor: pointer;\">AWS Cloud</span><span title=\"Click to set port\" style=\"color: var(--text-soft); margin-left: 6px; cursor: pointer;\">(-)</span></div></div>\n          </details>\n          <div class=\"badge-row\" id=\"node-tags\"></div>\n          <div class=\"size-controls\">\n            <label>Size:</label>\n            <input type=\"range\" id=\"size-slider\" min=\"20\" max=\"200\" value=\"55\">\n            <span id=\"size-value\">55</span>\n            <button id=\"reset-size\">Reset</button>\n          </div>\n          <div class=\"size-controls\">\n            <label>Rotation:</label>\n            <input type=\"range\" id=\"rotation-slider\" min=\"-360\" max=\"360\" value=\"0\">\n            <input type=\"number\" id=\"rotation-value\" value=\"0\" style=\"width: 60px; padding: 4px; background: var(--panel); border: 1px solid var(--edge-main); border-radius: 4px; color: var(--text-main); text-align: center;\">\n            <span style=\"color: var(--text-soft);\">°</span>\n            <button id=\"reset-rotation\">Reset</button>\n          </div>\n          <details class=\"style-section\">\n            <summary>Styling</summary>\n            <div class=\"style-content\">\n              <div class=\"style-row\">\n                <label>Screen:</label>\n                <select id=\"style-scope\">\n                  <option value=\"all\">All</option>\n                  <option value=\"desktop\">Desktop</option>\n                  <option value=\"tablet\">Tablet</option>\n                  <option value=\"mobile\">Mobile</option>\n                  <option value=\"fold\">Fold</option>\n                </select>\n              </div>\n              <div class=\"style-row\">\n                <label>Shape:</label>\n                <select id=\"shape-select\">\n                  <optgroup label=\"Basic Shapes\">\n\t\t\t\t  <option value=\"circle\">Circle</option>\n\t\t\t\t  <option value=\"square\">Square</option>\n\t\t\t\t  <option value=\"rectangle\">Rectangle</option>\n\t\t\t\t  <option value=\"triangle\">Triangle</option>\n\t\t\t\t  <option value=\"hexagon\">Hexagon</option>\n\t\t\t\t  <option value=\"diamond\">Diamond</option>\n\t\t\t\t  <option value=\"star\">Star</option>\n\t\t\t\t  <option value=\"stop-sign\">Stop Sign</option>\n\t\t\t\t  <option value=\"octagon\">Octagon</option>\n\t\t\t\t  <option value=\"pentagon\">Pentagon</option>\n\t\t\t\t  <option value=\"cross\">Cross</option>\n\t\t\t\t  <option value=\"rounded-square\">Rounded Square</option>\n\t\t\t\t  <option value=\"pill\">Pill</option>\n\t\t\t\t  <option value=\"parallelogram\">Parallelogram</option>\n\t\t\t\t  <option value=\"trapezoid\">Trapezoid</option>\n\t\t\t\t</optgroup>\n\t\t\t  <optgroup label=\"Computers &amp; Devices\">\n\t\t\t      <option value=\"server\">Server</option>\n\t\t\t\t  <option value=\"pc\">PC / Desktop</option>\n\t\t\t\t  <option value=\"laptop\">Laptop</option>\n\t\t\t\t  <option value=\"phone\">Phone / Mobile</option>\n\t\t\t\t  <option value=\"printer\">Printer</option>\n\t\t\t\t  <option value=\"pi\">Raspberry Pi</option>\n\t\t\t\t  <option value=\"sensor\">Sensor / IoT</option>\n\t\t\t  </optgroup>\n\t\t\t  <optgroup label=\"Network Equipment\">\n\t\t\t\t  <option value=\"router\">Router</option>\n\t\t\t\t  <option value=\"switch\">Switch</option>\n\t\t\t\t  <option value=\"firewall\">Firewall</option>\n\t\t\t\t  <option value=\"access-point\">Access Point</option>\n\t\t\t\t  <option value=\"load-balancer\">Load Balancer</option>\n\t\t\t\t  <option value=\"gateway\">Gateway</option>\n\t\t\t\t  <option value=\"vpn\">VPN / Tunnel</option>\n\t\t\t\t  <option value=\"nas\">NAS / Storage</option>\n\t\t\t  </optgroup>\n\t\t\t  <optgroup label=\"Cloud &amp; Services\">\n\t\t\t      <option value=\"cloud\">Cloud</option>\n\t\t\t\t  <option value=\"database\">Database</option>\n\t\t\t\t  <option value=\"docker\">Docker</option>\n\t\t\t\t  <option value=\"container\">Container</option>\n\t\t\t\t  <option value=\"vm\">Virtual Machine</option>\n\t\t\t\t  <option value=\"kubernetes\">Kubernetes</option>\n\t\t\t\t  <option value=\"api\">API / Endpoint</option>\n\t\t\t\t  <option value=\"queue\">Queue / Message</option>\n\t\t\t\t  <option value=\"lambda\">Lambda / Function</option>\n\t\t\t\t  <option value=\"bucket\">Bucket / S3</option>\n\t\t\t  </optgroup>\n\t\t\t  <optgroup label=\"Security &amp; Monitoring\">\n\t\t\t\t  <option value=\"shield\">Shield</option>\n\t\t\t\t  <option value=\"camera\">Camera / CCTV</option>\n\t\t\t\t  <option value=\"monitor\">Monitor / Dashboard</option>\n\t\t\t  </optgroup>\n\t\t\t  <optgroup label=\"Smart Home\">\n\t\t\t\t  <option value=\"thermostat\">Thermostat</option>\n\t\t\t\t  <option value=\"doorbell\">Video Doorbell</option>\n\t\t\t\t  <option value=\"smart-lock\">Smart Lock</option>\n\t\t\t\t  <option value=\"smart-bulb\">Smart Bulb</option>\n\t\t\t\t  <option value=\"smart-plug\">Smart Plug</option>\n\t\t\t\t  <option value=\"smart-speaker\">Smart Speaker</option>\n\t\t\t\t  <option value=\"smart-tv\">Smart TV</option>\n\t\t\t\t  <option value=\"hub\">Smart Hub</option>\n\t\t\t\t  <option value=\"smoke-detector\">Smoke Detector</option>\n\t\t\t\t  <option value=\"motion-sensor\">Motion Sensor</option>\n\t\t\t\t  <option value=\"garage\">Garage Door</option>\n\t\t\t\t  <option value=\"sprinkler\">Sprinkler</option>\n\t\t\t\t  <option value=\"vacuum\">Robot Vacuum</option>\n\t\t\t  </optgroup>\n             </select>\n              </div>\n              <div class=\"style-row\">\n                <label>Fill Color:</label>\n                <input type=\"color\" id=\"circle-color\" value=\"#1e293b\">\n              </div>\n              <div class=\"style-row\">\n                <label>Border Color:</label>\n                <input type=\"color\" id=\"circle-border\" value=\"#475569\">\n              </div>\n              <div class=\"style-row\">\n                <label>Title Color:</label>\n                <input type=\"color\" id=\"title-color\" value=\"#e2e8f0\">\n              </div>\n              <div class=\"style-row\">\n                <label>Title Font:</label>\n                <select id=\"title-font\">\n                  <option value=\"system-ui, sans-serif\"> System UI </option>\n                  <option value=\"monospace\">Monospace</option>\n                  <option value=\"serif\">Serif</option>\n                  <option value=\"cursive\">Cursive</option>\n                  <option value=\"'Courier New', monospace\"> Courier </option>\n                  <option value=\"Arial, sans-serif\">Arial</option>\n                  <option value=\"'Times New Roman', serif\"> Times </option>\n                </select>\n              </div>\n              <div class=\"style-row\">\n                <label>Title Size:</label>\n                <input type=\"number\" id=\"title-size\" min=\"8\" max=\"100\" value=\"18\">\n              </div>\n              <div class=\"style-row\">\n                <label>Sub Color:</label>\n                <input type=\"color\" id=\"sub-color\" value=\"#94a3b8\">\n              </div>\n              <div class=\"style-row\">\n                <label>Sub Font:</label>\n                <select id=\"sub-font\">\n                  <option value=\"system-ui, sans-serif\"> System UI </option>\n                  <option value=\"monospace\">Monospace</option>\n                  <option value=\"serif\">Serif</option>\n                  <option value=\"cursive\">Cursive</option>\n                  <option value=\"'Courier New', monospace\"> Courier </option>\n                  <option value=\"Arial, sans-serif\">Arial</option>\n                  <option value=\"'Times New Roman', serif\"> Times </option>\n                </select>\n              </div>\n              <div class=\"style-row\">\n                <label>Sub Size:</label>\n                <input type=\"number\" id=\"sub-size\" min=\"6\" max=\"80\" value=\"13\">\n              </div>\n              <div style=\"margin-top: 12px;padding-top: 10px;border-top: 1px solid var(--edge-main);\">\n                <div style=\"\n                  font-size: 12px;\n                  color: var(--text-soft);\n                  margin-bottom: 8px;\n                  text-transform: uppercase;\n                  \"> Text Position </div>\n                <div class=\"style-row\">\n                  <label>Name Y:</label>\n                  <input type=\"number\" id=\"title-offset-y\" min=\"-100\" max=\"100\" value=\"0\" style=\"width: 60px\">\n                </div>\n                <div class=\"style-row\">\n                  <label>Name X:</label>\n                  <input type=\"number\" id=\"title-offset-x\" min=\"-100\" max=\"100\" value=\"0\" style=\"width: 60px\">\n                </div>\n                <div class=\"style-row\">\n                  <label>IP Y:</label>\n                  <input type=\"number\" id=\"sub-offset-y\" min=\"-100\" max=\"100\" value=\"0\" style=\"width: 60px\">\n                </div>\n                <div class=\"style-row\">\n                  <label>IP X:</label>\n                  <input type=\"number\" id=\"sub-offset-x\" min=\"-100\" max=\"100\" value=\"0\" style=\"width: 60px\">\n                </div>\n              </div>\n              <button id=\"reset-styles\" style=\"width: 100%;margin-top: 10px;padding: 8px;background: var(--panel);color: var(--text-main);border: 1px solid var(--edge-main);border-radius: 6px;cursor: pointer;font-size: clamp(13px, 1.5vw, 17px);\">Reset Styles</button>\n            </div>\n          </details>\n          <div class=\"section-label\">Notes</div>\n          <ul class=\"list\" id=\"node-notes\"></ul>\n          <div style=\"margin-top: 10px; display: flex; gap: 8px\">\n            <input type=\"text\" id=\"new-note-input\" placeholder=\"Type new note...\" style=\"flex: 1;padding: 8px 12px;background: var(--panel);border: 1px solid var(--edge-main);border-radius: 6px;color: var(--text-main);font-size: clamp(14px, 1.6vw, 18px);\">\n            <button id=\"add-note-btn\" style=\"padding: 8px 16px;background: var(--accent);color: var(--bg);border: none;border-radius: 6px;cursor: pointer;font-size: clamp(14px, 1.6vw, 18px);font-weight: 600;\">Add</button>\n          </div>\n          <button id=\"delete-node-btn\" style=\"margin-top: 15px;padding: 10px 16px;background: var(--danger);color: rgb(255, 255, 255);border: none;border-radius: 6px;cursor: pointer;font-size: clamp(14px, 1.6vw, 18px);font-weight: 600;width: 100%;\">Delete Node</button>\n        </div>\n        <div id=\"edge-panel\" style=\"display: none\">\n          <div class=\"details-name\" id=\"edge-title\">Custom line</div>\n          <div class=\"style-row\" style=\"margin-top: 10px\">\n            <label>Width:</label>\n            <input type=\"number\" id=\"edge-width\" min=\"1\" max=\"20\" value=\"4\">\n          </div>\n          <div class=\"style-row\">\n            <label>Color:</label>\n            <input type=\"color\" id=\"edge-color\" value=\"#475569\">\n          </div>\n          <div class=\"style-row\">\n            <label>Line Style:</label>\n            <select id=\"edge-line-style\">\n  <option value=\"solid\">Solid</option>\n  <option value=\"dashed\">Dashed</option>\n  <option value=\"dotted\">Dotted</option>\n  <option value=\"wall\">Wall</option>\n</select>\n          </div>\n          <div class=\"style-row\">\n            <label>Routing:</label>\n            <select id=\"edge-routing\">\n              <option value=\"orthogonal\">Orthogonal (90°)</option>\n              <option value=\"curved\">Curved</option>\n              <option value=\"straight\">Straight</option>\n            </select>\n          </div>\n          <div class=\"style-row\">\n            <label>Direction:</label>\n            <select id=\"edge-direction\">\n              <option value=\"none\">No arrows</option>\n              <option value=\"forward\">→ Forward</option>\n              <option value=\"backward\">← Backward</option>\n              <option value=\"both\">↔ Bidirectional</option>\n            </select>\n          </div>\n          <div class=\"style-row\">\n            <label>Animate:</label>\n            <input type=\"checkbox\" id=\"edge-animate\">\n          </div>\n          <div class=\"style-row\">\n            <label>Style:</label>\n            <select id=\"edge-animation-style\">\n              <option value=\"\">Default</option>\n              <option value=\"arrows\">Flowing Arrows</option>\n              <option value=\"dots\">Dot Arrows</option>\n            </select>\n          </div>\n          <div class=\"style-row\">\n            <label>Speed:</label>\n            <select id=\"edge-animation-speed\">\n              <option value=\"\">Default</option>\n              <option value=\"0.5\">Very Fast</option>\n              <option value=\"1\">Fast</option>\n              <option value=\"1.5\">Normal</option>\n              <option value=\"2.5\">Slow</option>\n              <option value=\"4\">Very Slow</option>\n            </select>\n          </div>\n          <div class=\"style-row\" id=\"edge-from-port-row\" style=\"display: none;\">\n            <label>From Port:</label>\n            <input type=\"text\" id=\"edge-from-port\" placeholder=\"e.g. eth0, gi0/1\" style=\"flex: 1;\">\n          </div>\n          <div class=\"style-row\" id=\"edge-to-port-row\" style=\"display: none;\">\n            <label>To Port:</label>\n            <input type=\"text\" id=\"edge-to-port\" placeholder=\"e.g. eth1, gi0/2\" style=\"flex: 1;\">\n          </div>\n          <div class=\"section-label\">Line Notes</div>\n          <ul class=\"list\" id=\"edge-notes\"></ul>\n          <div style=\"margin-top: 10px; display: flex; gap: 8px\">\n            <input type=\"text\" id=\"new-edge-note\" placeholder=\"Add note...\" style=\"flex: 1;padding: 8px 12px;background: var(--panel);border: 1px solid var(--edge-main);border-radius: 6px;color: var(--text-main);font-size: clamp(14px, 1.6vw, 18px);\">\n            <button id=\"add-edge-note\" style=\"padding: 8px 16px;background: var(--accent);color: var(--bg);border: none;border-radius: 6px;cursor: pointer;font-size: clamp(14px, 1.6vw, 18px);font-weight: 600;\">Add</button>\n          </div>\n          <button id=\"delete-edge\" style=\"margin-top: 15px;padding: 10px 16px;background: var(--danger);color: #fff;border: none;border-radius: 6px;cursor: pointer;font-size: clamp(14px, 1.6vw, 18px);font-weight: 600;\">Delete Line</button>\n        </div>\n        <div id=\"rect-panel\" style=\"display: none\">\n          <div class=\"details-name\" id=\"rect-title\">Zone</div>\n          <div class=\"style-row\" id=\"rect-fill-row\" style=\"margin-top: 10px\">\n            <label>Fill Color:</label>\n            <input type=\"color\" id=\"rect-color\" value=\"#f97316\">\n          </div>\n          <div class=\"style-row\">\n            <label>Border Color:</label>\n            <input type=\"color\" id=\"rect-border-color\" value=\"#f97316\">\n          </div>\n          <div class=\"style-row\">\n            <label>Border Width:</label>\n            <input type=\"number\" id=\"rect-border-width\" min=\"0\" max=\"20\" value=\"2\">\n          </div>\n          <div class=\"style-row\">\n            <label>Style:</label>\n            <select id=\"rect-style-select\">\n              <option value=\"filled\">Filled</option>\n              <option value=\"outlined\">Outlined</option>\n            </select>\n          </div>\n          <div class=\"style-row\">\n            <label>Line Style:</label>\n            <select id=\"rect-line-style\">\n  <option value=\"solid\">Solid</option>\n  <option value=\"dashed\">Dashed</option>\n  <option value=\"dotted\">Dotted</option>\n  <option value=\"wall\">Wall</option>\n</select>\n          </div>\n          <div class=\"style-row\">\n            <label>Rotation:</label>\n            <input type=\"range\" id=\"rect-rotation\" min=\"-360\" max=\"360\" value=\"0\" style=\"flex: 1;\">\n            <input type=\"number\" id=\"rect-rotation-value\" value=\"0\" style=\"width: 60px; padding: 4px; background: var(--panel); border: 1px solid var(--edge-main); border-radius: 4px; color: var(--text-main); text-align: center;\">\n            <span style=\"color: var(--text-soft);\">°</span>\n          </div>\n          <div class=\"section-label\">Zone Notes</div>\n          <ul class=\"list\" id=\"rect-notes\"></ul>\n          <div style=\"margin-top: 10px; display: flex; gap: 8px\">\n            <input type=\"text\" id=\"new-rect-note\" placeholder=\"Add note...\" style=\"flex: 1;padding: 8px 12px;background: var(--panel);border: 1px solid var(--edge-main);border-radius: 6px;color: var(--text-main);font-size: clamp(14px, 1.6vw, 18px);\">\n            <button id=\"add-rect-note\" style=\"padding: 8px 16px;background: var(--accent);color: var(--bg);border: none;border-radius: 6px;cursor: pointer;font-size: clamp(14px, 1.6vw, 18px);font-weight: 600;\">Add</button>\n          </div>\n          <button id=\"delete-rect\" style=\"margin-top: 15px;padding: 10px 16px;background: var(--danger);color: #fff;border: none;border-radius: 6px;cursor: pointer;font-size: clamp(14px, 1.6vw, 18px);font-weight: 600;\">Delete Zone</button>\n        </div>\n        <div id=\"text-panel\" style=\"display: none\">\n          <div class=\"details-name\" id=\"text-title\">Text Element</div>\n          <div class=\"style-row\" style=\"margin-top: 10px\">\n            <label>Content:</label>\n            <textarea id=\"text-content\" placeholder=\"Enter text...\" style=\"width: 100%;padding: 8px 12px;background: var(--panel);border: 1px solid var(--edge-main);border-radius: 6px;color: var(--text-main);font-size: 14px;min-height: 80px;resize: vertical;font-family: inherit;\"></textarea>\n          </div>\n          <div class=\"style-row\">\n            <label>Font Size:</label>\n            <input type=\"number\" id=\"text-font-size\" min=\"8\" max=\"200\" value=\"18\" style=\"flex: 1;padding: 6px 10px;background: var(--panel);border: 1px solid var(--edge-main);border-radius: 4px;color: var(--text-main);\">\n          </div>\n          <div class=\"style-row\">\n            <label>Color:</label>\n            <input type=\"color\" id=\"text-color\" value=\"#e2e8f0\">\n          </div>\n          <div class=\"style-row\">\n            <label>Font Weight:</label>\n            <select id=\"text-font-weight\" style=\"flex: 1;padding: 6px 10px;background: var(--panel);border: 1px solid var(--edge-main);border-radius: 4px;color: var(--text-main);cursor: pointer;\">\n              <option value=\"normal\">Normal</option>\n              <option value=\"bold\">Bold</option>\n              <option value=\"600\">Semi-Bold</option>\n              <option value=\"300\">Light</option>\n            </select>\n          </div>\n          <div class=\"style-row\">\n            <label>Font Style:</label>\n            <select id=\"text-font-style\" style=\"flex: 1;padding: 6px 10px;background: var(--panel);border: 1px solid var(--edge-main);border-radius: 4px;color: var(--text-main);cursor: pointer;\">\n              <option value=\"normal\">Normal</option>\n              <option value=\"italic\">Italic</option>\n            </select>\n          </div>\n          <div class=\"style-row\">\n            <label>Text Align:</label>\n            <select id=\"text-align\" style=\"flex: 1;padding: 6px 10px;background: var(--panel);border: 1px solid var(--edge-main);border-radius: 4px;color: var(--text-main);cursor: pointer;\">\n              <option value=\"start\">Left</option>\n              <option value=\"middle\">Center</option>\n              <option value=\"end\">Right</option>\n            </select>\n          </div>\n          <div class=\"style-row\">\n            <label>Text Decoration:</label>\n            <select id=\"text-decoration\" style=\"flex: 1;padding: 6px 10px;background: var(--panel);border: 1px solid var(--edge-main);border-radius: 4px;color: var(--text-main);cursor: pointer;\">\n              <option value=\"none\">None</option>\n              <option value=\"underline\">Underline</option>\n              <option value=\"line-through\">Strike Through</option>\n            </select>\n          </div>\n          <div class=\"style-row\">\n            <label>Background:</label>\n            <input type=\"color\" id=\"text-bg-color\" value=\"#000000\">\n            <label style=\"margin-left: 10px; display: flex; align-items: center; gap: 6px;\">\n            <input type=\"checkbox\" id=\"text-bg-enabled\" style=\"cursor: pointer;\">\n            <span style=\"font-size: 13px;\">Enable</span>\n            </label>\n          </div>\n          <div class=\"style-row\">\n            <label>Opacity:</label>\n            <input type=\"range\" id=\"text-opacity\" min=\"0\" max=\"1\" step=\"0.1\" value=\"1\" style=\"flex: 1\">\n            <span id=\"text-opacity-val\" style=\"min-width: 35px; text-align: right\">100%</span>\n          </div>\n          <div class=\"style-row\">\n            <label>Rotation:</label>\n            <input type=\"range\" id=\"text-rotation\" min=\"-360\" max=\"360\" value=\"0\" style=\"flex: 1\">\n            <input type=\"number\" id=\"text-rotation-val\" value=\"0\" style=\"width: 60px; padding: 4px; background: var(--panel); border: 1px solid var(--edge-main); border-radius: 4px; color: var(--text-main); text-align: center;\">\n            <span style=\"color: var(--text-soft);\">°</span>\n          </div>\n          <button id=\"delete-text\" style=\"margin-top: 15px;padding: 10px 16px;background: var(--danger);color: #fff;border: none;border-radius: 6px;cursor: pointer;font-size: clamp(14px, 1.6vw, 18px);font-weight: 600;width: 100%;\"> Delete Text </button>\n        </div>\n        <details class=\"style-section\" open=\"\">\n          <summary>Page Layout</summary>\n          <div class=\"style-content\">\n            <div style=\"padding: 12px; background: var(--panel-alt); border-radius: 6px; color: var(--text-soft); font-size: 13px; line-height: 1.6;\">\n              <strong style=\"color: var(--accent); display: block; margin-bottom: 8px;\">Drag to Resize:</strong>\n              • <strong>Header:</strong> Drag the bottom edge of the header bar<br>\n              • <strong>Sidebar:</strong> Drag the left edge of the sidebar panel<br>\n              • <strong>Mobile:</strong> Drag the top edge of the footer panel<br>\n              <div style=\"margin-top: 8px; padding-top: 8px; border-top: 1px solid var(--edge-main); font-size: 12px;\">\n                Hover over panel edges to see resize handles\n              </div>\n            </div>\n          </div>\n        </details>\n      </aside>\n    </main>\n    <script id=\"nodes-json\" type=\"application/json\">{}</script>\n    <script id=\"topology-state\" type=\"application/json\">ENCRYPTED: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</script>\n    <script>\n      const BASE_NODE_DATA = JSON.parse(document.getElementById(\"nodes-json\").textContent);\n      let currentNodeId = \"host\";\n      let textDrawMode = false;\n      let currentEdgeId = null;\n      let currentStyleScope = \"all\";\n      let NODE_DATA = {};\n      let EDGE_DATA = {\n       list: []\n      };\n      let currentRectId = null;\n      let currentTextId = null;\n      let rectDrawMode = false;\n      let RECT_DATA = {\n       list: []\n      };\n      let TEXT_DATA = {\n       list: []\n      };\n      let EDGE_LEGEND = {};\n      let freeDrawMode = false;\n      let savedPositions = {};\n      let savedSizes = {};\n      let savedStyles = {};\n      let legendCollapsed = false;\n      let minimapCollapsed = false;\n      let drawToolbarCollapsed = false;\n      let activeLayers = new Set([\"physical\", \"logical\", \"security\", \"application\"]);\n      let currentView = {\n       mode: \"topology\",\n       rackId: null\n      };\n      let savedTopologyView = null;\n      let topologyToolbarCollapsed = false;\n      let legendMiniBtn = null;\n      let minimapMiniBtn = null;\n      let drawToolbarMiniBtn = null;\n      let topologyToolbarMiniBtn = null;\n      let undoStack = [];\n      let redoStack = [];\n      const MAX_UNDO_STACK = 50;\n      let forgeDebounceTimer = null;\n      let forgeImmediate = false;\n      let currentSearchQuery = \"\";\n      let currentSearchResults = [];\n      const AUTOSAVE_DB_NAME = \"TheOneFileAutosave\";\n      const AUTOSAVE_STORE_NAME = \"drafts\";\n      const AUTOSAVE_KEY = \"currentDraft\";\n      const AUTOSAVE_INTERVAL = 30000;\n      let autosaveTimer = null;\n      let lastAutosaveTime = 0;\n      let selectedNodes = new Set();\n      let selectedEdges = new Set();\n      let selectedRects = new Set();\n      let selectedTexts = new Set();\n      let isSelecting = false;\n      let selectionStart = null;\n      let preDragSelectedNodes = new Set();\n      let preDragSelectedEdges = new Set();\n      let preDragSelectedRects = new Set();\n      let preDragSelectedTexts = new Set();\n      let selectionRect = null;\n      let selectionBoxStyle = {\n       fillColor: \"#4fd1c5\",\n       fillOpacity: 0.1,\n       strokeColor: \"#4fd1c5\",\n       strokeWidth: 2,\n       strokeDasharray: \"5,5\"\n      };\n      let isDraggingSelection = false;\n      let dragSelectionStart = null;\n      let clipboard = null;\n      const ROLLBACK_STORAGE_KEY = \"theonefile_rollbacks\";\n      const MAX_ROLLBACK_VERSIONS = 50;\n      let rollbackVersions = [];\n      let currentRollbackIndex = -1;\n      const AUDIT_STORAGE_KEY = \"theonefile_audit_log\";\n      let auditLog = [];\n      const MAX_AUDIT_ENTRIES = 1000;\n      let savedStyleSets = [];\n      let documentTabs = [{\n        id: \"main\",\n        name: \"Main Topology\",\n        nodes: {},\n        edges: { list: [] },\n        positions: {},\n        sizes: {},\n        styles: {},\n        legend: {},\n        rects: { list: [] },\n        texts: { list: [] },\n      pageState: null\n      }];\n      let currentTabIndex = 0;\n      let encryptedSections = {};\n      let performanceMode = \"auto\";\n      let cullOffscreenNodes = true;\n      let minimapNeedsUpdate = true;\n      let lastMinimapUpdate = 0;\n      const MobileManager = {\n        isMobile: false,\n        detect() {\n          if (navigator.userAgentData?.mobile === true) {\n            this.isMobile = true;\n            return true;\n          }\n          const coarse = matchMedia(\"(pointer: coarse)\").matches;\n          const width = window.innerWidth <= 900;\n          const portrait = matchMedia(\"(orientation: portrait)\").matches;\n          this.isMobile = coarse;\n          return this.isMobile;\n        },\n        applyInitialCollapse() {\n          if (!this.isMobile) return;\n          legendCollapsed = true;\n          minimapCollapsed = true;\n          drawToolbarCollapsed = true;\n          topologyToolbarCollapsed = true;\n          if (typeof updateLegendVisibility === \"function\") updateLegendVisibility();\n          if (typeof updateMinimapVisibility === \"function\") updateMinimapVisibility();\n          if (typeof updateDrawToolbarVisibility === \"function\") updateDrawToolbarVisibility();\n          if (typeof updateTopologyToolbarVisibility === \"function\") updateTopologyToolbarVisibility();\n        },\n        updateBulkToolbar() {\n          const desktop = document.getElementById(\"bulk-toolbar\");\n          const mobile = document.getElementById(\"bulk-toolbar-mobile\");\n          if (!desktop || !mobile) return;\n          if (typeof selectedNodes === 'undefined' || selectedNodes.size === 0) {\n           desktop.style.display = \"none\";\n           mobile.style.display = \"none\";\n           return;\n          }\n          const isVisible = desktop.style.display !== \"none\" || mobile.style.display !== \"none\";\n          if (this.isMobile) {\n            desktop.style.display = \"none\";\n            if (isVisible) {\n              mobile.style.display = \"flex\";\n            }\n          } else {\n            mobile.style.display = \"none\";\n            if (isVisible) {\n              desktop.style.display = \"flex\";\n            }\n          }\n        },\n        updateCanvasHint() {\n      const hint = document.getElementById(\"canvas-hint\");\n      if (!hint) return;\n      if (PAGE_STATE.canvasHintText !== undefined && PAGE_STATE.canvasHintText !== \"\") {\n        hint.textContent = PAGE_STATE.canvasHintText;\n        return;\n      }\n      const items = this.isMobile\n      ? [\n        \"Pinch to zoom\",\n        \"Drag to pan\",\n        \"Add node from top menu\",\n        \"Double tap to clone and align\",\n        \"Double tap to select multiple\",\n        \"You have the power\",\n        \"Your time is NOW!\",\n      ]\n      : [\n        \"Scroll to zoom\",\n        \"Drag to pan\",\n        \"Right click to clone and align\",\n        \"Right click to select multiple\",\n\t    \"Hold Shift + drag mouse for marquee selection\",\n        \"You have the power\",\n        \"Your time is NOW!\",\n      ];\n      const list = document.createElement(\"ul\");\n      for (const item of items) {\n      const li = document.createElement(\"li\");\n      li.textContent = item;\n      list.appendChild(li);\n      }\n      hint.replaceChildren(list);\n      },\n        autoSelectStyleScope() {\n          const scope = document.getElementById(\"style-scope\");\n          if (!scope) return;\n          if (this.isMobile) scope.value = \"mobile\";\n        },\n        updateToolbarStack() {\n          if (!this.isMobile) return;\n          const draw = document.getElementById(\"draw-toolbar\");\n          const topo = document.getElementById(\"topology-toolbar\");\n          if (!draw || !topo) return;\n          const h = draw.getBoundingClientRect().height;\n          document.documentElement.style.setProperty(\"--draw-toolbar-height\", h + \"px\");\n        },\n        updateLayoutControls() {\n          const sidebarRow = document.getElementById(\"sidebar-width-row\");\n          const footerRow = document.getElementById(\"mobile-footer-row\");\n          if (sidebarRow && footerRow) {\n            if (this.isMobile) {\n              sidebarRow.style.display = \"none\";\n              footerRow.style.display = \"flex\";\n            } else {\n              sidebarRow.style.display = \"flex\";\n              footerRow.style.display = \"none\";\n            }\n          }\n        },\n        applyAll() {\n          this.detect();\n          this.applyInitialCollapse();\n          this.updateBulkToolbar();\n\t\t  this.updateCanvasHint();\n          this.autoSelectStyleScope();\n          this.updateToolbarStack();\n          this.updateLayoutControls();\n        }\n      };\n      function isMobileDevice() {\n        return MobileManager.isMobile;\n      }\n\t  function escapeHtml(str) {\n       if (!str) return '';\n       return String(str).replace(/[&<>\"']/g, c => ({\n        '&': '&amp;', '<': '&lt;', '>': '&gt;', '\"': '&quot;', \"'\": '&#39;'\n       }[c]));\n      }\n      function u8ToBase64(u8) {\n      let binary = \"\";\n      for (let i = 0; i < u8.length; i++) binary += String.fromCharCode(u8[i]);\n      return btoa(binary);\n      }\n      function base64ToU8(base64) {\n      const binary = atob(base64);\n      const bytes = new Uint8Array(binary.length);\n      for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);\n      return bytes;\n      }\n      function openAutosaveDB() {\n       return new Promise((resolve, reject) => {\n        const request = indexedDB.open(AUTOSAVE_DB_NAME, 1);\n        request.onerror = () => reject(request.error);\n        request.onsuccess = () => resolve(request.result);\n        request.onupgradeneeded = (e) => {\n         const db = e.target.result;\n         if (!db.objectStoreNames.contains(AUTOSAVE_STORE_NAME)) {\n          db.createObjectStore(AUTOSAVE_STORE_NAME, { keyPath: \"id\" });\n         }\n        };\n       });\n      }\n      async function saveToIndexedDB() {\n       try {\n        const db = await openAutosaveDB();\n        const currentTab = documentTabs[currentTabIndex];\n        currentTab.nodes = JSON.parse(JSON.stringify(NODE_DATA));\n        currentTab.edges = JSON.parse(JSON.stringify(EDGE_DATA));\n        currentTab.positions = JSON.parse(JSON.stringify(savedPositions));\n        currentTab.sizes = JSON.parse(JSON.stringify(savedSizes));\n        currentTab.styles = JSON.parse(JSON.stringify(savedStyles));\n        currentTab.legend = JSON.parse(JSON.stringify(EDGE_LEGEND));\n        currentTab.rects = JSON.parse(JSON.stringify(RECT_DATA));\n        currentTab.texts = JSON.parse(JSON.stringify(TEXT_DATA));\n        currentTab.pageState = JSON.parse(JSON.stringify(PAGE_STATE));\n        const state = {\n         id: AUTOSAVE_KEY,\n         timestamp: Date.now(),\n         pageTitle: document.getElementById(\"page-title\")?.textContent || \"The One File\",\n         nodeData: NODE_DATA,\n         edgeData: EDGE_DATA,\n         rectData: RECT_DATA,\n         textData: TEXT_DATA,\n         edgeLegend: EDGE_LEGEND,\n         zoneLegend: ZONE_LEGEND,\n         zonePresets: ZONE_PRESETS,\n         nodePositions: savedPositions,\n         nodeSizes: savedSizes,\n         nodeStyles: savedStyles,\n         page: PAGE_STATE,\n         canvas: {\n          zoom: canvasState.zoom,\n          panX: canvasState.panX,\n          panY: canvasState.panY,\n         },\n         savedTopologyView: savedTopologyView,\n         documentTabs: documentTabs,\n         currentTabIndex: currentTabIndex,\n         encryptedSections: encryptedSections,\n         auditLog: auditLog,\n\t\t savedStyleSets: savedStyleSets,\n\t\t selectedTheme: document.getElementById(\"theme-preset\")?.value || \"defaulted\",\n        };\n        const tx = db.transaction(AUTOSAVE_STORE_NAME, \"readwrite\");\n        const store = tx.objectStore(AUTOSAVE_STORE_NAME);\n        store.put(state);\n        await new Promise((resolve, reject) => {\n         tx.oncomplete = resolve;\n         tx.onerror = () => reject(tx.error);\n        });\n        lastAutosaveTime = Date.now();\n        console.log(\"Auto-saved to IndexedDB at\", new Date().toLocaleTimeString());\n       } catch (e) {\n        console.warn(\"Auto-save failed:\", e);\n       }\n      }\n      async function loadFromIndexedDB() {\n       try {\n        const db = await openAutosaveDB();\n        const tx = db.transaction(AUTOSAVE_STORE_NAME, \"readonly\");\n        const store = tx.objectStore(AUTOSAVE_STORE_NAME);\n        const request = store.get(AUTOSAVE_KEY);\n        return new Promise((resolve, reject) => {\n         request.onsuccess = () => resolve(request.result);\n         request.onerror = () => reject(request.error);\n        });\n       } catch (e) {\n        console.warn(\"Load from IndexedDB failed:\", e);\n        return null;\n       }\n      }\n      async function clearAutosave() {\n       try {\n        const db = await openAutosaveDB();\n        const tx = db.transaction(AUTOSAVE_STORE_NAME, \"readwrite\");\n        const store = tx.objectStore(AUTOSAVE_STORE_NAME);\n        store.delete(AUTOSAVE_KEY);\n       } catch (e) {\n        console.warn(\"Clear autosave failed:\", e);\n       }\n      }\n      function startAutosave() {\n       if (autosaveTimer) clearInterval(autosaveTimer);\n       autosaveTimer = setInterval(() => {\n        if (Object.keys(NODE_DATA).length > 0 || EDGE_DATA.list.length > 0 || documentTabs.length > 1) {\n         saveToIndexedDB();\n        }\n       }, AUTOSAVE_INTERVAL);\n      }\n      async function checkForAutosaveRecovery() {\n       try {\n        const saved = await loadFromIndexedDB();\n        if (saved && saved.timestamp) {\n         const age = Date.now() - saved.timestamp;\n         const hasCurrentData = Object.keys(NODE_DATA).length > 0 || EDGE_DATA.list.length > 0;\n         const savedNodeCount = saved.nodeData ? Object.keys(saved.nodeData).length : 0;\n         const savedTabCount = saved.documentTabs ? saved.documentTabs.length : 1;\n         if (age < 300000 && (savedNodeCount > 0 || savedTabCount > 1)) {\n          const savedDate = new Date(saved.timestamp).toLocaleString();\n          const tabInfo = savedTabCount > 1 ? ` across ${savedTabCount} tabs` : \"\";\n          if (!hasCurrentData || confirm(`Recover unsaved work from ${savedDate}?\\n(${savedNodeCount} nodes${tabInfo})\\n\\nClick OK to recover, Cancel to start fresh.`)) {\n           if (!hasCurrentData || confirm(\"This will replace current data. Continue?\")) {\n            NODE_DATA = saved.nodeData || {};\n            EDGE_DATA = saved.edgeData || { list: [] };\n            RECT_DATA = saved.rectData || { list: [] };\n            TEXT_DATA = saved.textData || { list: [] };\n            EDGE_LEGEND = saved.edgeLegend || {};\n\t\t\tZONE_LEGEND = saved.zoneLegend || {};\n            ZONE_PRESETS = saved.zonePresets || {};\n            loadCustomPresets();\n            savedPositions = saved.nodePositions || {};\n            savedSizes = saved.nodeSizes || {};\n            savedStyles = saved.nodeStyles || {};\n            if (saved.page) PAGE_STATE = Object.assign({}, DEFAULT_PAGE_STATE, saved.page);\n            if (saved.canvas) {\n             canvasState.zoom = saved.canvas.zoom || 1;\n             canvasState.panX = saved.canvas.panX || 0;\n             canvasState.panY = saved.canvas.panY || 0;\n            }\n            if (saved.savedTopologyView) savedTopologyView = saved.savedTopologyView;\n            if (saved.savedStyleSets) savedStyleSets = saved.savedStyleSets;\n            if (saved.selectedTheme) {\n              rebuildThemeDropdown();\n              document.getElementById(\"theme-preset\").value = saved.selectedTheme;\n              updateDeleteButton();\n            }\n            if (saved.documentTabs) documentTabs = saved.documentTabs;\n            if (saved.currentTabIndex !== undefined) currentTabIndex = saved.currentTabIndex;\n            if (saved.encryptedSections) encryptedSections = saved.encryptedSections;\n            if (saved.auditLog) auditLog = saved.auditLog;\n            if (saved.pageTitle) {\n             const titleEl = document.getElementById(\"page-title\");\n             if (titleEl) titleEl.textContent = saved.pageTitle;\n            }\n            wieldThePower();\n            forgeTheTopology();\n            updateViewBox();\n            console.log(\"Recovered from autosave:\", savedNodeCount, \"nodes,\", savedTabCount, \"tabs\");\n            return true;\n           }\n          }\n         }\n        }\n       } catch (e) {\n        console.warn(\"Autosave recovery check failed:\", e);\n       }\n       return false;\n      }\n\t\t\t  function ensureLegendMiniButton() {\n\t\t   if (legendMiniBtn) return legendMiniBtn;\n\t\t   const handleClick = (e) => {\n\t\t\te.stopPropagation();\n\t\t\te.preventDefault();\n\t\t\tlegendCollapsed = false;\n\t\t\tupdateLegendVisibility();\n\t\t   };\n\t\t   const preventTouch = (e) => { e.preventDefault(); };\n\t\t   const existing = document.getElementById(\"edge-legend-mini\");\n\t\t   if (existing) {\n\t\t\texisting.addEventListener(\"click\", handleClick);\n\t\t\texisting.addEventListener(\"touchstart\", preventTouch, { passive: false });\n\t\t\texisting.addEventListener(\"touchend\", handleClick);\n\t\t\tlegendMiniBtn = existing;\n\t\t\treturn existing;\n\t\t   }\n\t\t   const panel = document.querySelector(\".topology-panel\");\n\t\t   if (!panel) return null;\n\t\t   const btn = document.createElement(\"button\");\n\t\t   btn.type = \"button\";\n\t\t   btn.id = \"edge-legend-mini\";\n\t\t   btn.className = \"legend-mini-btn\";\n\t\t   btn.textContent = \"Legend\";\n\t\t   btn.style.bottom = \"10px\";\n\t\t   btn.addEventListener(\"click\", handleClick);\n\t\t   btn.addEventListener(\"touchstart\", preventTouch, { passive: false });\n\t\t   btn.addEventListener(\"touchend\", handleClick);\n\t\t   panel.appendChild(btn);\n\t\t   legendMiniBtn = btn;\n\t\t   return btn;\n\t\t}\n\t\t\t  function ensureMinimapMiniButton() {\n\t\t   if (minimapMiniBtn) return minimapMiniBtn;\n\t\t   const handleClick = (e) => {\n\t\t\te.stopPropagation();\n\t\t\te.preventDefault();\n\t\t\tminimapCollapsed = false;\n\t\t\tupdateMinimapVisibility();\n\t\t   };\n\t\t   const preventTouch = (e) => { e.preventDefault(); };\n\t\t   const existing = document.getElementById(\"minimap-mini\");\n\t\t   if (existing) {\n\t\t\texisting.addEventListener(\"click\", handleClick);\n\t\t\texisting.addEventListener(\"touchstart\", preventTouch, { passive: false });\n\t\t\texisting.addEventListener(\"touchend\", handleClick);\n\t\t\tminimapMiniBtn = existing;\n\t\t\treturn existing;\n\t\t   }\n\t\t   const panel = document.querySelector(\".topology-panel\");\n\t\t   if (!panel) return null;\n\t\t   const btn = document.createElement(\"button\");\n\t\t   btn.type = \"button\";\n\t\t   btn.id = \"minimap-mini\";\n\t\t   btn.className = \"legend-mini-btn\";\n\t\t   btn.textContent = \"Map\";\n\t\t   btn.style.right = \"10px\";\n\t\t   btn.style.left = \"auto\";\n\t\t   btn.style.bottom = \"10px\";\n\t\t   btn.addEventListener(\"click\", handleClick);\n\t\t   btn.addEventListener(\"touchstart\", preventTouch, { passive: false });\n\t\t   btn.addEventListener(\"touchend\", handleClick);\n\t\t   panel.appendChild(btn);\n\t\t   minimapMiniBtn = btn;\n\t\t   return btn;\n\t\t}\n\t\t\t  function ensureDrawToolbarMiniButton() {\n\t\t   if (drawToolbarMiniBtn) return drawToolbarMiniBtn;\n\t\t   const handleClick = (e) => {\n\t\t\te.stopPropagation();\n\t\t\te.preventDefault();\n\t\t\tdrawToolbarCollapsed = false;\n\t\t\tupdateDrawToolbarVisibility();\n\t\t   };\n\t\t   const preventTouch = (e) => { e.preventDefault(); };\n\t\t   const existing = document.getElementById(\"draw-toolbar-mini\");\n\t\t   if (existing) {\n\t\t\texisting.addEventListener(\"click\", handleClick);\n\t\t\texisting.addEventListener(\"touchstart\", preventTouch, { passive: false });\n\t\t\texisting.addEventListener(\"touchend\", handleClick);\n\t\t\tdrawToolbarMiniBtn = existing;\n\t\t\treturn existing;\n\t\t   }\n\t\t   const panel = document.querySelector(\".topology-panel\");\n\t\t   if (!panel) return null;\n\t\t   const btn = document.createElement(\"button\");\n\t\t   btn.type = \"button\";\n\t\t   btn.id = \"draw-toolbar-mini\";\n\t\t   btn.className = \"legend-mini-btn\";\n\t\t   btn.textContent = \"Draw\";\n\t\t   btn.style.top = \"10px\";\n\t\t   btn.style.left = \"10px\";\n\t\t   btn.style.right = \"auto\";\n\t\t   btn.addEventListener(\"click\", handleClick);\n\t\t   btn.addEventListener(\"touchstart\", preventTouch, { passive: false });\n\t\t   btn.addEventListener(\"touchend\", handleClick);\n\t\t   panel.appendChild(btn);\n\t\t   drawToolbarMiniBtn = btn;\n\t\t   return btn;\n\t\t}\n\t\t\t  function ensureTopologyToolbarMiniButton() {\n\t\t   if (topologyToolbarMiniBtn) return topologyToolbarMiniBtn;\n\t\t   const handleClick = (e) => {\n\t\t\te.stopPropagation();\n\t\t\te.preventDefault();\n\t\t\ttopologyToolbarCollapsed = false;\n\t\t\tupdateTopologyToolbarVisibility();\n\t\t   };\n\t\t   const preventTouch = (e) => { e.preventDefault(); };\n\t\t   const existing = document.getElementById(\"topology-toolbar-mini\");\n\t\t   if (existing) {\n\t\t\texisting.addEventListener(\"click\", handleClick);\n\t\t\texisting.addEventListener(\"touchstart\", preventTouch, { passive: false });\n\t\t\texisting.addEventListener(\"touchend\", handleClick);\n\t\t\ttopologyToolbarMiniBtn = existing;\n\t\t\treturn existing;\n\t\t   }\n\t\t   const panel = document.querySelector(\".topology-panel\");\n\t\t   if (!panel) return null;\n\t\t   const btn = document.createElement(\"button\");\n\t\t   btn.type = \"button\";\n\t\t   btn.id = \"topology-toolbar-mini\";\n\t\t   btn.className = \"legend-mini-btn\";\n\t\t   btn.textContent = \"Add Line\";\n\t\t   btn.style.top = \"10px\";\n\t\t   btn.style.left = \"auto\";\n\t\t   btn.style.right = \"40px\";\n\t\t   btn.addEventListener(\"click\", handleClick);\n\t\t   btn.addEventListener(\"touchstart\", preventTouch, { passive: false });\n\t\t   btn.addEventListener(\"touchend\", handleClick);\n\t\t   panel.appendChild(btn);\n\t\t   topologyToolbarMiniBtn = btn;\n\t\t   return btn;\n\t\t}\n      function updateToolbarStack() {\n       if (!isMobileDevice()) return;\n       const draw = document.getElementById(\"draw-toolbar\");\n       const topo = document.getElementById(\"topology-toolbar\");\n       if (!draw || !topo) return;\n       const h = draw.getBoundingClientRect().height;\n       document.documentElement.style.setProperty(\"--draw-toolbar-height\", h + \"px\");\n      }\n      window.addEventListener(\"resize\", updateToolbarStack);\n      window.addEventListener(\"DOMContentLoaded\", updateToolbarStack);\n      updateToolbarStack();\n      function updateLegendVisibility() {\n       const legend = document.getElementById(\"edge-legend\");\n       const mini = ensureLegendMiniButton();\n       if (!legend || !mini) return;\n       const hasItems = legend.querySelectorAll(\".legend-item\").length > 0;\n       if (!hasItems) {\n        legend.style.display = \"none\";\n        mini.style.display = \"none\";\n        return;\n       }\n       if (legendCollapsed) {\n        legend.style.display = \"none\";\n        mini.style.display = \"inline-flex\";\n       } else {\n        legend.style.display = \"flex\";\n        mini.style.display = \"none\";\n       }\n      }\n      function updateMinimapVisibility() {\n       const wrapper = document.getElementById(\"minimap-zoom-wrapper\");\n       const mini = ensureMinimapMiniButton();\n       if (!wrapper || !mini) return;\n       if (minimapCollapsed) {\n        wrapper.style.display = \"none\";\n        mini.style.display = \"inline-flex\";\n       } else {\n        wrapper.style.display = \"block\";\n        mini.style.display = \"none\";\n       }\n      }\n      function updateDrawToolbarVisibility() {\n       const toolbar = document.getElementById(\"draw-toolbar\");\n       const mini = ensureDrawToolbarMiniButton();\n       if (isViewOnly()) {\n        if (toolbar) toolbar.style.setProperty('display', 'none', 'important');\n        if (mini) mini.style.display = \"none\";\n        return;\n       }\n       if (!toolbar || !mini) return;\n       if (drawToolbarCollapsed) {\n        toolbar.style.setProperty('display', 'none', 'important');\n        mini.style.display = \"inline-flex\";\n       } else {\n      if (isMobileDevice()) {\n        toolbar.style.setProperty('display', 'grid', 'important');\n        mini.style.display = \"none\";\n      } else {\n        toolbar.style.setProperty('display', 'flex', 'important');\n        mini.style.display = \"none\";\n      }\n       }\n      }\n      function updateTopologyToolbarVisibility() {\n       const toolbar = document.getElementById(\"topology-toolbar\");\n       const mini = ensureTopologyToolbarMiniButton();\n       if (isViewOnly()) {\n        if (toolbar) toolbar.style.setProperty('display', 'none', 'important');\n        if (mini) mini.style.display = \"none\";\n        return;\n       }\n       if (!toolbar || !mini) return;\n       const hasSelectedNode = currentNodeId !== null;\n       if (!hasSelectedNode) {\n        toolbar.style.setProperty('display', 'none', 'important');\n        mini.style.display = \"none\";\n       } else if (topologyToolbarCollapsed) {\n        toolbar.style.setProperty('display', 'none', 'important');\n        mini.style.display = \"inline-flex\";\n       } else {\n      if (isMobileDevice()) {\n        toolbar.style.setProperty('display', 'grid', 'important');\n        mini.style.display = \"none\";\n        } else {\n        toolbar.style.setProperty('display', 'flex', 'important');\n        mini.style.display = \"none\";\n      }\n       }\n      }\n      const DEFAULT_CANVAS_HINT = document.getElementById(\"canvas-hint\")?.innerHTML || \"\";\n      const DEFAULT_PAGE_STATE = {\n       title: \"The One File\",\n       background: \"\",\n       topbarBg: \"rgba(9, 12, 20, 0.9)\",\n       topbarBorder: \"#1f2533\",\n       panel: \"#0b0e13\",\n       panelAlt: \"#10141b\",\n       accent: \"#4fd1c5\",\n       sidebarBg: \"#10141b\",\n       btnBg: \"#0b0e13\",\n       btnText: \"#e2e8f0\",\n       tagFill: \"#1e293b\",\n       tagText: \"#e2e8f0\",\n       tagBorder: \"#475569\",\n       inputBg: \"#0b0e13\",\n       inputText: \"#e2e8f0\",\n       inputBorder: \"#1f2937\",\n       inputFont: \"Inter, system-ui, sans-serif\",\n       inputFontSize: 14,\n       toolbarBg: \"#0f172a\",\n       toolbarBorder: \"#1f2937\",\n       toolbarText: \"#94a3b8\",\n       toolbarBtnBg: \"#0b0e13\",\n       toolbarBtnText: \"#e2e8f0\",\n       minimapDots: \"#94a3b8\",\n       canvasHintEnabled: true,\n       canvasHintText: \"\",\n       canvasHintBg: \"#0f172a\",\n       canvasHintColor: \"#94a3b8\",\n       danger: \"#f56565\",\n       textMain: \"#e2e8f0\",\n       textSoft: \"#94a3b8\",\n       topbarHeight: 52,\n       sidebarWidth: 350,\n       mobileFooterHeight: 40,\n       sidebarCollapsed: false,\n       nodeFill: \"#1e293b\",\n       nodeStroke: \"#475569\",\n       nodeTitle: \"#e2e8f0\",\n       nodeSub: \"#94a3b8\",\n       nodeTitleSize: 18,\n       nodeSubSize: 13,\n       nodeFont: \"Inter, system-ui, sans-serif\",\n       defaultEdge: \"#475569\",\n       selectionHandle: \"#f59e0b\",\n       selectionHandleSize: 8,\n       groupIndicator: \"#4fd1c5\",\n      canvasGradientTop: \"#1e2532\",\n       canvasGradientBottom: \"#050608\",\n       canvasBorder: \"#475569\",\n       canvasGrid: \"#475569\",\n       canvasGridSize: 50,\ncanvasGridEnabled: true,\nrackFrameFill: \"#0f172a\",\nrackGridEnabled: true,\n       rackFrameStroke: \"#4fd1c5\",\n       rackLineColor: \"#475569\",\n       rackTextColor: \"#4fd1c5\",\n\t   viewOnly: false,\n\t   defaultEdgeRouting: \"curved\",\n\t   animateConnections: false,\n\t   animationStyle: \"arrows\",\n\t   animationDirection: \"all\",\n\t   animationSpeed: 1.5,\n      };\n      let PAGE_STATE = Object.assign({}, DEFAULT_PAGE_STATE);\n\t  const FOV_ANIMATION_START = Date.now();\n\t  let ZONES_VISIBLE = true;\nconst ANIM_SETTINGS = {\n  masterAnim: true,\n  masterZones: true,\n  animTypes: { sweep: true, pulse: true, rings: true, spin: true, connections: true },\n  animCategories: { camera: true, doorbell: true, motion: true, smoke: true, wifi: true, sensor: true, sprinkler: true, connections: true },\n  zoneCategories: { camera: true, doorbell: true, motion: true, smoke: true, wifi: true, sensor: true, sprinkler: true }\n};\nfunction getShapeCategory(shape) {\n  const map = {\n    \"camera\": \"camera\", \"cctv\": \"camera\", \"ptz-cam\": \"camera\",\n    \"doorbell\": \"doorbell\",\n    \"motion-sensor\": \"motion\", \"motion-detect\": \"motion\",\n    \"smoke-detector\": \"smoke\", \"smoke-alarm\": \"smoke\",\n    \"access-point\": \"wifi\", \"wifi\": \"wifi\", \"router\": \"wifi\", \"wifi-strong\": \"wifi\", \"wifi-weak\": \"wifi\",\n    \"sensor\": \"sensor\", \"iot\": \"sensor\",\n    \"sprinkler\": \"sprinkler\", \"sprinkler-arc\": \"sprinkler\"\n  };\n  return map[shape] || null;\n}\nfunction isAnimationAllowed(shape, animType) {\n  if (!ANIM_SETTINGS.masterAnim) return false;\n  if (!ANIM_SETTINGS.animTypes[animType]) return false;\n  const cat = getShapeCategory(shape);\n  if (cat && !ANIM_SETTINGS.animCategories[cat]) return false;\n  return true;\n}\nfunction isZoneAllowed(shape) {\n  if (!ANIM_SETTINGS.masterZones) return false;\n  const cat = getShapeCategory(shape);\n  if (cat && !ANIM_SETTINGS.zoneCategories[cat]) return false;\n  return true;\n}\nfunction applyAnimZoneSettings() {\n  document.querySelectorAll(\".fov-group\").forEach(g => {\n    const nodeEl = g.closest(\"g[data-node-id]\");\n    if (!nodeEl) return;\n    const nodeId = nodeEl.dataset.nodeId;\n    const node = NODE_DATA[nodeId];\n    if (!node) return;\n    const cat = getShapeCategory(node.shape);\n    const zoneVisible = ANIM_SETTINGS.masterZones && (!cat || ANIM_SETTINGS.zoneCategories[cat]);\n    g.style.display = zoneVisible ? \"\" : \"none\";\n    if (zoneVisible && node.fovAnimate) {\n      const animType = node.fovAnimationType || \"sweep\";\n      const animAllowed = ANIM_SETTINGS.masterAnim && ANIM_SETTINGS.animTypes[animType] && (!cat || ANIM_SETTINGS.animCategories[cat]);\n      g.style.animationPlayState = animAllowed ? \"running\" : \"paused\";\n      g.querySelectorAll(\"circle\").forEach(c => c.style.animationPlayState = animAllowed ? \"running\" : \"paused\");\n    }\n  });\n  const connAnimAllowed = ANIM_SETTINGS.masterAnim && ANIM_SETTINGS.animTypes.connections && ANIM_SETTINGS.animCategories.connections;\n  document.querySelectorAll(\".edge-arrow-forward, .edge-arrow-backward\").forEach(a => {\n    a.style.animationPlayState = connAnimAllowed ? \"running\" : \"paused\";\n  });\n}\nlet ZONE_LEGEND = {};\nlet ZONE_PRESETS = {};\nlet copiedZoneStyle = null;\n\t  function hasCoverageZone(shape) {\n\t\t  const supportedShapes = [\n\t\t\t\"camera\", \"cctv\", \"doorbell\",\n\t\t\t\"motion-sensor\", \"smoke-detector\",\n\t\t\t\"access-point\", \"wifi\", \"router\",\n\t\t\t\"sensor\", \"iot\", \"sprinkler\"\n\t\t  ];\n\t\t  return supportedShapes.includes(shape);\n\t\t}\n\n\t\tfunction getCoverageDefaults(shape) {\n\t\t  const defaults = {\n\t\t\t\"camera\": { angle: 90, distance: 150, animationType: \"sweep\" },\n\t\t\t\"cctv\": { angle: 90, distance: 150, animationType: \"sweep\" },\n\t\t\t\"doorbell\": { angle: 120, distance: 100, animationType: \"sweep\" },\n\t\t\t\"motion-sensor\": { angle: 120, distance: 100, animationType: \"pulse\" },\n\t\t\t\"smoke-detector\": { angle: 360, distance: 80, animationType: \"pulse\" },\n\t\t\t\"access-point\": { angle: 360, distance: 200, animationType: \"rings\" },\n\t\t\t\"wifi\": { angle: 360, distance: 200, animationType: \"rings\" },\n\t\t\t\"router\": { angle: 360, distance: 200, animationType: \"rings\" },\n\t\t\t\"sensor\": { angle: 90, distance: 100, animationType: \"pulse\" },\n\t\t\t\"iot\": { angle: 90, distance: 100, animationType: \"pulse\" },\n\t\t\t\"sprinkler\": { angle: 90, distance: 120, animationType: \"spin\" }\n\t\t  };\n\t\t  return defaults[shape] || { angle: 90, distance: 150, animationType: \"sweep\" };\n\t\t}\n\t\tfunction toggleAllZones() {\n  ANIM_SETTINGS.masterZones = !ANIM_SETTINGS.masterZones;\n  const masterCheckbox = document.getElementById(\"zone-master\");\n  if (masterCheckbox) masterCheckbox.checked = ANIM_SETTINGS.masterZones;\n  applyAnimZoneSettings();\n}\n\nfunction copyZoneStyle(nodeId) {\n  const node = NODE_DATA[nodeId];\n  if (!node || !hasCoverageZone(node.shape)) return;\n  copiedZoneStyle = {\n    fovEnabled: node.fovEnabled,\n    fovAngle: node.fovAngle,\n    fovDistance: node.fovDistance,\n    fovInnerRadius: node.fovInnerRadius,\n    fovRotation: node.fovRotation,\n    fovColor: node.fovColor,\n    fovOpacity: node.fovOpacity,\n    fovGradient: node.fovGradient,\n    fovBorderColor: node.fovBorderColor,\n    fovBorderWidth: node.fovBorderWidth,\n    fovBorderStyle: node.fovBorderStyle,\n    fovBorderOpacity: node.fovBorderOpacity,\n    fovLabel: node.fovLabel,\n    fovLabelPosition: node.fovLabelPosition,\n    fovLabelSize: node.fovLabelSize,\n    fovLabelColor: node.fovLabelColor,\n    fovLabelBold: node.fovLabelBold,\n    fovLabelBg: node.fovLabelBg,\n    fovLabelBgColor: node.fovLabelBgColor,\n    fovLabelOffsetX: node.fovLabelOffsetX,\n    fovLabelOffsetY: node.fovLabelOffsetY,\n    fovAnimate: node.fovAnimate,\n    fovAnimationType: node.fovAnimationType,\n    fovSweep: node.fovSweep,\n    fovSpeed: node.fovSpeed\n  };\n}\n\nfunction pasteZoneStyle(nodeId) {\n  if (!copiedZoneStyle) return;\n  const node = NODE_DATA[nodeId];\n  if (!node || !hasCoverageZone(node.shape)) return;\n  pushUndo(\"paste zone style\");\n  Object.assign(node, copiedZoneStyle);\n  updateFovCone(nodeId);\n  if (currentNodeId === nodeId) {\n    claimTheImmortal(nodeId);\n  }\n}\n\nfunction applyZonePreset(preset) {\n  if (!currentNodeId) return;\n  const presets = {\n    \"security-cam\": { fovAngle: 90, fovDistance: 150, fovColor: \"#f59e0b\", fovOpacity: 20, fovAnimationType: \"sweep\", fovAnimate: false },\n    \"ptz-cam\": { fovAngle: 60, fovDistance: 200, fovColor: \"#f59e0b\", fovOpacity: 25, fovAnimationType: \"sweep\", fovAnimate: true, fovSweep: 180, fovSpeed: 8 },\n    \"motion-detect\": { fovAngle: 120, fovDistance: 100, fovColor: \"#10b981\", fovOpacity: 15, fovAnimationType: \"pulse\", fovAnimate: true, fovSpeed: 3 },\n    \"wifi-strong\": { fovAngle: 360, fovDistance: 150, fovColor: \"#3b82f6\", fovOpacity: 10, fovAnimationType: \"rings\", fovAnimate: true, fovSpeed: 4 },\n    \"wifi-weak\": { fovAngle: 360, fovDistance: 250, fovColor: \"#3b82f6\", fovOpacity: 8, fovGradient: true, fovAnimationType: \"rings\", fovAnimate: true, fovSpeed: 5 },\n    \"smoke-alarm\": { fovAngle: 360, fovDistance: 80, fovColor: \"#ef4444\", fovOpacity: 15, fovAnimationType: \"pulse\", fovAnimate: false },\n    \"sprinkler-arc\": { fovAngle: 90, fovDistance: 120, fovColor: \"#06b6d4\", fovOpacity: 20, fovAnimationType: \"spin\", fovAnimate: true, fovSpeed: 6 }\n  };\n  const allPresets = { ...presets, ...ZONE_PRESETS };\n  const settings = allPresets[preset];\n  if (!settings) return;\n  \n  pushUndo(\"apply zone preset\");\n  const node = NODE_DATA[currentNodeId];\n  node.fovEnabled = true;\n  Object.assign(node, settings);\n  updateFovCone(currentNodeId);\n  claimTheImmortal(currentNodeId);\n}\n\nfunction saveCustomZonePreset() {\n  if (!currentNodeId) return;\n  const node = NODE_DATA[currentNodeId];\n  if (!node.fovEnabled) {\n    alert(\"Enable the zone first before saving as preset\");\n    return;\n  }\n  const name = prompt(\"Enter preset name:\");\n  if (!name || !name.trim()) return;\n  \n  const presetId = name.toLowerCase().replace(/[^a-z0-9]+/g, \"-\");\n  ZONE_PRESETS[presetId] = {\n    fovAngle: node.fovAngle,\n    fovDistance: node.fovDistance,\n    fovInnerRadius: node.fovInnerRadius,\n    fovColor: node.fovColor,\n    fovOpacity: node.fovOpacity,\n    fovGradient: node.fovGradient,\n    fovBorderColor: node.fovBorderColor,\n    fovBorderWidth: node.fovBorderWidth,\n    fovBorderStyle: node.fovBorderStyle,\n    fovBorderOpacity: node.fovBorderOpacity,\n    fovAnimationType: node.fovAnimationType,\n    fovAnimate: node.fovAnimate,\n    fovSweep: node.fovSweep,\n    fovSpeed: node.fovSpeed\n  };\n  const select = document.getElementById(\"fov-preset\");\n  if (select && !select.querySelector(`option[value=\"${presetId}\"]`)) {\n    const opt = document.createElement(\"option\");\n    opt.value = presetId;\n    opt.textContent = name.trim() + \" (Custom)\";\n    select.appendChild(opt);\n  }\n  alert(\"Preset saved: \" + name.trim());\n}\n\nfunction bulkCopyZoneStyle() {\n  if (selectedNodes.size === 0) {\n    alert(\"Select at least one node first\");\n    return;\n  }\n  const firstId = [...selectedNodes][0];\n  if (copyZoneStyle(firstId)) {\n    alert(\"Zone style copied from first selected node\");\n  }\n}\n\nfunction bulkPasteZoneStyle() {\n  if (selectedNodes.size === 0) {\n    alert(\"Select nodes first\");\n    return;\n  }\n  if (!copiedZoneStyle) {\n    alert(\"Copy a zone style first\");\n    return;\n  }\n  pushUndo(\"bulk paste zone style\");\n  let count = 0;\n  selectedNodes.forEach(id => {\n    const node = NODE_DATA[id];\n    if (node && hasCoverageZone(node.shape)) {\n      Object.assign(node, copiedZoneStyle);\n      updateFovCone(id);\n      count++;\n    }\n  });\n  forgeTheTopology();\n  alert(`Zone style pasted to ${count} node(s)`);\n}\n\nfunction bulkToggleZones() {\n  if (selectedNodes.size === 0) {\n    alert(\"Select nodes first\");\n    return;\n  }\n  pushUndo(\"bulk toggle zones\");\n  const firstNode = NODE_DATA[[...selectedNodes][0]];\n  const newState = !(firstNode?.fovEnabled);\n  let count = 0;\n  selectedNodes.forEach(id => {\n    const node = NODE_DATA[id];\n    if (node && hasCoverageZone(node.shape)) {\n      node.fovEnabled = newState;\n      updateFovCone(id);\n      count++;\n    }\n  });\n  forgeTheTopology();\n  alert(`Toggled zones on ${count} node(s) to ${newState ? 'ON' : 'OFF'}`);\n}\n\nfunction loadCustomPresets() {\n  const select = document.getElementById(\"fov-preset\");\n  if (!select) return;\n  Object.keys(ZONE_PRESETS).forEach(id => {\n    if (!select.querySelector(`option[value=\"${id}\"]`)) {\n      const opt = document.createElement(\"option\");\n      opt.value = id;\n      opt.textContent = id.replace(/-/g, \" \") + \" ★\";\n      select.appendChild(opt);\n    }\n  });\n}\n\nfunction updateZoneLegend() {\n  const container = document.getElementById(\"edge-legend\");\n  if (!container) return;\n  container.querySelectorAll(\".zone-legend-item\").forEach(el => el.remove());\n  container.querySelectorAll(\".zone-legend-title\").forEach(el => el.remove());\n  const zoneColors = new Set();\n  Object.values(NODE_DATA).forEach(node => {\n    if (hasCoverageZone(node.shape) && node.fovEnabled && node.fovColor) {\n      zoneColors.add(node.fovColor);\n    }\n  });\n  \n  if (zoneColors.size === 0) return;\n  const zoneTitle = document.createElement(\"div\");\n  zoneTitle.className = \"legend-title zone-legend-title\";\n  zoneTitle.textContent = \"Zone Legend\";\n  zoneTitle.style.marginTop = \"12px\";\n  zoneTitle.style.paddingTop = \"8px\";\n  zoneTitle.style.borderTop = \"1px solid var(--edge-main)\";\n  container.appendChild(zoneTitle);\n  zoneColors.forEach(color => {\n    if (!ZONE_LEGEND[color]) {\n      ZONE_LEGEND[color] = \"Coverage Zone\";\n    }\n    const item = document.createElement(\"div\");\n    item.className = \"legend-item zone-legend-item\";\n    item.addEventListener(\"mousedown\", (e) => e.stopPropagation());\n    item.addEventListener(\"click\", (e) => e.stopPropagation());\n    \n    const swatch = document.createElement(\"span\");\n    swatch.className = \"legend-swatch\";\n    swatch.style.backgroundColor = color;\n    swatch.style.opacity = \"0.5\";\n    swatch.style.cursor = \"pointer\";\n    swatch.addEventListener(\"click\", (e) => {\n      e.stopPropagation();\n      const nodeWithColor = Object.entries(NODE_DATA).find(([id, n]) => \n        hasCoverageZone(n.shape) && n.fovEnabled && n.fovColor === color\n      );\n      if (nodeWithColor) {\n        claimTheImmortal(nodeWithColor[0]);\n      }\n    });\n    \n    const label = document.createElement(\"span\");\n    label.className = \"legend-label\";\n    label.textContent = ZONE_LEGEND[color];\n    label.contentEditable = true;\n    label.addEventListener(\"focus\", () => label.classList.add(\"editing\"));\n    label.addEventListener(\"blur\", () => {\n      label.classList.remove(\"editing\");\n      ZONE_LEGEND[color] = label.textContent.trim() || \"Coverage Zone\";\n    });\n    label.addEventListener(\"keydown\", (e) => {\n      if (e.key === \"Enter\") {\n        e.preventDefault();\n        label.blur();\n      }\n    });\n    \n    item.append(swatch, label);\n    container.appendChild(item);\n  });\n  \n  updateLegendVisibility();\n}\n\t  function isViewOnly() {\n       return PAGE_STATE.viewOnly === true;\n      }\n\t  let viewOnlyClickCount = 0;\n      let viewOnlyClickTimer = null;\n      let viewOnlyClickTarget = null;\n      function handleViewOnlyClick(id, type) {\n       if (!isViewOnly()) return false;\n       if (viewOnlyClickTarget !== id) {\n        viewOnlyClickCount = 0;\n        viewOnlyClickTarget = id;\n       }\n       viewOnlyClickCount++;\n       clearTimeout(viewOnlyClickTimer);\n       viewOnlyClickTimer = setTimeout(() => {\n        viewOnlyClickCount = 0;\n        viewOnlyClickTarget = null;\n       }, 2000);\n       if (viewOnlyClickCount >= 5) {\n        viewOnlyClickCount = 0;\n        viewOnlyClickTarget = null;\n        document.body.classList.add(\"view-only-inspect\");\n        if (type === 'node') {\n         currentNodeId = id;\n         const data = NODE_DATA[id];\n         if (data) {\n          document.querySelectorAll(\".node-group\").forEach((n) => {\n           n.classList.toggle(\"active\", n.dataset.nodeId === id);\n          });\n          document.getElementById(\"node-panel\").style.display = \"block\";\n          document.getElementById(\"edge-panel\").style.display = \"none\";\n          document.getElementById(\"node-name\").textContent = data.name;\n          document.getElementById(\"node-ip\").textContent = data.ip;\n\t\t\tconst fovSection = document.getElementById(\"fov-section\");\n\t\t\tif (fovSection) {\n\t\t\t  if (hasCoverageZone(data.shape)) {\n\t\t\t\tconst defaults = getCoverageDefaults(data.shape);\n\t\t\t\tfovSection.style.display = \"block\";\n\t\t\t\tdocument.getElementById(\"fov-enabled\").checked = data.fovEnabled || false;\n\t\t\t\tdocument.getElementById(\"fov-angle\").value = data.fovAngle || defaults.angle;\n\t\t\t\tdocument.getElementById(\"fov-angle-value\").textContent = (data.fovAngle || defaults.angle) + \"°\";\n\t\t\t\tdocument.getElementById(\"fov-distance\").value = data.fovDistance || defaults.distance;\n\t\t\t\tdocument.getElementById(\"fov-distance-value\").textContent = data.fovDistance || defaults.distance;\n\t\t\t\tdocument.getElementById(\"fov-inner-radius\").value = data.fovInnerRadius || 0;\n\t\t\t\tdocument.getElementById(\"fov-inner-radius-value\").textContent = data.fovInnerRadius || 0;\n\t\t\t\tdocument.getElementById(\"fov-rotation\").value = data.fovRotation || 0;\n\t\t\t\tdocument.getElementById(\"fov-rotation-value\").textContent = (data.fovRotation || 0) + \"°\";\n\t\t\t\tdocument.getElementById(\"fov-color\").value = data.fovColor || \"#f59e0b\";\n\t\t\t\tdocument.getElementById(\"fov-opacity\").value = data.fovOpacity || 20;\n\t\t\t\tdocument.getElementById(\"fov-opacity-value\").textContent = (data.fovOpacity || 20) + \"%\";\n\t\t\t\tdocument.getElementById(\"fov-gradient\").checked = data.fovGradient || false;\n\t\t\t\tdocument.getElementById(\"fov-border-color\").value = data.fovBorderColor || \"#f59e0b\";\n\t\t\t\tdocument.getElementById(\"fov-border-width\").value = data.fovBorderWidth ?? 2;\n\t\t\t\tdocument.getElementById(\"fov-border-width-value\").textContent = data.fovBorderWidth ?? 2;\n\t\t\t\tdocument.getElementById(\"fov-border-style\").value = data.fovBorderStyle || \"solid\";\n\t\t\t\tdocument.getElementById(\"fov-border-opacity\").value = data.fovBorderOpacity ?? 100;\n\t\t\t\tdocument.getElementById(\"fov-border-opacity-value\").textContent = (data.fovBorderOpacity ?? 100) + \"%\";\n\t\t\t\tdocument.getElementById(\"fov-label\").value = data.fovLabel || \"\";\n\t\t\t\tdocument.getElementById(\"fov-label-position\").value = data.fovLabelPosition || \"center\";\n\t\t\t\tdocument.getElementById(\"fov-label-size\").value = data.fovLabelSize || 14;\n\t\t\t\tdocument.getElementById(\"fov-label-size-value\").textContent = (data.fovLabelSize || 14) + \"px\";\n\t\t\t\tdocument.getElementById(\"fov-label-color\").value = data.fovLabelColor || \"#ffffff\";\n\t\t\t\tdocument.getElementById(\"fov-label-bold\").checked = data.fovLabelBold || false;\n\t\t\t\tdocument.getElementById(\"fov-label-bg\").checked = data.fovLabelBg || false;\n\t\t\t\tdocument.getElementById(\"fov-label-bg-color\").value = data.fovLabelBgColor || \"#000000\";\n\t\t\t\tdocument.getElementById(\"fov-label-offset-x\").value = data.fovLabelOffsetX || 0;\n\t\t\t\tdocument.getElementById(\"fov-label-offset-x-value\").textContent = data.fovLabelOffsetX || 0;\n\t\t\t\tdocument.getElementById(\"fov-label-offset-y\").value = data.fovLabelOffsetY || 0;\n\t\t\t\tdocument.getElementById(\"fov-label-offset-y-value\").textContent = data.fovLabelOffsetY || 0;\n\t\t\t\tdocument.getElementById(\"fov-animate\").checked = data.fovAnimate || false;\n\t\t\t\tdocument.getElementById(\"fov-animation-type\").value = data.fovAnimationType || defaults.animationType;\n\t\t\t\tdocument.getElementById(\"fov-sweep\").value = data.fovSweep || 120;\n\t\t\t\tdocument.getElementById(\"fov-sweep-value\").textContent = (data.fovSweep || 120) + \"°\";\n\t\t\t\tdocument.getElementById(\"fov-speed\").value = data.fovSpeed || 4;\n\t\t\t\tdocument.getElementById(\"fov-speed-value\").textContent = (data.fovSpeed || 4) + \"s\";\n\t\t\t\tdocument.getElementById(\"fov-opacity\").value = data.fovOpacity || 20;\n\t\t\t\tdocument.getElementById(\"fov-opacity-value\").textContent = (data.fovOpacity || 20) + \"%\";\n\t\t\t  } else {\n\t\t\t\tfovSection.style.display = \"none\";\n\t\t\t  }\n\t\t\t}\n          document.getElementById(\"node-role\").textContent = data.role;\n          document.getElementById(\"node-mac\").textContent = data.mac || \"--\";\n          document.getElementById(\"node-rack\").textContent = data.rackUnit || \"--\";\n          document.getElementById(\"node-uheight\").textContent = (data.uHeight || \"1\") + \"U\";\n          const tagList = document.getElementById(\"node-tags\");\n          if (tagList) {\n           tagList.innerHTML = \"\";\n           if (data.tags && data.tags.length > 0) {\n            data.tags.forEach((tag) => {\n             const li = document.createElement(\"li\");\n             li.textContent = tag;\n             tagList.appendChild(li);\n            });\n           }\n          }\n          const noteList = document.getElementById(\"node-notes\");\n          if (noteList) {\n           noteList.innerHTML = \"\";\n           if (data.notes && data.notes.length > 0) {\n            data.notes.forEach((note) => {\n             const li = document.createElement(\"li\");\n             li.textContent = note;\n             noteList.appendChild(li);\n            });\n           }\n          }\n         }\n        } else if (type === 'edge') {\n         currentEdgeId = id;\n         const edge = EDGE_DATA.list.find(e => e.id === id);\n         if (edge) {\n          document.querySelectorAll(\".edge\").forEach((e) => {\n           e.classList.toggle(\"active\", e.dataset.edgeId === id);\n          });\n          document.getElementById(\"edge-panel\").style.display = \"block\";\n          document.getElementById(\"node-panel\").style.display = \"none\";\n          document.getElementById(\"edge-from\").textContent = NODE_DATA[edge.from]?.name || edge.from;\n          document.getElementById(\"edge-to\").textContent = NODE_DATA[edge.to]?.name || edge.to;\n          document.getElementById(\"edge-label\").value = edge.label || \"\";\n         }\n        }\n        return true;\n       }\n       return false;\n      }\n      const CANVAS_WIDTH = 4000;\n      let CANVAS_HEIGHT = 3000;\n      const BASE_CANVAS_HEIGHT = 3000;\n      const CANVAS_PADDING = 100;\n      const RACK_U_HEIGHT = 70;\n      const RACK_WIDTH = 600;\n      const RACK_START_X = CANVAS_WIDTH / 2;\n      const RACK_START_Y = CANVAS_PADDING + 100;\n      function getRackUHeight(rackId) {\n      const capacity = getRackCapacity(rackId);\n      const availableHeight = CANVAS_HEIGHT - RACK_START_Y - 200;\n      return Math.floor(availableHeight / capacity);\n      }\n      let canvasState = {\n       zoom: 1,\n       panX: 0,\n       panY: 0,\n       minZoom: 0.25,\n       maxZoom: 4,\n       isPanning: false,\n       panStartX: 0,\n       panStartY: 0,\n       spacePressed: false,\n      };\n      function getViewBox() {\n       const viewWidth = CANVAS_WIDTH / canvasState.zoom;\n       const viewHeight = CANVAS_HEIGHT / canvasState.zoom;\n       return {\n        x: canvasState.panX,\n        y: canvasState.panY,\n        width: viewWidth,\n        height: viewHeight,\n       };\n      }\nfunction updateViewBox() {\n  const svg = document.getElementById(\"map\");\n  const vb = getViewBox();\n  svg.setAttribute(\"viewBox\", `${vb.x} ${vb.y} ${vb.width} ${vb.height}`);\n  const zoomLevel = document.getElementById(\"zoom-level\");\n  if (zoomLevel) {\n    zoomLevel.textContent = Math.round(canvasState.zoom * 100) + \"%\";\n  }\n  if (canvasState.zoom < 0.5) {\n    svg.classList.add(\"low-zoom\");\n  } else {\n    svg.classList.remove(\"low-zoom\");\n  }\n  updateMinimap();\n  populateRackDropdown();\n}\n\t  \nlet lastMinimapRender = 0;\nconst MINIMAP_THROTTLE = 100;\n\nfunction updateMinimap() {\n  const now = performance.now();\n  if (now - lastMinimapRender < MINIMAP_THROTTLE) return;\n  lastMinimapRender = now;\n  \n  const minimapViewport = document.getElementById(\"minimap-viewport\");\n  const minimapSvg = document.getElementById(\"minimap\");\n  if (!minimapViewport || !minimapSvg) return;\n  const vb = getViewBox();\n  minimapViewport.setAttribute(\"x\", vb.x);\n  minimapViewport.setAttribute(\"y\", vb.y);\n  minimapViewport.setAttribute(\"width\", vb.width);\n  minimapViewport.setAttribute(\"height\", vb.height);\n  minimapSvg.querySelectorAll(\".minimap-node, .minimap-edge, .minimap-wall, .minimap-rect\").forEach(el => el.remove());\n  const frag = document.createDocumentFragment();\n  const ns = \"http://www.w3.org/2000/svg\";\n  const dotColor = PAGE_STATE.minimapDots || \"#94a3b8\";\n\n  if (RECT_DATA && RECT_DATA.list && currentView.mode !== \"rack\") {\n    RECT_DATA.list.forEach((rect) => {\n      if (rect.lineStyle === \"wall\") {\n        const wallRect = document.createElementNS(ns, \"rect\");\n        wallRect.setAttribute(\"x\", rect.x);\n        wallRect.setAttribute(\"y\", rect.y);\n        wallRect.setAttribute(\"width\", rect.width);\n        wallRect.setAttribute(\"height\", rect.height);\n        wallRect.style.fill = rect.color || \"#666\";\n        wallRect.style.fillOpacity = \"0.6\";\n        wallRect.style.stroke = rect.borderColor || rect.color || \"#666\";\n        wallRect.style.strokeWidth = \"4\";\n        wallRect.classList.add(\"minimap-wall\");\n        frag.appendChild(wallRect);\n      }\n    });\n  }\n\n  EDGE_DATA.list.forEach((edge) => {\n    if (edge.type === \"custom\") {\n      if (Array.isArray(edge.points) && edge.points.length >= 2) {\n        const polyline = document.createElementNS(ns, \"polyline\");\n        polyline.setAttribute(\"points\", edge.points.map(p => `${p.x},${p.y}`).join(\" \"));\n        polyline.classList.add(\"minimap-edge\");\n        frag.appendChild(polyline);\n      }\n      return;\n    }\n    const fromNode = NODE_DATA[edge.from];\n    const toNode = NODE_DATA[edge.to];\n    if (!fromNode || !toNode) return;\n    if (currentView.mode === \"rack\") {\n      if (fromNode.assignedRack !== currentView.rackId || toNode.assignedRack !== currentView.rackId) return;\n    } else {\n      if (fromNode.assignedRack || toNode.assignedRack) return;\n    }\n    const p1 = savedPositions[edge.from];\n    const p2 = savedPositions[edge.to];\n    if (!p1 || !p2) return;\n    const routing = edge.routing || PAGE_STATE.defaultEdgeRouting || \"curved\";\n    \n    if (routing === \"orthogonal\") {\n      const dx = p2.x - p1.x;\n      const dy = p2.y - p1.y;\n      const polyline = document.createElementNS(ns, \"polyline\");\n      let points;\n      if (Math.abs(dx) > Math.abs(dy)) {\n        const midX = p1.x + dx / 2;\n        points = `${p1.x},${p1.y} ${midX},${p1.y} ${midX},${p2.y} ${p2.x},${p2.y}`;\n      } else {\n        const midY = p1.y + dy / 2;\n        points = `${p1.x},${p1.y} ${p1.x},${midY} ${p2.x},${midY} ${p2.x},${p2.y}`;\n      }\n      polyline.setAttribute(\"points\", points);\n      polyline.classList.add(\"minimap-edge\");\n      frag.appendChild(polyline);\n    } else {\n      const line = document.createElementNS(ns, \"line\");\n      line.setAttribute(\"x1\", p1.x);\n      line.setAttribute(\"y1\", p1.y);\n      line.setAttribute(\"x2\", p2.x);\n      line.setAttribute(\"y2\", p2.y);\n      line.classList.add(\"minimap-edge\");\n      frag.appendChild(line);\n    }\n  });\n\n  Object.entries(savedPositions).forEach(([id, pos]) => {\n    const node = NODE_DATA[id];\n    if (!node) return;\n    if (currentView.mode === \"rack\") {\n      if (node.assignedRack !== currentView.rackId) return;\n    } else {\n      if (node.assignedRack) return;\n    }\n    const nodeSize = savedSizes[id] || 55;\n    const s = nodeSize * 0.5;\n    \n    if (node.isRack) {\n      const rect = document.createElementNS(ns, \"rect\");\n      rect.setAttribute(\"x\", pos.x - s);\n      rect.setAttribute(\"y\", pos.y - s);\n      rect.setAttribute(\"width\", s * 2);\n      rect.setAttribute(\"height\", s * 2);\n      rect.style.fill = dotColor;\n      rect.classList.add(\"minimap-node\");\n      frag.appendChild(rect);\n    } else if (hasCoverageZone(node.shape)) {\n      const diamond = document.createElementNS(ns, \"polygon\");\n      const points = `${pos.x},${pos.y - s} ${pos.x + s},${pos.y} ${pos.x},${pos.y + s} ${pos.x - s},${pos.y}`;\n      diamond.setAttribute(\"points\", points);\n      diamond.style.fill = \"none\";\n      diamond.style.stroke = dotColor;\n      diamond.style.strokeWidth = \"6\";\n      diamond.classList.add(\"minimap-node\");\n      frag.appendChild(diamond);\n    } else {\n      const circle = document.createElementNS(ns, \"circle\");\n      circle.setAttribute(\"cx\", pos.x);\n      circle.setAttribute(\"cy\", pos.y);\n      circle.setAttribute(\"r\", s);\n      circle.style.fill = dotColor;\n      circle.classList.add(\"minimap-node\");\n      frag.appendChild(circle);\n    }\n  });\n\n  minimapSvg.insertBefore(frag, minimapViewport);\n}\n\n      function zoomTo(newZoom, centerX, centerY) {\n       const oldZoom = canvasState.zoom;\n       newZoom = Math.max(canvasState.minZoom, Math.min(canvasState.maxZoom, newZoom));\n       if (centerX !== undefined && centerY !== undefined) {\n        const oldWidth = CANVAS_WIDTH / oldZoom;\n        const oldHeight = CANVAS_HEIGHT / oldZoom;\n        const newWidth = CANVAS_WIDTH / newZoom;\n        const newHeight = CANVAS_HEIGHT / newZoom;\n        const pointX = canvasState.panX + centerX * oldWidth;\n        const pointY = canvasState.panY + centerY * oldHeight;\n        canvasState.panX = pointX - centerX * newWidth;\n        canvasState.panY = pointY - centerY * newHeight;\n       }\n       canvasState.zoom = newZoom;\n       constrainPan();\n       updateViewBox();\n      }\n      function constrainPan() {\n       const viewWidth = CANVAS_WIDTH / canvasState.zoom;\n       const viewHeight = CANVAS_HEIGHT / canvasState.zoom;\n       const minVisiblePortion = 0.1;\n       const maxPanX = CANVAS_WIDTH - viewWidth * minVisiblePortion;\n       const maxPanY = CANVAS_HEIGHT - viewHeight * minVisiblePortion;\n       const minPanX = -viewWidth * (1 - minVisiblePortion);\n       const minPanY = -viewHeight * (1 - minVisiblePortion);\n       canvasState.panX = Math.max(minPanX, Math.min(maxPanX, canvasState.panX));\n       canvasState.panY = Math.max(minPanY, Math.min(maxPanY, canvasState.panY));\n      }\n      function fitToContent() {\n       const positions = Object.values(savedPositions);\n       if (positions.length === 0) {\n        resetView();\n        return;\n       }\n       let minX = Infinity,\n        minY = Infinity,\n        maxX = -Infinity,\n        maxY = -Infinity;\n       positions.forEach((pos) => {\n        minX = Math.min(minX, pos.x - 100);\n        minY = Math.min(minY, pos.y - 100);\n        maxX = Math.max(maxX, pos.x + 100);\n        maxY = Math.max(maxY, pos.y + 100);\n       });\n       const contentWidth = maxX - minX + 200;\n       const contentHeight = maxY - minY + 200;\n       const viewport = document.getElementById(\"canvas-viewport\");\n       const vpRect = viewport.getBoundingClientRect();\n       const aspectRatio = vpRect.width / vpRect.height;\n       const contentAspect = contentWidth / contentHeight;\n       let newZoom;\n       if (contentAspect > aspectRatio) {\n        newZoom = (vpRect.width / contentWidth) * (CANVAS_WIDTH / vpRect.width);\n       } else {\n        newZoom = (vpRect.height / contentHeight) * (CANVAS_HEIGHT / vpRect.height);\n       }\n       newZoom = Math.max(canvasState.minZoom, Math.min(2, newZoom));\n       const viewWidth = CANVAS_WIDTH / newZoom;\n       const viewHeight = CANVAS_HEIGHT / newZoom;\n       canvasState.zoom = newZoom;\n       canvasState.panX = minX - 100 - (viewWidth - contentWidth) / 2;\n       canvasState.panY = minY - 100 - (viewHeight - contentHeight) / 2;\n       constrainPan();\n       updateViewBox();\n      }\n      function resetView() {\n       canvasState.zoom = 1;\n       canvasState.panX = 0;\n       canvasState.panY = 0;\n       updateViewBox();\n      }\n      function focusOnNodes(nodeIds) {\n       if (!nodeIds || nodeIds.length === 0) return;\n       const positions = nodeIds.map(id => savedPositions[id]).filter(Boolean);\n       if (positions.length === 0) return;\n       let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;\n       positions.forEach(pos => {\n        minX = Math.min(minX, pos.x);\n        minY = Math.min(minY, pos.y);\n        maxX = Math.max(maxX, pos.x);\n        maxY = Math.max(maxY, pos.y);\n       });\n       const padding = 150;\n       const contentWidth = Math.max(maxX - minX + padding * 2, 300);\n       const contentHeight = Math.max(maxY - minY + padding * 2, 300);\n       const centerX = (minX + maxX) / 2;\n       const centerY = (minY + maxY) / 2;\n       const viewport = document.getElementById(\"canvas-viewport\");\n       const vpRect = viewport.getBoundingClientRect();\n       const aspectRatio = vpRect.width / vpRect.height;\n       const contentAspect = contentWidth / contentHeight;\n       let newZoom;\n       if (contentAspect > aspectRatio) {\n        newZoom = (vpRect.width / contentWidth) * (CANVAS_WIDTH / vpRect.width);\n       } else {\n        newZoom = (vpRect.height / contentHeight) * (CANVAS_HEIGHT / vpRect.height);\n       }\n       newZoom = Math.max(canvasState.minZoom, Math.min(1.5, newZoom));\n       const viewWidth = CANVAS_WIDTH / newZoom;\n       const viewHeight = CANVAS_HEIGHT / newZoom;\n       canvasState.zoom = newZoom;\n       canvasState.panX = centerX - viewWidth / 2;\n       canvasState.panY = centerY - viewHeight / 2;\n       constrainPan();\n       updateViewBox();\n      }\n      window.addEventListener(\"DOMContentLoaded\", () => {\n       const toggle = document.getElementById(\"mobile-menu-toggle\");\n       const menu = document.getElementById(\"topbar-menu\");\n       if (!toggle || !menu) return;\n       toggle.addEventListener(\"click\", () => {\n        menu.classList.toggle(\"open\");\n       });\n       menu.addEventListener(\"click\", (e) => {\n        const target = e.target.closest(\"a, button\");\n        if (!target) return;\n        if (isMobileDevice()) {\n       menu.classList.remove(\"open\");\n      }\n       });\n       const minimapCloseBtn = document.getElementById(\"minimap-close-btn\");\n       if (minimapCloseBtn) {\n        const handleMinimapClose = (e) => {\n         e.stopPropagation();\n         e.preventDefault();\n         minimapCollapsed = true;\n         updateMinimapVisibility();\n        };\n        minimapCloseBtn.addEventListener(\"click\", handleMinimapClose);\n        minimapCloseBtn.addEventListener(\"touchend\", handleMinimapClose);\n       }\n       const drawToolbarCloseBtn = document.getElementById(\"draw-toolbar-close-btn\");\n       if (drawToolbarCloseBtn) {\n        const handleDrawClose = (e) => {\n         e.stopPropagation();\n         e.preventDefault();\n         drawToolbarCollapsed = true;\n         updateDrawToolbarVisibility();\n        };\n        drawToolbarCloseBtn.addEventListener(\"click\", handleDrawClose);\n        drawToolbarCloseBtn.addEventListener(\"touchend\", handleDrawClose);\n       }\n       const topologyToolbarCloseBtn = document.getElementById(\"topology-toolbar-close-btn\");\n       if (topologyToolbarCloseBtn) {\n        const handleTopologyClose = (e) => {\n         e.stopPropagation();\n         e.preventDefault();\n         topologyToolbarCollapsed = true;\n         updateTopologyToolbarVisibility();\n        };\n        topologyToolbarCloseBtn.addEventListener(\"click\", handleTopologyClose);\n        topologyToolbarCloseBtn.addEventListener(\"touchstart\", (e) => e.preventDefault(), {\n         passive: false\n        });\n        topologyToolbarCloseBtn.addEventListener(\"touchend\", handleTopologyClose);\n       }\n       updateMinimapVisibility();\n       updateDrawToolbarVisibility();\n       updateTopologyToolbarVisibility();\n      });\n      function applyLayerFilter() {\n       activeLayers.clear();\n       if (document.getElementById(\"layer-physical\").checked) activeLayers.add(\"physical\");\n       if (document.getElementById(\"layer-logical\").checked) activeLayers.add(\"logical\");\n       if (document.getElementById(\"layer-security\").checked) activeLayers.add(\"security\");\n       if (document.getElementById(\"layer-application\").checked) activeLayers.add(\"application\");\n       forgeTheTopology();\n      }\n      function isNodeVisible(nodeId) {\n       const node = NODE_DATA[nodeId];\n       if (!node) return false;\n       const nodeLayer = node.layer || \"physical\";\n       return activeLayers.has(nodeLayer);\n      }\n      function enterRack(rackId) {\n      if (!NODE_DATA[rackId] || !NODE_DATA[rackId].isRack) return;\n      const rackCapacity = getRackCapacity(rackId);\n      const neededHeight = RACK_START_Y + (rackCapacity * RACK_U_HEIGHT) + 200;\n      if (neededHeight > BASE_CANVAS_HEIGHT) {\n      CANVAS_HEIGHT = neededHeight;\n      }\n       savedTopologyView = {\n        zoom: canvasState.zoom,\n        panX: canvasState.panX,\n        panY: canvasState.panY\n       };\n       currentView.mode = \"rack\";\n       currentView.rackId = rackId;\n       const backBtn = document.getElementById(\"back-to-topology-btn\");\n       if (backBtn) backBtn.style.display = \"inline-block\";\n       const hint = document.getElementById(\"canvas-hint\");\n       if (hint) {\n        hint.textContent = `Viewing: ${NODE_DATA[rackId]?.name || 'Rack'} | Double click empty space to exit`;\n        hint.classList.add(\"visible\");\n       }\n      const rackUHeight = getRackUHeight(rackId);\n      const rackHeight = rackCapacity * rackUHeight;\n      const rackCenterX = RACK_START_X;\n      const rackCenterY = RACK_START_Y + (rackHeight / 2);\n       const viewWidth = CANVAS_WIDTH / canvasState.zoom;\n       const viewHeight = CANVAS_HEIGHT / canvasState.zoom;\n       canvasState.panX = rackCenterX - (viewWidth / 2);\n       canvasState.panY = rackCenterY - (viewHeight / 2);\n       constrainPan();\n       updateViewBox();\n       forgeTheTopology();\n      }\n      function exitRack() {\n      CANVAS_HEIGHT = BASE_CANVAS_HEIGHT;\n      currentView.mode = \"topology\";\n      currentView.rackId = null;\n       const backBtn = document.getElementById(\"back-to-topology-btn\");\n       if (backBtn) backBtn.style.display = \"none\";\n       const hint = document.getElementById(\"canvas-hint\");\n       if (hint) {\n        hint.classList.remove(\"visible\");\n       }\n       if (savedTopologyView) {\n        canvasState.zoom = savedTopologyView.zoom;\n        canvasState.panX = savedTopologyView.panX;\n        canvasState.panY = savedTopologyView.panY;\n        updateViewBox();\n       }\n       forgeTheTopology();\n      }\n      function getRackCapacity(rackId) {\n       const node = NODE_DATA[rackId];\n       return node && node.rackCapacity ? parseInt(node.rackCapacity) : 42;\n      }\n      function populateRackDropdown() {\n       const dropdown = document.getElementById(\"node-assigned-rack\");\n       if (!dropdown) return;\n       dropdown.innerHTML = '<option value=\"\">None</option>';\n       Object.keys(NODE_DATA).forEach(id => {\n        if (NODE_DATA[id].isRack) {\n         const option = document.createElement(\"option\");\n         option.value = id;\n         option.textContent = NODE_DATA[id].name;\n         dropdown.appendChild(option);\n        }\n       });\n      }\n      function wieldThePower() {\n       const root = document.documentElement;\n       root.style.setProperty(\"--panel\", PAGE_STATE.panel);\n       root.style.setProperty(\"--panel-alt\", PAGE_STATE.panelAlt);\n       root.style.setProperty(\"--sidebar-bg\", PAGE_STATE.sidebarBg || PAGE_STATE.panelAlt);\n       root.style.setProperty(\"--btn-bg\", PAGE_STATE.btnBg || PAGE_STATE.panel);\n       root.style.setProperty(\"--btn-text\", PAGE_STATE.btnText || \"#e2e8f0\");\n       root.style.setProperty(\"--tag-fill\", PAGE_STATE.tagFill || \"#1e293b\");\n       root.style.setProperty(\"--tag-text\", PAGE_STATE.tagText || \"#e2e8f0\");\n       root.style.setProperty(\"--tag-border\", PAGE_STATE.tagBorder || \"#475569\");\n       root.style.setProperty(\"--input-bg\", PAGE_STATE.inputBg || \"#0b0e13\");\n       root.style.setProperty(\"--input-text\", PAGE_STATE.inputText || \"#e2e8f0\");\n       root.style.setProperty(\"--input-border\", PAGE_STATE.inputBorder || \"#1f2937\");\n       root.style.setProperty(\"--input-font\", PAGE_STATE.inputFont || \"Inter, system-ui, sans-serif\");\n       root.style.setProperty(\"--input-font-size\", (PAGE_STATE.inputFontSize || 14) + \"px\");\n       root.style.setProperty(\"--toolbar-bg\", PAGE_STATE.toolbarBg || \"#0f172a\");\n       root.style.setProperty(\"--toolbar-border\", PAGE_STATE.toolbarBorder || \"#1f2937\");\n       root.style.setProperty(\"--toolbar-text\", PAGE_STATE.toolbarText || \"#94a3b8\");\n       root.style.setProperty(\"--toolbar-btn-bg\", PAGE_STATE.toolbarBtnBg || \"#0b0e13\");\n       root.style.setProperty(\"--toolbar-btn-text\", PAGE_STATE.toolbarBtnText || \"#e2e8f0\");\n       root.style.setProperty(\"--minimap-dots\", PAGE_STATE.minimapDots || \"#94a3b8\");\n       root.style.setProperty(\"--canvas-hint-bg\", PAGE_STATE.canvasHintBg || \"#0f172a\");\n       root.style.setProperty(\"--canvas-hint-color\", PAGE_STATE.canvasHintColor || \"#94a3b8\");\n       const canvasHint = document.getElementById(\"canvas-hint\");\n       if (canvasHint) {\n        canvasHint.style.display = PAGE_STATE.canvasHintEnabled === false ? \"none\" : \"\";\n        if (PAGE_STATE.canvasHintText !== undefined && PAGE_STATE.canvasHintText !== \"\") {\n         canvasHint.textContent = PAGE_STATE.canvasHintText;\n        } else {\n         canvasHint.innerHTML = DEFAULT_CANVAS_HINT;\n        }\n       }\n       root.style.setProperty(\"--accent\", PAGE_STATE.accent);\n       root.style.setProperty(\"--danger\", PAGE_STATE.danger);\n       root.style.setProperty(\"--text-main\", PAGE_STATE.textMain);\n       root.style.setProperty(\"--text-soft\", PAGE_STATE.textSoft);\n       root.style.setProperty(\"--topbar-bg\", PAGE_STATE.topbarBg);\n       root.style.setProperty(\"--topbar-border\", PAGE_STATE.topbarBorder);\n       root.style.setProperty(\"--node-fill\", PAGE_STATE.nodeFill || \"#1e293b\");\n       root.style.setProperty(\"--node-stroke\", PAGE_STATE.nodeStroke || \"#475569\");\n       root.style.setProperty(\"--node-title\", PAGE_STATE.nodeTitle || \"#e2e8f0\");\n       root.style.setProperty(\"--node-sub\", PAGE_STATE.nodeSub || \"#94a3b8\");\n       root.style.setProperty(\"--node-title-size\", (PAGE_STATE.nodeTitleSize || 18) + \"px\");\n       root.style.setProperty(\"--node-sub-size\", (PAGE_STATE.nodeSubSize || 13) + \"px\");\n       root.style.setProperty(\"--node-font\", PAGE_STATE.nodeFont || \"Inter, system-ui, sans-serif\");\n       root.style.setProperty(\"--default-edge\", PAGE_STATE.defaultEdge || \"#475569\");\n       root.style.setProperty(\"--selection-handle\", PAGE_STATE.selectionHandle || \"#f59e0b\");\n       root.style.setProperty(\"--selection-handle-size\", (PAGE_STATE.selectionHandleSize || 8) + \"px\");\n       root.style.setProperty(\"--group-indicator\", PAGE_STATE.groupIndicator || \"#4fd1c5\");\n       const topbarHeight = PAGE_STATE.topbarHeight || 52;\n       const sidebarWidth = PAGE_STATE.sidebarWidth || 350;\n       const mobileFooterHeight = PAGE_STATE.mobileFooterHeight || 40;\n       root.style.setProperty(\"--topbar-height\", topbarHeight + \"px\");\n       root.style.setProperty(\"--sidebar-width\", sidebarWidth + \"px\");\n       root.style.setProperty(\"--mobile-footer-height\", mobileFooterHeight + \"vh\");\n       const mainEl = document.querySelector(\"main\");\n       const detailsPanel = document.getElementById(\"details-panel\");\n       const sidebarToggle = document.getElementById(\"sidebar-toggle\");\n       if (PAGE_STATE.sidebarCollapsed) {\n        if (mainEl) mainEl.classList.add(\"sidebar-collapsed\");\n        if (detailsPanel) detailsPanel.classList.add(\"collapsed\");\n        if (sidebarToggle) {\n         sidebarToggle.classList.add(\"collapsed\");\n         sidebarToggle.textContent = \"▶\";\n        }\n       } else {\n        if (mainEl) mainEl.classList.remove(\"sidebar-collapsed\");\n        if (detailsPanel) detailsPanel.classList.remove(\"collapsed\");\n        if (sidebarToggle) {\n         sidebarToggle.classList.remove(\"collapsed\");\n         sidebarToggle.textContent = \"◀\";\n        }\n       }\n       if (PAGE_STATE.background) {\n        document.body.style.background = PAGE_STATE.background;\n       } else {\n        document.body.style.background = `radial-gradient(circle at top, ${PAGE_STATE.canvasGradientTop || \"#1e2532\"} 0, ${PAGE_STATE.canvasGradientBottom || \"#050608\"} 70%)`;\n       }\n       document.title = PAGE_STATE.title || DEFAULT_PAGE_STATE.title;\n       const titleEl = document.getElementById(\"page-title\");\n       if (titleEl) {\n        titleEl.textContent = PAGE_STATE.title || DEFAULT_PAGE_STATE.title;\n       }\n       const sidebarToggleEl = document.getElementById(\"sidebar-toggle\");\n       const isMobile = isMobileDevice();\n       if (sidebarToggleEl) {\n        sidebarToggleEl.style.display = isMobile ? \"none\" : \"flex\";\n       }\n       const viewOnlyMode = PAGE_STATE.viewOnly === true;\n       const addNodeBtn = document.getElementById(\"add-node-btn\");\n       const addRackBtn = document.getElementById(\"add-rack-btn\");\n       const drawToolbar = document.getElementById(\"draw-toolbar\");\n       const topologyToolbar = document.getElementById(\"topology-toolbar\");\n       const bulkToolbar = document.getElementById(\"bulk-toolbar\");\n       if (addNodeBtn) addNodeBtn.style.display = viewOnlyMode ? \"none\" : \"\";\n       if (addRackBtn) addRackBtn.style.display = viewOnlyMode ? \"none\" : \"\";\n       if (viewOnlyMode) {\n        if (drawToolbar) drawToolbar.style.setProperty('display', 'none', 'important');\n        if (topologyToolbar) topologyToolbar.style.setProperty('display', 'none', 'important');\n        if (bulkToolbar) bulkToolbar.style.display = \"none\";\n        const bulkToolbarMobile = document.getElementById(\"bulk-toolbar-mobile\");\n        if (bulkToolbarMobile) bulkToolbarMobile.style.display = \"none\";\n       }\n       const bulkToolbarMobile = document.getElementById(\"bulk-toolbar-mobile\");\n       if (bulkToolbarMobile) bulkToolbarMobile.style.display = viewOnlyMode ? \"none\" : \"\";\n       [\"draw-toolbar-mini\", \"topology-toolbar-mini\"].forEach(id => {\n        const el = document.getElementById(id);\n        if (el) el.style.display = viewOnlyMode ? \"none\" : \"\";\n       });\n       document.body.classList.toggle(\"view-only-mode\", viewOnlyMode);\n      }\n      (async function awakeTheImmortal() {\n       let initialState = {};\n       let decryptionCancelled = false;\n       const stateEl = document.getElementById(\"topology-state\");\n       if (stateEl && stateEl.textContent.trim()) {\n        try {\n         let stateText = stateEl.textContent.trim();\n         if (isEncrypted(stateText)) {\n          let decrypted = false;\n          let attempts = 0;\n          const maxAttempts = 3;\n          while (!decrypted && attempts < maxAttempts) {\n           const password = prompt(\"This file is encrypted. Enter password to decrypt:\\n(Attempt \" + (attempts + 1) + \" of \" + maxAttempts + \")\");\n           if (!password) {\n            alert(\"Decryption cancelled. The file will not be loaded.\");\n            decryptionCancelled = true;\n            break;\n           }\n           try {\n            stateText = await decryptData(stateText, password);\n            decrypted = true;\n           } catch (e) {\n            attempts++;\n            if (attempts < maxAttempts) {\n             alert(\"Incorrect password. Please try again.\");\n            } else {\n             alert(\"Maximum attempts reached. The file will not be loaded.\");\n             decryptionCancelled = true;\n            }\n           }\n          }\n          if (!decrypted) {\n           stateText = \"{}\";\n          }\n         }\n         initialState = JSON.parse(stateText);\n        } catch (e) {\n         console.error(\"Failed to load state:\", e);\n         initialState = {};\n        }\n       }\n       if (decryptionCancelled) {\n        NODE_DATA = {};\n        EDGE_DATA = {\n         list: []\n        };\n        RECT_DATA = { list: [] };\n        TEXT_DATA = { list: [] };\n        EDGE_LEGEND = {};\n        savedPositions = {};\n        savedSizes = {};\n        savedStyles = {};\n       } else {\n        NODE_DATA = initialState.nodeData ? initialState.nodeData : BASE_NODE_DATA;\n        EDGE_DATA = initialState.edgeData ? initialState.edgeData : {\n         list: [],\n        };\n        RECT_DATA = initialState.rectData ? initialState.rectData : { list: [] };\n        TEXT_DATA = initialState.textData ? initialState.textData : { list: [] };\n        EDGE_LEGEND = initialState.edgeLegend || {};\n        savedPositions = initialState.nodePositions || {};\n        savedSizes = initialState.nodeSizes || {};\n        savedStyles = initialState.nodeStyles || {};\n       }\n       if (initialState.page) {\n        PAGE_STATE = Object.assign({}, DEFAULT_PAGE_STATE, initialState.page);\n       }\n       if (initialState.canvas) {\n        canvasState.zoom = initialState.canvas.zoom || 1;\n        canvasState.panX = initialState.canvas.panX || 0;\n        canvasState.panY = initialState.canvas.panY || 0;\n       }\n       if (initialState.savedTopologyView) {\n        savedTopologyView = initialState.savedTopologyView;\n       }\n       if (initialState.documentTabs) {\n        documentTabs = initialState.documentTabs;\n        currentTabIndex = initialState.currentTabIndex || 0;\n        const currentTab = documentTabs[currentTabIndex];\n        if (currentTab) {\n          NODE_DATA = currentTab.nodes || NODE_DATA;\n          EDGE_DATA = currentTab.edges || EDGE_DATA;\n          savedPositions = currentTab.positions || savedPositions;\n          savedSizes = currentTab.sizes || savedSizes;\n          savedStyles = currentTab.styles || savedStyles;\n          EDGE_LEGEND = currentTab.legend || EDGE_LEGEND;\n          RECT_DATA = currentTab.rects || RECT_DATA;\n          TEXT_DATA = currentTab.texts || TEXT_DATA;\n      if (currentTab.pageState) PAGE_STATE = Object.assign({}, DEFAULT_PAGE_STATE, currentTab.pageState);\n        }\n       }\n       if (initialState.encryptedSections) {\n        encryptedSections = initialState.encryptedSections;\n       }\n       wieldThePower();\n       forgeTheTopology();\n       updateViewBox();\n       MobileManager.applyAll();\n       const initialNodes = Object.keys(NODE_DATA);\n       if (initialNodes.length > 0) {\n        claimTheImmortal(initialNodes.includes(\"host\") ? \"host\" : initialNodes[0]);\n       } else {\n        document.getElementById(\"node-panel\").style.display = \"none\";\n        document.getElementById(\"topology-toolbar\").style.display = \"none\";\n       }\n       if (Object.keys(NODE_DATA).length === 0 && EDGE_DATA.list.length === 0) {\n        checkForAutosaveRecovery();\n       }\n       startAutosave();\n      })();\n      let resizeDebounceTimer = null;\n      window.addEventListener(\"resize\", () => {\n        clearTimeout(resizeDebounceTimer);\n        resizeDebounceTimer = setTimeout(() => {\n          MobileManager.applyAll();\n          if (typeof updateDrawToolbarVisibility === 'function') updateDrawToolbarVisibility();\n          if (typeof updateTopologyToolbarVisibility === 'function') updateTopologyToolbarVisibility();\n        }, 100);\n      });\n      function getBreakpointKey() {\n       const w = window.innerWidth;\n       if (w <= 380) return \"fold\";\n       if (w <= 768) return \"mobile\";\n       if (w <= 1024) return \"tablet\";\n       return \"desktop\";\n      }\n      function resolveStylesEntry(styleEntry) {\n       if (!styleEntry) return {};\n       if (styleEntry.circleColor || styleEntry.titleColor || styleEntry.titleFont || styleEntry.titleSize || styleEntry.subColor || styleEntry.subFont || styleEntry.subSize) {\n        return styleEntry;\n       }\n       const bp = getBreakpointKey();\n       const base = styleEntry.all || {};\n       const bpStyles = styleEntry[bp] || {};\n       return Object.assign({}, base, bpStyles);\n      }\n      function resolveStylesForNode(id) {\n       const styleEntry = savedStyles[id];\n       if (!styleEntry) return {};\n       return resolveStylesEntry(styleEntry);\n      }\n      function ensureStyleEntry(id) {\n       if (!savedStyles[id]) savedStyles[id] = {};\n       const entry = savedStyles[id];\n       const flatProps = [\"circleColor\", \"circleBorder\", \"titleColor\", \"titleFont\", \"titleSize\", \"subColor\", \"subFont\", \"subSize\", ];\n       const isFlat = flatProps.some((p) => Object.prototype.hasOwnProperty.call(entry, p));\n       if (isFlat) {\n        const all = entry.all || {};\n        flatProps.forEach((p) => {\n         if (entry[p] !== undefined) {\n          all[p] = entry[p];\n          delete entry[p];\n         }\n        });\n        entry.all = all;\n       }\n       return entry;\n      }\n      function getDefaultSize() {\n       if (window.innerWidth <= 380) return 120;\n       if (window.innerWidth <= 768) return 140;\n       if (window.innerWidth <= 1024) return 70;\n       return 55;\n      }\n      function createShapeElement(shape, size) {\n       const ns = \"http://www.w3.org/2000/svg\";\n       if (shape === \"circle\") {\n        const c = document.createElementNS(ns, \"circle\");\n        c.setAttribute(\"r\", size);\n        return c;\n       }\n       if (shape === \"square\") {\n        const r = document.createElementNS(ns, \"rect\");\n        const s = size * 2;\n        r.setAttribute(\"x\", -size);\n        r.setAttribute(\"y\", -size);\n        r.setAttribute(\"width\", s);\n        r.setAttribute(\"height\", s);\n        r.setAttribute(\"rx\", 4);\n        return r;\n       }\n       if (shape === \"rectangle\") {\n        const r = document.createElementNS(ns, \"rect\");\n        r.setAttribute(\"x\", -size * 1.4);\n        r.setAttribute(\"y\", -size * 0.8);\n        r.setAttribute(\"width\", size * 2.8);\n        r.setAttribute(\"height\", size * 1.6);\n        r.setAttribute(\"rx\", 6);\n        return r;\n       }\n       if (shape === \"triangle\") {\n        const p = document.createElementNS(ns, \"polygon\");\n        const h = size * 1.8;\n        const w = size * 2;\n        p.setAttribute(\"points\", `0,${-h} ${w / 2},${h / 2} ${-w / 2},${h / 2}`);\n        return p;\n       }\n       if (shape === \"hexagon\") {\n        const p = document.createElementNS(ns, \"polygon\");\n        const s = size;\n        const pts = [\n         [0, -s],\n         [s * 0.86, -s * 0.5],\n         [s * 0.86, s * 0.5],\n         [0, s],\n         [-s * 0.86, s * 0.5],\n         [-s * 0.86, -s * 0.5],\n        ].map((pt) => pt.join(\",\")).join(\" \");\n        p.setAttribute(\"points\", pts);\n        return p;\n       }\n       if (shape === \"stop-sign\") {\n        const p = document.createElementNS(ns, \"polygon\");\n        const r = size;\n        const ptsArr = [];\n        for (let i = 0; i < 8; i++) {\n         const a = (Math.PI / 4) * i + Math.PI / 8;\n         ptsArr.push([Math.cos(a) * r, Math.sin(a) * r]);\n        }\n        const pts = ptsArr.map((pt) => pt.join(\",\")).join(\" \");\n        p.setAttribute(\"points\", pts);\n        return p;\n       }\n       if (shape === \"star\") {\n        const p = document.createElementNS(ns, \"polygon\");\n        const outer = size;\n        const inner = size * 0.45;\n        let pts = \"\";\n        for (let i = 0; i < 10; i++) {\n         const a = (Math.PI / 5) * i - Math.PI / 2;\n         const r = i % 2 === 0 ? outer : inner;\n         pts += `${Math.cos(a) * r},${Math.sin(a) * r} `;\n        }\n        p.setAttribute(\"points\", pts.trim());\n        return p;\n       }\n       if (shape === \"diamond\") {\n        const p = document.createElementNS(ns, \"polygon\");\n        const s = size;\n        p.setAttribute(\"points\", `0,${-s} ${s},0 0,${s} ${-s},0`);\n        return p;\n       }\n       if (shape === \"server\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 1.2);\n        body.setAttribute(\"y\", -size * 0.6);\n        body.setAttribute(\"width\", size * 2.4);\n        body.setAttribute(\"height\", size * 1.2);\n        body.setAttribute(\"rx\", 4);\n        g.appendChild(body);\n        for (let i = 0; i < 3; i++) {\n         const line = document.createElementNS(ns, \"line\");\n         line.setAttribute(\"x1\", -size * 0.9 + i * size * 0.5);\n         line.setAttribute(\"y1\", -size * 0.3);\n         line.setAttribute(\"x2\", -size * 0.9 + i * size * 0.5);\n         line.setAttribute(\"y2\", size * 0.3);\n         line.style.stroke = \"currentColor\";\n         line.style.strokeWidth = \"2\";\n         line.style.opacity = \"0.5\";\n         g.appendChild(line);\n        }\n        const led = document.createElementNS(ns, \"circle\");\n        led.setAttribute(\"cx\", size * 0.9);\n        led.setAttribute(\"cy\", 0);\n        led.setAttribute(\"r\", size * 0.1);\n        led.style.fill = \"#4ade80\";\n        g.appendChild(led);\n        return g;\n       }\n       if (shape === \"pc\" || shape === \"desktop\") {\n        const g = document.createElementNS(ns, \"g\");\n        const monitor = document.createElementNS(ns, \"rect\");\n        monitor.setAttribute(\"x\", -size * 0.9);\n        monitor.setAttribute(\"y\", -size * 0.8);\n        monitor.setAttribute(\"width\", size * 1.8);\n        monitor.setAttribute(\"height\", size * 1.2);\n        monitor.setAttribute(\"rx\", 4);\n        g.appendChild(monitor);\n        const screen = document.createElementNS(ns, \"rect\");\n        screen.setAttribute(\"x\", -size * 0.75);\n        screen.setAttribute(\"y\", -size * 0.65);\n        screen.setAttribute(\"width\", size * 1.5);\n        screen.setAttribute(\"height\", size * 0.9);\n        screen.setAttribute(\"rx\", 2);\n        screen.style.fill = \"#1e293b\";\n        g.appendChild(screen);\n        const neck = document.createElementNS(ns, \"rect\");\n        neck.setAttribute(\"x\", -size * 0.15);\n        neck.setAttribute(\"y\", size * 0.4);\n        neck.setAttribute(\"width\", size * 0.3);\n        neck.setAttribute(\"height\", size * 0.3);\n        g.appendChild(neck);\n        const base = document.createElementNS(ns, \"rect\");\n        base.setAttribute(\"x\", -size * 0.5);\n        base.setAttribute(\"y\", size * 0.7);\n        base.setAttribute(\"width\", size * 1);\n        base.setAttribute(\"height\", size * 0.15);\n        base.setAttribute(\"rx\", 2);\n        g.appendChild(base);\n        return g;\n       }\n       if (shape === \"laptop\") {\n        const g = document.createElementNS(ns, \"g\");\n        const screen = document.createElementNS(ns, \"rect\");\n        screen.setAttribute(\"x\", -size * 0.8);\n        screen.setAttribute(\"y\", -size * 0.9);\n        screen.setAttribute(\"width\", size * 1.6);\n        screen.setAttribute(\"height\", size * 1.1);\n        screen.setAttribute(\"rx\", 4);\n        g.appendChild(screen);\n        const base = document.createElementNS(ns, \"rect\");\n        base.setAttribute(\"x\", -size * 0.9);\n        base.setAttribute(\"y\", size * 0.25);\n        base.setAttribute(\"width\", size * 1.8);\n        base.setAttribute(\"height\", size * 0.6);\n        base.setAttribute(\"rx\", 4);\n        g.appendChild(base);\n        const pad = document.createElementNS(ns, \"rect\");\n        pad.setAttribute(\"x\", -size * 0.25);\n        pad.setAttribute(\"y\", size * 0.45);\n        pad.setAttribute(\"width\", size * 0.5);\n        pad.setAttribute(\"height\", size * 0.25);\n        pad.setAttribute(\"rx\", 2);\n        pad.style.fill = \"#1e293b\";\n        g.appendChild(pad);\n        return g;\n       }\n       if (shape === \"phone\" || shape === \"mobile\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 0.45);\n        body.setAttribute(\"y\", -size);\n        body.setAttribute(\"width\", size * 0.9);\n        body.setAttribute(\"height\", size * 2);\n        body.setAttribute(\"rx\", 8);\n        g.appendChild(body);\n        const screen = document.createElementNS(ns, \"rect\");\n        screen.setAttribute(\"x\", -size * 0.38);\n        screen.setAttribute(\"y\", -size * 0.85);\n        screen.setAttribute(\"width\", size * 0.76);\n        screen.setAttribute(\"height\", size * 1.6);\n        screen.setAttribute(\"rx\", 4);\n        screen.style.fill = \"#1e293b\";\n        g.appendChild(screen);\n        const btn = document.createElementNS(ns, \"rect\");\n        btn.setAttribute(\"x\", -size * 0.15);\n        btn.setAttribute(\"y\", size * 0.82);\n        btn.setAttribute(\"width\", size * 0.3);\n        btn.setAttribute(\"height\", size * 0.06);\n        btn.setAttribute(\"rx\", 2);\n        btn.style.fill = \"#475569\";\n        g.appendChild(btn);\n        return g;\n       }\n       if (shape === \"router\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 1.1);\n        body.setAttribute(\"y\", -size * 0.3);\n        body.setAttribute(\"width\", size * 2.2);\n        body.setAttribute(\"height\", size * 0.8);\n        body.setAttribute(\"rx\", 6);\n        g.appendChild(body);\n        for (let i = -1; i <= 1; i++) {\n         const ant = document.createElementNS(ns, \"rect\");\n         ant.setAttribute(\"x\", i * size * 0.6 - size * 0.05);\n         ant.setAttribute(\"y\", -size * 0.9);\n         ant.setAttribute(\"width\", size * 0.1);\n         ant.setAttribute(\"height\", size * 0.6);\n         ant.setAttribute(\"rx\", 2);\n         g.appendChild(ant);\n         const tip = document.createElementNS(ns, \"circle\");\n         tip.setAttribute(\"cx\", i * size * 0.6);\n         tip.setAttribute(\"cy\", -size * 0.95);\n         tip.setAttribute(\"r\", size * 0.08);\n         g.appendChild(tip);\n        }\n        for (let i = 0; i < 4; i++) {\n         const led = document.createElementNS(ns, \"circle\");\n         led.setAttribute(\"cx\", -size * 0.7 + i * size * 0.35);\n         led.setAttribute(\"cy\", size * 0.1);\n         led.setAttribute(\"r\", size * 0.06);\n         led.style.fill = i < 2 ? \"#4ade80\" : \"#facc15\";\n         g.appendChild(led);\n        }\n        return g;\n       }\n       if (shape === \"switch\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 1.4);\n        body.setAttribute(\"y\", -size * 0.4);\n        body.setAttribute(\"width\", size * 2.8);\n        body.setAttribute(\"height\", size * 0.8);\n        body.setAttribute(\"rx\", 4);\n        g.appendChild(body);\n        for (let i = 0; i < 8; i++) {\n         const port = document.createElementNS(ns, \"rect\");\n         port.setAttribute(\"x\", -size * 1.2 + i * size * 0.32);\n         port.setAttribute(\"y\", -size * 0.15);\n         port.setAttribute(\"width\", size * 0.22);\n         port.setAttribute(\"height\", size * 0.3);\n         port.setAttribute(\"rx\", 1);\n         port.style.fill = \"#1e293b\";\n         g.appendChild(port);\n        }\n        return g;\n       }\n       if (shape === \"firewall\") {\n        const g = document.createElementNS(ns, \"g\");\n        const wall = document.createElementNS(ns, \"rect\");\n        wall.setAttribute(\"x\", -size);\n        wall.setAttribute(\"y\", -size * 0.8);\n        wall.setAttribute(\"width\", size * 2);\n        wall.setAttribute(\"height\", size * 1.6);\n        wall.setAttribute(\"rx\", 4);\n        g.appendChild(wall);\n        for (let row = 0; row < 3; row++) {\n         const line = document.createElementNS(ns, \"line\");\n         line.setAttribute(\"x1\", -size * 0.85);\n         line.setAttribute(\"y1\", -size * 0.5 + row * size * 0.45);\n         line.setAttribute(\"x2\", size * 0.85);\n         line.setAttribute(\"y2\", -size * 0.5 + row * size * 0.45);\n         line.style.stroke = \"#475569\";\n         line.style.strokeWidth = \"2\";\n         g.appendChild(line);\n        }\n        for (let row = 0; row < 4; row++) {\n         const offset = row % 2 === 0 ? 0 : size * 0.35;\n         for (let col = 0; col < 3; col++) {\n          const line = document.createElementNS(ns, \"line\");\n          const x = -size * 0.5 + col * size * 0.7 + offset;\n          if (x > -size * 0.85 && x < size * 0.85) {\n           line.setAttribute(\"x1\", x);\n           line.setAttribute(\"y1\", -size * 0.8 + row * size * 0.45);\n           line.setAttribute(\"x2\", x);\n           line.setAttribute(\"y2\", -size * 0.8 + row * size * 0.45 + size * 0.45);\n           line.style.stroke = \"#475569\";\n           line.style.strokeWidth = \"2\";\n           g.appendChild(line);\n          }\n         }\n        }\n        return g;\n       }\n       if (shape === \"cloud\") {\n        const p = document.createElementNS(ns, \"path\");\n        const s = size;\n        p.setAttribute(\"d\", `\n             M ${-s * 0.8} ${s * 0.2}\n             Q ${-s * 1.1} ${s * 0.2} ${-s * 1.1} ${-s * 0.1}\n             Q ${-s * 1.1} ${-s * 0.5} ${-s * 0.7} ${-s * 0.5}\n             Q ${-s * 0.7} ${-s * 0.9} ${-s * 0.2} ${-s * 0.9}\n             Q ${s * 0.1} ${-s * 1.1} ${s * 0.5} ${-s * 0.8}\n             Q ${s * 1} ${-s * 0.8} ${s * 1.1} ${-s * 0.3}\n             Q ${s * 1.3} ${-s * 0.1} ${s * 1.1} ${s * 0.2}\n             Q ${s * 1.1} ${s * 0.5} ${s * 0.7} ${s * 0.5}\n             L ${-s * 0.5} ${s * 0.5}\n             Q ${-s * 0.9} ${s * 0.5} ${-s * 0.9} ${s * 0.2}\n             Z\n            `);\n        return p;\n       }\n       if (shape === \"database\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 0.7);\n        body.setAttribute(\"y\", -size * 0.6);\n        body.setAttribute(\"width\", size * 1.4);\n        body.setAttribute(\"height\", size * 1.4);\n        g.appendChild(body);\n        const top = document.createElementNS(ns, \"ellipse\");\n        top.setAttribute(\"cx\", 0);\n        top.setAttribute(\"cy\", -size * 0.6);\n        top.setAttribute(\"rx\", size * 0.7);\n        top.setAttribute(\"ry\", size * 0.25);\n        g.appendChild(top);\n        const bottom = document.createElementNS(ns, \"ellipse\");\n        bottom.setAttribute(\"cx\", 0);\n        bottom.setAttribute(\"cy\", size * 0.8);\n        bottom.setAttribute(\"rx\", size * 0.7);\n        bottom.setAttribute(\"ry\", size * 0.25);\n        g.appendChild(bottom);\n        const mid1 = document.createElementNS(ns, \"ellipse\");\n        mid1.setAttribute(\"cx\", 0);\n        mid1.setAttribute(\"cy\", -size * 0.15);\n        mid1.setAttribute(\"rx\", size * 0.7);\n        mid1.setAttribute(\"ry\", size * 0.2);\n        mid1.style.fill = \"none\";\n        mid1.style.stroke = \"#475569\";\n        mid1.style.strokeWidth = \"2\";\n        g.appendChild(mid1);\n        const mid2 = document.createElementNS(ns, \"ellipse\");\n        mid2.setAttribute(\"cx\", 0);\n        mid2.setAttribute(\"cy\", size * 0.35);\n        mid2.setAttribute(\"rx\", size * 0.7);\n        mid2.setAttribute(\"ry\", size * 0.2);\n        mid2.style.fill = \"none\";\n        mid2.style.stroke = \"#475569\";\n        mid2.style.strokeWidth = \"2\";\n        g.appendChild(mid2);\n        return g;\n       }\n       if (shape === \"printer\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size);\n        body.setAttribute(\"y\", -size * 0.4);\n        body.setAttribute(\"width\", size * 2);\n        body.setAttribute(\"height\", size * 0.9);\n        body.setAttribute(\"rx\", 4);\n        g.appendChild(body);\n        const trayTop = document.createElementNS(ns, \"rect\");\n        trayTop.setAttribute(\"x\", -size * 0.7);\n        trayTop.setAttribute(\"y\", -size * 0.8);\n        trayTop.setAttribute(\"width\", size * 1.4);\n        trayTop.setAttribute(\"height\", size * 0.4);\n        trayTop.setAttribute(\"rx\", 2);\n        trayTop.style.fill = \"#1e293b\";\n        g.appendChild(trayTop);\n        const trayOut = document.createElementNS(ns, \"rect\");\n        trayOut.setAttribute(\"x\", -size * 0.6);\n        trayOut.setAttribute(\"y\", size * 0.5);\n        trayOut.setAttribute(\"width\", size * 1.2);\n        trayOut.setAttribute(\"height\", size * 0.35);\n        trayOut.setAttribute(\"rx\", 2);\n        g.appendChild(trayOut);\n        const paper = document.createElementNS(ns, \"rect\");\n        paper.setAttribute(\"x\", -size * 0.5);\n        paper.setAttribute(\"y\", size * 0.3);\n        paper.setAttribute(\"width\", size * 1);\n        paper.setAttribute(\"height\", size * 0.5);\n        paper.style.fill = \"#e2e8f0\";\n        g.appendChild(paper);\n        return g;\n       }\n       if (shape === \"access-point\" || shape === \"wifi\") {\n        const g = document.createElementNS(ns, \"g\");\n        const base = document.createElementNS(ns, \"rect\");\n        base.setAttribute(\"x\", -size * 0.3);\n        base.setAttribute(\"y\", size * 0.2);\n        base.setAttribute(\"width\", size * 0.6);\n        base.setAttribute(\"height\", size * 0.3);\n        base.setAttribute(\"rx\", 3);\n        g.appendChild(base);\n        for (let i = 1; i <= 3; i++) {\n         const arc = document.createElementNS(ns, \"path\");\n         const r = size * 0.3 * i;\n         arc.setAttribute(\"d\", `M ${-r * 0.7} ${size * 0.1 - r * 0.5} A ${r} ${r} 0 0 1 ${r * 0.7} ${size * 0.1 - r * 0.5}`);\n         arc.style.fill = \"none\";\n         arc.style.stroke = \"currentColor\";\n         arc.style.strokeWidth = \"2\";\n         arc.style.opacity = 1 - (i * 0.2);\n         g.appendChild(arc);\n        }\n        return g;\n       }\n       if (shape === \"load-balancer\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size);\n        body.setAttribute(\"y\", -size * 0.5);\n        body.setAttribute(\"width\", size * 2);\n        body.setAttribute(\"height\", size);\n        body.setAttribute(\"rx\", 6);\n        g.appendChild(body);\n        const bar = document.createElementNS(ns, \"line\");\n        bar.setAttribute(\"x1\", -size * 0.6);\n        bar.setAttribute(\"y1\", 0);\n        bar.setAttribute(\"x2\", size * 0.6);\n        bar.setAttribute(\"y2\", 0);\n        bar.style.stroke = \"#4ade80\";\n        bar.style.strokeWidth = \"3\";\n        g.appendChild(bar);\n        [-1, 1].forEach(dir => {\n         const arrow = document.createElementNS(ns, \"path\");\n         arrow.setAttribute(\"d\", `M ${dir * size * 0.3} ${-size * 0.2} L ${dir * size * 0.6} 0 L ${dir * size * 0.3} ${size * 0.2}`);\n         arrow.style.fill = \"none\";\n         arrow.style.stroke = \"#4ade80\";\n         arrow.style.strokeWidth = \"2\";\n         g.appendChild(arrow);\n        });\n        return g;\n       }\n       if (shape === \"nas\" || shape === \"storage\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 0.8);\n        body.setAttribute(\"y\", -size * 0.9);\n        body.setAttribute(\"width\", size * 1.6);\n        body.setAttribute(\"height\", size * 1.8);\n        body.setAttribute(\"rx\", 4);\n        g.appendChild(body);\n        for (let i = 0; i < 4; i++) {\n         const bay = document.createElementNS(ns, \"rect\");\n         bay.setAttribute(\"x\", -size * 0.6);\n         bay.setAttribute(\"y\", -size * 0.7 + i * size * 0.4);\n         bay.setAttribute(\"width\", size * 1.2);\n         bay.setAttribute(\"height\", size * 0.3);\n         bay.setAttribute(\"rx\", 2);\n         bay.style.fill = \"#1e293b\";\n         g.appendChild(bay);\n        }\n        return g;\n       }\n       if (shape === \"gateway\" || shape === \"modem\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 0.5);\n        body.setAttribute(\"y\", -size * 0.9);\n        body.setAttribute(\"width\", size);\n        body.setAttribute(\"height\", size * 1.8);\n        body.setAttribute(\"rx\", 4);\n        g.appendChild(body);\n        for (let i = 0; i < 4; i++) {\n         const led = document.createElementNS(ns, \"circle\");\n         led.setAttribute(\"cx\", 0);\n         led.setAttribute(\"cy\", -size * 0.6 + i * size * 0.35);\n         led.setAttribute(\"r\", size * 0.08);\n         led.style.fill = [\"#4ade80\", \"#4ade80\", \"#facc15\", \"#60a5fa\"][i];\n         g.appendChild(led);\n        }\n        return g;\n       }\n       if (shape === \"vpn\" || shape === \"tunnel\") {\n        const g = document.createElementNS(ns, \"g\");\n        const outer = document.createElementNS(ns, \"ellipse\");\n        outer.setAttribute(\"cx\", 0);\n        outer.setAttribute(\"cy\", 0);\n        outer.setAttribute(\"rx\", size);\n        outer.setAttribute(\"ry\", size * 0.6);\n        g.appendChild(outer);\n        const inner = document.createElementNS(ns, \"ellipse\");\n        inner.setAttribute(\"cx\", 0);\n        inner.setAttribute(\"cy\", 0);\n        inner.setAttribute(\"rx\", size * 0.5);\n        inner.setAttribute(\"ry\", size * 0.3);\n        inner.style.fill = \"#1e293b\";\n        g.appendChild(inner);\n        const lock = document.createElementNS(ns, \"rect\");\n        lock.setAttribute(\"x\", -size * 0.15);\n        lock.setAttribute(\"y\", -size * 0.1);\n        lock.setAttribute(\"width\", size * 0.3);\n        lock.setAttribute(\"height\", size * 0.25);\n        lock.setAttribute(\"rx\", 2);\n        lock.style.fill = \"#4ade80\";\n        g.appendChild(lock);\n        return g;\n       }\n       if (shape === \"container\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size);\n        body.setAttribute(\"y\", -size * 0.5);\n        body.setAttribute(\"width\", size * 2);\n        body.setAttribute(\"height\", size);\n        body.setAttribute(\"rx\", 4);\n        g.appendChild(body);\n        for (let i = 0; i < 3; i++) {\n         const block = document.createElementNS(ns, \"rect\");\n         block.setAttribute(\"x\", -size * 0.8 + i * size * 0.55);\n         block.setAttribute(\"y\", -size * 0.3);\n         block.setAttribute(\"width\", size * 0.45);\n         block.setAttribute(\"height\", size * 0.6);\n         block.setAttribute(\"rx\", 2);\n         block.style.fill = \"#1e293b\";\n         g.appendChild(block);\n        }\n        return g;\n       }\n       if (shape === \"vm\" || shape === \"virtual\") {\n        const g = document.createElementNS(ns, \"g\");\n        const shadow = document.createElementNS(ns, \"rect\");\n        shadow.setAttribute(\"x\", -size * 0.85 + 4);\n        shadow.setAttribute(\"y\", -size * 0.65 + 4);\n        shadow.setAttribute(\"width\", size * 1.7);\n        shadow.setAttribute(\"height\", size * 1.3);\n        shadow.setAttribute(\"rx\", 4);\n        shadow.style.opacity = \"0.3\";\n        g.appendChild(shadow);\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 0.85);\n        body.setAttribute(\"y\", -size * 0.65);\n        body.setAttribute(\"width\", size * 1.7);\n        body.setAttribute(\"height\", size * 1.3);\n        body.setAttribute(\"rx\", 4);\n        g.appendChild(body);\n        const screen = document.createElementNS(ns, \"rect\");\n        screen.setAttribute(\"x\", -size * 0.65);\n        screen.setAttribute(\"y\", -size * 0.45);\n        screen.setAttribute(\"width\", size * 1.3);\n        screen.setAttribute(\"height\", size * 0.7);\n        screen.setAttribute(\"rx\", 2);\n        screen.style.fill = \"#1e293b\";\n        g.appendChild(screen);\n        return g;\n       }\n       if (shape === \"kubernetes\" || shape === \"k8s\") {\n        const g = document.createElementNS(ns, \"g\");\n        const outer = document.createElementNS(ns, \"circle\");\n        outer.setAttribute(\"cx\", 0);\n        outer.setAttribute(\"cy\", 0);\n        outer.setAttribute(\"r\", size);\n        g.appendChild(outer);\n        const inner = document.createElementNS(ns, \"circle\");\n        inner.setAttribute(\"cx\", 0);\n        inner.setAttribute(\"cy\", 0);\n        inner.setAttribute(\"r\", size * 0.4);\n        inner.style.fill = \"#1e293b\";\n        g.appendChild(inner);\n        for (let i = 0; i < 7; i++) {\n         const angle = (Math.PI * 2 / 7) * i - Math.PI / 2;\n         const spoke = document.createElementNS(ns, \"line\");\n         spoke.setAttribute(\"x1\", Math.cos(angle) * size * 0.4);\n         spoke.setAttribute(\"y1\", Math.sin(angle) * size * 0.4);\n         spoke.setAttribute(\"x2\", Math.cos(angle) * size * 0.85);\n         spoke.setAttribute(\"y2\", Math.sin(angle) * size * 0.85);\n         spoke.style.stroke = \"#326ce5\";\n         spoke.style.strokeWidth = \"3\";\n         g.appendChild(spoke);\n        }\n        return g;\n       }\n       if (shape === \"shield\" || shape === \"security\") {\n        const p = document.createElementNS(ns, \"path\");\n        const s = size;\n        p.setAttribute(\"d\", `M 0 ${-s} L ${s * 0.85} ${-s * 0.5} L ${s * 0.85} ${s * 0.2} Q ${s * 0.7} ${s * 0.9} 0 ${s} Q ${-s * 0.7} ${s * 0.9} ${-s * 0.85} ${s * 0.2} L ${-s * 0.85} ${-s * 0.5} Z`);\n        return p;\n       }\n       if (shape === \"camera\" || shape === \"cctv\") {\n        const g = document.createElementNS(ns, \"g\");\n        const mount = document.createElementNS(ns, \"rect\");\n        mount.setAttribute(\"x\", -size * 1.1);\n        mount.setAttribute(\"y\", -size * 0.5);\n        mount.setAttribute(\"width\", size * 0.25);\n        mount.setAttribute(\"height\", size * 0.6);\n        mount.setAttribute(\"rx\", 2);\n        g.appendChild(mount);\n        const arm = document.createElementNS(ns, \"rect\");\n        arm.setAttribute(\"x\", -size * 0.9);\n        arm.setAttribute(\"y\", -size * 0.15);\n        arm.setAttribute(\"width\", size * 0.5);\n        arm.setAttribute(\"height\", size * 0.15);\n        g.appendChild(arm);\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 0.45);\n        body.setAttribute(\"y\", -size * 0.35);\n        body.setAttribute(\"width\", size * 1.1);\n        body.setAttribute(\"height\", size * 0.7);\n        body.setAttribute(\"rx\", 4);\n        g.appendChild(body);\n        const lensHousing = document.createElementNS(ns, \"circle\");\n        lensHousing.setAttribute(\"cx\", size * 0.85);\n        lensHousing.setAttribute(\"cy\", 0);\n        lensHousing.setAttribute(\"r\", size * 0.4);\n        g.appendChild(lensHousing);\n        const lensOuter = document.createElementNS(ns, \"circle\");\n        lensOuter.setAttribute(\"cx\", size * 0.85);\n        lensOuter.setAttribute(\"cy\", 0);\n        lensOuter.setAttribute(\"r\", size * 0.28);\n        lensOuter.style.fill = \"#1e293b\";\n        g.appendChild(lensOuter);\n        const lensInner = document.createElementNS(ns, \"circle\");\n        lensInner.setAttribute(\"cx\", size * 0.85);\n        lensInner.setAttribute(\"cy\", 0);\n        lensInner.setAttribute(\"r\", size * 0.15);\n        lensInner.style.fill = \"#3b82f6\";\n        g.appendChild(lensInner);\n        const reflection = document.createElementNS(ns, \"circle\");\n        reflection.setAttribute(\"cx\", size * 0.8);\n        reflection.setAttribute(\"cy\", -size * 0.05);\n        reflection.setAttribute(\"r\", size * 0.05);\n        reflection.style.fill = \"#93c5fd\";\n        g.appendChild(reflection);\n        const led = document.createElementNS(ns, \"circle\");\n        led.setAttribute(\"cx\", -size * 0.2);\n        led.setAttribute(\"cy\", -size * 0.15);\n        led.setAttribute(\"r\", size * 0.06);\n        led.style.fill = \"#ef4444\";\n        g.appendChild(led);\n        return g;\n       }\n       if (shape === \"monitor\" || shape === \"dashboard\") {\n        const g = document.createElementNS(ns, \"g\");\n        const screen = document.createElementNS(ns, \"rect\");\n        screen.setAttribute(\"x\", -size);\n        screen.setAttribute(\"y\", -size * 0.7);\n        screen.setAttribute(\"width\", size * 2);\n        screen.setAttribute(\"height\", size * 1.2);\n        screen.setAttribute(\"rx\", 4);\n        g.appendChild(screen);\n        const graph = document.createElementNS(ns, \"polyline\");\n        graph.setAttribute(\"points\", `${-size * 0.8},${size * 0.2} ${-size * 0.3},${-size * 0.2} ${size * 0.2},${size * 0.1} ${size * 0.7},${-size * 0.3}`);\n        graph.style.fill = \"none\";\n        graph.style.stroke = \"#4ade80\";\n        graph.style.strokeWidth = \"3\";\n        g.appendChild(graph);\n        return g;\n       }\n       if (shape === \"docker\" || shape === \"whale\") {\n        const g = document.createElementNS(ns, \"g\");\n        const s = size;\n        const body = document.createElementNS(ns, \"path\");\n        body.setAttribute(\"d\", `\n         M ${-s * 0.9} ${s * 0.2}\n         Q ${-s * 1.1} ${s * 0.5} ${-s * 0.7} ${s * 0.7}\n         Q ${-s * 0.3} ${s * 0.85} ${s * 0.3} ${s * 0.75}\n         Q ${s * 0.8} ${s * 0.6} ${s * 1.0} ${s * 0.3}\n         Q ${s * 1.1} ${s * 0.1} ${s * 1.0} ${-s * 0.1}\n         L ${s * 0.85} ${-s * 0.1}\n         Q ${s * 0.7} ${-s * 0.15} ${s * 0.4} ${-s * 0.2}\n         L ${-s * 0.5} ${-s * 0.2}\n         Q ${-s * 0.8} ${-s * 0.1} ${-s * 0.9} ${s * 0.2}\n         Z\n        `);\n        body.style.fill = \"#0db7ed\";\n        g.appendChild(body);\n        const tail = document.createElementNS(ns, \"path\");\n        tail.setAttribute(\"d\", `\n         M ${-s * 0.85} ${s * 0.1}\n         Q ${-s * 1.2} ${-s * 0.2} ${-s * 1.0} ${-s * 0.55}\n         Q ${-s * 0.95} ${-s * 0.35} ${-s * 0.8} ${-s * 0.15}\n         Z\n        `);\n        tail.style.fill = \"#0db7ed\";\n        g.appendChild(tail);\n        const belly = document.createElementNS(ns, \"path\");\n        belly.setAttribute(\"d\", `\n         M ${-s * 0.5} ${s * 0.65}\n         Q ${s * 0.1} ${s * 0.75} ${s * 0.6} ${s * 0.55}\n         Q ${s * 0.8} ${s * 0.45} ${s * 0.9} ${s * 0.25}\n         Q ${s * 0.7} ${s * 0.5} ${s * 0.3} ${s * 0.6}\n         Q ${-s * 0.1} ${s * 0.7} ${-s * 0.5} ${s * 0.65}\n         Z\n        `);\n        belly.style.fill = \"#ffffff\";\n        belly.style.opacity = \"0.3\";\n        g.appendChild(belly);\n        for (let row = 0; row < 2; row++) {\n         for (let col = 0; col < 3; col++) {\n          const container = document.createElementNS(ns, \"rect\");\n          container.setAttribute(\"x\", -s * 0.45 + col * s * 0.35);\n          container.setAttribute(\"y\", -s * 0.7 + row * s * 0.28);\n          container.setAttribute(\"width\", s * 0.3);\n          container.setAttribute(\"height\", s * 0.23);\n          container.setAttribute(\"rx\", 2);\n          container.style.fill = \"#0db7ed\";\n          container.style.stroke = \"#0a9ed8\";\n          container.style.strokeWidth = \"1.5\";\n          g.appendChild(container);\n         }\n        }\n        const topContainer = document.createElementNS(ns, \"rect\");\n        topContainer.setAttribute(\"x\", -s * 0.1);\n        topContainer.setAttribute(\"y\", -s * 0.95);\n        topContainer.setAttribute(\"width\", s * 0.3);\n        topContainer.setAttribute(\"height\", s * 0.23);\n        topContainer.setAttribute(\"rx\", 2);\n        topContainer.style.fill = \"#0db7ed\";\n        topContainer.style.stroke = \"#0a9ed8\";\n        topContainer.style.strokeWidth = \"1.5\";\n        g.appendChild(topContainer);\n        const eye = document.createElementNS(ns, \"circle\");\n        eye.setAttribute(\"cx\", s * 0.65);\n        eye.setAttribute(\"cy\", s * 0.25);\n        eye.setAttribute(\"r\", s * 0.08);\n        eye.style.fill = \"#0a5f7a\";\n        g.appendChild(eye);\n        return g;\n       }\n       if (shape === \"rounded-square\") {\n        const r = document.createElementNS(ns, \"rect\");\n        const s = size * 1.6;\n        r.setAttribute(\"x\", -s / 2);\n        r.setAttribute(\"y\", -s / 2);\n        r.setAttribute(\"width\", s);\n        r.setAttribute(\"height\", s);\n        r.setAttribute(\"rx\", size * 0.4);\n        return r;\n       }\n       if (shape === \"pill\" || shape === \"capsule\") {\n        const r = document.createElementNS(ns, \"rect\");\n        r.setAttribute(\"x\", -size * 1.2);\n        r.setAttribute(\"y\", -size * 0.5);\n        r.setAttribute(\"width\", size * 2.4);\n        r.setAttribute(\"height\", size);\n        r.setAttribute(\"rx\", size * 0.5);\n        return r;\n       }\n       if (shape === \"octagon\") {\n        const p = document.createElementNS(ns, \"polygon\");\n        const r = size;\n        const pts = [];\n        for (let i = 0; i < 8; i++) {\n         const a = (Math.PI / 4) * i - Math.PI / 8;\n         pts.push([Math.cos(a) * r, Math.sin(a) * r]);\n        }\n        p.setAttribute(\"points\", pts.map(pt => pt.join(\",\")).join(\" \"));\n        return p;\n       }\n       if (shape === \"pentagon\") {\n        const p = document.createElementNS(ns, \"polygon\");\n        const pts = [];\n        for (let i = 0; i < 5; i++) {\n         const a = (Math.PI * 2 / 5) * i - Math.PI / 2;\n         pts.push([Math.cos(a) * size, Math.sin(a) * size]);\n        }\n        p.setAttribute(\"points\", pts.map(pt => pt.join(\",\")).join(\" \"));\n        return p;\n       }\n       if (shape === \"cross\" || shape === \"plus\") {\n        const p = document.createElementNS(ns, \"polygon\");\n        const s = size;\n        const t = size * 0.35;\n        p.setAttribute(\"points\", `${-t},${-s} ${t},${-s} ${t},${-t} ${s},${-t} ${s},${t} ${t},${t} ${t},${s} ${-t},${s} ${-t},${t} ${-s},${t} ${-s},${-t} ${-t},${-t}`);\n        return p;\n       }\n       if (shape === \"parallelogram\") {\n        const p = document.createElementNS(ns, \"polygon\");\n        const s = size;\n        p.setAttribute(\"points\", `${-s * 0.6},${-s * 0.6} ${s * 1.2},${-s * 0.6} ${s * 0.6},${s * 0.6} ${-s * 1.2},${s * 0.6}`);\n        return p;\n       }\n       if (shape === \"trapezoid\") {\n        const p = document.createElementNS(ns, \"polygon\");\n        const s = size;\n        p.setAttribute(\"points\", `${-s * 0.6},${-s * 0.6} ${s * 0.6},${-s * 0.6} ${s},${s * 0.6} ${-s},${s * 0.6}`);\n        return p;\n       }\n       if (shape === \"sensor\" || shape === \"iot\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"circle\");\n        body.setAttribute(\"cx\", 0);\n        body.setAttribute(\"cy\", 0);\n        body.setAttribute(\"r\", size * 0.7);\n        g.appendChild(body);\n        const ant = document.createElementNS(ns, \"line\");\n        ant.setAttribute(\"x1\", 0);\n        ant.setAttribute(\"y1\", -size * 0.7);\n        ant.setAttribute(\"x2\", 0);\n        ant.setAttribute(\"y2\", -size);\n        ant.style.stroke = \"currentColor\";\n        ant.style.strokeWidth = \"2\";\n        g.appendChild(ant);\n        const tip = document.createElementNS(ns, \"circle\");\n        tip.setAttribute(\"cx\", 0);\n        tip.setAttribute(\"cy\", -size);\n        tip.setAttribute(\"r\", size * 0.1);\n        tip.style.fill = \"#4ade80\";\n        g.appendChild(tip);\n        return g;\n       }\n       if (shape === \"pi\" || shape === \"sbc\" || shape === \"raspberry\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size);\n        body.setAttribute(\"y\", -size * 0.65);\n        body.setAttribute(\"width\", size * 2);\n        body.setAttribute(\"height\", size * 1.3);\n        body.setAttribute(\"rx\", 4);\n        g.appendChild(body);\n        for (let i = 0; i < 10; i++) {\n         const pin = document.createElementNS(ns, \"rect\");\n         pin.setAttribute(\"x\", -size * 0.85 + i * size * 0.19);\n         pin.setAttribute(\"y\", -size * 0.8);\n         pin.setAttribute(\"width\", size * 0.08);\n         pin.setAttribute(\"height\", size * 0.15);\n         pin.style.fill = \"#facc15\";\n         g.appendChild(pin);\n        }\n        const port = document.createElementNS(ns, \"rect\");\n        port.setAttribute(\"x\", size * 0.6);\n        port.setAttribute(\"y\", -size * 0.2);\n        port.setAttribute(\"width\", size * 0.35);\n        port.setAttribute(\"height\", size * 0.4);\n        port.style.fill = \"#1e293b\";\n        g.appendChild(port);\n        return g;\n       }\n       if (shape === \"api\" || shape === \"endpoint\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 0.9);\n        body.setAttribute(\"y\", -size * 0.7);\n        body.setAttribute(\"width\", size * 1.8);\n        body.setAttribute(\"height\", size * 1.4);\n        body.setAttribute(\"rx\", 6);\n        g.appendChild(body);\n        const left = document.createElementNS(ns, \"text\");\n        left.setAttribute(\"x\", -size * 0.5);\n        left.setAttribute(\"y\", size * 0.15);\n        left.setAttribute(\"font-size\", size * 0.9);\n        left.setAttribute(\"fill\", \"#4ade80\");\n        left.setAttribute(\"font-family\", \"monospace\");\n        left.textContent = \"{\";\n        g.appendChild(left);\n        const right = document.createElementNS(ns, \"text\");\n        right.setAttribute(\"x\", size * 0.15);\n        right.setAttribute(\"y\", size * 0.15);\n        right.setAttribute(\"font-size\", size * 0.9);\n        right.setAttribute(\"fill\", \"#4ade80\");\n        right.setAttribute(\"font-family\", \"monospace\");\n        right.textContent = \"}\";\n        g.appendChild(right);\n        return g;\n       }\n       if (shape === \"queue\" || shape === \"message\") {\n        const g = document.createElementNS(ns, \"g\");\n        for (let i = 2; i >= 0; i--) {\n         const card = document.createElementNS(ns, \"rect\");\n         card.setAttribute(\"x\", -size * 0.7 + i * 4);\n         card.setAttribute(\"y\", -size * 0.5 + i * 4);\n         card.setAttribute(\"width\", size * 1.4);\n         card.setAttribute(\"height\", size * 0.8);\n         card.setAttribute(\"rx\", 3);\n         card.style.opacity = 1 - i * 0.25;\n         g.appendChild(card);\n        }\n        return g;\n       }\n       if (shape === \"lambda\" || shape === \"function\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 0.8);\n        body.setAttribute(\"y\", -size * 0.8);\n        body.setAttribute(\"width\", size * 1.6);\n        body.setAttribute(\"height\", size * 1.6);\n        body.setAttribute(\"rx\", 4);\n        g.appendChild(body);\n        const lambda = document.createElementNS(ns, \"text\");\n        lambda.setAttribute(\"x\", 0);\n        lambda.setAttribute(\"y\", size * 0.2);\n        lambda.setAttribute(\"font-size\", size * 1.2);\n        lambda.setAttribute(\"fill\", \"#f59e0b\");\n        lambda.setAttribute(\"text-anchor\", \"middle\");\n        lambda.setAttribute(\"font-family\", \"serif\");\n        lambda.textContent = \"λ\";\n        g.appendChild(lambda);\n        return g;\n       }\n       if (shape === \"bucket\" || shape === \"s3\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"path\");\n        body.setAttribute(\"d\", `M ${-size * 0.8} ${-size * 0.7} L ${-size * 0.6} ${size * 0.8} Q ${-size * 0.5} ${size} 0 ${size} Q ${size * 0.5} ${size} ${size * 0.6} ${size * 0.8} L ${size * 0.8} ${-size * 0.7} Z`);\n        g.appendChild(body);\n        const rim = document.createElementNS(ns, \"ellipse\");\n        rim.setAttribute(\"cx\", 0);\n        rim.setAttribute(\"cy\", -size * 0.7);\n        rim.setAttribute(\"rx\", size * 0.8);\n        rim.setAttribute(\"ry\", size * 0.25);\n        g.appendChild(rim);\n        return g;\n       }\n\n       if (shape === \"thermostat\") {\n        const g = document.createElementNS(ns, \"g\");\n        const outer = document.createElementNS(ns, \"circle\");\n        outer.setAttribute(\"r\", size);\n        g.appendChild(outer);\n        const inner = document.createElementNS(ns, \"circle\");\n        inner.setAttribute(\"r\", size * 0.75);\n        inner.style.fill = \"#1e293b\";\n        g.appendChild(inner);\n        const temp = document.createElementNS(ns, \"text\");\n        temp.setAttribute(\"x\", 0);\n        temp.setAttribute(\"y\", size * 0.15);\n        temp.setAttribute(\"font-size\", size * 0.5);\n        temp.setAttribute(\"fill\", \"#4ade80\");\n        temp.setAttribute(\"text-anchor\", \"middle\");\n        temp.setAttribute(\"font-family\", \"monospace\");\n        temp.textContent = \"72°\";\n        g.appendChild(temp);\n        return g;\n       }\n       if (shape === \"doorbell\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 0.5);\n        body.setAttribute(\"y\", -size);\n        body.setAttribute(\"width\", size);\n        body.setAttribute(\"height\", size * 2);\n        body.setAttribute(\"rx\", size * 0.25);\n        g.appendChild(body);\n        const lens = document.createElementNS(ns, \"circle\");\n        lens.setAttribute(\"cx\", 0);\n        lens.setAttribute(\"cy\", -size * 0.35);\n        lens.setAttribute(\"r\", size * 0.3);\n        lens.style.fill = \"#1e293b\";\n        g.appendChild(lens);\n        const lensDot = document.createElementNS(ns, \"circle\");\n        lensDot.setAttribute(\"cx\", 0);\n        lensDot.setAttribute(\"cy\", -size * 0.35);\n        lensDot.setAttribute(\"r\", size * 0.12);\n        lensDot.style.fill = \"#3b82f6\";\n        g.appendChild(lensDot);\n        const btn = document.createElementNS(ns, \"circle\");\n        btn.setAttribute(\"cx\", 0);\n        btn.setAttribute(\"cy\", size * 0.5);\n        btn.setAttribute(\"r\", size * 0.2);\n        btn.style.fill = \"#f59e0b\";\n        g.appendChild(btn);\n        return g;\n       }\n       if (shape === \"smart-lock\") {\n        const g = document.createElementNS(ns, \"g\");\n        const shackle = document.createElementNS(ns, \"path\");\n        shackle.setAttribute(\"d\", `M ${-size * 0.4} ${-size * 0.1} L ${-size * 0.4} ${-size * 0.6} A ${size * 0.4} ${size * 0.4} 0 1 1 ${size * 0.4} ${-size * 0.6} L ${size * 0.4} ${-size * 0.1}`);\n        shackle.style.fill = \"none\";\n        shackle.style.strokeWidth = size * 0.2;\n        g.appendChild(shackle);\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 0.6);\n        body.setAttribute(\"y\", -size * 0.15);\n        body.setAttribute(\"width\", size * 1.2);\n        body.setAttribute(\"height\", size * 1);\n        body.setAttribute(\"rx\", 4);\n        g.appendChild(body);\n        const keyhole = document.createElementNS(ns, \"circle\");\n        keyhole.setAttribute(\"cx\", 0);\n        keyhole.setAttribute(\"cy\", size * 0.3);\n        keyhole.setAttribute(\"r\", size * 0.15);\n        keyhole.style.fill = \"#4ade80\";\n        g.appendChild(keyhole);\n        return g;\n       }\n       if (shape === \"smart-bulb\") {\n        const g = document.createElementNS(ns, \"g\");\n        const bulb = document.createElementNS(ns, \"path\");\n        bulb.setAttribute(\"d\", `M ${-size * 0.5} ${size * 0.2} Q ${-size * 0.8} ${-size * 0.3} ${-size * 0.5} ${-size * 0.7} Q 0 ${-size * 1.1} ${size * 0.5} ${-size * 0.7} Q ${size * 0.8} ${-size * 0.3} ${size * 0.5} ${size * 0.2} Z`);\n        g.appendChild(bulb);\n        const base1 = document.createElementNS(ns, \"rect\");\n        base1.setAttribute(\"x\", -size * 0.35);\n        base1.setAttribute(\"y\", size * 0.2);\n        base1.setAttribute(\"width\", size * 0.7);\n        base1.setAttribute(\"height\", size * 0.15);\n        base1.style.fill = \"#94a3b8\";\n        g.appendChild(base1);\n        const base2 = document.createElementNS(ns, \"rect\");\n        base2.setAttribute(\"x\", -size * 0.3);\n        base2.setAttribute(\"y\", size * 0.35);\n        base2.setAttribute(\"width\", size * 0.6);\n        base2.setAttribute(\"height\", size * 0.15);\n        base2.style.fill = \"#64748b\";\n        g.appendChild(base2);\n        const base3 = document.createElementNS(ns, \"rect\");\n        base3.setAttribute(\"x\", -size * 0.25);\n        base3.setAttribute(\"y\", size * 0.5);\n        base3.setAttribute(\"width\", size * 0.5);\n        base3.setAttribute(\"height\", size * 0.2);\n        base3.setAttribute(\"rx\", 2);\n        base3.style.fill = \"#475569\";\n        g.appendChild(base3);\n        return g;\n       }\n       if (shape === \"smart-plug\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 0.7);\n        body.setAttribute(\"y\", -size * 0.6);\n        body.setAttribute(\"width\", size * 1.4);\n        body.setAttribute(\"height\", size * 1.2);\n        body.setAttribute(\"rx\", 6);\n        g.appendChild(body);\n        const hole1 = document.createElementNS(ns, \"rect\");\n        hole1.setAttribute(\"x\", -size * 0.35);\n        hole1.setAttribute(\"y\", -size * 0.3);\n        hole1.setAttribute(\"width\", size * 0.15);\n        hole1.setAttribute(\"height\", size * 0.4);\n        hole1.setAttribute(\"rx\", 2);\n        hole1.style.fill = \"#1e293b\";\n        g.appendChild(hole1);\n        const hole2 = document.createElementNS(ns, \"rect\");\n        hole2.setAttribute(\"x\", size * 0.2);\n        hole2.setAttribute(\"y\", -size * 0.3);\n        hole2.setAttribute(\"width\", size * 0.15);\n        hole2.setAttribute(\"height\", size * 0.4);\n        hole2.setAttribute(\"rx\", 2);\n        hole2.style.fill = \"#1e293b\";\n        g.appendChild(hole2);\n        const led = document.createElementNS(ns, \"circle\");\n        led.setAttribute(\"cx\", 0);\n        led.setAttribute(\"cy\", size * 0.35);\n        led.setAttribute(\"r\", size * 0.1);\n        led.style.fill = \"#4ade80\";\n        g.appendChild(led);\n        return g;\n       }\n       if (shape === \"smart-speaker\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 0.6);\n        body.setAttribute(\"y\", -size);\n        body.setAttribute(\"width\", size * 1.2);\n        body.setAttribute(\"height\", size * 2);\n        body.setAttribute(\"rx\", size * 0.3);\n        g.appendChild(body);\n        const mesh = document.createElementNS(ns, \"rect\");\n        mesh.setAttribute(\"x\", -size * 0.5);\n        mesh.setAttribute(\"y\", -size * 0.3);\n        mesh.setAttribute(\"width\", size);\n        mesh.setAttribute(\"height\", size * 1.1);\n        mesh.setAttribute(\"rx\", 4);\n        mesh.style.fill = \"#1e293b\";\n        g.appendChild(mesh);\n        const ring = document.createElementNS(ns, \"circle\");\n        ring.setAttribute(\"cx\", 0);\n        ring.setAttribute(\"cy\", -size * 0.65);\n        ring.setAttribute(\"r\", size * 0.2);\n        ring.style.fill = \"#3b82f6\";\n        g.appendChild(ring);\n        return g;\n       }\n       if (shape === \"smart-tv\") {\n        const g = document.createElementNS(ns, \"g\");\n        const frame = document.createElementNS(ns, \"rect\");\n        frame.setAttribute(\"x\", -size * 1.4);\n        frame.setAttribute(\"y\", -size * 0.85);\n        frame.setAttribute(\"width\", size * 2.8);\n        frame.setAttribute(\"height\", size * 1.6);\n        frame.setAttribute(\"rx\", 4);\n        g.appendChild(frame);\n        const screen = document.createElementNS(ns, \"rect\");\n        screen.setAttribute(\"x\", -size * 1.3);\n        screen.setAttribute(\"y\", -size * 0.75);\n        screen.setAttribute(\"width\", size * 2.6);\n        screen.setAttribute(\"height\", size * 1.4);\n        screen.setAttribute(\"rx\", 2);\n        screen.style.fill = \"#1e293b\";\n        g.appendChild(screen);\n        const stand = document.createElementNS(ns, \"rect\");\n        stand.setAttribute(\"x\", -size * 0.8);\n        stand.setAttribute(\"y\", size * 0.75);\n        stand.setAttribute(\"width\", size * 1.6);\n        stand.setAttribute(\"height\", size * 0.12);\n        stand.setAttribute(\"rx\", 2);\n        g.appendChild(stand);\n        return g;\n       }\n       if (shape === \"hub\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 0.9);\n        body.setAttribute(\"y\", -size * 0.5);\n        body.setAttribute(\"width\", size * 1.8);\n        body.setAttribute(\"height\", size);\n        body.setAttribute(\"rx\", 8);\n        g.appendChild(body);\n        for (let i = 0; i < 4; i++) {\n         const led = document.createElementNS(ns, \"circle\");\n         led.setAttribute(\"cx\", -size * 0.5 + i * size * 0.35);\n         led.setAttribute(\"cy\", 0);\n         led.setAttribute(\"r\", size * 0.08);\n         led.style.fill = [\"#4ade80\", \"#3b82f6\", \"#f59e0b\", \"#ef4444\"][i];\n         g.appendChild(led);\n        }\n        return g;\n       }\n       if (shape === \"smoke-detector\") {\n        const g = document.createElementNS(ns, \"g\");\n        const outer = document.createElementNS(ns, \"circle\");\n        outer.setAttribute(\"r\", size);\n        g.appendChild(outer);\n        const inner = document.createElementNS(ns, \"circle\");\n        inner.setAttribute(\"r\", size * 0.6);\n        inner.style.fill = \"#1e293b\";\n        g.appendChild(inner);\n        for (let i = 0; i < 6; i++) {\n         const slot = document.createElementNS(ns, \"rect\");\n         const angle = (i * 60 - 90) * Math.PI / 180;\n         slot.setAttribute(\"x\", Math.cos(angle) * size * 0.35 - size * 0.08);\n         slot.setAttribute(\"y\", Math.sin(angle) * size * 0.35 - size * 0.03);\n         slot.setAttribute(\"width\", size * 0.16);\n         slot.setAttribute(\"height\", size * 0.06);\n         slot.setAttribute(\"rx\", 1);\n         slot.style.fill = \"#475569\";\n         slot.setAttribute(\"transform\", `rotate(${i * 60}, ${Math.cos(angle) * size * 0.35}, ${Math.sin(angle) * size * 0.35})`);\n         g.appendChild(slot);\n        }\n        const led = document.createElementNS(ns, \"circle\");\n        led.setAttribute(\"cx\", 0);\n        led.setAttribute(\"cy\", 0);\n        led.setAttribute(\"r\", size * 0.1);\n        led.style.fill = \"#ef4444\";\n        g.appendChild(led);\n        return g;\n       }\n       if (shape === \"motion-sensor\") {\n        const g = document.createElementNS(ns, \"g\");\n        const dome = document.createElementNS(ns, \"path\");\n        dome.setAttribute(\"d\", `M ${-size * 0.8} ${size * 0.3} Q ${-size * 0.8} ${-size * 0.8} 0 ${-size * 0.8} Q ${size * 0.8} ${-size * 0.8} ${size * 0.8} ${size * 0.3} Z`);\n        g.appendChild(dome);\n        const base = document.createElementNS(ns, \"rect\");\n        base.setAttribute(\"x\", -size * 0.8);\n        base.setAttribute(\"y\", size * 0.3);\n        base.setAttribute(\"width\", size * 1.6);\n        base.setAttribute(\"height\", size * 0.35);\n        base.setAttribute(\"rx\", 3);\n        g.appendChild(base);\n        const lens = document.createElementNS(ns, \"ellipse\");\n        lens.setAttribute(\"cx\", 0);\n        lens.setAttribute(\"cy\", -size * 0.15);\n        lens.setAttribute(\"rx\", size * 0.35);\n        lens.setAttribute(\"ry\", size * 0.25);\n        lens.style.fill = \"#1e293b\";\n        g.appendChild(lens);\n        const led = document.createElementNS(ns, \"circle\");\n        led.setAttribute(\"cx\", 0);\n        led.setAttribute(\"cy\", size * 0.45);\n        led.setAttribute(\"r\", size * 0.08);\n        led.style.fill = \"#4ade80\";\n        g.appendChild(led);\n        return g;\n       }\n       if (shape === \"garage\") {\n        const g = document.createElementNS(ns, \"g\");\n        const frame = document.createElementNS(ns, \"rect\");\n        frame.setAttribute(\"x\", -size);\n        frame.setAttribute(\"y\", -size * 0.9);\n        frame.setAttribute(\"width\", size * 2);\n        frame.setAttribute(\"height\", size * 1.8);\n        frame.setAttribute(\"rx\", 4);\n        g.appendChild(frame);\n        for (let i = 0; i < 4; i++) {\n         const panel = document.createElementNS(ns, \"rect\");\n         panel.setAttribute(\"x\", -size * 0.9);\n         panel.setAttribute(\"y\", -size * 0.8 + i * size * 0.42);\n         panel.setAttribute(\"width\", size * 1.8);\n         panel.setAttribute(\"height\", size * 0.35);\n         panel.setAttribute(\"rx\", 2);\n         panel.style.fill = \"#1e293b\";\n         g.appendChild(panel);\n        }\n        return g;\n       }\n       if (shape === \"sprinkler\") {\n        const g = document.createElementNS(ns, \"g\");\n        const head = document.createElementNS(ns, \"circle\");\n        head.setAttribute(\"r\", size * 0.5);\n        g.appendChild(head);\n        const nozzle = document.createElementNS(ns, \"rect\");\n        nozzle.setAttribute(\"x\", -size * 0.15);\n        nozzle.setAttribute(\"y\", size * 0.3);\n        nozzle.setAttribute(\"width\", size * 0.3);\n        nozzle.setAttribute(\"height\", size * 0.5);\n        g.appendChild(nozzle);\n        for (let i = 0; i < 5; i++) {\n         const spray = document.createElementNS(ns, \"line\");\n         const angle = (-60 + i * 30) * Math.PI / 180;\n         spray.setAttribute(\"x1\", 0);\n         spray.setAttribute(\"y1\", -size * 0.3);\n         spray.setAttribute(\"x2\", Math.cos(angle) * size * 0.8);\n         spray.setAttribute(\"y2\", Math.sin(angle) * size * 0.8 - size * 0.3);\n         spray.style.stroke = \"#3b82f6\";\n         spray.style.strokeWidth = \"2\";\n         spray.style.strokeDasharray = \"3,3\";\n         g.appendChild(spray);\n        }\n        return g;\n       }\n       if (shape === \"vacuum\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"circle\");\n        body.setAttribute(\"r\", size);\n        g.appendChild(body);\n        const top = document.createElementNS(ns, \"circle\");\n        top.setAttribute(\"r\", size * 0.7);\n        top.style.fill = \"#1e293b\";\n        g.appendChild(top);\n        const bumper = document.createElementNS(ns, \"path\");\n        bumper.setAttribute(\"d\", `M ${-size * 0.7} ${-size * 0.4} A ${size * 0.8} ${size * 0.8} 0 0 1 ${size * 0.7} ${-size * 0.4}`);\n        bumper.style.fill = \"none\";\n        bumper.style.strokeWidth = size * 0.15;\n        g.appendChild(bumper);\n        const btn = document.createElementNS(ns, \"circle\");\n        btn.setAttribute(\"cx\", 0);\n        btn.setAttribute(\"cy\", size * 0.1);\n        btn.setAttribute(\"r\", size * 0.2);\n        btn.style.fill = \"#4ade80\";\n        g.appendChild(btn);\n        return g;\n       }\n       const c = document.createElementNS(ns, \"circle\");\n       c.setAttribute(\"r\", size);\n       return c;\n      }\n      function createNodeShape(id, size) {\n       const shapeType = (NODE_DATA[id] && NODE_DATA[id].shape) || \"circle\";\n       const shapeEl = createShapeElement(shapeType, size);\n       shapeEl.classList.add(\"node-circle\");\n       const styles = resolveStylesForNode(id);\n       shapeEl.style.fill = styles.circleColor || PAGE_STATE.nodeFill || \"#1e293b\";\n       shapeEl.style.stroke = styles.circleBorder || PAGE_STATE.nodeStroke || \"#475569\";\n       return shapeEl;\n      }\n      function forgeTheLegend() {\n       const container = document.getElementById(\"edge-legend\");\n       if (!container) return;\n       container.innerHTML = \"\";\n       const title = document.createElement(\"div\");\n       title.className = \"legend-title\";\n       title.textContent = \"Line Legend\";\n       container.appendChild(title);\n       const closeBtn = document.createElement(\"button\");\n       closeBtn.type = \"button\";\n       closeBtn.className = \"legend-close-btn\";\n       closeBtn.textContent = \"✕\";\n       closeBtn.addEventListener(\"click\", (e) => {\n        e.stopPropagation();\n        legendCollapsed = true;\n        updateLegendVisibility();\n       });\n       container.appendChild(closeBtn);\n       const colors = [...new Set(EDGE_DATA.list.map((e) => e.color).filter(Boolean))];\n       if (colors.length === 0) {\n        updateLegendVisibility();\n        return;\n       }\n       colors.forEach((color) => {\n        if (!EDGE_LEGEND[color]) {\n         EDGE_LEGEND[color] = \"you can edit me too\";\n        }\n        const item = document.createElement(\"div\");\n        item.className = \"legend-item\";\n        item.addEventListener(\"mousedown\", (e) => e.stopPropagation());\n        item.addEventListener(\"click\", (e) => e.stopPropagation());\n        const swatch = document.createElement(\"span\");\n        swatch.className = \"legend-swatch\";\n        swatch.style.backgroundColor = color;\n        swatch.style.cursor = \"pointer\";\n        swatch.addEventListener(\"click\", (e) => {\n         e.stopPropagation();\n         const edgeWithColor = EDGE_DATA.list.find(edge => edge.color === color);\n         if (edgeWithColor) {\n          selectTheConnection(edgeWithColor.id);\n         }\n        });\n        let swatchTouchStart = null;\n        let swatchTouchMoved = false;\n        swatch.addEventListener(\"touchstart\", (e) => {\n         swatchTouchStart = Date.now();\n         swatchTouchMoved = false;\n         if (e.touches[0]) {\n          swatchTouchStartX = e.touches[0].clientX;\n          swatchTouchStartY = e.touches[0].clientY;\n         }\n        }, {\n         passive: false\n        });\n        let swatchTouchStartX = 0, swatchTouchStartY = 0;\n        swatch.addEventListener(\"touchmove\", (e) => {\n         if (e.touches[0]) {\n          const dx = Math.abs(e.touches[0].clientX - swatchTouchStartX);\n          const dy = Math.abs(e.touches[0].clientY - swatchTouchStartY);\n          if (dx > 10 || dy > 10) swatchTouchMoved = true;\n         }\n        }, {\n         passive: false\n        });\n        swatch.addEventListener(\"touchend\", (e) => {\n         if (swatchTouchStart && !swatchTouchMoved && Date.now() - swatchTouchStart < 400) {\n          e.stopPropagation();\n          e.preventDefault();\n          const edgeWithColor = EDGE_DATA.list.find(edge => edge.color === color);\n          if (edgeWithColor) {\n           selectTheConnection(edgeWithColor.id);\n          }\n         }\n         swatchTouchStart = null;\n         swatchTouchMoved = false;\n        }, {\n         passive: false\n        });\n        const label = document.createElement(\"span\");\n        label.className = \"legend-label\";\n        label.textContent = EDGE_LEGEND[color];\n        if (isMobileDevice()) {\n         label.style.cursor = \"pointer\";\n         let labelTapStart = null;\n         let labelTapMoved = false;\n         label.addEventListener(\"touchstart\", (e) => {\n          labelTapStart = Date.now();\n          labelTapMoved = false;\n          if (e.touches[0]) {\n           labelTapStartX = e.touches[0].clientX;\n           labelTapStartY = e.touches[0].clientY;\n          }\n          e.stopPropagation();\n         });\n         let labelTapStartX = 0, labelTapStartY = 0;\n         label.addEventListener(\"touchmove\", (e) => {\n          if (e.touches[0]) {\n           const dx = Math.abs(e.touches[0].clientX - labelTapStartX);\n           const dy = Math.abs(e.touches[0].clientY - labelTapStartY);\n           if (dx > 10 || dy > 10) labelTapMoved = true;\n          }\n         });\n         label.addEventListener(\"touchend\", (e) => {\n          if (labelTapStart && !labelTapMoved && Date.now() - labelTapStart < 400) {\n           e.preventDefault();\n           e.stopPropagation();\n           const currentText = label.textContent;\n           const newText = prompt(\"Edit legend label:\", currentText);\n           if (newText !== null && newText.trim()) {\n            label.textContent = newText.trim();\n            EDGE_LEGEND[color] = newText.trim();\n           }\n          }\n          labelTapStart = null;\n          labelTapMoved = false;\n         });\n        } else {\n         label.contentEditable = true;\n         label.addEventListener(\"focus\", () => {\n          label.classList.add(\"editing\");\n         });\n         label.addEventListener(\"blur\", () => {\n          label.classList.remove(\"editing\");\n          const text = label.textContent.trim() || \"you can edit me too\";\n          EDGE_LEGEND[color] = text;\n         });\n         label.addEventListener(\"keydown\", (e) => {\n          if (e.key === \"Enter\") {\n           e.preventDefault();\n           label.blur();\n          }\n         });\n        }\n        item.append(swatch, label);\n        container.appendChild(item);\n       });\n       updateLegendVisibility();\n      }\n       function deleteRectangle(rectId) {\n      pushUndo(\"delete zone\");\n        RECT_DATA.list = RECT_DATA.list.filter(r => r.id !== rectId);\n        forgeTheTopology();\n       }\n       function updateRectangleDeleteButtons() {\n        const deleteButtons = document.querySelectorAll('.rect-delete-btn');\n        deleteButtons.forEach(btn => {\n         btn.style.display = rectDrawMode ? 'block' : 'none';\n        });\n       }\nfunction updateFovCone(nodeId) {\n  const node = NODE_DATA[nodeId];\n  if (!node) return;\n  \n  const nodeGroup = document.querySelector(`g[data-node-id=\"${nodeId}\"]`);\n  if (!nodeGroup) return;\n  const existingFov = nodeGroup.querySelector(\".fov-group\");\n  if (existingFov) existingFov.remove();\n  if (!hasCoverageZone(node.shape) || !node.fovEnabled) return;\n  \n  const ns = \"http://www.w3.org/2000/svg\";\n  const defaults = getCoverageDefaults(node.shape);\n  const fovAngle = node.fovAngle || defaults.angle;\n  const fovDistance = node.fovDistance || defaults.distance;\n  const fovInnerRadius = node.fovInnerRadius || 0;\n  const fovRotation = node.fovRotation || 0;\n  const fovColor = node.fovColor || \"#f59e0b\";\n  const fovOpacity = node.fovOpacity || 20;\n  const fovGradient = node.fovGradient || false;\n  const fovBorderColor = node.fovBorderColor || \"#f59e0b\";\n  const fovBorderWidth = node.fovBorderWidth ?? 2;\n  const fovBorderStyle = node.fovBorderStyle || \"solid\";\n  const fovBorderOpacity = node.fovBorderOpacity ?? 100;\n  const fovLabel = node.fovLabel || \"\";\n  const fovAnimate = node.fovAnimate || false;\n  const fovAnimationType = node.fovAnimationType || defaults.animationType;\n  const fovSweep = node.fovSweep || 120;\n  const fovSpeed = node.fovSpeed || 4;\n  \n  const fovGroup = document.createElementNS(ns, \"g\");\n  fovGroup.classList.add(\"fov-group\");\n  \n  if (fovGradient) {\n    const gradientId = `fov-gradient-${nodeId}`;\n    const defs = document.createElementNS(ns, \"defs\");\n    const gradient = document.createElementNS(ns, \"radialGradient\");\n    gradient.id = gradientId;\n    gradient.setAttribute(\"cx\", \"0\");\n    gradient.setAttribute(\"cy\", \"0\");\n    gradient.setAttribute(\"r\", fovDistance);\n    gradient.setAttribute(\"gradientUnits\", \"userSpaceOnUse\");\n    \n    const stop1 = document.createElementNS(ns, \"stop\");\n    stop1.setAttribute(\"offset\", fovInnerRadius / fovDistance);\n    stop1.setAttribute(\"stop-color\", fovColor);\n    stop1.setAttribute(\"stop-opacity\", fovOpacity / 100);\n    \n    const stop2 = document.createElementNS(ns, \"stop\");\n    stop2.setAttribute(\"offset\", \"1\");\n    stop2.setAttribute(\"stop-color\", fovColor);\n    stop2.setAttribute(\"stop-opacity\", \"0\");\n    \n    gradient.appendChild(stop1);\n    gradient.appendChild(stop2);\n    defs.appendChild(gradient);\n    fovGroup.appendChild(defs);\n  }\n  \n  const fovPath = document.createElementNS(ns, \"path\");\n  \n  if (fovAngle >= 360) {\n    if (fovInnerRadius > 0) {\n      fovPath.setAttribute(\"d\", `M ${fovDistance},0 A ${fovDistance},${fovDistance} 0 1,1 ${-fovDistance},0 A ${fovDistance},${fovDistance} 0 1,1 ${fovDistance},0 M ${fovInnerRadius},0 A ${fovInnerRadius},${fovInnerRadius} 0 1,0 ${-fovInnerRadius},0 A ${fovInnerRadius},${fovInnerRadius} 0 1,0 ${fovInnerRadius},0`);\n      fovPath.setAttribute(\"fill-rule\", \"evenodd\");\n    } else {\n      fovPath.setAttribute(\"d\", `M 0,0 m -${fovDistance},0 a ${fovDistance},${fovDistance} 0 1,0 ${fovDistance * 2},0 a ${fovDistance},${fovDistance} 0 1,0 -${fovDistance * 2},0`);\n    }\n  } else {\n    const angleRad = (fovAngle * Math.PI) / 180;\n    const rotationRad = ((fovRotation - 90) * Math.PI) / 180;\n    \n    const startAngle = rotationRad - angleRad / 2;\n    const endAngle = rotationRad + angleRad / 2;\n    \n    const x1 = Math.cos(startAngle) * fovDistance;\n    const y1 = Math.sin(startAngle) * fovDistance;\n    const x2 = Math.cos(endAngle) * fovDistance;\n    const y2 = Math.sin(endAngle) * fovDistance;\n    \n    const largeArc = fovAngle > 180 ? 1 : 0;\n    \n    if (fovInnerRadius > 0) {\n      const ix1 = Math.cos(startAngle) * fovInnerRadius;\n      const iy1 = Math.sin(startAngle) * fovInnerRadius;\n      const ix2 = Math.cos(endAngle) * fovInnerRadius;\n      const iy2 = Math.sin(endAngle) * fovInnerRadius;\n      fovPath.setAttribute(\"d\", `M ${ix1},${iy1} L ${x1},${y1} A ${fovDistance},${fovDistance} 0 ${largeArc},1 ${x2},${y2} L ${ix2},${iy2} A ${fovInnerRadius},${fovInnerRadius} 0 ${largeArc},0 ${ix1},${iy1} Z`);\n    } else {\n      fovPath.setAttribute(\"d\", `M 0,0 L ${x1},${y1} A ${fovDistance},${fovDistance} 0 ${largeArc},1 ${x2},${y2} Z`);\n    }\n  }\n  \n  if (fovGradient) {\n    fovPath.style.fill = `url(#fov-gradient-${nodeId})`;\n  } else {\n    const opacityHex = Math.round((fovOpacity / 100) * 255).toString(16).padStart(2, '0');\n    fovPath.style.fill = fovColor + opacityHex;\n  }\n  \n  const borderOpacityHex = Math.round((fovBorderOpacity / 100) * 255).toString(16).padStart(2, '0');\n  fovPath.style.stroke = fovBorderColor + borderOpacityHex;\n  fovPath.style.strokeWidth = fovBorderWidth;\n  if (fovBorderStyle === \"dashed\") {\n    fovPath.style.strokeDasharray = \"10,5\";\n  } else if (fovBorderStyle === \"dotted\") {\n    fovPath.style.strokeDasharray = \"3,3\";\n  }\n  fovPath.style.pointerEvents = \"none\";\n  fovPath.classList.add(\"fov-cone\");\n  \n  fovGroup.appendChild(fovPath);\n  \n  if (fovLabel) {\n    const fovLabelPosition = node.fovLabelPosition || \"center\";\n    const fovLabelSize = node.fovLabelSize || 14;\n    const fovLabelColor = node.fovLabelColor || \"#ffffff\";\n    const fovLabelBold = node.fovLabelBold || false;\n    const fovLabelBg = node.fovLabelBg || false;\n    const fovLabelBgColor = node.fovLabelBgColor || \"#000000\";\n    \n    let labelDistance;\n    if (fovLabelPosition === \"center\") {\n      labelDistance = fovAngle >= 360 ? 0 : fovDistance * 0.4;\n    } else if (fovLabelPosition === \"edge\") {\n      labelDistance = fovDistance * 0.75;\n    } else {\n      labelDistance = fovDistance + fovLabelSize + 8;\n    }\n    \n    const labelAngle = ((fovRotation - 90) * Math.PI) / 180;\n    const fovLabelOffsetX = node.fovLabelOffsetX || 0;\n    const fovLabelOffsetY = node.fovLabelOffsetY || 0;\n    const labelX = (fovAngle >= 360 && fovLabelPosition === \"center\" ? 0 : Math.cos(labelAngle) * labelDistance) + fovLabelOffsetX;\n    const labelY = (fovAngle >= 360 && fovLabelPosition === \"center\" ? 0 : Math.sin(labelAngle) * labelDistance) + fovLabelOffsetY;\n    \n    if (fovLabelBg) {\n      const bgRect = document.createElementNS(ns, \"rect\");\n      const textWidth = fovLabel.length * fovLabelSize * 0.6;\n      const textHeight = fovLabelSize * 1.4;\n      bgRect.setAttribute(\"x\", labelX - textWidth / 2 - 6);\n      bgRect.setAttribute(\"y\", labelY - textHeight / 2);\n      bgRect.setAttribute(\"width\", textWidth + 12);\n      bgRect.setAttribute(\"height\", textHeight);\n      bgRect.setAttribute(\"rx\", \"4\");\n      bgRect.style.fill = fovLabelBgColor;\n      bgRect.style.opacity = \"0.8\";\n      bgRect.style.pointerEvents = \"none\";\n      fovGroup.appendChild(bgRect);\n    }\n    \n    const labelEl = document.createElementNS(ns, \"text\");\n    labelEl.setAttribute(\"x\", labelX);\n    labelEl.setAttribute(\"y\", labelY);\n    labelEl.setAttribute(\"text-anchor\", \"middle\");\n    labelEl.setAttribute(\"dominant-baseline\", \"middle\");\n    labelEl.style.fill = fovLabelColor;\n    labelEl.style.fontSize = fovLabelSize + \"px\";\n    labelEl.style.fontWeight = fovLabelBold ? \"bold\" : \"normal\";\n    labelEl.style.fontFamily = \"system-ui, sans-serif\";\n    labelEl.style.pointerEvents = \"none\";\n    labelEl.textContent = fovLabel;\n    fovGroup.appendChild(labelEl);\n  }\n  \n  if (fovAnimate) {\n    const animationName = `fov-anim-${nodeId}`;\n    const styleEl = document.createElementNS(\"http://www.w3.org/1999/xhtml\", \"style\");\n    \n    if (fovAnimationType === \"sweep\" && fovAngle < 360) {\n      styleEl.textContent = `\n        @keyframes ${animationName} {\n          0%, 100% { transform: rotate(0deg); }\n          50% { transform: rotate(${fovSweep}deg); }\n        }\n      `;\n      fovGroup.style.animation = `${animationName} ${fovSpeed}s ease-in-out infinite`;\n      fovGroup.style.transformOrigin = \"0 0\";\n    } else if (fovAnimationType === \"pulse\") {\n      styleEl.textContent = `\n        @keyframes ${animationName} {\n          0%, 100% { transform: scale(1); opacity: 1; }\n          50% { transform: scale(1.1); opacity: 0.7; }\n        }\n      `;\n      fovGroup.style.animation = `${animationName} ${fovSpeed}s ease-in-out infinite`;\n      fovGroup.style.transformOrigin = \"0 0\";\n    } else if (fovAnimationType === \"rings\") {\n\n      for (let i = 1; i <= 3; i++) {\n        const ring = document.createElementNS(ns, \"circle\");\n        ring.setAttribute(\"cx\", \"0\");\n        ring.setAttribute(\"cy\", \"0\");\n        ring.setAttribute(\"r\", fovDistance * 0.3 * i);\n        ring.style.fill = \"none\";\n        ring.style.stroke = fovBorderColor;\n        ring.style.strokeWidth = \"2\";\n        ring.style.opacity = \"0\";\n        const ringAnimName = `${animationName}-ring-${i}`;\n        const ringStyle = document.createElementNS(\"http://www.w3.org/1999/xhtml\", \"style\");\n        ringStyle.textContent = `\n          @keyframes ${ringAnimName} {\n            0% { r: ${fovInnerRadius || 10}; opacity: 0.8; }\n            100% { r: ${fovDistance}; opacity: 0; }\n          }\n        `;\n        ring.style.animation = `${ringAnimName} ${fovSpeed}s ease-out infinite`;\n        ring.style.animationDelay = `${(i - 1) * (fovSpeed / 3)}s`;\n        fovGroup.appendChild(ringStyle);\n        fovGroup.appendChild(ring);\n      }\n    } else if (fovAnimationType === \"spin\") {\n      styleEl.textContent = `\n        @keyframes ${animationName} {\n          0% { transform: rotate(0deg); }\n          100% { transform: rotate(360deg); }\n        }\n      `;\n      fovGroup.style.animation = `${animationName} ${fovSpeed}s linear infinite`;\n      fovGroup.style.transformOrigin = \"0 0\";\n    }\n    \n    if (fovAnimationType !== \"rings\") {\n      fovGroup.appendChild(styleEl);\n      const elapsedSeconds = (Date.now() - FOV_ANIMATION_START) / 1000;\n      const animationOffset = elapsedSeconds % fovSpeed;\n      fovGroup.style.animationDelay = `-${animationOffset}s`;\n    }\n  }\n  \n  nodeGroup.insertBefore(fovGroup, nodeGroup.firstChild);\n}\n      function forgeTheTopology() {\n       if (!NODE_DATA || !EDGE_DATA) {\n        console.warn(\"forgeTheTopology called before data initialized\");\n        return;\n       }\n       const svg = document.getElementById(\"map\");\n       svg.innerHTML = \"\";\n       const ns = \"http://www.w3.org/2000/svg\";\n       const defs = document.createElementNS(ns, \"defs\");\n       const flowArrowBig = document.createElementNS(ns, \"path\");\n       flowArrowBig.id = \"flow-arrow-big\";\n       flowArrowBig.setAttribute(\"d\", \"M-6,-4 L6,0 L-6,4 L-3,0 Z\");\n       defs.appendChild(flowArrowBig);\n       const flowArrowSmall = document.createElementNS(ns, \"path\");\n       flowArrowSmall.id = \"flow-arrow-small\";\n       flowArrowSmall.setAttribute(\"d\", \"M-4,-3 L4,0 L-4,3 Z\");\n       defs.appendChild(flowArrowSmall);\n       const markerForward = document.createElementNS(ns, \"marker\");\n       markerForward.id = \"arrow-forward\";\n       markerForward.setAttribute(\"markerWidth\", \"10\");\n       markerForward.setAttribute(\"markerHeight\", \"10\");\n       markerForward.setAttribute(\"refX\", \"9\");\n       markerForward.setAttribute(\"refY\", \"3\");\n       markerForward.setAttribute(\"orient\", \"auto\");\n       markerForward.setAttribute(\"markerUnits\", \"strokeWidth\");\n       const pathForward = document.createElementNS(ns, \"path\");\n       pathForward.setAttribute(\"d\", \"M0,0 L0,6 L9,3 z\");\n       pathForward.setAttribute(\"fill\", \"context-stroke\");\n       markerForward.appendChild(pathForward);\n       defs.appendChild(markerForward);\n       const markerBackward = document.createElementNS(ns, \"marker\");\n       markerBackward.id = \"arrow-backward\";\n       markerBackward.setAttribute(\"markerWidth\", \"10\");\n       markerBackward.setAttribute(\"markerHeight\", \"10\");\n       markerBackward.setAttribute(\"refX\", \"0\");\n       markerBackward.setAttribute(\"refY\", \"3\");\n       markerBackward.setAttribute(\"orient\", \"auto\");\n       markerBackward.setAttribute(\"markerUnits\", \"strokeWidth\");\n       const pathBackward = document.createElementNS(ns, \"path\");\n       pathBackward.setAttribute(\"d\", \"M9,0 L9,6 L0,3 z\");\n       pathBackward.setAttribute(\"fill\", \"context-stroke\");\n       markerBackward.appendChild(pathBackward);\ndefs.appendChild(markerBackward);\nconst wallPattern = document.createElementNS(ns, \"pattern\");\nwallPattern.id = \"wall-hatch\";\nwallPattern.setAttribute(\"patternUnits\", \"userSpaceOnUse\");\nwallPattern.setAttribute(\"width\", \"8\");\nwallPattern.setAttribute(\"height\", \"8\");\nwallPattern.setAttribute(\"patternTransform\", \"rotate(45)\");\nconst wallLine = document.createElementNS(ns, \"line\");\nwallLine.setAttribute(\"x1\", \"0\");\nwallLine.setAttribute(\"y1\", \"0\");\nwallLine.setAttribute(\"x2\", \"0\");\nwallLine.setAttribute(\"y2\", \"8\");\nwallLine.setAttribute(\"stroke\", \"#666\");\nwallLine.setAttribute(\"stroke-width\", \"2\");\nwallPattern.appendChild(wallLine);\ndefs.appendChild(wallPattern);\n\nsvg.appendChild(defs);\n       const boundary = document.createElementNS(ns, \"rect\");\n       boundary.setAttribute(\"x\", CANVAS_PADDING);\n       boundary.setAttribute(\"y\", CANVAS_PADDING);\n       boundary.setAttribute(\"width\", CANVAS_WIDTH - CANVAS_PADDING * 2);\n       boundary.setAttribute(\"height\", CANVAS_HEIGHT - CANVAS_PADDING * 2);\n       boundary.setAttribute(\"fill\", \"none\");\n       boundary.setAttribute(\"stroke\", (PAGE_STATE.canvasBorder || \"#475569\") + \"4D\");\n       boundary.setAttribute(\"stroke-width\", \"20\");\n       boundary.setAttribute(\"stroke-dasharray\", \"10 5\");\n       boundary.setAttribute(\"rx\", \"8\");\n       svg.appendChild(boundary);\n       if (currentView.mode !== \"rack\" && PAGE_STATE.canvasGridEnabled !== false) {\nconst gridGroup = document.createElementNS(ns, \"g\");\n gridGroup.id = \"canvas-grid\";\n const gridSize = PAGE_STATE.canvasGridSize || 50;\n const gridColor = (PAGE_STATE.canvasGrid || \"#475569\") + \"33\";\n const majorGridColor = (PAGE_STATE.canvasGrid || \"#475569\") + \"66\";\n for (let x = CANVAS_PADDING; x <= CANVAS_WIDTH - CANVAS_PADDING; x += gridSize) {\n  const line = document.createElementNS(ns, \"line\");\n  line.setAttribute(\"x1\", x);\n  line.setAttribute(\"y1\", CANVAS_PADDING);\n  line.setAttribute(\"x2\", x);\n  line.setAttribute(\"y2\", CANVAS_HEIGHT - CANVAS_PADDING);\n  line.setAttribute(\"stroke\", (x - CANVAS_PADDING) % (gridSize * 5) === 0 ? majorGridColor : gridColor);\n  line.setAttribute(\"stroke-width\", (x - CANVAS_PADDING) % (gridSize * 5) === 0 ? \"2\" : \"1\");\n  gridGroup.appendChild(line);\n }\n for (let y = CANVAS_PADDING; y <= CANVAS_HEIGHT - CANVAS_PADDING; y += gridSize) {\n  const line = document.createElementNS(ns, \"line\");\n  line.setAttribute(\"x1\", CANVAS_PADDING);\n  line.setAttribute(\"y1\", y);\n  line.setAttribute(\"x2\", CANVAS_WIDTH - CANVAS_PADDING);\n  line.setAttribute(\"y2\", y);\n  line.setAttribute(\"stroke\", (y - CANVAS_PADDING) % (gridSize * 5) === 0 ? majorGridColor : gridColor);\n  line.setAttribute(\"stroke-width\", (y - CANVAS_PADDING) % (gridSize * 5) === 0 ? \"2\" : \"1\");\n  gridGroup.appendChild(line);\n }\n svg.appendChild(gridGroup);\n}\n       if (currentView.mode === \"rack\" && currentView.rackId) {\n        const rackCapacity = getRackCapacity(currentView.rackId);\n      const rackUHeight = getRackUHeight(currentView.rackId);\n        const rackGroup = document.createElementNS(ns, \"g\");\n        rackGroup.id = \"rack-visualization\";\n        const rackFrame = document.createElementNS(ns, \"rect\");\n        rackFrame.setAttribute(\"x\", RACK_START_X - RACK_WIDTH / 2);\n        rackFrame.setAttribute(\"y\", RACK_START_Y);\n        rackFrame.setAttribute(\"width\", RACK_WIDTH);\n      rackFrame.setAttribute(\"height\", rackCapacity * rackUHeight);\n        rackFrame.setAttribute(\"fill\", (PAGE_STATE.rackFrameFill || \"#0f172a\") + \"4D\");\n        rackFrame.setAttribute(\"stroke\", PAGE_STATE.rackFrameStroke || \"#4fd1c5\");\n        rackFrame.setAttribute(\"stroke-width\", \"3\");\n        rackFrame.setAttribute(\"rx\", \"4\");\n        rackGroup.appendChild(rackFrame);\n        for (let u = 0; u <= rackCapacity; u++) {\n const y = RACK_START_Y + u * rackUHeight;\n if (PAGE_STATE.rackGridEnabled !== false) {\n const line = document.createElementNS(ns, \"line\");\n         line.setAttribute(\"x1\", RACK_START_X - RACK_WIDTH / 2);\n         line.setAttribute(\"y1\", y);\n         line.setAttribute(\"x2\", RACK_START_X + RACK_WIDTH / 2);\n         line.setAttribute(\"y2\", y);\n         line.setAttribute(\"stroke\", (PAGE_STATE.rackLineColor || \"#475569\") + \"66\");\n         line.setAttribute(\"stroke-width\", u % 5 === 0 ? \"2\" : \"1\");\n         line.setAttribute(\"stroke-dasharray\", u % 5 === 0 ? \"none\" : \"5,5\");\n         rackGroup.appendChild(line);\n }\n if (u < rackCapacity) {\n          const uNumber = rackCapacity - u;\n          const text = document.createElementNS(ns, \"text\");\n          text.setAttribute(\"x\", RACK_START_X - RACK_WIDTH / 2 - 30);\n          text.setAttribute(\"y\", y + rackUHeight / 2);\n          text.setAttribute(\"text-anchor\", \"middle\");\n          text.setAttribute(\"dominant-baseline\", \"middle\");\n          text.style.fill = PAGE_STATE.rackTextColor || \"#4fd1c5\";\n          text.style.fontSize = \"14px\";\n          text.style.fontWeight = \"bold\";\n          text.textContent = `U${uNumber}`;\n          rackGroup.appendChild(text);\n          const textRight = document.createElementNS(ns, \"text\");\n          textRight.setAttribute(\"x\", RACK_START_X + RACK_WIDTH / 2 + 30);\n          textRight.setAttribute(\"y\", y + rackUHeight / 2);\n          textRight.setAttribute(\"text-anchor\", \"middle\");\n          textRight.setAttribute(\"dominant-baseline\", \"middle\");\n          textRight.style.fill = PAGE_STATE.rackTextColor || \"#4fd1c5\";\n          textRight.style.fontSize = \"14px\";\n          textRight.style.fontWeight = \"bold\";\n          textRight.textContent = `U${uNumber}`;\n          rackGroup.appendChild(textRight);\n         }\n        }\n        svg.appendChild(rackGroup);\n       }\n        if (RECT_DATA && RECT_DATA.list) {\n         RECT_DATA.list.forEach((rect) => {\n          if (currentView.mode === \"rack\") return;\n          if (rect.style === \"filled\") {\n           const g = document.createElementNS(ns, \"g\");\n           g.classList.add(\"rect-group\");\n           g.dataset.rectId = rect.id;\n           const rectCenterX = rect.x + rect.width / 2;\n           const rectCenterY = rect.y + rect.height / 2;\n           const rectRotation = rect.rotation || 0;\n           if (rectRotation !== 0) {\n             g.setAttribute(\"transform\", `rotate(${rectRotation}, ${rectCenterX}, ${rectCenterY})`);\n           }\n           const rectEl = document.createElementNS(ns, \"rect\");\n           rectEl.classList.add(\"rect-shape\");\n           rectEl.setAttribute(\"x\", rect.x);\n           rectEl.setAttribute(\"y\", rect.y);\n           rectEl.setAttribute(\"width\", rect.width);\n           rectEl.setAttribute(\"height\", rect.height);\n           rectEl.style.fill = rect.color;\n           rectEl.style.fillOpacity = \"0.3\";\n           rectEl.style.stroke = rect.borderColor || rect.color;\n           rectEl.style.strokeWidth = (rect.borderWidth !== undefined ? rect.borderWidth : 2) + \"px\";\n           if (rect.lineStyle === \"dashed\") { rectEl.style.strokeDasharray = \"10,5\"; }\nelse if (rect.lineStyle === \"dotted\") { rectEl.style.strokeDasharray = \"2,4\"; }\nelse if (rect.lineStyle === \"wall\") {\n  rectEl.style.fill = rect.color;\n  rectEl.style.fillOpacity = \"0.5\";\n  rectEl.style.stroke = rect.borderColor || rect.color;\n  rectEl.style.strokeWidth = (rect.borderWidth !== undefined ? rect.borderWidth : 2) + \"px\";\n  const hatchGroup = document.createElementNS(ns, \"g\");\n  hatchGroup.classList.add(\"wall-hatch-lines\");\n  hatchGroup.style.pointerEvents = \"none\";\n  const spacing = 12;\n  const hatchColor = rect.borderColor || rect.color;\n  for (let i = -rect.height; i < rect.width + rect.height; i += spacing) {\n    const line = document.createElementNS(ns, \"line\");\n    line.setAttribute(\"x1\", rect.x + i);\n    line.setAttribute(\"y1\", rect.y);\n    line.setAttribute(\"x2\", rect.x + i - rect.height);\n    line.setAttribute(\"y2\", rect.y + rect.height);\n    line.style.stroke = hatchColor;\n    line.style.strokeWidth = \"2\";\n    hatchGroup.appendChild(line);\n  }\n  const clipId = \"clip-\" + rect.id;\n  const clipPath = document.createElementNS(ns, \"clipPath\");\n  clipPath.id = clipId;\n  const clipRect = document.createElementNS(ns, \"rect\");\n  clipRect.setAttribute(\"x\", rect.x);\n  clipRect.setAttribute(\"y\", rect.y);\n  clipRect.setAttribute(\"width\", rect.width);\n  clipRect.setAttribute(\"height\", rect.height);\n  clipPath.appendChild(clipRect);\n  defs.appendChild(clipPath);\n  hatchGroup.setAttribute(\"clip-path\", \"url(#\" + clipId + \")\");\n  g.appendChild(hatchGroup);\n}\n           else { rectEl.style.strokeDasharray = \"none\"; }\n           rectEl.style.cursor = \"move\";\n          rectEl.addEventListener(\"click\", (e) => {\n\t\t  if (isViewOnly()) return;\n             if (rectDrawMode) return;\n             e.stopPropagation();\n             currentRectId = rect.id;\n             selectTheRect(rect.id);\n           });\n           rectEl.addEventListener(\"contextmenu\", (e) => {\n\t\t   if (isViewOnly()) return;\n             e.preventDefault();\n             e.stopPropagation();\n             if (selectedRects.has(rect.id)) {\n               selectedRects.delete(rect.id);\n             } else {\n               selectedRects.add(rect.id);\n             }\n             updateAllSelections();\n           });\n           let rectLastTap = 0;\n           rectEl.addEventListener(\"touchend\", (e) => {\n             const now = Date.now();\n             if (now - rectLastTap < 300) {\n               e.preventDefault();\n               if (selectedRects.has(rect.id)) {\n                 selectedRects.delete(rect.id);\n               } else {\n                 selectedRects.add(rect.id);\n               }\n               updateAllSelections();\n               if (navigator.vibrate) navigator.vibrate(50);\n               rectLastTap = 0;\n             } else {\n               rectLastTap = now;\n             }\n           }, { passive: false });\n           const deleteBtn = document.createElementNS(ns, \"g\");\n           deleteBtn.classList.add(\"rect-delete-btn\");\n           deleteBtn.style.cursor = \"pointer\";\n           deleteBtn.style.display = rectDrawMode ? \"block\" : \"none\";\n           const deleteBg = document.createElementNS(ns, \"circle\");\n           deleteBg.setAttribute(\"cx\", rect.x + rect.width - 10);\n           deleteBg.setAttribute(\"cy\", rect.y + 10);\n           deleteBg.setAttribute(\"r\", 12);\n           deleteBg.style.fill = \"#f56565\";\n           deleteBg.style.stroke = \"white\";\n           deleteBg.style.strokeWidth = \"2\";\n           const deleteX = document.createElementNS(ns, \"text\");\n           deleteX.setAttribute(\"x\", rect.x + rect.width - 10);\n           deleteX.setAttribute(\"y\", rect.y + 10);\n           deleteX.setAttribute(\"text-anchor\", \"middle\");\n           deleteX.setAttribute(\"dominant-baseline\", \"middle\");\n           deleteX.style.fill = \"white\";\n           deleteX.style.fontSize = \"16px\";\n           deleteX.style.fontWeight = \"bold\";\n           deleteX.style.pointerEvents = \"none\";\n           deleteX.textContent = \"×\";\n           deleteBtn.appendChild(deleteBg);\n           deleteBtn.appendChild(deleteX);\n           deleteBtn.addEventListener(\"click\", (e) => {\n            e.stopPropagation();\n            e.preventDefault();\n            deleteRectangle(rect.id);\n           });\n           deleteBtn.addEventListener(\"touchend\", (e) => {\n      e.stopPropagation();\n           e.preventDefault();\n           deleteRectangle(rect.id);\n           });\n           let isDragging = false;\n           let dragStartX, dragStartY;\n           let rectStartX, rectStartY;\n         rectEl.addEventListener(\"mousedown\", (e) => {\n\t\t if (isViewOnly()) return;\n      if (rectDrawMode) return;\n      e.preventDefault();\n      e.stopPropagation();\n      isDragging = true;\n      dragStartX = e.clientX;\n      dragStartY = e.clientY;\n      rectStartX = rect.x;\n      rectStartY = rect.y;\n      rectEl.style.cursor = \"grabbing\";\n      if (selectedRects.has(rect.id)) {\n      initialPositions = {};\n      selectedNodes.forEach(nodeId => {\n      const nodePos = savedPositions[nodeId];\n      if (nodePos) initialPositions[nodeId] = { x: nodePos.x, y: nodePos.y };\n      });\n      selectedRects.forEach(rectId => {\n      const r = RECT_DATA.list.find(x => x.id === rectId);\n      if (r) { r._dragStartX = r.x; r._dragStartY = r.y; }\n      });\n      selectedTexts.forEach(textId => {\n      const t = TEXT_DATA.list.find(x => x.id === textId);\n      if (t) { t._dragStartX = t.x; t._dragStartY = t.y; }\n      });\n      selectedEdges.forEach(edgeId => {\n      const ed = EDGE_DATA.list.find(x => x.id === edgeId);\n      if (ed && ed.points) { ed._dragStartPoints = ed.points.map(p => ({x: p.x, y: p.y})); }\n      });\n      }\n      });\n           const moveHandler = (e) => {\n      if (!isDragging || rectDrawMode) return;\n      const svgEl = svg;\n      const pt1 = svgEl.createSVGPoint();\n      pt1.x = dragStartX;\n      pt1.y = dragStartY;\n      const svgP1 = pt1.matrixTransform(svgEl.getScreenCTM().inverse());\n      const pt2 = svgEl.createSVGPoint();\n      pt2.x = e.clientX;\n      pt2.y = e.clientY;\n      const svgP2 = pt2.matrixTransform(svgEl.getScreenCTM().inverse());\n      const dx = svgP2.x - svgP1.x;\n      const dy = svgP2.y - svgP1.y;\n      rect.x = rectStartX + dx;\n      rect.y = rectStartY + dy;\n      if (selectedRects.has(rect.id)) {\n      selectedNodes.forEach(nodeId => {\n      if (initialPositions[nodeId]) {\n       const newX = initialPositions[nodeId].x + dx;\n       const newY = initialPositions[nodeId].y + dy;\n       savedPositions[nodeId] = { x: newX, y: newY };\n      }\n      });\n      selectedRects.forEach(rectId => {\n      if (rectId === rect.id) return;\n      const r = RECT_DATA.list.find(x => x.id === rectId);\n      if (r && r._dragStartX !== undefined) { r.x = r._dragStartX + dx; r.y = r._dragStartY + dy; }\n      });\n      selectedTexts.forEach(textId => {\n      const t = TEXT_DATA.list.find(x => x.id === textId);\n      if (t && t._dragStartX !== undefined) { t.x = t._dragStartX + dx; t.y = t._dragStartY + dy; }\n      });\n      selectedEdges.forEach(edgeId => {\n      const ed = EDGE_DATA.list.find(x => x.id === edgeId);\n      if (ed && ed._dragStartPoints) { ed.points.forEach((p, i) => { p.x = ed._dragStartPoints[i].x + dx; p.y = ed._dragStartPoints[i].y + dy; }); }\n      });\n      }\n      forgeTheTopology();\n      };\n           const upHandler = () => {\n            if (isDragging) {\n             isDragging = false;\n             rectEl.style.cursor = \"move\";\n            }\n           };\n           document.addEventListener(\"mousemove\", moveHandler);\n           document.addEventListener(\"mouseup\", upHandler);\n           let touchStartX, touchStartY;\n           let touchRectStartX, touchRectStartY;\n           rectEl.addEventListener(\"touchstart\", (e) => {\n\t\t   if (isViewOnly()) return;\n            if (rectDrawMode) return;\n            e.preventDefault();\n            e.stopPropagation();\n            isDragging = true;\n            const touch = e.touches[0];\n            touchStartX = touch.clientX;\n            touchStartY = touch.clientY;\n            touchRectStartX = rect.x;\n            touchRectStartY = rect.y;\n            rectEl.style.cursor = \"grabbing\";\n           }, { passive: false });\n           rectEl.addEventListener(\"touchmove\", (e) => {\n\t\t    if (isViewOnly()) return;\n            if (!isDragging || rectDrawMode) return;\n            if (!e.touches[0]) return;\n            e.preventDefault();\n            e.stopPropagation();\n            const svgEl = svg;\n            const pt1 = svgEl.createSVGPoint();\n            pt1.x = touchStartX;\n            pt1.y = touchStartY;\n            const svgP1 = pt1.matrixTransform(svgEl.getScreenCTM().inverse());\n            const touch = e.touches[0];\n            const pt2 = svgEl.createSVGPoint();\n            pt2.x = touch.clientX;\n            pt2.y = touch.clientY;\n            const svgP2 = pt2.matrixTransform(svgEl.getScreenCTM().inverse());\n            const dx = svgP2.x - svgP1.x;\n            const dy = svgP2.y - svgP1.y;\n            rect.x = touchRectStartX + dx;\n            rect.y = touchRectStartY + dy;\n            forgeTheTopology();\n           }, { passive: false });\n           rectEl.addEventListener(\"touchend\", (e) => {\n            if (isDragging) {\n             e.preventDefault();\n             isDragging = false;\n             rectEl.style.cursor = \"move\";\n            }\n           }, { passive: false });\n            if (currentRectId === rect.id) {\n      const corners = [\n      { cx: rect.x, cy: rect.y, cursor: 'nwse-resize', dx: -1, dy: -1 },\n      { cx: rect.x + rect.width, cy: rect.y, cursor: 'nesw-resize', dx: 1, dy: -1 },\n      { cx: rect.x, cy: rect.y + rect.height, cursor: 'nesw-resize', dx: -1, dy: 1 },\n      { cx: rect.x + rect.width, cy: rect.y + rect.height, cursor: 'nwse-resize', dx: 1, dy: 1 }\n      ];\n      corners.forEach((corner, idx) => {\n      const handle = document.createElementNS(ns, \"circle\");\n      handle.setAttribute(\"cx\", corner.cx);\n      handle.setAttribute(\"cy\", corner.cy);\n      const borderW = rect.borderWidth !== undefined ? rect.borderWidth : 2;\n      const handleSize = Math.max(PAGE_STATE.selectionHandleSize || 8, borderW + 4);\n      handle.setAttribute(\"r\", handleSize);\n      handle.style.fill = PAGE_STATE.selectionHandle || \"#f59e0b\";\n      handle.style.stroke = \"#fff\";\n      handle.style.strokeWidth = \"2\";\n      handle.style.cursor = corner.cursor;\n      handle.addEventListener(\"mousedown\", (e) => {\n\t  if (isViewOnly()) return;\n      e.preventDefault();\n      e.stopPropagation();\n\t  pushUndo(\"resize zone\");\n      let dragging = true;\n      const startX = e.clientX, startY = e.clientY;\n      const origX = rect.x, origY = rect.y, origW = rect.width, origH = rect.height;\n      const moveHandler = (ev) => {\n        if (!dragging) return;\n        const pt1 = svg.createSVGPoint(); pt1.x = startX; pt1.y = startY;\n        const pt2 = svg.createSVGPoint(); pt2.x = ev.clientX; pt2.y = ev.clientY;\n        const svgP1 = pt1.matrixTransform(svg.getScreenCTM().inverse());\n        const svgP2 = pt2.matrixTransform(svg.getScreenCTM().inverse());\n        const dx = svgP2.x - svgP1.x, dy = svgP2.y - svgP1.y;\n        if (corner.dx < 0) { rect.x = origX + dx; rect.width = origW - dx; }\n        else { rect.width = origW + dx; }\n        if (corner.dy < 0) { rect.y = origY + dy; rect.height = origH - dy; }\n        else { rect.height = origH + dy; }\n        if (rect.width < 20) rect.width = 20;\n        if (rect.height < 20) rect.height = 20;\n        forgeTheTopology();\n      };\n      const upHandler = () => { dragging = false; document.removeEventListener(\"mousemove\", moveHandler); document.removeEventListener(\"mouseup\", upHandler); };\n      document.addEventListener(\"mousemove\", moveHandler);\n      document.addEventListener(\"mouseup\", upHandler);\n      });\n      g.appendChild(handle);\n      });\n      }\n          if (rect.groupId) {\n      const groupIndicator = document.createElementNS(ns, \"rect\");\n      groupIndicator.setAttribute(\"x\", rect.x - 4);\n      groupIndicator.setAttribute(\"y\", rect.y - 4);\n      groupIndicator.setAttribute(\"width\", rect.width + 8);\n      groupIndicator.setAttribute(\"height\", rect.height + 8);\n      groupIndicator.style.fill = \"none\";\n      groupIndicator.style.stroke = \"#4fd1c5\";\n      groupIndicator.style.strokeWidth = \"3\";\n      groupIndicator.style.strokeDasharray = \"5,5\";\n      groupIndicator.style.pointerEvents = \"none\";\n      g.insertBefore(groupIndicator, g.firstChild);\n      }\n      g.appendChild(rectEl);\n      g.appendChild(deleteBtn);\n      svg.appendChild(g);\n          }\n         });\n        }\n       const centerX = CANVAS_WIDTH / 2;\n       const centerY = CANVAS_HEIGHT / 2;\n       let positions = {};\n              Object.keys(NODE_DATA).forEach((id) => {\n        if (currentView.mode === \"rack\") {\n         const node = NODE_DATA[id];\n         if (!node || node.assignedRack !== currentView.rackId) {\n          return;\n         }\n        }\n        positions[id] = savedPositions[id] || {\n         x: centerX,\n         y: centerY\n        };\n       });\n       if (Object.keys(savedPositions).length === 0) {\n        const nodeIds = Object.keys(NODE_DATA).filter(id => {\n         if (currentView.mode === \"rack\") {\n          const node = NODE_DATA[id];\n          return node && node.assignedRack === currentView.rackId;\n         }\n         return true;\n        });\n        const baseY = centerY - 300;\n        if (nodeIds.length > 0) {\n         positions[nodeIds[0]] = {\n          x: centerX,\n          y: baseY\n         };\n         const remaining = nodeIds.slice(1);\n         const radius = 350;\n         const startAngle = Math.PI * 0.3;\n         const endAngle = Math.PI * 0.7;\n         remaining.forEach((id, i) => {\n          const angle = startAngle + (endAngle - startAngle) * (i / Math.max(1, remaining.length - 1));\n          positions[id] = {\n           x: centerX + Math.cos(angle) * radius * (i % 2 === 0 ? 1 : 1.3),\n           y: baseY + 200 + Math.sin(angle) * radius * 0.8 + i * 80,\n          };\n         });\n        }\n       }\n       Object.keys(positions).forEach((id) => {\n        let pos = savedPositions[id] || positions[id];\n        const nodeSize = savedSizes[id] || 55;\n        pos.x = Math.max(nodeSize + CANVAS_PADDING, Math.min(CANVAS_WIDTH - nodeSize - CANVAS_PADDING, pos.x));\n        pos.y = Math.max(nodeSize + CANVAS_PADDING, Math.min(CANVAS_HEIGHT - nodeSize - CANVAS_PADDING, pos.y));\n        positions[id] = {\n         x: pos.x,\n         y: pos.y\n        };\n        savedPositions[id] = {\n         x: pos.x,\n         y: pos.y\n        };\n       });\n       const edgePairCount = {};\n       const edgePairIndex = {};\n       EDGE_DATA.list.forEach((edge) => {\n        if (edge.type === \"custom\") return;\n        const key = [edge.from, edge.to].sort().join(\"||\");\n        edgePairCount[key] = (edgePairCount[key] || 0) + 1;\n       });\n       EDGE_DATA.list.forEach((edge) => {\n        if (edge.type === \"custom\") return;\n        const key = [edge.from, edge.to].sort().join(\"||\");\n        if (!edgePairIndex[key]) edgePairIndex[key] = 0;\n        edge._pairIndex = edgePairIndex[key];\n        edge._pairTotal = edgePairCount[key];\n        edgePairIndex[key]++;\n       });\n       \n       const orthoGaps = (function() {\n         const segments = [];\n         const GAP_SIZE = 12;\n         EDGE_DATA.list.forEach((edge, edgeIndex) => {\n           if ((edge.routing || PAGE_STATE.defaultEdgeRouting || \"curved\") !== \"orthogonal\") return;\n           if (edge.type === \"custom\") return;\n           const p1 = positions[edge.from];\n           const p2 = positions[edge.to];\n           if (!p1 || !p2) return;\n           const dx = p2.x - p1.x;\n           const dy = p2.y - p1.y;\n           const pairIndex = edge._pairIndex || 0;\n           const pairTotal = edge._pairTotal || 1;\n           const offset = (pairIndex - (pairTotal - 1) / 2) * 40;\n           if (Math.abs(dx) > Math.abs(dy)) {\n             const midX = p1.x + dx / 2 + offset;\n             segments.push({ edgeId: edge.id, x1: p1.x, y1: p1.y, x2: midX, y2: p1.y, idx: edgeIndex });\n             segments.push({ edgeId: edge.id, x1: midX, y1: p1.y, x2: midX, y2: p2.y, idx: edgeIndex });\n             segments.push({ edgeId: edge.id, x1: midX, y1: p2.y, x2: p2.x, y2: p2.y, idx: edgeIndex });\n           } else {\n             const midY = p1.y + dy / 2 + offset;\n             segments.push({ edgeId: edge.id, x1: p1.x, y1: p1.y, x2: p1.x, y2: midY, idx: edgeIndex });\n             segments.push({ edgeId: edge.id, x1: p1.x, y1: midY, x2: p2.x, y2: midY, idx: edgeIndex });\n             segments.push({ edgeId: edge.id, x1: p2.x, y1: midY, x2: p2.x, y2: p2.y, idx: edgeIndex });\n           }\n         });\n         const gaps = {};\n         for (let i = 0; i < segments.length; i++) {\n           for (let j = i + 1; j < segments.length; j++) {\n             const s1 = segments[i];\n             const s2 = segments[j];\n             if (s1.edgeId === s2.edgeId) continue;\n             const s1Horiz = Math.abs(s1.y1 - s1.y2) < 1;\n             const s2Horiz = Math.abs(s2.y1 - s2.y2) < 1;\n             if (s1Horiz === s2Horiz) continue;\n             const horiz = s1Horiz ? s1 : s2;\n             const vert = s1Horiz ? s2 : s1;\n             const hY = horiz.y1;\n             const vX = vert.x1;\n             const hMinX = Math.min(horiz.x1, horiz.x2);\n             const hMaxX = Math.max(horiz.x1, horiz.x2);\n             const vMinY = Math.min(vert.y1, vert.y2);\n             const vMaxY = Math.max(vert.y1, vert.y2);\n             if (vX > hMinX && vX < hMaxX && hY > vMinY && hY < vMaxY) {\n               const gapEdge = s1.idx > s2.idx ? s1.edgeId : s2.edgeId;\n               if (!gaps[gapEdge]) gaps[gapEdge] = [];\n               gaps[gapEdge].push({ x: vX, y: hY });\n             }\n           }\n         }\n         return gaps;\n       })();\n\n       EDGE_DATA.list.forEach((edge) => {\n        if (edge.type === \"custom\" && Array.isArray(edge.points) && edge.points.length >= 2) {\n         const poly = document.createElementNS(ns, \"polyline\");\n         poly.classList.add(\"edge\");\n         poly.dataset.edgeId = edge.id;\n         poly.style.stroke = edge.color || PAGE_STATE.defaultEdge || \"#475569\";\n         poly.style.strokeWidth = edge.width || 4;\n         poly.setAttribute(\"fill\", \"none\");\n         const lineStyle = edge.lineStyle || \"solid\";\nif (lineStyle === \"dashed\") {\n poly.style.strokeDasharray = \"10,5\";\n} else if (lineStyle === \"dotted\") {\n poly.style.strokeDasharray = \"2,4\";\n} else if (lineStyle === \"wall\") {\n poly.style.stroke = \"url(#wall-hatch)\";\n poly.style.strokeWidth = (edge.width || 4) * 3;\n poly.style.strokeDasharray = \"none\";\n} else {\n poly.style.strokeDasharray = \"none\";\n}\n         const direction = edge.direction || \"none\";\n         if (direction === \"forward\") {\n          poly.setAttribute(\"marker-end\", \"url(#arrow-forward)\");\n         } else if (direction === \"backward\") {\n          poly.setAttribute(\"marker-start\", \"url(#arrow-backward)\");\n         } else if (direction === \"both\") {\n          poly.setAttribute(\"marker-start\", \"url(#arrow-backward)\");\n          poly.setAttribute(\"marker-end\", \"url(#arrow-forward)\");\n         }\n         const ptsStr = edge.points.map((p) => `${p.x},${p.y}`).join(\" \");\n         poly.setAttribute(\"points\", ptsStr);\n         const animDir = PAGE_STATE.animationDirection || \"all\";\n         const shouldAnimate = (PAGE_STATE.animateConnections && edge.animate !== false || edge.animate === true) && direction !== \"none\" && edge.points.length >= 2 && (animDir === \"all\" || animDir === direction);\n         if (shouldAnimate) {\n          poly.style.opacity = \"0.25\";\n          const polyPathD = \"M \" + edge.points.map(p => `${p.x} ${p.y}`).join(\" L \");\n          const edgeStyle = edge.animationStyle || PAGE_STATE.animationStyle || \"arrows\";\n          const arrowId = edgeStyle === \"dots\" ? \"#flow-arrow-small\" : \"#flow-arrow-big\";\n          const arrowColor = edge.color || PAGE_STATE.defaultEdge || \"#475569\";\n          const arrowCount = 3;\n          const animDuration = edge.animationSpeed ? parseFloat(edge.animationSpeed) : (PAGE_STATE.animationSpeed || 1.5);\n          if (direction === \"forward\" || direction === \"both\") {\n           for (let i = 0; i < arrowCount; i++) {\n            const arrow = document.createElementNS(ns, \"use\");\n            arrow.setAttribute(\"href\", arrowId);\n            arrow.style.fill = arrowColor;\n            arrow.style.offsetPath = `path('${polyPathD}')`;\n            arrow.style.animationDuration = animDuration + \"s\";\n            arrow.style.animationDelay = (i * (animDuration / arrowCount)) + \"s\";\n            arrow.classList.add(\"edge-arrow-forward\");\n            svg.appendChild(arrow);\n           }\n          }\n          if (direction === \"backward\" || direction === \"both\") {\n           for (let i = 0; i < arrowCount; i++) {\n            const arrow = document.createElementNS(ns, \"use\");\n            arrow.setAttribute(\"href\", arrowId);\n            arrow.style.fill = arrowColor;\n            arrow.style.offsetPath = `path('${polyPathD}')`;\n            arrow.style.animationDuration = animDuration + \"s\";\n            arrow.style.animationDelay = ((i * (animDuration / arrowCount)) + (direction === \"both\" ? animDuration / (arrowCount * 2) : 0)) + \"s\";\n            arrow.classList.add(\"edge-arrow-backward\");\n            svg.appendChild(arrow);\n           }\n          }\n         }\n         const polyHit = document.createElementNS(ns, \"polyline\");\n         polyHit.setAttribute(\"points\", ptsStr);\n         polyHit.style.fill = \"none\";\n         polyHit.style.stroke = \"transparent\";\n         polyHit.style.strokeWidth = \"20\";\n         polyHit.style.cursor = \"pointer\";\n         polyHit.dataset.edgeId = edge.id;\n         polyHit.addEventListener(\"click\", (e) => {\n          e.stopPropagation();\n          selectTheConnection(edge.id);\n         });\n         let edgeTouchStart = null;\n         let edgeTouchMoved = false;\n         polyHit.addEventListener(\"touchstart\", (e) => {\n          edgeTouchStart = Date.now();\n          edgeTouchMoved = false;\n          if (e.touches[0]) {\n           edgeTouchStartX = e.touches[0].clientX;\n           edgeTouchStartY = e.touches[0].clientY;\n          }\n         }, {\n          passive: false\n         });\n         let edgeTouchStartX = 0, edgeTouchStartY = 0;\n         polyHit.addEventListener(\"touchmove\", (e) => {\n          if (e.touches[0]) {\n           const dx = Math.abs(e.touches[0].clientX - edgeTouchStartX);\n           const dy = Math.abs(e.touches[0].clientY - edgeTouchStartY);\n           if (dx > 10 || dy > 10) edgeTouchMoved = true;\n          }\n         }, {\n          passive: false\n         });\n         polyHit.addEventListener(\"touchend\", (e) => {\n          if (edgeTouchStart && !edgeTouchMoved && Date.now() - edgeTouchStart < 400) {\n           e.stopPropagation();\n           e.preventDefault();\n           selectTheConnection(edge.id);\n          }\n          edgeTouchStart = null;\n          edgeTouchMoved = false;\n         }, {\n          passive: false\n         });\n         poly.addEventListener(\"click\", (e) => {\n          e.stopPropagation();\n          selectTheConnection(edge.id);\n         });\n         polyHit.addEventListener(\"contextmenu\", (e) => {\n          e.preventDefault();\n          e.stopPropagation();\n          if (selectedEdges.has(edge.id)) {\n           selectedEdges.delete(edge.id);\n          } else {\n           selectedEdges.add(edge.id);\n          }\n          updateAllSelections();\n         });\n         let customEdgeLastTap = 0;\n         polyHit.addEventListener(\"touchend\", (e) => {\n          const now = Date.now();\n          if (now - customEdgeLastTap < 300) {\n           e.preventDefault();\n           if (selectedEdges.has(edge.id)) {\n            selectedEdges.delete(edge.id);\n           } else {\n            selectedEdges.add(edge.id);\n           }\n           updateAllSelections();\n           if (navigator.vibrate) navigator.vibrate(50);\n           customEdgeLastTap = 0;\n          } else {\n           customEdgeLastTap = now;\n          }\n         }, { passive: false });\n         poly.addEventListener(\"contextmenu\", (e) => {\n          e.preventDefault();\n          e.stopPropagation();\n          if (selectedEdges.has(edge.id)) {\n           selectedEdges.delete(edge.id);\n          } else {\n           selectedEdges.add(edge.id);\n          }\n          updateAllSelections();\n         });\n         let edgeLastTap = 0;\n         poly.addEventListener(\"touchend\", (e) => {\n          const now = Date.now();\n          if (now - edgeLastTap < 300) {\n           e.preventDefault();\n           if (selectedEdges.has(edge.id)) {\n            selectedEdges.delete(edge.id);\n           } else {\n            selectedEdges.add(edge.id);\n           }\n           updateAllSelections();\n           if (navigator.vibrate) navigator.vibrate(50);\n           edgeLastTap = 0;\n          } else {\n           edgeLastTap = now;\n          }\n         });\n         if (currentView.mode === \"rack\") {\n          return;\n         }\n         if (edge.groupId) {\n      const bounds = edge.points.reduce((acc, p) => ({ minX: Math.min(acc.minX, p.x), minY: Math.min(acc.minY, p.y), maxX: Math.max(acc.maxX, p.x), maxY: Math.max(acc.maxY, p.y) }), { minX: Infinity, minY: Infinity, maxX: -Infinity, maxY: -Infinity });\n      const groupIndicator = document.createElementNS(ns, \"rect\");\n      groupIndicator.setAttribute(\"x\", bounds.minX - 8);\n      groupIndicator.setAttribute(\"y\", bounds.minY - 8);\n      groupIndicator.setAttribute(\"width\", bounds.maxX - bounds.minX + 16);\n      groupIndicator.setAttribute(\"height\", bounds.maxY - bounds.minY + 16);\n      groupIndicator.setAttribute(\"rx\", \"4\");\n      groupIndicator.style.fill = \"none\";\n      groupIndicator.style.stroke = \"#4fd1c5\";\n      groupIndicator.style.strokeWidth = \"3\";\n      groupIndicator.style.strokeDasharray = \"5,5\";\n      groupIndicator.style.pointerEvents = \"none\";\n      svg.appendChild(groupIndicator);\n      }\n         let lineDragging = false;\n      let lineDragStartX, lineDragStartY;\n      let linePointsStart = [];\n      polyHit.addEventListener(\"mousedown\", (e) => {\n\t  if (isViewOnly()) return;\n      if (freeDrawMode) return;\n      e.preventDefault();\n      e.stopPropagation();\n      lineDragging = true;\n      lineDragStartX = e.clientX;\n      lineDragStartY = e.clientY;\n      linePointsStart = edge.points.map(p => ({x: p.x, y: p.y}));\n      if (selectedEdges.has(edge.id)) {\n      initialPositions = {};\n      selectedNodes.forEach(nodeId => {\n      const nodePos = savedPositions[nodeId];\n      if (nodePos) initialPositions[nodeId] = { x: nodePos.x, y: nodePos.y };\n      });\n      selectedRects.forEach(rectId => {\n      const r = RECT_DATA.list.find(x => x.id === rectId);\n      if (r) { r._dragStartX = r.x; r._dragStartY = r.y; }\n      });\n      selectedTexts.forEach(textId => {\n      const t = TEXT_DATA.list.find(x => x.id === textId);\n      if (t) { t._dragStartX = t.x; t._dragStartY = t.y; }\n      });\n      selectedEdges.forEach(edgeId => {\n      const ed = EDGE_DATA.list.find(x => x.id === edgeId);\n      if (ed && ed.points) { ed._dragStartPoints = ed.points.map(p => ({x: p.x, y: p.y})); }\n      });\n      }\n      });\n      const lineMoveHandler = (e) => {\n      if (!lineDragging || freeDrawMode) return;\n      const svgEl = svg;\n      const pt1 = svgEl.createSVGPoint();\n      pt1.x = lineDragStartX;\n      pt1.y = lineDragStartY;\n      const svgP1 = pt1.matrixTransform(svgEl.getScreenCTM().inverse());\n      const pt2 = svgEl.createSVGPoint();\n      pt2.x = e.clientX;\n      pt2.y = e.clientY;\n      const svgP2 = pt2.matrixTransform(svgEl.getScreenCTM().inverse());\n      const dx = svgP2.x - svgP1.x;\n      const dy = svgP2.y - svgP1.y;\n      edge.points.forEach((p, i) => { p.x = linePointsStart[i].x + dx; p.y = linePointsStart[i].y + dy; });\n      if (selectedEdges.has(edge.id)) {\n      selectedNodes.forEach(nodeId => {\n      if (initialPositions[nodeId]) {\n       savedPositions[nodeId] = { x: initialPositions[nodeId].x + dx, y: initialPositions[nodeId].y + dy };\n      }\n      });\n      selectedRects.forEach(rectId => {\n      const r = RECT_DATA.list.find(x => x.id === rectId);\n      if (r && r._dragStartX !== undefined) { r.x = r._dragStartX + dx; r.y = r._dragStartY + dy; }\n      });\n      selectedTexts.forEach(textId => {\n      const t = TEXT_DATA.list.find(x => x.id === textId);\n      if (t && t._dragStartX !== undefined) { t.x = t._dragStartX + dx; t.y = t._dragStartY + dy; }\n      });\n      selectedEdges.forEach(edgeId => {\n      if (edgeId === edge.id) return;\n      const ed = EDGE_DATA.list.find(x => x.id === edgeId);\n      if (ed && ed._dragStartPoints) { ed.points.forEach((p, i) => { p.x = ed._dragStartPoints[i].x + dx; p.y = ed._dragStartPoints[i].y + dy; }); }\n      });\n      }\n      forgeTheTopology();\n      };\n      const lineUpHandler = () => {\n        lineDragging = false;\n        document.removeEventListener(\"mousemove\", lineMoveHandler);\n        document.removeEventListener(\"mouseup\", lineUpHandler);\n      };\n      document.addEventListener(\"mousemove\", lineMoveHandler);\n      document.addEventListener(\"mouseup\", lineUpHandler);\n      svg.appendChild(poly);\n      svg.appendChild(polyHit);\n         if (currentEdgeId === edge.id) {\n          edge.points.forEach((p, idx) => {\n           const c = document.createElementNS(ns, \"circle\");\n           c.classList.add(\"edge-edit-point\");\n           c.setAttribute(\"cx\", p.x);\n           c.setAttribute(\"cy\", p.y);\n      c.setAttribute(\"r\", PAGE_STATE.selectionHandleSize || 8);\n           c.style.fill = PAGE_STATE.selectionHandle || \"#f59e0b\";\n           c.style.stroke = \"#fff\";\n           c.style.strokeWidth = \"2\";\n           c.style.cursor = \"move\";\n           c.dataset.edgeId = edge.id;\n           c.dataset.pointIndex = String(idx);\n           c.addEventListener(\"mousedown\", (e) => {\n\t\t   if (isViewOnly()) return;\n            e.preventDefault();\n            e.stopPropagation();\n            pushUndo(\"edit edge point\");\n            let dragging = true;\n            const svgEl = svg;\n            const moveHandler = (ev) => {\n             if (!dragging) return;\n             const pt = svgEl.createSVGPoint();\n             pt.x = ev.clientX;\n             pt.y = ev.clientY;\n             const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n             edge.points[idx].x = svgP.x;\n             edge.points[idx].y = svgP.y;\n             forgeTheTopology();\n            };\n            const upHandler = () => {\n             dragging = false;\n             document.removeEventListener(\"mousemove\", moveHandler);\n             document.removeEventListener(\"mouseup\", upHandler);\n            };\n            document.addEventListener(\"mousemove\", moveHandler);\n            document.addEventListener(\"mouseup\", upHandler);\n           });\n           c.addEventListener(\"touchstart\", (e) => {\n\t\t   if (isViewOnly()) return;\n            e.preventDefault();\n            e.stopPropagation();\n            pushUndo(\"edit edge point\");\n            let dragging = true;\n            const svgEl = svg;\n            const touchMoveHandler = (ev) => {\n             if (!dragging || !ev.touches[0]) return;\n             const pt = svgEl.createSVGPoint();\n             pt.x = ev.touches[0].clientX;\n             pt.y = ev.touches[0].clientY;\n             const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n             edge.points[idx].x = svgP.x;\n             edge.points[idx].y = svgP.y;\n             forgeTheTopology();\n            };\n            const touchUpHandler = () => {\n             dragging = false;\n             document.removeEventListener(\"touchmove\", touchMoveHandler);\n             document.removeEventListener(\"touchend\", touchUpHandler);\n            };\n            document.addEventListener(\"touchmove\", touchMoveHandler);\n            document.addEventListener(\"touchend\", touchUpHandler);\n           }, { passive: false });\n           svg.appendChild(c);\n          });\n         }\n         return;\n        }\n        const p1 = positions[edge.from];\n        const p2 = positions[edge.to];\n        if (!p1 || !p2) return;\n        const pairTotal = edge._pairTotal || 1;\n        const pairIndex = edge._pairIndex || 0;\n        const routing = edge.routing || \"curved\";\n        let pathD;\n        if (routing === \"straight\") {\n         pathD = `M ${p1.x} ${p1.y} L ${p2.x} ${p2.y}`;\n        } else if (routing === \"orthogonal\") {\n         const dx = p2.x - p1.x;\n         const dy = p2.y - p1.y;\n         const offset = (pairIndex - (pairTotal - 1) / 2) * 40;\n         const GAP = 10;\n         const edgeGaps = orthoGaps[edge.id] || [];\n         \n         if (Math.abs(dx) > Math.abs(dy)) {\n          const midX = p1.x + dx / 2 + offset;\n          const segs = [\n           { x1: p1.x, y1: p1.y, x2: midX, y2: p1.y },\n           { x1: midX, y1: p1.y, x2: midX, y2: p2.y },\n           { x1: midX, y1: p2.y, x2: p2.x, y2: p2.y }\n          ];\n          pathD = `M ${p1.x} ${p1.y}`;\n          segs.forEach(seg => {\n           const isHoriz = Math.abs(seg.y1 - seg.y2) < 1;\n           const segGaps = edgeGaps.filter(g => {\n            if (isHoriz) {\n             const minX = Math.min(seg.x1, seg.x2), maxX = Math.max(seg.x1, seg.x2);\n             return Math.abs(g.y - seg.y1) < 1 && g.x > minX + GAP && g.x < maxX - GAP;\n            } else {\n             const minY = Math.min(seg.y1, seg.y2), maxY = Math.max(seg.y1, seg.y2);\n             return Math.abs(g.x - seg.x1) < 1 && g.y > minY + GAP && g.y < maxY - GAP;\n            }\n           }).sort((a, b) => isHoriz ? (seg.x2 > seg.x1 ? a.x - b.x : b.x - a.x) : (seg.y2 > seg.y1 ? a.y - b.y : b.y - a.y));\n           let cx = seg.x1, cy = seg.y1;\n           segGaps.forEach(g => {\n            if (isHoriz) {\n             const gx = g.x;\n             pathD += ` L ${gx - GAP} ${cy} M ${gx + GAP} ${cy}`;\n             cx = gx + GAP;\n            } else {\n             const gy = g.y;\n             pathD += ` L ${cx} ${gy - GAP} M ${cx} ${gy + GAP}`;\n             cy = gy + GAP;\n            }\n           });\n           pathD += ` L ${seg.x2} ${seg.y2}`;\n          });\n         } else {\n          const midY = p1.y + dy / 2 + offset;\n          const segs = [\n           { x1: p1.x, y1: p1.y, x2: p1.x, y2: midY },\n           { x1: p1.x, y1: midY, x2: p2.x, y2: midY },\n           { x1: p2.x, y1: midY, x2: p2.x, y2: p2.y }\n          ];\n          pathD = `M ${p1.x} ${p1.y}`;\n          segs.forEach(seg => {\n           const isHoriz = Math.abs(seg.y1 - seg.y2) < 1;\n           const segGaps = edgeGaps.filter(g => {\n            if (isHoriz) {\n             const minX = Math.min(seg.x1, seg.x2), maxX = Math.max(seg.x1, seg.x2);\n             return Math.abs(g.y - seg.y1) < 1 && g.x > minX + GAP && g.x < maxX - GAP;\n            } else {\n             const minY = Math.min(seg.y1, seg.y2), maxY = Math.max(seg.y1, seg.y2);\n             return Math.abs(g.x - seg.x1) < 1 && g.y > minY + GAP && g.y < maxY - GAP;\n            }\n           }).sort((a, b) => isHoriz ? (seg.x2 > seg.x1 ? a.x - b.x : b.x - a.x) : (seg.y2 > seg.y1 ? a.y - b.y : b.y - a.y));\n           let cx = seg.x1, cy = seg.y1;\n           segGaps.forEach(g => {\n            if (isHoriz) {\n             const gx = g.x;\n             pathD += ` L ${gx - GAP} ${cy} M ${gx + GAP} ${cy}`;\n             cx = gx + GAP;\n            } else {\n             const gy = g.y;\n             pathD += ` L ${cx} ${gy - GAP} M ${cx} ${gy + GAP}`;\n             cy = gy + GAP;\n            }\n           });\n           pathD += ` L ${seg.x2} ${seg.y2}`;\n          });\n         }\n        } else {\n         const midX = (p1.x + p2.x) / 2;\n         const midY = (p1.y + p2.y) / 2;\n         const dx = p2.x - p1.x;\n         const dy = p2.y - p1.y;\n         const len = Math.sqrt(dx * dx + dy * dy) || 1;\n         const perpX = -dy / len;\n         const perpY = dx / len;\n         let offsetAmount = 0;\n         if (pairTotal > 1) {\n          offsetAmount = (pairIndex - (pairTotal - 1) / 2) * 40;\n         }\n         const ctrlX = midX + perpX * offsetAmount;\n         const ctrlY = midY + perpY * offsetAmount;\n         pathD = `M ${p1.x} ${p1.y} Q ${ctrlX} ${ctrlY} ${p2.x} ${p2.y}`;\n        }\n        const path = document.createElementNS(ns, \"path\");\n        path.setAttribute(\"d\", pathD);\n        path.setAttribute(\"fill\", \"none\");\n        path.classList.add(\"edge\");\n        if (edge.type === \"backup\") path.classList.add(\"backup\");\n        path.dataset.edgeId = edge.id;\n        path.dataset.from = edge.from;\n        path.dataset.to = edge.to;\n        path.style.stroke = edge.color;\n        path.style.strokeWidth = edge.width;\n        const edgeDirection = edge.direction || \"none\";\n        const edgeLineStyle = edge.lineStyle || \"solid\";\n        if (edgeLineStyle === \"dashed\") { path.style.strokeDasharray = \"10,5\"; }\n        else if (edgeLineStyle === \"dotted\") { path.style.strokeDasharray = \"2,4\"; }\n        if (edgeDirection === \"forward\") {\n         path.setAttribute(\"marker-end\", \"url(#arrow-forward)\");\n        } else if (edgeDirection === \"backward\") {\n         path.setAttribute(\"marker-start\", \"url(#arrow-backward)\");\n        } else if (edgeDirection === \"both\") {\n         path.setAttribute(\"marker-start\", \"url(#arrow-backward)\");\n         path.setAttribute(\"marker-end\", \"url(#arrow-forward)\");\n        }\n        const animDir = PAGE_STATE.animationDirection || \"all\";\n        const shouldAnimate = (PAGE_STATE.animateConnections && edge.animate !== false || edge.animate === true) && edgeDirection !== \"none\" && (animDir === \"all\" || animDir === edgeDirection);\n        if (shouldAnimate) {\n         path.style.opacity = \"0.25\";\n         const edgeStyle = edge.animationStyle || PAGE_STATE.animationStyle || \"arrows\";\n         const arrowId = edgeStyle === \"dots\" ? \"#flow-arrow-small\" : \"#flow-arrow-big\";\n         const arrowColor = edge.color || PAGE_STATE.defaultEdge || \"#475569\";\n         const arrowCount = 3;\n         const animDuration = edge.animationSpeed ? parseFloat(edge.animationSpeed) : (PAGE_STATE.animationSpeed || 1.5);\n         if (edgeDirection === \"forward\" || edgeDirection === \"both\") {\n          for (let i = 0; i < arrowCount; i++) {\n           const arrow = document.createElementNS(ns, \"use\");\n           arrow.setAttribute(\"href\", arrowId);\n           arrow.style.fill = arrowColor;\n           arrow.style.offsetPath = `path('${pathD}')`;\n           arrow.style.animationDuration = animDuration + \"s\";\n           arrow.style.animationDelay = (i * (animDuration / arrowCount)) + \"s\";\n           arrow.classList.add(\"edge-arrow-forward\");\n           svg.appendChild(arrow);\n          }\n         }\n         if (edgeDirection === \"backward\" || edgeDirection === \"both\") {\n          for (let i = 0; i < arrowCount; i++) {\n           const arrow = document.createElementNS(ns, \"use\");\n           arrow.setAttribute(\"href\", arrowId);\n           arrow.style.fill = arrowColor;\n           arrow.style.offsetPath = `path('${pathD}')`;\n           arrow.style.animationDuration = animDuration + \"s\";\n           arrow.style.animationDelay = ((i * (animDuration / arrowCount)) + (edgeDirection === \"both\" ? animDuration / (arrowCount * 2) : 0)) + \"s\";\n           arrow.classList.add(\"edge-arrow-backward\");\n           svg.appendChild(arrow);\n          }\n         }\n        }\n        const pathHit = document.createElementNS(ns, \"path\");\n        pathHit.setAttribute(\"d\", pathD);\n        pathHit.setAttribute(\"fill\", \"none\");\n        pathHit.style.stroke = \"transparent\";\n        pathHit.style.strokeWidth = \"20\";\n        pathHit.style.cursor = \"pointer\";\n        pathHit.dataset.edgeId = edge.id;\n        pathHit.addEventListener(\"click\", (e) => {\n         e.stopPropagation();\n         selectTheConnection(edge.id);\n        });\n        let pathTouchStart = null;\n        let pathTouchMoved = false;\n        pathHit.addEventListener(\"touchstart\", (e) => {\n         pathTouchStart = Date.now();\n         pathTouchMoved = false;\n         if (e.touches[0]) {\n          pathTouchStartX = e.touches[0].clientX;\n          pathTouchStartY = e.touches[0].clientY;\n         }\n        }, {\n         passive: false\n        });\n        let pathTouchStartX = 0, pathTouchStartY = 0;\n        pathHit.addEventListener(\"touchmove\", (e) => {\n         if (e.touches[0]) {\n          const dx = Math.abs(e.touches[0].clientX - pathTouchStartX);\n          const dy = Math.abs(e.touches[0].clientY - pathTouchStartY);\n          if (dx > 10 || dy > 10) pathTouchMoved = true;\n         }\n        }, {\n         passive: false\n        });\n        pathHit.addEventListener(\"touchend\", (e) => {\n         if (pathTouchStart && !pathTouchMoved && Date.now() - pathTouchStart < 400) {\n          e.stopPropagation();\n          e.preventDefault();\n          selectTheConnection(edge.id);\n         }\n         pathTouchStart = null;\n         pathTouchMoved = false;\n        }, {\n         passive: false\n        });\n        path.addEventListener(\"click\", (e) => {\n         e.stopPropagation();\n         selectTheConnection(edge.id);\n        });\n        if (currentView.mode === \"rack\") {\n         const fromNode = NODE_DATA[edge.from];\n         const toNode = NODE_DATA[edge.to];\n         if (!fromNode || !toNode ||\n             fromNode.assignedRack !== currentView.rackId ||\n             toNode.assignedRack !== currentView.rackId) {\n          return;\n         }\n        }\n        const fromVisible = isNodeVisible(edge.from);\n        const toVisible = isNodeVisible(edge.to);\n        if (!fromVisible || !toVisible) {\n         path.style.opacity = \"0.1\";\n         path.style.pointerEvents = \"none\";\n         pathHit.style.pointerEvents = \"none\";\n        }\n        svg.appendChild(path);\n        svg.appendChild(pathHit);\n        if (edge.fromPort || edge.toPort) {\n         const ns = \"http://www.w3.org/2000/svg\";\n         if (edge.fromPort) {\n          const fromLabel = document.createElementNS(ns, \"text\");\n          fromLabel.textContent = edge.fromPort;\n          fromLabel.setAttribute(\"x\", p1.x);\n          fromLabel.setAttribute(\"y\", p1.y - 10);\n          fromLabel.setAttribute(\"text-anchor\", \"middle\");\n          fromLabel.style.fill = \"#94a3b8\";\n          fromLabel.style.fontSize = \"12px\";\n          fromLabel.style.fontWeight = \"600\";\n          fromLabel.style.pointerEvents = \"none\";\n          fromLabel.classList.add(\"port-label\");\n          svg.appendChild(fromLabel);\n         }\n         if (edge.toPort) {\n          const toLabel = document.createElementNS(ns, \"text\");\n          toLabel.textContent = edge.toPort;\n          toLabel.setAttribute(\"x\", p2.x);\n          toLabel.setAttribute(\"y\", p2.y - 10);\n          toLabel.setAttribute(\"text-anchor\", \"middle\");\n          toLabel.style.fill = \"#94a3b8\";\n          toLabel.style.fontSize = \"12px\";\n          toLabel.style.fontWeight = \"600\";\n          toLabel.style.pointerEvents = \"none\";\n          toLabel.classList.add(\"port-label\");\n          svg.appendChild(toLabel);\n         }\n        }\n       });\n       Object.entries(positions).forEach(([id, pos]) => {\n        const node = NODE_DATA[id];\n        if (!node) return;\n        if (currentView.mode === \"rack\") {\n         if (node.assignedRack !== currentView.rackId) return;\n         const rackUnit = parseInt(node.rackUnit) || 1;\n      const uHeight = parseInt(node.uHeight) || 1;\n      const rackCapacity = getRackCapacity(currentView.rackId);\n      const rackUHeight = getRackUHeight(currentView.rackId);\n      pos.x = RACK_START_X;\n      pos.y = RACK_START_Y + (rackCapacity - rackUnit - uHeight + 1) * rackUHeight + (uHeight * rackUHeight) / 2;\n        } else {\n         if (node.assignedRack) return;\n        }\n        const g = document.createElementNS(\"http://www.w3.org/2000/svg\", \"g\");\n        g.classList.add(\"node-group\");\n        g.dataset.nodeId = id;\n        const nodeRotation = NODE_DATA[id].rotation || 0;\n        g.setAttribute(\"transform\", `translate(${pos.x},${pos.y}) rotate(${nodeRotation})`);\n\t\tlet r = savedSizes[id] || 55;\n\t\tif (!savedSizes[id]) {\n\t\t if (window.innerWidth <= 480) r = 45;\n\t\t else if (window.innerWidth <= 768) r = 50;\n\t\t}\n        const styles = resolveStylesForNode(id);\n        const ns = \"http://www.w3.org/2000/svg\";\n        const hitArea = document.createElementNS(ns, \"circle\");\n        hitArea.setAttribute(\"r\", r * 1.5);\n        hitArea.style.fill = \"transparent\";\n        hitArea.style.stroke = \"none\";\n        hitArea.style.cursor = \"grab\";\n        hitArea.classList.add(\"node-hit-area\");\n        const shapeEl = createNodeShape(id, r);\n        const titleOffsetX = styles.titleOffsetX || 0;\n        const titleOffsetY = styles.titleOffsetY || 0;\n        const subOffsetX = styles.subOffsetX || 0;\n        const subOffsetY = styles.subOffsetY || 0;\n        const label = document.createElementNS(\"http://www.w3.org/2000/svg\", \"text\");\n        label.classList.add(\"node-label\");\n        label.setAttribute(\"x\", titleOffsetX);\n        label.setAttribute(\"y\", -r * 0.28 + titleOffsetY);\n      const labelFontSize = styles.titleSize || PAGE_STATE.nodeTitleSize || r * 0.33;\n        label.style.fontSize = labelFontSize + \"px\";\n        label.textContent = NODE_DATA[id].name;\n      label.style.fill = styles.titleColor || PAGE_STATE.nodeTitle || \"#e2e8f0\";\n        label.style.fontFamily = styles.titleFont || PAGE_STATE.nodeFont || \"Inter, system-ui, sans-serif\";\n        label.style.pointerEvents = \"none\";\n        const sub = document.createElementNS(\"http://www.w3.org/2000/svg\", \"text\");\n        sub.classList.add(\"node-sub\");\n        sub.setAttribute(\"x\", subOffsetX);\n        sub.setAttribute(\"y\", r * 0.4 + subOffsetY);\n      const subFontSize = styles.subSize || PAGE_STATE.nodeSubSize || r * 0.24;\n        sub.style.fontSize = subFontSize + \"px\";\n        sub.textContent = NODE_DATA[id].ip;\n      sub.style.fill = styles.subColor || PAGE_STATE.nodeSub || \"#94a3b8\";\n        sub.style.fontFamily = styles.subFont || PAGE_STATE.nodeFont || \"Inter, system-ui, sans-serif\";\n        sub.style.pointerEvents = \"none\";\nif (hasCoverageZone(node.shape) && node.fovEnabled) {\n  const defaults = getCoverageDefaults(node.shape);\n  const fovAngle = node.fovAngle || defaults.angle;\n  const fovDistance = node.fovDistance || defaults.distance;\n  const fovInnerRadius = node.fovInnerRadius || 0;\n  const fovRotation = node.fovRotation || 0;\n  const fovColor = node.fovColor || \"#f59e0b\";\n  const fovOpacity = node.fovOpacity || 20;\n  const fovGradient = node.fovGradient || false;\n  const fovBorderColor = node.fovBorderColor || \"#f59e0b\";\n  const fovBorderWidth = node.fovBorderWidth ?? 2;\n  const fovBorderStyle = node.fovBorderStyle || \"solid\";\n  const fovBorderOpacity = node.fovBorderOpacity ?? 100;\n  const fovLabel = node.fovLabel || \"\";\n  const fovAnimate = node.fovAnimate || false;\n  const fovAnimationType = node.fovAnimationType || defaults.animationType;\n  const fovSweep = node.fovSweep || 120;\n  const fovSpeed = node.fovSpeed || 4;\n  \n  const fovGroup = document.createElementNS(ns, \"g\");\n  fovGroup.classList.add(\"fov-group\");\n  \n  if (fovGradient) {\n    const gradientId = `fov-gradient-${id}`;\n    const defs = document.createElementNS(ns, \"defs\");\n    const gradient = document.createElementNS(ns, \"radialGradient\");\n    gradient.id = gradientId;\n    gradient.setAttribute(\"cx\", \"0\");\n    gradient.setAttribute(\"cy\", \"0\");\n    gradient.setAttribute(\"r\", fovDistance);\n    gradient.setAttribute(\"gradientUnits\", \"userSpaceOnUse\");\n    const stop1 = document.createElementNS(ns, \"stop\");\n    stop1.setAttribute(\"offset\", fovInnerRadius / fovDistance);\n    stop1.setAttribute(\"stop-color\", fovColor);\n    stop1.setAttribute(\"stop-opacity\", fovOpacity / 100);\n    const stop2 = document.createElementNS(ns, \"stop\");\n    stop2.setAttribute(\"offset\", \"1\");\n    stop2.setAttribute(\"stop-color\", fovColor);\n    stop2.setAttribute(\"stop-opacity\", \"0\");\n    gradient.appendChild(stop1);\n    gradient.appendChild(stop2);\n    defs.appendChild(gradient);\n    fovGroup.appendChild(defs);\n  }\n  \n  const fovPath = document.createElementNS(ns, \"path\");\n  \n  if (fovAngle >= 360) {\n    if (fovInnerRadius > 0) {\n      fovPath.setAttribute(\"d\", `M ${fovDistance},0 A ${fovDistance},${fovDistance} 0 1,1 ${-fovDistance},0 A ${fovDistance},${fovDistance} 0 1,1 ${fovDistance},0 M ${fovInnerRadius},0 A ${fovInnerRadius},${fovInnerRadius} 0 1,0 ${-fovInnerRadius},0 A ${fovInnerRadius},${fovInnerRadius} 0 1,0 ${fovInnerRadius},0`);\n      fovPath.setAttribute(\"fill-rule\", \"evenodd\");\n    } else {\n      fovPath.setAttribute(\"d\", `M 0,0 m -${fovDistance},0 a ${fovDistance},${fovDistance} 0 1,0 ${fovDistance * 2},0 a ${fovDistance},${fovDistance} 0 1,0 -${fovDistance * 2},0`);\n    }\n  } else {\n    const angleRad = (fovAngle * Math.PI) / 180;\n    const rotationRad = ((fovRotation - 90) * Math.PI) / 180;\n    const startAngle = rotationRad - angleRad / 2;\n    const endAngle = rotationRad + angleRad / 2;\n    const x1 = Math.cos(startAngle) * fovDistance;\n    const y1 = Math.sin(startAngle) * fovDistance;\n    const x2 = Math.cos(endAngle) * fovDistance;\n    const y2 = Math.sin(endAngle) * fovDistance;\n    const largeArc = fovAngle > 180 ? 1 : 0;\n    \n    if (fovInnerRadius > 0) {\n      const ix1 = Math.cos(startAngle) * fovInnerRadius;\n      const iy1 = Math.sin(startAngle) * fovInnerRadius;\n      const ix2 = Math.cos(endAngle) * fovInnerRadius;\n      const iy2 = Math.sin(endAngle) * fovInnerRadius;\n      fovPath.setAttribute(\"d\", `M ${ix1},${iy1} L ${x1},${y1} A ${fovDistance},${fovDistance} 0 ${largeArc},1 ${x2},${y2} L ${ix2},${iy2} A ${fovInnerRadius},${fovInnerRadius} 0 ${largeArc},0 ${ix1},${iy1} Z`);\n    } else {\n      fovPath.setAttribute(\"d\", `M 0,0 L ${x1},${y1} A ${fovDistance},${fovDistance} 0 ${largeArc},1 ${x2},${y2} Z`);\n    }\n  }\n  \n  if (fovGradient) {\n    fovPath.style.fill = `url(#fov-gradient-${id})`;\n  } else {\n    const opacityHex = Math.round((fovOpacity / 100) * 255).toString(16).padStart(2, '0');\n    fovPath.style.fill = fovColor + opacityHex;\n  }\n  \n  const borderOpacityHex = Math.round((fovBorderOpacity / 100) * 255).toString(16).padStart(2, '0');\n  fovPath.style.stroke = fovBorderColor + borderOpacityHex;\n  fovPath.style.strokeWidth = fovBorderWidth;\n  if (fovBorderStyle === \"dashed\") {\n    fovPath.style.strokeDasharray = \"10,5\";\n  } else if (fovBorderStyle === \"dotted\") {\n    fovPath.style.strokeDasharray = \"3,3\";\n  }\n  fovPath.style.pointerEvents = \"none\";\n  fovPath.classList.add(\"fov-cone\");\n  \n  fovGroup.appendChild(fovPath);\n  \n  if (fovLabel) {\n    const fovLabelPosition = node.fovLabelPosition || \"center\";\n    const fovLabelSize = node.fovLabelSize || 14;\n    const fovLabelColor = node.fovLabelColor || \"#ffffff\";\n    const fovLabelBold = node.fovLabelBold || false;\n    const fovLabelBg = node.fovLabelBg || false;\n    const fovLabelBgColor = node.fovLabelBgColor || \"#000000\";\n    \n    let labelDistance;\n    if (fovLabelPosition === \"center\") {\n      labelDistance = fovAngle >= 360 ? 0 : fovDistance * 0.4;\n    } else if (fovLabelPosition === \"edge\") {\n      labelDistance = fovDistance * 0.75;\n    } else {\n      labelDistance = fovDistance + fovLabelSize + 8;\n    }\n    \n    const labelAngle = ((fovRotation - 90) * Math.PI) / 180;\n    const labelX = fovAngle >= 360 && fovLabelPosition === \"center\" ? 0 : Math.cos(labelAngle) * labelDistance;\n    const labelY = fovAngle >= 360 && fovLabelPosition === \"center\" ? 0 : Math.sin(labelAngle) * labelDistance;\n    \n    if (fovLabelBg) {\n      const bgRect = document.createElementNS(ns, \"rect\");\n      const textWidth = fovLabel.length * fovLabelSize * 0.6;\n      const textHeight = fovLabelSize * 1.4;\n      bgRect.setAttribute(\"x\", labelX - textWidth / 2 - 6);\n      bgRect.setAttribute(\"y\", labelY - textHeight / 2);\n      bgRect.setAttribute(\"width\", textWidth + 12);\n      bgRect.setAttribute(\"height\", textHeight);\n      bgRect.setAttribute(\"rx\", \"4\");\n      bgRect.style.fill = fovLabelBgColor;\n      bgRect.style.opacity = \"0.8\";\n      bgRect.style.pointerEvents = \"none\";\n      fovGroup.appendChild(bgRect);\n    }\n    \n    const labelEl = document.createElementNS(ns, \"text\");\n    labelEl.setAttribute(\"x\", labelX);\n    labelEl.setAttribute(\"y\", labelY);\n    labelEl.setAttribute(\"text-anchor\", \"middle\");\n    labelEl.setAttribute(\"dominant-baseline\", \"middle\");\n    labelEl.style.fill = fovLabelColor;\n    labelEl.style.fontSize = fovLabelSize + \"px\";\n    labelEl.style.fontWeight = fovLabelBold ? \"bold\" : \"normal\";\n    labelEl.style.fontFamily = \"system-ui, sans-serif\";\n    labelEl.style.pointerEvents = \"none\";\n    labelEl.textContent = fovLabel;\n    fovGroup.appendChild(labelEl);\n  }\n  \n  if (fovAnimate) {\n    const animationName = `fov-anim-${id}`;\n    const styleEl = document.createElementNS(\"http://www.w3.org/1999/xhtml\", \"style\");\n    \n    if (fovAnimationType === \"sweep\" && fovAngle < 360) {\n      styleEl.textContent = `\n        @keyframes ${animationName} {\n          0%, 100% { transform: rotate(0deg); }\n          50% { transform: rotate(${fovSweep}deg); }\n        }\n      `;\n      fovGroup.style.animation = `${animationName} ${fovSpeed}s ease-in-out infinite`;\n      fovGroup.style.transformOrigin = \"0 0\";\n    } else if (fovAnimationType === \"pulse\") {\n      styleEl.textContent = `\n        @keyframes ${animationName} {\n          0%, 100% { transform: scale(1); opacity: 1; }\n          50% { transform: scale(1.1); opacity: 0.7; }\n        }\n      `;\n      fovGroup.style.animation = `${animationName} ${fovSpeed}s ease-in-out infinite`;\n      fovGroup.style.transformOrigin = \"0 0\";\n    } else if (fovAnimationType === \"rings\") {\n      for (let i = 1; i <= 3; i++) {\n        const ring = document.createElementNS(ns, \"circle\");\n        ring.setAttribute(\"cx\", \"0\");\n        ring.setAttribute(\"cy\", \"0\");\n        ring.setAttribute(\"r\", fovDistance * 0.3 * i);\n        ring.style.fill = \"none\";\n        ring.style.stroke = fovBorderColor;\n        ring.style.strokeWidth = \"2\";\n        ring.style.opacity = \"0\";\n        const ringAnimName = `${animationName}-ring-${i}`;\n        const ringStyle = document.createElementNS(\"http://www.w3.org/1999/xhtml\", \"style\");\n        ringStyle.textContent = `\n          @keyframes ${ringAnimName} {\n            0% { r: ${fovInnerRadius || 10}; opacity: 0.8; }\n            100% { r: ${fovDistance}; opacity: 0; }\n          }\n        `;\n        ring.style.animation = `${ringAnimName} ${fovSpeed}s ease-out infinite`;\n        ring.style.animationDelay = `${(i - 1) * (fovSpeed / 3)}s`;\n        fovGroup.appendChild(ringStyle);\n        fovGroup.appendChild(ring);\n      }\n    } else if (fovAnimationType === \"spin\") {\n      styleEl.textContent = `\n        @keyframes ${animationName} {\n          0% { transform: rotate(0deg); }\n          100% { transform: rotate(360deg); }\n        }\n      `;\n      fovGroup.style.animation = `${animationName} ${fovSpeed}s linear infinite`;\n      fovGroup.style.transformOrigin = \"0 0\";\n    }\n    \n    if (fovAnimationType !== \"rings\") {\n      fovGroup.appendChild(styleEl);\n      const elapsedSeconds = (Date.now() - FOV_ANIMATION_START) / 1000;\n      const animationOffset = elapsedSeconds % fovSpeed;\n      fovGroup.style.animationDelay = `-${animationOffset}s`;\n    }\n  }\n  \n  g.appendChild(fovGroup);\n}\n        g.append(hitArea, shapeEl, label, sub);\n        if (NODE_DATA[id]?.locked) {\n          const lockIcon = document.createElementNS(ns, \"text\");\n          lockIcon.textContent = \"🔒\";\n          lockIcon.setAttribute(\"x\", r * 0.6);\n          lockIcon.setAttribute(\"y\", -r * 0.6);\n          lockIcon.style.fontSize = (r * 0.4) + \"px\";\n          lockIcon.style.pointerEvents = \"none\";\n          lockIcon.style.userSelect = \"none\";\n          lockIcon.classList.add(\"lock-indicator\");\n          g.appendChild(lockIcon);\n        }\n        if (NODE_DATA[id]?.groupId) {\n          const groupIndicator = document.createElementNS(ns, \"circle\");\n          groupIndicator.setAttribute(\"r\", r + 4);\n          groupIndicator.style.fill = \"none\";\n      groupIndicator.style.stroke = PAGE_STATE.groupIndicator || \"#4fd1c5\";\n          groupIndicator.style.strokeWidth = \"3\";\n          groupIndicator.style.strokeDasharray = \"5,5\";\n          groupIndicator.style.pointerEvents = \"none\";\n          groupIndicator.classList.add(\"group-indicator\");\n          g.insertBefore(groupIndicator, g.firstChild);\n        }\n        let isDragging = false;\n        let startX, startY;\n        let initialPositions = {};\n        let longPressTimer = null;\n        let longPressTriggered = false;\n        g.addEventListener(\"contextmenu\", (e) => {\n         e.preventDefault();\n         e.stopPropagation();\n         if (isViewOnly()) return;\n         if (selectedNodes.has(id)) {\n          selectedNodes.delete(id);\n         } else {\n          selectedNodes.add(id);\n         }\n         updateNodeSelection();\n         return false;\n        });\n        g.addEventListener(\"touchstart\", (e) => {\n         if (NODE_DATA[id].isRack) {\n          const touch = e.touches[0];\n          longPressStartX = touch.clientX;\n          longPressStartY = touch.clientY;\n          longPressTimer = setTimeout(() => {\n           longPressTriggered = true;\n           if (navigator.vibrate) {\n            navigator.vibrate(100);\n           }\n           enterRack(id);\n          }, 500);\n         }\n        }, { passive: true });\n        let lastTapTime = 0;\n        let lastTapNode = null;\n        g.addEventListener(\"touchend\", (e) => {\n         const currentTime = new Date().getTime();\n         const tapLength = currentTime - lastTapTime;\n         if (tapLength < 300 && tapLength > 0 && lastTapNode === id) {\n          e.preventDefault();\n          e.stopPropagation();\n          if (isViewOnly()) return;\n          if (selectedNodes.has(id)) {\n           selectedNodes.delete(id);\n          } else {\n           selectedNodes.add(id);\n          }\n          updateNodeSelection();\n          if (navigator.vibrate) {\n           navigator.vibrate(50);\n          }\n          lastTapTime = 0;\n          lastTapNode = null;\n         } else {\n          lastTapTime = currentTime;\n          lastTapNode = id;\n         }\n        });\n        g.addEventListener(\"touchend\", (e) => {\n         if (longPressTimer) {\n          clearTimeout(longPressTimer);\n          longPressTimer = null;\n         }\n         if (longPressTriggered) {\n          e.preventDefault();\n          e.stopPropagation();\n          longPressTriggered = false;\n         }\n        });\n        let longPressStartX = 0;\n        let longPressStartY = 0;\n        g.addEventListener(\"touchmove\", (e) => {\n         if (longPressTimer) {\n          const touch = e.touches[0];\n          const dx = Math.abs(touch.clientX - longPressStartX);\n          const dy = Math.abs(touch.clientY - longPressStartY);\n          if (dx > 15 || dy > 15) {\n           clearTimeout(longPressTimer);\n           longPressTimer = null;\n           longPressTriggered = false;\n          }\n         }\n        }, { passive: true });\n        g.addEventListener(\"mousedown\", (e) => {\n\t\t if (isViewOnly()) return;\n         if (e.button === 2) {\n          return;\n         }\n         if (NODE_DATA[id]?.locked) {\n          return;\n         }\n         e.preventDefault();\n         isDragging = true;\n         const svgEl = document.getElementById(\"map\");\n         const pt = svgEl.createSVGPoint();\n         pt.x = e.clientX;\n         pt.y = e.clientY;\n         const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n         startX = svgP.x;\n         startY = svgP.y;\n      if (selectedNodes.has(id)) {\n      initialPositions = {};\n      const allSelectedRects = Array.from(selectedRects);\n      const allSelectedTexts = Array.from(selectedTexts);\n      const allSelectedEdges = Array.from(selectedEdges).map(eid => EDGE_DATA.list.find(e => e.id === eid)).filter(Boolean);\n      selectedNodes.forEach(nodeId => {\n      const nodePos = savedPositions[nodeId];\n      if (nodePos) {\n      initialPositions[nodeId] = { x: nodePos.x, y: nodePos.y };\n      }\n      });\n      Array.from(selectedRects).forEach(rectId => {\n        const rect = RECT_DATA.list.find(r => r.id === rectId);\n        if (rect) { rect._dragStartX = rect.x; rect._dragStartY = rect.y; }\n      });\n      Array.from(selectedTexts).forEach(textId => {\n        const text = TEXT_DATA.list.find(t => t.id === textId);\n        if (text) { text._dragStartX = text.x; text._dragStartY = text.y; }\n      });\n      Array.from(selectedEdges).forEach(edgeId => {\n        const edge = EDGE_DATA.list.find(e => e.id === edgeId);\n        if (edge && edge.points) { edge._dragStartPoints = edge.points.map(p => ({x: p.x, y: p.y})); }\n      });\n      } else {\n      initialPositions = { [id]: { x: pos.x, y: pos.y } };\n      }\n      if (currentView.mode === \"rack\") {\n      initialPositions[id] = { x: pos.x, y: pos.y };\n      }\n         g.style.cursor = \"grabbing\";\n         hitArea.style.cursor = \"grabbing\";\n         e.stopPropagation();\n        });\n        const handleMouseMove = (e) => {\n         if (!isDragging) return;\n         e.preventDefault();\n         const svgEl = document.getElementById(\"map\");\n         const pt = svgEl.createSVGPoint();\n         pt.x = e.clientX;\n         pt.y = e.clientY;\n         const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n         const dx = svgP.x - startX;\n         const dy = svgP.y - startY;\n         let nodesToMove = selectedNodes.has(id) ? Array.from(selectedNodes) : [id];\n         const groupIds = new Set();\n         nodesToMove.forEach(nodeId => {\n          const groupId = NODE_DATA[nodeId]?.groupId;\n          if (groupId) {\n            groupIds.add(groupId);\n          }\n         });\n         if (groupIds.size > 0) {\n          Object.keys(NODE_DATA).forEach(nodeId => {\n            const nodeGroupId = NODE_DATA[nodeId]?.groupId;\n            if (nodeGroupId && groupIds.has(nodeGroupId)) {\n              if (!nodesToMove.includes(nodeId)) {\n                nodesToMove.push(nodeId);\n                if (!initialPositions[nodeId]) {\n                  const nodePos = savedPositions[nodeId];\n                  if (nodePos) {\n                    initialPositions[nodeId] = { x: nodePos.x, y: nodePos.y };\n                  }\n                }\n              }\n            }\n          });\n         }\n         nodesToMove = nodesToMove.filter(nodeId => !NODE_DATA[nodeId]?.locked);\n         nodesToMove.forEach(nodeId => {\n          if (!initialPositions[nodeId]) return;\n          const initialPos = initialPositions[nodeId];\n          let newX = initialPos.x + dx;\n          let newY = initialPos.y + dy;\n          const nodeSize = savedSizes[nodeId] || 55;\n          newX = Math.max(nodeSize + CANVAS_PADDING, Math.min(CANVAS_WIDTH - nodeSize - CANVAS_PADDING, newX));\n          newY = Math.max(nodeSize + CANVAS_PADDING, Math.min(CANVAS_HEIGHT - nodeSize - CANVAS_PADDING, newY));\n          savedPositions[nodeId] = { x: newX, y: newY };\n          positions[nodeId] = { x: newX, y: newY };\n          if (nodeId === id) {\n           pos.x = newX;\n           pos.y = newY;\n          }\n          const nodeGroup = document.querySelector(`g[data-node-id=\"${nodeId}\"]`);\n          if (nodeGroup) {\n           nodeGroup.setAttribute(\"transform\", `translate(${newX},${newY})`);\n          }\n         });\n         Array.from(selectedRects).forEach(rectId => {\n           const rect = RECT_DATA.list.find(r => r.id === rectId);\n           if (rect && rect._dragStartX !== undefined) {\n             rect.x = rect._dragStartX + dx;\n             rect.y = rect._dragStartY + dy;\n           }\n         });\n         Array.from(selectedTexts).forEach(textId => {\n           const text = TEXT_DATA.list.find(t => t.id === textId);\n           if (text && text._dragStartX !== undefined) {\n             text.x = text._dragStartX + dx;\n             text.y = text._dragStartY + dy;\n           }\n         });\n         Array.from(selectedEdges).forEach(edgeId => {\n           const edge = EDGE_DATA.list.find(e => e.id === edgeId);\n           if (edge && edge._dragStartPoints) {\n             edge.points.forEach((p, i) => {\n               p.x = edge._dragStartPoints[i].x + dx;\n               p.y = edge._dragStartPoints[i].y + dy;\n             });\n           }\n         });\n         forgeTheTopology();\n         updateMinimap();\n         document.querySelectorAll(\".edge\").forEach((edgeEl) => {\n          const fromId = edgeEl.dataset.from;\n          const toId = edgeEl.dataset.to;\n          if (nodesToMove.includes(fromId) || nodesToMove.includes(toId)) {\n           const p1 = savedPositions[fromId] || positions[fromId] || {\n            x: 600,\n            y: 350\n           };\n           const p2 = savedPositions[toId] || positions[toId] || {\n            x: 600,\n            y: 350\n           };\n           if (edgeEl.tagName === \"line\") {\n            edgeEl.setAttribute(\"x1\", p1.x);\n            edgeEl.setAttribute(\"y1\", p1.y);\n            edgeEl.setAttribute(\"x2\", p2.x);\n            edgeEl.setAttribute(\"y2\", p2.y);\n           } else if (edgeEl.tagName === \"path\") {\n            const edgeId = edgeEl.dataset.edgeId;\n            const edge = EDGE_DATA.list.find(\n             (e) => e.id === edgeId);\n            if (edge) {\n             const pairTotal = edge._pairTotal || 1;\n             const pairIndex = edge._pairIndex || 0;\n             const midX = (p1.x + p2.x) / 2;\n             const midY = (p1.y + p2.y) / 2;\n             const dx = p2.x - p1.x;\n             const dy = p2.y - p1.y;\n             const len = Math.sqrt(dx * dx + dy * dy) || 1;\n             const perpX = -dy / len;\n             const perpY = dx / len;\n             let offsetAmount = 0;\n             if (pairTotal > 1) {\n              offsetAmount = (pairIndex - (pairTotal - 1) / 2) * 40;\n             }\n             const ctrlX = midX + perpX * offsetAmount;\n             const ctrlY = midY + perpY * offsetAmount;\n             edgeEl.setAttribute(\"d\", `M ${p1.x} ${p1.y} Q ${ctrlX} ${ctrlY} ${p2.x} ${p2.y}`);\n            }\n           }\n          }\n         });\n        };\n      const handleMouseUp = () => {\n      if (isDragging) {\n      isDragging = false;\n      g.style.cursor = \"grab\";\n      hitArea.style.cursor = \"grab\";\n      if (currentView.mode === \"rack\" && NODE_DATA[id]?.assignedRack === currentView.rackId) {\n      const draggedY = savedPositions[id]?.y || pos.y;\n      const rackCapacity = getRackCapacity(currentView.rackId);\n      const rackUHeight = getRackUHeight(currentView.rackId);\n      let newUnit = rackCapacity - Math.round((draggedY - RACK_START_Y) / rackUHeight);\n      newUnit = Math.max(1, Math.min(newUnit, rackCapacity));\n      NODE_DATA[id].rackUnit = newUnit;\n      forgeTheTopology();\n      claimTheImmortal(id);\n      }\n      }\n      };\n        document.addEventListener(\"mousemove\", handleMouseMove);\n        document.addEventListener(\"mouseup\", handleMouseUp);\n        let touchStartTime = 0;\n        let touchStartX = 0;\n        let touchStartY = 0;\n        let touchMoved = false;\n        g.addEventListener(\"touchstart\",\n         (e) => {\n          if (isViewOnly()) {\n           touchStartTime = Date.now();\n           touchMoved = false;\n           e.stopPropagation();\n           return;\n          }\n          if (NODE_DATA[id]?.locked) {\n           return;\n          }\n          e.preventDefault();\n          touchStartTime = Date.now();\n          touchMoved = false;\n          const svgEl = document.getElementById(\"map\");\n          const pt = svgEl.createSVGPoint();\n          const touch = e.touches[0];\n          pt.x = touch.clientX;\n          pt.y = touch.clientY;\n          const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n          touchStartX = svgP.x;\n          touchStartY = svgP.y;\n          startX = svgP.x;\n          startY = svgP.y;\n          if (selectedNodes.has(id)) {\n           initialPositions = {};\n           selectedNodes.forEach(nodeId => {\n            const nodePos = savedPositions[nodeId];\n            if (nodePos) {\n             initialPositions[nodeId] = { x: nodePos.x, y: nodePos.y };\n            }\n           });\n          } else {\n           initialPositions = { [id]: { x: pos.x, y: pos.y } };\n          }\n          e.stopPropagation();\n         }, {\n          passive: false\n         });\n        g.addEventListener(\"touchmove\", (e) => {\n\t\tif (isViewOnly()) return;\n         e.preventDefault();\n         const svgEl = document.getElementById(\"map\");\n         const pt = svgEl.createSVGPoint();\n         const touch = e.touches[0];\n         pt.x = touch.clientX;\n         pt.y = touch.clientY;\n         const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n         const dx = Math.abs(svgP.x - touchStartX);\n         const dy = Math.abs(svgP.y - touchStartY);\n         if (dx > (isMobileDevice() ? 4 : 10) || dy > (isMobileDevice() ? 4 : 10)) {\n      touchMoved = true;\n      isDragging = true;\n      }\n         if (!isDragging) return;\n         const deltaX = svgP.x - startX;\n         const deltaY = svgP.y - startY;\n         let nodesToMove = selectedNodes.has(id) ? Array.from(selectedNodes) : [id];\n         const groupIds = new Set();\n         nodesToMove.forEach(nodeId => {\n          const groupId = NODE_DATA[nodeId]?.groupId;\n          if (groupId) {\n            groupIds.add(groupId);\n          }\n         });\n         if (groupIds.size > 0) {\n          Object.keys(NODE_DATA).forEach(nodeId => {\n            const nodeGroupId = NODE_DATA[nodeId]?.groupId;\n            if (nodeGroupId && groupIds.has(nodeGroupId)) {\n              if (!nodesToMove.includes(nodeId)) {\n                nodesToMove.push(nodeId);\n                if (!initialPositions[nodeId]) {\n                  const nodePos = savedPositions[nodeId];\n                  if (nodePos) {\n                    initialPositions[nodeId] = { x: nodePos.x, y: nodePos.y };\n                  }\n                }\n              }\n            }\n          });\n         }\n         nodesToMove = nodesToMove.filter(nodeId => !NODE_DATA[nodeId]?.locked);\n         nodesToMove.forEach(nodeId => {\n          if (!initialPositions[nodeId]) return;\n          const initialPos = initialPositions[nodeId];\n          let newX = initialPos.x + deltaX;\n          let newY = initialPos.y + deltaY;\n          const nodeSize = savedSizes[nodeId] || 55;\n          newX = Math.max(nodeSize + CANVAS_PADDING, Math.min(CANVAS_WIDTH - nodeSize - CANVAS_PADDING, newX));\n          newY = Math.max(nodeSize + CANVAS_PADDING, Math.min(CANVAS_HEIGHT - nodeSize - CANVAS_PADDING, newY));\n          savedPositions[nodeId] = { x: newX, y: newY };\n          positions[nodeId] = { x: newX, y: newY };\n          if (nodeId === id) {\n           pos.x = newX;\n           pos.y = newY;\n          }\n          const nodeGroup = document.querySelector(`g[data-node-id=\"${nodeId}\"]`);\n          if (nodeGroup) {\n           nodeGroup.setAttribute(\"transform\", `translate(${newX},${newY})`);\n          }\n         });\n         updateMinimap();\n         document.querySelectorAll(\".edge\").forEach((edgeEl) => {\n          const fromId = edgeEl.dataset.from;\n          const toId = edgeEl.dataset.to;\n          if (nodesToMove.includes(fromId) || nodesToMove.includes(toId)) {\n           const p1 = savedPositions[fromId] || positions[fromId] || {\n            x: 600,\n            y: 350\n           };\n           const p2 = savedPositions[toId] || positions[toId] || {\n            x: 600,\n            y: 350\n           };\n           if (edgeEl.tagName === \"line\") {\n            edgeEl.setAttribute(\"x1\", p1.x);\n            edgeEl.setAttribute(\"y1\", p1.y);\n            edgeEl.setAttribute(\"x2\", p2.x);\n            edgeEl.setAttribute(\"y2\", p2.y);\n           } else if (edgeEl.tagName === \"path\") {\n            const edgeId = edgeEl.dataset.edgeId;\n            const edge = EDGE_DATA.list.find(\n             (e) => e.id === edgeId);\n            if (edge) {\n             const pairTotal = edge._pairTotal || 1;\n             const pairIndex = edge._pairIndex || 0;\n             const midX = (p1.x + p2.x) / 2;\n             const midY = (p1.y + p2.y) / 2;\n             const dx = p2.x - p1.x;\n             const dy = p2.y - p1.y;\n             const len = Math.sqrt(dx * dx + dy * dy) || 1;\n             const perpX = -dy / len;\n             const perpY = dx / len;\n             let offsetAmount = 0;\n             if (pairTotal > 1) {\n              offsetAmount = (pairIndex - (pairTotal - 1) / 2) * 40;\n             }\n             const ctrlX = midX + perpX * offsetAmount;\n             const ctrlY = midY + perpY * offsetAmount;\n             edgeEl.setAttribute(\"d\", `M ${p1.x} ${p1.y} Q ${ctrlX} ${ctrlY} ${p2.x} ${p2.y}`);\n            }\n           }\n          }\n         });\n        }, {\n         passive: false\n        });\n      g.addEventListener(\"touchend\", (e) => {\n      const touchDuration = Date.now() - touchStartTime;\n      if (!touchMoved && touchDuration < 400) {\n       if (isViewOnly()) {\n        handleViewOnlyClick(id, 'node');\n        return;\n       }\n       claimTheImmortal(id);\n      }\n      if (isDragging) {\n      const draggedY = pos.y;\n      isDragging = false;\n      savedPositions[id] = {\n      x: pos.x,\n      y: pos.y\n      };\n      if (currentView.mode === \"rack\" && NODE_DATA[id]?.assignedRack === currentView.rackId) {\n      const rackCapacity = getRackCapacity(currentView.rackId);\n      const rackUHeight = getRackUHeight(currentView.rackId);\n      let newUnit = rackCapacity - Math.round((draggedY - RACK_START_Y) / rackUHeight);\n      newUnit = Math.max(1, Math.min(newUnit, rackCapacity));\n      NODE_DATA[id].rackUnit = newUnit;\n      forgeTheTopology();\n      claimTheImmortal(id);\n      }\n      }\n      touchMoved = false;\n      });\n        g.style.cursor = \"grab\";\n        g.addEventListener(\"click\", (e) => {\n         if (!isDragging) {\n          if (isViewOnly()) {\n           handleViewOnlyClick(id, 'node');\n           return;\n          }\n          claimTheImmortal(id);\n         }\n        });\n        g.addEventListener(\"dblclick\", (e) => {\n         e.preventDefault();\n         e.stopPropagation();\n         if (NODE_DATA[id].isRack) {\n          enterRack(id);\n         }\n        });\n        if (!isNodeVisible(id)) {\n         g.style.opacity = \"0.1\";\n         g.style.pointerEvents = \"none\";\n        }\n        svg.appendChild(g);\n       });\n        if (RECT_DATA && RECT_DATA.list) {\n         RECT_DATA.list.forEach((rect) => {\n          if (currentView.mode === \"rack\") return;\n          if (rect.style === \"outlined\") {\n           const g = document.createElementNS(ns, \"g\");\n           g.classList.add(\"rect-group\");\n           g.dataset.rectId = rect.id;\n           const rectCenterX = rect.x + rect.width / 2;\n           const rectCenterY = rect.y + rect.height / 2;\n           const rectRotation = rect.rotation || 0;\n           if (rectRotation !== 0) {\n             g.setAttribute(\"transform\", `rotate(${rectRotation}, ${rectCenterX}, ${rectCenterY})`);\n           }\n           const rectEl = document.createElementNS(ns, \"rect\");\n           rectEl.classList.add(\"rect-shape\");\n           rectEl.setAttribute(\"x\", rect.x);\n           rectEl.setAttribute(\"y\", rect.y);\n           rectEl.setAttribute(\"width\", rect.width);\n           rectEl.setAttribute(\"height\", rect.height);\n           rectEl.style.fill = \"none\";\n           rectEl.style.stroke = rect.borderColor || rect.color;\n           rectEl.style.strokeWidth = (rect.borderWidth !== undefined ? rect.borderWidth : 3) + \"px\";\n           if (rect.lineStyle === \"dashed\") { rectEl.style.strokeDasharray = \"10,5\"; }\nelse if (rect.lineStyle === \"dotted\") { rectEl.style.strokeDasharray = \"2,4\"; }\nelse if (rect.lineStyle === \"wall\") {\n  rectEl.style.fill = rect.color;\n  rectEl.style.fillOpacity = \"0.5\";\n  rectEl.style.stroke = rect.borderColor || rect.color;\n  rectEl.style.strokeWidth = (rect.borderWidth !== undefined ? rect.borderWidth : 2) + \"px\";\n  const hatchGroup = document.createElementNS(ns, \"g\");\n  hatchGroup.classList.add(\"wall-hatch-lines\");\n  hatchGroup.style.pointerEvents = \"none\";\n  const spacing = 12;\n  const hatchColor = rect.borderColor || rect.color;\n  for (let i = -rect.height; i < rect.width + rect.height; i += spacing) {\n    const line = document.createElementNS(ns, \"line\");\n    line.setAttribute(\"x1\", rect.x + i);\n    line.setAttribute(\"y1\", rect.y);\n    line.setAttribute(\"x2\", rect.x + i - rect.height);\n    line.setAttribute(\"y2\", rect.y + rect.height);\n    line.style.stroke = hatchColor;\n    line.style.strokeWidth = \"2\";\n    hatchGroup.appendChild(line);\n  }\n  const clipId = \"clip-\" + rect.id;\n  const clipPath = document.createElementNS(ns, \"clipPath\");\n  clipPath.id = clipId;\n  const clipRect = document.createElementNS(ns, \"rect\");\n  clipRect.setAttribute(\"x\", rect.x);\n  clipRect.setAttribute(\"y\", rect.y);\n  clipRect.setAttribute(\"width\", rect.width);\n  clipRect.setAttribute(\"height\", rect.height);\n  clipPath.appendChild(clipRect);\n  defs.appendChild(clipPath);\n  hatchGroup.setAttribute(\"clip-path\", \"url(#\" + clipId + \")\");\n  g.appendChild(hatchGroup);\n}\n           else { rectEl.style.strokeDasharray = \"none\"; }\n           rectEl.style.cursor = \"move\";\n           rectEl.addEventListener(\"click\", (e) => {\n             if (rectDrawMode) return;\n             e.stopPropagation();\n             currentRectId = rect.id;\n             selectTheRect(rect.id);\n           });\n           rectEl.addEventListener(\"contextmenu\", (e) => {\n\t\t   if (isViewOnly()) return;\n             e.preventDefault();\n             e.stopPropagation();\n             if (selectedRects.has(rect.id)) {\n               selectedRects.delete(rect.id);\n             } else {\n               selectedRects.add(rect.id);\n             }\n             updateAllSelections();\n           });\n           let rectLastTap = 0;\n           rectEl.addEventListener(\"touchend\", (e) => {\n             const now = Date.now();\n             if (now - rectLastTap < 300) {\n               e.preventDefault();\n               if (selectedRects.has(rect.id)) {\n                 selectedRects.delete(rect.id);\n               } else {\n                 selectedRects.add(rect.id);\n               }\n               updateAllSelections();\n               if (navigator.vibrate) navigator.vibrate(50);\n               rectLastTap = 0;\n             } else {\n               rectLastTap = now;\n             }\n           }, { passive: false });\n           const deleteBtn = document.createElementNS(ns, \"g\");\n           deleteBtn.classList.add(\"rect-delete-btn\");\n           deleteBtn.style.cursor = \"pointer\";\n           deleteBtn.style.display = rectDrawMode ? \"block\" : \"none\";\n           const deleteBg = document.createElementNS(ns, \"circle\");\n           deleteBg.setAttribute(\"cx\", rect.x + rect.width - 10);\n           deleteBg.setAttribute(\"cy\", rect.y + 10);\n           deleteBg.setAttribute(\"r\", 12);\n           deleteBg.style.fill = \"#f56565\";\n           deleteBg.style.stroke = \"white\";\n           deleteBg.style.strokeWidth = \"2\";\n           const deleteX = document.createElementNS(ns, \"text\");\n           deleteX.setAttribute(\"x\", rect.x + rect.width - 10);\n           deleteX.setAttribute(\"y\", rect.y + 10);\n           deleteX.setAttribute(\"text-anchor\", \"middle\");\n           deleteX.setAttribute(\"dominant-baseline\", \"middle\");\n           deleteX.style.fill = \"white\";\n           deleteX.style.fontSize = \"16px\";\n           deleteX.style.fontWeight = \"bold\";\n           deleteX.style.pointerEvents = \"none\";\n           deleteX.textContent = \"×\";\n           deleteBtn.appendChild(deleteBg);\n           deleteBtn.appendChild(deleteX);\n           deleteBtn.addEventListener(\"click\", (e) => {\n            e.stopPropagation();\n            e.preventDefault();\n            deleteRectangle(rect.id);\n           });\n           let isDragging = false;\n           let dragStartX, dragStartY;\n           let rectStartX, rectStartY;\n           rectEl.addEventListener(\"mousedown\", (e) => {\n\t\t   if (isViewOnly()) return;\n      if (rectDrawMode) return;\n      e.preventDefault();\n      e.stopPropagation();\n      isDragging = true;\n      dragStartX = e.clientX;\n      dragStartY = e.clientY;\n      rectStartX = rect.x;\n      rectStartY = rect.y;\n      rectEl.style.cursor = \"grabbing\";\n      if (selectedRects.has(rect.id)) {\n      initialPositions = {};\n      selectedNodes.forEach(nodeId => {\n      const nodePos = savedPositions[nodeId];\n      if (nodePos) initialPositions[nodeId] = { x: nodePos.x, y: nodePos.y };\n      });\n      selectedRects.forEach(rectId => {\n      const r = RECT_DATA.list.find(x => x.id === rectId);\n      if (r) { r._dragStartX = r.x; r._dragStartY = r.y; }\n      });\n      selectedTexts.forEach(textId => {\n      const t = TEXT_DATA.list.find(x => x.id === textId);\n      if (t) { t._dragStartX = t.x; t._dragStartY = t.y; }\n      });\n      selectedEdges.forEach(edgeId => {\n      const ed = EDGE_DATA.list.find(x => x.id === edgeId);\n      if (ed && ed.points) { ed._dragStartPoints = ed.points.map(p => ({x: p.x, y: p.y})); }\n      });\n      }\n      });\n           const moveHandler = (e) => {\n      if (!isDragging || rectDrawMode) return;\n      const svgEl = svg;\n      const pt1 = svgEl.createSVGPoint();\n      pt1.x = dragStartX;\n      pt1.y = dragStartY;\n      const svgP1 = pt1.matrixTransform(svgEl.getScreenCTM().inverse());\n      const pt2 = svgEl.createSVGPoint();\n      pt2.x = e.clientX;\n      pt2.y = e.clientY;\n      const svgP2 = pt2.matrixTransform(svgEl.getScreenCTM().inverse());\n      const dx = svgP2.x - svgP1.x;\n      const dy = svgP2.y - svgP1.y;\n      rect.x = rectStartX + dx;\n      rect.y = rectStartY + dy;\n      if (selectedRects.has(rect.id)) {\n      selectedNodes.forEach(nodeId => {\n      if (initialPositions[nodeId]) {\n       const newX = initialPositions[nodeId].x + dx;\n       const newY = initialPositions[nodeId].y + dy;\n       savedPositions[nodeId] = { x: newX, y: newY };\n      }\n      });\n      selectedRects.forEach(rectId => {\n      if (rectId === rect.id) return;\n      const r = RECT_DATA.list.find(x => x.id === rectId);\n      if (r && r._dragStartX !== undefined) { r.x = r._dragStartX + dx; r.y = r._dragStartY + dy; }\n      });\n      selectedTexts.forEach(textId => {\n      const t = TEXT_DATA.list.find(x => x.id === textId);\n      if (t && t._dragStartX !== undefined) { t.x = t._dragStartX + dx; t.y = t._dragStartY + dy; }\n      });\n      selectedEdges.forEach(edgeId => {\n      const ed = EDGE_DATA.list.find(x => x.id === edgeId);\n      if (ed && ed._dragStartPoints) { ed.points.forEach((p, i) => { p.x = ed._dragStartPoints[i].x + dx; p.y = ed._dragStartPoints[i].y + dy; }); }\n      });\n      }\n      forgeTheTopology();\n      };\n           const upHandler = () => {\n            if (isDragging) {\n             isDragging = false;\n             rectEl.style.cursor = \"move\";\n            }\n           };\n           document.addEventListener(\"mousemove\", moveHandler);\n           document.addEventListener(\"mouseup\", upHandler);\n           let touchStartX, touchStartY;\n           let touchRectStartX, touchRectStartY;\n           rectEl.addEventListener(\"touchstart\", (e) => {\n\t\t   if (isViewOnly()) return;\n            if (rectDrawMode) return;\n            e.preventDefault();\n            e.stopPropagation();\n            isDragging = true;\n            const touch = e.touches[0];\n            touchStartX = touch.clientX;\n            touchStartY = touch.clientY;\n            touchRectStartX = rect.x;\n            touchRectStartY = rect.y;\n            rectEl.style.cursor = \"grabbing\";\n           }, { passive: false });\n           rectEl.addEventListener(\"touchmove\", (e) => {\n\t\t    if (isViewOnly()) return;\n            if (!isDragging || rectDrawMode) return;\n            if (!e.touches[0]) return;\n            e.preventDefault();\n            e.stopPropagation();\n            const svgEl = svg;\n            const pt1 = svgEl.createSVGPoint();\n            pt1.x = touchStartX;\n            pt1.y = touchStartY;\n            const svgP1 = pt1.matrixTransform(svgEl.getScreenCTM().inverse());\n            const touch = e.touches[0];\n            const pt2 = svgEl.createSVGPoint();\n            pt2.x = touch.clientX;\n            pt2.y = touch.clientY;\n            const svgP2 = pt2.matrixTransform(svgEl.getScreenCTM().inverse());\n            const dx = svgP2.x - svgP1.x;\n            const dy = svgP2.y - svgP1.y;\n            rect.x = touchRectStartX + dx;\n            rect.y = touchRectStartY + dy;\n            forgeTheTopology();\n           }, { passive: false });\n           rectEl.addEventListener(\"touchend\", (e) => {\n            if (isDragging) {\n             e.preventDefault();\n             isDragging = false;\n             rectEl.style.cursor = \"move\";\n            }\n           }, { passive: false });\n           if (currentRectId === rect.id) {\n             const corners = [\n               { cx: rect.x, cy: rect.y, cursor: 'nwse-resize', dx: -1, dy: -1 },\n               { cx: rect.x + rect.width, cy: rect.y, cursor: 'nesw-resize', dx: 1, dy: -1 },\n               { cx: rect.x, cy: rect.y + rect.height, cursor: 'nesw-resize', dx: -1, dy: 1 },\n               { cx: rect.x + rect.width, cy: rect.y + rect.height, cursor: 'nwse-resize', dx: 1, dy: 1 }\n             ];\n             corners.forEach((corner, idx) => {\n               const handle = document.createElementNS(ns, \"circle\");\n               handle.setAttribute(\"cx\", corner.cx);\n               handle.setAttribute(\"cy\", corner.cy);\n               const borderW = rect.borderWidth !== undefined ? rect.borderWidth : 3;\n               const handleSize = Math.max(PAGE_STATE.selectionHandleSize || 8, borderW + 4);\n               handle.setAttribute(\"r\", handleSize);\n               handle.style.fill = PAGE_STATE.selectionHandle || \"#f59e0b\";\n               handle.style.stroke = \"#fff\";\n               handle.style.strokeWidth = \"2\";\n               handle.style.cursor = corner.cursor;\n               handle.addEventListener(\"mousedown\", (e) => {\n                 if (isViewOnly()) return;\n                 e.preventDefault();\n                 e.stopPropagation();\n                 pushUndo(\"resize zone\");\n                 let dragging = true;\n                 const startX = e.clientX, startY = e.clientY;\n                 const origX = rect.x, origY = rect.y, origW = rect.width, origH = rect.height;\n                 const moveHandler = (ev) => {\n                   if (!dragging) return;\n                   const pt1 = svg.createSVGPoint(); pt1.x = startX; pt1.y = startY;\n                   const pt2 = svg.createSVGPoint(); pt2.x = ev.clientX; pt2.y = ev.clientY;\n                   const svgP1 = pt1.matrixTransform(svg.getScreenCTM().inverse());\n                   const svgP2 = pt2.matrixTransform(svg.getScreenCTM().inverse());\n                   const dx = svgP2.x - svgP1.x, dy = svgP2.y - svgP1.y;\n                   if (corner.dx < 0) { rect.x = origX + dx; rect.width = origW - dx; }\n                   else { rect.width = origW + dx; }\n                   if (corner.dy < 0) { rect.y = origY + dy; rect.height = origH - dy; }\n                   else { rect.height = origH + dy; }\n                   if (rect.width < 20) rect.width = 20;\n                   if (rect.height < 20) rect.height = 20;\n                   forgeTheTopology();\n                 };\n                 const upHandler = () => { dragging = false; document.removeEventListener(\"mousemove\", moveHandler); document.removeEventListener(\"mouseup\", upHandler); };\n                 document.addEventListener(\"mousemove\", moveHandler);\n                 document.addEventListener(\"mouseup\", upHandler);\n               });\n               g.appendChild(handle);\n             });\n           }\n           if (rect.groupId) {\n             const groupIndicator = document.createElementNS(ns, \"rect\");\n             groupIndicator.setAttribute(\"x\", rect.x - 4);\n             groupIndicator.setAttribute(\"y\", rect.y - 4);\n             groupIndicator.setAttribute(\"width\", rect.width + 8);\n             groupIndicator.setAttribute(\"height\", rect.height + 8);\n             groupIndicator.style.fill = \"none\";\n             groupIndicator.style.stroke = \"#4fd1c5\";\n             groupIndicator.style.strokeWidth = \"3\";\n             groupIndicator.style.strokeDasharray = \"5,5\";\n             groupIndicator.style.pointerEvents = \"none\";\n             g.insertBefore(groupIndicator, g.firstChild);\n           }\n           g.appendChild(rectEl);\n           g.appendChild(deleteBtn);\n           svg.appendChild(g);\n          }\n         });\n        }\n        if (TEXT_DATA && TEXT_DATA.list) {\n         TEXT_DATA.list.forEach((textItem) => {\n          if (currentView.mode === \"rack\") return;\n          const g = document.createElementNS(ns, \"g\");\n          g.classList.add(\"text-group\");\n          g.dataset.textId = textItem.id;\n          const textRotation = textItem.rotation || 0;\n          if (textRotation !== 0) {\n            g.setAttribute(\"transform\", `rotate(${textRotation}, ${textItem.x}, ${textItem.y})`);\n          }\n          if (textItem.bgEnabled) {\n           const bgRect = document.createElementNS(ns, \"rect\");\n           bgRect.classList.add(\"text-bg\");\n           bgRect.setAttribute(\"x\", textItem.x - 5);\n           bgRect.setAttribute(\"y\", textItem.y - textItem.fontSize - 2);\n           bgRect.setAttribute(\"width\", 100);\n           bgRect.setAttribute(\"height\", textItem.fontSize + 10);\n           bgRect.style.fill = textItem.bgColor;\n           bgRect.style.opacity = \"0.7\";\n           bgRect.style.rx = \"4\";\n           g.appendChild(bgRect);\n          }\n          const textEl = document.createElementNS(ns, \"text\");\n          textEl.classList.add(\"text-element\");\n          textEl.setAttribute(\"x\", textItem.x);\n          textEl.setAttribute(\"y\", textItem.y);\n          textEl.style.fill = textItem.color;\n          textEl.style.fontSize = textItem.fontSize + \"px\";\n          textEl.style.fontWeight = textItem.fontWeight;\n          textEl.style.fontStyle = textItem.fontStyle;\n          textEl.style.textAnchor = textItem.textAlign;\n          textEl.style.textDecoration = textItem.textDecoration;\n          textEl.style.opacity = textItem.opacity;\n          textEl.style.cursor = \"move\";\n          textEl.style.userSelect = \"none\";\n          textEl.setAttribute(\"dominant-baseline\", \"middle\");\n          const lines = textItem.content.split('\\n');\n          if (lines.length === 1) {\n           textEl.textContent = textItem.content;\n          } else {\n           lines.forEach((line, i) => {\n            const tspan = document.createElementNS(ns, \"tspan\");\n            tspan.textContent = line;\n            tspan.setAttribute(\"x\", textItem.x);\n            tspan.setAttribute(\"dy\", i === 0 ? 0 : textItem.fontSize * 1.2);\n            textEl.appendChild(tspan);\n           });\n          }\n          g.appendChild(textEl);\n          if (textItem.bgEnabled) {\n           setTimeout(() => {\n            try {\n             const bbox = textEl.getBBox();\n             const bgRect = g.querySelector('.text-bg');\n             if (bgRect && bbox) {\n              bgRect.setAttribute(\"x\", bbox.x - 5);\n              bgRect.setAttribute(\"y\", bbox.y - 2);\n              bgRect.setAttribute(\"width\", bbox.width + 10);\n              bgRect.setAttribute(\"height\", bbox.height + 4);\n             }\n            } catch (e) {\n            }\n           }, 0);\n          }\n          const deleteBtn = document.createElementNS(ns, \"g\");\n          deleteBtn.classList.add(\"text-delete-btn\");\n          deleteBtn.style.cursor = \"pointer\";\n          deleteBtn.style.display = textDrawMode ? \"block\" : \"none\";\n          const deleteBg = document.createElementNS(ns, \"circle\");\n          deleteBg.setAttribute(\"cx\", textItem.x + 20);\n          deleteBg.setAttribute(\"cy\", textItem.y - textItem.fontSize);\n          deleteBg.setAttribute(\"r\", 12);\n          deleteBg.style.fill = \"#f56565\";\n          deleteBg.style.stroke = \"white\";\n          deleteBg.style.strokeWidth = \"2\";\n          const deleteX = document.createElementNS(ns, \"text\");\n          deleteX.setAttribute(\"x\", textItem.x + 20);\n          deleteX.setAttribute(\"y\", textItem.y - textItem.fontSize);\n          deleteX.setAttribute(\"text-anchor\", \"middle\");\n          deleteX.setAttribute(\"dominant-baseline\", \"middle\");\n          deleteX.style.fill = \"white\";\n          deleteX.style.fontSize = \"16px\";\n          deleteX.style.fontWeight = \"bold\";\n          deleteX.style.pointerEvents = \"none\";\n          deleteX.textContent = \"×\";\n          deleteBtn.appendChild(deleteBg);\n          deleteBtn.appendChild(deleteX);\n          deleteBtn.addEventListener(\"click\", (e) => {\n           e.stopPropagation();\n           e.preventDefault();\n           deleteText(textItem.id);\n          });\n          let isDragging = false;\n          let dragStartX, dragStartY;\n          let textStartX, textStartY;\n          textEl.addEventListener(\"mousedown\", (e) => {\n\t\t  if (isViewOnly()) return;\n      if (textDrawMode) return;\n      e.preventDefault();\n      e.stopPropagation();\n      isDragging = true;\n      dragStartX = e.clientX;\n      dragStartY = e.clientY;\n      textStartX = textItem.x;\n      textStartY = textItem.y;\n      textEl.style.cursor = \"grabbing\";\n      showTextPanel(textItem.id);\n      if (selectedTexts.has(textItem.id)) {\n      initialPositions = {};\n      selectedNodes.forEach(nodeId => {\n      const nodePos = savedPositions[nodeId];\n      if (nodePos) initialPositions[nodeId] = { x: nodePos.x, y: nodePos.y };\n      });\n      selectedRects.forEach(rectId => {\n      const r = RECT_DATA.list.find(x => x.id === rectId);\n      if (r) { r._dragStartX = r.x; r._dragStartY = r.y; }\n      });\n      selectedTexts.forEach(textId => {\n      const t = TEXT_DATA.list.find(x => x.id === textId);\n      if (t) { t._dragStartX = t.x; t._dragStartY = t.y; }\n      });\n      selectedEdges.forEach(edgeId => {\n      const ed = EDGE_DATA.list.find(x => x.id === edgeId);\n      if (ed && ed.points) { ed._dragStartPoints = ed.points.map(p => ({x: p.x, y: p.y})); }\n      });\n      }\n      });\n          const moveHandler = (e) => {\n      if (!isDragging || textDrawMode) return;\n      const svgEl = svg;\n      const pt1 = svgEl.createSVGPoint();\n      pt1.x = dragStartX;\n      pt1.y = dragStartY;\n      const svgP1 = pt1.matrixTransform(svgEl.getScreenCTM().inverse());\n      const pt2 = svgEl.createSVGPoint();\n      pt2.x = e.clientX;\n      pt2.y = e.clientY;\n      const svgP2 = pt2.matrixTransform(svgEl.getScreenCTM().inverse());\n      const dx = svgP2.x - svgP1.x;\n      const dy = svgP2.y - svgP1.y;\n      textItem.x = textStartX + dx;\n      textItem.y = textStartY + dy;\n      if (selectedTexts.has(textItem.id)) {\n      selectedNodes.forEach(nodeId => {\n      if (initialPositions[nodeId]) {\n       const newX = initialPositions[nodeId].x + dx;\n       const newY = initialPositions[nodeId].y + dy;\n       savedPositions[nodeId] = { x: newX, y: newY };\n      }\n      });\n      selectedRects.forEach(rectId => {\n      const r = RECT_DATA.list.find(x => x.id === rectId);\n      if (r && r._dragStartX !== undefined) { r.x = r._dragStartX + dx; r.y = r._dragStartY + dy; }\n      });\n      selectedTexts.forEach(textId => {\n      if (textId === textItem.id) return;\n      const t = TEXT_DATA.list.find(x => x.id === textId);\n      if (t && t._dragStartX !== undefined) { t.x = t._dragStartX + dx; t.y = t._dragStartY + dy; }\n      });\n      selectedEdges.forEach(edgeId => {\n      const ed = EDGE_DATA.list.find(x => x.id === edgeId);\n      if (ed && ed._dragStartPoints) { ed.points.forEach((p, i) => { p.x = ed._dragStartPoints[i].x + dx; p.y = ed._dragStartPoints[i].y + dy; }); }\n      });\n      }\n      forgeTheTopology();\n      };\n          const upHandler = () => {\n           if (isDragging) {\n            isDragging = false;\n            textEl.style.cursor = \"move\";\n           }\n          };\n          document.addEventListener(\"mousemove\", moveHandler);\n          document.addEventListener(\"mouseup\", upHandler);\n          let touchStartX = 0, touchStartY = 0;\n          let textTouchStartX = 0, textTouchStartY = 0;\n          textEl.addEventListener(\"touchstart\", (e) => {\n\t\t   if (isViewOnly()) return;\n           if (textDrawMode) return;\n           if (e.touches.length !== 1) return;\n           e.preventDefault();\n           e.stopPropagation();\n           const touch = e.touches[0];\n           isDragging = true;\n           touchStartX = touch.clientX;\n           touchStartY = touch.clientY;\n           textTouchStartX = textItem.x;\n           textTouchStartY = textItem.y;\n           showTextPanel(textItem.id);\n          }, { passive: false });\n          textEl.addEventListener(\"touchmove\", (e) => {\n           if (!isDragging || textDrawMode) return;\n           if (e.touches.length !== 1) return;\n           e.preventDefault();\n           const touch = e.touches[0];\n           const svgEl = svg;\n           const pt1 = svgEl.createSVGPoint();\n           pt1.x = touchStartX;\n           pt1.y = touchStartY;\n           const svgP1 = pt1.matrixTransform(svgEl.getScreenCTM().inverse());\n           const pt2 = svgEl.createSVGPoint();\n           pt2.x = touch.clientX;\n           pt2.y = touch.clientY;\n           const svgP2 = pt2.matrixTransform(svgEl.getScreenCTM().inverse());\n           const dx = svgP2.x - svgP1.x;\n           const dy = svgP2.y - svgP1.y;\n           textItem.x = textTouchStartX + dx;\n           textItem.y = textTouchStartY + dy;\n           forgeTheTopology();\n          }, { passive: false });\n          textEl.addEventListener(\"touchend\", () => {\n           if (isDragging) {\n            isDragging = false;\n           }\n          }, { passive: false });\n          textEl.addEventListener(\"contextmenu\", (e) => {\n\t\t  if (isViewOnly()) return;\n           e.preventDefault();\n           e.stopPropagation();\n           if (selectedTexts.has(textItem.id)) {\n            selectedTexts.delete(textItem.id);\n           } else {\n            selectedTexts.add(textItem.id);\n           }\n           updateAllSelections();\n          });\n          let textLastTap = 0;\n          g.addEventListener(\"touchend\", (e) => {\n           const now = Date.now();\n           if (now - textLastTap < 300) {\n            e.preventDefault();\n            if (selectedTexts.has(textItem.id)) {\n             selectedTexts.delete(textItem.id);\n            } else {\n             selectedTexts.add(textItem.id);\n            }\n            updateAllSelections();\n            if (navigator.vibrate) navigator.vibrate(50);\n            textLastTap = 0;\n           } else {\n            textLastTap = now;\n           }\n          }, { passive: false });\n          if (textItem.groupId) {\n            const groupIndicator = document.createElementNS(ns, \"rect\");\n            groupIndicator.setAttribute(\"x\", textItem.x - 54);\n            groupIndicator.setAttribute(\"y\", textItem.y - 24);\n            groupIndicator.setAttribute(\"width\", 108);\n            groupIndicator.setAttribute(\"height\", 48);\n            groupIndicator.setAttribute(\"rx\", \"8\");\n            groupIndicator.style.fill = \"none\";\n            groupIndicator.style.stroke = \"#4fd1c5\";\n            groupIndicator.style.strokeWidth = \"3\";\n            groupIndicator.style.strokeDasharray = \"5,5\";\n            groupIndicator.style.pointerEvents = \"none\";\n            g.insertBefore(groupIndicator, g.firstChild);\n          }\n          g.appendChild(deleteBtn);\n          svg.appendChild(g);\n         });\n        }\n       forgeTheLegend();\n       updateZoneLegend();\n       updateMinimap();\n       populateRackDropdown();\n       if (currentSearchQuery && currentSearchResults.length > 0) {\n        highlightSearchResults(currentSearchResults, true);\n       }\n      }\n      const _forgeTheTopologyImpl = forgeTheTopology;\n      forgeTheTopology = function(immediate = false) {\n       if (immediate || forgeImmediate) {\n        forgeImmediate = false;\n        clearTimeout(forgeDebounceTimer);\n        _forgeTheTopologyImpl();\n        return;\n       }\n       clearTimeout(forgeDebounceTimer);\n       forgeDebounceTimer = setTimeout(() => {\n        _forgeTheTopologyImpl();\n       }, 16);\n      };\n      function forgeTheTopologyImmediate() {\n       forgeImmediate = true;\n       forgeTheTopology();\n      }\n      function showEditModal(title, currentValue, onSave) {\n       const modal = document.getElementById(\"edit-modal\");\n       const input = document.getElementById(\"modal-input\");\n       const titleEl = document.getElementById(\"modal-title\");\n       const saveBtn = document.getElementById(\"modal-save\");\n       const cancelBtn = document.getElementById(\"modal-cancel\");\n       titleEl.textContent = title;\n       input.value = currentValue;\n       modal.classList.add(\"active\");\n       input.focus();\n       input.select();\n       const cleanup = () => {\n        modal.classList.remove(\"active\");\n        saveBtn.removeEventListener(\"click\", handleSave);\n        cancelBtn.removeEventListener(\"click\", handleCancel);\n        input.removeEventListener(\"keypress\", handleEnter);\n        modal.removeEventListener(\"click\", bgHandler);\n       };\n       const handleSave = () => {\n        if (input.value.trim()) {\n         onSave(input.value.trim());\n        }\n        cleanup();\n       };\n       const handleCancel = () => {\n        cleanup();\n       };\n       const handleEnter = (e) => {\n        if (e.key === \"Enter\") handleSave();\n       };\n       const bgHandler = (e) => {\n        if (e.target === modal) handleCancel();\n       };\n       saveBtn.addEventListener(\"click\", handleSave);\n       cancelBtn.addEventListener(\"click\", handleCancel);\n       input.addEventListener(\"keypress\", handleEnter);\n       modal.addEventListener(\"click\", bgHandler);\n      }\n      function challengeTheImmortal(message, onConfirm) {\n       const modal = document.getElementById(\"confirm-modal\");\n       const messageEl = document.getElementById(\"confirm-message\");\n       const deleteBtn = document.getElementById(\"confirm-delete\");\n       const cancelBtn = document.getElementById(\"confirm-cancel\");\n       messageEl.textContent = message;\n       modal.classList.add(\"active\");\n\t   const cleanup = () => {\n        modal.classList.remove(\"active\");\n        deleteBtn.removeEventListener(\"click\", handleConfirm);\n        cancelBtn.removeEventListener(\"click\", handleCancel);\n        modal.removeEventListener(\"click\", bgHandler);\n       };\n       const handleConfirm = () => {\n        onConfirm();\n        cleanup();\n       };\n       const handleCancel = () => {\n        cleanup();\n       };\n       const bgHandler = (e) => {\n        if (e.target === modal) handleCancel();\n       };\n       deleteBtn.addEventListener(\"click\", handleConfirm);\n       cancelBtn.addEventListener(\"click\", handleCancel);\n       modal.addEventListener(\"click\", bgHandler);\n      }\n      const pageTitleEl = document.getElementById(\"page-title\");\n      if (pageTitleEl) {\n       pageTitleEl.addEventListener(\"click\", () => {\n        showEditModal(\"Edit Title\", PAGE_STATE.title || DEFAULT_PAGE_STATE.title,\n         (newTitle) => {\n          PAGE_STATE.title = newTitle;\n          wieldThePower();\n         });\n       });\n      }\n      function editNodeName(id) {\n       showEditModal(\"Edit Name\", NODE_DATA[id].name, (newName) => {\n        pushUndo(\"edit node name\");\n        NODE_DATA[id].name = newName;\n        const nodeGroup = document.querySelector(`g[data-node-id=\"${id}\"]`);\n        if (nodeGroup) {\n         const label = nodeGroup.querySelector(\".node-label\");\n         if (label) label.textContent = newName;\n        }\n        if (currentNodeId === id) {\n         document.getElementById(\"node-name\").textContent = newName;\n        }\n       });\n      }\n      function editNodeIp(id) {\n       showEditModal(\"Edit IP/Subtitle\", NODE_DATA[id].ip, (newIp) => {\n        pushUndo(\"edit node ip\");\n        NODE_DATA[id].ip = newIp;\n        const nodeGroup = document.querySelector(`g[data-node-id=\"${id}\"]`);\n        if (nodeGroup) {\n         const sub = nodeGroup.querySelector(\".node-sub\");\n         if (sub) sub.textContent = newIp;\n        }\n        if (currentNodeId === id) {\n         document.getElementById(\"node-ip\").textContent = newIp;\n        }\n       });\n      }\n      function claimTheImmortal(id) {\n\t   if (isViewOnly()) return;\n\t   if (!NODE_DATA[id]) return;\n       currentNodeId = id;\n       currentEdgeId = null;\n       currentRectId = null;\n       currentTextId = null;\n       if (isMobileDevice()) {\n        const currentHeight = PAGE_STATE.mobileFooterHeight || 40;\n        if (currentHeight <= 20) {\n         PAGE_STATE.mobileFooterHeight = 80;\n         const root = document.documentElement;\n         root.style.setProperty(\"--mobile-footer-height\", \"80vh\");\n         const mobileFooterHeightInput = document.getElementById(\"mobile-footer-height\");\n         const mobileFooterHeightVal = document.getElementById(\"mobile-footer-height-val\");\n         if (mobileFooterHeightInput) mobileFooterHeightInput.value = 80;\n         if (mobileFooterHeightVal) mobileFooterHeightVal.textContent = \"80%\";\n        }\n       }\n       const data = NODE_DATA[id];\n       document.querySelectorAll(\".node-group\").forEach((n) => {\n        n.classList.toggle(\"active\", n.dataset.nodeId === id);\n       });\n       document.querySelectorAll(\".edge\").forEach((e) => {\n        const active = e.dataset.from === id || e.dataset.to === id;\n        e.classList.toggle(\"active\", active);\n       });\n       document.querySelectorAll(\".rect-group\").forEach((r) => r.classList.remove(\"active\"));\n       document.querySelectorAll(\".text-element\").forEach((t) => t.classList.remove(\"active\"));\n       document.getElementById(\"node-panel\").style.display = \"block\";\n       document.getElementById(\"edge-panel\").style.display = \"none\";\n       document.getElementById(\"text-panel\").style.display = \"none\";\n       const toolbar = document.getElementById(\"topology-toolbar\");\n       if (!topologyToolbarCollapsed) {\n        toolbar.style.display = \"flex\";\n       }\n       updateTopologyToolbarVisibility();\n       document.getElementById(\"node-name\").textContent = data.name;\n       document.getElementById(\"node-ip\").textContent = data.ip;\nconst fovSection = document.getElementById(\"fov-section\");\nif (fovSection) {\n  if (hasCoverageZone(data.shape)) {\n    const defaults = getCoverageDefaults(data.shape);\n    fovSection.style.display = \"block\";\n    document.getElementById(\"fov-enabled\").checked = data.fovEnabled || false;\n    document.getElementById(\"fov-angle\").value = data.fovAngle || defaults.angle;\n    document.getElementById(\"fov-angle-value\").textContent = (data.fovAngle || defaults.angle) + \"°\";\n    document.getElementById(\"fov-distance\").value = data.fovDistance || defaults.distance;\n    document.getElementById(\"fov-distance-value\").textContent = data.fovDistance || defaults.distance;\n    document.getElementById(\"fov-inner-radius\").value = data.fovInnerRadius || 0;\n    document.getElementById(\"fov-inner-radius-value\").textContent = data.fovInnerRadius || 0;\n    document.getElementById(\"fov-rotation\").value = data.fovRotation || 0;\n    document.getElementById(\"fov-rotation-value\").textContent = (data.fovRotation || 0) + \"°\";\n    document.getElementById(\"fov-color\").value = data.fovColor || \"#f59e0b\";\n    document.getElementById(\"fov-opacity\").value = data.fovOpacity || 20;\n    document.getElementById(\"fov-opacity-value\").textContent = (data.fovOpacity || 20) + \"%\";\n    document.getElementById(\"fov-gradient\").checked = data.fovGradient || false;\n    document.getElementById(\"fov-border-color\").value = data.fovBorderColor || \"#f59e0b\";\n    document.getElementById(\"fov-border-width\").value = data.fovBorderWidth ?? 2;\n    document.getElementById(\"fov-border-width-value\").textContent = data.fovBorderWidth ?? 2;\n    document.getElementById(\"fov-border-style\").value = data.fovBorderStyle || \"solid\";\n    document.getElementById(\"fov-border-opacity\").value = data.fovBorderOpacity ?? 100;\n    document.getElementById(\"fov-border-opacity-value\").textContent = (data.fovBorderOpacity ?? 100) + \"%\";\n    document.getElementById(\"fov-label\").value = data.fovLabel || \"\";\n    document.getElementById(\"fov-label-position\").value = data.fovLabelPosition || \"center\";\n    document.getElementById(\"fov-label-size\").value = data.fovLabelSize || 14;\n    document.getElementById(\"fov-label-size-value\").textContent = (data.fovLabelSize || 14) + \"px\";\n    document.getElementById(\"fov-label-color\").value = data.fovLabelColor || \"#ffffff\";\n    document.getElementById(\"fov-label-bold\").checked = data.fovLabelBold || false;\n    document.getElementById(\"fov-label-bg\").checked = data.fovLabelBg || false;\n    document.getElementById(\"fov-label-bg-color\").value = data.fovLabelBgColor || \"#000000\";\n    document.getElementById(\"fov-label-offset-x\").value = data.fovLabelOffsetX || 0;\n    document.getElementById(\"fov-label-offset-x-value\").textContent = data.fovLabelOffsetX || 0;\n    document.getElementById(\"fov-label-offset-y\").value = data.fovLabelOffsetY || 0;\n    document.getElementById(\"fov-label-offset-y-value\").textContent = data.fovLabelOffsetY || 0;\n    document.getElementById(\"fov-animate\").checked = data.fovAnimate || false;\n    document.getElementById(\"fov-animation-type\").value = data.fovAnimationType || defaults.animationType;\n    document.getElementById(\"fov-sweep\").value = data.fovSweep || 120;\n    document.getElementById(\"fov-sweep-value\").textContent = (data.fovSweep || 120) + \"°\";\n    document.getElementById(\"fov-speed\").value = data.fovSpeed || 4;\n    document.getElementById(\"fov-speed-value\").textContent = (data.fovSpeed || 4) + \"s\";\n  } else {\n    fovSection.style.display = \"none\";\n  }\n}\n       document.getElementById(\"node-role\").textContent = data.role;\n       document.getElementById(\"node-mac\").textContent = data.mac || \"--\";\n       document.getElementById(\"node-rack\").textContent = data.rackUnit || \"--\";\n       document.getElementById(\"node-uheight\").textContent = (data.uHeight || \"1\") + \"U\";\n       document.getElementById(\"node-layer\").value = data.layer || \"physical\";\n       populateRackDropdown();\n       const assignedRackSelect = document.getElementById(\"node-assigned-rack\");\n       if (assignedRackSelect) {\n        assignedRackSelect.value = data.assignedRack || \"\";\n       }\n       const rackCapacitySelect = document.getElementById(\"node-rack-capacity\");\n       if (rackCapacitySelect) {\n        rackCapacitySelect.value = data.rackCapacity || \"42\";\n       }\n       const isRack = data.isRack === true;\n       const isAssignedToRack = !!data.assignedRack;\n       const assignedRackRow = document.getElementById(\"assigned-rack-row\");\n       const rackCapacityRow = document.getElementById(\"rack-capacity-row\");\n       const uheightRow = document.getElementById(\"uheight-row\");\n       if (assignedRackRow) assignedRackRow.style.display = isRack ? \"none\" : \"flex\";\n       if (rackCapacityRow) rackCapacityRow.style.display = isRack ? \"flex\" : \"none\";\n       if (uheightRow) uheightRow.style.display = isAssignedToRack ? \"flex\" : \"none\";\n       const rackContentsSection = document.getElementById(\"rack-contents-section\");\n       const rackContentsList = document.getElementById(\"rack-contents-list\");\n       if (rackContentsSection && rackContentsList) {\n        if (isRack) {\n         const nodesInRack = Object.entries(NODE_DATA).filter(([nid, n]) => n.assignedRack === id);\n         if (nodesInRack.length > 0) {\n          rackContentsList.innerHTML = '';\n          nodesInRack.forEach(([nid, n]) => {\n           const div = document.createElement('div');\n           div.style.cssText = 'padding: 6px 0; border-bottom: 1px solid var(--edge-main); cursor: pointer;';\n           div.onclick = () => claimTheImmortal(nid);\n           const nameSpan = document.createElement('span');\n           nameSpan.style.color = 'var(--text-main)';\n           nameSpan.textContent = n.name;\n           div.appendChild(nameSpan);\n           (n.tags || []).forEach(t => {\n            const tagSpan = document.createElement('span');\n            tagSpan.style.cssText = 'background: var(--accent); color: var(--bg); padding: 2px 6px; border-radius: 4px; font-size: 11px; margin-left: 4px;';\n            tagSpan.textContent = t;\n            div.appendChild(tagSpan);\n           });\n           rackContentsList.appendChild(div);\n          });\n          rackContentsSection.style.display = \"block\";\n         } else {\n          rackContentsList.innerHTML = '<div style=\"color: var(--text-soft); font-style: italic;\">No nodes assigned</div>';\n          rackContentsSection.style.display = \"block\";\n         }\n        } else {\n         rackContentsSection.style.display = \"none\";\n        }\n       }\n       const connectionsSection = document.getElementById(\"node-connections-section\");\n       const connectionsList = document.getElementById(\"node-connections-list\");\n       const connectionsCount = document.getElementById(\"node-connections-count\");\n       if (connectionsSection && connectionsList) {\n        const connectedEdges = (EDGE_DATA.list || []).filter(e => e.from === id || e.to === id);\n        connectionsCount.textContent = connectedEdges.length;\n        if (connectedEdges.length > 0) {\n         connectionsList.innerHTML = '';\n         connectedEdges.forEach(e => {\n          const isFrom = e.from === id;\n          const localPort = isFrom ? e.fromPort : e.toPort;\n          const remoteNodeId = isFrom ? e.to : e.from;\n          const remotePort = isFrom ? e.toPort : e.fromPort;\n          const remoteName = NODE_DATA[remoteNodeId]?.name || remoteNodeId;\n          const div = document.createElement('div');\n          div.style.cssText = 'padding: 6px 0; border-bottom: 1px solid var(--edge-main); display: flex; align-items: center; font-size: 13px;';\n          const localSpan = document.createElement('span');\n          localSpan.style.cssText = localPort ? 'color: var(--accent); font-family: monospace; cursor: pointer;' : 'color: var(--text-soft); cursor: pointer;';\n          localSpan.textContent = localPort || '-';\n          localSpan.title = localPort ? 'Click to view connection' : 'Click to set port';\n          localSpan.onclick = () => { if (localPort) { selectTheConnection(e.id); } else { const label = `Port on ${NODE_DATA[id]?.name || id}:`; const newVal = prompt(label, ''); if (newVal !== null && newVal !== '') { const isDupe = (EDGE_DATA.list || []).some(x => x.id !== e.id && ((x.from === id && x.fromPort === newVal) || (x.to === id && x.toPort === newVal))); if (isDupe && !confirm(`Warning: Port \"${newVal}\" is already used on ${NODE_DATA[id]?.name || id}. Use anyway?`)) return; if (isFrom) e.fromPort = newVal; else e.toPort = newVal; claimTheImmortal(id); } } };\n          const arrow = document.createElement('span');\n          arrow.style.cssText = 'color: var(--text-soft); margin: 0 8px;';\n          arrow.textContent = '↔';\n          const remoteSpan = document.createElement('span');\n          remoteSpan.style.cssText = 'color: var(--text-main); cursor: pointer;';\n          remoteSpan.textContent = remoteName;\n          remoteSpan.title = 'Click to view connection';\n          remoteSpan.onclick = () => { claimTheImmortal(remoteNodeId); focusOnSelected(); };\n          const remotePortSpan = document.createElement('span');\n          remotePortSpan.style.cssText = remotePort ? 'color: var(--accent); font-family: monospace; margin-left: 6px; cursor: pointer;' : 'color: var(--text-soft); margin-left: 6px; cursor: pointer;';\n          remotePortSpan.textContent = '(' + (remotePort || '-') + ')';\n          remotePortSpan.title = remotePort ? 'Click to view connection' : 'Click to set port';\n          remotePortSpan.onclick = () => { if (remotePort) { selectTheConnection(e.id); } else { const label = `Port on ${remoteName}:`; const newVal = prompt(label, ''); if (newVal !== null && newVal !== '') { const isDupe = (EDGE_DATA.list || []).some(x => x.id !== e.id && ((x.from === remoteNodeId && x.fromPort === newVal) || (x.to === remoteNodeId && x.toPort === newVal))); if (isDupe && !confirm(`Warning: Port \"${newVal}\" is already used on ${remoteName}. Use anyway?`)) return; if (isFrom) e.toPort = newVal; else e.fromPort = newVal; claimTheImmortal(id); } } };\n          div.appendChild(localSpan);\n          div.appendChild(arrow);\n          div.appendChild(remoteSpan);\n          div.appendChild(remotePortSpan);\n          connectionsList.appendChild(div);\n         });\n         connectionsSection.style.display = \"block\";\n        } else {\n         connectionsList.innerHTML = '<div style=\"color: var(--text-soft); font-style: italic;\">No connections</div>';\n         connectionsSection.style.display = \"block\";\n        }\n       }\n       document.getElementById(\"node-name\").onclick = () => editNodeName(id);\n       document.getElementById(\"node-ip\").onclick = () => editNodeIp(id);\ndocument.getElementById(\"fov-enabled\").onchange = function() {\n  if (!currentNodeId) return;\n  pushUndo(\"toggle coverage zone\");\n  NODE_DATA[currentNodeId].fovEnabled = this.checked;\n  updateFovCone(currentNodeId);\n};\ndocument.getElementById(\"fov-angle\").oninput = function() {\n  document.getElementById(\"fov-angle-value\").textContent = this.value + \"°\";\n  if (!currentNodeId) return;\n  NODE_DATA[currentNodeId].fovAngle = parseInt(this.value);\n  updateFovCone(currentNodeId);\n};\ndocument.getElementById(\"fov-distance\").oninput = function() {\n  document.getElementById(\"fov-distance-value\").textContent = this.value;\n  if (!currentNodeId) return;\n  NODE_DATA[currentNodeId].fovDistance = parseInt(this.value);\n  updateFovCone(currentNodeId);\n};\ndocument.getElementById(\"fov-inner-radius\").oninput = function() {\n  document.getElementById(\"fov-inner-radius-value\").textContent = this.value;\n  if (!currentNodeId) return;\n  NODE_DATA[currentNodeId].fovInnerRadius = parseInt(this.value);\n  updateFovCone(currentNodeId);\n};\ndocument.getElementById(\"fov-rotation\").oninput = function() {\n  document.getElementById(\"fov-rotation-value\").textContent = this.value + \"°\";\n  if (!currentNodeId) return;\n  NODE_DATA[currentNodeId].fovRotation = parseInt(this.value);\n  updateFovCone(currentNodeId);\n};\ndocument.getElementById(\"fov-color\").oninput = function() {\n  if (!currentNodeId) return;\n  pushUndo(\"change coverage color\");\n  NODE_DATA[currentNodeId].fovColor = this.value;\n  updateFovCone(currentNodeId);\n  updateZoneLegend();\n};\ndocument.getElementById(\"fov-opacity\").oninput = function() {\n  document.getElementById(\"fov-opacity-value\").textContent = this.value + \"%\";\n  if (!currentNodeId) return;\n  NODE_DATA[currentNodeId].fovOpacity = parseInt(this.value);\n  updateFovCone(currentNodeId);\n};\ndocument.getElementById(\"fov-gradient\").onchange = function() {\n  if (!currentNodeId) return;\n  pushUndo(\"toggle coverage gradient\");\n  NODE_DATA[currentNodeId].fovGradient = this.checked;\n  updateFovCone(currentNodeId);\n};\ndocument.getElementById(\"fov-border-color\").oninput = function() {\n  if (!currentNodeId) return;\n  pushUndo(\"change coverage border color\");\n  NODE_DATA[currentNodeId].fovBorderColor = this.value;\n  updateFovCone(currentNodeId);\n};\ndocument.getElementById(\"fov-border-width\").oninput = function() {\n  document.getElementById(\"fov-border-width-value\").textContent = this.value;\n  if (!currentNodeId) return;\n  NODE_DATA[currentNodeId].fovBorderWidth = parseInt(this.value);\n  updateFovCone(currentNodeId);\n};\ndocument.getElementById(\"fov-border-style\").onchange = function() {\n  if (!currentNodeId) return;\n  pushUndo(\"change coverage border style\");\n  NODE_DATA[currentNodeId].fovBorderStyle = this.value;\n  updateFovCone(currentNodeId);\n};\ndocument.getElementById(\"fov-border-opacity\").oninput = function() {\n  document.getElementById(\"fov-border-opacity-value\").textContent = this.value + \"%\";\n  if (!currentNodeId) return;\n  NODE_DATA[currentNodeId].fovBorderOpacity = parseInt(this.value);\n  updateFovCone(currentNodeId);\n};\ndocument.getElementById(\"fov-label\").oninput = function() {\n  if (!currentNodeId) return;\n  NODE_DATA[currentNodeId].fovLabel = this.value;\n  updateFovCone(currentNodeId);\n};\ndocument.getElementById(\"fov-label-position\").onchange = function() {\n  if (!currentNodeId) return;\n  NODE_DATA[currentNodeId].fovLabelPosition = this.value;\n  updateFovCone(currentNodeId);\n};\ndocument.getElementById(\"fov-label-size\").oninput = function() {\n  document.getElementById(\"fov-label-size-value\").textContent = this.value + \"px\";\n  if (!currentNodeId) return;\n  NODE_DATA[currentNodeId].fovLabelSize = parseInt(this.value);\n  updateFovCone(currentNodeId);\n};\ndocument.getElementById(\"fov-label-color\").oninput = function() {\n  if (!currentNodeId) return;\n  NODE_DATA[currentNodeId].fovLabelColor = this.value;\n  updateFovCone(currentNodeId);\n};\ndocument.getElementById(\"fov-label-bold\").onchange = function() {\n  if (!currentNodeId) return;\n  NODE_DATA[currentNodeId].fovLabelBold = this.checked;\n  updateFovCone(currentNodeId);\n};\ndocument.getElementById(\"fov-label-bg\").onchange = function() {\n  if (!currentNodeId) return;\n  NODE_DATA[currentNodeId].fovLabelBg = this.checked;\n  updateFovCone(currentNodeId);\n};\ndocument.getElementById(\"fov-label-bg-color\").oninput = function() {\n  if (!currentNodeId) return;\n  NODE_DATA[currentNodeId].fovLabelBgColor = this.value;\n  updateFovCone(currentNodeId);\n};\ndocument.getElementById(\"fov-label-offset-x\").oninput = function() {\n  document.getElementById(\"fov-label-offset-x-value\").textContent = this.value;\n  if (!currentNodeId) return;\n  NODE_DATA[currentNodeId].fovLabelOffsetX = parseInt(this.value);\n  updateFovCone(currentNodeId);\n};\ndocument.getElementById(\"fov-label-offset-y\").oninput = function() {\n  document.getElementById(\"fov-label-offset-y-value\").textContent = this.value;\n  if (!currentNodeId) return;\n  NODE_DATA[currentNodeId].fovLabelOffsetY = parseInt(this.value);\n  updateFovCone(currentNodeId);\n};\ndocument.getElementById(\"fov-preset\").addEventListener(\"change\", function() {\n  if (this.value) {\n    applyZonePreset(this.value);\n    this.value = \"\";\n  }\n});\ndocument.getElementById(\"fov-save-preset\").addEventListener(\"click\", saveCustomZonePreset);\ndocument.getElementById(\"fov-copy-style\").addEventListener(\"click\", function() {\n  if (currentNodeId && copyZoneStyle(currentNodeId)) {\n    alert(\"Zone style copied!\");\n  }\n});\ndocument.getElementById(\"fov-paste-style\").addEventListener(\"click\", function() {\n  if (currentNodeId && pasteZoneStyle(currentNodeId)) {\n    claimTheImmortal(currentNodeId);\n  }\n});\ndocument.getElementById(\"bulk-zone-copy\").addEventListener(\"click\", bulkCopyZoneStyle);\ndocument.getElementById(\"bulk-zone-paste\").addEventListener(\"click\", bulkPasteZoneStyle);\ndocument.getElementById(\"bulk-zone-toggle\").addEventListener(\"click\", bulkToggleZones);\ndocument.getElementById(\"bulk-zone-copy-mobile\").addEventListener(\"click\", bulkCopyZoneStyle);\ndocument.getElementById(\"bulk-zone-paste-mobile\").addEventListener(\"click\", bulkPasteZoneStyle);\ndocument.getElementById(\"bulk-zone-toggle-mobile\").addEventListener(\"click\", bulkToggleZones);\ndocument.getElementById(\"fov-animate\").onchange = function() {\n  if (!currentNodeId) return;\n  pushUndo(\"toggle coverage animation\");\n  NODE_DATA[currentNodeId].fovAnimate = this.checked;\n  updateFovCone(currentNodeId);\n};\ndocument.getElementById(\"fov-animation-type\").onchange = function() {\n  if (!currentNodeId) return;\n  pushUndo(\"change animation type\");\n  NODE_DATA[currentNodeId].fovAnimationType = this.value;\n  updateFovCone(currentNodeId);\n};\ndocument.getElementById(\"fov-sweep\").oninput = function() {\n  document.getElementById(\"fov-sweep-value\").textContent = this.value + \"°\";\n  if (!currentNodeId) return;\n  NODE_DATA[currentNodeId].fovSweep = parseInt(this.value);\n  updateFovCone(currentNodeId);\n};\ndocument.getElementById(\"fov-speed\").oninput = function() {\n  document.getElementById(\"fov-speed-value\").textContent = this.value + \"s\";\n  if (!currentNodeId) return;\n  NODE_DATA[currentNodeId].fovSpeed = parseInt(this.value);\n  updateFovCone(currentNodeId);\n};\n       document.getElementById(\"node-mac\").onclick = () => editNodeMac(id);\n       document.getElementById(\"node-rack\").onclick = () => editNodeRack(id);\n       document.getElementById(\"node-uheight\").onclick = () => editNodeUHeight(id);\n       const currentSize = savedSizes[id] || getDefaultSize();\n       document.getElementById(\"size-slider\").value = currentSize;\n       document.getElementById(\"size-value\").textContent = currentSize;\n       const currentRotation = NODE_DATA[id].rotation || 0;\n       document.getElementById(\"rotation-slider\").value = Math.max(-360, Math.min(360, currentRotation));\n       document.getElementById(\"rotation-value\").value = currentRotation;\n       const styleEntry = savedStyles[id] || {};\n       const resolvedStyles = resolveStylesEntry(styleEntry);\n       const scopeKey = currentStyleScope || \"all\";\n       const flatProps = [\"circleColor\", \"circleBorder\", \"titleColor\", \"titleFont\", \"titleSize\", \"subColor\", \"subFont\", \"subSize\", ];\n       const isFlat = flatProps.some((p) => Object.prototype.hasOwnProperty.call(styleEntry, p));\n       const scopedStyles = isFlat ? styleEntry : styleEntry[scopeKey] || {};\n       const circleColorInput = document.getElementById(\"circle-color\");\n       const titleColorInput = document.getElementById(\"title-color\");\n       const titleFontSelect = document.getElementById(\"title-font\");\n       const titleSizeInput = document.getElementById(\"title-size\");\n       const subColorInput = document.getElementById(\"sub-color\");\n       const subFontSelect = document.getElementById(\"sub-font\");\n       const subSizeInput = document.getElementById(\"sub-size\");\n       const shapeSelect = document.getElementById(\"shape-select\");\n       const scopeSelect = document.getElementById(\"style-scope\");\n      circleColorInput.value = scopedStyles.circleColor || resolvedStyles.circleColor || PAGE_STATE.nodeFill || \"#1e293b\";\n       const circleBorderInput = document.getElementById(\"circle-border\");\n       circleBorderInput.value = scopedStyles.circleBorder || resolvedStyles.circleBorder || PAGE_STATE.nodeStroke || \"#475569\";\n        titleColorInput.value = scopedStyles.titleColor || resolvedStyles.titleColor || PAGE_STATE.textMain || \"#e2e8f0\";\n        titleFontSelect.value = scopedStyles.titleFont || resolvedStyles.titleFont || \"system-ui, sans-serif\";\n        titleSizeInput.value = scopedStyles.titleSize || resolvedStyles.titleSize || 18;\n        subColorInput.value = scopedStyles.subColor || resolvedStyles.subColor || PAGE_STATE.textSoft || \"#94a3b8\";\n       subFontSelect.value = scopedStyles.subFont || resolvedStyles.subFont || \"system-ui, sans-serif\";\n       subSizeInput.value = scopedStyles.subSize || resolvedStyles.subSize || 13;\n       shapeSelect.value = data.shape || \"circle\";\n       scopeSelect.value = currentStyleScope || \"all\";\n       document.getElementById(\"title-offset-y\").value = scopedStyles.titleOffsetY || resolvedStyles.titleOffsetY || 0;\n       document.getElementById(\"title-offset-x\").value = scopedStyles.titleOffsetX || resolvedStyles.titleOffsetX || 0;\n       document.getElementById(\"sub-offset-y\").value = scopedStyles.subOffsetY || resolvedStyles.subOffsetY || 0;\n       document.getElementById(\"sub-offset-x\").value = scopedStyles.subOffsetX || resolvedStyles.subOffsetX || 0;\n       const tagEl = document.getElementById(\"node-tags\");\n       tagEl.innerHTML = \"\";\n       data.tags.forEach((tag, i) => {\n        const b = document.createElement(\"span\");\n        b.className = \"badge\";\n        if (tag.toLowerCase().includes(\"wg\")) b.classList.add(\"wg\");\n        b.style.cursor = \"pointer\";\n        b.style.position = \"relative\";\n        const tagText = document.createElement(\"span\");\n        tagText.textContent = tag;\n        tagText.addEventListener(\"click\", (e) => {\n         e.stopPropagation();\n         showEditModal(\"Edit Tag\", tag, (newTag) => {\n          if (newTag) {\n           pushUndo(\"edit tag\");\n           data.tags[i] = newTag;\n           claimTheImmortal(id);\n          }\n         });\n        });\n        const deleteTag = document.createElement(\"span\");\n        deleteTag.textContent = \" ✕\";\n        deleteTag.style.opacity = \"0.6\";\n        deleteTag.style.marginLeft = \"4px\";\n        deleteTag.style.fontSize = \"10px\";\n        deleteTag.addEventListener(\"click\", (e) => {\n         e.stopPropagation();\n         pushUndo(\"delete tag\");\n         data.tags.splice(i, 1);\n         claimTheImmortal(id);\n        });\n        b.append(tagText, deleteTag);\n        tagEl.append(b);\n       });\n       const addTagBtn = document.createElement(\"span\");\n       addTagBtn.className = \"badge\";\n       addTagBtn.style.cursor = \"pointer\";\n       addTagBtn.style.opacity = \"0.6\";\n       addTagBtn.style.borderStyle = \"dashed\";\n       addTagBtn.textContent = \"+ Add Tag\";\n       addTagBtn.addEventListener(\"click\", () => {\n        showEditModal(\"Add Tag(s) : comma separated\", \"\",\n         (newTagStr) => {\n          if (newTagStr) {\n           pushUndo(\"add tags\");\n           const newTags = newTagStr.split(\",\").map((t) => t.trim()).filter((t) => t);\n           newTags.forEach((t) => data.tags.push(t));\n           claimTheImmortal(id);\n          }\n         });\n       });\n       tagEl.append(addTagBtn);\n       const notesEl = document.getElementById(\"node-notes\");\n       notesEl.innerHTML = \"\";\n       data.notes.forEach((note, i) => {\n        const li = document.createElement(\"li\");\n        const noteText = document.createElement(\"span\");\n        noteText.textContent = note;\n        noteText.style.flex = \"1\";\n        const deleteBtn = document.createElement(\"span\");\n        deleteBtn.className = \"delete-note\";\n        deleteBtn.textContent = \"✕\";\n        deleteBtn.addEventListener(\"click\", (e) => {\n         e.stopPropagation();\n         challengeTheImmortal(\"Are you sure you want to delete this note?\",\n          () => {\n           pushUndo(\"delete note\");\n           data.notes.splice(i, 1);\n           claimTheImmortal(id);\n          });\n        });\n        li.append(noteText, deleteBtn);\n        noteText.addEventListener(\"dblclick\", () => {\n         noteText.classList.add(\"editing\");\n         noteText.contentEditable = true;\n         noteText.focus();\n        });\n        noteText.addEventListener(\"blur\", () => {\n         noteText.classList.remove(\"editing\");\n         noteText.contentEditable = false;\n         data.notes[i] = noteText.textContent;\n        });\n        notesEl.append(li);\n       });\n      const addLineSelect = document.getElementById(\"add-line-select\");\n      addLineSelect.innerHTML = \"\";\n      const currentRack = data.assignedRack || \"\";\n      Object.entries(NODE_DATA).forEach(([nodeId, node]) => {\n      if (nodeId !== id) {\n      const nodeRack = node.assignedRack || \"\";\n      if (currentRack === nodeRack) {\n      const opt = document.createElement(\"option\");\n      opt.value = nodeId;\n      opt.textContent = node.name;\n      addLineSelect.appendChild(opt);\n      }\n      }\n      });\n      }\n      function selectTheConnection(id) {\n\t   if (isViewOnly()) return;\n       currentEdgeId = id;\n       currentNodeId = null;\n       currentRectId = null;\n       currentTextId = null;\n       if (isMobileDevice()) {\n        const currentHeight = PAGE_STATE.mobileFooterHeight || 40;\n        if (currentHeight <= 20) {\n         PAGE_STATE.mobileFooterHeight = 80;\n         const root = document.documentElement;\n         root.style.setProperty(\"--mobile-footer-height\", \"80vh\");\n         const mobileFooterHeightInput = document.getElementById(\"mobile-footer-height\");\n         const mobileFooterHeightVal = document.getElementById(\"mobile-footer-height-val\");\n         if (mobileFooterHeightInput) mobileFooterHeightInput.value = 80;\n         if (mobileFooterHeightVal) mobileFooterHeightVal.textContent = \"80%\";\n        }\n       }\n       document.getElementById(\"node-panel\").style.display = \"none\";\n       document.getElementById(\"edge-panel\").style.display = \"block\";\n       document.getElementById(\"text-panel\").style.display = \"none\";\n       document.getElementById(\"rect-panel\").style.display = \"none\";\n       document.getElementById(\"topology-toolbar\").style.display = \"none\";\n       document.querySelectorAll(\".node-group\").forEach((n) => n.classList.remove(\"active\"));\n       document.querySelectorAll(\".rect-group\").forEach((r) => r.classList.remove(\"active\"));\n       document.querySelectorAll(\".text-element\").forEach((t) => t.classList.remove(\"active\"));\n       document.querySelectorAll(\".edge\").forEach((e) => {\n        e.classList.toggle(\"active\", e.dataset.edgeId === id);\n       });\n       const edge = EDGE_DATA.list.find((e) => e.id === id);\n       if (!edge) return;\n       const directionSymbols = {\n        none: \"⇄\",\n        forward: \"→\",\n        backward: \"←\",\n        both: \"↔\",\n       };\n       const dirSymbol = directionSymbols[edge.direction] || \"⇄\";\n       let titleText = \"Custom line\";\n       if (edge.from || edge.to) {\n        const fromName = edge.from ? NODE_DATA[edge.from]?.name || edge.from : \"\";\n        const toName = edge.to ? NODE_DATA[edge.to]?.name || edge.to : \"\";\n        titleText = `${fromName || \"?\"} ${dirSymbol} ${toName || \"?\"}`;\n       }\n       document.getElementById(\"edge-title\").textContent = titleText;\n       const widthInput = document.getElementById(\"edge-width\");\n       const colorInput = document.getElementById(\"edge-color\");\n       const directionSelect = document.getElementById(\"edge-direction\");\n       const lineStyleSelect = document.getElementById(\"edge-line-style\");\n       const routingSelect = document.getElementById(\"edge-routing\");\n       widthInput.value = edge.width;\n       colorInput.value = edge.color;\n       directionSelect.value = edge.direction || \"none\";\n       lineStyleSelect.value = edge.lineStyle || \"solid\";\n       routingSelect.value = edge.routing || \"curved\";\n       document.getElementById(\"edge-animate\").checked = edge.animate === true;\n       document.getElementById(\"edge-animation-style\").value = edge.animationStyle || \"\";\n       document.getElementById(\"edge-animation-speed\").value = edge.animationSpeed || \"\";\n       const fromPortRow = document.getElementById(\"edge-from-port-row\");\n       const toPortRow = document.getElementById(\"edge-to-port-row\");\n       const fromPortInput = document.getElementById(\"edge-from-port\");\n       const toPortInput = document.getElementById(\"edge-to-port\");\n       if (edge.type === \"custom\") {\n        fromPortRow.style.display = \"none\";\n        toPortRow.style.display = \"none\";\n       } else {\n        fromPortRow.style.display = \"flex\";\n        toPortRow.style.display = \"flex\";\n        fromPortInput.value = edge.fromPort || \"\";\n        toPortInput.value = edge.toPort || \"\";\n        fromPortInput.onchange = () => updateEdgePortLabels(id);\n        toPortInput.onchange = () => updateEdgePortLabels(id);\n       }\n       const list = document.getElementById(\"edge-notes\");\n       list.innerHTML = \"\";\n       edge.notes.forEach((note, i) => {\n        const li = document.createElement(\"li\");\n        const txt = document.createElement(\"span\");\n        txt.textContent = note;\n        txt.style.flex = \"1\";\n        const del = document.createElement(\"span\");\n        del.className = \"delete-note\";\n        del.textContent = \"✕\";\n        del.addEventListener(\"click\", (e) => {\n         e.stopPropagation();\n         challengeTheImmortal(\"Delete this line note?\", () => {\n          pushUndo(\"delete edge note\");\n          edge.notes.splice(i, 1);\n          selectTheConnection(id);\n         });\n        });\n        txt.addEventListener(\"dblclick\", () => {\n         txt.classList.add(\"editing\");\n         txt.contentEditable = true;\n         txt.focus();\n        });\n        txt.addEventListener(\"blur\", () => {\n         txt.classList.remove(\"editing\");\n         txt.contentEditable = false;\n         pushUndo(\"edit edge note\");\n         edge.notes[i] = txt.textContent;\n        });\n        li.append(txt, del);\n        list.appendChild(li);\n       });\n       if (edge.type === \"custom\" && Array.isArray(edge.points)) {\n        forgeTheTopology();\n       }\n      }\n      window.addEventListener(\"resize\", () => {\n       forgeTheTopology();\n       if (currentEdgeId) {\n        selectTheConnection(currentEdgeId);\n       } else if (currentNodeId && NODE_DATA[currentNodeId]) {\n        claimTheImmortal(currentNodeId);\n       } else {\n        const availableNodes = Object.keys(NODE_DATA);\n        if (availableNodes.length > 0) {\n         claimTheImmortal(availableNodes[0]);\n        }\n       }\n      });\n      (function initZoomPan() {\n       const viewport = document.getElementById(\"canvas-viewport\");\n       const svg = document.getElementById(\"map\");\n       const hint = document.getElementById(\"canvas-hint\");\n       hint.style.cursor = \"pointer\";\n       let hintDismissed = false;\n       const dismissHint = () => { hintDismissed = true; hint.classList.remove(\"visible\"); };\n       hint.addEventListener(\"click\", dismissHint);\n       hint.addEventListener(\"touchend\", (e) => { e.preventDefault(); dismissHint(); });\n       setTimeout(() => {\n        if (hintDismissed) return;\n        hint.classList.add(\"visible\");\n        setTimeout(() => { if (!hintDismissed) hint.classList.remove(\"visible\"); }, 4000);\n       }, 1000);\n       viewport.addEventListener(\"wheel\",\n        (e) => {\n         e.preventDefault();\n         const rect = viewport.getBoundingClientRect();\n         const mouseX = (e.clientX - rect.left) / rect.width;\n         const mouseY = (e.clientY - rect.top) / rect.height;\n         const delta = e.deltaY > 0 ? 0.9 : 1.1;\n         zoomTo(canvasState.zoom * delta, mouseX, mouseY);\n        }, {\n         passive: false\n        });\n       let initialPinchDistance = 0;\n       let initialPinchZoom = 1;\n       let pinchCenter = {\n        x: 0.5,\n        y: 0.5\n       };\n       let threeFingerTapStart = 0;\n       viewport.addEventListener(\"touchstart\",\n        (e) => {\n         if (e.touches.length === 3) {\n          e.preventDefault();\n          threeFingerTapStart = Date.now();\n         }\n         if (e.touches.length === 2) {\n          e.preventDefault();\n          const touch1 = e.touches[0];\n          const touch2 = e.touches[1];\n          initialPinchDistance = Math.hypot(touch2.clientX - touch1.clientX, touch2.clientY - touch1.clientY);\n          initialPinchZoom = canvasState.zoom;\n          const rect = viewport.getBoundingClientRect();\n          const centerX = (touch1.clientX + touch2.clientX) / 2;\n          const centerY = (touch1.clientY + touch2.clientY) / 2;\n          pinchCenter.x = (centerX - rect.left) / rect.width;\n          pinchCenter.y = (centerY - rect.top) / rect.height;\n         }\n        }, {\n         passive: false\n        });\n       viewport.addEventListener(\"touchend\", (e) => {\n        if (e.touches.length === 0 && threeFingerTapStart > 0) {\n         const duration = Date.now() - threeFingerTapStart;\n         if (duration < 500) {\n          e.preventDefault();\n          undo();\n          if (navigator.vibrate) navigator.vibrate([50, 30, 50]);\n         }\n         threeFingerTapStart = 0;\n        }\n       }, { passive: false });\n       viewport.addEventListener(\"touchmove\",\n        (e) => {\n         if (e.touches.length === 2) {\n          e.preventDefault();\n          const touch1 = e.touches[0];\n          const touch2 = e.touches[1];\n          const currentDistance = Math.hypot(touch2.clientX - touch1.clientX, touch2.clientY - touch1.clientY);\n          if (initialPinchDistance > 0) {\n           const scale = currentDistance / initialPinchDistance;\n           const newZoom = initialPinchZoom * scale;\n           zoomTo(newZoom, pinchCenter.x, pinchCenter.y);\n          }\n         }\n        }, {\n         passive: false\n        });\n       let panStartViewX = 0;\n       let panStartViewY = 0;\n       let lastEmptyTapTime = 0;\n       let emptyTapTimeout = null;\n       let emptyTapMoved = false;\n       let emptyTapStartX = 0;\n       let emptyTapStartY = 0;\n       viewport.addEventListener(\"touchend\", (e) => {\n         if (currentView.mode !== \"rack\") return;\n         if (e.changedTouches.length !== 1) return;\n      const isNodeOrEdge = e.target.closest(\".node-group\") || e.target.closest(\".edge-group\");\n         if (isNodeOrEdge) return;\n         if (emptyTapMoved) {\n           emptyTapMoved = false;\n           return;\n         }\n         const currentTime = new Date().getTime();\n         const tapGap = currentTime - lastEmptyTapTime;\n         if (tapGap < 300 && tapGap > 0) {\n           e.preventDefault();\n           exitRack();\n           if (navigator.vibrate) {\n             navigator.vibrate(50);\n           }\n           lastEmptyTapTime = 0;\n           if (emptyTapTimeout) {\n             clearTimeout(emptyTapTimeout);\n             emptyTapTimeout = null;\n           }\n         } else {\n           lastEmptyTapTime = currentTime;\n           if (emptyTapTimeout) clearTimeout(emptyTapTimeout);\n           emptyTapTimeout = setTimeout(() => {\n             lastEmptyTapTime = 0;\n           }, 300);\n         }\n       }, { passive: false });\n       viewport.addEventListener(\"mousedown\", (e) => {\n        if (e.target.closest(\".draw-toolbar\") || e.target.closest(\".topology-toolbar\") || e.target.closest(\".legend-container\")) {\n         return;\n        }\n        if (freeDrawMode || rectDrawMode) {\n         return;\n        }\n      const isEmptySpace = e.target === svg || e.target === viewport || e.target.tagName === \"svg\";\n      const isEdgeElement = e.target.closest('.edge') || e.target.classList.contains('edge-edit-point') || e.target.dataset.edgeId;\n      const isTextElement = e.target.closest('.text-group') || e.target.closest('.text-element');\n      const isRectElement = e.target.closest('.rect-group');\n      if (!isEdgeElement && !isTextElement && !isRectElement && currentEdgeId) {\n      currentEdgeId = null;\n      forgeTheTopology();\n      }\n      if (!isRectElement && currentRectId) {\n      currentRectId = null;\n      document.getElementById(\"rect-panel\").style.display = \"none\";\n      forgeTheTopology();\n      }\n\t  if (isViewOnly()) {\n         document.body.classList.remove(\"view-only-inspect\");\n         document.getElementById(\"node-panel\").style.display = \"none\";\n         document.getElementById(\"edge-panel\").style.display = \"none\";\n        }\n        if (isEmptySpace && e.shiftKey && e.button === 0) {\n         if (isViewOnly()) return;\n         e.preventDefault();\n         startSelection(e);\n         return;\n        }\n        if (isEmptySpace || e.button === 2 || e.button === 1) {\n         e.preventDefault();\n         canvasState.isPanning = true;\n         canvasState.panStartX = e.clientX;\n         canvasState.panStartY = e.clientY;\n         panStartViewX = canvasState.panX;\n         panStartViewY = canvasState.panY;\n         viewport.classList.add(\"panning\");\n        }\n       });\n       viewport.addEventListener(\"touchstart\",\n        (e) => {\n         if (e.target.closest(\".draw-toolbar\") || e.target.closest(\".topology-toolbar\") || e.target.closest(\".legend-container\")) {\n          return;\n         }\n         if (freeDrawMode || rectDrawMode) {\n          return;\n         }\n      const isEmptySpace = e.target === svg || e.target === viewport || e.target.tagName === \"svg\";\n      const isEdgeElement = e.target.closest('.edge') || e.target.classList.contains('edge-edit-point') || e.target.dataset.edgeId;\n      const isTextElement = e.target.closest('.text-group') || e.target.closest('.text-element');\n      const isRectElement = e.target.closest('.rect-group');\n      if (!isEdgeElement && !isTextElement && !isRectElement && currentEdgeId) {\n      currentEdgeId = null;\n      forgeTheTopology();\n      }\n      if (!isRectElement && currentRectId) {\n      currentRectId = null;\n      document.getElementById(\"rect-panel\").style.display = \"none\";\n      forgeTheTopology();\n      }\n         if (e.touches.length === 1) {\n           e.preventDefault();\n           emptyTapMoved = false;\n           emptyTapStartX = e.touches[0].clientX;\n           emptyTapStartY = e.touches[0].clientY;\n           canvasState.isPanning = true;\n           canvasState.panStartX = e.touches[0].clientX;\n           canvasState.panStartY = e.touches[0].clientY;\n           panStartViewX = canvasState.panX;\n           panStartViewY = canvasState.panY;\n           viewport.classList.add(\"panning\");\n         }\n        }, {\n         passive: false\n        });\n       let panRAFPending = false;\n       let lastPanEvent = null;\n       document.addEventListener(\"mousemove\", (e) => {\n        if (isSelecting) {\n         updateSelection(e);\n         return;\n        }\n        if (!canvasState.isPanning) return;\n        lastPanEvent = e;\n        if (!panRAFPending) {\n         panRAFPending = true;\n         requestAnimationFrame(() => {\n          if (lastPanEvent && canvasState.isPanning) {\n           const dx = lastPanEvent.clientX - canvasState.panStartX;\n           const dy = lastPanEvent.clientY - canvasState.panStartY;\n           const rect = viewport.getBoundingClientRect();\n           const viewWidth = CANVAS_WIDTH / canvasState.zoom;\n           const viewHeight = CANVAS_HEIGHT / canvasState.zoom;\n           const canvasDx = (dx / rect.width) * viewWidth;\n           const canvasDy = (dy / rect.height) * viewHeight;\n           canvasState.panX = panStartViewX - canvasDx;\n           canvasState.panY = panStartViewY - canvasDy;\n           constrainPan();\n           updateViewBox();\n          }\n          panRAFPending = false;\n         });\n        }\n       });\n       document.addEventListener(\"touchmove\", (e) => {\n        if (!canvasState.isPanning || !e.touches[0]) return;\n        const touchX = e.touches[0].clientX;\n        const touchY = e.touches[0].clientY;\n        const moveDx = Math.abs(touchX - emptyTapStartX);\n        const moveDy = Math.abs(touchY - emptyTapStartY);\n        if (moveDx > 15 || moveDy > 15) {\n         emptyTapMoved = true;\n        }\n        if (!panRAFPending) {\n         panRAFPending = true;\n         requestAnimationFrame(() => {\n          if (canvasState.isPanning) {\n           const dx = touchX - canvasState.panStartX;\n           const dy = touchY - canvasState.panStartY;\n           const rect = viewport.getBoundingClientRect();\n           const viewWidth = CANVAS_WIDTH / canvasState.zoom;\n           const viewHeight = CANVAS_HEIGHT / canvasState.zoom;\n           const canvasDx = (dx / rect.width) * viewWidth;\n           const canvasDy = (dy / rect.height) * viewHeight;\n           canvasState.panX = panStartViewX - canvasDx;\n           canvasState.panY = panStartViewY - canvasDy;\n           constrainPan();\n           updateViewBox();\n          }\n          panRAFPending = false;\n         });\n        }\n       });\n       document.addEventListener(\"mouseup\", () => {\n        if (isSelecting) {\n         endSelection();\n        }\n        if (canvasState.isPanning) {\n         canvasState.isPanning = false;\n         viewport.classList.remove(\"panning\");\n        }\n       });\n       document.addEventListener(\"touchend\", () => {\n        if (canvasState.isPanning) {\n         canvasState.isPanning = false;\n         viewport.classList.remove(\"panning\");\n        }\n       });\n       document.addEventListener(\"keydown\", (e) => {\n        const isEditing = document.activeElement.tagName === \"INPUT\" || document.activeElement.tagName === \"TEXTAREA\" || document.activeElement.isContentEditable;\n        if (e.code === \"Space\" && !e.repeat && !isEditing) {\n         e.preventDefault();\n         canvasState.spacePressed = true;\n         viewport.style.cursor = \"grab\";\n        }\n       });\n       document.addEventListener(\"keyup\", (e) => {\n        if (e.code === \"Space\") {\n         canvasState.spacePressed = false;\n         viewport.style.cursor = \"\";\n        }\n       });\n       document.getElementById(\"zoom-in-btn\").addEventListener(\"click\", () => {\n        zoomTo(canvasState.zoom * 1.25, 0.5, 0.5);\n       });\n       document.getElementById(\"zoom-out-btn\").addEventListener(\"click\", () => {\n        zoomTo(canvasState.zoom * 0.8, 0.5, 0.5);\n       });\n       document.getElementById(\"zoom-fit-btn\").addEventListener(\"click\", fitToContent);\n       document.getElementById(\"zoom-reset-btn\").addEventListener(\"click\", resetView);\n       const minimapContainer = document.getElementById(\"minimap-container\");\n       const minimapSvg = document.getElementById(\"minimap\");\n       let minimapDragging = false;\n       minimapContainer.addEventListener(\"mousedown\", (e) => {\n        e.preventDefault();\n        minimapDragging = true;\n        updatePanFromMinimap(e);\n       });\n       minimapContainer.addEventListener(\"touchstart\",\n        (e) => {\n         e.preventDefault();\n         minimapDragging = true;\n         updatePanFromMinimapTouch(e);\n        }, {\n         passive: false\n        });\n       document.addEventListener(\"mousemove\", (e) => {\n        if (minimapDragging) {\n         updatePanFromMinimap(e);\n        }\n       });\n       document.addEventListener(\"touchmove\", (e) => {\n        if (minimapDragging && e.touches[0]) {\n         updatePanFromMinimapTouch(e);\n        }\n       });\n       document.addEventListener(\"mouseup\", () => {\n        minimapDragging = false;\n       });\n       document.addEventListener(\"touchend\", () => {\n        minimapDragging = false;\n       });\n       function updatePanFromMinimap(e) {\n        const rect = minimapContainer.getBoundingClientRect();\n        const x = (e.clientX - rect.left) / rect.width;\n        const y = (e.clientY - rect.top) / rect.height;\n        const viewWidth = CANVAS_WIDTH / canvasState.zoom;\n        const viewHeight = CANVAS_HEIGHT / canvasState.zoom;\n        canvasState.panX = x * CANVAS_WIDTH - viewWidth / 2;\n        canvasState.panY = y * CANVAS_HEIGHT - viewHeight / 2;\n        constrainPan();\n        updateViewBox();\n       }\n       function updatePanFromMinimapTouch(e) {\n        const rect = minimapContainer.getBoundingClientRect();\n        const touch = e.touches[0];\n        const x = (touch.clientX - rect.left) / rect.width;\n        const y = (touch.clientY - rect.top) / rect.height;\n        const viewWidth = CANVAS_WIDTH / canvasState.zoom;\n        const viewHeight = CANVAS_HEIGHT / canvasState.zoom;\n        canvasState.panX = x * CANVAS_WIDTH - viewWidth / 2;\n        canvasState.panY = y * CANVAS_HEIGHT - viewHeight / 2;\n        constrainPan();\n        updateViewBox();\n       }\n       document.addEventListener(\"keydown\", (e) => {\n        if (document.activeElement.tagName === \"INPUT\" || document.activeElement.tagName === \"TEXTAREA\") return;\n        if (\n         (e.key === \"+\" || e.key === \"=\") && (e.ctrlKey || e.metaKey)) {\n         e.preventDefault();\n         zoomTo(canvasState.zoom * 1.25, 0.5, 0.5);\n        } else if (e.key === \"-\" && (e.ctrlKey || e.metaKey)) {\n         e.preventDefault();\n         zoomTo(canvasState.zoom * 0.8, 0.5, 0.5);\n        } else if (e.key === \"0\" && (e.ctrlKey || e.metaKey)) {\n         e.preventDefault();\n         resetView();\n        }\n       });\n       setTimeout(() => {\n        fitToContent();\n       }, 100);\n      })();\n      const sizeSlider = document.getElementById(\"size-slider\");\n      const sizeValue = document.getElementById(\"size-value\");\n      const resetSizeBtn = document.getElementById(\"reset-size\");\n      sizeSlider.addEventListener(\"input\", () => {\n       const newSize = parseInt(sizeSlider.value, 10);\n       sizeValue.textContent = newSize;\n       pushUndo(\"resize node\");\n       savedSizes[currentNodeId] = newSize;\n       const nodeGroup = document.querySelector(`g[data-node-id=\"${currentNodeId}\"]`);\n       if (nodeGroup) {\n        const oldShape = nodeGroup.querySelector(\".node-circle\");\n        if (oldShape) oldShape.remove();\n        const shapeType = (NODE_DATA[currentNodeId] && NODE_DATA[currentNodeId].shape) || \"circle\";\n        const newShape = createShapeElement(shapeType, newSize);\n        newShape.classList.add(\"node-circle\");\n        const styles = resolveStylesForNode(currentNodeId);\n        if (styles.circleColor) newShape.style.fill = styles.circleColor;\n        if (styles.circleBorder) newShape.style.stroke = styles.circleBorder;\n        nodeGroup.insertBefore(newShape, nodeGroup.firstChild);\n        const label = nodeGroup.querySelector(\".node-label\");\n        const sub = nodeGroup.querySelector(\".node-sub\");\n        if (label) {\n         label.setAttribute(\"y\", -newSize * 0.28);\n         const labelSize = styles.titleSize || newSize * 0.33;\n         label.style.fontSize = labelSize + \"px\";\n        }\n        if (sub) {\n         sub.setAttribute(\"y\", newSize * 0.4);\n         const subSize = styles.subSize || newSize * 0.24;\n         sub.style.fontSize = subSize + \"px\";\n        }\n       }\n       updateMinimap();\n      });\n      resetSizeBtn.addEventListener(\"click\", () => {\n       pushUndo(\"reset size\");\n       delete savedSizes[currentNodeId];\n       const defaultSize = getDefaultSize();\n       sizeSlider.value = defaultSize;\n       sizeValue.textContent = defaultSize;\n       const nodeGroup = document.querySelector(`g[data-node-id=\"${currentNodeId}\"]`);\n       if (nodeGroup) {\n        const oldShape = nodeGroup.querySelector(\".node-circle\");\n        if (oldShape) oldShape.remove();\n        const shapeType = (NODE_DATA[currentNodeId] && NODE_DATA[currentNodeId].shape) || \"circle\";\n        const newShape = createNodeShape(currentNodeId, defaultSize);\n        nodeGroup.insertBefore(newShape, nodeGroup.firstChild);\n        const styles = resolveStylesForNode(currentNodeId);\n        if (styles.circleColor) newShape.style.fill = styles.circleColor;\n        if (styles.circleBorder) newShape.style.stroke = styles.circleBorder;\n        const label = nodeGroup.querySelector(\".node-label\");\n        const sub = nodeGroup.querySelector(\".node-sub\");\n        if (label) {\n         label.setAttribute(\"y\", -defaultSize * 0.28);\n         const labelSize = styles.titleSize || defaultSize * 0.33;\n         label.style.fontSize = labelSize + \"px\";\n        }\n        if (sub) {\n         sub.setAttribute(\"y\", defaultSize * 0.4);\n         const subSize = styles.subSize || defaultSize * 0.24;\n         sub.style.fontSize = subSize + \"px\";\n        }\n       }\n       updateMinimap();\n      });\n      const rotationSlider = document.getElementById(\"rotation-slider\");\n      const rotationInput = document.getElementById(\"rotation-value\");\n      const resetRotationBtn = document.getElementById(\"reset-rotation\");\n      rotationSlider.addEventListener(\"input\", () => {\n        const newRotation = parseInt(rotationSlider.value, 10);\n        rotationInput.value = newRotation;\n        pushUndo(\"rotate node\");\n        NODE_DATA[currentNodeId].rotation = newRotation;\n        const nodeGroup = document.querySelector(`g[data-node-id=\"${currentNodeId}\"]`);\n        if (nodeGroup) {\n          const pos = savedPositions[currentNodeId] || { x: 0, y: 0 };\n          nodeGroup.setAttribute(\"transform\", `translate(${pos.x},${pos.y}) rotate(${newRotation})`);\n        }\n      });\n      rotationInput.addEventListener(\"input\", () => {\n        const newRotation = parseInt(rotationInput.value, 10) || 0;\n        rotationSlider.value = Math.max(-360, Math.min(360, newRotation));\n        pushUndo(\"rotate node\");\n        NODE_DATA[currentNodeId].rotation = newRotation;\n        const nodeGroup = document.querySelector(`g[data-node-id=\"${currentNodeId}\"]`);\n        if (nodeGroup) {\n          const pos = savedPositions[currentNodeId] || { x: 0, y: 0 };\n          nodeGroup.setAttribute(\"transform\", `translate(${pos.x},${pos.y}) rotate(${newRotation})`);\n        }\n      });\n      resetRotationBtn.addEventListener(\"click\", () => {\n        pushUndo(\"reset rotation\");\n        NODE_DATA[currentNodeId].rotation = 0;\n        rotationSlider.value = 0;\n        rotationInput.value = 0;\n        const nodeGroup = document.querySelector(`g[data-node-id=\"${currentNodeId}\"]`);\n        if (nodeGroup) {\n          const pos = savedPositions[currentNodeId] || { x: 0, y: 0 };\n          nodeGroup.setAttribute(\"transform\", `translate(${pos.x},${pos.y}) rotate(0)`);\n        }\n      });\n      const applyStyle = (property, value) => {\n       pushUndo(\"style change\");\n       const styleEntry = ensureStyleEntry(currentNodeId);\n       const scopeKey = currentStyleScope || \"all\";\n       if (!styleEntry[scopeKey]) styleEntry[scopeKey] = {};\n       styleEntry[scopeKey][property] = value;\n       const nodeGroup = document.querySelector(`g[data-node-id=\"${currentNodeId}\"]`);\n       if (!nodeGroup) return;\n       const shapeEl = nodeGroup.querySelector(\".node-circle\");\n       const label = nodeGroup.querySelector(\".node-label\");\n       const sub = nodeGroup.querySelector(\".node-sub\");\n       if (property === \"circleColor\" && shapeEl) shapeEl.style.fill = value;\n      else if (property === \"circleBorder\" && shapeEl) shapeEl.style.stroke = value;\n       else if (property === \"titleColor\" && label) label.style.fill = value;\n       else if (property === \"titleFont\" && label) label.style.fontFamily = value;\n       else if (property === \"titleSize\" && label) label.style.fontSize = value + \"px\";\n       else if (property === \"subColor\" && sub) sub.style.fill = value;\n       else if (property === \"subFont\" && sub) sub.style.fontFamily = value;\n       else if (property === \"subSize\" && sub) sub.style.fontSize = value + \"px\";\n      };\n      document.getElementById(\"circle-color\").addEventListener(\"input\", (e) => applyStyle(\"circleColor\", e.target.value));\n      document.getElementById(\"circle-border\").addEventListener(\"input\", (e) => applyStyle(\"circleBorder\", e.target.value));\n      document.getElementById(\"title-color\").addEventListener(\"input\", (e) => applyStyle(\"titleColor\", e.target.value));\n      document.getElementById(\"title-font\").addEventListener(\"change\", (e) => applyStyle(\"titleFont\", e.target.value));\n      document.getElementById(\"title-size\").addEventListener(\"input\", (e) => applyStyle(\"titleSize\", parseInt(e.target.value, 10)));\n      document.getElementById(\"sub-color\").addEventListener(\"input\", (e) => applyStyle(\"subColor\", e.target.value));\n      document.getElementById(\"sub-font\").addEventListener(\"change\", (e) => applyStyle(\"subFont\", e.target.value));\n      document.getElementById(\"sub-size\").addEventListener(\"input\", (e) => applyStyle(\"subSize\", parseInt(e.target.value, 10)));\n      document.getElementById(\"title-offset-y\").addEventListener(\"input\", (e) => {\n       applyStyle(\"titleOffsetY\", parseInt(e.target.value, 10) || 0);\n       forgeTheTopology();\n       if (currentNodeId) claimTheImmortal(currentNodeId);\n      });\n      document.getElementById(\"title-offset-x\").addEventListener(\"input\", (e) => {\n       applyStyle(\"titleOffsetX\", parseInt(e.target.value, 10) || 0);\n       forgeTheTopology();\n       if (currentNodeId) claimTheImmortal(currentNodeId);\n      });\n      document.getElementById(\"sub-offset-y\").addEventListener(\"input\", (e) => {\n       applyStyle(\"subOffsetY\", parseInt(e.target.value, 10) || 0);\n       forgeTheTopology();\n       if (currentNodeId) claimTheImmortal(currentNodeId);\n      });\n      document.getElementById(\"sub-offset-x\").addEventListener(\"input\", (e) => {\n       applyStyle(\"subOffsetX\", parseInt(e.target.value, 10) || 0);\n       forgeTheTopology();\n       if (currentNodeId) claimTheImmortal(currentNodeId);\n      });\n      document.getElementById(\"reset-styles\").addEventListener(\"click\", () => {\n       delete savedStyles[currentNodeId];\n       forgeTheTopology();\n       claimTheImmortal(currentNodeId);\n      });\n      document.getElementById(\"style-scope\").addEventListener(\"change\", (e) => {\n       currentStyleScope = e.target.value || \"all\";\n       claimTheImmortal(currentNodeId);\n      });\n      document.getElementById(\"shape-select\").addEventListener(\"change\", (e) => {\n       const shape = e.target.value || \"circle\";\n       pushUndo(\"change shape\");\n       NODE_DATA[currentNodeId].shape = shape;\nconst fovSection = document.getElementById(\"fov-section\");\nif (fovSection) {\n  if (hasCoverageZone(shape)) {\n    const defaults = getCoverageDefaults(shape);\n    fovSection.style.display = \"block\";\n    document.getElementById(\"fov-angle\").value = defaults.angle;\n    document.getElementById(\"fov-angle-value\").textContent = defaults.angle + \"°\";\n    document.getElementById(\"fov-distance\").value = defaults.distance;\n    document.getElementById(\"fov-distance-value\").textContent = defaults.distance;\n    document.getElementById(\"fov-animation-type\").value = defaults.animationType;\n  } else {\n    fovSection.style.display = \"none\";\n  }\n}\n       const nodeGroup = document.querySelector(`g[data-node-id=\"${currentNodeId}\"]`);\n       if (!nodeGroup) return;\n       const oldShape = nodeGroup.querySelector(\".node-circle\");\n       if (oldShape) oldShape.remove();\n       const size = savedSizes[currentNodeId] || getDefaultSize();\n       const newShape = createNodeShape(currentNodeId, size);\n       nodeGroup.insertBefore(newShape, nodeGroup.firstChild);\n      });\n      const addNoteBtn = document.getElementById(\"add-note-btn\");\n      const noteInput = document.getElementById(\"new-note-input\");\n      addNoteBtn.addEventListener(\"click\", () => {\n       const newNote = noteInput.value.trim();\n       if (newNote && currentNodeId) {\n\t    pushUndo(\"add note\");\n        NODE_DATA[currentNodeId].notes.push(newNote);\n        claimTheImmortal(currentNodeId);\n        noteInput.value = \"\";\n       }\n      });\n      noteInput.addEventListener(\"keypress\", (e) => {\n       if (e.key === \"Enter\") {\n        addNoteBtn.click();\n       }\n      });\n      document.getElementById(\"edge-width\").addEventListener(\"input\", () => {\n       if (!currentEdgeId) return;\n       const edge = EDGE_DATA.list.find((e) => e.id === currentEdgeId);\n       if (!edge) return;\n       const v = parseInt(document.getElementById(\"edge-width\").value, 10);\n       if (Number.isNaN(v) || v <= 0) return;\n       pushUndo(\"edit edge\");\n       edge.width = v;\n       const el = document.querySelector(`.edge[data-edge-id=\"${currentEdgeId}\"]`);\n       if (el) el.style.strokeWidth = v;\n      });\n      document.getElementById(\"edge-color\").addEventListener(\"input\", () => {\n       if (!currentEdgeId) return;\n       const edge = EDGE_DATA.list.find((e) => e.id === currentEdgeId);\n       if (!edge) return;\n       const color = document.getElementById(\"edge-color\").value;\n       pushUndo(\"edit edge\");\n       edge.color = color;\n       const el = document.querySelector(`.edge[data-edge-id=\"${currentEdgeId}\"]`);\n       if (el) el.style.stroke = color;\n       forgeTheLegend();\n      });\n      document.getElementById(\"edge-direction\").addEventListener(\"change\", () => {\n       if (!currentEdgeId) return;\n       const edge = EDGE_DATA.list.find((e) => e.id === currentEdgeId);\n       if (!edge) return;\n       pushUndo(\"edit edge\");\n       edge.direction = document.getElementById(\"edge-direction\").value;\n       forgeTheTopology();\n       selectTheConnection(currentEdgeId);\n      });\n      document.getElementById(\"edge-line-style\").addEventListener(\"change\", () => {\n       if (!currentEdgeId) return;\n       const edge = EDGE_DATA.list.find((e) => e.id === currentEdgeId);\n       if (!edge) return;\n       pushUndo(\"edit edge\");\n       edge.lineStyle = document.getElementById(\"edge-line-style\").value;\n       forgeTheTopology();\n       selectTheConnection(currentEdgeId);\n      });\n      document.getElementById(\"edge-routing\").addEventListener(\"change\", () => {\n       if (!currentEdgeId) return;\n       const edge = EDGE_DATA.list.find((e) => e.id === currentEdgeId);\n       if (!edge) return;\n       pushUndo(\"edit edge routing\");\n       edge.routing = document.getElementById(\"edge-routing\").value;\n       forgeTheTopology();\n       selectTheConnection(currentEdgeId);\n      });\n      document.getElementById(\"edge-animate\").addEventListener(\"change\", (e) => {\n       if (!currentEdgeId) return;\n       const edge = EDGE_DATA.list.find((ed) => ed.id === currentEdgeId);\n       if (!edge) return;\n       pushUndo(\"edit edge animate\");\n       edge.animate = e.target.checked;\n       forgeTheTopology();\n       selectTheConnection(currentEdgeId);\n      });\n      document.getElementById(\"edge-animation-speed\").addEventListener(\"change\", (e) => {\n       if (!currentEdgeId) return;\n       const edge = EDGE_DATA.list.find((ed) => ed.id === currentEdgeId);\n       if (!edge) return;\n       pushUndo(\"edit edge animation speed\");\n       edge.animationSpeed = e.target.value || \"\";\n       if (PAGE_STATE.animateConnections && edge.animate !== false) forgeTheTopology();\n       selectTheConnection(currentEdgeId);\n      });\n      const addEdgeNoteBtn = document.getElementById(\"add-edge-note\");\n      const newEdgeNoteInput = document.getElementById(\"new-edge-note\");\n      addEdgeNoteBtn.addEventListener(\"click\", () => {\n       const txt = newEdgeNoteInput.value.trim();\n       if (!txt || !currentEdgeId) return;\n       const edge = EDGE_DATA.list.find((e) => e.id === currentEdgeId);\n       if (!edge) return;\n\t   pushUndo(\"add edge note\");\n       edge.notes.push(txt);\n       newEdgeNoteInput.value = \"\";\n       selectTheConnection(currentEdgeId);\n      });\n      newEdgeNoteInput.addEventListener(\"keypress\", (e) => {\n       if (e.key === \"Enter\") {\n        addEdgeNoteBtn.click();\n       }\n      });\n      function selectTheRect(id) {\n\t  if (isViewOnly()) return;\n      currentRectId = id;\n      currentNodeId = null;\n      currentEdgeId = null;\n      currentTextId = null;\n      document.getElementById(\"node-panel\").style.display = \"none\";\n      document.getElementById(\"edge-panel\").style.display = \"none\";\n      document.getElementById(\"text-panel\").style.display = \"none\";\n      document.getElementById(\"rect-panel\").style.display = \"block\";\n      document.getElementById(\"topology-toolbar\").style.display = \"none\";\n      document.querySelectorAll(\".node-group\").forEach(n => n.classList.remove(\"active\"));\n      document.querySelectorAll(\".edge\").forEach(e => e.classList.remove(\"active\"));\n      document.querySelectorAll(\".text-element\").forEach(t => t.classList.remove(\"active\"));\n      document.querySelectorAll(\".rect-group\").forEach(r => r.classList.toggle(\"active\", r.dataset.rectId === id));\n      const rect = RECT_DATA.list.find(r => r.id === id);\n      if (!rect) return;\n      document.getElementById(\"rect-title\").textContent = \"\";\n      document.getElementById(\"rect-color\").value = rect.color || \"#f97316\";\n      document.getElementById(\"rect-border-color\").value = rect.borderColor || rect.color || \"#f97316\";\n      document.getElementById(\"rect-border-width\").value = rect.borderWidth !== undefined ? rect.borderWidth : 2;\n      document.getElementById(\"rect-style-select\").value = rect.style || \"filled\";\n      document.getElementById(\"rect-line-style\").value = rect.lineStyle || \"solid\";\n\t  document.getElementById(\"rect-rotation\").value = Math.max(-360, Math.min(360, rect.rotation || 0));\n      document.getElementById(\"rect-rotation-value\").value = rect.rotation || 0;\n      document.getElementById(\"rect-fill-row\").style.display = (rect.style === \"outlined\") ? \"none\" : \"flex\";\n      const list = document.getElementById(\"rect-notes\");\n      list.innerHTML = \"\";\n      (rect.notes || []).forEach((note, i) => {\n      const li = document.createElement(\"li\");\n      const txt = document.createElement(\"span\");\n      txt.textContent = note;\n      txt.style.flex = \"1\";\n      const del = document.createElement(\"span\");\n      del.className = \"delete-note\";\n      del.textContent = \"✕\";\n       del.addEventListener(\"click\", (e) => {\n      e.stopPropagation();\n      challengeTheImmortal(\"Delete this note?\", () => {\n        pushUndo(\"delete zone note\");\n        rect.notes.splice(i, 1);\n        selectTheRect(id);\n      });\n      });\n      li.appendChild(txt);\n      li.appendChild(del);\n      list.appendChild(li);\n      });\n      forgeTheTopology();\n      }\n      document.getElementById(\"rect-color\").addEventListener(\"input\", () => {\n      if (!currentRectId) return;\n      const rect = RECT_DATA.list.find(r => r.id === currentRectId);\n      if (!rect) return;\n      pushUndo(\"edit zone\");\n      rect.color = document.getElementById(\"rect-color\").value;\n      forgeTheTopology();\n      });\n      document.getElementById(\"rect-border-color\").addEventListener(\"input\", () => {\n      if (!currentRectId) return;\n      const rect = RECT_DATA.list.find(r => r.id === currentRectId);\n      if (!rect) return;\n      pushUndo(\"edit zone\");\n      rect.borderColor = document.getElementById(\"rect-border-color\").value;\n      forgeTheTopology();\n      });\n      document.getElementById(\"rect-border-width\").addEventListener(\"input\", () => {\n      if (!currentRectId) return;\n      const rect = RECT_DATA.list.find(r => r.id === currentRectId);\n      if (!rect) return;\n      pushUndo(\"edit zone\");\n      rect.borderWidth = parseInt(document.getElementById(\"rect-border-width\").value) || 2;\n      forgeTheTopology();\n      });\n      document.getElementById(\"rect-style-select\").addEventListener(\"change\", () => {\n      if (!currentRectId) return;\n      const rect = RECT_DATA.list.find(r => r.id === currentRectId);\n      if (!rect) return;\n      pushUndo(\"edit zone\");\n      rect.style = document.getElementById(\"rect-style-select\").value;\n      forgeTheTopology();\n      selectTheRect(currentRectId);\n      });\ndocument.getElementById(\"rect-rotation\").addEventListener(\"input\", () => {\n        if (!currentRectId) return;\n        const rect = RECT_DATA.list.find(r => r.id === currentRectId);\n        if (!rect) return;\n        pushUndo(\"rotate zone\");\n        rect.rotation = parseInt(document.getElementById(\"rect-rotation\").value) || 0;\n        document.getElementById(\"rect-rotation-value\").value = rect.rotation;\n        forgeTheTopology();\n      });\n      document.getElementById(\"rect-rotation-value\").addEventListener(\"input\", () => {\n        if (!currentRectId) return;\n        const rect = RECT_DATA.list.find(r => r.id === currentRectId);\n        if (!rect) return;\n        pushUndo(\"rotate zone\");\n        const val = parseInt(document.getElementById(\"rect-rotation-value\").value) || 0;\n        rect.rotation = val;\n        document.getElementById(\"rect-rotation\").value = Math.max(-360, Math.min(360, val));\n        forgeTheTopology();\n      });\n      document.getElementById(\"rect-line-style\").addEventListener(\"change\", () => {\n      if (!currentRectId) return;\n      pushUndo(\"change zone line style\");\n      const rect = RECT_DATA.list.find(r => r.id === currentRectId);\n      if (!rect) return;\n      rect.lineStyle = document.getElementById(\"rect-line-style\").value;\n      forgeTheTopology();\n      });\n      document.getElementById(\"add-rect-note\").addEventListener(\"click\", () => {\n      const input = document.getElementById(\"new-rect-note\");\n      const txt = input.value.trim();\n      if (!txt || !currentRectId) return;\n      const rect = RECT_DATA.list.find(r => r.id === currentRectId);\n      if (!rect) return;\n      if (!rect.notes) rect.notes = [];\n\t  pushUndo(\"add zone note\");\n      rect.notes.push(txt);\n      input.value = \"\";\n      selectTheRect(currentRectId);\n      });\n      document.getElementById(\"new-rect-note\").addEventListener(\"keypress\", (e) => {\n      if (e.key === \"Enter\") {\n      document.getElementById(\"add-rect-note\").click();\n      }\n      });\n      document.getElementById(\"delete-rect\").addEventListener(\"click\", () => {\n      if (!currentRectId) return;\n      challengeTheImmortal(\"Delete this zone?\", () => {\n      pushUndo(\"delete zone\");\n      RECT_DATA.list = RECT_DATA.list.filter(r => r.id !== currentRectId);\n      currentRectId = null;\n      document.getElementById(\"rect-panel\").style.display = \"none\";\n      forgeTheTopology();\n      });\n      });\n      document.getElementById(\"delete-edge\").addEventListener(\"click\", () => {\n       if (!currentEdgeId) return;\n       challengeTheImmortal(\"Are you sure you want to delete this line?\",\n        () => {\n         pushUndo(\"delete edge\");\n         EDGE_DATA.list = EDGE_DATA.list.filter(\n          (e) => e.id !== currentEdgeId);\n         currentEdgeId = null;\n         forgeTheTopology();\n         const availableNodes = Object.keys(NODE_DATA);\n         if (availableNodes.length > 0) {\n          claimTheImmortal(availableNodes[0]);\n         } else {\n          document.getElementById(\"node-panel\").style.display = \"none\";\n          document.getElementById(\"edge-panel\").style.display = \"none\";\n          document.getElementById(\"topology-toolbar\", ).style.display = \"none\";\n         }\n        });\n      });\n      document.getElementById(\"add-line-btn\").addEventListener(\"click\", () => {\n       if (!currentNodeId) return;\n       const select = document.getElementById(\"add-line-select\");\n       const directionSelect = document.getElementById(\"add-line-direction\");\n       const colorInput = document.getElementById(\"add-line-color\");\n       const routingSelect = document.getElementById(\"add-line-routing\");\n       const targetId = select.value;\n       if (!targetId || targetId === currentNodeId) return;\n       const direction = directionSelect.value || \"none\";\n       const lineColor = colorInput.value || \"#475569\";\n       const routing = routingSelect.value || PAGE_STATE.defaultEdgeRouting || \"orthogonal\";\n       const newId = `${currentNodeId}-${targetId}-${Date.now()}`;\n       const newEdge = {\n        id: newId,\n        from: currentNodeId,\n        to: targetId,\n        width: 4,\n        color: lineColor,\n        direction: direction,\n        routing: routing,\n        type: \"main\",\n        notes: [],\n        fromPort: \"\",\n        toPort: \"\",\n        lineStyle: \"solid\",\n       };\n       pushUndo(\"add edge\");\n       EDGE_DATA.list.push(newEdge);\n       forgeTheTopology();\n       claimTheImmortal(currentNodeId);\n      });\n      let freeDrawPoints = [];\n      let freeDrawPolylineEl = null;\n      let freeDrawPointEls = [];\n       let rectStartPoint = null;\n       let rectPreviewEl = null;\n       let rectStyle = \"filled\";\n      const drawToggleBtn = document.getElementById(\"draw-toggle\");\n      const drawUndoBtn = document.getElementById(\"draw-undo\");\n      const drawColorInput = document.getElementById(\"draw-color\");\n      const drawStyleSelect = document.getElementById(\"draw-style\");\n      const drawArrowSelect = document.getElementById(\"draw-arrow\");\n      const svgMap = document.getElementById(\"map\");\n      function updateFreeDrawGraphics() {\n       const ns = \"http://www.w3.org/2000/svg\";\n       const svg = svgMap;\n       if (!freeDrawPolylineEl && freeDrawPoints.length > 0) {\n        freeDrawPolylineEl = document.createElementNS(ns, \"polyline\");\n        freeDrawPolylineEl.classList.add(\"edge\", \"free-preview\");\n        freeDrawPolylineEl.setAttribute(\"fill\", \"none\");\n        svg.appendChild(freeDrawPolylineEl);\n       }\n       if (freeDrawPolylineEl) {\n        if (freeDrawPoints.length === 0) {\n         freeDrawPolylineEl.remove();\n         freeDrawPolylineEl = null;\n        } else {\n         const ptsStr = freeDrawPoints.map((p) => `${p.x},${p.y}`).join(\" \");\n         freeDrawPolylineEl.setAttribute(\"points\", ptsStr);\n         freeDrawPolylineEl.style.stroke = drawColorInput.value || \"#475569\";\n         freeDrawPolylineEl.style.strokeWidth = 3;\n         const lineStyle = drawStyleSelect.value || \"solid\";\n         if (lineStyle === \"dashed\") {\n          freeDrawPolylineEl.style.strokeDasharray = \"10,5\";\n         } else if (lineStyle === \"dotted\") {\n          freeDrawPolylineEl.style.strokeDasharray = \"2,4\";\n         } else if (lineStyle === \"wall\") {\n  freeDrawPolylineEl.style.stroke = \"url(#wall-hatch)\";\n  freeDrawPolylineEl.style.strokeWidth = \"12\";\n  freeDrawPolylineEl.style.strokeDasharray = \"none\"; \n  } else {\n          freeDrawPolylineEl.style.strokeDasharray = \"none\";\n         }\n        }\n       }\n       freeDrawPointEls.forEach((el) => el.remove());\n       freeDrawPointEls = [];\n       freeDrawPoints.forEach((p, idx) => {\n        const c = document.createElementNS(ns, \"circle\");\n        c.classList.add(\"free-point\");\n        c.setAttribute(\"cx\", p.x);\n        c.setAttribute(\"cy\", p.y);\n        c.setAttribute(\"r\", 5);\n        c.dataset.index = String(idx);\n        c.addEventListener(\"mousedown\", (e) => {\n         if (!freeDrawMode) return;\n         e.preventDefault();\n         e.stopPropagation();\n         let dragging = true;\n         const svgEl = svgMap;\n         const moveHandler = (ev) => {\n          if (!dragging) return;\n          const pt = svgEl.createSVGPoint();\n          pt.x = ev.clientX;\n          pt.y = ev.clientY;\n          const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n          const i = parseInt(c.dataset.index, 10);\n          if (Number.isNaN(i) || !freeDrawPoints[i]) return;\n          freeDrawPoints[i].x = svgP.x;\n          freeDrawPoints[i].y = svgP.y;\n          updateFreeDrawGraphics();\n         };\n         const upHandler = () => {\n          dragging = false;\n          document.removeEventListener(\"mousemove\", moveHandler);\n          document.removeEventListener(\"mouseup\", upHandler);\n         };\n         document.addEventListener(\"mousemove\", moveHandler);\n         document.addEventListener(\"mouseup\", upHandler);\n        });\n        c.addEventListener(\"touchstart\",\n         (e) => {\n          if (!freeDrawMode) return;\n          e.preventDefault();\n          e.stopPropagation();\n          let dragging = true;\n          const svgEl = svgMap;\n          const touchMoveHandler = (ev) => {\n           if (!dragging || !ev.touches[0]) return;\n           const pt = svgEl.createSVGPoint();\n           pt.x = ev.touches[0].clientX;\n           pt.y = ev.touches[0].clientY;\n           const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n           const i = parseInt(c.dataset.index, 10);\n           if (Number.isNaN(i) || !freeDrawPoints[i]) return;\n           freeDrawPoints[i].x = svgP.x;\n           freeDrawPoints[i].y = svgP.y;\n           updateFreeDrawGraphics();\n          };\n          const touchUpHandler = () => {\n           dragging = false;\n           document.removeEventListener(\"touchmove\", touchMoveHandler);\n           document.removeEventListener(\"touchend\", touchUpHandler);\n          };\n          document.addEventListener(\"touchmove\", touchMoveHandler);\n          document.addEventListener(\"touchend\", touchUpHandler);\n         }, {\n          passive: false\n         });\n        svg.appendChild(c);\n        freeDrawPointEls.push(c);\n       });\n       drawUndoBtn.style.display = freeDrawPoints.length ? \"inline-block\" : \"none\";\n      }\n      function addFreeDrawPoint(x, y) {\n       freeDrawPoints.push({\n        x,\n        y\n       });\n       updateFreeDrawGraphics();\n      }\n      function startFreeDraw() {\n       freeDrawMode = true;\n       freeDrawPoints = [];\n       if (freeDrawPolylineEl) {\n        freeDrawPolylineEl.remove();\n        freeDrawPolylineEl = null;\n       }\n       freeDrawPointEls.forEach((el) => el.remove());\n       freeDrawPointEls = [];\n       svgMap.style.cursor = \"crosshair\";\n       drawToggleBtn.textContent = \"Done\";\n       drawToggleBtn.classList.add(\"done-btn-active\");\n       drawUndoBtn.style.display = \"none\";\n      }\n      function finishFreeDraw() {\n       freeDrawMode = false;\n       svgMap.style.cursor = \"\";\n       drawToggleBtn.textContent = \"✏️\";\n       drawToggleBtn.classList.remove(\"done-btn-active\");\n       if (freeDrawPoints.length >= 2) {\n        const color = drawColorInput.value || \"#475569\";\n        const lineStyle = drawStyleSelect.value || \"solid\";\n        const arrowDir = drawArrowSelect.value || \"none\";\n        const newId = \"custom-\" + Date.now();\n        const pointsCopy = freeDrawPoints.map((p) => ({\n         x: p.x,\n         y: p.y,\n        }));\n        EDGE_DATA.list.push({\n         id: newId,\n         type: \"custom\",\n         color,\n         width: 4,\n         lineStyle: lineStyle,\n         direction: arrowDir,\n         points: pointsCopy,\n         notes: [],\n        });\n        freeDrawPoints = [];\n        if (freeDrawPolylineEl) {\n         freeDrawPolylineEl.remove();\n         freeDrawPolylineEl = null;\n        }\n        freeDrawPointEls.forEach((el) => el.remove());\n        freeDrawPointEls = [];\n        forgeTheTopology();\n        selectTheConnection(newId);\n       } else {\n        freeDrawPoints = [];\n        if (freeDrawPolylineEl) {\n         freeDrawPolylineEl.remove();\n         freeDrawPolylineEl = null;\n        }\n        freeDrawPointEls.forEach((el) => el.remove());\n        freeDrawPointEls = [];\n        forgeTheLegend();\n       }\n       drawUndoBtn.style.display = \"none\";\n      }\n      drawToggleBtn.addEventListener(\"click\", () => {\n       if (currentView.mode === \"rack\") {\n        alert(\"Drawing tools are disabled in rack view. Exit to topology view to use drawing tools.\");\n        return;\n       }\n       if (freeDrawMode) {\n        finishFreeDraw();\n       } else {\n        startFreeDraw();\n       }\n      });\n      drawUndoBtn.addEventListener(\"click\", () => {\n       if (!freeDrawMode || !freeDrawPoints.length) return;\n       freeDrawPoints.pop();\n       updateFreeDrawGraphics();\n      });\n      const drawToolbar = document.getElementById(\"draw-toolbar\");\n      drawToolbar.addEventListener(\"mousedown\", (e) => {\n       e.stopPropagation();\n      });\n      drawToolbar.addEventListener(\"click\", (e) => {\n       if (e.target !== drawToggleBtn && e.target !== drawUndoBtn) {\n        e.stopPropagation();\n       }\n      });\n      drawStyleSelect.addEventListener(\"change\", () => {\n       if (freeDrawMode) {\n        updateFreeDrawGraphics();\n       }\n      });\n      drawArrowSelect.addEventListener(\"change\", () => {\n       if (freeDrawMode) {\n        updateFreeDrawGraphics();\n       }\n      });\n      drawColorInput.addEventListener(\"input\", () => {\n       if (freeDrawMode) {\n        updateFreeDrawGraphics();\n       }\n      });\n      drawStyleSelect.addEventListener(\"mousedown\", (e) => {\n       e.stopPropagation();\n      });\n      drawStyleSelect.addEventListener(\"click\", (e) => {\n       e.stopPropagation();\n      });\n      drawArrowSelect.addEventListener(\"mousedown\", (e) => {\n       e.stopPropagation();\n      });\n      drawArrowSelect.addEventListener(\"click\", (e) => {\n       e.stopPropagation();\n      });\n      drawColorInput.addEventListener(\"mousedown\", (e) => {\n       e.stopPropagation();\n      });\n      drawColorInput.addEventListener(\"click\", (e) => {\n       e.stopPropagation();\n      });\n      svgMap.addEventListener(\"click\", (e) => {\n       if (!freeDrawMode) return;\n       if (e.button !== 0) return;\n       const target = e.target;\n       if (target && target.classList && target.classList.contains(\"free-point\")) return;\n       const svgEl = svgMap;\n       const pt = svgEl.createSVGPoint();\n       pt.x = e.clientX;\n       pt.y = e.clientY;\n       const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n       addFreeDrawPoint(svgP.x, svgP.y);\n      });\n      svgMap.addEventListener(\"touchend\",\n       (e) => {\n        if (!freeDrawMode) return;\n        const target = e.target;\n        if (target && target.classList && target.classList.contains(\"free-point\")) return;\n        if (e.changedTouches && e.changedTouches[0]) {\n         e.preventDefault();\n         const svgEl = svgMap;\n         const pt = svgEl.createSVGPoint();\n         pt.x = e.changedTouches[0].clientX;\n         pt.y = e.changedTouches[0].clientY;\n         const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n         addFreeDrawPoint(svgP.x, svgP.y);\n        }\n       }, {\n        passive: false\n       });\n       const rectToggleBtn = document.getElementById(\"rect-toggle\");\n       const rectStyleSelect = document.getElementById(\"rect-style\");\n       function startRectDraw() {\n        rectDrawMode = true;\n        rectStartPoint = null;\n        rectPreviewEl = null;\n        svgMap.style.cursor = \"crosshair\";\n        rectToggleBtn.textContent = \"Done\";\n        rectToggleBtn.classList.add(\"done-btn-active\");\n        rectStyle = rectStyleSelect.value || \"filled\";\n        if (freeDrawMode) {\n         finishFreeDraw();\n        }\n        updateRectangleDeleteButtons();\n       }\n       function finishRectDraw() {\n        rectDrawMode = false;\n        svgMap.style.cursor = \"\";\n        rectToggleBtn.textContent = \"▭\";\n        rectToggleBtn.classList.remove(\"done-btn-active\");\n        if (rectPreviewEl) {\n         rectPreviewEl.remove();\n         rectPreviewEl = null;\n        }\n        rectStartPoint = null;\n        updateRectangleDeleteButtons();\n       }\n       rectToggleBtn.addEventListener(\"click\", () => {\n        if (currentView.mode === \"rack\") {\n         alert(\"Drawing tools are disabled in rack view. Exit to topology view to use drawing tools.\");\n         return;\n        }\n        if (rectDrawMode) {\n         finishRectDraw();\n        } else {\n         startRectDraw();\n        }\n       });\n       rectStyleSelect.addEventListener(\"mousedown\", (e) => {\n        e.stopPropagation();\n       });\n       rectStyleSelect.addEventListener(\"click\", (e) => {\n        e.stopPropagation();\n       });\n       rectStyleSelect.addEventListener(\"change\", () => {\n        if (rectDrawMode) {\n         rectStyle = rectStyleSelect.value || \"filled\";\n        }\n       });\n       svgMap.addEventListener(\"mousedown\", (e) => {\n        if (!rectDrawMode) return;\n        if (e.button !== 0) return;\n        e.preventDefault();\n        e.stopPropagation();\n        const svgEl = svgMap;\n        const pt = svgEl.createSVGPoint();\n        pt.x = e.clientX;\n        pt.y = e.clientY;\n        const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n        rectStartPoint = { x: svgP.x, y: svgP.y };\n        const ns = \"http://www.w3.org/2000/svg\";\n        rectPreviewEl = document.createElementNS(ns, \"rect\");\n        rectPreviewEl.classList.add(\"rect-preview\");\n        rectPreviewEl.setAttribute(\"x\", svgP.x);\n        rectPreviewEl.setAttribute(\"y\", svgP.y);\n        rectPreviewEl.setAttribute(\"width\", 0);\n        rectPreviewEl.setAttribute(\"height\", 0);\n        const color = drawColorInput.value || \"#f97316\";\n        rectPreviewEl.style.stroke = color;\n        rectPreviewEl.style.strokeWidth = 2;\n        rectPreviewEl.style.strokeDasharray = \"5,5\";\n        if (rectStyle === \"filled\") {\n         rectPreviewEl.style.fill = color;\n         rectPreviewEl.style.fillOpacity = \"0.3\";\n        } else {\n         rectPreviewEl.style.fill = \"none\";\n        }\n        svgEl.appendChild(rectPreviewEl);\n       });\n       svgMap.addEventListener(\"mousemove\", (e) => {\n        if (!rectDrawMode || !rectStartPoint || !rectPreviewEl) return;\n        const svgEl = svgMap;\n        const pt = svgEl.createSVGPoint();\n        pt.x = e.clientX;\n        pt.y = e.clientY;\n        const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n        const x = Math.min(rectStartPoint.x, svgP.x);\n        const y = Math.min(rectStartPoint.y, svgP.y);\n        const width = Math.abs(svgP.x - rectStartPoint.x);\n        const height = Math.abs(svgP.y - rectStartPoint.y);\n        rectPreviewEl.setAttribute(\"x\", x);\n        rectPreviewEl.setAttribute(\"y\", y);\n        rectPreviewEl.setAttribute(\"width\", width);\n        rectPreviewEl.setAttribute(\"height\", height);\n       });\n       svgMap.addEventListener(\"mouseup\", (e) => {\n        if (!rectDrawMode || !rectStartPoint || !rectPreviewEl) return;\n        const svgEl = svgMap;\n        const pt = svgEl.createSVGPoint();\n        pt.x = e.clientX;\n        pt.y = e.clientY;\n        const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n        const x = Math.min(rectStartPoint.x, svgP.x);\n        const y = Math.min(rectStartPoint.y, svgP.y);\n        const width = Math.abs(svgP.x - rectStartPoint.x);\n        const height = Math.abs(svgP.y - rectStartPoint.y);\n        if (width > 10 && height > 10) {\n      pushUndo(\"draw zone\");\n         const color = drawColorInput.value || \"#f97316\";\n         const newId = \"rect-\" + Date.now();\n          const lineStyle = document.getElementById(\"draw-style\").value || \"solid\";\n         RECT_DATA.list.push({\n          id: newId,\n          x: x,\n          y: y,\n          width: width,\n          height: height,\n          color: color,\n          style: rectStyle,\n          lineStyle: lineStyle,\n          notes: []\n         });\n         forgeTheTopology();\n        }\n        if (rectPreviewEl) {\n         rectPreviewEl.remove();\n         rectPreviewEl = null;\n        }\n        rectStartPoint = null;\n       });\n       let rectTouchStart = null;\n       svgMap.addEventListener(\"touchstart\", (e) => {\n        if (!rectDrawMode) return;\n        if (e.touches.length !== 1) return;\n        e.preventDefault();\n        const touch = e.touches[0];\n        const svgEl = svgMap;\n        const pt = svgEl.createSVGPoint();\n        pt.x = touch.clientX;\n        pt.y = touch.clientY;\n        const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n        rectStartPoint = { x: svgP.x, y: svgP.y };\n        rectTouchStart = { x: svgP.x, y: svgP.y };\n        const ns = \"http://www.w3.org/2000/svg\";\n        rectPreviewEl = document.createElementNS(ns, \"rect\");\n        rectPreviewEl.classList.add(\"rect-preview\");\n        rectPreviewEl.setAttribute(\"x\", svgP.x);\n        rectPreviewEl.setAttribute(\"y\", svgP.y);\n        rectPreviewEl.setAttribute(\"width\", 0);\n        rectPreviewEl.setAttribute(\"height\", 0);\n        const color = drawColorInput.value || \"#f97316\";\n        rectPreviewEl.style.stroke = color;\n        rectPreviewEl.style.strokeWidth = 2;\n        rectPreviewEl.style.strokeDasharray = \"5,5\";\n        if (rectStyle === \"filled\") {\n         rectPreviewEl.style.fill = color;\n         rectPreviewEl.style.fillOpacity = \"0.3\";\n        } else {\n         rectPreviewEl.style.fill = \"none\";\n        }\n        svgEl.appendChild(rectPreviewEl);\n       }, { passive: false });\n       svgMap.addEventListener(\"touchmove\", (e) => {\n        if (!rectDrawMode || !rectStartPoint || !rectPreviewEl) return;\n        if (e.touches.length !== 1) return;\n        e.preventDefault();\n        const touch = e.touches[0];\n        const svgEl = svgMap;\n        const pt = svgEl.createSVGPoint();\n        pt.x = touch.clientX;\n        pt.y = touch.clientY;\n        const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n        const x = Math.min(rectStartPoint.x, svgP.x);\n        const y = Math.min(rectStartPoint.y, svgP.y);\n        const width = Math.abs(svgP.x - rectStartPoint.x);\n        const height = Math.abs(svgP.y - rectStartPoint.y);\n        rectPreviewEl.setAttribute(\"x\", x);\n        rectPreviewEl.setAttribute(\"y\", y);\n        rectPreviewEl.setAttribute(\"width\", width);\n        rectPreviewEl.setAttribute(\"height\", height);\n       }, { passive: false });\n       svgMap.addEventListener(\"touchend\", (e) => {\n        if (!rectDrawMode || !rectStartPoint || !rectPreviewEl) return;\n        if (!rectTouchStart) return;\n        e.preventDefault();\n        const touch = e.changedTouches[0];\n        const svgEl = svgMap;\n        const pt = svgEl.createSVGPoint();\n        pt.x = touch.clientX;\n        pt.y = touch.clientY;\n        const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n        const x = Math.min(rectStartPoint.x, svgP.x);\n        const y = Math.min(rectStartPoint.y, svgP.y);\n        const width = Math.abs(svgP.x - rectStartPoint.x);\n        const height = Math.abs(svgP.y - rectStartPoint.y);\n        if (width > 10 && height > 10) {\n      pushUndo(\"draw zone\");\n         const color = drawColorInput.value || \"#f97316\";\n         const newId = \"rect-\" + Date.now();\n        const lineStyle = document.getElementById(\"draw-style\").value || \"solid\";\n         RECT_DATA.list.push({\n          id: newId,\n          x: x,\n          y: y,\n          width: width,\n          height: height,\n          color: color,\n          style: rectStyle,\n          lineStyle: lineStyle,\n          notes: []\n         });\n         forgeTheTopology();\n        }\n        if (rectPreviewEl) {\n         rectPreviewEl.remove();\n         rectPreviewEl = null;\n        }\n        rectStartPoint = null;\n        rectTouchStart = null;\n       }, { passive: false });\n      const textToggleBtn = document.getElementById(\"text-toggle\");\n      function startTextMode() {\n       textDrawMode = true;\n       svgMap.style.cursor = \"crosshair\";\n       textToggleBtn.textContent = \"Done\";\n       textToggleBtn.classList.add(\"done-btn-active\");\n       if (freeDrawMode) {\n        finishFreeDraw();\n       }\n       if (rectDrawMode) {\n        finishRectDraw();\n       }\n       updateTextDeleteButtons();\n      }\n      function finishTextMode() {\n       textDrawMode = false;\n       svgMap.style.cursor = \"\";\n       textToggleBtn.textContent = \"T\";\n       textToggleBtn.classList.remove(\"done-btn-active\");\n       updateTextDeleteButtons();\n      }\n      textToggleBtn.addEventListener(\"click\", () => {\n       if (currentView.mode === \"rack\") {\n        alert(\"Drawing tools are disabled in rack view. Exit to topology view to use drawing tools.\");\n        return;\n       }\n       if (textDrawMode) {\n        finishTextMode();\n       } else {\n        startTextMode();\n       }\n      });\n      function handleTextPlacement(e) {\n       if (!textDrawMode) return;\n       const svgEl = svgMap;\n       const pt = svgEl.createSVGPoint();\n       pt.x = e.clientX;\n       pt.y = e.clientY;\n       const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n       const newId = \"text-\" + Date.now();\n      pushUndo(\"add text\");\n       TEXT_DATA.list.push({\n        id: newId,\n        x: svgP.x,\n        y: svgP.y,\n        content: \"New Text\",\n        fontSize: 18,\n        color: \"#e2e8f0\",\n        fontWeight: \"normal\",\n        fontStyle: \"normal\",\n        textAlign: \"start\",\n        textDecoration: \"none\",\n        bgColor: \"#000000\",\n        bgEnabled: false,\n        opacity: 1\n       });\n       forgeTheTopology();\n       showTextPanel(newId);\n      }\n      svgMap.addEventListener(\"click\", (e) => {\n       if (!textDrawMode) return;\n       if (e.target.closest('.text-delete-btn')) return;\n       if (e.target.closest('.text-group')) return;\n       e.preventDefault();\n       e.stopPropagation();\n       handleTextPlacement(e);\n      });\n      svgMap.addEventListener(\"touchend\", (e) => {\n       if (!textDrawMode) return;\n       if (e.target.closest('.text-delete-btn')) return;\n       if (e.target.closest('.text-group')) return;\n       if (e.touches.length > 0) return;\n       e.preventDefault();\n       const touch = e.changedTouches[0];\n       const fakeEvent = {\n        clientX: touch.clientX,\n        clientY: touch.clientY,\n        preventDefault: () => {},\n        stopPropagation: () => {}\n       };\n       handleTextPlacement(fakeEvent);\n      }, { passive: false });\n      function showTextPanel(textId) {\n\t  if (isViewOnly()) return;\n       currentTextId = textId;\n       currentNodeId = null;\n       currentEdgeId = null;\n       currentRectId = null;\n       const textItem = TEXT_DATA.list.find(t => t.id === textId);\n       if (!textItem) return;\n       document.getElementById(\"node-panel\").style.display = \"none\";\n       document.getElementById(\"edge-panel\").style.display = \"none\";\n       document.getElementById(\"rect-panel\").style.display = \"none\";\n       const textPanel = document.getElementById(\"text-panel\");\n       textPanel.style.display = \"block\";\n       document.querySelectorAll(\".node-group\").forEach(n => n.classList.remove(\"active\"));\n       document.querySelectorAll(\".edge\").forEach(e => e.classList.remove(\"active\"));\n       document.querySelectorAll(\".rect-group\").forEach(r => r.classList.remove(\"active\"));\n       document.querySelectorAll(\".text-element\").forEach(t => t.classList.toggle(\"active\", t.dataset.textId === textId));\n       document.getElementById(\"text-content\").value = textItem.content;\n       document.getElementById(\"text-font-size\").value = textItem.fontSize;\n       document.getElementById(\"text-color\").value = textItem.color;\n       document.getElementById(\"text-font-weight\").value = textItem.fontWeight;\n       document.getElementById(\"text-font-style\").value = textItem.fontStyle;\n       document.getElementById(\"text-align\").value = textItem.textAlign;\n       document.getElementById(\"text-decoration\").value = textItem.textDecoration;\n       document.getElementById(\"text-bg-color\").value = textItem.bgColor;\n       document.getElementById(\"text-bg-enabled\").checked = textItem.bgEnabled;\n       document.getElementById(\"text-opacity\").value = textItem.opacity;\n       document.getElementById(\"text-opacity-val\").textContent = Math.round(textItem.opacity * 100) + \"%\";\n       document.getElementById(\"text-rotation\").value = Math.max(-360, Math.min(360, textItem.rotation || 0));\n       document.getElementById(\"text-rotation-val\").value = textItem.rotation || 0;\n      }\n      function updateTextDeleteButtons() {\n       const deleteButtons = document.querySelectorAll('.text-delete-btn');\n       deleteButtons.forEach(btn => {\n        btn.style.display = textDrawMode ? 'block' : 'none';\n       });\n      }\n      function deleteText(textId) {\n      pushUndo(\"delete text\");\n       TEXT_DATA.list = TEXT_DATA.list.filter(t => t.id !== textId);\n       forgeTheTopology();\n       if (currentTextId === textId) {\n        document.getElementById(\"text-panel\").style.display = \"none\";\n        currentTextId = null;\n       }\n      }\n      document.getElementById(\"text-content\").addEventListener(\"input\", (e) => {\n       if (!currentTextId) return;\n       const textItem = TEXT_DATA.list.find(t => t.id === currentTextId);\n       if (textItem) {\n      pushUndo(\"edit text\");\n        textItem.content = e.target.value;\n        forgeTheTopology();\n       }\n      });\n      document.getElementById(\"text-font-size\").addEventListener(\"input\", (e) => {\n       if (!currentTextId) return;\n       const textItem = TEXT_DATA.list.find(t => t.id === currentTextId);\n       if (textItem) {\n        pushUndo(\"edit text\");\n        textItem.fontSize = parseInt(e.target.value);\n        forgeTheTopology();\n       }\n      });\n      document.getElementById(\"text-color\").addEventListener(\"input\", (e) => {\n       if (!currentTextId) return;\n       const textItem = TEXT_DATA.list.find(t => t.id === currentTextId);\n       if (textItem) {\n        pushUndo(\"edit text\");\n        textItem.color = e.target.value;\n        forgeTheTopology();\n       }\n      });\n      document.getElementById(\"text-font-weight\").addEventListener(\"change\", (e) => {\n       if (!currentTextId) return;\n       const textItem = TEXT_DATA.list.find(t => t.id === currentTextId);\n       if (textItem) {\n        pushUndo(\"edit text\");\n        textItem.fontWeight = e.target.value;\n        forgeTheTopology();\n       }\n      });\n      document.getElementById(\"text-font-style\").addEventListener(\"change\", (e) => {\n       if (!currentTextId) return;\n       const textItem = TEXT_DATA.list.find(t => t.id === currentTextId);\n       if (textItem) {\n        pushUndo(\"edit text\");\n        textItem.fontStyle = e.target.value;\n        forgeTheTopology();\n       }\n      });\n      document.getElementById(\"text-align\").addEventListener(\"change\", (e) => {\n       if (!currentTextId) return;\n       const textItem = TEXT_DATA.list.find(t => t.id === currentTextId);\n       if (textItem) {\n        pushUndo(\"edit text\");\n        textItem.textAlign = e.target.value;\n        forgeTheTopology();\n       }\n      });\n      document.getElementById(\"text-decoration\").addEventListener(\"change\", (e) => {\n       if (!currentTextId) return;\n       const textItem = TEXT_DATA.list.find(t => t.id === currentTextId);\n       if (textItem) {\n        pushUndo(\"edit text\");\n        textItem.textDecoration = e.target.value;\n        forgeTheTopology();\n       }\n      });\n      document.getElementById(\"text-bg-color\").addEventListener(\"input\", (e) => {\n       if (!currentTextId) return;\n       const textItem = TEXT_DATA.list.find(t => t.id === currentTextId);\n       if (textItem) {\n        pushUndo(\"edit text\");\n        textItem.bgColor = e.target.value;\n        forgeTheTopology();\n       }\n      });\n      document.getElementById(\"text-bg-enabled\").addEventListener(\"change\", (e) => {\n       if (!currentTextId) return;\n       const textItem = TEXT_DATA.list.find(t => t.id === currentTextId);\n       if (textItem) {\n        pushUndo(\"edit text\");\n        textItem.bgEnabled = e.target.checked;\n        forgeTheTopology();\n       }\n      });\n      document.getElementById(\"text-rotation\").addEventListener(\"input\", (e) => {\n        if (!currentTextId) return;\n        const textItem = TEXT_DATA.list.find(t => t.id === currentTextId);\n        if (textItem) {\n          pushUndo(\"rotate text\");\n          textItem.rotation = parseInt(e.target.value) || 0;\n          document.getElementById(\"text-rotation-val\").value = textItem.rotation;\n          forgeTheTopology();\n        }\n      });\n      document.getElementById(\"text-rotation-val\").addEventListener(\"input\", (e) => {\n        if (!currentTextId) return;\n        const textItem = TEXT_DATA.list.find(t => t.id === currentTextId);\n        if (textItem) {\n          pushUndo(\"rotate text\");\n          const val = parseInt(e.target.value) || 0;\n          textItem.rotation = val;\n          document.getElementById(\"text-rotation\").value = Math.max(-360, Math.min(360, val));\n          forgeTheTopology();\n        }\n      });\n      document.getElementById(\"delete-text\").addEventListener(\"click\", () => {\n       if (!currentTextId) return;\n       const textItem = TEXT_DATA.list.find(t => t.id === currentTextId);\n       if (textItem) {\n        challengeTheImmortal(`Delete text \"${(textItem.content || '').substring(0, 30)}...\"?`, () => {\n         deleteText(currentTextId);\n        });\n       }\n      });\n      const settingsBtn = document.getElementById(\"settings-btn\");\n      const settingsModal = document.getElementById(\"settings-modal\");\n      const settingsClose = document.getElementById(\"settings-close\");\n      settingsBtn.addEventListener(\"click\", () => {\n       document.getElementById(\"page-bg-color\").value = PAGE_STATE.background || \"#050608\";\n       document.getElementById(\"topbar-bg-color\").value = PAGE_STATE.topbarBg || \"#0b0e13\";\n       document.getElementById(\"topbar-border-color\").value = PAGE_STATE.topbarBorder || \"#1f2533\";\n       document.getElementById(\"panel-color\").value = PAGE_STATE.panel || \"#0b0e13\";\n       document.getElementById(\"panel-alt-color\").value = PAGE_STATE.panelAlt || \"#10141b\";\n       document.getElementById(\"sidebar-bg-color\").value = PAGE_STATE.sidebarBg || \"#10141b\";\n       document.getElementById(\"btn-bg-color\").value = PAGE_STATE.btnBg || \"#0b0e13\";\n       document.getElementById(\"btn-text-color\").value = PAGE_STATE.btnText || \"#e2e8f0\";\n       document.getElementById(\"tag-fill-color\").value = PAGE_STATE.tagFill || \"#1e293b\";\n       document.getElementById(\"tag-text-color\").value = PAGE_STATE.tagText || \"#e2e8f0\";\n       document.getElementById(\"tag-border-color\").value = PAGE_STATE.tagBorder || \"#475569\";\n       document.getElementById(\"input-bg-color\").value = PAGE_STATE.inputBg || \"#0b0e13\";\n       document.getElementById(\"input-text-color\").value = PAGE_STATE.inputText || \"#e2e8f0\";\n       document.getElementById(\"input-border-color\").value = PAGE_STATE.inputBorder || \"#1f2937\";\n       document.getElementById(\"input-font-family\").value = PAGE_STATE.inputFont || \"Inter, system-ui, sans-serif\";\n       document.getElementById(\"input-font-size\").value = PAGE_STATE.inputFontSize || 14;\n       document.getElementById(\"toolbar-bg-color\").value = PAGE_STATE.toolbarBg || \"#0f172a\";\n       document.getElementById(\"toolbar-border-color\").value = PAGE_STATE.toolbarBorder || \"#1f2937\";\n       document.getElementById(\"toolbar-text-color\").value = PAGE_STATE.toolbarText || \"#94a3b8\";\n       document.getElementById(\"toolbar-btn-bg-color\").value = PAGE_STATE.toolbarBtnBg || \"#0b0e13\";\n       document.getElementById(\"toolbar-btn-text-color\").value = PAGE_STATE.toolbarBtnText || \"#e2e8f0\";\n       document.getElementById(\"minimap-dots-color\").value = PAGE_STATE.minimapDots || \"#94a3b8\";\n       document.getElementById(\"canvas-hint-enabled\").checked = PAGE_STATE.canvasHintEnabled !== false;\n       document.getElementById(\"canvas-hint-bg-color\").value = PAGE_STATE.canvasHintBg || \"#0f172a\";\n       document.getElementById(\"canvas-hint-text-color\").value = PAGE_STATE.canvasHintColor || \"#94a3b8\";\n       document.getElementById(\"canvas-hint-text\").value = PAGE_STATE.canvasHintText || \"\";\n       document.getElementById(\"accent-color\").value = PAGE_STATE.accent || \"#4fd1c5\";\n       document.getElementById(\"danger-color\").value = PAGE_STATE.danger || \"#f56565\";\n       document.getElementById(\"text-main-color\").value = PAGE_STATE.textMain || \"#e2e8f0\";\n       document.getElementById(\"text-soft-color\").value = PAGE_STATE.textSoft || \"#94a3b8\";\n       document.getElementById(\"node-fill-color\").value = PAGE_STATE.nodeFill || \"#1e293b\";\n       document.getElementById(\"node-stroke-color\").value = PAGE_STATE.nodeStroke || \"#475569\";\n       document.getElementById(\"node-title-color\").value = PAGE_STATE.nodeTitle || \"#e2e8f0\";\n       document.getElementById(\"node-sub-color\").value = PAGE_STATE.nodeSub || \"#94a3b8\";\n       document.getElementById(\"node-title-size\").value = PAGE_STATE.nodeTitleSize || 18;\n       document.getElementById(\"node-sub-size\").value = PAGE_STATE.nodeSubSize || 13;\n       document.getElementById(\"node-font-family\").value = PAGE_STATE.nodeFont || \"Inter, system-ui, sans-serif\";\n       document.getElementById(\"default-edge-color\").value = PAGE_STATE.defaultEdge || \"#475569\";\n       document.getElementById(\"add-line-color\").value = PAGE_STATE.defaultEdge || \"#475569\";\n       document.getElementById(\"default-edge-routing\").value = PAGE_STATE.defaultEdgeRouting || \"curved\";\n\t   document.getElementById(\"anim-master\").addEventListener(\"change\", e => { ANIM_SETTINGS.masterAnim = e.target.checked; applyAnimZoneSettings(); });\ndocument.getElementById(\"anim-type-sweep\").addEventListener(\"change\", e => { ANIM_SETTINGS.animTypes.sweep = e.target.checked; applyAnimZoneSettings(); });\ndocument.getElementById(\"anim-type-pulse\").addEventListener(\"change\", e => { ANIM_SETTINGS.animTypes.pulse = e.target.checked; applyAnimZoneSettings(); });\ndocument.getElementById(\"anim-type-rings\").addEventListener(\"change\", e => { ANIM_SETTINGS.animTypes.rings = e.target.checked; applyAnimZoneSettings(); });\ndocument.getElementById(\"anim-type-spin\").addEventListener(\"change\", e => { ANIM_SETTINGS.animTypes.spin = e.target.checked; applyAnimZoneSettings(); });\ndocument.getElementById(\"anim-type-connections\").addEventListener(\"change\", e => { ANIM_SETTINGS.animTypes.connections = e.target.checked; applyAnimZoneSettings(); });\ndocument.getElementById(\"anim-cat-camera\").addEventListener(\"change\", e => { ANIM_SETTINGS.animCategories.camera = e.target.checked; applyAnimZoneSettings(); });\ndocument.getElementById(\"anim-cat-doorbell\").addEventListener(\"change\", e => { ANIM_SETTINGS.animCategories.doorbell = e.target.checked; applyAnimZoneSettings(); });\ndocument.getElementById(\"anim-cat-motion\").addEventListener(\"change\", e => { ANIM_SETTINGS.animCategories.motion = e.target.checked; applyAnimZoneSettings(); });\ndocument.getElementById(\"anim-cat-smoke\").addEventListener(\"change\", e => { ANIM_SETTINGS.animCategories.smoke = e.target.checked; applyAnimZoneSettings(); });\ndocument.getElementById(\"anim-cat-wifi\").addEventListener(\"change\", e => { ANIM_SETTINGS.animCategories.wifi = e.target.checked; applyAnimZoneSettings(); });\ndocument.getElementById(\"anim-cat-sensor\").addEventListener(\"change\", e => { ANIM_SETTINGS.animCategories.sensor = e.target.checked; applyAnimZoneSettings(); });\ndocument.getElementById(\"anim-cat-sprinkler\").addEventListener(\"change\", e => { ANIM_SETTINGS.animCategories.sprinkler = e.target.checked; applyAnimZoneSettings(); });\ndocument.getElementById(\"anim-cat-connections\").addEventListener(\"change\", e => { ANIM_SETTINGS.animCategories.connections = e.target.checked; applyAnimZoneSettings(); });\ndocument.getElementById(\"zone-master\").addEventListener(\"change\", e => { ANIM_SETTINGS.masterZones = e.target.checked; applyAnimZoneSettings(); });\ndocument.getElementById(\"zone-cat-camera\").addEventListener(\"change\", e => { ANIM_SETTINGS.zoneCategories.camera = e.target.checked; applyAnimZoneSettings(); });\ndocument.getElementById(\"zone-cat-doorbell\").addEventListener(\"change\", e => { ANIM_SETTINGS.zoneCategories.doorbell = e.target.checked; applyAnimZoneSettings(); });\ndocument.getElementById(\"zone-cat-motion\").addEventListener(\"change\", e => { ANIM_SETTINGS.zoneCategories.motion = e.target.checked; applyAnimZoneSettings(); });\ndocument.getElementById(\"zone-cat-smoke\").addEventListener(\"change\", e => { ANIM_SETTINGS.zoneCategories.smoke = e.target.checked; applyAnimZoneSettings(); });\ndocument.getElementById(\"zone-cat-wifi\").addEventListener(\"change\", e => { ANIM_SETTINGS.zoneCategories.wifi = e.target.checked; applyAnimZoneSettings(); });\ndocument.getElementById(\"zone-cat-sensor\").addEventListener(\"change\", e => { ANIM_SETTINGS.zoneCategories.sensor = e.target.checked; applyAnimZoneSettings(); });\ndocument.getElementById(\"zone-cat-sprinkler\").addEventListener(\"change\", e => { ANIM_SETTINGS.zoneCategories.sprinkler = e.target.checked; applyAnimZoneSettings(); });\n\n      document.getElementById(\"animation-style-select\").value = PAGE_STATE.animationStyle || \"arrows\";\n      document.getElementById(\"animation-direction-select\").value = PAGE_STATE.animationDirection || \"all\";\n      document.getElementById(\"animation-speed-select\").value = PAGE_STATE.animationSpeed || 1.5;\n\n      document.getElementById(\"animation-style-select\").addEventListener(\"change\", (e) => {\n       PAGE_STATE.animationStyle = e.target.value;\n       if (PAGE_STATE.animateConnections) forgeTheTopology();\n      });\n      document.getElementById(\"animation-direction-select\").addEventListener(\"change\", (e) => {\n       PAGE_STATE.animationDirection = e.target.value;\n       if (PAGE_STATE.animateConnections) forgeTheTopology();\n      });\n      document.getElementById(\"animation-speed-select\").addEventListener(\"change\", (e) => {\n       PAGE_STATE.animationSpeed = parseFloat(e.target.value);\n       if (PAGE_STATE.animateConnections) forgeTheTopology();\n      });\n       document.getElementById(\"add-line-routing\").value = PAGE_STATE.defaultEdgeRouting || \"orthogonal\";\n       document.getElementById(\"selection-handle-color\").value = PAGE_STATE.selectionHandle || \"#f59e0b\";\n       document.getElementById(\"selection-handle-size\").value = PAGE_STATE.selectionHandleSize || 8;\n       document.getElementById(\"group-indicator-color\").value = PAGE_STATE.groupIndicator || \"#4fd1c5\";\n       document.getElementById(\"canvas-gradient-top\").value = PAGE_STATE.canvasGradientTop || \"#1e2532\";\n       document.getElementById(\"canvas-gradient-bottom\").value = PAGE_STATE.canvasGradientBottom || \"#050608\";\n       document.getElementById(\"canvas-border-color\").value = PAGE_STATE.canvasBorder || \"#475569\";\n       document.getElementById(\"canvas-grid-color\").value = PAGE_STATE.canvasGrid || \"#475569\";\n       document.getElementById(\"canvas-grid-size\").value = PAGE_STATE.canvasGridSize || 50;\n\t   document.getElementById(\"canvas-grid-enabled\").checked = PAGE_STATE.canvasGridEnabled !== false;\n       document.getElementById(\"rack-frame-fill\").value = PAGE_STATE.rackFrameFill || \"#0f172a\";\n       document.getElementById(\"rack-frame-stroke\").value = PAGE_STATE.rackFrameStroke || \"#4fd1c5\";\n       document.getElementById(\"rack-line-color\").value = PAGE_STATE.rackLineColor || \"#475569\";\n\t   document.getElementById(\"rack-grid-enabled\").checked = PAGE_STATE.rackGridEnabled !== false;\n       document.getElementById(\"rack-text-color\").value = PAGE_STATE.rackTextColor || \"#4fd1c5\";\n\t   document.getElementById(\"view-only-mode\").checked = PAGE_STATE.viewOnly === true;\n       rebuildThemeDropdown();\n       updateDeleteButton();\n       settingsModal.classList.add(\"active\");\n      });\n      settingsClose.addEventListener(\"click\", () => {\n       settingsModal.classList.remove(\"active\");\n      });\n\t  document.getElementById(\"view-only-mode\").addEventListener(\"change\", (e) => {\n       PAGE_STATE.viewOnly = e.target.checked;\n       if (e.target.checked) {\n        document.getElementById(\"node-panel\").style.display = \"none\";\n        document.getElementById(\"edge-panel\").style.display = \"none\";\n        document.getElementById(\"text-panel\").style.display = \"none\";\n        document.getElementById(\"rect-panel\").style.display = \"none\";\n        document.getElementById(\"topology-toolbar\").style.setProperty('display', 'none', 'important');\n        document.getElementById(\"draw-toolbar\").style.setProperty('display', 'none', 'important');\n        currentNodeId = null;\n        currentEdgeId = null;\n        currentRectId = null;\n        currentTextId = null;\n        selectedNodes.clear();\n        selectedRects.clear();\n        selectedTexts.clear();\n        selectedEdges.clear();\n        const bulkToolbar = document.getElementById(\"bulk-toolbar\");\n        const bulkToolbarMobile = document.getElementById(\"bulk-toolbar-mobile\");\n        if (bulkToolbar) bulkToolbar.style.display = \"none\";\n        if (bulkToolbarMobile) bulkToolbarMobile.style.display = \"none\";\n        document.querySelectorAll(\".node-group\").forEach(n => n.classList.remove(\"active\", \"selected\"));\n        document.querySelectorAll(\".edge\").forEach(e => e.classList.remove(\"active\"));\n        document.querySelectorAll(\".rect-group\").forEach(r => r.classList.remove(\"active\"));\n        document.querySelectorAll(\".text-element\").forEach(t => t.classList.remove(\"active\"));\n       }\n       wieldThePower();\n       if (!e.target.checked) {\n        updateDrawToolbarVisibility();\n        updateTopologyToolbarVisibility();\n       }\n       forgeTheTopology();\n      });\n      settingsModal.addEventListener(\"click\", (e) => {\n       if (e.target === settingsModal) {\n        settingsModal.classList.remove(\"active\");\n       }\n      });\n      document.getElementById(\"page-bg-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.background = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"topbar-bg-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.topbarBg = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"topbar-border-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.topbarBorder = e.target.value;\n       wieldThePower();\n      });\nconst THEME_PRESETS = {\n  defaulted: { panel:\"#0b0e13\",panelAlt:\"#10141b\",sidebarBg:\"#10141b\",btnBg:\"#0b0e13\",btnText:\"#e2e8f0\",accent:\"#4fd1c5\",danger:\"#f56565\",textMain:\"#e2e8f0\",textSoft:\"#94a3b8\",topbarBg:\"rgba(9,12,20,0.9)\",topbarBorder:\"#1f2533\",nodeFill:\"#1e293b\",nodeStroke:\"#475569\",nodeTitle:\"#e2e8f0\",nodeSub:\"#94a3b8\",defaultEdge:\"#475569\",canvasGradientTop:\"#1e2532\",canvasGradientBottom:\"#050608\",tagFill:\"#1e293b\",tagText:\"#e2e8f0\",tagBorder:\"#475569\",inputBg:\"#0b0e13\",inputText:\"#e2e8f0\",inputBorder:\"#1f2937\",toolbarBg:\"#0f172a\",toolbarBorder:\"#1f2937\",toolbarText:\"#94a3b8\",toolbarBtnBg:\"#0b0e13\",toolbarBtnText:\"#e2e8f0\",selectionHandle:\"#f59e0b\",groupIndicator:\"#4fd1c5\",minimapDots:\"#94a3b8\",canvasHintBg:\"#0f172a\",canvasHintColor:\"#94a3b8\",canvasBorder:\"#475569\",canvasGrid:\"#475569\",rackFrameFill:\"#0f172a\",rackFrameStroke:\"#4fd1c5\",rackLineColor:\"#475569\",rackTextColor:\"#4fd1c5\" },\n  slate: { panel:\"#1e293b\",panelAlt:\"#334155\",sidebarBg:\"#1e293b\",btnBg:\"#334155\",btnText:\"#f1f5f9\",accent:\"#3b82f6\",danger:\"#ef4444\",textMain:\"#f1f5f9\",textSoft:\"#94a3b8\",topbarBg:\"#0f172a\",topbarBorder:\"#334155\",nodeFill:\"#334155\",nodeStroke:\"#3b82f6\",nodeTitle:\"#f1f5f9\",nodeSub:\"#94a3b8\",defaultEdge:\"#64748b\",canvasGradientTop:\"#1e293b\",canvasGradientBottom:\"#0f172a\",tagFill:\"#1e3a5f\",tagText:\"#93c5fd\",tagBorder:\"#3b82f6\",inputBg:\"#0f172a\",inputText:\"#f1f5f9\",inputBorder:\"#475569\",toolbarBg:\"#2563eb\",toolbarBorder:\"#3b82f6\",toolbarText:\"#ffffff\",toolbarBtnBg:\"#3b82f6\",toolbarBtnText:\"#ffffff\",selectionHandle:\"#f59e0b\",groupIndicator:\"#22d3ee\",minimapDots:\"#64748b\",canvasHintBg:\"#1e293b\",canvasHintColor:\"#94a3b8\",canvasBorder:\"#334155\",canvasGrid:\"#334155\",rackFrameFill:\"#1e293b\",rackFrameStroke:\"#3b82f6\",rackLineColor:\"#475569\",rackTextColor:\"#3b82f6\" },\n  graphite: { panel:\"#1f2937\",panelAlt:\"#374151\",sidebarBg:\"#111827\",btnBg:\"#374151\",btnText:\"#f9fafb\",accent:\"#f59e0b\",danger:\"#ef4444\",textMain:\"#f9fafb\",textSoft:\"#9ca3af\",topbarBg:\"#111827\",topbarBorder:\"#4b5563\",nodeFill:\"#374151\",nodeStroke:\"#f59e0b\",nodeTitle:\"#f9fafb\",nodeSub:\"#9ca3af\",defaultEdge:\"#6b7280\",canvasGradientTop:\"#1f2937\",canvasGradientBottom:\"#111827\",tagFill:\"#44403c\",tagText:\"#fbbf24\",tagBorder:\"#f59e0b\",inputBg:\"#111827\",inputText:\"#f9fafb\",inputBorder:\"#4b5563\",toolbarBg:\"#b45309\",toolbarBorder:\"#f59e0b\",toolbarText:\"#ffffff\",toolbarBtnBg:\"#f59e0b\",toolbarBtnText:\"#000000\",selectionHandle:\"#10b981\",groupIndicator:\"#06b6d4\",minimapDots:\"#6b7280\",canvasHintBg:\"#1f2937\",canvasHintColor:\"#9ca3af\",canvasBorder:\"#4b5563\",canvasGrid:\"#374151\",rackFrameFill:\"#1f2937\",rackFrameStroke:\"#f59e0b\",rackLineColor:\"#4b5563\",rackTextColor:\"#fbbf24\" },\n  frost: { panel:\"#f8fafc\",panelAlt:\"#e2e8f0\",sidebarBg:\"#f1f5f9\",btnBg:\"#e2e8f0\",btnText:\"#1e293b\",accent:\"#1e40af\",danger:\"#dc2626\",textMain:\"#0f172a\",textSoft:\"#475569\",topbarBg:\"#1e40af\",topbarBorder:\"#1e3a8a\",nodeFill:\"#ffffff\",nodeStroke:\"#1e40af\",nodeTitle:\"#0f172a\",nodeSub:\"#475569\",defaultEdge:\"#64748b\",canvasGradientTop:\"#e0e7ef\",canvasGradientBottom:\"#f8fafc\",tagFill:\"#dbeafe\",tagText:\"#1e40af\",tagBorder:\"#3b82f6\",inputBg:\"#ffffff\",inputText:\"#0f172a\",inputBorder:\"#cbd5e1\",toolbarBg:\"#1e40af\",toolbarBorder:\"#1e3a8a\",toolbarText:\"#ffffff\",toolbarBtnBg:\"#3b82f6\",toolbarBtnText:\"#ffffff\",selectionHandle:\"#ea580c\",groupIndicator:\"#059669\",minimapDots:\"#64748b\",canvasHintBg:\"#e2e8f0\",canvasHintColor:\"#475569\",canvasBorder:\"#cbd5e1\",canvasGrid:\"#cbd5e1\",rackFrameFill:\"#f1f5f9\",rackFrameStroke:\"#1e40af\",rackLineColor:\"#94a3b8\",rackTextColor:\"#1e40af\" },\n  synthwave: { panel:\"#87366d\",panelAlt:\"#10141b\",sidebarBg:\"#340934\",btnBg:\"#0b0e13\",btnText:\"#e2e8f0\",accent:\"#4fd1c5\",danger:\"#f56565\",textMain:\"#e2e8f0\",textSoft:\"#94a3b8\",topbarBg:\"#781c67\",topbarBorder:\"#1f2533\",nodeFill:\"#1e293b\",nodeStroke:\"#475569\",nodeTitle:\"#e2e8f0\",nodeSub:\"#94a3b8\",defaultEdge:\"#475569\",canvasGradientTop:\"#1e2532\",canvasGradientBottom:\"#050608\",tagFill:\"#1e293b\",tagText:\"#e2e8f0\",tagBorder:\"#475569\",inputBg:\"#0b0e13\",inputText:\"#e2e8f0\",inputBorder:\"#1f2937\",toolbarBg:\"#b95aed\",toolbarBorder:\"#b800eb\",toolbarText:\"#000000\",toolbarBtnBg:\"#ed01fe\",toolbarBtnText:\"#000000\",selectionHandle:\"#f59e0b\",groupIndicator:\"#4fd1c5\",minimapDots:\"#000000\",canvasHintBg:\"#0f172a\",canvasHintColor:\"#94a3b8\",canvasBorder:\"#000000\",canvasGrid:\"#000000\",rackFrameFill:\"#0f172a\",rackFrameStroke:\"#4fd1c5\",rackLineColor:\"#475569\",rackTextColor:\"#4fd1c5\" },\n  terminal: { panel:\"#000000\",panelAlt:\"#0a0a0a\",sidebarBg:\"#050505\",btnBg:\"#0a0a0a\",btnText:\"#33ff33\",accent:\"#33ff33\",danger:\"#ffaa00\",textMain:\"#33ff33\",textSoft:\"#1a9a1a\",topbarBg:\"#000000\",topbarBorder:\"#33ff33\",nodeFill:\"#0a0a0a\",nodeStroke:\"#33ff33\",nodeTitle:\"#33ff33\",nodeSub:\"#1a9a1a\",defaultEdge:\"#1a9a1a\",canvasGradientTop:\"#0a0f0a\",canvasGradientBottom:\"#000000\",tagFill:\"#0a1a0a\",tagText:\"#33ff33\",tagBorder:\"#33ff33\",inputBg:\"#000000\",inputText:\"#33ff33\",inputBorder:\"#1a9a1a\",toolbarBg:\"#1a9a1a\",toolbarBorder:\"#33ff33\",toolbarText:\"#000000\",toolbarBtnBg:\"#33ff33\",toolbarBtnText:\"#000000\",selectionHandle:\"#ffaa00\",groupIndicator:\"#00ffff\",minimapDots:\"#33ff33\",canvasHintBg:\"#0a0a0a\",canvasHintColor:\"#1a9a1a\",canvasBorder:\"#1a9a1a\",canvasGrid:\"#0f1f0f\",rackFrameFill:\"#050505\",rackFrameStroke:\"#33ff33\",rackLineColor:\"#1a9a1a\",rackTextColor:\"#33ff33\" },\n  dracula: { panel:\"#282a36\",panelAlt:\"#44475a\",sidebarBg:\"#21222c\",btnBg:\"#44475a\",btnText:\"#f8f8f2\",accent:\"#bd93f9\",danger:\"#ff5555\",textMain:\"#f8f8f2\",textSoft:\"#6272a4\",topbarBg:\"#21222c\",topbarBorder:\"#6272a4\",nodeFill:\"#44475a\",nodeStroke:\"#ff79c6\",nodeTitle:\"#f8f8f2\",nodeSub:\"#8be9fd\",defaultEdge:\"#bd93f9\",canvasGradientTop:\"#282a36\",canvasGradientBottom:\"#1a1b23\",tagFill:\"#3d3f4a\",tagText:\"#50fa7b\",tagBorder:\"#50fa7b\",inputBg:\"#21222c\",inputText:\"#f8f8f2\",inputBorder:\"#6272a4\",toolbarBg:\"#6272a4\",toolbarBorder:\"#bd93f9\",toolbarText:\"#f8f8f2\",toolbarBtnBg:\"#bd93f9\",toolbarBtnText:\"#282a36\",selectionHandle:\"#f1fa8c\",groupIndicator:\"#ff79c6\",minimapDots:\"#bd93f9\",canvasHintBg:\"#282a36\",canvasHintColor:\"#6272a4\",canvasBorder:\"#44475a\",canvasGrid:\"#44475a\",rackFrameFill:\"#282a36\",rackFrameStroke:\"#ff79c6\",rackLineColor:\"#6272a4\",rackTextColor:\"#8be9fd\" },\n  cobalt: { panel:\"#002240\",panelAlt:\"#003366\",sidebarBg:\"#001b33\",btnBg:\"#003366\",btnText:\"#ffffff\",accent:\"#ffc600\",danger:\"#ff628c\",textMain:\"#ffffff\",textSoft:\"#8090a0\",topbarBg:\"#001525\",topbarBorder:\"#0088ff\",nodeFill:\"#003366\",nodeStroke:\"#0088ff\",nodeTitle:\"#ffffff\",nodeSub:\"#80ffbb\",defaultEdge:\"#0088ff\",canvasGradientTop:\"#002240\",canvasGradientBottom:\"#00111f\",tagFill:\"#004080\",tagText:\"#ffc600\",tagBorder:\"#0088ff\",inputBg:\"#001525\",inputText:\"#ffffff\",inputBorder:\"#0066cc\",toolbarBg:\"#0066cc\",toolbarBorder:\"#0088ff\",toolbarText:\"#ffffff\",toolbarBtnBg:\"#0088ff\",toolbarBtnText:\"#ffffff\",selectionHandle:\"#ffc600\",groupIndicator:\"#3ad900\",minimapDots:\"#0088ff\",canvasHintBg:\"#002240\",canvasHintColor:\"#8090a0\",canvasBorder:\"#0066cc\",canvasGrid:\"#003366\",rackFrameFill:\"#002240\",rackFrameStroke:\"#0088ff\",rackLineColor:\"#0066cc\",rackTextColor:\"#80ffbb\" },\n  solarized: { panel:\"#073642\",panelAlt:\"#002b36\",sidebarBg:\"#002b36\",btnBg:\"#073642\",btnText:\"#93a1a1\",accent:\"#268bd2\",danger:\"#dc322f\",textMain:\"#93a1a1\",textSoft:\"#657b83\",topbarBg:\"#002b36\",topbarBorder:\"#586e75\",nodeFill:\"#073642\",nodeStroke:\"#2aa198\",nodeTitle:\"#93a1a1\",nodeSub:\"#839496\",defaultEdge:\"#657b83\",canvasGradientTop:\"#073642\",canvasGradientBottom:\"#002b36\",tagFill:\"#0a4050\",tagText:\"#b58900\",tagBorder:\"#b58900\",inputBg:\"#002b36\",inputText:\"#93a1a1\",inputBorder:\"#586e75\",toolbarBg:\"#268bd2\",toolbarBorder:\"#2aa198\",toolbarText:\"#fdf6e3\",toolbarBtnBg:\"#2aa198\",toolbarBtnText:\"#002b36\",selectionHandle:\"#cb4b16\",groupIndicator:\"#d33682\",minimapDots:\"#657b83\",canvasHintBg:\"#073642\",canvasHintColor:\"#657b83\",canvasBorder:\"#586e75\",canvasGrid:\"#094050\",rackFrameFill:\"#002b36\",rackFrameStroke:\"#2aa198\",rackLineColor:\"#586e75\",rackTextColor:\"#859900\" }\n};\ndocument.getElementById(\"theme-preset\").addEventListener(\"change\", function() {\n  updateDeleteButton();\n  var p = THEME_PRESETS[this.value];\n  if (!p && this.value.startsWith(\"mytheme-\")) {\n    var found = savedStyleSets.find(function(s) { return s.id === document.getElementById(\"theme-preset\").value; });\n    if (found) p = found.styles;\n  }\n  if (!p) return;\n  Object.assign(PAGE_STATE, p);\n  document.getElementById(\"panel-color\").value = p.panel;\n  document.getElementById(\"panel-alt-color\").value = p.panelAlt;\n  document.getElementById(\"sidebar-bg-color\").value = p.sidebarBg;\n  document.getElementById(\"btn-bg-color\").value = p.btnBg;\n  document.getElementById(\"btn-text-color\").value = p.btnText;\n  document.getElementById(\"accent-color\").value = p.accent;\n  document.getElementById(\"danger-color\").value = p.danger;\n  document.getElementById(\"text-main-color\").value = p.textMain;\n  document.getElementById(\"text-soft-color\").value = p.textSoft;\n  document.getElementById(\"topbar-border-color\").value = p.topbarBorder;\n  document.getElementById(\"node-fill-color\").value = p.nodeFill;\n  document.getElementById(\"node-stroke-color\").value = p.nodeStroke;\n  document.getElementById(\"node-title-color\").value = p.nodeTitle;\n  document.getElementById(\"node-sub-color\").value = p.nodeSub;\n  document.getElementById(\"default-edge-color\").value = p.defaultEdge;\n  document.getElementById(\"canvas-gradient-top\").value = p.canvasGradientTop;\n  document.getElementById(\"canvas-gradient-bottom\").value = p.canvasGradientBottom;\n  document.getElementById(\"tag-fill-color\").value = p.tagFill;\n  document.getElementById(\"tag-text-color\").value = p.tagText;\n  document.getElementById(\"tag-border-color\").value = p.tagBorder;\n  document.getElementById(\"input-bg-color\").value = p.inputBg;\n  document.getElementById(\"input-text-color\").value = p.inputText;\n  document.getElementById(\"input-border-color\").value = p.inputBorder;\n  document.getElementById(\"toolbar-bg-color\").value = p.toolbarBg;\n  document.getElementById(\"toolbar-border-color\").value = p.toolbarBorder;\n  document.getElementById(\"toolbar-text-color\").value = p.toolbarText;\n  document.getElementById(\"toolbar-btn-bg-color\").value = p.toolbarBtnBg;\n  document.getElementById(\"toolbar-btn-text-color\").value = p.toolbarBtnText;\n  document.getElementById(\"selection-handle-color\").value = p.selectionHandle;\n  document.getElementById(\"group-indicator-color\").value = p.groupIndicator;\n  document.getElementById(\"minimap-dots-color\").value = p.minimapDots;\n  document.getElementById(\"canvas-hint-bg-color\").value = p.canvasHintBg;\n  document.getElementById(\"canvas-hint-text-color\").value = p.canvasHintColor;\n  document.getElementById(\"canvas-border-color\").value = p.canvasBorder;\n  document.getElementById(\"canvas-grid-color\").value = p.canvasGrid;\n  document.getElementById(\"rack-frame-fill\").value = p.rackFrameFill;\n  document.getElementById(\"rack-frame-stroke\").value = p.rackFrameStroke;\n  document.getElementById(\"rack-line-color\").value = p.rackLineColor;\n  document.getElementById(\"rack-text-color\").value = p.rackTextColor;\n  wieldThePower();\n  forgeTheTopology();\n});\ndocument.querySelectorAll('#settings-modal .style-content input, #settings-modal .style-content select').forEach(el => {\n  if (el.id === 'theme-preset') return;\n  el.addEventListener('input', () => document.getElementById('theme-preset').value = '');\n  el.addEventListener('change', () => document.getElementById('theme-preset').value = '');\n});\n      document.getElementById(\"panel-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.panel = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"panel-alt-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.panelAlt = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"sidebar-bg-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.sidebarBg = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"btn-bg-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.btnBg = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"btn-text-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.btnText = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"tag-fill-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.tagFill = e.target.value;\n       wieldThePower();\n       forgeTheTopology();\n      });\n      document.getElementById(\"tag-text-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.tagText = e.target.value;\n       wieldThePower();\n       forgeTheTopology();\n      });\n      document.getElementById(\"tag-border-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.tagBorder = e.target.value;\n       wieldThePower();\n       forgeTheTopology();\n      });\n\t  document.getElementById(\"selection-fill-color\").addEventListener(\"input\", function() {\n       selectionBoxStyle.fillColor = this.value;\n      });\n      document.getElementById(\"selection-fill-opacity\").addEventListener(\"input\", function() {\n       selectionBoxStyle.fillOpacity = parseFloat(this.value);\n       document.getElementById(\"selection-fill-opacity-val\").textContent = Math.round(this.value * 100) + \"%\";\n      });\n      document.getElementById(\"selection-stroke-color\").addEventListener(\"input\", function() {\n       selectionBoxStyle.strokeColor = this.value;\n      });\n      document.getElementById(\"selection-stroke-width\").addEventListener(\"input\", function() {\n       selectionBoxStyle.strokeWidth = parseInt(this.value) || 2;\n      });\n      document.getElementById(\"selection-stroke-style\").addEventListener(\"change\", function() {\n       selectionBoxStyle.strokeDasharray = this.value;\n      });\n      document.getElementById(\"input-bg-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.inputBg = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"input-text-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.inputText = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"input-border-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.inputBorder = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"input-font-family\").addEventListener(\"change\", (e) => {\n       PAGE_STATE.inputFont = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"input-font-size\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.inputFontSize = parseInt(e.target.value) || 14;\n       wieldThePower();\n      });\n      document.getElementById(\"toolbar-bg-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.toolbarBg = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"toolbar-border-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.toolbarBorder = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"toolbar-text-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.toolbarText = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"toolbar-btn-bg-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.toolbarBtnBg = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"toolbar-btn-text-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.toolbarBtnText = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"minimap-dots-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.minimapDots = e.target.value;\n       wieldThePower();\n       forgeTheTopology();\n      });\n      document.getElementById(\"canvas-hint-enabled\").addEventListener(\"change\", (e) => {\n       PAGE_STATE.canvasHintEnabled = e.target.checked;\n       wieldThePower();\n      });\n      document.getElementById(\"canvas-hint-bg-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.canvasHintBg = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"canvas-hint-text-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.canvasHintColor = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"canvas-hint-text\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.canvasHintText = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"accent-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.accent = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"danger-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.danger = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"text-main-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.textMain = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"text-soft-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.textSoft = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"node-fill-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.nodeFill = e.target.value;\n       forgeTheTopology();\n      });\n      document.getElementById(\"node-stroke-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.nodeStroke = e.target.value;\n       forgeTheTopology();\n      });\n      document.getElementById(\"node-title-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.nodeTitle = e.target.value;\n       forgeTheTopology();\n      });\n      document.getElementById(\"node-sub-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.nodeSub = e.target.value;\n       forgeTheTopology();\n      });\n      document.getElementById(\"node-title-size\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.nodeTitleSize = parseInt(e.target.value) || 18;\n       forgeTheTopology();\n      });\n      document.getElementById(\"node-sub-size\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.nodeSubSize = parseInt(e.target.value) || 13;\n       forgeTheTopology();\n      });\n      document.getElementById(\"node-font-family\").addEventListener(\"change\", (e) => {\n       PAGE_STATE.nodeFont = e.target.value;\n       forgeTheTopology();\n      });\n\t  document.getElementById(\"default-edge-routing\").addEventListener(\"change\", (e) => {\n       PAGE_STATE.defaultEdgeRouting = e.target.value;\n       document.getElementById(\"add-line-routing\").value = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"apply-routing-all\").addEventListener(\"click\", () => {\n       const routing = document.getElementById(\"default-edge-routing\").value;\n       if (!confirm(`Apply \"${routing}\" routing to all ${EDGE_DATA.list.length} connections?`)) return;\n       pushUndo(\"apply routing to all\");\n       EDGE_DATA.list.forEach(edge => {\n        edge.routing = routing;\n       });\n       forgeTheTopology();\n      });\n      document.getElementById(\"default-edge-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.defaultEdge = e.target.value;\n       document.getElementById(\"add-line-color\").value = e.target.value;\n       forgeTheTopology();\n      });\n      document.getElementById(\"selection-handle-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.selectionHandle = e.target.value;\n       forgeTheTopology();\n      });\n      document.getElementById(\"selection-handle-size\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.selectionHandleSize = parseInt(e.target.value) || 8;\n       forgeTheTopology();\n      });\n      document.getElementById(\"group-indicator-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.groupIndicator = e.target.value;\n       forgeTheTopology();\n      });\n      document.getElementById(\"canvas-gradient-top\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.canvasGradientTop = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"canvas-gradient-bottom\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.canvasGradientBottom = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"canvas-border-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.canvasBorder = e.target.value;\n       forgeTheTopology();\n      });\n      document.getElementById(\"canvas-grid-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.canvasGrid = e.target.value;\n       forgeTheTopology();\n      });\n      document.getElementById(\"canvas-grid-size\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.canvasGridSize = parseInt(e.target.value) || 50;\n       forgeTheTopology();\n      });\n\t  document.getElementById(\"canvas-grid-enabled\").addEventListener(\"change\", (e) => {\n PAGE_STATE.canvasGridEnabled = e.target.checked;\n forgeTheTopology();\n});\n      document.getElementById(\"rack-frame-fill\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.rackFrameFill = e.target.value;\n       forgeTheTopology();\n      });\n      document.getElementById(\"rack-frame-stroke\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.rackFrameStroke = e.target.value;\n       forgeTheTopology();\n      });\n      document.getElementById(\"rack-line-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.rackLineColor = e.target.value;\n       forgeTheTopology();\n      });\n\t  document.getElementById(\"rack-grid-enabled\").addEventListener(\"change\", (e) => {\n PAGE_STATE.rackGridEnabled = e.target.checked;\n forgeTheTopology();\n});\n      document.getElementById(\"rack-text-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.rackTextColor = e.target.value;\n       forgeTheTopology();\n      });\n      (function initializeResizers() {\n        const headerResizer = document.getElementById('header-resizer');\n        const sidebarResizer = document.getElementById('sidebar-resizer');\n        const mobileFooterResizer = document.getElementById('mobile-footer-resizer');\n        let isResizing = false;\n        let currentResizer = null;\n        let startY = 0;\n        let startX = 0;\n        let startHeight = 0;\n        let startWidth = 0;\n        function getClientPos(e) {\n          if (e.touches && e.touches.length > 0) {\n            return { x: e.touches[0].clientX, y: e.touches[0].clientY };\n          }\n          return { x: e.clientX, y: e.clientY };\n        }\n        function startResize(resizer, type, e) {\n          isResizing = true;\n          currentResizer = type;\n          const pos = getClientPos(e);\n          if (type === 'header') {\n            startY = pos.y;\n            startHeight = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--topbar-height'));\n          } else if (type === 'sidebar') {\n            startX = pos.x;\n            startWidth = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--sidebar-width'));\n          } else if (type === 'mobile-footer') {\n            startY = pos.y;\n            const currentVh = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--mobile-footer-height'));\n            startHeight = (currentVh / 100) * window.innerHeight;\n          }\n          resizer.classList.add('resizing');\n          document.body.classList.add('resizing');\n          document.body.style.cursor = (type === 'sidebar') ? 'col-resize' : 'row-resize';\n          e.preventDefault();\n        }\n        if (headerResizer) {\n          headerResizer.addEventListener('mousedown', (e) => startResize(headerResizer, 'header', e));\n          headerResizer.addEventListener('touchstart', (e) => startResize(headerResizer, 'header', e), { passive: false });\n        }\n        if (sidebarResizer) {\n          sidebarResizer.addEventListener('mousedown', (e) => startResize(sidebarResizer, 'sidebar', e));\n          sidebarResizer.addEventListener('touchstart', (e) => startResize(sidebarResizer, 'sidebar', e), { passive: false });\n        }\n        if (mobileFooterResizer) {\n          mobileFooterResizer.addEventListener('mousedown', (e) => startResize(mobileFooterResizer, 'mobile-footer', e));\n          mobileFooterResizer.addEventListener('touchstart', (e) => startResize(mobileFooterResizer, 'mobile-footer', e), { passive: false });\n        }\n        function handleMove(e) {\n          if (!isResizing) return;\n          const pos = getClientPos(e);\n          if (currentResizer === 'header') {\n            const deltaY = pos.y - startY;\n            const newHeight = Math.max(40, Math.min(150, startHeight + deltaY));\n            document.documentElement.style.setProperty('--topbar-height', newHeight + 'px');\n          } else if (currentResizer === 'sidebar') {\n            const deltaX = startX - pos.x;\n            const newWidth = Math.max(200, Math.min(800, startWidth + deltaX));\n            document.documentElement.style.setProperty('--sidebar-width', newWidth + 'px');\n          } else if (currentResizer === 'mobile-footer') {\n            const deltaY = startY - pos.y;\n            const newHeight = startHeight + deltaY;\n            const newVh = Math.max(15, Math.min(80, (newHeight / window.innerHeight) * 100));\n            document.documentElement.style.setProperty('--mobile-footer-height', newVh + 'vh');\n          }\n          e.preventDefault();\n        }\n        document.addEventListener('mousemove', handleMove);\n        document.addEventListener('touchmove', handleMove, { passive: false });\n        function handleEnd() {\n          if (isResizing) {\n            isResizing = false;\n            if (currentResizer === 'header') {\n              PAGE_STATE.topbarHeight = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--topbar-height'));\n              headerResizer.classList.remove('resizing');\n            } else if (currentResizer === 'sidebar') {\n              PAGE_STATE.sidebarWidth = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--sidebar-width'));\n              sidebarResizer.classList.remove('resizing');\n            } else if (currentResizer === 'mobile-footer') {\n              PAGE_STATE.mobileFooterHeight = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--mobile-footer-height'));\n              mobileFooterResizer.classList.remove('resizing');\n            }\n            document.body.classList.remove('resizing');\n            document.body.style.cursor = '';\n            currentResizer = null;\n          }\n        }\n        document.addEventListener('mouseup', handleEnd);\n        document.addEventListener('touchend', handleEnd);\n        document.addEventListener('touchcancel', handleEnd);\n      })();\n      document.getElementById(\"import-data-file\").addEventListener(\"change\", async (e) => {\n       const file = e.target.files[0];\n       if (!file) return;\n       try {\n        const text = await file.text();\n        const data = JSON.parse(text);\n        if (!data.nodeData || !data.edgeData) {\n         alert(\"Invalid data file. Missing required fields.\");\n         return;\n        }\n        const confirmMsg = `This will replace all current data with the imported data.\\n\\nImporting:\\n- ${Object.keys(data.nodeData).length} nodes\\n- ${data.edgeData.list?.length || 0} connections\\n- ${data.documentTabs?.length || 1} tab(s)\\n\\nContinue?`;\n        if (!confirm(confirmMsg)) {\n         e.target.value = \"\";\n         return;\n        }\n\t\tpushUndo('import json');\n        NODE_DATA = data.nodeData || {};\n        EDGE_DATA = data.edgeData || {\n         list: []\n        };\n        EDGE_LEGEND = data.edgeLegend || {};\n        RECT_DATA = data.rectData || { list: [] };\n        TEXT_DATA = data.textData || { list: [] };\n        savedPositions = data.nodePositions || {};\n        savedSizes = data.nodeSizes || {};\n        savedStyles = data.nodeStyles || {};\n        if (data.page) {\n         PAGE_STATE = Object.assign({}, DEFAULT_PAGE_STATE, data.page);\n         wieldThePower();\n        }\n        if (data.canvas) {\n         canvasState.zoom = data.canvas.zoom || 1;\n         canvasState.panX = data.canvas.panX || 0;\n         canvasState.panY = data.canvas.panY || 0;\n        }\n        if (data.page?.title) {\n         document.title = data.page.title;\n         document.querySelector(\".editable-page-title\", ).textContent = data.page.title;\n        }\n        if (data.savedStyleSets) {\n          savedStyleSets = data.savedStyleSets;\n        }\n        if (data.documentTabs) {\n         documentTabs = data.documentTabs;\n         currentTabIndex = data.currentTabIndex || 0;\n        }\n        if (data.savedTopologyView) {\n         savedTopologyView = data.savedTopologyView;\n        }\n        if (data.encryptedSections) {\n         encryptedSections = data.encryptedSections;\n        }\n        if (data.auditLog && Array.isArray(data.auditLog)) {\n         auditLog = data.auditLog;\n         try {\n           localStorage.setItem(AUDIT_STORAGE_KEY, JSON.stringify(auditLog));\n         } catch (e) {\n           console.warn(\"Failed to sync audit log to localStorage:\", e);\n         }\n        }\n        Object.values(NODE_DATA).forEach(node => {\n          if (!node.tags) node.tags = [];\n          if (!node.notes) node.notes = [];\n        });\n        EDGE_DATA.list.forEach(edge => {\n          if (!edge.notes) edge.notes = [];\n        });\n        forgeTheTopology();\n        if (typeof forgeTheLegend === 'function') forgeTheLegend();\n\t\tlogAuditEvent(\"import\", `Imported JSON: ${file.name} (${Object.keys(data.nodeData).length} nodes, ${data.edgeData.list?.length || 0} connections)`);\n        updateViewBox();\n        const nodeIds = Object.keys(NODE_DATA);\n        if (nodeIds.length > 0) {\n         claimTheImmortal(nodeIds[0]);\n        } else {\n         document.getElementById(\"node-panel\").style.display = \"none\";\n         document.getElementById(\"edge-panel\").style.display = \"none\";\n         document.getElementById(\"topology-toolbar\").style.display = \"none\";\n        }\n        e.target.value = \"\";\n       } catch (err) {\n        console.error(\"Import error:\", err);\n        alert(`Failed to import data: ${err.message}`);\n        e.target.value = \"\";\n       }\n      });\n      const saveHelpBtn = document.getElementById(\"save-help-btn\");\n      const saveInfoModal = document.getElementById(\"save-info-modal\");\n      const saveInfoClose = document.getElementById(\"save-info-close\");\n      saveHelpBtn.addEventListener(\"click\", () => {\n       saveInfoModal.classList.add(\"active\");\n      });\n      saveInfoClose.addEventListener(\"click\", () => {\n       saveInfoModal.classList.remove(\"active\");\n      });\n      saveInfoModal.addEventListener(\"click\", (e) => {\n       if (e.target === saveInfoModal) {\n        saveInfoModal.classList.remove(\"active\");\n       }\n      });\n      document.querySelectorAll(\".help-tab\").forEach(tab => {\n        tab.addEventListener(\"click\", () => {\n          document.querySelectorAll(\".help-tab\").forEach(t => { t.style.background = \"var(--panel)\"; t.style.color = \"var(--text-main)\"; });\n          tab.style.background = \"var(--accent)\"; tab.style.color = \"var(--bg)\";\n          document.querySelectorAll(\".help-tab-content\").forEach(c => c.style.display = \"none\");\n          document.getElementById(\"help-tab-\" + tab.dataset.tab).style.display = \"block\";\n        });\n      });\n      async function deriveKey(password, salt) {\n      const encoder = new TextEncoder();\n      const keyMaterial = await crypto.subtle.importKey(\n      \"raw\",\n      encoder.encode(password),\n      \"PBKDF2\",\n      false,\n      [\"deriveKey\"]\n      );\n      return crypto.subtle.deriveKey(\n      {\n      name: \"PBKDF2\",\n      salt: salt,\n      iterations: 200000,\n      hash: \"SHA-256\"\n      },\n      keyMaterial,\n      {\n      name: \"AES-GCM\",\n      length: 256\n      },\n      false,\n      [\"encrypt\", \"decrypt\"]\n      );\n      }\n      async function encryptData(data, password) {\n      const encoder = new TextEncoder();\n      const salt = crypto.getRandomValues(new Uint8Array(16));\n      const iv   = crypto.getRandomValues(new Uint8Array(12));\n      const key = await deriveKey(password, salt);\n      const encrypted = await crypto.subtle.encrypt(\n      { name: \"AES-GCM\", iv },\n      key,\n      encoder.encode(data)\n      );\n      const encryptedU8 = new Uint8Array(encrypted);\n      const result = new Uint8Array(salt.length + iv.length + encryptedU8.length);\n      result.set(salt, 0);\n      result.set(iv, salt.length);\n      result.set(encryptedU8, salt.length + iv.length);\n      return \"ENCRYPTED:\" + u8ToBase64(result);\n      }\n      async function decryptData(encryptedData, password) {\n      const base64Data = encryptedData.replace(\"ENCRYPTED:\", \"\");\n      const fullData   = base64ToU8(base64Data);\n      const salt      = fullData.slice(0, 16);\n      const iv        = fullData.slice(16, 28);\n      const encrypted = fullData.slice(28);\n      const key = await deriveKey(password, salt);\n      const decrypted = await crypto.subtle.decrypt(\n      { name: \"AES-GCM\", iv },\n      key,\n      encrypted\n      );\n      const decoder = new TextDecoder();\n      return decoder.decode(decrypted);\n      }\n      function isEncrypted(data) {\n       return typeof data === \"string\" && data.startsWith(\"ENCRYPTED:\");\n      }\n      function captureTheQuickening() {\n       const currentTab = documentTabs[currentTabIndex];\n       currentTab.nodes = JSON.parse(JSON.stringify(NODE_DATA));\n       currentTab.edges = JSON.parse(JSON.stringify(EDGE_DATA));\n       currentTab.positions = JSON.parse(JSON.stringify(savedPositions));\n       currentTab.sizes = JSON.parse(JSON.stringify(savedSizes));\n       currentTab.styles = JSON.parse(JSON.stringify(savedStyles));\n       currentTab.legend = JSON.parse(JSON.stringify(EDGE_LEGEND));\n       currentTab.rects = JSON.parse(JSON.stringify(RECT_DATA));\n       currentTab.texts = JSON.parse(JSON.stringify(TEXT_DATA));\n       currentTab.pageState = JSON.parse(JSON.stringify(PAGE_STATE));\n       return {\n        nodeData: NODE_DATA,\n        edgeData: EDGE_DATA,\n        rectData: RECT_DATA,\n        textData: TEXT_DATA,\n        edgeLegend: EDGE_LEGEND,\n        nodePositions: savedPositions,\n        nodeSizes: savedSizes,\n        nodeStyles: savedStyles,\n        page: PAGE_STATE,\n        canvas: {\n         zoom: canvasState.zoom,\n         panX: canvasState.panX,\n         panY: canvasState.panY,\n        },\n        savedTopologyView: savedTopologyView,\n        documentTabs: documentTabs,\n        currentTabIndex: currentTabIndex,\n        encryptedSections: encryptedSections,\n        auditLog: auditLog,\n\t\tsavedStyleSets: savedStyleSets,\n       };\n      }\n      function assembleTheImmortalForm() {\n      const canvasHintEl = document.getElementById(\"canvas-hint\");\n       const savedHintHTML = canvasHintEl ? canvasHintEl.innerHTML : \"\";\n       if (canvasHintEl) canvasHintEl.innerHTML = DEFAULT_CANVAS_HINT;\n       const clone = document.documentElement.cloneNode(true);\n       [\"edge-legend-mini\", \"minimap-mini\", \"draw-toolbar-mini\", \"topology-toolbar-mini\"].forEach(id => {\n        const all = clone.querySelectorAll(\"#\" + id);\n        for (let i = 1; i < all.length; i++) {\n         all[i].remove();\n        }\n       });\n       const addLineSelect = clone.querySelector(\"#add-line-select\");\n       if (addLineSelect) addLineSelect.innerHTML = \"\";\n       const minimapSvg = clone.querySelector(\"#minimap\");\n       if (minimapSvg) {\n        minimapSvg.querySelectorAll(\".minimap-node, .minimap-edge\").forEach(el => el.remove());\n       }\n       const canvasGrid = clone.querySelector(\"#canvas-grid\");\n       if (canvasGrid) canvasGrid.innerHTML = \"\";\n       const mainSvg = clone.querySelector(\"#map\");\n       if (mainSvg) mainSvg.innerHTML = \"\";\n       const nodeScript = clone.querySelector(\"#nodes-json\");\n       if (nodeScript) {\n        nodeScript.textContent = JSON.stringify(NODE_DATA, null, 2);\n       }\n       let stateScript = clone.querySelector(\"#topology-state\");\n       if (!stateScript) {\n        stateScript = document.createElement(\"script\");\n        stateScript.id = \"topology-state\";\n        stateScript.type = \"application/json\";\n        const body = clone.querySelector(\"body\") || clone;\n        body.appendChild(stateScript);\n       }\n       stateScript.textContent = JSON.stringify(captureTheQuickening(), null, 2);\n       return \"<!DOCTYPE html> \\n \" + clone.outerHTML;\n      }\n      async function becomeImmortal() {\n       saveRollbackVersion(\"Auto-save\");\n       const encryptEnabled = document.getElementById(\"encrypt-toggle\").checked;\n       let stateData = JSON.stringify(captureTheQuickening(), null, 2);\n       if (encryptEnabled) {\n        const password = prompt(\"Enter a password to encrypt your data:\\n(Remember this password! You will need it to open this file and its non recoverable!)\");\n        if (!password) {\n         alert(\"Encryption cancelled. File not saved.\");\n         return;\n        }\n        const confirmPassword = prompt(\"Confirm your password:\");\n        if (password !== confirmPassword) {\n         alert(\"Passwords do not match. File not saved.\");\n         return;\n        }\n        try {\n         stateData = await encryptData(stateData, password);\n        } catch (e) {\n         alert(\"Encryption failed: \" + e.message);\n         return;\n        }\n       }\n       const canvasHintEl = document.getElementById(\"canvas-hint\");\n       const savedHintHTML = canvasHintEl ? canvasHintEl.innerHTML : \"\";\n       if (canvasHintEl) canvasHintEl.innerHTML = DEFAULT_CANVAS_HINT;\n       const clone = document.documentElement.cloneNode(true);\n       [\"edge-legend-mini\", \"minimap-mini\", \"draw-toolbar-mini\", \"topology-toolbar-mini\"].forEach(id => {\n        const all = clone.querySelectorAll(\"#\" + id);\n        for (let i = 1; i < all.length; i++) {\n         all[i].remove();\n        }\n       });\n       const selectsToClear = [\"#add-line-select\", \"#new-edge-from\", \"#new-edge-to\", \"#assigned-rack-select\"];\n       selectsToClear.forEach(sel => {\n        const el = clone.querySelector(sel);\n        if (el) el.innerHTML = \"\";\n       });\n       const minimapSvg = clone.querySelector(\"#minimap\");\n       if (minimapSvg) minimapSvg.querySelectorAll(\".minimap-node, .minimap-edge\").forEach(el => el.remove());\n       const mainSvg = clone.querySelector(\"#map\");\n       if (mainSvg) mainSvg.innerHTML = \"\";\n       const edgeLegendContent = clone.querySelector(\"#edge-legend-content\");\n       if (edgeLegendContent) edgeLegendContent.innerHTML = \"\";\n       const nodeTags = clone.querySelector(\"#node-tags\");\n       if (nodeTags) nodeTags.innerHTML = \"\";\n       const nodeNotes = clone.querySelector(\"#node-notes\");\n       if (nodeNotes) nodeNotes.innerHTML = \"\";\n       const edgeNotes = clone.querySelector(\"#edge-notes\");\n       if (edgeNotes) edgeNotes.innerHTML = \"\";\n       const tabBar = clone.querySelector(\"#tab-bar\");\n       if (tabBar) tabBar.innerHTML = \"\";\n       const rollbackList = clone.querySelector(\"#rollback-list\");\n       if (rollbackList) rollbackList.innerHTML = \"\";\n       const auditList = clone.querySelector(\"#audit-log-list\");\n       if (auditList) auditList.innerHTML = \"\";\n       const nodeScript = clone.querySelector(\"#nodes-json\");\n       if (nodeScript) {\n        if (encryptEnabled) {\n         nodeScript.textContent = JSON.stringify({}, null, 2);\n        } else {\n         nodeScript.textContent = JSON.stringify(NODE_DATA, null, 2);\n        }\n       }\n       let stateScript = clone.querySelector(\"#topology-state\");\n       if (!stateScript) {\n        stateScript = document.createElement(\"script\");\n        stateScript.id = \"topology-state\";\n        stateScript.type = \"application/json\";\n        const body = clone.querySelector(\"body\") || clone;\n        body.appendChild(stateScript);\n       }\n       stateScript.textContent = stateData;\n       const html = \"<!DOCTYPE html> \\n \" + clone.outerHTML;\n       const blob = new Blob([html], {\n        type: \"text/html\"\n       });\n       const url = URL.createObjectURL(blob);\n       const a = document.createElement(\"a\");\n       a.href = url;\n       const safeTitle = (PAGE_STATE.title || document.title || \"network-topology\").toLowerCase().replace(/[^a-z0-9\\-]+/g, \"-\");\n       a.download = safeTitle + \".html\";\n       document.body.appendChild(a);\n       a.click();\n       document.body.removeChild(a);\n       URL.revokeObjectURL(url);\n       if (canvasHintEl) canvasHintEl.innerHTML = savedHintHTML;\n       logAuditEvent(\"save\", `File saved: ${safeTitle}.html`);\n      }\n      function captureState(scope = \"all\") {\n       const clone = typeof structuredClone === 'function' \n         ? (o) => structuredClone(o)\n         : (o) => JSON.parse(JSON.stringify(o));\n       \n       if (scope === \"all\") {\n        return {\n         scope: \"all\",\n         nodes: clone(NODE_DATA),\n         edges: clone(EDGE_DATA),\n         positions: clone(savedPositions),\n         sizes: clone(savedSizes),\n         styles: clone(savedStyles),\n         legend: clone(EDGE_LEGEND),\n         rects: clone(RECT_DATA),\n         texts: clone(TEXT_DATA)\n        };\n       }\n       \n       const state = { scope };\n       if (scope.includes(\"nodes\")) state.nodes = clone(NODE_DATA);\n       if (scope.includes(\"edges\")) state.edges = clone(EDGE_DATA);\n       if (scope.includes(\"positions\")) state.positions = clone(savedPositions);\n       if (scope.includes(\"sizes\")) state.sizes = clone(savedSizes);\n       if (scope.includes(\"styles\")) state.styles = clone(savedStyles);\n       if (scope.includes(\"legend\")) state.legend = clone(EDGE_LEGEND);\n       if (scope.includes(\"rects\")) state.rects = clone(RECT_DATA);\n       if (scope.includes(\"texts\")) state.texts = clone(TEXT_DATA);\n       return state;\n      }\n      let lastUndoPush = 0;\n\t  function pushUndo(action = \"\") {\n\t   const now = Date.now();\n\t   if (now - lastUndoPush < 100 && undoStack.length > 0) {\n\t     return;\n    \t }\n\t   lastUndoPush = now;\n       const actionScopes = {\n        \"move nodes\": \"positions\",\n        \"nudge\": \"positions\",\n        \"align nodes\": \"positions\",\n        \"distribute nodes\": \"positions\",\n        \"snap to grid\": \"positions\",\n        \"resize node\": \"sizes\",\n        \"reset size\": \"sizes\",\n        \"style change\": \"styles\",\n        \"edit edge\": \"edges\",\n        \"edit edge routing\": \"edges\",\n        \"edit edge point\": \"edges\",\n        \"add edge\": \"edges,positions\",\n        \"delete edge\": \"edges\",\n        \"add edge note\": \"edges\",\n        \"edit edge note\": \"edges\",\n        \"delete edge note\": \"edges\",\n        \"draw zone\": \"rects\",\n        \"delete zone\": \"rects\",\n        \"resize zone\": \"rects\",\n        \"edit zone\": \"rects\",\n        \"add zone note\": \"rects\",\n        \"delete zone note\": \"rects\",\n        \"change zone line style\": \"rects\",\n        \"add text\": \"texts\",\n        \"edit text\": \"texts\",\n        \"delete text\": \"texts\",\n       };\n       const scope = actionScopes[action] || \"all\";\n       const state = captureState(scope);\n\t   undoStack.push(state);\n        if (undoStack.length > MAX_UNDO_STACK) {\n        undoStack.shift();\n       }\n       redoStack = [];\n       updateUndoButtons();\n       if (action) {\n        const actionTypeMap = {\n  \"create node\": \"node\",\n  \"delete node\": \"node\",\n  \"add node\": \"node\",\n  \"edit\": \"node\",\n  \"clone node\": \"node\",\n  \"paste node\": \"node\",\n  \"move nodes\": \"node\",\n  \"nudge\": \"node\",\n  \"align nodes\": \"node\",\n  \"distribute nodes\": \"node\",\n  \"snap to grid\": \"node\",\n  \"toggle group\": \"node\",\n  \"toggle lock\": \"node\",\n  \"create rack\": \"rack\",\n  \"add rack\": \"rack\",\n  \"edit rack\": \"rack\",\n  \"edit mac\": \"rack\",\n  \"edit U height\": \"rack\",\n  \"change rack capacity\": \"rack\",\n  \"change assigned rack\": \"rack\",\n  \"add connection\": \"connection\",\n  \"delete connection\": \"connection\",\n  \"delete edge\": \"connection\",\n  \"clone edge\": \"connection\",\n  \"paste edge\": \"connection\",\n  \"style change\": \"style\",\n  \"change layer\": \"layer\",\n  \"add text\": \"text\",\n  \"edit text\": \"text\",\n  \"delete text\": \"text\",\n  \"clone text\": \"text\",\n  \"paste text\": \"text\",\n  \"draw zone\": \"zone\",\n  \"delete zone\": \"zone\",\n  \"delete rect\": \"zone\",\n  \"clone rect\": \"zone\",\n  \"paste rect\": \"zone\",\n  \"change zone line style\": \"zone\",\n  \"delete selected\": \"bulk\",\n  \"clone selected\": \"bulk\",\n};\n        const type = actionTypeMap[action] || \"node\";\n        logAuditEvent(type, action);\n       }\n      }\n      function undo() {\n       if (undoStack.length === 0) return;\n       const currentState = captureState();\n       redoStack.push(currentState);\n       const previousState = undoStack.pop();\n       restoreState(previousState);\n       updateUndoButtons();\n       logAuditEvent(\"undo\", \"Undo action performed\");\n      }\n      function redo() {\n       if (redoStack.length === 0) return;\n       logAuditEvent(\"redo\", \"Redo action performed\");\n       const currentState = captureState();\n       undoStack.push(currentState);\n       const nextState = redoStack.pop();\n       restoreState(nextState);\n       updateUndoButtons();\n      }\n      function restoreState(state) {\n      if (state.nodes) NODE_DATA = state.nodes;\n      if (state.edges) EDGE_DATA = state.edges;\n      if (state.positions) savedPositions = state.positions;\n      if (state.sizes) savedSizes = state.sizes;\n      if (state.styles) savedStyles = state.styles;\n      if (state.legend) EDGE_LEGEND = state.legend;\n      if (state.rects) RECT_DATA = state.rects;\n      if (state.texts) TEXT_DATA = state.texts;\n      forgeTheTopology();\n      if (currentNodeId && NODE_DATA[currentNodeId]) {\n       claimTheImmortal(currentNodeId);\n      } else if (currentEdgeId) {\n       selectTheConnection(currentEdgeId);\n       }\n      }\n      function updateUndoButtons() {\n       const undoBtn = document.getElementById(\"undo-btn\");\n       const redoBtn = document.getElementById(\"redo-btn\");\n       if (undoBtn) {\n        undoBtn.disabled = undoStack.length === 0;\n        undoBtn.style.opacity = undoStack.length === 0 ? \"0.5\" : \"1\";\n       }\n       if (redoBtn) {\n        redoBtn.disabled = redoStack.length === 0;\n        redoBtn.style.opacity = redoStack.length === 0 ? \"0.5\" : \"1\";\n       }\n      }\n      function editNodeMac(id) {\n       const currentMac = NODE_DATA[id].mac || \"\";\n       showEditModal(\"Edit MAC Address\", currentMac, (newMac) => {\n        pushUndo(\"edit mac\");\n        NODE_DATA[id].mac = newMac;\n        if (currentNodeId === id) {\n         document.getElementById(\"node-mac\").textContent = newMac || \"--\";\n        }\n       });\n      }\n      function editNodeRack(id) {\n       const node = NODE_DATA[id];\n       if (!node) return;\n       document.getElementById(\"modal-title\").textContent = \"Edit Rack Unit\";\n       document.getElementById(\"modal-input\").value = node.rackUnit || \"\";\n       document.getElementById(\"edit-modal\").style.display = \"\";\n       document.getElementById(\"edit-modal\").classList.add(\"active\");\n       document.getElementById(\"modal-input\").focus();\n\t   const modal = document.getElementById(\"edit-modal\");\n       const cleanup = () => {\n        modal.classList.remove(\"active\");\n        document.getElementById(\"modal-save\").removeEventListener(\"click\", saveHandler);\n        document.getElementById(\"modal-cancel\").removeEventListener(\"click\", cancelHandler);\n        modal.removeEventListener(\"click\", bgHandler);\n        document.removeEventListener(\"keydown\", escHandler);\n       };\n       const escHandler = (e) => {\n        if (e.key === \"Escape\") cleanup();\n       };\n       document.addEventListener(\"keydown\", escHandler);\n       const saveHandler = () => {\n        pushUndo(\"edit rack\");\n        const value = document.getElementById(\"modal-input\").value.trim();\n        node.rackUnit = value;\n        cleanup();\n        claimTheImmortal(id);\n       };\n       const cancelHandler = () => {\n        cleanup();\n       };\n       const bgHandler = (e) => {\n        if (e.target === modal) cancelHandler();\n       };\n       document.getElementById(\"modal-save\").addEventListener(\"click\", saveHandler);\n       document.getElementById(\"modal-cancel\").addEventListener(\"click\", cancelHandler);\n       modal.addEventListener(\"click\", bgHandler);\n      }\n      function editNodeUHeight(id) {\n       const node = NODE_DATA[id];\n       if (!node) return;\n       document.getElementById(\"modal-title\").textContent = \"Edit U Height\";\n       document.getElementById(\"modal-input\").value = node.uHeight || \"1\";\n       document.getElementById(\"edit-modal\").style.display = \"\";\n       document.getElementById(\"edit-modal\").classList.add(\"active\");\n       document.getElementById(\"modal-input\").focus();\n\t   const modal = document.getElementById(\"edit-modal\");\n       const cleanup = () => {\n        modal.classList.remove(\"active\");\n        document.getElementById(\"modal-save\").removeEventListener(\"click\", saveHandler);\n        document.getElementById(\"modal-cancel\").removeEventListener(\"click\", cancelHandler);\n        modal.removeEventListener(\"click\", bgHandler);\n        document.removeEventListener(\"keydown\", escHandler);\n       };\n       const escHandler = (e) => {\n        if (e.key === \"Escape\") cleanup();\n       };\n       document.addEventListener(\"keydown\", escHandler);\n       const saveHandler = () => {\n        pushUndo(\"edit U height\");\n        const value = document.getElementById(\"modal-input\").value.trim();\n        node.uHeight = value;\n        cleanup();\n        claimTheImmortal(id);\n        forgeTheTopology();\n       };\n       const cancelHandler = () => {\n        cleanup();\n       };\n       const bgHandler = (e) => {\n        if (e.target === modal) cancelHandler();\n       };\n       document.getElementById(\"modal-save\").addEventListener(\"click\", saveHandler);\n       document.getElementById(\"modal-cancel\").addEventListener(\"click\", cancelHandler);\n       modal.addEventListener(\"click\", bgHandler);\n      }\n      function updateEdgePortLabels(edgeId) {\n       const edge = EDGE_DATA.list.find(e => e.id === edgeId);\n       if (!edge || edge.type === \"custom\") return;\n       const fromPortInput = document.getElementById(\"edge-from-port\");\n       const toPortInput = document.getElementById(\"edge-to-port\");\n       if (fromPortInput && toPortInput) {\n        edge.fromPort = fromPortInput.value || \"\";\n        edge.toPort = toPortInput.value || \"\";\n        forgeTheTopology();\n       }\n      }\n      function clearSelection() {\n       selectedNodes.clear();\n       selectedEdges.clear();\n       selectedRects.clear();\n       selectedTexts.clear();\n       updateAllSelections();\n      }\n      function updateAllSelections() {\n      updateNodeSelection();\n      document.querySelectorAll(\".edge\").forEach(el => {\n      const edgeId = el.dataset.edgeId;\n      if (selectedEdges.has(edgeId)) {\n      el.style.filter = \"drop-shadow(0 0 8px #4fd1c5)\";\n      } else {\n      el.style.filter = \"\";\n      }\n      });\n      document.querySelectorAll(\".rect-group\").forEach(el => {\n      const rectId = el.dataset.rectId;\n      if (selectedRects.has(rectId)) {\n      el.style.filter = \"drop-shadow(0 0 8px #4fd1c5)\";\n      } else {\n      el.style.filter = \"\";\n      }\n      });\n      document.querySelectorAll(\".text-group\").forEach(el => {\n      const textId = el.dataset.textId;\n      if (selectedTexts.has(textId)) {\n      el.style.filter = \"drop-shadow(0 0 8px #4fd1c5)\";\n      } else {\n      el.style.filter = \"\";\n      }\n      });\n      const total = selectedNodes.size + selectedEdges.size + selectedRects.size + selectedTexts.size;\n      const bulkToolbar = document.getElementById(\"bulk-toolbar\");\n      const bulkToolbarMobile = document.getElementById(\"bulk-toolbar-mobile\");\n      const bulkCount = document.getElementById(\"bulk-count\");\n      const bulkCountMobile = document.getElementById(\"bulk-count-mobile\");\n      if (total > 0) {\n      if (bulkToolbar) bulkToolbar.style.display = \"flex\";\n      if (bulkToolbarMobile) bulkToolbarMobile.style.display = \"flex\";\n      if (bulkCount) bulkCount.textContent = total;\n      if (bulkCountMobile) bulkCountMobile.textContent = total;\n      } else {\n      if (bulkToolbar) bulkToolbar.style.display = \"none\";\n      if (bulkToolbarMobile) bulkToolbarMobile.style.display = \"none\";\n      }\n      }\n      function updateNodeSelection() {\n       if (isViewOnly()) {\n        selectedNodes.clear();\n        selectedRects.clear();\n        selectedTexts.clear();\n        const bulkToolbar = document.getElementById(\"bulk-toolbar\");\n        const bulkToolbarMobile = document.getElementById(\"bulk-toolbar-mobile\");\n        if (bulkToolbar) bulkToolbar.style.display = \"none\";\n        if (bulkToolbarMobile) bulkToolbarMobile.style.display = \"none\";\n        return;\n       }\n       document.querySelectorAll(\".node-group\").forEach(node => {\n        const nodeId = node.dataset.nodeId;\n        if (selectedNodes.has(nodeId)) {\n         node.classList.add(\"selected\");\n        } else {\n         node.classList.remove(\"selected\");\n        }\n       });\n       const bulkToolbar = document.getElementById(\"bulk-toolbar\");\n       const bulkToolbarMobile = document.getElementById(\"bulk-toolbar-mobile\");\n       const bulkCount = document.getElementById(\"bulk-count\");\n       const bulkCountMobile = document.getElementById(\"bulk-count-mobile\");\n       const bulkCountModal = document.getElementById(\"bulk-count-modal\");\n       if (selectedNodes.size > 0) {\n        if (bulkToolbar) bulkToolbar.style.display = \"flex\";\n        if (bulkToolbarMobile) bulkToolbarMobile.style.display = \"flex\";\n        if (bulkCount) bulkCount.textContent = selectedNodes.size;\n        if (bulkCountMobile) bulkCountMobile.textContent = selectedNodes.size;\n        if (bulkCountModal) bulkCountModal.textContent = selectedNodes.size;\n       } else {\n        if (bulkToolbar) bulkToolbar.style.display = \"none\";\n        if (bulkToolbarMobile) bulkToolbarMobile.style.display = \"none\";\n        const modal = document.getElementById(\"bulk-actions-modal\");\n        if (modal) modal.style.display = \"none\";\n       }\n      }\n      function deleteSelected() {\n       const total = selectedNodes.size + selectedEdges.size + selectedRects.size + selectedTexts.size;\n       if (total === 0) return;\n       let nodesInsideRacks = [];\n       selectedNodes.forEach(nodeId => {\n        if (NODE_DATA[nodeId]?.isRack) {\n         Object.entries(NODE_DATA).forEach(([id, n]) => {\n          if (n.assignedRack === nodeId) nodesInsideRacks.push(n.name || id);\n         });\n        }\n       });\n       let message = `Delete ${total} selected item(s)?`;\n       if (nodesInsideRacks.length > 0) {\n        message += `\\n\\nThis will also delete ${nodesInsideRacks.length} node(s) inside rack(s):\\n• ${nodesInsideRacks.join('\\n• ')}`;\n       }\n       challengeTheImmortal(message, () => {\n        pushUndo(\"delete selected\");\n        selectedNodes.forEach(nodeId => {\n         if (NODE_DATA[nodeId]?.isRack) {\n          Object.keys(NODE_DATA).forEach(id => {\n           if (NODE_DATA[id]?.assignedRack === nodeId) {\n            EDGE_DATA.list = EDGE_DATA.list.filter(e => e.from !== id && e.to !== id);\n            delete NODE_DATA[id];\n            delete savedPositions[id];\n            delete savedSizes[id];\n            delete savedStyles[id];\n           }\n          });\n         }\n         delete NODE_DATA[nodeId];\n         delete savedPositions[nodeId];\n         delete savedSizes[nodeId];\n         delete savedStyles[nodeId];\n         EDGE_DATA.list = EDGE_DATA.list.filter(e => e.from !== nodeId && e.to !== nodeId);\n        });\n        selectedEdges.forEach(edgeId => {\n         EDGE_DATA.list = EDGE_DATA.list.filter(e => e.id !== edgeId);\n        });\n        selectedRects.forEach(rectId => {\n         RECT_DATA.list = RECT_DATA.list.filter(r => r.id !== rectId);\n        });\n        selectedTexts.forEach(textId => {\n         TEXT_DATA.list = TEXT_DATA.list.filter(t => t.id !== textId);\n        });\n        clearSelection();\n        forgeTheTopology();\n       });\n      }\n      function startSelection(event) {\n       if (event.button !== 0) return;\n       if (event.target.closest(\".node-group\")) return;\n       isSelecting = true;\n       const svg = document.getElementById(\"map\");\n       const pt = svg.createSVGPoint();\n       pt.x = event.clientX;\n       pt.y = event.clientY;\n       const svgP = pt.matrixTransform(svg.getScreenCTM().inverse());\n       selectionStart = { x: svgP.x, y: svgP.y };\n       clearSelection();\n       preDragSelectedNodes = new Set(selectedNodes);\n       preDragSelectedEdges = new Set(selectedEdges);\n       preDragSelectedRects = new Set(selectedRects);\n       preDragSelectedTexts = new Set(selectedTexts);\n       if (!selectionRect || !selectionRect.parentNode) {\n        selectionRect = document.createElementNS(\"http://www.w3.org/2000/svg\", \"rect\");\n        selectionRect.setAttribute(\"fill\", selectionBoxStyle.fillColor);\n        selectionRect.setAttribute(\"fill-opacity\", selectionBoxStyle.fillOpacity);\n        selectionRect.setAttribute(\"stroke\", selectionBoxStyle.strokeColor);\n        selectionRect.setAttribute(\"stroke-width\", selectionBoxStyle.strokeWidth);\n        selectionRect.setAttribute(\"stroke-dasharray\", selectionBoxStyle.strokeDasharray);\n        svg.appendChild(selectionRect);\n       }\n       selectionRect.style.display = \"block\";\n      }\n      function updateSelection(event) {\n       if (!isSelecting || !selectionStart) return;\n       const svg = document.getElementById(\"map\");\n       const pt = svg.createSVGPoint();\n       pt.x = event.clientX;\n       pt.y = event.clientY;\n       const svgP = pt.matrixTransform(svg.getScreenCTM().inverse());\n       const x = Math.min(selectionStart.x, svgP.x);\n       const y = Math.min(selectionStart.y, svgP.y);\n       const width = Math.abs(svgP.x - selectionStart.x);\n       const height = Math.abs(svgP.y - selectionStart.y);\n       selectionRect.setAttribute(\"x\", x);\n       selectionRect.setAttribute(\"y\", y);\n       selectionRect.setAttribute(\"width\", width);\n       selectionRect.setAttribute(\"height\", height);\n       const box = { x, y, width, height };\n       Object.entries(savedPositions).forEach(([nodeId, pos]) => {\n        const size = savedSizes[nodeId] || 50;\n        const halfSize = size / 2;\n        const nodeBox = { x: pos.x - halfSize, y: pos.y - halfSize, width: size, height: size };\n        if (boxesIntersect(box, nodeBox)) {\n         selectedNodes.add(nodeId);\n        } else if (!preDragSelectedNodes.has(nodeId)) {\n         selectedNodes.delete(nodeId);\n        }\n       });\n      EDGE_DATA.list.forEach(edge => {\n        if (!edge.points || edge.points.length === 0) return;\n        for (let i = 0; i < edge.points.length - 1; i++) {\n         const p1 = edge.points[i];\n         const p2 = edge.points[i + 1];\n         if (lineIntersectsBox(p1.x, p1.y, p2.x, p2.y, box)) {\n          selectedEdges.add(edge.id);\n          return;\n         }\n        }\n        if (!preDragSelectedEdges.has(edge.id)) {\n         selectedEdges.delete(edge.id);\n        }\n       });\n       RECT_DATA.list.forEach(rect => {\n        const rectBox = { x: rect.x, y: rect.y, width: rect.width, height: rect.height };\n        if (boxesIntersect(box, rectBox)) {\n         selectedRects.add(rect.id);\n        } else if (!preDragSelectedRects.has(rect.id)) {\n         selectedRects.delete(rect.id);\n        }\n       });\n       TEXT_DATA.list.forEach(text => {\n        const fontSize = text.fontSize || 18;\n        const textBox = { x: text.x - 50, y: text.y - fontSize, width: 100, height: fontSize * 1.5 };\n        if (boxesIntersect(box, textBox)) {\n         selectedTexts.add(text.id);\n        } else if (!preDragSelectedTexts.has(text.id)) {\n         selectedTexts.delete(text.id);\n        }\n       });\n       updateAllSelectionVisuals();\n      }\n      function endSelection() {\n       isSelecting = false;\n       if (selectionRect) {\n        selectionRect.style.display = \"none\";\n       }\n      }\n      function boxesIntersect(a, b) {\n       return !(a.x + a.width < b.x || b.x + b.width < a.x || a.y + a.height < b.y || b.y + b.height < a.y);\n      }\n      function lineIntersectsBox(x1, y1, x2, y2, box) {\n       if (pointInBox(x1, y1, box) || pointInBox(x2, y2, box)) return true;\n       const lines = [\n        [box.x, box.y, box.x + box.width, box.y],\n        [box.x + box.width, box.y, box.x + box.width, box.y + box.height],\n        [box.x + box.width, box.y + box.height, box.x, box.y + box.height],\n        [box.x, box.y + box.height, box.x, box.y]\n       ];\n       for (const [bx1, by1, bx2, by2] of lines) {\n        if (linesIntersect(x1, y1, x2, y2, bx1, by1, bx2, by2)) return true;\n       }\n       return false;\n      }\n      function pointInBox(x, y, box) {\n       return x >= box.x && x <= box.x + box.width && y >= box.y && y <= box.y + box.height;\n      }\n      function linesIntersect(x1, y1, x2, y2, x3, y3, x4, y4) {\n       const denom = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1);\n       if (Math.abs(denom) < 0.0001) return false;\n       const ua = ((x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)) / denom;\n       const ub = ((x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)) / denom;\n       return ua >= 0 && ua <= 1 && ub >= 0 && ub <= 1;\n      }\n      function updateAllSelectionVisuals() {\n       document.querySelectorAll(\".node-group\").forEach(node => {\n        const nodeId = node.dataset.nodeId;\n        if (selectedNodes.has(nodeId)) {\n         node.classList.add(\"selected\");\n        } else {\n         node.classList.remove(\"selected\");\n        }\n       });\n       document.querySelectorAll(\".edge\").forEach(edge => {\n        const edgeId = edge.dataset.edgeId;\n        if (selectedEdges.has(edgeId)) {\n         edge.style.filter = \"drop-shadow(0 0 6px #4fd1c5)\";\n        } else {\n         edge.style.filter = \"\";\n        }\n       });\n       document.querySelectorAll(\".rect-group rect\").forEach(el => {\n        const rectId = el.closest(\".rect-group\")?.dataset?.rectId;\n        if (selectedRects.has(rectId)) {\n         el.style.filter = \"drop-shadow(0 0 8px #4fd1c5)\";\n        } else {\n         el.style.filter = \"\";\n        }\n       });\n       document.querySelectorAll(\".text-group, .text-element\").forEach(el => {\n        const textId = el.dataset?.textId;\n        if (selectedTexts.has(textId)) {\n         el.style.filter = \"drop-shadow(0 0 8px #4fd1c5)\";\n        } else {\n         el.style.filter = \"\";\n        }\n       });\n       const total = selectedNodes.size + selectedEdges.size + selectedRects.size + selectedTexts.size;\n       const bulkToolbar = document.getElementById(\"bulk-toolbar\");\n       const bulkToolbarMobile = document.getElementById(\"bulk-toolbar-mobile\");\n       const bulkCount = document.getElementById(\"bulk-count\");\n       const bulkCountMobile = document.getElementById(\"bulk-count-mobile\");\n       if (total > 0) {\n        if (bulkToolbar) bulkToolbar.style.display = \"flex\";\n        if (bulkToolbarMobile) bulkToolbarMobile.style.display = \"flex\";\n        if (bulkCount) bulkCount.textContent = total;\n        if (bulkCountMobile) bulkCountMobile.textContent = total;\n       } else {\n        if (bulkToolbar) bulkToolbar.style.display = \"none\";\n        if (bulkToolbarMobile) bulkToolbarMobile.style.display = \"none\";\n       }\n      }\n      function cloneNode(sourceId, skipUndo) {\n       if (!NODE_DATA[sourceId]) return;\n       if (!skipUndo) pushUndo(\"clone node\");\n       const source = NODE_DATA[sourceId];\n       const baseName = source.name + \" copy\";\n       let newName = baseName;\n       let counter = 1;\n       while (Object.values(NODE_DATA).some(n => n.name === newName)) {\n        newName = `${baseName} ${counter}`;\n        counter++;\n       }\n       const baseId = newName.toLowerCase().replace(/[^a-z0-9]+/g, \"-\");\n       let newId = baseId;\n       counter = 1;\n       while (NODE_DATA[newId]) {\n        newId = `${baseId}-${counter}`;\n        counter++;\n       }\n\t\tNODE_DATA[newId] = {\n\t\t shape: source.shape,\n\t\t name: newName,\n\t\t ip: source.ip,\n\t\t role: source.role,\n\t\t tags: [...source.tags],\n\t\t notes: [...source.notes],\n\t\t mac: source.mac || \"\",\n\t\t rackUnit: source.rackUnit || \"\",\n\t\t uHeight: source.uHeight || \"1\",\n\t\t layer: source.layer || \"physical\",\n\t\t assignedRack: source.assignedRack || \"\",\n\t\t rackCapacity: source.rackCapacity || \"42\",\n\t\t isRack: source.isRack || false,\nfovEnabled: source.fovEnabled || false,\n fovAngle: source.fovAngle || 90,\n fovDistance: source.fovDistance || 150,\n fovInnerRadius: source.fovInnerRadius || 0,\n fovRotation: source.fovRotation || 0,\n fovColor: source.fovColor || \"#f59e0b\",\n fovOpacity: source.fovOpacity || 20,\n fovGradient: source.fovGradient || false,\n fovBorderColor: source.fovBorderColor || \"#f59e0b\",\n fovBorderWidth: source.fovBorderWidth ?? 2,\n fovBorderStyle: source.fovBorderStyle || \"solid\",\n fovBorderOpacity: source.fovBorderOpacity ?? 100,\n fovLabel: source.fovLabel || \"\",\n fovLabelPosition: source.fovLabelPosition || \"center\",\n fovLabelSize: source.fovLabelSize || 14,\n fovLabelColor: source.fovLabelColor || \"#ffffff\",\n fovLabelBold: source.fovLabelBold || false,\n fovLabelBg: source.fovLabelBg || false,\n fovLabelBgColor: source.fovLabelBgColor || \"#000000\",\n fovLabelOffsetX: source.fovLabelOffsetX || 0,\n fovLabelOffsetY: source.fovLabelOffsetY || 0,\n fovAnimate: source.fovAnimate || false,\n fovAnimationType: source.fovAnimationType || \"sweep\",\n fovSweep: source.fovSweep || 120,\n fovSpeed: source.fovSpeed || 4\n\t\t};\n       if (source.isRack) {\n        const childNodes = Object.entries(NODE_DATA).filter(([id, n]) =>\n         id !== newId && n.assignedRack === sourceId\n        );\n        childNodes.forEach(([childId, childNode]) => {\n         const childBaseName = childNode.name + \" copy\";\n         let childNewName = childBaseName;\n         let c = 1;\n         while (Object.values(NODE_DATA).some(n => n.name === childNewName)) {\n          childNewName = `${childBaseName} ${c}`;\n          c++;\n         }\n         const childBaseId = childNewName.toLowerCase().replace(/[^a-z0-9]+/g, \"-\");\n         let childNewId = childBaseId;\n         c = 1;\n         while (NODE_DATA[childNewId]) {\n          childNewId = `${childBaseId}-${c}`;\n          c++;\n         }\n         NODE_DATA[childNewId] = {\n          ...JSON.parse(JSON.stringify(childNode)),\n          name: childNewName,\n          assignedRack: newId\n         };\n         if (savedPositions[childId]) {\n          savedPositions[childNewId] = { ...savedPositions[childId] };\n         }\n         if (savedSizes[childId]) {\n          savedSizes[childNewId] = savedSizes[childId];\n         }\n         if (savedStyles[childId]) {\n          savedStyles[childNewId] = JSON.parse(JSON.stringify(savedStyles[childId]));\n         }\n        });\n       }\n       if (currentView.mode === \"rack\" && currentView.rackId) {\n        NODE_DATA[newId].assignedRack = currentView.rackId;\n       }\n       const sourcePos = savedPositions[sourceId];\n       savedPositions[newId] = {\n        x: sourcePos.x + 100,\n        y: sourcePos.y + 100\n       };\n       if (savedSizes[sourceId]) {\n        savedSizes[newId] = savedSizes[sourceId];\n       }\n       if (savedStyles[sourceId]) {\n        savedStyles[newId] = JSON.parse(JSON.stringify(savedStyles[sourceId]));\n       }\n       forgeTheTopology();\n       claimTheImmortal(newId);\n      }\n      function alignSelectedNodes(direction) {\n       if (selectedNodes.size < 2) return;\n       pushUndo(\"align nodes\");\n       const nodes = Array.from(selectedNodes).map(id => ({\n        id,\n        pos: savedPositions[id]\n       }));\n       switch(direction) {\n        case \"left\":\n         const minX = Math.min(...nodes.map(n => n.pos.x));\n         nodes.forEach(n => savedPositions[n.id].x = minX);\n         break;\n        case \"right\":\n         const maxX = Math.max(...nodes.map(n => n.pos.x));\n         nodes.forEach(n => savedPositions[n.id].x = maxX);\n         break;\n        case \"top\":\n         const minY = Math.min(...nodes.map(n => n.pos.y));\n         nodes.forEach(n => savedPositions[n.id].y = minY);\n         break;\n        case \"bottom\":\n         const maxY = Math.max(...nodes.map(n => n.pos.y));\n         nodes.forEach(n => savedPositions[n.id].y = maxY);\n         break;\n        case \"center-h\":\n         const avgX = nodes.reduce((sum, n) => sum + n.pos.x, 0) / nodes.length;\n         nodes.forEach(n => savedPositions[n.id].x = avgX);\n         break;\n        case \"center-v\":\n         const avgY = nodes.reduce((sum, n) => sum + n.pos.y, 0) / nodes.length;\n         nodes.forEach(n => savedPositions[n.id].y = avgY);\n         break;\n       }\n       forgeTheTopology();\n      }\n      function distributeSelectedNodes(direction) {\n       if (selectedNodes.size < 3) return;\n       pushUndo(\"distribute nodes\");\n       const nodes = Array.from(selectedNodes).map(id => ({\n        id,\n        pos: savedPositions[id]\n       }));\n       if (direction === \"horizontal\") {\n        nodes.sort((a, b) => a.pos.x - b.pos.x);\n        const minX = nodes[0].pos.x;\n        const maxX = nodes[nodes.length - 1].pos.x;\n        const gap = (maxX - minX) / (nodes.length - 1);\n        nodes.forEach((n, i) => {\n         savedPositions[n.id].x = minX + (gap * i);\n        });\n       } else {\n        nodes.sort((a, b) => a.pos.y - b.pos.y);\n        const minY = nodes[0].pos.y;\n        const maxY = nodes[nodes.length - 1].pos.y;\n        const gap = (maxY - minY) / (nodes.length - 1);\n        nodes.forEach((n, i) => {\n         savedPositions[n.id].y = minY + (gap * i);\n        });\n       }\n       forgeTheTopology();\n      }\n      function snapToGrid(nodeId, gridSize = 50) {\n       if (!savedPositions[nodeId]) return;\n       pushUndo(\"snap to grid\");\n       const pos = savedPositions[nodeId];\n       pos.x = Math.round(pos.x / gridSize) * gridSize;\n       pos.y = Math.round(pos.y / gridSize) * gridSize;\n       forgeTheTopology();\n      }\n\t\tfunction searchNodes(query) {\n\t\t   if (!query) {\n\t\t\tcurrentSearchQuery = \"\";\n\t\t\tcurrentSearchResults = [];\n\t\t\tclearSearchHighlight();\n\t\t\treturn [];\n\t\t   }\n\t\t   currentSearchQuery = query;\n\t\t   query = query.toLowerCase();\n\t\t   const results = [];\n\t\t   Object.entries(NODE_DATA).forEach(([id, data]) => {\n\t\t\ttry {\n\t\t\t const nameMatch = data.name && data.name.toLowerCase().includes(query);\n\t\t\t const ipMatch = data.ip && data.ip.toLowerCase().includes(query);\n\t\t\t const roleMatch = data.role && data.role.toLowerCase().includes(query);\n\t\t\t const tagsMatch = data.tags && Array.isArray(data.tags) && data.tags.some(tag => tag && tag.toLowerCase().includes(query));\n\t\t\t const macMatch = data.mac && data.mac.toLowerCase().includes(query);\n\t\t\t const rackUnitMatch = data.rackUnit && String(data.rackUnit).toLowerCase().includes(query);\n\t\t\t if (nameMatch || ipMatch || roleMatch || tagsMatch || macMatch || rackUnitMatch) {\n\t\t\t  results.push(id);\n\t\t\t }\n\t\t\t} catch (e) {\n\t\t\t console.warn(\"Search error for node:\", id, e);\n\t\t\t}\n\t\t   });\n\t\t   currentSearchResults = results;\n\t\t   highlightSearchResults(results, true);\n\t\t   if (results.length > 0) {\n\t\t    focusOnNodes(results);\n\t\t   }\n\t\t   return results;\n\t\t}\n\t\tfunction highlightSearchResults(nodeIds, hasQuery = false) {\n\t\t   document.querySelectorAll(\".node-group\").forEach(node => {\n\t\t\tconst nodeId = node.dataset.nodeId;\n\t\t\tif (nodeIds.includes(nodeId)) {\n\t\t\t node.classList.add(\"search-highlight\");\n\t\t\t node.classList.remove(\"search-faded\");\n\t\t\t} else {\n\t\t\t node.classList.remove(\"search-highlight\");\n\t\t\t if (hasQuery) {\n\t\t\t  node.classList.add(\"search-faded\");\n\t\t\t } else {\n\t\t\t  node.classList.remove(\"search-faded\");\n\t\t\t }\n\t\t\t}\n\t\t   });\n\t\t   document.querySelectorAll(\".edge-group\").forEach(edge => {\n\t\t\tconst fromId = edge.dataset.from;\n\t\t\tconst toId = edge.dataset.to;\n\t\t\tif (hasQuery && !nodeIds.includes(fromId) && !nodeIds.includes(toId)) {\n\t\t\t edge.classList.add(\"search-faded\");\n\t\t\t} else {\n\t\t\t edge.classList.remove(\"search-faded\");\n\t\t\t}\n\t\t   });\n\t\t}\n\t\tfunction clearSearchHighlight() {\n   document.querySelectorAll(\".search-highlight\").forEach(node => {\n    node.classList.remove(\"search-highlight\");\n   });\n   document.querySelectorAll(\".search-faded\").forEach(el => {\n    el.classList.remove(\"search-faded\");\n   });\n}\n      function nudgeSelectedNodes(direction, distance) {\n        const nodesToNudge = selectedNodes.size > 0\n          ? Array.from(selectedNodes)\n          : (currentNodeId ? [currentNodeId] : []);\n        const rectsToNudge = selectedRects.size > 0\n          ? Array.from(selectedRects)\n          : (currentRectId ? [currentRectId] : []);\n        const textsToNudge = selectedTexts.size > 0\n          ? Array.from(selectedTexts)\n          : (currentTextId ? [currentTextId] : []);\n        if (nodesToNudge.length === 0 && rectsToNudge.length === 0 && textsToNudge.length === 0) return;\n        const unlockedNodes = nodesToNudge.filter(id => !NODE_DATA[id]?.locked);\n        const unlockedRects = rectsToNudge.filter(id => { const r = RECT_DATA.list.find(x => x.id === id); return r && !r.locked; });\n        const unlockedTexts = textsToNudge.filter(id => { const t = TEXT_DATA.list.find(x => x.id === id); return t && !t.locked; });\n        if (unlockedNodes.length === 0 && unlockedRects.length === 0 && unlockedTexts.length === 0) return;\n        pushUndo(\"nudge\");\n        const dx = direction === \"ArrowLeft\" ? -distance : direction === \"ArrowRight\" ? distance : 0;\n        const dy = direction === \"ArrowUp\" ? -distance : direction === \"ArrowDown\" ? distance : 0;\n        unlockedNodes.forEach(id => {\n          if (!savedPositions[id]) savedPositions[id] = { x: 0, y: 0 };\n          savedPositions[id].x += dx;\n          savedPositions[id].y += dy;\n        });\n        unlockedRects.forEach(id => {\n          const r = RECT_DATA.list.find(x => x.id === id);\n          if (r) { r.x += dx; r.y += dy; }\n        });\n        unlockedTexts.forEach(id => {\n          const t = TEXT_DATA.list.find(x => x.id === id);\n          if (t) { t.x += dx; t.y += dy; }\n        });\n        forgeTheTopology();\n      }\n      function cycleNodes(reverse = false) {\n        const nodeIds = Object.keys(NODE_DATA).filter(id => {\n          if (currentView.mode === \"rack\" && currentView.rackId) {\n            return NODE_DATA[id].assignedRack === currentView.rackId;\n          }\n          return !NODE_DATA[id].assignedRack;\n        });\n        if (nodeIds.length === 0) return;\n        let currentIndex = nodeIds.indexOf(currentNodeId);\n        if (reverse) {\n          currentIndex = currentIndex <= 0 ? nodeIds.length - 1 : currentIndex - 1;\n        } else {\n          currentIndex = currentIndex >= nodeIds.length - 1 ? 0 : currentIndex + 1;\n        }\n        const nextNodeId = nodeIds[currentIndex];\n        claimTheImmortal(nextNodeId);\n        selectedNodes.clear();\n        updateNodeSelection();\n      }\n      function focusOnSelected() {\n        let minX = Infinity, minY = Infinity;\n        let maxX = -Infinity, maxY = -Infinity;\n        let hasItems = false;\n        const nodesToFocus = selectedNodes.size > 0\n          ? Array.from(selectedNodes)\n          : (currentNodeId ? [currentNodeId] : []);\n        nodesToFocus.forEach(id => {\n          const pos = savedPositions[id];\n          if (pos) {\n            hasItems = true;\n            const size = savedSizes[id] || 50;\n            minX = Math.min(minX, pos.x - size/2);\n            minY = Math.min(minY, pos.y - size/2);\n            maxX = Math.max(maxX, pos.x + size/2);\n            maxY = Math.max(maxY, pos.y + size/2);\n          }\n        });\n        const rectsToFocus = selectedRects.size > 0 ? Array.from(selectedRects) : (currentRectId ? [currentRectId] : []);\n        rectsToFocus.forEach(id => {\n          const r = RECT_DATA.list.find(x => x.id === id);\n          if (r) {\n            hasItems = true;\n            minX = Math.min(minX, r.x);\n            minY = Math.min(minY, r.y);\n            maxX = Math.max(maxX, r.x + r.width);\n            maxY = Math.max(maxY, r.y + r.height);\n          }\n        });\n        const textsToFocus = selectedTexts.size > 0 ? Array.from(selectedTexts) : (currentTextId ? [currentTextId] : []);\n        textsToFocus.forEach(id => {\n          const t = TEXT_DATA.list.find(x => x.id === id);\n          if (t) {\n            hasItems = true;\n            minX = Math.min(minX, t.x - 50);\n            minY = Math.min(minY, t.y - 20);\n            maxX = Math.max(maxX, t.x + 50);\n            maxY = Math.max(maxY, t.y + 20);\n          }\n        });\n        const edgesToFocus = selectedEdges.size > 0 ? Array.from(selectedEdges) : (currentEdgeId ? [currentEdgeId] : []);\n        edgesToFocus.forEach(id => {\n          const e = EDGE_DATA.list.find(x => x.id === id);\n          if (e) {\n            const fromPos = savedPositions[e.from];\n            const toPos = savedPositions[e.to];\n            if (fromPos && toPos) {\n              hasItems = true;\n              minX = Math.min(minX, fromPos.x, toPos.x);\n              minY = Math.min(minY, fromPos.y, toPos.y);\n              maxX = Math.max(maxX, fromPos.x, toPos.x);\n              maxY = Math.max(maxY, fromPos.y, toPos.y);\n            }\n          }\n        });\n        if (!hasItems || !isFinite(minX)) return;\n        const padding = 100;\n        const centerX = (minX + maxX) / 2;\n        const centerY = (minY + maxY) / 2;\n        const width = maxX - minX + padding * 2;\n        const height = maxY - minY + padding * 2;\n        const zoomX = CANVAS_WIDTH / width;\n        const zoomY = CANVAS_HEIGHT / height;\n        const targetZoom = Math.min(zoomX, zoomY, 2);\n        canvasState.zoom = targetZoom;\n        canvasState.panX = centerX - (CANVAS_WIDTH / targetZoom) / 2;\n        canvasState.panY = centerY - (CANVAS_HEIGHT / targetZoom) / 2;\n\t\tupdateViewBox();\n      }\n      function toggleLockSelected() {\n        const nodesToToggle = selectedNodes.size > 0\n          ? Array.from(selectedNodes)\n          : (currentNodeId ? [currentNodeId] : []);\n        const rectsToToggle = selectedRects.size > 0\n          ? Array.from(selectedRects)\n          : (currentRectId ? [currentRectId] : []);\n        const textsToToggle = selectedTexts.size > 0\n          ? Array.from(selectedTexts)\n          : (currentTextId ? [currentTextId] : []);\n        if (nodesToToggle.length === 0 && rectsToToggle.length === 0 && textsToToggle.length === 0) return;\n        pushUndo(\"toggle lock\");\n        let hasUnlocked = nodesToToggle.some(id => !NODE_DATA[id]?.locked);\n        hasUnlocked = hasUnlocked || rectsToToggle.some(id => { const r = RECT_DATA.list.find(x => x.id === id); return r && !r.locked; });\n        hasUnlocked = hasUnlocked || textsToToggle.some(id => { const t = TEXT_DATA.list.find(x => x.id === id); return t && !t.locked; });\n        nodesToToggle.forEach(id => {\n          if (NODE_DATA[id]) NODE_DATA[id].locked = hasUnlocked;\n        });\n        rectsToToggle.forEach(id => {\n          const r = RECT_DATA.list.find(x => x.id === id);\n          if (r) r.locked = hasUnlocked;\n        });\n        textsToToggle.forEach(id => {\n          const t = TEXT_DATA.list.find(x => x.id === id);\n          if (t) t.locked = hasUnlocked;\n        });\n        forgeTheTopology();\n      }\n      function toggleGroupSelected() {\n      const nodesToGroup = Array.from(selectedNodes);\n      const rectsToGroup = Array.from(selectedRects);\n      const textsToGroup = Array.from(selectedTexts);\n      const edgesToGroup = Array.from(selectedEdges);\n      const totalItems = nodesToGroup.length + rectsToGroup.length + textsToGroup.length + edgesToGroup.length;\n      if (totalItems < 2) return;\n      pushUndo(\"toggle group\");\n      const allGroupIds = [];\n      nodesToGroup.forEach(id => { if (NODE_DATA[id]?.groupId) allGroupIds.push(NODE_DATA[id].groupId); });\n      rectsToGroup.forEach(id => { const r = RECT_DATA.list.find(x => x.id === id); if (r?.groupId) allGroupIds.push(r.groupId); });\n      textsToGroup.forEach(id => { const t = TEXT_DATA.list.find(x => x.id === id); if (t?.groupId) allGroupIds.push(t.groupId); });\n      edgesToGroup.forEach(id => { const e = EDGE_DATA.list.find(x => x.id === id); if (e?.groupId) allGroupIds.push(e.groupId); });\n      const uniqueGroups = [...new Set(allGroupIds)];\n      if (uniqueGroups.length === 1 && allGroupIds.length === totalItems) {\n      nodesToGroup.forEach(id => { if (NODE_DATA[id]) NODE_DATA[id].groupId = null; });\n      rectsToGroup.forEach(id => { const r = RECT_DATA.list.find(x => x.id === id); if (r) r.groupId = null; });\n      textsToGroup.forEach(id => { const t = TEXT_DATA.list.find(x => x.id === id); if (t) t.groupId = null; });\n      edgesToGroup.forEach(id => { const e = EDGE_DATA.list.find(x => x.id === id); if (e) e.groupId = null; });\n      } else {\n      const newGroupId = \"group-\" + Date.now();\n      nodesToGroup.forEach(id => { if (NODE_DATA[id]) NODE_DATA[id].groupId = newGroupId; });\n      rectsToGroup.forEach(id => { const r = RECT_DATA.list.find(x => x.id === id); if (r) r.groupId = newGroupId; });\n      textsToGroup.forEach(id => { const t = TEXT_DATA.list.find(x => x.id === id); if (t) t.groupId = newGroupId; });\n      edgesToGroup.forEach(id => { const e = EDGE_DATA.list.find(x => x.id === id); if (e) e.groupId = newGroupId; });\n      }\n      forgeTheTopology();\n      }\n      function handleKeyDown(event) {\n       if (event.metaKey && !event.ctrlKey) event.ctrlKey = true;\n       if (event.target.tagName === \"INPUT\" || event.target.tagName === \"TEXTAREA\" || event.target.isContentEditable) {\n        return;\n       }\n       if ([\"ArrowUp\", \"ArrowDown\", \"ArrowLeft\", \"ArrowRight\"].includes(event.key)) {\n        event.preventDefault();\n        const distance = event.shiftKey ? 10 : 1;\n        nudgeSelectedNodes(event.key, distance);\n       }\n       if (event.key === \"Tab\") {\n        event.preventDefault();\n        cycleNodes(event.shiftKey);\n       }\n       if (event.key === \"f\" || event.key === \"F\") {\n        event.preventDefault();\n        focusOnSelected();\n       }\n       if (event.key === \"l\" || event.key === \"L\") {\n        event.preventDefault();\n        toggleLockSelected();\n       }\n       if (event.key === \"g\" || event.key === \"G\") {\n        event.preventDefault();\n        toggleGroupSelected();\n       }\n       if (event.ctrlKey && event.key === \"z\") {\n        event.preventDefault();\n        undo();\n       }\n       if ((event.ctrlKey && event.key === \"y\") || (event.ctrlKey && event.shiftKey && event.key === \"z\")) {\n        event.preventDefault();\n        redo();\n       }\n if (event.ctrlKey && event.key === \"c\") {\n        event.preventDefault();\n        clipboard = null;\n        if (currentNodeId && NODE_DATA[currentNodeId]) {\n         clipboard = {\n          type: \"node\",\n          data: JSON.parse(JSON.stringify(NODE_DATA[currentNodeId])),\n          size: savedSizes[currentNodeId],\n          style: savedStyles[currentNodeId] ? JSON.parse(JSON.stringify(savedStyles[currentNodeId])) : null\n         };\n        } else if (currentEdgeId) {\n         const edge = EDGE_DATA.list.find(e => e.id === currentEdgeId);\n         if (edge) clipboard = { type: \"edge\", data: JSON.parse(JSON.stringify(edge)) };\n        } else if (currentRectId) {\n         const rect = RECT_DATA.list.find(r => r.id === currentRectId);\n         if (rect) clipboard = { type: \"rect\", data: JSON.parse(JSON.stringify(rect)) };\n        } else if (currentTextId) {\n         const text = TEXT_DATA.list.find(t => t.id === currentTextId);\n         if (text) clipboard = { type: \"text\", data: JSON.parse(JSON.stringify(text)) };\n        }\n       }\n       if (event.ctrlKey && event.key === \"v\") {\n        event.preventDefault();\n        if (!clipboard) return;\n        const viewWidth = CANVAS_WIDTH / canvasState.zoom;\n        const viewHeight = CANVAS_HEIGHT / canvasState.zoom;\n        const centerX = canvasState.panX + (viewWidth / 2);\n        const centerY = canvasState.panY + (viewHeight / 2);\n        if (clipboard.type === \"node\") {\n         const data = clipboard.data;\n         let newName = data.name + \" copy\";\n         let counter = 1;\n         while (Object.values(NODE_DATA).some(n => n.name === newName)) {\n          newName = data.name + \" copy \" + counter;\n          counter++;\n         }\n         let newId = newName.toLowerCase().replace(/[^a-z0-9]+/g, \"-\");\n         counter = 1;\n         while (NODE_DATA[newId]) {\n          newId = newName.toLowerCase().replace(/[^a-z0-9]+/g, \"-\") + \"-\" + counter;\n          counter++;\n         }\n         pushUndo(\"paste node\");\n         NODE_DATA[newId] = { ...data, name: newName };\n         savedPositions[newId] = { x: centerX, y: centerY };\n         if (clipboard.size) savedSizes[newId] = clipboard.size;\n         if (clipboard.style) savedStyles[newId] = JSON.parse(JSON.stringify(clipboard.style));\n         forgeTheTopology();\n         claimTheImmortal(newId);\n        } else if (clipboard.type === \"edge\") {\n         pushUndo(\"paste edge\");\n         const newEdge = { ...clipboard.data, id: \"edge-\" + Date.now() };\n         EDGE_DATA.list.push(newEdge);\n         forgeTheTopology();\n         selectTheConnection(newEdge.id);\n        } else if (clipboard.type === \"rect\") {\n         pushUndo(\"paste rect\");\n         const newRect = { ...clipboard.data, id: \"rect-\" + Date.now(), x: centerX - (clipboard.data.width || 100) / 2, y: centerY - (clipboard.data.height || 100) / 2 };\n         RECT_DATA.list.push(newRect);\n         forgeTheTopology();\n         selectTheRect(newRect.id);\n        } else if (clipboard.type === \"text\") {\n         pushUndo(\"paste text\");\n         const newText = { ...clipboard.data, id: \"text-\" + Date.now(), x: centerX, y: centerY };\n         TEXT_DATA.list.push(newText);\n         forgeTheTopology();\n         showTextPanel(newText.id);\n        }\n       }\n       if (event.key === \"Delete\") {\n        event.preventDefault();\n        const totalSelected = selectedNodes.size + selectedEdges.size + selectedRects.size + selectedTexts.size;\n        if (totalSelected > 0) {\n         deleteSelected();\n        } else if (currentNodeId && NODE_DATA[currentNodeId]) {\n         challengeTheImmortal(`Delete node \"${NODE_DATA[currentNodeId].name}\"?`, () => {\n          pushUndo(\"delete node\");\n          delete NODE_DATA[currentNodeId];\n          delete savedPositions[currentNodeId];\n          delete savedSizes[currentNodeId];\n          delete savedStyles[currentNodeId];\n          EDGE_DATA.list = EDGE_DATA.list.filter(e => e.from !== currentNodeId && e.to !== currentNodeId);\n          currentNodeId = null;\n          forgeTheTopology();\n          document.getElementById(\"node-panel\").style.display = \"none\";\n         });\n        } else if (currentEdgeId) {\n         challengeTheImmortal(\"Delete this line?\", () => {\n          pushUndo(\"delete edge\");\n          EDGE_DATA.list = EDGE_DATA.list.filter(e => e.id !== currentEdgeId);\n          currentEdgeId = null;\n          forgeTheTopology();\n          document.getElementById(\"edge-panel\").style.display = \"none\";\n         });\n        } else if (currentRectId) {\n         challengeTheImmortal(\"Delete this zone?\", () => {\n          pushUndo(\"delete rect\");\n          RECT_DATA.list = RECT_DATA.list.filter(r => r.id !== currentRectId);\n          currentRectId = null;\n          forgeTheTopology();\n          document.getElementById(\"rect-panel\").style.display = \"none\";\n         });\n        } else if (currentTextId) {\n         const textItem = TEXT_DATA.list.find(t => t.id === currentTextId);\n         if (textItem) {\n          challengeTheImmortal(`Delete text \"${(textItem.content || '').substring(0, 30)}...\"?`, () => {\n           pushUndo(\"delete text\");\n           TEXT_DATA.list = TEXT_DATA.list.filter(t => t.id !== currentTextId);\n           currentTextId = null;\n           forgeTheTopology();\n           document.getElementById(\"text-panel\").style.display = \"none\";\n          });\n         }\n        }\n       }\n       if (event.ctrlKey && event.key === \"a\") {\n        event.preventDefault();\n        Object.keys(NODE_DATA).forEach(id => selectedNodes.add(id));\n        EDGE_DATA.list.forEach(e => selectedEdges.add(e.id));\n        RECT_DATA.list.forEach(r => selectedRects.add(r.id));\n        TEXT_DATA.list.forEach(t => selectedTexts.add(t.id));\n        updateAllSelections();\n       }\n       if (event.key === \"Escape\") {\n        clearSelection();\n        clearSearchHighlight();\n       }\n       if (event.ctrlKey && event.key === \"d\") {\n        event.preventDefault();\n        if (currentNodeId && NODE_DATA[currentNodeId]) {\n         cloneNode(currentNodeId);\n        } else if (currentEdgeId) {\n         const edge = EDGE_DATA.list.find(e => e.id === currentEdgeId);\n         if (edge) {\n          pushUndo(\"clone edge\");\n          const newEdge = { ...JSON.parse(JSON.stringify(edge)), id: \"edge-\" + Date.now() };\n          EDGE_DATA.list.push(newEdge);\n          forgeTheTopology();\n          selectTheConnection(newEdge.id);\n         }\n        } else if (currentRectId) {\n         const rect = RECT_DATA.list.find(r => r.id === currentRectId);\n         if (rect) {\n          pushUndo(\"clone rect\");\n          const newRect = { ...JSON.parse(JSON.stringify(rect)), id: \"rect-\" + Date.now(), x: rect.x + 50, y: rect.y + 50 };\n          RECT_DATA.list.push(newRect);\n          forgeTheTopology();\n          selectTheRect(newRect.id);\n         }\n        } else if (currentTextId) {\n         const text = TEXT_DATA.list.find(t => t.id === currentTextId);\n         if (text) {\n          pushUndo(\"clone text\");\n          const newText = { ...JSON.parse(JSON.stringify(text)), id: \"text-\" + Date.now(), x: text.x + 50, y: text.y + 50 };\n          TEXT_DATA.list.push(newText);\n          forgeTheTopology();\n          showTextPanel(newText.id);\n         }\n        }\n       }\n      }\n      function saveRollbackVersion(description = \"Auto-save\") {\n        const version = {\n          timestamp: Date.now(),\n          description,\n          data: captureTheQuickening()\n        };\n        try {\n          const stored = localStorage.getItem(ROLLBACK_STORAGE_KEY);\n          rollbackVersions = stored ? JSON.parse(stored) : [];\n        } catch (e) {\n          rollbackVersions = [];\n        }\n        rollbackVersions.unshift(version);\n        if (rollbackVersions.length > MAX_ROLLBACK_VERSIONS) {\n          rollbackVersions = rollbackVersions.slice(0, MAX_ROLLBACK_VERSIONS);\n        }\n        try {\n          localStorage.setItem(ROLLBACK_STORAGE_KEY, JSON.stringify(rollbackVersions));\n        } catch (e) {\n          console.warn(\"Failed to save rollback version:\", e);\n        }\n      }\n      function loadRollbackVersions() {\n        try {\n          const stored = localStorage.getItem(ROLLBACK_STORAGE_KEY);\n          rollbackVersions = stored ? JSON.parse(stored) : [];\n          displayRollbackVersions();\n        } catch (e) {\n          console.warn(\"Failed to load rollback versions:\", e);\n          rollbackVersions = [];\n        }\n      }\n      function displayRollbackVersions() {\n        const listEl = document.getElementById(\"rollback-list\");\n        if (!listEl) return;\n        if (rollbackVersions.length === 0) {\n          listEl.innerHTML = '<div style=\"text-align: center; padding: 40px; color: var(--text-soft);\">No version history yet. Versions are saved automatically when you save the file.</div>';\n          return;\n        }\n        listEl.innerHTML = rollbackVersions.map((version, index) => {\n          const date = new Date(version.timestamp);\n          const timeStr = date.toLocaleString();\n          const nodeCount = Object.keys(version.data.nodeData || {}).length;\n          const edgeCount = (version.data.edgeData?.list || []).length;\n          return `\n            <div class=\"version-item\" onclick=\"restoreRollbackVersion(${index})\">\n              <div class=\"version-info\">\n                <div class=\"timestamp\">${escapeHtml(timeStr)}</div>\n                <div class=\"details\">${escapeHtml(version.description)} • ${nodeCount} nodes • ${edgeCount} connections</div>\n              </div>\n              <div class=\"version-actions\">\n                <button class=\"btn-cancel\" onclick=\"event.stopPropagation(); deleteRollbackVersion(${index})\" title=\"Delete this version\">🗑️</button>\n              </div>\n            </div>\n          `;\n        }).join('');\n      }\n      function restoreRollbackVersion(index) {\n        if (!confirm(`Restore version from ${new Date(rollbackVersions[index].timestamp).toLocaleString()}?\\n\\nYour current work will be lost unless you save first.`)) {\n          return;\n        }\n        const version = rollbackVersions[index];\n        const data = version.data;\n        NODE_DATA = data.nodeData || {};\n        EDGE_DATA = data.edgeData || { list: [] };\n        RECT_DATA = data.rectData || { list: [] };\n        TEXT_DATA = data.textData || { list: [] };\n        EDGE_LEGEND = data.edgeLegend || {};\n        savedPositions = data.nodePositions || {};\n        savedSizes = data.nodeSizes || {};\n        savedStyles = data.nodeStyles || {};\n        PAGE_STATE = data.page || PAGE_STATE;\n        if (data.canvas) {\n          canvasState.zoom = data.canvas.zoom;\n          canvasState.panX = data.canvas.panX;\n          canvasState.panY = data.canvas.panY;\n        }\n        wieldThePower();\n        forgeTheTopology();\n        document.getElementById(\"rollback-modal\").classList.remove(\"active\");\n        logAuditEvent(\"rollback\", `Restored version from ${new Date(version.timestamp).toLocaleString()}`);\n      }\n      function deleteRollbackVersion(index) {\n        if (!confirm(\"Delete this version from history?\")) return;\n        rollbackVersions.splice(index, 1);\n        try {\n          localStorage.setItem(ROLLBACK_STORAGE_KEY, JSON.stringify(rollbackVersions));\n        } catch (e) {\n          console.warn(\"Failed to delete version:\", e);\n        }\n        displayRollbackVersions();\n      }\n      function clearRollbackHistory() {\n        if (!confirm(\"Clear all version history?\\n\\nThis cannot be undone.\")) return;\n        rollbackVersions = [];\n        localStorage.removeItem(ROLLBACK_STORAGE_KEY);\n        displayRollbackVersions();\n      }\n      function createManualSnapshot() {\n        const description = prompt(\"Enter a description for this snapshot:\", \"Manual snapshot\");\n        if (!description) return;\n        saveRollbackVersion(description);\n        displayRollbackVersions();\n      }\n      function switchTab(index) {\n        if (index === currentTabIndex) return;\n        const currentTab = documentTabs[currentTabIndex];\n        currentTab.nodes = JSON.parse(JSON.stringify(NODE_DATA));\n        currentTab.edges = JSON.parse(JSON.stringify(EDGE_DATA));\n        currentTab.positions = JSON.parse(JSON.stringify(savedPositions));\n        currentTab.sizes = JSON.parse(JSON.stringify(savedSizes));\n        currentTab.styles = JSON.parse(JSON.stringify(savedStyles));\n        currentTab.legend = JSON.parse(JSON.stringify(EDGE_LEGEND));\n        currentTab.rects = JSON.parse(JSON.stringify(RECT_DATA));\n        currentTab.texts = JSON.parse(JSON.stringify(TEXT_DATA));\n        currentTab.pageState = JSON.parse(JSON.stringify(PAGE_STATE));\n        currentTabIndex = index;\n        const newTab = documentTabs[index];\n        NODE_DATA = JSON.parse(JSON.stringify(newTab.nodes));\n        EDGE_DATA = JSON.parse(JSON.stringify(newTab.edges));\n        savedPositions = JSON.parse(JSON.stringify(newTab.positions));\n        savedSizes = JSON.parse(JSON.stringify(newTab.sizes));\n        savedStyles = JSON.parse(JSON.stringify(newTab.styles));\n        EDGE_LEGEND = JSON.parse(JSON.stringify(newTab.legend));\n        RECT_DATA = JSON.parse(JSON.stringify(newTab.rects));\n        TEXT_DATA = JSON.parse(JSON.stringify(newTab.texts));\n        PAGE_STATE = Object.assign({}, DEFAULT_PAGE_STATE, newTab.pageState || {});\n        wieldThePower();\n        document.title = PAGE_STATE.title || newTab.name;\n        document.getElementById(\"page-title\").textContent = PAGE_STATE.title || newTab.name;\n        forgeTheTopology();\n        currentNodeId = null;\n        currentEdgeId = null;\n        currentTextId = null;\n        document.getElementById(\"node-panel\").style.display = \"none\";\n        document.getElementById(\"edge-panel\").style.display = \"none\";\n        document.getElementById(\"text-panel\").style.display = \"none\";\n        displayTabs();\n        logAuditEvent(\"tab\", `Switched to tab: ${newTab.name}`);\n      }\n      function createNewTab() {\n        const nameInput = document.getElementById(\"new-tab-name\");\n        const name = nameInput.value.trim();\n        if (!name) {\n          alert(\"Please enter a tab name\");\n          return;\n        }\n        const newTab = {\n          id: `tab-${Date.now()}`,\n          name,\n          nodes: {},\n          edges: { list: [] },\n          positions: {},\n          sizes: {},\n          styles: {},\n          legend: {},\n          rects: { list: [] },\n          texts: { list: [] },\n      pageState: null\n        };\n        documentTabs.push(newTab);\n        nameInput.value = \"\";\n        displayTabs();\n        logAuditEvent(\"tab\", `Created new tab: ${name}`);\n      }\n      function renameTab(index) {\n        const tab = documentTabs[index];\n        const newName = prompt(\"Enter new name:\", tab.name);\n        if (!newName || newName === tab.name) return;\n        tab.name = newName;\n        displayTabs();\n        logAuditEvent(\"tab\", `Renamed tab to: ${newName}`);\n      }\n      function deleteTab(index) {\n        if (documentTabs.length === 1) {\n          alert(\"Cannot delete the last tab\");\n          return;\n        }\n        if (!confirm(`Delete tab \"${documentTabs[index].name}\"?`)) return;\n        const wasCurrentTab = (index === currentTabIndex);\n        documentTabs.splice(index, 1);\n        if (currentTabIndex >= documentTabs.length) {\n          currentTabIndex = documentTabs.length - 1;\n        } else if (index < currentTabIndex) {\n          currentTabIndex--;\n        }\n        if (wasCurrentTab) {\n          const newTab = documentTabs[currentTabIndex];\n          NODE_DATA = JSON.parse(JSON.stringify(newTab.nodes));\n          EDGE_DATA = JSON.parse(JSON.stringify(newTab.edges));\n          savedPositions = JSON.parse(JSON.stringify(newTab.positions));\n          savedSizes = JSON.parse(JSON.stringify(newTab.sizes));\n          savedStyles = JSON.parse(JSON.stringify(newTab.styles));\n          EDGE_LEGEND = JSON.parse(JSON.stringify(newTab.legend));\n          RECT_DATA = JSON.parse(JSON.stringify(newTab.rects));\n          TEXT_DATA = JSON.parse(JSON.stringify(newTab.texts));\n          PAGE_STATE = Object.assign({}, DEFAULT_PAGE_STATE, newTab.pageState || {});\n          wieldThePower();\n          forgeTheTopology();\n          currentNodeId = null;\n          currentEdgeId = null;\n          currentTextId = null;\n          document.getElementById(\"node-panel\").style.display = \"none\";\n          document.getElementById(\"edge-panel\").style.display = \"none\";\n          document.getElementById(\"text-panel\").style.display = \"none\";\n        }\n        displayTabs();\n        logAuditEvent(\"tab\", `Deleted tab`);\n      }\n      function saveCurrentTheme() {\n        const name = prompt(\"Enter a name for this theme:\", \"My Theme \" + (savedStyleSets.length + 1));\n        if (!name || !name.trim()) return;\n        const existingIndex = savedStyleSets.findIndex(s => s.name.toLowerCase() === name.trim().toLowerCase());\n        if (existingIndex !== -1) {\n          if (!confirm(\"A theme named \\\"\" + name + \"\\\" already exists. Replace it?\")) return;\n          savedStyleSets.splice(existingIndex, 1);\n        }\n        const styleSet = {\n          id: \"mytheme-\" + Date.now(),\n          name: name.trim(),\n          styles: JSON.parse(JSON.stringify(PAGE_STATE))\n        };\n        delete styleSet.styles.title;\n        delete styleSet.styles.viewOnly;\n        savedStyleSets.push(styleSet);\n        rebuildThemeDropdown();\n        document.getElementById(\"theme-preset\").value = styleSet.id;\n        updateDeleteButton();\n        logAuditEvent(\"style\", \"Saved theme: \" + name);\n      }\n      function deleteCurrentTheme() {\n        const select = document.getElementById(\"theme-preset\");\n        const val = select.value;\n        if (!val.startsWith(\"mytheme-\")) return;\n        const index = savedStyleSets.findIndex(s => s.id === val);\n        if (index === -1) return;\n        if (!confirm(\"Delete theme \\\"\" + savedStyleSets[index].name + \"\\\"?\")) return;\n        savedStyleSets.splice(index, 1);\n        rebuildThemeDropdown();\n        select.value = \"defaulted\";\n        updateDeleteButton();\n        logAuditEvent(\"style\", \"Deleted theme\");\n      }\n      function rebuildThemeDropdown() {\n        const group = document.getElementById(\"my-themes-group\");\n        if (!group) return;\n        const select = document.getElementById(\"theme-preset\");\n        const currentValue = select ? select.value : \"\";\n        group.innerHTML = \"\";\n        savedStyleSets.forEach(function(s) {\n          const opt = document.createElement(\"option\");\n          opt.value = s.id;\n          opt.textContent = s.name;\n          group.appendChild(opt);\n        });\n        if (select && currentValue) {\n          select.value = currentValue;\n        }\n      }\n      function updateDeleteButton() {\n        const select = document.getElementById(\"theme-preset\");\n        const btn = document.getElementById(\"delete-theme-btn\");\n        if (!btn) return;\n\t\tbtn.style.display = \"block\";\n\t\tbtn.disabled = !select.value.startsWith(\"mytheme-\");\n      }\n      function displayTabs() {\n        const listEl = document.getElementById(\"tabs-list\");\n        if (!listEl) return;\n        listEl.innerHTML = documentTabs.map((tab, index) => {\n          const nodeCount = Object.keys(tab.nodes).length;\n          const edgeCount = tab.edges.list.length;\n          const isActive = index === currentTabIndex;\n          return `\n            <div class=\"tab-item ${isActive ? 'active' : ''}\" onclick=\"switchTab(${index})\">\n              <div class=\"tab-name\">${escapeHtml(tab.name)}</div>\n              <div class=\"tab-stats\">${nodeCount} nodes • ${edgeCount} connections</div>\n              <div class=\"tab-actions\">\n                <button class=\"btn-cancel\" onclick=\"event.stopPropagation(); renameTab(${index})\" title=\"Rename tab\">✏️</button>\n                ${documentTabs.length > 1 ? '<button class=\"btn-cancel\" onclick=\"event.stopPropagation(); deleteTab(' + index + ')\" title=\"Delete tab\">🗑️</button>' : ''}\n              </div>\n            </div>\n          `;\n        }).join('');\n      }\n      function logAuditEvent(type, description, details = {}) {\n        const event = {\n          timestamp: Date.now(),\n          type,\n          description,\n          details,\n          tab: documentTabs[currentTabIndex]?.name || \"Main\"\n        };\n        auditLog.unshift(event);\n        if (auditLog.length > MAX_AUDIT_ENTRIES) {\n          auditLog = auditLog.slice(0, MAX_AUDIT_ENTRIES);\n        }\n        try {\n          localStorage.setItem(AUDIT_STORAGE_KEY, JSON.stringify(auditLog));\n        } catch (e) {\n          console.warn(\"Failed to save audit log:\", e);\n        }\n      }\n      function loadAuditLog() {\n        let embeddedLog = [];\n        let localLog = [];\n        try {\n          const stateScript = document.getElementById(\"topology-state\");\n          if (stateScript) {\n            const stateData = JSON.parse(stateScript.textContent);\n            if (stateData.auditLog && Array.isArray(stateData.auditLog)) {\n              embeddedLog = stateData.auditLog;\n            }\n          }\n        } catch (e) {\n          console.warn(\"Failed to load embedded audit log:\", e);\n        }\n        try {\n          const stored = localStorage.getItem(AUDIT_STORAGE_KEY);\n          if (stored) {\n            localLog = JSON.parse(stored);\n          }\n        } catch (e) {\n          console.warn(\"Failed to load localStorage audit log:\", e);\n        }\n        const merged = [...embeddedLog, ...localLog];\n        const seen = new Set();\n        auditLog = merged.filter(entry => {\n          const key = entry.timestamp + entry.description;\n          if (seen.has(key)) return false;\n          seen.add(key);\n          return true;\n        }).sort((a, b) => b.timestamp - a.timestamp);\n        if (auditLog.length > MAX_AUDIT_ENTRIES) {\n          auditLog = auditLog.slice(0, MAX_AUDIT_ENTRIES);\n        }\n        try {\n          localStorage.setItem(AUDIT_STORAGE_KEY, JSON.stringify(auditLog));\n        } catch (e) {\n          console.warn(\"Failed to sync audit log:\", e);\n        }\n      }\n      function displayAuditLog(filter = \"all\") {\n        const listEl = document.getElementById(\"audit-log-list\");\n        if (!listEl) return;\n        const filtered = filter === \"all\" ? auditLog : auditLog.filter(e => e.type === filter);\n        if (filtered.length === 0) {\n          listEl.innerHTML = '<div style=\"text-align: center; padding: 40px; color: var(--text-soft);\">No audit entries yet</div>';\n          return;\n        }\n        listEl.innerHTML = filtered.map(event => {\n          const date = new Date(event.timestamp);\n          const timeStr = date.toLocaleString();\n          return `\n            <div class=\"audit-entry ${escapeHtml(event.type)}\">\n              <div class=\"time\">[${escapeHtml(timeStr)}] ${escapeHtml(event.tab)}</div>\n              <div class=\"action\">[${escapeHtml(event.type.toUpperCase())}] ${escapeHtml(event.description)}</div>\n            </div>\n          `;\n        }).join('');\n      }\n      function clearAuditLog() {\n        if (!confirm(\"Clear all audit log entries?\\n\\nThis cannot be undone.\")) return;\n        auditLog = [];\n        localStorage.removeItem(AUDIT_STORAGE_KEY);\n        displayAuditLog();\n      }\n      function exportAuditLog() {\n        if (auditLog.length === 0) {\n          alert(\"No audit entries to export\");\n          return;\n        }\n        const csv = [\n          [\"Timestamp\", \"Tab\", \"Type\", \"Description\"],\n          ...auditLog.map(e => [\n            new Date(e.timestamp).toISOString(),\n            e.tab,\n            e.type,\n            e.description\n          ])\n        ].map(row => row.map(cell => `\"${cell}\"`).join(\",\")).join(\"\\n\");\n        const blob = new Blob([csv], { type: \"text/csv\" });\n        const url = URL.createObjectURL(blob);\n        const a = document.createElement(\"a\");\n        a.href = url;\n        a.download = `audit-log-${Date.now()}.csv`;\n        document.body.appendChild(a);\n        a.click();\n        document.body.removeChild(a);\n        URL.revokeObjectURL(url);\n      }\n      let currentSecretName = null;\n      function createNewSecret() {\n        const nameInput = document.getElementById(\"new-secret-name\");\n        const name = nameInput.value.trim();\n        if (!name) {\n          alert(\"Please enter a note name\");\n          return;\n        }\n        if (encryptedSections[name]) {\n          alert(\"A note with this name already exists\");\n          return;\n        }\n        currentSecretName = name;\n        encryptedSections[name] = { encrypted: false, data: \"\" };\n        nameInput.value = \"\";\n        document.getElementById(\"secret-editor-title\").textContent = `New note: ${name}`;\n        document.getElementById(\"secret-editor-content\").value = \"\";\n        document.getElementById(\"secrets-modal\").classList.remove(\"active\");\n        document.getElementById(\"secret-editor-modal\").classList.add(\"active\");\n        displaySecrets();\n      }\n      function editSecret(name) {\n        currentSecretName = name;\n        const section = encryptedSections[name];\n        if (section.encrypted) {\n          const password = prompt(`Enter password to decrypt \"${name}\":`);\n          if (!password) return;\n          try {\n            decryptData(section.data, password).then(decrypted => {\n              document.getElementById(\"secret-editor-title\").textContent = `Edit note: ${name}`;\n              document.getElementById(\"secret-editor-content\").value = decrypted;\n              document.getElementById(\"secrets-modal\").classList.remove(\"active\");\n              document.getElementById(\"secret-editor-modal\").classList.add(\"active\");\n            }).catch(e => {\n              alert(\"Failed to decrypt. Wrong password?\");\n            });\n          } catch (e) {\n            alert(\"Failed to decrypt. Wrong password?\");\n          }\n        } else {\n          document.getElementById(\"secret-editor-title\").textContent = `Edit note: ${name}`;\n          document.getElementById(\"secret-editor-content\").value = section.data;\n          document.getElementById(\"secrets-modal\").classList.remove(\"active\");\n          document.getElementById(\"secret-editor-modal\").classList.add(\"active\");\n        }\n      }\n      async function saveSecret() {\n        if (!currentSecretName) return;\n        const content = document.getElementById(\"secret-editor-content\").value;\n        const autoEncrypt = document.getElementById(\"secret-auto-encrypt\").checked;\n        if (autoEncrypt) {\n          const password = prompt(`Enter password to encrypt \"${currentSecretName}\":`);\n          if (!password) return;\n          const confirmPassword = prompt(\"Confirm password:\");\n          if (password !== confirmPassword) {\n            alert(\"Passwords do not match\");\n            return;\n          }\n          try {\n            const encrypted = await encryptData(content, password);\n            encryptedSections[currentSecretName] = { encrypted: true, data: encrypted };\n          } catch (e) {\n            alert(\"Encryption failed: \" + e.message);\n            return;\n          }\n        } else {\n          encryptedSections[currentSecretName] = { encrypted: false, data: content };\n        }\n        closeSecretEditor();\n        displaySecrets();\n        logAuditEvent(\"secret\", `Saved note: ${currentSecretName}`);\n      }\n      function closeSecretEditor() {\n        document.getElementById(\"secret-editor-modal\").classList.remove(\"active\");\n        document.getElementById(\"secrets-modal\").classList.add(\"active\");\n        currentSecretName = null;\n      }\n      function deleteSecret(name) {\n        if (!confirm(`Delete note \"${name}\"?`)) return;\n        delete encryptedSections[name];\n        displaySecrets();\n        logAuditEvent(\"secret\", `Deleted note: ${name}`);\n      }\n      function displaySecrets() {\n        const listEl = document.getElementById(\"secrets-list\");\n        if (!listEl) return;\n        const secrets = Object.keys(encryptedSections);\n        if (secrets.length === 0) {\n          listEl.innerHTML = '<div style=\"text-align: center; padding: 40px; color: var(--text-soft);\">No notes yet</div>';\n          return;\n        }\n        listEl.innerHTML = secrets.map(name => {\n          const section = encryptedSections[name];\n          const status = section.encrypted ? \"🔒 Encrypted\" : \"🔓 Plaintext\";\n          return `\n            <div class=\"secret-item\">\n              <div class=\"secret-name\">${escapeHtml(name)}</div>\n              <div class=\"secret-status\">${status}</div>\n              <div class=\"tab-actions\">\n                <button class=\"btn-cancel\" onclick=\"editSecret('${escapeHtml(name)}')\" title=\"Edit note\">✏️</button>\n                <button class=\"btn-cancel\" onclick=\"deleteSecret('${escapeHtml(name)}')\" title=\"Delete note\">🗑️</button>\n              </div>\n            </div>\n          `;\n        }).join('');\n      }\n      let rafId = null;\n      let lastRender = 0;\n      const RENDER_THROTTLE = 16;\n      function setupDragToCreate() {\n        const addNodeBtn = document.getElementById(\"add-node-btn\");\n        const addRackBtn = document.getElementById(\"add-rack-btn\");\n        const canvas = document.getElementById(\"map\");\n        if (!addNodeBtn || !addRackBtn || !canvas) return;\n        let dragType = null;\n        [addNodeBtn, addRackBtn].forEach(btn => {\n          btn.setAttribute(\"draggable\", \"true\");\n          btn.addEventListener(\"dragstart\", e => {\n            dragType = btn.id === \"add-node-btn\" ? \"node\" : \"rack\";\n            e.dataTransfer.effectAllowed = \"copy\";\n            e.dataTransfer.setData(\"text/plain\", dragType);\n          });\n        });\n        canvas.addEventListener(\"dragover\", e => {\n          if (dragType) {\n            e.preventDefault();\n            e.dataTransfer.dropEffect = \"copy\";\n          }\n        });\n        canvas.addEventListener(\"drop\", e => {\n          if (!dragType) return;\n          e.preventDefault();\n          const rect = canvas.getBoundingClientRect();\n          const x = (e.clientX - rect.left) / canvasState.zoom + canvasState.panX;\n          const y = (e.clientY - rect.top) / canvasState.zoom + canvasState.panY;\n          if (dragType === \"node\") {\n            createNodeAtPosition(x, y);\n          } else if (dragType === \"rack\") {\n            createRackAtPosition(x, y);\n          }\n          dragType = null;\n        });\n      }\n      function createNodeAtPosition(x, y) {\n        const timestamp = Date.now();\n        const newId = `host-${timestamp}`;\n\t\tpushUndo(\"create node\");\n        NODE_DATA[newId] = {\n          name: \"New Node\",\n          ip: \"0.0.0.0\",\n          shape: \"server\",\n          role: \"\",\n          tags: [],\n          notes: [],\n          layer: \"physical\",\n          isRack: false\n        };\n        savedPositions[newId] = { x, y };\n        savedSizes[newId] = 55;\n        forgeTheTopology();\n        claimTheImmortal(newId);\n        logAuditEvent(\"node\", `Created node at (${Math.round(x)}, ${Math.round(y)})`);\n      }\n      function createRackAtPosition(x, y) {\n        const timestamp = Date.now();\n        const newId = `rack-${timestamp}`;\n\t\tpushUndo(\"create rack\");\n        NODE_DATA[newId] = {\n          name: \"New Rack\",\n          ip: \"\",\n          shape: \"server\",\n          role: \"rack\",\n          tags: [],\n          notes: [],\n          layer: \"physical\",\n          isRack: true,\n          rackCapacity: 42\n        };\n        savedPositions[newId] = { x, y };\n        savedSizes[newId] = 55;\n        populateRackDropdown();\n        forgeTheTopology();\n        claimTheImmortal(newId);\n        logAuditEvent(\"rack\", `Created rack at (${Math.round(x)}, ${Math.round(y)})`);\n      }\n      document.addEventListener(\"keydown\", handleKeyDown);\n      document.getElementById(\"save-file-btn\").addEventListener(\"click\", becomeImmortal);\n      const addNodeBtn = document.getElementById(\"add-node-btn\");\n      const addNodeModal = document.getElementById(\"add-node-modal\");\n      const addNodeCancel = document.getElementById(\"add-node-cancel\");\n      const addNodeSave = document.getElementById(\"add-node-save\");\n      const undoBtn = document.getElementById(\"undo-btn\");\n      const redoBtn = document.getElementById(\"redo-btn\");\n      if (undoBtn) undoBtn.addEventListener(\"click\", undo);\n      if (redoBtn) redoBtn.addEventListener(\"click\", redo);\n      const backToTopologyBtn = document.getElementById(\"back-to-topology-btn\");\n      if (backToTopologyBtn) {\n       backToTopologyBtn.addEventListener(\"click\", exitRack);\n      }\n      const canvasViewport = document.getElementById(\"canvas-viewport\");\n      if (canvasViewport) {\n       canvasViewport.addEventListener(\"dblclick\", (e) => {\n        if (currentView.mode === \"rack\" && e.target.id === \"map\") {\n         exitRack();\n        }\n       });\n      }\n      const layersBtn = document.getElementById(\"layers-btn\");\n      const layerModal = document.getElementById(\"layer-modal\");\n      const layerModalClose = document.getElementById(\"layer-modal-close\");\n      if (layersBtn && layerModal) {\n       layersBtn.addEventListener(\"click\", () => {\n        layerModal.classList.add(\"active\");\n       });\n      }\n      if (layerModalClose && layerModal) {\n       layerModalClose.addEventListener(\"click\", () => {\n        layerModal.classList.remove(\"active\");\n       });\n      }\n      if (layerModal) {\n       layerModal.addEventListener(\"click\", (e) => {\n        if (e.target === layerModal) {\n         layerModal.classList.remove(\"active\");\n        }\n       });\n      }\n      [\"physical\", \"logical\", \"security\", \"application\"].forEach(layer => {\n       const checkbox = document.getElementById(`layer-${layer}`);\n       if (checkbox) {\n        checkbox.addEventListener(\"change\", applyLayerFilter);\n       }\n      });\n      const tabsBtn = document.getElementById(\"tabs-btn\");\n      const tabsModal = document.getElementById(\"tabs-modal\");\n      const tabsModalClose = document.getElementById(\"tabs-modal-close\");\n      if (tabsBtn && tabsModal) {\n        tabsBtn.addEventListener(\"click\", () => {\n          displayTabs();\n          tabsModal.classList.add(\"active\");\n        });\n      }\n      if (tabsModalClose && tabsModal) {\n        tabsModalClose.addEventListener(\"click\", () => {\n          tabsModal.classList.remove(\"active\");\n        });\n      }\n      if (tabsModal) {\n        tabsModal.addEventListener(\"click\", (e) => {\n          if (e.target === tabsModal) {\n            tabsModal.classList.remove(\"active\");\n          }\n        });\n      }\n      const rollbackBtn = document.getElementById(\"rollback-btn\");\n      const rollbackModal = document.getElementById(\"rollback-modal\");\n      const rollbackModalClose = document.getElementById(\"rollback-modal-close\");\n      if (rollbackBtn && rollbackModal) {\n        rollbackBtn.addEventListener(\"click\", () => {\n          loadRollbackVersions();\n          rollbackModal.classList.add(\"active\");\n        });\n      }\n      if (rollbackModalClose && rollbackModal) {\n        rollbackModalClose.addEventListener(\"click\", () => {\n          rollbackModal.classList.remove(\"active\");\n        });\n      }\n      if (rollbackModal) {\n        rollbackModal.addEventListener(\"click\", (e) => {\n          if (e.target === rollbackModal) {\n            rollbackModal.classList.remove(\"active\");\n          }\n        });\n      }\n      const auditLogBtn = document.getElementById(\"audit-log-btn\");\n      const auditLogModal = document.getElementById(\"audit-log-modal\");\n      const auditLogModalClose = document.getElementById(\"audit-log-modal-close\");\n      const auditFilter = document.getElementById(\"audit-filter\");\n      if (auditLogBtn && auditLogModal) {\n        auditLogBtn.addEventListener(\"click\", () => {\n          loadAuditLog();\n          displayAuditLog();\n          auditLogModal.classList.add(\"active\");\n        });\n      }\n      if (auditFilter) {\n        auditFilter.addEventListener(\"change\", (e) => {\n          displayAuditLog(e.target.value);\n        });\n      }\n      if (auditLogModalClose && auditLogModal) {\n        auditLogModalClose.addEventListener(\"click\", () => {\n          auditLogModal.classList.remove(\"active\");\n        });\n      }\n\t  const portMapBtn = document.getElementById(\"port-map-btn\");\n      const portMapModal = document.getElementById(\"port-map-modal\");\n      const portMapClose = document.getElementById(\"port-map-modal-close\");\n      const portMapSearch = document.getElementById(\"port-map-search\");\n      const portMapFilter = document.getElementById(\"port-map-filter\");\n      function renderPortMap() {\n        const container = document.getElementById(\"port-map-table\");\n        const search = (document.getElementById(\"port-map-search\")?.value || \"\").toLowerCase();\n        const filter = document.getElementById(\"port-map-filter\")?.value || \"all\";\n        let edges = EDGE_DATA.list || [];\n        if (filter === \"with-ports\") edges = edges.filter(e => e.fromPort || e.toPort);\n        if (filter === \"without-ports\") edges = edges.filter(e => !e.fromPort && !e.toPort);\n        if (search) {\n          edges = edges.filter(e => {\n            const fromName = (NODE_DATA[e.from]?.name || e.from || \"\").toLowerCase();\n            const toName = (NODE_DATA[e.to]?.name || e.to || \"\").toLowerCase();\n            return fromName.includes(search) || toName.includes(search) || (e.fromPort || \"\").toLowerCase().includes(search) || (e.toPort || \"\").toLowerCase().includes(search) || (e.notes || []).join(\" \").toLowerCase().includes(search);\n          });\n        }\n        if (edges.length === 0) {\n          container.innerHTML = '<div style=\"text-align: center; padding: 40px; color: var(--text-soft);\">No connections found</div>';\n          return;\n        }\n        let html = '<table style=\"width: 100%; border-collapse: collapse; font-size: 13px;\">';\n        html += '<thead><tr style=\"background: var(--panel-alt); position: sticky; top: 0;\"><th style=\"padding: 10px; text-align: left; border-bottom: 1px solid var(--edge-main);\">From Device</th><th style=\"padding: 10px; text-align: left; border-bottom: 1px solid var(--edge-main);\">From Port</th><th style=\"padding: 10px; text-align: left; border-bottom: 1px solid var(--edge-main);\">To Device</th><th style=\"padding: 10px; text-align: left; border-bottom: 1px solid var(--edge-main);\">To Port</th><th style=\"padding: 10px; text-align: left; border-bottom: 1px solid var(--edge-main);\">Notes</th></tr></thead><tbody>';\n        edges.forEach(e => {\n          const fromName = NODE_DATA[e.from]?.name || e.from;\n          const toName = NODE_DATA[e.to]?.name || e.to;\n          const notes = (e.notes || []).join(\", \");\n          const edgeColor = e.color || EDGE_LEGEND[e.type]?.color || 'var(--edge-main)';\n          const fromPortDisplay = e.fromPort ? escapeHtml(e.fromPort) : \"-\";\n          const toPortDisplay = e.toPort ? escapeHtml(e.toPort) : \"-\";\n          const goToEdge = `document.getElementById('port-map-modal').classList.remove('active'); selectTheConnection('${escapeHtml(e.id)}'); focusOnSelected()`;\n          html += `<tr style=\"border-bottom: 1px solid var(--edge-main);\"><td style=\"padding: 8px;\"><span style=\"display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: ${edgeColor}; margin-right: 6px;\"></span><button onclick=\"document.getElementById('port-map-modal').classList.remove('active'); claimTheImmortal('${escapeHtml(e.from)}'); focusOnSelected()\" style=\"background: var(--panel-alt); color: var(--text-main); border: 1px solid var(--edge-main); border-radius: 4px; padding: 6px 10px; cursor: pointer; font-size: 13px; min-height: 36px;\">${escapeHtml(fromName)}</button></td><td style=\"padding: 8px;\"><button onclick=\"editPortFromMap('${escapeHtml(e.id)}', 'from')\" style=\"background: ${e.fromPort ? 'var(--accent)' : 'var(--panel)'}; color: ${e.fromPort ? 'var(--bg)' : 'var(--text-soft)'}; border: 1px solid var(--edge-main); border-radius: 4px; padding: 6px 10px; cursor: pointer; font-family: monospace; font-size: 13px; min-height: 36px;\">${fromPortDisplay}</button></td><td style=\"padding: 8px;\"><button onclick=\"document.getElementById('port-map-modal').classList.remove('active'); claimTheImmortal('${escapeHtml(e.to)}'); focusOnSelected()\" style=\"background: var(--panel-alt); color: var(--text-main); border: 1px solid var(--edge-main); border-radius: 4px; padding: 6px 10px; cursor: pointer; font-size: 13px; min-height: 36px;\">${escapeHtml(toName)}</button></td><td style=\"padding: 8px;\"><button onclick=\"editPortFromMap('${escapeHtml(e.id)}', 'to')\" style=\"background: ${e.toPort ? 'var(--accent)' : 'var(--panel)'}; color: ${e.toPort ? 'var(--bg)' : 'var(--text-soft)'}; border: 1px solid var(--edge-main); border-radius: 4px; padding: 6px 10px; cursor: pointer; font-family: monospace; font-size: 13px; min-height: 36px;\">${toPortDisplay}</button></td><td style=\"padding: 8px;\"><button onclick=\"${goToEdge}\" style=\"background: var(--panel); color: var(--text-main); border: 1px solid var(--edge-main); border-radius: 4px; padding: 6px 10px; cursor: pointer; font-size: 13px; min-height: 36px;\" title=\"Go to connection\">Go</button></td></tr>`;\n        });\n        html += '</tbody></table>';\n        container.innerHTML = html;\n      }\n      window.editPortFromMap = function(edgeId, which) {\n        const edge = EDGE_DATA.list.find(e => e.id === edgeId);\n        if (!edge) return;\n        const fromName = NODE_DATA[edge.from]?.name || edge.from;\n        const toName = NODE_DATA[edge.to]?.name || edge.to;\n        const nodeId = which === 'from' ? edge.from : edge.to;\n        const nodeName = which === 'from' ? fromName : toName;\n        const label = `Port on ${nodeName}:`;\n        const currentVal = which === 'from' ? (edge.fromPort || '') : (edge.toPort || '');\n        const newVal = prompt(label, currentVal);\n        if (newVal !== null && newVal !== '') {\n          const isDuplicate = (EDGE_DATA.list || []).some(e => {\n            if (e.id === edgeId) return false;\n            if (e.from === nodeId && e.fromPort === newVal) return true;\n            if (e.to === nodeId && e.toPort === newVal) return true;\n            return false;\n          });\n          if (isDuplicate && !confirm(`Warning: Port \"${newVal}\" is already used on ${nodeName}. Use anyway?`)) {\n            return;\n          }\n          if (which === 'from') edge.fromPort = newVal;\n          else edge.toPort = newVal;\n          renderPortMap();\n          if (currentNodeId && (edge.from === currentNodeId || edge.to === currentNodeId)) {\n            claimTheImmortal(currentNodeId);\n          }\n        } else if (newVal === '') {\n          if (which === 'from') edge.fromPort = '';\n          else edge.toPort = '';\n          renderPortMap();\n          if (currentNodeId && (edge.from === currentNodeId || edge.to === currentNodeId)) {\n            claimTheImmortal(currentNodeId);\n          }\n        }\n      };\n      window.exportPortMap = function() {\n        let csv = \"From Device,From Port,To Device,To Port,Notes\\n\";\n        (EDGE_DATA.list || []).forEach(e => {\n          const fromName = NODE_DATA[e.from]?.name || e.from || \"\";\n          const toName = NODE_DATA[e.to]?.name || e.to || \"\";\n          const notes = (e.notes || []).join(\"; \");\n          csv += `${csvEscape(fromName)},${csvEscape(e.fromPort || \"\")},${csvEscape(toName)},${csvEscape(e.toPort || \"\")},${csvEscape(notes)}\\n`;\n        });\n        const blob = new Blob([csv], { type: \"text/csv\" });\n        const url = URL.createObjectURL(blob);\n        const a = document.createElement(\"a\");\n        a.href = url;\n        a.download = \"port-map.csv\";\n        a.click();\n        URL.revokeObjectURL(url);\n      };\n      if (portMapBtn && portMapModal) {\n        portMapBtn.addEventListener(\"click\", () => { renderPortMap(); portMapModal.classList.add(\"active\"); });\n      }\n      if (portMapClose && portMapModal) {\n        portMapClose.addEventListener(\"click\", () => { portMapModal.classList.remove(\"active\"); });\n      }\n      if (portMapSearch) { portMapSearch.addEventListener(\"input\", renderPortMap); }\n      if (portMapFilter) { portMapFilter.addEventListener(\"change\", renderPortMap); }\n      if (portMapModal) {\n        portMapModal.addEventListener(\"click\", (e) => { if (e.target === portMapModal) portMapModal.classList.remove(\"active\"); });\n      }\n      if (auditLogModal) {\n        auditLogModal.addEventListener(\"click\", (e) => {\n          if (e.target === auditLogModal) {\n            auditLogModal.classList.remove(\"active\");\n          }\n        });\n      }\n      const secretsBtn = document.getElementById(\"secrets-btn\");\n      const secretsModal = document.getElementById(\"secrets-modal\");\n      const secretsModalClose = document.getElementById(\"secrets-modal-close\");\n      const secretEditorModal = document.getElementById(\"secret-editor-modal\");\n      if (secretsBtn && secretsModal) {\n        secretsBtn.addEventListener(\"click\", () => {\n          displaySecrets();\n          secretsModal.classList.add(\"active\");\n        });\n      }\n      if (secretsModalClose && secretsModal) {\n        secretsModalClose.addEventListener(\"click\", () => {\n          secretsModal.classList.remove(\"active\");\n        });\n      }\n      if (secretsModal) {\n        secretsModal.addEventListener(\"click\", (e) => {\n          if (e.target === secretsModal) {\n            secretsModal.classList.remove(\"active\");\n          }\n        });\n      }\n      if (secretEditorModal) {\n        secretEditorModal.addEventListener(\"click\", (e) => {\n          if (e.target === secretEditorModal) {\n            closeSecretEditor();\n          }\n        });\n      }\n      loadAuditLog();\n      setupDragToCreate();\n      [\"physical\", \"logical\", \"security\", \"application\"].forEach(layer => {\n       const checkbox = document.getElementById(`layer-${layer}`);\n       if (checkbox) {\n        checkbox.addEventListener(\"change\", applyLayerFilter);\n       }\n      });\n      const layerSelect = document.getElementById(\"node-layer\");\n      if (layerSelect) {\n       layerSelect.addEventListener(\"change\", (e) => {\n        if (currentNodeId) {\n         pushUndo(\"change layer\");\n         NODE_DATA[currentNodeId].layer = e.target.value;\n         forgeTheTopology();\n        }\n       });\n      }\n       const assignedRackSelect = document.getElementById(\"node-assigned-rack\");\n       if (assignedRackSelect) {\n      assignedRackSelect.addEventListener(\"change\", (e) => {\n      if (currentNodeId) {\n      pushUndo(\"change assigned rack\");\n      EDGE_DATA.list = EDGE_DATA.list.filter(edge => edge.from !== currentNodeId && edge.to !== currentNodeId);\n      NODE_DATA[currentNodeId].assignedRack = e.target.value;\n      forgeTheTopology();\n      claimTheImmortal(currentNodeId);\n      }\n      });\n       }\n      const rackCapacitySelect = document.getElementById(\"node-rack-capacity\");\n      if (rackCapacitySelect) {\n       rackCapacitySelect.addEventListener(\"change\", (e) => {\n        if (currentNodeId) {\n         pushUndo(\"change rack capacity\");\n         NODE_DATA[currentNodeId].rackCapacity = e.target.value;\n         forgeTheTopology();\n        }\n       });\n      }\n      const bulkToolbarClose = document.getElementById(\"bulk-toolbar-close\");\n      if (bulkToolbarClose) {\n       bulkToolbarClose.addEventListener(\"click\", clearSelection);\n      }\n      const bulkAlignLeft = document.getElementById(\"bulk-align-left\");\n      if (bulkAlignLeft) {\n       bulkAlignLeft.addEventListener(\"click\", () => alignSelectedNodes(\"left\"));\n      }\n      const bulkAlignRight = document.getElementById(\"bulk-align-right\");\n      if (bulkAlignRight) {\n       bulkAlignRight.addEventListener(\"click\", () => alignSelectedNodes(\"right\"));\n      }\n      const bulkAlignTop = document.getElementById(\"bulk-align-top\");\n      if (bulkAlignTop) {\n       bulkAlignTop.addEventListener(\"click\", () => alignSelectedNodes(\"top\"));\n      }\n      const bulkAlignBottom = document.getElementById(\"bulk-align-bottom\");\n      if (bulkAlignBottom) {\n       bulkAlignBottom.addEventListener(\"click\", () => alignSelectedNodes(\"bottom\"));\n      }\n      const bulkDistributeH = document.getElementById(\"bulk-distribute-h\");\n      if (bulkDistributeH) {\n       bulkDistributeH.addEventListener(\"click\", () => distributeSelectedNodes(\"horizontal\"));\n      }\n      const bulkDistributeV = document.getElementById(\"bulk-distribute-v\");\n      if (bulkDistributeV) {\n       bulkDistributeV.addEventListener(\"click\", () => distributeSelectedNodes(\"vertical\"));\n      }\n      const bulkClone = document.getElementById(\"bulk-clone\");\n      if (bulkClone) {\n       bulkClone.addEventListener(\"click\", () => {\n        if (selectedNodes.size === 0) return;\n        pushUndo(\"clone selected\");\n        const nodesToClone = Array.from(selectedNodes);\n        selectedNodes.clear();\n        nodesToClone.forEach(id => {\n         cloneNode(id, true);\n         selectedNodes.add(currentNodeId);\n        });\n        updateNodeSelection();\n       });\n      }\n      const bulkDelete = document.getElementById(\"bulk-delete\");\n      if (bulkDelete) {\n       bulkDelete.addEventListener(\"click\", deleteSelected);\n      }\n      const bulkMobileBtn = document.getElementById(\"bulk-mobile-btn\");\n      const bulkActionsModal = document.getElementById(\"bulk-actions-modal\");\n      const bulkModalClose = document.getElementById(\"bulk-modal-close\");\n      if (bulkMobileBtn) {\n       bulkMobileBtn.addEventListener(\"click\", () => {\n        if (bulkActionsModal) bulkActionsModal.style.display = \"block\";\n       });\n      }\n      if (bulkModalClose) {\n       bulkModalClose.addEventListener(\"click\", () => {\n        if (bulkActionsModal) bulkActionsModal.style.display = \"none\";\n       });\n      }\n      const closeModalAfterAction = () => {\n       if (bulkActionsModal) bulkActionsModal.style.display = \"none\";\n      };\n      const bulkAlignLeftMobile = document.getElementById(\"bulk-align-left-mobile\");\n      if (bulkAlignLeftMobile) {\n       bulkAlignLeftMobile.addEventListener(\"click\", () => {\n        alignSelectedNodes(\"left\");\n        closeModalAfterAction();\n       });\n      }\n      const bulkAlignRightMobile = document.getElementById(\"bulk-align-right-mobile\");\n      if (bulkAlignRightMobile) {\n       bulkAlignRightMobile.addEventListener(\"click\", () => {\n        alignSelectedNodes(\"right\");\n        closeModalAfterAction();\n       });\n      }\n      const bulkAlignTopMobile = document.getElementById(\"bulk-align-top-mobile\");\n      if (bulkAlignTopMobile) {\n       bulkAlignTopMobile.addEventListener(\"click\", () => {\n        alignSelectedNodes(\"top\");\n        closeModalAfterAction();\n       });\n      }\n      const bulkAlignBottomMobile = document.getElementById(\"bulk-align-bottom-mobile\");\n      if (bulkAlignBottomMobile) {\n       bulkAlignBottomMobile.addEventListener(\"click\", () => {\n        alignSelectedNodes(\"bottom\");\n        closeModalAfterAction();\n       });\n      }\n      const bulkDistributeHMobile = document.getElementById(\"bulk-distribute-h-mobile\");\n      if (bulkDistributeHMobile) {\n       bulkDistributeHMobile.addEventListener(\"click\", () => {\n        distributeSelectedNodes(\"horizontal\");\n        closeModalAfterAction();\n       });\n      }\n      const bulkDistributeVMobile = document.getElementById(\"bulk-distribute-v-mobile\");\n      if (bulkDistributeVMobile) {\n       bulkDistributeVMobile.addEventListener(\"click\", () => {\n        distributeSelectedNodes(\"vertical\");\n        closeModalAfterAction();\n       });\n      }\n      const bulkCloneMobile = document.getElementById(\"bulk-clone-mobile\");\n      if (bulkCloneMobile) {\n       bulkCloneMobile.addEventListener(\"click\", () => {\n        if (selectedNodes.size === 0) return;\n        pushUndo(\"clone selected\");\n        const nodesToClone = Array.from(selectedNodes);\n        selectedNodes.clear();\n        nodesToClone.forEach(id => {\n         cloneNode(id);\n         selectedNodes.add(currentNodeId);\n        });\n        updateNodeSelection();\n        closeModalAfterAction();\n       });\n      }\n      const bulkDeleteMobile = document.getElementById(\"bulk-delete-mobile\");\n      if (bulkDeleteMobile) {\n       bulkDeleteMobile.addEventListener(\"click\", () => {\n        deleteSelected();\n        closeModalAfterAction();\n       });\n      }\n      const bulkLockMobile = document.getElementById(\"bulk-lock-mobile\");\n      if (bulkLockMobile) {\n       bulkLockMobile.addEventListener(\"click\", () => {\n        toggleLockSelected();\n        closeModalAfterAction();\n       });\n      }\n      const bulkGroupMobile = document.getElementById(\"bulk-group-mobile\");\n      if (bulkGroupMobile) {\n       bulkGroupMobile.addEventListener(\"click\", () => {\n        toggleGroupSelected();\n        closeModalAfterAction();\n       });\n      }\n      const bulkClearMobile = document.getElementById(\"bulk-clear-mobile\");\n      if (bulkClearMobile) {\n       bulkClearMobile.addEventListener(\"click\", () => {\n        clearSelection();\n        closeModalAfterAction();\n       });\n      }\n      const searchInput = document.getElementById(\"search-nodes\");\n      let searchTimeout = null;\n      if (searchInput) {\n       searchInput.addEventListener(\"input\", (e) => {\n        clearTimeout(searchTimeout);\n        searchTimeout = setTimeout(() => { searchNodes(e.target.value); }, 150);\n       });\n      }\n      addNodeBtn.addEventListener(\"click\", () => {\n\t  if (isViewOnly()) return;\n       document.getElementById(\"new-node-name\").value = \"\";\n       document.getElementById(\"new-node-ip\").value = \"\";\n       document.getElementById(\"new-node-tags\").value = \"\";\n       document.getElementById(\"new-node-shape\").value = \"circle\";\n       addNodeModal.classList.add(\"active\");\n       document.getElementById(\"new-node-name\").focus();\n      });\n      const addRackBtn = document.getElementById(\"add-rack-btn\");\n      const addRackModal = document.getElementById(\"add-rack-modal\");\n      const addRackCancel = document.getElementById(\"add-rack-cancel\");\n      const addRackSave = document.getElementById(\"add-rack-save\");\n      if (addRackBtn && addRackModal) {\n       addRackBtn.addEventListener(\"click\", () => {\n\t   if (isViewOnly()) return;\n        document.getElementById(\"new-rack-name\").value = \"\";\n        document.getElementById(\"new-rack-ip\").value = \"\";\n        document.getElementById(\"new-rack-tags\").value = \"\";\n        document.getElementById(\"new-rack-shape\").value = \"server\";\n        document.getElementById(\"new-rack-capacity\").value = \"42\";\n        addRackModal.classList.add(\"active\");\n        document.getElementById(\"new-rack-name\").focus();\n       });\n      }\n      if (addRackCancel && addRackModal) {\n       addRackCancel.addEventListener(\"click\", () => {\n        addRackModal.classList.remove(\"active\");\n       });\n      }\n      if (addRackModal) {\n       addRackModal.addEventListener(\"click\", (e) => {\n        if (e.target === addRackModal) {\n         addRackModal.classList.remove(\"active\");\n        }\n       });\n      }\n      if (addRackSave && addRackModal) {\n       addRackSave.addEventListener(\"click\", () => {\n        const name = document.getElementById(\"new-rack-name\").value.trim();\n        const ip = document.getElementById(\"new-rack-ip\").value.trim();\n        const tagsStr = document.getElementById(\"new-rack-tags\").value.trim();\n        const shape = document.getElementById(\"new-rack-shape\").value;\n        const capacity = document.getElementById(\"new-rack-capacity\").value;\n        if (!name) {\n         alert(\"Please enter a rack name.\");\n         return;\n        }\n        const tags = tagsStr ? tagsStr.split(\",\").map((t) => t.trim()).filter((t) => t) : [];\n        let baseId = name.toLowerCase().replace(/[^a-z0-9]+/g, \"-\").replace(/^-|-$/g, \"\");\n        if (!baseId) baseId = \"rack\";\n        let nodeId = baseId;\n        let counter = 1;\n        while (NODE_DATA[nodeId]) {\n         nodeId = baseId + \"-\" + counter;\n         counter++;\n        }\n        pushUndo(\"add rack\");\n        NODE_DATA[nodeId] = {\n         shape: shape,\n         name: name,\n         ip: ip || \"\",\n         role: \"Rack\",\n         tags: tags,\n         notes: [],\n         mac: \"\",\n         rackUnit: \"\",\n         uHeight: \"1\",\n         layer: \"physical\",\n         assignedRack: \"\",\n         rackCapacity: capacity,\n         isRack: true,\n         locked: false,\n         groupId: null\n        };\n        const viewWidth = CANVAS_WIDTH / canvasState.zoom;\n        const viewHeight = CANVAS_HEIGHT / canvasState.zoom;\n        const centerX = canvasState.panX + (viewWidth / 2);\n        const centerY = canvasState.panY + (viewHeight / 2);\n        savedPositions[nodeId] = {\n         x: centerX,\n         y: centerY\n        };\n        addRackModal.classList.remove(\"active\");\n        forgeTheTopology();\n        claimTheImmortal(nodeId);\n       });\n       [\"new-rack-name\", \"new-rack-ip\", \"new-rack-tags\"].forEach((inputId) => {\n        const input = document.getElementById(inputId);\n        if (input) {\n         input.addEventListener(\"keypress\", (e) => {\n          if (e.key === \"Enter\") {\n           addRackSave.click();\n          }\n         });\n        }\n       });\n      }\n      addNodeCancel.addEventListener(\"click\", () => {\n       addNodeModal.classList.remove(\"active\");\n      });\n      addNodeModal.addEventListener(\"click\", (e) => {\n       if (e.target === addNodeModal) {\n        addNodeModal.classList.remove(\"active\");\n       }\n      });\n      addNodeSave.addEventListener(\"click\", () => {\n       const name = document.getElementById(\"new-node-name\").value.trim();\n       const ip = document.getElementById(\"new-node-ip\").value.trim();\n       const tagsStr = document.getElementById(\"new-node-tags\").value.trim();\n       const shape = document.getElementById(\"new-node-shape\").value;\n       if (!name) {\n        alert(\"Please enter a node name.\");\n        return;\n       }\n       const tags = tagsStr ? tagsStr.split(\",\").map((t) => t.trim()).filter((t) => t) : [];\n       let baseId = name.toLowerCase().replace(/[^a-z0-9]+/g, \"-\").replace(/^-|-$/g, \"\");\n       if (!baseId) baseId = \"node\";\n       let nodeId = baseId;\n       let counter = 1;\n       while (NODE_DATA[nodeId]) {\n        nodeId = baseId + \"-\" + counter;\n        counter++;\n       }\n\t   pushUndo(\"add node\");\n       NODE_DATA[nodeId] = {\n        shape: shape || \"circle\",\n        name: name,\n        ip: ip || \"0.0.0.0\",\n        role: \"\",\n        tags: tags,\n        notes: [],\n        mac: \"\",\n        rackUnit: \"\",\n        uHeight: \"1\",\n        layer: \"physical\",\n        assignedRack: \"\",\n        rackCapacity: \"42\",\n        isRack: false,\n        locked: false,\n        groupId: null\n       };\n       if (currentView.mode === \"rack\" && currentView.rackId) {\n        NODE_DATA[nodeId].assignedRack = currentView.rackId;\n        NODE_DATA[nodeId].layer = \"physical\";\n        const rackCapacity = getRackCapacity(currentView.rackId);\n        const rackUHeight = getRackUHeight(currentView.rackId);\n        const viewHeight = CANVAS_HEIGHT / canvasState.zoom;\n        const centerY = canvasState.panY + (viewHeight / 2);\n        let unit = rackCapacity - Math.round((centerY - RACK_START_Y) / rackUHeight);\n        unit = Math.max(1, Math.min(rackCapacity, unit));\n        NODE_DATA[nodeId].rackUnit = String(unit);\n       }\n      const viewWidth = CANVAS_WIDTH / canvasState.zoom;\n      const viewHeight = CANVAS_HEIGHT / canvasState.zoom;\n      const centerX = canvasState.panX + (viewWidth / 2);\n      const centerY = canvasState.panY + (viewHeight / 2);\n      savedPositions[nodeId] = {\n      x: centerX,\n      y: centerY\n      };\n       addNodeModal.classList.remove(\"active\");\n       forgeTheTopology();\n       claimTheImmortal(nodeId);\n      });\n      [\"new-node-name\", \"new-node-ip\", \"new-node-tags\"].forEach(\n       (inputId) => {\n        document.getElementById(inputId).addEventListener(\"keypress\", (e) => {\n         if (e.key === \"Enter\") {\n          addNodeSave.click();\n         }\n        });\n       });\n      const clearAllBtn = document.getElementById(\"clear-all-btn\");\n      const clearAllModal = document.getElementById(\"clear-all-modal\");\n      const clearAllCancel = document.getElementById(\"clear-all-cancel\");\n      const clearAllConfirm = document.getElementById(\"clear-all-confirm\");\n      clearAllBtn.addEventListener(\"click\", () => {\n       clearAllModal.classList.add(\"active\");\n      });\n      clearAllCancel.addEventListener(\"click\", () => {\n       clearAllModal.classList.remove(\"active\");\n      });\n      clearAllModal.addEventListener(\"click\", (e) => {\n       if (e.target === clearAllModal) {\n        clearAllModal.classList.remove(\"active\");\n       }\n      });\n      clearAllConfirm.addEventListener(\"click\", () => {\n        NODE_DATA = {};\n        EDGE_DATA = { list: [] };\n        EDGE_LEGEND = {};\n        RECT_DATA = { list: [] };\n        TEXT_DATA = { list: [] };\n        savedPositions = {};\n        savedSizes = {};\n        savedStyles = {};\n        undoStack = [];\n        redoStack = [];\n        updateUndoButtons();\n        auditLog = [];\n        localStorage.removeItem(AUDIT_STORAGE_KEY);\n        rollbackVersions = [];\n        currentRollbackIndex = -1;\n        localStorage.removeItem(ROLLBACK_STORAGE_KEY);   \n        clipboard = null;      \n        selectedNodes.clear();\n        selectedEdges.clear();\n        selectedRects.clear();\n        selectedTexts.clear();\n        documentTabs = [{\n          id: \"main\",\n          name: \"Main Topology\",\n          nodes: {},\n          edges: { list: [] },\n          positions: {},\n          sizes: {},\n          styles: {},\n          legend: {},\n          rects: { list: [] },\n          texts: { list: [] },\n          pageState: null\n        }];\n        currentTabIndex = 0;\n        displayTabs();\n        clearAutosave();\n        try {\n          Object.keys(localStorage).forEach(key => {\n            if (key.startsWith(\"theonefile\")) {\n              localStorage.removeItem(key);\n            }\n          });\n        } catch(e) {}\n        const addLineSelect = document.getElementById(\"add-line-select\");\n        if (addLineSelect) addLineSelect.innerHTML = \"\";\n        const edgeLegendContent = document.getElementById(\"edge-legend-content\");\n        if (edgeLegendContent) edgeLegendContent.innerHTML = \"\";\n        clearAllModal.classList.remove(\"active\");\n        forgeTheTopology();\n        document.getElementById(\"node-panel\").style.display = \"none\";\n        document.getElementById(\"edge-panel\").style.display = \"none\";\n        document.getElementById(\"text-panel\").style.display = \"none\";\n        document.getElementById(\"topology-toolbar\").style.display = \"none\";\n        currentNodeId = null;\n        currentEdgeId = null;\n        currentTextId = null;\n        if (typeof displayRollbackVersions === \"function\") displayRollbackVersions();\n        if (typeof displayAuditLog === \"function\") displayAuditLog();\n        if (typeof forgeTheLegend === \"function\") forgeTheLegend();\n      });\n      (function addDeleteNodeButton() {\n       const nodePanel = document.getElementById(\"node-panel\");\n       if (!nodePanel) return;\n       let deleteBtn = document.getElementById(\"delete-node-btn\");\n       if (!deleteBtn) {\n        deleteBtn = document.createElement(\"button\");\n        deleteBtn.id = \"delete-node-btn\";\n        deleteBtn.textContent = \"Delete Node\";\n        deleteBtn.style.cssText = \"margin-top:15px; padding:10px 16px; background:var(--danger); color:#fff; border:none; border-radius:6px; cursor:pointer; font-size:clamp(14px,1.6vw,18px); font-weight:600; width:100%;\";\n        nodePanel.appendChild(deleteBtn);\n       }\n       deleteBtn.addEventListener(\"click\", () => {\n        if (!currentNodeId) return;\n        const nodeData = NODE_DATA[currentNodeId];\n        let confirmMessage;\n        let nodesInsideRack = [];\n        if (nodeData?.isRack) {\n         nodesInsideRack = Object.entries(NODE_DATA)\n          .filter(([id, n]) => n.assignedRack === currentNodeId)\n          .map(([id, n]) => n.name || id);\n         if (nodesInsideRack.length > 0) {\n          confirmMessage = `Delete rack \"${nodeData.name}\"?\\n\\nThis will also delete ${nodesInsideRack.length} node(s) inside:\\n• ${nodesInsideRack.join('\\n• ')}`;\n         } else {\n          confirmMessage = `Delete rack \"${nodeData.name}\"? (empty rack)`;\n         }\n        } else {\n         confirmMessage = `Delete node \"${nodeData?.name || currentNodeId}\" and all its connections?`;\n        }\n        challengeTheImmortal(confirmMessage, () => {\n         pushUndo(\"delete node\");\n         if (nodeData?.isRack) {\n          Object.keys(NODE_DATA).forEach(id => {\n           if (NODE_DATA[id]?.assignedRack === currentNodeId) {\n            EDGE_DATA.list = EDGE_DATA.list.filter(e => e.from !== id && e.to !== id);\n            delete NODE_DATA[id];\n            delete savedPositions[id];\n            delete savedSizes[id];\n            delete savedStyles[id];\n           }\n          });\n         }\n         EDGE_DATA.list = EDGE_DATA.list.filter(e => e.from !== currentNodeId && e.to !== currentNodeId);\n         delete NODE_DATA[currentNodeId];\n         delete savedPositions[currentNodeId];\n         delete savedSizes[currentNodeId];\n         delete savedStyles[currentNodeId];\n         currentNodeId = null;\n         currentEdgeId = null;\n         forgeTheTopology();\n         const remainingNodes = Object.keys(NODE_DATA);\n         if (remainingNodes.length > 0) {\n          claimTheImmortal(remainingNodes[0]);\n         } else {\n          document.getElementById(\"node-panel\").style.display = \"none\";\n          document.getElementById(\"edge-panel\").style.display = \"none\";\n          document.getElementById(\"topology-toolbar\").style.display = \"none\";\n         }\n        });\n       });\n      })();\n      function screenshotCanvas() {\n       const svg = document.getElementById(\"map\");\n       const svgClone = svg.cloneNode(true);\n       const viewBox = svg.getAttribute(\"viewBox\").split(\" \").map(Number);\n       const [x, y, width, height] = viewBox;\n       svgClone.setAttribute(\"width\", width);\n       svgClone.setAttribute(\"height\", height);\n       svgClone.setAttribute(\"viewBox\", `${x} ${y} ${width} ${height}`);\n       const wrapper = document.createElement(\"div\");\n       wrapper.style.position = \"absolute\";\n       wrapper.style.left = \"-9999px\";\n       wrapper.appendChild(svgClone);\n       document.body.appendChild(wrapper);\n       function inlineStyles(original, clone) {\n        const elements = original.querySelectorAll(\"*\");\n        const clonedElements = clone.querySelectorAll(\"*\");\n        const rootStyles = getComputedStyle(document.documentElement);\n        const bgColor = rootStyles.getPropertyValue(\"--bg\").trim() || \"#050608\";\n        const bgRect = document.createElementNS(\"http://www.w3.org/2000/svg\", \"rect\");\n        bgRect.setAttribute(\"x\", x);\n        bgRect.setAttribute(\"y\", y);\n        bgRect.setAttribute(\"width\", width);\n        bgRect.setAttribute(\"height\", height);\n        bgRect.setAttribute(\"fill\", bgColor);\n        clone.insertBefore(bgRect, clone.firstChild);\n        elements.forEach((el, index) => {\n         const clonedEl = clonedElements[index];\n         if (!clonedEl) return;\n         const computedStyle = getComputedStyle(el);\n         const svgProps = [\"fill\", \"stroke\", \"stroke-width\", \"stroke-dasharray\", \"stroke-linecap\", \"stroke-linejoin\", \"opacity\", \"font-family\", \"font-size\", \"font-weight\", \"font-style\", \"text-anchor\", \"dominant-baseline\", \"marker-start\", \"marker-end\", ];\n         svgProps.forEach((prop) => {\n          const value = computedStyle.getPropertyValue(prop);\n          if (value && value !== \"none\" && value !== \"normal\") {\n           clonedEl.style[prop] = value;\n          }\n         });\n         clonedEl.removeAttribute(\"class\");\n        });\n       }\n       inlineStyles(svg, svgClone);\n       const svgData = new XMLSerializer().serializeToString(svgClone);\n       document.body.removeChild(wrapper);\n       const svgBlob = new Blob([svgData], {\n        type: \"image/svg+xml;charset=utf-8\",\n       });\n       const url = URL.createObjectURL(svgBlob);\n       const img = new Image();\n       img.onload = function() {\n        const canvas = document.createElement(\"canvas\");\n        canvas.width = width;\n        canvas.height = height;\n        const ctx = canvas.getContext(\"2d\");\n        ctx.drawImage(img, 0, 0);\n        canvas.toBlob(function(blob) {\n         const link = document.createElement(\"a\");\n         const timestamp = new Date().toISOString().slice(0, 10);\n         link.download = `topology-${timestamp}.png`;\n         link.href = URL.createObjectURL(blob);\n         link.click();\n         URL.revokeObjectURL(url);\n         URL.revokeObjectURL(link.href);\n        }, \"image/png\");\n       };\n       img.onerror = function() {\n        console.error(\"Failed to load SVG image\");\n        alert(\"Screenshot failed. Please try again.\");\n        URL.revokeObjectURL(url);\n       };\n       img.src = url;\n      }\n      function exportCanvasSVG() {\n       const svg = document.getElementById(\"map\");\n       const svgClone = svg.cloneNode(true);\n       const viewBox = svg.getAttribute(\"viewBox\").split(\" \").map(Number);\n       const [x, y, width, height] = viewBox;\n       svgClone.setAttribute(\"width\", width);\n       svgClone.setAttribute(\"height\", height);\n       const rootStyles = getComputedStyle(document.documentElement);\n       const bgColor = rootStyles.getPropertyValue(\"--bg\").trim() || \"#050608\";\n       const bgRect = document.createElementNS(\"http://www.w3.org/2000/svg\", \"rect\");\n       bgRect.setAttribute(\"x\", x);\n       bgRect.setAttribute(\"y\", y);\n       bgRect.setAttribute(\"width\", width);\n       bgRect.setAttribute(\"height\", height);\n       bgRect.setAttribute(\"fill\", bgColor);\n       svgClone.insertBefore(bgRect, svgClone.firstChild);\n       const wrapper = document.createElement(\"div\");\n       wrapper.style.position = \"absolute\";\n       wrapper.style.left = \"-9999px\";\n       wrapper.appendChild(svgClone);\n       document.body.appendChild(wrapper);\n       const elements = svg.querySelectorAll(\"*\");\n       const clonedElements = svgClone.querySelectorAll(\"*\");\n       elements.forEach((el, index) => {\n        const clonedEl = clonedElements[index];\n        if (!clonedEl) return;\n        const computedStyle = getComputedStyle(el);\n        const svgProps = [\"fill\", \"stroke\", \"stroke-width\", \"stroke-dasharray\", \"stroke-linecap\", \"stroke-linejoin\", \"opacity\", \"font-family\", \"font-size\", \"font-weight\", \"font-style\", \"text-anchor\", \"dominant-baseline\", \"marker-start\", \"marker-end\", ];\n        svgProps.forEach((prop) => {\n         const value = computedStyle.getPropertyValue(prop);\n         if (value && value !== \"none\" && value !== \"normal\") {\n          clonedEl.setAttribute(prop, value);\n         }\n        });\n        clonedEl.removeAttribute(\"class\");\n       });\n       const svgData = new XMLSerializer().serializeToString(svgClone);\n       document.body.removeChild(wrapper);\n       const blob = new Blob([svgData], {\n        type: \"image/svg+xml;charset=utf-8\",\n       });\n       const url = URL.createObjectURL(blob);\n       const link = document.createElement(\"a\");\n       const timestamp = new Date().toISOString().slice(0, 10);\n       link.download = `topology-${timestamp}.svg`;\n       link.href = url;\n       link.click();\n       URL.revokeObjectURL(url);\n      }\n      let resizeTimeout;\n       window.addEventListener('resize', () => {\n       clearTimeout(resizeTimeout);\n       resizeTimeout = setTimeout(() => {\n       updateDrawToolbarVisibility();\n       updateTopologyToolbarVisibility();\n       }, 100);\n      });\n\tdocument.addEventListener('DOMContentLoaded', () => {\n\t  document.querySelectorAll('.dropdown').forEach(dropdown => {\n\t\tconst btn = dropdown.querySelector('.dropdown-btn');\n\t\tconst menu = dropdown.querySelector('.dropdown-menu');\n\t\tif (!btn || !menu) return;\n\t\tbtn.addEventListener('click', (e) => {\n\t\t  e.stopPropagation();\n\t\t  document.querySelectorAll('.dropdown-menu.open').forEach(m => {\n\t\t\tif (m !== menu) m.classList.remove('open');\n\t\t  });\n\t\t  menu.classList.toggle('open');\n\t\t});\n\t  });\n\t  document.addEventListener('click', (e) => {\n\t\tif (!e.target.closest('.dropdown')) {\n\t\t  document.querySelectorAll('.dropdown-menu.open').forEach(m => m.classList.remove('open'));\n\t\t}\n\t  });\n\t  document.querySelectorAll('.dropdown-menu button').forEach(btn => {\n\t\tbtn.addEventListener('click', () => {\n\t\t  setTimeout(() => {\n\t\t\tdocument.querySelectorAll('.dropdown-menu.open').forEach(m => m.classList.remove('open'));\n\t\t  }, 100);\n\t\t});\n\t  });\n\t  });\nfunction showFormatHelp() {\n  document.getElementById('save-info-modal').classList.add('active');\n  document.querySelectorAll('.help-tab').forEach(t => {\n    t.style.background = 'var(--panel)';\n    t.style.color = 'var(--text-main)';\n  });\n  document.querySelectorAll('.help-tab-content').forEach(c => c.style.display = 'none');\n  const formatsTab = document.querySelector('.help-tab[data-tab=\"formats\"]');\n  if (formatsTab) {\n    formatsTab.style.background = 'var(--accent)';\n    formatsTab.style.color = 'var(--bg)';\n  }\n  document.getElementById('help-tab-formats').style.display = 'block';\n}\n\tfunction printTopology() {\n\t  const svg = document.getElementById('map');\n\t  if (!svg) { window.print(); return; }\n\t  const originalViewBox = svg.getAttribute('viewBox');\n\t  let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;\n\t  \n\t  Object.values(savedPositions).forEach(pos => {\n\t\tminX = Math.min(minX, pos.x - 100);\n\t\tminY = Math.min(minY, pos.y - 100);\n\t\tmaxX = Math.max(maxX, pos.x + 100);\n\t\tmaxY = Math.max(maxY, pos.y + 100);\n\t  });\n\t  \n\t  RECT_DATA.list.forEach(rect => {\n\t\tminX = Math.min(minX, rect.x);\n\t\tminY = Math.min(minY, rect.y);\n\t\tmaxX = Math.max(maxX, rect.x + rect.width);\n\t\tmaxY = Math.max(maxY, rect.y + rect.height);\n\t  });\n\t  \n\t  TEXT_DATA.list.forEach(text => {\n\t\tminX = Math.min(minX, text.x - 50);\n\t\tminY = Math.min(minY, text.y - 50);\n\t\tmaxX = Math.max(maxX, text.x + 200);\n\t\tmaxY = Math.max(maxY, text.y + 50);\n\t  });\n\n\t  const padding = 50;\n\t  minX -= padding;\n\t  minY -= padding;\n\t  maxX += padding;\n\t  maxY += padding;\n\n\t  if (minX !== Infinity) {\n\t\tsvg.setAttribute('viewBox', `${minX} ${minY} ${maxX - minX} ${maxY - minY}`);\n\t  }\n\n\t  window.print();\n\n\t  setTimeout(() => {\n\t\tsvg.setAttribute('viewBox', originalViewBox);\n\t  }, 1000);\n\t}\n\tfunction exportJSONFile() {\n\t  const data = captureTheQuickening();\n\t  const jsonStr = JSON.stringify(data, null, 2);\n\t  const blob = new Blob([jsonStr], { type: \"application/json\" });\n\t  const url = URL.createObjectURL(blob);\n\t  const a = document.createElement(\"a\");\n\t  a.href = url;\n\t  const safeTitle = (PAGE_STATE.title || \"network-topology\").toLowerCase().replace(/[^a-z0-9\\-]+/g, \"-\");\n\t  a.download = `${safeTitle}.json`;\n\t  a.click();\n\t  URL.revokeObjectURL(url);\n\t  logAuditEvent(\"export\", `Exported JSON: ${a.download}`);\n\t}\n\tfunction exportCSV() {\n\t  const safeTitle = (PAGE_STATE.title || 'topology').toLowerCase().replace(/[^a-z0-9]+/g, '-');\n\t  const timestamp = new Date().toISOString();\n  const config = captureTheQuickening();\n\t  let csv = `#THEONEFILE_CONFIG:${JSON.stringify(config)}\\n`;\n\t  csv += `#\\n# ${PAGE_STATE.title || 'Network Topology'} - Node List\\n`;\n\t  csv += `# Exported from The One File on ${timestamp}\\n`;\n\t  const headers = ['name','ip','role','shape','tags','layer','mac','rackUnit','uHeight','assignedRack','rackCapacity','isRack','locked','groupId','x','y','size','notes','styles'];\n\t  csv += headers.join(',') + '\\n';\n\t  Object.entries(NODE_DATA).forEach(([id, node]) => {\n\t\tconst pos = savedPositions[id] || { x: 0, y: 0 };\n\t\tconst size = savedSizes[id] || 50;\n\t\tconst styles = savedStyles[id] ? JSON.stringify(savedStyles[id]) : '';\n\t\tconst row = [\n\t\t  csvEscape(node.name || ''), csvEscape(node.ip || ''), csvEscape(node.role || ''),\n\t\t  node.shape || 'circle', csvEscape((node.tags || []).join(';')), node.layer || 'physical',\n\t\t  csvEscape(node.mac || ''), csvEscape(node.rackUnit || ''), node.uHeight || '1',\n\t\t  node.assignedRack || '', node.rackCapacity || '', node.isRack ? 'true' : 'false',\n\t\t  node.locked ? 'true' : 'false', node.groupId || '', Math.round(pos.x), Math.round(pos.y),\n\t\t  size, csvEscape((node.notes || []).join('|')), csvEscape(styles)\n\t\t];\n\t\tcsv += row.join(',') + '\\n';\n\t  });\n\t  const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });\n\t  const url = URL.createObjectURL(blob);\n\t  const a = document.createElement('a');\n\t  a.href = url;\n\t  a.download = `${safeTitle}.csv`;\n\t  a.click();\n\t  URL.revokeObjectURL(url);\n\t  logAuditEvent('export', `Exported CSV: ${a.download}`);\n\t}\n\tfunction csvEscape(val) {\n\t  if (val === null || val === undefined) return '';\n\t  const str = String(val);\n\t  if (str.includes(',') || str.includes('\"') || str.includes('\\n') || str.includes('\\r')) {\n\t\treturn '\"' + str.replace(/\"/g, '\"\"') + '\"';\n\t  }\n\t  return str;\n\t}\n\tdocument.getElementById('import-csv-file').addEventListener('change', async (e) => {\n\t  const file = e.target.files[0];\n\t  if (!file) return;\n\t  e.target.value = '';\n\t  try {\n\t\tconst text = await file.text();\n\t\tconst lines = text.split(/\\r?\\n/);\n\t\tlet config = null;\n\t\tlet dataLines = [];\n\t\tlet headers = null;\n\t\tfor (const line of lines) {\n\t\t  const trimmed = line.trim();\n\t\t  if (!trimmed) continue;\n\t\t  if (trimmed.startsWith('#THEONEFILE_CONFIG:')) {\n\t\t\ttry { config = JSON.parse(trimmed.substring(19)); } catch (err) { console.warn('Failed to parse CSV config:', err); }\n\t\t\tcontinue;\n\t\t  }\n\t\t  if (trimmed.startsWith('#')) continue;\n\t\t  if (!headers) { headers = parseCSVLine(trimmed).map(h => h.toLowerCase().trim()); continue; }\n\t\t  dataLines.push(trimmed);\n\t\t}\n\t\tif (!headers || dataLines.length === 0) { alert('CSV file has no data rows'); return; }\n\t\tconst nameIdx = headers.indexOf('name');\n\t\tif (nameIdx === -1) { alert('CSV must have a \"name\" column'); return; }\n\t\tconst nodes = dataLines.map(line => {\n\t\t  const values = parseCSVLine(line);\n\t\t  const node = {};\n\t\t  headers.forEach((h, idx) => { node[h] = values[idx] || ''; });\n\t\t  return node;\n\t\t});\n\t\tconst hasFullBackup = config && config.documentTabs;\n    const hasConfig = config && (config.pageState || config.page);\n    \n    let importMode = 'add'; \n    \n    if (hasFullBackup) {\n      const choice = confirm(\n        `This CSV contains a full backup.\\n\\n` +\n        `• ${nodes.length} nodes in CSV data\\n` +\n        `• ${config.documentTabs?.length || 1} tab(s) in backup\\n` +\n        `• ${config.edgeData?.list?.length || 0} connections in backup\\n\\n` +\n        `Click OK for FULL RESTORE (replace all data)\\n` +\n        `Click Cancel to just ADD the ${nodes.length} nodes`\n      );\n      if (choice) {\n        importMode = 'full';\n      }\n    } else {\n      let confirmMsg = `Import ${nodes.length} nodes from CSV?\\n\\n`;\n      confirmMsg += hasConfig ? 'This will ADD nodes and RESTORE settings/theme.\\n' : 'This will ADD nodes to your existing topology.\\n';\n      confirmMsg += '\\nNote: CSV does not include connections, zones, or text labels.';\n      if (!confirm(confirmMsg)) return;\n    }\n    \n    pushUndo('import csv');\n    \n    if (importMode === 'full' && hasFullBackup) {\n      NODE_DATA = config.nodeData || {};\n      EDGE_DATA = config.edgeData || { list: [] };\n      EDGE_LEGEND = config.edgeLegend || {};\n      RECT_DATA = config.rectData || { list: [] };\n      TEXT_DATA = config.textData || { list: [] };\n      savedPositions = config.nodePositions || {};\n      savedSizes = config.nodeSizes || {};\n      savedStyles = config.nodeStyles || {};\n      if (config.page) {\n        PAGE_STATE = Object.assign({}, DEFAULT_PAGE_STATE, config.page);\n        wieldThePower();\n      }\n      if (config.canvas) {\n        canvasState.zoom = config.canvas.zoom || 1;\n        canvasState.panX = config.canvas.panX || 0;\n        canvasState.panY = config.canvas.panY || 0;\n      }\n      if (config.page?.title) {\n        document.title = config.page.title;\n        document.querySelector(\".editable-page-title\").textContent = config.page.title;\n      }\n      if (config.documentTabs) {\n        documentTabs = config.documentTabs;\n        currentTabIndex = config.currentTabIndex || 0;\n      }\n      if (config.savedStyleSets) {\n        savedStyleSets = config.savedStyleSets;\n      }\n      if (config.savedTopologyView) {\n        savedTopologyView = config.savedTopologyView;\n      }\n      if (config.encryptedSections) {\n        encryptedSections = config.encryptedSections;\n      }\n      if (config.auditLog && Array.isArray(config.auditLog)) {\n        auditLog = config.auditLog;\n        try {\n          localStorage.setItem(AUDIT_STORAGE_KEY, JSON.stringify(auditLog));\n        } catch (e) {\n          console.warn(\"Failed to sync audit log to localStorage:\", e);\n        }\n      }\n      Object.values(NODE_DATA).forEach(node => {\n        if (!node.tags) node.tags = [];\n        if (!node.notes) node.notes = [];\n      });\n      EDGE_DATA.list.forEach(edge => {\n        if (!edge.notes) edge.notes = [];\n      });\n      forgeTheTopology();\n      if (typeof forgeTheLegend === 'function') forgeTheLegend();\n      updateViewBox();\n      logAuditEvent('import', `Imported CSV (full restore): ${file.name}`);\n      alert(`Full restore complete from CSV backup`);\n      return;\n    }\n\t\n\t\tif (hasConfig) {\n\t\t  Object.assign(PAGE_STATE, config.pageState || config.page);\n\t\t  if (config.canvasView || config.canvas) {\n\t\t\tcanvasState.zoom = (config.canvasView || config.canvas).zoom || 1;\n\t\t\tcanvasState.panX = (config.canvasView || config.canvas).panX || 0;\n\t\t\tcanvasState.panY = (config.canvasView || config.canvas).panY || 0;\n\t\t  }\n\t\t  if (config.legend || config.edgeLegend) Object.assign(EDGE_LEGEND, config.legend || config.edgeLegend);\n\t\t  wieldThePower();\n\t\t}\n\t\tlet gridX = 200, gridY = 200;\n\t\tconst spacing = 150;\n\t\tconst perRow = Math.ceil(Math.sqrt(nodes.length));\n\t\tlet gridIndex = 0;\n\t\tnodes.forEach((n) => {\n\t\t  let baseId = (n.name || 'node').toLowerCase().replace(/[^a-z0-9]+/g, '-');\n\t\t  if (!baseId) baseId = 'node';\n\t\t  let nodeId = baseId;\n\t\t  let counter = 1;\n\t\t  while (NODE_DATA[nodeId]) { nodeId = `${baseId}-${counter}`; counter++; }\n\t\t  NODE_DATA[nodeId] = {\n\t\t\tname: n.name || 'Unnamed', ip: n.ip || '', role: n.role || '', shape: n.shape || 'circle',\n\t\t\ttags: n.tags ? n.tags.split(';').map(t => t.trim()).filter(t => t) : [],\n\t\t\tnotes: n.notes ? n.notes.split('|').map(t => t.trim()).filter(t => t) : [],\n\t\t\tlayer: n.layer || 'physical', mac: n.mac || '', rackUnit: n.rackunit || '',\n\t\t\tuHeight: n.uheight || '1', assignedRack: n.assignedrack || '', rackCapacity: n.rackcapacity || '',\n\t\t\tisRack: n.israck === 'true', locked: n.locked === 'true', groupId: n.groupid || null\n\t\t  };\n\t\t  const hasPosition = n.x && n.y && !isNaN(parseFloat(n.x)) && !isNaN(parseFloat(n.y));\n\t\t  if (hasPosition) {\n\t\t\tsavedPositions[nodeId] = { x: parseFloat(n.x), y: parseFloat(n.y) };\n\t\t  } else {\n\t\t\tconst row = Math.floor(gridIndex / perRow);\n\t\t\tconst col = gridIndex % perRow;\n\t\t\tsavedPositions[nodeId] = { x: gridX + col * spacing, y: gridY + row * spacing };\n\t\t\tgridIndex++;\n\t\t  }\n\t\t  if (n.size && !isNaN(parseFloat(n.size))) savedSizes[nodeId] = parseFloat(n.size);\n\t\t  if (n.styles) { try { savedStyles[nodeId] = JSON.parse(n.styles); } catch (err) {} }\n\t\t});\n\t\tforgeTheTopology();\n\t\tupdateViewBox();\n\t\tlogAuditEvent('import', `Imported CSV: ${file.name} (${nodes.length} nodes)`);\n\t\talert(`Successfully imported ${nodes.length} nodes`);\n\t  } catch (err) {\n\t\tconsole.error('CSV import error:', err);\n\t\talert('Failed to import CSV: ' + err.message);\n\t  }\n\t});\n\tfunction parseCSVLine(line) {\n\t  const result = [];\n\t  let current = '';\n\t  let inQuotes = false;\n\t  for (let i = 0; i < line.length; i++) {\n\t\tconst char = line[i];\n\t\tif (char === '\"') {\n\t\t  if (inQuotes && line[i + 1] === '\"') { current += '\"'; i++; }\n\t\t  else { inQuotes = !inQuotes; }\n\t\t} else if (char === ',' && !inQuotes) { result.push(current); current = ''; }\n\t\telse { current += char; }\n\t  }\n\t  result.push(current);\n\t  return result;\n\t}\n\tfunction exportMarkdown() {\n  const safeTitle = (PAGE_STATE.title || 'topology').toLowerCase().replace(/[^a-z0-9]+/g, '-');\n  const timestamp = new Date().toISOString();\n  const title = PAGE_STATE.title || 'Network Topology';\n  const fullData = captureTheQuickening();\n  const config = fullData;\n\t  let md = `<!--THEONEFILE_CONFIG\\n${JSON.stringify(config, null, 2)}\\nTHEONEFILE_CONFIG-->\\n\\n`;\n\t  md += `# ${title}\\n\\n> Exported from The One File on ${timestamp}\\n\\n`;\n\t  md += `## Legend\\n\\n`;\n\t  if (Object.keys(EDGE_LEGEND).length > 0) {\n\t\tObject.entries(EDGE_LEGEND).forEach(([color, label]) => { md += `- ${color}: ${label}\\n`; });\n\t  } else { md += `_No legend entries_\\n`; }\n\t  md += '\\n## Nodes\\n\\n';\n\t  Object.entries(NODE_DATA).forEach(([id, node]) => {\n\t\tconst pos = savedPositions[id] || { x: 0, y: 0 };\n\t\tconst size = savedSizes[id] || 50;\n\t\tconst styles = savedStyles[id] || null;\n\t\tmd += `### ${id}\\n`;\n\t\tmd += `- **Name:** ${node.name || ''}\\n- **IP:** ${node.ip || ''}\\n- **Role:** ${node.role || ''}\\n`;\n\t\tmd += `- **Shape:** ${node.shape || 'circle'}\\n- **Tags:** ${(node.tags || []).join('; ') || '_none_'}\\n`;\n\t\tmd += `- **Layer:** ${node.layer || 'physical'}\\n- **MAC:** ${node.mac || ''}\\n`;\n\t\tmd += `- **Rack Unit:** ${node.rackUnit || ''}\\n- **U Height:** ${node.uHeight || '1'}\\n`;\n\t\tmd += `- **Assigned Rack:** ${node.assignedRack || ''}\\n- **Rack Capacity:** ${node.rackCapacity || ''}\\n`;\n\t\tmd += `- **Is Rack:** ${node.isRack ? 'true' : 'false'}\\n- **Locked:** ${node.locked ? 'true' : 'false'}\\n`;\n\t\tmd += `- **Group ID:** ${node.groupId || ''}\\n- **Position:** ${Math.round(pos.x)}, ${Math.round(pos.y)}\\n- **Size:** ${size}\\n`;\n\t\tif (node.notes && node.notes.length > 0) { md += `- **Notes:**\\n`; node.notes.forEach(note => { md += `  - ${note.replace(/\\n/g, '<br>')}\\n`; }); }\n\t\tif (styles) { md += `- **Styles:** \\`${JSON.stringify(styles)}\\`\\n`; }\n\t\tmd += '\\n';\n\t  });\n\t  md += `## Connections\\n\\n`;\n\t  if (EDGE_DATA.list && EDGE_DATA.list.length > 0) {\n\t\tEDGE_DATA.list.forEach(edge => {\n\t\t  const fromPort = edge.fromPort ? ` (${edge.fromPort})` : '';\n\t\t  const toPort = edge.toPort ? ` (${edge.toPort})` : '';\n\t\t  md += `- ${edge.from}${fromPort} --> ${edge.to}${toPort}\\n`;\n\t\t  md += `  - **ID:** ${edge.id}\\n  - **Label:** ${(edge.label || '').replace(/\\n/g, '<br>')}\\n  - **Color:** ${edge.color || ''}\\n`;\n\t\t  md += `  - **Width:** ${edge.width || 4}\\n  - **Direction:** ${edge.direction || 'none'}\\n`;\n\t\t  md += `  - **Routing:** ${edge.routing || 'curved'}\\n  - **Type:** ${edge.type || 'main'}\\n`;\n\t\t  md += `  - **Line Style:** ${edge.lineStyle || 'solid'}\\n  - **Group ID:** ${edge.groupId || ''}\\n`;\n\t\t  if (edge.points && edge.points.length > 0) { md += `  - **Points:** ${edge.points.map(p => `${Math.round(p.x)},${Math.round(p.y)}`).join(' ')}\\n`; }\n\t\t  if (edge.notes && edge.notes.length > 0) { md += `  - **Notes:**\\n`; edge.notes.forEach(note => { md += `    - ${note.replace(/\\n/g, '<br>')}\\n`; }); }\n\t\t  md += '\\n';\n\t\t});\n\t  } else { md += `_No connections_\\n\\n`; }\n\t  md += `## Zones\\n\\n`;\n\t  if (RECT_DATA.list && RECT_DATA.list.length > 0) {\n\t\tRECT_DATA.list.forEach(rect => {\n\t\t  md += `### ${rect.id}\\n`;\n\t\t  md += `- **Position:** ${Math.round(rect.x)}, ${Math.round(rect.y)}\\n- **Size:** ${Math.round(rect.width)} x ${Math.round(rect.height)}\\n`;\n\t\t  md += `- **Color:** ${rect.color || ''}\\n- **Style:** ${rect.style || 'filled'}\\n- **Line Style:** ${rect.lineStyle || 'solid'}\\n`;\n\t\t  md += `- **Border Color:** ${rect.borderColor || ''}\\n- **Border Width:** ${rect.borderWidth !== undefined ? rect.borderWidth : 2}\\n`;\n\t\t  if (rect.notes && rect.notes.length > 0) { md += `- **Notes:**\\n`; rect.notes.forEach(note => { md += `  - ${note.replace(/\\n/g, '<br>')}\\n`; }); }\n\t\t  md += '\\n';\n\t\t});\n\t  } else { md += `_No zones_\\n\\n`; }\n\t  md += `## Text Labels\\n\\n`;\n\t  if (TEXT_DATA.list && TEXT_DATA.list.length > 0) {\n\t\tTEXT_DATA.list.forEach(text => {\n\t\t  md += `### ${text.id}\\n`;\n\t\t  md += `- **Content:** ${(text.content || '').replace(/\\n/g, '<br>')}\\n- **Position:** ${Math.round(text.x)}, ${Math.round(text.y)}\\n`;\n\t\t  md += `- **Font Size:** ${text.fontSize || 18}\\n- **Color:** ${text.color || '#e2e8f0'}\\n`;\n\t\t  md += `- **Font Weight:** ${text.fontWeight || 'normal'}\\n- **Font Style:** ${text.fontStyle || 'normal'}\\n`;\n\t\t  md += `- **Text Align:** ${text.textAlign || 'start'}\\n- **Text Decoration:** ${text.textDecoration || 'none'}\\n`;\n\t\t  md += `- **Background Color:** ${text.bgColor || '#000000'}\\n- **Background Enabled:** ${text.bgEnabled ? 'true' : 'false'}\\n`;\n\t\t  md += `- **Opacity:** ${text.opacity !== undefined ? text.opacity : 1}\\n\\n`;\n\t\t});\n\t  } else { md += `_No text labels_\\n\\n`; }\n\t  const blob = new Blob([md], { type: 'text/markdown;charset=utf-8;' });\n\t  const url = URL.createObjectURL(blob);\n\t  const a = document.createElement('a');\n\t  a.href = url;\n\t  a.download = `${safeTitle}.md`;\n\t  a.click();\n\t  URL.revokeObjectURL(url);\n\t  logAuditEvent('export', `Exported Markdown: ${a.download}`);\n\t}\n\t\tdocument.getElementById('import-markdown-file').addEventListener('change', async (e) => {\n\t\t  const file = e.target.files[0];\n\t\t  if (!file) return;\n\t\t  e.target.value = '';\n\t\t  try {\n    const text = await file.text();\n    let config = null;\n    const configMatch = text.match(/<!--THEONEFILE_CONFIG\\s*([\\s\\S]*?)\\s*THEONEFILE_CONFIG-->/);\n    if (configMatch) { try { config = JSON.parse(configMatch[1].trim()); } catch (err) { console.warn('Failed to parse Markdown config:', err); } }\n    const nodes = {}, positions = {}, sizes = {}, styles = {}, edges = [], rects = [], texts = [], legend = {};\n    const sections = text.split(/^## /m);\n    sections.forEach(section => {\n      const lines = section.trim().split('\\n');\n      const sectionTitle = lines[0]?.trim().toLowerCase();\n      if (sectionTitle === 'legend') {\n        lines.slice(1).forEach(line => {\n          const match = line.match(/^- (#[a-fA-F0-9]{3,8}):\\s*(.+)$/);\n          if (match) legend[match[1]] = match[2].trim();\n        });\n      } else if (sectionTitle === 'nodes') {\n        let currentNodeId = null, currentNode = null, inNotes = false;\n        lines.slice(1).forEach(line => {\n          const nodeMatch = line.match(/^### (.+)$/);\n          if (nodeMatch) {\n            if (currentNodeId && currentNode) nodes[currentNodeId] = currentNode;\n            currentNodeId = nodeMatch[1].trim();\n            currentNode = { tags: [], notes: [] };\n            inNotes = false;\n            return;\n          }\n          if (!currentNode) return;\n          if (inNotes && line.match(/^\\s+-\\s+(.+)$/)) {\n            const noteMatch = line.match(/^\\s+-\\s+(.+)$/);\n            if (noteMatch) currentNode.notes.push(noteMatch[1].trim().replace(/<br>/g, '\\n'));\n            return;\n          }\n          inNotes = false;\n          const propMatch = line.match(/^- \\*\\*(.+?):\\*\\*\\s*(.*)$/);\n          if (propMatch) {\n            const key = propMatch[1].trim().toLowerCase(), val = propMatch[2].trim();\n            switch (key) {\n              case 'name': currentNode.name = val; break;\n              case 'ip': currentNode.ip = val; break;\n              case 'role': currentNode.role = val; break;\n              case 'shape': currentNode.shape = val; break;\n              case 'tags': currentNode.tags = val === '_none_' ? [] : val.split(';').map(t => t.trim()).filter(t => t); break;\n              case 'layer': currentNode.layer = val; break;\n              case 'mac': currentNode.mac = val; break;\n              case 'rack unit': currentNode.rackUnit = val; break;\n              case 'u height': currentNode.uHeight = val; break;\n              case 'assigned rack': currentNode.assignedRack = val; break;\n              case 'rack capacity': currentNode.rackCapacity = val; break;\n              case 'is rack': currentNode.isRack = val === 'true'; break;\n              case 'locked': currentNode.locked = val === 'true'; break;\n              case 'group id': currentNode.groupId = val || null; break;\n              case 'position':\n                const posMatch = val.match(/(-?\\d+),\\s*(-?\\d+)/);\n                if (posMatch) positions[currentNodeId] = { x: parseFloat(posMatch[1]), y: parseFloat(posMatch[2]) };\n                break;\n              case 'size': sizes[currentNodeId] = parseFloat(val) || 50; break;\n              case 'notes': inNotes = true; break;\n              case 'styles':\n                const stylesMatch = val.match(/`(.+)`/);\n                if (stylesMatch) { try { styles[currentNodeId] = JSON.parse(stylesMatch[1]); } catch (err) {} }\n                break;\n            }\n          }\n        });\n        if (currentNodeId && currentNode) nodes[currentNodeId] = currentNode;\n         } else if (sectionTitle === 'connections') {\n        let currentEdge = null, inNotes = false;\n        lines.slice(1).forEach(line => {\n          const connMatch = line.match(/^- (.+?)\\s*(?:\\(([^)]*)\\))?\\s*-->\\s*(.+?)\\s*(?:\\(([^)]*)\\))?$/);\n          if (connMatch) {\n            if (currentEdge) edges.push(currentEdge);\n            currentEdge = { from: connMatch[1].trim(), to: connMatch[3].trim(), fromPort: connMatch[2] || '', toPort: connMatch[4] || '', notes: [] };\n            inNotes = false;\n            return;\n          }\n          if (!currentEdge) return;\n          if (inNotes && line.match(/^\\s+-\\s+(.+)$/)) {\n            const noteMatch = line.match(/^\\s+-\\s+(.+)$/);\n            if (noteMatch) currentEdge.notes.push(noteMatch[1].trim().replace(/<br>/g, '\\n'));\n            return;\n          }\n          inNotes = false;\n          const propMatch = line.match(/^\\s+- \\*\\*(.+?):\\*\\*\\s*(.*)$/);\n          if (propMatch) {\n            const key = propMatch[1].trim().toLowerCase(), val = propMatch[2].trim();\n            switch (key) {\n              case 'id': currentEdge.id = val; break;\n              case 'label': currentEdge.label = val.replace(/<br>/g, '\\n'); break;\n              case 'color': currentEdge.color = val; break;\n              case 'width': currentEdge.width = parseFloat(val) || 4; break;\n              case 'direction': currentEdge.direction = val; break;\n              case 'routing': currentEdge.routing = val; break;\n              case 'type': currentEdge.type = val; break;\n              case 'line style': currentEdge.lineStyle = val; break;\n              case 'group id': currentEdge.groupId = val || null; break;\n              case 'points':\n                currentEdge.points = val.split(' ').map(p => { const [x, y] = p.split(',').map(Number); return { x, y }; }).filter(p => !isNaN(p.x) && !isNaN(p.y));\n                break;\n              case 'notes': inNotes = true; break;\n            }\n          }\n        });\n        if (currentEdge) edges.push(currentEdge);\n      } else if (sectionTitle === 'zones') {\n        let currentRect = null, inNotes = false;\n        lines.slice(1).forEach(line => {\n          const rectMatch = line.match(/^### (.+)$/);\n          if (rectMatch) {\n            if (currentRect) rects.push(currentRect);\n            currentRect = { id: rectMatch[1].trim(), notes: [] };\n            inNotes = false;\n            return;\n          }\n          if (!currentRect) return;\n          if (inNotes && line.match(/^\\s+-\\s+(.+)$/)) {\n            const noteMatch = line.match(/^\\s+-\\s+(.+)$/);\n            if (noteMatch) currentRect.notes.push(noteMatch[1].trim().replace(/<br>/g, '\\n'));\n            return;\n          }\n          inNotes = false;\n          const propMatch = line.match(/^- \\*\\*(.+?):\\*\\*\\s*(.*)$/);\n          if (propMatch) {\n            const key = propMatch[1].trim().toLowerCase(), val = propMatch[2].trim();\n            switch (key) {\n              case 'position': const posMatch = val.match(/(-?\\d+),\\s*(-?\\d+)/); if (posMatch) { currentRect.x = parseFloat(posMatch[1]); currentRect.y = parseFloat(posMatch[2]); } break;\n              case 'size': const sizeMatch = val.match(/(\\d+)\\s*x\\s*(\\d+)/); if (sizeMatch) { currentRect.width = parseFloat(sizeMatch[1]); currentRect.height = parseFloat(sizeMatch[2]); } break;\n              case 'color': currentRect.color = val; break;\n              case 'style': currentRect.style = val; break;\n              case 'line style': currentRect.lineStyle = val; break;\n              case 'border color': currentRect.borderColor = val; break;\n              case 'border width': currentRect.borderWidth = parseFloat(val) || 2; break;\n              case 'notes': inNotes = true; break;\n            }\n          }\n        });\n        if (currentRect) rects.push(currentRect);\n      } else if (sectionTitle === 'text labels') {\n        let currentText = null;\n        lines.slice(1).forEach(line => {\n          const textMatch = line.match(/^### (.+)$/);\n          if (textMatch) {\n            if (currentText) texts.push(currentText);\n            currentText = { id: textMatch[1].trim() };\n            return;\n          }\n          if (!currentText) return;\n          const propMatch = line.match(/^- \\*\\*(.+?):\\*\\*\\s*(.*)$/);\n          if (propMatch) {\n            const key = propMatch[1].trim().toLowerCase(), val = propMatch[2].trim();\n            switch (key) {\n              case 'content': currentText.content = val.replace(/<br>/g, '\\n'); break;\n              case 'position': const posMatch = val.match(/(-?\\d+),\\s*(-?\\d+)/); if (posMatch) { currentText.x = parseFloat(posMatch[1]); currentText.y = parseFloat(posMatch[2]); } break;\n              case 'font size': currentText.fontSize = parseFloat(val) || 18; break;\n              case 'color': currentText.color = val; break;\n              case 'font weight': currentText.fontWeight = val; break;\n              case 'font style': currentText.fontStyle = val; break;\n              case 'text align': currentText.textAlign = val; break;\n              case 'text decoration': currentText.textDecoration = val; break;\n              case 'background color': currentText.bgColor = val; break;\n              case 'background enabled': currentText.bgEnabled = val === 'true'; break;\n              case 'opacity': currentText.opacity = parseFloat(val) || 1; break;\n            }\n          }\n        });\n        if (currentText) texts.push(currentText);\n      }\n    });\n    const nodeCount = Object.keys(nodes).length, edgeCount = edges.length, rectCount = rects.length, textCount = texts.length;\n    if (nodeCount === 0 && edgeCount === 0 && rectCount === 0 && textCount === 0) {\n      alert('No valid topology data found in Markdown file.\\n\\nMake sure the file was exported from The One File.');\n      return;\n    }\n    let confirmMsg = `Import Markdown topology?\\n\\n- ${nodeCount} nodes\\n- ${edgeCount} connections\\n- ${rectCount} zones\\n- ${textCount} text labels\\n\\nThis will REPLACE all current data.`;\n    if (!confirm(confirmMsg)) return;\n    pushUndo('import markdown');\n    if (config && config.documentTabs) {\n      if (config.page) { Object.assign(PAGE_STATE, config.page); wieldThePower(); }\n      if (config.canvas) {\n        canvasState.zoom = config.canvas.zoom || 1;\n        canvasState.panX = config.canvas.panX || 0;\n        canvasState.panY = config.canvas.panY || 0;\n      }\n      if (config.documentTabs) {\n        documentTabs = config.documentTabs;\n        currentTabIndex = config.currentTabIndex || 0;\n      }\n      if (config.savedTopologyView) {\n        savedTopologyView = config.savedTopologyView;\n      }\n      if (config.encryptedSections) {\n        encryptedSections = config.encryptedSections;\n      }\n      if (config.auditLog && Array.isArray(config.auditLog)) {\n        auditLog = config.auditLog;\n        try {\n          localStorage.setItem(AUDIT_STORAGE_KEY, JSON.stringify(auditLog));\n        } catch (e) {\n          console.warn(\"Failed to sync audit log to localStorage:\", e);\n        }\n      }\n    } else if (config && config.pageState) {\n      Object.assign(PAGE_STATE, config.pageState);\n      wieldThePower();\n      if (config.canvasView) {\n        canvasState.zoom = config.canvasView.zoom || 1;\n        canvasState.panX = config.canvasView.panX || 0;\n        canvasState.panY = config.canvasView.panY || 0;\n      }\n    }\n    Object.keys(NODE_DATA).forEach(k => delete NODE_DATA[k]);\n    Object.keys(savedPositions).forEach(k => delete savedPositions[k]);\n    Object.keys(savedSizes).forEach(k => delete savedSizes[k]);\n    Object.keys(savedStyles).forEach(k => delete savedStyles[k]);\n    EDGE_DATA.list = [];\n    RECT_DATA.list = [];\n    TEXT_DATA.list = [];\n    Object.keys(EDGE_LEGEND).forEach(k => delete EDGE_LEGEND[k]);\n    Object.assign(EDGE_LEGEND, legend);\n    Object.entries(nodes).forEach(([id, node]) => {\n      NODE_DATA[id] = { name: node.name || '', ip: node.ip || '', role: node.role || '', shape: node.shape || 'circle', tags: node.tags || [], notes: node.notes || [], mac: node.mac || '', rackUnit: node.rackUnit || '', uHeight: node.uHeight || '1', layer: node.layer || 'physical', assignedRack: node.assignedRack || '', rackCapacity: node.rackCapacity || '', isRack: node.isRack || false, locked: node.locked || false, groupId: node.groupId || null };\n      savedPositions[id] = positions[id] || { x: 500, y: 300 };\n      if (sizes[id]) savedSizes[id] = sizes[id];\n      if (styles[id]) savedStyles[id] = styles[id];\n    });\n    let skippedEdges = 0;\n    edges.forEach(edge => {\n      if (!NODE_DATA[edge.from] || !NODE_DATA[edge.to]) {\n        console.warn(`Skipping orphan edge: ${edge.from} -> ${edge.to}`);\n        skippedEdges++;\n        return;\n      }\n      EDGE_DATA.list.push({ id: edge.id || `edge-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, from: edge.from, to: edge.to, fromPort: edge.fromPort || '', toPort: edge.toPort || '', label: edge.label || '', color: edge.color || '', width: edge.width || 4, direction: edge.direction || 'none', routing: edge.routing || 'curved', type: edge.type || 'main', lineStyle: edge.lineStyle || 'solid', groupId: edge.groupId || null, points: edge.points || [], notes: edge.notes || [] });\n    });\n    if (skippedEdges > 0) console.warn(`Total skipped orphan edges: ${skippedEdges}`);\n    rects.forEach(rect => {\n      RECT_DATA.list.push({ id: rect.id, x: rect.x || 0, y: rect.y || 0, width: rect.width || 100, height: rect.height || 100, color: rect.color || '#f97316', style: rect.style || 'filled', lineStyle: rect.lineStyle || 'solid', borderColor: rect.borderColor || '', borderWidth: rect.borderWidth !== undefined ? rect.borderWidth : 2, notes: rect.notes || [] });\n    });\n    texts.forEach(text => {\n      TEXT_DATA.list.push({ id: text.id, x: text.x || 0, y: text.y || 0, content: text.content || '', fontSize: text.fontSize || 18, color: text.color || '#e2e8f0', fontWeight: text.fontWeight || 'normal', fontStyle: text.fontStyle || 'normal', textAlign: text.textAlign || 'start', textDecoration: text.textDecoration || 'none', bgColor: text.bgColor || '#000000', bgEnabled: text.bgEnabled || false, opacity: text.opacity !== undefined ? text.opacity : 1 });\n    });\n    forgeTheTopology();\n    if (typeof forgeTheLegend === 'function') forgeTheLegend();\n    updateViewBox();\n    logAuditEvent('import', `Imported Markdown: ${file.name} (${nodeCount} nodes, ${edgeCount} connections)`);\n    alert(`Successfully imported:\\n- ${nodeCount} nodes\\n- ${edgeCount} connections\\n- ${rectCount} zones\\n- ${textCount} text labels`);\n     } catch (err) {\n    console.error('Markdown import error:', err);\n    alert('Failed to import Markdown: ' + err.message);\n     }\n\t});\n\tdocument.getElementById('import-json-file').addEventListener('change', async (e) => {\n\t  const file = e.target.files[0];\n\t  if (!file) return;\n\t  e.target.value = '';\n\t  const existingInput = document.getElementById('import-data-file');\n\t  if (existingInput) {\n\t\tconst dt = new DataTransfer();\n\t\tdt.items.add(file);\n\t\texistingInput.files = dt.files;\n\t\texistingInput.dispatchEvent(new Event('change'));\n\t  }\n\t});\n\tdocument.getElementById('mobile-export-btn')?.addEventListener('click', () => {\n\t  document.getElementById('mobile-export-modal').classList.add('active');\n\t  document.getElementById('topbar-menu').classList.remove('open');\n\t});\n\tdocument.getElementById('mobile-import-btn')?.addEventListener('click', () => {\n\t  document.getElementById('mobile-import-modal').classList.add('active');\n\t  document.getElementById('topbar-menu').classList.remove('open');\n\t});\n\tdocument.getElementById('mobile-export-modal')?.addEventListener('click', (e) => {\n\t  if (e.target.id === 'mobile-export-modal') e.target.classList.remove('active');\n\t});\n\tdocument.getElementById('mobile-import-modal')?.addEventListener('click', (e) => {\n\t  if (e.target.id === 'mobile-import-modal') e.target.classList.remove('active');\n\t});\n    </script>\n  \n</body></html>"
  },
  {
    "path": "demos/password-protected/the-one-file-homelab-demo.html",
    "content": "<!DOCTYPE html> \n <html lang=\"en\" style=\"--panel: #2f0e0e; --panel-alt: #10141b; --sidebar-bg: #10141b; --btn-bg: #0b0e13; --accent: #a75252; --danger: #f56565; --text-main: #e2e8f0; --text-soft: #94a3b8; --topbar-bg: rgba(9, 12, 20, 0.9); --topbar-border: #1f2533; --topbar-height: 112px; --sidebar-width: 350px; --mobile-footer-height: 40vh; --draw-toolbar-height: 0px; --btn-text: #e2e8f0; --tag-fill: #1e293b; --tag-text: #e2e8f0; --tag-border: #475569; --input-bg: #0b0e13; --input-text: #e2e8f0; --input-border: #1f2937; --input-font: Inter, system-ui, sans-serif; --input-font-size: 14px; --toolbar-bg: #441215; --toolbar-border: #1f2937; --toolbar-text: #94a3b8; --toolbar-btn-bg: #0b0e13; --toolbar-btn-text: #e2e8f0; --minimap-dots: #94a3b8; --canvas-hint-bg: #0f172a; --canvas-hint-color: #94a3b8; --node-fill: #1e293b; --node-stroke: #475569; --node-title: #e2e8f0; --node-sub: #94a3b8; --node-title-size: 18px; --node-sub-size: 13px; --node-font: Inter, system-ui, sans-serif; --default-edge: #475569; --selection-handle: #f59e0b; --selection-handle-size: 8px; --group-indicator: #4fd1c5;\"><head>\n    <meta charset=\"UTF-8\">\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>The One File</title>\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <!--\n      * ====================================================================================\n      * THE ONE FILE\n      * \"There can be only one\". A all in one file topology maker for enterprise or homelab\n      *\n      * This is your last backup when all others fail. A completely self contained\n      * network topology visualization tool that works as a single HTML file.\n      * Open it anywhere, anytime and the idea lives forever.\n      * ====================================================================================\n      -->\n<style>\n      :root {\n      color-scheme: dark;\n      --bg: #050608;\n      --panel: #0b0e13;\n      --panel-alt: #10141b;\n      --accent: #4fd1c5;\n      --danger: #f56565;\n      --text-main: #e2e8f0;\n      --text-soft: #94a3b8;\n      --edge-main: #475569;\n      --node-min: 35px;\n      --node-max: 70px;\n      --topbar-bg: rgba(9, 12, 20, 0.9);\n      --topbar-border: #1f2533;\n      }\n\t  html, body, svg, .map-container {\n      touch-action: none;\n      }\n      * {\n      box-sizing: border-box;\n      user-select: none;\n      }\n      input,\n      textarea,\n      [contenteditable=\"true\"] {\n      user-select: text;\n      -webkit-user-select: text;\n      }\n      body {\n      margin: 0;\n      font-family: system-ui, sans-serif;\n      background: radial-gradient(circle at top, #1e2532 0, #050608 70%);\n      color: var(--text-main);\n      display: flex;\n      flex-direction: column;\n      height: 100vh;\n      overflow: hidden;\n      }\n      header {\n      padding: 0 20px;\n      height: var(--topbar-height, 52px);\n      min-height: var(--topbar-height, 52px);\n      background: var(--topbar-bg);\n      backdrop-filter: blur(6px);\n      display: flex;\n      justify-content: space-between;\n      align-items: center;\n      border-bottom: 1px solid var(--topbar-border);\n      gap: 16px;\n      }\n      .title-block {\n      display: flex;\n      flex-direction: column;\n      gap: 6px;\n      min-width: 0;\n      }\n      header h1 {\n      font-size: clamp(22px, 3vw, 32px);\n      margin: 0;\n      white-space: nowrap;\n      text-overflow: ellipsis;\n      overflow: hidden;\n      }\n      .editable-page-title {\n      cursor: pointer;\n      transition: opacity 0.2s;\n      }\n      .editable-page-title:hover {\n      opacity: 0.7;\n      }\n      .save-row {\n      display: flex;\n      align-items: center;\n      gap: 8px;\n      flex-wrap: wrap;\n      }\n\t  .toggle-switch{position:relative;display:inline-block;min-width:44px !important;height:24px;flex-shrink:0;vertical-align:middle;}\n\t\t.toggle-switch input{opacity:0;width:0;height:0;position:absolute;}\n\t\t.toggle-slider{position:absolute;cursor:pointer;top:0;left:0;right:0;bottom:0;background:#475569;transition:.25s;border-radius:24px;}\n\t\t.toggle-slider:before{position:absolute;content:\"\";height:18px;width:18px;left:3px;bottom:3px;background:#e2e8f0;transition:.25s;border-radius:50%;}\n\t\t.toggle-switch input:checked+.toggle-slider{background:var(--accent);}\n\t\t.toggle-switch input:checked+.toggle-slider:before{transform:translateX(20px);}\n\t\t.anim-zone-row{display:flex;justify-content:space-between;align-items:center;padding:6px 0;}\n\t\t.anim-zone-row label{color:var(--text-main);font-size:14px;}\n\t\t.anim-zone-section{font-size:11px;color:var(--text-soft);margin:12px 0 6px;text-transform:uppercase;letter-spacing:0.05em;border-bottom:1px solid var(--edge-main);padding-bottom:4px;}\n\t\t.anim-zone-header{font-size:12px;color:var(--accent);margin-bottom:10px;text-transform:uppercase;letter-spacing:0.05em;font-weight:600;}\n      .save-btn {\n      padding: 6px 12px;\n      background: var(--accent);\n      color: var(--bg);\n      border: none;\n      border-radius: 6px;\n      cursor: pointer;\n      font-size: 14px;\n      font-weight: 600;\n      white-space: nowrap;\n      }\n      .save-btn:hover {\n      opacity: 0.9;\n      }\n      .help-icon {\n      width: 22px;\n      height: 22px;\n      border-radius: 50%;\n      border: 1px solid var(--edge-main);\n      display: inline-flex;\n      align-items: center;\n      justify-content: center;\n      font-size: 14px;\n      cursor: pointer;\n      color: var(--text-soft);\n      background: rgba(15, 23, 42, 0.9);\n      flex-shrink: 0;\n      }\n      .help-icon:hover {\n      color: var(--accent);\n      border-color: var(--accent);\n      }\n      #settings-btn {\n      background: var(--btn-bg, var(--panel));\n      color: var(--btn-text, var(--text-main));\n      border: 1px solid var(--edge-main);\n      padding: 6px 12px;\n      border-radius: 6px;\n      cursor: pointer;\n      font-size: 16px;\n      flex-shrink: 0;\n      }\n      #settings-btn:hover {\n      background: var(--accent);\n      color: var(--bg);\n      }\n      .header-resizer {\n      position: absolute;\n      bottom: 0;\n      left: 0;\n      right: 0;\n      height: 6px;\n      background: transparent;\n      cursor: row-resize;\n      z-index: 1000;\n      transition: background 0.2s;\n      }\n      .header-resizer:hover {\n      background: rgba(79, 209, 197, 0.3);\n      }\n      .header-resizer.resizing {\n      background: rgba(79, 209, 197, 0.5);\n      }\n      .sidebar-resizer {\n      position: absolute;\n      left: 0;\n      top: 0;\n      bottom: 0;\n      width: 6px;\n      background: transparent;\n      cursor: col-resize;\n      z-index: 1000;\n      transition: background 0.2s;\n      }\n      .sidebar-resizer:hover {\n      background: rgba(79, 209, 197, 0.3);\n      }\n      .sidebar-resizer.resizing {\n      background: rgba(79, 209, 197, 0.5);\n      }\n      .mobile-footer-resizer {\n      position: absolute;\n      top: 0;\n      left: 0;\n      right: 0;\n      height: 6px;\n      background: transparent;\n      cursor: row-resize;\n      z-index: 1000;\n      transition: background 0.2s;\n      display: none;\n      }\n      .mobile-footer-resizer:hover {\n      background: rgba(79, 209, 197, 0.3);\n      }\n      .mobile-footer-resizer.resizing {\n      background: rgba(79, 209, 197, 0.5);\n      }\n      @media (max-width: 900px) {\n      .mobile-footer-resizer {\n      display: block;\n      height: 12px;\n      }\n      .sidebar-resizer {\n      display: none;\n      }\n      .header-resizer {\n      height: 12px;\n      }\n      }\n      @media (pointer: coarse) {\n      .header-resizer {\n      height: 16px;\n      }\n      .mobile-footer-resizer {\n      height: 16px;\n      }\n      .sidebar-resizer {\n      width: 16px;\n      }\n      }\n      .resizer-icon {\n      position: absolute;\n      opacity: 0.5;\n      transition: opacity 0.2s, transform 0.2s;\n      pointer-events: none;\n      fill: var(--accent);\n      }\n      .header-resizer .resizer-icon {\n      left: 50%;\n      top: 50%;\n      transform: translate(-50%, -50%);\n      }\n      .sidebar-resizer .resizer-icon {\n      left: 50%;\n      top: 50%;\n      transform: translate(-50%, -50%);\n      }\n      .mobile-footer-resizer .resizer-icon {\n      left: 50%;\n      top: 50%;\n      transform: translate(-50%, -50%);\n      }\n      .header-resizer:hover .resizer-icon,\n      .sidebar-resizer:hover .resizer-icon,\n      .mobile-footer-resizer:hover .resizer-icon {\n      opacity: 1;\n      }\n      .header-resizer.resizing .resizer-icon,\n      .sidebar-resizer.resizing .resizer-icon,\n      .mobile-footer-resizer.resizing .resizer-icon {\n      opacity: 1;\n      transform: translate(-50%, -50%) scale(1.2);\n      }\n      body.resizing {\n      user-select: none;\n      }\n      body.resizing * {\n      cursor: inherit !important;\n      pointer-events: none;\n      }\n      header {\n      position: relative;\n      }\n      .details-panel {\n      position: relative;\n      }\n      main {\n      display: grid;\n      grid-template-columns: 1fr var(--sidebar-width, 350px);\n      flex: 1;\n      }\n      main.sidebar-collapsed {\n      grid-template-columns: 1fr 0;\n      }\n      @media (max-width: 900px) {\n      main {\n      grid-template-columns: 1fr;\n      grid-template-rows: calc(100vh - var(--topbar-height, 52px) - var(--mobile-footer-height, 40vh)) var(--mobile-footer-height, 40vh);\n      }\n      main.sidebar-collapsed {\n      grid-template-rows: 1fr 0;\n      }\n      .details-panel {\n      max-height: var(--mobile-footer-height, 40vh);\n      height: 100%;\n      }\n      }\n      .topology-panel {\n      background: var(--panel);\n      border-right: 1px solid #111827;\n      position: relative;\n      overflow: hidden;\n      }\n      .topology-toolbar {\n      position: absolute;\n      top: 10px;\n      right: 10px;\n      display: none;\n      align-items: center;\n      gap: 8px;\n      padding: 6px 10px;\n      background: var(--toolbar-bg, rgba(15, 23, 42, 0.92));\n      border: 1px solid var(--toolbar-border, #1f2937);\n      border-radius: 6px;\n      z-index: 99;\n      font-size: 13px;\n      color: var(--toolbar-text, #94a3b8);\n      }\n      .topology-toolbar label {\n      color: var(--toolbar-text, var(--text-soft));\n      }\n      .topology-toolbar select {\n      padding: 4px 6px;\n      background: var(--toolbar-btn-bg, var(--panel));\n      border: 1px solid var(--toolbar-border, var(--edge-main));\n      border-radius: 4px;\n      color: var(--toolbar-btn-text, var(--text-main));\n      font-size: 13px;\n      cursor: pointer;\n      }\n      .topology-toolbar button {\n      padding: 4px 10px;\n      background: var(--toolbar-btn-bg, var(--panel));\n      color: var(--toolbar-btn-text, var(--text-main));\n      border: 1px solid var(--toolbar-border, var(--edge-main));\n      border-radius: 4px;\n      cursor: pointer;\n      font-size: 13px;\n      font-weight: 600;\n      }\n      .topology-toolbar button:hover {\n      background: var(--accent);\n      color: var(--bg);\n      }\n      .bulk-toolbar-desktop,\n      .bulk-toolbar-mobile {\n      position: fixed;\n      bottom: 20px;\n      left: 50%;\n      transform: translateX(-50%);\n      z-index: 9999;\n      }\n      .bulk-toolbar-desktop {\n      position: absolute;\n      top: 10px;\n      right: 10px;\n      bottom: auto;\n      left: auto;\n      transform: none;\n      }\n      @media (min-width: 768px) {\n      .bulk-toolbar-mobile {\n      display: none !important;\n      }\n      }\n      .bulk-action-btn {\n      padding: 16px;\n      background: var(--panel-main);\n      border: 1px solid var(--edge-main);\n      border-radius: 8px;\n      color: var(--text-main);\n      font-size: 24px;\n      cursor: pointer;\n      text-align: center;\n      line-height: 1.2;\n      }\n      .bulk-action-btn:active {\n      transform: scale(0.95);\n      background: var(--accent);\n      }\n      .draw-toolbar {\n      position: absolute;\n      top: 10px;\n      left: 10px;\n      display: flex;\n      align-items: center;\n      gap: 6px;\n      padding: 6px 8px;\n      background: var(--toolbar-bg, rgba(15, 23, 42, 0.92));\n      border: 1px solid var(--toolbar-border, #1f2937);\n      border-radius: 6px;\n      z-index: 99;\n      font-size: 13px;\n      pointer-events: auto;\n      color: var(--toolbar-text, #94a3b8);\n      }\n      .draw-toolbar button {\n      padding: 4px 8px;\n      background: var(--toolbar-btn-bg, var(--panel));\n      color: var(--toolbar-btn-text, var(--text-main));\n      border: 1px solid var(--toolbar-border, var(--edge-main));\n      border-radius: 4px;\n      cursor: pointer;\n      font-size: 13px;\n      }\n      .draw-toolbar button:hover {\n      background: var(--accent);\n      color: var(--bg);\n      }\n      .draw-toolbar input[type=\"color\"] {\n      width: 30px;\n      height: 22px;\n      border-radius: 4px;\n      border: 1px solid var(--edge-main);\n      padding: 0;\n      background: transparent;\n      cursor: pointer;\n      }\n      .draw-toolbar select {\n      padding: 4px 6px;\n      background: var(--toolbar-btn-bg, var(--panel));\n      border: 1px solid var(--toolbar-border, var(--edge-main));\n      border-radius: 4px;\n      color: var(--toolbar-btn-text, var(--text-main));\n      font-size: 13px;\n      cursor: pointer;\n      }\n      .legend-container {\n      position: absolute;\n      left: 10px;\n      bottom: 10px;\n      display: flex;\n      flex-direction: column;\n      gap: 6px;\n      padding: 8px 10px;\n      background: var(--toolbar-bg, rgba(15, 23, 42, 0.92));\n      border: 1px solid var(--toolbar-border, #1f2937);\n      border-radius: 6px;\n      font-size: 12px;\n      z-index: 20;\n      max-width: 260px;\n      pointer-events: auto;\n      color: var(--toolbar-text, #94a3b8);\n      }\n      .legend-container {\n      padding-right: 22px;\n      }\n      @media (max-width: 900px) {\n      .legend-container {\n      height:250px;\n      overflow-y: auto;\n      z-index:99;\n      }\n      }\n      @media (max-width: 900px) {\n      .bulk-toolbar-desktop,\n      .topology-toolbar {\n      display: none !important;\n      }\n      }\n      .legend-close-btn {\n      position: absolute;\n      top: 5px;\n      right: 5px;\n      width: 18px;\n      height: 18px;\n      border-radius: 999px;\n      border: 1px solid var(--edge-main);\n      background: var(--panel-alt);\n      color: var(--text-soft);\n      font-size: 11px;\n      display: flex;\n      align-items: center;\n      justify-content: center;\n      cursor: pointer;\n      }\n      .legend-close-btn:hover {\n      background: var(--danger);\n      color: #fff;\n      }\n      .legend-mini-btn {\n      position: absolute;\n      left: 10px;\n      padding: 4px 8px;\n      border-radius: 4px;\n      border: 1px solid var(--toolbar-border, #1f2937);\n      background: var(--toolbar-bg, rgba(15, 23, 42, 0.92));\n      color: var(--toolbar-btn-text, var(--text-main));\n      font-size: 11px;\n      cursor: pointer;\n      z-index: 20;\n      display: none;\n      transition: all 0.2s;\n      }\n      .legend-mini-btn:hover,\n      .legend-mini-btn:active {\n      background: var(--accent);\n      color: var(--bg);\n      border-color: var(--accent);\n      }\n      .legend-title {\n      font-size: 11px;\n      text-transform: uppercase;\n      letter-spacing: 0.06em;\n      color: var(--toolbar-text, var(--text-soft));\n      margin-bottom: 2px;\n      }\n\t  .fov-group {\n        transition: opacity 0.3s ease;\n      }\n      g[data-node-id]:not(:hover) .fov-group {\n        opacity: 0.7;\n      }\n      g[data-node-id]:hover .fov-group {\n        opacity: 1;\n      }\n      .legend-item {\n      display: flex;\n      align-items: center;\n      gap: 8px;\n      }\n      .legend-swatch {\n      width: 14px;\n      height: 14px;\n      border-radius: 3px;\n      border: 1px solid #020617;\n      flex-shrink: 0;\n      }\n      .legend-label {\n      outline: none;\n      cursor: text;\n      flex: 1;\n      min-width: 60px;\n      user-select: text;\n      -webkit-user-select: text;\n      }\n      .legend-label.editing {\n      border-bottom: 1px dashed var(--accent);\n      }\n      .canvas-viewport {\n      position: absolute;\n      top: 0;\n      left: 0;\n      right: 0;\n      bottom: 0;\n      overflow: hidden;\n      }\n      .canvas-viewport.panning {\n      cursor: grabbing !important;\n      }\n      .canvas-viewport.panning * {\n      cursor: grabbing !important;\n      }\n      .canvas-viewport svg {\n      width: 100%;\n      height: 100%;\n      display: block; \n      }\n      .zoom-toolbar {\n      display: flex;\n      gap: 4px;\n      padding: 6px;\n      background: var(--toolbar-bg, rgba(15, 23, 42, 0.92));\n      border: 1px solid var(--toolbar-border, #1f2937);\n      border-radius: 6px;\n      }\n      .zoom-toolbar button {\n      width: 32px;\n      height: 32px;\n      padding: 0;\n      background: var(--toolbar-btn-bg, var(--panel));\n      color: var(--toolbar-btn-text, var(--text-main));\n      border: 1px solid var(--toolbar-border, var(--edge-main));\n      border-radius: 4px;\n      cursor: pointer;\n      font-size: 18px;\n      font-weight: bold;\n      display: flex;\n      align-items: center;\n      justify-content: center;\n      transition: all 0.15s;\n      }\n      .zoom-toolbar button:hover {\n      background: var(--accent);\n      color: var(--bg);\n      }\n      .zoom-toolbar .zoom-level {\n      font-size: 11px;\n      color: var(--toolbar-text, var(--text-soft));\n      text-align: center;\n      padding: 2px 0;\n      min-width: 32px;\n      }\n      .zoom-toolbar .divider {\n      height: 1px;\n      background: var(--toolbar-border, var(--edge-main));\n      margin: 2px 0;\n      }\n      .minimap-zoom-wrapper {\n      position: absolute;\n      bottom: 10px;\n      right: 10px;\n      z-index: 99;\n      }\n      .minimap-container {\n      background: var(--toolbar-bg, rgba(15, 23, 42, 0.92));\n      border: 1px solid var(--toolbar-border, #1f2937);\n      border-radius: 6px;\n      overflow: hidden;\n      margin-bottom: 4px;\n      }\n      .minimap-container svg {\n      width: 100%;\n      height: 100%;\n      }\n\t.minimap-container,\n\t.node-panel,\n\t.edge-panel,\n\t#canvas-viewport {\n\t  contain: layout paint;\n\t}\t  \n\t.low-zoom .node-group .node-circle,\n\t.low-zoom .node-group.active .node-circle,\n\t.low-zoom .node-group.selected .node-circle {\n\t  filter: none !important;\n\t  animation: none !important;\n\t}\t\n      .minimap-viewport {\n      fill: rgba(79, 209, 197, 0.2);\n      stroke: var(--accent);\n      stroke-width: 2;\n      cursor: move;\n      }\n      .minimap-node {\n      fill: var(--minimap-dots, var(--text-soft));\n      }\n\t  .minimap-edge {\n      stroke: var(--edge-main);\n      stroke-width: 8px;\n      fill: none;\n      }\n\t  .minimap-wall {\n      pointer-events: none;\n      }\n      .minimap-rect {\n      pointer-events: none;\n      }\t  \n      .minimap-close-btn {\n      position: absolute;\n      top: 2px;\n      right: 2px;\n      width: 18px;\n      height: 18px;\n      background: var(--toolbar-btn-bg, rgba(15, 23, 42, 0.9));\n      border: 1px solid var(--toolbar-border, var(--edge-main));\n      border-radius: 3px;\n      color: var(--toolbar-text, var(--text-soft));\n      font-size: 11px;\n      cursor: pointer;\n      display: flex;\n      align-items: center;\n      justify-content: center;\n      z-index: 10;\n      padding: 0;\n      line-height: 1;\n      }\n      .minimap-close-btn:hover {\n      background: var(--danger);\n      color: white;\n      border-color: var(--danger);\n      }\n      .toolbar-close-btn {\n      width: 24px;\n      height: 24px;\n      background: var(--toolbar-btn-bg, rgba(15, 23, 42, 0.9));\n      border: 1px solid var(--toolbar-border, var(--edge-main));\n      border-radius: 4px;\n      color: var(--toolbar-text, var(--text-soft));\n      font-size: 12px;\n      cursor: pointer;\n      display: flex;\n      align-items: center;\n      justify-content: center;\n      padding: 0;\n      line-height: 1;\n      flex-shrink: 0;\n      }\n      .toolbar-close-btn:hover {\n      background: var(--danger);\n      color: white;\n      border-color: var(--danger);\n      }\n      .canvas-hint {\n      position: absolute;\n      top: 50px;\n      left: 50%;\n      transform: translateX(-50%);\n      padding: 8px 16px;\n      background: rgba(15, 23, 42, 0.92);\n      border: 1px solid #1f2937;\n      border-radius: 6px;\n      font-size: 12px;\n      color: var(--text-soft);\n      z-index: 18;\n      pointer-events: none;\n      opacity: 0;\n      transition: opacity 0.3s;\n      }\n      .canvas-hint.visible {\n      opacity: 1;\n      pointer-events: auto;\n      }\n      .edge {\n      stroke: var(--edge-main);\n      stroke-width: 4;\n      opacity: 0.75;\n      transition: 0.25s ease-in-out;\n      cursor: pointer;\n      }\n      .edge.backup {\n      stroke: var(--danger);\n      stroke-width: 5;\n      }\n      .edge.active {\n      opacity: 1;\n      stroke-width: 7;\n      filter: drop-shadow(0 0 8px var(--accent, #4fd1c5));\n      }\n      .rect-group.active .rect-shape {\n      filter: drop-shadow(0 0 10px var(--accent, #4fd1c5));\n      animation: pulse 1.2s infinite ease-in-out;\n      }\n      .text-element.active {\n      filter: drop-shadow(0 0 10px var(--accent, #4fd1c5));\n      animation: pulse 1.2s infinite ease-in-out;\n      }\n      .free-preview {\n      fill: none;\n      stroke-dasharray: 4 4;\n      pointer-events: none;\n      }\n@keyframes edge-flow-arrow {\n  0% { offset-distance: 0%; }\n  100% { offset-distance: 100%; }\n}\n@keyframes edge-flow-arrow-reverse {\n  0% { offset-distance: 100%; }\n  100% { offset-distance: 0%; }\n}\n.edge-arrow-forward {\n  offset-rotate: auto;\n  animation: edge-flow-arrow 1.5s linear infinite;\n}\n.edge-arrow-backward {\n  offset-rotate: auto 180deg;\n  animation: edge-flow-arrow-reverse 1.5s linear infinite;\n}\n      .free-point {\n      fill: #e5e7eb;\n      stroke: #0f172a;\n      stroke-width: 1.5;\n      cursor: grab;\n      }\n      .node-circle {\n      fill: var(--node-fill, #1e293b);\n      stroke: var(--node-stroke, #475569);\n      stroke-width: 2;\n      transition: 0.25s ease;\n      transform-origin: center center;\n      }\n      .node-hit-area {\n      cursor: grab;\n      pointer-events: all;\n      }\n      .node-group:hover .node-circle {\n      filter: drop-shadow(0 0 10px rgba(79, 209, 197, 0.45));\n      }\n      .node-group.active .node-circle {\n      stroke: var(--accent);\n      animation: pulse 1.2s infinite ease-in-out;\n      }\n      .node-group.selected .node-circle {\n      stroke: var(--selection-handle, #f59e0b);\n      stroke-width: 3;\n      }\n      .node-group.search-highlight .node-circle {\n      stroke: #10b981;\n      stroke-width: 3;\n      filter: drop-shadow(0 0 8px #10b981);\n      }\n\t  .node-group.search-faded {\n\t\topacity: 0.15;\n\t\tpointer-events: none;\n\t\t}\n\t\t.edge-group.search-faded {\n\t\topacity: 0.1;\n\t\t}\n\t  .node-group.search-faded {\n      opacity: 0.15;\n      pointer-events: none;\n      }\n      .edge-group.search-faded {\n      opacity: 0.1;\n      }\n      @keyframes pulse {\n      0% {\n      filter: drop-shadow(0 0 4px #4fd1c5);\n      }\n      50% {\n      filter: drop-shadow(0 0 14px #4fd1c5);\n      }\n      100% {\n      filter: drop-shadow(0 0 4px #4fd1c5);\n      }\n      }\n      @keyframes done-pulse {\n        0%, 100% {\n          transform: scale(1);\n          box-shadow: 0 0 0 0 rgba(79, 209, 197, 0.7);\n        }\n        50% {\n          transform: scale(1.05);\n          box-shadow: 0 0 0 8px rgba(79, 209, 197, 0);\n        }\n      }\n      .done-btn-active {\n        animation: done-pulse 1.5s ease-in-out infinite;\n        background: var(--accent) !important;\n        color: var(--bg) !important;\n      }\n      .node-label {\n      fill: var(--text-main);\n      font-size: 18px;\n      text-anchor: middle;\n      font-weight: 600;\n      }\n      .node-sub {\n      fill: var(--text-soft);\n      font-size: 13px;\n      text-anchor: middle;\n      }\n      @media (max-width: 1024px) {\n      .node-label {\n      font-size: 28px;\n      }\n      .node-sub {\n      font-size: 20px;\n      }\n      }\n      @media (max-width: 768px) {\n      .node-label {\n      font-size: 70px;\n      }\n      .node-sub {\n      font-size: 50px;\n      }\n      }\n      @media (max-width: 380px) {\n      .node-label {\n      font-size: 60px;\n      }\n      .node-sub {\n      font-size: 42px;\n      }\n      }\n      .details-panel {\n      background: var(--sidebar-bg, var(--panel-alt));\n      padding: 22px;\n      padding-bottom: 80px;\n      overflow-y: auto;\n      display: flex;\n      flex-direction: column;\n      gap: 15px;\n      position: relative;\n      transition:\n      width 0.3s ease,\n      min-width 0.3s ease,\n      padding 0.3s ease,\n      opacity 0.3s ease;\n      }\n      .details-panel {\n      min-width: 260px !important;\n      }\n      .details-panel {\n      overflow-y: auto !important;\n      overflow-x: hidden;\n      }\n      .details-panel.collapsed {\n      width: 0 !important;\n      min-width: 0 !important;\n      padding: 0 !important;\n      overflow: hidden;\n      opacity: 0;\n      }\n      body {\n      overflow-x: hidden;\n      overflow-y: hidden;\n      }\n      main {\n      overflow-x: hidden;\n      overflow-y: visible;\n      }\n      .details-panel {\n      overflow-y: auto !important;\n      overflow-x: hidden;\n      }\n      .details-panel.collapsed {\n      min-width: 0 !important;\n      }\n      .sidebar-toggle {\n      position: absolute;\n      left: -16px;\n      top: 50%;\n      transform: translateY(-50%);\n      width: 16px;\n      height: 60px;\n      background: var(--sidebar-bg, var(--panel-alt));\n      border: 1px solid var(--edge-main);\n      border-right: none;\n      border-radius: 8px 0 0 8px;\n      cursor: pointer;\n      display: flex;\n      align-items: center;\n      justify-content: center;\n      color: var(--text-soft);\n      font-size: 10px;\n      z-index: 25;\n      transition:\n      background 0.2s,\n      color 0.2s;\n      }\n      .sidebar-toggle:hover {\n      background: var(--accent);\n      color: var(--bg);\n      }\n      .sidebar-toggle.collapsed {\n      left: 0;\n      border-right: 1px solid var(--edge-main);\n      border-radius: 0 8px 8px 0;\n      position: fixed;\n      right: 0;\n      left: auto;\n      }\n      .details-name {\n      font-size: clamp(22px, 2.5vw, 30px);\n      font-weight: 700;\n      }\n      .details-ip {\n      font-size: clamp(16px, 2vw, 22px);\n      color: var(--text-soft);\n      }\n      .details-role {\n      font-size: clamp(16px, 2vw, 22px);\n      color: var(--accent);\n      }\n      .details-info-row {\n      display: flex;\n      align-items: center;\n      margin: 6px 0;\n      }\n      .size-controls {\n      display: flex;\n      gap: 10px;\n      align-items: center;\n      margin-top: 10px;\n      }\n      .size-controls label {\n      font-size: clamp(14px, 1.6vw, 18px);\n      color: var(--text-soft);\n      }\n      .size-controls input[type=\"range\"] {\n      flex: 1;\n      accent-color: var(--accent);\n      }\n      .size-controls button {\n      padding: 6px 12px;\n      background: var(--panel);\n      color: var(--text-main);\n      border: 1px solid var(--edge-main);\n      border-radius: 4px;\n      cursor: pointer;\n      font-size: clamp(12px, 1.4vw, 16px);\n      }\n      .size-controls button:hover {\n      background: var(--accent);\n      color: var(--bg);\n      }\n      .style-section {\n      margin-top: 15px;\n      padding-top: 15px;\n      border-top: 1px solid var(--edge-main);\n      }\n      .style-section summary {\n      cursor: pointer;\n      font-size: clamp(14px, 1.6vw, 20px);\n      text-transform: uppercase;\n      color: var(--text-soft);\n      list-style: none;\n      display: flex;\n      justify-content: space-between;\n      align-items: center;\n      user-select: none;\n      }\n      .style-section summary::-webkit-details-marker {\n      display: none;\n      }\n      .password-overlay {\n      position: fixed;\n      top: 0;\n      left: 0;\n      width: 100%;\n      height: 100%;\n      background: #ffffff;\n      z-index: 9999;\n      }\n      .style-section summary::after {\n      content: \"▼\";\n      transition: transform 0.2s;\n      }\n      .style-section[open] summary::after {\n      transform: rotate(180deg);\n      }\n.style-content {\n  display: grid;\n  grid-template-columns: 1fr auto;\n  gap: 8px 12px;\n  align-items: center;\n  justify-items: end;\n}\n.style-content label {\n  justify-self: start;\n}\n.style-row {\n  display: contents;\n}\n#edge-panel .style-row,\n#rect-panel .style-row,\n#text-panel .style-row {\n  display: flex;\n  align-items: center;\n  gap: 12px;\n  margin-bottom: 8px;\n}\n#edge-panel .style-row label,\n#rect-panel .style-row label,\n#text-panel .style-row label {\n  min-width: 80px;\n}\nbutton:disabled {\n  opacity: 0.5;\n  cursor: not-allowed;\n}\n      .style-row label {\n      font-size: clamp(13px, 1.5vw, 17px);\n      color: var(--text-soft);\n      min-width: 80px;\n      }\n      .style-row input[type=\"color\"] {\n      width: 50px;\n      height: 30px;\n      border: 1px solid var(--edge-main);\n      border-radius: 4px;\n      cursor: pointer;\n      background: transparent;\n      }\n      .style-row input[type=\"number\"] {\n      width: 70px;\n      padding: 4px 8px;\n      background: var(--panel);\n      border: 1px solid var(--edge-main);\n      border-radius: 4px;\n      color: var(--text-main);\n      font-size: clamp(12px, 1.4vw, 16px);\n      }\n      .style-row select {\n      flex: 1;\n      padding: 4px 8px;\n      background: var(--panel);\n      border: 1px solid var(--edge-main);\n      border-radius: 4px;\n      color: var(--text-main);\n      font-size: clamp(12px, 1.4vw, 16px);\n      cursor: pointer;\n      }\n      .editable-text {\n      cursor: pointer;\n      transition: opacity 0.2s;\n      }\n      .editable-text:hover {\n      opacity: 0.7;\n      }\n      .modal {\n\t  display: none;\n\t  position: fixed;\n\t  top: 0;\n\t  left: 0;\n\t  width: 100%;\n\t  height: 100%;\n\t  z-index: 999999;\n\t  justify-content: center;\n\t  align-items: center;\n\t  overflow: auto;\n\t  }\n\t  .modal.active {\n\t  display: inline-grid;\n\t  }\n\t  .modal-content {\n\t  background: var(--panel-alt);\n\t  padding: 25px;\n\t  border-radius: 8px;\n\t  border: 1px solid var(--edge-main);\n\t  min-width: 300px;\n\t  max-width: 90%;\n\t  }\n      .modal-content h3 {\n      margin: 0 0 15px 0;\n      color: var(--text-main);\n      font-size: clamp(18px, 2vw, 24px);\n      }\n      .modal-content p {\n      color: var(--text-soft);\n      font-size: clamp(14px, 1.6vw, 18px);\n      margin: 0 0 20px 0;\n      }\n      .modal-content input:not([type=\"color\"]),\n      .modal-content select {\n      width: 100%;\n      padding: 10px;\n      background: var(--panel);\n      border: 1px solid var(--edge-main);\n      border-radius: 4px;\n      color: var(--text-main);\n      font-size: clamp(14px, 1.6vw, 18px);\n      margin-bottom: 15px;\n      user-select: text;\n      -webkit-user-select: text;\n      }\n      .modal-content input[type=\"color\"] {\n      width: 60px;\n      height: 36px;\n      padding: 2px;\n      border: 2px solid var(--edge-main);\n      border-radius: 6px;\n      cursor: pointer;\n      background: none;\n      -webkit-appearance: none;\n      appearance: none;\n      }\n      .modal-content input[type=\"color\"]::-webkit-color-swatch-wrapper {\n      padding: 0;\n      }\n      .modal-content input[type=\"color\"]::-webkit-color-swatch {\n      border: none;\n      border-radius: 4px;\n      }\n      .modal-buttons {\n      display: flex;\n      gap: 10px;\n      justify-content: flex-end;\n      margin-top: 10px;\n      }\n      .modal-buttons button {\n      padding: 8px 16px;\n      border: none;\n      border-radius: 4px;\n      cursor: pointer;\n      font-size: clamp(14px, 1.6vw, 18px);\n      font-weight: 600;\n      }\n      .modal-buttons .btn-cancel {\n      background: var(--panel);\n      color: var(--text-main);\n      border: 1px solid var(--edge-main);\n      }\n      .modal-buttons .btn-save {\n      background: var(--accent);\n      color: var(--bg);\n      }\n      .modal-buttons .btn-delete {\n      background: var(--danger);\n      color: white;\n      }\n      .confirm-modal p {\n      color: var(--text-soft);\n      font-size: clamp(14px, 1.6vw, 18px);\n      margin: 0 0 20px 0;\n      }\n      .context-menu-item {\n      transition: background 0.15s;\n      }\n      .context-menu-item:hover {\n      background: var(--panel);\n      }\n      .badge-row {\n      display: flex;\n      flex-wrap: wrap;\n      gap: 8px;\n      }\n      .badge {\n      background: var(--tag-fill, #1e293b);\n      color: var(--tag-text, #e2e8f0);\n      border: 1px solid var(--tag-border, var(--edge-main));\n      padding: 5px 12px;\n      border-radius: 22px;\n      font-size: clamp(12px, 1.4vw, 18px);\n      }\n      .badge.wg {\n      color: var(--accent);\n      border-color: var(--accent);\n      }\n      input[type=\"text\"],\n      input[type=\"number\"],\n      input[type=\"password\"],\n      textarea,\n      select {\n      background: var(--input-bg, #0b0e13) !important;\n      color: var(--input-text, #e2e8f0) !important;\n      border-color: var(--input-border, #1f2937) !important;\n      font-family: var(--input-font, Inter, system-ui, sans-serif) !important;\n      font-size: var(--input-font-size, 14px) !important;\n      }\n      .section-label {\n      margin-top: 8px;\n      font-size: clamp(14px, 1.6vw, 20px);\n      text-transform: uppercase;\n      color: var(--text-soft);\n      }\n      .list li {\n      margin: 6px 0;\n      font-size: clamp(14px, 1.6vw, 20px);\n      cursor: pointer;\n      display: flex;\n      justify-content: space-between;\n      align-items: center;\n      gap: 10px;\n      }\n      .list li:hover {\n      color: var(--accent);\n      }\n      .delete-note {\n      color: var(--danger);\n      font-size: 18px;\n      cursor: pointer;\n      opacity: 0;\n      transition: opacity 0.2s;\n      flex-shrink: 0;\n      }\n      .list li:hover .delete-note {\n      opacity: 1;\n      }\n      .editing {\n      outline: 2px solid var(--accent);\n      background: #0d141f;\n      padding: 4px;\n      border-radius: 6px;\n      user-select: text;\n      -webkit-user-select: text;\n      }\n\t  .fov-group {\n  transition: opacity 0.3s ease;\n}\ng[data-node-id]:not(:hover) .fov-group {\n  opacity: 0.6;\n}\ng[data-node-id]:hover .fov-group {\n  opacity: 1;\n}\n      .version-item {\n      display: flex;\n      justify-content: space-between;\n      align-items: center;\n      padding: 12px;\n      background: var(--panel);\n      border: 1px solid var(--edge-main);\n      border-radius: 6px;\n      cursor: pointer;\n      transition: all 0.2s;\n      }\n      .version-item:hover {\n      background: var(--panel-alt);\n      border-color: var(--accent);\n      }\n      .version-item.current {\n      border-color: var(--accent);\n      background: rgba(79, 209, 197, 0.1);\n      }\n      .version-info {\n      flex: 1;\n      }\n      .version-info .timestamp {\n      font-size: 13px;\n      color: var(--text-main);\n      font-weight: 600;\n      }\n      .version-info .details {\n      font-size: 11px;\n      color: var(--text-soft);\n      margin-top: 4px;\n      }\n      .version-actions {\n      display: flex;\n      gap: 6px;\n      }\n      .audit-entry {\n      padding: 8px 12px;\n      background: var(--panel);\n      border-left: 3px solid var(--edge-main);\n      border-radius: 4px;\n      font-size: 12px;\n      }\n      .audit-entry.node { border-left-color: #4fd1c5; }\n      .audit-entry.connection { border-left-color: #9f7aea; }\n      .audit-entry.style { border-left-color: #ed8936; }\n      .audit-entry.rack { border-left-color: #48bb78; }\n      .audit-entry.layer { border-left-color: #4299e1; }\n      .audit-entry .time {\n      color: var(--text-soft);\n      font-size: 10px;\n      }\n      .audit-entry .action {\n      color: var(--text-main);\n      font-weight: 500;\n      }\n      .tab-item {\n      display: flex;\n      justify-content: space-between;\n      align-items: center;\n      padding: 12px;\n      background: var(--panel);\n      border: 1px solid var(--edge-main);\n      border-radius: 6px;\n      cursor: pointer;\n      transition: all 0.2s;\n      }\n      .tab-item:hover {\n      background: var(--panel-alt);\n      }\n      .tab-item.active {\n      border-color: var(--accent);\n      background: rgba(79, 209, 197, 0.1);\n      }\n      .tab-item .tab-name {\n      font-weight: 600;\n      color: var(--text-main);\n      flex: 1;\n      }\n      .tab-item .tab-stats {\n      font-size: 11px;\n      color: var(--text-soft);\n      margin-right: 10px;\n      }\n      .tab-actions {\n      display: flex;\n      gap: 6px;\n      }\n      .secret-item {\n      display: flex;\n      justify-content: space-between;\n      align-items: center;\n      padding: 12px;\n      background: var(--panel);\n      border: 1px solid var(--edge-main);\n      border-radius: 6px;\n      border-left: 3px solid var(--danger);\n      }\n      .secret-item .secret-name {\n      font-weight: 600;\n      color: var(--text-main);\n      flex: 1;\n      }\n      .secret-item .secret-status {\n      font-size: 11px;\n      color: var(--text-soft);\n      margin-right: 10px;\n      }\n      .mobile-menu-btn {\n      display: none;\n      background: var(--panel);\n      border: 1px solid var(--edge-main);\n      color: var(--text-main);\n      font-size: 22px;\n      padding: 6px 12px;\n      border-radius: 6px;\n      cursor: pointer;\n      }\n      #topbar-menu {\n      gap: 8px;\n      }\n      @media (min-width: 721px) {\n      #topbar-menu {\n      display: flex !important;\n      }\n      }\n      @media (max-width: 720px) {\n      #topbar-menu {\n      display: none;\n      }\n      #topbar-menu.open {\n      display: flex;\n      }\n      }\n      @media (max-width: 720px) {\n      .mobile-menu-btn {\n      display: block;\n      }\n      #topbar-menu {\n      position: absolute;\n      top: var(--topbar-height);\n      right: 0;\n      background: var(--panel-alt);\n      border-left: 1px solid var(--topbar-border);\n      border-bottom: 1px solid var(--topbar-border);\n      padding: 12px;\n      display: none;\n      flex-direction: column;\n      width: 180px;\n      z-index: 999;\n      }\n      #topbar-menu.open {\n      display: flex;\n      }\n      header {\n      position: relative;\n      z-index: 9999;\n      }\n      }\n      @media (max-width: 720px) {\n      .draw-toolbar {\n      position: absolute;\n      top: auto;\n      bottom: auto;\n      left: 10px !important;\n      right: auto !important;\n      width: auto;\n      gap: 6px;\n      padding: 8px;\n      }\n      .topology-toolbar {\n      position: absolute;\n      top: auto;\n      bottom: auto;\n      width: auto;\n      gap: 6px;\n      padding: 8px;\n      }\n      .draw-toolbar {\n      top: 10px !important;\n      z-index:99;\n      }\n      .topology-toolbar {\n      z-index:99;\n      top: calc(10px + var(--draw-toolbar-height, 50px)) !important;\n      }\n      .canvas-hint {\n      top: calc(10px + 120px);\n      }\n      #search-input {\n      width: 100%;\n      }\n      }\n\t  body.view-only-mode:not(.view-only-inspect) #node-panel,\n      body.view-only-mode:not(.view-only-inspect) #edge-panel,\n      body.view-only-mode:not(.view-only-inspect) #text-panel,\n      body.view-only-mode:not(.view-only-inspect) #rect-panel {\n        display: none !important;\n      }\n      body.view-only-mode .node-group,\n      body.view-only-mode .edge,\n      body.view-only-mode .rect-group,\n      body.view-only-mode .text-element {\n        cursor: default !important;\n      }\n      body.view-only-mode #bulk-toolbar,\n      body.view-only-mode #bulk-toolbar-mobile,\n      body.view-only-mode #bulk-actions-modal {\n        display: none !important;\n      }\n      body.view-only-mode::after {\n        content: \"VIEW ONLY • tap 5× to inspect\";\n        position: fixed;\n        bottom: 20px;\n        left: 50%;\n        transform: translateX(-50%);\n        background: rgba(245, 101, 101, 0.9);\n        color: white;\n        padding: 6px 16px;\n        border-radius: 20px;\n        font-size: 12px;\n        font-weight: 600;\n        letter-spacing: 1px;\n        z-index: 9999;\n        pointer-events: none;\n      }\n.dropdown {\n  position: relative;\n  display: inline-block;\n}\n.dropdown-btn {\n  padding: 6px 12px;\n  background: var(--btn-bg, var(--panel));\n  color: var(--btn-text, var(--text-main));\n  border: 1px solid var(--edge-main);\n  border-radius: 6px;\n  cursor: pointer;\n  font-size: 14px;\n  font-weight: 600;\n  white-space: nowrap;\n}\n.dropdown-btn:hover {\n  background: var(--accent);\n  color: var(--bg);\n}\n.dropdown-menu {\n  display: none;\n  position: absolute;\n  top: 100%;\n  left: 0;\n  min-width: 180px;\n  background: var(--panel);\n  border: 1px solid var(--edge-main);\n  border-radius: 6px;\n  box-shadow: 0 4px 20px rgba(0,0,0,0.4);\n  z-index: 100000;\n  margin-top: 4px;\n  overflow: hidden;\n}\nheader {\n  z-index: 99999;\n  position: relative;\n}\n.dropdown-menu.open {\n  display: block;\n}\n.dropdown-menu button {\n  display: block;\n  width: 100%;\n  padding: 10px 14px;\n  background: none;\n  border: none;\n  color: var(--text-main);\n  text-align: left;\n  cursor: pointer;\n  font-size: 14px;\n}\n.dropdown-menu button:hover {\n  background: var(--panel-alt);\n  color: var(--accent);\n}\n.dropdown-divider {\n  height: 1px;\n  background: var(--edge-main);\n  margin: 4px 0;\n}\n@media (max-width: 900px) {\n  .save-row .dropdown {\n    display: none !important;\n  }\n}\n\t@media print {\n\t  @page {\n\t\tsize: landscape;\n\t\tmargin: 0.5cm;\n\t  }\n\t  html, body {\n\t\twidth: 100%;\n\t\theight: 100%;\n\t\tmargin: 0;\n\t\tpadding: 0;\n\t\toverflow: visible !important;\n\t  }\n\t  body * {\n\t\tvisibility: hidden;\n\t  }\n\t  #canvas-viewport,\n\t  #canvas-viewport *,\n\t  #map,\n\t  #map * {\n\t\tvisibility: visible;\n\t  }\n\t  #canvas-viewport {\n\t\tposition: absolute !important;\n\t\tleft: 0 !important;\n\t\ttop: 0 !important;\n\t\tright: 0 !important;\n\t\tbottom: 0 !important;\n\t\twidth: 100vw !important;\n\t\theight: 100vh !important;\n\t\toverflow: visible !important;\n\t  }\n\t  #map {\n\t\tposition: absolute !important;\n\t\tleft: 0 !important;\n\t\ttop: 0 !important;\n\t\twidth: 100% !important;\n\t\theight: 100% !important;\n\t\tbackground: white !important;\n\t\tbackground-image: none !important;\n\t  }\n\t  #canvas-grid {\n\t\tdisplay: none !important;\n\t  }\n\t  main, .topology-panel {\n\t\tdisplay: block !important;\n\t\tposition: static !important;\n\t\toverflow: visible !important;\n\t  }\n\n\t  #map .node-hit-area,\n\t  #map .group-indicator,\n\t  #map .lock-indicator,\n\t  #map .fov-group,\n\t  #map .edge-arrow-forward,\n\t  #map .edge-arrow-backward {\n\t\tdisplay: none !important;\n\t  }\n\n\t  #map .node-circle,\n\t  #map .node-shape {\n\t\tfill: white !important;\n\t\tstroke: #000 !important;\n\t\tstroke-width: 2px !important;\n\t  }\n\n\t  #map text {\n\t\tfill: #000 !important;\n\t\tstroke: none !important;\n\t  }\n\n\t  #map .edge,\n\t  #map polyline,\n\t  #map line:not([class*=\"grid\"]) {\n\t\tstroke: #333 !important;\n\t  }\n\n  #map .rect-group rect {\n    stroke: #333 !important;\n  }\n\t  header, .sidebar, .mobile-footer, .minimap-zoom-wrapper,\n\t  .draw-toolbar, .topology-toolbar, .legend-container,\n\t  .bulk-toolbar, #bulk-toolbar-mobile, .dropdown-menu,\n\t  #canvas-hint, .node-panel, .edge-panel, .text-panel, .rect-panel {\n\t\tdisplay: none !important;\n\t  }\n\t}\n\t#mobile-export-btn,\n#mobile-import-btn {\n  display: none;\n}\n@media (max-width: 900px) {\n  #mobile-export-btn,\n  #mobile-import-btn {\n    display: inline-block;\n  }\n}\n    </style>\n  </head>\n  <body style=\"background: radial-gradient(circle at center top, rgb(30, 37, 50) 0px, rgb(5, 6, 8) 70%);\" class=\"\">\n    <div class=\"modal\" id=\"edit-modal\">\n      <div class=\"modal-content\">\n        <h3 id=\"modal-title\">Edit Title</h3>\n        <input type=\"text\" id=\"modal-input\">\n        <div class=\"modal-buttons\">\n          <button class=\"btn-cancel\" id=\"modal-cancel\">Cancel</button>\n          <button class=\"btn-save\" id=\"modal-save\">Save</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal confirm-modal\" id=\"confirm-modal\">\n      <div class=\"modal-content\">\n        <h3>Confirm</h3>\n        <p id=\"confirm-message\"> Are you sure you want to delete this line? </p>\n        <div class=\"modal-buttons\">\n          <button class=\"btn-cancel\" id=\"confirm-cancel\">Cancel</button>\n          <button class=\"btn-delete\" id=\"confirm-delete\">Delete</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"save-info-modal\">\n      <div class=\"modal-content\" style=\"max-width: 700px; max-height: 80vh; overflow-y: auto;\">\n        <h3 style=\"margin-bottom: 16px;\">Help</h3>\n        <div style=\"display: flex; gap: 8px; margin-bottom: 16px; flex-wrap: wrap;\">\n          <button class=\"help-tab active\" data-tab=\"general\" style=\"padding: 8px 16px; border: 1px solid var(--edge-main); background: var(--accent); color: var(--bg); border-radius: 6px; cursor: pointer; font-weight: 600;\">General</button>\n          <button class=\"help-tab\" data-tab=\"desktop\" style=\"padding: 8px 16px; border: 1px solid var(--edge-main); background: var(--panel); color: var(--text-main); border-radius: 6px; cursor: pointer; font-weight: 600;\">Desktop</button>\n          <button class=\"help-tab\" data-tab=\"mobile\" style=\"padding: 8px 16px; border: 1px solid var(--edge-main); background: var(--panel); color: var(--text-main); border-radius: 6px; cursor: pointer; font-weight: 600;\">Mobile</button>\n\t\t  <button class=\"help-tab\" data-tab=\"formats\" style=\"padding: 8px 16px; border: 1px solid var(--edge-main); background: var(--panel); color: var(--text-main); border-radius: 6px; cursor: pointer; font-weight: 600;\">Import/Export</button>\n        </div>\n        <div id=\"help-tab-general\" class=\"help-tab-content\" style=\"display: block;\">\n          <ul style=\"margin-left: 20px; margin-bottom: 12px; line-height: 1.8;\">\n            <li><strong>Add Nodes:</strong> Click \"+ Node\" or \"+ Rack\" in the top menu</li>\n            <li><strong>Connect Nodes:</strong> Select a node, then use \"Add Connection\" in the panel</li>\n            <li><strong>Move Nodes:</strong> Drag nodes to reposition them</li>\n            <li><strong>Enter Rack View:</strong> Double click on desktop or Long press on mobile</li>\n            <li><strong>Multi Select:</strong> Right click (desktop) or double tap (mobile)</li>\n            <li><strong>Pan Canvas:</strong> Drag empty space or hold Space + drag</li>\n            <li><strong>Zoom:</strong> Scroll wheel or pinch gesture</li>\n            <li><strong>Free Draw:</strong> Use draw toolbar for drawing lines, boxes/zones , and text</li>\n          </ul>\n          <h4 style=\"color: var(--accent); margin-bottom: 8px;\">Saving &amp; Encryption</h4>\n          <p style=\"margin-bottom: 8px;\">Browsers cannot overwrite local files. Click <strong>Save File</strong> to download an updated HTML with all changes. Replace your old file to keep edits.</p>\n          <p style=\"margin-bottom: 8px;\"><strong>Encryption of data:</strong> Check \"Encrypt\" before saving to password protect your data. Beware! No recovery possible without password!!</p>\n          <p><strong>Decryption of data:</strong> Export via JSON in top settings menu after successful password validation to decrypt your data. You can then reimport back into the same file and save!</p>\n        </div>\n        <div id=\"help-tab-desktop\" class=\"help-tab-content\" style=\"display: none;\">\n          <h4 style=\"color: var(--accent); margin-bottom: 12px;\">Navigation</h4>\n          <table style=\"width: 100%; border-collapse: collapse; margin-bottom: 16px; font-size: 14px;\">\n            <tbody><tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\">Arrow Keys</code></td>\n              <td style=\"padding: 8px;\">Move selected 1px</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\">Shift + Arrows</code></td>\n              <td style=\"padding: 8px;\">Move selected 10px</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\">Tab / Shift+Tab</code></td>\n              <td style=\"padding: 8px;\">Cycle through nodes</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\">F</code></td>\n              <td style=\"padding: 8px;\">Focus on selected</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\">Space + Drag</code></td>\n              <td style=\"padding: 8px;\">Pan canvas</td>\n            </tr>\n          </tbody></table>\n          <h4 style=\"color: var(--accent); margin-bottom: 12px;\">Management</h4>\n          <table style=\"width: 100%; border-collapse: collapse; margin-bottom: 16px; font-size: 14px;\">\n            <tbody><tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\">L</code></td>\n              <td style=\"padding: 8px;\">Lock/unlock selected</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\">G</code></td>\n              <td style=\"padding: 8px;\">Group/ungroup selected</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\">Ctrl+C / Ctrl+V</code></td>\n              <td style=\"padding: 8px;\">Copy / Paste</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\">Ctrl+D</code></td>\n              <td style=\"padding: 8px;\">Duplicate</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\">Ctrl+A</code></td>\n              <td style=\"padding: 8px;\">Select all</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\">Delete</code></td>\n              <td style=\"padding: 8px;\">Delete selected</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\">Escape</code></td>\n              <td style=\"padding: 8px;\">Clear selection</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\">Ctrl+Z / Ctrl+Y</code></td>\n              <td style=\"padding: 8px;\">Undo / Redo</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\">Right Click</code></td>\n              <td style=\"padding: 8px;\">Multi select toggle</td>\n            </tr>\n          </tbody></table>\n        </div>\n        <div id=\"help-tab-mobile\" class=\"help-tab-content\" style=\"display: none;\">\n          <h4 style=\"color: var(--accent); margin-bottom: 12px;\">Basic Gestures</h4>\n          <table style=\"width: 100%; border-collapse: collapse; margin-bottom: 16px; font-size: 14px;\">\n            <tbody><tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><strong>Tap node</strong></td>\n              <td style=\"padding: 8px;\">Select &amp; open properties</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><strong>Tap empty</strong></td>\n              <td style=\"padding: 8px;\">Deselect all</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><strong>Drag node</strong></td>\n              <td style=\"padding: 8px;\">Move node</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><strong>Drag empty</strong></td>\n              <td style=\"padding: 8px;\">Pan canvas</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><strong>Pinch</strong></td>\n              <td style=\"padding: 8px;\">Zoom in/out</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><strong>Double tap node</strong></td>\n              <td style=\"padding: 8px;\">Add/remove from selection</td>\n            </tr>\n          </tbody></table>\n          <h4 style=\"color: var(--accent); margin-bottom: 12px;\">Rack View</h4>\n          <table style=\"width: 100%; border-collapse: collapse; margin-bottom: 16px; font-size: 14px;\">\n            <tbody><tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><strong>Long press rack</strong></td>\n              <td style=\"padding: 8px;\">Enter rack view</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><strong>Double tap empty</strong></td>\n              <td style=\"padding: 8px;\">Exit rack view</td>\n            </tr>\n          </tbody></table>\n        </div>\n\t\t<div id=\"help-tab-formats\" class=\"help-tab-content\" style=\"display: none;\">\n          <h4 style=\"color: var(--accent); margin-bottom: 12px;\">Export Help</h4>\n          <table style=\"width: 100%; border-collapse: collapse; margin-bottom: 16px; font-size: 14px;\">\n            <tbody>\n              <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n                <td style=\"padding: 8px;\"><strong>HTML</strong></td>\n                <td style=\"padding: 8px;\">Complete self contained file and the default save method. When in doubt, choose this. Use the Save HTML button for this</td>\n              </tr>\n              <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n                <td style=\"padding: 8px;\"><strong>JSON</strong></td>\n                <td style=\"padding: 8px;\">Full backup. Editable in your favorite text editor</td>\n              </tr>\n              <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n                <td style=\"padding: 8px;\"><strong>Markdown</strong></td>\n                <td style=\"padding: 8px;\">Full backup. Editable in your favorite text editor</td>\n              </tr>\n              <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n                <td style=\"padding: 8px;\"><strong>CSV</strong></td>\n                <td style=\"padding: 8px;\">Full backup in header, nodes in spreadsheet rows</td>\n              </tr>\n              <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n                <td style=\"padding: 8px;\"><strong>PNG</strong></td>\n                <td style=\"padding: 8px;\">Standard image of current canvas tab</td>\n              </tr>\n              <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n                <td style=\"padding: 8px;\"><strong>SVG</strong></td>\n                <td style=\"padding: 8px;\">Vector image, scalable and editable image of current canvas tab</td>\n              </tr>\n            </tbody>\n          </table>\n          <h4 style=\"color: var(--accent); margin-bottom: 12px;\">Import Help</h4>\n          <table style=\"width: 100%; border-collapse: collapse; margin-bottom: 16px; font-size: 14px;\">\n            <tbody>\n              <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n                <td style=\"padding: 8px;\"><strong>JSON</strong></td>\n                <td style=\"padding: 8px;\">Replaces all data</td>\n              </tr>\n              <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n                <td style=\"padding: 8px;\"><strong>Markdown</strong></td>\n                <td style=\"padding: 8px;\">Replaces all data</td>\n              </tr>\n              <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n                <td style=\"padding: 8px;\"><strong>CSV</strong></td>\n                <td style=\"padding: 8px;\">Choose full restore or add nodes only. Allows quickly creating nodes with a spreadsheet and importing into an existing save.</td>\n              </tr>\n            </tbody>\n          </table>\n        </div>\n        <div class=\"modal-buttons\" style=\"margin-top: 16px;\">\n          <button class=\"btn-cancel\" id=\"save-info-close\">Close</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"settings-modal\">\n      <div class=\"modal-content\">\n        <h2>Settings</h2>\n\t\t<details class=\"style-section\">\n          <summary>View Only Mode</summary>\n          <div class=\"style-content\">\n            <div class=\"style-row\">\n              <label>View Only (disable editing)</label>\n              <input type=\"checkbox\" id=\"view-only-mode\" style=\"width:auto;\">\n            </div>\n            <p style=\"margin-top:4px;font-size:12px;color:var(--text-soft);\">When enabled, all editing of the canvas is disabled. Pan and zoom still work.</p>\n          </div>\n        </details>\n        <details class=\"style-section\">\n          <summary>General Theme</summary>\n          <div class=\"style-content\">\n\t\t    <div class=\"style-row\">\n              <label>Theme Preset</label>\n              <div style=\"display:flex;gap:6px;flex:1;\">\n                <select id=\"theme-preset\" style=\"flex:1;padding:4px 8px;background:var(--input-bg);color:var(--input-text);border:1px solid var(--input-border);border-radius:4px;\">\n                 <option value=\"defaulted\">Default</option>\n                 <option value=\"\">Custom</option>\n                  <optgroup label=\"Corporate\">\n                    <option value=\"slate\">Slate</option>\n                    <option value=\"graphite\">Graphite</option>\n                    <option value=\"frost\">Frost (Light)</option>\n                  </optgroup>\n                  <optgroup label=\"Homelab\">\n                    <option value=\"synthwave\">Synthwave</option>\n                    <option value=\"terminal\">Terminal</option>\n                  </optgroup>\n                  <optgroup label=\"Dev\">\n                    <option value=\"dracula\">Dracula</option>\n                    <option value=\"cobalt\">Cobalt</option>\n                    <option value=\"solarized\">Solarized</option>\n                  </optgroup>\n                  <optgroup id=\"my-themes-group\" label=\"My Themes\"></optgroup>\n                </select>\n              </div>\n            </div>\n\t\t\t<div class=\"style-row\">\n\t\t\t <label>  \n\t\t\t  <button onclick=\"saveCurrentTheme()\" style=\"padding:4px 8px;background:var(--accent);color:var(--bg);border:none;border-radius:4px;cursor:pointer;font-size:12px;white-space:nowrap;\">Save Custom Theme</button>\n\t\t\t </label>\n\t\t\t  <button id=\"delete-theme-btn\" onclick=\"deleteCurrentTheme()\" style=\"padding: 4px 8px; background: var(--danger); color: rgb(255, 255, 255); border: none; border-radius: 4px; cursor: pointer; font-size: 12px; display: block;\" disabled=\"\">Delete Custom Theme</button>\n\t\t\t</div>\t\n            <div class=\"style-row\">\n              <label>Main Background</label>\n              <input type=\"color\" id=\"panel-color\" value=\"#0b0e13\">\n            </div>\n            <div class=\"style-row\">\n              <label>Sidebar / Mobile Footer</label>\n              <input type=\"color\" id=\"sidebar-bg-color\" value=\"#10141b\">\n            </div>\n            <div class=\"style-row\">\n              <label>Modal Window (popup) Background</label>\n              <input type=\"color\" id=\"panel-alt-color\" value=\"#10141b\">\n            </div>\n            <div class=\"style-row\">\n              <label>Accent Buttons</label>\n              <input type=\"color\" id=\"accent-color\" value=\"#4fd1c5\">\n            </div>\n            <div class=\"style-row\">\n              <label>Danger Buttons</label>\n              <input type=\"color\" id=\"danger-color\" value=\"#f56565\">\n            </div>\n            <div class=\"style-row\">\n              <label>Primary Text</label>\n              <input type=\"color\" id=\"text-main-color\" value=\"#e2e8f0\">\n            </div>\n            <div class=\"style-row\">\n              <label>Secondary Text</label>\n              <input type=\"color\" id=\"text-soft-color\" value=\"#94a3b8\">\n            </div>\n\t\t\t<div class=\"style-row\">\n              <label>Tag(s) Fill</label>\n              <input type=\"color\" id=\"tag-fill-color\" value=\"#1e293b\">\n            </div>\n            <div class=\"style-row\">\n              <label>Tag(s) Text</label>\n              <input type=\"color\" id=\"tag-text-color\" value=\"#e2e8f0\">\n            </div>\n            <div class=\"style-row\">\n              <label>Tag(s) Border</label>\n              <input type=\"color\" id=\"tag-border-color\" value=\"#475569\">\n            </div>\t\t\t\n          </div>\n        </details>\n\t\t<details class=\"style-section\">\n          <summary>Top Bar</summary>\n          <div class=\"style-content\">\n            <div class=\"style-row\">\n              <label>Background</label>\n              <input type=\"color\" id=\"topbar-bg-color\" value=\"#0b0e13\">\n            </div>\n            <div class=\"style-row\">\n              <label>Border</label>\n              <input type=\"color\" id=\"topbar-border-color\" value=\"#1f2533\">\n            </div>\n          </div>\n\t\t  <div class=\"style-content\">\n            <div class=\"style-row\">\n              <label>Button Fill</label>\n              <input type=\"color\" id=\"btn-bg-color\" value=\"#0b0e13\">\n            </div>\n            <div class=\"style-row\">\n              <label>Button Text</label>\n              <input type=\"color\" id=\"btn-text-color\" value=\"#e2e8f0\">\n            </div>\n          </div>\n        </details>\n        <details class=\"style-section\">\n          <summary>Main Canvas</summary>\n          <div class=\"style-content\">\n            <div class=\"style-row\">\n              <label>Border</label>\n              <input type=\"color\" id=\"canvas-border-color\" value=\"#475569\">\n            </div>\n            <div class=\"style-row\">\n              <label>Grid Lines</label>\n              <input type=\"color\" id=\"canvas-grid-color\" value=\"#475569\">\n            </div>\n            <div class=\"style-row\">\n              <label>Grid Size</label>\n              <input type=\"number\" id=\"canvas-grid-size\" value=\"50\" min=\"20\" max=\"200\" style=\"width:60px;\">\n            </div>\n\t\t\t<div class=\"style-row\">\n\t\t\t<label>Show Grid</label>\n\t\t\t<input type=\"checkbox\" id=\"canvas-grid-enabled\" checked=\"\">\n\t\t\t</div>\n\t\t\t<div class=\"style-row\">\n              <label>Background Top</label>\n              <input type=\"color\" id=\"canvas-gradient-top\" value=\"#1e2532\">\n            </div>\n            <div class=\"style-row\">\n              <label>Background Bottom</label>\n              <input type=\"color\" id=\"canvas-gradient-bottom\" value=\"#050608\">\n            </div>\n            <div class=\"style-row\">\n              <label>Solid Background</label>\n              <input type=\"color\" id=\"page-bg-color\" value=\"#050608\">\n            </div>\n            <p style=\"margin-top:4px;font-size:12px;color:var(--text-soft);\">Set solid background to override gradient.</p>\n          </div>\n\t\t\t</details>\n\t\t  <details class=\"style-section\">\n          <summary>Rack Canvas</summary>\n          <div class=\"style-content\">\n            <div class=\"style-row\">\n              <label>Frame Fill</label>\n              <input type=\"color\" id=\"rack-frame-fill\" value=\"#0f172a\">\n            </div>\n            <div class=\"style-row\">\n              <label>Frame Border</label>\n              <input type=\"color\" id=\"rack-frame-stroke\" value=\"#4fd1c5\">\n            </div>\n            <div class=\"style-row\">\n              <label>Grid Lines</label>\n              <input type=\"color\" id=\"rack-line-color\" value=\"#475569\">\n            </div>\n\t\t\t<div class=\"style-row\">\n\t\t\t  <label>Show Grid</label>\n\t\t\t  <input type=\"checkbox\" id=\"rack-grid-enabled\" checked=\"\">\n\t\t\t</div>\n            <div class=\"style-row\">\n              <label>U Labels</label>\n              <input type=\"color\" id=\"rack-text-color\" value=\"#4fd1c5\">\n            </div>\n          </div>\n        </details>\n\t\t        <details class=\"style-section\">\n          <summary>Canvas Toolbars</summary>\n          <div class=\"style-content\">\n            <div class=\"style-row\">\n              <label>Background</label>\n              <input type=\"color\" id=\"toolbar-bg-color\" value=\"#0f172a\">\n            </div>\n            <div class=\"style-row\">\n              <label>Border</label>\n              <input type=\"color\" id=\"toolbar-border-color\" value=\"#1f2937\">\n            </div>\n            <div class=\"style-row\">\n              <label>Text</label>\n              <input type=\"color\" id=\"toolbar-text-color\" value=\"#94a3b8\">\n            </div>\n            <div class=\"style-row\">\n              <label>Button Fill</label>\n              <input type=\"color\" id=\"toolbar-btn-bg-color\" value=\"#0b0e13\">\n            </div>\n            <div class=\"style-row\">\n              <label>Button Text</label>\n              <input type=\"color\" id=\"toolbar-btn-text-color\" value=\"#e2e8f0\">\n            </div>\n            <div class=\"style-row\">\n              <label>Minimap Dots</label>\n              <input type=\"color\" id=\"minimap-dots-color\" value=\"#94a3b8\">\n            </div>\n          </div>\n        </details>\n        <details class=\"style-section\">\n          <summary>Nodes</summary>\n          <div class=\"style-content\">\n            <div class=\"style-row\">\n              <label>Fill</label>\n              <input type=\"color\" id=\"node-fill-color\" value=\"#1e293b\">\n            </div>\n            <div class=\"style-row\">\n              <label>Border</label>\n              <input type=\"color\" id=\"node-stroke-color\" value=\"#475569\">\n            </div>\n            <div class=\"style-row\">\n              <label>Title Color</label>\n              <input type=\"color\" id=\"node-title-color\" value=\"#e2e8f0\">\n            </div>\n            <div class=\"style-row\">\n              <label>Title Size</label>\n              <input type=\"number\" id=\"node-title-size\" value=\"18\" min=\"8\" max=\"48\" style=\"width:60px;\">\n            </div>\n            <div class=\"style-row\">\n              <label>Subtitle Color</label>\n              <input type=\"color\" id=\"node-sub-color\" value=\"#94a3b8\">\n            </div>\n            <div class=\"style-row\">\n              <label>Subtitle Size</label>\n              <input type=\"number\" id=\"node-sub-size\" value=\"13\" min=\"8\" max=\"48\" style=\"width:60px;\">\n            </div>\n            <div class=\"style-row\">\n              <label>Font</label>\n              <select id=\"node-font-family\" style=\"flex:1;padding:4px;background:var(--panel);color:var(--text-main);border:1px solid var(--topbar-border);border-radius:4px;\">\n                <option value=\"Inter, system-ui, sans-serif\">Inter</option>\n                <option value=\"Arial, sans-serif\">Arial</option>\n                <option value=\"Helvetica, sans-serif\">Helvetica</option>\n                <option value=\"Georgia, serif\">Georgia</option>\n                <option value=\"monospace\">Monospace</option>\n              </select>\n            </div>\n          </div>\n        </details>\n        <details class=\"style-section\">\n          <summary>Connections</summary>\n          <div class=\"style-content\">\n            <div class=\"style-row\">\n              <label>Default Color</label>\n              <input type=\"color\" id=\"default-edge-color\" value=\"#475569\">\n            </div>\n            <div class=\"style-row\">\n              <label>Default Routing</label>\n              <select id=\"default-edge-routing\">\n                <option value=\"orthogonal\">Orthogonal (90°)</option>\n                <option value=\"curved\">Curved</option>\n                <option value=\"straight\">Straight</option>\n              </select>\n            </div>\n            <div class=\"style-row\">\n              <label>Animation Style</label>\n              <select id=\"animation-style-select\">\n                <option value=\"arrows\">Flowing Arrows</option>\n                <option value=\"dots\">Dot Arrows</option>\n              </select>\n            </div>\n            <div class=\"style-row\">\n              <label>Animate Directions</label>\n              <select id=\"animation-direction-select\">\n                <option value=\"all\">All Directions</option>\n                <option value=\"forward\">Forward Only</option>\n                <option value=\"backward\">Backward Only</option>\n                <option value=\"both\">Bidirectional Only</option>\n              </select>\n            </div>\n            <div class=\"style-row\">\n              <label>Animation Speed</label>\n              <select id=\"animation-speed-select\">\n                <option value=\"0.5\">Very Fast</option>\n                <option value=\"1\">Fast</option>\n                <option value=\"1.5\">Normal</option>\n                <option value=\"2.5\">Slow</option>\n                <option value=\"4\">Very Slow</option>\n              </select>\n            </div>\n            <div class=\"style-row\" style=\"grid-column: 1 / -1;\">\n              <button id=\"apply-routing-all\" style=\"width:100%;padding:8px;background:var(--accent);color:var(--bg);border:none;border-radius:6px;cursor:pointer;font-weight:600;\">Apply Routing to All Connections</button>\n            </div>\n          </div>\n        </details>\n\t\t<details class=\"style-section\">\n          <summary>Animations &amp; Zones</summary>\n          <div class=\"style-content\">\n            <div class=\"style-row\"><label>All Animations</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"anim-master\" checked=\"\"><span class=\"toggle-slider\"></span></label></div>\n            <div style=\"grid-column:1/-1;font-size:11px;color:var(--text-soft);text-transform:uppercase;border-bottom:1px solid var(--edge-main);padding-bottom:4px;margin-top:8px;\">By Type</div>\n            <div class=\"style-row\"><label>Sweep (Pan)</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"anim-type-sweep\" checked=\"\"><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label>Pulse (Breathe)</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"anim-type-pulse\" checked=\"\"><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label>Rings (Emanate)</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"anim-type-rings\" checked=\"\"><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label>Spin (Rotate)</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"anim-type-spin\" checked=\"\"><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label>Connections (Flow)</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"anim-type-connections\" checked=\"\"><span class=\"toggle-slider\"></span></label></div>\n            <div style=\"grid-column:1/-1;font-size:11px;color:var(--text-soft);text-transform:uppercase;border-bottom:1px solid var(--edge-main);padding-bottom:4px;margin-top:8px;\">By Category</div>\n            <div class=\"style-row\"><label>Cameras</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"anim-cat-camera\" checked=\"\"><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label>Doorbells</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"anim-cat-doorbell\" checked=\"\"><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label>Motion Sensors</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"anim-cat-motion\" checked=\"\"><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label>Smoke Detectors</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"anim-cat-smoke\" checked=\"\"><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label>WiFi/AP/Router</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"anim-cat-wifi\" checked=\"\"><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label>Sensors/IoT</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"anim-cat-sensor\" checked=\"\"><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label>Sprinklers</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"anim-cat-sprinkler\" checked=\"\"><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label>Connections</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"anim-cat-connections\" checked=\"\"><span class=\"toggle-slider\"></span></label></div>\n            <div style=\"grid-column:1/-1;font-size:12px;color:var(--accent);text-transform:uppercase;letter-spacing:0.05em;font-weight:600;margin-top:16px;padding-top:12px;border-top:1px solid var(--edge-main);\">Zone (Animation Cone) Setings</div>\n            <div class=\"style-row\"><label>All Zones</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"zone-master\" checked=\"\"><span class=\"toggle-slider\"></span></label></div>\n            <div style=\"grid-column:1/-1;font-size:11px;color:var(--text-soft);text-transform:uppercase;border-bottom:1px solid var(--edge-main);padding-bottom:4px;margin-top:8px;\">By Category</div>\n            <div class=\"style-row\"><label>Cameras</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"zone-cat-camera\" checked=\"\"><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label>Doorbells</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"zone-cat-doorbell\" checked=\"\"><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label>Motion Sensors</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"zone-cat-motion\" checked=\"\"><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label>Smoke Detectors</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"zone-cat-smoke\" checked=\"\"><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label>WiFi/AP/Router</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"zone-cat-wifi\" checked=\"\"><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label>Sensors/IoT</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"zone-cat-sensor\" checked=\"\"><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label>Sprinklers</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"zone-cat-sprinkler\" checked=\"\"><span class=\"toggle-slider\"></span></label></div>\n          </div>\n        </details>\n        <details class=\"style-section\">\n          <summary>Groups &amp; Editing</summary>\n          <div class=\"style-content\">\n            <div class=\"style-row\">\n              <label>Resize handle Color</label>\n              <input type=\"color\" id=\"selection-handle-color\" value=\"#f59e0b\">\n            </div>\n            <div class=\"style-row\">\n              <label>Resize Handle Size</label>\n              <input type=\"number\" id=\"selection-handle-size\" value=\"8\" min=\"4\" max=\"16\" style=\"width:60px;\">\n            </div>\n            <div class=\"style-row\">\n              <label>Grouped Icon Outline</label>\n              <input type=\"color\" id=\"group-indicator-color\" value=\"#4fd1c5\">\n            </div>\n          </div>\n\t\t   <div class=\"style-content\">\n            <div class=\"style-row\">\n              <label>Multiselect Fill Color (Desktop)</label>\n              <input type=\"color\" id=\"selection-fill-color\" value=\"#4fd1c5\">\n            </div>\n            <div class=\"style-row\">\n              <label>Multiselect Fill Opacity (Desktop)</label>\n              <div style=\"display:flex;align-items:center;gap:8px;\">\n                <input type=\"range\" id=\"selection-fill-opacity\" min=\"0\" max=\"1\" step=\"0.05\" value=\"0.1\">\n                <span id=\"selection-fill-opacity-val\" style=\"min-width:35px;text-align:right;\">10%</span>\n              </div>\n            </div>\n            <div class=\"style-row\">\n              <label>Multiselect Border Color (Desktop)</label>\n              <input type=\"color\" id=\"selection-stroke-color\" value=\"#4fd1c5\">\n            </div>\n            <div class=\"style-row\">\n              <label>Multiselect Border Width (Desktop)</label>\n              <input type=\"number\" id=\"selection-stroke-width\" min=\"1\" max=\"10\" value=\"2\">\n            </div>\n            <div class=\"style-row\">\n              <label>Multiselect Border Style (Desktop)</label>\n              <select id=\"selection-stroke-style\">\n                <option value=\"5,5\">Dashed</option>\n                <option value=\"2,4\">Dotted</option>\n                <option value=\"none\">Solid</option>\n              </select>\n            </div>\n          </div>\n        </details>\n        <details class=\"style-section\">\n          <summary>Inputs &amp; Dropdowns</summary>\n          <div class=\"style-content\">\n            <div class=\"style-row\">\n              <label>Background</label>\n              <input type=\"color\" id=\"input-bg-color\" value=\"#0b0e13\">\n            </div>\n            <div class=\"style-row\">\n              <label>Text</label>\n              <input type=\"color\" id=\"input-text-color\" value=\"#e2e8f0\">\n            </div>\n            <div class=\"style-row\">\n              <label>Border</label>\n              <input type=\"color\" id=\"input-border-color\" value=\"#1f2937\">\n            </div>\n            <div class=\"style-row\">\n              <label>Font</label>\n              <select id=\"input-font-family\" style=\"flex:1;padding:4px;background:var(--panel);color:var(--text-main);border:1px solid var(--topbar-border);border-radius:4px;\">\n                <option value=\"Inter, system-ui, sans-serif\">Inter</option>\n                <option value=\"Arial, sans-serif\">Arial</option>\n                <option value=\"Helvetica, sans-serif\">Helvetica</option>\n                <option value=\"Georgia, serif\">Georgia</option>\n                <option value=\"monospace\">Monospace</option>\n              </select>\n            </div>\n            <div class=\"style-row\">\n              <label>Font Size</label>\n              <input type=\"number\" id=\"input-font-size\" value=\"14\" min=\"10\" max=\"24\" style=\"width:60px;\">\n            </div>\n          </div>\n        </details>\n        <details class=\"style-section\">\n          <summary>Welcome Message</summary>\n          <div class=\"style-content\">\n            <div class=\"style-row\">\n              <label>Enabled</label>\n              <input type=\"checkbox\" id=\"canvas-hint-enabled\" checked=\"\" style=\"width:auto;\">\n            </div>\n            <div class=\"style-row\">\n              <label>Background</label>\n              <input type=\"color\" id=\"canvas-hint-bg-color\" value=\"#0f172a\">\n            </div>\n            <div class=\"style-row\">\n              <label>Text Color</label>\n              <input type=\"color\" id=\"canvas-hint-text-color\" value=\"#94a3b8\">\n            </div>\n            <div class=\"style-row\" style=\"flex-direction:column;align-items:stretch;\">\n              <label>Custom Text (HTML)</label>\n              <textarea id=\"canvas-hint-text\" rows=\"4\" style=\"width:100%;margin-top:4px;padding:6px;background:var(--panel);color:var(--text-main);border:1px solid var(--topbar-border);border-radius:4px;font-size:12px;resize:vertical;\" placeholder=\"Leave empty for default\"></textarea>\n            </div>\n          </div>\n        </details>\n        <details class=\"style-section\" open=\"\">\n          <summary>Danger Zone</summary>\n          <div class=\"style-content\">\n            <p style=\"margin-bottom:12px;font-size:13px;color:var(--text-soft);\">Permanently delete everything on the canvas.</p>\n            <button id=\"clear-all-btn\" title=\"Clear all nodes\" style=\"padding:6px 12px;background:var(--danger);color:#fff;border:none;border-radius:6px;cursor:pointer;font-size:14px;font-weight:600;\">Clear All</button>\n            <input type=\"file\" id=\"import-data-file\" accept=\".json\" style=\"display:none\">\n          </div>\n        </details>\n        <div class=\"modal-buttons\">\n          <button class=\"btn-cancel\" id=\"settings-close\">Close</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"add-node-modal\">\n      <div class=\"modal-content\">\n        <h3>Add New Node</h3>\n        <label style=\"display: block;margin-bottom: 4px;color: var(--text-soft);font-size: 13px;\">Name</label>\n        <input type=\"text\" id=\"new-node-name\" placeholder=\"e.g. web-server\">\n        <label style=\"display: block;margin-bottom: 4px;color: var(--text-soft);font-size: 13px;\">IP / Subtitle</label>\n        <input type=\"text\" id=\"new-node-ip\" placeholder=\"e.g. 192.168.1.100\">\n        <label style=\"display: block;margin-bottom: 4px;color: var(--text-soft);font-size: 13px;\">Tags (comma separated)</label>\n        <input type=\"text\" id=\"new-node-tags\" placeholder=\"e.g. Docker, nginx, WG: vpn\">\n        <label style=\"display: block;margin-bottom: 4px;color: var(--text-soft);font-size: 13px;\">Shape</label>\n        <select id=\"new-node-shape\">\n                  <optgroup label=\"Basic Shapes\">\n\t\t\t\t  <option value=\"circle\">Circle</option>\n\t\t\t\t  <option value=\"square\">Square</option>\n\t\t\t\t  <option value=\"rectangle\">Rectangle</option>\n\t\t\t\t  <option value=\"triangle\">Triangle</option>\n\t\t\t\t  <option value=\"hexagon\">Hexagon</option>\n\t\t\t\t  <option value=\"diamond\">Diamond</option>\n\t\t\t\t  <option value=\"star\">Star</option>\n\t\t\t\t  <option value=\"stop-sign\">Stop Sign</option>\n\t\t\t\t  <option value=\"octagon\">Octagon</option>\n\t\t\t\t  <option value=\"pentagon\">Pentagon</option>\n\t\t\t\t  <option value=\"cross\">Cross</option>\n\t\t\t\t  <option value=\"rounded-square\">Rounded Square</option>\n\t\t\t\t  <option value=\"pill\">Pill</option>\n\t\t\t\t  <option value=\"parallelogram\">Parallelogram</option>\n\t\t\t\t  <option value=\"trapezoid\">Trapezoid</option>\n\t\t\t\t</optgroup>\n\t\t\t  <optgroup label=\"Computers &amp; Devices\">\n\t\t\t      <option value=\"server\">Server</option>\n\t\t\t\t  <option value=\"pc\">PC / Desktop</option>\n\t\t\t\t  <option value=\"laptop\">Laptop</option>\n\t\t\t\t  <option value=\"phone\">Phone / Mobile</option>\n\t\t\t\t  <option value=\"printer\">Printer</option>\n\t\t\t\t  <option value=\"pi\">Raspberry Pi</option>\n\t\t\t\t  <option value=\"sensor\">Sensor / IoT</option>\n\t\t\t  </optgroup>\n\t\t\t  <optgroup label=\"Network Equipment\">\n\t\t\t\t  <option value=\"router\">Router</option>\n\t\t\t\t  <option value=\"switch\">Switch</option>\n\t\t\t\t  <option value=\"firewall\">Firewall</option>\n\t\t\t\t  <option value=\"access-point\">Access Point</option>\n\t\t\t\t  <option value=\"load-balancer\">Load Balancer</option>\n\t\t\t\t  <option value=\"gateway\">Gateway</option>\n\t\t\t\t  <option value=\"vpn\">VPN / Tunnel</option>\n\t\t\t\t  <option value=\"nas\">NAS / Storage</option>\n\t\t\t  </optgroup>\n\t\t\t  <optgroup label=\"Cloud &amp; Services\">\n\t\t\t      <option value=\"cloud\">Cloud</option>\n\t\t\t\t  <option value=\"database\">Database</option>\n\t\t\t\t  <option value=\"docker\">Docker</option>\n\t\t\t\t  <option value=\"container\">Container</option>\n\t\t\t\t  <option value=\"vm\">Virtual Machine</option>\n\t\t\t\t  <option value=\"kubernetes\">Kubernetes</option>\n\t\t\t\t  <option value=\"api\">API / Endpoint</option>\n\t\t\t\t  <option value=\"queue\">Queue / Message</option>\n\t\t\t\t  <option value=\"lambda\">Lambda / Function</option>\n\t\t\t\t  <option value=\"bucket\">Bucket / S3</option>\n\t\t\t  </optgroup>\n\t\t\t  <optgroup label=\"Security &amp; Monitoring\">\n\t\t\t\t  <option value=\"shield\">Shield</option>\n\t\t\t\t  <option value=\"camera\">Camera / CCTV</option>\n\t\t\t\t  <option value=\"monitor\">Monitor / Dashboard</option>\n\t\t\t  </optgroup>\n\t\t\t  <optgroup label=\"Smart Home\">\n\t\t\t\t  <option value=\"thermostat\">Thermostat</option>\n\t\t\t\t  <option value=\"doorbell\">Video Doorbell</option>\n\t\t\t\t  <option value=\"smart-lock\">Smart Lock</option>\n\t\t\t\t  <option value=\"smart-bulb\">Smart Bulb</option>\n\t\t\t\t  <option value=\"smart-plug\">Smart Plug</option>\n\t\t\t\t  <option value=\"smart-speaker\">Smart Speaker</option>\n\t\t\t\t  <option value=\"smart-tv\">Smart TV</option>\n\t\t\t\t  <option value=\"hub\">Smart Hub</option>\n\t\t\t\t  <option value=\"smoke-detector\">Smoke Detector</option>\n\t\t\t\t  <option value=\"motion-sensor\">Motion Sensor</option>\n\t\t\t\t  <option value=\"garage\">Garage Door</option>\n\t\t\t\t  <option value=\"sprinkler\">Sprinkler</option>\n\t\t\t\t  <option value=\"vacuum\">Robot Vacuum</option>\n\t\t\t  </optgroup>\n             </select>\n        <div class=\"modal-buttons\">\n          <button class=\"btn-cancel\" id=\"add-node-cancel\">Cancel</button>\n          <button class=\"btn-save\" id=\"add-node-save\">Add Node</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"add-rack-modal\">\n      <div class=\"modal-content\">\n        <h3>Add New Rack</h3>\n        <label style=\"display: block;margin-bottom: 4px;color: var(--text-soft);font-size: 13px;\">Rack Name</label>\n        <input type=\"text\" id=\"new-rack-name\" placeholder=\"e.g. Rack-A, DC1-R01\">\n        <label style=\"display: block;margin-bottom: 4px;color: var(--text-soft);font-size: 13px;\">IP / Network Range (optional)</label>\n        <input type=\"text\" id=\"new-rack-ip\" placeholder=\"e.g. 192.168.1.0/24\">\n        <label style=\"display: block;margin-bottom: 4px;color: var(--text-soft);font-size: 13px;\">Tags (comma separated)</label>\n        <input type=\"text\" id=\"new-rack-tags\" placeholder=\"e.g. Production, Data Center 1\">\n        <label style=\"display: block;margin-bottom: 4px;color: var(--text-soft);font-size: 13px;\">Icon / Shape</label>\n        <select id=\"new-rack-shape\">\n                  <optgroup label=\"Basic Shapes\">\n\t\t\t\t  <option value=\"circle\">Circle</option>\n\t\t\t\t  <option value=\"square\">Square</option>\n\t\t\t\t  <option value=\"rectangle\">Rectangle</option>\n\t\t\t\t  <option value=\"triangle\">Triangle</option>\n\t\t\t\t  <option value=\"hexagon\">Hexagon</option>\n\t\t\t\t  <option value=\"diamond\">Diamond</option>\n\t\t\t\t  <option value=\"star\">Star</option>\n\t\t\t\t  <option value=\"stop-sign\">Stop Sign</option>\n\t\t\t\t  <option value=\"octagon\">Octagon</option>\n\t\t\t\t  <option value=\"pentagon\">Pentagon</option>\n\t\t\t\t  <option value=\"cross\">Cross</option>\n\t\t\t\t  <option value=\"rounded-square\">Rounded Square</option>\n\t\t\t\t  <option value=\"pill\">Pill</option>\n\t\t\t\t  <option value=\"parallelogram\">Parallelogram</option>\n\t\t\t\t  <option value=\"trapezoid\">Trapezoid</option>\n\t\t\t\t</optgroup>\n\t\t\t  <optgroup label=\"Computers &amp; Devices\">\n\t\t\t      <option value=\"server\">Server</option>\n\t\t\t\t  <option value=\"pc\">PC / Desktop</option>\n\t\t\t\t  <option value=\"laptop\">Laptop</option>\n\t\t\t\t  <option value=\"phone\">Phone / Mobile</option>\n\t\t\t\t  <option value=\"printer\">Printer</option>\n\t\t\t\t  <option value=\"pi\">Raspberry Pi</option>\n\t\t\t\t  <option value=\"sensor\">Sensor / IoT</option>\n\t\t\t  </optgroup>\n\t\t\t  <optgroup label=\"Network Equipment\">\n\t\t\t\t  <option value=\"router\">Router</option>\n\t\t\t\t  <option value=\"switch\">Switch</option>\n\t\t\t\t  <option value=\"firewall\">Firewall</option>\n\t\t\t\t  <option value=\"access-point\">Access Point</option>\n\t\t\t\t  <option value=\"load-balancer\">Load Balancer</option>\n\t\t\t\t  <option value=\"gateway\">Gateway</option>\n\t\t\t\t  <option value=\"vpn\">VPN / Tunnel</option>\n\t\t\t\t  <option value=\"nas\">NAS / Storage</option>\n\t\t\t  </optgroup>\n\t\t\t  <optgroup label=\"Cloud &amp; Services\">\n\t\t\t      <option value=\"cloud\">Cloud</option>\n\t\t\t\t  <option value=\"database\">Database</option>\n\t\t\t\t  <option value=\"docker\">Docker</option>\n\t\t\t\t  <option value=\"container\">Container</option>\n\t\t\t\t  <option value=\"vm\">Virtual Machine</option>\n\t\t\t\t  <option value=\"kubernetes\">Kubernetes</option>\n\t\t\t\t  <option value=\"api\">API / Endpoint</option>\n\t\t\t\t  <option value=\"queue\">Queue / Message</option>\n\t\t\t\t  <option value=\"lambda\">Lambda / Function</option>\n\t\t\t\t  <option value=\"bucket\">Bucket / S3</option>\n\t\t\t  </optgroup>\n\t\t\t  <optgroup label=\"Security &amp; Monitoring\">\n\t\t\t\t  <option value=\"shield\">Shield</option>\n\t\t\t\t  <option value=\"camera\">Camera / CCTV</option>\n\t\t\t\t  <option value=\"monitor\">Monitor / Dashboard</option>\n\t\t\t  </optgroup>\n\t\t\t  <optgroup label=\"Smart Home\">\n\t\t\t\t  <option value=\"thermostat\">Thermostat</option>\n\t\t\t\t  <option value=\"doorbell\">Video Doorbell</option>\n\t\t\t\t  <option value=\"smart-lock\">Smart Lock</option>\n\t\t\t\t  <option value=\"smart-bulb\">Smart Bulb</option>\n\t\t\t\t  <option value=\"smart-plug\">Smart Plug</option>\n\t\t\t\t  <option value=\"smart-speaker\">Smart Speaker</option>\n\t\t\t\t  <option value=\"smart-tv\">Smart TV</option>\n\t\t\t\t  <option value=\"hub\">Smart Hub</option>\n\t\t\t\t  <option value=\"smoke-detector\">Smoke Detector</option>\n\t\t\t\t  <option value=\"motion-sensor\">Motion Sensor</option>\n\t\t\t\t  <option value=\"garage\">Garage Door</option>\n\t\t\t\t  <option value=\"sprinkler\">Sprinkler</option>\n\t\t\t\t  <option value=\"vacuum\">Robot Vacuum</option>\n\t\t\t  </optgroup>\n             </select>\n        <label style=\"display: block;margin-bottom: 4px;color: var(--text-soft);font-size: 13px;\">Rack Capacity</label>\n        <select id=\"new-rack-capacity\">\n          <option value=\"42\" selected=\"\">42U (Standard Full Rack)</option>\n          <option value=\"48\">48U (Large Rack)</option>\n          <option value=\"24\">24U (Half Rack)</option>\n          <option value=\"12\">12U (Small/Wall Mount)</option>\n          <option value=\"6\">6U (Mini Rack)</option>\n        </select>\n        <div class=\"modal-buttons\">\n          <button class=\"btn-cancel\" id=\"add-rack-cancel\">Cancel</button>\n          <button class=\"btn-save\" id=\"add-rack-save\">Add Rack</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal confirm-modal\" id=\"clear-all-modal\">\n      <div class=\"modal-content\">\n        <h3>Clear All Nodes</h3>\n        <p> This will remove ALL nodes and connections. This cannot be undone until you reload without saving. Are you sure? </p>\n        <div class=\"modal-buttons\">\n          <button class=\"btn-cancel\" id=\"clear-all-cancel\">Cancel</button>\n          <button class=\"btn-delete\" id=\"clear-all-confirm\">Clear Everything</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"layer-modal\">\n      <div class=\"modal-content\" style=\"max-width: 400px;\">\n        <h3>Layer Visibility</h3>\n        <p style=\"font-size: 13px; color: var(--text-soft); margin-bottom: 15px;\">Toggle which layers are visible on the canvas</p>\n        <div style=\"display: flex; flex-direction: column; gap: 10px;\">\n          <label style=\"display: flex; align-items: center; gap: 8px; cursor: pointer; padding: 8px; background: var(--panel-alt); border-radius: 6px;\">\n          <input type=\"checkbox\" id=\"layer-physical\" checked=\"\" style=\"cursor: pointer; width: 18px; height: 18px;\">\n          <span style=\"flex: 1; font-weight: 500;\">Physical Layer</span>\n          </label>\n          <label style=\"display: flex; align-items: center; gap: 8px; cursor: pointer; padding: 8px; background: var(--panel-alt); border-radius: 6px;\">\n          <input type=\"checkbox\" id=\"layer-logical\" checked=\"\" style=\"cursor: pointer; width: 18px; height: 18px;\">\n          <span style=\"flex: 1; font-weight: 500;\">Logical Layer</span>\n          </label>\n          <label style=\"display: flex; align-items: center; gap: 8px; cursor: pointer; padding: 8px; background: var(--panel-alt); border-radius: 6px;\">\n          <input type=\"checkbox\" id=\"layer-security\" checked=\"\" style=\"cursor: pointer; width: 18px; height: 18px;\">\n          <span style=\"flex: 1; font-weight: 500;\">Security Layer</span>\n          </label>\n          <label style=\"display: flex; align-items: center; gap: 8px; cursor: pointer; padding: 8px; background: var(--panel-alt); border-radius: 6px;\">\n          <input type=\"checkbox\" id=\"layer-application\" checked=\"\" style=\"cursor: pointer; width: 18px; height: 18px;\">\n          <span style=\"flex: 1; font-weight: 500;\">Application Layer</span>\n          </label>\n        </div>\n        <div style=\"margin-top: 15px; display: flex; gap: 8px;\">\n          <button class=\"btn-cancel\" style=\"flex: 1;\" onclick=\"document.querySelectorAll('#layer-modal input[type=checkbox]').forEach(cb =&gt; cb.checked = false); applyLayerFilter();\">Hide All</button>\n          <button class=\"btn-save\" style=\"flex: 1;\" onclick=\"document.querySelectorAll('#layer-modal input[type=checkbox]').forEach(cb =&gt; cb.checked = true); applyLayerFilter();\">Show All</button>\n        </div>\n        <div class=\"modal-buttons\" style=\"margin-top: 10px;\">\n          <button class=\"btn-cancel\" id=\"layer-modal-close\">Close</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"tabs-modal\">\n      <div class=\"modal-content\" style=\"max-width: 600px;\">\n        <p style=\"font-size: 13px; color: var(--text-soft); margin-bottom: 15px;\">Manage multiple topologies</p>\n        <div id=\"tabs-list\" style=\"display: flex; flex-direction: column; gap: 8px; max-height: 400px; overflow-y: auto; margin-bottom: 15px;\">\n            <div class=\"tab-item \" onclick=\"switchTab(0)\">\n              <div class=\"tab-name\">Corporate Site B</div>\n              <div class=\"tab-stats\">107 nodes • 64 connections</div>\n              <div class=\"tab-actions\">\n                <button class=\"btn-cancel\" onclick=\"event.stopPropagation(); renameTab(0)\" title=\"Rename tab\">✏️</button>\n                <button class=\"btn-cancel\" onclick=\"event.stopPropagation(); deleteTab(0)\" title=\"Delete tab\">🗑️</button>\n              </div>\n            </div>\n          \n            <div class=\"tab-item active\" onclick=\"switchTab(1)\">\n              <div class=\"tab-name\">Homelab 2</div>\n              <div class=\"tab-stats\">16 nodes • 10 connections</div>\n              <div class=\"tab-actions\">\n                <button class=\"btn-cancel\" onclick=\"event.stopPropagation(); renameTab(1)\" title=\"Rename tab\">✏️</button>\n                <button class=\"btn-cancel\" onclick=\"event.stopPropagation(); deleteTab(1)\" title=\"Delete tab\">🗑️</button>\n              </div>\n            </div>\n          </div>\n        <div style=\"display: flex; gap: 8px; margin-bottom: 10px;\">\n          <input type=\"text\" id=\"new-tab-name\" placeholder=\"New tab name...\" style=\"flex: 1; padding: 8px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 6px; color: var(--text-main);\">\n          <button class=\"btn-save\" onclick=\"createNewTab()\">+ Add Tab</button>\n        </div>\n        <div class=\"modal-buttons\">\n          <button class=\"btn-cancel\" id=\"tabs-modal-close\">Close</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"rollback-modal\">\n      <div class=\"modal-content\" style=\"max-width: 700px;\">\n        <h3>Version History</h3>\n        <p style=\"font-size: 13px; color: var(--text-soft); margin-bottom: 15px;\">Limit: Snapshots</p>\n        <div id=\"rollback-list\" style=\"display: flex; flex-direction: column; gap: 8px; max-height: 450px; overflow-y: auto; margin-bottom: 15px;\"></div>\n        <div style=\"display: flex; gap: 8px; margin-bottom: 10px;\">\n          <button class=\"btn-cancel\" onclick=\"clearRollbackHistory()\">Clear History</button>\n          <button class=\"btn-save\" onclick=\"createManualSnapshot()\">Create Snapshot</button>\n        </div>\n        <div class=\"modal-buttons\">\n          <button class=\"btn-cancel\" id=\"rollback-modal-close\">Close</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"audit-log-modal\">\n      <div class=\"modal-content\" style=\"max-width: 800px;\">\n        <h3>Audit Log</h3>\n        <p style=\"font-size: 13px; color: var(--text-soft); margin-bottom: 15px;\">Track all changes made to your topology</p>\n        <div style=\"display: flex; gap: 8px; margin-bottom: 10px;\">\n          <select id=\"audit-filter\" style=\"padding: 8px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 6px; color: var(--text-main);\">\n            <option value=\"all\">All Events</option>\n            <option value=\"node\">Node Operations</option>\n            <option value=\"connection\">Connections</option>\n            <option value=\"style\">Style Changes</option>\n            <option value=\"rack\">Rack Operations</option>\n            <option value=\"layer\">Layer Changes</option>\n          </select>\n          <button class=\"btn-save\" onclick=\"exportAuditLog()\">Export</button>\n        </div>\n        <div id=\"audit-log-list\" style=\"display: flex; flex-direction: column; gap: 4px; max-height: 450px; overflow-y: auto; margin-bottom: 15px; font-family: monospace; font-size: 12px;\"></div>\n        <div style=\"display: flex; gap: 8px;\">\n          <button class=\"btn-cancel\" onclick=\"clearAuditLog()\">Clear Log</button>\n          <button class=\"btn-cancel\" id=\"audit-log-modal-close\">Close</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"port-map-modal\">\n      <div class=\"modal-content\" style=\"max-width: 900px;\">\n        <h3>Port Map</h3>\n        <p style=\"font-size: 13px; color: var(--text-soft); margin-bottom: 15px;\">All connections with port assignments</p>\n        <div style=\"display: flex; gap: 8px; margin-bottom: 10px;\">\n          <input type=\"text\" id=\"port-map-search\" placeholder=\"Search devices or ports...\" style=\"flex: 1; min-width: 200px; padding: 8px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 6px; color: var(--text-main);\">\n          <select id=\"port-map-filter\" style=\"padding: 8px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 6px; color: var(--text-main);\">\n            <option value=\"all\">All Connections</option>\n            <option value=\"with-ports\">With Ports Only</option>\n            <option value=\"without-ports\">Missing Ports</option>\n          </select>\n        </div>\n        <div id=\"port-map-table\" style=\"max-height: 450px; overflow-y: auto; margin-bottom: 15px;\"><div style=\"text-align: center; padding: 40px; color: var(--text-soft);\">No connections found</div></div>\n        <div style=\"display: flex; gap: 8px;\">\n          <button class=\"btn-save\" onclick=\"exportPortMap()\">Export CSV</button>\n          <button class=\"btn-cancel\" id=\"port-map-modal-close\">Close</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"secrets-modal\">\n      <div class=\"modal-content\" style=\"max-width: 600px;\">\n        <h3>Notes</h3>\n        <p style=\"font-size: 13px; color: var(--text-soft); margin-bottom: 15px;\">Notes can also be stored with AES 256 encryption</p>\n        <div id=\"secrets-list\" style=\"display: flex; flex-direction: column; gap: 8px; max-height: 350px; overflow-y: auto; margin-bottom: 15px;\"><div style=\"text-align: center; padding: 40px; color: var(--text-soft);\">No notes yet</div></div>\n        <div style=\"display: flex; gap: 8px; margin-bottom: 10px;\">\n          <input type=\"text\" id=\"new-secret-name\" placeholder=\"Note name (e.g., 'Root Passwords')...\" style=\"flex: 1; padding: 8px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 6px; color: var(--text-main);\">\n          <button class=\"btn-save\" onclick=\"createNewSecret()\">+ Add Note</button>\n        </div>\n        <div class=\"modal-buttons\">\n          <button class=\"btn-cancel\" id=\"secrets-modal-close\">Close</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"secret-editor-modal\">\n      <div class=\"modal-content\" style=\"max-width: 600px;\">\n        <h3 id=\"secret-editor-title\">Edit Note</h3>\n        <textarea id=\"secret-editor-content\" placeholder=\"Enter sensitive information here...\" style=\"width: 100%; height: 200px; padding: 12px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 6px; color: var(--text-main); font-family: monospace; resize: vertical; margin-bottom: 15px;\"></textarea>\n        <div style=\"margin-bottom: 15px;\">\n          <label style=\"display: flex; align-items: center; gap: 8px; cursor: pointer;\">\n          <input type=\"checkbox\" id=\"secret-auto-encrypt\" checked=\"\" style=\"cursor: pointer;\">\n          <span>Encrypt Note</span>\n          </label>\n        </div>\n        <div class=\"modal-buttons\">\n          <button class=\"btn-cancel\" onclick=\"closeSecretEditor()\">Cancel</button>\n          <button class=\"btn-save\" onclick=\"saveSecret()\">Save</button>\n        </div>\n      </div>\n    </div>\n\t\n\t<div class=\"modal\" id=\"mobile-export-modal\">\n      <div class=\"modal-content\" style=\"max-width: 320px;\">\n        <h3 style=\"margin: 0 0 20px 0;\">Export</h3>\n        <div style=\"display: flex; flex-direction: column; gap: 12px;\">\n          <button onclick=\"screenshotCanvas(); document.getElementById('mobile-export-modal').classList.remove('active');\" style=\"padding: 14px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; color: var(--text-main); font-size: 16px; cursor: pointer;\">PNG Image</button>\n          <button onclick=\"exportCanvasSVG(); document.getElementById('mobile-export-modal').classList.remove('active');\" style=\"padding: 14px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; color: var(--text-main); font-size: 16px; cursor: pointer;\">SVG Vector</button>\n          <button onclick=\"exportJSONFile(); document.getElementById('mobile-export-modal').classList.remove('active');\" style=\"padding: 14px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; color: var(--text-main); font-size: 16px; cursor: pointer;\">JSON (Full Backup)</button>\n          <button onclick=\"exportMarkdown(); document.getElementById('mobile-export-modal').classList.remove('active');\" style=\"padding: 14px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; color: var(--text-main); font-size: 16px; cursor: pointer;\">Markdown</button>\n          <button onclick=\"exportCSV(); document.getElementById('mobile-export-modal').classList.remove('active');\" style=\"padding: 14px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; color: var(--text-main); font-size: 16px; cursor: pointer;\">CSV</button>\n          <button onclick=\"printTopology(); document.getElementById('mobile-export-modal').classList.remove('active');\" style=\"padding: 14px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; color: var(--text-main); font-size: 16px; cursor: pointer;\">Print...</button>\n\t\t  <button onclick=\"showFormatHelp(); document.getElementById('mobile-import-modal').classList.remove('active');\" style=\"padding: 14px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; color: var(--text-main); font-size: 16px; cursor: pointer;\">Export Help</button>\n        </div>\n        <button onclick=\"document.getElementById('mobile-export-modal').classList.remove('active');\" style=\"margin-top: 20px; padding: 12px; background: var(--danger); border: none; border-radius: 8px; color: white; font-size: 16px; cursor: pointer; width: 100%;\">Cancel</button>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"mobile-import-modal\">\n      <div class=\"modal-content\" style=\"max-width: 320px;\">\n        <h3 style=\"margin: 0 0 20px 0;\">Import</h3>\n        <div style=\"display: flex; flex-direction: column; gap: 12px;\">\n          <button onclick=\"document.getElementById('import-json-file').click(); document.getElementById('mobile-import-modal').classList.remove('active');\" style=\"padding: 14px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; color: var(--text-main); font-size: 16px; cursor: pointer;\">JSON</button>\n          <button onclick=\"document.getElementById('import-markdown-file').click(); document.getElementById('mobile-import-modal').classList.remove('active');\" style=\"padding: 14px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; color: var(--text-main); font-size: 16px; cursor: pointer;\">Markdown</button>\n          <button onclick=\"document.getElementById('import-csv-file').click(); document.getElementById('mobile-import-modal').classList.remove('active');\" style=\"padding: 14px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; color: var(--text-main); font-size: 16px; cursor: pointer;\">CSV</button>\n          <button onclick=\"showFormatHelp(); document.getElementById('mobile-import-modal').classList.remove('active');\" style=\"padding: 14px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; color: var(--text-main); font-size: 16px; cursor: pointer;\">Import Help</button>\n        </div>\n        <button onclick=\"document.getElementById('mobile-import-modal').classList.remove('active');\" style=\"margin-top: 20px; padding: 12px; background: var(--danger); border: none; border-radius: 8px; color: white; font-size: 16px; cursor: pointer; width: 100%;\">Cancel</button>\n      </div>\n    </div>\n    <div id=\"context-menu\" style=\"display: none !important;\">\n    </div>\n    <header>\n      <div class=\"title-block\">\n        <h1 id=\"page-title\" class=\"editable-page-title\">The One File</h1>\n        <div class=\"save-row\">\n          <button id=\"save-file-btn\" class=\"save-btn\" type=\"button\">Save HTML</button>\n          <label style=\"display: flex;align-items: center;gap: 4px;font-size: 12px;color: var(--text-soft);cursor: pointer;user-select: none;\">\n            <input type=\"checkbox\" id=\"encrypt-toggle\" style=\"cursor: pointer\">\n            <span title=\"Encrypt data with password\">🔒</span>\n          </label>\n          <div class=\"dropdown\" id=\"export-dropdown\">\n            <button class=\"dropdown-btn\" type=\"button\">Export ▾</button>\n            <div class=\"dropdown-menu\">\n              <button type=\"button\" onclick=\"screenshotCanvas()\">PNG Image</button>\n              <button type=\"button\" onclick=\"exportCanvasSVG()\">SVG Vector</button>\n              <div class=\"dropdown-divider\"></div>\n              <button type=\"button\" onclick=\"exportJSONFile()\">JSON</button>\n              <button type=\"button\" onclick=\"exportMarkdown()\">Markdown</button>\n              <button type=\"button\" onclick=\"exportCSV()\">CSV</button>\n              <div class=\"dropdown-divider\"></div>\n              <button type=\"button\" onclick=\"printTopology()\">Print...</button>\n\t\t\t  <div class=\"dropdown-divider\"></div>\n\t\t\t  <button type=\"button\" onclick=\"showFormatHelp()\">Export Help</button>\n            </div>\n          </div>\n          <div class=\"dropdown\" id=\"import-dropdown\">\n            <button class=\"dropdown-btn\" type=\"button\">Import ▾</button>\n            <div class=\"dropdown-menu\">\n              <button type=\"button\" onclick=\"document.getElementById('import-json-file').click()\">JSON</button>\n              <button type=\"button\" onclick=\"document.getElementById('import-markdown-file').click()\">Markdown</button>\n              <button type=\"button\" onclick=\"document.getElementById('import-csv-file').click()\">CSV</button>\n              <div class=\"dropdown-divider\"></div>\n              <button type=\"button\" onclick=\"showFormatHelp()\">Import Help</button>\n            </div>\n          </div>\n          <input type=\"file\" id=\"import-json-file\" accept=\".json\" style=\"display:none\">\n          <input type=\"file\" id=\"import-markdown-file\" accept=\".md,.markdown,.txt\" style=\"display:none\">\n          <input type=\"file\" id=\"import-csv-file\" accept=\".csv,.txt\" style=\"display:none\">\n          <span id=\"save-help-btn\" class=\"help-icon\" title=\"Why save?\">?</span>\n        </div>\n      </div>\n      <div id=\"topbar-menu\">\n        <button id=\"back-to-topology-btn\" title=\"Exit rack view and return to topology\" style=\"padding: 6px 12px;background: var(--accent);color: var(--bg);border: none;border-radius: 6px;cursor: pointer;font-size: 14px;font-weight: 600;display: none;\">← Back to Topology</button>\n        <button id=\"add-node-btn\" class=\"save-btn\" title=\"Add new node\" style=\"background: var(--accent)\" draggable=\"true\">+ Node</button>\n        <button id=\"add-rack-btn\" class=\"save-btn\" title=\"Add new rack\" style=\"background: var(--accent); margin-left: 8px;\" draggable=\"true\">+ Rack</button>\n        <button id=\"undo-btn\" title=\"Undo (Ctrl+Z)\" style=\"padding: 6px 12px; background: var(--btn-bg); color: var(--btn-text); border: 1px solid var(--edge-main); border-radius: 6px; cursor: pointer; font-size: 14px; font-weight: 600; opacity: 1;\">↶</button>\n        <button id=\"redo-btn\" title=\"Redo (Ctrl+Y)\" style=\"padding: 6px 12px; background: var(--btn-bg); color: var(--btn-text); border: 1px solid var(--edge-main); border-radius: 6px; cursor: pointer; font-size: 14px; font-weight: 600; opacity: 0.5;\" disabled=\"\">↷</button>\n        <input type=\"text\" id=\"search-nodes\" placeholder=\"Search nodes...\" title=\"Search by name, IP, MAC, role, or tag\" style=\"padding: 6px 12px;background: var(--input-bg);color: var(--input-text);border: 1px solid var(--input-border);border-radius: 6px;font-family: var(--input-font);font-size: var(--input-font-size);width: 180px;\">\n        <button id=\"layers-btn\" title=\"Toggle layer visibility\" style=\"padding: 6px 12px;background: var(--btn-bg);color: var(--btn-text);border: 1px solid var(--edge-main);border-radius: 6px;cursor: pointer;font-size: 14px;font-weight: 600;\">Layers</button>\n        <button id=\"tabs-btn\" title=\"Manage document tabs\" style=\"padding: 6px 12px;background: var(--btn-bg);color: var(--btn-text);border: 1px solid var(--edge-main);border-radius: 6px;cursor: pointer;font-size: 14px;font-weight: 600;\">Tabs</button>\n        <button id=\"rollback-btn\" title=\"View version history\" style=\"padding: 6px 12px;background: var(--btn-bg);color: var(--btn-text);border: 1px solid var(--edge-main);border-radius: 6px;cursor: pointer;font-size: 14px;font-weight: 600;\">Snapshots</button>\n        <button id=\"audit-log-btn\" title=\"View audit log\" style=\"padding: 6px 12px;background: var(--btn-bg);color: var(--btn-text);border: 1px solid var(--edge-main);border-radius: 6px;cursor: pointer;font-size: 14px;font-weight: 600;\">Audit</button>\n        <button id=\"port-map-btn\" title=\"View all port connections\" style=\"padding: 6px 12px;background: var(--btn-bg);color: var(--btn-text);border: 1px solid var(--edge-main);border-radius: 6px;cursor: pointer;font-size: 14px;font-weight: 600;\">Ports</button>\n        <button id=\"secrets-btn\" title=\"Manage encrypted notes\" style=\"padding: 6px 12px;background: var(--btn-bg);color: var(--btn-text);border: 1px solid var(--edge-main);border-radius: 6px;cursor: pointer;font-size: 14px;font-weight: 600;\">Notes</button>\n\t\t<button id=\"mobile-export-btn\" title=\"Export topology\" style=\"padding: 6px 12px;background: var(--btn-bg);color: var(--btn-text);border: 1px solid var(--edge-main);border-radius: 6px;cursor: pointer;font-size: 14px;font-weight: 600;\">Export</button>\n        <button id=\"mobile-import-btn\" title=\"Import topology\" style=\"padding: 6px 12px;background: var(--btn-bg);color: var(--btn-text);border: 1px solid var(--edge-main);border-radius: 6px;cursor: pointer;font-size: 14px;font-weight: 600;\">Import</button>\n        <button id=\"settings-btn\" title=\"Page settings\">⚙️</button>\n      </div>\n      <button id=\"mobile-menu-toggle\" class=\"mobile-menu-btn\">☰</button>\n      <div class=\"header-resizer\" id=\"header-resizer\">\n        <svg class=\"resizer-icon\" width=\"40\" height=\"6\" viewBox=\"0 0 40 6\" xmlns=\"http://www.w3.org/2000/svg\">\n          <circle cx=\"14\" cy=\"3\" r=\"2\"></circle>\n          <circle cx=\"20\" cy=\"3\" r=\"2\"></circle>\n          <circle cx=\"26\" cy=\"3\" r=\"2\"></circle>\n        </svg>\n      </div>\n    </header>\n    <main>\n      <section class=\"topology-panel\">\n        <div class=\"draw-toolbar\" id=\"draw-toolbar\" style=\"display: flex !important;\">\n          <button type=\"button\" class=\"toolbar-close-btn\" id=\"draw-toolbar-close-btn\" title=\"Hide draw toolbar\">✕</button>\n          <button id=\"draw-toggle\" title=\"Draw custom line\">✏️</button>\n          <button id=\"rect-toggle\" title=\"Draw zone\">▭</button>\n          <button id=\"text-toggle\" title=\"Add text\">T</button>\n          <span style=\"border-left: 1px solid var(--edge-main); height: 20px; margin: 0 4px;\"></span>\n          <select id=\"rect-style\" title=\"zone style\">\n            <option value=\"filled\">Filled</option>\n            <option value=\"outlined\">Outlined</option>\n          </select>\n          <input type=\"color\" id=\"draw-color\" value=\"#f97316\" title=\"Line color\">\n          <select id=\"draw-style\" title=\"Line style\">\n  <option value=\"solid\">Solid</option>\n  <option value=\"dashed\">Dashed</option>\n  <option value=\"dotted\">Dotted</option>\n  <option value=\"wall\">Wall</option>\n</select>\n          <select id=\"draw-arrow\" title=\"Arrow direction\">\n            <option value=\"none\">No arrows</option>\n            <option value=\"forward\">→ Right</option>\n            <option value=\"backward\">← Left</option>\n            <option value=\"both\">↔ Both</option>\n          </select>\n          <button id=\"draw-undo\" style=\"display: none\" title=\"Undo last point\">Undo</button>\n        </div>\n        <div class=\"topology-toolbar\" id=\"topology-toolbar\" style=\"display: flex !important;\">\n          <button type=\"button\" class=\"toolbar-close-btn\" id=\"topology-toolbar-close-btn\" title=\"Hide add line toolbar\">✕</button>\n          <label for=\"add-line-select\">Add line to:</label>\n          <select id=\"add-line-select\"></select>\n          <input type=\"color\" id=\"add-line-color\" value=\"#475569\" title=\"Line color\" style=\"width: 30px;height: 24px;border: 1px solid var(--toolbar-border);border-radius: 4px;cursor: pointer;background: transparent;padding: 0;\">\n          <select id=\"add-line-direction\" title=\"Line direction\">\n            <option value=\"none\">No arrows</option>\n            <option value=\"forward\">→ Forward</option>\n            <option value=\"backward\">← Backward</option>\n            <option value=\"both\">↔ Both</option>\n          </select>\n          <select id=\"add-line-routing\" title=\"Line routing\">\n            <option value=\"orthogonal\">Orthogonal</option>\n            <option value=\"curved\">Curved</option>\n            <option value=\"straight\">Straight</option>\n          </select>\n          <button id=\"add-line-btn\">Add</button>\n        </div>\n        <div class=\"topology-toolbar bulk-toolbar-desktop\" id=\"bulk-toolbar\" style=\"display: none\">\n          <button type=\"button\" class=\"toolbar-close-btn\" id=\"bulk-toolbar-close\" title=\"Clear selection\">✕</button>\n          <label style=\"font-weight: 600; color: var(--accent)\">Selected: <span id=\"bulk-count\">0</span></label>\n          <button id=\"bulk-align-left\" title=\"Align left\">⬅ Left</button>\n          <button id=\"bulk-align-right\" title=\"Align right\">➡ Right</button>\n          <button id=\"bulk-align-top\" title=\"Align top\">⬆ Top</button>\n          <button id=\"bulk-align-bottom\" title=\"Align bottom\">⬇ Bottom</button>\n          <button id=\"bulk-distribute-h\" title=\"Distribute horizontally\">↔ Distribute H</button>\n          <button id=\"bulk-distribute-v\" title=\"Distribute vertically\">↕ Distribute V</button>\n          <button id=\"bulk-clone\" title=\"Clone selected\">📋 Clone</button>\n          <button id=\"bulk-zone-copy\" title=\"Copy zone style\">📡 Copy Zone</button>\n          <button id=\"bulk-zone-paste\" title=\"Paste zone style\">📡 Paste Zone</button>\n          <button id=\"bulk-zone-toggle\" title=\"Toggle zones on selected\">📡 Toggle Zones</button>\n          <button id=\"bulk-delete\" title=\"Delete selected\" style=\"background: var(--danger); color: white;\">Delete</button>\n        </div>\n        <div class=\"bulk-toolbar-mobile\" id=\"bulk-toolbar-mobile\" style=\"\">\n          <button type=\"button\" id=\"bulk-mobile-btn\" style=\"background: var(--accent);color: white;padding: 12px 20px;border-radius: 25px;font-weight: 600;font-size: 16px;box-shadow: 0 4px 12px rgba(0,0,0,0.3);border: none;cursor: pointer;display: flex;align-items: center;gap: 8px;\">\n          <span id=\"bulk-count-mobile\">0</span>Selected</button>\n        </div>\n        <div id=\"bulk-actions-modal\" style=\"display: none;position: fixed;bottom: 0;left: 0;right: 0;background: var(--panel-alt);border-top-left-radius: 20px;border-top-right-radius: 20px;padding: 20px;padding-bottom: env(safe-area-inset-bottom, 20px);box-shadow: 0 -4px 20px rgba(0,0,0,0.5);z-index: 10000;max-height: calc(100vh - 80px);overflow-y: auto;\">\n          <div style=\"display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;\">\n            <h3 style=\"margin: 0; color: var(--accent); font-size: 20px;\">\n              <span id=\"bulk-count-modal\">0</span> Nodes Selected\n            </h3>\n            <button id=\"bulk-modal-close\" style=\"background: none;border: none;font-size: 24px;cursor: pointer;color: var(--text-main);padding: 0;width: 32px;height: 32px;\">✕</button>\n          </div>\n          <div style=\"display: grid; grid-template-columns: 1fr 1fr; gap: 12px;\">\n            <button id=\"bulk-align-left-mobile\" class=\"bulk-action-btn\">⬅<br><span style=\"font-size: 12px;\">Align Left</span></button>\n            <button id=\"bulk-align-right-mobile\" class=\"bulk-action-btn\">➡<br><span style=\"font-size: 12px;\">Align Right</span></button>\n            <button id=\"bulk-align-top-mobile\" class=\"bulk-action-btn\">⬆<br><span style=\"font-size: 12px;\">Align Top</span></button>\n            <button id=\"bulk-align-bottom-mobile\" class=\"bulk-action-btn\">⬇<br><span style=\"font-size: 12px;\">Align Bottom</span></button>\n            <button id=\"bulk-distribute-h-mobile\" class=\"bulk-action-btn\">↔<br><span style=\"font-size: 12px;\">Distribute H</span></button>\n            <button id=\"bulk-distribute-v-mobile\" class=\"bulk-action-btn\">↕<br><span style=\"font-size: 12px;\">Distribute V</span></button>\n            <button id=\"bulk-lock-mobile\" class=\"bulk-action-btn\">🔒<br><span style=\"font-size: 12px;\">Lock Toggle</span></button>\n            <button id=\"bulk-group-mobile\" class=\"bulk-action-btn\">⭕<br><span style=\"font-size: 12px;\">Group Toggle</span></button>\n            <button id=\"bulk-clone-mobile\" class=\"bulk-action-btn\">📋<br><span style=\"font-size: 12px;\">Clone All</span></button>\n            <button id=\"bulk-delete-mobile\" class=\"bulk-action-btn\" style=\"background: var(--danger); color: white;\">🗑<br><span style=\"font-size: 12px;\">Delete All</span></button>\n          </div>\n          <div style=\"margin-top: 16px; padding-top: 16px; border-top: 1px solid var(--edge-main);\">\n            <div style=\"font-size: 12px; color: var(--text-soft); margin-bottom: 12px; text-transform: uppercase; letter-spacing: 0.05em;\">Coverage Zones</div>\n            <div style=\"display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 12px;\">\n              <button id=\"bulk-zone-copy-mobile\" class=\"bulk-action-btn\">📡<br><span style=\"font-size: 12px;\">Copy Zone</span></button>\n              <button id=\"bulk-zone-paste-mobile\" class=\"bulk-action-btn\">📡<br><span style=\"font-size: 12px;\">Paste Zone</span></button>\n              <button id=\"bulk-zone-toggle-mobile\" class=\"bulk-action-btn\">📡<br><span style=\"font-size: 12px;\">Toggle Zones</span></button>\n            </div>\n          </div>\n          <button id=\"bulk-clear-mobile\" style=\"width: 100%;margin-top: 16px;padding: 14px;background: var(--panel-main);border: 1px solid var(--edge-main);border-radius: 8px;color: var(--text-main);font-size: 16px;cursor: pointer;\">Clear Selection</button>\n        </div>\n        <div class=\"canvas-hint\" id=\"canvas-hint\" style=\"cursor: pointer;\">\n          <ul>\n            <li>Scroll to zoom</li>\n            <li>Drag to pan</li>\n            <li>Right click to clone and align</li>\n            <li>Right click to select multiple</li>\n\t\t\t<li>Hold Shift + drag mouse for marquee selection</li>\n            <li>You have the power</li>\n            <li>Your time is NOW!</li>\n          </ul>\n        </div>\n        <div class=\"legend-container\" id=\"edge-legend\" style=\"display: flex;\"><div class=\"legend-title\">Line Legend</div><button type=\"button\" class=\"legend-close-btn\">✕</button><div class=\"legend-item\"><span class=\"legend-swatch\" style=\"background-color: rgb(85, 226, 8); cursor: pointer;\"></span><span class=\"legend-label\" contenteditable=\"true\">ISP LINE</span></div><div class=\"legend-item\"><span class=\"legend-swatch\" style=\"background-color: rgb(76, 0, 255); cursor: pointer;\"></span><span class=\"legend-label\" contenteditable=\"true\">MY Guest NETWORK</span></div><div class=\"legend-item\"><span class=\"legend-swatch\" style=\"background-color: rgb(128, 255, 0); cursor: pointer;\"></span><span class=\"legend-label\" contenteditable=\"true\">you can edit me too</span></div><div class=\"legend-item\"><span class=\"legend-swatch\" style=\"background-color: rgb(251, 0, 255); cursor: pointer;\"></span><span class=\"legend-label\" contenteditable=\"true\">you can edit me too</span></div><div class=\"legend-item\"><span class=\"legend-swatch\" style=\"background-color: rgb(255, 0, 208); cursor: pointer;\"></span><span class=\"legend-label\" contenteditable=\"true\">iPhone (always guest iPhone)</span></div><div class=\"legend-item\"><span class=\"legend-swatch\" style=\"background-color: rgb(249, 115, 22); cursor: pointer;\"></span><span class=\"legend-label\" contenteditable=\"true\">you can edit me too</span></div></div>\n        <div class=\"canvas-viewport\" id=\"canvas-viewport\">\n          <svg id=\"map\" viewBox=\"-191.26917538063572 -261.51832729948296 4570.339735330435 3427.7548014978265\" style=\"\"></svg>\n        </div>\n        <div class=\"minimap-zoom-wrapper\" id=\"minimap-zoom-wrapper\" style=\"display: block;\">\n          <button type=\"button\" class=\"minimap-close-btn\" id=\"minimap-close-btn\" title=\"Hide map &amp; zoom\">✕</button>\n          <div class=\"minimap-container\" id=\"minimap-container\">\n            <svg id=\"minimap\" viewBox=\"0 0 4000 3000\">\n              <rect class=\"minimap-viewport\" id=\"minimap-viewport\" x=\"-191.26917538063572\" y=\"-261.51832729948296\" width=\"4570.339735330435\" height=\"3427.7548014978265\"></rect>\n            </svg>\n          </div>\n          <div class=\"zoom-toolbar\" id=\"zoom-toolbar\">\n            <button id=\"zoom-in-btn\" title=\"Zoom in\">+</button>\n            <div class=\"zoom-level\" id=\"zoom-level\">88%</div>\n            <button id=\"zoom-out-btn\" title=\"Zoom out\">-</button>\n            <div class=\"divider\"></div>\n            <button id=\"zoom-fit-btn\" title=\"Fit to view\">[ ]</button>\n            <button id=\"zoom-reset-btn\" title=\"Reset view\">R</button>\n          </div>\n        </div>\n        <button type=\"button\" id=\"edge-legend-mini\" class=\"legend-mini-btn\" style=\"bottom: 10px; display: none;\">Connection &amp; Zone Legend</button>\n        <button type=\"button\" id=\"minimap-mini\" class=\"legend-mini-btn\" style=\"right: 10px; left: auto; bottom: 10px; display: none;\">Map</button>\n        <button type=\"button\" id=\"draw-toolbar-mini\" class=\"legend-mini-btn\" style=\"top: 10px; left: 10px; right: auto;\">Draw</button>\n        <button type=\"button\" id=\"topology-toolbar-mini\" class=\"legend-mini-btn\" style=\"top: 10px; left: auto; right: 40px;\">Add Connection</button></section>\n      <aside class=\"details-panel\" id=\"details-panel\">\n        <div class=\"sidebar-resizer\" id=\"sidebar-resizer\">\n          <svg class=\"resizer-icon\" width=\"6\" height=\"40\" viewBox=\"0 0 6 40\" xmlns=\"http://www.w3.org/2000/svg\">\n            <circle cx=\"3\" cy=\"14\" r=\"2\"></circle>\n            <circle cx=\"3\" cy=\"20\" r=\"2\"></circle>\n            <circle cx=\"3\" cy=\"26\" r=\"2\"></circle>\n          </svg>\n        </div>\n        <div class=\"mobile-footer-resizer\" id=\"mobile-footer-resizer\">\n          <svg class=\"resizer-icon\" width=\"40\" height=\"6\" viewBox=\"0 0 40 6\" xmlns=\"http://www.w3.org/2000/svg\">\n            <circle cx=\"14\" cy=\"3\" r=\"2\"></circle>\n            <circle cx=\"20\" cy=\"3\" r=\"2\"></circle>\n            <circle cx=\"26\" cy=\"3\" r=\"2\"></circle>\n          </svg>\n        </div>\n        <div id=\"node-panel\" style=\"display: none;\">\n          <div class=\"details-name editable-text\" id=\"node-name\">Core Router 1</div>\n          <div class=\"details-ip editable-text\" id=\"node-ip\">10.0.0.1</div>\n          <div class=\"details-role\" id=\"node-role\">Core Routing</div>\n          <div class=\"details-info-row\">\n            <span style=\"color: var(--text-soft); font-size: 14px;\">MAC:</span>\n            <span class=\"editable-text\" id=\"node-mac\" style=\"margin-left: 8px; font-size: 14px;\">00:1A:2B:3C:4D:01</span>\n          </div>\n          <div class=\"details-info-row\">\n            <span style=\"color: var(--text-soft); font-size: 14px;\">Rack Unit:</span>\n            <span class=\"editable-text\" id=\"node-rack\" style=\"margin-left: 8px; font-size: 14px;\">--</span>\n          </div>\n          <div class=\"details-info-row\" id=\"uheight-row\" style=\"display: none;\">\n            <span style=\"color: var(--text-soft); font-size: 14px;\">U Height:</span>\n            <span class=\"editable-text\" id=\"node-uheight\" style=\"margin-left: 8px; font-size: 14px;\">2U</span>\n          </div>\n          <div class=\"details-info-row\">\n            <span style=\"color: var(--text-soft); font-size: 14px;\">Layer:</span>\n            <select id=\"node-layer\" style=\"margin-left: 8px; padding: 4px 8px; background: var(--panel); color: var(--text-main); border: 1px solid var(--edge-main); border-radius: 4px; cursor: pointer; font-size: 14px;\">\n              <option value=\"physical\">Physical</option>\n              <option value=\"logical\">Logical</option>\n              <option value=\"security\">Security</option>\n              <option value=\"application\">Application</option>\n            </select>\n          </div>\n          <div class=\"details-info-row\" id=\"assigned-rack-row\" style=\"display: flex;\">\n            <span style=\"color: var(--text-soft); font-size: 14px;\">Assigned Rack:</span>\n            <select id=\"node-assigned-rack\" style=\"margin-left: 8px; padding: 4px 8px; background: var(--panel); color: var(--text-main); border: 1px solid var(--edge-main); border-radius: 4px; cursor: pointer; font-size: 14px;\"><option value=\"\">None</option><option value=\"racked\">Racked</option></select>\n          </div>\n          <div class=\"details-info-row\" id=\"rack-capacity-row\" style=\"display: none;\">\n            <span style=\"color: var(--text-soft); font-size: 14px;\">Rack Capacity:</span>\n            <select id=\"node-rack-capacity\" style=\"margin-left: 8px; padding: 4px 8px; background: var(--panel); color: var(--text-main); border: 1px solid var(--edge-main); border-radius: 4px; cursor: pointer; font-size: 14px;\">\n              <option value=\"42\">42U</option>\n              <option value=\"48\">48U</option>\n              <option value=\"24\">24U</option>\n              <option value=\"12\">12U</option>\n\t\t\t  <option value=\"6\">6U</option>\n            </select>\n          </div>\n\t\t  <details id=\"rack-contents-section\" style=\"display: none; margin-top: 12px; margin-bottom: 12px; padding: 10px; background: var(--panel-alt); border-radius: 6px;\" open=\"\">\n            <summary style=\"color: var(--accent); font-weight: 600; font-size: 14px; cursor: pointer;\">Nodes in Rack (<span id=\"rack-contents-count\">0</span>)</summary>\n            <div id=\"rack-contents-list\" style=\"max-height: 200px; overflow-y: auto; margin-top: 8px;\"></div>\n          </details>\n\t\t  \n          <details id=\"fov-section\" style=\"display: block; margin-top: 12px; margin-bottom: 12px; padding: 10px; background: var(--panel-alt); border-radius: 6px;\">\n            <summary style=\"color: var(--accent); font-weight: 600; font-size: 14px; cursor: pointer;\">Coverage Zone</summary>\n            <div style=\"padding: 10px 0;\">\n              <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px; gap: 8px;\">\n                <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Preset:</label>\n                <select id=\"fov-preset\" style=\"flex: 1; padding: 4px 8px; background: var(--panel); border: 1px solid var(--edge-main); border-radius: 4px; color: var(--text-main);\">\n                  <option value=\"\">-- Apply Preset --</option>\n                  <option value=\"security-cam\">Security Camera</option>\n                  <option value=\"ptz-cam\">PTZ Camera</option>\n                  <option value=\"motion-detect\">Motion Detector</option>\n                  <option value=\"wifi-strong\">WiFi</option>\n                  <option value=\"wifi-extended\">WiFi Extender</option>\n                  <option value=\"smoke-alarm\">Smoke Alarm</option>\n                  <option value=\"sprinkler-arc\">Sprinkler Arc</option>\n                </select>\n                <button id=\"fov-save-preset\" title=\"Save as preset\" style=\"padding: 4px 8px; background: var(--panel); border: 1px solid var(--edge-main); border-radius: 4px; color: var(--text-main); cursor: pointer;\">💾</button>\n                <button id=\"fov-copy-style\" title=\"Copy zone style\" style=\"padding: 4px 8px; background: var(--panel); border: 1px solid var(--edge-main); border-radius: 4px; color: var(--text-main); cursor: pointer;\">📋</button>\n                <button id=\"fov-paste-style\" title=\"Paste zone style\" style=\"padding: 4px 8px; background: var(--panel); border: 1px solid var(--edge-main); border-radius: 4px; color: var(--text-main); cursor: pointer;\">📥</button>\n              </div>\n              <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Show Zone:</label>\n                <input type=\"checkbox\" id=\"fov-enabled\" style=\"width: 18px; height: 18px; cursor: pointer;\">\n              </div>\n              <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Angle:</label>\n                <input type=\"range\" id=\"fov-angle\" min=\"10\" max=\"360\" value=\"90\" style=\"flex: 1;\">\n                <span id=\"fov-angle-value\" style=\"min-width: 40px; text-align: right; color: var(--text-main);\">360°</span>\n              </div>\n              <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Distance:</label>\n                <input type=\"range\" id=\"fov-distance\" min=\"50\" max=\"500\" value=\"150\" style=\"flex: 1;\">\n                <span id=\"fov-distance-value\" style=\"min-width: 40px; text-align: right; color: var(--text-main);\">200</span>\n              </div>\n              <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Inner Radius:</label>\n                <input type=\"range\" id=\"fov-inner-radius\" min=\"0\" max=\"200\" value=\"0\" style=\"flex: 1;\">\n                <span id=\"fov-inner-radius-value\" style=\"min-width: 40px; text-align: right; color: var(--text-main);\">0</span>\n              </div>\n              <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Rotation:</label>\n                <input type=\"range\" id=\"fov-rotation\" min=\"0\" max=\"360\" value=\"0\" style=\"flex: 1;\">\n                <span id=\"fov-rotation-value\" style=\"min-width: 40px; text-align: right; color: var(--text-main);\">0°</span>\n              </div>\n              <div style=\"border-top: 1px solid var(--edge-main); margin-top: 10px; padding-top: 10px;\">\n                <div style=\"font-size: 12px; color: var(--text-soft); margin-bottom: 8px; text-transform: uppercase; letter-spacing: 0.05em;\">Fill</div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Color:</label>\n                  <input type=\"color\" id=\"fov-color\" value=\"#f59e0b\" style=\"width: 50px; height: 30px; border: 1px solid var(--edge-main); border-radius: 4px; cursor: pointer;\">\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Opacity:</label>\n                  <input type=\"range\" id=\"fov-opacity\" min=\"5\" max=\"80\" value=\"20\" style=\"flex: 1;\">\n                  <span id=\"fov-opacity-value\" style=\"min-width: 40px; text-align: right; color: var(--text-main);\">20%</span>\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Gradient:</label>\n                  <input type=\"checkbox\" id=\"fov-gradient\" style=\"width: 18px; height: 18px; cursor: pointer;\">\n                  <span style=\"color: var(--text-soft); font-size: 12px; margin-left: 8px;\">Fade toward edge</span>\n                </div>\n              </div>\n              <div style=\"border-top: 1px solid var(--edge-main); margin-top: 10px; padding-top: 10px;\">\n                <div style=\"font-size: 12px; color: var(--text-soft); margin-bottom: 8px; text-transform: uppercase; letter-spacing: 0.05em;\">Border</div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Color:</label>\n                  <input type=\"color\" id=\"fov-border-color\" value=\"#f59e0b\" style=\"width: 50px; height: 30px; border: 1px solid var(--edge-main); border-radius: 4px; cursor: pointer;\">\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Width:</label>\n                  <input type=\"range\" id=\"fov-border-width\" min=\"0\" max=\"10\" value=\"2\" style=\"flex: 1;\">\n                  <span id=\"fov-border-width-value\" style=\"min-width: 40px; text-align: right; color: var(--text-main);\">2</span>\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Style:</label>\n                  <select id=\"fov-border-style\" style=\"flex: 1; padding: 4px 8px; background: var(--panel); border: 1px solid var(--edge-main); border-radius: 4px; color: var(--text-main);\">\n                    <option value=\"solid\">Solid</option>\n                    <option value=\"dashed\">Dashed</option>\n                    <option value=\"dotted\">Dotted</option>\n                  </select>\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Opacity:</label>\n                  <input type=\"range\" id=\"fov-border-opacity\" min=\"0\" max=\"100\" value=\"100\" style=\"flex: 1;\">\n                  <span id=\"fov-border-opacity-value\" style=\"min-width: 40px; text-align: right; color: var(--text-main);\">100%</span>\n                </div>\n              </div>\n              <div style=\"border-top: 1px solid var(--edge-main); margin-top: 10px; padding-top: 10px;\">\n                <div style=\"font-size: 12px; color: var(--text-soft); margin-bottom: 8px; text-transform: uppercase; letter-spacing: 0.05em;\">Label</div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Text:</label>\n                  <input type=\"text\" id=\"fov-label\" placeholder=\"e.g. Detection Zone\" style=\"flex: 1; padding: 4px 8px; background: var(--panel); border: 1px solid var(--edge-main); border-radius: 4px; color: var(--text-main);\">\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Position:</label>\n                  <select id=\"fov-label-position\" style=\"flex: 1; padding: 4px 8px; background: var(--panel); border: 1px solid var(--edge-main); border-radius: 4px; color: var(--text-main);\">\n                    <option value=\"center\">Center</option>\n                    <option value=\"edge\">Edge</option>\n                    <option value=\"outside\">Outside</option>\n                  </select>\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Font Size:</label>\n                  <input type=\"range\" id=\"fov-label-size\" min=\"8\" max=\"32\" value=\"14\" style=\"flex: 1;\">\n                  <span id=\"fov-label-size-value\" style=\"min-width: 40px; text-align: right; color: var(--text-main);\">14px</span>\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Font Color:</label>\n                  <input type=\"color\" id=\"fov-label-color\" value=\"#ffffff\" style=\"width: 50px; height: 30px; border: 1px solid var(--edge-main); border-radius: 4px; cursor: pointer;\">\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Bold:</label>\n                  <input type=\"checkbox\" id=\"fov-label-bold\" style=\"width: 18px; height: 18px; cursor: pointer;\">\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Background:</label>\n                  <input type=\"checkbox\" id=\"fov-label-bg\" style=\"width: 18px; height: 18px; cursor: pointer;\">\n                  <input type=\"color\" id=\"fov-label-bg-color\" value=\"#000000\" style=\"width: 50px; height: 30px; border: 1px solid var(--edge-main); border-radius: 4px; cursor: pointer; margin-left: 8px;\">\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Offset X:</label>\n                  <input type=\"range\" id=\"fov-label-offset-x\" min=\"-200\" max=\"200\" value=\"0\" style=\"flex: 1;\">\n                  <span id=\"fov-label-offset-x-value\" style=\"min-width: 40px; text-align: right; color: var(--text-main);\">0</span>\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Offset Y:</label>\n                  <input type=\"range\" id=\"fov-label-offset-y\" min=\"-200\" max=\"200\" value=\"0\" style=\"flex: 1;\">\n                  <span id=\"fov-label-offset-y-value\" style=\"min-width: 40px; text-align: right; color: var(--text-main);\">0</span>\n                </div>\n              </div>\n              <div style=\"border-top: 1px solid var(--edge-main); margin-top: 10px; padding-top: 10px;\">\n                <div style=\"font-size: 12px; color: var(--text-soft); margin-bottom: 8px; text-transform: uppercase; letter-spacing: 0.05em;\">Animation</div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Animate:</label>\n                  <input type=\"checkbox\" id=\"fov-animate\" style=\"width: 18px; height: 18px; cursor: pointer;\">\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Type:</label>\n                  <select id=\"fov-animation-type\" style=\"flex: 1; padding: 4px 8px; background: var(--panel); border: 1px solid var(--edge-main); border-radius: 4px; color: var(--text-main);\">\n                    <option value=\"sweep\">Sweep (Pan)</option>\n                    <option value=\"pulse\">Pulse (Breathe)</option>\n                    <option value=\"rings\">Rings (Emanate)</option>\n                    <option value=\"spin\">Spin (Rotate)</option>\n                  </select>\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Sweep:</label>\n                  <input type=\"range\" id=\"fov-sweep\" min=\"30\" max=\"360\" value=\"120\" style=\"flex: 1;\">\n                  <span id=\"fov-sweep-value\" style=\"min-width: 40px; text-align: right; color: var(--text-main);\">120°</span>\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Speed:</label>\n                  <input type=\"range\" id=\"fov-speed\" min=\"1\" max=\"60\" value=\"4\" style=\"flex: 1;\">\n                  <span id=\"fov-speed-value\" style=\"min-width: 40px; text-align: right; color: var(--text-main);\">4s</span>\n                </div>\n              </div>\n            </div>\n          </details>\n          <details id=\"node-connections-section\" ...=\"\" <details=\"\" style=\"margin-top: 12px; margin-bottom: 12px; padding: 10px; background: var(--panel-alt); border-radius: 6px; display: block;\">\n            <summary style=\"color: var(--accent); font-weight: 600; font-size: 14px; cursor: pointer;\">Connections (<span id=\"node-connections-count\">9</span>)</summary>\n            <div id=\"node-connections-list\" style=\"max-height: 200px; overflow-y: auto; margin-top: 8px;\"><div style=\"padding: 6px 0px; border-bottom: 1px solid var(--edge-main); display: flex; align-items: center; font-size: 13px;\"><span title=\"Click to view connection\" style=\"color: var(--accent); font-family: monospace; cursor: pointer;\">Gi1/0/1</span><span style=\"color: var(--text-soft); margin: 0px 8px;\">↔</span><span title=\"Click to view connection\" style=\"color: var(--text-main); cursor: pointer;\">ISP Primary</span><span title=\"Click to view connection\" style=\"color: var(--accent); font-family: monospace; margin-left: 6px; cursor: pointer;\">(Gi0/0)</span></div><div style=\"padding: 6px 0px; border-bottom: 1px solid var(--edge-main); display: flex; align-items: center; font-size: 13px;\"><span title=\"Click to view connection\" style=\"color: var(--accent); font-family: monospace; cursor: pointer;\">Gi1/0/24</span><span style=\"color: var(--text-soft); margin: 0px 8px;\">↔</span><span title=\"Click to view connection\" style=\"color: var(--text-main); cursor: pointer;\">Core Router 2</span><span title=\"Click to view connection\" style=\"color: var(--accent); font-family: monospace; margin-left: 6px; cursor: pointer;\">(Gi1/0/24)</span></div><div style=\"padding: 6px 0px; border-bottom: 1px solid var(--edge-main); display: flex; align-items: center; font-size: 13px;\"><span title=\"Click to set port\" style=\"color: var(--text-soft); cursor: pointer;\">-</span><span style=\"color: var(--text-soft); margin: 0px 8px;\">↔</span><span title=\"Click to view connection\" style=\"color: var(--text-main); cursor: pointer;\">External FW 1</span><span title=\"Click to set port\" style=\"color: var(--text-soft); margin-left: 6px; cursor: pointer;\">(-)</span></div><div style=\"padding: 6px 0px; border-bottom: 1px solid var(--edge-main); display: flex; align-items: center; font-size: 13px;\"><span title=\"Click to set port\" style=\"color: var(--text-soft); cursor: pointer;\">-</span><span style=\"color: var(--text-soft); margin: 0px 8px;\">↔</span><span title=\"Click to view connection\" style=\"color: var(--text-main); cursor: pointer;\">NYC Branch Router</span><span title=\"Click to set port\" style=\"color: var(--text-soft); margin-left: 6px; cursor: pointer;\">(-)</span></div><div style=\"padding: 6px 0px; border-bottom: 1px solid var(--edge-main); display: flex; align-items: center; font-size: 13px;\"><span title=\"Click to set port\" style=\"color: var(--text-soft); cursor: pointer;\">-</span><span style=\"color: var(--text-soft); margin: 0px 8px;\">↔</span><span title=\"Click to view connection\" style=\"color: var(--text-main); cursor: pointer;\">LA Branch Router</span><span title=\"Click to set port\" style=\"color: var(--text-soft); margin-left: 6px; cursor: pointer;\">(-)</span></div><div style=\"padding: 6px 0px; border-bottom: 1px solid var(--edge-main); display: flex; align-items: center; font-size: 13px;\"><span title=\"Click to set port\" style=\"color: var(--text-soft); cursor: pointer;\">-</span><span style=\"color: var(--text-soft); margin: 0px 8px;\">↔</span><span title=\"Click to view connection\" style=\"color: var(--text-main); cursor: pointer;\">Chicago Branch Router</span><span title=\"Click to set port\" style=\"color: var(--text-soft); margin-left: 6px; cursor: pointer;\">(-)</span></div><div style=\"padding: 6px 0px; border-bottom: 1px solid var(--edge-main); display: flex; align-items: center; font-size: 13px;\"><span title=\"Click to set port\" style=\"color: var(--text-soft); cursor: pointer;\">-</span><span style=\"color: var(--text-soft); margin: 0px 8px;\">↔</span><span title=\"Click to view connection\" style=\"color: var(--text-main); cursor: pointer;\">London Branch Router</span><span title=\"Click to set port\" style=\"color: var(--text-soft); margin-left: 6px; cursor: pointer;\">(-)</span></div><div style=\"padding: 6px 0px; border-bottom: 1px solid var(--edge-main); display: flex; align-items: center; font-size: 13px;\"><span title=\"Click to set port\" style=\"color: var(--text-soft); cursor: pointer;\">-</span><span style=\"color: var(--text-soft); margin: 0px 8px;\">↔</span><span title=\"Click to view connection\" style=\"color: var(--text-main); cursor: pointer;\">Tokyo Branch Router</span><span title=\"Click to set port\" style=\"color: var(--text-soft); margin-left: 6px; cursor: pointer;\">(-)</span></div><div style=\"padding: 6px 0px; border-bottom: 1px solid var(--edge-main); display: flex; align-items: center; font-size: 13px;\"><span title=\"Click to set port\" style=\"color: var(--text-soft); cursor: pointer;\">-</span><span style=\"color: var(--text-soft); margin: 0px 8px;\">↔</span><span title=\"Click to view connection\" style=\"color: var(--text-main); cursor: pointer;\">AWS Cloud</span><span title=\"Click to set port\" style=\"color: var(--text-soft); margin-left: 6px; cursor: pointer;\">(-)</span></div></div>\n          </details>\n          <div class=\"badge-row\" id=\"node-tags\"></div>\n          <div class=\"size-controls\">\n            <label>Size:</label>\n            <input type=\"range\" id=\"size-slider\" min=\"20\" max=\"200\" value=\"55\">\n            <span id=\"size-value\">55</span>\n            <button id=\"reset-size\">Reset</button>\n          </div>\n          <div class=\"size-controls\">\n            <label>Rotation:</label>\n            <input type=\"range\" id=\"rotation-slider\" min=\"-360\" max=\"360\" value=\"0\">\n            <input type=\"number\" id=\"rotation-value\" value=\"0\" style=\"width: 60px; padding: 4px; background: var(--panel); border: 1px solid var(--edge-main); border-radius: 4px; color: var(--text-main); text-align: center;\">\n            <span style=\"color: var(--text-soft);\">°</span>\n            <button id=\"reset-rotation\">Reset</button>\n          </div>\n          <details class=\"style-section\">\n            <summary>Styling</summary>\n            <div class=\"style-content\">\n              <div class=\"style-row\">\n                <label>Screen:</label>\n                <select id=\"style-scope\">\n                  <option value=\"all\">All</option>\n                  <option value=\"desktop\">Desktop</option>\n                  <option value=\"tablet\">Tablet</option>\n                  <option value=\"mobile\">Mobile</option>\n                  <option value=\"fold\">Fold</option>\n                </select>\n              </div>\n              <div class=\"style-row\">\n                <label>Shape:</label>\n                <select id=\"shape-select\">\n                  <optgroup label=\"Basic Shapes\">\n\t\t\t\t  <option value=\"circle\">Circle</option>\n\t\t\t\t  <option value=\"square\">Square</option>\n\t\t\t\t  <option value=\"rectangle\">Rectangle</option>\n\t\t\t\t  <option value=\"triangle\">Triangle</option>\n\t\t\t\t  <option value=\"hexagon\">Hexagon</option>\n\t\t\t\t  <option value=\"diamond\">Diamond</option>\n\t\t\t\t  <option value=\"star\">Star</option>\n\t\t\t\t  <option value=\"stop-sign\">Stop Sign</option>\n\t\t\t\t  <option value=\"octagon\">Octagon</option>\n\t\t\t\t  <option value=\"pentagon\">Pentagon</option>\n\t\t\t\t  <option value=\"cross\">Cross</option>\n\t\t\t\t  <option value=\"rounded-square\">Rounded Square</option>\n\t\t\t\t  <option value=\"pill\">Pill</option>\n\t\t\t\t  <option value=\"parallelogram\">Parallelogram</option>\n\t\t\t\t  <option value=\"trapezoid\">Trapezoid</option>\n\t\t\t\t</optgroup>\n\t\t\t  <optgroup label=\"Computers &amp; Devices\">\n\t\t\t      <option value=\"server\">Server</option>\n\t\t\t\t  <option value=\"pc\">PC / Desktop</option>\n\t\t\t\t  <option value=\"laptop\">Laptop</option>\n\t\t\t\t  <option value=\"phone\">Phone / Mobile</option>\n\t\t\t\t  <option value=\"printer\">Printer</option>\n\t\t\t\t  <option value=\"pi\">Raspberry Pi</option>\n\t\t\t\t  <option value=\"sensor\">Sensor / IoT</option>\n\t\t\t  </optgroup>\n\t\t\t  <optgroup label=\"Network Equipment\">\n\t\t\t\t  <option value=\"router\">Router</option>\n\t\t\t\t  <option value=\"switch\">Switch</option>\n\t\t\t\t  <option value=\"firewall\">Firewall</option>\n\t\t\t\t  <option value=\"access-point\">Access Point</option>\n\t\t\t\t  <option value=\"load-balancer\">Load Balancer</option>\n\t\t\t\t  <option value=\"gateway\">Gateway</option>\n\t\t\t\t  <option value=\"vpn\">VPN / Tunnel</option>\n\t\t\t\t  <option value=\"nas\">NAS / Storage</option>\n\t\t\t  </optgroup>\n\t\t\t  <optgroup label=\"Cloud &amp; Services\">\n\t\t\t      <option value=\"cloud\">Cloud</option>\n\t\t\t\t  <option value=\"database\">Database</option>\n\t\t\t\t  <option value=\"docker\">Docker</option>\n\t\t\t\t  <option value=\"container\">Container</option>\n\t\t\t\t  <option value=\"vm\">Virtual Machine</option>\n\t\t\t\t  <option value=\"kubernetes\">Kubernetes</option>\n\t\t\t\t  <option value=\"api\">API / Endpoint</option>\n\t\t\t\t  <option value=\"queue\">Queue / Message</option>\n\t\t\t\t  <option value=\"lambda\">Lambda / Function</option>\n\t\t\t\t  <option value=\"bucket\">Bucket / S3</option>\n\t\t\t  </optgroup>\n\t\t\t  <optgroup label=\"Security &amp; Monitoring\">\n\t\t\t\t  <option value=\"shield\">Shield</option>\n\t\t\t\t  <option value=\"camera\">Camera / CCTV</option>\n\t\t\t\t  <option value=\"monitor\">Monitor / Dashboard</option>\n\t\t\t  </optgroup>\n\t\t\t  <optgroup label=\"Smart Home\">\n\t\t\t\t  <option value=\"thermostat\">Thermostat</option>\n\t\t\t\t  <option value=\"doorbell\">Video Doorbell</option>\n\t\t\t\t  <option value=\"smart-lock\">Smart Lock</option>\n\t\t\t\t  <option value=\"smart-bulb\">Smart Bulb</option>\n\t\t\t\t  <option value=\"smart-plug\">Smart Plug</option>\n\t\t\t\t  <option value=\"smart-speaker\">Smart Speaker</option>\n\t\t\t\t  <option value=\"smart-tv\">Smart TV</option>\n\t\t\t\t  <option value=\"hub\">Smart Hub</option>\n\t\t\t\t  <option value=\"smoke-detector\">Smoke Detector</option>\n\t\t\t\t  <option value=\"motion-sensor\">Motion Sensor</option>\n\t\t\t\t  <option value=\"garage\">Garage Door</option>\n\t\t\t\t  <option value=\"sprinkler\">Sprinkler</option>\n\t\t\t\t  <option value=\"vacuum\">Robot Vacuum</option>\n\t\t\t  </optgroup>\n             </select>\n              </div>\n              <div class=\"style-row\">\n                <label>Fill Color:</label>\n                <input type=\"color\" id=\"circle-color\" value=\"#1e293b\">\n              </div>\n              <div class=\"style-row\">\n                <label>Border Color:</label>\n                <input type=\"color\" id=\"circle-border\" value=\"#475569\">\n              </div>\n              <div class=\"style-row\">\n                <label>Title Color:</label>\n                <input type=\"color\" id=\"title-color\" value=\"#e2e8f0\">\n              </div>\n              <div class=\"style-row\">\n                <label>Title Font:</label>\n                <select id=\"title-font\">\n                  <option value=\"system-ui, sans-serif\"> System UI </option>\n                  <option value=\"monospace\">Monospace</option>\n                  <option value=\"serif\">Serif</option>\n                  <option value=\"cursive\">Cursive</option>\n                  <option value=\"'Courier New', monospace\"> Courier </option>\n                  <option value=\"Arial, sans-serif\">Arial</option>\n                  <option value=\"'Times New Roman', serif\"> Times </option>\n                </select>\n              </div>\n              <div class=\"style-row\">\n                <label>Title Size:</label>\n                <input type=\"number\" id=\"title-size\" min=\"8\" max=\"100\" value=\"18\">\n              </div>\n              <div class=\"style-row\">\n                <label>Sub Color:</label>\n                <input type=\"color\" id=\"sub-color\" value=\"#94a3b8\">\n              </div>\n              <div class=\"style-row\">\n                <label>Sub Font:</label>\n                <select id=\"sub-font\">\n                  <option value=\"system-ui, sans-serif\"> System UI </option>\n                  <option value=\"monospace\">Monospace</option>\n                  <option value=\"serif\">Serif</option>\n                  <option value=\"cursive\">Cursive</option>\n                  <option value=\"'Courier New', monospace\"> Courier </option>\n                  <option value=\"Arial, sans-serif\">Arial</option>\n                  <option value=\"'Times New Roman', serif\"> Times </option>\n                </select>\n              </div>\n              <div class=\"style-row\">\n                <label>Sub Size:</label>\n                <input type=\"number\" id=\"sub-size\" min=\"6\" max=\"80\" value=\"13\">\n              </div>\n              <div style=\"margin-top: 12px;padding-top: 10px;border-top: 1px solid var(--edge-main);\">\n                <div style=\"\n                  font-size: 12px;\n                  color: var(--text-soft);\n                  margin-bottom: 8px;\n                  text-transform: uppercase;\n                  \"> Text Position </div>\n                <div class=\"style-row\">\n                  <label>Name Y:</label>\n                  <input type=\"number\" id=\"title-offset-y\" min=\"-100\" max=\"100\" value=\"0\" style=\"width: 60px\">\n                </div>\n                <div class=\"style-row\">\n                  <label>Name X:</label>\n                  <input type=\"number\" id=\"title-offset-x\" min=\"-100\" max=\"100\" value=\"0\" style=\"width: 60px\">\n                </div>\n                <div class=\"style-row\">\n                  <label>IP Y:</label>\n                  <input type=\"number\" id=\"sub-offset-y\" min=\"-100\" max=\"100\" value=\"0\" style=\"width: 60px\">\n                </div>\n                <div class=\"style-row\">\n                  <label>IP X:</label>\n                  <input type=\"number\" id=\"sub-offset-x\" min=\"-100\" max=\"100\" value=\"0\" style=\"width: 60px\">\n                </div>\n              </div>\n              <button id=\"reset-styles\" style=\"width: 100%;margin-top: 10px;padding: 8px;background: var(--panel);color: var(--text-main);border: 1px solid var(--edge-main);border-radius: 6px;cursor: pointer;font-size: clamp(13px, 1.5vw, 17px);\">Reset Styles</button>\n            </div>\n          </details>\n          <div class=\"section-label\">Notes</div>\n          <ul class=\"list\" id=\"node-notes\"></ul>\n          <div style=\"margin-top: 10px; display: flex; gap: 8px\">\n            <input type=\"text\" id=\"new-note-input\" placeholder=\"Type new note...\" style=\"flex: 1;padding: 8px 12px;background: var(--panel);border: 1px solid var(--edge-main);border-radius: 6px;color: var(--text-main);font-size: clamp(14px, 1.6vw, 18px);\">\n            <button id=\"add-note-btn\" style=\"padding: 8px 16px;background: var(--accent);color: var(--bg);border: none;border-radius: 6px;cursor: pointer;font-size: clamp(14px, 1.6vw, 18px);font-weight: 600;\">Add</button>\n          </div>\n          <button id=\"delete-node-btn\" style=\"margin-top: 15px;padding: 10px 16px;background: var(--danger);color: rgb(255, 255, 255);border: none;border-radius: 6px;cursor: pointer;font-size: clamp(14px, 1.6vw, 18px);font-weight: 600;width: 100%;\">Delete Node</button>\n        </div>\n        <div id=\"edge-panel\" style=\"display: none\">\n          <div class=\"details-name\" id=\"edge-title\">Custom line</div>\n          <div class=\"style-row\" style=\"margin-top: 10px\">\n            <label>Width:</label>\n            <input type=\"number\" id=\"edge-width\" min=\"1\" max=\"20\" value=\"4\">\n          </div>\n          <div class=\"style-row\">\n            <label>Color:</label>\n            <input type=\"color\" id=\"edge-color\" value=\"#475569\">\n          </div>\n          <div class=\"style-row\">\n            <label>Line Style:</label>\n            <select id=\"edge-line-style\">\n  <option value=\"solid\">Solid</option>\n  <option value=\"dashed\">Dashed</option>\n  <option value=\"dotted\">Dotted</option>\n  <option value=\"wall\">Wall</option>\n</select>\n          </div>\n          <div class=\"style-row\">\n            <label>Routing:</label>\n            <select id=\"edge-routing\">\n              <option value=\"orthogonal\">Orthogonal (90°)</option>\n              <option value=\"curved\">Curved</option>\n              <option value=\"straight\">Straight</option>\n            </select>\n          </div>\n          <div class=\"style-row\">\n            <label>Direction:</label>\n            <select id=\"edge-direction\">\n              <option value=\"none\">No arrows</option>\n              <option value=\"forward\">→ Forward</option>\n              <option value=\"backward\">← Backward</option>\n              <option value=\"both\">↔ Bidirectional</option>\n            </select>\n          </div>\n          <div class=\"style-row\">\n            <label>Animate:</label>\n            <input type=\"checkbox\" id=\"edge-animate\">\n          </div>\n          <div class=\"style-row\">\n            <label>Style:</label>\n            <select id=\"edge-animation-style\">\n              <option value=\"\">Default</option>\n              <option value=\"arrows\">Flowing Arrows</option>\n              <option value=\"dots\">Dot Arrows</option>\n            </select>\n          </div>\n          <div class=\"style-row\">\n            <label>Speed:</label>\n            <select id=\"edge-animation-speed\">\n              <option value=\"\">Default</option>\n              <option value=\"0.5\">Very Fast</option>\n              <option value=\"1\">Fast</option>\n              <option value=\"1.5\">Normal</option>\n              <option value=\"2.5\">Slow</option>\n              <option value=\"4\">Very Slow</option>\n            </select>\n          </div>\n          <div class=\"style-row\" id=\"edge-from-port-row\" style=\"display: none;\">\n            <label>From Port:</label>\n            <input type=\"text\" id=\"edge-from-port\" placeholder=\"e.g. eth0, gi0/1\" style=\"flex: 1;\">\n          </div>\n          <div class=\"style-row\" id=\"edge-to-port-row\" style=\"display: none;\">\n            <label>To Port:</label>\n            <input type=\"text\" id=\"edge-to-port\" placeholder=\"e.g. eth1, gi0/2\" style=\"flex: 1;\">\n          </div>\n          <div class=\"section-label\">Line Notes</div>\n          <ul class=\"list\" id=\"edge-notes\"></ul>\n          <div style=\"margin-top: 10px; display: flex; gap: 8px\">\n            <input type=\"text\" id=\"new-edge-note\" placeholder=\"Add note...\" style=\"flex: 1;padding: 8px 12px;background: var(--panel);border: 1px solid var(--edge-main);border-radius: 6px;color: var(--text-main);font-size: clamp(14px, 1.6vw, 18px);\">\n            <button id=\"add-edge-note\" style=\"padding: 8px 16px;background: var(--accent);color: var(--bg);border: none;border-radius: 6px;cursor: pointer;font-size: clamp(14px, 1.6vw, 18px);font-weight: 600;\">Add</button>\n          </div>\n          <button id=\"delete-edge\" style=\"margin-top: 15px;padding: 10px 16px;background: var(--danger);color: #fff;border: none;border-radius: 6px;cursor: pointer;font-size: clamp(14px, 1.6vw, 18px);font-weight: 600;\">Delete Line</button>\n        </div>\n        <div id=\"rect-panel\" style=\"display: none\">\n          <div class=\"details-name\" id=\"rect-title\">Zone</div>\n          <div class=\"style-row\" id=\"rect-fill-row\" style=\"margin-top: 10px\">\n            <label>Fill Color:</label>\n            <input type=\"color\" id=\"rect-color\" value=\"#f97316\">\n          </div>\n          <div class=\"style-row\">\n            <label>Border Color:</label>\n            <input type=\"color\" id=\"rect-border-color\" value=\"#f97316\">\n          </div>\n          <div class=\"style-row\">\n            <label>Border Width:</label>\n            <input type=\"number\" id=\"rect-border-width\" min=\"0\" max=\"20\" value=\"2\">\n          </div>\n          <div class=\"style-row\">\n            <label>Style:</label>\n            <select id=\"rect-style-select\">\n              <option value=\"filled\">Filled</option>\n              <option value=\"outlined\">Outlined</option>\n            </select>\n          </div>\n          <div class=\"style-row\">\n            <label>Line Style:</label>\n            <select id=\"rect-line-style\">\n  <option value=\"solid\">Solid</option>\n  <option value=\"dashed\">Dashed</option>\n  <option value=\"dotted\">Dotted</option>\n  <option value=\"wall\">Wall</option>\n</select>\n          </div>\n          <div class=\"style-row\">\n            <label>Rotation:</label>\n            <input type=\"range\" id=\"rect-rotation\" min=\"-360\" max=\"360\" value=\"0\" style=\"flex: 1;\">\n            <input type=\"number\" id=\"rect-rotation-value\" value=\"0\" style=\"width: 60px; padding: 4px; background: var(--panel); border: 1px solid var(--edge-main); border-radius: 4px; color: var(--text-main); text-align: center;\">\n            <span style=\"color: var(--text-soft);\">°</span>\n          </div>\n          <div class=\"section-label\">Zone Notes</div>\n          <ul class=\"list\" id=\"rect-notes\"></ul>\n          <div style=\"margin-top: 10px; display: flex; gap: 8px\">\n            <input type=\"text\" id=\"new-rect-note\" placeholder=\"Add note...\" style=\"flex: 1;padding: 8px 12px;background: var(--panel);border: 1px solid var(--edge-main);border-radius: 6px;color: var(--text-main);font-size: clamp(14px, 1.6vw, 18px);\">\n            <button id=\"add-rect-note\" style=\"padding: 8px 16px;background: var(--accent);color: var(--bg);border: none;border-radius: 6px;cursor: pointer;font-size: clamp(14px, 1.6vw, 18px);font-weight: 600;\">Add</button>\n          </div>\n          <button id=\"delete-rect\" style=\"margin-top: 15px;padding: 10px 16px;background: var(--danger);color: #fff;border: none;border-radius: 6px;cursor: pointer;font-size: clamp(14px, 1.6vw, 18px);font-weight: 600;\">Delete Zone</button>\n        </div>\n        <div id=\"text-panel\" style=\"display: none\">\n          <div class=\"details-name\" id=\"text-title\">Text Element</div>\n          <div class=\"style-row\" style=\"margin-top: 10px\">\n            <label>Content:</label>\n            <textarea id=\"text-content\" placeholder=\"Enter text...\" style=\"width: 100%;padding: 8px 12px;background: var(--panel);border: 1px solid var(--edge-main);border-radius: 6px;color: var(--text-main);font-size: 14px;min-height: 80px;resize: vertical;font-family: inherit;\"></textarea>\n          </div>\n          <div class=\"style-row\">\n            <label>Font Size:</label>\n            <input type=\"number\" id=\"text-font-size\" min=\"8\" max=\"200\" value=\"18\" style=\"flex: 1;padding: 6px 10px;background: var(--panel);border: 1px solid var(--edge-main);border-radius: 4px;color: var(--text-main);\">\n          </div>\n          <div class=\"style-row\">\n            <label>Color:</label>\n            <input type=\"color\" id=\"text-color\" value=\"#e2e8f0\">\n          </div>\n          <div class=\"style-row\">\n            <label>Font Weight:</label>\n            <select id=\"text-font-weight\" style=\"flex: 1;padding: 6px 10px;background: var(--panel);border: 1px solid var(--edge-main);border-radius: 4px;color: var(--text-main);cursor: pointer;\">\n              <option value=\"normal\">Normal</option>\n              <option value=\"bold\">Bold</option>\n              <option value=\"600\">Semi-Bold</option>\n              <option value=\"300\">Light</option>\n            </select>\n          </div>\n          <div class=\"style-row\">\n            <label>Font Style:</label>\n            <select id=\"text-font-style\" style=\"flex: 1;padding: 6px 10px;background: var(--panel);border: 1px solid var(--edge-main);border-radius: 4px;color: var(--text-main);cursor: pointer;\">\n              <option value=\"normal\">Normal</option>\n              <option value=\"italic\">Italic</option>\n            </select>\n          </div>\n          <div class=\"style-row\">\n            <label>Text Align:</label>\n            <select id=\"text-align\" style=\"flex: 1;padding: 6px 10px;background: var(--panel);border: 1px solid var(--edge-main);border-radius: 4px;color: var(--text-main);cursor: pointer;\">\n              <option value=\"start\">Left</option>\n              <option value=\"middle\">Center</option>\n              <option value=\"end\">Right</option>\n            </select>\n          </div>\n          <div class=\"style-row\">\n            <label>Text Decoration:</label>\n            <select id=\"text-decoration\" style=\"flex: 1;padding: 6px 10px;background: var(--panel);border: 1px solid var(--edge-main);border-radius: 4px;color: var(--text-main);cursor: pointer;\">\n              <option value=\"none\">None</option>\n              <option value=\"underline\">Underline</option>\n              <option value=\"line-through\">Strike Through</option>\n            </select>\n          </div>\n          <div class=\"style-row\">\n            <label>Background:</label>\n            <input type=\"color\" id=\"text-bg-color\" value=\"#000000\">\n            <label style=\"margin-left: 10px; display: flex; align-items: center; gap: 6px;\">\n            <input type=\"checkbox\" id=\"text-bg-enabled\" style=\"cursor: pointer;\">\n            <span style=\"font-size: 13px;\">Enable</span>\n            </label>\n          </div>\n          <div class=\"style-row\">\n            <label>Opacity:</label>\n            <input type=\"range\" id=\"text-opacity\" min=\"0\" max=\"1\" step=\"0.1\" value=\"1\" style=\"flex: 1\">\n            <span id=\"text-opacity-val\" style=\"min-width: 35px; text-align: right\">100%</span>\n          </div>\n          <div class=\"style-row\">\n            <label>Rotation:</label>\n            <input type=\"range\" id=\"text-rotation\" min=\"-360\" max=\"360\" value=\"0\" style=\"flex: 1\">\n            <input type=\"number\" id=\"text-rotation-val\" value=\"0\" style=\"width: 60px; padding: 4px; background: var(--panel); border: 1px solid var(--edge-main); border-radius: 4px; color: var(--text-main); text-align: center;\">\n            <span style=\"color: var(--text-soft);\">°</span>\n          </div>\n          <button id=\"delete-text\" style=\"margin-top: 15px;padding: 10px 16px;background: var(--danger);color: #fff;border: none;border-radius: 6px;cursor: pointer;font-size: clamp(14px, 1.6vw, 18px);font-weight: 600;width: 100%;\"> Delete Text </button>\n        </div>\n        <details class=\"style-section\" open=\"\">\n          <summary>Page Layout</summary>\n          <div class=\"style-content\">\n            <div style=\"padding: 12px; background: var(--panel-alt); border-radius: 6px; color: var(--text-soft); font-size: 13px; line-height: 1.6;\">\n              <strong style=\"color: var(--accent); display: block; margin-bottom: 8px;\">Drag to Resize:</strong>\n              • <strong>Header:</strong> Drag the bottom edge of the header bar<br>\n              • <strong>Sidebar:</strong> Drag the left edge of the sidebar panel<br>\n              • <strong>Mobile:</strong> Drag the top edge of the footer panel<br>\n              <div style=\"margin-top: 8px; padding-top: 8px; border-top: 1px solid var(--edge-main); font-size: 12px;\">\n                Hover over panel edges to see resize handles\n              </div>\n            </div>\n          </div>\n        </details>\n      </aside>\n    </main>\n    <script id=\"nodes-json\" type=\"application/json\">{}</script>\n    <script id=\"topology-state\" type=\"application/json\">ENCRYPTED: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</script>\n    <script>\n      const BASE_NODE_DATA = JSON.parse(document.getElementById(\"nodes-json\").textContent);\n      let currentNodeId = \"host\";\n      let textDrawMode = false;\n      let currentEdgeId = null;\n      let currentStyleScope = \"all\";\n      let NODE_DATA = {};\n      let EDGE_DATA = {\n       list: []\n      };\n      let currentRectId = null;\n      let currentTextId = null;\n      let rectDrawMode = false;\n      let RECT_DATA = {\n       list: []\n      };\n      let TEXT_DATA = {\n       list: []\n      };\n      let EDGE_LEGEND = {};\n      let freeDrawMode = false;\n      let savedPositions = {};\n      let savedSizes = {};\n      let savedStyles = {};\n      let legendCollapsed = false;\n      let minimapCollapsed = false;\n      let drawToolbarCollapsed = false;\n      let activeLayers = new Set([\"physical\", \"logical\", \"security\", \"application\"]);\n      let currentView = {\n       mode: \"topology\",\n       rackId: null\n      };\n      let savedTopologyView = null;\n      let topologyToolbarCollapsed = false;\n      let legendMiniBtn = null;\n      let minimapMiniBtn = null;\n      let drawToolbarMiniBtn = null;\n      let topologyToolbarMiniBtn = null;\n      let undoStack = [];\n      let redoStack = [];\n      const MAX_UNDO_STACK = 50;\n      let forgeDebounceTimer = null;\n      let forgeImmediate = false;\n      let currentSearchQuery = \"\";\n      let currentSearchResults = [];\n      const AUTOSAVE_DB_NAME = \"TheOneFileAutosave\";\n      const AUTOSAVE_STORE_NAME = \"drafts\";\n      const AUTOSAVE_KEY = \"currentDraft\";\n      const AUTOSAVE_INTERVAL = 30000;\n      let autosaveTimer = null;\n      let lastAutosaveTime = 0;\n      let selectedNodes = new Set();\n      let selectedEdges = new Set();\n      let selectedRects = new Set();\n      let selectedTexts = new Set();\n      let isSelecting = false;\n      let selectionStart = null;\n      let preDragSelectedNodes = new Set();\n      let preDragSelectedEdges = new Set();\n      let preDragSelectedRects = new Set();\n      let preDragSelectedTexts = new Set();\n      let selectionRect = null;\n      let selectionBoxStyle = {\n       fillColor: \"#4fd1c5\",\n       fillOpacity: 0.1,\n       strokeColor: \"#4fd1c5\",\n       strokeWidth: 2,\n       strokeDasharray: \"5,5\"\n      };\n      let isDraggingSelection = false;\n      let dragSelectionStart = null;\n      let clipboard = null;\n      const ROLLBACK_STORAGE_KEY = \"theonefile_rollbacks\";\n      const MAX_ROLLBACK_VERSIONS = 50;\n      let rollbackVersions = [];\n      let currentRollbackIndex = -1;\n      const AUDIT_STORAGE_KEY = \"theonefile_audit_log\";\n      let auditLog = [];\n      const MAX_AUDIT_ENTRIES = 1000;\n      let savedStyleSets = [];\n      let documentTabs = [{\n        id: \"main\",\n        name: \"Main Topology\",\n        nodes: {},\n        edges: { list: [] },\n        positions: {},\n        sizes: {},\n        styles: {},\n        legend: {},\n        rects: { list: [] },\n        texts: { list: [] },\n      pageState: null\n      }];\n      let currentTabIndex = 0;\n      let encryptedSections = {};\n      let performanceMode = \"auto\";\n      let cullOffscreenNodes = true;\n      let minimapNeedsUpdate = true;\n      let lastMinimapUpdate = 0;\n      const MobileManager = {\n        isMobile: false,\n        detect() {\n          if (navigator.userAgentData?.mobile === true) {\n            this.isMobile = true;\n            return true;\n          }\n          const coarse = matchMedia(\"(pointer: coarse)\").matches;\n          const width = window.innerWidth <= 900;\n          const portrait = matchMedia(\"(orientation: portrait)\").matches;\n          this.isMobile = coarse;\n          return this.isMobile;\n        },\n        applyInitialCollapse() {\n          if (!this.isMobile) return;\n          legendCollapsed = true;\n          minimapCollapsed = true;\n          drawToolbarCollapsed = true;\n          topologyToolbarCollapsed = true;\n          if (typeof updateLegendVisibility === \"function\") updateLegendVisibility();\n          if (typeof updateMinimapVisibility === \"function\") updateMinimapVisibility();\n          if (typeof updateDrawToolbarVisibility === \"function\") updateDrawToolbarVisibility();\n          if (typeof updateTopologyToolbarVisibility === \"function\") updateTopologyToolbarVisibility();\n        },\n        updateBulkToolbar() {\n          const desktop = document.getElementById(\"bulk-toolbar\");\n          const mobile = document.getElementById(\"bulk-toolbar-mobile\");\n          if (!desktop || !mobile) return;\n          if (typeof selectedNodes === 'undefined' || selectedNodes.size === 0) {\n           desktop.style.display = \"none\";\n           mobile.style.display = \"none\";\n           return;\n          }\n          const isVisible = desktop.style.display !== \"none\" || mobile.style.display !== \"none\";\n          if (this.isMobile) {\n            desktop.style.display = \"none\";\n            if (isVisible) {\n              mobile.style.display = \"flex\";\n            }\n          } else {\n            mobile.style.display = \"none\";\n            if (isVisible) {\n              desktop.style.display = \"flex\";\n            }\n          }\n        },\n        updateCanvasHint() {\n      const hint = document.getElementById(\"canvas-hint\");\n      if (!hint) return;\n      if (PAGE_STATE.canvasHintText !== undefined && PAGE_STATE.canvasHintText !== \"\") {\n        hint.textContent = PAGE_STATE.canvasHintText;\n        return;\n      }\n      const items = this.isMobile\n      ? [\n        \"Pinch to zoom\",\n        \"Drag to pan\",\n        \"Add node from top menu\",\n        \"Double tap to clone and align\",\n        \"Double tap to select multiple\",\n        \"You have the power\",\n        \"Your time is NOW!\",\n      ]\n      : [\n        \"Scroll to zoom\",\n        \"Drag to pan\",\n        \"Right click to clone and align\",\n        \"Right click to select multiple\",\n\t    \"Hold Shift + drag mouse for marquee selection\",\n        \"You have the power\",\n        \"Your time is NOW!\",\n      ];\n      const list = document.createElement(\"ul\");\n      for (const item of items) {\n      const li = document.createElement(\"li\");\n      li.textContent = item;\n      list.appendChild(li);\n      }\n      hint.replaceChildren(list);\n      },\n        autoSelectStyleScope() {\n          const scope = document.getElementById(\"style-scope\");\n          if (!scope) return;\n          if (this.isMobile) scope.value = \"mobile\";\n        },\n        updateToolbarStack() {\n          if (!this.isMobile) return;\n          const draw = document.getElementById(\"draw-toolbar\");\n          const topo = document.getElementById(\"topology-toolbar\");\n          if (!draw || !topo) return;\n          const h = draw.getBoundingClientRect().height;\n          document.documentElement.style.setProperty(\"--draw-toolbar-height\", h + \"px\");\n        },\n        updateLayoutControls() {\n          const sidebarRow = document.getElementById(\"sidebar-width-row\");\n          const footerRow = document.getElementById(\"mobile-footer-row\");\n          if (sidebarRow && footerRow) {\n            if (this.isMobile) {\n              sidebarRow.style.display = \"none\";\n              footerRow.style.display = \"flex\";\n            } else {\n              sidebarRow.style.display = \"flex\";\n              footerRow.style.display = \"none\";\n            }\n          }\n        },\n        applyAll() {\n          this.detect();\n          this.applyInitialCollapse();\n          this.updateBulkToolbar();\n\t\t  this.updateCanvasHint();\n          this.autoSelectStyleScope();\n          this.updateToolbarStack();\n          this.updateLayoutControls();\n        }\n      };\n      function isMobileDevice() {\n        return MobileManager.isMobile;\n      }\n\t  function escapeHtml(str) {\n       if (!str) return '';\n       return String(str).replace(/[&<>\"']/g, c => ({\n        '&': '&amp;', '<': '&lt;', '>': '&gt;', '\"': '&quot;', \"'\": '&#39;'\n       }[c]));\n      }\n      function u8ToBase64(u8) {\n      let binary = \"\";\n      for (let i = 0; i < u8.length; i++) binary += String.fromCharCode(u8[i]);\n      return btoa(binary);\n      }\n      function base64ToU8(base64) {\n      const binary = atob(base64);\n      const bytes = new Uint8Array(binary.length);\n      for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);\n      return bytes;\n      }\n      function openAutosaveDB() {\n       return new Promise((resolve, reject) => {\n        const request = indexedDB.open(AUTOSAVE_DB_NAME, 1);\n        request.onerror = () => reject(request.error);\n        request.onsuccess = () => resolve(request.result);\n        request.onupgradeneeded = (e) => {\n         const db = e.target.result;\n         if (!db.objectStoreNames.contains(AUTOSAVE_STORE_NAME)) {\n          db.createObjectStore(AUTOSAVE_STORE_NAME, { keyPath: \"id\" });\n         }\n        };\n       });\n      }\n      async function saveToIndexedDB() {\n       try {\n        const db = await openAutosaveDB();\n        const currentTab = documentTabs[currentTabIndex];\n        currentTab.nodes = JSON.parse(JSON.stringify(NODE_DATA));\n        currentTab.edges = JSON.parse(JSON.stringify(EDGE_DATA));\n        currentTab.positions = JSON.parse(JSON.stringify(savedPositions));\n        currentTab.sizes = JSON.parse(JSON.stringify(savedSizes));\n        currentTab.styles = JSON.parse(JSON.stringify(savedStyles));\n        currentTab.legend = JSON.parse(JSON.stringify(EDGE_LEGEND));\n        currentTab.rects = JSON.parse(JSON.stringify(RECT_DATA));\n        currentTab.texts = JSON.parse(JSON.stringify(TEXT_DATA));\n        currentTab.pageState = JSON.parse(JSON.stringify(PAGE_STATE));\n        const state = {\n         id: AUTOSAVE_KEY,\n         timestamp: Date.now(),\n         pageTitle: document.getElementById(\"page-title\")?.textContent || \"The One File\",\n         nodeData: NODE_DATA,\n         edgeData: EDGE_DATA,\n         rectData: RECT_DATA,\n         textData: TEXT_DATA,\n         edgeLegend: EDGE_LEGEND,\n         zoneLegend: ZONE_LEGEND,\n         zonePresets: ZONE_PRESETS,\n         nodePositions: savedPositions,\n         nodeSizes: savedSizes,\n         nodeStyles: savedStyles,\n         page: PAGE_STATE,\n         canvas: {\n          zoom: canvasState.zoom,\n          panX: canvasState.panX,\n          panY: canvasState.panY,\n         },\n         savedTopologyView: savedTopologyView,\n         documentTabs: documentTabs,\n         currentTabIndex: currentTabIndex,\n         encryptedSections: encryptedSections,\n         auditLog: auditLog,\n\t\t savedStyleSets: savedStyleSets,\n\t\t selectedTheme: document.getElementById(\"theme-preset\")?.value || \"defaulted\",\n        };\n        const tx = db.transaction(AUTOSAVE_STORE_NAME, \"readwrite\");\n        const store = tx.objectStore(AUTOSAVE_STORE_NAME);\n        store.put(state);\n        await new Promise((resolve, reject) => {\n         tx.oncomplete = resolve;\n         tx.onerror = () => reject(tx.error);\n        });\n        lastAutosaveTime = Date.now();\n        console.log(\"Auto-saved to IndexedDB at\", new Date().toLocaleTimeString());\n       } catch (e) {\n        console.warn(\"Auto-save failed:\", e);\n       }\n      }\n      async function loadFromIndexedDB() {\n       try {\n        const db = await openAutosaveDB();\n        const tx = db.transaction(AUTOSAVE_STORE_NAME, \"readonly\");\n        const store = tx.objectStore(AUTOSAVE_STORE_NAME);\n        const request = store.get(AUTOSAVE_KEY);\n        return new Promise((resolve, reject) => {\n         request.onsuccess = () => resolve(request.result);\n         request.onerror = () => reject(request.error);\n        });\n       } catch (e) {\n        console.warn(\"Load from IndexedDB failed:\", e);\n        return null;\n       }\n      }\n      async function clearAutosave() {\n       try {\n        const db = await openAutosaveDB();\n        const tx = db.transaction(AUTOSAVE_STORE_NAME, \"readwrite\");\n        const store = tx.objectStore(AUTOSAVE_STORE_NAME);\n        store.delete(AUTOSAVE_KEY);\n       } catch (e) {\n        console.warn(\"Clear autosave failed:\", e);\n       }\n      }\n      function startAutosave() {\n       if (autosaveTimer) clearInterval(autosaveTimer);\n       autosaveTimer = setInterval(() => {\n        if (Object.keys(NODE_DATA).length > 0 || EDGE_DATA.list.length > 0 || documentTabs.length > 1) {\n         saveToIndexedDB();\n        }\n       }, AUTOSAVE_INTERVAL);\n      }\n      async function checkForAutosaveRecovery() {\n       try {\n        const saved = await loadFromIndexedDB();\n        if (saved && saved.timestamp) {\n         const age = Date.now() - saved.timestamp;\n         const hasCurrentData = Object.keys(NODE_DATA).length > 0 || EDGE_DATA.list.length > 0;\n         const savedNodeCount = saved.nodeData ? Object.keys(saved.nodeData).length : 0;\n         const savedTabCount = saved.documentTabs ? saved.documentTabs.length : 1;\n         if (age < 300000 && (savedNodeCount > 0 || savedTabCount > 1)) {\n          const savedDate = new Date(saved.timestamp).toLocaleString();\n          const tabInfo = savedTabCount > 1 ? ` across ${savedTabCount} tabs` : \"\";\n          if (!hasCurrentData || confirm(`Recover unsaved work from ${savedDate}?\\n(${savedNodeCount} nodes${tabInfo})\\n\\nClick OK to recover, Cancel to start fresh.`)) {\n           if (!hasCurrentData || confirm(\"This will replace current data. Continue?\")) {\n            NODE_DATA = saved.nodeData || {};\n            EDGE_DATA = saved.edgeData || { list: [] };\n            RECT_DATA = saved.rectData || { list: [] };\n            TEXT_DATA = saved.textData || { list: [] };\n            EDGE_LEGEND = saved.edgeLegend || {};\n\t\t\tZONE_LEGEND = saved.zoneLegend || {};\n            ZONE_PRESETS = saved.zonePresets || {};\n            loadCustomPresets();\n            savedPositions = saved.nodePositions || {};\n            savedSizes = saved.nodeSizes || {};\n            savedStyles = saved.nodeStyles || {};\n            if (saved.page) PAGE_STATE = Object.assign({}, DEFAULT_PAGE_STATE, saved.page);\n            if (saved.canvas) {\n             canvasState.zoom = saved.canvas.zoom || 1;\n             canvasState.panX = saved.canvas.panX || 0;\n             canvasState.panY = saved.canvas.panY || 0;\n            }\n            if (saved.savedTopologyView) savedTopologyView = saved.savedTopologyView;\n            if (saved.savedStyleSets) savedStyleSets = saved.savedStyleSets;\n            if (saved.selectedTheme) {\n              rebuildThemeDropdown();\n              document.getElementById(\"theme-preset\").value = saved.selectedTheme;\n              updateDeleteButton();\n            }\n            if (saved.documentTabs) documentTabs = saved.documentTabs;\n            if (saved.currentTabIndex !== undefined) currentTabIndex = saved.currentTabIndex;\n            if (saved.encryptedSections) encryptedSections = saved.encryptedSections;\n            if (saved.auditLog) auditLog = saved.auditLog;\n            if (saved.pageTitle) {\n             const titleEl = document.getElementById(\"page-title\");\n             if (titleEl) titleEl.textContent = saved.pageTitle;\n            }\n            wieldThePower();\n            forgeTheTopology();\n            updateViewBox();\n            console.log(\"Recovered from autosave:\", savedNodeCount, \"nodes,\", savedTabCount, \"tabs\");\n            return true;\n           }\n          }\n         }\n        }\n       } catch (e) {\n        console.warn(\"Autosave recovery check failed:\", e);\n       }\n       return false;\n      }\n\t\t\t  function ensureLegendMiniButton() {\n\t\t   if (legendMiniBtn) return legendMiniBtn;\n\t\t   const handleClick = (e) => {\n\t\t\te.stopPropagation();\n\t\t\te.preventDefault();\n\t\t\tlegendCollapsed = false;\n\t\t\tupdateLegendVisibility();\n\t\t   };\n\t\t   const preventTouch = (e) => { e.preventDefault(); };\n\t\t   const existing = document.getElementById(\"edge-legend-mini\");\n\t\t   if (existing) {\n\t\t\texisting.addEventListener(\"click\", handleClick);\n\t\t\texisting.addEventListener(\"touchstart\", preventTouch, { passive: false });\n\t\t\texisting.addEventListener(\"touchend\", handleClick);\n\t\t\tlegendMiniBtn = existing;\n\t\t\treturn existing;\n\t\t   }\n\t\t   const panel = document.querySelector(\".topology-panel\");\n\t\t   if (!panel) return null;\n\t\t   const btn = document.createElement(\"button\");\n\t\t   btn.type = \"button\";\n\t\t   btn.id = \"edge-legend-mini\";\n\t\t   btn.className = \"legend-mini-btn\";\n\t\t   btn.textContent = \"Legend\";\n\t\t   btn.style.bottom = \"10px\";\n\t\t   btn.addEventListener(\"click\", handleClick);\n\t\t   btn.addEventListener(\"touchstart\", preventTouch, { passive: false });\n\t\t   btn.addEventListener(\"touchend\", handleClick);\n\t\t   panel.appendChild(btn);\n\t\t   legendMiniBtn = btn;\n\t\t   return btn;\n\t\t}\n\t\t\t  function ensureMinimapMiniButton() {\n\t\t   if (minimapMiniBtn) return minimapMiniBtn;\n\t\t   const handleClick = (e) => {\n\t\t\te.stopPropagation();\n\t\t\te.preventDefault();\n\t\t\tminimapCollapsed = false;\n\t\t\tupdateMinimapVisibility();\n\t\t   };\n\t\t   const preventTouch = (e) => { e.preventDefault(); };\n\t\t   const existing = document.getElementById(\"minimap-mini\");\n\t\t   if (existing) {\n\t\t\texisting.addEventListener(\"click\", handleClick);\n\t\t\texisting.addEventListener(\"touchstart\", preventTouch, { passive: false });\n\t\t\texisting.addEventListener(\"touchend\", handleClick);\n\t\t\tminimapMiniBtn = existing;\n\t\t\treturn existing;\n\t\t   }\n\t\t   const panel = document.querySelector(\".topology-panel\");\n\t\t   if (!panel) return null;\n\t\t   const btn = document.createElement(\"button\");\n\t\t   btn.type = \"button\";\n\t\t   btn.id = \"minimap-mini\";\n\t\t   btn.className = \"legend-mini-btn\";\n\t\t   btn.textContent = \"Map\";\n\t\t   btn.style.right = \"10px\";\n\t\t   btn.style.left = \"auto\";\n\t\t   btn.style.bottom = \"10px\";\n\t\t   btn.addEventListener(\"click\", handleClick);\n\t\t   btn.addEventListener(\"touchstart\", preventTouch, { passive: false });\n\t\t   btn.addEventListener(\"touchend\", handleClick);\n\t\t   panel.appendChild(btn);\n\t\t   minimapMiniBtn = btn;\n\t\t   return btn;\n\t\t}\n\t\t\t  function ensureDrawToolbarMiniButton() {\n\t\t   if (drawToolbarMiniBtn) return drawToolbarMiniBtn;\n\t\t   const handleClick = (e) => {\n\t\t\te.stopPropagation();\n\t\t\te.preventDefault();\n\t\t\tdrawToolbarCollapsed = false;\n\t\t\tupdateDrawToolbarVisibility();\n\t\t   };\n\t\t   const preventTouch = (e) => { e.preventDefault(); };\n\t\t   const existing = document.getElementById(\"draw-toolbar-mini\");\n\t\t   if (existing) {\n\t\t\texisting.addEventListener(\"click\", handleClick);\n\t\t\texisting.addEventListener(\"touchstart\", preventTouch, { passive: false });\n\t\t\texisting.addEventListener(\"touchend\", handleClick);\n\t\t\tdrawToolbarMiniBtn = existing;\n\t\t\treturn existing;\n\t\t   }\n\t\t   const panel = document.querySelector(\".topology-panel\");\n\t\t   if (!panel) return null;\n\t\t   const btn = document.createElement(\"button\");\n\t\t   btn.type = \"button\";\n\t\t   btn.id = \"draw-toolbar-mini\";\n\t\t   btn.className = \"legend-mini-btn\";\n\t\t   btn.textContent = \"Draw\";\n\t\t   btn.style.top = \"10px\";\n\t\t   btn.style.left = \"10px\";\n\t\t   btn.style.right = \"auto\";\n\t\t   btn.addEventListener(\"click\", handleClick);\n\t\t   btn.addEventListener(\"touchstart\", preventTouch, { passive: false });\n\t\t   btn.addEventListener(\"touchend\", handleClick);\n\t\t   panel.appendChild(btn);\n\t\t   drawToolbarMiniBtn = btn;\n\t\t   return btn;\n\t\t}\n\t\t\t  function ensureTopologyToolbarMiniButton() {\n\t\t   if (topologyToolbarMiniBtn) return topologyToolbarMiniBtn;\n\t\t   const handleClick = (e) => {\n\t\t\te.stopPropagation();\n\t\t\te.preventDefault();\n\t\t\ttopologyToolbarCollapsed = false;\n\t\t\tupdateTopologyToolbarVisibility();\n\t\t   };\n\t\t   const preventTouch = (e) => { e.preventDefault(); };\n\t\t   const existing = document.getElementById(\"topology-toolbar-mini\");\n\t\t   if (existing) {\n\t\t\texisting.addEventListener(\"click\", handleClick);\n\t\t\texisting.addEventListener(\"touchstart\", preventTouch, { passive: false });\n\t\t\texisting.addEventListener(\"touchend\", handleClick);\n\t\t\ttopologyToolbarMiniBtn = existing;\n\t\t\treturn existing;\n\t\t   }\n\t\t   const panel = document.querySelector(\".topology-panel\");\n\t\t   if (!panel) return null;\n\t\t   const btn = document.createElement(\"button\");\n\t\t   btn.type = \"button\";\n\t\t   btn.id = \"topology-toolbar-mini\";\n\t\t   btn.className = \"legend-mini-btn\";\n\t\t   btn.textContent = \"Add Line\";\n\t\t   btn.style.top = \"10px\";\n\t\t   btn.style.left = \"auto\";\n\t\t   btn.style.right = \"40px\";\n\t\t   btn.addEventListener(\"click\", handleClick);\n\t\t   btn.addEventListener(\"touchstart\", preventTouch, { passive: false });\n\t\t   btn.addEventListener(\"touchend\", handleClick);\n\t\t   panel.appendChild(btn);\n\t\t   topologyToolbarMiniBtn = btn;\n\t\t   return btn;\n\t\t}\n      function updateToolbarStack() {\n       if (!isMobileDevice()) return;\n       const draw = document.getElementById(\"draw-toolbar\");\n       const topo = document.getElementById(\"topology-toolbar\");\n       if (!draw || !topo) return;\n       const h = draw.getBoundingClientRect().height;\n       document.documentElement.style.setProperty(\"--draw-toolbar-height\", h + \"px\");\n      }\n      window.addEventListener(\"resize\", updateToolbarStack);\n      window.addEventListener(\"DOMContentLoaded\", updateToolbarStack);\n      updateToolbarStack();\n      function updateLegendVisibility() {\n       const legend = document.getElementById(\"edge-legend\");\n       const mini = ensureLegendMiniButton();\n       if (!legend || !mini) return;\n       const hasItems = legend.querySelectorAll(\".legend-item\").length > 0;\n       if (!hasItems) {\n        legend.style.display = \"none\";\n        mini.style.display = \"none\";\n        return;\n       }\n       if (legendCollapsed) {\n        legend.style.display = \"none\";\n        mini.style.display = \"inline-flex\";\n       } else {\n        legend.style.display = \"flex\";\n        mini.style.display = \"none\";\n       }\n      }\n      function updateMinimapVisibility() {\n       const wrapper = document.getElementById(\"minimap-zoom-wrapper\");\n       const mini = ensureMinimapMiniButton();\n       if (!wrapper || !mini) return;\n       if (minimapCollapsed) {\n        wrapper.style.display = \"none\";\n        mini.style.display = \"inline-flex\";\n       } else {\n        wrapper.style.display = \"block\";\n        mini.style.display = \"none\";\n       }\n      }\n      function updateDrawToolbarVisibility() {\n       const toolbar = document.getElementById(\"draw-toolbar\");\n       const mini = ensureDrawToolbarMiniButton();\n       if (isViewOnly()) {\n        if (toolbar) toolbar.style.setProperty('display', 'none', 'important');\n        if (mini) mini.style.display = \"none\";\n        return;\n       }\n       if (!toolbar || !mini) return;\n       if (drawToolbarCollapsed) {\n        toolbar.style.setProperty('display', 'none', 'important');\n        mini.style.display = \"inline-flex\";\n       } else {\n      if (isMobileDevice()) {\n        toolbar.style.setProperty('display', 'grid', 'important');\n        mini.style.display = \"none\";\n      } else {\n        toolbar.style.setProperty('display', 'flex', 'important');\n        mini.style.display = \"none\";\n      }\n       }\n      }\n      function updateTopologyToolbarVisibility() {\n       const toolbar = document.getElementById(\"topology-toolbar\");\n       const mini = ensureTopologyToolbarMiniButton();\n       if (isViewOnly()) {\n        if (toolbar) toolbar.style.setProperty('display', 'none', 'important');\n        if (mini) mini.style.display = \"none\";\n        return;\n       }\n       if (!toolbar || !mini) return;\n       const hasSelectedNode = currentNodeId !== null;\n       if (!hasSelectedNode) {\n        toolbar.style.setProperty('display', 'none', 'important');\n        mini.style.display = \"none\";\n       } else if (topologyToolbarCollapsed) {\n        toolbar.style.setProperty('display', 'none', 'important');\n        mini.style.display = \"inline-flex\";\n       } else {\n      if (isMobileDevice()) {\n        toolbar.style.setProperty('display', 'grid', 'important');\n        mini.style.display = \"none\";\n        } else {\n        toolbar.style.setProperty('display', 'flex', 'important');\n        mini.style.display = \"none\";\n      }\n       }\n      }\n      const DEFAULT_CANVAS_HINT = document.getElementById(\"canvas-hint\")?.innerHTML || \"\";\n      const DEFAULT_PAGE_STATE = {\n       title: \"The One File\",\n       background: \"\",\n       topbarBg: \"rgba(9, 12, 20, 0.9)\",\n       topbarBorder: \"#1f2533\",\n       panel: \"#0b0e13\",\n       panelAlt: \"#10141b\",\n       accent: \"#4fd1c5\",\n       sidebarBg: \"#10141b\",\n       btnBg: \"#0b0e13\",\n       btnText: \"#e2e8f0\",\n       tagFill: \"#1e293b\",\n       tagText: \"#e2e8f0\",\n       tagBorder: \"#475569\",\n       inputBg: \"#0b0e13\",\n       inputText: \"#e2e8f0\",\n       inputBorder: \"#1f2937\",\n       inputFont: \"Inter, system-ui, sans-serif\",\n       inputFontSize: 14,\n       toolbarBg: \"#0f172a\",\n       toolbarBorder: \"#1f2937\",\n       toolbarText: \"#94a3b8\",\n       toolbarBtnBg: \"#0b0e13\",\n       toolbarBtnText: \"#e2e8f0\",\n       minimapDots: \"#94a3b8\",\n       canvasHintEnabled: true,\n       canvasHintText: \"\",\n       canvasHintBg: \"#0f172a\",\n       canvasHintColor: \"#94a3b8\",\n       danger: \"#f56565\",\n       textMain: \"#e2e8f0\",\n       textSoft: \"#94a3b8\",\n       topbarHeight: 52,\n       sidebarWidth: 350,\n       mobileFooterHeight: 40,\n       sidebarCollapsed: false,\n       nodeFill: \"#1e293b\",\n       nodeStroke: \"#475569\",\n       nodeTitle: \"#e2e8f0\",\n       nodeSub: \"#94a3b8\",\n       nodeTitleSize: 18,\n       nodeSubSize: 13,\n       nodeFont: \"Inter, system-ui, sans-serif\",\n       defaultEdge: \"#475569\",\n       selectionHandle: \"#f59e0b\",\n       selectionHandleSize: 8,\n       groupIndicator: \"#4fd1c5\",\n      canvasGradientTop: \"#1e2532\",\n       canvasGradientBottom: \"#050608\",\n       canvasBorder: \"#475569\",\n       canvasGrid: \"#475569\",\n       canvasGridSize: 50,\ncanvasGridEnabled: true,\nrackFrameFill: \"#0f172a\",\nrackGridEnabled: true,\n       rackFrameStroke: \"#4fd1c5\",\n       rackLineColor: \"#475569\",\n       rackTextColor: \"#4fd1c5\",\n\t   viewOnly: false,\n\t   defaultEdgeRouting: \"curved\",\n\t   animateConnections: false,\n\t   animationStyle: \"arrows\",\n\t   animationDirection: \"all\",\n\t   animationSpeed: 1.5,\n      };\n      let PAGE_STATE = Object.assign({}, DEFAULT_PAGE_STATE);\n\t  const FOV_ANIMATION_START = Date.now();\n\t  let ZONES_VISIBLE = true;\nconst ANIM_SETTINGS = {\n  masterAnim: true,\n  masterZones: true,\n  animTypes: { sweep: true, pulse: true, rings: true, spin: true, connections: true },\n  animCategories: { camera: true, doorbell: true, motion: true, smoke: true, wifi: true, sensor: true, sprinkler: true, connections: true },\n  zoneCategories: { camera: true, doorbell: true, motion: true, smoke: true, wifi: true, sensor: true, sprinkler: true }\n};\nfunction getShapeCategory(shape) {\n  const map = {\n    \"camera\": \"camera\", \"cctv\": \"camera\", \"ptz-cam\": \"camera\",\n    \"doorbell\": \"doorbell\",\n    \"motion-sensor\": \"motion\", \"motion-detect\": \"motion\",\n    \"smoke-detector\": \"smoke\", \"smoke-alarm\": \"smoke\",\n    \"access-point\": \"wifi\", \"wifi\": \"wifi\", \"router\": \"wifi\", \"wifi-strong\": \"wifi\", \"wifi-weak\": \"wifi\",\n    \"sensor\": \"sensor\", \"iot\": \"sensor\",\n    \"sprinkler\": \"sprinkler\", \"sprinkler-arc\": \"sprinkler\"\n  };\n  return map[shape] || null;\n}\nfunction isAnimationAllowed(shape, animType) {\n  if (!ANIM_SETTINGS.masterAnim) return false;\n  if (!ANIM_SETTINGS.animTypes[animType]) return false;\n  const cat = getShapeCategory(shape);\n  if (cat && !ANIM_SETTINGS.animCategories[cat]) return false;\n  return true;\n}\nfunction isZoneAllowed(shape) {\n  if (!ANIM_SETTINGS.masterZones) return false;\n  const cat = getShapeCategory(shape);\n  if (cat && !ANIM_SETTINGS.zoneCategories[cat]) return false;\n  return true;\n}\nfunction applyAnimZoneSettings() {\n  document.querySelectorAll(\".fov-group\").forEach(g => {\n    const nodeEl = g.closest(\"g[data-node-id]\");\n    if (!nodeEl) return;\n    const nodeId = nodeEl.dataset.nodeId;\n    const node = NODE_DATA[nodeId];\n    if (!node) return;\n    const cat = getShapeCategory(node.shape);\n    const zoneVisible = ANIM_SETTINGS.masterZones && (!cat || ANIM_SETTINGS.zoneCategories[cat]);\n    g.style.display = zoneVisible ? \"\" : \"none\";\n    if (zoneVisible && node.fovAnimate) {\n      const animType = node.fovAnimationType || \"sweep\";\n      const animAllowed = ANIM_SETTINGS.masterAnim && ANIM_SETTINGS.animTypes[animType] && (!cat || ANIM_SETTINGS.animCategories[cat]);\n      g.style.animationPlayState = animAllowed ? \"running\" : \"paused\";\n      g.querySelectorAll(\"circle\").forEach(c => c.style.animationPlayState = animAllowed ? \"running\" : \"paused\");\n    }\n  });\n  const connAnimAllowed = ANIM_SETTINGS.masterAnim && ANIM_SETTINGS.animTypes.connections && ANIM_SETTINGS.animCategories.connections;\n  document.querySelectorAll(\".edge-arrow-forward, .edge-arrow-backward\").forEach(a => {\n    a.style.animationPlayState = connAnimAllowed ? \"running\" : \"paused\";\n  });\n}\nlet ZONE_LEGEND = {};\nlet ZONE_PRESETS = {};\nlet copiedZoneStyle = null;\n\t  function hasCoverageZone(shape) {\n\t\t  const supportedShapes = [\n\t\t\t\"camera\", \"cctv\", \"doorbell\",\n\t\t\t\"motion-sensor\", \"smoke-detector\",\n\t\t\t\"access-point\", \"wifi\", \"router\",\n\t\t\t\"sensor\", \"iot\", \"sprinkler\"\n\t\t  ];\n\t\t  return supportedShapes.includes(shape);\n\t\t}\n\n\t\tfunction getCoverageDefaults(shape) {\n\t\t  const defaults = {\n\t\t\t\"camera\": { angle: 90, distance: 150, animationType: \"sweep\" },\n\t\t\t\"cctv\": { angle: 90, distance: 150, animationType: \"sweep\" },\n\t\t\t\"doorbell\": { angle: 120, distance: 100, animationType: \"sweep\" },\n\t\t\t\"motion-sensor\": { angle: 120, distance: 100, animationType: \"pulse\" },\n\t\t\t\"smoke-detector\": { angle: 360, distance: 80, animationType: \"pulse\" },\n\t\t\t\"access-point\": { angle: 360, distance: 200, animationType: \"rings\" },\n\t\t\t\"wifi\": { angle: 360, distance: 200, animationType: \"rings\" },\n\t\t\t\"router\": { angle: 360, distance: 200, animationType: \"rings\" },\n\t\t\t\"sensor\": { angle: 90, distance: 100, animationType: \"pulse\" },\n\t\t\t\"iot\": { angle: 90, distance: 100, animationType: \"pulse\" },\n\t\t\t\"sprinkler\": { angle: 90, distance: 120, animationType: \"spin\" }\n\t\t  };\n\t\t  return defaults[shape] || { angle: 90, distance: 150, animationType: \"sweep\" };\n\t\t}\n\t\tfunction toggleAllZones() {\n  ANIM_SETTINGS.masterZones = !ANIM_SETTINGS.masterZones;\n  const masterCheckbox = document.getElementById(\"zone-master\");\n  if (masterCheckbox) masterCheckbox.checked = ANIM_SETTINGS.masterZones;\n  applyAnimZoneSettings();\n}\n\nfunction copyZoneStyle(nodeId) {\n  const node = NODE_DATA[nodeId];\n  if (!node || !hasCoverageZone(node.shape)) return;\n  copiedZoneStyle = {\n    fovEnabled: node.fovEnabled,\n    fovAngle: node.fovAngle,\n    fovDistance: node.fovDistance,\n    fovInnerRadius: node.fovInnerRadius,\n    fovRotation: node.fovRotation,\n    fovColor: node.fovColor,\n    fovOpacity: node.fovOpacity,\n    fovGradient: node.fovGradient,\n    fovBorderColor: node.fovBorderColor,\n    fovBorderWidth: node.fovBorderWidth,\n    fovBorderStyle: node.fovBorderStyle,\n    fovBorderOpacity: node.fovBorderOpacity,\n    fovLabel: node.fovLabel,\n    fovLabelPosition: node.fovLabelPosition,\n    fovLabelSize: node.fovLabelSize,\n    fovLabelColor: node.fovLabelColor,\n    fovLabelBold: node.fovLabelBold,\n    fovLabelBg: node.fovLabelBg,\n    fovLabelBgColor: node.fovLabelBgColor,\n    fovLabelOffsetX: node.fovLabelOffsetX,\n    fovLabelOffsetY: node.fovLabelOffsetY,\n    fovAnimate: node.fovAnimate,\n    fovAnimationType: node.fovAnimationType,\n    fovSweep: node.fovSweep,\n    fovSpeed: node.fovSpeed\n  };\n}\n\nfunction pasteZoneStyle(nodeId) {\n  if (!copiedZoneStyle) return;\n  const node = NODE_DATA[nodeId];\n  if (!node || !hasCoverageZone(node.shape)) return;\n  pushUndo(\"paste zone style\");\n  Object.assign(node, copiedZoneStyle);\n  updateFovCone(nodeId);\n  if (currentNodeId === nodeId) {\n    claimTheImmortal(nodeId);\n  }\n}\n\nfunction applyZonePreset(preset) {\n  if (!currentNodeId) return;\n  const presets = {\n    \"security-cam\": { fovAngle: 90, fovDistance: 150, fovColor: \"#f59e0b\", fovOpacity: 20, fovAnimationType: \"sweep\", fovAnimate: false },\n    \"ptz-cam\": { fovAngle: 60, fovDistance: 200, fovColor: \"#f59e0b\", fovOpacity: 25, fovAnimationType: \"sweep\", fovAnimate: true, fovSweep: 180, fovSpeed: 8 },\n    \"motion-detect\": { fovAngle: 120, fovDistance: 100, fovColor: \"#10b981\", fovOpacity: 15, fovAnimationType: \"pulse\", fovAnimate: true, fovSpeed: 3 },\n    \"wifi-strong\": { fovAngle: 360, fovDistance: 150, fovColor: \"#3b82f6\", fovOpacity: 10, fovAnimationType: \"rings\", fovAnimate: true, fovSpeed: 4 },\n    \"wifi-weak\": { fovAngle: 360, fovDistance: 250, fovColor: \"#3b82f6\", fovOpacity: 8, fovGradient: true, fovAnimationType: \"rings\", fovAnimate: true, fovSpeed: 5 },\n    \"smoke-alarm\": { fovAngle: 360, fovDistance: 80, fovColor: \"#ef4444\", fovOpacity: 15, fovAnimationType: \"pulse\", fovAnimate: false },\n    \"sprinkler-arc\": { fovAngle: 90, fovDistance: 120, fovColor: \"#06b6d4\", fovOpacity: 20, fovAnimationType: \"spin\", fovAnimate: true, fovSpeed: 6 }\n  };\n  const allPresets = { ...presets, ...ZONE_PRESETS };\n  const settings = allPresets[preset];\n  if (!settings) return;\n  \n  pushUndo(\"apply zone preset\");\n  const node = NODE_DATA[currentNodeId];\n  node.fovEnabled = true;\n  Object.assign(node, settings);\n  updateFovCone(currentNodeId);\n  claimTheImmortal(currentNodeId);\n}\n\nfunction saveCustomZonePreset() {\n  if (!currentNodeId) return;\n  const node = NODE_DATA[currentNodeId];\n  if (!node.fovEnabled) {\n    alert(\"Enable the zone first before saving as preset\");\n    return;\n  }\n  const name = prompt(\"Enter preset name:\");\n  if (!name || !name.trim()) return;\n  \n  const presetId = name.toLowerCase().replace(/[^a-z0-9]+/g, \"-\");\n  ZONE_PRESETS[presetId] = {\n    fovAngle: node.fovAngle,\n    fovDistance: node.fovDistance,\n    fovInnerRadius: node.fovInnerRadius,\n    fovColor: node.fovColor,\n    fovOpacity: node.fovOpacity,\n    fovGradient: node.fovGradient,\n    fovBorderColor: node.fovBorderColor,\n    fovBorderWidth: node.fovBorderWidth,\n    fovBorderStyle: node.fovBorderStyle,\n    fovBorderOpacity: node.fovBorderOpacity,\n    fovAnimationType: node.fovAnimationType,\n    fovAnimate: node.fovAnimate,\n    fovSweep: node.fovSweep,\n    fovSpeed: node.fovSpeed\n  };\n  const select = document.getElementById(\"fov-preset\");\n  if (select && !select.querySelector(`option[value=\"${presetId}\"]`)) {\n    const opt = document.createElement(\"option\");\n    opt.value = presetId;\n    opt.textContent = name.trim() + \" (Custom)\";\n    select.appendChild(opt);\n  }\n  alert(\"Preset saved: \" + name.trim());\n}\n\nfunction bulkCopyZoneStyle() {\n  if (selectedNodes.size === 0) {\n    alert(\"Select at least one node first\");\n    return;\n  }\n  const firstId = [...selectedNodes][0];\n  if (copyZoneStyle(firstId)) {\n    alert(\"Zone style copied from first selected node\");\n  }\n}\n\nfunction bulkPasteZoneStyle() {\n  if (selectedNodes.size === 0) {\n    alert(\"Select nodes first\");\n    return;\n  }\n  if (!copiedZoneStyle) {\n    alert(\"Copy a zone style first\");\n    return;\n  }\n  pushUndo(\"bulk paste zone style\");\n  let count = 0;\n  selectedNodes.forEach(id => {\n    const node = NODE_DATA[id];\n    if (node && hasCoverageZone(node.shape)) {\n      Object.assign(node, copiedZoneStyle);\n      updateFovCone(id);\n      count++;\n    }\n  });\n  forgeTheTopology();\n  alert(`Zone style pasted to ${count} node(s)`);\n}\n\nfunction bulkToggleZones() {\n  if (selectedNodes.size === 0) {\n    alert(\"Select nodes first\");\n    return;\n  }\n  pushUndo(\"bulk toggle zones\");\n  const firstNode = NODE_DATA[[...selectedNodes][0]];\n  const newState = !(firstNode?.fovEnabled);\n  let count = 0;\n  selectedNodes.forEach(id => {\n    const node = NODE_DATA[id];\n    if (node && hasCoverageZone(node.shape)) {\n      node.fovEnabled = newState;\n      updateFovCone(id);\n      count++;\n    }\n  });\n  forgeTheTopology();\n  alert(`Toggled zones on ${count} node(s) to ${newState ? 'ON' : 'OFF'}`);\n}\n\nfunction loadCustomPresets() {\n  const select = document.getElementById(\"fov-preset\");\n  if (!select) return;\n  Object.keys(ZONE_PRESETS).forEach(id => {\n    if (!select.querySelector(`option[value=\"${id}\"]`)) {\n      const opt = document.createElement(\"option\");\n      opt.value = id;\n      opt.textContent = id.replace(/-/g, \" \") + \" ★\";\n      select.appendChild(opt);\n    }\n  });\n}\n\nfunction updateZoneLegend() {\n  const container = document.getElementById(\"edge-legend\");\n  if (!container) return;\n  container.querySelectorAll(\".zone-legend-item\").forEach(el => el.remove());\n  container.querySelectorAll(\".zone-legend-title\").forEach(el => el.remove());\n  const zoneColors = new Set();\n  Object.values(NODE_DATA).forEach(node => {\n    if (hasCoverageZone(node.shape) && node.fovEnabled && node.fovColor) {\n      zoneColors.add(node.fovColor);\n    }\n  });\n  \n  if (zoneColors.size === 0) return;\n  const zoneTitle = document.createElement(\"div\");\n  zoneTitle.className = \"legend-title zone-legend-title\";\n  zoneTitle.textContent = \"Zone Legend\";\n  zoneTitle.style.marginTop = \"12px\";\n  zoneTitle.style.paddingTop = \"8px\";\n  zoneTitle.style.borderTop = \"1px solid var(--edge-main)\";\n  container.appendChild(zoneTitle);\n  zoneColors.forEach(color => {\n    if (!ZONE_LEGEND[color]) {\n      ZONE_LEGEND[color] = \"Coverage Zone\";\n    }\n    const item = document.createElement(\"div\");\n    item.className = \"legend-item zone-legend-item\";\n    item.addEventListener(\"mousedown\", (e) => e.stopPropagation());\n    item.addEventListener(\"click\", (e) => e.stopPropagation());\n    \n    const swatch = document.createElement(\"span\");\n    swatch.className = \"legend-swatch\";\n    swatch.style.backgroundColor = color;\n    swatch.style.opacity = \"0.5\";\n    swatch.style.cursor = \"pointer\";\n    swatch.addEventListener(\"click\", (e) => {\n      e.stopPropagation();\n      const nodeWithColor = Object.entries(NODE_DATA).find(([id, n]) => \n        hasCoverageZone(n.shape) && n.fovEnabled && n.fovColor === color\n      );\n      if (nodeWithColor) {\n        claimTheImmortal(nodeWithColor[0]);\n      }\n    });\n    \n    const label = document.createElement(\"span\");\n    label.className = \"legend-label\";\n    label.textContent = ZONE_LEGEND[color];\n    label.contentEditable = true;\n    label.addEventListener(\"focus\", () => label.classList.add(\"editing\"));\n    label.addEventListener(\"blur\", () => {\n      label.classList.remove(\"editing\");\n      ZONE_LEGEND[color] = label.textContent.trim() || \"Coverage Zone\";\n    });\n    label.addEventListener(\"keydown\", (e) => {\n      if (e.key === \"Enter\") {\n        e.preventDefault();\n        label.blur();\n      }\n    });\n    \n    item.append(swatch, label);\n    container.appendChild(item);\n  });\n  \n  updateLegendVisibility();\n}\n\t  function isViewOnly() {\n       return PAGE_STATE.viewOnly === true;\n      }\n\t  let viewOnlyClickCount = 0;\n      let viewOnlyClickTimer = null;\n      let viewOnlyClickTarget = null;\n      function handleViewOnlyClick(id, type) {\n       if (!isViewOnly()) return false;\n       if (viewOnlyClickTarget !== id) {\n        viewOnlyClickCount = 0;\n        viewOnlyClickTarget = id;\n       }\n       viewOnlyClickCount++;\n       clearTimeout(viewOnlyClickTimer);\n       viewOnlyClickTimer = setTimeout(() => {\n        viewOnlyClickCount = 0;\n        viewOnlyClickTarget = null;\n       }, 2000);\n       if (viewOnlyClickCount >= 5) {\n        viewOnlyClickCount = 0;\n        viewOnlyClickTarget = null;\n        document.body.classList.add(\"view-only-inspect\");\n        if (type === 'node') {\n         currentNodeId = id;\n         const data = NODE_DATA[id];\n         if (data) {\n          document.querySelectorAll(\".node-group\").forEach((n) => {\n           n.classList.toggle(\"active\", n.dataset.nodeId === id);\n          });\n          document.getElementById(\"node-panel\").style.display = \"block\";\n          document.getElementById(\"edge-panel\").style.display = \"none\";\n          document.getElementById(\"node-name\").textContent = data.name;\n          document.getElementById(\"node-ip\").textContent = data.ip;\n\t\t\tconst fovSection = document.getElementById(\"fov-section\");\n\t\t\tif (fovSection) {\n\t\t\t  if (hasCoverageZone(data.shape)) {\n\t\t\t\tconst defaults = getCoverageDefaults(data.shape);\n\t\t\t\tfovSection.style.display = \"block\";\n\t\t\t\tdocument.getElementById(\"fov-enabled\").checked = data.fovEnabled || false;\n\t\t\t\tdocument.getElementById(\"fov-angle\").value = data.fovAngle || defaults.angle;\n\t\t\t\tdocument.getElementById(\"fov-angle-value\").textContent = (data.fovAngle || defaults.angle) + \"°\";\n\t\t\t\tdocument.getElementById(\"fov-distance\").value = data.fovDistance || defaults.distance;\n\t\t\t\tdocument.getElementById(\"fov-distance-value\").textContent = data.fovDistance || defaults.distance;\n\t\t\t\tdocument.getElementById(\"fov-inner-radius\").value = data.fovInnerRadius || 0;\n\t\t\t\tdocument.getElementById(\"fov-inner-radius-value\").textContent = data.fovInnerRadius || 0;\n\t\t\t\tdocument.getElementById(\"fov-rotation\").value = data.fovRotation || 0;\n\t\t\t\tdocument.getElementById(\"fov-rotation-value\").textContent = (data.fovRotation || 0) + \"°\";\n\t\t\t\tdocument.getElementById(\"fov-color\").value = data.fovColor || \"#f59e0b\";\n\t\t\t\tdocument.getElementById(\"fov-opacity\").value = data.fovOpacity || 20;\n\t\t\t\tdocument.getElementById(\"fov-opacity-value\").textContent = (data.fovOpacity || 20) + \"%\";\n\t\t\t\tdocument.getElementById(\"fov-gradient\").checked = data.fovGradient || false;\n\t\t\t\tdocument.getElementById(\"fov-border-color\").value = data.fovBorderColor || \"#f59e0b\";\n\t\t\t\tdocument.getElementById(\"fov-border-width\").value = data.fovBorderWidth ?? 2;\n\t\t\t\tdocument.getElementById(\"fov-border-width-value\").textContent = data.fovBorderWidth ?? 2;\n\t\t\t\tdocument.getElementById(\"fov-border-style\").value = data.fovBorderStyle || \"solid\";\n\t\t\t\tdocument.getElementById(\"fov-border-opacity\").value = data.fovBorderOpacity ?? 100;\n\t\t\t\tdocument.getElementById(\"fov-border-opacity-value\").textContent = (data.fovBorderOpacity ?? 100) + \"%\";\n\t\t\t\tdocument.getElementById(\"fov-label\").value = data.fovLabel || \"\";\n\t\t\t\tdocument.getElementById(\"fov-label-position\").value = data.fovLabelPosition || \"center\";\n\t\t\t\tdocument.getElementById(\"fov-label-size\").value = data.fovLabelSize || 14;\n\t\t\t\tdocument.getElementById(\"fov-label-size-value\").textContent = (data.fovLabelSize || 14) + \"px\";\n\t\t\t\tdocument.getElementById(\"fov-label-color\").value = data.fovLabelColor || \"#ffffff\";\n\t\t\t\tdocument.getElementById(\"fov-label-bold\").checked = data.fovLabelBold || false;\n\t\t\t\tdocument.getElementById(\"fov-label-bg\").checked = data.fovLabelBg || false;\n\t\t\t\tdocument.getElementById(\"fov-label-bg-color\").value = data.fovLabelBgColor || \"#000000\";\n\t\t\t\tdocument.getElementById(\"fov-label-offset-x\").value = data.fovLabelOffsetX || 0;\n\t\t\t\tdocument.getElementById(\"fov-label-offset-x-value\").textContent = data.fovLabelOffsetX || 0;\n\t\t\t\tdocument.getElementById(\"fov-label-offset-y\").value = data.fovLabelOffsetY || 0;\n\t\t\t\tdocument.getElementById(\"fov-label-offset-y-value\").textContent = data.fovLabelOffsetY || 0;\n\t\t\t\tdocument.getElementById(\"fov-animate\").checked = data.fovAnimate || false;\n\t\t\t\tdocument.getElementById(\"fov-animation-type\").value = data.fovAnimationType || defaults.animationType;\n\t\t\t\tdocument.getElementById(\"fov-sweep\").value = data.fovSweep || 120;\n\t\t\t\tdocument.getElementById(\"fov-sweep-value\").textContent = (data.fovSweep || 120) + \"°\";\n\t\t\t\tdocument.getElementById(\"fov-speed\").value = data.fovSpeed || 4;\n\t\t\t\tdocument.getElementById(\"fov-speed-value\").textContent = (data.fovSpeed || 4) + \"s\";\n\t\t\t\tdocument.getElementById(\"fov-opacity\").value = data.fovOpacity || 20;\n\t\t\t\tdocument.getElementById(\"fov-opacity-value\").textContent = (data.fovOpacity || 20) + \"%\";\n\t\t\t  } else {\n\t\t\t\tfovSection.style.display = \"none\";\n\t\t\t  }\n\t\t\t}\n          document.getElementById(\"node-role\").textContent = data.role;\n          document.getElementById(\"node-mac\").textContent = data.mac || \"--\";\n          document.getElementById(\"node-rack\").textContent = data.rackUnit || \"--\";\n          document.getElementById(\"node-uheight\").textContent = (data.uHeight || \"1\") + \"U\";\n          const tagList = document.getElementById(\"node-tags\");\n          if (tagList) {\n           tagList.innerHTML = \"\";\n           if (data.tags && data.tags.length > 0) {\n            data.tags.forEach((tag) => {\n             const li = document.createElement(\"li\");\n             li.textContent = tag;\n             tagList.appendChild(li);\n            });\n           }\n          }\n          const noteList = document.getElementById(\"node-notes\");\n          if (noteList) {\n           noteList.innerHTML = \"\";\n           if (data.notes && data.notes.length > 0) {\n            data.notes.forEach((note) => {\n             const li = document.createElement(\"li\");\n             li.textContent = note;\n             noteList.appendChild(li);\n            });\n           }\n          }\n         }\n        } else if (type === 'edge') {\n         currentEdgeId = id;\n         const edge = EDGE_DATA.list.find(e => e.id === id);\n         if (edge) {\n          document.querySelectorAll(\".edge\").forEach((e) => {\n           e.classList.toggle(\"active\", e.dataset.edgeId === id);\n          });\n          document.getElementById(\"edge-panel\").style.display = \"block\";\n          document.getElementById(\"node-panel\").style.display = \"none\";\n          document.getElementById(\"edge-from\").textContent = NODE_DATA[edge.from]?.name || edge.from;\n          document.getElementById(\"edge-to\").textContent = NODE_DATA[edge.to]?.name || edge.to;\n          document.getElementById(\"edge-label\").value = edge.label || \"\";\n         }\n        }\n        return true;\n       }\n       return false;\n      }\n      const CANVAS_WIDTH = 4000;\n      let CANVAS_HEIGHT = 3000;\n      const BASE_CANVAS_HEIGHT = 3000;\n      const CANVAS_PADDING = 100;\n      const RACK_U_HEIGHT = 70;\n      const RACK_WIDTH = 600;\n      const RACK_START_X = CANVAS_WIDTH / 2;\n      const RACK_START_Y = CANVAS_PADDING + 100;\n      function getRackUHeight(rackId) {\n      const capacity = getRackCapacity(rackId);\n      const availableHeight = CANVAS_HEIGHT - RACK_START_Y - 200;\n      return Math.floor(availableHeight / capacity);\n      }\n      let canvasState = {\n       zoom: 1,\n       panX: 0,\n       panY: 0,\n       minZoom: 0.25,\n       maxZoom: 4,\n       isPanning: false,\n       panStartX: 0,\n       panStartY: 0,\n       spacePressed: false,\n      };\n      function getViewBox() {\n       const viewWidth = CANVAS_WIDTH / canvasState.zoom;\n       const viewHeight = CANVAS_HEIGHT / canvasState.zoom;\n       return {\n        x: canvasState.panX,\n        y: canvasState.panY,\n        width: viewWidth,\n        height: viewHeight,\n       };\n      }\nfunction updateViewBox() {\n  const svg = document.getElementById(\"map\");\n  const vb = getViewBox();\n  svg.setAttribute(\"viewBox\", `${vb.x} ${vb.y} ${vb.width} ${vb.height}`);\n  const zoomLevel = document.getElementById(\"zoom-level\");\n  if (zoomLevel) {\n    zoomLevel.textContent = Math.round(canvasState.zoom * 100) + \"%\";\n  }\n  if (canvasState.zoom < 0.5) {\n    svg.classList.add(\"low-zoom\");\n  } else {\n    svg.classList.remove(\"low-zoom\");\n  }\n  updateMinimap();\n  populateRackDropdown();\n}\n\t  \nlet lastMinimapRender = 0;\nconst MINIMAP_THROTTLE = 100;\n\nfunction updateMinimap() {\n  const now = performance.now();\n  if (now - lastMinimapRender < MINIMAP_THROTTLE) return;\n  lastMinimapRender = now;\n  \n  const minimapViewport = document.getElementById(\"minimap-viewport\");\n  const minimapSvg = document.getElementById(\"minimap\");\n  if (!minimapViewport || !minimapSvg) return;\n  const vb = getViewBox();\n  minimapViewport.setAttribute(\"x\", vb.x);\n  minimapViewport.setAttribute(\"y\", vb.y);\n  minimapViewport.setAttribute(\"width\", vb.width);\n  minimapViewport.setAttribute(\"height\", vb.height);\n  minimapSvg.querySelectorAll(\".minimap-node, .minimap-edge, .minimap-wall, .minimap-rect\").forEach(el => el.remove());\n  const frag = document.createDocumentFragment();\n  const ns = \"http://www.w3.org/2000/svg\";\n  const dotColor = PAGE_STATE.minimapDots || \"#94a3b8\";\n\n  if (RECT_DATA && RECT_DATA.list && currentView.mode !== \"rack\") {\n    RECT_DATA.list.forEach((rect) => {\n      if (rect.lineStyle === \"wall\") {\n        const wallRect = document.createElementNS(ns, \"rect\");\n        wallRect.setAttribute(\"x\", rect.x);\n        wallRect.setAttribute(\"y\", rect.y);\n        wallRect.setAttribute(\"width\", rect.width);\n        wallRect.setAttribute(\"height\", rect.height);\n        wallRect.style.fill = rect.color || \"#666\";\n        wallRect.style.fillOpacity = \"0.6\";\n        wallRect.style.stroke = rect.borderColor || rect.color || \"#666\";\n        wallRect.style.strokeWidth = \"4\";\n        wallRect.classList.add(\"minimap-wall\");\n        frag.appendChild(wallRect);\n      }\n    });\n  }\n\n  EDGE_DATA.list.forEach((edge) => {\n    if (edge.type === \"custom\") {\n      if (Array.isArray(edge.points) && edge.points.length >= 2) {\n        const polyline = document.createElementNS(ns, \"polyline\");\n        polyline.setAttribute(\"points\", edge.points.map(p => `${p.x},${p.y}`).join(\" \"));\n        polyline.classList.add(\"minimap-edge\");\n        frag.appendChild(polyline);\n      }\n      return;\n    }\n    const fromNode = NODE_DATA[edge.from];\n    const toNode = NODE_DATA[edge.to];\n    if (!fromNode || !toNode) return;\n    if (currentView.mode === \"rack\") {\n      if (fromNode.assignedRack !== currentView.rackId || toNode.assignedRack !== currentView.rackId) return;\n    } else {\n      if (fromNode.assignedRack || toNode.assignedRack) return;\n    }\n    const p1 = savedPositions[edge.from];\n    const p2 = savedPositions[edge.to];\n    if (!p1 || !p2) return;\n    const routing = edge.routing || PAGE_STATE.defaultEdgeRouting || \"curved\";\n    \n    if (routing === \"orthogonal\") {\n      const dx = p2.x - p1.x;\n      const dy = p2.y - p1.y;\n      const polyline = document.createElementNS(ns, \"polyline\");\n      let points;\n      if (Math.abs(dx) > Math.abs(dy)) {\n        const midX = p1.x + dx / 2;\n        points = `${p1.x},${p1.y} ${midX},${p1.y} ${midX},${p2.y} ${p2.x},${p2.y}`;\n      } else {\n        const midY = p1.y + dy / 2;\n        points = `${p1.x},${p1.y} ${p1.x},${midY} ${p2.x},${midY} ${p2.x},${p2.y}`;\n      }\n      polyline.setAttribute(\"points\", points);\n      polyline.classList.add(\"minimap-edge\");\n      frag.appendChild(polyline);\n    } else {\n      const line = document.createElementNS(ns, \"line\");\n      line.setAttribute(\"x1\", p1.x);\n      line.setAttribute(\"y1\", p1.y);\n      line.setAttribute(\"x2\", p2.x);\n      line.setAttribute(\"y2\", p2.y);\n      line.classList.add(\"minimap-edge\");\n      frag.appendChild(line);\n    }\n  });\n\n  Object.entries(savedPositions).forEach(([id, pos]) => {\n    const node = NODE_DATA[id];\n    if (!node) return;\n    if (currentView.mode === \"rack\") {\n      if (node.assignedRack !== currentView.rackId) return;\n    } else {\n      if (node.assignedRack) return;\n    }\n    const nodeSize = savedSizes[id] || 55;\n    const s = nodeSize * 0.5;\n    \n    if (node.isRack) {\n      const rect = document.createElementNS(ns, \"rect\");\n      rect.setAttribute(\"x\", pos.x - s);\n      rect.setAttribute(\"y\", pos.y - s);\n      rect.setAttribute(\"width\", s * 2);\n      rect.setAttribute(\"height\", s * 2);\n      rect.style.fill = dotColor;\n      rect.classList.add(\"minimap-node\");\n      frag.appendChild(rect);\n    } else if (hasCoverageZone(node.shape)) {\n      const diamond = document.createElementNS(ns, \"polygon\");\n      const points = `${pos.x},${pos.y - s} ${pos.x + s},${pos.y} ${pos.x},${pos.y + s} ${pos.x - s},${pos.y}`;\n      diamond.setAttribute(\"points\", points);\n      diamond.style.fill = \"none\";\n      diamond.style.stroke = dotColor;\n      diamond.style.strokeWidth = \"6\";\n      diamond.classList.add(\"minimap-node\");\n      frag.appendChild(diamond);\n    } else {\n      const circle = document.createElementNS(ns, \"circle\");\n      circle.setAttribute(\"cx\", pos.x);\n      circle.setAttribute(\"cy\", pos.y);\n      circle.setAttribute(\"r\", s);\n      circle.style.fill = dotColor;\n      circle.classList.add(\"minimap-node\");\n      frag.appendChild(circle);\n    }\n  });\n\n  minimapSvg.insertBefore(frag, minimapViewport);\n}\n\n      function zoomTo(newZoom, centerX, centerY) {\n       const oldZoom = canvasState.zoom;\n       newZoom = Math.max(canvasState.minZoom, Math.min(canvasState.maxZoom, newZoom));\n       if (centerX !== undefined && centerY !== undefined) {\n        const oldWidth = CANVAS_WIDTH / oldZoom;\n        const oldHeight = CANVAS_HEIGHT / oldZoom;\n        const newWidth = CANVAS_WIDTH / newZoom;\n        const newHeight = CANVAS_HEIGHT / newZoom;\n        const pointX = canvasState.panX + centerX * oldWidth;\n        const pointY = canvasState.panY + centerY * oldHeight;\n        canvasState.panX = pointX - centerX * newWidth;\n        canvasState.panY = pointY - centerY * newHeight;\n       }\n       canvasState.zoom = newZoom;\n       constrainPan();\n       updateViewBox();\n      }\n      function constrainPan() {\n       const viewWidth = CANVAS_WIDTH / canvasState.zoom;\n       const viewHeight = CANVAS_HEIGHT / canvasState.zoom;\n       const minVisiblePortion = 0.1;\n       const maxPanX = CANVAS_WIDTH - viewWidth * minVisiblePortion;\n       const maxPanY = CANVAS_HEIGHT - viewHeight * minVisiblePortion;\n       const minPanX = -viewWidth * (1 - minVisiblePortion);\n       const minPanY = -viewHeight * (1 - minVisiblePortion);\n       canvasState.panX = Math.max(minPanX, Math.min(maxPanX, canvasState.panX));\n       canvasState.panY = Math.max(minPanY, Math.min(maxPanY, canvasState.panY));\n      }\n      function fitToContent() {\n       const positions = Object.values(savedPositions);\n       if (positions.length === 0) {\n        resetView();\n        return;\n       }\n       let minX = Infinity,\n        minY = Infinity,\n        maxX = -Infinity,\n        maxY = -Infinity;\n       positions.forEach((pos) => {\n        minX = Math.min(minX, pos.x - 100);\n        minY = Math.min(minY, pos.y - 100);\n        maxX = Math.max(maxX, pos.x + 100);\n        maxY = Math.max(maxY, pos.y + 100);\n       });\n       const contentWidth = maxX - minX + 200;\n       const contentHeight = maxY - minY + 200;\n       const viewport = document.getElementById(\"canvas-viewport\");\n       const vpRect = viewport.getBoundingClientRect();\n       const aspectRatio = vpRect.width / vpRect.height;\n       const contentAspect = contentWidth / contentHeight;\n       let newZoom;\n       if (contentAspect > aspectRatio) {\n        newZoom = (vpRect.width / contentWidth) * (CANVAS_WIDTH / vpRect.width);\n       } else {\n        newZoom = (vpRect.height / contentHeight) * (CANVAS_HEIGHT / vpRect.height);\n       }\n       newZoom = Math.max(canvasState.minZoom, Math.min(2, newZoom));\n       const viewWidth = CANVAS_WIDTH / newZoom;\n       const viewHeight = CANVAS_HEIGHT / newZoom;\n       canvasState.zoom = newZoom;\n       canvasState.panX = minX - 100 - (viewWidth - contentWidth) / 2;\n       canvasState.panY = minY - 100 - (viewHeight - contentHeight) / 2;\n       constrainPan();\n       updateViewBox();\n      }\n      function resetView() {\n       canvasState.zoom = 1;\n       canvasState.panX = 0;\n       canvasState.panY = 0;\n       updateViewBox();\n      }\n      function focusOnNodes(nodeIds) {\n       if (!nodeIds || nodeIds.length === 0) return;\n       const positions = nodeIds.map(id => savedPositions[id]).filter(Boolean);\n       if (positions.length === 0) return;\n       let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;\n       positions.forEach(pos => {\n        minX = Math.min(minX, pos.x);\n        minY = Math.min(minY, pos.y);\n        maxX = Math.max(maxX, pos.x);\n        maxY = Math.max(maxY, pos.y);\n       });\n       const padding = 150;\n       const contentWidth = Math.max(maxX - minX + padding * 2, 300);\n       const contentHeight = Math.max(maxY - minY + padding * 2, 300);\n       const centerX = (minX + maxX) / 2;\n       const centerY = (minY + maxY) / 2;\n       const viewport = document.getElementById(\"canvas-viewport\");\n       const vpRect = viewport.getBoundingClientRect();\n       const aspectRatio = vpRect.width / vpRect.height;\n       const contentAspect = contentWidth / contentHeight;\n       let newZoom;\n       if (contentAspect > aspectRatio) {\n        newZoom = (vpRect.width / contentWidth) * (CANVAS_WIDTH / vpRect.width);\n       } else {\n        newZoom = (vpRect.height / contentHeight) * (CANVAS_HEIGHT / vpRect.height);\n       }\n       newZoom = Math.max(canvasState.minZoom, Math.min(1.5, newZoom));\n       const viewWidth = CANVAS_WIDTH / newZoom;\n       const viewHeight = CANVAS_HEIGHT / newZoom;\n       canvasState.zoom = newZoom;\n       canvasState.panX = centerX - viewWidth / 2;\n       canvasState.panY = centerY - viewHeight / 2;\n       constrainPan();\n       updateViewBox();\n      }\n      window.addEventListener(\"DOMContentLoaded\", () => {\n       const toggle = document.getElementById(\"mobile-menu-toggle\");\n       const menu = document.getElementById(\"topbar-menu\");\n       if (!toggle || !menu) return;\n       toggle.addEventListener(\"click\", () => {\n        menu.classList.toggle(\"open\");\n       });\n       menu.addEventListener(\"click\", (e) => {\n        const target = e.target.closest(\"a, button\");\n        if (!target) return;\n        if (isMobileDevice()) {\n       menu.classList.remove(\"open\");\n      }\n       });\n       const minimapCloseBtn = document.getElementById(\"minimap-close-btn\");\n       if (minimapCloseBtn) {\n        const handleMinimapClose = (e) => {\n         e.stopPropagation();\n         e.preventDefault();\n         minimapCollapsed = true;\n         updateMinimapVisibility();\n        };\n        minimapCloseBtn.addEventListener(\"click\", handleMinimapClose);\n        minimapCloseBtn.addEventListener(\"touchend\", handleMinimapClose);\n       }\n       const drawToolbarCloseBtn = document.getElementById(\"draw-toolbar-close-btn\");\n       if (drawToolbarCloseBtn) {\n        const handleDrawClose = (e) => {\n         e.stopPropagation();\n         e.preventDefault();\n         drawToolbarCollapsed = true;\n         updateDrawToolbarVisibility();\n        };\n        drawToolbarCloseBtn.addEventListener(\"click\", handleDrawClose);\n        drawToolbarCloseBtn.addEventListener(\"touchend\", handleDrawClose);\n       }\n       const topologyToolbarCloseBtn = document.getElementById(\"topology-toolbar-close-btn\");\n       if (topologyToolbarCloseBtn) {\n        const handleTopologyClose = (e) => {\n         e.stopPropagation();\n         e.preventDefault();\n         topologyToolbarCollapsed = true;\n         updateTopologyToolbarVisibility();\n        };\n        topologyToolbarCloseBtn.addEventListener(\"click\", handleTopologyClose);\n        topologyToolbarCloseBtn.addEventListener(\"touchstart\", (e) => e.preventDefault(), {\n         passive: false\n        });\n        topologyToolbarCloseBtn.addEventListener(\"touchend\", handleTopologyClose);\n       }\n       updateMinimapVisibility();\n       updateDrawToolbarVisibility();\n       updateTopologyToolbarVisibility();\n      });\n      function applyLayerFilter() {\n       activeLayers.clear();\n       if (document.getElementById(\"layer-physical\").checked) activeLayers.add(\"physical\");\n       if (document.getElementById(\"layer-logical\").checked) activeLayers.add(\"logical\");\n       if (document.getElementById(\"layer-security\").checked) activeLayers.add(\"security\");\n       if (document.getElementById(\"layer-application\").checked) activeLayers.add(\"application\");\n       forgeTheTopology();\n      }\n      function isNodeVisible(nodeId) {\n       const node = NODE_DATA[nodeId];\n       if (!node) return false;\n       const nodeLayer = node.layer || \"physical\";\n       return activeLayers.has(nodeLayer);\n      }\n      function enterRack(rackId) {\n      if (!NODE_DATA[rackId] || !NODE_DATA[rackId].isRack) return;\n      const rackCapacity = getRackCapacity(rackId);\n      const neededHeight = RACK_START_Y + (rackCapacity * RACK_U_HEIGHT) + 200;\n      if (neededHeight > BASE_CANVAS_HEIGHT) {\n      CANVAS_HEIGHT = neededHeight;\n      }\n       savedTopologyView = {\n        zoom: canvasState.zoom,\n        panX: canvasState.panX,\n        panY: canvasState.panY\n       };\n       currentView.mode = \"rack\";\n       currentView.rackId = rackId;\n       const backBtn = document.getElementById(\"back-to-topology-btn\");\n       if (backBtn) backBtn.style.display = \"inline-block\";\n       const hint = document.getElementById(\"canvas-hint\");\n       if (hint) {\n        hint.textContent = `Viewing: ${NODE_DATA[rackId]?.name || 'Rack'} | Double click empty space to exit`;\n        hint.classList.add(\"visible\");\n       }\n      const rackUHeight = getRackUHeight(rackId);\n      const rackHeight = rackCapacity * rackUHeight;\n      const rackCenterX = RACK_START_X;\n      const rackCenterY = RACK_START_Y + (rackHeight / 2);\n       const viewWidth = CANVAS_WIDTH / canvasState.zoom;\n       const viewHeight = CANVAS_HEIGHT / canvasState.zoom;\n       canvasState.panX = rackCenterX - (viewWidth / 2);\n       canvasState.panY = rackCenterY - (viewHeight / 2);\n       constrainPan();\n       updateViewBox();\n       forgeTheTopology();\n      }\n      function exitRack() {\n      CANVAS_HEIGHT = BASE_CANVAS_HEIGHT;\n      currentView.mode = \"topology\";\n      currentView.rackId = null;\n       const backBtn = document.getElementById(\"back-to-topology-btn\");\n       if (backBtn) backBtn.style.display = \"none\";\n       const hint = document.getElementById(\"canvas-hint\");\n       if (hint) {\n        hint.classList.remove(\"visible\");\n       }\n       if (savedTopologyView) {\n        canvasState.zoom = savedTopologyView.zoom;\n        canvasState.panX = savedTopologyView.panX;\n        canvasState.panY = savedTopologyView.panY;\n        updateViewBox();\n       }\n       forgeTheTopology();\n      }\n      function getRackCapacity(rackId) {\n       const node = NODE_DATA[rackId];\n       return node && node.rackCapacity ? parseInt(node.rackCapacity) : 42;\n      }\n      function populateRackDropdown() {\n       const dropdown = document.getElementById(\"node-assigned-rack\");\n       if (!dropdown) return;\n       dropdown.innerHTML = '<option value=\"\">None</option>';\n       Object.keys(NODE_DATA).forEach(id => {\n        if (NODE_DATA[id].isRack) {\n         const option = document.createElement(\"option\");\n         option.value = id;\n         option.textContent = NODE_DATA[id].name;\n         dropdown.appendChild(option);\n        }\n       });\n      }\n      function wieldThePower() {\n       const root = document.documentElement;\n       root.style.setProperty(\"--panel\", PAGE_STATE.panel);\n       root.style.setProperty(\"--panel-alt\", PAGE_STATE.panelAlt);\n       root.style.setProperty(\"--sidebar-bg\", PAGE_STATE.sidebarBg || PAGE_STATE.panelAlt);\n       root.style.setProperty(\"--btn-bg\", PAGE_STATE.btnBg || PAGE_STATE.panel);\n       root.style.setProperty(\"--btn-text\", PAGE_STATE.btnText || \"#e2e8f0\");\n       root.style.setProperty(\"--tag-fill\", PAGE_STATE.tagFill || \"#1e293b\");\n       root.style.setProperty(\"--tag-text\", PAGE_STATE.tagText || \"#e2e8f0\");\n       root.style.setProperty(\"--tag-border\", PAGE_STATE.tagBorder || \"#475569\");\n       root.style.setProperty(\"--input-bg\", PAGE_STATE.inputBg || \"#0b0e13\");\n       root.style.setProperty(\"--input-text\", PAGE_STATE.inputText || \"#e2e8f0\");\n       root.style.setProperty(\"--input-border\", PAGE_STATE.inputBorder || \"#1f2937\");\n       root.style.setProperty(\"--input-font\", PAGE_STATE.inputFont || \"Inter, system-ui, sans-serif\");\n       root.style.setProperty(\"--input-font-size\", (PAGE_STATE.inputFontSize || 14) + \"px\");\n       root.style.setProperty(\"--toolbar-bg\", PAGE_STATE.toolbarBg || \"#0f172a\");\n       root.style.setProperty(\"--toolbar-border\", PAGE_STATE.toolbarBorder || \"#1f2937\");\n       root.style.setProperty(\"--toolbar-text\", PAGE_STATE.toolbarText || \"#94a3b8\");\n       root.style.setProperty(\"--toolbar-btn-bg\", PAGE_STATE.toolbarBtnBg || \"#0b0e13\");\n       root.style.setProperty(\"--toolbar-btn-text\", PAGE_STATE.toolbarBtnText || \"#e2e8f0\");\n       root.style.setProperty(\"--minimap-dots\", PAGE_STATE.minimapDots || \"#94a3b8\");\n       root.style.setProperty(\"--canvas-hint-bg\", PAGE_STATE.canvasHintBg || \"#0f172a\");\n       root.style.setProperty(\"--canvas-hint-color\", PAGE_STATE.canvasHintColor || \"#94a3b8\");\n       const canvasHint = document.getElementById(\"canvas-hint\");\n       if (canvasHint) {\n        canvasHint.style.display = PAGE_STATE.canvasHintEnabled === false ? \"none\" : \"\";\n        if (PAGE_STATE.canvasHintText !== undefined && PAGE_STATE.canvasHintText !== \"\") {\n         canvasHint.textContent = PAGE_STATE.canvasHintText;\n        } else {\n         canvasHint.innerHTML = DEFAULT_CANVAS_HINT;\n        }\n       }\n       root.style.setProperty(\"--accent\", PAGE_STATE.accent);\n       root.style.setProperty(\"--danger\", PAGE_STATE.danger);\n       root.style.setProperty(\"--text-main\", PAGE_STATE.textMain);\n       root.style.setProperty(\"--text-soft\", PAGE_STATE.textSoft);\n       root.style.setProperty(\"--topbar-bg\", PAGE_STATE.topbarBg);\n       root.style.setProperty(\"--topbar-border\", PAGE_STATE.topbarBorder);\n       root.style.setProperty(\"--node-fill\", PAGE_STATE.nodeFill || \"#1e293b\");\n       root.style.setProperty(\"--node-stroke\", PAGE_STATE.nodeStroke || \"#475569\");\n       root.style.setProperty(\"--node-title\", PAGE_STATE.nodeTitle || \"#e2e8f0\");\n       root.style.setProperty(\"--node-sub\", PAGE_STATE.nodeSub || \"#94a3b8\");\n       root.style.setProperty(\"--node-title-size\", (PAGE_STATE.nodeTitleSize || 18) + \"px\");\n       root.style.setProperty(\"--node-sub-size\", (PAGE_STATE.nodeSubSize || 13) + \"px\");\n       root.style.setProperty(\"--node-font\", PAGE_STATE.nodeFont || \"Inter, system-ui, sans-serif\");\n       root.style.setProperty(\"--default-edge\", PAGE_STATE.defaultEdge || \"#475569\");\n       root.style.setProperty(\"--selection-handle\", PAGE_STATE.selectionHandle || \"#f59e0b\");\n       root.style.setProperty(\"--selection-handle-size\", (PAGE_STATE.selectionHandleSize || 8) + \"px\");\n       root.style.setProperty(\"--group-indicator\", PAGE_STATE.groupIndicator || \"#4fd1c5\");\n       const topbarHeight = PAGE_STATE.topbarHeight || 52;\n       const sidebarWidth = PAGE_STATE.sidebarWidth || 350;\n       const mobileFooterHeight = PAGE_STATE.mobileFooterHeight || 40;\n       root.style.setProperty(\"--topbar-height\", topbarHeight + \"px\");\n       root.style.setProperty(\"--sidebar-width\", sidebarWidth + \"px\");\n       root.style.setProperty(\"--mobile-footer-height\", mobileFooterHeight + \"vh\");\n       const mainEl = document.querySelector(\"main\");\n       const detailsPanel = document.getElementById(\"details-panel\");\n       const sidebarToggle = document.getElementById(\"sidebar-toggle\");\n       if (PAGE_STATE.sidebarCollapsed) {\n        if (mainEl) mainEl.classList.add(\"sidebar-collapsed\");\n        if (detailsPanel) detailsPanel.classList.add(\"collapsed\");\n        if (sidebarToggle) {\n         sidebarToggle.classList.add(\"collapsed\");\n         sidebarToggle.textContent = \"▶\";\n        }\n       } else {\n        if (mainEl) mainEl.classList.remove(\"sidebar-collapsed\");\n        if (detailsPanel) detailsPanel.classList.remove(\"collapsed\");\n        if (sidebarToggle) {\n         sidebarToggle.classList.remove(\"collapsed\");\n         sidebarToggle.textContent = \"◀\";\n        }\n       }\n       if (PAGE_STATE.background) {\n        document.body.style.background = PAGE_STATE.background;\n       } else {\n        document.body.style.background = `radial-gradient(circle at top, ${PAGE_STATE.canvasGradientTop || \"#1e2532\"} 0, ${PAGE_STATE.canvasGradientBottom || \"#050608\"} 70%)`;\n       }\n       document.title = PAGE_STATE.title || DEFAULT_PAGE_STATE.title;\n       const titleEl = document.getElementById(\"page-title\");\n       if (titleEl) {\n        titleEl.textContent = PAGE_STATE.title || DEFAULT_PAGE_STATE.title;\n       }\n       const sidebarToggleEl = document.getElementById(\"sidebar-toggle\");\n       const isMobile = isMobileDevice();\n       if (sidebarToggleEl) {\n        sidebarToggleEl.style.display = isMobile ? \"none\" : \"flex\";\n       }\n       const viewOnlyMode = PAGE_STATE.viewOnly === true;\n       const addNodeBtn = document.getElementById(\"add-node-btn\");\n       const addRackBtn = document.getElementById(\"add-rack-btn\");\n       const drawToolbar = document.getElementById(\"draw-toolbar\");\n       const topologyToolbar = document.getElementById(\"topology-toolbar\");\n       const bulkToolbar = document.getElementById(\"bulk-toolbar\");\n       if (addNodeBtn) addNodeBtn.style.display = viewOnlyMode ? \"none\" : \"\";\n       if (addRackBtn) addRackBtn.style.display = viewOnlyMode ? \"none\" : \"\";\n       if (viewOnlyMode) {\n        if (drawToolbar) drawToolbar.style.setProperty('display', 'none', 'important');\n        if (topologyToolbar) topologyToolbar.style.setProperty('display', 'none', 'important');\n        if (bulkToolbar) bulkToolbar.style.display = \"none\";\n        const bulkToolbarMobile = document.getElementById(\"bulk-toolbar-mobile\");\n        if (bulkToolbarMobile) bulkToolbarMobile.style.display = \"none\";\n       }\n       const bulkToolbarMobile = document.getElementById(\"bulk-toolbar-mobile\");\n       if (bulkToolbarMobile) bulkToolbarMobile.style.display = viewOnlyMode ? \"none\" : \"\";\n       [\"draw-toolbar-mini\", \"topology-toolbar-mini\"].forEach(id => {\n        const el = document.getElementById(id);\n        if (el) el.style.display = viewOnlyMode ? \"none\" : \"\";\n       });\n       document.body.classList.toggle(\"view-only-mode\", viewOnlyMode);\n      }\n      (async function awakeTheImmortal() {\n       let initialState = {};\n       let decryptionCancelled = false;\n       const stateEl = document.getElementById(\"topology-state\");\n       if (stateEl && stateEl.textContent.trim()) {\n        try {\n         let stateText = stateEl.textContent.trim();\n         if (isEncrypted(stateText)) {\n          let decrypted = false;\n          let attempts = 0;\n          const maxAttempts = 3;\n          while (!decrypted && attempts < maxAttempts) {\n           const password = prompt(\"This file is encrypted. Enter password to decrypt:\\n(Attempt \" + (attempts + 1) + \" of \" + maxAttempts + \")\");\n           if (!password) {\n            alert(\"Decryption cancelled. The file will not be loaded.\");\n            decryptionCancelled = true;\n            break;\n           }\n           try {\n            stateText = await decryptData(stateText, password);\n            decrypted = true;\n           } catch (e) {\n            attempts++;\n            if (attempts < maxAttempts) {\n             alert(\"Incorrect password. Please try again.\");\n            } else {\n             alert(\"Maximum attempts reached. The file will not be loaded.\");\n             decryptionCancelled = true;\n            }\n           }\n          }\n          if (!decrypted) {\n           stateText = \"{}\";\n          }\n         }\n         initialState = JSON.parse(stateText);\n        } catch (e) {\n         console.error(\"Failed to load state:\", e);\n         initialState = {};\n        }\n       }\n       if (decryptionCancelled) {\n        NODE_DATA = {};\n        EDGE_DATA = {\n         list: []\n        };\n        RECT_DATA = { list: [] };\n        TEXT_DATA = { list: [] };\n        EDGE_LEGEND = {};\n        savedPositions = {};\n        savedSizes = {};\n        savedStyles = {};\n       } else {\n        NODE_DATA = initialState.nodeData ? initialState.nodeData : BASE_NODE_DATA;\n        EDGE_DATA = initialState.edgeData ? initialState.edgeData : {\n         list: [],\n        };\n        RECT_DATA = initialState.rectData ? initialState.rectData : { list: [] };\n        TEXT_DATA = initialState.textData ? initialState.textData : { list: [] };\n        EDGE_LEGEND = initialState.edgeLegend || {};\n        savedPositions = initialState.nodePositions || {};\n        savedSizes = initialState.nodeSizes || {};\n        savedStyles = initialState.nodeStyles || {};\n       }\n       if (initialState.page) {\n        PAGE_STATE = Object.assign({}, DEFAULT_PAGE_STATE, initialState.page);\n       }\n       if (initialState.canvas) {\n        canvasState.zoom = initialState.canvas.zoom || 1;\n        canvasState.panX = initialState.canvas.panX || 0;\n        canvasState.panY = initialState.canvas.panY || 0;\n       }\n       if (initialState.savedTopologyView) {\n        savedTopologyView = initialState.savedTopologyView;\n       }\n       if (initialState.documentTabs) {\n        documentTabs = initialState.documentTabs;\n        currentTabIndex = initialState.currentTabIndex || 0;\n        const currentTab = documentTabs[currentTabIndex];\n        if (currentTab) {\n          NODE_DATA = currentTab.nodes || NODE_DATA;\n          EDGE_DATA = currentTab.edges || EDGE_DATA;\n          savedPositions = currentTab.positions || savedPositions;\n          savedSizes = currentTab.sizes || savedSizes;\n          savedStyles = currentTab.styles || savedStyles;\n          EDGE_LEGEND = currentTab.legend || EDGE_LEGEND;\n          RECT_DATA = currentTab.rects || RECT_DATA;\n          TEXT_DATA = currentTab.texts || TEXT_DATA;\n      if (currentTab.pageState) PAGE_STATE = Object.assign({}, DEFAULT_PAGE_STATE, currentTab.pageState);\n        }\n       }\n       if (initialState.encryptedSections) {\n        encryptedSections = initialState.encryptedSections;\n       }\n       wieldThePower();\n       forgeTheTopology();\n       updateViewBox();\n       MobileManager.applyAll();\n       const initialNodes = Object.keys(NODE_DATA);\n       if (initialNodes.length > 0) {\n        claimTheImmortal(initialNodes.includes(\"host\") ? \"host\" : initialNodes[0]);\n       } else {\n        document.getElementById(\"node-panel\").style.display = \"none\";\n        document.getElementById(\"topology-toolbar\").style.display = \"none\";\n       }\n       if (Object.keys(NODE_DATA).length === 0 && EDGE_DATA.list.length === 0) {\n        checkForAutosaveRecovery();\n       }\n       startAutosave();\n      })();\n      let resizeDebounceTimer = null;\n      window.addEventListener(\"resize\", () => {\n        clearTimeout(resizeDebounceTimer);\n        resizeDebounceTimer = setTimeout(() => {\n          MobileManager.applyAll();\n          if (typeof updateDrawToolbarVisibility === 'function') updateDrawToolbarVisibility();\n          if (typeof updateTopologyToolbarVisibility === 'function') updateTopologyToolbarVisibility();\n        }, 100);\n      });\n      function getBreakpointKey() {\n       const w = window.innerWidth;\n       if (w <= 380) return \"fold\";\n       if (w <= 768) return \"mobile\";\n       if (w <= 1024) return \"tablet\";\n       return \"desktop\";\n      }\n      function resolveStylesEntry(styleEntry) {\n       if (!styleEntry) return {};\n       if (styleEntry.circleColor || styleEntry.titleColor || styleEntry.titleFont || styleEntry.titleSize || styleEntry.subColor || styleEntry.subFont || styleEntry.subSize) {\n        return styleEntry;\n       }\n       const bp = getBreakpointKey();\n       const base = styleEntry.all || {};\n       const bpStyles = styleEntry[bp] || {};\n       return Object.assign({}, base, bpStyles);\n      }\n      function resolveStylesForNode(id) {\n       const styleEntry = savedStyles[id];\n       if (!styleEntry) return {};\n       return resolveStylesEntry(styleEntry);\n      }\n      function ensureStyleEntry(id) {\n       if (!savedStyles[id]) savedStyles[id] = {};\n       const entry = savedStyles[id];\n       const flatProps = [\"circleColor\", \"circleBorder\", \"titleColor\", \"titleFont\", \"titleSize\", \"subColor\", \"subFont\", \"subSize\", ];\n       const isFlat = flatProps.some((p) => Object.prototype.hasOwnProperty.call(entry, p));\n       if (isFlat) {\n        const all = entry.all || {};\n        flatProps.forEach((p) => {\n         if (entry[p] !== undefined) {\n          all[p] = entry[p];\n          delete entry[p];\n         }\n        });\n        entry.all = all;\n       }\n       return entry;\n      }\n      function getDefaultSize() {\n       if (window.innerWidth <= 380) return 120;\n       if (window.innerWidth <= 768) return 140;\n       if (window.innerWidth <= 1024) return 70;\n       return 55;\n      }\n      function createShapeElement(shape, size) {\n       const ns = \"http://www.w3.org/2000/svg\";\n       if (shape === \"circle\") {\n        const c = document.createElementNS(ns, \"circle\");\n        c.setAttribute(\"r\", size);\n        return c;\n       }\n       if (shape === \"square\") {\n        const r = document.createElementNS(ns, \"rect\");\n        const s = size * 2;\n        r.setAttribute(\"x\", -size);\n        r.setAttribute(\"y\", -size);\n        r.setAttribute(\"width\", s);\n        r.setAttribute(\"height\", s);\n        r.setAttribute(\"rx\", 4);\n        return r;\n       }\n       if (shape === \"rectangle\") {\n        const r = document.createElementNS(ns, \"rect\");\n        r.setAttribute(\"x\", -size * 1.4);\n        r.setAttribute(\"y\", -size * 0.8);\n        r.setAttribute(\"width\", size * 2.8);\n        r.setAttribute(\"height\", size * 1.6);\n        r.setAttribute(\"rx\", 6);\n        return r;\n       }\n       if (shape === \"triangle\") {\n        const p = document.createElementNS(ns, \"polygon\");\n        const h = size * 1.8;\n        const w = size * 2;\n        p.setAttribute(\"points\", `0,${-h} ${w / 2},${h / 2} ${-w / 2},${h / 2}`);\n        return p;\n       }\n       if (shape === \"hexagon\") {\n        const p = document.createElementNS(ns, \"polygon\");\n        const s = size;\n        const pts = [\n         [0, -s],\n         [s * 0.86, -s * 0.5],\n         [s * 0.86, s * 0.5],\n         [0, s],\n         [-s * 0.86, s * 0.5],\n         [-s * 0.86, -s * 0.5],\n        ].map((pt) => pt.join(\",\")).join(\" \");\n        p.setAttribute(\"points\", pts);\n        return p;\n       }\n       if (shape === \"stop-sign\") {\n        const p = document.createElementNS(ns, \"polygon\");\n        const r = size;\n        const ptsArr = [];\n        for (let i = 0; i < 8; i++) {\n         const a = (Math.PI / 4) * i + Math.PI / 8;\n         ptsArr.push([Math.cos(a) * r, Math.sin(a) * r]);\n        }\n        const pts = ptsArr.map((pt) => pt.join(\",\")).join(\" \");\n        p.setAttribute(\"points\", pts);\n        return p;\n       }\n       if (shape === \"star\") {\n        const p = document.createElementNS(ns, \"polygon\");\n        const outer = size;\n        const inner = size * 0.45;\n        let pts = \"\";\n        for (let i = 0; i < 10; i++) {\n         const a = (Math.PI / 5) * i - Math.PI / 2;\n         const r = i % 2 === 0 ? outer : inner;\n         pts += `${Math.cos(a) * r},${Math.sin(a) * r} `;\n        }\n        p.setAttribute(\"points\", pts.trim());\n        return p;\n       }\n       if (shape === \"diamond\") {\n        const p = document.createElementNS(ns, \"polygon\");\n        const s = size;\n        p.setAttribute(\"points\", `0,${-s} ${s},0 0,${s} ${-s},0`);\n        return p;\n       }\n       if (shape === \"server\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 1.2);\n        body.setAttribute(\"y\", -size * 0.6);\n        body.setAttribute(\"width\", size * 2.4);\n        body.setAttribute(\"height\", size * 1.2);\n        body.setAttribute(\"rx\", 4);\n        g.appendChild(body);\n        for (let i = 0; i < 3; i++) {\n         const line = document.createElementNS(ns, \"line\");\n         line.setAttribute(\"x1\", -size * 0.9 + i * size * 0.5);\n         line.setAttribute(\"y1\", -size * 0.3);\n         line.setAttribute(\"x2\", -size * 0.9 + i * size * 0.5);\n         line.setAttribute(\"y2\", size * 0.3);\n         line.style.stroke = \"currentColor\";\n         line.style.strokeWidth = \"2\";\n         line.style.opacity = \"0.5\";\n         g.appendChild(line);\n        }\n        const led = document.createElementNS(ns, \"circle\");\n        led.setAttribute(\"cx\", size * 0.9);\n        led.setAttribute(\"cy\", 0);\n        led.setAttribute(\"r\", size * 0.1);\n        led.style.fill = \"#4ade80\";\n        g.appendChild(led);\n        return g;\n       }\n       if (shape === \"pc\" || shape === \"desktop\") {\n        const g = document.createElementNS(ns, \"g\");\n        const monitor = document.createElementNS(ns, \"rect\");\n        monitor.setAttribute(\"x\", -size * 0.9);\n        monitor.setAttribute(\"y\", -size * 0.8);\n        monitor.setAttribute(\"width\", size * 1.8);\n        monitor.setAttribute(\"height\", size * 1.2);\n        monitor.setAttribute(\"rx\", 4);\n        g.appendChild(monitor);\n        const screen = document.createElementNS(ns, \"rect\");\n        screen.setAttribute(\"x\", -size * 0.75);\n        screen.setAttribute(\"y\", -size * 0.65);\n        screen.setAttribute(\"width\", size * 1.5);\n        screen.setAttribute(\"height\", size * 0.9);\n        screen.setAttribute(\"rx\", 2);\n        screen.style.fill = \"#1e293b\";\n        g.appendChild(screen);\n        const neck = document.createElementNS(ns, \"rect\");\n        neck.setAttribute(\"x\", -size * 0.15);\n        neck.setAttribute(\"y\", size * 0.4);\n        neck.setAttribute(\"width\", size * 0.3);\n        neck.setAttribute(\"height\", size * 0.3);\n        g.appendChild(neck);\n        const base = document.createElementNS(ns, \"rect\");\n        base.setAttribute(\"x\", -size * 0.5);\n        base.setAttribute(\"y\", size * 0.7);\n        base.setAttribute(\"width\", size * 1);\n        base.setAttribute(\"height\", size * 0.15);\n        base.setAttribute(\"rx\", 2);\n        g.appendChild(base);\n        return g;\n       }\n       if (shape === \"laptop\") {\n        const g = document.createElementNS(ns, \"g\");\n        const screen = document.createElementNS(ns, \"rect\");\n        screen.setAttribute(\"x\", -size * 0.8);\n        screen.setAttribute(\"y\", -size * 0.9);\n        screen.setAttribute(\"width\", size * 1.6);\n        screen.setAttribute(\"height\", size * 1.1);\n        screen.setAttribute(\"rx\", 4);\n        g.appendChild(screen);\n        const base = document.createElementNS(ns, \"rect\");\n        base.setAttribute(\"x\", -size * 0.9);\n        base.setAttribute(\"y\", size * 0.25);\n        base.setAttribute(\"width\", size * 1.8);\n        base.setAttribute(\"height\", size * 0.6);\n        base.setAttribute(\"rx\", 4);\n        g.appendChild(base);\n        const pad = document.createElementNS(ns, \"rect\");\n        pad.setAttribute(\"x\", -size * 0.25);\n        pad.setAttribute(\"y\", size * 0.45);\n        pad.setAttribute(\"width\", size * 0.5);\n        pad.setAttribute(\"height\", size * 0.25);\n        pad.setAttribute(\"rx\", 2);\n        pad.style.fill = \"#1e293b\";\n        g.appendChild(pad);\n        return g;\n       }\n       if (shape === \"phone\" || shape === \"mobile\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 0.45);\n        body.setAttribute(\"y\", -size);\n        body.setAttribute(\"width\", size * 0.9);\n        body.setAttribute(\"height\", size * 2);\n        body.setAttribute(\"rx\", 8);\n        g.appendChild(body);\n        const screen = document.createElementNS(ns, \"rect\");\n        screen.setAttribute(\"x\", -size * 0.38);\n        screen.setAttribute(\"y\", -size * 0.85);\n        screen.setAttribute(\"width\", size * 0.76);\n        screen.setAttribute(\"height\", size * 1.6);\n        screen.setAttribute(\"rx\", 4);\n        screen.style.fill = \"#1e293b\";\n        g.appendChild(screen);\n        const btn = document.createElementNS(ns, \"rect\");\n        btn.setAttribute(\"x\", -size * 0.15);\n        btn.setAttribute(\"y\", size * 0.82);\n        btn.setAttribute(\"width\", size * 0.3);\n        btn.setAttribute(\"height\", size * 0.06);\n        btn.setAttribute(\"rx\", 2);\n        btn.style.fill = \"#475569\";\n        g.appendChild(btn);\n        return g;\n       }\n       if (shape === \"router\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 1.1);\n        body.setAttribute(\"y\", -size * 0.3);\n        body.setAttribute(\"width\", size * 2.2);\n        body.setAttribute(\"height\", size * 0.8);\n        body.setAttribute(\"rx\", 6);\n        g.appendChild(body);\n        for (let i = -1; i <= 1; i++) {\n         const ant = document.createElementNS(ns, \"rect\");\n         ant.setAttribute(\"x\", i * size * 0.6 - size * 0.05);\n         ant.setAttribute(\"y\", -size * 0.9);\n         ant.setAttribute(\"width\", size * 0.1);\n         ant.setAttribute(\"height\", size * 0.6);\n         ant.setAttribute(\"rx\", 2);\n         g.appendChild(ant);\n         const tip = document.createElementNS(ns, \"circle\");\n         tip.setAttribute(\"cx\", i * size * 0.6);\n         tip.setAttribute(\"cy\", -size * 0.95);\n         tip.setAttribute(\"r\", size * 0.08);\n         g.appendChild(tip);\n        }\n        for (let i = 0; i < 4; i++) {\n         const led = document.createElementNS(ns, \"circle\");\n         led.setAttribute(\"cx\", -size * 0.7 + i * size * 0.35);\n         led.setAttribute(\"cy\", size * 0.1);\n         led.setAttribute(\"r\", size * 0.06);\n         led.style.fill = i < 2 ? \"#4ade80\" : \"#facc15\";\n         g.appendChild(led);\n        }\n        return g;\n       }\n       if (shape === \"switch\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 1.4);\n        body.setAttribute(\"y\", -size * 0.4);\n        body.setAttribute(\"width\", size * 2.8);\n        body.setAttribute(\"height\", size * 0.8);\n        body.setAttribute(\"rx\", 4);\n        g.appendChild(body);\n        for (let i = 0; i < 8; i++) {\n         const port = document.createElementNS(ns, \"rect\");\n         port.setAttribute(\"x\", -size * 1.2 + i * size * 0.32);\n         port.setAttribute(\"y\", -size * 0.15);\n         port.setAttribute(\"width\", size * 0.22);\n         port.setAttribute(\"height\", size * 0.3);\n         port.setAttribute(\"rx\", 1);\n         port.style.fill = \"#1e293b\";\n         g.appendChild(port);\n        }\n        return g;\n       }\n       if (shape === \"firewall\") {\n        const g = document.createElementNS(ns, \"g\");\n        const wall = document.createElementNS(ns, \"rect\");\n        wall.setAttribute(\"x\", -size);\n        wall.setAttribute(\"y\", -size * 0.8);\n        wall.setAttribute(\"width\", size * 2);\n        wall.setAttribute(\"height\", size * 1.6);\n        wall.setAttribute(\"rx\", 4);\n        g.appendChild(wall);\n        for (let row = 0; row < 3; row++) {\n         const line = document.createElementNS(ns, \"line\");\n         line.setAttribute(\"x1\", -size * 0.85);\n         line.setAttribute(\"y1\", -size * 0.5 + row * size * 0.45);\n         line.setAttribute(\"x2\", size * 0.85);\n         line.setAttribute(\"y2\", -size * 0.5 + row * size * 0.45);\n         line.style.stroke = \"#475569\";\n         line.style.strokeWidth = \"2\";\n         g.appendChild(line);\n        }\n        for (let row = 0; row < 4; row++) {\n         const offset = row % 2 === 0 ? 0 : size * 0.35;\n         for (let col = 0; col < 3; col++) {\n          const line = document.createElementNS(ns, \"line\");\n          const x = -size * 0.5 + col * size * 0.7 + offset;\n          if (x > -size * 0.85 && x < size * 0.85) {\n           line.setAttribute(\"x1\", x);\n           line.setAttribute(\"y1\", -size * 0.8 + row * size * 0.45);\n           line.setAttribute(\"x2\", x);\n           line.setAttribute(\"y2\", -size * 0.8 + row * size * 0.45 + size * 0.45);\n           line.style.stroke = \"#475569\";\n           line.style.strokeWidth = \"2\";\n           g.appendChild(line);\n          }\n         }\n        }\n        return g;\n       }\n       if (shape === \"cloud\") {\n        const p = document.createElementNS(ns, \"path\");\n        const s = size;\n        p.setAttribute(\"d\", `\n             M ${-s * 0.8} ${s * 0.2}\n             Q ${-s * 1.1} ${s * 0.2} ${-s * 1.1} ${-s * 0.1}\n             Q ${-s * 1.1} ${-s * 0.5} ${-s * 0.7} ${-s * 0.5}\n             Q ${-s * 0.7} ${-s * 0.9} ${-s * 0.2} ${-s * 0.9}\n             Q ${s * 0.1} ${-s * 1.1} ${s * 0.5} ${-s * 0.8}\n             Q ${s * 1} ${-s * 0.8} ${s * 1.1} ${-s * 0.3}\n             Q ${s * 1.3} ${-s * 0.1} ${s * 1.1} ${s * 0.2}\n             Q ${s * 1.1} ${s * 0.5} ${s * 0.7} ${s * 0.5}\n             L ${-s * 0.5} ${s * 0.5}\n             Q ${-s * 0.9} ${s * 0.5} ${-s * 0.9} ${s * 0.2}\n             Z\n            `);\n        return p;\n       }\n       if (shape === \"database\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 0.7);\n        body.setAttribute(\"y\", -size * 0.6);\n        body.setAttribute(\"width\", size * 1.4);\n        body.setAttribute(\"height\", size * 1.4);\n        g.appendChild(body);\n        const top = document.createElementNS(ns, \"ellipse\");\n        top.setAttribute(\"cx\", 0);\n        top.setAttribute(\"cy\", -size * 0.6);\n        top.setAttribute(\"rx\", size * 0.7);\n        top.setAttribute(\"ry\", size * 0.25);\n        g.appendChild(top);\n        const bottom = document.createElementNS(ns, \"ellipse\");\n        bottom.setAttribute(\"cx\", 0);\n        bottom.setAttribute(\"cy\", size * 0.8);\n        bottom.setAttribute(\"rx\", size * 0.7);\n        bottom.setAttribute(\"ry\", size * 0.25);\n        g.appendChild(bottom);\n        const mid1 = document.createElementNS(ns, \"ellipse\");\n        mid1.setAttribute(\"cx\", 0);\n        mid1.setAttribute(\"cy\", -size * 0.15);\n        mid1.setAttribute(\"rx\", size * 0.7);\n        mid1.setAttribute(\"ry\", size * 0.2);\n        mid1.style.fill = \"none\";\n        mid1.style.stroke = \"#475569\";\n        mid1.style.strokeWidth = \"2\";\n        g.appendChild(mid1);\n        const mid2 = document.createElementNS(ns, \"ellipse\");\n        mid2.setAttribute(\"cx\", 0);\n        mid2.setAttribute(\"cy\", size * 0.35);\n        mid2.setAttribute(\"rx\", size * 0.7);\n        mid2.setAttribute(\"ry\", size * 0.2);\n        mid2.style.fill = \"none\";\n        mid2.style.stroke = \"#475569\";\n        mid2.style.strokeWidth = \"2\";\n        g.appendChild(mid2);\n        return g;\n       }\n       if (shape === \"printer\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size);\n        body.setAttribute(\"y\", -size * 0.4);\n        body.setAttribute(\"width\", size * 2);\n        body.setAttribute(\"height\", size * 0.9);\n        body.setAttribute(\"rx\", 4);\n        g.appendChild(body);\n        const trayTop = document.createElementNS(ns, \"rect\");\n        trayTop.setAttribute(\"x\", -size * 0.7);\n        trayTop.setAttribute(\"y\", -size * 0.8);\n        trayTop.setAttribute(\"width\", size * 1.4);\n        trayTop.setAttribute(\"height\", size * 0.4);\n        trayTop.setAttribute(\"rx\", 2);\n        trayTop.style.fill = \"#1e293b\";\n        g.appendChild(trayTop);\n        const trayOut = document.createElementNS(ns, \"rect\");\n        trayOut.setAttribute(\"x\", -size * 0.6);\n        trayOut.setAttribute(\"y\", size * 0.5);\n        trayOut.setAttribute(\"width\", size * 1.2);\n        trayOut.setAttribute(\"height\", size * 0.35);\n        trayOut.setAttribute(\"rx\", 2);\n        g.appendChild(trayOut);\n        const paper = document.createElementNS(ns, \"rect\");\n        paper.setAttribute(\"x\", -size * 0.5);\n        paper.setAttribute(\"y\", size * 0.3);\n        paper.setAttribute(\"width\", size * 1);\n        paper.setAttribute(\"height\", size * 0.5);\n        paper.style.fill = \"#e2e8f0\";\n        g.appendChild(paper);\n        return g;\n       }\n       if (shape === \"access-point\" || shape === \"wifi\") {\n        const g = document.createElementNS(ns, \"g\");\n        const base = document.createElementNS(ns, \"rect\");\n        base.setAttribute(\"x\", -size * 0.3);\n        base.setAttribute(\"y\", size * 0.2);\n        base.setAttribute(\"width\", size * 0.6);\n        base.setAttribute(\"height\", size * 0.3);\n        base.setAttribute(\"rx\", 3);\n        g.appendChild(base);\n        for (let i = 1; i <= 3; i++) {\n         const arc = document.createElementNS(ns, \"path\");\n         const r = size * 0.3 * i;\n         arc.setAttribute(\"d\", `M ${-r * 0.7} ${size * 0.1 - r * 0.5} A ${r} ${r} 0 0 1 ${r * 0.7} ${size * 0.1 - r * 0.5}`);\n         arc.style.fill = \"none\";\n         arc.style.stroke = \"currentColor\";\n         arc.style.strokeWidth = \"2\";\n         arc.style.opacity = 1 - (i * 0.2);\n         g.appendChild(arc);\n        }\n        return g;\n       }\n       if (shape === \"load-balancer\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size);\n        body.setAttribute(\"y\", -size * 0.5);\n        body.setAttribute(\"width\", size * 2);\n        body.setAttribute(\"height\", size);\n        body.setAttribute(\"rx\", 6);\n        g.appendChild(body);\n        const bar = document.createElementNS(ns, \"line\");\n        bar.setAttribute(\"x1\", -size * 0.6);\n        bar.setAttribute(\"y1\", 0);\n        bar.setAttribute(\"x2\", size * 0.6);\n        bar.setAttribute(\"y2\", 0);\n        bar.style.stroke = \"#4ade80\";\n        bar.style.strokeWidth = \"3\";\n        g.appendChild(bar);\n        [-1, 1].forEach(dir => {\n         const arrow = document.createElementNS(ns, \"path\");\n         arrow.setAttribute(\"d\", `M ${dir * size * 0.3} ${-size * 0.2} L ${dir * size * 0.6} 0 L ${dir * size * 0.3} ${size * 0.2}`);\n         arrow.style.fill = \"none\";\n         arrow.style.stroke = \"#4ade80\";\n         arrow.style.strokeWidth = \"2\";\n         g.appendChild(arrow);\n        });\n        return g;\n       }\n       if (shape === \"nas\" || shape === \"storage\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 0.8);\n        body.setAttribute(\"y\", -size * 0.9);\n        body.setAttribute(\"width\", size * 1.6);\n        body.setAttribute(\"height\", size * 1.8);\n        body.setAttribute(\"rx\", 4);\n        g.appendChild(body);\n        for (let i = 0; i < 4; i++) {\n         const bay = document.createElementNS(ns, \"rect\");\n         bay.setAttribute(\"x\", -size * 0.6);\n         bay.setAttribute(\"y\", -size * 0.7 + i * size * 0.4);\n         bay.setAttribute(\"width\", size * 1.2);\n         bay.setAttribute(\"height\", size * 0.3);\n         bay.setAttribute(\"rx\", 2);\n         bay.style.fill = \"#1e293b\";\n         g.appendChild(bay);\n        }\n        return g;\n       }\n       if (shape === \"gateway\" || shape === \"modem\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 0.5);\n        body.setAttribute(\"y\", -size * 0.9);\n        body.setAttribute(\"width\", size);\n        body.setAttribute(\"height\", size * 1.8);\n        body.setAttribute(\"rx\", 4);\n        g.appendChild(body);\n        for (let i = 0; i < 4; i++) {\n         const led = document.createElementNS(ns, \"circle\");\n         led.setAttribute(\"cx\", 0);\n         led.setAttribute(\"cy\", -size * 0.6 + i * size * 0.35);\n         led.setAttribute(\"r\", size * 0.08);\n         led.style.fill = [\"#4ade80\", \"#4ade80\", \"#facc15\", \"#60a5fa\"][i];\n         g.appendChild(led);\n        }\n        return g;\n       }\n       if (shape === \"vpn\" || shape === \"tunnel\") {\n        const g = document.createElementNS(ns, \"g\");\n        const outer = document.createElementNS(ns, \"ellipse\");\n        outer.setAttribute(\"cx\", 0);\n        outer.setAttribute(\"cy\", 0);\n        outer.setAttribute(\"rx\", size);\n        outer.setAttribute(\"ry\", size * 0.6);\n        g.appendChild(outer);\n        const inner = document.createElementNS(ns, \"ellipse\");\n        inner.setAttribute(\"cx\", 0);\n        inner.setAttribute(\"cy\", 0);\n        inner.setAttribute(\"rx\", size * 0.5);\n        inner.setAttribute(\"ry\", size * 0.3);\n        inner.style.fill = \"#1e293b\";\n        g.appendChild(inner);\n        const lock = document.createElementNS(ns, \"rect\");\n        lock.setAttribute(\"x\", -size * 0.15);\n        lock.setAttribute(\"y\", -size * 0.1);\n        lock.setAttribute(\"width\", size * 0.3);\n        lock.setAttribute(\"height\", size * 0.25);\n        lock.setAttribute(\"rx\", 2);\n        lock.style.fill = \"#4ade80\";\n        g.appendChild(lock);\n        return g;\n       }\n       if (shape === \"container\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size);\n        body.setAttribute(\"y\", -size * 0.5);\n        body.setAttribute(\"width\", size * 2);\n        body.setAttribute(\"height\", size);\n        body.setAttribute(\"rx\", 4);\n        g.appendChild(body);\n        for (let i = 0; i < 3; i++) {\n         const block = document.createElementNS(ns, \"rect\");\n         block.setAttribute(\"x\", -size * 0.8 + i * size * 0.55);\n         block.setAttribute(\"y\", -size * 0.3);\n         block.setAttribute(\"width\", size * 0.45);\n         block.setAttribute(\"height\", size * 0.6);\n         block.setAttribute(\"rx\", 2);\n         block.style.fill = \"#1e293b\";\n         g.appendChild(block);\n        }\n        return g;\n       }\n       if (shape === \"vm\" || shape === \"virtual\") {\n        const g = document.createElementNS(ns, \"g\");\n        const shadow = document.createElementNS(ns, \"rect\");\n        shadow.setAttribute(\"x\", -size * 0.85 + 4);\n        shadow.setAttribute(\"y\", -size * 0.65 + 4);\n        shadow.setAttribute(\"width\", size * 1.7);\n        shadow.setAttribute(\"height\", size * 1.3);\n        shadow.setAttribute(\"rx\", 4);\n        shadow.style.opacity = \"0.3\";\n        g.appendChild(shadow);\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 0.85);\n        body.setAttribute(\"y\", -size * 0.65);\n        body.setAttribute(\"width\", size * 1.7);\n        body.setAttribute(\"height\", size * 1.3);\n        body.setAttribute(\"rx\", 4);\n        g.appendChild(body);\n        const screen = document.createElementNS(ns, \"rect\");\n        screen.setAttribute(\"x\", -size * 0.65);\n        screen.setAttribute(\"y\", -size * 0.45);\n        screen.setAttribute(\"width\", size * 1.3);\n        screen.setAttribute(\"height\", size * 0.7);\n        screen.setAttribute(\"rx\", 2);\n        screen.style.fill = \"#1e293b\";\n        g.appendChild(screen);\n        return g;\n       }\n       if (shape === \"kubernetes\" || shape === \"k8s\") {\n        const g = document.createElementNS(ns, \"g\");\n        const outer = document.createElementNS(ns, \"circle\");\n        outer.setAttribute(\"cx\", 0);\n        outer.setAttribute(\"cy\", 0);\n        outer.setAttribute(\"r\", size);\n        g.appendChild(outer);\n        const inner = document.createElementNS(ns, \"circle\");\n        inner.setAttribute(\"cx\", 0);\n        inner.setAttribute(\"cy\", 0);\n        inner.setAttribute(\"r\", size * 0.4);\n        inner.style.fill = \"#1e293b\";\n        g.appendChild(inner);\n        for (let i = 0; i < 7; i++) {\n         const angle = (Math.PI * 2 / 7) * i - Math.PI / 2;\n         const spoke = document.createElementNS(ns, \"line\");\n         spoke.setAttribute(\"x1\", Math.cos(angle) * size * 0.4);\n         spoke.setAttribute(\"y1\", Math.sin(angle) * size * 0.4);\n         spoke.setAttribute(\"x2\", Math.cos(angle) * size * 0.85);\n         spoke.setAttribute(\"y2\", Math.sin(angle) * size * 0.85);\n         spoke.style.stroke = \"#326ce5\";\n         spoke.style.strokeWidth = \"3\";\n         g.appendChild(spoke);\n        }\n        return g;\n       }\n       if (shape === \"shield\" || shape === \"security\") {\n        const p = document.createElementNS(ns, \"path\");\n        const s = size;\n        p.setAttribute(\"d\", `M 0 ${-s} L ${s * 0.85} ${-s * 0.5} L ${s * 0.85} ${s * 0.2} Q ${s * 0.7} ${s * 0.9} 0 ${s} Q ${-s * 0.7} ${s * 0.9} ${-s * 0.85} ${s * 0.2} L ${-s * 0.85} ${-s * 0.5} Z`);\n        return p;\n       }\n       if (shape === \"camera\" || shape === \"cctv\") {\n        const g = document.createElementNS(ns, \"g\");\n        const mount = document.createElementNS(ns, \"rect\");\n        mount.setAttribute(\"x\", -size * 1.1);\n        mount.setAttribute(\"y\", -size * 0.5);\n        mount.setAttribute(\"width\", size * 0.25);\n        mount.setAttribute(\"height\", size * 0.6);\n        mount.setAttribute(\"rx\", 2);\n        g.appendChild(mount);\n        const arm = document.createElementNS(ns, \"rect\");\n        arm.setAttribute(\"x\", -size * 0.9);\n        arm.setAttribute(\"y\", -size * 0.15);\n        arm.setAttribute(\"width\", size * 0.5);\n        arm.setAttribute(\"height\", size * 0.15);\n        g.appendChild(arm);\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 0.45);\n        body.setAttribute(\"y\", -size * 0.35);\n        body.setAttribute(\"width\", size * 1.1);\n        body.setAttribute(\"height\", size * 0.7);\n        body.setAttribute(\"rx\", 4);\n        g.appendChild(body);\n        const lensHousing = document.createElementNS(ns, \"circle\");\n        lensHousing.setAttribute(\"cx\", size * 0.85);\n        lensHousing.setAttribute(\"cy\", 0);\n        lensHousing.setAttribute(\"r\", size * 0.4);\n        g.appendChild(lensHousing);\n        const lensOuter = document.createElementNS(ns, \"circle\");\n        lensOuter.setAttribute(\"cx\", size * 0.85);\n        lensOuter.setAttribute(\"cy\", 0);\n        lensOuter.setAttribute(\"r\", size * 0.28);\n        lensOuter.style.fill = \"#1e293b\";\n        g.appendChild(lensOuter);\n        const lensInner = document.createElementNS(ns, \"circle\");\n        lensInner.setAttribute(\"cx\", size * 0.85);\n        lensInner.setAttribute(\"cy\", 0);\n        lensInner.setAttribute(\"r\", size * 0.15);\n        lensInner.style.fill = \"#3b82f6\";\n        g.appendChild(lensInner);\n        const reflection = document.createElementNS(ns, \"circle\");\n        reflection.setAttribute(\"cx\", size * 0.8);\n        reflection.setAttribute(\"cy\", -size * 0.05);\n        reflection.setAttribute(\"r\", size * 0.05);\n        reflection.style.fill = \"#93c5fd\";\n        g.appendChild(reflection);\n        const led = document.createElementNS(ns, \"circle\");\n        led.setAttribute(\"cx\", -size * 0.2);\n        led.setAttribute(\"cy\", -size * 0.15);\n        led.setAttribute(\"r\", size * 0.06);\n        led.style.fill = \"#ef4444\";\n        g.appendChild(led);\n        return g;\n       }\n       if (shape === \"monitor\" || shape === \"dashboard\") {\n        const g = document.createElementNS(ns, \"g\");\n        const screen = document.createElementNS(ns, \"rect\");\n        screen.setAttribute(\"x\", -size);\n        screen.setAttribute(\"y\", -size * 0.7);\n        screen.setAttribute(\"width\", size * 2);\n        screen.setAttribute(\"height\", size * 1.2);\n        screen.setAttribute(\"rx\", 4);\n        g.appendChild(screen);\n        const graph = document.createElementNS(ns, \"polyline\");\n        graph.setAttribute(\"points\", `${-size * 0.8},${size * 0.2} ${-size * 0.3},${-size * 0.2} ${size * 0.2},${size * 0.1} ${size * 0.7},${-size * 0.3}`);\n        graph.style.fill = \"none\";\n        graph.style.stroke = \"#4ade80\";\n        graph.style.strokeWidth = \"3\";\n        g.appendChild(graph);\n        return g;\n       }\n       if (shape === \"docker\" || shape === \"whale\") {\n        const g = document.createElementNS(ns, \"g\");\n        const s = size;\n        const body = document.createElementNS(ns, \"path\");\n        body.setAttribute(\"d\", `\n         M ${-s * 0.9} ${s * 0.2}\n         Q ${-s * 1.1} ${s * 0.5} ${-s * 0.7} ${s * 0.7}\n         Q ${-s * 0.3} ${s * 0.85} ${s * 0.3} ${s * 0.75}\n         Q ${s * 0.8} ${s * 0.6} ${s * 1.0} ${s * 0.3}\n         Q ${s * 1.1} ${s * 0.1} ${s * 1.0} ${-s * 0.1}\n         L ${s * 0.85} ${-s * 0.1}\n         Q ${s * 0.7} ${-s * 0.15} ${s * 0.4} ${-s * 0.2}\n         L ${-s * 0.5} ${-s * 0.2}\n         Q ${-s * 0.8} ${-s * 0.1} ${-s * 0.9} ${s * 0.2}\n         Z\n        `);\n        body.style.fill = \"#0db7ed\";\n        g.appendChild(body);\n        const tail = document.createElementNS(ns, \"path\");\n        tail.setAttribute(\"d\", `\n         M ${-s * 0.85} ${s * 0.1}\n         Q ${-s * 1.2} ${-s * 0.2} ${-s * 1.0} ${-s * 0.55}\n         Q ${-s * 0.95} ${-s * 0.35} ${-s * 0.8} ${-s * 0.15}\n         Z\n        `);\n        tail.style.fill = \"#0db7ed\";\n        g.appendChild(tail);\n        const belly = document.createElementNS(ns, \"path\");\n        belly.setAttribute(\"d\", `\n         M ${-s * 0.5} ${s * 0.65}\n         Q ${s * 0.1} ${s * 0.75} ${s * 0.6} ${s * 0.55}\n         Q ${s * 0.8} ${s * 0.45} ${s * 0.9} ${s * 0.25}\n         Q ${s * 0.7} ${s * 0.5} ${s * 0.3} ${s * 0.6}\n         Q ${-s * 0.1} ${s * 0.7} ${-s * 0.5} ${s * 0.65}\n         Z\n        `);\n        belly.style.fill = \"#ffffff\";\n        belly.style.opacity = \"0.3\";\n        g.appendChild(belly);\n        for (let row = 0; row < 2; row++) {\n         for (let col = 0; col < 3; col++) {\n          const container = document.createElementNS(ns, \"rect\");\n          container.setAttribute(\"x\", -s * 0.45 + col * s * 0.35);\n          container.setAttribute(\"y\", -s * 0.7 + row * s * 0.28);\n          container.setAttribute(\"width\", s * 0.3);\n          container.setAttribute(\"height\", s * 0.23);\n          container.setAttribute(\"rx\", 2);\n          container.style.fill = \"#0db7ed\";\n          container.style.stroke = \"#0a9ed8\";\n          container.style.strokeWidth = \"1.5\";\n          g.appendChild(container);\n         }\n        }\n        const topContainer = document.createElementNS(ns, \"rect\");\n        topContainer.setAttribute(\"x\", -s * 0.1);\n        topContainer.setAttribute(\"y\", -s * 0.95);\n        topContainer.setAttribute(\"width\", s * 0.3);\n        topContainer.setAttribute(\"height\", s * 0.23);\n        topContainer.setAttribute(\"rx\", 2);\n        topContainer.style.fill = \"#0db7ed\";\n        topContainer.style.stroke = \"#0a9ed8\";\n        topContainer.style.strokeWidth = \"1.5\";\n        g.appendChild(topContainer);\n        const eye = document.createElementNS(ns, \"circle\");\n        eye.setAttribute(\"cx\", s * 0.65);\n        eye.setAttribute(\"cy\", s * 0.25);\n        eye.setAttribute(\"r\", s * 0.08);\n        eye.style.fill = \"#0a5f7a\";\n        g.appendChild(eye);\n        return g;\n       }\n       if (shape === \"rounded-square\") {\n        const r = document.createElementNS(ns, \"rect\");\n        const s = size * 1.6;\n        r.setAttribute(\"x\", -s / 2);\n        r.setAttribute(\"y\", -s / 2);\n        r.setAttribute(\"width\", s);\n        r.setAttribute(\"height\", s);\n        r.setAttribute(\"rx\", size * 0.4);\n        return r;\n       }\n       if (shape === \"pill\" || shape === \"capsule\") {\n        const r = document.createElementNS(ns, \"rect\");\n        r.setAttribute(\"x\", -size * 1.2);\n        r.setAttribute(\"y\", -size * 0.5);\n        r.setAttribute(\"width\", size * 2.4);\n        r.setAttribute(\"height\", size);\n        r.setAttribute(\"rx\", size * 0.5);\n        return r;\n       }\n       if (shape === \"octagon\") {\n        const p = document.createElementNS(ns, \"polygon\");\n        const r = size;\n        const pts = [];\n        for (let i = 0; i < 8; i++) {\n         const a = (Math.PI / 4) * i - Math.PI / 8;\n         pts.push([Math.cos(a) * r, Math.sin(a) * r]);\n        }\n        p.setAttribute(\"points\", pts.map(pt => pt.join(\",\")).join(\" \"));\n        return p;\n       }\n       if (shape === \"pentagon\") {\n        const p = document.createElementNS(ns, \"polygon\");\n        const pts = [];\n        for (let i = 0; i < 5; i++) {\n         const a = (Math.PI * 2 / 5) * i - Math.PI / 2;\n         pts.push([Math.cos(a) * size, Math.sin(a) * size]);\n        }\n        p.setAttribute(\"points\", pts.map(pt => pt.join(\",\")).join(\" \"));\n        return p;\n       }\n       if (shape === \"cross\" || shape === \"plus\") {\n        const p = document.createElementNS(ns, \"polygon\");\n        const s = size;\n        const t = size * 0.35;\n        p.setAttribute(\"points\", `${-t},${-s} ${t},${-s} ${t},${-t} ${s},${-t} ${s},${t} ${t},${t} ${t},${s} ${-t},${s} ${-t},${t} ${-s},${t} ${-s},${-t} ${-t},${-t}`);\n        return p;\n       }\n       if (shape === \"parallelogram\") {\n        const p = document.createElementNS(ns, \"polygon\");\n        const s = size;\n        p.setAttribute(\"points\", `${-s * 0.6},${-s * 0.6} ${s * 1.2},${-s * 0.6} ${s * 0.6},${s * 0.6} ${-s * 1.2},${s * 0.6}`);\n        return p;\n       }\n       if (shape === \"trapezoid\") {\n        const p = document.createElementNS(ns, \"polygon\");\n        const s = size;\n        p.setAttribute(\"points\", `${-s * 0.6},${-s * 0.6} ${s * 0.6},${-s * 0.6} ${s},${s * 0.6} ${-s},${s * 0.6}`);\n        return p;\n       }\n       if (shape === \"sensor\" || shape === \"iot\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"circle\");\n        body.setAttribute(\"cx\", 0);\n        body.setAttribute(\"cy\", 0);\n        body.setAttribute(\"r\", size * 0.7);\n        g.appendChild(body);\n        const ant = document.createElementNS(ns, \"line\");\n        ant.setAttribute(\"x1\", 0);\n        ant.setAttribute(\"y1\", -size * 0.7);\n        ant.setAttribute(\"x2\", 0);\n        ant.setAttribute(\"y2\", -size);\n        ant.style.stroke = \"currentColor\";\n        ant.style.strokeWidth = \"2\";\n        g.appendChild(ant);\n        const tip = document.createElementNS(ns, \"circle\");\n        tip.setAttribute(\"cx\", 0);\n        tip.setAttribute(\"cy\", -size);\n        tip.setAttribute(\"r\", size * 0.1);\n        tip.style.fill = \"#4ade80\";\n        g.appendChild(tip);\n        return g;\n       }\n       if (shape === \"pi\" || shape === \"sbc\" || shape === \"raspberry\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size);\n        body.setAttribute(\"y\", -size * 0.65);\n        body.setAttribute(\"width\", size * 2);\n        body.setAttribute(\"height\", size * 1.3);\n        body.setAttribute(\"rx\", 4);\n        g.appendChild(body);\n        for (let i = 0; i < 10; i++) {\n         const pin = document.createElementNS(ns, \"rect\");\n         pin.setAttribute(\"x\", -size * 0.85 + i * size * 0.19);\n         pin.setAttribute(\"y\", -size * 0.8);\n         pin.setAttribute(\"width\", size * 0.08);\n         pin.setAttribute(\"height\", size * 0.15);\n         pin.style.fill = \"#facc15\";\n         g.appendChild(pin);\n        }\n        const port = document.createElementNS(ns, \"rect\");\n        port.setAttribute(\"x\", size * 0.6);\n        port.setAttribute(\"y\", -size * 0.2);\n        port.setAttribute(\"width\", size * 0.35);\n        port.setAttribute(\"height\", size * 0.4);\n        port.style.fill = \"#1e293b\";\n        g.appendChild(port);\n        return g;\n       }\n       if (shape === \"api\" || shape === \"endpoint\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 0.9);\n        body.setAttribute(\"y\", -size * 0.7);\n        body.setAttribute(\"width\", size * 1.8);\n        body.setAttribute(\"height\", size * 1.4);\n        body.setAttribute(\"rx\", 6);\n        g.appendChild(body);\n        const left = document.createElementNS(ns, \"text\");\n        left.setAttribute(\"x\", -size * 0.5);\n        left.setAttribute(\"y\", size * 0.15);\n        left.setAttribute(\"font-size\", size * 0.9);\n        left.setAttribute(\"fill\", \"#4ade80\");\n        left.setAttribute(\"font-family\", \"monospace\");\n        left.textContent = \"{\";\n        g.appendChild(left);\n        const right = document.createElementNS(ns, \"text\");\n        right.setAttribute(\"x\", size * 0.15);\n        right.setAttribute(\"y\", size * 0.15);\n        right.setAttribute(\"font-size\", size * 0.9);\n        right.setAttribute(\"fill\", \"#4ade80\");\n        right.setAttribute(\"font-family\", \"monospace\");\n        right.textContent = \"}\";\n        g.appendChild(right);\n        return g;\n       }\n       if (shape === \"queue\" || shape === \"message\") {\n        const g = document.createElementNS(ns, \"g\");\n        for (let i = 2; i >= 0; i--) {\n         const card = document.createElementNS(ns, \"rect\");\n         card.setAttribute(\"x\", -size * 0.7 + i * 4);\n         card.setAttribute(\"y\", -size * 0.5 + i * 4);\n         card.setAttribute(\"width\", size * 1.4);\n         card.setAttribute(\"height\", size * 0.8);\n         card.setAttribute(\"rx\", 3);\n         card.style.opacity = 1 - i * 0.25;\n         g.appendChild(card);\n        }\n        return g;\n       }\n       if (shape === \"lambda\" || shape === \"function\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 0.8);\n        body.setAttribute(\"y\", -size * 0.8);\n        body.setAttribute(\"width\", size * 1.6);\n        body.setAttribute(\"height\", size * 1.6);\n        body.setAttribute(\"rx\", 4);\n        g.appendChild(body);\n        const lambda = document.createElementNS(ns, \"text\");\n        lambda.setAttribute(\"x\", 0);\n        lambda.setAttribute(\"y\", size * 0.2);\n        lambda.setAttribute(\"font-size\", size * 1.2);\n        lambda.setAttribute(\"fill\", \"#f59e0b\");\n        lambda.setAttribute(\"text-anchor\", \"middle\");\n        lambda.setAttribute(\"font-family\", \"serif\");\n        lambda.textContent = \"λ\";\n        g.appendChild(lambda);\n        return g;\n       }\n       if (shape === \"bucket\" || shape === \"s3\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"path\");\n        body.setAttribute(\"d\", `M ${-size * 0.8} ${-size * 0.7} L ${-size * 0.6} ${size * 0.8} Q ${-size * 0.5} ${size} 0 ${size} Q ${size * 0.5} ${size} ${size * 0.6} ${size * 0.8} L ${size * 0.8} ${-size * 0.7} Z`);\n        g.appendChild(body);\n        const rim = document.createElementNS(ns, \"ellipse\");\n        rim.setAttribute(\"cx\", 0);\n        rim.setAttribute(\"cy\", -size * 0.7);\n        rim.setAttribute(\"rx\", size * 0.8);\n        rim.setAttribute(\"ry\", size * 0.25);\n        g.appendChild(rim);\n        return g;\n       }\n\n       if (shape === \"thermostat\") {\n        const g = document.createElementNS(ns, \"g\");\n        const outer = document.createElementNS(ns, \"circle\");\n        outer.setAttribute(\"r\", size);\n        g.appendChild(outer);\n        const inner = document.createElementNS(ns, \"circle\");\n        inner.setAttribute(\"r\", size * 0.75);\n        inner.style.fill = \"#1e293b\";\n        g.appendChild(inner);\n        const temp = document.createElementNS(ns, \"text\");\n        temp.setAttribute(\"x\", 0);\n        temp.setAttribute(\"y\", size * 0.15);\n        temp.setAttribute(\"font-size\", size * 0.5);\n        temp.setAttribute(\"fill\", \"#4ade80\");\n        temp.setAttribute(\"text-anchor\", \"middle\");\n        temp.setAttribute(\"font-family\", \"monospace\");\n        temp.textContent = \"72°\";\n        g.appendChild(temp);\n        return g;\n       }\n       if (shape === \"doorbell\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 0.5);\n        body.setAttribute(\"y\", -size);\n        body.setAttribute(\"width\", size);\n        body.setAttribute(\"height\", size * 2);\n        body.setAttribute(\"rx\", size * 0.25);\n        g.appendChild(body);\n        const lens = document.createElementNS(ns, \"circle\");\n        lens.setAttribute(\"cx\", 0);\n        lens.setAttribute(\"cy\", -size * 0.35);\n        lens.setAttribute(\"r\", size * 0.3);\n        lens.style.fill = \"#1e293b\";\n        g.appendChild(lens);\n        const lensDot = document.createElementNS(ns, \"circle\");\n        lensDot.setAttribute(\"cx\", 0);\n        lensDot.setAttribute(\"cy\", -size * 0.35);\n        lensDot.setAttribute(\"r\", size * 0.12);\n        lensDot.style.fill = \"#3b82f6\";\n        g.appendChild(lensDot);\n        const btn = document.createElementNS(ns, \"circle\");\n        btn.setAttribute(\"cx\", 0);\n        btn.setAttribute(\"cy\", size * 0.5);\n        btn.setAttribute(\"r\", size * 0.2);\n        btn.style.fill = \"#f59e0b\";\n        g.appendChild(btn);\n        return g;\n       }\n       if (shape === \"smart-lock\") {\n        const g = document.createElementNS(ns, \"g\");\n        const shackle = document.createElementNS(ns, \"path\");\n        shackle.setAttribute(\"d\", `M ${-size * 0.4} ${-size * 0.1} L ${-size * 0.4} ${-size * 0.6} A ${size * 0.4} ${size * 0.4} 0 1 1 ${size * 0.4} ${-size * 0.6} L ${size * 0.4} ${-size * 0.1}`);\n        shackle.style.fill = \"none\";\n        shackle.style.strokeWidth = size * 0.2;\n        g.appendChild(shackle);\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 0.6);\n        body.setAttribute(\"y\", -size * 0.15);\n        body.setAttribute(\"width\", size * 1.2);\n        body.setAttribute(\"height\", size * 1);\n        body.setAttribute(\"rx\", 4);\n        g.appendChild(body);\n        const keyhole = document.createElementNS(ns, \"circle\");\n        keyhole.setAttribute(\"cx\", 0);\n        keyhole.setAttribute(\"cy\", size * 0.3);\n        keyhole.setAttribute(\"r\", size * 0.15);\n        keyhole.style.fill = \"#4ade80\";\n        g.appendChild(keyhole);\n        return g;\n       }\n       if (shape === \"smart-bulb\") {\n        const g = document.createElementNS(ns, \"g\");\n        const bulb = document.createElementNS(ns, \"path\");\n        bulb.setAttribute(\"d\", `M ${-size * 0.5} ${size * 0.2} Q ${-size * 0.8} ${-size * 0.3} ${-size * 0.5} ${-size * 0.7} Q 0 ${-size * 1.1} ${size * 0.5} ${-size * 0.7} Q ${size * 0.8} ${-size * 0.3} ${size * 0.5} ${size * 0.2} Z`);\n        g.appendChild(bulb);\n        const base1 = document.createElementNS(ns, \"rect\");\n        base1.setAttribute(\"x\", -size * 0.35);\n        base1.setAttribute(\"y\", size * 0.2);\n        base1.setAttribute(\"width\", size * 0.7);\n        base1.setAttribute(\"height\", size * 0.15);\n        base1.style.fill = \"#94a3b8\";\n        g.appendChild(base1);\n        const base2 = document.createElementNS(ns, \"rect\");\n        base2.setAttribute(\"x\", -size * 0.3);\n        base2.setAttribute(\"y\", size * 0.35);\n        base2.setAttribute(\"width\", size * 0.6);\n        base2.setAttribute(\"height\", size * 0.15);\n        base2.style.fill = \"#64748b\";\n        g.appendChild(base2);\n        const base3 = document.createElementNS(ns, \"rect\");\n        base3.setAttribute(\"x\", -size * 0.25);\n        base3.setAttribute(\"y\", size * 0.5);\n        base3.setAttribute(\"width\", size * 0.5);\n        base3.setAttribute(\"height\", size * 0.2);\n        base3.setAttribute(\"rx\", 2);\n        base3.style.fill = \"#475569\";\n        g.appendChild(base3);\n        return g;\n       }\n       if (shape === \"smart-plug\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 0.7);\n        body.setAttribute(\"y\", -size * 0.6);\n        body.setAttribute(\"width\", size * 1.4);\n        body.setAttribute(\"height\", size * 1.2);\n        body.setAttribute(\"rx\", 6);\n        g.appendChild(body);\n        const hole1 = document.createElementNS(ns, \"rect\");\n        hole1.setAttribute(\"x\", -size * 0.35);\n        hole1.setAttribute(\"y\", -size * 0.3);\n        hole1.setAttribute(\"width\", size * 0.15);\n        hole1.setAttribute(\"height\", size * 0.4);\n        hole1.setAttribute(\"rx\", 2);\n        hole1.style.fill = \"#1e293b\";\n        g.appendChild(hole1);\n        const hole2 = document.createElementNS(ns, \"rect\");\n        hole2.setAttribute(\"x\", size * 0.2);\n        hole2.setAttribute(\"y\", -size * 0.3);\n        hole2.setAttribute(\"width\", size * 0.15);\n        hole2.setAttribute(\"height\", size * 0.4);\n        hole2.setAttribute(\"rx\", 2);\n        hole2.style.fill = \"#1e293b\";\n        g.appendChild(hole2);\n        const led = document.createElementNS(ns, \"circle\");\n        led.setAttribute(\"cx\", 0);\n        led.setAttribute(\"cy\", size * 0.35);\n        led.setAttribute(\"r\", size * 0.1);\n        led.style.fill = \"#4ade80\";\n        g.appendChild(led);\n        return g;\n       }\n       if (shape === \"smart-speaker\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 0.6);\n        body.setAttribute(\"y\", -size);\n        body.setAttribute(\"width\", size * 1.2);\n        body.setAttribute(\"height\", size * 2);\n        body.setAttribute(\"rx\", size * 0.3);\n        g.appendChild(body);\n        const mesh = document.createElementNS(ns, \"rect\");\n        mesh.setAttribute(\"x\", -size * 0.5);\n        mesh.setAttribute(\"y\", -size * 0.3);\n        mesh.setAttribute(\"width\", size);\n        mesh.setAttribute(\"height\", size * 1.1);\n        mesh.setAttribute(\"rx\", 4);\n        mesh.style.fill = \"#1e293b\";\n        g.appendChild(mesh);\n        const ring = document.createElementNS(ns, \"circle\");\n        ring.setAttribute(\"cx\", 0);\n        ring.setAttribute(\"cy\", -size * 0.65);\n        ring.setAttribute(\"r\", size * 0.2);\n        ring.style.fill = \"#3b82f6\";\n        g.appendChild(ring);\n        return g;\n       }\n       if (shape === \"smart-tv\") {\n        const g = document.createElementNS(ns, \"g\");\n        const frame = document.createElementNS(ns, \"rect\");\n        frame.setAttribute(\"x\", -size * 1.4);\n        frame.setAttribute(\"y\", -size * 0.85);\n        frame.setAttribute(\"width\", size * 2.8);\n        frame.setAttribute(\"height\", size * 1.6);\n        frame.setAttribute(\"rx\", 4);\n        g.appendChild(frame);\n        const screen = document.createElementNS(ns, \"rect\");\n        screen.setAttribute(\"x\", -size * 1.3);\n        screen.setAttribute(\"y\", -size * 0.75);\n        screen.setAttribute(\"width\", size * 2.6);\n        screen.setAttribute(\"height\", size * 1.4);\n        screen.setAttribute(\"rx\", 2);\n        screen.style.fill = \"#1e293b\";\n        g.appendChild(screen);\n        const stand = document.createElementNS(ns, \"rect\");\n        stand.setAttribute(\"x\", -size * 0.8);\n        stand.setAttribute(\"y\", size * 0.75);\n        stand.setAttribute(\"width\", size * 1.6);\n        stand.setAttribute(\"height\", size * 0.12);\n        stand.setAttribute(\"rx\", 2);\n        g.appendChild(stand);\n        return g;\n       }\n       if (shape === \"hub\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 0.9);\n        body.setAttribute(\"y\", -size * 0.5);\n        body.setAttribute(\"width\", size * 1.8);\n        body.setAttribute(\"height\", size);\n        body.setAttribute(\"rx\", 8);\n        g.appendChild(body);\n        for (let i = 0; i < 4; i++) {\n         const led = document.createElementNS(ns, \"circle\");\n         led.setAttribute(\"cx\", -size * 0.5 + i * size * 0.35);\n         led.setAttribute(\"cy\", 0);\n         led.setAttribute(\"r\", size * 0.08);\n         led.style.fill = [\"#4ade80\", \"#3b82f6\", \"#f59e0b\", \"#ef4444\"][i];\n         g.appendChild(led);\n        }\n        return g;\n       }\n       if (shape === \"smoke-detector\") {\n        const g = document.createElementNS(ns, \"g\");\n        const outer = document.createElementNS(ns, \"circle\");\n        outer.setAttribute(\"r\", size);\n        g.appendChild(outer);\n        const inner = document.createElementNS(ns, \"circle\");\n        inner.setAttribute(\"r\", size * 0.6);\n        inner.style.fill = \"#1e293b\";\n        g.appendChild(inner);\n        for (let i = 0; i < 6; i++) {\n         const slot = document.createElementNS(ns, \"rect\");\n         const angle = (i * 60 - 90) * Math.PI / 180;\n         slot.setAttribute(\"x\", Math.cos(angle) * size * 0.35 - size * 0.08);\n         slot.setAttribute(\"y\", Math.sin(angle) * size * 0.35 - size * 0.03);\n         slot.setAttribute(\"width\", size * 0.16);\n         slot.setAttribute(\"height\", size * 0.06);\n         slot.setAttribute(\"rx\", 1);\n         slot.style.fill = \"#475569\";\n         slot.setAttribute(\"transform\", `rotate(${i * 60}, ${Math.cos(angle) * size * 0.35}, ${Math.sin(angle) * size * 0.35})`);\n         g.appendChild(slot);\n        }\n        const led = document.createElementNS(ns, \"circle\");\n        led.setAttribute(\"cx\", 0);\n        led.setAttribute(\"cy\", 0);\n        led.setAttribute(\"r\", size * 0.1);\n        led.style.fill = \"#ef4444\";\n        g.appendChild(led);\n        return g;\n       }\n       if (shape === \"motion-sensor\") {\n        const g = document.createElementNS(ns, \"g\");\n        const dome = document.createElementNS(ns, \"path\");\n        dome.setAttribute(\"d\", `M ${-size * 0.8} ${size * 0.3} Q ${-size * 0.8} ${-size * 0.8} 0 ${-size * 0.8} Q ${size * 0.8} ${-size * 0.8} ${size * 0.8} ${size * 0.3} Z`);\n        g.appendChild(dome);\n        const base = document.createElementNS(ns, \"rect\");\n        base.setAttribute(\"x\", -size * 0.8);\n        base.setAttribute(\"y\", size * 0.3);\n        base.setAttribute(\"width\", size * 1.6);\n        base.setAttribute(\"height\", size * 0.35);\n        base.setAttribute(\"rx\", 3);\n        g.appendChild(base);\n        const lens = document.createElementNS(ns, \"ellipse\");\n        lens.setAttribute(\"cx\", 0);\n        lens.setAttribute(\"cy\", -size * 0.15);\n        lens.setAttribute(\"rx\", size * 0.35);\n        lens.setAttribute(\"ry\", size * 0.25);\n        lens.style.fill = \"#1e293b\";\n        g.appendChild(lens);\n        const led = document.createElementNS(ns, \"circle\");\n        led.setAttribute(\"cx\", 0);\n        led.setAttribute(\"cy\", size * 0.45);\n        led.setAttribute(\"r\", size * 0.08);\n        led.style.fill = \"#4ade80\";\n        g.appendChild(led);\n        return g;\n       }\n       if (shape === \"garage\") {\n        const g = document.createElementNS(ns, \"g\");\n        const frame = document.createElementNS(ns, \"rect\");\n        frame.setAttribute(\"x\", -size);\n        frame.setAttribute(\"y\", -size * 0.9);\n        frame.setAttribute(\"width\", size * 2);\n        frame.setAttribute(\"height\", size * 1.8);\n        frame.setAttribute(\"rx\", 4);\n        g.appendChild(frame);\n        for (let i = 0; i < 4; i++) {\n         const panel = document.createElementNS(ns, \"rect\");\n         panel.setAttribute(\"x\", -size * 0.9);\n         panel.setAttribute(\"y\", -size * 0.8 + i * size * 0.42);\n         panel.setAttribute(\"width\", size * 1.8);\n         panel.setAttribute(\"height\", size * 0.35);\n         panel.setAttribute(\"rx\", 2);\n         panel.style.fill = \"#1e293b\";\n         g.appendChild(panel);\n        }\n        return g;\n       }\n       if (shape === \"sprinkler\") {\n        const g = document.createElementNS(ns, \"g\");\n        const head = document.createElementNS(ns, \"circle\");\n        head.setAttribute(\"r\", size * 0.5);\n        g.appendChild(head);\n        const nozzle = document.createElementNS(ns, \"rect\");\n        nozzle.setAttribute(\"x\", -size * 0.15);\n        nozzle.setAttribute(\"y\", size * 0.3);\n        nozzle.setAttribute(\"width\", size * 0.3);\n        nozzle.setAttribute(\"height\", size * 0.5);\n        g.appendChild(nozzle);\n        for (let i = 0; i < 5; i++) {\n         const spray = document.createElementNS(ns, \"line\");\n         const angle = (-60 + i * 30) * Math.PI / 180;\n         spray.setAttribute(\"x1\", 0);\n         spray.setAttribute(\"y1\", -size * 0.3);\n         spray.setAttribute(\"x2\", Math.cos(angle) * size * 0.8);\n         spray.setAttribute(\"y2\", Math.sin(angle) * size * 0.8 - size * 0.3);\n         spray.style.stroke = \"#3b82f6\";\n         spray.style.strokeWidth = \"2\";\n         spray.style.strokeDasharray = \"3,3\";\n         g.appendChild(spray);\n        }\n        return g;\n       }\n       if (shape === \"vacuum\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"circle\");\n        body.setAttribute(\"r\", size);\n        g.appendChild(body);\n        const top = document.createElementNS(ns, \"circle\");\n        top.setAttribute(\"r\", size * 0.7);\n        top.style.fill = \"#1e293b\";\n        g.appendChild(top);\n        const bumper = document.createElementNS(ns, \"path\");\n        bumper.setAttribute(\"d\", `M ${-size * 0.7} ${-size * 0.4} A ${size * 0.8} ${size * 0.8} 0 0 1 ${size * 0.7} ${-size * 0.4}`);\n        bumper.style.fill = \"none\";\n        bumper.style.strokeWidth = size * 0.15;\n        g.appendChild(bumper);\n        const btn = document.createElementNS(ns, \"circle\");\n        btn.setAttribute(\"cx\", 0);\n        btn.setAttribute(\"cy\", size * 0.1);\n        btn.setAttribute(\"r\", size * 0.2);\n        btn.style.fill = \"#4ade80\";\n        g.appendChild(btn);\n        return g;\n       }\n       const c = document.createElementNS(ns, \"circle\");\n       c.setAttribute(\"r\", size);\n       return c;\n      }\n      function createNodeShape(id, size) {\n       const shapeType = (NODE_DATA[id] && NODE_DATA[id].shape) || \"circle\";\n       const shapeEl = createShapeElement(shapeType, size);\n       shapeEl.classList.add(\"node-circle\");\n       const styles = resolveStylesForNode(id);\n       shapeEl.style.fill = styles.circleColor || PAGE_STATE.nodeFill || \"#1e293b\";\n       shapeEl.style.stroke = styles.circleBorder || PAGE_STATE.nodeStroke || \"#475569\";\n       return shapeEl;\n      }\n      function forgeTheLegend() {\n       const container = document.getElementById(\"edge-legend\");\n       if (!container) return;\n       container.innerHTML = \"\";\n       const title = document.createElement(\"div\");\n       title.className = \"legend-title\";\n       title.textContent = \"Line Legend\";\n       container.appendChild(title);\n       const closeBtn = document.createElement(\"button\");\n       closeBtn.type = \"button\";\n       closeBtn.className = \"legend-close-btn\";\n       closeBtn.textContent = \"✕\";\n       closeBtn.addEventListener(\"click\", (e) => {\n        e.stopPropagation();\n        legendCollapsed = true;\n        updateLegendVisibility();\n       });\n       container.appendChild(closeBtn);\n       const colors = [...new Set(EDGE_DATA.list.map((e) => e.color).filter(Boolean))];\n       if (colors.length === 0) {\n        updateLegendVisibility();\n        return;\n       }\n       colors.forEach((color) => {\n        if (!EDGE_LEGEND[color]) {\n         EDGE_LEGEND[color] = \"you can edit me too\";\n        }\n        const item = document.createElement(\"div\");\n        item.className = \"legend-item\";\n        item.addEventListener(\"mousedown\", (e) => e.stopPropagation());\n        item.addEventListener(\"click\", (e) => e.stopPropagation());\n        const swatch = document.createElement(\"span\");\n        swatch.className = \"legend-swatch\";\n        swatch.style.backgroundColor = color;\n        swatch.style.cursor = \"pointer\";\n        swatch.addEventListener(\"click\", (e) => {\n         e.stopPropagation();\n         const edgeWithColor = EDGE_DATA.list.find(edge => edge.color === color);\n         if (edgeWithColor) {\n          selectTheConnection(edgeWithColor.id);\n         }\n        });\n        let swatchTouchStart = null;\n        let swatchTouchMoved = false;\n        swatch.addEventListener(\"touchstart\", (e) => {\n         swatchTouchStart = Date.now();\n         swatchTouchMoved = false;\n         if (e.touches[0]) {\n          swatchTouchStartX = e.touches[0].clientX;\n          swatchTouchStartY = e.touches[0].clientY;\n         }\n        }, {\n         passive: false\n        });\n        let swatchTouchStartX = 0, swatchTouchStartY = 0;\n        swatch.addEventListener(\"touchmove\", (e) => {\n         if (e.touches[0]) {\n          const dx = Math.abs(e.touches[0].clientX - swatchTouchStartX);\n          const dy = Math.abs(e.touches[0].clientY - swatchTouchStartY);\n          if (dx > 10 || dy > 10) swatchTouchMoved = true;\n         }\n        }, {\n         passive: false\n        });\n        swatch.addEventListener(\"touchend\", (e) => {\n         if (swatchTouchStart && !swatchTouchMoved && Date.now() - swatchTouchStart < 400) {\n          e.stopPropagation();\n          e.preventDefault();\n          const edgeWithColor = EDGE_DATA.list.find(edge => edge.color === color);\n          if (edgeWithColor) {\n           selectTheConnection(edgeWithColor.id);\n          }\n         }\n         swatchTouchStart = null;\n         swatchTouchMoved = false;\n        }, {\n         passive: false\n        });\n        const label = document.createElement(\"span\");\n        label.className = \"legend-label\";\n        label.textContent = EDGE_LEGEND[color];\n        if (isMobileDevice()) {\n         label.style.cursor = \"pointer\";\n         let labelTapStart = null;\n         let labelTapMoved = false;\n         label.addEventListener(\"touchstart\", (e) => {\n          labelTapStart = Date.now();\n          labelTapMoved = false;\n          if (e.touches[0]) {\n           labelTapStartX = e.touches[0].clientX;\n           labelTapStartY = e.touches[0].clientY;\n          }\n          e.stopPropagation();\n         });\n         let labelTapStartX = 0, labelTapStartY = 0;\n         label.addEventListener(\"touchmove\", (e) => {\n          if (e.touches[0]) {\n           const dx = Math.abs(e.touches[0].clientX - labelTapStartX);\n           const dy = Math.abs(e.touches[0].clientY - labelTapStartY);\n           if (dx > 10 || dy > 10) labelTapMoved = true;\n          }\n         });\n         label.addEventListener(\"touchend\", (e) => {\n          if (labelTapStart && !labelTapMoved && Date.now() - labelTapStart < 400) {\n           e.preventDefault();\n           e.stopPropagation();\n           const currentText = label.textContent;\n           const newText = prompt(\"Edit legend label:\", currentText);\n           if (newText !== null && newText.trim()) {\n            label.textContent = newText.trim();\n            EDGE_LEGEND[color] = newText.trim();\n           }\n          }\n          labelTapStart = null;\n          labelTapMoved = false;\n         });\n        } else {\n         label.contentEditable = true;\n         label.addEventListener(\"focus\", () => {\n          label.classList.add(\"editing\");\n         });\n         label.addEventListener(\"blur\", () => {\n          label.classList.remove(\"editing\");\n          const text = label.textContent.trim() || \"you can edit me too\";\n          EDGE_LEGEND[color] = text;\n         });\n         label.addEventListener(\"keydown\", (e) => {\n          if (e.key === \"Enter\") {\n           e.preventDefault();\n           label.blur();\n          }\n         });\n        }\n        item.append(swatch, label);\n        container.appendChild(item);\n       });\n       updateLegendVisibility();\n      }\n       function deleteRectangle(rectId) {\n      pushUndo(\"delete zone\");\n        RECT_DATA.list = RECT_DATA.list.filter(r => r.id !== rectId);\n        forgeTheTopology();\n       }\n       function updateRectangleDeleteButtons() {\n        const deleteButtons = document.querySelectorAll('.rect-delete-btn');\n        deleteButtons.forEach(btn => {\n         btn.style.display = rectDrawMode ? 'block' : 'none';\n        });\n       }\nfunction updateFovCone(nodeId) {\n  const node = NODE_DATA[nodeId];\n  if (!node) return;\n  \n  const nodeGroup = document.querySelector(`g[data-node-id=\"${nodeId}\"]`);\n  if (!nodeGroup) return;\n  const existingFov = nodeGroup.querySelector(\".fov-group\");\n  if (existingFov) existingFov.remove();\n  if (!hasCoverageZone(node.shape) || !node.fovEnabled) return;\n  \n  const ns = \"http://www.w3.org/2000/svg\";\n  const defaults = getCoverageDefaults(node.shape);\n  const fovAngle = node.fovAngle || defaults.angle;\n  const fovDistance = node.fovDistance || defaults.distance;\n  const fovInnerRadius = node.fovInnerRadius || 0;\n  const fovRotation = node.fovRotation || 0;\n  const fovColor = node.fovColor || \"#f59e0b\";\n  const fovOpacity = node.fovOpacity || 20;\n  const fovGradient = node.fovGradient || false;\n  const fovBorderColor = node.fovBorderColor || \"#f59e0b\";\n  const fovBorderWidth = node.fovBorderWidth ?? 2;\n  const fovBorderStyle = node.fovBorderStyle || \"solid\";\n  const fovBorderOpacity = node.fovBorderOpacity ?? 100;\n  const fovLabel = node.fovLabel || \"\";\n  const fovAnimate = node.fovAnimate || false;\n  const fovAnimationType = node.fovAnimationType || defaults.animationType;\n  const fovSweep = node.fovSweep || 120;\n  const fovSpeed = node.fovSpeed || 4;\n  \n  const fovGroup = document.createElementNS(ns, \"g\");\n  fovGroup.classList.add(\"fov-group\");\n  \n  if (fovGradient) {\n    const gradientId = `fov-gradient-${nodeId}`;\n    const defs = document.createElementNS(ns, \"defs\");\n    const gradient = document.createElementNS(ns, \"radialGradient\");\n    gradient.id = gradientId;\n    gradient.setAttribute(\"cx\", \"0\");\n    gradient.setAttribute(\"cy\", \"0\");\n    gradient.setAttribute(\"r\", fovDistance);\n    gradient.setAttribute(\"gradientUnits\", \"userSpaceOnUse\");\n    \n    const stop1 = document.createElementNS(ns, \"stop\");\n    stop1.setAttribute(\"offset\", fovInnerRadius / fovDistance);\n    stop1.setAttribute(\"stop-color\", fovColor);\n    stop1.setAttribute(\"stop-opacity\", fovOpacity / 100);\n    \n    const stop2 = document.createElementNS(ns, \"stop\");\n    stop2.setAttribute(\"offset\", \"1\");\n    stop2.setAttribute(\"stop-color\", fovColor);\n    stop2.setAttribute(\"stop-opacity\", \"0\");\n    \n    gradient.appendChild(stop1);\n    gradient.appendChild(stop2);\n    defs.appendChild(gradient);\n    fovGroup.appendChild(defs);\n  }\n  \n  const fovPath = document.createElementNS(ns, \"path\");\n  \n  if (fovAngle >= 360) {\n    if (fovInnerRadius > 0) {\n      fovPath.setAttribute(\"d\", `M ${fovDistance},0 A ${fovDistance},${fovDistance} 0 1,1 ${-fovDistance},0 A ${fovDistance},${fovDistance} 0 1,1 ${fovDistance},0 M ${fovInnerRadius},0 A ${fovInnerRadius},${fovInnerRadius} 0 1,0 ${-fovInnerRadius},0 A ${fovInnerRadius},${fovInnerRadius} 0 1,0 ${fovInnerRadius},0`);\n      fovPath.setAttribute(\"fill-rule\", \"evenodd\");\n    } else {\n      fovPath.setAttribute(\"d\", `M 0,0 m -${fovDistance},0 a ${fovDistance},${fovDistance} 0 1,0 ${fovDistance * 2},0 a ${fovDistance},${fovDistance} 0 1,0 -${fovDistance * 2},0`);\n    }\n  } else {\n    const angleRad = (fovAngle * Math.PI) / 180;\n    const rotationRad = ((fovRotation - 90) * Math.PI) / 180;\n    \n    const startAngle = rotationRad - angleRad / 2;\n    const endAngle = rotationRad + angleRad / 2;\n    \n    const x1 = Math.cos(startAngle) * fovDistance;\n    const y1 = Math.sin(startAngle) * fovDistance;\n    const x2 = Math.cos(endAngle) * fovDistance;\n    const y2 = Math.sin(endAngle) * fovDistance;\n    \n    const largeArc = fovAngle > 180 ? 1 : 0;\n    \n    if (fovInnerRadius > 0) {\n      const ix1 = Math.cos(startAngle) * fovInnerRadius;\n      const iy1 = Math.sin(startAngle) * fovInnerRadius;\n      const ix2 = Math.cos(endAngle) * fovInnerRadius;\n      const iy2 = Math.sin(endAngle) * fovInnerRadius;\n      fovPath.setAttribute(\"d\", `M ${ix1},${iy1} L ${x1},${y1} A ${fovDistance},${fovDistance} 0 ${largeArc},1 ${x2},${y2} L ${ix2},${iy2} A ${fovInnerRadius},${fovInnerRadius} 0 ${largeArc},0 ${ix1},${iy1} Z`);\n    } else {\n      fovPath.setAttribute(\"d\", `M 0,0 L ${x1},${y1} A ${fovDistance},${fovDistance} 0 ${largeArc},1 ${x2},${y2} Z`);\n    }\n  }\n  \n  if (fovGradient) {\n    fovPath.style.fill = `url(#fov-gradient-${nodeId})`;\n  } else {\n    const opacityHex = Math.round((fovOpacity / 100) * 255).toString(16).padStart(2, '0');\n    fovPath.style.fill = fovColor + opacityHex;\n  }\n  \n  const borderOpacityHex = Math.round((fovBorderOpacity / 100) * 255).toString(16).padStart(2, '0');\n  fovPath.style.stroke = fovBorderColor + borderOpacityHex;\n  fovPath.style.strokeWidth = fovBorderWidth;\n  if (fovBorderStyle === \"dashed\") {\n    fovPath.style.strokeDasharray = \"10,5\";\n  } else if (fovBorderStyle === \"dotted\") {\n    fovPath.style.strokeDasharray = \"3,3\";\n  }\n  fovPath.style.pointerEvents = \"none\";\n  fovPath.classList.add(\"fov-cone\");\n  \n  fovGroup.appendChild(fovPath);\n  \n  if (fovLabel) {\n    const fovLabelPosition = node.fovLabelPosition || \"center\";\n    const fovLabelSize = node.fovLabelSize || 14;\n    const fovLabelColor = node.fovLabelColor || \"#ffffff\";\n    const fovLabelBold = node.fovLabelBold || false;\n    const fovLabelBg = node.fovLabelBg || false;\n    const fovLabelBgColor = node.fovLabelBgColor || \"#000000\";\n    \n    let labelDistance;\n    if (fovLabelPosition === \"center\") {\n      labelDistance = fovAngle >= 360 ? 0 : fovDistance * 0.4;\n    } else if (fovLabelPosition === \"edge\") {\n      labelDistance = fovDistance * 0.75;\n    } else {\n      labelDistance = fovDistance + fovLabelSize + 8;\n    }\n    \n    const labelAngle = ((fovRotation - 90) * Math.PI) / 180;\n    const fovLabelOffsetX = node.fovLabelOffsetX || 0;\n    const fovLabelOffsetY = node.fovLabelOffsetY || 0;\n    const labelX = (fovAngle >= 360 && fovLabelPosition === \"center\" ? 0 : Math.cos(labelAngle) * labelDistance) + fovLabelOffsetX;\n    const labelY = (fovAngle >= 360 && fovLabelPosition === \"center\" ? 0 : Math.sin(labelAngle) * labelDistance) + fovLabelOffsetY;\n    \n    if (fovLabelBg) {\n      const bgRect = document.createElementNS(ns, \"rect\");\n      const textWidth = fovLabel.length * fovLabelSize * 0.6;\n      const textHeight = fovLabelSize * 1.4;\n      bgRect.setAttribute(\"x\", labelX - textWidth / 2 - 6);\n      bgRect.setAttribute(\"y\", labelY - textHeight / 2);\n      bgRect.setAttribute(\"width\", textWidth + 12);\n      bgRect.setAttribute(\"height\", textHeight);\n      bgRect.setAttribute(\"rx\", \"4\");\n      bgRect.style.fill = fovLabelBgColor;\n      bgRect.style.opacity = \"0.8\";\n      bgRect.style.pointerEvents = \"none\";\n      fovGroup.appendChild(bgRect);\n    }\n    \n    const labelEl = document.createElementNS(ns, \"text\");\n    labelEl.setAttribute(\"x\", labelX);\n    labelEl.setAttribute(\"y\", labelY);\n    labelEl.setAttribute(\"text-anchor\", \"middle\");\n    labelEl.setAttribute(\"dominant-baseline\", \"middle\");\n    labelEl.style.fill = fovLabelColor;\n    labelEl.style.fontSize = fovLabelSize + \"px\";\n    labelEl.style.fontWeight = fovLabelBold ? \"bold\" : \"normal\";\n    labelEl.style.fontFamily = \"system-ui, sans-serif\";\n    labelEl.style.pointerEvents = \"none\";\n    labelEl.textContent = fovLabel;\n    fovGroup.appendChild(labelEl);\n  }\n  \n  if (fovAnimate) {\n    const animationName = `fov-anim-${nodeId}`;\n    const styleEl = document.createElementNS(\"http://www.w3.org/1999/xhtml\", \"style\");\n    \n    if (fovAnimationType === \"sweep\" && fovAngle < 360) {\n      styleEl.textContent = `\n        @keyframes ${animationName} {\n          0%, 100% { transform: rotate(0deg); }\n          50% { transform: rotate(${fovSweep}deg); }\n        }\n      `;\n      fovGroup.style.animation = `${animationName} ${fovSpeed}s ease-in-out infinite`;\n      fovGroup.style.transformOrigin = \"0 0\";\n    } else if (fovAnimationType === \"pulse\") {\n      styleEl.textContent = `\n        @keyframes ${animationName} {\n          0%, 100% { transform: scale(1); opacity: 1; }\n          50% { transform: scale(1.1); opacity: 0.7; }\n        }\n      `;\n      fovGroup.style.animation = `${animationName} ${fovSpeed}s ease-in-out infinite`;\n      fovGroup.style.transformOrigin = \"0 0\";\n    } else if (fovAnimationType === \"rings\") {\n\n      for (let i = 1; i <= 3; i++) {\n        const ring = document.createElementNS(ns, \"circle\");\n        ring.setAttribute(\"cx\", \"0\");\n        ring.setAttribute(\"cy\", \"0\");\n        ring.setAttribute(\"r\", fovDistance * 0.3 * i);\n        ring.style.fill = \"none\";\n        ring.style.stroke = fovBorderColor;\n        ring.style.strokeWidth = \"2\";\n        ring.style.opacity = \"0\";\n        const ringAnimName = `${animationName}-ring-${i}`;\n        const ringStyle = document.createElementNS(\"http://www.w3.org/1999/xhtml\", \"style\");\n        ringStyle.textContent = `\n          @keyframes ${ringAnimName} {\n            0% { r: ${fovInnerRadius || 10}; opacity: 0.8; }\n            100% { r: ${fovDistance}; opacity: 0; }\n          }\n        `;\n        ring.style.animation = `${ringAnimName} ${fovSpeed}s ease-out infinite`;\n        ring.style.animationDelay = `${(i - 1) * (fovSpeed / 3)}s`;\n        fovGroup.appendChild(ringStyle);\n        fovGroup.appendChild(ring);\n      }\n    } else if (fovAnimationType === \"spin\") {\n      styleEl.textContent = `\n        @keyframes ${animationName} {\n          0% { transform: rotate(0deg); }\n          100% { transform: rotate(360deg); }\n        }\n      `;\n      fovGroup.style.animation = `${animationName} ${fovSpeed}s linear infinite`;\n      fovGroup.style.transformOrigin = \"0 0\";\n    }\n    \n    if (fovAnimationType !== \"rings\") {\n      fovGroup.appendChild(styleEl);\n      const elapsedSeconds = (Date.now() - FOV_ANIMATION_START) / 1000;\n      const animationOffset = elapsedSeconds % fovSpeed;\n      fovGroup.style.animationDelay = `-${animationOffset}s`;\n    }\n  }\n  \n  nodeGroup.insertBefore(fovGroup, nodeGroup.firstChild);\n}\n      function forgeTheTopology() {\n       if (!NODE_DATA || !EDGE_DATA) {\n        console.warn(\"forgeTheTopology called before data initialized\");\n        return;\n       }\n       const svg = document.getElementById(\"map\");\n       svg.innerHTML = \"\";\n       const ns = \"http://www.w3.org/2000/svg\";\n       const defs = document.createElementNS(ns, \"defs\");\n       const flowArrowBig = document.createElementNS(ns, \"path\");\n       flowArrowBig.id = \"flow-arrow-big\";\n       flowArrowBig.setAttribute(\"d\", \"M-6,-4 L6,0 L-6,4 L-3,0 Z\");\n       defs.appendChild(flowArrowBig);\n       const flowArrowSmall = document.createElementNS(ns, \"path\");\n       flowArrowSmall.id = \"flow-arrow-small\";\n       flowArrowSmall.setAttribute(\"d\", \"M-4,-3 L4,0 L-4,3 Z\");\n       defs.appendChild(flowArrowSmall);\n       const markerForward = document.createElementNS(ns, \"marker\");\n       markerForward.id = \"arrow-forward\";\n       markerForward.setAttribute(\"markerWidth\", \"10\");\n       markerForward.setAttribute(\"markerHeight\", \"10\");\n       markerForward.setAttribute(\"refX\", \"9\");\n       markerForward.setAttribute(\"refY\", \"3\");\n       markerForward.setAttribute(\"orient\", \"auto\");\n       markerForward.setAttribute(\"markerUnits\", \"strokeWidth\");\n       const pathForward = document.createElementNS(ns, \"path\");\n       pathForward.setAttribute(\"d\", \"M0,0 L0,6 L9,3 z\");\n       pathForward.setAttribute(\"fill\", \"context-stroke\");\n       markerForward.appendChild(pathForward);\n       defs.appendChild(markerForward);\n       const markerBackward = document.createElementNS(ns, \"marker\");\n       markerBackward.id = \"arrow-backward\";\n       markerBackward.setAttribute(\"markerWidth\", \"10\");\n       markerBackward.setAttribute(\"markerHeight\", \"10\");\n       markerBackward.setAttribute(\"refX\", \"0\");\n       markerBackward.setAttribute(\"refY\", \"3\");\n       markerBackward.setAttribute(\"orient\", \"auto\");\n       markerBackward.setAttribute(\"markerUnits\", \"strokeWidth\");\n       const pathBackward = document.createElementNS(ns, \"path\");\n       pathBackward.setAttribute(\"d\", \"M9,0 L9,6 L0,3 z\");\n       pathBackward.setAttribute(\"fill\", \"context-stroke\");\n       markerBackward.appendChild(pathBackward);\ndefs.appendChild(markerBackward);\nconst wallPattern = document.createElementNS(ns, \"pattern\");\nwallPattern.id = \"wall-hatch\";\nwallPattern.setAttribute(\"patternUnits\", \"userSpaceOnUse\");\nwallPattern.setAttribute(\"width\", \"8\");\nwallPattern.setAttribute(\"height\", \"8\");\nwallPattern.setAttribute(\"patternTransform\", \"rotate(45)\");\nconst wallLine = document.createElementNS(ns, \"line\");\nwallLine.setAttribute(\"x1\", \"0\");\nwallLine.setAttribute(\"y1\", \"0\");\nwallLine.setAttribute(\"x2\", \"0\");\nwallLine.setAttribute(\"y2\", \"8\");\nwallLine.setAttribute(\"stroke\", \"#666\");\nwallLine.setAttribute(\"stroke-width\", \"2\");\nwallPattern.appendChild(wallLine);\ndefs.appendChild(wallPattern);\n\nsvg.appendChild(defs);\n       const boundary = document.createElementNS(ns, \"rect\");\n       boundary.setAttribute(\"x\", CANVAS_PADDING);\n       boundary.setAttribute(\"y\", CANVAS_PADDING);\n       boundary.setAttribute(\"width\", CANVAS_WIDTH - CANVAS_PADDING * 2);\n       boundary.setAttribute(\"height\", CANVAS_HEIGHT - CANVAS_PADDING * 2);\n       boundary.setAttribute(\"fill\", \"none\");\n       boundary.setAttribute(\"stroke\", (PAGE_STATE.canvasBorder || \"#475569\") + \"4D\");\n       boundary.setAttribute(\"stroke-width\", \"20\");\n       boundary.setAttribute(\"stroke-dasharray\", \"10 5\");\n       boundary.setAttribute(\"rx\", \"8\");\n       svg.appendChild(boundary);\n       if (currentView.mode !== \"rack\" && PAGE_STATE.canvasGridEnabled !== false) {\nconst gridGroup = document.createElementNS(ns, \"g\");\n gridGroup.id = \"canvas-grid\";\n const gridSize = PAGE_STATE.canvasGridSize || 50;\n const gridColor = (PAGE_STATE.canvasGrid || \"#475569\") + \"33\";\n const majorGridColor = (PAGE_STATE.canvasGrid || \"#475569\") + \"66\";\n for (let x = CANVAS_PADDING; x <= CANVAS_WIDTH - CANVAS_PADDING; x += gridSize) {\n  const line = document.createElementNS(ns, \"line\");\n  line.setAttribute(\"x1\", x);\n  line.setAttribute(\"y1\", CANVAS_PADDING);\n  line.setAttribute(\"x2\", x);\n  line.setAttribute(\"y2\", CANVAS_HEIGHT - CANVAS_PADDING);\n  line.setAttribute(\"stroke\", (x - CANVAS_PADDING) % (gridSize * 5) === 0 ? majorGridColor : gridColor);\n  line.setAttribute(\"stroke-width\", (x - CANVAS_PADDING) % (gridSize * 5) === 0 ? \"2\" : \"1\");\n  gridGroup.appendChild(line);\n }\n for (let y = CANVAS_PADDING; y <= CANVAS_HEIGHT - CANVAS_PADDING; y += gridSize) {\n  const line = document.createElementNS(ns, \"line\");\n  line.setAttribute(\"x1\", CANVAS_PADDING);\n  line.setAttribute(\"y1\", y);\n  line.setAttribute(\"x2\", CANVAS_WIDTH - CANVAS_PADDING);\n  line.setAttribute(\"y2\", y);\n  line.setAttribute(\"stroke\", (y - CANVAS_PADDING) % (gridSize * 5) === 0 ? majorGridColor : gridColor);\n  line.setAttribute(\"stroke-width\", (y - CANVAS_PADDING) % (gridSize * 5) === 0 ? \"2\" : \"1\");\n  gridGroup.appendChild(line);\n }\n svg.appendChild(gridGroup);\n}\n       if (currentView.mode === \"rack\" && currentView.rackId) {\n        const rackCapacity = getRackCapacity(currentView.rackId);\n      const rackUHeight = getRackUHeight(currentView.rackId);\n        const rackGroup = document.createElementNS(ns, \"g\");\n        rackGroup.id = \"rack-visualization\";\n        const rackFrame = document.createElementNS(ns, \"rect\");\n        rackFrame.setAttribute(\"x\", RACK_START_X - RACK_WIDTH / 2);\n        rackFrame.setAttribute(\"y\", RACK_START_Y);\n        rackFrame.setAttribute(\"width\", RACK_WIDTH);\n      rackFrame.setAttribute(\"height\", rackCapacity * rackUHeight);\n        rackFrame.setAttribute(\"fill\", (PAGE_STATE.rackFrameFill || \"#0f172a\") + \"4D\");\n        rackFrame.setAttribute(\"stroke\", PAGE_STATE.rackFrameStroke || \"#4fd1c5\");\n        rackFrame.setAttribute(\"stroke-width\", \"3\");\n        rackFrame.setAttribute(\"rx\", \"4\");\n        rackGroup.appendChild(rackFrame);\n        for (let u = 0; u <= rackCapacity; u++) {\n const y = RACK_START_Y + u * rackUHeight;\n if (PAGE_STATE.rackGridEnabled !== false) {\n const line = document.createElementNS(ns, \"line\");\n         line.setAttribute(\"x1\", RACK_START_X - RACK_WIDTH / 2);\n         line.setAttribute(\"y1\", y);\n         line.setAttribute(\"x2\", RACK_START_X + RACK_WIDTH / 2);\n         line.setAttribute(\"y2\", y);\n         line.setAttribute(\"stroke\", (PAGE_STATE.rackLineColor || \"#475569\") + \"66\");\n         line.setAttribute(\"stroke-width\", u % 5 === 0 ? \"2\" : \"1\");\n         line.setAttribute(\"stroke-dasharray\", u % 5 === 0 ? \"none\" : \"5,5\");\n         rackGroup.appendChild(line);\n }\n if (u < rackCapacity) {\n          const uNumber = rackCapacity - u;\n          const text = document.createElementNS(ns, \"text\");\n          text.setAttribute(\"x\", RACK_START_X - RACK_WIDTH / 2 - 30);\n          text.setAttribute(\"y\", y + rackUHeight / 2);\n          text.setAttribute(\"text-anchor\", \"middle\");\n          text.setAttribute(\"dominant-baseline\", \"middle\");\n          text.style.fill = PAGE_STATE.rackTextColor || \"#4fd1c5\";\n          text.style.fontSize = \"14px\";\n          text.style.fontWeight = \"bold\";\n          text.textContent = `U${uNumber}`;\n          rackGroup.appendChild(text);\n          const textRight = document.createElementNS(ns, \"text\");\n          textRight.setAttribute(\"x\", RACK_START_X + RACK_WIDTH / 2 + 30);\n          textRight.setAttribute(\"y\", y + rackUHeight / 2);\n          textRight.setAttribute(\"text-anchor\", \"middle\");\n          textRight.setAttribute(\"dominant-baseline\", \"middle\");\n          textRight.style.fill = PAGE_STATE.rackTextColor || \"#4fd1c5\";\n          textRight.style.fontSize = \"14px\";\n          textRight.style.fontWeight = \"bold\";\n          textRight.textContent = `U${uNumber}`;\n          rackGroup.appendChild(textRight);\n         }\n        }\n        svg.appendChild(rackGroup);\n       }\n        if (RECT_DATA && RECT_DATA.list) {\n         RECT_DATA.list.forEach((rect) => {\n          if (currentView.mode === \"rack\") return;\n          if (rect.style === \"filled\") {\n           const g = document.createElementNS(ns, \"g\");\n           g.classList.add(\"rect-group\");\n           g.dataset.rectId = rect.id;\n           const rectCenterX = rect.x + rect.width / 2;\n           const rectCenterY = rect.y + rect.height / 2;\n           const rectRotation = rect.rotation || 0;\n           if (rectRotation !== 0) {\n             g.setAttribute(\"transform\", `rotate(${rectRotation}, ${rectCenterX}, ${rectCenterY})`);\n           }\n           const rectEl = document.createElementNS(ns, \"rect\");\n           rectEl.classList.add(\"rect-shape\");\n           rectEl.setAttribute(\"x\", rect.x);\n           rectEl.setAttribute(\"y\", rect.y);\n           rectEl.setAttribute(\"width\", rect.width);\n           rectEl.setAttribute(\"height\", rect.height);\n           rectEl.style.fill = rect.color;\n           rectEl.style.fillOpacity = \"0.3\";\n           rectEl.style.stroke = rect.borderColor || rect.color;\n           rectEl.style.strokeWidth = (rect.borderWidth !== undefined ? rect.borderWidth : 2) + \"px\";\n           if (rect.lineStyle === \"dashed\") { rectEl.style.strokeDasharray = \"10,5\"; }\nelse if (rect.lineStyle === \"dotted\") { rectEl.style.strokeDasharray = \"2,4\"; }\nelse if (rect.lineStyle === \"wall\") {\n  rectEl.style.fill = rect.color;\n  rectEl.style.fillOpacity = \"0.5\";\n  rectEl.style.stroke = rect.borderColor || rect.color;\n  rectEl.style.strokeWidth = (rect.borderWidth !== undefined ? rect.borderWidth : 2) + \"px\";\n  const hatchGroup = document.createElementNS(ns, \"g\");\n  hatchGroup.classList.add(\"wall-hatch-lines\");\n  hatchGroup.style.pointerEvents = \"none\";\n  const spacing = 12;\n  const hatchColor = rect.borderColor || rect.color;\n  for (let i = -rect.height; i < rect.width + rect.height; i += spacing) {\n    const line = document.createElementNS(ns, \"line\");\n    line.setAttribute(\"x1\", rect.x + i);\n    line.setAttribute(\"y1\", rect.y);\n    line.setAttribute(\"x2\", rect.x + i - rect.height);\n    line.setAttribute(\"y2\", rect.y + rect.height);\n    line.style.stroke = hatchColor;\n    line.style.strokeWidth = \"2\";\n    hatchGroup.appendChild(line);\n  }\n  const clipId = \"clip-\" + rect.id;\n  const clipPath = document.createElementNS(ns, \"clipPath\");\n  clipPath.id = clipId;\n  const clipRect = document.createElementNS(ns, \"rect\");\n  clipRect.setAttribute(\"x\", rect.x);\n  clipRect.setAttribute(\"y\", rect.y);\n  clipRect.setAttribute(\"width\", rect.width);\n  clipRect.setAttribute(\"height\", rect.height);\n  clipPath.appendChild(clipRect);\n  defs.appendChild(clipPath);\n  hatchGroup.setAttribute(\"clip-path\", \"url(#\" + clipId + \")\");\n  g.appendChild(hatchGroup);\n}\n           else { rectEl.style.strokeDasharray = \"none\"; }\n           rectEl.style.cursor = \"move\";\n          rectEl.addEventListener(\"click\", (e) => {\n\t\t  if (isViewOnly()) return;\n             if (rectDrawMode) return;\n             e.stopPropagation();\n             currentRectId = rect.id;\n             selectTheRect(rect.id);\n           });\n           rectEl.addEventListener(\"contextmenu\", (e) => {\n\t\t   if (isViewOnly()) return;\n             e.preventDefault();\n             e.stopPropagation();\n             if (selectedRects.has(rect.id)) {\n               selectedRects.delete(rect.id);\n             } else {\n               selectedRects.add(rect.id);\n             }\n             updateAllSelections();\n           });\n           let rectLastTap = 0;\n           rectEl.addEventListener(\"touchend\", (e) => {\n             const now = Date.now();\n             if (now - rectLastTap < 300) {\n               e.preventDefault();\n               if (selectedRects.has(rect.id)) {\n                 selectedRects.delete(rect.id);\n               } else {\n                 selectedRects.add(rect.id);\n               }\n               updateAllSelections();\n               if (navigator.vibrate) navigator.vibrate(50);\n               rectLastTap = 0;\n             } else {\n               rectLastTap = now;\n             }\n           }, { passive: false });\n           const deleteBtn = document.createElementNS(ns, \"g\");\n           deleteBtn.classList.add(\"rect-delete-btn\");\n           deleteBtn.style.cursor = \"pointer\";\n           deleteBtn.style.display = rectDrawMode ? \"block\" : \"none\";\n           const deleteBg = document.createElementNS(ns, \"circle\");\n           deleteBg.setAttribute(\"cx\", rect.x + rect.width - 10);\n           deleteBg.setAttribute(\"cy\", rect.y + 10);\n           deleteBg.setAttribute(\"r\", 12);\n           deleteBg.style.fill = \"#f56565\";\n           deleteBg.style.stroke = \"white\";\n           deleteBg.style.strokeWidth = \"2\";\n           const deleteX = document.createElementNS(ns, \"text\");\n           deleteX.setAttribute(\"x\", rect.x + rect.width - 10);\n           deleteX.setAttribute(\"y\", rect.y + 10);\n           deleteX.setAttribute(\"text-anchor\", \"middle\");\n           deleteX.setAttribute(\"dominant-baseline\", \"middle\");\n           deleteX.style.fill = \"white\";\n           deleteX.style.fontSize = \"16px\";\n           deleteX.style.fontWeight = \"bold\";\n           deleteX.style.pointerEvents = \"none\";\n           deleteX.textContent = \"×\";\n           deleteBtn.appendChild(deleteBg);\n           deleteBtn.appendChild(deleteX);\n           deleteBtn.addEventListener(\"click\", (e) => {\n            e.stopPropagation();\n            e.preventDefault();\n            deleteRectangle(rect.id);\n           });\n           deleteBtn.addEventListener(\"touchend\", (e) => {\n      e.stopPropagation();\n           e.preventDefault();\n           deleteRectangle(rect.id);\n           });\n           let isDragging = false;\n           let dragStartX, dragStartY;\n           let rectStartX, rectStartY;\n         rectEl.addEventListener(\"mousedown\", (e) => {\n\t\t if (isViewOnly()) return;\n      if (rectDrawMode) return;\n      e.preventDefault();\n      e.stopPropagation();\n      isDragging = true;\n      dragStartX = e.clientX;\n      dragStartY = e.clientY;\n      rectStartX = rect.x;\n      rectStartY = rect.y;\n      rectEl.style.cursor = \"grabbing\";\n      if (selectedRects.has(rect.id)) {\n      initialPositions = {};\n      selectedNodes.forEach(nodeId => {\n      const nodePos = savedPositions[nodeId];\n      if (nodePos) initialPositions[nodeId] = { x: nodePos.x, y: nodePos.y };\n      });\n      selectedRects.forEach(rectId => {\n      const r = RECT_DATA.list.find(x => x.id === rectId);\n      if (r) { r._dragStartX = r.x; r._dragStartY = r.y; }\n      });\n      selectedTexts.forEach(textId => {\n      const t = TEXT_DATA.list.find(x => x.id === textId);\n      if (t) { t._dragStartX = t.x; t._dragStartY = t.y; }\n      });\n      selectedEdges.forEach(edgeId => {\n      const ed = EDGE_DATA.list.find(x => x.id === edgeId);\n      if (ed && ed.points) { ed._dragStartPoints = ed.points.map(p => ({x: p.x, y: p.y})); }\n      });\n      }\n      });\n           const moveHandler = (e) => {\n      if (!isDragging || rectDrawMode) return;\n      const svgEl = svg;\n      const pt1 = svgEl.createSVGPoint();\n      pt1.x = dragStartX;\n      pt1.y = dragStartY;\n      const svgP1 = pt1.matrixTransform(svgEl.getScreenCTM().inverse());\n      const pt2 = svgEl.createSVGPoint();\n      pt2.x = e.clientX;\n      pt2.y = e.clientY;\n      const svgP2 = pt2.matrixTransform(svgEl.getScreenCTM().inverse());\n      const dx = svgP2.x - svgP1.x;\n      const dy = svgP2.y - svgP1.y;\n      rect.x = rectStartX + dx;\n      rect.y = rectStartY + dy;\n      if (selectedRects.has(rect.id)) {\n      selectedNodes.forEach(nodeId => {\n      if (initialPositions[nodeId]) {\n       const newX = initialPositions[nodeId].x + dx;\n       const newY = initialPositions[nodeId].y + dy;\n       savedPositions[nodeId] = { x: newX, y: newY };\n      }\n      });\n      selectedRects.forEach(rectId => {\n      if (rectId === rect.id) return;\n      const r = RECT_DATA.list.find(x => x.id === rectId);\n      if (r && r._dragStartX !== undefined) { r.x = r._dragStartX + dx; r.y = r._dragStartY + dy; }\n      });\n      selectedTexts.forEach(textId => {\n      const t = TEXT_DATA.list.find(x => x.id === textId);\n      if (t && t._dragStartX !== undefined) { t.x = t._dragStartX + dx; t.y = t._dragStartY + dy; }\n      });\n      selectedEdges.forEach(edgeId => {\n      const ed = EDGE_DATA.list.find(x => x.id === edgeId);\n      if (ed && ed._dragStartPoints) { ed.points.forEach((p, i) => { p.x = ed._dragStartPoints[i].x + dx; p.y = ed._dragStartPoints[i].y + dy; }); }\n      });\n      }\n      forgeTheTopology();\n      };\n           const upHandler = () => {\n            if (isDragging) {\n             isDragging = false;\n             rectEl.style.cursor = \"move\";\n            }\n           };\n           document.addEventListener(\"mousemove\", moveHandler);\n           document.addEventListener(\"mouseup\", upHandler);\n           let touchStartX, touchStartY;\n           let touchRectStartX, touchRectStartY;\n           rectEl.addEventListener(\"touchstart\", (e) => {\n\t\t   if (isViewOnly()) return;\n            if (rectDrawMode) return;\n            e.preventDefault();\n            e.stopPropagation();\n            isDragging = true;\n            const touch = e.touches[0];\n            touchStartX = touch.clientX;\n            touchStartY = touch.clientY;\n            touchRectStartX = rect.x;\n            touchRectStartY = rect.y;\n            rectEl.style.cursor = \"grabbing\";\n           }, { passive: false });\n           rectEl.addEventListener(\"touchmove\", (e) => {\n\t\t    if (isViewOnly()) return;\n            if (!isDragging || rectDrawMode) return;\n            if (!e.touches[0]) return;\n            e.preventDefault();\n            e.stopPropagation();\n            const svgEl = svg;\n            const pt1 = svgEl.createSVGPoint();\n            pt1.x = touchStartX;\n            pt1.y = touchStartY;\n            const svgP1 = pt1.matrixTransform(svgEl.getScreenCTM().inverse());\n            const touch = e.touches[0];\n            const pt2 = svgEl.createSVGPoint();\n            pt2.x = touch.clientX;\n            pt2.y = touch.clientY;\n            const svgP2 = pt2.matrixTransform(svgEl.getScreenCTM().inverse());\n            const dx = svgP2.x - svgP1.x;\n            const dy = svgP2.y - svgP1.y;\n            rect.x = touchRectStartX + dx;\n            rect.y = touchRectStartY + dy;\n            forgeTheTopology();\n           }, { passive: false });\n           rectEl.addEventListener(\"touchend\", (e) => {\n            if (isDragging) {\n             e.preventDefault();\n             isDragging = false;\n             rectEl.style.cursor = \"move\";\n            }\n           }, { passive: false });\n            if (currentRectId === rect.id) {\n      const corners = [\n      { cx: rect.x, cy: rect.y, cursor: 'nwse-resize', dx: -1, dy: -1 },\n      { cx: rect.x + rect.width, cy: rect.y, cursor: 'nesw-resize', dx: 1, dy: -1 },\n      { cx: rect.x, cy: rect.y + rect.height, cursor: 'nesw-resize', dx: -1, dy: 1 },\n      { cx: rect.x + rect.width, cy: rect.y + rect.height, cursor: 'nwse-resize', dx: 1, dy: 1 }\n      ];\n      corners.forEach((corner, idx) => {\n      const handle = document.createElementNS(ns, \"circle\");\n      handle.setAttribute(\"cx\", corner.cx);\n      handle.setAttribute(\"cy\", corner.cy);\n      const borderW = rect.borderWidth !== undefined ? rect.borderWidth : 2;\n      const handleSize = Math.max(PAGE_STATE.selectionHandleSize || 8, borderW + 4);\n      handle.setAttribute(\"r\", handleSize);\n      handle.style.fill = PAGE_STATE.selectionHandle || \"#f59e0b\";\n      handle.style.stroke = \"#fff\";\n      handle.style.strokeWidth = \"2\";\n      handle.style.cursor = corner.cursor;\n      handle.addEventListener(\"mousedown\", (e) => {\n\t  if (isViewOnly()) return;\n      e.preventDefault();\n      e.stopPropagation();\n\t  pushUndo(\"resize zone\");\n      let dragging = true;\n      const startX = e.clientX, startY = e.clientY;\n      const origX = rect.x, origY = rect.y, origW = rect.width, origH = rect.height;\n      const moveHandler = (ev) => {\n        if (!dragging) return;\n        const pt1 = svg.createSVGPoint(); pt1.x = startX; pt1.y = startY;\n        const pt2 = svg.createSVGPoint(); pt2.x = ev.clientX; pt2.y = ev.clientY;\n        const svgP1 = pt1.matrixTransform(svg.getScreenCTM().inverse());\n        const svgP2 = pt2.matrixTransform(svg.getScreenCTM().inverse());\n        const dx = svgP2.x - svgP1.x, dy = svgP2.y - svgP1.y;\n        if (corner.dx < 0) { rect.x = origX + dx; rect.width = origW - dx; }\n        else { rect.width = origW + dx; }\n        if (corner.dy < 0) { rect.y = origY + dy; rect.height = origH - dy; }\n        else { rect.height = origH + dy; }\n        if (rect.width < 20) rect.width = 20;\n        if (rect.height < 20) rect.height = 20;\n        forgeTheTopology();\n      };\n      const upHandler = () => { dragging = false; document.removeEventListener(\"mousemove\", moveHandler); document.removeEventListener(\"mouseup\", upHandler); };\n      document.addEventListener(\"mousemove\", moveHandler);\n      document.addEventListener(\"mouseup\", upHandler);\n      });\n      g.appendChild(handle);\n      });\n      }\n          if (rect.groupId) {\n      const groupIndicator = document.createElementNS(ns, \"rect\");\n      groupIndicator.setAttribute(\"x\", rect.x - 4);\n      groupIndicator.setAttribute(\"y\", rect.y - 4);\n      groupIndicator.setAttribute(\"width\", rect.width + 8);\n      groupIndicator.setAttribute(\"height\", rect.height + 8);\n      groupIndicator.style.fill = \"none\";\n      groupIndicator.style.stroke = \"#4fd1c5\";\n      groupIndicator.style.strokeWidth = \"3\";\n      groupIndicator.style.strokeDasharray = \"5,5\";\n      groupIndicator.style.pointerEvents = \"none\";\n      g.insertBefore(groupIndicator, g.firstChild);\n      }\n      g.appendChild(rectEl);\n      g.appendChild(deleteBtn);\n      svg.appendChild(g);\n          }\n         });\n        }\n       const centerX = CANVAS_WIDTH / 2;\n       const centerY = CANVAS_HEIGHT / 2;\n       let positions = {};\n              Object.keys(NODE_DATA).forEach((id) => {\n        if (currentView.mode === \"rack\") {\n         const node = NODE_DATA[id];\n         if (!node || node.assignedRack !== currentView.rackId) {\n          return;\n         }\n        }\n        positions[id] = savedPositions[id] || {\n         x: centerX,\n         y: centerY\n        };\n       });\n       if (Object.keys(savedPositions).length === 0) {\n        const nodeIds = Object.keys(NODE_DATA).filter(id => {\n         if (currentView.mode === \"rack\") {\n          const node = NODE_DATA[id];\n          return node && node.assignedRack === currentView.rackId;\n         }\n         return true;\n        });\n        const baseY = centerY - 300;\n        if (nodeIds.length > 0) {\n         positions[nodeIds[0]] = {\n          x: centerX,\n          y: baseY\n         };\n         const remaining = nodeIds.slice(1);\n         const radius = 350;\n         const startAngle = Math.PI * 0.3;\n         const endAngle = Math.PI * 0.7;\n         remaining.forEach((id, i) => {\n          const angle = startAngle + (endAngle - startAngle) * (i / Math.max(1, remaining.length - 1));\n          positions[id] = {\n           x: centerX + Math.cos(angle) * radius * (i % 2 === 0 ? 1 : 1.3),\n           y: baseY + 200 + Math.sin(angle) * radius * 0.8 + i * 80,\n          };\n         });\n        }\n       }\n       Object.keys(positions).forEach((id) => {\n        let pos = savedPositions[id] || positions[id];\n        const nodeSize = savedSizes[id] || 55;\n        pos.x = Math.max(nodeSize + CANVAS_PADDING, Math.min(CANVAS_WIDTH - nodeSize - CANVAS_PADDING, pos.x));\n        pos.y = Math.max(nodeSize + CANVAS_PADDING, Math.min(CANVAS_HEIGHT - nodeSize - CANVAS_PADDING, pos.y));\n        positions[id] = {\n         x: pos.x,\n         y: pos.y\n        };\n        savedPositions[id] = {\n         x: pos.x,\n         y: pos.y\n        };\n       });\n       const edgePairCount = {};\n       const edgePairIndex = {};\n       EDGE_DATA.list.forEach((edge) => {\n        if (edge.type === \"custom\") return;\n        const key = [edge.from, edge.to].sort().join(\"||\");\n        edgePairCount[key] = (edgePairCount[key] || 0) + 1;\n       });\n       EDGE_DATA.list.forEach((edge) => {\n        if (edge.type === \"custom\") return;\n        const key = [edge.from, edge.to].sort().join(\"||\");\n        if (!edgePairIndex[key]) edgePairIndex[key] = 0;\n        edge._pairIndex = edgePairIndex[key];\n        edge._pairTotal = edgePairCount[key];\n        edgePairIndex[key]++;\n       });\n       \n       const orthoGaps = (function() {\n         const segments = [];\n         const GAP_SIZE = 12;\n         EDGE_DATA.list.forEach((edge, edgeIndex) => {\n           if ((edge.routing || PAGE_STATE.defaultEdgeRouting || \"curved\") !== \"orthogonal\") return;\n           if (edge.type === \"custom\") return;\n           const p1 = positions[edge.from];\n           const p2 = positions[edge.to];\n           if (!p1 || !p2) return;\n           const dx = p2.x - p1.x;\n           const dy = p2.y - p1.y;\n           const pairIndex = edge._pairIndex || 0;\n           const pairTotal = edge._pairTotal || 1;\n           const offset = (pairIndex - (pairTotal - 1) / 2) * 40;\n           if (Math.abs(dx) > Math.abs(dy)) {\n             const midX = p1.x + dx / 2 + offset;\n             segments.push({ edgeId: edge.id, x1: p1.x, y1: p1.y, x2: midX, y2: p1.y, idx: edgeIndex });\n             segments.push({ edgeId: edge.id, x1: midX, y1: p1.y, x2: midX, y2: p2.y, idx: edgeIndex });\n             segments.push({ edgeId: edge.id, x1: midX, y1: p2.y, x2: p2.x, y2: p2.y, idx: edgeIndex });\n           } else {\n             const midY = p1.y + dy / 2 + offset;\n             segments.push({ edgeId: edge.id, x1: p1.x, y1: p1.y, x2: p1.x, y2: midY, idx: edgeIndex });\n             segments.push({ edgeId: edge.id, x1: p1.x, y1: midY, x2: p2.x, y2: midY, idx: edgeIndex });\n             segments.push({ edgeId: edge.id, x1: p2.x, y1: midY, x2: p2.x, y2: p2.y, idx: edgeIndex });\n           }\n         });\n         const gaps = {};\n         for (let i = 0; i < segments.length; i++) {\n           for (let j = i + 1; j < segments.length; j++) {\n             const s1 = segments[i];\n             const s2 = segments[j];\n             if (s1.edgeId === s2.edgeId) continue;\n             const s1Horiz = Math.abs(s1.y1 - s1.y2) < 1;\n             const s2Horiz = Math.abs(s2.y1 - s2.y2) < 1;\n             if (s1Horiz === s2Horiz) continue;\n             const horiz = s1Horiz ? s1 : s2;\n             const vert = s1Horiz ? s2 : s1;\n             const hY = horiz.y1;\n             const vX = vert.x1;\n             const hMinX = Math.min(horiz.x1, horiz.x2);\n             const hMaxX = Math.max(horiz.x1, horiz.x2);\n             const vMinY = Math.min(vert.y1, vert.y2);\n             const vMaxY = Math.max(vert.y1, vert.y2);\n             if (vX > hMinX && vX < hMaxX && hY > vMinY && hY < vMaxY) {\n               const gapEdge = s1.idx > s2.idx ? s1.edgeId : s2.edgeId;\n               if (!gaps[gapEdge]) gaps[gapEdge] = [];\n               gaps[gapEdge].push({ x: vX, y: hY });\n             }\n           }\n         }\n         return gaps;\n       })();\n\n       EDGE_DATA.list.forEach((edge) => {\n        if (edge.type === \"custom\" && Array.isArray(edge.points) && edge.points.length >= 2) {\n         const poly = document.createElementNS(ns, \"polyline\");\n         poly.classList.add(\"edge\");\n         poly.dataset.edgeId = edge.id;\n         poly.style.stroke = edge.color || PAGE_STATE.defaultEdge || \"#475569\";\n         poly.style.strokeWidth = edge.width || 4;\n         poly.setAttribute(\"fill\", \"none\");\n         const lineStyle = edge.lineStyle || \"solid\";\nif (lineStyle === \"dashed\") {\n poly.style.strokeDasharray = \"10,5\";\n} else if (lineStyle === \"dotted\") {\n poly.style.strokeDasharray = \"2,4\";\n} else if (lineStyle === \"wall\") {\n poly.style.stroke = \"url(#wall-hatch)\";\n poly.style.strokeWidth = (edge.width || 4) * 3;\n poly.style.strokeDasharray = \"none\";\n} else {\n poly.style.strokeDasharray = \"none\";\n}\n         const direction = edge.direction || \"none\";\n         if (direction === \"forward\") {\n          poly.setAttribute(\"marker-end\", \"url(#arrow-forward)\");\n         } else if (direction === \"backward\") {\n          poly.setAttribute(\"marker-start\", \"url(#arrow-backward)\");\n         } else if (direction === \"both\") {\n          poly.setAttribute(\"marker-start\", \"url(#arrow-backward)\");\n          poly.setAttribute(\"marker-end\", \"url(#arrow-forward)\");\n         }\n         const ptsStr = edge.points.map((p) => `${p.x},${p.y}`).join(\" \");\n         poly.setAttribute(\"points\", ptsStr);\n         const animDir = PAGE_STATE.animationDirection || \"all\";\n         const shouldAnimate = (PAGE_STATE.animateConnections && edge.animate !== false || edge.animate === true) && direction !== \"none\" && edge.points.length >= 2 && (animDir === \"all\" || animDir === direction);\n         if (shouldAnimate) {\n          poly.style.opacity = \"0.25\";\n          const polyPathD = \"M \" + edge.points.map(p => `${p.x} ${p.y}`).join(\" L \");\n          const edgeStyle = edge.animationStyle || PAGE_STATE.animationStyle || \"arrows\";\n          const arrowId = edgeStyle === \"dots\" ? \"#flow-arrow-small\" : \"#flow-arrow-big\";\n          const arrowColor = edge.color || PAGE_STATE.defaultEdge || \"#475569\";\n          const arrowCount = 3;\n          const animDuration = edge.animationSpeed ? parseFloat(edge.animationSpeed) : (PAGE_STATE.animationSpeed || 1.5);\n          if (direction === \"forward\" || direction === \"both\") {\n           for (let i = 0; i < arrowCount; i++) {\n            const arrow = document.createElementNS(ns, \"use\");\n            arrow.setAttribute(\"href\", arrowId);\n            arrow.style.fill = arrowColor;\n            arrow.style.offsetPath = `path('${polyPathD}')`;\n            arrow.style.animationDuration = animDuration + \"s\";\n            arrow.style.animationDelay = (i * (animDuration / arrowCount)) + \"s\";\n            arrow.classList.add(\"edge-arrow-forward\");\n            svg.appendChild(arrow);\n           }\n          }\n          if (direction === \"backward\" || direction === \"both\") {\n           for (let i = 0; i < arrowCount; i++) {\n            const arrow = document.createElementNS(ns, \"use\");\n            arrow.setAttribute(\"href\", arrowId);\n            arrow.style.fill = arrowColor;\n            arrow.style.offsetPath = `path('${polyPathD}')`;\n            arrow.style.animationDuration = animDuration + \"s\";\n            arrow.style.animationDelay = ((i * (animDuration / arrowCount)) + (direction === \"both\" ? animDuration / (arrowCount * 2) : 0)) + \"s\";\n            arrow.classList.add(\"edge-arrow-backward\");\n            svg.appendChild(arrow);\n           }\n          }\n         }\n         const polyHit = document.createElementNS(ns, \"polyline\");\n         polyHit.setAttribute(\"points\", ptsStr);\n         polyHit.style.fill = \"none\";\n         polyHit.style.stroke = \"transparent\";\n         polyHit.style.strokeWidth = \"20\";\n         polyHit.style.cursor = \"pointer\";\n         polyHit.dataset.edgeId = edge.id;\n         polyHit.addEventListener(\"click\", (e) => {\n          e.stopPropagation();\n          selectTheConnection(edge.id);\n         });\n         let edgeTouchStart = null;\n         let edgeTouchMoved = false;\n         polyHit.addEventListener(\"touchstart\", (e) => {\n          edgeTouchStart = Date.now();\n          edgeTouchMoved = false;\n          if (e.touches[0]) {\n           edgeTouchStartX = e.touches[0].clientX;\n           edgeTouchStartY = e.touches[0].clientY;\n          }\n         }, {\n          passive: false\n         });\n         let edgeTouchStartX = 0, edgeTouchStartY = 0;\n         polyHit.addEventListener(\"touchmove\", (e) => {\n          if (e.touches[0]) {\n           const dx = Math.abs(e.touches[0].clientX - edgeTouchStartX);\n           const dy = Math.abs(e.touches[0].clientY - edgeTouchStartY);\n           if (dx > 10 || dy > 10) edgeTouchMoved = true;\n          }\n         }, {\n          passive: false\n         });\n         polyHit.addEventListener(\"touchend\", (e) => {\n          if (edgeTouchStart && !edgeTouchMoved && Date.now() - edgeTouchStart < 400) {\n           e.stopPropagation();\n           e.preventDefault();\n           selectTheConnection(edge.id);\n          }\n          edgeTouchStart = null;\n          edgeTouchMoved = false;\n         }, {\n          passive: false\n         });\n         poly.addEventListener(\"click\", (e) => {\n          e.stopPropagation();\n          selectTheConnection(edge.id);\n         });\n         polyHit.addEventListener(\"contextmenu\", (e) => {\n          e.preventDefault();\n          e.stopPropagation();\n          if (selectedEdges.has(edge.id)) {\n           selectedEdges.delete(edge.id);\n          } else {\n           selectedEdges.add(edge.id);\n          }\n          updateAllSelections();\n         });\n         let customEdgeLastTap = 0;\n         polyHit.addEventListener(\"touchend\", (e) => {\n          const now = Date.now();\n          if (now - customEdgeLastTap < 300) {\n           e.preventDefault();\n           if (selectedEdges.has(edge.id)) {\n            selectedEdges.delete(edge.id);\n           } else {\n            selectedEdges.add(edge.id);\n           }\n           updateAllSelections();\n           if (navigator.vibrate) navigator.vibrate(50);\n           customEdgeLastTap = 0;\n          } else {\n           customEdgeLastTap = now;\n          }\n         }, { passive: false });\n         poly.addEventListener(\"contextmenu\", (e) => {\n          e.preventDefault();\n          e.stopPropagation();\n          if (selectedEdges.has(edge.id)) {\n           selectedEdges.delete(edge.id);\n          } else {\n           selectedEdges.add(edge.id);\n          }\n          updateAllSelections();\n         });\n         let edgeLastTap = 0;\n         poly.addEventListener(\"touchend\", (e) => {\n          const now = Date.now();\n          if (now - edgeLastTap < 300) {\n           e.preventDefault();\n           if (selectedEdges.has(edge.id)) {\n            selectedEdges.delete(edge.id);\n           } else {\n            selectedEdges.add(edge.id);\n           }\n           updateAllSelections();\n           if (navigator.vibrate) navigator.vibrate(50);\n           edgeLastTap = 0;\n          } else {\n           edgeLastTap = now;\n          }\n         });\n         if (currentView.mode === \"rack\") {\n          return;\n         }\n         if (edge.groupId) {\n      const bounds = edge.points.reduce((acc, p) => ({ minX: Math.min(acc.minX, p.x), minY: Math.min(acc.minY, p.y), maxX: Math.max(acc.maxX, p.x), maxY: Math.max(acc.maxY, p.y) }), { minX: Infinity, minY: Infinity, maxX: -Infinity, maxY: -Infinity });\n      const groupIndicator = document.createElementNS(ns, \"rect\");\n      groupIndicator.setAttribute(\"x\", bounds.minX - 8);\n      groupIndicator.setAttribute(\"y\", bounds.minY - 8);\n      groupIndicator.setAttribute(\"width\", bounds.maxX - bounds.minX + 16);\n      groupIndicator.setAttribute(\"height\", bounds.maxY - bounds.minY + 16);\n      groupIndicator.setAttribute(\"rx\", \"4\");\n      groupIndicator.style.fill = \"none\";\n      groupIndicator.style.stroke = \"#4fd1c5\";\n      groupIndicator.style.strokeWidth = \"3\";\n      groupIndicator.style.strokeDasharray = \"5,5\";\n      groupIndicator.style.pointerEvents = \"none\";\n      svg.appendChild(groupIndicator);\n      }\n         let lineDragging = false;\n      let lineDragStartX, lineDragStartY;\n      let linePointsStart = [];\n      polyHit.addEventListener(\"mousedown\", (e) => {\n\t  if (isViewOnly()) return;\n      if (freeDrawMode) return;\n      e.preventDefault();\n      e.stopPropagation();\n      lineDragging = true;\n      lineDragStartX = e.clientX;\n      lineDragStartY = e.clientY;\n      linePointsStart = edge.points.map(p => ({x: p.x, y: p.y}));\n      if (selectedEdges.has(edge.id)) {\n      initialPositions = {};\n      selectedNodes.forEach(nodeId => {\n      const nodePos = savedPositions[nodeId];\n      if (nodePos) initialPositions[nodeId] = { x: nodePos.x, y: nodePos.y };\n      });\n      selectedRects.forEach(rectId => {\n      const r = RECT_DATA.list.find(x => x.id === rectId);\n      if (r) { r._dragStartX = r.x; r._dragStartY = r.y; }\n      });\n      selectedTexts.forEach(textId => {\n      const t = TEXT_DATA.list.find(x => x.id === textId);\n      if (t) { t._dragStartX = t.x; t._dragStartY = t.y; }\n      });\n      selectedEdges.forEach(edgeId => {\n      const ed = EDGE_DATA.list.find(x => x.id === edgeId);\n      if (ed && ed.points) { ed._dragStartPoints = ed.points.map(p => ({x: p.x, y: p.y})); }\n      });\n      }\n      });\n      const lineMoveHandler = (e) => {\n      if (!lineDragging || freeDrawMode) return;\n      const svgEl = svg;\n      const pt1 = svgEl.createSVGPoint();\n      pt1.x = lineDragStartX;\n      pt1.y = lineDragStartY;\n      const svgP1 = pt1.matrixTransform(svgEl.getScreenCTM().inverse());\n      const pt2 = svgEl.createSVGPoint();\n      pt2.x = e.clientX;\n      pt2.y = e.clientY;\n      const svgP2 = pt2.matrixTransform(svgEl.getScreenCTM().inverse());\n      const dx = svgP2.x - svgP1.x;\n      const dy = svgP2.y - svgP1.y;\n      edge.points.forEach((p, i) => { p.x = linePointsStart[i].x + dx; p.y = linePointsStart[i].y + dy; });\n      if (selectedEdges.has(edge.id)) {\n      selectedNodes.forEach(nodeId => {\n      if (initialPositions[nodeId]) {\n       savedPositions[nodeId] = { x: initialPositions[nodeId].x + dx, y: initialPositions[nodeId].y + dy };\n      }\n      });\n      selectedRects.forEach(rectId => {\n      const r = RECT_DATA.list.find(x => x.id === rectId);\n      if (r && r._dragStartX !== undefined) { r.x = r._dragStartX + dx; r.y = r._dragStartY + dy; }\n      });\n      selectedTexts.forEach(textId => {\n      const t = TEXT_DATA.list.find(x => x.id === textId);\n      if (t && t._dragStartX !== undefined) { t.x = t._dragStartX + dx; t.y = t._dragStartY + dy; }\n      });\n      selectedEdges.forEach(edgeId => {\n      if (edgeId === edge.id) return;\n      const ed = EDGE_DATA.list.find(x => x.id === edgeId);\n      if (ed && ed._dragStartPoints) { ed.points.forEach((p, i) => { p.x = ed._dragStartPoints[i].x + dx; p.y = ed._dragStartPoints[i].y + dy; }); }\n      });\n      }\n      forgeTheTopology();\n      };\n      const lineUpHandler = () => {\n        lineDragging = false;\n        document.removeEventListener(\"mousemove\", lineMoveHandler);\n        document.removeEventListener(\"mouseup\", lineUpHandler);\n      };\n      document.addEventListener(\"mousemove\", lineMoveHandler);\n      document.addEventListener(\"mouseup\", lineUpHandler);\n      svg.appendChild(poly);\n      svg.appendChild(polyHit);\n         if (currentEdgeId === edge.id) {\n          edge.points.forEach((p, idx) => {\n           const c = document.createElementNS(ns, \"circle\");\n           c.classList.add(\"edge-edit-point\");\n           c.setAttribute(\"cx\", p.x);\n           c.setAttribute(\"cy\", p.y);\n      c.setAttribute(\"r\", PAGE_STATE.selectionHandleSize || 8);\n           c.style.fill = PAGE_STATE.selectionHandle || \"#f59e0b\";\n           c.style.stroke = \"#fff\";\n           c.style.strokeWidth = \"2\";\n           c.style.cursor = \"move\";\n           c.dataset.edgeId = edge.id;\n           c.dataset.pointIndex = String(idx);\n           c.addEventListener(\"mousedown\", (e) => {\n\t\t   if (isViewOnly()) return;\n            e.preventDefault();\n            e.stopPropagation();\n            pushUndo(\"edit edge point\");\n            let dragging = true;\n            const svgEl = svg;\n            const moveHandler = (ev) => {\n             if (!dragging) return;\n             const pt = svgEl.createSVGPoint();\n             pt.x = ev.clientX;\n             pt.y = ev.clientY;\n             const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n             edge.points[idx].x = svgP.x;\n             edge.points[idx].y = svgP.y;\n             forgeTheTopology();\n            };\n            const upHandler = () => {\n             dragging = false;\n             document.removeEventListener(\"mousemove\", moveHandler);\n             document.removeEventListener(\"mouseup\", upHandler);\n            };\n            document.addEventListener(\"mousemove\", moveHandler);\n            document.addEventListener(\"mouseup\", upHandler);\n           });\n           c.addEventListener(\"touchstart\", (e) => {\n\t\t   if (isViewOnly()) return;\n            e.preventDefault();\n            e.stopPropagation();\n            pushUndo(\"edit edge point\");\n            let dragging = true;\n            const svgEl = svg;\n            const touchMoveHandler = (ev) => {\n             if (!dragging || !ev.touches[0]) return;\n             const pt = svgEl.createSVGPoint();\n             pt.x = ev.touches[0].clientX;\n             pt.y = ev.touches[0].clientY;\n             const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n             edge.points[idx].x = svgP.x;\n             edge.points[idx].y = svgP.y;\n             forgeTheTopology();\n            };\n            const touchUpHandler = () => {\n             dragging = false;\n             document.removeEventListener(\"touchmove\", touchMoveHandler);\n             document.removeEventListener(\"touchend\", touchUpHandler);\n            };\n            document.addEventListener(\"touchmove\", touchMoveHandler);\n            document.addEventListener(\"touchend\", touchUpHandler);\n           }, { passive: false });\n           svg.appendChild(c);\n          });\n         }\n         return;\n        }\n        const p1 = positions[edge.from];\n        const p2 = positions[edge.to];\n        if (!p1 || !p2) return;\n        const pairTotal = edge._pairTotal || 1;\n        const pairIndex = edge._pairIndex || 0;\n        const routing = edge.routing || \"curved\";\n        let pathD;\n        if (routing === \"straight\") {\n         pathD = `M ${p1.x} ${p1.y} L ${p2.x} ${p2.y}`;\n        } else if (routing === \"orthogonal\") {\n         const dx = p2.x - p1.x;\n         const dy = p2.y - p1.y;\n         const offset = (pairIndex - (pairTotal - 1) / 2) * 40;\n         const GAP = 10;\n         const edgeGaps = orthoGaps[edge.id] || [];\n         \n         if (Math.abs(dx) > Math.abs(dy)) {\n          const midX = p1.x + dx / 2 + offset;\n          const segs = [\n           { x1: p1.x, y1: p1.y, x2: midX, y2: p1.y },\n           { x1: midX, y1: p1.y, x2: midX, y2: p2.y },\n           { x1: midX, y1: p2.y, x2: p2.x, y2: p2.y }\n          ];\n          pathD = `M ${p1.x} ${p1.y}`;\n          segs.forEach(seg => {\n           const isHoriz = Math.abs(seg.y1 - seg.y2) < 1;\n           const segGaps = edgeGaps.filter(g => {\n            if (isHoriz) {\n             const minX = Math.min(seg.x1, seg.x2), maxX = Math.max(seg.x1, seg.x2);\n             return Math.abs(g.y - seg.y1) < 1 && g.x > minX + GAP && g.x < maxX - GAP;\n            } else {\n             const minY = Math.min(seg.y1, seg.y2), maxY = Math.max(seg.y1, seg.y2);\n             return Math.abs(g.x - seg.x1) < 1 && g.y > minY + GAP && g.y < maxY - GAP;\n            }\n           }).sort((a, b) => isHoriz ? (seg.x2 > seg.x1 ? a.x - b.x : b.x - a.x) : (seg.y2 > seg.y1 ? a.y - b.y : b.y - a.y));\n           let cx = seg.x1, cy = seg.y1;\n           segGaps.forEach(g => {\n            if (isHoriz) {\n             const gx = g.x;\n             pathD += ` L ${gx - GAP} ${cy} M ${gx + GAP} ${cy}`;\n             cx = gx + GAP;\n            } else {\n             const gy = g.y;\n             pathD += ` L ${cx} ${gy - GAP} M ${cx} ${gy + GAP}`;\n             cy = gy + GAP;\n            }\n           });\n           pathD += ` L ${seg.x2} ${seg.y2}`;\n          });\n         } else {\n          const midY = p1.y + dy / 2 + offset;\n          const segs = [\n           { x1: p1.x, y1: p1.y, x2: p1.x, y2: midY },\n           { x1: p1.x, y1: midY, x2: p2.x, y2: midY },\n           { x1: p2.x, y1: midY, x2: p2.x, y2: p2.y }\n          ];\n          pathD = `M ${p1.x} ${p1.y}`;\n          segs.forEach(seg => {\n           const isHoriz = Math.abs(seg.y1 - seg.y2) < 1;\n           const segGaps = edgeGaps.filter(g => {\n            if (isHoriz) {\n             const minX = Math.min(seg.x1, seg.x2), maxX = Math.max(seg.x1, seg.x2);\n             return Math.abs(g.y - seg.y1) < 1 && g.x > minX + GAP && g.x < maxX - GAP;\n            } else {\n             const minY = Math.min(seg.y1, seg.y2), maxY = Math.max(seg.y1, seg.y2);\n             return Math.abs(g.x - seg.x1) < 1 && g.y > minY + GAP && g.y < maxY - GAP;\n            }\n           }).sort((a, b) => isHoriz ? (seg.x2 > seg.x1 ? a.x - b.x : b.x - a.x) : (seg.y2 > seg.y1 ? a.y - b.y : b.y - a.y));\n           let cx = seg.x1, cy = seg.y1;\n           segGaps.forEach(g => {\n            if (isHoriz) {\n             const gx = g.x;\n             pathD += ` L ${gx - GAP} ${cy} M ${gx + GAP} ${cy}`;\n             cx = gx + GAP;\n            } else {\n             const gy = g.y;\n             pathD += ` L ${cx} ${gy - GAP} M ${cx} ${gy + GAP}`;\n             cy = gy + GAP;\n            }\n           });\n           pathD += ` L ${seg.x2} ${seg.y2}`;\n          });\n         }\n        } else {\n         const midX = (p1.x + p2.x) / 2;\n         const midY = (p1.y + p2.y) / 2;\n         const dx = p2.x - p1.x;\n         const dy = p2.y - p1.y;\n         const len = Math.sqrt(dx * dx + dy * dy) || 1;\n         const perpX = -dy / len;\n         const perpY = dx / len;\n         let offsetAmount = 0;\n         if (pairTotal > 1) {\n          offsetAmount = (pairIndex - (pairTotal - 1) / 2) * 40;\n         }\n         const ctrlX = midX + perpX * offsetAmount;\n         const ctrlY = midY + perpY * offsetAmount;\n         pathD = `M ${p1.x} ${p1.y} Q ${ctrlX} ${ctrlY} ${p2.x} ${p2.y}`;\n        }\n        const path = document.createElementNS(ns, \"path\");\n        path.setAttribute(\"d\", pathD);\n        path.setAttribute(\"fill\", \"none\");\n        path.classList.add(\"edge\");\n        if (edge.type === \"backup\") path.classList.add(\"backup\");\n        path.dataset.edgeId = edge.id;\n        path.dataset.from = edge.from;\n        path.dataset.to = edge.to;\n        path.style.stroke = edge.color;\n        path.style.strokeWidth = edge.width;\n        const edgeDirection = edge.direction || \"none\";\n        const edgeLineStyle = edge.lineStyle || \"solid\";\n        if (edgeLineStyle === \"dashed\") { path.style.strokeDasharray = \"10,5\"; }\n        else if (edgeLineStyle === \"dotted\") { path.style.strokeDasharray = \"2,4\"; }\n        if (edgeDirection === \"forward\") {\n         path.setAttribute(\"marker-end\", \"url(#arrow-forward)\");\n        } else if (edgeDirection === \"backward\") {\n         path.setAttribute(\"marker-start\", \"url(#arrow-backward)\");\n        } else if (edgeDirection === \"both\") {\n         path.setAttribute(\"marker-start\", \"url(#arrow-backward)\");\n         path.setAttribute(\"marker-end\", \"url(#arrow-forward)\");\n        }\n        const animDir = PAGE_STATE.animationDirection || \"all\";\n        const shouldAnimate = (PAGE_STATE.animateConnections && edge.animate !== false || edge.animate === true) && edgeDirection !== \"none\" && (animDir === \"all\" || animDir === edgeDirection);\n        if (shouldAnimate) {\n         path.style.opacity = \"0.25\";\n         const edgeStyle = edge.animationStyle || PAGE_STATE.animationStyle || \"arrows\";\n         const arrowId = edgeStyle === \"dots\" ? \"#flow-arrow-small\" : \"#flow-arrow-big\";\n         const arrowColor = edge.color || PAGE_STATE.defaultEdge || \"#475569\";\n         const arrowCount = 3;\n         const animDuration = edge.animationSpeed ? parseFloat(edge.animationSpeed) : (PAGE_STATE.animationSpeed || 1.5);\n         if (edgeDirection === \"forward\" || edgeDirection === \"both\") {\n          for (let i = 0; i < arrowCount; i++) {\n           const arrow = document.createElementNS(ns, \"use\");\n           arrow.setAttribute(\"href\", arrowId);\n           arrow.style.fill = arrowColor;\n           arrow.style.offsetPath = `path('${pathD}')`;\n           arrow.style.animationDuration = animDuration + \"s\";\n           arrow.style.animationDelay = (i * (animDuration / arrowCount)) + \"s\";\n           arrow.classList.add(\"edge-arrow-forward\");\n           svg.appendChild(arrow);\n          }\n         }\n         if (edgeDirection === \"backward\" || edgeDirection === \"both\") {\n          for (let i = 0; i < arrowCount; i++) {\n           const arrow = document.createElementNS(ns, \"use\");\n           arrow.setAttribute(\"href\", arrowId);\n           arrow.style.fill = arrowColor;\n           arrow.style.offsetPath = `path('${pathD}')`;\n           arrow.style.animationDuration = animDuration + \"s\";\n           arrow.style.animationDelay = ((i * (animDuration / arrowCount)) + (edgeDirection === \"both\" ? animDuration / (arrowCount * 2) : 0)) + \"s\";\n           arrow.classList.add(\"edge-arrow-backward\");\n           svg.appendChild(arrow);\n          }\n         }\n        }\n        const pathHit = document.createElementNS(ns, \"path\");\n        pathHit.setAttribute(\"d\", pathD);\n        pathHit.setAttribute(\"fill\", \"none\");\n        pathHit.style.stroke = \"transparent\";\n        pathHit.style.strokeWidth = \"20\";\n        pathHit.style.cursor = \"pointer\";\n        pathHit.dataset.edgeId = edge.id;\n        pathHit.addEventListener(\"click\", (e) => {\n         e.stopPropagation();\n         selectTheConnection(edge.id);\n        });\n        let pathTouchStart = null;\n        let pathTouchMoved = false;\n        pathHit.addEventListener(\"touchstart\", (e) => {\n         pathTouchStart = Date.now();\n         pathTouchMoved = false;\n         if (e.touches[0]) {\n          pathTouchStartX = e.touches[0].clientX;\n          pathTouchStartY = e.touches[0].clientY;\n         }\n        }, {\n         passive: false\n        });\n        let pathTouchStartX = 0, pathTouchStartY = 0;\n        pathHit.addEventListener(\"touchmove\", (e) => {\n         if (e.touches[0]) {\n          const dx = Math.abs(e.touches[0].clientX - pathTouchStartX);\n          const dy = Math.abs(e.touches[0].clientY - pathTouchStartY);\n          if (dx > 10 || dy > 10) pathTouchMoved = true;\n         }\n        }, {\n         passive: false\n        });\n        pathHit.addEventListener(\"touchend\", (e) => {\n         if (pathTouchStart && !pathTouchMoved && Date.now() - pathTouchStart < 400) {\n          e.stopPropagation();\n          e.preventDefault();\n          selectTheConnection(edge.id);\n         }\n         pathTouchStart = null;\n         pathTouchMoved = false;\n        }, {\n         passive: false\n        });\n        path.addEventListener(\"click\", (e) => {\n         e.stopPropagation();\n         selectTheConnection(edge.id);\n        });\n        if (currentView.mode === \"rack\") {\n         const fromNode = NODE_DATA[edge.from];\n         const toNode = NODE_DATA[edge.to];\n         if (!fromNode || !toNode ||\n             fromNode.assignedRack !== currentView.rackId ||\n             toNode.assignedRack !== currentView.rackId) {\n          return;\n         }\n        }\n        const fromVisible = isNodeVisible(edge.from);\n        const toVisible = isNodeVisible(edge.to);\n        if (!fromVisible || !toVisible) {\n         path.style.opacity = \"0.1\";\n         path.style.pointerEvents = \"none\";\n         pathHit.style.pointerEvents = \"none\";\n        }\n        svg.appendChild(path);\n        svg.appendChild(pathHit);\n        if (edge.fromPort || edge.toPort) {\n         const ns = \"http://www.w3.org/2000/svg\";\n         if (edge.fromPort) {\n          const fromLabel = document.createElementNS(ns, \"text\");\n          fromLabel.textContent = edge.fromPort;\n          fromLabel.setAttribute(\"x\", p1.x);\n          fromLabel.setAttribute(\"y\", p1.y - 10);\n          fromLabel.setAttribute(\"text-anchor\", \"middle\");\n          fromLabel.style.fill = \"#94a3b8\";\n          fromLabel.style.fontSize = \"12px\";\n          fromLabel.style.fontWeight = \"600\";\n          fromLabel.style.pointerEvents = \"none\";\n          fromLabel.classList.add(\"port-label\");\n          svg.appendChild(fromLabel);\n         }\n         if (edge.toPort) {\n          const toLabel = document.createElementNS(ns, \"text\");\n          toLabel.textContent = edge.toPort;\n          toLabel.setAttribute(\"x\", p2.x);\n          toLabel.setAttribute(\"y\", p2.y - 10);\n          toLabel.setAttribute(\"text-anchor\", \"middle\");\n          toLabel.style.fill = \"#94a3b8\";\n          toLabel.style.fontSize = \"12px\";\n          toLabel.style.fontWeight = \"600\";\n          toLabel.style.pointerEvents = \"none\";\n          toLabel.classList.add(\"port-label\");\n          svg.appendChild(toLabel);\n         }\n        }\n       });\n       Object.entries(positions).forEach(([id, pos]) => {\n        const node = NODE_DATA[id];\n        if (!node) return;\n        if (currentView.mode === \"rack\") {\n         if (node.assignedRack !== currentView.rackId) return;\n         const rackUnit = parseInt(node.rackUnit) || 1;\n      const uHeight = parseInt(node.uHeight) || 1;\n      const rackCapacity = getRackCapacity(currentView.rackId);\n      const rackUHeight = getRackUHeight(currentView.rackId);\n      pos.x = RACK_START_X;\n      pos.y = RACK_START_Y + (rackCapacity - rackUnit - uHeight + 1) * rackUHeight + (uHeight * rackUHeight) / 2;\n        } else {\n         if (node.assignedRack) return;\n        }\n        const g = document.createElementNS(\"http://www.w3.org/2000/svg\", \"g\");\n        g.classList.add(\"node-group\");\n        g.dataset.nodeId = id;\n        const nodeRotation = NODE_DATA[id].rotation || 0;\n        g.setAttribute(\"transform\", `translate(${pos.x},${pos.y}) rotate(${nodeRotation})`);\n\t\tlet r = savedSizes[id] || 55;\n\t\tif (!savedSizes[id]) {\n\t\t if (window.innerWidth <= 480) r = 45;\n\t\t else if (window.innerWidth <= 768) r = 50;\n\t\t}\n        const styles = resolveStylesForNode(id);\n        const ns = \"http://www.w3.org/2000/svg\";\n        const hitArea = document.createElementNS(ns, \"circle\");\n        hitArea.setAttribute(\"r\", r * 1.5);\n        hitArea.style.fill = \"transparent\";\n        hitArea.style.stroke = \"none\";\n        hitArea.style.cursor = \"grab\";\n        hitArea.classList.add(\"node-hit-area\");\n        const shapeEl = createNodeShape(id, r);\n        const titleOffsetX = styles.titleOffsetX || 0;\n        const titleOffsetY = styles.titleOffsetY || 0;\n        const subOffsetX = styles.subOffsetX || 0;\n        const subOffsetY = styles.subOffsetY || 0;\n        const label = document.createElementNS(\"http://www.w3.org/2000/svg\", \"text\");\n        label.classList.add(\"node-label\");\n        label.setAttribute(\"x\", titleOffsetX);\n        label.setAttribute(\"y\", -r * 0.28 + titleOffsetY);\n      const labelFontSize = styles.titleSize || PAGE_STATE.nodeTitleSize || r * 0.33;\n        label.style.fontSize = labelFontSize + \"px\";\n        label.textContent = NODE_DATA[id].name;\n      label.style.fill = styles.titleColor || PAGE_STATE.nodeTitle || \"#e2e8f0\";\n        label.style.fontFamily = styles.titleFont || PAGE_STATE.nodeFont || \"Inter, system-ui, sans-serif\";\n        label.style.pointerEvents = \"none\";\n        const sub = document.createElementNS(\"http://www.w3.org/2000/svg\", \"text\");\n        sub.classList.add(\"node-sub\");\n        sub.setAttribute(\"x\", subOffsetX);\n        sub.setAttribute(\"y\", r * 0.4 + subOffsetY);\n      const subFontSize = styles.subSize || PAGE_STATE.nodeSubSize || r * 0.24;\n        sub.style.fontSize = subFontSize + \"px\";\n        sub.textContent = NODE_DATA[id].ip;\n      sub.style.fill = styles.subColor || PAGE_STATE.nodeSub || \"#94a3b8\";\n        sub.style.fontFamily = styles.subFont || PAGE_STATE.nodeFont || \"Inter, system-ui, sans-serif\";\n        sub.style.pointerEvents = \"none\";\nif (hasCoverageZone(node.shape) && node.fovEnabled) {\n  const defaults = getCoverageDefaults(node.shape);\n  const fovAngle = node.fovAngle || defaults.angle;\n  const fovDistance = node.fovDistance || defaults.distance;\n  const fovInnerRadius = node.fovInnerRadius || 0;\n  const fovRotation = node.fovRotation || 0;\n  const fovColor = node.fovColor || \"#f59e0b\";\n  const fovOpacity = node.fovOpacity || 20;\n  const fovGradient = node.fovGradient || false;\n  const fovBorderColor = node.fovBorderColor || \"#f59e0b\";\n  const fovBorderWidth = node.fovBorderWidth ?? 2;\n  const fovBorderStyle = node.fovBorderStyle || \"solid\";\n  const fovBorderOpacity = node.fovBorderOpacity ?? 100;\n  const fovLabel = node.fovLabel || \"\";\n  const fovAnimate = node.fovAnimate || false;\n  const fovAnimationType = node.fovAnimationType || defaults.animationType;\n  const fovSweep = node.fovSweep || 120;\n  const fovSpeed = node.fovSpeed || 4;\n  \n  const fovGroup = document.createElementNS(ns, \"g\");\n  fovGroup.classList.add(\"fov-group\");\n  \n  if (fovGradient) {\n    const gradientId = `fov-gradient-${id}`;\n    const defs = document.createElementNS(ns, \"defs\");\n    const gradient = document.createElementNS(ns, \"radialGradient\");\n    gradient.id = gradientId;\n    gradient.setAttribute(\"cx\", \"0\");\n    gradient.setAttribute(\"cy\", \"0\");\n    gradient.setAttribute(\"r\", fovDistance);\n    gradient.setAttribute(\"gradientUnits\", \"userSpaceOnUse\");\n    const stop1 = document.createElementNS(ns, \"stop\");\n    stop1.setAttribute(\"offset\", fovInnerRadius / fovDistance);\n    stop1.setAttribute(\"stop-color\", fovColor);\n    stop1.setAttribute(\"stop-opacity\", fovOpacity / 100);\n    const stop2 = document.createElementNS(ns, \"stop\");\n    stop2.setAttribute(\"offset\", \"1\");\n    stop2.setAttribute(\"stop-color\", fovColor);\n    stop2.setAttribute(\"stop-opacity\", \"0\");\n    gradient.appendChild(stop1);\n    gradient.appendChild(stop2);\n    defs.appendChild(gradient);\n    fovGroup.appendChild(defs);\n  }\n  \n  const fovPath = document.createElementNS(ns, \"path\");\n  \n  if (fovAngle >= 360) {\n    if (fovInnerRadius > 0) {\n      fovPath.setAttribute(\"d\", `M ${fovDistance},0 A ${fovDistance},${fovDistance} 0 1,1 ${-fovDistance},0 A ${fovDistance},${fovDistance} 0 1,1 ${fovDistance},0 M ${fovInnerRadius},0 A ${fovInnerRadius},${fovInnerRadius} 0 1,0 ${-fovInnerRadius},0 A ${fovInnerRadius},${fovInnerRadius} 0 1,0 ${fovInnerRadius},0`);\n      fovPath.setAttribute(\"fill-rule\", \"evenodd\");\n    } else {\n      fovPath.setAttribute(\"d\", `M 0,0 m -${fovDistance},0 a ${fovDistance},${fovDistance} 0 1,0 ${fovDistance * 2},0 a ${fovDistance},${fovDistance} 0 1,0 -${fovDistance * 2},0`);\n    }\n  } else {\n    const angleRad = (fovAngle * Math.PI) / 180;\n    const rotationRad = ((fovRotation - 90) * Math.PI) / 180;\n    const startAngle = rotationRad - angleRad / 2;\n    const endAngle = rotationRad + angleRad / 2;\n    const x1 = Math.cos(startAngle) * fovDistance;\n    const y1 = Math.sin(startAngle) * fovDistance;\n    const x2 = Math.cos(endAngle) * fovDistance;\n    const y2 = Math.sin(endAngle) * fovDistance;\n    const largeArc = fovAngle > 180 ? 1 : 0;\n    \n    if (fovInnerRadius > 0) {\n      const ix1 = Math.cos(startAngle) * fovInnerRadius;\n      const iy1 = Math.sin(startAngle) * fovInnerRadius;\n      const ix2 = Math.cos(endAngle) * fovInnerRadius;\n      const iy2 = Math.sin(endAngle) * fovInnerRadius;\n      fovPath.setAttribute(\"d\", `M ${ix1},${iy1} L ${x1},${y1} A ${fovDistance},${fovDistance} 0 ${largeArc},1 ${x2},${y2} L ${ix2},${iy2} A ${fovInnerRadius},${fovInnerRadius} 0 ${largeArc},0 ${ix1},${iy1} Z`);\n    } else {\n      fovPath.setAttribute(\"d\", `M 0,0 L ${x1},${y1} A ${fovDistance},${fovDistance} 0 ${largeArc},1 ${x2},${y2} Z`);\n    }\n  }\n  \n  if (fovGradient) {\n    fovPath.style.fill = `url(#fov-gradient-${id})`;\n  } else {\n    const opacityHex = Math.round((fovOpacity / 100) * 255).toString(16).padStart(2, '0');\n    fovPath.style.fill = fovColor + opacityHex;\n  }\n  \n  const borderOpacityHex = Math.round((fovBorderOpacity / 100) * 255).toString(16).padStart(2, '0');\n  fovPath.style.stroke = fovBorderColor + borderOpacityHex;\n  fovPath.style.strokeWidth = fovBorderWidth;\n  if (fovBorderStyle === \"dashed\") {\n    fovPath.style.strokeDasharray = \"10,5\";\n  } else if (fovBorderStyle === \"dotted\") {\n    fovPath.style.strokeDasharray = \"3,3\";\n  }\n  fovPath.style.pointerEvents = \"none\";\n  fovPath.classList.add(\"fov-cone\");\n  \n  fovGroup.appendChild(fovPath);\n  \n  if (fovLabel) {\n    const fovLabelPosition = node.fovLabelPosition || \"center\";\n    const fovLabelSize = node.fovLabelSize || 14;\n    const fovLabelColor = node.fovLabelColor || \"#ffffff\";\n    const fovLabelBold = node.fovLabelBold || false;\n    const fovLabelBg = node.fovLabelBg || false;\n    const fovLabelBgColor = node.fovLabelBgColor || \"#000000\";\n    \n    let labelDistance;\n    if (fovLabelPosition === \"center\") {\n      labelDistance = fovAngle >= 360 ? 0 : fovDistance * 0.4;\n    } else if (fovLabelPosition === \"edge\") {\n      labelDistance = fovDistance * 0.75;\n    } else {\n      labelDistance = fovDistance + fovLabelSize + 8;\n    }\n    \n    const labelAngle = ((fovRotation - 90) * Math.PI) / 180;\n    const labelX = fovAngle >= 360 && fovLabelPosition === \"center\" ? 0 : Math.cos(labelAngle) * labelDistance;\n    const labelY = fovAngle >= 360 && fovLabelPosition === \"center\" ? 0 : Math.sin(labelAngle) * labelDistance;\n    \n    if (fovLabelBg) {\n      const bgRect = document.createElementNS(ns, \"rect\");\n      const textWidth = fovLabel.length * fovLabelSize * 0.6;\n      const textHeight = fovLabelSize * 1.4;\n      bgRect.setAttribute(\"x\", labelX - textWidth / 2 - 6);\n      bgRect.setAttribute(\"y\", labelY - textHeight / 2);\n      bgRect.setAttribute(\"width\", textWidth + 12);\n      bgRect.setAttribute(\"height\", textHeight);\n      bgRect.setAttribute(\"rx\", \"4\");\n      bgRect.style.fill = fovLabelBgColor;\n      bgRect.style.opacity = \"0.8\";\n      bgRect.style.pointerEvents = \"none\";\n      fovGroup.appendChild(bgRect);\n    }\n    \n    const labelEl = document.createElementNS(ns, \"text\");\n    labelEl.setAttribute(\"x\", labelX);\n    labelEl.setAttribute(\"y\", labelY);\n    labelEl.setAttribute(\"text-anchor\", \"middle\");\n    labelEl.setAttribute(\"dominant-baseline\", \"middle\");\n    labelEl.style.fill = fovLabelColor;\n    labelEl.style.fontSize = fovLabelSize + \"px\";\n    labelEl.style.fontWeight = fovLabelBold ? \"bold\" : \"normal\";\n    labelEl.style.fontFamily = \"system-ui, sans-serif\";\n    labelEl.style.pointerEvents = \"none\";\n    labelEl.textContent = fovLabel;\n    fovGroup.appendChild(labelEl);\n  }\n  \n  if (fovAnimate) {\n    const animationName = `fov-anim-${id}`;\n    const styleEl = document.createElementNS(\"http://www.w3.org/1999/xhtml\", \"style\");\n    \n    if (fovAnimationType === \"sweep\" && fovAngle < 360) {\n      styleEl.textContent = `\n        @keyframes ${animationName} {\n          0%, 100% { transform: rotate(0deg); }\n          50% { transform: rotate(${fovSweep}deg); }\n        }\n      `;\n      fovGroup.style.animation = `${animationName} ${fovSpeed}s ease-in-out infinite`;\n      fovGroup.style.transformOrigin = \"0 0\";\n    } else if (fovAnimationType === \"pulse\") {\n      styleEl.textContent = `\n        @keyframes ${animationName} {\n          0%, 100% { transform: scale(1); opacity: 1; }\n          50% { transform: scale(1.1); opacity: 0.7; }\n        }\n      `;\n      fovGroup.style.animation = `${animationName} ${fovSpeed}s ease-in-out infinite`;\n      fovGroup.style.transformOrigin = \"0 0\";\n    } else if (fovAnimationType === \"rings\") {\n      for (let i = 1; i <= 3; i++) {\n        const ring = document.createElementNS(ns, \"circle\");\n        ring.setAttribute(\"cx\", \"0\");\n        ring.setAttribute(\"cy\", \"0\");\n        ring.setAttribute(\"r\", fovDistance * 0.3 * i);\n        ring.style.fill = \"none\";\n        ring.style.stroke = fovBorderColor;\n        ring.style.strokeWidth = \"2\";\n        ring.style.opacity = \"0\";\n        const ringAnimName = `${animationName}-ring-${i}`;\n        const ringStyle = document.createElementNS(\"http://www.w3.org/1999/xhtml\", \"style\");\n        ringStyle.textContent = `\n          @keyframes ${ringAnimName} {\n            0% { r: ${fovInnerRadius || 10}; opacity: 0.8; }\n            100% { r: ${fovDistance}; opacity: 0; }\n          }\n        `;\n        ring.style.animation = `${ringAnimName} ${fovSpeed}s ease-out infinite`;\n        ring.style.animationDelay = `${(i - 1) * (fovSpeed / 3)}s`;\n        fovGroup.appendChild(ringStyle);\n        fovGroup.appendChild(ring);\n      }\n    } else if (fovAnimationType === \"spin\") {\n      styleEl.textContent = `\n        @keyframes ${animationName} {\n          0% { transform: rotate(0deg); }\n          100% { transform: rotate(360deg); }\n        }\n      `;\n      fovGroup.style.animation = `${animationName} ${fovSpeed}s linear infinite`;\n      fovGroup.style.transformOrigin = \"0 0\";\n    }\n    \n    if (fovAnimationType !== \"rings\") {\n      fovGroup.appendChild(styleEl);\n      const elapsedSeconds = (Date.now() - FOV_ANIMATION_START) / 1000;\n      const animationOffset = elapsedSeconds % fovSpeed;\n      fovGroup.style.animationDelay = `-${animationOffset}s`;\n    }\n  }\n  \n  g.appendChild(fovGroup);\n}\n        g.append(hitArea, shapeEl, label, sub);\n        if (NODE_DATA[id]?.locked) {\n          const lockIcon = document.createElementNS(ns, \"text\");\n          lockIcon.textContent = \"🔒\";\n          lockIcon.setAttribute(\"x\", r * 0.6);\n          lockIcon.setAttribute(\"y\", -r * 0.6);\n          lockIcon.style.fontSize = (r * 0.4) + \"px\";\n          lockIcon.style.pointerEvents = \"none\";\n          lockIcon.style.userSelect = \"none\";\n          lockIcon.classList.add(\"lock-indicator\");\n          g.appendChild(lockIcon);\n        }\n        if (NODE_DATA[id]?.groupId) {\n          const groupIndicator = document.createElementNS(ns, \"circle\");\n          groupIndicator.setAttribute(\"r\", r + 4);\n          groupIndicator.style.fill = \"none\";\n      groupIndicator.style.stroke = PAGE_STATE.groupIndicator || \"#4fd1c5\";\n          groupIndicator.style.strokeWidth = \"3\";\n          groupIndicator.style.strokeDasharray = \"5,5\";\n          groupIndicator.style.pointerEvents = \"none\";\n          groupIndicator.classList.add(\"group-indicator\");\n          g.insertBefore(groupIndicator, g.firstChild);\n        }\n        let isDragging = false;\n        let startX, startY;\n        let initialPositions = {};\n        let longPressTimer = null;\n        let longPressTriggered = false;\n        g.addEventListener(\"contextmenu\", (e) => {\n         e.preventDefault();\n         e.stopPropagation();\n         if (isViewOnly()) return;\n         if (selectedNodes.has(id)) {\n          selectedNodes.delete(id);\n         } else {\n          selectedNodes.add(id);\n         }\n         updateNodeSelection();\n         return false;\n        });\n        g.addEventListener(\"touchstart\", (e) => {\n         if (NODE_DATA[id].isRack) {\n          const touch = e.touches[0];\n          longPressStartX = touch.clientX;\n          longPressStartY = touch.clientY;\n          longPressTimer = setTimeout(() => {\n           longPressTriggered = true;\n           if (navigator.vibrate) {\n            navigator.vibrate(100);\n           }\n           enterRack(id);\n          }, 500);\n         }\n        }, { passive: true });\n        let lastTapTime = 0;\n        let lastTapNode = null;\n        g.addEventListener(\"touchend\", (e) => {\n         const currentTime = new Date().getTime();\n         const tapLength = currentTime - lastTapTime;\n         if (tapLength < 300 && tapLength > 0 && lastTapNode === id) {\n          e.preventDefault();\n          e.stopPropagation();\n          if (isViewOnly()) return;\n          if (selectedNodes.has(id)) {\n           selectedNodes.delete(id);\n          } else {\n           selectedNodes.add(id);\n          }\n          updateNodeSelection();\n          if (navigator.vibrate) {\n           navigator.vibrate(50);\n          }\n          lastTapTime = 0;\n          lastTapNode = null;\n         } else {\n          lastTapTime = currentTime;\n          lastTapNode = id;\n         }\n        });\n        g.addEventListener(\"touchend\", (e) => {\n         if (longPressTimer) {\n          clearTimeout(longPressTimer);\n          longPressTimer = null;\n         }\n         if (longPressTriggered) {\n          e.preventDefault();\n          e.stopPropagation();\n          longPressTriggered = false;\n         }\n        });\n        let longPressStartX = 0;\n        let longPressStartY = 0;\n        g.addEventListener(\"touchmove\", (e) => {\n         if (longPressTimer) {\n          const touch = e.touches[0];\n          const dx = Math.abs(touch.clientX - longPressStartX);\n          const dy = Math.abs(touch.clientY - longPressStartY);\n          if (dx > 15 || dy > 15) {\n           clearTimeout(longPressTimer);\n           longPressTimer = null;\n           longPressTriggered = false;\n          }\n         }\n        }, { passive: true });\n        g.addEventListener(\"mousedown\", (e) => {\n\t\t if (isViewOnly()) return;\n         if (e.button === 2) {\n          return;\n         }\n         if (NODE_DATA[id]?.locked) {\n          return;\n         }\n         e.preventDefault();\n         isDragging = true;\n         const svgEl = document.getElementById(\"map\");\n         const pt = svgEl.createSVGPoint();\n         pt.x = e.clientX;\n         pt.y = e.clientY;\n         const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n         startX = svgP.x;\n         startY = svgP.y;\n      if (selectedNodes.has(id)) {\n      initialPositions = {};\n      const allSelectedRects = Array.from(selectedRects);\n      const allSelectedTexts = Array.from(selectedTexts);\n      const allSelectedEdges = Array.from(selectedEdges).map(eid => EDGE_DATA.list.find(e => e.id === eid)).filter(Boolean);\n      selectedNodes.forEach(nodeId => {\n      const nodePos = savedPositions[nodeId];\n      if (nodePos) {\n      initialPositions[nodeId] = { x: nodePos.x, y: nodePos.y };\n      }\n      });\n      Array.from(selectedRects).forEach(rectId => {\n        const rect = RECT_DATA.list.find(r => r.id === rectId);\n        if (rect) { rect._dragStartX = rect.x; rect._dragStartY = rect.y; }\n      });\n      Array.from(selectedTexts).forEach(textId => {\n        const text = TEXT_DATA.list.find(t => t.id === textId);\n        if (text) { text._dragStartX = text.x; text._dragStartY = text.y; }\n      });\n      Array.from(selectedEdges).forEach(edgeId => {\n        const edge = EDGE_DATA.list.find(e => e.id === edgeId);\n        if (edge && edge.points) { edge._dragStartPoints = edge.points.map(p => ({x: p.x, y: p.y})); }\n      });\n      } else {\n      initialPositions = { [id]: { x: pos.x, y: pos.y } };\n      }\n      if (currentView.mode === \"rack\") {\n      initialPositions[id] = { x: pos.x, y: pos.y };\n      }\n         g.style.cursor = \"grabbing\";\n         hitArea.style.cursor = \"grabbing\";\n         e.stopPropagation();\n        });\n        const handleMouseMove = (e) => {\n         if (!isDragging) return;\n         e.preventDefault();\n         const svgEl = document.getElementById(\"map\");\n         const pt = svgEl.createSVGPoint();\n         pt.x = e.clientX;\n         pt.y = e.clientY;\n         const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n         const dx = svgP.x - startX;\n         const dy = svgP.y - startY;\n         let nodesToMove = selectedNodes.has(id) ? Array.from(selectedNodes) : [id];\n         const groupIds = new Set();\n         nodesToMove.forEach(nodeId => {\n          const groupId = NODE_DATA[nodeId]?.groupId;\n          if (groupId) {\n            groupIds.add(groupId);\n          }\n         });\n         if (groupIds.size > 0) {\n          Object.keys(NODE_DATA).forEach(nodeId => {\n            const nodeGroupId = NODE_DATA[nodeId]?.groupId;\n            if (nodeGroupId && groupIds.has(nodeGroupId)) {\n              if (!nodesToMove.includes(nodeId)) {\n                nodesToMove.push(nodeId);\n                if (!initialPositions[nodeId]) {\n                  const nodePos = savedPositions[nodeId];\n                  if (nodePos) {\n                    initialPositions[nodeId] = { x: nodePos.x, y: nodePos.y };\n                  }\n                }\n              }\n            }\n          });\n         }\n         nodesToMove = nodesToMove.filter(nodeId => !NODE_DATA[nodeId]?.locked);\n         nodesToMove.forEach(nodeId => {\n          if (!initialPositions[nodeId]) return;\n          const initialPos = initialPositions[nodeId];\n          let newX = initialPos.x + dx;\n          let newY = initialPos.y + dy;\n          const nodeSize = savedSizes[nodeId] || 55;\n          newX = Math.max(nodeSize + CANVAS_PADDING, Math.min(CANVAS_WIDTH - nodeSize - CANVAS_PADDING, newX));\n          newY = Math.max(nodeSize + CANVAS_PADDING, Math.min(CANVAS_HEIGHT - nodeSize - CANVAS_PADDING, newY));\n          savedPositions[nodeId] = { x: newX, y: newY };\n          positions[nodeId] = { x: newX, y: newY };\n          if (nodeId === id) {\n           pos.x = newX;\n           pos.y = newY;\n          }\n          const nodeGroup = document.querySelector(`g[data-node-id=\"${nodeId}\"]`);\n          if (nodeGroup) {\n           nodeGroup.setAttribute(\"transform\", `translate(${newX},${newY})`);\n          }\n         });\n         Array.from(selectedRects).forEach(rectId => {\n           const rect = RECT_DATA.list.find(r => r.id === rectId);\n           if (rect && rect._dragStartX !== undefined) {\n             rect.x = rect._dragStartX + dx;\n             rect.y = rect._dragStartY + dy;\n           }\n         });\n         Array.from(selectedTexts).forEach(textId => {\n           const text = TEXT_DATA.list.find(t => t.id === textId);\n           if (text && text._dragStartX !== undefined) {\n             text.x = text._dragStartX + dx;\n             text.y = text._dragStartY + dy;\n           }\n         });\n         Array.from(selectedEdges).forEach(edgeId => {\n           const edge = EDGE_DATA.list.find(e => e.id === edgeId);\n           if (edge && edge._dragStartPoints) {\n             edge.points.forEach((p, i) => {\n               p.x = edge._dragStartPoints[i].x + dx;\n               p.y = edge._dragStartPoints[i].y + dy;\n             });\n           }\n         });\n         forgeTheTopology();\n         updateMinimap();\n         document.querySelectorAll(\".edge\").forEach((edgeEl) => {\n          const fromId = edgeEl.dataset.from;\n          const toId = edgeEl.dataset.to;\n          if (nodesToMove.includes(fromId) || nodesToMove.includes(toId)) {\n           const p1 = savedPositions[fromId] || positions[fromId] || {\n            x: 600,\n            y: 350\n           };\n           const p2 = savedPositions[toId] || positions[toId] || {\n            x: 600,\n            y: 350\n           };\n           if (edgeEl.tagName === \"line\") {\n            edgeEl.setAttribute(\"x1\", p1.x);\n            edgeEl.setAttribute(\"y1\", p1.y);\n            edgeEl.setAttribute(\"x2\", p2.x);\n            edgeEl.setAttribute(\"y2\", p2.y);\n           } else if (edgeEl.tagName === \"path\") {\n            const edgeId = edgeEl.dataset.edgeId;\n            const edge = EDGE_DATA.list.find(\n             (e) => e.id === edgeId);\n            if (edge) {\n             const pairTotal = edge._pairTotal || 1;\n             const pairIndex = edge._pairIndex || 0;\n             const midX = (p1.x + p2.x) / 2;\n             const midY = (p1.y + p2.y) / 2;\n             const dx = p2.x - p1.x;\n             const dy = p2.y - p1.y;\n             const len = Math.sqrt(dx * dx + dy * dy) || 1;\n             const perpX = -dy / len;\n             const perpY = dx / len;\n             let offsetAmount = 0;\n             if (pairTotal > 1) {\n              offsetAmount = (pairIndex - (pairTotal - 1) / 2) * 40;\n             }\n             const ctrlX = midX + perpX * offsetAmount;\n             const ctrlY = midY + perpY * offsetAmount;\n             edgeEl.setAttribute(\"d\", `M ${p1.x} ${p1.y} Q ${ctrlX} ${ctrlY} ${p2.x} ${p2.y}`);\n            }\n           }\n          }\n         });\n        };\n      const handleMouseUp = () => {\n      if (isDragging) {\n      isDragging = false;\n      g.style.cursor = \"grab\";\n      hitArea.style.cursor = \"grab\";\n      if (currentView.mode === \"rack\" && NODE_DATA[id]?.assignedRack === currentView.rackId) {\n      const draggedY = savedPositions[id]?.y || pos.y;\n      const rackCapacity = getRackCapacity(currentView.rackId);\n      const rackUHeight = getRackUHeight(currentView.rackId);\n      let newUnit = rackCapacity - Math.round((draggedY - RACK_START_Y) / rackUHeight);\n      newUnit = Math.max(1, Math.min(newUnit, rackCapacity));\n      NODE_DATA[id].rackUnit = newUnit;\n      forgeTheTopology();\n      claimTheImmortal(id);\n      }\n      }\n      };\n        document.addEventListener(\"mousemove\", handleMouseMove);\n        document.addEventListener(\"mouseup\", handleMouseUp);\n        let touchStartTime = 0;\n        let touchStartX = 0;\n        let touchStartY = 0;\n        let touchMoved = false;\n        g.addEventListener(\"touchstart\",\n         (e) => {\n          if (isViewOnly()) {\n           touchStartTime = Date.now();\n           touchMoved = false;\n           e.stopPropagation();\n           return;\n          }\n          if (NODE_DATA[id]?.locked) {\n           return;\n          }\n          e.preventDefault();\n          touchStartTime = Date.now();\n          touchMoved = false;\n          const svgEl = document.getElementById(\"map\");\n          const pt = svgEl.createSVGPoint();\n          const touch = e.touches[0];\n          pt.x = touch.clientX;\n          pt.y = touch.clientY;\n          const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n          touchStartX = svgP.x;\n          touchStartY = svgP.y;\n          startX = svgP.x;\n          startY = svgP.y;\n          if (selectedNodes.has(id)) {\n           initialPositions = {};\n           selectedNodes.forEach(nodeId => {\n            const nodePos = savedPositions[nodeId];\n            if (nodePos) {\n             initialPositions[nodeId] = { x: nodePos.x, y: nodePos.y };\n            }\n           });\n          } else {\n           initialPositions = { [id]: { x: pos.x, y: pos.y } };\n          }\n          e.stopPropagation();\n         }, {\n          passive: false\n         });\n        g.addEventListener(\"touchmove\", (e) => {\n\t\tif (isViewOnly()) return;\n         e.preventDefault();\n         const svgEl = document.getElementById(\"map\");\n         const pt = svgEl.createSVGPoint();\n         const touch = e.touches[0];\n         pt.x = touch.clientX;\n         pt.y = touch.clientY;\n         const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n         const dx = Math.abs(svgP.x - touchStartX);\n         const dy = Math.abs(svgP.y - touchStartY);\n         if (dx > (isMobileDevice() ? 4 : 10) || dy > (isMobileDevice() ? 4 : 10)) {\n      touchMoved = true;\n      isDragging = true;\n      }\n         if (!isDragging) return;\n         const deltaX = svgP.x - startX;\n         const deltaY = svgP.y - startY;\n         let nodesToMove = selectedNodes.has(id) ? Array.from(selectedNodes) : [id];\n         const groupIds = new Set();\n         nodesToMove.forEach(nodeId => {\n          const groupId = NODE_DATA[nodeId]?.groupId;\n          if (groupId) {\n            groupIds.add(groupId);\n          }\n         });\n         if (groupIds.size > 0) {\n          Object.keys(NODE_DATA).forEach(nodeId => {\n            const nodeGroupId = NODE_DATA[nodeId]?.groupId;\n            if (nodeGroupId && groupIds.has(nodeGroupId)) {\n              if (!nodesToMove.includes(nodeId)) {\n                nodesToMove.push(nodeId);\n                if (!initialPositions[nodeId]) {\n                  const nodePos = savedPositions[nodeId];\n                  if (nodePos) {\n                    initialPositions[nodeId] = { x: nodePos.x, y: nodePos.y };\n                  }\n                }\n              }\n            }\n          });\n         }\n         nodesToMove = nodesToMove.filter(nodeId => !NODE_DATA[nodeId]?.locked);\n         nodesToMove.forEach(nodeId => {\n          if (!initialPositions[nodeId]) return;\n          const initialPos = initialPositions[nodeId];\n          let newX = initialPos.x + deltaX;\n          let newY = initialPos.y + deltaY;\n          const nodeSize = savedSizes[nodeId] || 55;\n          newX = Math.max(nodeSize + CANVAS_PADDING, Math.min(CANVAS_WIDTH - nodeSize - CANVAS_PADDING, newX));\n          newY = Math.max(nodeSize + CANVAS_PADDING, Math.min(CANVAS_HEIGHT - nodeSize - CANVAS_PADDING, newY));\n          savedPositions[nodeId] = { x: newX, y: newY };\n          positions[nodeId] = { x: newX, y: newY };\n          if (nodeId === id) {\n           pos.x = newX;\n           pos.y = newY;\n          }\n          const nodeGroup = document.querySelector(`g[data-node-id=\"${nodeId}\"]`);\n          if (nodeGroup) {\n           nodeGroup.setAttribute(\"transform\", `translate(${newX},${newY})`);\n          }\n         });\n         updateMinimap();\n         document.querySelectorAll(\".edge\").forEach((edgeEl) => {\n          const fromId = edgeEl.dataset.from;\n          const toId = edgeEl.dataset.to;\n          if (nodesToMove.includes(fromId) || nodesToMove.includes(toId)) {\n           const p1 = savedPositions[fromId] || positions[fromId] || {\n            x: 600,\n            y: 350\n           };\n           const p2 = savedPositions[toId] || positions[toId] || {\n            x: 600,\n            y: 350\n           };\n           if (edgeEl.tagName === \"line\") {\n            edgeEl.setAttribute(\"x1\", p1.x);\n            edgeEl.setAttribute(\"y1\", p1.y);\n            edgeEl.setAttribute(\"x2\", p2.x);\n            edgeEl.setAttribute(\"y2\", p2.y);\n           } else if (edgeEl.tagName === \"path\") {\n            const edgeId = edgeEl.dataset.edgeId;\n            const edge = EDGE_DATA.list.find(\n             (e) => e.id === edgeId);\n            if (edge) {\n             const pairTotal = edge._pairTotal || 1;\n             const pairIndex = edge._pairIndex || 0;\n             const midX = (p1.x + p2.x) / 2;\n             const midY = (p1.y + p2.y) / 2;\n             const dx = p2.x - p1.x;\n             const dy = p2.y - p1.y;\n             const len = Math.sqrt(dx * dx + dy * dy) || 1;\n             const perpX = -dy / len;\n             const perpY = dx / len;\n             let offsetAmount = 0;\n             if (pairTotal > 1) {\n              offsetAmount = (pairIndex - (pairTotal - 1) / 2) * 40;\n             }\n             const ctrlX = midX + perpX * offsetAmount;\n             const ctrlY = midY + perpY * offsetAmount;\n             edgeEl.setAttribute(\"d\", `M ${p1.x} ${p1.y} Q ${ctrlX} ${ctrlY} ${p2.x} ${p2.y}`);\n            }\n           }\n          }\n         });\n        }, {\n         passive: false\n        });\n      g.addEventListener(\"touchend\", (e) => {\n      const touchDuration = Date.now() - touchStartTime;\n      if (!touchMoved && touchDuration < 400) {\n       if (isViewOnly()) {\n        handleViewOnlyClick(id, 'node');\n        return;\n       }\n       claimTheImmortal(id);\n      }\n      if (isDragging) {\n      const draggedY = pos.y;\n      isDragging = false;\n      savedPositions[id] = {\n      x: pos.x,\n      y: pos.y\n      };\n      if (currentView.mode === \"rack\" && NODE_DATA[id]?.assignedRack === currentView.rackId) {\n      const rackCapacity = getRackCapacity(currentView.rackId);\n      const rackUHeight = getRackUHeight(currentView.rackId);\n      let newUnit = rackCapacity - Math.round((draggedY - RACK_START_Y) / rackUHeight);\n      newUnit = Math.max(1, Math.min(newUnit, rackCapacity));\n      NODE_DATA[id].rackUnit = newUnit;\n      forgeTheTopology();\n      claimTheImmortal(id);\n      }\n      }\n      touchMoved = false;\n      });\n        g.style.cursor = \"grab\";\n        g.addEventListener(\"click\", (e) => {\n         if (!isDragging) {\n          if (isViewOnly()) {\n           handleViewOnlyClick(id, 'node');\n           return;\n          }\n          claimTheImmortal(id);\n         }\n        });\n        g.addEventListener(\"dblclick\", (e) => {\n         e.preventDefault();\n         e.stopPropagation();\n         if (NODE_DATA[id].isRack) {\n          enterRack(id);\n         }\n        });\n        if (!isNodeVisible(id)) {\n         g.style.opacity = \"0.1\";\n         g.style.pointerEvents = \"none\";\n        }\n        svg.appendChild(g);\n       });\n        if (RECT_DATA && RECT_DATA.list) {\n         RECT_DATA.list.forEach((rect) => {\n          if (currentView.mode === \"rack\") return;\n          if (rect.style === \"outlined\") {\n           const g = document.createElementNS(ns, \"g\");\n           g.classList.add(\"rect-group\");\n           g.dataset.rectId = rect.id;\n           const rectCenterX = rect.x + rect.width / 2;\n           const rectCenterY = rect.y + rect.height / 2;\n           const rectRotation = rect.rotation || 0;\n           if (rectRotation !== 0) {\n             g.setAttribute(\"transform\", `rotate(${rectRotation}, ${rectCenterX}, ${rectCenterY})`);\n           }\n           const rectEl = document.createElementNS(ns, \"rect\");\n           rectEl.classList.add(\"rect-shape\");\n           rectEl.setAttribute(\"x\", rect.x);\n           rectEl.setAttribute(\"y\", rect.y);\n           rectEl.setAttribute(\"width\", rect.width);\n           rectEl.setAttribute(\"height\", rect.height);\n           rectEl.style.fill = \"none\";\n           rectEl.style.stroke = rect.borderColor || rect.color;\n           rectEl.style.strokeWidth = (rect.borderWidth !== undefined ? rect.borderWidth : 3) + \"px\";\n           if (rect.lineStyle === \"dashed\") { rectEl.style.strokeDasharray = \"10,5\"; }\nelse if (rect.lineStyle === \"dotted\") { rectEl.style.strokeDasharray = \"2,4\"; }\nelse if (rect.lineStyle === \"wall\") {\n  rectEl.style.fill = rect.color;\n  rectEl.style.fillOpacity = \"0.5\";\n  rectEl.style.stroke = rect.borderColor || rect.color;\n  rectEl.style.strokeWidth = (rect.borderWidth !== undefined ? rect.borderWidth : 2) + \"px\";\n  const hatchGroup = document.createElementNS(ns, \"g\");\n  hatchGroup.classList.add(\"wall-hatch-lines\");\n  hatchGroup.style.pointerEvents = \"none\";\n  const spacing = 12;\n  const hatchColor = rect.borderColor || rect.color;\n  for (let i = -rect.height; i < rect.width + rect.height; i += spacing) {\n    const line = document.createElementNS(ns, \"line\");\n    line.setAttribute(\"x1\", rect.x + i);\n    line.setAttribute(\"y1\", rect.y);\n    line.setAttribute(\"x2\", rect.x + i - rect.height);\n    line.setAttribute(\"y2\", rect.y + rect.height);\n    line.style.stroke = hatchColor;\n    line.style.strokeWidth = \"2\";\n    hatchGroup.appendChild(line);\n  }\n  const clipId = \"clip-\" + rect.id;\n  const clipPath = document.createElementNS(ns, \"clipPath\");\n  clipPath.id = clipId;\n  const clipRect = document.createElementNS(ns, \"rect\");\n  clipRect.setAttribute(\"x\", rect.x);\n  clipRect.setAttribute(\"y\", rect.y);\n  clipRect.setAttribute(\"width\", rect.width);\n  clipRect.setAttribute(\"height\", rect.height);\n  clipPath.appendChild(clipRect);\n  defs.appendChild(clipPath);\n  hatchGroup.setAttribute(\"clip-path\", \"url(#\" + clipId + \")\");\n  g.appendChild(hatchGroup);\n}\n           else { rectEl.style.strokeDasharray = \"none\"; }\n           rectEl.style.cursor = \"move\";\n           rectEl.addEventListener(\"click\", (e) => {\n             if (rectDrawMode) return;\n             e.stopPropagation();\n             currentRectId = rect.id;\n             selectTheRect(rect.id);\n           });\n           rectEl.addEventListener(\"contextmenu\", (e) => {\n\t\t   if (isViewOnly()) return;\n             e.preventDefault();\n             e.stopPropagation();\n             if (selectedRects.has(rect.id)) {\n               selectedRects.delete(rect.id);\n             } else {\n               selectedRects.add(rect.id);\n             }\n             updateAllSelections();\n           });\n           let rectLastTap = 0;\n           rectEl.addEventListener(\"touchend\", (e) => {\n             const now = Date.now();\n             if (now - rectLastTap < 300) {\n               e.preventDefault();\n               if (selectedRects.has(rect.id)) {\n                 selectedRects.delete(rect.id);\n               } else {\n                 selectedRects.add(rect.id);\n               }\n               updateAllSelections();\n               if (navigator.vibrate) navigator.vibrate(50);\n               rectLastTap = 0;\n             } else {\n               rectLastTap = now;\n             }\n           }, { passive: false });\n           const deleteBtn = document.createElementNS(ns, \"g\");\n           deleteBtn.classList.add(\"rect-delete-btn\");\n           deleteBtn.style.cursor = \"pointer\";\n           deleteBtn.style.display = rectDrawMode ? \"block\" : \"none\";\n           const deleteBg = document.createElementNS(ns, \"circle\");\n           deleteBg.setAttribute(\"cx\", rect.x + rect.width - 10);\n           deleteBg.setAttribute(\"cy\", rect.y + 10);\n           deleteBg.setAttribute(\"r\", 12);\n           deleteBg.style.fill = \"#f56565\";\n           deleteBg.style.stroke = \"white\";\n           deleteBg.style.strokeWidth = \"2\";\n           const deleteX = document.createElementNS(ns, \"text\");\n           deleteX.setAttribute(\"x\", rect.x + rect.width - 10);\n           deleteX.setAttribute(\"y\", rect.y + 10);\n           deleteX.setAttribute(\"text-anchor\", \"middle\");\n           deleteX.setAttribute(\"dominant-baseline\", \"middle\");\n           deleteX.style.fill = \"white\";\n           deleteX.style.fontSize = \"16px\";\n           deleteX.style.fontWeight = \"bold\";\n           deleteX.style.pointerEvents = \"none\";\n           deleteX.textContent = \"×\";\n           deleteBtn.appendChild(deleteBg);\n           deleteBtn.appendChild(deleteX);\n           deleteBtn.addEventListener(\"click\", (e) => {\n            e.stopPropagation();\n            e.preventDefault();\n            deleteRectangle(rect.id);\n           });\n           let isDragging = false;\n           let dragStartX, dragStartY;\n           let rectStartX, rectStartY;\n           rectEl.addEventListener(\"mousedown\", (e) => {\n\t\t   if (isViewOnly()) return;\n      if (rectDrawMode) return;\n      e.preventDefault();\n      e.stopPropagation();\n      isDragging = true;\n      dragStartX = e.clientX;\n      dragStartY = e.clientY;\n      rectStartX = rect.x;\n      rectStartY = rect.y;\n      rectEl.style.cursor = \"grabbing\";\n      if (selectedRects.has(rect.id)) {\n      initialPositions = {};\n      selectedNodes.forEach(nodeId => {\n      const nodePos = savedPositions[nodeId];\n      if (nodePos) initialPositions[nodeId] = { x: nodePos.x, y: nodePos.y };\n      });\n      selectedRects.forEach(rectId => {\n      const r = RECT_DATA.list.find(x => x.id === rectId);\n      if (r) { r._dragStartX = r.x; r._dragStartY = r.y; }\n      });\n      selectedTexts.forEach(textId => {\n      const t = TEXT_DATA.list.find(x => x.id === textId);\n      if (t) { t._dragStartX = t.x; t._dragStartY = t.y; }\n      });\n      selectedEdges.forEach(edgeId => {\n      const ed = EDGE_DATA.list.find(x => x.id === edgeId);\n      if (ed && ed.points) { ed._dragStartPoints = ed.points.map(p => ({x: p.x, y: p.y})); }\n      });\n      }\n      });\n           const moveHandler = (e) => {\n      if (!isDragging || rectDrawMode) return;\n      const svgEl = svg;\n      const pt1 = svgEl.createSVGPoint();\n      pt1.x = dragStartX;\n      pt1.y = dragStartY;\n      const svgP1 = pt1.matrixTransform(svgEl.getScreenCTM().inverse());\n      const pt2 = svgEl.createSVGPoint();\n      pt2.x = e.clientX;\n      pt2.y = e.clientY;\n      const svgP2 = pt2.matrixTransform(svgEl.getScreenCTM().inverse());\n      const dx = svgP2.x - svgP1.x;\n      const dy = svgP2.y - svgP1.y;\n      rect.x = rectStartX + dx;\n      rect.y = rectStartY + dy;\n      if (selectedRects.has(rect.id)) {\n      selectedNodes.forEach(nodeId => {\n      if (initialPositions[nodeId]) {\n       const newX = initialPositions[nodeId].x + dx;\n       const newY = initialPositions[nodeId].y + dy;\n       savedPositions[nodeId] = { x: newX, y: newY };\n      }\n      });\n      selectedRects.forEach(rectId => {\n      if (rectId === rect.id) return;\n      const r = RECT_DATA.list.find(x => x.id === rectId);\n      if (r && r._dragStartX !== undefined) { r.x = r._dragStartX + dx; r.y = r._dragStartY + dy; }\n      });\n      selectedTexts.forEach(textId => {\n      const t = TEXT_DATA.list.find(x => x.id === textId);\n      if (t && t._dragStartX !== undefined) { t.x = t._dragStartX + dx; t.y = t._dragStartY + dy; }\n      });\n      selectedEdges.forEach(edgeId => {\n      const ed = EDGE_DATA.list.find(x => x.id === edgeId);\n      if (ed && ed._dragStartPoints) { ed.points.forEach((p, i) => { p.x = ed._dragStartPoints[i].x + dx; p.y = ed._dragStartPoints[i].y + dy; }); }\n      });\n      }\n      forgeTheTopology();\n      };\n           const upHandler = () => {\n            if (isDragging) {\n             isDragging = false;\n             rectEl.style.cursor = \"move\";\n            }\n           };\n           document.addEventListener(\"mousemove\", moveHandler);\n           document.addEventListener(\"mouseup\", upHandler);\n           let touchStartX, touchStartY;\n           let touchRectStartX, touchRectStartY;\n           rectEl.addEventListener(\"touchstart\", (e) => {\n\t\t   if (isViewOnly()) return;\n            if (rectDrawMode) return;\n            e.preventDefault();\n            e.stopPropagation();\n            isDragging = true;\n            const touch = e.touches[0];\n            touchStartX = touch.clientX;\n            touchStartY = touch.clientY;\n            touchRectStartX = rect.x;\n            touchRectStartY = rect.y;\n            rectEl.style.cursor = \"grabbing\";\n           }, { passive: false });\n           rectEl.addEventListener(\"touchmove\", (e) => {\n\t\t    if (isViewOnly()) return;\n            if (!isDragging || rectDrawMode) return;\n            if (!e.touches[0]) return;\n            e.preventDefault();\n            e.stopPropagation();\n            const svgEl = svg;\n            const pt1 = svgEl.createSVGPoint();\n            pt1.x = touchStartX;\n            pt1.y = touchStartY;\n            const svgP1 = pt1.matrixTransform(svgEl.getScreenCTM().inverse());\n            const touch = e.touches[0];\n            const pt2 = svgEl.createSVGPoint();\n            pt2.x = touch.clientX;\n            pt2.y = touch.clientY;\n            const svgP2 = pt2.matrixTransform(svgEl.getScreenCTM().inverse());\n            const dx = svgP2.x - svgP1.x;\n            const dy = svgP2.y - svgP1.y;\n            rect.x = touchRectStartX + dx;\n            rect.y = touchRectStartY + dy;\n            forgeTheTopology();\n           }, { passive: false });\n           rectEl.addEventListener(\"touchend\", (e) => {\n            if (isDragging) {\n             e.preventDefault();\n             isDragging = false;\n             rectEl.style.cursor = \"move\";\n            }\n           }, { passive: false });\n           if (currentRectId === rect.id) {\n             const corners = [\n               { cx: rect.x, cy: rect.y, cursor: 'nwse-resize', dx: -1, dy: -1 },\n               { cx: rect.x + rect.width, cy: rect.y, cursor: 'nesw-resize', dx: 1, dy: -1 },\n               { cx: rect.x, cy: rect.y + rect.height, cursor: 'nesw-resize', dx: -1, dy: 1 },\n               { cx: rect.x + rect.width, cy: rect.y + rect.height, cursor: 'nwse-resize', dx: 1, dy: 1 }\n             ];\n             corners.forEach((corner, idx) => {\n               const handle = document.createElementNS(ns, \"circle\");\n               handle.setAttribute(\"cx\", corner.cx);\n               handle.setAttribute(\"cy\", corner.cy);\n               const borderW = rect.borderWidth !== undefined ? rect.borderWidth : 3;\n               const handleSize = Math.max(PAGE_STATE.selectionHandleSize || 8, borderW + 4);\n               handle.setAttribute(\"r\", handleSize);\n               handle.style.fill = PAGE_STATE.selectionHandle || \"#f59e0b\";\n               handle.style.stroke = \"#fff\";\n               handle.style.strokeWidth = \"2\";\n               handle.style.cursor = corner.cursor;\n               handle.addEventListener(\"mousedown\", (e) => {\n                 if (isViewOnly()) return;\n                 e.preventDefault();\n                 e.stopPropagation();\n                 pushUndo(\"resize zone\");\n                 let dragging = true;\n                 const startX = e.clientX, startY = e.clientY;\n                 const origX = rect.x, origY = rect.y, origW = rect.width, origH = rect.height;\n                 const moveHandler = (ev) => {\n                   if (!dragging) return;\n                   const pt1 = svg.createSVGPoint(); pt1.x = startX; pt1.y = startY;\n                   const pt2 = svg.createSVGPoint(); pt2.x = ev.clientX; pt2.y = ev.clientY;\n                   const svgP1 = pt1.matrixTransform(svg.getScreenCTM().inverse());\n                   const svgP2 = pt2.matrixTransform(svg.getScreenCTM().inverse());\n                   const dx = svgP2.x - svgP1.x, dy = svgP2.y - svgP1.y;\n                   if (corner.dx < 0) { rect.x = origX + dx; rect.width = origW - dx; }\n                   else { rect.width = origW + dx; }\n                   if (corner.dy < 0) { rect.y = origY + dy; rect.height = origH - dy; }\n                   else { rect.height = origH + dy; }\n                   if (rect.width < 20) rect.width = 20;\n                   if (rect.height < 20) rect.height = 20;\n                   forgeTheTopology();\n                 };\n                 const upHandler = () => { dragging = false; document.removeEventListener(\"mousemove\", moveHandler); document.removeEventListener(\"mouseup\", upHandler); };\n                 document.addEventListener(\"mousemove\", moveHandler);\n                 document.addEventListener(\"mouseup\", upHandler);\n               });\n               g.appendChild(handle);\n             });\n           }\n           if (rect.groupId) {\n             const groupIndicator = document.createElementNS(ns, \"rect\");\n             groupIndicator.setAttribute(\"x\", rect.x - 4);\n             groupIndicator.setAttribute(\"y\", rect.y - 4);\n             groupIndicator.setAttribute(\"width\", rect.width + 8);\n             groupIndicator.setAttribute(\"height\", rect.height + 8);\n             groupIndicator.style.fill = \"none\";\n             groupIndicator.style.stroke = \"#4fd1c5\";\n             groupIndicator.style.strokeWidth = \"3\";\n             groupIndicator.style.strokeDasharray = \"5,5\";\n             groupIndicator.style.pointerEvents = \"none\";\n             g.insertBefore(groupIndicator, g.firstChild);\n           }\n           g.appendChild(rectEl);\n           g.appendChild(deleteBtn);\n           svg.appendChild(g);\n          }\n         });\n        }\n        if (TEXT_DATA && TEXT_DATA.list) {\n         TEXT_DATA.list.forEach((textItem) => {\n          if (currentView.mode === \"rack\") return;\n          const g = document.createElementNS(ns, \"g\");\n          g.classList.add(\"text-group\");\n          g.dataset.textId = textItem.id;\n          const textRotation = textItem.rotation || 0;\n          if (textRotation !== 0) {\n            g.setAttribute(\"transform\", `rotate(${textRotation}, ${textItem.x}, ${textItem.y})`);\n          }\n          if (textItem.bgEnabled) {\n           const bgRect = document.createElementNS(ns, \"rect\");\n           bgRect.classList.add(\"text-bg\");\n           bgRect.setAttribute(\"x\", textItem.x - 5);\n           bgRect.setAttribute(\"y\", textItem.y - textItem.fontSize - 2);\n           bgRect.setAttribute(\"width\", 100);\n           bgRect.setAttribute(\"height\", textItem.fontSize + 10);\n           bgRect.style.fill = textItem.bgColor;\n           bgRect.style.opacity = \"0.7\";\n           bgRect.style.rx = \"4\";\n           g.appendChild(bgRect);\n          }\n          const textEl = document.createElementNS(ns, \"text\");\n          textEl.classList.add(\"text-element\");\n          textEl.setAttribute(\"x\", textItem.x);\n          textEl.setAttribute(\"y\", textItem.y);\n          textEl.style.fill = textItem.color;\n          textEl.style.fontSize = textItem.fontSize + \"px\";\n          textEl.style.fontWeight = textItem.fontWeight;\n          textEl.style.fontStyle = textItem.fontStyle;\n          textEl.style.textAnchor = textItem.textAlign;\n          textEl.style.textDecoration = textItem.textDecoration;\n          textEl.style.opacity = textItem.opacity;\n          textEl.style.cursor = \"move\";\n          textEl.style.userSelect = \"none\";\n          textEl.setAttribute(\"dominant-baseline\", \"middle\");\n          const lines = textItem.content.split('\\n');\n          if (lines.length === 1) {\n           textEl.textContent = textItem.content;\n          } else {\n           lines.forEach((line, i) => {\n            const tspan = document.createElementNS(ns, \"tspan\");\n            tspan.textContent = line;\n            tspan.setAttribute(\"x\", textItem.x);\n            tspan.setAttribute(\"dy\", i === 0 ? 0 : textItem.fontSize * 1.2);\n            textEl.appendChild(tspan);\n           });\n          }\n          g.appendChild(textEl);\n          if (textItem.bgEnabled) {\n           setTimeout(() => {\n            try {\n             const bbox = textEl.getBBox();\n             const bgRect = g.querySelector('.text-bg');\n             if (bgRect && bbox) {\n              bgRect.setAttribute(\"x\", bbox.x - 5);\n              bgRect.setAttribute(\"y\", bbox.y - 2);\n              bgRect.setAttribute(\"width\", bbox.width + 10);\n              bgRect.setAttribute(\"height\", bbox.height + 4);\n             }\n            } catch (e) {\n            }\n           }, 0);\n          }\n          const deleteBtn = document.createElementNS(ns, \"g\");\n          deleteBtn.classList.add(\"text-delete-btn\");\n          deleteBtn.style.cursor = \"pointer\";\n          deleteBtn.style.display = textDrawMode ? \"block\" : \"none\";\n          const deleteBg = document.createElementNS(ns, \"circle\");\n          deleteBg.setAttribute(\"cx\", textItem.x + 20);\n          deleteBg.setAttribute(\"cy\", textItem.y - textItem.fontSize);\n          deleteBg.setAttribute(\"r\", 12);\n          deleteBg.style.fill = \"#f56565\";\n          deleteBg.style.stroke = \"white\";\n          deleteBg.style.strokeWidth = \"2\";\n          const deleteX = document.createElementNS(ns, \"text\");\n          deleteX.setAttribute(\"x\", textItem.x + 20);\n          deleteX.setAttribute(\"y\", textItem.y - textItem.fontSize);\n          deleteX.setAttribute(\"text-anchor\", \"middle\");\n          deleteX.setAttribute(\"dominant-baseline\", \"middle\");\n          deleteX.style.fill = \"white\";\n          deleteX.style.fontSize = \"16px\";\n          deleteX.style.fontWeight = \"bold\";\n          deleteX.style.pointerEvents = \"none\";\n          deleteX.textContent = \"×\";\n          deleteBtn.appendChild(deleteBg);\n          deleteBtn.appendChild(deleteX);\n          deleteBtn.addEventListener(\"click\", (e) => {\n           e.stopPropagation();\n           e.preventDefault();\n           deleteText(textItem.id);\n          });\n          let isDragging = false;\n          let dragStartX, dragStartY;\n          let textStartX, textStartY;\n          textEl.addEventListener(\"mousedown\", (e) => {\n\t\t  if (isViewOnly()) return;\n      if (textDrawMode) return;\n      e.preventDefault();\n      e.stopPropagation();\n      isDragging = true;\n      dragStartX = e.clientX;\n      dragStartY = e.clientY;\n      textStartX = textItem.x;\n      textStartY = textItem.y;\n      textEl.style.cursor = \"grabbing\";\n      showTextPanel(textItem.id);\n      if (selectedTexts.has(textItem.id)) {\n      initialPositions = {};\n      selectedNodes.forEach(nodeId => {\n      const nodePos = savedPositions[nodeId];\n      if (nodePos) initialPositions[nodeId] = { x: nodePos.x, y: nodePos.y };\n      });\n      selectedRects.forEach(rectId => {\n      const r = RECT_DATA.list.find(x => x.id === rectId);\n      if (r) { r._dragStartX = r.x; r._dragStartY = r.y; }\n      });\n      selectedTexts.forEach(textId => {\n      const t = TEXT_DATA.list.find(x => x.id === textId);\n      if (t) { t._dragStartX = t.x; t._dragStartY = t.y; }\n      });\n      selectedEdges.forEach(edgeId => {\n      const ed = EDGE_DATA.list.find(x => x.id === edgeId);\n      if (ed && ed.points) { ed._dragStartPoints = ed.points.map(p => ({x: p.x, y: p.y})); }\n      });\n      }\n      });\n          const moveHandler = (e) => {\n      if (!isDragging || textDrawMode) return;\n      const svgEl = svg;\n      const pt1 = svgEl.createSVGPoint();\n      pt1.x = dragStartX;\n      pt1.y = dragStartY;\n      const svgP1 = pt1.matrixTransform(svgEl.getScreenCTM().inverse());\n      const pt2 = svgEl.createSVGPoint();\n      pt2.x = e.clientX;\n      pt2.y = e.clientY;\n      const svgP2 = pt2.matrixTransform(svgEl.getScreenCTM().inverse());\n      const dx = svgP2.x - svgP1.x;\n      const dy = svgP2.y - svgP1.y;\n      textItem.x = textStartX + dx;\n      textItem.y = textStartY + dy;\n      if (selectedTexts.has(textItem.id)) {\n      selectedNodes.forEach(nodeId => {\n      if (initialPositions[nodeId]) {\n       const newX = initialPositions[nodeId].x + dx;\n       const newY = initialPositions[nodeId].y + dy;\n       savedPositions[nodeId] = { x: newX, y: newY };\n      }\n      });\n      selectedRects.forEach(rectId => {\n      const r = RECT_DATA.list.find(x => x.id === rectId);\n      if (r && r._dragStartX !== undefined) { r.x = r._dragStartX + dx; r.y = r._dragStartY + dy; }\n      });\n      selectedTexts.forEach(textId => {\n      if (textId === textItem.id) return;\n      const t = TEXT_DATA.list.find(x => x.id === textId);\n      if (t && t._dragStartX !== undefined) { t.x = t._dragStartX + dx; t.y = t._dragStartY + dy; }\n      });\n      selectedEdges.forEach(edgeId => {\n      const ed = EDGE_DATA.list.find(x => x.id === edgeId);\n      if (ed && ed._dragStartPoints) { ed.points.forEach((p, i) => { p.x = ed._dragStartPoints[i].x + dx; p.y = ed._dragStartPoints[i].y + dy; }); }\n      });\n      }\n      forgeTheTopology();\n      };\n          const upHandler = () => {\n           if (isDragging) {\n            isDragging = false;\n            textEl.style.cursor = \"move\";\n           }\n          };\n          document.addEventListener(\"mousemove\", moveHandler);\n          document.addEventListener(\"mouseup\", upHandler);\n          let touchStartX = 0, touchStartY = 0;\n          let textTouchStartX = 0, textTouchStartY = 0;\n          textEl.addEventListener(\"touchstart\", (e) => {\n\t\t   if (isViewOnly()) return;\n           if (textDrawMode) return;\n           if (e.touches.length !== 1) return;\n           e.preventDefault();\n           e.stopPropagation();\n           const touch = e.touches[0];\n           isDragging = true;\n           touchStartX = touch.clientX;\n           touchStartY = touch.clientY;\n           textTouchStartX = textItem.x;\n           textTouchStartY = textItem.y;\n           showTextPanel(textItem.id);\n          }, { passive: false });\n          textEl.addEventListener(\"touchmove\", (e) => {\n           if (!isDragging || textDrawMode) return;\n           if (e.touches.length !== 1) return;\n           e.preventDefault();\n           const touch = e.touches[0];\n           const svgEl = svg;\n           const pt1 = svgEl.createSVGPoint();\n           pt1.x = touchStartX;\n           pt1.y = touchStartY;\n           const svgP1 = pt1.matrixTransform(svgEl.getScreenCTM().inverse());\n           const pt2 = svgEl.createSVGPoint();\n           pt2.x = touch.clientX;\n           pt2.y = touch.clientY;\n           const svgP2 = pt2.matrixTransform(svgEl.getScreenCTM().inverse());\n           const dx = svgP2.x - svgP1.x;\n           const dy = svgP2.y - svgP1.y;\n           textItem.x = textTouchStartX + dx;\n           textItem.y = textTouchStartY + dy;\n           forgeTheTopology();\n          }, { passive: false });\n          textEl.addEventListener(\"touchend\", () => {\n           if (isDragging) {\n            isDragging = false;\n           }\n          }, { passive: false });\n          textEl.addEventListener(\"contextmenu\", (e) => {\n\t\t  if (isViewOnly()) return;\n           e.preventDefault();\n           e.stopPropagation();\n           if (selectedTexts.has(textItem.id)) {\n            selectedTexts.delete(textItem.id);\n           } else {\n            selectedTexts.add(textItem.id);\n           }\n           updateAllSelections();\n          });\n          let textLastTap = 0;\n          g.addEventListener(\"touchend\", (e) => {\n           const now = Date.now();\n           if (now - textLastTap < 300) {\n            e.preventDefault();\n            if (selectedTexts.has(textItem.id)) {\n             selectedTexts.delete(textItem.id);\n            } else {\n             selectedTexts.add(textItem.id);\n            }\n            updateAllSelections();\n            if (navigator.vibrate) navigator.vibrate(50);\n            textLastTap = 0;\n           } else {\n            textLastTap = now;\n           }\n          }, { passive: false });\n          if (textItem.groupId) {\n            const groupIndicator = document.createElementNS(ns, \"rect\");\n            groupIndicator.setAttribute(\"x\", textItem.x - 54);\n            groupIndicator.setAttribute(\"y\", textItem.y - 24);\n            groupIndicator.setAttribute(\"width\", 108);\n            groupIndicator.setAttribute(\"height\", 48);\n            groupIndicator.setAttribute(\"rx\", \"8\");\n            groupIndicator.style.fill = \"none\";\n            groupIndicator.style.stroke = \"#4fd1c5\";\n            groupIndicator.style.strokeWidth = \"3\";\n            groupIndicator.style.strokeDasharray = \"5,5\";\n            groupIndicator.style.pointerEvents = \"none\";\n            g.insertBefore(groupIndicator, g.firstChild);\n          }\n          g.appendChild(deleteBtn);\n          svg.appendChild(g);\n         });\n        }\n       forgeTheLegend();\n       updateZoneLegend();\n       updateMinimap();\n       populateRackDropdown();\n       if (currentSearchQuery && currentSearchResults.length > 0) {\n        highlightSearchResults(currentSearchResults, true);\n       }\n      }\n      const _forgeTheTopologyImpl = forgeTheTopology;\n      forgeTheTopology = function(immediate = false) {\n       if (immediate || forgeImmediate) {\n        forgeImmediate = false;\n        clearTimeout(forgeDebounceTimer);\n        _forgeTheTopologyImpl();\n        return;\n       }\n       clearTimeout(forgeDebounceTimer);\n       forgeDebounceTimer = setTimeout(() => {\n        _forgeTheTopologyImpl();\n       }, 16);\n      };\n      function forgeTheTopologyImmediate() {\n       forgeImmediate = true;\n       forgeTheTopology();\n      }\n      function showEditModal(title, currentValue, onSave) {\n       const modal = document.getElementById(\"edit-modal\");\n       const input = document.getElementById(\"modal-input\");\n       const titleEl = document.getElementById(\"modal-title\");\n       const saveBtn = document.getElementById(\"modal-save\");\n       const cancelBtn = document.getElementById(\"modal-cancel\");\n       titleEl.textContent = title;\n       input.value = currentValue;\n       modal.classList.add(\"active\");\n       input.focus();\n       input.select();\n       const cleanup = () => {\n        modal.classList.remove(\"active\");\n        saveBtn.removeEventListener(\"click\", handleSave);\n        cancelBtn.removeEventListener(\"click\", handleCancel);\n        input.removeEventListener(\"keypress\", handleEnter);\n        modal.removeEventListener(\"click\", bgHandler);\n       };\n       const handleSave = () => {\n        if (input.value.trim()) {\n         onSave(input.value.trim());\n        }\n        cleanup();\n       };\n       const handleCancel = () => {\n        cleanup();\n       };\n       const handleEnter = (e) => {\n        if (e.key === \"Enter\") handleSave();\n       };\n       const bgHandler = (e) => {\n        if (e.target === modal) handleCancel();\n       };\n       saveBtn.addEventListener(\"click\", handleSave);\n       cancelBtn.addEventListener(\"click\", handleCancel);\n       input.addEventListener(\"keypress\", handleEnter);\n       modal.addEventListener(\"click\", bgHandler);\n      }\n      function challengeTheImmortal(message, onConfirm) {\n       const modal = document.getElementById(\"confirm-modal\");\n       const messageEl = document.getElementById(\"confirm-message\");\n       const deleteBtn = document.getElementById(\"confirm-delete\");\n       const cancelBtn = document.getElementById(\"confirm-cancel\");\n       messageEl.textContent = message;\n       modal.classList.add(\"active\");\n\t   const cleanup = () => {\n        modal.classList.remove(\"active\");\n        deleteBtn.removeEventListener(\"click\", handleConfirm);\n        cancelBtn.removeEventListener(\"click\", handleCancel);\n        modal.removeEventListener(\"click\", bgHandler);\n       };\n       const handleConfirm = () => {\n        onConfirm();\n        cleanup();\n       };\n       const handleCancel = () => {\n        cleanup();\n       };\n       const bgHandler = (e) => {\n        if (e.target === modal) handleCancel();\n       };\n       deleteBtn.addEventListener(\"click\", handleConfirm);\n       cancelBtn.addEventListener(\"click\", handleCancel);\n       modal.addEventListener(\"click\", bgHandler);\n      }\n      const pageTitleEl = document.getElementById(\"page-title\");\n      if (pageTitleEl) {\n       pageTitleEl.addEventListener(\"click\", () => {\n        showEditModal(\"Edit Title\", PAGE_STATE.title || DEFAULT_PAGE_STATE.title,\n         (newTitle) => {\n          PAGE_STATE.title = newTitle;\n          wieldThePower();\n         });\n       });\n      }\n      function editNodeName(id) {\n       showEditModal(\"Edit Name\", NODE_DATA[id].name, (newName) => {\n        pushUndo(\"edit node name\");\n        NODE_DATA[id].name = newName;\n        const nodeGroup = document.querySelector(`g[data-node-id=\"${id}\"]`);\n        if (nodeGroup) {\n         const label = nodeGroup.querySelector(\".node-label\");\n         if (label) label.textContent = newName;\n        }\n        if (currentNodeId === id) {\n         document.getElementById(\"node-name\").textContent = newName;\n        }\n       });\n      }\n      function editNodeIp(id) {\n       showEditModal(\"Edit IP/Subtitle\", NODE_DATA[id].ip, (newIp) => {\n        pushUndo(\"edit node ip\");\n        NODE_DATA[id].ip = newIp;\n        const nodeGroup = document.querySelector(`g[data-node-id=\"${id}\"]`);\n        if (nodeGroup) {\n         const sub = nodeGroup.querySelector(\".node-sub\");\n         if (sub) sub.textContent = newIp;\n        }\n        if (currentNodeId === id) {\n         document.getElementById(\"node-ip\").textContent = newIp;\n        }\n       });\n      }\n      function claimTheImmortal(id) {\n\t   if (isViewOnly()) return;\n\t   if (!NODE_DATA[id]) return;\n       currentNodeId = id;\n       currentEdgeId = null;\n       currentRectId = null;\n       currentTextId = null;\n       if (isMobileDevice()) {\n        const currentHeight = PAGE_STATE.mobileFooterHeight || 40;\n        if (currentHeight <= 20) {\n         PAGE_STATE.mobileFooterHeight = 80;\n         const root = document.documentElement;\n         root.style.setProperty(\"--mobile-footer-height\", \"80vh\");\n         const mobileFooterHeightInput = document.getElementById(\"mobile-footer-height\");\n         const mobileFooterHeightVal = document.getElementById(\"mobile-footer-height-val\");\n         if (mobileFooterHeightInput) mobileFooterHeightInput.value = 80;\n         if (mobileFooterHeightVal) mobileFooterHeightVal.textContent = \"80%\";\n        }\n       }\n       const data = NODE_DATA[id];\n       document.querySelectorAll(\".node-group\").forEach((n) => {\n        n.classList.toggle(\"active\", n.dataset.nodeId === id);\n       });\n       document.querySelectorAll(\".edge\").forEach((e) => {\n        const active = e.dataset.from === id || e.dataset.to === id;\n        e.classList.toggle(\"active\", active);\n       });\n       document.querySelectorAll(\".rect-group\").forEach((r) => r.classList.remove(\"active\"));\n       document.querySelectorAll(\".text-element\").forEach((t) => t.classList.remove(\"active\"));\n       document.getElementById(\"node-panel\").style.display = \"block\";\n       document.getElementById(\"edge-panel\").style.display = \"none\";\n       document.getElementById(\"text-panel\").style.display = \"none\";\n       const toolbar = document.getElementById(\"topology-toolbar\");\n       if (!topologyToolbarCollapsed) {\n        toolbar.style.display = \"flex\";\n       }\n       updateTopologyToolbarVisibility();\n       document.getElementById(\"node-name\").textContent = data.name;\n       document.getElementById(\"node-ip\").textContent = data.ip;\nconst fovSection = document.getElementById(\"fov-section\");\nif (fovSection) {\n  if (hasCoverageZone(data.shape)) {\n    const defaults = getCoverageDefaults(data.shape);\n    fovSection.style.display = \"block\";\n    document.getElementById(\"fov-enabled\").checked = data.fovEnabled || false;\n    document.getElementById(\"fov-angle\").value = data.fovAngle || defaults.angle;\n    document.getElementById(\"fov-angle-value\").textContent = (data.fovAngle || defaults.angle) + \"°\";\n    document.getElementById(\"fov-distance\").value = data.fovDistance || defaults.distance;\n    document.getElementById(\"fov-distance-value\").textContent = data.fovDistance || defaults.distance;\n    document.getElementById(\"fov-inner-radius\").value = data.fovInnerRadius || 0;\n    document.getElementById(\"fov-inner-radius-value\").textContent = data.fovInnerRadius || 0;\n    document.getElementById(\"fov-rotation\").value = data.fovRotation || 0;\n    document.getElementById(\"fov-rotation-value\").textContent = (data.fovRotation || 0) + \"°\";\n    document.getElementById(\"fov-color\").value = data.fovColor || \"#f59e0b\";\n    document.getElementById(\"fov-opacity\").value = data.fovOpacity || 20;\n    document.getElementById(\"fov-opacity-value\").textContent = (data.fovOpacity || 20) + \"%\";\n    document.getElementById(\"fov-gradient\").checked = data.fovGradient || false;\n    document.getElementById(\"fov-border-color\").value = data.fovBorderColor || \"#f59e0b\";\n    document.getElementById(\"fov-border-width\").value = data.fovBorderWidth ?? 2;\n    document.getElementById(\"fov-border-width-value\").textContent = data.fovBorderWidth ?? 2;\n    document.getElementById(\"fov-border-style\").value = data.fovBorderStyle || \"solid\";\n    document.getElementById(\"fov-border-opacity\").value = data.fovBorderOpacity ?? 100;\n    document.getElementById(\"fov-border-opacity-value\").textContent = (data.fovBorderOpacity ?? 100) + \"%\";\n    document.getElementById(\"fov-label\").value = data.fovLabel || \"\";\n    document.getElementById(\"fov-label-position\").value = data.fovLabelPosition || \"center\";\n    document.getElementById(\"fov-label-size\").value = data.fovLabelSize || 14;\n    document.getElementById(\"fov-label-size-value\").textContent = (data.fovLabelSize || 14) + \"px\";\n    document.getElementById(\"fov-label-color\").value = data.fovLabelColor || \"#ffffff\";\n    document.getElementById(\"fov-label-bold\").checked = data.fovLabelBold || false;\n    document.getElementById(\"fov-label-bg\").checked = data.fovLabelBg || false;\n    document.getElementById(\"fov-label-bg-color\").value = data.fovLabelBgColor || \"#000000\";\n    document.getElementById(\"fov-label-offset-x\").value = data.fovLabelOffsetX || 0;\n    document.getElementById(\"fov-label-offset-x-value\").textContent = data.fovLabelOffsetX || 0;\n    document.getElementById(\"fov-label-offset-y\").value = data.fovLabelOffsetY || 0;\n    document.getElementById(\"fov-label-offset-y-value\").textContent = data.fovLabelOffsetY || 0;\n    document.getElementById(\"fov-animate\").checked = data.fovAnimate || false;\n    document.getElementById(\"fov-animation-type\").value = data.fovAnimationType || defaults.animationType;\n    document.getElementById(\"fov-sweep\").value = data.fovSweep || 120;\n    document.getElementById(\"fov-sweep-value\").textContent = (data.fovSweep || 120) + \"°\";\n    document.getElementById(\"fov-speed\").value = data.fovSpeed || 4;\n    document.getElementById(\"fov-speed-value\").textContent = (data.fovSpeed || 4) + \"s\";\n  } else {\n    fovSection.style.display = \"none\";\n  }\n}\n       document.getElementById(\"node-role\").textContent = data.role;\n       document.getElementById(\"node-mac\").textContent = data.mac || \"--\";\n       document.getElementById(\"node-rack\").textContent = data.rackUnit || \"--\";\n       document.getElementById(\"node-uheight\").textContent = (data.uHeight || \"1\") + \"U\";\n       document.getElementById(\"node-layer\").value = data.layer || \"physical\";\n       populateRackDropdown();\n       const assignedRackSelect = document.getElementById(\"node-assigned-rack\");\n       if (assignedRackSelect) {\n        assignedRackSelect.value = data.assignedRack || \"\";\n       }\n       const rackCapacitySelect = document.getElementById(\"node-rack-capacity\");\n       if (rackCapacitySelect) {\n        rackCapacitySelect.value = data.rackCapacity || \"42\";\n       }\n       const isRack = data.isRack === true;\n       const isAssignedToRack = !!data.assignedRack;\n       const assignedRackRow = document.getElementById(\"assigned-rack-row\");\n       const rackCapacityRow = document.getElementById(\"rack-capacity-row\");\n       const uheightRow = document.getElementById(\"uheight-row\");\n       if (assignedRackRow) assignedRackRow.style.display = isRack ? \"none\" : \"flex\";\n       if (rackCapacityRow) rackCapacityRow.style.display = isRack ? \"flex\" : \"none\";\n       if (uheightRow) uheightRow.style.display = isAssignedToRack ? \"flex\" : \"none\";\n       const rackContentsSection = document.getElementById(\"rack-contents-section\");\n       const rackContentsList = document.getElementById(\"rack-contents-list\");\n       if (rackContentsSection && rackContentsList) {\n        if (isRack) {\n         const nodesInRack = Object.entries(NODE_DATA).filter(([nid, n]) => n.assignedRack === id);\n         if (nodesInRack.length > 0) {\n          rackContentsList.innerHTML = '';\n          nodesInRack.forEach(([nid, n]) => {\n           const div = document.createElement('div');\n           div.style.cssText = 'padding: 6px 0; border-bottom: 1px solid var(--edge-main); cursor: pointer;';\n           div.onclick = () => claimTheImmortal(nid);\n           const nameSpan = document.createElement('span');\n           nameSpan.style.color = 'var(--text-main)';\n           nameSpan.textContent = n.name;\n           div.appendChild(nameSpan);\n           (n.tags || []).forEach(t => {\n            const tagSpan = document.createElement('span');\n            tagSpan.style.cssText = 'background: var(--accent); color: var(--bg); padding: 2px 6px; border-radius: 4px; font-size: 11px; margin-left: 4px;';\n            tagSpan.textContent = t;\n            div.appendChild(tagSpan);\n           });\n           rackContentsList.appendChild(div);\n          });\n          rackContentsSection.style.display = \"block\";\n         } else {\n          rackContentsList.innerHTML = '<div style=\"color: var(--text-soft); font-style: italic;\">No nodes assigned</div>';\n          rackContentsSection.style.display = \"block\";\n         }\n        } else {\n         rackContentsSection.style.display = \"none\";\n        }\n       }\n       const connectionsSection = document.getElementById(\"node-connections-section\");\n       const connectionsList = document.getElementById(\"node-connections-list\");\n       const connectionsCount = document.getElementById(\"node-connections-count\");\n       if (connectionsSection && connectionsList) {\n        const connectedEdges = (EDGE_DATA.list || []).filter(e => e.from === id || e.to === id);\n        connectionsCount.textContent = connectedEdges.length;\n        if (connectedEdges.length > 0) {\n         connectionsList.innerHTML = '';\n         connectedEdges.forEach(e => {\n          const isFrom = e.from === id;\n          const localPort = isFrom ? e.fromPort : e.toPort;\n          const remoteNodeId = isFrom ? e.to : e.from;\n          const remotePort = isFrom ? e.toPort : e.fromPort;\n          const remoteName = NODE_DATA[remoteNodeId]?.name || remoteNodeId;\n          const div = document.createElement('div');\n          div.style.cssText = 'padding: 6px 0; border-bottom: 1px solid var(--edge-main); display: flex; align-items: center; font-size: 13px;';\n          const localSpan = document.createElement('span');\n          localSpan.style.cssText = localPort ? 'color: var(--accent); font-family: monospace; cursor: pointer;' : 'color: var(--text-soft); cursor: pointer;';\n          localSpan.textContent = localPort || '-';\n          localSpan.title = localPort ? 'Click to view connection' : 'Click to set port';\n          localSpan.onclick = () => { if (localPort) { selectTheConnection(e.id); } else { const label = `Port on ${NODE_DATA[id]?.name || id}:`; const newVal = prompt(label, ''); if (newVal !== null && newVal !== '') { const isDupe = (EDGE_DATA.list || []).some(x => x.id !== e.id && ((x.from === id && x.fromPort === newVal) || (x.to === id && x.toPort === newVal))); if (isDupe && !confirm(`Warning: Port \"${newVal}\" is already used on ${NODE_DATA[id]?.name || id}. Use anyway?`)) return; if (isFrom) e.fromPort = newVal; else e.toPort = newVal; claimTheImmortal(id); } } };\n          const arrow = document.createElement('span');\n          arrow.style.cssText = 'color: var(--text-soft); margin: 0 8px;';\n          arrow.textContent = '↔';\n          const remoteSpan = document.createElement('span');\n          remoteSpan.style.cssText = 'color: var(--text-main); cursor: pointer;';\n          remoteSpan.textContent = remoteName;\n          remoteSpan.title = 'Click to view connection';\n          remoteSpan.onclick = () => { claimTheImmortal(remoteNodeId); focusOnSelected(); };\n          const remotePortSpan = document.createElement('span');\n          remotePortSpan.style.cssText = remotePort ? 'color: var(--accent); font-family: monospace; margin-left: 6px; cursor: pointer;' : 'color: var(--text-soft); margin-left: 6px; cursor: pointer;';\n          remotePortSpan.textContent = '(' + (remotePort || '-') + ')';\n          remotePortSpan.title = remotePort ? 'Click to view connection' : 'Click to set port';\n          remotePortSpan.onclick = () => { if (remotePort) { selectTheConnection(e.id); } else { const label = `Port on ${remoteName}:`; const newVal = prompt(label, ''); if (newVal !== null && newVal !== '') { const isDupe = (EDGE_DATA.list || []).some(x => x.id !== e.id && ((x.from === remoteNodeId && x.fromPort === newVal) || (x.to === remoteNodeId && x.toPort === newVal))); if (isDupe && !confirm(`Warning: Port \"${newVal}\" is already used on ${remoteName}. Use anyway?`)) return; if (isFrom) e.toPort = newVal; else e.fromPort = newVal; claimTheImmortal(id); } } };\n          div.appendChild(localSpan);\n          div.appendChild(arrow);\n          div.appendChild(remoteSpan);\n          div.appendChild(remotePortSpan);\n          connectionsList.appendChild(div);\n         });\n         connectionsSection.style.display = \"block\";\n        } else {\n         connectionsList.innerHTML = '<div style=\"color: var(--text-soft); font-style: italic;\">No connections</div>';\n         connectionsSection.style.display = \"block\";\n        }\n       }\n       document.getElementById(\"node-name\").onclick = () => editNodeName(id);\n       document.getElementById(\"node-ip\").onclick = () => editNodeIp(id);\ndocument.getElementById(\"fov-enabled\").onchange = function() {\n  if (!currentNodeId) return;\n  pushUndo(\"toggle coverage zone\");\n  NODE_DATA[currentNodeId].fovEnabled = this.checked;\n  updateFovCone(currentNodeId);\n};\ndocument.getElementById(\"fov-angle\").oninput = function() {\n  document.getElementById(\"fov-angle-value\").textContent = this.value + \"°\";\n  if (!currentNodeId) return;\n  NODE_DATA[currentNodeId].fovAngle = parseInt(this.value);\n  updateFovCone(currentNodeId);\n};\ndocument.getElementById(\"fov-distance\").oninput = function() {\n  document.getElementById(\"fov-distance-value\").textContent = this.value;\n  if (!currentNodeId) return;\n  NODE_DATA[currentNodeId].fovDistance = parseInt(this.value);\n  updateFovCone(currentNodeId);\n};\ndocument.getElementById(\"fov-inner-radius\").oninput = function() {\n  document.getElementById(\"fov-inner-radius-value\").textContent = this.value;\n  if (!currentNodeId) return;\n  NODE_DATA[currentNodeId].fovInnerRadius = parseInt(this.value);\n  updateFovCone(currentNodeId);\n};\ndocument.getElementById(\"fov-rotation\").oninput = function() {\n  document.getElementById(\"fov-rotation-value\").textContent = this.value + \"°\";\n  if (!currentNodeId) return;\n  NODE_DATA[currentNodeId].fovRotation = parseInt(this.value);\n  updateFovCone(currentNodeId);\n};\ndocument.getElementById(\"fov-color\").oninput = function() {\n  if (!currentNodeId) return;\n  pushUndo(\"change coverage color\");\n  NODE_DATA[currentNodeId].fovColor = this.value;\n  updateFovCone(currentNodeId);\n  updateZoneLegend();\n};\ndocument.getElementById(\"fov-opacity\").oninput = function() {\n  document.getElementById(\"fov-opacity-value\").textContent = this.value + \"%\";\n  if (!currentNodeId) return;\n  NODE_DATA[currentNodeId].fovOpacity = parseInt(this.value);\n  updateFovCone(currentNodeId);\n};\ndocument.getElementById(\"fov-gradient\").onchange = function() {\n  if (!currentNodeId) return;\n  pushUndo(\"toggle coverage gradient\");\n  NODE_DATA[currentNodeId].fovGradient = this.checked;\n  updateFovCone(currentNodeId);\n};\ndocument.getElementById(\"fov-border-color\").oninput = function() {\n  if (!currentNodeId) return;\n  pushUndo(\"change coverage border color\");\n  NODE_DATA[currentNodeId].fovBorderColor = this.value;\n  updateFovCone(currentNodeId);\n};\ndocument.getElementById(\"fov-border-width\").oninput = function() {\n  document.getElementById(\"fov-border-width-value\").textContent = this.value;\n  if (!currentNodeId) return;\n  NODE_DATA[currentNodeId].fovBorderWidth = parseInt(this.value);\n  updateFovCone(currentNodeId);\n};\ndocument.getElementById(\"fov-border-style\").onchange = function() {\n  if (!currentNodeId) return;\n  pushUndo(\"change coverage border style\");\n  NODE_DATA[currentNodeId].fovBorderStyle = this.value;\n  updateFovCone(currentNodeId);\n};\ndocument.getElementById(\"fov-border-opacity\").oninput = function() {\n  document.getElementById(\"fov-border-opacity-value\").textContent = this.value + \"%\";\n  if (!currentNodeId) return;\n  NODE_DATA[currentNodeId].fovBorderOpacity = parseInt(this.value);\n  updateFovCone(currentNodeId);\n};\ndocument.getElementById(\"fov-label\").oninput = function() {\n  if (!currentNodeId) return;\n  NODE_DATA[currentNodeId].fovLabel = this.value;\n  updateFovCone(currentNodeId);\n};\ndocument.getElementById(\"fov-label-position\").onchange = function() {\n  if (!currentNodeId) return;\n  NODE_DATA[currentNodeId].fovLabelPosition = this.value;\n  updateFovCone(currentNodeId);\n};\ndocument.getElementById(\"fov-label-size\").oninput = function() {\n  document.getElementById(\"fov-label-size-value\").textContent = this.value + \"px\";\n  if (!currentNodeId) return;\n  NODE_DATA[currentNodeId].fovLabelSize = parseInt(this.value);\n  updateFovCone(currentNodeId);\n};\ndocument.getElementById(\"fov-label-color\").oninput = function() {\n  if (!currentNodeId) return;\n  NODE_DATA[currentNodeId].fovLabelColor = this.value;\n  updateFovCone(currentNodeId);\n};\ndocument.getElementById(\"fov-label-bold\").onchange = function() {\n  if (!currentNodeId) return;\n  NODE_DATA[currentNodeId].fovLabelBold = this.checked;\n  updateFovCone(currentNodeId);\n};\ndocument.getElementById(\"fov-label-bg\").onchange = function() {\n  if (!currentNodeId) return;\n  NODE_DATA[currentNodeId].fovLabelBg = this.checked;\n  updateFovCone(currentNodeId);\n};\ndocument.getElementById(\"fov-label-bg-color\").oninput = function() {\n  if (!currentNodeId) return;\n  NODE_DATA[currentNodeId].fovLabelBgColor = this.value;\n  updateFovCone(currentNodeId);\n};\ndocument.getElementById(\"fov-label-offset-x\").oninput = function() {\n  document.getElementById(\"fov-label-offset-x-value\").textContent = this.value;\n  if (!currentNodeId) return;\n  NODE_DATA[currentNodeId].fovLabelOffsetX = parseInt(this.value);\n  updateFovCone(currentNodeId);\n};\ndocument.getElementById(\"fov-label-offset-y\").oninput = function() {\n  document.getElementById(\"fov-label-offset-y-value\").textContent = this.value;\n  if (!currentNodeId) return;\n  NODE_DATA[currentNodeId].fovLabelOffsetY = parseInt(this.value);\n  updateFovCone(currentNodeId);\n};\ndocument.getElementById(\"fov-preset\").addEventListener(\"change\", function() {\n  if (this.value) {\n    applyZonePreset(this.value);\n    this.value = \"\";\n  }\n});\ndocument.getElementById(\"fov-save-preset\").addEventListener(\"click\", saveCustomZonePreset);\ndocument.getElementById(\"fov-copy-style\").addEventListener(\"click\", function() {\n  if (currentNodeId && copyZoneStyle(currentNodeId)) {\n    alert(\"Zone style copied!\");\n  }\n});\ndocument.getElementById(\"fov-paste-style\").addEventListener(\"click\", function() {\n  if (currentNodeId && pasteZoneStyle(currentNodeId)) {\n    claimTheImmortal(currentNodeId);\n  }\n});\ndocument.getElementById(\"bulk-zone-copy\").addEventListener(\"click\", bulkCopyZoneStyle);\ndocument.getElementById(\"bulk-zone-paste\").addEventListener(\"click\", bulkPasteZoneStyle);\ndocument.getElementById(\"bulk-zone-toggle\").addEventListener(\"click\", bulkToggleZones);\ndocument.getElementById(\"bulk-zone-copy-mobile\").addEventListener(\"click\", bulkCopyZoneStyle);\ndocument.getElementById(\"bulk-zone-paste-mobile\").addEventListener(\"click\", bulkPasteZoneStyle);\ndocument.getElementById(\"bulk-zone-toggle-mobile\").addEventListener(\"click\", bulkToggleZones);\ndocument.getElementById(\"fov-animate\").onchange = function() {\n  if (!currentNodeId) return;\n  pushUndo(\"toggle coverage animation\");\n  NODE_DATA[currentNodeId].fovAnimate = this.checked;\n  updateFovCone(currentNodeId);\n};\ndocument.getElementById(\"fov-animation-type\").onchange = function() {\n  if (!currentNodeId) return;\n  pushUndo(\"change animation type\");\n  NODE_DATA[currentNodeId].fovAnimationType = this.value;\n  updateFovCone(currentNodeId);\n};\ndocument.getElementById(\"fov-sweep\").oninput = function() {\n  document.getElementById(\"fov-sweep-value\").textContent = this.value + \"°\";\n  if (!currentNodeId) return;\n  NODE_DATA[currentNodeId].fovSweep = parseInt(this.value);\n  updateFovCone(currentNodeId);\n};\ndocument.getElementById(\"fov-speed\").oninput = function() {\n  document.getElementById(\"fov-speed-value\").textContent = this.value + \"s\";\n  if (!currentNodeId) return;\n  NODE_DATA[currentNodeId].fovSpeed = parseInt(this.value);\n  updateFovCone(currentNodeId);\n};\n       document.getElementById(\"node-mac\").onclick = () => editNodeMac(id);\n       document.getElementById(\"node-rack\").onclick = () => editNodeRack(id);\n       document.getElementById(\"node-uheight\").onclick = () => editNodeUHeight(id);\n       const currentSize = savedSizes[id] || getDefaultSize();\n       document.getElementById(\"size-slider\").value = currentSize;\n       document.getElementById(\"size-value\").textContent = currentSize;\n       const currentRotation = NODE_DATA[id].rotation || 0;\n       document.getElementById(\"rotation-slider\").value = Math.max(-360, Math.min(360, currentRotation));\n       document.getElementById(\"rotation-value\").value = currentRotation;\n       const styleEntry = savedStyles[id] || {};\n       const resolvedStyles = resolveStylesEntry(styleEntry);\n       const scopeKey = currentStyleScope || \"all\";\n       const flatProps = [\"circleColor\", \"circleBorder\", \"titleColor\", \"titleFont\", \"titleSize\", \"subColor\", \"subFont\", \"subSize\", ];\n       const isFlat = flatProps.some((p) => Object.prototype.hasOwnProperty.call(styleEntry, p));\n       const scopedStyles = isFlat ? styleEntry : styleEntry[scopeKey] || {};\n       const circleColorInput = document.getElementById(\"circle-color\");\n       const titleColorInput = document.getElementById(\"title-color\");\n       const titleFontSelect = document.getElementById(\"title-font\");\n       const titleSizeInput = document.getElementById(\"title-size\");\n       const subColorInput = document.getElementById(\"sub-color\");\n       const subFontSelect = document.getElementById(\"sub-font\");\n       const subSizeInput = document.getElementById(\"sub-size\");\n       const shapeSelect = document.getElementById(\"shape-select\");\n       const scopeSelect = document.getElementById(\"style-scope\");\n      circleColorInput.value = scopedStyles.circleColor || resolvedStyles.circleColor || PAGE_STATE.nodeFill || \"#1e293b\";\n       const circleBorderInput = document.getElementById(\"circle-border\");\n       circleBorderInput.value = scopedStyles.circleBorder || resolvedStyles.circleBorder || PAGE_STATE.nodeStroke || \"#475569\";\n        titleColorInput.value = scopedStyles.titleColor || resolvedStyles.titleColor || PAGE_STATE.textMain || \"#e2e8f0\";\n        titleFontSelect.value = scopedStyles.titleFont || resolvedStyles.titleFont || \"system-ui, sans-serif\";\n        titleSizeInput.value = scopedStyles.titleSize || resolvedStyles.titleSize || 18;\n        subColorInput.value = scopedStyles.subColor || resolvedStyles.subColor || PAGE_STATE.textSoft || \"#94a3b8\";\n       subFontSelect.value = scopedStyles.subFont || resolvedStyles.subFont || \"system-ui, sans-serif\";\n       subSizeInput.value = scopedStyles.subSize || resolvedStyles.subSize || 13;\n       shapeSelect.value = data.shape || \"circle\";\n       scopeSelect.value = currentStyleScope || \"all\";\n       document.getElementById(\"title-offset-y\").value = scopedStyles.titleOffsetY || resolvedStyles.titleOffsetY || 0;\n       document.getElementById(\"title-offset-x\").value = scopedStyles.titleOffsetX || resolvedStyles.titleOffsetX || 0;\n       document.getElementById(\"sub-offset-y\").value = scopedStyles.subOffsetY || resolvedStyles.subOffsetY || 0;\n       document.getElementById(\"sub-offset-x\").value = scopedStyles.subOffsetX || resolvedStyles.subOffsetX || 0;\n       const tagEl = document.getElementById(\"node-tags\");\n       tagEl.innerHTML = \"\";\n       data.tags.forEach((tag, i) => {\n        const b = document.createElement(\"span\");\n        b.className = \"badge\";\n        if (tag.toLowerCase().includes(\"wg\")) b.classList.add(\"wg\");\n        b.style.cursor = \"pointer\";\n        b.style.position = \"relative\";\n        const tagText = document.createElement(\"span\");\n        tagText.textContent = tag;\n        tagText.addEventListener(\"click\", (e) => {\n         e.stopPropagation();\n         showEditModal(\"Edit Tag\", tag, (newTag) => {\n          if (newTag) {\n           pushUndo(\"edit tag\");\n           data.tags[i] = newTag;\n           claimTheImmortal(id);\n          }\n         });\n        });\n        const deleteTag = document.createElement(\"span\");\n        deleteTag.textContent = \" ✕\";\n        deleteTag.style.opacity = \"0.6\";\n        deleteTag.style.marginLeft = \"4px\";\n        deleteTag.style.fontSize = \"10px\";\n        deleteTag.addEventListener(\"click\", (e) => {\n         e.stopPropagation();\n         pushUndo(\"delete tag\");\n         data.tags.splice(i, 1);\n         claimTheImmortal(id);\n        });\n        b.append(tagText, deleteTag);\n        tagEl.append(b);\n       });\n       const addTagBtn = document.createElement(\"span\");\n       addTagBtn.className = \"badge\";\n       addTagBtn.style.cursor = \"pointer\";\n       addTagBtn.style.opacity = \"0.6\";\n       addTagBtn.style.borderStyle = \"dashed\";\n       addTagBtn.textContent = \"+ Add Tag\";\n       addTagBtn.addEventListener(\"click\", () => {\n        showEditModal(\"Add Tag(s) : comma separated\", \"\",\n         (newTagStr) => {\n          if (newTagStr) {\n           pushUndo(\"add tags\");\n           const newTags = newTagStr.split(\",\").map((t) => t.trim()).filter((t) => t);\n           newTags.forEach((t) => data.tags.push(t));\n           claimTheImmortal(id);\n          }\n         });\n       });\n       tagEl.append(addTagBtn);\n       const notesEl = document.getElementById(\"node-notes\");\n       notesEl.innerHTML = \"\";\n       data.notes.forEach((note, i) => {\n        const li = document.createElement(\"li\");\n        const noteText = document.createElement(\"span\");\n        noteText.textContent = note;\n        noteText.style.flex = \"1\";\n        const deleteBtn = document.createElement(\"span\");\n        deleteBtn.className = \"delete-note\";\n        deleteBtn.textContent = \"✕\";\n        deleteBtn.addEventListener(\"click\", (e) => {\n         e.stopPropagation();\n         challengeTheImmortal(\"Are you sure you want to delete this note?\",\n          () => {\n           pushUndo(\"delete note\");\n           data.notes.splice(i, 1);\n           claimTheImmortal(id);\n          });\n        });\n        li.append(noteText, deleteBtn);\n        noteText.addEventListener(\"dblclick\", () => {\n         noteText.classList.add(\"editing\");\n         noteText.contentEditable = true;\n         noteText.focus();\n        });\n        noteText.addEventListener(\"blur\", () => {\n         noteText.classList.remove(\"editing\");\n         noteText.contentEditable = false;\n         data.notes[i] = noteText.textContent;\n        });\n        notesEl.append(li);\n       });\n      const addLineSelect = document.getElementById(\"add-line-select\");\n      addLineSelect.innerHTML = \"\";\n      const currentRack = data.assignedRack || \"\";\n      Object.entries(NODE_DATA).forEach(([nodeId, node]) => {\n      if (nodeId !== id) {\n      const nodeRack = node.assignedRack || \"\";\n      if (currentRack === nodeRack) {\n      const opt = document.createElement(\"option\");\n      opt.value = nodeId;\n      opt.textContent = node.name;\n      addLineSelect.appendChild(opt);\n      }\n      }\n      });\n      }\n      function selectTheConnection(id) {\n\t   if (isViewOnly()) return;\n       currentEdgeId = id;\n       currentNodeId = null;\n       currentRectId = null;\n       currentTextId = null;\n       if (isMobileDevice()) {\n        const currentHeight = PAGE_STATE.mobileFooterHeight || 40;\n        if (currentHeight <= 20) {\n         PAGE_STATE.mobileFooterHeight = 80;\n         const root = document.documentElement;\n         root.style.setProperty(\"--mobile-footer-height\", \"80vh\");\n         const mobileFooterHeightInput = document.getElementById(\"mobile-footer-height\");\n         const mobileFooterHeightVal = document.getElementById(\"mobile-footer-height-val\");\n         if (mobileFooterHeightInput) mobileFooterHeightInput.value = 80;\n         if (mobileFooterHeightVal) mobileFooterHeightVal.textContent = \"80%\";\n        }\n       }\n       document.getElementById(\"node-panel\").style.display = \"none\";\n       document.getElementById(\"edge-panel\").style.display = \"block\";\n       document.getElementById(\"text-panel\").style.display = \"none\";\n       document.getElementById(\"rect-panel\").style.display = \"none\";\n       document.getElementById(\"topology-toolbar\").style.display = \"none\";\n       document.querySelectorAll(\".node-group\").forEach((n) => n.classList.remove(\"active\"));\n       document.querySelectorAll(\".rect-group\").forEach((r) => r.classList.remove(\"active\"));\n       document.querySelectorAll(\".text-element\").forEach((t) => t.classList.remove(\"active\"));\n       document.querySelectorAll(\".edge\").forEach((e) => {\n        e.classList.toggle(\"active\", e.dataset.edgeId === id);\n       });\n       const edge = EDGE_DATA.list.find((e) => e.id === id);\n       if (!edge) return;\n       const directionSymbols = {\n        none: \"⇄\",\n        forward: \"→\",\n        backward: \"←\",\n        both: \"↔\",\n       };\n       const dirSymbol = directionSymbols[edge.direction] || \"⇄\";\n       let titleText = \"Custom line\";\n       if (edge.from || edge.to) {\n        const fromName = edge.from ? NODE_DATA[edge.from]?.name || edge.from : \"\";\n        const toName = edge.to ? NODE_DATA[edge.to]?.name || edge.to : \"\";\n        titleText = `${fromName || \"?\"} ${dirSymbol} ${toName || \"?\"}`;\n       }\n       document.getElementById(\"edge-title\").textContent = titleText;\n       const widthInput = document.getElementById(\"edge-width\");\n       const colorInput = document.getElementById(\"edge-color\");\n       const directionSelect = document.getElementById(\"edge-direction\");\n       const lineStyleSelect = document.getElementById(\"edge-line-style\");\n       const routingSelect = document.getElementById(\"edge-routing\");\n       widthInput.value = edge.width;\n       colorInput.value = edge.color;\n       directionSelect.value = edge.direction || \"none\";\n       lineStyleSelect.value = edge.lineStyle || \"solid\";\n       routingSelect.value = edge.routing || \"curved\";\n       document.getElementById(\"edge-animate\").checked = edge.animate === true;\n       document.getElementById(\"edge-animation-style\").value = edge.animationStyle || \"\";\n       document.getElementById(\"edge-animation-speed\").value = edge.animationSpeed || \"\";\n       const fromPortRow = document.getElementById(\"edge-from-port-row\");\n       const toPortRow = document.getElementById(\"edge-to-port-row\");\n       const fromPortInput = document.getElementById(\"edge-from-port\");\n       const toPortInput = document.getElementById(\"edge-to-port\");\n       if (edge.type === \"custom\") {\n        fromPortRow.style.display = \"none\";\n        toPortRow.style.display = \"none\";\n       } else {\n        fromPortRow.style.display = \"flex\";\n        toPortRow.style.display = \"flex\";\n        fromPortInput.value = edge.fromPort || \"\";\n        toPortInput.value = edge.toPort || \"\";\n        fromPortInput.onchange = () => updateEdgePortLabels(id);\n        toPortInput.onchange = () => updateEdgePortLabels(id);\n       }\n       const list = document.getElementById(\"edge-notes\");\n       list.innerHTML = \"\";\n       edge.notes.forEach((note, i) => {\n        const li = document.createElement(\"li\");\n        const txt = document.createElement(\"span\");\n        txt.textContent = note;\n        txt.style.flex = \"1\";\n        const del = document.createElement(\"span\");\n        del.className = \"delete-note\";\n        del.textContent = \"✕\";\n        del.addEventListener(\"click\", (e) => {\n         e.stopPropagation();\n         challengeTheImmortal(\"Delete this line note?\", () => {\n          pushUndo(\"delete edge note\");\n          edge.notes.splice(i, 1);\n          selectTheConnection(id);\n         });\n        });\n        txt.addEventListener(\"dblclick\", () => {\n         txt.classList.add(\"editing\");\n         txt.contentEditable = true;\n         txt.focus();\n        });\n        txt.addEventListener(\"blur\", () => {\n         txt.classList.remove(\"editing\");\n         txt.contentEditable = false;\n         pushUndo(\"edit edge note\");\n         edge.notes[i] = txt.textContent;\n        });\n        li.append(txt, del);\n        list.appendChild(li);\n       });\n       if (edge.type === \"custom\" && Array.isArray(edge.points)) {\n        forgeTheTopology();\n       }\n      }\n      window.addEventListener(\"resize\", () => {\n       forgeTheTopology();\n       if (currentEdgeId) {\n        selectTheConnection(currentEdgeId);\n       } else if (currentNodeId && NODE_DATA[currentNodeId]) {\n        claimTheImmortal(currentNodeId);\n       } else {\n        const availableNodes = Object.keys(NODE_DATA);\n        if (availableNodes.length > 0) {\n         claimTheImmortal(availableNodes[0]);\n        }\n       }\n      });\n      (function initZoomPan() {\n       const viewport = document.getElementById(\"canvas-viewport\");\n       const svg = document.getElementById(\"map\");\n       const hint = document.getElementById(\"canvas-hint\");\n       hint.style.cursor = \"pointer\";\n       let hintDismissed = false;\n       const dismissHint = () => { hintDismissed = true; hint.classList.remove(\"visible\"); };\n       hint.addEventListener(\"click\", dismissHint);\n       hint.addEventListener(\"touchend\", (e) => { e.preventDefault(); dismissHint(); });\n       setTimeout(() => {\n        if (hintDismissed) return;\n        hint.classList.add(\"visible\");\n        setTimeout(() => { if (!hintDismissed) hint.classList.remove(\"visible\"); }, 4000);\n       }, 1000);\n       viewport.addEventListener(\"wheel\",\n        (e) => {\n         e.preventDefault();\n         const rect = viewport.getBoundingClientRect();\n         const mouseX = (e.clientX - rect.left) / rect.width;\n         const mouseY = (e.clientY - rect.top) / rect.height;\n         const delta = e.deltaY > 0 ? 0.9 : 1.1;\n         zoomTo(canvasState.zoom * delta, mouseX, mouseY);\n        }, {\n         passive: false\n        });\n       let initialPinchDistance = 0;\n       let initialPinchZoom = 1;\n       let pinchCenter = {\n        x: 0.5,\n        y: 0.5\n       };\n       let threeFingerTapStart = 0;\n       viewport.addEventListener(\"touchstart\",\n        (e) => {\n         if (e.touches.length === 3) {\n          e.preventDefault();\n          threeFingerTapStart = Date.now();\n         }\n         if (e.touches.length === 2) {\n          e.preventDefault();\n          const touch1 = e.touches[0];\n          const touch2 = e.touches[1];\n          initialPinchDistance = Math.hypot(touch2.clientX - touch1.clientX, touch2.clientY - touch1.clientY);\n          initialPinchZoom = canvasState.zoom;\n          const rect = viewport.getBoundingClientRect();\n          const centerX = (touch1.clientX + touch2.clientX) / 2;\n          const centerY = (touch1.clientY + touch2.clientY) / 2;\n          pinchCenter.x = (centerX - rect.left) / rect.width;\n          pinchCenter.y = (centerY - rect.top) / rect.height;\n         }\n        }, {\n         passive: false\n        });\n       viewport.addEventListener(\"touchend\", (e) => {\n        if (e.touches.length === 0 && threeFingerTapStart > 0) {\n         const duration = Date.now() - threeFingerTapStart;\n         if (duration < 500) {\n          e.preventDefault();\n          undo();\n          if (navigator.vibrate) navigator.vibrate([50, 30, 50]);\n         }\n         threeFingerTapStart = 0;\n        }\n       }, { passive: false });\n       viewport.addEventListener(\"touchmove\",\n        (e) => {\n         if (e.touches.length === 2) {\n          e.preventDefault();\n          const touch1 = e.touches[0];\n          const touch2 = e.touches[1];\n          const currentDistance = Math.hypot(touch2.clientX - touch1.clientX, touch2.clientY - touch1.clientY);\n          if (initialPinchDistance > 0) {\n           const scale = currentDistance / initialPinchDistance;\n           const newZoom = initialPinchZoom * scale;\n           zoomTo(newZoom, pinchCenter.x, pinchCenter.y);\n          }\n         }\n        }, {\n         passive: false\n        });\n       let panStartViewX = 0;\n       let panStartViewY = 0;\n       let lastEmptyTapTime = 0;\n       let emptyTapTimeout = null;\n       let emptyTapMoved = false;\n       let emptyTapStartX = 0;\n       let emptyTapStartY = 0;\n       viewport.addEventListener(\"touchend\", (e) => {\n         if (currentView.mode !== \"rack\") return;\n         if (e.changedTouches.length !== 1) return;\n      const isNodeOrEdge = e.target.closest(\".node-group\") || e.target.closest(\".edge-group\");\n         if (isNodeOrEdge) return;\n         if (emptyTapMoved) {\n           emptyTapMoved = false;\n           return;\n         }\n         const currentTime = new Date().getTime();\n         const tapGap = currentTime - lastEmptyTapTime;\n         if (tapGap < 300 && tapGap > 0) {\n           e.preventDefault();\n           exitRack();\n           if (navigator.vibrate) {\n             navigator.vibrate(50);\n           }\n           lastEmptyTapTime = 0;\n           if (emptyTapTimeout) {\n             clearTimeout(emptyTapTimeout);\n             emptyTapTimeout = null;\n           }\n         } else {\n           lastEmptyTapTime = currentTime;\n           if (emptyTapTimeout) clearTimeout(emptyTapTimeout);\n           emptyTapTimeout = setTimeout(() => {\n             lastEmptyTapTime = 0;\n           }, 300);\n         }\n       }, { passive: false });\n       viewport.addEventListener(\"mousedown\", (e) => {\n        if (e.target.closest(\".draw-toolbar\") || e.target.closest(\".topology-toolbar\") || e.target.closest(\".legend-container\")) {\n         return;\n        }\n        if (freeDrawMode || rectDrawMode) {\n         return;\n        }\n      const isEmptySpace = e.target === svg || e.target === viewport || e.target.tagName === \"svg\";\n      const isEdgeElement = e.target.closest('.edge') || e.target.classList.contains('edge-edit-point') || e.target.dataset.edgeId;\n      const isTextElement = e.target.closest('.text-group') || e.target.closest('.text-element');\n      const isRectElement = e.target.closest('.rect-group');\n      if (!isEdgeElement && !isTextElement && !isRectElement && currentEdgeId) {\n      currentEdgeId = null;\n      forgeTheTopology();\n      }\n      if (!isRectElement && currentRectId) {\n      currentRectId = null;\n      document.getElementById(\"rect-panel\").style.display = \"none\";\n      forgeTheTopology();\n      }\n\t  if (isViewOnly()) {\n         document.body.classList.remove(\"view-only-inspect\");\n         document.getElementById(\"node-panel\").style.display = \"none\";\n         document.getElementById(\"edge-panel\").style.display = \"none\";\n        }\n        if (isEmptySpace && e.shiftKey && e.button === 0) {\n         if (isViewOnly()) return;\n         e.preventDefault();\n         startSelection(e);\n         return;\n        }\n        if (isEmptySpace || e.button === 2 || e.button === 1) {\n         e.preventDefault();\n         canvasState.isPanning = true;\n         canvasState.panStartX = e.clientX;\n         canvasState.panStartY = e.clientY;\n         panStartViewX = canvasState.panX;\n         panStartViewY = canvasState.panY;\n         viewport.classList.add(\"panning\");\n        }\n       });\n       viewport.addEventListener(\"touchstart\",\n        (e) => {\n         if (e.target.closest(\".draw-toolbar\") || e.target.closest(\".topology-toolbar\") || e.target.closest(\".legend-container\")) {\n          return;\n         }\n         if (freeDrawMode || rectDrawMode) {\n          return;\n         }\n      const isEmptySpace = e.target === svg || e.target === viewport || e.target.tagName === \"svg\";\n      const isEdgeElement = e.target.closest('.edge') || e.target.classList.contains('edge-edit-point') || e.target.dataset.edgeId;\n      const isTextElement = e.target.closest('.text-group') || e.target.closest('.text-element');\n      const isRectElement = e.target.closest('.rect-group');\n      if (!isEdgeElement && !isTextElement && !isRectElement && currentEdgeId) {\n      currentEdgeId = null;\n      forgeTheTopology();\n      }\n      if (!isRectElement && currentRectId) {\n      currentRectId = null;\n      document.getElementById(\"rect-panel\").style.display = \"none\";\n      forgeTheTopology();\n      }\n         if (e.touches.length === 1) {\n           e.preventDefault();\n           emptyTapMoved = false;\n           emptyTapStartX = e.touches[0].clientX;\n           emptyTapStartY = e.touches[0].clientY;\n           canvasState.isPanning = true;\n           canvasState.panStartX = e.touches[0].clientX;\n           canvasState.panStartY = e.touches[0].clientY;\n           panStartViewX = canvasState.panX;\n           panStartViewY = canvasState.panY;\n           viewport.classList.add(\"panning\");\n         }\n        }, {\n         passive: false\n        });\n       let panRAFPending = false;\n       let lastPanEvent = null;\n       document.addEventListener(\"mousemove\", (e) => {\n        if (isSelecting) {\n         updateSelection(e);\n         return;\n        }\n        if (!canvasState.isPanning) return;\n        lastPanEvent = e;\n        if (!panRAFPending) {\n         panRAFPending = true;\n         requestAnimationFrame(() => {\n          if (lastPanEvent && canvasState.isPanning) {\n           const dx = lastPanEvent.clientX - canvasState.panStartX;\n           const dy = lastPanEvent.clientY - canvasState.panStartY;\n           const rect = viewport.getBoundingClientRect();\n           const viewWidth = CANVAS_WIDTH / canvasState.zoom;\n           const viewHeight = CANVAS_HEIGHT / canvasState.zoom;\n           const canvasDx = (dx / rect.width) * viewWidth;\n           const canvasDy = (dy / rect.height) * viewHeight;\n           canvasState.panX = panStartViewX - canvasDx;\n           canvasState.panY = panStartViewY - canvasDy;\n           constrainPan();\n           updateViewBox();\n          }\n          panRAFPending = false;\n         });\n        }\n       });\n       document.addEventListener(\"touchmove\", (e) => {\n        if (!canvasState.isPanning || !e.touches[0]) return;\n        const touchX = e.touches[0].clientX;\n        const touchY = e.touches[0].clientY;\n        const moveDx = Math.abs(touchX - emptyTapStartX);\n        const moveDy = Math.abs(touchY - emptyTapStartY);\n        if (moveDx > 15 || moveDy > 15) {\n         emptyTapMoved = true;\n        }\n        if (!panRAFPending) {\n         panRAFPending = true;\n         requestAnimationFrame(() => {\n          if (canvasState.isPanning) {\n           const dx = touchX - canvasState.panStartX;\n           const dy = touchY - canvasState.panStartY;\n           const rect = viewport.getBoundingClientRect();\n           const viewWidth = CANVAS_WIDTH / canvasState.zoom;\n           const viewHeight = CANVAS_HEIGHT / canvasState.zoom;\n           const canvasDx = (dx / rect.width) * viewWidth;\n           const canvasDy = (dy / rect.height) * viewHeight;\n           canvasState.panX = panStartViewX - canvasDx;\n           canvasState.panY = panStartViewY - canvasDy;\n           constrainPan();\n           updateViewBox();\n          }\n          panRAFPending = false;\n         });\n        }\n       });\n       document.addEventListener(\"mouseup\", () => {\n        if (isSelecting) {\n         endSelection();\n        }\n        if (canvasState.isPanning) {\n         canvasState.isPanning = false;\n         viewport.classList.remove(\"panning\");\n        }\n       });\n       document.addEventListener(\"touchend\", () => {\n        if (canvasState.isPanning) {\n         canvasState.isPanning = false;\n         viewport.classList.remove(\"panning\");\n        }\n       });\n       document.addEventListener(\"keydown\", (e) => {\n        const isEditing = document.activeElement.tagName === \"INPUT\" || document.activeElement.tagName === \"TEXTAREA\" || document.activeElement.isContentEditable;\n        if (e.code === \"Space\" && !e.repeat && !isEditing) {\n         e.preventDefault();\n         canvasState.spacePressed = true;\n         viewport.style.cursor = \"grab\";\n        }\n       });\n       document.addEventListener(\"keyup\", (e) => {\n        if (e.code === \"Space\") {\n         canvasState.spacePressed = false;\n         viewport.style.cursor = \"\";\n        }\n       });\n       document.getElementById(\"zoom-in-btn\").addEventListener(\"click\", () => {\n        zoomTo(canvasState.zoom * 1.25, 0.5, 0.5);\n       });\n       document.getElementById(\"zoom-out-btn\").addEventListener(\"click\", () => {\n        zoomTo(canvasState.zoom * 0.8, 0.5, 0.5);\n       });\n       document.getElementById(\"zoom-fit-btn\").addEventListener(\"click\", fitToContent);\n       document.getElementById(\"zoom-reset-btn\").addEventListener(\"click\", resetView);\n       const minimapContainer = document.getElementById(\"minimap-container\");\n       const minimapSvg = document.getElementById(\"minimap\");\n       let minimapDragging = false;\n       minimapContainer.addEventListener(\"mousedown\", (e) => {\n        e.preventDefault();\n        minimapDragging = true;\n        updatePanFromMinimap(e);\n       });\n       minimapContainer.addEventListener(\"touchstart\",\n        (e) => {\n         e.preventDefault();\n         minimapDragging = true;\n         updatePanFromMinimapTouch(e);\n        }, {\n         passive: false\n        });\n       document.addEventListener(\"mousemove\", (e) => {\n        if (minimapDragging) {\n         updatePanFromMinimap(e);\n        }\n       });\n       document.addEventListener(\"touchmove\", (e) => {\n        if (minimapDragging && e.touches[0]) {\n         updatePanFromMinimapTouch(e);\n        }\n       });\n       document.addEventListener(\"mouseup\", () => {\n        minimapDragging = false;\n       });\n       document.addEventListener(\"touchend\", () => {\n        minimapDragging = false;\n       });\n       function updatePanFromMinimap(e) {\n        const rect = minimapContainer.getBoundingClientRect();\n        const x = (e.clientX - rect.left) / rect.width;\n        const y = (e.clientY - rect.top) / rect.height;\n        const viewWidth = CANVAS_WIDTH / canvasState.zoom;\n        const viewHeight = CANVAS_HEIGHT / canvasState.zoom;\n        canvasState.panX = x * CANVAS_WIDTH - viewWidth / 2;\n        canvasState.panY = y * CANVAS_HEIGHT - viewHeight / 2;\n        constrainPan();\n        updateViewBox();\n       }\n       function updatePanFromMinimapTouch(e) {\n        const rect = minimapContainer.getBoundingClientRect();\n        const touch = e.touches[0];\n        const x = (touch.clientX - rect.left) / rect.width;\n        const y = (touch.clientY - rect.top) / rect.height;\n        const viewWidth = CANVAS_WIDTH / canvasState.zoom;\n        const viewHeight = CANVAS_HEIGHT / canvasState.zoom;\n        canvasState.panX = x * CANVAS_WIDTH - viewWidth / 2;\n        canvasState.panY = y * CANVAS_HEIGHT - viewHeight / 2;\n        constrainPan();\n        updateViewBox();\n       }\n       document.addEventListener(\"keydown\", (e) => {\n        if (document.activeElement.tagName === \"INPUT\" || document.activeElement.tagName === \"TEXTAREA\") return;\n        if (\n         (e.key === \"+\" || e.key === \"=\") && (e.ctrlKey || e.metaKey)) {\n         e.preventDefault();\n         zoomTo(canvasState.zoom * 1.25, 0.5, 0.5);\n        } else if (e.key === \"-\" && (e.ctrlKey || e.metaKey)) {\n         e.preventDefault();\n         zoomTo(canvasState.zoom * 0.8, 0.5, 0.5);\n        } else if (e.key === \"0\" && (e.ctrlKey || e.metaKey)) {\n         e.preventDefault();\n         resetView();\n        }\n       });\n       setTimeout(() => {\n        fitToContent();\n       }, 100);\n      })();\n      const sizeSlider = document.getElementById(\"size-slider\");\n      const sizeValue = document.getElementById(\"size-value\");\n      const resetSizeBtn = document.getElementById(\"reset-size\");\n      sizeSlider.addEventListener(\"input\", () => {\n       const newSize = parseInt(sizeSlider.value, 10);\n       sizeValue.textContent = newSize;\n       pushUndo(\"resize node\");\n       savedSizes[currentNodeId] = newSize;\n       const nodeGroup = document.querySelector(`g[data-node-id=\"${currentNodeId}\"]`);\n       if (nodeGroup) {\n        const oldShape = nodeGroup.querySelector(\".node-circle\");\n        if (oldShape) oldShape.remove();\n        const shapeType = (NODE_DATA[currentNodeId] && NODE_DATA[currentNodeId].shape) || \"circle\";\n        const newShape = createShapeElement(shapeType, newSize);\n        newShape.classList.add(\"node-circle\");\n        const styles = resolveStylesForNode(currentNodeId);\n        if (styles.circleColor) newShape.style.fill = styles.circleColor;\n        if (styles.circleBorder) newShape.style.stroke = styles.circleBorder;\n        nodeGroup.insertBefore(newShape, nodeGroup.firstChild);\n        const label = nodeGroup.querySelector(\".node-label\");\n        const sub = nodeGroup.querySelector(\".node-sub\");\n        if (label) {\n         label.setAttribute(\"y\", -newSize * 0.28);\n         const labelSize = styles.titleSize || newSize * 0.33;\n         label.style.fontSize = labelSize + \"px\";\n        }\n        if (sub) {\n         sub.setAttribute(\"y\", newSize * 0.4);\n         const subSize = styles.subSize || newSize * 0.24;\n         sub.style.fontSize = subSize + \"px\";\n        }\n       }\n       updateMinimap();\n      });\n      resetSizeBtn.addEventListener(\"click\", () => {\n       pushUndo(\"reset size\");\n       delete savedSizes[currentNodeId];\n       const defaultSize = getDefaultSize();\n       sizeSlider.value = defaultSize;\n       sizeValue.textContent = defaultSize;\n       const nodeGroup = document.querySelector(`g[data-node-id=\"${currentNodeId}\"]`);\n       if (nodeGroup) {\n        const oldShape = nodeGroup.querySelector(\".node-circle\");\n        if (oldShape) oldShape.remove();\n        const shapeType = (NODE_DATA[currentNodeId] && NODE_DATA[currentNodeId].shape) || \"circle\";\n        const newShape = createNodeShape(currentNodeId, defaultSize);\n        nodeGroup.insertBefore(newShape, nodeGroup.firstChild);\n        const styles = resolveStylesForNode(currentNodeId);\n        if (styles.circleColor) newShape.style.fill = styles.circleColor;\n        if (styles.circleBorder) newShape.style.stroke = styles.circleBorder;\n        const label = nodeGroup.querySelector(\".node-label\");\n        const sub = nodeGroup.querySelector(\".node-sub\");\n        if (label) {\n         label.setAttribute(\"y\", -defaultSize * 0.28);\n         const labelSize = styles.titleSize || defaultSize * 0.33;\n         label.style.fontSize = labelSize + \"px\";\n        }\n        if (sub) {\n         sub.setAttribute(\"y\", defaultSize * 0.4);\n         const subSize = styles.subSize || defaultSize * 0.24;\n         sub.style.fontSize = subSize + \"px\";\n        }\n       }\n       updateMinimap();\n      });\n      const rotationSlider = document.getElementById(\"rotation-slider\");\n      const rotationInput = document.getElementById(\"rotation-value\");\n      const resetRotationBtn = document.getElementById(\"reset-rotation\");\n      rotationSlider.addEventListener(\"input\", () => {\n        const newRotation = parseInt(rotationSlider.value, 10);\n        rotationInput.value = newRotation;\n        pushUndo(\"rotate node\");\n        NODE_DATA[currentNodeId].rotation = newRotation;\n        const nodeGroup = document.querySelector(`g[data-node-id=\"${currentNodeId}\"]`);\n        if (nodeGroup) {\n          const pos = savedPositions[currentNodeId] || { x: 0, y: 0 };\n          nodeGroup.setAttribute(\"transform\", `translate(${pos.x},${pos.y}) rotate(${newRotation})`);\n        }\n      });\n      rotationInput.addEventListener(\"input\", () => {\n        const newRotation = parseInt(rotationInput.value, 10) || 0;\n        rotationSlider.value = Math.max(-360, Math.min(360, newRotation));\n        pushUndo(\"rotate node\");\n        NODE_DATA[currentNodeId].rotation = newRotation;\n        const nodeGroup = document.querySelector(`g[data-node-id=\"${currentNodeId}\"]`);\n        if (nodeGroup) {\n          const pos = savedPositions[currentNodeId] || { x: 0, y: 0 };\n          nodeGroup.setAttribute(\"transform\", `translate(${pos.x},${pos.y}) rotate(${newRotation})`);\n        }\n      });\n      resetRotationBtn.addEventListener(\"click\", () => {\n        pushUndo(\"reset rotation\");\n        NODE_DATA[currentNodeId].rotation = 0;\n        rotationSlider.value = 0;\n        rotationInput.value = 0;\n        const nodeGroup = document.querySelector(`g[data-node-id=\"${currentNodeId}\"]`);\n        if (nodeGroup) {\n          const pos = savedPositions[currentNodeId] || { x: 0, y: 0 };\n          nodeGroup.setAttribute(\"transform\", `translate(${pos.x},${pos.y}) rotate(0)`);\n        }\n      });\n      const applyStyle = (property, value) => {\n       pushUndo(\"style change\");\n       const styleEntry = ensureStyleEntry(currentNodeId);\n       const scopeKey = currentStyleScope || \"all\";\n       if (!styleEntry[scopeKey]) styleEntry[scopeKey] = {};\n       styleEntry[scopeKey][property] = value;\n       const nodeGroup = document.querySelector(`g[data-node-id=\"${currentNodeId}\"]`);\n       if (!nodeGroup) return;\n       const shapeEl = nodeGroup.querySelector(\".node-circle\");\n       const label = nodeGroup.querySelector(\".node-label\");\n       const sub = nodeGroup.querySelector(\".node-sub\");\n       if (property === \"circleColor\" && shapeEl) shapeEl.style.fill = value;\n      else if (property === \"circleBorder\" && shapeEl) shapeEl.style.stroke = value;\n       else if (property === \"titleColor\" && label) label.style.fill = value;\n       else if (property === \"titleFont\" && label) label.style.fontFamily = value;\n       else if (property === \"titleSize\" && label) label.style.fontSize = value + \"px\";\n       else if (property === \"subColor\" && sub) sub.style.fill = value;\n       else if (property === \"subFont\" && sub) sub.style.fontFamily = value;\n       else if (property === \"subSize\" && sub) sub.style.fontSize = value + \"px\";\n      };\n      document.getElementById(\"circle-color\").addEventListener(\"input\", (e) => applyStyle(\"circleColor\", e.target.value));\n      document.getElementById(\"circle-border\").addEventListener(\"input\", (e) => applyStyle(\"circleBorder\", e.target.value));\n      document.getElementById(\"title-color\").addEventListener(\"input\", (e) => applyStyle(\"titleColor\", e.target.value));\n      document.getElementById(\"title-font\").addEventListener(\"change\", (e) => applyStyle(\"titleFont\", e.target.value));\n      document.getElementById(\"title-size\").addEventListener(\"input\", (e) => applyStyle(\"titleSize\", parseInt(e.target.value, 10)));\n      document.getElementById(\"sub-color\").addEventListener(\"input\", (e) => applyStyle(\"subColor\", e.target.value));\n      document.getElementById(\"sub-font\").addEventListener(\"change\", (e) => applyStyle(\"subFont\", e.target.value));\n      document.getElementById(\"sub-size\").addEventListener(\"input\", (e) => applyStyle(\"subSize\", parseInt(e.target.value, 10)));\n      document.getElementById(\"title-offset-y\").addEventListener(\"input\", (e) => {\n       applyStyle(\"titleOffsetY\", parseInt(e.target.value, 10) || 0);\n       forgeTheTopology();\n       if (currentNodeId) claimTheImmortal(currentNodeId);\n      });\n      document.getElementById(\"title-offset-x\").addEventListener(\"input\", (e) => {\n       applyStyle(\"titleOffsetX\", parseInt(e.target.value, 10) || 0);\n       forgeTheTopology();\n       if (currentNodeId) claimTheImmortal(currentNodeId);\n      });\n      document.getElementById(\"sub-offset-y\").addEventListener(\"input\", (e) => {\n       applyStyle(\"subOffsetY\", parseInt(e.target.value, 10) || 0);\n       forgeTheTopology();\n       if (currentNodeId) claimTheImmortal(currentNodeId);\n      });\n      document.getElementById(\"sub-offset-x\").addEventListener(\"input\", (e) => {\n       applyStyle(\"subOffsetX\", parseInt(e.target.value, 10) || 0);\n       forgeTheTopology();\n       if (currentNodeId) claimTheImmortal(currentNodeId);\n      });\n      document.getElementById(\"reset-styles\").addEventListener(\"click\", () => {\n       delete savedStyles[currentNodeId];\n       forgeTheTopology();\n       claimTheImmortal(currentNodeId);\n      });\n      document.getElementById(\"style-scope\").addEventListener(\"change\", (e) => {\n       currentStyleScope = e.target.value || \"all\";\n       claimTheImmortal(currentNodeId);\n      });\n      document.getElementById(\"shape-select\").addEventListener(\"change\", (e) => {\n       const shape = e.target.value || \"circle\";\n       pushUndo(\"change shape\");\n       NODE_DATA[currentNodeId].shape = shape;\nconst fovSection = document.getElementById(\"fov-section\");\nif (fovSection) {\n  if (hasCoverageZone(shape)) {\n    const defaults = getCoverageDefaults(shape);\n    fovSection.style.display = \"block\";\n    document.getElementById(\"fov-angle\").value = defaults.angle;\n    document.getElementById(\"fov-angle-value\").textContent = defaults.angle + \"°\";\n    document.getElementById(\"fov-distance\").value = defaults.distance;\n    document.getElementById(\"fov-distance-value\").textContent = defaults.distance;\n    document.getElementById(\"fov-animation-type\").value = defaults.animationType;\n  } else {\n    fovSection.style.display = \"none\";\n  }\n}\n       const nodeGroup = document.querySelector(`g[data-node-id=\"${currentNodeId}\"]`);\n       if (!nodeGroup) return;\n       const oldShape = nodeGroup.querySelector(\".node-circle\");\n       if (oldShape) oldShape.remove();\n       const size = savedSizes[currentNodeId] || getDefaultSize();\n       const newShape = createNodeShape(currentNodeId, size);\n       nodeGroup.insertBefore(newShape, nodeGroup.firstChild);\n      });\n      const addNoteBtn = document.getElementById(\"add-note-btn\");\n      const noteInput = document.getElementById(\"new-note-input\");\n      addNoteBtn.addEventListener(\"click\", () => {\n       const newNote = noteInput.value.trim();\n       if (newNote && currentNodeId) {\n\t    pushUndo(\"add note\");\n        NODE_DATA[currentNodeId].notes.push(newNote);\n        claimTheImmortal(currentNodeId);\n        noteInput.value = \"\";\n       }\n      });\n      noteInput.addEventListener(\"keypress\", (e) => {\n       if (e.key === \"Enter\") {\n        addNoteBtn.click();\n       }\n      });\n      document.getElementById(\"edge-width\").addEventListener(\"input\", () => {\n       if (!currentEdgeId) return;\n       const edge = EDGE_DATA.list.find((e) => e.id === currentEdgeId);\n       if (!edge) return;\n       const v = parseInt(document.getElementById(\"edge-width\").value, 10);\n       if (Number.isNaN(v) || v <= 0) return;\n       pushUndo(\"edit edge\");\n       edge.width = v;\n       const el = document.querySelector(`.edge[data-edge-id=\"${currentEdgeId}\"]`);\n       if (el) el.style.strokeWidth = v;\n      });\n      document.getElementById(\"edge-color\").addEventListener(\"input\", () => {\n       if (!currentEdgeId) return;\n       const edge = EDGE_DATA.list.find((e) => e.id === currentEdgeId);\n       if (!edge) return;\n       const color = document.getElementById(\"edge-color\").value;\n       pushUndo(\"edit edge\");\n       edge.color = color;\n       const el = document.querySelector(`.edge[data-edge-id=\"${currentEdgeId}\"]`);\n       if (el) el.style.stroke = color;\n       forgeTheLegend();\n      });\n      document.getElementById(\"edge-direction\").addEventListener(\"change\", () => {\n       if (!currentEdgeId) return;\n       const edge = EDGE_DATA.list.find((e) => e.id === currentEdgeId);\n       if (!edge) return;\n       pushUndo(\"edit edge\");\n       edge.direction = document.getElementById(\"edge-direction\").value;\n       forgeTheTopology();\n       selectTheConnection(currentEdgeId);\n      });\n      document.getElementById(\"edge-line-style\").addEventListener(\"change\", () => {\n       if (!currentEdgeId) return;\n       const edge = EDGE_DATA.list.find((e) => e.id === currentEdgeId);\n       if (!edge) return;\n       pushUndo(\"edit edge\");\n       edge.lineStyle = document.getElementById(\"edge-line-style\").value;\n       forgeTheTopology();\n       selectTheConnection(currentEdgeId);\n      });\n      document.getElementById(\"edge-routing\").addEventListener(\"change\", () => {\n       if (!currentEdgeId) return;\n       const edge = EDGE_DATA.list.find((e) => e.id === currentEdgeId);\n       if (!edge) return;\n       pushUndo(\"edit edge routing\");\n       edge.routing = document.getElementById(\"edge-routing\").value;\n       forgeTheTopology();\n       selectTheConnection(currentEdgeId);\n      });\n      document.getElementById(\"edge-animate\").addEventListener(\"change\", (e) => {\n       if (!currentEdgeId) return;\n       const edge = EDGE_DATA.list.find((ed) => ed.id === currentEdgeId);\n       if (!edge) return;\n       pushUndo(\"edit edge animate\");\n       edge.animate = e.target.checked;\n       forgeTheTopology();\n       selectTheConnection(currentEdgeId);\n      });\n      document.getElementById(\"edge-animation-speed\").addEventListener(\"change\", (e) => {\n       if (!currentEdgeId) return;\n       const edge = EDGE_DATA.list.find((ed) => ed.id === currentEdgeId);\n       if (!edge) return;\n       pushUndo(\"edit edge animation speed\");\n       edge.animationSpeed = e.target.value || \"\";\n       if (PAGE_STATE.animateConnections && edge.animate !== false) forgeTheTopology();\n       selectTheConnection(currentEdgeId);\n      });\n      const addEdgeNoteBtn = document.getElementById(\"add-edge-note\");\n      const newEdgeNoteInput = document.getElementById(\"new-edge-note\");\n      addEdgeNoteBtn.addEventListener(\"click\", () => {\n       const txt = newEdgeNoteInput.value.trim();\n       if (!txt || !currentEdgeId) return;\n       const edge = EDGE_DATA.list.find((e) => e.id === currentEdgeId);\n       if (!edge) return;\n\t   pushUndo(\"add edge note\");\n       edge.notes.push(txt);\n       newEdgeNoteInput.value = \"\";\n       selectTheConnection(currentEdgeId);\n      });\n      newEdgeNoteInput.addEventListener(\"keypress\", (e) => {\n       if (e.key === \"Enter\") {\n        addEdgeNoteBtn.click();\n       }\n      });\n      function selectTheRect(id) {\n\t  if (isViewOnly()) return;\n      currentRectId = id;\n      currentNodeId = null;\n      currentEdgeId = null;\n      currentTextId = null;\n      document.getElementById(\"node-panel\").style.display = \"none\";\n      document.getElementById(\"edge-panel\").style.display = \"none\";\n      document.getElementById(\"text-panel\").style.display = \"none\";\n      document.getElementById(\"rect-panel\").style.display = \"block\";\n      document.getElementById(\"topology-toolbar\").style.display = \"none\";\n      document.querySelectorAll(\".node-group\").forEach(n => n.classList.remove(\"active\"));\n      document.querySelectorAll(\".edge\").forEach(e => e.classList.remove(\"active\"));\n      document.querySelectorAll(\".text-element\").forEach(t => t.classList.remove(\"active\"));\n      document.querySelectorAll(\".rect-group\").forEach(r => r.classList.toggle(\"active\", r.dataset.rectId === id));\n      const rect = RECT_DATA.list.find(r => r.id === id);\n      if (!rect) return;\n      document.getElementById(\"rect-title\").textContent = \"\";\n      document.getElementById(\"rect-color\").value = rect.color || \"#f97316\";\n      document.getElementById(\"rect-border-color\").value = rect.borderColor || rect.color || \"#f97316\";\n      document.getElementById(\"rect-border-width\").value = rect.borderWidth !== undefined ? rect.borderWidth : 2;\n      document.getElementById(\"rect-style-select\").value = rect.style || \"filled\";\n      document.getElementById(\"rect-line-style\").value = rect.lineStyle || \"solid\";\n\t  document.getElementById(\"rect-rotation\").value = Math.max(-360, Math.min(360, rect.rotation || 0));\n      document.getElementById(\"rect-rotation-value\").value = rect.rotation || 0;\n      document.getElementById(\"rect-fill-row\").style.display = (rect.style === \"outlined\") ? \"none\" : \"flex\";\n      const list = document.getElementById(\"rect-notes\");\n      list.innerHTML = \"\";\n      (rect.notes || []).forEach((note, i) => {\n      const li = document.createElement(\"li\");\n      const txt = document.createElement(\"span\");\n      txt.textContent = note;\n      txt.style.flex = \"1\";\n      const del = document.createElement(\"span\");\n      del.className = \"delete-note\";\n      del.textContent = \"✕\";\n       del.addEventListener(\"click\", (e) => {\n      e.stopPropagation();\n      challengeTheImmortal(\"Delete this note?\", () => {\n        pushUndo(\"delete zone note\");\n        rect.notes.splice(i, 1);\n        selectTheRect(id);\n      });\n      });\n      li.appendChild(txt);\n      li.appendChild(del);\n      list.appendChild(li);\n      });\n      forgeTheTopology();\n      }\n      document.getElementById(\"rect-color\").addEventListener(\"input\", () => {\n      if (!currentRectId) return;\n      const rect = RECT_DATA.list.find(r => r.id === currentRectId);\n      if (!rect) return;\n      pushUndo(\"edit zone\");\n      rect.color = document.getElementById(\"rect-color\").value;\n      forgeTheTopology();\n      });\n      document.getElementById(\"rect-border-color\").addEventListener(\"input\", () => {\n      if (!currentRectId) return;\n      const rect = RECT_DATA.list.find(r => r.id === currentRectId);\n      if (!rect) return;\n      pushUndo(\"edit zone\");\n      rect.borderColor = document.getElementById(\"rect-border-color\").value;\n      forgeTheTopology();\n      });\n      document.getElementById(\"rect-border-width\").addEventListener(\"input\", () => {\n      if (!currentRectId) return;\n      const rect = RECT_DATA.list.find(r => r.id === currentRectId);\n      if (!rect) return;\n      pushUndo(\"edit zone\");\n      rect.borderWidth = parseInt(document.getElementById(\"rect-border-width\").value) || 2;\n      forgeTheTopology();\n      });\n      document.getElementById(\"rect-style-select\").addEventListener(\"change\", () => {\n      if (!currentRectId) return;\n      const rect = RECT_DATA.list.find(r => r.id === currentRectId);\n      if (!rect) return;\n      pushUndo(\"edit zone\");\n      rect.style = document.getElementById(\"rect-style-select\").value;\n      forgeTheTopology();\n      selectTheRect(currentRectId);\n      });\ndocument.getElementById(\"rect-rotation\").addEventListener(\"input\", () => {\n        if (!currentRectId) return;\n        const rect = RECT_DATA.list.find(r => r.id === currentRectId);\n        if (!rect) return;\n        pushUndo(\"rotate zone\");\n        rect.rotation = parseInt(document.getElementById(\"rect-rotation\").value) || 0;\n        document.getElementById(\"rect-rotation-value\").value = rect.rotation;\n        forgeTheTopology();\n      });\n      document.getElementById(\"rect-rotation-value\").addEventListener(\"input\", () => {\n        if (!currentRectId) return;\n        const rect = RECT_DATA.list.find(r => r.id === currentRectId);\n        if (!rect) return;\n        pushUndo(\"rotate zone\");\n        const val = parseInt(document.getElementById(\"rect-rotation-value\").value) || 0;\n        rect.rotation = val;\n        document.getElementById(\"rect-rotation\").value = Math.max(-360, Math.min(360, val));\n        forgeTheTopology();\n      });\n      document.getElementById(\"rect-line-style\").addEventListener(\"change\", () => {\n      if (!currentRectId) return;\n      pushUndo(\"change zone line style\");\n      const rect = RECT_DATA.list.find(r => r.id === currentRectId);\n      if (!rect) return;\n      rect.lineStyle = document.getElementById(\"rect-line-style\").value;\n      forgeTheTopology();\n      });\n      document.getElementById(\"add-rect-note\").addEventListener(\"click\", () => {\n      const input = document.getElementById(\"new-rect-note\");\n      const txt = input.value.trim();\n      if (!txt || !currentRectId) return;\n      const rect = RECT_DATA.list.find(r => r.id === currentRectId);\n      if (!rect) return;\n      if (!rect.notes) rect.notes = [];\n\t  pushUndo(\"add zone note\");\n      rect.notes.push(txt);\n      input.value = \"\";\n      selectTheRect(currentRectId);\n      });\n      document.getElementById(\"new-rect-note\").addEventListener(\"keypress\", (e) => {\n      if (e.key === \"Enter\") {\n      document.getElementById(\"add-rect-note\").click();\n      }\n      });\n      document.getElementById(\"delete-rect\").addEventListener(\"click\", () => {\n      if (!currentRectId) return;\n      challengeTheImmortal(\"Delete this zone?\", () => {\n      pushUndo(\"delete zone\");\n      RECT_DATA.list = RECT_DATA.list.filter(r => r.id !== currentRectId);\n      currentRectId = null;\n      document.getElementById(\"rect-panel\").style.display = \"none\";\n      forgeTheTopology();\n      });\n      });\n      document.getElementById(\"delete-edge\").addEventListener(\"click\", () => {\n       if (!currentEdgeId) return;\n       challengeTheImmortal(\"Are you sure you want to delete this line?\",\n        () => {\n         pushUndo(\"delete edge\");\n         EDGE_DATA.list = EDGE_DATA.list.filter(\n          (e) => e.id !== currentEdgeId);\n         currentEdgeId = null;\n         forgeTheTopology();\n         const availableNodes = Object.keys(NODE_DATA);\n         if (availableNodes.length > 0) {\n          claimTheImmortal(availableNodes[0]);\n         } else {\n          document.getElementById(\"node-panel\").style.display = \"none\";\n          document.getElementById(\"edge-panel\").style.display = \"none\";\n          document.getElementById(\"topology-toolbar\", ).style.display = \"none\";\n         }\n        });\n      });\n      document.getElementById(\"add-line-btn\").addEventListener(\"click\", () => {\n       if (!currentNodeId) return;\n       const select = document.getElementById(\"add-line-select\");\n       const directionSelect = document.getElementById(\"add-line-direction\");\n       const colorInput = document.getElementById(\"add-line-color\");\n       const routingSelect = document.getElementById(\"add-line-routing\");\n       const targetId = select.value;\n       if (!targetId || targetId === currentNodeId) return;\n       const direction = directionSelect.value || \"none\";\n       const lineColor = colorInput.value || \"#475569\";\n       const routing = routingSelect.value || PAGE_STATE.defaultEdgeRouting || \"orthogonal\";\n       const newId = `${currentNodeId}-${targetId}-${Date.now()}`;\n       const newEdge = {\n        id: newId,\n        from: currentNodeId,\n        to: targetId,\n        width: 4,\n        color: lineColor,\n        direction: direction,\n        routing: routing,\n        type: \"main\",\n        notes: [],\n        fromPort: \"\",\n        toPort: \"\",\n        lineStyle: \"solid\",\n       };\n       pushUndo(\"add edge\");\n       EDGE_DATA.list.push(newEdge);\n       forgeTheTopology();\n       claimTheImmortal(currentNodeId);\n      });\n      let freeDrawPoints = [];\n      let freeDrawPolylineEl = null;\n      let freeDrawPointEls = [];\n       let rectStartPoint = null;\n       let rectPreviewEl = null;\n       let rectStyle = \"filled\";\n      const drawToggleBtn = document.getElementById(\"draw-toggle\");\n      const drawUndoBtn = document.getElementById(\"draw-undo\");\n      const drawColorInput = document.getElementById(\"draw-color\");\n      const drawStyleSelect = document.getElementById(\"draw-style\");\n      const drawArrowSelect = document.getElementById(\"draw-arrow\");\n      const svgMap = document.getElementById(\"map\");\n      function updateFreeDrawGraphics() {\n       const ns = \"http://www.w3.org/2000/svg\";\n       const svg = svgMap;\n       if (!freeDrawPolylineEl && freeDrawPoints.length > 0) {\n        freeDrawPolylineEl = document.createElementNS(ns, \"polyline\");\n        freeDrawPolylineEl.classList.add(\"edge\", \"free-preview\");\n        freeDrawPolylineEl.setAttribute(\"fill\", \"none\");\n        svg.appendChild(freeDrawPolylineEl);\n       }\n       if (freeDrawPolylineEl) {\n        if (freeDrawPoints.length === 0) {\n         freeDrawPolylineEl.remove();\n         freeDrawPolylineEl = null;\n        } else {\n         const ptsStr = freeDrawPoints.map((p) => `${p.x},${p.y}`).join(\" \");\n         freeDrawPolylineEl.setAttribute(\"points\", ptsStr);\n         freeDrawPolylineEl.style.stroke = drawColorInput.value || \"#475569\";\n         freeDrawPolylineEl.style.strokeWidth = 3;\n         const lineStyle = drawStyleSelect.value || \"solid\";\n         if (lineStyle === \"dashed\") {\n          freeDrawPolylineEl.style.strokeDasharray = \"10,5\";\n         } else if (lineStyle === \"dotted\") {\n          freeDrawPolylineEl.style.strokeDasharray = \"2,4\";\n         } else if (lineStyle === \"wall\") {\n  freeDrawPolylineEl.style.stroke = \"url(#wall-hatch)\";\n  freeDrawPolylineEl.style.strokeWidth = \"12\";\n  freeDrawPolylineEl.style.strokeDasharray = \"none\"; \n  } else {\n          freeDrawPolylineEl.style.strokeDasharray = \"none\";\n         }\n        }\n       }\n       freeDrawPointEls.forEach((el) => el.remove());\n       freeDrawPointEls = [];\n       freeDrawPoints.forEach((p, idx) => {\n        const c = document.createElementNS(ns, \"circle\");\n        c.classList.add(\"free-point\");\n        c.setAttribute(\"cx\", p.x);\n        c.setAttribute(\"cy\", p.y);\n        c.setAttribute(\"r\", 5);\n        c.dataset.index = String(idx);\n        c.addEventListener(\"mousedown\", (e) => {\n         if (!freeDrawMode) return;\n         e.preventDefault();\n         e.stopPropagation();\n         let dragging = true;\n         const svgEl = svgMap;\n         const moveHandler = (ev) => {\n          if (!dragging) return;\n          const pt = svgEl.createSVGPoint();\n          pt.x = ev.clientX;\n          pt.y = ev.clientY;\n          const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n          const i = parseInt(c.dataset.index, 10);\n          if (Number.isNaN(i) || !freeDrawPoints[i]) return;\n          freeDrawPoints[i].x = svgP.x;\n          freeDrawPoints[i].y = svgP.y;\n          updateFreeDrawGraphics();\n         };\n         const upHandler = () => {\n          dragging = false;\n          document.removeEventListener(\"mousemove\", moveHandler);\n          document.removeEventListener(\"mouseup\", upHandler);\n         };\n         document.addEventListener(\"mousemove\", moveHandler);\n         document.addEventListener(\"mouseup\", upHandler);\n        });\n        c.addEventListener(\"touchstart\",\n         (e) => {\n          if (!freeDrawMode) return;\n          e.preventDefault();\n          e.stopPropagation();\n          let dragging = true;\n          const svgEl = svgMap;\n          const touchMoveHandler = (ev) => {\n           if (!dragging || !ev.touches[0]) return;\n           const pt = svgEl.createSVGPoint();\n           pt.x = ev.touches[0].clientX;\n           pt.y = ev.touches[0].clientY;\n           const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n           const i = parseInt(c.dataset.index, 10);\n           if (Number.isNaN(i) || !freeDrawPoints[i]) return;\n           freeDrawPoints[i].x = svgP.x;\n           freeDrawPoints[i].y = svgP.y;\n           updateFreeDrawGraphics();\n          };\n          const touchUpHandler = () => {\n           dragging = false;\n           document.removeEventListener(\"touchmove\", touchMoveHandler);\n           document.removeEventListener(\"touchend\", touchUpHandler);\n          };\n          document.addEventListener(\"touchmove\", touchMoveHandler);\n          document.addEventListener(\"touchend\", touchUpHandler);\n         }, {\n          passive: false\n         });\n        svg.appendChild(c);\n        freeDrawPointEls.push(c);\n       });\n       drawUndoBtn.style.display = freeDrawPoints.length ? \"inline-block\" : \"none\";\n      }\n      function addFreeDrawPoint(x, y) {\n       freeDrawPoints.push({\n        x,\n        y\n       });\n       updateFreeDrawGraphics();\n      }\n      function startFreeDraw() {\n       freeDrawMode = true;\n       freeDrawPoints = [];\n       if (freeDrawPolylineEl) {\n        freeDrawPolylineEl.remove();\n        freeDrawPolylineEl = null;\n       }\n       freeDrawPointEls.forEach((el) => el.remove());\n       freeDrawPointEls = [];\n       svgMap.style.cursor = \"crosshair\";\n       drawToggleBtn.textContent = \"Done\";\n       drawToggleBtn.classList.add(\"done-btn-active\");\n       drawUndoBtn.style.display = \"none\";\n      }\n      function finishFreeDraw() {\n       freeDrawMode = false;\n       svgMap.style.cursor = \"\";\n       drawToggleBtn.textContent = \"✏️\";\n       drawToggleBtn.classList.remove(\"done-btn-active\");\n       if (freeDrawPoints.length >= 2) {\n        const color = drawColorInput.value || \"#475569\";\n        const lineStyle = drawStyleSelect.value || \"solid\";\n        const arrowDir = drawArrowSelect.value || \"none\";\n        const newId = \"custom-\" + Date.now();\n        const pointsCopy = freeDrawPoints.map((p) => ({\n         x: p.x,\n         y: p.y,\n        }));\n        EDGE_DATA.list.push({\n         id: newId,\n         type: \"custom\",\n         color,\n         width: 4,\n         lineStyle: lineStyle,\n         direction: arrowDir,\n         points: pointsCopy,\n         notes: [],\n        });\n        freeDrawPoints = [];\n        if (freeDrawPolylineEl) {\n         freeDrawPolylineEl.remove();\n         freeDrawPolylineEl = null;\n        }\n        freeDrawPointEls.forEach((el) => el.remove());\n        freeDrawPointEls = [];\n        forgeTheTopology();\n        selectTheConnection(newId);\n       } else {\n        freeDrawPoints = [];\n        if (freeDrawPolylineEl) {\n         freeDrawPolylineEl.remove();\n         freeDrawPolylineEl = null;\n        }\n        freeDrawPointEls.forEach((el) => el.remove());\n        freeDrawPointEls = [];\n        forgeTheLegend();\n       }\n       drawUndoBtn.style.display = \"none\";\n      }\n      drawToggleBtn.addEventListener(\"click\", () => {\n       if (currentView.mode === \"rack\") {\n        alert(\"Drawing tools are disabled in rack view. Exit to topology view to use drawing tools.\");\n        return;\n       }\n       if (freeDrawMode) {\n        finishFreeDraw();\n       } else {\n        startFreeDraw();\n       }\n      });\n      drawUndoBtn.addEventListener(\"click\", () => {\n       if (!freeDrawMode || !freeDrawPoints.length) return;\n       freeDrawPoints.pop();\n       updateFreeDrawGraphics();\n      });\n      const drawToolbar = document.getElementById(\"draw-toolbar\");\n      drawToolbar.addEventListener(\"mousedown\", (e) => {\n       e.stopPropagation();\n      });\n      drawToolbar.addEventListener(\"click\", (e) => {\n       if (e.target !== drawToggleBtn && e.target !== drawUndoBtn) {\n        e.stopPropagation();\n       }\n      });\n      drawStyleSelect.addEventListener(\"change\", () => {\n       if (freeDrawMode) {\n        updateFreeDrawGraphics();\n       }\n      });\n      drawArrowSelect.addEventListener(\"change\", () => {\n       if (freeDrawMode) {\n        updateFreeDrawGraphics();\n       }\n      });\n      drawColorInput.addEventListener(\"input\", () => {\n       if (freeDrawMode) {\n        updateFreeDrawGraphics();\n       }\n      });\n      drawStyleSelect.addEventListener(\"mousedown\", (e) => {\n       e.stopPropagation();\n      });\n      drawStyleSelect.addEventListener(\"click\", (e) => {\n       e.stopPropagation();\n      });\n      drawArrowSelect.addEventListener(\"mousedown\", (e) => {\n       e.stopPropagation();\n      });\n      drawArrowSelect.addEventListener(\"click\", (e) => {\n       e.stopPropagation();\n      });\n      drawColorInput.addEventListener(\"mousedown\", (e) => {\n       e.stopPropagation();\n      });\n      drawColorInput.addEventListener(\"click\", (e) => {\n       e.stopPropagation();\n      });\n      svgMap.addEventListener(\"click\", (e) => {\n       if (!freeDrawMode) return;\n       if (e.button !== 0) return;\n       const target = e.target;\n       if (target && target.classList && target.classList.contains(\"free-point\")) return;\n       const svgEl = svgMap;\n       const pt = svgEl.createSVGPoint();\n       pt.x = e.clientX;\n       pt.y = e.clientY;\n       const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n       addFreeDrawPoint(svgP.x, svgP.y);\n      });\n      svgMap.addEventListener(\"touchend\",\n       (e) => {\n        if (!freeDrawMode) return;\n        const target = e.target;\n        if (target && target.classList && target.classList.contains(\"free-point\")) return;\n        if (e.changedTouches && e.changedTouches[0]) {\n         e.preventDefault();\n         const svgEl = svgMap;\n         const pt = svgEl.createSVGPoint();\n         pt.x = e.changedTouches[0].clientX;\n         pt.y = e.changedTouches[0].clientY;\n         const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n         addFreeDrawPoint(svgP.x, svgP.y);\n        }\n       }, {\n        passive: false\n       });\n       const rectToggleBtn = document.getElementById(\"rect-toggle\");\n       const rectStyleSelect = document.getElementById(\"rect-style\");\n       function startRectDraw() {\n        rectDrawMode = true;\n        rectStartPoint = null;\n        rectPreviewEl = null;\n        svgMap.style.cursor = \"crosshair\";\n        rectToggleBtn.textContent = \"Done\";\n        rectToggleBtn.classList.add(\"done-btn-active\");\n        rectStyle = rectStyleSelect.value || \"filled\";\n        if (freeDrawMode) {\n         finishFreeDraw();\n        }\n        updateRectangleDeleteButtons();\n       }\n       function finishRectDraw() {\n        rectDrawMode = false;\n        svgMap.style.cursor = \"\";\n        rectToggleBtn.textContent = \"▭\";\n        rectToggleBtn.classList.remove(\"done-btn-active\");\n        if (rectPreviewEl) {\n         rectPreviewEl.remove();\n         rectPreviewEl = null;\n        }\n        rectStartPoint = null;\n        updateRectangleDeleteButtons();\n       }\n       rectToggleBtn.addEventListener(\"click\", () => {\n        if (currentView.mode === \"rack\") {\n         alert(\"Drawing tools are disabled in rack view. Exit to topology view to use drawing tools.\");\n         return;\n        }\n        if (rectDrawMode) {\n         finishRectDraw();\n        } else {\n         startRectDraw();\n        }\n       });\n       rectStyleSelect.addEventListener(\"mousedown\", (e) => {\n        e.stopPropagation();\n       });\n       rectStyleSelect.addEventListener(\"click\", (e) => {\n        e.stopPropagation();\n       });\n       rectStyleSelect.addEventListener(\"change\", () => {\n        if (rectDrawMode) {\n         rectStyle = rectStyleSelect.value || \"filled\";\n        }\n       });\n       svgMap.addEventListener(\"mousedown\", (e) => {\n        if (!rectDrawMode) return;\n        if (e.button !== 0) return;\n        e.preventDefault();\n        e.stopPropagation();\n        const svgEl = svgMap;\n        const pt = svgEl.createSVGPoint();\n        pt.x = e.clientX;\n        pt.y = e.clientY;\n        const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n        rectStartPoint = { x: svgP.x, y: svgP.y };\n        const ns = \"http://www.w3.org/2000/svg\";\n        rectPreviewEl = document.createElementNS(ns, \"rect\");\n        rectPreviewEl.classList.add(\"rect-preview\");\n        rectPreviewEl.setAttribute(\"x\", svgP.x);\n        rectPreviewEl.setAttribute(\"y\", svgP.y);\n        rectPreviewEl.setAttribute(\"width\", 0);\n        rectPreviewEl.setAttribute(\"height\", 0);\n        const color = drawColorInput.value || \"#f97316\";\n        rectPreviewEl.style.stroke = color;\n        rectPreviewEl.style.strokeWidth = 2;\n        rectPreviewEl.style.strokeDasharray = \"5,5\";\n        if (rectStyle === \"filled\") {\n         rectPreviewEl.style.fill = color;\n         rectPreviewEl.style.fillOpacity = \"0.3\";\n        } else {\n         rectPreviewEl.style.fill = \"none\";\n        }\n        svgEl.appendChild(rectPreviewEl);\n       });\n       svgMap.addEventListener(\"mousemove\", (e) => {\n        if (!rectDrawMode || !rectStartPoint || !rectPreviewEl) return;\n        const svgEl = svgMap;\n        const pt = svgEl.createSVGPoint();\n        pt.x = e.clientX;\n        pt.y = e.clientY;\n        const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n        const x = Math.min(rectStartPoint.x, svgP.x);\n        const y = Math.min(rectStartPoint.y, svgP.y);\n        const width = Math.abs(svgP.x - rectStartPoint.x);\n        const height = Math.abs(svgP.y - rectStartPoint.y);\n        rectPreviewEl.setAttribute(\"x\", x);\n        rectPreviewEl.setAttribute(\"y\", y);\n        rectPreviewEl.setAttribute(\"width\", width);\n        rectPreviewEl.setAttribute(\"height\", height);\n       });\n       svgMap.addEventListener(\"mouseup\", (e) => {\n        if (!rectDrawMode || !rectStartPoint || !rectPreviewEl) return;\n        const svgEl = svgMap;\n        const pt = svgEl.createSVGPoint();\n        pt.x = e.clientX;\n        pt.y = e.clientY;\n        const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n        const x = Math.min(rectStartPoint.x, svgP.x);\n        const y = Math.min(rectStartPoint.y, svgP.y);\n        const width = Math.abs(svgP.x - rectStartPoint.x);\n        const height = Math.abs(svgP.y - rectStartPoint.y);\n        if (width > 10 && height > 10) {\n      pushUndo(\"draw zone\");\n         const color = drawColorInput.value || \"#f97316\";\n         const newId = \"rect-\" + Date.now();\n          const lineStyle = document.getElementById(\"draw-style\").value || \"solid\";\n         RECT_DATA.list.push({\n          id: newId,\n          x: x,\n          y: y,\n          width: width,\n          height: height,\n          color: color,\n          style: rectStyle,\n          lineStyle: lineStyle,\n          notes: []\n         });\n         forgeTheTopology();\n        }\n        if (rectPreviewEl) {\n         rectPreviewEl.remove();\n         rectPreviewEl = null;\n        }\n        rectStartPoint = null;\n       });\n       let rectTouchStart = null;\n       svgMap.addEventListener(\"touchstart\", (e) => {\n        if (!rectDrawMode) return;\n        if (e.touches.length !== 1) return;\n        e.preventDefault();\n        const touch = e.touches[0];\n        const svgEl = svgMap;\n        const pt = svgEl.createSVGPoint();\n        pt.x = touch.clientX;\n        pt.y = touch.clientY;\n        const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n        rectStartPoint = { x: svgP.x, y: svgP.y };\n        rectTouchStart = { x: svgP.x, y: svgP.y };\n        const ns = \"http://www.w3.org/2000/svg\";\n        rectPreviewEl = document.createElementNS(ns, \"rect\");\n        rectPreviewEl.classList.add(\"rect-preview\");\n        rectPreviewEl.setAttribute(\"x\", svgP.x);\n        rectPreviewEl.setAttribute(\"y\", svgP.y);\n        rectPreviewEl.setAttribute(\"width\", 0);\n        rectPreviewEl.setAttribute(\"height\", 0);\n        const color = drawColorInput.value || \"#f97316\";\n        rectPreviewEl.style.stroke = color;\n        rectPreviewEl.style.strokeWidth = 2;\n        rectPreviewEl.style.strokeDasharray = \"5,5\";\n        if (rectStyle === \"filled\") {\n         rectPreviewEl.style.fill = color;\n         rectPreviewEl.style.fillOpacity = \"0.3\";\n        } else {\n         rectPreviewEl.style.fill = \"none\";\n        }\n        svgEl.appendChild(rectPreviewEl);\n       }, { passive: false });\n       svgMap.addEventListener(\"touchmove\", (e) => {\n        if (!rectDrawMode || !rectStartPoint || !rectPreviewEl) return;\n        if (e.touches.length !== 1) return;\n        e.preventDefault();\n        const touch = e.touches[0];\n        const svgEl = svgMap;\n        const pt = svgEl.createSVGPoint();\n        pt.x = touch.clientX;\n        pt.y = touch.clientY;\n        const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n        const x = Math.min(rectStartPoint.x, svgP.x);\n        const y = Math.min(rectStartPoint.y, svgP.y);\n        const width = Math.abs(svgP.x - rectStartPoint.x);\n        const height = Math.abs(svgP.y - rectStartPoint.y);\n        rectPreviewEl.setAttribute(\"x\", x);\n        rectPreviewEl.setAttribute(\"y\", y);\n        rectPreviewEl.setAttribute(\"width\", width);\n        rectPreviewEl.setAttribute(\"height\", height);\n       }, { passive: false });\n       svgMap.addEventListener(\"touchend\", (e) => {\n        if (!rectDrawMode || !rectStartPoint || !rectPreviewEl) return;\n        if (!rectTouchStart) return;\n        e.preventDefault();\n        const touch = e.changedTouches[0];\n        const svgEl = svgMap;\n        const pt = svgEl.createSVGPoint();\n        pt.x = touch.clientX;\n        pt.y = touch.clientY;\n        const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n        const x = Math.min(rectStartPoint.x, svgP.x);\n        const y = Math.min(rectStartPoint.y, svgP.y);\n        const width = Math.abs(svgP.x - rectStartPoint.x);\n        const height = Math.abs(svgP.y - rectStartPoint.y);\n        if (width > 10 && height > 10) {\n      pushUndo(\"draw zone\");\n         const color = drawColorInput.value || \"#f97316\";\n         const newId = \"rect-\" + Date.now();\n        const lineStyle = document.getElementById(\"draw-style\").value || \"solid\";\n         RECT_DATA.list.push({\n          id: newId,\n          x: x,\n          y: y,\n          width: width,\n          height: height,\n          color: color,\n          style: rectStyle,\n          lineStyle: lineStyle,\n          notes: []\n         });\n         forgeTheTopology();\n        }\n        if (rectPreviewEl) {\n         rectPreviewEl.remove();\n         rectPreviewEl = null;\n        }\n        rectStartPoint = null;\n        rectTouchStart = null;\n       }, { passive: false });\n      const textToggleBtn = document.getElementById(\"text-toggle\");\n      function startTextMode() {\n       textDrawMode = true;\n       svgMap.style.cursor = \"crosshair\";\n       textToggleBtn.textContent = \"Done\";\n       textToggleBtn.classList.add(\"done-btn-active\");\n       if (freeDrawMode) {\n        finishFreeDraw();\n       }\n       if (rectDrawMode) {\n        finishRectDraw();\n       }\n       updateTextDeleteButtons();\n      }\n      function finishTextMode() {\n       textDrawMode = false;\n       svgMap.style.cursor = \"\";\n       textToggleBtn.textContent = \"T\";\n       textToggleBtn.classList.remove(\"done-btn-active\");\n       updateTextDeleteButtons();\n      }\n      textToggleBtn.addEventListener(\"click\", () => {\n       if (currentView.mode === \"rack\") {\n        alert(\"Drawing tools are disabled in rack view. Exit to topology view to use drawing tools.\");\n        return;\n       }\n       if (textDrawMode) {\n        finishTextMode();\n       } else {\n        startTextMode();\n       }\n      });\n      function handleTextPlacement(e) {\n       if (!textDrawMode) return;\n       const svgEl = svgMap;\n       const pt = svgEl.createSVGPoint();\n       pt.x = e.clientX;\n       pt.y = e.clientY;\n       const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n       const newId = \"text-\" + Date.now();\n      pushUndo(\"add text\");\n       TEXT_DATA.list.push({\n        id: newId,\n        x: svgP.x,\n        y: svgP.y,\n        content: \"New Text\",\n        fontSize: 18,\n        color: \"#e2e8f0\",\n        fontWeight: \"normal\",\n        fontStyle: \"normal\",\n        textAlign: \"start\",\n        textDecoration: \"none\",\n        bgColor: \"#000000\",\n        bgEnabled: false,\n        opacity: 1\n       });\n       forgeTheTopology();\n       showTextPanel(newId);\n      }\n      svgMap.addEventListener(\"click\", (e) => {\n       if (!textDrawMode) return;\n       if (e.target.closest('.text-delete-btn')) return;\n       if (e.target.closest('.text-group')) return;\n       e.preventDefault();\n       e.stopPropagation();\n       handleTextPlacement(e);\n      });\n      svgMap.addEventListener(\"touchend\", (e) => {\n       if (!textDrawMode) return;\n       if (e.target.closest('.text-delete-btn')) return;\n       if (e.target.closest('.text-group')) return;\n       if (e.touches.length > 0) return;\n       e.preventDefault();\n       const touch = e.changedTouches[0];\n       const fakeEvent = {\n        clientX: touch.clientX,\n        clientY: touch.clientY,\n        preventDefault: () => {},\n        stopPropagation: () => {}\n       };\n       handleTextPlacement(fakeEvent);\n      }, { passive: false });\n      function showTextPanel(textId) {\n\t  if (isViewOnly()) return;\n       currentTextId = textId;\n       currentNodeId = null;\n       currentEdgeId = null;\n       currentRectId = null;\n       const textItem = TEXT_DATA.list.find(t => t.id === textId);\n       if (!textItem) return;\n       document.getElementById(\"node-panel\").style.display = \"none\";\n       document.getElementById(\"edge-panel\").style.display = \"none\";\n       document.getElementById(\"rect-panel\").style.display = \"none\";\n       const textPanel = document.getElementById(\"text-panel\");\n       textPanel.style.display = \"block\";\n       document.querySelectorAll(\".node-group\").forEach(n => n.classList.remove(\"active\"));\n       document.querySelectorAll(\".edge\").forEach(e => e.classList.remove(\"active\"));\n       document.querySelectorAll(\".rect-group\").forEach(r => r.classList.remove(\"active\"));\n       document.querySelectorAll(\".text-element\").forEach(t => t.classList.toggle(\"active\", t.dataset.textId === textId));\n       document.getElementById(\"text-content\").value = textItem.content;\n       document.getElementById(\"text-font-size\").value = textItem.fontSize;\n       document.getElementById(\"text-color\").value = textItem.color;\n       document.getElementById(\"text-font-weight\").value = textItem.fontWeight;\n       document.getElementById(\"text-font-style\").value = textItem.fontStyle;\n       document.getElementById(\"text-align\").value = textItem.textAlign;\n       document.getElementById(\"text-decoration\").value = textItem.textDecoration;\n       document.getElementById(\"text-bg-color\").value = textItem.bgColor;\n       document.getElementById(\"text-bg-enabled\").checked = textItem.bgEnabled;\n       document.getElementById(\"text-opacity\").value = textItem.opacity;\n       document.getElementById(\"text-opacity-val\").textContent = Math.round(textItem.opacity * 100) + \"%\";\n       document.getElementById(\"text-rotation\").value = Math.max(-360, Math.min(360, textItem.rotation || 0));\n       document.getElementById(\"text-rotation-val\").value = textItem.rotation || 0;\n      }\n      function updateTextDeleteButtons() {\n       const deleteButtons = document.querySelectorAll('.text-delete-btn');\n       deleteButtons.forEach(btn => {\n        btn.style.display = textDrawMode ? 'block' : 'none';\n       });\n      }\n      function deleteText(textId) {\n      pushUndo(\"delete text\");\n       TEXT_DATA.list = TEXT_DATA.list.filter(t => t.id !== textId);\n       forgeTheTopology();\n       if (currentTextId === textId) {\n        document.getElementById(\"text-panel\").style.display = \"none\";\n        currentTextId = null;\n       }\n      }\n      document.getElementById(\"text-content\").addEventListener(\"input\", (e) => {\n       if (!currentTextId) return;\n       const textItem = TEXT_DATA.list.find(t => t.id === currentTextId);\n       if (textItem) {\n      pushUndo(\"edit text\");\n        textItem.content = e.target.value;\n        forgeTheTopology();\n       }\n      });\n      document.getElementById(\"text-font-size\").addEventListener(\"input\", (e) => {\n       if (!currentTextId) return;\n       const textItem = TEXT_DATA.list.find(t => t.id === currentTextId);\n       if (textItem) {\n        pushUndo(\"edit text\");\n        textItem.fontSize = parseInt(e.target.value);\n        forgeTheTopology();\n       }\n      });\n      document.getElementById(\"text-color\").addEventListener(\"input\", (e) => {\n       if (!currentTextId) return;\n       const textItem = TEXT_DATA.list.find(t => t.id === currentTextId);\n       if (textItem) {\n        pushUndo(\"edit text\");\n        textItem.color = e.target.value;\n        forgeTheTopology();\n       }\n      });\n      document.getElementById(\"text-font-weight\").addEventListener(\"change\", (e) => {\n       if (!currentTextId) return;\n       const textItem = TEXT_DATA.list.find(t => t.id === currentTextId);\n       if (textItem) {\n        pushUndo(\"edit text\");\n        textItem.fontWeight = e.target.value;\n        forgeTheTopology();\n       }\n      });\n      document.getElementById(\"text-font-style\").addEventListener(\"change\", (e) => {\n       if (!currentTextId) return;\n       const textItem = TEXT_DATA.list.find(t => t.id === currentTextId);\n       if (textItem) {\n        pushUndo(\"edit text\");\n        textItem.fontStyle = e.target.value;\n        forgeTheTopology();\n       }\n      });\n      document.getElementById(\"text-align\").addEventListener(\"change\", (e) => {\n       if (!currentTextId) return;\n       const textItem = TEXT_DATA.list.find(t => t.id === currentTextId);\n       if (textItem) {\n        pushUndo(\"edit text\");\n        textItem.textAlign = e.target.value;\n        forgeTheTopology();\n       }\n      });\n      document.getElementById(\"text-decoration\").addEventListener(\"change\", (e) => {\n       if (!currentTextId) return;\n       const textItem = TEXT_DATA.list.find(t => t.id === currentTextId);\n       if (textItem) {\n        pushUndo(\"edit text\");\n        textItem.textDecoration = e.target.value;\n        forgeTheTopology();\n       }\n      });\n      document.getElementById(\"text-bg-color\").addEventListener(\"input\", (e) => {\n       if (!currentTextId) return;\n       const textItem = TEXT_DATA.list.find(t => t.id === currentTextId);\n       if (textItem) {\n        pushUndo(\"edit text\");\n        textItem.bgColor = e.target.value;\n        forgeTheTopology();\n       }\n      });\n      document.getElementById(\"text-bg-enabled\").addEventListener(\"change\", (e) => {\n       if (!currentTextId) return;\n       const textItem = TEXT_DATA.list.find(t => t.id === currentTextId);\n       if (textItem) {\n        pushUndo(\"edit text\");\n        textItem.bgEnabled = e.target.checked;\n        forgeTheTopology();\n       }\n      });\n      document.getElementById(\"text-rotation\").addEventListener(\"input\", (e) => {\n        if (!currentTextId) return;\n        const textItem = TEXT_DATA.list.find(t => t.id === currentTextId);\n        if (textItem) {\n          pushUndo(\"rotate text\");\n          textItem.rotation = parseInt(e.target.value) || 0;\n          document.getElementById(\"text-rotation-val\").value = textItem.rotation;\n          forgeTheTopology();\n        }\n      });\n      document.getElementById(\"text-rotation-val\").addEventListener(\"input\", (e) => {\n        if (!currentTextId) return;\n        const textItem = TEXT_DATA.list.find(t => t.id === currentTextId);\n        if (textItem) {\n          pushUndo(\"rotate text\");\n          const val = parseInt(e.target.value) || 0;\n          textItem.rotation = val;\n          document.getElementById(\"text-rotation\").value = Math.max(-360, Math.min(360, val));\n          forgeTheTopology();\n        }\n      });\n      document.getElementById(\"delete-text\").addEventListener(\"click\", () => {\n       if (!currentTextId) return;\n       const textItem = TEXT_DATA.list.find(t => t.id === currentTextId);\n       if (textItem) {\n        challengeTheImmortal(`Delete text \"${(textItem.content || '').substring(0, 30)}...\"?`, () => {\n         deleteText(currentTextId);\n        });\n       }\n      });\n      const settingsBtn = document.getElementById(\"settings-btn\");\n      const settingsModal = document.getElementById(\"settings-modal\");\n      const settingsClose = document.getElementById(\"settings-close\");\n      settingsBtn.addEventListener(\"click\", () => {\n       document.getElementById(\"page-bg-color\").value = PAGE_STATE.background || \"#050608\";\n       document.getElementById(\"topbar-bg-color\").value = PAGE_STATE.topbarBg || \"#0b0e13\";\n       document.getElementById(\"topbar-border-color\").value = PAGE_STATE.topbarBorder || \"#1f2533\";\n       document.getElementById(\"panel-color\").value = PAGE_STATE.panel || \"#0b0e13\";\n       document.getElementById(\"panel-alt-color\").value = PAGE_STATE.panelAlt || \"#10141b\";\n       document.getElementById(\"sidebar-bg-color\").value = PAGE_STATE.sidebarBg || \"#10141b\";\n       document.getElementById(\"btn-bg-color\").value = PAGE_STATE.btnBg || \"#0b0e13\";\n       document.getElementById(\"btn-text-color\").value = PAGE_STATE.btnText || \"#e2e8f0\";\n       document.getElementById(\"tag-fill-color\").value = PAGE_STATE.tagFill || \"#1e293b\";\n       document.getElementById(\"tag-text-color\").value = PAGE_STATE.tagText || \"#e2e8f0\";\n       document.getElementById(\"tag-border-color\").value = PAGE_STATE.tagBorder || \"#475569\";\n       document.getElementById(\"input-bg-color\").value = PAGE_STATE.inputBg || \"#0b0e13\";\n       document.getElementById(\"input-text-color\").value = PAGE_STATE.inputText || \"#e2e8f0\";\n       document.getElementById(\"input-border-color\").value = PAGE_STATE.inputBorder || \"#1f2937\";\n       document.getElementById(\"input-font-family\").value = PAGE_STATE.inputFont || \"Inter, system-ui, sans-serif\";\n       document.getElementById(\"input-font-size\").value = PAGE_STATE.inputFontSize || 14;\n       document.getElementById(\"toolbar-bg-color\").value = PAGE_STATE.toolbarBg || \"#0f172a\";\n       document.getElementById(\"toolbar-border-color\").value = PAGE_STATE.toolbarBorder || \"#1f2937\";\n       document.getElementById(\"toolbar-text-color\").value = PAGE_STATE.toolbarText || \"#94a3b8\";\n       document.getElementById(\"toolbar-btn-bg-color\").value = PAGE_STATE.toolbarBtnBg || \"#0b0e13\";\n       document.getElementById(\"toolbar-btn-text-color\").value = PAGE_STATE.toolbarBtnText || \"#e2e8f0\";\n       document.getElementById(\"minimap-dots-color\").value = PAGE_STATE.minimapDots || \"#94a3b8\";\n       document.getElementById(\"canvas-hint-enabled\").checked = PAGE_STATE.canvasHintEnabled !== false;\n       document.getElementById(\"canvas-hint-bg-color\").value = PAGE_STATE.canvasHintBg || \"#0f172a\";\n       document.getElementById(\"canvas-hint-text-color\").value = PAGE_STATE.canvasHintColor || \"#94a3b8\";\n       document.getElementById(\"canvas-hint-text\").value = PAGE_STATE.canvasHintText || \"\";\n       document.getElementById(\"accent-color\").value = PAGE_STATE.accent || \"#4fd1c5\";\n       document.getElementById(\"danger-color\").value = PAGE_STATE.danger || \"#f56565\";\n       document.getElementById(\"text-main-color\").value = PAGE_STATE.textMain || \"#e2e8f0\";\n       document.getElementById(\"text-soft-color\").value = PAGE_STATE.textSoft || \"#94a3b8\";\n       document.getElementById(\"node-fill-color\").value = PAGE_STATE.nodeFill || \"#1e293b\";\n       document.getElementById(\"node-stroke-color\").value = PAGE_STATE.nodeStroke || \"#475569\";\n       document.getElementById(\"node-title-color\").value = PAGE_STATE.nodeTitle || \"#e2e8f0\";\n       document.getElementById(\"node-sub-color\").value = PAGE_STATE.nodeSub || \"#94a3b8\";\n       document.getElementById(\"node-title-size\").value = PAGE_STATE.nodeTitleSize || 18;\n       document.getElementById(\"node-sub-size\").value = PAGE_STATE.nodeSubSize || 13;\n       document.getElementById(\"node-font-family\").value = PAGE_STATE.nodeFont || \"Inter, system-ui, sans-serif\";\n       document.getElementById(\"default-edge-color\").value = PAGE_STATE.defaultEdge || \"#475569\";\n       document.getElementById(\"add-line-color\").value = PAGE_STATE.defaultEdge || \"#475569\";\n       document.getElementById(\"default-edge-routing\").value = PAGE_STATE.defaultEdgeRouting || \"curved\";\n\t   document.getElementById(\"anim-master\").addEventListener(\"change\", e => { ANIM_SETTINGS.masterAnim = e.target.checked; applyAnimZoneSettings(); });\ndocument.getElementById(\"anim-type-sweep\").addEventListener(\"change\", e => { ANIM_SETTINGS.animTypes.sweep = e.target.checked; applyAnimZoneSettings(); });\ndocument.getElementById(\"anim-type-pulse\").addEventListener(\"change\", e => { ANIM_SETTINGS.animTypes.pulse = e.target.checked; applyAnimZoneSettings(); });\ndocument.getElementById(\"anim-type-rings\").addEventListener(\"change\", e => { ANIM_SETTINGS.animTypes.rings = e.target.checked; applyAnimZoneSettings(); });\ndocument.getElementById(\"anim-type-spin\").addEventListener(\"change\", e => { ANIM_SETTINGS.animTypes.spin = e.target.checked; applyAnimZoneSettings(); });\ndocument.getElementById(\"anim-type-connections\").addEventListener(\"change\", e => { ANIM_SETTINGS.animTypes.connections = e.target.checked; applyAnimZoneSettings(); });\ndocument.getElementById(\"anim-cat-camera\").addEventListener(\"change\", e => { ANIM_SETTINGS.animCategories.camera = e.target.checked; applyAnimZoneSettings(); });\ndocument.getElementById(\"anim-cat-doorbell\").addEventListener(\"change\", e => { ANIM_SETTINGS.animCategories.doorbell = e.target.checked; applyAnimZoneSettings(); });\ndocument.getElementById(\"anim-cat-motion\").addEventListener(\"change\", e => { ANIM_SETTINGS.animCategories.motion = e.target.checked; applyAnimZoneSettings(); });\ndocument.getElementById(\"anim-cat-smoke\").addEventListener(\"change\", e => { ANIM_SETTINGS.animCategories.smoke = e.target.checked; applyAnimZoneSettings(); });\ndocument.getElementById(\"anim-cat-wifi\").addEventListener(\"change\", e => { ANIM_SETTINGS.animCategories.wifi = e.target.checked; applyAnimZoneSettings(); });\ndocument.getElementById(\"anim-cat-sensor\").addEventListener(\"change\", e => { ANIM_SETTINGS.animCategories.sensor = e.target.checked; applyAnimZoneSettings(); });\ndocument.getElementById(\"anim-cat-sprinkler\").addEventListener(\"change\", e => { ANIM_SETTINGS.animCategories.sprinkler = e.target.checked; applyAnimZoneSettings(); });\ndocument.getElementById(\"anim-cat-connections\").addEventListener(\"change\", e => { ANIM_SETTINGS.animCategories.connections = e.target.checked; applyAnimZoneSettings(); });\ndocument.getElementById(\"zone-master\").addEventListener(\"change\", e => { ANIM_SETTINGS.masterZones = e.target.checked; applyAnimZoneSettings(); });\ndocument.getElementById(\"zone-cat-camera\").addEventListener(\"change\", e => { ANIM_SETTINGS.zoneCategories.camera = e.target.checked; applyAnimZoneSettings(); });\ndocument.getElementById(\"zone-cat-doorbell\").addEventListener(\"change\", e => { ANIM_SETTINGS.zoneCategories.doorbell = e.target.checked; applyAnimZoneSettings(); });\ndocument.getElementById(\"zone-cat-motion\").addEventListener(\"change\", e => { ANIM_SETTINGS.zoneCategories.motion = e.target.checked; applyAnimZoneSettings(); });\ndocument.getElementById(\"zone-cat-smoke\").addEventListener(\"change\", e => { ANIM_SETTINGS.zoneCategories.smoke = e.target.checked; applyAnimZoneSettings(); });\ndocument.getElementById(\"zone-cat-wifi\").addEventListener(\"change\", e => { ANIM_SETTINGS.zoneCategories.wifi = e.target.checked; applyAnimZoneSettings(); });\ndocument.getElementById(\"zone-cat-sensor\").addEventListener(\"change\", e => { ANIM_SETTINGS.zoneCategories.sensor = e.target.checked; applyAnimZoneSettings(); });\ndocument.getElementById(\"zone-cat-sprinkler\").addEventListener(\"change\", e => { ANIM_SETTINGS.zoneCategories.sprinkler = e.target.checked; applyAnimZoneSettings(); });\n\n      document.getElementById(\"animation-style-select\").value = PAGE_STATE.animationStyle || \"arrows\";\n      document.getElementById(\"animation-direction-select\").value = PAGE_STATE.animationDirection || \"all\";\n      document.getElementById(\"animation-speed-select\").value = PAGE_STATE.animationSpeed || 1.5;\n\n      document.getElementById(\"animation-style-select\").addEventListener(\"change\", (e) => {\n       PAGE_STATE.animationStyle = e.target.value;\n       if (PAGE_STATE.animateConnections) forgeTheTopology();\n      });\n      document.getElementById(\"animation-direction-select\").addEventListener(\"change\", (e) => {\n       PAGE_STATE.animationDirection = e.target.value;\n       if (PAGE_STATE.animateConnections) forgeTheTopology();\n      });\n      document.getElementById(\"animation-speed-select\").addEventListener(\"change\", (e) => {\n       PAGE_STATE.animationSpeed = parseFloat(e.target.value);\n       if (PAGE_STATE.animateConnections) forgeTheTopology();\n      });\n       document.getElementById(\"add-line-routing\").value = PAGE_STATE.defaultEdgeRouting || \"orthogonal\";\n       document.getElementById(\"selection-handle-color\").value = PAGE_STATE.selectionHandle || \"#f59e0b\";\n       document.getElementById(\"selection-handle-size\").value = PAGE_STATE.selectionHandleSize || 8;\n       document.getElementById(\"group-indicator-color\").value = PAGE_STATE.groupIndicator || \"#4fd1c5\";\n       document.getElementById(\"canvas-gradient-top\").value = PAGE_STATE.canvasGradientTop || \"#1e2532\";\n       document.getElementById(\"canvas-gradient-bottom\").value = PAGE_STATE.canvasGradientBottom || \"#050608\";\n       document.getElementById(\"canvas-border-color\").value = PAGE_STATE.canvasBorder || \"#475569\";\n       document.getElementById(\"canvas-grid-color\").value = PAGE_STATE.canvasGrid || \"#475569\";\n       document.getElementById(\"canvas-grid-size\").value = PAGE_STATE.canvasGridSize || 50;\n\t   document.getElementById(\"canvas-grid-enabled\").checked = PAGE_STATE.canvasGridEnabled !== false;\n       document.getElementById(\"rack-frame-fill\").value = PAGE_STATE.rackFrameFill || \"#0f172a\";\n       document.getElementById(\"rack-frame-stroke\").value = PAGE_STATE.rackFrameStroke || \"#4fd1c5\";\n       document.getElementById(\"rack-line-color\").value = PAGE_STATE.rackLineColor || \"#475569\";\n\t   document.getElementById(\"rack-grid-enabled\").checked = PAGE_STATE.rackGridEnabled !== false;\n       document.getElementById(\"rack-text-color\").value = PAGE_STATE.rackTextColor || \"#4fd1c5\";\n\t   document.getElementById(\"view-only-mode\").checked = PAGE_STATE.viewOnly === true;\n       rebuildThemeDropdown();\n       updateDeleteButton();\n       settingsModal.classList.add(\"active\");\n      });\n      settingsClose.addEventListener(\"click\", () => {\n       settingsModal.classList.remove(\"active\");\n      });\n\t  document.getElementById(\"view-only-mode\").addEventListener(\"change\", (e) => {\n       PAGE_STATE.viewOnly = e.target.checked;\n       if (e.target.checked) {\n        document.getElementById(\"node-panel\").style.display = \"none\";\n        document.getElementById(\"edge-panel\").style.display = \"none\";\n        document.getElementById(\"text-panel\").style.display = \"none\";\n        document.getElementById(\"rect-panel\").style.display = \"none\";\n        document.getElementById(\"topology-toolbar\").style.setProperty('display', 'none', 'important');\n        document.getElementById(\"draw-toolbar\").style.setProperty('display', 'none', 'important');\n        currentNodeId = null;\n        currentEdgeId = null;\n        currentRectId = null;\n        currentTextId = null;\n        selectedNodes.clear();\n        selectedRects.clear();\n        selectedTexts.clear();\n        selectedEdges.clear();\n        const bulkToolbar = document.getElementById(\"bulk-toolbar\");\n        const bulkToolbarMobile = document.getElementById(\"bulk-toolbar-mobile\");\n        if (bulkToolbar) bulkToolbar.style.display = \"none\";\n        if (bulkToolbarMobile) bulkToolbarMobile.style.display = \"none\";\n        document.querySelectorAll(\".node-group\").forEach(n => n.classList.remove(\"active\", \"selected\"));\n        document.querySelectorAll(\".edge\").forEach(e => e.classList.remove(\"active\"));\n        document.querySelectorAll(\".rect-group\").forEach(r => r.classList.remove(\"active\"));\n        document.querySelectorAll(\".text-element\").forEach(t => t.classList.remove(\"active\"));\n       }\n       wieldThePower();\n       if (!e.target.checked) {\n        updateDrawToolbarVisibility();\n        updateTopologyToolbarVisibility();\n       }\n       forgeTheTopology();\n      });\n      settingsModal.addEventListener(\"click\", (e) => {\n       if (e.target === settingsModal) {\n        settingsModal.classList.remove(\"active\");\n       }\n      });\n      document.getElementById(\"page-bg-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.background = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"topbar-bg-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.topbarBg = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"topbar-border-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.topbarBorder = e.target.value;\n       wieldThePower();\n      });\nconst THEME_PRESETS = {\n  defaulted: { panel:\"#0b0e13\",panelAlt:\"#10141b\",sidebarBg:\"#10141b\",btnBg:\"#0b0e13\",btnText:\"#e2e8f0\",accent:\"#4fd1c5\",danger:\"#f56565\",textMain:\"#e2e8f0\",textSoft:\"#94a3b8\",topbarBg:\"rgba(9,12,20,0.9)\",topbarBorder:\"#1f2533\",nodeFill:\"#1e293b\",nodeStroke:\"#475569\",nodeTitle:\"#e2e8f0\",nodeSub:\"#94a3b8\",defaultEdge:\"#475569\",canvasGradientTop:\"#1e2532\",canvasGradientBottom:\"#050608\",tagFill:\"#1e293b\",tagText:\"#e2e8f0\",tagBorder:\"#475569\",inputBg:\"#0b0e13\",inputText:\"#e2e8f0\",inputBorder:\"#1f2937\",toolbarBg:\"#0f172a\",toolbarBorder:\"#1f2937\",toolbarText:\"#94a3b8\",toolbarBtnBg:\"#0b0e13\",toolbarBtnText:\"#e2e8f0\",selectionHandle:\"#f59e0b\",groupIndicator:\"#4fd1c5\",minimapDots:\"#94a3b8\",canvasHintBg:\"#0f172a\",canvasHintColor:\"#94a3b8\",canvasBorder:\"#475569\",canvasGrid:\"#475569\",rackFrameFill:\"#0f172a\",rackFrameStroke:\"#4fd1c5\",rackLineColor:\"#475569\",rackTextColor:\"#4fd1c5\" },\n  slate: { panel:\"#1e293b\",panelAlt:\"#334155\",sidebarBg:\"#1e293b\",btnBg:\"#334155\",btnText:\"#f1f5f9\",accent:\"#3b82f6\",danger:\"#ef4444\",textMain:\"#f1f5f9\",textSoft:\"#94a3b8\",topbarBg:\"#0f172a\",topbarBorder:\"#334155\",nodeFill:\"#334155\",nodeStroke:\"#3b82f6\",nodeTitle:\"#f1f5f9\",nodeSub:\"#94a3b8\",defaultEdge:\"#64748b\",canvasGradientTop:\"#1e293b\",canvasGradientBottom:\"#0f172a\",tagFill:\"#1e3a5f\",tagText:\"#93c5fd\",tagBorder:\"#3b82f6\",inputBg:\"#0f172a\",inputText:\"#f1f5f9\",inputBorder:\"#475569\",toolbarBg:\"#2563eb\",toolbarBorder:\"#3b82f6\",toolbarText:\"#ffffff\",toolbarBtnBg:\"#3b82f6\",toolbarBtnText:\"#ffffff\",selectionHandle:\"#f59e0b\",groupIndicator:\"#22d3ee\",minimapDots:\"#64748b\",canvasHintBg:\"#1e293b\",canvasHintColor:\"#94a3b8\",canvasBorder:\"#334155\",canvasGrid:\"#334155\",rackFrameFill:\"#1e293b\",rackFrameStroke:\"#3b82f6\",rackLineColor:\"#475569\",rackTextColor:\"#3b82f6\" },\n  graphite: { panel:\"#1f2937\",panelAlt:\"#374151\",sidebarBg:\"#111827\",btnBg:\"#374151\",btnText:\"#f9fafb\",accent:\"#f59e0b\",danger:\"#ef4444\",textMain:\"#f9fafb\",textSoft:\"#9ca3af\",topbarBg:\"#111827\",topbarBorder:\"#4b5563\",nodeFill:\"#374151\",nodeStroke:\"#f59e0b\",nodeTitle:\"#f9fafb\",nodeSub:\"#9ca3af\",defaultEdge:\"#6b7280\",canvasGradientTop:\"#1f2937\",canvasGradientBottom:\"#111827\",tagFill:\"#44403c\",tagText:\"#fbbf24\",tagBorder:\"#f59e0b\",inputBg:\"#111827\",inputText:\"#f9fafb\",inputBorder:\"#4b5563\",toolbarBg:\"#b45309\",toolbarBorder:\"#f59e0b\",toolbarText:\"#ffffff\",toolbarBtnBg:\"#f59e0b\",toolbarBtnText:\"#000000\",selectionHandle:\"#10b981\",groupIndicator:\"#06b6d4\",minimapDots:\"#6b7280\",canvasHintBg:\"#1f2937\",canvasHintColor:\"#9ca3af\",canvasBorder:\"#4b5563\",canvasGrid:\"#374151\",rackFrameFill:\"#1f2937\",rackFrameStroke:\"#f59e0b\",rackLineColor:\"#4b5563\",rackTextColor:\"#fbbf24\" },\n  frost: { panel:\"#f8fafc\",panelAlt:\"#e2e8f0\",sidebarBg:\"#f1f5f9\",btnBg:\"#e2e8f0\",btnText:\"#1e293b\",accent:\"#1e40af\",danger:\"#dc2626\",textMain:\"#0f172a\",textSoft:\"#475569\",topbarBg:\"#1e40af\",topbarBorder:\"#1e3a8a\",nodeFill:\"#ffffff\",nodeStroke:\"#1e40af\",nodeTitle:\"#0f172a\",nodeSub:\"#475569\",defaultEdge:\"#64748b\",canvasGradientTop:\"#e0e7ef\",canvasGradientBottom:\"#f8fafc\",tagFill:\"#dbeafe\",tagText:\"#1e40af\",tagBorder:\"#3b82f6\",inputBg:\"#ffffff\",inputText:\"#0f172a\",inputBorder:\"#cbd5e1\",toolbarBg:\"#1e40af\",toolbarBorder:\"#1e3a8a\",toolbarText:\"#ffffff\",toolbarBtnBg:\"#3b82f6\",toolbarBtnText:\"#ffffff\",selectionHandle:\"#ea580c\",groupIndicator:\"#059669\",minimapDots:\"#64748b\",canvasHintBg:\"#e2e8f0\",canvasHintColor:\"#475569\",canvasBorder:\"#cbd5e1\",canvasGrid:\"#cbd5e1\",rackFrameFill:\"#f1f5f9\",rackFrameStroke:\"#1e40af\",rackLineColor:\"#94a3b8\",rackTextColor:\"#1e40af\" },\n  synthwave: { panel:\"#87366d\",panelAlt:\"#10141b\",sidebarBg:\"#340934\",btnBg:\"#0b0e13\",btnText:\"#e2e8f0\",accent:\"#4fd1c5\",danger:\"#f56565\",textMain:\"#e2e8f0\",textSoft:\"#94a3b8\",topbarBg:\"#781c67\",topbarBorder:\"#1f2533\",nodeFill:\"#1e293b\",nodeStroke:\"#475569\",nodeTitle:\"#e2e8f0\",nodeSub:\"#94a3b8\",defaultEdge:\"#475569\",canvasGradientTop:\"#1e2532\",canvasGradientBottom:\"#050608\",tagFill:\"#1e293b\",tagText:\"#e2e8f0\",tagBorder:\"#475569\",inputBg:\"#0b0e13\",inputText:\"#e2e8f0\",inputBorder:\"#1f2937\",toolbarBg:\"#b95aed\",toolbarBorder:\"#b800eb\",toolbarText:\"#000000\",toolbarBtnBg:\"#ed01fe\",toolbarBtnText:\"#000000\",selectionHandle:\"#f59e0b\",groupIndicator:\"#4fd1c5\",minimapDots:\"#000000\",canvasHintBg:\"#0f172a\",canvasHintColor:\"#94a3b8\",canvasBorder:\"#000000\",canvasGrid:\"#000000\",rackFrameFill:\"#0f172a\",rackFrameStroke:\"#4fd1c5\",rackLineColor:\"#475569\",rackTextColor:\"#4fd1c5\" },\n  terminal: { panel:\"#000000\",panelAlt:\"#0a0a0a\",sidebarBg:\"#050505\",btnBg:\"#0a0a0a\",btnText:\"#33ff33\",accent:\"#33ff33\",danger:\"#ffaa00\",textMain:\"#33ff33\",textSoft:\"#1a9a1a\",topbarBg:\"#000000\",topbarBorder:\"#33ff33\",nodeFill:\"#0a0a0a\",nodeStroke:\"#33ff33\",nodeTitle:\"#33ff33\",nodeSub:\"#1a9a1a\",defaultEdge:\"#1a9a1a\",canvasGradientTop:\"#0a0f0a\",canvasGradientBottom:\"#000000\",tagFill:\"#0a1a0a\",tagText:\"#33ff33\",tagBorder:\"#33ff33\",inputBg:\"#000000\",inputText:\"#33ff33\",inputBorder:\"#1a9a1a\",toolbarBg:\"#1a9a1a\",toolbarBorder:\"#33ff33\",toolbarText:\"#000000\",toolbarBtnBg:\"#33ff33\",toolbarBtnText:\"#000000\",selectionHandle:\"#ffaa00\",groupIndicator:\"#00ffff\",minimapDots:\"#33ff33\",canvasHintBg:\"#0a0a0a\",canvasHintColor:\"#1a9a1a\",canvasBorder:\"#1a9a1a\",canvasGrid:\"#0f1f0f\",rackFrameFill:\"#050505\",rackFrameStroke:\"#33ff33\",rackLineColor:\"#1a9a1a\",rackTextColor:\"#33ff33\" },\n  dracula: { panel:\"#282a36\",panelAlt:\"#44475a\",sidebarBg:\"#21222c\",btnBg:\"#44475a\",btnText:\"#f8f8f2\",accent:\"#bd93f9\",danger:\"#ff5555\",textMain:\"#f8f8f2\",textSoft:\"#6272a4\",topbarBg:\"#21222c\",topbarBorder:\"#6272a4\",nodeFill:\"#44475a\",nodeStroke:\"#ff79c6\",nodeTitle:\"#f8f8f2\",nodeSub:\"#8be9fd\",defaultEdge:\"#bd93f9\",canvasGradientTop:\"#282a36\",canvasGradientBottom:\"#1a1b23\",tagFill:\"#3d3f4a\",tagText:\"#50fa7b\",tagBorder:\"#50fa7b\",inputBg:\"#21222c\",inputText:\"#f8f8f2\",inputBorder:\"#6272a4\",toolbarBg:\"#6272a4\",toolbarBorder:\"#bd93f9\",toolbarText:\"#f8f8f2\",toolbarBtnBg:\"#bd93f9\",toolbarBtnText:\"#282a36\",selectionHandle:\"#f1fa8c\",groupIndicator:\"#ff79c6\",minimapDots:\"#bd93f9\",canvasHintBg:\"#282a36\",canvasHintColor:\"#6272a4\",canvasBorder:\"#44475a\",canvasGrid:\"#44475a\",rackFrameFill:\"#282a36\",rackFrameStroke:\"#ff79c6\",rackLineColor:\"#6272a4\",rackTextColor:\"#8be9fd\" },\n  cobalt: { panel:\"#002240\",panelAlt:\"#003366\",sidebarBg:\"#001b33\",btnBg:\"#003366\",btnText:\"#ffffff\",accent:\"#ffc600\",danger:\"#ff628c\",textMain:\"#ffffff\",textSoft:\"#8090a0\",topbarBg:\"#001525\",topbarBorder:\"#0088ff\",nodeFill:\"#003366\",nodeStroke:\"#0088ff\",nodeTitle:\"#ffffff\",nodeSub:\"#80ffbb\",defaultEdge:\"#0088ff\",canvasGradientTop:\"#002240\",canvasGradientBottom:\"#00111f\",tagFill:\"#004080\",tagText:\"#ffc600\",tagBorder:\"#0088ff\",inputBg:\"#001525\",inputText:\"#ffffff\",inputBorder:\"#0066cc\",toolbarBg:\"#0066cc\",toolbarBorder:\"#0088ff\",toolbarText:\"#ffffff\",toolbarBtnBg:\"#0088ff\",toolbarBtnText:\"#ffffff\",selectionHandle:\"#ffc600\",groupIndicator:\"#3ad900\",minimapDots:\"#0088ff\",canvasHintBg:\"#002240\",canvasHintColor:\"#8090a0\",canvasBorder:\"#0066cc\",canvasGrid:\"#003366\",rackFrameFill:\"#002240\",rackFrameStroke:\"#0088ff\",rackLineColor:\"#0066cc\",rackTextColor:\"#80ffbb\" },\n  solarized: { panel:\"#073642\",panelAlt:\"#002b36\",sidebarBg:\"#002b36\",btnBg:\"#073642\",btnText:\"#93a1a1\",accent:\"#268bd2\",danger:\"#dc322f\",textMain:\"#93a1a1\",textSoft:\"#657b83\",topbarBg:\"#002b36\",topbarBorder:\"#586e75\",nodeFill:\"#073642\",nodeStroke:\"#2aa198\",nodeTitle:\"#93a1a1\",nodeSub:\"#839496\",defaultEdge:\"#657b83\",canvasGradientTop:\"#073642\",canvasGradientBottom:\"#002b36\",tagFill:\"#0a4050\",tagText:\"#b58900\",tagBorder:\"#b58900\",inputBg:\"#002b36\",inputText:\"#93a1a1\",inputBorder:\"#586e75\",toolbarBg:\"#268bd2\",toolbarBorder:\"#2aa198\",toolbarText:\"#fdf6e3\",toolbarBtnBg:\"#2aa198\",toolbarBtnText:\"#002b36\",selectionHandle:\"#cb4b16\",groupIndicator:\"#d33682\",minimapDots:\"#657b83\",canvasHintBg:\"#073642\",canvasHintColor:\"#657b83\",canvasBorder:\"#586e75\",canvasGrid:\"#094050\",rackFrameFill:\"#002b36\",rackFrameStroke:\"#2aa198\",rackLineColor:\"#586e75\",rackTextColor:\"#859900\" }\n};\ndocument.getElementById(\"theme-preset\").addEventListener(\"change\", function() {\n  updateDeleteButton();\n  var p = THEME_PRESETS[this.value];\n  if (!p && this.value.startsWith(\"mytheme-\")) {\n    var found = savedStyleSets.find(function(s) { return s.id === document.getElementById(\"theme-preset\").value; });\n    if (found) p = found.styles;\n  }\n  if (!p) return;\n  Object.assign(PAGE_STATE, p);\n  document.getElementById(\"panel-color\").value = p.panel;\n  document.getElementById(\"panel-alt-color\").value = p.panelAlt;\n  document.getElementById(\"sidebar-bg-color\").value = p.sidebarBg;\n  document.getElementById(\"btn-bg-color\").value = p.btnBg;\n  document.getElementById(\"btn-text-color\").value = p.btnText;\n  document.getElementById(\"accent-color\").value = p.accent;\n  document.getElementById(\"danger-color\").value = p.danger;\n  document.getElementById(\"text-main-color\").value = p.textMain;\n  document.getElementById(\"text-soft-color\").value = p.textSoft;\n  document.getElementById(\"topbar-border-color\").value = p.topbarBorder;\n  document.getElementById(\"node-fill-color\").value = p.nodeFill;\n  document.getElementById(\"node-stroke-color\").value = p.nodeStroke;\n  document.getElementById(\"node-title-color\").value = p.nodeTitle;\n  document.getElementById(\"node-sub-color\").value = p.nodeSub;\n  document.getElementById(\"default-edge-color\").value = p.defaultEdge;\n  document.getElementById(\"canvas-gradient-top\").value = p.canvasGradientTop;\n  document.getElementById(\"canvas-gradient-bottom\").value = p.canvasGradientBottom;\n  document.getElementById(\"tag-fill-color\").value = p.tagFill;\n  document.getElementById(\"tag-text-color\").value = p.tagText;\n  document.getElementById(\"tag-border-color\").value = p.tagBorder;\n  document.getElementById(\"input-bg-color\").value = p.inputBg;\n  document.getElementById(\"input-text-color\").value = p.inputText;\n  document.getElementById(\"input-border-color\").value = p.inputBorder;\n  document.getElementById(\"toolbar-bg-color\").value = p.toolbarBg;\n  document.getElementById(\"toolbar-border-color\").value = p.toolbarBorder;\n  document.getElementById(\"toolbar-text-color\").value = p.toolbarText;\n  document.getElementById(\"toolbar-btn-bg-color\").value = p.toolbarBtnBg;\n  document.getElementById(\"toolbar-btn-text-color\").value = p.toolbarBtnText;\n  document.getElementById(\"selection-handle-color\").value = p.selectionHandle;\n  document.getElementById(\"group-indicator-color\").value = p.groupIndicator;\n  document.getElementById(\"minimap-dots-color\").value = p.minimapDots;\n  document.getElementById(\"canvas-hint-bg-color\").value = p.canvasHintBg;\n  document.getElementById(\"canvas-hint-text-color\").value = p.canvasHintColor;\n  document.getElementById(\"canvas-border-color\").value = p.canvasBorder;\n  document.getElementById(\"canvas-grid-color\").value = p.canvasGrid;\n  document.getElementById(\"rack-frame-fill\").value = p.rackFrameFill;\n  document.getElementById(\"rack-frame-stroke\").value = p.rackFrameStroke;\n  document.getElementById(\"rack-line-color\").value = p.rackLineColor;\n  document.getElementById(\"rack-text-color\").value = p.rackTextColor;\n  wieldThePower();\n  forgeTheTopology();\n});\ndocument.querySelectorAll('#settings-modal .style-content input, #settings-modal .style-content select').forEach(el => {\n  if (el.id === 'theme-preset') return;\n  el.addEventListener('input', () => document.getElementById('theme-preset').value = '');\n  el.addEventListener('change', () => document.getElementById('theme-preset').value = '');\n});\n      document.getElementById(\"panel-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.panel = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"panel-alt-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.panelAlt = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"sidebar-bg-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.sidebarBg = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"btn-bg-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.btnBg = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"btn-text-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.btnText = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"tag-fill-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.tagFill = e.target.value;\n       wieldThePower();\n       forgeTheTopology();\n      });\n      document.getElementById(\"tag-text-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.tagText = e.target.value;\n       wieldThePower();\n       forgeTheTopology();\n      });\n      document.getElementById(\"tag-border-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.tagBorder = e.target.value;\n       wieldThePower();\n       forgeTheTopology();\n      });\n\t  document.getElementById(\"selection-fill-color\").addEventListener(\"input\", function() {\n       selectionBoxStyle.fillColor = this.value;\n      });\n      document.getElementById(\"selection-fill-opacity\").addEventListener(\"input\", function() {\n       selectionBoxStyle.fillOpacity = parseFloat(this.value);\n       document.getElementById(\"selection-fill-opacity-val\").textContent = Math.round(this.value * 100) + \"%\";\n      });\n      document.getElementById(\"selection-stroke-color\").addEventListener(\"input\", function() {\n       selectionBoxStyle.strokeColor = this.value;\n      });\n      document.getElementById(\"selection-stroke-width\").addEventListener(\"input\", function() {\n       selectionBoxStyle.strokeWidth = parseInt(this.value) || 2;\n      });\n      document.getElementById(\"selection-stroke-style\").addEventListener(\"change\", function() {\n       selectionBoxStyle.strokeDasharray = this.value;\n      });\n      document.getElementById(\"input-bg-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.inputBg = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"input-text-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.inputText = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"input-border-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.inputBorder = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"input-font-family\").addEventListener(\"change\", (e) => {\n       PAGE_STATE.inputFont = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"input-font-size\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.inputFontSize = parseInt(e.target.value) || 14;\n       wieldThePower();\n      });\n      document.getElementById(\"toolbar-bg-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.toolbarBg = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"toolbar-border-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.toolbarBorder = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"toolbar-text-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.toolbarText = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"toolbar-btn-bg-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.toolbarBtnBg = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"toolbar-btn-text-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.toolbarBtnText = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"minimap-dots-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.minimapDots = e.target.value;\n       wieldThePower();\n       forgeTheTopology();\n      });\n      document.getElementById(\"canvas-hint-enabled\").addEventListener(\"change\", (e) => {\n       PAGE_STATE.canvasHintEnabled = e.target.checked;\n       wieldThePower();\n      });\n      document.getElementById(\"canvas-hint-bg-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.canvasHintBg = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"canvas-hint-text-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.canvasHintColor = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"canvas-hint-text\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.canvasHintText = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"accent-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.accent = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"danger-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.danger = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"text-main-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.textMain = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"text-soft-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.textSoft = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"node-fill-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.nodeFill = e.target.value;\n       forgeTheTopology();\n      });\n      document.getElementById(\"node-stroke-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.nodeStroke = e.target.value;\n       forgeTheTopology();\n      });\n      document.getElementById(\"node-title-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.nodeTitle = e.target.value;\n       forgeTheTopology();\n      });\n      document.getElementById(\"node-sub-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.nodeSub = e.target.value;\n       forgeTheTopology();\n      });\n      document.getElementById(\"node-title-size\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.nodeTitleSize = parseInt(e.target.value) || 18;\n       forgeTheTopology();\n      });\n      document.getElementById(\"node-sub-size\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.nodeSubSize = parseInt(e.target.value) || 13;\n       forgeTheTopology();\n      });\n      document.getElementById(\"node-font-family\").addEventListener(\"change\", (e) => {\n       PAGE_STATE.nodeFont = e.target.value;\n       forgeTheTopology();\n      });\n\t  document.getElementById(\"default-edge-routing\").addEventListener(\"change\", (e) => {\n       PAGE_STATE.defaultEdgeRouting = e.target.value;\n       document.getElementById(\"add-line-routing\").value = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"apply-routing-all\").addEventListener(\"click\", () => {\n       const routing = document.getElementById(\"default-edge-routing\").value;\n       if (!confirm(`Apply \"${routing}\" routing to all ${EDGE_DATA.list.length} connections?`)) return;\n       pushUndo(\"apply routing to all\");\n       EDGE_DATA.list.forEach(edge => {\n        edge.routing = routing;\n       });\n       forgeTheTopology();\n      });\n      document.getElementById(\"default-edge-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.defaultEdge = e.target.value;\n       document.getElementById(\"add-line-color\").value = e.target.value;\n       forgeTheTopology();\n      });\n      document.getElementById(\"selection-handle-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.selectionHandle = e.target.value;\n       forgeTheTopology();\n      });\n      document.getElementById(\"selection-handle-size\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.selectionHandleSize = parseInt(e.target.value) || 8;\n       forgeTheTopology();\n      });\n      document.getElementById(\"group-indicator-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.groupIndicator = e.target.value;\n       forgeTheTopology();\n      });\n      document.getElementById(\"canvas-gradient-top\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.canvasGradientTop = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"canvas-gradient-bottom\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.canvasGradientBottom = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"canvas-border-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.canvasBorder = e.target.value;\n       forgeTheTopology();\n      });\n      document.getElementById(\"canvas-grid-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.canvasGrid = e.target.value;\n       forgeTheTopology();\n      });\n      document.getElementById(\"canvas-grid-size\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.canvasGridSize = parseInt(e.target.value) || 50;\n       forgeTheTopology();\n      });\n\t  document.getElementById(\"canvas-grid-enabled\").addEventListener(\"change\", (e) => {\n PAGE_STATE.canvasGridEnabled = e.target.checked;\n forgeTheTopology();\n});\n      document.getElementById(\"rack-frame-fill\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.rackFrameFill = e.target.value;\n       forgeTheTopology();\n      });\n      document.getElementById(\"rack-frame-stroke\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.rackFrameStroke = e.target.value;\n       forgeTheTopology();\n      });\n      document.getElementById(\"rack-line-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.rackLineColor = e.target.value;\n       forgeTheTopology();\n      });\n\t  document.getElementById(\"rack-grid-enabled\").addEventListener(\"change\", (e) => {\n PAGE_STATE.rackGridEnabled = e.target.checked;\n forgeTheTopology();\n});\n      document.getElementById(\"rack-text-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.rackTextColor = e.target.value;\n       forgeTheTopology();\n      });\n      (function initializeResizers() {\n        const headerResizer = document.getElementById('header-resizer');\n        const sidebarResizer = document.getElementById('sidebar-resizer');\n        const mobileFooterResizer = document.getElementById('mobile-footer-resizer');\n        let isResizing = false;\n        let currentResizer = null;\n        let startY = 0;\n        let startX = 0;\n        let startHeight = 0;\n        let startWidth = 0;\n        function getClientPos(e) {\n          if (e.touches && e.touches.length > 0) {\n            return { x: e.touches[0].clientX, y: e.touches[0].clientY };\n          }\n          return { x: e.clientX, y: e.clientY };\n        }\n        function startResize(resizer, type, e) {\n          isResizing = true;\n          currentResizer = type;\n          const pos = getClientPos(e);\n          if (type === 'header') {\n            startY = pos.y;\n            startHeight = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--topbar-height'));\n          } else if (type === 'sidebar') {\n            startX = pos.x;\n            startWidth = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--sidebar-width'));\n          } else if (type === 'mobile-footer') {\n            startY = pos.y;\n            const currentVh = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--mobile-footer-height'));\n            startHeight = (currentVh / 100) * window.innerHeight;\n          }\n          resizer.classList.add('resizing');\n          document.body.classList.add('resizing');\n          document.body.style.cursor = (type === 'sidebar') ? 'col-resize' : 'row-resize';\n          e.preventDefault();\n        }\n        if (headerResizer) {\n          headerResizer.addEventListener('mousedown', (e) => startResize(headerResizer, 'header', e));\n          headerResizer.addEventListener('touchstart', (e) => startResize(headerResizer, 'header', e), { passive: false });\n        }\n        if (sidebarResizer) {\n          sidebarResizer.addEventListener('mousedown', (e) => startResize(sidebarResizer, 'sidebar', e));\n          sidebarResizer.addEventListener('touchstart', (e) => startResize(sidebarResizer, 'sidebar', e), { passive: false });\n        }\n        if (mobileFooterResizer) {\n          mobileFooterResizer.addEventListener('mousedown', (e) => startResize(mobileFooterResizer, 'mobile-footer', e));\n          mobileFooterResizer.addEventListener('touchstart', (e) => startResize(mobileFooterResizer, 'mobile-footer', e), { passive: false });\n        }\n        function handleMove(e) {\n          if (!isResizing) return;\n          const pos = getClientPos(e);\n          if (currentResizer === 'header') {\n            const deltaY = pos.y - startY;\n            const newHeight = Math.max(40, Math.min(150, startHeight + deltaY));\n            document.documentElement.style.setProperty('--topbar-height', newHeight + 'px');\n          } else if (currentResizer === 'sidebar') {\n            const deltaX = startX - pos.x;\n            const newWidth = Math.max(200, Math.min(800, startWidth + deltaX));\n            document.documentElement.style.setProperty('--sidebar-width', newWidth + 'px');\n          } else if (currentResizer === 'mobile-footer') {\n            const deltaY = startY - pos.y;\n            const newHeight = startHeight + deltaY;\n            const newVh = Math.max(15, Math.min(80, (newHeight / window.innerHeight) * 100));\n            document.documentElement.style.setProperty('--mobile-footer-height', newVh + 'vh');\n          }\n          e.preventDefault();\n        }\n        document.addEventListener('mousemove', handleMove);\n        document.addEventListener('touchmove', handleMove, { passive: false });\n        function handleEnd() {\n          if (isResizing) {\n            isResizing = false;\n            if (currentResizer === 'header') {\n              PAGE_STATE.topbarHeight = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--topbar-height'));\n              headerResizer.classList.remove('resizing');\n            } else if (currentResizer === 'sidebar') {\n              PAGE_STATE.sidebarWidth = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--sidebar-width'));\n              sidebarResizer.classList.remove('resizing');\n            } else if (currentResizer === 'mobile-footer') {\n              PAGE_STATE.mobileFooterHeight = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--mobile-footer-height'));\n              mobileFooterResizer.classList.remove('resizing');\n            }\n            document.body.classList.remove('resizing');\n            document.body.style.cursor = '';\n            currentResizer = null;\n          }\n        }\n        document.addEventListener('mouseup', handleEnd);\n        document.addEventListener('touchend', handleEnd);\n        document.addEventListener('touchcancel', handleEnd);\n      })();\n      document.getElementById(\"import-data-file\").addEventListener(\"change\", async (e) => {\n       const file = e.target.files[0];\n       if (!file) return;\n       try {\n        const text = await file.text();\n        const data = JSON.parse(text);\n        if (!data.nodeData || !data.edgeData) {\n         alert(\"Invalid data file. Missing required fields.\");\n         return;\n        }\n        const confirmMsg = `This will replace all current data with the imported data.\\n\\nImporting:\\n- ${Object.keys(data.nodeData).length} nodes\\n- ${data.edgeData.list?.length || 0} connections\\n- ${data.documentTabs?.length || 1} tab(s)\\n\\nContinue?`;\n        if (!confirm(confirmMsg)) {\n         e.target.value = \"\";\n         return;\n        }\n\t\tpushUndo('import json');\n        NODE_DATA = data.nodeData || {};\n        EDGE_DATA = data.edgeData || {\n         list: []\n        };\n        EDGE_LEGEND = data.edgeLegend || {};\n        RECT_DATA = data.rectData || { list: [] };\n        TEXT_DATA = data.textData || { list: [] };\n        savedPositions = data.nodePositions || {};\n        savedSizes = data.nodeSizes || {};\n        savedStyles = data.nodeStyles || {};\n        if (data.page) {\n         PAGE_STATE = Object.assign({}, DEFAULT_PAGE_STATE, data.page);\n         wieldThePower();\n        }\n        if (data.canvas) {\n         canvasState.zoom = data.canvas.zoom || 1;\n         canvasState.panX = data.canvas.panX || 0;\n         canvasState.panY = data.canvas.panY || 0;\n        }\n        if (data.page?.title) {\n         document.title = data.page.title;\n         document.querySelector(\".editable-page-title\", ).textContent = data.page.title;\n        }\n        if (data.savedStyleSets) {\n          savedStyleSets = data.savedStyleSets;\n        }\n        if (data.documentTabs) {\n         documentTabs = data.documentTabs;\n         currentTabIndex = data.currentTabIndex || 0;\n        }\n        if (data.savedTopologyView) {\n         savedTopologyView = data.savedTopologyView;\n        }\n        if (data.encryptedSections) {\n         encryptedSections = data.encryptedSections;\n        }\n        if (data.auditLog && Array.isArray(data.auditLog)) {\n         auditLog = data.auditLog;\n         try {\n           localStorage.setItem(AUDIT_STORAGE_KEY, JSON.stringify(auditLog));\n         } catch (e) {\n           console.warn(\"Failed to sync audit log to localStorage:\", e);\n         }\n        }\n        Object.values(NODE_DATA).forEach(node => {\n          if (!node.tags) node.tags = [];\n          if (!node.notes) node.notes = [];\n        });\n        EDGE_DATA.list.forEach(edge => {\n          if (!edge.notes) edge.notes = [];\n        });\n        forgeTheTopology();\n        if (typeof forgeTheLegend === 'function') forgeTheLegend();\n\t\tlogAuditEvent(\"import\", `Imported JSON: ${file.name} (${Object.keys(data.nodeData).length} nodes, ${data.edgeData.list?.length || 0} connections)`);\n        updateViewBox();\n        const nodeIds = Object.keys(NODE_DATA);\n        if (nodeIds.length > 0) {\n         claimTheImmortal(nodeIds[0]);\n        } else {\n         document.getElementById(\"node-panel\").style.display = \"none\";\n         document.getElementById(\"edge-panel\").style.display = \"none\";\n         document.getElementById(\"topology-toolbar\").style.display = \"none\";\n        }\n        e.target.value = \"\";\n       } catch (err) {\n        console.error(\"Import error:\", err);\n        alert(`Failed to import data: ${err.message}`);\n        e.target.value = \"\";\n       }\n      });\n      const saveHelpBtn = document.getElementById(\"save-help-btn\");\n      const saveInfoModal = document.getElementById(\"save-info-modal\");\n      const saveInfoClose = document.getElementById(\"save-info-close\");\n      saveHelpBtn.addEventListener(\"click\", () => {\n       saveInfoModal.classList.add(\"active\");\n      });\n      saveInfoClose.addEventListener(\"click\", () => {\n       saveInfoModal.classList.remove(\"active\");\n      });\n      saveInfoModal.addEventListener(\"click\", (e) => {\n       if (e.target === saveInfoModal) {\n        saveInfoModal.classList.remove(\"active\");\n       }\n      });\n      document.querySelectorAll(\".help-tab\").forEach(tab => {\n        tab.addEventListener(\"click\", () => {\n          document.querySelectorAll(\".help-tab\").forEach(t => { t.style.background = \"var(--panel)\"; t.style.color = \"var(--text-main)\"; });\n          tab.style.background = \"var(--accent)\"; tab.style.color = \"var(--bg)\";\n          document.querySelectorAll(\".help-tab-content\").forEach(c => c.style.display = \"none\");\n          document.getElementById(\"help-tab-\" + tab.dataset.tab).style.display = \"block\";\n        });\n      });\n      async function deriveKey(password, salt) {\n      const encoder = new TextEncoder();\n      const keyMaterial = await crypto.subtle.importKey(\n      \"raw\",\n      encoder.encode(password),\n      \"PBKDF2\",\n      false,\n      [\"deriveKey\"]\n      );\n      return crypto.subtle.deriveKey(\n      {\n      name: \"PBKDF2\",\n      salt: salt,\n      iterations: 200000,\n      hash: \"SHA-256\"\n      },\n      keyMaterial,\n      {\n      name: \"AES-GCM\",\n      length: 256\n      },\n      false,\n      [\"encrypt\", \"decrypt\"]\n      );\n      }\n      async function encryptData(data, password) {\n      const encoder = new TextEncoder();\n      const salt = crypto.getRandomValues(new Uint8Array(16));\n      const iv   = crypto.getRandomValues(new Uint8Array(12));\n      const key = await deriveKey(password, salt);\n      const encrypted = await crypto.subtle.encrypt(\n      { name: \"AES-GCM\", iv },\n      key,\n      encoder.encode(data)\n      );\n      const encryptedU8 = new Uint8Array(encrypted);\n      const result = new Uint8Array(salt.length + iv.length + encryptedU8.length);\n      result.set(salt, 0);\n      result.set(iv, salt.length);\n      result.set(encryptedU8, salt.length + iv.length);\n      return \"ENCRYPTED:\" + u8ToBase64(result);\n      }\n      async function decryptData(encryptedData, password) {\n      const base64Data = encryptedData.replace(\"ENCRYPTED:\", \"\");\n      const fullData   = base64ToU8(base64Data);\n      const salt      = fullData.slice(0, 16);\n      const iv        = fullData.slice(16, 28);\n      const encrypted = fullData.slice(28);\n      const key = await deriveKey(password, salt);\n      const decrypted = await crypto.subtle.decrypt(\n      { name: \"AES-GCM\", iv },\n      key,\n      encrypted\n      );\n      const decoder = new TextDecoder();\n      return decoder.decode(decrypted);\n      }\n      function isEncrypted(data) {\n       return typeof data === \"string\" && data.startsWith(\"ENCRYPTED:\");\n      }\n      function captureTheQuickening() {\n       const currentTab = documentTabs[currentTabIndex];\n       currentTab.nodes = JSON.parse(JSON.stringify(NODE_DATA));\n       currentTab.edges = JSON.parse(JSON.stringify(EDGE_DATA));\n       currentTab.positions = JSON.parse(JSON.stringify(savedPositions));\n       currentTab.sizes = JSON.parse(JSON.stringify(savedSizes));\n       currentTab.styles = JSON.parse(JSON.stringify(savedStyles));\n       currentTab.legend = JSON.parse(JSON.stringify(EDGE_LEGEND));\n       currentTab.rects = JSON.parse(JSON.stringify(RECT_DATA));\n       currentTab.texts = JSON.parse(JSON.stringify(TEXT_DATA));\n       currentTab.pageState = JSON.parse(JSON.stringify(PAGE_STATE));\n       return {\n        nodeData: NODE_DATA,\n        edgeData: EDGE_DATA,\n        rectData: RECT_DATA,\n        textData: TEXT_DATA,\n        edgeLegend: EDGE_LEGEND,\n        nodePositions: savedPositions,\n        nodeSizes: savedSizes,\n        nodeStyles: savedStyles,\n        page: PAGE_STATE,\n        canvas: {\n         zoom: canvasState.zoom,\n         panX: canvasState.panX,\n         panY: canvasState.panY,\n        },\n        savedTopologyView: savedTopologyView,\n        documentTabs: documentTabs,\n        currentTabIndex: currentTabIndex,\n        encryptedSections: encryptedSections,\n        auditLog: auditLog,\n\t\tsavedStyleSets: savedStyleSets,\n       };\n      }\n      function assembleTheImmortalForm() {\n      const canvasHintEl = document.getElementById(\"canvas-hint\");\n       const savedHintHTML = canvasHintEl ? canvasHintEl.innerHTML : \"\";\n       if (canvasHintEl) canvasHintEl.innerHTML = DEFAULT_CANVAS_HINT;\n       const clone = document.documentElement.cloneNode(true);\n       [\"edge-legend-mini\", \"minimap-mini\", \"draw-toolbar-mini\", \"topology-toolbar-mini\"].forEach(id => {\n        const all = clone.querySelectorAll(\"#\" + id);\n        for (let i = 1; i < all.length; i++) {\n         all[i].remove();\n        }\n       });\n       const addLineSelect = clone.querySelector(\"#add-line-select\");\n       if (addLineSelect) addLineSelect.innerHTML = \"\";\n       const minimapSvg = clone.querySelector(\"#minimap\");\n       if (minimapSvg) {\n        minimapSvg.querySelectorAll(\".minimap-node, .minimap-edge\").forEach(el => el.remove());\n       }\n       const canvasGrid = clone.querySelector(\"#canvas-grid\");\n       if (canvasGrid) canvasGrid.innerHTML = \"\";\n       const mainSvg = clone.querySelector(\"#map\");\n       if (mainSvg) mainSvg.innerHTML = \"\";\n       const nodeScript = clone.querySelector(\"#nodes-json\");\n       if (nodeScript) {\n        nodeScript.textContent = JSON.stringify(NODE_DATA, null, 2);\n       }\n       let stateScript = clone.querySelector(\"#topology-state\");\n       if (!stateScript) {\n        stateScript = document.createElement(\"script\");\n        stateScript.id = \"topology-state\";\n        stateScript.type = \"application/json\";\n        const body = clone.querySelector(\"body\") || clone;\n        body.appendChild(stateScript);\n       }\n       stateScript.textContent = JSON.stringify(captureTheQuickening(), null, 2);\n       return \"<!DOCTYPE html> \\n \" + clone.outerHTML;\n      }\n      async function becomeImmortal() {\n       saveRollbackVersion(\"Auto-save\");\n       const encryptEnabled = document.getElementById(\"encrypt-toggle\").checked;\n       let stateData = JSON.stringify(captureTheQuickening(), null, 2);\n       if (encryptEnabled) {\n        const password = prompt(\"Enter a password to encrypt your data:\\n(Remember this password! You will need it to open this file and its non recoverable!)\");\n        if (!password) {\n         alert(\"Encryption cancelled. File not saved.\");\n         return;\n        }\n        const confirmPassword = prompt(\"Confirm your password:\");\n        if (password !== confirmPassword) {\n         alert(\"Passwords do not match. File not saved.\");\n         return;\n        }\n        try {\n         stateData = await encryptData(stateData, password);\n        } catch (e) {\n         alert(\"Encryption failed: \" + e.message);\n         return;\n        }\n       }\n       const canvasHintEl = document.getElementById(\"canvas-hint\");\n       const savedHintHTML = canvasHintEl ? canvasHintEl.innerHTML : \"\";\n       if (canvasHintEl) canvasHintEl.innerHTML = DEFAULT_CANVAS_HINT;\n       const clone = document.documentElement.cloneNode(true);\n       [\"edge-legend-mini\", \"minimap-mini\", \"draw-toolbar-mini\", \"topology-toolbar-mini\"].forEach(id => {\n        const all = clone.querySelectorAll(\"#\" + id);\n        for (let i = 1; i < all.length; i++) {\n         all[i].remove();\n        }\n       });\n       const selectsToClear = [\"#add-line-select\", \"#new-edge-from\", \"#new-edge-to\", \"#assigned-rack-select\"];\n       selectsToClear.forEach(sel => {\n        const el = clone.querySelector(sel);\n        if (el) el.innerHTML = \"\";\n       });\n       const minimapSvg = clone.querySelector(\"#minimap\");\n       if (minimapSvg) minimapSvg.querySelectorAll(\".minimap-node, .minimap-edge\").forEach(el => el.remove());\n       const mainSvg = clone.querySelector(\"#map\");\n       if (mainSvg) mainSvg.innerHTML = \"\";\n       const edgeLegendContent = clone.querySelector(\"#edge-legend-content\");\n       if (edgeLegendContent) edgeLegendContent.innerHTML = \"\";\n       const nodeTags = clone.querySelector(\"#node-tags\");\n       if (nodeTags) nodeTags.innerHTML = \"\";\n       const nodeNotes = clone.querySelector(\"#node-notes\");\n       if (nodeNotes) nodeNotes.innerHTML = \"\";\n       const edgeNotes = clone.querySelector(\"#edge-notes\");\n       if (edgeNotes) edgeNotes.innerHTML = \"\";\n       const tabBar = clone.querySelector(\"#tab-bar\");\n       if (tabBar) tabBar.innerHTML = \"\";\n       const rollbackList = clone.querySelector(\"#rollback-list\");\n       if (rollbackList) rollbackList.innerHTML = \"\";\n       const auditList = clone.querySelector(\"#audit-log-list\");\n       if (auditList) auditList.innerHTML = \"\";\n       const nodeScript = clone.querySelector(\"#nodes-json\");\n       if (nodeScript) {\n        if (encryptEnabled) {\n         nodeScript.textContent = JSON.stringify({}, null, 2);\n        } else {\n         nodeScript.textContent = JSON.stringify(NODE_DATA, null, 2);\n        }\n       }\n       let stateScript = clone.querySelector(\"#topology-state\");\n       if (!stateScript) {\n        stateScript = document.createElement(\"script\");\n        stateScript.id = \"topology-state\";\n        stateScript.type = \"application/json\";\n        const body = clone.querySelector(\"body\") || clone;\n        body.appendChild(stateScript);\n       }\n       stateScript.textContent = stateData;\n       const html = \"<!DOCTYPE html> \\n \" + clone.outerHTML;\n       const blob = new Blob([html], {\n        type: \"text/html\"\n       });\n       const url = URL.createObjectURL(blob);\n       const a = document.createElement(\"a\");\n       a.href = url;\n       const safeTitle = (PAGE_STATE.title || document.title || \"network-topology\").toLowerCase().replace(/[^a-z0-9\\-]+/g, \"-\");\n       a.download = safeTitle + \".html\";\n       document.body.appendChild(a);\n       a.click();\n       document.body.removeChild(a);\n       URL.revokeObjectURL(url);\n       if (canvasHintEl) canvasHintEl.innerHTML = savedHintHTML;\n       logAuditEvent(\"save\", `File saved: ${safeTitle}.html`);\n      }\n      function captureState(scope = \"all\") {\n       const clone = typeof structuredClone === 'function' \n         ? (o) => structuredClone(o)\n         : (o) => JSON.parse(JSON.stringify(o));\n       \n       if (scope === \"all\") {\n        return {\n         scope: \"all\",\n         nodes: clone(NODE_DATA),\n         edges: clone(EDGE_DATA),\n         positions: clone(savedPositions),\n         sizes: clone(savedSizes),\n         styles: clone(savedStyles),\n         legend: clone(EDGE_LEGEND),\n         rects: clone(RECT_DATA),\n         texts: clone(TEXT_DATA)\n        };\n       }\n       \n       const state = { scope };\n       if (scope.includes(\"nodes\")) state.nodes = clone(NODE_DATA);\n       if (scope.includes(\"edges\")) state.edges = clone(EDGE_DATA);\n       if (scope.includes(\"positions\")) state.positions = clone(savedPositions);\n       if (scope.includes(\"sizes\")) state.sizes = clone(savedSizes);\n       if (scope.includes(\"styles\")) state.styles = clone(savedStyles);\n       if (scope.includes(\"legend\")) state.legend = clone(EDGE_LEGEND);\n       if (scope.includes(\"rects\")) state.rects = clone(RECT_DATA);\n       if (scope.includes(\"texts\")) state.texts = clone(TEXT_DATA);\n       return state;\n      }\n      let lastUndoPush = 0;\n\t  function pushUndo(action = \"\") {\n\t   const now = Date.now();\n\t   if (now - lastUndoPush < 100 && undoStack.length > 0) {\n\t     return;\n    \t }\n\t   lastUndoPush = now;\n       const actionScopes = {\n        \"move nodes\": \"positions\",\n        \"nudge\": \"positions\",\n        \"align nodes\": \"positions\",\n        \"distribute nodes\": \"positions\",\n        \"snap to grid\": \"positions\",\n        \"resize node\": \"sizes\",\n        \"reset size\": \"sizes\",\n        \"style change\": \"styles\",\n        \"edit edge\": \"edges\",\n        \"edit edge routing\": \"edges\",\n        \"edit edge point\": \"edges\",\n        \"add edge\": \"edges,positions\",\n        \"delete edge\": \"edges\",\n        \"add edge note\": \"edges\",\n        \"edit edge note\": \"edges\",\n        \"delete edge note\": \"edges\",\n        \"draw zone\": \"rects\",\n        \"delete zone\": \"rects\",\n        \"resize zone\": \"rects\",\n        \"edit zone\": \"rects\",\n        \"add zone note\": \"rects\",\n        \"delete zone note\": \"rects\",\n        \"change zone line style\": \"rects\",\n        \"add text\": \"texts\",\n        \"edit text\": \"texts\",\n        \"delete text\": \"texts\",\n       };\n       const scope = actionScopes[action] || \"all\";\n       const state = captureState(scope);\n\t   undoStack.push(state);\n        if (undoStack.length > MAX_UNDO_STACK) {\n        undoStack.shift();\n       }\n       redoStack = [];\n       updateUndoButtons();\n       if (action) {\n        const actionTypeMap = {\n  \"create node\": \"node\",\n  \"delete node\": \"node\",\n  \"add node\": \"node\",\n  \"edit\": \"node\",\n  \"clone node\": \"node\",\n  \"paste node\": \"node\",\n  \"move nodes\": \"node\",\n  \"nudge\": \"node\",\n  \"align nodes\": \"node\",\n  \"distribute nodes\": \"node\",\n  \"snap to grid\": \"node\",\n  \"toggle group\": \"node\",\n  \"toggle lock\": \"node\",\n  \"create rack\": \"rack\",\n  \"add rack\": \"rack\",\n  \"edit rack\": \"rack\",\n  \"edit mac\": \"rack\",\n  \"edit U height\": \"rack\",\n  \"change rack capacity\": \"rack\",\n  \"change assigned rack\": \"rack\",\n  \"add connection\": \"connection\",\n  \"delete connection\": \"connection\",\n  \"delete edge\": \"connection\",\n  \"clone edge\": \"connection\",\n  \"paste edge\": \"connection\",\n  \"style change\": \"style\",\n  \"change layer\": \"layer\",\n  \"add text\": \"text\",\n  \"edit text\": \"text\",\n  \"delete text\": \"text\",\n  \"clone text\": \"text\",\n  \"paste text\": \"text\",\n  \"draw zone\": \"zone\",\n  \"delete zone\": \"zone\",\n  \"delete rect\": \"zone\",\n  \"clone rect\": \"zone\",\n  \"paste rect\": \"zone\",\n  \"change zone line style\": \"zone\",\n  \"delete selected\": \"bulk\",\n  \"clone selected\": \"bulk\",\n};\n        const type = actionTypeMap[action] || \"node\";\n        logAuditEvent(type, action);\n       }\n      }\n      function undo() {\n       if (undoStack.length === 0) return;\n       const currentState = captureState();\n       redoStack.push(currentState);\n       const previousState = undoStack.pop();\n       restoreState(previousState);\n       updateUndoButtons();\n       logAuditEvent(\"undo\", \"Undo action performed\");\n      }\n      function redo() {\n       if (redoStack.length === 0) return;\n       logAuditEvent(\"redo\", \"Redo action performed\");\n       const currentState = captureState();\n       undoStack.push(currentState);\n       const nextState = redoStack.pop();\n       restoreState(nextState);\n       updateUndoButtons();\n      }\n      function restoreState(state) {\n      if (state.nodes) NODE_DATA = state.nodes;\n      if (state.edges) EDGE_DATA = state.edges;\n      if (state.positions) savedPositions = state.positions;\n      if (state.sizes) savedSizes = state.sizes;\n      if (state.styles) savedStyles = state.styles;\n      if (state.legend) EDGE_LEGEND = state.legend;\n      if (state.rects) RECT_DATA = state.rects;\n      if (state.texts) TEXT_DATA = state.texts;\n      forgeTheTopology();\n      if (currentNodeId && NODE_DATA[currentNodeId]) {\n       claimTheImmortal(currentNodeId);\n      } else if (currentEdgeId) {\n       selectTheConnection(currentEdgeId);\n       }\n      }\n      function updateUndoButtons() {\n       const undoBtn = document.getElementById(\"undo-btn\");\n       const redoBtn = document.getElementById(\"redo-btn\");\n       if (undoBtn) {\n        undoBtn.disabled = undoStack.length === 0;\n        undoBtn.style.opacity = undoStack.length === 0 ? \"0.5\" : \"1\";\n       }\n       if (redoBtn) {\n        redoBtn.disabled = redoStack.length === 0;\n        redoBtn.style.opacity = redoStack.length === 0 ? \"0.5\" : \"1\";\n       }\n      }\n      function editNodeMac(id) {\n       const currentMac = NODE_DATA[id].mac || \"\";\n       showEditModal(\"Edit MAC Address\", currentMac, (newMac) => {\n        pushUndo(\"edit mac\");\n        NODE_DATA[id].mac = newMac;\n        if (currentNodeId === id) {\n         document.getElementById(\"node-mac\").textContent = newMac || \"--\";\n        }\n       });\n      }\n      function editNodeRack(id) {\n       const node = NODE_DATA[id];\n       if (!node) return;\n       document.getElementById(\"modal-title\").textContent = \"Edit Rack Unit\";\n       document.getElementById(\"modal-input\").value = node.rackUnit || \"\";\n       document.getElementById(\"edit-modal\").style.display = \"\";\n       document.getElementById(\"edit-modal\").classList.add(\"active\");\n       document.getElementById(\"modal-input\").focus();\n\t   const modal = document.getElementById(\"edit-modal\");\n       const cleanup = () => {\n        modal.classList.remove(\"active\");\n        document.getElementById(\"modal-save\").removeEventListener(\"click\", saveHandler);\n        document.getElementById(\"modal-cancel\").removeEventListener(\"click\", cancelHandler);\n        modal.removeEventListener(\"click\", bgHandler);\n        document.removeEventListener(\"keydown\", escHandler);\n       };\n       const escHandler = (e) => {\n        if (e.key === \"Escape\") cleanup();\n       };\n       document.addEventListener(\"keydown\", escHandler);\n       const saveHandler = () => {\n        pushUndo(\"edit rack\");\n        const value = document.getElementById(\"modal-input\").value.trim();\n        node.rackUnit = value;\n        cleanup();\n        claimTheImmortal(id);\n       };\n       const cancelHandler = () => {\n        cleanup();\n       };\n       const bgHandler = (e) => {\n        if (e.target === modal) cancelHandler();\n       };\n       document.getElementById(\"modal-save\").addEventListener(\"click\", saveHandler);\n       document.getElementById(\"modal-cancel\").addEventListener(\"click\", cancelHandler);\n       modal.addEventListener(\"click\", bgHandler);\n      }\n      function editNodeUHeight(id) {\n       const node = NODE_DATA[id];\n       if (!node) return;\n       document.getElementById(\"modal-title\").textContent = \"Edit U Height\";\n       document.getElementById(\"modal-input\").value = node.uHeight || \"1\";\n       document.getElementById(\"edit-modal\").style.display = \"\";\n       document.getElementById(\"edit-modal\").classList.add(\"active\");\n       document.getElementById(\"modal-input\").focus();\n\t   const modal = document.getElementById(\"edit-modal\");\n       const cleanup = () => {\n        modal.classList.remove(\"active\");\n        document.getElementById(\"modal-save\").removeEventListener(\"click\", saveHandler);\n        document.getElementById(\"modal-cancel\").removeEventListener(\"click\", cancelHandler);\n        modal.removeEventListener(\"click\", bgHandler);\n        document.removeEventListener(\"keydown\", escHandler);\n       };\n       const escHandler = (e) => {\n        if (e.key === \"Escape\") cleanup();\n       };\n       document.addEventListener(\"keydown\", escHandler);\n       const saveHandler = () => {\n        pushUndo(\"edit U height\");\n        const value = document.getElementById(\"modal-input\").value.trim();\n        node.uHeight = value;\n        cleanup();\n        claimTheImmortal(id);\n        forgeTheTopology();\n       };\n       const cancelHandler = () => {\n        cleanup();\n       };\n       const bgHandler = (e) => {\n        if (e.target === modal) cancelHandler();\n       };\n       document.getElementById(\"modal-save\").addEventListener(\"click\", saveHandler);\n       document.getElementById(\"modal-cancel\").addEventListener(\"click\", cancelHandler);\n       modal.addEventListener(\"click\", bgHandler);\n      }\n      function updateEdgePortLabels(edgeId) {\n       const edge = EDGE_DATA.list.find(e => e.id === edgeId);\n       if (!edge || edge.type === \"custom\") return;\n       const fromPortInput = document.getElementById(\"edge-from-port\");\n       const toPortInput = document.getElementById(\"edge-to-port\");\n       if (fromPortInput && toPortInput) {\n        edge.fromPort = fromPortInput.value || \"\";\n        edge.toPort = toPortInput.value || \"\";\n        forgeTheTopology();\n       }\n      }\n      function clearSelection() {\n       selectedNodes.clear();\n       selectedEdges.clear();\n       selectedRects.clear();\n       selectedTexts.clear();\n       updateAllSelections();\n      }\n      function updateAllSelections() {\n      updateNodeSelection();\n      document.querySelectorAll(\".edge\").forEach(el => {\n      const edgeId = el.dataset.edgeId;\n      if (selectedEdges.has(edgeId)) {\n      el.style.filter = \"drop-shadow(0 0 8px #4fd1c5)\";\n      } else {\n      el.style.filter = \"\";\n      }\n      });\n      document.querySelectorAll(\".rect-group\").forEach(el => {\n      const rectId = el.dataset.rectId;\n      if (selectedRects.has(rectId)) {\n      el.style.filter = \"drop-shadow(0 0 8px #4fd1c5)\";\n      } else {\n      el.style.filter = \"\";\n      }\n      });\n      document.querySelectorAll(\".text-group\").forEach(el => {\n      const textId = el.dataset.textId;\n      if (selectedTexts.has(textId)) {\n      el.style.filter = \"drop-shadow(0 0 8px #4fd1c5)\";\n      } else {\n      el.style.filter = \"\";\n      }\n      });\n      const total = selectedNodes.size + selectedEdges.size + selectedRects.size + selectedTexts.size;\n      const bulkToolbar = document.getElementById(\"bulk-toolbar\");\n      const bulkToolbarMobile = document.getElementById(\"bulk-toolbar-mobile\");\n      const bulkCount = document.getElementById(\"bulk-count\");\n      const bulkCountMobile = document.getElementById(\"bulk-count-mobile\");\n      if (total > 0) {\n      if (bulkToolbar) bulkToolbar.style.display = \"flex\";\n      if (bulkToolbarMobile) bulkToolbarMobile.style.display = \"flex\";\n      if (bulkCount) bulkCount.textContent = total;\n      if (bulkCountMobile) bulkCountMobile.textContent = total;\n      } else {\n      if (bulkToolbar) bulkToolbar.style.display = \"none\";\n      if (bulkToolbarMobile) bulkToolbarMobile.style.display = \"none\";\n      }\n      }\n      function updateNodeSelection() {\n       if (isViewOnly()) {\n        selectedNodes.clear();\n        selectedRects.clear();\n        selectedTexts.clear();\n        const bulkToolbar = document.getElementById(\"bulk-toolbar\");\n        const bulkToolbarMobile = document.getElementById(\"bulk-toolbar-mobile\");\n        if (bulkToolbar) bulkToolbar.style.display = \"none\";\n        if (bulkToolbarMobile) bulkToolbarMobile.style.display = \"none\";\n        return;\n       }\n       document.querySelectorAll(\".node-group\").forEach(node => {\n        const nodeId = node.dataset.nodeId;\n        if (selectedNodes.has(nodeId)) {\n         node.classList.add(\"selected\");\n        } else {\n         node.classList.remove(\"selected\");\n        }\n       });\n       const bulkToolbar = document.getElementById(\"bulk-toolbar\");\n       const bulkToolbarMobile = document.getElementById(\"bulk-toolbar-mobile\");\n       const bulkCount = document.getElementById(\"bulk-count\");\n       const bulkCountMobile = document.getElementById(\"bulk-count-mobile\");\n       const bulkCountModal = document.getElementById(\"bulk-count-modal\");\n       if (selectedNodes.size > 0) {\n        if (bulkToolbar) bulkToolbar.style.display = \"flex\";\n        if (bulkToolbarMobile) bulkToolbarMobile.style.display = \"flex\";\n        if (bulkCount) bulkCount.textContent = selectedNodes.size;\n        if (bulkCountMobile) bulkCountMobile.textContent = selectedNodes.size;\n        if (bulkCountModal) bulkCountModal.textContent = selectedNodes.size;\n       } else {\n        if (bulkToolbar) bulkToolbar.style.display = \"none\";\n        if (bulkToolbarMobile) bulkToolbarMobile.style.display = \"none\";\n        const modal = document.getElementById(\"bulk-actions-modal\");\n        if (modal) modal.style.display = \"none\";\n       }\n      }\n      function deleteSelected() {\n       const total = selectedNodes.size + selectedEdges.size + selectedRects.size + selectedTexts.size;\n       if (total === 0) return;\n       let nodesInsideRacks = [];\n       selectedNodes.forEach(nodeId => {\n        if (NODE_DATA[nodeId]?.isRack) {\n         Object.entries(NODE_DATA).forEach(([id, n]) => {\n          if (n.assignedRack === nodeId) nodesInsideRacks.push(n.name || id);\n         });\n        }\n       });\n       let message = `Delete ${total} selected item(s)?`;\n       if (nodesInsideRacks.length > 0) {\n        message += `\\n\\nThis will also delete ${nodesInsideRacks.length} node(s) inside rack(s):\\n• ${nodesInsideRacks.join('\\n• ')}`;\n       }\n       challengeTheImmortal(message, () => {\n        pushUndo(\"delete selected\");\n        selectedNodes.forEach(nodeId => {\n         if (NODE_DATA[nodeId]?.isRack) {\n          Object.keys(NODE_DATA).forEach(id => {\n           if (NODE_DATA[id]?.assignedRack === nodeId) {\n            EDGE_DATA.list = EDGE_DATA.list.filter(e => e.from !== id && e.to !== id);\n            delete NODE_DATA[id];\n            delete savedPositions[id];\n            delete savedSizes[id];\n            delete savedStyles[id];\n           }\n          });\n         }\n         delete NODE_DATA[nodeId];\n         delete savedPositions[nodeId];\n         delete savedSizes[nodeId];\n         delete savedStyles[nodeId];\n         EDGE_DATA.list = EDGE_DATA.list.filter(e => e.from !== nodeId && e.to !== nodeId);\n        });\n        selectedEdges.forEach(edgeId => {\n         EDGE_DATA.list = EDGE_DATA.list.filter(e => e.id !== edgeId);\n        });\n        selectedRects.forEach(rectId => {\n         RECT_DATA.list = RECT_DATA.list.filter(r => r.id !== rectId);\n        });\n        selectedTexts.forEach(textId => {\n         TEXT_DATA.list = TEXT_DATA.list.filter(t => t.id !== textId);\n        });\n        clearSelection();\n        forgeTheTopology();\n       });\n      }\n      function startSelection(event) {\n       if (event.button !== 0) return;\n       if (event.target.closest(\".node-group\")) return;\n       isSelecting = true;\n       const svg = document.getElementById(\"map\");\n       const pt = svg.createSVGPoint();\n       pt.x = event.clientX;\n       pt.y = event.clientY;\n       const svgP = pt.matrixTransform(svg.getScreenCTM().inverse());\n       selectionStart = { x: svgP.x, y: svgP.y };\n       clearSelection();\n       preDragSelectedNodes = new Set(selectedNodes);\n       preDragSelectedEdges = new Set(selectedEdges);\n       preDragSelectedRects = new Set(selectedRects);\n       preDragSelectedTexts = new Set(selectedTexts);\n       if (!selectionRect || !selectionRect.parentNode) {\n        selectionRect = document.createElementNS(\"http://www.w3.org/2000/svg\", \"rect\");\n        selectionRect.setAttribute(\"fill\", selectionBoxStyle.fillColor);\n        selectionRect.setAttribute(\"fill-opacity\", selectionBoxStyle.fillOpacity);\n        selectionRect.setAttribute(\"stroke\", selectionBoxStyle.strokeColor);\n        selectionRect.setAttribute(\"stroke-width\", selectionBoxStyle.strokeWidth);\n        selectionRect.setAttribute(\"stroke-dasharray\", selectionBoxStyle.strokeDasharray);\n        svg.appendChild(selectionRect);\n       }\n       selectionRect.style.display = \"block\";\n      }\n      function updateSelection(event) {\n       if (!isSelecting || !selectionStart) return;\n       const svg = document.getElementById(\"map\");\n       const pt = svg.createSVGPoint();\n       pt.x = event.clientX;\n       pt.y = event.clientY;\n       const svgP = pt.matrixTransform(svg.getScreenCTM().inverse());\n       const x = Math.min(selectionStart.x, svgP.x);\n       const y = Math.min(selectionStart.y, svgP.y);\n       const width = Math.abs(svgP.x - selectionStart.x);\n       const height = Math.abs(svgP.y - selectionStart.y);\n       selectionRect.setAttribute(\"x\", x);\n       selectionRect.setAttribute(\"y\", y);\n       selectionRect.setAttribute(\"width\", width);\n       selectionRect.setAttribute(\"height\", height);\n       const box = { x, y, width, height };\n       Object.entries(savedPositions).forEach(([nodeId, pos]) => {\n        const size = savedSizes[nodeId] || 50;\n        const halfSize = size / 2;\n        const nodeBox = { x: pos.x - halfSize, y: pos.y - halfSize, width: size, height: size };\n        if (boxesIntersect(box, nodeBox)) {\n         selectedNodes.add(nodeId);\n        } else if (!preDragSelectedNodes.has(nodeId)) {\n         selectedNodes.delete(nodeId);\n        }\n       });\n      EDGE_DATA.list.forEach(edge => {\n        if (!edge.points || edge.points.length === 0) return;\n        for (let i = 0; i < edge.points.length - 1; i++) {\n         const p1 = edge.points[i];\n         const p2 = edge.points[i + 1];\n         if (lineIntersectsBox(p1.x, p1.y, p2.x, p2.y, box)) {\n          selectedEdges.add(edge.id);\n          return;\n         }\n        }\n        if (!preDragSelectedEdges.has(edge.id)) {\n         selectedEdges.delete(edge.id);\n        }\n       });\n       RECT_DATA.list.forEach(rect => {\n        const rectBox = { x: rect.x, y: rect.y, width: rect.width, height: rect.height };\n        if (boxesIntersect(box, rectBox)) {\n         selectedRects.add(rect.id);\n        } else if (!preDragSelectedRects.has(rect.id)) {\n         selectedRects.delete(rect.id);\n        }\n       });\n       TEXT_DATA.list.forEach(text => {\n        const fontSize = text.fontSize || 18;\n        const textBox = { x: text.x - 50, y: text.y - fontSize, width: 100, height: fontSize * 1.5 };\n        if (boxesIntersect(box, textBox)) {\n         selectedTexts.add(text.id);\n        } else if (!preDragSelectedTexts.has(text.id)) {\n         selectedTexts.delete(text.id);\n        }\n       });\n       updateAllSelectionVisuals();\n      }\n      function endSelection() {\n       isSelecting = false;\n       if (selectionRect) {\n        selectionRect.style.display = \"none\";\n       }\n      }\n      function boxesIntersect(a, b) {\n       return !(a.x + a.width < b.x || b.x + b.width < a.x || a.y + a.height < b.y || b.y + b.height < a.y);\n      }\n      function lineIntersectsBox(x1, y1, x2, y2, box) {\n       if (pointInBox(x1, y1, box) || pointInBox(x2, y2, box)) return true;\n       const lines = [\n        [box.x, box.y, box.x + box.width, box.y],\n        [box.x + box.width, box.y, box.x + box.width, box.y + box.height],\n        [box.x + box.width, box.y + box.height, box.x, box.y + box.height],\n        [box.x, box.y + box.height, box.x, box.y]\n       ];\n       for (const [bx1, by1, bx2, by2] of lines) {\n        if (linesIntersect(x1, y1, x2, y2, bx1, by1, bx2, by2)) return true;\n       }\n       return false;\n      }\n      function pointInBox(x, y, box) {\n       return x >= box.x && x <= box.x + box.width && y >= box.y && y <= box.y + box.height;\n      }\n      function linesIntersect(x1, y1, x2, y2, x3, y3, x4, y4) {\n       const denom = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1);\n       if (Math.abs(denom) < 0.0001) return false;\n       const ua = ((x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)) / denom;\n       const ub = ((x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)) / denom;\n       return ua >= 0 && ua <= 1 && ub >= 0 && ub <= 1;\n      }\n      function updateAllSelectionVisuals() {\n       document.querySelectorAll(\".node-group\").forEach(node => {\n        const nodeId = node.dataset.nodeId;\n        if (selectedNodes.has(nodeId)) {\n         node.classList.add(\"selected\");\n        } else {\n         node.classList.remove(\"selected\");\n        }\n       });\n       document.querySelectorAll(\".edge\").forEach(edge => {\n        const edgeId = edge.dataset.edgeId;\n        if (selectedEdges.has(edgeId)) {\n         edge.style.filter = \"drop-shadow(0 0 6px #4fd1c5)\";\n        } else {\n         edge.style.filter = \"\";\n        }\n       });\n       document.querySelectorAll(\".rect-group rect\").forEach(el => {\n        const rectId = el.closest(\".rect-group\")?.dataset?.rectId;\n        if (selectedRects.has(rectId)) {\n         el.style.filter = \"drop-shadow(0 0 8px #4fd1c5)\";\n        } else {\n         el.style.filter = \"\";\n        }\n       });\n       document.querySelectorAll(\".text-group, .text-element\").forEach(el => {\n        const textId = el.dataset?.textId;\n        if (selectedTexts.has(textId)) {\n         el.style.filter = \"drop-shadow(0 0 8px #4fd1c5)\";\n        } else {\n         el.style.filter = \"\";\n        }\n       });\n       const total = selectedNodes.size + selectedEdges.size + selectedRects.size + selectedTexts.size;\n       const bulkToolbar = document.getElementById(\"bulk-toolbar\");\n       const bulkToolbarMobile = document.getElementById(\"bulk-toolbar-mobile\");\n       const bulkCount = document.getElementById(\"bulk-count\");\n       const bulkCountMobile = document.getElementById(\"bulk-count-mobile\");\n       if (total > 0) {\n        if (bulkToolbar) bulkToolbar.style.display = \"flex\";\n        if (bulkToolbarMobile) bulkToolbarMobile.style.display = \"flex\";\n        if (bulkCount) bulkCount.textContent = total;\n        if (bulkCountMobile) bulkCountMobile.textContent = total;\n       } else {\n        if (bulkToolbar) bulkToolbar.style.display = \"none\";\n        if (bulkToolbarMobile) bulkToolbarMobile.style.display = \"none\";\n       }\n      }\n      function cloneNode(sourceId, skipUndo) {\n       if (!NODE_DATA[sourceId]) return;\n       if (!skipUndo) pushUndo(\"clone node\");\n       const source = NODE_DATA[sourceId];\n       const baseName = source.name + \" copy\";\n       let newName = baseName;\n       let counter = 1;\n       while (Object.values(NODE_DATA).some(n => n.name === newName)) {\n        newName = `${baseName} ${counter}`;\n        counter++;\n       }\n       const baseId = newName.toLowerCase().replace(/[^a-z0-9]+/g, \"-\");\n       let newId = baseId;\n       counter = 1;\n       while (NODE_DATA[newId]) {\n        newId = `${baseId}-${counter}`;\n        counter++;\n       }\n\t\tNODE_DATA[newId] = {\n\t\t shape: source.shape,\n\t\t name: newName,\n\t\t ip: source.ip,\n\t\t role: source.role,\n\t\t tags: [...source.tags],\n\t\t notes: [...source.notes],\n\t\t mac: source.mac || \"\",\n\t\t rackUnit: source.rackUnit || \"\",\n\t\t uHeight: source.uHeight || \"1\",\n\t\t layer: source.layer || \"physical\",\n\t\t assignedRack: source.assignedRack || \"\",\n\t\t rackCapacity: source.rackCapacity || \"42\",\n\t\t isRack: source.isRack || false,\nfovEnabled: source.fovEnabled || false,\n fovAngle: source.fovAngle || 90,\n fovDistance: source.fovDistance || 150,\n fovInnerRadius: source.fovInnerRadius || 0,\n fovRotation: source.fovRotation || 0,\n fovColor: source.fovColor || \"#f59e0b\",\n fovOpacity: source.fovOpacity || 20,\n fovGradient: source.fovGradient || false,\n fovBorderColor: source.fovBorderColor || \"#f59e0b\",\n fovBorderWidth: source.fovBorderWidth ?? 2,\n fovBorderStyle: source.fovBorderStyle || \"solid\",\n fovBorderOpacity: source.fovBorderOpacity ?? 100,\n fovLabel: source.fovLabel || \"\",\n fovLabelPosition: source.fovLabelPosition || \"center\",\n fovLabelSize: source.fovLabelSize || 14,\n fovLabelColor: source.fovLabelColor || \"#ffffff\",\n fovLabelBold: source.fovLabelBold || false,\n fovLabelBg: source.fovLabelBg || false,\n fovLabelBgColor: source.fovLabelBgColor || \"#000000\",\n fovLabelOffsetX: source.fovLabelOffsetX || 0,\n fovLabelOffsetY: source.fovLabelOffsetY || 0,\n fovAnimate: source.fovAnimate || false,\n fovAnimationType: source.fovAnimationType || \"sweep\",\n fovSweep: source.fovSweep || 120,\n fovSpeed: source.fovSpeed || 4\n\t\t};\n       if (source.isRack) {\n        const childNodes = Object.entries(NODE_DATA).filter(([id, n]) =>\n         id !== newId && n.assignedRack === sourceId\n        );\n        childNodes.forEach(([childId, childNode]) => {\n         const childBaseName = childNode.name + \" copy\";\n         let childNewName = childBaseName;\n         let c = 1;\n         while (Object.values(NODE_DATA).some(n => n.name === childNewName)) {\n          childNewName = `${childBaseName} ${c}`;\n          c++;\n         }\n         const childBaseId = childNewName.toLowerCase().replace(/[^a-z0-9]+/g, \"-\");\n         let childNewId = childBaseId;\n         c = 1;\n         while (NODE_DATA[childNewId]) {\n          childNewId = `${childBaseId}-${c}`;\n          c++;\n         }\n         NODE_DATA[childNewId] = {\n          ...JSON.parse(JSON.stringify(childNode)),\n          name: childNewName,\n          assignedRack: newId\n         };\n         if (savedPositions[childId]) {\n          savedPositions[childNewId] = { ...savedPositions[childId] };\n         }\n         if (savedSizes[childId]) {\n          savedSizes[childNewId] = savedSizes[childId];\n         }\n         if (savedStyles[childId]) {\n          savedStyles[childNewId] = JSON.parse(JSON.stringify(savedStyles[childId]));\n         }\n        });\n       }\n       if (currentView.mode === \"rack\" && currentView.rackId) {\n        NODE_DATA[newId].assignedRack = currentView.rackId;\n       }\n       const sourcePos = savedPositions[sourceId];\n       savedPositions[newId] = {\n        x: sourcePos.x + 100,\n        y: sourcePos.y + 100\n       };\n       if (savedSizes[sourceId]) {\n        savedSizes[newId] = savedSizes[sourceId];\n       }\n       if (savedStyles[sourceId]) {\n        savedStyles[newId] = JSON.parse(JSON.stringify(savedStyles[sourceId]));\n       }\n       forgeTheTopology();\n       claimTheImmortal(newId);\n      }\n      function alignSelectedNodes(direction) {\n       if (selectedNodes.size < 2) return;\n       pushUndo(\"align nodes\");\n       const nodes = Array.from(selectedNodes).map(id => ({\n        id,\n        pos: savedPositions[id]\n       }));\n       switch(direction) {\n        case \"left\":\n         const minX = Math.min(...nodes.map(n => n.pos.x));\n         nodes.forEach(n => savedPositions[n.id].x = minX);\n         break;\n        case \"right\":\n         const maxX = Math.max(...nodes.map(n => n.pos.x));\n         nodes.forEach(n => savedPositions[n.id].x = maxX);\n         break;\n        case \"top\":\n         const minY = Math.min(...nodes.map(n => n.pos.y));\n         nodes.forEach(n => savedPositions[n.id].y = minY);\n         break;\n        case \"bottom\":\n         const maxY = Math.max(...nodes.map(n => n.pos.y));\n         nodes.forEach(n => savedPositions[n.id].y = maxY);\n         break;\n        case \"center-h\":\n         const avgX = nodes.reduce((sum, n) => sum + n.pos.x, 0) / nodes.length;\n         nodes.forEach(n => savedPositions[n.id].x = avgX);\n         break;\n        case \"center-v\":\n         const avgY = nodes.reduce((sum, n) => sum + n.pos.y, 0) / nodes.length;\n         nodes.forEach(n => savedPositions[n.id].y = avgY);\n         break;\n       }\n       forgeTheTopology();\n      }\n      function distributeSelectedNodes(direction) {\n       if (selectedNodes.size < 3) return;\n       pushUndo(\"distribute nodes\");\n       const nodes = Array.from(selectedNodes).map(id => ({\n        id,\n        pos: savedPositions[id]\n       }));\n       if (direction === \"horizontal\") {\n        nodes.sort((a, b) => a.pos.x - b.pos.x);\n        const minX = nodes[0].pos.x;\n        const maxX = nodes[nodes.length - 1].pos.x;\n        const gap = (maxX - minX) / (nodes.length - 1);\n        nodes.forEach((n, i) => {\n         savedPositions[n.id].x = minX + (gap * i);\n        });\n       } else {\n        nodes.sort((a, b) => a.pos.y - b.pos.y);\n        const minY = nodes[0].pos.y;\n        const maxY = nodes[nodes.length - 1].pos.y;\n        const gap = (maxY - minY) / (nodes.length - 1);\n        nodes.forEach((n, i) => {\n         savedPositions[n.id].y = minY + (gap * i);\n        });\n       }\n       forgeTheTopology();\n      }\n      function snapToGrid(nodeId, gridSize = 50) {\n       if (!savedPositions[nodeId]) return;\n       pushUndo(\"snap to grid\");\n       const pos = savedPositions[nodeId];\n       pos.x = Math.round(pos.x / gridSize) * gridSize;\n       pos.y = Math.round(pos.y / gridSize) * gridSize;\n       forgeTheTopology();\n      }\n\t\tfunction searchNodes(query) {\n\t\t   if (!query) {\n\t\t\tcurrentSearchQuery = \"\";\n\t\t\tcurrentSearchResults = [];\n\t\t\tclearSearchHighlight();\n\t\t\treturn [];\n\t\t   }\n\t\t   currentSearchQuery = query;\n\t\t   query = query.toLowerCase();\n\t\t   const results = [];\n\t\t   Object.entries(NODE_DATA).forEach(([id, data]) => {\n\t\t\ttry {\n\t\t\t const nameMatch = data.name && data.name.toLowerCase().includes(query);\n\t\t\t const ipMatch = data.ip && data.ip.toLowerCase().includes(query);\n\t\t\t const roleMatch = data.role && data.role.toLowerCase().includes(query);\n\t\t\t const tagsMatch = data.tags && Array.isArray(data.tags) && data.tags.some(tag => tag && tag.toLowerCase().includes(query));\n\t\t\t const macMatch = data.mac && data.mac.toLowerCase().includes(query);\n\t\t\t const rackUnitMatch = data.rackUnit && String(data.rackUnit).toLowerCase().includes(query);\n\t\t\t if (nameMatch || ipMatch || roleMatch || tagsMatch || macMatch || rackUnitMatch) {\n\t\t\t  results.push(id);\n\t\t\t }\n\t\t\t} catch (e) {\n\t\t\t console.warn(\"Search error for node:\", id, e);\n\t\t\t}\n\t\t   });\n\t\t   currentSearchResults = results;\n\t\t   highlightSearchResults(results, true);\n\t\t   if (results.length > 0) {\n\t\t    focusOnNodes(results);\n\t\t   }\n\t\t   return results;\n\t\t}\n\t\tfunction highlightSearchResults(nodeIds, hasQuery = false) {\n\t\t   document.querySelectorAll(\".node-group\").forEach(node => {\n\t\t\tconst nodeId = node.dataset.nodeId;\n\t\t\tif (nodeIds.includes(nodeId)) {\n\t\t\t node.classList.add(\"search-highlight\");\n\t\t\t node.classList.remove(\"search-faded\");\n\t\t\t} else {\n\t\t\t node.classList.remove(\"search-highlight\");\n\t\t\t if (hasQuery) {\n\t\t\t  node.classList.add(\"search-faded\");\n\t\t\t } else {\n\t\t\t  node.classList.remove(\"search-faded\");\n\t\t\t }\n\t\t\t}\n\t\t   });\n\t\t   document.querySelectorAll(\".edge-group\").forEach(edge => {\n\t\t\tconst fromId = edge.dataset.from;\n\t\t\tconst toId = edge.dataset.to;\n\t\t\tif (hasQuery && !nodeIds.includes(fromId) && !nodeIds.includes(toId)) {\n\t\t\t edge.classList.add(\"search-faded\");\n\t\t\t} else {\n\t\t\t edge.classList.remove(\"search-faded\");\n\t\t\t}\n\t\t   });\n\t\t}\n\t\tfunction clearSearchHighlight() {\n   document.querySelectorAll(\".search-highlight\").forEach(node => {\n    node.classList.remove(\"search-highlight\");\n   });\n   document.querySelectorAll(\".search-faded\").forEach(el => {\n    el.classList.remove(\"search-faded\");\n   });\n}\n      function nudgeSelectedNodes(direction, distance) {\n        const nodesToNudge = selectedNodes.size > 0\n          ? Array.from(selectedNodes)\n          : (currentNodeId ? [currentNodeId] : []);\n        const rectsToNudge = selectedRects.size > 0\n          ? Array.from(selectedRects)\n          : (currentRectId ? [currentRectId] : []);\n        const textsToNudge = selectedTexts.size > 0\n          ? Array.from(selectedTexts)\n          : (currentTextId ? [currentTextId] : []);\n        if (nodesToNudge.length === 0 && rectsToNudge.length === 0 && textsToNudge.length === 0) return;\n        const unlockedNodes = nodesToNudge.filter(id => !NODE_DATA[id]?.locked);\n        const unlockedRects = rectsToNudge.filter(id => { const r = RECT_DATA.list.find(x => x.id === id); return r && !r.locked; });\n        const unlockedTexts = textsToNudge.filter(id => { const t = TEXT_DATA.list.find(x => x.id === id); return t && !t.locked; });\n        if (unlockedNodes.length === 0 && unlockedRects.length === 0 && unlockedTexts.length === 0) return;\n        pushUndo(\"nudge\");\n        const dx = direction === \"ArrowLeft\" ? -distance : direction === \"ArrowRight\" ? distance : 0;\n        const dy = direction === \"ArrowUp\" ? -distance : direction === \"ArrowDown\" ? distance : 0;\n        unlockedNodes.forEach(id => {\n          if (!savedPositions[id]) savedPositions[id] = { x: 0, y: 0 };\n          savedPositions[id].x += dx;\n          savedPositions[id].y += dy;\n        });\n        unlockedRects.forEach(id => {\n          const r = RECT_DATA.list.find(x => x.id === id);\n          if (r) { r.x += dx; r.y += dy; }\n        });\n        unlockedTexts.forEach(id => {\n          const t = TEXT_DATA.list.find(x => x.id === id);\n          if (t) { t.x += dx; t.y += dy; }\n        });\n        forgeTheTopology();\n      }\n      function cycleNodes(reverse = false) {\n        const nodeIds = Object.keys(NODE_DATA).filter(id => {\n          if (currentView.mode === \"rack\" && currentView.rackId) {\n            return NODE_DATA[id].assignedRack === currentView.rackId;\n          }\n          return !NODE_DATA[id].assignedRack;\n        });\n        if (nodeIds.length === 0) return;\n        let currentIndex = nodeIds.indexOf(currentNodeId);\n        if (reverse) {\n          currentIndex = currentIndex <= 0 ? nodeIds.length - 1 : currentIndex - 1;\n        } else {\n          currentIndex = currentIndex >= nodeIds.length - 1 ? 0 : currentIndex + 1;\n        }\n        const nextNodeId = nodeIds[currentIndex];\n        claimTheImmortal(nextNodeId);\n        selectedNodes.clear();\n        updateNodeSelection();\n      }\n      function focusOnSelected() {\n        let minX = Infinity, minY = Infinity;\n        let maxX = -Infinity, maxY = -Infinity;\n        let hasItems = false;\n        const nodesToFocus = selectedNodes.size > 0\n          ? Array.from(selectedNodes)\n          : (currentNodeId ? [currentNodeId] : []);\n        nodesToFocus.forEach(id => {\n          const pos = savedPositions[id];\n          if (pos) {\n            hasItems = true;\n            const size = savedSizes[id] || 50;\n            minX = Math.min(minX, pos.x - size/2);\n            minY = Math.min(minY, pos.y - size/2);\n            maxX = Math.max(maxX, pos.x + size/2);\n            maxY = Math.max(maxY, pos.y + size/2);\n          }\n        });\n        const rectsToFocus = selectedRects.size > 0 ? Array.from(selectedRects) : (currentRectId ? [currentRectId] : []);\n        rectsToFocus.forEach(id => {\n          const r = RECT_DATA.list.find(x => x.id === id);\n          if (r) {\n            hasItems = true;\n            minX = Math.min(minX, r.x);\n            minY = Math.min(minY, r.y);\n            maxX = Math.max(maxX, r.x + r.width);\n            maxY = Math.max(maxY, r.y + r.height);\n          }\n        });\n        const textsToFocus = selectedTexts.size > 0 ? Array.from(selectedTexts) : (currentTextId ? [currentTextId] : []);\n        textsToFocus.forEach(id => {\n          const t = TEXT_DATA.list.find(x => x.id === id);\n          if (t) {\n            hasItems = true;\n            minX = Math.min(minX, t.x - 50);\n            minY = Math.min(minY, t.y - 20);\n            maxX = Math.max(maxX, t.x + 50);\n            maxY = Math.max(maxY, t.y + 20);\n          }\n        });\n        const edgesToFocus = selectedEdges.size > 0 ? Array.from(selectedEdges) : (currentEdgeId ? [currentEdgeId] : []);\n        edgesToFocus.forEach(id => {\n          const e = EDGE_DATA.list.find(x => x.id === id);\n          if (e) {\n            const fromPos = savedPositions[e.from];\n            const toPos = savedPositions[e.to];\n            if (fromPos && toPos) {\n              hasItems = true;\n              minX = Math.min(minX, fromPos.x, toPos.x);\n              minY = Math.min(minY, fromPos.y, toPos.y);\n              maxX = Math.max(maxX, fromPos.x, toPos.x);\n              maxY = Math.max(maxY, fromPos.y, toPos.y);\n            }\n          }\n        });\n        if (!hasItems || !isFinite(minX)) return;\n        const padding = 100;\n        const centerX = (minX + maxX) / 2;\n        const centerY = (minY + maxY) / 2;\n        const width = maxX - minX + padding * 2;\n        const height = maxY - minY + padding * 2;\n        const zoomX = CANVAS_WIDTH / width;\n        const zoomY = CANVAS_HEIGHT / height;\n        const targetZoom = Math.min(zoomX, zoomY, 2);\n        canvasState.zoom = targetZoom;\n        canvasState.panX = centerX - (CANVAS_WIDTH / targetZoom) / 2;\n        canvasState.panY = centerY - (CANVAS_HEIGHT / targetZoom) / 2;\n\t\tupdateViewBox();\n      }\n      function toggleLockSelected() {\n        const nodesToToggle = selectedNodes.size > 0\n          ? Array.from(selectedNodes)\n          : (currentNodeId ? [currentNodeId] : []);\n        const rectsToToggle = selectedRects.size > 0\n          ? Array.from(selectedRects)\n          : (currentRectId ? [currentRectId] : []);\n        const textsToToggle = selectedTexts.size > 0\n          ? Array.from(selectedTexts)\n          : (currentTextId ? [currentTextId] : []);\n        if (nodesToToggle.length === 0 && rectsToToggle.length === 0 && textsToToggle.length === 0) return;\n        pushUndo(\"toggle lock\");\n        let hasUnlocked = nodesToToggle.some(id => !NODE_DATA[id]?.locked);\n        hasUnlocked = hasUnlocked || rectsToToggle.some(id => { const r = RECT_DATA.list.find(x => x.id === id); return r && !r.locked; });\n        hasUnlocked = hasUnlocked || textsToToggle.some(id => { const t = TEXT_DATA.list.find(x => x.id === id); return t && !t.locked; });\n        nodesToToggle.forEach(id => {\n          if (NODE_DATA[id]) NODE_DATA[id].locked = hasUnlocked;\n        });\n        rectsToToggle.forEach(id => {\n          const r = RECT_DATA.list.find(x => x.id === id);\n          if (r) r.locked = hasUnlocked;\n        });\n        textsToToggle.forEach(id => {\n          const t = TEXT_DATA.list.find(x => x.id === id);\n          if (t) t.locked = hasUnlocked;\n        });\n        forgeTheTopology();\n      }\n      function toggleGroupSelected() {\n      const nodesToGroup = Array.from(selectedNodes);\n      const rectsToGroup = Array.from(selectedRects);\n      const textsToGroup = Array.from(selectedTexts);\n      const edgesToGroup = Array.from(selectedEdges);\n      const totalItems = nodesToGroup.length + rectsToGroup.length + textsToGroup.length + edgesToGroup.length;\n      if (totalItems < 2) return;\n      pushUndo(\"toggle group\");\n      const allGroupIds = [];\n      nodesToGroup.forEach(id => { if (NODE_DATA[id]?.groupId) allGroupIds.push(NODE_DATA[id].groupId); });\n      rectsToGroup.forEach(id => { const r = RECT_DATA.list.find(x => x.id === id); if (r?.groupId) allGroupIds.push(r.groupId); });\n      textsToGroup.forEach(id => { const t = TEXT_DATA.list.find(x => x.id === id); if (t?.groupId) allGroupIds.push(t.groupId); });\n      edgesToGroup.forEach(id => { const e = EDGE_DATA.list.find(x => x.id === id); if (e?.groupId) allGroupIds.push(e.groupId); });\n      const uniqueGroups = [...new Set(allGroupIds)];\n      if (uniqueGroups.length === 1 && allGroupIds.length === totalItems) {\n      nodesToGroup.forEach(id => { if (NODE_DATA[id]) NODE_DATA[id].groupId = null; });\n      rectsToGroup.forEach(id => { const r = RECT_DATA.list.find(x => x.id === id); if (r) r.groupId = null; });\n      textsToGroup.forEach(id => { const t = TEXT_DATA.list.find(x => x.id === id); if (t) t.groupId = null; });\n      edgesToGroup.forEach(id => { const e = EDGE_DATA.list.find(x => x.id === id); if (e) e.groupId = null; });\n      } else {\n      const newGroupId = \"group-\" + Date.now();\n      nodesToGroup.forEach(id => { if (NODE_DATA[id]) NODE_DATA[id].groupId = newGroupId; });\n      rectsToGroup.forEach(id => { const r = RECT_DATA.list.find(x => x.id === id); if (r) r.groupId = newGroupId; });\n      textsToGroup.forEach(id => { const t = TEXT_DATA.list.find(x => x.id === id); if (t) t.groupId = newGroupId; });\n      edgesToGroup.forEach(id => { const e = EDGE_DATA.list.find(x => x.id === id); if (e) e.groupId = newGroupId; });\n      }\n      forgeTheTopology();\n      }\n      function handleKeyDown(event) {\n       if (event.metaKey && !event.ctrlKey) event.ctrlKey = true;\n       if (event.target.tagName === \"INPUT\" || event.target.tagName === \"TEXTAREA\" || event.target.isContentEditable) {\n        return;\n       }\n       if ([\"ArrowUp\", \"ArrowDown\", \"ArrowLeft\", \"ArrowRight\"].includes(event.key)) {\n        event.preventDefault();\n        const distance = event.shiftKey ? 10 : 1;\n        nudgeSelectedNodes(event.key, distance);\n       }\n       if (event.key === \"Tab\") {\n        event.preventDefault();\n        cycleNodes(event.shiftKey);\n       }\n       if (event.key === \"f\" || event.key === \"F\") {\n        event.preventDefault();\n        focusOnSelected();\n       }\n       if (event.key === \"l\" || event.key === \"L\") {\n        event.preventDefault();\n        toggleLockSelected();\n       }\n       if (event.key === \"g\" || event.key === \"G\") {\n        event.preventDefault();\n        toggleGroupSelected();\n       }\n       if (event.ctrlKey && event.key === \"z\") {\n        event.preventDefault();\n        undo();\n       }\n       if ((event.ctrlKey && event.key === \"y\") || (event.ctrlKey && event.shiftKey && event.key === \"z\")) {\n        event.preventDefault();\n        redo();\n       }\n if (event.ctrlKey && event.key === \"c\") {\n        event.preventDefault();\n        clipboard = null;\n        if (currentNodeId && NODE_DATA[currentNodeId]) {\n         clipboard = {\n          type: \"node\",\n          data: JSON.parse(JSON.stringify(NODE_DATA[currentNodeId])),\n          size: savedSizes[currentNodeId],\n          style: savedStyles[currentNodeId] ? JSON.parse(JSON.stringify(savedStyles[currentNodeId])) : null\n         };\n        } else if (currentEdgeId) {\n         const edge = EDGE_DATA.list.find(e => e.id === currentEdgeId);\n         if (edge) clipboard = { type: \"edge\", data: JSON.parse(JSON.stringify(edge)) };\n        } else if (currentRectId) {\n         const rect = RECT_DATA.list.find(r => r.id === currentRectId);\n         if (rect) clipboard = { type: \"rect\", data: JSON.parse(JSON.stringify(rect)) };\n        } else if (currentTextId) {\n         const text = TEXT_DATA.list.find(t => t.id === currentTextId);\n         if (text) clipboard = { type: \"text\", data: JSON.parse(JSON.stringify(text)) };\n        }\n       }\n       if (event.ctrlKey && event.key === \"v\") {\n        event.preventDefault();\n        if (!clipboard) return;\n        const viewWidth = CANVAS_WIDTH / canvasState.zoom;\n        const viewHeight = CANVAS_HEIGHT / canvasState.zoom;\n        const centerX = canvasState.panX + (viewWidth / 2);\n        const centerY = canvasState.panY + (viewHeight / 2);\n        if (clipboard.type === \"node\") {\n         const data = clipboard.data;\n         let newName = data.name + \" copy\";\n         let counter = 1;\n         while (Object.values(NODE_DATA).some(n => n.name === newName)) {\n          newName = data.name + \" copy \" + counter;\n          counter++;\n         }\n         let newId = newName.toLowerCase().replace(/[^a-z0-9]+/g, \"-\");\n         counter = 1;\n         while (NODE_DATA[newId]) {\n          newId = newName.toLowerCase().replace(/[^a-z0-9]+/g, \"-\") + \"-\" + counter;\n          counter++;\n         }\n         pushUndo(\"paste node\");\n         NODE_DATA[newId] = { ...data, name: newName };\n         savedPositions[newId] = { x: centerX, y: centerY };\n         if (clipboard.size) savedSizes[newId] = clipboard.size;\n         if (clipboard.style) savedStyles[newId] = JSON.parse(JSON.stringify(clipboard.style));\n         forgeTheTopology();\n         claimTheImmortal(newId);\n        } else if (clipboard.type === \"edge\") {\n         pushUndo(\"paste edge\");\n         const newEdge = { ...clipboard.data, id: \"edge-\" + Date.now() };\n         EDGE_DATA.list.push(newEdge);\n         forgeTheTopology();\n         selectTheConnection(newEdge.id);\n        } else if (clipboard.type === \"rect\") {\n         pushUndo(\"paste rect\");\n         const newRect = { ...clipboard.data, id: \"rect-\" + Date.now(), x: centerX - (clipboard.data.width || 100) / 2, y: centerY - (clipboard.data.height || 100) / 2 };\n         RECT_DATA.list.push(newRect);\n         forgeTheTopology();\n         selectTheRect(newRect.id);\n        } else if (clipboard.type === \"text\") {\n         pushUndo(\"paste text\");\n         const newText = { ...clipboard.data, id: \"text-\" + Date.now(), x: centerX, y: centerY };\n         TEXT_DATA.list.push(newText);\n         forgeTheTopology();\n         showTextPanel(newText.id);\n        }\n       }\n       if (event.key === \"Delete\") {\n        event.preventDefault();\n        const totalSelected = selectedNodes.size + selectedEdges.size + selectedRects.size + selectedTexts.size;\n        if (totalSelected > 0) {\n         deleteSelected();\n        } else if (currentNodeId && NODE_DATA[currentNodeId]) {\n         challengeTheImmortal(`Delete node \"${NODE_DATA[currentNodeId].name}\"?`, () => {\n          pushUndo(\"delete node\");\n          delete NODE_DATA[currentNodeId];\n          delete savedPositions[currentNodeId];\n          delete savedSizes[currentNodeId];\n          delete savedStyles[currentNodeId];\n          EDGE_DATA.list = EDGE_DATA.list.filter(e => e.from !== currentNodeId && e.to !== currentNodeId);\n          currentNodeId = null;\n          forgeTheTopology();\n          document.getElementById(\"node-panel\").style.display = \"none\";\n         });\n        } else if (currentEdgeId) {\n         challengeTheImmortal(\"Delete this line?\", () => {\n          pushUndo(\"delete edge\");\n          EDGE_DATA.list = EDGE_DATA.list.filter(e => e.id !== currentEdgeId);\n          currentEdgeId = null;\n          forgeTheTopology();\n          document.getElementById(\"edge-panel\").style.display = \"none\";\n         });\n        } else if (currentRectId) {\n         challengeTheImmortal(\"Delete this zone?\", () => {\n          pushUndo(\"delete rect\");\n          RECT_DATA.list = RECT_DATA.list.filter(r => r.id !== currentRectId);\n          currentRectId = null;\n          forgeTheTopology();\n          document.getElementById(\"rect-panel\").style.display = \"none\";\n         });\n        } else if (currentTextId) {\n         const textItem = TEXT_DATA.list.find(t => t.id === currentTextId);\n         if (textItem) {\n          challengeTheImmortal(`Delete text \"${(textItem.content || '').substring(0, 30)}...\"?`, () => {\n           pushUndo(\"delete text\");\n           TEXT_DATA.list = TEXT_DATA.list.filter(t => t.id !== currentTextId);\n           currentTextId = null;\n           forgeTheTopology();\n           document.getElementById(\"text-panel\").style.display = \"none\";\n          });\n         }\n        }\n       }\n       if (event.ctrlKey && event.key === \"a\") {\n        event.preventDefault();\n        Object.keys(NODE_DATA).forEach(id => selectedNodes.add(id));\n        EDGE_DATA.list.forEach(e => selectedEdges.add(e.id));\n        RECT_DATA.list.forEach(r => selectedRects.add(r.id));\n        TEXT_DATA.list.forEach(t => selectedTexts.add(t.id));\n        updateAllSelections();\n       }\n       if (event.key === \"Escape\") {\n        clearSelection();\n        clearSearchHighlight();\n       }\n       if (event.ctrlKey && event.key === \"d\") {\n        event.preventDefault();\n        if (currentNodeId && NODE_DATA[currentNodeId]) {\n         cloneNode(currentNodeId);\n        } else if (currentEdgeId) {\n         const edge = EDGE_DATA.list.find(e => e.id === currentEdgeId);\n         if (edge) {\n          pushUndo(\"clone edge\");\n          const newEdge = { ...JSON.parse(JSON.stringify(edge)), id: \"edge-\" + Date.now() };\n          EDGE_DATA.list.push(newEdge);\n          forgeTheTopology();\n          selectTheConnection(newEdge.id);\n         }\n        } else if (currentRectId) {\n         const rect = RECT_DATA.list.find(r => r.id === currentRectId);\n         if (rect) {\n          pushUndo(\"clone rect\");\n          const newRect = { ...JSON.parse(JSON.stringify(rect)), id: \"rect-\" + Date.now(), x: rect.x + 50, y: rect.y + 50 };\n          RECT_DATA.list.push(newRect);\n          forgeTheTopology();\n          selectTheRect(newRect.id);\n         }\n        } else if (currentTextId) {\n         const text = TEXT_DATA.list.find(t => t.id === currentTextId);\n         if (text) {\n          pushUndo(\"clone text\");\n          const newText = { ...JSON.parse(JSON.stringify(text)), id: \"text-\" + Date.now(), x: text.x + 50, y: text.y + 50 };\n          TEXT_DATA.list.push(newText);\n          forgeTheTopology();\n          showTextPanel(newText.id);\n         }\n        }\n       }\n      }\n      function saveRollbackVersion(description = \"Auto-save\") {\n        const version = {\n          timestamp: Date.now(),\n          description,\n          data: captureTheQuickening()\n        };\n        try {\n          const stored = localStorage.getItem(ROLLBACK_STORAGE_KEY);\n          rollbackVersions = stored ? JSON.parse(stored) : [];\n        } catch (e) {\n          rollbackVersions = [];\n        }\n        rollbackVersions.unshift(version);\n        if (rollbackVersions.length > MAX_ROLLBACK_VERSIONS) {\n          rollbackVersions = rollbackVersions.slice(0, MAX_ROLLBACK_VERSIONS);\n        }\n        try {\n          localStorage.setItem(ROLLBACK_STORAGE_KEY, JSON.stringify(rollbackVersions));\n        } catch (e) {\n          console.warn(\"Failed to save rollback version:\", e);\n        }\n      }\n      function loadRollbackVersions() {\n        try {\n          const stored = localStorage.getItem(ROLLBACK_STORAGE_KEY);\n          rollbackVersions = stored ? JSON.parse(stored) : [];\n          displayRollbackVersions();\n        } catch (e) {\n          console.warn(\"Failed to load rollback versions:\", e);\n          rollbackVersions = [];\n        }\n      }\n      function displayRollbackVersions() {\n        const listEl = document.getElementById(\"rollback-list\");\n        if (!listEl) return;\n        if (rollbackVersions.length === 0) {\n          listEl.innerHTML = '<div style=\"text-align: center; padding: 40px; color: var(--text-soft);\">No version history yet. Versions are saved automatically when you save the file.</div>';\n          return;\n        }\n        listEl.innerHTML = rollbackVersions.map((version, index) => {\n          const date = new Date(version.timestamp);\n          const timeStr = date.toLocaleString();\n          const nodeCount = Object.keys(version.data.nodeData || {}).length;\n          const edgeCount = (version.data.edgeData?.list || []).length;\n          return `\n            <div class=\"version-item\" onclick=\"restoreRollbackVersion(${index})\">\n              <div class=\"version-info\">\n                <div class=\"timestamp\">${escapeHtml(timeStr)}</div>\n                <div class=\"details\">${escapeHtml(version.description)} • ${nodeCount} nodes • ${edgeCount} connections</div>\n              </div>\n              <div class=\"version-actions\">\n                <button class=\"btn-cancel\" onclick=\"event.stopPropagation(); deleteRollbackVersion(${index})\" title=\"Delete this version\">🗑️</button>\n              </div>\n            </div>\n          `;\n        }).join('');\n      }\n      function restoreRollbackVersion(index) {\n        if (!confirm(`Restore version from ${new Date(rollbackVersions[index].timestamp).toLocaleString()}?\\n\\nYour current work will be lost unless you save first.`)) {\n          return;\n        }\n        const version = rollbackVersions[index];\n        const data = version.data;\n        NODE_DATA = data.nodeData || {};\n        EDGE_DATA = data.edgeData || { list: [] };\n        RECT_DATA = data.rectData || { list: [] };\n        TEXT_DATA = data.textData || { list: [] };\n        EDGE_LEGEND = data.edgeLegend || {};\n        savedPositions = data.nodePositions || {};\n        savedSizes = data.nodeSizes || {};\n        savedStyles = data.nodeStyles || {};\n        PAGE_STATE = data.page || PAGE_STATE;\n        if (data.canvas) {\n          canvasState.zoom = data.canvas.zoom;\n          canvasState.panX = data.canvas.panX;\n          canvasState.panY = data.canvas.panY;\n        }\n        wieldThePower();\n        forgeTheTopology();\n        document.getElementById(\"rollback-modal\").classList.remove(\"active\");\n        logAuditEvent(\"rollback\", `Restored version from ${new Date(version.timestamp).toLocaleString()}`);\n      }\n      function deleteRollbackVersion(index) {\n        if (!confirm(\"Delete this version from history?\")) return;\n        rollbackVersions.splice(index, 1);\n        try {\n          localStorage.setItem(ROLLBACK_STORAGE_KEY, JSON.stringify(rollbackVersions));\n        } catch (e) {\n          console.warn(\"Failed to delete version:\", e);\n        }\n        displayRollbackVersions();\n      }\n      function clearRollbackHistory() {\n        if (!confirm(\"Clear all version history?\\n\\nThis cannot be undone.\")) return;\n        rollbackVersions = [];\n        localStorage.removeItem(ROLLBACK_STORAGE_KEY);\n        displayRollbackVersions();\n      }\n      function createManualSnapshot() {\n        const description = prompt(\"Enter a description for this snapshot:\", \"Manual snapshot\");\n        if (!description) return;\n        saveRollbackVersion(description);\n        displayRollbackVersions();\n      }\n      function switchTab(index) {\n        if (index === currentTabIndex) return;\n        const currentTab = documentTabs[currentTabIndex];\n        currentTab.nodes = JSON.parse(JSON.stringify(NODE_DATA));\n        currentTab.edges = JSON.parse(JSON.stringify(EDGE_DATA));\n        currentTab.positions = JSON.parse(JSON.stringify(savedPositions));\n        currentTab.sizes = JSON.parse(JSON.stringify(savedSizes));\n        currentTab.styles = JSON.parse(JSON.stringify(savedStyles));\n        currentTab.legend = JSON.parse(JSON.stringify(EDGE_LEGEND));\n        currentTab.rects = JSON.parse(JSON.stringify(RECT_DATA));\n        currentTab.texts = JSON.parse(JSON.stringify(TEXT_DATA));\n        currentTab.pageState = JSON.parse(JSON.stringify(PAGE_STATE));\n        currentTabIndex = index;\n        const newTab = documentTabs[index];\n        NODE_DATA = JSON.parse(JSON.stringify(newTab.nodes));\n        EDGE_DATA = JSON.parse(JSON.stringify(newTab.edges));\n        savedPositions = JSON.parse(JSON.stringify(newTab.positions));\n        savedSizes = JSON.parse(JSON.stringify(newTab.sizes));\n        savedStyles = JSON.parse(JSON.stringify(newTab.styles));\n        EDGE_LEGEND = JSON.parse(JSON.stringify(newTab.legend));\n        RECT_DATA = JSON.parse(JSON.stringify(newTab.rects));\n        TEXT_DATA = JSON.parse(JSON.stringify(newTab.texts));\n        PAGE_STATE = Object.assign({}, DEFAULT_PAGE_STATE, newTab.pageState || {});\n        wieldThePower();\n        document.title = PAGE_STATE.title || newTab.name;\n        document.getElementById(\"page-title\").textContent = PAGE_STATE.title || newTab.name;\n        forgeTheTopology();\n        currentNodeId = null;\n        currentEdgeId = null;\n        currentTextId = null;\n        document.getElementById(\"node-panel\").style.display = \"none\";\n        document.getElementById(\"edge-panel\").style.display = \"none\";\n        document.getElementById(\"text-panel\").style.display = \"none\";\n        displayTabs();\n        logAuditEvent(\"tab\", `Switched to tab: ${newTab.name}`);\n      }\n      function createNewTab() {\n        const nameInput = document.getElementById(\"new-tab-name\");\n        const name = nameInput.value.trim();\n        if (!name) {\n          alert(\"Please enter a tab name\");\n          return;\n        }\n        const newTab = {\n          id: `tab-${Date.now()}`,\n          name,\n          nodes: {},\n          edges: { list: [] },\n          positions: {},\n          sizes: {},\n          styles: {},\n          legend: {},\n          rects: { list: [] },\n          texts: { list: [] },\n      pageState: null\n        };\n        documentTabs.push(newTab);\n        nameInput.value = \"\";\n        displayTabs();\n        logAuditEvent(\"tab\", `Created new tab: ${name}`);\n      }\n      function renameTab(index) {\n        const tab = documentTabs[index];\n        const newName = prompt(\"Enter new name:\", tab.name);\n        if (!newName || newName === tab.name) return;\n        tab.name = newName;\n        displayTabs();\n        logAuditEvent(\"tab\", `Renamed tab to: ${newName}`);\n      }\n      function deleteTab(index) {\n        if (documentTabs.length === 1) {\n          alert(\"Cannot delete the last tab\");\n          return;\n        }\n        if (!confirm(`Delete tab \"${documentTabs[index].name}\"?`)) return;\n        const wasCurrentTab = (index === currentTabIndex);\n        documentTabs.splice(index, 1);\n        if (currentTabIndex >= documentTabs.length) {\n          currentTabIndex = documentTabs.length - 1;\n        } else if (index < currentTabIndex) {\n          currentTabIndex--;\n        }\n        if (wasCurrentTab) {\n          const newTab = documentTabs[currentTabIndex];\n          NODE_DATA = JSON.parse(JSON.stringify(newTab.nodes));\n          EDGE_DATA = JSON.parse(JSON.stringify(newTab.edges));\n          savedPositions = JSON.parse(JSON.stringify(newTab.positions));\n          savedSizes = JSON.parse(JSON.stringify(newTab.sizes));\n          savedStyles = JSON.parse(JSON.stringify(newTab.styles));\n          EDGE_LEGEND = JSON.parse(JSON.stringify(newTab.legend));\n          RECT_DATA = JSON.parse(JSON.stringify(newTab.rects));\n          TEXT_DATA = JSON.parse(JSON.stringify(newTab.texts));\n          PAGE_STATE = Object.assign({}, DEFAULT_PAGE_STATE, newTab.pageState || {});\n          wieldThePower();\n          forgeTheTopology();\n          currentNodeId = null;\n          currentEdgeId = null;\n          currentTextId = null;\n          document.getElementById(\"node-panel\").style.display = \"none\";\n          document.getElementById(\"edge-panel\").style.display = \"none\";\n          document.getElementById(\"text-panel\").style.display = \"none\";\n        }\n        displayTabs();\n        logAuditEvent(\"tab\", `Deleted tab`);\n      }\n      function saveCurrentTheme() {\n        const name = prompt(\"Enter a name for this theme:\", \"My Theme \" + (savedStyleSets.length + 1));\n        if (!name || !name.trim()) return;\n        const existingIndex = savedStyleSets.findIndex(s => s.name.toLowerCase() === name.trim().toLowerCase());\n        if (existingIndex !== -1) {\n          if (!confirm(\"A theme named \\\"\" + name + \"\\\" already exists. Replace it?\")) return;\n          savedStyleSets.splice(existingIndex, 1);\n        }\n        const styleSet = {\n          id: \"mytheme-\" + Date.now(),\n          name: name.trim(),\n          styles: JSON.parse(JSON.stringify(PAGE_STATE))\n        };\n        delete styleSet.styles.title;\n        delete styleSet.styles.viewOnly;\n        savedStyleSets.push(styleSet);\n        rebuildThemeDropdown();\n        document.getElementById(\"theme-preset\").value = styleSet.id;\n        updateDeleteButton();\n        logAuditEvent(\"style\", \"Saved theme: \" + name);\n      }\n      function deleteCurrentTheme() {\n        const select = document.getElementById(\"theme-preset\");\n        const val = select.value;\n        if (!val.startsWith(\"mytheme-\")) return;\n        const index = savedStyleSets.findIndex(s => s.id === val);\n        if (index === -1) return;\n        if (!confirm(\"Delete theme \\\"\" + savedStyleSets[index].name + \"\\\"?\")) return;\n        savedStyleSets.splice(index, 1);\n        rebuildThemeDropdown();\n        select.value = \"defaulted\";\n        updateDeleteButton();\n        logAuditEvent(\"style\", \"Deleted theme\");\n      }\n      function rebuildThemeDropdown() {\n        const group = document.getElementById(\"my-themes-group\");\n        if (!group) return;\n        const select = document.getElementById(\"theme-preset\");\n        const currentValue = select ? select.value : \"\";\n        group.innerHTML = \"\";\n        savedStyleSets.forEach(function(s) {\n          const opt = document.createElement(\"option\");\n          opt.value = s.id;\n          opt.textContent = s.name;\n          group.appendChild(opt);\n        });\n        if (select && currentValue) {\n          select.value = currentValue;\n        }\n      }\n      function updateDeleteButton() {\n        const select = document.getElementById(\"theme-preset\");\n        const btn = document.getElementById(\"delete-theme-btn\");\n        if (!btn) return;\n\t\tbtn.style.display = \"block\";\n\t\tbtn.disabled = !select.value.startsWith(\"mytheme-\");\n      }\n      function displayTabs() {\n        const listEl = document.getElementById(\"tabs-list\");\n        if (!listEl) return;\n        listEl.innerHTML = documentTabs.map((tab, index) => {\n          const nodeCount = Object.keys(tab.nodes).length;\n          const edgeCount = tab.edges.list.length;\n          const isActive = index === currentTabIndex;\n          return `\n            <div class=\"tab-item ${isActive ? 'active' : ''}\" onclick=\"switchTab(${index})\">\n              <div class=\"tab-name\">${escapeHtml(tab.name)}</div>\n              <div class=\"tab-stats\">${nodeCount} nodes • ${edgeCount} connections</div>\n              <div class=\"tab-actions\">\n                <button class=\"btn-cancel\" onclick=\"event.stopPropagation(); renameTab(${index})\" title=\"Rename tab\">✏️</button>\n                ${documentTabs.length > 1 ? '<button class=\"btn-cancel\" onclick=\"event.stopPropagation(); deleteTab(' + index + ')\" title=\"Delete tab\">🗑️</button>' : ''}\n              </div>\n            </div>\n          `;\n        }).join('');\n      }\n      function logAuditEvent(type, description, details = {}) {\n        const event = {\n          timestamp: Date.now(),\n          type,\n          description,\n          details,\n          tab: documentTabs[currentTabIndex]?.name || \"Main\"\n        };\n        auditLog.unshift(event);\n        if (auditLog.length > MAX_AUDIT_ENTRIES) {\n          auditLog = auditLog.slice(0, MAX_AUDIT_ENTRIES);\n        }\n        try {\n          localStorage.setItem(AUDIT_STORAGE_KEY, JSON.stringify(auditLog));\n        } catch (e) {\n          console.warn(\"Failed to save audit log:\", e);\n        }\n      }\n      function loadAuditLog() {\n        let embeddedLog = [];\n        let localLog = [];\n        try {\n          const stateScript = document.getElementById(\"topology-state\");\n          if (stateScript) {\n            const stateData = JSON.parse(stateScript.textContent);\n            if (stateData.auditLog && Array.isArray(stateData.auditLog)) {\n              embeddedLog = stateData.auditLog;\n            }\n          }\n        } catch (e) {\n          console.warn(\"Failed to load embedded audit log:\", e);\n        }\n        try {\n          const stored = localStorage.getItem(AUDIT_STORAGE_KEY);\n          if (stored) {\n            localLog = JSON.parse(stored);\n          }\n        } catch (e) {\n          console.warn(\"Failed to load localStorage audit log:\", e);\n        }\n        const merged = [...embeddedLog, ...localLog];\n        const seen = new Set();\n        auditLog = merged.filter(entry => {\n          const key = entry.timestamp + entry.description;\n          if (seen.has(key)) return false;\n          seen.add(key);\n          return true;\n        }).sort((a, b) => b.timestamp - a.timestamp);\n        if (auditLog.length > MAX_AUDIT_ENTRIES) {\n          auditLog = auditLog.slice(0, MAX_AUDIT_ENTRIES);\n        }\n        try {\n          localStorage.setItem(AUDIT_STORAGE_KEY, JSON.stringify(auditLog));\n        } catch (e) {\n          console.warn(\"Failed to sync audit log:\", e);\n        }\n      }\n      function displayAuditLog(filter = \"all\") {\n        const listEl = document.getElementById(\"audit-log-list\");\n        if (!listEl) return;\n        const filtered = filter === \"all\" ? auditLog : auditLog.filter(e => e.type === filter);\n        if (filtered.length === 0) {\n          listEl.innerHTML = '<div style=\"text-align: center; padding: 40px; color: var(--text-soft);\">No audit entries yet</div>';\n          return;\n        }\n        listEl.innerHTML = filtered.map(event => {\n          const date = new Date(event.timestamp);\n          const timeStr = date.toLocaleString();\n          return `\n            <div class=\"audit-entry ${escapeHtml(event.type)}\">\n              <div class=\"time\">[${escapeHtml(timeStr)}] ${escapeHtml(event.tab)}</div>\n              <div class=\"action\">[${escapeHtml(event.type.toUpperCase())}] ${escapeHtml(event.description)}</div>\n            </div>\n          `;\n        }).join('');\n      }\n      function clearAuditLog() {\n        if (!confirm(\"Clear all audit log entries?\\n\\nThis cannot be undone.\")) return;\n        auditLog = [];\n        localStorage.removeItem(AUDIT_STORAGE_KEY);\n        displayAuditLog();\n      }\n      function exportAuditLog() {\n        if (auditLog.length === 0) {\n          alert(\"No audit entries to export\");\n          return;\n        }\n        const csv = [\n          [\"Timestamp\", \"Tab\", \"Type\", \"Description\"],\n          ...auditLog.map(e => [\n            new Date(e.timestamp).toISOString(),\n            e.tab,\n            e.type,\n            e.description\n          ])\n        ].map(row => row.map(cell => `\"${cell}\"`).join(\",\")).join(\"\\n\");\n        const blob = new Blob([csv], { type: \"text/csv\" });\n        const url = URL.createObjectURL(blob);\n        const a = document.createElement(\"a\");\n        a.href = url;\n        a.download = `audit-log-${Date.now()}.csv`;\n        document.body.appendChild(a);\n        a.click();\n        document.body.removeChild(a);\n        URL.revokeObjectURL(url);\n      }\n      let currentSecretName = null;\n      function createNewSecret() {\n        const nameInput = document.getElementById(\"new-secret-name\");\n        const name = nameInput.value.trim();\n        if (!name) {\n          alert(\"Please enter a note name\");\n          return;\n        }\n        if (encryptedSections[name]) {\n          alert(\"A note with this name already exists\");\n          return;\n        }\n        currentSecretName = name;\n        encryptedSections[name] = { encrypted: false, data: \"\" };\n        nameInput.value = \"\";\n        document.getElementById(\"secret-editor-title\").textContent = `New note: ${name}`;\n        document.getElementById(\"secret-editor-content\").value = \"\";\n        document.getElementById(\"secrets-modal\").classList.remove(\"active\");\n        document.getElementById(\"secret-editor-modal\").classList.add(\"active\");\n        displaySecrets();\n      }\n      function editSecret(name) {\n        currentSecretName = name;\n        const section = encryptedSections[name];\n        if (section.encrypted) {\n          const password = prompt(`Enter password to decrypt \"${name}\":`);\n          if (!password) return;\n          try {\n            decryptData(section.data, password).then(decrypted => {\n              document.getElementById(\"secret-editor-title\").textContent = `Edit note: ${name}`;\n              document.getElementById(\"secret-editor-content\").value = decrypted;\n              document.getElementById(\"secrets-modal\").classList.remove(\"active\");\n              document.getElementById(\"secret-editor-modal\").classList.add(\"active\");\n            }).catch(e => {\n              alert(\"Failed to decrypt. Wrong password?\");\n            });\n          } catch (e) {\n            alert(\"Failed to decrypt. Wrong password?\");\n          }\n        } else {\n          document.getElementById(\"secret-editor-title\").textContent = `Edit note: ${name}`;\n          document.getElementById(\"secret-editor-content\").value = section.data;\n          document.getElementById(\"secrets-modal\").classList.remove(\"active\");\n          document.getElementById(\"secret-editor-modal\").classList.add(\"active\");\n        }\n      }\n      async function saveSecret() {\n        if (!currentSecretName) return;\n        const content = document.getElementById(\"secret-editor-content\").value;\n        const autoEncrypt = document.getElementById(\"secret-auto-encrypt\").checked;\n        if (autoEncrypt) {\n          const password = prompt(`Enter password to encrypt \"${currentSecretName}\":`);\n          if (!password) return;\n          const confirmPassword = prompt(\"Confirm password:\");\n          if (password !== confirmPassword) {\n            alert(\"Passwords do not match\");\n            return;\n          }\n          try {\n            const encrypted = await encryptData(content, password);\n            encryptedSections[currentSecretName] = { encrypted: true, data: encrypted };\n          } catch (e) {\n            alert(\"Encryption failed: \" + e.message);\n            return;\n          }\n        } else {\n          encryptedSections[currentSecretName] = { encrypted: false, data: content };\n        }\n        closeSecretEditor();\n        displaySecrets();\n        logAuditEvent(\"secret\", `Saved note: ${currentSecretName}`);\n      }\n      function closeSecretEditor() {\n        document.getElementById(\"secret-editor-modal\").classList.remove(\"active\");\n        document.getElementById(\"secrets-modal\").classList.add(\"active\");\n        currentSecretName = null;\n      }\n      function deleteSecret(name) {\n        if (!confirm(`Delete note \"${name}\"?`)) return;\n        delete encryptedSections[name];\n        displaySecrets();\n        logAuditEvent(\"secret\", `Deleted note: ${name}`);\n      }\n      function displaySecrets() {\n        const listEl = document.getElementById(\"secrets-list\");\n        if (!listEl) return;\n        const secrets = Object.keys(encryptedSections);\n        if (secrets.length === 0) {\n          listEl.innerHTML = '<div style=\"text-align: center; padding: 40px; color: var(--text-soft);\">No notes yet</div>';\n          return;\n        }\n        listEl.innerHTML = secrets.map(name => {\n          const section = encryptedSections[name];\n          const status = section.encrypted ? \"🔒 Encrypted\" : \"🔓 Plaintext\";\n          return `\n            <div class=\"secret-item\">\n              <div class=\"secret-name\">${escapeHtml(name)}</div>\n              <div class=\"secret-status\">${status}</div>\n              <div class=\"tab-actions\">\n                <button class=\"btn-cancel\" onclick=\"editSecret('${escapeHtml(name)}')\" title=\"Edit note\">✏️</button>\n                <button class=\"btn-cancel\" onclick=\"deleteSecret('${escapeHtml(name)}')\" title=\"Delete note\">🗑️</button>\n              </div>\n            </div>\n          `;\n        }).join('');\n      }\n      let rafId = null;\n      let lastRender = 0;\n      const RENDER_THROTTLE = 16;\n      function setupDragToCreate() {\n        const addNodeBtn = document.getElementById(\"add-node-btn\");\n        const addRackBtn = document.getElementById(\"add-rack-btn\");\n        const canvas = document.getElementById(\"map\");\n        if (!addNodeBtn || !addRackBtn || !canvas) return;\n        let dragType = null;\n        [addNodeBtn, addRackBtn].forEach(btn => {\n          btn.setAttribute(\"draggable\", \"true\");\n          btn.addEventListener(\"dragstart\", e => {\n            dragType = btn.id === \"add-node-btn\" ? \"node\" : \"rack\";\n            e.dataTransfer.effectAllowed = \"copy\";\n            e.dataTransfer.setData(\"text/plain\", dragType);\n          });\n        });\n        canvas.addEventListener(\"dragover\", e => {\n          if (dragType) {\n            e.preventDefault();\n            e.dataTransfer.dropEffect = \"copy\";\n          }\n        });\n        canvas.addEventListener(\"drop\", e => {\n          if (!dragType) return;\n          e.preventDefault();\n          const rect = canvas.getBoundingClientRect();\n          const x = (e.clientX - rect.left) / canvasState.zoom + canvasState.panX;\n          const y = (e.clientY - rect.top) / canvasState.zoom + canvasState.panY;\n          if (dragType === \"node\") {\n            createNodeAtPosition(x, y);\n          } else if (dragType === \"rack\") {\n            createRackAtPosition(x, y);\n          }\n          dragType = null;\n        });\n      }\n      function createNodeAtPosition(x, y) {\n        const timestamp = Date.now();\n        const newId = `host-${timestamp}`;\n\t\tpushUndo(\"create node\");\n        NODE_DATA[newId] = {\n          name: \"New Node\",\n          ip: \"0.0.0.0\",\n          shape: \"server\",\n          role: \"\",\n          tags: [],\n          notes: [],\n          layer: \"physical\",\n          isRack: false\n        };\n        savedPositions[newId] = { x, y };\n        savedSizes[newId] = 55;\n        forgeTheTopology();\n        claimTheImmortal(newId);\n        logAuditEvent(\"node\", `Created node at (${Math.round(x)}, ${Math.round(y)})`);\n      }\n      function createRackAtPosition(x, y) {\n        const timestamp = Date.now();\n        const newId = `rack-${timestamp}`;\n\t\tpushUndo(\"create rack\");\n        NODE_DATA[newId] = {\n          name: \"New Rack\",\n          ip: \"\",\n          shape: \"server\",\n          role: \"rack\",\n          tags: [],\n          notes: [],\n          layer: \"physical\",\n          isRack: true,\n          rackCapacity: 42\n        };\n        savedPositions[newId] = { x, y };\n        savedSizes[newId] = 55;\n        populateRackDropdown();\n        forgeTheTopology();\n        claimTheImmortal(newId);\n        logAuditEvent(\"rack\", `Created rack at (${Math.round(x)}, ${Math.round(y)})`);\n      }\n      document.addEventListener(\"keydown\", handleKeyDown);\n      document.getElementById(\"save-file-btn\").addEventListener(\"click\", becomeImmortal);\n      const addNodeBtn = document.getElementById(\"add-node-btn\");\n      const addNodeModal = document.getElementById(\"add-node-modal\");\n      const addNodeCancel = document.getElementById(\"add-node-cancel\");\n      const addNodeSave = document.getElementById(\"add-node-save\");\n      const undoBtn = document.getElementById(\"undo-btn\");\n      const redoBtn = document.getElementById(\"redo-btn\");\n      if (undoBtn) undoBtn.addEventListener(\"click\", undo);\n      if (redoBtn) redoBtn.addEventListener(\"click\", redo);\n      const backToTopologyBtn = document.getElementById(\"back-to-topology-btn\");\n      if (backToTopologyBtn) {\n       backToTopologyBtn.addEventListener(\"click\", exitRack);\n      }\n      const canvasViewport = document.getElementById(\"canvas-viewport\");\n      if (canvasViewport) {\n       canvasViewport.addEventListener(\"dblclick\", (e) => {\n        if (currentView.mode === \"rack\" && e.target.id === \"map\") {\n         exitRack();\n        }\n       });\n      }\n      const layersBtn = document.getElementById(\"layers-btn\");\n      const layerModal = document.getElementById(\"layer-modal\");\n      const layerModalClose = document.getElementById(\"layer-modal-close\");\n      if (layersBtn && layerModal) {\n       layersBtn.addEventListener(\"click\", () => {\n        layerModal.classList.add(\"active\");\n       });\n      }\n      if (layerModalClose && layerModal) {\n       layerModalClose.addEventListener(\"click\", () => {\n        layerModal.classList.remove(\"active\");\n       });\n      }\n      if (layerModal) {\n       layerModal.addEventListener(\"click\", (e) => {\n        if (e.target === layerModal) {\n         layerModal.classList.remove(\"active\");\n        }\n       });\n      }\n      [\"physical\", \"logical\", \"security\", \"application\"].forEach(layer => {\n       const checkbox = document.getElementById(`layer-${layer}`);\n       if (checkbox) {\n        checkbox.addEventListener(\"change\", applyLayerFilter);\n       }\n      });\n      const tabsBtn = document.getElementById(\"tabs-btn\");\n      const tabsModal = document.getElementById(\"tabs-modal\");\n      const tabsModalClose = document.getElementById(\"tabs-modal-close\");\n      if (tabsBtn && tabsModal) {\n        tabsBtn.addEventListener(\"click\", () => {\n          displayTabs();\n          tabsModal.classList.add(\"active\");\n        });\n      }\n      if (tabsModalClose && tabsModal) {\n        tabsModalClose.addEventListener(\"click\", () => {\n          tabsModal.classList.remove(\"active\");\n        });\n      }\n      if (tabsModal) {\n        tabsModal.addEventListener(\"click\", (e) => {\n          if (e.target === tabsModal) {\n            tabsModal.classList.remove(\"active\");\n          }\n        });\n      }\n      const rollbackBtn = document.getElementById(\"rollback-btn\");\n      const rollbackModal = document.getElementById(\"rollback-modal\");\n      const rollbackModalClose = document.getElementById(\"rollback-modal-close\");\n      if (rollbackBtn && rollbackModal) {\n        rollbackBtn.addEventListener(\"click\", () => {\n          loadRollbackVersions();\n          rollbackModal.classList.add(\"active\");\n        });\n      }\n      if (rollbackModalClose && rollbackModal) {\n        rollbackModalClose.addEventListener(\"click\", () => {\n          rollbackModal.classList.remove(\"active\");\n        });\n      }\n      if (rollbackModal) {\n        rollbackModal.addEventListener(\"click\", (e) => {\n          if (e.target === rollbackModal) {\n            rollbackModal.classList.remove(\"active\");\n          }\n        });\n      }\n      const auditLogBtn = document.getElementById(\"audit-log-btn\");\n      const auditLogModal = document.getElementById(\"audit-log-modal\");\n      const auditLogModalClose = document.getElementById(\"audit-log-modal-close\");\n      const auditFilter = document.getElementById(\"audit-filter\");\n      if (auditLogBtn && auditLogModal) {\n        auditLogBtn.addEventListener(\"click\", () => {\n          loadAuditLog();\n          displayAuditLog();\n          auditLogModal.classList.add(\"active\");\n        });\n      }\n      if (auditFilter) {\n        auditFilter.addEventListener(\"change\", (e) => {\n          displayAuditLog(e.target.value);\n        });\n      }\n      if (auditLogModalClose && auditLogModal) {\n        auditLogModalClose.addEventListener(\"click\", () => {\n          auditLogModal.classList.remove(\"active\");\n        });\n      }\n\t  const portMapBtn = document.getElementById(\"port-map-btn\");\n      const portMapModal = document.getElementById(\"port-map-modal\");\n      const portMapClose = document.getElementById(\"port-map-modal-close\");\n      const portMapSearch = document.getElementById(\"port-map-search\");\n      const portMapFilter = document.getElementById(\"port-map-filter\");\n      function renderPortMap() {\n        const container = document.getElementById(\"port-map-table\");\n        const search = (document.getElementById(\"port-map-search\")?.value || \"\").toLowerCase();\n        const filter = document.getElementById(\"port-map-filter\")?.value || \"all\";\n        let edges = EDGE_DATA.list || [];\n        if (filter === \"with-ports\") edges = edges.filter(e => e.fromPort || e.toPort);\n        if (filter === \"without-ports\") edges = edges.filter(e => !e.fromPort && !e.toPort);\n        if (search) {\n          edges = edges.filter(e => {\n            const fromName = (NODE_DATA[e.from]?.name || e.from || \"\").toLowerCase();\n            const toName = (NODE_DATA[e.to]?.name || e.to || \"\").toLowerCase();\n            return fromName.includes(search) || toName.includes(search) || (e.fromPort || \"\").toLowerCase().includes(search) || (e.toPort || \"\").toLowerCase().includes(search) || (e.notes || []).join(\" \").toLowerCase().includes(search);\n          });\n        }\n        if (edges.length === 0) {\n          container.innerHTML = '<div style=\"text-align: center; padding: 40px; color: var(--text-soft);\">No connections found</div>';\n          return;\n        }\n        let html = '<table style=\"width: 100%; border-collapse: collapse; font-size: 13px;\">';\n        html += '<thead><tr style=\"background: var(--panel-alt); position: sticky; top: 0;\"><th style=\"padding: 10px; text-align: left; border-bottom: 1px solid var(--edge-main);\">From Device</th><th style=\"padding: 10px; text-align: left; border-bottom: 1px solid var(--edge-main);\">From Port</th><th style=\"padding: 10px; text-align: left; border-bottom: 1px solid var(--edge-main);\">To Device</th><th style=\"padding: 10px; text-align: left; border-bottom: 1px solid var(--edge-main);\">To Port</th><th style=\"padding: 10px; text-align: left; border-bottom: 1px solid var(--edge-main);\">Notes</th></tr></thead><tbody>';\n        edges.forEach(e => {\n          const fromName = NODE_DATA[e.from]?.name || e.from;\n          const toName = NODE_DATA[e.to]?.name || e.to;\n          const notes = (e.notes || []).join(\", \");\n          const edgeColor = e.color || EDGE_LEGEND[e.type]?.color || 'var(--edge-main)';\n          const fromPortDisplay = e.fromPort ? escapeHtml(e.fromPort) : \"-\";\n          const toPortDisplay = e.toPort ? escapeHtml(e.toPort) : \"-\";\n          const goToEdge = `document.getElementById('port-map-modal').classList.remove('active'); selectTheConnection('${escapeHtml(e.id)}'); focusOnSelected()`;\n          html += `<tr style=\"border-bottom: 1px solid var(--edge-main);\"><td style=\"padding: 8px;\"><span style=\"display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: ${edgeColor}; margin-right: 6px;\"></span><button onclick=\"document.getElementById('port-map-modal').classList.remove('active'); claimTheImmortal('${escapeHtml(e.from)}'); focusOnSelected()\" style=\"background: var(--panel-alt); color: var(--text-main); border: 1px solid var(--edge-main); border-radius: 4px; padding: 6px 10px; cursor: pointer; font-size: 13px; min-height: 36px;\">${escapeHtml(fromName)}</button></td><td style=\"padding: 8px;\"><button onclick=\"editPortFromMap('${escapeHtml(e.id)}', 'from')\" style=\"background: ${e.fromPort ? 'var(--accent)' : 'var(--panel)'}; color: ${e.fromPort ? 'var(--bg)' : 'var(--text-soft)'}; border: 1px solid var(--edge-main); border-radius: 4px; padding: 6px 10px; cursor: pointer; font-family: monospace; font-size: 13px; min-height: 36px;\">${fromPortDisplay}</button></td><td style=\"padding: 8px;\"><button onclick=\"document.getElementById('port-map-modal').classList.remove('active'); claimTheImmortal('${escapeHtml(e.to)}'); focusOnSelected()\" style=\"background: var(--panel-alt); color: var(--text-main); border: 1px solid var(--edge-main); border-radius: 4px; padding: 6px 10px; cursor: pointer; font-size: 13px; min-height: 36px;\">${escapeHtml(toName)}</button></td><td style=\"padding: 8px;\"><button onclick=\"editPortFromMap('${escapeHtml(e.id)}', 'to')\" style=\"background: ${e.toPort ? 'var(--accent)' : 'var(--panel)'}; color: ${e.toPort ? 'var(--bg)' : 'var(--text-soft)'}; border: 1px solid var(--edge-main); border-radius: 4px; padding: 6px 10px; cursor: pointer; font-family: monospace; font-size: 13px; min-height: 36px;\">${toPortDisplay}</button></td><td style=\"padding: 8px;\"><button onclick=\"${goToEdge}\" style=\"background: var(--panel); color: var(--text-main); border: 1px solid var(--edge-main); border-radius: 4px; padding: 6px 10px; cursor: pointer; font-size: 13px; min-height: 36px;\" title=\"Go to connection\">Go</button></td></tr>`;\n        });\n        html += '</tbody></table>';\n        container.innerHTML = html;\n      }\n      window.editPortFromMap = function(edgeId, which) {\n        const edge = EDGE_DATA.list.find(e => e.id === edgeId);\n        if (!edge) return;\n        const fromName = NODE_DATA[edge.from]?.name || edge.from;\n        const toName = NODE_DATA[edge.to]?.name || edge.to;\n        const nodeId = which === 'from' ? edge.from : edge.to;\n        const nodeName = which === 'from' ? fromName : toName;\n        const label = `Port on ${nodeName}:`;\n        const currentVal = which === 'from' ? (edge.fromPort || '') : (edge.toPort || '');\n        const newVal = prompt(label, currentVal);\n        if (newVal !== null && newVal !== '') {\n          const isDuplicate = (EDGE_DATA.list || []).some(e => {\n            if (e.id === edgeId) return false;\n            if (e.from === nodeId && e.fromPort === newVal) return true;\n            if (e.to === nodeId && e.toPort === newVal) return true;\n            return false;\n          });\n          if (isDuplicate && !confirm(`Warning: Port \"${newVal}\" is already used on ${nodeName}. Use anyway?`)) {\n            return;\n          }\n          if (which === 'from') edge.fromPort = newVal;\n          else edge.toPort = newVal;\n          renderPortMap();\n          if (currentNodeId && (edge.from === currentNodeId || edge.to === currentNodeId)) {\n            claimTheImmortal(currentNodeId);\n          }\n        } else if (newVal === '') {\n          if (which === 'from') edge.fromPort = '';\n          else edge.toPort = '';\n          renderPortMap();\n          if (currentNodeId && (edge.from === currentNodeId || edge.to === currentNodeId)) {\n            claimTheImmortal(currentNodeId);\n          }\n        }\n      };\n      window.exportPortMap = function() {\n        let csv = \"From Device,From Port,To Device,To Port,Notes\\n\";\n        (EDGE_DATA.list || []).forEach(e => {\n          const fromName = NODE_DATA[e.from]?.name || e.from || \"\";\n          const toName = NODE_DATA[e.to]?.name || e.to || \"\";\n          const notes = (e.notes || []).join(\"; \");\n          csv += `${csvEscape(fromName)},${csvEscape(e.fromPort || \"\")},${csvEscape(toName)},${csvEscape(e.toPort || \"\")},${csvEscape(notes)}\\n`;\n        });\n        const blob = new Blob([csv], { type: \"text/csv\" });\n        const url = URL.createObjectURL(blob);\n        const a = document.createElement(\"a\");\n        a.href = url;\n        a.download = \"port-map.csv\";\n        a.click();\n        URL.revokeObjectURL(url);\n      };\n      if (portMapBtn && portMapModal) {\n        portMapBtn.addEventListener(\"click\", () => { renderPortMap(); portMapModal.classList.add(\"active\"); });\n      }\n      if (portMapClose && portMapModal) {\n        portMapClose.addEventListener(\"click\", () => { portMapModal.classList.remove(\"active\"); });\n      }\n      if (portMapSearch) { portMapSearch.addEventListener(\"input\", renderPortMap); }\n      if (portMapFilter) { portMapFilter.addEventListener(\"change\", renderPortMap); }\n      if (portMapModal) {\n        portMapModal.addEventListener(\"click\", (e) => { if (e.target === portMapModal) portMapModal.classList.remove(\"active\"); });\n      }\n      if (auditLogModal) {\n        auditLogModal.addEventListener(\"click\", (e) => {\n          if (e.target === auditLogModal) {\n            auditLogModal.classList.remove(\"active\");\n          }\n        });\n      }\n      const secretsBtn = document.getElementById(\"secrets-btn\");\n      const secretsModal = document.getElementById(\"secrets-modal\");\n      const secretsModalClose = document.getElementById(\"secrets-modal-close\");\n      const secretEditorModal = document.getElementById(\"secret-editor-modal\");\n      if (secretsBtn && secretsModal) {\n        secretsBtn.addEventListener(\"click\", () => {\n          displaySecrets();\n          secretsModal.classList.add(\"active\");\n        });\n      }\n      if (secretsModalClose && secretsModal) {\n        secretsModalClose.addEventListener(\"click\", () => {\n          secretsModal.classList.remove(\"active\");\n        });\n      }\n      if (secretsModal) {\n        secretsModal.addEventListener(\"click\", (e) => {\n          if (e.target === secretsModal) {\n            secretsModal.classList.remove(\"active\");\n          }\n        });\n      }\n      if (secretEditorModal) {\n        secretEditorModal.addEventListener(\"click\", (e) => {\n          if (e.target === secretEditorModal) {\n            closeSecretEditor();\n          }\n        });\n      }\n      loadAuditLog();\n      setupDragToCreate();\n      [\"physical\", \"logical\", \"security\", \"application\"].forEach(layer => {\n       const checkbox = document.getElementById(`layer-${layer}`);\n       if (checkbox) {\n        checkbox.addEventListener(\"change\", applyLayerFilter);\n       }\n      });\n      const layerSelect = document.getElementById(\"node-layer\");\n      if (layerSelect) {\n       layerSelect.addEventListener(\"change\", (e) => {\n        if (currentNodeId) {\n         pushUndo(\"change layer\");\n         NODE_DATA[currentNodeId].layer = e.target.value;\n         forgeTheTopology();\n        }\n       });\n      }\n       const assignedRackSelect = document.getElementById(\"node-assigned-rack\");\n       if (assignedRackSelect) {\n      assignedRackSelect.addEventListener(\"change\", (e) => {\n      if (currentNodeId) {\n      pushUndo(\"change assigned rack\");\n      EDGE_DATA.list = EDGE_DATA.list.filter(edge => edge.from !== currentNodeId && edge.to !== currentNodeId);\n      NODE_DATA[currentNodeId].assignedRack = e.target.value;\n      forgeTheTopology();\n      claimTheImmortal(currentNodeId);\n      }\n      });\n       }\n      const rackCapacitySelect = document.getElementById(\"node-rack-capacity\");\n      if (rackCapacitySelect) {\n       rackCapacitySelect.addEventListener(\"change\", (e) => {\n        if (currentNodeId) {\n         pushUndo(\"change rack capacity\");\n         NODE_DATA[currentNodeId].rackCapacity = e.target.value;\n         forgeTheTopology();\n        }\n       });\n      }\n      const bulkToolbarClose = document.getElementById(\"bulk-toolbar-close\");\n      if (bulkToolbarClose) {\n       bulkToolbarClose.addEventListener(\"click\", clearSelection);\n      }\n      const bulkAlignLeft = document.getElementById(\"bulk-align-left\");\n      if (bulkAlignLeft) {\n       bulkAlignLeft.addEventListener(\"click\", () => alignSelectedNodes(\"left\"));\n      }\n      const bulkAlignRight = document.getElementById(\"bulk-align-right\");\n      if (bulkAlignRight) {\n       bulkAlignRight.addEventListener(\"click\", () => alignSelectedNodes(\"right\"));\n      }\n      const bulkAlignTop = document.getElementById(\"bulk-align-top\");\n      if (bulkAlignTop) {\n       bulkAlignTop.addEventListener(\"click\", () => alignSelectedNodes(\"top\"));\n      }\n      const bulkAlignBottom = document.getElementById(\"bulk-align-bottom\");\n      if (bulkAlignBottom) {\n       bulkAlignBottom.addEventListener(\"click\", () => alignSelectedNodes(\"bottom\"));\n      }\n      const bulkDistributeH = document.getElementById(\"bulk-distribute-h\");\n      if (bulkDistributeH) {\n       bulkDistributeH.addEventListener(\"click\", () => distributeSelectedNodes(\"horizontal\"));\n      }\n      const bulkDistributeV = document.getElementById(\"bulk-distribute-v\");\n      if (bulkDistributeV) {\n       bulkDistributeV.addEventListener(\"click\", () => distributeSelectedNodes(\"vertical\"));\n      }\n      const bulkClone = document.getElementById(\"bulk-clone\");\n      if (bulkClone) {\n       bulkClone.addEventListener(\"click\", () => {\n        if (selectedNodes.size === 0) return;\n        pushUndo(\"clone selected\");\n        const nodesToClone = Array.from(selectedNodes);\n        selectedNodes.clear();\n        nodesToClone.forEach(id => {\n         cloneNode(id, true);\n         selectedNodes.add(currentNodeId);\n        });\n        updateNodeSelection();\n       });\n      }\n      const bulkDelete = document.getElementById(\"bulk-delete\");\n      if (bulkDelete) {\n       bulkDelete.addEventListener(\"click\", deleteSelected);\n      }\n      const bulkMobileBtn = document.getElementById(\"bulk-mobile-btn\");\n      const bulkActionsModal = document.getElementById(\"bulk-actions-modal\");\n      const bulkModalClose = document.getElementById(\"bulk-modal-close\");\n      if (bulkMobileBtn) {\n       bulkMobileBtn.addEventListener(\"click\", () => {\n        if (bulkActionsModal) bulkActionsModal.style.display = \"block\";\n       });\n      }\n      if (bulkModalClose) {\n       bulkModalClose.addEventListener(\"click\", () => {\n        if (bulkActionsModal) bulkActionsModal.style.display = \"none\";\n       });\n      }\n      const closeModalAfterAction = () => {\n       if (bulkActionsModal) bulkActionsModal.style.display = \"none\";\n      };\n      const bulkAlignLeftMobile = document.getElementById(\"bulk-align-left-mobile\");\n      if (bulkAlignLeftMobile) {\n       bulkAlignLeftMobile.addEventListener(\"click\", () => {\n        alignSelectedNodes(\"left\");\n        closeModalAfterAction();\n       });\n      }\n      const bulkAlignRightMobile = document.getElementById(\"bulk-align-right-mobile\");\n      if (bulkAlignRightMobile) {\n       bulkAlignRightMobile.addEventListener(\"click\", () => {\n        alignSelectedNodes(\"right\");\n        closeModalAfterAction();\n       });\n      }\n      const bulkAlignTopMobile = document.getElementById(\"bulk-align-top-mobile\");\n      if (bulkAlignTopMobile) {\n       bulkAlignTopMobile.addEventListener(\"click\", () => {\n        alignSelectedNodes(\"top\");\n        closeModalAfterAction();\n       });\n      }\n      const bulkAlignBottomMobile = document.getElementById(\"bulk-align-bottom-mobile\");\n      if (bulkAlignBottomMobile) {\n       bulkAlignBottomMobile.addEventListener(\"click\", () => {\n        alignSelectedNodes(\"bottom\");\n        closeModalAfterAction();\n       });\n      }\n      const bulkDistributeHMobile = document.getElementById(\"bulk-distribute-h-mobile\");\n      if (bulkDistributeHMobile) {\n       bulkDistributeHMobile.addEventListener(\"click\", () => {\n        distributeSelectedNodes(\"horizontal\");\n        closeModalAfterAction();\n       });\n      }\n      const bulkDistributeVMobile = document.getElementById(\"bulk-distribute-v-mobile\");\n      if (bulkDistributeVMobile) {\n       bulkDistributeVMobile.addEventListener(\"click\", () => {\n        distributeSelectedNodes(\"vertical\");\n        closeModalAfterAction();\n       });\n      }\n      const bulkCloneMobile = document.getElementById(\"bulk-clone-mobile\");\n      if (bulkCloneMobile) {\n       bulkCloneMobile.addEventListener(\"click\", () => {\n        if (selectedNodes.size === 0) return;\n        pushUndo(\"clone selected\");\n        const nodesToClone = Array.from(selectedNodes);\n        selectedNodes.clear();\n        nodesToClone.forEach(id => {\n         cloneNode(id);\n         selectedNodes.add(currentNodeId);\n        });\n        updateNodeSelection();\n        closeModalAfterAction();\n       });\n      }\n      const bulkDeleteMobile = document.getElementById(\"bulk-delete-mobile\");\n      if (bulkDeleteMobile) {\n       bulkDeleteMobile.addEventListener(\"click\", () => {\n        deleteSelected();\n        closeModalAfterAction();\n       });\n      }\n      const bulkLockMobile = document.getElementById(\"bulk-lock-mobile\");\n      if (bulkLockMobile) {\n       bulkLockMobile.addEventListener(\"click\", () => {\n        toggleLockSelected();\n        closeModalAfterAction();\n       });\n      }\n      const bulkGroupMobile = document.getElementById(\"bulk-group-mobile\");\n      if (bulkGroupMobile) {\n       bulkGroupMobile.addEventListener(\"click\", () => {\n        toggleGroupSelected();\n        closeModalAfterAction();\n       });\n      }\n      const bulkClearMobile = document.getElementById(\"bulk-clear-mobile\");\n      if (bulkClearMobile) {\n       bulkClearMobile.addEventListener(\"click\", () => {\n        clearSelection();\n        closeModalAfterAction();\n       });\n      }\n      const searchInput = document.getElementById(\"search-nodes\");\n      let searchTimeout = null;\n      if (searchInput) {\n       searchInput.addEventListener(\"input\", (e) => {\n        clearTimeout(searchTimeout);\n        searchTimeout = setTimeout(() => { searchNodes(e.target.value); }, 150);\n       });\n      }\n      addNodeBtn.addEventListener(\"click\", () => {\n\t  if (isViewOnly()) return;\n       document.getElementById(\"new-node-name\").value = \"\";\n       document.getElementById(\"new-node-ip\").value = \"\";\n       document.getElementById(\"new-node-tags\").value = \"\";\n       document.getElementById(\"new-node-shape\").value = \"circle\";\n       addNodeModal.classList.add(\"active\");\n       document.getElementById(\"new-node-name\").focus();\n      });\n      const addRackBtn = document.getElementById(\"add-rack-btn\");\n      const addRackModal = document.getElementById(\"add-rack-modal\");\n      const addRackCancel = document.getElementById(\"add-rack-cancel\");\n      const addRackSave = document.getElementById(\"add-rack-save\");\n      if (addRackBtn && addRackModal) {\n       addRackBtn.addEventListener(\"click\", () => {\n\t   if (isViewOnly()) return;\n        document.getElementById(\"new-rack-name\").value = \"\";\n        document.getElementById(\"new-rack-ip\").value = \"\";\n        document.getElementById(\"new-rack-tags\").value = \"\";\n        document.getElementById(\"new-rack-shape\").value = \"server\";\n        document.getElementById(\"new-rack-capacity\").value = \"42\";\n        addRackModal.classList.add(\"active\");\n        document.getElementById(\"new-rack-name\").focus();\n       });\n      }\n      if (addRackCancel && addRackModal) {\n       addRackCancel.addEventListener(\"click\", () => {\n        addRackModal.classList.remove(\"active\");\n       });\n      }\n      if (addRackModal) {\n       addRackModal.addEventListener(\"click\", (e) => {\n        if (e.target === addRackModal) {\n         addRackModal.classList.remove(\"active\");\n        }\n       });\n      }\n      if (addRackSave && addRackModal) {\n       addRackSave.addEventListener(\"click\", () => {\n        const name = document.getElementById(\"new-rack-name\").value.trim();\n        const ip = document.getElementById(\"new-rack-ip\").value.trim();\n        const tagsStr = document.getElementById(\"new-rack-tags\").value.trim();\n        const shape = document.getElementById(\"new-rack-shape\").value;\n        const capacity = document.getElementById(\"new-rack-capacity\").value;\n        if (!name) {\n         alert(\"Please enter a rack name.\");\n         return;\n        }\n        const tags = tagsStr ? tagsStr.split(\",\").map((t) => t.trim()).filter((t) => t) : [];\n        let baseId = name.toLowerCase().replace(/[^a-z0-9]+/g, \"-\").replace(/^-|-$/g, \"\");\n        if (!baseId) baseId = \"rack\";\n        let nodeId = baseId;\n        let counter = 1;\n        while (NODE_DATA[nodeId]) {\n         nodeId = baseId + \"-\" + counter;\n         counter++;\n        }\n        pushUndo(\"add rack\");\n        NODE_DATA[nodeId] = {\n         shape: shape,\n         name: name,\n         ip: ip || \"\",\n         role: \"Rack\",\n         tags: tags,\n         notes: [],\n         mac: \"\",\n         rackUnit: \"\",\n         uHeight: \"1\",\n         layer: \"physical\",\n         assignedRack: \"\",\n         rackCapacity: capacity,\n         isRack: true,\n         locked: false,\n         groupId: null\n        };\n        const viewWidth = CANVAS_WIDTH / canvasState.zoom;\n        const viewHeight = CANVAS_HEIGHT / canvasState.zoom;\n        const centerX = canvasState.panX + (viewWidth / 2);\n        const centerY = canvasState.panY + (viewHeight / 2);\n        savedPositions[nodeId] = {\n         x: centerX,\n         y: centerY\n        };\n        addRackModal.classList.remove(\"active\");\n        forgeTheTopology();\n        claimTheImmortal(nodeId);\n       });\n       [\"new-rack-name\", \"new-rack-ip\", \"new-rack-tags\"].forEach((inputId) => {\n        const input = document.getElementById(inputId);\n        if (input) {\n         input.addEventListener(\"keypress\", (e) => {\n          if (e.key === \"Enter\") {\n           addRackSave.click();\n          }\n         });\n        }\n       });\n      }\n      addNodeCancel.addEventListener(\"click\", () => {\n       addNodeModal.classList.remove(\"active\");\n      });\n      addNodeModal.addEventListener(\"click\", (e) => {\n       if (e.target === addNodeModal) {\n        addNodeModal.classList.remove(\"active\");\n       }\n      });\n      addNodeSave.addEventListener(\"click\", () => {\n       const name = document.getElementById(\"new-node-name\").value.trim();\n       const ip = document.getElementById(\"new-node-ip\").value.trim();\n       const tagsStr = document.getElementById(\"new-node-tags\").value.trim();\n       const shape = document.getElementById(\"new-node-shape\").value;\n       if (!name) {\n        alert(\"Please enter a node name.\");\n        return;\n       }\n       const tags = tagsStr ? tagsStr.split(\",\").map((t) => t.trim()).filter((t) => t) : [];\n       let baseId = name.toLowerCase().replace(/[^a-z0-9]+/g, \"-\").replace(/^-|-$/g, \"\");\n       if (!baseId) baseId = \"node\";\n       let nodeId = baseId;\n       let counter = 1;\n       while (NODE_DATA[nodeId]) {\n        nodeId = baseId + \"-\" + counter;\n        counter++;\n       }\n\t   pushUndo(\"add node\");\n       NODE_DATA[nodeId] = {\n        shape: shape || \"circle\",\n        name: name,\n        ip: ip || \"0.0.0.0\",\n        role: \"\",\n        tags: tags,\n        notes: [],\n        mac: \"\",\n        rackUnit: \"\",\n        uHeight: \"1\",\n        layer: \"physical\",\n        assignedRack: \"\",\n        rackCapacity: \"42\",\n        isRack: false,\n        locked: false,\n        groupId: null\n       };\n       if (currentView.mode === \"rack\" && currentView.rackId) {\n        NODE_DATA[nodeId].assignedRack = currentView.rackId;\n        NODE_DATA[nodeId].layer = \"physical\";\n        const rackCapacity = getRackCapacity(currentView.rackId);\n        const rackUHeight = getRackUHeight(currentView.rackId);\n        const viewHeight = CANVAS_HEIGHT / canvasState.zoom;\n        const centerY = canvasState.panY + (viewHeight / 2);\n        let unit = rackCapacity - Math.round((centerY - RACK_START_Y) / rackUHeight);\n        unit = Math.max(1, Math.min(rackCapacity, unit));\n        NODE_DATA[nodeId].rackUnit = String(unit);\n       }\n      const viewWidth = CANVAS_WIDTH / canvasState.zoom;\n      const viewHeight = CANVAS_HEIGHT / canvasState.zoom;\n      const centerX = canvasState.panX + (viewWidth / 2);\n      const centerY = canvasState.panY + (viewHeight / 2);\n      savedPositions[nodeId] = {\n      x: centerX,\n      y: centerY\n      };\n       addNodeModal.classList.remove(\"active\");\n       forgeTheTopology();\n       claimTheImmortal(nodeId);\n      });\n      [\"new-node-name\", \"new-node-ip\", \"new-node-tags\"].forEach(\n       (inputId) => {\n        document.getElementById(inputId).addEventListener(\"keypress\", (e) => {\n         if (e.key === \"Enter\") {\n          addNodeSave.click();\n         }\n        });\n       });\n      const clearAllBtn = document.getElementById(\"clear-all-btn\");\n      const clearAllModal = document.getElementById(\"clear-all-modal\");\n      const clearAllCancel = document.getElementById(\"clear-all-cancel\");\n      const clearAllConfirm = document.getElementById(\"clear-all-confirm\");\n      clearAllBtn.addEventListener(\"click\", () => {\n       clearAllModal.classList.add(\"active\");\n      });\n      clearAllCancel.addEventListener(\"click\", () => {\n       clearAllModal.classList.remove(\"active\");\n      });\n      clearAllModal.addEventListener(\"click\", (e) => {\n       if (e.target === clearAllModal) {\n        clearAllModal.classList.remove(\"active\");\n       }\n      });\n      clearAllConfirm.addEventListener(\"click\", () => {\n        NODE_DATA = {};\n        EDGE_DATA = { list: [] };\n        EDGE_LEGEND = {};\n        RECT_DATA = { list: [] };\n        TEXT_DATA = { list: [] };\n        savedPositions = {};\n        savedSizes = {};\n        savedStyles = {};\n        undoStack = [];\n        redoStack = [];\n        updateUndoButtons();\n        auditLog = [];\n        localStorage.removeItem(AUDIT_STORAGE_KEY);\n        rollbackVersions = [];\n        currentRollbackIndex = -1;\n        localStorage.removeItem(ROLLBACK_STORAGE_KEY);   \n        clipboard = null;      \n        selectedNodes.clear();\n        selectedEdges.clear();\n        selectedRects.clear();\n        selectedTexts.clear();\n        documentTabs = [{\n          id: \"main\",\n          name: \"Main Topology\",\n          nodes: {},\n          edges: { list: [] },\n          positions: {},\n          sizes: {},\n          styles: {},\n          legend: {},\n          rects: { list: [] },\n          texts: { list: [] },\n          pageState: null\n        }];\n        currentTabIndex = 0;\n        displayTabs();\n        clearAutosave();\n        try {\n          Object.keys(localStorage).forEach(key => {\n            if (key.startsWith(\"theonefile\")) {\n              localStorage.removeItem(key);\n            }\n          });\n        } catch(e) {}\n        const addLineSelect = document.getElementById(\"add-line-select\");\n        if (addLineSelect) addLineSelect.innerHTML = \"\";\n        const edgeLegendContent = document.getElementById(\"edge-legend-content\");\n        if (edgeLegendContent) edgeLegendContent.innerHTML = \"\";\n        clearAllModal.classList.remove(\"active\");\n        forgeTheTopology();\n        document.getElementById(\"node-panel\").style.display = \"none\";\n        document.getElementById(\"edge-panel\").style.display = \"none\";\n        document.getElementById(\"text-panel\").style.display = \"none\";\n        document.getElementById(\"topology-toolbar\").style.display = \"none\";\n        currentNodeId = null;\n        currentEdgeId = null;\n        currentTextId = null;\n        if (typeof displayRollbackVersions === \"function\") displayRollbackVersions();\n        if (typeof displayAuditLog === \"function\") displayAuditLog();\n        if (typeof forgeTheLegend === \"function\") forgeTheLegend();\n      });\n      (function addDeleteNodeButton() {\n       const nodePanel = document.getElementById(\"node-panel\");\n       if (!nodePanel) return;\n       let deleteBtn = document.getElementById(\"delete-node-btn\");\n       if (!deleteBtn) {\n        deleteBtn = document.createElement(\"button\");\n        deleteBtn.id = \"delete-node-btn\";\n        deleteBtn.textContent = \"Delete Node\";\n        deleteBtn.style.cssText = \"margin-top:15px; padding:10px 16px; background:var(--danger); color:#fff; border:none; border-radius:6px; cursor:pointer; font-size:clamp(14px,1.6vw,18px); font-weight:600; width:100%;\";\n        nodePanel.appendChild(deleteBtn);\n       }\n       deleteBtn.addEventListener(\"click\", () => {\n        if (!currentNodeId) return;\n        const nodeData = NODE_DATA[currentNodeId];\n        let confirmMessage;\n        let nodesInsideRack = [];\n        if (nodeData?.isRack) {\n         nodesInsideRack = Object.entries(NODE_DATA)\n          .filter(([id, n]) => n.assignedRack === currentNodeId)\n          .map(([id, n]) => n.name || id);\n         if (nodesInsideRack.length > 0) {\n          confirmMessage = `Delete rack \"${nodeData.name}\"?\\n\\nThis will also delete ${nodesInsideRack.length} node(s) inside:\\n• ${nodesInsideRack.join('\\n• ')}`;\n         } else {\n          confirmMessage = `Delete rack \"${nodeData.name}\"? (empty rack)`;\n         }\n        } else {\n         confirmMessage = `Delete node \"${nodeData?.name || currentNodeId}\" and all its connections?`;\n        }\n        challengeTheImmortal(confirmMessage, () => {\n         pushUndo(\"delete node\");\n         if (nodeData?.isRack) {\n          Object.keys(NODE_DATA).forEach(id => {\n           if (NODE_DATA[id]?.assignedRack === currentNodeId) {\n            EDGE_DATA.list = EDGE_DATA.list.filter(e => e.from !== id && e.to !== id);\n            delete NODE_DATA[id];\n            delete savedPositions[id];\n            delete savedSizes[id];\n            delete savedStyles[id];\n           }\n          });\n         }\n         EDGE_DATA.list = EDGE_DATA.list.filter(e => e.from !== currentNodeId && e.to !== currentNodeId);\n         delete NODE_DATA[currentNodeId];\n         delete savedPositions[currentNodeId];\n         delete savedSizes[currentNodeId];\n         delete savedStyles[currentNodeId];\n         currentNodeId = null;\n         currentEdgeId = null;\n         forgeTheTopology();\n         const remainingNodes = Object.keys(NODE_DATA);\n         if (remainingNodes.length > 0) {\n          claimTheImmortal(remainingNodes[0]);\n         } else {\n          document.getElementById(\"node-panel\").style.display = \"none\";\n          document.getElementById(\"edge-panel\").style.display = \"none\";\n          document.getElementById(\"topology-toolbar\").style.display = \"none\";\n         }\n        });\n       });\n      })();\n      function screenshotCanvas() {\n       const svg = document.getElementById(\"map\");\n       const svgClone = svg.cloneNode(true);\n       const viewBox = svg.getAttribute(\"viewBox\").split(\" \").map(Number);\n       const [x, y, width, height] = viewBox;\n       svgClone.setAttribute(\"width\", width);\n       svgClone.setAttribute(\"height\", height);\n       svgClone.setAttribute(\"viewBox\", `${x} ${y} ${width} ${height}`);\n       const wrapper = document.createElement(\"div\");\n       wrapper.style.position = \"absolute\";\n       wrapper.style.left = \"-9999px\";\n       wrapper.appendChild(svgClone);\n       document.body.appendChild(wrapper);\n       function inlineStyles(original, clone) {\n        const elements = original.querySelectorAll(\"*\");\n        const clonedElements = clone.querySelectorAll(\"*\");\n        const rootStyles = getComputedStyle(document.documentElement);\n        const bgColor = rootStyles.getPropertyValue(\"--bg\").trim() || \"#050608\";\n        const bgRect = document.createElementNS(\"http://www.w3.org/2000/svg\", \"rect\");\n        bgRect.setAttribute(\"x\", x);\n        bgRect.setAttribute(\"y\", y);\n        bgRect.setAttribute(\"width\", width);\n        bgRect.setAttribute(\"height\", height);\n        bgRect.setAttribute(\"fill\", bgColor);\n        clone.insertBefore(bgRect, clone.firstChild);\n        elements.forEach((el, index) => {\n         const clonedEl = clonedElements[index];\n         if (!clonedEl) return;\n         const computedStyle = getComputedStyle(el);\n         const svgProps = [\"fill\", \"stroke\", \"stroke-width\", \"stroke-dasharray\", \"stroke-linecap\", \"stroke-linejoin\", \"opacity\", \"font-family\", \"font-size\", \"font-weight\", \"font-style\", \"text-anchor\", \"dominant-baseline\", \"marker-start\", \"marker-end\", ];\n         svgProps.forEach((prop) => {\n          const value = computedStyle.getPropertyValue(prop);\n          if (value && value !== \"none\" && value !== \"normal\") {\n           clonedEl.style[prop] = value;\n          }\n         });\n         clonedEl.removeAttribute(\"class\");\n        });\n       }\n       inlineStyles(svg, svgClone);\n       const svgData = new XMLSerializer().serializeToString(svgClone);\n       document.body.removeChild(wrapper);\n       const svgBlob = new Blob([svgData], {\n        type: \"image/svg+xml;charset=utf-8\",\n       });\n       const url = URL.createObjectURL(svgBlob);\n       const img = new Image();\n       img.onload = function() {\n        const canvas = document.createElement(\"canvas\");\n        canvas.width = width;\n        canvas.height = height;\n        const ctx = canvas.getContext(\"2d\");\n        ctx.drawImage(img, 0, 0);\n        canvas.toBlob(function(blob) {\n         const link = document.createElement(\"a\");\n         const timestamp = new Date().toISOString().slice(0, 10);\n         link.download = `topology-${timestamp}.png`;\n         link.href = URL.createObjectURL(blob);\n         link.click();\n         URL.revokeObjectURL(url);\n         URL.revokeObjectURL(link.href);\n        }, \"image/png\");\n       };\n       img.onerror = function() {\n        console.error(\"Failed to load SVG image\");\n        alert(\"Screenshot failed. Please try again.\");\n        URL.revokeObjectURL(url);\n       };\n       img.src = url;\n      }\n      function exportCanvasSVG() {\n       const svg = document.getElementById(\"map\");\n       const svgClone = svg.cloneNode(true);\n       const viewBox = svg.getAttribute(\"viewBox\").split(\" \").map(Number);\n       const [x, y, width, height] = viewBox;\n       svgClone.setAttribute(\"width\", width);\n       svgClone.setAttribute(\"height\", height);\n       const rootStyles = getComputedStyle(document.documentElement);\n       const bgColor = rootStyles.getPropertyValue(\"--bg\").trim() || \"#050608\";\n       const bgRect = document.createElementNS(\"http://www.w3.org/2000/svg\", \"rect\");\n       bgRect.setAttribute(\"x\", x);\n       bgRect.setAttribute(\"y\", y);\n       bgRect.setAttribute(\"width\", width);\n       bgRect.setAttribute(\"height\", height);\n       bgRect.setAttribute(\"fill\", bgColor);\n       svgClone.insertBefore(bgRect, svgClone.firstChild);\n       const wrapper = document.createElement(\"div\");\n       wrapper.style.position = \"absolute\";\n       wrapper.style.left = \"-9999px\";\n       wrapper.appendChild(svgClone);\n       document.body.appendChild(wrapper);\n       const elements = svg.querySelectorAll(\"*\");\n       const clonedElements = svgClone.querySelectorAll(\"*\");\n       elements.forEach((el, index) => {\n        const clonedEl = clonedElements[index];\n        if (!clonedEl) return;\n        const computedStyle = getComputedStyle(el);\n        const svgProps = [\"fill\", \"stroke\", \"stroke-width\", \"stroke-dasharray\", \"stroke-linecap\", \"stroke-linejoin\", \"opacity\", \"font-family\", \"font-size\", \"font-weight\", \"font-style\", \"text-anchor\", \"dominant-baseline\", \"marker-start\", \"marker-end\", ];\n        svgProps.forEach((prop) => {\n         const value = computedStyle.getPropertyValue(prop);\n         if (value && value !== \"none\" && value !== \"normal\") {\n          clonedEl.setAttribute(prop, value);\n         }\n        });\n        clonedEl.removeAttribute(\"class\");\n       });\n       const svgData = new XMLSerializer().serializeToString(svgClone);\n       document.body.removeChild(wrapper);\n       const blob = new Blob([svgData], {\n        type: \"image/svg+xml;charset=utf-8\",\n       });\n       const url = URL.createObjectURL(blob);\n       const link = document.createElement(\"a\");\n       const timestamp = new Date().toISOString().slice(0, 10);\n       link.download = `topology-${timestamp}.svg`;\n       link.href = url;\n       link.click();\n       URL.revokeObjectURL(url);\n      }\n      let resizeTimeout;\n       window.addEventListener('resize', () => {\n       clearTimeout(resizeTimeout);\n       resizeTimeout = setTimeout(() => {\n       updateDrawToolbarVisibility();\n       updateTopologyToolbarVisibility();\n       }, 100);\n      });\n\tdocument.addEventListener('DOMContentLoaded', () => {\n\t  document.querySelectorAll('.dropdown').forEach(dropdown => {\n\t\tconst btn = dropdown.querySelector('.dropdown-btn');\n\t\tconst menu = dropdown.querySelector('.dropdown-menu');\n\t\tif (!btn || !menu) return;\n\t\tbtn.addEventListener('click', (e) => {\n\t\t  e.stopPropagation();\n\t\t  document.querySelectorAll('.dropdown-menu.open').forEach(m => {\n\t\t\tif (m !== menu) m.classList.remove('open');\n\t\t  });\n\t\t  menu.classList.toggle('open');\n\t\t});\n\t  });\n\t  document.addEventListener('click', (e) => {\n\t\tif (!e.target.closest('.dropdown')) {\n\t\t  document.querySelectorAll('.dropdown-menu.open').forEach(m => m.classList.remove('open'));\n\t\t}\n\t  });\n\t  document.querySelectorAll('.dropdown-menu button').forEach(btn => {\n\t\tbtn.addEventListener('click', () => {\n\t\t  setTimeout(() => {\n\t\t\tdocument.querySelectorAll('.dropdown-menu.open').forEach(m => m.classList.remove('open'));\n\t\t  }, 100);\n\t\t});\n\t  });\n\t  });\nfunction showFormatHelp() {\n  document.getElementById('save-info-modal').classList.add('active');\n  document.querySelectorAll('.help-tab').forEach(t => {\n    t.style.background = 'var(--panel)';\n    t.style.color = 'var(--text-main)';\n  });\n  document.querySelectorAll('.help-tab-content').forEach(c => c.style.display = 'none');\n  const formatsTab = document.querySelector('.help-tab[data-tab=\"formats\"]');\n  if (formatsTab) {\n    formatsTab.style.background = 'var(--accent)';\n    formatsTab.style.color = 'var(--bg)';\n  }\n  document.getElementById('help-tab-formats').style.display = 'block';\n}\n\tfunction printTopology() {\n\t  const svg = document.getElementById('map');\n\t  if (!svg) { window.print(); return; }\n\t  const originalViewBox = svg.getAttribute('viewBox');\n\t  let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;\n\t  \n\t  Object.values(savedPositions).forEach(pos => {\n\t\tminX = Math.min(minX, pos.x - 100);\n\t\tminY = Math.min(minY, pos.y - 100);\n\t\tmaxX = Math.max(maxX, pos.x + 100);\n\t\tmaxY = Math.max(maxY, pos.y + 100);\n\t  });\n\t  \n\t  RECT_DATA.list.forEach(rect => {\n\t\tminX = Math.min(minX, rect.x);\n\t\tminY = Math.min(minY, rect.y);\n\t\tmaxX = Math.max(maxX, rect.x + rect.width);\n\t\tmaxY = Math.max(maxY, rect.y + rect.height);\n\t  });\n\t  \n\t  TEXT_DATA.list.forEach(text => {\n\t\tminX = Math.min(minX, text.x - 50);\n\t\tminY = Math.min(minY, text.y - 50);\n\t\tmaxX = Math.max(maxX, text.x + 200);\n\t\tmaxY = Math.max(maxY, text.y + 50);\n\t  });\n\n\t  const padding = 50;\n\t  minX -= padding;\n\t  minY -= padding;\n\t  maxX += padding;\n\t  maxY += padding;\n\n\t  if (minX !== Infinity) {\n\t\tsvg.setAttribute('viewBox', `${minX} ${minY} ${maxX - minX} ${maxY - minY}`);\n\t  }\n\n\t  window.print();\n\n\t  setTimeout(() => {\n\t\tsvg.setAttribute('viewBox', originalViewBox);\n\t  }, 1000);\n\t}\n\tfunction exportJSONFile() {\n\t  const data = captureTheQuickening();\n\t  const jsonStr = JSON.stringify(data, null, 2);\n\t  const blob = new Blob([jsonStr], { type: \"application/json\" });\n\t  const url = URL.createObjectURL(blob);\n\t  const a = document.createElement(\"a\");\n\t  a.href = url;\n\t  const safeTitle = (PAGE_STATE.title || \"network-topology\").toLowerCase().replace(/[^a-z0-9\\-]+/g, \"-\");\n\t  a.download = `${safeTitle}.json`;\n\t  a.click();\n\t  URL.revokeObjectURL(url);\n\t  logAuditEvent(\"export\", `Exported JSON: ${a.download}`);\n\t}\n\tfunction exportCSV() {\n\t  const safeTitle = (PAGE_STATE.title || 'topology').toLowerCase().replace(/[^a-z0-9]+/g, '-');\n\t  const timestamp = new Date().toISOString();\n  const config = captureTheQuickening();\n\t  let csv = `#THEONEFILE_CONFIG:${JSON.stringify(config)}\\n`;\n\t  csv += `#\\n# ${PAGE_STATE.title || 'Network Topology'} - Node List\\n`;\n\t  csv += `# Exported from The One File on ${timestamp}\\n`;\n\t  const headers = ['name','ip','role','shape','tags','layer','mac','rackUnit','uHeight','assignedRack','rackCapacity','isRack','locked','groupId','x','y','size','notes','styles'];\n\t  csv += headers.join(',') + '\\n';\n\t  Object.entries(NODE_DATA).forEach(([id, node]) => {\n\t\tconst pos = savedPositions[id] || { x: 0, y: 0 };\n\t\tconst size = savedSizes[id] || 50;\n\t\tconst styles = savedStyles[id] ? JSON.stringify(savedStyles[id]) : '';\n\t\tconst row = [\n\t\t  csvEscape(node.name || ''), csvEscape(node.ip || ''), csvEscape(node.role || ''),\n\t\t  node.shape || 'circle', csvEscape((node.tags || []).join(';')), node.layer || 'physical',\n\t\t  csvEscape(node.mac || ''), csvEscape(node.rackUnit || ''), node.uHeight || '1',\n\t\t  node.assignedRack || '', node.rackCapacity || '', node.isRack ? 'true' : 'false',\n\t\t  node.locked ? 'true' : 'false', node.groupId || '', Math.round(pos.x), Math.round(pos.y),\n\t\t  size, csvEscape((node.notes || []).join('|')), csvEscape(styles)\n\t\t];\n\t\tcsv += row.join(',') + '\\n';\n\t  });\n\t  const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });\n\t  const url = URL.createObjectURL(blob);\n\t  const a = document.createElement('a');\n\t  a.href = url;\n\t  a.download = `${safeTitle}.csv`;\n\t  a.click();\n\t  URL.revokeObjectURL(url);\n\t  logAuditEvent('export', `Exported CSV: ${a.download}`);\n\t}\n\tfunction csvEscape(val) {\n\t  if (val === null || val === undefined) return '';\n\t  const str = String(val);\n\t  if (str.includes(',') || str.includes('\"') || str.includes('\\n') || str.includes('\\r')) {\n\t\treturn '\"' + str.replace(/\"/g, '\"\"') + '\"';\n\t  }\n\t  return str;\n\t}\n\tdocument.getElementById('import-csv-file').addEventListener('change', async (e) => {\n\t  const file = e.target.files[0];\n\t  if (!file) return;\n\t  e.target.value = '';\n\t  try {\n\t\tconst text = await file.text();\n\t\tconst lines = text.split(/\\r?\\n/);\n\t\tlet config = null;\n\t\tlet dataLines = [];\n\t\tlet headers = null;\n\t\tfor (const line of lines) {\n\t\t  const trimmed = line.trim();\n\t\t  if (!trimmed) continue;\n\t\t  if (trimmed.startsWith('#THEONEFILE_CONFIG:')) {\n\t\t\ttry { config = JSON.parse(trimmed.substring(19)); } catch (err) { console.warn('Failed to parse CSV config:', err); }\n\t\t\tcontinue;\n\t\t  }\n\t\t  if (trimmed.startsWith('#')) continue;\n\t\t  if (!headers) { headers = parseCSVLine(trimmed).map(h => h.toLowerCase().trim()); continue; }\n\t\t  dataLines.push(trimmed);\n\t\t}\n\t\tif (!headers || dataLines.length === 0) { alert('CSV file has no data rows'); return; }\n\t\tconst nameIdx = headers.indexOf('name');\n\t\tif (nameIdx === -1) { alert('CSV must have a \"name\" column'); return; }\n\t\tconst nodes = dataLines.map(line => {\n\t\t  const values = parseCSVLine(line);\n\t\t  const node = {};\n\t\t  headers.forEach((h, idx) => { node[h] = values[idx] || ''; });\n\t\t  return node;\n\t\t});\n\t\tconst hasFullBackup = config && config.documentTabs;\n    const hasConfig = config && (config.pageState || config.page);\n    \n    let importMode = 'add'; \n    \n    if (hasFullBackup) {\n      const choice = confirm(\n        `This CSV contains a full backup.\\n\\n` +\n        `• ${nodes.length} nodes in CSV data\\n` +\n        `• ${config.documentTabs?.length || 1} tab(s) in backup\\n` +\n        `• ${config.edgeData?.list?.length || 0} connections in backup\\n\\n` +\n        `Click OK for FULL RESTORE (replace all data)\\n` +\n        `Click Cancel to just ADD the ${nodes.length} nodes`\n      );\n      if (choice) {\n        importMode = 'full';\n      }\n    } else {\n      let confirmMsg = `Import ${nodes.length} nodes from CSV?\\n\\n`;\n      confirmMsg += hasConfig ? 'This will ADD nodes and RESTORE settings/theme.\\n' : 'This will ADD nodes to your existing topology.\\n';\n      confirmMsg += '\\nNote: CSV does not include connections, zones, or text labels.';\n      if (!confirm(confirmMsg)) return;\n    }\n    \n    pushUndo('import csv');\n    \n    if (importMode === 'full' && hasFullBackup) {\n      NODE_DATA = config.nodeData || {};\n      EDGE_DATA = config.edgeData || { list: [] };\n      EDGE_LEGEND = config.edgeLegend || {};\n      RECT_DATA = config.rectData || { list: [] };\n      TEXT_DATA = config.textData || { list: [] };\n      savedPositions = config.nodePositions || {};\n      savedSizes = config.nodeSizes || {};\n      savedStyles = config.nodeStyles || {};\n      if (config.page) {\n        PAGE_STATE = Object.assign({}, DEFAULT_PAGE_STATE, config.page);\n        wieldThePower();\n      }\n      if (config.canvas) {\n        canvasState.zoom = config.canvas.zoom || 1;\n        canvasState.panX = config.canvas.panX || 0;\n        canvasState.panY = config.canvas.panY || 0;\n      }\n      if (config.page?.title) {\n        document.title = config.page.title;\n        document.querySelector(\".editable-page-title\").textContent = config.page.title;\n      }\n      if (config.documentTabs) {\n        documentTabs = config.documentTabs;\n        currentTabIndex = config.currentTabIndex || 0;\n      }\n      if (config.savedStyleSets) {\n        savedStyleSets = config.savedStyleSets;\n      }\n      if (config.savedTopologyView) {\n        savedTopologyView = config.savedTopologyView;\n      }\n      if (config.encryptedSections) {\n        encryptedSections = config.encryptedSections;\n      }\n      if (config.auditLog && Array.isArray(config.auditLog)) {\n        auditLog = config.auditLog;\n        try {\n          localStorage.setItem(AUDIT_STORAGE_KEY, JSON.stringify(auditLog));\n        } catch (e) {\n          console.warn(\"Failed to sync audit log to localStorage:\", e);\n        }\n      }\n      Object.values(NODE_DATA).forEach(node => {\n        if (!node.tags) node.tags = [];\n        if (!node.notes) node.notes = [];\n      });\n      EDGE_DATA.list.forEach(edge => {\n        if (!edge.notes) edge.notes = [];\n      });\n      forgeTheTopology();\n      if (typeof forgeTheLegend === 'function') forgeTheLegend();\n      updateViewBox();\n      logAuditEvent('import', `Imported CSV (full restore): ${file.name}`);\n      alert(`Full restore complete from CSV backup`);\n      return;\n    }\n\t\n\t\tif (hasConfig) {\n\t\t  Object.assign(PAGE_STATE, config.pageState || config.page);\n\t\t  if (config.canvasView || config.canvas) {\n\t\t\tcanvasState.zoom = (config.canvasView || config.canvas).zoom || 1;\n\t\t\tcanvasState.panX = (config.canvasView || config.canvas).panX || 0;\n\t\t\tcanvasState.panY = (config.canvasView || config.canvas).panY || 0;\n\t\t  }\n\t\t  if (config.legend || config.edgeLegend) Object.assign(EDGE_LEGEND, config.legend || config.edgeLegend);\n\t\t  wieldThePower();\n\t\t}\n\t\tlet gridX = 200, gridY = 200;\n\t\tconst spacing = 150;\n\t\tconst perRow = Math.ceil(Math.sqrt(nodes.length));\n\t\tlet gridIndex = 0;\n\t\tnodes.forEach((n) => {\n\t\t  let baseId = (n.name || 'node').toLowerCase().replace(/[^a-z0-9]+/g, '-');\n\t\t  if (!baseId) baseId = 'node';\n\t\t  let nodeId = baseId;\n\t\t  let counter = 1;\n\t\t  while (NODE_DATA[nodeId]) { nodeId = `${baseId}-${counter}`; counter++; }\n\t\t  NODE_DATA[nodeId] = {\n\t\t\tname: n.name || 'Unnamed', ip: n.ip || '', role: n.role || '', shape: n.shape || 'circle',\n\t\t\ttags: n.tags ? n.tags.split(';').map(t => t.trim()).filter(t => t) : [],\n\t\t\tnotes: n.notes ? n.notes.split('|').map(t => t.trim()).filter(t => t) : [],\n\t\t\tlayer: n.layer || 'physical', mac: n.mac || '', rackUnit: n.rackunit || '',\n\t\t\tuHeight: n.uheight || '1', assignedRack: n.assignedrack || '', rackCapacity: n.rackcapacity || '',\n\t\t\tisRack: n.israck === 'true', locked: n.locked === 'true', groupId: n.groupid || null\n\t\t  };\n\t\t  const hasPosition = n.x && n.y && !isNaN(parseFloat(n.x)) && !isNaN(parseFloat(n.y));\n\t\t  if (hasPosition) {\n\t\t\tsavedPositions[nodeId] = { x: parseFloat(n.x), y: parseFloat(n.y) };\n\t\t  } else {\n\t\t\tconst row = Math.floor(gridIndex / perRow);\n\t\t\tconst col = gridIndex % perRow;\n\t\t\tsavedPositions[nodeId] = { x: gridX + col * spacing, y: gridY + row * spacing };\n\t\t\tgridIndex++;\n\t\t  }\n\t\t  if (n.size && !isNaN(parseFloat(n.size))) savedSizes[nodeId] = parseFloat(n.size);\n\t\t  if (n.styles) { try { savedStyles[nodeId] = JSON.parse(n.styles); } catch (err) {} }\n\t\t});\n\t\tforgeTheTopology();\n\t\tupdateViewBox();\n\t\tlogAuditEvent('import', `Imported CSV: ${file.name} (${nodes.length} nodes)`);\n\t\talert(`Successfully imported ${nodes.length} nodes`);\n\t  } catch (err) {\n\t\tconsole.error('CSV import error:', err);\n\t\talert('Failed to import CSV: ' + err.message);\n\t  }\n\t});\n\tfunction parseCSVLine(line) {\n\t  const result = [];\n\t  let current = '';\n\t  let inQuotes = false;\n\t  for (let i = 0; i < line.length; i++) {\n\t\tconst char = line[i];\n\t\tif (char === '\"') {\n\t\t  if (inQuotes && line[i + 1] === '\"') { current += '\"'; i++; }\n\t\t  else { inQuotes = !inQuotes; }\n\t\t} else if (char === ',' && !inQuotes) { result.push(current); current = ''; }\n\t\telse { current += char; }\n\t  }\n\t  result.push(current);\n\t  return result;\n\t}\n\tfunction exportMarkdown() {\n  const safeTitle = (PAGE_STATE.title || 'topology').toLowerCase().replace(/[^a-z0-9]+/g, '-');\n  const timestamp = new Date().toISOString();\n  const title = PAGE_STATE.title || 'Network Topology';\n  const fullData = captureTheQuickening();\n  const config = fullData;\n\t  let md = `<!--THEONEFILE_CONFIG\\n${JSON.stringify(config, null, 2)}\\nTHEONEFILE_CONFIG-->\\n\\n`;\n\t  md += `# ${title}\\n\\n> Exported from The One File on ${timestamp}\\n\\n`;\n\t  md += `## Legend\\n\\n`;\n\t  if (Object.keys(EDGE_LEGEND).length > 0) {\n\t\tObject.entries(EDGE_LEGEND).forEach(([color, label]) => { md += `- ${color}: ${label}\\n`; });\n\t  } else { md += `_No legend entries_\\n`; }\n\t  md += '\\n## Nodes\\n\\n';\n\t  Object.entries(NODE_DATA).forEach(([id, node]) => {\n\t\tconst pos = savedPositions[id] || { x: 0, y: 0 };\n\t\tconst size = savedSizes[id] || 50;\n\t\tconst styles = savedStyles[id] || null;\n\t\tmd += `### ${id}\\n`;\n\t\tmd += `- **Name:** ${node.name || ''}\\n- **IP:** ${node.ip || ''}\\n- **Role:** ${node.role || ''}\\n`;\n\t\tmd += `- **Shape:** ${node.shape || 'circle'}\\n- **Tags:** ${(node.tags || []).join('; ') || '_none_'}\\n`;\n\t\tmd += `- **Layer:** ${node.layer || 'physical'}\\n- **MAC:** ${node.mac || ''}\\n`;\n\t\tmd += `- **Rack Unit:** ${node.rackUnit || ''}\\n- **U Height:** ${node.uHeight || '1'}\\n`;\n\t\tmd += `- **Assigned Rack:** ${node.assignedRack || ''}\\n- **Rack Capacity:** ${node.rackCapacity || ''}\\n`;\n\t\tmd += `- **Is Rack:** ${node.isRack ? 'true' : 'false'}\\n- **Locked:** ${node.locked ? 'true' : 'false'}\\n`;\n\t\tmd += `- **Group ID:** ${node.groupId || ''}\\n- **Position:** ${Math.round(pos.x)}, ${Math.round(pos.y)}\\n- **Size:** ${size}\\n`;\n\t\tif (node.notes && node.notes.length > 0) { md += `- **Notes:**\\n`; node.notes.forEach(note => { md += `  - ${note.replace(/\\n/g, '<br>')}\\n`; }); }\n\t\tif (styles) { md += `- **Styles:** \\`${JSON.stringify(styles)}\\`\\n`; }\n\t\tmd += '\\n';\n\t  });\n\t  md += `## Connections\\n\\n`;\n\t  if (EDGE_DATA.list && EDGE_DATA.list.length > 0) {\n\t\tEDGE_DATA.list.forEach(edge => {\n\t\t  const fromPort = edge.fromPort ? ` (${edge.fromPort})` : '';\n\t\t  const toPort = edge.toPort ? ` (${edge.toPort})` : '';\n\t\t  md += `- ${edge.from}${fromPort} --> ${edge.to}${toPort}\\n`;\n\t\t  md += `  - **ID:** ${edge.id}\\n  - **Label:** ${(edge.label || '').replace(/\\n/g, '<br>')}\\n  - **Color:** ${edge.color || ''}\\n`;\n\t\t  md += `  - **Width:** ${edge.width || 4}\\n  - **Direction:** ${edge.direction || 'none'}\\n`;\n\t\t  md += `  - **Routing:** ${edge.routing || 'curved'}\\n  - **Type:** ${edge.type || 'main'}\\n`;\n\t\t  md += `  - **Line Style:** ${edge.lineStyle || 'solid'}\\n  - **Group ID:** ${edge.groupId || ''}\\n`;\n\t\t  if (edge.points && edge.points.length > 0) { md += `  - **Points:** ${edge.points.map(p => `${Math.round(p.x)},${Math.round(p.y)}`).join(' ')}\\n`; }\n\t\t  if (edge.notes && edge.notes.length > 0) { md += `  - **Notes:**\\n`; edge.notes.forEach(note => { md += `    - ${note.replace(/\\n/g, '<br>')}\\n`; }); }\n\t\t  md += '\\n';\n\t\t});\n\t  } else { md += `_No connections_\\n\\n`; }\n\t  md += `## Zones\\n\\n`;\n\t  if (RECT_DATA.list && RECT_DATA.list.length > 0) {\n\t\tRECT_DATA.list.forEach(rect => {\n\t\t  md += `### ${rect.id}\\n`;\n\t\t  md += `- **Position:** ${Math.round(rect.x)}, ${Math.round(rect.y)}\\n- **Size:** ${Math.round(rect.width)} x ${Math.round(rect.height)}\\n`;\n\t\t  md += `- **Color:** ${rect.color || ''}\\n- **Style:** ${rect.style || 'filled'}\\n- **Line Style:** ${rect.lineStyle || 'solid'}\\n`;\n\t\t  md += `- **Border Color:** ${rect.borderColor || ''}\\n- **Border Width:** ${rect.borderWidth !== undefined ? rect.borderWidth : 2}\\n`;\n\t\t  if (rect.notes && rect.notes.length > 0) { md += `- **Notes:**\\n`; rect.notes.forEach(note => { md += `  - ${note.replace(/\\n/g, '<br>')}\\n`; }); }\n\t\t  md += '\\n';\n\t\t});\n\t  } else { md += `_No zones_\\n\\n`; }\n\t  md += `## Text Labels\\n\\n`;\n\t  if (TEXT_DATA.list && TEXT_DATA.list.length > 0) {\n\t\tTEXT_DATA.list.forEach(text => {\n\t\t  md += `### ${text.id}\\n`;\n\t\t  md += `- **Content:** ${(text.content || '').replace(/\\n/g, '<br>')}\\n- **Position:** ${Math.round(text.x)}, ${Math.round(text.y)}\\n`;\n\t\t  md += `- **Font Size:** ${text.fontSize || 18}\\n- **Color:** ${text.color || '#e2e8f0'}\\n`;\n\t\t  md += `- **Font Weight:** ${text.fontWeight || 'normal'}\\n- **Font Style:** ${text.fontStyle || 'normal'}\\n`;\n\t\t  md += `- **Text Align:** ${text.textAlign || 'start'}\\n- **Text Decoration:** ${text.textDecoration || 'none'}\\n`;\n\t\t  md += `- **Background Color:** ${text.bgColor || '#000000'}\\n- **Background Enabled:** ${text.bgEnabled ? 'true' : 'false'}\\n`;\n\t\t  md += `- **Opacity:** ${text.opacity !== undefined ? text.opacity : 1}\\n\\n`;\n\t\t});\n\t  } else { md += `_No text labels_\\n\\n`; }\n\t  const blob = new Blob([md], { type: 'text/markdown;charset=utf-8;' });\n\t  const url = URL.createObjectURL(blob);\n\t  const a = document.createElement('a');\n\t  a.href = url;\n\t  a.download = `${safeTitle}.md`;\n\t  a.click();\n\t  URL.revokeObjectURL(url);\n\t  logAuditEvent('export', `Exported Markdown: ${a.download}`);\n\t}\n\t\tdocument.getElementById('import-markdown-file').addEventListener('change', async (e) => {\n\t\t  const file = e.target.files[0];\n\t\t  if (!file) return;\n\t\t  e.target.value = '';\n\t\t  try {\n    const text = await file.text();\n    let config = null;\n    const configMatch = text.match(/<!--THEONEFILE_CONFIG\\s*([\\s\\S]*?)\\s*THEONEFILE_CONFIG-->/);\n    if (configMatch) { try { config = JSON.parse(configMatch[1].trim()); } catch (err) { console.warn('Failed to parse Markdown config:', err); } }\n    const nodes = {}, positions = {}, sizes = {}, styles = {}, edges = [], rects = [], texts = [], legend = {};\n    const sections = text.split(/^## /m);\n    sections.forEach(section => {\n      const lines = section.trim().split('\\n');\n      const sectionTitle = lines[0]?.trim().toLowerCase();\n      if (sectionTitle === 'legend') {\n        lines.slice(1).forEach(line => {\n          const match = line.match(/^- (#[a-fA-F0-9]{3,8}):\\s*(.+)$/);\n          if (match) legend[match[1]] = match[2].trim();\n        });\n      } else if (sectionTitle === 'nodes') {\n        let currentNodeId = null, currentNode = null, inNotes = false;\n        lines.slice(1).forEach(line => {\n          const nodeMatch = line.match(/^### (.+)$/);\n          if (nodeMatch) {\n            if (currentNodeId && currentNode) nodes[currentNodeId] = currentNode;\n            currentNodeId = nodeMatch[1].trim();\n            currentNode = { tags: [], notes: [] };\n            inNotes = false;\n            return;\n          }\n          if (!currentNode) return;\n          if (inNotes && line.match(/^\\s+-\\s+(.+)$/)) {\n            const noteMatch = line.match(/^\\s+-\\s+(.+)$/);\n            if (noteMatch) currentNode.notes.push(noteMatch[1].trim().replace(/<br>/g, '\\n'));\n            return;\n          }\n          inNotes = false;\n          const propMatch = line.match(/^- \\*\\*(.+?):\\*\\*\\s*(.*)$/);\n          if (propMatch) {\n            const key = propMatch[1].trim().toLowerCase(), val = propMatch[2].trim();\n            switch (key) {\n              case 'name': currentNode.name = val; break;\n              case 'ip': currentNode.ip = val; break;\n              case 'role': currentNode.role = val; break;\n              case 'shape': currentNode.shape = val; break;\n              case 'tags': currentNode.tags = val === '_none_' ? [] : val.split(';').map(t => t.trim()).filter(t => t); break;\n              case 'layer': currentNode.layer = val; break;\n              case 'mac': currentNode.mac = val; break;\n              case 'rack unit': currentNode.rackUnit = val; break;\n              case 'u height': currentNode.uHeight = val; break;\n              case 'assigned rack': currentNode.assignedRack = val; break;\n              case 'rack capacity': currentNode.rackCapacity = val; break;\n              case 'is rack': currentNode.isRack = val === 'true'; break;\n              case 'locked': currentNode.locked = val === 'true'; break;\n              case 'group id': currentNode.groupId = val || null; break;\n              case 'position':\n                const posMatch = val.match(/(-?\\d+),\\s*(-?\\d+)/);\n                if (posMatch) positions[currentNodeId] = { x: parseFloat(posMatch[1]), y: parseFloat(posMatch[2]) };\n                break;\n              case 'size': sizes[currentNodeId] = parseFloat(val) || 50; break;\n              case 'notes': inNotes = true; break;\n              case 'styles':\n                const stylesMatch = val.match(/`(.+)`/);\n                if (stylesMatch) { try { styles[currentNodeId] = JSON.parse(stylesMatch[1]); } catch (err) {} }\n                break;\n            }\n          }\n        });\n        if (currentNodeId && currentNode) nodes[currentNodeId] = currentNode;\n         } else if (sectionTitle === 'connections') {\n        let currentEdge = null, inNotes = false;\n        lines.slice(1).forEach(line => {\n          const connMatch = line.match(/^- (.+?)\\s*(?:\\(([^)]*)\\))?\\s*-->\\s*(.+?)\\s*(?:\\(([^)]*)\\))?$/);\n          if (connMatch) {\n            if (currentEdge) edges.push(currentEdge);\n            currentEdge = { from: connMatch[1].trim(), to: connMatch[3].trim(), fromPort: connMatch[2] || '', toPort: connMatch[4] || '', notes: [] };\n            inNotes = false;\n            return;\n          }\n          if (!currentEdge) return;\n          if (inNotes && line.match(/^\\s+-\\s+(.+)$/)) {\n            const noteMatch = line.match(/^\\s+-\\s+(.+)$/);\n            if (noteMatch) currentEdge.notes.push(noteMatch[1].trim().replace(/<br>/g, '\\n'));\n            return;\n          }\n          inNotes = false;\n          const propMatch = line.match(/^\\s+- \\*\\*(.+?):\\*\\*\\s*(.*)$/);\n          if (propMatch) {\n            const key = propMatch[1].trim().toLowerCase(), val = propMatch[2].trim();\n            switch (key) {\n              case 'id': currentEdge.id = val; break;\n              case 'label': currentEdge.label = val.replace(/<br>/g, '\\n'); break;\n              case 'color': currentEdge.color = val; break;\n              case 'width': currentEdge.width = parseFloat(val) || 4; break;\n              case 'direction': currentEdge.direction = val; break;\n              case 'routing': currentEdge.routing = val; break;\n              case 'type': currentEdge.type = val; break;\n              case 'line style': currentEdge.lineStyle = val; break;\n              case 'group id': currentEdge.groupId = val || null; break;\n              case 'points':\n                currentEdge.points = val.split(' ').map(p => { const [x, y] = p.split(',').map(Number); return { x, y }; }).filter(p => !isNaN(p.x) && !isNaN(p.y));\n                break;\n              case 'notes': inNotes = true; break;\n            }\n          }\n        });\n        if (currentEdge) edges.push(currentEdge);\n      } else if (sectionTitle === 'zones') {\n        let currentRect = null, inNotes = false;\n        lines.slice(1).forEach(line => {\n          const rectMatch = line.match(/^### (.+)$/);\n          if (rectMatch) {\n            if (currentRect) rects.push(currentRect);\n            currentRect = { id: rectMatch[1].trim(), notes: [] };\n            inNotes = false;\n            return;\n          }\n          if (!currentRect) return;\n          if (inNotes && line.match(/^\\s+-\\s+(.+)$/)) {\n            const noteMatch = line.match(/^\\s+-\\s+(.+)$/);\n            if (noteMatch) currentRect.notes.push(noteMatch[1].trim().replace(/<br>/g, '\\n'));\n            return;\n          }\n          inNotes = false;\n          const propMatch = line.match(/^- \\*\\*(.+?):\\*\\*\\s*(.*)$/);\n          if (propMatch) {\n            const key = propMatch[1].trim().toLowerCase(), val = propMatch[2].trim();\n            switch (key) {\n              case 'position': const posMatch = val.match(/(-?\\d+),\\s*(-?\\d+)/); if (posMatch) { currentRect.x = parseFloat(posMatch[1]); currentRect.y = parseFloat(posMatch[2]); } break;\n              case 'size': const sizeMatch = val.match(/(\\d+)\\s*x\\s*(\\d+)/); if (sizeMatch) { currentRect.width = parseFloat(sizeMatch[1]); currentRect.height = parseFloat(sizeMatch[2]); } break;\n              case 'color': currentRect.color = val; break;\n              case 'style': currentRect.style = val; break;\n              case 'line style': currentRect.lineStyle = val; break;\n              case 'border color': currentRect.borderColor = val; break;\n              case 'border width': currentRect.borderWidth = parseFloat(val) || 2; break;\n              case 'notes': inNotes = true; break;\n            }\n          }\n        });\n        if (currentRect) rects.push(currentRect);\n      } else if (sectionTitle === 'text labels') {\n        let currentText = null;\n        lines.slice(1).forEach(line => {\n          const textMatch = line.match(/^### (.+)$/);\n          if (textMatch) {\n            if (currentText) texts.push(currentText);\n            currentText = { id: textMatch[1].trim() };\n            return;\n          }\n          if (!currentText) return;\n          const propMatch = line.match(/^- \\*\\*(.+?):\\*\\*\\s*(.*)$/);\n          if (propMatch) {\n            const key = propMatch[1].trim().toLowerCase(), val = propMatch[2].trim();\n            switch (key) {\n              case 'content': currentText.content = val.replace(/<br>/g, '\\n'); break;\n              case 'position': const posMatch = val.match(/(-?\\d+),\\s*(-?\\d+)/); if (posMatch) { currentText.x = parseFloat(posMatch[1]); currentText.y = parseFloat(posMatch[2]); } break;\n              case 'font size': currentText.fontSize = parseFloat(val) || 18; break;\n              case 'color': currentText.color = val; break;\n              case 'font weight': currentText.fontWeight = val; break;\n              case 'font style': currentText.fontStyle = val; break;\n              case 'text align': currentText.textAlign = val; break;\n              case 'text decoration': currentText.textDecoration = val; break;\n              case 'background color': currentText.bgColor = val; break;\n              case 'background enabled': currentText.bgEnabled = val === 'true'; break;\n              case 'opacity': currentText.opacity = parseFloat(val) || 1; break;\n            }\n          }\n        });\n        if (currentText) texts.push(currentText);\n      }\n    });\n    const nodeCount = Object.keys(nodes).length, edgeCount = edges.length, rectCount = rects.length, textCount = texts.length;\n    if (nodeCount === 0 && edgeCount === 0 && rectCount === 0 && textCount === 0) {\n      alert('No valid topology data found in Markdown file.\\n\\nMake sure the file was exported from The One File.');\n      return;\n    }\n    let confirmMsg = `Import Markdown topology?\\n\\n- ${nodeCount} nodes\\n- ${edgeCount} connections\\n- ${rectCount} zones\\n- ${textCount} text labels\\n\\nThis will REPLACE all current data.`;\n    if (!confirm(confirmMsg)) return;\n    pushUndo('import markdown');\n    if (config && config.documentTabs) {\n      if (config.page) { Object.assign(PAGE_STATE, config.page); wieldThePower(); }\n      if (config.canvas) {\n        canvasState.zoom = config.canvas.zoom || 1;\n        canvasState.panX = config.canvas.panX || 0;\n        canvasState.panY = config.canvas.panY || 0;\n      }\n      if (config.documentTabs) {\n        documentTabs = config.documentTabs;\n        currentTabIndex = config.currentTabIndex || 0;\n      }\n      if (config.savedTopologyView) {\n        savedTopologyView = config.savedTopologyView;\n      }\n      if (config.encryptedSections) {\n        encryptedSections = config.encryptedSections;\n      }\n      if (config.auditLog && Array.isArray(config.auditLog)) {\n        auditLog = config.auditLog;\n        try {\n          localStorage.setItem(AUDIT_STORAGE_KEY, JSON.stringify(auditLog));\n        } catch (e) {\n          console.warn(\"Failed to sync audit log to localStorage:\", e);\n        }\n      }\n    } else if (config && config.pageState) {\n      Object.assign(PAGE_STATE, config.pageState);\n      wieldThePower();\n      if (config.canvasView) {\n        canvasState.zoom = config.canvasView.zoom || 1;\n        canvasState.panX = config.canvasView.panX || 0;\n        canvasState.panY = config.canvasView.panY || 0;\n      }\n    }\n    Object.keys(NODE_DATA).forEach(k => delete NODE_DATA[k]);\n    Object.keys(savedPositions).forEach(k => delete savedPositions[k]);\n    Object.keys(savedSizes).forEach(k => delete savedSizes[k]);\n    Object.keys(savedStyles).forEach(k => delete savedStyles[k]);\n    EDGE_DATA.list = [];\n    RECT_DATA.list = [];\n    TEXT_DATA.list = [];\n    Object.keys(EDGE_LEGEND).forEach(k => delete EDGE_LEGEND[k]);\n    Object.assign(EDGE_LEGEND, legend);\n    Object.entries(nodes).forEach(([id, node]) => {\n      NODE_DATA[id] = { name: node.name || '', ip: node.ip || '', role: node.role || '', shape: node.shape || 'circle', tags: node.tags || [], notes: node.notes || [], mac: node.mac || '', rackUnit: node.rackUnit || '', uHeight: node.uHeight || '1', layer: node.layer || 'physical', assignedRack: node.assignedRack || '', rackCapacity: node.rackCapacity || '', isRack: node.isRack || false, locked: node.locked || false, groupId: node.groupId || null };\n      savedPositions[id] = positions[id] || { x: 500, y: 300 };\n      if (sizes[id]) savedSizes[id] = sizes[id];\n      if (styles[id]) savedStyles[id] = styles[id];\n    });\n    let skippedEdges = 0;\n    edges.forEach(edge => {\n      if (!NODE_DATA[edge.from] || !NODE_DATA[edge.to]) {\n        console.warn(`Skipping orphan edge: ${edge.from} -> ${edge.to}`);\n        skippedEdges++;\n        return;\n      }\n      EDGE_DATA.list.push({ id: edge.id || `edge-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, from: edge.from, to: edge.to, fromPort: edge.fromPort || '', toPort: edge.toPort || '', label: edge.label || '', color: edge.color || '', width: edge.width || 4, direction: edge.direction || 'none', routing: edge.routing || 'curved', type: edge.type || 'main', lineStyle: edge.lineStyle || 'solid', groupId: edge.groupId || null, points: edge.points || [], notes: edge.notes || [] });\n    });\n    if (skippedEdges > 0) console.warn(`Total skipped orphan edges: ${skippedEdges}`);\n    rects.forEach(rect => {\n      RECT_DATA.list.push({ id: rect.id, x: rect.x || 0, y: rect.y || 0, width: rect.width || 100, height: rect.height || 100, color: rect.color || '#f97316', style: rect.style || 'filled', lineStyle: rect.lineStyle || 'solid', borderColor: rect.borderColor || '', borderWidth: rect.borderWidth !== undefined ? rect.borderWidth : 2, notes: rect.notes || [] });\n    });\n    texts.forEach(text => {\n      TEXT_DATA.list.push({ id: text.id, x: text.x || 0, y: text.y || 0, content: text.content || '', fontSize: text.fontSize || 18, color: text.color || '#e2e8f0', fontWeight: text.fontWeight || 'normal', fontStyle: text.fontStyle || 'normal', textAlign: text.textAlign || 'start', textDecoration: text.textDecoration || 'none', bgColor: text.bgColor || '#000000', bgEnabled: text.bgEnabled || false, opacity: text.opacity !== undefined ? text.opacity : 1 });\n    });\n    forgeTheTopology();\n    if (typeof forgeTheLegend === 'function') forgeTheLegend();\n    updateViewBox();\n    logAuditEvent('import', `Imported Markdown: ${file.name} (${nodeCount} nodes, ${edgeCount} connections)`);\n    alert(`Successfully imported:\\n- ${nodeCount} nodes\\n- ${edgeCount} connections\\n- ${rectCount} zones\\n- ${textCount} text labels`);\n     } catch (err) {\n    console.error('Markdown import error:', err);\n    alert('Failed to import Markdown: ' + err.message);\n     }\n\t});\n\tdocument.getElementById('import-json-file').addEventListener('change', async (e) => {\n\t  const file = e.target.files[0];\n\t  if (!file) return;\n\t  e.target.value = '';\n\t  const existingInput = document.getElementById('import-data-file');\n\t  if (existingInput) {\n\t\tconst dt = new DataTransfer();\n\t\tdt.items.add(file);\n\t\texistingInput.files = dt.files;\n\t\texistingInput.dispatchEvent(new Event('change'));\n\t  }\n\t});\n\tdocument.getElementById('mobile-export-btn')?.addEventListener('click', () => {\n\t  document.getElementById('mobile-export-modal').classList.add('active');\n\t  document.getElementById('topbar-menu').classList.remove('open');\n\t});\n\tdocument.getElementById('mobile-import-btn')?.addEventListener('click', () => {\n\t  document.getElementById('mobile-import-modal').classList.add('active');\n\t  document.getElementById('topbar-menu').classList.remove('open');\n\t});\n\tdocument.getElementById('mobile-export-modal')?.addEventListener('click', (e) => {\n\t  if (e.target.id === 'mobile-export-modal') e.target.classList.remove('active');\n\t});\n\tdocument.getElementById('mobile-import-modal')?.addEventListener('click', (e) => {\n\t  if (e.target.id === 'mobile-import-modal') e.target.classList.remove('active');\n\t});\n    </script>\n  \n</body></html>"
  },
  {
    "path": "demos/password-protected/theonefile-networkening-corporate-demo.html",
    "content": "<!DOCTYPE html> \n <html lang=\"en\" style=\"--panel: #0b0e13; --panel-alt: #10141b; --accent: #4fd1c5; --danger: #f56565; --text-main: #e2e8f0; --text-soft: #94a3b8; --topbar-bg: rgba(9, 12, 20, 0.9); --topbar-border: #1f2533; --topbar-height: 103px; --sidebar-width: 350px; --mobile-footer-height: 40vh; --draw-toolbar-height: 45px; --sidebar-bg: #10141b; --btn-bg: #0b0e13; --btn-text: #e2e8f0; --tag-fill: #1e293b; --tag-text: #e2e8f0; --tag-border: #475569; --input-bg: #0b0e13; --input-text: #e2e8f0; --input-border: #1f2937; --input-font: Inter, system-ui, sans-serif; --input-font-size: 14px; --toolbar-bg: #0f172a; --toolbar-border: #1f2937; --toolbar-text: #94a3b8; --toolbar-btn-bg: #0b0e13; --toolbar-btn-text: #e2e8f0; --minimap-dots: #94a3b8; --canvas-hint-bg: #0f172a; --canvas-hint-color: #94a3b8; --node-fill: #1e293b; --node-stroke: #475569; --node-title: #e2e8f0; --node-sub: #94a3b8; --node-title-size: 41px; --node-sub-size: 27px; --node-font: monospace; --default-edge: #475569; --selection-handle: #f59e0b; --selection-handle-size: 8px; --group-indicator: #4fd1c5;\"><head>\n    <meta charset=\"UTF-8\">\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>The One File Corporate</title>\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <!--\n      * ==================================================================================\n      * The One File: The Networkening\n      * !!!!!!!!!!!!!!!!!!!NOTE: THIS IS THE ONLINE VERSION!!!!!!!!!!!!!!!!!!!!!!\n      * Online version uses 3 cdn calls from cdn.jsdelivr.net to display additional icons\n      * Since 3.0 Online version uses http as a form of ping to display uptime\n      * \"There can be only one\". A all in one file topology maker.\n      *\n      * This is your last backup when all others fail. A completely self contained\n      * network topology visualization tool that works as a single HTML file.\n      * Open it anywhere, anytime and the idea lives forever.\n      * ==================================================================================\n      -->\n    <style>\n      :root {\n      color-scheme: dark;\n      --bg: #050608;\n      --panel: #0b0e13;\n      --panel-alt: #10141b;\n      --accent: #4fd1c5;\n      --danger: #f56565;\n      --text-main: #e2e8f0;\n      --text-soft: #94a3b8;\n      --edge-main: #475569;\n      --node-min: 35px;\n      --node-max: 70px;\n      --topbar-bg: rgba(9, 12, 20, 0.9);\n      --topbar-border: #1f2533;\n      }\n\t  html, body, svg, .map-container {\n      touch-action: none;\n      }\n      * {\n      box-sizing: border-box;\n      user-select: none;\n      }\n      input,\n      textarea,\n      [contenteditable=\"true\"] {\n      user-select: text;\n      -webkit-user-select: text;\n      }\n      body {\n      margin: 0;\n      font-family: system-ui, sans-serif;\n      background: radial-gradient(circle at top, #1e2532 0, #050608 70%);\n      color: var(--text-main);\n      display: flex;\n      flex-direction: column;\n      height: 100vh;\n      overflow: hidden;\n      }\n      header {\n      padding: 0 20px;\n      height: var(--topbar-height, 52px);\n      min-height: var(--topbar-height, 52px);\n      background: var(--topbar-bg);\n      backdrop-filter: blur(6px);\n      display: flex;\n      justify-content: space-between;\n      align-items: center;\n      border-bottom: 1px solid var(--topbar-border);\n      gap: 16px;\n      }\n      .title-block {\n      display: flex;\n      flex-direction: column;\n      gap: 6px;\n      min-width: 0;\n      }\n      header h1 {\n      font-size: clamp(22px, 3vw, 32px);\n      margin: 0;\n      white-space: nowrap;\n      text-overflow: ellipsis;\n      overflow: hidden;\n      }\n      .editable-page-title {\n      cursor: pointer;\n      transition: opacity 0.2s;\n      }\n      .editable-page-title:hover {\n      opacity: 0.7;\n      }\n      .save-row {\n      display: flex;\n      align-items: center;\n      gap: 8px;\n      flex-wrap: wrap;\n      }\n\t  .toggle-switch{position:relative;display:inline-block;min-width:44px !important;height:24px;flex-shrink:0;vertical-align:middle;}\n\t\t.toggle-switch input{opacity:0;width:0;height:0;position:absolute;}\n\t\t.toggle-slider{position:absolute;cursor:pointer;top:0;left:0;right:0;bottom:0;background:#475569;transition:.25s;border-radius:24px;}\n\t\t.toggle-slider:before{position:absolute;content:\"\";height:18px;width:18px;left:3px;bottom:3px;background:#e2e8f0;transition:.25s;border-radius:50%;}\n\t\t.toggle-switch input:checked+.toggle-slider{background:var(--accent);}\n\t\t.toggle-switch input:checked+.toggle-slider:before{transform:translateX(20px);}\n\t\t.anim-zone-row{display:flex;justify-content:space-between;align-items:center;padding:6px 0;}\n\t\t.anim-zone-row label{color:var(--text-main);font-size:14px;}\n\t\t.anim-zone-section{font-size:11px;color:var(--text-soft);margin:12px 0 6px;text-transform:uppercase;letter-spacing:0.05em;border-bottom:1px solid var(--edge-main);padding-bottom:4px;}\n\t\t.anim-zone-header{font-size:12px;color:var(--accent);margin-bottom:10px;text-transform:uppercase;letter-spacing:0.05em;font-weight:600;}\n      .save-btn {\n      padding: 6px 12px;\n      background: var(--accent);\n      color: var(--bg);\n      border: none;\n      border-radius: 6px;\n      cursor: pointer;\n      font-size: 14px;\n      font-weight: 600;\n      white-space: nowrap;\n      }\n      .save-btn:hover {\n      opacity: 0.9;\n      }\n      .help-icon {\n      width: 22px;\n      height: 22px;\n      border-radius: 50%;\n      border: 1px solid var(--edge-main);\n      display: inline-flex;\n      align-items: center;\n      justify-content: center;\n      font-size: 14px;\n      cursor: pointer;\n      color: var(--text-soft);\n      background: rgba(15, 23, 42, 0.9);\n      flex-shrink: 0;\n      }\n      .help-icon:hover {\n      color: var(--accent);\n      border-color: var(--accent);\n      }\n      #settings-btn {\n      background: var(--btn-bg, var(--panel));\n      color: var(--btn-text, var(--text-main));\n      border: 1px solid var(--edge-main);\n      padding: 6px 12px;\n      border-radius: 6px;\n      cursor: pointer;\n      font-size: 16px;\n      flex-shrink: 0;\n      }\n      #settings-btn:hover {\n      background: var(--accent);\n      color: var(--bg);\n      }\n      .header-resizer {\n      position: absolute;\n      bottom: 0;\n      left: 0;\n      right: 0;\n      height: 6px;\n      background: transparent;\n      cursor: row-resize;\n      z-index: 1000;\n      transition: background 0.2s;\n      }\n      .header-resizer:hover {\n      background: rgba(79, 209, 197, 0.3);\n      }\n      .header-resizer.resizing {\n      background: rgba(79, 209, 197, 0.5);\n      }\n      .sidebar-resizer {\n      position: absolute;\n      left: 0;\n      top: 0;\n      bottom: 0;\n      width: 6px;\n      background: transparent;\n      cursor: col-resize;\n      z-index: 1000;\n      transition: background 0.2s;\n      }\n      .sidebar-resizer:hover {\n      background: rgba(79, 209, 197, 0.3);\n      }\n      .sidebar-resizer.resizing {\n      background: rgba(79, 209, 197, 0.5);\n      }\n      .mobile-footer-resizer {\n      position: absolute;\n      top: 0;\n      left: 0;\n      right: 0;\n      height: 6px;\n      background: transparent;\n      cursor: row-resize;\n      z-index: 1000;\n      transition: background 0.2s;\n      display: none;\n      }\n      .mobile-footer-resizer:hover {\n      background: rgba(79, 209, 197, 0.3);\n      }\n      .mobile-footer-resizer.resizing {\n      background: rgba(79, 209, 197, 0.5);\n      }\n      @media (max-width: 900px) {\n      .mobile-footer-resizer {\n      display: block;\n      height: 12px;\n      }\n      .sidebar-resizer {\n      display: none;\n      }\n      .header-resizer {\n      height: 12px;\n      }\n      }\n      @media (pointer: coarse) {\n      .header-resizer {\n      height: 16px;\n      }\n      .mobile-footer-resizer {\n      height: 16px;\n      }\n      .sidebar-resizer {\n      width: 16px;\n      }\n      }\n      .resizer-icon {\n      position: absolute;\n      opacity: 0.5;\n      transition: opacity 0.2s, transform 0.2s;\n      pointer-events: none;\n      fill: var(--accent);\n      }\n      .header-resizer .resizer-icon {\n      left: 50%;\n      top: 50%;\n      transform: translate(-50%, -50%);\n      }\n      .sidebar-resizer .resizer-icon {\n      left: 50%;\n      top: 50%;\n      transform: translate(-50%, -50%);\n      }\n      .mobile-footer-resizer .resizer-icon {\n      left: 50%;\n      top: 50%;\n      transform: translate(-50%, -50%);\n      }\n      .header-resizer:hover .resizer-icon,\n      .sidebar-resizer:hover .resizer-icon,\n      .mobile-footer-resizer:hover .resizer-icon {\n      opacity: 1;\n      }\n      .header-resizer.resizing .resizer-icon,\n      .sidebar-resizer.resizing .resizer-icon,\n      .mobile-footer-resizer.resizing .resizer-icon {\n      opacity: 1;\n      transform: translate(-50%, -50%) scale(1.2);\n      }\n      body.resizing {\n      user-select: none;\n      }\n      body.resizing * {\n      cursor: inherit !important;\n      pointer-events: none;\n      }\n      header {\n      position: relative;\n      }\n      .details-panel {\n      position: relative;\n      }\n      main {\n      display: grid;\n      grid-template-columns: 1fr var(--sidebar-width, 350px);\n      flex: 1;\n      }\n      main.sidebar-collapsed {\n      grid-template-columns: 1fr 0;\n      }\n      @media (max-width: 900px) {\n      main {\n      grid-template-columns: 1fr;\n      grid-template-rows: calc(100vh - var(--topbar-height, 52px) - var(--mobile-footer-height, 40vh)) var(--mobile-footer-height, 40vh);\n      }\n      main.sidebar-collapsed {\n      grid-template-rows: 1fr 0;\n      }\n      .details-panel {\n      max-height: var(--mobile-footer-height, 40vh);\n      height: 100%;\n      }\n      }\n      .topology-panel {\n      background: var(--panel);\n      border-right: 1px solid #111827;\n      position: relative;\n      overflow: hidden;\n      }\n      .topology-toolbar {\n      position: absolute;\n      top: 10px;\n      right: 10px;\n      display: none;\n      align-items: center;\n      gap: 8px;\n      padding: 6px 10px;\n      background: var(--toolbar-bg, rgba(15, 23, 42, 0.92));\n      border: 1px solid var(--toolbar-border, #1f2937);\n      border-radius: 6px;\n      z-index: 99;\n      font-size: 13px;\n      color: var(--toolbar-text, #94a3b8);\n      }\n      .topology-toolbar label {\n      color: var(--toolbar-text, var(--text-soft));\n      }\n      .topology-toolbar select {\n      padding: 4px 6px;\n      background: var(--toolbar-btn-bg, var(--panel));\n      border: 1px solid var(--toolbar-border, var(--edge-main));\n      border-radius: 4px;\n      color: var(--toolbar-btn-text, var(--text-main));\n      font-size: 13px;\n      cursor: pointer;\n      }\n      .topology-toolbar button {\n      padding: 4px 10px;\n      background: var(--toolbar-btn-bg, var(--panel));\n      color: var(--toolbar-btn-text, var(--text-main));\n      border: 1px solid var(--toolbar-border, var(--edge-main));\n      border-radius: 4px;\n      cursor: pointer;\n      font-size: 13px;\n      font-weight: 600;\n      }\n      .topology-toolbar button:hover {\n      background: var(--accent);\n      color: var(--bg);\n      }\n      .bulk-toolbar-desktop,\n      .bulk-toolbar-mobile {\n      position: fixed;\n      bottom: 20px;\n      left: 50%;\n      transform: translateX(-50%);\n      z-index: 9999;\n      }\n      .bulk-toolbar-desktop {\n      position: absolute;\n      top: 10px;\n      right: 10px;\n      bottom: auto;\n      left: auto;\n      transform: none;\n      }\n      @media (min-width: 768px) {\n      .bulk-toolbar-mobile {\n      display: none !important;\n      }\n      }\n      .bulk-action-btn {\n      padding: 16px;\n      background: var(--panel-main);\n      border: 1px solid var(--edge-main);\n      border-radius: 8px;\n      color: var(--text-main);\n      font-size: 24px;\n      cursor: pointer;\n      text-align: center;\n      line-height: 1.2;\n      }\n      .bulk-action-btn:active {\n      transform: scale(0.95);\n      background: var(--accent);\n      }\n      .draw-toolbar {\n      position: absolute;\n      top: 10px;\n      left: 10px;\n      display: flex;\n      align-items: center;\n      gap: 6px;\n      padding: 6px 8px;\n      background: var(--toolbar-bg, rgba(15, 23, 42, 0.92));\n      border: 1px solid var(--toolbar-border, #1f2937);\n      border-radius: 6px;\n      z-index: 99;\n      font-size: 13px;\n      pointer-events: auto;\n      color: var(--toolbar-text, #94a3b8);\n      }\n      .draw-toolbar button {\n      padding: 4px 8px;\n      background: var(--toolbar-btn-bg, var(--panel));\n      color: var(--toolbar-btn-text, var(--text-main));\n      border: 1px solid var(--toolbar-border, var(--edge-main));\n      border-radius: 4px;\n      cursor: pointer;\n      font-size: 13px;\n      }\n      .draw-toolbar button:hover {\n      background: var(--accent);\n      color: var(--bg);\n      }\n      .draw-toolbar input[type=\"color\"] {\n      width: 30px;\n      height: 22px;\n      border-radius: 4px;\n      border: 1px solid var(--edge-main);\n      padding: 0;\n      background: transparent;\n      cursor: pointer;\n      }\n      .draw-toolbar select {\n      padding: 4px 6px;\n      background: var(--toolbar-btn-bg, var(--panel));\n      border: 1px solid var(--toolbar-border, var(--edge-main));\n      border-radius: 4px;\n      color: var(--toolbar-btn-text, var(--text-main));\n      font-size: 13px;\n      cursor: pointer;\n      }\n      .legend-container {\n      position: absolute;\n      left: 10px;\n      bottom: 10px;\n      display: flex;\n      flex-direction: column;\n      gap: 6px;\n      padding: 8px 10px;\n      background: var(--toolbar-bg, rgba(15, 23, 42, 0.92));\n      border: 1px solid var(--toolbar-border, #1f2937);\n      border-radius: 6px;\n      font-size: 12px;\n      z-index: 20;\n      max-width: 260px;\n      pointer-events: auto;\n      color: var(--toolbar-text, #94a3b8);\n      }\n      .legend-container {\n      padding-right: 22px;\n      }\n      @media (max-width: 900px) {\n      .legend-container {\n      max-height: calc(100vh - var(--topbar-height, 100px) - 120px);\n      overflow-y: auto;\n      }\n      }\n      @media (max-width: 900px) {\n      .bulk-toolbar-desktop,\n      .topology-toolbar {\n      display: none !important;\n      }\n      }\n      .legend-close-btn {\n      position: absolute;\n      top: 5px;\n      right: 5px;\n      width: 18px;\n      height: 18px;\n      border-radius: 999px;\n      border: 1px solid var(--edge-main);\n      background: var(--panel-alt);\n      color: var(--text-soft);\n      font-size: 11px;\n      display: flex;\n      align-items: center;\n      justify-content: center;\n      cursor: pointer;\n      }\n      .legend-close-btn:hover {\n      background: var(--danger);\n      color: #fff;\n      }\n      .legend-mini-btn {\n      position: absolute;\n      left: 10px;\n      padding: 4px 8px;\n      border-radius: 4px;\n      border: 1px solid var(--toolbar-border, #1f2937);\n      background: var(--toolbar-bg, rgba(15, 23, 42, 0.92));\n      color: var(--toolbar-btn-text, var(--text-main));\n      font-size: 11px;\n      cursor: pointer;\n      z-index: 20;\n      display: none;\n      transition: all 0.2s;\n      }\n      .legend-mini-btn:hover,\n      .legend-mini-btn:active {\n      background: var(--accent);\n      color: var(--bg);\n      border-color: var(--accent);\n      }\n      .legend-title {\n      font-size: 11px;\n      text-transform: uppercase;\n      letter-spacing: 0.06em;\n      color: var(--toolbar-text, var(--text-soft));\n      margin-bottom: 2px;\n      }\n\t  .fov-group {\n        transition: opacity 0.3s ease;\n      }\n      g[data-node-id]:not(:hover) .fov-group {\n        opacity: 0.7;\n      }\n      g[data-node-id]:hover .fov-group {\n        opacity: 1;\n      }\n      .legend-item {\n      display: flex;\n      align-items: center;\n      gap: 8px;\n      }\n      .legend-swatch {\n      width: 14px;\n      height: 14px;\n      border-radius: 3px;\n      border: 1px solid #020617;\n      flex-shrink: 0;\n      }\n      .legend-label {\n      outline: none;\n      cursor: text;\n      flex: 1;\n      min-width: 60px;\n      user-select: text;\n      -webkit-user-select: text;\n      }\n      .legend-label.editing {\n      border-bottom: 1px dashed var(--accent);\n      }\n      .canvas-viewport {\n      position: absolute;\n      top: 0;\n      left: 0;\n      right: 0;\n      bottom: 0;\n      overflow: hidden;\n      }\n      .canvas-viewport.panning {\n      cursor: grabbing !important;\n      }\n      .canvas-viewport.panning * {\n      cursor: grabbing !important;\n      }\n      .canvas-viewport svg {\n      width: 100%;\n      height: 100%;\n      display: block; \n      }\n      .zoom-toolbar {\n      display: flex;\n      gap: 4px;\n      padding: 6px;\n      background: var(--toolbar-bg, rgba(15, 23, 42, 0.92));\n      border: 1px solid var(--toolbar-border, #1f2937);\n      border-radius: 6px;\n      }\n      .zoom-toolbar button {\n      width: 32px;\n      height: 32px;\n      padding: 0;\n      background: var(--toolbar-btn-bg, var(--panel));\n      color: var(--toolbar-btn-text, var(--text-main));\n      border: 1px solid var(--toolbar-border, var(--edge-main));\n      border-radius: 4px;\n      cursor: pointer;\n      font-size: 18px;\n      font-weight: bold;\n      display: flex;\n      align-items: center;\n      justify-content: center;\n      transition: all 0.15s;\n      }\n      .zoom-toolbar button:hover {\n      background: var(--accent);\n      color: var(--bg);\n      }\n      .zoom-toolbar .zoom-level {\n      font-size: 11px;\n      color: var(--toolbar-text, var(--text-soft));\n      text-align: center;\n      padding: 2px 0;\n      min-width: 32px;\n      }\n      .zoom-toolbar .divider {\n      height: 1px;\n      background: var(--toolbar-border, var(--edge-main));\n      margin: 2px 0;\n      }\n      .minimap-zoom-wrapper {\n      position: absolute;\n      bottom: 10px;\n      right: 10px;\n      z-index: 99;\n      }\n      .minimap-container {\n      background: var(--toolbar-bg, rgba(15, 23, 42, 0.92));\n      border: 1px solid var(--toolbar-border, #1f2937);\n      border-radius: 6px;\n      overflow: hidden;\n      margin-bottom: 4px;\n      }\n      .minimap-container svg {\n      width: 100%;\n      height: 100%;\n      }\n\t.minimap-container,\n\t.node-panel,\n\t.edge-panel,\n\t#canvas-viewport {\n\t  contain: layout paint;\n\t}\t\n\t.low-zoom .node-group .node-circle,\n\t.low-zoom .node-group.active .node-circle,\n\t.low-zoom .node-group.selected .node-circle {\n\t  filter: none !important;\n\t  animation: none !important;\n\t}\t\n      .minimap-viewport {\n      fill: rgba(79, 209, 197, 0.2);\n      stroke: var(--accent);\n      stroke-width: 2;\n      cursor: move;\n      }\n      .minimap-node {\n      fill: var(--minimap-dots, var(--text-soft));\n      }\n      .minimap-edge {\n      stroke: var(--edge-main);\n      stroke-width: 8px;\n      fill: none;\n      }\n      .minimap-wall {\n      pointer-events: none;\n      }\n      .minimap-rect {\n      pointer-events: none;\n      }\n\t  .minimap-close-btn {\n      position: absolute;\n      top: 2px;\n      right: 2px;\n      width: 18px;\n      height: 18px;\n      background: var(--toolbar-btn-bg, rgba(15, 23, 42, 0.9));\n      border: 1px solid var(--toolbar-border, var(--edge-main));\n      border-radius: 3px;\n      color: var(--toolbar-text, var(--text-soft));\n      font-size: 11px;\n      cursor: pointer;\n      display: flex;\n      align-items: center;\n      justify-content: center;\n      z-index: 10;\n      padding: 0;\n      line-height: 1;\n      }\n      .minimap-close-btn:hover {\n      background: var(--danger);\n      color: white;\n      border-color: var(--danger);\n      }\n\t  .toolbar-close-btn {\n      width: 24px;\n      height: 24px;\n      background: var(--toolbar-btn-bg, rgba(15, 23, 42, 0.9));\n      border: 1px solid var(--toolbar-border, var(--edge-main));\n      border-radius: 4px;\n      color: var(--toolbar-text, var(--text-soft));\n      font-size: 12px;\n      cursor: pointer;\n      display: flex;\n      align-items: center;\n      justify-content: center;\n      padding: 0;\n      line-height: 1;\n      flex-shrink: 0;\n      }\n      .toolbar-close-btn:hover {\n      background: var(--danger);\n      color: white;\n      border-color: var(--danger);\n      }\n\t  .canvas-hint {\n      position: absolute;\n      top: 50px;\n      left: 50%;\n      transform: translateX(-50%);\n      padding: 8px 16px;\n      background: var(--canvas-hint-bg, rgba(15, 23, 42, 0.92));\n      border: 1px solid var(--toolbar-border, #1f2937);\n      border-radius: 6px;\n      font-size: 12px;\n      color: var(--canvas-hint-color, var(--text-soft));\n      z-index: 18;\n      pointer-events: none;\n      opacity: 0;\n      transition: opacity 0.3s;\n      }\n      .canvas-hint.visible {\n      opacity: 1;\n      pointer-events: auto;\n      }\n      .edge {\n      stroke: var(--edge-main);\n      stroke-width: 4;\n      opacity: 0.75;\n      transition: 0.25s ease-in-out;\n      cursor: pointer;\n      }\n      .edge.backup {\n      stroke: var(--danger);\n      stroke-width: 5;\n      }\n      .edge.active {\n      opacity: 1;\n      stroke-width: 7;\n      filter: drop-shadow(0 0 8px var(--accent, #4fd1c5));\n      }\n      .rect-group.active .rect-shape {\n      filter: drop-shadow(0 0 10px var(--accent, #4fd1c5));\n      animation: pulse 1.2s infinite ease-in-out;\n      }\n      .text-element.active {\n      filter: drop-shadow(0 0 10px var(--accent, #4fd1c5));\n      animation: pulse 1.2s infinite ease-in-out;\n      }\n      .free-preview {\n      fill: none;\n      stroke-dasharray: 4 4;\n      pointer-events: none;\n      }\n@keyframes edge-flow-arrow {\n  0% { offset-distance: 0%; }\n  100% { offset-distance: 100%; }\n}\n@keyframes edge-flow-arrow-reverse {\n  0% { offset-distance: 100%; }\n  100% { offset-distance: 0%; }\n}\n.edge-arrow-forward {\n  offset-rotate: auto;\n  animation: edge-flow-arrow 1.5s linear infinite;\n}\n.edge-arrow-backward {\n  offset-rotate: auto 180deg;\n  animation: edge-flow-arrow-reverse 1.5s linear infinite;\n}\n      .free-point {\n      fill: #e5e7eb;\n      stroke: #0f172a;\n      stroke-width: 1.5;\n      cursor: grab;\n      }\n      .node-circle {\n      fill: var(--node-fill, #1e293b);\n      stroke: var(--node-stroke, #475569);\n      stroke-width: 2;\n      transition: 0.25s ease;\n      transform-origin: center center;\n      }\n      .node-hit-area {\n      cursor: grab;\n      pointer-events: all;\n      }\n      .node-group:hover .node-circle {\n      filter: drop-shadow(0 0 10px rgba(79, 209, 197, 0.45));\n      }\n      .node-group.active .node-circle {\n      stroke: var(--accent);\n      animation: pulse 1.2s infinite ease-in-out;\n      }\n      @keyframes pulse {\n      0% {\n      filter: drop-shadow(0 0 4px #4fd1c5);\n      }\n      50% {\n      filter: drop-shadow(0 0 14px #4fd1c5);\n      }\n      100% {\n      filter: drop-shadow(0 0 4px #4fd1c5);\n      }\n      }\n      @keyframes done-pulse {\n        0%, 100% {\n          transform: scale(1);\n          box-shadow: 0 0 0 0 rgba(79, 209, 197, 0.7);\n        }\n        50% {\n          transform: scale(1.05);\n          box-shadow: 0 0 0 8px rgba(79, 209, 197, 0);\n        }\n      }\n      .done-btn-active {\n        animation: done-pulse 1.5s ease-in-out infinite;\n        background: var(--accent) !important;\n        color: var(--bg) !important;\n      }\n      .node-label {\n      fill: var(--text-main);\n      font-size: 18px;\n      text-anchor: middle;\n      font-weight: 600;\n      }\n      .node-sub {\n      fill: var(--text-soft);\n      font-size: 13px;\n      text-anchor: middle;\n      }\n      .ping-indicator {\n      fill: #6b7280;\n      stroke: #4b5563;\n      stroke-width: 1;\n      }\n      .ping-indicator.online {\n      fill: #10b981;\n      stroke: #059669;\n      }\n      .ping-indicator.offline {\n      fill: #ef4444;\n      stroke: #dc2626;\n      }\n      .ping-indicator.checking {\n      fill: #f59e0b;\n      stroke: #d97706;\n      }\n      @media (max-width: 1024px) {\n      .node-label {\n      font-size: 28px;\n      }\n      .node-sub {\n      font-size: 20px;\n      }\n      }\n      @media (max-width: 768px) {\n      .node-label {\n      font-size: 70px;\n      }\n      .node-sub {\n      font-size: 50px;\n      }\n      }\n      @media (max-width: 380px) {\n      .node-label {\n      font-size: 60px;\n      }\n      .node-sub {\n      font-size: 42px;\n      }\n      }\n      .details-panel {\n      background: var(--sidebar-bg, var(--panel-alt));\n      padding: 22px;\n      padding-bottom: 80px;\n      overflow-y: auto;\n      display: flex;\n      flex-direction: column;\n      gap: 15px;\n      position: relative;\n      transition:\n      width 0.3s ease,\n      min-width 0.3s ease,\n      padding 0.3s ease,\n      opacity 0.3s ease;\n      }\n      .details-panel {\n      min-width: 260px !important;\n      }\n      .details-panel {\n      overflow-y: auto !important;\n      overflow-x: hidden;\n      }\n      .details-panel.collapsed {\n      width: 0 !important;\n      min-width: 0 !important;\n      padding: 0 !important;\n      overflow: hidden;\n      opacity: 0;\n      }\n      body {\n      overflow-x: hidden;\n      overflow-y: hidden;\n      }\n      main {\n      overflow-x: hidden;\n      overflow-y: visible;\n      }\n      .details-panel {\n      overflow-y: auto !important;\n      overflow-x: hidden;\n      }\n      .details-panel.collapsed {\n      min-width: 0 !important;\n      }\n      .sidebar-toggle {\n      position: absolute;\n      left: -16px;\n      top: 50%;\n      transform: translateY(-50%);\n      width: 16px;\n      height: 60px;\n      background: var(--sidebar-bg, var(--panel-alt));\n      border: 1px solid var(--edge-main);\n      border-right: none;\n      border-radius: 8px 0 0 8px;\n      cursor: pointer;\n      display: flex;\n      align-items: center;\n      justify-content: center;\n      color: var(--text-soft);\n      font-size: 10px;\n      z-index: 25;\n      transition:\n      background 0.2s,\n      color 0.2s;\n      }\n      .sidebar-toggle:hover {\n      background: var(--accent);\n      color: var(--bg);\n      }\n      .sidebar-toggle.collapsed {\n      left: 0;\n      border-right: 1px solid var(--edge-main);\n      border-radius: 0 8px 8px 0;\n      position: fixed;\n      right: 0;\n      left: auto;\n      }\n      .details-name {\n      font-size: clamp(22px, 2.5vw, 30px);\n      font-weight: 700;\n      }\n      .details-ip {\n      font-size: clamp(16px, 2vw, 22px);\n      color: var(--text-soft);\n      }\n      .details-role {\n      font-size: clamp(16px, 2vw, 22px);\n      color: var(--accent);\n      }\n      .size-controls {\n      display: flex;\n      gap: 10px;\n      align-items: center;\n      margin-top: 10px;\n      }\n      .size-controls label {\n      font-size: clamp(14px, 1.6vw, 18px);\n      color: var(--text-soft);\n      }\n      .size-controls input[type=\"range\"] {\n      flex: 1;\n      accent-color: var(--accent);\n      }\n      .size-controls button {\n      padding: 6px 12px;\n      background: var(--panel);\n      color: var(--text-main);\n      border: 1px solid var(--edge-main);\n      border-radius: 4px;\n      cursor: pointer;\n      font-size: clamp(12px, 1.4vw, 16px);\n      }\n      .size-controls button:hover {\n      background: var(--accent);\n      color: var(--bg);\n      }\n      .style-section {\n      margin-top: 15px;\n      padding-top: 15px;\n      border-top: 1px solid var(--edge-main);\n      }\n      .style-section summary {\n      cursor: pointer;\n      font-size: clamp(14px, 1.6vw, 20px);\n      text-transform: uppercase;\n      color: var(--text-soft);\n      list-style: none;\n      display: flex;\n      justify-content: space-between;\n      align-items: center;\n      user-select: none;\n      }\n      .style-section summary::-webkit-details-marker {\n      display: none;\n      }\n      .password-overlay {\n      position: fixed;\n      top: 0;\n      left: 0;\n      width: 100%;\n      height: 100%;\n      background: #ffffff;\n      z-index: 9999;\n      }\n      .style-section summary::after {\n      content: \"▼\";\n      transition: transform 0.2s;\n      }\n      .style-section[open] summary::after {\n      transform: rotate(180deg);\n      }\n      .style-content {\n  display: grid;\n  grid-template-columns: 1fr auto;\n  gap: 8px 12px;\n  align-items: center;\n  justify-items: end;\n}\n.style-content label {\n  justify-self: start;\n}\n      .style-row {\n      display: contents;\n      }\n#edge-panel .style-row,\n#rect-panel .style-row,\n#text-panel .style-row {\n  display: flex;\n  align-items: center;\n  gap: 12px;\n  margin-bottom: 8px;\n}\n#edge-panel .style-row label,\n#rect-panel .style-row label,\n#text-panel .style-row label {\n  min-width: 80px;\n}\nbutton:disabled {\n  opacity: 0.5;\n  cursor: not-allowed;\n}\n      .style-row label {\n      font-size: clamp(13px, 1.5vw, 17px);\n      color: var(--text-soft);\n      min-width: 80px;\n      }\n      .style-row input[type=\"color\"] {\n      width: 50px;\n      height: 30px;\n      border: 1px solid var(--edge-main);\n      border-radius: 4px;\n      cursor: pointer;\n      background: transparent;\n      }\n      .style-row input[type=\"number\"] {\n      width: 70px;\n      padding: 4px 8px;\n      background: var(--panel);\n      border: 1px solid var(--edge-main);\n      border-radius: 4px;\n      color: var(--text-main);\n      font-size: clamp(12px, 1.4vw, 16px);\n      }\n      .style-row select {\n      flex: 1;\n      padding: 4px 8px;\n      background: var(--panel);\n      border: 1px solid var(--edge-main);\n      border-radius: 4px;\n      color: var(--text-main);\n      font-size: clamp(12px, 1.4vw, 16px);\n      cursor: pointer;\n      }\n      .editable-text {\n      cursor: pointer;\n      transition: opacity 0.2s;\n      }\n      .editable-text:hover {\n      opacity: 0.7;\n      }\n      .modal {\n      display: none;\n      position: fixed;\n      top: 0;\n      left: 0;\n      width: 100%;\n      height: 100%;\n      z-index: 999999;\n      justify-content: center;\n      align-items: center;\n      overflow: auto;\n      }\n      .modal.active {\n      display: inline-grid;\n      }\n      .modal-content {\n      background: var(--panel-alt);\n      padding: 25px;\n      border-radius: 8px;\n      border: 1px solid var(--edge-main);\n      min-width: 300px;\n      max-width: 90%;\n      }\n      .modal-content h3 {\n      margin: 0 0 15px 0;\n      color: var(--text-main);\n      font-size: clamp(18px, 2vw, 24px);\n      }\n      .modal-content p {\n      color: var(--text-soft);\n      font-size: clamp(14px, 1.6vw, 18px);\n      margin: 0 0 20px 0;\n      }\n      .modal-content input:not([type=\"color\"]),\n      .modal-content select {\n      width: 100%;\n      padding: 10px;\n      background: var(--panel);\n      border: 1px solid var(--edge-main);\n      border-radius: 4px;\n      color: var(--text-main);\n      font-size: clamp(14px, 1.6vw, 18px);\n      margin-bottom: 15px;\n      user-select: text;\n      -webkit-user-select: text;\n      }\n      .modal-content input[type=\"color\"] {\n      width: 60px;\n      height: 36px;\n      padding: 2px;\n      border: 2px solid var(--edge-main);\n      border-radius: 6px;\n      cursor: pointer;\n      background: none;\n      -webkit-appearance: none;\n      appearance: none;\n      }\n      .modal-content input[type=\"color\"]::-webkit-color-swatch-wrapper {\n      padding: 0;\n      }\n      .modal-content input[type=\"color\"]::-webkit-color-swatch {\n      border: none;\n      border-radius: 4px;\n      }\n      .modal-buttons {\n      display: flex;\n      gap: 10px;\n      justify-content: flex-end;\n      margin-top: 10px;\n      }\n      .modal-buttons button {\n      padding: 8px 16px;\n      border: none;\n      border-radius: 4px;\n      cursor: pointer;\n      font-size: clamp(14px, 1.6vw, 18px);\n      font-weight: 600;\n      }\n      .modal-buttons .btn-cancel {\n      background: var(--panel);\n      color: var(--text-main);\n      border: 1px solid var(--edge-main);\n      }\n      .modal-buttons .btn-save {\n      background: var(--accent);\n      color: var(--bg);\n      }\n      .modal-buttons .btn-delete {\n      background: var(--danger);\n      color: white;\n      }\n      .confirm-modal p {\n      color: var(--text-soft);\n      font-size: clamp(14px, 1.6vw, 18px);\n      margin: 0 0 20px 0;\n      }\n      .context-menu-item {\n      transition: background 0.15s;\n      }\n      .context-menu-item:hover {\n      background: var(--panel);\n      }\n      .details-info-row {\n      display: flex;\n      align-items: center;\n      margin: 6px 0;\n      }\n\t .node-group.selected .node-circle {\n      stroke: var(--selection-handle, #f59e0b);\n      stroke-width: 3;\n      }\n\t  .node-group.layer-faded {\n      pointer-events: none;\n      }\n      .edge.layer-faded {\n      pointer-events: none;\n      }\n      .node-group.search-highlight .node-circle,\n      .node-group.search-highlight rect,\n      .node-group.search-highlight polygon {\n      stroke: #10b981;\n      stroke-width: 3;\n      filter: drop-shadow(0 0 8px #10b981);\n      }\n\t  .node-group.search-faded {\n  opacity: 0.15;\n  pointer-events: none;\n}\n.node-group.search-faded .node-label,\n.node-group.search-faded .node-sub {\n  opacity: 0.3;\n}\n.edge-group.search-faded,\n.edge.search-faded {\n  opacity: 0.1;\n}\n      .badge-row {\n      display: flex;\n      flex-wrap: wrap;\n      gap: 8px;\n      }\n     .badge {\n      background: var(--tag-fill, #1e293b);\n      color: var(--tag-text, #e2e8f0);\n      border: 1px solid var(--tag-border, var(--edge-main));\n      padding: 5px 12px;\n      border-radius: 22px;\n      font-size: clamp(12px, 1.4vw, 18px);\n      }\n\t  .badge.wg {\n      color: var(--accent);\n      border-color: var(--accent);\n      }\n\t  input[type=\"text\"],\n      input[type=\"number\"],\n      input[type=\"password\"],\n      textarea,\n      select {\n      background: var(--input-bg, #0b0e13) !important;\n      color: var(--input-text, #e2e8f0) !important;\n      border-color: var(--input-border, #1f2937) !important;\n      font-family: var(--input-font, Inter, system-ui, sans-serif) !important;\n      font-size: var(--input-font-size, 14px) !important;\n      }\n      .section-label {\n      margin-top: 8px;\n      font-size: clamp(14px, 1.6vw, 20px);\n      text-transform: uppercase;\n      color: var(--text-soft);\n      }\n      .list li {\n      margin: 6px 0;\n      font-size: clamp(14px, 1.6vw, 20px);\n      cursor: pointer;\n      display: flex;\n      justify-content: space-between;\n      align-items: center;\n      gap: 10px;\n      }\n      .list li:hover {\n      color: var(--accent);\n      }\n      .delete-note {\n      color: var(--danger);\n      font-size: 18px;\n      cursor: pointer;\n      opacity: 0;\n      transition: opacity 0.2s;\n      flex-shrink: 0;\n      }\n      .list li:hover .delete-note {\n      opacity: 1;\n      }\n      .editing {\n      outline: 2px solid var(--accent);\n      background: #0d141f;\n      padding: 4px;\n      border-radius: 6px;\n      user-select: text;\n      -webkit-user-select: text;\n      }\n      .mobile-menu-btn {\n      display: none;\n      background: var(--panel);\n      border: 1px solid var(--edge-main);\n      color: var(--text-main);\n      font-size: 22px;\n      padding: 6px 12px;\n      border-radius: 6px;\n      cursor: pointer;\n      }\n      #topbar-menu {\n      gap: 8px;\n      }\n      @media (min-width: 721px) {\n      #topbar-menu {\n      display: flex !important;\n      }\n      }\n      @media (max-width: 720px) {\n      #topbar-menu {\n      display: none;\n      }\n      #topbar-menu.open {\n      display: flex;\n      }\n      }\n      @media (max-width: 720px) {\n      .mobile-menu-btn {\n      display: block;\n      }\n      #topbar-menu {\n      position: absolute;\n      top: var(--topbar-height);\n      right: 0;\n      background: var(--panel-alt);\n      border-left: 1px solid var(--topbar-border);\n      border-bottom: 1px solid var(--topbar-border);\n      padding: 12px;\n      display: none;\n      flex-direction: column;\n      width: 180px;\n      z-index: 999;\n      }\n      #topbar-menu.open {\n      display: flex;\n      }\n      header {\n      position: relative;\n      z-index: 9999;\n      }\n      }\n      @media (max-width: 720px) {\n      .draw-toolbar {\n      position: absolute;\n      top: auto;\n      bottom: auto;\n      left: 10px !important;\n      right: auto !important;\n      width: auto;\n      gap: 6px;\n      padding: 8px;\n      }\n      .topology-toolbar {\n      position: absolute;\n      top: auto;\n      bottom: auto;\n      width: auto;\n      gap: 6px;\n      padding: 8px;\n      }\n      .draw-toolbar {\n      top: 10px !important;\n      }\n      .topology-toolbar {\n      top: calc(10px + var(--draw-toolbar-height, 50px)) !important;\n      }\n      .canvas-hint {\n      top: calc(10px + 120px);\n      }\n      }\n      .icon-picker-modal {\n      display: none;\n      position: fixed;\n      top: 0;\n      left: 0;\n      width: 100%;\n      height: 100%;\n      background: rgba(0, 0, 0, 0.8);\n      z-index: 999999999;\n      justify-content: center;\n      align-items: center;\n      }\n      .icon-picker-modal.active {\n      display: flex;\n      }\n      .icon-picker-content {\n      background: var(--panel);\n      border-radius: 12px;\n      width: 90%;\n      max-width: 800px;\n      max-height: 80vh;\n      display: flex;\n      flex-direction: column;\n      border: 1px solid var(--edge-main);\n      }\n      .icon-picker-header {\n      padding: 20px;\n      border-bottom: 1px solid var(--edge-main);\n      }\n      .icon-picker-header h3 {\n      margin: 0 0 15px 0;\n      color: var(--text-main);\n      }\n      .icon-picker-tabs {\n      display: flex;\n      gap: 10px;\n      margin-bottom: 15px;\n      flex-wrap: wrap;\n      }\n      .icon-picker-tab {\n      padding: 8px 16px;\n      background: var(--panel-alt);\n      border: 1px solid var(--edge-main);\n      border-radius: 6px;\n      cursor: pointer;\n      color: var(--text-soft);\n      font-size: 14px;\n      transition: all 0.2s;\n      }\n      .icon-picker-tab:hover {\n      background: var(--accent);\n      color: var(--bg);\n      }\n      .icon-picker-tab.active {\n      background: var(--accent);\n      color: var(--bg);\n      border-color: var(--accent);\n      }\n      .icon-picker-search {\n      width: 100%;\n      padding: 10px 15px;\n      background: var(--panel-alt);\n      border: 1px solid var(--edge-main);\n      border-radius: 6px;\n      color: var(--text-main);\n      font-size: 14px;\n      }\n      .icon-picker-search::placeholder {\n      color: var(--text-soft);\n      }\n      .icon-picker-body {\n      padding: 20px;\n      overflow-y: auto;\n      flex: 1;\n      }\n      .icon-grid {\n      display: grid;\n      grid-template-columns: repeat(auto-fill, minmax(80px, 1fr));\n      gap: 10px;\n      }\n      .icon-item {\n      display: flex;\n      flex-direction: column;\n      align-items: center;\n      justify-content: center;\n      padding: 15px 10px;\n      background: var(--panel-alt);\n      border: 1px solid var(--edge-main);\n      border-radius: 6px;\n      cursor: pointer;\n      transition: all 0.2s;\n      }\n      .icon-item:hover {\n      background: var(--accent);\n      border-color: var(--accent);\n      transform: scale(1.05);\n      }\n      .icon-item svg {\n      width: 32px;\n      height: 32px;\n      fill: var(--text-main);\n      }\n      .icon-item:hover svg {\n      fill: var(--bg);\n      }\n      .icon-item-name {\n      margin-top: 8px;\n      font-size: 10px;\n      color: var(--text-soft);\n      text-align: center;\n      word-break: break-word;\n      }\n      .icon-item:hover .icon-item-name {\n      color: var(--bg);\n      }\n      .icon-picker-loading {\n      text-align: center;\n      padding: 40px;\n      color: var(--text-soft);\n      }\n      .icon-picker-footer {\n      padding: 15px 20px;\n      border-top: 1px solid var(--edge-main);\n      display: flex;\n      justify-content: flex-end;\n      }\n      .icon-btn-cancel {\n      padding: 8px 20px;\n      background: var(--panel-alt);\n      color: var(--text-main);\n      border: 1px solid var(--edge-main);\n      border-radius: 6px;\n      cursor: pointer;\n      font-size: 14px;\n      }\n      .icon-btn-cancel:hover {\n      background: var(--edge-main);\n      }\n      .icon-badge {\n      display: inline-flex;\n      align-items: center;\n      gap: 5px;\n      padding: 4px 8px;\n      background: var(--panel-alt);\n      border-radius: 4px;\n      font-size: 12px;\n      margin: 2px;\n      }\n      .icon-badge svg {\n      width: 16px;\n      height: 16px;\n      fill: currentColor;\n      }\n      .pick-icon-btn {\n      padding: 6px 12px;\n      background: var(--accent);\n      color: var(--bg);\n      border: none;\n      border-radius: 6px;\n      cursor: pointer;\n      font-size: 13px;\n      font-weight: 600;\n      margin-top: 8px;\n      width: 100%;\n      }\n      .pick-icon-btn:hover {\n      opacity: 0.9;\n      }\n      @media (max-width: 768px) {\n      .icon-picker-content {\n      width: 95%;\n      max-height: 90vh;\n      }\n      .modal-content {\n      background: var(--panel-alt);\n      }\n      #search-input {\n      width: 100%;\n      }\n      }\n      .version-item {\n      display: flex;\n      justify-content: space-between;\n      align-items: center;\n      padding: 12px;\n      background: var(--panel);\n      border: 1px solid var(--edge-main);\n      border-radius: 6px;\n      cursor: pointer;\n      transition: all 0.2s;\n      }\n      .version-item:hover {\n      background: var(--panel-alt);\n      border-color: var(--accent);\n      }\n      .version-item.current {\n      border-color: var(--accent);\n      background: rgba(79, 209, 197, 0.1);\n      }\n      .version-info {\n      flex: 1;\n      }\n      .version-info .timestamp {\n      font-size: 13px;\n      color: var(--text-main);\n      font-weight: 600;\n      }\n      .version-info .details {\n      font-size: 11px;\n      color: var(--text-soft);\n      margin-top: 4px;\n      }\n      .version-actions {\n      display: flex;\n      gap: 6px;\n      }\n      .audit-entry {\n      padding: 8px 12px;\n      background: var(--panel);\n      border-left: 3px solid var(--edge-main);\n      border-radius: 4px;\n      font-size: 12px;\n      }\n      .audit-entry.node { border-left-color: #4fd1c5; }\n      .audit-entry.connection { border-left-color: #9f7aea; }\n      .audit-entry.style { border-left-color: #ed8936; }\n      .audit-entry.rack { border-left-color: #48bb78; }\n      .audit-entry.layer { border-left-color: #4299e1; }\n      .audit-entry .time {\n      color: var(--text-soft);\n      font-size: 10px;\n      }\n      .audit-entry .action {\n      color: var(--text-main);\n      font-weight: 500;\n      }\n      .tab-item {\n      display: flex;\n      justify-content: space-between;\n      align-items: center;\n      padding: 12px;\n      background: var(--panel);\n      border: 1px solid var(--edge-main);\n      border-radius: 6px;\n      cursor: pointer;\n      transition: all 0.2s;\n      }\n      .tab-item:hover {\n      background: var(--panel-alt);\n      }\n      .tab-item.active {\n      border-color: var(--accent);\n      background: rgba(79, 209, 197, 0.1);\n      }\n      .tab-item .tab-name {\n      font-weight: 600;\n      color: var(--text-main);\n      flex: 1;\n      }\n      .tab-item .tab-stats {\n      font-size: 11px;\n      color: var(--text-soft);\n      margin-right: 10px;\n      }\n      .tab-actions {\n      display: flex;\n      gap: 6px;\n      }\n      .secret-item {\n      display: flex;\n      justify-content: space-between;\n      align-items: center;\n      padding: 12px;\n      background: var(--panel);\n      border: 1px solid var(--edge-main);\n      border-radius: 6px;\n      border-left: 3px solid var(--danger);\n      }\n      .secret-item .secret-name {\n      font-weight: 600;\n      color: var(--text-main);\n      flex: 1;\n      }\n      .secret-item .secret-status {\n      font-size: 11px;\n      color: var(--text-soft);\n      margin-right: 10px;\n      }\n\t  body.view-only-mode:not(.view-only-inspect) #node-panel,\n      body.view-only-mode:not(.view-only-inspect) #edge-panel,\n      body.view-only-mode:not(.view-only-inspect) #text-panel,\n      body.view-only-mode:not(.view-only-inspect) #rect-panel {\n        display: none !important;\n      }\n      body.view-only-mode .node-group,\n      body.view-only-mode .edge,\n      body.view-only-mode .rect-group,\n      body.view-only-mode .text-element {\n        cursor: default !important;\n      }\n      body.view-only-mode .resize-handle,\n      body.view-only-mode .edge-control-point {\n        display: none !important;\n      }\n      body.view-only-mode #bulk-toolbar,\n      body.view-only-mode #bulk-toolbar-mobile,\n      body.view-only-mode #bulk-actions-modal {\n        display: none !important;\n      }\n      body.view-only-mode::after {\n        content: \"VIEW ONLY • click 5× to inspect\";\n        position: fixed;\n        bottom: 20px;\n        left: 50%;\n        transform: translateX(-50%);\n        background: rgba(245, 101, 101, 0.9);\n        color: white;\n        padding: 6px 16px;\n        border-radius: 20px;\n        font-size: 12px;\n        font-weight: 600;\n        letter-spacing: 1px;\n        z-index: 9999;\n        pointer-events: none;\n      }\n\n.dropdown {\n  position: relative;\n  display: inline-block;\n}\n.dropdown-btn {\n  padding: 6px 12px;\n  background: var(--btn-bg, var(--panel));\n  color: var(--btn-text, var(--text-main));\n  border: 1px solid var(--edge-main);\n  border-radius: 6px;\n  cursor: pointer;\n  font-size: 14px;\n  font-weight: 600;\n  white-space: nowrap;\n}\n.dropdown-btn:hover {\n  background: var(--accent);\n  color: var(--bg);\n}\n.dropdown-menu {\n  display: none;\n  position: absolute;\n  top: 100%;\n  left: 0;\n  min-width: 180px;\n  background: var(--panel);\n  border: 1px solid var(--edge-main);\n  border-radius: 6px;\n  box-shadow: 0 4px 20px rgba(0,0,0,0.4);\n  z-index: 100000;\n  margin-top: 4px;\n  overflow: hidden;\n}\nheader {\n  z-index: 99999;\n  position: relative;\n}\n.dropdown-menu.open {\n  display: block;\n}\n.dropdown-menu button {\n  display: block;\n  width: 100%;\n  padding: 10px 14px;\n  background: none;\n  border: none;\n  color: var(--text-main);\n  text-align: left;\n  cursor: pointer;\n  font-size: 14px;\n}\n.dropdown-menu button:hover {\n  background: var(--panel-alt);\n  color: var(--accent);\n}\n.dropdown-divider {\n  height: 1px;\n  background: var(--edge-main);\n  margin: 4px 0;\n}\n#mobile-export-btn,\n#mobile-import-btn {\n  display: none;\n}\n@media (max-width: 900px) {\n  .save-row .dropdown {\n    display: none !important;\n  }\n  #mobile-export-btn,\n  #mobile-import-btn {\n    display: inline-block;\n  }\n}\n\n@media print {\n  @page {\n    size: landscape;\n    margin: 0.5cm;\n  }\n  html, body {\n    width: 100%;\n    height: 100%;\n    margin: 0;\n    padding: 0;\n    overflow: visible !important;\n  }\n  body * {\n    visibility: hidden;\n  }\n  #canvas-viewport,\n  #canvas-viewport *,\n  #map,\n  #map * {\n    visibility: visible;\n  }\n  #canvas-viewport {\n    position: absolute !important;\n    left: 0 !important;\n    top: 0 !important;\n    right: 0 !important;\n    bottom: 0 !important;\n    width: 100vw !important;\n    height: 100vh !important;\n    overflow: visible !important;\n  }\n  #map {\n    position: absolute !important;\n    left: 0 !important;\n    top: 0 !important;\n    width: 100% !important;\n    height: 100% !important;\n    background: white !important;\n    background-image: none !important;\n  }\n  #canvas-grid {\n    display: none !important;\n  }\n  main, .topology-panel {\n    display: block !important;\n    position: static !important;\n    overflow: visible !important;\n  }\n\n  #map .node-hit-area,\n  #map .group-indicator,\n  #map .lock-indicator,\n  #map .ping-indicator,\n  #map .fov-group,\n  #map .edge-arrow-forward,\n  #map .edge-arrow-backward {\n    display: none !important;\n  }\n\n  #map .node-circle,\n  #map .node-shape {\n    fill: white !important;\n    stroke: #000 !important;\n    stroke-width: 2px !important;\n  }\n\n  #map .node-circle svg,\n  #map .node-circle svg path,\n  #map .node-circle svg circle,\n  #map .node-circle svg rect,\n  #map .node-circle svg polygon {\n    fill: #000 !important;\n    stroke: none !important;\n  }\n\n  #map text {\n    fill: #000 !important;\n    stroke: none !important;\n  }\n  #map .edge,\n  #map polyline,\n  #map line:not([class*=\"grid\"]) {\n    stroke: #333 !important;\n  }\n  #map .rect-group rect {\n    stroke: #333 !important;\n  }\n  header, .sidebar, .mobile-footer, .minimap-zoom-wrapper,\n  .draw-toolbar, .topology-toolbar, .legend-container,\n  .bulk-toolbar, #bulk-toolbar-mobile, .dropdown-menu,\n  #canvas-hint, .node-panel, .edge-panel, .text-panel, .rect-panel {\n    display: none !important;\n  }\n}\n    </style>\n  </head>\n  <body style=\"background: radial-gradient(circle at center top, rgb(30, 37, 50) 0px, rgb(5, 6, 8) 70%);\" class=\"\">\n    <div class=\"icon-picker-modal\" id=\"icon-picker-modal\">\n      <div class=\"icon-picker-content\">\n        <div class=\"icon-picker-header\">\n          <h3>Select Icon</h3>\n          <div class=\"icon-picker-tabs\">\n            <button class=\"icon-picker-tab\" data-library=\"mdi\">MDI</button>\n            <button class=\"icon-picker-tab\" data-library=\"simple\">Simple Icons</button>\n            <button class=\"icon-picker-tab active\" data-library=\"selfhst\">selfh.st/icons</button>\n          </div>\n          <input type=\"text\" class=\"icon-picker-search\" id=\"icon-search\" placeholder=\"Search icons...\" style=\"display: none;\">\n        </div>\n        <div class=\"icon-picker-body\" id=\"icon-picker-body\">\n        </div>\n        <div class=\"icon-picker-footer\">\n          <button class=\"icon-btn-cancel\" id=\"icon-picker-cancel\">Cancel</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"tabs-modal\">\n      <div class=\"modal-content\" style=\"max-width: 600px;\">\n        <p style=\"font-size: 13px; color: var(--text-soft); margin-bottom: 15px;\">Manage multiple topologies</p>\n        <div id=\"tabs-list\" style=\"display: flex; flex-direction: column; gap: 8px; max-height: 400px; overflow-y: auto; margin-bottom: 15px;\">\n            <div class=\"tab-item active\" onclick=\"switchTab(0)\">\n              <div class=\"tab-name\">Corporate Site B</div>\n              <div class=\"tab-stats\">107 nodes • 65 connections</div>\n              <div class=\"tab-actions\">\n                <button class=\"btn-cancel\" onclick=\"event.stopPropagation(); renameTab(0)\" title=\"Rename tab\">✏️</button>\n                <button class=\"btn-cancel\" onclick=\"event.stopPropagation(); deleteTab(0)\" title=\"Delete tab\">🗑️</button>\n              </div>\n            </div>\n          \n            <div class=\"tab-item \" onclick=\"switchTab(1)\">\n              <div class=\"tab-name\">Homelab 2</div>\n              <div class=\"tab-stats\">11 nodes • 10 connections</div>\n              <div class=\"tab-actions\">\n                <button class=\"btn-cancel\" onclick=\"event.stopPropagation(); renameTab(1)\" title=\"Rename tab\">✏️</button>\n                <button class=\"btn-cancel\" onclick=\"event.stopPropagation(); deleteTab(1)\" title=\"Delete tab\">🗑️</button>\n              </div>\n            </div>\n          </div>\n        <div style=\"display: flex; gap: 8px; margin-bottom: 10px;\">\n          <input type=\"text\" id=\"new-tab-name\" placeholder=\"New tab name...\" style=\"flex: 1; padding: 8px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 6px; color: var(--text-main);\">\n          <button class=\"btn-save\" onclick=\"createNewTab()\">+ Add Tab</button>\n        </div>\n        <div class=\"modal-buttons\">\n          <button class=\"btn-cancel\" id=\"tabs-modal-close\">Close</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"rollback-modal\">\n      <div class=\"modal-content\" style=\"max-width: 700px;\">\n        <h3>Version History</h3>\n        <p style=\"font-size: 13px; color: var(--text-soft); margin-bottom: 15px;\">Limit: Snapshots</p>\n        <div id=\"rollback-list\" style=\"display: flex; flex-direction: column; gap: 8px; max-height: 450px; overflow-y: auto; margin-bottom: 15px;\"></div>\n        <div style=\"display: flex; gap: 8px; margin-bottom: 10px;\">\n          <button class=\"btn-cancel\" onclick=\"clearRollbackHistory()\">Clear History</button>\n          <button class=\"btn-save\" onclick=\"createManualSnapshot()\">Create Snapshot</button>\n        </div>\n        <div class=\"modal-buttons\">\n          <button class=\"btn-cancel\" id=\"rollback-modal-close\">Close</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"audit-log-modal\">\n      <div class=\"modal-content\" style=\"max-width: 800px;\">\n        <h3>Audit Log</h3>\n        <p style=\"font-size: 13px; color: var(--text-soft); margin-bottom: 15px;\">Track all changes made to your topology</p>\n        <div style=\"display: flex; gap: 8px; margin-bottom: 10px;\">\n          <select id=\"audit-filter\" style=\"padding: 8px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 6px; color: var(--text-main);\">\n            <option value=\"all\">All Events</option>\n            <option value=\"node\">Node Operations</option>\n            <option value=\"connection\">Connections</option>\n            <option value=\"style\">Style Changes</option>\n            <option value=\"rack\">Rack Operations</option>\n            <option value=\"layer\">Layer Changes</option>\n          </select>\n          <button class=\"btn-save\" onclick=\"exportAuditLog()\">Export</button>\n        </div>\n        <div id=\"audit-log-list\" style=\"display: flex; flex-direction: column; gap: 4px; max-height: 450px; overflow-y: auto; margin-bottom: 15px; font-family: monospace; font-size: 12px;\"></div>\n        <div style=\"display: flex; gap: 8px;\">\n          <button class=\"btn-cancel\" onclick=\"clearAuditLog()\">Clear Log</button>\n          <button class=\"btn-cancel\" id=\"audit-log-modal-close\">Close</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"port-map-modal\">\n      <div class=\"modal-content\" style=\"max-width: 900px;\">\n        <h3>Port Map</h3>\n        <p style=\"font-size: 13px; color: var(--text-soft); margin-bottom: 15px;\">All connections with port assignments</p>\n        <div style=\"display: flex; gap: 8px; margin-bottom: 10px;\">\n          <input type=\"text\" id=\"port-map-search\" placeholder=\"Search devices or ports...\" style=\"flex: 1; min-width: 200px; padding: 8px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 6px; color: var(--text-main);\">\n          <select id=\"port-map-filter\" style=\"padding: 8px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 6px; color: var(--text-main);\">\n            <option value=\"all\">All Connections</option>\n            <option value=\"with-ports\">With Ports Only</option>\n            <option value=\"without-ports\">Missing Ports</option>\n          </select>\n        </div>\n        <div id=\"port-map-table\" style=\"max-height: 450px; overflow-y: auto; margin-bottom: 15px;\"><div style=\"text-align: center; padding: 40px; color: var(--text-soft);\">No connections found</div></div>\n        <div style=\"display: flex; gap: 8px;\">\n          <button class=\"btn-save\" onclick=\"exportPortMap()\">Export CSV</button>\n          <button class=\"btn-cancel\" id=\"port-map-modal-close\">Close</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"secrets-modal\">\n      <div class=\"modal-content\" style=\"max-width: 600px;\">\n        <h3>Notes</h3>\n        <p style=\"font-size: 13px; color: var(--text-soft); margin-bottom: 15px;\">Notes can also be stored with AES 256 encryption</p>\n        <div id=\"secrets-list\" style=\"display: flex; flex-direction: column; gap: 8px; max-height: 350px; overflow-y: auto; margin-bottom: 15px;\">\n        </div>\n        <div style=\"display: flex; gap: 8px; margin-bottom: 10px;\">\n          <input type=\"text\" id=\"new-secret-name\" placeholder=\"Section name (e.g., 'Root Passwords')...\" style=\"flex: 1; padding: 8px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 6px; color: var(--text-main);\">\n          <button class=\"btn-save\" onclick=\"createNewSecret()\">+ Add Note</button>\n        </div>\n        <div class=\"modal-buttons\">\n          <button class=\"btn-cancel\" id=\"secrets-modal-close\">Close</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"secret-editor-modal\">\n      <div class=\"modal-content\" style=\"max-width: 600px;\">\n        <h3 id=\"secret-editor-title\">Edit Note</h3>\n        <textarea id=\"secret-editor-content\" placeholder=\"Enter sensitive information here...\" style=\"width: 100%; height: 200px; padding: 12px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 6px; color: var(--text-main); font-family: monospace; resize: vertical; margin-bottom: 15px;\"></textarea>\n        <div style=\"margin-bottom: 15px;\">\n          <label style=\"display: flex; align-items: center; gap: 8px; cursor: pointer;\">\n          <input type=\"checkbox\" id=\"secret-auto-encrypt\" checked=\"\" style=\"cursor: pointer;\">\n          <span>Encrypt Note</span>\n          </label>\n        </div>\n        <div class=\"modal-buttons\">\n          <button class=\"btn-cancel\" onclick=\"closeSecretEditor()\">Cancel</button>\n          <button class=\"btn-save\" onclick=\"saveSecret()\">Save</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"mobile-export-modal\">\n      <div class=\"modal-content\" style=\"max-width: 320px;\">\n        <h3 style=\"margin: 0 0 20px 0;\">Export</h3>\n        <div style=\"display: flex; flex-direction: column; gap: 12px;\">\n          <button onclick=\"screenshotCanvas(); document.getElementById('mobile-export-modal').classList.remove('active');\" style=\"padding: 14px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; color: var(--text-main); font-size: 16px; cursor: pointer;\">PNG Image</button>\n          <button onclick=\"exportCanvasSVG(); document.getElementById('mobile-export-modal').classList.remove('active');\" style=\"padding: 14px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; color: var(--text-main); font-size: 16px; cursor: pointer;\">SVG Vector</button>\n          <button onclick=\"exportJSONFile(); document.getElementById('mobile-export-modal').classList.remove('active');\" style=\"padding: 14px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; color: var(--text-main); font-size: 16px; cursor: pointer;\">JSON</button>\n          <button onclick=\"exportMarkdown(); document.getElementById('mobile-export-modal').classList.remove('active');\" style=\"padding: 14px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; color: var(--text-main); font-size: 16px; cursor: pointer;\">Markdown</button>\n          <button onclick=\"exportCSV(); document.getElementById('mobile-export-modal').classList.remove('active');\" style=\"padding: 14px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; color: var(--text-main); font-size: 16px; cursor: pointer;\">CSV</button>\n          <button onclick=\"printTopology(); document.getElementById('mobile-export-modal').classList.remove('active');\" style=\"padding: 14px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; color: var(--text-main); font-size: 16px; cursor: pointer;\">Print...</button>\n\t\t  <button onclick=\"showFormatHelp(); document.getElementById('mobile-import-modal').classList.remove('active');\" style=\"padding: 14px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; color: var(--text-main); font-size: 16px; cursor: pointer;\">Import Help</button>\n        </div>\n        <button onclick=\"document.getElementById('mobile-export-modal').classList.remove('active');\" style=\"margin-top: 20px; padding: 12px; background: var(--danger); border: none; border-radius: 8px; color: white; font-size: 16px; cursor: pointer; width: 100%;\">Cancel</button>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"mobile-import-modal\">\n      <div class=\"modal-content\" style=\"max-width: 320px;\">\n        <h3 style=\"margin: 0 0 20px 0;\">Import</h3>\n        <div style=\"display: flex; flex-direction: column; gap: 12px;\">\n          <button onclick=\"document.getElementById('import-json-file').click(); document.getElementById('mobile-import-modal').classList.remove('active');\" style=\"padding: 14px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; color: var(--text-main); font-size: 16px; cursor: pointer;\">JSON</button>\n          <button onclick=\"document.getElementById('import-markdown-file').click(); document.getElementById('mobile-import-modal').classList.remove('active');\" style=\"padding: 14px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; color: var(--text-main); font-size: 16px; cursor: pointer;\">Markdown</button>\n          <button onclick=\"document.getElementById('import-csv-file').click(); document.getElementById('mobile-import-modal').classList.remove('active');\" style=\"padding: 14px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; color: var(--text-main); font-size: 16px; cursor: pointer;\">CSV</button>\n          <button onclick=\"showFormatHelp(); document.getElementById('mobile-import-modal').classList.remove('active');\" style=\"padding: 14px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; color: var(--text-main); font-size: 16px; cursor: pointer;\">Export Help</button>\n        </div>\n        <button onclick=\"document.getElementById('mobile-import-modal').classList.remove('active');\" style=\"margin-top: 20px; padding: 12px; background: var(--danger); border: none; border-radius: 8px; color: white; font-size: 16px; cursor: pointer; width: 100%;\">Cancel</button>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"edit-modal\">\n      <div class=\"modal-content\">\n        <h3 id=\"modal-title\">Edit Name</h3>\n        <input type=\"text\" id=\"modal-input\">\n        <div class=\"modal-buttons\">\n          <button class=\"btn-cancel\" id=\"modal-cancel\">Cancel</button>\n          <button class=\"btn-save\" id=\"modal-save\">Save</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal confirm-modal\" id=\"confirm-modal\">\n      <div class=\"modal-content\">\n        <h3>Confirm</h3>\n        <p id=\"confirm-message\"> Are you sure you want to delete this line? </p>\n        <div class=\"modal-buttons\">\n          <button class=\"btn-cancel\" id=\"confirm-cancel\">Cancel</button>\n          <button class=\"btn-delete\" id=\"confirm-delete\">Delete</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"save-info-modal\">\n      <div class=\"modal-content\" style=\"max-width: 700px; max-height: 80vh; overflow-y: auto;\">\n        <h3 style=\"margin-bottom: 16px;\">Help</h3>\n        <div style=\"display: flex; gap: 8px; margin-bottom: 16px; flex-wrap: wrap;\">\n          <button class=\"help-tab active\" data-tab=\"general\" style=\"padding: 8px 16px; border: 1px solid var(--edge-main); background: var(--accent); color: var(--bg); border-radius: 6px; cursor: pointer; font-weight: 600;\">General</button>\n          <button class=\"help-tab\" data-tab=\"desktop\" style=\"padding: 8px 16px; border: 1px solid var(--edge-main); background: var(--panel); color: var(--text-main); border-radius: 6px; cursor: pointer; font-weight: 600;\">Desktop</button>\n          <button class=\"help-tab\" data-tab=\"mobile\" style=\"padding: 8px 16px; border: 1px solid var(--edge-main); background: var(--panel); color: var(--text-main); border-radius: 6px; cursor: pointer; font-weight: 600;\">Mobile</button>\n\t\t  <button class=\"help-tab\" data-tab=\"formats\" style=\"padding: 8px 16px; border: 1px solid var(--edge-main); background: var(--panel); color: var(--text-main); border-radius: 6px; cursor: pointer; font-weight: 600;\">Import/Export</button>\n        </div>\n        <div id=\"help-tab-general\" class=\"help-tab-content\" style=\"display: block;\">\n          <ul style=\"margin-left: 20px; margin-bottom: 12px; line-height: 1.8;\">\n            <li><strong>Add Nodes:</strong> Click \"+ Node\" or \"+ Rack\" in the top menu</li>\n            <li><strong>Connect Nodes:</strong> Select a node, then use \"Add Connection\" in the panel</li>\n            <li><strong>Move Nodes:</strong> Drag nodes to reposition them</li>\n            <li><strong>Enter Rack View:</strong> Double click on desktop or Long press on mobile</li>\n            <li><strong>Multi Select:</strong> Right click (desktop) or double tap (mobile)</li>\n            <li><strong>Pan Canvas:</strong> Drag empty space or hold Space + drag</li>\n            <li><strong>Zoom:</strong> Scroll wheel or pinch gesture</li>\n            <li><strong>Free Draw:</strong> Use draw toolbar for drawing lines, boxes/zones , and text</li>\n          </ul>\n          <h4 style=\"color: var(--accent); margin-bottom: 8px;\">Saving &amp; Encryption</h4>\n          <p style=\"margin-bottom: 8px;\">Browsers cannot overwrite local files. Click <strong>Save File</strong> to download an updated HTML with all changes. Replace your old file to keep edits.</p>\n          <p style=\"margin-bottom: 8px;\"><strong>Encryption of data:</strong> Check \"Encrypt\" before saving to password protect your data. Beware! No recovery possible without password!!</p>\n          <p><strong>Decryption of data:</strong> Export via JSON in top settings menu after successful password validation to decrypt your data. You can then reimport back into the same file and save!</p>\n        </div>\n        <div id=\"help-tab-desktop\" class=\"help-tab-content\" style=\"display: none;\">\n          <h4 style=\"color: var(--accent); margin-bottom: 12px;\">Navigation</h4>\n          <table style=\"width: 100%; border-collapse: collapse; margin-bottom: 16px; font-size: 14px;\">\n            <tbody><tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\">Arrow Keys</code></td>\n              <td style=\"padding: 8px;\">Move selected 1px</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\">Shift + Arrows</code></td>\n              <td style=\"padding: 8px;\">Move selected 10px</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\">Tab / Shift+Tab</code></td>\n              <td style=\"padding: 8px;\">Cycle through nodes</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\">F</code></td>\n              <td style=\"padding: 8px;\">Focus on selected</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\">Space + Drag</code></td>\n              <td style=\"padding: 8px;\">Pan canvas</td>\n            </tr>\n          </tbody></table>\n          <h4 style=\"color: var(--accent); margin-bottom: 12px;\">Management</h4>\n          <table style=\"width: 100%; border-collapse: collapse; margin-bottom: 16px; font-size: 14px;\">\n            <tbody><tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\">L</code></td>\n              <td style=\"padding: 8px;\">Lock/unlock selected</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\">G</code></td>\n              <td style=\"padding: 8px;\">Group/ungroup selected</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\">Ctrl+C / Ctrl+V</code></td>\n              <td style=\"padding: 8px;\">Copy / Paste</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\">Ctrl+D</code></td>\n              <td style=\"padding: 8px;\">Duplicate</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\">Ctrl+A</code></td>\n              <td style=\"padding: 8px;\">Select all</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\">Delete</code></td>\n              <td style=\"padding: 8px;\">Delete selected</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\">Escape</code></td>\n              <td style=\"padding: 8px;\">Clear selection</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\">Ctrl+Z / Ctrl+Y</code></td>\n              <td style=\"padding: 8px;\">Undo / Redo</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\">Right Click</code></td>\n              <td style=\"padding: 8px;\">Multi select toggle</td>\n            </tr>\n          </tbody></table>\n        </div>\n        <div id=\"help-tab-mobile\" class=\"help-tab-content\" style=\"display: none;\">\n          <h4 style=\"color: var(--accent); margin-bottom: 12px;\">Basic Gestures</h4>\n          <table style=\"width: 100%; border-collapse: collapse; margin-bottom: 16px; font-size: 14px;\">\n            <tbody><tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><strong>Tap node</strong></td>\n              <td style=\"padding: 8px;\">Select &amp; open properties</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><strong>Tap empty</strong></td>\n              <td style=\"padding: 8px;\">Deselect all</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><strong>Drag node</strong></td>\n              <td style=\"padding: 8px;\">Move node</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><strong>Drag empty</strong></td>\n              <td style=\"padding: 8px;\">Pan canvas</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><strong>Pinch</strong></td>\n              <td style=\"padding: 8px;\">Zoom in/out</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><strong>Double tap node</strong></td>\n              <td style=\"padding: 8px;\">Add/remove from selection</td>\n            </tr>\n          </tbody></table>\n          <h4 style=\"color: var(--accent); margin-bottom: 12px;\">Rack View</h4>\n          <table style=\"width: 100%; border-collapse: collapse; margin-bottom: 16px; font-size: 14px;\">\n            <tbody><tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><strong>Long press rack</strong></td>\n              <td style=\"padding: 8px;\">Enter rack view</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><strong>Double tap empty</strong></td>\n              <td style=\"padding: 8px;\">Exit rack view</td>\n            </tr>\n          </tbody></table>\n        </div>\n\t\t<div id=\"help-tab-formats\" class=\"help-tab-content\" style=\"display: none;\">\n          <h4 style=\"color: var(--accent); margin-bottom: 12px;\">Export Help</h4>\n          <table style=\"width: 100%; border-collapse: collapse; margin-bottom: 16px; font-size: 14px;\">\n            <tbody>\n              <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n                <td style=\"padding: 8px;\"><strong>HTML</strong></td>\n                <td style=\"padding: 8px;\">Complete self contained file and the default save method. When in doubt, choose this. Use the Save HTML button for this</td>\n              </tr>\n              <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n                <td style=\"padding: 8px;\"><strong>JSON</strong></td>\n                <td style=\"padding: 8px;\">Full backup. Editable in your favorite text editor</td>\n              </tr>\n              <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n                <td style=\"padding: 8px;\"><strong>Markdown</strong></td>\n                <td style=\"padding: 8px;\">Full backup. Editable in your favorite text editor</td>\n              </tr>\n              <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n                <td style=\"padding: 8px;\"><strong>CSV</strong></td>\n                <td style=\"padding: 8px;\">Full backup in header, nodes in spreadsheet rows</td>\n              </tr>\n              <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n                <td style=\"padding: 8px;\"><strong>PNG</strong></td>\n                <td style=\"padding: 8px;\">Standard image of current canvas tab</td>\n              </tr>\n              <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n                <td style=\"padding: 8px;\"><strong>SVG</strong></td>\n                <td style=\"padding: 8px;\">Vector image, scalable and editable image of current canvas tab</td>\n              </tr>\n            </tbody>\n          </table>\n          <h4 style=\"color: var(--accent); margin-bottom: 12px;\">Import Help</h4>\n          <table style=\"width: 100%; border-collapse: collapse; margin-bottom: 16px; font-size: 14px;\">\n            <tbody>\n              <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n                <td style=\"padding: 8px;\"><strong>JSON</strong></td>\n                <td style=\"padding: 8px;\">Replaces all data</td>\n              </tr>\n              <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n                <td style=\"padding: 8px;\"><strong>Markdown</strong></td>\n                <td style=\"padding: 8px;\">Replaces all data</td>\n              </tr>\n              <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n                <td style=\"padding: 8px;\"><strong>CSV</strong></td>\n                <td style=\"padding: 8px;\">Choose full restore or add nodes only. Allows quickly creating nodes with a spreadsheet and importing into an existing save.</td>\n              </tr>\n            </tbody>\n          </table>\n        </div>\n        <div class=\"modal-buttons\" style=\"margin-top: 16px;\">\n          <button class=\"btn-cancel\" id=\"save-info-close\">Close</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"settings-modal\">\n\t<div class=\"modal-content\">\n        <h2>Settings</h2>\n        <details class=\"style-section\">\n          <summary>View Only Mode</summary>\n          <div class=\"style-content\">\n            <div class=\"style-row\">\n              <label>View Only (disable all editing)</label>\n              <input type=\"checkbox\" id=\"view-only-mode\" style=\"width:auto;\">\n            </div>\n            <p style=\"margin-top:4px;font-size:12px;color:var(--text-soft);\">When enabled, all editing of the canvas is disabled. Pan and zoom still work.</p>\n          </div>\n        </details>\n        <details class=\"style-section\">\n          <summary>General Theme</summary>\n          <div class=\"style-content\">\n\t\t    <div class=\"style-row\">\n              <label>Theme Preset</label>\n              <div style=\"display:flex;gap:6px;flex:1;\">\n                <select id=\"theme-preset\" style=\"flex:1;padding:4px 8px;background:var(--input-bg);color:var(--input-text);border:1px solid var(--input-border);border-radius:4px;\">\n                  <option value=\"defaulted\">Default</option>\n                  <option value=\"\">Custom</option>\n                  <optgroup label=\"Corporate\">\n                    <option value=\"slate\">Slate</option>\n                    <option value=\"graphite\">Graphite</option>\n                    <option value=\"frost\">Frost (Light)</option>\n                  </optgroup>\n                  <optgroup label=\"Homelab\">\n                    <option value=\"synthwave\">Synthwave</option>\n                    <option value=\"terminal\">Terminal</option>\n                  </optgroup>\n                  <optgroup label=\"Dev\">\n                    <option value=\"dracula\">Dracula</option>\n                    <option value=\"cobalt\">Cobalt</option>\n                    <option value=\"solarized\">Solarized</option>\n                  </optgroup>\n                  <optgroup id=\"my-themes-group\" label=\"My Themes\"></optgroup>\n                </select>\n              </div>\n            </div>\n\t\t\t<div class=\"style-row\">\n\t\t\t <label>  \n\t\t\t  <button onclick=\"saveCurrentTheme()\" style=\"padding:4px 8px;background:var(--accent);color:var(--bg);border:none;border-radius:4px;cursor:pointer;font-size:12px;white-space:nowrap;\">Save Custom Theme</button>\n\t\t\t </label>\n\t\t\t  <button id=\"delete-theme-btn\" onclick=\"deleteCurrentTheme()\" style=\"padding: 4px 8px; background: var(--danger); color: rgb(255, 255, 255); border: none; border-radius: 4px; cursor: pointer; font-size: 12px; display: block;\" disabled=\"\">Delete Custom Theme</button>\n\t\t\t</div>\n            <div class=\"style-row\">\n              <label>Main Background</label>\n              <input type=\"color\" id=\"panel-color\" value=\"#0b0e13\">\n            </div>\n            <div class=\"style-row\">\n              <label>Sidebar / Mobile Footer</label>\n              <input type=\"color\" id=\"sidebar-bg-color\" value=\"#10141b\">\n            </div>\n            <div class=\"style-row\">\n              <label>Modal Window (popup) Background</label>\n              <input type=\"color\" id=\"panel-alt-color\" value=\"#10141b\">\n            </div>\n            <div class=\"style-row\">\n              <label>Accent Buttons</label>\n              <input type=\"color\" id=\"accent-color\" value=\"#4fd1c5\">\n            </div>\n            <div class=\"style-row\">\n              <label>Danger Buttons</label>\n              <input type=\"color\" id=\"danger-color\" value=\"#f56565\">\n            </div>\n            <div class=\"style-row\">\n              <label>Primary Text</label>\n              <input type=\"color\" id=\"text-main-color\" value=\"#e2e8f0\">\n            </div>\n            <div class=\"style-row\">\n              <label>Secondary Text</label>\n              <input type=\"color\" id=\"text-soft-color\" value=\"#94a3b8\">\n            </div>\n\t\t\t<div class=\"style-row\">\n              <label>Tag(s) Fill</label>\n              <input type=\"color\" id=\"tag-fill-color\" value=\"#1e293b\">\n            </div>\n            <div class=\"style-row\">\n              <label>Tag(s) Text</label>\n              <input type=\"color\" id=\"tag-text-color\" value=\"#e2e8f0\">\n            </div>\n            <div class=\"style-row\">\n              <label>Tag(s) Border</label>\n              <input type=\"color\" id=\"tag-border-color\" value=\"#475569\">\n            </div>\n          </div>\n        </details>\n        <details class=\"style-section\">\n          <summary>Top Bar</summary>\n          <div class=\"style-content\">\n            <div class=\"style-row\">\n              <label>Background</label>\n              <input type=\"color\" id=\"topbar-bg-color\" value=\"#0b0e13\">\n            </div>\n            <div class=\"style-row\">\n              <label>Border</label>\n              <input type=\"color\" id=\"topbar-border-color\" value=\"#1f2533\">\n            </div>\n          </div>\n\t\t  <div class=\"style-content\">\n            <div class=\"style-row\">\n              <label>Button Fill</label>\n              <input type=\"color\" id=\"btn-bg-color\" value=\"#0b0e13\">\n            </div>\n            <div class=\"style-row\">\n              <label>Button Text</label>\n              <input type=\"color\" id=\"btn-text-color\" value=\"#e2e8f0\">\n            </div>\n          </div>\n        </details>\n        <details class=\"style-section\">\n          <summary>Main Canvas</summary>\n          <div class=\"style-content\">\n            <div class=\"style-row\">\n              <label>Border</label>\n              <input type=\"color\" id=\"canvas-border-color\" value=\"#475569\">\n            </div>\n            <div class=\"style-row\">\n              <label>Grid Lines</label>\n              <input type=\"color\" id=\"canvas-grid-color\" value=\"#475569\">\n            </div>\n            <div class=\"style-row\">\n              <label>Grid Size</label>\n              <input type=\"number\" id=\"canvas-grid-size\" value=\"50\" min=\"20\" max=\"200\" style=\"width:60px;\">\n            </div>\n            <div class=\"style-row\">\n              <label>Show Grid</label>\n              <input type=\"checkbox\" id=\"canvas-grid-enabled\" checked=\"\">\n            </div>\n\t\t    <div class=\"style-row\">\n              <label>Background Top</label>\n              <input type=\"color\" id=\"canvas-gradient-top\" value=\"#1e2532\">\n            </div>\n            <div class=\"style-row\">\n              <label>Background Bottom</label>\n              <input type=\"color\" id=\"canvas-gradient-bottom\" value=\"#050608\">\n            </div>\n            <div class=\"style-row\">\n              <label>Solid Background</label>\n              <input type=\"color\" id=\"page-bg-color\" value=\"#050608\">\n            </div>\n            <p style=\"margin-top:4px;font-size:12px;color:var(--text-soft);\">Set solid background to override gradient.</p>\n          </div>\n        </details>\n        <details class=\"style-section\">\n          <summary>Rack Canvas</summary>\n          <div class=\"style-content\">\n            <div class=\"style-row\">\n              <label>Frame Fill</label>\n              <input type=\"color\" id=\"rack-frame-fill\" value=\"#0f172a\">\n            </div>\n            <div class=\"style-row\">\n              <label>Frame Border</label>\n              <input type=\"color\" id=\"rack-frame-stroke\" value=\"#4fd1c5\">\n            </div>\n            <div class=\"style-row\">\n              <label>Grid Lines</label>\n              <input type=\"color\" id=\"rack-line-color\" value=\"#475569\">\n            </div>\n            <div class=\"style-row\">\n              <label>Show Grid</label>\n              <input type=\"checkbox\" id=\"rack-grid-enabled\" checked=\"\">\n            </div>\n            <div class=\"style-row\">\n              <label>U Labels</label>\n              <input type=\"color\" id=\"rack-text-color\" value=\"#4fd1c5\">\n            </div>\n          </div>\n       </details>\n\t   <details class=\"style-section\">\n          <summary>Canvas Toolbars</summary>\n          <div class=\"style-content\">\n            <div class=\"style-row\">\n              <label>Background</label>\n              <input type=\"color\" id=\"toolbar-bg-color\" value=\"#0f172a\">\n            </div>\n            <div class=\"style-row\">\n              <label>Border</label>\n              <input type=\"color\" id=\"toolbar-border-color\" value=\"#1f2937\">\n            </div>\n            <div class=\"style-row\">\n              <label>Text</label>\n              <input type=\"color\" id=\"toolbar-text-color\" value=\"#94a3b8\">\n            </div>\n            <div class=\"style-row\">\n              <label>Button Fill</label>\n              <input type=\"color\" id=\"toolbar-btn-bg-color\" value=\"#0b0e13\">\n            </div>\n            <div class=\"style-row\">\n              <label>Button Text</label>\n              <input type=\"color\" id=\"toolbar-btn-text-color\" value=\"#e2e8f0\">\n            </div>\n            <div class=\"style-row\">\n              <label>Minimap Dots</label>\n              <input type=\"color\" id=\"minimap-dots-color\" value=\"#94a3b8\">\n            </div>\n          </div>\n        </details>\n        <details class=\"style-section\">\n          <summary>Nodes</summary>\n          <div class=\"style-content\">\n            <div class=\"style-row\">\n              <label>Fill</label>\n              <input type=\"color\" id=\"node-fill-color\" value=\"#1e293b\">\n            </div>\n            <div class=\"style-row\">\n              <label>Border</label>\n              <input type=\"color\" id=\"node-stroke-color\" value=\"#475569\">\n            </div>\n            <div class=\"style-row\">\n              <label>Title Color</label>\n              <input type=\"color\" id=\"node-title-color\" value=\"#e2e8f0\">\n            </div>\n            <div class=\"style-row\">\n              <label>Title Size</label>\n              <input type=\"number\" id=\"node-title-size\" value=\"18\" min=\"8\" max=\"48\" style=\"width:60px;\">\n            </div>\n            <div class=\"style-row\">\n              <label>Subtitle Color</label>\n              <input type=\"color\" id=\"node-sub-color\" value=\"#94a3b8\">\n            </div>\n            <div class=\"style-row\">\n              <label>Subtitle Size</label>\n              <input type=\"number\" id=\"node-sub-size\" value=\"13\" min=\"8\" max=\"48\" style=\"width:60px;\">\n            </div>\n            <div class=\"style-row\">\n              <label>Font</label>\n              <select id=\"node-font-family\" style=\"flex:1;padding:4px;background:var(--panel);color:var(--text-main);border:1px solid var(--topbar-border);border-radius:4px;\">\n                <option value=\"Inter, system-ui, sans-serif\">Inter</option>\n                <option value=\"Arial, sans-serif\">Arial</option>\n                <option value=\"Helvetica, sans-serif\">Helvetica</option>\n                <option value=\"Georgia, serif\">Georgia</option>\n                <option value=\"monospace\">Monospace</option>\n              </select>\n            </div>\n          </div>\n        </details>\n        <details class=\"style-section\">\n          <summary>Connections</summary>\n          <div class=\"style-content\">\n            <div class=\"style-row\">\n              <label>Default Color</label>\n              <input type=\"color\" id=\"default-edge-color\" value=\"#475569\">\n            </div>\n            <div class=\"style-row\">\n              <label>Default Routing</label>\n              <select id=\"default-edge-routing\">\n                <option value=\"orthogonal\">Orthogonal (90°)</option>\n                <option value=\"curved\">Curved</option>\n                <option value=\"straight\">Straight</option>\n              </select>\n            </div>\n            <div class=\"style-row\">\n              <label>Animation Style</label>\n              <select id=\"animation-style-select\">\n                <option value=\"arrows\">Flowing Arrows</option>\n                <option value=\"dots\">Dot Arrows</option>\n              </select>\n            </div>\n            <div class=\"style-row\">\n              <label>Animate Directions</label>\n              <select id=\"animation-direction-select\">\n                <option value=\"all\">All Directions</option>\n                <option value=\"forward\">Forward Only</option>\n                <option value=\"backward\">Backward Only</option>\n                <option value=\"both\">Bidirectional Only</option>\n              </select>\n            </div>\n            <div class=\"style-row\">\n              <label>Animation Speed</label>\n              <select id=\"animation-speed-select\">\n                <option value=\"0.5\">Very Fast</option>\n                <option value=\"1\">Fast</option>\n                <option value=\"1.5\">Normal</option>\n                <option value=\"2.5\">Slow</option>\n                <option value=\"4\">Very Slow</option>\n              </select>\n            </div>\n            <div class=\"style-row\" style=\"grid-column: 1 / -1;\">\n              <button id=\"apply-routing-all\" style=\"width:100%;padding:8px;background:var(--accent);color:var(--bg);border:none;border-radius:6px;cursor:pointer;font-weight:600;\">Apply Routing to All Connections</button>\n            </div>\n          </div>\n        </details>\n\t\t<details class=\"style-section\">\n          <summary>Animations &amp; Zones</summary>\n          <div class=\"style-content\">\n            <div class=\"style-row\"><label>All Animations</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"anim-master\" checked=\"\"><span class=\"toggle-slider\"></span></label></div>\n            <div style=\"grid-column:1/-1;font-size:11px;color:var(--text-soft);text-transform:uppercase;border-bottom:1px solid var(--edge-main);padding-bottom:4px;margin-top:8px;\">By Type</div>\n            <div class=\"style-row\"><label>Sweep (Pan)</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"anim-type-sweep\" checked=\"\"><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label>Pulse (Breathe)</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"anim-type-pulse\" checked=\"\"><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label>Rings (Emanate)</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"anim-type-rings\" checked=\"\"><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label>Spin (Rotate)</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"anim-type-spin\" checked=\"\"><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label>Connections (Flow)</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"anim-type-connections\" checked=\"\"><span class=\"toggle-slider\"></span></label></div>\n            <div style=\"grid-column:1/-1;font-size:11px;color:var(--text-soft);text-transform:uppercase;border-bottom:1px solid var(--edge-main);padding-bottom:4px;margin-top:8px;\">By Category</div>\n            <div class=\"style-row\"><label>Cameras</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"anim-cat-camera\" checked=\"\"><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label>Doorbells</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"anim-cat-doorbell\" checked=\"\"><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label>Motion Sensors</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"anim-cat-motion\" checked=\"\"><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label>Smoke Detectors</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"anim-cat-smoke\" checked=\"\"><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label>WiFi/AP/Router</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"anim-cat-wifi\" checked=\"\"><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label>Sensors/IoT</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"anim-cat-sensor\" checked=\"\"><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label>Sprinklers</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"anim-cat-sprinkler\" checked=\"\"><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label>Connections</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"anim-cat-connections\" checked=\"\"><span class=\"toggle-slider\"></span></label></div>\n            <div style=\"grid-column:1/-1;font-size:12px;color:var(--accent);text-transform:uppercase;letter-spacing:0.05em;font-weight:600;margin-top:16px;padding-top:12px;border-top:1px solid var(--edge-main);\">Zone (Animation Cone) Setings</div>\n            <div class=\"style-row\"><label>All Zones</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"zone-master\" checked=\"\"><span class=\"toggle-slider\"></span></label></div>\n            <div style=\"grid-column:1/-1;font-size:11px;color:var(--text-soft);text-transform:uppercase;border-bottom:1px solid var(--edge-main);padding-bottom:4px;margin-top:8px;\">By Category</div>\n            <div class=\"style-row\"><label>Cameras</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"zone-cat-camera\" checked=\"\"><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label>Doorbells</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"zone-cat-doorbell\" checked=\"\"><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label>Motion Sensors</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"zone-cat-motion\" checked=\"\"><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label>Smoke Detectors</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"zone-cat-smoke\" checked=\"\"><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label>WiFi/AP/Router</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"zone-cat-wifi\" checked=\"\"><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label>Sensors/IoT</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"zone-cat-sensor\" checked=\"\"><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label>Sprinklers</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"zone-cat-sprinkler\" checked=\"\"><span class=\"toggle-slider\"></span></label></div>\n          </div>\n        </details>\n        <details class=\"style-section\">\n          <summary>Groups &amp; Editing</summary>\n          <div class=\"style-content\">\n            <div class=\"style-row\">\n              <label>Resize handle Color</label>\n              <input type=\"color\" id=\"selection-handle-color\" value=\"#f59e0b\">\n            </div>\n            <div class=\"style-row\">\n              <label>Resize Handle Size</label>\n              <input type=\"number\" id=\"selection-handle-size\" value=\"8\" min=\"4\" max=\"16\" style=\"width:60px;\">\n            </div>\n            <div class=\"style-row\">\n              <label>Grouped Icon Outline</label>\n              <input type=\"color\" id=\"group-indicator-color\" value=\"#4fd1c5\">\n            </div>\n          </div>\n\t\t  <div class=\"style-content\">\n            <div class=\"style-row\">\n              <label>Multiselect Fill Color (Desktop)</label>\n              <input type=\"color\" id=\"selection-fill-color\" value=\"#4fd1c5\">\n            </div>\n           <div class=\"style-row\">\n              <label>Multiselect Fill Opacity (Desktop)</label>\n              <div style=\"display:flex;align-items:center;gap:8px;\">\n                <input type=\"range\" id=\"selection-fill-opacity\" min=\"0\" max=\"1\" step=\"0.05\" value=\"0.1\">\n                <span id=\"selection-fill-opacity-val\" style=\"min-width:35px;text-align:right;\">10%</span>\n              </div>\n            </div>\n            <div class=\"style-row\">\n              <label>Multiselect Border Color (Desktop)</label>\n              <input type=\"color\" id=\"selection-stroke-color\" value=\"#4fd1c5\">\n            </div>\n            <div class=\"style-row\">\n              <label>Multiselect Border Width (Desktop)</label>\n              <input type=\"number\" id=\"selection-stroke-width\" min=\"1\" max=\"10\" value=\"2\">\n            </div>\n            <div class=\"style-row\">\n              <label>Multiselect Border Style (Desktop)</label>\n              <select id=\"selection-stroke-style\">\n                <option value=\"5,5\">Dashed</option>\n                <option value=\"2,4\">Dotted</option>\n                <option value=\"none\">Solid</option>\n              </select>\n            </div>\n          </div>\n        </details>\n\t\t<details class=\"style-section\">\n          <summary>Inputs &amp; Dropdowns</summary>\n          <div class=\"style-content\">\n            <div class=\"style-row\">\n              <label>Background</label>\n              <input type=\"color\" id=\"input-bg-color\" value=\"#0b0e13\">\n            </div>\n            <div class=\"style-row\">\n              <label>Text</label>\n              <input type=\"color\" id=\"input-text-color\" value=\"#e2e8f0\">\n            </div>\n            <div class=\"style-row\">\n              <label>Border</label>\n              <input type=\"color\" id=\"input-border-color\" value=\"#1f2937\">\n            </div>\n            <div class=\"style-row\">\n              <label>Font</label>\n              <select id=\"input-font-family\" style=\"flex:1;padding:4px;background:var(--panel);color:var(--text-main);border:1px solid var(--topbar-border);border-radius:4px;\">\n                <option value=\"Inter, system-ui, sans-serif\">Inter</option>\n                <option value=\"Arial, sans-serif\">Arial</option>\n                <option value=\"Helvetica, sans-serif\">Helvetica</option>\n                <option value=\"Georgia, serif\">Georgia</option>\n                <option value=\"monospace\">Monospace</option>\n              </select>\n            </div>\n            <div class=\"style-row\">\n              <label>Font Size</label>\n              <input type=\"number\" id=\"input-font-size\" value=\"14\" min=\"10\" max=\"24\" style=\"width:60px;\">\n            </div>\n          </div>\n        </details>\n        <details class=\"style-section\">\n          <summary>Welcome Message</summary>\n          <div class=\"style-content\">\n            <div class=\"style-row\">\n              <label>Enabled</label>\n              <input type=\"checkbox\" id=\"canvas-hint-enabled\" checked=\"\" style=\"width:auto;\">\n            </div>\n            <div class=\"style-row\">\n              <label>Background</label>\n              <input type=\"color\" id=\"canvas-hint-bg-color\" value=\"#0f172a\">\n            </div>\n            <div class=\"style-row\">\n              <label>Text Color</label>\n              <input type=\"color\" id=\"canvas-hint-text-color\" value=\"#94a3b8\">\n            </div>\n            <div class=\"style-row\" style=\"flex-direction:column;align-items:stretch;\">\n              <label>Custom Text (HTML)</label>\n              <textarea id=\"canvas-hint-text\" rows=\"4\" style=\"width:100%;margin-top:4px;padding:6px;background:var(--panel);color:var(--text-main);border:1px solid var(--topbar-border);border-radius:4px;font-size:12px;resize:vertical;\" placeholder=\"Leave empty for default\"></textarea>\n            </div>\n          </div>\n        </details>\n         <details class=\"style-section\">\n          <summary>Auto Status Checking</summary>\n          <div class=\"style-content\">\n            <label style=\"display: flex; align-items: center; gap: 8px; margin-bottom: 16px; cursor: pointer;\">\n            <input type=\"checkbox\" id=\"auto-ping-enabled\" style=\"cursor: pointer;\">\n            <span style=\"font-size: 14px; font-weight: 600;\">Enable automatic status checking</span>\n            </label>\n            <div id=\"auto-ping-settings\" style=\"display: none; padding-left: 20px; border-left: 2px solid var(--edge-main);\">\n              <div class=\"style-row\" style=\"margin-bottom: 12px;\">\n                <label>Check Interval (seconds):</label>\n                <input type=\"number\" id=\"auto-ping-interval\" min=\"5\" max=\"3600\" value=\"30\" style=\"width: 80px; padding: 6px; background: var(--panel); border: 1px solid var(--edge-main); border-radius: 4px; color: var(--text-main);\">\n              </div>\n              <div style=\"padding: 10px; background: var(--panel); border-radius: 6px; border: 1px solid var(--edge-main); font-size: 12px; color: var(--text-soft);\">\n                <div style=\"margin-bottom: 4px;\"><span id=\"auto-ping-next-check\">Next check in: --</span></div>\n                <div><span id=\"auto-ping-last-run\">Last run: 1:15:07 PM</span></div>\n              </div>\n            </div>\n            <p style=\"margin-top: 12px; font-size: 12px; color: var(--text-soft); font-style: italic;\">\n              Automatically checks all ping enabled nodes at the specified interval. I recommend 30 to 60 seconds for local networks.\n            </p>\n\t\t\t</div>\n        </details>\n        <details class=\"style-section\" open=\"\">\n          <summary>Danger Zone</summary>\n          <div class=\"style-content\">\n            <p style=\"margin-bottom:12px;font-size:13px;color:var(--text-soft);\">Permanently delete everything on the canvas.</p>\n            <button id=\"clear-all-btn\" title=\"Clear all nodes\" style=\"padding:6px 12px;background:var(--danger);color:#fff;border:none;border-radius:6px;cursor:pointer;font-size:14px;font-weight:600;\">Clear All</button>\n            <input type=\"file\" id=\"import-data-file\" accept=\".json\" style=\"display:none\">\n          </div>\n        </details>\n        <div class=\"modal-buttons\">\n          <button class=\"btn-cancel\" id=\"settings-close\">Close</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"add-node-modal\">\n      <div class=\"modal-content\">\n        <h3>Add New Node</h3>\n        <div style=\"background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; padding: 15px; margin-bottom: 15px;\">\n          <div style=\"font-weight: 600; color: var(--text-main); margin-bottom: 12px; font-size: 14px;\">Basic Information</div>\n          <label style=\"display: block;margin-bottom: 4px;color: var(--text-soft);font-size: 13px;\">Name</label>\n          <input type=\"text\" id=\"new-node-name\" placeholder=\"e.g. web server, jellyfin\">\n          <label style=\"display: block;margin-bottom: 4px;margin-top: 12px;color: var(--text-soft);font-size: 13px;\">IP / Subtitle</label>\n          <input type=\"text\" id=\"new-node-ip\" placeholder=\"e.g. 192.168.1.100\">\n        </div>\n        <div style=\"background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; padding: 15px; margin-bottom: 15px;\">\n          <div style=\"font-weight: 600; color: var(--text-main); margin-bottom: 12px; font-size: 14px;\">Tags</div>\n          <label style=\"display: block;margin-bottom: 4px;color: var(--text-soft);font-size: 13px;\">Text Tags (comma separated)</label>\n          <input type=\"text\" id=\"new-node-tags\" placeholder=\"e.g. Docker, nginx, WG: vpn\">\n          <button class=\"pick-icon-btn\" id=\"pick-new-node-tag-icon-btn\" style=\"margin-top: 10px;\">Add Icon Tag</button>\n          <div id=\"new-node-icon-tags\" style=\"margin-top: 10px; display: none;\">\n            <label style=\"display: block; margin-bottom: 8px; color: var(--text-soft); font-size: 13px;\">Icon Tags:</label>\n            <div id=\"new-node-icon-tags-list\" style=\"display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 10px;\"></div>\n          </div>\n        </div>\n        <div style=\"background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; padding: 15px; margin-bottom: 15px;\">\n          <div style=\"font-weight: 600; color: var(--text-main); margin-bottom: 12px; font-size: 14px;\">Node Appearance</div>\n          <label style=\"display: block;margin-bottom: 4px;color: var(--text-soft);font-size: 13px;\">Basic Shapes</label>\n          <select id=\"new-node-shape\">\n            <optgroup label=\"Basic Shapes\">\n            <option value=\"circle\">Circle</option>\n            <option value=\"square\">Square</option>\n            <option value=\"rectangle\">Rectangle</option>\n            <option value=\"triangle\">Triangle</option>\n            <option value=\"hexagon\">Hexagon</option>\n            <option value=\"diamond\">Diamond</option>\n            <option value=\"star\">Star</option>\n            <option value=\"stop-sign\">Stop Sign</option>\n            <option value=\"octagon\">Octagon</option>\n            <option value=\"pentagon\">Pentagon</option>\n            <option value=\"cross\">Cross</option>\n            <option value=\"rounded-square\">Rounded Square</option>\n            <option value=\"pill\">Pill</option>\n            <option value=\"parallelogram\">Parallelogram</option>\n            <option value=\"trapezoid\">Trapezoid</option>\n          </optgroup>\n          <optgroup label=\"Computers &amp; Devices\">\n            <option value=\"server\">Server</option>\n            <option value=\"pc\">PC / Desktop</option>\n            <option value=\"laptop\">Laptop</option>\n            <option value=\"phone\">Phone / Mobile</option>\n            <option value=\"printer\">Printer</option>\n            <option value=\"pi\">Raspberry Pi</option>\n            <option value=\"sensor\">Sensor / IoT</option>\n          </optgroup>\n          <optgroup label=\"Network Equipment\">\n            <option value=\"router\">Router</option>\n            <option value=\"switch\">Switch</option>\n            <option value=\"firewall\">Firewall</option>\n            <option value=\"access-point\">Access Point</option>\n            <option value=\"load-balancer\">Load Balancer</option>\n            <option value=\"gateway\">Gateway</option>\n            <option value=\"vpn\">VPN / Tunnel</option>\n            <option value=\"nas\">NAS / Storage</option>\n          </optgroup>\n          <optgroup label=\"Cloud &amp; Services\">\n            <option value=\"cloud\">Cloud</option>\n            <option value=\"database\">Database</option>\n\t\t\t<option value=\"docker\">Docker</option>\n            <option value=\"container\">Container</option>\n            <option value=\"vm\">Virtual Machine</option>\n            <option value=\"kubernetes\">Kubernetes</option>\n            <option value=\"api\">API / Endpoint</option>\n            <option value=\"queue\">Queue / Message</option>\n            <option value=\"lambda\">Lambda / Function</option>\n            <option value=\"bucket\">Bucket / S3</option>\n          </optgroup>\n          <optgroup label=\"Security &amp; Monitoring\">\n            <option value=\"shield\">Shield</option>\n            <option value=\"camera\">Camera / CCTV</option>\n            <option value=\"monitor\">Monitor / Dashboard</option>\n          </optgroup>\n          <optgroup label=\"Smart Home\">\n            <option value=\"thermostat\">Thermostat</option>\n            <option value=\"doorbell\">Video Doorbell</option>\n            <option value=\"smart-lock\">Smart Lock</option>\n            <option value=\"smart-bulb\">Smart Bulb</option>\n            <option value=\"smart-plug\">Smart Plug</option>\n            <option value=\"smart-speaker\">Smart Speaker</option>\n            <option value=\"smart-tv\">Smart TV</option>\n            <option value=\"hub\">Smart Hub</option>\n            <option value=\"smoke-detector\">Smoke Detector</option>\n            <option value=\"motion-sensor\">Motion Sensor</option>\n            <option value=\"garage\">Garage Door</option>\n            <option value=\"sprinkler\">Sprinkler</option>\n            <option value=\"vacuum\">Robot Vacuum</option>\n          </optgroup>\n          </select>\n          <button class=\"pick-icon-btn\" id=\"pick-node-icon-btn\" style=\"margin-top: 10px;\"> Or search web icons</button>\n          <div id=\"selected-node-icon\" style=\"margin-top: 10px; display: none;\">\n            <label style=\"display: block; margin-bottom: 8px; color: var(--text-soft); font-size: 13px;\">Selected Icon:</label>\n            <div class=\"icon-badge\" id=\"selected-node-icon-preview\">\n              <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" width=\"24\" height=\"24\">\n                <image href=\"https://cdn.jsdelivr.net/gh/selfhst/icons@master/png/docker.png\" width=\"24\" height=\"24\"></image>\n              </svg>\n              <span>docker</span>\n            </div>\n          </div>\n        </div>\n        <div style=\"background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; padding: 15px; margin-bottom: 15px;\">\n          <div style=\"font-weight: 600; color: var(--text-main); margin-bottom: 12px; font-size: 14px;\">Ping / Status Monitoring</div>\n          <label style=\"display: flex; align-items: center; gap: 8px; margin-bottom: 12px; cursor: pointer;\">\n          <input type=\"checkbox\" id=\"new-node-pingable\" style=\"cursor: pointer;\">\n          <span style=\"color: var(--text-soft); font-size: 13px;\">Enable ping/status check for this node</span>\n          </label>\n          <div id=\"new-node-ping-options\" style=\"display: block; padding-left: 24px; border-left: 2px solid var(--edge-main);\">\n            <label style=\"display: block; margin-bottom: 4px; color: var(--text-soft); font-size: 13px;\">Protocol</label>\n            <select id=\"new-node-ping-protocol\" style=\"margin-bottom: 12px;\">\n              <option value=\"http\">HTTP (port 80) uses IP field</option>\n              <option value=\"https\">HTTPS (port 443) uses IP field</option>\n              <option value=\"custom\">Custom URL</option>\n            </select>\n            <div id=\"new-node-custom-url-container\" style=\"display: block;\">\n              <label style=\"display: block; margin-bottom: 4px; color: var(--text-soft); font-size: 13px;\">Custom URL</label>\n              <input type=\"text\" id=\"new-node-custom-url\" placeholder=\"e.g. http://192.168.1.1:8080\">\n            </div>\n            <label style=\"display: block; margin-bottom: 4px; margin-top: 8px; color: var(--text-soft); font-size: 13px;\">\n            Timeout (ms)\n            </label>\n            <input type=\"number\" id=\"new-node-ping-timeout\" value=\"3000\" min=\"1000\" max=\"10000\" step=\"500\">\n          </div>\n        </div>\n        <div class=\"modal-buttons\">\n          <button class=\"btn-cancel\" id=\"add-node-cancel\">Cancel</button>\n          <button class=\"btn-save\" id=\"add-node-save\">Add Node</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"add-rack-modal\">\n      <div class=\"modal-content\">\n        <h3>Add New Rack</h3>\n        <div style=\"background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; padding: 15px; margin-bottom: 15px;\">\n          <div style=\"font-weight: 600; color: var(--text-main); margin-bottom: 12px; font-size: 14px;\">Basic Information</div>\n          <label style=\"display: block;margin-bottom: 4px;color: var(--text-soft);font-size: 13px;\">Rack Name</label>\n          <input type=\"text\" id=\"new-rack-name\" placeholder=\"e.g. Rack-A, DC1-R01, Production-01\">\n          <label style=\"display: block;margin-bottom: 4px;margin-top: 12px;color: var(--text-soft);font-size: 13px;\">IP / Network Range (optional)</label>\n          <input type=\"text\" id=\"new-rack-ip\" placeholder=\"e.g. 192.168.1.0/24\">\n        </div>\n        <div style=\"background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; padding: 15px; margin-bottom: 15px;\">\n          <div style=\"font-weight: 600; color: var(--text-main); margin-bottom: 12px; font-size: 14px;\">Tags</div>\n          <label style=\"display: block;margin-bottom: 4px;color: var(--text-soft);font-size: 13px;\">Text Tags (comma separated)</label>\n          <input type=\"text\" id=\"new-rack-tags\" placeholder=\"e.g. Production, Data Center 1, Row A\">\n        </div>\n        <div style=\"background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; padding: 15px; margin-bottom: 15px;\">\n          <div style=\"font-weight: 600; color: var(--text-main); margin-bottom: 12px; font-size: 14px;\">Rack Appearance</div>\n          <label style=\"display: block;margin-bottom: 4px;color: var(--text-soft);font-size: 13px;\">Icon / Shape</label>\n          <select id=\"new-rack-shape\">\n            <optgroup label=\"Basic Shapes\">\n            <option value=\"circle\">Circle</option>\n            <option value=\"square\">Square</option>\n            <option value=\"rectangle\">Rectangle</option>\n            <option value=\"triangle\">Triangle</option>\n            <option value=\"hexagon\">Hexagon</option>\n            <option value=\"diamond\">Diamond</option>\n            <option value=\"star\">Star</option>\n            <option value=\"stop-sign\">Stop Sign</option>\n            <option value=\"octagon\">Octagon</option>\n            <option value=\"pentagon\">Pentagon</option>\n            <option value=\"cross\">Cross</option>\n            <option value=\"rounded-square\">Rounded Square</option>\n            <option value=\"pill\">Pill</option>\n            <option value=\"parallelogram\">Parallelogram</option>\n            <option value=\"trapezoid\">Trapezoid</option>\n          </optgroup>\n          <optgroup label=\"Computers &amp; Devices\">\n            <option value=\"server\">Server</option>\n            <option value=\"pc\">PC / Desktop</option>\n            <option value=\"laptop\">Laptop</option>\n            <option value=\"phone\">Phone / Mobile</option>\n            <option value=\"printer\">Printer</option>\n            <option value=\"pi\">Raspberry Pi</option>\n            <option value=\"sensor\">Sensor / IoT</option>\n          </optgroup>\n          <optgroup label=\"Network Equipment\">\n            <option value=\"router\">Router</option>\n            <option value=\"switch\">Switch</option>\n            <option value=\"firewall\">Firewall</option>\n            <option value=\"access-point\">Access Point</option>\n            <option value=\"load-balancer\">Load Balancer</option>\n            <option value=\"gateway\">Gateway</option>\n            <option value=\"vpn\">VPN / Tunnel</option>\n            <option value=\"nas\">NAS / Storage</option>\n          </optgroup>\n          <optgroup label=\"Cloud &amp; Services\">\n            <option value=\"cloud\">Cloud</option>\n            <option value=\"database\">Database</option>\n\t\t\t<option value=\"docker\">Docker</option>\n            <option value=\"container\">Container</option>\n            <option value=\"vm\">Virtual Machine</option>\n            <option value=\"kubernetes\">Kubernetes</option>\n            <option value=\"api\">API / Endpoint</option>\n            <option value=\"queue\">Queue / Message</option>\n            <option value=\"lambda\">Lambda / Function</option>\n            <option value=\"bucket\">Bucket / S3</option>\n          </optgroup>\n          <optgroup label=\"Security &amp; Monitoring\">\n            <option value=\"shield\">Shield</option>\n            <option value=\"camera\">Camera / CCTV</option>\n            <option value=\"monitor\">Monitor / Dashboard</option>\n          </optgroup>\n          <optgroup label=\"Smart Home\">\n            <option value=\"thermostat\">Thermostat</option>\n            <option value=\"doorbell\">Video Doorbell</option>\n            <option value=\"smart-lock\">Smart Lock</option>\n            <option value=\"smart-bulb\">Smart Bulb</option>\n            <option value=\"smart-plug\">Smart Plug</option>\n            <option value=\"smart-speaker\">Smart Speaker</option>\n            <option value=\"smart-tv\">Smart TV</option>\n            <option value=\"hub\">Smart Hub</option>\n            <option value=\"smoke-detector\">Smoke Detector</option>\n            <option value=\"motion-sensor\">Motion Sensor</option>\n            <option value=\"garage\">Garage Door</option>\n            <option value=\"sprinkler\">Sprinkler</option>\n            <option value=\"vacuum\">Robot Vacuum</option>\n          </optgroup>\n          </select>\n          <button class=\"pick-icon-btn\" id=\"pick-rack-icon-btn\" style=\"margin-top: 10px;\">Or Search Web Icons</button>\n          <div id=\"selected-rack-icon\" style=\"margin-top: 10px; display: none;\">\n            <label style=\"display: block; margin-bottom: 8px; color: var(--text-soft); font-size: 13px;\">Selected Icon:</label>\n            <div class=\"icon-badge\" id=\"selected-rack-icon-preview\">\n              <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" width=\"24\" height=\"24\">\n                <image href=\"\" width=\"24\" height=\"24\"></image>\n              </svg>\n              <span></span>\n            </div>\n          </div>\n        </div>\n        <div style=\"background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; padding: 15px; margin-bottom: 15px;\">\n          <div style=\"font-weight: 600; color: var(--text-main); margin-bottom: 12px; font-size: 14px;\">Rack Configuration</div>\n          <label style=\"display: block;margin-bottom: 4px;color: var(--text-soft);font-size: 13px;\">Rack Capacity</label>\n          <select id=\"new-rack-capacity\">\n            <option value=\"42\" selected=\"\">42U (Standard Full Rack)</option>\n            <option value=\"48\">48U (Large Rack)</option>\n            <option value=\"24\">24U (Half Rack)</option>\n            <option value=\"12\">12U (Small/Wall Mount)</option>\n            <option value=\"6\">6U (Mini Rack)</option>\n          </select>\n        </div>\n        <div class=\"modal-buttons\">\n          <button class=\"btn-cancel\" id=\"add-rack-cancel\">Cancel</button>\n          <button class=\"btn-save\" id=\"add-rack-save\">Add Rack</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal confirm-modal\" id=\"clear-all-modal\">\n      <div class=\"modal-content\">\n        <h3>Clear All Nodes</h3>\n        <p> This will remove ALL nodes and connections. This cannot be undone until you reload without saving. Are you sure? </p>\n        <div class=\"modal-buttons\">\n          <button class=\"btn-cancel\" id=\"clear-all-cancel\">Cancel</button>\n          <button class=\"btn-delete\" id=\"clear-all-confirm\">Clear Everything</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"layer-modal\">\n      <div class=\"modal-content\" style=\"max-width: 400px;\">\n        <h3>Layer Visibility</h3>\n        <p style=\"font-size: 13px; color: var(--text-soft); margin-bottom: 15px;\">Toggle which layers are visible on the canvas</p>\n        <div style=\"display: flex; flex-direction: column; gap: 10px;\">\n          <label style=\"display: flex; align-items: center; gap: 8px; cursor: pointer; padding: 8px; background: var(--panel-alt); border-radius: 6px;\">\n          <input type=\"checkbox\" id=\"layer-physical\" checked=\"\" style=\"cursor: pointer; width: 18px; height: 18px;\">\n          <span style=\"flex: 1; font-weight: 500;\">Physical Layer</span>\n          </label>\n          <label style=\"display: flex; align-items: center; gap: 8px; cursor: pointer; padding: 8px; background: var(--panel-alt); border-radius: 6px;\">\n          <input type=\"checkbox\" id=\"layer-logical\" checked=\"\" style=\"cursor: pointer; width: 18px; height: 18px;\">\n          <span style=\"flex: 1; font-weight: 500;\">Logical Layer</span>\n          </label>\n          <label style=\"display: flex; align-items: center; gap: 8px; cursor: pointer; padding: 8px; background: var(--panel-alt); border-radius: 6px;\">\n          <input type=\"checkbox\" id=\"layer-security\" checked=\"\" style=\"cursor: pointer; width: 18px; height: 18px;\">\n          <span style=\"flex: 1; font-weight: 500;\">Security Layer</span>\n          </label>\n          <label style=\"display: flex; align-items: center; gap: 8px; cursor: pointer; padding: 8px; background: var(--panel-alt); border-radius: 6px;\">\n          <input type=\"checkbox\" id=\"layer-application\" checked=\"\" style=\"cursor: pointer; width: 18px; height: 18px;\">\n          <span style=\"flex: 1; font-weight: 500;\">Application Layer</span>\n          </label>\n        </div>\n        <div style=\"margin-top: 15px; display: flex; gap: 8px;\">\n          <button class=\"btn-cancel\" style=\"flex: 1;\" onclick=\"document.querySelectorAll('#layer-modal input[type=checkbox]').forEach(cb =&gt; cb.checked = false); applyLayerFilter();\">Hide All</button>\n          <button class=\"btn-save\" style=\"flex: 1;\" onclick=\"document.querySelectorAll('#layer-modal input[type=checkbox]').forEach(cb =&gt; cb.checked = true); applyLayerFilter();\">Show All</button>\n        </div>\n        <div class=\"modal-buttons\" style=\"margin-top: 10px;\">\n          <button class=\"btn-cancel\" id=\"layer-modal-close\">Close</button>\n        </div>\n      </div>\n    </div>\n    <div id=\"context-menu\" style=\"display: none !important;\">\n    </div>\n    <header>\n      <div class=\"title-block\">\n        <h1 id=\"page-title\" class=\"editable-page-title\">The One File Corporate</h1>\n        <div class=\"save-row\">\n          <button id=\"save-file-btn\" class=\"save-btn\" type=\"button\">Save HTML</button>\n          <label style=\"display: flex;align-items: center;gap: 4px;font-size: 12px;color: var(--text-soft);cursor: pointer;user-select: none;\">\n            <input type=\"checkbox\" id=\"encrypt-toggle\" style=\"cursor: pointer\">\n            <span title=\"Encrypt data with password\">🔒</span>\n          </label>\n          <div class=\"dropdown\" id=\"export-dropdown\">\n            <button class=\"dropdown-btn\" type=\"button\">Export ▾</button>\n            <div class=\"dropdown-menu\">\n              <button type=\"button\" onclick=\"screenshotCanvas()\">PNG Image</button>\n              <button type=\"button\" onclick=\"exportCanvasSVG()\">SVG Vector</button>\n              <div class=\"dropdown-divider\"></div>\n              <button type=\"button\" onclick=\"exportJSONFile()\">JSON</button>\n              <button type=\"button\" onclick=\"exportMarkdown()\">Markdown</button>\n              <button type=\"button\" onclick=\"exportCSV()\">CSV</button>\n              <div class=\"dropdown-divider\"></div>\n              <button type=\"button\" onclick=\"printTopology()\">Print...</button>\n\t\t\t  <div class=\"dropdown-divider\"></div>\n\t\t\t  <button type=\"button\" onclick=\"showFormatHelp()\">Export Help</button>\n            </div>\n          </div>\n          <div class=\"dropdown\" id=\"import-dropdown\">\n            <button class=\"dropdown-btn\" type=\"button\">Import ▾</button>\n            <div class=\"dropdown-menu\">\n              <button type=\"button\" onclick=\"document.getElementById('import-data-file').click()\">JSON</button>\n              <button type=\"button\" onclick=\"document.getElementById('import-markdown-file').click()\">Markdown</button>\n              <button type=\"button\" onclick=\"document.getElementById('import-csv-file').click()\">CSV</button>\n              <div class=\"dropdown-divider\"></div>\n              <button type=\"button\" onclick=\"showFormatHelp()\">Import Help</button>\n            </div>\n          </div>\n          <input type=\"file\" id=\"import-markdown-file\" accept=\".md,.markdown,.txt\" style=\"display:none\">\n          <input type=\"file\" id=\"import-csv-file\" accept=\".csv,.txt\" style=\"display:none\">\n          <span id=\"save-help-btn\" class=\"help-icon\" title=\"Why save?\">?</span>\n        </div>\n      </div>\n      <div id=\"topbar-menu\">\n        <button id=\"back-to-topology-btn\" title=\"Exit rack view and return to topology\" style=\"padding: 6px 12px;background: var(--accent);color: var(--bg);border: none;border-radius: 6px;cursor: pointer;font-size: 14px;font-weight: 600;display: none;\">← Back to Topology</button>\n        <button id=\"add-node-btn\" class=\"save-btn\" title=\"Add new node\" style=\"background: var(--accent)\">+ Node</button>\n        <button id=\"add-rack-btn\" class=\"save-btn\" title=\"Add new rack\" style=\"background: var(--accent); margin-left: 8px;\">+ Rack</button>\n        <button id=\"undo-btn\" title=\"Undo (Ctrl+Z)\" style=\"padding: 6px 12px; background: var(--btn-bg); color: var(--btn-text); border: 1px solid var(--edge-main); border-radius: 6px; cursor: pointer; font-size: 14px; font-weight: 600; opacity: 1;\">↶</button>\n        <button id=\"redo-btn\" title=\"Redo (Ctrl+Y)\" style=\"padding: 6px 12px; background: var(--btn-bg); color: var(--btn-text); border: 1px solid var(--edge-main); border-radius: 6px; cursor: pointer; font-size: 14px; font-weight: 600; opacity: 0.5;\" disabled=\"\">↷</button>\n<input type=\"text\" id=\"search-nodes\" placeholder=\"Search nodes...\" title=\"Search by name, IP, MAC, role, or tag\" style=\"padding: 6px 12px;background: var(--input-bg);color: var(--input-text);border: 1px solid var(--input-border);border-radius: 6px;font-family: var(--input-font);font-size: var(--input-font-size);width: 180px;\">\n        <button id=\"check-all-ping-btn\" title=\"Check status of all enabled nodes\" style=\"padding: 6px 12px;background: var(--btn-bg);color: var(--btn-text);border: 1px solid var(--edge-main);border-radius: 6px;cursor: pointer;font-size: 14px;font-weight: 600\">Check Pings</button>\n        <button id=\"layers-btn\" title=\"Toggle layer visibility\" style=\"padding: 6px 12px;background: var(--btn-bg);color: var(--btn-text);border: 1px solid var(--edge-main);border-radius: 6px;cursor: pointer;font-size: 14px;font-weight: 600;\">Layers</button>\n        <button id=\"tabs-btn\" title=\"Manage document tabs\" style=\"padding: 6px 12px;background: var(--btn-bg);color: var(--btn-text);border: 1px solid var(--edge-main);border-radius: 6px;cursor: pointer;font-size: 14px;font-weight: 600;\">Tabs</button>\n        <button id=\"rollback-btn\" title=\"View version history\" style=\"padding: 6px 12px;background: var(--btn-bg);color: var(--btn-text);border: 1px solid var(--edge-main);border-radius: 6px;cursor: pointer;font-size: 14px;font-weight: 600;\">Snapshots</button>\n        <button id=\"audit-log-btn\" title=\"View audit log\" style=\"padding: 6px 12px;background: var(--btn-bg);color: var(--btn-text);border: 1px solid var(--edge-main);border-radius: 6px;cursor: pointer;font-size: 14px;font-weight: 600;\">Audit</button>\n        <button id=\"port-map-btn\" title=\"View all port connections\" style=\"padding: 6px 12px;background: var(--btn-bg);color: var(--btn-text);border: 1px solid var(--edge-main);border-radius: 6px;cursor: pointer;font-size: 14px;font-weight: 600;\">Ports</button>\n        <button id=\"secrets-btn\" title=\"Manage encrypted notes\" style=\"padding: 6px 12px;background: var(--btn-bg);color: var(--btn-text);border: 1px solid var(--edge-main);border-radius: 6px;cursor: pointer;font-size: 14px;font-weight: 600;\">Notes</button>\n\t\t<button id=\"mobile-export-btn\" title=\"Export topology\" style=\"padding: 6px 12px;background: var(--btn-bg);color: var(--btn-text);border: 1px solid var(--edge-main);border-radius: 6px;cursor: pointer;font-size: 14px;font-weight: 600;\">Export</button>\n        <button id=\"mobile-import-btn\" title=\"Import topology\" style=\"padding: 6px 12px;background: var(--btn-bg);color: var(--btn-text);border: 1px solid var(--edge-main);border-radius: 6px;cursor: pointer;font-size: 14px;font-weight: 600;\">Import</button>\n        <button id=\"settings-btn\" title=\"Page settings\">⚙️</button>\n      </div>\n      <button id=\"mobile-menu-toggle\" class=\"mobile-menu-btn\">☰</button>\n      <div class=\"header-resizer\" id=\"header-resizer\">\n        <svg class=\"resizer-icon\" width=\"40\" height=\"6\" viewBox=\"0 0 40 6\" xmlns=\"http://www.w3.org/2000/svg\">\n          <circle cx=\"14\" cy=\"3\" r=\"2\"></circle>\n          <circle cx=\"20\" cy=\"3\" r=\"2\"></circle>\n          <circle cx=\"26\" cy=\"3\" r=\"2\"></circle>\n        </svg>\n      </div>\n    </header>\n    <main>\n      <section class=\"topology-panel\">\n        <div class=\"draw-toolbar\" id=\"draw-toolbar\" style=\"display: flex !important;\">\n          <button type=\"button\" class=\"toolbar-close-btn\" id=\"draw-toolbar-close-btn\" title=\"Hide draw toolbar\">✕</button>\n          <button id=\"draw-toggle\" title=\"Draw custom line\">✏️</button>\n          <button id=\"rect-toggle\" title=\"Draw zone\">▭</button>\n          <button id=\"text-toggle\" title=\"Add text\">T</button>\n          <span style=\"border-left: 1px solid var(--edge-main); height: 20px; margin: 0 4px;\"></span>\n          <select id=\"rect-style\" title=\"zone style\">\n            <option value=\"filled\">Filled</option>\n            <option value=\"outlined\">Outlined</option>\n          </select>\n          <input type=\"color\" id=\"draw-color\" value=\"#f97316\" title=\"Line color\">\n          <select id=\"draw-style\" title=\"Line style\">\n            <option value=\"solid\">Solid</option>\n            <option value=\"dashed\">Dashed</option>\n            <option value=\"dotted\">Dotted</option>\n            <option value=\"wall\">Wall</option>\n          </select>\n          <select id=\"draw-arrow\" title=\"Arrow direction\">\n            <option value=\"none\">No arrows</option>\n            <option value=\"forward\">→ Right</option>\n            <option value=\"backward\">← Left</option>\n            <option value=\"both\">↔ Both</option>\n          </select>\n          <button id=\"draw-undo\" style=\"display: none\" title=\"Undo last point\">Undo</button>\n        </div>\n        <div class=\"topology-toolbar\" id=\"topology-toolbar\" style=\"display: none !important;\">\n          <button type=\"button\" class=\"toolbar-close-btn\" id=\"topology-toolbar-close-btn\" title=\"Hide add line toolbar\">✕</button>\n          <label for=\"add-line-select\">Add line to:</label>\n          <select id=\"add-line-select\"></select>\n\t      <input type=\"color\" id=\"add-line-color\" value=\"#475569\" title=\"Line color\" style=\"width: 30px;height: 24px;border: 1px solid var(--toolbar-border);border-radius: 4px;cursor: pointer;background: transparent;padding: 0;\">     \n\t\t <select id=\"add-line-direction\" title=\"Line direction\">\n            <option value=\"none\">No arrows</option>\n            <option value=\"forward\">→ Forward</option>\n            <option value=\"backward\">← Backward</option>\n            <option value=\"both\">↔ Both</option>\n          </select>\n          <select id=\"add-line-routing\" title=\"Line routing\">\n            <option value=\"orthogonal\">Orthogonal</option>\n            <option value=\"curved\">Curved</option>\n            <option value=\"straight\">Straight</option>\n          </select>\n          <button id=\"add-line-btn\">Add</button>\n        </div>\n        <div class=\"topology-toolbar bulk-toolbar-desktop\" id=\"bulk-toolbar\" style=\"display: none\">\n          <button type=\"button\" class=\"toolbar-close-btn\" id=\"bulk-toolbar-close\" title=\"Clear selection\">✕</button>\n          <label style=\"font-weight: 600; color: var(--accent)\">Selected: <span id=\"bulk-count\">0</span></label>\n          <button id=\"bulk-align-left\" title=\"Align left\">⬅ Left</button>\n          <button id=\"bulk-align-right\" title=\"Align right\">➡ Right</button>\n          <button id=\"bulk-align-top\" title=\"Align top\">⬆ Top</button>\n          <button id=\"bulk-align-bottom\" title=\"Align bottom\">⬇ Bottom</button>\n          <button id=\"bulk-distribute-h\" title=\"Distribute horizontally\">↔ Distribute H</button>\n          <button id=\"bulk-distribute-v\" title=\"Distribute vertically\">↕ Distribute V</button>\n          <button id=\"bulk-clone\" title=\"Clone selected\">📋 Clone</button>\n          <button id=\"bulk-zone-copy\" title=\"Copy zone style\">📡 Copy Zone</button>\n          <button id=\"bulk-zone-paste\" title=\"Paste zone style\">📡 Paste Zone</button>\n          <button id=\"bulk-zone-toggle\" title=\"Toggle zones on selected\">📡 Toggle Zones</button>\n          <button id=\"bulk-delete\" title=\"Delete selected\" style=\"background: var(--danger); color: white;\">Delete</button>\n        </div>\n        <div class=\"bulk-toolbar-mobile\" id=\"bulk-toolbar-mobile\" style=\"display: none\">\n          <button type=\"button\" id=\"bulk-mobile-btn\" style=\"background: var(--accent);color: white;padding: 12px 20px;border-radius: 25px;font-weight: 600;font-size: 16px;box-shadow: 0 4px 12px rgba(0,0,0,0.3);border: none;cursor: pointer;display: flex;align-items: center;gap: 8px;\">\n          <span id=\"bulk-count-mobile\">0</span>Selected</button>\n        </div>\n        <div id=\"bulk-actions-modal\" style=\"display: none;position: fixed;bottom: 0;left: 0;right: 0;background: var(--panel-alt);border-top-left-radius:20px;border-top-right-radius:20px;padding:20px;padding-bottom:(safe-area-inset-bottom, 20px);box-shadow: 0 -4px 20px rgba(0,0,0,0.5);z-index: 10000;max-height: calc(100vh - 80px);overflow-y: auto;\">\n          <div style=\"display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;\">\n            <h3 style=\"margin: 0; color: var(--accent); font-size: 20px;\">\n              <span id=\"bulk-count-modal\">0</span> Nodes Selected\n            </h3>\n            <button id=\"bulk-modal-close\" style=\"background: none;border: none;font-size: 24px;cursor: pointer;color: var(--text-main);padding: 0;width: 32px;height: 32px;\">✕</button>\n          </div>\n          <div style=\"display: grid; grid-template-columns: 1fr 1fr; gap: 12px;\">\n            <button id=\"bulk-align-left-mobile\" class=\"bulk-action-btn\">⬅<br><span style=\"font-size: 12px;\">Align Left</span></button>\n            <button id=\"bulk-align-right-mobile\" class=\"bulk-action-btn\">➡<br><span style=\"font-size: 12px;\">Align Right</span></button>\n            <button id=\"bulk-align-top-mobile\" class=\"bulk-action-btn\">⬆<br><span style=\"font-size: 12px;\">Align Top</span></button>\n            <button id=\"bulk-align-bottom-mobile\" class=\"bulk-action-btn\">⬇<br><span style=\"font-size: 12px;\">Align Bottom</span></button>\n            <button id=\"bulk-distribute-h-mobile\" class=\"bulk-action-btn\">↔<br><span style=\"font-size: 12px;\">Distribute H</span></button>\n            <button id=\"bulk-distribute-v-mobile\" class=\"bulk-action-btn\">↕<br><span style=\"font-size: 12px;\">Distribute V</span></button>\n            <button id=\"bulk-lock-mobile\" class=\"bulk-action-btn\">🔒<br><span style=\"font-size: 12px;\">Lock Toggle</span></button>\n            <button id=\"bulk-group-mobile\" class=\"bulk-action-btn\">⭕<br><span style=\"font-size: 12px;\">Group Toggle</span></button>\n            <button id=\"bulk-clone-mobile\" class=\"bulk-action-btn\">📋<br><span style=\"font-size: 12px;\">Clone All</span></button>\n            <button id=\"bulk-delete-mobile\" class=\"bulk-action-btn\" style=\"background: var(--danger); color: white;\">🗑<br><span style=\"font-size: 12px;\">Delete All</span></button>\n          </div>\n          <div style=\"margin-top: 16px; padding-top: 16px; border-top: 1px solid var(--edge-main);\">\n            <div style=\"font-size: 12px; color: var(--text-soft); margin-bottom: 12px; text-transform: uppercase; letter-spacing: 0.05em;\">Coverage Zones</div>\n            <div style=\"display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 12px;\">\n              <button id=\"bulk-zone-copy-mobile\" class=\"bulk-action-btn\">📡<br><span style=\"font-size: 12px;\">Copy Zone</span></button>\n              <button id=\"bulk-zone-paste-mobile\" class=\"bulk-action-btn\">📡<br><span style=\"font-size: 12px;\">Paste Zone</span></button>\n              <button id=\"bulk-zone-toggle-mobile\" class=\"bulk-action-btn\">📡<br><span style=\"font-size: 12px;\">Toggle Zones</span></button>\n            </div>\n          </div>\n          <button id=\"bulk-clear-mobile\" style=\"width: 100%;margin-top: 16px;padding: 14px;background: var(--panel-main);border: 1px solid var(--edge-main);border-radius: 8px;color: var(--text-main);font-size: 16px;cursor: pointer;\">Clear Selection</button>\n        </div>\n        <div class=\"canvas-hint\" id=\"canvas-hint\" style=\"cursor: pointer;\"></div>\n        <div class=\"legend-container\" id=\"edge-legend\" style=\"display: flex;\"><div class=\"legend-title\">Line Legend</div><button type=\"button\" class=\"legend-close-btn\">✕</button><div class=\"legend-item\"><span class=\"legend-swatch\" style=\"background-color: rgb(16, 185, 129); cursor: pointer;\"></span><span class=\"legend-label\" contenteditable=\"true\">Trusted Lan</span></div><div class=\"legend-item\"><span class=\"legend-swatch\" style=\"background-color: rgb(245, 158, 11); cursor: pointer;\"></span><span class=\"legend-label\" contenteditable=\"true\">Secure Lan</span></div><div class=\"legend-item\"><span class=\"legend-swatch\" style=\"background-color: rgb(239, 68, 68); cursor: pointer;\"></span><span class=\"legend-label\" contenteditable=\"true\">DMZ</span></div><div class=\"legend-item\"><span class=\"legend-swatch\" style=\"background-color: rgb(71, 85, 105); cursor: pointer;\"></span><span class=\"legend-label\" contenteditable=\"true\">Main ISP</span></div><div class=\"legend-item\"><span class=\"legend-swatch\" style=\"background-color: rgb(59, 130, 246); cursor: pointer;\"></span><span class=\"legend-label\" contenteditable=\"true\">Alternate ISP</span></div><div class=\"legend-item\"><span class=\"legend-swatch\" style=\"background-color: rgb(139, 92, 246); cursor: pointer;\"></span><span class=\"legend-label\" contenteditable=\"true\">you can edit me too</span></div><div class=\"legend-item\"><span class=\"legend-swatch\" style=\"background-color: rgb(6, 182, 212); cursor: pointer;\"></span><span class=\"legend-label\" contenteditable=\"true\">you can edit me too</span></div><div class=\"legend-item\"><span class=\"legend-swatch\" style=\"background-color: rgb(168, 85, 247); cursor: pointer;\"></span><span class=\"legend-label\" contenteditable=\"true\">you can edit me too</span></div><div class=\"legend-item\"><span class=\"legend-swatch\" style=\"background-color: rgb(249, 115, 22); cursor: pointer;\"></span><span class=\"legend-label\" contenteditable=\"true\">you can edit me too</span></div><div class=\"legend-item\"><span class=\"legend-swatch\" style=\"background-color: rgb(14, 165, 233); cursor: pointer;\"></span><span class=\"legend-label\" contenteditable=\"true\">you can edit me too</span></div><div class=\"legend-item\"><span class=\"legend-swatch\" style=\"background-color: rgb(34, 197, 94); cursor: pointer;\"></span><span class=\"legend-label\" contenteditable=\"true\">you can edit me too</span></div><div class=\"legend-item\"><span class=\"legend-swatch\" style=\"background-color: rgb(148, 163, 184); cursor: pointer;\"></span><span class=\"legend-label\" contenteditable=\"true\">you can edit me too</span></div><div class=\"legend-item\"><span class=\"legend-swatch\" style=\"background-color: rgb(251, 191, 36); cursor: pointer;\"></span><span class=\"legend-label\" contenteditable=\"true\">you can edit me too</span></div><div class=\"legend-item\"><span class=\"legend-swatch\" style=\"background-color: rgb(56, 189, 248); cursor: pointer;\"></span><span class=\"legend-label\" contenteditable=\"true\">you can edit me too</span></div><div class=\"legend-item\"><span class=\"legend-swatch\" style=\"background-color: rgb(200, 0, 255); cursor: pointer;\"></span><span class=\"legend-label\" contenteditable=\"true\">you can edit me too</span></div></div>\n        <div class=\"canvas-viewport\" id=\"canvas-viewport\">\n          <svg id=\"map\" viewBox=\"-5.584863670202822 -99.90831573327841 4031.4509771376233 3023.5882328532175\" style=\"\"></svg>\n        </div>\n        <div class=\"minimap-zoom-wrapper\" id=\"minimap-zoom-wrapper\" style=\"display: block;\">\n          <button type=\"button\" class=\"minimap-close-btn\" id=\"minimap-close-btn\" title=\"Hide map &amp; zoom\">✕</button>\n          <div class=\"minimap-container\" id=\"minimap-container\">\n            <svg id=\"minimap\" viewBox=\"0 0 4000 3000\">\n              <rect x=\"904.5889892578125\" y=\"115.40318298339844\" width=\"110.93878173828125\" height=\"919.6242218017578\" class=\"minimap-wall\" style=\"fill: rgb(82, 21, 249); fill-opacity: 0.6; stroke: rgb(82, 21, 249); stroke-width: 4;\"></rect><rect x=\"130.93685150146484\" y=\"1072.3624877929688\" width=\"872.9131851196289\" height=\"99.260986328125\" class=\"minimap-wall\" style=\"fill: rgb(82, 21, 249); fill-opacity: 0.6; stroke: rgb(82, 21, 249); stroke-width: 4;\"></rect><rect class=\"minimap-viewport\" id=\"minimap-viewport\" x=\"-5.584863670202822\" y=\"-99.90831573327841\" width=\"4031.4509771376233\" height=\"3023.5882328532175\"></rect>\n            </svg>\n          </div>\n          <div class=\"zoom-toolbar\" id=\"zoom-toolbar\">\n            <button id=\"zoom-in-btn\" title=\"Zoom in\">+</button>\n            <div class=\"zoom-level\" id=\"zoom-level\">99%</div>\n            <button id=\"zoom-out-btn\" title=\"Zoom out\">-</button>\n            <div class=\"divider\"></div>\n            <button id=\"zoom-fit-btn\" title=\"Fit to view\">[ ]</button>\n            <button id=\"zoom-reset-btn\" title=\"Reset view\">R</button>\n          </div>\n        </div>\n        <button type=\"button\" id=\"edge-legend-mini\" class=\"legend-mini-btn\" style=\"bottom: 10px; display: none;\">Connection &amp; Zone Legend</button>\n        <button type=\"button\" id=\"minimap-mini\" class=\"legend-mini-btn\" style=\"right: 10px; left: auto; bottom: 10px; display: none;\">Map</button>\n        <button type=\"button\" id=\"draw-toolbar-mini\" class=\"legend-mini-btn\" style=\"top: 10px; left: 10px; right: auto; display: none;\">Draw</button>\n        <button type=\"button\" id=\"topology-toolbar-mini\" class=\"legend-mini-btn\" style=\"top: 10px; left: auto; right: 40px; display: none;\">Add Connection</button>\n     </section>\n      <aside class=\"details-panel\" id=\"details-panel\">\n        <div class=\"sidebar-resizer\" id=\"sidebar-resizer\">\n          <svg class=\"resizer-icon\" width=\"6\" height=\"40\" viewBox=\"0 0 6 40\" xmlns=\"http://www.w3.org/2000/svg\">\n            <circle cx=\"3\" cy=\"14\" r=\"2\"></circle>\n            <circle cx=\"3\" cy=\"20\" r=\"2\"></circle>\n            <circle cx=\"3\" cy=\"26\" r=\"2\"></circle>\n          </svg>\n        </div>\n        <div class=\"mobile-footer-resizer\" id=\"mobile-footer-resizer\">\n          <svg class=\"resizer-icon\" width=\"40\" height=\"6\" viewBox=\"0 0 40 6\" xmlns=\"http://www.w3.org/2000/svg\">\n            <circle cx=\"14\" cy=\"3\" r=\"2\"></circle>\n            <circle cx=\"20\" cy=\"3\" r=\"2\"></circle>\n            <circle cx=\"26\" cy=\"3\" r=\"2\"></circle>\n          </svg>\n        </div>\n        <div id=\"node-panel\" style=\"display: none;\">\n          <div class=\"details-name editable-text\" id=\"node-name\">Core Router 1</div>\n          <div class=\"details-ip editable-text\" id=\"node-ip\">10.0.0.1</div>\n          <div class=\"details-info-row\">\n            <span style=\"color: var(--text-soft); font-size: 14px; width: 80px;\">MAC:</span>\n            <span class=\"editable-text\" id=\"node-mac\" style=\"flex: 1; cursor: pointer; color: var(--text-main);\">00:1A:2B:3C:4D:01</span>\n          </div>\n          <div class=\"details-info-row\">\n            <span style=\"color: var(--text-soft); font-size: 14px; width: 80px;\">Rack Unit:</span>\n            <span class=\"editable-text\" id=\"node-rack\" style=\"flex: 1; cursor: pointer; color: var(--text-main);\">--</span>\n          </div>\n          <div class=\"details-info-row\" id=\"uheight-row\" style=\"display: none;\">\n            <span style=\"color: var(--text-soft); font-size: 14px; width: 80px;\">U Height:</span>\n            <span class=\"editable-text\" id=\"node-uheight\" style=\"flex: 1; cursor: pointer; color: var(--text-main);\">2U</span>\n          </div>\n          <div class=\"details-info-row\" id=\"assigned-rack-row\" style=\"display: flex;\">\n            <span style=\"color: var(--text-soft); font-size: 14px; width: 80px;\">Assigned Rack:</span>\n            <select id=\"node-assigned-rack\" style=\"flex: 1; padding: 4px 8px; background: var(--panel); color: var(--text-main); border: 1px solid var(--edge-main); border-radius: 4px; cursor: pointer; font-size: 14px;\"><option value=\"\">None</option><option value=\"dc-rack-a1\">DC Rack A1</option><option value=\"dc-rack-a2\">DC Rack A2</option><option value=\"dc-rack-b1\">DC Rack B1</option><option value=\"dc-rack-b2\">DC Rack B2</option><option value=\"dmz-rack\">DMZ Rack</option><option value=\"mgmt-rack\">Management Rack</option></select>\n          </div>\n          <div class=\"details-info-row\" id=\"rack-capacity-row\" style=\"display: none;\">\n            <span style=\"color: var(--text-soft); font-size: 14px; width: 80px;\">Rack Capacity:</span>\n            <select id=\"node-rack-capacity\" style=\"flex: 1; padding: 4px 8px; background: var(--panel); color: var(--text-main); border: 1px solid var(--edge-main); border-radius: 4px; cursor: pointer; font-size: 14px;\">\n              <option value=\"42\">42U</option>\n              <option value=\"48\">48U</option>\n              <option value=\"24\">24U</option>\n              <option value=\"12\">12U</option>\n              <option value=\"6\">6U</option>\n            </select>\n          </div>\n          <div class=\"details-info-row\">\n            <span style=\"color: var(--text-soft); font-size: 14px; width: 80px;\">Layer:</span>\n            <select id=\"node-layer\" style=\"flex: 1; padding: 4px 8px; background: var(--panel); color: var(--text-main); border: 1px solid var(--edge-main); border-radius: 4px; cursor: pointer; font-size: 14px;\">\n              <option value=\"physical\">Physical</option>\n              <option value=\"logical\">Logical</option>\n              <option value=\"security\">Security</option>\n              <option value=\"application\">Application</option>\n            </select>\n          </div>\n          <div class=\"details-role\" id=\"node-role\">Core Routing</div>\n\t\t  <details id=\"rack-contents-section\" style=\"display: none; margin-top: 12px; margin-bottom: 12px; padding: 10px; background: var(--panel-alt); border-radius: 6px;\" open=\"\">\n            <summary style=\"color: var(--accent); font-weight: 600; font-size: 14px; cursor: pointer;\">Nodes in Rack (<span id=\"rack-contents-count\">0</span>)</summary>\n            <div id=\"rack-contents-list\" style=\"max-height: 200px; overflow-y: auto; margin-top: 8px;\"></div>\n          </details>\n          <details id=\"fov-section\" style=\"display: block; margin-top: 12px; margin-bottom: 12px; padding: 10px; background: var(--panel-alt); border-radius: 6px;\">\n            <summary style=\"color: var(--accent); font-weight: 600; font-size: 14px; cursor: pointer;\">Coverage Zone</summary>\n            <div style=\"padding: 10px 0;\">\n              <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px; gap: 8px;\">\n                <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Preset:</label>\n                <select id=\"fov-preset\" style=\"flex: 1; padding: 4px 8px; background: var(--panel); border: 1px solid var(--edge-main); border-radius: 4px; color: var(--text-main);\">\n                  <option value=\"\">-- Apply Preset --</option>\n                  <option value=\"security-cam\">Security Camera</option>\n                  <option value=\"ptz-cam\">PTZ Camera</option>\n                  <option value=\"motion-detect\">Motion Detector</option>\n                  <option value=\"wifi-strong\">WiFi</option>\n                  <option value=\"wifi-extended\">WiFi Extender</option>\n                  <option value=\"smoke-alarm\">Smoke Alarm</option>\n                  <option value=\"sprinkler-arc\">Sprinkler Arc</option>\n                </select>\n                <button id=\"fov-save-preset\" title=\"Save as preset\" style=\"padding: 4px 8px; background: var(--panel); border: 1px solid var(--edge-main); border-radius: 4px; color: var(--text-main); cursor: pointer;\">💾</button>\n                <button id=\"fov-copy-style\" title=\"Copy zone style\" style=\"padding: 4px 8px; background: var(--panel); border: 1px solid var(--edge-main); border-radius: 4px; color: var(--text-main); cursor: pointer;\">📋</button>\n                <button id=\"fov-paste-style\" title=\"Paste zone style\" style=\"padding: 4px 8px; background: var(--panel); border: 1px solid var(--edge-main); border-radius: 4px; color: var(--text-main); cursor: pointer;\">📥</button>\n              </div>\n              <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Show Zone:</label>\n                <input type=\"checkbox\" id=\"fov-enabled\" style=\"width: 18px; height: 18px; cursor: pointer;\">\n              </div>\n              <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Angle:</label>\n                <input type=\"range\" id=\"fov-angle\" min=\"10\" max=\"360\" value=\"90\" style=\"flex: 1;\">\n                <span id=\"fov-angle-value\" style=\"min-width: 40px; text-align: right; color: var(--text-main);\">360°</span>\n              </div>\n              <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Distance:</label>\n                <input type=\"range\" id=\"fov-distance\" min=\"50\" max=\"500\" value=\"150\" style=\"flex: 1;\">\n                <span id=\"fov-distance-value\" style=\"min-width: 40px; text-align: right; color: var(--text-main);\">200</span>\n              </div>\n              <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Inner Radius:</label>\n                <input type=\"range\" id=\"fov-inner-radius\" min=\"0\" max=\"200\" value=\"0\" style=\"flex: 1;\">\n                <span id=\"fov-inner-radius-value\" style=\"min-width: 40px; text-align: right; color: var(--text-main);\">0</span>\n              </div>\n              <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Rotation:</label>\n                <input type=\"range\" id=\"fov-rotation\" min=\"0\" max=\"360\" value=\"0\" style=\"flex: 1;\">\n                <span id=\"fov-rotation-value\" style=\"min-width: 40px; text-align: right; color: var(--text-main);\">0°</span>\n              </div>\n              <div style=\"border-top: 1px solid var(--edge-main); margin-top: 10px; padding-top: 10px;\">\n                <div style=\"font-size: 12px; color: var(--text-soft); margin-bottom: 8px; text-transform: uppercase; letter-spacing: 0.05em;\">Fill</div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Color:</label>\n                  <input type=\"color\" id=\"fov-color\" value=\"#f59e0b\" style=\"width: 50px; height: 30px; border: 1px solid var(--edge-main); border-radius: 4px; cursor: pointer;\">\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Opacity:</label>\n                  <input type=\"range\" id=\"fov-opacity\" min=\"5\" max=\"80\" value=\"20\" style=\"flex: 1;\">\n                  <span id=\"fov-opacity-value\" style=\"min-width: 40px; text-align: right; color: var(--text-main);\">20%</span>\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Gradient:</label>\n                  <input type=\"checkbox\" id=\"fov-gradient\" style=\"width: 18px; height: 18px; cursor: pointer;\">\n                  <span style=\"color: var(--text-soft); font-size: 12px; margin-left: 8px;\">Fade toward edge</span>\n                </div>\n              </div>\n              <div style=\"border-top: 1px solid var(--edge-main); margin-top: 10px; padding-top: 10px;\">\n                <div style=\"font-size: 12px; color: var(--text-soft); margin-bottom: 8px; text-transform: uppercase; letter-spacing: 0.05em;\">Border</div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Color:</label>\n                  <input type=\"color\" id=\"fov-border-color\" value=\"#f59e0b\" style=\"width: 50px; height: 30px; border: 1px solid var(--edge-main); border-radius: 4px; cursor: pointer;\">\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Width:</label>\n                  <input type=\"range\" id=\"fov-border-width\" min=\"0\" max=\"10\" value=\"2\" style=\"flex: 1;\">\n                  <span id=\"fov-border-width-value\" style=\"min-width: 40px; text-align: right; color: var(--text-main);\">2</span>\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Style:</label>\n                  <select id=\"fov-border-style\" style=\"flex: 1; padding: 4px 8px; background: var(--panel); border: 1px solid var(--edge-main); border-radius: 4px; color: var(--text-main);\">\n                    <option value=\"solid\">Solid</option>\n                    <option value=\"dashed\">Dashed</option>\n                    <option value=\"dotted\">Dotted</option>\n                  </select>\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Opacity:</label>\n                  <input type=\"range\" id=\"fov-border-opacity\" min=\"0\" max=\"100\" value=\"100\" style=\"flex: 1;\">\n                  <span id=\"fov-border-opacity-value\" style=\"min-width: 40px; text-align: right; color: var(--text-main);\">100%</span>\n                </div>\n              </div>\n              <div style=\"border-top: 1px solid var(--edge-main); margin-top: 10px; padding-top: 10px;\">\n                <div style=\"font-size: 12px; color: var(--text-soft); margin-bottom: 8px; text-transform: uppercase; letter-spacing: 0.05em;\">Label</div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Text:</label>\n                  <input type=\"text\" id=\"fov-label\" placeholder=\"e.g. Detection Zone\" style=\"flex: 1; padding: 4px 8px; background: var(--panel); border: 1px solid var(--edge-main); border-radius: 4px; color: var(--text-main);\">\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Position:</label>\n                  <select id=\"fov-label-position\" style=\"flex: 1; padding: 4px 8px; background: var(--panel); border: 1px solid var(--edge-main); border-radius: 4px; color: var(--text-main);\">\n                    <option value=\"center\">Center</option>\n                    <option value=\"edge\">Edge</option>\n                    <option value=\"outside\">Outside</option>\n                  </select>\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Font Size:</label>\n                  <input type=\"range\" id=\"fov-label-size\" min=\"8\" max=\"32\" value=\"14\" style=\"flex: 1;\">\n                  <span id=\"fov-label-size-value\" style=\"min-width: 40px; text-align: right; color: var(--text-main);\">14px</span>\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Font Color:</label>\n                  <input type=\"color\" id=\"fov-label-color\" value=\"#ffffff\" style=\"width: 50px; height: 30px; border: 1px solid var(--edge-main); border-radius: 4px; cursor: pointer;\">\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Bold:</label>\n                  <input type=\"checkbox\" id=\"fov-label-bold\" style=\"width: 18px; height: 18px; cursor: pointer;\">\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Background:</label>\n                  <input type=\"checkbox\" id=\"fov-label-bg\" style=\"width: 18px; height: 18px; cursor: pointer;\">\n                  <input type=\"color\" id=\"fov-label-bg-color\" value=\"#000000\" style=\"width: 50px; height: 30px; border: 1px solid var(--edge-main); border-radius: 4px; cursor: pointer; margin-left: 8px;\">\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Offset X:</label>\n                  <input type=\"range\" id=\"fov-label-offset-x\" min=\"-200\" max=\"200\" value=\"0\" style=\"flex: 1;\">\n                  <span id=\"fov-label-offset-x-value\" style=\"min-width: 40px; text-align: right; color: var(--text-main);\">0</span>\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Offset Y:</label>\n                  <input type=\"range\" id=\"fov-label-offset-y\" min=\"-200\" max=\"200\" value=\"0\" style=\"flex: 1;\">\n                  <span id=\"fov-label-offset-y-value\" style=\"min-width: 40px; text-align: right; color: var(--text-main);\">0</span>\n                </div>\n              </div>\n              <div style=\"border-top: 1px solid var(--edge-main); margin-top: 10px; padding-top: 10px;\">\n                <div style=\"font-size: 12px; color: var(--text-soft); margin-bottom: 8px; text-transform: uppercase; letter-spacing: 0.05em;\">Animation</div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Animate:</label>\n                  <input type=\"checkbox\" id=\"fov-animate\" style=\"width: 18px; height: 18px; cursor: pointer;\">\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Type:</label>\n                  <select id=\"fov-animation-type\" style=\"flex: 1; padding: 4px 8px; background: var(--panel); border: 1px solid var(--edge-main); border-radius: 4px; color: var(--text-main);\">\n                    <option value=\"sweep\">Sweep (Pan)</option>\n                    <option value=\"pulse\">Pulse (Breathe)</option>\n                    <option value=\"rings\">Rings (Emanate)</option>\n                    <option value=\"spin\">Spin (Rotate)</option>\n                  </select>\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Sweep:</label>\n                  <input type=\"range\" id=\"fov-sweep\" min=\"30\" max=\"360\" value=\"120\" style=\"flex: 1;\">\n                  <span id=\"fov-sweep-value\" style=\"min-width: 40px; text-align: right; color: var(--text-main);\">120°</span>\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Speed:</label>\n                  <input type=\"range\" id=\"fov-speed\" min=\"1\" max=\"60\" value=\"4\" style=\"flex: 1;\">\n                  <span id=\"fov-speed-value\" style=\"min-width: 40px; text-align: right; color: var(--text-main);\">4s</span>\n                </div>\n              </div>\n            </div>\n          </details>\n          <details id=\"node-connections-section\" style=\"margin-top: 12px; margin-bottom: 12px; padding: 10px; background: var(--panel-alt); border-radius: 6px; display: block;\">\n            <summary style=\"color: var(--accent); font-weight: 600; font-size: 14px; cursor: pointer;\">Connections (<span id=\"node-connections-count\">9</span>)</summary>\n            <div id=\"node-connections-list\" style=\"max-height: 200px; overflow-y: auto; margin-top: 8px;\"><div style=\"padding: 6px 0px; border-bottom: 1px solid var(--edge-main); display: flex; align-items: center; font-size: 13px;\"><span title=\"Click to view connection\" style=\"color: var(--accent); font-family: monospace; cursor: pointer;\">Gi1/0/1</span><span style=\"color: var(--text-soft); margin: 0px 8px;\">↔</span><span title=\"Click to view connection\" style=\"color: var(--text-main); cursor: pointer;\">ISP Primary</span><span title=\"Click to view connection\" style=\"color: var(--accent); font-family: monospace; margin-left: 6px; cursor: pointer;\">(Gi0/0)</span></div><div style=\"padding: 6px 0px; border-bottom: 1px solid var(--edge-main); display: flex; align-items: center; font-size: 13px;\"><span title=\"Click to view connection\" style=\"color: var(--accent); font-family: monospace; cursor: pointer;\">Gi1/0/24</span><span style=\"color: var(--text-soft); margin: 0px 8px;\">↔</span><span title=\"Click to view connection\" style=\"color: var(--text-main); cursor: pointer;\">Core Router 2</span><span title=\"Click to view connection\" style=\"color: var(--accent); font-family: monospace; margin-left: 6px; cursor: pointer;\">(Gi1/0/24)</span></div><div style=\"padding: 6px 0px; border-bottom: 1px solid var(--edge-main); display: flex; align-items: center; font-size: 13px;\"><span title=\"Click to set port\" style=\"color: var(--text-soft); cursor: pointer;\">-</span><span style=\"color: var(--text-soft); margin: 0px 8px;\">↔</span><span title=\"Click to view connection\" style=\"color: var(--text-main); cursor: pointer;\">External FW 1</span><span title=\"Click to set port\" style=\"color: var(--text-soft); margin-left: 6px; cursor: pointer;\">(-)</span></div><div style=\"padding: 6px 0px; border-bottom: 1px solid var(--edge-main); display: flex; align-items: center; font-size: 13px;\"><span title=\"Click to set port\" style=\"color: var(--text-soft); cursor: pointer;\">-</span><span style=\"color: var(--text-soft); margin: 0px 8px;\">↔</span><span title=\"Click to view connection\" style=\"color: var(--text-main); cursor: pointer;\">NYC Branch Router</span><span title=\"Click to set port\" style=\"color: var(--text-soft); margin-left: 6px; cursor: pointer;\">(-)</span></div><div style=\"padding: 6px 0px; border-bottom: 1px solid var(--edge-main); display: flex; align-items: center; font-size: 13px;\"><span title=\"Click to set port\" style=\"color: var(--text-soft); cursor: pointer;\">-</span><span style=\"color: var(--text-soft); margin: 0px 8px;\">↔</span><span title=\"Click to view connection\" style=\"color: var(--text-main); cursor: pointer;\">LA Branch Router</span><span title=\"Click to set port\" style=\"color: var(--text-soft); margin-left: 6px; cursor: pointer;\">(-)</span></div><div style=\"padding: 6px 0px; border-bottom: 1px solid var(--edge-main); display: flex; align-items: center; font-size: 13px;\"><span title=\"Click to set port\" style=\"color: var(--text-soft); cursor: pointer;\">-</span><span style=\"color: var(--text-soft); margin: 0px 8px;\">↔</span><span title=\"Click to view connection\" style=\"color: var(--text-main); cursor: pointer;\">Chicago Branch Router</span><span title=\"Click to set port\" style=\"color: var(--text-soft); margin-left: 6px; cursor: pointer;\">(-)</span></div><div style=\"padding: 6px 0px; border-bottom: 1px solid var(--edge-main); display: flex; align-items: center; font-size: 13px;\"><span title=\"Click to set port\" style=\"color: var(--text-soft); cursor: pointer;\">-</span><span style=\"color: var(--text-soft); margin: 0px 8px;\">↔</span><span title=\"Click to view connection\" style=\"color: var(--text-main); cursor: pointer;\">London Branch Router</span><span title=\"Click to set port\" style=\"color: var(--text-soft); margin-left: 6px; cursor: pointer;\">(-)</span></div><div style=\"padding: 6px 0px; border-bottom: 1px solid var(--edge-main); display: flex; align-items: center; font-size: 13px;\"><span title=\"Click to set port\" style=\"color: var(--text-soft); cursor: pointer;\">-</span><span style=\"color: var(--text-soft); margin: 0px 8px;\">↔</span><span title=\"Click to view connection\" style=\"color: var(--text-main); cursor: pointer;\">Tokyo Branch Router</span><span title=\"Click to set port\" style=\"color: var(--text-soft); margin-left: 6px; cursor: pointer;\">(-)</span></div><div style=\"padding: 6px 0px; border-bottom: 1px solid var(--edge-main); display: flex; align-items: center; font-size: 13px;\"><span title=\"Click to set port\" style=\"color: var(--text-soft); cursor: pointer;\">-</span><span style=\"color: var(--text-soft); margin: 0px 8px;\">↔</span><span title=\"Click to view connection\" style=\"color: var(--text-main); cursor: pointer;\">AWS Cloud</span><span title=\"Click to set port\" style=\"color: var(--text-soft); margin-left: 6px; cursor: pointer;\">(-)</span></div></div>\n          </details>\n          <div class=\"badge-row\" id=\"node-tags\"></div>\n          <div style=\"margin-top: 10px; display: flex; gap: 8px\">\n            <input type=\"text\" id=\"new-tag-input\" placeholder=\"Add tag...\" style=\"flex: 1;padding: 8px 12px;background: var(--panel);border: 1px solid var(--edge-main);border-radius: 6px;color: var(--text-main);font-size: clamp(14px, 1.6vw, 18px);\">\n            <button id=\"add-tag-btn\" style=\"padding: 8px 16px;background: var(--accent);color: var(--bg);border: none;border-radius: 6px;cursor: pointer;font-size: clamp(14px, 1.6vw, 18px);font-weight: 600;\">Add</button>\n          </div>\n          <button class=\"pick-icon-btn\" id=\"pick-tag-icon-btn\">Add Icon Tag</button>\n          <div class=\"section-label\">Size</div>\n          <div class=\"size-controls\">\n            <label>Size:</label>\n            <input type=\"range\" id=\"size-slider\" min=\"20\" max=\"200\" value=\"55\">\n            <span id=\"size-value\">127</span>\n            <button id=\"reset-size\">Reset</button>\n          </div>\n          <div class=\"size-controls\">\n            <label>Rotation:</label>\n            <input type=\"range\" id=\"rotation-slider\" min=\"-360\" max=\"360\" value=\"0\">\n            <input type=\"number\" id=\"rotation-value\" value=\"0\" style=\"width: 60px; padding: 4px; background: var(--panel); border: 1px solid var(--edge-main); border-radius: 4px; color: var(--text-main); text-align: center;\">\n            <span style=\"color: var(--text-soft);\">°</span>\n            <button id=\"reset-rotation\">Reset</button>\n          </div>\n          <details class=\"style-section\">\n            <summary>Styling</summary>\n            <div class=\"style-content\">\n              <div class=\"style-row\">\n                <label>Screen:</label>\n                <select id=\"style-scope\">\n                  <option value=\"all\">All</option>\n                  <option value=\"desktop\">Desktop</option>\n                  <option value=\"tablet\">Tablet</option>\n                  <option value=\"mobile\">Mobile</option>\n                  <option value=\"fold\">Fold</option>\n                </select>\n              </div>\n              <div class=\"style-row\">\n                <label>Shape:</label>\n                <select id=\"shape-select\"><option value=\"custom-icon\">Custom Icon</option>\n                  <optgroup label=\"Basic Shapes\">\n            <option value=\"circle\">Circle</option>\n            <option value=\"square\">Square</option>\n            <option value=\"rectangle\">Rectangle</option>\n            <option value=\"triangle\">Triangle</option>\n            <option value=\"hexagon\">Hexagon</option>\n            <option value=\"diamond\">Diamond</option>\n            <option value=\"star\">Star</option>\n            <option value=\"stop-sign\">Stop Sign</option>\n            <option value=\"octagon\">Octagon</option>\n            <option value=\"pentagon\">Pentagon</option>\n            <option value=\"cross\">Cross</option>\n            <option value=\"rounded-square\">Rounded Square</option>\n            <option value=\"pill\">Pill</option>\n            <option value=\"parallelogram\">Parallelogram</option>\n            <option value=\"trapezoid\">Trapezoid</option>\n          </optgroup>\n          <optgroup label=\"Computers &amp; Devices\">\n            <option value=\"server\">Server</option>\n            <option value=\"pc\">PC / Desktop</option>\n            <option value=\"laptop\">Laptop</option>\n            <option value=\"phone\">Phone / Mobile</option>\n            <option value=\"printer\">Printer</option>\n            <option value=\"pi\">Raspberry Pi</option>\n            <option value=\"sensor\">Sensor / IoT</option>\n          </optgroup>\n          <optgroup label=\"Network Equipment\">\n            <option value=\"router\">Router</option>\n            <option value=\"switch\">Switch</option>\n            <option value=\"firewall\">Firewall</option>\n            <option value=\"access-point\">Access Point</option>\n            <option value=\"load-balancer\">Load Balancer</option>\n            <option value=\"gateway\">Gateway</option>\n            <option value=\"vpn\">VPN / Tunnel</option>\n            <option value=\"nas\">NAS / Storage</option>\n          </optgroup>\n          <optgroup label=\"Cloud &amp; Services\">\n            <option value=\"cloud\">Cloud</option>\n            <option value=\"database\">Database</option>\n\t\t\t<option value=\"docker\">Docker</option>\n            <option value=\"container\">Container</option>\n            <option value=\"vm\">Virtual Machine</option>\n            <option value=\"kubernetes\">Kubernetes</option>\n            <option value=\"api\">API / Endpoint</option>\n            <option value=\"queue\">Queue / Message</option>\n            <option value=\"lambda\">Lambda / Function</option>\n            <option value=\"bucket\">Bucket / S3</option>\n          </optgroup>\n          <optgroup label=\"Security &amp; Monitoring\">\n            <option value=\"shield\">Shield</option>\n            <option value=\"camera\">Camera / CCTV</option>\n            <option value=\"monitor\">Monitor / Dashboard</option>\n          </optgroup>\n          <optgroup label=\"Smart Home\">\n            <option value=\"thermostat\">Thermostat</option>\n            <option value=\"doorbell\">Video Doorbell</option>\n            <option value=\"smart-lock\">Smart Lock</option>\n            <option value=\"smart-bulb\">Smart Bulb</option>\n            <option value=\"smart-plug\">Smart Plug</option>\n            <option value=\"smart-speaker\">Smart Speaker</option>\n            <option value=\"smart-tv\">Smart TV</option>\n            <option value=\"hub\">Smart Hub</option>\n            <option value=\"smoke-detector\">Smoke Detector</option>\n            <option value=\"motion-sensor\">Motion Sensor</option>\n            <option value=\"garage\">Garage Door</option>\n            <option value=\"sprinkler\">Sprinkler</option>\n            <option value=\"vacuum\">Robot Vacuum</option>\n          </optgroup>\n                </select>\n              </div>\n              <button class=\"pick-icon-btn\" id=\"pick-shape-icon-btn\">Or Search Web Icons</button>\n              <div class=\"style-row\">\n                <label>Fill Color:</label>\n                <input type=\"color\" id=\"circle-color\" value=\"#1e293b\">\n              </div>\n              <div class=\"style-row\">\n                <label>Border Color:</label>\n                <input type=\"color\" id=\"circle-border\" value=\"#475569\">\n              </div>\n              <div class=\"style-row\">\n                <label>Title Color:</label>\n                <input type=\"color\" id=\"title-color\" value=\"#e2e8f0\">\n              </div>\n              <div class=\"style-row\">\n                <label>Title Font:</label>\n                <select id=\"title-font\">\n                  <option value=\"system-ui, sans-serif\">System UI</option>\n                  <option value=\"monospace\">Monospace</option>\n                  <option value=\"serif\">Serif</option>\n                  <option value=\"cursive\">Cursive</option>\n                  <option value=\"'Courier New', monospace\">Courier</option>\n                  <option value=\"Arial, sans-serif\">Arial</option>\n                  <option value=\"'Times New Roman', serif\">Times</option>\n                </select>\n              </div>\n              <div class=\"style-row\">\n                <label>Title Size:</label>\n                <input type=\"number\" id=\"title-size\" min=\"8\" max=\"100\" value=\"18\">\n              </div>\n              <div class=\"style-row\">\n                <label>Sub Color:</label>\n                <input type=\"color\" id=\"sub-color\" value=\"#94a3b8\">\n              </div>\n              <div class=\"style-row\">\n                <label>Sub Font:</label>\n                <select id=\"sub-font\">\n                  <option value=\"system-ui, sans-serif\">System UI</option>\n                  <option value=\"monospace\">Monospace</option>\n                  <option value=\"serif\">Serif</option>\n                  <option value=\"cursive\">Cursive</option>\n                  <option value=\"'Courier New', monospace\">Courier</option>\n                  <option value=\"Arial, sans-serif\">Arial</option>\n                  <option value=\"'Times New Roman', serif\">Times</option>\n                </select>\n              </div>\n              <div class=\"style-row\">\n                <label>Sub Size:</label>\n                <input type=\"number\" id=\"sub-size\" min=\"6\" max=\"80\" value=\"13\">\n              </div>\n              <div style=\"\n                margin-top: 12px;\n                padding-top: 10px;\n                border-top: 1px solid var(--edge-main);\n                \">\n                <div style=\"font-size: 12px;color: var(--text-soft);margin-bottom: 8px;text-transform: uppercase;\">Text Position</div>\n                <div class=\"style-row\">\n                  <label>Name Y:</label>\n                  <input type=\"number\" id=\"title-offset-y\" min=\"-100\" max=\"100\" value=\"0\" style=\"width: 60px\">\n                </div>\n                <div class=\"style-row\">\n                  <label>Name X:</label>\n                  <input type=\"number\" id=\"title-offset-x\" min=\"-100\" max=\"100\" value=\"0\" style=\"width: 60px\">\n                </div>\n                <div class=\"style-row\">\n                  <label>IP Y:</label>\n                  <input type=\"number\" id=\"sub-offset-y\" min=\"-100\" max=\"100\" value=\"0\" style=\"width: 60px\">\n                </div>\n                <div class=\"style-row\">\n                  <label>IP X:</label>\n                  <input type=\"number\" id=\"sub-offset-x\" min=\"-100\" max=\"100\" value=\"0\" style=\"width: 60px\">\n                </div>\n                <div style=\"margin-top: 16px; font-size: 12px; color: var(--text-soft); text-transform: uppercase;\">\n                  Ping Indicator Position\n                </div>\n                <div class=\"style-row\">\n                  <label>Ping X:</label>\n                  <input type=\"number\" id=\"ping-offset-x\" min=\"-200\" max=\"200\" value=\"0\" style=\"width: 60px;\">\n                </div>\n                <div class=\"style-row\">\n                  <label>Ping Y:</label>\n                  <input type=\"number\" id=\"ping-offset-y\" min=\"-200\" max=\"200\" value=\"0\" style=\"width: 60px;\">\n                </div>\n              </div>\n              <button id=\"reset-styles\" style=\"width: 100%;margin-top: 10px;padding: 8px;background: var(--panel);color: var(--text-main);border: 1px solid var(--edge-main);border-radius: 6px;cursor: pointer;font-size: clamp(13px, 1.5vw, 17px);\">Reset Styles</button>\n            </div>\n          </details>\n          <details class=\"style-section\">\n            <summary>Ping / Status Monitoring</summary>\n            <div class=\"style-content\">\n              <label style=\"display: flex; align-items: center; gap: 8px; margin-bottom: 12px; cursor: pointer;\">\n              <input type=\"checkbox\" id=\"node-pingable\" style=\"cursor: pointer;\">\n              <span style=\"font-size: 14px;\">Enable status check for this node</span>\n              </label>\n              <div id=\"node-ping-options\" style=\"display: none;\">\n                <div class=\"style-row\">\n                  <label>Protocol:</label>\n                  <select id=\"node-ping-protocol\">\n                    <option value=\"http\">HTTP (port 80)</option>\n                    <option value=\"https\">HTTPS (port 443)</option>\n                    <option value=\"custom\">Custom URL</option>\n                  </select>\n                </div>\n                <div id=\"node-custom-url-container\" style=\"display: block; margin-top: 8px;\">\n                  <label style=\"display: block; margin-bottom: 4px; font-size: 13px;\">Custom URL:</label>\n                  <input type=\"text\" id=\"node-custom-url\" placeholder=\"e.g. http://192.168.1.1:8080\" style=\"width: 100%;padding: 8px 12px;background: var(--panel);border: 1px solid var(--edge-main);border-radius: 6px;color: var(--text-main);font-size: 14px;\">\n                </div>\n                <div class=\"style-row\" style=\"margin-top: 8px;\">\n                  <label>Timeout (ms):</label>\n                  <input type=\"number\" id=\"node-ping-timeout\" value=\"3000\" min=\"1000\" max=\"10000\" step=\"500\" style=\"width: 100px;\">\n                </div>\n                <div style=\"margin-top: 12px; padding: 10px; background: var(--panel); border-radius: 6px; border: 1px solid var(--edge-main);\">\n                  <div style=\"font-size: 12px; color: var(--text-soft); margin-bottom: 8px;\">Current Status:</div>\n                  <div id=\"node-ping-status\" style=\"font-size: 14px; font-weight: 600; color: var(--accent);\">● Online</div>\n                  <div id=\"node-ping-last-check\" style=\"font-size: 11px; color: var(--text-soft); margin-top: 4px;\">Last checked: 2:25:19 PM</div>\n                </div>\n                <button id=\"check-ping-now\" style=\"width: 100%;margin-top: 10px;padding: 8px;background: var(--accent);color: var(--bg);border: none;border-radius: 6px;cursor: pointer;font-size: 14px;font-weight: 600;\">Check Status Now</button>\n              </div>\n            </div>\n          </details>\n          <div class=\"section-label\">Notes</div>\n          <ul class=\"list\" id=\"node-notes\"></ul>\n          <div style=\"margin-top: 10px; display: flex; gap: 8px\">\n            <input type=\"text\" id=\"new-note-input\" placeholder=\"Type new note...\" style=\"flex: 1;padding: 8px 12px;background: var(--panel);border: 1px solid var(--edge-main);border-radius: 6px;color: var(--text-main);font-size: clamp(14px, 1.6vw, 18px);\">\n            <button id=\"add-note-btn\" style=\"padding: 8px 16px;background: var(--accent);color: var(--bg);border: none;border-radius: 6px;cursor: pointer;font-size: clamp(14px, 1.6vw, 18px);font-weight: 600;\">Add</button>\n          </div>\n          <button id=\"delete-node-btn\" style=\"margin-top: 15px;padding: 10px 16px;background: var(--danger);color: rgb(255, 255, 255);border: none;border-radius: 6px;cursor: pointer;font-size: clamp(14px, 1.6vw, 18px);font-weight: 600;width: 100%;\">Delete Node</button>\n        </div>\n        <div id=\"edge-panel\" style=\"display: none\">\n          <div class=\"details-name\" id=\"edge-title\">Custom line</div>\n          <div class=\"style-row\" style=\"margin-top: 10px\">\n            <label>Width:</label>\n            <input type=\"number\" id=\"edge-width\" min=\"1\" max=\"20\" value=\"4\">\n          </div>\n          <div class=\"style-row\">\n            <label>Color:</label>\n            <input type=\"color\" id=\"edge-color\" value=\"#475569\">\n          </div>\n          <div class=\"style-row\">\n            <label>Line Style:</label>\n            <select id=\"edge-line-style\">\n              <option value=\"solid\">Solid</option>\n              <option value=\"dashed\">Dashed</option>\n              <option value=\"dotted\">Dotted</option>\n              <option value=\"wall\">Wall</option>\n            </select>\n          </div>\n          <div class=\"style-row\">\n            <label>Routing:</label>\n            <select id=\"edge-routing\">\n              <option value=\"orthogonal\">Orthogonal (90°)</option>\n              <option value=\"curved\">Curved</option>\n              <option value=\"straight\">Straight</option>\n            </select>\n          </div>\n          <div class=\"style-row\">\n            <label>Direction:</label>\n            <select id=\"edge-direction\">\n              <option value=\"none\">No arrows</option>\n              <option value=\"forward\">→ Forward</option>\n              <option value=\"backward\">← Backward</option>\n              <option value=\"both\">↔ Bidirectional</option>\n            </select>\n          </div>\n          <div class=\"style-row\">\n            <label>Animate:</label>\n            <input type=\"checkbox\" id=\"edge-animate\">\n          </div>\n          <div class=\"style-row\">\n            <label>Style:</label>\n            <select id=\"edge-animation-style\">\n              <option value=\"\">Default</option>\n              <option value=\"arrows\">Flowing Arrows</option>\n              <option value=\"dots\">Dot Arrows</option>\n            </select>\n          </div>\n          <div class=\"style-row\">\n            <label>Speed:</label>\n            <select id=\"edge-animation-speed\">\n              <option value=\"\">Default</option>\n              <option value=\"0.5\">Very Fast</option>\n              <option value=\"1\">Fast</option>\n              <option value=\"1.5\">Normal</option>\n              <option value=\"2.5\">Slow</option>\n              <option value=\"4\">Very Slow</option>\n            </select>\n          </div>\n          <div class=\"style-row\" id=\"edge-port-fields\">\n            <label>From Port:</label>\n            <input type=\"text\" id=\"edge-from-port\" placeholder=\"e.g. eth0, gi0/1\" style=\"flex: 1;padding: 6px 10px;background: var(--panel);border: 1px solid var(--edge-main);border-radius: 4px;color: var(--text-main);font-size: 14px;\">\n          </div>\n          <div class=\"style-row\" id=\"edge-port-fields-to\">\n            <label>To Port:</label>\n            <input type=\"text\" id=\"edge-to-port\" placeholder=\"e.g. eth1, gi0/2\" style=\"flex: 1;padding: 6px 10px;background: var(--panel);border: 1px solid var(--edge-main);border-radius: 4px;color: var(--text-main);font-size: 14px;\">\n          </div>\n          <div class=\"section-label\">Line Notes</div>\n          <ul class=\"list\" id=\"edge-notes\"></ul>\n          <div style=\"margin-top: 10px; display: flex; gap: 8px\">\n            <input type=\"text\" id=\"new-edge-note\" placeholder=\"Add note...\" style=\"flex: 1;padding: 8px 12px;background: var(--panel);border: 1px solid var(--edge-main);border-radius: 6px;color: var(--text-main);font-size: clamp(14px, 1.6vw, 18px);\">\n            <button id=\"add-edge-note\" style=\"padding: 8px 16px;background: var(--accent);color: var(--bg);border: none;border-radius: 6px;cursor: pointer;font-size: clamp(14px, 1.6vw, 18px);font-weight: 600;\">Add</button>\n          </div>\n          <button id=\"delete-edge\" style=\"margin-top: 15px;padding: 10px 16px;background: var(--danger);color: #fff;border: none;border-radius: 6px;cursor: pointer;font-size: clamp(14px, 1.6vw, 18px);font-weight: 600;\">Delete Line</button>\n        </div>\n        <div id=\"rect-panel\" style=\"display: none\">\n          <div class=\"details-name\" id=\"rect-title\">Zone</div>\n          <div class=\"style-row\" id=\"rect-fill-row\" style=\"margin-top: 10px\">\n            <label>Fill Color:</label>\n            <input type=\"color\" id=\"rect-color\" value=\"#f97316\">\n          </div>\n          <div class=\"style-row\">\n            <label>Border Color:</label>\n            <input type=\"color\" id=\"rect-border-color\" value=\"#f97316\">\n          </div>\n          <div class=\"style-row\">\n            <label>Border Width:</label>\n            <input type=\"number\" id=\"rect-border-width\" min=\"0\" max=\"20\" value=\"2\">\n          </div>\n          <div class=\"style-row\">\n            <label>Style:</label>\n            <select id=\"rect-style-select\">\n              <option value=\"filled\">Filled</option>\n              <option value=\"outlined\">Outlined</option>\n            </select>\n          </div>\n\t\t  <div class=\"style-row\">\n            <label>Line Style:</label>\n            <select id=\"rect-line-style\">\n              <option value=\"solid\">Solid</option>\n              <option value=\"dashed\">Dashed</option>\n              <option value=\"dotted\">Dotted</option>\n              <option value=\"wall\">Wall</option>\n            </select>\n          </div>\n          <div class=\"style-row\">\n            <label>Rotation:</label>\n            <input type=\"range\" id=\"rect-rotation\" min=\"-360\" max=\"360\" value=\"0\" style=\"flex: 1;\">\n            <input type=\"number\" id=\"rect-rotation-value\" value=\"0\" style=\"width: 60px; padding: 4px; background: var(--panel); border: 1px solid var(--edge-main); border-radius: 4px; color: var(--text-main); text-align: center;\">\n            <span style=\"color: var(--text-soft);\">°</span>\n          </div>\n          <div class=\"section-label\">Zone Notes</div>\n          <ul class=\"list\" id=\"rect-notes\"></ul>\n          <div style=\"margin-top: 10px; display: flex; gap: 8px\">\n            <input type=\"text\" id=\"new-rect-note\" placeholder=\"Add note...\" style=\"flex: 1;padding: 8px 12px;background: var(--panel);border: 1px solid var(--edge-main);border-radius: 6px;color: var(--text-main);font-size: clamp(14px, 1.6vw, 18px);\">\n            <button id=\"add-rect-note\" style=\"padding: 8px 16px;background: var(--accent);color: var(--bg);border: none;border-radius: 6px;cursor: pointer;font-size: clamp(14px, 1.6vw, 18px);font-weight: 600;\">Add</button>\n          </div>\n          <button id=\"delete-rect\" style=\"margin-top: 15px;padding: 10px 16px;background: var(--danger);color: #fff;border: none;border-radius: 6px;cursor: pointer;font-size: clamp(14px, 1.6vw, 18px);font-weight: 600;\">Delete Zone</button>\n        </div>\n        <div id=\"text-panel\" style=\"display: none\">\n          <div class=\"details-name\" id=\"text-title\">Text Element</div>\n          <div class=\"style-row\" style=\"margin-top: 10px\">\n            <label>Content:</label>\n            <textarea id=\"text-content\" placeholder=\"Enter text...\" style=\"width: 100%;padding: 8px 12px;background: var(--panel);border: 1px solid var(--edge-main);border-radius: 6px;color: var(--text-main);font-size: 14px;min-height: 80px;resize: vertical;font-family: inherit;\"></textarea>\n          </div>\n          <div class=\"style-row\">\n            <label>Font Size:</label>\n            <input type=\"number\" id=\"text-font-size\" min=\"8\" max=\"200\" value=\"18\" style=\"flex: 1;padding: 6px 10px;background: var(--panel);border: 1px solid var(--edge-main);border-radius: 4px;color: var(--text-main);\">\n          </div>\n          <div class=\"style-row\">\n            <label>Color:</label>\n            <input type=\"color\" id=\"text-color\" value=\"#e2e8f0\">\n          </div>\n          <div class=\"style-row\">\n            <label>Font Weight:</label>\n            <select id=\"text-font-weight\" style=\"flex: 1;padding: 6px 10px;background: var(--panel);border: 1px solid var(--edge-main);border-radius: 4px;color: var(--text-main);cursor: pointer;\">\n              <option value=\"normal\">Normal</option>\n              <option value=\"bold\">Bold</option>\n              <option value=\"600\">Semi-Bold</option>\n              <option value=\"300\">Light</option>\n            </select>\n          </div>\n          <div class=\"style-row\">\n            <label>Font Style:</label>\n            <select id=\"text-font-style\" style=\"flex: 1;padding: 6px 10px;background: var(--panel);border: 1px solid var(--edge-main);border-radius: 4px;color: var(--text-main);cursor: pointer;\">\n              <option value=\"normal\">Normal</option>\n              <option value=\"italic\">Italic</option>\n            </select>\n          </div>\n          <div class=\"style-row\">\n            <label>Text Align:</label>\n            <select id=\"text-align\" style=\"flex: 1;padding: 6px 10px;background: var(--panel);border: 1px solid var(--edge-main);border-radius: 4px;color: var(--text-main);cursor: pointer;\">\n              <option value=\"start\">Left</option>\n              <option value=\"middle\">Center</option>\n              <option value=\"end\">Right</option>\n            </select>\n          </div>\n          <div class=\"style-row\">\n            <label>Text Decoration:</label>\n            <select id=\"text-decoration\" style=\"flex: 1;padding: 6px 10px;background: var(--panel);border: 1px solid var(--edge-main);border-radius: 4px;color: var(--text-main);cursor: pointer;\">\n              <option value=\"none\">None</option>\n              <option value=\"underline\">Underline</option>\n              <option value=\"line-through\">Strike Through</option>\n            </select>\n          </div>\n          <div class=\"style-row\">\n            <label>Background:</label>\n            <input type=\"color\" id=\"text-bg-color\" value=\"#000000\">\n            <label style=\"margin-left: 10px; display: flex; align-items: center; gap: 6px;\">\n            <input type=\"checkbox\" id=\"text-bg-enabled\" style=\"cursor: pointer;\">\n            <span style=\"font-size: 13px;\">Enable</span>\n            </label>\n          </div>\n          <div class=\"style-row\">\n            <label>Opacity:</label>\n            <input type=\"range\" id=\"text-opacity\" min=\"0\" max=\"1\" step=\"0.1\" value=\"1\" style=\"flex: 1\">\n            <span id=\"text-opacity-val\" style=\"min-width: 35px; text-align: right\">100%</span>\n          </div>\n          <div class=\"style-row\">\n            <label>Rotation:</label>\n            <input type=\"range\" id=\"text-rotation\" min=\"-360\" max=\"360\" value=\"0\" style=\"flex: 1\">\n            <input type=\"number\" id=\"text-rotation-val\" value=\"0\" style=\"width: 60px; padding: 4px; background: var(--panel); border: 1px solid var(--edge-main); border-radius: 4px; color: var(--text-main); text-align: center;\">\n            <span style=\"color: var(--text-soft);\">°</span>\n          </div>\n          <button id=\"delete-text\" style=\"margin-top: 15px;padding: 10px 16px;background: var(--danger);color: #fff;border: none;border-radius: 6px;cursor: pointer;font-size: clamp(14px, 1.6vw, 18px);font-weight: 600;width: 100%;\">Delete Text</button>\n        </div>\n     <details class=\"style-section\" open=\"\">\n          <summary>Page Layout</summary>\n          <div class=\"style-content\">\n            <div style=\"padding: 12px; background: var(--panel-alt); border-radius: 6px; color: var(--text-soft); font-size: 13px; line-height: 1.6;\">\n              <strong style=\"color: var(--accent); display: block; margin-bottom: 8px;\">Drag to Resize:</strong>\n              • <strong>Header:</strong> Drag the bottom edge of the header bar<br>\n              • <strong>Sidebar:</strong> Drag the left edge of the sidebar panel<br>\n              • <strong>Mobile:</strong> Drag the top edge of the footer panel<br>\n              <div style=\"margin-top: 8px; padding-top: 8px; border-top: 1px solid var(--edge-main); font-size: 12px;\">\n                Hover over panel edges to see resize handles\n              </div>\n            </div>\n          </div>\n        </details>\n      </aside>\n    </main>\n    <script id=\"nodes-json\" type=\"application/json\">{}</script>\n    <script id=\"topology-state\" type=\"application/json\">ENCRYPTED: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</script>\n    <script>\n\tObject.keys(localStorage).forEach(key => {\n        if (key.startsWith('icon-')) {\n          localStorage.removeItem(key);\n        }\n      });\n      const IconLibrary = {\n       libraries: {\n        mdi: {\n         name: 'Material Design Icons',\n         cdnBase: 'https://cdn.jsdelivr.net/npm/@mdi/svg@latest/svg/',\n         metaUrl: 'https://cdn.jsdelivr.net/npm/@mdi/svg@latest/meta.json',\n         icons: []\n        },\n        simple: {\n         name: 'Simple Icons',\n         cdnBase: 'https://cdn.jsdelivr.net/npm/simple-icons@latest/icons/',\n         indexUrl: 'https://cdn.jsdelivr.net/npm/@iconify-json/simple-icons@latest/icons.json',\n         icons: []\n        },\n        selfhst: {\n         name: 'selfh.st/icons',\n         cdnBase: 'https://cdn.jsdelivr.net/gh/selfhst/icons@master/svg/',\n         indexUrl: 'https://raw.githubusercontent.com/selfhst/icons/refs/heads/main/index.json',\n         icons: []\n        }\n       },\n       currentLibrary: 'selfhst',\n       iconCache: {},\n       indexCache: {},\n       indexLoading: {},\n       async loadLibraryIndex(library) {\n        if (this.indexCache[library]) {\n         return this.indexCache[library];\n        }\n        if (this.indexLoading[library]) {\n         return this.indexLoading[library];\n        }\n        const lib = this.libraries[library];\n        this.indexLoading[library] = (async () => {\n         try {\n          if (library === 'selfhst') {\n           const response = await fetch(lib.indexUrl);\n           const data = await response.json();\n           const icons = data.filter(item => item.SVG === \"Yes\").map(item => ({\n            name: item.Reference,\n            displayName: item.Name,\n            tags: item.Tags ? item.Tags.split(',').map(t => t.trim()).filter(t => t) : [],\n            category: item.Category\n           }));\n           this.indexCache[library] = icons;\n           lib.icons = icons;\n           return icons;\n          } else if (library === 'simple') {\n           const response = await fetch('https://cdn.jsdelivr.net/npm/@iconify-json/simple-icons@latest/icons.json');\n           const data = await response.json();\n           const icons = Object.keys(data.icons).map(slug => ({\n            name: slug,\n            displayName: data.icons[slug].title || slug,\n            tags: data.aliases && data.aliases[slug] ? [data.aliases[slug].parent] : [],\n            hex: data.icons[slug].hex\n           }));\n           this.indexCache[library] = icons;\n           lib.icons = icons;\n           return icons;\n          } else if (library === 'mdi') {\n           const response = await fetch(lib.metaUrl);\n           const data = await response.json();\n           const icons = data.map(item => ({\n            name: item.name,\n            displayName: item.name,\n            tags: item.tags || [],\n            author: item.author\n           }));\n           this.indexCache[library] = icons;\n           lib.icons = icons;\n           return icons;\n          }\n         } catch (error) {\n          console.error(`Failed to load index for ${library}:`, error);\n          this.indexCache[library] = [];\n          lib.icons = [];\n          return [];\n         } finally {\n          delete this.indexLoading[library];\n         }\n        })();\n        return this.indexLoading[library];\n       },\n       async getIcon(library, name) {\n        const cacheKey = `${library}-${name}`;\n        if (this.iconCache[cacheKey]) {\n         return this.iconCache[cacheKey];\n        }\n        const lib = this.libraries[library];\n        const url = `${lib.cdnBase}${name}.svg`;\n        try {\n         const response = await fetch(url);\n         if (!response.ok) {\n          throw new Error(`HTTP ${response.status}`);\n         }\n         const svg = await response.text();\n         this.iconCache[cacheKey] = svg;\n         return svg;\n        } catch (error) {\n         console.error(`Failed to fetch icon ${cacheKey}:`, error);\n         return null;\n        }\n       },\n       searchIcons(library, query) {\n        const lib = this.libraries[library];\n        if (!lib.icons.length) return [];\n        const q = query.toLowerCase();\n        return lib.icons.filter(icon => {\n         const nameMatch = icon.name.toLowerCase().includes(q);\n         const displayMatch = icon.displayName && icon.displayName.toLowerCase().includes(q);\n         const tagMatch = icon.tags && icon.tags.some(t => t.toLowerCase().includes(q));\n         const categoryMatch = icon.category && icon.category.toLowerCase().includes(q);\n         return nameMatch || displayMatch || tagMatch || categoryMatch;\n        }).slice(0, 50);\n       }\n      };\n      let iconPickerCallback = null;\n      let selectedNodeIconData = null;\n      let selectedRackIconData = null;\n      let newNodeIconTags = [];\n      let freeDrawMode = false;\n      async function checkNodeStatus(nodeId) {\n       const data = NODE_DATA[nodeId];\n       if (!data || !data.ping || !data.ping.enabled) return;\n       data.ping.status = 'checking';\n       data.ping.lastCheck = new Date().toISOString();\n       data.ping.responseTime = null;\n       updatePingIndicator(nodeId);\n       if (currentNodeId === nodeId) {\n        updatePingStatusDisplay(nodeId);\n       }\n       let url;\n       if (data.ping.protocol === 'custom') {\n        url = data.ping.customUrl;\n       } else {\n        const ip = data.ip || '0.0.0.0';\n        const protocol = data.ping.protocol || 'http';\n        url = `${protocol}://${ip}`;\n       }\n       if (!url) {\n        data.ping.status = 'unknown';\n        updatePingIndicator(nodeId);\n        if (currentNodeId === nodeId) {\n         updatePingStatusDisplay(nodeId);\n        }\n        return;\n       }\n       const timeout = data.ping.timeout || 3000;\n       const startTime = performance.now();\n       try {\n        const result = await Promise.race([\n         new Promise((resolve, reject) => {\n          const img = new Image();\n          img.onload = () => resolve('online');\n          img.onerror = () => resolve('check-fetch');\n          img.src = `${url}/favicon.ico?_=${Date.now()}`;\n         }),\n         new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), timeout))\n        ]);\n        if (result === 'check-fetch') {\n         await Promise.race([\n          fetch(url, { method: 'HEAD', mode: 'no-cors' }),\n          new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), timeout))\n         ]);\n        }\n        data.ping.status = 'online';\n        data.ping.responseTime = Math.round(performance.now() - startTime);\n       } catch (error) {\n        data.ping.status = 'offline';\n        data.ping.responseTime = null;\n       }\n       data.ping.lastCheck = new Date().toISOString();\n       updatePingIndicator(nodeId);\n       if (currentNodeId === nodeId) {\n        updatePingStatusDisplay(nodeId);\n       }\n      }\n      function rgbaToHex(val) {\n      if (!val) return \"#000000\";\n      if (val.startsWith(\"#\")) return val;\n      const m = val.match(/rgba?\\((\\d+),\\s*(\\d+),\\s*(\\d+)/);\n      if (!m) return \"#000000\";\n      const r = Number(m[1]).toString(16).padStart(2, \"0\");\n      const g = Number(m[2]).toString(16).padStart(2, \"0\");\n      const b = Number(m[3]).toString(16).padStart(2, \"0\");\n      return `#${r}${g}${b}`;\n      }\n      function updatePingIndicator(nodeId) {\n      const nodeGroup = document.querySelector(`g[data-node-id=\"${nodeId}\"]`);\n      if (!nodeGroup) return;\n      const data = NODE_DATA[nodeId];\n      if (!data || !data.ping || !data.ping.enabled) {\n       const existingIndicator = nodeGroup.querySelector('.ping-indicator');\n       if (existingIndicator) existingIndicator.remove();\n       return;\n      }\n      let indicator = nodeGroup.querySelector('.ping-indicator');\n      const label = nodeGroup.querySelector('.node-label');\n      if (!indicator && label) {\n       indicator = document.createElementNS('http://www.w3.org/2000/svg', 'circle');\n       indicator.classList.add('ping-indicator');\n       nodeGroup.appendChild(indicator);\n      }\n      if (indicator && label) {\n       const size = savedSizes[nodeId] || getDefaultSize();\n       const radius = Math.max(4, size * 0.06);\n       indicator.setAttribute('r', radius);\n       const labelBBox = label.getBBox();\n       const labelX = parseFloat(label.getAttribute('x') || 0);\n       const labelY = parseFloat(label.getAttribute('y') || 0);\n       const styles = resolveStylesForNode(nodeId);\n      const offX = styles.pingOffsetX || 0;\n      const offY = styles.pingOffsetY || 0;\n      indicator.setAttribute('cx', (labelX - labelBBox.width / 2 - radius * 1.1) + offX);\n      indicator.setAttribute('cy', (labelY - radius * 0.7) + offY);\n      }\n      if (indicator) {\n       indicator.classList.remove('online', 'offline', 'checking');\n       if (data.ping.status) indicator.classList.add(data.ping.status);\n      }\n      }\n      async function checkAllNodesStatus() {\n       const nodesToCheck = Object.keys(NODE_DATA).filter(nodeId => {\n        const data = NODE_DATA[nodeId];\n        return data && data.ping && data.ping.enabled;\n       });\n       const batchSize = 5;\n       for (let i = 0; i < nodesToCheck.length; i += batchSize) {\n        const batch = nodesToCheck.slice(i, i + batchSize);\n        await Promise.all(batch.map(nodeId => checkNodeStatus(nodeId)));\n       }\n      }\n      function startAutoPing() {\n       stopAutoPing();\n       checkAllNodesStatus();\n       updateAutoPingLastRun();\n       autoPingSecondsRemaining = autoPingInterval;\n       autoPingTimer = setInterval(() => {\n        checkAllNodesStatus();\n        updateAutoPingLastRun();\n        autoPingSecondsRemaining = autoPingInterval;\n       }, autoPingInterval * 1000);\n       autoPingCountdown = setInterval(() => {\n        autoPingSecondsRemaining--;\n        updateAutoPingCountdown();\n        if (autoPingSecondsRemaining <= 0) {\n         autoPingSecondsRemaining = autoPingInterval;\n        }\n       }, 1000);\n       updateAutoPingCountdown();\n      }\n      function stopAutoPing() {\n       if (autoPingTimer) {\n        clearInterval(autoPingTimer);\n        autoPingTimer = null;\n       }\n       if (autoPingCountdown) {\n        clearInterval(autoPingCountdown);\n        autoPingCountdown = null;\n       }\n       autoPingSecondsRemaining = 0;\n       updateAutoPingCountdown();\n      }\n      function updateAutoPingCountdown() {\n       const nextCheckEl = document.getElementById('auto-ping-next-check');\n       if (nextCheckEl) {\n        if (autoPingSecondsRemaining > 0 && autoPingEnabled) {\n         const mins = Math.floor(autoPingSecondsRemaining / 60);\n         const secs = autoPingSecondsRemaining % 60;\n         if (mins > 0) {\n          nextCheckEl.textContent = `Next check in: ${mins}m ${secs}s`;\n         } else {\n          nextCheckEl.textContent = `Next check in: ${secs}s`;\n         }\n        } else {\n         nextCheckEl.textContent = 'Next check in: --';\n        }\n       }\n      }\n      function updateAutoPingLastRun() {\n       const lastRunEl = document.getElementById('auto-ping-last-run');\n       if (lastRunEl) {\n        const now = new Date();\n        lastRunEl.textContent = `Last run: ${now.toLocaleTimeString()}`;\n       }\n      }\n      function openIconPicker(callback) {\n       iconPickerCallback = callback;\n       const modal = document.getElementById('icon-picker-modal');\n       modal.classList.add('active');\n       const searchInput = document.getElementById('icon-search');\n       searchInput.style.display = 'none';\n       loadIconsForCurrentLibrary();\n      }\n      function closeIconPicker() {\n       const modal = document.getElementById('icon-picker-modal');\n       modal.classList.remove('active');\n       iconPickerCallback = null;\n      }\n      async function loadIconsForCurrentLibrary() {\n       const body = document.getElementById('icon-picker-body');\n       const libNames = {\n        mdi: 'MDI (Material Design Icons)',\n        simple: 'Simple Icons',\n        selfhst: 'selfh.st/icons'\n       };\n       body.innerHTML = `<div style=\"padding: 20px;\"><p style=\"color: var(--text-soft); margin-bottom: 15px; text-align: center;\">Search ${libNames[IconLibrary.currentLibrary]}:</p><input type=\"text\" id=\"icon-search-field\" placeholder=\"Search icons...\" style=\"width: 100%; padding: 12px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 6px; color: var(--text-main); font-size: 16px; margin-bottom: 20px;\"><div id=\"icon-grid-container\" style=\"max-height: 400px; overflow-y: auto;\"><div style=\"text-align: center; color: var(--text-soft); padding: 40px;\">Loading icons...</div></div></div>`;\n       const searchField = document.getElementById('icon-search-field');\n       const gridContainer = document.getElementById('icon-grid-container');\n       await IconLibrary.loadLibraryIndex(IconLibrary.currentLibrary);\n       const renderIcons = (icons) => {\n        if (!icons || icons.length === 0) {\n         gridContainer.innerHTML = '<div style=\"text-align: center; color: var(--text-soft); padding: 40px;\">No icons found</div>';\n         return;\n        }\n        const grid = document.createElement('div');\n        grid.style.cssText = 'display: grid; grid-template-columns: repeat(auto-fill, minmax(100px, 1fr)); gap: 15px; padding: 10px;';\n        icons.forEach(icon => {\n         const item = document.createElement('div');\n         item.style.cssText = 'display: flex; flex-direction: column; align-items: center; gap: 8px; padding: 15px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 6px; cursor: pointer; transition: all 0.2s;';\n         item.onmouseover = () => {\n          item.style.background = 'var(--panel)';\n          item.style.borderColor = 'var(--accent)';\n         };\n         item.onmouseout = () => {\n          item.style.background = 'var(--panel-alt)';\n          item.style.borderColor = 'var(--edge-main)';\n         };\n         const iconPreview = document.createElement('div');\n         iconPreview.style.cssText = 'width: 48px; height: 48px; display: flex; align-items: center; justify-content: center;';\n         iconPreview.innerHTML = '<div style=\"color: var(--text-soft); font-size: 12px;\">...</div>';\n         IconLibrary.getIcon(IconLibrary.currentLibrary, icon.name).then(svg => {\n          if (svg) {\n           const parser = new DOMParser();\n           const doc = parser.parseFromString(svg, 'image/svg+xml');\n           const svgEl = doc.querySelector('svg');\n           if (svgEl) {\n            svgEl.setAttribute('width', '48');\n            svgEl.setAttribute('height', '48');\n            svgEl.style.fill = 'var(--text-main)';\n            iconPreview.innerHTML = '';\n            iconPreview.appendChild(svgEl);\n           }\n          }\n         });\n         const name = document.createElement('div');\n         name.textContent = icon.displayName || icon.name;\n         name.style.cssText = 'font-size: 11px; color: var(--text-soft); text-align: center; word-break: break-word; max-width: 100%;';\n         item.appendChild(iconPreview);\n         item.appendChild(name);\n         item.addEventListener('click', async () => {\n          const svg = await IconLibrary.getIcon(IconLibrary.currentLibrary, icon.name);\n          if (iconPickerCallback && svg) {\n           iconPickerCallback({\n            library: IconLibrary.currentLibrary,\n            name: icon.name,\n            svg: svg\n           });\n          }\n          closeIconPicker();\n         });\n         grid.appendChild(item);\n        });\n        gridContainer.innerHTML = '';\n        gridContainer.appendChild(grid);\n       };\n       gridContainer.innerHTML = '<div style=\"text-align: center; color: var(--text-soft); padding: 40px;\">Type to search icons</div>';\n       let searchTimeout;\n       searchField.addEventListener('input', (e) => {\n        clearTimeout(searchTimeout);\n        const query = e.target.value.trim();\n        searchTimeout = setTimeout(() => {\n         if (!query) {\n          gridContainer.innerHTML = '<div style=\"text-align: center; color: var(--text-soft); padding: 40px;\">Type to search icons</div>';\n          return;\n         }\n         const results = IconLibrary.searchIcons(IconLibrary.currentLibrary, query);\n         renderIcons(results);\n        }, 300);\n       });\n       searchField.focus();\n      }\n      async function displayIcons(icons) {\n       const body = document.getElementById('icon-picker-body');\n       const grid = document.createElement('div');\n       grid.className = 'icon-grid';\n       for (const icon of icons) {\n        const item = document.createElement('div');\n        item.className = 'icon-item';\n        const svg = await IconLibrary.getIcon(icon.library, icon.name);\n        if (svg) {\n         const parser = new DOMParser();\n         const doc = parser.parseFromString(svg, 'image/svg+xml');\n         const svgEl = doc.querySelector('svg');\n         if (svgEl) {\n          item.innerHTML = svgEl.outerHTML;\n         }\n        } else {\n         item.innerHTML = '<svg width = \"32\" height = \"32\"><rect width = \"32\" height = \"32\" fill = \"currentColor\"/> </svg>';\n        }\n        const name = document.createElement('div');\n        name.className = 'icon-item-name';\n        name.textContent = icon.name;\n        item.appendChild(name);\n        item.addEventListener('click', () => {\n         if (iconPickerCallback) {\n          iconPickerCallback({\n           library: icon.library,\n           name: icon.name,\n           svg: svg\n          });\n         }\n         closeIconPicker();\n        });\n        grid.appendChild(item);\n       }\n       body.innerHTML = '';\n       body.appendChild(grid);\n      }\n      window.addEventListener('DOMContentLoaded', () => {\n       document.querySelectorAll('.icon-picker-tab').forEach(tab => {\n        tab.addEventListener('click', () => {\n         document.querySelectorAll('.icon-picker-tab').forEach(t => t.classList.remove('active'));\n         tab.classList.add('active');\n         IconLibrary.currentLibrary = tab.dataset.library;\n         loadIconsForCurrentLibrary();\n        });\n       });\n       document.getElementById('icon-picker-cancel').addEventListener('click', closeIconPicker);\n       document.getElementById('icon-picker-modal').addEventListener('click', (e) => {\n        if (e.target.id === 'icon-picker-modal') {\n         closeIconPicker();\n        }\n       });\n      });\n      let textDrawMode = false;\n      const BASE_NODE_DATA = JSON.parse(document.getElementById(\"nodes-json\").textContent);\n      let currentNodeId = \"host\";\n      let currentEdgeId = null;\n      let currentStyleScope = \"all\";\n      let NODE_DATA = {};\n      let EDGE_DATA = {\n       list: []\n      };\n      let currentRectId = null;\n      let currentTextId = null;\n      let rectDrawMode = false;\n      let RECT_DATA = {\n       list: []\n      };\n      let TEXT_DATA = {\n       list: []\n      };\n      let EDGE_LEGEND = {};\n      let savedPositions = {};\n      let savedSizes = {};\n      let savedStyles = {};\n      let legendCollapsed = false;\n      let minimapCollapsed = false;\n      let drawToolbarCollapsed = false;\n      let currentView = {\n       mode: \"topology\",\n       rackId: null\n      };\n      let savedTopologyView = null;\n      let activeLayers = new Set([\"physical\", \"logical\", \"security\", \"application\"]);\n      let topologyToolbarCollapsed = false;\n      let legendMiniBtn = null;\n      let minimapMiniBtn = null;\n      let drawToolbarMiniBtn = null;\n      let topologyToolbarMiniBtn = null;\n      let autoPingEnabled = false;\n      let autoPingInterval = 30;\n      let autoPingTimer = null;\n      let autoPingCountdown = null;\n      let autoPingSecondsRemaining = 0;\n      const ROLLBACK_STORAGE_KEY = \"theonefile_rollback_history\";\n      let rollbackVersions = [];\n      const MAX_ROLLBACK_VERSIONS = 50;\n      let currentRollbackIndex = -1;\n      const AUDIT_STORAGE_KEY = \"theonefile_audit_log\";\n      let auditLog = [];\n      const MAX_AUDIT_ENTRIES = 1000;\n      let savedStyleSets = [];\n      let documentTabs = [{\n        id: \"main\",\n        name: \"Main Topology\",\n        nodes: {},\n        edges: { list: [] },\n        positions: {},\n        sizes: {},\n        styles: {},\n        legend: {},\n        rects: { list: [] },\n        texts: { list: [] },\n      pageState: null\n      }];\n      let currentTabIndex = 0;\n      let encryptedSections = {};\n      let undoStack = [];\n      let redoStack = [];\n      const MAX_UNDO_STACK = 50;\n      let forgeDebounceTimer = null;\n      let forgeImmediate = false;\n      let currentSearchQuery = \"\";\n      let currentSearchResults = [];\n      const AUTOSAVE_DB_NAME = \"TheOneFileNetworkeningAutosave\";\n      const AUTOSAVE_STORE_NAME = \"drafts\";\n      const AUTOSAVE_KEY = \"currentDraft\";\n      const AUTOSAVE_INTERVAL = 30000;\n      let autosaveTimer = null;\n      let lastAutosaveTime = 0;\n      let selectedNodes = new Set();\n      let selectedEdges = new Set();\n      let selectedRects = new Set();\n      let selectedTexts = new Set();\n      let isSelecting = false;\n      let selectionStart = null;\n      let preDragSelectedNodes = new Set();\n      let preDragSelectedEdges = new Set();\n      let preDragSelectedRects = new Set();\n      let preDragSelectedTexts = new Set();\n      let selectionRect = null;\n      let isDraggingSelection = false;\n      let dragSelectionStart = null;\n      let selectionBoxStyle = {\n       fillColor: \"#4fd1c5\",\n       fillOpacity: 0.1,\n       strokeColor: \"#4fd1c5\",\n       strokeWidth: 2,\n       strokeDasharray: \"5,5\"\n      };\n      let clipboard = null;\n\t  function escapeHtml(str) {\n       if (!str) return '';\n       return String(str).replace(/[&<>\"']/g, c => ({\n        '&': '&amp;', '<': '&lt;', '>': '&gt;', '\"': '&quot;', \"'\": '&#39;'\n       }[c]));\n      }\n      const MobileManager = {\n        isMobile: false,\n        detect() {\n          if (navigator.userAgentData?.mobile === true) {\n            this.isMobile = true;\n            return true;\n          }\n          const coarse = matchMedia(\"(pointer: coarse)\").matches;\n          const width = window.innerWidth <= 900;\n          const portrait = matchMedia(\"(orientation: portrait)\").matches;\n          this.isMobile = coarse;\n          return this.isMobile;\n        },\n        applyInitialCollapse() {\n          if (!this.isMobile) return;\n          legendCollapsed = true;\n          minimapCollapsed = true;\n          drawToolbarCollapsed = true;\n          topologyToolbarCollapsed = true;\n          if (typeof updateLegendVisibility === \"function\") updateLegendVisibility();\n          if (typeof updateMinimapVisibility === \"function\") updateMinimapVisibility();\n          if (typeof updateDrawToolbarVisibility === \"function\") updateDrawToolbarVisibility();\n          if (typeof updateTopologyToolbarVisibility === \"function\") updateTopologyToolbarVisibility();\n        },\n        updateBulkToolbar() {\n          const desktop = document.getElementById(\"bulk-toolbar\");\n          const mobile = document.getElementById(\"bulk-toolbar-mobile\");\n          if (!desktop || !mobile) return;\n          if (typeof isViewOnly === 'function' && isViewOnly()) {\n           desktop.style.display = \"none\";\n           mobile.style.display = \"none\";\n           return;\n          }\n          if (typeof selectedNodes === 'undefined' || selectedNodes.size === 0) {\n           desktop.style.display = \"none\";\n           mobile.style.display = \"none\";\n           return;\n          }\n          const isVisible = desktop.style.display !== \"none\" || mobile.style.display !== \"none\";\n          if (this.isMobile) {\n            desktop.style.display = \"none\";\n            if (isVisible) {\n              mobile.style.display = \"flex\";\n            }\n          } else {\n            mobile.style.display = \"none\";\n            if (isVisible) {\n              desktop.style.display = \"flex\";\n            }\n          }\n        },\n        updateCanvasHint() {\n      const hint = document.getElementById(\"canvas-hint\");\n      if (!hint) return;\n      if (PAGE_STATE.canvasHintText !== undefined && PAGE_STATE.canvasHintText !== \"\") {\n        hint.textContent = PAGE_STATE.canvasHintText;\n        return;\n      }\n      const items = this.isMobile\n      ? [\n        \"Pinch to zoom\",\n        \"Drag to pan\",\n        \"Add node from top menu\",\n        \"Double tap to clone and align\",\n        \"Double tap to select multiple\",\n        \"You have the power\",\n        \"Your time is NOW!\",\n      ]\n      : [\n        \"Scroll to zoom\",\n        \"Drag to pan\",\n        \"Right click to clone and align\",\n        \"Right click to select multiple\",\n        \"You have the power\",\n        \"Your time is NOW!\",\n      ];\n      const list = document.createElement(\"ul\");\n      for (const item of items) {\n      const li = document.createElement(\"li\");\n      li.textContent = item;\n      list.appendChild(li);\n      }\n      hint.replaceChildren(list);\n      },\n        autoSelectStyleScope() {\n          const scope = document.getElementById(\"style-scope\");\n          if (!scope) return;\n          if (this.isMobile) scope.value = \"mobile\";\n        },\n        updateToolbarStack() {\n          if (!this.isMobile) return;\n          const draw = document.getElementById(\"draw-toolbar\");\n          const topo = document.getElementById(\"topology-toolbar\");\n          if (!draw || !topo) return;\n          const h = draw.getBoundingClientRect().height;\n          document.documentElement.style.setProperty(\"--draw-toolbar-height\", h + \"px\");\n        },\n        updateLayoutControls() {\n          const sidebarRow = document.getElementById(\"sidebar-width-row\");\n          const footerRow = document.getElementById(\"mobile-footer-row\");\n          if (sidebarRow && footerRow) {\n            if (this.isMobile) {\n              sidebarRow.style.display = \"none\";\n              footerRow.style.display = \"flex\";\n            } else {\n              sidebarRow.style.display = \"flex\";\n              footerRow.style.display = \"none\";\n            }\n          }\n        },\n        applyAll() {\n          this.detect();\n          this.applyInitialCollapse();\n          this.updateBulkToolbar();\n          this.updateCanvasHint();\n          this.autoSelectStyleScope();\n          this.updateToolbarStack();\n          this.updateLayoutControls();\n        }\n      };\n      function isMobileDevice() {\n        return MobileManager.isMobile;\n      }\n      function ensureLegendMiniButton() {\n\t\tif (legendMiniBtn) return legendMiniBtn;\n\t\tconst handleClick = (e) => {\n\t\t\te.stopPropagation();\n\t\t\te.preventDefault();\n\t\t\tlegendCollapsed = false;\n\t\t\tupdateLegendVisibility();\n\t\t};\n\t\tconst preventTouch = (e) => { e.preventDefault(); };\n\t\tconst existing = document.getElementById(\"edge-legend-mini\");\n\t\tif (existing) {\n\t\t\texisting.addEventListener(\"click\", handleClick);\n\t\t\texisting.addEventListener(\"touchstart\", preventTouch, { passive: false });\n\t\t\texisting.addEventListener(\"touchend\", handleClick);\n\t\t\tlegendMiniBtn = existing;\n\t\t\treturn existing;\n\t\t}\n\t\tconst panel = document.querySelector(\".topology-panel\");\n\t\tif (!panel) return null;\n\t\t   const btn = document.createElement(\"button\");\n\t\t   btn.type = \"button\";\n\t\t   btn.id = \"edge-legend-mini\";\n\t\t   btn.className = \"legend-mini-btn\";\n\t\t   btn.textContent = \"Legend\";\n\t\t   btn.style.bottom = \"10px\";\n\t\t   btn.addEventListener(\"click\", handleClick);\n\t\t   btn.addEventListener(\"touchstart\", preventTouch, { passive: false });\n\t\t   btn.addEventListener(\"touchend\", handleClick);\n\t\t   panel.appendChild(btn);\n\t\t   legendMiniBtn = btn;\n\t\t   return btn;\n\t\t}\n\t\t\t function ensureMinimapMiniButton() {\n\t\t   if (minimapMiniBtn) return minimapMiniBtn;\n\t\t   const handleClick = (e) => {\n\t\t\te.stopPropagation();\n\t\t\te.preventDefault();\n\t\t\tminimapCollapsed = false;\n\t\t\tupdateMinimapVisibility();\n\t\t   };\n\t\t   const preventTouch = (e) => { e.preventDefault(); };\n\t\t   const existing = document.getElementById(\"minimap-mini\");\n\t\t   if (existing) {\n\t\t\texisting.addEventListener(\"click\", handleClick);\n\t\t\texisting.addEventListener(\"touchstart\", preventTouch, { passive: false });\n\t\t\texisting.addEventListener(\"touchend\", handleClick);\n\t\t\tminimapMiniBtn = existing;\n\t\t\treturn existing;\n\t\t   }\n\t\t   const panel = document.querySelector(\".topology-panel\");\n\t\t   if (!panel) return null;\n\t\t   const btn = document.createElement(\"button\");\n\t\t   btn.type = \"button\";\n\t\t   btn.id = \"minimap-mini\";\n\t\t   btn.className = \"legend-mini-btn\";\n\t\t   btn.textContent = \"Map\";\n\t\t   btn.style.right = \"10px\";\n\t\t   btn.style.left = \"auto\";\n\t\t   btn.style.bottom = \"10px\";\n\t\t   btn.addEventListener(\"click\", handleClick);\n\t\t   btn.addEventListener(\"touchstart\", preventTouch, { passive: false });\n\t\t   btn.addEventListener(\"touchend\", handleClick);\n\t\t   panel.appendChild(btn);\n\t\t   minimapMiniBtn = btn;\n\t\t   return btn;\n\t\t}\n\t\t\t  function ensureDrawToolbarMiniButton() {\n\t\t   if (drawToolbarMiniBtn) return drawToolbarMiniBtn;\n\t\t   const handleClick = (e) => {\n\t\t\te.stopPropagation();\n\t\t\te.preventDefault();\n\t\t\tdrawToolbarCollapsed = false;\n\t\t\tupdateDrawToolbarVisibility();\n\t\t   };\n\t\t   const preventTouch = (e) => { e.preventDefault(); };\n\t\t   const existing = document.getElementById(\"draw-toolbar-mini\");\n\t\t   if (existing) {\n\t\t\texisting.addEventListener(\"click\", handleClick);\n\t\t\texisting.addEventListener(\"touchstart\", preventTouch, { passive: false });\n\t\t\texisting.addEventListener(\"touchend\", handleClick);\n\t\t\tdrawToolbarMiniBtn = existing;\n\t\t\treturn existing;\n\t\t   }\n\t\t   const panel = document.querySelector(\".topology-panel\");\n\t\t   if (!panel) return null;\n\t\t   const btn = document.createElement(\"button\");\n\t\t   btn.type = \"button\";\n\t\t   btn.id = \"draw-toolbar-mini\";\n\t\t   btn.className = \"legend-mini-btn\";\n\t\t   btn.textContent = \"Draw\";\n\t\t   btn.style.top = \"10px\";\n\t\t   btn.style.left = \"10px\";\n\t\t   btn.style.right = \"auto\";\n\t\t   btn.addEventListener(\"click\", handleClick);\n\t\t   btn.addEventListener(\"touchstart\", preventTouch, { passive: false });\n\t\t   btn.addEventListener(\"touchend\", handleClick);\n\t\t   panel.appendChild(btn);\n\t\t   drawToolbarMiniBtn = btn;\n\t\t   return btn;\n\t\t}\n\t\t  function ensureTopologyToolbarMiniButton() {\n\t   if (topologyToolbarMiniBtn) return topologyToolbarMiniBtn;\n\t   const handleClick = (e) => {\n\t\te.stopPropagation();\n\t\te.preventDefault();\n\t\ttopologyToolbarCollapsed = false;\n\t\tupdateTopologyToolbarVisibility();\n\t   };\n\t   const preventTouch = (e) => { e.preventDefault(); };\n\t   const existing = document.getElementById(\"topology-toolbar-mini\");\n\t   if (existing) {\n\t\texisting.addEventListener(\"click\", handleClick);\n\t\texisting.addEventListener(\"touchstart\", preventTouch, { passive: false });\n\t\texisting.addEventListener(\"touchend\", handleClick);\n\t\ttopologyToolbarMiniBtn = existing;\n\t\treturn existing;\n\t   }\n\t   const panel = document.querySelector(\".topology-panel\");\n\t   if (!panel) return null;\n\t   const btn = document.createElement(\"button\");\n\t   btn.type = \"button\";\n\t   btn.id = \"topology-toolbar-mini\";\n\t   btn.className = \"legend-mini-btn\";\n\t   btn.textContent = \"Add Line\";\n\t   btn.style.top = \"10px\";\n\t   btn.style.left = \"auto\";\n\t   btn.style.right = \"40px\";\n\t   btn.addEventListener(\"click\", handleClick);\n\t   btn.addEventListener(\"touchstart\", preventTouch, { passive: false });\n\t   btn.addEventListener(\"touchend\", handleClick);\n\t   panel.appendChild(btn);\n\t   topologyToolbarMiniBtn = btn;\n\t   return btn;\n\t}\n      function updateToolbarStack() {\n       if (!isMobileDevice()) return;\n       const draw = document.getElementById(\"draw-toolbar\");\n       const topo = document.getElementById(\"topology-toolbar\");\n       if (!draw || !topo) return;\n       const h = draw.getBoundingClientRect().height;\n       document.documentElement.style.setProperty(\"--draw-toolbar-height\", h + \"px\");\n      }\n      window.addEventListener(\"resize\", updateToolbarStack);\n      window.addEventListener(\"DOMContentLoaded\", updateToolbarStack);\n      updateToolbarStack();\n      function updateLegendVisibility() {\n       const legend = document.getElementById(\"edge-legend\");\n       const mini = ensureLegendMiniButton();\n       if (!legend || !mini) return;\n       const hasItems = legend.querySelectorAll(\".legend-item\").length > 0;\n       if (!hasItems) {\n        legend.style.display = \"none\";\n        mini.style.display = \"none\";\n        return;\n       }\n       if (legendCollapsed) {\n        legend.style.display = \"none\";\n        mini.style.display = \"inline-flex\";\n       } else {\n        legend.style.display = \"flex\";\n        mini.style.display = \"none\";\n       }\n      }\n      function updateMinimapVisibility() {\n       const wrapper = document.getElementById(\"minimap-zoom-wrapper\");\n       const mini = ensureMinimapMiniButton();\n       if (!wrapper || !mini) return;\n       if (minimapCollapsed) {\n        wrapper.style.display = \"none\";\n        mini.style.display = \"inline-flex\";\n       } else {\n        wrapper.style.display = \"block\";\n        mini.style.display = \"none\";\n       }\n      }\n      function updateDrawToolbarVisibility() {\n       const toolbar = document.getElementById(\"draw-toolbar\");\n       const mini = ensureDrawToolbarMiniButton();\n       if (!toolbar || !mini) return;\n       if (typeof isViewOnly === 'function' && isViewOnly()) {\n        toolbar.style.setProperty('display', 'none', 'important');\n        mini.style.display = \"none\";\n        return;\n       }\n       if (drawToolbarCollapsed) {\n        toolbar.style.setProperty('display', 'none', 'important');\n        mini.style.display = \"inline-flex\";\n       } else {\n      if (isMobileDevice()) {\n        toolbar.style.setProperty('display', 'grid', 'important');\n        mini.style.display = \"none\";\n        } else {\n        toolbar.style.setProperty('display', 'flex', 'important');\n        mini.style.display = \"none\";\n      }\n       }\n      }\n      function u8ToBase64(u8) {\n      let binary = \"\";\n      for (let i = 0; i < u8.length; i++) binary += String.fromCharCode(u8[i]);\n      return btoa(binary);\n      }\n      function base64ToU8(base64) {\n      const binary = atob(base64);\n      const bytes = new Uint8Array(binary.length);\n      for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);\n      return bytes;\n      }\n      function openAutosaveDB() {\n       return new Promise((resolve, reject) => {\n        const request = indexedDB.open(AUTOSAVE_DB_NAME, 1);\n        request.onerror = () => reject(request.error);\n        request.onsuccess = () => resolve(request.result);\n        request.onupgradeneeded = (e) => {\n         const db = e.target.result;\n         if (!db.objectStoreNames.contains(AUTOSAVE_STORE_NAME)) {\n          db.createObjectStore(AUTOSAVE_STORE_NAME, { keyPath: \"id\" });\n         }\n        };\n       });\n      }\n      async function saveToIndexedDB() {\n       try {\n        const db = await openAutosaveDB();\n        const currentTab = documentTabs[currentTabIndex];\n        currentTab.nodes = JSON.parse(JSON.stringify(NODE_DATA));\n        currentTab.edges = JSON.parse(JSON.stringify(EDGE_DATA));\n        currentTab.positions = JSON.parse(JSON.stringify(savedPositions));\n        currentTab.sizes = JSON.parse(JSON.stringify(savedSizes));\n        currentTab.styles = JSON.parse(JSON.stringify(savedStyles));\n        currentTab.legend = JSON.parse(JSON.stringify(EDGE_LEGEND));\n        currentTab.rects = JSON.parse(JSON.stringify(RECT_DATA));\n        currentTab.texts = JSON.parse(JSON.stringify(TEXT_DATA));\n        currentTab.pageState = JSON.parse(JSON.stringify(PAGE_STATE));\n        const state = {\n         id: AUTOSAVE_KEY,\n         timestamp: Date.now(),\n         pageTitle: document.getElementById(\"page-title\")?.textContent || \"The One File\",\n         nodeData: NODE_DATA,\n         edgeData: EDGE_DATA,\n         rectData: RECT_DATA,\n         textData: TEXT_DATA,\n         edgeLegend: EDGE_LEGEND,\n         zoneLegend: ZONE_LEGEND,\n         zonePresets: ZONE_PRESETS,\n         nodePositions: savedPositions,\n         nodeSizes: savedSizes,\n         nodeStyles: savedStyles,\n         page: PAGE_STATE,\n         canvas: {\n          zoom: canvasState.zoom,\n          panX: canvasState.panX,\n          panY: canvasState.panY,\n         },\n         savedTopologyView: savedTopologyView,\n         documentTabs: documentTabs,\n         currentTabIndex: currentTabIndex,\n         encryptedSections: encryptedSections,\n         auditLog: auditLog,\n\t\t savedStyleSets: savedStyleSets,\n\t\t selectedTheme: document.getElementById(\"theme-preset\")?.value || \"defaulted\",\n        };\n        const tx = db.transaction(AUTOSAVE_STORE_NAME, \"readwrite\");\n        const store = tx.objectStore(AUTOSAVE_STORE_NAME);\n        store.put(state);\n        await new Promise((resolve, reject) => {\n         tx.oncomplete = resolve;\n         tx.onerror = () => reject(tx.error);\n        });\n        lastAutosaveTime = Date.now();\n        console.log(\"Auto-saved to IndexedDB at\", new Date().toLocaleTimeString());\n       } catch (e) {\n        console.warn(\"Auto-save failed:\", e);\n       }\n      }\n      async function loadFromIndexedDB() {\n       try {\n        const db = await openAutosaveDB();\n        const tx = db.transaction(AUTOSAVE_STORE_NAME, \"readonly\");\n        const store = tx.objectStore(AUTOSAVE_STORE_NAME);\n        const request = store.get(AUTOSAVE_KEY);\n        return new Promise((resolve, reject) => {\n         request.onsuccess = () => resolve(request.result);\n         request.onerror = () => reject(request.error);\n        });\n       } catch (e) {\n        console.warn(\"Load from IndexedDB failed:\", e);\n        return null;\n       }\n      }\n      async function clearAutosave() {\n       try {\n        const db = await openAutosaveDB();\n        const tx = db.transaction(AUTOSAVE_STORE_NAME, \"readwrite\");\n        const store = tx.objectStore(AUTOSAVE_STORE_NAME);\n        store.delete(AUTOSAVE_KEY);\n       } catch (e) {\n        console.warn(\"Clear autosave failed:\", e);\n       }\n      }\n      function startAutosave() {\n       if (autosaveTimer) clearInterval(autosaveTimer);\n       autosaveTimer = setInterval(() => {\n        if (Object.keys(NODE_DATA).length > 0 || EDGE_DATA.list.length > 0 || documentTabs.length > 1) {\n         saveToIndexedDB();\n        }\n       }, AUTOSAVE_INTERVAL);\n      }\n      async function checkForAutosaveRecovery() {\n       try {\n        const saved = await loadFromIndexedDB();\n        if (saved && saved.timestamp) {\n         const age = Date.now() - saved.timestamp;\n         const hasCurrentData = Object.keys(NODE_DATA).length > 0 || EDGE_DATA.list.length > 0;\n         const savedNodeCount = saved.nodeData ? Object.keys(saved.nodeData).length : 0;\n         const savedTabCount = saved.documentTabs ? saved.documentTabs.length : 1;\n         if (age < 300000 && (savedNodeCount > 0 || savedTabCount > 1)) {\n          const savedDate = new Date(saved.timestamp).toLocaleString();\n          const tabInfo = savedTabCount > 1 ? ` across ${savedTabCount} tabs` : \"\";\n          if (!hasCurrentData || confirm(`Recover unsaved work from ${savedDate}?\\n(${savedNodeCount} nodes${tabInfo})\\n\\nClick OK to recover, Cancel to start fresh.`)) {\n           if (!hasCurrentData || confirm(\"This will replace current data. Continue?\")) {\n            NODE_DATA = saved.nodeData || {};\n            EDGE_DATA = saved.edgeData || { list: [] };\n            RECT_DATA = saved.rectData || { list: [] };\n            TEXT_DATA = saved.textData || { list: [] };\n            EDGE_LEGEND = saved.edgeLegend || {};\n            ZONE_LEGEND = saved.zoneLegend || {};\n            ZONE_PRESETS = saved.zonePresets || {};\n            loadCustomPresets();\n            savedPositions = saved.nodePositions || {};\n            savedSizes = saved.nodeSizes || {};\n            savedStyles = saved.nodeStyles || {};\n            if (saved.page) PAGE_STATE = Object.assign({}, DEFAULT_PAGE_STATE, saved.page);\n            if (saved.canvas) {\n             canvasState.zoom = saved.canvas.zoom || 1;\n             canvasState.panX = saved.canvas.panX || 0;\n             canvasState.panY = saved.canvas.panY || 0;\n            }\n            if (saved.savedTopologyView) savedTopologyView = saved.savedTopologyView;\n            if (saved.savedStyleSets) savedStyleSets = saved.savedStyleSets;\n            if (saved.selectedTheme) {\n              rebuildThemeDropdown();\n              document.getElementById(\"theme-preset\").value = saved.selectedTheme;\n              updateDeleteButton();\n            }\n            if (saved.documentTabs) documentTabs = saved.documentTabs;\n            if (saved.currentTabIndex !== undefined) currentTabIndex = saved.currentTabIndex;\n            if (saved.encryptedSections) encryptedSections = saved.encryptedSections;\n            if (saved.auditLog) auditLog = saved.auditLog;\n            if (saved.pageTitle) {\n             const titleEl = document.getElementById(\"page-title\");\n             if (titleEl) titleEl.textContent = saved.pageTitle;\n            }\n            wieldThePower();\n            forgeTheTopology();\n            updateViewBox();\n            console.log(\"Recovered from autosave:\", savedNodeCount, \"nodes,\", savedTabCount, \"tabs\");\n            return true;\n           }\n          }\n         }\n        }\n       } catch (e) {\n        console.warn(\"Autosave recovery check failed:\", e);\n       }\n       return false;\n      }\n      function updateTopologyToolbarVisibility() {\n       const toolbar = document.getElementById(\"topology-toolbar\");\n       const mini = ensureTopologyToolbarMiniButton();\n       if (!toolbar || !mini) return;\n       if (typeof isViewOnly === 'function' && isViewOnly()) {\n        toolbar.style.setProperty('display', 'none', 'important');\n        mini.style.display = \"none\";\n        return;\n       }\n       const hasSelectedNode = currentNodeId !== null;\n       if (!hasSelectedNode) {\n        toolbar.style.setProperty('display', 'none', 'important');\n        mini.style.display = \"none\";\n       } else if (topologyToolbarCollapsed) {\n        toolbar.style.setProperty('display', 'none', 'important');\n        mini.style.display = \"inline-flex\";\n       } else {\n      if (isMobileDevice()) {\n        toolbar.style.setProperty('display', 'grid', 'important');\n        mini.style.display = \"none\";\n        } else {\n        toolbar.style.setProperty('display', 'flex', 'important');\n        mini.style.display = \"none\";\n      }\n       }\n      }\n      const DEFAULT_CANVAS_HINT = document.getElementById(\"canvas-hint\")?.innerHTML || \"\";\n      const DEFAULT_PAGE_STATE = {\n       title: \"The One File: The Networkening\",\n       background: \"\",\n       topbarBg: \"rgba(9, 12, 20, 0.9)\",\n       topbarBorder: \"#1f2533\",\n       panel: \"#0b0e13\",\n       panelAlt: \"#10141b\",\n       accent: \"#4fd1c5\",\n       sidebarBg: \"#10141b\",\n       btnBg: \"#0b0e13\",\n       btnText: \"#e2e8f0\",\n       tagFill: \"#1e293b\",\n       tagText: \"#e2e8f0\",\n       tagBorder: \"#475569\",\n       inputBg: \"#0b0e13\",\n       inputText: \"#e2e8f0\",\n       inputBorder: \"#1f2937\",\n       inputFont: \"Inter, system-ui, sans-serif\",\n       inputFontSize: 14,\n       toolbarBg: \"#0f172a\",\n       toolbarBorder: \"#1f2937\",\n       toolbarText: \"#94a3b8\",\n       toolbarBtnBg: \"#0b0e13\",\n       toolbarBtnText: \"#e2e8f0\",\n       minimapDots: \"#94a3b8\",\n       canvasHintEnabled: true,\n       canvasHintText: \"\",\n       canvasHintBg: \"#0f172a\",\n       canvasHintColor: \"#94a3b8\",\n       danger: \"#f56565\",\n       textMain: \"#e2e8f0\",\n       textSoft: \"#94a3b8\",\n       topbarHeight: 52,\n       sidebarWidth: 350,\n       mobileFooterHeight: 40,\n       sidebarCollapsed: false,\n       nodeFill: \"#1e293b\",\n       nodeStroke: \"#475569\",\n       nodeTitle: \"#e2e8f0\",\n       nodeSub: \"#94a3b8\",\n       nodeTitleSize: 18,\n       nodeSubSize: 13,\n       nodeFont: \"Inter, system-ui, sans-serif\",\n       defaultEdge: \"#475569\",\n       selectionHandle: \"#f59e0b\",\n       selectionHandleSize: 8,\n       groupIndicator: \"#4fd1c5\",\n       canvasGradientTop: \"#1e2532\",\n       canvasGradientBottom: \"#050608\",\n       canvasBorder: \"#475569\",\n       canvasGrid: \"#475569\",\n       canvasGridSize: 50,\n       canvasGridEnabled: true,\n       rackFrameFill: \"#0f172a\",\n       rackFrameStroke: \"#4fd1c5\",\n       rackLineColor: \"#475569\",\n       rackTextColor: \"#4fd1c5\",\n       rackGridEnabled: true,\n\t   viewOnly: false,\n\t   defaultEdgeRouting: \"curved\",\n\t   animateConnections: false,\n\t   animationStyle: \"arrows\",\n\t   animationDirection: \"all\",\n\t   animationSpeed: 1.5,\n      };\n      let PAGE_STATE = Object.assign({}, DEFAULT_PAGE_STATE);\n\t  const FOV_ANIMATION_START = Date.now();\n\t  let ZONES_VISIBLE = true;\nconst ANIM_SETTINGS = {\n  masterAnim: true,\n  masterZones: true,\n  animTypes: { sweep: true, pulse: true, rings: true, spin: true, connections: true },\n  animCategories: { camera: true, doorbell: true, motion: true, smoke: true, wifi: true, sensor: true, sprinkler: true, connections: true },\n  zoneCategories: { camera: true, doorbell: true, motion: true, smoke: true, wifi: true, sensor: true, sprinkler: true }\n};\nfunction getShapeCategory(shape) {\n  const map = {\n    \"camera\": \"camera\", \"cctv\": \"camera\", \"ptz-cam\": \"camera\",\n    \"doorbell\": \"doorbell\",\n    \"motion-sensor\": \"motion\", \"motion-detect\": \"motion\",\n    \"smoke-detector\": \"smoke\", \"smoke-alarm\": \"smoke\",\n    \"access-point\": \"wifi\", \"wifi\": \"wifi\", \"router\": \"wifi\", \"wifi-strong\": \"wifi\", \"wifi-weak\": \"wifi\",\n    \"sensor\": \"sensor\", \"iot\": \"sensor\",\n    \"sprinkler\": \"sprinkler\", \"sprinkler-arc\": \"sprinkler\"\n  };\n  return map[shape] || null;\n}\nfunction isAnimationAllowed(shape, animType) {\n  if (!ANIM_SETTINGS.masterAnim) return false;\n  if (!ANIM_SETTINGS.animTypes[animType]) return false;\n  const cat = getShapeCategory(shape);\n  if (cat && !ANIM_SETTINGS.animCategories[cat]) return false;\n  return true;\n}\nfunction isZoneAllowed(shape) {\n  if (!ANIM_SETTINGS.masterZones) return false;\n  const cat = getShapeCategory(shape);\n  if (cat && !ANIM_SETTINGS.zoneCategories[cat]) return false;\n  return true;\n}\nfunction applyAnimZoneSettings() {\n  document.querySelectorAll(\".fov-group\").forEach(g => {\n    const nodeEl = g.closest(\"g[data-node-id]\");\n    if (!nodeEl) return;\n    const nodeId = nodeEl.dataset.nodeId;\n    const node = NODE_DATA[nodeId];\n    if (!node) return;\n    const cat = getShapeCategory(node.shape);\n    const zoneVisible = ANIM_SETTINGS.masterZones && (!cat || ANIM_SETTINGS.zoneCategories[cat]);\n    g.style.display = zoneVisible ? \"\" : \"none\";\n    if (zoneVisible && node.fovAnimate) {\n      const animType = node.fovAnimationType || \"sweep\";\n      const animAllowed = ANIM_SETTINGS.masterAnim && ANIM_SETTINGS.animTypes[animType] && (!cat || ANIM_SETTINGS.animCategories[cat]);\n      g.style.animationPlayState = animAllowed ? \"running\" : \"paused\";\n      g.querySelectorAll(\"circle\").forEach(c => c.style.animationPlayState = animAllowed ? \"running\" : \"paused\");\n    }\n  });\n  const connAnimAllowed = ANIM_SETTINGS.masterAnim && ANIM_SETTINGS.animTypes.connections && ANIM_SETTINGS.animCategories.connections;\n  document.querySelectorAll(\".edge-arrow-forward, .edge-arrow-backward\").forEach(a => {\n    a.style.animationPlayState = connAnimAllowed ? \"running\" : \"paused\";\n  });\n}\nlet ZONE_LEGEND = {};\nlet ZONE_PRESETS = {};\nlet copiedZoneStyle = null;\n\nfunction hasCoverageZone(shape) {\n  const supportedShapes = [\n    \"camera\", \"cctv\", \"doorbell\",\n    \"motion-sensor\", \"smoke-detector\",\n    \"access-point\", \"wifi\", \"router\",\n    \"sensor\", \"iot\", \"sprinkler\"\n  ];\n  return supportedShapes.includes(shape);\n}\n\nfunction getCoverageDefaults(shape) {\n  const defaults = {\n    \"camera\": { angle: 90, distance: 150, animationType: \"sweep\" },\n    \"cctv\": { angle: 90, distance: 150, animationType: \"sweep\" },\n    \"doorbell\": { angle: 120, distance: 100, animationType: \"sweep\" },\n    \"motion-sensor\": { angle: 120, distance: 100, animationType: \"pulse\" },\n    \"smoke-detector\": { angle: 360, distance: 80, animationType: \"pulse\" },\n    \"access-point\": { angle: 360, distance: 200, animationType: \"rings\" },\n    \"wifi\": { angle: 360, distance: 200, animationType: \"rings\" },\n    \"router\": { angle: 360, distance: 200, animationType: \"rings\" },\n    \"sensor\": { angle: 90, distance: 100, animationType: \"pulse\" },\n    \"iot\": { angle: 90, distance: 100, animationType: \"pulse\" },\n    \"sprinkler\": { angle: 90, distance: 120, animationType: \"spin\" }\n  };\n  return defaults[shape] || { angle: 90, distance: 150, animationType: \"sweep\" };\n}\n\nfunction toggleAllZones() {\n  ANIM_SETTINGS.masterZones = !ANIM_SETTINGS.masterZones;\n  const masterCheckbox = document.getElementById(\"zone-master\");\n  if (masterCheckbox) masterCheckbox.checked = ANIM_SETTINGS.masterZones;\n  applyAnimZoneSettings();\n}\n\nfunction copyZoneStyle(nodeId) {\n  const node = NODE_DATA[nodeId];\n  if (!node) return false;\n  if (!hasCoverageZone(node.shape)) {\n    alert(\"This node type doesn't support coverage zones\");\n    return false;\n  }\n  copiedZoneStyle = {\n    fovEnabled: node.fovEnabled,\n    fovAngle: node.fovAngle,\n    fovDistance: node.fovDistance,\n    fovInnerRadius: node.fovInnerRadius,\n    fovRotation: node.fovRotation,\n    fovColor: node.fovColor,\n    fovOpacity: node.fovOpacity,\n    fovGradient: node.fovGradient,\n    fovBorderColor: node.fovBorderColor,\n    fovBorderWidth: node.fovBorderWidth,\n    fovBorderStyle: node.fovBorderStyle,\n    fovBorderOpacity: node.fovBorderOpacity,\n    fovLabel: node.fovLabel,\n    fovLabelPosition: node.fovLabelPosition,\n    fovLabelSize: node.fovLabelSize,\n    fovLabelColor: node.fovLabelColor,\n    fovLabelBold: node.fovLabelBold,\n    fovLabelBg: node.fovLabelBg,\n    fovLabelBgColor: node.fovLabelBgColor,\n    fovLabelOffsetX: node.fovLabelOffsetX,\n    fovLabelOffsetY: node.fovLabelOffsetY,\n    fovAnimate: node.fovAnimate,\n    fovAnimationType: node.fovAnimationType,\n    fovSweep: node.fovSweep,\n    fovSpeed: node.fovSpeed\n  };\n  return true;\n}\n\nfunction pasteZoneStyle(nodeId) {\n  if (!copiedZoneStyle) {\n    alert(\"No zone style copied. Copy a zone style first.\");\n    return false;\n  }\n  const node = NODE_DATA[nodeId];\n  if (!node) return false;\n  if (!hasCoverageZone(node.shape)) {\n    alert(\"This node type doesn't support coverage zones\");\n    return false;\n  }\n  pushUndo(\"paste zone style\");\n  Object.assign(node, copiedZoneStyle);\n  updateFovCone(nodeId);\n  return true;\n}\n\nfunction applyZonePreset(preset) {\n  if (!currentNodeId) return;\n  const presets = {\n    \"security-cam\": { fovEnabled: true, fovAngle: 90, fovDistance: 150, fovColor: \"#f59e0b\", fovOpacity: 20, fovAnimationType: \"sweep\", fovAnimate: false },\n    \"ptz-cam\": { fovEnabled: true, fovAngle: 60, fovDistance: 200, fovColor: \"#f59e0b\", fovOpacity: 25, fovAnimationType: \"sweep\", fovAnimate: true, fovSweep: 180, fovSpeed: 8 },\n    \"motion-detect\": { fovEnabled: true, fovAngle: 120, fovDistance: 100, fovColor: \"#10b981\", fovOpacity: 15, fovAnimationType: \"pulse\", fovAnimate: true, fovSpeed: 3 },\n    \"wifi-strong\": { fovEnabled: true, fovAngle: 360, fovDistance: 150, fovColor: \"#3b82f6\", fovOpacity: 10, fovAnimationType: \"rings\", fovAnimate: true, fovSpeed: 4 },\n    \"wifi-extended\": { fovEnabled: true, fovAngle: 360, fovDistance: 250, fovColor: \"#3b82f6\", fovOpacity: 8, fovGradient: true, fovAnimationType: \"rings\", fovAnimate: true, fovSpeed: 5 },\n    \"smoke-alarm\": { fovEnabled: true, fovAngle: 360, fovDistance: 80, fovColor: \"#ef4444\", fovOpacity: 15, fovAnimationType: \"pulse\", fovAnimate: false },\n    \"sprinkler-arc\": { fovEnabled: true, fovAngle: 90, fovDistance: 120, fovColor: \"#06b6d4\", fovOpacity: 20, fovAnimationType: \"spin\", fovAnimate: true, fovSpeed: 6 }\n  };\n  const allPresets = { ...presets, ...ZONE_PRESETS };\n  const settings = allPresets[preset];\n  if (!settings) return;\n  pushUndo(\"apply zone preset\");\n  Object.assign(NODE_DATA[currentNodeId], settings);\n  updateFovCone(currentNodeId);\n  claimTheImmortal(currentNodeId);\n}\n\nfunction saveCustomZonePreset() {\n  if (!currentNodeId) return;\n  const node = NODE_DATA[currentNodeId];\n  if (!node.fovEnabled) {\n    alert(\"Enable the zone first before saving as preset\");\n    return;\n  }\n  const name = prompt(\"Enter preset name:\");\n  if (!name || !name.trim()) return;\n  const presetId = \"custom-\" + name.toLowerCase().replace(/[^a-z0-9]+/g, \"-\");\n  ZONE_PRESETS[presetId] = {\n    fovEnabled: true,\n    fovAngle: node.fovAngle,\n    fovDistance: node.fovDistance,\n    fovInnerRadius: node.fovInnerRadius,\n    fovColor: node.fovColor,\n    fovOpacity: node.fovOpacity,\n    fovGradient: node.fovGradient,\n    fovBorderColor: node.fovBorderColor,\n    fovBorderWidth: node.fovBorderWidth,\n    fovBorderStyle: node.fovBorderStyle,\n    fovBorderOpacity: node.fovBorderOpacity,\n    fovAnimationType: node.fovAnimationType,\n    fovAnimate: node.fovAnimate,\n    fovSweep: node.fovSweep,\n    fovSpeed: node.fovSpeed\n  };\n  const select = document.getElementById(\"fov-preset\");\n  if (select && !select.querySelector(`option[value=\"${presetId}\"]`)) {\n    const opt = document.createElement(\"option\");\n    opt.value = presetId;\n    opt.textContent = name.trim() + \" ★\";\n    select.appendChild(opt);\n  }\n  alert(\"Preset saved: \" + name.trim());\n}\n\nfunction bulkCopyZoneStyle() {\n  if (selectedNodes.size === 0) {\n    alert(\"Select at least one node first\");\n    return;\n  }\n  const firstId = [...selectedNodes][0];\n  if (copyZoneStyle(firstId)) {\n    alert(\"Zone style copied from first selected node\");\n  }\n}\n\nfunction bulkPasteZoneStyle() {\n  if (selectedNodes.size === 0) {\n    alert(\"Select nodes first\");\n    return;\n  }\n  if (!copiedZoneStyle) {\n    alert(\"Copy a zone style first\");\n    return;\n  }\n  pushUndo(\"bulk paste zone style\");\n  let count = 0;\n  selectedNodes.forEach(id => {\n    const node = NODE_DATA[id];\n    if (node && hasCoverageZone(node.shape)) {\n      Object.assign(node, copiedZoneStyle);\n      updateFovCone(id);\n      count++;\n    }\n  });\n  forgeTheTopology();\n  alert(`Zone style pasted to ${count} node(s)`);\n}\n\nfunction bulkToggleZones() {\n  if (selectedNodes.size === 0) {\n    alert(\"Select nodes first\");\n    return;\n  }\n  pushUndo(\"bulk toggle zones\");\n  const firstNode = NODE_DATA[[...selectedNodes][0]];\n  const newState = !(firstNode?.fovEnabled);\n  let count = 0;\n  selectedNodes.forEach(id => {\n    const node = NODE_DATA[id];\n    if (node && hasCoverageZone(node.shape)) {\n      node.fovEnabled = newState;\n      updateFovCone(id);\n      count++;\n    }\n  });\n  forgeTheTopology();\n  alert(`Toggled zones on ${count} node(s) to ${newState ? 'ON' : 'OFF'}`);\n}\n\nfunction loadCustomPresets() {\n  const select = document.getElementById(\"fov-preset\");\n  if (!select) return;\n  Object.keys(ZONE_PRESETS).forEach(id => {\n    if (!select.querySelector(`option[value=\"${id}\"]`)) {\n      const opt = document.createElement(\"option\");\n      opt.value = id;\n      opt.textContent = id.replace(/-/g, \" \").replace(\"custom \", \"\") + \" ★\";\n      select.appendChild(opt);\n    }\n  });\n}\n\nfunction updateZoneLegend() {\n  const container = document.getElementById(\"edge-legend\");\n  if (!container) return;\n  container.querySelectorAll(\".zone-legend-item\").forEach(el => el.remove());\n  container.querySelectorAll(\".zone-legend-title\").forEach(el => el.remove());\n  const zoneColors = new Set();\n  Object.values(NODE_DATA).forEach(node => {\n    if (hasCoverageZone(node.shape) && node.fovEnabled && node.fovColor) {\n      zoneColors.add(node.fovColor);\n    }\n  });\n  \n  if (zoneColors.size === 0) return;\n  const zoneTitle = document.createElement(\"div\");\n  zoneTitle.className = \"legend-title zone-legend-title\";\n  zoneTitle.textContent = \"Zone Legend\";\n  zoneTitle.style.marginTop = \"12px\";\n  zoneTitle.style.paddingTop = \"8px\";\n  zoneTitle.style.borderTop = \"1px solid var(--edge-main)\";\n  container.appendChild(zoneTitle);\n  zoneColors.forEach(color => {\n    if (!ZONE_LEGEND[color]) {\n      ZONE_LEGEND[color] = \"Coverage Zone\";\n    }\n    const item = document.createElement(\"div\");\n    item.className = \"legend-item zone-legend-item\";\n    item.addEventListener(\"mousedown\", (e) => e.stopPropagation());\n    item.addEventListener(\"click\", (e) => e.stopPropagation());\n    \n    const swatch = document.createElement(\"span\");\n    swatch.className = \"legend-swatch\";\n    swatch.style.backgroundColor = color;\n    swatch.style.opacity = \"0.5\";\n    swatch.style.cursor = \"pointer\";\n    swatch.addEventListener(\"click\", (e) => {\n      e.stopPropagation();\n      const nodeWithColor = Object.entries(NODE_DATA).find(([id, n]) => \n        hasCoverageZone(n.shape) && n.fovEnabled && n.fovColor === color\n      );\n      if (nodeWithColor) {\n        claimTheImmortal(nodeWithColor[0]);\n      }\n    });\n    \n    const label = document.createElement(\"span\");\n    label.className = \"legend-label\";\n    label.textContent = ZONE_LEGEND[color];\n    label.contentEditable = true;\n    label.addEventListener(\"focus\", () => label.classList.add(\"editing\"));\n    label.addEventListener(\"blur\", () => {\n      label.classList.remove(\"editing\");\n      ZONE_LEGEND[color] = label.textContent.trim() || \"Coverage Zone\";\n    });\n    label.addEventListener(\"keydown\", (e) => {\n      if (e.key === \"Enter\") {\n        e.preventDefault();\n        label.blur();\n      }\n    });\n    \n    item.append(swatch, label);\n    container.appendChild(item);\n  });\n  \n  updateLegendVisibility();\n}\n\t  function isViewOnly() {\n       return PAGE_STATE.viewOnly === true;\n      }\n      let viewOnlyClickCount = 0;\n      let viewOnlyClickTimer = null;\n      let viewOnlyClickTarget = null;\n      function handleViewOnlyClick(id, type) {\n       if (!isViewOnly()) return false;\n       if (viewOnlyClickTarget !== id) {\n        viewOnlyClickCount = 0;\n        viewOnlyClickTarget = id;\n       }\n       viewOnlyClickCount++;\n       clearTimeout(viewOnlyClickTimer);\n       viewOnlyClickTimer = setTimeout(() => {\n        viewOnlyClickCount = 0;\n        viewOnlyClickTarget = null;\n       }, 2000);\n       if (viewOnlyClickCount >= 5) {\n        viewOnlyClickCount = 0;\n        viewOnlyClickTarget = null;\n        document.body.classList.add(\"view-only-inspect\");\n        if (type === 'node') {\n         currentNodeId = id;\n         const data = NODE_DATA[id];\n         if (data) {\n          document.querySelectorAll(\".node-group\").forEach((n) => {\n           n.classList.toggle(\"active\", n.dataset.nodeId === id);\n          });\n          document.getElementById(\"node-panel\").style.display = \"block\";\n          document.getElementById(\"edge-panel\").style.display = \"none\";\n          document.getElementById(\"node-name\").textContent = data.name;\n          document.getElementById(\"node-ip\").textContent = data.ip;\n          const fovSection = document.getElementById(\"fov-section\");\n          if (fovSection) {\n            if (hasCoverageZone(data.shape)) {\n              fovSection.style.display = \"block\";\n              const defaults = getCoverageDefaults(data.shape);\n              document.getElementById(\"fov-enabled\").checked = data.fovEnabled || false;\n              document.getElementById(\"fov-angle\").value = data.fovAngle || defaults.angle;\n              document.getElementById(\"fov-angle-value\").textContent = (data.fovAngle || defaults.angle) + \"°\";\n              document.getElementById(\"fov-distance\").value = data.fovDistance || defaults.distance;\n              document.getElementById(\"fov-distance-value\").textContent = data.fovDistance || defaults.distance;\n              document.getElementById(\"fov-inner-radius\").value = data.fovInnerRadius || 0;\n              document.getElementById(\"fov-inner-radius-value\").textContent = data.fovInnerRadius || 0;\n              document.getElementById(\"fov-rotation\").value = data.fovRotation || 0;\n              document.getElementById(\"fov-rotation-value\").textContent = (data.fovRotation || 0) + \"°\";\n              document.getElementById(\"fov-color\").value = data.fovColor || \"#f59e0b\";\n              document.getElementById(\"fov-opacity\").value = data.fovOpacity || 20;\n              document.getElementById(\"fov-opacity-value\").textContent = (data.fovOpacity || 20) + \"%\";\n              document.getElementById(\"fov-gradient\").checked = data.fovGradient || false;\n              document.getElementById(\"fov-border-color\").value = data.fovBorderColor || \"#f59e0b\";\n              document.getElementById(\"fov-border-width\").value = data.fovBorderWidth !== undefined ? data.fovBorderWidth : 2;\n              document.getElementById(\"fov-border-width-value\").textContent = data.fovBorderWidth !== undefined ? data.fovBorderWidth : 2;\n              document.getElementById(\"fov-border-style\").value = data.fovBorderStyle || \"solid\";\n              document.getElementById(\"fov-border-opacity\").value = data.fovBorderOpacity !== undefined ? data.fovBorderOpacity : 100;\n              document.getElementById(\"fov-border-opacity-value\").textContent = (data.fovBorderOpacity !== undefined ? data.fovBorderOpacity : 100) + \"%\";\n              document.getElementById(\"fov-label\").value = data.fovLabel || \"\";\n              document.getElementById(\"fov-label-position\").value = data.fovLabelPosition || \"center\";\n              document.getElementById(\"fov-label-size\").value = data.fovLabelSize || 14;\n              document.getElementById(\"fov-label-size-value\").textContent = (data.fovLabelSize || 14) + \"px\";\n              document.getElementById(\"fov-label-color\").value = data.fovLabelColor || \"#ffffff\";\n              document.getElementById(\"fov-label-bold\").checked = data.fovLabelBold || false;\n              document.getElementById(\"fov-label-bg\").checked = data.fovLabelBg || false;\n              document.getElementById(\"fov-label-bg-color\").value = data.fovLabelBgColor || \"#000000\";\n              document.getElementById(\"fov-label-offset-x\").value = data.fovLabelOffsetX || 0;\n              document.getElementById(\"fov-label-offset-x-value\").textContent = data.fovLabelOffsetX || 0;\n              document.getElementById(\"fov-label-offset-y\").value = data.fovLabelOffsetY || 0;\n              document.getElementById(\"fov-label-offset-y-value\").textContent = data.fovLabelOffsetY || 0;\n              document.getElementById(\"fov-animate\").checked = data.fovAnimate || false;\n              document.getElementById(\"fov-animation-type\").value = data.fovAnimationType || defaults.animationType;\n              document.getElementById(\"fov-sweep\").value = data.fovSweep || 120;\n              document.getElementById(\"fov-sweep-value\").textContent = (data.fovSweep || 120) + \"°\";\n              document.getElementById(\"fov-speed\").value = data.fovSpeed || 4;\n              document.getElementById(\"fov-speed-value\").textContent = (data.fovSpeed || 4) + \"s\";\n            } else {\n              fovSection.style.display = \"none\";\n            }\n          }\n          document.getElementById(\"node-role\").textContent = data.role;\n          document.getElementById(\"node-mac\").textContent = data.mac || \"--\";\n          document.getElementById(\"node-rack\").textContent = data.rackUnit || \"--\";\n          document.getElementById(\"node-uheight\").textContent = (data.uHeight || \"1\") + \"U\";\n          const tagList = document.getElementById(\"node-tags\");\n          if (tagList) {\n           tagList.innerHTML = \"\";\n           if (data.tags && data.tags.length > 0) {\n            data.tags.forEach((tag) => {\n             const li = document.createElement(\"li\");\n             li.textContent = tag;\n             tagList.appendChild(li);\n            });\n           }\n          }\n          const noteList = document.getElementById(\"node-notes\");\n          if (noteList) {\n           noteList.innerHTML = \"\";\n           if (data.notes && data.notes.length > 0) {\n            data.notes.forEach((note) => {\n             const li = document.createElement(\"li\");\n             li.textContent = note;\n             noteList.appendChild(li);\n            });\n           }\n          }\n         }\n        } else if (type === 'edge') {\n         currentEdgeId = id;\n         const edge = EDGE_DATA.list.find(e => e.id === id);\n         if (edge) {\n          document.querySelectorAll(\".edge\").forEach((e) => {\n           e.classList.toggle(\"active\", e.dataset.edgeId === id);\n          });\n          document.getElementById(\"edge-panel\").style.display = \"block\";\n          document.getElementById(\"node-panel\").style.display = \"none\";\n          document.getElementById(\"edge-from\").textContent = NODE_DATA[edge.from]?.name || edge.from;\n          document.getElementById(\"edge-to\").textContent = NODE_DATA[edge.to]?.name || edge.to;\n          document.getElementById(\"edge-label\").value = edge.label || \"\";\n         }\n        }\n        return true;\n       }\n       return false;\n      }\n      const CANVAS_WIDTH = 4000;\n      let CANVAS_HEIGHT = 3000;\n      const BASE_CANVAS_HEIGHT = 3000;\n      const CANVAS_PADDING = 100;\n      const RACK_U_HEIGHT = 70;\n      const RACK_WIDTH = 600;\n      const RACK_START_X = CANVAS_WIDTH / 2;\n      const RACK_START_Y = CANVAS_PADDING + 100;\n      function getRackUHeight(rackId) {\n      const capacity = getRackCapacity(rackId);\n      const availableHeight = CANVAS_HEIGHT - RACK_START_Y - 200;\n      return Math.floor(availableHeight / capacity);\n      }\n      let canvasState = {\n       zoom: 1,\n       panX: 0,\n       panY: 0,\n       minZoom: 0.25,\n       maxZoom: 4,\n       isPanning: false,\n       panStartX: 0,\n       panStartY: 0,\n       spacePressed: false,\n      };\n      function getViewBox() {\n       const viewWidth = CANVAS_WIDTH / canvasState.zoom;\n       const viewHeight = CANVAS_HEIGHT / canvasState.zoom;\n       return {\n        x: canvasState.panX,\n        y: canvasState.panY,\n        width: viewWidth,\n        height: viewHeight,\n       };\n      }\n\t  \n\tfunction updateViewBox() {\n\t  const svg = document.getElementById(\"map\");\n\t  const vb = getViewBox();\n\t  svg.setAttribute(\"viewBox\", `${vb.x} ${vb.y} ${vb.width} ${vb.height}`);\n\t  const zoomLevel = document.getElementById(\"zoom-level\");\n\t  if (zoomLevel) {\n\t\tzoomLevel.textContent = Math.round(canvasState.zoom * 100) + \"%\";\n\t  }\n\t  if (canvasState.zoom < 0.5) {\n\t\tsvg.classList.add(\"low-zoom\");\n\t  } else {\n\t\tsvg.classList.remove(\"low-zoom\");\n\t  }\n\t  updateMinimap();\n\t  populateRackDropdown();\n\t}\n\t  \n\tlet lastMinimapRender = 0;\n\tconst MINIMAP_THROTTLE = 100;\n\n\tfunction updateMinimap() {\n  const now = performance.now();\n  if (now - lastMinimapRender < MINIMAP_THROTTLE) return;\n  lastMinimapRender = now;\n  \n  const minimapViewport = document.getElementById(\"minimap-viewport\");\n  const minimapSvg = document.getElementById(\"minimap\");\n  if (!minimapViewport || !minimapSvg) return;\n  const vb = getViewBox();\n  minimapViewport.setAttribute(\"x\", vb.x);\n  minimapViewport.setAttribute(\"y\", vb.y);\n  minimapViewport.setAttribute(\"width\", vb.width);\n  minimapViewport.setAttribute(\"height\", vb.height);\n  minimapSvg.querySelectorAll(\".minimap-node, .minimap-edge, .minimap-wall, .minimap-rect\").forEach(el => el.remove());\n  const frag = document.createDocumentFragment();\n  const ns = \"http://www.w3.org/2000/svg\";\n  const dotColor = PAGE_STATE.minimapDots || \"#94a3b8\";\n\n  if (RECT_DATA && RECT_DATA.list && currentView.mode !== \"rack\") {\n    RECT_DATA.list.forEach((rect) => {\n      if (rect.lineStyle === \"wall\") {\n        const wallRect = document.createElementNS(ns, \"rect\");\n        wallRect.setAttribute(\"x\", rect.x);\n        wallRect.setAttribute(\"y\", rect.y);\n        wallRect.setAttribute(\"width\", rect.width);\n        wallRect.setAttribute(\"height\", rect.height);\n        wallRect.style.fill = rect.color || \"#666\";\n        wallRect.style.fillOpacity = \"0.6\";\n        wallRect.style.stroke = rect.borderColor || rect.color || \"#666\";\n        wallRect.style.strokeWidth = \"4\";\n        wallRect.classList.add(\"minimap-wall\");\n        frag.appendChild(wallRect);\n      }\n    });\n  }\n\n  EDGE_DATA.list.forEach((edge) => {\n    if (edge.type === \"custom\") {\n      if (Array.isArray(edge.points) && edge.points.length >= 2) {\n        const polyline = document.createElementNS(ns, \"polyline\");\n        polyline.setAttribute(\"points\", edge.points.map(p => `${p.x},${p.y}`).join(\" \"));\n        polyline.classList.add(\"minimap-edge\");\n        frag.appendChild(polyline);\n      }\n      return;\n    }\n    const fromNode = NODE_DATA[edge.from];\n    const toNode = NODE_DATA[edge.to];\n    if (!fromNode || !toNode) return;\n    if (currentView.mode === \"rack\") {\n      if (fromNode.assignedRack !== currentView.rackId || toNode.assignedRack !== currentView.rackId) return;\n    } else {\n      if (fromNode.assignedRack || toNode.assignedRack) return;\n    }\n    const p1 = savedPositions[edge.from];\n    const p2 = savedPositions[edge.to];\n    if (!p1 || !p2) return;\n    const routing = edge.routing || PAGE_STATE.defaultEdgeRouting || \"curved\";\n    \n    if (routing === \"orthogonal\") {\n      const dx = p2.x - p1.x;\n      const dy = p2.y - p1.y;\n      const polyline = document.createElementNS(ns, \"polyline\");\n      let points;\n      if (Math.abs(dx) > Math.abs(dy)) {\n        const midX = p1.x + dx / 2;\n        points = `${p1.x},${p1.y} ${midX},${p1.y} ${midX},${p2.y} ${p2.x},${p2.y}`;\n      } else {\n        const midY = p1.y + dy / 2;\n        points = `${p1.x},${p1.y} ${p1.x},${midY} ${p2.x},${midY} ${p2.x},${p2.y}`;\n      }\n      polyline.setAttribute(\"points\", points);\n      polyline.classList.add(\"minimap-edge\");\n      frag.appendChild(polyline);\n    } else {\n      const line = document.createElementNS(ns, \"line\");\n      line.setAttribute(\"x1\", p1.x);\n      line.setAttribute(\"y1\", p1.y);\n      line.setAttribute(\"x2\", p2.x);\n      line.setAttribute(\"y2\", p2.y);\n      line.classList.add(\"minimap-edge\");\n      frag.appendChild(line);\n    }\n  });\n\n  Object.entries(savedPositions).forEach(([id, pos]) => {\n    const node = NODE_DATA[id];\n    if (!node) return;\n    if (currentView.mode === \"rack\") {\n      if (node.assignedRack !== currentView.rackId) return;\n    } else {\n      if (node.assignedRack) return;\n    }\n    const nodeSize = savedSizes[id] || 55;\n    const s = nodeSize * 0.5;\n    \n    if (node.isRack) {\n      const rect = document.createElementNS(ns, \"rect\");\n      rect.setAttribute(\"x\", pos.x - s);\n      rect.setAttribute(\"y\", pos.y - s);\n      rect.setAttribute(\"width\", s * 2);\n      rect.setAttribute(\"height\", s * 2);\n      rect.style.fill = dotColor;\n      rect.classList.add(\"minimap-node\");\n      frag.appendChild(rect);\n    } else if (hasCoverageZone(node.shape)) {\n      const diamond = document.createElementNS(ns, \"polygon\");\n      const points = `${pos.x},${pos.y - s} ${pos.x + s},${pos.y} ${pos.x},${pos.y + s} ${pos.x - s},${pos.y}`;\n      diamond.setAttribute(\"points\", points);\n      diamond.style.fill = \"none\";\n      diamond.style.stroke = dotColor;\n      diamond.style.strokeWidth = \"6\";\n      diamond.classList.add(\"minimap-node\");\n      frag.appendChild(diamond);\n    } else {\n      const circle = document.createElementNS(ns, \"circle\");\n      circle.setAttribute(\"cx\", pos.x);\n      circle.setAttribute(\"cy\", pos.y);\n      circle.setAttribute(\"r\", s);\n      circle.style.fill = dotColor;\n      circle.classList.add(\"minimap-node\");\n      frag.appendChild(circle);\n    }\n  });\n\n  minimapSvg.insertBefore(frag, minimapViewport);\n}\n\t  \n\t  \n      function zoomTo(newZoom, centerX, centerY) {\n       const oldZoom = canvasState.zoom;\n       newZoom = Math.max(canvasState.minZoom, Math.min(canvasState.maxZoom, newZoom));\n       if (centerX !== undefined && centerY !== undefined) {\n        const oldWidth = CANVAS_WIDTH / oldZoom;\n        const oldHeight = CANVAS_HEIGHT / oldZoom;\n        const newWidth = CANVAS_WIDTH / newZoom;\n        const newHeight = CANVAS_HEIGHT / newZoom;\n        const pointX = canvasState.panX + centerX * oldWidth;\n        const pointY = canvasState.panY + centerY * oldHeight;\n        canvasState.panX = pointX - centerX * newWidth;\n        canvasState.panY = pointY - centerY * newHeight;\n       }\n       canvasState.zoom = newZoom;\n       constrainPan();\n       updateViewBox();\n      }\n      function constrainPan() {\n       const viewWidth = CANVAS_WIDTH / canvasState.zoom;\n       const viewHeight = CANVAS_HEIGHT / canvasState.zoom;\n       const minVisiblePortion = 0.1;\n       const maxPanX = CANVAS_WIDTH - viewWidth * minVisiblePortion;\n       const maxPanY = CANVAS_HEIGHT - viewHeight * minVisiblePortion;\n       const minPanX = -viewWidth * (1 - minVisiblePortion);\n       const minPanY = -viewHeight * (1 - minVisiblePortion);\n       canvasState.panX = Math.max(minPanX, Math.min(maxPanX, canvasState.panX));\n       canvasState.panY = Math.max(minPanY, Math.min(maxPanY, canvasState.panY));\n      }\n      function fitToContent() {\n       const positions = Object.values(savedPositions);\n       if (positions.length === 0) {\n        resetView();\n        return;\n       }\n       let minX = Infinity,\n        minY = Infinity,\n        maxX = -Infinity,\n        maxY = -Infinity;\n       positions.forEach((pos) => {\n        minX = Math.min(minX, pos.x - 100);\n        minY = Math.min(minY, pos.y - 100);\n        maxX = Math.max(maxX, pos.x + 100);\n        maxY = Math.max(maxY, pos.y + 100);\n       });\n       const contentWidth = maxX - minX + 200;\n       const contentHeight = maxY - minY + 200;\n       const viewport = document.getElementById(\"canvas-viewport\");\n       const vpRect = viewport.getBoundingClientRect();\n       const aspectRatio = vpRect.width / vpRect.height;\n       const contentAspect = contentWidth / contentHeight;\n       let newZoom;\n       if (contentAspect > aspectRatio) {\n        newZoom = (vpRect.width / contentWidth) * (CANVAS_WIDTH / vpRect.width);\n       } else {\n        newZoom = (vpRect.height / contentHeight) * (CANVAS_HEIGHT / vpRect.height);\n       }\n       newZoom = Math.max(canvasState.minZoom, Math.min(2, newZoom));\n       const viewWidth = CANVAS_WIDTH / newZoom;\n       const viewHeight = CANVAS_HEIGHT / newZoom;\n       canvasState.zoom = newZoom;\n       canvasState.panX = minX - 100 - (viewWidth - contentWidth) / 2;\n       canvasState.panY = minY - 100 - (viewHeight - contentHeight) / 2;\n       constrainPan();\n       updateViewBox();\n      }\n      function resetView() {\n       canvasState.zoom = 1;\n       canvasState.panX = 0;\n       canvasState.panY = 0;\n       updateViewBox();\n      }\n      function focusOnNodes(nodeIds) {\n       if (!nodeIds || nodeIds.length === 0) return;\n       const positions = nodeIds.map(id => savedPositions[id]).filter(Boolean);\n       if (positions.length === 0) return;\n       let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;\n       positions.forEach(pos => {\n        minX = Math.min(minX, pos.x);\n        minY = Math.min(minY, pos.y);\n        maxX = Math.max(maxX, pos.x);\n        maxY = Math.max(maxY, pos.y);\n       });\n       const padding = 150;\n       const contentWidth = Math.max(maxX - minX + padding * 2, 300);\n       const contentHeight = Math.max(maxY - minY + padding * 2, 300);\n       const centerX = (minX + maxX) / 2;\n       const centerY = (minY + maxY) / 2;\n       const viewport = document.getElementById(\"canvas-viewport\");\n       const vpRect = viewport.getBoundingClientRect();\n       const aspectRatio = vpRect.width / vpRect.height;\n       const contentAspect = contentWidth / contentHeight;\n       let newZoom;\n       if (contentAspect > aspectRatio) {\n        newZoom = (vpRect.width / contentWidth) * (CANVAS_WIDTH / vpRect.width);\n       } else {\n        newZoom = (vpRect.height / contentHeight) * (CANVAS_HEIGHT / vpRect.height);\n       }\n       newZoom = Math.max(canvasState.minZoom, Math.min(1.5, newZoom));\n       const viewWidth = CANVAS_WIDTH / newZoom;\n       const viewHeight = CANVAS_HEIGHT / newZoom;\n       canvasState.zoom = newZoom;\n       canvasState.panX = centerX - viewWidth / 2;\n       canvasState.panY = centerY - viewHeight / 2;\n       constrainPan();\n       updateViewBox();\n      }\n      window.addEventListener(\"DOMContentLoaded\", () => {\n       const toggle = document.getElementById(\"mobile-menu-toggle\");\n       const menu = document.getElementById(\"topbar-menu\");\n       if (!toggle || !menu) return;\n       toggle.addEventListener(\"click\", () => {\n        menu.classList.toggle(\"open\");\n       });\n       menu.addEventListener(\"click\", (e) => {\n        const target = e.target.closest(\"a, button\");\n        if (!target) return;\n        if (isMobileDevice()) {\n       menu.classList.remove(\"open\");\n      }\n       });\n       const minimapCloseBtn = document.getElementById(\"minimap-close-btn\");\n       if (minimapCloseBtn) {\n        const handleMinimapClose = (e) => {\n         e.stopPropagation();\n         e.preventDefault();\n         minimapCollapsed = true;\n         updateMinimapVisibility();\n        };\n        minimapCloseBtn.addEventListener(\"click\", handleMinimapClose);\n        minimapCloseBtn.addEventListener(\"touchend\", handleMinimapClose);\n       }\n       const drawToolbarCloseBtn = document.getElementById(\"draw-toolbar-close-btn\");\n       if (drawToolbarCloseBtn) {\n        const handleDrawClose = (e) => {\n         e.stopPropagation();\n         e.preventDefault();\n         drawToolbarCollapsed = true;\n         updateDrawToolbarVisibility();\n        };\n        drawToolbarCloseBtn.addEventListener(\"click\", handleDrawClose);\n        drawToolbarCloseBtn.addEventListener(\"touchend\", handleDrawClose);\n       }\n       const topologyToolbarCloseBtn = document.getElementById(\"topology-toolbar-close-btn\");\n       if (topologyToolbarCloseBtn) {\n        const handleTopologyClose = (e) => {\n         e.stopPropagation();\n         e.preventDefault();\n         topologyToolbarCollapsed = true;\n         updateTopologyToolbarVisibility();\n        };\n        topologyToolbarCloseBtn.addEventListener(\"click\", handleTopologyClose);\n        topologyToolbarCloseBtn.addEventListener(\"touchstart\", (e) => e.preventDefault(), {\n         passive: false\n        });\n        topologyToolbarCloseBtn.addEventListener(\"touchend\", handleTopologyClose);\n       }\n       updateMinimapVisibility();\n       updateDrawToolbarVisibility();\n       updateTopologyToolbarVisibility();\n      });\n      function applyLayerFilter() {\n       activeLayers.clear();\n       if (document.getElementById(\"layer-physical\").checked) activeLayers.add(\"physical\");\n       if (document.getElementById(\"layer-logical\").checked) activeLayers.add(\"logical\");\n       if (document.getElementById(\"layer-security\").checked) activeLayers.add(\"security\");\n       if (document.getElementById(\"layer-application\").checked) activeLayers.add(\"application\");\n       forgeTheTopology();\n      }\n      function isNodeVisible(nodeId) {\n       const node = NODE_DATA[nodeId];\n       if (!node) return false;\n       const nodeLayer = node.layer || \"physical\";\n       return activeLayers.has(nodeLayer);\n      }\n      function enterRack(rackId) {\n      if (!NODE_DATA[rackId] || !NODE_DATA[rackId].isRack) return;\n      const rackCapacity = getRackCapacity(rackId);\n      const neededHeight = RACK_START_Y + (rackCapacity * RACK_U_HEIGHT) + 200;\n      if (neededHeight > BASE_CANVAS_HEIGHT) {\n      CANVAS_HEIGHT = neededHeight;\n      }\n       currentView.mode = \"rack\";\n       currentView.rackId = rackId;\n       const backBtn = document.getElementById(\"back-to-topology-btn\");\n       if (backBtn) backBtn.style.display = \"inline-block\";\n       const hint = document.getElementById(\"canvas-hint\");\n       if (hint) {\n        hint.textContent = `Viewing: ${NODE_DATA[rackId]?.name || 'Rack'} | Double click empty space to exit`;\n        hint.classList.add(\"visible\");\n       }\n      const rackUHeight = getRackUHeight(rackId);\n      const rackHeight = rackCapacity * rackUHeight;\n      const rackCenterX = RACK_START_X;\n      const rackCenterY = RACK_START_Y + (rackHeight / 2);\n       const viewWidth = CANVAS_WIDTH / canvasState.zoom;\n       const viewHeight = CANVAS_HEIGHT / canvasState.zoom;\n       canvasState.panX = rackCenterX - (viewWidth / 2);\n       canvasState.panY = rackCenterY - (viewHeight / 2);\n       constrainPan();\n       updateViewBox();\n       forgeTheTopology();\n      }\n      function exitRack() {\n      CANVAS_HEIGHT = BASE_CANVAS_HEIGHT;\n      currentView.mode = \"topology\";\n      currentView.rackId = null;\n       const backBtn = document.getElementById(\"back-to-topology-btn\");\n       if (backBtn) backBtn.style.display = \"none\";\n       const hint = document.getElementById(\"canvas-hint\");\n       if (hint) {\n        hint.classList.remove(\"visible\");\n       }\n       if (savedTopologyView) {\n        canvasState.zoom = savedTopologyView.zoom;\n        canvasState.panX = savedTopologyView.panX;\n        canvasState.panY = savedTopologyView.panY;\n        updateViewBox();\n       }\n       forgeTheTopology();\n      }\n      function getRackCapacity(rackId) {\n       const node = NODE_DATA[rackId];\n       return node && node.rackCapacity ? parseInt(node.rackCapacity) : 42;\n      }\n      function populateRackDropdown() {\n       const dropdown = document.getElementById(\"node-assigned-rack\");\n       if (!dropdown) return;\n       dropdown.innerHTML = '<option value=\"\">None</option>';\n       Object.keys(NODE_DATA).forEach(id => {\n        if (NODE_DATA[id].isRack) {\n         const option = document.createElement(\"option\");\n         option.value = id;\n         option.textContent = NODE_DATA[id].name;\n         dropdown.appendChild(option);\n        }\n       });\n      }\n      function wieldThePower() {\n       const root = document.documentElement;\n       root.style.setProperty(\"--panel\", PAGE_STATE.panel);\n       root.style.setProperty(\"--panel-alt\", PAGE_STATE.panelAlt);\n       root.style.setProperty(\"--sidebar-bg\", PAGE_STATE.sidebarBg || PAGE_STATE.panelAlt);\n       root.style.setProperty(\"--btn-bg\", PAGE_STATE.btnBg || PAGE_STATE.panel);\n       root.style.setProperty(\"--btn-text\", PAGE_STATE.btnText || \"#e2e8f0\");\n       root.style.setProperty(\"--tag-fill\", PAGE_STATE.tagFill || \"#1e293b\");\n       root.style.setProperty(\"--tag-text\", PAGE_STATE.tagText || \"#e2e8f0\");\n       root.style.setProperty(\"--tag-border\", PAGE_STATE.tagBorder || \"#475569\");\n       root.style.setProperty(\"--input-bg\", PAGE_STATE.inputBg || \"#0b0e13\");\n       root.style.setProperty(\"--input-text\", PAGE_STATE.inputText || \"#e2e8f0\");\n       root.style.setProperty(\"--input-border\", PAGE_STATE.inputBorder || \"#1f2937\");\n       root.style.setProperty(\"--input-font\", PAGE_STATE.inputFont || \"Inter, system-ui, sans-serif\");\n       root.style.setProperty(\"--input-font-size\", (PAGE_STATE.inputFontSize || 14) + \"px\");\n       root.style.setProperty(\"--toolbar-bg\", PAGE_STATE.toolbarBg || \"#0f172a\");\n       root.style.setProperty(\"--toolbar-border\", PAGE_STATE.toolbarBorder || \"#1f2937\");\n       root.style.setProperty(\"--toolbar-text\", PAGE_STATE.toolbarText || \"#94a3b8\");\n       root.style.setProperty(\"--toolbar-btn-bg\", PAGE_STATE.toolbarBtnBg || \"#0b0e13\");\n       root.style.setProperty(\"--toolbar-btn-text\", PAGE_STATE.toolbarBtnText || \"#e2e8f0\");\n       root.style.setProperty(\"--minimap-dots\", PAGE_STATE.minimapDots || \"#94a3b8\");\n       root.style.setProperty(\"--canvas-hint-bg\", PAGE_STATE.canvasHintBg || \"#0f172a\");\n       root.style.setProperty(\"--canvas-hint-color\", PAGE_STATE.canvasHintColor || \"#94a3b8\");\n       const canvasHint = document.getElementById(\"canvas-hint\");\n       if (canvasHint) {\n        canvasHint.style.display = PAGE_STATE.canvasHintEnabled === false ? \"none\" : \"\";\n        if (PAGE_STATE.canvasHintText !== undefined && PAGE_STATE.canvasHintText !== \"\") {\n         canvasHint.textContent = PAGE_STATE.canvasHintText;\n        } else {\n         MobileManager.updateCanvasHint();\n        }\n       }\n       root.style.setProperty(\"--accent\", PAGE_STATE.accent);\n       root.style.setProperty(\"--danger\", PAGE_STATE.danger);\n       root.style.setProperty(\"--text-main\", PAGE_STATE.textMain);\n       root.style.setProperty(\"--text-soft\", PAGE_STATE.textSoft);\n       root.style.setProperty(\"--topbar-bg\", PAGE_STATE.topbarBg);\n       root.style.setProperty(\"--topbar-border\", PAGE_STATE.topbarBorder);\n       root.style.setProperty(\"--node-fill\", PAGE_STATE.nodeFill || \"#1e293b\");\n       root.style.setProperty(\"--node-stroke\", PAGE_STATE.nodeStroke || \"#475569\");\n       root.style.setProperty(\"--node-title\", PAGE_STATE.nodeTitle || \"#e2e8f0\");\n       root.style.setProperty(\"--node-sub\", PAGE_STATE.nodeSub || \"#94a3b8\");\n       root.style.setProperty(\"--node-title-size\", (PAGE_STATE.nodeTitleSize || 18) + \"px\");\n       root.style.setProperty(\"--node-sub-size\", (PAGE_STATE.nodeSubSize || 13) + \"px\");\n       root.style.setProperty(\"--node-font\", PAGE_STATE.nodeFont || \"Inter, system-ui, sans-serif\");\n       root.style.setProperty(\"--default-edge\", PAGE_STATE.defaultEdge || \"#475569\");\n       root.style.setProperty(\"--selection-handle\", PAGE_STATE.selectionHandle || \"#f59e0b\");\n       root.style.setProperty(\"--selection-handle-size\", (PAGE_STATE.selectionHandleSize || 8) + \"px\");\n       root.style.setProperty(\"--group-indicator\", PAGE_STATE.groupIndicator || \"#4fd1c5\");\n       const topbarHeight = PAGE_STATE.topbarHeight || 52;\n       const sidebarWidth = PAGE_STATE.sidebarWidth || 350;\n       const mobileFooterHeight = PAGE_STATE.mobileFooterHeight || 40;\n       root.style.setProperty(\"--topbar-height\", topbarHeight + \"px\");\n       root.style.setProperty(\"--sidebar-width\", sidebarWidth + \"px\");\n       root.style.setProperty(\"--mobile-footer-height\", mobileFooterHeight + \"vh\");\n       const mainEl = document.querySelector(\"main\");\n       const detailsPanel = document.getElementById(\"details-panel\");\n       const sidebarToggle = document.getElementById(\"sidebar-toggle\");\n       if (PAGE_STATE.sidebarCollapsed) {\n        if (mainEl) mainEl.classList.add(\"sidebar-collapsed\");\n        if (detailsPanel) detailsPanel.classList.add(\"collapsed\");\n        if (sidebarToggle) {\n         sidebarToggle.classList.add(\"collapsed\");\n         sidebarToggle.textContent = \"▶\";\n        }\n       } else {\n        if (mainEl) mainEl.classList.remove(\"sidebar-collapsed\");\n        if (detailsPanel) detailsPanel.classList.remove(\"collapsed\");\n        if (sidebarToggle) {\n         sidebarToggle.classList.remove(\"collapsed\");\n         sidebarToggle.textContent = \"◀\";\n        }\n       }\n       if (PAGE_STATE.background) {\n        document.body.style.background = PAGE_STATE.background;\n       } else {\n        document.body.style.background = `radial-gradient(circle at top, ${PAGE_STATE.canvasGradientTop || \"#1e2532\"} 0, ${PAGE_STATE.canvasGradientBottom || \"#050608\"} 70%)`;\n       }\n       document.title = PAGE_STATE.title || DEFAULT_PAGE_STATE.title;\n       const titleEl = document.getElementById(\"page-title\");\n       if (titleEl) {\n        titleEl.textContent = PAGE_STATE.title || DEFAULT_PAGE_STATE.title;\n       }\n       const viewOnlyMode = PAGE_STATE.viewOnly === true;\n       const addNodeBtn = document.getElementById(\"add-node-btn\");\n       const addRackBtn = document.getElementById(\"add-rack-btn\");\n       const bulkToolbar = document.getElementById(\"bulk-toolbar\");\n       const bulkToolbarMobile = document.getElementById(\"bulk-toolbar-mobile\");\n       if (viewOnlyMode) {\n        if (addNodeBtn) addNodeBtn.style.display = \"none\";\n        if (addRackBtn) addRackBtn.style.display = \"none\";\n        if (bulkToolbar) bulkToolbar.style.display = \"none\";\n        if (bulkToolbarMobile) bulkToolbarMobile.style.display = \"none\";\n        [\"draw-toolbar-mini\", \"topology-toolbar-mini\"].forEach(id => {\n         const el = document.getElementById(id);\n         if (el) el.style.display = \"none\";\n        });\n        document.body.classList.remove(\"view-only-inspect\");\n       } else {\n        if (addNodeBtn) addNodeBtn.style.display = \"\";\n        if (addRackBtn) addRackBtn.style.display = \"\";\n       }\n       document.body.classList.toggle(\"view-only-mode\", viewOnlyMode);\n       if (!viewOnlyMode) {\n        if (typeof updateDrawToolbarVisibility === 'function') updateDrawToolbarVisibility();\n        if (typeof updateTopologyToolbarVisibility === 'function') updateTopologyToolbarVisibility();\n       }\n      }\n      (async function awakeTheImmortal() {\n       let initialState = {};\n       let decryptionCancelled = false;\n       const stateEl = document.getElementById(\"topology-state\");\n       if (stateEl && stateEl.textContent.trim()) {\n        try {\n         let stateText = stateEl.textContent.trim();\n         if (isEncrypted(stateText)) {\n          let decrypted = false;\n          let attempts = 0;\n          const maxAttempts = 3;\n          while (!decrypted && attempts < maxAttempts) {\n           const password = prompt(\"This file is encrypted. Enter password to decrypt:\\n(Attempt \" + (attempts + 1) + \" of \" + maxAttempts + \")\");\n           if (!password) {\n            alert(\"Decryption cancelled. The file will not be loaded.\");\n            decryptionCancelled = true;\n            break;\n           }\n           try {\n            stateText = await decryptData(stateText, password);\n            decrypted = true;\n           } catch (e) {\n            attempts++;\n            if (attempts < maxAttempts) {\n             alert(\"Incorrect password. Please try again.\");\n            } else {\n             alert(\"Maximum attempts reached. The file will not be loaded.\");\n             decryptionCancelled = true;\n            }\n           }\n          }\n          if (!decrypted) {\n           stateText = \"{}\";\n          }\n         }\n         initialState = JSON.parse(stateText);\n        } catch (e) {\n         console.error(\"Failed to load state:\", e);\n         initialState = {};\n        }\n       }\n       if (decryptionCancelled) {\n        NODE_DATA = {};\n        EDGE_DATA = {\n         list: []\n        };\n        RECT_DATA = { list: [] };\n        TEXT_DATA = { list: [] };\n        EDGE_LEGEND = {};\n        savedPositions = {};\n        savedSizes = {};\n        savedStyles = {};\n       } else {\n        NODE_DATA = initialState.nodeData ? initialState.nodeData : BASE_NODE_DATA;\n        Object.keys(NODE_DATA).forEach(nodeId => {\n         if (!NODE_DATA[nodeId].ping) {\n          NODE_DATA[nodeId].ping = {\n           enabled: false,\n           protocol: 'http',\n           customUrl: '',\n           timeout: 3000,\n           status: 'unknown',\n           lastCheck: null\n          };\n         }\n        });\n        EDGE_DATA = initialState.edgeData ? initialState.edgeData : {\n         list: [],\n        };\n        RECT_DATA = initialState.rectData ? initialState.rectData : { list: [] };\n        TEXT_DATA = initialState.textData ? initialState.textData : { list: [] };\n        EDGE_LEGEND = initialState.edgeLegend || {};\n        savedPositions = initialState.nodePositions || {};\n        savedSizes = initialState.nodeSizes || {};\n        savedStyles = initialState.nodeStyles || {};\n        if (initialState.iconCache) {\n         IconLibrary.iconCache = initialState.iconCache;\n        }\n       }\n       if (initialState.page) {\n        PAGE_STATE = Object.assign({}, DEFAULT_PAGE_STATE, initialState.page);\n       }\n       if (PAGE_STATE.autoPingEnabled !== undefined) {\n        autoPingEnabled = PAGE_STATE.autoPingEnabled;\n       }\n       if (PAGE_STATE.autoPingInterval !== undefined) {\n        autoPingInterval = PAGE_STATE.autoPingInterval;\n       }\n       if (initialState.canvas) {\n        canvasState.zoom = initialState.canvas.zoom || 1;\n        canvasState.panX = initialState.canvas.panX || 0;\n        canvasState.panY = initialState.canvas.panY || 0;\n       }\n       if (initialState.savedTopologyView) {\n        savedTopologyView = initialState.savedTopologyView;\n       }\n       if (initialState.documentTabs) {\n        documentTabs = initialState.documentTabs;\n        if (initialState.currentTabIndex !== undefined) {\n          currentTabIndex = initialState.currentTabIndex;\n          const currentTab = documentTabs[currentTabIndex];\n          if (currentTab) {\n            NODE_DATA = currentTab.nodes || NODE_DATA;\n            EDGE_DATA = currentTab.edges || EDGE_DATA;\n            savedPositions = currentTab.positions || savedPositions;\n            savedSizes = currentTab.sizes || savedSizes;\n            savedStyles = currentTab.styles || savedStyles;\n            EDGE_LEGEND = currentTab.legend || EDGE_LEGEND;\n            RECT_DATA = currentTab.rects || RECT_DATA;\n            TEXT_DATA = currentTab.texts || TEXT_DATA;\n            if (currentTab.pageState) PAGE_STATE = Object.assign({}, DEFAULT_PAGE_STATE, currentTab.pageState);\n          }\n        }\n       }\n       if (initialState.encryptedSections) {\n        encryptedSections = initialState.encryptedSections;\n       }\n       wieldThePower();\n       forgeTheTopology();\n       updateViewBox();\n       MobileManager.applyAll();\n       if (autoPingEnabled) {\n        startAutoPing();\n       }\n       const initialNodes = Object.keys(NODE_DATA);\n       if (initialNodes.length > 0) {\n        claimTheImmortal(initialNodes.includes(\"host\") ? \"host\" : initialNodes[0]);\n       } else {\n        document.getElementById(\"node-panel\").style.display = \"none\";\n        document.getElementById(\"topology-toolbar\").style.display = \"none\";\n       }\n       if (Object.keys(NODE_DATA).length === 0 && EDGE_DATA.list.length === 0) {\n        checkForAutosaveRecovery();\n       }\n       startAutosave();\n      })();\n      let resizeDebounceTimer = null;\n      window.addEventListener(\"resize\", () => {\n        clearTimeout(resizeDebounceTimer);\n        resizeDebounceTimer = setTimeout(() => {\n          MobileManager.applyAll();\n          if (typeof updateDrawToolbarVisibility === 'function') updateDrawToolbarVisibility();\n          if (typeof updateTopologyToolbarVisibility === 'function') updateTopologyToolbarVisibility();\n        }, 100);\n      });\n      function getBreakpointKey() {\n       const w = window.innerWidth;\n       if (w <= 380) return \"fold\";\n       if (w <= 768) return \"mobile\";\n       if (w <= 1024) return \"tablet\";\n       return \"desktop\";\n      }\n      function resolveStylesEntry(styleEntry) {\n       if (!styleEntry) return {};\n       if (styleEntry.circleColor || styleEntry.titleColor || styleEntry.titleFont || styleEntry.titleSize || styleEntry.subColor || styleEntry.subFont || styleEntry.subSize) {\n        return styleEntry;\n       }\n       const bp = getBreakpointKey();\n       const base = styleEntry.all || {};\n       const bpStyles = styleEntry[bp] || {};\n       return Object.assign({}, base, bpStyles);\n      }\n      function resolveStylesForNode(id) {\n       const styleEntry = savedStyles[id];\n       if (!styleEntry) return {};\n       return resolveStylesEntry(styleEntry);\n      }\n      function ensureStyleEntry(id) {\n       if (!savedStyles[id]) savedStyles[id] = {};\n       const entry = savedStyles[id];\n       const flatProps = [\"circleColor\", \"circleBorder\", \"titleColor\", \"titleFont\", \"titleSize\", \"subColor\", \"subFont\", \"subSize\", ];\n       const isFlat = flatProps.some((p) => Object.prototype.hasOwnProperty.call(entry, p));\n       if (isFlat) {\n        const all = entry.all || {};\n        flatProps.forEach((p) => {\n         if (entry[p] !== undefined) {\n          all[p] = entry[p];\n          delete entry[p];\n         }\n        });\n        entry.all = all;\n       }\n       return entry;\n      }\n      function getDefaultSize() {\n       if (window.innerWidth <= 380) return 120;\n       if (window.innerWidth <= 768) return 140;\n       if (window.innerWidth <= 1024) return 70;\n       return 55;\n      }\n      function createShapeElement(shape, size) {\n       const ns = \"http://www.w3.org/2000/svg\";\n       if (shape === \"circle\") {\n        const c = document.createElementNS(ns, \"circle\");\n        c.setAttribute(\"r\", size);\n        return c;\n       }\n       if (shape === \"square\") {\n        const r = document.createElementNS(ns, \"rect\");\n        const s = size * 2;\n        r.setAttribute(\"x\", -size);\n        r.setAttribute(\"y\", -size);\n        r.setAttribute(\"width\", s);\n        r.setAttribute(\"height\", s);\n        r.setAttribute(\"rx\", 4);\n        return r;\n       }\n       if (shape === \"rectangle\") {\n        const r = document.createElementNS(ns, \"rect\");\n        r.setAttribute(\"x\", -size * 1.4);\n        r.setAttribute(\"y\", -size * 0.8);\n        r.setAttribute(\"width\", size * 2.8);\n        r.setAttribute(\"height\", size * 1.6);\n        r.setAttribute(\"rx\", 6);\n        return r;\n       }\n       if (shape === \"triangle\") {\n        const p = document.createElementNS(ns, \"polygon\");\n        const h = size * 1.8;\n        const w = size * 2;\n        p.setAttribute(\"points\", `0,${-h} ${w / 2},${h / 2} ${-w / 2},${h / 2}`);\n        return p;\n       }\n       if (shape === \"hexagon\") {\n        const p = document.createElementNS(ns, \"polygon\");\n        const s = size;\n        const pts = [\n         [0, -s],\n         [s * 0.86, -s * 0.5],\n         [s * 0.86, s * 0.5],\n         [0, s],\n         [-s * 0.86, s * 0.5],\n         [-s * 0.86, -s * 0.5],\n        ].map((pt) => pt.join(\",\")).join(\" \");\n        p.setAttribute(\"points\", pts);\n        return p;\n       }\n       if (shape === \"stop-sign\") {\n        const p = document.createElementNS(ns, \"polygon\");\n        const r = size;\n        const ptsArr = [];\n        for (let i = 0; i < 8; i++) {\n         const a = (Math.PI / 4) * i + Math.PI / 8;\n         ptsArr.push([Math.cos(a) * r, Math.sin(a) * r]);\n        }\n        const pts = ptsArr.map((pt) => pt.join(\",\")).join(\" \");\n        p.setAttribute(\"points\", pts);\n        return p;\n       }\n       if (shape === \"star\") {\n        const p = document.createElementNS(ns, \"polygon\");\n        const outer = size;\n        const inner = size * 0.45;\n        let pts = \"\";\n        for (let i = 0; i < 10; i++) {\n         const a = (Math.PI / 5) * i - Math.PI / 2;\n         const r = i % 2 === 0 ? outer : inner;\n         pts += `${Math.cos(a) * r},${Math.sin(a) * r} `;\n        }\n        p.setAttribute(\"points\", pts.trim());\n        return p;\n       }\n       if (shape === \"diamond\") {\n        const p = document.createElementNS(ns, \"polygon\");\n        const s = size;\n        p.setAttribute(\"points\", `0,${-s} ${s},0 0,${s} ${-s},0`);\n        return p;\n       }\n       if (shape === \"server\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 1.2);\n        body.setAttribute(\"y\", -size * 0.6);\n        body.setAttribute(\"width\", size * 2.4);\n        body.setAttribute(\"height\", size * 1.2);\n        body.setAttribute(\"rx\", 4);\n        g.appendChild(body);\n        for (let i = 0; i < 3; i++) {\n         const line = document.createElementNS(ns, \"line\");\n         line.setAttribute(\"x1\", -size * 0.9 + i * size * 0.5);\n         line.setAttribute(\"y1\", -size * 0.3);\n         line.setAttribute(\"x2\", -size * 0.9 + i * size * 0.5);\n         line.setAttribute(\"y2\", size * 0.3);\n         line.style.stroke = \"currentColor\";\n         line.style.strokeWidth = \"2\";\n         line.style.opacity = \"0.5\";\n         g.appendChild(line);\n        }\n        const led = document.createElementNS(ns, \"circle\");\n        led.setAttribute(\"cx\", size * 0.9);\n        led.setAttribute(\"cy\", 0);\n        led.setAttribute(\"r\", size * 0.1);\n        led.style.fill = \"#4ade80\";\n        g.appendChild(led);\n        return g;\n       }\n       if (shape === \"pc\" || shape === \"desktop\") {\n        const g = document.createElementNS(ns, \"g\");\n        const monitor = document.createElementNS(ns, \"rect\");\n        monitor.setAttribute(\"x\", -size * 0.9);\n        monitor.setAttribute(\"y\", -size * 0.8);\n        monitor.setAttribute(\"width\", size * 1.8);\n        monitor.setAttribute(\"height\", size * 1.2);\n        monitor.setAttribute(\"rx\", 4);\n        g.appendChild(monitor);\n        const screen = document.createElementNS(ns, \"rect\");\n        screen.setAttribute(\"x\", -size * 0.75);\n        screen.setAttribute(\"y\", -size * 0.65);\n        screen.setAttribute(\"width\", size * 1.5);\n        screen.setAttribute(\"height\", size * 0.9);\n        screen.setAttribute(\"rx\", 2);\n        screen.style.fill = \"#1e293b\";\n        g.appendChild(screen);\n        const neck = document.createElementNS(ns, \"rect\");\n        neck.setAttribute(\"x\", -size * 0.15);\n        neck.setAttribute(\"y\", size * 0.4);\n        neck.setAttribute(\"width\", size * 0.3);\n        neck.setAttribute(\"height\", size * 0.3);\n        g.appendChild(neck);\n        const base = document.createElementNS(ns, \"rect\");\n        base.setAttribute(\"x\", -size * 0.5);\n        base.setAttribute(\"y\", size * 0.7);\n        base.setAttribute(\"width\", size * 1);\n        base.setAttribute(\"height\", size * 0.15);\n        base.setAttribute(\"rx\", 2);\n        g.appendChild(base);\n        return g;\n       }\n       if (shape === \"laptop\") {\n        const g = document.createElementNS(ns, \"g\");\n        const screen = document.createElementNS(ns, \"rect\");\n        screen.setAttribute(\"x\", -size * 0.8);\n        screen.setAttribute(\"y\", -size * 0.9);\n        screen.setAttribute(\"width\", size * 1.6);\n        screen.setAttribute(\"height\", size * 1.1);\n        screen.setAttribute(\"rx\", 4);\n        g.appendChild(screen);\n        const base = document.createElementNS(ns, \"rect\");\n        base.setAttribute(\"x\", -size * 0.9);\n        base.setAttribute(\"y\", size * 0.25);\n        base.setAttribute(\"width\", size * 1.8);\n        base.setAttribute(\"height\", size * 0.6);\n        base.setAttribute(\"rx\", 4);\n        g.appendChild(base);\n        const pad = document.createElementNS(ns, \"rect\");\n        pad.setAttribute(\"x\", -size * 0.25);\n        pad.setAttribute(\"y\", size * 0.45);\n        pad.setAttribute(\"width\", size * 0.5);\n        pad.setAttribute(\"height\", size * 0.25);\n        pad.setAttribute(\"rx\", 2);\n        pad.style.fill = \"#1e293b\";\n        g.appendChild(pad);\n        return g;\n       }\n       if (shape === \"phone\" || shape === \"mobile\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 0.45);\n        body.setAttribute(\"y\", -size);\n        body.setAttribute(\"width\", size * 0.9);\n        body.setAttribute(\"height\", size * 2);\n        body.setAttribute(\"rx\", 8);\n        g.appendChild(body);\n        const screen = document.createElementNS(ns, \"rect\");\n        screen.setAttribute(\"x\", -size * 0.38);\n        screen.setAttribute(\"y\", -size * 0.85);\n        screen.setAttribute(\"width\", size * 0.76);\n        screen.setAttribute(\"height\", size * 1.6);\n        screen.setAttribute(\"rx\", 4);\n        screen.style.fill = \"#1e293b\";\n        g.appendChild(screen);\n        const btn = document.createElementNS(ns, \"rect\");\n        btn.setAttribute(\"x\", -size * 0.15);\n        btn.setAttribute(\"y\", size * 0.82);\n        btn.setAttribute(\"width\", size * 0.3);\n        btn.setAttribute(\"height\", size * 0.06);\n        btn.setAttribute(\"rx\", 2);\n        btn.style.fill = \"#475569\";\n        g.appendChild(btn);\n        return g;\n       }\n       if (shape === \"router\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 1.1);\n        body.setAttribute(\"y\", -size * 0.3);\n        body.setAttribute(\"width\", size * 2.2);\n        body.setAttribute(\"height\", size * 0.8);\n        body.setAttribute(\"rx\", 6);\n        g.appendChild(body);\n        for (let i = -1; i <= 1; i++) {\n         const ant = document.createElementNS(ns, \"rect\");\n         ant.setAttribute(\"x\", i * size * 0.6 - size * 0.05);\n         ant.setAttribute(\"y\", -size * 0.9);\n         ant.setAttribute(\"width\", size * 0.1);\n         ant.setAttribute(\"height\", size * 0.6);\n         ant.setAttribute(\"rx\", 2);\n         g.appendChild(ant);\n         const tip = document.createElementNS(ns, \"circle\");\n         tip.setAttribute(\"cx\", i * size * 0.6);\n         tip.setAttribute(\"cy\", -size * 0.95);\n         tip.setAttribute(\"r\", size * 0.08);\n         g.appendChild(tip);\n        }\n        for (let i = 0; i < 4; i++) {\n         const led = document.createElementNS(ns, \"circle\");\n         led.setAttribute(\"cx\", -size * 0.7 + i * size * 0.35);\n         led.setAttribute(\"cy\", size * 0.1);\n         led.setAttribute(\"r\", size * 0.06);\n         led.style.fill = i < 2 ? \"#4ade80\" : \"#facc15\";\n         g.appendChild(led);\n        }\n        return g;\n       }\n       if (shape === \"switch\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 1.4);\n        body.setAttribute(\"y\", -size * 0.4);\n        body.setAttribute(\"width\", size * 2.8);\n        body.setAttribute(\"height\", size * 0.8);\n        body.setAttribute(\"rx\", 4);\n        g.appendChild(body);\n        for (let i = 0; i < 8; i++) {\n         const port = document.createElementNS(ns, \"rect\");\n         port.setAttribute(\"x\", -size * 1.2 + i * size * 0.32);\n         port.setAttribute(\"y\", -size * 0.15);\n         port.setAttribute(\"width\", size * 0.22);\n         port.setAttribute(\"height\", size * 0.3);\n         port.setAttribute(\"rx\", 1);\n         port.style.fill = \"#1e293b\";\n         g.appendChild(port);\n        }\n        return g;\n       }\n       if (shape === \"firewall\") {\n        const g = document.createElementNS(ns, \"g\");\n        const wall = document.createElementNS(ns, \"rect\");\n        wall.setAttribute(\"x\", -size);\n        wall.setAttribute(\"y\", -size * 0.8);\n        wall.setAttribute(\"width\", size * 2);\n        wall.setAttribute(\"height\", size * 1.6);\n        wall.setAttribute(\"rx\", 4);\n        g.appendChild(wall);\n        for (let row = 0; row < 3; row++) {\n         const line = document.createElementNS(ns, \"line\");\n         line.setAttribute(\"x1\", -size * 0.85);\n         line.setAttribute(\"y1\", -size * 0.5 + row * size * 0.45);\n         line.setAttribute(\"x2\", size * 0.85);\n         line.setAttribute(\"y2\", -size * 0.5 + row * size * 0.45);\n         line.style.stroke = \"#475569\";\n         line.style.strokeWidth = \"2\";\n         g.appendChild(line);\n        }\n        for (let row = 0; row < 4; row++) {\n         const offset = row % 2 === 0 ? 0 : size * 0.35;\n         for (let col = 0; col < 3; col++) {\n          const line = document.createElementNS(ns, \"line\");\n          const x = -size * 0.5 + col * size * 0.7 + offset;\n          if (x > -size * 0.85 && x < size * 0.85) {\n           line.setAttribute(\"x1\", x);\n           line.setAttribute(\"y1\", -size * 0.8 + row * size * 0.45);\n           line.setAttribute(\"x2\", x);\n           line.setAttribute(\"y2\", -size * 0.8 + row * size * 0.45 + size * 0.45);\n           line.style.stroke = \"#475569\";\n           line.style.strokeWidth = \"2\";\n           g.appendChild(line);\n          }\n         }\n        }\n        return g;\n       }\n       if (shape === \"cloud\") {\n        const p = document.createElementNS(ns, \"path\");\n        const s = size;\n        p.setAttribute(\"d\", `\n             M ${-s * 0.8} ${s * 0.2}\n             Q ${-s * 1.1} ${s * 0.2} ${-s * 1.1} ${-s * 0.1}\n             Q ${-s * 1.1} ${-s * 0.5} ${-s * 0.7} ${-s * 0.5}\n             Q ${-s * 0.7} ${-s * 0.9} ${-s * 0.2} ${-s * 0.9}\n             Q ${s * 0.1} ${-s * 1.1} ${s * 0.5} ${-s * 0.8}\n             Q ${s * 1} ${-s * 0.8} ${s * 1.1} ${-s * 0.3}\n             Q ${s * 1.3} ${-s * 0.1} ${s * 1.1} ${s * 0.2}\n             Q ${s * 1.1} ${s * 0.5} ${s * 0.7} ${s * 0.5}\n             L ${-s * 0.5} ${s * 0.5}\n             Q ${-s * 0.9} ${s * 0.5} ${-s * 0.9} ${s * 0.2}\n             Z\n            `);\n        return p;\n       }\n       if (shape === \"database\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 0.7);\n        body.setAttribute(\"y\", -size * 0.6);\n        body.setAttribute(\"width\", size * 1.4);\n        body.setAttribute(\"height\", size * 1.4);\n        g.appendChild(body);\n        const top = document.createElementNS(ns, \"ellipse\");\n        top.setAttribute(\"cx\", 0);\n        top.setAttribute(\"cy\", -size * 0.6);\n        top.setAttribute(\"rx\", size * 0.7);\n        top.setAttribute(\"ry\", size * 0.25);\n        g.appendChild(top);\n        const bottom = document.createElementNS(ns, \"ellipse\");\n        bottom.setAttribute(\"cx\", 0);\n        bottom.setAttribute(\"cy\", size * 0.8);\n        bottom.setAttribute(\"rx\", size * 0.7);\n        bottom.setAttribute(\"ry\", size * 0.25);\n        g.appendChild(bottom);\n        const mid1 = document.createElementNS(ns, \"ellipse\");\n        mid1.setAttribute(\"cx\", 0);\n        mid1.setAttribute(\"cy\", -size * 0.15);\n        mid1.setAttribute(\"rx\", size * 0.7);\n        mid1.setAttribute(\"ry\", size * 0.2);\n        mid1.style.fill = \"none\";\n        mid1.style.stroke = \"#475569\";\n        mid1.style.strokeWidth = \"2\";\n        g.appendChild(mid1);\n        const mid2 = document.createElementNS(ns, \"ellipse\");\n        mid2.setAttribute(\"cx\", 0);\n        mid2.setAttribute(\"cy\", size * 0.35);\n        mid2.setAttribute(\"rx\", size * 0.7);\n        mid2.setAttribute(\"ry\", size * 0.2);\n        mid2.style.fill = \"none\";\n        mid2.style.stroke = \"#475569\";\n        mid2.style.strokeWidth = \"2\";\n        g.appendChild(mid2);\n        return g;\n       }\n       if (shape === \"printer\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size);\n        body.setAttribute(\"y\", -size * 0.4);\n        body.setAttribute(\"width\", size * 2);\n        body.setAttribute(\"height\", size * 0.9);\n        body.setAttribute(\"rx\", 4);\n        g.appendChild(body);\n        const trayTop = document.createElementNS(ns, \"rect\");\n        trayTop.setAttribute(\"x\", -size * 0.7);\n        trayTop.setAttribute(\"y\", -size * 0.8);\n        trayTop.setAttribute(\"width\", size * 1.4);\n        trayTop.setAttribute(\"height\", size * 0.4);\n        trayTop.setAttribute(\"rx\", 2);\n        trayTop.style.fill = \"#1e293b\";\n        g.appendChild(trayTop);\n        const trayOut = document.createElementNS(ns, \"rect\");\n        trayOut.setAttribute(\"x\", -size * 0.6);\n        trayOut.setAttribute(\"y\", size * 0.5);\n        trayOut.setAttribute(\"width\", size * 1.2);\n        trayOut.setAttribute(\"height\", size * 0.35);\n        trayOut.setAttribute(\"rx\", 2);\n        g.appendChild(trayOut);\n        const paper = document.createElementNS(ns, \"rect\");\n        paper.setAttribute(\"x\", -size * 0.5);\n        paper.setAttribute(\"y\", size * 0.3);\n        paper.setAttribute(\"width\", size * 1);\n        paper.setAttribute(\"height\", size * 0.5);\n        paper.style.fill = \"#e2e8f0\";\n        g.appendChild(paper);\n        return g;\n       }\n       if (shape === \"access-point\" || shape === \"wifi\") {\n        const g = document.createElementNS(ns, \"g\");\n        const base = document.createElementNS(ns, \"rect\");\n        base.setAttribute(\"x\", -size * 0.3);\n        base.setAttribute(\"y\", size * 0.2);\n        base.setAttribute(\"width\", size * 0.6);\n        base.setAttribute(\"height\", size * 0.3);\n        base.setAttribute(\"rx\", 3);\n        g.appendChild(base);\n        for (let i = 1; i <= 3; i++) {\n         const arc = document.createElementNS(ns, \"path\");\n         const r = size * 0.3 * i;\n         arc.setAttribute(\"d\", `M ${-r * 0.7} ${size * 0.1 - r * 0.5} A ${r} ${r} 0 0 1 ${r * 0.7} ${size * 0.1 - r * 0.5}`);\n         arc.style.fill = \"none\";\n         arc.style.stroke = \"currentColor\";\n         arc.style.strokeWidth = \"2\";\n         arc.style.opacity = 1 - (i * 0.2);\n         g.appendChild(arc);\n        }\n        return g;\n       }\n       if (shape === \"load-balancer\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size);\n        body.setAttribute(\"y\", -size * 0.5);\n        body.setAttribute(\"width\", size * 2);\n        body.setAttribute(\"height\", size);\n        body.setAttribute(\"rx\", 6);\n        g.appendChild(body);\n        const bar = document.createElementNS(ns, \"line\");\n        bar.setAttribute(\"x1\", -size * 0.6);\n        bar.setAttribute(\"y1\", 0);\n        bar.setAttribute(\"x2\", size * 0.6);\n        bar.setAttribute(\"y2\", 0);\n        bar.style.stroke = \"#4ade80\";\n        bar.style.strokeWidth = \"3\";\n        g.appendChild(bar);\n        [-1, 1].forEach(dir => {\n         const arrow = document.createElementNS(ns, \"path\");\n         arrow.setAttribute(\"d\", `M ${dir * size * 0.3} ${-size * 0.2} L ${dir * size * 0.6} 0 L ${dir * size * 0.3} ${size * 0.2}`);\n         arrow.style.fill = \"none\";\n         arrow.style.stroke = \"#4ade80\";\n         arrow.style.strokeWidth = \"2\";\n         g.appendChild(arrow);\n        });\n        return g;\n       }\n       if (shape === \"nas\" || shape === \"storage\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 0.8);\n        body.setAttribute(\"y\", -size * 0.9);\n        body.setAttribute(\"width\", size * 1.6);\n        body.setAttribute(\"height\", size * 1.8);\n        body.setAttribute(\"rx\", 4);\n        g.appendChild(body);\n        for (let i = 0; i < 4; i++) {\n         const bay = document.createElementNS(ns, \"rect\");\n         bay.setAttribute(\"x\", -size * 0.6);\n         bay.setAttribute(\"y\", -size * 0.7 + i * size * 0.4);\n         bay.setAttribute(\"width\", size * 1.2);\n         bay.setAttribute(\"height\", size * 0.3);\n         bay.setAttribute(\"rx\", 2);\n         bay.style.fill = \"#1e293b\";\n         g.appendChild(bay);\n        }\n        return g;\n       }\n       if (shape === \"gateway\" || shape === \"modem\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 0.5);\n        body.setAttribute(\"y\", -size * 0.9);\n        body.setAttribute(\"width\", size);\n        body.setAttribute(\"height\", size * 1.8);\n        body.setAttribute(\"rx\", 4);\n        g.appendChild(body);\n        for (let i = 0; i < 4; i++) {\n         const led = document.createElementNS(ns, \"circle\");\n         led.setAttribute(\"cx\", 0);\n         led.setAttribute(\"cy\", -size * 0.6 + i * size * 0.35);\n         led.setAttribute(\"r\", size * 0.08);\n         led.style.fill = [\"#4ade80\", \"#4ade80\", \"#facc15\", \"#60a5fa\"][i];\n         g.appendChild(led);\n        }\n        return g;\n       }\n       if (shape === \"vpn\" || shape === \"tunnel\") {\n        const g = document.createElementNS(ns, \"g\");\n        const outer = document.createElementNS(ns, \"ellipse\");\n        outer.setAttribute(\"cx\", 0);\n        outer.setAttribute(\"cy\", 0);\n        outer.setAttribute(\"rx\", size);\n        outer.setAttribute(\"ry\", size * 0.6);\n        g.appendChild(outer);\n        const inner = document.createElementNS(ns, \"ellipse\");\n        inner.setAttribute(\"cx\", 0);\n        inner.setAttribute(\"cy\", 0);\n        inner.setAttribute(\"rx\", size * 0.5);\n        inner.setAttribute(\"ry\", size * 0.3);\n        inner.style.fill = \"#1e293b\";\n        g.appendChild(inner);\n        const lock = document.createElementNS(ns, \"rect\");\n        lock.setAttribute(\"x\", -size * 0.15);\n        lock.setAttribute(\"y\", -size * 0.1);\n        lock.setAttribute(\"width\", size * 0.3);\n        lock.setAttribute(\"height\", size * 0.25);\n        lock.setAttribute(\"rx\", 2);\n        lock.style.fill = \"#4ade80\";\n        g.appendChild(lock);\n        return g;\n       }\n       if (shape === \"container\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size);\n        body.setAttribute(\"y\", -size * 0.5);\n        body.setAttribute(\"width\", size * 2);\n        body.setAttribute(\"height\", size);\n        body.setAttribute(\"rx\", 4);\n        g.appendChild(body);\n        for (let i = 0; i < 3; i++) {\n         const block = document.createElementNS(ns, \"rect\");\n         block.setAttribute(\"x\", -size * 0.8 + i * size * 0.55);\n         block.setAttribute(\"y\", -size * 0.3);\n         block.setAttribute(\"width\", size * 0.45);\n         block.setAttribute(\"height\", size * 0.6);\n         block.setAttribute(\"rx\", 2);\n         block.style.fill = \"#1e293b\";\n         g.appendChild(block);\n        }\n        return g;\n       }\n       if (shape === \"vm\" || shape === \"virtual\") {\n        const g = document.createElementNS(ns, \"g\");\n        const shadow = document.createElementNS(ns, \"rect\");\n        shadow.setAttribute(\"x\", -size * 0.85 + 4);\n        shadow.setAttribute(\"y\", -size * 0.65 + 4);\n        shadow.setAttribute(\"width\", size * 1.7);\n        shadow.setAttribute(\"height\", size * 1.3);\n        shadow.setAttribute(\"rx\", 4);\n        shadow.style.opacity = \"0.3\";\n        g.appendChild(shadow);\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 0.85);\n        body.setAttribute(\"y\", -size * 0.65);\n        body.setAttribute(\"width\", size * 1.7);\n        body.setAttribute(\"height\", size * 1.3);\n        body.setAttribute(\"rx\", 4);\n        g.appendChild(body);\n        const screen = document.createElementNS(ns, \"rect\");\n        screen.setAttribute(\"x\", -size * 0.65);\n        screen.setAttribute(\"y\", -size * 0.45);\n        screen.setAttribute(\"width\", size * 1.3);\n        screen.setAttribute(\"height\", size * 0.7);\n        screen.setAttribute(\"rx\", 2);\n        screen.style.fill = \"#1e293b\";\n        g.appendChild(screen);\n        return g;\n       }\n       if (shape === \"kubernetes\" || shape === \"k8s\") {\n        const g = document.createElementNS(ns, \"g\");\n        const outer = document.createElementNS(ns, \"circle\");\n        outer.setAttribute(\"cx\", 0);\n        outer.setAttribute(\"cy\", 0);\n        outer.setAttribute(\"r\", size);\n        g.appendChild(outer);\n        const inner = document.createElementNS(ns, \"circle\");\n        inner.setAttribute(\"cx\", 0);\n        inner.setAttribute(\"cy\", 0);\n        inner.setAttribute(\"r\", size * 0.4);\n        inner.style.fill = \"#1e293b\";\n        g.appendChild(inner);\n        for (let i = 0; i < 7; i++) {\n         const angle = (Math.PI * 2 / 7) * i - Math.PI / 2;\n         const spoke = document.createElementNS(ns, \"line\");\n         spoke.setAttribute(\"x1\", Math.cos(angle) * size * 0.4);\n         spoke.setAttribute(\"y1\", Math.sin(angle) * size * 0.4);\n         spoke.setAttribute(\"x2\", Math.cos(angle) * size * 0.85);\n         spoke.setAttribute(\"y2\", Math.sin(angle) * size * 0.85);\n         spoke.style.stroke = \"#326ce5\";\n         spoke.style.strokeWidth = \"3\";\n         g.appendChild(spoke);\n        }\n        return g;\n       }\n       if (shape === \"shield\" || shape === \"security\") {\n        const p = document.createElementNS(ns, \"path\");\n        const s = size;\n        p.setAttribute(\"d\", `M 0 ${-s} L ${s * 0.85} ${-s * 0.5} L ${s * 0.85} ${s * 0.2} Q ${s * 0.7} ${s * 0.9} 0 ${s} Q ${-s * 0.7} ${s * 0.9} ${-s * 0.85} ${s * 0.2} L ${-s * 0.85} ${-s * 0.5} Z`);\n        return p;\n       }\n       if (shape === \"camera\" || shape === \"cctv\") {\n        const g = document.createElementNS(ns, \"g\");\n        const mount = document.createElementNS(ns, \"rect\");\n        mount.setAttribute(\"x\", -size * 1.1);\n        mount.setAttribute(\"y\", -size * 0.5);\n        mount.setAttribute(\"width\", size * 0.25);\n        mount.setAttribute(\"height\", size * 0.6);\n        mount.setAttribute(\"rx\", 2);\n        g.appendChild(mount);\n        const arm = document.createElementNS(ns, \"rect\");\n        arm.setAttribute(\"x\", -size * 0.9);\n        arm.setAttribute(\"y\", -size * 0.15);\n        arm.setAttribute(\"width\", size * 0.5);\n        arm.setAttribute(\"height\", size * 0.15);\n        g.appendChild(arm);\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 0.45);\n        body.setAttribute(\"y\", -size * 0.35);\n        body.setAttribute(\"width\", size * 1.1);\n        body.setAttribute(\"height\", size * 0.7);\n        body.setAttribute(\"rx\", 4);\n        g.appendChild(body);\n        const lensHousing = document.createElementNS(ns, \"circle\");\n        lensHousing.setAttribute(\"cx\", size * 0.85);\n        lensHousing.setAttribute(\"cy\", 0);\n        lensHousing.setAttribute(\"r\", size * 0.4);\n        g.appendChild(lensHousing);\n        const lensOuter = document.createElementNS(ns, \"circle\");\n        lensOuter.setAttribute(\"cx\", size * 0.85);\n        lensOuter.setAttribute(\"cy\", 0);\n        lensOuter.setAttribute(\"r\", size * 0.28);\n        lensOuter.style.fill = \"#1e293b\";\n        g.appendChild(lensOuter);\n        const lensInner = document.createElementNS(ns, \"circle\");\n        lensInner.setAttribute(\"cx\", size * 0.85);\n        lensInner.setAttribute(\"cy\", 0);\n        lensInner.setAttribute(\"r\", size * 0.15);\n        lensInner.style.fill = \"#3b82f6\";\n        g.appendChild(lensInner);\n        const reflection = document.createElementNS(ns, \"circle\");\n        reflection.setAttribute(\"cx\", size * 0.8);\n        reflection.setAttribute(\"cy\", -size * 0.05);\n        reflection.setAttribute(\"r\", size * 0.05);\n        reflection.style.fill = \"#93c5fd\";\n        g.appendChild(reflection);\n        const led = document.createElementNS(ns, \"circle\");\n        led.setAttribute(\"cx\", -size * 0.2);\n        led.setAttribute(\"cy\", -size * 0.15);\n        led.setAttribute(\"r\", size * 0.06);\n        led.style.fill = \"#ef4444\";\n        g.appendChild(led);\n        return g;\n       }\n       if (shape === \"monitor\" || shape === \"dashboard\") {\n        const g = document.createElementNS(ns, \"g\");\n        const screen = document.createElementNS(ns, \"rect\");\n        screen.setAttribute(\"x\", -size);\n        screen.setAttribute(\"y\", -size * 0.7);\n        screen.setAttribute(\"width\", size * 2);\n        screen.setAttribute(\"height\", size * 1.2);\n        screen.setAttribute(\"rx\", 4);\n        g.appendChild(screen);\n        const graph = document.createElementNS(ns, \"polyline\");\n        graph.setAttribute(\"points\", `${-size * 0.8},${size * 0.2} ${-size * 0.3},${-size * 0.2} ${size * 0.2},${size * 0.1} ${size * 0.7},${-size * 0.3}`);\n        graph.style.fill = \"none\";\n        graph.style.stroke = \"#4ade80\";\n        graph.style.strokeWidth = \"3\";\n        g.appendChild(graph);\n        return g;\n       }\n       if (shape === \"docker\" || shape === \"whale\") {\n        const g = document.createElementNS(ns, \"g\");\n        const s = size;\n        const body = document.createElementNS(ns, \"path\");\n        body.setAttribute(\"d\", `\n         M ${-s * 0.9} ${s * 0.2}\n         Q ${-s * 1.1} ${s * 0.5} ${-s * 0.7} ${s * 0.7}\n         Q ${-s * 0.3} ${s * 0.85} ${s * 0.3} ${s * 0.75}\n         Q ${s * 0.8} ${s * 0.6} ${s * 1.0} ${s * 0.3}\n         Q ${s * 1.1} ${s * 0.1} ${s * 1.0} ${-s * 0.1}\n         L ${s * 0.85} ${-s * 0.1}\n         Q ${s * 0.7} ${-s * 0.15} ${s * 0.4} ${-s * 0.2}\n         L ${-s * 0.5} ${-s * 0.2}\n         Q ${-s * 0.8} ${-s * 0.1} ${-s * 0.9} ${s * 0.2}\n         Z\n        `);\n        body.style.fill = \"#0db7ed\";\n        g.appendChild(body);\n        const tail = document.createElementNS(ns, \"path\");\n        tail.setAttribute(\"d\", `\n         M ${-s * 0.85} ${s * 0.1}\n         Q ${-s * 1.2} ${-s * 0.2} ${-s * 1.0} ${-s * 0.55}\n         Q ${-s * 0.95} ${-s * 0.35} ${-s * 0.8} ${-s * 0.15}\n         Z\n        `);\n        tail.style.fill = \"#0db7ed\";\n        g.appendChild(tail);\n        const belly = document.createElementNS(ns, \"path\");\n        belly.setAttribute(\"d\", `\n         M ${-s * 0.5} ${s * 0.65}\n         Q ${s * 0.1} ${s * 0.75} ${s * 0.6} ${s * 0.55}\n         Q ${s * 0.8} ${s * 0.45} ${s * 0.9} ${s * 0.25}\n         Q ${s * 0.7} ${s * 0.5} ${s * 0.3} ${s * 0.6}\n         Q ${-s * 0.1} ${s * 0.7} ${-s * 0.5} ${s * 0.65}\n         Z\n        `);\n        belly.style.fill = \"#ffffff\";\n        belly.style.opacity = \"0.3\";\n        g.appendChild(belly);\n        for (let row = 0; row < 2; row++) {\n         for (let col = 0; col < 3; col++) {\n          const container = document.createElementNS(ns, \"rect\");\n          container.setAttribute(\"x\", -s * 0.45 + col * s * 0.35);\n          container.setAttribute(\"y\", -s * 0.7 + row * s * 0.28);\n          container.setAttribute(\"width\", s * 0.3);\n          container.setAttribute(\"height\", s * 0.23);\n          container.setAttribute(\"rx\", 2);\n          container.style.fill = \"#0db7ed\";\n          container.style.stroke = \"#0a9ed8\";\n          container.style.strokeWidth = \"1.5\";\n          g.appendChild(container);\n         }\n        }\n        const topContainer = document.createElementNS(ns, \"rect\");\n        topContainer.setAttribute(\"x\", -s * 0.1);\n        topContainer.setAttribute(\"y\", -s * 0.95);\n        topContainer.setAttribute(\"width\", s * 0.3);\n        topContainer.setAttribute(\"height\", s * 0.23);\n        topContainer.setAttribute(\"rx\", 2);\n        topContainer.style.fill = \"#0db7ed\";\n        topContainer.style.stroke = \"#0a9ed8\";\n        topContainer.style.strokeWidth = \"1.5\";\n        g.appendChild(topContainer);\n        const eye = document.createElementNS(ns, \"circle\");\n        eye.setAttribute(\"cx\", s * 0.65);\n        eye.setAttribute(\"cy\", s * 0.25);\n        eye.setAttribute(\"r\", s * 0.08);\n        eye.style.fill = \"#0a5f7a\";\n        g.appendChild(eye);\n        return g;\n       }\n       if (shape === \"rounded-square\") {\n        const r = document.createElementNS(ns, \"rect\");\n        const s = size * 1.6;\n        r.setAttribute(\"x\", -s / 2);\n        r.setAttribute(\"y\", -s / 2);\n        r.setAttribute(\"width\", s);\n        r.setAttribute(\"height\", s);\n        r.setAttribute(\"rx\", size * 0.4);\n        return r;\n       }\n       if (shape === \"pill\" || shape === \"capsule\") {\n        const r = document.createElementNS(ns, \"rect\");\n        r.setAttribute(\"x\", -size * 1.2);\n        r.setAttribute(\"y\", -size * 0.5);\n        r.setAttribute(\"width\", size * 2.4);\n        r.setAttribute(\"height\", size);\n        r.setAttribute(\"rx\", size * 0.5);\n        return r;\n       }\n       if (shape === \"octagon\") {\n        const p = document.createElementNS(ns, \"polygon\");\n        const r = size;\n        const pts = [];\n        for (let i = 0; i < 8; i++) {\n         const a = (Math.PI / 4) * i - Math.PI / 8;\n         pts.push([Math.cos(a) * r, Math.sin(a) * r]);\n        }\n        p.setAttribute(\"points\", pts.map(pt => pt.join(\",\")).join(\" \"));\n        return p;\n       }\n       if (shape === \"pentagon\") {\n        const p = document.createElementNS(ns, \"polygon\");\n        const pts = [];\n        for (let i = 0; i < 5; i++) {\n         const a = (Math.PI * 2 / 5) * i - Math.PI / 2;\n         pts.push([Math.cos(a) * size, Math.sin(a) * size]);\n        }\n        p.setAttribute(\"points\", pts.map(pt => pt.join(\",\")).join(\" \"));\n        return p;\n       }\n       if (shape === \"cross\" || shape === \"plus\") {\n        const p = document.createElementNS(ns, \"polygon\");\n        const s = size;\n        const t = size * 0.35;\n        p.setAttribute(\"points\", `${-t},${-s} ${t},${-s} ${t},${-t} ${s},${-t} ${s},${t} ${t},${t} ${t},${s} ${-t},${s} ${-t},${t} ${-s},${t} ${-s},${-t} ${-t},${-t}`);\n        return p;\n       }\n       if (shape === \"parallelogram\") {\n        const p = document.createElementNS(ns, \"polygon\");\n        const s = size;\n        p.setAttribute(\"points\", `${-s * 0.6},${-s * 0.6} ${s * 1.2},${-s * 0.6} ${s * 0.6},${s * 0.6} ${-s * 1.2},${s * 0.6}`);\n        return p;\n       }\n       if (shape === \"trapezoid\") {\n        const p = document.createElementNS(ns, \"polygon\");\n        const s = size;\n        p.setAttribute(\"points\", `${-s * 0.6},${-s * 0.6} ${s * 0.6},${-s * 0.6} ${s},${s * 0.6} ${-s},${s * 0.6}`);\n        return p;\n       }\n       if (shape === \"sensor\" || shape === \"iot\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"circle\");\n        body.setAttribute(\"cx\", 0);\n        body.setAttribute(\"cy\", 0);\n        body.setAttribute(\"r\", size * 0.7);\n        g.appendChild(body);\n        const ant = document.createElementNS(ns, \"line\");\n        ant.setAttribute(\"x1\", 0);\n        ant.setAttribute(\"y1\", -size * 0.7);\n        ant.setAttribute(\"x2\", 0);\n        ant.setAttribute(\"y2\", -size);\n        ant.style.stroke = \"currentColor\";\n        ant.style.strokeWidth = \"2\";\n        g.appendChild(ant);\n        const tip = document.createElementNS(ns, \"circle\");\n        tip.setAttribute(\"cx\", 0);\n        tip.setAttribute(\"cy\", -size);\n        tip.setAttribute(\"r\", size * 0.1);\n        tip.style.fill = \"#4ade80\";\n        g.appendChild(tip);\n        return g;\n       }\n       if (shape === \"pi\" || shape === \"sbc\" || shape === \"raspberry\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size);\n        body.setAttribute(\"y\", -size * 0.65);\n        body.setAttribute(\"width\", size * 2);\n        body.setAttribute(\"height\", size * 1.3);\n        body.setAttribute(\"rx\", 4);\n        g.appendChild(body);\n        for (let i = 0; i < 10; i++) {\n         const pin = document.createElementNS(ns, \"rect\");\n         pin.setAttribute(\"x\", -size * 0.85 + i * size * 0.19);\n         pin.setAttribute(\"y\", -size * 0.8);\n         pin.setAttribute(\"width\", size * 0.08);\n         pin.setAttribute(\"height\", size * 0.15);\n         pin.style.fill = \"#facc15\";\n         g.appendChild(pin);\n        }\n        const port = document.createElementNS(ns, \"rect\");\n        port.setAttribute(\"x\", size * 0.6);\n        port.setAttribute(\"y\", -size * 0.2);\n        port.setAttribute(\"width\", size * 0.35);\n        port.setAttribute(\"height\", size * 0.4);\n        port.style.fill = \"#1e293b\";\n        g.appendChild(port);\n        return g;\n       }\n       if (shape === \"api\" || shape === \"endpoint\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 0.9);\n        body.setAttribute(\"y\", -size * 0.7);\n        body.setAttribute(\"width\", size * 1.8);\n        body.setAttribute(\"height\", size * 1.4);\n        body.setAttribute(\"rx\", 6);\n        g.appendChild(body);\n        const left = document.createElementNS(ns, \"text\");\n        left.setAttribute(\"x\", -size * 0.5);\n        left.setAttribute(\"y\", size * 0.15);\n        left.setAttribute(\"font-size\", size * 0.9);\n        left.setAttribute(\"fill\", \"#4ade80\");\n        left.setAttribute(\"font-family\", \"monospace\");\n        left.textContent = \"{\";\n        g.appendChild(left);\n        const right = document.createElementNS(ns, \"text\");\n        right.setAttribute(\"x\", size * 0.15);\n        right.setAttribute(\"y\", size * 0.15);\n        right.setAttribute(\"font-size\", size * 0.9);\n        right.setAttribute(\"fill\", \"#4ade80\");\n        right.setAttribute(\"font-family\", \"monospace\");\n        right.textContent = \"}\";\n        g.appendChild(right);\n        return g;\n       }\n       if (shape === \"queue\" || shape === \"message\") {\n        const g = document.createElementNS(ns, \"g\");\n        for (let i = 2; i >= 0; i--) {\n         const card = document.createElementNS(ns, \"rect\");\n         card.setAttribute(\"x\", -size * 0.7 + i * 4);\n         card.setAttribute(\"y\", -size * 0.5 + i * 4);\n         card.setAttribute(\"width\", size * 1.4);\n         card.setAttribute(\"height\", size * 0.8);\n         card.setAttribute(\"rx\", 3);\n         card.style.opacity = 1 - i * 0.25;\n         g.appendChild(card);\n        }\n        return g;\n       }\n       if (shape === \"lambda\" || shape === \"function\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 0.8);\n        body.setAttribute(\"y\", -size * 0.8);\n        body.setAttribute(\"width\", size * 1.6);\n        body.setAttribute(\"height\", size * 1.6);\n        body.setAttribute(\"rx\", 4);\n        g.appendChild(body);\n        const lambda = document.createElementNS(ns, \"text\");\n        lambda.setAttribute(\"x\", 0);\n        lambda.setAttribute(\"y\", size * 0.2);\n        lambda.setAttribute(\"font-size\", size * 1.2);\n        lambda.setAttribute(\"fill\", \"#f59e0b\");\n        lambda.setAttribute(\"text-anchor\", \"middle\");\n        lambda.setAttribute(\"font-family\", \"serif\");\n        lambda.textContent = \"λ\";\n        g.appendChild(lambda);\n        return g;\n       }\n       if (shape === \"bucket\" || shape === \"s3\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"path\");\n        body.setAttribute(\"d\", `M ${-size * 0.8} ${-size * 0.7} L ${-size * 0.6} ${size * 0.8} Q ${-size * 0.5} ${size} 0 ${size} Q ${size * 0.5} ${size} ${size * 0.6} ${size * 0.8} L ${size * 0.8} ${-size * 0.7} Z`);\n        g.appendChild(body);\n        const rim = document.createElementNS(ns, \"ellipse\");\n        rim.setAttribute(\"cx\", 0);\n        rim.setAttribute(\"cy\", -size * 0.7);\n        rim.setAttribute(\"rx\", size * 0.8);\n        rim.setAttribute(\"ry\", size * 0.25);\n        g.appendChild(rim);\n        return g;\n       }\n       if (shape === \"thermostat\") {\n        const g = document.createElementNS(ns, \"g\");\n        const outer = document.createElementNS(ns, \"circle\");\n        outer.setAttribute(\"r\", size);\n        g.appendChild(outer);\n        const inner = document.createElementNS(ns, \"circle\");\n        inner.setAttribute(\"r\", size * 0.75);\n        inner.style.fill = \"#1e293b\";\n        g.appendChild(inner);\n        const temp = document.createElementNS(ns, \"text\");\n        temp.setAttribute(\"x\", 0);\n        temp.setAttribute(\"y\", size * 0.15);\n        temp.setAttribute(\"font-size\", size * 0.5);\n        temp.setAttribute(\"fill\", \"#4ade80\");\n        temp.setAttribute(\"text-anchor\", \"middle\");\n        temp.setAttribute(\"font-family\", \"monospace\");\n        temp.textContent = \"72°\";\n        g.appendChild(temp);\n        return g;\n       }\n       if (shape === \"doorbell\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 0.5);\n        body.setAttribute(\"y\", -size);\n        body.setAttribute(\"width\", size);\n        body.setAttribute(\"height\", size * 2);\n        body.setAttribute(\"rx\", size * 0.25);\n        g.appendChild(body);\n        const lens = document.createElementNS(ns, \"circle\");\n        lens.setAttribute(\"cx\", 0);\n        lens.setAttribute(\"cy\", -size * 0.35);\n        lens.setAttribute(\"r\", size * 0.3);\n        lens.style.fill = \"#1e293b\";\n        g.appendChild(lens);\n        const lensDot = document.createElementNS(ns, \"circle\");\n        lensDot.setAttribute(\"cx\", 0);\n        lensDot.setAttribute(\"cy\", -size * 0.35);\n        lensDot.setAttribute(\"r\", size * 0.12);\n        lensDot.style.fill = \"#3b82f6\";\n        g.appendChild(lensDot);\n        const btn = document.createElementNS(ns, \"circle\");\n        btn.setAttribute(\"cx\", 0);\n        btn.setAttribute(\"cy\", size * 0.5);\n        btn.setAttribute(\"r\", size * 0.2);\n        btn.style.fill = \"#f59e0b\";\n        g.appendChild(btn);\n        return g;\n       }\n       if (shape === \"smart-lock\") {\n        const g = document.createElementNS(ns, \"g\");\n        const shackle = document.createElementNS(ns, \"path\");\n        shackle.setAttribute(\"d\", `M ${-size * 0.4} ${-size * 0.1} L ${-size * 0.4} ${-size * 0.6} A ${size * 0.4} ${size * 0.4} 0 1 1 ${size * 0.4} ${-size * 0.6} L ${size * 0.4} ${-size * 0.1}`);\n        shackle.style.fill = \"none\";\n        shackle.style.strokeWidth = size * 0.2;\n        g.appendChild(shackle);\n        const lockBody = document.createElementNS(ns, \"rect\");\n        lockBody.setAttribute(\"x\", -size * 0.6);\n        lockBody.setAttribute(\"y\", -size * 0.15);\n        lockBody.setAttribute(\"width\", size * 1.2);\n        lockBody.setAttribute(\"height\", size * 1);\n        lockBody.setAttribute(\"rx\", 4);\n        g.appendChild(lockBody);\n        const keyhole = document.createElementNS(ns, \"circle\");\n        keyhole.setAttribute(\"cx\", 0);\n        keyhole.setAttribute(\"cy\", size * 0.3);\n        keyhole.setAttribute(\"r\", size * 0.15);\n        keyhole.style.fill = \"#4ade80\";\n        g.appendChild(keyhole);\n        return g;\n       }\n       if (shape === \"smart-bulb\") {\n        const g = document.createElementNS(ns, \"g\");\n        const bulb = document.createElementNS(ns, \"path\");\n        bulb.setAttribute(\"d\", `M ${-size * 0.5} ${size * 0.2} Q ${-size * 0.8} ${-size * 0.3} ${-size * 0.5} ${-size * 0.7} Q 0 ${-size * 1.1} ${size * 0.5} ${-size * 0.7} Q ${size * 0.8} ${-size * 0.3} ${size * 0.5} ${size * 0.2} Z`);\n        g.appendChild(bulb);\n        const base1 = document.createElementNS(ns, \"rect\");\n        base1.setAttribute(\"x\", -size * 0.35);\n        base1.setAttribute(\"y\", size * 0.2);\n        base1.setAttribute(\"width\", size * 0.7);\n        base1.setAttribute(\"height\", size * 0.15);\n        base1.style.fill = \"#94a3b8\";\n        g.appendChild(base1);\n        const base2 = document.createElementNS(ns, \"rect\");\n        base2.setAttribute(\"x\", -size * 0.3);\n        base2.setAttribute(\"y\", size * 0.35);\n        base2.setAttribute(\"width\", size * 0.6);\n        base2.setAttribute(\"height\", size * 0.15);\n        base2.style.fill = \"#64748b\";\n        g.appendChild(base2);\n        const base3 = document.createElementNS(ns, \"rect\");\n        base3.setAttribute(\"x\", -size * 0.25);\n        base3.setAttribute(\"y\", size * 0.5);\n        base3.setAttribute(\"width\", size * 0.5);\n        base3.setAttribute(\"height\", size * 0.2);\n        base3.setAttribute(\"rx\", 2);\n        base3.style.fill = \"#475569\";\n        g.appendChild(base3);\n        return g;\n       }\n       if (shape === \"smart-plug\") {\n        const g = document.createElementNS(ns, \"g\");\n        const plugBody = document.createElementNS(ns, \"rect\");\n        plugBody.setAttribute(\"x\", -size * 0.7);\n        plugBody.setAttribute(\"y\", -size * 0.6);\n        plugBody.setAttribute(\"width\", size * 1.4);\n        plugBody.setAttribute(\"height\", size * 1.2);\n        plugBody.setAttribute(\"rx\", 6);\n        g.appendChild(plugBody);\n        const hole1 = document.createElementNS(ns, \"rect\");\n        hole1.setAttribute(\"x\", -size * 0.35);\n        hole1.setAttribute(\"y\", -size * 0.3);\n        hole1.setAttribute(\"width\", size * 0.15);\n        hole1.setAttribute(\"height\", size * 0.4);\n        hole1.setAttribute(\"rx\", 2);\n        hole1.style.fill = \"#1e293b\";\n        g.appendChild(hole1);\n        const hole2 = document.createElementNS(ns, \"rect\");\n        hole2.setAttribute(\"x\", size * 0.2);\n        hole2.setAttribute(\"y\", -size * 0.3);\n        hole2.setAttribute(\"width\", size * 0.15);\n        hole2.setAttribute(\"height\", size * 0.4);\n        hole2.setAttribute(\"rx\", 2);\n        hole2.style.fill = \"#1e293b\";\n        g.appendChild(hole2);\n        const led = document.createElementNS(ns, \"circle\");\n        led.setAttribute(\"cx\", 0);\n        led.setAttribute(\"cy\", size * 0.35);\n        led.setAttribute(\"r\", size * 0.1);\n        led.style.fill = \"#4ade80\";\n        g.appendChild(led);\n        return g;\n       }\n       if (shape === \"smart-speaker\") {\n        const g = document.createElementNS(ns, \"g\");\n        const speakerBody = document.createElementNS(ns, \"rect\");\n        speakerBody.setAttribute(\"x\", -size * 0.6);\n        speakerBody.setAttribute(\"y\", -size);\n        speakerBody.setAttribute(\"width\", size * 1.2);\n        speakerBody.setAttribute(\"height\", size * 2);\n        speakerBody.setAttribute(\"rx\", size * 0.3);\n        g.appendChild(speakerBody);\n        const mesh = document.createElementNS(ns, \"rect\");\n        mesh.setAttribute(\"x\", -size * 0.5);\n        mesh.setAttribute(\"y\", -size * 0.3);\n        mesh.setAttribute(\"width\", size);\n        mesh.setAttribute(\"height\", size * 1.1);\n        mesh.setAttribute(\"rx\", 4);\n        mesh.style.fill = \"#1e293b\";\n        g.appendChild(mesh);\n        const ring = document.createElementNS(ns, \"circle\");\n        ring.setAttribute(\"cx\", 0);\n        ring.setAttribute(\"cy\", -size * 0.65);\n        ring.setAttribute(\"r\", size * 0.2);\n        ring.style.fill = \"#3b82f6\";\n        g.appendChild(ring);\n        return g;\n       }\n       if (shape === \"smart-tv\") {\n        const g = document.createElementNS(ns, \"g\");\n        const frame = document.createElementNS(ns, \"rect\");\n        frame.setAttribute(\"x\", -size * 1.4);\n        frame.setAttribute(\"y\", -size * 0.85);\n        frame.setAttribute(\"width\", size * 2.8);\n        frame.setAttribute(\"height\", size * 1.6);\n        frame.setAttribute(\"rx\", 4);\n        g.appendChild(frame);\n        const screen = document.createElementNS(ns, \"rect\");\n        screen.setAttribute(\"x\", -size * 1.3);\n        screen.setAttribute(\"y\", -size * 0.75);\n        screen.setAttribute(\"width\", size * 2.6);\n        screen.setAttribute(\"height\", size * 1.4);\n        screen.setAttribute(\"rx\", 2);\n        screen.style.fill = \"#1e293b\";\n        g.appendChild(screen);\n        const stand = document.createElementNS(ns, \"rect\");\n        stand.setAttribute(\"x\", -size * 0.8);\n        stand.setAttribute(\"y\", size * 0.75);\n        stand.setAttribute(\"width\", size * 1.6);\n        stand.setAttribute(\"height\", size * 0.12);\n        stand.setAttribute(\"rx\", 2);\n        g.appendChild(stand);\n        return g;\n       }\n       if (shape === \"hub\") {\n        const g = document.createElementNS(ns, \"g\");\n        const hubBody = document.createElementNS(ns, \"rect\");\n        hubBody.setAttribute(\"x\", -size * 0.9);\n        hubBody.setAttribute(\"y\", -size * 0.5);\n        hubBody.setAttribute(\"width\", size * 1.8);\n        hubBody.setAttribute(\"height\", size);\n        hubBody.setAttribute(\"rx\", 8);\n        g.appendChild(hubBody);\n        for (let i = 0; i < 4; i++) {\n         const led = document.createElementNS(ns, \"circle\");\n         led.setAttribute(\"cx\", -size * 0.5 + i * size * 0.35);\n         led.setAttribute(\"cy\", 0);\n         led.setAttribute(\"r\", size * 0.08);\n         led.style.fill = [\"#4ade80\", \"#3b82f6\", \"#f59e0b\", \"#ef4444\"][i];\n         g.appendChild(led);\n        }\n        return g;\n       }\n       if (shape === \"smoke-detector\") {\n        const g = document.createElementNS(ns, \"g\");\n        const outer = document.createElementNS(ns, \"circle\");\n        outer.setAttribute(\"r\", size);\n        g.appendChild(outer);\n        const inner = document.createElementNS(ns, \"circle\");\n        inner.setAttribute(\"r\", size * 0.6);\n        inner.style.fill = \"#1e293b\";\n        g.appendChild(inner);\n        for (let i = 0; i < 6; i++) {\n         const slot = document.createElementNS(ns, \"rect\");\n         const angle = (i * 60 - 90) * Math.PI / 180;\n         slot.setAttribute(\"x\", Math.cos(angle) * size * 0.35 - size * 0.08);\n         slot.setAttribute(\"y\", Math.sin(angle) * size * 0.35 - size * 0.03);\n         slot.setAttribute(\"width\", size * 0.16);\n         slot.setAttribute(\"height\", size * 0.06);\n         slot.setAttribute(\"rx\", 1);\n         slot.style.fill = \"#475569\";\n         slot.setAttribute(\"transform\", `rotate(${i * 60}, ${Math.cos(angle) * size * 0.35}, ${Math.sin(angle) * size * 0.35})`);\n         g.appendChild(slot);\n        }\n        const led = document.createElementNS(ns, \"circle\");\n        led.setAttribute(\"cx\", 0);\n        led.setAttribute(\"cy\", 0);\n        led.setAttribute(\"r\", size * 0.1);\n        led.style.fill = \"#ef4444\";\n        g.appendChild(led);\n        return g;\n       }\n       if (shape === \"motion-sensor\") {\n        const g = document.createElementNS(ns, \"g\");\n        const dome = document.createElementNS(ns, \"path\");\n        dome.setAttribute(\"d\", `M ${-size * 0.8} ${size * 0.3} Q ${-size * 0.8} ${-size * 0.8} 0 ${-size * 0.8} Q ${size * 0.8} ${-size * 0.8} ${size * 0.8} ${size * 0.3} Z`);\n        g.appendChild(dome);\n        const base = document.createElementNS(ns, \"rect\");\n        base.setAttribute(\"x\", -size * 0.8);\n        base.setAttribute(\"y\", size * 0.3);\n        base.setAttribute(\"width\", size * 1.6);\n        base.setAttribute(\"height\", size * 0.35);\n        base.setAttribute(\"rx\", 3);\n        g.appendChild(base);\n        const lens = document.createElementNS(ns, \"ellipse\");\n        lens.setAttribute(\"cx\", 0);\n        lens.setAttribute(\"cy\", -size * 0.15);\n        lens.setAttribute(\"rx\", size * 0.35);\n        lens.setAttribute(\"ry\", size * 0.25);\n        lens.style.fill = \"#1e293b\";\n        g.appendChild(lens);\n        const led = document.createElementNS(ns, \"circle\");\n        led.setAttribute(\"cx\", 0);\n        led.setAttribute(\"cy\", size * 0.45);\n        led.setAttribute(\"r\", size * 0.08);\n        led.style.fill = \"#4ade80\";\n        g.appendChild(led);\n        return g;\n       }\n       if (shape === \"garage\") {\n        const g = document.createElementNS(ns, \"g\");\n        const frame = document.createElementNS(ns, \"rect\");\n        frame.setAttribute(\"x\", -size);\n        frame.setAttribute(\"y\", -size * 0.9);\n        frame.setAttribute(\"width\", size * 2);\n        frame.setAttribute(\"height\", size * 1.8);\n        frame.setAttribute(\"rx\", 4);\n        g.appendChild(frame);\n        for (let i = 0; i < 4; i++) {\n         const panel = document.createElementNS(ns, \"rect\");\n         panel.setAttribute(\"x\", -size * 0.9);\n         panel.setAttribute(\"y\", -size * 0.8 + i * size * 0.42);\n         panel.setAttribute(\"width\", size * 1.8);\n         panel.setAttribute(\"height\", size * 0.35);\n         panel.setAttribute(\"rx\", 2);\n         panel.style.fill = \"#1e293b\";\n         g.appendChild(panel);\n        }\n        return g;\n       }\n       if (shape === \"sprinkler\") {\n        const g = document.createElementNS(ns, \"g\");\n        const head = document.createElementNS(ns, \"circle\");\n        head.setAttribute(\"r\", size * 0.5);\n        g.appendChild(head);\n        const nozzle = document.createElementNS(ns, \"rect\");\n        nozzle.setAttribute(\"x\", -size * 0.15);\n        nozzle.setAttribute(\"y\", size * 0.3);\n        nozzle.setAttribute(\"width\", size * 0.3);\n        nozzle.setAttribute(\"height\", size * 0.5);\n        g.appendChild(nozzle);\n        for (let i = 0; i < 5; i++) {\n         const spray = document.createElementNS(ns, \"line\");\n         const angle = (-60 + i * 30) * Math.PI / 180;\n         spray.setAttribute(\"x1\", 0);\n         spray.setAttribute(\"y1\", -size * 0.3);\n         spray.setAttribute(\"x2\", Math.cos(angle) * size * 0.8);\n         spray.setAttribute(\"y2\", Math.sin(angle) * size * 0.8 - size * 0.3);\n         spray.style.stroke = \"#3b82f6\";\n         spray.style.strokeWidth = \"2\";\n         spray.style.strokeDasharray = \"3,3\";\n         g.appendChild(spray);\n        }\n        return g;\n       }\n       if (shape === \"vacuum\") {\n        const g = document.createElementNS(ns, \"g\");\n        const vacBody = document.createElementNS(ns, \"circle\");\n        vacBody.setAttribute(\"r\", size);\n        g.appendChild(vacBody);\n        const top = document.createElementNS(ns, \"circle\");\n        top.setAttribute(\"r\", size * 0.7);\n        top.style.fill = \"#1e293b\";\n        g.appendChild(top);\n        const bumper = document.createElementNS(ns, \"path\");\n        bumper.setAttribute(\"d\", `M ${-size * 0.7} ${-size * 0.4} A ${size * 0.8} ${size * 0.8} 0 0 1 ${size * 0.7} ${-size * 0.4}`);\n        bumper.style.fill = \"none\";\n        bumper.style.strokeWidth = size * 0.15;\n        g.appendChild(bumper);\n        const btn = document.createElementNS(ns, \"circle\");\n        btn.setAttribute(\"cx\", 0);\n        btn.setAttribute(\"cy\", size * 0.1);\n        btn.setAttribute(\"r\", size * 0.2);\n        btn.style.fill = \"#4ade80\";\n        g.appendChild(btn);\n        return g;\n       }\n       const c = document.createElementNS(ns, \"circle\");\n       c.setAttribute(\"r\", size);\n       return c;\n      }\n      function createNodeShape(id, size) {\n       const styles = resolveStylesForNode(id);\n       if (styles.icon && styles.icon.library && styles.icon.name) {\n        const g = document.createElementNS(\"http://www.w3.org/2000/svg\", \"g\");\n        g.classList.add(\"node-circle\");\n        IconLibrary.getIcon(styles.icon.library, styles.icon.name).then(svgText => {\n         if (svgText) {\n          const parser = new DOMParser();\n          const doc = parser.parseFromString(svgText, 'image/svg+xml');\n          const svgEl = doc.querySelector('svg');\n          if (svgEl) {\n           svgEl.setAttribute('width', size * 1.2);\n           svgEl.setAttribute('height', size * 1.2);\n           svgEl.setAttribute('x', -size * 0.6);\n           svgEl.setAttribute('y', -size * 0.6);\n           if (styles.circleColor) {\n            svgEl.style.fill = styles.circleColor;\n            svgEl.querySelectorAll('path, circle, rect, polygon, ellipse').forEach(el => {\n             el.style.fill = styles.circleColor;\n            });\n           }\n           if (styles.circleBorder) {\n            svgEl.style.stroke = styles.circleBorder;\n            svgEl.querySelectorAll('path, circle, rect, polygon, ellipse').forEach(el => {\n             el.style.stroke = styles.circleBorder;\n            });\n           }\n           g.innerHTML = svgEl.outerHTML;\n          }\n         }\n        });\n        return g;\n       }\n       const shapeType = (NODE_DATA[id] && NODE_DATA[id].shape) || \"circle\";\n       const shapeEl = createShapeElement(shapeType, size);\n       shapeEl.classList.add(\"node-circle\");\n       shapeEl.style.fill = styles.circleColor || PAGE_STATE.nodeFill || \"#1e293b\";\n       shapeEl.style.stroke = styles.circleBorder || PAGE_STATE.nodeStroke || \"#475569\";\n       return shapeEl;\n      }\n      function forgeTheLegend() {\n       const container = document.getElementById(\"edge-legend\");\n       if (!container) return;\n       container.innerHTML = \"\";\n       const title = document.createElement(\"div\");\n       title.className = \"legend-title\";\n       title.textContent = \"Line Legend\";\n       container.appendChild(title);\n       const closeBtn = document.createElement(\"button\");\n       closeBtn.type = \"button\";\n       closeBtn.className = \"legend-close-btn\";\n       closeBtn.textContent = \"✕\";\n       closeBtn.addEventListener(\"click\", (e) => {\n        e.stopPropagation();\n        legendCollapsed = true;\n        updateLegendVisibility();\n       });\n       container.appendChild(closeBtn);\n       const colors = [...new Set(EDGE_DATA.list.map((e) => e.color).filter(Boolean))];\n       if (colors.length === 0) {\n        updateLegendVisibility();\n        return;\n       }\n       colors.forEach((color) => {\n         if (!EDGE_LEGEND[color]) {\n          EDGE_LEGEND[color] = \"you can edit me too\";\n         }\n         const item = document.createElement(\"div\");\n         item.className = \"legend-item\";\n         item.addEventListener(\"mousedown\", (e) => e.stopPropagation());\n         item.addEventListener(\"click\", (e) => e.stopPropagation());\n         const swatch = document.createElement(\"span\");\n         swatch.className = \"legend-swatch\";\n         swatch.style.backgroundColor = color;\n         swatch.style.cursor = \"pointer\";\n         swatch.addEventListener(\"click\", (e) => {\n          e.stopPropagation();\n          const edgeWithColor = EDGE_DATA.list.find(edge => edge.color === color);\n          if (edgeWithColor) {\n           selectTheConnection(edgeWithColor.id);\n          }\n         });\n         let swatchTouchStart = null;\n         let swatchTouchMoved = false;\n         swatch.addEventListener(\"touchstart\", (e) => {\n         swatchTouchStart = Date.now();\n         swatchTouchMoved = false;\n         if (e.touches[0]) {\n          swatchTouchStartX = e.touches[0].clientX;\n          swatchTouchStartY = e.touches[0].clientY;\n         }\n        }, {\n          passive: false\n         });\n         let swatchTouchStartX = 0, swatchTouchStartY = 0;\n        swatch.addEventListener(\"touchmove\", (e) => {\n         if (e.touches[0]) {\n          const dx = Math.abs(e.touches[0].clientX - swatchTouchStartX);\n          const dy = Math.abs(e.touches[0].clientY - swatchTouchStartY);\n          if (dx > 10 || dy > 10) swatchTouchMoved = true;\n         }\n        }, {\n          passive: false\n         });\n         swatch.addEventListener(\"touchend\", (e) => {\n          if (swatchTouchStart && !swatchTouchMoved && Date.now() - swatchTouchStart < 400) {\n           e.stopPropagation();\n           e.preventDefault();\n           const edgeWithColor = EDGE_DATA.list.find(edge => edge.color === color);\n           if (edgeWithColor) {\n            selectTheConnection(edgeWithColor.id);\n           }\n          }\n          swatchTouchStart = null;\n          swatchTouchMoved = false;\n         }, {\n          passive: false\n         });\n        const label = document.createElement(\"span\");\n        label.className = \"legend-label\";\n        label.textContent = EDGE_LEGEND[color];\n        if (isMobileDevice()) {\n         label.style.cursor = \"pointer\";\n         let labelTapStart = null;\n         let labelTapMoved = false;\n         label.addEventListener(\"touchstart\", (e) => {\n          labelTapStart = Date.now();\n          labelTapMoved = false;\n          if (e.touches[0]) {\n           labelTapStartX = e.touches[0].clientX;\n           labelTapStartY = e.touches[0].clientY;\n          }\n          e.stopPropagation();\n         });\n         let labelTapStartX = 0, labelTapStartY = 0;\n         label.addEventListener(\"touchmove\", (e) => {\n          if (e.touches[0]) {\n           const dx = Math.abs(e.touches[0].clientX - labelTapStartX);\n           const dy = Math.abs(e.touches[0].clientY - labelTapStartY);\n           if (dx > 10 || dy > 10) labelTapMoved = true;\n          }\n         });\n         label.addEventListener(\"touchend\", (e) => {\n          if (labelTapStart && !labelTapMoved && Date.now() - labelTapStart < 400) {\n           e.preventDefault();\n           e.stopPropagation();\n           const currentText = label.textContent;\n           const newText = prompt(\"Edit legend label:\", currentText);\n           if (newText !== null && newText.trim()) {\n            label.textContent = newText.trim();\n            EDGE_LEGEND[color] = newText.trim();\n           }\n          }\n          labelTapStart = null;\n          labelTapMoved = false;\n         });\n        } else {\n           label.contentEditable = true;\n           label.addEventListener(\"focus\", () => {\n            label.classList.add(\"editing\");\n           });\n           label.addEventListener(\"blur\", () => {\n            label.classList.remove(\"editing\");\n            const text = label.textContent.trim() || \"you can edit me too\";\n            EDGE_LEGEND[color] = text;\n           });\n           label.addEventListener(\"keydown\", (e) => {\n            if (e.key === \"Enter\") {\n             e.preventDefault();\n             label.blur();\n            }\n           });\n          }\n          item.append(swatch, label); container.appendChild(item);\n         }); updateLegendVisibility();\n       }\n       function deleteRectangle(rectId) {\n      pushUndo(\"delete zone\");\n        RECT_DATA.list = RECT_DATA.list.filter(r => r.id !== rectId);\n        forgeTheTopology();\n       }\n       function updateRectangleDeleteButtons() {\n        const deleteButtons = document.querySelectorAll('.rect-delete-btn');\n        deleteButtons.forEach(btn => {\n         btn.style.display = rectDrawMode ? 'block' : 'none';\n        });\n       }\n       function updateFovCone(nodeId) {\n        const node = NODE_DATA[nodeId];\n        if (!node) return;\n        \n        const nodeGroup = document.querySelector(`g[data-node-id=\"${nodeId}\"]`);\n        if (!nodeGroup) return;\n        const existingFov = nodeGroup.querySelector(\".fov-group\");\n        if (existingFov) existingFov.remove();\n        if (!hasCoverageZone(node.shape) || !node.fovEnabled) return;\n        \n        const ns = \"http://www.w3.org/2000/svg\";\n        const defaults = getCoverageDefaults(node.shape);\n        const fovAngle = node.fovAngle || defaults.angle;\n        const fovDistance = node.fovDistance || defaults.distance;\n        const fovInnerRadius = node.fovInnerRadius || 0;\n        const fovRotation = node.fovRotation || 0;\n        const fovColor = node.fovColor || \"#f59e0b\";\n        const fovOpacity = node.fovOpacity || 20;\n        const fovGradient = node.fovGradient || false;\n        const fovBorderColor = node.fovBorderColor || \"#f59e0b\";\n        const fovBorderWidth = node.fovBorderWidth ?? 2;\n        const fovBorderStyle = node.fovBorderStyle || \"solid\";\n        const fovBorderOpacity = node.fovBorderOpacity ?? 100;\n        const fovLabel = node.fovLabel || \"\";\n        const fovAnimate = node.fovAnimate || false;\n        const fovAnimationType = node.fovAnimationType || defaults.animationType;\n        const fovSweep = node.fovSweep || 120;\n        const fovSpeed = node.fovSpeed || 4;\n        \n        const fovGroup = document.createElementNS(ns, \"g\");\n        fovGroup.classList.add(\"fov-group\");\n        if (!ZONES_VISIBLE) fovGroup.style.display = \"none\";\n        \n        if (fovGradient) {\n          const gradientId = `fov-gradient-${nodeId}`;\n          const defs = document.createElementNS(ns, \"defs\");\n          const gradient = document.createElementNS(ns, \"radialGradient\");\n          gradient.id = gradientId;\n          gradient.setAttribute(\"cx\", \"0\");\n          gradient.setAttribute(\"cy\", \"0\");\n          gradient.setAttribute(\"r\", fovDistance);\n          gradient.setAttribute(\"gradientUnits\", \"userSpaceOnUse\");\n          const stop1 = document.createElementNS(ns, \"stop\");\n          stop1.setAttribute(\"offset\", fovInnerRadius / fovDistance);\n          stop1.setAttribute(\"stop-color\", fovColor);\n          stop1.setAttribute(\"stop-opacity\", fovOpacity / 100);\n          const stop2 = document.createElementNS(ns, \"stop\");\n          stop2.setAttribute(\"offset\", \"1\");\n          stop2.setAttribute(\"stop-color\", fovColor);\n          stop2.setAttribute(\"stop-opacity\", \"0\");\n          gradient.appendChild(stop1);\n          gradient.appendChild(stop2);\n          defs.appendChild(gradient);\n          fovGroup.appendChild(defs);\n        }\n        \n        const fovPath = document.createElementNS(ns, \"path\");\n        \n        if (fovAngle >= 360) {\n          if (fovInnerRadius > 0) {\n            fovPath.setAttribute(\"d\", `M ${fovDistance},0 A ${fovDistance},${fovDistance} 0 1,1 ${-fovDistance},0 A ${fovDistance},${fovDistance} 0 1,1 ${fovDistance},0 M ${fovInnerRadius},0 A ${fovInnerRadius},${fovInnerRadius} 0 1,0 ${-fovInnerRadius},0 A ${fovInnerRadius},${fovInnerRadius} 0 1,0 ${fovInnerRadius},0`);\n            fovPath.setAttribute(\"fill-rule\", \"evenodd\");\n          } else {\n            fovPath.setAttribute(\"d\", `M 0,0 m -${fovDistance},0 a ${fovDistance},${fovDistance} 0 1,0 ${fovDistance * 2},0 a ${fovDistance},${fovDistance} 0 1,0 -${fovDistance * 2},0`);\n          }\n        } else {\n          const angleRad = (fovAngle * Math.PI) / 180;\n          const rotationRad = ((fovRotation - 90) * Math.PI) / 180;\n          const startAngle = rotationRad - angleRad / 2;\n          const endAngle = rotationRad + angleRad / 2;\n          const x1 = Math.cos(startAngle) * fovDistance;\n          const y1 = Math.sin(startAngle) * fovDistance;\n          const x2 = Math.cos(endAngle) * fovDistance;\n          const y2 = Math.sin(endAngle) * fovDistance;\n          const largeArc = fovAngle > 180 ? 1 : 0;\n          if (fovInnerRadius > 0) {\n            const ix1 = Math.cos(startAngle) * fovInnerRadius;\n            const iy1 = Math.sin(startAngle) * fovInnerRadius;\n            const ix2 = Math.cos(endAngle) * fovInnerRadius;\n            const iy2 = Math.sin(endAngle) * fovInnerRadius;\n            fovPath.setAttribute(\"d\", `M ${ix1},${iy1} L ${x1},${y1} A ${fovDistance},${fovDistance} 0 ${largeArc},1 ${x2},${y2} L ${ix2},${iy2} A ${fovInnerRadius},${fovInnerRadius} 0 ${largeArc},0 ${ix1},${iy1} Z`);\n          } else {\n            fovPath.setAttribute(\"d\", `M 0,0 L ${x1},${y1} A ${fovDistance},${fovDistance} 0 ${largeArc},1 ${x2},${y2} Z`);\n          }\n        }\n        \n        if (fovGradient) {\n          fovPath.style.fill = `url(#fov-gradient-${nodeId})`;\n        } else {\n          const opacityHex = Math.round((fovOpacity / 100) * 255).toString(16).padStart(2, '0');\n          fovPath.style.fill = fovColor + opacityHex;\n        }\n        \n        const borderOpacityHex = Math.round((fovBorderOpacity / 100) * 255).toString(16).padStart(2, '0');\n        fovPath.style.stroke = fovBorderColor + borderOpacityHex;\n        fovPath.style.strokeWidth = fovBorderWidth;\n        if (fovBorderStyle === \"dashed\") {\n          fovPath.style.strokeDasharray = \"10,5\";\n        } else if (fovBorderStyle === \"dotted\") {\n          fovPath.style.strokeDasharray = \"3,3\";\n        }\n        fovPath.style.pointerEvents = \"none\";\n        fovPath.classList.add(\"fov-cone\");\n        \n        fovGroup.appendChild(fovPath);\n        \n        if (fovLabel) {\n          const fovLabelPosition = node.fovLabelPosition || \"center\";\n          const fovLabelSize = node.fovLabelSize || 14;\n          const fovLabelColor = node.fovLabelColor || \"#ffffff\";\n          const fovLabelBold = node.fovLabelBold || false;\n          const fovLabelBg = node.fovLabelBg || false;\n          const fovLabelBgColor = node.fovLabelBgColor || \"#000000\";\n          let labelDistance;\n          if (fovLabelPosition === \"center\") {\n            labelDistance = fovAngle >= 360 ? 0 : fovDistance * 0.4;\n          } else if (fovLabelPosition === \"edge\") {\n            labelDistance = fovDistance * 0.75;\n          } else {\n            labelDistance = fovDistance + fovLabelSize + 8;\n          }\n          const labelAngle = ((fovRotation - 90) * Math.PI) / 180;\n          const fovLabelOffsetX = node.fovLabelOffsetX || 0;\n          const fovLabelOffsetY = node.fovLabelOffsetY || 0;\n          const labelX = (fovAngle >= 360 && fovLabelPosition === \"center\" ? 0 : Math.cos(labelAngle) * labelDistance) + fovLabelOffsetX;\n          const labelY = (fovAngle >= 360 && fovLabelPosition === \"center\" ? 0 : Math.sin(labelAngle) * labelDistance) + fovLabelOffsetY;\n          if (fovLabelBg) {\n            const bgRect = document.createElementNS(ns, \"rect\");\n            const textWidth = fovLabel.length * fovLabelSize * 0.6;\n            const textHeight = fovLabelSize * 1.4;\n            bgRect.setAttribute(\"x\", labelX - textWidth / 2 - 6);\n            bgRect.setAttribute(\"y\", labelY - textHeight / 2);\n            bgRect.setAttribute(\"width\", textWidth + 12);\n            bgRect.setAttribute(\"height\", textHeight);\n            bgRect.setAttribute(\"rx\", \"4\");\n            bgRect.style.fill = fovLabelBgColor;\n            bgRect.style.opacity = \"0.8\";\n            bgRect.style.pointerEvents = \"none\";\n            fovGroup.appendChild(bgRect);\n          }\n          const labelEl = document.createElementNS(ns, \"text\");\n          labelEl.setAttribute(\"x\", labelX);\n          labelEl.setAttribute(\"y\", labelY);\n          labelEl.setAttribute(\"text-anchor\", \"middle\");\n          labelEl.setAttribute(\"dominant-baseline\", \"middle\");\n          labelEl.style.fill = fovLabelColor;\n          labelEl.style.fontSize = fovLabelSize + \"px\";\n          labelEl.style.fontWeight = fovLabelBold ? \"bold\" : \"normal\";\n          labelEl.style.fontFamily = \"system-ui, sans-serif\";\n          labelEl.style.pointerEvents = \"none\";\n          labelEl.textContent = fovLabel;\n          fovGroup.appendChild(labelEl);\n        }\n        \n        if (fovAnimate) {\n          const animationName = `fov-anim-${nodeId}`;\n          const styleEl = document.createElementNS(\"http://www.w3.org/1999/xhtml\", \"style\");\n          \n          if (fovAnimationType === \"sweep\" && fovAngle < 360) {\n            styleEl.textContent = `\n              @keyframes ${animationName} {\n                0%, 100% { transform: rotate(0deg); }\n                50% { transform: rotate(${fovSweep}deg); }\n              }\n            `;\n            fovGroup.style.animation = `${animationName} ${fovSpeed}s ease-in-out infinite`;\n            fovGroup.style.transformOrigin = \"0 0\";\n          } else if (fovAnimationType === \"pulse\") {\n            styleEl.textContent = `\n              @keyframes ${animationName} {\n                0%, 100% { transform: scale(1); opacity: 1; }\n                50% { transform: scale(1.1); opacity: 0.7; }\n              }\n            `;\n            fovGroup.style.animation = `${animationName} ${fovSpeed}s ease-in-out infinite`;\n            fovGroup.style.transformOrigin = \"0 0\";\n          } else if (fovAnimationType === \"rings\") {\n            for (let i = 1; i <= 3; i++) {\n              const ring = document.createElementNS(ns, \"circle\");\n              ring.setAttribute(\"cx\", \"0\");\n              ring.setAttribute(\"cy\", \"0\");\n              ring.setAttribute(\"r\", fovDistance * 0.3 * i);\n              ring.style.fill = \"none\";\n              ring.style.stroke = fovBorderColor;\n              ring.style.strokeWidth = \"2\";\n              ring.style.opacity = \"0\";\n              const ringAnimName = `${animationName}-ring-${i}`;\n              const ringStyle = document.createElementNS(\"http://www.w3.org/1999/xhtml\", \"style\");\n              ringStyle.textContent = `\n                @keyframes ${ringAnimName} {\n                  0% { r: ${fovInnerRadius || 10}; opacity: 0.8; }\n                  100% { r: ${fovDistance}; opacity: 0; }\n                }\n              `;\n              ring.style.animation = `${ringAnimName} ${fovSpeed}s ease-out infinite`;\n              ring.style.animationDelay = `${(i - 1) * (fovSpeed / 3)}s`;\n              fovGroup.appendChild(ringStyle);\n              fovGroup.appendChild(ring);\n            }\n          } else if (fovAnimationType === \"spin\") {\n            styleEl.textContent = `\n              @keyframes ${animationName} {\n                0% { transform: rotate(0deg); }\n                100% { transform: rotate(360deg); }\n              }\n            `;\n            fovGroup.style.animation = `${animationName} ${fovSpeed}s linear infinite`;\n            fovGroup.style.transformOrigin = \"0 0\";\n          }\n          \n          if (fovAnimationType !== \"rings\") {\n            fovGroup.appendChild(styleEl);\n            const elapsedSeconds = (Date.now() - FOV_ANIMATION_START) / 1000;\n            const animationOffset = elapsedSeconds % fovSpeed;\n            fovGroup.style.animationDelay = `-${animationOffset}s`;\n          }\n        }\n        \n        nodeGroup.insertBefore(fovGroup, nodeGroup.firstChild);\n      }\n       \n      function forgeTheTopology() {\n        if (!NODE_DATA || !EDGE_DATA) {\n         console.warn(\"forgeTheTopology called before data initialized\");\n         return;\n        }\n        const svg = document.getElementById(\"map\");\n        svg.innerHTML = \"\";\n        const ns = \"http://www.w3.org/2000/svg\";\n        const defs = document.createElementNS(ns, \"defs\");\n        const flowArrowBig = document.createElementNS(ns, \"path\");\n        flowArrowBig.id = \"flow-arrow-big\";\n        flowArrowBig.setAttribute(\"d\", \"M-6,-4 L6,0 L-6,4 L-3,0 Z\");\n        defs.appendChild(flowArrowBig);\n        const flowArrowSmall = document.createElementNS(ns, \"path\");\n        flowArrowSmall.id = \"flow-arrow-small\";\n        flowArrowSmall.setAttribute(\"d\", \"M-4,-3 L4,0 L-4,3 Z\");\n        defs.appendChild(flowArrowSmall);\n        const markerForward = document.createElementNS(ns, \"marker\");\n        markerForward.id = \"arrow-forward\";\n        markerForward.setAttribute(\"markerWidth\", \"10\");\n        markerForward.setAttribute(\"markerHeight\", \"10\");\n        markerForward.setAttribute(\"refX\", \"9\");\n        markerForward.setAttribute(\"refY\", \"3\");\n        markerForward.setAttribute(\"orient\", \"auto\");\n        markerForward.setAttribute(\"markerUnits\", \"strokeWidth\");\n        const pathForward = document.createElementNS(ns, \"path\");\n        pathForward.setAttribute(\"d\", \"M0,0 L0,6 L9,3 z\");\n        pathForward.setAttribute(\"fill\", \"context-stroke\");\n        markerForward.appendChild(pathForward);\n        defs.appendChild(markerForward);\n        const markerBackward = document.createElementNS(ns, \"marker\");\n        markerBackward.id = \"arrow-backward\";\n        markerBackward.setAttribute(\"markerWidth\", \"10\");\n        markerBackward.setAttribute(\"markerHeight\", \"10\");\n        markerBackward.setAttribute(\"refX\", \"0\");\n        markerBackward.setAttribute(\"refY\", \"3\");\n        markerBackward.setAttribute(\"orient\", \"auto\");\n        markerBackward.setAttribute(\"markerUnits\", \"strokeWidth\");\n        const pathBackward = document.createElementNS(ns, \"path\");\n        pathBackward.setAttribute(\"d\", \"M9,0 L9,6 L0,3 z\");\n        pathBackward.setAttribute(\"fill\", \"context-stroke\");\n        markerBackward.appendChild(pathBackward);\n        defs.appendChild(markerBackward);\n\n        const wallPattern = document.createElementNS(ns, \"pattern\");\n        wallPattern.id = \"wall-hatch\";\n        wallPattern.setAttribute(\"patternUnits\", \"userSpaceOnUse\");\n        wallPattern.setAttribute(\"width\", \"8\");\n        wallPattern.setAttribute(\"height\", \"8\");\n        wallPattern.setAttribute(\"patternTransform\", \"rotate(45)\");\n        const wallLine = document.createElementNS(ns, \"line\");\n        wallLine.setAttribute(\"x1\", \"0\");\n        wallLine.setAttribute(\"y1\", \"0\");\n        wallLine.setAttribute(\"x2\", \"0\");\n        wallLine.setAttribute(\"y2\", \"8\");\n        wallLine.setAttribute(\"stroke\", \"#666\");\n        wallLine.setAttribute(\"stroke-width\", \"2\");\n        wallPattern.appendChild(wallLine);\n        defs.appendChild(wallPattern);\n\n        svg.appendChild(defs);\n        const boundary = document.createElementNS(ns, \"rect\");\n        boundary.setAttribute(\"x\", CANVAS_PADDING);\n        boundary.setAttribute(\"y\", CANVAS_PADDING);\n        boundary.setAttribute(\"width\", CANVAS_WIDTH - CANVAS_PADDING * 2);\n        boundary.setAttribute(\"height\", CANVAS_HEIGHT - CANVAS_PADDING * 2);\n        boundary.setAttribute(\"fill\", \"none\");\n        boundary.setAttribute(\"stroke\", (PAGE_STATE.canvasBorder || \"#475569\") + \"4D\");\n        boundary.setAttribute(\"stroke-width\", \"20\");\n        boundary.setAttribute(\"stroke-dasharray\", \"10 5\");\n        boundary.setAttribute(\"rx\", \"8\");\n        svg.appendChild(boundary);\n\t\tif (currentView.mode !== \"rack\" && PAGE_STATE.canvasGridEnabled !== false) {\n\t\t const gridGroup = document.createElementNS(ns, \"g\");\n\t\t gridGroup.id = \"canvas-grid\";\n\t\t const gridSize = PAGE_STATE.canvasGridSize || 50;\n\t\t const gridColor = (PAGE_STATE.canvasGrid || \"#475569\") + \"33\";\n\t\t const majorGridColor = (PAGE_STATE.canvasGrid || \"#475569\") + \"66\";\n\t\t for (let x = CANVAS_PADDING; x <= CANVAS_WIDTH - CANVAS_PADDING; x += gridSize) {\n\t\t  const line = document.createElementNS(ns, \"line\");\n\t\t  line.setAttribute(\"x1\", x);\n\t\t  line.setAttribute(\"y1\", CANVAS_PADDING);\n\t\t  line.setAttribute(\"x2\", x);\n\t\t  line.setAttribute(\"y2\", CANVAS_HEIGHT - CANVAS_PADDING);\n\t\t  line.setAttribute(\"stroke\", (x - CANVAS_PADDING) % (gridSize * 5) === 0 ? majorGridColor : gridColor);\n\t\t  line.setAttribute(\"stroke-width\", (x - CANVAS_PADDING) % (gridSize * 5) === 0 ? \"2\" : \"1\");\n\t\t  gridGroup.appendChild(line);\n\t\t }\n\t\t for (let y = CANVAS_PADDING; y <= CANVAS_HEIGHT - CANVAS_PADDING; y += gridSize) {\n\t\t  const line = document.createElementNS(ns, \"line\");\n\t\t  line.setAttribute(\"x1\", CANVAS_PADDING);\n\t\t  line.setAttribute(\"y1\", y);\n\t\t  line.setAttribute(\"x2\", CANVAS_WIDTH - CANVAS_PADDING);\n\t\t  line.setAttribute(\"y2\", y);\n\t\t  line.setAttribute(\"stroke\", (y - CANVAS_PADDING) % (gridSize * 5) === 0 ? majorGridColor : gridColor);\n\t\t  line.setAttribute(\"stroke-width\", (y - CANVAS_PADDING) % (gridSize * 5) === 0 ? \"2\" : \"1\");\n\t\t  gridGroup.appendChild(line);\n\t\t }\n\t\t svg.appendChild(gridGroup);\n\t\t}\n       if (currentView.mode === \"rack\" && currentView.rackId) {\n         const rackCapacity = getRackCapacity(currentView.rackId);\n      const rackUHeight = getRackUHeight(currentView.rackId);\n         const rackGroup = document.createElementNS(ns, \"g\");\n         rackGroup.id = \"rack-visualization\";\n         const rackFrame = document.createElementNS(ns, \"rect\");\n         rackFrame.setAttribute(\"x\", RACK_START_X - RACK_WIDTH / 2);\n         rackFrame.setAttribute(\"y\", RACK_START_Y);\n         rackFrame.setAttribute(\"width\", RACK_WIDTH);\n         rackFrame.setAttribute(\"height\", rackCapacity * rackUHeight);\n         rackFrame.setAttribute(\"fill\", (PAGE_STATE.rackFrameFill || \"#0f172a\") + \"4D\");\n         rackFrame.setAttribute(\"stroke\", PAGE_STATE.rackFrameStroke || \"#4fd1c5\");\n         rackFrame.setAttribute(\"stroke-width\", \"3\");\n         rackFrame.setAttribute(\"rx\", \"4\");\n         rackGroup.appendChild(rackFrame);\n         if (PAGE_STATE.rackGridEnabled !== false) {\n          for (let u = 0; u <= rackCapacity; u++) {\n           const y = RACK_START_Y + u * rackUHeight;\n           const line = document.createElementNS(ns, \"line\");\n           line.setAttribute(\"x1\", RACK_START_X - RACK_WIDTH / 2);\n           line.setAttribute(\"y1\", y);\n           line.setAttribute(\"x2\", RACK_START_X + RACK_WIDTH / 2);\n           line.setAttribute(\"y2\", y);\n           line.setAttribute(\"stroke\", (PAGE_STATE.rackLineColor || \"#475569\") + \"66\");\n           line.setAttribute(\"stroke-width\", u % 5 === 0 ? \"2\" : \"1\");\n           line.setAttribute(\"stroke-dasharray\", u % 5 === 0 ? \"none\" : \"5,5\");\n           rackGroup.appendChild(line);\n           if (u < rackCapacity) {\n            const uNumber = rackCapacity - u;\n            const text = document.createElementNS(ns, \"text\");\n            text.setAttribute(\"x\", RACK_START_X - RACK_WIDTH / 2 - 30);\n            text.setAttribute(\"y\", y + rackUHeight / 2);\n            text.setAttribute(\"text-anchor\", \"middle\");\n            text.setAttribute(\"dominant-baseline\", \"middle\");\n            text.style.fill = PAGE_STATE.rackTextColor || \"#4fd1c5\";\n            text.style.fontSize = \"14px\";\n            text.style.fontWeight = \"bold\";\n            text.textContent = `U${uNumber}`;\n            rackGroup.appendChild(text);\n            const textRight = document.createElementNS(ns, \"text\");\n            textRight.setAttribute(\"x\", RACK_START_X + RACK_WIDTH / 2 + 30);\n            textRight.setAttribute(\"y\", y + rackUHeight / 2);\n            textRight.setAttribute(\"text-anchor\", \"middle\");\n            textRight.setAttribute(\"dominant-baseline\", \"middle\");\n            textRight.style.fill = PAGE_STATE.rackTextColor || \"#4fd1c5\";\n            textRight.style.fontSize = \"14px\";\n            textRight.style.fontWeight = \"bold\";\n            textRight.textContent = `U${uNumber}`;\n            rackGroup.appendChild(textRight);\n           }\n          }\n         }\n         svg.appendChild(rackGroup);\n        }\n        const centerX = CANVAS_WIDTH / 2;\n        if (RECT_DATA && RECT_DATA.list) {\n         RECT_DATA.list.forEach((rect) => {\n          if (currentView.mode === \"rack\") return;\n          if (rect.style === \"filled\") {\n           const g = document.createElementNS(ns, \"g\");\n           g.classList.add(\"rect-group\");\n           g.dataset.rectId = rect.id;\n           const rectCenterX = rect.x + rect.width / 2;\n           const rectCenterY = rect.y + rect.height / 2;\n           const rectRotation = rect.rotation || 0;\n           if (rectRotation !== 0) {\n             g.setAttribute(\"transform\", `rotate(${rectRotation}, ${rectCenterX}, ${rectCenterY})`);\n           }\n           const rectEl = document.createElementNS(ns, \"rect\");\n           rectEl.classList.add(\"rect-shape\");\n           rectEl.setAttribute(\"x\", rect.x);\n           rectEl.setAttribute(\"y\", rect.y);\n           rectEl.setAttribute(\"width\", rect.width);\n           rectEl.setAttribute(\"height\", rect.height);\n           rectEl.style.fill = rect.color;\n           rectEl.style.fillOpacity = \"0.3\";\n           rectEl.style.stroke = rect.borderColor || rect.color;\n           rectEl.style.strokeWidth = (rect.borderWidth !== undefined ? rect.borderWidth : 2) + \"px\";\n           if (rect.lineStyle === \"dashed\") { rectEl.style.strokeDasharray = \"10,5\"; }\n           else if (rect.lineStyle === \"dotted\") { rectEl.style.strokeDasharray = \"2,4\"; }\n           else if (rect.lineStyle === \"wall\") {\n             rectEl.style.fill = rect.color;\n             rectEl.style.fillOpacity = \"0.5\";\n             rectEl.style.stroke = rect.borderColor || rect.color;\n             rectEl.style.strokeWidth = (rect.borderWidth !== undefined ? rect.borderWidth : 2) + \"px\";\n             const hatchGroup = document.createElementNS(ns, \"g\");\n             hatchGroup.classList.add(\"wall-hatch-lines\");\n             hatchGroup.style.pointerEvents = \"none\";\n             const spacing = 12;\n             const hatchColor = rect.borderColor || rect.color;\n             for (let i = -rect.height; i < rect.width + rect.height; i += spacing) {\n               const line = document.createElementNS(ns, \"line\");\n               line.setAttribute(\"x1\", rect.x + i);\n               line.setAttribute(\"y1\", rect.y);\n               line.setAttribute(\"x2\", rect.x + i - rect.height);\n               line.setAttribute(\"y2\", rect.y + rect.height);\n               line.style.stroke = hatchColor;\n               line.style.strokeWidth = \"2\";\n               hatchGroup.appendChild(line);\n             }\n             const clipId = \"clip-\" + rect.id;\n             const clipPath = document.createElementNS(ns, \"clipPath\");\n             clipPath.id = clipId;\n             const clipRect = document.createElementNS(ns, \"rect\");\n             clipRect.setAttribute(\"x\", rect.x);\n             clipRect.setAttribute(\"y\", rect.y);\n             clipRect.setAttribute(\"width\", rect.width);\n             clipRect.setAttribute(\"height\", rect.height);\n             clipPath.appendChild(clipRect);\n             defs.appendChild(clipPath);\n             hatchGroup.setAttribute(\"clip-path\", \"url(#\" + clipId + \")\");\n             g.appendChild(hatchGroup);\n           }\n           else { rectEl.style.strokeDasharray = \"none\"; }\n           rectEl.style.cursor = \"move\";\n           rectEl.addEventListener(\"click\", (e) => {\n\t\t   if (isViewOnly()) return;\n             if (rectDrawMode) return;\n             e.stopPropagation();\n             currentRectId = rect.id;\n             selectTheRect(rect.id);\n           });\n           rectEl.addEventListener(\"contextmenu\", (e) => {\n\t\t    if (isViewOnly()) return;\n             e.preventDefault();\n             e.stopPropagation();\n             if (selectedRects.has(rect.id)) {\n               selectedRects.delete(rect.id);\n             } else {\n               selectedRects.add(rect.id);\n             }\n             updateAllSelections();\n           });\n           let rectLastTap = 0;\n           rectEl.addEventListener(\"touchend\", (e) => {\n             const now = Date.now();\n             if (now - rectLastTap < 300) {\n               e.preventDefault();\n               if (selectedRects.has(rect.id)) {\n                 selectedRects.delete(rect.id);\n               } else {\n                 selectedRects.add(rect.id);\n               }\n               updateAllSelections();\n               if (navigator.vibrate) navigator.vibrate(50);\n               rectLastTap = 0;\n             } else {\n               rectLastTap = now;\n             }\n           }, { passive: false });\n           const deleteBtn = document.createElementNS(ns, \"g\");\n           deleteBtn.classList.add(\"rect-delete-btn\");\n           deleteBtn.style.cursor = \"pointer\";\n           deleteBtn.style.display = rectDrawMode ? \"block\" : \"none\";\n           const deleteBg = document.createElementNS(ns, \"circle\");\n           deleteBg.setAttribute(\"cx\", rect.x + rect.width - 10);\n           deleteBg.setAttribute(\"cy\", rect.y + 10);\n           deleteBg.setAttribute(\"r\", 12);\n           deleteBg.style.fill = \"#f56565\";\n           deleteBg.style.stroke = \"white\";\n           deleteBg.style.strokeWidth = \"2\";\n           const deleteX = document.createElementNS(ns, \"text\");\n           deleteX.setAttribute(\"x\", rect.x + rect.width - 10);\n           deleteX.setAttribute(\"y\", rect.y + 10);\n           deleteX.setAttribute(\"text-anchor\", \"middle\");\n           deleteX.setAttribute(\"dominant-baseline\", \"middle\");\n           deleteX.style.fill = \"white\";\n           deleteX.style.fontSize = \"16px\";\n           deleteX.style.fontWeight = \"bold\";\n           deleteX.style.pointerEvents = \"none\";\n           deleteX.textContent = \"×\";\n           deleteBtn.appendChild(deleteBg);\n           deleteBtn.appendChild(deleteX);\n           deleteBtn.addEventListener(\"click\", (e) => {\n            e.stopPropagation();\n            e.preventDefault();\n            deleteRectangle(rect.id);\n           });\n           deleteBtn.addEventListener(\"touchend\", (e) => {\n      e.stopPropagation();\n           e.preventDefault();\n           deleteRectangle(rect.id);\n           });\n           let isDragging = false;\n           let dragStartX, dragStartY;\n           let rectStartX, rectStartY;\n           rectEl.addEventListener(\"mousedown\", (e) => {\n\t\t   if (isViewOnly()) return;\n      if (rectDrawMode) return;\n      e.preventDefault();\n      e.stopPropagation();\n      isDragging = true;\n      dragStartX = e.clientX;\n      dragStartY = e.clientY;\n      rectStartX = rect.x;\n      rectStartY = rect.y;\n      rectEl.style.cursor = \"grabbing\";\n      if (selectedRects.has(rect.id)) {\n      initialPositions = {};\n      selectedNodes.forEach(nodeId => {\n      const nodePos = savedPositions[nodeId];\n      if (nodePos) initialPositions[nodeId] = { x: nodePos.x, y: nodePos.y };\n      });\n      selectedRects.forEach(rectId => {\n      const r = RECT_DATA.list.find(x => x.id === rectId);\n      if (r) { r._dragStartX = r.x; r._dragStartY = r.y; }\n      });\n      selectedTexts.forEach(textId => {\n      const t = TEXT_DATA.list.find(x => x.id === textId);\n      if (t) { t._dragStartX = t.x; t._dragStartY = t.y; }\n      });\n      selectedEdges.forEach(edgeId => {\n      const ed = EDGE_DATA.list.find(x => x.id === edgeId);\n      if (ed && ed.points) { ed._dragStartPoints = ed.points.map(p => ({x: p.x, y: p.y})); }\n      });\n      }\n      });\n      const moveHandler = (e) => {\n      if (!isDragging || rectDrawMode) return;\n      const svgEl = svg;\n      const pt1 = svgEl.createSVGPoint();\n      pt1.x = dragStartX;\n      pt1.y = dragStartY;\n      const svgP1 = pt1.matrixTransform(svgEl.getScreenCTM().inverse());\n      const pt2 = svgEl.createSVGPoint();\n      pt2.x = e.clientX;\n      pt2.y = e.clientY;\n      const svgP2 = pt2.matrixTransform(svgEl.getScreenCTM().inverse());\n      const dx = svgP2.x - svgP1.x;\n      const dy = svgP2.y - svgP1.y;\n      rect.x = rectStartX + dx;\n      rect.y = rectStartY + dy;\n      if (selectedRects.has(rect.id)) {\n      selectedNodes.forEach(nodeId => { if (initialPositions[nodeId]) { savedPositions[nodeId] = { x: initialPositions[nodeId].x + dx, y: initialPositions[nodeId].y + dy }; } });\n      selectedRects.forEach(rectId => { if (rectId === rect.id) return; const r = RECT_DATA.list.find(x => x.id === rectId); if (r && r._dragStartX !== undefined) { r.x = r._dragStartX + dx; r.y = r._dragStartY + dy; } });\n      selectedTexts.forEach(textId => { const t = TEXT_DATA.list.find(x => x.id === textId); if (t && t._dragStartX !== undefined) { t.x = t._dragStartX + dx; t.y = t._dragStartY + dy; } });\n      selectedEdges.forEach(edgeId => { const ed = EDGE_DATA.list.find(x => x.id === edgeId); if (ed && ed._dragStartPoints) { ed.points.forEach((p, i) => { p.x = ed._dragStartPoints[i].x + dx; p.y = ed._dragStartPoints[i].y + dy; }); } });\n      }\n      forgeTheTopology();\n      };\n           const upHandler = () => {\n            if (isDragging) {\n             isDragging = false;\n             rectEl.style.cursor = \"move\";\n            }\n           };\n           document.addEventListener(\"mousemove\", moveHandler);\n           document.addEventListener(\"mouseup\", upHandler);\n           let touchStartX, touchStartY;\n           let touchRectStartX, touchRectStartY;\n           rectEl.addEventListener(\"touchstart\", (e) => {\n\t\t   if (isViewOnly()) return;\n            if (rectDrawMode) return;\n            e.preventDefault();\n            e.stopPropagation();\n            isDragging = true;\n            const touch = e.touches[0];\n            touchStartX = touch.clientX;\n            touchStartY = touch.clientY;\n            touchRectStartX = rect.x;\n            touchRectStartY = rect.y;\n            rectEl.style.cursor = \"grabbing\";\n           }, { passive: false });\n           rectEl.addEventListener(\"touchmove\", (e) => {\n\t\t   if (isViewOnly()) return;\n            if (!isDragging || rectDrawMode) return;\n            if (!e.touches[0]) return;\n            e.preventDefault();\n            e.stopPropagation();\n            const svgEl = svg;\n            const pt1 = svgEl.createSVGPoint();\n            pt1.x = touchStartX;\n            pt1.y = touchStartY;\n            const svgP1 = pt1.matrixTransform(svgEl.getScreenCTM().inverse());\n            const touch = e.touches[0];\n            const pt2 = svgEl.createSVGPoint();\n            pt2.x = touch.clientX;\n            pt2.y = touch.clientY;\n            const svgP2 = pt2.matrixTransform(svgEl.getScreenCTM().inverse());\n            const dx = svgP2.x - svgP1.x;\n            const dy = svgP2.y - svgP1.y;\n            rect.x = touchRectStartX + dx;\n            rect.y = touchRectStartY + dy;\n            forgeTheTopology();\n           }, { passive: false });\n           rectEl.addEventListener(\"touchend\", (e) => {\n            if (isDragging) {\n             e.preventDefault();\n             isDragging = false;\n             rectEl.style.cursor = \"move\";\n            }\n           }, { passive: false });\n            if (currentRectId === rect.id) {\n      const corners = [\n      { cx: rect.x, cy: rect.y, cursor: 'nwse-resize', dx: -1, dy: -1 },\n      { cx: rect.x + rect.width, cy: rect.y, cursor: 'nesw-resize', dx: 1, dy: -1 },\n      { cx: rect.x, cy: rect.y + rect.height, cursor: 'nesw-resize', dx: -1, dy: 1 },\n      { cx: rect.x + rect.width, cy: rect.y + rect.height, cursor: 'nwse-resize', dx: 1, dy: 1 }\n      ];\n      corners.forEach((corner, idx) => {\n      const handle = document.createElementNS(ns, \"circle\");\n      handle.setAttribute(\"cx\", corner.cx);\n      handle.setAttribute(\"cy\", corner.cy);\n      const borderW = rect.borderWidth !== undefined ? rect.borderWidth : 2;\n      const handleSize = Math.max(PAGE_STATE.selectionHandleSize || 8, borderW + 4);\n      handle.setAttribute(\"r\", handleSize);\n      handle.style.fill = PAGE_STATE.selectionHandle || \"#f59e0b\";\n      handle.style.stroke = \"#fff\";\n      handle.style.strokeWidth = \"2\";\n      handle.style.cursor = corner.cursor;\n      handle.addEventListener(\"mousedown\", (e) => {\n\t  if (isViewOnly()) return;\n      e.preventDefault();\n      e.stopPropagation();\n\t  pushUndo(\"resize zone\");\n      let dragging = true;\n      const startX = e.clientX, startY = e.clientY;\n      const origX = rect.x, origY = rect.y, origW = rect.width, origH = rect.height;\n      const moveHandler = (ev) => {\n        if (!dragging) return;\n        const pt1 = svg.createSVGPoint(); pt1.x = startX; pt1.y = startY;\n        const pt2 = svg.createSVGPoint(); pt2.x = ev.clientX; pt2.y = ev.clientY;\n        const svgP1 = pt1.matrixTransform(svg.getScreenCTM().inverse());\n        const svgP2 = pt2.matrixTransform(svg.getScreenCTM().inverse());\n        const dx = svgP2.x - svgP1.x, dy = svgP2.y - svgP1.y;\n        if (corner.dx < 0) { rect.x = origX + dx; rect.width = origW - dx; }\n        else { rect.width = origW + dx; }\n        if (corner.dy < 0) { rect.y = origY + dy; rect.height = origH - dy; }\n        else { rect.height = origH + dy; }\n        if (rect.width < 20) rect.width = 20;\n        if (rect.height < 20) rect.height = 20;\n        forgeTheTopology();\n      };\n      const upHandler = () => { dragging = false; document.removeEventListener(\"mousemove\", moveHandler); document.removeEventListener(\"mouseup\", upHandler); };\n      document.addEventListener(\"mousemove\", moveHandler);\n      document.addEventListener(\"mouseup\", upHandler);\n      });\n      g.appendChild(handle);\n      });\n      }\n            if (rect.groupId) {\n             const groupIndicator = document.createElementNS(ns, \"rect\");\n             groupIndicator.setAttribute(\"x\", rect.x - 4);\n             groupIndicator.setAttribute(\"y\", rect.y - 4);\n             groupIndicator.setAttribute(\"width\", rect.width + 8);\n             groupIndicator.setAttribute(\"height\", rect.height + 8);\n             groupIndicator.style.fill = \"none\";\n        groupIndicator.style.stroke = PAGE_STATE.groupIndicator || \"#4fd1c5\";\n             groupIndicator.style.strokeWidth = \"3\";\n             groupIndicator.style.strokeDasharray = \"5,5\";\n             groupIndicator.style.pointerEvents = \"none\";\n             g.insertBefore(groupIndicator, g.firstChild);\n           }\n           g.appendChild(rectEl);\n           g.appendChild(deleteBtn);\n           svg.appendChild(g);\n          }\n         });\n        }\n        const centerY = CANVAS_HEIGHT / 2;\n        let positions = {};\n        Object.keys(NODE_DATA).forEach((id) => {\n         if (currentView.mode === \"rack\") {\n          const node = NODE_DATA[id];\n          if (!node || node.assignedRack !== currentView.rackId) {\n           return;\n          }\n         }\n         positions[id] = savedPositions[id] || {\n          x: centerX,\n          y: centerY\n         };\n        });\n        if (Object.keys(savedPositions).length === 0) {\n         const nodeIds = Object.keys(NODE_DATA).filter(id => {\n          if (currentView.mode === \"rack\") {\n           const node = NODE_DATA[id];\n           return node && node.assignedRack === currentView.rackId;\n          }\n          return true;\n         });\n         const baseY = centerY - 300;\n         if (nodeIds.length > 0) {\n          positions[nodeIds[0]] = {\n           x: centerX,\n           y: baseY\n          };\n          const remaining = nodeIds.slice(1);\n          const radius = 350;\n          const startAngle = Math.PI * 0.3;\n          const endAngle = Math.PI * 0.7;\n          remaining.forEach((id, i) => {\n           const angle = startAngle + (endAngle - startAngle) * (i / Math.max(1, remaining.length - 1));\n           positions[id] = {\n            x: centerX + Math.cos(angle) * radius * (i % 2 === 0 ? 1 : 1.3),\n            y: baseY + 200 + Math.sin(angle) * radius * 0.8 + i * 80,\n           };\n          });\n         }\n        }\n        Object.keys(positions).forEach((id) => {\n         let pos = savedPositions[id] || positions[id];\n         const nodeSize = savedSizes[id] || 55;\n         pos.x = Math.max(nodeSize + CANVAS_PADDING, Math.min(CANVAS_WIDTH - nodeSize - CANVAS_PADDING, pos.x));\n         pos.y = Math.max(nodeSize + CANVAS_PADDING, Math.min(CANVAS_HEIGHT - nodeSize - CANVAS_PADDING, pos.y));\n         positions[id] = {\n          x: pos.x,\n          y: pos.y\n         };\n         savedPositions[id] = {\n          x: pos.x,\n          y: pos.y\n         };\n        });\n        const edgePairCount = {};\n        const edgePairIndex = {};\n        EDGE_DATA.list.forEach((edge) => {\n         if (edge.type === \"custom\") return;\n         const key = [edge.from, edge.to].sort().join(\"||\");\n         edgePairCount[key] = (edgePairCount[key] || 0) + 1;\n        });\n        EDGE_DATA.list.forEach((edge) => {\n         if (edge.type === \"custom\") return;\n         const key = [edge.from, edge.to].sort().join(\"||\");\n         if (!edgePairIndex[key]) edgePairIndex[key] = 0;\n         edge._pairIndex = edgePairIndex[key];\n         edge._pairTotal = edgePairCount[key];\n         edgePairIndex[key]++;\n       });\n       \n       const orthoGaps = (function() {\n         const segments = [];\n         const GAP_SIZE = 12;\n         EDGE_DATA.list.forEach((edge, edgeIndex) => {\n           if ((edge.routing || PAGE_STATE.defaultEdgeRouting || \"curved\") !== \"orthogonal\") return;\n           if (edge.type === \"custom\") return;\n           const p1 = positions[edge.from];\n           const p2 = positions[edge.to];\n           if (!p1 || !p2) return;\n           const dx = p2.x - p1.x;\n           const dy = p2.y - p1.y;\n           const pairIndex = edge._pairIndex || 0;\n           const pairTotal = edge._pairTotal || 1;\n           const offset = (pairIndex - (pairTotal - 1) / 2) * 40;\n           if (Math.abs(dx) > Math.abs(dy)) {\n             const midX = p1.x + dx / 2 + offset;\n             segments.push({ edgeId: edge.id, x1: p1.x, y1: p1.y, x2: midX, y2: p1.y, idx: edgeIndex });\n             segments.push({ edgeId: edge.id, x1: midX, y1: p1.y, x2: midX, y2: p2.y, idx: edgeIndex });\n             segments.push({ edgeId: edge.id, x1: midX, y1: p2.y, x2: p2.x, y2: p2.y, idx: edgeIndex });\n           } else {\n             const midY = p1.y + dy / 2 + offset;\n             segments.push({ edgeId: edge.id, x1: p1.x, y1: p1.y, x2: p1.x, y2: midY, idx: edgeIndex });\n             segments.push({ edgeId: edge.id, x1: p1.x, y1: midY, x2: p2.x, y2: midY, idx: edgeIndex });\n             segments.push({ edgeId: edge.id, x1: p2.x, y1: midY, x2: p2.x, y2: p2.y, idx: edgeIndex });\n           }\n         });\n         const gaps = {};\n         for (let i = 0; i < segments.length; i++) {\n           for (let j = i + 1; j < segments.length; j++) {\n             const s1 = segments[i];\n             const s2 = segments[j];\n             if (s1.edgeId === s2.edgeId) continue;\n             const s1Horiz = Math.abs(s1.y1 - s1.y2) < 1;\n             const s2Horiz = Math.abs(s2.y1 - s2.y2) < 1;\n             if (s1Horiz === s2Horiz) continue;\n             const horiz = s1Horiz ? s1 : s2;\n             const vert = s1Horiz ? s2 : s1;\n             const hY = horiz.y1;\n             const vX = vert.x1;\n             const hMinX = Math.min(horiz.x1, horiz.x2);\n             const hMaxX = Math.max(horiz.x1, horiz.x2);\n             const vMinY = Math.min(vert.y1, vert.y2);\n             const vMaxY = Math.max(vert.y1, vert.y2);\n             if (vX > hMinX && vX < hMaxX && hY > vMinY && hY < vMaxY) {\n               const gapEdge = s1.idx > s2.idx ? s1.edgeId : s2.edgeId;\n               if (!gaps[gapEdge]) gaps[gapEdge] = [];\n               gaps[gapEdge].push({ x: vX, y: hY });\n             }\n           }\n         }\n         return gaps;\n       })();\n\n       EDGE_DATA.list.forEach((edge) => {\n        if (edge.type === \"custom\" && Array.isArray(edge.points) && edge.points.length >= 2) {\n          const customEdgeFaded = currentView.mode !== \"rack\" && edge.from && edge.to && (!isNodeVisible(edge.from) || !isNodeVisible(edge.to));\n          const poly = document.createElementNS(ns, \"polyline\");\n          poly.classList.add(\"edge\");\n          if (customEdgeFaded) {\n           poly.style.opacity = \"0.25\";\n           poly.classList.add(\"layer-faded\");\n          }\n          poly.dataset.edgeId = edge.id;\n          poly.style.stroke = edge.color || PAGE_STATE.defaultEdge || \"#475569\";\n          poly.style.strokeWidth = edge.width || 4;\n          poly.setAttribute(\"fill\", \"none\");\n          const lineStyle = edge.lineStyle || \"solid\";\n          if (lineStyle === \"dashed\") {\n           poly.style.strokeDasharray = \"10,5\";\n          } else if (lineStyle === \"dotted\") {\n           poly.style.strokeDasharray = \"2,4\";\n          } else if (lineStyle === \"wall\") {\n           poly.style.stroke = \"url(#wall-hatch)\";\n           poly.style.strokeWidth = (edge.width || 4) * 3;\n           poly.style.strokeDasharray = \"none\";\n          } else {\n           poly.style.strokeDasharray = \"none\";\n          }\n          const direction = edge.direction || \"none\";\n          if (direction === \"forward\") {\n           poly.setAttribute(\"marker-end\", \"url(#arrow-forward)\");\n          } else if (direction === \"backward\") {\n           poly.setAttribute(\"marker-start\", \"url(#arrow-backward)\");\n          } else if (direction === \"both\") {\n           poly.setAttribute(\"marker-start\", \"url(#arrow-backward)\");\n           poly.setAttribute(\"marker-end\", \"url(#arrow-forward)\");\n          }\n          const ptsStr = edge.points.map((p) => `${p.x},${p.y}`).join(\" \");\n          poly.setAttribute(\"points\", ptsStr);\n          const animDir = PAGE_STATE.animationDirection || \"all\";\n          const shouldAnimatePoly = (PAGE_STATE.animateConnections && edge.animate !== false || edge.animate === true) && direction !== \"none\" && edge.points.length >= 2 && (animDir === \"all\" || animDir === direction);\n          if (shouldAnimatePoly) {\n           poly.style.opacity = \"0.25\";\n           const polyPathD = \"M \" + edge.points.map(p => `${p.x} ${p.y}`).join(\" L \");\n           const edgeStyle = edge.animationStyle || PAGE_STATE.animationStyle || \"arrows\";\n           const arrowId = edgeStyle === \"dots\" ? \"#flow-arrow-small\" : \"#flow-arrow-big\";\n           const arrowColor = edge.color || PAGE_STATE.defaultEdge || \"#475569\";\n           const arrowCount = 3;\n           const animDuration = edge.animationSpeed ? parseFloat(edge.animationSpeed) : (PAGE_STATE.animationSpeed || 1.5);\n           if (direction === \"forward\" || direction === \"both\") {\n            for (let i = 0; i < arrowCount; i++) {\n             const arrow = document.createElementNS(ns, \"use\");\n             arrow.setAttribute(\"href\", arrowId);\n             arrow.style.fill = arrowColor;\n             arrow.style.offsetPath = `path('${polyPathD}')`;\n             arrow.style.animationDuration = animDuration + \"s\";\n             arrow.style.animationDelay = (i * (animDuration / arrowCount)) + \"s\";\n             arrow.classList.add(\"edge-arrow-forward\");\n             svg.appendChild(arrow);\n            }\n           }\n           if (direction === \"backward\" || direction === \"both\") {\n            for (let i = 0; i < arrowCount; i++) {\n             const arrow = document.createElementNS(ns, \"use\");\n             arrow.setAttribute(\"href\", arrowId);\n             arrow.style.fill = arrowColor;\n             arrow.style.offsetPath = `path('${polyPathD}')`;\n             arrow.style.animationDuration = animDuration + \"s\";\n             arrow.style.animationDelay = ((i * (animDuration / arrowCount)) + (direction === \"both\" ? animDuration / (arrowCount * 2) : 0)) + \"s\";\n             arrow.classList.add(\"edge-arrow-backward\");\n             svg.appendChild(arrow);\n            }\n           }\n          }\n          const polyHit = document.createElementNS(ns, \"polyline\");\n          polyHit.setAttribute(\"points\", ptsStr);\n          polyHit.style.fill = \"none\";\n          polyHit.style.stroke = \"transparent\";\n          polyHit.style.strokeWidth = \"20\";\n          polyHit.style.cursor = \"pointer\";\n          polyHit.dataset.edgeId = edge.id;\n          polyHit.addEventListener(\"click\", (e) => {\n           e.stopPropagation();\n           selectTheConnection(edge.id);\n          });\n          let edgeTouchStart = null;\n          let edgeTouchMoved = false;\n          polyHit.addEventListener(\"touchstart\", (e) => {\n          edgeTouchStart = Date.now();\n          edgeTouchMoved = false;\n          if (e.touches[0]) {\n           edgeTouchStartX = e.touches[0].clientX;\n           edgeTouchStartY = e.touches[0].clientY;\n          }\n         }, {\n           passive: false\n          });\n          let edgeTouchStartX = 0, edgeTouchStartY = 0;\n         polyHit.addEventListener(\"touchmove\", (e) => {\n          if (e.touches[0]) {\n           const dx = Math.abs(e.touches[0].clientX - edgeTouchStartX);\n           const dy = Math.abs(e.touches[0].clientY - edgeTouchStartY);\n           if (dx > 10 || dy > 10) edgeTouchMoved = true;\n          }\n         }, {\n           passive: false\n          });\n          polyHit.addEventListener(\"touchend\", (e) => {\n           if (edgeTouchStart && !edgeTouchMoved && Date.now() - edgeTouchStart < 400) {\n            e.stopPropagation();\n            e.preventDefault();\n            selectTheConnection(edge.id);\n           }\n           edgeTouchStart = null;\n           edgeTouchMoved = false;\n          }, {\n           passive: false\n          });\n          poly.addEventListener(\"click\", (e) => {\n          e.stopPropagation();\n          selectTheConnection(edge.id);\n         });\n         polyHit.addEventListener(\"contextmenu\", (e) => {\n          e.preventDefault();\n          e.stopPropagation();\n          if (selectedEdges.has(edge.id)) {\n           selectedEdges.delete(edge.id);\n          } else {\n           selectedEdges.add(edge.id);\n          }\n          updateAllSelections();\n         });\n         let customEdgeLastTap = 0;\n         polyHit.addEventListener(\"touchend\", (e) => {\n          const now = Date.now();\n          if (now - customEdgeLastTap < 300) {\n           e.preventDefault();\n           if (selectedEdges.has(edge.id)) {\n            selectedEdges.delete(edge.id);\n           } else {\n            selectedEdges.add(edge.id);\n           }\n           updateAllSelections();\n           if (navigator.vibrate) navigator.vibrate(50);\n           customEdgeLastTap = 0;\n          } else {\n           customEdgeLastTap = now;\n          }\n         }, { passive: false });\n         poly.addEventListener(\"contextmenu\", (e) => {\n          e.preventDefault();\n          e.stopPropagation();\n          if (selectedEdges.has(edge.id)) {\n           selectedEdges.delete(edge.id);\n          } else {\n           selectedEdges.add(edge.id);\n          }\n          updateAllSelections();\n         });\n         let edgeLastTap = 0;\n         poly.addEventListener(\"touchend\", (e) => {\n          const now = Date.now();\n          if (now - edgeLastTap < 300) {\n           e.preventDefault();\n           if (selectedEdges.has(edge.id)) {\n            selectedEdges.delete(edge.id);\n           } else {\n            selectedEdges.add(edge.id);\n           }\n           updateAllSelections();\n           if (navigator.vibrate) navigator.vibrate(50);\n           edgeLastTap = 0;\n          } else {\n           edgeLastTap = now;\n          }\n         });\n          if (currentView.mode === \"rack\") {\n           return;\n          }\n          if (edge.groupId) {\n      const bounds = edge.points.reduce((acc, p) => ({ minX: Math.min(acc.minX, p.x), minY: Math.min(acc.minY, p.y), maxX: Math.max(acc.maxX, p.x), maxY: Math.max(acc.maxY, p.y) }), { minX: Infinity, minY: Infinity, maxX: -Infinity, maxY: -Infinity });\n      const groupIndicator = document.createElementNS(ns, \"rect\");\n      groupIndicator.setAttribute(\"x\", bounds.minX - 8);\n      groupIndicator.setAttribute(\"y\", bounds.minY - 8);\n      groupIndicator.setAttribute(\"width\", bounds.maxX - bounds.minX + 16);\n      groupIndicator.setAttribute(\"height\", bounds.maxY - bounds.minY + 16);\n      groupIndicator.setAttribute(\"rx\", \"4\");\n      groupIndicator.style.fill = \"none\";\n  groupIndicator.style.stroke = PAGE_STATE.groupIndicator || \"#4fd1c5\";\n      groupIndicator.style.strokeWidth = \"3\";\n      groupIndicator.style.strokeDasharray = \"5,5\";\n      groupIndicator.style.pointerEvents = \"none\";\n      svg.appendChild(groupIndicator);\n      }\n         let lineDragging = false;\n      let lineDragStartX, lineDragStartY;\n      let linePointsStart = [];\n      polyHit.addEventListener(\"mousedown\", (e) => {\n\t  if (isViewOnly()) return;\n      if (freeDrawMode) return;\n      e.preventDefault();\n      e.stopPropagation();\n      lineDragging = true;\n      lineDragStartX = e.clientX;\n      lineDragStartY = e.clientY;\n      linePointsStart = edge.points.map(p => ({x: p.x, y: p.y}));\n      if (selectedEdges.has(edge.id)) {\n      initialPositions = {};\n      selectedNodes.forEach(nodeId => {\n      const nodePos = savedPositions[nodeId];\n      if (nodePos) initialPositions[nodeId] = { x: nodePos.x, y: nodePos.y };\n      });\n      selectedRects.forEach(rectId => {\n      const r = RECT_DATA.list.find(x => x.id === rectId);\n      if (r) { r._dragStartX = r.x; r._dragStartY = r.y; }\n      });\n      selectedTexts.forEach(textId => {\n      const t = TEXT_DATA.list.find(x => x.id === textId);\n      if (t) { t._dragStartX = t.x; t._dragStartY = t.y; }\n      });\n      selectedEdges.forEach(edgeId => {\n      const ed = EDGE_DATA.list.find(x => x.id === edgeId);\n      if (ed && ed.points) { ed._dragStartPoints = ed.points.map(p => ({x: p.x, y: p.y})); }\n      });\n      }\n      });\n      const lineMoveHandler = (e) => {\n      if (!lineDragging || freeDrawMode) return;\n      const svgEl = svg;\n      const pt1 = svgEl.createSVGPoint();\n      pt1.x = lineDragStartX;\n      pt1.y = lineDragStartY;\n      const svgP1 = pt1.matrixTransform(svgEl.getScreenCTM().inverse());\n      const pt2 = svgEl.createSVGPoint();\n      pt2.x = e.clientX;\n      pt2.y = e.clientY;\n      const svgP2 = pt2.matrixTransform(svgEl.getScreenCTM().inverse());\n      const dx = svgP2.x - svgP1.x;\n      const dy = svgP2.y - svgP1.y;\n      edge.points.forEach((p, i) => { p.x = linePointsStart[i].x + dx; p.y = linePointsStart[i].y + dy; });\n      if (selectedEdges.has(edge.id)) {\n      selectedNodes.forEach(nodeId => {\n      if (initialPositions[nodeId]) {\n       savedPositions[nodeId] = { x: initialPositions[nodeId].x + dx, y: initialPositions[nodeId].y + dy };\n      }\n      });\n      selectedRects.forEach(rectId => {\n      const r = RECT_DATA.list.find(x => x.id === rectId);\n      if (r && r._dragStartX !== undefined) { r.x = r._dragStartX + dx; r.y = r._dragStartY + dy; }\n      });\n      selectedTexts.forEach(textId => {\n      const t = TEXT_DATA.list.find(x => x.id === textId);\n      if (t && t._dragStartX !== undefined) { t.x = t._dragStartX + dx; t.y = t._dragStartY + dy; }\n      });\n      selectedEdges.forEach(edgeId => {\n      if (edgeId === edge.id) return;\n      const ed = EDGE_DATA.list.find(x => x.id === edgeId);\n      if (ed && ed._dragStartPoints) { ed.points.forEach((p, i) => { p.x = ed._dragStartPoints[i].x + dx; p.y = ed._dragStartPoints[i].y + dy; }); }\n      });\n      }\n      forgeTheTopology();\n      };\n      const lineUpHandler = () => {\n        lineDragging = false;\n        document.removeEventListener(\"mousemove\", lineMoveHandler);\n        document.removeEventListener(\"mouseup\", lineUpHandler);\n      };\n      document.addEventListener(\"mousemove\", lineMoveHandler);\n      document.addEventListener(\"mouseup\", lineUpHandler);\n      svg.appendChild(poly);\n      svg.appendChild(polyHit);\n         if (currentEdgeId === edge.id) {\n          edge.points.forEach((p, idx) => {\n           const c = document.createElementNS(ns, \"circle\");\n           c.classList.add(\"edge-edit-point\");\n           c.setAttribute(\"cx\", p.x);\n           c.setAttribute(\"cy\", p.y);\n\t\t   c.setAttribute(\"r\", PAGE_STATE.selectionHandleSize || 8);\n           c.style.fill = PAGE_STATE.selectionHandle || \"#f59e0b\";\n           c.style.stroke = \"#fff\";\n           c.style.strokeWidth = \"2\";\n           c.style.cursor = \"move\";\n           c.dataset.edgeId = edge.id;\n           c.dataset.pointIndex = String(idx);\n           c.addEventListener(\"mousedown\", (e) => {\n\t\t   if (isViewOnly()) return;\n            e.preventDefault();\n            e.stopPropagation();\n            pushUndo(\"edit edge point\");\n            let dragging = true;\n            const svgEl = svg;\n            const moveHandler = (ev) => {\n             if (!dragging) return;\n             const pt = svgEl.createSVGPoint();\n             pt.x = ev.clientX;\n             pt.y = ev.clientY;\n             const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n             edge.points[idx].x = svgP.x;\n             edge.points[idx].y = svgP.y;\n             forgeTheTopology();\n            };\n            const upHandler = () => {\n             dragging = false;\n             document.removeEventListener(\"mousemove\", moveHandler);\n             document.removeEventListener(\"mouseup\", upHandler);\n            };\n            document.addEventListener(\"mousemove\", moveHandler);\n            document.addEventListener(\"mouseup\", upHandler);\n           });\n           c.addEventListener(\"touchstart\", (e) => {\n\t\t   if (isViewOnly()) return;\n            e.preventDefault();\n            e.stopPropagation();\n            pushUndo(\"edit edge point\");\n            let dragging = true;\n            const svgEl = svg;\n            const touchMoveHandler = (ev) => {\n             if (!dragging || !ev.touches[0]) return;\n             const pt = svgEl.createSVGPoint();\n             pt.x = ev.touches[0].clientX;\n             pt.y = ev.touches[0].clientY;\n             const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n             edge.points[idx].x = svgP.x;\n             edge.points[idx].y = svgP.y;\n             forgeTheTopology();\n            };\n            const touchUpHandler = () => {\n             dragging = false;\n             document.removeEventListener(\"touchmove\", touchMoveHandler);\n             document.removeEventListener(\"touchend\", touchUpHandler);\n            };\n            document.addEventListener(\"touchmove\", touchMoveHandler);\n            document.addEventListener(\"touchend\", touchUpHandler);\n           }, { passive: false });\n           svg.appendChild(c);\n          });\n         }\n         return;\n         }\n         const p1 = positions[edge.from];\n         const p2 = positions[edge.to];\n         if (!p1 || !p2) return;\n         const edgeFaded = currentView.mode !== \"rack\" && (!isNodeVisible(edge.from) || !isNodeVisible(edge.to));\n         const pairTotal = edge._pairTotal || 1;\n         const pairIndex = edge._pairIndex || 0;\n         const routing = edge.routing || \"curved\";\n         let pathD;\n         if (routing === \"straight\") {\n          pathD = `M ${p1.x} ${p1.y} L ${p2.x} ${p2.y}`;\n         } else if (routing === \"orthogonal\") {\n         const dx = p2.x - p1.x;\n         const dy = p2.y - p1.y;\n         const offset = (pairIndex - (pairTotal - 1) / 2) * 40;\n         const GAP = 10;\n         const edgeGaps = orthoGaps[edge.id] || [];\n         \n         if (Math.abs(dx) > Math.abs(dy)) {\n          const midX = p1.x + dx / 2 + offset;\n          const segs = [\n           { x1: p1.x, y1: p1.y, x2: midX, y2: p1.y },\n           { x1: midX, y1: p1.y, x2: midX, y2: p2.y },\n           { x1: midX, y1: p2.y, x2: p2.x, y2: p2.y }\n          ];\n          pathD = `M ${p1.x} ${p1.y}`;\n          segs.forEach(seg => {\n           const isHoriz = Math.abs(seg.y1 - seg.y2) < 1;\n           const segGaps = edgeGaps.filter(g => {\n            if (isHoriz) {\n             const minX = Math.min(seg.x1, seg.x2), maxX = Math.max(seg.x1, seg.x2);\n             return Math.abs(g.y - seg.y1) < 1 && g.x > minX + GAP && g.x < maxX - GAP;\n            } else {\n             const minY = Math.min(seg.y1, seg.y2), maxY = Math.max(seg.y1, seg.y2);\n             return Math.abs(g.x - seg.x1) < 1 && g.y > minY + GAP && g.y < maxY - GAP;\n            }\n           }).sort((a, b) => isHoriz ? (seg.x2 > seg.x1 ? a.x - b.x : b.x - a.x) : (seg.y2 > seg.y1 ? a.y - b.y : b.y - a.y));\n           let cx = seg.x1, cy = seg.y1;\n           segGaps.forEach(g => {\n            if (isHoriz) {\n             const gx = g.x;\n             pathD += ` L ${gx - GAP} ${cy} M ${gx + GAP} ${cy}`;\n             cx = gx + GAP;\n            } else {\n             const gy = g.y;\n             pathD += ` L ${cx} ${gy - GAP} M ${cx} ${gy + GAP}`;\n             cy = gy + GAP;\n            }\n           });\n           pathD += ` L ${seg.x2} ${seg.y2}`;\n          });\n         } else {\n          const midY = p1.y + dy / 2 + offset;\n          const segs = [\n           { x1: p1.x, y1: p1.y, x2: p1.x, y2: midY },\n           { x1: p1.x, y1: midY, x2: p2.x, y2: midY },\n           { x1: p2.x, y1: midY, x2: p2.x, y2: p2.y }\n          ];\n          pathD = `M ${p1.x} ${p1.y}`;\n          segs.forEach(seg => {\n           const isHoriz = Math.abs(seg.y1 - seg.y2) < 1;\n           const segGaps = edgeGaps.filter(g => {\n            if (isHoriz) {\n             const minX = Math.min(seg.x1, seg.x2), maxX = Math.max(seg.x1, seg.x2);\n             return Math.abs(g.y - seg.y1) < 1 && g.x > minX + GAP && g.x < maxX - GAP;\n            } else {\n             const minY = Math.min(seg.y1, seg.y2), maxY = Math.max(seg.y1, seg.y2);\n             return Math.abs(g.x - seg.x1) < 1 && g.y > minY + GAP && g.y < maxY - GAP;\n            }\n           }).sort((a, b) => isHoriz ? (seg.x2 > seg.x1 ? a.x - b.x : b.x - a.x) : (seg.y2 > seg.y1 ? a.y - b.y : b.y - a.y));\n           let cx = seg.x1, cy = seg.y1;\n           segGaps.forEach(g => {\n            if (isHoriz) {\n             const gx = g.x;\n             pathD += ` L ${gx - GAP} ${cy} M ${gx + GAP} ${cy}`;\n             cx = gx + GAP;\n            } else {\n             const gy = g.y;\n             pathD += ` L ${cx} ${gy - GAP} M ${cx} ${gy + GAP}`;\n             cy = gy + GAP;\n            }\n           });\n           pathD += ` L ${seg.x2} ${seg.y2}`;\n          });\n         }\n        } else {\n          const midX = (p1.x + p2.x) / 2;\n          const midY = (p1.y + p2.y) / 2;\n          const dx = p2.x - p1.x;\n          const dy = p2.y - p1.y;\n          const len = Math.sqrt(dx * dx + dy * dy) || 1;\n          const perpX = -dy / len;\n          const perpY = dx / len;\n          let offsetAmount = 0;\n          if (pairTotal > 1) {\n           offsetAmount = (pairIndex - (pairTotal - 1) / 2) * 40;\n          }\n          const ctrlX = midX + perpX * offsetAmount;\n          const ctrlY = midY + perpY * offsetAmount;\n          pathD = `M ${p1.x} ${p1.y} Q ${ctrlX} ${ctrlY} ${p2.x} ${p2.y}`;\n         }\n         const path = document.createElementNS(ns, \"path\");\n         path.setAttribute(\"d\", pathD);\n         path.setAttribute(\"fill\", \"none\");\n         path.classList.add(\"edge\");\n         if (edgeFaded) {\n          path.style.opacity = \"0.25\";\n          path.classList.add(\"layer-faded\");\n         }\n         if (edge.type === \"backup\") path.classList.add(\"backup\");\n         path.dataset.edgeId = edge.id;\n         path.dataset.from = edge.from;\n         path.dataset.to = edge.to;\n         path.style.stroke = edge.color;\n         path.style.strokeWidth = edge.width;\n         const edgeDirection = edge.direction || \"none\";\n         const edgeLineStyle = edge.lineStyle || \"solid\";\n         if (edgeLineStyle === \"dashed\") { path.style.strokeDasharray = \"10,5\"; }\n         else if (edgeLineStyle === \"dotted\") { path.style.strokeDasharray = \"2,4\"; }\n         if (edgeDirection === \"forward\") {\n          path.setAttribute(\"marker-end\", \"url(#arrow-forward)\");\n         } else if (edgeDirection === \"backward\") {\n          path.setAttribute(\"marker-start\", \"url(#arrow-backward)\");\n         } else if (edgeDirection === \"both\") {\n          path.setAttribute(\"marker-start\", \"url(#arrow-backward)\");\n          path.setAttribute(\"marker-end\", \"url(#arrow-forward)\");\n         }\n         const animDir = PAGE_STATE.animationDirection || \"all\";\n         const shouldAnimate = (PAGE_STATE.animateConnections && edge.animate !== false || edge.animate === true) && edgeDirection !== \"none\" && (animDir === \"all\" || animDir === edgeDirection);\n         if (shouldAnimate && !edgeFaded) {\n          path.style.opacity = \"0.25\";\n          const edgeStyle = edge.animationStyle || PAGE_STATE.animationStyle || \"arrows\";\n          const arrowId = edgeStyle === \"dots\" ? \"#flow-arrow-small\" : \"#flow-arrow-big\";\n          const arrowColor = edge.color || PAGE_STATE.defaultEdge || \"#475569\";\n          const arrowCount = 3;\n          const animDuration = edge.animationSpeed ? parseFloat(edge.animationSpeed) : (PAGE_STATE.animationSpeed || 1.5);\n          if (edgeDirection === \"forward\" || edgeDirection === \"both\") {\n           for (let i = 0; i < arrowCount; i++) {\n            const arrow = document.createElementNS(ns, \"use\");\n            arrow.setAttribute(\"href\", arrowId);\n            arrow.style.fill = arrowColor;\n            arrow.style.offsetPath = `path('${pathD}')`;\n            arrow.style.animationDuration = animDuration + \"s\";\n            arrow.style.animationDelay = (i * (animDuration / arrowCount)) + \"s\";\n            arrow.classList.add(\"edge-arrow-forward\");\n            svg.appendChild(arrow);\n           }\n          }\n          if (edgeDirection === \"backward\" || edgeDirection === \"both\") {\n           for (let i = 0; i < arrowCount; i++) {\n            const arrow = document.createElementNS(ns, \"use\");\n            arrow.setAttribute(\"href\", arrowId);\n            arrow.style.fill = arrowColor;\n            arrow.style.offsetPath = `path('${pathD}')`;\n            arrow.style.animationDuration = animDuration + \"s\";\n            arrow.style.animationDelay = ((i * (animDuration / arrowCount)) + (edgeDirection === \"both\" ? animDuration / (arrowCount * 2) : 0)) + \"s\";\n            arrow.classList.add(\"edge-arrow-backward\");\n            svg.appendChild(arrow);\n           }\n          }\n         }\n         const pathHit = document.createElementNS(ns, \"path\");\n         pathHit.setAttribute(\"d\", pathD);\n         pathHit.setAttribute(\"fill\", \"none\");\n         pathHit.style.stroke = \"transparent\";\n         pathHit.style.strokeWidth = \"20\";\n         pathHit.style.cursor = \"pointer\";\n         pathHit.dataset.edgeId = edge.id;\n         pathHit.addEventListener(\"click\", (e) => {\n          e.stopPropagation();\n          selectTheConnection(edge.id);\n         });\n         let pathTouchStart = null;\n         let pathTouchMoved = false;\n         pathHit.addEventListener(\"touchstart\", (e) => {\n         pathTouchStart = Date.now();\n         pathTouchMoved = false;\n         if (e.touches[0]) {\n          pathTouchStartX = e.touches[0].clientX;\n          pathTouchStartY = e.touches[0].clientY;\n         }\n        }, {\n          passive: false\n         });\n         let pathTouchStartX = 0, pathTouchStartY = 0;\n        pathHit.addEventListener(\"touchmove\", (e) => {\n         if (e.touches[0]) {\n          const dx = Math.abs(e.touches[0].clientX - pathTouchStartX);\n          const dy = Math.abs(e.touches[0].clientY - pathTouchStartY);\n          if (dx > 10 || dy > 10) pathTouchMoved = true;\n         }\n        }, {\n          passive: false\n         });\n         pathHit.addEventListener(\"touchend\", (e) => {\n          if (pathTouchStart && !pathTouchMoved && Date.now() - pathTouchStart < 400) {\n           e.stopPropagation();\n           e.preventDefault();\n           selectTheConnection(edge.id);\n          }\n          pathTouchStart = null;\n          pathTouchMoved = false;\n         }, {\n          passive: false\n         });\n         path.addEventListener(\"click\", (e) => {\n          e.stopPropagation();\n          selectTheConnection(edge.id);\n         });\n         if (currentView.mode === \"rack\") {\n          const fromNode = NODE_DATA[edge.from];\n          const toNode = NODE_DATA[edge.to];\n          if (!fromNode || !toNode ||\n              fromNode.assignedRack !== currentView.rackId ||\n              toNode.assignedRack !== currentView.rackId) {\n           return;\n          }\n         }\n         svg.appendChild(path);\n         svg.appendChild(pathHit);\n         if (edge.fromPort || edge.toPort) {\n          const ns = \"http://www.w3.org/2000/svg\";\n          if (edge.fromPort) {\n           const fromLabel = document.createElementNS(ns, \"text\");\n           fromLabel.textContent = edge.fromPort;\n           fromLabel.setAttribute(\"x\", p1.x);\n           fromLabel.setAttribute(\"y\", p1.y - 10);\n           fromLabel.setAttribute(\"text-anchor\", \"middle\");\n           fromLabel.style.fill = \"#94a3b8\";\n           fromLabel.style.fontSize = \"12px\";\n           fromLabel.style.fontWeight = \"600\";\n           fromLabel.style.pointerEvents = \"none\";\n           fromLabel.classList.add(\"port-label\");\n           svg.appendChild(fromLabel);\n          }\n          if (edge.toPort) {\n           const toLabel = document.createElementNS(ns, \"text\");\n           toLabel.textContent = edge.toPort;\n           toLabel.setAttribute(\"x\", p2.x);\n           toLabel.setAttribute(\"y\", p2.y - 10);\n           toLabel.setAttribute(\"text-anchor\", \"middle\");\n           toLabel.style.fill = \"#94a3b8\";\n           toLabel.style.fontSize = \"12px\";\n           toLabel.style.fontWeight = \"600\";\n           toLabel.style.pointerEvents = \"none\";\n           toLabel.classList.add(\"port-label\");\n           svg.appendChild(toLabel);\n          }\n         }\n        });\n        Object.entries(positions).forEach(([id, pos]) => {\n         const node = NODE_DATA[id];\n         if (!node) return;\n         if (currentView.mode === \"rack\") {\n          if (node.assignedRack !== currentView.rackId) return;\n         const rackUnit = parseInt(node.rackUnit) || 1;\n      const uHeight = parseInt(node.uHeight) || 1;\n      const rackCapacity = getRackCapacity(currentView.rackId);\n      const rackUHeight = getRackUHeight(currentView.rackId);\n      pos.x = RACK_START_X;\n      pos.y = RACK_START_Y + (rackCapacity - rackUnit - uHeight + 1) * rackUHeight + (uHeight * rackUHeight) / 2;\n         } else {\n          if (node.assignedRack) return;\n         }\n         const g = document.createElementNS(\"http://www.w3.org/2000/svg\", \"g\");\n         g.classList.add(\"node-group\");\n         g.dataset.nodeId = id;\n         const nodeRotation = NODE_DATA[id].rotation || 0;\n         g.setAttribute(\"transform\", `translate(${pos.x},${pos.y}) rotate(${nodeRotation})`);\n         if (currentView.mode !== \"rack\" && !isNodeVisible(id)) {\n          g.style.opacity = \"0.25\";\n          g.classList.add(\"layer-faded\");\n         }\n         let r = savedSizes[id] || 55;\n\t\tif (!savedSizes[id]) {\n\t\t if (window.innerWidth <= 480) r = 45;\n\t\t else if (window.innerWidth <= 768) r = 50;\n\t\t}\n         const styles = resolveStylesForNode(id);\n         const ns = \"http://www.w3.org/2000/svg\";\n         const hitArea = document.createElementNS(ns, \"circle\");\n         hitArea.setAttribute(\"r\", r * 1.5);\n         hitArea.style.fill = \"transparent\";\n         hitArea.style.stroke = \"none\";\n         hitArea.style.cursor = \"grab\";\n         hitArea.classList.add(\"node-hit-area\");\n         const shapeEl = createNodeShape(id, r);\n         const titleOffsetX = styles.titleOffsetX || 0;\n         const titleOffsetY = styles.titleOffsetY || 0;\n         const subOffsetX = styles.subOffsetX || 0;\n         const subOffsetY = styles.subOffsetY || 0;\n         const label = document.createElementNS(\"http://www.w3.org/2000/svg\", \"text\");\n         label.classList.add(\"node-label\");\n         label.setAttribute(\"x\", titleOffsetX);\n         label.setAttribute(\"y\", -r * 0.28 + titleOffsetY);\n         const labelFontSize = styles.titleSize || PAGE_STATE.nodeTitleSize || r * 0.33;\n        label.style.fontSize = labelFontSize + \"px\";\n         label.textContent = NODE_DATA[id].name;\n\t\tlabel.style.fill = styles.titleColor || PAGE_STATE.nodeTitle || \"#e2e8f0\";\n        label.style.fontFamily = styles.titleFont || PAGE_STATE.nodeFont || \"Inter, system-ui, sans-serif\";\n         label.style.pointerEvents = \"none\";\n         const sub = document.createElementNS(\"http://www.w3.org/2000/svg\", \"text\");\n         sub.classList.add(\"node-sub\");\n         sub.setAttribute(\"x\", subOffsetX);\n         sub.setAttribute(\"y\", r * 0.4 + subOffsetY);\n         const subFontSize = styles.subSize || PAGE_STATE.nodeSubSize || r * 0.24;\n        sub.style.fontSize = subFontSize + \"px\";\n         sub.textContent = NODE_DATA[id].ip;\n\t\tsub.style.fill = styles.subColor || PAGE_STATE.nodeSub || \"#94a3b8\";\n        sub.style.fontFamily = styles.subFont || PAGE_STATE.nodeFont || \"Inter, system-ui, sans-serif\";\n         sub.style.pointerEvents = \"none\";\n         if (hasCoverageZone(node.shape) && node.fovEnabled) {\n           const defaults = getCoverageDefaults(node.shape);\n           const fovAngle = node.fovAngle || defaults.angle;\n           const fovDistance = node.fovDistance || defaults.distance;\n           const fovInnerRadius = node.fovInnerRadius || 0;\n           const fovRotation = node.fovRotation || 0;\n           const fovColor = node.fovColor || \"#f59e0b\";\n           const fovOpacity = node.fovOpacity || 20;\n           const fovGradient = node.fovGradient || false;\n           const fovBorderColor = node.fovBorderColor || \"#f59e0b\";\n           const fovBorderWidth = node.fovBorderWidth ?? 2;\n           const fovBorderStyle = node.fovBorderStyle || \"solid\";\n           const fovBorderOpacity = node.fovBorderOpacity ?? 100;\n           const fovLabel = node.fovLabel || \"\";\n           const fovAnimate = node.fovAnimate || false;\n           const fovAnimationType = node.fovAnimationType || defaults.animationType;\n           const fovSweep = node.fovSweep || 120;\n           const fovSpeed = node.fovSpeed || 4;\n           \n           const fovGroup = document.createElementNS(ns, \"g\");\n           fovGroup.classList.add(\"fov-group\");\n           if (!ZONES_VISIBLE) fovGroup.style.display = \"none\";\n           \n           if (fovGradient) {\n             const gradientId = `fov-gradient-${id}`;\n             const defs = document.createElementNS(ns, \"defs\");\n             const gradient = document.createElementNS(ns, \"radialGradient\");\n             gradient.id = gradientId;\n             gradient.setAttribute(\"cx\", \"0\");\n             gradient.setAttribute(\"cy\", \"0\");\n             gradient.setAttribute(\"r\", fovDistance);\n             gradient.setAttribute(\"gradientUnits\", \"userSpaceOnUse\");\n             const stop1 = document.createElementNS(ns, \"stop\");\n             stop1.setAttribute(\"offset\", fovInnerRadius / fovDistance);\n             stop1.setAttribute(\"stop-color\", fovColor);\n             stop1.setAttribute(\"stop-opacity\", fovOpacity / 100);\n             const stop2 = document.createElementNS(ns, \"stop\");\n             stop2.setAttribute(\"offset\", \"1\");\n             stop2.setAttribute(\"stop-color\", fovColor);\n             stop2.setAttribute(\"stop-opacity\", \"0\");\n             gradient.appendChild(stop1);\n             gradient.appendChild(stop2);\n             defs.appendChild(gradient);\n             fovGroup.appendChild(defs);\n           }\n           \n           const fovPath = document.createElementNS(ns, \"path\");\n           \n           if (fovAngle >= 360) {\n             if (fovInnerRadius > 0) {\n               fovPath.setAttribute(\"d\", `M ${fovDistance},0 A ${fovDistance},${fovDistance} 0 1,1 ${-fovDistance},0 A ${fovDistance},${fovDistance} 0 1,1 ${fovDistance},0 M ${fovInnerRadius},0 A ${fovInnerRadius},${fovInnerRadius} 0 1,0 ${-fovInnerRadius},0 A ${fovInnerRadius},${fovInnerRadius} 0 1,0 ${fovInnerRadius},0`);\n               fovPath.setAttribute(\"fill-rule\", \"evenodd\");\n             } else {\n               fovPath.setAttribute(\"d\", `M 0,0 m -${fovDistance},0 a ${fovDistance},${fovDistance} 0 1,0 ${fovDistance * 2},0 a ${fovDistance},${fovDistance} 0 1,0 -${fovDistance * 2},0`);\n             }\n           } else {\n             const angleRad = (fovAngle * Math.PI) / 180;\n             const rotationRad = ((fovRotation - 90) * Math.PI) / 180;\n             const startAngle = rotationRad - angleRad / 2;\n             const endAngle = rotationRad + angleRad / 2;\n             const x1 = Math.cos(startAngle) * fovDistance;\n             const y1 = Math.sin(startAngle) * fovDistance;\n             const x2 = Math.cos(endAngle) * fovDistance;\n             const y2 = Math.sin(endAngle) * fovDistance;\n             const largeArc = fovAngle > 180 ? 1 : 0;\n             if (fovInnerRadius > 0) {\n               const ix1 = Math.cos(startAngle) * fovInnerRadius;\n               const iy1 = Math.sin(startAngle) * fovInnerRadius;\n               const ix2 = Math.cos(endAngle) * fovInnerRadius;\n               const iy2 = Math.sin(endAngle) * fovInnerRadius;\n               fovPath.setAttribute(\"d\", `M ${ix1},${iy1} L ${x1},${y1} A ${fovDistance},${fovDistance} 0 ${largeArc},1 ${x2},${y2} L ${ix2},${iy2} A ${fovInnerRadius},${fovInnerRadius} 0 ${largeArc},0 ${ix1},${iy1} Z`);\n             } else {\n               fovPath.setAttribute(\"d\", `M 0,0 L ${x1},${y1} A ${fovDistance},${fovDistance} 0 ${largeArc},1 ${x2},${y2} Z`);\n             }\n           }\n           \n           if (fovGradient) {\n             fovPath.style.fill = `url(#fov-gradient-${id})`;\n           } else {\n             const opacityHex = Math.round((fovOpacity / 100) * 255).toString(16).padStart(2, '0');\n             fovPath.style.fill = fovColor + opacityHex;\n           }\n           \n           const borderOpacityHex = Math.round((fovBorderOpacity / 100) * 255).toString(16).padStart(2, '0');\n           fovPath.style.stroke = fovBorderColor + borderOpacityHex;\n           fovPath.style.strokeWidth = fovBorderWidth;\n           if (fovBorderStyle === \"dashed\") {\n             fovPath.style.strokeDasharray = \"10,5\";\n           } else if (fovBorderStyle === \"dotted\") {\n             fovPath.style.strokeDasharray = \"3,3\";\n           }\n           fovPath.style.pointerEvents = \"none\";\n           fovPath.classList.add(\"fov-cone\");\n           \n           fovGroup.appendChild(fovPath);\n           \n           if (fovLabel) {\n             const fovLabelPosition = node.fovLabelPosition || \"center\";\n             const fovLabelSize = node.fovLabelSize || 14;\n             const fovLabelColor = node.fovLabelColor || \"#ffffff\";\n             const fovLabelBold = node.fovLabelBold || false;\n             const fovLabelBg = node.fovLabelBg || false;\n             const fovLabelBgColor = node.fovLabelBgColor || \"#000000\";\n             let labelDistance;\n             if (fovLabelPosition === \"center\") {\n               labelDistance = fovAngle >= 360 ? 0 : fovDistance * 0.4;\n             } else if (fovLabelPosition === \"edge\") {\n               labelDistance = fovDistance * 0.75;\n             } else {\n               labelDistance = fovDistance + fovLabelSize + 8;\n             }\n             const labelAngle = ((fovRotation - 90) * Math.PI) / 180;\n             const fovLabelOffsetX = node.fovLabelOffsetX || 0;\n             const fovLabelOffsetY = node.fovLabelOffsetY || 0;\n             const labelX = (fovAngle >= 360 && fovLabelPosition === \"center\" ? 0 : Math.cos(labelAngle) * labelDistance) + fovLabelOffsetX;\n             const labelY = (fovAngle >= 360 && fovLabelPosition === \"center\" ? 0 : Math.sin(labelAngle) * labelDistance) + fovLabelOffsetY;\n             if (fovLabelBg) {\n               const bgRect = document.createElementNS(ns, \"rect\");\n               const textWidth = fovLabel.length * fovLabelSize * 0.6;\n               const textHeight = fovLabelSize * 1.4;\n               bgRect.setAttribute(\"x\", labelX - textWidth / 2 - 6);\n               bgRect.setAttribute(\"y\", labelY - textHeight / 2);\n               bgRect.setAttribute(\"width\", textWidth + 12);\n               bgRect.setAttribute(\"height\", textHeight);\n               bgRect.setAttribute(\"rx\", \"4\");\n               bgRect.style.fill = fovLabelBgColor;\n               bgRect.style.opacity = \"0.8\";\n               bgRect.style.pointerEvents = \"none\";\n               fovGroup.appendChild(bgRect);\n             }\n             const labelEl = document.createElementNS(ns, \"text\");\n             labelEl.setAttribute(\"x\", labelX);\n             labelEl.setAttribute(\"y\", labelY);\n             labelEl.setAttribute(\"text-anchor\", \"middle\");\n             labelEl.setAttribute(\"dominant-baseline\", \"middle\");\n             labelEl.style.fill = fovLabelColor;\n             labelEl.style.fontSize = fovLabelSize + \"px\";\n             labelEl.style.fontWeight = fovLabelBold ? \"bold\" : \"normal\";\n             labelEl.style.fontFamily = \"system-ui, sans-serif\";\n             labelEl.style.pointerEvents = \"none\";\n             labelEl.textContent = fovLabel;\n             fovGroup.appendChild(labelEl);\n           }\n           \n           if (fovAnimate) {\n             const animationName = `fov-anim-${id}`;\n             const styleEl = document.createElementNS(\"http://www.w3.org/1999/xhtml\", \"style\");\n             \n             if (fovAnimationType === \"sweep\" && fovAngle < 360) {\n               styleEl.textContent = `\n                 @keyframes ${animationName} {\n                   0%, 100% { transform: rotate(0deg); }\n                   50% { transform: rotate(${fovSweep}deg); }\n                 }\n               `;\n               fovGroup.style.animation = `${animationName} ${fovSpeed}s ease-in-out infinite`;\n               fovGroup.style.transformOrigin = \"0 0\";\n             } else if (fovAnimationType === \"pulse\") {\n               styleEl.textContent = `\n                 @keyframes ${animationName} {\n                   0%, 100% { transform: scale(1); opacity: 1; }\n                   50% { transform: scale(1.1); opacity: 0.7; }\n                 }\n               `;\n               fovGroup.style.animation = `${animationName} ${fovSpeed}s ease-in-out infinite`;\n               fovGroup.style.transformOrigin = \"0 0\";\n             } else if (fovAnimationType === \"rings\") {\n               for (let i = 1; i <= 3; i++) {\n                 const ring = document.createElementNS(ns, \"circle\");\n                 ring.setAttribute(\"cx\", \"0\");\n                 ring.setAttribute(\"cy\", \"0\");\n                 ring.setAttribute(\"r\", fovDistance * 0.3 * i);\n                 ring.style.fill = \"none\";\n                 ring.style.stroke = fovBorderColor;\n                 ring.style.strokeWidth = \"2\";\n                 ring.style.opacity = \"0\";\n                 const ringAnimName = `${animationName}-ring-${i}`;\n                 const ringStyle = document.createElementNS(\"http://www.w3.org/1999/xhtml\", \"style\");\n                 ringStyle.textContent = `\n                   @keyframes ${ringAnimName} {\n                     0% { r: ${fovInnerRadius || 10}; opacity: 0.8; }\n                     100% { r: ${fovDistance}; opacity: 0; }\n                   }\n                 `;\n                 ring.style.animation = `${ringAnimName} ${fovSpeed}s ease-out infinite`;\n                 ring.style.animationDelay = `${(i - 1) * (fovSpeed / 3)}s`;\n                 fovGroup.appendChild(ringStyle);\n                 fovGroup.appendChild(ring);\n               }\n             } else if (fovAnimationType === \"spin\") {\n               styleEl.textContent = `\n                 @keyframes ${animationName} {\n                   0% { transform: rotate(0deg); }\n                   100% { transform: rotate(360deg); }\n                 }\n               `;\n               fovGroup.style.animation = `${animationName} ${fovSpeed}s linear infinite`;\n               fovGroup.style.transformOrigin = \"0 0\";\n             }\n             \n             if (fovAnimationType !== \"rings\") {\n               fovGroup.appendChild(styleEl);\n               const elapsedSeconds = (Date.now() - FOV_ANIMATION_START) / 1000;\n               const animationOffset = elapsedSeconds % fovSpeed;\n               fovGroup.style.animationDelay = `-${animationOffset}s`;\n             }\n           }\n           \n           g.appendChild(fovGroup);\n         }\n        g.append(hitArea, shapeEl, label, sub);\n         if (NODE_DATA[id]?.locked) {\n           const lockIndicator = document.createElementNS(ns, \"text\");\n           lockIndicator.textContent = \"🔒\";\n           lockIndicator.setAttribute(\"x\", r * 0.7);\n           lockIndicator.setAttribute(\"y\", -r * 0.7);\n           lockIndicator.style.fontSize = (r * 0.3) + \"px\";\n           lockIndicator.style.pointerEvents = \"none\";\n           lockIndicator.classList.add(\"lock-indicator\");\n           g.appendChild(lockIndicator);\n         }\n         if (NODE_DATA[id]?.groupId) {\n           const groupIndicator = document.createElementNS(ns, \"circle\");\n           groupIndicator.setAttribute(\"r\", r + 4);\n           groupIndicator.style.fill = \"none\";\n         groupIndicator.style.stroke = PAGE_STATE.groupIndicator || \"#4fd1c5\";\n           groupIndicator.style.strokeWidth = \"3\";\n           groupIndicator.style.strokeDasharray = \"5,5\";\n           groupIndicator.style.pointerEvents = \"none\";\n           groupIndicator.classList.add(\"group-indicator\");\n           g.insertBefore(groupIndicator, g.firstChild);\n         }\n         let isDragging = false;\n         let startX, startY;\n         let initialPositions = {};\n         let longPressTimer = null;\n         let longPressTriggered = false;\n         g.addEventListener(\"contextmenu\", (e) => {\n          e.preventDefault();\n          e.stopPropagation();\n          if (isViewOnly()) return;\n          if (selectedNodes.has(id)) {\n           selectedNodes.delete(id);\n          } else {\n           selectedNodes.add(id);\n          }\n          updateNodeSelection();\n          return false;\n         });\n        g.addEventListener(\"touchstart\", (e) => {\n         if (NODE_DATA[id].isRack) {\n          const touch = e.touches[0];\n          longPressStartX = touch.clientX;\n          longPressStartY = touch.clientY;\n          longPressTimer = setTimeout(() => {\n           longPressTriggered = true;\n           if (navigator.vibrate) {\n            navigator.vibrate(100);\n           }\n           enterRack(id);\n          }, 500);\n         }\n        }, { passive: true });\n        g.addEventListener(\"dblclick\", (e) => {\n         e.preventDefault();\n         e.stopPropagation();\n         if (NODE_DATA[id].isRack) {\n          enterRack(id);\n         }\n        });\n        let lastTapTime = 0;\n        let lastTapNode = null;\n        g.addEventListener(\"touchend\", (e) => {\n         const currentTime = new Date().getTime();\n         const tapLength = currentTime - lastTapTime;\n         if (tapLength < 300 && tapLength > 0 && lastTapNode === id) {\n          e.preventDefault();\n          e.stopPropagation();\n          if (isViewOnly()) return;\n          if (selectedNodes.has(id)) {\n           selectedNodes.delete(id);\n          } else {\n           selectedNodes.add(id);\n          }\n          updateNodeSelection();\n          if (navigator.vibrate) {\n           navigator.vibrate(50);\n          }\n          lastTapTime = 0;\n          lastTapNode = null;\n         } else {\n          lastTapTime = currentTime;\n          lastTapNode = id;\n         }\n        });\n         g.addEventListener(\"touchend\", (e) => {\n          if (longPressTimer) {\n           clearTimeout(longPressTimer);\n           longPressTimer = null;\n          }\n          if (longPressTriggered) {\n           e.preventDefault();\n           e.stopPropagation();\n           longPressTriggered = false;\n          }\n         });\n         let longPressStartX = 0;\n        let longPressStartY = 0;\n        g.addEventListener(\"touchmove\", (e) => {\n         if (longPressTimer) {\n          const touch = e.touches[0];\n          const dx = Math.abs(touch.clientX - longPressStartX);\n          const dy = Math.abs(touch.clientY - longPressStartY);\n          if (dx > 15 || dy > 15) {\n           clearTimeout(longPressTimer);\n           longPressTimer = null;\n           longPressTriggered = false;\n          }\n         }\n        }, { passive: true });\n         g.addEventListener(\"mousedown\", (e) => {\n          if (isViewOnly()) return;\n          if (e.button === 2) {\n           return;\n          }\n          if (NODE_DATA[id]?.locked) {\n           return;\n          }\n          e.preventDefault();\n          isDragging = true;\n\t\t  pushUndo(\"move nodes\");\n          const svgEl = document.getElementById(\"map\");\n          const pt = svgEl.createSVGPoint();\n          pt.x = e.clientX;\n          pt.y = e.clientY;\n          const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n          startX = svgP.x;\n          startY = svgP.y;\n          let nodesToCollect = [];\n      if (selectedNodes.has(id)) {\n      initialPositions = {};\n      const allSelectedRects = Array.from(selectedRects);\n      const allSelectedTexts = Array.from(selectedTexts);\n      const allSelectedEdges = Array.from(selectedEdges).map(eid => EDGE_DATA.list.find(e => e.id === eid)).filter(Boolean);\n      selectedNodes.forEach(nodeId => {\n      const nodePos = savedPositions[nodeId];\n      if (nodePos) {\n      initialPositions[nodeId] = { x: nodePos.x, y: nodePos.y };\n      }\n      });\n      Array.from(selectedRects).forEach(rectId => {\n        const rect = RECT_DATA.list.find(r => r.id === rectId);\n        if (rect) { rect._dragStartX = rect.x; rect._dragStartY = rect.y; }\n      });\n      Array.from(selectedTexts).forEach(textId => {\n        const text = TEXT_DATA.list.find(t => t.id === textId);\n        if (text) { text._dragStartX = text.x; text._dragStartY = text.y; }\n      });\n      Array.from(selectedEdges).forEach(edgeId => {\n        const edge = EDGE_DATA.list.find(e => e.id === edgeId);\n        if (edge && edge.points) { edge._dragStartPoints = edge.points.map(p => ({x: p.x, y: p.y})); }\n      });\n      } else {\n      initialPositions = { [id]: { x: pos.x, y: pos.y } };\n      }\n          const groupIds = new Set();\n          nodesToCollect.forEach(nodeId => {\n           const groupId = NODE_DATA[nodeId]?.groupId;\n           if (groupId) {\n             groupIds.add(groupId);\n           }\n          });\n          if (groupIds.size > 0) {\n           Object.keys(NODE_DATA).forEach(nodeId => {\n             const nodeGroupId = NODE_DATA[nodeId]?.groupId;\n             if (nodeGroupId && groupIds.has(nodeGroupId)) {\n               if (!nodesToCollect.includes(nodeId)) {\n                 nodesToCollect.push(nodeId);\n               }\n             }\n           });\n          }\n          nodesToCollect = nodesToCollect.filter(nodeId => !NODE_DATA[nodeId]?.locked);\n          if (nodesToCollect.length === 0) {\n           return;\n          }\n          initialPositions = {};\n          nodesToCollect.forEach(nodeId => {\n           const nodePos = savedPositions[nodeId];\n           if (nodePos) {\n            initialPositions[nodeId] = { x: nodePos.x, y: nodePos.y };\n           }\n          });\n          g.style.cursor = \"grabbing\";\n          hitArea.style.cursor = \"grabbing\";\n          e.stopPropagation();\n         });\n         const handleMouseMove = (e) => {\n          if (!isDragging) return;\n          e.preventDefault();\n          const svgEl = document.getElementById(\"map\");\n          const pt = svgEl.createSVGPoint();\n          pt.x = e.clientX;\n          pt.y = e.clientY;\n          const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n          const dx = svgP.x - startX;\n          const dy = svgP.y - startY;\n          const nodesToMove = selectedNodes.has(id) ? Array.from(selectedNodes) : [id];\n          nodesToMove.forEach(nodeId => {\n           if (!initialPositions[nodeId]) return;\n           const initialPos = initialPositions[nodeId];\n           let newX = initialPos.x + dx;\n           let newY = initialPos.y + dy;\n           const nodeSize = savedSizes[nodeId] || 55;\n           newX = Math.max(nodeSize + CANVAS_PADDING, Math.min(CANVAS_WIDTH - nodeSize - CANVAS_PADDING, newX));\n           newY = Math.max(nodeSize + CANVAS_PADDING, Math.min(CANVAS_HEIGHT - nodeSize - CANVAS_PADDING, newY));\n           savedPositions[nodeId] = { x: newX, y: newY };\n           positions[nodeId] = { x: newX, y: newY };\n           if (nodeId === id) {\n            pos.x = newX;\n            pos.y = newY;\n           }\n           const nodeGroup = document.querySelector(`g[data-node-id=\"${nodeId}\"]`);\n          if (nodeGroup) {\n           nodeGroup.setAttribute(\"transform\", `translate(${newX},${newY})`);\n          }\n         });\n         Array.from(selectedRects).forEach(rectId => {\n           const rect = RECT_DATA.list.find(r => r.id === rectId);\n           if (rect && rect._dragStartX !== undefined) {\n             rect.x = rect._dragStartX + dx;\n             rect.y = rect._dragStartY + dy;\n           }\n         });\n         Array.from(selectedTexts).forEach(textId => {\n           const text = TEXT_DATA.list.find(t => t.id === textId);\n           if (text && text._dragStartX !== undefined) {\n             text.x = text._dragStartX + dx;\n             text.y = text._dragStartY + dy;\n           }\n         });\n         Array.from(selectedEdges).forEach(edgeId => {\n           const edge = EDGE_DATA.list.find(e => e.id === edgeId);\n           if (edge && edge._dragStartPoints) {\n             edge.points.forEach((p, i) => {\n               p.x = edge._dragStartPoints[i].x + dx;\n               p.y = edge._dragStartPoints[i].y + dy;\n             });\n           }\n         });\n         forgeTheTopology();\n         updateMinimap();\n          document.querySelectorAll(\".edge\").forEach((edgeEl) => {\n           const fromId = edgeEl.dataset.from;\n           const toId = edgeEl.dataset.to;\n           if (nodesToMove.includes(fromId) || nodesToMove.includes(toId)) {\n            const p1 = savedPositions[fromId] || positions[fromId] || {\n             x: 600,\n             y: 350\n            };\n            const p2 = savedPositions[toId] || positions[toId] || {\n             x: 600,\n             y: 350\n            };\n            if (edgeEl.tagName === \"line\") {\n             edgeEl.setAttribute(\"x1\", p1.x);\n             edgeEl.setAttribute(\"y1\", p1.y);\n             edgeEl.setAttribute(\"x2\", p2.x);\n             edgeEl.setAttribute(\"y2\", p2.y);\n            } else if (edgeEl.tagName === \"path\") {\n             const edgeId = edgeEl.dataset.edgeId;\n             const edge = EDGE_DATA.list.find(\n              (e) => e.id === edgeId);\n             if (edge) {\n              const pairTotal = edge._pairTotal || 1;\n              const pairIndex = edge._pairIndex || 0;\n              const midX = (p1.x + p2.x) / 2;\n              const midY = (p1.y + p2.y) / 2;\n              const dx = p2.x - p1.x;\n              const dy = p2.y - p1.y;\n              const len = Math.sqrt(dx * dx + dy * dy) || 1;\n              const perpX = -dy / len;\n              const perpY = dx / len;\n              let offsetAmount = 0;\n              if (pairTotal > 1) {\n               offsetAmount = (pairIndex - (pairTotal - 1) / 2) * 40;\n              }\n              const ctrlX = midX + perpX * offsetAmount;\n              const ctrlY = midY + perpY * offsetAmount;\n              edgeEl.setAttribute(\"d\", `M ${p1.x} ${p1.y} Q ${ctrlX} ${ctrlY} ${p2.x} ${p2.y}`);\n             }\n            }\n           }\n          });\n         };\n      const handleMouseUp = () => {\n      if (isDragging) {\n      isDragging = false;\n      g.style.cursor = \"grab\";\n      hitArea.style.cursor = \"grab\";\n      if (currentView.mode === \"rack\" && NODE_DATA[id]?.assignedRack === currentView.rackId) {\n      const draggedY = savedPositions[id]?.y || pos.y;\n      const rackCapacity = getRackCapacity(currentView.rackId);\n      const rackUHeight = getRackUHeight(currentView.rackId);\n      let newUnit = rackCapacity - Math.round((draggedY - RACK_START_Y) / rackUHeight);\n      newUnit = Math.max(1, Math.min(newUnit, rackCapacity));\n      NODE_DATA[id].rackUnit = newUnit;\n      forgeTheTopology();\n      claimTheImmortal(id);\n      }\n      }\n      };\n         document.addEventListener(\"mousemove\", handleMouseMove);\n         document.addEventListener(\"mouseup\", handleMouseUp);\n         let touchStartTime = 0;\n         let touchStartX = 0;\n         let touchStartY = 0;\n         let touchMoved = false;\n       g.addEventListener(\"touchstart\",\n         (e) => {\n          if (NODE_DATA[id]?.locked) {\n           return;\n          }\n          e.preventDefault();\n          touchStartTime = Date.now();\n          touchMoved = false;\n          const svgEl = document.getElementById(\"map\");\n          const pt = svgEl.createSVGPoint();\n          const touch = e.touches[0];\n          pt.x = touch.clientX;\n          pt.y = touch.clientY;\n          const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n          touchStartX = svgP.x;\n          touchStartY = svgP.y;\n          startX = svgP.x;\n          startY = svgP.y;\n          let nodesToCollect = [];\n          if (selectedNodes.has(id)) {\n           nodesToCollect = Array.from(selectedNodes);\n          } else {\n           nodesToCollect = [id];\n          }\n          const groupIds = new Set();\n          nodesToCollect.forEach(nodeId => {\n           const groupId = NODE_DATA[nodeId]?.groupId;\n           if (groupId) {\n             groupIds.add(groupId);\n           }\n          });\n          if (groupIds.size > 0) {\n           Object.keys(NODE_DATA).forEach(nodeId => {\n             const nodeGroupId = NODE_DATA[nodeId]?.groupId;\n             if (nodeGroupId && groupIds.has(nodeGroupId)) {\n               if (!nodesToCollect.includes(nodeId)) {\n                 nodesToCollect.push(nodeId);\n               }\n             }\n           });\n          }\n          nodesToCollect = nodesToCollect.filter(nodeId => !NODE_DATA[nodeId]?.locked);\n          if (nodesToCollect.length === 0) {\n           return;\n          }\n          initialPositions = {};\n          nodesToCollect.forEach(nodeId => {\n           const nodePos = savedPositions[nodeId];\n           if (nodePos) {\n            initialPositions[nodeId] = { x: nodePos.x, y: nodePos.y };\n           }\n          });\n          e.stopPropagation();\n         }, {\n          passive: false\n         });\n         g.addEventListener(\"touchmove\", (e) => {\n\t\t  if (isViewOnly()) return;\n         e.preventDefault();\n         const svgEl = document.getElementById(\"map\");\n         const pt = svgEl.createSVGPoint();\n         const touch = e.touches[0];\n         pt.x = touch.clientX;\n         pt.y = touch.clientY;\n         const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n         const dx = Math.abs(svgP.x - touchStartX);\n         const dy = Math.abs(svgP.y - touchStartY);\n         if (dx > (isMobileDevice() ? 4 : 10) || dy > (isMobileDevice() ? 4 : 10)) {\n      touchMoved = true;\n      isDragging = true;\n      }\n         if (!isDragging) return;\n         const deltaX = svgP.x - startX;\n         const deltaY = svgP.y - startY;\n         const nodesToMove = selectedNodes.has(id) ? Array.from(selectedNodes) : [id];\n         nodesToMove.forEach(nodeId => {\n          if (!initialPositions[nodeId]) return;\n          const initialPos = initialPositions[nodeId];\n          let newX = initialPos.x + deltaX;\n          let newY = initialPos.y + deltaY;\n          const nodeSize = savedSizes[nodeId] || 55;\n          newX = Math.max(nodeSize + CANVAS_PADDING, Math.min(CANVAS_WIDTH - nodeSize - CANVAS_PADDING, newX));\n          newY = Math.max(nodeSize + CANVAS_PADDING, Math.min(CANVAS_HEIGHT - nodeSize - CANVAS_PADDING, newY));\n          savedPositions[nodeId] = { x: newX, y: newY };\n          positions[nodeId] = { x: newX, y: newY };\n          if (nodeId === id) {\n           pos.x = newX;\n           pos.y = newY;\n          }\n          const nodeGroup = document.querySelector(`g[data-node-id=\"${nodeId}\"]`);\n          if (nodeGroup) {\n           nodeGroup.setAttribute(\"transform\", `translate(${newX},${newY})`);\n          }\n         });\n         updateMinimap();\n         document.querySelectorAll(\".edge\").forEach((edgeEl) => {\n          const fromId = edgeEl.dataset.from;\n          const toId = edgeEl.dataset.to;\n          if (nodesToMove.includes(fromId) || nodesToMove.includes(toId)) {\n            const p1 = savedPositions[fromId] || positions[fromId] || {\n             x: 600,\n             y: 350\n            };\n            const p2 = savedPositions[toId] || positions[toId] || {\n             x: 600,\n             y: 350\n            };\n            if (edgeEl.tagName === \"line\") {\n             edgeEl.setAttribute(\"x1\", p1.x);\n             edgeEl.setAttribute(\"y1\", p1.y);\n             edgeEl.setAttribute(\"x2\", p2.x);\n             edgeEl.setAttribute(\"y2\", p2.y);\n            } else if (edgeEl.tagName === \"path\") {\n             const edgeId = edgeEl.dataset.edgeId;\n             const edge = EDGE_DATA.list.find(\n              (e) => e.id === edgeId);\n             if (edge) {\n              const pairTotal = edge._pairTotal || 1;\n              const pairIndex = edge._pairIndex || 0;\n              const midX = (p1.x + p2.x) / 2;\n              const midY = (p1.y + p2.y) / 2;\n              const dx = p2.x - p1.x;\n              const dy = p2.y - p1.y;\n              const len = Math.sqrt(dx * dx + dy * dy) || 1;\n              const perpX = -dy / len;\n              const perpY = dx / len;\n              let offsetAmount = 0;\n              if (pairTotal > 1) {\n               offsetAmount = (pairIndex - (pairTotal - 1) / 2) * 40;\n              }\n              const ctrlX = midX + perpX * offsetAmount;\n              const ctrlY = midY + perpY * offsetAmount;\n              edgeEl.setAttribute(\"d\", `M ${p1.x} ${p1.y} Q ${ctrlX} ${ctrlY} ${p2.x} ${p2.y}`);\n             }\n            }\n           }\n          });\n         }, {\n          passive: false\n         });\n      g.addEventListener(\"touchend\", (e) => {\n      const touchDuration = Date.now() - touchStartTime;\n      if (!touchMoved && touchDuration < 400) {\n       if (isViewOnly()) {\n        handleViewOnlyClick(id, 'node');\n        return;\n       }\n       claimTheImmortal(id);\n      }\n      if (isDragging) {\n      const draggedY = pos.y;\n      isDragging = false;\n      savedPositions[id] = {\n      x: pos.x,\n      y: pos.y\n      };\n      if (currentView.mode === \"rack\" && NODE_DATA[id]?.assignedRack === currentView.rackId) {\n      const rackCapacity = getRackCapacity(currentView.rackId);\n      const rackUHeight = getRackUHeight(currentView.rackId);\n      let newUnit = rackCapacity - Math.round((draggedY - RACK_START_Y) / rackUHeight);\n      newUnit = Math.max(1, Math.min(newUnit, rackCapacity));\n      NODE_DATA[id].rackUnit = newUnit;\n      forgeTheTopology();\n      claimTheImmortal(id);\n      }\n      }\n      touchMoved = false;\n      });\n         g.style.cursor = \"grab\";\n         g.addEventListener(\"click\", (e) => {\n          if (!isDragging) {\n           if (isViewOnly()) {\n            handleViewOnlyClick(id, 'node');\n            return;\n           }\n           claimTheImmortal(id);\n          }\n         });\n         svg.appendChild(g);\n        });\n        if (RECT_DATA && RECT_DATA.list) {\n         RECT_DATA.list.forEach((rect) => {\n          if (currentView.mode === \"rack\") return;\n          if (rect.style === \"outlined\") {\n           const g = document.createElementNS(ns, \"g\");\n           g.classList.add(\"rect-group\");\n           g.dataset.rectId = rect.id;\n           const rectCenterX = rect.x + rect.width / 2;\n           const rectCenterY = rect.y + rect.height / 2;\n           const rectRotation = rect.rotation || 0;\n           if (rectRotation !== 0) {\n             g.setAttribute(\"transform\", `rotate(${rectRotation}, ${rectCenterX}, ${rectCenterY})`);\n           }\n           const rectEl = document.createElementNS(ns, \"rect\");\n           rectEl.classList.add(\"rect-shape\");\n           rectEl.setAttribute(\"x\", rect.x);\n           rectEl.setAttribute(\"y\", rect.y);\n           rectEl.setAttribute(\"width\", rect.width);\n           rectEl.setAttribute(\"height\", rect.height);\n           rectEl.style.fill = \"none\";\n           rectEl.style.stroke = rect.borderColor || rect.color;\n           rectEl.style.strokeWidth = (rect.borderWidth !== undefined ? rect.borderWidth : 3) + \"px\";\nif (rect.lineStyle === \"dashed\") { rectEl.style.strokeDasharray = \"10,5\"; }\n           else if (rect.lineStyle === \"dotted\") { rectEl.style.strokeDasharray = \"2,4\"; }\n           else if (rect.lineStyle === \"wall\") {\n             rectEl.style.fill = rect.color;\n             rectEl.style.fillOpacity = \"0.5\";\n             rectEl.style.stroke = rect.borderColor || rect.color;\n             rectEl.style.strokeWidth = (rect.borderWidth !== undefined ? rect.borderWidth : 2) + \"px\";\n             const hatchGroup = document.createElementNS(ns, \"g\");\n             hatchGroup.classList.add(\"wall-hatch-lines\");\n             hatchGroup.style.pointerEvents = \"none\";\n             const spacing = 12;\n             const hatchColor = rect.borderColor || rect.color;\n             for (let i = -rect.height; i < rect.width + rect.height; i += spacing) {\n               const line = document.createElementNS(ns, \"line\");\n               line.setAttribute(\"x1\", rect.x + i);\n               line.setAttribute(\"y1\", rect.y);\n               line.setAttribute(\"x2\", rect.x + i - rect.height);\n               line.setAttribute(\"y2\", rect.y + rect.height);\n               line.style.stroke = hatchColor;\n               line.style.strokeWidth = \"2\";\n               hatchGroup.appendChild(line);\n             }\n             const clipId = \"clip-\" + rect.id;\n             const clipPath = document.createElementNS(ns, \"clipPath\");\n             clipPath.id = clipId;\n             const clipRect = document.createElementNS(ns, \"rect\");\n             clipRect.setAttribute(\"x\", rect.x);\n             clipRect.setAttribute(\"y\", rect.y);\n             clipRect.setAttribute(\"width\", rect.width);\n             clipRect.setAttribute(\"height\", rect.height);\n             clipPath.appendChild(clipRect);\n             defs.appendChild(clipPath);\n             hatchGroup.setAttribute(\"clip-path\", \"url(#\" + clipId + \")\");\n             g.appendChild(hatchGroup);\n           }\n           else { rectEl.style.strokeDasharray = \"none\"; }\n           rectEl.style.cursor = \"move\";\n           rectEl.addEventListener(\"click\", (e) => {\n             if (rectDrawMode) return;\n             e.stopPropagation();\n             currentRectId = rect.id;\n             selectTheRect(rect.id);\n           });\n           rectEl.addEventListener(\"contextmenu\", (e) => {\n             e.preventDefault();\n             e.stopPropagation();\n             if (selectedRects.has(rect.id)) {\n               selectedRects.delete(rect.id);\n             } else {\n               selectedRects.add(rect.id);\n             }\n             updateAllSelections();\n           });\n           let rectLastTap = 0;\n           rectEl.addEventListener(\"touchend\", (e) => {\n             const now = Date.now();\n             if (now - rectLastTap < 300) {\n               e.preventDefault();\n               if (selectedRects.has(rect.id)) {\n                 selectedRects.delete(rect.id);\n               } else {\n                 selectedRects.add(rect.id);\n               }\n               updateAllSelections();\n               if (navigator.vibrate) navigator.vibrate(50);\n               rectLastTap = 0;\n             } else {\n               rectLastTap = now;\n             }\n           }, { passive: false });\n           const deleteBtn = document.createElementNS(ns, \"g\");\n           deleteBtn.classList.add(\"rect-delete-btn\");\n           deleteBtn.style.cursor = \"pointer\";\n           deleteBtn.style.display = rectDrawMode ? \"block\" : \"none\";\n           const deleteBg = document.createElementNS(ns, \"circle\");\n           deleteBg.setAttribute(\"cx\", rect.x + rect.width - 10);\n           deleteBg.setAttribute(\"cy\", rect.y + 10);\n           deleteBg.setAttribute(\"r\", 12);\n           deleteBg.style.fill = \"#f56565\";\n           deleteBg.style.stroke = \"white\";\n           deleteBg.style.strokeWidth = \"2\";\n           const deleteX = document.createElementNS(ns, \"text\");\n           deleteX.setAttribute(\"x\", rect.x + rect.width - 10);\n           deleteX.setAttribute(\"y\", rect.y + 10);\n           deleteX.setAttribute(\"text-anchor\", \"middle\");\n           deleteX.setAttribute(\"dominant-baseline\", \"middle\");\n           deleteX.style.fill = \"white\";\n           deleteX.style.fontSize = \"16px\";\n           deleteX.style.fontWeight = \"bold\";\n           deleteX.style.pointerEvents = \"none\";\n           deleteX.textContent = \"×\";\n           deleteBtn.appendChild(deleteBg);\n           deleteBtn.appendChild(deleteX);\n           deleteBtn.addEventListener(\"click\", (e) => {\n            e.stopPropagation();\n            e.preventDefault();\n            deleteRectangle(rect.id);\n           });\n           let isDragging = false;\n           let dragStartX, dragStartY;\n           let rectStartX, rectStartY;\n           rectEl.addEventListener(\"mousedown\", (e) => {\n\t\t   if (isViewOnly()) return;\n            if (rectDrawMode) return;\n            e.preventDefault();\n            e.stopPropagation();\n            isDragging = true;\n            dragStartX = e.clientX;\n            dragStartY = e.clientY;\n            rectStartX = rect.x;\n            rectStartY = rect.y;\n            rectEl.style.cursor = \"grabbing\";\n           });\n      const moveHandler = (e) => {\n      if (!isDragging || rectDrawMode) return;\n      const svgEl = svg;\n      const pt1 = svgEl.createSVGPoint();\n      pt1.x = dragStartX;\n      pt1.y = dragStartY;\n      const svgP1 = pt1.matrixTransform(svgEl.getScreenCTM().inverse());\n      const pt2 = svgEl.createSVGPoint();\n      pt2.x = e.clientX;\n      pt2.y = e.clientY;\n      const svgP2 = pt2.matrixTransform(svgEl.getScreenCTM().inverse());\n      const dx = svgP2.x - svgP1.x;\n      const dy = svgP2.y - svgP1.y;\n      rect.x = rectStartX + dx;\n      rect.y = rectStartY + dy;\n      if (selectedRects.has(rect.id)) {\n      selectedNodes.forEach(nodeId => { if (initialPositions[nodeId]) { savedPositions[nodeId] = { x: initialPositions[nodeId].x + dx, y: initialPositions[nodeId].y + dy }; } });\n      selectedRects.forEach(rectId => { if (rectId === rect.id) return; const r = RECT_DATA.list.find(x => x.id === rectId); if (r && r._dragStartX !== undefined) { r.x = r._dragStartX + dx; r.y = r._dragStartY + dy; } });\n      selectedTexts.forEach(textId => { const t = TEXT_DATA.list.find(x => x.id === textId); if (t && t._dragStartX !== undefined) { t.x = t._dragStartX + dx; t.y = t._dragStartY + dy; } });\n      selectedEdges.forEach(edgeId => { const ed = EDGE_DATA.list.find(x => x.id === edgeId); if (ed && ed._dragStartPoints) { ed.points.forEach((p, i) => { p.x = ed._dragStartPoints[i].x + dx; p.y = ed._dragStartPoints[i].y + dy; }); } });\n      }\n      forgeTheTopology();\n      };\n           const upHandler = () => {\n            if (isDragging) {\n             isDragging = false;\n             rectEl.style.cursor = \"move\";\n            }\n           };\n           document.addEventListener(\"mousemove\", moveHandler);\n           document.addEventListener(\"mouseup\", upHandler);\n           let touchStartX, touchStartY;\n           let touchRectStartX, touchRectStartY;\n           rectEl.addEventListener(\"touchstart\", (e) => {\n\t\t    if (isViewOnly()) return;\n            if (rectDrawMode) return;\n            e.preventDefault();\n            e.stopPropagation();\n            isDragging = true;\n            const touch = e.touches[0];\n            touchStartX = touch.clientX;\n            touchStartY = touch.clientY;\n            touchRectStartX = rect.x;\n            touchRectStartY = rect.y;\n            rectEl.style.cursor = \"grabbing\";\n           }, { passive: false });\n           rectEl.addEventListener(\"touchmove\", (e) => {\n\t\t   if (isViewOnly()) return;\n            if (!isDragging || rectDrawMode) return;\n            if (!e.touches[0]) return;\n            e.preventDefault();\n            e.stopPropagation();\n            const svgEl = svg;\n            const pt1 = svgEl.createSVGPoint();\n            pt1.x = touchStartX;\n            pt1.y = touchStartY;\n            const svgP1 = pt1.matrixTransform(svgEl.getScreenCTM().inverse());\n            const touch = e.touches[0];\n            const pt2 = svgEl.createSVGPoint();\n            pt2.x = touch.clientX;\n            pt2.y = touch.clientY;\n            const svgP2 = pt2.matrixTransform(svgEl.getScreenCTM().inverse());\n            const dx = svgP2.x - svgP1.x;\n            const dy = svgP2.y - svgP1.y;\n            rect.x = touchRectStartX + dx;\n            rect.y = touchRectStartY + dy;\n            forgeTheTopology();\n           }, { passive: false });\n           rectEl.addEventListener(\"touchend\", (e) => {\n            if (isDragging) {\n             e.preventDefault();\n             isDragging = false;\n             rectEl.style.cursor = \"move\";\n            }\n           }, { passive: false });\n           if (currentRectId === rect.id) {\n             const corners = [\n               { cx: rect.x, cy: rect.y, cursor: 'nwse-resize', dx: -1, dy: -1 },\n               { cx: rect.x + rect.width, cy: rect.y, cursor: 'nesw-resize', dx: 1, dy: -1 },\n               { cx: rect.x, cy: rect.y + rect.height, cursor: 'nesw-resize', dx: -1, dy: 1 },\n               { cx: rect.x + rect.width, cy: rect.y + rect.height, cursor: 'nwse-resize', dx: 1, dy: 1 }\n             ];\n             corners.forEach((corner) => {\n               const handle = document.createElementNS(ns, \"circle\");\n               handle.setAttribute(\"cx\", corner.cx);\n               handle.setAttribute(\"cy\", corner.cy);\n               const borderW = rect.borderWidth !== undefined ? rect.borderWidth : 3;\n               const handleSize = Math.max(PAGE_STATE.selectionHandleSize || 8, borderW + 4);\n               handle.setAttribute(\"r\", handleSize);\n               handle.style.fill = PAGE_STATE.selectionHandle || \"#f59e0b\";\n               handle.style.stroke = \"#fff\";\n               handle.style.strokeWidth = \"2\";\n               handle.style.cursor = corner.cursor;\n               handle.addEventListener(\"mousedown\", (e) => {\n\t\t\t   if (isViewOnly()) return;\n                 e.preventDefault();\n                 e.stopPropagation();\n\t\t\t\t pushUndo(\"resize zone\");\n                 let dragging = true;\n                 const startX = e.clientX, startY = e.clientY;\n                 const origX = rect.x, origY = rect.y, origW = rect.width, origH = rect.height;\n                 const moveHandler = (ev) => {\n                   if (!dragging) return;\n                   const pt1 = svg.createSVGPoint(); pt1.x = startX; pt1.y = startY;\n                   const pt2 = svg.createSVGPoint(); pt2.x = ev.clientX; pt2.y = ev.clientY;\n                   const svgP1 = pt1.matrixTransform(svg.getScreenCTM().inverse());\n                   const svgP2 = pt2.matrixTransform(svg.getScreenCTM().inverse());\n                   const dx = svgP2.x - svgP1.x, dy = svgP2.y - svgP1.y;\n                   if (corner.dx < 0) { rect.x = origX + dx; rect.width = origW - dx; }\n                   else { rect.width = origW + dx; }\n                   if (corner.dy < 0) { rect.y = origY + dy; rect.height = origH - dy; }\n                   else { rect.height = origH + dy; }\n                   if (rect.width < 20) rect.width = 20;\n                   if (rect.height < 20) rect.height = 20;\n                   forgeTheTopology();\n                 };\n                 const upHandler = () => { dragging = false; document.removeEventListener(\"mousemove\", moveHandler); document.removeEventListener(\"mouseup\", upHandler); };\n                 document.addEventListener(\"mousemove\", moveHandler);\n                 document.addEventListener(\"mouseup\", upHandler);\n               });\n               g.appendChild(handle);\n             });\n           }\n           if (rect.groupId) {\n             const groupIndicator = document.createElementNS(ns, \"rect\");\n             groupIndicator.setAttribute(\"x\", rect.x - 4);\n             groupIndicator.setAttribute(\"y\", rect.y - 4);\n             groupIndicator.setAttribute(\"width\", rect.width + 8);\n             groupIndicator.setAttribute(\"height\", rect.height + 8);\n             groupIndicator.style.fill = \"none\";\n         groupIndicator.style.stroke = PAGE_STATE.groupIndicator || \"#4fd1c5\";\n             groupIndicator.style.strokeWidth = \"3\";\n             groupIndicator.style.strokeDasharray = \"5,5\";\n             groupIndicator.style.pointerEvents = \"none\";\n             g.insertBefore(groupIndicator, g.firstChild);\n           }\n           g.appendChild(rectEl);\n           g.appendChild(deleteBtn);\n           svg.appendChild(g);\n          }\n         });\n        }\n        if (TEXT_DATA && TEXT_DATA.list) {\n         TEXT_DATA.list.forEach((textItem) => {\n          if (currentView.mode === \"rack\") return;\n          const g = document.createElementNS(ns, \"g\");\n          g.classList.add(\"text-group\");\n          g.dataset.textId = textItem.id;\n          const textRotation = textItem.rotation || 0;\n          if (textRotation !== 0) {\n            g.setAttribute(\"transform\", `rotate(${textRotation}, ${textItem.x}, ${textItem.y})`);\n          }\n          if (textItem.bgEnabled) {\n           const bgRect = document.createElementNS(ns, \"rect\");\n           bgRect.classList.add(\"text-bg\");\n           bgRect.setAttribute(\"x\", textItem.x - 5);\n           bgRect.setAttribute(\"y\", textItem.y - textItem.fontSize - 2);\n           bgRect.setAttribute(\"width\", 100);\n           bgRect.setAttribute(\"height\", textItem.fontSize + 10);\n           bgRect.style.fill = textItem.bgColor;\n           bgRect.style.opacity = \"0.7\";\n           bgRect.style.rx = \"4\";\n           g.appendChild(bgRect);\n          }\n          const textEl = document.createElementNS(ns, \"text\");\n          textEl.classList.add(\"text-element\");\n          textEl.setAttribute(\"x\", textItem.x);\n          textEl.setAttribute(\"y\", textItem.y);\n          textEl.style.fill = textItem.color;\n          textEl.style.fontSize = textItem.fontSize + \"px\";\n          textEl.style.fontWeight = textItem.fontWeight;\n          textEl.style.fontStyle = textItem.fontStyle;\n          textEl.style.textAnchor = textItem.textAlign;\n          textEl.style.textDecoration = textItem.textDecoration;\n          textEl.style.opacity = textItem.opacity;\n          textEl.style.cursor = \"move\";\n          textEl.style.userSelect = \"none\";\n          textEl.setAttribute(\"dominant-baseline\", \"middle\");\n          const lines = textItem.content.split('\\n');\n          if (lines.length === 1) {\n           textEl.textContent = textItem.content;\n          } else {\n           lines.forEach((line, i) => {\n            const tspan = document.createElementNS(ns, \"tspan\");\n            tspan.textContent = line;\n            tspan.setAttribute(\"x\", textItem.x);\n            tspan.setAttribute(\"dy\", i === 0 ? 0 : textItem.fontSize * 1.2);\n            textEl.appendChild(tspan);\n           });\n          }\n          g.appendChild(textEl);\n          if (textItem.bgEnabled) {\n           setTimeout(() => {\n            try {\n             const bbox = textEl.getBBox();\n             const bgRect = g.querySelector('.text-bg');\n             if (bgRect && bbox) {\n              bgRect.setAttribute(\"x\", bbox.x - 5);\n              bgRect.setAttribute(\"y\", bbox.y - 2);\n              bgRect.setAttribute(\"width\", bbox.width + 10);\n              bgRect.setAttribute(\"height\", bbox.height + 4);\n             }\n            } catch (e) {\n            }\n           }, 0);\n          }\n          const deleteBtn = document.createElementNS(ns, \"g\");\n          deleteBtn.classList.add(\"text-delete-btn\");\n          deleteBtn.style.cursor = \"pointer\";\n          deleteBtn.style.display = textDrawMode ? \"block\" : \"none\";\n          const deleteBg = document.createElementNS(ns, \"circle\");\n          deleteBg.setAttribute(\"cx\", textItem.x + 20);\n          deleteBg.setAttribute(\"cy\", textItem.y - textItem.fontSize);\n          deleteBg.setAttribute(\"r\", 12);\n          deleteBg.style.fill = \"#f56565\";\n          deleteBg.style.stroke = \"white\";\n          deleteBg.style.strokeWidth = \"2\";\n          const deleteX = document.createElementNS(ns, \"text\");\n          deleteX.setAttribute(\"x\", textItem.x + 20);\n          deleteX.setAttribute(\"y\", textItem.y - textItem.fontSize);\n          deleteX.setAttribute(\"text-anchor\", \"middle\");\n          deleteX.setAttribute(\"dominant-baseline\", \"middle\");\n          deleteX.style.fill = \"white\";\n          deleteX.style.fontSize = \"16px\";\n          deleteX.style.fontWeight = \"bold\";\n          deleteX.style.pointerEvents = \"none\";\n          deleteX.textContent = \"×\";\n          deleteBtn.appendChild(deleteBg);\n          deleteBtn.appendChild(deleteX);\n          deleteBtn.addEventListener(\"click\", (e) => {\n           e.stopPropagation();\n           e.preventDefault();\n           deleteText(textItem.id);\n          });\n          let isDragging = false;\n          let dragStartX, dragStartY;\n          let textStartX, textStartY;\n          textEl.addEventListener(\"mousedown\", (e) => {\n\t\t  if (isViewOnly()) return;\n      if (textDrawMode) return;\n      e.preventDefault();\n      e.stopPropagation();\n      isDragging = true;\n      dragStartX = e.clientX;\n      dragStartY = e.clientY;\n      textStartX = textItem.x;\n      textStartY = textItem.y;\n      textEl.style.cursor = \"grabbing\";\n      showTextPanel(textItem.id);\n      if (selectedTexts.has(textItem.id)) {\n      initialPositions = {};\n      selectedNodes.forEach(nodeId => {\n      const nodePos = savedPositions[nodeId];\n      if (nodePos) initialPositions[nodeId] = { x: nodePos.x, y: nodePos.y };\n      });\n      selectedRects.forEach(rectId => {\n      const r = RECT_DATA.list.find(x => x.id === rectId);\n      if (r) { r._dragStartX = r.x; r._dragStartY = r.y; }\n      });\n      selectedTexts.forEach(textId => {\n      const t = TEXT_DATA.list.find(x => x.id === textId);\n      if (t) { t._dragStartX = t.x; t._dragStartY = t.y; }\n      });\n      selectedEdges.forEach(edgeId => {\n      const ed = EDGE_DATA.list.find(x => x.id === edgeId);\n      if (ed && ed.points) { ed._dragStartPoints = ed.points.map(p => ({x: p.x, y: p.y})); }\n      });\n      }\n      });\n          const moveHandler = (e) => {\n      if (!isDragging || textDrawMode) return;\n      const svgEl = svg;\n      const pt1 = svgEl.createSVGPoint();\n      pt1.x = dragStartX;\n      pt1.y = dragStartY;\n      const svgP1 = pt1.matrixTransform(svgEl.getScreenCTM().inverse());\n      const pt2 = svgEl.createSVGPoint();\n      pt2.x = e.clientX;\n      pt2.y = e.clientY;\n      const svgP2 = pt2.matrixTransform(svgEl.getScreenCTM().inverse());\n      const dx = svgP2.x - svgP1.x;\n      const dy = svgP2.y - svgP1.y;\n      textItem.x = textStartX + dx;\n      textItem.y = textStartY + dy;\n      if (selectedTexts.has(textItem.id)) {\n      selectedNodes.forEach(nodeId => {\n      if (initialPositions[nodeId]) {\n       const newX = initialPositions[nodeId].x + dx;\n       const newY = initialPositions[nodeId].y + dy;\n       savedPositions[nodeId] = { x: newX, y: newY };\n      }\n      });\n      selectedRects.forEach(rectId => {\n      const r = RECT_DATA.list.find(x => x.id === rectId);\n      if (r && r._dragStartX !== undefined) { r.x = r._dragStartX + dx; r.y = r._dragStartY + dy; }\n      });\n      selectedTexts.forEach(textId => {\n      if (textId === textItem.id) return;\n      const t = TEXT_DATA.list.find(x => x.id === textId);\n      if (t && t._dragStartX !== undefined) { t.x = t._dragStartX + dx; t.y = t._dragStartY + dy; }\n      });\n      selectedEdges.forEach(edgeId => {\n      const ed = EDGE_DATA.list.find(x => x.id === edgeId);\n      if (ed && ed._dragStartPoints) { ed.points.forEach((p, i) => { p.x = ed._dragStartPoints[i].x + dx; p.y = ed._dragStartPoints[i].y + dy; }); }\n      });\n      }\n      forgeTheTopology();\n      };\n          const upHandler = () => {\n           if (isDragging) {\n            isDragging = false;\n            textEl.style.cursor = \"move\";\n           }\n          };\n          document.addEventListener(\"mousemove\", moveHandler);\n          document.addEventListener(\"mouseup\", upHandler);\n          let touchStartX = 0, touchStartY = 0;\n          let textTouchStartX = 0, textTouchStartY = 0;\n          textEl.addEventListener(\"touchstart\", (e) => {\n\t\t   if (isViewOnly()) return;\n           if (textDrawMode) return;\n           if (e.touches.length !== 1) return;\n           e.preventDefault();\n           e.stopPropagation();\n           const touch = e.touches[0];\n           isDragging = true;\n           touchStartX = touch.clientX;\n           touchStartY = touch.clientY;\n           textTouchStartX = textItem.x;\n           textTouchStartY = textItem.y;\n           showTextPanel(textItem.id);\n          }, { passive: false });\n          textEl.addEventListener(\"touchmove\", (e) => {\n           if (!isDragging || textDrawMode) return;\n           if (e.touches.length !== 1) return;\n           e.preventDefault();\n           const touch = e.touches[0];\n           const svgEl = svg;\n           const pt1 = svgEl.createSVGPoint();\n           pt1.x = touchStartX;\n           pt1.y = touchStartY;\n           const svgP1 = pt1.matrixTransform(svgEl.getScreenCTM().inverse());\n           const pt2 = svgEl.createSVGPoint();\n           pt2.x = touch.clientX;\n           pt2.y = touch.clientY;\n           const svgP2 = pt2.matrixTransform(svgEl.getScreenCTM().inverse());\n           const dx = svgP2.x - svgP1.x;\n           const dy = svgP2.y - svgP1.y;\n           textItem.x = textTouchStartX + dx;\n           textItem.y = textTouchStartY + dy;\n           forgeTheTopology();\n          }, { passive: false });\n          textEl.addEventListener(\"touchend\", () => {\n           if (isDragging) {\n            isDragging = false;\n           }\n          }, { passive: false });\n          textEl.addEventListener(\"contextmenu\", (e) => {\n           e.preventDefault();\n           e.stopPropagation();\n           if (selectedTexts.has(textItem.id)) {\n            selectedTexts.delete(textItem.id);\n           } else {\n            selectedTexts.add(textItem.id);\n           }\n           updateAllSelections();\n          });\n          let textLastTap = 0;\n          g.addEventListener(\"touchend\", (e) => {\n           const now = Date.now();\n           if (now - textLastTap < 300) {\n            e.preventDefault();\n            if (selectedTexts.has(textItem.id)) {\n             selectedTexts.delete(textItem.id);\n            } else {\n             selectedTexts.add(textItem.id);\n            }\n            updateAllSelections();\n            if (navigator.vibrate) navigator.vibrate(50);\n            textLastTap = 0;\n           } else {\n            textLastTap = now;\n           }\n          }, { passive: false });\n          if (textItem.groupId) {\n            const groupIndicator = document.createElementNS(ns, \"rect\");\n            groupIndicator.setAttribute(\"x\", textItem.x - 54);\n            groupIndicator.setAttribute(\"y\", textItem.y - 24);\n            groupIndicator.setAttribute(\"width\", 108);\n            groupIndicator.setAttribute(\"height\", 48);\n            groupIndicator.setAttribute(\"rx\", \"8\");\n            groupIndicator.style.fill = \"none\";\n       groupIndicator.style.stroke = PAGE_STATE.groupIndicator || \"#4fd1c5\";\n            groupIndicator.style.strokeWidth = \"3\";\n            groupIndicator.style.strokeDasharray = \"5,5\";\n            groupIndicator.style.pointerEvents = \"none\";\n            g.insertBefore(groupIndicator, g.firstChild);\n          }\n          g.appendChild(deleteBtn);\n          svg.appendChild(g);\n         });\n        }\n        Object.keys(NODE_DATA).forEach(nodeId => {\n         updatePingIndicator(nodeId);\n        });\n        forgeTheLegend();\n\t\tupdateZoneLegend();\n        updateMinimap();\n        if (currentSearchQuery && currentSearchResults.length > 0) {\n         highlightSearchResults(currentSearchResults, true);\n        }\n       }\n       const _forgeTheTopologyImpl = forgeTheTopology;\n       forgeTheTopology = function(immediate = false) {\n        if (immediate || forgeImmediate) {\n         forgeImmediate = false;\n         clearTimeout(forgeDebounceTimer);\n         _forgeTheTopologyImpl();\n         return;\n        }\n        clearTimeout(forgeDebounceTimer);\n        forgeDebounceTimer = setTimeout(() => {\n         _forgeTheTopologyImpl();\n        }, 16);\n       };\n       function forgeTheTopologyImmediate() {\n        forgeImmediate = true;\n        forgeTheTopology();\n       }\n       function showEditModal(title, currentValue, onSave) {\n        const modal = document.getElementById(\"edit-modal\");\n        const input = document.getElementById(\"modal-input\");\n        const titleEl = document.getElementById(\"modal-title\");\n        const saveBtn = document.getElementById(\"modal-save\");\n        const cancelBtn = document.getElementById(\"modal-cancel\");\n        titleEl.textContent = title;\n        input.value = currentValue;\n        modal.classList.add(\"active\");\n        input.focus();\n        input.select();\n        const cleanup = () => {\n         modal.classList.remove(\"active\");\n         saveBtn.removeEventListener(\"click\", handleSave);\n         cancelBtn.removeEventListener(\"click\", handleCancel);\n         input.removeEventListener(\"keypress\", handleEnter);\n         modal.removeEventListener(\"click\", bgHandler);\n        };\n        const handleSave = () => {\n         if (input.value.trim()) {\n          onSave(input.value.trim());\n         }\n         cleanup();\n        };\n        const handleCancel = () => {\n         cleanup();\n        };\n        const handleEnter = (e) => {\n         if (e.key === \"Enter\") handleSave();\n        };\n        const bgHandler = (e) => {\n         if (e.target === modal) handleCancel();\n        };\n        saveBtn.addEventListener(\"click\", handleSave);\n        cancelBtn.addEventListener(\"click\", handleCancel);\n        input.addEventListener(\"keypress\", handleEnter);\n        modal.addEventListener(\"click\", bgHandler);\n       }\n       function challengeTheImmortal(message, onConfirm) {\n        const modal = document.getElementById(\"confirm-modal\");\n        const messageEl = document.getElementById(\"confirm-message\");\n        const deleteBtn = document.getElementById(\"confirm-delete\");\n        const cancelBtn = document.getElementById(\"confirm-cancel\");\n        messageEl.textContent = message;\n        modal.classList.add(\"active\");\n\t    const cleanup = () => {\n         modal.classList.remove(\"active\");\n         deleteBtn.removeEventListener(\"click\", handleConfirm);\n         cancelBtn.removeEventListener(\"click\", handleCancel);\n         modal.removeEventListener(\"click\", bgHandler);\n        };\n        const handleConfirm = () => {\n         onConfirm();\n         cleanup();\n        };\n        const handleCancel = () => {\n         cleanup();\n        };\n        const bgHandler = (e) => {\n         if (e.target === modal) handleCancel();\n        };\n        deleteBtn.addEventListener(\"click\", handleConfirm);\n        cancelBtn.addEventListener(\"click\", handleCancel);\n        modal.addEventListener(\"click\", bgHandler);\n       }\n       const pageTitleEl = document.getElementById(\"page-title\");\n       if (pageTitleEl) {\n        pageTitleEl.addEventListener(\"click\", () => {\n         showEditModal(\"Edit Title\", PAGE_STATE.title || DEFAULT_PAGE_STATE.title,\n          (newTitle) => {\n           PAGE_STATE.title = newTitle;\n           wieldThePower();\n          });\n        });\n       }\n       function editNodeName(id) {\n        if (!NODE_DATA[id]) return;\n        showEditModal(\"Edit Name\", NODE_DATA[id].name, (newName) => {\n         if (!NODE_DATA[id]) return;\n         pushUndo(\"edit node name\");\n         NODE_DATA[id].name = newName;\n         const nodeGroup = document.querySelector(`g[data-node-id=\"${id}\"]`);\n         if (nodeGroup) {\n          const label = nodeGroup.querySelector(\".node-label\");\n          if (label) label.textContent = newName;\n         }\n         if (currentNodeId === id) {\n          document.getElementById(\"node-name\").textContent = newName;\n         }\n        });\n       }\n       function editNodeIp(id) {\n        if (!NODE_DATA[id]) return;\n        showEditModal(\"Edit IP/Subtitle\", NODE_DATA[id].ip, (newIp) => {\n         if (!NODE_DATA[id]) return;\n         pushUndo(\"edit node ip\");\n         NODE_DATA[id].ip = newIp;\n         const nodeGroup = document.querySelector(`g[data-node-id=\"${id}\"]`);\n         if (nodeGroup) {\n          const sub = nodeGroup.querySelector(\".node-sub\");\n          if (sub) sub.textContent = newIp;\n         }\n         if (currentNodeId === id) {\n          document.getElementById(\"node-ip\").textContent = newIp;\n         }\n        });\n       }\n       function claimTheImmortal(id) {\n\t   if (isViewOnly()) return;\n        if (!NODE_DATA[id]) return;\n        currentNodeId = id;\n        currentEdgeId = null;\n        currentRectId = null;\n        currentTextId = null;\n        const data = NODE_DATA[id];\n        document.querySelectorAll(\".node-group\").forEach((n) => {\n         n.classList.toggle(\"active\", n.dataset.nodeId === id);\n        });\n        document.querySelectorAll(\".edge\").forEach((e) => {\n        const active = e.dataset.from === id || e.dataset.to === id;\n        e.classList.toggle(\"active\", active);\n       });\n       document.querySelectorAll(\".rect-group\").forEach((r) => r.classList.remove(\"active\"));\n       document.querySelectorAll(\".text-element\").forEach((t) => t.classList.remove(\"active\"));\n       document.getElementById(\"node-panel\").style.display = \"block\";\n        document.getElementById(\"edge-panel\").style.display = \"none\";\n        document.getElementById(\"text-panel\").style.display = \"none\";\n        const toolbar = document.getElementById(\"topology-toolbar\");\n        if (!topologyToolbarCollapsed) {\n         toolbar.style.display = \"flex\";\n        }\n        updateTopologyToolbarVisibility();\n        document.getElementById(\"node-name\").textContent = data.name;\n        document.getElementById(\"node-ip\").textContent = data.ip;\n        const fovSection = document.getElementById(\"fov-section\");\n        if (fovSection) {\n            if (hasCoverageZone(data.shape)) {\n              fovSection.style.display = \"block\";\n              const defaults = getCoverageDefaults(data.shape);\n              document.getElementById(\"fov-enabled\").checked = data.fovEnabled || false;\n              document.getElementById(\"fov-angle\").value = data.fovAngle || defaults.angle;\n              document.getElementById(\"fov-angle-value\").textContent = (data.fovAngle || defaults.angle) + \"°\";\n              document.getElementById(\"fov-distance\").value = data.fovDistance || defaults.distance;\n              document.getElementById(\"fov-distance-value\").textContent = data.fovDistance || defaults.distance;\n              document.getElementById(\"fov-inner-radius\").value = data.fovInnerRadius || 0;\n              document.getElementById(\"fov-inner-radius-value\").textContent = data.fovInnerRadius || 0;\n              document.getElementById(\"fov-rotation\").value = data.fovRotation || 0;\n              document.getElementById(\"fov-rotation-value\").textContent = (data.fovRotation || 0) + \"°\";\n              document.getElementById(\"fov-color\").value = data.fovColor || \"#f59e0b\";\n              document.getElementById(\"fov-opacity\").value = data.fovOpacity || 20;\n              document.getElementById(\"fov-opacity-value\").textContent = (data.fovOpacity || 20) + \"%\";\n              document.getElementById(\"fov-gradient\").checked = data.fovGradient || false;\n              document.getElementById(\"fov-border-color\").value = data.fovBorderColor || \"#f59e0b\";\n              document.getElementById(\"fov-border-width\").value = data.fovBorderWidth !== undefined ? data.fovBorderWidth : 2;\n              document.getElementById(\"fov-border-width-value\").textContent = data.fovBorderWidth !== undefined ? data.fovBorderWidth : 2;\n              document.getElementById(\"fov-border-style\").value = data.fovBorderStyle || \"solid\";\n              document.getElementById(\"fov-border-opacity\").value = data.fovBorderOpacity !== undefined ? data.fovBorderOpacity : 100;\n              document.getElementById(\"fov-border-opacity-value\").textContent = (data.fovBorderOpacity !== undefined ? data.fovBorderOpacity : 100) + \"%\";\n              document.getElementById(\"fov-label\").value = data.fovLabel || \"\";\n              document.getElementById(\"fov-label-position\").value = data.fovLabelPosition || \"center\";\n              document.getElementById(\"fov-label-size\").value = data.fovLabelSize || 14;\n              document.getElementById(\"fov-label-size-value\").textContent = (data.fovLabelSize || 14) + \"px\";\n              document.getElementById(\"fov-label-color\").value = data.fovLabelColor || \"#ffffff\";\n              document.getElementById(\"fov-label-bold\").checked = data.fovLabelBold || false;\n              document.getElementById(\"fov-label-bg\").checked = data.fovLabelBg || false;\n              document.getElementById(\"fov-label-bg-color\").value = data.fovLabelBgColor || \"#000000\";\n              document.getElementById(\"fov-label-offset-x\").value = data.fovLabelOffsetX || 0;\n              document.getElementById(\"fov-label-offset-x-value\").textContent = data.fovLabelOffsetX || 0;\n              document.getElementById(\"fov-label-offset-y\").value = data.fovLabelOffsetY || 0;\n              document.getElementById(\"fov-label-offset-y-value\").textContent = data.fovLabelOffsetY || 0;\n              document.getElementById(\"fov-animate\").checked = data.fovAnimate || false;\n              document.getElementById(\"fov-animation-type\").value = data.fovAnimationType || defaults.animationType;\n              document.getElementById(\"fov-sweep\").value = data.fovSweep || 120;\n              document.getElementById(\"fov-sweep-value\").textContent = (data.fovSweep || 120) + \"°\";\n              document.getElementById(\"fov-speed\").value = data.fovSpeed || 4;\n              document.getElementById(\"fov-speed-value\").textContent = (data.fovSpeed || 4) + \"s\";\n            } else {\n              fovSection.style.display = \"none\";\n            }\n          }\n        document.getElementById(\"node-mac\").textContent = data.mac || \"--\";\n        document.getElementById(\"node-rack\").textContent = data.rackUnit || \"--\";\n        document.getElementById(\"node-uheight\").textContent = (data.uHeight || \"1\") + \"U\";\n        document.getElementById(\"node-role\").textContent = data.role;\n        populateRackDropdown();\n        const assignedRackSelect = document.getElementById(\"node-assigned-rack\");\n        if (assignedRackSelect) {\n         assignedRackSelect.value = data.assignedRack || \"\";\n        }\n        const rackCapacitySelect = document.getElementById(\"node-rack-capacity\");\n        if (rackCapacitySelect) {\n         rackCapacitySelect.value = data.rackCapacity || \"42\";\n        }\n        const isRack = data.isRack === true;\n        const isAssignedToRack = !!data.assignedRack;\n        const assignedRackRow = document.getElementById(\"assigned-rack-row\");\n        const rackCapacityRow = document.getElementById(\"rack-capacity-row\");\n        const uheightRow = document.getElementById(\"uheight-row\");\n        if (assignedRackRow) assignedRackRow.style.display = isRack ? \"none\" : \"flex\";\n        if (rackCapacityRow) rackCapacityRow.style.display = isRack ? \"flex\" : \"none\";\n        if (uheightRow) uheightRow.style.display = isAssignedToRack ? \"flex\" : \"none\";\n\t\tconst delBtn = document.getElementById(\"delete-node-btn\");\n        if (delBtn) delBtn.textContent = isRack ? \"Delete Rack\" : \"Delete Node\";\n\t\tconst rackContentsSection = document.getElementById(\"rack-contents-section\");\n        const rackContentsList = document.getElementById(\"rack-contents-list\");\n        const rackContentsCount = document.getElementById(\"rack-contents-count\");\n        if (rackContentsSection && rackContentsList) {\n        if (isRack) {\n         const nodesInRack = Object.entries(NODE_DATA).filter(([nid, n]) => n.assignedRack === id);\n         rackContentsCount.textContent = nodesInRack.length;\n         if (nodesInRack.length > 0) {\n          rackContentsList.innerHTML = '';\n          nodesInRack.forEach(([nid, n]) => {\n           const div = document.createElement('div');\n           div.style.cssText = 'padding: 8px 4px; border-bottom: 1px solid var(--edge-main); cursor: pointer;';\n           div.onclick = () => claimTheImmortal(nid);\n           const nameSpan = document.createElement('span');\n           nameSpan.style.color = 'var(--text-main)';\n           nameSpan.textContent = n.name;\n           div.appendChild(nameSpan);\n           (n.tags || []).forEach(t => {\n            const tagSpan = document.createElement('span');\n            tagSpan.style.cssText = 'background: var(--accent); color: var(--bg); padding: 2px 6px; border-radius: 4px; font-size: 11px; margin-left: 4px;';\n            tagSpan.textContent = t;\n            div.appendChild(tagSpan);\n           });\n           rackContentsList.appendChild(div);\n          });\n         } else {\n          rackContentsList.innerHTML = '<div style=\"color: var(--text-soft); font-style: italic; padding: 8px 4px;\">No nodes assigned</div>';\n         }\n         rackContentsSection.style.display = \"block\";\n         } else {\n         rackContentsSection.style.display = \"none\";\n         }\n        }\n       const connectionsSection = document.getElementById(\"node-connections-section\");\n       const connectionsList = document.getElementById(\"node-connections-list\");\n       const connectionsCount = document.getElementById(\"node-connections-count\");\n       if (connectionsSection && connectionsList) {\n        const connectedEdges = (EDGE_DATA.list || []).filter(e => e.from === id || e.to === id);\n        connectionsCount.textContent = connectedEdges.length;\n        if (connectedEdges.length > 0) {\n         connectionsList.innerHTML = '';\n         connectedEdges.forEach(e => {\n          const isFrom = e.from === id;\n          const localPort = isFrom ? e.fromPort : e.toPort;\n          const remoteNodeId = isFrom ? e.to : e.from;\n          const remotePort = isFrom ? e.toPort : e.fromPort;\n          const remoteName = NODE_DATA[remoteNodeId]?.name || remoteNodeId;\n          const div = document.createElement('div');\n          div.style.cssText = 'padding: 6px 0; border-bottom: 1px solid var(--edge-main); display: flex; align-items: center; font-size: 13px;';\n          const localSpan = document.createElement('span');\n          localSpan.style.cssText = localPort ? 'color: var(--accent); font-family: monospace; cursor: pointer;' : 'color: var(--text-soft); cursor: pointer;';\n          localSpan.textContent = localPort || '-';\n          localSpan.title = localPort ? 'Click to view connection' : 'Click to set port';\n          localSpan.onclick = () => { if (localPort) { selectTheConnection(e.id); } else { const label = `Port on ${NODE_DATA[id]?.name || id}:`; const newVal = prompt(label, ''); if (newVal !== null && newVal !== '') { const isDupe = (EDGE_DATA.list || []).some(x => x.id !== e.id && ((x.from === id && x.fromPort === newVal) || (x.to === id && x.toPort === newVal))); if (isDupe && !confirm(`Warning: Port \"${newVal}\" is already used on ${NODE_DATA[id]?.name || id}. Use anyway?`)) return; if (isFrom) e.fromPort = newVal; else e.toPort = newVal; claimTheImmortal(id); } } };\n          const arrow = document.createElement('span');\n          arrow.style.cssText = 'color: var(--text-soft); margin: 0 8px;';\n          arrow.textContent = '↔';\n          const remoteSpan = document.createElement('span');\n          remoteSpan.style.cssText = 'color: var(--text-main); cursor: pointer;';\n          remoteSpan.textContent = remoteName;\n          remoteSpan.title = 'Click to view connection';\n          remoteSpan.onclick = () => { claimTheImmortal(remoteNodeId); focusOnSelected(); };\n          const remotePortSpan = document.createElement('span');\n          remotePortSpan.style.cssText = remotePort ? 'color: var(--accent); font-family: monospace; margin-left: 6px; cursor: pointer;' : 'color: var(--text-soft); margin-left: 6px; cursor: pointer;';\n          remotePortSpan.textContent = '(' + (remotePort || '-') + ')';\n          remotePortSpan.title = remotePort ? 'Click to view connection' : 'Click to set port';\n          remotePortSpan.onclick = () => { if (remotePort) { selectTheConnection(e.id); } else { const label = `Port on ${remoteName}:`; const newVal = prompt(label, ''); if (newVal !== null && newVal !== '') { const isDupe = (EDGE_DATA.list || []).some(x => x.id !== e.id && ((x.from === remoteNodeId && x.fromPort === newVal) || (x.to === remoteNodeId && x.toPort === newVal))); if (isDupe && !confirm(`Warning: Port \"${newVal}\" is already used on ${remoteName}. Use anyway?`)) return; if (isFrom) e.toPort = newVal; else e.fromPort = newVal; claimTheImmortal(id); } } };\n          div.appendChild(localSpan);\n          div.appendChild(arrow);\n          div.appendChild(remoteSpan);\n          div.appendChild(remotePortSpan);\n          connectionsList.appendChild(div);\n         });\n         connectionsSection.style.display = \"block\";\n        } else {\n         connectionsList.innerHTML = '<div style=\"color: var(--text-soft); font-style: italic;\">No connections</div>';\n         connectionsSection.style.display = \"block\";\n        }\n       }\n       document.getElementById(\"node-name\").onclick = () => editNodeName(id);\n        document.getElementById(\"node-ip\").onclick = () => editNodeIp(id);\n        document.getElementById(\"node-mac\").onclick = () => editNodeMac(id);\n        document.getElementById(\"node-rack\").onclick = () => editNodeRack(id);\n        document.getElementById(\"node-uheight\").onclick = () => editNodeUHeight(id);\n        document.getElementById(\"fov-enabled\").onchange = function() {\n  if (!currentNodeId) return;\n  pushUndo(\"toggle coverage zone\");\n  NODE_DATA[currentNodeId].fovEnabled = this.checked;\n  if (this.checked && !NODE_DATA[currentNodeId].fovColor) {\n    NODE_DATA[currentNodeId].fovColor = document.getElementById(\"fov-color\").value || \"#f59e0b\";\n  }\n  updateFovCone(currentNodeId);\n  updateZoneLegend();\n};\n        document.getElementById(\"fov-angle\").oninput = function() {\n          document.getElementById(\"fov-angle-value\").textContent = this.value + \"°\";\n          if (!currentNodeId) return;\n          NODE_DATA[currentNodeId].fovAngle = parseInt(this.value);\n          updateFovCone(currentNodeId);\n        };\n        document.getElementById(\"fov-distance\").oninput = function() {\n          document.getElementById(\"fov-distance-value\").textContent = this.value;\n          if (!currentNodeId) return;\n          NODE_DATA[currentNodeId].fovDistance = parseInt(this.value);\n          updateFovCone(currentNodeId);\n        };\n        document.getElementById(\"fov-rotation\").oninput = function() {\n          document.getElementById(\"fov-rotation-value\").textContent = this.value + \"°\";\n          if (!currentNodeId) return;\n          NODE_DATA[currentNodeId].fovRotation = parseInt(this.value);\n          updateFovCone(currentNodeId);\n        };\n        document.getElementById(\"fov-color\").oninput = function() {\n          if (!currentNodeId) return;\n          NODE_DATA[currentNodeId].fovColor = this.value;\n          updateFovCone(currentNodeId);\n          updateZoneLegend();\n        };\n        document.getElementById(\"fov-animate\").onchange = function() {\n          if (!currentNodeId) return;\n          pushUndo(\"toggle fov animation\");\n          NODE_DATA[currentNodeId].fovAnimate = this.checked;\n          updateFovCone(currentNodeId);\n        };\n        document.getElementById(\"fov-sweep\").oninput = function() {\n          document.getElementById(\"fov-sweep-value\").textContent = this.value + \"°\";\n          if (!currentNodeId) return;\n          NODE_DATA[currentNodeId].fovSweep = parseInt(this.value);\n          updateFovCone(currentNodeId);\n        };\n        document.getElementById(\"fov-speed\").oninput = function() {\n          document.getElementById(\"fov-speed-value\").textContent = this.value + \"s\";\n          if (!currentNodeId) return;\n          NODE_DATA[currentNodeId].fovSpeed = parseInt(this.value);\n          updateFovCone(currentNodeId);\n        };\n        document.getElementById(\"fov-inner-radius\").oninput = function() {\n          document.getElementById(\"fov-inner-radius-value\").textContent = this.value;\n          if (!currentNodeId) return;\n          NODE_DATA[currentNodeId].fovInnerRadius = parseInt(this.value);\n          updateFovCone(currentNodeId);\n        };\n        document.getElementById(\"fov-opacity\").oninput = function() {\n          document.getElementById(\"fov-opacity-value\").textContent = this.value + \"%\";\n          if (!currentNodeId) return;\n          NODE_DATA[currentNodeId].fovOpacity = parseInt(this.value);\n          updateFovCone(currentNodeId);\n        };\n        document.getElementById(\"fov-gradient\").onchange = function() {\n          if (!currentNodeId) return;\n          NODE_DATA[currentNodeId].fovGradient = this.checked;\n          updateFovCone(currentNodeId);\n        };\n        document.getElementById(\"fov-border-color\").oninput = function() {\n          if (!currentNodeId) return;\n          NODE_DATA[currentNodeId].fovBorderColor = this.value;\n          updateFovCone(currentNodeId);\n        };\n        document.getElementById(\"fov-border-width\").oninput = function() {\n          document.getElementById(\"fov-border-width-value\").textContent = this.value;\n          if (!currentNodeId) return;\n          NODE_DATA[currentNodeId].fovBorderWidth = parseInt(this.value);\n          updateFovCone(currentNodeId);\n        };\n        document.getElementById(\"fov-border-style\").onchange = function() {\n          if (!currentNodeId) return;\n          NODE_DATA[currentNodeId].fovBorderStyle = this.value;\n          updateFovCone(currentNodeId);\n        };\n        document.getElementById(\"fov-border-opacity\").oninput = function() {\n          document.getElementById(\"fov-border-opacity-value\").textContent = this.value + \"%\";\n          if (!currentNodeId) return;\n          NODE_DATA[currentNodeId].fovBorderOpacity = parseInt(this.value);\n          updateFovCone(currentNodeId);\n        };\n        document.getElementById(\"fov-label\").oninput = function() {\n          if (!currentNodeId) return;\n          NODE_DATA[currentNodeId].fovLabel = this.value;\n          updateFovCone(currentNodeId);\n        };\n        document.getElementById(\"fov-label-position\").onchange = function() {\n          if (!currentNodeId) return;\n          NODE_DATA[currentNodeId].fovLabelPosition = this.value;\n          updateFovCone(currentNodeId);\n        };\n        document.getElementById(\"fov-label-size\").oninput = function() {\n          document.getElementById(\"fov-label-size-value\").textContent = this.value + \"px\";\n          if (!currentNodeId) return;\n          NODE_DATA[currentNodeId].fovLabelSize = parseInt(this.value);\n          updateFovCone(currentNodeId);\n        };\n        document.getElementById(\"fov-label-color\").oninput = function() {\n          if (!currentNodeId) return;\n          NODE_DATA[currentNodeId].fovLabelColor = this.value;\n          updateFovCone(currentNodeId);\n        };\n        document.getElementById(\"fov-label-bold\").onchange = function() {\n          if (!currentNodeId) return;\n          NODE_DATA[currentNodeId].fovLabelBold = this.checked;\n          updateFovCone(currentNodeId);\n        };\n        document.getElementById(\"fov-label-bg\").onchange = function() {\n          if (!currentNodeId) return;\n          NODE_DATA[currentNodeId].fovLabelBg = this.checked;\n          updateFovCone(currentNodeId);\n        };\n        document.getElementById(\"fov-label-bg-color\").oninput = function() {\n          if (!currentNodeId) return;\n          NODE_DATA[currentNodeId].fovLabelBgColor = this.value;\n          updateFovCone(currentNodeId);\n        };\n        document.getElementById(\"fov-label-offset-x\").oninput = function() {\n          document.getElementById(\"fov-label-offset-x-value\").textContent = this.value;\n          if (!currentNodeId) return;\n          NODE_DATA[currentNodeId].fovLabelOffsetX = parseInt(this.value);\n          updateFovCone(currentNodeId);\n        };\n        document.getElementById(\"fov-label-offset-y\").oninput = function() {\n          document.getElementById(\"fov-label-offset-y-value\").textContent = this.value;\n          if (!currentNodeId) return;\n          NODE_DATA[currentNodeId].fovLabelOffsetY = parseInt(this.value);\n          updateFovCone(currentNodeId);\n        };\n        document.getElementById(\"fov-animation-type\").onchange = function() {\n          if (!currentNodeId) return;\n          NODE_DATA[currentNodeId].fovAnimationType = this.value;\n          updateFovCone(currentNodeId);\n        };\n        document.getElementById(\"fov-preset\").addEventListener(\"change\", function() {\n          if (this.value) {\n            applyZonePreset(this.value);\n            this.value = \"\";\n          }\n        });\n        document.getElementById(\"fov-save-preset\").addEventListener(\"click\", saveCustomZonePreset);\n        document.getElementById(\"fov-copy-style\").addEventListener(\"click\", function() {\n          if (currentNodeId && copyZoneStyle(currentNodeId)) {\n            alert(\"Zone style copied!\");\n          }\n        });\n        document.getElementById(\"fov-paste-style\").addEventListener(\"click\", function() {\n          if (currentNodeId && pasteZoneStyle(currentNodeId)) {\n            claimTheImmortal(currentNodeId);\n          }\n        });\n        document.getElementById(\"bulk-zone-copy\").addEventListener(\"click\", bulkCopyZoneStyle);\n        document.getElementById(\"bulk-zone-paste\").addEventListener(\"click\", bulkPasteZoneStyle);\n        document.getElementById(\"bulk-zone-toggle\").addEventListener(\"click\", bulkToggleZones);\n        document.getElementById(\"bulk-zone-copy-mobile\").addEventListener(\"click\", bulkCopyZoneStyle);\n        document.getElementById(\"bulk-zone-paste-mobile\").addEventListener(\"click\", bulkPasteZoneStyle);\n        document.getElementById(\"bulk-zone-toggle-mobile\").addEventListener(\"click\", bulkToggleZones);\n        const currentSize = savedSizes[id] || getDefaultSize();\n        document.getElementById(\"size-slider\").value = currentSize;\n        const currentRotation = NODE_DATA[id].rotation || 0;\n        document.getElementById(\"rotation-slider\").value = Math.max(-360, Math.min(360, currentRotation));\n        document.getElementById(\"rotation-value\").value = currentRotation;\n        const styleEntry = savedStyles[id] || {};\n        const resolvedStyles = resolveStylesEntry(styleEntry);\n        const scopeKey = currentStyleScope || \"all\";\n        const flatProps = [\"circleColor\", \"circleBorder\", \"titleColor\", \"titleFont\", \"titleSize\", \"subColor\", \"subFont\", \"subSize\", ];\n        const isFlat = flatProps.some((p) => Object.prototype.hasOwnProperty.call(styleEntry, p));\n        const scopedStyles = isFlat ? styleEntry : styleEntry[scopeKey] || {};\n        const circleColorInput = document.getElementById(\"circle-color\");\n        const titleColorInput = document.getElementById(\"title-color\");\n        const titleFontSelect = document.getElementById(\"title-font\");\n        const titleSizeInput = document.getElementById(\"title-size\");\n        const subColorInput = document.getElementById(\"sub-color\");\n        const subFontSelect = document.getElementById(\"sub-font\");\n        const subSizeInput = document.getElementById(\"sub-size\");\n        const shapeSelect = document.getElementById(\"shape-select\");\n        const scopeSelect = document.getElementById(\"style-scope\");\n        circleColorInput.value = scopedStyles.circleColor || resolvedStyles.circleColor || PAGE_STATE.nodeFill || \"#1e293b\";\n       const circleBorderInput = document.getElementById(\"circle-border\");\n       circleBorderInput.value = scopedStyles.circleBorder || resolvedStyles.circleBorder || PAGE_STATE.nodeStroke || \"#475569\";\n        titleColorInput.value = scopedStyles.titleColor || resolvedStyles.titleColor || PAGE_STATE.textMain || \"#e2e8f0\";\n        titleFontSelect.value = scopedStyles.titleFont || resolvedStyles.titleFont || \"system-ui, sans-serif\";\n        titleSizeInput.value = scopedStyles.titleSize || resolvedStyles.titleSize || 18;\n        subColorInput.value = scopedStyles.subColor || resolvedStyles.subColor || PAGE_STATE.textSoft || \"#94a3b8\";\n        subFontSelect.value = scopedStyles.subFont || resolvedStyles.subFont || \"system-ui, sans-serif\";\n        subSizeInput.value = scopedStyles.subSize || resolvedStyles.subSize || 13;\n        const hasWebIcon = resolvedStyles.icon && resolvedStyles.icon.library;\n        const customOpt = shapeSelect.querySelector('option[value=\"custom-icon\"]');\n        if (hasWebIcon) {\n         if (!customOpt) {\n          const opt = document.createElement('option');\n          opt.value = 'custom-icon';\n          opt.textContent = 'Custom Icon';\n          shapeSelect.insertBefore(opt, shapeSelect.firstChild);\n         }\n         shapeSelect.value = 'custom-icon';\n        } else {\n         if (customOpt) customOpt.remove();\n         shapeSelect.value = data.shape || \"circle\";\n        }\n        const layerSelect = document.getElementById(\"node-layer\");\n        if (layerSelect) {\n         layerSelect.value = data.layer || \"physical\";\n        }\n        scopeSelect.value = currentStyleScope || \"all\";\n        document.getElementById(\"title-offset-y\").value = scopedStyles.titleOffsetY || resolvedStyles.titleOffsetY || 0;\n        document.getElementById(\"title-offset-x\").value = scopedStyles.titleOffsetX || resolvedStyles.titleOffsetX || 0;\n        document.getElementById(\"sub-offset-y\").value = scopedStyles.subOffsetY || resolvedStyles.subOffsetY || 0;\n        document.getElementById(\"sub-offset-x\").value = scopedStyles.subOffsetX || resolvedStyles.subOffsetX || 0;\n      const pingOffsetXInput = document.getElementById(\"ping-offset-x\");\n      const pingOffsetYInput = document.getElementById(\"ping-offset-y\");\n      if (pingOffsetXInput && pingOffsetYInput) {\n      pingOffsetXInput.value =\n       (scopedStyles.pingOffsetX !== undefined\n         ? scopedStyles.pingOffsetX\n         : (resolvedStyles.pingOffsetX !== undefined\n             ? resolvedStyles.pingOffsetX\n             : 0));\n      pingOffsetYInput.value =\n       (scopedStyles.pingOffsetY !== undefined\n         ? scopedStyles.pingOffsetY\n         : (resolvedStyles.pingOffsetY !== undefined\n             ? resolvedStyles.pingOffsetY\n             : 0));\n      }\n        const tagEl = document.getElementById(\"node-tags\");\n        tagEl.innerHTML = \"\";\n        data.tags.forEach((tag, i) => {\n         const b = document.createElement(\"span\");\n         b.className = \"badge\";\n         const isIconTag = typeof tag === 'object' && tag.type === 'icon';\n         if (!isIconTag && typeof tag === 'string' && tag.toLowerCase().includes(\"wg\")) b.classList.add(\"wg\");\n         b.style.cursor = \"pointer\";\n         b.style.position = \"relative\";\n         const tagContent = document.createElement(\"span\");\n         if (isIconTag) {\n          b.classList.add(\"icon-badge\");\n          IconLibrary.getIcon(tag.library, tag.name).then(svgText => {\n           if (svgText) {\n            const parser = new DOMParser();\n            const doc = parser.parseFromString(svgText, 'image/svg+xml');\n            const svgEl = doc.querySelector('svg');\n            if (svgEl) {\n             svgEl.setAttribute('width', '16');\n             svgEl.setAttribute('height', '16');\n             tagContent.innerHTML = '';\n             tagContent.appendChild(svgEl);\n             const nameSpan = document.createElement('span');\n             nameSpan.textContent = tag.name;\n             nameSpan.style.marginLeft = '4px';\n             tagContent.appendChild(nameSpan);\n            }\n           }\n          });\n         } else {\n          tagContent.textContent = tag;\n          tagContent.addEventListener(\"click\", (e) => {\n           e.stopPropagation();\n           showEditModal(\"Edit Tag\", tag, (newTag) => {\n            if (newTag) {\n             pushUndo(\"edit tag\");\n             data.tags[i] = newTag;\n             claimTheImmortal(id);\n            }\n           });\n          });\n         }\n         const deleteTag = document.createElement(\"span\");\n         deleteTag.textContent = \" ✕\";\n         deleteTag.style.opacity = \"0.6\";\n         deleteTag.style.marginLeft = \"4px\";\n         deleteTag.style.fontSize = \"10px\";\n         deleteTag.addEventListener(\"click\", (e) => {\n          e.stopPropagation();\n          pushUndo(\"delete tag\");\n          data.tags.splice(i, 1);\n          claimTheImmortal(id);\n         });\n         b.append(tagContent, deleteTag);\n         tagEl.append(b);\n        });\n        const addTagBtn = document.createElement(\"span\");\n        addTagBtn.className = \"badge\";\n        addTagBtn.style.cursor = \"pointer\";\n        addTagBtn.style.opacity = \"0.6\";\n        addTagBtn.style.borderStyle = \"dashed\";\n        addTagBtn.textContent = \"+ Add Tag\";\n        addTagBtn.addEventListener(\"click\", () => {\n         showEditModal(\"Add Tag(s) : comma separated\", \"\",\n          (newTagStr) => {\n           if (newTagStr) {\n            pushUndo(\"add tags\");\n            const newTags = newTagStr.split(\",\").map((t) => t.trim()).filter((t) => t);\n            newTags.forEach((t) => data.tags.push(t));\n            claimTheImmortal(id);\n           }\n          });\n        });\n        tagEl.append(addTagBtn);\n        const notesEl = document.getElementById(\"node-notes\");\n        notesEl.innerHTML = \"\";\n        data.notes.forEach((note, i) => {\n         const li = document.createElement(\"li\");\n         const noteText = document.createElement(\"span\");\n         noteText.textContent = note;\n         noteText.style.flex = \"1\";\n         const deleteBtn = document.createElement(\"span\");\n         deleteBtn.className = \"delete-note\";\n         deleteBtn.textContent = \"✕\";\n         deleteBtn.addEventListener(\"click\", (e) => {\n          e.stopPropagation();\n          challengeTheImmortal(\"Are you sure you want to delete this note?\",\n           () => {\n            pushUndo(\"delete note\");\n            data.notes.splice(i, 1);\n            claimTheImmortal(id);\n           });\n         });\n         li.append(noteText, deleteBtn);\n         noteText.addEventListener(\"dblclick\", () => {\n          noteText.classList.add(\"editing\");\n          noteText.contentEditable = true;\n          noteText.focus();\n         });\n         noteText.addEventListener(\"blur\", () => {\n          noteText.classList.remove(\"editing\");\n          noteText.contentEditable = false;\n          pushUndo(\"edit note\");\n          data.notes[i] = noteText.textContent;\n         });\n         notesEl.append(li);\n        });\n        const addLineSelect = document.getElementById(\"add-line-select\");\n      addLineSelect.innerHTML = \"\";\n      const currentRack = data.assignedRack || \"\";\n      Object.entries(NODE_DATA).forEach(([nodeId, node]) => {\n      if (nodeId !== id) {\n      const nodeRack = node.assignedRack || \"\";\n      if (currentRack === nodeRack) {\n      const opt = document.createElement(\"option\");\n      opt.value = nodeId;\n      opt.textContent = node.name;\n      addLineSelect.appendChild(opt);\n      }\n      }\n      });\n        const pingEnabled = data.ping && data.ping.enabled;\n        document.getElementById('node-pingable').checked = pingEnabled;\n        document.getElementById('node-ping-options').style.display = pingEnabled ? 'block' : 'none';\n        if (data.ping) {\n         document.getElementById('node-ping-protocol').value = data.ping.protocol || 'http';\n         document.getElementById('node-custom-url').value = data.ping.customUrl || '';\n         document.getElementById('node-ping-timeout').value = data.ping.timeout || 3000;\n         document.getElementById('node-custom-url-container').style.display =\n          data.ping.protocol === 'custom' ? 'block' : 'none';\n         updatePingStatusDisplay(id);\n        }\n       }\n       function updatePingStatusDisplay(nodeId) {\n        const data = NODE_DATA[nodeId];\n        if (!data || !data.ping) return;\n        const statusEl = document.getElementById('node-ping-status');\n        const lastCheckEl = document.getElementById('node-ping-last-check');\n        const statusColors = {\n         online: 'var(--accent)',\n         offline: 'var(--danger)',\n         checking: '#f59e0b',\n         unknown: 'var(--text-soft)'\n        };\n        const statusTexts = {\n         online: '● Online',\n         offline: '● Offline',\n         checking: '● Checking...',\n         unknown: '● Unknown'\n        };\n        const statusText = statusTexts[data.ping.status] || statusTexts.unknown;\n\t\tstatusEl.textContent = data.ping.responseTime ? `${statusText} (${data.ping.responseTime}ms)` : statusText;\n        statusEl.style.color = statusColors[data.ping.status] || statusColors.unknown;\n        if (data.ping.lastCheck) {\n         const checkTime = new Date(data.ping.lastCheck);\n         lastCheckEl.textContent = `Last checked: ${checkTime.toLocaleTimeString()}`;\n        } else {\n         lastCheckEl.textContent = 'Never checked';\n        }\n       }\n       function selectTheConnection(id) {\n\t   if (isViewOnly()) return;\n        currentEdgeId = id;\n        currentNodeId = null;\n        currentRectId = null;\n        currentTextId = null;\n\t\tif (isMobileDevice()) {\n        const currentHeight = PAGE_STATE.mobileFooterHeight || 40;\n        if (currentHeight <= 20) {\n         PAGE_STATE.mobileFooterHeight = 80;\n         const root = document.documentElement;\n         root.style.setProperty(\"--mobile-footer-height\", \"80vh\");\n         const mobileFooterHeightInput = document.getElementById(\"mobile-footer-height\");\n         const mobileFooterHeightVal = document.getElementById(\"mobile-footer-height-val\");\n         if (mobileFooterHeightInput) mobileFooterHeightInput.value = 80;\n         if (mobileFooterHeightVal) mobileFooterHeightVal.textContent = \"80%\";\n        }\n       }\n        document.getElementById(\"node-panel\").style.display = \"none\";\n        document.getElementById(\"edge-panel\").style.display = \"block\";\n        document.getElementById(\"text-panel\").style.display = \"none\";\n       document.getElementById(\"rect-panel\").style.display = \"none\";\n       document.getElementById(\"topology-toolbar\").style.display = \"none\";\n       document.querySelectorAll(\".node-group\").forEach((n) => n.classList.remove(\"active\"));\n       document.querySelectorAll(\".rect-group\").forEach((r) => r.classList.remove(\"active\"));\n       document.querySelectorAll(\".text-element\").forEach((t) => t.classList.remove(\"active\"));\n       document.querySelectorAll(\".edge\").forEach((e) => {\n        e.classList.toggle(\"active\", e.dataset.edgeId === id);\n       });\n        const edge = EDGE_DATA.list.find((e) => e.id === id);\n        if (!edge) return;\n        const directionSymbols = {\n         none: \"⇄\",\n         forward: \"→\",\n         backward: \"←\",\n         both: \"↔\",\n        };\n        const dirSymbol = directionSymbols[edge.direction] || \"⇄\";\n        let titleText = \"Custom line\";\n        if (edge.from || edge.to) {\n         const fromName = edge.from ? NODE_DATA[edge.from]?.name || edge.from : \"\";\n         const toName = edge.to ? NODE_DATA[edge.to]?.name || edge.to : \"\";\n         titleText = `${fromName || \"?\"} ${dirSymbol} ${toName || \"?\"}`;\n        }\n        document.getElementById(\"edge-title\").textContent = titleText;\n        const widthInput = document.getElementById(\"edge-width\");\n        const colorInput = document.getElementById(\"edge-color\");\n        const directionSelect = document.getElementById(\"edge-direction\");\n        const lineStyleSelect = document.getElementById(\"edge-line-style\");\n        const routingSelect = document.getElementById(\"edge-routing\");\n        widthInput.value = edge.width;\n        colorInput.value = edge.color;\n        directionSelect.value = edge.direction || \"none\";\n        lineStyleSelect.value = edge.lineStyle || \"solid\";\n        routingSelect.value = edge.routing || \"curved\";\n        document.getElementById(\"edge-animate\").checked = edge.animate === true;\n        document.getElementById(\"edge-animation-style\").value = edge.animationStyle || \"\";\n        document.getElementById(\"edge-animation-speed\").value = edge.animationSpeed || \"\";\n        const fromPortInput = document.getElementById(\"edge-from-port\");\n        const toPortInput = document.getElementById(\"edge-to-port\");\n        const portFieldsFrom = document.getElementById(\"edge-port-fields\");\n        const portFieldsTo = document.getElementById(\"edge-port-fields-to\");\n        if (edge.type === \"custom\") {\n         if (portFieldsFrom) portFieldsFrom.style.display = \"none\";\n         if (portFieldsTo) portFieldsTo.style.display = \"none\";\n        } else {\n         if (portFieldsFrom) portFieldsFrom.style.display = \"flex\";\n         if (portFieldsTo) portFieldsTo.style.display = \"flex\";\n         if (fromPortInput) {\n          fromPortInput.value = edge.fromPort || \"\";\n          fromPortInput.onchange = () => updateEdgePortLabels(id);\n         }\n         if (toPortInput) {\n          toPortInput.value = edge.toPort || \"\";\n          toPortInput.onchange = () => updateEdgePortLabels(id);\n         }\n        }\n        const list = document.getElementById(\"edge-notes\");\n        list.innerHTML = \"\";\n        edge.notes.forEach((note, i) => {\n         const li = document.createElement(\"li\");\n         const txt = document.createElement(\"span\");\n         txt.textContent = note;\n         txt.style.flex = \"1\";\n         const del = document.createElement(\"span\");\n         del.className = \"delete-note\";\n         del.textContent = \"✕\";\n         del.addEventListener(\"click\", (e) => {\n          e.stopPropagation();\n         challengeTheImmortal(\"Delete this line note?\", () => {\n           pushUndo(\"delete edge note\");\n           edge.notes.splice(i, 1);\n           selectTheConnection(id);\n          });\n         });\n         txt.addEventListener(\"dblclick\", () => {\n          txt.classList.add(\"editing\");\n          txt.contentEditable = true;\n          txt.focus();\n         });\n        txt.addEventListener(\"blur\", () => {\n          txt.classList.remove(\"editing\");\n          txt.contentEditable = false;\n          pushUndo(\"edit edge note\");\n          edge.notes[i] = txt.textContent;\n         });\n         li.append(txt, del);\n         list.appendChild(li);\n       });\n       if (edge.type === \"custom\" && Array.isArray(edge.points)) {\n        forgeTheTopology();\n       }\n      }\n       window.addEventListener(\"resize\", () => {\n        forgeTheTopology();\n        if (currentEdgeId) {\n         selectTheConnection(currentEdgeId);\n        } else if (currentNodeId && NODE_DATA[currentNodeId]) {\n         claimTheImmortal(currentNodeId);\n        } else {\n         const availableNodes = Object.keys(NODE_DATA);\n         if (availableNodes.length > 0) {\n          claimTheImmortal(availableNodes[0]);\n         }\n        }\n       });\n       (function initZoomPan() {\n       const viewport = document.getElementById(\"canvas-viewport\");\n       const svg = document.getElementById(\"map\");\n       const hint = document.getElementById(\"canvas-hint\");\n       hint.style.cursor = \"pointer\";\n       let hintDismissed = false;\n       const dismissHint = () => { hintDismissed = true; hint.classList.remove(\"visible\"); };\n       hint.addEventListener(\"click\", dismissHint);\n       hint.addEventListener(\"touchend\", (e) => { e.preventDefault(); dismissHint(); });\n       setTimeout(() => {\n        if (hintDismissed) return;\n        hint.classList.add(\"visible\");\n        setTimeout(() => { if (!hintDismissed) hint.classList.remove(\"visible\"); }, 4000);\n       }, 1000);\n        viewport.addEventListener(\"wheel\",\n         (e) => {\n          e.preventDefault();\n          const rect = viewport.getBoundingClientRect();\n          const mouseX = (e.clientX - rect.left) / rect.width;\n          const mouseY = (e.clientY - rect.top) / rect.height;\n          const delta = e.deltaY > 0 ? 0.9 : 1.1;\n          zoomTo(canvasState.zoom * delta, mouseX, mouseY);\n         }, {\n          passive: false\n         });\n        let initialPinchDistance = 0;\n        let initialPinchZoom = 1;\n        let pinchCenter = {\n         x: 0.5,\n         y: 0.5\n        };\n        let threeFingerTapStart = 0;\n        viewport.addEventListener(\"touchstart\",\n         (e) => {\n          if (e.touches.length === 3) {\n           e.preventDefault();\n           threeFingerTapStart = Date.now();\n          }\n          if (e.touches.length === 2) {\n           e.preventDefault();\n           const touch1 = e.touches[0];\n           const touch2 = e.touches[1];\n           initialPinchDistance = Math.hypot(touch2.clientX - touch1.clientX, touch2.clientY - touch1.clientY);\n           initialPinchZoom = canvasState.zoom;\n           const rect = viewport.getBoundingClientRect();\n           const centerX = (touch1.clientX + touch2.clientX) / 2;\n           const centerY = (touch1.clientY + touch2.clientY) / 2;\n           pinchCenter.x = (centerX - rect.left) / rect.width;\n           pinchCenter.y = (centerY - rect.top) / rect.height;\n          }\n         }, {\n          passive: false\n         });\n        viewport.addEventListener(\"touchend\", (e) => {\n         if (e.touches.length === 0 && threeFingerTapStart > 0) {\n          const duration = Date.now() - threeFingerTapStart;\n          if (duration < 500) {\n           e.preventDefault();\n           undo();\n           if (navigator.vibrate) navigator.vibrate([50, 30, 50]);\n          }\n          threeFingerTapStart = 0;\n         }\n        }, { passive: false });\n        viewport.addEventListener(\"touchmove\",\n         (e) => {\n          if (e.touches.length === 2) {\n           e.preventDefault();\n           const touch1 = e.touches[0];\n           const touch2 = e.touches[1];\n           const currentDistance = Math.hypot(touch2.clientX - touch1.clientX, touch2.clientY - touch1.clientY);\n           if (initialPinchDistance > 0) {\n            const scale = currentDistance / initialPinchDistance;\n            const newZoom = initialPinchZoom * scale;\n            zoomTo(newZoom, pinchCenter.x, pinchCenter.y);\n           }\n          }\n         }, {\n          passive: false\n         });\n        let panStartViewX = 0;\n        let panStartViewY = 0;\n        let lastEmptyTapTime = 0;\n        let emptyTapTimeout = null;\n        let emptyTapMoved = false;\n        let emptyTapStartX = 0;\n        let emptyTapStartY = 0;\n        viewport.addEventListener(\"touchend\", (e) => {\n          if (currentView.mode !== \"rack\") return;\n          if (e.changedTouches.length !== 1) return;\n      const isNodeOrEdge = e.target.closest(\".node-group\") || e.target.closest(\".edge-group\");\n         if (isNodeOrEdge) return;\n          if (emptyTapMoved) {\n            emptyTapMoved = false;\n            return;\n          }\n          const currentTime = new Date().getTime();\n          const tapGap = currentTime - lastEmptyTapTime;\n          if (tapGap < 300 && tapGap > 0) {\n            e.preventDefault();\n            exitRack();\n            if (navigator.vibrate) {\n              navigator.vibrate(50);\n            }\n            lastEmptyTapTime = 0;\n            if (emptyTapTimeout) {\n              clearTimeout(emptyTapTimeout);\n              emptyTapTimeout = null;\n            }\n          } else {\n            lastEmptyTapTime = currentTime;\n            if (emptyTapTimeout) clearTimeout(emptyTapTimeout);\n            emptyTapTimeout = setTimeout(() => {\n              lastEmptyTapTime = 0;\n            }, 300);\n          }\n        }, { passive: false });\n        viewport.addEventListener(\"mousedown\", (e) => {\n         if (e.target.closest(\".draw-toolbar\") || e.target.closest(\".topology-toolbar\") || e.target.closest(\".legend-container\")) {\n          return;\n         }\n         if (freeDrawMode || rectDrawMode) {\n          return;\n         }\n      const isEmptySpace = e.target === svg || e.target === viewport || e.target.tagName === \"svg\";\n      const isEdgeElement = e.target.closest('.edge') || e.target.classList.contains('edge-edit-point') || e.target.dataset.edgeId;\n      const isTextElement = e.target.closest('.text-group') || e.target.closest('.text-element');\n      const isRectElement = e.target.closest('.rect-group');\n      if (!isEdgeElement && !isTextElement && !isRectElement && currentEdgeId) {\n      currentEdgeId = null;\n      forgeTheTopology();\n      }\n      if (!isRectElement && currentRectId) {\n      currentRectId = null;\n      document.getElementById(\"rect-panel\").style.display = \"none\";\n      forgeTheTopology();\n      }\n         if (isViewOnly()) {\n         document.body.classList.remove(\"view-only-inspect\");\n         document.getElementById(\"node-panel\").style.display = \"none\";\n         document.getElementById(\"edge-panel\").style.display = \"none\";\n        }\n         if (isEmptySpace && e.shiftKey && e.button === 0) {\n         if (isViewOnly()) return;\n         e.preventDefault();\n         startSelection(e);\n         return;\n        }\n        if (isEmptySpace || e.button === 2 || e.button === 1) {\n          e.preventDefault();\n          canvasState.isPanning = true;\n          canvasState.panStartX = e.clientX;\n          canvasState.panStartY = e.clientY;\n          panStartViewX = canvasState.panX;\n          panStartViewY = canvasState.panY;\n          viewport.classList.add(\"panning\");\n         }\n        });\n        viewport.addEventListener(\"touchstart\",\n         (e) => {\n          if (e.target.closest(\".draw-toolbar\") || e.target.closest(\".topology-toolbar\") || e.target.closest(\".legend-container\")) {\n           return;\n          }\n          if (freeDrawMode || rectDrawMode) {\n           return;\n          }\n      const isEmptySpace = e.target === svg || e.target === viewport || e.target.tagName === \"svg\";\n      const isEdgeElement = e.target.closest('.edge') || e.target.classList.contains('edge-edit-point') || e.target.dataset.edgeId;\n      const isTextElement = e.target.closest('.text-group') || e.target.closest('.text-element');\n      const isRectElement = e.target.closest('.rect-group');\n      if (!isEdgeElement && !isTextElement && !isRectElement && currentEdgeId) {\n      currentEdgeId = null;\n      forgeTheTopology();\n      }\n      if (!isRectElement && currentRectId) {\n      currentRectId = null;\n      document.getElementById(\"rect-panel\").style.display = \"none\";\n      forgeTheTopology();\n      }\n          if (isEmptySpace && e.touches.length === 1) {\n           e.preventDefault();\n           emptyTapMoved = false;\n           emptyTapStartX = e.touches[0].clientX;\n           emptyTapStartY = e.touches[0].clientY;\n           canvasState.isPanning = true;\n           canvasState.panStartX = e.touches[0].clientX;\n           canvasState.panStartY = e.touches[0].clientY;\n           panStartViewX = canvasState.panX;\n           panStartViewY = canvasState.panY;\n           viewport.classList.add(\"panning\");\n          }\n         }, {\n          passive: false\n         });\n        let panRAFPending = false;\n       let lastPanEvent = null;\n       document.addEventListener(\"mousemove\", (e) => {\n        if (isSelecting) {\n         updateSelection(e);\n         return;\n        }\n        if (!canvasState.isPanning) return;\n        lastPanEvent = e;\n        if (!panRAFPending) {\n         panRAFPending = true;\n         requestAnimationFrame(() => {\n          if (lastPanEvent && canvasState.isPanning) {\n           const dx = lastPanEvent.clientX - canvasState.panStartX;\n           const dy = lastPanEvent.clientY - canvasState.panStartY;\n           const rect = viewport.getBoundingClientRect();\n           const viewWidth = CANVAS_WIDTH / canvasState.zoom;\n           const viewHeight = CANVAS_HEIGHT / canvasState.zoom;\n           const canvasDx = (dx / rect.width) * viewWidth;\n           const canvasDy = (dy / rect.height) * viewHeight;\n           canvasState.panX = panStartViewX - canvasDx;\n           canvasState.panY = panStartViewY - canvasDy;\n           constrainPan();\n           updateViewBox();\n          }\n          panRAFPending = false;\n         });\n        }\n       });\n       document.addEventListener(\"touchmove\", (e) => {\n        if (!canvasState.isPanning || !e.touches[0]) return;\n        const touchX = e.touches[0].clientX;\n        const touchY = e.touches[0].clientY;\n        const moveDx = Math.abs(touchX - emptyTapStartX);\n        const moveDy = Math.abs(touchY - emptyTapStartY);\n        if (moveDx > 15 || moveDy > 15) {\n         emptyTapMoved = true;\n        }\n        if (!panRAFPending) {\n         panRAFPending = true;\n         requestAnimationFrame(() => {\n          if (canvasState.isPanning) {\n           const dx = touchX - canvasState.panStartX;\n           const dy = touchY - canvasState.panStartY;\n           const rect = viewport.getBoundingClientRect();\n           const viewWidth = CANVAS_WIDTH / canvasState.zoom;\n           const viewHeight = CANVAS_HEIGHT / canvasState.zoom;\n           const canvasDx = (dx / rect.width) * viewWidth;\n           const canvasDy = (dy / rect.height) * viewHeight;\n           canvasState.panX = panStartViewX - canvasDx;\n           canvasState.panY = panStartViewY - canvasDy;\n           constrainPan();\n           updateViewBox();\n          }\n          panRAFPending = false;\n         });\n        }\n       });\n        document.addEventListener(\"mouseup\", () => {\n         if (isSelecting) {\n          endSelection();\n         }\n         if (canvasState.isPanning) {\n          canvasState.isPanning = false;\n          viewport.classList.remove(\"panning\");\n         }\n        });\n        document.addEventListener(\"touchend\", () => {\n         if (canvasState.isPanning) {\n          canvasState.isPanning = false;\n          viewport.classList.remove(\"panning\");\n         }\n        });\n        document.addEventListener(\"keydown\", (e) => {\n         const isEditing = document.activeElement.tagName === \"INPUT\" || document.activeElement.tagName === \"TEXTAREA\" || document.activeElement.isContentEditable;\n         if (e.code === \"Space\" && !e.repeat && !isEditing) {\n          e.preventDefault();\n          canvasState.spacePressed = true;\n          viewport.style.cursor = \"grab\";\n         }\n        });\n        document.addEventListener(\"keyup\", (e) => {\n         if (e.code === \"Space\") {\n          canvasState.spacePressed = false;\n          viewport.style.cursor = \"\";\n         }\n        });\n        document.getElementById(\"zoom-in-btn\").addEventListener(\"click\", () => {\n         zoomTo(canvasState.zoom * 1.25, 0.5, 0.5);\n        });\n        document.getElementById(\"zoom-out-btn\").addEventListener(\"click\", () => {\n         zoomTo(canvasState.zoom * 0.8, 0.5, 0.5);\n        });\n        document.getElementById(\"zoom-fit-btn\").addEventListener(\"click\", fitToContent);\n        document.getElementById(\"zoom-reset-btn\").addEventListener(\"click\", resetView);\n        const minimapContainer = document.getElementById(\"minimap-container\");\n        const minimapSvg = document.getElementById(\"minimap\");\n        let minimapDragging = false;\n        minimapContainer.addEventListener(\"mousedown\", (e) => {\n         e.preventDefault();\n         minimapDragging = true;\n         updatePanFromMinimap(e);\n        });\n        minimapContainer.addEventListener(\"touchstart\",\n         (e) => {\n          e.preventDefault();\n          minimapDragging = true;\n          updatePanFromMinimapTouch(e);\n         }, {\n          passive: false\n         });\n        document.addEventListener(\"mousemove\", (e) => {\n         if (minimapDragging) {\n          updatePanFromMinimap(e);\n         }\n        });\n        document.addEventListener(\"touchmove\", (e) => {\n         if (minimapDragging && e.touches[0]) {\n          updatePanFromMinimapTouch(e);\n         }\n        });\n        document.addEventListener(\"mouseup\", () => {\n         minimapDragging = false;\n        });\n        document.addEventListener(\"touchend\", () => {\n         minimapDragging = false;\n        });\n        function updatePanFromMinimap(e) {\n         const rect = minimapContainer.getBoundingClientRect();\n         const x = (e.clientX - rect.left) / rect.width;\n         const y = (e.clientY - rect.top) / rect.height;\n         const viewWidth = CANVAS_WIDTH / canvasState.zoom;\n         const viewHeight = CANVAS_HEIGHT / canvasState.zoom;\n         canvasState.panX = x * CANVAS_WIDTH - viewWidth / 2;\n         canvasState.panY = y * CANVAS_HEIGHT - viewHeight / 2;\n         constrainPan();\n         updateViewBox();\n        }\n        function updatePanFromMinimapTouch(e) {\n         const rect = minimapContainer.getBoundingClientRect();\n         const touch = e.touches[0];\n         const x = (touch.clientX - rect.left) / rect.width;\n         const y = (touch.clientY - rect.top) / rect.height;\n         const viewWidth = CANVAS_WIDTH / canvasState.zoom;\n         const viewHeight = CANVAS_HEIGHT / canvasState.zoom;\n         canvasState.panX = x * CANVAS_WIDTH - viewWidth / 2;\n         canvasState.panY = y * CANVAS_HEIGHT - viewHeight / 2;\n         constrainPan();\n         updateViewBox();\n        }\n        document.addEventListener(\"keydown\", (e) => {\n         if (document.activeElement.tagName === \"INPUT\" || document.activeElement.tagName === \"TEXTAREA\") return;\n         if (\n          (e.key === \"+\" || e.key === \"=\") && (e.ctrlKey || e.metaKey)) {\n          e.preventDefault();\n          zoomTo(canvasState.zoom * 1.25, 0.5, 0.5);\n         } else if (e.key === \"-\" && (e.ctrlKey || e.metaKey)) {\n          e.preventDefault();\n          zoomTo(canvasState.zoom * 0.8, 0.5, 0.5);\n         } else if (e.key === \"0\" && (e.ctrlKey || e.metaKey)) {\n          e.preventDefault();\n          resetView();\n         }\n        });\n        setTimeout(() => {\n         fitToContent();\n        }, 100);\n       })();\n       const sizeSlider = document.getElementById(\"size-slider\");\n       const sizeValue = document.getElementById(\"size-value\");\n       const resetSizeBtn = document.getElementById(\"reset-size\");\n       sizeSlider.addEventListener(\"input\", () => {\n        const newSize = parseInt(sizeSlider.value, 10);\n        sizeValue.textContent = newSize;\n        pushUndo(\"resize node\");\n        savedSizes[currentNodeId] = newSize;\n        const nodeGroup = document.querySelector(`g[data-node-id=\"${currentNodeId}\"]`);\n        if (nodeGroup) {\n         const oldShape = nodeGroup.querySelector(\".node-circle\");\n         if (oldShape) oldShape.remove();\n         const newShape = createNodeShape(currentNodeId, newSize);\n         nodeGroup.insertBefore(newShape, nodeGroup.firstChild);\n         const styles = resolveStylesForNode(currentNodeId);\n         if (styles.circleColor) newShape.style.fill = styles.circleColor;\n      if (styles.circleBorder) newShape.style.stroke = styles.circleBorder;\n         const label = nodeGroup.querySelector(\".node-label\");\n         const sub = nodeGroup.querySelector(\".node-sub\");\n         if (label) {\n          label.setAttribute(\"y\", -newSize * 0.28);\n          const labelSize = styles.titleSize || newSize * 0.33;\n          label.style.fontSize = labelSize + \"px\";\n         }\n         if (sub) {\n          sub.setAttribute(\"y\", newSize * 0.4);\n          const subSize = styles.subSize || newSize * 0.24;\n          sub.style.fontSize = subSize + \"px\";\n         }\n       updatePingIndicator(currentNodeId);\n        }\n\t\tupdateMinimap();\n       });\n       resetSizeBtn.addEventListener(\"click\", () => {\n        pushUndo(\"reset size\");\n        delete savedSizes[currentNodeId];\n        const defaultSize = getDefaultSize();\n        sizeSlider.value = defaultSize;\n        sizeValue.textContent = defaultSize;\n        const nodeGroup = document.querySelector(`g[data-node-id=\"${currentNodeId}\"]`);\n        if (nodeGroup) {\n         const oldShape = nodeGroup.querySelector(\".node-circle\");\n         if (oldShape) oldShape.remove();\n         const shapeType = (NODE_DATA[currentNodeId] && NODE_DATA[currentNodeId].shape) || \"circle\";\n         const newShape = createNodeShape(currentNodeId, defaultSize);\n         nodeGroup.insertBefore(newShape, nodeGroup.firstChild);\n         const styles = resolveStylesForNode(currentNodeId);\n         if (styles.circleColor) newShape.style.fill = styles.circleColor;\n      if (styles.circleBorder) newShape.style.stroke = styles.circleBorder;\n         const label = nodeGroup.querySelector(\".node-label\");\n         const sub = nodeGroup.querySelector(\".node-sub\");\n         if (label) {\n          label.setAttribute(\"y\", -defaultSize * 0.28);\n          const labelSize = styles.titleSize || defaultSize * 0.33;\n          label.style.fontSize = labelSize + \"px\";\n         }\n         if (sub) {\n          sub.setAttribute(\"y\", defaultSize * 0.4);\n          const subSize = styles.subSize || defaultSize * 0.24;\n          sub.style.fontSize = subSize + \"px\";\n         }\n          updatePingIndicator(currentNodeId);\n        }\n\t\tupdateMinimap();\n       });\n       const rotationSlider = document.getElementById(\"rotation-slider\");\n       const rotationInput = document.getElementById(\"rotation-value\");\n       const resetRotationBtn = document.getElementById(\"reset-rotation\");\n       rotationSlider.addEventListener(\"input\", () => {\n         const newRotation = parseInt(rotationSlider.value, 10);\n         rotationInput.value = newRotation;\n         pushUndo(\"rotate node\");\n         NODE_DATA[currentNodeId].rotation = newRotation;\n         const nodeGroup = document.querySelector(`g[data-node-id=\"${currentNodeId}\"]`);\n         if (nodeGroup) {\n           const pos = savedPositions[currentNodeId] || { x: 0, y: 0 };\n           nodeGroup.setAttribute(\"transform\", `translate(${pos.x},${pos.y}) rotate(${newRotation})`);\n         }\n       });\n       rotationInput.addEventListener(\"input\", () => {\n         const newRotation = parseInt(rotationInput.value, 10) || 0;\n         rotationSlider.value = Math.max(-360, Math.min(360, newRotation));\n         pushUndo(\"rotate node\");\n         NODE_DATA[currentNodeId].rotation = newRotation;\n         const nodeGroup = document.querySelector(`g[data-node-id=\"${currentNodeId}\"]`);\n         if (nodeGroup) {\n           const pos = savedPositions[currentNodeId] || { x: 0, y: 0 };\n           nodeGroup.setAttribute(\"transform\", `translate(${pos.x},${pos.y}) rotate(${newRotation})`);\n         }\n       });\n       resetRotationBtn.addEventListener(\"click\", () => {\n         pushUndo(\"reset rotation\");\n         NODE_DATA[currentNodeId].rotation = 0;\n         rotationSlider.value = 0;\n         rotationInput.value = 0;\n         const nodeGroup = document.querySelector(`g[data-node-id=\"${currentNodeId}\"]`);\n         if (nodeGroup) {\n           const pos = savedPositions[currentNodeId] || { x: 0, y: 0 };\n           nodeGroup.setAttribute(\"transform\", `translate(${pos.x},${pos.y}) rotate(0)`);\n         }\n       });\n       const applyStyle = (property, value) => {\n        pushUndo(\"style change\");\n        const styleEntry = ensureStyleEntry(currentNodeId);\n        const scopeKey = currentStyleScope || \"all\";\n        if (!styleEntry[scopeKey]) styleEntry[scopeKey] = {};\n        styleEntry[scopeKey][property] = value;\n        const nodeGroup = document.querySelector(`g[data-node-id=\"${currentNodeId}\"]`);\n        if (!nodeGroup) return;\n        const shapeEl = nodeGroup.querySelector(\".node-circle\");\n        const label = nodeGroup.querySelector(\".node-label\");\n        const sub = nodeGroup.querySelector(\".node-sub\");\n        const isWebIcon = shapeEl && shapeEl.querySelector('svg');\n        if (property === \"circleColor\" && shapeEl) {\n         if (isWebIcon) {\n          shapeEl.querySelectorAll('svg, path, circle, rect, polygon, ellipse').forEach(el => el.style.fill = value);\n         } else {\n          shapeEl.style.fill = value;\n         }\n        } else if (property === \"circleBorder\" && shapeEl) {\n         if (isWebIcon) {\n          shapeEl.querySelectorAll('svg, path, circle, rect, polygon, ellipse').forEach(el => el.style.stroke = value);\n         } else {\n          shapeEl.style.stroke = value;\n         }\n        } else if (property === \"titleColor\" && label) label.style.fill = value;\n        else if (property === \"titleFont\" && label) label.style.fontFamily = value;\n        else if (property === \"titleSize\" && label) label.style.fontSize = value + \"px\";\n        else if (property === \"subColor\" && sub) sub.style.fill = value;\n        else if (property === \"subFont\" && sub) sub.style.fontFamily = value;\n        else if (property === \"subSize\" && sub) sub.style.fontSize = value + \"px\";\n       };\n\n       document.getElementById(\"circle-color\").addEventListener(\"input\", (e) => applyStyle(\"circleColor\", e.target.value));\n      document.getElementById(\"circle-border\").addEventListener(\"input\", (e) => applyStyle(\"circleBorder\", e.target.value));\n       document.getElementById(\"title-color\").addEventListener(\"input\", (e) => applyStyle(\"titleColor\", e.target.value));\n       document.getElementById(\"title-font\").addEventListener(\"change\", (e) => applyStyle(\"titleFont\", e.target.value));\n       document.getElementById(\"title-size\").addEventListener(\"input\", (e) => applyStyle(\"titleSize\", parseInt(e.target.value, 10)));\n       document.getElementById(\"sub-color\").addEventListener(\"input\", (e) => applyStyle(\"subColor\", e.target.value));\n       document.getElementById(\"sub-font\").addEventListener(\"change\", (e) => applyStyle(\"subFont\", e.target.value));\n       document.getElementById(\"sub-size\").addEventListener(\"input\", (e) => applyStyle(\"subSize\", parseInt(e.target.value, 10)));\n       document.getElementById(\"title-offset-y\").addEventListener(\"input\", (e) => {\n        applyStyle(\"titleOffsetY\", parseInt(e.target.value, 10) || 0);\n        forgeTheTopology();\n        if (currentNodeId) claimTheImmortal(currentNodeId);\n       });\n       document.getElementById(\"title-offset-x\").addEventListener(\"input\", (e) => {\n        applyStyle(\"titleOffsetX\", parseInt(e.target.value, 10) || 0);\n        forgeTheTopology();\n        if (currentNodeId) claimTheImmortal(currentNodeId);\n       });\n       document.getElementById(\"sub-offset-y\").addEventListener(\"input\", (e) => {\n        applyStyle(\"subOffsetY\", parseInt(e.target.value, 10) || 0);\n        forgeTheTopology();\n        if (currentNodeId) claimTheImmortal(currentNodeId);\n       });\n       document.getElementById(\"sub-offset-x\").addEventListener(\"input\", (e) => {\n        applyStyle(\"subOffsetX\", parseInt(e.target.value, 10) || 0);\n        forgeTheTopology();\n        if (currentNodeId) claimTheImmortal(currentNodeId);\n       });\n      document.getElementById(\"ping-offset-x\").addEventListener(\"input\", (e) => {\n      applyStyle(\"pingOffsetX\", parseInt(e.target.value, 10) || 0);\n      updatePingIndicator(currentNodeId);\n      if (currentNodeId) claimTheImmortal(currentNodeId);\n      });\n      document.getElementById(\"ping-offset-y\").addEventListener(\"input\", (e) => {\n      applyStyle(\"pingOffsetY\", parseInt(e.target.value, 10) || 0);\n      updatePingIndicator(currentNodeId);\n      if (currentNodeId) claimTheImmortal(currentNodeId);\n      });\n       document.getElementById(\"reset-styles\").addEventListener(\"click\", () => {\n        delete savedStyles[currentNodeId];\n        forgeTheTopology();\n        claimTheImmortal(currentNodeId);\n       });\n       document.getElementById(\"style-scope\").addEventListener(\"change\", (e) => {\n        currentStyleScope = e.target.value || \"all\";\n        claimTheImmortal(currentNodeId);\n       });\n       document.getElementById(\"shape-select\").addEventListener(\"change\", (e) => {\n        if (!currentNodeId || !NODE_DATA[currentNodeId]) return;\n        const shape = e.target.value || \"circle\";\n        if (shape === 'custom-icon') return;\n        pushUndo(\"change shape\");\n        NODE_DATA[currentNodeId].shape = shape;\n        const fovSection = document.getElementById(\"fov-section\");\n        if (fovSection) {\n          const oldShape = NODE_DATA[currentNodeId].shape;\n          fovSection.style.display = hasCoverageZone(shape) ? \"block\" : \"none\";\n          if (hasCoverageZone(shape) && !hasCoverageZone(oldShape)) {\n            const defaults = getCoverageDefaults(shape);\n            document.getElementById(\"fov-angle\").value = defaults.angle;\n            document.getElementById(\"fov-angle-value\").textContent = defaults.angle + \"°\";\n            document.getElementById(\"fov-distance\").value = defaults.distance;\n            document.getElementById(\"fov-distance-value\").textContent = defaults.distance;\n            document.getElementById(\"fov-animation-type\").value = defaults.animationType;\n            NODE_DATA[currentNodeId].fovAngle = defaults.angle;\n            NODE_DATA[currentNodeId].fovDistance = defaults.distance;\n            NODE_DATA[currentNodeId].fovAnimationType = defaults.animationType;\n          }\n        }\n        if (savedStyles[currentNodeId]) {\n         Object.keys(savedStyles[currentNodeId]).forEach(scope => {\n          if (savedStyles[currentNodeId][scope] && savedStyles[currentNodeId][scope].icon) {\n           delete savedStyles[currentNodeId][scope].icon;\n          }\n         });\n        }\n        const customOpt = e.target.querySelector('option[value=\"custom-icon\"]');\n        if (customOpt) customOpt.remove();\n        forgeTheTopology();\n       });\n       const addNoteBtn = document.getElementById(\"add-note-btn\");\n       const noteInput = document.getElementById(\"new-note-input\");\n       addNoteBtn.addEventListener(\"click\", () => {\n        const newNote = noteInput.value.trim();\n        if (newNote && currentNodeId && NODE_DATA[currentNodeId]) {\n         pushUndo(\"add note\");\n         NODE_DATA[currentNodeId].notes.push(newNote);\n         claimTheImmortal(currentNodeId);\n         noteInput.value = \"\";\n        }\n       });\n       noteInput.addEventListener(\"keypress\", (e) => {\n        if (e.key === \"Enter\") {\n         addNoteBtn.click();\n        }\n       });\n       document.getElementById('node-pingable').addEventListener('change', (e) => {\n        if (!currentNodeId || !NODE_DATA[currentNodeId]) return;\n        if (!NODE_DATA[currentNodeId].ping) {\n         NODE_DATA[currentNodeId].ping = {\n          enabled: false,\n          protocol: 'http',\n          customUrl: '',\n          timeout: 3000,\n          status: 'unknown',\n          lastCheck: null\n         };\n        }\n        NODE_DATA[currentNodeId].ping.enabled = e.target.checked;\n        document.getElementById('node-ping-options').style.display = e.target.checked ? 'block' : 'none';\n        forgeTheTopology();\n       });\n       document.getElementById('node-ping-protocol').addEventListener('change', (e) => {\n        if (!currentNodeId || !NODE_DATA[currentNodeId].ping) return;\n        NODE_DATA[currentNodeId].ping.protocol = e.target.value;\n        document.getElementById('node-custom-url-container').style.display =\n         e.target.value === 'custom' ? 'block' : 'none';\n       });\n       document.getElementById('node-custom-url').addEventListener('change', (e) => {\n        if (!currentNodeId || !NODE_DATA[currentNodeId].ping) return;\n        NODE_DATA[currentNodeId].ping.customUrl = e.target.value.trim();\n       });\n       document.getElementById('node-ping-timeout').addEventListener('change', (e) => {\n        if (!currentNodeId || !NODE_DATA[currentNodeId].ping) return;\n        NODE_DATA[currentNodeId].ping.timeout = parseInt(e.target.value) || 3000;\n       });\n       document.getElementById('check-ping-now').addEventListener('click', () => {\n        if (!currentNodeId) return;\n        checkNodeStatus(currentNodeId);\n       });\n       document.getElementById(\"edge-width\").addEventListener(\"input\", () => {\n        if (!currentEdgeId) return;\n        const edge = EDGE_DATA.list.find((e) => e.id === currentEdgeId);\n        if (!edge) return;\n        const v = parseInt(document.getElementById(\"edge-width\").value, 10);\n        if (Number.isNaN(v) || v <= 0) return;\n        pushUndo(\"edit edge\");\n        edge.width = v;\n        const el = document.querySelector(`.edge[data-edge-id=\"${currentEdgeId}\"]`);\n        if (el) el.style.strokeWidth = v;\n       });\n       document.getElementById(\"edge-color\").addEventListener(\"input\", () => {\n        if (!currentEdgeId) return;\n        const edge = EDGE_DATA.list.find((e) => e.id === currentEdgeId);\n        if (!edge) return;\n        const color = document.getElementById(\"edge-color\").value;\n        pushUndo(\"edit edge\");\n        edge.color = color;\n        const el = document.querySelector(`.edge[data-edge-id=\"${currentEdgeId}\"]`);\n        if (el) el.style.stroke = color;\n        forgeTheLegend();\n\t\tupdateZoneLegend();\n       });\n       document.getElementById(\"edge-direction\").addEventListener(\"change\", () => {\n        if (!currentEdgeId) return;\n        const edge = EDGE_DATA.list.find((e) => e.id === currentEdgeId);\n        if (!edge) return;\n        pushUndo(\"edit edge\");\n        edge.direction = document.getElementById(\"edge-direction\").value;\n        forgeTheTopology();\n        selectTheConnection(currentEdgeId);\n       });\n       document.getElementById(\"edge-line-style\").addEventListener(\"change\", () => {\n        if (!currentEdgeId) return;\n        const edge = EDGE_DATA.list.find((e) => e.id === currentEdgeId);\n        if (!edge) return;\n        pushUndo(\"edit edge\");\n        edge.lineStyle = document.getElementById(\"edge-line-style\").value;\n        forgeTheTopology();\n        selectTheConnection(currentEdgeId);\n       });\n       document.getElementById(\"edge-routing\").addEventListener(\"change\", () => {\n        if (!currentEdgeId) return;\n        const edge = EDGE_DATA.list.find((e) => e.id === currentEdgeId);\n        if (!edge) return;\n        pushUndo(\"edit edge routing\");\n        edge.routing = document.getElementById(\"edge-routing\").value;\n        forgeTheTopology();\n        selectTheConnection(currentEdgeId);\n       });\n       document.getElementById(\"edge-animate\").addEventListener(\"change\", (e) => {\n        if (!currentEdgeId) return;\n        const edge = EDGE_DATA.list.find((ed) => ed.id === currentEdgeId);\n        if (!edge) return;\n        pushUndo(\"edit edge animate\");\n        edge.animate = e.target.checked;\n        forgeTheTopology();\n        selectTheConnection(currentEdgeId);\n       });\n       document.getElementById(\"animation-style-select\").addEventListener(\"change\", (e) => {\n        PAGE_STATE.animationStyle = e.target.value;\n        if (PAGE_STATE.animateConnections) forgeTheTopology();\n       });\n       document.getElementById(\"animation-direction-select\").addEventListener(\"change\", (e) => {\n        PAGE_STATE.animationDirection = e.target.value;\n        if (PAGE_STATE.animateConnections) forgeTheTopology();\n       });\n       document.getElementById(\"animation-speed-select\").addEventListener(\"change\", (e) => {\n        PAGE_STATE.animationSpeed = parseFloat(e.target.value);\n        if (PAGE_STATE.animateConnections) forgeTheTopology();\n       });\n       document.getElementById(\"edge-animation-style\").addEventListener(\"change\", (e) => {\n        if (!currentEdgeId) return;\n        const edge = EDGE_DATA.list.find((ed) => ed.id === currentEdgeId);\n        if (!edge) return;\n        pushUndo(\"edit edge animation style\");\n        edge.animationStyle = e.target.value || \"\";\n        if (PAGE_STATE.animateConnections || edge.animate === true) forgeTheTopology();\n        selectTheConnection(currentEdgeId);\n       });\n       document.getElementById(\"edge-animation-speed\").addEventListener(\"change\", (e) => {\n        if (!currentEdgeId) return;\n        const edge = EDGE_DATA.list.find((ed) => ed.id === currentEdgeId);\n        if (!edge) return;\n        pushUndo(\"edit edge animation speed\");\n        edge.animationSpeed = e.target.value || \"\";\n        if (PAGE_STATE.animateConnections || edge.animate === true) forgeTheTopology();\n        selectTheConnection(currentEdgeId);\n       });\n       const addEdgeNoteBtn = document.getElementById(\"add-edge-note\");\n       const newEdgeNoteInput = document.getElementById(\"new-edge-note\");\n       addEdgeNoteBtn.addEventListener(\"click\", () => {\n        const txt = newEdgeNoteInput.value.trim();\n        if (!txt || !currentEdgeId) return;\n        const edge = EDGE_DATA.list.find((e) => e.id === currentEdgeId);\n        if (!edge) return;\n\t\tpushUndo(\"add edge note\");\n        edge.notes.push(txt);\n        newEdgeNoteInput.value = \"\";\n        selectTheConnection(currentEdgeId);\n       });\n       newEdgeNoteInput.addEventListener(\"keypress\", (e) => {\n        if (e.key === \"Enter\") {\n         addEdgeNoteBtn.click();\n        }\n       });\n      function selectTheRect(id) {\n\t  if (isViewOnly()) return;\n      currentRectId = id;\n      currentNodeId = null;\n      currentEdgeId = null;\n      currentTextId = null;\n      document.getElementById(\"node-panel\").style.display = \"none\";\n      document.getElementById(\"edge-panel\").style.display = \"none\";\n      document.getElementById(\"text-panel\").style.display = \"none\";\n      document.getElementById(\"rect-panel\").style.display = \"block\";\n      document.getElementById(\"topology-toolbar\").style.display = \"none\";\n      document.querySelectorAll(\".node-group\").forEach(n => n.classList.remove(\"active\"));\n      document.querySelectorAll(\".edge\").forEach(e => e.classList.remove(\"active\"));\n      document.querySelectorAll(\".text-element\").forEach(t => t.classList.remove(\"active\"));\n      document.querySelectorAll(\".rect-group\").forEach(r => r.classList.toggle(\"active\", r.dataset.rectId === id));\n      const rect = RECT_DATA.list.find(r => r.id === id);\n      if (!rect) return;\n      document.getElementById(\"rect-title\").textContent = \"\";\n      document.getElementById(\"rect-color\").value = rect.color || \"#f97316\";\n      document.getElementById(\"rect-border-color\").value = rect.borderColor || rect.color || \"#f97316\";\n      document.getElementById(\"rect-border-width\").value = rect.borderWidth !== undefined ? rect.borderWidth : 2;\n      document.getElementById(\"rect-style-select\").value = rect.style || \"filled\";\n      document.getElementById(\"rect-rotation\").value = Math.max(-360, Math.min(360, rect.rotation || 0));\n      document.getElementById(\"rect-rotation-value\").value = rect.rotation || 0;\n      document.getElementById(\"rect-fill-row\").style.display = (rect.style === \"outlined\") ? \"none\" : \"flex\";\n      const list = document.getElementById(\"rect-notes\");\n      list.innerHTML = \"\";\n      (rect.notes || []).forEach((note, i) => {\n      const li = document.createElement(\"li\");\n      const txt = document.createElement(\"span\");\n      txt.textContent = note;\n      txt.style.flex = \"1\";\n      const del = document.createElement(\"span\");\n      del.className = \"delete-note\";\n      del.textContent = \"✕\";\n      del.addEventListener(\"click\", (e) => {\n      e.stopPropagation();\n      challengeTheImmortal(\"Delete this note?\", () => {\n        pushUndo(\"delete zone note\");\n        rect.notes.splice(i, 1);\n        selectTheRect(id);\n      });\n      });\n      li.appendChild(txt);\n      li.appendChild(del);\n      list.appendChild(li);\n      });\n      forgeTheTopology();\n      }\n      document.getElementById(\"rect-color\").addEventListener(\"input\", () => {\n      if (!currentRectId) return;\n      const rect = RECT_DATA.list.find(r => r.id === currentRectId);\n      if (!rect) return;\n      pushUndo(\"edit zone\");\n      rect.color = document.getElementById(\"rect-color\").value;\n      forgeTheTopology();\n      });\n      document.getElementById(\"rect-border-color\").addEventListener(\"input\", () => {\n      if (!currentRectId) return;\n      const rect = RECT_DATA.list.find(r => r.id === currentRectId);\n      if (!rect) return;\n      pushUndo(\"edit zone\");\n      rect.borderColor = document.getElementById(\"rect-border-color\").value;\n      forgeTheTopology();\n      });\n      document.getElementById(\"rect-border-width\").addEventListener(\"input\", () => {\n      if (!currentRectId) return;\n      const rect = RECT_DATA.list.find(r => r.id === currentRectId);\n      if (!rect) return;\n      pushUndo(\"edit zone\");\n      rect.borderWidth = parseInt(document.getElementById(\"rect-border-width\").value) || 2;\n      forgeTheTopology();\n      });\n      document.getElementById(\"rect-style-select\").addEventListener(\"change\", () => {\n      if (!currentRectId) return;\n      const rect = RECT_DATA.list.find(r => r.id === currentRectId);\n      if (!rect) return;\n      pushUndo(\"edit zone\");\n      rect.style = document.getElementById(\"rect-style-select\").value;\n      forgeTheTopology();\n      selectTheRect(currentRectId);\n      });\n\t  document.getElementById(\"rect-rotation\").addEventListener(\"input\", () => {\n        if (!currentRectId) return;\n        const rect = RECT_DATA.list.find(r => r.id === currentRectId);\n        if (!rect) return;\n        pushUndo(\"rotate zone\");\n        rect.rotation = parseInt(document.getElementById(\"rect-rotation\").value) || 0;\n        document.getElementById(\"rect-rotation-value\").value = rect.rotation;\n        forgeTheTopology();\n      });\n      document.getElementById(\"rect-rotation-value\").addEventListener(\"input\", () => {\n        if (!currentRectId) return;\n        const rect = RECT_DATA.list.find(r => r.id === currentRectId);\n        if (!rect) return;\n        pushUndo(\"rotate zone\");\n        const val = parseInt(document.getElementById(\"rect-rotation-value\").value) || 0;\n        rect.rotation = val;\n        document.getElementById(\"rect-rotation\").value = Math.max(-360, Math.min(360, val));\n        forgeTheTopology();\n      });\n      document.getElementById(\"add-rect-note\").addEventListener(\"click\", () => {\n      const input = document.getElementById(\"new-rect-note\");\n      const txt = input.value.trim();\n      if (!txt || !currentRectId) return;\n      const rect = RECT_DATA.list.find(r => r.id === currentRectId);\n      if (!rect) return;\n      if (!rect.notes) rect.notes = [];\n\t  pushUndo(\"add zone note\");\n      rect.notes.push(txt);\n      input.value = \"\";\n      selectTheRect(currentRectId);\n      });\n      document.getElementById(\"new-rect-note\").addEventListener(\"keypress\", (e) => {\n      if (e.key === \"Enter\") {\n      document.getElementById(\"add-rect-note\").click();\n      }\n      });\n      document.getElementById(\"delete-rect\").addEventListener(\"click\", () => {\n      if (!currentRectId) return;\n      challengeTheImmortal(\"Delete this box?\", () => {\n      pushUndo(\"delete zone\");\n      RECT_DATA.list = RECT_DATA.list.filter(r => r.id !== currentRectId);\n      currentRectId = null;\n      document.getElementById(\"rect-panel\").style.display = \"none\";\n      forgeTheTopology();\n      });\n      });\n       document.getElementById(\"delete-edge\").addEventListener(\"click\", () => {\n        if (!currentEdgeId) return;\n        challengeTheImmortal(\"Are you sure you want to delete this line?\",\n         () => {\n          pushUndo(\"delete edge\");\n          EDGE_DATA.list = EDGE_DATA.list.filter(\n           (e) => e.id !== currentEdgeId);\n          currentEdgeId = null;\n          forgeTheTopology();\n          const availableNodes = Object.keys(NODE_DATA);\n          if (availableNodes.length > 0) {\n           claimTheImmortal(availableNodes[0]);\n          } else {\n           document.getElementById(\"node-panel\").style.display = \"none\";\n           document.getElementById(\"edge-panel\").style.display = \"none\";\n           document.getElementById(\"topology-toolbar\", ).style.display = \"none\";\n          }\n         });\n       });\n       document.getElementById(\"add-line-btn\").addEventListener(\"click\", () => {\n        if (!currentNodeId) return;\n        const select = document.getElementById(\"add-line-select\");\n        const directionSelect = document.getElementById(\"add-line-direction\");\n        const colorInput = document.getElementById(\"add-line-color\");\n        const routingSelect = document.getElementById(\"add-line-routing\");\n        const targetId = select.value;\n        if (!targetId || targetId === currentNodeId) return;\n        const direction = directionSelect.value || \"none\";\n        const lineColor = colorInput.value || \"#475569\";\n        const routing = routingSelect.value || PAGE_STATE.defaultEdgeRouting || \"orthogonal\";\n        const newId = `${currentNodeId}-${targetId}-${Date.now()}`;\n        const newEdge = {\n         id: newId,\n         from: currentNodeId,\n         to: targetId,\n         width: 4,\n         color: lineColor,\n         direction: direction,\n         routing: routing,\n         type: \"main\",\n         notes: [],\n         fromPort: \"\",\n         toPort: \"\",\n        };\n        pushUndo(\"add edge\");\n        EDGE_DATA.list.push(newEdge);\n        forgeTheTopology();\n        claimTheImmortal(currentNodeId);\n       });\n       let rectStartPoint = null;\n       let rectPreviewEl = null;\n       let rectStyle = \"filled\";\n       let freeDrawPoints = [];\n       let freeDrawPolylineEl = null;\n       let freeDrawPointEls = [];\n       const drawToggleBtn = document.getElementById(\"draw-toggle\");\n       const drawUndoBtn = document.getElementById(\"draw-undo\");\n       const drawColorInput = document.getElementById(\"draw-color\");\n       const drawStyleSelect = document.getElementById(\"draw-style\");\n       const drawArrowSelect = document.getElementById(\"draw-arrow\");\n       const svgMap = document.getElementById(\"map\");\n       function updateFreeDrawGraphics() {\n        const ns = \"http://www.w3.org/2000/svg\";\n        const svg = svgMap;\n        if (!freeDrawPolylineEl && freeDrawPoints.length > 0) {\n         freeDrawPolylineEl = document.createElementNS(ns, \"polyline\");\n         freeDrawPolylineEl.classList.add(\"edge\", \"free-preview\");\n         freeDrawPolylineEl.setAttribute(\"fill\", \"none\");\n         svg.appendChild(freeDrawPolylineEl);\n        }\n        if (freeDrawPolylineEl) {\n         if (freeDrawPoints.length === 0) {\n          freeDrawPolylineEl.remove();\n          freeDrawPolylineEl = null;\n         } else {\n          const ptsStr = freeDrawPoints.map((p) => `${p.x},${p.y}`).join(\" \");\n          freeDrawPolylineEl.setAttribute(\"points\", ptsStr);\n          freeDrawPolylineEl.style.stroke = drawColorInput.value || \"#475569\";\n          freeDrawPolylineEl.style.strokeWidth = 3;\n          const lineStyle = drawStyleSelect.value || \"solid\";\n          if (lineStyle === \"dashed\") {\n           freeDrawPolylineEl.style.strokeDasharray = \"10,5\";\n          } else if (lineStyle === \"dotted\") {\n           freeDrawPolylineEl.style.strokeDasharray = \"2,4\";\n          } else {\n           freeDrawPolylineEl.style.strokeDasharray = \"none\";\n          }\n         }\n        }\n        freeDrawPointEls.forEach((el) => el.remove());\n        freeDrawPointEls = [];\n        freeDrawPoints.forEach((p, idx) => {\n         const c = document.createElementNS(ns, \"circle\");\n         c.classList.add(\"free-point\");\n         c.setAttribute(\"cx\", p.x);\n         c.setAttribute(\"cy\", p.y);\n         c.setAttribute(\"r\", 5);\n         c.dataset.index = String(idx);\n         c.addEventListener(\"mousedown\", (e) => {\n          if (!freeDrawMode) return;\n          e.preventDefault();\n          e.stopPropagation();\n          let dragging = true;\n          const svgEl = svgMap;\n          const moveHandler = (ev) => {\n           if (!dragging) return;\n           const pt = svgEl.createSVGPoint();\n           pt.x = ev.clientX;\n           pt.y = ev.clientY;\n           const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n           const i = parseInt(c.dataset.index, 10);\n           if (Number.isNaN(i) || !freeDrawPoints[i]) return;\n           freeDrawPoints[i].x = svgP.x;\n           freeDrawPoints[i].y = svgP.y;\n           updateFreeDrawGraphics();\n          };\n          const upHandler = () => {\n           dragging = false;\n           document.removeEventListener(\"mousemove\", moveHandler);\n           document.removeEventListener(\"mouseup\", upHandler);\n          };\n          document.addEventListener(\"mousemove\", moveHandler);\n          document.addEventListener(\"mouseup\", upHandler);\n         });\n         c.addEventListener(\"touchstart\",\n          (e) => {\n           if (!freeDrawMode) return;\n           e.preventDefault();\n           e.stopPropagation();\n           let dragging = true;\n           const svgEl = svgMap;\n           const touchMoveHandler = (ev) => {\n            if (!dragging || !ev.touches[0]) return;\n            const pt = svgEl.createSVGPoint();\n            pt.x = ev.touches[0].clientX;\n            pt.y = ev.touches[0].clientY;\n            const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n            const i = parseInt(c.dataset.index, 10);\n            if (Number.isNaN(i) || !freeDrawPoints[i]) return;\n            freeDrawPoints[i].x = svgP.x;\n            freeDrawPoints[i].y = svgP.y;\n            updateFreeDrawGraphics();\n           };\n           const touchUpHandler = () => {\n            dragging = false;\n            document.removeEventListener(\"touchmove\", touchMoveHandler);\n            document.removeEventListener(\"touchend\", touchUpHandler);\n           };\n           document.addEventListener(\"touchmove\", touchMoveHandler);\n           document.addEventListener(\"touchend\", touchUpHandler);\n          }, {\n           passive: false\n          });\n         svg.appendChild(c);\n         freeDrawPointEls.push(c);\n        });\n        drawUndoBtn.style.display = freeDrawPoints.length ? \"inline-block\" : \"none\";\n       }\n       function addFreeDrawPoint(x, y) {\n        freeDrawPoints.push({\n         x,\n         y\n        });\n        updateFreeDrawGraphics();\n       }\n       function startFreeDraw() {\n        freeDrawMode = true;\n        freeDrawPoints = [];\n        if (freeDrawPolylineEl) {\n         freeDrawPolylineEl.remove();\n         freeDrawPolylineEl = null;\n        }\n        freeDrawPointEls.forEach((el) => el.remove());\n        freeDrawPointEls = [];\n        svgMap.style.cursor = \"crosshair\";\n        drawToggleBtn.textContent = \"Done\";\n        drawToggleBtn.classList.add(\"done-btn-active\");\n        drawUndoBtn.style.display = \"none\";\n       }\n       function finishFreeDraw() {\n        freeDrawMode = false;\n        svgMap.style.cursor = \"\";\n        drawToggleBtn.textContent = \"✏️\";\n        drawToggleBtn.classList.remove(\"done-btn-active\");\n        if (freeDrawPoints.length >= 2) {\n         const color = drawColorInput.value || \"#475569\";\n         const lineStyle = drawStyleSelect.value || \"solid\";\n         const arrowDir = drawArrowSelect.value || \"none\";\n         const newId = \"custom-\" + Date.now();\n         const pointsCopy = freeDrawPoints.map((p) => ({\n          x: p.x,\n          y: p.y,\n         }));\n         EDGE_DATA.list.push({\n          id: newId,\n          type: \"custom\",\n          color,\n          width: 4,\n          lineStyle: lineStyle,\n          direction: arrowDir,\n          points: pointsCopy,\n          notes: [],\n         });\n         freeDrawPoints = [];\n         if (freeDrawPolylineEl) {\n          freeDrawPolylineEl.remove();\n          freeDrawPolylineEl = null;\n         }\n         freeDrawPointEls.forEach((el) => el.remove());\n         freeDrawPointEls = [];\n         forgeTheTopology();\n         selectTheConnection(newId);\n        } else {\n         freeDrawPoints = [];\n         if (freeDrawPolylineEl) {\n          freeDrawPolylineEl.remove();\n          freeDrawPolylineEl = null;\n         }\n         freeDrawPointEls.forEach((el) => el.remove());\n         freeDrawPointEls = [];\n         forgeTheLegend();\n\t\t updateZoneLegend();\n        }\n        drawUndoBtn.style.display = \"none\";\n       }\n       drawToggleBtn.addEventListener(\"click\", () => {\n        if (currentView.mode === \"rack\") {\n         alert(\"Drawing tools are disabled in rack view. Exit to topology view to use drawing tools.\");\n         return;\n        }\n        if (freeDrawMode) {\n         finishFreeDraw();\n        } else {\n         startFreeDraw();\n        }\n       });\n       drawUndoBtn.addEventListener(\"click\", () => {\n        if (!freeDrawMode || !freeDrawPoints.length) return;\n        freeDrawPoints.pop();\n        updateFreeDrawGraphics();\n       });\n       const drawToolbar = document.getElementById(\"draw-toolbar\");\n       drawToolbar.addEventListener(\"mousedown\", (e) => {\n        e.stopPropagation();\n       });\n       drawToolbar.addEventListener(\"click\", (e) => {\n        if (e.target !== drawToggleBtn && e.target !== drawUndoBtn) {\n         e.stopPropagation();\n        }\n       });\n       drawStyleSelect.addEventListener(\"change\", () => {\n        if (freeDrawMode) {\n         updateFreeDrawGraphics();\n        }\n       });\n       drawArrowSelect.addEventListener(\"change\", () => {\n        if (freeDrawMode) {\n         updateFreeDrawGraphics();\n        }\n       });\n       drawColorInput.addEventListener(\"input\", () => {\n        if (freeDrawMode) {\n         updateFreeDrawGraphics();\n        }\n       });\n       drawStyleSelect.addEventListener(\"mousedown\", (e) => {\n        e.stopPropagation();\n       });\n       drawStyleSelect.addEventListener(\"click\", (e) => {\n        e.stopPropagation();\n       });\n       drawArrowSelect.addEventListener(\"mousedown\", (e) => {\n        e.stopPropagation();\n       });\n       drawArrowSelect.addEventListener(\"click\", (e) => {\n        e.stopPropagation();\n       });\n       drawColorInput.addEventListener(\"mousedown\", (e) => {\n        e.stopPropagation();\n       });\n       drawColorInput.addEventListener(\"click\", (e) => {\n        e.stopPropagation();\n       });\n       svgMap.addEventListener(\"click\", (e) => {\n        if (!freeDrawMode) return;\n        if (e.button !== 0) return;\n        const target = e.target;\n        if (target && target.classList && target.classList.contains(\"free-point\")) return;\n        const svgEl = svgMap;\n        const pt = svgEl.createSVGPoint();\n        pt.x = e.clientX;\n        pt.y = e.clientY;\n        const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n        addFreeDrawPoint(svgP.x, svgP.y);\n       });\n       svgMap.addEventListener(\"touchend\",\n        (e) => {\n         if (!freeDrawMode) return;\n         const target = e.target;\n         if (target && target.classList && target.classList.contains(\"free-point\")) return;\n         if (e.changedTouches && e.changedTouches[0]) {\n          e.preventDefault();\n          const svgEl = svgMap;\n          const pt = svgEl.createSVGPoint();\n          pt.x = e.changedTouches[0].clientX;\n          pt.y = e.changedTouches[0].clientY;\n          const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n          addFreeDrawPoint(svgP.x, svgP.y);\n         }\n        }, {\n         passive: false\n        });\n       const settingsBtn = document.getElementById(\"settings-btn\");\n       const rectToggleBtn = document.getElementById(\"rect-toggle\");\n       const rectStyleSelect = document.getElementById(\"rect-style\");\n       function updateRectPreview() {\n        if (!rectPreviewEl || !rectStartPoint) return;\n        const ns = \"http://www.w3.org/2000/svg\";\n        const svg = svgMap;\n        if (rectPreviewEl) {\n         rectPreviewEl.remove();\n         rectPreviewEl = null;\n        }\n       }\n       function startRectDraw() {\n        rectDrawMode = true;\n        rectStartPoint = null;\n        rectPreviewEl = null;\n        svgMap.style.cursor = \"crosshair\";\n        rectToggleBtn.textContent = \"Done\";\n        rectToggleBtn.classList.add(\"done-btn-active\");\n        rectStyle = rectStyleSelect.value || \"filled\";\n        if (freeDrawMode) {\n         finishFreeDraw();\n        }\n        updateRectangleDeleteButtons();\n       }\n       function finishRectDraw() {\n        rectDrawMode = false;\n        svgMap.style.cursor = \"\";\n        rectToggleBtn.textContent = \"▭\";\n        rectToggleBtn.classList.remove(\"done-btn-active\");\n        if (rectPreviewEl) {\n         rectPreviewEl.remove();\n         rectPreviewEl = null;\n        }\n        rectStartPoint = null;\n\t\tupdateRectangleDeleteButtons();\n       }\n       rectToggleBtn.addEventListener(\"click\", () => {\n        if (currentView.mode === \"rack\") {\n         alert(\"Drawing tools are disabled in rack view. Exit to topology view to use drawing tools.\");\n         return;\n        }\n        if (rectDrawMode) {\n         finishRectDraw();\n        } else {\n         startRectDraw();\n        }\n       });\n       rectStyleSelect.addEventListener(\"mousedown\", (e) => {\n        e.stopPropagation();\n       });\n       rectStyleSelect.addEventListener(\"click\", (e) => {\n        e.stopPropagation();\n       });\n       rectStyleSelect.addEventListener(\"change\", () => {\n        if (rectDrawMode) {\n         rectStyle = rectStyleSelect.value || \"filled\";\n        }\n       });\n       svgMap.addEventListener(\"mousedown\", (e) => {\n        if (!rectDrawMode) return;\n        if (e.button !== 0) return;\n        e.preventDefault();\n        e.stopPropagation();\n        const svgEl = svgMap;\n        const pt = svgEl.createSVGPoint();\n        pt.x = e.clientX;\n        pt.y = e.clientY;\n        const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n        rectStartPoint = { x: svgP.x, y: svgP.y };\n        const ns = \"http://www.w3.org/2000/svg\";\n        rectPreviewEl = document.createElementNS(ns, \"rect\");\n        rectPreviewEl.classList.add(\"rect-preview\");\n        rectPreviewEl.setAttribute(\"x\", svgP.x);\n        rectPreviewEl.setAttribute(\"y\", svgP.y);\n        rectPreviewEl.setAttribute(\"width\", 0);\n        rectPreviewEl.setAttribute(\"height\", 0);\n        const color = drawColorInput.value || \"#f97316\";\n        rectPreviewEl.style.stroke = color;\n        rectPreviewEl.style.strokeWidth = 2;\n        rectPreviewEl.style.strokeDasharray = \"5,5\";\n        if (rectStyle === \"filled\") {\n         rectPreviewEl.style.fill = color;\n         rectPreviewEl.style.fillOpacity = \"0.3\";\n        } else {\n         rectPreviewEl.style.fill = \"none\";\n        }\n        svgEl.appendChild(rectPreviewEl);\n       });\n       svgMap.addEventListener(\"mousemove\", (e) => {\n        if (!rectDrawMode || !rectStartPoint || !rectPreviewEl) return;\n        const svgEl = svgMap;\n        const pt = svgEl.createSVGPoint();\n        pt.x = e.clientX;\n        pt.y = e.clientY;\n        const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n        const x = Math.min(rectStartPoint.x, svgP.x);\n        const y = Math.min(rectStartPoint.y, svgP.y);\n        const width = Math.abs(svgP.x - rectStartPoint.x);\n        const height = Math.abs(svgP.y - rectStartPoint.y);\n        rectPreviewEl.setAttribute(\"x\", x);\n        rectPreviewEl.setAttribute(\"y\", y);\n        rectPreviewEl.setAttribute(\"width\", width);\n        rectPreviewEl.setAttribute(\"height\", height);\n       });\n       svgMap.addEventListener(\"mouseup\", (e) => {\n        if (!rectDrawMode || !rectStartPoint || !rectPreviewEl) return;\n        const svgEl = svgMap;\n        const pt = svgEl.createSVGPoint();\n        pt.x = e.clientX;\n        pt.y = e.clientY;\n        const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n        const x = Math.min(rectStartPoint.x, svgP.x);\n        const y = Math.min(rectStartPoint.y, svgP.y);\n        const width = Math.abs(svgP.x - rectStartPoint.x);\n        const height = Math.abs(svgP.y - rectStartPoint.y);\n        if (width > 10 && height > 10) {\n      pushUndo(\"draw zone\");\n         const color = drawColorInput.value || \"#f97316\";\n         const newId = \"rect-\" + Date.now();\n         const lineStyle = document.getElementById(\"draw-style\").value || \"solid\";\n         RECT_DATA.list.push({\n          id: newId,\n          x: x,\n          y: y,\n          width: width,\n          height: height,\n          color: color,\n          style: rectStyle,\n          lineStyle: lineStyle,\n          notes: []\n         });\n         forgeTheTopology();\n        }\n        if (rectPreviewEl) {\n         rectPreviewEl.remove();\n         rectPreviewEl = null;\n        }\n        rectStartPoint = null;\n       });\n       let rectTouchStart = null;\n       svgMap.addEventListener(\"touchstart\", (e) => {\n        if (!rectDrawMode) return;\n        if (e.touches.length !== 1) return;\n        e.preventDefault();\n        const touch = e.touches[0];\n        const svgEl = svgMap;\n        const pt = svgEl.createSVGPoint();\n        pt.x = touch.clientX;\n        pt.y = touch.clientY;\n        const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n        rectStartPoint = { x: svgP.x, y: svgP.y };\n        rectTouchStart = { x: svgP.x, y: svgP.y };\n        const ns = \"http://www.w3.org/2000/svg\";\n        rectPreviewEl = document.createElementNS(ns, \"rect\");\n        rectPreviewEl.classList.add(\"rect-preview\");\n        rectPreviewEl.setAttribute(\"x\", svgP.x);\n        rectPreviewEl.setAttribute(\"y\", svgP.y);\n        rectPreviewEl.setAttribute(\"width\", 0);\n        rectPreviewEl.setAttribute(\"height\", 0);\n        const color = drawColorInput.value || \"#f97316\";\n        rectPreviewEl.style.stroke = color;\n        rectPreviewEl.style.strokeWidth = 2;\n        rectPreviewEl.style.strokeDasharray = \"5,5\";\n        if (rectStyle === \"filled\") {\n         rectPreviewEl.style.fill = color;\n         rectPreviewEl.style.fillOpacity = \"0.3\";\n        } else {\n         rectPreviewEl.style.fill = \"none\";\n        }\n        svgEl.appendChild(rectPreviewEl);\n       }, { passive: false });\n       svgMap.addEventListener(\"touchmove\", (e) => {\n        if (!rectDrawMode || !rectStartPoint || !rectPreviewEl) return;\n        if (e.touches.length !== 1) return;\n        e.preventDefault();\n        const touch = e.touches[0];\n        const svgEl = svgMap;\n        const pt = svgEl.createSVGPoint();\n        pt.x = touch.clientX;\n        pt.y = touch.clientY;\n        const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n        const x = Math.min(rectStartPoint.x, svgP.x);\n        const y = Math.min(rectStartPoint.y, svgP.y);\n        const width = Math.abs(svgP.x - rectStartPoint.x);\n        const height = Math.abs(svgP.y - rectStartPoint.y);\n        rectPreviewEl.setAttribute(\"x\", x);\n        rectPreviewEl.setAttribute(\"y\", y);\n        rectPreviewEl.setAttribute(\"width\", width);\n        rectPreviewEl.setAttribute(\"height\", height);\n       }, { passive: false });\n       svgMap.addEventListener(\"touchend\", (e) => {\n        if (!rectDrawMode || !rectStartPoint || !rectPreviewEl) return;\n        if (!rectTouchStart) return;\n        e.preventDefault();\n        const touch = e.changedTouches[0];\n        const svgEl = svgMap;\n        const pt = svgEl.createSVGPoint();\n        pt.x = touch.clientX;\n        pt.y = touch.clientY;\n        const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n        const x = Math.min(rectStartPoint.x, svgP.x);\n        const y = Math.min(rectStartPoint.y, svgP.y);\n        const width = Math.abs(svgP.x - rectStartPoint.x);\n        const height = Math.abs(svgP.y - rectStartPoint.y);\n        if (width > 10 && height > 10) {\n      pushUndo(\"draw zone\");\n         const color = drawColorInput.value || \"#f97316\";\n         const newId = \"rect-\" + Date.now();\n         const lineStyle = document.getElementById(\"draw-style\").value || \"solid\";\n         RECT_DATA.list.push({\n          id: newId,\n          x: x,\n          y: y,\n          width: width,\n          height: height,\n          color: color,\n          style: rectStyle,\n          lineStyle: lineStyle,\n          notes: []\n         });\n         forgeTheTopology();\n        }\n        if (rectPreviewEl) {\n         rectPreviewEl.remove();\n         rectPreviewEl = null;\n        }\n        rectStartPoint = null;\n        rectTouchStart = null;\n       }, { passive: false });\n       const textToggleBtn = document.getElementById(\"text-toggle\");\n       function startTextMode() {\n        textDrawMode = true;\n        svgMap.style.cursor = \"crosshair\";\n        textToggleBtn.textContent = \"Done\";\n        textToggleBtn.classList.add(\"done-btn-active\");\n        if (freeDrawMode) {\n         finishFreeDraw();\n        }\n        if (rectDrawMode) {\n         finishRectDraw();\n        }\n        updateTextDeleteButtons();\n       }\n       function finishTextMode() {\n        textDrawMode = false;\n        svgMap.style.cursor = \"\";\n        textToggleBtn.textContent = \"T\";\n        textToggleBtn.classList.remove(\"done-btn-active\");\n        updateTextDeleteButtons();\n       }\n       textToggleBtn.addEventListener(\"click\", () => {\n        if (currentView.mode === \"rack\") {\n         alert(\"Drawing tools are disabled in rack view. Exit to topology view to use drawing tools.\");\n         return;\n        }\n        if (textDrawMode) {\n         finishTextMode();\n        } else {\n         startTextMode();\n        }\n       });\n       function handleTextPlacement(e) {\n        if (!textDrawMode) return;\n        const svgEl = svgMap;\n        const pt = svgEl.createSVGPoint();\n        pt.x = e.clientX;\n        pt.y = e.clientY;\n        const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n        const newId = \"text-\" + Date.now();\n      pushUndo(\"add text\");\n        TEXT_DATA.list.push({\n         id: newId,\n         x: svgP.x,\n         y: svgP.y,\n         content: \"New Text\",\n         fontSize: 18,\n         color: \"#e2e8f0\",\n         fontWeight: \"normal\",\n         fontStyle: \"normal\",\n         textAlign: \"start\",\n         textDecoration: \"none\",\n         bgColor: \"#000000\",\n         bgEnabled: false,\n         opacity: 1\n        });\n        forgeTheTopology();\n        showTextPanel(newId);\n       }\n       svgMap.addEventListener(\"click\", (e) => {\n        if (!textDrawMode) return;\n        if (e.target.closest('.text-delete-btn')) return;\n        if (e.target.closest('.text-group')) return;\n        e.preventDefault();\n        e.stopPropagation();\n        handleTextPlacement(e);\n       });\n       svgMap.addEventListener(\"touchend\", (e) => {\n        if (!textDrawMode) return;\n        if (e.target.closest('.text-delete-btn')) return;\n        if (e.target.closest('.text-group')) return;\n        if (e.touches.length > 0) return;\n        e.preventDefault();\n        const touch = e.changedTouches[0];\n        const fakeEvent = {\n         clientX: touch.clientX,\n         clientY: touch.clientY,\n         preventDefault: () => {},\n         stopPropagation: () => {}\n        };\n        handleTextPlacement(fakeEvent);\n       }, { passive: false });\n       function showTextPanel(textId) {\n\t   if (isViewOnly()) return;\n       currentTextId = textId;\n       currentNodeId = null;\n       currentEdgeId = null;\n       currentRectId = null;\n       const textItem = TEXT_DATA.list.find(t => t.id === textId);\n       if (!textItem) return;\n       document.getElementById(\"node-panel\").style.display = \"none\";\n       document.getElementById(\"edge-panel\").style.display = \"none\";\n       document.getElementById(\"rect-panel\").style.display = \"none\";\n       const textPanel = document.getElementById(\"text-panel\");\n       textPanel.style.display = \"block\";\n       document.querySelectorAll(\".node-group\").forEach(n => n.classList.remove(\"active\"));\n       document.querySelectorAll(\".edge\").forEach(e => e.classList.remove(\"active\"));\n       document.querySelectorAll(\".rect-group\").forEach(r => r.classList.remove(\"active\"));\n       document.querySelectorAll(\".text-element\").forEach(t => t.classList.toggle(\"active\", t.dataset.textId === textId));\n        document.getElementById(\"text-content\").value = textItem.content;\n        document.getElementById(\"text-font-size\").value = textItem.fontSize;\n        document.getElementById(\"text-color\").value = textItem.color;\n        document.getElementById(\"text-font-weight\").value = textItem.fontWeight;\n        document.getElementById(\"text-font-style\").value = textItem.fontStyle;\n        document.getElementById(\"text-align\").value = textItem.textAlign;\n        document.getElementById(\"text-decoration\").value = textItem.textDecoration;\n        document.getElementById(\"text-bg-color\").value = textItem.bgColor;\n        document.getElementById(\"text-bg-enabled\").checked = textItem.bgEnabled;\n        document.getElementById(\"text-opacity\").value = textItem.opacity;\n        document.getElementById(\"text-opacity-val\").textContent = Math.round(textItem.opacity * 100) + \"%\";\n        document.getElementById(\"text-rotation\").value = Math.max(-360, Math.min(360, textItem.rotation || 0));\n        document.getElementById(\"text-rotation-val\").value = textItem.rotation || 0;\n       }\n       function updateTextDeleteButtons() {\n        const deleteButtons = document.querySelectorAll('.text-delete-btn');\n        deleteButtons.forEach(btn => {\n         btn.style.display = textDrawMode ? 'block' : 'none';\n        });\n       }\n       function deleteText(textId) {\n      pushUndo(\"delete text\");\n        TEXT_DATA.list = TEXT_DATA.list.filter(t => t.id !== textId);\n        forgeTheTopology();\n        if (currentTextId === textId) {\n         document.getElementById(\"text-panel\").style.display = \"none\";\n         currentTextId = null;\n        }\n       }\n       document.getElementById(\"text-content\").addEventListener(\"input\", (e) => {\n        if (!currentTextId) return;\n        const textItem = TEXT_DATA.list.find(t => t.id === currentTextId);\n        if (textItem) {\n      pushUndo(\"edit text\");\n         textItem.content = e.target.value;\n         forgeTheTopology();\n        }\n       });\n       document.getElementById(\"text-font-size\").addEventListener(\"input\", (e) => {\n        if (!currentTextId) return;\n        const textItem = TEXT_DATA.list.find(t => t.id === currentTextId);\n        if (textItem) {\n         pushUndo(\"edit text\");\n         textItem.fontSize = parseInt(e.target.value);\n         forgeTheTopology();\n        }\n       });\n       document.getElementById(\"text-color\").addEventListener(\"input\", (e) => {\n        if (!currentTextId) return;\n        const textItem = TEXT_DATA.list.find(t => t.id === currentTextId);\n        if (textItem) {\n         pushUndo(\"edit text\");\n         textItem.color = e.target.value;\n         forgeTheTopology();\n        }\n       });\n       document.getElementById(\"text-font-weight\").addEventListener(\"change\", (e) => {\n        if (!currentTextId) return;\n        const textItem = TEXT_DATA.list.find(t => t.id === currentTextId);\n        if (textItem) {\n         pushUndo(\"edit text\");\n         textItem.fontWeight = e.target.value;\n         forgeTheTopology();\n        }\n       });\n       document.getElementById(\"text-font-style\").addEventListener(\"change\", (e) => {\n        if (!currentTextId) return;\n        const textItem = TEXT_DATA.list.find(t => t.id === currentTextId);\n        if (textItem) {\n         pushUndo(\"edit text\");\n         textItem.fontStyle = e.target.value;\n         forgeTheTopology();\n        }\n       });\n       document.getElementById(\"text-align\").addEventListener(\"change\", (e) => {\n        if (!currentTextId) return;\n        const textItem = TEXT_DATA.list.find(t => t.id === currentTextId);\n        if (textItem) {\n         pushUndo(\"edit text\");\n         textItem.textAlign = e.target.value;\n         forgeTheTopology();\n        }\n       });\n        document.getElementById(\"text-decoration\").addEventListener(\"change\", (e) => {\n        if (!currentTextId) return;\n        const textItem = TEXT_DATA.list.find(t => t.id === currentTextId);\n        if (textItem) {\n         pushUndo(\"edit text\");\n         textItem.textDecoration = e.target.value;\n         forgeTheTopology();\n        }\n       });\n       document.getElementById(\"text-bg-color\").addEventListener(\"input\", (e) => {\n        if (!currentTextId) return;\n        const textItem = TEXT_DATA.list.find(t => t.id === currentTextId);\n        if (textItem) {\n         pushUndo(\"edit text\");\n         textItem.bgColor = e.target.value;\n         forgeTheTopology();\n        }\n       });\n       document.getElementById(\"text-bg-enabled\").addEventListener(\"change\", (e) => {\n        if (!currentTextId) return;\n        const textItem = TEXT_DATA.list.find(t => t.id === currentTextId);\n        if (textItem) {\n         pushUndo(\"edit text\");\n         textItem.bgEnabled = e.target.checked;\n         forgeTheTopology();\n        }\n       });\n       document.getElementById(\"text-opacity\").addEventListener(\"input\", (e) => {\n        if (!currentTextId) return;\n        const textItem = TEXT_DATA.list.find(t => t.id === currentTextId);\n        if (textItem) {\n         pushUndo(\"edit text\");\n         textItem.opacity = parseFloat(e.target.value);\n         document.getElementById(\"text-opacity-val\").textContent = Math.round(textItem.opacity * 100) + \"%\";\n         forgeTheTopology();\n        }\n       });\n       document.getElementById(\"text-rotation\").addEventListener(\"input\", (e) => {\n         if (!currentTextId) return;\n         const textItem = TEXT_DATA.list.find(t => t.id === currentTextId);\n         if (textItem) {\n           pushUndo(\"rotate text\");\n           textItem.rotation = parseInt(e.target.value) || 0;\n           document.getElementById(\"text-rotation-val\").value = textItem.rotation;\n           forgeTheTopology();\n         }\n       });\n       document.getElementById(\"text-rotation-val\").addEventListener(\"input\", (e) => {\n         if (!currentTextId) return;\n         const textItem = TEXT_DATA.list.find(t => t.id === currentTextId);\n         if (textItem) {\n           pushUndo(\"rotate text\");\n           const val = parseInt(e.target.value) || 0;\n           textItem.rotation = val;\n           document.getElementById(\"text-rotation\").value = Math.max(-360, Math.min(360, val));\n           forgeTheTopology();\n         }\n       });\n       document.getElementById(\"delete-text\").addEventListener(\"click\", () => {\n        if (!currentTextId) return;\n        const textItem = TEXT_DATA.list.find(t => t.id === currentTextId);\n        if (textItem) {\n         challengeTheImmortal(`Delete text \"${(textItem.content || '').substring(0, 30)}...\"?`, () => {\n          deleteText(currentTextId);\n         });\n        }\n       });\n       const settingsModal = document.getElementById(\"settings-modal\");\n       const settingsClose = document.getElementById(\"settings-close\");\n       settingsBtn.addEventListener(\"click\", () => {\n       document.getElementById(\"page-bg-color\").value = rgbaToHex(PAGE_STATE.background) || \"#050608\";\n       document.getElementById(\"topbar-bg-color\").value = rgbaToHex(PAGE_STATE.topbarBg) || \"#0b0e13\";\n       document.getElementById(\"topbar-border-color\").value = rgbaToHex(PAGE_STATE.topbarBorder) || \"#1f2533\";\n       document.getElementById(\"panel-color\").value = rgbaToHex(PAGE_STATE.panel) || \"#0b0e13\";\n       document.getElementById(\"panel-alt-color\").value = PAGE_STATE.panelAlt || \"#10141b\";\n       document.getElementById(\"sidebar-bg-color\").value = PAGE_STATE.sidebarBg || \"#10141b\";\n       document.getElementById(\"btn-bg-color\").value = PAGE_STATE.btnBg || \"#0b0e13\";\n       document.getElementById(\"btn-text-color\").value = PAGE_STATE.btnText || \"#e2e8f0\";\n       document.getElementById(\"tag-fill-color\").value = PAGE_STATE.tagFill || \"#1e293b\";\n       document.getElementById(\"tag-text-color\").value = PAGE_STATE.tagText || \"#e2e8f0\";\n       document.getElementById(\"tag-border-color\").value = PAGE_STATE.tagBorder || \"#475569\";\n       document.getElementById(\"input-bg-color\").value = PAGE_STATE.inputBg || \"#0b0e13\";\n       document.getElementById(\"input-text-color\").value = PAGE_STATE.inputText || \"#e2e8f0\";\n\t   document.getElementById(\"input-border-color\").value = PAGE_STATE.inputBorder || \"#1f2937\";\n       document.getElementById(\"input-font-family\").value = PAGE_STATE.inputFont || \"Inter, system-ui, sans-serif\";\n       document.getElementById(\"input-font-size\").value = PAGE_STATE.inputFontSize || 14;\n       document.getElementById(\"toolbar-bg-color\").value = PAGE_STATE.toolbarBg || \"#0f172a\";\n       document.getElementById(\"toolbar-border-color\").value = PAGE_STATE.toolbarBorder || \"#1f2937\";\n       document.getElementById(\"toolbar-text-color\").value = PAGE_STATE.toolbarText || \"#94a3b8\";\n       document.getElementById(\"toolbar-btn-bg-color\").value = PAGE_STATE.toolbarBtnBg || \"#0b0e13\";\n       document.getElementById(\"toolbar-btn-text-color\").value = PAGE_STATE.toolbarBtnText || \"#e2e8f0\";\n       document.getElementById(\"minimap-dots-color\").value = PAGE_STATE.minimapDots || \"#94a3b8\";\n       document.getElementById(\"canvas-hint-enabled\").checked = PAGE_STATE.canvasHintEnabled !== false;\n       document.getElementById(\"canvas-hint-bg-color\").value = PAGE_STATE.canvasHintBg || \"#0f172a\";\n       document.getElementById(\"canvas-hint-text-color\").value = PAGE_STATE.canvasHintColor || \"#94a3b8\";\n       document.getElementById(\"canvas-hint-text\").value = PAGE_STATE.canvasHintText || \"\";\n       document.getElementById(\"accent-color\").value = rgbaToHex(PAGE_STATE.accent) || \"#4fd1c5\";\n       document.getElementById(\"danger-color\").value = rgbaToHex(PAGE_STATE.danger) || \"#f56565\";\n       document.getElementById(\"text-main-color\").value = rgbaToHex(PAGE_STATE.textMain) || \"#e2e8f0\";\n       document.getElementById(\"text-soft-color\").value = PAGE_STATE.textSoft || \"#94a3b8\";\n       document.getElementById(\"node-fill-color\").value = PAGE_STATE.nodeFill || \"#1e293b\";\n       document.getElementById(\"node-stroke-color\").value = PAGE_STATE.nodeStroke || \"#475569\";\n       document.getElementById(\"node-title-color\").value = PAGE_STATE.nodeTitle || \"#e2e8f0\";\n       document.getElementById(\"node-sub-color\").value = PAGE_STATE.nodeSub || \"#94a3b8\";\n       document.getElementById(\"node-title-size\").value = PAGE_STATE.nodeTitleSize || 18;\n       document.getElementById(\"node-sub-size\").value = PAGE_STATE.nodeSubSize || 13;\n       document.getElementById(\"node-font-family\").value = PAGE_STATE.nodeFont || \"Inter, system-ui, sans-serif\";\n       document.getElementById(\"default-edge-color\").value = PAGE_STATE.defaultEdge || \"#475569\";\n       document.getElementById(\"add-line-color\").value = PAGE_STATE.defaultEdge || \"#475569\";\n       document.getElementById(\"default-edge-routing\").value = PAGE_STATE.defaultEdgeRouting || \"curved\";\n\t   document.getElementById(\"anim-master\").addEventListener(\"change\", e => { ANIM_SETTINGS.masterAnim = e.target.checked; applyAnimZoneSettings(); });\n\t\tdocument.getElementById(\"anim-type-sweep\").addEventListener(\"change\", e => { ANIM_SETTINGS.animTypes.sweep = e.target.checked; applyAnimZoneSettings(); });\n\t\tdocument.getElementById(\"anim-type-pulse\").addEventListener(\"change\", e => { ANIM_SETTINGS.animTypes.pulse = e.target.checked; applyAnimZoneSettings(); });\n\t\tdocument.getElementById(\"anim-type-rings\").addEventListener(\"change\", e => { ANIM_SETTINGS.animTypes.rings = e.target.checked; applyAnimZoneSettings(); });\n\t\tdocument.getElementById(\"anim-type-spin\").addEventListener(\"change\", e => { ANIM_SETTINGS.animTypes.spin = e.target.checked; applyAnimZoneSettings(); });\n\t\tdocument.getElementById(\"anim-type-connections\").addEventListener(\"change\", e => { ANIM_SETTINGS.animTypes.connections = e.target.checked; applyAnimZoneSettings(); });\n\t\tdocument.getElementById(\"anim-cat-camera\").addEventListener(\"change\", e => { ANIM_SETTINGS.animCategories.camera = e.target.checked; applyAnimZoneSettings(); });\n\t\tdocument.getElementById(\"anim-cat-doorbell\").addEventListener(\"change\", e => { ANIM_SETTINGS.animCategories.doorbell = e.target.checked; applyAnimZoneSettings(); });\n\t\tdocument.getElementById(\"anim-cat-motion\").addEventListener(\"change\", e => { ANIM_SETTINGS.animCategories.motion = e.target.checked; applyAnimZoneSettings(); });\n\t\tdocument.getElementById(\"anim-cat-smoke\").addEventListener(\"change\", e => { ANIM_SETTINGS.animCategories.smoke = e.target.checked; applyAnimZoneSettings(); });\n\t\tdocument.getElementById(\"anim-cat-wifi\").addEventListener(\"change\", e => { ANIM_SETTINGS.animCategories.wifi = e.target.checked; applyAnimZoneSettings(); });\n\t\tdocument.getElementById(\"anim-cat-sensor\").addEventListener(\"change\", e => { ANIM_SETTINGS.animCategories.sensor = e.target.checked; applyAnimZoneSettings(); });\n\t\tdocument.getElementById(\"anim-cat-sprinkler\").addEventListener(\"change\", e => { ANIM_SETTINGS.animCategories.sprinkler = e.target.checked; applyAnimZoneSettings(); });\n\t\tdocument.getElementById(\"anim-cat-connections\").addEventListener(\"change\", e => { ANIM_SETTINGS.animCategories.connections = e.target.checked; applyAnimZoneSettings(); });\n\t\tdocument.getElementById(\"zone-master\").addEventListener(\"change\", e => { ANIM_SETTINGS.masterZones = e.target.checked; applyAnimZoneSettings(); });\n\t\tdocument.getElementById(\"zone-cat-camera\").addEventListener(\"change\", e => { ANIM_SETTINGS.zoneCategories.camera = e.target.checked; applyAnimZoneSettings(); });\n\t\tdocument.getElementById(\"zone-cat-doorbell\").addEventListener(\"change\", e => { ANIM_SETTINGS.zoneCategories.doorbell = e.target.checked; applyAnimZoneSettings(); });\n\t\tdocument.getElementById(\"zone-cat-motion\").addEventListener(\"change\", e => { ANIM_SETTINGS.zoneCategories.motion = e.target.checked; applyAnimZoneSettings(); });\n\t\tdocument.getElementById(\"zone-cat-smoke\").addEventListener(\"change\", e => { ANIM_SETTINGS.zoneCategories.smoke = e.target.checked; applyAnimZoneSettings(); });\n\t\tdocument.getElementById(\"zone-cat-wifi\").addEventListener(\"change\", e => { ANIM_SETTINGS.zoneCategories.wifi = e.target.checked; applyAnimZoneSettings(); });\n\t\tdocument.getElementById(\"zone-cat-sensor\").addEventListener(\"change\", e => { ANIM_SETTINGS.zoneCategories.sensor = e.target.checked; applyAnimZoneSettings(); });\n\t\tdocument.getElementById(\"zone-cat-sprinkler\").addEventListener(\"change\", e => { ANIM_SETTINGS.zoneCategories.sprinkler = e.target.checked; applyAnimZoneSettings(); });\n       document.getElementById(\"animation-style-select\").value = PAGE_STATE.animationStyle || \"arrows\";\n       document.getElementById(\"animation-direction-select\").value = PAGE_STATE.animationDirection || \"all\";\n       document.getElementById(\"animation-speed-select\").value = PAGE_STATE.animationSpeed || 1.5;\n       document.getElementById(\"add-line-routing\").value = PAGE_STATE.defaultEdgeRouting || \"orthogonal\";\n       document.getElementById(\"selection-handle-color\").value = PAGE_STATE.selectionHandle || \"#f59e0b\";\n       document.getElementById(\"selection-handle-size\").value = PAGE_STATE.selectionHandleSize || 8;\n       document.getElementById(\"group-indicator-color\").value = PAGE_STATE.groupIndicator || \"#4fd1c5\";\n       document.getElementById(\"canvas-gradient-top\").value = PAGE_STATE.canvasGradientTop || \"#1e2532\";\n       document.getElementById(\"canvas-gradient-bottom\").value = PAGE_STATE.canvasGradientBottom || \"#050608\";\n       document.getElementById(\"canvas-border-color\").value = PAGE_STATE.canvasBorder || \"#475569\";\n       document.getElementById(\"canvas-grid-color\").value = PAGE_STATE.canvasGrid || \"#475569\";\n       document.getElementById(\"canvas-grid-size\").value = PAGE_STATE.canvasGridSize || 50;\n       document.getElementById(\"canvas-grid-enabled\").checked = PAGE_STATE.canvasGridEnabled !== false;\n       document.getElementById(\"rack-frame-fill\").value = PAGE_STATE.rackFrameFill || \"#0f172a\";\n       document.getElementById(\"rack-frame-stroke\").value = PAGE_STATE.rackFrameStroke || \"#4fd1c5\";\n       document.getElementById(\"rack-line-color\").value = PAGE_STATE.rackLineColor || \"#475569\";\n       document.getElementById(\"rack-grid-enabled\").checked = PAGE_STATE.rackGridEnabled !== false;\n       document.getElementById(\"rack-text-color\").value = PAGE_STATE.rackTextColor || \"#4fd1c5\";\n        document.getElementById(\"auto-ping-enabled\").checked = autoPingEnabled;\n        document.getElementById(\"auto-ping-interval\").value = autoPingInterval;\n        document.getElementById(\"auto-ping-settings\").style.display = autoPingEnabled ? 'block' : 'none';\n        document.getElementById(\"view-only-mode\").checked = PAGE_STATE.viewOnly === true;\n        rebuildThemeDropdown();\n        updateDeleteButton();\n        settingsModal.classList.add(\"active\");\n       });\n       settingsClose.addEventListener(\"click\", () => {\n        settingsModal.classList.remove(\"active\");\n       });\n\t   document.getElementById(\"view-only-mode\").addEventListener(\"change\", (e) => {\n       PAGE_STATE.viewOnly = e.target.checked;\n       if (e.target.checked) {\n        document.getElementById(\"node-panel\").style.display = \"none\";\n        document.getElementById(\"edge-panel\").style.display = \"none\";\n        document.getElementById(\"text-panel\").style.display = \"none\";\n        document.getElementById(\"rect-panel\").style.display = \"none\";\n        document.getElementById(\"topology-toolbar\").style.setProperty('display', 'none', 'important');\n        document.getElementById(\"draw-toolbar\").style.setProperty('display', 'none', 'important');\n        currentNodeId = null;\n        currentEdgeId = null;\n        currentRectId = null;\n        currentTextId = null;\n        selectedNodes.clear();\n        selectedRects.clear();\n        selectedTexts.clear();\n        selectedEdges.clear();\n        const bulkToolbar = document.getElementById(\"bulk-toolbar\");\n        const bulkToolbarMobile = document.getElementById(\"bulk-toolbar-mobile\");\n        if (bulkToolbar) bulkToolbar.style.display = \"none\";\n        if (bulkToolbarMobile) bulkToolbarMobile.style.display = \"none\";\n        document.querySelectorAll(\".node-group\").forEach(n => n.classList.remove(\"active\", \"selected\"));\n        document.querySelectorAll(\".edge\").forEach(e => e.classList.remove(\"active\"));\n        document.querySelectorAll(\".rect-group\").forEach(r => r.classList.remove(\"active\"));\n        document.querySelectorAll(\".text-element\").forEach(t => t.classList.remove(\"active\"));\n       }\n       wieldThePower();\n       if (!e.target.checked) {\n        updateDrawToolbarVisibility();\n        updateTopologyToolbarVisibility();\n       }\n       forgeTheTopology();\n      });\n       settingsModal.addEventListener(\"click\", (e) => {\n        if (e.target === settingsModal) {\n         settingsModal.classList.remove(\"active\");\n        }\n       });\n       document.getElementById(\"page-bg-color\").addEventListener(\"input\", (e) => {\n        PAGE_STATE.background = e.target.value;\n        wieldThePower();\n       });\n       document.getElementById(\"topbar-bg-color\").addEventListener(\"input\", (e) => {\n        PAGE_STATE.topbarBg = e.target.value;\n        wieldThePower();\n       });\n       document.getElementById(\"topbar-border-color\").addEventListener(\"input\", (e) => {\n        PAGE_STATE.topbarBorder = e.target.value;\n        wieldThePower();\n       });\nconst THEME_PRESETS = {\n  defaulted: { panel:\"#0b0e13\",panelAlt:\"#10141b\",sidebarBg:\"#10141b\",btnBg:\"#0b0e13\",btnText:\"#e2e8f0\",accent:\"#4fd1c5\",danger:\"#f56565\",textMain:\"#e2e8f0\",textSoft:\"#94a3b8\",topbarBg:\"rgba(9,12,20,0.9)\",topbarBorder:\"#1f2533\",nodeFill:\"#1e293b\",nodeStroke:\"#475569\",nodeTitle:\"#e2e8f0\",nodeSub:\"#94a3b8\",defaultEdge:\"#475569\",canvasGradientTop:\"#1e2532\",canvasGradientBottom:\"#050608\",tagFill:\"#1e293b\",tagText:\"#e2e8f0\",tagBorder:\"#475569\",inputBg:\"#0b0e13\",inputText:\"#e2e8f0\",inputBorder:\"#1f2937\",toolbarBg:\"#0f172a\",toolbarBorder:\"#1f2937\",toolbarText:\"#94a3b8\",toolbarBtnBg:\"#0b0e13\",toolbarBtnText:\"#e2e8f0\",selectionHandle:\"#f59e0b\",groupIndicator:\"#4fd1c5\",minimapDots:\"#94a3b8\",canvasHintBg:\"#0f172a\",canvasHintColor:\"#94a3b8\",canvasBorder:\"#475569\",canvasGrid:\"#475569\",rackFrameFill:\"#0f172a\",rackFrameStroke:\"#4fd1c5\",rackLineColor:\"#475569\",rackTextColor:\"#4fd1c5\" },\n  slate: { panel:\"#1e293b\",panelAlt:\"#334155\",sidebarBg:\"#1e293b\",btnBg:\"#334155\",btnText:\"#f1f5f9\",accent:\"#3b82f6\",danger:\"#ef4444\",textMain:\"#f1f5f9\",textSoft:\"#94a3b8\",topbarBg:\"#0f172a\",topbarBorder:\"#334155\",nodeFill:\"#334155\",nodeStroke:\"#3b82f6\",nodeTitle:\"#f1f5f9\",nodeSub:\"#94a3b8\",defaultEdge:\"#64748b\",canvasGradientTop:\"#1e293b\",canvasGradientBottom:\"#0f172a\",tagFill:\"#1e3a5f\",tagText:\"#93c5fd\",tagBorder:\"#3b82f6\",inputBg:\"#0f172a\",inputText:\"#f1f5f9\",inputBorder:\"#475569\",toolbarBg:\"#2563eb\",toolbarBorder:\"#3b82f6\",toolbarText:\"#ffffff\",toolbarBtnBg:\"#3b82f6\",toolbarBtnText:\"#ffffff\",selectionHandle:\"#f59e0b\",groupIndicator:\"#22d3ee\",minimapDots:\"#64748b\",canvasHintBg:\"#1e293b\",canvasHintColor:\"#94a3b8\",canvasBorder:\"#334155\",canvasGrid:\"#334155\",rackFrameFill:\"#1e293b\",rackFrameStroke:\"#3b82f6\",rackLineColor:\"#475569\",rackTextColor:\"#3b82f6\" },\n  graphite: { panel:\"#1f2937\",panelAlt:\"#374151\",sidebarBg:\"#111827\",btnBg:\"#374151\",btnText:\"#f9fafb\",accent:\"#f59e0b\",danger:\"#ef4444\",textMain:\"#f9fafb\",textSoft:\"#9ca3af\",topbarBg:\"#111827\",topbarBorder:\"#4b5563\",nodeFill:\"#374151\",nodeStroke:\"#f59e0b\",nodeTitle:\"#f9fafb\",nodeSub:\"#9ca3af\",defaultEdge:\"#6b7280\",canvasGradientTop:\"#1f2937\",canvasGradientBottom:\"#111827\",tagFill:\"#44403c\",tagText:\"#fbbf24\",tagBorder:\"#f59e0b\",inputBg:\"#111827\",inputText:\"#f9fafb\",inputBorder:\"#4b5563\",toolbarBg:\"#b45309\",toolbarBorder:\"#f59e0b\",toolbarText:\"#ffffff\",toolbarBtnBg:\"#f59e0b\",toolbarBtnText:\"#000000\",selectionHandle:\"#10b981\",groupIndicator:\"#06b6d4\",minimapDots:\"#6b7280\",canvasHintBg:\"#1f2937\",canvasHintColor:\"#9ca3af\",canvasBorder:\"#4b5563\",canvasGrid:\"#374151\",rackFrameFill:\"#1f2937\",rackFrameStroke:\"#f59e0b\",rackLineColor:\"#4b5563\",rackTextColor:\"#fbbf24\" },\n  frost: { panel:\"#f8fafc\",panelAlt:\"#e2e8f0\",sidebarBg:\"#f1f5f9\",btnBg:\"#e2e8f0\",btnText:\"#1e293b\",accent:\"#1e40af\",danger:\"#dc2626\",textMain:\"#0f172a\",textSoft:\"#475569\",topbarBg:\"#1e40af\",topbarBorder:\"#1e3a8a\",nodeFill:\"#ffffff\",nodeStroke:\"#1e40af\",nodeTitle:\"#0f172a\",nodeSub:\"#475569\",defaultEdge:\"#64748b\",canvasGradientTop:\"#e0e7ef\",canvasGradientBottom:\"#f8fafc\",tagFill:\"#dbeafe\",tagText:\"#1e40af\",tagBorder:\"#3b82f6\",inputBg:\"#ffffff\",inputText:\"#0f172a\",inputBorder:\"#cbd5e1\",toolbarBg:\"#1e40af\",toolbarBorder:\"#1e3a8a\",toolbarText:\"#ffffff\",toolbarBtnBg:\"#3b82f6\",toolbarBtnText:\"#ffffff\",selectionHandle:\"#ea580c\",groupIndicator:\"#059669\",minimapDots:\"#64748b\",canvasHintBg:\"#e2e8f0\",canvasHintColor:\"#475569\",canvasBorder:\"#cbd5e1\",canvasGrid:\"#cbd5e1\",rackFrameFill:\"#f1f5f9\",rackFrameStroke:\"#1e40af\",rackLineColor:\"#94a3b8\",rackTextColor:\"#1e40af\" },\n  synthwave: { panel:\"#87366d\",panelAlt:\"#10141b\",sidebarBg:\"#340934\",btnBg:\"#0b0e13\",btnText:\"#e2e8f0\",accent:\"#4fd1c5\",danger:\"#f56565\",textMain:\"#e2e8f0\",textSoft:\"#94a3b8\",topbarBg:\"#781c67\",topbarBorder:\"#1f2533\",nodeFill:\"#1e293b\",nodeStroke:\"#475569\",nodeTitle:\"#e2e8f0\",nodeSub:\"#94a3b8\",defaultEdge:\"#475569\",canvasGradientTop:\"#1e2532\",canvasGradientBottom:\"#050608\",tagFill:\"#1e293b\",tagText:\"#e2e8f0\",tagBorder:\"#475569\",inputBg:\"#0b0e13\",inputText:\"#e2e8f0\",inputBorder:\"#1f2937\",toolbarBg:\"#b95aed\",toolbarBorder:\"#b800eb\",toolbarText:\"#000000\",toolbarBtnBg:\"#ed01fe\",toolbarBtnText:\"#000000\",selectionHandle:\"#f59e0b\",groupIndicator:\"#4fd1c5\",minimapDots:\"#000000\",canvasHintBg:\"#0f172a\",canvasHintColor:\"#94a3b8\",canvasBorder:\"#000000\",canvasGrid:\"#000000\",rackFrameFill:\"#0f172a\",rackFrameStroke:\"#4fd1c5\",rackLineColor:\"#475569\",rackTextColor:\"#4fd1c5\" },\n  terminal: { panel:\"#000000\",panelAlt:\"#0a0a0a\",sidebarBg:\"#050505\",btnBg:\"#0a0a0a\",btnText:\"#33ff33\",accent:\"#33ff33\",danger:\"#ffaa00\",textMain:\"#33ff33\",textSoft:\"#1a9a1a\",topbarBg:\"#000000\",topbarBorder:\"#33ff33\",nodeFill:\"#0a0a0a\",nodeStroke:\"#33ff33\",nodeTitle:\"#33ff33\",nodeSub:\"#1a9a1a\",defaultEdge:\"#1a9a1a\",canvasGradientTop:\"#0a0f0a\",canvasGradientBottom:\"#000000\",tagFill:\"#0a1a0a\",tagText:\"#33ff33\",tagBorder:\"#33ff33\",inputBg:\"#000000\",inputText:\"#33ff33\",inputBorder:\"#1a9a1a\",toolbarBg:\"#1a9a1a\",toolbarBorder:\"#33ff33\",toolbarText:\"#000000\",toolbarBtnBg:\"#33ff33\",toolbarBtnText:\"#000000\",selectionHandle:\"#ffaa00\",groupIndicator:\"#00ffff\",minimapDots:\"#33ff33\",canvasHintBg:\"#0a0a0a\",canvasHintColor:\"#1a9a1a\",canvasBorder:\"#1a9a1a\",canvasGrid:\"#0f1f0f\",rackFrameFill:\"#050505\",rackFrameStroke:\"#33ff33\",rackLineColor:\"#1a9a1a\",rackTextColor:\"#33ff33\" },\n  dracula: { panel:\"#282a36\",panelAlt:\"#44475a\",sidebarBg:\"#21222c\",btnBg:\"#44475a\",btnText:\"#f8f8f2\",accent:\"#bd93f9\",danger:\"#ff5555\",textMain:\"#f8f8f2\",textSoft:\"#6272a4\",topbarBg:\"#21222c\",topbarBorder:\"#6272a4\",nodeFill:\"#44475a\",nodeStroke:\"#ff79c6\",nodeTitle:\"#f8f8f2\",nodeSub:\"#8be9fd\",defaultEdge:\"#bd93f9\",canvasGradientTop:\"#282a36\",canvasGradientBottom:\"#1a1b23\",tagFill:\"#3d3f4a\",tagText:\"#50fa7b\",tagBorder:\"#50fa7b\",inputBg:\"#21222c\",inputText:\"#f8f8f2\",inputBorder:\"#6272a4\",toolbarBg:\"#6272a4\",toolbarBorder:\"#bd93f9\",toolbarText:\"#f8f8f2\",toolbarBtnBg:\"#bd93f9\",toolbarBtnText:\"#282a36\",selectionHandle:\"#f1fa8c\",groupIndicator:\"#ff79c6\",minimapDots:\"#bd93f9\",canvasHintBg:\"#282a36\",canvasHintColor:\"#6272a4\",canvasBorder:\"#44475a\",canvasGrid:\"#44475a\",rackFrameFill:\"#282a36\",rackFrameStroke:\"#ff79c6\",rackLineColor:\"#6272a4\",rackTextColor:\"#8be9fd\" },\n  cobalt: { panel:\"#002240\",panelAlt:\"#003366\",sidebarBg:\"#001b33\",btnBg:\"#003366\",btnText:\"#ffffff\",accent:\"#ffc600\",danger:\"#ff628c\",textMain:\"#ffffff\",textSoft:\"#8090a0\",topbarBg:\"#001525\",topbarBorder:\"#0088ff\",nodeFill:\"#003366\",nodeStroke:\"#0088ff\",nodeTitle:\"#ffffff\",nodeSub:\"#80ffbb\",defaultEdge:\"#0088ff\",canvasGradientTop:\"#002240\",canvasGradientBottom:\"#00111f\",tagFill:\"#004080\",tagText:\"#ffc600\",tagBorder:\"#0088ff\",inputBg:\"#001525\",inputText:\"#ffffff\",inputBorder:\"#0066cc\",toolbarBg:\"#0066cc\",toolbarBorder:\"#0088ff\",toolbarText:\"#ffffff\",toolbarBtnBg:\"#0088ff\",toolbarBtnText:\"#ffffff\",selectionHandle:\"#ffc600\",groupIndicator:\"#3ad900\",minimapDots:\"#0088ff\",canvasHintBg:\"#002240\",canvasHintColor:\"#8090a0\",canvasBorder:\"#0066cc\",canvasGrid:\"#003366\",rackFrameFill:\"#002240\",rackFrameStroke:\"#0088ff\",rackLineColor:\"#0066cc\",rackTextColor:\"#80ffbb\" },\n  solarized: { panel:\"#073642\",panelAlt:\"#002b36\",sidebarBg:\"#002b36\",btnBg:\"#073642\",btnText:\"#93a1a1\",accent:\"#268bd2\",danger:\"#dc322f\",textMain:\"#93a1a1\",textSoft:\"#657b83\",topbarBg:\"#002b36\",topbarBorder:\"#586e75\",nodeFill:\"#073642\",nodeStroke:\"#2aa198\",nodeTitle:\"#93a1a1\",nodeSub:\"#839496\",defaultEdge:\"#657b83\",canvasGradientTop:\"#073642\",canvasGradientBottom:\"#002b36\",tagFill:\"#0a4050\",tagText:\"#b58900\",tagBorder:\"#b58900\",inputBg:\"#002b36\",inputText:\"#93a1a1\",inputBorder:\"#586e75\",toolbarBg:\"#268bd2\",toolbarBorder:\"#2aa198\",toolbarText:\"#fdf6e3\",toolbarBtnBg:\"#2aa198\",toolbarBtnText:\"#002b36\",selectionHandle:\"#cb4b16\",groupIndicator:\"#d33682\",minimapDots:\"#657b83\",canvasHintBg:\"#073642\",canvasHintColor:\"#657b83\",canvasBorder:\"#586e75\",canvasGrid:\"#094050\",rackFrameFill:\"#002b36\",rackFrameStroke:\"#2aa198\",rackLineColor:\"#586e75\",rackTextColor:\"#859900\" }\n};\ndocument.getElementById(\"theme-preset\").addEventListener(\"change\", function() {\n  updateDeleteButton();\n  var p = THEME_PRESETS[this.value];\n  if (!p && this.value.startsWith(\"mytheme-\")) {\n    var found = savedStyleSets.find(function(s) { return s.id === document.getElementById(\"theme-preset\").value; });\n    if (found) p = found.styles;\n  }\n  if (!p) return;\n  Object.assign(PAGE_STATE, p);\n  document.getElementById(\"panel-color\").value = p.panel;\n  document.getElementById(\"panel-alt-color\").value = p.panelAlt;\n  document.getElementById(\"sidebar-bg-color\").value = p.sidebarBg;\n  document.getElementById(\"btn-bg-color\").value = p.btnBg;\n  document.getElementById(\"btn-text-color\").value = p.btnText;\n  document.getElementById(\"accent-color\").value = p.accent;\n  document.getElementById(\"danger-color\").value = p.danger;\n  document.getElementById(\"text-main-color\").value = p.textMain;\n  document.getElementById(\"text-soft-color\").value = p.textSoft;\n  document.getElementById(\"topbar-border-color\").value = p.topbarBorder;\n  document.getElementById(\"node-fill-color\").value = p.nodeFill;\n  document.getElementById(\"node-stroke-color\").value = p.nodeStroke;\n  document.getElementById(\"node-title-color\").value = p.nodeTitle;\n  document.getElementById(\"node-sub-color\").value = p.nodeSub;\n  document.getElementById(\"default-edge-color\").value = p.defaultEdge;\n  document.getElementById(\"canvas-gradient-top\").value = p.canvasGradientTop;\n  document.getElementById(\"canvas-gradient-bottom\").value = p.canvasGradientBottom;\n  document.getElementById(\"tag-fill-color\").value = p.tagFill;\n  document.getElementById(\"tag-text-color\").value = p.tagText;\n  document.getElementById(\"tag-border-color\").value = p.tagBorder;\n  document.getElementById(\"input-bg-color\").value = p.inputBg;\n  document.getElementById(\"input-text-color\").value = p.inputText;\n  document.getElementById(\"input-border-color\").value = p.inputBorder;\n  document.getElementById(\"toolbar-bg-color\").value = p.toolbarBg;\n  document.getElementById(\"toolbar-border-color\").value = p.toolbarBorder;\n  document.getElementById(\"toolbar-text-color\").value = p.toolbarText;\n  document.getElementById(\"toolbar-btn-bg-color\").value = p.toolbarBtnBg;\n  document.getElementById(\"toolbar-btn-text-color\").value = p.toolbarBtnText;\n  document.getElementById(\"selection-handle-color\").value = p.selectionHandle;\n  document.getElementById(\"group-indicator-color\").value = p.groupIndicator;\n  document.getElementById(\"minimap-dots-color\").value = p.minimapDots;\n  document.getElementById(\"canvas-hint-bg-color\").value = p.canvasHintBg;\n  document.getElementById(\"canvas-hint-text-color\").value = p.canvasHintColor;\n  document.getElementById(\"canvas-border-color\").value = p.canvasBorder;\n  document.getElementById(\"canvas-grid-color\").value = p.canvasGrid;\n  document.getElementById(\"rack-frame-fill\").value = p.rackFrameFill;\n  document.getElementById(\"rack-frame-stroke\").value = p.rackFrameStroke;\n  document.getElementById(\"rack-line-color\").value = p.rackLineColor;\n  document.getElementById(\"rack-text-color\").value = p.rackTextColor;\n  wieldThePower();\n  forgeTheTopology();\n});\ndocument.querySelectorAll('#settings-modal .style-content input, #settings-modal .style-content select').forEach(el => {\n  if (el.id === 'theme-preset') return;\n  el.addEventListener('input', () => document.getElementById('theme-preset').value = '');\n  el.addEventListener('change', () => document.getElementById('theme-preset').value = '');\n});\n       document.getElementById(\"panel-color\").addEventListener(\"input\", (e) => {\n        PAGE_STATE.panel = e.target.value;\n        wieldThePower();\n       });\n       document.getElementById(\"panel-alt-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.panelAlt = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"sidebar-bg-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.sidebarBg = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"btn-bg-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.btnBg = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"btn-text-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.btnText = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"tag-fill-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.tagFill = e.target.value;\n       wieldThePower();\n       forgeTheTopology();\n      });\n      document.getElementById(\"tag-text-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.tagText = e.target.value;\n       wieldThePower();\n       forgeTheTopology();\n      });\n      document.getElementById(\"tag-border-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.tagBorder = e.target.value;\n       wieldThePower();\n       forgeTheTopology();\n      });\n      document.getElementById(\"selection-fill-color\").addEventListener(\"input\", function() {\n       selectionBoxStyle.fillColor = this.value;\n      });\n      document.getElementById(\"selection-fill-opacity\").addEventListener(\"input\", function() {\n       selectionBoxStyle.fillOpacity = parseFloat(this.value);\n       document.getElementById(\"selection-fill-opacity-val\").textContent = Math.round(this.value * 100) + \"%\";\n      });\n      document.getElementById(\"selection-stroke-color\").addEventListener(\"input\", function() {\n       selectionBoxStyle.strokeColor = this.value;\n      });\n      document.getElementById(\"selection-stroke-width\").addEventListener(\"input\", function() {\n       selectionBoxStyle.strokeWidth = parseInt(this.value) || 2;\n      });\n      document.getElementById(\"selection-stroke-style\").addEventListener(\"change\", function() {\n       selectionBoxStyle.strokeDasharray = this.value;\n      });\n      document.getElementById(\"input-bg-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.inputBg = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"input-text-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.inputText = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"input-border-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.inputBorder = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"input-font-family\").addEventListener(\"change\", (e) => {\n       PAGE_STATE.inputFont = e.target.value;\n       wieldThePower();\n      });\n\t  document.getElementById(\"input-font-size\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.inputFontSize = parseInt(e.target.value) || 14;\n       wieldThePower();\n      });\n      document.getElementById(\"toolbar-bg-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.toolbarBg = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"toolbar-border-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.toolbarBorder = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"toolbar-text-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.toolbarText = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"toolbar-btn-bg-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.toolbarBtnBg = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"toolbar-btn-text-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.toolbarBtnText = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"minimap-dots-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.minimapDots = e.target.value;\n       wieldThePower();\n       forgeTheTopology();\n      });\n      document.getElementById(\"canvas-hint-enabled\").addEventListener(\"change\", (e) => {\n       PAGE_STATE.canvasHintEnabled = e.target.checked;\n       wieldThePower();\n      });\n      document.getElementById(\"canvas-hint-bg-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.canvasHintBg = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"canvas-hint-text-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.canvasHintColor = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"canvas-hint-text\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.canvasHintText = e.target.value;\n       wieldThePower();\n      });\n       document.getElementById(\"accent-color\").addEventListener(\"input\", (e) => {\n        PAGE_STATE.accent = e.target.value;\n        wieldThePower();\n       });\n       document.getElementById(\"danger-color\").addEventListener(\"input\", (e) => {\n        PAGE_STATE.danger = e.target.value;\n        wieldThePower();\n       });\n       document.getElementById(\"text-main-color\").addEventListener(\"input\", (e) => {\n        PAGE_STATE.textMain = e.target.value;\n        wieldThePower();\n       });\n       document.getElementById(\"text-soft-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.textSoft = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"node-fill-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.nodeFill = e.target.value;\n       forgeTheTopology();\n      });\n      document.getElementById(\"node-stroke-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.nodeStroke = e.target.value;\n       forgeTheTopology();\n      });\n      document.getElementById(\"node-title-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.nodeTitle = e.target.value;\n       forgeTheTopology();\n      });\n      document.getElementById(\"node-sub-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.nodeSub = e.target.value;\n       forgeTheTopology();\n      });\n      document.getElementById(\"node-title-size\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.nodeTitleSize = parseInt(e.target.value) || 18;\n       forgeTheTopology();\n      });\n      document.getElementById(\"node-sub-size\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.nodeSubSize = parseInt(e.target.value) || 13;\n       forgeTheTopology();\n      });\n      document.getElementById(\"node-font-family\").addEventListener(\"change\", (e) => {\n       PAGE_STATE.nodeFont = e.target.value;\n       forgeTheTopology();\n      });\n      document.getElementById(\"default-edge-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.defaultEdge = e.target.value;\n       document.getElementById(\"add-line-color\").value = e.target.value;\n       forgeTheTopology();\n      });\n      document.getElementById(\"default-edge-routing\").addEventListener(\"change\", (e) => {\n       PAGE_STATE.defaultEdgeRouting = e.target.value;\n       document.getElementById(\"add-line-routing\").value = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"apply-routing-all\").addEventListener(\"click\", () => {\n       const routing = document.getElementById(\"default-edge-routing\").value;\n       if (!confirm(`Apply \"${routing}\" routing to all ${EDGE_DATA.list.length} connections?`)) return;\n       pushUndo(\"apply routing to all\");\n       EDGE_DATA.list.forEach(edge => {\n        edge.routing = routing;\n       });\n       forgeTheTopology();\n      });\n      document.getElementById(\"selection-handle-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.selectionHandle = e.target.value;\n       forgeTheTopology();\n      });\n      document.getElementById(\"selection-handle-size\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.selectionHandleSize = parseInt(e.target.value) || 8;\n       forgeTheTopology();\n      });\n      document.getElementById(\"group-indicator-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.groupIndicator = e.target.value;\n       forgeTheTopology();\n      });\n      document.getElementById(\"canvas-gradient-top\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.canvasGradientTop = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"canvas-gradient-bottom\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.canvasGradientBottom = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"canvas-border-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.canvasBorder = e.target.value;\n       forgeTheTopology();\n      });\n      document.getElementById(\"canvas-grid-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.canvasGrid = e.target.value;\n       forgeTheTopology();\n      });\n      document.getElementById(\"canvas-grid-size\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.canvasGridSize = parseInt(e.target.value) || 50;\n       forgeTheTopology();\n      });\n      document.getElementById(\"canvas-grid-enabled\").addEventListener(\"change\", (e) => {\n       PAGE_STATE.canvasGridEnabled = e.target.checked;\n       forgeTheTopology();\n      });\n      document.getElementById(\"rack-frame-fill\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.rackFrameFill = e.target.value;\n       forgeTheTopology();\n      });\n      document.getElementById(\"rack-frame-stroke\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.rackFrameStroke = e.target.value;\n       forgeTheTopology();\n      });\n      document.getElementById(\"rack-line-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.rackLineColor = e.target.value;\n       forgeTheTopology();\n      });\n      document.getElementById(\"rack-grid-enabled\").addEventListener(\"change\", (e) => {\n       PAGE_STATE.rackGridEnabled = e.target.checked;\n       forgeTheTopology();\n      });\n      document.getElementById(\"rack-text-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.rackTextColor = e.target.value;\n       forgeTheTopology();\n      });\n      (function initializeResizers() {\n         const headerResizer = document.getElementById('header-resizer');\n         const sidebarResizer = document.getElementById('sidebar-resizer');\n         const mobileFooterResizer = document.getElementById('mobile-footer-resizer');\n         let isResizing = false;\n         let currentResizer = null;\n         let startY = 0;\n         let startX = 0;\n         let startHeight = 0;\n         let startWidth = 0;\n         function getClientPos(e) {\n           if (e.touches && e.touches.length > 0) {\n             return { x: e.touches[0].clientX, y: e.touches[0].clientY };\n           }\n           return { x: e.clientX, y: e.clientY };\n         }\n         function startResize(resizer, type, e) {\n           isResizing = true;\n           currentResizer = type;\n           const pos = getClientPos(e);\n           if (type === 'header') {\n             startY = pos.y;\n             startHeight = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--topbar-height'));\n           } else if (type === 'sidebar') {\n             startX = pos.x;\n             startWidth = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--sidebar-width'));\n           } else if (type === 'mobile-footer') {\n             startY = pos.y;\n             const currentVh = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--mobile-footer-height'));\n             startHeight = (currentVh / 100) * window.innerHeight;\n           }\n           resizer.classList.add('resizing');\n           document.body.classList.add('resizing');\n           document.body.style.cursor = (type === 'sidebar') ? 'col-resize' : 'row-resize';\n           e.preventDefault();\n         }\n         if (headerResizer) {\n           headerResizer.addEventListener('mousedown', (e) => startResize(headerResizer, 'header', e));\n           headerResizer.addEventListener('touchstart', (e) => startResize(headerResizer, 'header', e), { passive: false });\n         }\n         if (sidebarResizer) {\n           sidebarResizer.addEventListener('mousedown', (e) => startResize(sidebarResizer, 'sidebar', e));\n           sidebarResizer.addEventListener('touchstart', (e) => startResize(sidebarResizer, 'sidebar', e), { passive: false });\n         }\n         if (mobileFooterResizer) {\n           mobileFooterResizer.addEventListener('mousedown', (e) => startResize(mobileFooterResizer, 'mobile-footer', e));\n           mobileFooterResizer.addEventListener('touchstart', (e) => startResize(mobileFooterResizer, 'mobile-footer', e), { passive: false });\n         }\n         function handleMove(e) {\n           if (!isResizing) return;\n           const pos = getClientPos(e);\n           if (currentResizer === 'header') {\n             const deltaY = pos.y - startY;\n             const newHeight = Math.max(40, Math.min(150, startHeight + deltaY));\n             document.documentElement.style.setProperty('--topbar-height', newHeight + 'px');\n           } else if (currentResizer === 'sidebar') {\n             const deltaX = startX - pos.x;\n             const newWidth = Math.max(200, Math.min(800, startWidth + deltaX));\n             document.documentElement.style.setProperty('--sidebar-width', newWidth + 'px');\n           } else if (currentResizer === 'mobile-footer') {\n             const deltaY = startY - pos.y;\n             const newHeight = startHeight + deltaY;\n             const newVh = Math.max(15, Math.min(80, (newHeight / window.innerHeight) * 100));\n             document.documentElement.style.setProperty('--mobile-footer-height', newVh + 'vh');\n           }\n           e.preventDefault();\n         }\n         document.addEventListener('mousemove', handleMove);\n         document.addEventListener('touchmove', handleMove, { passive: false });\n         function handleEnd() {\n           if (isResizing) {\n             isResizing = false;\n             if (currentResizer === 'header') {\n               PAGE_STATE.topbarHeight = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--topbar-height'));\n               headerResizer.classList.remove('resizing');\n             } else if (currentResizer === 'sidebar') {\n               PAGE_STATE.sidebarWidth = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--sidebar-width'));\n               sidebarResizer.classList.remove('resizing');\n             } else if (currentResizer === 'mobile-footer') {\n               PAGE_STATE.mobileFooterHeight = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--mobile-footer-height'));\n               mobileFooterResizer.classList.remove('resizing');\n             }\n             document.body.classList.remove('resizing');\n             document.body.style.cursor = '';\n             currentResizer = null;\n           }\n         }\n         document.addEventListener('mouseup', handleEnd);\n         document.addEventListener('touchend', handleEnd);\n         document.addEventListener('touchcancel', handleEnd);\n       })();\n       document.getElementById(\"auto-ping-enabled\").addEventListener(\"change\", (e) => {\n        autoPingEnabled = e.target.checked;\n        PAGE_STATE.autoPingEnabled = autoPingEnabled;\n        document.getElementById(\"auto-ping-settings\").style.display = autoPingEnabled ? 'block' : 'none';\n        if (autoPingEnabled) {\n         startAutoPing();\n        } else {\n         stopAutoPing();\n        }\n       });\n       document.getElementById(\"auto-ping-interval\").addEventListener(\"change\", (e) => {\n        const newInterval = parseInt(e.target.value, 10);\n        if (newInterval >= 5 && newInterval <= 3600) {\n         autoPingInterval = newInterval;\n         PAGE_STATE.autoPingInterval = autoPingInterval;\n         if (autoPingEnabled) {\n          startAutoPing();\n         }\n        }\n       });\n       document.getElementById(\"import-data-file\").addEventListener(\"change\", async (e) => {\n        const file = e.target.files[0];\n        if (!file) return;\n        try {\n         const text = await file.text();\n         const data = JSON.parse(text);\n         if (!data.nodeData || !data.edgeData) {\n          alert(\"Invalid data file. Missing required fields.\");\n          return;\n         }\n         const confirmMsg = `This will replace all current data with the imported data.\\n\\nImporting:\\n- ${Object.keys(data.nodeData).length} nodes\\n- ${data.edgeData.list?.length || 0} connections\\n- ${data.documentTabs?.length || 1} tab(s)\\n\\nContinue?`;         if (!confirm(confirmMsg)) {\n          e.target.value = \"\";\n          return;\n         }\n\t\t pushUndo('import json');\n         NODE_DATA = data.nodeData || {};\n         EDGE_DATA = data.edgeData || {\n          list: []\n         };\n         EDGE_LEGEND = data.edgeLegend || {};\n         RECT_DATA = data.rectData || { list: [] };\n         TEXT_DATA = data.textData || { list: [] };\n         savedPositions = data.nodePositions || {};\n         savedSizes = data.nodeSizes || {};\n         savedStyles = data.nodeStyles || {};\n         if (data.page) {\n          PAGE_STATE = Object.assign({}, DEFAULT_PAGE_STATE, data.page);\n          wieldThePower();\n         }\n      if (data.autoPingEnabled !== undefined) {\n      autoPingEnabled = data.autoPingEnabled;\n      PAGE_STATE.autoPingEnabled = autoPingEnabled;\n      document.getElementById(\"auto-ping-enabled\").checked = autoPingEnabled;\n      }\n      if (data.autoPingInterval !== undefined) {\n      autoPingInterval = data.autoPingInterval;\n      PAGE_STATE.autoPingInterval = autoPingInterval;\n      document.getElementById(\"auto-ping-interval\").value = autoPingInterval;\n      }\n         if (data.canvas) {\n          canvasState.zoom = data.canvas.zoom || 1;\n          canvasState.panX = data.canvas.panX || 0;\n          canvasState.panY = data.canvas.panY || 0;\n         }\n         if (data.page?.title) {\n         document.title = data.page.title;\n         document.querySelector(\".editable-page-title\", ).textContent = data.page.title;\n        }\n        if (data.savedStyleSets) {\n          savedStyleSets = data.savedStyleSets;\n        }\n        if (data.documentTabs) {\n         documentTabs = data.documentTabs;\n         currentTabIndex = data.currentTabIndex || 0;\n        }\n        if (data.savedTopologyView) {\n         savedTopologyView = data.savedTopologyView;\n        }\n        if (data.encryptedSections) {\n         encryptedSections = data.encryptedSections;\n        }\n        if (data.auditLog && Array.isArray(data.auditLog)) {\n         auditLog = data.auditLog;\n         try {\n           localStorage.setItem(AUDIT_STORAGE_KEY, JSON.stringify(auditLog));\n         } catch (e) {\n           console.warn(\"Failed to sync audit log to localStorage:\", e);\n         }\n        }\n        Object.values(NODE_DATA).forEach(node => {\n          if (!node.tags) node.tags = [];\n          if (!node.notes) node.notes = [];\n        });\n        EDGE_DATA.list.forEach(edge => {\n          if (!edge.notes) edge.notes = [];\n        });\n        forgeTheTopology();\n        if (typeof forgeTheLegend === 'function') forgeTheLegend();\n        if (typeof updateZoneLegend === 'function') updateZoneLegend();\n        logAuditEvent(\"import\", `Imported JSON: ${file.name} (${Object.keys(data.nodeData).length} nodes, ${data.edgeData.list?.length || 0} connections)`);\n        updateViewBox();\n      if (autoPingEnabled) {\n      startAutoPing();\n      } else {\n      stopAutoPing();\n      }\n         const nodeIds = Object.keys(NODE_DATA);\n         if (nodeIds.length > 0) {\n          claimTheImmortal(nodeIds[0]);\n         } else {\n          document.getElementById(\"node-panel\").style.display = \"none\";\n          document.getElementById(\"edge-panel\").style.display = \"none\";\n          document.getElementById(\"topology-toolbar\").style.display = \"none\";\n         }\n         alert(\"Data imported successfully!\");\n         e.target.value = \"\";\n        } catch (err) {\n         console.error(\"Import error:\", err);\n         alert(`Failed to import data: ${err.message}`);\n         e.target.value = \"\";\n        }\n       });\n       const saveHelpBtn = document.getElementById(\"save-help-btn\");\n       const saveInfoModal = document.getElementById(\"save-info-modal\");\n       const saveInfoClose = document.getElementById(\"save-info-close\");\n       saveHelpBtn.addEventListener(\"click\", () => {\n        saveInfoModal.classList.add(\"active\");\n       });\n       saveInfoClose.addEventListener(\"click\", () => {\n        saveInfoModal.classList.remove(\"active\");\n       });\n       saveInfoModal.addEventListener(\"click\", (e) => {\n       if (e.target === saveInfoModal) {\n        saveInfoModal.classList.remove(\"active\");\n       }\n      });\n      document.querySelectorAll(\".help-tab\").forEach(tab => {\n        tab.addEventListener(\"click\", () => {\n          document.querySelectorAll(\".help-tab\").forEach(t => { t.style.background = \"var(--panel)\"; t.style.color = \"var(--text-main)\"; });\n          tab.style.background = \"var(--accent)\"; tab.style.color = \"var(--bg)\";\n          document.querySelectorAll(\".help-tab-content\").forEach(c => c.style.display = \"none\");\n          document.getElementById(\"help-tab-\" + tab.dataset.tab).style.display = \"block\";\n        });\n      });\n      async function deriveKey(password, salt) {\n      const encoder = new TextEncoder();\n      const keyMaterial = await crypto.subtle.importKey(\n      \"raw\",\n      encoder.encode(password),\n      \"PBKDF2\",\n      false,\n      [\"deriveKey\"]\n      );\n      return crypto.subtle.deriveKey(\n      {\n      name: \"PBKDF2\",\n      salt: salt,\n      iterations: 200000,\n      hash: \"SHA-256\"\n      },\n      keyMaterial,\n      {\n      name: \"AES-GCM\",\n      length: 256\n      },\n      false,\n      [\"encrypt\", \"decrypt\"]\n      );\n      }\n       async function encryptData(data, password) {\n      const encoder = new TextEncoder();\n      const salt = crypto.getRandomValues(new Uint8Array(16));\n      const iv   = crypto.getRandomValues(new Uint8Array(12));\n      const key = await deriveKey(password, salt);\n      const encrypted = await crypto.subtle.encrypt(\n      { name: \"AES-GCM\", iv },\n      key,\n      encoder.encode(data)\n      );\n      const encryptedU8 = new Uint8Array(encrypted);\n      const result = new Uint8Array(salt.length + iv.length + encryptedU8.length);\n      result.set(salt, 0);\n      result.set(iv, salt.length);\n      result.set(encryptedU8, salt.length + iv.length);\n      return \"ENCRYPTED:\" + u8ToBase64(result);\n      }\n       async function decryptData(encryptedData, password) {\n      const base64Data = encryptedData.replace(\"ENCRYPTED:\", \"\");\n      const fullData   = base64ToU8(base64Data);\n      const salt      = fullData.slice(0, 16);\n      const iv        = fullData.slice(16, 28);\n      const encrypted = fullData.slice(28);\n      const key = await deriveKey(password, salt);\n      const decrypted = await crypto.subtle.decrypt(\n      { name: \"AES-GCM\", iv },\n      key,\n      encrypted\n      );\n      const decoder = new TextDecoder();\n      return decoder.decode(decrypted);\n      }\n       function isEncrypted(data) {\n        return typeof data === \"string\" && data.startsWith(\"ENCRYPTED:\");\n       }\n       function captureTheQuickening() {\n      const currentTab = documentTabs[currentTabIndex];\n      currentTab.nodes = JSON.parse(JSON.stringify(NODE_DATA));\n      currentTab.edges = JSON.parse(JSON.stringify(EDGE_DATA));\n      currentTab.positions = JSON.parse(JSON.stringify(savedPositions));\n      currentTab.sizes = JSON.parse(JSON.stringify(savedSizes));\n      currentTab.styles = JSON.parse(JSON.stringify(savedStyles));\n      currentTab.legend = JSON.parse(JSON.stringify(EDGE_LEGEND));\n      currentTab.rects = JSON.parse(JSON.stringify(RECT_DATA));\n      currentTab.texts = JSON.parse(JSON.stringify(TEXT_DATA));\n      currentTab.pageState = JSON.parse(JSON.stringify(PAGE_STATE));\n      return {\n      nodeData: NODE_DATA,\n       edgeData: EDGE_DATA,\n       rectData: RECT_DATA,\n       textData: TEXT_DATA,\n       edgeLegend: EDGE_LEGEND,\n       zoneLegend: ZONE_LEGEND,\n       zonePresets: ZONE_PRESETS,\n       nodePositions: savedPositions,\n       nodeSizes: savedSizes,\n       nodeStyles: savedStyles,\n       iconCache: IconLibrary.iconCache,\n       page: PAGE_STATE,\n       autoPingEnabled: autoPingEnabled,\n       autoPingInterval: autoPingInterval,\n       canvas: {\n         zoom: canvasState.zoom,\n         panX: canvasState.panX,\n         panY: canvasState.panY,\n       },\n       savedTopologyView: savedTopologyView,\n       documentTabs: documentTabs,\n       currentTabIndex: currentTabIndex,\n       encryptedSections: encryptedSections,\n\t   auditLog: auditLog,\n       savedStyleSets: savedStyleSets,\n       };\n      }\n       function assembleTheImmortalForm() {\n\t   const canvasHintEl = document.getElementById(\"canvas-hint\");\n       const savedHintHTML = canvasHintEl ? canvasHintEl.innerHTML : \"\";\n       if (canvasHintEl) canvasHintEl.innerHTML = DEFAULT_CANVAS_HINT;\n       const clone = document.documentElement.cloneNode(true);\n       [\"edge-legend-mini\", \"minimap-mini\", \"draw-toolbar-mini\", \"topology-toolbar-mini\"].forEach(id => {\n        const all = clone.querySelectorAll(\"#\" + id);\n        for (let i = 1; i < all.length; i++) {\n         all[i].remove();\n        }\n       });\n       const addLineSelect = clone.querySelector(\"#add-line-select\");\n       if (addLineSelect) addLineSelect.innerHTML = \"\";\n       const minimapSvg = clone.querySelector(\"#minimap\");\n       if (minimapSvg) {\n        minimapSvg.querySelectorAll(\".minimap-node, .minimap-edge\").forEach(el => el.remove());\n       }\n       const canvasGrid = clone.querySelector(\"#canvas-grid\");\n       if (canvasGrid) canvasGrid.innerHTML = \"\";\n       const mainSvg = clone.querySelector(\"#map\");\n       if (mainSvg) mainSvg.innerHTML = \"\";\n       const nodeScript = clone.querySelector(\"#nodes-json\");\n        if (nodeScript) {\n         nodeScript.textContent = JSON.stringify(NODE_DATA, null, 2);\n        }\n        let stateScript = clone.querySelector(\"#topology-state\");\n        if (!stateScript) {\n         stateScript = document.createElement(\"script\");\n         stateScript.id = \"topology-state\";\n         stateScript.type = \"application/json\";\n         const body = clone.querySelector(\"body\") || clone;\n         body.appendChild(stateScript);\n        }\n        stateScript.textContent = JSON.stringify(captureTheQuickening(), null, 2);\n        return \" <!DOCTYPE html> \\n \" + clone.outerHTML;\n       }\n       async function becomeImmortal() {\n        const encryptEnabled = document.getElementById(\"encrypt-toggle\").checked;\n        let stateData = JSON.stringify(captureTheQuickening(), null, 2);\n        if (encryptEnabled) {\n         const password = prompt(\"Enter a password to encrypt your data:\\n(Remember this password! You will need it to open this file and its non recoverable!)\");\n         if (!password) {\n          alert(\"Encryption cancelled. File not saved.\");\n          return;\n         }\n         const confirmPassword = prompt(\"Confirm your password:\");\n         if (password !== confirmPassword) {\n          alert(\"Passwords do not match. File not saved.\");\n          return;\n         }\n         try {\n          stateData = await encryptData(stateData, password);\n         } catch (e) {\n          alert(\"Encryption failed: \" + e.message);\n          return;\n         }\n        }\n       const canvasHintEl = document.getElementById(\"canvas-hint\");\n       const savedHintHTML = canvasHintEl ? canvasHintEl.innerHTML : \"\";\n       if (canvasHintEl) canvasHintEl.innerHTML = DEFAULT_CANVAS_HINT;\n       const clone = document.documentElement.cloneNode(true);\n       [\"edge-legend-mini\", \"minimap-mini\", \"draw-toolbar-mini\", \"topology-toolbar-mini\"].forEach(id => {\n        const all = clone.querySelectorAll(\"#\" + id);\n        for (let i = 1; i < all.length; i++) {\n         all[i].remove();\n        }\n       });\n       const selectsToClear = [\"#add-line-select\", \"#new-edge-from\", \"#new-edge-to\", \"#assigned-rack-select\"];\n       selectsToClear.forEach(sel => {\n        const el = clone.querySelector(sel);\n        if (el) el.innerHTML = \"\";\n       });\n       const minimapSvg = clone.querySelector(\"#minimap\");\n       if (minimapSvg) minimapSvg.querySelectorAll(\".minimap-node, .minimap-edge\").forEach(el => el.remove());\n       const mainSvg = clone.querySelector(\"#map\");\n       if (mainSvg) mainSvg.innerHTML = \"\";\n       const edgeLegendContent = clone.querySelector(\"#edge-legend-content\");\n       if (edgeLegendContent) edgeLegendContent.innerHTML = \"\";\n       const nodeTags = clone.querySelector(\"#node-tags\");\n       if (nodeTags) nodeTags.innerHTML = \"\";\n       const nodeNotes = clone.querySelector(\"#node-notes\");\n       if (nodeNotes) nodeNotes.innerHTML = \"\";\n       const edgeNotes = clone.querySelector(\"#edge-notes\");\n       if (edgeNotes) edgeNotes.innerHTML = \"\";\n       const tabBar = clone.querySelector(\"#tab-bar\");\n       if (tabBar) tabBar.innerHTML = \"\";\n       const rollbackList = clone.querySelector(\"#rollback-list\");\n       if (rollbackList) rollbackList.innerHTML = \"\";\n       const auditList = clone.querySelector(\"#audit-log-list\");\n       if (auditList) auditList.innerHTML = \"\";\n       clone.querySelectorAll(\".ping-indicator\").forEach(el => el.remove());\n       const nodeScript = clone.querySelector(\"#nodes-json\");\n        if (nodeScript) {\n         if (encryptEnabled) {\n          nodeScript.textContent = JSON.stringify({}, null, 2);\n         } else {\n          nodeScript.textContent = JSON.stringify(NODE_DATA, null, 2);\n         }\n        }\n        let stateScript = clone.querySelector(\"#topology-state\");\n        if (!stateScript) {\n         stateScript = document.createElement(\"script\");\n         stateScript.id = \"topology-state\";\n         stateScript.type = \"application/json\";\n         const body = clone.querySelector(\"body\") || clone;\n         body.appendChild(stateScript);\n        }\n        stateScript.textContent = stateData;\n        const html = \"<!DOCTYPE html> \\n \" + clone.outerHTML;\n        const blob = new Blob([html], {\n         type: \"text/html\"\n        });\n        const url = URL.createObjectURL(blob);\n        const a = document.createElement(\"a\");\n        a.href = url;\n        const safeTitle = (PAGE_STATE.title || document.title || \"network-topology\").toLowerCase().replace(/[^a-z0-9\\-]+/g, \"-\");\n        a.download = safeTitle + \".html\";\n        document.body.appendChild(a);\n        a.click();\n        document.body.removeChild(a);\n        URL.revokeObjectURL(url);\n\t\tif (canvasHintEl) canvasHintEl.innerHTML = savedHintHTML;\n        saveRollbackVersion(\"Manual save\");\n\t\tlogAuditEvent(\"save\", `File saved: ${safeTitle}.html`);\n       }\n      function captureState() {\n\t\t const clone = typeof structuredClone === 'function' \n\t\t   ? (o) => structuredClone(o)\n\t\t   : (o) => JSON.parse(JSON.stringify(o));\n\t\t return {\n\t\t  nodes: clone(NODE_DATA),\n\t\t  edges: clone(EDGE_DATA),\n\t\t  positions: clone(savedPositions),\n\t\t  sizes: clone(savedSizes),\n\t\t  styles: clone(savedStyles),\n\t\t  legend: clone(EDGE_LEGEND),\n\t\t  rects: clone(RECT_DATA),\n\t\t  texts: clone(TEXT_DATA)\n\t\t };\n\t\t}\n      let lastUndoPush = 0;\n\t   function pushUndo(action = \"\") {\n\t   const now = Date.now();\n\t   if (now - lastUndoPush < 100 && undoStack.length > 0) {\n\t    return;\n\t   }\n\t   lastUndoPush = now;\n\t   const state = captureState();\n\t   undoStack.push(state);\n       if (undoStack.length > MAX_UNDO_STACK) {\n        undoStack.shift();\n       }\n       redoStack = [];\n       updateUndoButtons();\n       if (action) {\n        const actionTypeMap = {\n          \"create node\": \"node\",\n          \"delete node\": \"node\",\n          \"add node\": \"node\",\n          \"edit\": \"node\",\n          \"clone node\": \"node\",\n          \"paste node\": \"node\",\n          \"move nodes\": \"node\",\n          \"nudge\": \"node\",\n          \"nudge nodes\": \"node\",\n          \"align nodes\": \"node\",\n          \"distribute nodes\": \"node\",\n          \"snap to grid\": \"node\",\n          \"toggle group\": \"node\",\n          \"toggle lock\": \"node\",\n          \"create rack\": \"rack\",\n          \"add rack\": \"rack\",\n          \"edit rack\": \"rack\",\n          \"edit mac\": \"rack\",\n          \"edit U height\": \"rack\",\n          \"change rack capacity\": \"rack\",\n          \"change assigned rack\": \"rack\",\n          \"add connection\": \"connection\",\n          \"delete connection\": \"connection\",\n          \"delete edge\": \"connection\",\n          \"clone edge\": \"connection\",\n          \"paste edge\": \"connection\",\n          \"style change\": \"style\",\n          \"change layer\": \"layer\",\n          \"add text\": \"text\",\n          \"edit text\": \"text\",\n          \"delete text\": \"text\",\n          \"clone text\": \"text\",\n          \"paste text\": \"text\",\n          \"draw zone\": \"zone\",\n          \"delete zone\": \"zone\",\n          \"delete rect\": \"zone\",\n          \"clone rect\": \"zone\",\n          \"paste rect\": \"zone\",\n          \"change zone line style\": \"zone\",\n          \"delete selected\": \"bulk\",\n          \"clone selected\": \"bulk\",\n        };\n        const type = actionTypeMap[action] || \"edit\";\n        logAuditEvent(type, action);\n       }\n      }\n      function undo() {\n       if (undoStack.length === 0) return;\n       const currentState = captureState();\n       redoStack.push(currentState);\n       const previousState = undoStack.pop();\n       restoreState(previousState);\n       updateUndoButtons();\n       logAuditEvent(\"undo\", \"Undo action performed\");\n      }\n      function redo() {\n       if (redoStack.length === 0) return;\n       logAuditEvent(\"redo\", \"Redo action performed\");\n       const currentState = captureState();\n       undoStack.push(currentState);\n       const nextState = redoStack.pop();\n       restoreState(nextState);\n       updateUndoButtons();\n      }\n      function restoreState(state) {\n      NODE_DATA = state.nodes;\n       EDGE_DATA = state.edges;\n       savedPositions = state.positions;\n       savedSizes = state.sizes;\n       savedStyles = state.styles;\n       EDGE_LEGEND = state.legend;\n       RECT_DATA = state.rects || { list: [] };\n       TEXT_DATA = state.texts || { list: [] };\n       forgeTheTopology();\n       if (currentNodeId && NODE_DATA[currentNodeId]) {\n       claimTheImmortal(currentNodeId);\n       } else if (currentEdgeId) {\n       selectTheConnection(currentEdgeId);\n       }\n      }\n      function updateUndoButtons() {\n       const undoBtn = document.getElementById(\"undo-btn\");\n       const redoBtn = document.getElementById(\"redo-btn\");\n       if (undoBtn) {\n        undoBtn.disabled = undoStack.length === 0;\n        undoBtn.style.opacity = undoStack.length === 0 ? \"0.5\" : \"1\";\n       }\n       if (redoBtn) {\n        redoBtn.disabled = redoStack.length === 0;\n        redoBtn.style.opacity = redoStack.length === 0 ? \"0.5\" : \"1\";\n       }\n      }\n      function clearSelection() {\n       selectedNodes.clear();\n       selectedEdges.clear();\n       selectedRects.clear();\n       selectedTexts.clear();\n       updateAllSelections();\n      }\n      function updateAllSelections() {\n      updateNodeSelection();\n      clearSearchHighlight();\n      document.querySelectorAll(\".edge\").forEach(el => {\n      const edgeId = el.dataset.edgeId;\n      if (selectedEdges.has(edgeId)) {\n      el.style.filter = \"drop-shadow(0 0 8px #4fd1c5)\";\n      } else {\n      el.style.filter = \"\";\n      }\n      });\n      document.querySelectorAll(\".rect-group\").forEach(el => {\n      const rectId = el.dataset.rectId;\n      if (selectedRects.has(rectId)) {\n      el.style.filter = \"drop-shadow(0 0 8px #4fd1c5)\";\n      } else {\n      el.style.filter = \"\";\n      }\n      });\n      document.querySelectorAll(\".text-group\").forEach(el => {\n      const textId = el.dataset.textId;\n      if (selectedTexts.has(textId)) {\n      el.style.filter = \"drop-shadow(0 0 8px #4fd1c5)\";\n      } else {\n      el.style.filter = \"\";\n      }\n      });\n      const total = selectedNodes.size + selectedEdges.size + selectedRects.size + selectedTexts.size;\n      const bulkToolbar = document.getElementById(\"bulk-toolbar\");\n      const bulkToolbarMobile = document.getElementById(\"bulk-toolbar-mobile\");\n      const bulkCount = document.getElementById(\"bulk-count\");\n      const bulkCountMobile = document.getElementById(\"bulk-count-mobile\");\n      if (total > 0) {\n      if (bulkToolbar) bulkToolbar.style.display = \"flex\";\n      if (bulkToolbarMobile) bulkToolbarMobile.style.display = \"flex\";\n      if (bulkCount) bulkCount.textContent = total;\n      if (bulkCountMobile) bulkCountMobile.textContent = total;\n      } else {\n      if (bulkToolbar) bulkToolbar.style.display = \"none\";\n      if (bulkToolbarMobile) bulkToolbarMobile.style.display = \"none\";\n      }\n      }\n      function updateNodeSelection() {\n       if (isViewOnly()) {\n        selectedNodes.clear();\n        selectedRects.clear();\n        selectedTexts.clear();\n        const bulkToolbar = document.getElementById(\"bulk-toolbar\");\n        const bulkToolbarMobile = document.getElementById(\"bulk-toolbar-mobile\");\n        if (bulkToolbar) bulkToolbar.style.display = \"none\";\n        if (bulkToolbarMobile) bulkToolbarMobile.style.display = \"none\";\n        return;\n       }\n       document.querySelectorAll(\".node-group\").forEach(node => {\n        const nodeId = node.dataset.nodeId;\n        if (selectedNodes.has(nodeId)) {\n         node.classList.add(\"selected\");\n        } else {\n         node.classList.remove(\"selected\");\n        }\n       });\n       const bulkToolbar = document.getElementById(\"bulk-toolbar\");\n       const bulkToolbarMobile = document.getElementById(\"bulk-toolbar-mobile\");\n       const bulkCount = document.getElementById(\"bulk-count\");\n       const bulkCountMobile = document.getElementById(\"bulk-count-mobile\");\n       const bulkCountModal = document.getElementById(\"bulk-count-modal\");\n       if (selectedNodes.size > 0) {\n        if (bulkToolbar) bulkToolbar.style.display = \"flex\";\n        if (bulkToolbarMobile) bulkToolbarMobile.style.display = \"flex\";\n        if (bulkCount) bulkCount.textContent = selectedNodes.size;\n        if (bulkCountMobile) bulkCountMobile.textContent = selectedNodes.size;\n        if (bulkCountModal) bulkCountModal.textContent = selectedNodes.size;\n       } else {\n        if (bulkToolbar) bulkToolbar.style.display = \"none\";\n        if (bulkToolbarMobile) bulkToolbarMobile.style.display = \"none\";\n        const modal = document.getElementById(\"bulk-actions-modal\");\n        if (modal) modal.style.display = \"none\";\n       }\n      }\n      function deleteSelected() {\n       const total = selectedNodes.size + selectedEdges.size + selectedRects.size + selectedTexts.size;\n       if (total === 0) return;\n       let nodesInsideRacks = [];\n       selectedNodes.forEach(nodeId => {\n        if (NODE_DATA[nodeId]?.isRack) {\n         Object.entries(NODE_DATA).forEach(([id, n]) => {\n          if (n.assignedRack === nodeId) nodesInsideRacks.push(n.name || id);\n         });\n        }\n       });\n       let message = `Delete ${total} selected item(s)?`;\n       if (nodesInsideRacks.length > 0) {\n        message += `\\n\\nThis will also delete ${nodesInsideRacks.length} node(s) inside rack(s):\\n• ${nodesInsideRacks.join('\\n• ')}`;\n       }\n       challengeTheImmortal(message, () => {\n        pushUndo(\"delete selected\");\n        selectedNodes.forEach(nodeId => {\n         if (NODE_DATA[nodeId]?.isRack) {\n          Object.keys(NODE_DATA).forEach(id => {\n           if (NODE_DATA[id]?.assignedRack === nodeId) {\n            EDGE_DATA.list = EDGE_DATA.list.filter(e => e.from !== id && e.to !== id);\n            delete NODE_DATA[id];\n            delete savedPositions[id];\n            delete savedSizes[id];\n            delete savedStyles[id];\n           }\n          });\n         }\n         delete NODE_DATA[nodeId];\n         delete savedPositions[nodeId];\n         delete savedSizes[nodeId];\n         delete savedStyles[nodeId];\n         EDGE_DATA.list = EDGE_DATA.list.filter(e => e.from !== nodeId && e.to !== nodeId);\n        });\n        selectedEdges.forEach(edgeId => {\n         EDGE_DATA.list = EDGE_DATA.list.filter(e => e.id !== edgeId);\n        });\n        selectedRects.forEach(rectId => {\n         RECT_DATA.list = RECT_DATA.list.filter(r => r.id !== rectId);\n        });\n        selectedTexts.forEach(textId => {\n         TEXT_DATA.list = TEXT_DATA.list.filter(t => t.id !== textId);\n        });\n        clearSelection();\n        forgeTheTopology();\n       });\n      }\n      function startSelection(event) {\n       const svgEl = document.getElementById(\"map\");\n       const pt = svgEl.createSVGPoint();\n       pt.x = event.clientX;\n       pt.y = event.clientY;\n       const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n       isSelecting = true;\n       selectionStart = { x: svgP.x, y: svgP.y };\n       if (!selectionRect || !selectionRect.parentNode) {\n        selectionRect = document.createElementNS(\"http://www.w3.org/2000/svg\", \"rect\");\n        selectionRect.setAttribute(\"fill\", selectionBoxStyle.fillColor);\n        selectionRect.setAttribute(\"fill-opacity\", selectionBoxStyle.fillOpacity);\n        selectionRect.setAttribute(\"stroke\", selectionBoxStyle.strokeColor);\n        selectionRect.setAttribute(\"stroke-width\", selectionBoxStyle.strokeWidth);\n        selectionRect.setAttribute(\"stroke-dasharray\", selectionBoxStyle.strokeDasharray);\n        selectionRect.style.pointerEvents = \"none\";\n        svgEl.appendChild(selectionRect);\n       }\n       clearSelection();\n       preDragSelectedNodes = new Set(selectedNodes);\n       preDragSelectedEdges = new Set(selectedEdges);\n       preDragSelectedRects = new Set(selectedRects);\n       preDragSelectedTexts = new Set(selectedTexts);\n      }\n      function updateSelection(event) {\n       if (!isSelecting || !selectionStart) return;\n       const svgEl = document.getElementById(\"map\");\n       const pt = svgEl.createSVGPoint();\n       pt.x = event.clientX;\n       pt.y = event.clientY;\n       const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n       const x = Math.min(selectionStart.x, svgP.x);\n       const y = Math.min(selectionStart.y, svgP.y);\n       const width = Math.abs(svgP.x - selectionStart.x);\n       const height = Math.abs(svgP.y - selectionStart.y);\n       selectionRect.setAttribute(\"x\", x);\n       selectionRect.setAttribute(\"y\", y);\n       selectionRect.setAttribute(\"width\", width);\n       selectionRect.setAttribute(\"height\", height);\n       selectionRect.style.display = \"block\";\n       const box = { x, y, width, height };\n       Object.entries(savedPositions).forEach(([nodeId, pos]) => {\n        const size = savedSizes[nodeId] || 50;\n        const halfSize = size / 2;\n        const nodeBox = { x: pos.x - halfSize, y: pos.y - halfSize, width: size, height: size };\n        if (boxesIntersect(box, nodeBox)) {\n         selectedNodes.add(nodeId);\n        } else if (!preDragSelectedNodes.has(nodeId)) {\n         selectedNodes.delete(nodeId);\n        }\n       });\n       EDGE_DATA.list.forEach(edge => {\n        if (!edge.points || edge.points.length === 0) return;\n        for (let i = 0; i < edge.points.length - 1; i++) {\n         const p1 = edge.points[i];\n         const p2 = edge.points[i + 1];\n         if (lineIntersectsBox(p1.x, p1.y, p2.x, p2.y, box)) {\n          selectedEdges.add(edge.id);\n          return;\n         }\n        }\n        if (!preDragSelectedEdges.has(edge.id)) {\n         selectedEdges.delete(edge.id);\n        }\n       });\n       RECT_DATA.list.forEach(rect => {\n        const rectBox = { x: rect.x, y: rect.y, width: rect.width, height: rect.height };\n        if (boxesIntersect(box, rectBox)) {\n         selectedRects.add(rect.id);\n        } else if (!preDragSelectedRects.has(rect.id)) {\n         selectedRects.delete(rect.id);\n        }\n       });\n       TEXT_DATA.list.forEach(text => {\n        const fontSize = text.fontSize || 18;\n        const textBox = { x: text.x - 50, y: text.y - fontSize, width: 100, height: fontSize * 1.5 };\n        if (boxesIntersect(box, textBox)) {\n         selectedTexts.add(text.id);\n        } else if (!preDragSelectedTexts.has(text.id)) {\n         selectedTexts.delete(text.id);\n        }\n       });\n       updateAllSelectionVisuals();\n      }\n      function endSelection() {\n       isSelecting = false;\n       selectionStart = null;\n       if (selectionRect) {\n        selectionRect.style.display = \"none\";\n       }\n      }\n      function boxesIntersect(a, b) {\n       return !(a.x + a.width < b.x || b.x + b.width < a.x || a.y + a.height < b.y || b.y + b.height < a.y);\n      }\n      function lineIntersectsBox(x1, y1, x2, y2, box) {\n       if (pointInBox(x1, y1, box) || pointInBox(x2, y2, box)) return true;\n       const lines = [\n        [box.x, box.y, box.x + box.width, box.y],\n        [box.x + box.width, box.y, box.x + box.width, box.y + box.height],\n        [box.x + box.width, box.y + box.height, box.x, box.y + box.height],\n        [box.x, box.y + box.height, box.x, box.y]\n       ];\n       for (const [bx1, by1, bx2, by2] of lines) {\n        if (linesIntersect(x1, y1, x2, y2, bx1, by1, bx2, by2)) return true;\n       }\n       return false;\n      }\n      function pointInBox(x, y, box) {\n       return x >= box.x && x <= box.x + box.width && y >= box.y && y <= box.y + box.height;\n      }\n      function linesIntersect(x1, y1, x2, y2, x3, y3, x4, y4) {\n       const denom = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1);\n       if (Math.abs(denom) < 0.0001) return false;\n       const ua = ((x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)) / denom;\n       const ub = ((x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)) / denom;\n       return ua >= 0 && ua <= 1 && ub >= 0 && ub <= 1;\n      }\n      function updateAllSelectionVisuals() {\n       document.querySelectorAll(\".node-group\").forEach(node => {\n        const nodeId = node.dataset.nodeId;\n        if (selectedNodes.has(nodeId)) {\n         node.classList.add(\"selected\");\n        } else {\n         node.classList.remove(\"selected\");\n        }\n       });\n       document.querySelectorAll(\".edge\").forEach(edge => {\n        const edgeId = edge.dataset.edgeId;\n        if (selectedEdges.has(edgeId)) {\n         edge.style.filter = \"drop-shadow(0 0 6px #4fd1c5)\";\n        } else {\n         edge.style.filter = \"\";\n        }\n       });\n       document.querySelectorAll(\".rect-group rect\").forEach(el => {\n        const rectId = el.closest(\".rect-group\")?.dataset?.rectId;\n        if (selectedRects.has(rectId)) {\n         el.style.filter = \"drop-shadow(0 0 8px #4fd1c5)\";\n        } else {\n         el.style.filter = \"\";\n        }\n       });\n       document.querySelectorAll(\".text-group, .text-element\").forEach(el => {\n        const textId = el.dataset?.textId;\n        if (selectedTexts.has(textId)) {\n         el.style.filter = \"drop-shadow(0 0 8px #4fd1c5)\";\n        } else {\n         el.style.filter = \"\";\n        }\n       });\n       const total = selectedNodes.size + selectedEdges.size + selectedRects.size + selectedTexts.size;\n       const bulkToolbar = document.getElementById(\"bulk-toolbar\");\n       const bulkToolbarMobile = document.getElementById(\"bulk-toolbar-mobile\");\n       const bulkCount = document.getElementById(\"bulk-count\");\n       const bulkCountMobile = document.getElementById(\"bulk-count-mobile\");\n       if (total > 0) {\n        if (bulkToolbar) bulkToolbar.style.display = \"flex\";\n        if (bulkToolbarMobile) bulkToolbarMobile.style.display = \"flex\";\n        if (bulkCount) bulkCount.textContent = total;\n        if (bulkCountMobile) bulkCountMobile.textContent = total;\n       } else {\n        if (bulkToolbar) bulkToolbar.style.display = \"none\";\n        if (bulkToolbarMobile) bulkToolbarMobile.style.display = \"none\";\n       }\n      }\n      function cloneNode(sourceId, skipUndo) {\n       const source = NODE_DATA[sourceId];\n       if (!source) return;\n       if (!skipUndo) pushUndo(\"clone node\");\n       let baseName = source.name;\n       let copyNum = 0;\n       let newName = baseName + \" copy\";\n       while (Object.values(NODE_DATA).some(n => n.name === newName)) {\n        copyNum++;\n        newName = baseName + \" copy \" + copyNum;\n       }\n       const newId = newName.toLowerCase().replace(/[^a-z0-9]+/g, \"-\");\n       NODE_DATA[newId] = {\n        shape: source.shape,\n        name: newName,\n        ip: source.ip,\n        role: source.role,\n        tags: [...source.tags],\n        notes: [...source.notes],\n        mac: source.mac || \"\",\n        rackUnit: source.rackUnit || \"\",\n        uHeight: source.uHeight || \"1\",\n        ping: source.ping ? JSON.parse(JSON.stringify(source.ping)) : {\n         enabled: false,\n         protocol: 'http',\n         customUrl: '',\n         timeout: 3000,\n         status: 'unknown',\n         lastCheck: null\n        },\n        layer: source.layer || \"physical\",\n        assignedRack: source.assignedRack || \"\",\n        rackCapacity: source.rackCapacity || \"42\",\n        isRack: source.isRack || false,\n        fovEnabled: source.fovEnabled || false,\n        fovAngle: source.fovAngle || 90,\n        fovDistance: source.fovDistance || 150,\n        fovInnerRadius: source.fovInnerRadius || 0,\n        fovRotation: source.fovRotation || 0,\n        fovColor: source.fovColor || \"#f59e0b\",\n        fovOpacity: source.fovOpacity || 20,\n        fovGradient: source.fovGradient || false,\n        fovBorderColor: source.fovBorderColor || \"#f59e0b\",\n        fovBorderWidth: source.fovBorderWidth !== undefined ? source.fovBorderWidth : 2,\n        fovBorderStyle: source.fovBorderStyle || \"solid\",\n        fovBorderOpacity: source.fovBorderOpacity !== undefined ? source.fovBorderOpacity : 100,\n        fovLabel: source.fovLabel || \"\",\n        fovLabelPosition: source.fovLabelPosition || \"center\",\n        fovLabelSize: source.fovLabelSize || 14,\n        fovLabelColor: source.fovLabelColor || \"#ffffff\",\n        fovLabelBold: source.fovLabelBold || false,\n        fovLabelBg: source.fovLabelBg || false,\n        fovLabelBgColor: source.fovLabelBgColor || \"#000000\",\n        fovLabelOffsetX: source.fovLabelOffsetX || 0,\n        fovLabelOffsetY: source.fovLabelOffsetY || 0,\n        fovAnimate: source.fovAnimate || false,\n        fovAnimationType: source.fovAnimationType || \"sweep\",\n        fovSweep: source.fovSweep || 120,\n        fovSpeed: source.fovSpeed || 4\n       };\n       if (source.isRack) {\n        const childNodes = Object.entries(NODE_DATA).filter(([id, n]) =>\n         id !== newId && n.assignedRack === sourceId\n        );\n        childNodes.forEach(([childId, childNode]) => {\n         let childBaseName = childNode.name;\n         let c = 0;\n         let childNewName = childBaseName + \" copy\";\n         while (Object.values(NODE_DATA).some(n => n.name === childNewName)) {\n          c++;\n          childNewName = childBaseName + \" copy \" + c;\n         }\n         const childNewId = childNewName.toLowerCase().replace(/[^a-z0-9]+/g, \"-\");\n         NODE_DATA[childNewId] = {\n          ...JSON.parse(JSON.stringify(childNode)),\n          name: childNewName,\n          assignedRack: newId\n         };\n         if (savedPositions[childId]) {\n          savedPositions[childNewId] = { ...savedPositions[childId] };\n         }\n         if (savedSizes[childId]) {\n          savedSizes[childNewId] = savedSizes[childId];\n         }\n         if (savedStyles[childId]) {\n          savedStyles[childNewId] = JSON.parse(JSON.stringify(savedStyles[childId]));\n         }\n        });\n       }\n       if (savedSizes[sourceId]) {\n        savedSizes[newId] = savedSizes[sourceId];\n       }\n       if (savedStyles[sourceId]) {\n        savedStyles[newId] = JSON.parse(JSON.stringify(savedStyles[sourceId]));\n       }\n      if (currentView.mode === \"rack\" && currentView.rackId) {\n        NODE_DATA[newId].assignedRack = currentView.rackId;\n       }\n       const sourcePos = savedPositions[sourceId];\n       savedPositions[newId] = {\n        x: sourcePos.x + 100,\n        y: sourcePos.y + 100\n       };\n       forgeTheTopology();\n       claimTheImmortal(newId);\n      }\n      function alignSelectedNodes(direction) {\n       if (selectedNodes.size < 2) return;\n       pushUndo(\"align nodes\");\n       const nodeIds = Array.from(selectedNodes);\n       const positions = nodeIds.map(id => ({ id, pos: savedPositions[id] }));\n       switch(direction) {\n        case \"left\":\n         const minX = Math.min(...positions.map(p => p.pos.x));\n         positions.forEach(p => savedPositions[p.id].x = minX);\n         break;\n        case \"right\":\n         const maxX = Math.max(...positions.map(p => p.pos.x));\n         positions.forEach(p => savedPositions[p.id].x = maxX);\n         break;\n        case \"top\":\n         const minY = Math.min(...positions.map(p => p.pos.y));\n         positions.forEach(p => savedPositions[p.id].y = minY);\n         break;\n        case \"bottom\":\n         const maxY = Math.max(...positions.map(p => p.pos.y));\n         positions.forEach(p => savedPositions[p.id].y = maxY);\n         break;\n        case \"center-h\":\n         const avgX = positions.reduce((sum, p) => sum + p.pos.x, 0) / positions.length;\n         positions.forEach(p => savedPositions[p.id].x = avgX);\n         break;\n        case \"center-v\":\n         const avgY = positions.reduce((sum, p) => sum + p.pos.y, 0) / positions.length;\n         positions.forEach(p => savedPositions[p.id].y = avgY);\n         break;\n       }\n       forgeTheTopology();\n      }\n      function distributeSelectedNodes(direction) {\n       if (selectedNodes.size < 3) return;\n       pushUndo(\"distribute nodes\");\n       const nodeIds = Array.from(selectedNodes);\n       const positions = nodeIds.map(id => ({ id, pos: savedPositions[id] }));\n       if (direction === \"horizontal\") {\n        positions.sort((a, b) => a.pos.x - b.pos.x);\n        const minX = positions[0].pos.x;\n        const maxX = positions[positions.length - 1].pos.x;\n        const gap = (maxX - minX) / (positions.length - 1);\n        positions.forEach((p, i) => {\n         savedPositions[p.id].x = minX + (gap * i);\n        });\n       } else if (direction === \"vertical\") {\n        positions.sort((a, b) => a.pos.y - b.pos.y);\n        const minY = positions[0].pos.y;\n        const maxY = positions[positions.length - 1].pos.y;\n        const gap = (maxY - minY) / (positions.length - 1);\n        positions.forEach((p, i) => {\n         savedPositions[p.id].y = minY + (gap * i);\n        });\n       }\n       forgeTheTopology();\n      }\n      function snapToGrid(nodeId, gridSize = 50) {\n       if (!savedPositions[nodeId]) return;\n       pushUndo(\"snap to grid\");\n       const pos = savedPositions[nodeId];\n       pos.x = Math.round(pos.x / gridSize) * gridSize;\n       pos.y = Math.round(pos.y / gridSize) * gridSize;\n       forgeTheTopology();\n      }\n      function nudgeSelectedNodes(direction, distance) {\n        const nodesToNudge = selectedNodes.size > 0\n          ? Array.from(selectedNodes)\n          : (currentNodeId ? [currentNodeId] : []);\n        if (nodesToNudge.length === 0) return;\n        const unlockedNodes = nodesToNudge.filter(id => !NODE_DATA[id]?.locked);\n        if (unlockedNodes.length === 0) {\n          return;\n        }\n        pushUndo(\"nudge nodes\");\n        unlockedNodes.forEach(id => {\n          if (!savedPositions[id]) {\n            savedPositions[id] = { x: 0, y: 0 };\n          }\n          switch(direction) {\n            case \"ArrowUp\":\n              savedPositions[id].y -= distance;\n              break;\n            case \"ArrowDown\":\n              savedPositions[id].y += distance;\n              break;\n            case \"ArrowLeft\":\n              savedPositions[id].x -= distance;\n              break;\n            case \"ArrowRight\":\n              savedPositions[id].x += distance;\n              break;\n          }\n        });\n        forgeTheTopology();\n      }\n      function cycleNodes(reverse = false) {\n        const nodeIds = Object.keys(NODE_DATA).filter(id => {\n          if (currentView.mode === \"rack\") {\n           const node = NODE_DATA[id];\n           return node && node.assignedRack === currentView.rackId;\n          }\n          return isNodeVisible(id);\n         });\n        if (nodeIds.length === 0) return;\n        let currentIndex = nodeIds.indexOf(currentNodeId);\n        if (reverse) {\n          currentIndex = currentIndex <= 0 ? nodeIds.length - 1 : currentIndex - 1;\n        } else {\n          currentIndex = currentIndex >= nodeIds.length - 1 ? 0 : currentIndex + 1;\n        }\n        const nextNodeId = nodeIds[currentIndex];\n        claimTheImmortal(nextNodeId);\n        selectedNodes.clear();\n        updateNodeSelection();\n      }\n      function focusOnSelected() {\n        let minX = Infinity, minY = Infinity;\n        let maxX = -Infinity, maxY = -Infinity;\n        let hasItems = false;\n        const nodesToFocus = selectedNodes.size > 0\n          ? Array.from(selectedNodes)\n          : (currentNodeId ? [currentNodeId] : []);\n        nodesToFocus.forEach(id => {\n          const pos = savedPositions[id];\n          if (pos) {\n            hasItems = true;\n            const size = savedSizes[id] || 50;\n            minX = Math.min(minX, pos.x - size/2);\n            minY = Math.min(minY, pos.y - size/2);\n            maxX = Math.max(maxX, pos.x + size/2);\n            maxY = Math.max(maxY, pos.y + size/2);\n          }\n        });\n        const rectsToFocus = selectedRects.size > 0 ? Array.from(selectedRects) : (currentRectId ? [currentRectId] : []);\n        rectsToFocus.forEach(id => {\n          const r = RECT_DATA.list.find(x => x.id === id);\n          if (r) {\n            hasItems = true;\n            minX = Math.min(minX, r.x);\n            minY = Math.min(minY, r.y);\n            maxX = Math.max(maxX, r.x + r.width);\n            maxY = Math.max(maxY, r.y + r.height);\n          }\n        });\n        const textsToFocus = selectedTexts.size > 0 ? Array.from(selectedTexts) : (currentTextId ? [currentTextId] : []);\n        textsToFocus.forEach(id => {\n          const t = TEXT_DATA.list.find(x => x.id === id);\n          if (t) {\n            hasItems = true;\n            minX = Math.min(minX, t.x - 50);\n            minY = Math.min(minY, t.y - 20);\n            maxX = Math.max(maxX, t.x + 50);\n            maxY = Math.max(maxY, t.y + 20);\n          }\n        });\n        const edgesToFocus = selectedEdges.size > 0 ? Array.from(selectedEdges) : (currentEdgeId ? [currentEdgeId] : []);\n        edgesToFocus.forEach(id => {\n          const e = EDGE_DATA.list.find(x => x.id === id);\n          if (e) {\n            const fromPos = savedPositions[e.from];\n            const toPos = savedPositions[e.to];\n            if (fromPos && toPos) {\n              hasItems = true;\n              minX = Math.min(minX, fromPos.x, toPos.x);\n              minY = Math.min(minY, fromPos.y, toPos.y);\n              maxX = Math.max(maxX, fromPos.x, toPos.x);\n              maxY = Math.max(maxY, fromPos.y, toPos.y);\n            }\n          }\n        });\n        if (!hasItems || !isFinite(minX)) return;\n        const padding = 100;\n        const centerX = (minX + maxX) / 2;\n        const centerY = (minY + maxY) / 2;\n        const width = maxX - minX + padding * 2;\n        const height = maxY - minY + padding * 2;\n        const zoomX = CANVAS_WIDTH / width;\n        const zoomY = CANVAS_HEIGHT / height;\n        const targetZoom = Math.min(zoomX, zoomY, 2);\n        canvasState.zoom = targetZoom;\n        canvasState.panX = centerX - (CANVAS_WIDTH / targetZoom) / 2;\n        canvasState.panY = centerY - (CANVAS_HEIGHT / targetZoom) / 2;\n        updateViewBox();\n      }\n      function toggleLockSelected() {\n        const nodesToToggle = selectedNodes.size > 0\n          ? Array.from(selectedNodes)\n          : (currentNodeId ? [currentNodeId] : []);\n        const rectsToToggle = selectedRects.size > 0\n          ? Array.from(selectedRects)\n          : (currentRectId ? [currentRectId] : []);\n        const textsToToggle = selectedTexts.size > 0\n          ? Array.from(selectedTexts)\n          : (currentTextId ? [currentTextId] : []);\n        if (nodesToToggle.length === 0 && rectsToToggle.length === 0 && textsToToggle.length === 0) return;\n        pushUndo(\"toggle lock\");\n        let hasUnlocked = nodesToToggle.some(id => !NODE_DATA[id]?.locked);\n        hasUnlocked = hasUnlocked || rectsToToggle.some(id => { const r = RECT_DATA.list.find(x => x.id === id); return r && !r.locked; });\n        hasUnlocked = hasUnlocked || textsToToggle.some(id => { const t = TEXT_DATA.list.find(x => x.id === id); return t && !t.locked; });\n        nodesToToggle.forEach(id => {\n          if (NODE_DATA[id]) NODE_DATA[id].locked = hasUnlocked;\n        });\n        rectsToToggle.forEach(id => {\n          const r = RECT_DATA.list.find(x => x.id === id);\n          if (r) r.locked = hasUnlocked;\n        });\n        textsToToggle.forEach(id => {\n          const t = TEXT_DATA.list.find(x => x.id === id);\n          if (t) t.locked = hasUnlocked;\n        });\n        forgeTheTopology();\n      }\n      function toggleGroupSelected() {\n      const nodesToGroup = Array.from(selectedNodes);\n      const rectsToGroup = Array.from(selectedRects);\n      const textsToGroup = Array.from(selectedTexts);\n      const edgesToGroup = Array.from(selectedEdges);\n      const totalItems = nodesToGroup.length + rectsToGroup.length + textsToGroup.length + edgesToGroup.length;\n      if (totalItems < 2) return;\n      pushUndo(\"toggle group\");\n      const allGroupIds = [];\n      nodesToGroup.forEach(id => { if (NODE_DATA[id]?.groupId) allGroupIds.push(NODE_DATA[id].groupId); });\n      rectsToGroup.forEach(id => { const r = RECT_DATA.list.find(x => x.id === id); if (r?.groupId) allGroupIds.push(r.groupId); });\n      textsToGroup.forEach(id => { const t = TEXT_DATA.list.find(x => x.id === id); if (t?.groupId) allGroupIds.push(t.groupId); });\n      edgesToGroup.forEach(id => { const e = EDGE_DATA.list.find(x => x.id === id); if (e?.groupId) allGroupIds.push(e.groupId); });\n      const uniqueGroups = [...new Set(allGroupIds)];\n      if (uniqueGroups.length === 1 && allGroupIds.length === totalItems) {\n      nodesToGroup.forEach(id => { if (NODE_DATA[id]) NODE_DATA[id].groupId = null; });\n      rectsToGroup.forEach(id => { const r = RECT_DATA.list.find(x => x.id === id); if (r) r.groupId = null; });\n      textsToGroup.forEach(id => { const t = TEXT_DATA.list.find(x => x.id === id); if (t) t.groupId = null; });\n      edgesToGroup.forEach(id => { const e = EDGE_DATA.list.find(x => x.id === id); if (e) e.groupId = null; });\n      } else {\n      const newGroupId = \"group-\" + Date.now();\n      nodesToGroup.forEach(id => { if (NODE_DATA[id]) NODE_DATA[id].groupId = newGroupId; });\n      rectsToGroup.forEach(id => { const r = RECT_DATA.list.find(x => x.id === id); if (r) r.groupId = newGroupId; });\n      textsToGroup.forEach(id => { const t = TEXT_DATA.list.find(x => x.id === id); if (t) t.groupId = newGroupId; });\n      edgesToGroup.forEach(id => { const e = EDGE_DATA.list.find(x => x.id === id); if (e) e.groupId = newGroupId; });\n      }\n      forgeTheTopology();\n      }\n      function handleKeyDown(event) {\n       if (event.metaKey && !event.ctrlKey) event.ctrlKey = true;\n       if (event.target.tagName === \"INPUT\" || event.target.tagName === \"TEXTAREA\" || event.target.isContentEditable) {\n        return;\n       }\n       if ([\"ArrowUp\", \"ArrowDown\", \"ArrowLeft\", \"ArrowRight\"].includes(event.key)) {\n        event.preventDefault();\n        const distance = event.shiftKey ? 10 : 1;\n        nudgeSelectedNodes(event.key, distance);\n       }\n       if (event.key === \"Tab\") {\n        event.preventDefault();\n        cycleNodes(event.shiftKey);\n       }\n       if (event.key === \"f\" || event.key === \"F\") {\n        event.preventDefault();\n        focusOnSelected();\n       }\n       if (event.key === \"l\" || event.key === \"L\") {\n        event.preventDefault();\n        toggleLockSelected();\n       }\n       if (event.key === \"g\" || event.key === \"G\") {\n        event.preventDefault();\n        toggleGroupSelected();\n       }\n       if (event.ctrlKey && event.key === \"z\" && !event.shiftKey) {\n        event.preventDefault();\n        undo();\n       }\n       if ((event.ctrlKey && event.key === \"y\") || (event.ctrlKey && event.shiftKey && event.key === \"z\")) {\n        event.preventDefault();\n        redo();\n       }\n       if (event.ctrlKey && event.key === \"c\") {\n        event.preventDefault();\n        clipboard = null;\n        if (currentNodeId && NODE_DATA[currentNodeId]) {\n         clipboard = {\n          type: \"node\",\n          data: JSON.parse(JSON.stringify(NODE_DATA[currentNodeId])),\n          size: savedSizes[currentNodeId],\n          style: savedStyles[currentNodeId] ? JSON.parse(JSON.stringify(savedStyles[currentNodeId])) : null\n         };\n        } else if (currentEdgeId) {\n         const edge = EDGE_DATA.list.find(e => e.id === currentEdgeId);\n         if (edge) clipboard = { type: \"edge\", data: JSON.parse(JSON.stringify(edge)) };\n        } else if (currentRectId) {\n         const rect = RECT_DATA.list.find(r => r.id === currentRectId);\n         if (rect) clipboard = { type: \"rect\", data: JSON.parse(JSON.stringify(rect)) };\n        } else if (currentTextId) {\n         const text = TEXT_DATA.list.find(t => t.id === currentTextId);\n         if (text) clipboard = { type: \"text\", data: JSON.parse(JSON.stringify(text)) };\n        }\n       }\n       if (event.ctrlKey && event.key === \"v\") {\n        event.preventDefault();\n        if (!clipboard) return;\n        const svgEl = document.getElementById(\"map\");\n        const rect = svgEl.getBoundingClientRect();\n        const pt = svgEl.createSVGPoint();\n        pt.x = rect.left + rect.width / 2;\n        pt.y = rect.top + rect.height / 2;\n        const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n        const centerX = svgP.x;\n        const centerY = svgP.y;\n        if (clipboard.type === \"node\") {\n         let newName = clipboard.data.name + \" copy\";\n         let counter = 1;\n         while (Object.values(NODE_DATA).some(n => n.name === newName)) {\n          newName = clipboard.data.name + \" copy \" + counter;\n          counter++;\n         }\n         let newId = newName.toLowerCase().replace(/[^a-z0-9]+/g, \"-\");\n         counter = 1;\n         while (NODE_DATA[newId]) {\n          newId = newName.toLowerCase().replace(/[^a-z0-9]+/g, \"-\") + \"-\" + counter;\n          counter++;\n         }\n         pushUndo(\"paste node\");\n         NODE_DATA[newId] = { ...JSON.parse(JSON.stringify(clipboard.data)), name: newName };\n         savedPositions[newId] = { x: centerX, y: centerY };\n         if (clipboard.size) savedSizes[newId] = clipboard.size;\n         if (clipboard.style) savedStyles[newId] = JSON.parse(JSON.stringify(clipboard.style));\n         forgeTheTopology();\n         claimTheImmortal(newId);\n        } else if (clipboard.type === \"edge\") {\n         pushUndo(\"paste edge\");\n         const newEdge = { ...clipboard.data, id: \"edge-\" + Date.now() };\n         EDGE_DATA.list.push(newEdge);\n         forgeTheTopology();\n         selectTheConnection(newEdge.id);\n        } else if (clipboard.type === \"rect\") {\n         pushUndo(\"paste rect\");\n         const newRect = { ...clipboard.data, id: \"rect-\" + Date.now(), x: centerX - (clipboard.data.width || 100) / 2, y: centerY - (clipboard.data.height || 100) / 2 };\n         RECT_DATA.list.push(newRect);\n         forgeTheTopology();\n         selectTheRect(newRect.id);\n        } else if (clipboard.type === \"text\") {\n         pushUndo(\"paste text\");\n         const newText = { ...clipboard.data, id: \"text-\" + Date.now(), x: centerX, y: centerY };\n         TEXT_DATA.list.push(newText);\n         forgeTheTopology();\n         showTextPanel(newText.id);\n        }\n       }\n       if (event.ctrlKey && event.key === \"d\") {\n        event.preventDefault();\n        if (currentNodeId && NODE_DATA[currentNodeId]) {\n         cloneNode(currentNodeId);\n        } else if (currentEdgeId) {\n         const edge = EDGE_DATA.list.find(e => e.id === currentEdgeId);\n         if (edge) {\n          pushUndo(\"clone edge\");\n          const newEdge = { ...JSON.parse(JSON.stringify(edge)), id: \"edge-\" + Date.now() };\n          EDGE_DATA.list.push(newEdge);\n          forgeTheTopology();\n          selectTheConnection(newEdge.id);\n         }\n        } else if (currentRectId) {\n         const rect = RECT_DATA.list.find(r => r.id === currentRectId);\n         if (rect) {\n          pushUndo(\"clone rect\");\n          const newRect = { ...JSON.parse(JSON.stringify(rect)), id: \"rect-\" + Date.now(), x: rect.x + 50, y: rect.y + 50 };\n          RECT_DATA.list.push(newRect);\n          forgeTheTopology();\n          selectTheRect(newRect.id);\n         }\n        } else if (currentTextId) {\n         const text = TEXT_DATA.list.find(t => t.id === currentTextId);\n         if (text) {\n          pushUndo(\"clone text\");\n          const newText = { ...JSON.parse(JSON.stringify(text)), id: \"text-\" + Date.now(), x: text.x + 50, y: text.y + 50 };\n          TEXT_DATA.list.push(newText);\n          forgeTheTopology();\n          showTextPanel(newText.id);\n         }\n        }\n       }\n       if (event.key === \"Delete\") {\n        event.preventDefault();\n        const totalSelected = selectedNodes.size + selectedEdges.size + selectedRects.size + selectedTexts.size;\n        if (totalSelected > 0) {\n         deleteSelected();\n        } else if (currentNodeId && NODE_DATA[currentNodeId]) {\n         challengeTheImmortal(`Delete node \"${NODE_DATA[currentNodeId].name}\"?`, () => {\n          pushUndo(\"delete node\");\n          delete NODE_DATA[currentNodeId];\n          delete savedPositions[currentNodeId];\n          delete savedSizes[currentNodeId];\n          delete savedStyles[currentNodeId];\n          EDGE_DATA.list = EDGE_DATA.list.filter(e => e.from !== currentNodeId && e.to !== currentNodeId);\n          currentNodeId = null;\n          forgeTheTopology();\n          document.getElementById(\"node-panel\").style.display = \"none\";\n         });\n        } else if (currentEdgeId) {\n         challengeTheImmortal(\"Delete this line?\", () => {\n          pushUndo(\"delete edge\");\n          EDGE_DATA.list = EDGE_DATA.list.filter(e => e.id !== currentEdgeId);\n          currentEdgeId = null;\n          forgeTheTopology();\n          document.getElementById(\"edge-panel\").style.display = \"none\";\n         });\n        } else if (currentRectId) {\n         challengeTheImmortal(\"Delete this zone?\", () => {\n          pushUndo(\"delete rect\");\n          RECT_DATA.list = RECT_DATA.list.filter(r => r.id !== currentRectId);\n          currentRectId = null;\n          forgeTheTopology();\n          document.getElementById(\"rect-panel\").style.display = \"none\";\n         });\n        } else if (currentTextId) {\n         const textItem = TEXT_DATA.list.find(t => t.id === currentTextId);\n         if (textItem) {\n          challengeTheImmortal(`Delete text \"${(textItem.content || '').substring(0, 30)}...\"?`, () => {\n           pushUndo(\"delete text\");\n           TEXT_DATA.list = TEXT_DATA.list.filter(t => t.id !== currentTextId);\n           currentTextId = null;\n           forgeTheTopology();\n           document.getElementById(\"text-panel\").style.display = \"none\";\n          });\n         }\n        }\n       }\n       if (event.ctrlKey && event.key === \"a\") {\n        event.preventDefault();\n        Object.keys(NODE_DATA).forEach(id => selectedNodes.add(id));\n        EDGE_DATA.list.forEach(e => selectedEdges.add(e.id));\n        RECT_DATA.list.forEach(r => selectedRects.add(r.id));\n        TEXT_DATA.list.forEach(t => selectedTexts.add(t.id));\n        updateAllSelections();\n       }\n       if (event.key === \"Escape\") {\n        clearSelection();\n       }\n      }\n\t\tfunction searchNodes(query) {\n\t\t   if (!query) {\n\t\t\tcurrentSearchQuery = \"\";\n\t\t\tcurrentSearchResults = [];\n\t\t\tclearSearchHighlight();\n\t\t\treturn [];\n\t\t   }\n\t\t   currentSearchQuery = query;\n\t\t   query = query.toLowerCase();\n\t\t   const results = [];\n\t\t   Object.entries(NODE_DATA).forEach(([id, data]) => {\n\t\t\ttry {\n\t\t\t const nameMatch = data.name && data.name.toLowerCase().includes(query);\n\t\t\t const ipMatch = data.ip && data.ip.toLowerCase().includes(query);\n\t\t\t const roleMatch = data.role && data.role.toLowerCase().includes(query);\n\t\t\t const tagsMatch = data.tags && Array.isArray(data.tags) && data.tags.some(tag => tag && tag.toLowerCase().includes(query));\n\t\t\t const macMatch = data.mac && data.mac.toLowerCase().includes(query);\n\t\t\t const rackUnitMatch = data.rackUnit && String(data.rackUnit).toLowerCase().includes(query);\n\t\t\t if (nameMatch || ipMatch || roleMatch || tagsMatch || macMatch || rackUnitMatch) {\n\t\t\t  results.push(id);\n\t\t\t }\n\t\t\t} catch (e) {\n\t\t\t console.warn(\"Search error for node:\", id, e);\n\t\t\t}\n\t\t   });\n\t\t   currentSearchResults = results;\n\t\t   highlightSearchResults(results, true);\n\t\t   if (results.length > 0) {\n\t\t\tfocusOnNodes(results);\n\t\t   }\n\t\t   return results;\n\t\t}\n\t\tfunction highlightSearchResults(nodeIds, hasQuery = true) {\n\t\t   document.querySelectorAll(\".node-group\").forEach(node => {\n\t\t\tconst nodeId = node.dataset.nodeId;\n\t\t\tif (nodeIds.includes(nodeId)) {\n\t\t\t node.classList.add(\"search-highlight\");\n\t\t\t node.classList.remove(\"search-faded\");\n\t\t\t} else {\n\t\t\t node.classList.remove(\"search-highlight\");\n\t\t\t if (hasQuery) {\n\t\t\t  node.classList.add(\"search-faded\");\n\t\t\t } else {\n\t\t\t  node.classList.remove(\"search-faded\");\n\t\t\t }\n\t\t\t}\n\t\t   });\n\t\t   document.querySelectorAll(\".edge-group, .edge\").forEach(edge => {\n\t\t\tif (hasQuery) {\n\t\t\t edge.classList.add(\"search-faded\");\n\t\t\t} else {\n\t\t\t edge.classList.remove(\"search-faded\");\n\t\t\t}\n\t\t   });\n\t\t}\n\t\tfunction clearSearchHighlight() {\n\t\t   document.querySelectorAll(\".search-highlight\").forEach(node => {\n\t\t\tnode.classList.remove(\"search-highlight\");\n\t\t   });\n\t\t   document.querySelectorAll(\".search-faded\").forEach(el => {\n\t\t\tel.classList.remove(\"search-faded\");\n\t\t   });\n\t\t}\n      function editNodeMac(id) {\n       const node = NODE_DATA[id];\n       if (!node) return;\n       document.getElementById(\"modal-title\").textContent = \"Edit MAC Address\";\n       document.getElementById(\"modal-input\").value = node.mac || \"\";\n       document.getElementById(\"edit-modal\").style.display = \"\";\n\t   document.getElementById(\"edit-modal\").classList.add(\"active\");\n       document.getElementById(\"modal-input\").focus();\n       const modal = document.getElementById(\"edit-modal\");\n       const cleanup = () => {\n        modal.classList.remove(\"active\");\n        document.getElementById(\"modal-save\").removeEventListener(\"click\", saveHandler);\n        document.getElementById(\"modal-cancel\").removeEventListener(\"click\", cancelHandler);\n        modal.removeEventListener(\"click\", bgHandler);\n       };\n       const saveHandler = () => {\n        pushUndo(\"edit mac\");\n        const value = document.getElementById(\"modal-input\").value.trim();\n        node.mac = value;\n        cleanup();\n        claimTheImmortal(id);\n       };\n       const cancelHandler = () => {\n        cleanup();\n       };\n       const bgHandler = (e) => {\n        if (e.target === modal) cancelHandler();\n       };\n       document.getElementById(\"modal-save\").addEventListener(\"click\", saveHandler);\n       document.getElementById(\"modal-cancel\").addEventListener(\"click\", cancelHandler);\n       modal.addEventListener(\"click\", bgHandler);\n      }\n      function editNodeRack(id) {\n       const node = NODE_DATA[id];\n       if (!node) return;\n       document.getElementById(\"modal-title\").textContent = \"Edit Rack Unit\";\n       document.getElementById(\"modal-input\").value = node.rackUnit || \"\";\n       document.getElementById(\"edit-modal\").style.display = \"\";\n       document.getElementById(\"edit-modal\").classList.add(\"active\");\n       document.getElementById(\"modal-input\").focus();\n\t   const modal = document.getElementById(\"edit-modal\");\n       const cleanup = () => {\n        modal.classList.remove(\"active\");\n        document.getElementById(\"modal-save\").removeEventListener(\"click\", saveHandler);\n        document.getElementById(\"modal-cancel\").removeEventListener(\"click\", cancelHandler);\n        modal.removeEventListener(\"click\", bgHandler);\n        document.removeEventListener(\"keydown\", escHandler);\n       };\n       const escHandler = (e) => {\n        if (e.key === \"Escape\") cleanup();\n       };\n       document.addEventListener(\"keydown\", escHandler);\n       const saveHandler = () => {\n        pushUndo(\"edit rack\");\n        const value = document.getElementById(\"modal-input\").value.trim();\n        node.rackUnit = value;\n        cleanup();\n        claimTheImmortal(id);\n       };\n       const cancelHandler = () => {\n        cleanup();\n       };\n       const bgHandler = (e) => {\n        if (e.target === modal) cancelHandler();\n       };\n       document.getElementById(\"modal-save\").addEventListener(\"click\", saveHandler);\n       document.getElementById(\"modal-cancel\").addEventListener(\"click\", cancelHandler);\n       modal.addEventListener(\"click\", bgHandler);\n      }\n      function editNodeUHeight(id) {\n       const node = NODE_DATA[id];\n       if (!node) return;\n       document.getElementById(\"modal-title\").textContent = \"Edit U Height\";\n       document.getElementById(\"modal-input\").value = node.uHeight || \"1\";\n       document.getElementById(\"edit-modal\").style.display = \"\";\n       document.getElementById(\"edit-modal\").classList.add(\"active\");\n       document.getElementById(\"modal-input\").focus();\n\t   const modal = document.getElementById(\"edit-modal\");\n       const cleanup = () => {\n        modal.classList.remove(\"active\");\n        document.getElementById(\"modal-save\").removeEventListener(\"click\", saveHandler);\n        document.getElementById(\"modal-cancel\").removeEventListener(\"click\", cancelHandler);\n        modal.removeEventListener(\"click\", bgHandler);\n        document.removeEventListener(\"keydown\", escHandler);\n       };\n       const escHandler = (e) => {\n        if (e.key === \"Escape\") cleanup();\n       };\n       document.addEventListener(\"keydown\", escHandler);\n       const saveHandler = () => {\n        pushUndo(\"edit U height\");\n        const value = document.getElementById(\"modal-input\").value.trim();\n        node.uHeight = value;\n        cleanup();\n        claimTheImmortal(id);\n        forgeTheTopology();\n       };\n       const cancelHandler = () => {\n        cleanup();\n       };\n       const bgHandler = (e) => {\n        if (e.target === modal) cancelHandler();\n       };\n       document.getElementById(\"modal-save\").addEventListener(\"click\", saveHandler);\n       document.getElementById(\"modal-cancel\").addEventListener(\"click\", cancelHandler);\n       modal.addEventListener(\"click\", bgHandler);\n      }\n      function updateEdgePortLabels(edgeId) {\n       const edge = EDGE_DATA.list.find(e => e.id === edgeId);\n       if (!edge) return;\n       const fromPortInput = document.getElementById(\"edge-from-port\");\n       const toPortInput = document.getElementById(\"edge-to-port\");\n       edge.fromPort = fromPortInput ? fromPortInput.value.trim() : \"\";\n       edge.toPort = toPortInput ? toPortInput.value.trim() : \"\";\n       forgeTheTopology();\n      }\n      document.addEventListener(\"keydown\", handleKeyDown);\n      const undoBtn = document.getElementById(\"undo-btn\");\n      const redoBtn = document.getElementById(\"redo-btn\");\n      if (undoBtn) undoBtn.addEventListener(\"click\", undo);\n      if (redoBtn) redoBtn.addEventListener(\"click\", redo);\n      const backToTopologyBtn = document.getElementById(\"back-to-topology-btn\");\n      if (backToTopologyBtn) {\n       backToTopologyBtn.addEventListener(\"click\", exitRack);\n      }\n      const bulkToolbarClose = document.getElementById(\"bulk-toolbar-close\");\n      if (bulkToolbarClose) {\n       bulkToolbarClose.addEventListener(\"click\", clearSelection);\n      }\n      const bulkAlignLeft = document.getElementById(\"bulk-align-left\");\n      if (bulkAlignLeft) {\n       bulkAlignLeft.addEventListener(\"click\", () => alignSelectedNodes(\"left\"));\n      }\n      const bulkAlignRight = document.getElementById(\"bulk-align-right\");\n      if (bulkAlignRight) {\n       bulkAlignRight.addEventListener(\"click\", () => alignSelectedNodes(\"right\"));\n      }\n      const bulkAlignTop = document.getElementById(\"bulk-align-top\");\n      if (bulkAlignTop) {\n       bulkAlignTop.addEventListener(\"click\", () => alignSelectedNodes(\"top\"));\n      }\n      const bulkAlignBottom = document.getElementById(\"bulk-align-bottom\");\n      if (bulkAlignBottom) {\n       bulkAlignBottom.addEventListener(\"click\", () => alignSelectedNodes(\"bottom\"));\n      }\n      const bulkDistributeH = document.getElementById(\"bulk-distribute-h\");\n      if (bulkDistributeH) {\n       bulkDistributeH.addEventListener(\"click\", () => distributeSelectedNodes(\"horizontal\"));\n      }\n      const bulkDistributeV = document.getElementById(\"bulk-distribute-v\");\n      if (bulkDistributeV) {\n       bulkDistributeV.addEventListener(\"click\", () => distributeSelectedNodes(\"vertical\"));\n      }\n      const bulkClone = document.getElementById(\"bulk-clone\");\n      if (bulkClone) {\n       bulkClone.addEventListener(\"click\", () => {\n        if (selectedNodes.size === 0) return;\n        pushUndo(\"clone selected\");\n        const nodesToClone = Array.from(selectedNodes);\n        selectedNodes.clear();\n       nodesToClone.forEach(id => {\n         cloneNode(id, true);\n         selectedNodes.add(currentNodeId);\n        });\n        updateNodeSelection();\n       });\n      }\n      const bulkDelete = document.getElementById(\"bulk-delete\");\n      if (bulkDelete) {\n       bulkDelete.addEventListener(\"click\", deleteSelected);\n      }\n      const bulkMobileBtn = document.getElementById(\"bulk-mobile-btn\");\n      const bulkActionsModal = document.getElementById(\"bulk-actions-modal\");\n      const bulkModalClose = document.getElementById(\"bulk-modal-close\");\n      if (bulkMobileBtn) {\n       bulkMobileBtn.addEventListener(\"click\", () => {\n        if (bulkActionsModal) bulkActionsModal.style.display = \"block\";\n       });\n      }\n      if (bulkModalClose) {\n       bulkModalClose.addEventListener(\"click\", () => {\n        if (bulkActionsModal) bulkActionsModal.style.display = \"none\";\n       });\n      }\n      const closeModalAfterAction = () => {\n       if (bulkActionsModal) bulkActionsModal.style.display = \"none\";\n      };\n      const bulkAlignLeftMobile = document.getElementById(\"bulk-align-left-mobile\");\n      if (bulkAlignLeftMobile) {\n       bulkAlignLeftMobile.addEventListener(\"click\", () => {\n        alignSelectedNodes(\"left\");\n        closeModalAfterAction();\n       });\n      }\n      const bulkAlignRightMobile = document.getElementById(\"bulk-align-right-mobile\");\n      if (bulkAlignRightMobile) {\n       bulkAlignRightMobile.addEventListener(\"click\", () => {\n        alignSelectedNodes(\"right\");\n        closeModalAfterAction();\n       });\n      }\n      const bulkAlignTopMobile = document.getElementById(\"bulk-align-top-mobile\");\n      if (bulkAlignTopMobile) {\n       bulkAlignTopMobile.addEventListener(\"click\", () => {\n        alignSelectedNodes(\"top\");\n        closeModalAfterAction();\n       });\n      }\n      const bulkAlignBottomMobile = document.getElementById(\"bulk-align-bottom-mobile\");\n      if (bulkAlignBottomMobile) {\n       bulkAlignBottomMobile.addEventListener(\"click\", () => {\n        alignSelectedNodes(\"bottom\");\n        closeModalAfterAction();\n       });\n      }\n      const bulkDistributeHMobile = document.getElementById(\"bulk-distribute-h-mobile\");\n      if (bulkDistributeHMobile) {\n       bulkDistributeHMobile.addEventListener(\"click\", () => {\n        distributeSelectedNodes(\"horizontal\");\n        closeModalAfterAction();\n       });\n      }\n      const bulkDistributeVMobile = document.getElementById(\"bulk-distribute-v-mobile\");\n      if (bulkDistributeVMobile) {\n       bulkDistributeVMobile.addEventListener(\"click\", () => {\n        distributeSelectedNodes(\"vertical\");\n        closeModalAfterAction();\n       });\n      }\n      const bulkCloneMobile = document.getElementById(\"bulk-clone-mobile\");\n      if (bulkCloneMobile) {\n       bulkCloneMobile.addEventListener(\"click\", () => {\n        if (selectedNodes.size === 0) return;\n        pushUndo(\"clone selected\");\n        const nodesToClone = Array.from(selectedNodes);\n        selectedNodes.clear();\n        nodesToClone.forEach(id => {\n         cloneNode(id, true);\n         selectedNodes.add(currentNodeId);\n        });\n        updateNodeSelection();\n        closeModalAfterAction();\n       });\n      }\n      const bulkDeleteMobile = document.getElementById(\"bulk-delete-mobile\");\n      if (bulkDeleteMobile) {\n       bulkDeleteMobile.addEventListener(\"click\", () => {\n        deleteSelected();\n        closeModalAfterAction();\n       });\n      }\n      const bulkLockMobile = document.getElementById(\"bulk-lock-mobile\");\n      if (bulkLockMobile) {\n       bulkLockMobile.addEventListener(\"click\", () => {\n        toggleLockSelected();\n        closeModalAfterAction();\n       });\n      }\n      const bulkGroupMobile = document.getElementById(\"bulk-group-mobile\");\n      if (bulkGroupMobile) {\n       bulkGroupMobile.addEventListener(\"click\", () => {\n        toggleGroupSelected();\n        closeModalAfterAction();\n       });\n      }\n      const bulkClearMobile = document.getElementById(\"bulk-clear-mobile\");\n      if (bulkClearMobile) {\n       bulkClearMobile.addEventListener(\"click\", () => {\n        clearSelection();\n        closeModalAfterAction();\n       });\n      }\n      const searchInput = document.getElementById(\"search-nodes\");\n      let searchTimeout = null;\n      if (searchInput) {\n       searchInput.addEventListener(\"input\", (e) => {\n        clearTimeout(searchTimeout);\n        searchTimeout = setTimeout(() => { searchNodes(e.target.value); }, 150);\n\t\t });\n\t\t}\n       document.getElementById(\"save-file-btn\").addEventListener(\"click\", becomeImmortal);\n       document.getElementById(\"check-all-ping-btn\").addEventListener(\"click\", checkAllNodesStatus);\n       const addNodeBtn = document.getElementById(\"add-node-btn\");\n       const addNodeModal = document.getElementById(\"add-node-modal\");\n       const addNodeCancel = document.getElementById(\"add-node-cancel\");\n       const addNodeSave = document.getElementById(\"add-node-save\");\n       addNodeBtn.addEventListener(\"click\", () => {\n\t    if (isViewOnly()) return;\n        document.getElementById(\"new-node-name\").value = \"\";\n        document.getElementById(\"new-node-ip\").value = \"\";\n        document.getElementById(\"new-node-tags\").value = \"\";\n        document.getElementById(\"new-node-shape\").value = \"circle\";\n        document.getElementById(\"new-node-pingable\").checked = false;\n        document.getElementById(\"new-node-ping-protocol\").value = \"http\";\n        document.getElementById(\"new-node-custom-url\").value = \"\";\n        document.getElementById(\"new-node-ping-timeout\").value = \"3000\";\n        document.getElementById(\"new-node-ping-options\").style.display = \"none\";\n        document.getElementById(\"new-node-custom-url-container\").style.display = \"none\";\n        newNodeIconTags = [];\n        document.getElementById(\"new-node-icon-tags\").style.display = \"none\";\n        document.getElementById(\"new-node-icon-tags-list\").innerHTML = \"\";\n        addNodeModal.classList.add(\"active\");\n        document.getElementById(\"new-node-name\").focus();\n       });\n       const canvasViewport = document.getElementById(\"canvas-viewport\");\n       if (canvasViewport) {\n        canvasViewport.addEventListener(\"dblclick\", (e) => {\n         if (currentView.mode === \"rack\" && e.target.id === \"map\") {\n          exitRack();\n         }\n        });\n       }\n       const layersBtn = document.getElementById(\"layers-btn\");\n       const layerModal = document.getElementById(\"layer-modal\");\n       const layerModalClose = document.getElementById(\"layer-modal-close\");\n       if (layersBtn && layerModal) {\n        layersBtn.addEventListener(\"click\", () => {\n         layerModal.classList.add(\"active\");\n        });\n       }\n       if (layerModalClose && layerModal) {\n        layerModalClose.addEventListener(\"click\", () => {\n         layerModal.classList.remove(\"active\");\n        });\n       }\n       if (layerModal) {\n        layerModal.addEventListener(\"click\", (e) => {\n         if (e.target === layerModal) {\n          layerModal.classList.remove(\"active\");\n         }\n        });\n       }\n       const tabsBtn = document.getElementById(\"tabs-btn\");\n       const tabsModal = document.getElementById(\"tabs-modal\");\n       const tabsModalClose = document.getElementById(\"tabs-modal-close\");\n       if (tabsBtn && tabsModal) {\n         tabsBtn.addEventListener(\"click\", () => {\n           displayTabs();\n           tabsModal.classList.add(\"active\");\n         });\n       }\n       if (tabsModalClose && tabsModal) {\n         tabsModalClose.addEventListener(\"click\", () => {\n           tabsModal.classList.remove(\"active\");\n         });\n       }\n       if (tabsModal) {\n         tabsModal.addEventListener(\"click\", (e) => {\n           if (e.target === tabsModal) {\n             tabsModal.classList.remove(\"active\");\n           }\n         });\n       }\n       const rollbackBtn = document.getElementById(\"rollback-btn\");\n       const rollbackModal = document.getElementById(\"rollback-modal\");\n       const rollbackModalClose = document.getElementById(\"rollback-modal-close\");\n       if (rollbackBtn && rollbackModal) {\n         rollbackBtn.addEventListener(\"click\", () => {\n           loadRollbackVersions();\n           rollbackModal.classList.add(\"active\");\n         });\n       }\n       if (rollbackModalClose && rollbackModal) {\n         rollbackModalClose.addEventListener(\"click\", () => {\n           rollbackModal.classList.remove(\"active\");\n         });\n       }\n       if (rollbackModal) {\n         rollbackModal.addEventListener(\"click\", (e) => {\n           if (e.target === rollbackModal) {\n             rollbackModal.classList.remove(\"active\");\n           }\n         });\n       }\n       const auditLogBtn = document.getElementById(\"audit-log-btn\");\n       const auditLogModal = document.getElementById(\"audit-log-modal\");\n       const auditLogModalClose = document.getElementById(\"audit-log-modal-close\");\n       if (auditLogBtn && auditLogModal) {\n         auditLogBtn.addEventListener(\"click\", () => {\n           loadAuditLog();\n           displayAuditLog();\n           auditLogModal.classList.add(\"active\");\n         });\n       }\n       if (auditLogModalClose && auditLogModal) {\n         auditLogModalClose.addEventListener(\"click\", () => {\n           auditLogModal.classList.remove(\"active\");\n         });\n       }\n\t   const portMapBtn = document.getElementById(\"port-map-btn\");\n      const portMapModal = document.getElementById(\"port-map-modal\");\n      const portMapClose = document.getElementById(\"port-map-modal-close\");\n      const portMapSearch = document.getElementById(\"port-map-search\");\n      const portMapFilter = document.getElementById(\"port-map-filter\");\n      function renderPortMap() {\n        const container = document.getElementById(\"port-map-table\");\n        const search = (document.getElementById(\"port-map-search\")?.value || \"\").toLowerCase();\n        const filter = document.getElementById(\"port-map-filter\")?.value || \"all\";\n        let edges = EDGE_DATA.list || [];\n        if (filter === \"with-ports\") edges = edges.filter(e => e.fromPort || e.toPort);\n        if (filter === \"without-ports\") edges = edges.filter(e => !e.fromPort && !e.toPort);\n        if (search) {\n          edges = edges.filter(e => {\n            const fromName = (NODE_DATA[e.from]?.name || e.from || \"\").toLowerCase();\n            const toName = (NODE_DATA[e.to]?.name || e.to || \"\").toLowerCase();\n            return fromName.includes(search) || toName.includes(search) || (e.fromPort || \"\").toLowerCase().includes(search) || (e.toPort || \"\").toLowerCase().includes(search) || (e.notes || []).join(\" \").toLowerCase().includes(search);\n          });\n        }\n        if (edges.length === 0) {\n          container.innerHTML = '<div style=\"text-align: center; padding: 40px; color: var(--text-soft);\">No connections found</div>';\n          return;\n        }\n        let html = '<table style=\"width: 100%; border-collapse: collapse; font-size: 13px;\">';\n        html += '<thead><tr style=\"background: var(--panel-alt); position: sticky; top: 0;\"><th style=\"padding: 10px; text-align: left; border-bottom: 1px solid var(--edge-main);\">From Device</th><th style=\"padding: 10px; text-align: left; border-bottom: 1px solid var(--edge-main);\">From Port</th><th style=\"padding: 10px; text-align: left; border-bottom: 1px solid var(--edge-main);\">To Device</th><th style=\"padding: 10px; text-align: left; border-bottom: 1px solid var(--edge-main);\">To Port</th><th style=\"padding: 10px; text-align: left; border-bottom: 1px solid var(--edge-main);\">Notes</th></tr></thead><tbody>';\n        edges.forEach(e => {\n          const fromName = NODE_DATA[e.from]?.name || e.from;\n          const toName = NODE_DATA[e.to]?.name || e.to;\n          const notes = (e.notes || []).join(\", \");\n          const edgeColor = e.color || EDGE_LEGEND[e.type]?.color || 'var(--edge-main)';\n          const fromPortDisplay = e.fromPort ? escapeHtml(e.fromPort) : \"-\";\n          const toPortDisplay = e.toPort ? escapeHtml(e.toPort) : \"-\";\n          const goToEdge = `document.getElementById('port-map-modal').classList.remove('active'); selectTheConnection('${escapeHtml(e.id)}'); focusOnSelected()`;\n          html += `<tr style=\"border-bottom: 1px solid var(--edge-main);\"><td style=\"padding: 8px;\"><span style=\"display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: ${edgeColor}; margin-right: 6px;\"></span><button onclick=\"document.getElementById('port-map-modal').classList.remove('active'); claimTheImmortal('${escapeHtml(e.from)}'); focusOnSelected()\" style=\"background: var(--panel-alt); color: var(--text-main); border: 1px solid var(--edge-main); border-radius: 4px; padding: 6px 10px; cursor: pointer; font-size: 13px; min-height: 36px;\">${escapeHtml(fromName)}</button></td><td style=\"padding: 8px;\"><button onclick=\"editPortFromMap('${escapeHtml(e.id)}', 'from')\" style=\"background: ${e.fromPort ? 'var(--accent)' : 'var(--panel)'}; color: ${e.fromPort ? 'var(--bg)' : 'var(--text-soft)'}; border: 1px solid var(--edge-main); border-radius: 4px; padding: 6px 10px; cursor: pointer; font-family: monospace; font-size: 13px; min-height: 36px;\">${fromPortDisplay}</button></td><td style=\"padding: 8px;\"><button onclick=\"document.getElementById('port-map-modal').classList.remove('active'); claimTheImmortal('${escapeHtml(e.to)}'); focusOnSelected()\" style=\"background: var(--panel-alt); color: var(--text-main); border: 1px solid var(--edge-main); border-radius: 4px; padding: 6px 10px; cursor: pointer; font-size: 13px; min-height: 36px;\">${escapeHtml(toName)}</button></td><td style=\"padding: 8px;\"><button onclick=\"editPortFromMap('${escapeHtml(e.id)}', 'to')\" style=\"background: ${e.toPort ? 'var(--accent)' : 'var(--panel)'}; color: ${e.toPort ? 'var(--bg)' : 'var(--text-soft)'}; border: 1px solid var(--edge-main); border-radius: 4px; padding: 6px 10px; cursor: pointer; font-family: monospace; font-size: 13px; min-height: 36px;\">${toPortDisplay}</button></td><td style=\"padding: 8px;\"><button onclick=\"${goToEdge}\" style=\"background: var(--panel); color: var(--text-main); border: 1px solid var(--edge-main); border-radius: 4px; padding: 6px 10px; cursor: pointer; font-size: 13px; min-height: 36px;\" title=\"Go to connection\">Go</button></td></tr>`;\n        });\n        html += '</tbody></table>';\n        container.innerHTML = html;\n      }\n      window.editPortFromMap = function(edgeId, which) {\n        const edge = EDGE_DATA.list.find(e => e.id === edgeId);\n        if (!edge) return;\n        const fromName = NODE_DATA[edge.from]?.name || edge.from;\n        const toName = NODE_DATA[edge.to]?.name || edge.to;\n        const nodeId = which === 'from' ? edge.from : edge.to;\n        const nodeName = which === 'from' ? fromName : toName;\n        const label = `Port on ${nodeName}:`;\n        const currentVal = which === 'from' ? (edge.fromPort || '') : (edge.toPort || '');\n        const newVal = prompt(label, currentVal);\n        if (newVal !== null && newVal !== '') {\n          const isDuplicate = (EDGE_DATA.list || []).some(e => {\n            if (e.id === edgeId) return false;\n            if (e.from === nodeId && e.fromPort === newVal) return true;\n            if (e.to === nodeId && e.toPort === newVal) return true;\n            return false;\n          });\n          if (isDuplicate && !confirm(`Warning: Port \"${newVal}\" is already used on ${nodeName}. Use anyway?`)) {\n            return;\n          }\n          if (which === 'from') edge.fromPort = newVal;\n          else edge.toPort = newVal;\n          renderPortMap();\n          if (currentNodeId && (edge.from === currentNodeId || edge.to === currentNodeId)) {\n            claimTheImmortal(currentNodeId);\n          }\n        } else if (newVal === '') {\n          if (which === 'from') edge.fromPort = '';\n          else edge.toPort = '';\n          renderPortMap();\n          if (currentNodeId && (edge.from === currentNodeId || edge.to === currentNodeId)) {\n            claimTheImmortal(currentNodeId);\n          }\n        }\n      };\n      window.exportPortMap = function() {\n        let csv = \"From Device,From Port,To Device,To Port,Notes\\n\";\n        (EDGE_DATA.list || []).forEach(e => {\n          const fromName = NODE_DATA[e.from]?.name || e.from || \"\";\n          const toName = NODE_DATA[e.to]?.name || e.to || \"\";\n          const notes = (e.notes || []).join(\"; \");\n          csv += `${csvEscape(fromName)},${csvEscape(e.fromPort || \"\")},${csvEscape(toName)},${csvEscape(e.toPort || \"\")},${csvEscape(notes)}\\n`;\n        });\n        const blob = new Blob([csv], { type: \"text/csv\" });\n        const url = URL.createObjectURL(blob);\n        const a = document.createElement(\"a\");\n        a.href = url;\n        a.download = \"port-map.csv\";\n        a.click();\n        URL.revokeObjectURL(url);\n      };\n      if (portMapBtn && portMapModal) {\n        portMapBtn.addEventListener(\"click\", () => { renderPortMap(); portMapModal.classList.add(\"active\"); });\n      }\n      if (portMapClose && portMapModal) {\n        portMapClose.addEventListener(\"click\", () => { portMapModal.classList.remove(\"active\"); });\n      }\n      if (portMapSearch) { portMapSearch.addEventListener(\"input\", renderPortMap); }\n      if (portMapFilter) { portMapFilter.addEventListener(\"change\", renderPortMap); }\n      if (portMapModal) {\n        portMapModal.addEventListener(\"click\", (e) => { if (e.target === portMapModal) portMapModal.classList.remove(\"active\"); });\n      }\n       if (auditLogModal) {\n         auditLogModal.addEventListener(\"click\", (e) => {\n           if (e.target === auditLogModal) {\n             auditLogModal.classList.remove(\"active\");\n           }\n         });\n       }\n       const auditFilter = document.getElementById(\"audit-filter\");\n       if (auditFilter) {\n         auditFilter.addEventListener(\"change\", (e) => {\n           displayAuditLog(e.target.value);\n         });\n       }\n       const secretsBtn = document.getElementById(\"secrets-btn\");\n       const secretsModal = document.getElementById(\"secrets-modal\");\n       const secretsModalClose = document.getElementById(\"secrets-modal-close\");\n       if (secretsBtn && secretsModal) {\n         secretsBtn.addEventListener(\"click\", () => {\n           displaySecrets();\n           secretsModal.classList.add(\"active\");\n         });\n       }\n       if (secretsModalClose && secretsModal) {\n         secretsModalClose.addEventListener(\"click\", () => {\n           secretsModal.classList.remove(\"active\");\n         });\n       }\n       if (secretsModal) {\n         secretsModal.addEventListener(\"click\", (e) => {\n           if (e.target === secretsModal) {\n             secretsModal.classList.remove(\"active\");\n           }\n         });\n       }\n       const secretEditorModal = document.getElementById(\"secret-editor-modal\");\n       if (secretEditorModal) {\n         secretEditorModal.addEventListener(\"click\", (e) => {\n           if (e.target === secretEditorModal) {\n             closeSecretEditor();\n           }\n         });\n       }\n       [\"physical\", \"logical\", \"security\", \"application\"].forEach(layer => {\n        const checkbox = document.getElementById(`layer-${layer}`);\n        if (checkbox) {\n         checkbox.addEventListener(\"change\", applyLayerFilter);\n        }\n       });\n       const layerSelect = document.getElementById(\"node-layer\");\n       if (layerSelect) {\n        layerSelect.addEventListener(\"change\", (e) => {\n         if (currentNodeId) {\n          pushUndo(\"change layer\");\n          NODE_DATA[currentNodeId].layer = e.target.value;\n          forgeTheTopology();\n         }\n        });\n       }\n       const assignedRackSelect = document.getElementById(\"node-assigned-rack\");\n       if (assignedRackSelect) {\n        assignedRackSelect.addEventListener(\"change\", (e) => {\n      if (currentNodeId) {\n      pushUndo(\"change assigned rack\");\n      EDGE_DATA.list = EDGE_DATA.list.filter(edge => edge.from !== currentNodeId && edge.to !== currentNodeId);\n      NODE_DATA[currentNodeId].assignedRack = e.target.value;\n      forgeTheTopology();\n      claimTheImmortal(currentNodeId);\n      }\n      });\n       }\n       const rackCapacitySelect = document.getElementById(\"node-rack-capacity\");\n       if (rackCapacitySelect) {\n        rackCapacitySelect.addEventListener(\"change\", (e) => {\n         if (currentNodeId) {\n          pushUndo(\"change rack capacity\");\n          NODE_DATA[currentNodeId].rackCapacity = e.target.value;\n          forgeTheTopology();\n         }\n        });\n       }\n       const addRackBtn = document.getElementById(\"add-rack-btn\");\n       const addRackModal = document.getElementById(\"add-rack-modal\");\n       const addRackCancel = document.getElementById(\"add-rack-cancel\");\n       const addRackSave = document.getElementById(\"add-rack-save\");\n       if (addRackBtn && addRackModal) {\n        addRackBtn.addEventListener(\"click\", () => {\n\t\tif (isViewOnly()) return;\n         document.getElementById(\"new-rack-name\").value = \"\";\n         document.getElementById(\"new-rack-ip\").value = \"\";\n         document.getElementById(\"new-rack-tags\").value = \"\";\n         document.getElementById(\"new-rack-shape\").value = \"server\";\n         document.getElementById(\"new-rack-capacity\").value = \"42\";\n         selectedRackIconData = null;\n         document.getElementById('selected-rack-icon').style.display = 'none';\n         addRackModal.classList.add(\"active\");\n         document.getElementById(\"new-rack-name\").focus();\n        });\n       }\n       if (addRackCancel && addRackModal) {\n        addRackCancel.addEventListener(\"click\", () => {\n         addRackModal.classList.remove(\"active\");\n        });\n       }\n       if (addRackModal) {\n        addRackModal.addEventListener(\"click\", (e) => {\n         if (e.target === addRackModal) {\n          addRackModal.classList.remove(\"active\");\n         }\n        });\n       }\n       if (addRackSave && addRackModal) {\n        addRackSave.addEventListener(\"click\", () => {\n         const name = document.getElementById(\"new-rack-name\").value.trim();\n         const ip = document.getElementById(\"new-rack-ip\").value.trim();\n         const tagsStr = document.getElementById(\"new-rack-tags\").value.trim();\n         const shape = document.getElementById(\"new-rack-shape\").value;\n         const capacity = document.getElementById(\"new-rack-capacity\").value;\n         if (!name) {\n          alert(\"Please enter a rack name.\");\n          return;\n         }\n         const tags = tagsStr ? tagsStr.split(\",\").map((t) => t.trim()).filter((t) => t) : [];\n         let baseId = name.toLowerCase().replace(/[^a-z0-9]+/g, \"-\").replace(/^-|-$/g, \"\");\n         if (!baseId) baseId = \"rack\";\n         let nodeId = baseId;\n         let counter = 1;\n         while (NODE_DATA[nodeId]) {\n          nodeId = baseId + \"-\" + counter;\n          counter++;\n         }\n         pushUndo(\"add rack\");\n         NODE_DATA[nodeId] = {\n          shape: shape,\n          name: name,\n          ip: ip || \"\",\n          role: \"Rack\",\n          tags: tags,\n          notes: [],\n          mac: \"\",\n          rackUnit: \"\",\n          uHeight: \"1\",\n          layer: \"physical\",\n          assignedRack: \"\",\n          rackCapacity: capacity,\n          isRack: true,\n          locked: false,\n          groupId: null\n         };\n         const viewWidth = CANVAS_WIDTH / canvasState.zoom;\n         const viewHeight = CANVAS_HEIGHT / canvasState.zoom;\n         const centerX = canvasState.panX + (viewWidth / 2);\n         const centerY = canvasState.panY + (viewHeight / 2);\n         savedPositions[nodeId] = {\n          x: centerX,\n          y: centerY\n         };\n         if (selectedRackIconData) {\n          if (!savedStyles[nodeId]) {\n           savedStyles[nodeId] = {};\n          }\n          if (!savedStyles[nodeId]['all']) {\n           savedStyles[nodeId]['all'] = {};\n          }\n          savedStyles[nodeId]['all'].icon = {\n           library: selectedRackIconData.library,\n           name: selectedRackIconData.name\n          };\n          selectedRackIconData = null;\n          document.getElementById('selected-rack-icon').style.display = 'none';\n         }\n         addRackModal.classList.remove(\"active\");\n         forgeTheTopology();\n         claimTheImmortal(nodeId);\n        });\n        [\"new-rack-name\", \"new-rack-ip\", \"new-rack-tags\"].forEach((inputId) => {\n         const input = document.getElementById(inputId);\n         if (input) {\n          input.addEventListener(\"keypress\", (e) => {\n           if (e.key === \"Enter\") {\n            addRackSave.click();\n           }\n          });\n         }\n        });\n       }\n       addNodeCancel.addEventListener(\"click\", () => {\n        addNodeModal.classList.remove(\"active\");\n       });\n       addNodeModal.addEventListener(\"click\", (e) => {\n        if (e.target === addNodeModal) {\n         addNodeModal.classList.remove(\"active\");\n        }\n       });\n       addNodeSave.addEventListener(\"click\", () => {\n        const name = document.getElementById(\"new-node-name\").value.trim();\n        const ip = document.getElementById(\"new-node-ip\").value.trim();\n        const tagsStr = document.getElementById(\"new-node-tags\").value.trim();\n        const shape = document.getElementById(\"new-node-shape\").value;\n        const pingable = document.getElementById(\"new-node-pingable\").checked;\n        const pingProtocol = document.getElementById(\"new-node-ping-protocol\").value;\n        const pingCustomUrl = document.getElementById(\"new-node-custom-url\").value.trim();\n        const pingTimeout = parseInt(document.getElementById(\"new-node-ping-timeout\").value) || 3000;\n        if (!name) {\n         alert(\"Please enter a node name.\");\n         return;\n        }\n        const tags = tagsStr ? tagsStr.split(\",\").map((t) => t.trim()).filter((t) => t) : [];\n        if (newNodeIconTags.length > 0) {\n         tags.push(...newNodeIconTags);\n        }\n        let baseId = name.toLowerCase().replace(/[^a-z0-9]+/g, \"-\").replace(/^-|-$/g, \"\");\n        if (!baseId) baseId = \"node\";\n        let nodeId = baseId;\n        let counter = 1;\n        while (NODE_DATA[nodeId]) {\n         nodeId = baseId + \"-\" + counter;\n         counter++;\n        }\n\t\tpushUndo(\"add node\");\n        NODE_DATA[nodeId] = {\n         shape: shape || \"circle\",\n         name: name,\n         ip: ip || \"0.0.0.0\",\n         role: \"\",\n         tags: tags,\n         notes: [],\n         mac: \"\",\n         rackUnit: \"\",\n         uHeight: \"1\",\n         ping: {\n          enabled: pingable,\n          protocol: pingProtocol,\n          customUrl: pingCustomUrl,\n          timeout: pingTimeout,\n          status: 'unknown',\n          lastCheck: null\n         },\n         locked: false,\n         groupId: null\n        };\n        if (currentView.mode === \"rack\" && currentView.rackId) {\n         NODE_DATA[nodeId].assignedRack = currentView.rackId;\n         NODE_DATA[nodeId].layer = \"physical\";\n         const rackCapacity = getRackCapacity(currentView.rackId);\n         const rackUHeight = getRackUHeight(currentView.rackId);\n         const viewHeight = CANVAS_HEIGHT / canvasState.zoom;\n         const centerY = canvasState.panY + (viewHeight / 2);\n         let unit = rackCapacity - Math.round((centerY - RACK_START_Y) / rackUHeight);\n         unit = Math.max(1, Math.min(rackCapacity, unit));\n         NODE_DATA[nodeId].rackUnit = String(unit);\n        }\n        if (selectedNodeIconData) {\n         if (!savedStyles[nodeId]) savedStyles[nodeId] = {};\n         if (!savedStyles[nodeId]['all']) savedStyles[nodeId]['all'] = {};\n         savedStyles[nodeId]['all'].icon = {\n          library: selectedNodeIconData.library,\n          name: selectedNodeIconData.name\n         };\n         selectedNodeIconData = null;\n         document.getElementById('selected-node-icon').style.display = 'none';\n        }\n        newNodeIconTags = [];\n      const viewWidth = CANVAS_WIDTH / canvasState.zoom;\n      const viewHeight = CANVAS_HEIGHT / canvasState.zoom;\n      const centerX = canvasState.panX + (viewWidth / 2);\n      const centerY = canvasState.panY + (viewHeight / 2);\n      savedPositions[nodeId] = {\n      x: centerX,\n      y: centerY\n      };\n        addNodeModal.classList.remove(\"active\");\n        forgeTheTopology();\n        claimTheImmortal(nodeId);\n       });\n       [\"new-node-name\", \"new-node-ip\", \"new-node-tags\"].forEach(\n        (inputId) => {\n         document.getElementById(inputId).addEventListener(\"keypress\", (e) => {\n          if (e.key === \"Enter\") {\n           addNodeSave.click();\n          }\n         });\n        });\n       document.getElementById('new-node-pingable').addEventListener('change', (e) => {\n        const pingOptions = document.getElementById('new-node-ping-options');\n        pingOptions.style.display = e.target.checked ? 'block' : 'none';\n       });\n       document.getElementById('new-node-ping-protocol').addEventListener('change', (e) => {\n        const customUrlContainer = document.getElementById('new-node-custom-url-container');\n        customUrlContainer.style.display = e.target.value === 'custom' ? 'block' : 'none';\n       });\n       document.getElementById('pick-rack-icon-btn').addEventListener('click', () => {\n        openIconPicker((iconData) => {\n         selectedRackIconData = iconData;\n         const preview = document.getElementById('selected-rack-icon-preview');\n         const container = document.getElementById('selected-rack-icon');\n         const parser = new DOMParser();\n         const doc = parser.parseFromString(iconData.svg, 'image/svg+xml');\n         const svgEl = doc.querySelector('svg');\n         preview.innerHTML = '';\n         if (svgEl) {\n          preview.appendChild(svgEl.cloneNode(true));\n         }\n         const nameSpan = document.createElement('span');\n         nameSpan.textContent = iconData.name;\n         preview.appendChild(nameSpan);\n         container.style.display = 'block';\n        });\n       });\n       document.getElementById('pick-node-icon-btn').addEventListener('click', () => {\n        openIconPicker((iconData) => {\n         selectedNodeIconData = iconData;\n         const preview = document.getElementById('selected-node-icon-preview');\n         const container = document.getElementById('selected-node-icon');\n         const parser = new DOMParser();\n         const doc = parser.parseFromString(iconData.svg, 'image/svg+xml');\n         const svgEl = doc.querySelector('svg');\n         preview.innerHTML = '';\n         if (svgEl) {\n          preview.appendChild(svgEl.cloneNode(true));\n         }\n         const nameSpan = document.createElement('span');\n         nameSpan.textContent = iconData.name;\n         preview.appendChild(nameSpan);\n         container.style.display = 'block';\n        });\n       });\n       document.getElementById('pick-tag-icon-btn').addEventListener('click', () => {\n        if (!currentNodeId) return;\n        openIconPicker((iconData) => {\n         if (!currentNodeId || !NODE_DATA[currentNodeId]) return;\n         if (!NODE_DATA[currentNodeId].tags) {\n          NODE_DATA[currentNodeId].tags = [];\n         }\n         NODE_DATA[currentNodeId].tags.push({\n          type: 'icon',\n          library: iconData.library,\n          name: iconData.name\n         });\n         forgeTheTopology();\n         claimTheImmortal(currentNodeId);\n        });\n       });\n       document.getElementById('pick-new-node-tag-icon-btn').addEventListener('click', () => {\n        openIconPicker((iconData) => {\n         newNodeIconTags.push({\n          type: 'icon',\n          library: iconData.library,\n          name: iconData.name\n         });\n         const container = document.getElementById('new-node-icon-tags');\n         const list = document.getElementById('new-node-icon-tags-list');\n         const badge = document.createElement('div');\n         badge.className = 'icon-badge';\n         badge.style.cssText = 'display: inline-flex; align-items: center; gap: 6px; padding: 6px 10px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 6px; font-size: 13px;';\n         const iconPreview = document.createElement('div');\n         iconPreview.style.cssText = 'width: 16px; height: 16px; display: flex; align-items: center; justify-content: center;';\n         IconLibrary.getIcon(iconData.library, iconData.name).then(svg => {\n          if (svg) {\n           const parser = new DOMParser();\n           const doc = parser.parseFromString(svg, 'image/svg+xml');\n           const svgEl = doc.querySelector('svg');\n           if (svgEl) {\n            svgEl.setAttribute('width', '16');\n            svgEl.setAttribute('height', '16');\n            svgEl.style.fill = 'var(--text-main)';\n            iconPreview.appendChild(svgEl);\n           }\n          }\n         });\n         const name = document.createElement('span');\n         name.textContent = iconData.name;\n         name.style.color = 'var(--text-soft)';\n         const removeBtn = document.createElement('button');\n         removeBtn.textContent = '×';\n         removeBtn.style.cssText = 'background: none; border: none; color: var(--danger); cursor: pointer; font-size: 18px; line-height: 1; padding: 0 4px;';\n         removeBtn.addEventListener('click', () => {\n          const index = newNodeIconTags.findIndex(t => t.type === 'icon' && t.library === iconData.library && t.name === iconData.name);\n          if (index > -1) {\n           newNodeIconTags.splice(index, 1);\n          }\n          badge.remove();\n          if (list.children.length === 0) {\n           container.style.display = 'none';\n          }\n         });\n         badge.appendChild(iconPreview);\n         badge.appendChild(name);\n         badge.appendChild(removeBtn);\n         list.appendChild(badge);\n         container.style.display = 'block';\n        });\n       });\n       document.getElementById('pick-shape-icon-btn').addEventListener('click', () => {\n        if (!currentNodeId) return;\n        openIconPicker((iconData) => {\n         if (!savedStyles[currentNodeId]) {\n          savedStyles[currentNodeId] = {};\n         }\n         if (!savedStyles[currentNodeId][currentStyleScope]) {\n          savedStyles[currentNodeId][currentStyleScope] = {};\n         }\n         savedStyles[currentNodeId][currentStyleScope].icon = {\n          library: iconData.library,\n          name: iconData.name\n         };\n\t\t delete savedStyles[currentNodeId][currentStyleScope].circleColor;\n         delete savedStyles[currentNodeId][currentStyleScope].circleBorder;\n         const shapeSelect = document.getElementById('shape-select');\n         if (!shapeSelect.querySelector('option[value=\"custom-icon\"]')) {\n          const opt = document.createElement('option');\n          opt.value = 'custom-icon';\n          opt.textContent = 'Custom Icon';\n          shapeSelect.insertBefore(opt, shapeSelect.firstChild);\n         }\n         shapeSelect.value = 'custom-icon';\n         forgeTheTopology();\n        });\n       });\n       document.getElementById('add-tag-btn').addEventListener('click', () => {\n        if (!currentNodeId) return;\n        if (!NODE_DATA[currentNodeId]) return;\n        const input = document.getElementById('new-tag-input');\n        const tagText = input.value.trim();\n        if (!tagText) return;\n        if (!NODE_DATA[currentNodeId].tags) {\n         NODE_DATA[currentNodeId].tags = [];\n        }\n        NODE_DATA[currentNodeId].tags.push(tagText);\n        input.value = '';\n        forgeTheTopology();\n        claimTheImmortal(currentNodeId);\n       });\n      function saveRollbackVersion(description = \"Auto-save\") {\n        const version = {\n          timestamp: Date.now(),\n          description,\n          data: captureTheQuickening()\n        };\n        try {\n          const stored = localStorage.getItem(ROLLBACK_STORAGE_KEY);\n          rollbackVersions = stored ? JSON.parse(stored) : [];\n        } catch (e) {\n          rollbackVersions = [];\n        }\n        rollbackVersions.unshift(version);\n        if (rollbackVersions.length > MAX_ROLLBACK_VERSIONS) {\n          rollbackVersions = rollbackVersions.slice(0, MAX_ROLLBACK_VERSIONS);\n        }\n        try {\n          localStorage.setItem(ROLLBACK_STORAGE_KEY, JSON.stringify(rollbackVersions));\n        } catch (e) {\n          console.warn(\"Failed to save rollback version:\", e);\n        }\n      }\n      function loadRollbackVersions() {\n        try {\n          const stored = localStorage.getItem(ROLLBACK_STORAGE_KEY);\n          rollbackVersions = stored ? JSON.parse(stored) : [];\n          displayRollbackVersions();\n        } catch (e) {\n          console.warn(\"Failed to load rollback versions:\", e);\n          rollbackVersions = [];\n        }\n      }\n      function displayRollbackVersions() {\n        const listEl = document.getElementById(\"rollback-list\");\n        if (!listEl) return;\n        if (rollbackVersions.length === 0) {\n          listEl.innerHTML = '<div style=\"text-align: center; padding: 40px; color: var(--text-soft);\">No version history yet. Versions are saved automatically when you save the file.</div>';\n          return;\n        }\n        listEl.innerHTML = rollbackVersions.map((version, index) => {\n          const date = new Date(version.timestamp);\n          const timeStr = date.toLocaleString();\n          const nodeCount = Object.keys(version.data.nodeData || {}).length;\n          const edgeCount = (version.data.edgeData?.list || []).length;\n          return `\n            <div class=\"version-item\" onclick=\"restoreRollbackVersion(${index})\">\n              <div class=\"version-info\">\n                <div class=\"timestamp\">${escapeHtml(timeStr)}</div>\n                <div class=\"details\">${escapeHtml(version.description)} • ${nodeCount} nodes • ${edgeCount} connections</div>\n              </div>\n              <div class=\"version-actions\">\n                <button class=\"btn-cancel\" onclick=\"event.stopPropagation(); deleteRollbackVersion(${index})\" title=\"Delete this version\">🗑️</button>\n              </div>\n            </div>\n          `;\n        }).join('');\n      }\n      function restoreRollbackVersion(index) {\n        if (!confirm(`Restore version from ${new Date(rollbackVersions[index].timestamp).toLocaleString()}?\\n\\nYour current work will be lost unless you save first.`)) {\n          return;\n        }\n        const version = rollbackVersions[index];\n        const data = version.data;\n        NODE_DATA = data.nodeData || {};\n        EDGE_DATA = data.edgeData || { list: [] };\n        RECT_DATA = data.rectData || { list: [] };\n        TEXT_DATA = data.textData || { list: [] };\n        EDGE_LEGEND = data.edgeLegend || {};\n        savedPositions = data.nodePositions || {};\n        savedSizes = data.nodeSizes || {};\n        savedStyles = data.nodeStyles || {};\n        PAGE_STATE = data.page || PAGE_STATE;\n        if (data.canvas) {\n          canvasState.zoom = data.canvas.zoom;\n          canvasState.panX = data.canvas.panX;\n          canvasState.panY = data.canvas.panY;\n        }\n        wieldThePower();\n        forgeTheTopology();\n        document.getElementById(\"rollback-modal\").classList.remove(\"active\");\n        logAuditEvent(\"rollback\", `Restored version from ${new Date(version.timestamp).toLocaleString()}`);\n      }\n      function deleteRollbackVersion(index) {\n        if (!confirm(\"Delete this version from history?\")) return;\n        rollbackVersions.splice(index, 1);\n        try {\n          localStorage.setItem(ROLLBACK_STORAGE_KEY, JSON.stringify(rollbackVersions));\n        } catch (e) {\n          console.warn(\"Failed to delete version:\", e);\n        }\n        displayRollbackVersions();\n      }\n      function clearRollbackHistory() {\n        if (!confirm(\"Clear all version history?\\n\\nThis cannot be undone.\")) return;\n        rollbackVersions = [];\n        localStorage.removeItem(ROLLBACK_STORAGE_KEY);\n        displayRollbackVersions();\n      }\n      function createManualSnapshot() {\n        const description = prompt(\"Enter a description for this snapshot:\", \"Manual snapshot\");\n        if (!description) return;\n        saveRollbackVersion(description);\n        displayRollbackVersions();\n      }\n      function switchTab(index) {\n        if (index === currentTabIndex) return;\n        const currentTab = documentTabs[currentTabIndex];\n        currentTab.nodes = JSON.parse(JSON.stringify(NODE_DATA));\n        currentTab.edges = JSON.parse(JSON.stringify(EDGE_DATA));\n        currentTab.positions = JSON.parse(JSON.stringify(savedPositions));\n        currentTab.sizes = JSON.parse(JSON.stringify(savedSizes));\n        currentTab.styles = JSON.parse(JSON.stringify(savedStyles));\n        currentTab.legend = JSON.parse(JSON.stringify(EDGE_LEGEND));\n        currentTab.rects = JSON.parse(JSON.stringify(RECT_DATA));\n        currentTab.texts = JSON.parse(JSON.stringify(TEXT_DATA));\n        currentTab.pageState = JSON.parse(JSON.stringify(PAGE_STATE));\n        currentTabIndex = index;\n        const newTab = documentTabs[index];\n        NODE_DATA = JSON.parse(JSON.stringify(newTab.nodes));\n        EDGE_DATA = JSON.parse(JSON.stringify(newTab.edges));\n        savedPositions = JSON.parse(JSON.stringify(newTab.positions));\n        savedSizes = JSON.parse(JSON.stringify(newTab.sizes));\n        savedStyles = JSON.parse(JSON.stringify(newTab.styles));\n        EDGE_LEGEND = JSON.parse(JSON.stringify(newTab.legend));\n        RECT_DATA = JSON.parse(JSON.stringify(newTab.rects));\n        TEXT_DATA = JSON.parse(JSON.stringify(newTab.texts));\n        PAGE_STATE = Object.assign({}, DEFAULT_PAGE_STATE, newTab.pageState || {});\n        wieldThePower();\n        document.title = PAGE_STATE.title || newTab.name;\n        document.getElementById(\"page-title\").textContent = PAGE_STATE.title || newTab.name;\n        forgeTheTopology();\n        currentNodeId = null;\n        currentEdgeId = null;\n        currentTextId = null;\n        document.getElementById(\"node-panel\").style.display = \"none\";\n        document.getElementById(\"edge-panel\").style.display = \"none\";\n        document.getElementById(\"text-panel\").style.display = \"none\";\n        displayTabs();\n        logAuditEvent(\"tab\", `Switched to tab: ${newTab.name}`);\n      }\n      function createNewTab() {\n        const nameInput = document.getElementById(\"new-tab-name\");\n        const name = nameInput.value.trim();\n        if (!name) {\n          alert(\"Please enter a tab name\");\n          return;\n        }\n        const newTab = {\n          id: `tab-${Date.now()}`,\n          name,\n          nodes: {},\n          edges: { list: [] },\n          positions: {},\n          sizes: {},\n          styles: {},\n          legend: {},\n          rects: { list: [] },\n          texts: { list: [] },\n      pageState: null\n        };\n        documentTabs.push(newTab);\n        nameInput.value = \"\";\n        displayTabs();\n        logAuditEvent(\"tab\", `Created new tab: ${name}`);\n      }\n      function renameTab(index) {\n        const tab = documentTabs[index];\n        const newName = prompt(\"Enter new name:\", tab.name);\n        if (!newName || newName === tab.name) return;\n        tab.name = newName;\n        displayTabs();\n        logAuditEvent(\"tab\", `Renamed tab to: ${newName}`);\n      }\n      function deleteTab(index) {\n        if (documentTabs.length === 1) {\n          alert(\"Cannot delete the last tab\");\n          return;\n        }\n        if (!confirm(`Delete tab \"${documentTabs[index].name}\"?`)) return;\n        const wasCurrentTab = (index === currentTabIndex);\n        documentTabs.splice(index, 1);\n        if (currentTabIndex >= documentTabs.length) {\n          currentTabIndex = documentTabs.length - 1;\n        } else if (index < currentTabIndex) {\n          currentTabIndex--;\n        }\n        if (wasCurrentTab) {\n          const newTab = documentTabs[currentTabIndex];\n          NODE_DATA = JSON.parse(JSON.stringify(newTab.nodes));\n          EDGE_DATA = JSON.parse(JSON.stringify(newTab.edges));\n          savedPositions = JSON.parse(JSON.stringify(newTab.positions));\n          savedSizes = JSON.parse(JSON.stringify(newTab.sizes));\n          savedStyles = JSON.parse(JSON.stringify(newTab.styles));\n          EDGE_LEGEND = JSON.parse(JSON.stringify(newTab.legend));\n          RECT_DATA = JSON.parse(JSON.stringify(newTab.rects));\n          TEXT_DATA = JSON.parse(JSON.stringify(newTab.texts));\n          PAGE_STATE = Object.assign({}, DEFAULT_PAGE_STATE, newTab.pageState || {});\n          wieldThePower();\n          forgeTheTopology();\n          currentNodeId = null;\n          currentEdgeId = null;\n          currentTextId = null;\n          document.getElementById(\"node-panel\").style.display = \"none\";\n          document.getElementById(\"edge-panel\").style.display = \"none\";\n          document.getElementById(\"text-panel\").style.display = \"none\";\n        }\n        displayTabs();\n        logAuditEvent(\"tab\", `Deleted tab`);\n      }\n      function saveCurrentTheme() {\n        const name = prompt(\"Enter a name for this theme:\", \"My Theme \" + (savedStyleSets.length + 1));\n        if (!name || !name.trim()) return;\n        const existingIndex = savedStyleSets.findIndex(s => s.name.toLowerCase() === name.trim().toLowerCase());\n        if (existingIndex !== -1) {\n          if (!confirm(\"A theme named \\\"\" + name + \"\\\" already exists. Replace it?\")) return;\n          savedStyleSets.splice(existingIndex, 1);\n        }\n        const styleSet = {\n          id: \"mytheme-\" + Date.now(),\n          name: name.trim(),\n          styles: JSON.parse(JSON.stringify(PAGE_STATE))\n        };\n        delete styleSet.styles.title;\n        delete styleSet.styles.viewOnly;\n        savedStyleSets.push(styleSet);\n        rebuildThemeDropdown();\n        document.getElementById(\"theme-preset\").value = styleSet.id;\n        updateDeleteButton();\n        logAuditEvent(\"style\", \"Saved theme: \" + name);\n      }\n      function deleteCurrentTheme() {\n        const select = document.getElementById(\"theme-preset\");\n        const val = select.value;\n        if (!val.startsWith(\"mytheme-\")) return;\n        const index = savedStyleSets.findIndex(s => s.id === val);\n        if (index === -1) return;\n        if (!confirm(\"Delete theme \\\"\" + savedStyleSets[index].name + \"\\\"?\")) return;\n        savedStyleSets.splice(index, 1);\n        rebuildThemeDropdown();\n        select.value = \"defaulted\";\n        updateDeleteButton();\n        logAuditEvent(\"style\", \"Deleted theme\");\n      }\n      function rebuildThemeDropdown() {\n        const group = document.getElementById(\"my-themes-group\");\n        if (!group) return;\n        const select = document.getElementById(\"theme-preset\");\n        const currentValue = select ? select.value : \"\";\n        group.innerHTML = \"\";\n        savedStyleSets.forEach(function(s) {\n          const opt = document.createElement(\"option\");\n          opt.value = s.id;\n          opt.textContent = s.name;\n          group.appendChild(opt);\n        });\n        if (select && currentValue) {\n          select.value = currentValue;\n        }\n      }\n      function updateDeleteButton() {\n        const select = document.getElementById(\"theme-preset\");\n        const btn = document.getElementById(\"delete-theme-btn\");\n        if (!btn) return;\n\t\tbtn.style.display = \"block\";\n\t\tbtn.disabled = !select.value.startsWith(\"mytheme-\");\n      }\n      function displayTabs() {\n        const listEl = document.getElementById(\"tabs-list\");\n        if (!listEl) return;\n        listEl.innerHTML = documentTabs.map((tab, index) => {\n          const nodeCount = Object.keys(tab.nodes).length;\n          const edgeCount = tab.edges.list.length;\n          const isActive = index === currentTabIndex;\n          return `\n            <div class=\"tab-item ${isActive ? 'active' : ''}\" onclick=\"switchTab(${index})\">\n              <div class=\"tab-name\">${escapeHtml(tab.name)}</div>\n              <div class=\"tab-stats\">${nodeCount} nodes • ${edgeCount} connections</div>\n              <div class=\"tab-actions\">\n                <button class=\"btn-cancel\" onclick=\"event.stopPropagation(); renameTab(${index})\" title=\"Rename tab\">✏️</button>\n                ${documentTabs.length > 1 ? '<button class=\"btn-cancel\" onclick=\"event.stopPropagation(); deleteTab(' + index + ')\" title=\"Delete tab\">🗑️</button>' : ''}\n              </div>\n            </div>\n          `;\n        }).join('');\n      }\n      function logAuditEvent(type, description, details = {}) {\n        const event = {\n          timestamp: Date.now(),\n          type,\n          description,\n          details,\n          tab: documentTabs[currentTabIndex]?.name || \"Main\"\n        };\n        auditLog.unshift(event);\n        if (auditLog.length > MAX_AUDIT_ENTRIES) {\n          auditLog = auditLog.slice(0, MAX_AUDIT_ENTRIES);\n        }\n        try {\n          localStorage.setItem(AUDIT_STORAGE_KEY, JSON.stringify(auditLog));\n        } catch (e) {\n          console.warn(\"Failed to save audit log:\", e);\n        }\n      }\n      function loadAuditLog() {\n        let embeddedLog = [];\n        let localLog = [];\n        try {\n          const stateScript = document.getElementById(\"topology-state\");\n          if (stateScript) {\n            const stateData = JSON.parse(stateScript.textContent);\n            if (stateData.auditLog && Array.isArray(stateData.auditLog)) {\n              embeddedLog = stateData.auditLog;\n            }\n          }\n        } catch (e) {\n          console.warn(\"Failed to load embedded audit log:\", e);\n        }\n        try {\n          const stored = localStorage.getItem(AUDIT_STORAGE_KEY);\n          if (stored) {\n            localLog = JSON.parse(stored);\n          }\n        } catch (e) {\n          console.warn(\"Failed to load localStorage audit log:\", e);\n        }\n        const merged = [...embeddedLog, ...localLog];\n        const seen = new Set();\n        auditLog = merged.filter(entry => {\n          const key = entry.timestamp + entry.description;\n          if (seen.has(key)) return false;\n          seen.add(key);\n          return true;\n        }).sort((a, b) => b.timestamp - a.timestamp);\n        if (auditLog.length > MAX_AUDIT_ENTRIES) {\n          auditLog = auditLog.slice(0, MAX_AUDIT_ENTRIES);\n        }\n        try {\n          localStorage.setItem(AUDIT_STORAGE_KEY, JSON.stringify(auditLog));\n        } catch (e) {\n          console.warn(\"Failed to sync audit log:\", e);\n        }\n      }\n      function displayAuditLog(filter = \"all\") {\n        const listEl = document.getElementById(\"audit-log-list\");\n        if (!listEl) return;\n        const filtered = filter === \"all\" ? auditLog : auditLog.filter(e => e.type === filter);\n        if (filtered.length === 0) {\n          listEl.innerHTML = '<div style=\"text-align: center; padding: 40px; color: var(--text-soft);\">No audit entries yet</div>';\n          return;\n        }\n        listEl.innerHTML = filtered.map(event => {\n          const date = new Date(event.timestamp);\n          const timeStr = date.toLocaleString();\n          return `\n            <div class=\"audit-entry ${escapeHtml(event.type)}\">\n              <div class=\"time\">[${escapeHtml(timeStr)}] ${escapeHtml(event.tab)}</div>\n              <div class=\"action\">[${escapeHtml(event.type.toUpperCase())}] ${escapeHtml(event.description)}</div>\n            </div>\n          `;\n        }).join('');\n      }\n      function clearAuditLog() {\n        if (!confirm(\"Clear all audit log entries?\\n\\nThis cannot be undone.\")) return;\n        auditLog = [];\n        localStorage.removeItem(AUDIT_STORAGE_KEY);\n        displayAuditLog();\n      }\n      function exportAuditLog() {\n        if (auditLog.length === 0) {\n          alert(\"No audit entries to export\");\n          return;\n        }\n        const csv = [\n          [\"Timestamp\", \"Tab\", \"Type\", \"Description\"],\n          ...auditLog.map(e => [\n            new Date(e.timestamp).toISOString(),\n            e.tab,\n            e.type,\n            e.description\n          ])\n        ].map(row => row.map(cell => `\"${cell}\"`).join(\",\")).join(\"\\n\");\n        const blob = new Blob([csv], { type: \"text/csv\" });\n        const url = URL.createObjectURL(blob);\n        const a = document.createElement(\"a\");\n        a.href = url;\n        a.download = `audit-log-${Date.now()}.csv`;\n        document.body.appendChild(a);\n        a.click();\n        document.body.removeChild(a);\n        URL.revokeObjectURL(url);\n      }\n      let currentSecretName = null;\n      function createNewSecret() {\n        const nameInput = document.getElementById(\"new-secret-name\");\n        const name = nameInput.value.trim();\n        if (!name) {\n          alert(\"Please enter a note name\");\n          return;\n        }\n        if (encryptedSections[name]) {\n          alert(\"A note note with this name already exists\");\n          return;\n        }\n        currentSecretName = name;\n        encryptedSections[name] = { encrypted: false, data: \"\" };\n        nameInput.value = \"\";\n        document.getElementById(\"secret-editor-title\").textContent = `New note: ${name}`;\n        document.getElementById(\"secret-editor-content\").value = \"\";\n        document.getElementById(\"secrets-modal\").classList.remove(\"active\");\n        document.getElementById(\"secret-editor-modal\").classList.add(\"active\");\n        displaySecrets();\n      }\n      function editSecret(name) {\n        currentSecretName = name;\n        const section = encryptedSections[name];\n        if (section.encrypted) {\n          const password = prompt(`Enter password to decrypt \"${name}\":`);\n          if (!password) return;\n          try {\n            decryptData(section.data, password).then(decrypted => {\n              document.getElementById(\"secret-editor-title\").textContent = `Edit Secret: ${name}`;\n              document.getElementById(\"secret-editor-content\").value = decrypted;\n              document.getElementById(\"secrets-modal\").classList.remove(\"active\");\n              document.getElementById(\"secret-editor-modal\").classList.add(\"active\");\n            }).catch(e => {\n              alert(\"Failed to decrypt. Wrong password?\");\n            });\n          } catch (e) {\n            alert(\"Failed to decrypt. Wrong password?\");\n          }\n        } else {\n          document.getElementById(\"secret-editor-title\").textContent = `Edit note: ${name}`;\n          document.getElementById(\"secret-editor-content\").value = section.data;\n          document.getElementById(\"secrets-modal\").classList.remove(\"active\");\n          document.getElementById(\"secret-editor-modal\").classList.add(\"active\");\n        }\n      }\n      async function saveSecret() {\n        if (!currentSecretName) return;\n        const content = document.getElementById(\"secret-editor-content\").value;\n        const autoEncrypt = document.getElementById(\"secret-auto-encrypt\").checked;\n        if (autoEncrypt) {\n          const password = prompt(`Enter password to encrypt \"${currentSecretName}\":`);\n          if (!password) return;\n          const confirmPassword = prompt(\"Confirm password:\");\n          if (password !== confirmPassword) {\n            alert(\"Passwords do not match\");\n            return;\n          }\n          try {\n            const encrypted = await encryptData(content, password);\n            encryptedSections[currentSecretName] = { encrypted: true, data: encrypted };\n          } catch (e) {\n            alert(\"Encryption failed: \" + e.message);\n            return;\n          }\n        } else {\n          encryptedSections[currentSecretName] = { encrypted: false, data: content };\n        }\n        closeSecretEditor();\n        displaySecrets();\n        logAuditEvent(\"secret\", `Saved note section: ${currentSecretName}`);\n      }\n      function closeSecretEditor() {\n        document.getElementById(\"secret-editor-modal\").classList.remove(\"active\");\n        document.getElementById(\"secrets-modal\").classList.add(\"active\");\n        currentSecretName = null;\n      }\n      function deleteSecret(name) {\n        if (!confirm(`Delete note \"${name}\"?`)) return;\n        delete encryptedSections[name];\n        displaySecrets();\n        logAuditEvent(\"secret\", `Deleted note: ${name}`);\n      }\n      function displaySecrets() {\n        const listEl = document.getElementById(\"secrets-list\");\n        if (!listEl) return;\n        const secrets = Object.keys(encryptedSections);\n        if (secrets.length === 0) {\n          listEl.innerHTML = '<div style=\"text-align: center; padding: 40px; color: var(--text-soft);\">No notes yet</div>';\n          return;\n        }\n        listEl.innerHTML = secrets.map(name => {\n          const section = encryptedSections[name];\n          const status = section.encrypted ? \"🔒 Encrypted\" : \"🔓 Plaintext\";\n          return `\n            <div class=\"secret-item\">\n              <div class=\"secret-name\">${escapeHtml(name)}</div>\n              <div class=\"secret-status\">${status}</div>\n              <div class=\"tab-actions\">\n                <button class=\"btn-cancel\" onclick=\"editSecret('${escapeHtml(name)}')\" title=\"Edit note\">✏️</button>\n                <button class=\"btn-cancel\" onclick=\"deleteSecret('${escapeHtml(name)}')\" title=\"Delete note\">🗑️</button>\n              </div>\n            </div>\n          `;\n        }).join('');\n      }\n       const clearAllBtn = document.getElementById(\"clear-all-btn\");\n       const clearAllModal = document.getElementById(\"clear-all-modal\");\n       const clearAllCancel = document.getElementById(\"clear-all-cancel\");\n       const clearAllConfirm = document.getElementById(\"clear-all-confirm\");\n       clearAllBtn.addEventListener(\"click\", () => {\n        clearAllModal.classList.add(\"active\");\n       });\n       clearAllCancel.addEventListener(\"click\", () => {\n        clearAllModal.classList.remove(\"active\");\n       });\n       clearAllModal.addEventListener(\"click\", (e) => {\n        if (e.target === clearAllModal) {\n         clearAllModal.classList.remove(\"active\");\n        }\n       });\n       clearAllConfirm.addEventListener(\"click\", () => {\n        NODE_DATA = {};\n        EDGE_DATA = { list: [] };\n        EDGE_LEGEND = {};\n        RECT_DATA = { list: [] };\n        TEXT_DATA = { list: [] };\n        savedPositions = {};\n        savedSizes = {};\n        savedStyles = {};\n        undoStack = [];\n        redoStack = [];\n        updateUndoButtons();\n        auditLog = [];\n        localStorage.removeItem(AUDIT_STORAGE_KEY);\n        rollbackVersions = [];\n        currentRollbackIndex = -1;\n        localStorage.removeItem(ROLLBACK_STORAGE_KEY);\n        clipboard = null;\n        selectedNodes.clear();\n        selectedEdges.clear();\n        selectedRects.clear();\n        selectedTexts.clear();\n        documentTabs = [{\n          id: \"main\",\n          name: \"Main Topology\",\n          nodes: {},\n          edges: { list: [] },\n          positions: {},\n          sizes: {},\n          styles: {},\n          legend: {},\n          rects: { list: [] },\n          texts: { list: [] },\n          pageState: null\n        }];\n        currentTabIndex = 0;\n        displayTabs();\n        clearAutosave();\n        try {\n          Object.keys(localStorage).forEach(key => {\n            if (key.startsWith(\"theonefile\")) {\n              localStorage.removeItem(key);\n            }\n          });\n        } catch(e) {}\n        const addLineSelect = document.getElementById(\"add-line-select\");\n        if (addLineSelect) addLineSelect.innerHTML = \"\";\n        const edgeLegendContent = document.getElementById(\"edge-legend-content\");\n        if (edgeLegendContent) edgeLegendContent.innerHTML = \"\";\n        clearAllModal.classList.remove(\"active\");\n        forgeTheTopology();\n        document.getElementById(\"node-panel\").style.display = \"none\";\n        document.getElementById(\"edge-panel\").style.display = \"none\";\n        document.getElementById(\"text-panel\").style.display = \"none\";\n        document.getElementById(\"topology-toolbar\").style.display = \"none\";\n        currentNodeId = null;\n        currentEdgeId = null;\n        currentTextId = null;\n        if (typeof displayRollbackVersions === \"function\") displayRollbackVersions();\n        if (typeof displayAuditLog === \"function\") displayAuditLog();\n        if (typeof forgeTheLegend === \"function\") forgeTheLegend();\n        if (typeof updateZoneLegend === \"function\") updateZoneLegend();\n      });\n       (function addDeleteNodeButton() {\n       const nodePanel = document.getElementById(\"node-panel\");\n       if (!nodePanel) return;\n       let deleteBtn = document.getElementById(\"delete-node-btn\");\n       if (!deleteBtn) {\n        deleteBtn = document.createElement(\"button\");\n        deleteBtn.id = \"delete-node-btn\";\n        deleteBtn.textContent = \"Delete Node\";\n        deleteBtn.style.cssText = \"margin-top:15px; padding:10px 16px; background:var(--danger); color:#fff; border:none; border-radius:6px; cursor:pointer; font-size:clamp(14px,1.6vw,18px); font-weight:600; width:100%;\";\n        nodePanel.appendChild(deleteBtn);\n       }\n       deleteBtn.addEventListener(\"click\", () => {\n        if (!currentNodeId) return;\n        const nodeData = NODE_DATA[currentNodeId];\n        let confirmMessage;\n        let nodesInsideRack = [];\n        if (nodeData?.isRack) {\n         nodesInsideRack = Object.entries(NODE_DATA)\n          .filter(([id, n]) => n.assignedRack === currentNodeId)\n          .map(([id, n]) => n.name || id);\n         if (nodesInsideRack.length > 0) {\n          confirmMessage = `Delete rack \"${nodeData.name}\"?\\n\\nThis will also delete ${nodesInsideRack.length} node(s) inside:\\n• ${nodesInsideRack.join('\\n• ')}`;\n         } else {\n          confirmMessage = `Delete rack \"${nodeData.name}\"? (empty rack)`;\n         }\n        } else {\n         confirmMessage = `Delete node \"${nodeData?.name || currentNodeId}\" and all its connections?`;\n        }\n        challengeTheImmortal(confirmMessage, () => {\n         pushUndo(\"delete node\");\n         if (nodeData?.isRack) {\n          Object.keys(NODE_DATA).forEach(id => {\n           if (NODE_DATA[id]?.assignedRack === currentNodeId) {\n            EDGE_DATA.list = EDGE_DATA.list.filter(e => e.from !== id && e.to !== id);\n            delete NODE_DATA[id];\n            delete savedPositions[id];\n            delete savedSizes[id];\n            delete savedStyles[id];\n           }\n          });\n         }\n         EDGE_DATA.list = EDGE_DATA.list.filter(e => e.from !== currentNodeId && e.to !== currentNodeId);\n         delete NODE_DATA[currentNodeId];\n         delete savedPositions[currentNodeId];\n         delete savedSizes[currentNodeId];\n         delete savedStyles[currentNodeId];\n         currentNodeId = null;\n         currentEdgeId = null;\n         forgeTheTopology();\n         const remainingNodes = Object.keys(NODE_DATA);\n         if (remainingNodes.length > 0) {\n          claimTheImmortal(remainingNodes[0]);\n         } else {\n          document.getElementById(\"node-panel\").style.display = \"none\";\n          document.getElementById(\"edge-panel\").style.display = \"none\";\n          document.getElementById(\"topology-toolbar\").style.display = \"none\";\n         }\n        });\n       });\n      })();\n       function screenshotCanvas() {\n        const svg = document.getElementById(\"map\");\n        const svgClone = svg.cloneNode(true);\n        const viewBox = svg.getAttribute(\"viewBox\").split(\" \").map(Number);\n        const [x, y, width, height] = viewBox;\n        svgClone.setAttribute(\"width\", width);\n        svgClone.setAttribute(\"height\", height);\n        svgClone.setAttribute(\"viewBox\", `${x} ${y} ${width} ${height}`);\n        const wrapper = document.createElement(\"div\");\n        wrapper.style.position = \"absolute\";\n        wrapper.style.left = \"-9999px\";\n        wrapper.appendChild(svgClone);\n        document.body.appendChild(wrapper);\n        function inlineStyles(original, clone) {\n         const elements = original.querySelectorAll(\"*\");\n         const clonedElements = clone.querySelectorAll(\"*\");\n         const rootStyles = getComputedStyle(document.documentElement);\n         const bgColor = rootStyles.getPropertyValue(\"--bg\").trim() || \"#050608\";\n         const bgRect = document.createElementNS(\"http://www.w3.org/2000/svg\", \"rect\");\n         bgRect.setAttribute(\"x\", x);\n         bgRect.setAttribute(\"y\", y);\n         bgRect.setAttribute(\"width\", width);\n         bgRect.setAttribute(\"height\", height);\n         bgRect.setAttribute(\"fill\", bgColor);\n         clone.insertBefore(bgRect, clone.firstChild);\n         elements.forEach((el, index) => {\n          const clonedEl = clonedElements[index];\n          if (!clonedEl) return;\n          const computedStyle = getComputedStyle(el);\n          const svgProps = [\"fill\", \"stroke\", \"stroke-width\", \"stroke-dasharray\", \"stroke-linecap\", \"stroke-linejoin\", \"opacity\", \"font-family\", \"font-size\", \"font-weight\", \"font-style\", \"text-anchor\", \"dominant-baseline\", \"marker-start\", \"marker-end\", ];\n          svgProps.forEach((prop) => {\n           const value = computedStyle.getPropertyValue(prop);\n           if (value && value !== \"none\" && value !== \"normal\") {\n            clonedEl.style[prop] = value;\n           }\n          });\n          clonedEl.removeAttribute(\"class\");\n         });\n        }\n        inlineStyles(svg, svgClone);\n        const svgData = new XMLSerializer().serializeToString(svgClone);\n        document.body.removeChild(wrapper);\n        const svgBlob = new Blob([svgData], {\n         type: \"image/svg+xml;charset=utf-8\",\n        });\n        const url = URL.createObjectURL(svgBlob);\n        const img = new Image();\n        img.onload = function() {\n         const canvas = document.createElement(\"canvas\");\n         canvas.width = width;\n         canvas.height = height;\n         const ctx = canvas.getContext(\"2d\");\n         ctx.drawImage(img, 0, 0);\n         canvas.toBlob(function(blob) {\n          const link = document.createElement(\"a\");\n          const timestamp = new Date().toISOString().slice(0, 10);\n          link.download = `topology-${timestamp}.png`;\n          link.href = URL.createObjectURL(blob);\n          link.click();\n          URL.revokeObjectURL(url);\n          URL.revokeObjectURL(link.href);\n         }, \"image/png\");\n        };\n        img.onerror = function() {\n         console.error(\"Failed to load SVG image\");\n         alert(\"Screenshot failed. Please try again.\");\n         URL.revokeObjectURL(url);\n        };\n        img.src = url;\n       }\n       function exportCanvasSVG() {\n        const svg = document.getElementById(\"map\");\n        const svgClone = svg.cloneNode(true);\n        const viewBox = svg.getAttribute(\"viewBox\").split(\" \").map(Number);\n        const [x, y, width, height] = viewBox;\n        svgClone.setAttribute(\"width\", width);\n        svgClone.setAttribute(\"height\", height);\n        const rootStyles = getComputedStyle(document.documentElement);\n        const bgColor = rootStyles.getPropertyValue(\"--bg\").trim() || \"#050608\";\n        const bgRect = document.createElementNS(\"http://www.w3.org/2000/svg\", \"rect\");\n        bgRect.setAttribute(\"x\", x);\n        bgRect.setAttribute(\"y\", y);\n        bgRect.setAttribute(\"width\", width);\n        bgRect.setAttribute(\"height\", height);\n        bgRect.setAttribute(\"fill\", bgColor);\n        svgClone.insertBefore(bgRect, svgClone.firstChild);\n        const wrapper = document.createElement(\"div\");\n        wrapper.style.position = \"absolute\";\n        wrapper.style.left = \"-9999px\";\n        wrapper.appendChild(svgClone);\n        document.body.appendChild(wrapper);\n        const elements = svg.querySelectorAll(\"*\");\n        const clonedElements = svgClone.querySelectorAll(\"*\");\n        elements.forEach((el, index) => {\n         const clonedEl = clonedElements[index];\n         if (!clonedEl) return;\n         const computedStyle = getComputedStyle(el);\n         const svgProps = [\"fill\", \"stroke\", \"stroke-width\", \"stroke-dasharray\", \"stroke-linecap\", \"stroke-linejoin\", \"opacity\", \"font-family\", \"font-size\", \"font-weight\", \"font-style\", \"text-anchor\", \"dominant-baseline\", \"marker-start\", \"marker-end\", ];\n         svgProps.forEach((prop) => {\n          const value = computedStyle.getPropertyValue(prop);\n          if (value && value !== \"none\" && value !== \"normal\") {\n           clonedEl.setAttribute(prop, value);\n          }\n         });\n         clonedEl.removeAttribute(\"class\");\n        });\n        const svgData = new XMLSerializer().serializeToString(svgClone);\n        document.body.removeChild(wrapper);\n        const blob = new Blob([svgData], {\n         type: \"image/svg+xml;charset=utf-8\",\n        });\n        const url = URL.createObjectURL(blob);\n        const link = document.createElement(\"a\");\n        const timestamp = new Date().toISOString().slice(0, 10);\n        link.download = `topology-${timestamp}.svg`;\n        link.href = url;\n        link.click();\n        URL.revokeObjectURL(url);\n       }\n      let resizeTimeout;\n       window.addEventListener('resize', () => {\n       clearTimeout(resizeTimeout);\n       resizeTimeout = setTimeout(() => {\n       updateDrawToolbarVisibility();\n       updateTopologyToolbarVisibility();\n       }, 100);\n      });\ndocument.addEventListener('DOMContentLoaded', () => {\n  document.querySelectorAll('.dropdown').forEach(dropdown => {\n    const btn = dropdown.querySelector('.dropdown-btn');\n    const menu = dropdown.querySelector('.dropdown-menu');\n    if (!btn || !menu) return;\n    btn.addEventListener('click', (e) => {\n      e.stopPropagation();\n      document.querySelectorAll('.dropdown-menu.open').forEach(m => {\n        if (m !== menu) m.classList.remove('open');\n      });\n      menu.classList.toggle('open');\n    });\n  });\n  document.addEventListener('click', (e) => {\n    if (!e.target.closest('.dropdown')) {\n      document.querySelectorAll('.dropdown-menu.open').forEach(m => m.classList.remove('open'));\n    }\n  });\n  document.querySelectorAll('.dropdown-menu button').forEach(btn => {\n    btn.addEventListener('click', () => {\n      setTimeout(() => {\n        document.querySelectorAll('.dropdown-menu.open').forEach(m => m.classList.remove('open'));\n      }, 100);\n    });\n  });\n  });\nfunction showFormatHelp() {\n  document.getElementById('save-info-modal').classList.add('active');\n  document.querySelectorAll('.help-tab').forEach(t => {\n    t.style.background = 'var(--panel)';\n    t.style.color = 'var(--text-main)';\n  });\n  document.querySelectorAll('.help-tab-content').forEach(c => c.style.display = 'none');\n  const formatsTab = document.querySelector('.help-tab[data-tab=\"formats\"]');\n  if (formatsTab) {\n    formatsTab.style.background = 'var(--accent)';\n    formatsTab.style.color = 'var(--bg)';\n  }\n  document.getElementById('help-tab-formats').style.display = 'block';\n}\nfunction printTopology() {\n  const svg = document.getElementById('map');\n  if (!svg) { window.print(); return; }\n  const originalViewBox = svg.getAttribute('viewBox');\n  let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;\n  let hasContent = false;\n  Object.entries(savedPositions).forEach(([id, pos]) => {\n    const node = NODE_DATA[id];\n    if (!node) return;\n    if (currentView.mode !== 'rack' && node.assignedRack) return;\n    const size = savedSizes[id] || 50;\n    hasContent = true;\n    minX = Math.min(minX, pos.x - size);\n    minY = Math.min(minY, pos.y - size);\n    maxX = Math.max(maxX, pos.x + size);\n    maxY = Math.max(maxY, pos.y + size);\n  });\n  RECT_DATA.list.forEach(rect => {\n    hasContent = true;\n    minX = Math.min(minX, rect.x);\n    minY = Math.min(minY, rect.y);\n    maxX = Math.max(maxX, rect.x + rect.width);\n    maxY = Math.max(maxY, rect.y + rect.height);\n  });\n  TEXT_DATA.list.forEach(text => {\n    hasContent = true;\n    minX = Math.min(minX, text.x - 100);\n    minY = Math.min(minY, text.y - 50);\n    maxX = Math.max(maxX, text.x + 300);\n    maxY = Math.max(maxY, text.y + 50);\n  });\n  EDGE_DATA.list.forEach(edge => {\n    if (edge.points && edge.points.length > 0) {\n      edge.points.forEach(p => {\n        hasContent = true;\n        minX = Math.min(minX, p.x - 10);\n        minY = Math.min(minY, p.y - 10);\n        maxX = Math.max(maxX, p.x + 10);\n        maxY = Math.max(maxY, p.y + 10);\n      });\n    }\n  });\n  if (!hasContent) { window.print(); return; }\n  const padding = 100;\n  minX -= padding; minY -= padding; maxX += padding; maxY += padding;\n  const width = maxX - minX;\n  const height = maxY - minY;\n  const grid = document.getElementById('canvas-grid');\n  const gridDisplay = grid ? grid.style.display : '';\n  if (grid) grid.style.display = 'none';\n  svg.setAttribute('viewBox', `${minX} ${minY} ${width} ${height}`);\n  const originalWidth = svg.style.width;\n  const originalHeight = svg.style.height;\n  svg.style.width = '100%';\n  svg.style.height = '100%';\n  setTimeout(() => {\n    window.print();\n    setTimeout(() => {\n      svg.setAttribute('viewBox', originalViewBox);\n      svg.style.width = originalWidth;\n      svg.style.height = originalHeight;\n      if (grid) grid.style.display = gridDisplay;\n    }, 500);\n  }, 100);\n}\nfunction exportJSONFile() {\n  const data = captureTheQuickening();\n  const jsonStr = JSON.stringify(data, null, 2);\n  const blob = new Blob([jsonStr], { type: \"application/json\" });\n  const url = URL.createObjectURL(blob);\n  const a = document.createElement(\"a\");\n  a.href = url;\n  const safeTitle = (PAGE_STATE.title || \"network-topology\").toLowerCase().replace(/[^a-z0-9\\-]+/g, \"-\");\n  a.download = `${safeTitle}.json`;\n  a.click();\n  URL.revokeObjectURL(url);\n  logAuditEvent(\"export\", `Exported JSON: ${a.download}`);\n}\nfunction exportCSV() {\n  const safeTitle = (PAGE_STATE.title || 'topology').toLowerCase().replace(/[^a-z0-9]+/g, '-');\n  const timestamp = new Date().toISOString();\n  const config = captureTheQuickening();\n  let csv = `#THEONEFILE_CONFIG:${JSON.stringify(config)}\\n`;\n  csv += `#\\n# ${PAGE_STATE.title || 'Network Topology'} - Node List\\n`;\n  csv += `# Exported from The One File on ${timestamp}\\n`;\n  const headers = ['name','ip','role','shape','tags','layer','mac','rackUnit','uHeight','assignedRack','rackCapacity','isRack','locked','groupId','x','y','size','notes','styles'];\n  csv += headers.join(',') + '\\n';\n  Object.entries(NODE_DATA).forEach(([id, node]) => {\n    const pos = savedPositions[id] || { x: 0, y: 0 };\n    const size = savedSizes[id] || 50;\n    const styles = savedStyles[id] ? JSON.stringify(savedStyles[id]) : '';\n    const row = [\n      csvEscape(node.name || ''), csvEscape(node.ip || ''), csvEscape(node.role || ''),\n      node.shape || 'circle', csvEscape((node.tags || []).join(';')), node.layer || 'physical',\n      csvEscape(node.mac || ''), csvEscape(node.rackUnit || ''), node.uHeight || '1',\n      node.assignedRack || '', node.rackCapacity || '', node.isRack ? 'true' : 'false',\n      node.locked ? 'true' : 'false', node.groupId || '', Math.round(pos.x), Math.round(pos.y),\n      size, csvEscape((node.notes || []).join('|')), csvEscape(styles)\n    ];\n    csv += row.join(',') + '\\n';\n  });\n  const blob = new Blob([csv], { 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 = `${safeTitle}.csv`;\n  a.click();\n  URL.revokeObjectURL(url);\n  logAuditEvent('export', `Exported CSV: ${a.download}`);\n}\nfunction csvEscape(val) {\n  if (val === null || val === undefined) return '';\n  const str = String(val);\n  if (str.includes(',') || str.includes('\"') || str.includes('\\n') || str.includes('\\r')) {\n    return '\"' + str.replace(/\"/g, '\"\"') + '\"';\n  }\n  return str;\n}\ndocument.getElementById('import-csv-file')?.addEventListener('change', async (e) => {\n  const file = e.target.files[0];\n  if (!file) return;\n  e.target.value = '';\n  try {\n    const text = await file.text();\n    const lines = text.split(/\\r?\\n/);\n    let config = null;\n    let dataLines = [];\n    let headers = null;\n    for (const line of lines) {\n      const trimmed = line.trim();\n      if (!trimmed) continue;\n      if (trimmed.startsWith('#THEONEFILE_CONFIG:')) {\n        try { config = JSON.parse(trimmed.substring(19)); } catch (err) { console.warn('Failed to parse CSV config:', err); }\n        continue;\n      }\n      if (trimmed.startsWith('#')) continue;\n      if (!headers) { headers = parseCSVLine(trimmed).map(h => h.toLowerCase().trim()); continue; }\n      dataLines.push(trimmed);\n    }\n    if (!headers || dataLines.length === 0) { alert('CSV file has no data rows'); return; }\n    const nameIdx = headers.indexOf('name');\n    if (nameIdx === -1) { alert('CSV must have a \"name\" column'); return; }\n    const nodes = dataLines.map(line => {\n      const values = parseCSVLine(line);\n      const node = {};\n      headers.forEach((h, idx) => { node[h] = values[idx] || ''; });\n      return node;\n    });\n    const hasFullBackup = config && config.documentTabs;\n    const hasConfig = config && (config.pageState || config.page);\n    \n    let importMode = 'add';\n    \n    if (hasFullBackup) {\n      const choice = confirm(\n        `This CSV contains a full backup.\\n\\n` +\n        `• ${nodes.length} nodes in CSV data\\n` +\n        `• ${config.documentTabs?.length || 1} tab(s) in backup\\n` +\n        `• ${config.edgeData?.list?.length || 0} connections in backup\\n\\n` +\n        `Click OK for FULL RESTORE (replace all data)\\n` +\n        `Click Cancel to just ADD the ${nodes.length} nodes`\n      );\n      if (choice) {\n        importMode = 'full';\n      }\n    } else {\n      let confirmMsg = `Import ${nodes.length} nodes from CSV?\\n\\n`;\n      confirmMsg += hasConfig ? 'This will ADD nodes and RESTORE settings/theme.\\n' : 'This will ADD nodes to your existing topology.\\n';\n      confirmMsg += '\\nNote: CSV does not include connections, zones, or text labels.';\n      if (!confirm(confirmMsg)) return;\n    }\n    \n    pushUndo('import csv');\n    \n    if (importMode === 'full' && hasFullBackup) {\n      NODE_DATA = config.nodeData || {};\n      EDGE_DATA = config.edgeData || { list: [] };\n      EDGE_LEGEND = config.edgeLegend || {};\n      RECT_DATA = config.rectData || { list: [] };\n      TEXT_DATA = config.textData || { list: [] };\n      savedPositions = config.nodePositions || {};\n      savedSizes = config.nodeSizes || {};\n      savedStyles = config.nodeStyles || {};\n      if (config.page) {\n        PAGE_STATE = Object.assign({}, DEFAULT_PAGE_STATE, config.page);\n        wieldThePower();\n      }\n      if (config.canvas) {\n        canvasState.zoom = config.canvas.zoom || 1;\n        canvasState.panX = config.canvas.panX || 0;\n        canvasState.panY = config.canvas.panY || 0;\n      }\n      if (config.page?.title) {\n        document.title = config.page.title;\n        document.querySelector(\".editable-page-title\").textContent = config.page.title;\n      }\n      if (config.savedStyleSets) {\n        savedStyleSets = config.savedStyleSets;\n      }\n      if (config.documentTabs) {\n        documentTabs = config.documentTabs;\n        currentTabIndex = config.currentTabIndex || 0;\n      }\n      if (config.savedTopologyView) {\n        savedTopologyView = config.savedTopologyView;\n      }\n      if (config.encryptedSections) {\n        encryptedSections = config.encryptedSections;\n      }\n      if (config.auditLog && Array.isArray(config.auditLog)) {\n        auditLog = config.auditLog;\n        try {\n          localStorage.setItem(AUDIT_STORAGE_KEY, JSON.stringify(auditLog));\n        } catch (e) {\n          console.warn(\"Failed to sync audit log to localStorage:\", e);\n        }\n      }\n      Object.values(NODE_DATA).forEach(node => {\n        if (!node.tags) node.tags = [];\n        if (!node.notes) node.notes = [];\n      });\n      EDGE_DATA.list.forEach(edge => {\n        if (!edge.notes) edge.notes = [];\n      });\n      forgeTheTopology();\n      if (typeof forgeTheLegend === 'function') forgeTheLegend();\n      if (typeof updateZoneLegend === 'function') updateZoneLegend();\n      updateViewBox();\n      logAuditEvent('import', `Imported CSV (full restore): ${file.name}`);\n      alert(`Full restore complete from CSV backup`);\n      return;\n    }\n    if (hasConfig) {\n      Object.assign(PAGE_STATE, config.pageState || config.page);\n      if (config.canvasView || config.canvas) {\n        const canvasConfig = config.canvasView || config.canvas;\n        canvasState.zoom = canvasConfig.zoom || 1;\n        canvasState.panX = canvasConfig.panX || 0;\n        canvasState.panY = canvasConfig.panY || 0;\n      }\n      if (config.legend || config.edgeLegend) Object.assign(EDGE_LEGEND, config.legend || config.edgeLegend);\n      wieldThePower();\n    }\n    let gridX = 200, gridY = 200;\n    const spacing = 150;\n    const perRow = Math.ceil(Math.sqrt(nodes.length));\n    let gridIndex = 0;\n    nodes.forEach((n) => {\n      let baseId = (n.name || 'node').toLowerCase().replace(/[^a-z0-9]+/g, '-');\n      if (!baseId) baseId = 'node';\n      let nodeId = baseId;\n      let counter = 1;\n      while (NODE_DATA[nodeId]) { nodeId = `${baseId}-${counter}`; counter++; }\n      NODE_DATA[nodeId] = {\n        name: n.name || 'Unnamed', ip: n.ip || '', role: n.role || '', shape: n.shape || 'circle',\n        tags: n.tags ? n.tags.split(';').map(t => t.trim()).filter(t => t) : [],\n        notes: n.notes ? n.notes.split('|').map(t => t.trim()).filter(t => t) : [],\n        layer: n.layer || 'physical', mac: n.mac || '', rackUnit: n.rackunit || '',\n        uHeight: n.uheight || '1', assignedRack: n.assignedrack || '', rackCapacity: n.rackcapacity || '',\n        isRack: n.israck === 'true', locked: n.locked === 'true', groupId: n.groupid || null\n      };\n      const hasPosition = n.x && n.y && !isNaN(parseFloat(n.x)) && !isNaN(parseFloat(n.y));\n      if (hasPosition) {\n        savedPositions[nodeId] = { x: parseFloat(n.x), y: parseFloat(n.y) };\n      } else {\n        const row = Math.floor(gridIndex / perRow);\n        const col = gridIndex % perRow;\n        savedPositions[nodeId] = { x: gridX + col * spacing, y: gridY + row * spacing };\n        gridIndex++;\n      }\n      if (n.size && !isNaN(parseFloat(n.size))) savedSizes[nodeId] = parseFloat(n.size);\n      if (n.styles) { try { savedStyles[nodeId] = JSON.parse(n.styles); } catch (err) {} }\n    });\n    forgeTheTopology();\n    updateViewBox();\n    logAuditEvent('import', `Imported CSV: ${file.name} (${nodes.length} nodes)`);\n    alert(`Successfully imported ${nodes.length} nodes`);\n  } catch (err) {\n    console.error('CSV import error:', err);\n    alert('Failed to import CSV: ' + err.message);\n  }\n});\nfunction parseCSVLine(line) {\n  const result = [];\n  let current = '';\n  let inQuotes = false;\n  for (let i = 0; i < line.length; i++) {\n    const char = line[i];\n    if (char === '\"') {\n      if (inQuotes && line[i + 1] === '\"') { current += '\"'; i++; }\n      else { inQuotes = !inQuotes; }\n    } else if (char === ',' && !inQuotes) { result.push(current); current = ''; }\n    else { current += char; }\n  }\n  result.push(current);\n  return result;\n}\n\nfunction exportMarkdown() {\n  const safeTitle = (PAGE_STATE.title || 'topology').toLowerCase().replace(/[^a-z0-9]+/g, '-');\n  const timestamp = new Date().toISOString();\n  const title = PAGE_STATE.title || 'Network Topology';\n  const fullData = captureTheQuickening();\n  const config = fullData;\n  let md = `<!--THEONEFILE_CONFIG\\n${JSON.stringify(config, null, 2)}\\nTHEONEFILE_CONFIG-->\\n\\n`;\n  md += `# ${title}\\n\\n> Exported from The One File on ${timestamp}\\n\\n`;\n  md += `## Legend\\n\\n`;\n  if (Object.keys(EDGE_LEGEND).length > 0) {\n    Object.entries(EDGE_LEGEND).forEach(([color, label]) => { md += `- ${color}: ${label}\\n`; });\n  } else { md += `_No legend entries_\\n`; }\n  md += '\\n## Nodes\\n\\n';\n  Object.entries(NODE_DATA).forEach(([id, node]) => {\n    const pos = savedPositions[id] || { x: 0, y: 0 };\n    const size = savedSizes[id] || 50;\n    const styles = savedStyles[id] || null;\n    md += `### ${id}\\n`;\n    md += `- **Name:** ${node.name || ''}\\n- **IP:** ${node.ip || ''}\\n- **Role:** ${node.role || ''}\\n`;\n    md += `- **Shape:** ${node.shape || 'circle'}\\n- **Tags:** ${(node.tags || []).join('; ') || '_none_'}\\n`;\n    md += `- **Layer:** ${node.layer || 'physical'}\\n- **MAC:** ${node.mac || ''}\\n`;\n    md += `- **Rack Unit:** ${node.rackUnit || ''}\\n- **U Height:** ${node.uHeight || '1'}\\n`;\n    md += `- **Assigned Rack:** ${node.assignedRack || ''}\\n- **Rack Capacity:** ${node.rackCapacity || ''}\\n`;\n    md += `- **Is Rack:** ${node.isRack ? 'true' : 'false'}\\n- **Locked:** ${node.locked ? 'true' : 'false'}\\n`;\n    md += `- **Group ID:** ${node.groupId || ''}\\n- **Position:** ${Math.round(pos.x)}, ${Math.round(pos.y)}\\n- **Size:** ${size}\\n`;\n    if (node.notes && node.notes.length > 0) { md += `- **Notes:**\\n`; node.notes.forEach(note => { md += `  - ${note.replace(/\\n/g, '<br>')}\\n`; }); }\n    if (styles) { md += `- **Styles:** \\`${JSON.stringify(styles)}\\`\\n`; }\n    md += '\\n';\n  });\n  md += `## Connections\\n\\n`;\n  if (EDGE_DATA.list && EDGE_DATA.list.length > 0) {\n    EDGE_DATA.list.forEach(edge => {\n      const fromPort = edge.fromPort ? ` (${edge.fromPort})` : '';\n      const toPort = edge.toPort ? ` (${edge.toPort})` : '';\n      md += `- ${edge.from}${fromPort} --> ${edge.to}${toPort}\\n`;\n      md += `  - **ID:** ${edge.id}\\n  - **Label:** ${(edge.label || '').replace(/\\n/g, '<br>')}\\n  - **Color:** ${edge.color || ''}\\n`;\n      md += `  - **Width:** ${edge.width || 4}\\n  - **Direction:** ${edge.direction || 'none'}\\n`;\n      md += `  - **Routing:** ${edge.routing || 'curved'}\\n  - **Type:** ${edge.type || 'main'}\\n`;\n      md += `  - **Line Style:** ${edge.lineStyle || 'solid'}\\n  - **Group ID:** ${edge.groupId || ''}\\n`;\n      if (edge.points && edge.points.length > 0) { md += `  - **Points:** ${edge.points.map(p => `${Math.round(p.x)},${Math.round(p.y)}`).join(' ')}\\n`; }\n      if (edge.notes && edge.notes.length > 0) { md += `  - **Notes:**\\n`; edge.notes.forEach(note => { md += `    - ${note.replace(/\\n/g, '<br>')}\\n`; }); }\n      md += '\\n';\n    });\n  } else { md += `_No connections_\\n\\n`; }\n  md += `## Zones\\n\\n`;\n  if (RECT_DATA.list && RECT_DATA.list.length > 0) {\n    RECT_DATA.list.forEach(rect => {\n      md += `### ${rect.id}\\n`;\n      md += `- **Position:** ${Math.round(rect.x)}, ${Math.round(rect.y)}\\n- **Size:** ${Math.round(rect.width)} x ${Math.round(rect.height)}\\n`;\n      md += `- **Color:** ${rect.color || ''}\\n- **Style:** ${rect.style || 'filled'}\\n- **Line Style:** ${rect.lineStyle || 'solid'}\\n`;\n      md += `- **Border Color:** ${rect.borderColor || ''}\\n- **Border Width:** ${rect.borderWidth !== undefined ? rect.borderWidth : 2}\\n`;\n      if (rect.notes && rect.notes.length > 0) { md += `- **Notes:**\\n`; rect.notes.forEach(note => { md += `  - ${note.replace(/\\n/g, '<br>')}\\n`; }); }\n      md += '\\n';\n    });\n  } else { md += `_No zones_\\n\\n`; }\n  md += `## Text Labels\\n\\n`;\n  if (TEXT_DATA.list && TEXT_DATA.list.length > 0) {\n    TEXT_DATA.list.forEach(text => {\n      md += `### ${text.id}\\n`;\n      md += `- **Content:** ${(text.content || '').replace(/\\n/g, '<br>')}\\n- **Position:** ${Math.round(text.x)}, ${Math.round(text.y)}\\n`;\n      md += `- **Font Size:** ${text.fontSize || 18}\\n- **Color:** ${text.color || '#e2e8f0'}\\n`;\n      md += `- **Font Weight:** ${text.fontWeight || 'normal'}\\n- **Font Style:** ${text.fontStyle || 'normal'}\\n`;\n      md += `- **Text Align:** ${text.textAlign || 'start'}\\n- **Text Decoration:** ${text.textDecoration || 'none'}\\n`;\n      md += `- **Background Color:** ${text.bgColor || '#000000'}\\n- **Background Enabled:** ${text.bgEnabled ? 'true' : 'false'}\\n`;\n      md += `- **Opacity:** ${text.opacity !== undefined ? text.opacity : 1}\\n\\n`;\n    });\n  } else { md += `_No text labels_\\n\\n`; }\n  const blob = new Blob([md], { type: 'text/markdown;charset=utf-8;' });\n  const url = URL.createObjectURL(blob);\n  const a = document.createElement('a');\n  a.href = url;\n  a.download = `${safeTitle}.md`;\n  a.click();\n  URL.revokeObjectURL(url);\n  logAuditEvent('export', `Exported Markdown: ${a.download}`);\n}\n\ndocument.getElementById('import-markdown-file')?.addEventListener('change', async (e) => {\n  const file = e.target.files[0];\n  if (!file) return;\n  e.target.value = '';\n  try {\n    const text = await file.text();\n    let config = null;\n    const configMatch = text.match(/<!--THEONEFILE_CONFIG\\s*([\\s\\S]*?)\\s*THEONEFILE_CONFIG-->/);\n    if (configMatch) { try { config = JSON.parse(configMatch[1].trim()); } catch (err) { console.warn('Failed to parse Markdown config:', err); } }\n    const nodes = {}, positions = {}, sizes = {}, styles = {}, edges = [], rects = [], texts = [], legend = {};\n    const sections = text.split(/^## /m);\n    sections.forEach(section => {\n      const lines = section.trim().split('\\n');\n      const sectionTitle = lines[0]?.trim().toLowerCase();\n      if (sectionTitle === 'legend') {\n        lines.slice(1).forEach(line => {\n          const match = line.match(/^- (#[a-fA-F0-9]{3,8}):\\s*(.+)$/);\n          if (match) legend[match[1]] = match[2].trim();\n        });\n      } else if (sectionTitle === 'nodes') {\n        let currentNodeId = null, currentNode = null, inNotes = false;\n        lines.slice(1).forEach(line => {\n          const nodeMatch = line.match(/^### (.+)$/);\n          if (nodeMatch) {\n            if (currentNodeId && currentNode) nodes[currentNodeId] = currentNode;\n            currentNodeId = nodeMatch[1].trim();\n            currentNode = { tags: [], notes: [] };\n            inNotes = false;\n            return;\n          }\n          if (!currentNode) return;\n          if (inNotes && line.match(/^\\s+-\\s+(.+)$/)) {\n            const noteMatch = line.match(/^\\s+-\\s+(.+)$/);\n            if (noteMatch) currentNode.notes.push(noteMatch[1].trim().replace(/<br>/g, '\\n'));\n            return;\n          }\n          inNotes = false;\n          const propMatch = line.match(/^- \\*\\*(.+?):\\*\\*\\s*(.*)$/);\n          if (propMatch) {\n            const key = propMatch[1].trim().toLowerCase(), val = propMatch[2].trim();\n            switch (key) {\n              case 'name': currentNode.name = val; break;\n              case 'ip': currentNode.ip = val; break;\n              case 'role': currentNode.role = val; break;\n              case 'shape': currentNode.shape = val; break;\n              case 'tags': currentNode.tags = val === '_none_' ? [] : val.split(';').map(t => t.trim()).filter(t => t); break;\n              case 'layer': currentNode.layer = val; break;\n              case 'mac': currentNode.mac = val; break;\n              case 'rack unit': currentNode.rackUnit = val; break;\n              case 'u height': currentNode.uHeight = val; break;\n              case 'assigned rack': currentNode.assignedRack = val; break;\n              case 'rack capacity': currentNode.rackCapacity = val; break;\n              case 'is rack': currentNode.isRack = val === 'true'; break;\n              case 'locked': currentNode.locked = val === 'true'; break;\n              case 'group id': currentNode.groupId = val || null; break;\n              case 'position':\n                const posMatch = val.match(/(-?\\d+),\\s*(-?\\d+)/);\n                if (posMatch) positions[currentNodeId] = { x: parseFloat(posMatch[1]), y: parseFloat(posMatch[2]) };\n                break;\n              case 'size': sizes[currentNodeId] = parseFloat(val) || 50; break;\n              case 'notes': inNotes = true; break;\n              case 'styles':\n                const stylesMatch = val.match(/`(.+)`/);\n                if (stylesMatch) { try { styles[currentNodeId] = JSON.parse(stylesMatch[1]); } catch (err) {} }\n                break;\n            }\n          }\n        });\n        if (currentNodeId && currentNode) nodes[currentNodeId] = currentNode;\n      } else if (sectionTitle === 'connections') {\n        let currentEdge = null, inNotes = false;\n        lines.slice(1).forEach(line => {\n          const connMatch = line.match(/^- (.+?)\\s*(?:\\(([^)]*)\\))?\\s*-->\\s*(.+?)\\s*(?:\\(([^)]*)\\))?$/);\n          if (connMatch) {\n            if (currentEdge) edges.push(currentEdge);\n            currentEdge = { from: connMatch[1].trim(), to: connMatch[3].trim(), fromPort: connMatch[2] || '', toPort: connMatch[4] || '', notes: [] };\n            inNotes = false;\n            return;\n          }\n          if (!currentEdge) return;\n          if (inNotes && line.match(/^\\s+-\\s+(.+)$/)) {\n            const noteMatch = line.match(/^\\s+-\\s+(.+)$/);\n            if (noteMatch) currentEdge.notes.push(noteMatch[1].trim().replace(/<br>/g, '\\n'));\n            return;\n          }\n          inNotes = false;\n          const propMatch = line.match(/^\\s+- \\*\\*(.+?):\\*\\*\\s*(.*)$/);\n          if (propMatch) {\n            const key = propMatch[1].trim().toLowerCase(), val = propMatch[2].trim();\n            switch (key) {\n              case 'id': currentEdge.id = val; break;\n              case 'label': currentEdge.label = val.replace(/<br>/g, '\\n'); break;\n              case 'color': currentEdge.color = val; break;\n              case 'width': currentEdge.width = parseFloat(val) || 4; break;\n              case 'direction': currentEdge.direction = val; break;\n              case 'routing': currentEdge.routing = val; break;\n              case 'type': currentEdge.type = val; break;\n              case 'line style': currentEdge.lineStyle = val; break;\n              case 'group id': currentEdge.groupId = val || null; break;\n              case 'points':\n                currentEdge.points = val.split(' ').map(p => { const [x, y] = p.split(',').map(Number); return { x, y }; }).filter(p => !isNaN(p.x) && !isNaN(p.y));\n                break;\n              case 'notes': inNotes = true; break;\n            }\n          }\n        });\n        if (currentEdge) edges.push(currentEdge);\n      } else if (sectionTitle === 'zones') {\n        let currentRect = null, inNotes = false;\n        lines.slice(1).forEach(line => {\n          const rectMatch = line.match(/^### (.+)$/);\n          if (rectMatch) {\n            if (currentRect) rects.push(currentRect);\n            currentRect = { id: rectMatch[1].trim(), notes: [] };\n            inNotes = false;\n            return;\n          }\n          if (!currentRect) return;\n          if (inNotes && line.match(/^\\s+-\\s+(.+)$/)) {\n            const noteMatch = line.match(/^\\s+-\\s+(.+)$/);\n            if (noteMatch) currentRect.notes.push(noteMatch[1].trim().replace(/<br>/g, '\\n'));\n            return;\n          }\n          inNotes = false;\n          const propMatch = line.match(/^- \\*\\*(.+?):\\*\\*\\s*(.*)$/);\n          if (propMatch) {\n            const key = propMatch[1].trim().toLowerCase(), val = propMatch[2].trim();\n            switch (key) {\n              case 'position': const posMatch = val.match(/(-?\\d+),\\s*(-?\\d+)/); if (posMatch) { currentRect.x = parseFloat(posMatch[1]); currentRect.y = parseFloat(posMatch[2]); } break;\n              case 'size': const sizeMatch = val.match(/(\\d+)\\s*x\\s*(\\d+)/); if (sizeMatch) { currentRect.width = parseFloat(sizeMatch[1]); currentRect.height = parseFloat(sizeMatch[2]); } break;\n              case 'color': currentRect.color = val; break;\n              case 'style': currentRect.style = val; break;\n              case 'line style': currentRect.lineStyle = val; break;\n              case 'border color': currentRect.borderColor = val; break;\n              case 'border width': currentRect.borderWidth = parseFloat(val) || 2; break;\n              case 'notes': inNotes = true; break;\n            }\n          }\n        });\n        if (currentRect) rects.push(currentRect);\n      } else if (sectionTitle === 'text labels') {\n        let currentText = null;\n        lines.slice(1).forEach(line => {\n          const textMatch = line.match(/^### (.+)$/);\n          if (textMatch) {\n            if (currentText) texts.push(currentText);\n            currentText = { id: textMatch[1].trim() };\n            return;\n          }\n          if (!currentText) return;\n          const propMatch = line.match(/^- \\*\\*(.+?):\\*\\*\\s*(.*)$/);\n          if (propMatch) {\n            const key = propMatch[1].trim().toLowerCase(), val = propMatch[2].trim();\n            switch (key) {\n              case 'content': currentText.content = val.replace(/<br>/g, '\\n'); break;\n              case 'position': const posMatch = val.match(/(-?\\d+),\\s*(-?\\d+)/); if (posMatch) { currentText.x = parseFloat(posMatch[1]); currentText.y = parseFloat(posMatch[2]); } break;\n              case 'font size': currentText.fontSize = parseFloat(val) || 18; break;\n              case 'color': currentText.color = val; break;\n              case 'font weight': currentText.fontWeight = val; break;\n              case 'font style': currentText.fontStyle = val; break;\n              case 'text align': currentText.textAlign = val; break;\n              case 'text decoration': currentText.textDecoration = val; break;\n              case 'background color': currentText.bgColor = val; break;\n              case 'background enabled': currentText.bgEnabled = val === 'true'; break;\n              case 'opacity': currentText.opacity = parseFloat(val) || 1; break;\n            }\n          }\n        });\n        if (currentText) texts.push(currentText);\n      }\n    });\n    const nodeCount = Object.keys(nodes).length, edgeCount = edges.length, rectCount = rects.length, textCount = texts.length;\n    if (nodeCount === 0 && edgeCount === 0 && rectCount === 0 && textCount === 0) {\n      alert('No valid topology data found in Markdown file.\\n\\nMake sure the file was exported from The One File.');\n      return;\n    }\n    let confirmMsg = `Import Markdown topology?\\n\\n- ${nodeCount} nodes\\n- ${edgeCount} connections\\n- ${rectCount} zones\\n- ${textCount} text labels\\n\\nThis will REPLACE all current data.`;\n    if (!confirm(confirmMsg)) return;\n    pushUndo('import markdown');\n    if (config && config.documentTabs) {\n      if (config.page) { Object.assign(PAGE_STATE, config.page); wieldThePower(); }\n      if (config.canvas) {\n        canvasState.zoom = config.canvas.zoom || 1;\n        canvasState.panX = config.canvas.panX || 0;\n        canvasState.panY = config.canvas.panY || 0;\n      }\n      if (config.savedStyleSets) {\n        savedStyleSets = config.savedStyleSets;\n      }\n      if (config.documentTabs) {\n        documentTabs = config.documentTabs;\n        currentTabIndex = config.currentTabIndex || 0;\n      }\n      if (config.savedTopologyView) {\n        savedTopologyView = config.savedTopologyView;\n      }\n      if (config.encryptedSections) {\n        encryptedSections = config.encryptedSections;\n      }\n      if (config.auditLog && Array.isArray(config.auditLog)) {\n        auditLog = config.auditLog;\n        try {\n          localStorage.setItem(AUDIT_STORAGE_KEY, JSON.stringify(auditLog));\n        } catch (e) {\n          console.warn(\"Failed to sync audit log to localStorage:\", e);\n        }\n      }\n    } else if (config && config.pageState) {\n      Object.assign(PAGE_STATE, config.pageState);\n      wieldThePower();\n      if (config.canvasView) {\n        canvasState.zoom = config.canvasView.zoom || 1;\n        canvasState.panX = config.canvasView.panX || 0;\n        canvasState.panY = config.canvasView.panY || 0;\n      }\n    }\n    Object.keys(NODE_DATA).forEach(k => delete NODE_DATA[k]);\n    Object.keys(savedPositions).forEach(k => delete savedPositions[k]);\n    Object.keys(savedSizes).forEach(k => delete savedSizes[k]);\n    Object.keys(savedStyles).forEach(k => delete savedStyles[k]);\n    EDGE_DATA.list = [];\n    RECT_DATA.list = [];\n    TEXT_DATA.list = [];\n    Object.keys(EDGE_LEGEND).forEach(k => delete EDGE_LEGEND[k]);\n    Object.assign(EDGE_LEGEND, legend);\n    Object.entries(nodes).forEach(([id, node]) => {\n      NODE_DATA[id] = { name: node.name || '', ip: node.ip || '', role: node.role || '', shape: node.shape || 'circle', tags: node.tags || [], notes: node.notes || [], mac: node.mac || '', rackUnit: node.rackUnit || '', uHeight: node.uHeight || '1', layer: node.layer || 'physical', assignedRack: node.assignedRack || '', rackCapacity: node.rackCapacity || '', isRack: node.isRack || false, locked: node.locked || false, groupId: node.groupId || null };\n      savedPositions[id] = positions[id] || { x: 500, y: 300 };\n      if (sizes[id]) savedSizes[id] = sizes[id];\n      if (styles[id]) savedStyles[id] = styles[id];\n    });\n    let skippedEdges = 0;\n    edges.forEach(edge => {\n      if (!NODE_DATA[edge.from] || !NODE_DATA[edge.to]) {\n        console.warn(`Skipping orphan edge: ${edge.from} -> ${edge.to}`);\n        skippedEdges++;\n        return;\n      }\n      EDGE_DATA.list.push({ id: edge.id || `edge-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, from: edge.from, to: edge.to, fromPort: edge.fromPort || '', toPort: edge.toPort || '', label: edge.label || '', color: edge.color || '', width: edge.width || 4, direction: edge.direction || 'none', routing: edge.routing || 'curved', type: edge.type || 'main', lineStyle: edge.lineStyle || 'solid', groupId: edge.groupId || null, points: edge.points || [], notes: edge.notes || [] });\n    });\n    if (skippedEdges > 0) console.warn(`Total skipped orphan edges: ${skippedEdges}`);\n    rects.forEach(rect => {\n      RECT_DATA.list.push({ id: rect.id, x: rect.x || 0, y: rect.y || 0, width: rect.width || 100, height: rect.height || 100, color: rect.color || '#f97316', style: rect.style || 'filled', lineStyle: rect.lineStyle || 'solid', borderColor: rect.borderColor || '', borderWidth: rect.borderWidth !== undefined ? rect.borderWidth : 2, notes: rect.notes || [] });\n    });\n    texts.forEach(text => {\n      TEXT_DATA.list.push({ id: text.id, x: text.x || 0, y: text.y || 0, content: text.content || '', fontSize: text.fontSize || 18, color: text.color || '#e2e8f0', fontWeight: text.fontWeight || 'normal', fontStyle: text.fontStyle || 'normal', textAlign: text.textAlign || 'start', textDecoration: text.textDecoration || 'none', bgColor: text.bgColor || '#000000', bgEnabled: text.bgEnabled || false, opacity: text.opacity !== undefined ? text.opacity : 1 });\n    });\n    forgeTheTopology();\n    if (typeof forgeTheLegend === 'function') forgeTheLegend();\n    if (typeof updateZoneLegend === 'function') updateZoneLegend();\n    updateViewBox();\n    logAuditEvent('import', `Imported Markdown: ${file.name} (${nodeCount} nodes, ${edgeCount} connections)`);\n    alert(`Successfully imported:\\n- ${nodeCount} nodes\\n- ${edgeCount} connections\\n- ${rectCount} zones\\n- ${textCount} text labels`);\n  } catch (err) {\n    console.error('Markdown import error:', err);\n    alert('Failed to import Markdown: ' + err.message);\n  }\n});\n\ndocument.getElementById('mobile-export-btn')?.addEventListener('click', () => {\n  document.getElementById('mobile-export-modal').classList.add('active');\n  document.getElementById('topbar-menu').classList.remove('open');\n});\ndocument.getElementById('mobile-import-btn')?.addEventListener('click', () => {\n  document.getElementById('mobile-import-modal').classList.add('active');\n  document.getElementById('topbar-menu').classList.remove('open');\n});\ndocument.getElementById('mobile-export-modal')?.addEventListener('click', (e) => {\n  if (e.target.id === 'mobile-export-modal') e.target.classList.remove('active');\n});\ndocument.getElementById('mobile-import-modal')?.addEventListener('click', (e) => {\n  if (e.target.id === 'mobile-import-modal') e.target.classList.remove('active');\n});\t  \n    </script>\n</body></html>"
  },
  {
    "path": "demos/password-protected/theonefile-networkening-homelab-demo.html",
    "content": "<!DOCTYPE html> \n <html lang=\"en\" style=\"--panel: #2f0e0e; --panel-alt: #10141b; --accent: #a75252; --danger: #f56565; --text-main: #e2e8f0; --text-soft: #94a3b8; --topbar-bg: rgba(9, 12, 20, 0.9); --topbar-border: #1f2533; --topbar-height: 112px; --sidebar-width: 350px; --mobile-footer-height: 40vh; --draw-toolbar-height: 45px; --sidebar-bg: #10141b; --btn-bg: #0b0e13; --btn-text: #e2e8f0; --tag-fill: #1e293b; --tag-text: #e2e8f0; --tag-border: #475569; --input-bg: #0b0e13; --input-text: #e2e8f0; --input-border: #1f2937; --input-font: Inter, system-ui, sans-serif; --input-font-size: 14px; --toolbar-bg: #441215; --toolbar-border: #1f2937; --toolbar-text: #94a3b8; --toolbar-btn-bg: #0b0e13; --toolbar-btn-text: #e2e8f0; --minimap-dots: #94a3b8; --canvas-hint-bg: #0f172a; --canvas-hint-color: #94a3b8; --node-fill: #1e293b; --node-stroke: #475569; --node-title: #e2e8f0; --node-sub: #94a3b8; --node-title-size: 18px; --node-sub-size: 13px; --node-font: Inter, system-ui, sans-serif; --default-edge: #475569; --selection-handle: #f59e0b; --selection-handle-size: 8px; --group-indicator: #4fd1c5;\"><head>\n    <meta charset=\"UTF-8\">\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>The One File</title>\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <!--\n      * ==================================================================================\n      * The One File: The Networkening\n      * !!!!!!!!!!!!!!!!!!!NOTE: THIS IS THE ONLINE VERSION!!!!!!!!!!!!!!!!!!!!!!\n      * Online version uses 3 cdn calls from cdn.jsdelivr.net to display additional icons\n      * Since 3.0 Online version uses http as a form of ping to display uptime\n      * \"There can be only one\". A all in one file topology maker.\n      *\n      * This is your last backup when all others fail. A completely self contained\n      * network topology visualization tool that works as a single HTML file.\n      * Open it anywhere, anytime and the idea lives forever.\n      * ==================================================================================\n      -->\n    <style>\n      :root {\n      color-scheme: dark;\n      --bg: #050608;\n      --panel: #0b0e13;\n      --panel-alt: #10141b;\n      --accent: #4fd1c5;\n      --danger: #f56565;\n      --text-main: #e2e8f0;\n      --text-soft: #94a3b8;\n      --edge-main: #475569;\n      --node-min: 35px;\n      --node-max: 70px;\n      --topbar-bg: rgba(9, 12, 20, 0.9);\n      --topbar-border: #1f2533;\n      }\n\t  html, body, svg, .map-container {\n      touch-action: none;\n      }\n      * {\n      box-sizing: border-box;\n      user-select: none;\n      }\n      input,\n      textarea,\n      [contenteditable=\"true\"] {\n      user-select: text;\n      -webkit-user-select: text;\n      }\n      body {\n      margin: 0;\n      font-family: system-ui, sans-serif;\n      background: radial-gradient(circle at top, #1e2532 0, #050608 70%);\n      color: var(--text-main);\n      display: flex;\n      flex-direction: column;\n      height: 100vh;\n      overflow: hidden;\n      }\n      header {\n      padding: 0 20px;\n      height: var(--topbar-height, 52px);\n      min-height: var(--topbar-height, 52px);\n      background: var(--topbar-bg);\n      backdrop-filter: blur(6px);\n      display: flex;\n      justify-content: space-between;\n      align-items: center;\n      border-bottom: 1px solid var(--topbar-border);\n      gap: 16px;\n      }\n      .title-block {\n      display: flex;\n      flex-direction: column;\n      gap: 6px;\n      min-width: 0;\n      }\n      header h1 {\n      font-size: clamp(22px, 3vw, 32px);\n      margin: 0;\n      white-space: nowrap;\n      text-overflow: ellipsis;\n      overflow: hidden;\n      }\n      .editable-page-title {\n      cursor: pointer;\n      transition: opacity 0.2s;\n      }\n      .editable-page-title:hover {\n      opacity: 0.7;\n      }\n      .save-row {\n      display: flex;\n      align-items: center;\n      gap: 8px;\n      flex-wrap: wrap;\n      }\n\t  .toggle-switch{position:relative;display:inline-block;min-width:44px !important;height:24px;flex-shrink:0;vertical-align:middle;}\n\t\t.toggle-switch input{opacity:0;width:0;height:0;position:absolute;}\n\t\t.toggle-slider{position:absolute;cursor:pointer;top:0;left:0;right:0;bottom:0;background:#475569;transition:.25s;border-radius:24px;}\n\t\t.toggle-slider:before{position:absolute;content:\"\";height:18px;width:18px;left:3px;bottom:3px;background:#e2e8f0;transition:.25s;border-radius:50%;}\n\t\t.toggle-switch input:checked+.toggle-slider{background:var(--accent);}\n\t\t.toggle-switch input:checked+.toggle-slider:before{transform:translateX(20px);}\n\t\t.anim-zone-row{display:flex;justify-content:space-between;align-items:center;padding:6px 0;}\n\t\t.anim-zone-row label{color:var(--text-main);font-size:14px;}\n\t\t.anim-zone-section{font-size:11px;color:var(--text-soft);margin:12px 0 6px;text-transform:uppercase;letter-spacing:0.05em;border-bottom:1px solid var(--edge-main);padding-bottom:4px;}\n\t\t.anim-zone-header{font-size:12px;color:var(--accent);margin-bottom:10px;text-transform:uppercase;letter-spacing:0.05em;font-weight:600;}\n      .save-btn {\n      padding: 6px 12px;\n      background: var(--accent);\n      color: var(--bg);\n      border: none;\n      border-radius: 6px;\n      cursor: pointer;\n      font-size: 14px;\n      font-weight: 600;\n      white-space: nowrap;\n      }\n      .save-btn:hover {\n      opacity: 0.9;\n      }\n      .help-icon {\n      width: 22px;\n      height: 22px;\n      border-radius: 50%;\n      border: 1px solid var(--edge-main);\n      display: inline-flex;\n      align-items: center;\n      justify-content: center;\n      font-size: 14px;\n      cursor: pointer;\n      color: var(--text-soft);\n      background: rgba(15, 23, 42, 0.9);\n      flex-shrink: 0;\n      }\n      .help-icon:hover {\n      color: var(--accent);\n      border-color: var(--accent);\n      }\n      #settings-btn {\n      background: var(--btn-bg, var(--panel));\n      color: var(--btn-text, var(--text-main));\n      border: 1px solid var(--edge-main);\n      padding: 6px 12px;\n      border-radius: 6px;\n      cursor: pointer;\n      font-size: 16px;\n      flex-shrink: 0;\n      }\n      #settings-btn:hover {\n      background: var(--accent);\n      color: var(--bg);\n      }\n      .header-resizer {\n      position: absolute;\n      bottom: 0;\n      left: 0;\n      right: 0;\n      height: 6px;\n      background: transparent;\n      cursor: row-resize;\n      z-index: 1000;\n      transition: background 0.2s;\n      }\n      .header-resizer:hover {\n      background: rgba(79, 209, 197, 0.3);\n      }\n      .header-resizer.resizing {\n      background: rgba(79, 209, 197, 0.5);\n      }\n      .sidebar-resizer {\n      position: absolute;\n      left: 0;\n      top: 0;\n      bottom: 0;\n      width: 6px;\n      background: transparent;\n      cursor: col-resize;\n      z-index: 1000;\n      transition: background 0.2s;\n      }\n      .sidebar-resizer:hover {\n      background: rgba(79, 209, 197, 0.3);\n      }\n      .sidebar-resizer.resizing {\n      background: rgba(79, 209, 197, 0.5);\n      }\n      .mobile-footer-resizer {\n      position: absolute;\n      top: 0;\n      left: 0;\n      right: 0;\n      height: 6px;\n      background: transparent;\n      cursor: row-resize;\n      z-index: 1000;\n      transition: background 0.2s;\n      display: none;\n      }\n      .mobile-footer-resizer:hover {\n      background: rgba(79, 209, 197, 0.3);\n      }\n      .mobile-footer-resizer.resizing {\n      background: rgba(79, 209, 197, 0.5);\n      }\n      @media (max-width: 900px) {\n      .mobile-footer-resizer {\n      display: block;\n      height: 12px;\n      }\n      .sidebar-resizer {\n      display: none;\n      }\n      .header-resizer {\n      height: 12px;\n      }\n      }\n      @media (pointer: coarse) {\n      .header-resizer {\n      height: 16px;\n      }\n      .mobile-footer-resizer {\n      height: 16px;\n      }\n      .sidebar-resizer {\n      width: 16px;\n      }\n      }\n      .resizer-icon {\n      position: absolute;\n      opacity: 0.5;\n      transition: opacity 0.2s, transform 0.2s;\n      pointer-events: none;\n      fill: var(--accent);\n      }\n      .header-resizer .resizer-icon {\n      left: 50%;\n      top: 50%;\n      transform: translate(-50%, -50%);\n      }\n      .sidebar-resizer .resizer-icon {\n      left: 50%;\n      top: 50%;\n      transform: translate(-50%, -50%);\n      }\n      .mobile-footer-resizer .resizer-icon {\n      left: 50%;\n      top: 50%;\n      transform: translate(-50%, -50%);\n      }\n      .header-resizer:hover .resizer-icon,\n      .sidebar-resizer:hover .resizer-icon,\n      .mobile-footer-resizer:hover .resizer-icon {\n      opacity: 1;\n      }\n      .header-resizer.resizing .resizer-icon,\n      .sidebar-resizer.resizing .resizer-icon,\n      .mobile-footer-resizer.resizing .resizer-icon {\n      opacity: 1;\n      transform: translate(-50%, -50%) scale(1.2);\n      }\n      body.resizing {\n      user-select: none;\n      }\n      body.resizing * {\n      cursor: inherit !important;\n      pointer-events: none;\n      }\n      header {\n      position: relative;\n      }\n      .details-panel {\n      position: relative;\n      }\n      main {\n      display: grid;\n      grid-template-columns: 1fr var(--sidebar-width, 350px);\n      flex: 1;\n      }\n      main.sidebar-collapsed {\n      grid-template-columns: 1fr 0;\n      }\n      @media (max-width: 900px) {\n      main {\n      grid-template-columns: 1fr;\n      grid-template-rows: calc(100vh - var(--topbar-height, 52px) - var(--mobile-footer-height, 40vh)) var(--mobile-footer-height, 40vh);\n      }\n      main.sidebar-collapsed {\n      grid-template-rows: 1fr 0;\n      }\n      .details-panel {\n      max-height: var(--mobile-footer-height, 40vh);\n      height: 100%;\n      }\n      }\n      .topology-panel {\n      background: var(--panel);\n      border-right: 1px solid #111827;\n      position: relative;\n      overflow: hidden;\n      }\n      .topology-toolbar {\n      position: absolute;\n      top: 10px;\n      right: 10px;\n      display: none;\n      align-items: center;\n      gap: 8px;\n      padding: 6px 10px;\n      background: var(--toolbar-bg, rgba(15, 23, 42, 0.92));\n      border: 1px solid var(--toolbar-border, #1f2937);\n      border-radius: 6px;\n      z-index: 99;\n      font-size: 13px;\n      color: var(--toolbar-text, #94a3b8);\n      }\n      .topology-toolbar label {\n      color: var(--toolbar-text, var(--text-soft));\n      }\n      .topology-toolbar select {\n      padding: 4px 6px;\n      background: var(--toolbar-btn-bg, var(--panel));\n      border: 1px solid var(--toolbar-border, var(--edge-main));\n      border-radius: 4px;\n      color: var(--toolbar-btn-text, var(--text-main));\n      font-size: 13px;\n      cursor: pointer;\n      }\n      .topology-toolbar button {\n      padding: 4px 10px;\n      background: var(--toolbar-btn-bg, var(--panel));\n      color: var(--toolbar-btn-text, var(--text-main));\n      border: 1px solid var(--toolbar-border, var(--edge-main));\n      border-radius: 4px;\n      cursor: pointer;\n      font-size: 13px;\n      font-weight: 600;\n      }\n      .topology-toolbar button:hover {\n      background: var(--accent);\n      color: var(--bg);\n      }\n      .bulk-toolbar-desktop,\n      .bulk-toolbar-mobile {\n      position: fixed;\n      bottom: 20px;\n      left: 50%;\n      transform: translateX(-50%);\n      z-index: 9999;\n      }\n      .bulk-toolbar-desktop {\n      position: absolute;\n      top: 10px;\n      right: 10px;\n      bottom: auto;\n      left: auto;\n      transform: none;\n      }\n      @media (min-width: 768px) {\n      .bulk-toolbar-mobile {\n      display: none !important;\n      }\n      }\n      .bulk-action-btn {\n      padding: 16px;\n      background: var(--panel-main);\n      border: 1px solid var(--edge-main);\n      border-radius: 8px;\n      color: var(--text-main);\n      font-size: 24px;\n      cursor: pointer;\n      text-align: center;\n      line-height: 1.2;\n      }\n      .bulk-action-btn:active {\n      transform: scale(0.95);\n      background: var(--accent);\n      }\n      .draw-toolbar {\n      position: absolute;\n      top: 10px;\n      left: 10px;\n      display: flex;\n      align-items: center;\n      gap: 6px;\n      padding: 6px 8px;\n      background: var(--toolbar-bg, rgba(15, 23, 42, 0.92));\n      border: 1px solid var(--toolbar-border, #1f2937);\n      border-radius: 6px;\n      z-index: 99;\n      font-size: 13px;\n      pointer-events: auto;\n      color: var(--toolbar-text, #94a3b8);\n      }\n      .draw-toolbar button {\n      padding: 4px 8px;\n      background: var(--toolbar-btn-bg, var(--panel));\n      color: var(--toolbar-btn-text, var(--text-main));\n      border: 1px solid var(--toolbar-border, var(--edge-main));\n      border-radius: 4px;\n      cursor: pointer;\n      font-size: 13px;\n      }\n      .draw-toolbar button:hover {\n      background: var(--accent);\n      color: var(--bg);\n      }\n      .draw-toolbar input[type=\"color\"] {\n      width: 30px;\n      height: 22px;\n      border-radius: 4px;\n      border: 1px solid var(--edge-main);\n      padding: 0;\n      background: transparent;\n      cursor: pointer;\n      }\n      .draw-toolbar select {\n      padding: 4px 6px;\n      background: var(--toolbar-btn-bg, var(--panel));\n      border: 1px solid var(--toolbar-border, var(--edge-main));\n      border-radius: 4px;\n      color: var(--toolbar-btn-text, var(--text-main));\n      font-size: 13px;\n      cursor: pointer;\n      }\n      .legend-container {\n      position: absolute;\n      left: 10px;\n      bottom: 10px;\n      display: flex;\n      flex-direction: column;\n      gap: 6px;\n      padding: 8px 10px;\n      background: var(--toolbar-bg, rgba(15, 23, 42, 0.92));\n      border: 1px solid var(--toolbar-border, #1f2937);\n      border-radius: 6px;\n      font-size: 12px;\n      z-index: 20;\n      max-width: 260px;\n      pointer-events: auto;\n      color: var(--toolbar-text, #94a3b8);\n      }\n      .legend-container {\n      padding-right: 22px;\n      }\n      @media (max-width: 900px) {\n      .legend-container {\n      max-height: calc(100vh - var(--topbar-height, 100px) - 120px);\n      overflow-y: auto;\n      }\n      }\n      @media (max-width: 900px) {\n      .bulk-toolbar-desktop,\n      .topology-toolbar {\n      display: none !important;\n      }\n      }\n      .legend-close-btn {\n      position: absolute;\n      top: 5px;\n      right: 5px;\n      width: 18px;\n      height: 18px;\n      border-radius: 999px;\n      border: 1px solid var(--edge-main);\n      background: var(--panel-alt);\n      color: var(--text-soft);\n      font-size: 11px;\n      display: flex;\n      align-items: center;\n      justify-content: center;\n      cursor: pointer;\n      }\n      .legend-close-btn:hover {\n      background: var(--danger);\n      color: #fff;\n      }\n      .legend-mini-btn {\n      position: absolute;\n      left: 10px;\n      padding: 4px 8px;\n      border-radius: 4px;\n      border: 1px solid var(--toolbar-border, #1f2937);\n      background: var(--toolbar-bg, rgba(15, 23, 42, 0.92));\n      color: var(--toolbar-btn-text, var(--text-main));\n      font-size: 11px;\n      cursor: pointer;\n      z-index: 20;\n      display: none;\n      transition: all 0.2s;\n      }\n      .legend-mini-btn:hover,\n      .legend-mini-btn:active {\n      background: var(--accent);\n      color: var(--bg);\n      border-color: var(--accent);\n      }\n      .legend-title {\n      font-size: 11px;\n      text-transform: uppercase;\n      letter-spacing: 0.06em;\n      color: var(--toolbar-text, var(--text-soft));\n      margin-bottom: 2px;\n      }\n\t  .fov-group {\n        transition: opacity 0.3s ease;\n      }\n      g[data-node-id]:not(:hover) .fov-group {\n        opacity: 0.7;\n      }\n      g[data-node-id]:hover .fov-group {\n        opacity: 1;\n      }\n      .legend-item {\n      display: flex;\n      align-items: center;\n      gap: 8px;\n      }\n      .legend-swatch {\n      width: 14px;\n      height: 14px;\n      border-radius: 3px;\n      border: 1px solid #020617;\n      flex-shrink: 0;\n      }\n      .legend-label {\n      outline: none;\n      cursor: text;\n      flex: 1;\n      min-width: 60px;\n      user-select: text;\n      -webkit-user-select: text;\n      }\n      .legend-label.editing {\n      border-bottom: 1px dashed var(--accent);\n      }\n      .canvas-viewport {\n      position: absolute;\n      top: 0;\n      left: 0;\n      right: 0;\n      bottom: 0;\n      overflow: hidden;\n      }\n      .canvas-viewport.panning {\n      cursor: grabbing !important;\n      }\n      .canvas-viewport.panning * {\n      cursor: grabbing !important;\n      }\n      .canvas-viewport svg {\n      width: 100%;\n      height: 100%;\n      display: block; \n      }\n      .zoom-toolbar {\n      display: flex;\n      gap: 4px;\n      padding: 6px;\n      background: var(--toolbar-bg, rgba(15, 23, 42, 0.92));\n      border: 1px solid var(--toolbar-border, #1f2937);\n      border-radius: 6px;\n      }\n      .zoom-toolbar button {\n      width: 32px;\n      height: 32px;\n      padding: 0;\n      background: var(--toolbar-btn-bg, var(--panel));\n      color: var(--toolbar-btn-text, var(--text-main));\n      border: 1px solid var(--toolbar-border, var(--edge-main));\n      border-radius: 4px;\n      cursor: pointer;\n      font-size: 18px;\n      font-weight: bold;\n      display: flex;\n      align-items: center;\n      justify-content: center;\n      transition: all 0.15s;\n      }\n      .zoom-toolbar button:hover {\n      background: var(--accent);\n      color: var(--bg);\n      }\n      .zoom-toolbar .zoom-level {\n      font-size: 11px;\n      color: var(--toolbar-text, var(--text-soft));\n      text-align: center;\n      padding: 2px 0;\n      min-width: 32px;\n      }\n      .zoom-toolbar .divider {\n      height: 1px;\n      background: var(--toolbar-border, var(--edge-main));\n      margin: 2px 0;\n      }\n      .minimap-zoom-wrapper {\n      position: absolute;\n      bottom: 10px;\n      right: 10px;\n      z-index: 99;\n      }\n      .minimap-container {\n      background: var(--toolbar-bg, rgba(15, 23, 42, 0.92));\n      border: 1px solid var(--toolbar-border, #1f2937);\n      border-radius: 6px;\n      overflow: hidden;\n      margin-bottom: 4px;\n      }\n      .minimap-container svg {\n      width: 100%;\n      height: 100%;\n      }\n\t.minimap-container,\n\t.node-panel,\n\t.edge-panel,\n\t#canvas-viewport {\n\t  contain: layout paint;\n\t}\t\n\t.low-zoom .node-group .node-circle,\n\t.low-zoom .node-group.active .node-circle,\n\t.low-zoom .node-group.selected .node-circle {\n\t  filter: none !important;\n\t  animation: none !important;\n\t}\t\n      .minimap-viewport {\n      fill: rgba(79, 209, 197, 0.2);\n      stroke: var(--accent);\n      stroke-width: 2;\n      cursor: move;\n      }\n      .minimap-node {\n      fill: var(--minimap-dots, var(--text-soft));\n      }\n      .minimap-edge {\n      stroke: var(--edge-main);\n      stroke-width: 8px;\n      fill: none;\n      }\n      .minimap-wall {\n      pointer-events: none;\n      }\n      .minimap-rect {\n      pointer-events: none;\n      }\n\t  .minimap-close-btn {\n      position: absolute;\n      top: 2px;\n      right: 2px;\n      width: 18px;\n      height: 18px;\n      background: var(--toolbar-btn-bg, rgba(15, 23, 42, 0.9));\n      border: 1px solid var(--toolbar-border, var(--edge-main));\n      border-radius: 3px;\n      color: var(--toolbar-text, var(--text-soft));\n      font-size: 11px;\n      cursor: pointer;\n      display: flex;\n      align-items: center;\n      justify-content: center;\n      z-index: 10;\n      padding: 0;\n      line-height: 1;\n      }\n      .minimap-close-btn:hover {\n      background: var(--danger);\n      color: white;\n      border-color: var(--danger);\n      }\n\t  .toolbar-close-btn {\n      width: 24px;\n      height: 24px;\n      background: var(--toolbar-btn-bg, rgba(15, 23, 42, 0.9));\n      border: 1px solid var(--toolbar-border, var(--edge-main));\n      border-radius: 4px;\n      color: var(--toolbar-text, var(--text-soft));\n      font-size: 12px;\n      cursor: pointer;\n      display: flex;\n      align-items: center;\n      justify-content: center;\n      padding: 0;\n      line-height: 1;\n      flex-shrink: 0;\n      }\n      .toolbar-close-btn:hover {\n      background: var(--danger);\n      color: white;\n      border-color: var(--danger);\n      }\n\t  .canvas-hint {\n      position: absolute;\n      top: 50px;\n      left: 50%;\n      transform: translateX(-50%);\n      padding: 8px 16px;\n      background: var(--canvas-hint-bg, rgba(15, 23, 42, 0.92));\n      border: 1px solid var(--toolbar-border, #1f2937);\n      border-radius: 6px;\n      font-size: 12px;\n      color: var(--canvas-hint-color, var(--text-soft));\n      z-index: 18;\n      pointer-events: none;\n      opacity: 0;\n      transition: opacity 0.3s;\n      }\n      .canvas-hint.visible {\n      opacity: 1;\n      pointer-events: auto;\n      }\n      .edge {\n      stroke: var(--edge-main);\n      stroke-width: 4;\n      opacity: 0.75;\n      transition: 0.25s ease-in-out;\n      cursor: pointer;\n      }\n      .edge.backup {\n      stroke: var(--danger);\n      stroke-width: 5;\n      }\n      .edge.active {\n      opacity: 1;\n      stroke-width: 7;\n      filter: drop-shadow(0 0 8px var(--accent, #4fd1c5));\n      }\n      .rect-group.active .rect-shape {\n      filter: drop-shadow(0 0 10px var(--accent, #4fd1c5));\n      animation: pulse 1.2s infinite ease-in-out;\n      }\n      .text-element.active {\n      filter: drop-shadow(0 0 10px var(--accent, #4fd1c5));\n      animation: pulse 1.2s infinite ease-in-out;\n      }\n      .free-preview {\n      fill: none;\n      stroke-dasharray: 4 4;\n      pointer-events: none;\n      }\n@keyframes edge-flow-arrow {\n  0% { offset-distance: 0%; }\n  100% { offset-distance: 100%; }\n}\n@keyframes edge-flow-arrow-reverse {\n  0% { offset-distance: 100%; }\n  100% { offset-distance: 0%; }\n}\n.edge-arrow-forward {\n  offset-rotate: auto;\n  animation: edge-flow-arrow 1.5s linear infinite;\n}\n.edge-arrow-backward {\n  offset-rotate: auto 180deg;\n  animation: edge-flow-arrow-reverse 1.5s linear infinite;\n}\n      .free-point {\n      fill: #e5e7eb;\n      stroke: #0f172a;\n      stroke-width: 1.5;\n      cursor: grab;\n      }\n      .node-circle {\n      fill: var(--node-fill, #1e293b);\n      stroke: var(--node-stroke, #475569);\n      stroke-width: 2;\n      transition: 0.25s ease;\n      transform-origin: center center;\n      }\n      .node-hit-area {\n      cursor: grab;\n      pointer-events: all;\n      }\n      .node-group:hover .node-circle {\n      filter: drop-shadow(0 0 10px rgba(79, 209, 197, 0.45));\n      }\n      .node-group.active .node-circle {\n      stroke: var(--accent);\n      animation: pulse 1.2s infinite ease-in-out;\n      }\n      @keyframes pulse {\n      0% {\n      filter: drop-shadow(0 0 4px #4fd1c5);\n      }\n      50% {\n      filter: drop-shadow(0 0 14px #4fd1c5);\n      }\n      100% {\n      filter: drop-shadow(0 0 4px #4fd1c5);\n      }\n      }\n      @keyframes done-pulse {\n        0%, 100% {\n          transform: scale(1);\n          box-shadow: 0 0 0 0 rgba(79, 209, 197, 0.7);\n        }\n        50% {\n          transform: scale(1.05);\n          box-shadow: 0 0 0 8px rgba(79, 209, 197, 0);\n        }\n      }\n      .done-btn-active {\n        animation: done-pulse 1.5s ease-in-out infinite;\n        background: var(--accent) !important;\n        color: var(--bg) !important;\n      }\n      .node-label {\n      fill: var(--text-main);\n      font-size: 18px;\n      text-anchor: middle;\n      font-weight: 600;\n      }\n      .node-sub {\n      fill: var(--text-soft);\n      font-size: 13px;\n      text-anchor: middle;\n      }\n      .ping-indicator {\n      fill: #6b7280;\n      stroke: #4b5563;\n      stroke-width: 1;\n      }\n      .ping-indicator.online {\n      fill: #10b981;\n      stroke: #059669;\n      }\n      .ping-indicator.offline {\n      fill: #ef4444;\n      stroke: #dc2626;\n      }\n      .ping-indicator.checking {\n      fill: #f59e0b;\n      stroke: #d97706;\n      }\n      @media (max-width: 1024px) {\n      .node-label {\n      font-size: 28px;\n      }\n      .node-sub {\n      font-size: 20px;\n      }\n      }\n      @media (max-width: 768px) {\n      .node-label {\n      font-size: 70px;\n      }\n      .node-sub {\n      font-size: 50px;\n      }\n      }\n      @media (max-width: 380px) {\n      .node-label {\n      font-size: 60px;\n      }\n      .node-sub {\n      font-size: 42px;\n      }\n      }\n      .details-panel {\n      background: var(--sidebar-bg, var(--panel-alt));\n      padding: 22px;\n      padding-bottom: 80px;\n      overflow-y: auto;\n      display: flex;\n      flex-direction: column;\n      gap: 15px;\n      position: relative;\n      transition:\n      width 0.3s ease,\n      min-width 0.3s ease,\n      padding 0.3s ease,\n      opacity 0.3s ease;\n      }\n      .details-panel {\n      min-width: 260px !important;\n      }\n      .details-panel {\n      overflow-y: auto !important;\n      overflow-x: hidden;\n      }\n      .details-panel.collapsed {\n      width: 0 !important;\n      min-width: 0 !important;\n      padding: 0 !important;\n      overflow: hidden;\n      opacity: 0;\n      }\n      body {\n      overflow-x: hidden;\n      overflow-y: hidden;\n      }\n      main {\n      overflow-x: hidden;\n      overflow-y: visible;\n      }\n      .details-panel {\n      overflow-y: auto !important;\n      overflow-x: hidden;\n      }\n      .details-panel.collapsed {\n      min-width: 0 !important;\n      }\n      .sidebar-toggle {\n      position: absolute;\n      left: -16px;\n      top: 50%;\n      transform: translateY(-50%);\n      width: 16px;\n      height: 60px;\n      background: var(--sidebar-bg, var(--panel-alt));\n      border: 1px solid var(--edge-main);\n      border-right: none;\n      border-radius: 8px 0 0 8px;\n      cursor: pointer;\n      display: flex;\n      align-items: center;\n      justify-content: center;\n      color: var(--text-soft);\n      font-size: 10px;\n      z-index: 25;\n      transition:\n      background 0.2s,\n      color 0.2s;\n      }\n      .sidebar-toggle:hover {\n      background: var(--accent);\n      color: var(--bg);\n      }\n      .sidebar-toggle.collapsed {\n      left: 0;\n      border-right: 1px solid var(--edge-main);\n      border-radius: 0 8px 8px 0;\n      position: fixed;\n      right: 0;\n      left: auto;\n      }\n      .details-name {\n      font-size: clamp(22px, 2.5vw, 30px);\n      font-weight: 700;\n      }\n      .details-ip {\n      font-size: clamp(16px, 2vw, 22px);\n      color: var(--text-soft);\n      }\n      .details-role {\n      font-size: clamp(16px, 2vw, 22px);\n      color: var(--accent);\n      }\n      .size-controls {\n      display: flex;\n      gap: 10px;\n      align-items: center;\n      margin-top: 10px;\n      }\n      .size-controls label {\n      font-size: clamp(14px, 1.6vw, 18px);\n      color: var(--text-soft);\n      }\n      .size-controls input[type=\"range\"] {\n      flex: 1;\n      accent-color: var(--accent);\n      }\n      .size-controls button {\n      padding: 6px 12px;\n      background: var(--panel);\n      color: var(--text-main);\n      border: 1px solid var(--edge-main);\n      border-radius: 4px;\n      cursor: pointer;\n      font-size: clamp(12px, 1.4vw, 16px);\n      }\n      .size-controls button:hover {\n      background: var(--accent);\n      color: var(--bg);\n      }\n      .style-section {\n      margin-top: 15px;\n      padding-top: 15px;\n      border-top: 1px solid var(--edge-main);\n      }\n      .style-section summary {\n      cursor: pointer;\n      font-size: clamp(14px, 1.6vw, 20px);\n      text-transform: uppercase;\n      color: var(--text-soft);\n      list-style: none;\n      display: flex;\n      justify-content: space-between;\n      align-items: center;\n      user-select: none;\n      }\n      .style-section summary::-webkit-details-marker {\n      display: none;\n      }\n      .password-overlay {\n      position: fixed;\n      top: 0;\n      left: 0;\n      width: 100%;\n      height: 100%;\n      background: #ffffff;\n      z-index: 9999;\n      }\n      .style-section summary::after {\n      content: \"▼\";\n      transition: transform 0.2s;\n      }\n      .style-section[open] summary::after {\n      transform: rotate(180deg);\n      }\n      .style-content {\n  display: grid;\n  grid-template-columns: 1fr auto;\n  gap: 8px 12px;\n  align-items: center;\n  justify-items: end;\n}\n.style-content label {\n  justify-self: start;\n}\n      .style-row {\n      display: contents;\n      }\n#edge-panel .style-row,\n#rect-panel .style-row,\n#text-panel .style-row {\n  display: flex;\n  align-items: center;\n  gap: 12px;\n  margin-bottom: 8px;\n}\n#edge-panel .style-row label,\n#rect-panel .style-row label,\n#text-panel .style-row label {\n  min-width: 80px;\n}\nbutton:disabled {\n  opacity: 0.5;\n  cursor: not-allowed;\n}\n      .style-row label {\n      font-size: clamp(13px, 1.5vw, 17px);\n      color: var(--text-soft);\n      min-width: 80px;\n      }\n      .style-row input[type=\"color\"] {\n      width: 50px;\n      height: 30px;\n      border: 1px solid var(--edge-main);\n      border-radius: 4px;\n      cursor: pointer;\n      background: transparent;\n      }\n      .style-row input[type=\"number\"] {\n      width: 70px;\n      padding: 4px 8px;\n      background: var(--panel);\n      border: 1px solid var(--edge-main);\n      border-radius: 4px;\n      color: var(--text-main);\n      font-size: clamp(12px, 1.4vw, 16px);\n      }\n      .style-row select {\n      flex: 1;\n      padding: 4px 8px;\n      background: var(--panel);\n      border: 1px solid var(--edge-main);\n      border-radius: 4px;\n      color: var(--text-main);\n      font-size: clamp(12px, 1.4vw, 16px);\n      cursor: pointer;\n      }\n      .editable-text {\n      cursor: pointer;\n      transition: opacity 0.2s;\n      }\n      .editable-text:hover {\n      opacity: 0.7;\n      }\n      .modal {\n      display: none;\n      position: fixed;\n      top: 0;\n      left: 0;\n      width: 100%;\n      height: 100%;\n      z-index: 999999;\n      justify-content: center;\n      align-items: center;\n      overflow: auto;\n      }\n      .modal.active {\n      display: inline-grid;\n      }\n      .modal-content {\n      background: var(--panel-alt);\n      padding: 25px;\n      border-radius: 8px;\n      border: 1px solid var(--edge-main);\n      min-width: 300px;\n      max-width: 90%;\n      }\n      .modal-content h3 {\n      margin: 0 0 15px 0;\n      color: var(--text-main);\n      font-size: clamp(18px, 2vw, 24px);\n      }\n      .modal-content p {\n      color: var(--text-soft);\n      font-size: clamp(14px, 1.6vw, 18px);\n      margin: 0 0 20px 0;\n      }\n      .modal-content input:not([type=\"color\"]),\n      .modal-content select {\n      width: 100%;\n      padding: 10px;\n      background: var(--panel);\n      border: 1px solid var(--edge-main);\n      border-radius: 4px;\n      color: var(--text-main);\n      font-size: clamp(14px, 1.6vw, 18px);\n      margin-bottom: 15px;\n      user-select: text;\n      -webkit-user-select: text;\n      }\n      .modal-content input[type=\"color\"] {\n      width: 60px;\n      height: 36px;\n      padding: 2px;\n      border: 2px solid var(--edge-main);\n      border-radius: 6px;\n      cursor: pointer;\n      background: none;\n      -webkit-appearance: none;\n      appearance: none;\n      }\n      .modal-content input[type=\"color\"]::-webkit-color-swatch-wrapper {\n      padding: 0;\n      }\n      .modal-content input[type=\"color\"]::-webkit-color-swatch {\n      border: none;\n      border-radius: 4px;\n      }\n      .modal-buttons {\n      display: flex;\n      gap: 10px;\n      justify-content: flex-end;\n      margin-top: 10px;\n      }\n      .modal-buttons button {\n      padding: 8px 16px;\n      border: none;\n      border-radius: 4px;\n      cursor: pointer;\n      font-size: clamp(14px, 1.6vw, 18px);\n      font-weight: 600;\n      }\n      .modal-buttons .btn-cancel {\n      background: var(--panel);\n      color: var(--text-main);\n      border: 1px solid var(--edge-main);\n      }\n      .modal-buttons .btn-save {\n      background: var(--accent);\n      color: var(--bg);\n      }\n      .modal-buttons .btn-delete {\n      background: var(--danger);\n      color: white;\n      }\n      .confirm-modal p {\n      color: var(--text-soft);\n      font-size: clamp(14px, 1.6vw, 18px);\n      margin: 0 0 20px 0;\n      }\n      .context-menu-item {\n      transition: background 0.15s;\n      }\n      .context-menu-item:hover {\n      background: var(--panel);\n      }\n      .details-info-row {\n      display: flex;\n      align-items: center;\n      margin: 6px 0;\n      }\n\t .node-group.selected .node-circle {\n      stroke: var(--selection-handle, #f59e0b);\n      stroke-width: 3;\n      }\n\t  .node-group.layer-faded {\n      pointer-events: none;\n      }\n      .edge.layer-faded {\n      pointer-events: none;\n      }\n      .node-group.search-highlight .node-circle,\n      .node-group.search-highlight rect,\n      .node-group.search-highlight polygon {\n      stroke: #10b981;\n      stroke-width: 3;\n      filter: drop-shadow(0 0 8px #10b981);\n      }\n\t  .node-group.search-faded {\n  opacity: 0.15;\n  pointer-events: none;\n}\n.node-group.search-faded .node-label,\n.node-group.search-faded .node-sub {\n  opacity: 0.3;\n}\n.edge-group.search-faded,\n.edge.search-faded {\n  opacity: 0.1;\n}\n      .badge-row {\n      display: flex;\n      flex-wrap: wrap;\n      gap: 8px;\n      }\n     .badge {\n      background: var(--tag-fill, #1e293b);\n      color: var(--tag-text, #e2e8f0);\n      border: 1px solid var(--tag-border, var(--edge-main));\n      padding: 5px 12px;\n      border-radius: 22px;\n      font-size: clamp(12px, 1.4vw, 18px);\n      }\n\t  .badge.wg {\n      color: var(--accent);\n      border-color: var(--accent);\n      }\n\t  input[type=\"text\"],\n      input[type=\"number\"],\n      input[type=\"password\"],\n      textarea,\n      select {\n      background: var(--input-bg, #0b0e13) !important;\n      color: var(--input-text, #e2e8f0) !important;\n      border-color: var(--input-border, #1f2937) !important;\n      font-family: var(--input-font, Inter, system-ui, sans-serif) !important;\n      font-size: var(--input-font-size, 14px) !important;\n      }\n      .section-label {\n      margin-top: 8px;\n      font-size: clamp(14px, 1.6vw, 20px);\n      text-transform: uppercase;\n      color: var(--text-soft);\n      }\n      .list li {\n      margin: 6px 0;\n      font-size: clamp(14px, 1.6vw, 20px);\n      cursor: pointer;\n      display: flex;\n      justify-content: space-between;\n      align-items: center;\n      gap: 10px;\n      }\n      .list li:hover {\n      color: var(--accent);\n      }\n      .delete-note {\n      color: var(--danger);\n      font-size: 18px;\n      cursor: pointer;\n      opacity: 0;\n      transition: opacity 0.2s;\n      flex-shrink: 0;\n      }\n      .list li:hover .delete-note {\n      opacity: 1;\n      }\n      .editing {\n      outline: 2px solid var(--accent);\n      background: #0d141f;\n      padding: 4px;\n      border-radius: 6px;\n      user-select: text;\n      -webkit-user-select: text;\n      }\n      .mobile-menu-btn {\n      display: none;\n      background: var(--panel);\n      border: 1px solid var(--edge-main);\n      color: var(--text-main);\n      font-size: 22px;\n      padding: 6px 12px;\n      border-radius: 6px;\n      cursor: pointer;\n      }\n      #topbar-menu {\n      gap: 8px;\n      }\n      @media (min-width: 721px) {\n      #topbar-menu {\n      display: flex !important;\n      }\n      }\n      @media (max-width: 720px) {\n      #topbar-menu {\n      display: none;\n      }\n      #topbar-menu.open {\n      display: flex;\n      }\n      }\n      @media (max-width: 720px) {\n      .mobile-menu-btn {\n      display: block;\n      }\n      #topbar-menu {\n      position: absolute;\n      top: var(--topbar-height);\n      right: 0;\n      background: var(--panel-alt);\n      border-left: 1px solid var(--topbar-border);\n      border-bottom: 1px solid var(--topbar-border);\n      padding: 12px;\n      display: none;\n      flex-direction: column;\n      width: 180px;\n      z-index: 999;\n      }\n      #topbar-menu.open {\n      display: flex;\n      }\n      header {\n      position: relative;\n      z-index: 9999;\n      }\n      }\n      @media (max-width: 720px) {\n      .draw-toolbar {\n      position: absolute;\n      top: auto;\n      bottom: auto;\n      left: 10px !important;\n      right: auto !important;\n      width: auto;\n      gap: 6px;\n      padding: 8px;\n      }\n      .topology-toolbar {\n      position: absolute;\n      top: auto;\n      bottom: auto;\n      width: auto;\n      gap: 6px;\n      padding: 8px;\n      }\n      .draw-toolbar {\n      top: 10px !important;\n      }\n      .topology-toolbar {\n      top: calc(10px + var(--draw-toolbar-height, 50px)) !important;\n      }\n      .canvas-hint {\n      top: calc(10px + 120px);\n      }\n      }\n      .icon-picker-modal {\n      display: none;\n      position: fixed;\n      top: 0;\n      left: 0;\n      width: 100%;\n      height: 100%;\n      background: rgba(0, 0, 0, 0.8);\n      z-index: 999999999;\n      justify-content: center;\n      align-items: center;\n      }\n      .icon-picker-modal.active {\n      display: flex;\n      }\n      .icon-picker-content {\n      background: var(--panel);\n      border-radius: 12px;\n      width: 90%;\n      max-width: 800px;\n      max-height: 80vh;\n      display: flex;\n      flex-direction: column;\n      border: 1px solid var(--edge-main);\n      }\n      .icon-picker-header {\n      padding: 20px;\n      border-bottom: 1px solid var(--edge-main);\n      }\n      .icon-picker-header h3 {\n      margin: 0 0 15px 0;\n      color: var(--text-main);\n      }\n      .icon-picker-tabs {\n      display: flex;\n      gap: 10px;\n      margin-bottom: 15px;\n      flex-wrap: wrap;\n      }\n      .icon-picker-tab {\n      padding: 8px 16px;\n      background: var(--panel-alt);\n      border: 1px solid var(--edge-main);\n      border-radius: 6px;\n      cursor: pointer;\n      color: var(--text-soft);\n      font-size: 14px;\n      transition: all 0.2s;\n      }\n      .icon-picker-tab:hover {\n      background: var(--accent);\n      color: var(--bg);\n      }\n      .icon-picker-tab.active {\n      background: var(--accent);\n      color: var(--bg);\n      border-color: var(--accent);\n      }\n      .icon-picker-search {\n      width: 100%;\n      padding: 10px 15px;\n      background: var(--panel-alt);\n      border: 1px solid var(--edge-main);\n      border-radius: 6px;\n      color: var(--text-main);\n      font-size: 14px;\n      }\n      .icon-picker-search::placeholder {\n      color: var(--text-soft);\n      }\n      .icon-picker-body {\n      padding: 20px;\n      overflow-y: auto;\n      flex: 1;\n      }\n      .icon-grid {\n      display: grid;\n      grid-template-columns: repeat(auto-fill, minmax(80px, 1fr));\n      gap: 10px;\n      }\n      .icon-item {\n      display: flex;\n      flex-direction: column;\n      align-items: center;\n      justify-content: center;\n      padding: 15px 10px;\n      background: var(--panel-alt);\n      border: 1px solid var(--edge-main);\n      border-radius: 6px;\n      cursor: pointer;\n      transition: all 0.2s;\n      }\n      .icon-item:hover {\n      background: var(--accent);\n      border-color: var(--accent);\n      transform: scale(1.05);\n      }\n      .icon-item svg {\n      width: 32px;\n      height: 32px;\n      fill: var(--text-main);\n      }\n      .icon-item:hover svg {\n      fill: var(--bg);\n      }\n      .icon-item-name {\n      margin-top: 8px;\n      font-size: 10px;\n      color: var(--text-soft);\n      text-align: center;\n      word-break: break-word;\n      }\n      .icon-item:hover .icon-item-name {\n      color: var(--bg);\n      }\n      .icon-picker-loading {\n      text-align: center;\n      padding: 40px;\n      color: var(--text-soft);\n      }\n      .icon-picker-footer {\n      padding: 15px 20px;\n      border-top: 1px solid var(--edge-main);\n      display: flex;\n      justify-content: flex-end;\n      }\n      .icon-btn-cancel {\n      padding: 8px 20px;\n      background: var(--panel-alt);\n      color: var(--text-main);\n      border: 1px solid var(--edge-main);\n      border-radius: 6px;\n      cursor: pointer;\n      font-size: 14px;\n      }\n      .icon-btn-cancel:hover {\n      background: var(--edge-main);\n      }\n      .icon-badge {\n      display: inline-flex;\n      align-items: center;\n      gap: 5px;\n      padding: 4px 8px;\n      background: var(--panel-alt);\n      border-radius: 4px;\n      font-size: 12px;\n      margin: 2px;\n      }\n      .icon-badge svg {\n      width: 16px;\n      height: 16px;\n      fill: currentColor;\n      }\n      .pick-icon-btn {\n      padding: 6px 12px;\n      background: var(--accent);\n      color: var(--bg);\n      border: none;\n      border-radius: 6px;\n      cursor: pointer;\n      font-size: 13px;\n      font-weight: 600;\n      margin-top: 8px;\n      width: 100%;\n      }\n      .pick-icon-btn:hover {\n      opacity: 0.9;\n      }\n      @media (max-width: 768px) {\n      .icon-picker-content {\n      width: 95%;\n      max-height: 90vh;\n      }\n      .modal-content {\n      background: var(--panel-alt);\n      }\n      #search-input {\n      width: 100%;\n      }\n      }\n      .version-item {\n      display: flex;\n      justify-content: space-between;\n      align-items: center;\n      padding: 12px;\n      background: var(--panel);\n      border: 1px solid var(--edge-main);\n      border-radius: 6px;\n      cursor: pointer;\n      transition: all 0.2s;\n      }\n      .version-item:hover {\n      background: var(--panel-alt);\n      border-color: var(--accent);\n      }\n      .version-item.current {\n      border-color: var(--accent);\n      background: rgba(79, 209, 197, 0.1);\n      }\n      .version-info {\n      flex: 1;\n      }\n      .version-info .timestamp {\n      font-size: 13px;\n      color: var(--text-main);\n      font-weight: 600;\n      }\n      .version-info .details {\n      font-size: 11px;\n      color: var(--text-soft);\n      margin-top: 4px;\n      }\n      .version-actions {\n      display: flex;\n      gap: 6px;\n      }\n      .audit-entry {\n      padding: 8px 12px;\n      background: var(--panel);\n      border-left: 3px solid var(--edge-main);\n      border-radius: 4px;\n      font-size: 12px;\n      }\n      .audit-entry.node { border-left-color: #4fd1c5; }\n      .audit-entry.connection { border-left-color: #9f7aea; }\n      .audit-entry.style { border-left-color: #ed8936; }\n      .audit-entry.rack { border-left-color: #48bb78; }\n      .audit-entry.layer { border-left-color: #4299e1; }\n      .audit-entry .time {\n      color: var(--text-soft);\n      font-size: 10px;\n      }\n      .audit-entry .action {\n      color: var(--text-main);\n      font-weight: 500;\n      }\n      .tab-item {\n      display: flex;\n      justify-content: space-between;\n      align-items: center;\n      padding: 12px;\n      background: var(--panel);\n      border: 1px solid var(--edge-main);\n      border-radius: 6px;\n      cursor: pointer;\n      transition: all 0.2s;\n      }\n      .tab-item:hover {\n      background: var(--panel-alt);\n      }\n      .tab-item.active {\n      border-color: var(--accent);\n      background: rgba(79, 209, 197, 0.1);\n      }\n      .tab-item .tab-name {\n      font-weight: 600;\n      color: var(--text-main);\n      flex: 1;\n      }\n      .tab-item .tab-stats {\n      font-size: 11px;\n      color: var(--text-soft);\n      margin-right: 10px;\n      }\n      .tab-actions {\n      display: flex;\n      gap: 6px;\n      }\n      .secret-item {\n      display: flex;\n      justify-content: space-between;\n      align-items: center;\n      padding: 12px;\n      background: var(--panel);\n      border: 1px solid var(--edge-main);\n      border-radius: 6px;\n      border-left: 3px solid var(--danger);\n      }\n      .secret-item .secret-name {\n      font-weight: 600;\n      color: var(--text-main);\n      flex: 1;\n      }\n      .secret-item .secret-status {\n      font-size: 11px;\n      color: var(--text-soft);\n      margin-right: 10px;\n      }\n\t  body.view-only-mode:not(.view-only-inspect) #node-panel,\n      body.view-only-mode:not(.view-only-inspect) #edge-panel,\n      body.view-only-mode:not(.view-only-inspect) #text-panel,\n      body.view-only-mode:not(.view-only-inspect) #rect-panel {\n        display: none !important;\n      }\n      body.view-only-mode .node-group,\n      body.view-only-mode .edge,\n      body.view-only-mode .rect-group,\n      body.view-only-mode .text-element {\n        cursor: default !important;\n      }\n      body.view-only-mode .resize-handle,\n      body.view-only-mode .edge-control-point {\n        display: none !important;\n      }\n      body.view-only-mode #bulk-toolbar,\n      body.view-only-mode #bulk-toolbar-mobile,\n      body.view-only-mode #bulk-actions-modal {\n        display: none !important;\n      }\n      body.view-only-mode::after {\n        content: \"VIEW ONLY • click 5× to inspect\";\n        position: fixed;\n        bottom: 20px;\n        left: 50%;\n        transform: translateX(-50%);\n        background: rgba(245, 101, 101, 0.9);\n        color: white;\n        padding: 6px 16px;\n        border-radius: 20px;\n        font-size: 12px;\n        font-weight: 600;\n        letter-spacing: 1px;\n        z-index: 9999;\n        pointer-events: none;\n      }\n\n.dropdown {\n  position: relative;\n  display: inline-block;\n}\n.dropdown-btn {\n  padding: 6px 12px;\n  background: var(--btn-bg, var(--panel));\n  color: var(--btn-text, var(--text-main));\n  border: 1px solid var(--edge-main);\n  border-radius: 6px;\n  cursor: pointer;\n  font-size: 14px;\n  font-weight: 600;\n  white-space: nowrap;\n}\n.dropdown-btn:hover {\n  background: var(--accent);\n  color: var(--bg);\n}\n.dropdown-menu {\n  display: none;\n  position: absolute;\n  top: 100%;\n  left: 0;\n  min-width: 180px;\n  background: var(--panel);\n  border: 1px solid var(--edge-main);\n  border-radius: 6px;\n  box-shadow: 0 4px 20px rgba(0,0,0,0.4);\n  z-index: 100000;\n  margin-top: 4px;\n  overflow: hidden;\n}\nheader {\n  z-index: 99999;\n  position: relative;\n}\n.dropdown-menu.open {\n  display: block;\n}\n.dropdown-menu button {\n  display: block;\n  width: 100%;\n  padding: 10px 14px;\n  background: none;\n  border: none;\n  color: var(--text-main);\n  text-align: left;\n  cursor: pointer;\n  font-size: 14px;\n}\n.dropdown-menu button:hover {\n  background: var(--panel-alt);\n  color: var(--accent);\n}\n.dropdown-divider {\n  height: 1px;\n  background: var(--edge-main);\n  margin: 4px 0;\n}\n#mobile-export-btn,\n#mobile-import-btn {\n  display: none;\n}\n@media (max-width: 900px) {\n  .save-row .dropdown {\n    display: none !important;\n  }\n  #mobile-export-btn,\n  #mobile-import-btn {\n    display: inline-block;\n  }\n}\n\n@media print {\n  @page {\n    size: landscape;\n    margin: 0.5cm;\n  }\n  html, body {\n    width: 100%;\n    height: 100%;\n    margin: 0;\n    padding: 0;\n    overflow: visible !important;\n  }\n  body * {\n    visibility: hidden;\n  }\n  #canvas-viewport,\n  #canvas-viewport *,\n  #map,\n  #map * {\n    visibility: visible;\n  }\n  #canvas-viewport {\n    position: absolute !important;\n    left: 0 !important;\n    top: 0 !important;\n    right: 0 !important;\n    bottom: 0 !important;\n    width: 100vw !important;\n    height: 100vh !important;\n    overflow: visible !important;\n  }\n  #map {\n    position: absolute !important;\n    left: 0 !important;\n    top: 0 !important;\n    width: 100% !important;\n    height: 100% !important;\n    background: white !important;\n    background-image: none !important;\n  }\n  #canvas-grid {\n    display: none !important;\n  }\n  main, .topology-panel {\n    display: block !important;\n    position: static !important;\n    overflow: visible !important;\n  }\n\n  #map .node-hit-area,\n  #map .group-indicator,\n  #map .lock-indicator,\n  #map .ping-indicator,\n  #map .fov-group,\n  #map .edge-arrow-forward,\n  #map .edge-arrow-backward {\n    display: none !important;\n  }\n\n  #map .node-circle,\n  #map .node-shape {\n    fill: white !important;\n    stroke: #000 !important;\n    stroke-width: 2px !important;\n  }\n\n  #map .node-circle svg,\n  #map .node-circle svg path,\n  #map .node-circle svg circle,\n  #map .node-circle svg rect,\n  #map .node-circle svg polygon {\n    fill: #000 !important;\n    stroke: none !important;\n  }\n\n  #map text {\n    fill: #000 !important;\n    stroke: none !important;\n  }\n  #map .edge,\n  #map polyline,\n  #map line:not([class*=\"grid\"]) {\n    stroke: #333 !important;\n  }\n  #map .rect-group rect {\n    stroke: #333 !important;\n  }\n  header, .sidebar, .mobile-footer, .minimap-zoom-wrapper,\n  .draw-toolbar, .topology-toolbar, .legend-container,\n  .bulk-toolbar, #bulk-toolbar-mobile, .dropdown-menu,\n  #canvas-hint, .node-panel, .edge-panel, .text-panel, .rect-panel {\n    display: none !important;\n  }\n}\n    </style>\n  </head>\n  <body style=\"background: radial-gradient(circle at center top, rgb(30, 37, 50) 0px, rgb(5, 6, 8) 70%);\" class=\"\">\n    <div class=\"icon-picker-modal\" id=\"icon-picker-modal\">\n      <div class=\"icon-picker-content\">\n        <div class=\"icon-picker-header\">\n          <h3>Select Icon</h3>\n          <div class=\"icon-picker-tabs\">\n            <button class=\"icon-picker-tab\" data-library=\"mdi\">MDI</button>\n            <button class=\"icon-picker-tab\" data-library=\"simple\">Simple Icons</button>\n            <button class=\"icon-picker-tab active\" data-library=\"selfhst\">selfh.st/icons</button>\n          </div>\n          <input type=\"text\" class=\"icon-picker-search\" id=\"icon-search\" placeholder=\"Search icons...\" style=\"display: none;\">\n        </div>\n        <div class=\"icon-picker-body\" id=\"icon-picker-body\">\n        </div>\n        <div class=\"icon-picker-footer\">\n          <button class=\"icon-btn-cancel\" id=\"icon-picker-cancel\">Cancel</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"tabs-modal\">\n      <div class=\"modal-content\" style=\"max-width: 600px;\">\n        <p style=\"font-size: 13px; color: var(--text-soft); margin-bottom: 15px;\">Manage multiple topologies</p>\n        <div id=\"tabs-list\" style=\"display: flex; flex-direction: column; gap: 8px; max-height: 400px; overflow-y: auto; margin-bottom: 15px;\">\n            <div class=\"tab-item \" onclick=\"switchTab(0)\">\n              <div class=\"tab-name\">Corporate Site B</div>\n              <div class=\"tab-stats\">107 nodes • 65 connections</div>\n              <div class=\"tab-actions\">\n                <button class=\"btn-cancel\" onclick=\"event.stopPropagation(); renameTab(0)\" title=\"Rename tab\">✏️</button>\n                <button class=\"btn-cancel\" onclick=\"event.stopPropagation(); deleteTab(0)\" title=\"Delete tab\">🗑️</button>\n              </div>\n            </div>\n          \n            <div class=\"tab-item active\" onclick=\"switchTab(1)\">\n              <div class=\"tab-name\">Homelab 2</div>\n              <div class=\"tab-stats\">11 nodes • 10 connections</div>\n              <div class=\"tab-actions\">\n                <button class=\"btn-cancel\" onclick=\"event.stopPropagation(); renameTab(1)\" title=\"Rename tab\">✏️</button>\n                <button class=\"btn-cancel\" onclick=\"event.stopPropagation(); deleteTab(1)\" title=\"Delete tab\">🗑️</button>\n              </div>\n            </div>\n          </div>\n        <div style=\"display: flex; gap: 8px; margin-bottom: 10px;\">\n          <input type=\"text\" id=\"new-tab-name\" placeholder=\"New tab name...\" style=\"flex: 1; padding: 8px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 6px; color: var(--text-main);\">\n          <button class=\"btn-save\" onclick=\"createNewTab()\">+ Add Tab</button>\n        </div>\n        <div class=\"modal-buttons\">\n          <button class=\"btn-cancel\" id=\"tabs-modal-close\">Close</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"rollback-modal\">\n      <div class=\"modal-content\" style=\"max-width: 700px;\">\n        <h3>Version History</h3>\n        <p style=\"font-size: 13px; color: var(--text-soft); margin-bottom: 15px;\">Limit: Snapshots</p>\n        <div id=\"rollback-list\" style=\"display: flex; flex-direction: column; gap: 8px; max-height: 450px; overflow-y: auto; margin-bottom: 15px;\"></div>\n        <div style=\"display: flex; gap: 8px; margin-bottom: 10px;\">\n          <button class=\"btn-cancel\" onclick=\"clearRollbackHistory()\">Clear History</button>\n          <button class=\"btn-save\" onclick=\"createManualSnapshot()\">Create Snapshot</button>\n        </div>\n        <div class=\"modal-buttons\">\n          <button class=\"btn-cancel\" id=\"rollback-modal-close\">Close</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"audit-log-modal\">\n      <div class=\"modal-content\" style=\"max-width: 800px;\">\n        <h3>Audit Log</h3>\n        <p style=\"font-size: 13px; color: var(--text-soft); margin-bottom: 15px;\">Track all changes made to your topology</p>\n        <div style=\"display: flex; gap: 8px; margin-bottom: 10px;\">\n          <select id=\"audit-filter\" style=\"padding: 8px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 6px; color: var(--text-main);\">\n            <option value=\"all\">All Events</option>\n            <option value=\"node\">Node Operations</option>\n            <option value=\"connection\">Connections</option>\n            <option value=\"style\">Style Changes</option>\n            <option value=\"rack\">Rack Operations</option>\n            <option value=\"layer\">Layer Changes</option>\n          </select>\n          <button class=\"btn-save\" onclick=\"exportAuditLog()\">Export</button>\n        </div>\n        <div id=\"audit-log-list\" style=\"display: flex; flex-direction: column; gap: 4px; max-height: 450px; overflow-y: auto; margin-bottom: 15px; font-family: monospace; font-size: 12px;\"></div>\n        <div style=\"display: flex; gap: 8px;\">\n          <button class=\"btn-cancel\" onclick=\"clearAuditLog()\">Clear Log</button>\n          <button class=\"btn-cancel\" id=\"audit-log-modal-close\">Close</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"port-map-modal\">\n      <div class=\"modal-content\" style=\"max-width: 900px;\">\n        <h3>Port Map</h3>\n        <p style=\"font-size: 13px; color: var(--text-soft); margin-bottom: 15px;\">All connections with port assignments</p>\n        <div style=\"display: flex; gap: 8px; margin-bottom: 10px;\">\n          <input type=\"text\" id=\"port-map-search\" placeholder=\"Search devices or ports...\" style=\"flex: 1; min-width: 200px; padding: 8px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 6px; color: var(--text-main);\">\n          <select id=\"port-map-filter\" style=\"padding: 8px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 6px; color: var(--text-main);\">\n            <option value=\"all\">All Connections</option>\n            <option value=\"with-ports\">With Ports Only</option>\n            <option value=\"without-ports\">Missing Ports</option>\n          </select>\n        </div>\n        <div id=\"port-map-table\" style=\"max-height: 450px; overflow-y: auto; margin-bottom: 15px;\"><div style=\"text-align: center; padding: 40px; color: var(--text-soft);\">No connections found</div></div>\n        <div style=\"display: flex; gap: 8px;\">\n          <button class=\"btn-save\" onclick=\"exportPortMap()\">Export CSV</button>\n          <button class=\"btn-cancel\" id=\"port-map-modal-close\">Close</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"secrets-modal\">\n      <div class=\"modal-content\" style=\"max-width: 600px;\">\n        <h3>Notes</h3>\n        <p style=\"font-size: 13px; color: var(--text-soft); margin-bottom: 15px;\">Notes can also be stored with AES 256 encryption</p>\n        <div id=\"secrets-list\" style=\"display: flex; flex-direction: column; gap: 8px; max-height: 350px; overflow-y: auto; margin-bottom: 15px;\">\n        </div>\n        <div style=\"display: flex; gap: 8px; margin-bottom: 10px;\">\n          <input type=\"text\" id=\"new-secret-name\" placeholder=\"Section name (e.g., 'Root Passwords')...\" style=\"flex: 1; padding: 8px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 6px; color: var(--text-main);\">\n          <button class=\"btn-save\" onclick=\"createNewSecret()\">+ Add Note</button>\n        </div>\n        <div class=\"modal-buttons\">\n          <button class=\"btn-cancel\" id=\"secrets-modal-close\">Close</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"secret-editor-modal\">\n      <div class=\"modal-content\" style=\"max-width: 600px;\">\n        <h3 id=\"secret-editor-title\">Edit Note</h3>\n        <textarea id=\"secret-editor-content\" placeholder=\"Enter sensitive information here...\" style=\"width: 100%; height: 200px; padding: 12px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 6px; color: var(--text-main); font-family: monospace; resize: vertical; margin-bottom: 15px;\"></textarea>\n        <div style=\"margin-bottom: 15px;\">\n          <label style=\"display: flex; align-items: center; gap: 8px; cursor: pointer;\">\n          <input type=\"checkbox\" id=\"secret-auto-encrypt\" checked=\"\" style=\"cursor: pointer;\">\n          <span>Encrypt Note</span>\n          </label>\n        </div>\n        <div class=\"modal-buttons\">\n          <button class=\"btn-cancel\" onclick=\"closeSecretEditor()\">Cancel</button>\n          <button class=\"btn-save\" onclick=\"saveSecret()\">Save</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"mobile-export-modal\">\n      <div class=\"modal-content\" style=\"max-width: 320px;\">\n        <h3 style=\"margin: 0 0 20px 0;\">Export</h3>\n        <div style=\"display: flex; flex-direction: column; gap: 12px;\">\n          <button onclick=\"screenshotCanvas(); document.getElementById('mobile-export-modal').classList.remove('active');\" style=\"padding: 14px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; color: var(--text-main); font-size: 16px; cursor: pointer;\">PNG Image</button>\n          <button onclick=\"exportCanvasSVG(); document.getElementById('mobile-export-modal').classList.remove('active');\" style=\"padding: 14px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; color: var(--text-main); font-size: 16px; cursor: pointer;\">SVG Vector</button>\n          <button onclick=\"exportJSONFile(); document.getElementById('mobile-export-modal').classList.remove('active');\" style=\"padding: 14px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; color: var(--text-main); font-size: 16px; cursor: pointer;\">JSON</button>\n          <button onclick=\"exportMarkdown(); document.getElementById('mobile-export-modal').classList.remove('active');\" style=\"padding: 14px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; color: var(--text-main); font-size: 16px; cursor: pointer;\">Markdown</button>\n          <button onclick=\"exportCSV(); document.getElementById('mobile-export-modal').classList.remove('active');\" style=\"padding: 14px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; color: var(--text-main); font-size: 16px; cursor: pointer;\">CSV</button>\n          <button onclick=\"printTopology(); document.getElementById('mobile-export-modal').classList.remove('active');\" style=\"padding: 14px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; color: var(--text-main); font-size: 16px; cursor: pointer;\">Print...</button>\n\t\t  <button onclick=\"showFormatHelp(); document.getElementById('mobile-import-modal').classList.remove('active');\" style=\"padding: 14px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; color: var(--text-main); font-size: 16px; cursor: pointer;\">Import Help</button>\n        </div>\n        <button onclick=\"document.getElementById('mobile-export-modal').classList.remove('active');\" style=\"margin-top: 20px; padding: 12px; background: var(--danger); border: none; border-radius: 8px; color: white; font-size: 16px; cursor: pointer; width: 100%;\">Cancel</button>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"mobile-import-modal\">\n      <div class=\"modal-content\" style=\"max-width: 320px;\">\n        <h3 style=\"margin: 0 0 20px 0;\">Import</h3>\n        <div style=\"display: flex; flex-direction: column; gap: 12px;\">\n          <button onclick=\"document.getElementById('import-json-file').click(); document.getElementById('mobile-import-modal').classList.remove('active');\" style=\"padding: 14px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; color: var(--text-main); font-size: 16px; cursor: pointer;\">JSON</button>\n          <button onclick=\"document.getElementById('import-markdown-file').click(); document.getElementById('mobile-import-modal').classList.remove('active');\" style=\"padding: 14px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; color: var(--text-main); font-size: 16px; cursor: pointer;\">Markdown</button>\n          <button onclick=\"document.getElementById('import-csv-file').click(); document.getElementById('mobile-import-modal').classList.remove('active');\" style=\"padding: 14px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; color: var(--text-main); font-size: 16px; cursor: pointer;\">CSV</button>\n          <button onclick=\"showFormatHelp(); document.getElementById('mobile-import-modal').classList.remove('active');\" style=\"padding: 14px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; color: var(--text-main); font-size: 16px; cursor: pointer;\">Export Help</button>\n        </div>\n        <button onclick=\"document.getElementById('mobile-import-modal').classList.remove('active');\" style=\"margin-top: 20px; padding: 12px; background: var(--danger); border: none; border-radius: 8px; color: white; font-size: 16px; cursor: pointer; width: 100%;\">Cancel</button>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"edit-modal\">\n      <div class=\"modal-content\">\n        <h3 id=\"modal-title\">Edit Name</h3>\n        <input type=\"text\" id=\"modal-input\">\n        <div class=\"modal-buttons\">\n          <button class=\"btn-cancel\" id=\"modal-cancel\">Cancel</button>\n          <button class=\"btn-save\" id=\"modal-save\">Save</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal confirm-modal\" id=\"confirm-modal\">\n      <div class=\"modal-content\">\n        <h3>Confirm</h3>\n        <p id=\"confirm-message\"> Are you sure you want to delete this line? </p>\n        <div class=\"modal-buttons\">\n          <button class=\"btn-cancel\" id=\"confirm-cancel\">Cancel</button>\n          <button class=\"btn-delete\" id=\"confirm-delete\">Delete</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"save-info-modal\">\n      <div class=\"modal-content\" style=\"max-width: 700px; max-height: 80vh; overflow-y: auto;\">\n        <h3 style=\"margin-bottom: 16px;\">Help</h3>\n        <div style=\"display: flex; gap: 8px; margin-bottom: 16px; flex-wrap: wrap;\">\n          <button class=\"help-tab active\" data-tab=\"general\" style=\"padding: 8px 16px; border: 1px solid var(--edge-main); background: var(--accent); color: var(--bg); border-radius: 6px; cursor: pointer; font-weight: 600;\">General</button>\n          <button class=\"help-tab\" data-tab=\"desktop\" style=\"padding: 8px 16px; border: 1px solid var(--edge-main); background: var(--panel); color: var(--text-main); border-radius: 6px; cursor: pointer; font-weight: 600;\">Desktop</button>\n          <button class=\"help-tab\" data-tab=\"mobile\" style=\"padding: 8px 16px; border: 1px solid var(--edge-main); background: var(--panel); color: var(--text-main); border-radius: 6px; cursor: pointer; font-weight: 600;\">Mobile</button>\n\t\t  <button class=\"help-tab\" data-tab=\"formats\" style=\"padding: 8px 16px; border: 1px solid var(--edge-main); background: var(--panel); color: var(--text-main); border-radius: 6px; cursor: pointer; font-weight: 600;\">Import/Export</button>\n        </div>\n        <div id=\"help-tab-general\" class=\"help-tab-content\" style=\"display: block;\">\n          <ul style=\"margin-left: 20px; margin-bottom: 12px; line-height: 1.8;\">\n            <li><strong>Add Nodes:</strong> Click \"+ Node\" or \"+ Rack\" in the top menu</li>\n            <li><strong>Connect Nodes:</strong> Select a node, then use \"Add Connection\" in the panel</li>\n            <li><strong>Move Nodes:</strong> Drag nodes to reposition them</li>\n            <li><strong>Enter Rack View:</strong> Double click on desktop or Long press on mobile</li>\n            <li><strong>Multi Select:</strong> Right click (desktop) or double tap (mobile)</li>\n            <li><strong>Pan Canvas:</strong> Drag empty space or hold Space + drag</li>\n            <li><strong>Zoom:</strong> Scroll wheel or pinch gesture</li>\n            <li><strong>Free Draw:</strong> Use draw toolbar for drawing lines, boxes/zones , and text</li>\n          </ul>\n          <h4 style=\"color: var(--accent); margin-bottom: 8px;\">Saving &amp; Encryption</h4>\n          <p style=\"margin-bottom: 8px;\">Browsers cannot overwrite local files. Click <strong>Save File</strong> to download an updated HTML with all changes. Replace your old file to keep edits.</p>\n          <p style=\"margin-bottom: 8px;\"><strong>Encryption of data:</strong> Check \"Encrypt\" before saving to password protect your data. Beware! No recovery possible without password!!</p>\n          <p><strong>Decryption of data:</strong> Export via JSON in top settings menu after successful password validation to decrypt your data. You can then reimport back into the same file and save!</p>\n        </div>\n        <div id=\"help-tab-desktop\" class=\"help-tab-content\" style=\"display: none;\">\n          <h4 style=\"color: var(--accent); margin-bottom: 12px;\">Navigation</h4>\n          <table style=\"width: 100%; border-collapse: collapse; margin-bottom: 16px; font-size: 14px;\">\n            <tbody><tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\">Arrow Keys</code></td>\n              <td style=\"padding: 8px;\">Move selected 1px</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\">Shift + Arrows</code></td>\n              <td style=\"padding: 8px;\">Move selected 10px</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\">Tab / Shift+Tab</code></td>\n              <td style=\"padding: 8px;\">Cycle through nodes</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\">F</code></td>\n              <td style=\"padding: 8px;\">Focus on selected</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\">Space + Drag</code></td>\n              <td style=\"padding: 8px;\">Pan canvas</td>\n            </tr>\n          </tbody></table>\n          <h4 style=\"color: var(--accent); margin-bottom: 12px;\">Management</h4>\n          <table style=\"width: 100%; border-collapse: collapse; margin-bottom: 16px; font-size: 14px;\">\n            <tbody><tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\">L</code></td>\n              <td style=\"padding: 8px;\">Lock/unlock selected</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\">G</code></td>\n              <td style=\"padding: 8px;\">Group/ungroup selected</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\">Ctrl+C / Ctrl+V</code></td>\n              <td style=\"padding: 8px;\">Copy / Paste</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\">Ctrl+D</code></td>\n              <td style=\"padding: 8px;\">Duplicate</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\">Ctrl+A</code></td>\n              <td style=\"padding: 8px;\">Select all</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\">Delete</code></td>\n              <td style=\"padding: 8px;\">Delete selected</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\">Escape</code></td>\n              <td style=\"padding: 8px;\">Clear selection</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\">Ctrl+Z / Ctrl+Y</code></td>\n              <td style=\"padding: 8px;\">Undo / Redo</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\">Right Click</code></td>\n              <td style=\"padding: 8px;\">Multi select toggle</td>\n            </tr>\n          </tbody></table>\n        </div>\n        <div id=\"help-tab-mobile\" class=\"help-tab-content\" style=\"display: none;\">\n          <h4 style=\"color: var(--accent); margin-bottom: 12px;\">Basic Gestures</h4>\n          <table style=\"width: 100%; border-collapse: collapse; margin-bottom: 16px; font-size: 14px;\">\n            <tbody><tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><strong>Tap node</strong></td>\n              <td style=\"padding: 8px;\">Select &amp; open properties</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><strong>Tap empty</strong></td>\n              <td style=\"padding: 8px;\">Deselect all</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><strong>Drag node</strong></td>\n              <td style=\"padding: 8px;\">Move node</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><strong>Drag empty</strong></td>\n              <td style=\"padding: 8px;\">Pan canvas</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><strong>Pinch</strong></td>\n              <td style=\"padding: 8px;\">Zoom in/out</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><strong>Double tap node</strong></td>\n              <td style=\"padding: 8px;\">Add/remove from selection</td>\n            </tr>\n          </tbody></table>\n          <h4 style=\"color: var(--accent); margin-bottom: 12px;\">Rack View</h4>\n          <table style=\"width: 100%; border-collapse: collapse; margin-bottom: 16px; font-size: 14px;\">\n            <tbody><tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><strong>Long press rack</strong></td>\n              <td style=\"padding: 8px;\">Enter rack view</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><strong>Double tap empty</strong></td>\n              <td style=\"padding: 8px;\">Exit rack view</td>\n            </tr>\n          </tbody></table>\n        </div>\n\t\t<div id=\"help-tab-formats\" class=\"help-tab-content\" style=\"display: none;\">\n          <h4 style=\"color: var(--accent); margin-bottom: 12px;\">Export Help</h4>\n          <table style=\"width: 100%; border-collapse: collapse; margin-bottom: 16px; font-size: 14px;\">\n            <tbody>\n              <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n                <td style=\"padding: 8px;\"><strong>HTML</strong></td>\n                <td style=\"padding: 8px;\">Complete self contained file and the default save method. When in doubt, choose this. Use the Save HTML button for this</td>\n              </tr>\n              <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n                <td style=\"padding: 8px;\"><strong>JSON</strong></td>\n                <td style=\"padding: 8px;\">Full backup. Editable in your favorite text editor</td>\n              </tr>\n              <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n                <td style=\"padding: 8px;\"><strong>Markdown</strong></td>\n                <td style=\"padding: 8px;\">Full backup. Editable in your favorite text editor</td>\n              </tr>\n              <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n                <td style=\"padding: 8px;\"><strong>CSV</strong></td>\n                <td style=\"padding: 8px;\">Full backup in header, nodes in spreadsheet rows</td>\n              </tr>\n              <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n                <td style=\"padding: 8px;\"><strong>PNG</strong></td>\n                <td style=\"padding: 8px;\">Standard image of current canvas tab</td>\n              </tr>\n              <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n                <td style=\"padding: 8px;\"><strong>SVG</strong></td>\n                <td style=\"padding: 8px;\">Vector image, scalable and editable image of current canvas tab</td>\n              </tr>\n            </tbody>\n          </table>\n          <h4 style=\"color: var(--accent); margin-bottom: 12px;\">Import Help</h4>\n          <table style=\"width: 100%; border-collapse: collapse; margin-bottom: 16px; font-size: 14px;\">\n            <tbody>\n              <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n                <td style=\"padding: 8px;\"><strong>JSON</strong></td>\n                <td style=\"padding: 8px;\">Replaces all data</td>\n              </tr>\n              <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n                <td style=\"padding: 8px;\"><strong>Markdown</strong></td>\n                <td style=\"padding: 8px;\">Replaces all data</td>\n              </tr>\n              <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n                <td style=\"padding: 8px;\"><strong>CSV</strong></td>\n                <td style=\"padding: 8px;\">Choose full restore or add nodes only. Allows quickly creating nodes with a spreadsheet and importing into an existing save.</td>\n              </tr>\n            </tbody>\n          </table>\n        </div>\n        <div class=\"modal-buttons\" style=\"margin-top: 16px;\">\n          <button class=\"btn-cancel\" id=\"save-info-close\">Close</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"settings-modal\">\n\t<div class=\"modal-content\">\n        <h2>Settings</h2>\n        <details class=\"style-section\">\n          <summary>View Only Mode</summary>\n          <div class=\"style-content\">\n            <div class=\"style-row\">\n              <label>View Only (disable all editing)</label>\n              <input type=\"checkbox\" id=\"view-only-mode\" style=\"width:auto;\">\n            </div>\n            <p style=\"margin-top:4px;font-size:12px;color:var(--text-soft);\">When enabled, all editing of the canvas is disabled. Pan and zoom still work.</p>\n          </div>\n        </details>\n        <details class=\"style-section\">\n          <summary>General Theme</summary>\n          <div class=\"style-content\">\n\t\t    <div class=\"style-row\">\n              <label>Theme Preset</label>\n              <div style=\"display:flex;gap:6px;flex:1;\">\n                <select id=\"theme-preset\" style=\"flex:1;padding:4px 8px;background:var(--input-bg);color:var(--input-text);border:1px solid var(--input-border);border-radius:4px;\">\n                  <option value=\"defaulted\">Default</option>\n                  <option value=\"\">Custom</option>\n                  <optgroup label=\"Corporate\">\n                    <option value=\"slate\">Slate</option>\n                    <option value=\"graphite\">Graphite</option>\n                    <option value=\"frost\">Frost (Light)</option>\n                  </optgroup>\n                  <optgroup label=\"Homelab\">\n                    <option value=\"synthwave\">Synthwave</option>\n                    <option value=\"terminal\">Terminal</option>\n                  </optgroup>\n                  <optgroup label=\"Dev\">\n                    <option value=\"dracula\">Dracula</option>\n                    <option value=\"cobalt\">Cobalt</option>\n                    <option value=\"solarized\">Solarized</option>\n                  </optgroup>\n                  <optgroup id=\"my-themes-group\" label=\"My Themes\"></optgroup>\n                </select>\n              </div>\n            </div>\n\t\t\t<div class=\"style-row\">\n\t\t\t <label>  \n\t\t\t  <button onclick=\"saveCurrentTheme()\" style=\"padding:4px 8px;background:var(--accent);color:var(--bg);border:none;border-radius:4px;cursor:pointer;font-size:12px;white-space:nowrap;\">Save Custom Theme</button>\n\t\t\t </label>\n\t\t\t  <button id=\"delete-theme-btn\" onclick=\"deleteCurrentTheme()\" style=\"padding: 4px 8px; background: var(--danger); color: rgb(255, 255, 255); border: none; border-radius: 4px; cursor: pointer; font-size: 12px; display: block;\" disabled=\"\">Delete Custom Theme</button>\n\t\t\t</div>\n            <div class=\"style-row\">\n              <label>Main Background</label>\n              <input type=\"color\" id=\"panel-color\" value=\"#0b0e13\">\n            </div>\n            <div class=\"style-row\">\n              <label>Sidebar / Mobile Footer</label>\n              <input type=\"color\" id=\"sidebar-bg-color\" value=\"#10141b\">\n            </div>\n            <div class=\"style-row\">\n              <label>Modal Window (popup) Background</label>\n              <input type=\"color\" id=\"panel-alt-color\" value=\"#10141b\">\n            </div>\n            <div class=\"style-row\">\n              <label>Accent Buttons</label>\n              <input type=\"color\" id=\"accent-color\" value=\"#4fd1c5\">\n            </div>\n            <div class=\"style-row\">\n              <label>Danger Buttons</label>\n              <input type=\"color\" id=\"danger-color\" value=\"#f56565\">\n            </div>\n            <div class=\"style-row\">\n              <label>Primary Text</label>\n              <input type=\"color\" id=\"text-main-color\" value=\"#e2e8f0\">\n            </div>\n            <div class=\"style-row\">\n              <label>Secondary Text</label>\n              <input type=\"color\" id=\"text-soft-color\" value=\"#94a3b8\">\n            </div>\n\t\t\t<div class=\"style-row\">\n              <label>Tag(s) Fill</label>\n              <input type=\"color\" id=\"tag-fill-color\" value=\"#1e293b\">\n            </div>\n            <div class=\"style-row\">\n              <label>Tag(s) Text</label>\n              <input type=\"color\" id=\"tag-text-color\" value=\"#e2e8f0\">\n            </div>\n            <div class=\"style-row\">\n              <label>Tag(s) Border</label>\n              <input type=\"color\" id=\"tag-border-color\" value=\"#475569\">\n            </div>\n          </div>\n        </details>\n        <details class=\"style-section\">\n          <summary>Top Bar</summary>\n          <div class=\"style-content\">\n            <div class=\"style-row\">\n              <label>Background</label>\n              <input type=\"color\" id=\"topbar-bg-color\" value=\"#0b0e13\">\n            </div>\n            <div class=\"style-row\">\n              <label>Border</label>\n              <input type=\"color\" id=\"topbar-border-color\" value=\"#1f2533\">\n            </div>\n          </div>\n\t\t  <div class=\"style-content\">\n            <div class=\"style-row\">\n              <label>Button Fill</label>\n              <input type=\"color\" id=\"btn-bg-color\" value=\"#0b0e13\">\n            </div>\n            <div class=\"style-row\">\n              <label>Button Text</label>\n              <input type=\"color\" id=\"btn-text-color\" value=\"#e2e8f0\">\n            </div>\n          </div>\n        </details>\n        <details class=\"style-section\">\n          <summary>Main Canvas</summary>\n          <div class=\"style-content\">\n            <div class=\"style-row\">\n              <label>Border</label>\n              <input type=\"color\" id=\"canvas-border-color\" value=\"#475569\">\n            </div>\n            <div class=\"style-row\">\n              <label>Grid Lines</label>\n              <input type=\"color\" id=\"canvas-grid-color\" value=\"#475569\">\n            </div>\n            <div class=\"style-row\">\n              <label>Grid Size</label>\n              <input type=\"number\" id=\"canvas-grid-size\" value=\"50\" min=\"20\" max=\"200\" style=\"width:60px;\">\n            </div>\n            <div class=\"style-row\">\n              <label>Show Grid</label>\n              <input type=\"checkbox\" id=\"canvas-grid-enabled\" checked=\"\">\n            </div>\n\t\t    <div class=\"style-row\">\n              <label>Background Top</label>\n              <input type=\"color\" id=\"canvas-gradient-top\" value=\"#1e2532\">\n            </div>\n            <div class=\"style-row\">\n              <label>Background Bottom</label>\n              <input type=\"color\" id=\"canvas-gradient-bottom\" value=\"#050608\">\n            </div>\n            <div class=\"style-row\">\n              <label>Solid Background</label>\n              <input type=\"color\" id=\"page-bg-color\" value=\"#050608\">\n            </div>\n            <p style=\"margin-top:4px;font-size:12px;color:var(--text-soft);\">Set solid background to override gradient.</p>\n          </div>\n        </details>\n        <details class=\"style-section\">\n          <summary>Rack Canvas</summary>\n          <div class=\"style-content\">\n            <div class=\"style-row\">\n              <label>Frame Fill</label>\n              <input type=\"color\" id=\"rack-frame-fill\" value=\"#0f172a\">\n            </div>\n            <div class=\"style-row\">\n              <label>Frame Border</label>\n              <input type=\"color\" id=\"rack-frame-stroke\" value=\"#4fd1c5\">\n            </div>\n            <div class=\"style-row\">\n              <label>Grid Lines</label>\n              <input type=\"color\" id=\"rack-line-color\" value=\"#475569\">\n            </div>\n            <div class=\"style-row\">\n              <label>Show Grid</label>\n              <input type=\"checkbox\" id=\"rack-grid-enabled\" checked=\"\">\n            </div>\n            <div class=\"style-row\">\n              <label>U Labels</label>\n              <input type=\"color\" id=\"rack-text-color\" value=\"#4fd1c5\">\n            </div>\n          </div>\n       </details>\n\t   <details class=\"style-section\">\n          <summary>Canvas Toolbars</summary>\n          <div class=\"style-content\">\n            <div class=\"style-row\">\n              <label>Background</label>\n              <input type=\"color\" id=\"toolbar-bg-color\" value=\"#0f172a\">\n            </div>\n            <div class=\"style-row\">\n              <label>Border</label>\n              <input type=\"color\" id=\"toolbar-border-color\" value=\"#1f2937\">\n            </div>\n            <div class=\"style-row\">\n              <label>Text</label>\n              <input type=\"color\" id=\"toolbar-text-color\" value=\"#94a3b8\">\n            </div>\n            <div class=\"style-row\">\n              <label>Button Fill</label>\n              <input type=\"color\" id=\"toolbar-btn-bg-color\" value=\"#0b0e13\">\n            </div>\n            <div class=\"style-row\">\n              <label>Button Text</label>\n              <input type=\"color\" id=\"toolbar-btn-text-color\" value=\"#e2e8f0\">\n            </div>\n            <div class=\"style-row\">\n              <label>Minimap Dots</label>\n              <input type=\"color\" id=\"minimap-dots-color\" value=\"#94a3b8\">\n            </div>\n          </div>\n        </details>\n        <details class=\"style-section\">\n          <summary>Nodes</summary>\n          <div class=\"style-content\">\n            <div class=\"style-row\">\n              <label>Fill</label>\n              <input type=\"color\" id=\"node-fill-color\" value=\"#1e293b\">\n            </div>\n            <div class=\"style-row\">\n              <label>Border</label>\n              <input type=\"color\" id=\"node-stroke-color\" value=\"#475569\">\n            </div>\n            <div class=\"style-row\">\n              <label>Title Color</label>\n              <input type=\"color\" id=\"node-title-color\" value=\"#e2e8f0\">\n            </div>\n            <div class=\"style-row\">\n              <label>Title Size</label>\n              <input type=\"number\" id=\"node-title-size\" value=\"18\" min=\"8\" max=\"48\" style=\"width:60px;\">\n            </div>\n            <div class=\"style-row\">\n              <label>Subtitle Color</label>\n              <input type=\"color\" id=\"node-sub-color\" value=\"#94a3b8\">\n            </div>\n            <div class=\"style-row\">\n              <label>Subtitle Size</label>\n              <input type=\"number\" id=\"node-sub-size\" value=\"13\" min=\"8\" max=\"48\" style=\"width:60px;\">\n            </div>\n            <div class=\"style-row\">\n              <label>Font</label>\n              <select id=\"node-font-family\" style=\"flex:1;padding:4px;background:var(--panel);color:var(--text-main);border:1px solid var(--topbar-border);border-radius:4px;\">\n                <option value=\"Inter, system-ui, sans-serif\">Inter</option>\n                <option value=\"Arial, sans-serif\">Arial</option>\n                <option value=\"Helvetica, sans-serif\">Helvetica</option>\n                <option value=\"Georgia, serif\">Georgia</option>\n                <option value=\"monospace\">Monospace</option>\n              </select>\n            </div>\n          </div>\n        </details>\n        <details class=\"style-section\">\n          <summary>Connections</summary>\n          <div class=\"style-content\">\n            <div class=\"style-row\">\n              <label>Default Color</label>\n              <input type=\"color\" id=\"default-edge-color\" value=\"#475569\">\n            </div>\n            <div class=\"style-row\">\n              <label>Default Routing</label>\n              <select id=\"default-edge-routing\">\n                <option value=\"orthogonal\">Orthogonal (90°)</option>\n                <option value=\"curved\">Curved</option>\n                <option value=\"straight\">Straight</option>\n              </select>\n            </div>\n            <div class=\"style-row\">\n              <label>Animation Style</label>\n              <select id=\"animation-style-select\">\n                <option value=\"arrows\">Flowing Arrows</option>\n                <option value=\"dots\">Dot Arrows</option>\n              </select>\n            </div>\n            <div class=\"style-row\">\n              <label>Animate Directions</label>\n              <select id=\"animation-direction-select\">\n                <option value=\"all\">All Directions</option>\n                <option value=\"forward\">Forward Only</option>\n                <option value=\"backward\">Backward Only</option>\n                <option value=\"both\">Bidirectional Only</option>\n              </select>\n            </div>\n            <div class=\"style-row\">\n              <label>Animation Speed</label>\n              <select id=\"animation-speed-select\">\n                <option value=\"0.5\">Very Fast</option>\n                <option value=\"1\">Fast</option>\n                <option value=\"1.5\">Normal</option>\n                <option value=\"2.5\">Slow</option>\n                <option value=\"4\">Very Slow</option>\n              </select>\n            </div>\n            <div class=\"style-row\" style=\"grid-column: 1 / -1;\">\n              <button id=\"apply-routing-all\" style=\"width:100%;padding:8px;background:var(--accent);color:var(--bg);border:none;border-radius:6px;cursor:pointer;font-weight:600;\">Apply Routing to All Connections</button>\n            </div>\n          </div>\n        </details>\n\t\t<details class=\"style-section\">\n          <summary>Animations &amp; Zones</summary>\n          <div class=\"style-content\">\n            <div class=\"style-row\"><label>All Animations</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"anim-master\" checked=\"\"><span class=\"toggle-slider\"></span></label></div>\n            <div style=\"grid-column:1/-1;font-size:11px;color:var(--text-soft);text-transform:uppercase;border-bottom:1px solid var(--edge-main);padding-bottom:4px;margin-top:8px;\">By Type</div>\n            <div class=\"style-row\"><label>Sweep (Pan)</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"anim-type-sweep\" checked=\"\"><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label>Pulse (Breathe)</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"anim-type-pulse\" checked=\"\"><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label>Rings (Emanate)</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"anim-type-rings\" checked=\"\"><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label>Spin (Rotate)</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"anim-type-spin\" checked=\"\"><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label>Connections (Flow)</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"anim-type-connections\" checked=\"\"><span class=\"toggle-slider\"></span></label></div>\n            <div style=\"grid-column:1/-1;font-size:11px;color:var(--text-soft);text-transform:uppercase;border-bottom:1px solid var(--edge-main);padding-bottom:4px;margin-top:8px;\">By Category</div>\n            <div class=\"style-row\"><label>Cameras</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"anim-cat-camera\" checked=\"\"><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label>Doorbells</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"anim-cat-doorbell\" checked=\"\"><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label>Motion Sensors</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"anim-cat-motion\" checked=\"\"><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label>Smoke Detectors</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"anim-cat-smoke\" checked=\"\"><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label>WiFi/AP/Router</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"anim-cat-wifi\" checked=\"\"><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label>Sensors/IoT</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"anim-cat-sensor\" checked=\"\"><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label>Sprinklers</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"anim-cat-sprinkler\" checked=\"\"><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label>Connections</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"anim-cat-connections\" checked=\"\"><span class=\"toggle-slider\"></span></label></div>\n            <div style=\"grid-column:1/-1;font-size:12px;color:var(--accent);text-transform:uppercase;letter-spacing:0.05em;font-weight:600;margin-top:16px;padding-top:12px;border-top:1px solid var(--edge-main);\">Zone (Animation Cone) Setings</div>\n            <div class=\"style-row\"><label>All Zones</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"zone-master\" checked=\"\"><span class=\"toggle-slider\"></span></label></div>\n            <div style=\"grid-column:1/-1;font-size:11px;color:var(--text-soft);text-transform:uppercase;border-bottom:1px solid var(--edge-main);padding-bottom:4px;margin-top:8px;\">By Category</div>\n            <div class=\"style-row\"><label>Cameras</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"zone-cat-camera\" checked=\"\"><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label>Doorbells</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"zone-cat-doorbell\" checked=\"\"><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label>Motion Sensors</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"zone-cat-motion\" checked=\"\"><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label>Smoke Detectors</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"zone-cat-smoke\" checked=\"\"><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label>WiFi/AP/Router</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"zone-cat-wifi\" checked=\"\"><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label>Sensors/IoT</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"zone-cat-sensor\" checked=\"\"><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label>Sprinklers</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"zone-cat-sprinkler\" checked=\"\"><span class=\"toggle-slider\"></span></label></div>\n          </div>\n        </details>\n        <details class=\"style-section\">\n          <summary>Groups &amp; Editing</summary>\n          <div class=\"style-content\">\n            <div class=\"style-row\">\n              <label>Resize handle Color</label>\n              <input type=\"color\" id=\"selection-handle-color\" value=\"#f59e0b\">\n            </div>\n            <div class=\"style-row\">\n              <label>Resize Handle Size</label>\n              <input type=\"number\" id=\"selection-handle-size\" value=\"8\" min=\"4\" max=\"16\" style=\"width:60px;\">\n            </div>\n            <div class=\"style-row\">\n              <label>Grouped Icon Outline</label>\n              <input type=\"color\" id=\"group-indicator-color\" value=\"#4fd1c5\">\n            </div>\n          </div>\n\t\t  <div class=\"style-content\">\n            <div class=\"style-row\">\n              <label>Multiselect Fill Color (Desktop)</label>\n              <input type=\"color\" id=\"selection-fill-color\" value=\"#4fd1c5\">\n            </div>\n           <div class=\"style-row\">\n              <label>Multiselect Fill Opacity (Desktop)</label>\n              <div style=\"display:flex;align-items:center;gap:8px;\">\n                <input type=\"range\" id=\"selection-fill-opacity\" min=\"0\" max=\"1\" step=\"0.05\" value=\"0.1\">\n                <span id=\"selection-fill-opacity-val\" style=\"min-width:35px;text-align:right;\">10%</span>\n              </div>\n            </div>\n            <div class=\"style-row\">\n              <label>Multiselect Border Color (Desktop)</label>\n              <input type=\"color\" id=\"selection-stroke-color\" value=\"#4fd1c5\">\n            </div>\n            <div class=\"style-row\">\n              <label>Multiselect Border Width (Desktop)</label>\n              <input type=\"number\" id=\"selection-stroke-width\" min=\"1\" max=\"10\" value=\"2\">\n            </div>\n            <div class=\"style-row\">\n              <label>Multiselect Border Style (Desktop)</label>\n              <select id=\"selection-stroke-style\">\n                <option value=\"5,5\">Dashed</option>\n                <option value=\"2,4\">Dotted</option>\n                <option value=\"none\">Solid</option>\n              </select>\n            </div>\n          </div>\n        </details>\n\t\t<details class=\"style-section\">\n          <summary>Inputs &amp; Dropdowns</summary>\n          <div class=\"style-content\">\n            <div class=\"style-row\">\n              <label>Background</label>\n              <input type=\"color\" id=\"input-bg-color\" value=\"#0b0e13\">\n            </div>\n            <div class=\"style-row\">\n              <label>Text</label>\n              <input type=\"color\" id=\"input-text-color\" value=\"#e2e8f0\">\n            </div>\n            <div class=\"style-row\">\n              <label>Border</label>\n              <input type=\"color\" id=\"input-border-color\" value=\"#1f2937\">\n            </div>\n            <div class=\"style-row\">\n              <label>Font</label>\n              <select id=\"input-font-family\" style=\"flex:1;padding:4px;background:var(--panel);color:var(--text-main);border:1px solid var(--topbar-border);border-radius:4px;\">\n                <option value=\"Inter, system-ui, sans-serif\">Inter</option>\n                <option value=\"Arial, sans-serif\">Arial</option>\n                <option value=\"Helvetica, sans-serif\">Helvetica</option>\n                <option value=\"Georgia, serif\">Georgia</option>\n                <option value=\"monospace\">Monospace</option>\n              </select>\n            </div>\n            <div class=\"style-row\">\n              <label>Font Size</label>\n              <input type=\"number\" id=\"input-font-size\" value=\"14\" min=\"10\" max=\"24\" style=\"width:60px;\">\n            </div>\n          </div>\n        </details>\n        <details class=\"style-section\">\n          <summary>Welcome Message</summary>\n          <div class=\"style-content\">\n            <div class=\"style-row\">\n              <label>Enabled</label>\n              <input type=\"checkbox\" id=\"canvas-hint-enabled\" checked=\"\" style=\"width:auto;\">\n            </div>\n            <div class=\"style-row\">\n              <label>Background</label>\n              <input type=\"color\" id=\"canvas-hint-bg-color\" value=\"#0f172a\">\n            </div>\n            <div class=\"style-row\">\n              <label>Text Color</label>\n              <input type=\"color\" id=\"canvas-hint-text-color\" value=\"#94a3b8\">\n            </div>\n            <div class=\"style-row\" style=\"flex-direction:column;align-items:stretch;\">\n              <label>Custom Text (HTML)</label>\n              <textarea id=\"canvas-hint-text\" rows=\"4\" style=\"width:100%;margin-top:4px;padding:6px;background:var(--panel);color:var(--text-main);border:1px solid var(--topbar-border);border-radius:4px;font-size:12px;resize:vertical;\" placeholder=\"Leave empty for default\"></textarea>\n            </div>\n          </div>\n        </details>\n         <details class=\"style-section\">\n          <summary>Auto Status Checking</summary>\n          <div class=\"style-content\">\n            <label style=\"display: flex; align-items: center; gap: 8px; margin-bottom: 16px; cursor: pointer;\">\n            <input type=\"checkbox\" id=\"auto-ping-enabled\" style=\"cursor: pointer;\">\n            <span style=\"font-size: 14px; font-weight: 600;\">Enable automatic status checking</span>\n            </label>\n            <div id=\"auto-ping-settings\" style=\"display: none; padding-left: 20px; border-left: 2px solid var(--edge-main);\">\n              <div class=\"style-row\" style=\"margin-bottom: 12px;\">\n                <label>Check Interval (seconds):</label>\n                <input type=\"number\" id=\"auto-ping-interval\" min=\"5\" max=\"3600\" value=\"30\" style=\"width: 80px; padding: 6px; background: var(--panel); border: 1px solid var(--edge-main); border-radius: 4px; color: var(--text-main);\">\n              </div>\n              <div style=\"padding: 10px; background: var(--panel); border-radius: 6px; border: 1px solid var(--edge-main); font-size: 12px; color: var(--text-soft);\">\n                <div style=\"margin-bottom: 4px;\"><span id=\"auto-ping-next-check\">Next check in: --</span></div>\n                <div><span id=\"auto-ping-last-run\">Last run: 1:15:07 PM</span></div>\n              </div>\n            </div>\n            <p style=\"margin-top: 12px; font-size: 12px; color: var(--text-soft); font-style: italic;\">\n              Automatically checks all ping enabled nodes at the specified interval. I recommend 30 to 60 seconds for local networks.\n            </p>\n\t\t\t</div>\n        </details>\n        <details class=\"style-section\" open=\"\">\n          <summary>Danger Zone</summary>\n          <div class=\"style-content\">\n            <p style=\"margin-bottom:12px;font-size:13px;color:var(--text-soft);\">Permanently delete everything on the canvas.</p>\n            <button id=\"clear-all-btn\" title=\"Clear all nodes\" style=\"padding:6px 12px;background:var(--danger);color:#fff;border:none;border-radius:6px;cursor:pointer;font-size:14px;font-weight:600;\">Clear All</button>\n            <input type=\"file\" id=\"import-data-file\" accept=\".json\" style=\"display:none\">\n          </div>\n        </details>\n        <div class=\"modal-buttons\">\n          <button class=\"btn-cancel\" id=\"settings-close\">Close</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"add-node-modal\">\n      <div class=\"modal-content\">\n        <h3>Add New Node</h3>\n        <div style=\"background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; padding: 15px; margin-bottom: 15px;\">\n          <div style=\"font-weight: 600; color: var(--text-main); margin-bottom: 12px; font-size: 14px;\">Basic Information</div>\n          <label style=\"display: block;margin-bottom: 4px;color: var(--text-soft);font-size: 13px;\">Name</label>\n          <input type=\"text\" id=\"new-node-name\" placeholder=\"e.g. web server, jellyfin\">\n          <label style=\"display: block;margin-bottom: 4px;margin-top: 12px;color: var(--text-soft);font-size: 13px;\">IP / Subtitle</label>\n          <input type=\"text\" id=\"new-node-ip\" placeholder=\"e.g. 192.168.1.100\">\n        </div>\n        <div style=\"background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; padding: 15px; margin-bottom: 15px;\">\n          <div style=\"font-weight: 600; color: var(--text-main); margin-bottom: 12px; font-size: 14px;\">Tags</div>\n          <label style=\"display: block;margin-bottom: 4px;color: var(--text-soft);font-size: 13px;\">Text Tags (comma separated)</label>\n          <input type=\"text\" id=\"new-node-tags\" placeholder=\"e.g. Docker, nginx, WG: vpn\">\n          <button class=\"pick-icon-btn\" id=\"pick-new-node-tag-icon-btn\" style=\"margin-top: 10px;\">Add Icon Tag</button>\n          <div id=\"new-node-icon-tags\" style=\"margin-top: 10px; display: none;\">\n            <label style=\"display: block; margin-bottom: 8px; color: var(--text-soft); font-size: 13px;\">Icon Tags:</label>\n            <div id=\"new-node-icon-tags-list\" style=\"display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 10px;\"></div>\n          </div>\n        </div>\n        <div style=\"background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; padding: 15px; margin-bottom: 15px;\">\n          <div style=\"font-weight: 600; color: var(--text-main); margin-bottom: 12px; font-size: 14px;\">Node Appearance</div>\n          <label style=\"display: block;margin-bottom: 4px;color: var(--text-soft);font-size: 13px;\">Basic Shapes</label>\n          <select id=\"new-node-shape\">\n            <optgroup label=\"Basic Shapes\">\n            <option value=\"circle\">Circle</option>\n            <option value=\"square\">Square</option>\n            <option value=\"rectangle\">Rectangle</option>\n            <option value=\"triangle\">Triangle</option>\n            <option value=\"hexagon\">Hexagon</option>\n            <option value=\"diamond\">Diamond</option>\n            <option value=\"star\">Star</option>\n            <option value=\"stop-sign\">Stop Sign</option>\n            <option value=\"octagon\">Octagon</option>\n            <option value=\"pentagon\">Pentagon</option>\n            <option value=\"cross\">Cross</option>\n            <option value=\"rounded-square\">Rounded Square</option>\n            <option value=\"pill\">Pill</option>\n            <option value=\"parallelogram\">Parallelogram</option>\n            <option value=\"trapezoid\">Trapezoid</option>\n          </optgroup>\n          <optgroup label=\"Computers &amp; Devices\">\n            <option value=\"server\">Server</option>\n            <option value=\"pc\">PC / Desktop</option>\n            <option value=\"laptop\">Laptop</option>\n            <option value=\"phone\">Phone / Mobile</option>\n            <option value=\"printer\">Printer</option>\n            <option value=\"pi\">Raspberry Pi</option>\n            <option value=\"sensor\">Sensor / IoT</option>\n          </optgroup>\n          <optgroup label=\"Network Equipment\">\n            <option value=\"router\">Router</option>\n            <option value=\"switch\">Switch</option>\n            <option value=\"firewall\">Firewall</option>\n            <option value=\"access-point\">Access Point</option>\n            <option value=\"load-balancer\">Load Balancer</option>\n            <option value=\"gateway\">Gateway</option>\n            <option value=\"vpn\">VPN / Tunnel</option>\n            <option value=\"nas\">NAS / Storage</option>\n          </optgroup>\n          <optgroup label=\"Cloud &amp; Services\">\n            <option value=\"cloud\">Cloud</option>\n            <option value=\"database\">Database</option>\n\t\t\t<option value=\"docker\">Docker</option>\n            <option value=\"container\">Container</option>\n            <option value=\"vm\">Virtual Machine</option>\n            <option value=\"kubernetes\">Kubernetes</option>\n            <option value=\"api\">API / Endpoint</option>\n            <option value=\"queue\">Queue / Message</option>\n            <option value=\"lambda\">Lambda / Function</option>\n            <option value=\"bucket\">Bucket / S3</option>\n          </optgroup>\n          <optgroup label=\"Security &amp; Monitoring\">\n            <option value=\"shield\">Shield</option>\n            <option value=\"camera\">Camera / CCTV</option>\n            <option value=\"monitor\">Monitor / Dashboard</option>\n          </optgroup>\n          <optgroup label=\"Smart Home\">\n            <option value=\"thermostat\">Thermostat</option>\n            <option value=\"doorbell\">Video Doorbell</option>\n            <option value=\"smart-lock\">Smart Lock</option>\n            <option value=\"smart-bulb\">Smart Bulb</option>\n            <option value=\"smart-plug\">Smart Plug</option>\n            <option value=\"smart-speaker\">Smart Speaker</option>\n            <option value=\"smart-tv\">Smart TV</option>\n            <option value=\"hub\">Smart Hub</option>\n            <option value=\"smoke-detector\">Smoke Detector</option>\n            <option value=\"motion-sensor\">Motion Sensor</option>\n            <option value=\"garage\">Garage Door</option>\n            <option value=\"sprinkler\">Sprinkler</option>\n            <option value=\"vacuum\">Robot Vacuum</option>\n          </optgroup>\n          </select>\n          <button class=\"pick-icon-btn\" id=\"pick-node-icon-btn\" style=\"margin-top: 10px;\"> Or search web icons</button>\n          <div id=\"selected-node-icon\" style=\"margin-top: 10px; display: none;\">\n            <label style=\"display: block; margin-bottom: 8px; color: var(--text-soft); font-size: 13px;\">Selected Icon:</label>\n            <div class=\"icon-badge\" id=\"selected-node-icon-preview\">\n              <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" width=\"24\" height=\"24\">\n                <image href=\"https://cdn.jsdelivr.net/gh/selfhst/icons@master/png/docker.png\" width=\"24\" height=\"24\"></image>\n              </svg>\n              <span>docker</span>\n            </div>\n          </div>\n        </div>\n        <div style=\"background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; padding: 15px; margin-bottom: 15px;\">\n          <div style=\"font-weight: 600; color: var(--text-main); margin-bottom: 12px; font-size: 14px;\">Ping / Status Monitoring</div>\n          <label style=\"display: flex; align-items: center; gap: 8px; margin-bottom: 12px; cursor: pointer;\">\n          <input type=\"checkbox\" id=\"new-node-pingable\" style=\"cursor: pointer;\">\n          <span style=\"color: var(--text-soft); font-size: 13px;\">Enable ping/status check for this node</span>\n          </label>\n          <div id=\"new-node-ping-options\" style=\"display: block; padding-left: 24px; border-left: 2px solid var(--edge-main);\">\n            <label style=\"display: block; margin-bottom: 4px; color: var(--text-soft); font-size: 13px;\">Protocol</label>\n            <select id=\"new-node-ping-protocol\" style=\"margin-bottom: 12px;\">\n              <option value=\"http\">HTTP (port 80) uses IP field</option>\n              <option value=\"https\">HTTPS (port 443) uses IP field</option>\n              <option value=\"custom\">Custom URL</option>\n            </select>\n            <div id=\"new-node-custom-url-container\" style=\"display: block;\">\n              <label style=\"display: block; margin-bottom: 4px; color: var(--text-soft); font-size: 13px;\">Custom URL</label>\n              <input type=\"text\" id=\"new-node-custom-url\" placeholder=\"e.g. http://192.168.1.1:8080\">\n            </div>\n            <label style=\"display: block; margin-bottom: 4px; margin-top: 8px; color: var(--text-soft); font-size: 13px;\">\n            Timeout (ms)\n            </label>\n            <input type=\"number\" id=\"new-node-ping-timeout\" value=\"3000\" min=\"1000\" max=\"10000\" step=\"500\">\n          </div>\n        </div>\n        <div class=\"modal-buttons\">\n          <button class=\"btn-cancel\" id=\"add-node-cancel\">Cancel</button>\n          <button class=\"btn-save\" id=\"add-node-save\">Add Node</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"add-rack-modal\">\n      <div class=\"modal-content\">\n        <h3>Add New Rack</h3>\n        <div style=\"background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; padding: 15px; margin-bottom: 15px;\">\n          <div style=\"font-weight: 600; color: var(--text-main); margin-bottom: 12px; font-size: 14px;\">Basic Information</div>\n          <label style=\"display: block;margin-bottom: 4px;color: var(--text-soft);font-size: 13px;\">Rack Name</label>\n          <input type=\"text\" id=\"new-rack-name\" placeholder=\"e.g. Rack-A, DC1-R01, Production-01\">\n          <label style=\"display: block;margin-bottom: 4px;margin-top: 12px;color: var(--text-soft);font-size: 13px;\">IP / Network Range (optional)</label>\n          <input type=\"text\" id=\"new-rack-ip\" placeholder=\"e.g. 192.168.1.0/24\">\n        </div>\n        <div style=\"background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; padding: 15px; margin-bottom: 15px;\">\n          <div style=\"font-weight: 600; color: var(--text-main); margin-bottom: 12px; font-size: 14px;\">Tags</div>\n          <label style=\"display: block;margin-bottom: 4px;color: var(--text-soft);font-size: 13px;\">Text Tags (comma separated)</label>\n          <input type=\"text\" id=\"new-rack-tags\" placeholder=\"e.g. Production, Data Center 1, Row A\">\n        </div>\n        <div style=\"background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; padding: 15px; margin-bottom: 15px;\">\n          <div style=\"font-weight: 600; color: var(--text-main); margin-bottom: 12px; font-size: 14px;\">Rack Appearance</div>\n          <label style=\"display: block;margin-bottom: 4px;color: var(--text-soft);font-size: 13px;\">Icon / Shape</label>\n          <select id=\"new-rack-shape\">\n            <optgroup label=\"Basic Shapes\">\n            <option value=\"circle\">Circle</option>\n            <option value=\"square\">Square</option>\n            <option value=\"rectangle\">Rectangle</option>\n            <option value=\"triangle\">Triangle</option>\n            <option value=\"hexagon\">Hexagon</option>\n            <option value=\"diamond\">Diamond</option>\n            <option value=\"star\">Star</option>\n            <option value=\"stop-sign\">Stop Sign</option>\n            <option value=\"octagon\">Octagon</option>\n            <option value=\"pentagon\">Pentagon</option>\n            <option value=\"cross\">Cross</option>\n            <option value=\"rounded-square\">Rounded Square</option>\n            <option value=\"pill\">Pill</option>\n            <option value=\"parallelogram\">Parallelogram</option>\n            <option value=\"trapezoid\">Trapezoid</option>\n          </optgroup>\n          <optgroup label=\"Computers &amp; Devices\">\n            <option value=\"server\">Server</option>\n            <option value=\"pc\">PC / Desktop</option>\n            <option value=\"laptop\">Laptop</option>\n            <option value=\"phone\">Phone / Mobile</option>\n            <option value=\"printer\">Printer</option>\n            <option value=\"pi\">Raspberry Pi</option>\n            <option value=\"sensor\">Sensor / IoT</option>\n          </optgroup>\n          <optgroup label=\"Network Equipment\">\n            <option value=\"router\">Router</option>\n            <option value=\"switch\">Switch</option>\n            <option value=\"firewall\">Firewall</option>\n            <option value=\"access-point\">Access Point</option>\n            <option value=\"load-balancer\">Load Balancer</option>\n            <option value=\"gateway\">Gateway</option>\n            <option value=\"vpn\">VPN / Tunnel</option>\n            <option value=\"nas\">NAS / Storage</option>\n          </optgroup>\n          <optgroup label=\"Cloud &amp; Services\">\n            <option value=\"cloud\">Cloud</option>\n            <option value=\"database\">Database</option>\n\t\t\t<option value=\"docker\">Docker</option>\n            <option value=\"container\">Container</option>\n            <option value=\"vm\">Virtual Machine</option>\n            <option value=\"kubernetes\">Kubernetes</option>\n            <option value=\"api\">API / Endpoint</option>\n            <option value=\"queue\">Queue / Message</option>\n            <option value=\"lambda\">Lambda / Function</option>\n            <option value=\"bucket\">Bucket / S3</option>\n          </optgroup>\n          <optgroup label=\"Security &amp; Monitoring\">\n            <option value=\"shield\">Shield</option>\n            <option value=\"camera\">Camera / CCTV</option>\n            <option value=\"monitor\">Monitor / Dashboard</option>\n          </optgroup>\n          <optgroup label=\"Smart Home\">\n            <option value=\"thermostat\">Thermostat</option>\n            <option value=\"doorbell\">Video Doorbell</option>\n            <option value=\"smart-lock\">Smart Lock</option>\n            <option value=\"smart-bulb\">Smart Bulb</option>\n            <option value=\"smart-plug\">Smart Plug</option>\n            <option value=\"smart-speaker\">Smart Speaker</option>\n            <option value=\"smart-tv\">Smart TV</option>\n            <option value=\"hub\">Smart Hub</option>\n            <option value=\"smoke-detector\">Smoke Detector</option>\n            <option value=\"motion-sensor\">Motion Sensor</option>\n            <option value=\"garage\">Garage Door</option>\n            <option value=\"sprinkler\">Sprinkler</option>\n            <option value=\"vacuum\">Robot Vacuum</option>\n          </optgroup>\n          </select>\n          <button class=\"pick-icon-btn\" id=\"pick-rack-icon-btn\" style=\"margin-top: 10px;\">Or Search Web Icons</button>\n          <div id=\"selected-rack-icon\" style=\"margin-top: 10px; display: none;\">\n            <label style=\"display: block; margin-bottom: 8px; color: var(--text-soft); font-size: 13px;\">Selected Icon:</label>\n            <div class=\"icon-badge\" id=\"selected-rack-icon-preview\">\n              <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" width=\"24\" height=\"24\">\n                <image href=\"\" width=\"24\" height=\"24\"></image>\n              </svg>\n              <span></span>\n            </div>\n          </div>\n        </div>\n        <div style=\"background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; padding: 15px; margin-bottom: 15px;\">\n          <div style=\"font-weight: 600; color: var(--text-main); margin-bottom: 12px; font-size: 14px;\">Rack Configuration</div>\n          <label style=\"display: block;margin-bottom: 4px;color: var(--text-soft);font-size: 13px;\">Rack Capacity</label>\n          <select id=\"new-rack-capacity\">\n            <option value=\"42\" selected=\"\">42U (Standard Full Rack)</option>\n            <option value=\"48\">48U (Large Rack)</option>\n            <option value=\"24\">24U (Half Rack)</option>\n            <option value=\"12\">12U (Small/Wall Mount)</option>\n            <option value=\"6\">6U (Mini Rack)</option>\n          </select>\n        </div>\n        <div class=\"modal-buttons\">\n          <button class=\"btn-cancel\" id=\"add-rack-cancel\">Cancel</button>\n          <button class=\"btn-save\" id=\"add-rack-save\">Add Rack</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal confirm-modal\" id=\"clear-all-modal\">\n      <div class=\"modal-content\">\n        <h3>Clear All Nodes</h3>\n        <p> This will remove ALL nodes and connections. This cannot be undone until you reload without saving. Are you sure? </p>\n        <div class=\"modal-buttons\">\n          <button class=\"btn-cancel\" id=\"clear-all-cancel\">Cancel</button>\n          <button class=\"btn-delete\" id=\"clear-all-confirm\">Clear Everything</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"layer-modal\">\n      <div class=\"modal-content\" style=\"max-width: 400px;\">\n        <h3>Layer Visibility</h3>\n        <p style=\"font-size: 13px; color: var(--text-soft); margin-bottom: 15px;\">Toggle which layers are visible on the canvas</p>\n        <div style=\"display: flex; flex-direction: column; gap: 10px;\">\n          <label style=\"display: flex; align-items: center; gap: 8px; cursor: pointer; padding: 8px; background: var(--panel-alt); border-radius: 6px;\">\n          <input type=\"checkbox\" id=\"layer-physical\" checked=\"\" style=\"cursor: pointer; width: 18px; height: 18px;\">\n          <span style=\"flex: 1; font-weight: 500;\">Physical Layer</span>\n          </label>\n          <label style=\"display: flex; align-items: center; gap: 8px; cursor: pointer; padding: 8px; background: var(--panel-alt); border-radius: 6px;\">\n          <input type=\"checkbox\" id=\"layer-logical\" checked=\"\" style=\"cursor: pointer; width: 18px; height: 18px;\">\n          <span style=\"flex: 1; font-weight: 500;\">Logical Layer</span>\n          </label>\n          <label style=\"display: flex; align-items: center; gap: 8px; cursor: pointer; padding: 8px; background: var(--panel-alt); border-radius: 6px;\">\n          <input type=\"checkbox\" id=\"layer-security\" checked=\"\" style=\"cursor: pointer; width: 18px; height: 18px;\">\n          <span style=\"flex: 1; font-weight: 500;\">Security Layer</span>\n          </label>\n          <label style=\"display: flex; align-items: center; gap: 8px; cursor: pointer; padding: 8px; background: var(--panel-alt); border-radius: 6px;\">\n          <input type=\"checkbox\" id=\"layer-application\" checked=\"\" style=\"cursor: pointer; width: 18px; height: 18px;\">\n          <span style=\"flex: 1; font-weight: 500;\">Application Layer</span>\n          </label>\n        </div>\n        <div style=\"margin-top: 15px; display: flex; gap: 8px;\">\n          <button class=\"btn-cancel\" style=\"flex: 1;\" onclick=\"document.querySelectorAll('#layer-modal input[type=checkbox]').forEach(cb =&gt; cb.checked = false); applyLayerFilter();\">Hide All</button>\n          <button class=\"btn-save\" style=\"flex: 1;\" onclick=\"document.querySelectorAll('#layer-modal input[type=checkbox]').forEach(cb =&gt; cb.checked = true); applyLayerFilter();\">Show All</button>\n        </div>\n        <div class=\"modal-buttons\" style=\"margin-top: 10px;\">\n          <button class=\"btn-cancel\" id=\"layer-modal-close\">Close</button>\n        </div>\n      </div>\n    </div>\n    <div id=\"context-menu\" style=\"display: none !important;\">\n    </div>\n    <header>\n      <div class=\"title-block\">\n        <h1 id=\"page-title\" class=\"editable-page-title\">The One File</h1>\n        <div class=\"save-row\">\n          <button id=\"save-file-btn\" class=\"save-btn\" type=\"button\">Save HTML</button>\n          <label style=\"display: flex;align-items: center;gap: 4px;font-size: 12px;color: var(--text-soft);cursor: pointer;user-select: none;\">\n            <input type=\"checkbox\" id=\"encrypt-toggle\" style=\"cursor: pointer\">\n            <span title=\"Encrypt data with password\">🔒</span>\n          </label>\n          <div class=\"dropdown\" id=\"export-dropdown\">\n            <button class=\"dropdown-btn\" type=\"button\">Export ▾</button>\n            <div class=\"dropdown-menu\">\n              <button type=\"button\" onclick=\"screenshotCanvas()\">PNG Image</button>\n              <button type=\"button\" onclick=\"exportCanvasSVG()\">SVG Vector</button>\n              <div class=\"dropdown-divider\"></div>\n              <button type=\"button\" onclick=\"exportJSONFile()\">JSON</button>\n              <button type=\"button\" onclick=\"exportMarkdown()\">Markdown</button>\n              <button type=\"button\" onclick=\"exportCSV()\">CSV</button>\n              <div class=\"dropdown-divider\"></div>\n              <button type=\"button\" onclick=\"printTopology()\">Print...</button>\n\t\t\t  <div class=\"dropdown-divider\"></div>\n\t\t\t  <button type=\"button\" onclick=\"showFormatHelp()\">Export Help</button>\n            </div>\n          </div>\n          <div class=\"dropdown\" id=\"import-dropdown\">\n            <button class=\"dropdown-btn\" type=\"button\">Import ▾</button>\n            <div class=\"dropdown-menu\">\n              <button type=\"button\" onclick=\"document.getElementById('import-data-file').click()\">JSON</button>\n              <button type=\"button\" onclick=\"document.getElementById('import-markdown-file').click()\">Markdown</button>\n              <button type=\"button\" onclick=\"document.getElementById('import-csv-file').click()\">CSV</button>\n              <div class=\"dropdown-divider\"></div>\n              <button type=\"button\" onclick=\"showFormatHelp()\">Import Help</button>\n            </div>\n          </div>\n          <input type=\"file\" id=\"import-markdown-file\" accept=\".md,.markdown,.txt\" style=\"display:none\">\n          <input type=\"file\" id=\"import-csv-file\" accept=\".csv,.txt\" style=\"display:none\">\n          <span id=\"save-help-btn\" class=\"help-icon\" title=\"Why save?\">?</span>\n        </div>\n      </div>\n      <div id=\"topbar-menu\">\n        <button id=\"back-to-topology-btn\" title=\"Exit rack view and return to topology\" style=\"padding: 6px 12px;background: var(--accent);color: var(--bg);border: none;border-radius: 6px;cursor: pointer;font-size: 14px;font-weight: 600;display: none;\">← Back to Topology</button>\n        <button id=\"add-node-btn\" class=\"save-btn\" title=\"Add new node\" style=\"background: var(--accent)\">+ Node</button>\n        <button id=\"add-rack-btn\" class=\"save-btn\" title=\"Add new rack\" style=\"background: var(--accent); margin-left: 8px;\">+ Rack</button>\n        <button id=\"undo-btn\" title=\"Undo (Ctrl+Z)\" style=\"padding: 6px 12px; background: var(--btn-bg); color: var(--btn-text); border: 1px solid var(--edge-main); border-radius: 6px; cursor: pointer; font-size: 14px; font-weight: 600; opacity: 1;\">↶</button>\n        <button id=\"redo-btn\" title=\"Redo (Ctrl+Y)\" style=\"padding: 6px 12px; background: var(--btn-bg); color: var(--btn-text); border: 1px solid var(--edge-main); border-radius: 6px; cursor: pointer; font-size: 14px; font-weight: 600; opacity: 0.5;\" disabled=\"\">↷</button>\n<input type=\"text\" id=\"search-nodes\" placeholder=\"Search nodes...\" title=\"Search by name, IP, MAC, role, or tag\" style=\"padding: 6px 12px;background: var(--input-bg);color: var(--input-text);border: 1px solid var(--input-border);border-radius: 6px;font-family: var(--input-font);font-size: var(--input-font-size);width: 180px;\">\n        <button id=\"check-all-ping-btn\" title=\"Check status of all enabled nodes\" style=\"padding: 6px 12px;background: var(--btn-bg);color: var(--btn-text);border: 1px solid var(--edge-main);border-radius: 6px;cursor: pointer;font-size: 14px;font-weight: 600\">Check Pings</button>\n        <button id=\"layers-btn\" title=\"Toggle layer visibility\" style=\"padding: 6px 12px;background: var(--btn-bg);color: var(--btn-text);border: 1px solid var(--edge-main);border-radius: 6px;cursor: pointer;font-size: 14px;font-weight: 600;\">Layers</button>\n        <button id=\"tabs-btn\" title=\"Manage document tabs\" style=\"padding: 6px 12px;background: var(--btn-bg);color: var(--btn-text);border: 1px solid var(--edge-main);border-radius: 6px;cursor: pointer;font-size: 14px;font-weight: 600;\">Tabs</button>\n        <button id=\"rollback-btn\" title=\"View version history\" style=\"padding: 6px 12px;background: var(--btn-bg);color: var(--btn-text);border: 1px solid var(--edge-main);border-radius: 6px;cursor: pointer;font-size: 14px;font-weight: 600;\">Snapshots</button>\n        <button id=\"audit-log-btn\" title=\"View audit log\" style=\"padding: 6px 12px;background: var(--btn-bg);color: var(--btn-text);border: 1px solid var(--edge-main);border-radius: 6px;cursor: pointer;font-size: 14px;font-weight: 600;\">Audit</button>\n        <button id=\"port-map-btn\" title=\"View all port connections\" style=\"padding: 6px 12px;background: var(--btn-bg);color: var(--btn-text);border: 1px solid var(--edge-main);border-radius: 6px;cursor: pointer;font-size: 14px;font-weight: 600;\">Ports</button>\n        <button id=\"secrets-btn\" title=\"Manage encrypted notes\" style=\"padding: 6px 12px;background: var(--btn-bg);color: var(--btn-text);border: 1px solid var(--edge-main);border-radius: 6px;cursor: pointer;font-size: 14px;font-weight: 600;\">Notes</button>\n\t\t<button id=\"mobile-export-btn\" title=\"Export topology\" style=\"padding: 6px 12px;background: var(--btn-bg);color: var(--btn-text);border: 1px solid var(--edge-main);border-radius: 6px;cursor: pointer;font-size: 14px;font-weight: 600;\">Export</button>\n        <button id=\"mobile-import-btn\" title=\"Import topology\" style=\"padding: 6px 12px;background: var(--btn-bg);color: var(--btn-text);border: 1px solid var(--edge-main);border-radius: 6px;cursor: pointer;font-size: 14px;font-weight: 600;\">Import</button>\n        <button id=\"settings-btn\" title=\"Page settings\">⚙️</button>\n      </div>\n      <button id=\"mobile-menu-toggle\" class=\"mobile-menu-btn\">☰</button>\n      <div class=\"header-resizer\" id=\"header-resizer\">\n        <svg class=\"resizer-icon\" width=\"40\" height=\"6\" viewBox=\"0 0 40 6\" xmlns=\"http://www.w3.org/2000/svg\">\n          <circle cx=\"14\" cy=\"3\" r=\"2\"></circle>\n          <circle cx=\"20\" cy=\"3\" r=\"2\"></circle>\n          <circle cx=\"26\" cy=\"3\" r=\"2\"></circle>\n        </svg>\n      </div>\n    </header>\n    <main>\n      <section class=\"topology-panel\">\n        <div class=\"draw-toolbar\" id=\"draw-toolbar\" style=\"display: flex !important;\">\n          <button type=\"button\" class=\"toolbar-close-btn\" id=\"draw-toolbar-close-btn\" title=\"Hide draw toolbar\">✕</button>\n          <button id=\"draw-toggle\" title=\"Draw custom line\">✏️</button>\n          <button id=\"rect-toggle\" title=\"Draw zone\">▭</button>\n          <button id=\"text-toggle\" title=\"Add text\">T</button>\n          <span style=\"border-left: 1px solid var(--edge-main); height: 20px; margin: 0 4px;\"></span>\n          <select id=\"rect-style\" title=\"zone style\">\n            <option value=\"filled\">Filled</option>\n            <option value=\"outlined\">Outlined</option>\n          </select>\n          <input type=\"color\" id=\"draw-color\" value=\"#f97316\" title=\"Line color\">\n          <select id=\"draw-style\" title=\"Line style\">\n            <option value=\"solid\">Solid</option>\n            <option value=\"dashed\">Dashed</option>\n            <option value=\"dotted\">Dotted</option>\n            <option value=\"wall\">Wall</option>\n          </select>\n          <select id=\"draw-arrow\" title=\"Arrow direction\">\n            <option value=\"none\">No arrows</option>\n            <option value=\"forward\">→ Right</option>\n            <option value=\"backward\">← Left</option>\n            <option value=\"both\">↔ Both</option>\n          </select>\n          <button id=\"draw-undo\" style=\"display: none\" title=\"Undo last point\">Undo</button>\n        </div>\n        <div class=\"topology-toolbar\" id=\"topology-toolbar\" style=\"display: flex !important;\">\n          <button type=\"button\" class=\"toolbar-close-btn\" id=\"topology-toolbar-close-btn\" title=\"Hide add line toolbar\">✕</button>\n          <label for=\"add-line-select\">Add line to:</label>\n          <select id=\"add-line-select\"></select>\n\t      <input type=\"color\" id=\"add-line-color\" value=\"#475569\" title=\"Line color\" style=\"width: 30px;height: 24px;border: 1px solid var(--toolbar-border);border-radius: 4px;cursor: pointer;background: transparent;padding: 0;\">     \n\t\t <select id=\"add-line-direction\" title=\"Line direction\">\n            <option value=\"none\">No arrows</option>\n            <option value=\"forward\">→ Forward</option>\n            <option value=\"backward\">← Backward</option>\n            <option value=\"both\">↔ Both</option>\n          </select>\n          <select id=\"add-line-routing\" title=\"Line routing\">\n            <option value=\"orthogonal\">Orthogonal</option>\n            <option value=\"curved\">Curved</option>\n            <option value=\"straight\">Straight</option>\n          </select>\n          <button id=\"add-line-btn\">Add</button>\n        </div>\n        <div class=\"topology-toolbar bulk-toolbar-desktop\" id=\"bulk-toolbar\" style=\"display: none\">\n          <button type=\"button\" class=\"toolbar-close-btn\" id=\"bulk-toolbar-close\" title=\"Clear selection\">✕</button>\n          <label style=\"font-weight: 600; color: var(--accent)\">Selected: <span id=\"bulk-count\">0</span></label>\n          <button id=\"bulk-align-left\" title=\"Align left\">⬅ Left</button>\n          <button id=\"bulk-align-right\" title=\"Align right\">➡ Right</button>\n          <button id=\"bulk-align-top\" title=\"Align top\">⬆ Top</button>\n          <button id=\"bulk-align-bottom\" title=\"Align bottom\">⬇ Bottom</button>\n          <button id=\"bulk-distribute-h\" title=\"Distribute horizontally\">↔ Distribute H</button>\n          <button id=\"bulk-distribute-v\" title=\"Distribute vertically\">↕ Distribute V</button>\n          <button id=\"bulk-clone\" title=\"Clone selected\">📋 Clone</button>\n          <button id=\"bulk-zone-copy\" title=\"Copy zone style\">📡 Copy Zone</button>\n          <button id=\"bulk-zone-paste\" title=\"Paste zone style\">📡 Paste Zone</button>\n          <button id=\"bulk-zone-toggle\" title=\"Toggle zones on selected\">📡 Toggle Zones</button>\n          <button id=\"bulk-delete\" title=\"Delete selected\" style=\"background: var(--danger); color: white;\">Delete</button>\n        </div>\n        <div class=\"bulk-toolbar-mobile\" id=\"bulk-toolbar-mobile\" style=\"display: none\">\n          <button type=\"button\" id=\"bulk-mobile-btn\" style=\"background: var(--accent);color: white;padding: 12px 20px;border-radius: 25px;font-weight: 600;font-size: 16px;box-shadow: 0 4px 12px rgba(0,0,0,0.3);border: none;cursor: pointer;display: flex;align-items: center;gap: 8px;\">\n          <span id=\"bulk-count-mobile\">0</span>Selected</button>\n        </div>\n        <div id=\"bulk-actions-modal\" style=\"display: none;position: fixed;bottom: 0;left: 0;right: 0;background: var(--panel-alt);border-top-left-radius:20px;border-top-right-radius:20px;padding:20px;padding-bottom:(safe-area-inset-bottom, 20px);box-shadow: 0 -4px 20px rgba(0,0,0,0.5);z-index: 10000;max-height: calc(100vh - 80px);overflow-y: auto;\">\n          <div style=\"display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;\">\n            <h3 style=\"margin: 0; color: var(--accent); font-size: 20px;\">\n              <span id=\"bulk-count-modal\">0</span> Nodes Selected\n            </h3>\n            <button id=\"bulk-modal-close\" style=\"background: none;border: none;font-size: 24px;cursor: pointer;color: var(--text-main);padding: 0;width: 32px;height: 32px;\">✕</button>\n          </div>\n          <div style=\"display: grid; grid-template-columns: 1fr 1fr; gap: 12px;\">\n            <button id=\"bulk-align-left-mobile\" class=\"bulk-action-btn\">⬅<br><span style=\"font-size: 12px;\">Align Left</span></button>\n            <button id=\"bulk-align-right-mobile\" class=\"bulk-action-btn\">➡<br><span style=\"font-size: 12px;\">Align Right</span></button>\n            <button id=\"bulk-align-top-mobile\" class=\"bulk-action-btn\">⬆<br><span style=\"font-size: 12px;\">Align Top</span></button>\n            <button id=\"bulk-align-bottom-mobile\" class=\"bulk-action-btn\">⬇<br><span style=\"font-size: 12px;\">Align Bottom</span></button>\n            <button id=\"bulk-distribute-h-mobile\" class=\"bulk-action-btn\">↔<br><span style=\"font-size: 12px;\">Distribute H</span></button>\n            <button id=\"bulk-distribute-v-mobile\" class=\"bulk-action-btn\">↕<br><span style=\"font-size: 12px;\">Distribute V</span></button>\n            <button id=\"bulk-lock-mobile\" class=\"bulk-action-btn\">🔒<br><span style=\"font-size: 12px;\">Lock Toggle</span></button>\n            <button id=\"bulk-group-mobile\" class=\"bulk-action-btn\">⭕<br><span style=\"font-size: 12px;\">Group Toggle</span></button>\n            <button id=\"bulk-clone-mobile\" class=\"bulk-action-btn\">📋<br><span style=\"font-size: 12px;\">Clone All</span></button>\n            <button id=\"bulk-delete-mobile\" class=\"bulk-action-btn\" style=\"background: var(--danger); color: white;\">🗑<br><span style=\"font-size: 12px;\">Delete All</span></button>\n          </div>\n          <div style=\"margin-top: 16px; padding-top: 16px; border-top: 1px solid var(--edge-main);\">\n            <div style=\"font-size: 12px; color: var(--text-soft); margin-bottom: 12px; text-transform: uppercase; letter-spacing: 0.05em;\">Coverage Zones</div>\n            <div style=\"display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 12px;\">\n              <button id=\"bulk-zone-copy-mobile\" class=\"bulk-action-btn\">📡<br><span style=\"font-size: 12px;\">Copy Zone</span></button>\n              <button id=\"bulk-zone-paste-mobile\" class=\"bulk-action-btn\">📡<br><span style=\"font-size: 12px;\">Paste Zone</span></button>\n              <button id=\"bulk-zone-toggle-mobile\" class=\"bulk-action-btn\">📡<br><span style=\"font-size: 12px;\">Toggle Zones</span></button>\n            </div>\n          </div>\n          <button id=\"bulk-clear-mobile\" style=\"width: 100%;margin-top: 16px;padding: 14px;background: var(--panel-main);border: 1px solid var(--edge-main);border-radius: 8px;color: var(--text-main);font-size: 16px;cursor: pointer;\">Clear Selection</button>\n        </div>\n        <div class=\"canvas-hint\" id=\"canvas-hint\" style=\"cursor: pointer;\"></div>\n        <div class=\"legend-container\" id=\"edge-legend\" style=\"display: flex;\"><div class=\"legend-title\">Line Legend</div><button type=\"button\" class=\"legend-close-btn\">✕</button><div class=\"legend-item\"><span class=\"legend-swatch\" style=\"background-color: rgb(85, 226, 8); cursor: pointer;\"></span><span class=\"legend-label\" contenteditable=\"true\">ISP LINE</span></div><div class=\"legend-item\"><span class=\"legend-swatch\" style=\"background-color: rgb(76, 0, 255); cursor: pointer;\"></span><span class=\"legend-label\" contenteditable=\"true\">MY Guest NETWORK</span></div><div class=\"legend-item\"><span class=\"legend-swatch\" style=\"background-color: rgb(128, 255, 0); cursor: pointer;\"></span><span class=\"legend-label\" contenteditable=\"true\">you can edit me too</span></div><div class=\"legend-item\"><span class=\"legend-swatch\" style=\"background-color: rgb(251, 0, 255); cursor: pointer;\"></span><span class=\"legend-label\" contenteditable=\"true\">you can edit me too</span></div><div class=\"legend-item\"><span class=\"legend-swatch\" style=\"background-color: rgb(255, 0, 208); cursor: pointer;\"></span><span class=\"legend-label\" contenteditable=\"true\">iPhone (always guest iPhone)</span></div><div class=\"legend-item\"><span class=\"legend-swatch\" style=\"background-color: rgb(249, 115, 22); cursor: pointer;\"></span><span class=\"legend-label\" contenteditable=\"true\">you can edit me too</span></div></div>\n        <div class=\"canvas-viewport\" id=\"canvas-viewport\">\n          <svg id=\"map\" viewBox=\"-5.584863670202822 -99.90831573327841 4031.4509771376233 3023.5882328532175\" style=\"\"></svg>\n        </div>\n        <div class=\"minimap-zoom-wrapper\" id=\"minimap-zoom-wrapper\" style=\"display: block;\">\n          <button type=\"button\" class=\"minimap-close-btn\" id=\"minimap-close-btn\" title=\"Hide map &amp; zoom\">✕</button>\n          <div class=\"minimap-container\" id=\"minimap-container\">\n            <svg id=\"minimap\" viewBox=\"0 0 4000 3000\">\n              <rect class=\"minimap-viewport\" id=\"minimap-viewport\" x=\"-5.584863670202822\" y=\"-99.90831573327841\" width=\"4031.4509771376233\" height=\"3023.5882328532175\"></rect>\n            </svg>\n          </div>\n          <div class=\"zoom-toolbar\" id=\"zoom-toolbar\">\n            <button id=\"zoom-in-btn\" title=\"Zoom in\">+</button>\n            <div class=\"zoom-level\" id=\"zoom-level\">99%</div>\n            <button id=\"zoom-out-btn\" title=\"Zoom out\">-</button>\n            <div class=\"divider\"></div>\n            <button id=\"zoom-fit-btn\" title=\"Fit to view\">[ ]</button>\n            <button id=\"zoom-reset-btn\" title=\"Reset view\">R</button>\n          </div>\n        </div>\n        <button type=\"button\" id=\"edge-legend-mini\" class=\"legend-mini-btn\" style=\"bottom: 10px; display: none;\">Connection &amp; Zone Legend</button>\n        <button type=\"button\" id=\"minimap-mini\" class=\"legend-mini-btn\" style=\"right: 10px; left: auto; bottom: 10px; display: none;\">Map</button>\n        <button type=\"button\" id=\"draw-toolbar-mini\" class=\"legend-mini-btn\" style=\"top: 10px; left: 10px; right: auto; display: none;\">Draw</button>\n        <button type=\"button\" id=\"topology-toolbar-mini\" class=\"legend-mini-btn\" style=\"top: 10px; left: auto; right: 40px; display: none;\">Add Connection</button>\n     </section>\n      <aside class=\"details-panel\" id=\"details-panel\">\n        <div class=\"sidebar-resizer\" id=\"sidebar-resizer\">\n          <svg class=\"resizer-icon\" width=\"6\" height=\"40\" viewBox=\"0 0 6 40\" xmlns=\"http://www.w3.org/2000/svg\">\n            <circle cx=\"3\" cy=\"14\" r=\"2\"></circle>\n            <circle cx=\"3\" cy=\"20\" r=\"2\"></circle>\n            <circle cx=\"3\" cy=\"26\" r=\"2\"></circle>\n          </svg>\n        </div>\n        <div class=\"mobile-footer-resizer\" id=\"mobile-footer-resizer\">\n          <svg class=\"resizer-icon\" width=\"40\" height=\"6\" viewBox=\"0 0 40 6\" xmlns=\"http://www.w3.org/2000/svg\">\n            <circle cx=\"14\" cy=\"3\" r=\"2\"></circle>\n            <circle cx=\"20\" cy=\"3\" r=\"2\"></circle>\n            <circle cx=\"26\" cy=\"3\" r=\"2\"></circle>\n          </svg>\n        </div>\n        <div id=\"node-panel\" style=\"display: none;\">\n          <div class=\"details-name editable-text\" id=\"node-name\">Core Router 1</div>\n          <div class=\"details-ip editable-text\" id=\"node-ip\">10.0.0.1</div>\n          <div class=\"details-info-row\">\n            <span style=\"color: var(--text-soft); font-size: 14px; width: 80px;\">MAC:</span>\n            <span class=\"editable-text\" id=\"node-mac\" style=\"flex: 1; cursor: pointer; color: var(--text-main);\">00:1A:2B:3C:4D:01</span>\n          </div>\n          <div class=\"details-info-row\">\n            <span style=\"color: var(--text-soft); font-size: 14px; width: 80px;\">Rack Unit:</span>\n            <span class=\"editable-text\" id=\"node-rack\" style=\"flex: 1; cursor: pointer; color: var(--text-main);\">--</span>\n          </div>\n          <div class=\"details-info-row\" id=\"uheight-row\" style=\"display: none;\">\n            <span style=\"color: var(--text-soft); font-size: 14px; width: 80px;\">U Height:</span>\n            <span class=\"editable-text\" id=\"node-uheight\" style=\"flex: 1; cursor: pointer; color: var(--text-main);\">2U</span>\n          </div>\n          <div class=\"details-info-row\" id=\"assigned-rack-row\" style=\"display: flex;\">\n            <span style=\"color: var(--text-soft); font-size: 14px; width: 80px;\">Assigned Rack:</span>\n            <select id=\"node-assigned-rack\" style=\"flex: 1; padding: 4px 8px; background: var(--panel); color: var(--text-main); border: 1px solid var(--edge-main); border-radius: 4px; cursor: pointer; font-size: 14px;\"><option value=\"\">None</option><option value=\"dc-rack-a1\">DC Rack A1</option><option value=\"dc-rack-a2\">DC Rack A2</option><option value=\"dc-rack-b1\">DC Rack B1</option><option value=\"dc-rack-b2\">DC Rack B2</option><option value=\"dmz-rack\">DMZ Rack</option><option value=\"mgmt-rack\">Management Rack</option></select>\n          </div>\n          <div class=\"details-info-row\" id=\"rack-capacity-row\" style=\"display: none;\">\n            <span style=\"color: var(--text-soft); font-size: 14px; width: 80px;\">Rack Capacity:</span>\n            <select id=\"node-rack-capacity\" style=\"flex: 1; padding: 4px 8px; background: var(--panel); color: var(--text-main); border: 1px solid var(--edge-main); border-radius: 4px; cursor: pointer; font-size: 14px;\">\n              <option value=\"42\">42U</option>\n              <option value=\"48\">48U</option>\n              <option value=\"24\">24U</option>\n              <option value=\"12\">12U</option>\n              <option value=\"6\">6U</option>\n            </select>\n          </div>\n          <div class=\"details-info-row\">\n            <span style=\"color: var(--text-soft); font-size: 14px; width: 80px;\">Layer:</span>\n            <select id=\"node-layer\" style=\"flex: 1; padding: 4px 8px; background: var(--panel); color: var(--text-main); border: 1px solid var(--edge-main); border-radius: 4px; cursor: pointer; font-size: 14px;\">\n              <option value=\"physical\">Physical</option>\n              <option value=\"logical\">Logical</option>\n              <option value=\"security\">Security</option>\n              <option value=\"application\">Application</option>\n            </select>\n          </div>\n          <div class=\"details-role\" id=\"node-role\">Core Routing</div>\n\t\t  <details id=\"rack-contents-section\" style=\"display: none; margin-top: 12px; margin-bottom: 12px; padding: 10px; background: var(--panel-alt); border-radius: 6px;\" open=\"\">\n            <summary style=\"color: var(--accent); font-weight: 600; font-size: 14px; cursor: pointer;\">Nodes in Rack (<span id=\"rack-contents-count\">0</span>)</summary>\n            <div id=\"rack-contents-list\" style=\"max-height: 200px; overflow-y: auto; margin-top: 8px;\"></div>\n          </details>\n          <details id=\"fov-section\" style=\"display: block; margin-top: 12px; margin-bottom: 12px; padding: 10px; background: var(--panel-alt); border-radius: 6px;\">\n            <summary style=\"color: var(--accent); font-weight: 600; font-size: 14px; cursor: pointer;\">Coverage Zone</summary>\n            <div style=\"padding: 10px 0;\">\n              <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px; gap: 8px;\">\n                <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Preset:</label>\n                <select id=\"fov-preset\" style=\"flex: 1; padding: 4px 8px; background: var(--panel); border: 1px solid var(--edge-main); border-radius: 4px; color: var(--text-main);\">\n                  <option value=\"\">-- Apply Preset --</option>\n                  <option value=\"security-cam\">Security Camera</option>\n                  <option value=\"ptz-cam\">PTZ Camera</option>\n                  <option value=\"motion-detect\">Motion Detector</option>\n                  <option value=\"wifi-strong\">WiFi</option>\n                  <option value=\"wifi-extended\">WiFi Extender</option>\n                  <option value=\"smoke-alarm\">Smoke Alarm</option>\n                  <option value=\"sprinkler-arc\">Sprinkler Arc</option>\n                </select>\n                <button id=\"fov-save-preset\" title=\"Save as preset\" style=\"padding: 4px 8px; background: var(--panel); border: 1px solid var(--edge-main); border-radius: 4px; color: var(--text-main); cursor: pointer;\">💾</button>\n                <button id=\"fov-copy-style\" title=\"Copy zone style\" style=\"padding: 4px 8px; background: var(--panel); border: 1px solid var(--edge-main); border-radius: 4px; color: var(--text-main); cursor: pointer;\">📋</button>\n                <button id=\"fov-paste-style\" title=\"Paste zone style\" style=\"padding: 4px 8px; background: var(--panel); border: 1px solid var(--edge-main); border-radius: 4px; color: var(--text-main); cursor: pointer;\">📥</button>\n              </div>\n              <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Show Zone:</label>\n                <input type=\"checkbox\" id=\"fov-enabled\" style=\"width: 18px; height: 18px; cursor: pointer;\">\n              </div>\n              <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Angle:</label>\n                <input type=\"range\" id=\"fov-angle\" min=\"10\" max=\"360\" value=\"90\" style=\"flex: 1;\">\n                <span id=\"fov-angle-value\" style=\"min-width: 40px; text-align: right; color: var(--text-main);\">360°</span>\n              </div>\n              <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Distance:</label>\n                <input type=\"range\" id=\"fov-distance\" min=\"50\" max=\"500\" value=\"150\" style=\"flex: 1;\">\n                <span id=\"fov-distance-value\" style=\"min-width: 40px; text-align: right; color: var(--text-main);\">200</span>\n              </div>\n              <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Inner Radius:</label>\n                <input type=\"range\" id=\"fov-inner-radius\" min=\"0\" max=\"200\" value=\"0\" style=\"flex: 1;\">\n                <span id=\"fov-inner-radius-value\" style=\"min-width: 40px; text-align: right; color: var(--text-main);\">0</span>\n              </div>\n              <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Rotation:</label>\n                <input type=\"range\" id=\"fov-rotation\" min=\"0\" max=\"360\" value=\"0\" style=\"flex: 1;\">\n                <span id=\"fov-rotation-value\" style=\"min-width: 40px; text-align: right; color: var(--text-main);\">0°</span>\n              </div>\n              <div style=\"border-top: 1px solid var(--edge-main); margin-top: 10px; padding-top: 10px;\">\n                <div style=\"font-size: 12px; color: var(--text-soft); margin-bottom: 8px; text-transform: uppercase; letter-spacing: 0.05em;\">Fill</div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Color:</label>\n                  <input type=\"color\" id=\"fov-color\" value=\"#f59e0b\" style=\"width: 50px; height: 30px; border: 1px solid var(--edge-main); border-radius: 4px; cursor: pointer;\">\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Opacity:</label>\n                  <input type=\"range\" id=\"fov-opacity\" min=\"5\" max=\"80\" value=\"20\" style=\"flex: 1;\">\n                  <span id=\"fov-opacity-value\" style=\"min-width: 40px; text-align: right; color: var(--text-main);\">20%</span>\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Gradient:</label>\n                  <input type=\"checkbox\" id=\"fov-gradient\" style=\"width: 18px; height: 18px; cursor: pointer;\">\n                  <span style=\"color: var(--text-soft); font-size: 12px; margin-left: 8px;\">Fade toward edge</span>\n                </div>\n              </div>\n              <div style=\"border-top: 1px solid var(--edge-main); margin-top: 10px; padding-top: 10px;\">\n                <div style=\"font-size: 12px; color: var(--text-soft); margin-bottom: 8px; text-transform: uppercase; letter-spacing: 0.05em;\">Border</div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Color:</label>\n                  <input type=\"color\" id=\"fov-border-color\" value=\"#f59e0b\" style=\"width: 50px; height: 30px; border: 1px solid var(--edge-main); border-radius: 4px; cursor: pointer;\">\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Width:</label>\n                  <input type=\"range\" id=\"fov-border-width\" min=\"0\" max=\"10\" value=\"2\" style=\"flex: 1;\">\n                  <span id=\"fov-border-width-value\" style=\"min-width: 40px; text-align: right; color: var(--text-main);\">2</span>\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Style:</label>\n                  <select id=\"fov-border-style\" style=\"flex: 1; padding: 4px 8px; background: var(--panel); border: 1px solid var(--edge-main); border-radius: 4px; color: var(--text-main);\">\n                    <option value=\"solid\">Solid</option>\n                    <option value=\"dashed\">Dashed</option>\n                    <option value=\"dotted\">Dotted</option>\n                  </select>\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Opacity:</label>\n                  <input type=\"range\" id=\"fov-border-opacity\" min=\"0\" max=\"100\" value=\"100\" style=\"flex: 1;\">\n                  <span id=\"fov-border-opacity-value\" style=\"min-width: 40px; text-align: right; color: var(--text-main);\">100%</span>\n                </div>\n              </div>\n              <div style=\"border-top: 1px solid var(--edge-main); margin-top: 10px; padding-top: 10px;\">\n                <div style=\"font-size: 12px; color: var(--text-soft); margin-bottom: 8px; text-transform: uppercase; letter-spacing: 0.05em;\">Label</div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Text:</label>\n                  <input type=\"text\" id=\"fov-label\" placeholder=\"e.g. Detection Zone\" style=\"flex: 1; padding: 4px 8px; background: var(--panel); border: 1px solid var(--edge-main); border-radius: 4px; color: var(--text-main);\">\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Position:</label>\n                  <select id=\"fov-label-position\" style=\"flex: 1; padding: 4px 8px; background: var(--panel); border: 1px solid var(--edge-main); border-radius: 4px; color: var(--text-main);\">\n                    <option value=\"center\">Center</option>\n                    <option value=\"edge\">Edge</option>\n                    <option value=\"outside\">Outside</option>\n                  </select>\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Font Size:</label>\n                  <input type=\"range\" id=\"fov-label-size\" min=\"8\" max=\"32\" value=\"14\" style=\"flex: 1;\">\n                  <span id=\"fov-label-size-value\" style=\"min-width: 40px; text-align: right; color: var(--text-main);\">14px</span>\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Font Color:</label>\n                  <input type=\"color\" id=\"fov-label-color\" value=\"#ffffff\" style=\"width: 50px; height: 30px; border: 1px solid var(--edge-main); border-radius: 4px; cursor: pointer;\">\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Bold:</label>\n                  <input type=\"checkbox\" id=\"fov-label-bold\" style=\"width: 18px; height: 18px; cursor: pointer;\">\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Background:</label>\n                  <input type=\"checkbox\" id=\"fov-label-bg\" style=\"width: 18px; height: 18px; cursor: pointer;\">\n                  <input type=\"color\" id=\"fov-label-bg-color\" value=\"#000000\" style=\"width: 50px; height: 30px; border: 1px solid var(--edge-main); border-radius: 4px; cursor: pointer; margin-left: 8px;\">\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Offset X:</label>\n                  <input type=\"range\" id=\"fov-label-offset-x\" min=\"-200\" max=\"200\" value=\"0\" style=\"flex: 1;\">\n                  <span id=\"fov-label-offset-x-value\" style=\"min-width: 40px; text-align: right; color: var(--text-main);\">0</span>\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Offset Y:</label>\n                  <input type=\"range\" id=\"fov-label-offset-y\" min=\"-200\" max=\"200\" value=\"0\" style=\"flex: 1;\">\n                  <span id=\"fov-label-offset-y-value\" style=\"min-width: 40px; text-align: right; color: var(--text-main);\">0</span>\n                </div>\n              </div>\n              <div style=\"border-top: 1px solid var(--edge-main); margin-top: 10px; padding-top: 10px;\">\n                <div style=\"font-size: 12px; color: var(--text-soft); margin-bottom: 8px; text-transform: uppercase; letter-spacing: 0.05em;\">Animation</div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Animate:</label>\n                  <input type=\"checkbox\" id=\"fov-animate\" style=\"width: 18px; height: 18px; cursor: pointer;\">\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Type:</label>\n                  <select id=\"fov-animation-type\" style=\"flex: 1; padding: 4px 8px; background: var(--panel); border: 1px solid var(--edge-main); border-radius: 4px; color: var(--text-main);\">\n                    <option value=\"sweep\">Sweep (Pan)</option>\n                    <option value=\"pulse\">Pulse (Breathe)</option>\n                    <option value=\"rings\">Rings (Emanate)</option>\n                    <option value=\"spin\">Spin (Rotate)</option>\n                  </select>\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Sweep:</label>\n                  <input type=\"range\" id=\"fov-sweep\" min=\"30\" max=\"360\" value=\"120\" style=\"flex: 1;\">\n                  <span id=\"fov-sweep-value\" style=\"min-width: 40px; text-align: right; color: var(--text-main);\">120°</span>\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Speed:</label>\n                  <input type=\"range\" id=\"fov-speed\" min=\"1\" max=\"60\" value=\"4\" style=\"flex: 1;\">\n                  <span id=\"fov-speed-value\" style=\"min-width: 40px; text-align: right; color: var(--text-main);\">4s</span>\n                </div>\n              </div>\n            </div>\n          </details>\n          <details id=\"node-connections-section\" style=\"margin-top: 12px; margin-bottom: 12px; padding: 10px; background: var(--panel-alt); border-radius: 6px; display: block;\">\n            <summary style=\"color: var(--accent); font-weight: 600; font-size: 14px; cursor: pointer;\">Connections (<span id=\"node-connections-count\">9</span>)</summary>\n            <div id=\"node-connections-list\" style=\"max-height: 200px; overflow-y: auto; margin-top: 8px;\"><div style=\"padding: 6px 0px; border-bottom: 1px solid var(--edge-main); display: flex; align-items: center; font-size: 13px;\"><span title=\"Click to view connection\" style=\"color: var(--accent); font-family: monospace; cursor: pointer;\">Gi1/0/1</span><span style=\"color: var(--text-soft); margin: 0px 8px;\">↔</span><span title=\"Click to view connection\" style=\"color: var(--text-main); cursor: pointer;\">ISP Primary</span><span title=\"Click to view connection\" style=\"color: var(--accent); font-family: monospace; margin-left: 6px; cursor: pointer;\">(Gi0/0)</span></div><div style=\"padding: 6px 0px; border-bottom: 1px solid var(--edge-main); display: flex; align-items: center; font-size: 13px;\"><span title=\"Click to view connection\" style=\"color: var(--accent); font-family: monospace; cursor: pointer;\">Gi1/0/24</span><span style=\"color: var(--text-soft); margin: 0px 8px;\">↔</span><span title=\"Click to view connection\" style=\"color: var(--text-main); cursor: pointer;\">Core Router 2</span><span title=\"Click to view connection\" style=\"color: var(--accent); font-family: monospace; margin-left: 6px; cursor: pointer;\">(Gi1/0/24)</span></div><div style=\"padding: 6px 0px; border-bottom: 1px solid var(--edge-main); display: flex; align-items: center; font-size: 13px;\"><span title=\"Click to set port\" style=\"color: var(--text-soft); cursor: pointer;\">-</span><span style=\"color: var(--text-soft); margin: 0px 8px;\">↔</span><span title=\"Click to view connection\" style=\"color: var(--text-main); cursor: pointer;\">External FW 1</span><span title=\"Click to set port\" style=\"color: var(--text-soft); margin-left: 6px; cursor: pointer;\">(-)</span></div><div style=\"padding: 6px 0px; border-bottom: 1px solid var(--edge-main); display: flex; align-items: center; font-size: 13px;\"><span title=\"Click to set port\" style=\"color: var(--text-soft); cursor: pointer;\">-</span><span style=\"color: var(--text-soft); margin: 0px 8px;\">↔</span><span title=\"Click to view connection\" style=\"color: var(--text-main); cursor: pointer;\">NYC Branch Router</span><span title=\"Click to set port\" style=\"color: var(--text-soft); margin-left: 6px; cursor: pointer;\">(-)</span></div><div style=\"padding: 6px 0px; border-bottom: 1px solid var(--edge-main); display: flex; align-items: center; font-size: 13px;\"><span title=\"Click to set port\" style=\"color: var(--text-soft); cursor: pointer;\">-</span><span style=\"color: var(--text-soft); margin: 0px 8px;\">↔</span><span title=\"Click to view connection\" style=\"color: var(--text-main); cursor: pointer;\">LA Branch Router</span><span title=\"Click to set port\" style=\"color: var(--text-soft); margin-left: 6px; cursor: pointer;\">(-)</span></div><div style=\"padding: 6px 0px; border-bottom: 1px solid var(--edge-main); display: flex; align-items: center; font-size: 13px;\"><span title=\"Click to set port\" style=\"color: var(--text-soft); cursor: pointer;\">-</span><span style=\"color: var(--text-soft); margin: 0px 8px;\">↔</span><span title=\"Click to view connection\" style=\"color: var(--text-main); cursor: pointer;\">Chicago Branch Router</span><span title=\"Click to set port\" style=\"color: var(--text-soft); margin-left: 6px; cursor: pointer;\">(-)</span></div><div style=\"padding: 6px 0px; border-bottom: 1px solid var(--edge-main); display: flex; align-items: center; font-size: 13px;\"><span title=\"Click to set port\" style=\"color: var(--text-soft); cursor: pointer;\">-</span><span style=\"color: var(--text-soft); margin: 0px 8px;\">↔</span><span title=\"Click to view connection\" style=\"color: var(--text-main); cursor: pointer;\">London Branch Router</span><span title=\"Click to set port\" style=\"color: var(--text-soft); margin-left: 6px; cursor: pointer;\">(-)</span></div><div style=\"padding: 6px 0px; border-bottom: 1px solid var(--edge-main); display: flex; align-items: center; font-size: 13px;\"><span title=\"Click to set port\" style=\"color: var(--text-soft); cursor: pointer;\">-</span><span style=\"color: var(--text-soft); margin: 0px 8px;\">↔</span><span title=\"Click to view connection\" style=\"color: var(--text-main); cursor: pointer;\">Tokyo Branch Router</span><span title=\"Click to set port\" style=\"color: var(--text-soft); margin-left: 6px; cursor: pointer;\">(-)</span></div><div style=\"padding: 6px 0px; border-bottom: 1px solid var(--edge-main); display: flex; align-items: center; font-size: 13px;\"><span title=\"Click to set port\" style=\"color: var(--text-soft); cursor: pointer;\">-</span><span style=\"color: var(--text-soft); margin: 0px 8px;\">↔</span><span title=\"Click to view connection\" style=\"color: var(--text-main); cursor: pointer;\">AWS Cloud</span><span title=\"Click to set port\" style=\"color: var(--text-soft); margin-left: 6px; cursor: pointer;\">(-)</span></div></div>\n          </details>\n          <div class=\"badge-row\" id=\"node-tags\"></div>\n          <div style=\"margin-top: 10px; display: flex; gap: 8px\">\n            <input type=\"text\" id=\"new-tag-input\" placeholder=\"Add tag...\" style=\"flex: 1;padding: 8px 12px;background: var(--panel);border: 1px solid var(--edge-main);border-radius: 6px;color: var(--text-main);font-size: clamp(14px, 1.6vw, 18px);\">\n            <button id=\"add-tag-btn\" style=\"padding: 8px 16px;background: var(--accent);color: var(--bg);border: none;border-radius: 6px;cursor: pointer;font-size: clamp(14px, 1.6vw, 18px);font-weight: 600;\">Add</button>\n          </div>\n          <button class=\"pick-icon-btn\" id=\"pick-tag-icon-btn\">Add Icon Tag</button>\n          <div class=\"section-label\">Size</div>\n          <div class=\"size-controls\">\n            <label>Size:</label>\n            <input type=\"range\" id=\"size-slider\" min=\"20\" max=\"200\" value=\"55\">\n            <span id=\"size-value\">127</span>\n            <button id=\"reset-size\">Reset</button>\n          </div>\n          <div class=\"size-controls\">\n            <label>Rotation:</label>\n            <input type=\"range\" id=\"rotation-slider\" min=\"-360\" max=\"360\" value=\"0\">\n            <input type=\"number\" id=\"rotation-value\" value=\"0\" style=\"width: 60px; padding: 4px; background: var(--panel); border: 1px solid var(--edge-main); border-radius: 4px; color: var(--text-main); text-align: center;\">\n            <span style=\"color: var(--text-soft);\">°</span>\n            <button id=\"reset-rotation\">Reset</button>\n          </div>\n          <details class=\"style-section\">\n            <summary>Styling</summary>\n            <div class=\"style-content\">\n              <div class=\"style-row\">\n                <label>Screen:</label>\n                <select id=\"style-scope\">\n                  <option value=\"all\">All</option>\n                  <option value=\"desktop\">Desktop</option>\n                  <option value=\"tablet\">Tablet</option>\n                  <option value=\"mobile\">Mobile</option>\n                  <option value=\"fold\">Fold</option>\n                </select>\n              </div>\n              <div class=\"style-row\">\n                <label>Shape:</label>\n                <select id=\"shape-select\"><option value=\"custom-icon\">Custom Icon</option>\n                  <optgroup label=\"Basic Shapes\">\n            <option value=\"circle\">Circle</option>\n            <option value=\"square\">Square</option>\n            <option value=\"rectangle\">Rectangle</option>\n            <option value=\"triangle\">Triangle</option>\n            <option value=\"hexagon\">Hexagon</option>\n            <option value=\"diamond\">Diamond</option>\n            <option value=\"star\">Star</option>\n            <option value=\"stop-sign\">Stop Sign</option>\n            <option value=\"octagon\">Octagon</option>\n            <option value=\"pentagon\">Pentagon</option>\n            <option value=\"cross\">Cross</option>\n            <option value=\"rounded-square\">Rounded Square</option>\n            <option value=\"pill\">Pill</option>\n            <option value=\"parallelogram\">Parallelogram</option>\n            <option value=\"trapezoid\">Trapezoid</option>\n          </optgroup>\n          <optgroup label=\"Computers &amp; Devices\">\n            <option value=\"server\">Server</option>\n            <option value=\"pc\">PC / Desktop</option>\n            <option value=\"laptop\">Laptop</option>\n            <option value=\"phone\">Phone / Mobile</option>\n            <option value=\"printer\">Printer</option>\n            <option value=\"pi\">Raspberry Pi</option>\n            <option value=\"sensor\">Sensor / IoT</option>\n          </optgroup>\n          <optgroup label=\"Network Equipment\">\n            <option value=\"router\">Router</option>\n            <option value=\"switch\">Switch</option>\n            <option value=\"firewall\">Firewall</option>\n            <option value=\"access-point\">Access Point</option>\n            <option value=\"load-balancer\">Load Balancer</option>\n            <option value=\"gateway\">Gateway</option>\n            <option value=\"vpn\">VPN / Tunnel</option>\n            <option value=\"nas\">NAS / Storage</option>\n          </optgroup>\n          <optgroup label=\"Cloud &amp; Services\">\n            <option value=\"cloud\">Cloud</option>\n            <option value=\"database\">Database</option>\n\t\t\t<option value=\"docker\">Docker</option>\n            <option value=\"container\">Container</option>\n            <option value=\"vm\">Virtual Machine</option>\n            <option value=\"kubernetes\">Kubernetes</option>\n            <option value=\"api\">API / Endpoint</option>\n            <option value=\"queue\">Queue / Message</option>\n            <option value=\"lambda\">Lambda / Function</option>\n            <option value=\"bucket\">Bucket / S3</option>\n          </optgroup>\n          <optgroup label=\"Security &amp; Monitoring\">\n            <option value=\"shield\">Shield</option>\n            <option value=\"camera\">Camera / CCTV</option>\n            <option value=\"monitor\">Monitor / Dashboard</option>\n          </optgroup>\n          <optgroup label=\"Smart Home\">\n            <option value=\"thermostat\">Thermostat</option>\n            <option value=\"doorbell\">Video Doorbell</option>\n            <option value=\"smart-lock\">Smart Lock</option>\n            <option value=\"smart-bulb\">Smart Bulb</option>\n            <option value=\"smart-plug\">Smart Plug</option>\n            <option value=\"smart-speaker\">Smart Speaker</option>\n            <option value=\"smart-tv\">Smart TV</option>\n            <option value=\"hub\">Smart Hub</option>\n            <option value=\"smoke-detector\">Smoke Detector</option>\n            <option value=\"motion-sensor\">Motion Sensor</option>\n            <option value=\"garage\">Garage Door</option>\n            <option value=\"sprinkler\">Sprinkler</option>\n            <option value=\"vacuum\">Robot Vacuum</option>\n          </optgroup>\n                </select>\n              </div>\n              <button class=\"pick-icon-btn\" id=\"pick-shape-icon-btn\">Or Search Web Icons</button>\n              <div class=\"style-row\">\n                <label>Fill Color:</label>\n                <input type=\"color\" id=\"circle-color\" value=\"#1e293b\">\n              </div>\n              <div class=\"style-row\">\n                <label>Border Color:</label>\n                <input type=\"color\" id=\"circle-border\" value=\"#475569\">\n              </div>\n              <div class=\"style-row\">\n                <label>Title Color:</label>\n                <input type=\"color\" id=\"title-color\" value=\"#e2e8f0\">\n              </div>\n              <div class=\"style-row\">\n                <label>Title Font:</label>\n                <select id=\"title-font\">\n                  <option value=\"system-ui, sans-serif\">System UI</option>\n                  <option value=\"monospace\">Monospace</option>\n                  <option value=\"serif\">Serif</option>\n                  <option value=\"cursive\">Cursive</option>\n                  <option value=\"'Courier New', monospace\">Courier</option>\n                  <option value=\"Arial, sans-serif\">Arial</option>\n                  <option value=\"'Times New Roman', serif\">Times</option>\n                </select>\n              </div>\n              <div class=\"style-row\">\n                <label>Title Size:</label>\n                <input type=\"number\" id=\"title-size\" min=\"8\" max=\"100\" value=\"18\">\n              </div>\n              <div class=\"style-row\">\n                <label>Sub Color:</label>\n                <input type=\"color\" id=\"sub-color\" value=\"#94a3b8\">\n              </div>\n              <div class=\"style-row\">\n                <label>Sub Font:</label>\n                <select id=\"sub-font\">\n                  <option value=\"system-ui, sans-serif\">System UI</option>\n                  <option value=\"monospace\">Monospace</option>\n                  <option value=\"serif\">Serif</option>\n                  <option value=\"cursive\">Cursive</option>\n                  <option value=\"'Courier New', monospace\">Courier</option>\n                  <option value=\"Arial, sans-serif\">Arial</option>\n                  <option value=\"'Times New Roman', serif\">Times</option>\n                </select>\n              </div>\n              <div class=\"style-row\">\n                <label>Sub Size:</label>\n                <input type=\"number\" id=\"sub-size\" min=\"6\" max=\"80\" value=\"13\">\n              </div>\n              <div style=\"\n                margin-top: 12px;\n                padding-top: 10px;\n                border-top: 1px solid var(--edge-main);\n                \">\n                <div style=\"font-size: 12px;color: var(--text-soft);margin-bottom: 8px;text-transform: uppercase;\">Text Position</div>\n                <div class=\"style-row\">\n                  <label>Name Y:</label>\n                  <input type=\"number\" id=\"title-offset-y\" min=\"-100\" max=\"100\" value=\"0\" style=\"width: 60px\">\n                </div>\n                <div class=\"style-row\">\n                  <label>Name X:</label>\n                  <input type=\"number\" id=\"title-offset-x\" min=\"-100\" max=\"100\" value=\"0\" style=\"width: 60px\">\n                </div>\n                <div class=\"style-row\">\n                  <label>IP Y:</label>\n                  <input type=\"number\" id=\"sub-offset-y\" min=\"-100\" max=\"100\" value=\"0\" style=\"width: 60px\">\n                </div>\n                <div class=\"style-row\">\n                  <label>IP X:</label>\n                  <input type=\"number\" id=\"sub-offset-x\" min=\"-100\" max=\"100\" value=\"0\" style=\"width: 60px\">\n                </div>\n                <div style=\"margin-top: 16px; font-size: 12px; color: var(--text-soft); text-transform: uppercase;\">\n                  Ping Indicator Position\n                </div>\n                <div class=\"style-row\">\n                  <label>Ping X:</label>\n                  <input type=\"number\" id=\"ping-offset-x\" min=\"-200\" max=\"200\" value=\"0\" style=\"width: 60px;\">\n                </div>\n                <div class=\"style-row\">\n                  <label>Ping Y:</label>\n                  <input type=\"number\" id=\"ping-offset-y\" min=\"-200\" max=\"200\" value=\"0\" style=\"width: 60px;\">\n                </div>\n              </div>\n              <button id=\"reset-styles\" style=\"width: 100%;margin-top: 10px;padding: 8px;background: var(--panel);color: var(--text-main);border: 1px solid var(--edge-main);border-radius: 6px;cursor: pointer;font-size: clamp(13px, 1.5vw, 17px);\">Reset Styles</button>\n            </div>\n          </details>\n          <details class=\"style-section\">\n            <summary>Ping / Status Monitoring</summary>\n            <div class=\"style-content\">\n              <label style=\"display: flex; align-items: center; gap: 8px; margin-bottom: 12px; cursor: pointer;\">\n              <input type=\"checkbox\" id=\"node-pingable\" style=\"cursor: pointer;\">\n              <span style=\"font-size: 14px;\">Enable status check for this node</span>\n              </label>\n              <div id=\"node-ping-options\" style=\"display: none;\">\n                <div class=\"style-row\">\n                  <label>Protocol:</label>\n                  <select id=\"node-ping-protocol\">\n                    <option value=\"http\">HTTP (port 80)</option>\n                    <option value=\"https\">HTTPS (port 443)</option>\n                    <option value=\"custom\">Custom URL</option>\n                  </select>\n                </div>\n                <div id=\"node-custom-url-container\" style=\"display: block; margin-top: 8px;\">\n                  <label style=\"display: block; margin-bottom: 4px; font-size: 13px;\">Custom URL:</label>\n                  <input type=\"text\" id=\"node-custom-url\" placeholder=\"e.g. http://192.168.1.1:8080\" style=\"width: 100%;padding: 8px 12px;background: var(--panel);border: 1px solid var(--edge-main);border-radius: 6px;color: var(--text-main);font-size: 14px;\">\n                </div>\n                <div class=\"style-row\" style=\"margin-top: 8px;\">\n                  <label>Timeout (ms):</label>\n                  <input type=\"number\" id=\"node-ping-timeout\" value=\"3000\" min=\"1000\" max=\"10000\" step=\"500\" style=\"width: 100px;\">\n                </div>\n                <div style=\"margin-top: 12px; padding: 10px; background: var(--panel); border-radius: 6px; border: 1px solid var(--edge-main);\">\n                  <div style=\"font-size: 12px; color: var(--text-soft); margin-bottom: 8px;\">Current Status:</div>\n                  <div id=\"node-ping-status\" style=\"font-size: 14px; font-weight: 600; color: var(--accent);\">● Online</div>\n                  <div id=\"node-ping-last-check\" style=\"font-size: 11px; color: var(--text-soft); margin-top: 4px;\">Last checked: 2:25:19 PM</div>\n                </div>\n                <button id=\"check-ping-now\" style=\"width: 100%;margin-top: 10px;padding: 8px;background: var(--accent);color: var(--bg);border: none;border-radius: 6px;cursor: pointer;font-size: 14px;font-weight: 600;\">Check Status Now</button>\n              </div>\n            </div>\n          </details>\n          <div class=\"section-label\">Notes</div>\n          <ul class=\"list\" id=\"node-notes\"></ul>\n          <div style=\"margin-top: 10px; display: flex; gap: 8px\">\n            <input type=\"text\" id=\"new-note-input\" placeholder=\"Type new note...\" style=\"flex: 1;padding: 8px 12px;background: var(--panel);border: 1px solid var(--edge-main);border-radius: 6px;color: var(--text-main);font-size: clamp(14px, 1.6vw, 18px);\">\n            <button id=\"add-note-btn\" style=\"padding: 8px 16px;background: var(--accent);color: var(--bg);border: none;border-radius: 6px;cursor: pointer;font-size: clamp(14px, 1.6vw, 18px);font-weight: 600;\">Add</button>\n          </div>\n          <button id=\"delete-node-btn\" style=\"margin-top: 15px;padding: 10px 16px;background: var(--danger);color: rgb(255, 255, 255);border: none;border-radius: 6px;cursor: pointer;font-size: clamp(14px, 1.6vw, 18px);font-weight: 600;width: 100%;\">Delete Node</button>\n        </div>\n        <div id=\"edge-panel\" style=\"display: none\">\n          <div class=\"details-name\" id=\"edge-title\">Custom line</div>\n          <div class=\"style-row\" style=\"margin-top: 10px\">\n            <label>Width:</label>\n            <input type=\"number\" id=\"edge-width\" min=\"1\" max=\"20\" value=\"4\">\n          </div>\n          <div class=\"style-row\">\n            <label>Color:</label>\n            <input type=\"color\" id=\"edge-color\" value=\"#475569\">\n          </div>\n          <div class=\"style-row\">\n            <label>Line Style:</label>\n            <select id=\"edge-line-style\">\n              <option value=\"solid\">Solid</option>\n              <option value=\"dashed\">Dashed</option>\n              <option value=\"dotted\">Dotted</option>\n              <option value=\"wall\">Wall</option>\n            </select>\n          </div>\n          <div class=\"style-row\">\n            <label>Routing:</label>\n            <select id=\"edge-routing\">\n              <option value=\"orthogonal\">Orthogonal (90°)</option>\n              <option value=\"curved\">Curved</option>\n              <option value=\"straight\">Straight</option>\n            </select>\n          </div>\n          <div class=\"style-row\">\n            <label>Direction:</label>\n            <select id=\"edge-direction\">\n              <option value=\"none\">No arrows</option>\n              <option value=\"forward\">→ Forward</option>\n              <option value=\"backward\">← Backward</option>\n              <option value=\"both\">↔ Bidirectional</option>\n            </select>\n          </div>\n          <div class=\"style-row\">\n            <label>Animate:</label>\n            <input type=\"checkbox\" id=\"edge-animate\">\n          </div>\n          <div class=\"style-row\">\n            <label>Style:</label>\n            <select id=\"edge-animation-style\">\n              <option value=\"\">Default</option>\n              <option value=\"arrows\">Flowing Arrows</option>\n              <option value=\"dots\">Dot Arrows</option>\n            </select>\n          </div>\n          <div class=\"style-row\">\n            <label>Speed:</label>\n            <select id=\"edge-animation-speed\">\n              <option value=\"\">Default</option>\n              <option value=\"0.5\">Very Fast</option>\n              <option value=\"1\">Fast</option>\n              <option value=\"1.5\">Normal</option>\n              <option value=\"2.5\">Slow</option>\n              <option value=\"4\">Very Slow</option>\n            </select>\n          </div>\n          <div class=\"style-row\" id=\"edge-port-fields\">\n            <label>From Port:</label>\n            <input type=\"text\" id=\"edge-from-port\" placeholder=\"e.g. eth0, gi0/1\" style=\"flex: 1;padding: 6px 10px;background: var(--panel);border: 1px solid var(--edge-main);border-radius: 4px;color: var(--text-main);font-size: 14px;\">\n          </div>\n          <div class=\"style-row\" id=\"edge-port-fields-to\">\n            <label>To Port:</label>\n            <input type=\"text\" id=\"edge-to-port\" placeholder=\"e.g. eth1, gi0/2\" style=\"flex: 1;padding: 6px 10px;background: var(--panel);border: 1px solid var(--edge-main);border-radius: 4px;color: var(--text-main);font-size: 14px;\">\n          </div>\n          <div class=\"section-label\">Line Notes</div>\n          <ul class=\"list\" id=\"edge-notes\"></ul>\n          <div style=\"margin-top: 10px; display: flex; gap: 8px\">\n            <input type=\"text\" id=\"new-edge-note\" placeholder=\"Add note...\" style=\"flex: 1;padding: 8px 12px;background: var(--panel);border: 1px solid var(--edge-main);border-radius: 6px;color: var(--text-main);font-size: clamp(14px, 1.6vw, 18px);\">\n            <button id=\"add-edge-note\" style=\"padding: 8px 16px;background: var(--accent);color: var(--bg);border: none;border-radius: 6px;cursor: pointer;font-size: clamp(14px, 1.6vw, 18px);font-weight: 600;\">Add</button>\n          </div>\n          <button id=\"delete-edge\" style=\"margin-top: 15px;padding: 10px 16px;background: var(--danger);color: #fff;border: none;border-radius: 6px;cursor: pointer;font-size: clamp(14px, 1.6vw, 18px);font-weight: 600;\">Delete Line</button>\n        </div>\n        <div id=\"rect-panel\" style=\"display: none\">\n          <div class=\"details-name\" id=\"rect-title\">Zone</div>\n          <div class=\"style-row\" id=\"rect-fill-row\" style=\"margin-top: 10px\">\n            <label>Fill Color:</label>\n            <input type=\"color\" id=\"rect-color\" value=\"#f97316\">\n          </div>\n          <div class=\"style-row\">\n            <label>Border Color:</label>\n            <input type=\"color\" id=\"rect-border-color\" value=\"#f97316\">\n          </div>\n          <div class=\"style-row\">\n            <label>Border Width:</label>\n            <input type=\"number\" id=\"rect-border-width\" min=\"0\" max=\"20\" value=\"2\">\n          </div>\n          <div class=\"style-row\">\n            <label>Style:</label>\n            <select id=\"rect-style-select\">\n              <option value=\"filled\">Filled</option>\n              <option value=\"outlined\">Outlined</option>\n            </select>\n          </div>\n\t\t  <div class=\"style-row\">\n            <label>Line Style:</label>\n            <select id=\"rect-line-style\">\n              <option value=\"solid\">Solid</option>\n              <option value=\"dashed\">Dashed</option>\n              <option value=\"dotted\">Dotted</option>\n              <option value=\"wall\">Wall</option>\n            </select>\n          </div>\n          <div class=\"style-row\">\n            <label>Rotation:</label>\n            <input type=\"range\" id=\"rect-rotation\" min=\"-360\" max=\"360\" value=\"0\" style=\"flex: 1;\">\n            <input type=\"number\" id=\"rect-rotation-value\" value=\"0\" style=\"width: 60px; padding: 4px; background: var(--panel); border: 1px solid var(--edge-main); border-radius: 4px; color: var(--text-main); text-align: center;\">\n            <span style=\"color: var(--text-soft);\">°</span>\n          </div>\n          <div class=\"section-label\">Zone Notes</div>\n          <ul class=\"list\" id=\"rect-notes\"></ul>\n          <div style=\"margin-top: 10px; display: flex; gap: 8px\">\n            <input type=\"text\" id=\"new-rect-note\" placeholder=\"Add note...\" style=\"flex: 1;padding: 8px 12px;background: var(--panel);border: 1px solid var(--edge-main);border-radius: 6px;color: var(--text-main);font-size: clamp(14px, 1.6vw, 18px);\">\n            <button id=\"add-rect-note\" style=\"padding: 8px 16px;background: var(--accent);color: var(--bg);border: none;border-radius: 6px;cursor: pointer;font-size: clamp(14px, 1.6vw, 18px);font-weight: 600;\">Add</button>\n          </div>\n          <button id=\"delete-rect\" style=\"margin-top: 15px;padding: 10px 16px;background: var(--danger);color: #fff;border: none;border-radius: 6px;cursor: pointer;font-size: clamp(14px, 1.6vw, 18px);font-weight: 600;\">Delete Zone</button>\n        </div>\n        <div id=\"text-panel\" style=\"display: none\">\n          <div class=\"details-name\" id=\"text-title\">Text Element</div>\n          <div class=\"style-row\" style=\"margin-top: 10px\">\n            <label>Content:</label>\n            <textarea id=\"text-content\" placeholder=\"Enter text...\" style=\"width: 100%;padding: 8px 12px;background: var(--panel);border: 1px solid var(--edge-main);border-radius: 6px;color: var(--text-main);font-size: 14px;min-height: 80px;resize: vertical;font-family: inherit;\"></textarea>\n          </div>\n          <div class=\"style-row\">\n            <label>Font Size:</label>\n            <input type=\"number\" id=\"text-font-size\" min=\"8\" max=\"200\" value=\"18\" style=\"flex: 1;padding: 6px 10px;background: var(--panel);border: 1px solid var(--edge-main);border-radius: 4px;color: var(--text-main);\">\n          </div>\n          <div class=\"style-row\">\n            <label>Color:</label>\n            <input type=\"color\" id=\"text-color\" value=\"#e2e8f0\">\n          </div>\n          <div class=\"style-row\">\n            <label>Font Weight:</label>\n            <select id=\"text-font-weight\" style=\"flex: 1;padding: 6px 10px;background: var(--panel);border: 1px solid var(--edge-main);border-radius: 4px;color: var(--text-main);cursor: pointer;\">\n              <option value=\"normal\">Normal</option>\n              <option value=\"bold\">Bold</option>\n              <option value=\"600\">Semi-Bold</option>\n              <option value=\"300\">Light</option>\n            </select>\n          </div>\n          <div class=\"style-row\">\n            <label>Font Style:</label>\n            <select id=\"text-font-style\" style=\"flex: 1;padding: 6px 10px;background: var(--panel);border: 1px solid var(--edge-main);border-radius: 4px;color: var(--text-main);cursor: pointer;\">\n              <option value=\"normal\">Normal</option>\n              <option value=\"italic\">Italic</option>\n            </select>\n          </div>\n          <div class=\"style-row\">\n            <label>Text Align:</label>\n            <select id=\"text-align\" style=\"flex: 1;padding: 6px 10px;background: var(--panel);border: 1px solid var(--edge-main);border-radius: 4px;color: var(--text-main);cursor: pointer;\">\n              <option value=\"start\">Left</option>\n              <option value=\"middle\">Center</option>\n              <option value=\"end\">Right</option>\n            </select>\n          </div>\n          <div class=\"style-row\">\n            <label>Text Decoration:</label>\n            <select id=\"text-decoration\" style=\"flex: 1;padding: 6px 10px;background: var(--panel);border: 1px solid var(--edge-main);border-radius: 4px;color: var(--text-main);cursor: pointer;\">\n              <option value=\"none\">None</option>\n              <option value=\"underline\">Underline</option>\n              <option value=\"line-through\">Strike Through</option>\n            </select>\n          </div>\n          <div class=\"style-row\">\n            <label>Background:</label>\n            <input type=\"color\" id=\"text-bg-color\" value=\"#000000\">\n            <label style=\"margin-left: 10px; display: flex; align-items: center; gap: 6px;\">\n            <input type=\"checkbox\" id=\"text-bg-enabled\" style=\"cursor: pointer;\">\n            <span style=\"font-size: 13px;\">Enable</span>\n            </label>\n          </div>\n          <div class=\"style-row\">\n            <label>Opacity:</label>\n            <input type=\"range\" id=\"text-opacity\" min=\"0\" max=\"1\" step=\"0.1\" value=\"1\" style=\"flex: 1\">\n            <span id=\"text-opacity-val\" style=\"min-width: 35px; text-align: right\">100%</span>\n          </div>\n          <div class=\"style-row\">\n            <label>Rotation:</label>\n            <input type=\"range\" id=\"text-rotation\" min=\"-360\" max=\"360\" value=\"0\" style=\"flex: 1\">\n            <input type=\"number\" id=\"text-rotation-val\" value=\"0\" style=\"width: 60px; padding: 4px; background: var(--panel); border: 1px solid var(--edge-main); border-radius: 4px; color: var(--text-main); text-align: center;\">\n            <span style=\"color: var(--text-soft);\">°</span>\n          </div>\n          <button id=\"delete-text\" style=\"margin-top: 15px;padding: 10px 16px;background: var(--danger);color: #fff;border: none;border-radius: 6px;cursor: pointer;font-size: clamp(14px, 1.6vw, 18px);font-weight: 600;width: 100%;\">Delete Text</button>\n        </div>\n     <details class=\"style-section\" open=\"\">\n          <summary>Page Layout</summary>\n          <div class=\"style-content\">\n            <div style=\"padding: 12px; background: var(--panel-alt); border-radius: 6px; color: var(--text-soft); font-size: 13px; line-height: 1.6;\">\n              <strong style=\"color: var(--accent); display: block; margin-bottom: 8px;\">Drag to Resize:</strong>\n              • <strong>Header:</strong> Drag the bottom edge of the header bar<br>\n              • <strong>Sidebar:</strong> Drag the left edge of the sidebar panel<br>\n              • <strong>Mobile:</strong> Drag the top edge of the footer panel<br>\n              <div style=\"margin-top: 8px; padding-top: 8px; border-top: 1px solid var(--edge-main); font-size: 12px;\">\n                Hover over panel edges to see resize handles\n              </div>\n            </div>\n          </div>\n        </details>\n      </aside>\n    </main>\n    <script id=\"nodes-json\" type=\"application/json\">{}</script>\n    <script id=\"topology-state\" type=\"application/json\">ENCRYPTED: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</script>\n    <script>\n\tObject.keys(localStorage).forEach(key => {\n        if (key.startsWith('icon-')) {\n          localStorage.removeItem(key);\n        }\n      });\n      const IconLibrary = {\n       libraries: {\n        mdi: {\n         name: 'Material Design Icons',\n         cdnBase: 'https://cdn.jsdelivr.net/npm/@mdi/svg@latest/svg/',\n         metaUrl: 'https://cdn.jsdelivr.net/npm/@mdi/svg@latest/meta.json',\n         icons: []\n        },\n        simple: {\n         name: 'Simple Icons',\n         cdnBase: 'https://cdn.jsdelivr.net/npm/simple-icons@latest/icons/',\n         indexUrl: 'https://cdn.jsdelivr.net/npm/@iconify-json/simple-icons@latest/icons.json',\n         icons: []\n        },\n        selfhst: {\n         name: 'selfh.st/icons',\n         cdnBase: 'https://cdn.jsdelivr.net/gh/selfhst/icons@master/svg/',\n         indexUrl: 'https://raw.githubusercontent.com/selfhst/icons/refs/heads/main/index.json',\n         icons: []\n        }\n       },\n       currentLibrary: 'selfhst',\n       iconCache: {},\n       indexCache: {},\n       indexLoading: {},\n       async loadLibraryIndex(library) {\n        if (this.indexCache[library]) {\n         return this.indexCache[library];\n        }\n        if (this.indexLoading[library]) {\n         return this.indexLoading[library];\n        }\n        const lib = this.libraries[library];\n        this.indexLoading[library] = (async () => {\n         try {\n          if (library === 'selfhst') {\n           const response = await fetch(lib.indexUrl);\n           const data = await response.json();\n           const icons = data.filter(item => item.SVG === \"Yes\").map(item => ({\n            name: item.Reference,\n            displayName: item.Name,\n            tags: item.Tags ? item.Tags.split(',').map(t => t.trim()).filter(t => t) : [],\n            category: item.Category\n           }));\n           this.indexCache[library] = icons;\n           lib.icons = icons;\n           return icons;\n          } else if (library === 'simple') {\n           const response = await fetch('https://cdn.jsdelivr.net/npm/@iconify-json/simple-icons@latest/icons.json');\n           const data = await response.json();\n           const icons = Object.keys(data.icons).map(slug => ({\n            name: slug,\n            displayName: data.icons[slug].title || slug,\n            tags: data.aliases && data.aliases[slug] ? [data.aliases[slug].parent] : [],\n            hex: data.icons[slug].hex\n           }));\n           this.indexCache[library] = icons;\n           lib.icons = icons;\n           return icons;\n          } else if (library === 'mdi') {\n           const response = await fetch(lib.metaUrl);\n           const data = await response.json();\n           const icons = data.map(item => ({\n            name: item.name,\n            displayName: item.name,\n            tags: item.tags || [],\n            author: item.author\n           }));\n           this.indexCache[library] = icons;\n           lib.icons = icons;\n           return icons;\n          }\n         } catch (error) {\n          console.error(`Failed to load index for ${library}:`, error);\n          this.indexCache[library] = [];\n          lib.icons = [];\n          return [];\n         } finally {\n          delete this.indexLoading[library];\n         }\n        })();\n        return this.indexLoading[library];\n       },\n       async getIcon(library, name) {\n        const cacheKey = `${library}-${name}`;\n        if (this.iconCache[cacheKey]) {\n         return this.iconCache[cacheKey];\n        }\n        const lib = this.libraries[library];\n        const url = `${lib.cdnBase}${name}.svg`;\n        try {\n         const response = await fetch(url);\n         if (!response.ok) {\n          throw new Error(`HTTP ${response.status}`);\n         }\n         const svg = await response.text();\n         this.iconCache[cacheKey] = svg;\n         return svg;\n        } catch (error) {\n         console.error(`Failed to fetch icon ${cacheKey}:`, error);\n         return null;\n        }\n       },\n       searchIcons(library, query) {\n        const lib = this.libraries[library];\n        if (!lib.icons.length) return [];\n        const q = query.toLowerCase();\n        return lib.icons.filter(icon => {\n         const nameMatch = icon.name.toLowerCase().includes(q);\n         const displayMatch = icon.displayName && icon.displayName.toLowerCase().includes(q);\n         const tagMatch = icon.tags && icon.tags.some(t => t.toLowerCase().includes(q));\n         const categoryMatch = icon.category && icon.category.toLowerCase().includes(q);\n         return nameMatch || displayMatch || tagMatch || categoryMatch;\n        }).slice(0, 50);\n       }\n      };\n      let iconPickerCallback = null;\n      let selectedNodeIconData = null;\n      let selectedRackIconData = null;\n      let newNodeIconTags = [];\n      let freeDrawMode = false;\n      async function checkNodeStatus(nodeId) {\n       const data = NODE_DATA[nodeId];\n       if (!data || !data.ping || !data.ping.enabled) return;\n       data.ping.status = 'checking';\n       data.ping.lastCheck = new Date().toISOString();\n       data.ping.responseTime = null;\n       updatePingIndicator(nodeId);\n       if (currentNodeId === nodeId) {\n        updatePingStatusDisplay(nodeId);\n       }\n       let url;\n       if (data.ping.protocol === 'custom') {\n        url = data.ping.customUrl;\n       } else {\n        const ip = data.ip || '0.0.0.0';\n        const protocol = data.ping.protocol || 'http';\n        url = `${protocol}://${ip}`;\n       }\n       if (!url) {\n        data.ping.status = 'unknown';\n        updatePingIndicator(nodeId);\n        if (currentNodeId === nodeId) {\n         updatePingStatusDisplay(nodeId);\n        }\n        return;\n       }\n       const timeout = data.ping.timeout || 3000;\n       const startTime = performance.now();\n       try {\n        const result = await Promise.race([\n         new Promise((resolve, reject) => {\n          const img = new Image();\n          img.onload = () => resolve('online');\n          img.onerror = () => resolve('check-fetch');\n          img.src = `${url}/favicon.ico?_=${Date.now()}`;\n         }),\n         new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), timeout))\n        ]);\n        if (result === 'check-fetch') {\n         await Promise.race([\n          fetch(url, { method: 'HEAD', mode: 'no-cors' }),\n          new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), timeout))\n         ]);\n        }\n        data.ping.status = 'online';\n        data.ping.responseTime = Math.round(performance.now() - startTime);\n       } catch (error) {\n        data.ping.status = 'offline';\n        data.ping.responseTime = null;\n       }\n       data.ping.lastCheck = new Date().toISOString();\n       updatePingIndicator(nodeId);\n       if (currentNodeId === nodeId) {\n        updatePingStatusDisplay(nodeId);\n       }\n      }\n      function rgbaToHex(val) {\n      if (!val) return \"#000000\";\n      if (val.startsWith(\"#\")) return val;\n      const m = val.match(/rgba?\\((\\d+),\\s*(\\d+),\\s*(\\d+)/);\n      if (!m) return \"#000000\";\n      const r = Number(m[1]).toString(16).padStart(2, \"0\");\n      const g = Number(m[2]).toString(16).padStart(2, \"0\");\n      const b = Number(m[3]).toString(16).padStart(2, \"0\");\n      return `#${r}${g}${b}`;\n      }\n      function updatePingIndicator(nodeId) {\n      const nodeGroup = document.querySelector(`g[data-node-id=\"${nodeId}\"]`);\n      if (!nodeGroup) return;\n      const data = NODE_DATA[nodeId];\n      if (!data || !data.ping || !data.ping.enabled) {\n       const existingIndicator = nodeGroup.querySelector('.ping-indicator');\n       if (existingIndicator) existingIndicator.remove();\n       return;\n      }\n      let indicator = nodeGroup.querySelector('.ping-indicator');\n      const label = nodeGroup.querySelector('.node-label');\n      if (!indicator && label) {\n       indicator = document.createElementNS('http://www.w3.org/2000/svg', 'circle');\n       indicator.classList.add('ping-indicator');\n       nodeGroup.appendChild(indicator);\n      }\n      if (indicator && label) {\n       const size = savedSizes[nodeId] || getDefaultSize();\n       const radius = Math.max(4, size * 0.06);\n       indicator.setAttribute('r', radius);\n       const labelBBox = label.getBBox();\n       const labelX = parseFloat(label.getAttribute('x') || 0);\n       const labelY = parseFloat(label.getAttribute('y') || 0);\n       const styles = resolveStylesForNode(nodeId);\n      const offX = styles.pingOffsetX || 0;\n      const offY = styles.pingOffsetY || 0;\n      indicator.setAttribute('cx', (labelX - labelBBox.width / 2 - radius * 1.1) + offX);\n      indicator.setAttribute('cy', (labelY - radius * 0.7) + offY);\n      }\n      if (indicator) {\n       indicator.classList.remove('online', 'offline', 'checking');\n       if (data.ping.status) indicator.classList.add(data.ping.status);\n      }\n      }\n      async function checkAllNodesStatus() {\n       const nodesToCheck = Object.keys(NODE_DATA).filter(nodeId => {\n        const data = NODE_DATA[nodeId];\n        return data && data.ping && data.ping.enabled;\n       });\n       const batchSize = 5;\n       for (let i = 0; i < nodesToCheck.length; i += batchSize) {\n        const batch = nodesToCheck.slice(i, i + batchSize);\n        await Promise.all(batch.map(nodeId => checkNodeStatus(nodeId)));\n       }\n      }\n      function startAutoPing() {\n       stopAutoPing();\n       checkAllNodesStatus();\n       updateAutoPingLastRun();\n       autoPingSecondsRemaining = autoPingInterval;\n       autoPingTimer = setInterval(() => {\n        checkAllNodesStatus();\n        updateAutoPingLastRun();\n        autoPingSecondsRemaining = autoPingInterval;\n       }, autoPingInterval * 1000);\n       autoPingCountdown = setInterval(() => {\n        autoPingSecondsRemaining--;\n        updateAutoPingCountdown();\n        if (autoPingSecondsRemaining <= 0) {\n         autoPingSecondsRemaining = autoPingInterval;\n        }\n       }, 1000);\n       updateAutoPingCountdown();\n      }\n      function stopAutoPing() {\n       if (autoPingTimer) {\n        clearInterval(autoPingTimer);\n        autoPingTimer = null;\n       }\n       if (autoPingCountdown) {\n        clearInterval(autoPingCountdown);\n        autoPingCountdown = null;\n       }\n       autoPingSecondsRemaining = 0;\n       updateAutoPingCountdown();\n      }\n      function updateAutoPingCountdown() {\n       const nextCheckEl = document.getElementById('auto-ping-next-check');\n       if (nextCheckEl) {\n        if (autoPingSecondsRemaining > 0 && autoPingEnabled) {\n         const mins = Math.floor(autoPingSecondsRemaining / 60);\n         const secs = autoPingSecondsRemaining % 60;\n         if (mins > 0) {\n          nextCheckEl.textContent = `Next check in: ${mins}m ${secs}s`;\n         } else {\n          nextCheckEl.textContent = `Next check in: ${secs}s`;\n         }\n        } else {\n         nextCheckEl.textContent = 'Next check in: --';\n        }\n       }\n      }\n      function updateAutoPingLastRun() {\n       const lastRunEl = document.getElementById('auto-ping-last-run');\n       if (lastRunEl) {\n        const now = new Date();\n        lastRunEl.textContent = `Last run: ${now.toLocaleTimeString()}`;\n       }\n      }\n      function openIconPicker(callback) {\n       iconPickerCallback = callback;\n       const modal = document.getElementById('icon-picker-modal');\n       modal.classList.add('active');\n       const searchInput = document.getElementById('icon-search');\n       searchInput.style.display = 'none';\n       loadIconsForCurrentLibrary();\n      }\n      function closeIconPicker() {\n       const modal = document.getElementById('icon-picker-modal');\n       modal.classList.remove('active');\n       iconPickerCallback = null;\n      }\n      async function loadIconsForCurrentLibrary() {\n       const body = document.getElementById('icon-picker-body');\n       const libNames = {\n        mdi: 'MDI (Material Design Icons)',\n        simple: 'Simple Icons',\n        selfhst: 'selfh.st/icons'\n       };\n       body.innerHTML = `<div style=\"padding: 20px;\"><p style=\"color: var(--text-soft); margin-bottom: 15px; text-align: center;\">Search ${libNames[IconLibrary.currentLibrary]}:</p><input type=\"text\" id=\"icon-search-field\" placeholder=\"Search icons...\" style=\"width: 100%; padding: 12px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 6px; color: var(--text-main); font-size: 16px; margin-bottom: 20px;\"><div id=\"icon-grid-container\" style=\"max-height: 400px; overflow-y: auto;\"><div style=\"text-align: center; color: var(--text-soft); padding: 40px;\">Loading icons...</div></div></div>`;\n       const searchField = document.getElementById('icon-search-field');\n       const gridContainer = document.getElementById('icon-grid-container');\n       await IconLibrary.loadLibraryIndex(IconLibrary.currentLibrary);\n       const renderIcons = (icons) => {\n        if (!icons || icons.length === 0) {\n         gridContainer.innerHTML = '<div style=\"text-align: center; color: var(--text-soft); padding: 40px;\">No icons found</div>';\n         return;\n        }\n        const grid = document.createElement('div');\n        grid.style.cssText = 'display: grid; grid-template-columns: repeat(auto-fill, minmax(100px, 1fr)); gap: 15px; padding: 10px;';\n        icons.forEach(icon => {\n         const item = document.createElement('div');\n         item.style.cssText = 'display: flex; flex-direction: column; align-items: center; gap: 8px; padding: 15px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 6px; cursor: pointer; transition: all 0.2s;';\n         item.onmouseover = () => {\n          item.style.background = 'var(--panel)';\n          item.style.borderColor = 'var(--accent)';\n         };\n         item.onmouseout = () => {\n          item.style.background = 'var(--panel-alt)';\n          item.style.borderColor = 'var(--edge-main)';\n         };\n         const iconPreview = document.createElement('div');\n         iconPreview.style.cssText = 'width: 48px; height: 48px; display: flex; align-items: center; justify-content: center;';\n         iconPreview.innerHTML = '<div style=\"color: var(--text-soft); font-size: 12px;\">...</div>';\n         IconLibrary.getIcon(IconLibrary.currentLibrary, icon.name).then(svg => {\n          if (svg) {\n           const parser = new DOMParser();\n           const doc = parser.parseFromString(svg, 'image/svg+xml');\n           const svgEl = doc.querySelector('svg');\n           if (svgEl) {\n            svgEl.setAttribute('width', '48');\n            svgEl.setAttribute('height', '48');\n            svgEl.style.fill = 'var(--text-main)';\n            iconPreview.innerHTML = '';\n            iconPreview.appendChild(svgEl);\n           }\n          }\n         });\n         const name = document.createElement('div');\n         name.textContent = icon.displayName || icon.name;\n         name.style.cssText = 'font-size: 11px; color: var(--text-soft); text-align: center; word-break: break-word; max-width: 100%;';\n         item.appendChild(iconPreview);\n         item.appendChild(name);\n         item.addEventListener('click', async () => {\n          const svg = await IconLibrary.getIcon(IconLibrary.currentLibrary, icon.name);\n          if (iconPickerCallback && svg) {\n           iconPickerCallback({\n            library: IconLibrary.currentLibrary,\n            name: icon.name,\n            svg: svg\n           });\n          }\n          closeIconPicker();\n         });\n         grid.appendChild(item);\n        });\n        gridContainer.innerHTML = '';\n        gridContainer.appendChild(grid);\n       };\n       gridContainer.innerHTML = '<div style=\"text-align: center; color: var(--text-soft); padding: 40px;\">Type to search icons</div>';\n       let searchTimeout;\n       searchField.addEventListener('input', (e) => {\n        clearTimeout(searchTimeout);\n        const query = e.target.value.trim();\n        searchTimeout = setTimeout(() => {\n         if (!query) {\n          gridContainer.innerHTML = '<div style=\"text-align: center; color: var(--text-soft); padding: 40px;\">Type to search icons</div>';\n          return;\n         }\n         const results = IconLibrary.searchIcons(IconLibrary.currentLibrary, query);\n         renderIcons(results);\n        }, 300);\n       });\n       searchField.focus();\n      }\n      async function displayIcons(icons) {\n       const body = document.getElementById('icon-picker-body');\n       const grid = document.createElement('div');\n       grid.className = 'icon-grid';\n       for (const icon of icons) {\n        const item = document.createElement('div');\n        item.className = 'icon-item';\n        const svg = await IconLibrary.getIcon(icon.library, icon.name);\n        if (svg) {\n         const parser = new DOMParser();\n         const doc = parser.parseFromString(svg, 'image/svg+xml');\n         const svgEl = doc.querySelector('svg');\n         if (svgEl) {\n          item.innerHTML = svgEl.outerHTML;\n         }\n        } else {\n         item.innerHTML = '<svg width = \"32\" height = \"32\"><rect width = \"32\" height = \"32\" fill = \"currentColor\"/> </svg>';\n        }\n        const name = document.createElement('div');\n        name.className = 'icon-item-name';\n        name.textContent = icon.name;\n        item.appendChild(name);\n        item.addEventListener('click', () => {\n         if (iconPickerCallback) {\n          iconPickerCallback({\n           library: icon.library,\n           name: icon.name,\n           svg: svg\n          });\n         }\n         closeIconPicker();\n        });\n        grid.appendChild(item);\n       }\n       body.innerHTML = '';\n       body.appendChild(grid);\n      }\n      window.addEventListener('DOMContentLoaded', () => {\n       document.querySelectorAll('.icon-picker-tab').forEach(tab => {\n        tab.addEventListener('click', () => {\n         document.querySelectorAll('.icon-picker-tab').forEach(t => t.classList.remove('active'));\n         tab.classList.add('active');\n         IconLibrary.currentLibrary = tab.dataset.library;\n         loadIconsForCurrentLibrary();\n        });\n       });\n       document.getElementById('icon-picker-cancel').addEventListener('click', closeIconPicker);\n       document.getElementById('icon-picker-modal').addEventListener('click', (e) => {\n        if (e.target.id === 'icon-picker-modal') {\n         closeIconPicker();\n        }\n       });\n      });\n      let textDrawMode = false;\n      const BASE_NODE_DATA = JSON.parse(document.getElementById(\"nodes-json\").textContent);\n      let currentNodeId = \"host\";\n      let currentEdgeId = null;\n      let currentStyleScope = \"all\";\n      let NODE_DATA = {};\n      let EDGE_DATA = {\n       list: []\n      };\n      let currentRectId = null;\n      let currentTextId = null;\n      let rectDrawMode = false;\n      let RECT_DATA = {\n       list: []\n      };\n      let TEXT_DATA = {\n       list: []\n      };\n      let EDGE_LEGEND = {};\n      let savedPositions = {};\n      let savedSizes = {};\n      let savedStyles = {};\n      let legendCollapsed = false;\n      let minimapCollapsed = false;\n      let drawToolbarCollapsed = false;\n      let currentView = {\n       mode: \"topology\",\n       rackId: null\n      };\n      let savedTopologyView = null;\n      let activeLayers = new Set([\"physical\", \"logical\", \"security\", \"application\"]);\n      let topologyToolbarCollapsed = false;\n      let legendMiniBtn = null;\n      let minimapMiniBtn = null;\n      let drawToolbarMiniBtn = null;\n      let topologyToolbarMiniBtn = null;\n      let autoPingEnabled = false;\n      let autoPingInterval = 30;\n      let autoPingTimer = null;\n      let autoPingCountdown = null;\n      let autoPingSecondsRemaining = 0;\n      const ROLLBACK_STORAGE_KEY = \"theonefile_rollback_history\";\n      let rollbackVersions = [];\n      const MAX_ROLLBACK_VERSIONS = 50;\n      let currentRollbackIndex = -1;\n      const AUDIT_STORAGE_KEY = \"theonefile_audit_log\";\n      let auditLog = [];\n      const MAX_AUDIT_ENTRIES = 1000;\n      let savedStyleSets = [];\n      let documentTabs = [{\n        id: \"main\",\n        name: \"Main Topology\",\n        nodes: {},\n        edges: { list: [] },\n        positions: {},\n        sizes: {},\n        styles: {},\n        legend: {},\n        rects: { list: [] },\n        texts: { list: [] },\n      pageState: null\n      }];\n      let currentTabIndex = 0;\n      let encryptedSections = {};\n      let undoStack = [];\n      let redoStack = [];\n      const MAX_UNDO_STACK = 50;\n      let forgeDebounceTimer = null;\n      let forgeImmediate = false;\n      let currentSearchQuery = \"\";\n      let currentSearchResults = [];\n      const AUTOSAVE_DB_NAME = \"TheOneFileNetworkeningAutosave\";\n      const AUTOSAVE_STORE_NAME = \"drafts\";\n      const AUTOSAVE_KEY = \"currentDraft\";\n      const AUTOSAVE_INTERVAL = 30000;\n      let autosaveTimer = null;\n      let lastAutosaveTime = 0;\n      let selectedNodes = new Set();\n      let selectedEdges = new Set();\n      let selectedRects = new Set();\n      let selectedTexts = new Set();\n      let isSelecting = false;\n      let selectionStart = null;\n      let preDragSelectedNodes = new Set();\n      let preDragSelectedEdges = new Set();\n      let preDragSelectedRects = new Set();\n      let preDragSelectedTexts = new Set();\n      let selectionRect = null;\n      let isDraggingSelection = false;\n      let dragSelectionStart = null;\n      let selectionBoxStyle = {\n       fillColor: \"#4fd1c5\",\n       fillOpacity: 0.1,\n       strokeColor: \"#4fd1c5\",\n       strokeWidth: 2,\n       strokeDasharray: \"5,5\"\n      };\n      let clipboard = null;\n\t  function escapeHtml(str) {\n       if (!str) return '';\n       return String(str).replace(/[&<>\"']/g, c => ({\n        '&': '&amp;', '<': '&lt;', '>': '&gt;', '\"': '&quot;', \"'\": '&#39;'\n       }[c]));\n      }\n      const MobileManager = {\n        isMobile: false,\n        detect() {\n          if (navigator.userAgentData?.mobile === true) {\n            this.isMobile = true;\n            return true;\n          }\n          const coarse = matchMedia(\"(pointer: coarse)\").matches;\n          const width = window.innerWidth <= 900;\n          const portrait = matchMedia(\"(orientation: portrait)\").matches;\n          this.isMobile = coarse;\n          return this.isMobile;\n        },\n        applyInitialCollapse() {\n          if (!this.isMobile) return;\n          legendCollapsed = true;\n          minimapCollapsed = true;\n          drawToolbarCollapsed = true;\n          topologyToolbarCollapsed = true;\n          if (typeof updateLegendVisibility === \"function\") updateLegendVisibility();\n          if (typeof updateMinimapVisibility === \"function\") updateMinimapVisibility();\n          if (typeof updateDrawToolbarVisibility === \"function\") updateDrawToolbarVisibility();\n          if (typeof updateTopologyToolbarVisibility === \"function\") updateTopologyToolbarVisibility();\n        },\n        updateBulkToolbar() {\n          const desktop = document.getElementById(\"bulk-toolbar\");\n          const mobile = document.getElementById(\"bulk-toolbar-mobile\");\n          if (!desktop || !mobile) return;\n          if (typeof isViewOnly === 'function' && isViewOnly()) {\n           desktop.style.display = \"none\";\n           mobile.style.display = \"none\";\n           return;\n          }\n          if (typeof selectedNodes === 'undefined' || selectedNodes.size === 0) {\n           desktop.style.display = \"none\";\n           mobile.style.display = \"none\";\n           return;\n          }\n          const isVisible = desktop.style.display !== \"none\" || mobile.style.display !== \"none\";\n          if (this.isMobile) {\n            desktop.style.display = \"none\";\n            if (isVisible) {\n              mobile.style.display = \"flex\";\n            }\n          } else {\n            mobile.style.display = \"none\";\n            if (isVisible) {\n              desktop.style.display = \"flex\";\n            }\n          }\n        },\n        updateCanvasHint() {\n      const hint = document.getElementById(\"canvas-hint\");\n      if (!hint) return;\n      if (PAGE_STATE.canvasHintText !== undefined && PAGE_STATE.canvasHintText !== \"\") {\n        hint.textContent = PAGE_STATE.canvasHintText;\n        return;\n      }\n      const items = this.isMobile\n      ? [\n        \"Pinch to zoom\",\n        \"Drag to pan\",\n        \"Add node from top menu\",\n        \"Double tap to clone and align\",\n        \"Double tap to select multiple\",\n        \"You have the power\",\n        \"Your time is NOW!\",\n      ]\n      : [\n        \"Scroll to zoom\",\n        \"Drag to pan\",\n        \"Right click to clone and align\",\n        \"Right click to select multiple\",\n        \"You have the power\",\n        \"Your time is NOW!\",\n      ];\n      const list = document.createElement(\"ul\");\n      for (const item of items) {\n      const li = document.createElement(\"li\");\n      li.textContent = item;\n      list.appendChild(li);\n      }\n      hint.replaceChildren(list);\n      },\n        autoSelectStyleScope() {\n          const scope = document.getElementById(\"style-scope\");\n          if (!scope) return;\n          if (this.isMobile) scope.value = \"mobile\";\n        },\n        updateToolbarStack() {\n          if (!this.isMobile) return;\n          const draw = document.getElementById(\"draw-toolbar\");\n          const topo = document.getElementById(\"topology-toolbar\");\n          if (!draw || !topo) return;\n          const h = draw.getBoundingClientRect().height;\n          document.documentElement.style.setProperty(\"--draw-toolbar-height\", h + \"px\");\n        },\n        updateLayoutControls() {\n          const sidebarRow = document.getElementById(\"sidebar-width-row\");\n          const footerRow = document.getElementById(\"mobile-footer-row\");\n          if (sidebarRow && footerRow) {\n            if (this.isMobile) {\n              sidebarRow.style.display = \"none\";\n              footerRow.style.display = \"flex\";\n            } else {\n              sidebarRow.style.display = \"flex\";\n              footerRow.style.display = \"none\";\n            }\n          }\n        },\n        applyAll() {\n          this.detect();\n          this.applyInitialCollapse();\n          this.updateBulkToolbar();\n          this.updateCanvasHint();\n          this.autoSelectStyleScope();\n          this.updateToolbarStack();\n          this.updateLayoutControls();\n        }\n      };\n      function isMobileDevice() {\n        return MobileManager.isMobile;\n      }\n      function ensureLegendMiniButton() {\n\t\tif (legendMiniBtn) return legendMiniBtn;\n\t\tconst handleClick = (e) => {\n\t\t\te.stopPropagation();\n\t\t\te.preventDefault();\n\t\t\tlegendCollapsed = false;\n\t\t\tupdateLegendVisibility();\n\t\t};\n\t\tconst preventTouch = (e) => { e.preventDefault(); };\n\t\tconst existing = document.getElementById(\"edge-legend-mini\");\n\t\tif (existing) {\n\t\t\texisting.addEventListener(\"click\", handleClick);\n\t\t\texisting.addEventListener(\"touchstart\", preventTouch, { passive: false });\n\t\t\texisting.addEventListener(\"touchend\", handleClick);\n\t\t\tlegendMiniBtn = existing;\n\t\t\treturn existing;\n\t\t}\n\t\tconst panel = document.querySelector(\".topology-panel\");\n\t\tif (!panel) return null;\n\t\t   const btn = document.createElement(\"button\");\n\t\t   btn.type = \"button\";\n\t\t   btn.id = \"edge-legend-mini\";\n\t\t   btn.className = \"legend-mini-btn\";\n\t\t   btn.textContent = \"Legend\";\n\t\t   btn.style.bottom = \"10px\";\n\t\t   btn.addEventListener(\"click\", handleClick);\n\t\t   btn.addEventListener(\"touchstart\", preventTouch, { passive: false });\n\t\t   btn.addEventListener(\"touchend\", handleClick);\n\t\t   panel.appendChild(btn);\n\t\t   legendMiniBtn = btn;\n\t\t   return btn;\n\t\t}\n\t\t\t function ensureMinimapMiniButton() {\n\t\t   if (minimapMiniBtn) return minimapMiniBtn;\n\t\t   const handleClick = (e) => {\n\t\t\te.stopPropagation();\n\t\t\te.preventDefault();\n\t\t\tminimapCollapsed = false;\n\t\t\tupdateMinimapVisibility();\n\t\t   };\n\t\t   const preventTouch = (e) => { e.preventDefault(); };\n\t\t   const existing = document.getElementById(\"minimap-mini\");\n\t\t   if (existing) {\n\t\t\texisting.addEventListener(\"click\", handleClick);\n\t\t\texisting.addEventListener(\"touchstart\", preventTouch, { passive: false });\n\t\t\texisting.addEventListener(\"touchend\", handleClick);\n\t\t\tminimapMiniBtn = existing;\n\t\t\treturn existing;\n\t\t   }\n\t\t   const panel = document.querySelector(\".topology-panel\");\n\t\t   if (!panel) return null;\n\t\t   const btn = document.createElement(\"button\");\n\t\t   btn.type = \"button\";\n\t\t   btn.id = \"minimap-mini\";\n\t\t   btn.className = \"legend-mini-btn\";\n\t\t   btn.textContent = \"Map\";\n\t\t   btn.style.right = \"10px\";\n\t\t   btn.style.left = \"auto\";\n\t\t   btn.style.bottom = \"10px\";\n\t\t   btn.addEventListener(\"click\", handleClick);\n\t\t   btn.addEventListener(\"touchstart\", preventTouch, { passive: false });\n\t\t   btn.addEventListener(\"touchend\", handleClick);\n\t\t   panel.appendChild(btn);\n\t\t   minimapMiniBtn = btn;\n\t\t   return btn;\n\t\t}\n\t\t\t  function ensureDrawToolbarMiniButton() {\n\t\t   if (drawToolbarMiniBtn) return drawToolbarMiniBtn;\n\t\t   const handleClick = (e) => {\n\t\t\te.stopPropagation();\n\t\t\te.preventDefault();\n\t\t\tdrawToolbarCollapsed = false;\n\t\t\tupdateDrawToolbarVisibility();\n\t\t   };\n\t\t   const preventTouch = (e) => { e.preventDefault(); };\n\t\t   const existing = document.getElementById(\"draw-toolbar-mini\");\n\t\t   if (existing) {\n\t\t\texisting.addEventListener(\"click\", handleClick);\n\t\t\texisting.addEventListener(\"touchstart\", preventTouch, { passive: false });\n\t\t\texisting.addEventListener(\"touchend\", handleClick);\n\t\t\tdrawToolbarMiniBtn = existing;\n\t\t\treturn existing;\n\t\t   }\n\t\t   const panel = document.querySelector(\".topology-panel\");\n\t\t   if (!panel) return null;\n\t\t   const btn = document.createElement(\"button\");\n\t\t   btn.type = \"button\";\n\t\t   btn.id = \"draw-toolbar-mini\";\n\t\t   btn.className = \"legend-mini-btn\";\n\t\t   btn.textContent = \"Draw\";\n\t\t   btn.style.top = \"10px\";\n\t\t   btn.style.left = \"10px\";\n\t\t   btn.style.right = \"auto\";\n\t\t   btn.addEventListener(\"click\", handleClick);\n\t\t   btn.addEventListener(\"touchstart\", preventTouch, { passive: false });\n\t\t   btn.addEventListener(\"touchend\", handleClick);\n\t\t   panel.appendChild(btn);\n\t\t   drawToolbarMiniBtn = btn;\n\t\t   return btn;\n\t\t}\n\t\t  function ensureTopologyToolbarMiniButton() {\n\t   if (topologyToolbarMiniBtn) return topologyToolbarMiniBtn;\n\t   const handleClick = (e) => {\n\t\te.stopPropagation();\n\t\te.preventDefault();\n\t\ttopologyToolbarCollapsed = false;\n\t\tupdateTopologyToolbarVisibility();\n\t   };\n\t   const preventTouch = (e) => { e.preventDefault(); };\n\t   const existing = document.getElementById(\"topology-toolbar-mini\");\n\t   if (existing) {\n\t\texisting.addEventListener(\"click\", handleClick);\n\t\texisting.addEventListener(\"touchstart\", preventTouch, { passive: false });\n\t\texisting.addEventListener(\"touchend\", handleClick);\n\t\ttopologyToolbarMiniBtn = existing;\n\t\treturn existing;\n\t   }\n\t   const panel = document.querySelector(\".topology-panel\");\n\t   if (!panel) return null;\n\t   const btn = document.createElement(\"button\");\n\t   btn.type = \"button\";\n\t   btn.id = \"topology-toolbar-mini\";\n\t   btn.className = \"legend-mini-btn\";\n\t   btn.textContent = \"Add Line\";\n\t   btn.style.top = \"10px\";\n\t   btn.style.left = \"auto\";\n\t   btn.style.right = \"40px\";\n\t   btn.addEventListener(\"click\", handleClick);\n\t   btn.addEventListener(\"touchstart\", preventTouch, { passive: false });\n\t   btn.addEventListener(\"touchend\", handleClick);\n\t   panel.appendChild(btn);\n\t   topologyToolbarMiniBtn = btn;\n\t   return btn;\n\t}\n      function updateToolbarStack() {\n       if (!isMobileDevice()) return;\n       const draw = document.getElementById(\"draw-toolbar\");\n       const topo = document.getElementById(\"topology-toolbar\");\n       if (!draw || !topo) return;\n       const h = draw.getBoundingClientRect().height;\n       document.documentElement.style.setProperty(\"--draw-toolbar-height\", h + \"px\");\n      }\n      window.addEventListener(\"resize\", updateToolbarStack);\n      window.addEventListener(\"DOMContentLoaded\", updateToolbarStack);\n      updateToolbarStack();\n      function updateLegendVisibility() {\n       const legend = document.getElementById(\"edge-legend\");\n       const mini = ensureLegendMiniButton();\n       if (!legend || !mini) return;\n       const hasItems = legend.querySelectorAll(\".legend-item\").length > 0;\n       if (!hasItems) {\n        legend.style.display = \"none\";\n        mini.style.display = \"none\";\n        return;\n       }\n       if (legendCollapsed) {\n        legend.style.display = \"none\";\n        mini.style.display = \"inline-flex\";\n       } else {\n        legend.style.display = \"flex\";\n        mini.style.display = \"none\";\n       }\n      }\n      function updateMinimapVisibility() {\n       const wrapper = document.getElementById(\"minimap-zoom-wrapper\");\n       const mini = ensureMinimapMiniButton();\n       if (!wrapper || !mini) return;\n       if (minimapCollapsed) {\n        wrapper.style.display = \"none\";\n        mini.style.display = \"inline-flex\";\n       } else {\n        wrapper.style.display = \"block\";\n        mini.style.display = \"none\";\n       }\n      }\n      function updateDrawToolbarVisibility() {\n       const toolbar = document.getElementById(\"draw-toolbar\");\n       const mini = ensureDrawToolbarMiniButton();\n       if (!toolbar || !mini) return;\n       if (typeof isViewOnly === 'function' && isViewOnly()) {\n        toolbar.style.setProperty('display', 'none', 'important');\n        mini.style.display = \"none\";\n        return;\n       }\n       if (drawToolbarCollapsed) {\n        toolbar.style.setProperty('display', 'none', 'important');\n        mini.style.display = \"inline-flex\";\n       } else {\n      if (isMobileDevice()) {\n        toolbar.style.setProperty('display', 'grid', 'important');\n        mini.style.display = \"none\";\n        } else {\n        toolbar.style.setProperty('display', 'flex', 'important');\n        mini.style.display = \"none\";\n      }\n       }\n      }\n      function u8ToBase64(u8) {\n      let binary = \"\";\n      for (let i = 0; i < u8.length; i++) binary += String.fromCharCode(u8[i]);\n      return btoa(binary);\n      }\n      function base64ToU8(base64) {\n      const binary = atob(base64);\n      const bytes = new Uint8Array(binary.length);\n      for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);\n      return bytes;\n      }\n      function openAutosaveDB() {\n       return new Promise((resolve, reject) => {\n        const request = indexedDB.open(AUTOSAVE_DB_NAME, 1);\n        request.onerror = () => reject(request.error);\n        request.onsuccess = () => resolve(request.result);\n        request.onupgradeneeded = (e) => {\n         const db = e.target.result;\n         if (!db.objectStoreNames.contains(AUTOSAVE_STORE_NAME)) {\n          db.createObjectStore(AUTOSAVE_STORE_NAME, { keyPath: \"id\" });\n         }\n        };\n       });\n      }\n      async function saveToIndexedDB() {\n       try {\n        const db = await openAutosaveDB();\n        const currentTab = documentTabs[currentTabIndex];\n        currentTab.nodes = JSON.parse(JSON.stringify(NODE_DATA));\n        currentTab.edges = JSON.parse(JSON.stringify(EDGE_DATA));\n        currentTab.positions = JSON.parse(JSON.stringify(savedPositions));\n        currentTab.sizes = JSON.parse(JSON.stringify(savedSizes));\n        currentTab.styles = JSON.parse(JSON.stringify(savedStyles));\n        currentTab.legend = JSON.parse(JSON.stringify(EDGE_LEGEND));\n        currentTab.rects = JSON.parse(JSON.stringify(RECT_DATA));\n        currentTab.texts = JSON.parse(JSON.stringify(TEXT_DATA));\n        currentTab.pageState = JSON.parse(JSON.stringify(PAGE_STATE));\n        const state = {\n         id: AUTOSAVE_KEY,\n         timestamp: Date.now(),\n         pageTitle: document.getElementById(\"page-title\")?.textContent || \"The One File\",\n         nodeData: NODE_DATA,\n         edgeData: EDGE_DATA,\n         rectData: RECT_DATA,\n         textData: TEXT_DATA,\n         edgeLegend: EDGE_LEGEND,\n         zoneLegend: ZONE_LEGEND,\n         zonePresets: ZONE_PRESETS,\n         nodePositions: savedPositions,\n         nodeSizes: savedSizes,\n         nodeStyles: savedStyles,\n         page: PAGE_STATE,\n         canvas: {\n          zoom: canvasState.zoom,\n          panX: canvasState.panX,\n          panY: canvasState.panY,\n         },\n         savedTopologyView: savedTopologyView,\n         documentTabs: documentTabs,\n         currentTabIndex: currentTabIndex,\n         encryptedSections: encryptedSections,\n         auditLog: auditLog,\n\t\t savedStyleSets: savedStyleSets,\n\t\t selectedTheme: document.getElementById(\"theme-preset\")?.value || \"defaulted\",\n        };\n        const tx = db.transaction(AUTOSAVE_STORE_NAME, \"readwrite\");\n        const store = tx.objectStore(AUTOSAVE_STORE_NAME);\n        store.put(state);\n        await new Promise((resolve, reject) => {\n         tx.oncomplete = resolve;\n         tx.onerror = () => reject(tx.error);\n        });\n        lastAutosaveTime = Date.now();\n        console.log(\"Auto-saved to IndexedDB at\", new Date().toLocaleTimeString());\n       } catch (e) {\n        console.warn(\"Auto-save failed:\", e);\n       }\n      }\n      async function loadFromIndexedDB() {\n       try {\n        const db = await openAutosaveDB();\n        const tx = db.transaction(AUTOSAVE_STORE_NAME, \"readonly\");\n        const store = tx.objectStore(AUTOSAVE_STORE_NAME);\n        const request = store.get(AUTOSAVE_KEY);\n        return new Promise((resolve, reject) => {\n         request.onsuccess = () => resolve(request.result);\n         request.onerror = () => reject(request.error);\n        });\n       } catch (e) {\n        console.warn(\"Load from IndexedDB failed:\", e);\n        return null;\n       }\n      }\n      async function clearAutosave() {\n       try {\n        const db = await openAutosaveDB();\n        const tx = db.transaction(AUTOSAVE_STORE_NAME, \"readwrite\");\n        const store = tx.objectStore(AUTOSAVE_STORE_NAME);\n        store.delete(AUTOSAVE_KEY);\n       } catch (e) {\n        console.warn(\"Clear autosave failed:\", e);\n       }\n      }\n      function startAutosave() {\n       if (autosaveTimer) clearInterval(autosaveTimer);\n       autosaveTimer = setInterval(() => {\n        if (Object.keys(NODE_DATA).length > 0 || EDGE_DATA.list.length > 0 || documentTabs.length > 1) {\n         saveToIndexedDB();\n        }\n       }, AUTOSAVE_INTERVAL);\n      }\n      async function checkForAutosaveRecovery() {\n       try {\n        const saved = await loadFromIndexedDB();\n        if (saved && saved.timestamp) {\n         const age = Date.now() - saved.timestamp;\n         const hasCurrentData = Object.keys(NODE_DATA).length > 0 || EDGE_DATA.list.length > 0;\n         const savedNodeCount = saved.nodeData ? Object.keys(saved.nodeData).length : 0;\n         const savedTabCount = saved.documentTabs ? saved.documentTabs.length : 1;\n         if (age < 300000 && (savedNodeCount > 0 || savedTabCount > 1)) {\n          const savedDate = new Date(saved.timestamp).toLocaleString();\n          const tabInfo = savedTabCount > 1 ? ` across ${savedTabCount} tabs` : \"\";\n          if (!hasCurrentData || confirm(`Recover unsaved work from ${savedDate}?\\n(${savedNodeCount} nodes${tabInfo})\\n\\nClick OK to recover, Cancel to start fresh.`)) {\n           if (!hasCurrentData || confirm(\"This will replace current data. Continue?\")) {\n            NODE_DATA = saved.nodeData || {};\n            EDGE_DATA = saved.edgeData || { list: [] };\n            RECT_DATA = saved.rectData || { list: [] };\n            TEXT_DATA = saved.textData || { list: [] };\n            EDGE_LEGEND = saved.edgeLegend || {};\n            ZONE_LEGEND = saved.zoneLegend || {};\n            ZONE_PRESETS = saved.zonePresets || {};\n            loadCustomPresets();\n            savedPositions = saved.nodePositions || {};\n            savedSizes = saved.nodeSizes || {};\n            savedStyles = saved.nodeStyles || {};\n            if (saved.page) PAGE_STATE = Object.assign({}, DEFAULT_PAGE_STATE, saved.page);\n            if (saved.canvas) {\n             canvasState.zoom = saved.canvas.zoom || 1;\n             canvasState.panX = saved.canvas.panX || 0;\n             canvasState.panY = saved.canvas.panY || 0;\n            }\n            if (saved.savedTopologyView) savedTopologyView = saved.savedTopologyView;\n            if (saved.savedStyleSets) savedStyleSets = saved.savedStyleSets;\n            if (saved.selectedTheme) {\n              rebuildThemeDropdown();\n              document.getElementById(\"theme-preset\").value = saved.selectedTheme;\n              updateDeleteButton();\n            }\n            if (saved.documentTabs) documentTabs = saved.documentTabs;\n            if (saved.currentTabIndex !== undefined) currentTabIndex = saved.currentTabIndex;\n            if (saved.encryptedSections) encryptedSections = saved.encryptedSections;\n            if (saved.auditLog) auditLog = saved.auditLog;\n            if (saved.pageTitle) {\n             const titleEl = document.getElementById(\"page-title\");\n             if (titleEl) titleEl.textContent = saved.pageTitle;\n            }\n            wieldThePower();\n            forgeTheTopology();\n            updateViewBox();\n            console.log(\"Recovered from autosave:\", savedNodeCount, \"nodes,\", savedTabCount, \"tabs\");\n            return true;\n           }\n          }\n         }\n        }\n       } catch (e) {\n        console.warn(\"Autosave recovery check failed:\", e);\n       }\n       return false;\n      }\n      function updateTopologyToolbarVisibility() {\n       const toolbar = document.getElementById(\"topology-toolbar\");\n       const mini = ensureTopologyToolbarMiniButton();\n       if (!toolbar || !mini) return;\n       if (typeof isViewOnly === 'function' && isViewOnly()) {\n        toolbar.style.setProperty('display', 'none', 'important');\n        mini.style.display = \"none\";\n        return;\n       }\n       const hasSelectedNode = currentNodeId !== null;\n       if (!hasSelectedNode) {\n        toolbar.style.setProperty('display', 'none', 'important');\n        mini.style.display = \"none\";\n       } else if (topologyToolbarCollapsed) {\n        toolbar.style.setProperty('display', 'none', 'important');\n        mini.style.display = \"inline-flex\";\n       } else {\n      if (isMobileDevice()) {\n        toolbar.style.setProperty('display', 'grid', 'important');\n        mini.style.display = \"none\";\n        } else {\n        toolbar.style.setProperty('display', 'flex', 'important');\n        mini.style.display = \"none\";\n      }\n       }\n      }\n      const DEFAULT_CANVAS_HINT = document.getElementById(\"canvas-hint\")?.innerHTML || \"\";\n      const DEFAULT_PAGE_STATE = {\n       title: \"The One File: The Networkening\",\n       background: \"\",\n       topbarBg: \"rgba(9, 12, 20, 0.9)\",\n       topbarBorder: \"#1f2533\",\n       panel: \"#0b0e13\",\n       panelAlt: \"#10141b\",\n       accent: \"#4fd1c5\",\n       sidebarBg: \"#10141b\",\n       btnBg: \"#0b0e13\",\n       btnText: \"#e2e8f0\",\n       tagFill: \"#1e293b\",\n       tagText: \"#e2e8f0\",\n       tagBorder: \"#475569\",\n       inputBg: \"#0b0e13\",\n       inputText: \"#e2e8f0\",\n       inputBorder: \"#1f2937\",\n       inputFont: \"Inter, system-ui, sans-serif\",\n       inputFontSize: 14,\n       toolbarBg: \"#0f172a\",\n       toolbarBorder: \"#1f2937\",\n       toolbarText: \"#94a3b8\",\n       toolbarBtnBg: \"#0b0e13\",\n       toolbarBtnText: \"#e2e8f0\",\n       minimapDots: \"#94a3b8\",\n       canvasHintEnabled: true,\n       canvasHintText: \"\",\n       canvasHintBg: \"#0f172a\",\n       canvasHintColor: \"#94a3b8\",\n       danger: \"#f56565\",\n       textMain: \"#e2e8f0\",\n       textSoft: \"#94a3b8\",\n       topbarHeight: 52,\n       sidebarWidth: 350,\n       mobileFooterHeight: 40,\n       sidebarCollapsed: false,\n       nodeFill: \"#1e293b\",\n       nodeStroke: \"#475569\",\n       nodeTitle: \"#e2e8f0\",\n       nodeSub: \"#94a3b8\",\n       nodeTitleSize: 18,\n       nodeSubSize: 13,\n       nodeFont: \"Inter, system-ui, sans-serif\",\n       defaultEdge: \"#475569\",\n       selectionHandle: \"#f59e0b\",\n       selectionHandleSize: 8,\n       groupIndicator: \"#4fd1c5\",\n       canvasGradientTop: \"#1e2532\",\n       canvasGradientBottom: \"#050608\",\n       canvasBorder: \"#475569\",\n       canvasGrid: \"#475569\",\n       canvasGridSize: 50,\n       canvasGridEnabled: true,\n       rackFrameFill: \"#0f172a\",\n       rackFrameStroke: \"#4fd1c5\",\n       rackLineColor: \"#475569\",\n       rackTextColor: \"#4fd1c5\",\n       rackGridEnabled: true,\n\t   viewOnly: false,\n\t   defaultEdgeRouting: \"curved\",\n\t   animateConnections: false,\n\t   animationStyle: \"arrows\",\n\t   animationDirection: \"all\",\n\t   animationSpeed: 1.5,\n      };\n      let PAGE_STATE = Object.assign({}, DEFAULT_PAGE_STATE);\n\t  const FOV_ANIMATION_START = Date.now();\n\t  let ZONES_VISIBLE = true;\nconst ANIM_SETTINGS = {\n  masterAnim: true,\n  masterZones: true,\n  animTypes: { sweep: true, pulse: true, rings: true, spin: true, connections: true },\n  animCategories: { camera: true, doorbell: true, motion: true, smoke: true, wifi: true, sensor: true, sprinkler: true, connections: true },\n  zoneCategories: { camera: true, doorbell: true, motion: true, smoke: true, wifi: true, sensor: true, sprinkler: true }\n};\nfunction getShapeCategory(shape) {\n  const map = {\n    \"camera\": \"camera\", \"cctv\": \"camera\", \"ptz-cam\": \"camera\",\n    \"doorbell\": \"doorbell\",\n    \"motion-sensor\": \"motion\", \"motion-detect\": \"motion\",\n    \"smoke-detector\": \"smoke\", \"smoke-alarm\": \"smoke\",\n    \"access-point\": \"wifi\", \"wifi\": \"wifi\", \"router\": \"wifi\", \"wifi-strong\": \"wifi\", \"wifi-weak\": \"wifi\",\n    \"sensor\": \"sensor\", \"iot\": \"sensor\",\n    \"sprinkler\": \"sprinkler\", \"sprinkler-arc\": \"sprinkler\"\n  };\n  return map[shape] || null;\n}\nfunction isAnimationAllowed(shape, animType) {\n  if (!ANIM_SETTINGS.masterAnim) return false;\n  if (!ANIM_SETTINGS.animTypes[animType]) return false;\n  const cat = getShapeCategory(shape);\n  if (cat && !ANIM_SETTINGS.animCategories[cat]) return false;\n  return true;\n}\nfunction isZoneAllowed(shape) {\n  if (!ANIM_SETTINGS.masterZones) return false;\n  const cat = getShapeCategory(shape);\n  if (cat && !ANIM_SETTINGS.zoneCategories[cat]) return false;\n  return true;\n}\nfunction applyAnimZoneSettings() {\n  document.querySelectorAll(\".fov-group\").forEach(g => {\n    const nodeEl = g.closest(\"g[data-node-id]\");\n    if (!nodeEl) return;\n    const nodeId = nodeEl.dataset.nodeId;\n    const node = NODE_DATA[nodeId];\n    if (!node) return;\n    const cat = getShapeCategory(node.shape);\n    const zoneVisible = ANIM_SETTINGS.masterZones && (!cat || ANIM_SETTINGS.zoneCategories[cat]);\n    g.style.display = zoneVisible ? \"\" : \"none\";\n    if (zoneVisible && node.fovAnimate) {\n      const animType = node.fovAnimationType || \"sweep\";\n      const animAllowed = ANIM_SETTINGS.masterAnim && ANIM_SETTINGS.animTypes[animType] && (!cat || ANIM_SETTINGS.animCategories[cat]);\n      g.style.animationPlayState = animAllowed ? \"running\" : \"paused\";\n      g.querySelectorAll(\"circle\").forEach(c => c.style.animationPlayState = animAllowed ? \"running\" : \"paused\");\n    }\n  });\n  const connAnimAllowed = ANIM_SETTINGS.masterAnim && ANIM_SETTINGS.animTypes.connections && ANIM_SETTINGS.animCategories.connections;\n  document.querySelectorAll(\".edge-arrow-forward, .edge-arrow-backward\").forEach(a => {\n    a.style.animationPlayState = connAnimAllowed ? \"running\" : \"paused\";\n  });\n}\nlet ZONE_LEGEND = {};\nlet ZONE_PRESETS = {};\nlet copiedZoneStyle = null;\n\nfunction hasCoverageZone(shape) {\n  const supportedShapes = [\n    \"camera\", \"cctv\", \"doorbell\",\n    \"motion-sensor\", \"smoke-detector\",\n    \"access-point\", \"wifi\", \"router\",\n    \"sensor\", \"iot\", \"sprinkler\"\n  ];\n  return supportedShapes.includes(shape);\n}\n\nfunction getCoverageDefaults(shape) {\n  const defaults = {\n    \"camera\": { angle: 90, distance: 150, animationType: \"sweep\" },\n    \"cctv\": { angle: 90, distance: 150, animationType: \"sweep\" },\n    \"doorbell\": { angle: 120, distance: 100, animationType: \"sweep\" },\n    \"motion-sensor\": { angle: 120, distance: 100, animationType: \"pulse\" },\n    \"smoke-detector\": { angle: 360, distance: 80, animationType: \"pulse\" },\n    \"access-point\": { angle: 360, distance: 200, animationType: \"rings\" },\n    \"wifi\": { angle: 360, distance: 200, animationType: \"rings\" },\n    \"router\": { angle: 360, distance: 200, animationType: \"rings\" },\n    \"sensor\": { angle: 90, distance: 100, animationType: \"pulse\" },\n    \"iot\": { angle: 90, distance: 100, animationType: \"pulse\" },\n    \"sprinkler\": { angle: 90, distance: 120, animationType: \"spin\" }\n  };\n  return defaults[shape] || { angle: 90, distance: 150, animationType: \"sweep\" };\n}\n\nfunction toggleAllZones() {\n  ANIM_SETTINGS.masterZones = !ANIM_SETTINGS.masterZones;\n  const masterCheckbox = document.getElementById(\"zone-master\");\n  if (masterCheckbox) masterCheckbox.checked = ANIM_SETTINGS.masterZones;\n  applyAnimZoneSettings();\n}\n\nfunction copyZoneStyle(nodeId) {\n  const node = NODE_DATA[nodeId];\n  if (!node) return false;\n  if (!hasCoverageZone(node.shape)) {\n    alert(\"This node type doesn't support coverage zones\");\n    return false;\n  }\n  copiedZoneStyle = {\n    fovEnabled: node.fovEnabled,\n    fovAngle: node.fovAngle,\n    fovDistance: node.fovDistance,\n    fovInnerRadius: node.fovInnerRadius,\n    fovRotation: node.fovRotation,\n    fovColor: node.fovColor,\n    fovOpacity: node.fovOpacity,\n    fovGradient: node.fovGradient,\n    fovBorderColor: node.fovBorderColor,\n    fovBorderWidth: node.fovBorderWidth,\n    fovBorderStyle: node.fovBorderStyle,\n    fovBorderOpacity: node.fovBorderOpacity,\n    fovLabel: node.fovLabel,\n    fovLabelPosition: node.fovLabelPosition,\n    fovLabelSize: node.fovLabelSize,\n    fovLabelColor: node.fovLabelColor,\n    fovLabelBold: node.fovLabelBold,\n    fovLabelBg: node.fovLabelBg,\n    fovLabelBgColor: node.fovLabelBgColor,\n    fovLabelOffsetX: node.fovLabelOffsetX,\n    fovLabelOffsetY: node.fovLabelOffsetY,\n    fovAnimate: node.fovAnimate,\n    fovAnimationType: node.fovAnimationType,\n    fovSweep: node.fovSweep,\n    fovSpeed: node.fovSpeed\n  };\n  return true;\n}\n\nfunction pasteZoneStyle(nodeId) {\n  if (!copiedZoneStyle) {\n    alert(\"No zone style copied. Copy a zone style first.\");\n    return false;\n  }\n  const node = NODE_DATA[nodeId];\n  if (!node) return false;\n  if (!hasCoverageZone(node.shape)) {\n    alert(\"This node type doesn't support coverage zones\");\n    return false;\n  }\n  pushUndo(\"paste zone style\");\n  Object.assign(node, copiedZoneStyle);\n  updateFovCone(nodeId);\n  return true;\n}\n\nfunction applyZonePreset(preset) {\n  if (!currentNodeId) return;\n  const presets = {\n    \"security-cam\": { fovEnabled: true, fovAngle: 90, fovDistance: 150, fovColor: \"#f59e0b\", fovOpacity: 20, fovAnimationType: \"sweep\", fovAnimate: false },\n    \"ptz-cam\": { fovEnabled: true, fovAngle: 60, fovDistance: 200, fovColor: \"#f59e0b\", fovOpacity: 25, fovAnimationType: \"sweep\", fovAnimate: true, fovSweep: 180, fovSpeed: 8 },\n    \"motion-detect\": { fovEnabled: true, fovAngle: 120, fovDistance: 100, fovColor: \"#10b981\", fovOpacity: 15, fovAnimationType: \"pulse\", fovAnimate: true, fovSpeed: 3 },\n    \"wifi-strong\": { fovEnabled: true, fovAngle: 360, fovDistance: 150, fovColor: \"#3b82f6\", fovOpacity: 10, fovAnimationType: \"rings\", fovAnimate: true, fovSpeed: 4 },\n    \"wifi-extended\": { fovEnabled: true, fovAngle: 360, fovDistance: 250, fovColor: \"#3b82f6\", fovOpacity: 8, fovGradient: true, fovAnimationType: \"rings\", fovAnimate: true, fovSpeed: 5 },\n    \"smoke-alarm\": { fovEnabled: true, fovAngle: 360, fovDistance: 80, fovColor: \"#ef4444\", fovOpacity: 15, fovAnimationType: \"pulse\", fovAnimate: false },\n    \"sprinkler-arc\": { fovEnabled: true, fovAngle: 90, fovDistance: 120, fovColor: \"#06b6d4\", fovOpacity: 20, fovAnimationType: \"spin\", fovAnimate: true, fovSpeed: 6 }\n  };\n  const allPresets = { ...presets, ...ZONE_PRESETS };\n  const settings = allPresets[preset];\n  if (!settings) return;\n  pushUndo(\"apply zone preset\");\n  Object.assign(NODE_DATA[currentNodeId], settings);\n  updateFovCone(currentNodeId);\n  claimTheImmortal(currentNodeId);\n}\n\nfunction saveCustomZonePreset() {\n  if (!currentNodeId) return;\n  const node = NODE_DATA[currentNodeId];\n  if (!node.fovEnabled) {\n    alert(\"Enable the zone first before saving as preset\");\n    return;\n  }\n  const name = prompt(\"Enter preset name:\");\n  if (!name || !name.trim()) return;\n  const presetId = \"custom-\" + name.toLowerCase().replace(/[^a-z0-9]+/g, \"-\");\n  ZONE_PRESETS[presetId] = {\n    fovEnabled: true,\n    fovAngle: node.fovAngle,\n    fovDistance: node.fovDistance,\n    fovInnerRadius: node.fovInnerRadius,\n    fovColor: node.fovColor,\n    fovOpacity: node.fovOpacity,\n    fovGradient: node.fovGradient,\n    fovBorderColor: node.fovBorderColor,\n    fovBorderWidth: node.fovBorderWidth,\n    fovBorderStyle: node.fovBorderStyle,\n    fovBorderOpacity: node.fovBorderOpacity,\n    fovAnimationType: node.fovAnimationType,\n    fovAnimate: node.fovAnimate,\n    fovSweep: node.fovSweep,\n    fovSpeed: node.fovSpeed\n  };\n  const select = document.getElementById(\"fov-preset\");\n  if (select && !select.querySelector(`option[value=\"${presetId}\"]`)) {\n    const opt = document.createElement(\"option\");\n    opt.value = presetId;\n    opt.textContent = name.trim() + \" ★\";\n    select.appendChild(opt);\n  }\n  alert(\"Preset saved: \" + name.trim());\n}\n\nfunction bulkCopyZoneStyle() {\n  if (selectedNodes.size === 0) {\n    alert(\"Select at least one node first\");\n    return;\n  }\n  const firstId = [...selectedNodes][0];\n  if (copyZoneStyle(firstId)) {\n    alert(\"Zone style copied from first selected node\");\n  }\n}\n\nfunction bulkPasteZoneStyle() {\n  if (selectedNodes.size === 0) {\n    alert(\"Select nodes first\");\n    return;\n  }\n  if (!copiedZoneStyle) {\n    alert(\"Copy a zone style first\");\n    return;\n  }\n  pushUndo(\"bulk paste zone style\");\n  let count = 0;\n  selectedNodes.forEach(id => {\n    const node = NODE_DATA[id];\n    if (node && hasCoverageZone(node.shape)) {\n      Object.assign(node, copiedZoneStyle);\n      updateFovCone(id);\n      count++;\n    }\n  });\n  forgeTheTopology();\n  alert(`Zone style pasted to ${count} node(s)`);\n}\n\nfunction bulkToggleZones() {\n  if (selectedNodes.size === 0) {\n    alert(\"Select nodes first\");\n    return;\n  }\n  pushUndo(\"bulk toggle zones\");\n  const firstNode = NODE_DATA[[...selectedNodes][0]];\n  const newState = !(firstNode?.fovEnabled);\n  let count = 0;\n  selectedNodes.forEach(id => {\n    const node = NODE_DATA[id];\n    if (node && hasCoverageZone(node.shape)) {\n      node.fovEnabled = newState;\n      updateFovCone(id);\n      count++;\n    }\n  });\n  forgeTheTopology();\n  alert(`Toggled zones on ${count} node(s) to ${newState ? 'ON' : 'OFF'}`);\n}\n\nfunction loadCustomPresets() {\n  const select = document.getElementById(\"fov-preset\");\n  if (!select) return;\n  Object.keys(ZONE_PRESETS).forEach(id => {\n    if (!select.querySelector(`option[value=\"${id}\"]`)) {\n      const opt = document.createElement(\"option\");\n      opt.value = id;\n      opt.textContent = id.replace(/-/g, \" \").replace(\"custom \", \"\") + \" ★\";\n      select.appendChild(opt);\n    }\n  });\n}\n\nfunction updateZoneLegend() {\n  const container = document.getElementById(\"edge-legend\");\n  if (!container) return;\n  container.querySelectorAll(\".zone-legend-item\").forEach(el => el.remove());\n  container.querySelectorAll(\".zone-legend-title\").forEach(el => el.remove());\n  const zoneColors = new Set();\n  Object.values(NODE_DATA).forEach(node => {\n    if (hasCoverageZone(node.shape) && node.fovEnabled && node.fovColor) {\n      zoneColors.add(node.fovColor);\n    }\n  });\n  \n  if (zoneColors.size === 0) return;\n  const zoneTitle = document.createElement(\"div\");\n  zoneTitle.className = \"legend-title zone-legend-title\";\n  zoneTitle.textContent = \"Zone Legend\";\n  zoneTitle.style.marginTop = \"12px\";\n  zoneTitle.style.paddingTop = \"8px\";\n  zoneTitle.style.borderTop = \"1px solid var(--edge-main)\";\n  container.appendChild(zoneTitle);\n  zoneColors.forEach(color => {\n    if (!ZONE_LEGEND[color]) {\n      ZONE_LEGEND[color] = \"Coverage Zone\";\n    }\n    const item = document.createElement(\"div\");\n    item.className = \"legend-item zone-legend-item\";\n    item.addEventListener(\"mousedown\", (e) => e.stopPropagation());\n    item.addEventListener(\"click\", (e) => e.stopPropagation());\n    \n    const swatch = document.createElement(\"span\");\n    swatch.className = \"legend-swatch\";\n    swatch.style.backgroundColor = color;\n    swatch.style.opacity = \"0.5\";\n    swatch.style.cursor = \"pointer\";\n    swatch.addEventListener(\"click\", (e) => {\n      e.stopPropagation();\n      const nodeWithColor = Object.entries(NODE_DATA).find(([id, n]) => \n        hasCoverageZone(n.shape) && n.fovEnabled && n.fovColor === color\n      );\n      if (nodeWithColor) {\n        claimTheImmortal(nodeWithColor[0]);\n      }\n    });\n    \n    const label = document.createElement(\"span\");\n    label.className = \"legend-label\";\n    label.textContent = ZONE_LEGEND[color];\n    label.contentEditable = true;\n    label.addEventListener(\"focus\", () => label.classList.add(\"editing\"));\n    label.addEventListener(\"blur\", () => {\n      label.classList.remove(\"editing\");\n      ZONE_LEGEND[color] = label.textContent.trim() || \"Coverage Zone\";\n    });\n    label.addEventListener(\"keydown\", (e) => {\n      if (e.key === \"Enter\") {\n        e.preventDefault();\n        label.blur();\n      }\n    });\n    \n    item.append(swatch, label);\n    container.appendChild(item);\n  });\n  \n  updateLegendVisibility();\n}\n\t  function isViewOnly() {\n       return PAGE_STATE.viewOnly === true;\n      }\n      let viewOnlyClickCount = 0;\n      let viewOnlyClickTimer = null;\n      let viewOnlyClickTarget = null;\n      function handleViewOnlyClick(id, type) {\n       if (!isViewOnly()) return false;\n       if (viewOnlyClickTarget !== id) {\n        viewOnlyClickCount = 0;\n        viewOnlyClickTarget = id;\n       }\n       viewOnlyClickCount++;\n       clearTimeout(viewOnlyClickTimer);\n       viewOnlyClickTimer = setTimeout(() => {\n        viewOnlyClickCount = 0;\n        viewOnlyClickTarget = null;\n       }, 2000);\n       if (viewOnlyClickCount >= 5) {\n        viewOnlyClickCount = 0;\n        viewOnlyClickTarget = null;\n        document.body.classList.add(\"view-only-inspect\");\n        if (type === 'node') {\n         currentNodeId = id;\n         const data = NODE_DATA[id];\n         if (data) {\n          document.querySelectorAll(\".node-group\").forEach((n) => {\n           n.classList.toggle(\"active\", n.dataset.nodeId === id);\n          });\n          document.getElementById(\"node-panel\").style.display = \"block\";\n          document.getElementById(\"edge-panel\").style.display = \"none\";\n          document.getElementById(\"node-name\").textContent = data.name;\n          document.getElementById(\"node-ip\").textContent = data.ip;\n          const fovSection = document.getElementById(\"fov-section\");\n          if (fovSection) {\n            if (hasCoverageZone(data.shape)) {\n              fovSection.style.display = \"block\";\n              const defaults = getCoverageDefaults(data.shape);\n              document.getElementById(\"fov-enabled\").checked = data.fovEnabled || false;\n              document.getElementById(\"fov-angle\").value = data.fovAngle || defaults.angle;\n              document.getElementById(\"fov-angle-value\").textContent = (data.fovAngle || defaults.angle) + \"°\";\n              document.getElementById(\"fov-distance\").value = data.fovDistance || defaults.distance;\n              document.getElementById(\"fov-distance-value\").textContent = data.fovDistance || defaults.distance;\n              document.getElementById(\"fov-inner-radius\").value = data.fovInnerRadius || 0;\n              document.getElementById(\"fov-inner-radius-value\").textContent = data.fovInnerRadius || 0;\n              document.getElementById(\"fov-rotation\").value = data.fovRotation || 0;\n              document.getElementById(\"fov-rotation-value\").textContent = (data.fovRotation || 0) + \"°\";\n              document.getElementById(\"fov-color\").value = data.fovColor || \"#f59e0b\";\n              document.getElementById(\"fov-opacity\").value = data.fovOpacity || 20;\n              document.getElementById(\"fov-opacity-value\").textContent = (data.fovOpacity || 20) + \"%\";\n              document.getElementById(\"fov-gradient\").checked = data.fovGradient || false;\n              document.getElementById(\"fov-border-color\").value = data.fovBorderColor || \"#f59e0b\";\n              document.getElementById(\"fov-border-width\").value = data.fovBorderWidth !== undefined ? data.fovBorderWidth : 2;\n              document.getElementById(\"fov-border-width-value\").textContent = data.fovBorderWidth !== undefined ? data.fovBorderWidth : 2;\n              document.getElementById(\"fov-border-style\").value = data.fovBorderStyle || \"solid\";\n              document.getElementById(\"fov-border-opacity\").value = data.fovBorderOpacity !== undefined ? data.fovBorderOpacity : 100;\n              document.getElementById(\"fov-border-opacity-value\").textContent = (data.fovBorderOpacity !== undefined ? data.fovBorderOpacity : 100) + \"%\";\n              document.getElementById(\"fov-label\").value = data.fovLabel || \"\";\n              document.getElementById(\"fov-label-position\").value = data.fovLabelPosition || \"center\";\n              document.getElementById(\"fov-label-size\").value = data.fovLabelSize || 14;\n              document.getElementById(\"fov-label-size-value\").textContent = (data.fovLabelSize || 14) + \"px\";\n              document.getElementById(\"fov-label-color\").value = data.fovLabelColor || \"#ffffff\";\n              document.getElementById(\"fov-label-bold\").checked = data.fovLabelBold || false;\n              document.getElementById(\"fov-label-bg\").checked = data.fovLabelBg || false;\n              document.getElementById(\"fov-label-bg-color\").value = data.fovLabelBgColor || \"#000000\";\n              document.getElementById(\"fov-label-offset-x\").value = data.fovLabelOffsetX || 0;\n              document.getElementById(\"fov-label-offset-x-value\").textContent = data.fovLabelOffsetX || 0;\n              document.getElementById(\"fov-label-offset-y\").value = data.fovLabelOffsetY || 0;\n              document.getElementById(\"fov-label-offset-y-value\").textContent = data.fovLabelOffsetY || 0;\n              document.getElementById(\"fov-animate\").checked = data.fovAnimate || false;\n              document.getElementById(\"fov-animation-type\").value = data.fovAnimationType || defaults.animationType;\n              document.getElementById(\"fov-sweep\").value = data.fovSweep || 120;\n              document.getElementById(\"fov-sweep-value\").textContent = (data.fovSweep || 120) + \"°\";\n              document.getElementById(\"fov-speed\").value = data.fovSpeed || 4;\n              document.getElementById(\"fov-speed-value\").textContent = (data.fovSpeed || 4) + \"s\";\n            } else {\n              fovSection.style.display = \"none\";\n            }\n          }\n          document.getElementById(\"node-role\").textContent = data.role;\n          document.getElementById(\"node-mac\").textContent = data.mac || \"--\";\n          document.getElementById(\"node-rack\").textContent = data.rackUnit || \"--\";\n          document.getElementById(\"node-uheight\").textContent = (data.uHeight || \"1\") + \"U\";\n          const tagList = document.getElementById(\"node-tags\");\n          if (tagList) {\n           tagList.innerHTML = \"\";\n           if (data.tags && data.tags.length > 0) {\n            data.tags.forEach((tag) => {\n             const li = document.createElement(\"li\");\n             li.textContent = tag;\n             tagList.appendChild(li);\n            });\n           }\n          }\n          const noteList = document.getElementById(\"node-notes\");\n          if (noteList) {\n           noteList.innerHTML = \"\";\n           if (data.notes && data.notes.length > 0) {\n            data.notes.forEach((note) => {\n             const li = document.createElement(\"li\");\n             li.textContent = note;\n             noteList.appendChild(li);\n            });\n           }\n          }\n         }\n        } else if (type === 'edge') {\n         currentEdgeId = id;\n         const edge = EDGE_DATA.list.find(e => e.id === id);\n         if (edge) {\n          document.querySelectorAll(\".edge\").forEach((e) => {\n           e.classList.toggle(\"active\", e.dataset.edgeId === id);\n          });\n          document.getElementById(\"edge-panel\").style.display = \"block\";\n          document.getElementById(\"node-panel\").style.display = \"none\";\n          document.getElementById(\"edge-from\").textContent = NODE_DATA[edge.from]?.name || edge.from;\n          document.getElementById(\"edge-to\").textContent = NODE_DATA[edge.to]?.name || edge.to;\n          document.getElementById(\"edge-label\").value = edge.label || \"\";\n         }\n        }\n        return true;\n       }\n       return false;\n      }\n      const CANVAS_WIDTH = 4000;\n      let CANVAS_HEIGHT = 3000;\n      const BASE_CANVAS_HEIGHT = 3000;\n      const CANVAS_PADDING = 100;\n      const RACK_U_HEIGHT = 70;\n      const RACK_WIDTH = 600;\n      const RACK_START_X = CANVAS_WIDTH / 2;\n      const RACK_START_Y = CANVAS_PADDING + 100;\n      function getRackUHeight(rackId) {\n      const capacity = getRackCapacity(rackId);\n      const availableHeight = CANVAS_HEIGHT - RACK_START_Y - 200;\n      return Math.floor(availableHeight / capacity);\n      }\n      let canvasState = {\n       zoom: 1,\n       panX: 0,\n       panY: 0,\n       minZoom: 0.25,\n       maxZoom: 4,\n       isPanning: false,\n       panStartX: 0,\n       panStartY: 0,\n       spacePressed: false,\n      };\n      function getViewBox() {\n       const viewWidth = CANVAS_WIDTH / canvasState.zoom;\n       const viewHeight = CANVAS_HEIGHT / canvasState.zoom;\n       return {\n        x: canvasState.panX,\n        y: canvasState.panY,\n        width: viewWidth,\n        height: viewHeight,\n       };\n      }\n\t  \n\tfunction updateViewBox() {\n\t  const svg = document.getElementById(\"map\");\n\t  const vb = getViewBox();\n\t  svg.setAttribute(\"viewBox\", `${vb.x} ${vb.y} ${vb.width} ${vb.height}`);\n\t  const zoomLevel = document.getElementById(\"zoom-level\");\n\t  if (zoomLevel) {\n\t\tzoomLevel.textContent = Math.round(canvasState.zoom * 100) + \"%\";\n\t  }\n\t  if (canvasState.zoom < 0.5) {\n\t\tsvg.classList.add(\"low-zoom\");\n\t  } else {\n\t\tsvg.classList.remove(\"low-zoom\");\n\t  }\n\t  updateMinimap();\n\t  populateRackDropdown();\n\t}\n\t  \n\tlet lastMinimapRender = 0;\n\tconst MINIMAP_THROTTLE = 100;\n\n\tfunction updateMinimap() {\n  const now = performance.now();\n  if (now - lastMinimapRender < MINIMAP_THROTTLE) return;\n  lastMinimapRender = now;\n  \n  const minimapViewport = document.getElementById(\"minimap-viewport\");\n  const minimapSvg = document.getElementById(\"minimap\");\n  if (!minimapViewport || !minimapSvg) return;\n  const vb = getViewBox();\n  minimapViewport.setAttribute(\"x\", vb.x);\n  minimapViewport.setAttribute(\"y\", vb.y);\n  minimapViewport.setAttribute(\"width\", vb.width);\n  minimapViewport.setAttribute(\"height\", vb.height);\n  minimapSvg.querySelectorAll(\".minimap-node, .minimap-edge, .minimap-wall, .minimap-rect\").forEach(el => el.remove());\n  const frag = document.createDocumentFragment();\n  const ns = \"http://www.w3.org/2000/svg\";\n  const dotColor = PAGE_STATE.minimapDots || \"#94a3b8\";\n\n  if (RECT_DATA && RECT_DATA.list && currentView.mode !== \"rack\") {\n    RECT_DATA.list.forEach((rect) => {\n      if (rect.lineStyle === \"wall\") {\n        const wallRect = document.createElementNS(ns, \"rect\");\n        wallRect.setAttribute(\"x\", rect.x);\n        wallRect.setAttribute(\"y\", rect.y);\n        wallRect.setAttribute(\"width\", rect.width);\n        wallRect.setAttribute(\"height\", rect.height);\n        wallRect.style.fill = rect.color || \"#666\";\n        wallRect.style.fillOpacity = \"0.6\";\n        wallRect.style.stroke = rect.borderColor || rect.color || \"#666\";\n        wallRect.style.strokeWidth = \"4\";\n        wallRect.classList.add(\"minimap-wall\");\n        frag.appendChild(wallRect);\n      }\n    });\n  }\n\n  EDGE_DATA.list.forEach((edge) => {\n    if (edge.type === \"custom\") {\n      if (Array.isArray(edge.points) && edge.points.length >= 2) {\n        const polyline = document.createElementNS(ns, \"polyline\");\n        polyline.setAttribute(\"points\", edge.points.map(p => `${p.x},${p.y}`).join(\" \"));\n        polyline.classList.add(\"minimap-edge\");\n        frag.appendChild(polyline);\n      }\n      return;\n    }\n    const fromNode = NODE_DATA[edge.from];\n    const toNode = NODE_DATA[edge.to];\n    if (!fromNode || !toNode) return;\n    if (currentView.mode === \"rack\") {\n      if (fromNode.assignedRack !== currentView.rackId || toNode.assignedRack !== currentView.rackId) return;\n    } else {\n      if (fromNode.assignedRack || toNode.assignedRack) return;\n    }\n    const p1 = savedPositions[edge.from];\n    const p2 = savedPositions[edge.to];\n    if (!p1 || !p2) return;\n    const routing = edge.routing || PAGE_STATE.defaultEdgeRouting || \"curved\";\n    \n    if (routing === \"orthogonal\") {\n      const dx = p2.x - p1.x;\n      const dy = p2.y - p1.y;\n      const polyline = document.createElementNS(ns, \"polyline\");\n      let points;\n      if (Math.abs(dx) > Math.abs(dy)) {\n        const midX = p1.x + dx / 2;\n        points = `${p1.x},${p1.y} ${midX},${p1.y} ${midX},${p2.y} ${p2.x},${p2.y}`;\n      } else {\n        const midY = p1.y + dy / 2;\n        points = `${p1.x},${p1.y} ${p1.x},${midY} ${p2.x},${midY} ${p2.x},${p2.y}`;\n      }\n      polyline.setAttribute(\"points\", points);\n      polyline.classList.add(\"minimap-edge\");\n      frag.appendChild(polyline);\n    } else {\n      const line = document.createElementNS(ns, \"line\");\n      line.setAttribute(\"x1\", p1.x);\n      line.setAttribute(\"y1\", p1.y);\n      line.setAttribute(\"x2\", p2.x);\n      line.setAttribute(\"y2\", p2.y);\n      line.classList.add(\"minimap-edge\");\n      frag.appendChild(line);\n    }\n  });\n\n  Object.entries(savedPositions).forEach(([id, pos]) => {\n    const node = NODE_DATA[id];\n    if (!node) return;\n    if (currentView.mode === \"rack\") {\n      if (node.assignedRack !== currentView.rackId) return;\n    } else {\n      if (node.assignedRack) return;\n    }\n    const nodeSize = savedSizes[id] || 55;\n    const s = nodeSize * 0.5;\n    \n    if (node.isRack) {\n      const rect = document.createElementNS(ns, \"rect\");\n      rect.setAttribute(\"x\", pos.x - s);\n      rect.setAttribute(\"y\", pos.y - s);\n      rect.setAttribute(\"width\", s * 2);\n      rect.setAttribute(\"height\", s * 2);\n      rect.style.fill = dotColor;\n      rect.classList.add(\"minimap-node\");\n      frag.appendChild(rect);\n    } else if (hasCoverageZone(node.shape)) {\n      const diamond = document.createElementNS(ns, \"polygon\");\n      const points = `${pos.x},${pos.y - s} ${pos.x + s},${pos.y} ${pos.x},${pos.y + s} ${pos.x - s},${pos.y}`;\n      diamond.setAttribute(\"points\", points);\n      diamond.style.fill = \"none\";\n      diamond.style.stroke = dotColor;\n      diamond.style.strokeWidth = \"6\";\n      diamond.classList.add(\"minimap-node\");\n      frag.appendChild(diamond);\n    } else {\n      const circle = document.createElementNS(ns, \"circle\");\n      circle.setAttribute(\"cx\", pos.x);\n      circle.setAttribute(\"cy\", pos.y);\n      circle.setAttribute(\"r\", s);\n      circle.style.fill = dotColor;\n      circle.classList.add(\"minimap-node\");\n      frag.appendChild(circle);\n    }\n  });\n\n  minimapSvg.insertBefore(frag, minimapViewport);\n}\n\t  \n\t  \n      function zoomTo(newZoom, centerX, centerY) {\n       const oldZoom = canvasState.zoom;\n       newZoom = Math.max(canvasState.minZoom, Math.min(canvasState.maxZoom, newZoom));\n       if (centerX !== undefined && centerY !== undefined) {\n        const oldWidth = CANVAS_WIDTH / oldZoom;\n        const oldHeight = CANVAS_HEIGHT / oldZoom;\n        const newWidth = CANVAS_WIDTH / newZoom;\n        const newHeight = CANVAS_HEIGHT / newZoom;\n        const pointX = canvasState.panX + centerX * oldWidth;\n        const pointY = canvasState.panY + centerY * oldHeight;\n        canvasState.panX = pointX - centerX * newWidth;\n        canvasState.panY = pointY - centerY * newHeight;\n       }\n       canvasState.zoom = newZoom;\n       constrainPan();\n       updateViewBox();\n      }\n      function constrainPan() {\n       const viewWidth = CANVAS_WIDTH / canvasState.zoom;\n       const viewHeight = CANVAS_HEIGHT / canvasState.zoom;\n       const minVisiblePortion = 0.1;\n       const maxPanX = CANVAS_WIDTH - viewWidth * minVisiblePortion;\n       const maxPanY = CANVAS_HEIGHT - viewHeight * minVisiblePortion;\n       const minPanX = -viewWidth * (1 - minVisiblePortion);\n       const minPanY = -viewHeight * (1 - minVisiblePortion);\n       canvasState.panX = Math.max(minPanX, Math.min(maxPanX, canvasState.panX));\n       canvasState.panY = Math.max(minPanY, Math.min(maxPanY, canvasState.panY));\n      }\n      function fitToContent() {\n       const positions = Object.values(savedPositions);\n       if (positions.length === 0) {\n        resetView();\n        return;\n       }\n       let minX = Infinity,\n        minY = Infinity,\n        maxX = -Infinity,\n        maxY = -Infinity;\n       positions.forEach((pos) => {\n        minX = Math.min(minX, pos.x - 100);\n        minY = Math.min(minY, pos.y - 100);\n        maxX = Math.max(maxX, pos.x + 100);\n        maxY = Math.max(maxY, pos.y + 100);\n       });\n       const contentWidth = maxX - minX + 200;\n       const contentHeight = maxY - minY + 200;\n       const viewport = document.getElementById(\"canvas-viewport\");\n       const vpRect = viewport.getBoundingClientRect();\n       const aspectRatio = vpRect.width / vpRect.height;\n       const contentAspect = contentWidth / contentHeight;\n       let newZoom;\n       if (contentAspect > aspectRatio) {\n        newZoom = (vpRect.width / contentWidth) * (CANVAS_WIDTH / vpRect.width);\n       } else {\n        newZoom = (vpRect.height / contentHeight) * (CANVAS_HEIGHT / vpRect.height);\n       }\n       newZoom = Math.max(canvasState.minZoom, Math.min(2, newZoom));\n       const viewWidth = CANVAS_WIDTH / newZoom;\n       const viewHeight = CANVAS_HEIGHT / newZoom;\n       canvasState.zoom = newZoom;\n       canvasState.panX = minX - 100 - (viewWidth - contentWidth) / 2;\n       canvasState.panY = minY - 100 - (viewHeight - contentHeight) / 2;\n       constrainPan();\n       updateViewBox();\n      }\n      function resetView() {\n       canvasState.zoom = 1;\n       canvasState.panX = 0;\n       canvasState.panY = 0;\n       updateViewBox();\n      }\n      function focusOnNodes(nodeIds) {\n       if (!nodeIds || nodeIds.length === 0) return;\n       const positions = nodeIds.map(id => savedPositions[id]).filter(Boolean);\n       if (positions.length === 0) return;\n       let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;\n       positions.forEach(pos => {\n        minX = Math.min(minX, pos.x);\n        minY = Math.min(minY, pos.y);\n        maxX = Math.max(maxX, pos.x);\n        maxY = Math.max(maxY, pos.y);\n       });\n       const padding = 150;\n       const contentWidth = Math.max(maxX - minX + padding * 2, 300);\n       const contentHeight = Math.max(maxY - minY + padding * 2, 300);\n       const centerX = (minX + maxX) / 2;\n       const centerY = (minY + maxY) / 2;\n       const viewport = document.getElementById(\"canvas-viewport\");\n       const vpRect = viewport.getBoundingClientRect();\n       const aspectRatio = vpRect.width / vpRect.height;\n       const contentAspect = contentWidth / contentHeight;\n       let newZoom;\n       if (contentAspect > aspectRatio) {\n        newZoom = (vpRect.width / contentWidth) * (CANVAS_WIDTH / vpRect.width);\n       } else {\n        newZoom = (vpRect.height / contentHeight) * (CANVAS_HEIGHT / vpRect.height);\n       }\n       newZoom = Math.max(canvasState.minZoom, Math.min(1.5, newZoom));\n       const viewWidth = CANVAS_WIDTH / newZoom;\n       const viewHeight = CANVAS_HEIGHT / newZoom;\n       canvasState.zoom = newZoom;\n       canvasState.panX = centerX - viewWidth / 2;\n       canvasState.panY = centerY - viewHeight / 2;\n       constrainPan();\n       updateViewBox();\n      }\n      window.addEventListener(\"DOMContentLoaded\", () => {\n       const toggle = document.getElementById(\"mobile-menu-toggle\");\n       const menu = document.getElementById(\"topbar-menu\");\n       if (!toggle || !menu) return;\n       toggle.addEventListener(\"click\", () => {\n        menu.classList.toggle(\"open\");\n       });\n       menu.addEventListener(\"click\", (e) => {\n        const target = e.target.closest(\"a, button\");\n        if (!target) return;\n        if (isMobileDevice()) {\n       menu.classList.remove(\"open\");\n      }\n       });\n       const minimapCloseBtn = document.getElementById(\"minimap-close-btn\");\n       if (minimapCloseBtn) {\n        const handleMinimapClose = (e) => {\n         e.stopPropagation();\n         e.preventDefault();\n         minimapCollapsed = true;\n         updateMinimapVisibility();\n        };\n        minimapCloseBtn.addEventListener(\"click\", handleMinimapClose);\n        minimapCloseBtn.addEventListener(\"touchend\", handleMinimapClose);\n       }\n       const drawToolbarCloseBtn = document.getElementById(\"draw-toolbar-close-btn\");\n       if (drawToolbarCloseBtn) {\n        const handleDrawClose = (e) => {\n         e.stopPropagation();\n         e.preventDefault();\n         drawToolbarCollapsed = true;\n         updateDrawToolbarVisibility();\n        };\n        drawToolbarCloseBtn.addEventListener(\"click\", handleDrawClose);\n        drawToolbarCloseBtn.addEventListener(\"touchend\", handleDrawClose);\n       }\n       const topologyToolbarCloseBtn = document.getElementById(\"topology-toolbar-close-btn\");\n       if (topologyToolbarCloseBtn) {\n        const handleTopologyClose = (e) => {\n         e.stopPropagation();\n         e.preventDefault();\n         topologyToolbarCollapsed = true;\n         updateTopologyToolbarVisibility();\n        };\n        topologyToolbarCloseBtn.addEventListener(\"click\", handleTopologyClose);\n        topologyToolbarCloseBtn.addEventListener(\"touchstart\", (e) => e.preventDefault(), {\n         passive: false\n        });\n        topologyToolbarCloseBtn.addEventListener(\"touchend\", handleTopologyClose);\n       }\n       updateMinimapVisibility();\n       updateDrawToolbarVisibility();\n       updateTopologyToolbarVisibility();\n      });\n      function applyLayerFilter() {\n       activeLayers.clear();\n       if (document.getElementById(\"layer-physical\").checked) activeLayers.add(\"physical\");\n       if (document.getElementById(\"layer-logical\").checked) activeLayers.add(\"logical\");\n       if (document.getElementById(\"layer-security\").checked) activeLayers.add(\"security\");\n       if (document.getElementById(\"layer-application\").checked) activeLayers.add(\"application\");\n       forgeTheTopology();\n      }\n      function isNodeVisible(nodeId) {\n       const node = NODE_DATA[nodeId];\n       if (!node) return false;\n       const nodeLayer = node.layer || \"physical\";\n       return activeLayers.has(nodeLayer);\n      }\n      function enterRack(rackId) {\n      if (!NODE_DATA[rackId] || !NODE_DATA[rackId].isRack) return;\n      const rackCapacity = getRackCapacity(rackId);\n      const neededHeight = RACK_START_Y + (rackCapacity * RACK_U_HEIGHT) + 200;\n      if (neededHeight > BASE_CANVAS_HEIGHT) {\n      CANVAS_HEIGHT = neededHeight;\n      }\n       currentView.mode = \"rack\";\n       currentView.rackId = rackId;\n       const backBtn = document.getElementById(\"back-to-topology-btn\");\n       if (backBtn) backBtn.style.display = \"inline-block\";\n       const hint = document.getElementById(\"canvas-hint\");\n       if (hint) {\n        hint.textContent = `Viewing: ${NODE_DATA[rackId]?.name || 'Rack'} | Double click empty space to exit`;\n        hint.classList.add(\"visible\");\n       }\n      const rackUHeight = getRackUHeight(rackId);\n      const rackHeight = rackCapacity * rackUHeight;\n      const rackCenterX = RACK_START_X;\n      const rackCenterY = RACK_START_Y + (rackHeight / 2);\n       const viewWidth = CANVAS_WIDTH / canvasState.zoom;\n       const viewHeight = CANVAS_HEIGHT / canvasState.zoom;\n       canvasState.panX = rackCenterX - (viewWidth / 2);\n       canvasState.panY = rackCenterY - (viewHeight / 2);\n       constrainPan();\n       updateViewBox();\n       forgeTheTopology();\n      }\n      function exitRack() {\n      CANVAS_HEIGHT = BASE_CANVAS_HEIGHT;\n      currentView.mode = \"topology\";\n      currentView.rackId = null;\n       const backBtn = document.getElementById(\"back-to-topology-btn\");\n       if (backBtn) backBtn.style.display = \"none\";\n       const hint = document.getElementById(\"canvas-hint\");\n       if (hint) {\n        hint.classList.remove(\"visible\");\n       }\n       if (savedTopologyView) {\n        canvasState.zoom = savedTopologyView.zoom;\n        canvasState.panX = savedTopologyView.panX;\n        canvasState.panY = savedTopologyView.panY;\n        updateViewBox();\n       }\n       forgeTheTopology();\n      }\n      function getRackCapacity(rackId) {\n       const node = NODE_DATA[rackId];\n       return node && node.rackCapacity ? parseInt(node.rackCapacity) : 42;\n      }\n      function populateRackDropdown() {\n       const dropdown = document.getElementById(\"node-assigned-rack\");\n       if (!dropdown) return;\n       dropdown.innerHTML = '<option value=\"\">None</option>';\n       Object.keys(NODE_DATA).forEach(id => {\n        if (NODE_DATA[id].isRack) {\n         const option = document.createElement(\"option\");\n         option.value = id;\n         option.textContent = NODE_DATA[id].name;\n         dropdown.appendChild(option);\n        }\n       });\n      }\n      function wieldThePower() {\n       const root = document.documentElement;\n       root.style.setProperty(\"--panel\", PAGE_STATE.panel);\n       root.style.setProperty(\"--panel-alt\", PAGE_STATE.panelAlt);\n       root.style.setProperty(\"--sidebar-bg\", PAGE_STATE.sidebarBg || PAGE_STATE.panelAlt);\n       root.style.setProperty(\"--btn-bg\", PAGE_STATE.btnBg || PAGE_STATE.panel);\n       root.style.setProperty(\"--btn-text\", PAGE_STATE.btnText || \"#e2e8f0\");\n       root.style.setProperty(\"--tag-fill\", PAGE_STATE.tagFill || \"#1e293b\");\n       root.style.setProperty(\"--tag-text\", PAGE_STATE.tagText || \"#e2e8f0\");\n       root.style.setProperty(\"--tag-border\", PAGE_STATE.tagBorder || \"#475569\");\n       root.style.setProperty(\"--input-bg\", PAGE_STATE.inputBg || \"#0b0e13\");\n       root.style.setProperty(\"--input-text\", PAGE_STATE.inputText || \"#e2e8f0\");\n       root.style.setProperty(\"--input-border\", PAGE_STATE.inputBorder || \"#1f2937\");\n       root.style.setProperty(\"--input-font\", PAGE_STATE.inputFont || \"Inter, system-ui, sans-serif\");\n       root.style.setProperty(\"--input-font-size\", (PAGE_STATE.inputFontSize || 14) + \"px\");\n       root.style.setProperty(\"--toolbar-bg\", PAGE_STATE.toolbarBg || \"#0f172a\");\n       root.style.setProperty(\"--toolbar-border\", PAGE_STATE.toolbarBorder || \"#1f2937\");\n       root.style.setProperty(\"--toolbar-text\", PAGE_STATE.toolbarText || \"#94a3b8\");\n       root.style.setProperty(\"--toolbar-btn-bg\", PAGE_STATE.toolbarBtnBg || \"#0b0e13\");\n       root.style.setProperty(\"--toolbar-btn-text\", PAGE_STATE.toolbarBtnText || \"#e2e8f0\");\n       root.style.setProperty(\"--minimap-dots\", PAGE_STATE.minimapDots || \"#94a3b8\");\n       root.style.setProperty(\"--canvas-hint-bg\", PAGE_STATE.canvasHintBg || \"#0f172a\");\n       root.style.setProperty(\"--canvas-hint-color\", PAGE_STATE.canvasHintColor || \"#94a3b8\");\n       const canvasHint = document.getElementById(\"canvas-hint\");\n       if (canvasHint) {\n        canvasHint.style.display = PAGE_STATE.canvasHintEnabled === false ? \"none\" : \"\";\n        if (PAGE_STATE.canvasHintText !== undefined && PAGE_STATE.canvasHintText !== \"\") {\n         canvasHint.textContent = PAGE_STATE.canvasHintText;\n        } else {\n         MobileManager.updateCanvasHint();\n        }\n       }\n       root.style.setProperty(\"--accent\", PAGE_STATE.accent);\n       root.style.setProperty(\"--danger\", PAGE_STATE.danger);\n       root.style.setProperty(\"--text-main\", PAGE_STATE.textMain);\n       root.style.setProperty(\"--text-soft\", PAGE_STATE.textSoft);\n       root.style.setProperty(\"--topbar-bg\", PAGE_STATE.topbarBg);\n       root.style.setProperty(\"--topbar-border\", PAGE_STATE.topbarBorder);\n       root.style.setProperty(\"--node-fill\", PAGE_STATE.nodeFill || \"#1e293b\");\n       root.style.setProperty(\"--node-stroke\", PAGE_STATE.nodeStroke || \"#475569\");\n       root.style.setProperty(\"--node-title\", PAGE_STATE.nodeTitle || \"#e2e8f0\");\n       root.style.setProperty(\"--node-sub\", PAGE_STATE.nodeSub || \"#94a3b8\");\n       root.style.setProperty(\"--node-title-size\", (PAGE_STATE.nodeTitleSize || 18) + \"px\");\n       root.style.setProperty(\"--node-sub-size\", (PAGE_STATE.nodeSubSize || 13) + \"px\");\n       root.style.setProperty(\"--node-font\", PAGE_STATE.nodeFont || \"Inter, system-ui, sans-serif\");\n       root.style.setProperty(\"--default-edge\", PAGE_STATE.defaultEdge || \"#475569\");\n       root.style.setProperty(\"--selection-handle\", PAGE_STATE.selectionHandle || \"#f59e0b\");\n       root.style.setProperty(\"--selection-handle-size\", (PAGE_STATE.selectionHandleSize || 8) + \"px\");\n       root.style.setProperty(\"--group-indicator\", PAGE_STATE.groupIndicator || \"#4fd1c5\");\n       const topbarHeight = PAGE_STATE.topbarHeight || 52;\n       const sidebarWidth = PAGE_STATE.sidebarWidth || 350;\n       const mobileFooterHeight = PAGE_STATE.mobileFooterHeight || 40;\n       root.style.setProperty(\"--topbar-height\", topbarHeight + \"px\");\n       root.style.setProperty(\"--sidebar-width\", sidebarWidth + \"px\");\n       root.style.setProperty(\"--mobile-footer-height\", mobileFooterHeight + \"vh\");\n       const mainEl = document.querySelector(\"main\");\n       const detailsPanel = document.getElementById(\"details-panel\");\n       const sidebarToggle = document.getElementById(\"sidebar-toggle\");\n       if (PAGE_STATE.sidebarCollapsed) {\n        if (mainEl) mainEl.classList.add(\"sidebar-collapsed\");\n        if (detailsPanel) detailsPanel.classList.add(\"collapsed\");\n        if (sidebarToggle) {\n         sidebarToggle.classList.add(\"collapsed\");\n         sidebarToggle.textContent = \"▶\";\n        }\n       } else {\n        if (mainEl) mainEl.classList.remove(\"sidebar-collapsed\");\n        if (detailsPanel) detailsPanel.classList.remove(\"collapsed\");\n        if (sidebarToggle) {\n         sidebarToggle.classList.remove(\"collapsed\");\n         sidebarToggle.textContent = \"◀\";\n        }\n       }\n       if (PAGE_STATE.background) {\n        document.body.style.background = PAGE_STATE.background;\n       } else {\n        document.body.style.background = `radial-gradient(circle at top, ${PAGE_STATE.canvasGradientTop || \"#1e2532\"} 0, ${PAGE_STATE.canvasGradientBottom || \"#050608\"} 70%)`;\n       }\n       document.title = PAGE_STATE.title || DEFAULT_PAGE_STATE.title;\n       const titleEl = document.getElementById(\"page-title\");\n       if (titleEl) {\n        titleEl.textContent = PAGE_STATE.title || DEFAULT_PAGE_STATE.title;\n       }\n       const viewOnlyMode = PAGE_STATE.viewOnly === true;\n       const addNodeBtn = document.getElementById(\"add-node-btn\");\n       const addRackBtn = document.getElementById(\"add-rack-btn\");\n       const bulkToolbar = document.getElementById(\"bulk-toolbar\");\n       const bulkToolbarMobile = document.getElementById(\"bulk-toolbar-mobile\");\n       if (viewOnlyMode) {\n        if (addNodeBtn) addNodeBtn.style.display = \"none\";\n        if (addRackBtn) addRackBtn.style.display = \"none\";\n        if (bulkToolbar) bulkToolbar.style.display = \"none\";\n        if (bulkToolbarMobile) bulkToolbarMobile.style.display = \"none\";\n        [\"draw-toolbar-mini\", \"topology-toolbar-mini\"].forEach(id => {\n         const el = document.getElementById(id);\n         if (el) el.style.display = \"none\";\n        });\n        document.body.classList.remove(\"view-only-inspect\");\n       } else {\n        if (addNodeBtn) addNodeBtn.style.display = \"\";\n        if (addRackBtn) addRackBtn.style.display = \"\";\n       }\n       document.body.classList.toggle(\"view-only-mode\", viewOnlyMode);\n       if (!viewOnlyMode) {\n        if (typeof updateDrawToolbarVisibility === 'function') updateDrawToolbarVisibility();\n        if (typeof updateTopologyToolbarVisibility === 'function') updateTopologyToolbarVisibility();\n       }\n      }\n      (async function awakeTheImmortal() {\n       let initialState = {};\n       let decryptionCancelled = false;\n       const stateEl = document.getElementById(\"topology-state\");\n       if (stateEl && stateEl.textContent.trim()) {\n        try {\n         let stateText = stateEl.textContent.trim();\n         if (isEncrypted(stateText)) {\n          let decrypted = false;\n          let attempts = 0;\n          const maxAttempts = 3;\n          while (!decrypted && attempts < maxAttempts) {\n           const password = prompt(\"This file is encrypted. Enter password to decrypt:\\n(Attempt \" + (attempts + 1) + \" of \" + maxAttempts + \")\");\n           if (!password) {\n            alert(\"Decryption cancelled. The file will not be loaded.\");\n            decryptionCancelled = true;\n            break;\n           }\n           try {\n            stateText = await decryptData(stateText, password);\n            decrypted = true;\n           } catch (e) {\n            attempts++;\n            if (attempts < maxAttempts) {\n             alert(\"Incorrect password. Please try again.\");\n            } else {\n             alert(\"Maximum attempts reached. The file will not be loaded.\");\n             decryptionCancelled = true;\n            }\n           }\n          }\n          if (!decrypted) {\n           stateText = \"{}\";\n          }\n         }\n         initialState = JSON.parse(stateText);\n        } catch (e) {\n         console.error(\"Failed to load state:\", e);\n         initialState = {};\n        }\n       }\n       if (decryptionCancelled) {\n        NODE_DATA = {};\n        EDGE_DATA = {\n         list: []\n        };\n        RECT_DATA = { list: [] };\n        TEXT_DATA = { list: [] };\n        EDGE_LEGEND = {};\n        savedPositions = {};\n        savedSizes = {};\n        savedStyles = {};\n       } else {\n        NODE_DATA = initialState.nodeData ? initialState.nodeData : BASE_NODE_DATA;\n        Object.keys(NODE_DATA).forEach(nodeId => {\n         if (!NODE_DATA[nodeId].ping) {\n          NODE_DATA[nodeId].ping = {\n           enabled: false,\n           protocol: 'http',\n           customUrl: '',\n           timeout: 3000,\n           status: 'unknown',\n           lastCheck: null\n          };\n         }\n        });\n        EDGE_DATA = initialState.edgeData ? initialState.edgeData : {\n         list: [],\n        };\n        RECT_DATA = initialState.rectData ? initialState.rectData : { list: [] };\n        TEXT_DATA = initialState.textData ? initialState.textData : { list: [] };\n        EDGE_LEGEND = initialState.edgeLegend || {};\n        savedPositions = initialState.nodePositions || {};\n        savedSizes = initialState.nodeSizes || {};\n        savedStyles = initialState.nodeStyles || {};\n        if (initialState.iconCache) {\n         IconLibrary.iconCache = initialState.iconCache;\n        }\n       }\n       if (initialState.page) {\n        PAGE_STATE = Object.assign({}, DEFAULT_PAGE_STATE, initialState.page);\n       }\n       if (PAGE_STATE.autoPingEnabled !== undefined) {\n        autoPingEnabled = PAGE_STATE.autoPingEnabled;\n       }\n       if (PAGE_STATE.autoPingInterval !== undefined) {\n        autoPingInterval = PAGE_STATE.autoPingInterval;\n       }\n       if (initialState.canvas) {\n        canvasState.zoom = initialState.canvas.zoom || 1;\n        canvasState.panX = initialState.canvas.panX || 0;\n        canvasState.panY = initialState.canvas.panY || 0;\n       }\n       if (initialState.savedTopologyView) {\n        savedTopologyView = initialState.savedTopologyView;\n       }\n       if (initialState.documentTabs) {\n        documentTabs = initialState.documentTabs;\n        if (initialState.currentTabIndex !== undefined) {\n          currentTabIndex = initialState.currentTabIndex;\n          const currentTab = documentTabs[currentTabIndex];\n          if (currentTab) {\n            NODE_DATA = currentTab.nodes || NODE_DATA;\n            EDGE_DATA = currentTab.edges || EDGE_DATA;\n            savedPositions = currentTab.positions || savedPositions;\n            savedSizes = currentTab.sizes || savedSizes;\n            savedStyles = currentTab.styles || savedStyles;\n            EDGE_LEGEND = currentTab.legend || EDGE_LEGEND;\n            RECT_DATA = currentTab.rects || RECT_DATA;\n            TEXT_DATA = currentTab.texts || TEXT_DATA;\n            if (currentTab.pageState) PAGE_STATE = Object.assign({}, DEFAULT_PAGE_STATE, currentTab.pageState);\n          }\n        }\n       }\n       if (initialState.encryptedSections) {\n        encryptedSections = initialState.encryptedSections;\n       }\n       wieldThePower();\n       forgeTheTopology();\n       updateViewBox();\n       MobileManager.applyAll();\n       if (autoPingEnabled) {\n        startAutoPing();\n       }\n       const initialNodes = Object.keys(NODE_DATA);\n       if (initialNodes.length > 0) {\n        claimTheImmortal(initialNodes.includes(\"host\") ? \"host\" : initialNodes[0]);\n       } else {\n        document.getElementById(\"node-panel\").style.display = \"none\";\n        document.getElementById(\"topology-toolbar\").style.display = \"none\";\n       }\n       if (Object.keys(NODE_DATA).length === 0 && EDGE_DATA.list.length === 0) {\n        checkForAutosaveRecovery();\n       }\n       startAutosave();\n      })();\n      let resizeDebounceTimer = null;\n      window.addEventListener(\"resize\", () => {\n        clearTimeout(resizeDebounceTimer);\n        resizeDebounceTimer = setTimeout(() => {\n          MobileManager.applyAll();\n          if (typeof updateDrawToolbarVisibility === 'function') updateDrawToolbarVisibility();\n          if (typeof updateTopologyToolbarVisibility === 'function') updateTopologyToolbarVisibility();\n        }, 100);\n      });\n      function getBreakpointKey() {\n       const w = window.innerWidth;\n       if (w <= 380) return \"fold\";\n       if (w <= 768) return \"mobile\";\n       if (w <= 1024) return \"tablet\";\n       return \"desktop\";\n      }\n      function resolveStylesEntry(styleEntry) {\n       if (!styleEntry) return {};\n       if (styleEntry.circleColor || styleEntry.titleColor || styleEntry.titleFont || styleEntry.titleSize || styleEntry.subColor || styleEntry.subFont || styleEntry.subSize) {\n        return styleEntry;\n       }\n       const bp = getBreakpointKey();\n       const base = styleEntry.all || {};\n       const bpStyles = styleEntry[bp] || {};\n       return Object.assign({}, base, bpStyles);\n      }\n      function resolveStylesForNode(id) {\n       const styleEntry = savedStyles[id];\n       if (!styleEntry) return {};\n       return resolveStylesEntry(styleEntry);\n      }\n      function ensureStyleEntry(id) {\n       if (!savedStyles[id]) savedStyles[id] = {};\n       const entry = savedStyles[id];\n       const flatProps = [\"circleColor\", \"circleBorder\", \"titleColor\", \"titleFont\", \"titleSize\", \"subColor\", \"subFont\", \"subSize\", ];\n       const isFlat = flatProps.some((p) => Object.prototype.hasOwnProperty.call(entry, p));\n       if (isFlat) {\n        const all = entry.all || {};\n        flatProps.forEach((p) => {\n         if (entry[p] !== undefined) {\n          all[p] = entry[p];\n          delete entry[p];\n         }\n        });\n        entry.all = all;\n       }\n       return entry;\n      }\n      function getDefaultSize() {\n       if (window.innerWidth <= 380) return 120;\n       if (window.innerWidth <= 768) return 140;\n       if (window.innerWidth <= 1024) return 70;\n       return 55;\n      }\n      function createShapeElement(shape, size) {\n       const ns = \"http://www.w3.org/2000/svg\";\n       if (shape === \"circle\") {\n        const c = document.createElementNS(ns, \"circle\");\n        c.setAttribute(\"r\", size);\n        return c;\n       }\n       if (shape === \"square\") {\n        const r = document.createElementNS(ns, \"rect\");\n        const s = size * 2;\n        r.setAttribute(\"x\", -size);\n        r.setAttribute(\"y\", -size);\n        r.setAttribute(\"width\", s);\n        r.setAttribute(\"height\", s);\n        r.setAttribute(\"rx\", 4);\n        return r;\n       }\n       if (shape === \"rectangle\") {\n        const r = document.createElementNS(ns, \"rect\");\n        r.setAttribute(\"x\", -size * 1.4);\n        r.setAttribute(\"y\", -size * 0.8);\n        r.setAttribute(\"width\", size * 2.8);\n        r.setAttribute(\"height\", size * 1.6);\n        r.setAttribute(\"rx\", 6);\n        return r;\n       }\n       if (shape === \"triangle\") {\n        const p = document.createElementNS(ns, \"polygon\");\n        const h = size * 1.8;\n        const w = size * 2;\n        p.setAttribute(\"points\", `0,${-h} ${w / 2},${h / 2} ${-w / 2},${h / 2}`);\n        return p;\n       }\n       if (shape === \"hexagon\") {\n        const p = document.createElementNS(ns, \"polygon\");\n        const s = size;\n        const pts = [\n         [0, -s],\n         [s * 0.86, -s * 0.5],\n         [s * 0.86, s * 0.5],\n         [0, s],\n         [-s * 0.86, s * 0.5],\n         [-s * 0.86, -s * 0.5],\n        ].map((pt) => pt.join(\",\")).join(\" \");\n        p.setAttribute(\"points\", pts);\n        return p;\n       }\n       if (shape === \"stop-sign\") {\n        const p = document.createElementNS(ns, \"polygon\");\n        const r = size;\n        const ptsArr = [];\n        for (let i = 0; i < 8; i++) {\n         const a = (Math.PI / 4) * i + Math.PI / 8;\n         ptsArr.push([Math.cos(a) * r, Math.sin(a) * r]);\n        }\n        const pts = ptsArr.map((pt) => pt.join(\",\")).join(\" \");\n        p.setAttribute(\"points\", pts);\n        return p;\n       }\n       if (shape === \"star\") {\n        const p = document.createElementNS(ns, \"polygon\");\n        const outer = size;\n        const inner = size * 0.45;\n        let pts = \"\";\n        for (let i = 0; i < 10; i++) {\n         const a = (Math.PI / 5) * i - Math.PI / 2;\n         const r = i % 2 === 0 ? outer : inner;\n         pts += `${Math.cos(a) * r},${Math.sin(a) * r} `;\n        }\n        p.setAttribute(\"points\", pts.trim());\n        return p;\n       }\n       if (shape === \"diamond\") {\n        const p = document.createElementNS(ns, \"polygon\");\n        const s = size;\n        p.setAttribute(\"points\", `0,${-s} ${s},0 0,${s} ${-s},0`);\n        return p;\n       }\n       if (shape === \"server\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 1.2);\n        body.setAttribute(\"y\", -size * 0.6);\n        body.setAttribute(\"width\", size * 2.4);\n        body.setAttribute(\"height\", size * 1.2);\n        body.setAttribute(\"rx\", 4);\n        g.appendChild(body);\n        for (let i = 0; i < 3; i++) {\n         const line = document.createElementNS(ns, \"line\");\n         line.setAttribute(\"x1\", -size * 0.9 + i * size * 0.5);\n         line.setAttribute(\"y1\", -size * 0.3);\n         line.setAttribute(\"x2\", -size * 0.9 + i * size * 0.5);\n         line.setAttribute(\"y2\", size * 0.3);\n         line.style.stroke = \"currentColor\";\n         line.style.strokeWidth = \"2\";\n         line.style.opacity = \"0.5\";\n         g.appendChild(line);\n        }\n        const led = document.createElementNS(ns, \"circle\");\n        led.setAttribute(\"cx\", size * 0.9);\n        led.setAttribute(\"cy\", 0);\n        led.setAttribute(\"r\", size * 0.1);\n        led.style.fill = \"#4ade80\";\n        g.appendChild(led);\n        return g;\n       }\n       if (shape === \"pc\" || shape === \"desktop\") {\n        const g = document.createElementNS(ns, \"g\");\n        const monitor = document.createElementNS(ns, \"rect\");\n        monitor.setAttribute(\"x\", -size * 0.9);\n        monitor.setAttribute(\"y\", -size * 0.8);\n        monitor.setAttribute(\"width\", size * 1.8);\n        monitor.setAttribute(\"height\", size * 1.2);\n        monitor.setAttribute(\"rx\", 4);\n        g.appendChild(monitor);\n        const screen = document.createElementNS(ns, \"rect\");\n        screen.setAttribute(\"x\", -size * 0.75);\n        screen.setAttribute(\"y\", -size * 0.65);\n        screen.setAttribute(\"width\", size * 1.5);\n        screen.setAttribute(\"height\", size * 0.9);\n        screen.setAttribute(\"rx\", 2);\n        screen.style.fill = \"#1e293b\";\n        g.appendChild(screen);\n        const neck = document.createElementNS(ns, \"rect\");\n        neck.setAttribute(\"x\", -size * 0.15);\n        neck.setAttribute(\"y\", size * 0.4);\n        neck.setAttribute(\"width\", size * 0.3);\n        neck.setAttribute(\"height\", size * 0.3);\n        g.appendChild(neck);\n        const base = document.createElementNS(ns, \"rect\");\n        base.setAttribute(\"x\", -size * 0.5);\n        base.setAttribute(\"y\", size * 0.7);\n        base.setAttribute(\"width\", size * 1);\n        base.setAttribute(\"height\", size * 0.15);\n        base.setAttribute(\"rx\", 2);\n        g.appendChild(base);\n        return g;\n       }\n       if (shape === \"laptop\") {\n        const g = document.createElementNS(ns, \"g\");\n        const screen = document.createElementNS(ns, \"rect\");\n        screen.setAttribute(\"x\", -size * 0.8);\n        screen.setAttribute(\"y\", -size * 0.9);\n        screen.setAttribute(\"width\", size * 1.6);\n        screen.setAttribute(\"height\", size * 1.1);\n        screen.setAttribute(\"rx\", 4);\n        g.appendChild(screen);\n        const base = document.createElementNS(ns, \"rect\");\n        base.setAttribute(\"x\", -size * 0.9);\n        base.setAttribute(\"y\", size * 0.25);\n        base.setAttribute(\"width\", size * 1.8);\n        base.setAttribute(\"height\", size * 0.6);\n        base.setAttribute(\"rx\", 4);\n        g.appendChild(base);\n        const pad = document.createElementNS(ns, \"rect\");\n        pad.setAttribute(\"x\", -size * 0.25);\n        pad.setAttribute(\"y\", size * 0.45);\n        pad.setAttribute(\"width\", size * 0.5);\n        pad.setAttribute(\"height\", size * 0.25);\n        pad.setAttribute(\"rx\", 2);\n        pad.style.fill = \"#1e293b\";\n        g.appendChild(pad);\n        return g;\n       }\n       if (shape === \"phone\" || shape === \"mobile\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 0.45);\n        body.setAttribute(\"y\", -size);\n        body.setAttribute(\"width\", size * 0.9);\n        body.setAttribute(\"height\", size * 2);\n        body.setAttribute(\"rx\", 8);\n        g.appendChild(body);\n        const screen = document.createElementNS(ns, \"rect\");\n        screen.setAttribute(\"x\", -size * 0.38);\n        screen.setAttribute(\"y\", -size * 0.85);\n        screen.setAttribute(\"width\", size * 0.76);\n        screen.setAttribute(\"height\", size * 1.6);\n        screen.setAttribute(\"rx\", 4);\n        screen.style.fill = \"#1e293b\";\n        g.appendChild(screen);\n        const btn = document.createElementNS(ns, \"rect\");\n        btn.setAttribute(\"x\", -size * 0.15);\n        btn.setAttribute(\"y\", size * 0.82);\n        btn.setAttribute(\"width\", size * 0.3);\n        btn.setAttribute(\"height\", size * 0.06);\n        btn.setAttribute(\"rx\", 2);\n        btn.style.fill = \"#475569\";\n        g.appendChild(btn);\n        return g;\n       }\n       if (shape === \"router\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 1.1);\n        body.setAttribute(\"y\", -size * 0.3);\n        body.setAttribute(\"width\", size * 2.2);\n        body.setAttribute(\"height\", size * 0.8);\n        body.setAttribute(\"rx\", 6);\n        g.appendChild(body);\n        for (let i = -1; i <= 1; i++) {\n         const ant = document.createElementNS(ns, \"rect\");\n         ant.setAttribute(\"x\", i * size * 0.6 - size * 0.05);\n         ant.setAttribute(\"y\", -size * 0.9);\n         ant.setAttribute(\"width\", size * 0.1);\n         ant.setAttribute(\"height\", size * 0.6);\n         ant.setAttribute(\"rx\", 2);\n         g.appendChild(ant);\n         const tip = document.createElementNS(ns, \"circle\");\n         tip.setAttribute(\"cx\", i * size * 0.6);\n         tip.setAttribute(\"cy\", -size * 0.95);\n         tip.setAttribute(\"r\", size * 0.08);\n         g.appendChild(tip);\n        }\n        for (let i = 0; i < 4; i++) {\n         const led = document.createElementNS(ns, \"circle\");\n         led.setAttribute(\"cx\", -size * 0.7 + i * size * 0.35);\n         led.setAttribute(\"cy\", size * 0.1);\n         led.setAttribute(\"r\", size * 0.06);\n         led.style.fill = i < 2 ? \"#4ade80\" : \"#facc15\";\n         g.appendChild(led);\n        }\n        return g;\n       }\n       if (shape === \"switch\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 1.4);\n        body.setAttribute(\"y\", -size * 0.4);\n        body.setAttribute(\"width\", size * 2.8);\n        body.setAttribute(\"height\", size * 0.8);\n        body.setAttribute(\"rx\", 4);\n        g.appendChild(body);\n        for (let i = 0; i < 8; i++) {\n         const port = document.createElementNS(ns, \"rect\");\n         port.setAttribute(\"x\", -size * 1.2 + i * size * 0.32);\n         port.setAttribute(\"y\", -size * 0.15);\n         port.setAttribute(\"width\", size * 0.22);\n         port.setAttribute(\"height\", size * 0.3);\n         port.setAttribute(\"rx\", 1);\n         port.style.fill = \"#1e293b\";\n         g.appendChild(port);\n        }\n        return g;\n       }\n       if (shape === \"firewall\") {\n        const g = document.createElementNS(ns, \"g\");\n        const wall = document.createElementNS(ns, \"rect\");\n        wall.setAttribute(\"x\", -size);\n        wall.setAttribute(\"y\", -size * 0.8);\n        wall.setAttribute(\"width\", size * 2);\n        wall.setAttribute(\"height\", size * 1.6);\n        wall.setAttribute(\"rx\", 4);\n        g.appendChild(wall);\n        for (let row = 0; row < 3; row++) {\n         const line = document.createElementNS(ns, \"line\");\n         line.setAttribute(\"x1\", -size * 0.85);\n         line.setAttribute(\"y1\", -size * 0.5 + row * size * 0.45);\n         line.setAttribute(\"x2\", size * 0.85);\n         line.setAttribute(\"y2\", -size * 0.5 + row * size * 0.45);\n         line.style.stroke = \"#475569\";\n         line.style.strokeWidth = \"2\";\n         g.appendChild(line);\n        }\n        for (let row = 0; row < 4; row++) {\n         const offset = row % 2 === 0 ? 0 : size * 0.35;\n         for (let col = 0; col < 3; col++) {\n          const line = document.createElementNS(ns, \"line\");\n          const x = -size * 0.5 + col * size * 0.7 + offset;\n          if (x > -size * 0.85 && x < size * 0.85) {\n           line.setAttribute(\"x1\", x);\n           line.setAttribute(\"y1\", -size * 0.8 + row * size * 0.45);\n           line.setAttribute(\"x2\", x);\n           line.setAttribute(\"y2\", -size * 0.8 + row * size * 0.45 + size * 0.45);\n           line.style.stroke = \"#475569\";\n           line.style.strokeWidth = \"2\";\n           g.appendChild(line);\n          }\n         }\n        }\n        return g;\n       }\n       if (shape === \"cloud\") {\n        const p = document.createElementNS(ns, \"path\");\n        const s = size;\n        p.setAttribute(\"d\", `\n             M ${-s * 0.8} ${s * 0.2}\n             Q ${-s * 1.1} ${s * 0.2} ${-s * 1.1} ${-s * 0.1}\n             Q ${-s * 1.1} ${-s * 0.5} ${-s * 0.7} ${-s * 0.5}\n             Q ${-s * 0.7} ${-s * 0.9} ${-s * 0.2} ${-s * 0.9}\n             Q ${s * 0.1} ${-s * 1.1} ${s * 0.5} ${-s * 0.8}\n             Q ${s * 1} ${-s * 0.8} ${s * 1.1} ${-s * 0.3}\n             Q ${s * 1.3} ${-s * 0.1} ${s * 1.1} ${s * 0.2}\n             Q ${s * 1.1} ${s * 0.5} ${s * 0.7} ${s * 0.5}\n             L ${-s * 0.5} ${s * 0.5}\n             Q ${-s * 0.9} ${s * 0.5} ${-s * 0.9} ${s * 0.2}\n             Z\n            `);\n        return p;\n       }\n       if (shape === \"database\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 0.7);\n        body.setAttribute(\"y\", -size * 0.6);\n        body.setAttribute(\"width\", size * 1.4);\n        body.setAttribute(\"height\", size * 1.4);\n        g.appendChild(body);\n        const top = document.createElementNS(ns, \"ellipse\");\n        top.setAttribute(\"cx\", 0);\n        top.setAttribute(\"cy\", -size * 0.6);\n        top.setAttribute(\"rx\", size * 0.7);\n        top.setAttribute(\"ry\", size * 0.25);\n        g.appendChild(top);\n        const bottom = document.createElementNS(ns, \"ellipse\");\n        bottom.setAttribute(\"cx\", 0);\n        bottom.setAttribute(\"cy\", size * 0.8);\n        bottom.setAttribute(\"rx\", size * 0.7);\n        bottom.setAttribute(\"ry\", size * 0.25);\n        g.appendChild(bottom);\n        const mid1 = document.createElementNS(ns, \"ellipse\");\n        mid1.setAttribute(\"cx\", 0);\n        mid1.setAttribute(\"cy\", -size * 0.15);\n        mid1.setAttribute(\"rx\", size * 0.7);\n        mid1.setAttribute(\"ry\", size * 0.2);\n        mid1.style.fill = \"none\";\n        mid1.style.stroke = \"#475569\";\n        mid1.style.strokeWidth = \"2\";\n        g.appendChild(mid1);\n        const mid2 = document.createElementNS(ns, \"ellipse\");\n        mid2.setAttribute(\"cx\", 0);\n        mid2.setAttribute(\"cy\", size * 0.35);\n        mid2.setAttribute(\"rx\", size * 0.7);\n        mid2.setAttribute(\"ry\", size * 0.2);\n        mid2.style.fill = \"none\";\n        mid2.style.stroke = \"#475569\";\n        mid2.style.strokeWidth = \"2\";\n        g.appendChild(mid2);\n        return g;\n       }\n       if (shape === \"printer\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size);\n        body.setAttribute(\"y\", -size * 0.4);\n        body.setAttribute(\"width\", size * 2);\n        body.setAttribute(\"height\", size * 0.9);\n        body.setAttribute(\"rx\", 4);\n        g.appendChild(body);\n        const trayTop = document.createElementNS(ns, \"rect\");\n        trayTop.setAttribute(\"x\", -size * 0.7);\n        trayTop.setAttribute(\"y\", -size * 0.8);\n        trayTop.setAttribute(\"width\", size * 1.4);\n        trayTop.setAttribute(\"height\", size * 0.4);\n        trayTop.setAttribute(\"rx\", 2);\n        trayTop.style.fill = \"#1e293b\";\n        g.appendChild(trayTop);\n        const trayOut = document.createElementNS(ns, \"rect\");\n        trayOut.setAttribute(\"x\", -size * 0.6);\n        trayOut.setAttribute(\"y\", size * 0.5);\n        trayOut.setAttribute(\"width\", size * 1.2);\n        trayOut.setAttribute(\"height\", size * 0.35);\n        trayOut.setAttribute(\"rx\", 2);\n        g.appendChild(trayOut);\n        const paper = document.createElementNS(ns, \"rect\");\n        paper.setAttribute(\"x\", -size * 0.5);\n        paper.setAttribute(\"y\", size * 0.3);\n        paper.setAttribute(\"width\", size * 1);\n        paper.setAttribute(\"height\", size * 0.5);\n        paper.style.fill = \"#e2e8f0\";\n        g.appendChild(paper);\n        return g;\n       }\n       if (shape === \"access-point\" || shape === \"wifi\") {\n        const g = document.createElementNS(ns, \"g\");\n        const base = document.createElementNS(ns, \"rect\");\n        base.setAttribute(\"x\", -size * 0.3);\n        base.setAttribute(\"y\", size * 0.2);\n        base.setAttribute(\"width\", size * 0.6);\n        base.setAttribute(\"height\", size * 0.3);\n        base.setAttribute(\"rx\", 3);\n        g.appendChild(base);\n        for (let i = 1; i <= 3; i++) {\n         const arc = document.createElementNS(ns, \"path\");\n         const r = size * 0.3 * i;\n         arc.setAttribute(\"d\", `M ${-r * 0.7} ${size * 0.1 - r * 0.5} A ${r} ${r} 0 0 1 ${r * 0.7} ${size * 0.1 - r * 0.5}`);\n         arc.style.fill = \"none\";\n         arc.style.stroke = \"currentColor\";\n         arc.style.strokeWidth = \"2\";\n         arc.style.opacity = 1 - (i * 0.2);\n         g.appendChild(arc);\n        }\n        return g;\n       }\n       if (shape === \"load-balancer\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size);\n        body.setAttribute(\"y\", -size * 0.5);\n        body.setAttribute(\"width\", size * 2);\n        body.setAttribute(\"height\", size);\n        body.setAttribute(\"rx\", 6);\n        g.appendChild(body);\n        const bar = document.createElementNS(ns, \"line\");\n        bar.setAttribute(\"x1\", -size * 0.6);\n        bar.setAttribute(\"y1\", 0);\n        bar.setAttribute(\"x2\", size * 0.6);\n        bar.setAttribute(\"y2\", 0);\n        bar.style.stroke = \"#4ade80\";\n        bar.style.strokeWidth = \"3\";\n        g.appendChild(bar);\n        [-1, 1].forEach(dir => {\n         const arrow = document.createElementNS(ns, \"path\");\n         arrow.setAttribute(\"d\", `M ${dir * size * 0.3} ${-size * 0.2} L ${dir * size * 0.6} 0 L ${dir * size * 0.3} ${size * 0.2}`);\n         arrow.style.fill = \"none\";\n         arrow.style.stroke = \"#4ade80\";\n         arrow.style.strokeWidth = \"2\";\n         g.appendChild(arrow);\n        });\n        return g;\n       }\n       if (shape === \"nas\" || shape === \"storage\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 0.8);\n        body.setAttribute(\"y\", -size * 0.9);\n        body.setAttribute(\"width\", size * 1.6);\n        body.setAttribute(\"height\", size * 1.8);\n        body.setAttribute(\"rx\", 4);\n        g.appendChild(body);\n        for (let i = 0; i < 4; i++) {\n         const bay = document.createElementNS(ns, \"rect\");\n         bay.setAttribute(\"x\", -size * 0.6);\n         bay.setAttribute(\"y\", -size * 0.7 + i * size * 0.4);\n         bay.setAttribute(\"width\", size * 1.2);\n         bay.setAttribute(\"height\", size * 0.3);\n         bay.setAttribute(\"rx\", 2);\n         bay.style.fill = \"#1e293b\";\n         g.appendChild(bay);\n        }\n        return g;\n       }\n       if (shape === \"gateway\" || shape === \"modem\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 0.5);\n        body.setAttribute(\"y\", -size * 0.9);\n        body.setAttribute(\"width\", size);\n        body.setAttribute(\"height\", size * 1.8);\n        body.setAttribute(\"rx\", 4);\n        g.appendChild(body);\n        for (let i = 0; i < 4; i++) {\n         const led = document.createElementNS(ns, \"circle\");\n         led.setAttribute(\"cx\", 0);\n         led.setAttribute(\"cy\", -size * 0.6 + i * size * 0.35);\n         led.setAttribute(\"r\", size * 0.08);\n         led.style.fill = [\"#4ade80\", \"#4ade80\", \"#facc15\", \"#60a5fa\"][i];\n         g.appendChild(led);\n        }\n        return g;\n       }\n       if (shape === \"vpn\" || shape === \"tunnel\") {\n        const g = document.createElementNS(ns, \"g\");\n        const outer = document.createElementNS(ns, \"ellipse\");\n        outer.setAttribute(\"cx\", 0);\n        outer.setAttribute(\"cy\", 0);\n        outer.setAttribute(\"rx\", size);\n        outer.setAttribute(\"ry\", size * 0.6);\n        g.appendChild(outer);\n        const inner = document.createElementNS(ns, \"ellipse\");\n        inner.setAttribute(\"cx\", 0);\n        inner.setAttribute(\"cy\", 0);\n        inner.setAttribute(\"rx\", size * 0.5);\n        inner.setAttribute(\"ry\", size * 0.3);\n        inner.style.fill = \"#1e293b\";\n        g.appendChild(inner);\n        const lock = document.createElementNS(ns, \"rect\");\n        lock.setAttribute(\"x\", -size * 0.15);\n        lock.setAttribute(\"y\", -size * 0.1);\n        lock.setAttribute(\"width\", size * 0.3);\n        lock.setAttribute(\"height\", size * 0.25);\n        lock.setAttribute(\"rx\", 2);\n        lock.style.fill = \"#4ade80\";\n        g.appendChild(lock);\n        return g;\n       }\n       if (shape === \"container\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size);\n        body.setAttribute(\"y\", -size * 0.5);\n        body.setAttribute(\"width\", size * 2);\n        body.setAttribute(\"height\", size);\n        body.setAttribute(\"rx\", 4);\n        g.appendChild(body);\n        for (let i = 0; i < 3; i++) {\n         const block = document.createElementNS(ns, \"rect\");\n         block.setAttribute(\"x\", -size * 0.8 + i * size * 0.55);\n         block.setAttribute(\"y\", -size * 0.3);\n         block.setAttribute(\"width\", size * 0.45);\n         block.setAttribute(\"height\", size * 0.6);\n         block.setAttribute(\"rx\", 2);\n         block.style.fill = \"#1e293b\";\n         g.appendChild(block);\n        }\n        return g;\n       }\n       if (shape === \"vm\" || shape === \"virtual\") {\n        const g = document.createElementNS(ns, \"g\");\n        const shadow = document.createElementNS(ns, \"rect\");\n        shadow.setAttribute(\"x\", -size * 0.85 + 4);\n        shadow.setAttribute(\"y\", -size * 0.65 + 4);\n        shadow.setAttribute(\"width\", size * 1.7);\n        shadow.setAttribute(\"height\", size * 1.3);\n        shadow.setAttribute(\"rx\", 4);\n        shadow.style.opacity = \"0.3\";\n        g.appendChild(shadow);\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 0.85);\n        body.setAttribute(\"y\", -size * 0.65);\n        body.setAttribute(\"width\", size * 1.7);\n        body.setAttribute(\"height\", size * 1.3);\n        body.setAttribute(\"rx\", 4);\n        g.appendChild(body);\n        const screen = document.createElementNS(ns, \"rect\");\n        screen.setAttribute(\"x\", -size * 0.65);\n        screen.setAttribute(\"y\", -size * 0.45);\n        screen.setAttribute(\"width\", size * 1.3);\n        screen.setAttribute(\"height\", size * 0.7);\n        screen.setAttribute(\"rx\", 2);\n        screen.style.fill = \"#1e293b\";\n        g.appendChild(screen);\n        return g;\n       }\n       if (shape === \"kubernetes\" || shape === \"k8s\") {\n        const g = document.createElementNS(ns, \"g\");\n        const outer = document.createElementNS(ns, \"circle\");\n        outer.setAttribute(\"cx\", 0);\n        outer.setAttribute(\"cy\", 0);\n        outer.setAttribute(\"r\", size);\n        g.appendChild(outer);\n        const inner = document.createElementNS(ns, \"circle\");\n        inner.setAttribute(\"cx\", 0);\n        inner.setAttribute(\"cy\", 0);\n        inner.setAttribute(\"r\", size * 0.4);\n        inner.style.fill = \"#1e293b\";\n        g.appendChild(inner);\n        for (let i = 0; i < 7; i++) {\n         const angle = (Math.PI * 2 / 7) * i - Math.PI / 2;\n         const spoke = document.createElementNS(ns, \"line\");\n         spoke.setAttribute(\"x1\", Math.cos(angle) * size * 0.4);\n         spoke.setAttribute(\"y1\", Math.sin(angle) * size * 0.4);\n         spoke.setAttribute(\"x2\", Math.cos(angle) * size * 0.85);\n         spoke.setAttribute(\"y2\", Math.sin(angle) * size * 0.85);\n         spoke.style.stroke = \"#326ce5\";\n         spoke.style.strokeWidth = \"3\";\n         g.appendChild(spoke);\n        }\n        return g;\n       }\n       if (shape === \"shield\" || shape === \"security\") {\n        const p = document.createElementNS(ns, \"path\");\n        const s = size;\n        p.setAttribute(\"d\", `M 0 ${-s} L ${s * 0.85} ${-s * 0.5} L ${s * 0.85} ${s * 0.2} Q ${s * 0.7} ${s * 0.9} 0 ${s} Q ${-s * 0.7} ${s * 0.9} ${-s * 0.85} ${s * 0.2} L ${-s * 0.85} ${-s * 0.5} Z`);\n        return p;\n       }\n       if (shape === \"camera\" || shape === \"cctv\") {\n        const g = document.createElementNS(ns, \"g\");\n        const mount = document.createElementNS(ns, \"rect\");\n        mount.setAttribute(\"x\", -size * 1.1);\n        mount.setAttribute(\"y\", -size * 0.5);\n        mount.setAttribute(\"width\", size * 0.25);\n        mount.setAttribute(\"height\", size * 0.6);\n        mount.setAttribute(\"rx\", 2);\n        g.appendChild(mount);\n        const arm = document.createElementNS(ns, \"rect\");\n        arm.setAttribute(\"x\", -size * 0.9);\n        arm.setAttribute(\"y\", -size * 0.15);\n        arm.setAttribute(\"width\", size * 0.5);\n        arm.setAttribute(\"height\", size * 0.15);\n        g.appendChild(arm);\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 0.45);\n        body.setAttribute(\"y\", -size * 0.35);\n        body.setAttribute(\"width\", size * 1.1);\n        body.setAttribute(\"height\", size * 0.7);\n        body.setAttribute(\"rx\", 4);\n        g.appendChild(body);\n        const lensHousing = document.createElementNS(ns, \"circle\");\n        lensHousing.setAttribute(\"cx\", size * 0.85);\n        lensHousing.setAttribute(\"cy\", 0);\n        lensHousing.setAttribute(\"r\", size * 0.4);\n        g.appendChild(lensHousing);\n        const lensOuter = document.createElementNS(ns, \"circle\");\n        lensOuter.setAttribute(\"cx\", size * 0.85);\n        lensOuter.setAttribute(\"cy\", 0);\n        lensOuter.setAttribute(\"r\", size * 0.28);\n        lensOuter.style.fill = \"#1e293b\";\n        g.appendChild(lensOuter);\n        const lensInner = document.createElementNS(ns, \"circle\");\n        lensInner.setAttribute(\"cx\", size * 0.85);\n        lensInner.setAttribute(\"cy\", 0);\n        lensInner.setAttribute(\"r\", size * 0.15);\n        lensInner.style.fill = \"#3b82f6\";\n        g.appendChild(lensInner);\n        const reflection = document.createElementNS(ns, \"circle\");\n        reflection.setAttribute(\"cx\", size * 0.8);\n        reflection.setAttribute(\"cy\", -size * 0.05);\n        reflection.setAttribute(\"r\", size * 0.05);\n        reflection.style.fill = \"#93c5fd\";\n        g.appendChild(reflection);\n        const led = document.createElementNS(ns, \"circle\");\n        led.setAttribute(\"cx\", -size * 0.2);\n        led.setAttribute(\"cy\", -size * 0.15);\n        led.setAttribute(\"r\", size * 0.06);\n        led.style.fill = \"#ef4444\";\n        g.appendChild(led);\n        return g;\n       }\n       if (shape === \"monitor\" || shape === \"dashboard\") {\n        const g = document.createElementNS(ns, \"g\");\n        const screen = document.createElementNS(ns, \"rect\");\n        screen.setAttribute(\"x\", -size);\n        screen.setAttribute(\"y\", -size * 0.7);\n        screen.setAttribute(\"width\", size * 2);\n        screen.setAttribute(\"height\", size * 1.2);\n        screen.setAttribute(\"rx\", 4);\n        g.appendChild(screen);\n        const graph = document.createElementNS(ns, \"polyline\");\n        graph.setAttribute(\"points\", `${-size * 0.8},${size * 0.2} ${-size * 0.3},${-size * 0.2} ${size * 0.2},${size * 0.1} ${size * 0.7},${-size * 0.3}`);\n        graph.style.fill = \"none\";\n        graph.style.stroke = \"#4ade80\";\n        graph.style.strokeWidth = \"3\";\n        g.appendChild(graph);\n        return g;\n       }\n       if (shape === \"docker\" || shape === \"whale\") {\n        const g = document.createElementNS(ns, \"g\");\n        const s = size;\n        const body = document.createElementNS(ns, \"path\");\n        body.setAttribute(\"d\", `\n         M ${-s * 0.9} ${s * 0.2}\n         Q ${-s * 1.1} ${s * 0.5} ${-s * 0.7} ${s * 0.7}\n         Q ${-s * 0.3} ${s * 0.85} ${s * 0.3} ${s * 0.75}\n         Q ${s * 0.8} ${s * 0.6} ${s * 1.0} ${s * 0.3}\n         Q ${s * 1.1} ${s * 0.1} ${s * 1.0} ${-s * 0.1}\n         L ${s * 0.85} ${-s * 0.1}\n         Q ${s * 0.7} ${-s * 0.15} ${s * 0.4} ${-s * 0.2}\n         L ${-s * 0.5} ${-s * 0.2}\n         Q ${-s * 0.8} ${-s * 0.1} ${-s * 0.9} ${s * 0.2}\n         Z\n        `);\n        body.style.fill = \"#0db7ed\";\n        g.appendChild(body);\n        const tail = document.createElementNS(ns, \"path\");\n        tail.setAttribute(\"d\", `\n         M ${-s * 0.85} ${s * 0.1}\n         Q ${-s * 1.2} ${-s * 0.2} ${-s * 1.0} ${-s * 0.55}\n         Q ${-s * 0.95} ${-s * 0.35} ${-s * 0.8} ${-s * 0.15}\n         Z\n        `);\n        tail.style.fill = \"#0db7ed\";\n        g.appendChild(tail);\n        const belly = document.createElementNS(ns, \"path\");\n        belly.setAttribute(\"d\", `\n         M ${-s * 0.5} ${s * 0.65}\n         Q ${s * 0.1} ${s * 0.75} ${s * 0.6} ${s * 0.55}\n         Q ${s * 0.8} ${s * 0.45} ${s * 0.9} ${s * 0.25}\n         Q ${s * 0.7} ${s * 0.5} ${s * 0.3} ${s * 0.6}\n         Q ${-s * 0.1} ${s * 0.7} ${-s * 0.5} ${s * 0.65}\n         Z\n        `);\n        belly.style.fill = \"#ffffff\";\n        belly.style.opacity = \"0.3\";\n        g.appendChild(belly);\n        for (let row = 0; row < 2; row++) {\n         for (let col = 0; col < 3; col++) {\n          const container = document.createElementNS(ns, \"rect\");\n          container.setAttribute(\"x\", -s * 0.45 + col * s * 0.35);\n          container.setAttribute(\"y\", -s * 0.7 + row * s * 0.28);\n          container.setAttribute(\"width\", s * 0.3);\n          container.setAttribute(\"height\", s * 0.23);\n          container.setAttribute(\"rx\", 2);\n          container.style.fill = \"#0db7ed\";\n          container.style.stroke = \"#0a9ed8\";\n          container.style.strokeWidth = \"1.5\";\n          g.appendChild(container);\n         }\n        }\n        const topContainer = document.createElementNS(ns, \"rect\");\n        topContainer.setAttribute(\"x\", -s * 0.1);\n        topContainer.setAttribute(\"y\", -s * 0.95);\n        topContainer.setAttribute(\"width\", s * 0.3);\n        topContainer.setAttribute(\"height\", s * 0.23);\n        topContainer.setAttribute(\"rx\", 2);\n        topContainer.style.fill = \"#0db7ed\";\n        topContainer.style.stroke = \"#0a9ed8\";\n        topContainer.style.strokeWidth = \"1.5\";\n        g.appendChild(topContainer);\n        const eye = document.createElementNS(ns, \"circle\");\n        eye.setAttribute(\"cx\", s * 0.65);\n        eye.setAttribute(\"cy\", s * 0.25);\n        eye.setAttribute(\"r\", s * 0.08);\n        eye.style.fill = \"#0a5f7a\";\n        g.appendChild(eye);\n        return g;\n       }\n       if (shape === \"rounded-square\") {\n        const r = document.createElementNS(ns, \"rect\");\n        const s = size * 1.6;\n        r.setAttribute(\"x\", -s / 2);\n        r.setAttribute(\"y\", -s / 2);\n        r.setAttribute(\"width\", s);\n        r.setAttribute(\"height\", s);\n        r.setAttribute(\"rx\", size * 0.4);\n        return r;\n       }\n       if (shape === \"pill\" || shape === \"capsule\") {\n        const r = document.createElementNS(ns, \"rect\");\n        r.setAttribute(\"x\", -size * 1.2);\n        r.setAttribute(\"y\", -size * 0.5);\n        r.setAttribute(\"width\", size * 2.4);\n        r.setAttribute(\"height\", size);\n        r.setAttribute(\"rx\", size * 0.5);\n        return r;\n       }\n       if (shape === \"octagon\") {\n        const p = document.createElementNS(ns, \"polygon\");\n        const r = size;\n        const pts = [];\n        for (let i = 0; i < 8; i++) {\n         const a = (Math.PI / 4) * i - Math.PI / 8;\n         pts.push([Math.cos(a) * r, Math.sin(a) * r]);\n        }\n        p.setAttribute(\"points\", pts.map(pt => pt.join(\",\")).join(\" \"));\n        return p;\n       }\n       if (shape === \"pentagon\") {\n        const p = document.createElementNS(ns, \"polygon\");\n        const pts = [];\n        for (let i = 0; i < 5; i++) {\n         const a = (Math.PI * 2 / 5) * i - Math.PI / 2;\n         pts.push([Math.cos(a) * size, Math.sin(a) * size]);\n        }\n        p.setAttribute(\"points\", pts.map(pt => pt.join(\",\")).join(\" \"));\n        return p;\n       }\n       if (shape === \"cross\" || shape === \"plus\") {\n        const p = document.createElementNS(ns, \"polygon\");\n        const s = size;\n        const t = size * 0.35;\n        p.setAttribute(\"points\", `${-t},${-s} ${t},${-s} ${t},${-t} ${s},${-t} ${s},${t} ${t},${t} ${t},${s} ${-t},${s} ${-t},${t} ${-s},${t} ${-s},${-t} ${-t},${-t}`);\n        return p;\n       }\n       if (shape === \"parallelogram\") {\n        const p = document.createElementNS(ns, \"polygon\");\n        const s = size;\n        p.setAttribute(\"points\", `${-s * 0.6},${-s * 0.6} ${s * 1.2},${-s * 0.6} ${s * 0.6},${s * 0.6} ${-s * 1.2},${s * 0.6}`);\n        return p;\n       }\n       if (shape === \"trapezoid\") {\n        const p = document.createElementNS(ns, \"polygon\");\n        const s = size;\n        p.setAttribute(\"points\", `${-s * 0.6},${-s * 0.6} ${s * 0.6},${-s * 0.6} ${s},${s * 0.6} ${-s},${s * 0.6}`);\n        return p;\n       }\n       if (shape === \"sensor\" || shape === \"iot\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"circle\");\n        body.setAttribute(\"cx\", 0);\n        body.setAttribute(\"cy\", 0);\n        body.setAttribute(\"r\", size * 0.7);\n        g.appendChild(body);\n        const ant = document.createElementNS(ns, \"line\");\n        ant.setAttribute(\"x1\", 0);\n        ant.setAttribute(\"y1\", -size * 0.7);\n        ant.setAttribute(\"x2\", 0);\n        ant.setAttribute(\"y2\", -size);\n        ant.style.stroke = \"currentColor\";\n        ant.style.strokeWidth = \"2\";\n        g.appendChild(ant);\n        const tip = document.createElementNS(ns, \"circle\");\n        tip.setAttribute(\"cx\", 0);\n        tip.setAttribute(\"cy\", -size);\n        tip.setAttribute(\"r\", size * 0.1);\n        tip.style.fill = \"#4ade80\";\n        g.appendChild(tip);\n        return g;\n       }\n       if (shape === \"pi\" || shape === \"sbc\" || shape === \"raspberry\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size);\n        body.setAttribute(\"y\", -size * 0.65);\n        body.setAttribute(\"width\", size * 2);\n        body.setAttribute(\"height\", size * 1.3);\n        body.setAttribute(\"rx\", 4);\n        g.appendChild(body);\n        for (let i = 0; i < 10; i++) {\n         const pin = document.createElementNS(ns, \"rect\");\n         pin.setAttribute(\"x\", -size * 0.85 + i * size * 0.19);\n         pin.setAttribute(\"y\", -size * 0.8);\n         pin.setAttribute(\"width\", size * 0.08);\n         pin.setAttribute(\"height\", size * 0.15);\n         pin.style.fill = \"#facc15\";\n         g.appendChild(pin);\n        }\n        const port = document.createElementNS(ns, \"rect\");\n        port.setAttribute(\"x\", size * 0.6);\n        port.setAttribute(\"y\", -size * 0.2);\n        port.setAttribute(\"width\", size * 0.35);\n        port.setAttribute(\"height\", size * 0.4);\n        port.style.fill = \"#1e293b\";\n        g.appendChild(port);\n        return g;\n       }\n       if (shape === \"api\" || shape === \"endpoint\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 0.9);\n        body.setAttribute(\"y\", -size * 0.7);\n        body.setAttribute(\"width\", size * 1.8);\n        body.setAttribute(\"height\", size * 1.4);\n        body.setAttribute(\"rx\", 6);\n        g.appendChild(body);\n        const left = document.createElementNS(ns, \"text\");\n        left.setAttribute(\"x\", -size * 0.5);\n        left.setAttribute(\"y\", size * 0.15);\n        left.setAttribute(\"font-size\", size * 0.9);\n        left.setAttribute(\"fill\", \"#4ade80\");\n        left.setAttribute(\"font-family\", \"monospace\");\n        left.textContent = \"{\";\n        g.appendChild(left);\n        const right = document.createElementNS(ns, \"text\");\n        right.setAttribute(\"x\", size * 0.15);\n        right.setAttribute(\"y\", size * 0.15);\n        right.setAttribute(\"font-size\", size * 0.9);\n        right.setAttribute(\"fill\", \"#4ade80\");\n        right.setAttribute(\"font-family\", \"monospace\");\n        right.textContent = \"}\";\n        g.appendChild(right);\n        return g;\n       }\n       if (shape === \"queue\" || shape === \"message\") {\n        const g = document.createElementNS(ns, \"g\");\n        for (let i = 2; i >= 0; i--) {\n         const card = document.createElementNS(ns, \"rect\");\n         card.setAttribute(\"x\", -size * 0.7 + i * 4);\n         card.setAttribute(\"y\", -size * 0.5 + i * 4);\n         card.setAttribute(\"width\", size * 1.4);\n         card.setAttribute(\"height\", size * 0.8);\n         card.setAttribute(\"rx\", 3);\n         card.style.opacity = 1 - i * 0.25;\n         g.appendChild(card);\n        }\n        return g;\n       }\n       if (shape === \"lambda\" || shape === \"function\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 0.8);\n        body.setAttribute(\"y\", -size * 0.8);\n        body.setAttribute(\"width\", size * 1.6);\n        body.setAttribute(\"height\", size * 1.6);\n        body.setAttribute(\"rx\", 4);\n        g.appendChild(body);\n        const lambda = document.createElementNS(ns, \"text\");\n        lambda.setAttribute(\"x\", 0);\n        lambda.setAttribute(\"y\", size * 0.2);\n        lambda.setAttribute(\"font-size\", size * 1.2);\n        lambda.setAttribute(\"fill\", \"#f59e0b\");\n        lambda.setAttribute(\"text-anchor\", \"middle\");\n        lambda.setAttribute(\"font-family\", \"serif\");\n        lambda.textContent = \"λ\";\n        g.appendChild(lambda);\n        return g;\n       }\n       if (shape === \"bucket\" || shape === \"s3\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"path\");\n        body.setAttribute(\"d\", `M ${-size * 0.8} ${-size * 0.7} L ${-size * 0.6} ${size * 0.8} Q ${-size * 0.5} ${size} 0 ${size} Q ${size * 0.5} ${size} ${size * 0.6} ${size * 0.8} L ${size * 0.8} ${-size * 0.7} Z`);\n        g.appendChild(body);\n        const rim = document.createElementNS(ns, \"ellipse\");\n        rim.setAttribute(\"cx\", 0);\n        rim.setAttribute(\"cy\", -size * 0.7);\n        rim.setAttribute(\"rx\", size * 0.8);\n        rim.setAttribute(\"ry\", size * 0.25);\n        g.appendChild(rim);\n        return g;\n       }\n       if (shape === \"thermostat\") {\n        const g = document.createElementNS(ns, \"g\");\n        const outer = document.createElementNS(ns, \"circle\");\n        outer.setAttribute(\"r\", size);\n        g.appendChild(outer);\n        const inner = document.createElementNS(ns, \"circle\");\n        inner.setAttribute(\"r\", size * 0.75);\n        inner.style.fill = \"#1e293b\";\n        g.appendChild(inner);\n        const temp = document.createElementNS(ns, \"text\");\n        temp.setAttribute(\"x\", 0);\n        temp.setAttribute(\"y\", size * 0.15);\n        temp.setAttribute(\"font-size\", size * 0.5);\n        temp.setAttribute(\"fill\", \"#4ade80\");\n        temp.setAttribute(\"text-anchor\", \"middle\");\n        temp.setAttribute(\"font-family\", \"monospace\");\n        temp.textContent = \"72°\";\n        g.appendChild(temp);\n        return g;\n       }\n       if (shape === \"doorbell\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 0.5);\n        body.setAttribute(\"y\", -size);\n        body.setAttribute(\"width\", size);\n        body.setAttribute(\"height\", size * 2);\n        body.setAttribute(\"rx\", size * 0.25);\n        g.appendChild(body);\n        const lens = document.createElementNS(ns, \"circle\");\n        lens.setAttribute(\"cx\", 0);\n        lens.setAttribute(\"cy\", -size * 0.35);\n        lens.setAttribute(\"r\", size * 0.3);\n        lens.style.fill = \"#1e293b\";\n        g.appendChild(lens);\n        const lensDot = document.createElementNS(ns, \"circle\");\n        lensDot.setAttribute(\"cx\", 0);\n        lensDot.setAttribute(\"cy\", -size * 0.35);\n        lensDot.setAttribute(\"r\", size * 0.12);\n        lensDot.style.fill = \"#3b82f6\";\n        g.appendChild(lensDot);\n        const btn = document.createElementNS(ns, \"circle\");\n        btn.setAttribute(\"cx\", 0);\n        btn.setAttribute(\"cy\", size * 0.5);\n        btn.setAttribute(\"r\", size * 0.2);\n        btn.style.fill = \"#f59e0b\";\n        g.appendChild(btn);\n        return g;\n       }\n       if (shape === \"smart-lock\") {\n        const g = document.createElementNS(ns, \"g\");\n        const shackle = document.createElementNS(ns, \"path\");\n        shackle.setAttribute(\"d\", `M ${-size * 0.4} ${-size * 0.1} L ${-size * 0.4} ${-size * 0.6} A ${size * 0.4} ${size * 0.4} 0 1 1 ${size * 0.4} ${-size * 0.6} L ${size * 0.4} ${-size * 0.1}`);\n        shackle.style.fill = \"none\";\n        shackle.style.strokeWidth = size * 0.2;\n        g.appendChild(shackle);\n        const lockBody = document.createElementNS(ns, \"rect\");\n        lockBody.setAttribute(\"x\", -size * 0.6);\n        lockBody.setAttribute(\"y\", -size * 0.15);\n        lockBody.setAttribute(\"width\", size * 1.2);\n        lockBody.setAttribute(\"height\", size * 1);\n        lockBody.setAttribute(\"rx\", 4);\n        g.appendChild(lockBody);\n        const keyhole = document.createElementNS(ns, \"circle\");\n        keyhole.setAttribute(\"cx\", 0);\n        keyhole.setAttribute(\"cy\", size * 0.3);\n        keyhole.setAttribute(\"r\", size * 0.15);\n        keyhole.style.fill = \"#4ade80\";\n        g.appendChild(keyhole);\n        return g;\n       }\n       if (shape === \"smart-bulb\") {\n        const g = document.createElementNS(ns, \"g\");\n        const bulb = document.createElementNS(ns, \"path\");\n        bulb.setAttribute(\"d\", `M ${-size * 0.5} ${size * 0.2} Q ${-size * 0.8} ${-size * 0.3} ${-size * 0.5} ${-size * 0.7} Q 0 ${-size * 1.1} ${size * 0.5} ${-size * 0.7} Q ${size * 0.8} ${-size * 0.3} ${size * 0.5} ${size * 0.2} Z`);\n        g.appendChild(bulb);\n        const base1 = document.createElementNS(ns, \"rect\");\n        base1.setAttribute(\"x\", -size * 0.35);\n        base1.setAttribute(\"y\", size * 0.2);\n        base1.setAttribute(\"width\", size * 0.7);\n        base1.setAttribute(\"height\", size * 0.15);\n        base1.style.fill = \"#94a3b8\";\n        g.appendChild(base1);\n        const base2 = document.createElementNS(ns, \"rect\");\n        base2.setAttribute(\"x\", -size * 0.3);\n        base2.setAttribute(\"y\", size * 0.35);\n        base2.setAttribute(\"width\", size * 0.6);\n        base2.setAttribute(\"height\", size * 0.15);\n        base2.style.fill = \"#64748b\";\n        g.appendChild(base2);\n        const base3 = document.createElementNS(ns, \"rect\");\n        base3.setAttribute(\"x\", -size * 0.25);\n        base3.setAttribute(\"y\", size * 0.5);\n        base3.setAttribute(\"width\", size * 0.5);\n        base3.setAttribute(\"height\", size * 0.2);\n        base3.setAttribute(\"rx\", 2);\n        base3.style.fill = \"#475569\";\n        g.appendChild(base3);\n        return g;\n       }\n       if (shape === \"smart-plug\") {\n        const g = document.createElementNS(ns, \"g\");\n        const plugBody = document.createElementNS(ns, \"rect\");\n        plugBody.setAttribute(\"x\", -size * 0.7);\n        plugBody.setAttribute(\"y\", -size * 0.6);\n        plugBody.setAttribute(\"width\", size * 1.4);\n        plugBody.setAttribute(\"height\", size * 1.2);\n        plugBody.setAttribute(\"rx\", 6);\n        g.appendChild(plugBody);\n        const hole1 = document.createElementNS(ns, \"rect\");\n        hole1.setAttribute(\"x\", -size * 0.35);\n        hole1.setAttribute(\"y\", -size * 0.3);\n        hole1.setAttribute(\"width\", size * 0.15);\n        hole1.setAttribute(\"height\", size * 0.4);\n        hole1.setAttribute(\"rx\", 2);\n        hole1.style.fill = \"#1e293b\";\n        g.appendChild(hole1);\n        const hole2 = document.createElementNS(ns, \"rect\");\n        hole2.setAttribute(\"x\", size * 0.2);\n        hole2.setAttribute(\"y\", -size * 0.3);\n        hole2.setAttribute(\"width\", size * 0.15);\n        hole2.setAttribute(\"height\", size * 0.4);\n        hole2.setAttribute(\"rx\", 2);\n        hole2.style.fill = \"#1e293b\";\n        g.appendChild(hole2);\n        const led = document.createElementNS(ns, \"circle\");\n        led.setAttribute(\"cx\", 0);\n        led.setAttribute(\"cy\", size * 0.35);\n        led.setAttribute(\"r\", size * 0.1);\n        led.style.fill = \"#4ade80\";\n        g.appendChild(led);\n        return g;\n       }\n       if (shape === \"smart-speaker\") {\n        const g = document.createElementNS(ns, \"g\");\n        const speakerBody = document.createElementNS(ns, \"rect\");\n        speakerBody.setAttribute(\"x\", -size * 0.6);\n        speakerBody.setAttribute(\"y\", -size);\n        speakerBody.setAttribute(\"width\", size * 1.2);\n        speakerBody.setAttribute(\"height\", size * 2);\n        speakerBody.setAttribute(\"rx\", size * 0.3);\n        g.appendChild(speakerBody);\n        const mesh = document.createElementNS(ns, \"rect\");\n        mesh.setAttribute(\"x\", -size * 0.5);\n        mesh.setAttribute(\"y\", -size * 0.3);\n        mesh.setAttribute(\"width\", size);\n        mesh.setAttribute(\"height\", size * 1.1);\n        mesh.setAttribute(\"rx\", 4);\n        mesh.style.fill = \"#1e293b\";\n        g.appendChild(mesh);\n        const ring = document.createElementNS(ns, \"circle\");\n        ring.setAttribute(\"cx\", 0);\n        ring.setAttribute(\"cy\", -size * 0.65);\n        ring.setAttribute(\"r\", size * 0.2);\n        ring.style.fill = \"#3b82f6\";\n        g.appendChild(ring);\n        return g;\n       }\n       if (shape === \"smart-tv\") {\n        const g = document.createElementNS(ns, \"g\");\n        const frame = document.createElementNS(ns, \"rect\");\n        frame.setAttribute(\"x\", -size * 1.4);\n        frame.setAttribute(\"y\", -size * 0.85);\n        frame.setAttribute(\"width\", size * 2.8);\n        frame.setAttribute(\"height\", size * 1.6);\n        frame.setAttribute(\"rx\", 4);\n        g.appendChild(frame);\n        const screen = document.createElementNS(ns, \"rect\");\n        screen.setAttribute(\"x\", -size * 1.3);\n        screen.setAttribute(\"y\", -size * 0.75);\n        screen.setAttribute(\"width\", size * 2.6);\n        screen.setAttribute(\"height\", size * 1.4);\n        screen.setAttribute(\"rx\", 2);\n        screen.style.fill = \"#1e293b\";\n        g.appendChild(screen);\n        const stand = document.createElementNS(ns, \"rect\");\n        stand.setAttribute(\"x\", -size * 0.8);\n        stand.setAttribute(\"y\", size * 0.75);\n        stand.setAttribute(\"width\", size * 1.6);\n        stand.setAttribute(\"height\", size * 0.12);\n        stand.setAttribute(\"rx\", 2);\n        g.appendChild(stand);\n        return g;\n       }\n       if (shape === \"hub\") {\n        const g = document.createElementNS(ns, \"g\");\n        const hubBody = document.createElementNS(ns, \"rect\");\n        hubBody.setAttribute(\"x\", -size * 0.9);\n        hubBody.setAttribute(\"y\", -size * 0.5);\n        hubBody.setAttribute(\"width\", size * 1.8);\n        hubBody.setAttribute(\"height\", size);\n        hubBody.setAttribute(\"rx\", 8);\n        g.appendChild(hubBody);\n        for (let i = 0; i < 4; i++) {\n         const led = document.createElementNS(ns, \"circle\");\n         led.setAttribute(\"cx\", -size * 0.5 + i * size * 0.35);\n         led.setAttribute(\"cy\", 0);\n         led.setAttribute(\"r\", size * 0.08);\n         led.style.fill = [\"#4ade80\", \"#3b82f6\", \"#f59e0b\", \"#ef4444\"][i];\n         g.appendChild(led);\n        }\n        return g;\n       }\n       if (shape === \"smoke-detector\") {\n        const g = document.createElementNS(ns, \"g\");\n        const outer = document.createElementNS(ns, \"circle\");\n        outer.setAttribute(\"r\", size);\n        g.appendChild(outer);\n        const inner = document.createElementNS(ns, \"circle\");\n        inner.setAttribute(\"r\", size * 0.6);\n        inner.style.fill = \"#1e293b\";\n        g.appendChild(inner);\n        for (let i = 0; i < 6; i++) {\n         const slot = document.createElementNS(ns, \"rect\");\n         const angle = (i * 60 - 90) * Math.PI / 180;\n         slot.setAttribute(\"x\", Math.cos(angle) * size * 0.35 - size * 0.08);\n         slot.setAttribute(\"y\", Math.sin(angle) * size * 0.35 - size * 0.03);\n         slot.setAttribute(\"width\", size * 0.16);\n         slot.setAttribute(\"height\", size * 0.06);\n         slot.setAttribute(\"rx\", 1);\n         slot.style.fill = \"#475569\";\n         slot.setAttribute(\"transform\", `rotate(${i * 60}, ${Math.cos(angle) * size * 0.35}, ${Math.sin(angle) * size * 0.35})`);\n         g.appendChild(slot);\n        }\n        const led = document.createElementNS(ns, \"circle\");\n        led.setAttribute(\"cx\", 0);\n        led.setAttribute(\"cy\", 0);\n        led.setAttribute(\"r\", size * 0.1);\n        led.style.fill = \"#ef4444\";\n        g.appendChild(led);\n        return g;\n       }\n       if (shape === \"motion-sensor\") {\n        const g = document.createElementNS(ns, \"g\");\n        const dome = document.createElementNS(ns, \"path\");\n        dome.setAttribute(\"d\", `M ${-size * 0.8} ${size * 0.3} Q ${-size * 0.8} ${-size * 0.8} 0 ${-size * 0.8} Q ${size * 0.8} ${-size * 0.8} ${size * 0.8} ${size * 0.3} Z`);\n        g.appendChild(dome);\n        const base = document.createElementNS(ns, \"rect\");\n        base.setAttribute(\"x\", -size * 0.8);\n        base.setAttribute(\"y\", size * 0.3);\n        base.setAttribute(\"width\", size * 1.6);\n        base.setAttribute(\"height\", size * 0.35);\n        base.setAttribute(\"rx\", 3);\n        g.appendChild(base);\n        const lens = document.createElementNS(ns, \"ellipse\");\n        lens.setAttribute(\"cx\", 0);\n        lens.setAttribute(\"cy\", -size * 0.15);\n        lens.setAttribute(\"rx\", size * 0.35);\n        lens.setAttribute(\"ry\", size * 0.25);\n        lens.style.fill = \"#1e293b\";\n        g.appendChild(lens);\n        const led = document.createElementNS(ns, \"circle\");\n        led.setAttribute(\"cx\", 0);\n        led.setAttribute(\"cy\", size * 0.45);\n        led.setAttribute(\"r\", size * 0.08);\n        led.style.fill = \"#4ade80\";\n        g.appendChild(led);\n        return g;\n       }\n       if (shape === \"garage\") {\n        const g = document.createElementNS(ns, \"g\");\n        const frame = document.createElementNS(ns, \"rect\");\n        frame.setAttribute(\"x\", -size);\n        frame.setAttribute(\"y\", -size * 0.9);\n        frame.setAttribute(\"width\", size * 2);\n        frame.setAttribute(\"height\", size * 1.8);\n        frame.setAttribute(\"rx\", 4);\n        g.appendChild(frame);\n        for (let i = 0; i < 4; i++) {\n         const panel = document.createElementNS(ns, \"rect\");\n         panel.setAttribute(\"x\", -size * 0.9);\n         panel.setAttribute(\"y\", -size * 0.8 + i * size * 0.42);\n         panel.setAttribute(\"width\", size * 1.8);\n         panel.setAttribute(\"height\", size * 0.35);\n         panel.setAttribute(\"rx\", 2);\n         panel.style.fill = \"#1e293b\";\n         g.appendChild(panel);\n        }\n        return g;\n       }\n       if (shape === \"sprinkler\") {\n        const g = document.createElementNS(ns, \"g\");\n        const head = document.createElementNS(ns, \"circle\");\n        head.setAttribute(\"r\", size * 0.5);\n        g.appendChild(head);\n        const nozzle = document.createElementNS(ns, \"rect\");\n        nozzle.setAttribute(\"x\", -size * 0.15);\n        nozzle.setAttribute(\"y\", size * 0.3);\n        nozzle.setAttribute(\"width\", size * 0.3);\n        nozzle.setAttribute(\"height\", size * 0.5);\n        g.appendChild(nozzle);\n        for (let i = 0; i < 5; i++) {\n         const spray = document.createElementNS(ns, \"line\");\n         const angle = (-60 + i * 30) * Math.PI / 180;\n         spray.setAttribute(\"x1\", 0);\n         spray.setAttribute(\"y1\", -size * 0.3);\n         spray.setAttribute(\"x2\", Math.cos(angle) * size * 0.8);\n         spray.setAttribute(\"y2\", Math.sin(angle) * size * 0.8 - size * 0.3);\n         spray.style.stroke = \"#3b82f6\";\n         spray.style.strokeWidth = \"2\";\n         spray.style.strokeDasharray = \"3,3\";\n         g.appendChild(spray);\n        }\n        return g;\n       }\n       if (shape === \"vacuum\") {\n        const g = document.createElementNS(ns, \"g\");\n        const vacBody = document.createElementNS(ns, \"circle\");\n        vacBody.setAttribute(\"r\", size);\n        g.appendChild(vacBody);\n        const top = document.createElementNS(ns, \"circle\");\n        top.setAttribute(\"r\", size * 0.7);\n        top.style.fill = \"#1e293b\";\n        g.appendChild(top);\n        const bumper = document.createElementNS(ns, \"path\");\n        bumper.setAttribute(\"d\", `M ${-size * 0.7} ${-size * 0.4} A ${size * 0.8} ${size * 0.8} 0 0 1 ${size * 0.7} ${-size * 0.4}`);\n        bumper.style.fill = \"none\";\n        bumper.style.strokeWidth = size * 0.15;\n        g.appendChild(bumper);\n        const btn = document.createElementNS(ns, \"circle\");\n        btn.setAttribute(\"cx\", 0);\n        btn.setAttribute(\"cy\", size * 0.1);\n        btn.setAttribute(\"r\", size * 0.2);\n        btn.style.fill = \"#4ade80\";\n        g.appendChild(btn);\n        return g;\n       }\n       const c = document.createElementNS(ns, \"circle\");\n       c.setAttribute(\"r\", size);\n       return c;\n      }\n      function createNodeShape(id, size) {\n       const styles = resolveStylesForNode(id);\n       if (styles.icon && styles.icon.library && styles.icon.name) {\n        const g = document.createElementNS(\"http://www.w3.org/2000/svg\", \"g\");\n        g.classList.add(\"node-circle\");\n        IconLibrary.getIcon(styles.icon.library, styles.icon.name).then(svgText => {\n         if (svgText) {\n          const parser = new DOMParser();\n          const doc = parser.parseFromString(svgText, 'image/svg+xml');\n          const svgEl = doc.querySelector('svg');\n          if (svgEl) {\n           svgEl.setAttribute('width', size * 1.2);\n           svgEl.setAttribute('height', size * 1.2);\n           svgEl.setAttribute('x', -size * 0.6);\n           svgEl.setAttribute('y', -size * 0.6);\n           if (styles.circleColor) {\n            svgEl.style.fill = styles.circleColor;\n            svgEl.querySelectorAll('path, circle, rect, polygon, ellipse').forEach(el => {\n             el.style.fill = styles.circleColor;\n            });\n           }\n           if (styles.circleBorder) {\n            svgEl.style.stroke = styles.circleBorder;\n            svgEl.querySelectorAll('path, circle, rect, polygon, ellipse').forEach(el => {\n             el.style.stroke = styles.circleBorder;\n            });\n           }\n           g.innerHTML = svgEl.outerHTML;\n          }\n         }\n        });\n        return g;\n       }\n       const shapeType = (NODE_DATA[id] && NODE_DATA[id].shape) || \"circle\";\n       const shapeEl = createShapeElement(shapeType, size);\n       shapeEl.classList.add(\"node-circle\");\n       shapeEl.style.fill = styles.circleColor || PAGE_STATE.nodeFill || \"#1e293b\";\n       shapeEl.style.stroke = styles.circleBorder || PAGE_STATE.nodeStroke || \"#475569\";\n       return shapeEl;\n      }\n      function forgeTheLegend() {\n       const container = document.getElementById(\"edge-legend\");\n       if (!container) return;\n       container.innerHTML = \"\";\n       const title = document.createElement(\"div\");\n       title.className = \"legend-title\";\n       title.textContent = \"Line Legend\";\n       container.appendChild(title);\n       const closeBtn = document.createElement(\"button\");\n       closeBtn.type = \"button\";\n       closeBtn.className = \"legend-close-btn\";\n       closeBtn.textContent = \"✕\";\n       closeBtn.addEventListener(\"click\", (e) => {\n        e.stopPropagation();\n        legendCollapsed = true;\n        updateLegendVisibility();\n       });\n       container.appendChild(closeBtn);\n       const colors = [...new Set(EDGE_DATA.list.map((e) => e.color).filter(Boolean))];\n       if (colors.length === 0) {\n        updateLegendVisibility();\n        return;\n       }\n       colors.forEach((color) => {\n         if (!EDGE_LEGEND[color]) {\n          EDGE_LEGEND[color] = \"you can edit me too\";\n         }\n         const item = document.createElement(\"div\");\n         item.className = \"legend-item\";\n         item.addEventListener(\"mousedown\", (e) => e.stopPropagation());\n         item.addEventListener(\"click\", (e) => e.stopPropagation());\n         const swatch = document.createElement(\"span\");\n         swatch.className = \"legend-swatch\";\n         swatch.style.backgroundColor = color;\n         swatch.style.cursor = \"pointer\";\n         swatch.addEventListener(\"click\", (e) => {\n          e.stopPropagation();\n          const edgeWithColor = EDGE_DATA.list.find(edge => edge.color === color);\n          if (edgeWithColor) {\n           selectTheConnection(edgeWithColor.id);\n          }\n         });\n         let swatchTouchStart = null;\n         let swatchTouchMoved = false;\n         swatch.addEventListener(\"touchstart\", (e) => {\n         swatchTouchStart = Date.now();\n         swatchTouchMoved = false;\n         if (e.touches[0]) {\n          swatchTouchStartX = e.touches[0].clientX;\n          swatchTouchStartY = e.touches[0].clientY;\n         }\n        }, {\n          passive: false\n         });\n         let swatchTouchStartX = 0, swatchTouchStartY = 0;\n        swatch.addEventListener(\"touchmove\", (e) => {\n         if (e.touches[0]) {\n          const dx = Math.abs(e.touches[0].clientX - swatchTouchStartX);\n          const dy = Math.abs(e.touches[0].clientY - swatchTouchStartY);\n          if (dx > 10 || dy > 10) swatchTouchMoved = true;\n         }\n        }, {\n          passive: false\n         });\n         swatch.addEventListener(\"touchend\", (e) => {\n          if (swatchTouchStart && !swatchTouchMoved && Date.now() - swatchTouchStart < 400) {\n           e.stopPropagation();\n           e.preventDefault();\n           const edgeWithColor = EDGE_DATA.list.find(edge => edge.color === color);\n           if (edgeWithColor) {\n            selectTheConnection(edgeWithColor.id);\n           }\n          }\n          swatchTouchStart = null;\n          swatchTouchMoved = false;\n         }, {\n          passive: false\n         });\n        const label = document.createElement(\"span\");\n        label.className = \"legend-label\";\n        label.textContent = EDGE_LEGEND[color];\n        if (isMobileDevice()) {\n         label.style.cursor = \"pointer\";\n         let labelTapStart = null;\n         let labelTapMoved = false;\n         label.addEventListener(\"touchstart\", (e) => {\n          labelTapStart = Date.now();\n          labelTapMoved = false;\n          if (e.touches[0]) {\n           labelTapStartX = e.touches[0].clientX;\n           labelTapStartY = e.touches[0].clientY;\n          }\n          e.stopPropagation();\n         });\n         let labelTapStartX = 0, labelTapStartY = 0;\n         label.addEventListener(\"touchmove\", (e) => {\n          if (e.touches[0]) {\n           const dx = Math.abs(e.touches[0].clientX - labelTapStartX);\n           const dy = Math.abs(e.touches[0].clientY - labelTapStartY);\n           if (dx > 10 || dy > 10) labelTapMoved = true;\n          }\n         });\n         label.addEventListener(\"touchend\", (e) => {\n          if (labelTapStart && !labelTapMoved && Date.now() - labelTapStart < 400) {\n           e.preventDefault();\n           e.stopPropagation();\n           const currentText = label.textContent;\n           const newText = prompt(\"Edit legend label:\", currentText);\n           if (newText !== null && newText.trim()) {\n            label.textContent = newText.trim();\n            EDGE_LEGEND[color] = newText.trim();\n           }\n          }\n          labelTapStart = null;\n          labelTapMoved = false;\n         });\n        } else {\n           label.contentEditable = true;\n           label.addEventListener(\"focus\", () => {\n            label.classList.add(\"editing\");\n           });\n           label.addEventListener(\"blur\", () => {\n            label.classList.remove(\"editing\");\n            const text = label.textContent.trim() || \"you can edit me too\";\n            EDGE_LEGEND[color] = text;\n           });\n           label.addEventListener(\"keydown\", (e) => {\n            if (e.key === \"Enter\") {\n             e.preventDefault();\n             label.blur();\n            }\n           });\n          }\n          item.append(swatch, label); container.appendChild(item);\n         }); updateLegendVisibility();\n       }\n       function deleteRectangle(rectId) {\n      pushUndo(\"delete zone\");\n        RECT_DATA.list = RECT_DATA.list.filter(r => r.id !== rectId);\n        forgeTheTopology();\n       }\n       function updateRectangleDeleteButtons() {\n        const deleteButtons = document.querySelectorAll('.rect-delete-btn');\n        deleteButtons.forEach(btn => {\n         btn.style.display = rectDrawMode ? 'block' : 'none';\n        });\n       }\n       function updateFovCone(nodeId) {\n        const node = NODE_DATA[nodeId];\n        if (!node) return;\n        \n        const nodeGroup = document.querySelector(`g[data-node-id=\"${nodeId}\"]`);\n        if (!nodeGroup) return;\n        const existingFov = nodeGroup.querySelector(\".fov-group\");\n        if (existingFov) existingFov.remove();\n        if (!hasCoverageZone(node.shape) || !node.fovEnabled) return;\n        \n        const ns = \"http://www.w3.org/2000/svg\";\n        const defaults = getCoverageDefaults(node.shape);\n        const fovAngle = node.fovAngle || defaults.angle;\n        const fovDistance = node.fovDistance || defaults.distance;\n        const fovInnerRadius = node.fovInnerRadius || 0;\n        const fovRotation = node.fovRotation || 0;\n        const fovColor = node.fovColor || \"#f59e0b\";\n        const fovOpacity = node.fovOpacity || 20;\n        const fovGradient = node.fovGradient || false;\n        const fovBorderColor = node.fovBorderColor || \"#f59e0b\";\n        const fovBorderWidth = node.fovBorderWidth ?? 2;\n        const fovBorderStyle = node.fovBorderStyle || \"solid\";\n        const fovBorderOpacity = node.fovBorderOpacity ?? 100;\n        const fovLabel = node.fovLabel || \"\";\n        const fovAnimate = node.fovAnimate || false;\n        const fovAnimationType = node.fovAnimationType || defaults.animationType;\n        const fovSweep = node.fovSweep || 120;\n        const fovSpeed = node.fovSpeed || 4;\n        \n        const fovGroup = document.createElementNS(ns, \"g\");\n        fovGroup.classList.add(\"fov-group\");\n        if (!ZONES_VISIBLE) fovGroup.style.display = \"none\";\n        \n        if (fovGradient) {\n          const gradientId = `fov-gradient-${nodeId}`;\n          const defs = document.createElementNS(ns, \"defs\");\n          const gradient = document.createElementNS(ns, \"radialGradient\");\n          gradient.id = gradientId;\n          gradient.setAttribute(\"cx\", \"0\");\n          gradient.setAttribute(\"cy\", \"0\");\n          gradient.setAttribute(\"r\", fovDistance);\n          gradient.setAttribute(\"gradientUnits\", \"userSpaceOnUse\");\n          const stop1 = document.createElementNS(ns, \"stop\");\n          stop1.setAttribute(\"offset\", fovInnerRadius / fovDistance);\n          stop1.setAttribute(\"stop-color\", fovColor);\n          stop1.setAttribute(\"stop-opacity\", fovOpacity / 100);\n          const stop2 = document.createElementNS(ns, \"stop\");\n          stop2.setAttribute(\"offset\", \"1\");\n          stop2.setAttribute(\"stop-color\", fovColor);\n          stop2.setAttribute(\"stop-opacity\", \"0\");\n          gradient.appendChild(stop1);\n          gradient.appendChild(stop2);\n          defs.appendChild(gradient);\n          fovGroup.appendChild(defs);\n        }\n        \n        const fovPath = document.createElementNS(ns, \"path\");\n        \n        if (fovAngle >= 360) {\n          if (fovInnerRadius > 0) {\n            fovPath.setAttribute(\"d\", `M ${fovDistance},0 A ${fovDistance},${fovDistance} 0 1,1 ${-fovDistance},0 A ${fovDistance},${fovDistance} 0 1,1 ${fovDistance},0 M ${fovInnerRadius},0 A ${fovInnerRadius},${fovInnerRadius} 0 1,0 ${-fovInnerRadius},0 A ${fovInnerRadius},${fovInnerRadius} 0 1,0 ${fovInnerRadius},0`);\n            fovPath.setAttribute(\"fill-rule\", \"evenodd\");\n          } else {\n            fovPath.setAttribute(\"d\", `M 0,0 m -${fovDistance},0 a ${fovDistance},${fovDistance} 0 1,0 ${fovDistance * 2},0 a ${fovDistance},${fovDistance} 0 1,0 -${fovDistance * 2},0`);\n          }\n        } else {\n          const angleRad = (fovAngle * Math.PI) / 180;\n          const rotationRad = ((fovRotation - 90) * Math.PI) / 180;\n          const startAngle = rotationRad - angleRad / 2;\n          const endAngle = rotationRad + angleRad / 2;\n          const x1 = Math.cos(startAngle) * fovDistance;\n          const y1 = Math.sin(startAngle) * fovDistance;\n          const x2 = Math.cos(endAngle) * fovDistance;\n          const y2 = Math.sin(endAngle) * fovDistance;\n          const largeArc = fovAngle > 180 ? 1 : 0;\n          if (fovInnerRadius > 0) {\n            const ix1 = Math.cos(startAngle) * fovInnerRadius;\n            const iy1 = Math.sin(startAngle) * fovInnerRadius;\n            const ix2 = Math.cos(endAngle) * fovInnerRadius;\n            const iy2 = Math.sin(endAngle) * fovInnerRadius;\n            fovPath.setAttribute(\"d\", `M ${ix1},${iy1} L ${x1},${y1} A ${fovDistance},${fovDistance} 0 ${largeArc},1 ${x2},${y2} L ${ix2},${iy2} A ${fovInnerRadius},${fovInnerRadius} 0 ${largeArc},0 ${ix1},${iy1} Z`);\n          } else {\n            fovPath.setAttribute(\"d\", `M 0,0 L ${x1},${y1} A ${fovDistance},${fovDistance} 0 ${largeArc},1 ${x2},${y2} Z`);\n          }\n        }\n        \n        if (fovGradient) {\n          fovPath.style.fill = `url(#fov-gradient-${nodeId})`;\n        } else {\n          const opacityHex = Math.round((fovOpacity / 100) * 255).toString(16).padStart(2, '0');\n          fovPath.style.fill = fovColor + opacityHex;\n        }\n        \n        const borderOpacityHex = Math.round((fovBorderOpacity / 100) * 255).toString(16).padStart(2, '0');\n        fovPath.style.stroke = fovBorderColor + borderOpacityHex;\n        fovPath.style.strokeWidth = fovBorderWidth;\n        if (fovBorderStyle === \"dashed\") {\n          fovPath.style.strokeDasharray = \"10,5\";\n        } else if (fovBorderStyle === \"dotted\") {\n          fovPath.style.strokeDasharray = \"3,3\";\n        }\n        fovPath.style.pointerEvents = \"none\";\n        fovPath.classList.add(\"fov-cone\");\n        \n        fovGroup.appendChild(fovPath);\n        \n        if (fovLabel) {\n          const fovLabelPosition = node.fovLabelPosition || \"center\";\n          const fovLabelSize = node.fovLabelSize || 14;\n          const fovLabelColor = node.fovLabelColor || \"#ffffff\";\n          const fovLabelBold = node.fovLabelBold || false;\n          const fovLabelBg = node.fovLabelBg || false;\n          const fovLabelBgColor = node.fovLabelBgColor || \"#000000\";\n          let labelDistance;\n          if (fovLabelPosition === \"center\") {\n            labelDistance = fovAngle >= 360 ? 0 : fovDistance * 0.4;\n          } else if (fovLabelPosition === \"edge\") {\n            labelDistance = fovDistance * 0.75;\n          } else {\n            labelDistance = fovDistance + fovLabelSize + 8;\n          }\n          const labelAngle = ((fovRotation - 90) * Math.PI) / 180;\n          const fovLabelOffsetX = node.fovLabelOffsetX || 0;\n          const fovLabelOffsetY = node.fovLabelOffsetY || 0;\n          const labelX = (fovAngle >= 360 && fovLabelPosition === \"center\" ? 0 : Math.cos(labelAngle) * labelDistance) + fovLabelOffsetX;\n          const labelY = (fovAngle >= 360 && fovLabelPosition === \"center\" ? 0 : Math.sin(labelAngle) * labelDistance) + fovLabelOffsetY;\n          if (fovLabelBg) {\n            const bgRect = document.createElementNS(ns, \"rect\");\n            const textWidth = fovLabel.length * fovLabelSize * 0.6;\n            const textHeight = fovLabelSize * 1.4;\n            bgRect.setAttribute(\"x\", labelX - textWidth / 2 - 6);\n            bgRect.setAttribute(\"y\", labelY - textHeight / 2);\n            bgRect.setAttribute(\"width\", textWidth + 12);\n            bgRect.setAttribute(\"height\", textHeight);\n            bgRect.setAttribute(\"rx\", \"4\");\n            bgRect.style.fill = fovLabelBgColor;\n            bgRect.style.opacity = \"0.8\";\n            bgRect.style.pointerEvents = \"none\";\n            fovGroup.appendChild(bgRect);\n          }\n          const labelEl = document.createElementNS(ns, \"text\");\n          labelEl.setAttribute(\"x\", labelX);\n          labelEl.setAttribute(\"y\", labelY);\n          labelEl.setAttribute(\"text-anchor\", \"middle\");\n          labelEl.setAttribute(\"dominant-baseline\", \"middle\");\n          labelEl.style.fill = fovLabelColor;\n          labelEl.style.fontSize = fovLabelSize + \"px\";\n          labelEl.style.fontWeight = fovLabelBold ? \"bold\" : \"normal\";\n          labelEl.style.fontFamily = \"system-ui, sans-serif\";\n          labelEl.style.pointerEvents = \"none\";\n          labelEl.textContent = fovLabel;\n          fovGroup.appendChild(labelEl);\n        }\n        \n        if (fovAnimate) {\n          const animationName = `fov-anim-${nodeId}`;\n          const styleEl = document.createElementNS(\"http://www.w3.org/1999/xhtml\", \"style\");\n          \n          if (fovAnimationType === \"sweep\" && fovAngle < 360) {\n            styleEl.textContent = `\n              @keyframes ${animationName} {\n                0%, 100% { transform: rotate(0deg); }\n                50% { transform: rotate(${fovSweep}deg); }\n              }\n            `;\n            fovGroup.style.animation = `${animationName} ${fovSpeed}s ease-in-out infinite`;\n            fovGroup.style.transformOrigin = \"0 0\";\n          } else if (fovAnimationType === \"pulse\") {\n            styleEl.textContent = `\n              @keyframes ${animationName} {\n                0%, 100% { transform: scale(1); opacity: 1; }\n                50% { transform: scale(1.1); opacity: 0.7; }\n              }\n            `;\n            fovGroup.style.animation = `${animationName} ${fovSpeed}s ease-in-out infinite`;\n            fovGroup.style.transformOrigin = \"0 0\";\n          } else if (fovAnimationType === \"rings\") {\n            for (let i = 1; i <= 3; i++) {\n              const ring = document.createElementNS(ns, \"circle\");\n              ring.setAttribute(\"cx\", \"0\");\n              ring.setAttribute(\"cy\", \"0\");\n              ring.setAttribute(\"r\", fovDistance * 0.3 * i);\n              ring.style.fill = \"none\";\n              ring.style.stroke = fovBorderColor;\n              ring.style.strokeWidth = \"2\";\n              ring.style.opacity = \"0\";\n              const ringAnimName = `${animationName}-ring-${i}`;\n              const ringStyle = document.createElementNS(\"http://www.w3.org/1999/xhtml\", \"style\");\n              ringStyle.textContent = `\n                @keyframes ${ringAnimName} {\n                  0% { r: ${fovInnerRadius || 10}; opacity: 0.8; }\n                  100% { r: ${fovDistance}; opacity: 0; }\n                }\n              `;\n              ring.style.animation = `${ringAnimName} ${fovSpeed}s ease-out infinite`;\n              ring.style.animationDelay = `${(i - 1) * (fovSpeed / 3)}s`;\n              fovGroup.appendChild(ringStyle);\n              fovGroup.appendChild(ring);\n            }\n          } else if (fovAnimationType === \"spin\") {\n            styleEl.textContent = `\n              @keyframes ${animationName} {\n                0% { transform: rotate(0deg); }\n                100% { transform: rotate(360deg); }\n              }\n            `;\n            fovGroup.style.animation = `${animationName} ${fovSpeed}s linear infinite`;\n            fovGroup.style.transformOrigin = \"0 0\";\n          }\n          \n          if (fovAnimationType !== \"rings\") {\n            fovGroup.appendChild(styleEl);\n            const elapsedSeconds = (Date.now() - FOV_ANIMATION_START) / 1000;\n            const animationOffset = elapsedSeconds % fovSpeed;\n            fovGroup.style.animationDelay = `-${animationOffset}s`;\n          }\n        }\n        \n        nodeGroup.insertBefore(fovGroup, nodeGroup.firstChild);\n      }\n       \n      function forgeTheTopology() {\n        if (!NODE_DATA || !EDGE_DATA) {\n         console.warn(\"forgeTheTopology called before data initialized\");\n         return;\n        }\n        const svg = document.getElementById(\"map\");\n        svg.innerHTML = \"\";\n        const ns = \"http://www.w3.org/2000/svg\";\n        const defs = document.createElementNS(ns, \"defs\");\n        const flowArrowBig = document.createElementNS(ns, \"path\");\n        flowArrowBig.id = \"flow-arrow-big\";\n        flowArrowBig.setAttribute(\"d\", \"M-6,-4 L6,0 L-6,4 L-3,0 Z\");\n        defs.appendChild(flowArrowBig);\n        const flowArrowSmall = document.createElementNS(ns, \"path\");\n        flowArrowSmall.id = \"flow-arrow-small\";\n        flowArrowSmall.setAttribute(\"d\", \"M-4,-3 L4,0 L-4,3 Z\");\n        defs.appendChild(flowArrowSmall);\n        const markerForward = document.createElementNS(ns, \"marker\");\n        markerForward.id = \"arrow-forward\";\n        markerForward.setAttribute(\"markerWidth\", \"10\");\n        markerForward.setAttribute(\"markerHeight\", \"10\");\n        markerForward.setAttribute(\"refX\", \"9\");\n        markerForward.setAttribute(\"refY\", \"3\");\n        markerForward.setAttribute(\"orient\", \"auto\");\n        markerForward.setAttribute(\"markerUnits\", \"strokeWidth\");\n        const pathForward = document.createElementNS(ns, \"path\");\n        pathForward.setAttribute(\"d\", \"M0,0 L0,6 L9,3 z\");\n        pathForward.setAttribute(\"fill\", \"context-stroke\");\n        markerForward.appendChild(pathForward);\n        defs.appendChild(markerForward);\n        const markerBackward = document.createElementNS(ns, \"marker\");\n        markerBackward.id = \"arrow-backward\";\n        markerBackward.setAttribute(\"markerWidth\", \"10\");\n        markerBackward.setAttribute(\"markerHeight\", \"10\");\n        markerBackward.setAttribute(\"refX\", \"0\");\n        markerBackward.setAttribute(\"refY\", \"3\");\n        markerBackward.setAttribute(\"orient\", \"auto\");\n        markerBackward.setAttribute(\"markerUnits\", \"strokeWidth\");\n        const pathBackward = document.createElementNS(ns, \"path\");\n        pathBackward.setAttribute(\"d\", \"M9,0 L9,6 L0,3 z\");\n        pathBackward.setAttribute(\"fill\", \"context-stroke\");\n        markerBackward.appendChild(pathBackward);\n        defs.appendChild(markerBackward);\n\n        const wallPattern = document.createElementNS(ns, \"pattern\");\n        wallPattern.id = \"wall-hatch\";\n        wallPattern.setAttribute(\"patternUnits\", \"userSpaceOnUse\");\n        wallPattern.setAttribute(\"width\", \"8\");\n        wallPattern.setAttribute(\"height\", \"8\");\n        wallPattern.setAttribute(\"patternTransform\", \"rotate(45)\");\n        const wallLine = document.createElementNS(ns, \"line\");\n        wallLine.setAttribute(\"x1\", \"0\");\n        wallLine.setAttribute(\"y1\", \"0\");\n        wallLine.setAttribute(\"x2\", \"0\");\n        wallLine.setAttribute(\"y2\", \"8\");\n        wallLine.setAttribute(\"stroke\", \"#666\");\n        wallLine.setAttribute(\"stroke-width\", \"2\");\n        wallPattern.appendChild(wallLine);\n        defs.appendChild(wallPattern);\n\n        svg.appendChild(defs);\n        const boundary = document.createElementNS(ns, \"rect\");\n        boundary.setAttribute(\"x\", CANVAS_PADDING);\n        boundary.setAttribute(\"y\", CANVAS_PADDING);\n        boundary.setAttribute(\"width\", CANVAS_WIDTH - CANVAS_PADDING * 2);\n        boundary.setAttribute(\"height\", CANVAS_HEIGHT - CANVAS_PADDING * 2);\n        boundary.setAttribute(\"fill\", \"none\");\n        boundary.setAttribute(\"stroke\", (PAGE_STATE.canvasBorder || \"#475569\") + \"4D\");\n        boundary.setAttribute(\"stroke-width\", \"20\");\n        boundary.setAttribute(\"stroke-dasharray\", \"10 5\");\n        boundary.setAttribute(\"rx\", \"8\");\n        svg.appendChild(boundary);\n\t\tif (currentView.mode !== \"rack\" && PAGE_STATE.canvasGridEnabled !== false) {\n\t\t const gridGroup = document.createElementNS(ns, \"g\");\n\t\t gridGroup.id = \"canvas-grid\";\n\t\t const gridSize = PAGE_STATE.canvasGridSize || 50;\n\t\t const gridColor = (PAGE_STATE.canvasGrid || \"#475569\") + \"33\";\n\t\t const majorGridColor = (PAGE_STATE.canvasGrid || \"#475569\") + \"66\";\n\t\t for (let x = CANVAS_PADDING; x <= CANVAS_WIDTH - CANVAS_PADDING; x += gridSize) {\n\t\t  const line = document.createElementNS(ns, \"line\");\n\t\t  line.setAttribute(\"x1\", x);\n\t\t  line.setAttribute(\"y1\", CANVAS_PADDING);\n\t\t  line.setAttribute(\"x2\", x);\n\t\t  line.setAttribute(\"y2\", CANVAS_HEIGHT - CANVAS_PADDING);\n\t\t  line.setAttribute(\"stroke\", (x - CANVAS_PADDING) % (gridSize * 5) === 0 ? majorGridColor : gridColor);\n\t\t  line.setAttribute(\"stroke-width\", (x - CANVAS_PADDING) % (gridSize * 5) === 0 ? \"2\" : \"1\");\n\t\t  gridGroup.appendChild(line);\n\t\t }\n\t\t for (let y = CANVAS_PADDING; y <= CANVAS_HEIGHT - CANVAS_PADDING; y += gridSize) {\n\t\t  const line = document.createElementNS(ns, \"line\");\n\t\t  line.setAttribute(\"x1\", CANVAS_PADDING);\n\t\t  line.setAttribute(\"y1\", y);\n\t\t  line.setAttribute(\"x2\", CANVAS_WIDTH - CANVAS_PADDING);\n\t\t  line.setAttribute(\"y2\", y);\n\t\t  line.setAttribute(\"stroke\", (y - CANVAS_PADDING) % (gridSize * 5) === 0 ? majorGridColor : gridColor);\n\t\t  line.setAttribute(\"stroke-width\", (y - CANVAS_PADDING) % (gridSize * 5) === 0 ? \"2\" : \"1\");\n\t\t  gridGroup.appendChild(line);\n\t\t }\n\t\t svg.appendChild(gridGroup);\n\t\t}\n       if (currentView.mode === \"rack\" && currentView.rackId) {\n         const rackCapacity = getRackCapacity(currentView.rackId);\n      const rackUHeight = getRackUHeight(currentView.rackId);\n         const rackGroup = document.createElementNS(ns, \"g\");\n         rackGroup.id = \"rack-visualization\";\n         const rackFrame = document.createElementNS(ns, \"rect\");\n         rackFrame.setAttribute(\"x\", RACK_START_X - RACK_WIDTH / 2);\n         rackFrame.setAttribute(\"y\", RACK_START_Y);\n         rackFrame.setAttribute(\"width\", RACK_WIDTH);\n         rackFrame.setAttribute(\"height\", rackCapacity * rackUHeight);\n         rackFrame.setAttribute(\"fill\", (PAGE_STATE.rackFrameFill || \"#0f172a\") + \"4D\");\n         rackFrame.setAttribute(\"stroke\", PAGE_STATE.rackFrameStroke || \"#4fd1c5\");\n         rackFrame.setAttribute(\"stroke-width\", \"3\");\n         rackFrame.setAttribute(\"rx\", \"4\");\n         rackGroup.appendChild(rackFrame);\n         if (PAGE_STATE.rackGridEnabled !== false) {\n          for (let u = 0; u <= rackCapacity; u++) {\n           const y = RACK_START_Y + u * rackUHeight;\n           const line = document.createElementNS(ns, \"line\");\n           line.setAttribute(\"x1\", RACK_START_X - RACK_WIDTH / 2);\n           line.setAttribute(\"y1\", y);\n           line.setAttribute(\"x2\", RACK_START_X + RACK_WIDTH / 2);\n           line.setAttribute(\"y2\", y);\n           line.setAttribute(\"stroke\", (PAGE_STATE.rackLineColor || \"#475569\") + \"66\");\n           line.setAttribute(\"stroke-width\", u % 5 === 0 ? \"2\" : \"1\");\n           line.setAttribute(\"stroke-dasharray\", u % 5 === 0 ? \"none\" : \"5,5\");\n           rackGroup.appendChild(line);\n           if (u < rackCapacity) {\n            const uNumber = rackCapacity - u;\n            const text = document.createElementNS(ns, \"text\");\n            text.setAttribute(\"x\", RACK_START_X - RACK_WIDTH / 2 - 30);\n            text.setAttribute(\"y\", y + rackUHeight / 2);\n            text.setAttribute(\"text-anchor\", \"middle\");\n            text.setAttribute(\"dominant-baseline\", \"middle\");\n            text.style.fill = PAGE_STATE.rackTextColor || \"#4fd1c5\";\n            text.style.fontSize = \"14px\";\n            text.style.fontWeight = \"bold\";\n            text.textContent = `U${uNumber}`;\n            rackGroup.appendChild(text);\n            const textRight = document.createElementNS(ns, \"text\");\n            textRight.setAttribute(\"x\", RACK_START_X + RACK_WIDTH / 2 + 30);\n            textRight.setAttribute(\"y\", y + rackUHeight / 2);\n            textRight.setAttribute(\"text-anchor\", \"middle\");\n            textRight.setAttribute(\"dominant-baseline\", \"middle\");\n            textRight.style.fill = PAGE_STATE.rackTextColor || \"#4fd1c5\";\n            textRight.style.fontSize = \"14px\";\n            textRight.style.fontWeight = \"bold\";\n            textRight.textContent = `U${uNumber}`;\n            rackGroup.appendChild(textRight);\n           }\n          }\n         }\n         svg.appendChild(rackGroup);\n        }\n        const centerX = CANVAS_WIDTH / 2;\n        if (RECT_DATA && RECT_DATA.list) {\n         RECT_DATA.list.forEach((rect) => {\n          if (currentView.mode === \"rack\") return;\n          if (rect.style === \"filled\") {\n           const g = document.createElementNS(ns, \"g\");\n           g.classList.add(\"rect-group\");\n           g.dataset.rectId = rect.id;\n           const rectCenterX = rect.x + rect.width / 2;\n           const rectCenterY = rect.y + rect.height / 2;\n           const rectRotation = rect.rotation || 0;\n           if (rectRotation !== 0) {\n             g.setAttribute(\"transform\", `rotate(${rectRotation}, ${rectCenterX}, ${rectCenterY})`);\n           }\n           const rectEl = document.createElementNS(ns, \"rect\");\n           rectEl.classList.add(\"rect-shape\");\n           rectEl.setAttribute(\"x\", rect.x);\n           rectEl.setAttribute(\"y\", rect.y);\n           rectEl.setAttribute(\"width\", rect.width);\n           rectEl.setAttribute(\"height\", rect.height);\n           rectEl.style.fill = rect.color;\n           rectEl.style.fillOpacity = \"0.3\";\n           rectEl.style.stroke = rect.borderColor || rect.color;\n           rectEl.style.strokeWidth = (rect.borderWidth !== undefined ? rect.borderWidth : 2) + \"px\";\n           if (rect.lineStyle === \"dashed\") { rectEl.style.strokeDasharray = \"10,5\"; }\n           else if (rect.lineStyle === \"dotted\") { rectEl.style.strokeDasharray = \"2,4\"; }\n           else if (rect.lineStyle === \"wall\") {\n             rectEl.style.fill = rect.color;\n             rectEl.style.fillOpacity = \"0.5\";\n             rectEl.style.stroke = rect.borderColor || rect.color;\n             rectEl.style.strokeWidth = (rect.borderWidth !== undefined ? rect.borderWidth : 2) + \"px\";\n             const hatchGroup = document.createElementNS(ns, \"g\");\n             hatchGroup.classList.add(\"wall-hatch-lines\");\n             hatchGroup.style.pointerEvents = \"none\";\n             const spacing = 12;\n             const hatchColor = rect.borderColor || rect.color;\n             for (let i = -rect.height; i < rect.width + rect.height; i += spacing) {\n               const line = document.createElementNS(ns, \"line\");\n               line.setAttribute(\"x1\", rect.x + i);\n               line.setAttribute(\"y1\", rect.y);\n               line.setAttribute(\"x2\", rect.x + i - rect.height);\n               line.setAttribute(\"y2\", rect.y + rect.height);\n               line.style.stroke = hatchColor;\n               line.style.strokeWidth = \"2\";\n               hatchGroup.appendChild(line);\n             }\n             const clipId = \"clip-\" + rect.id;\n             const clipPath = document.createElementNS(ns, \"clipPath\");\n             clipPath.id = clipId;\n             const clipRect = document.createElementNS(ns, \"rect\");\n             clipRect.setAttribute(\"x\", rect.x);\n             clipRect.setAttribute(\"y\", rect.y);\n             clipRect.setAttribute(\"width\", rect.width);\n             clipRect.setAttribute(\"height\", rect.height);\n             clipPath.appendChild(clipRect);\n             defs.appendChild(clipPath);\n             hatchGroup.setAttribute(\"clip-path\", \"url(#\" + clipId + \")\");\n             g.appendChild(hatchGroup);\n           }\n           else { rectEl.style.strokeDasharray = \"none\"; }\n           rectEl.style.cursor = \"move\";\n           rectEl.addEventListener(\"click\", (e) => {\n\t\t   if (isViewOnly()) return;\n             if (rectDrawMode) return;\n             e.stopPropagation();\n             currentRectId = rect.id;\n             selectTheRect(rect.id);\n           });\n           rectEl.addEventListener(\"contextmenu\", (e) => {\n\t\t    if (isViewOnly()) return;\n             e.preventDefault();\n             e.stopPropagation();\n             if (selectedRects.has(rect.id)) {\n               selectedRects.delete(rect.id);\n             } else {\n               selectedRects.add(rect.id);\n             }\n             updateAllSelections();\n           });\n           let rectLastTap = 0;\n           rectEl.addEventListener(\"touchend\", (e) => {\n             const now = Date.now();\n             if (now - rectLastTap < 300) {\n               e.preventDefault();\n               if (selectedRects.has(rect.id)) {\n                 selectedRects.delete(rect.id);\n               } else {\n                 selectedRects.add(rect.id);\n               }\n               updateAllSelections();\n               if (navigator.vibrate) navigator.vibrate(50);\n               rectLastTap = 0;\n             } else {\n               rectLastTap = now;\n             }\n           }, { passive: false });\n           const deleteBtn = document.createElementNS(ns, \"g\");\n           deleteBtn.classList.add(\"rect-delete-btn\");\n           deleteBtn.style.cursor = \"pointer\";\n           deleteBtn.style.display = rectDrawMode ? \"block\" : \"none\";\n           const deleteBg = document.createElementNS(ns, \"circle\");\n           deleteBg.setAttribute(\"cx\", rect.x + rect.width - 10);\n           deleteBg.setAttribute(\"cy\", rect.y + 10);\n           deleteBg.setAttribute(\"r\", 12);\n           deleteBg.style.fill = \"#f56565\";\n           deleteBg.style.stroke = \"white\";\n           deleteBg.style.strokeWidth = \"2\";\n           const deleteX = document.createElementNS(ns, \"text\");\n           deleteX.setAttribute(\"x\", rect.x + rect.width - 10);\n           deleteX.setAttribute(\"y\", rect.y + 10);\n           deleteX.setAttribute(\"text-anchor\", \"middle\");\n           deleteX.setAttribute(\"dominant-baseline\", \"middle\");\n           deleteX.style.fill = \"white\";\n           deleteX.style.fontSize = \"16px\";\n           deleteX.style.fontWeight = \"bold\";\n           deleteX.style.pointerEvents = \"none\";\n           deleteX.textContent = \"×\";\n           deleteBtn.appendChild(deleteBg);\n           deleteBtn.appendChild(deleteX);\n           deleteBtn.addEventListener(\"click\", (e) => {\n            e.stopPropagation();\n            e.preventDefault();\n            deleteRectangle(rect.id);\n           });\n           deleteBtn.addEventListener(\"touchend\", (e) => {\n      e.stopPropagation();\n           e.preventDefault();\n           deleteRectangle(rect.id);\n           });\n           let isDragging = false;\n           let dragStartX, dragStartY;\n           let rectStartX, rectStartY;\n           rectEl.addEventListener(\"mousedown\", (e) => {\n\t\t   if (isViewOnly()) return;\n      if (rectDrawMode) return;\n      e.preventDefault();\n      e.stopPropagation();\n      isDragging = true;\n      dragStartX = e.clientX;\n      dragStartY = e.clientY;\n      rectStartX = rect.x;\n      rectStartY = rect.y;\n      rectEl.style.cursor = \"grabbing\";\n      if (selectedRects.has(rect.id)) {\n      initialPositions = {};\n      selectedNodes.forEach(nodeId => {\n      const nodePos = savedPositions[nodeId];\n      if (nodePos) initialPositions[nodeId] = { x: nodePos.x, y: nodePos.y };\n      });\n      selectedRects.forEach(rectId => {\n      const r = RECT_DATA.list.find(x => x.id === rectId);\n      if (r) { r._dragStartX = r.x; r._dragStartY = r.y; }\n      });\n      selectedTexts.forEach(textId => {\n      const t = TEXT_DATA.list.find(x => x.id === textId);\n      if (t) { t._dragStartX = t.x; t._dragStartY = t.y; }\n      });\n      selectedEdges.forEach(edgeId => {\n      const ed = EDGE_DATA.list.find(x => x.id === edgeId);\n      if (ed && ed.points) { ed._dragStartPoints = ed.points.map(p => ({x: p.x, y: p.y})); }\n      });\n      }\n      });\n      const moveHandler = (e) => {\n      if (!isDragging || rectDrawMode) return;\n      const svgEl = svg;\n      const pt1 = svgEl.createSVGPoint();\n      pt1.x = dragStartX;\n      pt1.y = dragStartY;\n      const svgP1 = pt1.matrixTransform(svgEl.getScreenCTM().inverse());\n      const pt2 = svgEl.createSVGPoint();\n      pt2.x = e.clientX;\n      pt2.y = e.clientY;\n      const svgP2 = pt2.matrixTransform(svgEl.getScreenCTM().inverse());\n      const dx = svgP2.x - svgP1.x;\n      const dy = svgP2.y - svgP1.y;\n      rect.x = rectStartX + dx;\n      rect.y = rectStartY + dy;\n      if (selectedRects.has(rect.id)) {\n      selectedNodes.forEach(nodeId => { if (initialPositions[nodeId]) { savedPositions[nodeId] = { x: initialPositions[nodeId].x + dx, y: initialPositions[nodeId].y + dy }; } });\n      selectedRects.forEach(rectId => { if (rectId === rect.id) return; const r = RECT_DATA.list.find(x => x.id === rectId); if (r && r._dragStartX !== undefined) { r.x = r._dragStartX + dx; r.y = r._dragStartY + dy; } });\n      selectedTexts.forEach(textId => { const t = TEXT_DATA.list.find(x => x.id === textId); if (t && t._dragStartX !== undefined) { t.x = t._dragStartX + dx; t.y = t._dragStartY + dy; } });\n      selectedEdges.forEach(edgeId => { const ed = EDGE_DATA.list.find(x => x.id === edgeId); if (ed && ed._dragStartPoints) { ed.points.forEach((p, i) => { p.x = ed._dragStartPoints[i].x + dx; p.y = ed._dragStartPoints[i].y + dy; }); } });\n      }\n      forgeTheTopology();\n      };\n           const upHandler = () => {\n            if (isDragging) {\n             isDragging = false;\n             rectEl.style.cursor = \"move\";\n            }\n           };\n           document.addEventListener(\"mousemove\", moveHandler);\n           document.addEventListener(\"mouseup\", upHandler);\n           let touchStartX, touchStartY;\n           let touchRectStartX, touchRectStartY;\n           rectEl.addEventListener(\"touchstart\", (e) => {\n\t\t   if (isViewOnly()) return;\n            if (rectDrawMode) return;\n            e.preventDefault();\n            e.stopPropagation();\n            isDragging = true;\n            const touch = e.touches[0];\n            touchStartX = touch.clientX;\n            touchStartY = touch.clientY;\n            touchRectStartX = rect.x;\n            touchRectStartY = rect.y;\n            rectEl.style.cursor = \"grabbing\";\n           }, { passive: false });\n           rectEl.addEventListener(\"touchmove\", (e) => {\n\t\t   if (isViewOnly()) return;\n            if (!isDragging || rectDrawMode) return;\n            if (!e.touches[0]) return;\n            e.preventDefault();\n            e.stopPropagation();\n            const svgEl = svg;\n            const pt1 = svgEl.createSVGPoint();\n            pt1.x = touchStartX;\n            pt1.y = touchStartY;\n            const svgP1 = pt1.matrixTransform(svgEl.getScreenCTM().inverse());\n            const touch = e.touches[0];\n            const pt2 = svgEl.createSVGPoint();\n            pt2.x = touch.clientX;\n            pt2.y = touch.clientY;\n            const svgP2 = pt2.matrixTransform(svgEl.getScreenCTM().inverse());\n            const dx = svgP2.x - svgP1.x;\n            const dy = svgP2.y - svgP1.y;\n            rect.x = touchRectStartX + dx;\n            rect.y = touchRectStartY + dy;\n            forgeTheTopology();\n           }, { passive: false });\n           rectEl.addEventListener(\"touchend\", (e) => {\n            if (isDragging) {\n             e.preventDefault();\n             isDragging = false;\n             rectEl.style.cursor = \"move\";\n            }\n           }, { passive: false });\n            if (currentRectId === rect.id) {\n      const corners = [\n      { cx: rect.x, cy: rect.y, cursor: 'nwse-resize', dx: -1, dy: -1 },\n      { cx: rect.x + rect.width, cy: rect.y, cursor: 'nesw-resize', dx: 1, dy: -1 },\n      { cx: rect.x, cy: rect.y + rect.height, cursor: 'nesw-resize', dx: -1, dy: 1 },\n      { cx: rect.x + rect.width, cy: rect.y + rect.height, cursor: 'nwse-resize', dx: 1, dy: 1 }\n      ];\n      corners.forEach((corner, idx) => {\n      const handle = document.createElementNS(ns, \"circle\");\n      handle.setAttribute(\"cx\", corner.cx);\n      handle.setAttribute(\"cy\", corner.cy);\n      const borderW = rect.borderWidth !== undefined ? rect.borderWidth : 2;\n      const handleSize = Math.max(PAGE_STATE.selectionHandleSize || 8, borderW + 4);\n      handle.setAttribute(\"r\", handleSize);\n      handle.style.fill = PAGE_STATE.selectionHandle || \"#f59e0b\";\n      handle.style.stroke = \"#fff\";\n      handle.style.strokeWidth = \"2\";\n      handle.style.cursor = corner.cursor;\n      handle.addEventListener(\"mousedown\", (e) => {\n\t  if (isViewOnly()) return;\n      e.preventDefault();\n      e.stopPropagation();\n\t  pushUndo(\"resize zone\");\n      let dragging = true;\n      const startX = e.clientX, startY = e.clientY;\n      const origX = rect.x, origY = rect.y, origW = rect.width, origH = rect.height;\n      const moveHandler = (ev) => {\n        if (!dragging) return;\n        const pt1 = svg.createSVGPoint(); pt1.x = startX; pt1.y = startY;\n        const pt2 = svg.createSVGPoint(); pt2.x = ev.clientX; pt2.y = ev.clientY;\n        const svgP1 = pt1.matrixTransform(svg.getScreenCTM().inverse());\n        const svgP2 = pt2.matrixTransform(svg.getScreenCTM().inverse());\n        const dx = svgP2.x - svgP1.x, dy = svgP2.y - svgP1.y;\n        if (corner.dx < 0) { rect.x = origX + dx; rect.width = origW - dx; }\n        else { rect.width = origW + dx; }\n        if (corner.dy < 0) { rect.y = origY + dy; rect.height = origH - dy; }\n        else { rect.height = origH + dy; }\n        if (rect.width < 20) rect.width = 20;\n        if (rect.height < 20) rect.height = 20;\n        forgeTheTopology();\n      };\n      const upHandler = () => { dragging = false; document.removeEventListener(\"mousemove\", moveHandler); document.removeEventListener(\"mouseup\", upHandler); };\n      document.addEventListener(\"mousemove\", moveHandler);\n      document.addEventListener(\"mouseup\", upHandler);\n      });\n      g.appendChild(handle);\n      });\n      }\n            if (rect.groupId) {\n             const groupIndicator = document.createElementNS(ns, \"rect\");\n             groupIndicator.setAttribute(\"x\", rect.x - 4);\n             groupIndicator.setAttribute(\"y\", rect.y - 4);\n             groupIndicator.setAttribute(\"width\", rect.width + 8);\n             groupIndicator.setAttribute(\"height\", rect.height + 8);\n             groupIndicator.style.fill = \"none\";\n        groupIndicator.style.stroke = PAGE_STATE.groupIndicator || \"#4fd1c5\";\n             groupIndicator.style.strokeWidth = \"3\";\n             groupIndicator.style.strokeDasharray = \"5,5\";\n             groupIndicator.style.pointerEvents = \"none\";\n             g.insertBefore(groupIndicator, g.firstChild);\n           }\n           g.appendChild(rectEl);\n           g.appendChild(deleteBtn);\n           svg.appendChild(g);\n          }\n         });\n        }\n        const centerY = CANVAS_HEIGHT / 2;\n        let positions = {};\n        Object.keys(NODE_DATA).forEach((id) => {\n         if (currentView.mode === \"rack\") {\n          const node = NODE_DATA[id];\n          if (!node || node.assignedRack !== currentView.rackId) {\n           return;\n          }\n         }\n         positions[id] = savedPositions[id] || {\n          x: centerX,\n          y: centerY\n         };\n        });\n        if (Object.keys(savedPositions).length === 0) {\n         const nodeIds = Object.keys(NODE_DATA).filter(id => {\n          if (currentView.mode === \"rack\") {\n           const node = NODE_DATA[id];\n           return node && node.assignedRack === currentView.rackId;\n          }\n          return true;\n         });\n         const baseY = centerY - 300;\n         if (nodeIds.length > 0) {\n          positions[nodeIds[0]] = {\n           x: centerX,\n           y: baseY\n          };\n          const remaining = nodeIds.slice(1);\n          const radius = 350;\n          const startAngle = Math.PI * 0.3;\n          const endAngle = Math.PI * 0.7;\n          remaining.forEach((id, i) => {\n           const angle = startAngle + (endAngle - startAngle) * (i / Math.max(1, remaining.length - 1));\n           positions[id] = {\n            x: centerX + Math.cos(angle) * radius * (i % 2 === 0 ? 1 : 1.3),\n            y: baseY + 200 + Math.sin(angle) * radius * 0.8 + i * 80,\n           };\n          });\n         }\n        }\n        Object.keys(positions).forEach((id) => {\n         let pos = savedPositions[id] || positions[id];\n         const nodeSize = savedSizes[id] || 55;\n         pos.x = Math.max(nodeSize + CANVAS_PADDING, Math.min(CANVAS_WIDTH - nodeSize - CANVAS_PADDING, pos.x));\n         pos.y = Math.max(nodeSize + CANVAS_PADDING, Math.min(CANVAS_HEIGHT - nodeSize - CANVAS_PADDING, pos.y));\n         positions[id] = {\n          x: pos.x,\n          y: pos.y\n         };\n         savedPositions[id] = {\n          x: pos.x,\n          y: pos.y\n         };\n        });\n        const edgePairCount = {};\n        const edgePairIndex = {};\n        EDGE_DATA.list.forEach((edge) => {\n         if (edge.type === \"custom\") return;\n         const key = [edge.from, edge.to].sort().join(\"||\");\n         edgePairCount[key] = (edgePairCount[key] || 0) + 1;\n        });\n        EDGE_DATA.list.forEach((edge) => {\n         if (edge.type === \"custom\") return;\n         const key = [edge.from, edge.to].sort().join(\"||\");\n         if (!edgePairIndex[key]) edgePairIndex[key] = 0;\n         edge._pairIndex = edgePairIndex[key];\n         edge._pairTotal = edgePairCount[key];\n         edgePairIndex[key]++;\n       });\n       \n       const orthoGaps = (function() {\n         const segments = [];\n         const GAP_SIZE = 12;\n         EDGE_DATA.list.forEach((edge, edgeIndex) => {\n           if ((edge.routing || PAGE_STATE.defaultEdgeRouting || \"curved\") !== \"orthogonal\") return;\n           if (edge.type === \"custom\") return;\n           const p1 = positions[edge.from];\n           const p2 = positions[edge.to];\n           if (!p1 || !p2) return;\n           const dx = p2.x - p1.x;\n           const dy = p2.y - p1.y;\n           const pairIndex = edge._pairIndex || 0;\n           const pairTotal = edge._pairTotal || 1;\n           const offset = (pairIndex - (pairTotal - 1) / 2) * 40;\n           if (Math.abs(dx) > Math.abs(dy)) {\n             const midX = p1.x + dx / 2 + offset;\n             segments.push({ edgeId: edge.id, x1: p1.x, y1: p1.y, x2: midX, y2: p1.y, idx: edgeIndex });\n             segments.push({ edgeId: edge.id, x1: midX, y1: p1.y, x2: midX, y2: p2.y, idx: edgeIndex });\n             segments.push({ edgeId: edge.id, x1: midX, y1: p2.y, x2: p2.x, y2: p2.y, idx: edgeIndex });\n           } else {\n             const midY = p1.y + dy / 2 + offset;\n             segments.push({ edgeId: edge.id, x1: p1.x, y1: p1.y, x2: p1.x, y2: midY, idx: edgeIndex });\n             segments.push({ edgeId: edge.id, x1: p1.x, y1: midY, x2: p2.x, y2: midY, idx: edgeIndex });\n             segments.push({ edgeId: edge.id, x1: p2.x, y1: midY, x2: p2.x, y2: p2.y, idx: edgeIndex });\n           }\n         });\n         const gaps = {};\n         for (let i = 0; i < segments.length; i++) {\n           for (let j = i + 1; j < segments.length; j++) {\n             const s1 = segments[i];\n             const s2 = segments[j];\n             if (s1.edgeId === s2.edgeId) continue;\n             const s1Horiz = Math.abs(s1.y1 - s1.y2) < 1;\n             const s2Horiz = Math.abs(s2.y1 - s2.y2) < 1;\n             if (s1Horiz === s2Horiz) continue;\n             const horiz = s1Horiz ? s1 : s2;\n             const vert = s1Horiz ? s2 : s1;\n             const hY = horiz.y1;\n             const vX = vert.x1;\n             const hMinX = Math.min(horiz.x1, horiz.x2);\n             const hMaxX = Math.max(horiz.x1, horiz.x2);\n             const vMinY = Math.min(vert.y1, vert.y2);\n             const vMaxY = Math.max(vert.y1, vert.y2);\n             if (vX > hMinX && vX < hMaxX && hY > vMinY && hY < vMaxY) {\n               const gapEdge = s1.idx > s2.idx ? s1.edgeId : s2.edgeId;\n               if (!gaps[gapEdge]) gaps[gapEdge] = [];\n               gaps[gapEdge].push({ x: vX, y: hY });\n             }\n           }\n         }\n         return gaps;\n       })();\n\n       EDGE_DATA.list.forEach((edge) => {\n        if (edge.type === \"custom\" && Array.isArray(edge.points) && edge.points.length >= 2) {\n          const customEdgeFaded = currentView.mode !== \"rack\" && edge.from && edge.to && (!isNodeVisible(edge.from) || !isNodeVisible(edge.to));\n          const poly = document.createElementNS(ns, \"polyline\");\n          poly.classList.add(\"edge\");\n          if (customEdgeFaded) {\n           poly.style.opacity = \"0.25\";\n           poly.classList.add(\"layer-faded\");\n          }\n          poly.dataset.edgeId = edge.id;\n          poly.style.stroke = edge.color || PAGE_STATE.defaultEdge || \"#475569\";\n          poly.style.strokeWidth = edge.width || 4;\n          poly.setAttribute(\"fill\", \"none\");\n          const lineStyle = edge.lineStyle || \"solid\";\n          if (lineStyle === \"dashed\") {\n           poly.style.strokeDasharray = \"10,5\";\n          } else if (lineStyle === \"dotted\") {\n           poly.style.strokeDasharray = \"2,4\";\n          } else if (lineStyle === \"wall\") {\n           poly.style.stroke = \"url(#wall-hatch)\";\n           poly.style.strokeWidth = (edge.width || 4) * 3;\n           poly.style.strokeDasharray = \"none\";\n          } else {\n           poly.style.strokeDasharray = \"none\";\n          }\n          const direction = edge.direction || \"none\";\n          if (direction === \"forward\") {\n           poly.setAttribute(\"marker-end\", \"url(#arrow-forward)\");\n          } else if (direction === \"backward\") {\n           poly.setAttribute(\"marker-start\", \"url(#arrow-backward)\");\n          } else if (direction === \"both\") {\n           poly.setAttribute(\"marker-start\", \"url(#arrow-backward)\");\n           poly.setAttribute(\"marker-end\", \"url(#arrow-forward)\");\n          }\n          const ptsStr = edge.points.map((p) => `${p.x},${p.y}`).join(\" \");\n          poly.setAttribute(\"points\", ptsStr);\n          const animDir = PAGE_STATE.animationDirection || \"all\";\n          const shouldAnimatePoly = (PAGE_STATE.animateConnections && edge.animate !== false || edge.animate === true) && direction !== \"none\" && edge.points.length >= 2 && (animDir === \"all\" || animDir === direction);\n          if (shouldAnimatePoly) {\n           poly.style.opacity = \"0.25\";\n           const polyPathD = \"M \" + edge.points.map(p => `${p.x} ${p.y}`).join(\" L \");\n           const edgeStyle = edge.animationStyle || PAGE_STATE.animationStyle || \"arrows\";\n           const arrowId = edgeStyle === \"dots\" ? \"#flow-arrow-small\" : \"#flow-arrow-big\";\n           const arrowColor = edge.color || PAGE_STATE.defaultEdge || \"#475569\";\n           const arrowCount = 3;\n           const animDuration = edge.animationSpeed ? parseFloat(edge.animationSpeed) : (PAGE_STATE.animationSpeed || 1.5);\n           if (direction === \"forward\" || direction === \"both\") {\n            for (let i = 0; i < arrowCount; i++) {\n             const arrow = document.createElementNS(ns, \"use\");\n             arrow.setAttribute(\"href\", arrowId);\n             arrow.style.fill = arrowColor;\n             arrow.style.offsetPath = `path('${polyPathD}')`;\n             arrow.style.animationDuration = animDuration + \"s\";\n             arrow.style.animationDelay = (i * (animDuration / arrowCount)) + \"s\";\n             arrow.classList.add(\"edge-arrow-forward\");\n             svg.appendChild(arrow);\n            }\n           }\n           if (direction === \"backward\" || direction === \"both\") {\n            for (let i = 0; i < arrowCount; i++) {\n             const arrow = document.createElementNS(ns, \"use\");\n             arrow.setAttribute(\"href\", arrowId);\n             arrow.style.fill = arrowColor;\n             arrow.style.offsetPath = `path('${polyPathD}')`;\n             arrow.style.animationDuration = animDuration + \"s\";\n             arrow.style.animationDelay = ((i * (animDuration / arrowCount)) + (direction === \"both\" ? animDuration / (arrowCount * 2) : 0)) + \"s\";\n             arrow.classList.add(\"edge-arrow-backward\");\n             svg.appendChild(arrow);\n            }\n           }\n          }\n          const polyHit = document.createElementNS(ns, \"polyline\");\n          polyHit.setAttribute(\"points\", ptsStr);\n          polyHit.style.fill = \"none\";\n          polyHit.style.stroke = \"transparent\";\n          polyHit.style.strokeWidth = \"20\";\n          polyHit.style.cursor = \"pointer\";\n          polyHit.dataset.edgeId = edge.id;\n          polyHit.addEventListener(\"click\", (e) => {\n           e.stopPropagation();\n           selectTheConnection(edge.id);\n          });\n          let edgeTouchStart = null;\n          let edgeTouchMoved = false;\n          polyHit.addEventListener(\"touchstart\", (e) => {\n          edgeTouchStart = Date.now();\n          edgeTouchMoved = false;\n          if (e.touches[0]) {\n           edgeTouchStartX = e.touches[0].clientX;\n           edgeTouchStartY = e.touches[0].clientY;\n          }\n         }, {\n           passive: false\n          });\n          let edgeTouchStartX = 0, edgeTouchStartY = 0;\n         polyHit.addEventListener(\"touchmove\", (e) => {\n          if (e.touches[0]) {\n           const dx = Math.abs(e.touches[0].clientX - edgeTouchStartX);\n           const dy = Math.abs(e.touches[0].clientY - edgeTouchStartY);\n           if (dx > 10 || dy > 10) edgeTouchMoved = true;\n          }\n         }, {\n           passive: false\n          });\n          polyHit.addEventListener(\"touchend\", (e) => {\n           if (edgeTouchStart && !edgeTouchMoved && Date.now() - edgeTouchStart < 400) {\n            e.stopPropagation();\n            e.preventDefault();\n            selectTheConnection(edge.id);\n           }\n           edgeTouchStart = null;\n           edgeTouchMoved = false;\n          }, {\n           passive: false\n          });\n          poly.addEventListener(\"click\", (e) => {\n          e.stopPropagation();\n          selectTheConnection(edge.id);\n         });\n         polyHit.addEventListener(\"contextmenu\", (e) => {\n          e.preventDefault();\n          e.stopPropagation();\n          if (selectedEdges.has(edge.id)) {\n           selectedEdges.delete(edge.id);\n          } else {\n           selectedEdges.add(edge.id);\n          }\n          updateAllSelections();\n         });\n         let customEdgeLastTap = 0;\n         polyHit.addEventListener(\"touchend\", (e) => {\n          const now = Date.now();\n          if (now - customEdgeLastTap < 300) {\n           e.preventDefault();\n           if (selectedEdges.has(edge.id)) {\n            selectedEdges.delete(edge.id);\n           } else {\n            selectedEdges.add(edge.id);\n           }\n           updateAllSelections();\n           if (navigator.vibrate) navigator.vibrate(50);\n           customEdgeLastTap = 0;\n          } else {\n           customEdgeLastTap = now;\n          }\n         }, { passive: false });\n         poly.addEventListener(\"contextmenu\", (e) => {\n          e.preventDefault();\n          e.stopPropagation();\n          if (selectedEdges.has(edge.id)) {\n           selectedEdges.delete(edge.id);\n          } else {\n           selectedEdges.add(edge.id);\n          }\n          updateAllSelections();\n         });\n         let edgeLastTap = 0;\n         poly.addEventListener(\"touchend\", (e) => {\n          const now = Date.now();\n          if (now - edgeLastTap < 300) {\n           e.preventDefault();\n           if (selectedEdges.has(edge.id)) {\n            selectedEdges.delete(edge.id);\n           } else {\n            selectedEdges.add(edge.id);\n           }\n           updateAllSelections();\n           if (navigator.vibrate) navigator.vibrate(50);\n           edgeLastTap = 0;\n          } else {\n           edgeLastTap = now;\n          }\n         });\n          if (currentView.mode === \"rack\") {\n           return;\n          }\n          if (edge.groupId) {\n      const bounds = edge.points.reduce((acc, p) => ({ minX: Math.min(acc.minX, p.x), minY: Math.min(acc.minY, p.y), maxX: Math.max(acc.maxX, p.x), maxY: Math.max(acc.maxY, p.y) }), { minX: Infinity, minY: Infinity, maxX: -Infinity, maxY: -Infinity });\n      const groupIndicator = document.createElementNS(ns, \"rect\");\n      groupIndicator.setAttribute(\"x\", bounds.minX - 8);\n      groupIndicator.setAttribute(\"y\", bounds.minY - 8);\n      groupIndicator.setAttribute(\"width\", bounds.maxX - bounds.minX + 16);\n      groupIndicator.setAttribute(\"height\", bounds.maxY - bounds.minY + 16);\n      groupIndicator.setAttribute(\"rx\", \"4\");\n      groupIndicator.style.fill = \"none\";\n  groupIndicator.style.stroke = PAGE_STATE.groupIndicator || \"#4fd1c5\";\n      groupIndicator.style.strokeWidth = \"3\";\n      groupIndicator.style.strokeDasharray = \"5,5\";\n      groupIndicator.style.pointerEvents = \"none\";\n      svg.appendChild(groupIndicator);\n      }\n         let lineDragging = false;\n      let lineDragStartX, lineDragStartY;\n      let linePointsStart = [];\n      polyHit.addEventListener(\"mousedown\", (e) => {\n\t  if (isViewOnly()) return;\n      if (freeDrawMode) return;\n      e.preventDefault();\n      e.stopPropagation();\n      lineDragging = true;\n      lineDragStartX = e.clientX;\n      lineDragStartY = e.clientY;\n      linePointsStart = edge.points.map(p => ({x: p.x, y: p.y}));\n      if (selectedEdges.has(edge.id)) {\n      initialPositions = {};\n      selectedNodes.forEach(nodeId => {\n      const nodePos = savedPositions[nodeId];\n      if (nodePos) initialPositions[nodeId] = { x: nodePos.x, y: nodePos.y };\n      });\n      selectedRects.forEach(rectId => {\n      const r = RECT_DATA.list.find(x => x.id === rectId);\n      if (r) { r._dragStartX = r.x; r._dragStartY = r.y; }\n      });\n      selectedTexts.forEach(textId => {\n      const t = TEXT_DATA.list.find(x => x.id === textId);\n      if (t) { t._dragStartX = t.x; t._dragStartY = t.y; }\n      });\n      selectedEdges.forEach(edgeId => {\n      const ed = EDGE_DATA.list.find(x => x.id === edgeId);\n      if (ed && ed.points) { ed._dragStartPoints = ed.points.map(p => ({x: p.x, y: p.y})); }\n      });\n      }\n      });\n      const lineMoveHandler = (e) => {\n      if (!lineDragging || freeDrawMode) return;\n      const svgEl = svg;\n      const pt1 = svgEl.createSVGPoint();\n      pt1.x = lineDragStartX;\n      pt1.y = lineDragStartY;\n      const svgP1 = pt1.matrixTransform(svgEl.getScreenCTM().inverse());\n      const pt2 = svgEl.createSVGPoint();\n      pt2.x = e.clientX;\n      pt2.y = e.clientY;\n      const svgP2 = pt2.matrixTransform(svgEl.getScreenCTM().inverse());\n      const dx = svgP2.x - svgP1.x;\n      const dy = svgP2.y - svgP1.y;\n      edge.points.forEach((p, i) => { p.x = linePointsStart[i].x + dx; p.y = linePointsStart[i].y + dy; });\n      if (selectedEdges.has(edge.id)) {\n      selectedNodes.forEach(nodeId => {\n      if (initialPositions[nodeId]) {\n       savedPositions[nodeId] = { x: initialPositions[nodeId].x + dx, y: initialPositions[nodeId].y + dy };\n      }\n      });\n      selectedRects.forEach(rectId => {\n      const r = RECT_DATA.list.find(x => x.id === rectId);\n      if (r && r._dragStartX !== undefined) { r.x = r._dragStartX + dx; r.y = r._dragStartY + dy; }\n      });\n      selectedTexts.forEach(textId => {\n      const t = TEXT_DATA.list.find(x => x.id === textId);\n      if (t && t._dragStartX !== undefined) { t.x = t._dragStartX + dx; t.y = t._dragStartY + dy; }\n      });\n      selectedEdges.forEach(edgeId => {\n      if (edgeId === edge.id) return;\n      const ed = EDGE_DATA.list.find(x => x.id === edgeId);\n      if (ed && ed._dragStartPoints) { ed.points.forEach((p, i) => { p.x = ed._dragStartPoints[i].x + dx; p.y = ed._dragStartPoints[i].y + dy; }); }\n      });\n      }\n      forgeTheTopology();\n      };\n      const lineUpHandler = () => {\n        lineDragging = false;\n        document.removeEventListener(\"mousemove\", lineMoveHandler);\n        document.removeEventListener(\"mouseup\", lineUpHandler);\n      };\n      document.addEventListener(\"mousemove\", lineMoveHandler);\n      document.addEventListener(\"mouseup\", lineUpHandler);\n      svg.appendChild(poly);\n      svg.appendChild(polyHit);\n         if (currentEdgeId === edge.id) {\n          edge.points.forEach((p, idx) => {\n           const c = document.createElementNS(ns, \"circle\");\n           c.classList.add(\"edge-edit-point\");\n           c.setAttribute(\"cx\", p.x);\n           c.setAttribute(\"cy\", p.y);\n\t\t   c.setAttribute(\"r\", PAGE_STATE.selectionHandleSize || 8);\n           c.style.fill = PAGE_STATE.selectionHandle || \"#f59e0b\";\n           c.style.stroke = \"#fff\";\n           c.style.strokeWidth = \"2\";\n           c.style.cursor = \"move\";\n           c.dataset.edgeId = edge.id;\n           c.dataset.pointIndex = String(idx);\n           c.addEventListener(\"mousedown\", (e) => {\n\t\t   if (isViewOnly()) return;\n            e.preventDefault();\n            e.stopPropagation();\n            pushUndo(\"edit edge point\");\n            let dragging = true;\n            const svgEl = svg;\n            const moveHandler = (ev) => {\n             if (!dragging) return;\n             const pt = svgEl.createSVGPoint();\n             pt.x = ev.clientX;\n             pt.y = ev.clientY;\n             const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n             edge.points[idx].x = svgP.x;\n             edge.points[idx].y = svgP.y;\n             forgeTheTopology();\n            };\n            const upHandler = () => {\n             dragging = false;\n             document.removeEventListener(\"mousemove\", moveHandler);\n             document.removeEventListener(\"mouseup\", upHandler);\n            };\n            document.addEventListener(\"mousemove\", moveHandler);\n            document.addEventListener(\"mouseup\", upHandler);\n           });\n           c.addEventListener(\"touchstart\", (e) => {\n\t\t   if (isViewOnly()) return;\n            e.preventDefault();\n            e.stopPropagation();\n            pushUndo(\"edit edge point\");\n            let dragging = true;\n            const svgEl = svg;\n            const touchMoveHandler = (ev) => {\n             if (!dragging || !ev.touches[0]) return;\n             const pt = svgEl.createSVGPoint();\n             pt.x = ev.touches[0].clientX;\n             pt.y = ev.touches[0].clientY;\n             const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n             edge.points[idx].x = svgP.x;\n             edge.points[idx].y = svgP.y;\n             forgeTheTopology();\n            };\n            const touchUpHandler = () => {\n             dragging = false;\n             document.removeEventListener(\"touchmove\", touchMoveHandler);\n             document.removeEventListener(\"touchend\", touchUpHandler);\n            };\n            document.addEventListener(\"touchmove\", touchMoveHandler);\n            document.addEventListener(\"touchend\", touchUpHandler);\n           }, { passive: false });\n           svg.appendChild(c);\n          });\n         }\n         return;\n         }\n         const p1 = positions[edge.from];\n         const p2 = positions[edge.to];\n         if (!p1 || !p2) return;\n         const edgeFaded = currentView.mode !== \"rack\" && (!isNodeVisible(edge.from) || !isNodeVisible(edge.to));\n         const pairTotal = edge._pairTotal || 1;\n         const pairIndex = edge._pairIndex || 0;\n         const routing = edge.routing || \"curved\";\n         let pathD;\n         if (routing === \"straight\") {\n          pathD = `M ${p1.x} ${p1.y} L ${p2.x} ${p2.y}`;\n         } else if (routing === \"orthogonal\") {\n         const dx = p2.x - p1.x;\n         const dy = p2.y - p1.y;\n         const offset = (pairIndex - (pairTotal - 1) / 2) * 40;\n         const GAP = 10;\n         const edgeGaps = orthoGaps[edge.id] || [];\n         \n         if (Math.abs(dx) > Math.abs(dy)) {\n          const midX = p1.x + dx / 2 + offset;\n          const segs = [\n           { x1: p1.x, y1: p1.y, x2: midX, y2: p1.y },\n           { x1: midX, y1: p1.y, x2: midX, y2: p2.y },\n           { x1: midX, y1: p2.y, x2: p2.x, y2: p2.y }\n          ];\n          pathD = `M ${p1.x} ${p1.y}`;\n          segs.forEach(seg => {\n           const isHoriz = Math.abs(seg.y1 - seg.y2) < 1;\n           const segGaps = edgeGaps.filter(g => {\n            if (isHoriz) {\n             const minX = Math.min(seg.x1, seg.x2), maxX = Math.max(seg.x1, seg.x2);\n             return Math.abs(g.y - seg.y1) < 1 && g.x > minX + GAP && g.x < maxX - GAP;\n            } else {\n             const minY = Math.min(seg.y1, seg.y2), maxY = Math.max(seg.y1, seg.y2);\n             return Math.abs(g.x - seg.x1) < 1 && g.y > minY + GAP && g.y < maxY - GAP;\n            }\n           }).sort((a, b) => isHoriz ? (seg.x2 > seg.x1 ? a.x - b.x : b.x - a.x) : (seg.y2 > seg.y1 ? a.y - b.y : b.y - a.y));\n           let cx = seg.x1, cy = seg.y1;\n           segGaps.forEach(g => {\n            if (isHoriz) {\n             const gx = g.x;\n             pathD += ` L ${gx - GAP} ${cy} M ${gx + GAP} ${cy}`;\n             cx = gx + GAP;\n            } else {\n             const gy = g.y;\n             pathD += ` L ${cx} ${gy - GAP} M ${cx} ${gy + GAP}`;\n             cy = gy + GAP;\n            }\n           });\n           pathD += ` L ${seg.x2} ${seg.y2}`;\n          });\n         } else {\n          const midY = p1.y + dy / 2 + offset;\n          const segs = [\n           { x1: p1.x, y1: p1.y, x2: p1.x, y2: midY },\n           { x1: p1.x, y1: midY, x2: p2.x, y2: midY },\n           { x1: p2.x, y1: midY, x2: p2.x, y2: p2.y }\n          ];\n          pathD = `M ${p1.x} ${p1.y}`;\n          segs.forEach(seg => {\n           const isHoriz = Math.abs(seg.y1 - seg.y2) < 1;\n           const segGaps = edgeGaps.filter(g => {\n            if (isHoriz) {\n             const minX = Math.min(seg.x1, seg.x2), maxX = Math.max(seg.x1, seg.x2);\n             return Math.abs(g.y - seg.y1) < 1 && g.x > minX + GAP && g.x < maxX - GAP;\n            } else {\n             const minY = Math.min(seg.y1, seg.y2), maxY = Math.max(seg.y1, seg.y2);\n             return Math.abs(g.x - seg.x1) < 1 && g.y > minY + GAP && g.y < maxY - GAP;\n            }\n           }).sort((a, b) => isHoriz ? (seg.x2 > seg.x1 ? a.x - b.x : b.x - a.x) : (seg.y2 > seg.y1 ? a.y - b.y : b.y - a.y));\n           let cx = seg.x1, cy = seg.y1;\n           segGaps.forEach(g => {\n            if (isHoriz) {\n             const gx = g.x;\n             pathD += ` L ${gx - GAP} ${cy} M ${gx + GAP} ${cy}`;\n             cx = gx + GAP;\n            } else {\n             const gy = g.y;\n             pathD += ` L ${cx} ${gy - GAP} M ${cx} ${gy + GAP}`;\n             cy = gy + GAP;\n            }\n           });\n           pathD += ` L ${seg.x2} ${seg.y2}`;\n          });\n         }\n        } else {\n          const midX = (p1.x + p2.x) / 2;\n          const midY = (p1.y + p2.y) / 2;\n          const dx = p2.x - p1.x;\n          const dy = p2.y - p1.y;\n          const len = Math.sqrt(dx * dx + dy * dy) || 1;\n          const perpX = -dy / len;\n          const perpY = dx / len;\n          let offsetAmount = 0;\n          if (pairTotal > 1) {\n           offsetAmount = (pairIndex - (pairTotal - 1) / 2) * 40;\n          }\n          const ctrlX = midX + perpX * offsetAmount;\n          const ctrlY = midY + perpY * offsetAmount;\n          pathD = `M ${p1.x} ${p1.y} Q ${ctrlX} ${ctrlY} ${p2.x} ${p2.y}`;\n         }\n         const path = document.createElementNS(ns, \"path\");\n         path.setAttribute(\"d\", pathD);\n         path.setAttribute(\"fill\", \"none\");\n         path.classList.add(\"edge\");\n         if (edgeFaded) {\n          path.style.opacity = \"0.25\";\n          path.classList.add(\"layer-faded\");\n         }\n         if (edge.type === \"backup\") path.classList.add(\"backup\");\n         path.dataset.edgeId = edge.id;\n         path.dataset.from = edge.from;\n         path.dataset.to = edge.to;\n         path.style.stroke = edge.color;\n         path.style.strokeWidth = edge.width;\n         const edgeDirection = edge.direction || \"none\";\n         const edgeLineStyle = edge.lineStyle || \"solid\";\n         if (edgeLineStyle === \"dashed\") { path.style.strokeDasharray = \"10,5\"; }\n         else if (edgeLineStyle === \"dotted\") { path.style.strokeDasharray = \"2,4\"; }\n         if (edgeDirection === \"forward\") {\n          path.setAttribute(\"marker-end\", \"url(#arrow-forward)\");\n         } else if (edgeDirection === \"backward\") {\n          path.setAttribute(\"marker-start\", \"url(#arrow-backward)\");\n         } else if (edgeDirection === \"both\") {\n          path.setAttribute(\"marker-start\", \"url(#arrow-backward)\");\n          path.setAttribute(\"marker-end\", \"url(#arrow-forward)\");\n         }\n         const animDir = PAGE_STATE.animationDirection || \"all\";\n         const shouldAnimate = (PAGE_STATE.animateConnections && edge.animate !== false || edge.animate === true) && edgeDirection !== \"none\" && (animDir === \"all\" || animDir === edgeDirection);\n         if (shouldAnimate && !edgeFaded) {\n          path.style.opacity = \"0.25\";\n          const edgeStyle = edge.animationStyle || PAGE_STATE.animationStyle || \"arrows\";\n          const arrowId = edgeStyle === \"dots\" ? \"#flow-arrow-small\" : \"#flow-arrow-big\";\n          const arrowColor = edge.color || PAGE_STATE.defaultEdge || \"#475569\";\n          const arrowCount = 3;\n          const animDuration = edge.animationSpeed ? parseFloat(edge.animationSpeed) : (PAGE_STATE.animationSpeed || 1.5);\n          if (edgeDirection === \"forward\" || edgeDirection === \"both\") {\n           for (let i = 0; i < arrowCount; i++) {\n            const arrow = document.createElementNS(ns, \"use\");\n            arrow.setAttribute(\"href\", arrowId);\n            arrow.style.fill = arrowColor;\n            arrow.style.offsetPath = `path('${pathD}')`;\n            arrow.style.animationDuration = animDuration + \"s\";\n            arrow.style.animationDelay = (i * (animDuration / arrowCount)) + \"s\";\n            arrow.classList.add(\"edge-arrow-forward\");\n            svg.appendChild(arrow);\n           }\n          }\n          if (edgeDirection === \"backward\" || edgeDirection === \"both\") {\n           for (let i = 0; i < arrowCount; i++) {\n            const arrow = document.createElementNS(ns, \"use\");\n            arrow.setAttribute(\"href\", arrowId);\n            arrow.style.fill = arrowColor;\n            arrow.style.offsetPath = `path('${pathD}')`;\n            arrow.style.animationDuration = animDuration + \"s\";\n            arrow.style.animationDelay = ((i * (animDuration / arrowCount)) + (edgeDirection === \"both\" ? animDuration / (arrowCount * 2) : 0)) + \"s\";\n            arrow.classList.add(\"edge-arrow-backward\");\n            svg.appendChild(arrow);\n           }\n          }\n         }\n         const pathHit = document.createElementNS(ns, \"path\");\n         pathHit.setAttribute(\"d\", pathD);\n         pathHit.setAttribute(\"fill\", \"none\");\n         pathHit.style.stroke = \"transparent\";\n         pathHit.style.strokeWidth = \"20\";\n         pathHit.style.cursor = \"pointer\";\n         pathHit.dataset.edgeId = edge.id;\n         pathHit.addEventListener(\"click\", (e) => {\n          e.stopPropagation();\n          selectTheConnection(edge.id);\n         });\n         let pathTouchStart = null;\n         let pathTouchMoved = false;\n         pathHit.addEventListener(\"touchstart\", (e) => {\n         pathTouchStart = Date.now();\n         pathTouchMoved = false;\n         if (e.touches[0]) {\n          pathTouchStartX = e.touches[0].clientX;\n          pathTouchStartY = e.touches[0].clientY;\n         }\n        }, {\n          passive: false\n         });\n         let pathTouchStartX = 0, pathTouchStartY = 0;\n        pathHit.addEventListener(\"touchmove\", (e) => {\n         if (e.touches[0]) {\n          const dx = Math.abs(e.touches[0].clientX - pathTouchStartX);\n          const dy = Math.abs(e.touches[0].clientY - pathTouchStartY);\n          if (dx > 10 || dy > 10) pathTouchMoved = true;\n         }\n        }, {\n          passive: false\n         });\n         pathHit.addEventListener(\"touchend\", (e) => {\n          if (pathTouchStart && !pathTouchMoved && Date.now() - pathTouchStart < 400) {\n           e.stopPropagation();\n           e.preventDefault();\n           selectTheConnection(edge.id);\n          }\n          pathTouchStart = null;\n          pathTouchMoved = false;\n         }, {\n          passive: false\n         });\n         path.addEventListener(\"click\", (e) => {\n          e.stopPropagation();\n          selectTheConnection(edge.id);\n         });\n         if (currentView.mode === \"rack\") {\n          const fromNode = NODE_DATA[edge.from];\n          const toNode = NODE_DATA[edge.to];\n          if (!fromNode || !toNode ||\n              fromNode.assignedRack !== currentView.rackId ||\n              toNode.assignedRack !== currentView.rackId) {\n           return;\n          }\n         }\n         svg.appendChild(path);\n         svg.appendChild(pathHit);\n         if (edge.fromPort || edge.toPort) {\n          const ns = \"http://www.w3.org/2000/svg\";\n          if (edge.fromPort) {\n           const fromLabel = document.createElementNS(ns, \"text\");\n           fromLabel.textContent = edge.fromPort;\n           fromLabel.setAttribute(\"x\", p1.x);\n           fromLabel.setAttribute(\"y\", p1.y - 10);\n           fromLabel.setAttribute(\"text-anchor\", \"middle\");\n           fromLabel.style.fill = \"#94a3b8\";\n           fromLabel.style.fontSize = \"12px\";\n           fromLabel.style.fontWeight = \"600\";\n           fromLabel.style.pointerEvents = \"none\";\n           fromLabel.classList.add(\"port-label\");\n           svg.appendChild(fromLabel);\n          }\n          if (edge.toPort) {\n           const toLabel = document.createElementNS(ns, \"text\");\n           toLabel.textContent = edge.toPort;\n           toLabel.setAttribute(\"x\", p2.x);\n           toLabel.setAttribute(\"y\", p2.y - 10);\n           toLabel.setAttribute(\"text-anchor\", \"middle\");\n           toLabel.style.fill = \"#94a3b8\";\n           toLabel.style.fontSize = \"12px\";\n           toLabel.style.fontWeight = \"600\";\n           toLabel.style.pointerEvents = \"none\";\n           toLabel.classList.add(\"port-label\");\n           svg.appendChild(toLabel);\n          }\n         }\n        });\n        Object.entries(positions).forEach(([id, pos]) => {\n         const node = NODE_DATA[id];\n         if (!node) return;\n         if (currentView.mode === \"rack\") {\n          if (node.assignedRack !== currentView.rackId) return;\n         const rackUnit = parseInt(node.rackUnit) || 1;\n      const uHeight = parseInt(node.uHeight) || 1;\n      const rackCapacity = getRackCapacity(currentView.rackId);\n      const rackUHeight = getRackUHeight(currentView.rackId);\n      pos.x = RACK_START_X;\n      pos.y = RACK_START_Y + (rackCapacity - rackUnit - uHeight + 1) * rackUHeight + (uHeight * rackUHeight) / 2;\n         } else {\n          if (node.assignedRack) return;\n         }\n         const g = document.createElementNS(\"http://www.w3.org/2000/svg\", \"g\");\n         g.classList.add(\"node-group\");\n         g.dataset.nodeId = id;\n         const nodeRotation = NODE_DATA[id].rotation || 0;\n         g.setAttribute(\"transform\", `translate(${pos.x},${pos.y}) rotate(${nodeRotation})`);\n         if (currentView.mode !== \"rack\" && !isNodeVisible(id)) {\n          g.style.opacity = \"0.25\";\n          g.classList.add(\"layer-faded\");\n         }\n         let r = savedSizes[id] || 55;\n\t\tif (!savedSizes[id]) {\n\t\t if (window.innerWidth <= 480) r = 45;\n\t\t else if (window.innerWidth <= 768) r = 50;\n\t\t}\n         const styles = resolveStylesForNode(id);\n         const ns = \"http://www.w3.org/2000/svg\";\n         const hitArea = document.createElementNS(ns, \"circle\");\n         hitArea.setAttribute(\"r\", r * 1.5);\n         hitArea.style.fill = \"transparent\";\n         hitArea.style.stroke = \"none\";\n         hitArea.style.cursor = \"grab\";\n         hitArea.classList.add(\"node-hit-area\");\n         const shapeEl = createNodeShape(id, r);\n         const titleOffsetX = styles.titleOffsetX || 0;\n         const titleOffsetY = styles.titleOffsetY || 0;\n         const subOffsetX = styles.subOffsetX || 0;\n         const subOffsetY = styles.subOffsetY || 0;\n         const label = document.createElementNS(\"http://www.w3.org/2000/svg\", \"text\");\n         label.classList.add(\"node-label\");\n         label.setAttribute(\"x\", titleOffsetX);\n         label.setAttribute(\"y\", -r * 0.28 + titleOffsetY);\n         const labelFontSize = styles.titleSize || PAGE_STATE.nodeTitleSize || r * 0.33;\n        label.style.fontSize = labelFontSize + \"px\";\n         label.textContent = NODE_DATA[id].name;\n\t\tlabel.style.fill = styles.titleColor || PAGE_STATE.nodeTitle || \"#e2e8f0\";\n        label.style.fontFamily = styles.titleFont || PAGE_STATE.nodeFont || \"Inter, system-ui, sans-serif\";\n         label.style.pointerEvents = \"none\";\n         const sub = document.createElementNS(\"http://www.w3.org/2000/svg\", \"text\");\n         sub.classList.add(\"node-sub\");\n         sub.setAttribute(\"x\", subOffsetX);\n         sub.setAttribute(\"y\", r * 0.4 + subOffsetY);\n         const subFontSize = styles.subSize || PAGE_STATE.nodeSubSize || r * 0.24;\n        sub.style.fontSize = subFontSize + \"px\";\n         sub.textContent = NODE_DATA[id].ip;\n\t\tsub.style.fill = styles.subColor || PAGE_STATE.nodeSub || \"#94a3b8\";\n        sub.style.fontFamily = styles.subFont || PAGE_STATE.nodeFont || \"Inter, system-ui, sans-serif\";\n         sub.style.pointerEvents = \"none\";\n         if (hasCoverageZone(node.shape) && node.fovEnabled) {\n           const defaults = getCoverageDefaults(node.shape);\n           const fovAngle = node.fovAngle || defaults.angle;\n           const fovDistance = node.fovDistance || defaults.distance;\n           const fovInnerRadius = node.fovInnerRadius || 0;\n           const fovRotation = node.fovRotation || 0;\n           const fovColor = node.fovColor || \"#f59e0b\";\n           const fovOpacity = node.fovOpacity || 20;\n           const fovGradient = node.fovGradient || false;\n           const fovBorderColor = node.fovBorderColor || \"#f59e0b\";\n           const fovBorderWidth = node.fovBorderWidth ?? 2;\n           const fovBorderStyle = node.fovBorderStyle || \"solid\";\n           const fovBorderOpacity = node.fovBorderOpacity ?? 100;\n           const fovLabel = node.fovLabel || \"\";\n           const fovAnimate = node.fovAnimate || false;\n           const fovAnimationType = node.fovAnimationType || defaults.animationType;\n           const fovSweep = node.fovSweep || 120;\n           const fovSpeed = node.fovSpeed || 4;\n           \n           const fovGroup = document.createElementNS(ns, \"g\");\n           fovGroup.classList.add(\"fov-group\");\n           if (!ZONES_VISIBLE) fovGroup.style.display = \"none\";\n           \n           if (fovGradient) {\n             const gradientId = `fov-gradient-${id}`;\n             const defs = document.createElementNS(ns, \"defs\");\n             const gradient = document.createElementNS(ns, \"radialGradient\");\n             gradient.id = gradientId;\n             gradient.setAttribute(\"cx\", \"0\");\n             gradient.setAttribute(\"cy\", \"0\");\n             gradient.setAttribute(\"r\", fovDistance);\n             gradient.setAttribute(\"gradientUnits\", \"userSpaceOnUse\");\n             const stop1 = document.createElementNS(ns, \"stop\");\n             stop1.setAttribute(\"offset\", fovInnerRadius / fovDistance);\n             stop1.setAttribute(\"stop-color\", fovColor);\n             stop1.setAttribute(\"stop-opacity\", fovOpacity / 100);\n             const stop2 = document.createElementNS(ns, \"stop\");\n             stop2.setAttribute(\"offset\", \"1\");\n             stop2.setAttribute(\"stop-color\", fovColor);\n             stop2.setAttribute(\"stop-opacity\", \"0\");\n             gradient.appendChild(stop1);\n             gradient.appendChild(stop2);\n             defs.appendChild(gradient);\n             fovGroup.appendChild(defs);\n           }\n           \n           const fovPath = document.createElementNS(ns, \"path\");\n           \n           if (fovAngle >= 360) {\n             if (fovInnerRadius > 0) {\n               fovPath.setAttribute(\"d\", `M ${fovDistance},0 A ${fovDistance},${fovDistance} 0 1,1 ${-fovDistance},0 A ${fovDistance},${fovDistance} 0 1,1 ${fovDistance},0 M ${fovInnerRadius},0 A ${fovInnerRadius},${fovInnerRadius} 0 1,0 ${-fovInnerRadius},0 A ${fovInnerRadius},${fovInnerRadius} 0 1,0 ${fovInnerRadius},0`);\n               fovPath.setAttribute(\"fill-rule\", \"evenodd\");\n             } else {\n               fovPath.setAttribute(\"d\", `M 0,0 m -${fovDistance},0 a ${fovDistance},${fovDistance} 0 1,0 ${fovDistance * 2},0 a ${fovDistance},${fovDistance} 0 1,0 -${fovDistance * 2},0`);\n             }\n           } else {\n             const angleRad = (fovAngle * Math.PI) / 180;\n             const rotationRad = ((fovRotation - 90) * Math.PI) / 180;\n             const startAngle = rotationRad - angleRad / 2;\n             const endAngle = rotationRad + angleRad / 2;\n             const x1 = Math.cos(startAngle) * fovDistance;\n             const y1 = Math.sin(startAngle) * fovDistance;\n             const x2 = Math.cos(endAngle) * fovDistance;\n             const y2 = Math.sin(endAngle) * fovDistance;\n             const largeArc = fovAngle > 180 ? 1 : 0;\n             if (fovInnerRadius > 0) {\n               const ix1 = Math.cos(startAngle) * fovInnerRadius;\n               const iy1 = Math.sin(startAngle) * fovInnerRadius;\n               const ix2 = Math.cos(endAngle) * fovInnerRadius;\n               const iy2 = Math.sin(endAngle) * fovInnerRadius;\n               fovPath.setAttribute(\"d\", `M ${ix1},${iy1} L ${x1},${y1} A ${fovDistance},${fovDistance} 0 ${largeArc},1 ${x2},${y2} L ${ix2},${iy2} A ${fovInnerRadius},${fovInnerRadius} 0 ${largeArc},0 ${ix1},${iy1} Z`);\n             } else {\n               fovPath.setAttribute(\"d\", `M 0,0 L ${x1},${y1} A ${fovDistance},${fovDistance} 0 ${largeArc},1 ${x2},${y2} Z`);\n             }\n           }\n           \n           if (fovGradient) {\n             fovPath.style.fill = `url(#fov-gradient-${id})`;\n           } else {\n             const opacityHex = Math.round((fovOpacity / 100) * 255).toString(16).padStart(2, '0');\n             fovPath.style.fill = fovColor + opacityHex;\n           }\n           \n           const borderOpacityHex = Math.round((fovBorderOpacity / 100) * 255).toString(16).padStart(2, '0');\n           fovPath.style.stroke = fovBorderColor + borderOpacityHex;\n           fovPath.style.strokeWidth = fovBorderWidth;\n           if (fovBorderStyle === \"dashed\") {\n             fovPath.style.strokeDasharray = \"10,5\";\n           } else if (fovBorderStyle === \"dotted\") {\n             fovPath.style.strokeDasharray = \"3,3\";\n           }\n           fovPath.style.pointerEvents = \"none\";\n           fovPath.classList.add(\"fov-cone\");\n           \n           fovGroup.appendChild(fovPath);\n           \n           if (fovLabel) {\n             const fovLabelPosition = node.fovLabelPosition || \"center\";\n             const fovLabelSize = node.fovLabelSize || 14;\n             const fovLabelColor = node.fovLabelColor || \"#ffffff\";\n             const fovLabelBold = node.fovLabelBold || false;\n             const fovLabelBg = node.fovLabelBg || false;\n             const fovLabelBgColor = node.fovLabelBgColor || \"#000000\";\n             let labelDistance;\n             if (fovLabelPosition === \"center\") {\n               labelDistance = fovAngle >= 360 ? 0 : fovDistance * 0.4;\n             } else if (fovLabelPosition === \"edge\") {\n               labelDistance = fovDistance * 0.75;\n             } else {\n               labelDistance = fovDistance + fovLabelSize + 8;\n             }\n             const labelAngle = ((fovRotation - 90) * Math.PI) / 180;\n             const fovLabelOffsetX = node.fovLabelOffsetX || 0;\n             const fovLabelOffsetY = node.fovLabelOffsetY || 0;\n             const labelX = (fovAngle >= 360 && fovLabelPosition === \"center\" ? 0 : Math.cos(labelAngle) * labelDistance) + fovLabelOffsetX;\n             const labelY = (fovAngle >= 360 && fovLabelPosition === \"center\" ? 0 : Math.sin(labelAngle) * labelDistance) + fovLabelOffsetY;\n             if (fovLabelBg) {\n               const bgRect = document.createElementNS(ns, \"rect\");\n               const textWidth = fovLabel.length * fovLabelSize * 0.6;\n               const textHeight = fovLabelSize * 1.4;\n               bgRect.setAttribute(\"x\", labelX - textWidth / 2 - 6);\n               bgRect.setAttribute(\"y\", labelY - textHeight / 2);\n               bgRect.setAttribute(\"width\", textWidth + 12);\n               bgRect.setAttribute(\"height\", textHeight);\n               bgRect.setAttribute(\"rx\", \"4\");\n               bgRect.style.fill = fovLabelBgColor;\n               bgRect.style.opacity = \"0.8\";\n               bgRect.style.pointerEvents = \"none\";\n               fovGroup.appendChild(bgRect);\n             }\n             const labelEl = document.createElementNS(ns, \"text\");\n             labelEl.setAttribute(\"x\", labelX);\n             labelEl.setAttribute(\"y\", labelY);\n             labelEl.setAttribute(\"text-anchor\", \"middle\");\n             labelEl.setAttribute(\"dominant-baseline\", \"middle\");\n             labelEl.style.fill = fovLabelColor;\n             labelEl.style.fontSize = fovLabelSize + \"px\";\n             labelEl.style.fontWeight = fovLabelBold ? \"bold\" : \"normal\";\n             labelEl.style.fontFamily = \"system-ui, sans-serif\";\n             labelEl.style.pointerEvents = \"none\";\n             labelEl.textContent = fovLabel;\n             fovGroup.appendChild(labelEl);\n           }\n           \n           if (fovAnimate) {\n             const animationName = `fov-anim-${id}`;\n             const styleEl = document.createElementNS(\"http://www.w3.org/1999/xhtml\", \"style\");\n             \n             if (fovAnimationType === \"sweep\" && fovAngle < 360) {\n               styleEl.textContent = `\n                 @keyframes ${animationName} {\n                   0%, 100% { transform: rotate(0deg); }\n                   50% { transform: rotate(${fovSweep}deg); }\n                 }\n               `;\n               fovGroup.style.animation = `${animationName} ${fovSpeed}s ease-in-out infinite`;\n               fovGroup.style.transformOrigin = \"0 0\";\n             } else if (fovAnimationType === \"pulse\") {\n               styleEl.textContent = `\n                 @keyframes ${animationName} {\n                   0%, 100% { transform: scale(1); opacity: 1; }\n                   50% { transform: scale(1.1); opacity: 0.7; }\n                 }\n               `;\n               fovGroup.style.animation = `${animationName} ${fovSpeed}s ease-in-out infinite`;\n               fovGroup.style.transformOrigin = \"0 0\";\n             } else if (fovAnimationType === \"rings\") {\n               for (let i = 1; i <= 3; i++) {\n                 const ring = document.createElementNS(ns, \"circle\");\n                 ring.setAttribute(\"cx\", \"0\");\n                 ring.setAttribute(\"cy\", \"0\");\n                 ring.setAttribute(\"r\", fovDistance * 0.3 * i);\n                 ring.style.fill = \"none\";\n                 ring.style.stroke = fovBorderColor;\n                 ring.style.strokeWidth = \"2\";\n                 ring.style.opacity = \"0\";\n                 const ringAnimName = `${animationName}-ring-${i}`;\n                 const ringStyle = document.createElementNS(\"http://www.w3.org/1999/xhtml\", \"style\");\n                 ringStyle.textContent = `\n                   @keyframes ${ringAnimName} {\n                     0% { r: ${fovInnerRadius || 10}; opacity: 0.8; }\n                     100% { r: ${fovDistance}; opacity: 0; }\n                   }\n                 `;\n                 ring.style.animation = `${ringAnimName} ${fovSpeed}s ease-out infinite`;\n                 ring.style.animationDelay = `${(i - 1) * (fovSpeed / 3)}s`;\n                 fovGroup.appendChild(ringStyle);\n                 fovGroup.appendChild(ring);\n               }\n             } else if (fovAnimationType === \"spin\") {\n               styleEl.textContent = `\n                 @keyframes ${animationName} {\n                   0% { transform: rotate(0deg); }\n                   100% { transform: rotate(360deg); }\n                 }\n               `;\n               fovGroup.style.animation = `${animationName} ${fovSpeed}s linear infinite`;\n               fovGroup.style.transformOrigin = \"0 0\";\n             }\n             \n             if (fovAnimationType !== \"rings\") {\n               fovGroup.appendChild(styleEl);\n               const elapsedSeconds = (Date.now() - FOV_ANIMATION_START) / 1000;\n               const animationOffset = elapsedSeconds % fovSpeed;\n               fovGroup.style.animationDelay = `-${animationOffset}s`;\n             }\n           }\n           \n           g.appendChild(fovGroup);\n         }\n        g.append(hitArea, shapeEl, label, sub);\n         if (NODE_DATA[id]?.locked) {\n           const lockIndicator = document.createElementNS(ns, \"text\");\n           lockIndicator.textContent = \"🔒\";\n           lockIndicator.setAttribute(\"x\", r * 0.7);\n           lockIndicator.setAttribute(\"y\", -r * 0.7);\n           lockIndicator.style.fontSize = (r * 0.3) + \"px\";\n           lockIndicator.style.pointerEvents = \"none\";\n           lockIndicator.classList.add(\"lock-indicator\");\n           g.appendChild(lockIndicator);\n         }\n         if (NODE_DATA[id]?.groupId) {\n           const groupIndicator = document.createElementNS(ns, \"circle\");\n           groupIndicator.setAttribute(\"r\", r + 4);\n           groupIndicator.style.fill = \"none\";\n         groupIndicator.style.stroke = PAGE_STATE.groupIndicator || \"#4fd1c5\";\n           groupIndicator.style.strokeWidth = \"3\";\n           groupIndicator.style.strokeDasharray = \"5,5\";\n           groupIndicator.style.pointerEvents = \"none\";\n           groupIndicator.classList.add(\"group-indicator\");\n           g.insertBefore(groupIndicator, g.firstChild);\n         }\n         let isDragging = false;\n         let startX, startY;\n         let initialPositions = {};\n         let longPressTimer = null;\n         let longPressTriggered = false;\n         g.addEventListener(\"contextmenu\", (e) => {\n          e.preventDefault();\n          e.stopPropagation();\n          if (isViewOnly()) return;\n          if (selectedNodes.has(id)) {\n           selectedNodes.delete(id);\n          } else {\n           selectedNodes.add(id);\n          }\n          updateNodeSelection();\n          return false;\n         });\n        g.addEventListener(\"touchstart\", (e) => {\n         if (NODE_DATA[id].isRack) {\n          const touch = e.touches[0];\n          longPressStartX = touch.clientX;\n          longPressStartY = touch.clientY;\n          longPressTimer = setTimeout(() => {\n           longPressTriggered = true;\n           if (navigator.vibrate) {\n            navigator.vibrate(100);\n           }\n           enterRack(id);\n          }, 500);\n         }\n        }, { passive: true });\n        g.addEventListener(\"dblclick\", (e) => {\n         e.preventDefault();\n         e.stopPropagation();\n         if (NODE_DATA[id].isRack) {\n          enterRack(id);\n         }\n        });\n        let lastTapTime = 0;\n        let lastTapNode = null;\n        g.addEventListener(\"touchend\", (e) => {\n         const currentTime = new Date().getTime();\n         const tapLength = currentTime - lastTapTime;\n         if (tapLength < 300 && tapLength > 0 && lastTapNode === id) {\n          e.preventDefault();\n          e.stopPropagation();\n          if (isViewOnly()) return;\n          if (selectedNodes.has(id)) {\n           selectedNodes.delete(id);\n          } else {\n           selectedNodes.add(id);\n          }\n          updateNodeSelection();\n          if (navigator.vibrate) {\n           navigator.vibrate(50);\n          }\n          lastTapTime = 0;\n          lastTapNode = null;\n         } else {\n          lastTapTime = currentTime;\n          lastTapNode = id;\n         }\n        });\n         g.addEventListener(\"touchend\", (e) => {\n          if (longPressTimer) {\n           clearTimeout(longPressTimer);\n           longPressTimer = null;\n          }\n          if (longPressTriggered) {\n           e.preventDefault();\n           e.stopPropagation();\n           longPressTriggered = false;\n          }\n         });\n         let longPressStartX = 0;\n        let longPressStartY = 0;\n        g.addEventListener(\"touchmove\", (e) => {\n         if (longPressTimer) {\n          const touch = e.touches[0];\n          const dx = Math.abs(touch.clientX - longPressStartX);\n          const dy = Math.abs(touch.clientY - longPressStartY);\n          if (dx > 15 || dy > 15) {\n           clearTimeout(longPressTimer);\n           longPressTimer = null;\n           longPressTriggered = false;\n          }\n         }\n        }, { passive: true });\n         g.addEventListener(\"mousedown\", (e) => {\n          if (isViewOnly()) return;\n          if (e.button === 2) {\n           return;\n          }\n          if (NODE_DATA[id]?.locked) {\n           return;\n          }\n          e.preventDefault();\n          isDragging = true;\n\t\t  pushUndo(\"move nodes\");\n          const svgEl = document.getElementById(\"map\");\n          const pt = svgEl.createSVGPoint();\n          pt.x = e.clientX;\n          pt.y = e.clientY;\n          const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n          startX = svgP.x;\n          startY = svgP.y;\n          let nodesToCollect = [];\n      if (selectedNodes.has(id)) {\n      initialPositions = {};\n      const allSelectedRects = Array.from(selectedRects);\n      const allSelectedTexts = Array.from(selectedTexts);\n      const allSelectedEdges = Array.from(selectedEdges).map(eid => EDGE_DATA.list.find(e => e.id === eid)).filter(Boolean);\n      selectedNodes.forEach(nodeId => {\n      const nodePos = savedPositions[nodeId];\n      if (nodePos) {\n      initialPositions[nodeId] = { x: nodePos.x, y: nodePos.y };\n      }\n      });\n      Array.from(selectedRects).forEach(rectId => {\n        const rect = RECT_DATA.list.find(r => r.id === rectId);\n        if (rect) { rect._dragStartX = rect.x; rect._dragStartY = rect.y; }\n      });\n      Array.from(selectedTexts).forEach(textId => {\n        const text = TEXT_DATA.list.find(t => t.id === textId);\n        if (text) { text._dragStartX = text.x; text._dragStartY = text.y; }\n      });\n      Array.from(selectedEdges).forEach(edgeId => {\n        const edge = EDGE_DATA.list.find(e => e.id === edgeId);\n        if (edge && edge.points) { edge._dragStartPoints = edge.points.map(p => ({x: p.x, y: p.y})); }\n      });\n      } else {\n      initialPositions = { [id]: { x: pos.x, y: pos.y } };\n      }\n          const groupIds = new Set();\n          nodesToCollect.forEach(nodeId => {\n           const groupId = NODE_DATA[nodeId]?.groupId;\n           if (groupId) {\n             groupIds.add(groupId);\n           }\n          });\n          if (groupIds.size > 0) {\n           Object.keys(NODE_DATA).forEach(nodeId => {\n             const nodeGroupId = NODE_DATA[nodeId]?.groupId;\n             if (nodeGroupId && groupIds.has(nodeGroupId)) {\n               if (!nodesToCollect.includes(nodeId)) {\n                 nodesToCollect.push(nodeId);\n               }\n             }\n           });\n          }\n          nodesToCollect = nodesToCollect.filter(nodeId => !NODE_DATA[nodeId]?.locked);\n          if (nodesToCollect.length === 0) {\n           return;\n          }\n          initialPositions = {};\n          nodesToCollect.forEach(nodeId => {\n           const nodePos = savedPositions[nodeId];\n           if (nodePos) {\n            initialPositions[nodeId] = { x: nodePos.x, y: nodePos.y };\n           }\n          });\n          g.style.cursor = \"grabbing\";\n          hitArea.style.cursor = \"grabbing\";\n          e.stopPropagation();\n         });\n         const handleMouseMove = (e) => {\n          if (!isDragging) return;\n          e.preventDefault();\n          const svgEl = document.getElementById(\"map\");\n          const pt = svgEl.createSVGPoint();\n          pt.x = e.clientX;\n          pt.y = e.clientY;\n          const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n          const dx = svgP.x - startX;\n          const dy = svgP.y - startY;\n          const nodesToMove = selectedNodes.has(id) ? Array.from(selectedNodes) : [id];\n          nodesToMove.forEach(nodeId => {\n           if (!initialPositions[nodeId]) return;\n           const initialPos = initialPositions[nodeId];\n           let newX = initialPos.x + dx;\n           let newY = initialPos.y + dy;\n           const nodeSize = savedSizes[nodeId] || 55;\n           newX = Math.max(nodeSize + CANVAS_PADDING, Math.min(CANVAS_WIDTH - nodeSize - CANVAS_PADDING, newX));\n           newY = Math.max(nodeSize + CANVAS_PADDING, Math.min(CANVAS_HEIGHT - nodeSize - CANVAS_PADDING, newY));\n           savedPositions[nodeId] = { x: newX, y: newY };\n           positions[nodeId] = { x: newX, y: newY };\n           if (nodeId === id) {\n            pos.x = newX;\n            pos.y = newY;\n           }\n           const nodeGroup = document.querySelector(`g[data-node-id=\"${nodeId}\"]`);\n          if (nodeGroup) {\n           nodeGroup.setAttribute(\"transform\", `translate(${newX},${newY})`);\n          }\n         });\n         Array.from(selectedRects).forEach(rectId => {\n           const rect = RECT_DATA.list.find(r => r.id === rectId);\n           if (rect && rect._dragStartX !== undefined) {\n             rect.x = rect._dragStartX + dx;\n             rect.y = rect._dragStartY + dy;\n           }\n         });\n         Array.from(selectedTexts).forEach(textId => {\n           const text = TEXT_DATA.list.find(t => t.id === textId);\n           if (text && text._dragStartX !== undefined) {\n             text.x = text._dragStartX + dx;\n             text.y = text._dragStartY + dy;\n           }\n         });\n         Array.from(selectedEdges).forEach(edgeId => {\n           const edge = EDGE_DATA.list.find(e => e.id === edgeId);\n           if (edge && edge._dragStartPoints) {\n             edge.points.forEach((p, i) => {\n               p.x = edge._dragStartPoints[i].x + dx;\n               p.y = edge._dragStartPoints[i].y + dy;\n             });\n           }\n         });\n         forgeTheTopology();\n         updateMinimap();\n          document.querySelectorAll(\".edge\").forEach((edgeEl) => {\n           const fromId = edgeEl.dataset.from;\n           const toId = edgeEl.dataset.to;\n           if (nodesToMove.includes(fromId) || nodesToMove.includes(toId)) {\n            const p1 = savedPositions[fromId] || positions[fromId] || {\n             x: 600,\n             y: 350\n            };\n            const p2 = savedPositions[toId] || positions[toId] || {\n             x: 600,\n             y: 350\n            };\n            if (edgeEl.tagName === \"line\") {\n             edgeEl.setAttribute(\"x1\", p1.x);\n             edgeEl.setAttribute(\"y1\", p1.y);\n             edgeEl.setAttribute(\"x2\", p2.x);\n             edgeEl.setAttribute(\"y2\", p2.y);\n            } else if (edgeEl.tagName === \"path\") {\n             const edgeId = edgeEl.dataset.edgeId;\n             const edge = EDGE_DATA.list.find(\n              (e) => e.id === edgeId);\n             if (edge) {\n              const pairTotal = edge._pairTotal || 1;\n              const pairIndex = edge._pairIndex || 0;\n              const midX = (p1.x + p2.x) / 2;\n              const midY = (p1.y + p2.y) / 2;\n              const dx = p2.x - p1.x;\n              const dy = p2.y - p1.y;\n              const len = Math.sqrt(dx * dx + dy * dy) || 1;\n              const perpX = -dy / len;\n              const perpY = dx / len;\n              let offsetAmount = 0;\n              if (pairTotal > 1) {\n               offsetAmount = (pairIndex - (pairTotal - 1) / 2) * 40;\n              }\n              const ctrlX = midX + perpX * offsetAmount;\n              const ctrlY = midY + perpY * offsetAmount;\n              edgeEl.setAttribute(\"d\", `M ${p1.x} ${p1.y} Q ${ctrlX} ${ctrlY} ${p2.x} ${p2.y}`);\n             }\n            }\n           }\n          });\n         };\n      const handleMouseUp = () => {\n      if (isDragging) {\n      isDragging = false;\n      g.style.cursor = \"grab\";\n      hitArea.style.cursor = \"grab\";\n      if (currentView.mode === \"rack\" && NODE_DATA[id]?.assignedRack === currentView.rackId) {\n      const draggedY = savedPositions[id]?.y || pos.y;\n      const rackCapacity = getRackCapacity(currentView.rackId);\n      const rackUHeight = getRackUHeight(currentView.rackId);\n      let newUnit = rackCapacity - Math.round((draggedY - RACK_START_Y) / rackUHeight);\n      newUnit = Math.max(1, Math.min(newUnit, rackCapacity));\n      NODE_DATA[id].rackUnit = newUnit;\n      forgeTheTopology();\n      claimTheImmortal(id);\n      }\n      }\n      };\n         document.addEventListener(\"mousemove\", handleMouseMove);\n         document.addEventListener(\"mouseup\", handleMouseUp);\n         let touchStartTime = 0;\n         let touchStartX = 0;\n         let touchStartY = 0;\n         let touchMoved = false;\n       g.addEventListener(\"touchstart\",\n         (e) => {\n          if (NODE_DATA[id]?.locked) {\n           return;\n          }\n          e.preventDefault();\n          touchStartTime = Date.now();\n          touchMoved = false;\n          const svgEl = document.getElementById(\"map\");\n          const pt = svgEl.createSVGPoint();\n          const touch = e.touches[0];\n          pt.x = touch.clientX;\n          pt.y = touch.clientY;\n          const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n          touchStartX = svgP.x;\n          touchStartY = svgP.y;\n          startX = svgP.x;\n          startY = svgP.y;\n          let nodesToCollect = [];\n          if (selectedNodes.has(id)) {\n           nodesToCollect = Array.from(selectedNodes);\n          } else {\n           nodesToCollect = [id];\n          }\n          const groupIds = new Set();\n          nodesToCollect.forEach(nodeId => {\n           const groupId = NODE_DATA[nodeId]?.groupId;\n           if (groupId) {\n             groupIds.add(groupId);\n           }\n          });\n          if (groupIds.size > 0) {\n           Object.keys(NODE_DATA).forEach(nodeId => {\n             const nodeGroupId = NODE_DATA[nodeId]?.groupId;\n             if (nodeGroupId && groupIds.has(nodeGroupId)) {\n               if (!nodesToCollect.includes(nodeId)) {\n                 nodesToCollect.push(nodeId);\n               }\n             }\n           });\n          }\n          nodesToCollect = nodesToCollect.filter(nodeId => !NODE_DATA[nodeId]?.locked);\n          if (nodesToCollect.length === 0) {\n           return;\n          }\n          initialPositions = {};\n          nodesToCollect.forEach(nodeId => {\n           const nodePos = savedPositions[nodeId];\n           if (nodePos) {\n            initialPositions[nodeId] = { x: nodePos.x, y: nodePos.y };\n           }\n          });\n          e.stopPropagation();\n         }, {\n          passive: false\n         });\n         g.addEventListener(\"touchmove\", (e) => {\n\t\t  if (isViewOnly()) return;\n         e.preventDefault();\n         const svgEl = document.getElementById(\"map\");\n         const pt = svgEl.createSVGPoint();\n         const touch = e.touches[0];\n         pt.x = touch.clientX;\n         pt.y = touch.clientY;\n         const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n         const dx = Math.abs(svgP.x - touchStartX);\n         const dy = Math.abs(svgP.y - touchStartY);\n         if (dx > (isMobileDevice() ? 4 : 10) || dy > (isMobileDevice() ? 4 : 10)) {\n      touchMoved = true;\n      isDragging = true;\n      }\n         if (!isDragging) return;\n         const deltaX = svgP.x - startX;\n         const deltaY = svgP.y - startY;\n         const nodesToMove = selectedNodes.has(id) ? Array.from(selectedNodes) : [id];\n         nodesToMove.forEach(nodeId => {\n          if (!initialPositions[nodeId]) return;\n          const initialPos = initialPositions[nodeId];\n          let newX = initialPos.x + deltaX;\n          let newY = initialPos.y + deltaY;\n          const nodeSize = savedSizes[nodeId] || 55;\n          newX = Math.max(nodeSize + CANVAS_PADDING, Math.min(CANVAS_WIDTH - nodeSize - CANVAS_PADDING, newX));\n          newY = Math.max(nodeSize + CANVAS_PADDING, Math.min(CANVAS_HEIGHT - nodeSize - CANVAS_PADDING, newY));\n          savedPositions[nodeId] = { x: newX, y: newY };\n          positions[nodeId] = { x: newX, y: newY };\n          if (nodeId === id) {\n           pos.x = newX;\n           pos.y = newY;\n          }\n          const nodeGroup = document.querySelector(`g[data-node-id=\"${nodeId}\"]`);\n          if (nodeGroup) {\n           nodeGroup.setAttribute(\"transform\", `translate(${newX},${newY})`);\n          }\n         });\n         updateMinimap();\n         document.querySelectorAll(\".edge\").forEach((edgeEl) => {\n          const fromId = edgeEl.dataset.from;\n          const toId = edgeEl.dataset.to;\n          if (nodesToMove.includes(fromId) || nodesToMove.includes(toId)) {\n            const p1 = savedPositions[fromId] || positions[fromId] || {\n             x: 600,\n             y: 350\n            };\n            const p2 = savedPositions[toId] || positions[toId] || {\n             x: 600,\n             y: 350\n            };\n            if (edgeEl.tagName === \"line\") {\n             edgeEl.setAttribute(\"x1\", p1.x);\n             edgeEl.setAttribute(\"y1\", p1.y);\n             edgeEl.setAttribute(\"x2\", p2.x);\n             edgeEl.setAttribute(\"y2\", p2.y);\n            } else if (edgeEl.tagName === \"path\") {\n             const edgeId = edgeEl.dataset.edgeId;\n             const edge = EDGE_DATA.list.find(\n              (e) => e.id === edgeId);\n             if (edge) {\n              const pairTotal = edge._pairTotal || 1;\n              const pairIndex = edge._pairIndex || 0;\n              const midX = (p1.x + p2.x) / 2;\n              const midY = (p1.y + p2.y) / 2;\n              const dx = p2.x - p1.x;\n              const dy = p2.y - p1.y;\n              const len = Math.sqrt(dx * dx + dy * dy) || 1;\n              const perpX = -dy / len;\n              const perpY = dx / len;\n              let offsetAmount = 0;\n              if (pairTotal > 1) {\n               offsetAmount = (pairIndex - (pairTotal - 1) / 2) * 40;\n              }\n              const ctrlX = midX + perpX * offsetAmount;\n              const ctrlY = midY + perpY * offsetAmount;\n              edgeEl.setAttribute(\"d\", `M ${p1.x} ${p1.y} Q ${ctrlX} ${ctrlY} ${p2.x} ${p2.y}`);\n             }\n            }\n           }\n          });\n         }, {\n          passive: false\n         });\n      g.addEventListener(\"touchend\", (e) => {\n      const touchDuration = Date.now() - touchStartTime;\n      if (!touchMoved && touchDuration < 400) {\n       if (isViewOnly()) {\n        handleViewOnlyClick(id, 'node');\n        return;\n       }\n       claimTheImmortal(id);\n      }\n      if (isDragging) {\n      const draggedY = pos.y;\n      isDragging = false;\n      savedPositions[id] = {\n      x: pos.x,\n      y: pos.y\n      };\n      if (currentView.mode === \"rack\" && NODE_DATA[id]?.assignedRack === currentView.rackId) {\n      const rackCapacity = getRackCapacity(currentView.rackId);\n      const rackUHeight = getRackUHeight(currentView.rackId);\n      let newUnit = rackCapacity - Math.round((draggedY - RACK_START_Y) / rackUHeight);\n      newUnit = Math.max(1, Math.min(newUnit, rackCapacity));\n      NODE_DATA[id].rackUnit = newUnit;\n      forgeTheTopology();\n      claimTheImmortal(id);\n      }\n      }\n      touchMoved = false;\n      });\n         g.style.cursor = \"grab\";\n         g.addEventListener(\"click\", (e) => {\n          if (!isDragging) {\n           if (isViewOnly()) {\n            handleViewOnlyClick(id, 'node');\n            return;\n           }\n           claimTheImmortal(id);\n          }\n         });\n         svg.appendChild(g);\n        });\n        if (RECT_DATA && RECT_DATA.list) {\n         RECT_DATA.list.forEach((rect) => {\n          if (currentView.mode === \"rack\") return;\n          if (rect.style === \"outlined\") {\n           const g = document.createElementNS(ns, \"g\");\n           g.classList.add(\"rect-group\");\n           g.dataset.rectId = rect.id;\n           const rectCenterX = rect.x + rect.width / 2;\n           const rectCenterY = rect.y + rect.height / 2;\n           const rectRotation = rect.rotation || 0;\n           if (rectRotation !== 0) {\n             g.setAttribute(\"transform\", `rotate(${rectRotation}, ${rectCenterX}, ${rectCenterY})`);\n           }\n           const rectEl = document.createElementNS(ns, \"rect\");\n           rectEl.classList.add(\"rect-shape\");\n           rectEl.setAttribute(\"x\", rect.x);\n           rectEl.setAttribute(\"y\", rect.y);\n           rectEl.setAttribute(\"width\", rect.width);\n           rectEl.setAttribute(\"height\", rect.height);\n           rectEl.style.fill = \"none\";\n           rectEl.style.stroke = rect.borderColor || rect.color;\n           rectEl.style.strokeWidth = (rect.borderWidth !== undefined ? rect.borderWidth : 3) + \"px\";\nif (rect.lineStyle === \"dashed\") { rectEl.style.strokeDasharray = \"10,5\"; }\n           else if (rect.lineStyle === \"dotted\") { rectEl.style.strokeDasharray = \"2,4\"; }\n           else if (rect.lineStyle === \"wall\") {\n             rectEl.style.fill = rect.color;\n             rectEl.style.fillOpacity = \"0.5\";\n             rectEl.style.stroke = rect.borderColor || rect.color;\n             rectEl.style.strokeWidth = (rect.borderWidth !== undefined ? rect.borderWidth : 2) + \"px\";\n             const hatchGroup = document.createElementNS(ns, \"g\");\n             hatchGroup.classList.add(\"wall-hatch-lines\");\n             hatchGroup.style.pointerEvents = \"none\";\n             const spacing = 12;\n             const hatchColor = rect.borderColor || rect.color;\n             for (let i = -rect.height; i < rect.width + rect.height; i += spacing) {\n               const line = document.createElementNS(ns, \"line\");\n               line.setAttribute(\"x1\", rect.x + i);\n               line.setAttribute(\"y1\", rect.y);\n               line.setAttribute(\"x2\", rect.x + i - rect.height);\n               line.setAttribute(\"y2\", rect.y + rect.height);\n               line.style.stroke = hatchColor;\n               line.style.strokeWidth = \"2\";\n               hatchGroup.appendChild(line);\n             }\n             const clipId = \"clip-\" + rect.id;\n             const clipPath = document.createElementNS(ns, \"clipPath\");\n             clipPath.id = clipId;\n             const clipRect = document.createElementNS(ns, \"rect\");\n             clipRect.setAttribute(\"x\", rect.x);\n             clipRect.setAttribute(\"y\", rect.y);\n             clipRect.setAttribute(\"width\", rect.width);\n             clipRect.setAttribute(\"height\", rect.height);\n             clipPath.appendChild(clipRect);\n             defs.appendChild(clipPath);\n             hatchGroup.setAttribute(\"clip-path\", \"url(#\" + clipId + \")\");\n             g.appendChild(hatchGroup);\n           }\n           else { rectEl.style.strokeDasharray = \"none\"; }\n           rectEl.style.cursor = \"move\";\n           rectEl.addEventListener(\"click\", (e) => {\n             if (rectDrawMode) return;\n             e.stopPropagation();\n             currentRectId = rect.id;\n             selectTheRect(rect.id);\n           });\n           rectEl.addEventListener(\"contextmenu\", (e) => {\n             e.preventDefault();\n             e.stopPropagation();\n             if (selectedRects.has(rect.id)) {\n               selectedRects.delete(rect.id);\n             } else {\n               selectedRects.add(rect.id);\n             }\n             updateAllSelections();\n           });\n           let rectLastTap = 0;\n           rectEl.addEventListener(\"touchend\", (e) => {\n             const now = Date.now();\n             if (now - rectLastTap < 300) {\n               e.preventDefault();\n               if (selectedRects.has(rect.id)) {\n                 selectedRects.delete(rect.id);\n               } else {\n                 selectedRects.add(rect.id);\n               }\n               updateAllSelections();\n               if (navigator.vibrate) navigator.vibrate(50);\n               rectLastTap = 0;\n             } else {\n               rectLastTap = now;\n             }\n           }, { passive: false });\n           const deleteBtn = document.createElementNS(ns, \"g\");\n           deleteBtn.classList.add(\"rect-delete-btn\");\n           deleteBtn.style.cursor = \"pointer\";\n           deleteBtn.style.display = rectDrawMode ? \"block\" : \"none\";\n           const deleteBg = document.createElementNS(ns, \"circle\");\n           deleteBg.setAttribute(\"cx\", rect.x + rect.width - 10);\n           deleteBg.setAttribute(\"cy\", rect.y + 10);\n           deleteBg.setAttribute(\"r\", 12);\n           deleteBg.style.fill = \"#f56565\";\n           deleteBg.style.stroke = \"white\";\n           deleteBg.style.strokeWidth = \"2\";\n           const deleteX = document.createElementNS(ns, \"text\");\n           deleteX.setAttribute(\"x\", rect.x + rect.width - 10);\n           deleteX.setAttribute(\"y\", rect.y + 10);\n           deleteX.setAttribute(\"text-anchor\", \"middle\");\n           deleteX.setAttribute(\"dominant-baseline\", \"middle\");\n           deleteX.style.fill = \"white\";\n           deleteX.style.fontSize = \"16px\";\n           deleteX.style.fontWeight = \"bold\";\n           deleteX.style.pointerEvents = \"none\";\n           deleteX.textContent = \"×\";\n           deleteBtn.appendChild(deleteBg);\n           deleteBtn.appendChild(deleteX);\n           deleteBtn.addEventListener(\"click\", (e) => {\n            e.stopPropagation();\n            e.preventDefault();\n            deleteRectangle(rect.id);\n           });\n           let isDragging = false;\n           let dragStartX, dragStartY;\n           let rectStartX, rectStartY;\n           rectEl.addEventListener(\"mousedown\", (e) => {\n\t\t   if (isViewOnly()) return;\n            if (rectDrawMode) return;\n            e.preventDefault();\n            e.stopPropagation();\n            isDragging = true;\n            dragStartX = e.clientX;\n            dragStartY = e.clientY;\n            rectStartX = rect.x;\n            rectStartY = rect.y;\n            rectEl.style.cursor = \"grabbing\";\n           });\n      const moveHandler = (e) => {\n      if (!isDragging || rectDrawMode) return;\n      const svgEl = svg;\n      const pt1 = svgEl.createSVGPoint();\n      pt1.x = dragStartX;\n      pt1.y = dragStartY;\n      const svgP1 = pt1.matrixTransform(svgEl.getScreenCTM().inverse());\n      const pt2 = svgEl.createSVGPoint();\n      pt2.x = e.clientX;\n      pt2.y = e.clientY;\n      const svgP2 = pt2.matrixTransform(svgEl.getScreenCTM().inverse());\n      const dx = svgP2.x - svgP1.x;\n      const dy = svgP2.y - svgP1.y;\n      rect.x = rectStartX + dx;\n      rect.y = rectStartY + dy;\n      if (selectedRects.has(rect.id)) {\n      selectedNodes.forEach(nodeId => { if (initialPositions[nodeId]) { savedPositions[nodeId] = { x: initialPositions[nodeId].x + dx, y: initialPositions[nodeId].y + dy }; } });\n      selectedRects.forEach(rectId => { if (rectId === rect.id) return; const r = RECT_DATA.list.find(x => x.id === rectId); if (r && r._dragStartX !== undefined) { r.x = r._dragStartX + dx; r.y = r._dragStartY + dy; } });\n      selectedTexts.forEach(textId => { const t = TEXT_DATA.list.find(x => x.id === textId); if (t && t._dragStartX !== undefined) { t.x = t._dragStartX + dx; t.y = t._dragStartY + dy; } });\n      selectedEdges.forEach(edgeId => { const ed = EDGE_DATA.list.find(x => x.id === edgeId); if (ed && ed._dragStartPoints) { ed.points.forEach((p, i) => { p.x = ed._dragStartPoints[i].x + dx; p.y = ed._dragStartPoints[i].y + dy; }); } });\n      }\n      forgeTheTopology();\n      };\n           const upHandler = () => {\n            if (isDragging) {\n             isDragging = false;\n             rectEl.style.cursor = \"move\";\n            }\n           };\n           document.addEventListener(\"mousemove\", moveHandler);\n           document.addEventListener(\"mouseup\", upHandler);\n           let touchStartX, touchStartY;\n           let touchRectStartX, touchRectStartY;\n           rectEl.addEventListener(\"touchstart\", (e) => {\n\t\t    if (isViewOnly()) return;\n            if (rectDrawMode) return;\n            e.preventDefault();\n            e.stopPropagation();\n            isDragging = true;\n            const touch = e.touches[0];\n            touchStartX = touch.clientX;\n            touchStartY = touch.clientY;\n            touchRectStartX = rect.x;\n            touchRectStartY = rect.y;\n            rectEl.style.cursor = \"grabbing\";\n           }, { passive: false });\n           rectEl.addEventListener(\"touchmove\", (e) => {\n\t\t   if (isViewOnly()) return;\n            if (!isDragging || rectDrawMode) return;\n            if (!e.touches[0]) return;\n            e.preventDefault();\n            e.stopPropagation();\n            const svgEl = svg;\n            const pt1 = svgEl.createSVGPoint();\n            pt1.x = touchStartX;\n            pt1.y = touchStartY;\n            const svgP1 = pt1.matrixTransform(svgEl.getScreenCTM().inverse());\n            const touch = e.touches[0];\n            const pt2 = svgEl.createSVGPoint();\n            pt2.x = touch.clientX;\n            pt2.y = touch.clientY;\n            const svgP2 = pt2.matrixTransform(svgEl.getScreenCTM().inverse());\n            const dx = svgP2.x - svgP1.x;\n            const dy = svgP2.y - svgP1.y;\n            rect.x = touchRectStartX + dx;\n            rect.y = touchRectStartY + dy;\n            forgeTheTopology();\n           }, { passive: false });\n           rectEl.addEventListener(\"touchend\", (e) => {\n            if (isDragging) {\n             e.preventDefault();\n             isDragging = false;\n             rectEl.style.cursor = \"move\";\n            }\n           }, { passive: false });\n           if (currentRectId === rect.id) {\n             const corners = [\n               { cx: rect.x, cy: rect.y, cursor: 'nwse-resize', dx: -1, dy: -1 },\n               { cx: rect.x + rect.width, cy: rect.y, cursor: 'nesw-resize', dx: 1, dy: -1 },\n               { cx: rect.x, cy: rect.y + rect.height, cursor: 'nesw-resize', dx: -1, dy: 1 },\n               { cx: rect.x + rect.width, cy: rect.y + rect.height, cursor: 'nwse-resize', dx: 1, dy: 1 }\n             ];\n             corners.forEach((corner) => {\n               const handle = document.createElementNS(ns, \"circle\");\n               handle.setAttribute(\"cx\", corner.cx);\n               handle.setAttribute(\"cy\", corner.cy);\n               const borderW = rect.borderWidth !== undefined ? rect.borderWidth : 3;\n               const handleSize = Math.max(PAGE_STATE.selectionHandleSize || 8, borderW + 4);\n               handle.setAttribute(\"r\", handleSize);\n               handle.style.fill = PAGE_STATE.selectionHandle || \"#f59e0b\";\n               handle.style.stroke = \"#fff\";\n               handle.style.strokeWidth = \"2\";\n               handle.style.cursor = corner.cursor;\n               handle.addEventListener(\"mousedown\", (e) => {\n\t\t\t   if (isViewOnly()) return;\n                 e.preventDefault();\n                 e.stopPropagation();\n\t\t\t\t pushUndo(\"resize zone\");\n                 let dragging = true;\n                 const startX = e.clientX, startY = e.clientY;\n                 const origX = rect.x, origY = rect.y, origW = rect.width, origH = rect.height;\n                 const moveHandler = (ev) => {\n                   if (!dragging) return;\n                   const pt1 = svg.createSVGPoint(); pt1.x = startX; pt1.y = startY;\n                   const pt2 = svg.createSVGPoint(); pt2.x = ev.clientX; pt2.y = ev.clientY;\n                   const svgP1 = pt1.matrixTransform(svg.getScreenCTM().inverse());\n                   const svgP2 = pt2.matrixTransform(svg.getScreenCTM().inverse());\n                   const dx = svgP2.x - svgP1.x, dy = svgP2.y - svgP1.y;\n                   if (corner.dx < 0) { rect.x = origX + dx; rect.width = origW - dx; }\n                   else { rect.width = origW + dx; }\n                   if (corner.dy < 0) { rect.y = origY + dy; rect.height = origH - dy; }\n                   else { rect.height = origH + dy; }\n                   if (rect.width < 20) rect.width = 20;\n                   if (rect.height < 20) rect.height = 20;\n                   forgeTheTopology();\n                 };\n                 const upHandler = () => { dragging = false; document.removeEventListener(\"mousemove\", moveHandler); document.removeEventListener(\"mouseup\", upHandler); };\n                 document.addEventListener(\"mousemove\", moveHandler);\n                 document.addEventListener(\"mouseup\", upHandler);\n               });\n               g.appendChild(handle);\n             });\n           }\n           if (rect.groupId) {\n             const groupIndicator = document.createElementNS(ns, \"rect\");\n             groupIndicator.setAttribute(\"x\", rect.x - 4);\n             groupIndicator.setAttribute(\"y\", rect.y - 4);\n             groupIndicator.setAttribute(\"width\", rect.width + 8);\n             groupIndicator.setAttribute(\"height\", rect.height + 8);\n             groupIndicator.style.fill = \"none\";\n         groupIndicator.style.stroke = PAGE_STATE.groupIndicator || \"#4fd1c5\";\n             groupIndicator.style.strokeWidth = \"3\";\n             groupIndicator.style.strokeDasharray = \"5,5\";\n             groupIndicator.style.pointerEvents = \"none\";\n             g.insertBefore(groupIndicator, g.firstChild);\n           }\n           g.appendChild(rectEl);\n           g.appendChild(deleteBtn);\n           svg.appendChild(g);\n          }\n         });\n        }\n        if (TEXT_DATA && TEXT_DATA.list) {\n         TEXT_DATA.list.forEach((textItem) => {\n          if (currentView.mode === \"rack\") return;\n          const g = document.createElementNS(ns, \"g\");\n          g.classList.add(\"text-group\");\n          g.dataset.textId = textItem.id;\n          const textRotation = textItem.rotation || 0;\n          if (textRotation !== 0) {\n            g.setAttribute(\"transform\", `rotate(${textRotation}, ${textItem.x}, ${textItem.y})`);\n          }\n          if (textItem.bgEnabled) {\n           const bgRect = document.createElementNS(ns, \"rect\");\n           bgRect.classList.add(\"text-bg\");\n           bgRect.setAttribute(\"x\", textItem.x - 5);\n           bgRect.setAttribute(\"y\", textItem.y - textItem.fontSize - 2);\n           bgRect.setAttribute(\"width\", 100);\n           bgRect.setAttribute(\"height\", textItem.fontSize + 10);\n           bgRect.style.fill = textItem.bgColor;\n           bgRect.style.opacity = \"0.7\";\n           bgRect.style.rx = \"4\";\n           g.appendChild(bgRect);\n          }\n          const textEl = document.createElementNS(ns, \"text\");\n          textEl.classList.add(\"text-element\");\n          textEl.setAttribute(\"x\", textItem.x);\n          textEl.setAttribute(\"y\", textItem.y);\n          textEl.style.fill = textItem.color;\n          textEl.style.fontSize = textItem.fontSize + \"px\";\n          textEl.style.fontWeight = textItem.fontWeight;\n          textEl.style.fontStyle = textItem.fontStyle;\n          textEl.style.textAnchor = textItem.textAlign;\n          textEl.style.textDecoration = textItem.textDecoration;\n          textEl.style.opacity = textItem.opacity;\n          textEl.style.cursor = \"move\";\n          textEl.style.userSelect = \"none\";\n          textEl.setAttribute(\"dominant-baseline\", \"middle\");\n          const lines = textItem.content.split('\\n');\n          if (lines.length === 1) {\n           textEl.textContent = textItem.content;\n          } else {\n           lines.forEach((line, i) => {\n            const tspan = document.createElementNS(ns, \"tspan\");\n            tspan.textContent = line;\n            tspan.setAttribute(\"x\", textItem.x);\n            tspan.setAttribute(\"dy\", i === 0 ? 0 : textItem.fontSize * 1.2);\n            textEl.appendChild(tspan);\n           });\n          }\n          g.appendChild(textEl);\n          if (textItem.bgEnabled) {\n           setTimeout(() => {\n            try {\n             const bbox = textEl.getBBox();\n             const bgRect = g.querySelector('.text-bg');\n             if (bgRect && bbox) {\n              bgRect.setAttribute(\"x\", bbox.x - 5);\n              bgRect.setAttribute(\"y\", bbox.y - 2);\n              bgRect.setAttribute(\"width\", bbox.width + 10);\n              bgRect.setAttribute(\"height\", bbox.height + 4);\n             }\n            } catch (e) {\n            }\n           }, 0);\n          }\n          const deleteBtn = document.createElementNS(ns, \"g\");\n          deleteBtn.classList.add(\"text-delete-btn\");\n          deleteBtn.style.cursor = \"pointer\";\n          deleteBtn.style.display = textDrawMode ? \"block\" : \"none\";\n          const deleteBg = document.createElementNS(ns, \"circle\");\n          deleteBg.setAttribute(\"cx\", textItem.x + 20);\n          deleteBg.setAttribute(\"cy\", textItem.y - textItem.fontSize);\n          deleteBg.setAttribute(\"r\", 12);\n          deleteBg.style.fill = \"#f56565\";\n          deleteBg.style.stroke = \"white\";\n          deleteBg.style.strokeWidth = \"2\";\n          const deleteX = document.createElementNS(ns, \"text\");\n          deleteX.setAttribute(\"x\", textItem.x + 20);\n          deleteX.setAttribute(\"y\", textItem.y - textItem.fontSize);\n          deleteX.setAttribute(\"text-anchor\", \"middle\");\n          deleteX.setAttribute(\"dominant-baseline\", \"middle\");\n          deleteX.style.fill = \"white\";\n          deleteX.style.fontSize = \"16px\";\n          deleteX.style.fontWeight = \"bold\";\n          deleteX.style.pointerEvents = \"none\";\n          deleteX.textContent = \"×\";\n          deleteBtn.appendChild(deleteBg);\n          deleteBtn.appendChild(deleteX);\n          deleteBtn.addEventListener(\"click\", (e) => {\n           e.stopPropagation();\n           e.preventDefault();\n           deleteText(textItem.id);\n          });\n          let isDragging = false;\n          let dragStartX, dragStartY;\n          let textStartX, textStartY;\n          textEl.addEventListener(\"mousedown\", (e) => {\n\t\t  if (isViewOnly()) return;\n      if (textDrawMode) return;\n      e.preventDefault();\n      e.stopPropagation();\n      isDragging = true;\n      dragStartX = e.clientX;\n      dragStartY = e.clientY;\n      textStartX = textItem.x;\n      textStartY = textItem.y;\n      textEl.style.cursor = \"grabbing\";\n      showTextPanel(textItem.id);\n      if (selectedTexts.has(textItem.id)) {\n      initialPositions = {};\n      selectedNodes.forEach(nodeId => {\n      const nodePos = savedPositions[nodeId];\n      if (nodePos) initialPositions[nodeId] = { x: nodePos.x, y: nodePos.y };\n      });\n      selectedRects.forEach(rectId => {\n      const r = RECT_DATA.list.find(x => x.id === rectId);\n      if (r) { r._dragStartX = r.x; r._dragStartY = r.y; }\n      });\n      selectedTexts.forEach(textId => {\n      const t = TEXT_DATA.list.find(x => x.id === textId);\n      if (t) { t._dragStartX = t.x; t._dragStartY = t.y; }\n      });\n      selectedEdges.forEach(edgeId => {\n      const ed = EDGE_DATA.list.find(x => x.id === edgeId);\n      if (ed && ed.points) { ed._dragStartPoints = ed.points.map(p => ({x: p.x, y: p.y})); }\n      });\n      }\n      });\n          const moveHandler = (e) => {\n      if (!isDragging || textDrawMode) return;\n      const svgEl = svg;\n      const pt1 = svgEl.createSVGPoint();\n      pt1.x = dragStartX;\n      pt1.y = dragStartY;\n      const svgP1 = pt1.matrixTransform(svgEl.getScreenCTM().inverse());\n      const pt2 = svgEl.createSVGPoint();\n      pt2.x = e.clientX;\n      pt2.y = e.clientY;\n      const svgP2 = pt2.matrixTransform(svgEl.getScreenCTM().inverse());\n      const dx = svgP2.x - svgP1.x;\n      const dy = svgP2.y - svgP1.y;\n      textItem.x = textStartX + dx;\n      textItem.y = textStartY + dy;\n      if (selectedTexts.has(textItem.id)) {\n      selectedNodes.forEach(nodeId => {\n      if (initialPositions[nodeId]) {\n       const newX = initialPositions[nodeId].x + dx;\n       const newY = initialPositions[nodeId].y + dy;\n       savedPositions[nodeId] = { x: newX, y: newY };\n      }\n      });\n      selectedRects.forEach(rectId => {\n      const r = RECT_DATA.list.find(x => x.id === rectId);\n      if (r && r._dragStartX !== undefined) { r.x = r._dragStartX + dx; r.y = r._dragStartY + dy; }\n      });\n      selectedTexts.forEach(textId => {\n      if (textId === textItem.id) return;\n      const t = TEXT_DATA.list.find(x => x.id === textId);\n      if (t && t._dragStartX !== undefined) { t.x = t._dragStartX + dx; t.y = t._dragStartY + dy; }\n      });\n      selectedEdges.forEach(edgeId => {\n      const ed = EDGE_DATA.list.find(x => x.id === edgeId);\n      if (ed && ed._dragStartPoints) { ed.points.forEach((p, i) => { p.x = ed._dragStartPoints[i].x + dx; p.y = ed._dragStartPoints[i].y + dy; }); }\n      });\n      }\n      forgeTheTopology();\n      };\n          const upHandler = () => {\n           if (isDragging) {\n            isDragging = false;\n            textEl.style.cursor = \"move\";\n           }\n          };\n          document.addEventListener(\"mousemove\", moveHandler);\n          document.addEventListener(\"mouseup\", upHandler);\n          let touchStartX = 0, touchStartY = 0;\n          let textTouchStartX = 0, textTouchStartY = 0;\n          textEl.addEventListener(\"touchstart\", (e) => {\n\t\t   if (isViewOnly()) return;\n           if (textDrawMode) return;\n           if (e.touches.length !== 1) return;\n           e.preventDefault();\n           e.stopPropagation();\n           const touch = e.touches[0];\n           isDragging = true;\n           touchStartX = touch.clientX;\n           touchStartY = touch.clientY;\n           textTouchStartX = textItem.x;\n           textTouchStartY = textItem.y;\n           showTextPanel(textItem.id);\n          }, { passive: false });\n          textEl.addEventListener(\"touchmove\", (e) => {\n           if (!isDragging || textDrawMode) return;\n           if (e.touches.length !== 1) return;\n           e.preventDefault();\n           const touch = e.touches[0];\n           const svgEl = svg;\n           const pt1 = svgEl.createSVGPoint();\n           pt1.x = touchStartX;\n           pt1.y = touchStartY;\n           const svgP1 = pt1.matrixTransform(svgEl.getScreenCTM().inverse());\n           const pt2 = svgEl.createSVGPoint();\n           pt2.x = touch.clientX;\n           pt2.y = touch.clientY;\n           const svgP2 = pt2.matrixTransform(svgEl.getScreenCTM().inverse());\n           const dx = svgP2.x - svgP1.x;\n           const dy = svgP2.y - svgP1.y;\n           textItem.x = textTouchStartX + dx;\n           textItem.y = textTouchStartY + dy;\n           forgeTheTopology();\n          }, { passive: false });\n          textEl.addEventListener(\"touchend\", () => {\n           if (isDragging) {\n            isDragging = false;\n           }\n          }, { passive: false });\n          textEl.addEventListener(\"contextmenu\", (e) => {\n           e.preventDefault();\n           e.stopPropagation();\n           if (selectedTexts.has(textItem.id)) {\n            selectedTexts.delete(textItem.id);\n           } else {\n            selectedTexts.add(textItem.id);\n           }\n           updateAllSelections();\n          });\n          let textLastTap = 0;\n          g.addEventListener(\"touchend\", (e) => {\n           const now = Date.now();\n           if (now - textLastTap < 300) {\n            e.preventDefault();\n            if (selectedTexts.has(textItem.id)) {\n             selectedTexts.delete(textItem.id);\n            } else {\n             selectedTexts.add(textItem.id);\n            }\n            updateAllSelections();\n            if (navigator.vibrate) navigator.vibrate(50);\n            textLastTap = 0;\n           } else {\n            textLastTap = now;\n           }\n          }, { passive: false });\n          if (textItem.groupId) {\n            const groupIndicator = document.createElementNS(ns, \"rect\");\n            groupIndicator.setAttribute(\"x\", textItem.x - 54);\n            groupIndicator.setAttribute(\"y\", textItem.y - 24);\n            groupIndicator.setAttribute(\"width\", 108);\n            groupIndicator.setAttribute(\"height\", 48);\n            groupIndicator.setAttribute(\"rx\", \"8\");\n            groupIndicator.style.fill = \"none\";\n       groupIndicator.style.stroke = PAGE_STATE.groupIndicator || \"#4fd1c5\";\n            groupIndicator.style.strokeWidth = \"3\";\n            groupIndicator.style.strokeDasharray = \"5,5\";\n            groupIndicator.style.pointerEvents = \"none\";\n            g.insertBefore(groupIndicator, g.firstChild);\n          }\n          g.appendChild(deleteBtn);\n          svg.appendChild(g);\n         });\n        }\n        Object.keys(NODE_DATA).forEach(nodeId => {\n         updatePingIndicator(nodeId);\n        });\n        forgeTheLegend();\n\t\tupdateZoneLegend();\n        updateMinimap();\n        if (currentSearchQuery && currentSearchResults.length > 0) {\n         highlightSearchResults(currentSearchResults, true);\n        }\n       }\n       const _forgeTheTopologyImpl = forgeTheTopology;\n       forgeTheTopology = function(immediate = false) {\n        if (immediate || forgeImmediate) {\n         forgeImmediate = false;\n         clearTimeout(forgeDebounceTimer);\n         _forgeTheTopologyImpl();\n         return;\n        }\n        clearTimeout(forgeDebounceTimer);\n        forgeDebounceTimer = setTimeout(() => {\n         _forgeTheTopologyImpl();\n        }, 16);\n       };\n       function forgeTheTopologyImmediate() {\n        forgeImmediate = true;\n        forgeTheTopology();\n       }\n       function showEditModal(title, currentValue, onSave) {\n        const modal = document.getElementById(\"edit-modal\");\n        const input = document.getElementById(\"modal-input\");\n        const titleEl = document.getElementById(\"modal-title\");\n        const saveBtn = document.getElementById(\"modal-save\");\n        const cancelBtn = document.getElementById(\"modal-cancel\");\n        titleEl.textContent = title;\n        input.value = currentValue;\n        modal.classList.add(\"active\");\n        input.focus();\n        input.select();\n        const cleanup = () => {\n         modal.classList.remove(\"active\");\n         saveBtn.removeEventListener(\"click\", handleSave);\n         cancelBtn.removeEventListener(\"click\", handleCancel);\n         input.removeEventListener(\"keypress\", handleEnter);\n         modal.removeEventListener(\"click\", bgHandler);\n        };\n        const handleSave = () => {\n         if (input.value.trim()) {\n          onSave(input.value.trim());\n         }\n         cleanup();\n        };\n        const handleCancel = () => {\n         cleanup();\n        };\n        const handleEnter = (e) => {\n         if (e.key === \"Enter\") handleSave();\n        };\n        const bgHandler = (e) => {\n         if (e.target === modal) handleCancel();\n        };\n        saveBtn.addEventListener(\"click\", handleSave);\n        cancelBtn.addEventListener(\"click\", handleCancel);\n        input.addEventListener(\"keypress\", handleEnter);\n        modal.addEventListener(\"click\", bgHandler);\n       }\n       function challengeTheImmortal(message, onConfirm) {\n        const modal = document.getElementById(\"confirm-modal\");\n        const messageEl = document.getElementById(\"confirm-message\");\n        const deleteBtn = document.getElementById(\"confirm-delete\");\n        const cancelBtn = document.getElementById(\"confirm-cancel\");\n        messageEl.textContent = message;\n        modal.classList.add(\"active\");\n\t    const cleanup = () => {\n         modal.classList.remove(\"active\");\n         deleteBtn.removeEventListener(\"click\", handleConfirm);\n         cancelBtn.removeEventListener(\"click\", handleCancel);\n         modal.removeEventListener(\"click\", bgHandler);\n        };\n        const handleConfirm = () => {\n         onConfirm();\n         cleanup();\n        };\n        const handleCancel = () => {\n         cleanup();\n        };\n        const bgHandler = (e) => {\n         if (e.target === modal) handleCancel();\n        };\n        deleteBtn.addEventListener(\"click\", handleConfirm);\n        cancelBtn.addEventListener(\"click\", handleCancel);\n        modal.addEventListener(\"click\", bgHandler);\n       }\n       const pageTitleEl = document.getElementById(\"page-title\");\n       if (pageTitleEl) {\n        pageTitleEl.addEventListener(\"click\", () => {\n         showEditModal(\"Edit Title\", PAGE_STATE.title || DEFAULT_PAGE_STATE.title,\n          (newTitle) => {\n           PAGE_STATE.title = newTitle;\n           wieldThePower();\n          });\n        });\n       }\n       function editNodeName(id) {\n        if (!NODE_DATA[id]) return;\n        showEditModal(\"Edit Name\", NODE_DATA[id].name, (newName) => {\n         if (!NODE_DATA[id]) return;\n         pushUndo(\"edit node name\");\n         NODE_DATA[id].name = newName;\n         const nodeGroup = document.querySelector(`g[data-node-id=\"${id}\"]`);\n         if (nodeGroup) {\n          const label = nodeGroup.querySelector(\".node-label\");\n          if (label) label.textContent = newName;\n         }\n         if (currentNodeId === id) {\n          document.getElementById(\"node-name\").textContent = newName;\n         }\n        });\n       }\n       function editNodeIp(id) {\n        if (!NODE_DATA[id]) return;\n        showEditModal(\"Edit IP/Subtitle\", NODE_DATA[id].ip, (newIp) => {\n         if (!NODE_DATA[id]) return;\n         pushUndo(\"edit node ip\");\n         NODE_DATA[id].ip = newIp;\n         const nodeGroup = document.querySelector(`g[data-node-id=\"${id}\"]`);\n         if (nodeGroup) {\n          const sub = nodeGroup.querySelector(\".node-sub\");\n          if (sub) sub.textContent = newIp;\n         }\n         if (currentNodeId === id) {\n          document.getElementById(\"node-ip\").textContent = newIp;\n         }\n        });\n       }\n       function claimTheImmortal(id) {\n\t   if (isViewOnly()) return;\n        if (!NODE_DATA[id]) return;\n        currentNodeId = id;\n        currentEdgeId = null;\n        currentRectId = null;\n        currentTextId = null;\n        const data = NODE_DATA[id];\n        document.querySelectorAll(\".node-group\").forEach((n) => {\n         n.classList.toggle(\"active\", n.dataset.nodeId === id);\n        });\n        document.querySelectorAll(\".edge\").forEach((e) => {\n        const active = e.dataset.from === id || e.dataset.to === id;\n        e.classList.toggle(\"active\", active);\n       });\n       document.querySelectorAll(\".rect-group\").forEach((r) => r.classList.remove(\"active\"));\n       document.querySelectorAll(\".text-element\").forEach((t) => t.classList.remove(\"active\"));\n       document.getElementById(\"node-panel\").style.display = \"block\";\n        document.getElementById(\"edge-panel\").style.display = \"none\";\n        document.getElementById(\"text-panel\").style.display = \"none\";\n        const toolbar = document.getElementById(\"topology-toolbar\");\n        if (!topologyToolbarCollapsed) {\n         toolbar.style.display = \"flex\";\n        }\n        updateTopologyToolbarVisibility();\n        document.getElementById(\"node-name\").textContent = data.name;\n        document.getElementById(\"node-ip\").textContent = data.ip;\n        const fovSection = document.getElementById(\"fov-section\");\n        if (fovSection) {\n            if (hasCoverageZone(data.shape)) {\n              fovSection.style.display = \"block\";\n              const defaults = getCoverageDefaults(data.shape);\n              document.getElementById(\"fov-enabled\").checked = data.fovEnabled || false;\n              document.getElementById(\"fov-angle\").value = data.fovAngle || defaults.angle;\n              document.getElementById(\"fov-angle-value\").textContent = (data.fovAngle || defaults.angle) + \"°\";\n              document.getElementById(\"fov-distance\").value = data.fovDistance || defaults.distance;\n              document.getElementById(\"fov-distance-value\").textContent = data.fovDistance || defaults.distance;\n              document.getElementById(\"fov-inner-radius\").value = data.fovInnerRadius || 0;\n              document.getElementById(\"fov-inner-radius-value\").textContent = data.fovInnerRadius || 0;\n              document.getElementById(\"fov-rotation\").value = data.fovRotation || 0;\n              document.getElementById(\"fov-rotation-value\").textContent = (data.fovRotation || 0) + \"°\";\n              document.getElementById(\"fov-color\").value = data.fovColor || \"#f59e0b\";\n              document.getElementById(\"fov-opacity\").value = data.fovOpacity || 20;\n              document.getElementById(\"fov-opacity-value\").textContent = (data.fovOpacity || 20) + \"%\";\n              document.getElementById(\"fov-gradient\").checked = data.fovGradient || false;\n              document.getElementById(\"fov-border-color\").value = data.fovBorderColor || \"#f59e0b\";\n              document.getElementById(\"fov-border-width\").value = data.fovBorderWidth !== undefined ? data.fovBorderWidth : 2;\n              document.getElementById(\"fov-border-width-value\").textContent = data.fovBorderWidth !== undefined ? data.fovBorderWidth : 2;\n              document.getElementById(\"fov-border-style\").value = data.fovBorderStyle || \"solid\";\n              document.getElementById(\"fov-border-opacity\").value = data.fovBorderOpacity !== undefined ? data.fovBorderOpacity : 100;\n              document.getElementById(\"fov-border-opacity-value\").textContent = (data.fovBorderOpacity !== undefined ? data.fovBorderOpacity : 100) + \"%\";\n              document.getElementById(\"fov-label\").value = data.fovLabel || \"\";\n              document.getElementById(\"fov-label-position\").value = data.fovLabelPosition || \"center\";\n              document.getElementById(\"fov-label-size\").value = data.fovLabelSize || 14;\n              document.getElementById(\"fov-label-size-value\").textContent = (data.fovLabelSize || 14) + \"px\";\n              document.getElementById(\"fov-label-color\").value = data.fovLabelColor || \"#ffffff\";\n              document.getElementById(\"fov-label-bold\").checked = data.fovLabelBold || false;\n              document.getElementById(\"fov-label-bg\").checked = data.fovLabelBg || false;\n              document.getElementById(\"fov-label-bg-color\").value = data.fovLabelBgColor || \"#000000\";\n              document.getElementById(\"fov-label-offset-x\").value = data.fovLabelOffsetX || 0;\n              document.getElementById(\"fov-label-offset-x-value\").textContent = data.fovLabelOffsetX || 0;\n              document.getElementById(\"fov-label-offset-y\").value = data.fovLabelOffsetY || 0;\n              document.getElementById(\"fov-label-offset-y-value\").textContent = data.fovLabelOffsetY || 0;\n              document.getElementById(\"fov-animate\").checked = data.fovAnimate || false;\n              document.getElementById(\"fov-animation-type\").value = data.fovAnimationType || defaults.animationType;\n              document.getElementById(\"fov-sweep\").value = data.fovSweep || 120;\n              document.getElementById(\"fov-sweep-value\").textContent = (data.fovSweep || 120) + \"°\";\n              document.getElementById(\"fov-speed\").value = data.fovSpeed || 4;\n              document.getElementById(\"fov-speed-value\").textContent = (data.fovSpeed || 4) + \"s\";\n            } else {\n              fovSection.style.display = \"none\";\n            }\n          }\n        document.getElementById(\"node-mac\").textContent = data.mac || \"--\";\n        document.getElementById(\"node-rack\").textContent = data.rackUnit || \"--\";\n        document.getElementById(\"node-uheight\").textContent = (data.uHeight || \"1\") + \"U\";\n        document.getElementById(\"node-role\").textContent = data.role;\n        populateRackDropdown();\n        const assignedRackSelect = document.getElementById(\"node-assigned-rack\");\n        if (assignedRackSelect) {\n         assignedRackSelect.value = data.assignedRack || \"\";\n        }\n        const rackCapacitySelect = document.getElementById(\"node-rack-capacity\");\n        if (rackCapacitySelect) {\n         rackCapacitySelect.value = data.rackCapacity || \"42\";\n        }\n        const isRack = data.isRack === true;\n        const isAssignedToRack = !!data.assignedRack;\n        const assignedRackRow = document.getElementById(\"assigned-rack-row\");\n        const rackCapacityRow = document.getElementById(\"rack-capacity-row\");\n        const uheightRow = document.getElementById(\"uheight-row\");\n        if (assignedRackRow) assignedRackRow.style.display = isRack ? \"none\" : \"flex\";\n        if (rackCapacityRow) rackCapacityRow.style.display = isRack ? \"flex\" : \"none\";\n        if (uheightRow) uheightRow.style.display = isAssignedToRack ? \"flex\" : \"none\";\n\t\tconst delBtn = document.getElementById(\"delete-node-btn\");\n        if (delBtn) delBtn.textContent = isRack ? \"Delete Rack\" : \"Delete Node\";\n\t\tconst rackContentsSection = document.getElementById(\"rack-contents-section\");\n        const rackContentsList = document.getElementById(\"rack-contents-list\");\n        const rackContentsCount = document.getElementById(\"rack-contents-count\");\n        if (rackContentsSection && rackContentsList) {\n        if (isRack) {\n         const nodesInRack = Object.entries(NODE_DATA).filter(([nid, n]) => n.assignedRack === id);\n         rackContentsCount.textContent = nodesInRack.length;\n         if (nodesInRack.length > 0) {\n          rackContentsList.innerHTML = '';\n          nodesInRack.forEach(([nid, n]) => {\n           const div = document.createElement('div');\n           div.style.cssText = 'padding: 8px 4px; border-bottom: 1px solid var(--edge-main); cursor: pointer;';\n           div.onclick = () => claimTheImmortal(nid);\n           const nameSpan = document.createElement('span');\n           nameSpan.style.color = 'var(--text-main)';\n           nameSpan.textContent = n.name;\n           div.appendChild(nameSpan);\n           (n.tags || []).forEach(t => {\n            const tagSpan = document.createElement('span');\n            tagSpan.style.cssText = 'background: var(--accent); color: var(--bg); padding: 2px 6px; border-radius: 4px; font-size: 11px; margin-left: 4px;';\n            tagSpan.textContent = t;\n            div.appendChild(tagSpan);\n           });\n           rackContentsList.appendChild(div);\n          });\n         } else {\n          rackContentsList.innerHTML = '<div style=\"color: var(--text-soft); font-style: italic; padding: 8px 4px;\">No nodes assigned</div>';\n         }\n         rackContentsSection.style.display = \"block\";\n         } else {\n         rackContentsSection.style.display = \"none\";\n         }\n        }\n       const connectionsSection = document.getElementById(\"node-connections-section\");\n       const connectionsList = document.getElementById(\"node-connections-list\");\n       const connectionsCount = document.getElementById(\"node-connections-count\");\n       if (connectionsSection && connectionsList) {\n        const connectedEdges = (EDGE_DATA.list || []).filter(e => e.from === id || e.to === id);\n        connectionsCount.textContent = connectedEdges.length;\n        if (connectedEdges.length > 0) {\n         connectionsList.innerHTML = '';\n         connectedEdges.forEach(e => {\n          const isFrom = e.from === id;\n          const localPort = isFrom ? e.fromPort : e.toPort;\n          const remoteNodeId = isFrom ? e.to : e.from;\n          const remotePort = isFrom ? e.toPort : e.fromPort;\n          const remoteName = NODE_DATA[remoteNodeId]?.name || remoteNodeId;\n          const div = document.createElement('div');\n          div.style.cssText = 'padding: 6px 0; border-bottom: 1px solid var(--edge-main); display: flex; align-items: center; font-size: 13px;';\n          const localSpan = document.createElement('span');\n          localSpan.style.cssText = localPort ? 'color: var(--accent); font-family: monospace; cursor: pointer;' : 'color: var(--text-soft); cursor: pointer;';\n          localSpan.textContent = localPort || '-';\n          localSpan.title = localPort ? 'Click to view connection' : 'Click to set port';\n          localSpan.onclick = () => { if (localPort) { selectTheConnection(e.id); } else { const label = `Port on ${NODE_DATA[id]?.name || id}:`; const newVal = prompt(label, ''); if (newVal !== null && newVal !== '') { const isDupe = (EDGE_DATA.list || []).some(x => x.id !== e.id && ((x.from === id && x.fromPort === newVal) || (x.to === id && x.toPort === newVal))); if (isDupe && !confirm(`Warning: Port \"${newVal}\" is already used on ${NODE_DATA[id]?.name || id}. Use anyway?`)) return; if (isFrom) e.fromPort = newVal; else e.toPort = newVal; claimTheImmortal(id); } } };\n          const arrow = document.createElement('span');\n          arrow.style.cssText = 'color: var(--text-soft); margin: 0 8px;';\n          arrow.textContent = '↔';\n          const remoteSpan = document.createElement('span');\n          remoteSpan.style.cssText = 'color: var(--text-main); cursor: pointer;';\n          remoteSpan.textContent = remoteName;\n          remoteSpan.title = 'Click to view connection';\n          remoteSpan.onclick = () => { claimTheImmortal(remoteNodeId); focusOnSelected(); };\n          const remotePortSpan = document.createElement('span');\n          remotePortSpan.style.cssText = remotePort ? 'color: var(--accent); font-family: monospace; margin-left: 6px; cursor: pointer;' : 'color: var(--text-soft); margin-left: 6px; cursor: pointer;';\n          remotePortSpan.textContent = '(' + (remotePort || '-') + ')';\n          remotePortSpan.title = remotePort ? 'Click to view connection' : 'Click to set port';\n          remotePortSpan.onclick = () => { if (remotePort) { selectTheConnection(e.id); } else { const label = `Port on ${remoteName}:`; const newVal = prompt(label, ''); if (newVal !== null && newVal !== '') { const isDupe = (EDGE_DATA.list || []).some(x => x.id !== e.id && ((x.from === remoteNodeId && x.fromPort === newVal) || (x.to === remoteNodeId && x.toPort === newVal))); if (isDupe && !confirm(`Warning: Port \"${newVal}\" is already used on ${remoteName}. Use anyway?`)) return; if (isFrom) e.toPort = newVal; else e.fromPort = newVal; claimTheImmortal(id); } } };\n          div.appendChild(localSpan);\n          div.appendChild(arrow);\n          div.appendChild(remoteSpan);\n          div.appendChild(remotePortSpan);\n          connectionsList.appendChild(div);\n         });\n         connectionsSection.style.display = \"block\";\n        } else {\n         connectionsList.innerHTML = '<div style=\"color: var(--text-soft); font-style: italic;\">No connections</div>';\n         connectionsSection.style.display = \"block\";\n        }\n       }\n       document.getElementById(\"node-name\").onclick = () => editNodeName(id);\n        document.getElementById(\"node-ip\").onclick = () => editNodeIp(id);\n        document.getElementById(\"node-mac\").onclick = () => editNodeMac(id);\n        document.getElementById(\"node-rack\").onclick = () => editNodeRack(id);\n        document.getElementById(\"node-uheight\").onclick = () => editNodeUHeight(id);\n        document.getElementById(\"fov-enabled\").onchange = function() {\n  if (!currentNodeId) return;\n  pushUndo(\"toggle coverage zone\");\n  NODE_DATA[currentNodeId].fovEnabled = this.checked;\n  if (this.checked && !NODE_DATA[currentNodeId].fovColor) {\n    NODE_DATA[currentNodeId].fovColor = document.getElementById(\"fov-color\").value || \"#f59e0b\";\n  }\n  updateFovCone(currentNodeId);\n  updateZoneLegend();\n};\n        document.getElementById(\"fov-angle\").oninput = function() {\n          document.getElementById(\"fov-angle-value\").textContent = this.value + \"°\";\n          if (!currentNodeId) return;\n          NODE_DATA[currentNodeId].fovAngle = parseInt(this.value);\n          updateFovCone(currentNodeId);\n        };\n        document.getElementById(\"fov-distance\").oninput = function() {\n          document.getElementById(\"fov-distance-value\").textContent = this.value;\n          if (!currentNodeId) return;\n          NODE_DATA[currentNodeId].fovDistance = parseInt(this.value);\n          updateFovCone(currentNodeId);\n        };\n        document.getElementById(\"fov-rotation\").oninput = function() {\n          document.getElementById(\"fov-rotation-value\").textContent = this.value + \"°\";\n          if (!currentNodeId) return;\n          NODE_DATA[currentNodeId].fovRotation = parseInt(this.value);\n          updateFovCone(currentNodeId);\n        };\n        document.getElementById(\"fov-color\").oninput = function() {\n          if (!currentNodeId) return;\n          NODE_DATA[currentNodeId].fovColor = this.value;\n          updateFovCone(currentNodeId);\n          updateZoneLegend();\n        };\n        document.getElementById(\"fov-animate\").onchange = function() {\n          if (!currentNodeId) return;\n          pushUndo(\"toggle fov animation\");\n          NODE_DATA[currentNodeId].fovAnimate = this.checked;\n          updateFovCone(currentNodeId);\n        };\n        document.getElementById(\"fov-sweep\").oninput = function() {\n          document.getElementById(\"fov-sweep-value\").textContent = this.value + \"°\";\n          if (!currentNodeId) return;\n          NODE_DATA[currentNodeId].fovSweep = parseInt(this.value);\n          updateFovCone(currentNodeId);\n        };\n        document.getElementById(\"fov-speed\").oninput = function() {\n          document.getElementById(\"fov-speed-value\").textContent = this.value + \"s\";\n          if (!currentNodeId) return;\n          NODE_DATA[currentNodeId].fovSpeed = parseInt(this.value);\n          updateFovCone(currentNodeId);\n        };\n        document.getElementById(\"fov-inner-radius\").oninput = function() {\n          document.getElementById(\"fov-inner-radius-value\").textContent = this.value;\n          if (!currentNodeId) return;\n          NODE_DATA[currentNodeId].fovInnerRadius = parseInt(this.value);\n          updateFovCone(currentNodeId);\n        };\n        document.getElementById(\"fov-opacity\").oninput = function() {\n          document.getElementById(\"fov-opacity-value\").textContent = this.value + \"%\";\n          if (!currentNodeId) return;\n          NODE_DATA[currentNodeId].fovOpacity = parseInt(this.value);\n          updateFovCone(currentNodeId);\n        };\n        document.getElementById(\"fov-gradient\").onchange = function() {\n          if (!currentNodeId) return;\n          NODE_DATA[currentNodeId].fovGradient = this.checked;\n          updateFovCone(currentNodeId);\n        };\n        document.getElementById(\"fov-border-color\").oninput = function() {\n          if (!currentNodeId) return;\n          NODE_DATA[currentNodeId].fovBorderColor = this.value;\n          updateFovCone(currentNodeId);\n        };\n        document.getElementById(\"fov-border-width\").oninput = function() {\n          document.getElementById(\"fov-border-width-value\").textContent = this.value;\n          if (!currentNodeId) return;\n          NODE_DATA[currentNodeId].fovBorderWidth = parseInt(this.value);\n          updateFovCone(currentNodeId);\n        };\n        document.getElementById(\"fov-border-style\").onchange = function() {\n          if (!currentNodeId) return;\n          NODE_DATA[currentNodeId].fovBorderStyle = this.value;\n          updateFovCone(currentNodeId);\n        };\n        document.getElementById(\"fov-border-opacity\").oninput = function() {\n          document.getElementById(\"fov-border-opacity-value\").textContent = this.value + \"%\";\n          if (!currentNodeId) return;\n          NODE_DATA[currentNodeId].fovBorderOpacity = parseInt(this.value);\n          updateFovCone(currentNodeId);\n        };\n        document.getElementById(\"fov-label\").oninput = function() {\n          if (!currentNodeId) return;\n          NODE_DATA[currentNodeId].fovLabel = this.value;\n          updateFovCone(currentNodeId);\n        };\n        document.getElementById(\"fov-label-position\").onchange = function() {\n          if (!currentNodeId) return;\n          NODE_DATA[currentNodeId].fovLabelPosition = this.value;\n          updateFovCone(currentNodeId);\n        };\n        document.getElementById(\"fov-label-size\").oninput = function() {\n          document.getElementById(\"fov-label-size-value\").textContent = this.value + \"px\";\n          if (!currentNodeId) return;\n          NODE_DATA[currentNodeId].fovLabelSize = parseInt(this.value);\n          updateFovCone(currentNodeId);\n        };\n        document.getElementById(\"fov-label-color\").oninput = function() {\n          if (!currentNodeId) return;\n          NODE_DATA[currentNodeId].fovLabelColor = this.value;\n          updateFovCone(currentNodeId);\n        };\n        document.getElementById(\"fov-label-bold\").onchange = function() {\n          if (!currentNodeId) return;\n          NODE_DATA[currentNodeId].fovLabelBold = this.checked;\n          updateFovCone(currentNodeId);\n        };\n        document.getElementById(\"fov-label-bg\").onchange = function() {\n          if (!currentNodeId) return;\n          NODE_DATA[currentNodeId].fovLabelBg = this.checked;\n          updateFovCone(currentNodeId);\n        };\n        document.getElementById(\"fov-label-bg-color\").oninput = function() {\n          if (!currentNodeId) return;\n          NODE_DATA[currentNodeId].fovLabelBgColor = this.value;\n          updateFovCone(currentNodeId);\n        };\n        document.getElementById(\"fov-label-offset-x\").oninput = function() {\n          document.getElementById(\"fov-label-offset-x-value\").textContent = this.value;\n          if (!currentNodeId) return;\n          NODE_DATA[currentNodeId].fovLabelOffsetX = parseInt(this.value);\n          updateFovCone(currentNodeId);\n        };\n        document.getElementById(\"fov-label-offset-y\").oninput = function() {\n          document.getElementById(\"fov-label-offset-y-value\").textContent = this.value;\n          if (!currentNodeId) return;\n          NODE_DATA[currentNodeId].fovLabelOffsetY = parseInt(this.value);\n          updateFovCone(currentNodeId);\n        };\n        document.getElementById(\"fov-animation-type\").onchange = function() {\n          if (!currentNodeId) return;\n          NODE_DATA[currentNodeId].fovAnimationType = this.value;\n          updateFovCone(currentNodeId);\n        };\n        document.getElementById(\"fov-preset\").addEventListener(\"change\", function() {\n          if (this.value) {\n            applyZonePreset(this.value);\n            this.value = \"\";\n          }\n        });\n        document.getElementById(\"fov-save-preset\").addEventListener(\"click\", saveCustomZonePreset);\n        document.getElementById(\"fov-copy-style\").addEventListener(\"click\", function() {\n          if (currentNodeId && copyZoneStyle(currentNodeId)) {\n            alert(\"Zone style copied!\");\n          }\n        });\n        document.getElementById(\"fov-paste-style\").addEventListener(\"click\", function() {\n          if (currentNodeId && pasteZoneStyle(currentNodeId)) {\n            claimTheImmortal(currentNodeId);\n          }\n        });\n        document.getElementById(\"bulk-zone-copy\").addEventListener(\"click\", bulkCopyZoneStyle);\n        document.getElementById(\"bulk-zone-paste\").addEventListener(\"click\", bulkPasteZoneStyle);\n        document.getElementById(\"bulk-zone-toggle\").addEventListener(\"click\", bulkToggleZones);\n        document.getElementById(\"bulk-zone-copy-mobile\").addEventListener(\"click\", bulkCopyZoneStyle);\n        document.getElementById(\"bulk-zone-paste-mobile\").addEventListener(\"click\", bulkPasteZoneStyle);\n        document.getElementById(\"bulk-zone-toggle-mobile\").addEventListener(\"click\", bulkToggleZones);\n        const currentSize = savedSizes[id] || getDefaultSize();\n        document.getElementById(\"size-slider\").value = currentSize;\n        const currentRotation = NODE_DATA[id].rotation || 0;\n        document.getElementById(\"rotation-slider\").value = Math.max(-360, Math.min(360, currentRotation));\n        document.getElementById(\"rotation-value\").value = currentRotation;\n        const styleEntry = savedStyles[id] || {};\n        const resolvedStyles = resolveStylesEntry(styleEntry);\n        const scopeKey = currentStyleScope || \"all\";\n        const flatProps = [\"circleColor\", \"circleBorder\", \"titleColor\", \"titleFont\", \"titleSize\", \"subColor\", \"subFont\", \"subSize\", ];\n        const isFlat = flatProps.some((p) => Object.prototype.hasOwnProperty.call(styleEntry, p));\n        const scopedStyles = isFlat ? styleEntry : styleEntry[scopeKey] || {};\n        const circleColorInput = document.getElementById(\"circle-color\");\n        const titleColorInput = document.getElementById(\"title-color\");\n        const titleFontSelect = document.getElementById(\"title-font\");\n        const titleSizeInput = document.getElementById(\"title-size\");\n        const subColorInput = document.getElementById(\"sub-color\");\n        const subFontSelect = document.getElementById(\"sub-font\");\n        const subSizeInput = document.getElementById(\"sub-size\");\n        const shapeSelect = document.getElementById(\"shape-select\");\n        const scopeSelect = document.getElementById(\"style-scope\");\n        circleColorInput.value = scopedStyles.circleColor || resolvedStyles.circleColor || PAGE_STATE.nodeFill || \"#1e293b\";\n       const circleBorderInput = document.getElementById(\"circle-border\");\n       circleBorderInput.value = scopedStyles.circleBorder || resolvedStyles.circleBorder || PAGE_STATE.nodeStroke || \"#475569\";\n        titleColorInput.value = scopedStyles.titleColor || resolvedStyles.titleColor || PAGE_STATE.textMain || \"#e2e8f0\";\n        titleFontSelect.value = scopedStyles.titleFont || resolvedStyles.titleFont || \"system-ui, sans-serif\";\n        titleSizeInput.value = scopedStyles.titleSize || resolvedStyles.titleSize || 18;\n        subColorInput.value = scopedStyles.subColor || resolvedStyles.subColor || PAGE_STATE.textSoft || \"#94a3b8\";\n        subFontSelect.value = scopedStyles.subFont || resolvedStyles.subFont || \"system-ui, sans-serif\";\n        subSizeInput.value = scopedStyles.subSize || resolvedStyles.subSize || 13;\n        const hasWebIcon = resolvedStyles.icon && resolvedStyles.icon.library;\n        const customOpt = shapeSelect.querySelector('option[value=\"custom-icon\"]');\n        if (hasWebIcon) {\n         if (!customOpt) {\n          const opt = document.createElement('option');\n          opt.value = 'custom-icon';\n          opt.textContent = 'Custom Icon';\n          shapeSelect.insertBefore(opt, shapeSelect.firstChild);\n         }\n         shapeSelect.value = 'custom-icon';\n        } else {\n         if (customOpt) customOpt.remove();\n         shapeSelect.value = data.shape || \"circle\";\n        }\n        const layerSelect = document.getElementById(\"node-layer\");\n        if (layerSelect) {\n         layerSelect.value = data.layer || \"physical\";\n        }\n        scopeSelect.value = currentStyleScope || \"all\";\n        document.getElementById(\"title-offset-y\").value = scopedStyles.titleOffsetY || resolvedStyles.titleOffsetY || 0;\n        document.getElementById(\"title-offset-x\").value = scopedStyles.titleOffsetX || resolvedStyles.titleOffsetX || 0;\n        document.getElementById(\"sub-offset-y\").value = scopedStyles.subOffsetY || resolvedStyles.subOffsetY || 0;\n        document.getElementById(\"sub-offset-x\").value = scopedStyles.subOffsetX || resolvedStyles.subOffsetX || 0;\n      const pingOffsetXInput = document.getElementById(\"ping-offset-x\");\n      const pingOffsetYInput = document.getElementById(\"ping-offset-y\");\n      if (pingOffsetXInput && pingOffsetYInput) {\n      pingOffsetXInput.value =\n       (scopedStyles.pingOffsetX !== undefined\n         ? scopedStyles.pingOffsetX\n         : (resolvedStyles.pingOffsetX !== undefined\n             ? resolvedStyles.pingOffsetX\n             : 0));\n      pingOffsetYInput.value =\n       (scopedStyles.pingOffsetY !== undefined\n         ? scopedStyles.pingOffsetY\n         : (resolvedStyles.pingOffsetY !== undefined\n             ? resolvedStyles.pingOffsetY\n             : 0));\n      }\n        const tagEl = document.getElementById(\"node-tags\");\n        tagEl.innerHTML = \"\";\n        data.tags.forEach((tag, i) => {\n         const b = document.createElement(\"span\");\n         b.className = \"badge\";\n         const isIconTag = typeof tag === 'object' && tag.type === 'icon';\n         if (!isIconTag && typeof tag === 'string' && tag.toLowerCase().includes(\"wg\")) b.classList.add(\"wg\");\n         b.style.cursor = \"pointer\";\n         b.style.position = \"relative\";\n         const tagContent = document.createElement(\"span\");\n         if (isIconTag) {\n          b.classList.add(\"icon-badge\");\n          IconLibrary.getIcon(tag.library, tag.name).then(svgText => {\n           if (svgText) {\n            const parser = new DOMParser();\n            const doc = parser.parseFromString(svgText, 'image/svg+xml');\n            const svgEl = doc.querySelector('svg');\n            if (svgEl) {\n             svgEl.setAttribute('width', '16');\n             svgEl.setAttribute('height', '16');\n             tagContent.innerHTML = '';\n             tagContent.appendChild(svgEl);\n             const nameSpan = document.createElement('span');\n             nameSpan.textContent = tag.name;\n             nameSpan.style.marginLeft = '4px';\n             tagContent.appendChild(nameSpan);\n            }\n           }\n          });\n         } else {\n          tagContent.textContent = tag;\n          tagContent.addEventListener(\"click\", (e) => {\n           e.stopPropagation();\n           showEditModal(\"Edit Tag\", tag, (newTag) => {\n            if (newTag) {\n             pushUndo(\"edit tag\");\n             data.tags[i] = newTag;\n             claimTheImmortal(id);\n            }\n           });\n          });\n         }\n         const deleteTag = document.createElement(\"span\");\n         deleteTag.textContent = \" ✕\";\n         deleteTag.style.opacity = \"0.6\";\n         deleteTag.style.marginLeft = \"4px\";\n         deleteTag.style.fontSize = \"10px\";\n         deleteTag.addEventListener(\"click\", (e) => {\n          e.stopPropagation();\n          pushUndo(\"delete tag\");\n          data.tags.splice(i, 1);\n          claimTheImmortal(id);\n         });\n         b.append(tagContent, deleteTag);\n         tagEl.append(b);\n        });\n        const addTagBtn = document.createElement(\"span\");\n        addTagBtn.className = \"badge\";\n        addTagBtn.style.cursor = \"pointer\";\n        addTagBtn.style.opacity = \"0.6\";\n        addTagBtn.style.borderStyle = \"dashed\";\n        addTagBtn.textContent = \"+ Add Tag\";\n        addTagBtn.addEventListener(\"click\", () => {\n         showEditModal(\"Add Tag(s) : comma separated\", \"\",\n          (newTagStr) => {\n           if (newTagStr) {\n            pushUndo(\"add tags\");\n            const newTags = newTagStr.split(\",\").map((t) => t.trim()).filter((t) => t);\n            newTags.forEach((t) => data.tags.push(t));\n            claimTheImmortal(id);\n           }\n          });\n        });\n        tagEl.append(addTagBtn);\n        const notesEl = document.getElementById(\"node-notes\");\n        notesEl.innerHTML = \"\";\n        data.notes.forEach((note, i) => {\n         const li = document.createElement(\"li\");\n         const noteText = document.createElement(\"span\");\n         noteText.textContent = note;\n         noteText.style.flex = \"1\";\n         const deleteBtn = document.createElement(\"span\");\n         deleteBtn.className = \"delete-note\";\n         deleteBtn.textContent = \"✕\";\n         deleteBtn.addEventListener(\"click\", (e) => {\n          e.stopPropagation();\n          challengeTheImmortal(\"Are you sure you want to delete this note?\",\n           () => {\n            pushUndo(\"delete note\");\n            data.notes.splice(i, 1);\n            claimTheImmortal(id);\n           });\n         });\n         li.append(noteText, deleteBtn);\n         noteText.addEventListener(\"dblclick\", () => {\n          noteText.classList.add(\"editing\");\n          noteText.contentEditable = true;\n          noteText.focus();\n         });\n         noteText.addEventListener(\"blur\", () => {\n          noteText.classList.remove(\"editing\");\n          noteText.contentEditable = false;\n          pushUndo(\"edit note\");\n          data.notes[i] = noteText.textContent;\n         });\n         notesEl.append(li);\n        });\n        const addLineSelect = document.getElementById(\"add-line-select\");\n      addLineSelect.innerHTML = \"\";\n      const currentRack = data.assignedRack || \"\";\n      Object.entries(NODE_DATA).forEach(([nodeId, node]) => {\n      if (nodeId !== id) {\n      const nodeRack = node.assignedRack || \"\";\n      if (currentRack === nodeRack) {\n      const opt = document.createElement(\"option\");\n      opt.value = nodeId;\n      opt.textContent = node.name;\n      addLineSelect.appendChild(opt);\n      }\n      }\n      });\n        const pingEnabled = data.ping && data.ping.enabled;\n        document.getElementById('node-pingable').checked = pingEnabled;\n        document.getElementById('node-ping-options').style.display = pingEnabled ? 'block' : 'none';\n        if (data.ping) {\n         document.getElementById('node-ping-protocol').value = data.ping.protocol || 'http';\n         document.getElementById('node-custom-url').value = data.ping.customUrl || '';\n         document.getElementById('node-ping-timeout').value = data.ping.timeout || 3000;\n         document.getElementById('node-custom-url-container').style.display =\n          data.ping.protocol === 'custom' ? 'block' : 'none';\n         updatePingStatusDisplay(id);\n        }\n       }\n       function updatePingStatusDisplay(nodeId) {\n        const data = NODE_DATA[nodeId];\n        if (!data || !data.ping) return;\n        const statusEl = document.getElementById('node-ping-status');\n        const lastCheckEl = document.getElementById('node-ping-last-check');\n        const statusColors = {\n         online: 'var(--accent)',\n         offline: 'var(--danger)',\n         checking: '#f59e0b',\n         unknown: 'var(--text-soft)'\n        };\n        const statusTexts = {\n         online: '● Online',\n         offline: '● Offline',\n         checking: '● Checking...',\n         unknown: '● Unknown'\n        };\n        const statusText = statusTexts[data.ping.status] || statusTexts.unknown;\n\t\tstatusEl.textContent = data.ping.responseTime ? `${statusText} (${data.ping.responseTime}ms)` : statusText;\n        statusEl.style.color = statusColors[data.ping.status] || statusColors.unknown;\n        if (data.ping.lastCheck) {\n         const checkTime = new Date(data.ping.lastCheck);\n         lastCheckEl.textContent = `Last checked: ${checkTime.toLocaleTimeString()}`;\n        } else {\n         lastCheckEl.textContent = 'Never checked';\n        }\n       }\n       function selectTheConnection(id) {\n\t   if (isViewOnly()) return;\n        currentEdgeId = id;\n        currentNodeId = null;\n        currentRectId = null;\n        currentTextId = null;\n\t\tif (isMobileDevice()) {\n        const currentHeight = PAGE_STATE.mobileFooterHeight || 40;\n        if (currentHeight <= 20) {\n         PAGE_STATE.mobileFooterHeight = 80;\n         const root = document.documentElement;\n         root.style.setProperty(\"--mobile-footer-height\", \"80vh\");\n         const mobileFooterHeightInput = document.getElementById(\"mobile-footer-height\");\n         const mobileFooterHeightVal = document.getElementById(\"mobile-footer-height-val\");\n         if (mobileFooterHeightInput) mobileFooterHeightInput.value = 80;\n         if (mobileFooterHeightVal) mobileFooterHeightVal.textContent = \"80%\";\n        }\n       }\n        document.getElementById(\"node-panel\").style.display = \"none\";\n        document.getElementById(\"edge-panel\").style.display = \"block\";\n        document.getElementById(\"text-panel\").style.display = \"none\";\n       document.getElementById(\"rect-panel\").style.display = \"none\";\n       document.getElementById(\"topology-toolbar\").style.display = \"none\";\n       document.querySelectorAll(\".node-group\").forEach((n) => n.classList.remove(\"active\"));\n       document.querySelectorAll(\".rect-group\").forEach((r) => r.classList.remove(\"active\"));\n       document.querySelectorAll(\".text-element\").forEach((t) => t.classList.remove(\"active\"));\n       document.querySelectorAll(\".edge\").forEach((e) => {\n        e.classList.toggle(\"active\", e.dataset.edgeId === id);\n       });\n        const edge = EDGE_DATA.list.find((e) => e.id === id);\n        if (!edge) return;\n        const directionSymbols = {\n         none: \"⇄\",\n         forward: \"→\",\n         backward: \"←\",\n         both: \"↔\",\n        };\n        const dirSymbol = directionSymbols[edge.direction] || \"⇄\";\n        let titleText = \"Custom line\";\n        if (edge.from || edge.to) {\n         const fromName = edge.from ? NODE_DATA[edge.from]?.name || edge.from : \"\";\n         const toName = edge.to ? NODE_DATA[edge.to]?.name || edge.to : \"\";\n         titleText = `${fromName || \"?\"} ${dirSymbol} ${toName || \"?\"}`;\n        }\n        document.getElementById(\"edge-title\").textContent = titleText;\n        const widthInput = document.getElementById(\"edge-width\");\n        const colorInput = document.getElementById(\"edge-color\");\n        const directionSelect = document.getElementById(\"edge-direction\");\n        const lineStyleSelect = document.getElementById(\"edge-line-style\");\n        const routingSelect = document.getElementById(\"edge-routing\");\n        widthInput.value = edge.width;\n        colorInput.value = edge.color;\n        directionSelect.value = edge.direction || \"none\";\n        lineStyleSelect.value = edge.lineStyle || \"solid\";\n        routingSelect.value = edge.routing || \"curved\";\n        document.getElementById(\"edge-animate\").checked = edge.animate === true;\n        document.getElementById(\"edge-animation-style\").value = edge.animationStyle || \"\";\n        document.getElementById(\"edge-animation-speed\").value = edge.animationSpeed || \"\";\n        const fromPortInput = document.getElementById(\"edge-from-port\");\n        const toPortInput = document.getElementById(\"edge-to-port\");\n        const portFieldsFrom = document.getElementById(\"edge-port-fields\");\n        const portFieldsTo = document.getElementById(\"edge-port-fields-to\");\n        if (edge.type === \"custom\") {\n         if (portFieldsFrom) portFieldsFrom.style.display = \"none\";\n         if (portFieldsTo) portFieldsTo.style.display = \"none\";\n        } else {\n         if (portFieldsFrom) portFieldsFrom.style.display = \"flex\";\n         if (portFieldsTo) portFieldsTo.style.display = \"flex\";\n         if (fromPortInput) {\n          fromPortInput.value = edge.fromPort || \"\";\n          fromPortInput.onchange = () => updateEdgePortLabels(id);\n         }\n         if (toPortInput) {\n          toPortInput.value = edge.toPort || \"\";\n          toPortInput.onchange = () => updateEdgePortLabels(id);\n         }\n        }\n        const list = document.getElementById(\"edge-notes\");\n        list.innerHTML = \"\";\n        edge.notes.forEach((note, i) => {\n         const li = document.createElement(\"li\");\n         const txt = document.createElement(\"span\");\n         txt.textContent = note;\n         txt.style.flex = \"1\";\n         const del = document.createElement(\"span\");\n         del.className = \"delete-note\";\n         del.textContent = \"✕\";\n         del.addEventListener(\"click\", (e) => {\n          e.stopPropagation();\n         challengeTheImmortal(\"Delete this line note?\", () => {\n           pushUndo(\"delete edge note\");\n           edge.notes.splice(i, 1);\n           selectTheConnection(id);\n          });\n         });\n         txt.addEventListener(\"dblclick\", () => {\n          txt.classList.add(\"editing\");\n          txt.contentEditable = true;\n          txt.focus();\n         });\n        txt.addEventListener(\"blur\", () => {\n          txt.classList.remove(\"editing\");\n          txt.contentEditable = false;\n          pushUndo(\"edit edge note\");\n          edge.notes[i] = txt.textContent;\n         });\n         li.append(txt, del);\n         list.appendChild(li);\n       });\n       if (edge.type === \"custom\" && Array.isArray(edge.points)) {\n        forgeTheTopology();\n       }\n      }\n       window.addEventListener(\"resize\", () => {\n        forgeTheTopology();\n        if (currentEdgeId) {\n         selectTheConnection(currentEdgeId);\n        } else if (currentNodeId && NODE_DATA[currentNodeId]) {\n         claimTheImmortal(currentNodeId);\n        } else {\n         const availableNodes = Object.keys(NODE_DATA);\n         if (availableNodes.length > 0) {\n          claimTheImmortal(availableNodes[0]);\n         }\n        }\n       });\n       (function initZoomPan() {\n       const viewport = document.getElementById(\"canvas-viewport\");\n       const svg = document.getElementById(\"map\");\n       const hint = document.getElementById(\"canvas-hint\");\n       hint.style.cursor = \"pointer\";\n       let hintDismissed = false;\n       const dismissHint = () => { hintDismissed = true; hint.classList.remove(\"visible\"); };\n       hint.addEventListener(\"click\", dismissHint);\n       hint.addEventListener(\"touchend\", (e) => { e.preventDefault(); dismissHint(); });\n       setTimeout(() => {\n        if (hintDismissed) return;\n        hint.classList.add(\"visible\");\n        setTimeout(() => { if (!hintDismissed) hint.classList.remove(\"visible\"); }, 4000);\n       }, 1000);\n        viewport.addEventListener(\"wheel\",\n         (e) => {\n          e.preventDefault();\n          const rect = viewport.getBoundingClientRect();\n          const mouseX = (e.clientX - rect.left) / rect.width;\n          const mouseY = (e.clientY - rect.top) / rect.height;\n          const delta = e.deltaY > 0 ? 0.9 : 1.1;\n          zoomTo(canvasState.zoom * delta, mouseX, mouseY);\n         }, {\n          passive: false\n         });\n        let initialPinchDistance = 0;\n        let initialPinchZoom = 1;\n        let pinchCenter = {\n         x: 0.5,\n         y: 0.5\n        };\n        let threeFingerTapStart = 0;\n        viewport.addEventListener(\"touchstart\",\n         (e) => {\n          if (e.touches.length === 3) {\n           e.preventDefault();\n           threeFingerTapStart = Date.now();\n          }\n          if (e.touches.length === 2) {\n           e.preventDefault();\n           const touch1 = e.touches[0];\n           const touch2 = e.touches[1];\n           initialPinchDistance = Math.hypot(touch2.clientX - touch1.clientX, touch2.clientY - touch1.clientY);\n           initialPinchZoom = canvasState.zoom;\n           const rect = viewport.getBoundingClientRect();\n           const centerX = (touch1.clientX + touch2.clientX) / 2;\n           const centerY = (touch1.clientY + touch2.clientY) / 2;\n           pinchCenter.x = (centerX - rect.left) / rect.width;\n           pinchCenter.y = (centerY - rect.top) / rect.height;\n          }\n         }, {\n          passive: false\n         });\n        viewport.addEventListener(\"touchend\", (e) => {\n         if (e.touches.length === 0 && threeFingerTapStart > 0) {\n          const duration = Date.now() - threeFingerTapStart;\n          if (duration < 500) {\n           e.preventDefault();\n           undo();\n           if (navigator.vibrate) navigator.vibrate([50, 30, 50]);\n          }\n          threeFingerTapStart = 0;\n         }\n        }, { passive: false });\n        viewport.addEventListener(\"touchmove\",\n         (e) => {\n          if (e.touches.length === 2) {\n           e.preventDefault();\n           const touch1 = e.touches[0];\n           const touch2 = e.touches[1];\n           const currentDistance = Math.hypot(touch2.clientX - touch1.clientX, touch2.clientY - touch1.clientY);\n           if (initialPinchDistance > 0) {\n            const scale = currentDistance / initialPinchDistance;\n            const newZoom = initialPinchZoom * scale;\n            zoomTo(newZoom, pinchCenter.x, pinchCenter.y);\n           }\n          }\n         }, {\n          passive: false\n         });\n        let panStartViewX = 0;\n        let panStartViewY = 0;\n        let lastEmptyTapTime = 0;\n        let emptyTapTimeout = null;\n        let emptyTapMoved = false;\n        let emptyTapStartX = 0;\n        let emptyTapStartY = 0;\n        viewport.addEventListener(\"touchend\", (e) => {\n          if (currentView.mode !== \"rack\") return;\n          if (e.changedTouches.length !== 1) return;\n      const isNodeOrEdge = e.target.closest(\".node-group\") || e.target.closest(\".edge-group\");\n         if (isNodeOrEdge) return;\n          if (emptyTapMoved) {\n            emptyTapMoved = false;\n            return;\n          }\n          const currentTime = new Date().getTime();\n          const tapGap = currentTime - lastEmptyTapTime;\n          if (tapGap < 300 && tapGap > 0) {\n            e.preventDefault();\n            exitRack();\n            if (navigator.vibrate) {\n              navigator.vibrate(50);\n            }\n            lastEmptyTapTime = 0;\n            if (emptyTapTimeout) {\n              clearTimeout(emptyTapTimeout);\n              emptyTapTimeout = null;\n            }\n          } else {\n            lastEmptyTapTime = currentTime;\n            if (emptyTapTimeout) clearTimeout(emptyTapTimeout);\n            emptyTapTimeout = setTimeout(() => {\n              lastEmptyTapTime = 0;\n            }, 300);\n          }\n        }, { passive: false });\n        viewport.addEventListener(\"mousedown\", (e) => {\n         if (e.target.closest(\".draw-toolbar\") || e.target.closest(\".topology-toolbar\") || e.target.closest(\".legend-container\")) {\n          return;\n         }\n         if (freeDrawMode || rectDrawMode) {\n          return;\n         }\n      const isEmptySpace = e.target === svg || e.target === viewport || e.target.tagName === \"svg\";\n      const isEdgeElement = e.target.closest('.edge') || e.target.classList.contains('edge-edit-point') || e.target.dataset.edgeId;\n      const isTextElement = e.target.closest('.text-group') || e.target.closest('.text-element');\n      const isRectElement = e.target.closest('.rect-group');\n      if (!isEdgeElement && !isTextElement && !isRectElement && currentEdgeId) {\n      currentEdgeId = null;\n      forgeTheTopology();\n      }\n      if (!isRectElement && currentRectId) {\n      currentRectId = null;\n      document.getElementById(\"rect-panel\").style.display = \"none\";\n      forgeTheTopology();\n      }\n         if (isViewOnly()) {\n         document.body.classList.remove(\"view-only-inspect\");\n         document.getElementById(\"node-panel\").style.display = \"none\";\n         document.getElementById(\"edge-panel\").style.display = \"none\";\n        }\n         if (isEmptySpace && e.shiftKey && e.button === 0) {\n         if (isViewOnly()) return;\n         e.preventDefault();\n         startSelection(e);\n         return;\n        }\n        if (isEmptySpace || e.button === 2 || e.button === 1) {\n          e.preventDefault();\n          canvasState.isPanning = true;\n          canvasState.panStartX = e.clientX;\n          canvasState.panStartY = e.clientY;\n          panStartViewX = canvasState.panX;\n          panStartViewY = canvasState.panY;\n          viewport.classList.add(\"panning\");\n         }\n        });\n        viewport.addEventListener(\"touchstart\",\n         (e) => {\n          if (e.target.closest(\".draw-toolbar\") || e.target.closest(\".topology-toolbar\") || e.target.closest(\".legend-container\")) {\n           return;\n          }\n          if (freeDrawMode || rectDrawMode) {\n           return;\n          }\n      const isEmptySpace = e.target === svg || e.target === viewport || e.target.tagName === \"svg\";\n      const isEdgeElement = e.target.closest('.edge') || e.target.classList.contains('edge-edit-point') || e.target.dataset.edgeId;\n      const isTextElement = e.target.closest('.text-group') || e.target.closest('.text-element');\n      const isRectElement = e.target.closest('.rect-group');\n      if (!isEdgeElement && !isTextElement && !isRectElement && currentEdgeId) {\n      currentEdgeId = null;\n      forgeTheTopology();\n      }\n      if (!isRectElement && currentRectId) {\n      currentRectId = null;\n      document.getElementById(\"rect-panel\").style.display = \"none\";\n      forgeTheTopology();\n      }\n          if (isEmptySpace && e.touches.length === 1) {\n           e.preventDefault();\n           emptyTapMoved = false;\n           emptyTapStartX = e.touches[0].clientX;\n           emptyTapStartY = e.touches[0].clientY;\n           canvasState.isPanning = true;\n           canvasState.panStartX = e.touches[0].clientX;\n           canvasState.panStartY = e.touches[0].clientY;\n           panStartViewX = canvasState.panX;\n           panStartViewY = canvasState.panY;\n           viewport.classList.add(\"panning\");\n          }\n         }, {\n          passive: false\n         });\n        let panRAFPending = false;\n       let lastPanEvent = null;\n       document.addEventListener(\"mousemove\", (e) => {\n        if (isSelecting) {\n         updateSelection(e);\n         return;\n        }\n        if (!canvasState.isPanning) return;\n        lastPanEvent = e;\n        if (!panRAFPending) {\n         panRAFPending = true;\n         requestAnimationFrame(() => {\n          if (lastPanEvent && canvasState.isPanning) {\n           const dx = lastPanEvent.clientX - canvasState.panStartX;\n           const dy = lastPanEvent.clientY - canvasState.panStartY;\n           const rect = viewport.getBoundingClientRect();\n           const viewWidth = CANVAS_WIDTH / canvasState.zoom;\n           const viewHeight = CANVAS_HEIGHT / canvasState.zoom;\n           const canvasDx = (dx / rect.width) * viewWidth;\n           const canvasDy = (dy / rect.height) * viewHeight;\n           canvasState.panX = panStartViewX - canvasDx;\n           canvasState.panY = panStartViewY - canvasDy;\n           constrainPan();\n           updateViewBox();\n          }\n          panRAFPending = false;\n         });\n        }\n       });\n       document.addEventListener(\"touchmove\", (e) => {\n        if (!canvasState.isPanning || !e.touches[0]) return;\n        const touchX = e.touches[0].clientX;\n        const touchY = e.touches[0].clientY;\n        const moveDx = Math.abs(touchX - emptyTapStartX);\n        const moveDy = Math.abs(touchY - emptyTapStartY);\n        if (moveDx > 15 || moveDy > 15) {\n         emptyTapMoved = true;\n        }\n        if (!panRAFPending) {\n         panRAFPending = true;\n         requestAnimationFrame(() => {\n          if (canvasState.isPanning) {\n           const dx = touchX - canvasState.panStartX;\n           const dy = touchY - canvasState.panStartY;\n           const rect = viewport.getBoundingClientRect();\n           const viewWidth = CANVAS_WIDTH / canvasState.zoom;\n           const viewHeight = CANVAS_HEIGHT / canvasState.zoom;\n           const canvasDx = (dx / rect.width) * viewWidth;\n           const canvasDy = (dy / rect.height) * viewHeight;\n           canvasState.panX = panStartViewX - canvasDx;\n           canvasState.panY = panStartViewY - canvasDy;\n           constrainPan();\n           updateViewBox();\n          }\n          panRAFPending = false;\n         });\n        }\n       });\n        document.addEventListener(\"mouseup\", () => {\n         if (isSelecting) {\n          endSelection();\n         }\n         if (canvasState.isPanning) {\n          canvasState.isPanning = false;\n          viewport.classList.remove(\"panning\");\n         }\n        });\n        document.addEventListener(\"touchend\", () => {\n         if (canvasState.isPanning) {\n          canvasState.isPanning = false;\n          viewport.classList.remove(\"panning\");\n         }\n        });\n        document.addEventListener(\"keydown\", (e) => {\n         const isEditing = document.activeElement.tagName === \"INPUT\" || document.activeElement.tagName === \"TEXTAREA\" || document.activeElement.isContentEditable;\n         if (e.code === \"Space\" && !e.repeat && !isEditing) {\n          e.preventDefault();\n          canvasState.spacePressed = true;\n          viewport.style.cursor = \"grab\";\n         }\n        });\n        document.addEventListener(\"keyup\", (e) => {\n         if (e.code === \"Space\") {\n          canvasState.spacePressed = false;\n          viewport.style.cursor = \"\";\n         }\n        });\n        document.getElementById(\"zoom-in-btn\").addEventListener(\"click\", () => {\n         zoomTo(canvasState.zoom * 1.25, 0.5, 0.5);\n        });\n        document.getElementById(\"zoom-out-btn\").addEventListener(\"click\", () => {\n         zoomTo(canvasState.zoom * 0.8, 0.5, 0.5);\n        });\n        document.getElementById(\"zoom-fit-btn\").addEventListener(\"click\", fitToContent);\n        document.getElementById(\"zoom-reset-btn\").addEventListener(\"click\", resetView);\n        const minimapContainer = document.getElementById(\"minimap-container\");\n        const minimapSvg = document.getElementById(\"minimap\");\n        let minimapDragging = false;\n        minimapContainer.addEventListener(\"mousedown\", (e) => {\n         e.preventDefault();\n         minimapDragging = true;\n         updatePanFromMinimap(e);\n        });\n        minimapContainer.addEventListener(\"touchstart\",\n         (e) => {\n          e.preventDefault();\n          minimapDragging = true;\n          updatePanFromMinimapTouch(e);\n         }, {\n          passive: false\n         });\n        document.addEventListener(\"mousemove\", (e) => {\n         if (minimapDragging) {\n          updatePanFromMinimap(e);\n         }\n        });\n        document.addEventListener(\"touchmove\", (e) => {\n         if (minimapDragging && e.touches[0]) {\n          updatePanFromMinimapTouch(e);\n         }\n        });\n        document.addEventListener(\"mouseup\", () => {\n         minimapDragging = false;\n        });\n        document.addEventListener(\"touchend\", () => {\n         minimapDragging = false;\n        });\n        function updatePanFromMinimap(e) {\n         const rect = minimapContainer.getBoundingClientRect();\n         const x = (e.clientX - rect.left) / rect.width;\n         const y = (e.clientY - rect.top) / rect.height;\n         const viewWidth = CANVAS_WIDTH / canvasState.zoom;\n         const viewHeight = CANVAS_HEIGHT / canvasState.zoom;\n         canvasState.panX = x * CANVAS_WIDTH - viewWidth / 2;\n         canvasState.panY = y * CANVAS_HEIGHT - viewHeight / 2;\n         constrainPan();\n         updateViewBox();\n        }\n        function updatePanFromMinimapTouch(e) {\n         const rect = minimapContainer.getBoundingClientRect();\n         const touch = e.touches[0];\n         const x = (touch.clientX - rect.left) / rect.width;\n         const y = (touch.clientY - rect.top) / rect.height;\n         const viewWidth = CANVAS_WIDTH / canvasState.zoom;\n         const viewHeight = CANVAS_HEIGHT / canvasState.zoom;\n         canvasState.panX = x * CANVAS_WIDTH - viewWidth / 2;\n         canvasState.panY = y * CANVAS_HEIGHT - viewHeight / 2;\n         constrainPan();\n         updateViewBox();\n        }\n        document.addEventListener(\"keydown\", (e) => {\n         if (document.activeElement.tagName === \"INPUT\" || document.activeElement.tagName === \"TEXTAREA\") return;\n         if (\n          (e.key === \"+\" || e.key === \"=\") && (e.ctrlKey || e.metaKey)) {\n          e.preventDefault();\n          zoomTo(canvasState.zoom * 1.25, 0.5, 0.5);\n         } else if (e.key === \"-\" && (e.ctrlKey || e.metaKey)) {\n          e.preventDefault();\n          zoomTo(canvasState.zoom * 0.8, 0.5, 0.5);\n         } else if (e.key === \"0\" && (e.ctrlKey || e.metaKey)) {\n          e.preventDefault();\n          resetView();\n         }\n        });\n        setTimeout(() => {\n         fitToContent();\n        }, 100);\n       })();\n       const sizeSlider = document.getElementById(\"size-slider\");\n       const sizeValue = document.getElementById(\"size-value\");\n       const resetSizeBtn = document.getElementById(\"reset-size\");\n       sizeSlider.addEventListener(\"input\", () => {\n        const newSize = parseInt(sizeSlider.value, 10);\n        sizeValue.textContent = newSize;\n        pushUndo(\"resize node\");\n        savedSizes[currentNodeId] = newSize;\n        const nodeGroup = document.querySelector(`g[data-node-id=\"${currentNodeId}\"]`);\n        if (nodeGroup) {\n         const oldShape = nodeGroup.querySelector(\".node-circle\");\n         if (oldShape) oldShape.remove();\n         const newShape = createNodeShape(currentNodeId, newSize);\n         nodeGroup.insertBefore(newShape, nodeGroup.firstChild);\n         const styles = resolveStylesForNode(currentNodeId);\n         if (styles.circleColor) newShape.style.fill = styles.circleColor;\n      if (styles.circleBorder) newShape.style.stroke = styles.circleBorder;\n         const label = nodeGroup.querySelector(\".node-label\");\n         const sub = nodeGroup.querySelector(\".node-sub\");\n         if (label) {\n          label.setAttribute(\"y\", -newSize * 0.28);\n          const labelSize = styles.titleSize || newSize * 0.33;\n          label.style.fontSize = labelSize + \"px\";\n         }\n         if (sub) {\n          sub.setAttribute(\"y\", newSize * 0.4);\n          const subSize = styles.subSize || newSize * 0.24;\n          sub.style.fontSize = subSize + \"px\";\n         }\n       updatePingIndicator(currentNodeId);\n        }\n\t\tupdateMinimap();\n       });\n       resetSizeBtn.addEventListener(\"click\", () => {\n        pushUndo(\"reset size\");\n        delete savedSizes[currentNodeId];\n        const defaultSize = getDefaultSize();\n        sizeSlider.value = defaultSize;\n        sizeValue.textContent = defaultSize;\n        const nodeGroup = document.querySelector(`g[data-node-id=\"${currentNodeId}\"]`);\n        if (nodeGroup) {\n         const oldShape = nodeGroup.querySelector(\".node-circle\");\n         if (oldShape) oldShape.remove();\n         const shapeType = (NODE_DATA[currentNodeId] && NODE_DATA[currentNodeId].shape) || \"circle\";\n         const newShape = createNodeShape(currentNodeId, defaultSize);\n         nodeGroup.insertBefore(newShape, nodeGroup.firstChild);\n         const styles = resolveStylesForNode(currentNodeId);\n         if (styles.circleColor) newShape.style.fill = styles.circleColor;\n      if (styles.circleBorder) newShape.style.stroke = styles.circleBorder;\n         const label = nodeGroup.querySelector(\".node-label\");\n         const sub = nodeGroup.querySelector(\".node-sub\");\n         if (label) {\n          label.setAttribute(\"y\", -defaultSize * 0.28);\n          const labelSize = styles.titleSize || defaultSize * 0.33;\n          label.style.fontSize = labelSize + \"px\";\n         }\n         if (sub) {\n          sub.setAttribute(\"y\", defaultSize * 0.4);\n          const subSize = styles.subSize || defaultSize * 0.24;\n          sub.style.fontSize = subSize + \"px\";\n         }\n          updatePingIndicator(currentNodeId);\n        }\n\t\tupdateMinimap();\n       });\n       const rotationSlider = document.getElementById(\"rotation-slider\");\n       const rotationInput = document.getElementById(\"rotation-value\");\n       const resetRotationBtn = document.getElementById(\"reset-rotation\");\n       rotationSlider.addEventListener(\"input\", () => {\n         const newRotation = parseInt(rotationSlider.value, 10);\n         rotationInput.value = newRotation;\n         pushUndo(\"rotate node\");\n         NODE_DATA[currentNodeId].rotation = newRotation;\n         const nodeGroup = document.querySelector(`g[data-node-id=\"${currentNodeId}\"]`);\n         if (nodeGroup) {\n           const pos = savedPositions[currentNodeId] || { x: 0, y: 0 };\n           nodeGroup.setAttribute(\"transform\", `translate(${pos.x},${pos.y}) rotate(${newRotation})`);\n         }\n       });\n       rotationInput.addEventListener(\"input\", () => {\n         const newRotation = parseInt(rotationInput.value, 10) || 0;\n         rotationSlider.value = Math.max(-360, Math.min(360, newRotation));\n         pushUndo(\"rotate node\");\n         NODE_DATA[currentNodeId].rotation = newRotation;\n         const nodeGroup = document.querySelector(`g[data-node-id=\"${currentNodeId}\"]`);\n         if (nodeGroup) {\n           const pos = savedPositions[currentNodeId] || { x: 0, y: 0 };\n           nodeGroup.setAttribute(\"transform\", `translate(${pos.x},${pos.y}) rotate(${newRotation})`);\n         }\n       });\n       resetRotationBtn.addEventListener(\"click\", () => {\n         pushUndo(\"reset rotation\");\n         NODE_DATA[currentNodeId].rotation = 0;\n         rotationSlider.value = 0;\n         rotationInput.value = 0;\n         const nodeGroup = document.querySelector(`g[data-node-id=\"${currentNodeId}\"]`);\n         if (nodeGroup) {\n           const pos = savedPositions[currentNodeId] || { x: 0, y: 0 };\n           nodeGroup.setAttribute(\"transform\", `translate(${pos.x},${pos.y}) rotate(0)`);\n         }\n       });\n       const applyStyle = (property, value) => {\n        pushUndo(\"style change\");\n        const styleEntry = ensureStyleEntry(currentNodeId);\n        const scopeKey = currentStyleScope || \"all\";\n        if (!styleEntry[scopeKey]) styleEntry[scopeKey] = {};\n        styleEntry[scopeKey][property] = value;\n        const nodeGroup = document.querySelector(`g[data-node-id=\"${currentNodeId}\"]`);\n        if (!nodeGroup) return;\n        const shapeEl = nodeGroup.querySelector(\".node-circle\");\n        const label = nodeGroup.querySelector(\".node-label\");\n        const sub = nodeGroup.querySelector(\".node-sub\");\n        const isWebIcon = shapeEl && shapeEl.querySelector('svg');\n        if (property === \"circleColor\" && shapeEl) {\n         if (isWebIcon) {\n          shapeEl.querySelectorAll('svg, path, circle, rect, polygon, ellipse').forEach(el => el.style.fill = value);\n         } else {\n          shapeEl.style.fill = value;\n         }\n        } else if (property === \"circleBorder\" && shapeEl) {\n         if (isWebIcon) {\n          shapeEl.querySelectorAll('svg, path, circle, rect, polygon, ellipse').forEach(el => el.style.stroke = value);\n         } else {\n          shapeEl.style.stroke = value;\n         }\n        } else if (property === \"titleColor\" && label) label.style.fill = value;\n        else if (property === \"titleFont\" && label) label.style.fontFamily = value;\n        else if (property === \"titleSize\" && label) label.style.fontSize = value + \"px\";\n        else if (property === \"subColor\" && sub) sub.style.fill = value;\n        else if (property === \"subFont\" && sub) sub.style.fontFamily = value;\n        else if (property === \"subSize\" && sub) sub.style.fontSize = value + \"px\";\n       };\n\n       document.getElementById(\"circle-color\").addEventListener(\"input\", (e) => applyStyle(\"circleColor\", e.target.value));\n      document.getElementById(\"circle-border\").addEventListener(\"input\", (e) => applyStyle(\"circleBorder\", e.target.value));\n       document.getElementById(\"title-color\").addEventListener(\"input\", (e) => applyStyle(\"titleColor\", e.target.value));\n       document.getElementById(\"title-font\").addEventListener(\"change\", (e) => applyStyle(\"titleFont\", e.target.value));\n       document.getElementById(\"title-size\").addEventListener(\"input\", (e) => applyStyle(\"titleSize\", parseInt(e.target.value, 10)));\n       document.getElementById(\"sub-color\").addEventListener(\"input\", (e) => applyStyle(\"subColor\", e.target.value));\n       document.getElementById(\"sub-font\").addEventListener(\"change\", (e) => applyStyle(\"subFont\", e.target.value));\n       document.getElementById(\"sub-size\").addEventListener(\"input\", (e) => applyStyle(\"subSize\", parseInt(e.target.value, 10)));\n       document.getElementById(\"title-offset-y\").addEventListener(\"input\", (e) => {\n        applyStyle(\"titleOffsetY\", parseInt(e.target.value, 10) || 0);\n        forgeTheTopology();\n        if (currentNodeId) claimTheImmortal(currentNodeId);\n       });\n       document.getElementById(\"title-offset-x\").addEventListener(\"input\", (e) => {\n        applyStyle(\"titleOffsetX\", parseInt(e.target.value, 10) || 0);\n        forgeTheTopology();\n        if (currentNodeId) claimTheImmortal(currentNodeId);\n       });\n       document.getElementById(\"sub-offset-y\").addEventListener(\"input\", (e) => {\n        applyStyle(\"subOffsetY\", parseInt(e.target.value, 10) || 0);\n        forgeTheTopology();\n        if (currentNodeId) claimTheImmortal(currentNodeId);\n       });\n       document.getElementById(\"sub-offset-x\").addEventListener(\"input\", (e) => {\n        applyStyle(\"subOffsetX\", parseInt(e.target.value, 10) || 0);\n        forgeTheTopology();\n        if (currentNodeId) claimTheImmortal(currentNodeId);\n       });\n      document.getElementById(\"ping-offset-x\").addEventListener(\"input\", (e) => {\n      applyStyle(\"pingOffsetX\", parseInt(e.target.value, 10) || 0);\n      updatePingIndicator(currentNodeId);\n      if (currentNodeId) claimTheImmortal(currentNodeId);\n      });\n      document.getElementById(\"ping-offset-y\").addEventListener(\"input\", (e) => {\n      applyStyle(\"pingOffsetY\", parseInt(e.target.value, 10) || 0);\n      updatePingIndicator(currentNodeId);\n      if (currentNodeId) claimTheImmortal(currentNodeId);\n      });\n       document.getElementById(\"reset-styles\").addEventListener(\"click\", () => {\n        delete savedStyles[currentNodeId];\n        forgeTheTopology();\n        claimTheImmortal(currentNodeId);\n       });\n       document.getElementById(\"style-scope\").addEventListener(\"change\", (e) => {\n        currentStyleScope = e.target.value || \"all\";\n        claimTheImmortal(currentNodeId);\n       });\n       document.getElementById(\"shape-select\").addEventListener(\"change\", (e) => {\n        if (!currentNodeId || !NODE_DATA[currentNodeId]) return;\n        const shape = e.target.value || \"circle\";\n        if (shape === 'custom-icon') return;\n        pushUndo(\"change shape\");\n        NODE_DATA[currentNodeId].shape = shape;\n        const fovSection = document.getElementById(\"fov-section\");\n        if (fovSection) {\n          const oldShape = NODE_DATA[currentNodeId].shape;\n          fovSection.style.display = hasCoverageZone(shape) ? \"block\" : \"none\";\n          if (hasCoverageZone(shape) && !hasCoverageZone(oldShape)) {\n            const defaults = getCoverageDefaults(shape);\n            document.getElementById(\"fov-angle\").value = defaults.angle;\n            document.getElementById(\"fov-angle-value\").textContent = defaults.angle + \"°\";\n            document.getElementById(\"fov-distance\").value = defaults.distance;\n            document.getElementById(\"fov-distance-value\").textContent = defaults.distance;\n            document.getElementById(\"fov-animation-type\").value = defaults.animationType;\n            NODE_DATA[currentNodeId].fovAngle = defaults.angle;\n            NODE_DATA[currentNodeId].fovDistance = defaults.distance;\n            NODE_DATA[currentNodeId].fovAnimationType = defaults.animationType;\n          }\n        }\n        if (savedStyles[currentNodeId]) {\n         Object.keys(savedStyles[currentNodeId]).forEach(scope => {\n          if (savedStyles[currentNodeId][scope] && savedStyles[currentNodeId][scope].icon) {\n           delete savedStyles[currentNodeId][scope].icon;\n          }\n         });\n        }\n        const customOpt = e.target.querySelector('option[value=\"custom-icon\"]');\n        if (customOpt) customOpt.remove();\n        forgeTheTopology();\n       });\n       const addNoteBtn = document.getElementById(\"add-note-btn\");\n       const noteInput = document.getElementById(\"new-note-input\");\n       addNoteBtn.addEventListener(\"click\", () => {\n        const newNote = noteInput.value.trim();\n        if (newNote && currentNodeId && NODE_DATA[currentNodeId]) {\n         pushUndo(\"add note\");\n         NODE_DATA[currentNodeId].notes.push(newNote);\n         claimTheImmortal(currentNodeId);\n         noteInput.value = \"\";\n        }\n       });\n       noteInput.addEventListener(\"keypress\", (e) => {\n        if (e.key === \"Enter\") {\n         addNoteBtn.click();\n        }\n       });\n       document.getElementById('node-pingable').addEventListener('change', (e) => {\n        if (!currentNodeId || !NODE_DATA[currentNodeId]) return;\n        if (!NODE_DATA[currentNodeId].ping) {\n         NODE_DATA[currentNodeId].ping = {\n          enabled: false,\n          protocol: 'http',\n          customUrl: '',\n          timeout: 3000,\n          status: 'unknown',\n          lastCheck: null\n         };\n        }\n        NODE_DATA[currentNodeId].ping.enabled = e.target.checked;\n        document.getElementById('node-ping-options').style.display = e.target.checked ? 'block' : 'none';\n        forgeTheTopology();\n       });\n       document.getElementById('node-ping-protocol').addEventListener('change', (e) => {\n        if (!currentNodeId || !NODE_DATA[currentNodeId].ping) return;\n        NODE_DATA[currentNodeId].ping.protocol = e.target.value;\n        document.getElementById('node-custom-url-container').style.display =\n         e.target.value === 'custom' ? 'block' : 'none';\n       });\n       document.getElementById('node-custom-url').addEventListener('change', (e) => {\n        if (!currentNodeId || !NODE_DATA[currentNodeId].ping) return;\n        NODE_DATA[currentNodeId].ping.customUrl = e.target.value.trim();\n       });\n       document.getElementById('node-ping-timeout').addEventListener('change', (e) => {\n        if (!currentNodeId || !NODE_DATA[currentNodeId].ping) return;\n        NODE_DATA[currentNodeId].ping.timeout = parseInt(e.target.value) || 3000;\n       });\n       document.getElementById('check-ping-now').addEventListener('click', () => {\n        if (!currentNodeId) return;\n        checkNodeStatus(currentNodeId);\n       });\n       document.getElementById(\"edge-width\").addEventListener(\"input\", () => {\n        if (!currentEdgeId) return;\n        const edge = EDGE_DATA.list.find((e) => e.id === currentEdgeId);\n        if (!edge) return;\n        const v = parseInt(document.getElementById(\"edge-width\").value, 10);\n        if (Number.isNaN(v) || v <= 0) return;\n        pushUndo(\"edit edge\");\n        edge.width = v;\n        const el = document.querySelector(`.edge[data-edge-id=\"${currentEdgeId}\"]`);\n        if (el) el.style.strokeWidth = v;\n       });\n       document.getElementById(\"edge-color\").addEventListener(\"input\", () => {\n        if (!currentEdgeId) return;\n        const edge = EDGE_DATA.list.find((e) => e.id === currentEdgeId);\n        if (!edge) return;\n        const color = document.getElementById(\"edge-color\").value;\n        pushUndo(\"edit edge\");\n        edge.color = color;\n        const el = document.querySelector(`.edge[data-edge-id=\"${currentEdgeId}\"]`);\n        if (el) el.style.stroke = color;\n        forgeTheLegend();\n\t\tupdateZoneLegend();\n       });\n       document.getElementById(\"edge-direction\").addEventListener(\"change\", () => {\n        if (!currentEdgeId) return;\n        const edge = EDGE_DATA.list.find((e) => e.id === currentEdgeId);\n        if (!edge) return;\n        pushUndo(\"edit edge\");\n        edge.direction = document.getElementById(\"edge-direction\").value;\n        forgeTheTopology();\n        selectTheConnection(currentEdgeId);\n       });\n       document.getElementById(\"edge-line-style\").addEventListener(\"change\", () => {\n        if (!currentEdgeId) return;\n        const edge = EDGE_DATA.list.find((e) => e.id === currentEdgeId);\n        if (!edge) return;\n        pushUndo(\"edit edge\");\n        edge.lineStyle = document.getElementById(\"edge-line-style\").value;\n        forgeTheTopology();\n        selectTheConnection(currentEdgeId);\n       });\n       document.getElementById(\"edge-routing\").addEventListener(\"change\", () => {\n        if (!currentEdgeId) return;\n        const edge = EDGE_DATA.list.find((e) => e.id === currentEdgeId);\n        if (!edge) return;\n        pushUndo(\"edit edge routing\");\n        edge.routing = document.getElementById(\"edge-routing\").value;\n        forgeTheTopology();\n        selectTheConnection(currentEdgeId);\n       });\n       document.getElementById(\"edge-animate\").addEventListener(\"change\", (e) => {\n        if (!currentEdgeId) return;\n        const edge = EDGE_DATA.list.find((ed) => ed.id === currentEdgeId);\n        if (!edge) return;\n        pushUndo(\"edit edge animate\");\n        edge.animate = e.target.checked;\n        forgeTheTopology();\n        selectTheConnection(currentEdgeId);\n       });\n       document.getElementById(\"animation-style-select\").addEventListener(\"change\", (e) => {\n        PAGE_STATE.animationStyle = e.target.value;\n        if (PAGE_STATE.animateConnections) forgeTheTopology();\n       });\n       document.getElementById(\"animation-direction-select\").addEventListener(\"change\", (e) => {\n        PAGE_STATE.animationDirection = e.target.value;\n        if (PAGE_STATE.animateConnections) forgeTheTopology();\n       });\n       document.getElementById(\"animation-speed-select\").addEventListener(\"change\", (e) => {\n        PAGE_STATE.animationSpeed = parseFloat(e.target.value);\n        if (PAGE_STATE.animateConnections) forgeTheTopology();\n       });\n       document.getElementById(\"edge-animation-style\").addEventListener(\"change\", (e) => {\n        if (!currentEdgeId) return;\n        const edge = EDGE_DATA.list.find((ed) => ed.id === currentEdgeId);\n        if (!edge) return;\n        pushUndo(\"edit edge animation style\");\n        edge.animationStyle = e.target.value || \"\";\n        if (PAGE_STATE.animateConnections || edge.animate === true) forgeTheTopology();\n        selectTheConnection(currentEdgeId);\n       });\n       document.getElementById(\"edge-animation-speed\").addEventListener(\"change\", (e) => {\n        if (!currentEdgeId) return;\n        const edge = EDGE_DATA.list.find((ed) => ed.id === currentEdgeId);\n        if (!edge) return;\n        pushUndo(\"edit edge animation speed\");\n        edge.animationSpeed = e.target.value || \"\";\n        if (PAGE_STATE.animateConnections || edge.animate === true) forgeTheTopology();\n        selectTheConnection(currentEdgeId);\n       });\n       const addEdgeNoteBtn = document.getElementById(\"add-edge-note\");\n       const newEdgeNoteInput = document.getElementById(\"new-edge-note\");\n       addEdgeNoteBtn.addEventListener(\"click\", () => {\n        const txt = newEdgeNoteInput.value.trim();\n        if (!txt || !currentEdgeId) return;\n        const edge = EDGE_DATA.list.find((e) => e.id === currentEdgeId);\n        if (!edge) return;\n\t\tpushUndo(\"add edge note\");\n        edge.notes.push(txt);\n        newEdgeNoteInput.value = \"\";\n        selectTheConnection(currentEdgeId);\n       });\n       newEdgeNoteInput.addEventListener(\"keypress\", (e) => {\n        if (e.key === \"Enter\") {\n         addEdgeNoteBtn.click();\n        }\n       });\n      function selectTheRect(id) {\n\t  if (isViewOnly()) return;\n      currentRectId = id;\n      currentNodeId = null;\n      currentEdgeId = null;\n      currentTextId = null;\n      document.getElementById(\"node-panel\").style.display = \"none\";\n      document.getElementById(\"edge-panel\").style.display = \"none\";\n      document.getElementById(\"text-panel\").style.display = \"none\";\n      document.getElementById(\"rect-panel\").style.display = \"block\";\n      document.getElementById(\"topology-toolbar\").style.display = \"none\";\n      document.querySelectorAll(\".node-group\").forEach(n => n.classList.remove(\"active\"));\n      document.querySelectorAll(\".edge\").forEach(e => e.classList.remove(\"active\"));\n      document.querySelectorAll(\".text-element\").forEach(t => t.classList.remove(\"active\"));\n      document.querySelectorAll(\".rect-group\").forEach(r => r.classList.toggle(\"active\", r.dataset.rectId === id));\n      const rect = RECT_DATA.list.find(r => r.id === id);\n      if (!rect) return;\n      document.getElementById(\"rect-title\").textContent = \"\";\n      document.getElementById(\"rect-color\").value = rect.color || \"#f97316\";\n      document.getElementById(\"rect-border-color\").value = rect.borderColor || rect.color || \"#f97316\";\n      document.getElementById(\"rect-border-width\").value = rect.borderWidth !== undefined ? rect.borderWidth : 2;\n      document.getElementById(\"rect-style-select\").value = rect.style || \"filled\";\n      document.getElementById(\"rect-rotation\").value = Math.max(-360, Math.min(360, rect.rotation || 0));\n      document.getElementById(\"rect-rotation-value\").value = rect.rotation || 0;\n      document.getElementById(\"rect-fill-row\").style.display = (rect.style === \"outlined\") ? \"none\" : \"flex\";\n      const list = document.getElementById(\"rect-notes\");\n      list.innerHTML = \"\";\n      (rect.notes || []).forEach((note, i) => {\n      const li = document.createElement(\"li\");\n      const txt = document.createElement(\"span\");\n      txt.textContent = note;\n      txt.style.flex = \"1\";\n      const del = document.createElement(\"span\");\n      del.className = \"delete-note\";\n      del.textContent = \"✕\";\n      del.addEventListener(\"click\", (e) => {\n      e.stopPropagation();\n      challengeTheImmortal(\"Delete this note?\", () => {\n        pushUndo(\"delete zone note\");\n        rect.notes.splice(i, 1);\n        selectTheRect(id);\n      });\n      });\n      li.appendChild(txt);\n      li.appendChild(del);\n      list.appendChild(li);\n      });\n      forgeTheTopology();\n      }\n      document.getElementById(\"rect-color\").addEventListener(\"input\", () => {\n      if (!currentRectId) return;\n      const rect = RECT_DATA.list.find(r => r.id === currentRectId);\n      if (!rect) return;\n      pushUndo(\"edit zone\");\n      rect.color = document.getElementById(\"rect-color\").value;\n      forgeTheTopology();\n      });\n      document.getElementById(\"rect-border-color\").addEventListener(\"input\", () => {\n      if (!currentRectId) return;\n      const rect = RECT_DATA.list.find(r => r.id === currentRectId);\n      if (!rect) return;\n      pushUndo(\"edit zone\");\n      rect.borderColor = document.getElementById(\"rect-border-color\").value;\n      forgeTheTopology();\n      });\n      document.getElementById(\"rect-border-width\").addEventListener(\"input\", () => {\n      if (!currentRectId) return;\n      const rect = RECT_DATA.list.find(r => r.id === currentRectId);\n      if (!rect) return;\n      pushUndo(\"edit zone\");\n      rect.borderWidth = parseInt(document.getElementById(\"rect-border-width\").value) || 2;\n      forgeTheTopology();\n      });\n      document.getElementById(\"rect-style-select\").addEventListener(\"change\", () => {\n      if (!currentRectId) return;\n      const rect = RECT_DATA.list.find(r => r.id === currentRectId);\n      if (!rect) return;\n      pushUndo(\"edit zone\");\n      rect.style = document.getElementById(\"rect-style-select\").value;\n      forgeTheTopology();\n      selectTheRect(currentRectId);\n      });\n\t  document.getElementById(\"rect-rotation\").addEventListener(\"input\", () => {\n        if (!currentRectId) return;\n        const rect = RECT_DATA.list.find(r => r.id === currentRectId);\n        if (!rect) return;\n        pushUndo(\"rotate zone\");\n        rect.rotation = parseInt(document.getElementById(\"rect-rotation\").value) || 0;\n        document.getElementById(\"rect-rotation-value\").value = rect.rotation;\n        forgeTheTopology();\n      });\n      document.getElementById(\"rect-rotation-value\").addEventListener(\"input\", () => {\n        if (!currentRectId) return;\n        const rect = RECT_DATA.list.find(r => r.id === currentRectId);\n        if (!rect) return;\n        pushUndo(\"rotate zone\");\n        const val = parseInt(document.getElementById(\"rect-rotation-value\").value) || 0;\n        rect.rotation = val;\n        document.getElementById(\"rect-rotation\").value = Math.max(-360, Math.min(360, val));\n        forgeTheTopology();\n      });\n      document.getElementById(\"add-rect-note\").addEventListener(\"click\", () => {\n      const input = document.getElementById(\"new-rect-note\");\n      const txt = input.value.trim();\n      if (!txt || !currentRectId) return;\n      const rect = RECT_DATA.list.find(r => r.id === currentRectId);\n      if (!rect) return;\n      if (!rect.notes) rect.notes = [];\n\t  pushUndo(\"add zone note\");\n      rect.notes.push(txt);\n      input.value = \"\";\n      selectTheRect(currentRectId);\n      });\n      document.getElementById(\"new-rect-note\").addEventListener(\"keypress\", (e) => {\n      if (e.key === \"Enter\") {\n      document.getElementById(\"add-rect-note\").click();\n      }\n      });\n      document.getElementById(\"delete-rect\").addEventListener(\"click\", () => {\n      if (!currentRectId) return;\n      challengeTheImmortal(\"Delete this box?\", () => {\n      pushUndo(\"delete zone\");\n      RECT_DATA.list = RECT_DATA.list.filter(r => r.id !== currentRectId);\n      currentRectId = null;\n      document.getElementById(\"rect-panel\").style.display = \"none\";\n      forgeTheTopology();\n      });\n      });\n       document.getElementById(\"delete-edge\").addEventListener(\"click\", () => {\n        if (!currentEdgeId) return;\n        challengeTheImmortal(\"Are you sure you want to delete this line?\",\n         () => {\n          pushUndo(\"delete edge\");\n          EDGE_DATA.list = EDGE_DATA.list.filter(\n           (e) => e.id !== currentEdgeId);\n          currentEdgeId = null;\n          forgeTheTopology();\n          const availableNodes = Object.keys(NODE_DATA);\n          if (availableNodes.length > 0) {\n           claimTheImmortal(availableNodes[0]);\n          } else {\n           document.getElementById(\"node-panel\").style.display = \"none\";\n           document.getElementById(\"edge-panel\").style.display = \"none\";\n           document.getElementById(\"topology-toolbar\", ).style.display = \"none\";\n          }\n         });\n       });\n       document.getElementById(\"add-line-btn\").addEventListener(\"click\", () => {\n        if (!currentNodeId) return;\n        const select = document.getElementById(\"add-line-select\");\n        const directionSelect = document.getElementById(\"add-line-direction\");\n        const colorInput = document.getElementById(\"add-line-color\");\n        const routingSelect = document.getElementById(\"add-line-routing\");\n        const targetId = select.value;\n        if (!targetId || targetId === currentNodeId) return;\n        const direction = directionSelect.value || \"none\";\n        const lineColor = colorInput.value || \"#475569\";\n        const routing = routingSelect.value || PAGE_STATE.defaultEdgeRouting || \"orthogonal\";\n        const newId = `${currentNodeId}-${targetId}-${Date.now()}`;\n        const newEdge = {\n         id: newId,\n         from: currentNodeId,\n         to: targetId,\n         width: 4,\n         color: lineColor,\n         direction: direction,\n         routing: routing,\n         type: \"main\",\n         notes: [],\n         fromPort: \"\",\n         toPort: \"\",\n        };\n        pushUndo(\"add edge\");\n        EDGE_DATA.list.push(newEdge);\n        forgeTheTopology();\n        claimTheImmortal(currentNodeId);\n       });\n       let rectStartPoint = null;\n       let rectPreviewEl = null;\n       let rectStyle = \"filled\";\n       let freeDrawPoints = [];\n       let freeDrawPolylineEl = null;\n       let freeDrawPointEls = [];\n       const drawToggleBtn = document.getElementById(\"draw-toggle\");\n       const drawUndoBtn = document.getElementById(\"draw-undo\");\n       const drawColorInput = document.getElementById(\"draw-color\");\n       const drawStyleSelect = document.getElementById(\"draw-style\");\n       const drawArrowSelect = document.getElementById(\"draw-arrow\");\n       const svgMap = document.getElementById(\"map\");\n       function updateFreeDrawGraphics() {\n        const ns = \"http://www.w3.org/2000/svg\";\n        const svg = svgMap;\n        if (!freeDrawPolylineEl && freeDrawPoints.length > 0) {\n         freeDrawPolylineEl = document.createElementNS(ns, \"polyline\");\n         freeDrawPolylineEl.classList.add(\"edge\", \"free-preview\");\n         freeDrawPolylineEl.setAttribute(\"fill\", \"none\");\n         svg.appendChild(freeDrawPolylineEl);\n        }\n        if (freeDrawPolylineEl) {\n         if (freeDrawPoints.length === 0) {\n          freeDrawPolylineEl.remove();\n          freeDrawPolylineEl = null;\n         } else {\n          const ptsStr = freeDrawPoints.map((p) => `${p.x},${p.y}`).join(\" \");\n          freeDrawPolylineEl.setAttribute(\"points\", ptsStr);\n          freeDrawPolylineEl.style.stroke = drawColorInput.value || \"#475569\";\n          freeDrawPolylineEl.style.strokeWidth = 3;\n          const lineStyle = drawStyleSelect.value || \"solid\";\n          if (lineStyle === \"dashed\") {\n           freeDrawPolylineEl.style.strokeDasharray = \"10,5\";\n          } else if (lineStyle === \"dotted\") {\n           freeDrawPolylineEl.style.strokeDasharray = \"2,4\";\n          } else {\n           freeDrawPolylineEl.style.strokeDasharray = \"none\";\n          }\n         }\n        }\n        freeDrawPointEls.forEach((el) => el.remove());\n        freeDrawPointEls = [];\n        freeDrawPoints.forEach((p, idx) => {\n         const c = document.createElementNS(ns, \"circle\");\n         c.classList.add(\"free-point\");\n         c.setAttribute(\"cx\", p.x);\n         c.setAttribute(\"cy\", p.y);\n         c.setAttribute(\"r\", 5);\n         c.dataset.index = String(idx);\n         c.addEventListener(\"mousedown\", (e) => {\n          if (!freeDrawMode) return;\n          e.preventDefault();\n          e.stopPropagation();\n          let dragging = true;\n          const svgEl = svgMap;\n          const moveHandler = (ev) => {\n           if (!dragging) return;\n           const pt = svgEl.createSVGPoint();\n           pt.x = ev.clientX;\n           pt.y = ev.clientY;\n           const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n           const i = parseInt(c.dataset.index, 10);\n           if (Number.isNaN(i) || !freeDrawPoints[i]) return;\n           freeDrawPoints[i].x = svgP.x;\n           freeDrawPoints[i].y = svgP.y;\n           updateFreeDrawGraphics();\n          };\n          const upHandler = () => {\n           dragging = false;\n           document.removeEventListener(\"mousemove\", moveHandler);\n           document.removeEventListener(\"mouseup\", upHandler);\n          };\n          document.addEventListener(\"mousemove\", moveHandler);\n          document.addEventListener(\"mouseup\", upHandler);\n         });\n         c.addEventListener(\"touchstart\",\n          (e) => {\n           if (!freeDrawMode) return;\n           e.preventDefault();\n           e.stopPropagation();\n           let dragging = true;\n           const svgEl = svgMap;\n           const touchMoveHandler = (ev) => {\n            if (!dragging || !ev.touches[0]) return;\n            const pt = svgEl.createSVGPoint();\n            pt.x = ev.touches[0].clientX;\n            pt.y = ev.touches[0].clientY;\n            const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n            const i = parseInt(c.dataset.index, 10);\n            if (Number.isNaN(i) || !freeDrawPoints[i]) return;\n            freeDrawPoints[i].x = svgP.x;\n            freeDrawPoints[i].y = svgP.y;\n            updateFreeDrawGraphics();\n           };\n           const touchUpHandler = () => {\n            dragging = false;\n            document.removeEventListener(\"touchmove\", touchMoveHandler);\n            document.removeEventListener(\"touchend\", touchUpHandler);\n           };\n           document.addEventListener(\"touchmove\", touchMoveHandler);\n           document.addEventListener(\"touchend\", touchUpHandler);\n          }, {\n           passive: false\n          });\n         svg.appendChild(c);\n         freeDrawPointEls.push(c);\n        });\n        drawUndoBtn.style.display = freeDrawPoints.length ? \"inline-block\" : \"none\";\n       }\n       function addFreeDrawPoint(x, y) {\n        freeDrawPoints.push({\n         x,\n         y\n        });\n        updateFreeDrawGraphics();\n       }\n       function startFreeDraw() {\n        freeDrawMode = true;\n        freeDrawPoints = [];\n        if (freeDrawPolylineEl) {\n         freeDrawPolylineEl.remove();\n         freeDrawPolylineEl = null;\n        }\n        freeDrawPointEls.forEach((el) => el.remove());\n        freeDrawPointEls = [];\n        svgMap.style.cursor = \"crosshair\";\n        drawToggleBtn.textContent = \"Done\";\n        drawToggleBtn.classList.add(\"done-btn-active\");\n        drawUndoBtn.style.display = \"none\";\n       }\n       function finishFreeDraw() {\n        freeDrawMode = false;\n        svgMap.style.cursor = \"\";\n        drawToggleBtn.textContent = \"✏️\";\n        drawToggleBtn.classList.remove(\"done-btn-active\");\n        if (freeDrawPoints.length >= 2) {\n         const color = drawColorInput.value || \"#475569\";\n         const lineStyle = drawStyleSelect.value || \"solid\";\n         const arrowDir = drawArrowSelect.value || \"none\";\n         const newId = \"custom-\" + Date.now();\n         const pointsCopy = freeDrawPoints.map((p) => ({\n          x: p.x,\n          y: p.y,\n         }));\n         EDGE_DATA.list.push({\n          id: newId,\n          type: \"custom\",\n          color,\n          width: 4,\n          lineStyle: lineStyle,\n          direction: arrowDir,\n          points: pointsCopy,\n          notes: [],\n         });\n         freeDrawPoints = [];\n         if (freeDrawPolylineEl) {\n          freeDrawPolylineEl.remove();\n          freeDrawPolylineEl = null;\n         }\n         freeDrawPointEls.forEach((el) => el.remove());\n         freeDrawPointEls = [];\n         forgeTheTopology();\n         selectTheConnection(newId);\n        } else {\n         freeDrawPoints = [];\n         if (freeDrawPolylineEl) {\n          freeDrawPolylineEl.remove();\n          freeDrawPolylineEl = null;\n         }\n         freeDrawPointEls.forEach((el) => el.remove());\n         freeDrawPointEls = [];\n         forgeTheLegend();\n\t\t updateZoneLegend();\n        }\n        drawUndoBtn.style.display = \"none\";\n       }\n       drawToggleBtn.addEventListener(\"click\", () => {\n        if (currentView.mode === \"rack\") {\n         alert(\"Drawing tools are disabled in rack view. Exit to topology view to use drawing tools.\");\n         return;\n        }\n        if (freeDrawMode) {\n         finishFreeDraw();\n        } else {\n         startFreeDraw();\n        }\n       });\n       drawUndoBtn.addEventListener(\"click\", () => {\n        if (!freeDrawMode || !freeDrawPoints.length) return;\n        freeDrawPoints.pop();\n        updateFreeDrawGraphics();\n       });\n       const drawToolbar = document.getElementById(\"draw-toolbar\");\n       drawToolbar.addEventListener(\"mousedown\", (e) => {\n        e.stopPropagation();\n       });\n       drawToolbar.addEventListener(\"click\", (e) => {\n        if (e.target !== drawToggleBtn && e.target !== drawUndoBtn) {\n         e.stopPropagation();\n        }\n       });\n       drawStyleSelect.addEventListener(\"change\", () => {\n        if (freeDrawMode) {\n         updateFreeDrawGraphics();\n        }\n       });\n       drawArrowSelect.addEventListener(\"change\", () => {\n        if (freeDrawMode) {\n         updateFreeDrawGraphics();\n        }\n       });\n       drawColorInput.addEventListener(\"input\", () => {\n        if (freeDrawMode) {\n         updateFreeDrawGraphics();\n        }\n       });\n       drawStyleSelect.addEventListener(\"mousedown\", (e) => {\n        e.stopPropagation();\n       });\n       drawStyleSelect.addEventListener(\"click\", (e) => {\n        e.stopPropagation();\n       });\n       drawArrowSelect.addEventListener(\"mousedown\", (e) => {\n        e.stopPropagation();\n       });\n       drawArrowSelect.addEventListener(\"click\", (e) => {\n        e.stopPropagation();\n       });\n       drawColorInput.addEventListener(\"mousedown\", (e) => {\n        e.stopPropagation();\n       });\n       drawColorInput.addEventListener(\"click\", (e) => {\n        e.stopPropagation();\n       });\n       svgMap.addEventListener(\"click\", (e) => {\n        if (!freeDrawMode) return;\n        if (e.button !== 0) return;\n        const target = e.target;\n        if (target && target.classList && target.classList.contains(\"free-point\")) return;\n        const svgEl = svgMap;\n        const pt = svgEl.createSVGPoint();\n        pt.x = e.clientX;\n        pt.y = e.clientY;\n        const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n        addFreeDrawPoint(svgP.x, svgP.y);\n       });\n       svgMap.addEventListener(\"touchend\",\n        (e) => {\n         if (!freeDrawMode) return;\n         const target = e.target;\n         if (target && target.classList && target.classList.contains(\"free-point\")) return;\n         if (e.changedTouches && e.changedTouches[0]) {\n          e.preventDefault();\n          const svgEl = svgMap;\n          const pt = svgEl.createSVGPoint();\n          pt.x = e.changedTouches[0].clientX;\n          pt.y = e.changedTouches[0].clientY;\n          const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n          addFreeDrawPoint(svgP.x, svgP.y);\n         }\n        }, {\n         passive: false\n        });\n       const settingsBtn = document.getElementById(\"settings-btn\");\n       const rectToggleBtn = document.getElementById(\"rect-toggle\");\n       const rectStyleSelect = document.getElementById(\"rect-style\");\n       function updateRectPreview() {\n        if (!rectPreviewEl || !rectStartPoint) return;\n        const ns = \"http://www.w3.org/2000/svg\";\n        const svg = svgMap;\n        if (rectPreviewEl) {\n         rectPreviewEl.remove();\n         rectPreviewEl = null;\n        }\n       }\n       function startRectDraw() {\n        rectDrawMode = true;\n        rectStartPoint = null;\n        rectPreviewEl = null;\n        svgMap.style.cursor = \"crosshair\";\n        rectToggleBtn.textContent = \"Done\";\n        rectToggleBtn.classList.add(\"done-btn-active\");\n        rectStyle = rectStyleSelect.value || \"filled\";\n        if (freeDrawMode) {\n         finishFreeDraw();\n        }\n        updateRectangleDeleteButtons();\n       }\n       function finishRectDraw() {\n        rectDrawMode = false;\n        svgMap.style.cursor = \"\";\n        rectToggleBtn.textContent = \"▭\";\n        rectToggleBtn.classList.remove(\"done-btn-active\");\n        if (rectPreviewEl) {\n         rectPreviewEl.remove();\n         rectPreviewEl = null;\n        }\n        rectStartPoint = null;\n\t\tupdateRectangleDeleteButtons();\n       }\n       rectToggleBtn.addEventListener(\"click\", () => {\n        if (currentView.mode === \"rack\") {\n         alert(\"Drawing tools are disabled in rack view. Exit to topology view to use drawing tools.\");\n         return;\n        }\n        if (rectDrawMode) {\n         finishRectDraw();\n        } else {\n         startRectDraw();\n        }\n       });\n       rectStyleSelect.addEventListener(\"mousedown\", (e) => {\n        e.stopPropagation();\n       });\n       rectStyleSelect.addEventListener(\"click\", (e) => {\n        e.stopPropagation();\n       });\n       rectStyleSelect.addEventListener(\"change\", () => {\n        if (rectDrawMode) {\n         rectStyle = rectStyleSelect.value || \"filled\";\n        }\n       });\n       svgMap.addEventListener(\"mousedown\", (e) => {\n        if (!rectDrawMode) return;\n        if (e.button !== 0) return;\n        e.preventDefault();\n        e.stopPropagation();\n        const svgEl = svgMap;\n        const pt = svgEl.createSVGPoint();\n        pt.x = e.clientX;\n        pt.y = e.clientY;\n        const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n        rectStartPoint = { x: svgP.x, y: svgP.y };\n        const ns = \"http://www.w3.org/2000/svg\";\n        rectPreviewEl = document.createElementNS(ns, \"rect\");\n        rectPreviewEl.classList.add(\"rect-preview\");\n        rectPreviewEl.setAttribute(\"x\", svgP.x);\n        rectPreviewEl.setAttribute(\"y\", svgP.y);\n        rectPreviewEl.setAttribute(\"width\", 0);\n        rectPreviewEl.setAttribute(\"height\", 0);\n        const color = drawColorInput.value || \"#f97316\";\n        rectPreviewEl.style.stroke = color;\n        rectPreviewEl.style.strokeWidth = 2;\n        rectPreviewEl.style.strokeDasharray = \"5,5\";\n        if (rectStyle === \"filled\") {\n         rectPreviewEl.style.fill = color;\n         rectPreviewEl.style.fillOpacity = \"0.3\";\n        } else {\n         rectPreviewEl.style.fill = \"none\";\n        }\n        svgEl.appendChild(rectPreviewEl);\n       });\n       svgMap.addEventListener(\"mousemove\", (e) => {\n        if (!rectDrawMode || !rectStartPoint || !rectPreviewEl) return;\n        const svgEl = svgMap;\n        const pt = svgEl.createSVGPoint();\n        pt.x = e.clientX;\n        pt.y = e.clientY;\n        const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n        const x = Math.min(rectStartPoint.x, svgP.x);\n        const y = Math.min(rectStartPoint.y, svgP.y);\n        const width = Math.abs(svgP.x - rectStartPoint.x);\n        const height = Math.abs(svgP.y - rectStartPoint.y);\n        rectPreviewEl.setAttribute(\"x\", x);\n        rectPreviewEl.setAttribute(\"y\", y);\n        rectPreviewEl.setAttribute(\"width\", width);\n        rectPreviewEl.setAttribute(\"height\", height);\n       });\n       svgMap.addEventListener(\"mouseup\", (e) => {\n        if (!rectDrawMode || !rectStartPoint || !rectPreviewEl) return;\n        const svgEl = svgMap;\n        const pt = svgEl.createSVGPoint();\n        pt.x = e.clientX;\n        pt.y = e.clientY;\n        const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n        const x = Math.min(rectStartPoint.x, svgP.x);\n        const y = Math.min(rectStartPoint.y, svgP.y);\n        const width = Math.abs(svgP.x - rectStartPoint.x);\n        const height = Math.abs(svgP.y - rectStartPoint.y);\n        if (width > 10 && height > 10) {\n      pushUndo(\"draw zone\");\n         const color = drawColorInput.value || \"#f97316\";\n         const newId = \"rect-\" + Date.now();\n         const lineStyle = document.getElementById(\"draw-style\").value || \"solid\";\n         RECT_DATA.list.push({\n          id: newId,\n          x: x,\n          y: y,\n          width: width,\n          height: height,\n          color: color,\n          style: rectStyle,\n          lineStyle: lineStyle,\n          notes: []\n         });\n         forgeTheTopology();\n        }\n        if (rectPreviewEl) {\n         rectPreviewEl.remove();\n         rectPreviewEl = null;\n        }\n        rectStartPoint = null;\n       });\n       let rectTouchStart = null;\n       svgMap.addEventListener(\"touchstart\", (e) => {\n        if (!rectDrawMode) return;\n        if (e.touches.length !== 1) return;\n        e.preventDefault();\n        const touch = e.touches[0];\n        const svgEl = svgMap;\n        const pt = svgEl.createSVGPoint();\n        pt.x = touch.clientX;\n        pt.y = touch.clientY;\n        const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n        rectStartPoint = { x: svgP.x, y: svgP.y };\n        rectTouchStart = { x: svgP.x, y: svgP.y };\n        const ns = \"http://www.w3.org/2000/svg\";\n        rectPreviewEl = document.createElementNS(ns, \"rect\");\n        rectPreviewEl.classList.add(\"rect-preview\");\n        rectPreviewEl.setAttribute(\"x\", svgP.x);\n        rectPreviewEl.setAttribute(\"y\", svgP.y);\n        rectPreviewEl.setAttribute(\"width\", 0);\n        rectPreviewEl.setAttribute(\"height\", 0);\n        const color = drawColorInput.value || \"#f97316\";\n        rectPreviewEl.style.stroke = color;\n        rectPreviewEl.style.strokeWidth = 2;\n        rectPreviewEl.style.strokeDasharray = \"5,5\";\n        if (rectStyle === \"filled\") {\n         rectPreviewEl.style.fill = color;\n         rectPreviewEl.style.fillOpacity = \"0.3\";\n        } else {\n         rectPreviewEl.style.fill = \"none\";\n        }\n        svgEl.appendChild(rectPreviewEl);\n       }, { passive: false });\n       svgMap.addEventListener(\"touchmove\", (e) => {\n        if (!rectDrawMode || !rectStartPoint || !rectPreviewEl) return;\n        if (e.touches.length !== 1) return;\n        e.preventDefault();\n        const touch = e.touches[0];\n        const svgEl = svgMap;\n        const pt = svgEl.createSVGPoint();\n        pt.x = touch.clientX;\n        pt.y = touch.clientY;\n        const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n        const x = Math.min(rectStartPoint.x, svgP.x);\n        const y = Math.min(rectStartPoint.y, svgP.y);\n        const width = Math.abs(svgP.x - rectStartPoint.x);\n        const height = Math.abs(svgP.y - rectStartPoint.y);\n        rectPreviewEl.setAttribute(\"x\", x);\n        rectPreviewEl.setAttribute(\"y\", y);\n        rectPreviewEl.setAttribute(\"width\", width);\n        rectPreviewEl.setAttribute(\"height\", height);\n       }, { passive: false });\n       svgMap.addEventListener(\"touchend\", (e) => {\n        if (!rectDrawMode || !rectStartPoint || !rectPreviewEl) return;\n        if (!rectTouchStart) return;\n        e.preventDefault();\n        const touch = e.changedTouches[0];\n        const svgEl = svgMap;\n        const pt = svgEl.createSVGPoint();\n        pt.x = touch.clientX;\n        pt.y = touch.clientY;\n        const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n        const x = Math.min(rectStartPoint.x, svgP.x);\n        const y = Math.min(rectStartPoint.y, svgP.y);\n        const width = Math.abs(svgP.x - rectStartPoint.x);\n        const height = Math.abs(svgP.y - rectStartPoint.y);\n        if (width > 10 && height > 10) {\n      pushUndo(\"draw zone\");\n         const color = drawColorInput.value || \"#f97316\";\n         const newId = \"rect-\" + Date.now();\n         const lineStyle = document.getElementById(\"draw-style\").value || \"solid\";\n         RECT_DATA.list.push({\n          id: newId,\n          x: x,\n          y: y,\n          width: width,\n          height: height,\n          color: color,\n          style: rectStyle,\n          lineStyle: lineStyle,\n          notes: []\n         });\n         forgeTheTopology();\n        }\n        if (rectPreviewEl) {\n         rectPreviewEl.remove();\n         rectPreviewEl = null;\n        }\n        rectStartPoint = null;\n        rectTouchStart = null;\n       }, { passive: false });\n       const textToggleBtn = document.getElementById(\"text-toggle\");\n       function startTextMode() {\n        textDrawMode = true;\n        svgMap.style.cursor = \"crosshair\";\n        textToggleBtn.textContent = \"Done\";\n        textToggleBtn.classList.add(\"done-btn-active\");\n        if (freeDrawMode) {\n         finishFreeDraw();\n        }\n        if (rectDrawMode) {\n         finishRectDraw();\n        }\n        updateTextDeleteButtons();\n       }\n       function finishTextMode() {\n        textDrawMode = false;\n        svgMap.style.cursor = \"\";\n        textToggleBtn.textContent = \"T\";\n        textToggleBtn.classList.remove(\"done-btn-active\");\n        updateTextDeleteButtons();\n       }\n       textToggleBtn.addEventListener(\"click\", () => {\n        if (currentView.mode === \"rack\") {\n         alert(\"Drawing tools are disabled in rack view. Exit to topology view to use drawing tools.\");\n         return;\n        }\n        if (textDrawMode) {\n         finishTextMode();\n        } else {\n         startTextMode();\n        }\n       });\n       function handleTextPlacement(e) {\n        if (!textDrawMode) return;\n        const svgEl = svgMap;\n        const pt = svgEl.createSVGPoint();\n        pt.x = e.clientX;\n        pt.y = e.clientY;\n        const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n        const newId = \"text-\" + Date.now();\n      pushUndo(\"add text\");\n        TEXT_DATA.list.push({\n         id: newId,\n         x: svgP.x,\n         y: svgP.y,\n         content: \"New Text\",\n         fontSize: 18,\n         color: \"#e2e8f0\",\n         fontWeight: \"normal\",\n         fontStyle: \"normal\",\n         textAlign: \"start\",\n         textDecoration: \"none\",\n         bgColor: \"#000000\",\n         bgEnabled: false,\n         opacity: 1\n        });\n        forgeTheTopology();\n        showTextPanel(newId);\n       }\n       svgMap.addEventListener(\"click\", (e) => {\n        if (!textDrawMode) return;\n        if (e.target.closest('.text-delete-btn')) return;\n        if (e.target.closest('.text-group')) return;\n        e.preventDefault();\n        e.stopPropagation();\n        handleTextPlacement(e);\n       });\n       svgMap.addEventListener(\"touchend\", (e) => {\n        if (!textDrawMode) return;\n        if (e.target.closest('.text-delete-btn')) return;\n        if (e.target.closest('.text-group')) return;\n        if (e.touches.length > 0) return;\n        e.preventDefault();\n        const touch = e.changedTouches[0];\n        const fakeEvent = {\n         clientX: touch.clientX,\n         clientY: touch.clientY,\n         preventDefault: () => {},\n         stopPropagation: () => {}\n        };\n        handleTextPlacement(fakeEvent);\n       }, { passive: false });\n       function showTextPanel(textId) {\n\t   if (isViewOnly()) return;\n       currentTextId = textId;\n       currentNodeId = null;\n       currentEdgeId = null;\n       currentRectId = null;\n       const textItem = TEXT_DATA.list.find(t => t.id === textId);\n       if (!textItem) return;\n       document.getElementById(\"node-panel\").style.display = \"none\";\n       document.getElementById(\"edge-panel\").style.display = \"none\";\n       document.getElementById(\"rect-panel\").style.display = \"none\";\n       const textPanel = document.getElementById(\"text-panel\");\n       textPanel.style.display = \"block\";\n       document.querySelectorAll(\".node-group\").forEach(n => n.classList.remove(\"active\"));\n       document.querySelectorAll(\".edge\").forEach(e => e.classList.remove(\"active\"));\n       document.querySelectorAll(\".rect-group\").forEach(r => r.classList.remove(\"active\"));\n       document.querySelectorAll(\".text-element\").forEach(t => t.classList.toggle(\"active\", t.dataset.textId === textId));\n        document.getElementById(\"text-content\").value = textItem.content;\n        document.getElementById(\"text-font-size\").value = textItem.fontSize;\n        document.getElementById(\"text-color\").value = textItem.color;\n        document.getElementById(\"text-font-weight\").value = textItem.fontWeight;\n        document.getElementById(\"text-font-style\").value = textItem.fontStyle;\n        document.getElementById(\"text-align\").value = textItem.textAlign;\n        document.getElementById(\"text-decoration\").value = textItem.textDecoration;\n        document.getElementById(\"text-bg-color\").value = textItem.bgColor;\n        document.getElementById(\"text-bg-enabled\").checked = textItem.bgEnabled;\n        document.getElementById(\"text-opacity\").value = textItem.opacity;\n        document.getElementById(\"text-opacity-val\").textContent = Math.round(textItem.opacity * 100) + \"%\";\n        document.getElementById(\"text-rotation\").value = Math.max(-360, Math.min(360, textItem.rotation || 0));\n        document.getElementById(\"text-rotation-val\").value = textItem.rotation || 0;\n       }\n       function updateTextDeleteButtons() {\n        const deleteButtons = document.querySelectorAll('.text-delete-btn');\n        deleteButtons.forEach(btn => {\n         btn.style.display = textDrawMode ? 'block' : 'none';\n        });\n       }\n       function deleteText(textId) {\n      pushUndo(\"delete text\");\n        TEXT_DATA.list = TEXT_DATA.list.filter(t => t.id !== textId);\n        forgeTheTopology();\n        if (currentTextId === textId) {\n         document.getElementById(\"text-panel\").style.display = \"none\";\n         currentTextId = null;\n        }\n       }\n       document.getElementById(\"text-content\").addEventListener(\"input\", (e) => {\n        if (!currentTextId) return;\n        const textItem = TEXT_DATA.list.find(t => t.id === currentTextId);\n        if (textItem) {\n      pushUndo(\"edit text\");\n         textItem.content = e.target.value;\n         forgeTheTopology();\n        }\n       });\n       document.getElementById(\"text-font-size\").addEventListener(\"input\", (e) => {\n        if (!currentTextId) return;\n        const textItem = TEXT_DATA.list.find(t => t.id === currentTextId);\n        if (textItem) {\n         pushUndo(\"edit text\");\n         textItem.fontSize = parseInt(e.target.value);\n         forgeTheTopology();\n        }\n       });\n       document.getElementById(\"text-color\").addEventListener(\"input\", (e) => {\n        if (!currentTextId) return;\n        const textItem = TEXT_DATA.list.find(t => t.id === currentTextId);\n        if (textItem) {\n         pushUndo(\"edit text\");\n         textItem.color = e.target.value;\n         forgeTheTopology();\n        }\n       });\n       document.getElementById(\"text-font-weight\").addEventListener(\"change\", (e) => {\n        if (!currentTextId) return;\n        const textItem = TEXT_DATA.list.find(t => t.id === currentTextId);\n        if (textItem) {\n         pushUndo(\"edit text\");\n         textItem.fontWeight = e.target.value;\n         forgeTheTopology();\n        }\n       });\n       document.getElementById(\"text-font-style\").addEventListener(\"change\", (e) => {\n        if (!currentTextId) return;\n        const textItem = TEXT_DATA.list.find(t => t.id === currentTextId);\n        if (textItem) {\n         pushUndo(\"edit text\");\n         textItem.fontStyle = e.target.value;\n         forgeTheTopology();\n        }\n       });\n       document.getElementById(\"text-align\").addEventListener(\"change\", (e) => {\n        if (!currentTextId) return;\n        const textItem = TEXT_DATA.list.find(t => t.id === currentTextId);\n        if (textItem) {\n         pushUndo(\"edit text\");\n         textItem.textAlign = e.target.value;\n         forgeTheTopology();\n        }\n       });\n        document.getElementById(\"text-decoration\").addEventListener(\"change\", (e) => {\n        if (!currentTextId) return;\n        const textItem = TEXT_DATA.list.find(t => t.id === currentTextId);\n        if (textItem) {\n         pushUndo(\"edit text\");\n         textItem.textDecoration = e.target.value;\n         forgeTheTopology();\n        }\n       });\n       document.getElementById(\"text-bg-color\").addEventListener(\"input\", (e) => {\n        if (!currentTextId) return;\n        const textItem = TEXT_DATA.list.find(t => t.id === currentTextId);\n        if (textItem) {\n         pushUndo(\"edit text\");\n         textItem.bgColor = e.target.value;\n         forgeTheTopology();\n        }\n       });\n       document.getElementById(\"text-bg-enabled\").addEventListener(\"change\", (e) => {\n        if (!currentTextId) return;\n        const textItem = TEXT_DATA.list.find(t => t.id === currentTextId);\n        if (textItem) {\n         pushUndo(\"edit text\");\n         textItem.bgEnabled = e.target.checked;\n         forgeTheTopology();\n        }\n       });\n       document.getElementById(\"text-opacity\").addEventListener(\"input\", (e) => {\n        if (!currentTextId) return;\n        const textItem = TEXT_DATA.list.find(t => t.id === currentTextId);\n        if (textItem) {\n         pushUndo(\"edit text\");\n         textItem.opacity = parseFloat(e.target.value);\n         document.getElementById(\"text-opacity-val\").textContent = Math.round(textItem.opacity * 100) + \"%\";\n         forgeTheTopology();\n        }\n       });\n       document.getElementById(\"text-rotation\").addEventListener(\"input\", (e) => {\n         if (!currentTextId) return;\n         const textItem = TEXT_DATA.list.find(t => t.id === currentTextId);\n         if (textItem) {\n           pushUndo(\"rotate text\");\n           textItem.rotation = parseInt(e.target.value) || 0;\n           document.getElementById(\"text-rotation-val\").value = textItem.rotation;\n           forgeTheTopology();\n         }\n       });\n       document.getElementById(\"text-rotation-val\").addEventListener(\"input\", (e) => {\n         if (!currentTextId) return;\n         const textItem = TEXT_DATA.list.find(t => t.id === currentTextId);\n         if (textItem) {\n           pushUndo(\"rotate text\");\n           const val = parseInt(e.target.value) || 0;\n           textItem.rotation = val;\n           document.getElementById(\"text-rotation\").value = Math.max(-360, Math.min(360, val));\n           forgeTheTopology();\n         }\n       });\n       document.getElementById(\"delete-text\").addEventListener(\"click\", () => {\n        if (!currentTextId) return;\n        const textItem = TEXT_DATA.list.find(t => t.id === currentTextId);\n        if (textItem) {\n         challengeTheImmortal(`Delete text \"${(textItem.content || '').substring(0, 30)}...\"?`, () => {\n          deleteText(currentTextId);\n         });\n        }\n       });\n       const settingsModal = document.getElementById(\"settings-modal\");\n       const settingsClose = document.getElementById(\"settings-close\");\n       settingsBtn.addEventListener(\"click\", () => {\n       document.getElementById(\"page-bg-color\").value = rgbaToHex(PAGE_STATE.background) || \"#050608\";\n       document.getElementById(\"topbar-bg-color\").value = rgbaToHex(PAGE_STATE.topbarBg) || \"#0b0e13\";\n       document.getElementById(\"topbar-border-color\").value = rgbaToHex(PAGE_STATE.topbarBorder) || \"#1f2533\";\n       document.getElementById(\"panel-color\").value = rgbaToHex(PAGE_STATE.panel) || \"#0b0e13\";\n       document.getElementById(\"panel-alt-color\").value = PAGE_STATE.panelAlt || \"#10141b\";\n       document.getElementById(\"sidebar-bg-color\").value = PAGE_STATE.sidebarBg || \"#10141b\";\n       document.getElementById(\"btn-bg-color\").value = PAGE_STATE.btnBg || \"#0b0e13\";\n       document.getElementById(\"btn-text-color\").value = PAGE_STATE.btnText || \"#e2e8f0\";\n       document.getElementById(\"tag-fill-color\").value = PAGE_STATE.tagFill || \"#1e293b\";\n       document.getElementById(\"tag-text-color\").value = PAGE_STATE.tagText || \"#e2e8f0\";\n       document.getElementById(\"tag-border-color\").value = PAGE_STATE.tagBorder || \"#475569\";\n       document.getElementById(\"input-bg-color\").value = PAGE_STATE.inputBg || \"#0b0e13\";\n       document.getElementById(\"input-text-color\").value = PAGE_STATE.inputText || \"#e2e8f0\";\n\t   document.getElementById(\"input-border-color\").value = PAGE_STATE.inputBorder || \"#1f2937\";\n       document.getElementById(\"input-font-family\").value = PAGE_STATE.inputFont || \"Inter, system-ui, sans-serif\";\n       document.getElementById(\"input-font-size\").value = PAGE_STATE.inputFontSize || 14;\n       document.getElementById(\"toolbar-bg-color\").value = PAGE_STATE.toolbarBg || \"#0f172a\";\n       document.getElementById(\"toolbar-border-color\").value = PAGE_STATE.toolbarBorder || \"#1f2937\";\n       document.getElementById(\"toolbar-text-color\").value = PAGE_STATE.toolbarText || \"#94a3b8\";\n       document.getElementById(\"toolbar-btn-bg-color\").value = PAGE_STATE.toolbarBtnBg || \"#0b0e13\";\n       document.getElementById(\"toolbar-btn-text-color\").value = PAGE_STATE.toolbarBtnText || \"#e2e8f0\";\n       document.getElementById(\"minimap-dots-color\").value = PAGE_STATE.minimapDots || \"#94a3b8\";\n       document.getElementById(\"canvas-hint-enabled\").checked = PAGE_STATE.canvasHintEnabled !== false;\n       document.getElementById(\"canvas-hint-bg-color\").value = PAGE_STATE.canvasHintBg || \"#0f172a\";\n       document.getElementById(\"canvas-hint-text-color\").value = PAGE_STATE.canvasHintColor || \"#94a3b8\";\n       document.getElementById(\"canvas-hint-text\").value = PAGE_STATE.canvasHintText || \"\";\n       document.getElementById(\"accent-color\").value = rgbaToHex(PAGE_STATE.accent) || \"#4fd1c5\";\n       document.getElementById(\"danger-color\").value = rgbaToHex(PAGE_STATE.danger) || \"#f56565\";\n       document.getElementById(\"text-main-color\").value = rgbaToHex(PAGE_STATE.textMain) || \"#e2e8f0\";\n       document.getElementById(\"text-soft-color\").value = PAGE_STATE.textSoft || \"#94a3b8\";\n       document.getElementById(\"node-fill-color\").value = PAGE_STATE.nodeFill || \"#1e293b\";\n       document.getElementById(\"node-stroke-color\").value = PAGE_STATE.nodeStroke || \"#475569\";\n       document.getElementById(\"node-title-color\").value = PAGE_STATE.nodeTitle || \"#e2e8f0\";\n       document.getElementById(\"node-sub-color\").value = PAGE_STATE.nodeSub || \"#94a3b8\";\n       document.getElementById(\"node-title-size\").value = PAGE_STATE.nodeTitleSize || 18;\n       document.getElementById(\"node-sub-size\").value = PAGE_STATE.nodeSubSize || 13;\n       document.getElementById(\"node-font-family\").value = PAGE_STATE.nodeFont || \"Inter, system-ui, sans-serif\";\n       document.getElementById(\"default-edge-color\").value = PAGE_STATE.defaultEdge || \"#475569\";\n       document.getElementById(\"add-line-color\").value = PAGE_STATE.defaultEdge || \"#475569\";\n       document.getElementById(\"default-edge-routing\").value = PAGE_STATE.defaultEdgeRouting || \"curved\";\n\t   document.getElementById(\"anim-master\").addEventListener(\"change\", e => { ANIM_SETTINGS.masterAnim = e.target.checked; applyAnimZoneSettings(); });\n\t\tdocument.getElementById(\"anim-type-sweep\").addEventListener(\"change\", e => { ANIM_SETTINGS.animTypes.sweep = e.target.checked; applyAnimZoneSettings(); });\n\t\tdocument.getElementById(\"anim-type-pulse\").addEventListener(\"change\", e => { ANIM_SETTINGS.animTypes.pulse = e.target.checked; applyAnimZoneSettings(); });\n\t\tdocument.getElementById(\"anim-type-rings\").addEventListener(\"change\", e => { ANIM_SETTINGS.animTypes.rings = e.target.checked; applyAnimZoneSettings(); });\n\t\tdocument.getElementById(\"anim-type-spin\").addEventListener(\"change\", e => { ANIM_SETTINGS.animTypes.spin = e.target.checked; applyAnimZoneSettings(); });\n\t\tdocument.getElementById(\"anim-type-connections\").addEventListener(\"change\", e => { ANIM_SETTINGS.animTypes.connections = e.target.checked; applyAnimZoneSettings(); });\n\t\tdocument.getElementById(\"anim-cat-camera\").addEventListener(\"change\", e => { ANIM_SETTINGS.animCategories.camera = e.target.checked; applyAnimZoneSettings(); });\n\t\tdocument.getElementById(\"anim-cat-doorbell\").addEventListener(\"change\", e => { ANIM_SETTINGS.animCategories.doorbell = e.target.checked; applyAnimZoneSettings(); });\n\t\tdocument.getElementById(\"anim-cat-motion\").addEventListener(\"change\", e => { ANIM_SETTINGS.animCategories.motion = e.target.checked; applyAnimZoneSettings(); });\n\t\tdocument.getElementById(\"anim-cat-smoke\").addEventListener(\"change\", e => { ANIM_SETTINGS.animCategories.smoke = e.target.checked; applyAnimZoneSettings(); });\n\t\tdocument.getElementById(\"anim-cat-wifi\").addEventListener(\"change\", e => { ANIM_SETTINGS.animCategories.wifi = e.target.checked; applyAnimZoneSettings(); });\n\t\tdocument.getElementById(\"anim-cat-sensor\").addEventListener(\"change\", e => { ANIM_SETTINGS.animCategories.sensor = e.target.checked; applyAnimZoneSettings(); });\n\t\tdocument.getElementById(\"anim-cat-sprinkler\").addEventListener(\"change\", e => { ANIM_SETTINGS.animCategories.sprinkler = e.target.checked; applyAnimZoneSettings(); });\n\t\tdocument.getElementById(\"anim-cat-connections\").addEventListener(\"change\", e => { ANIM_SETTINGS.animCategories.connections = e.target.checked; applyAnimZoneSettings(); });\n\t\tdocument.getElementById(\"zone-master\").addEventListener(\"change\", e => { ANIM_SETTINGS.masterZones = e.target.checked; applyAnimZoneSettings(); });\n\t\tdocument.getElementById(\"zone-cat-camera\").addEventListener(\"change\", e => { ANIM_SETTINGS.zoneCategories.camera = e.target.checked; applyAnimZoneSettings(); });\n\t\tdocument.getElementById(\"zone-cat-doorbell\").addEventListener(\"change\", e => { ANIM_SETTINGS.zoneCategories.doorbell = e.target.checked; applyAnimZoneSettings(); });\n\t\tdocument.getElementById(\"zone-cat-motion\").addEventListener(\"change\", e => { ANIM_SETTINGS.zoneCategories.motion = e.target.checked; applyAnimZoneSettings(); });\n\t\tdocument.getElementById(\"zone-cat-smoke\").addEventListener(\"change\", e => { ANIM_SETTINGS.zoneCategories.smoke = e.target.checked; applyAnimZoneSettings(); });\n\t\tdocument.getElementById(\"zone-cat-wifi\").addEventListener(\"change\", e => { ANIM_SETTINGS.zoneCategories.wifi = e.target.checked; applyAnimZoneSettings(); });\n\t\tdocument.getElementById(\"zone-cat-sensor\").addEventListener(\"change\", e => { ANIM_SETTINGS.zoneCategories.sensor = e.target.checked; applyAnimZoneSettings(); });\n\t\tdocument.getElementById(\"zone-cat-sprinkler\").addEventListener(\"change\", e => { ANIM_SETTINGS.zoneCategories.sprinkler = e.target.checked; applyAnimZoneSettings(); });\n       document.getElementById(\"animation-style-select\").value = PAGE_STATE.animationStyle || \"arrows\";\n       document.getElementById(\"animation-direction-select\").value = PAGE_STATE.animationDirection || \"all\";\n       document.getElementById(\"animation-speed-select\").value = PAGE_STATE.animationSpeed || 1.5;\n       document.getElementById(\"add-line-routing\").value = PAGE_STATE.defaultEdgeRouting || \"orthogonal\";\n       document.getElementById(\"selection-handle-color\").value = PAGE_STATE.selectionHandle || \"#f59e0b\";\n       document.getElementById(\"selection-handle-size\").value = PAGE_STATE.selectionHandleSize || 8;\n       document.getElementById(\"group-indicator-color\").value = PAGE_STATE.groupIndicator || \"#4fd1c5\";\n       document.getElementById(\"canvas-gradient-top\").value = PAGE_STATE.canvasGradientTop || \"#1e2532\";\n       document.getElementById(\"canvas-gradient-bottom\").value = PAGE_STATE.canvasGradientBottom || \"#050608\";\n       document.getElementById(\"canvas-border-color\").value = PAGE_STATE.canvasBorder || \"#475569\";\n       document.getElementById(\"canvas-grid-color\").value = PAGE_STATE.canvasGrid || \"#475569\";\n       document.getElementById(\"canvas-grid-size\").value = PAGE_STATE.canvasGridSize || 50;\n       document.getElementById(\"canvas-grid-enabled\").checked = PAGE_STATE.canvasGridEnabled !== false;\n       document.getElementById(\"rack-frame-fill\").value = PAGE_STATE.rackFrameFill || \"#0f172a\";\n       document.getElementById(\"rack-frame-stroke\").value = PAGE_STATE.rackFrameStroke || \"#4fd1c5\";\n       document.getElementById(\"rack-line-color\").value = PAGE_STATE.rackLineColor || \"#475569\";\n       document.getElementById(\"rack-grid-enabled\").checked = PAGE_STATE.rackGridEnabled !== false;\n       document.getElementById(\"rack-text-color\").value = PAGE_STATE.rackTextColor || \"#4fd1c5\";\n        document.getElementById(\"auto-ping-enabled\").checked = autoPingEnabled;\n        document.getElementById(\"auto-ping-interval\").value = autoPingInterval;\n        document.getElementById(\"auto-ping-settings\").style.display = autoPingEnabled ? 'block' : 'none';\n        document.getElementById(\"view-only-mode\").checked = PAGE_STATE.viewOnly === true;\n        rebuildThemeDropdown();\n        updateDeleteButton();\n        settingsModal.classList.add(\"active\");\n       });\n       settingsClose.addEventListener(\"click\", () => {\n        settingsModal.classList.remove(\"active\");\n       });\n\t   document.getElementById(\"view-only-mode\").addEventListener(\"change\", (e) => {\n       PAGE_STATE.viewOnly = e.target.checked;\n       if (e.target.checked) {\n        document.getElementById(\"node-panel\").style.display = \"none\";\n        document.getElementById(\"edge-panel\").style.display = \"none\";\n        document.getElementById(\"text-panel\").style.display = \"none\";\n        document.getElementById(\"rect-panel\").style.display = \"none\";\n        document.getElementById(\"topology-toolbar\").style.setProperty('display', 'none', 'important');\n        document.getElementById(\"draw-toolbar\").style.setProperty('display', 'none', 'important');\n        currentNodeId = null;\n        currentEdgeId = null;\n        currentRectId = null;\n        currentTextId = null;\n        selectedNodes.clear();\n        selectedRects.clear();\n        selectedTexts.clear();\n        selectedEdges.clear();\n        const bulkToolbar = document.getElementById(\"bulk-toolbar\");\n        const bulkToolbarMobile = document.getElementById(\"bulk-toolbar-mobile\");\n        if (bulkToolbar) bulkToolbar.style.display = \"none\";\n        if (bulkToolbarMobile) bulkToolbarMobile.style.display = \"none\";\n        document.querySelectorAll(\".node-group\").forEach(n => n.classList.remove(\"active\", \"selected\"));\n        document.querySelectorAll(\".edge\").forEach(e => e.classList.remove(\"active\"));\n        document.querySelectorAll(\".rect-group\").forEach(r => r.classList.remove(\"active\"));\n        document.querySelectorAll(\".text-element\").forEach(t => t.classList.remove(\"active\"));\n       }\n       wieldThePower();\n       if (!e.target.checked) {\n        updateDrawToolbarVisibility();\n        updateTopologyToolbarVisibility();\n       }\n       forgeTheTopology();\n      });\n       settingsModal.addEventListener(\"click\", (e) => {\n        if (e.target === settingsModal) {\n         settingsModal.classList.remove(\"active\");\n        }\n       });\n       document.getElementById(\"page-bg-color\").addEventListener(\"input\", (e) => {\n        PAGE_STATE.background = e.target.value;\n        wieldThePower();\n       });\n       document.getElementById(\"topbar-bg-color\").addEventListener(\"input\", (e) => {\n        PAGE_STATE.topbarBg = e.target.value;\n        wieldThePower();\n       });\n       document.getElementById(\"topbar-border-color\").addEventListener(\"input\", (e) => {\n        PAGE_STATE.topbarBorder = e.target.value;\n        wieldThePower();\n       });\nconst THEME_PRESETS = {\n  defaulted: { panel:\"#0b0e13\",panelAlt:\"#10141b\",sidebarBg:\"#10141b\",btnBg:\"#0b0e13\",btnText:\"#e2e8f0\",accent:\"#4fd1c5\",danger:\"#f56565\",textMain:\"#e2e8f0\",textSoft:\"#94a3b8\",topbarBg:\"rgba(9,12,20,0.9)\",topbarBorder:\"#1f2533\",nodeFill:\"#1e293b\",nodeStroke:\"#475569\",nodeTitle:\"#e2e8f0\",nodeSub:\"#94a3b8\",defaultEdge:\"#475569\",canvasGradientTop:\"#1e2532\",canvasGradientBottom:\"#050608\",tagFill:\"#1e293b\",tagText:\"#e2e8f0\",tagBorder:\"#475569\",inputBg:\"#0b0e13\",inputText:\"#e2e8f0\",inputBorder:\"#1f2937\",toolbarBg:\"#0f172a\",toolbarBorder:\"#1f2937\",toolbarText:\"#94a3b8\",toolbarBtnBg:\"#0b0e13\",toolbarBtnText:\"#e2e8f0\",selectionHandle:\"#f59e0b\",groupIndicator:\"#4fd1c5\",minimapDots:\"#94a3b8\",canvasHintBg:\"#0f172a\",canvasHintColor:\"#94a3b8\",canvasBorder:\"#475569\",canvasGrid:\"#475569\",rackFrameFill:\"#0f172a\",rackFrameStroke:\"#4fd1c5\",rackLineColor:\"#475569\",rackTextColor:\"#4fd1c5\" },\n  slate: { panel:\"#1e293b\",panelAlt:\"#334155\",sidebarBg:\"#1e293b\",btnBg:\"#334155\",btnText:\"#f1f5f9\",accent:\"#3b82f6\",danger:\"#ef4444\",textMain:\"#f1f5f9\",textSoft:\"#94a3b8\",topbarBg:\"#0f172a\",topbarBorder:\"#334155\",nodeFill:\"#334155\",nodeStroke:\"#3b82f6\",nodeTitle:\"#f1f5f9\",nodeSub:\"#94a3b8\",defaultEdge:\"#64748b\",canvasGradientTop:\"#1e293b\",canvasGradientBottom:\"#0f172a\",tagFill:\"#1e3a5f\",tagText:\"#93c5fd\",tagBorder:\"#3b82f6\",inputBg:\"#0f172a\",inputText:\"#f1f5f9\",inputBorder:\"#475569\",toolbarBg:\"#2563eb\",toolbarBorder:\"#3b82f6\",toolbarText:\"#ffffff\",toolbarBtnBg:\"#3b82f6\",toolbarBtnText:\"#ffffff\",selectionHandle:\"#f59e0b\",groupIndicator:\"#22d3ee\",minimapDots:\"#64748b\",canvasHintBg:\"#1e293b\",canvasHintColor:\"#94a3b8\",canvasBorder:\"#334155\",canvasGrid:\"#334155\",rackFrameFill:\"#1e293b\",rackFrameStroke:\"#3b82f6\",rackLineColor:\"#475569\",rackTextColor:\"#3b82f6\" },\n  graphite: { panel:\"#1f2937\",panelAlt:\"#374151\",sidebarBg:\"#111827\",btnBg:\"#374151\",btnText:\"#f9fafb\",accent:\"#f59e0b\",danger:\"#ef4444\",textMain:\"#f9fafb\",textSoft:\"#9ca3af\",topbarBg:\"#111827\",topbarBorder:\"#4b5563\",nodeFill:\"#374151\",nodeStroke:\"#f59e0b\",nodeTitle:\"#f9fafb\",nodeSub:\"#9ca3af\",defaultEdge:\"#6b7280\",canvasGradientTop:\"#1f2937\",canvasGradientBottom:\"#111827\",tagFill:\"#44403c\",tagText:\"#fbbf24\",tagBorder:\"#f59e0b\",inputBg:\"#111827\",inputText:\"#f9fafb\",inputBorder:\"#4b5563\",toolbarBg:\"#b45309\",toolbarBorder:\"#f59e0b\",toolbarText:\"#ffffff\",toolbarBtnBg:\"#f59e0b\",toolbarBtnText:\"#000000\",selectionHandle:\"#10b981\",groupIndicator:\"#06b6d4\",minimapDots:\"#6b7280\",canvasHintBg:\"#1f2937\",canvasHintColor:\"#9ca3af\",canvasBorder:\"#4b5563\",canvasGrid:\"#374151\",rackFrameFill:\"#1f2937\",rackFrameStroke:\"#f59e0b\",rackLineColor:\"#4b5563\",rackTextColor:\"#fbbf24\" },\n  frost: { panel:\"#f8fafc\",panelAlt:\"#e2e8f0\",sidebarBg:\"#f1f5f9\",btnBg:\"#e2e8f0\",btnText:\"#1e293b\",accent:\"#1e40af\",danger:\"#dc2626\",textMain:\"#0f172a\",textSoft:\"#475569\",topbarBg:\"#1e40af\",topbarBorder:\"#1e3a8a\",nodeFill:\"#ffffff\",nodeStroke:\"#1e40af\",nodeTitle:\"#0f172a\",nodeSub:\"#475569\",defaultEdge:\"#64748b\",canvasGradientTop:\"#e0e7ef\",canvasGradientBottom:\"#f8fafc\",tagFill:\"#dbeafe\",tagText:\"#1e40af\",tagBorder:\"#3b82f6\",inputBg:\"#ffffff\",inputText:\"#0f172a\",inputBorder:\"#cbd5e1\",toolbarBg:\"#1e40af\",toolbarBorder:\"#1e3a8a\",toolbarText:\"#ffffff\",toolbarBtnBg:\"#3b82f6\",toolbarBtnText:\"#ffffff\",selectionHandle:\"#ea580c\",groupIndicator:\"#059669\",minimapDots:\"#64748b\",canvasHintBg:\"#e2e8f0\",canvasHintColor:\"#475569\",canvasBorder:\"#cbd5e1\",canvasGrid:\"#cbd5e1\",rackFrameFill:\"#f1f5f9\",rackFrameStroke:\"#1e40af\",rackLineColor:\"#94a3b8\",rackTextColor:\"#1e40af\" },\n  synthwave: { panel:\"#87366d\",panelAlt:\"#10141b\",sidebarBg:\"#340934\",btnBg:\"#0b0e13\",btnText:\"#e2e8f0\",accent:\"#4fd1c5\",danger:\"#f56565\",textMain:\"#e2e8f0\",textSoft:\"#94a3b8\",topbarBg:\"#781c67\",topbarBorder:\"#1f2533\",nodeFill:\"#1e293b\",nodeStroke:\"#475569\",nodeTitle:\"#e2e8f0\",nodeSub:\"#94a3b8\",defaultEdge:\"#475569\",canvasGradientTop:\"#1e2532\",canvasGradientBottom:\"#050608\",tagFill:\"#1e293b\",tagText:\"#e2e8f0\",tagBorder:\"#475569\",inputBg:\"#0b0e13\",inputText:\"#e2e8f0\",inputBorder:\"#1f2937\",toolbarBg:\"#b95aed\",toolbarBorder:\"#b800eb\",toolbarText:\"#000000\",toolbarBtnBg:\"#ed01fe\",toolbarBtnText:\"#000000\",selectionHandle:\"#f59e0b\",groupIndicator:\"#4fd1c5\",minimapDots:\"#000000\",canvasHintBg:\"#0f172a\",canvasHintColor:\"#94a3b8\",canvasBorder:\"#000000\",canvasGrid:\"#000000\",rackFrameFill:\"#0f172a\",rackFrameStroke:\"#4fd1c5\",rackLineColor:\"#475569\",rackTextColor:\"#4fd1c5\" },\n  terminal: { panel:\"#000000\",panelAlt:\"#0a0a0a\",sidebarBg:\"#050505\",btnBg:\"#0a0a0a\",btnText:\"#33ff33\",accent:\"#33ff33\",danger:\"#ffaa00\",textMain:\"#33ff33\",textSoft:\"#1a9a1a\",topbarBg:\"#000000\",topbarBorder:\"#33ff33\",nodeFill:\"#0a0a0a\",nodeStroke:\"#33ff33\",nodeTitle:\"#33ff33\",nodeSub:\"#1a9a1a\",defaultEdge:\"#1a9a1a\",canvasGradientTop:\"#0a0f0a\",canvasGradientBottom:\"#000000\",tagFill:\"#0a1a0a\",tagText:\"#33ff33\",tagBorder:\"#33ff33\",inputBg:\"#000000\",inputText:\"#33ff33\",inputBorder:\"#1a9a1a\",toolbarBg:\"#1a9a1a\",toolbarBorder:\"#33ff33\",toolbarText:\"#000000\",toolbarBtnBg:\"#33ff33\",toolbarBtnText:\"#000000\",selectionHandle:\"#ffaa00\",groupIndicator:\"#00ffff\",minimapDots:\"#33ff33\",canvasHintBg:\"#0a0a0a\",canvasHintColor:\"#1a9a1a\",canvasBorder:\"#1a9a1a\",canvasGrid:\"#0f1f0f\",rackFrameFill:\"#050505\",rackFrameStroke:\"#33ff33\",rackLineColor:\"#1a9a1a\",rackTextColor:\"#33ff33\" },\n  dracula: { panel:\"#282a36\",panelAlt:\"#44475a\",sidebarBg:\"#21222c\",btnBg:\"#44475a\",btnText:\"#f8f8f2\",accent:\"#bd93f9\",danger:\"#ff5555\",textMain:\"#f8f8f2\",textSoft:\"#6272a4\",topbarBg:\"#21222c\",topbarBorder:\"#6272a4\",nodeFill:\"#44475a\",nodeStroke:\"#ff79c6\",nodeTitle:\"#f8f8f2\",nodeSub:\"#8be9fd\",defaultEdge:\"#bd93f9\",canvasGradientTop:\"#282a36\",canvasGradientBottom:\"#1a1b23\",tagFill:\"#3d3f4a\",tagText:\"#50fa7b\",tagBorder:\"#50fa7b\",inputBg:\"#21222c\",inputText:\"#f8f8f2\",inputBorder:\"#6272a4\",toolbarBg:\"#6272a4\",toolbarBorder:\"#bd93f9\",toolbarText:\"#f8f8f2\",toolbarBtnBg:\"#bd93f9\",toolbarBtnText:\"#282a36\",selectionHandle:\"#f1fa8c\",groupIndicator:\"#ff79c6\",minimapDots:\"#bd93f9\",canvasHintBg:\"#282a36\",canvasHintColor:\"#6272a4\",canvasBorder:\"#44475a\",canvasGrid:\"#44475a\",rackFrameFill:\"#282a36\",rackFrameStroke:\"#ff79c6\",rackLineColor:\"#6272a4\",rackTextColor:\"#8be9fd\" },\n  cobalt: { panel:\"#002240\",panelAlt:\"#003366\",sidebarBg:\"#001b33\",btnBg:\"#003366\",btnText:\"#ffffff\",accent:\"#ffc600\",danger:\"#ff628c\",textMain:\"#ffffff\",textSoft:\"#8090a0\",topbarBg:\"#001525\",topbarBorder:\"#0088ff\",nodeFill:\"#003366\",nodeStroke:\"#0088ff\",nodeTitle:\"#ffffff\",nodeSub:\"#80ffbb\",defaultEdge:\"#0088ff\",canvasGradientTop:\"#002240\",canvasGradientBottom:\"#00111f\",tagFill:\"#004080\",tagText:\"#ffc600\",tagBorder:\"#0088ff\",inputBg:\"#001525\",inputText:\"#ffffff\",inputBorder:\"#0066cc\",toolbarBg:\"#0066cc\",toolbarBorder:\"#0088ff\",toolbarText:\"#ffffff\",toolbarBtnBg:\"#0088ff\",toolbarBtnText:\"#ffffff\",selectionHandle:\"#ffc600\",groupIndicator:\"#3ad900\",minimapDots:\"#0088ff\",canvasHintBg:\"#002240\",canvasHintColor:\"#8090a0\",canvasBorder:\"#0066cc\",canvasGrid:\"#003366\",rackFrameFill:\"#002240\",rackFrameStroke:\"#0088ff\",rackLineColor:\"#0066cc\",rackTextColor:\"#80ffbb\" },\n  solarized: { panel:\"#073642\",panelAlt:\"#002b36\",sidebarBg:\"#002b36\",btnBg:\"#073642\",btnText:\"#93a1a1\",accent:\"#268bd2\",danger:\"#dc322f\",textMain:\"#93a1a1\",textSoft:\"#657b83\",topbarBg:\"#002b36\",topbarBorder:\"#586e75\",nodeFill:\"#073642\",nodeStroke:\"#2aa198\",nodeTitle:\"#93a1a1\",nodeSub:\"#839496\",defaultEdge:\"#657b83\",canvasGradientTop:\"#073642\",canvasGradientBottom:\"#002b36\",tagFill:\"#0a4050\",tagText:\"#b58900\",tagBorder:\"#b58900\",inputBg:\"#002b36\",inputText:\"#93a1a1\",inputBorder:\"#586e75\",toolbarBg:\"#268bd2\",toolbarBorder:\"#2aa198\",toolbarText:\"#fdf6e3\",toolbarBtnBg:\"#2aa198\",toolbarBtnText:\"#002b36\",selectionHandle:\"#cb4b16\",groupIndicator:\"#d33682\",minimapDots:\"#657b83\",canvasHintBg:\"#073642\",canvasHintColor:\"#657b83\",canvasBorder:\"#586e75\",canvasGrid:\"#094050\",rackFrameFill:\"#002b36\",rackFrameStroke:\"#2aa198\",rackLineColor:\"#586e75\",rackTextColor:\"#859900\" }\n};\ndocument.getElementById(\"theme-preset\").addEventListener(\"change\", function() {\n  updateDeleteButton();\n  var p = THEME_PRESETS[this.value];\n  if (!p && this.value.startsWith(\"mytheme-\")) {\n    var found = savedStyleSets.find(function(s) { return s.id === document.getElementById(\"theme-preset\").value; });\n    if (found) p = found.styles;\n  }\n  if (!p) return;\n  Object.assign(PAGE_STATE, p);\n  document.getElementById(\"panel-color\").value = p.panel;\n  document.getElementById(\"panel-alt-color\").value = p.panelAlt;\n  document.getElementById(\"sidebar-bg-color\").value = p.sidebarBg;\n  document.getElementById(\"btn-bg-color\").value = p.btnBg;\n  document.getElementById(\"btn-text-color\").value = p.btnText;\n  document.getElementById(\"accent-color\").value = p.accent;\n  document.getElementById(\"danger-color\").value = p.danger;\n  document.getElementById(\"text-main-color\").value = p.textMain;\n  document.getElementById(\"text-soft-color\").value = p.textSoft;\n  document.getElementById(\"topbar-border-color\").value = p.topbarBorder;\n  document.getElementById(\"node-fill-color\").value = p.nodeFill;\n  document.getElementById(\"node-stroke-color\").value = p.nodeStroke;\n  document.getElementById(\"node-title-color\").value = p.nodeTitle;\n  document.getElementById(\"node-sub-color\").value = p.nodeSub;\n  document.getElementById(\"default-edge-color\").value = p.defaultEdge;\n  document.getElementById(\"canvas-gradient-top\").value = p.canvasGradientTop;\n  document.getElementById(\"canvas-gradient-bottom\").value = p.canvasGradientBottom;\n  document.getElementById(\"tag-fill-color\").value = p.tagFill;\n  document.getElementById(\"tag-text-color\").value = p.tagText;\n  document.getElementById(\"tag-border-color\").value = p.tagBorder;\n  document.getElementById(\"input-bg-color\").value = p.inputBg;\n  document.getElementById(\"input-text-color\").value = p.inputText;\n  document.getElementById(\"input-border-color\").value = p.inputBorder;\n  document.getElementById(\"toolbar-bg-color\").value = p.toolbarBg;\n  document.getElementById(\"toolbar-border-color\").value = p.toolbarBorder;\n  document.getElementById(\"toolbar-text-color\").value = p.toolbarText;\n  document.getElementById(\"toolbar-btn-bg-color\").value = p.toolbarBtnBg;\n  document.getElementById(\"toolbar-btn-text-color\").value = p.toolbarBtnText;\n  document.getElementById(\"selection-handle-color\").value = p.selectionHandle;\n  document.getElementById(\"group-indicator-color\").value = p.groupIndicator;\n  document.getElementById(\"minimap-dots-color\").value = p.minimapDots;\n  document.getElementById(\"canvas-hint-bg-color\").value = p.canvasHintBg;\n  document.getElementById(\"canvas-hint-text-color\").value = p.canvasHintColor;\n  document.getElementById(\"canvas-border-color\").value = p.canvasBorder;\n  document.getElementById(\"canvas-grid-color\").value = p.canvasGrid;\n  document.getElementById(\"rack-frame-fill\").value = p.rackFrameFill;\n  document.getElementById(\"rack-frame-stroke\").value = p.rackFrameStroke;\n  document.getElementById(\"rack-line-color\").value = p.rackLineColor;\n  document.getElementById(\"rack-text-color\").value = p.rackTextColor;\n  wieldThePower();\n  forgeTheTopology();\n});\ndocument.querySelectorAll('#settings-modal .style-content input, #settings-modal .style-content select').forEach(el => {\n  if (el.id === 'theme-preset') return;\n  el.addEventListener('input', () => document.getElementById('theme-preset').value = '');\n  el.addEventListener('change', () => document.getElementById('theme-preset').value = '');\n});\n       document.getElementById(\"panel-color\").addEventListener(\"input\", (e) => {\n        PAGE_STATE.panel = e.target.value;\n        wieldThePower();\n       });\n       document.getElementById(\"panel-alt-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.panelAlt = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"sidebar-bg-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.sidebarBg = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"btn-bg-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.btnBg = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"btn-text-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.btnText = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"tag-fill-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.tagFill = e.target.value;\n       wieldThePower();\n       forgeTheTopology();\n      });\n      document.getElementById(\"tag-text-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.tagText = e.target.value;\n       wieldThePower();\n       forgeTheTopology();\n      });\n      document.getElementById(\"tag-border-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.tagBorder = e.target.value;\n       wieldThePower();\n       forgeTheTopology();\n      });\n      document.getElementById(\"selection-fill-color\").addEventListener(\"input\", function() {\n       selectionBoxStyle.fillColor = this.value;\n      });\n      document.getElementById(\"selection-fill-opacity\").addEventListener(\"input\", function() {\n       selectionBoxStyle.fillOpacity = parseFloat(this.value);\n       document.getElementById(\"selection-fill-opacity-val\").textContent = Math.round(this.value * 100) + \"%\";\n      });\n      document.getElementById(\"selection-stroke-color\").addEventListener(\"input\", function() {\n       selectionBoxStyle.strokeColor = this.value;\n      });\n      document.getElementById(\"selection-stroke-width\").addEventListener(\"input\", function() {\n       selectionBoxStyle.strokeWidth = parseInt(this.value) || 2;\n      });\n      document.getElementById(\"selection-stroke-style\").addEventListener(\"change\", function() {\n       selectionBoxStyle.strokeDasharray = this.value;\n      });\n      document.getElementById(\"input-bg-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.inputBg = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"input-text-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.inputText = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"input-border-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.inputBorder = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"input-font-family\").addEventListener(\"change\", (e) => {\n       PAGE_STATE.inputFont = e.target.value;\n       wieldThePower();\n      });\n\t  document.getElementById(\"input-font-size\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.inputFontSize = parseInt(e.target.value) || 14;\n       wieldThePower();\n      });\n      document.getElementById(\"toolbar-bg-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.toolbarBg = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"toolbar-border-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.toolbarBorder = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"toolbar-text-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.toolbarText = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"toolbar-btn-bg-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.toolbarBtnBg = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"toolbar-btn-text-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.toolbarBtnText = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"minimap-dots-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.minimapDots = e.target.value;\n       wieldThePower();\n       forgeTheTopology();\n      });\n      document.getElementById(\"canvas-hint-enabled\").addEventListener(\"change\", (e) => {\n       PAGE_STATE.canvasHintEnabled = e.target.checked;\n       wieldThePower();\n      });\n      document.getElementById(\"canvas-hint-bg-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.canvasHintBg = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"canvas-hint-text-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.canvasHintColor = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"canvas-hint-text\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.canvasHintText = e.target.value;\n       wieldThePower();\n      });\n       document.getElementById(\"accent-color\").addEventListener(\"input\", (e) => {\n        PAGE_STATE.accent = e.target.value;\n        wieldThePower();\n       });\n       document.getElementById(\"danger-color\").addEventListener(\"input\", (e) => {\n        PAGE_STATE.danger = e.target.value;\n        wieldThePower();\n       });\n       document.getElementById(\"text-main-color\").addEventListener(\"input\", (e) => {\n        PAGE_STATE.textMain = e.target.value;\n        wieldThePower();\n       });\n       document.getElementById(\"text-soft-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.textSoft = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"node-fill-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.nodeFill = e.target.value;\n       forgeTheTopology();\n      });\n      document.getElementById(\"node-stroke-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.nodeStroke = e.target.value;\n       forgeTheTopology();\n      });\n      document.getElementById(\"node-title-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.nodeTitle = e.target.value;\n       forgeTheTopology();\n      });\n      document.getElementById(\"node-sub-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.nodeSub = e.target.value;\n       forgeTheTopology();\n      });\n      document.getElementById(\"node-title-size\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.nodeTitleSize = parseInt(e.target.value) || 18;\n       forgeTheTopology();\n      });\n      document.getElementById(\"node-sub-size\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.nodeSubSize = parseInt(e.target.value) || 13;\n       forgeTheTopology();\n      });\n      document.getElementById(\"node-font-family\").addEventListener(\"change\", (e) => {\n       PAGE_STATE.nodeFont = e.target.value;\n       forgeTheTopology();\n      });\n      document.getElementById(\"default-edge-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.defaultEdge = e.target.value;\n       document.getElementById(\"add-line-color\").value = e.target.value;\n       forgeTheTopology();\n      });\n      document.getElementById(\"default-edge-routing\").addEventListener(\"change\", (e) => {\n       PAGE_STATE.defaultEdgeRouting = e.target.value;\n       document.getElementById(\"add-line-routing\").value = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"apply-routing-all\").addEventListener(\"click\", () => {\n       const routing = document.getElementById(\"default-edge-routing\").value;\n       if (!confirm(`Apply \"${routing}\" routing to all ${EDGE_DATA.list.length} connections?`)) return;\n       pushUndo(\"apply routing to all\");\n       EDGE_DATA.list.forEach(edge => {\n        edge.routing = routing;\n       });\n       forgeTheTopology();\n      });\n      document.getElementById(\"selection-handle-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.selectionHandle = e.target.value;\n       forgeTheTopology();\n      });\n      document.getElementById(\"selection-handle-size\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.selectionHandleSize = parseInt(e.target.value) || 8;\n       forgeTheTopology();\n      });\n      document.getElementById(\"group-indicator-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.groupIndicator = e.target.value;\n       forgeTheTopology();\n      });\n      document.getElementById(\"canvas-gradient-top\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.canvasGradientTop = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"canvas-gradient-bottom\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.canvasGradientBottom = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"canvas-border-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.canvasBorder = e.target.value;\n       forgeTheTopology();\n      });\n      document.getElementById(\"canvas-grid-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.canvasGrid = e.target.value;\n       forgeTheTopology();\n      });\n      document.getElementById(\"canvas-grid-size\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.canvasGridSize = parseInt(e.target.value) || 50;\n       forgeTheTopology();\n      });\n      document.getElementById(\"canvas-grid-enabled\").addEventListener(\"change\", (e) => {\n       PAGE_STATE.canvasGridEnabled = e.target.checked;\n       forgeTheTopology();\n      });\n      document.getElementById(\"rack-frame-fill\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.rackFrameFill = e.target.value;\n       forgeTheTopology();\n      });\n      document.getElementById(\"rack-frame-stroke\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.rackFrameStroke = e.target.value;\n       forgeTheTopology();\n      });\n      document.getElementById(\"rack-line-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.rackLineColor = e.target.value;\n       forgeTheTopology();\n      });\n      document.getElementById(\"rack-grid-enabled\").addEventListener(\"change\", (e) => {\n       PAGE_STATE.rackGridEnabled = e.target.checked;\n       forgeTheTopology();\n      });\n      document.getElementById(\"rack-text-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.rackTextColor = e.target.value;\n       forgeTheTopology();\n      });\n      (function initializeResizers() {\n         const headerResizer = document.getElementById('header-resizer');\n         const sidebarResizer = document.getElementById('sidebar-resizer');\n         const mobileFooterResizer = document.getElementById('mobile-footer-resizer');\n         let isResizing = false;\n         let currentResizer = null;\n         let startY = 0;\n         let startX = 0;\n         let startHeight = 0;\n         let startWidth = 0;\n         function getClientPos(e) {\n           if (e.touches && e.touches.length > 0) {\n             return { x: e.touches[0].clientX, y: e.touches[0].clientY };\n           }\n           return { x: e.clientX, y: e.clientY };\n         }\n         function startResize(resizer, type, e) {\n           isResizing = true;\n           currentResizer = type;\n           const pos = getClientPos(e);\n           if (type === 'header') {\n             startY = pos.y;\n             startHeight = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--topbar-height'));\n           } else if (type === 'sidebar') {\n             startX = pos.x;\n             startWidth = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--sidebar-width'));\n           } else if (type === 'mobile-footer') {\n             startY = pos.y;\n             const currentVh = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--mobile-footer-height'));\n             startHeight = (currentVh / 100) * window.innerHeight;\n           }\n           resizer.classList.add('resizing');\n           document.body.classList.add('resizing');\n           document.body.style.cursor = (type === 'sidebar') ? 'col-resize' : 'row-resize';\n           e.preventDefault();\n         }\n         if (headerResizer) {\n           headerResizer.addEventListener('mousedown', (e) => startResize(headerResizer, 'header', e));\n           headerResizer.addEventListener('touchstart', (e) => startResize(headerResizer, 'header', e), { passive: false });\n         }\n         if (sidebarResizer) {\n           sidebarResizer.addEventListener('mousedown', (e) => startResize(sidebarResizer, 'sidebar', e));\n           sidebarResizer.addEventListener('touchstart', (e) => startResize(sidebarResizer, 'sidebar', e), { passive: false });\n         }\n         if (mobileFooterResizer) {\n           mobileFooterResizer.addEventListener('mousedown', (e) => startResize(mobileFooterResizer, 'mobile-footer', e));\n           mobileFooterResizer.addEventListener('touchstart', (e) => startResize(mobileFooterResizer, 'mobile-footer', e), { passive: false });\n         }\n         function handleMove(e) {\n           if (!isResizing) return;\n           const pos = getClientPos(e);\n           if (currentResizer === 'header') {\n             const deltaY = pos.y - startY;\n             const newHeight = Math.max(40, Math.min(150, startHeight + deltaY));\n             document.documentElement.style.setProperty('--topbar-height', newHeight + 'px');\n           } else if (currentResizer === 'sidebar') {\n             const deltaX = startX - pos.x;\n             const newWidth = Math.max(200, Math.min(800, startWidth + deltaX));\n             document.documentElement.style.setProperty('--sidebar-width', newWidth + 'px');\n           } else if (currentResizer === 'mobile-footer') {\n             const deltaY = startY - pos.y;\n             const newHeight = startHeight + deltaY;\n             const newVh = Math.max(15, Math.min(80, (newHeight / window.innerHeight) * 100));\n             document.documentElement.style.setProperty('--mobile-footer-height', newVh + 'vh');\n           }\n           e.preventDefault();\n         }\n         document.addEventListener('mousemove', handleMove);\n         document.addEventListener('touchmove', handleMove, { passive: false });\n         function handleEnd() {\n           if (isResizing) {\n             isResizing = false;\n             if (currentResizer === 'header') {\n               PAGE_STATE.topbarHeight = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--topbar-height'));\n               headerResizer.classList.remove('resizing');\n             } else if (currentResizer === 'sidebar') {\n               PAGE_STATE.sidebarWidth = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--sidebar-width'));\n               sidebarResizer.classList.remove('resizing');\n             } else if (currentResizer === 'mobile-footer') {\n               PAGE_STATE.mobileFooterHeight = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--mobile-footer-height'));\n               mobileFooterResizer.classList.remove('resizing');\n             }\n             document.body.classList.remove('resizing');\n             document.body.style.cursor = '';\n             currentResizer = null;\n           }\n         }\n         document.addEventListener('mouseup', handleEnd);\n         document.addEventListener('touchend', handleEnd);\n         document.addEventListener('touchcancel', handleEnd);\n       })();\n       document.getElementById(\"auto-ping-enabled\").addEventListener(\"change\", (e) => {\n        autoPingEnabled = e.target.checked;\n        PAGE_STATE.autoPingEnabled = autoPingEnabled;\n        document.getElementById(\"auto-ping-settings\").style.display = autoPingEnabled ? 'block' : 'none';\n        if (autoPingEnabled) {\n         startAutoPing();\n        } else {\n         stopAutoPing();\n        }\n       });\n       document.getElementById(\"auto-ping-interval\").addEventListener(\"change\", (e) => {\n        const newInterval = parseInt(e.target.value, 10);\n        if (newInterval >= 5 && newInterval <= 3600) {\n         autoPingInterval = newInterval;\n         PAGE_STATE.autoPingInterval = autoPingInterval;\n         if (autoPingEnabled) {\n          startAutoPing();\n         }\n        }\n       });\n       document.getElementById(\"import-data-file\").addEventListener(\"change\", async (e) => {\n        const file = e.target.files[0];\n        if (!file) return;\n        try {\n         const text = await file.text();\n         const data = JSON.parse(text);\n         if (!data.nodeData || !data.edgeData) {\n          alert(\"Invalid data file. Missing required fields.\");\n          return;\n         }\n         const confirmMsg = `This will replace all current data with the imported data.\\n\\nImporting:\\n- ${Object.keys(data.nodeData).length} nodes\\n- ${data.edgeData.list?.length || 0} connections\\n- ${data.documentTabs?.length || 1} tab(s)\\n\\nContinue?`;         if (!confirm(confirmMsg)) {\n          e.target.value = \"\";\n          return;\n         }\n\t\t pushUndo('import json');\n         NODE_DATA = data.nodeData || {};\n         EDGE_DATA = data.edgeData || {\n          list: []\n         };\n         EDGE_LEGEND = data.edgeLegend || {};\n         RECT_DATA = data.rectData || { list: [] };\n         TEXT_DATA = data.textData || { list: [] };\n         savedPositions = data.nodePositions || {};\n         savedSizes = data.nodeSizes || {};\n         savedStyles = data.nodeStyles || {};\n         if (data.page) {\n          PAGE_STATE = Object.assign({}, DEFAULT_PAGE_STATE, data.page);\n          wieldThePower();\n         }\n      if (data.autoPingEnabled !== undefined) {\n      autoPingEnabled = data.autoPingEnabled;\n      PAGE_STATE.autoPingEnabled = autoPingEnabled;\n      document.getElementById(\"auto-ping-enabled\").checked = autoPingEnabled;\n      }\n      if (data.autoPingInterval !== undefined) {\n      autoPingInterval = data.autoPingInterval;\n      PAGE_STATE.autoPingInterval = autoPingInterval;\n      document.getElementById(\"auto-ping-interval\").value = autoPingInterval;\n      }\n         if (data.canvas) {\n          canvasState.zoom = data.canvas.zoom || 1;\n          canvasState.panX = data.canvas.panX || 0;\n          canvasState.panY = data.canvas.panY || 0;\n         }\n         if (data.page?.title) {\n         document.title = data.page.title;\n         document.querySelector(\".editable-page-title\", ).textContent = data.page.title;\n        }\n        if (data.savedStyleSets) {\n          savedStyleSets = data.savedStyleSets;\n        }\n        if (data.documentTabs) {\n         documentTabs = data.documentTabs;\n         currentTabIndex = data.currentTabIndex || 0;\n        }\n        if (data.savedTopologyView) {\n         savedTopologyView = data.savedTopologyView;\n        }\n        if (data.encryptedSections) {\n         encryptedSections = data.encryptedSections;\n        }\n        if (data.auditLog && Array.isArray(data.auditLog)) {\n         auditLog = data.auditLog;\n         try {\n           localStorage.setItem(AUDIT_STORAGE_KEY, JSON.stringify(auditLog));\n         } catch (e) {\n           console.warn(\"Failed to sync audit log to localStorage:\", e);\n         }\n        }\n        Object.values(NODE_DATA).forEach(node => {\n          if (!node.tags) node.tags = [];\n          if (!node.notes) node.notes = [];\n        });\n        EDGE_DATA.list.forEach(edge => {\n          if (!edge.notes) edge.notes = [];\n        });\n        forgeTheTopology();\n        if (typeof forgeTheLegend === 'function') forgeTheLegend();\n        if (typeof updateZoneLegend === 'function') updateZoneLegend();\n        logAuditEvent(\"import\", `Imported JSON: ${file.name} (${Object.keys(data.nodeData).length} nodes, ${data.edgeData.list?.length || 0} connections)`);\n        updateViewBox();\n      if (autoPingEnabled) {\n      startAutoPing();\n      } else {\n      stopAutoPing();\n      }\n         const nodeIds = Object.keys(NODE_DATA);\n         if (nodeIds.length > 0) {\n          claimTheImmortal(nodeIds[0]);\n         } else {\n          document.getElementById(\"node-panel\").style.display = \"none\";\n          document.getElementById(\"edge-panel\").style.display = \"none\";\n          document.getElementById(\"topology-toolbar\").style.display = \"none\";\n         }\n         alert(\"Data imported successfully!\");\n         e.target.value = \"\";\n        } catch (err) {\n         console.error(\"Import error:\", err);\n         alert(`Failed to import data: ${err.message}`);\n         e.target.value = \"\";\n        }\n       });\n       const saveHelpBtn = document.getElementById(\"save-help-btn\");\n       const saveInfoModal = document.getElementById(\"save-info-modal\");\n       const saveInfoClose = document.getElementById(\"save-info-close\");\n       saveHelpBtn.addEventListener(\"click\", () => {\n        saveInfoModal.classList.add(\"active\");\n       });\n       saveInfoClose.addEventListener(\"click\", () => {\n        saveInfoModal.classList.remove(\"active\");\n       });\n       saveInfoModal.addEventListener(\"click\", (e) => {\n       if (e.target === saveInfoModal) {\n        saveInfoModal.classList.remove(\"active\");\n       }\n      });\n      document.querySelectorAll(\".help-tab\").forEach(tab => {\n        tab.addEventListener(\"click\", () => {\n          document.querySelectorAll(\".help-tab\").forEach(t => { t.style.background = \"var(--panel)\"; t.style.color = \"var(--text-main)\"; });\n          tab.style.background = \"var(--accent)\"; tab.style.color = \"var(--bg)\";\n          document.querySelectorAll(\".help-tab-content\").forEach(c => c.style.display = \"none\");\n          document.getElementById(\"help-tab-\" + tab.dataset.tab).style.display = \"block\";\n        });\n      });\n      async function deriveKey(password, salt) {\n      const encoder = new TextEncoder();\n      const keyMaterial = await crypto.subtle.importKey(\n      \"raw\",\n      encoder.encode(password),\n      \"PBKDF2\",\n      false,\n      [\"deriveKey\"]\n      );\n      return crypto.subtle.deriveKey(\n      {\n      name: \"PBKDF2\",\n      salt: salt,\n      iterations: 200000,\n      hash: \"SHA-256\"\n      },\n      keyMaterial,\n      {\n      name: \"AES-GCM\",\n      length: 256\n      },\n      false,\n      [\"encrypt\", \"decrypt\"]\n      );\n      }\n       async function encryptData(data, password) {\n      const encoder = new TextEncoder();\n      const salt = crypto.getRandomValues(new Uint8Array(16));\n      const iv   = crypto.getRandomValues(new Uint8Array(12));\n      const key = await deriveKey(password, salt);\n      const encrypted = await crypto.subtle.encrypt(\n      { name: \"AES-GCM\", iv },\n      key,\n      encoder.encode(data)\n      );\n      const encryptedU8 = new Uint8Array(encrypted);\n      const result = new Uint8Array(salt.length + iv.length + encryptedU8.length);\n      result.set(salt, 0);\n      result.set(iv, salt.length);\n      result.set(encryptedU8, salt.length + iv.length);\n      return \"ENCRYPTED:\" + u8ToBase64(result);\n      }\n       async function decryptData(encryptedData, password) {\n      const base64Data = encryptedData.replace(\"ENCRYPTED:\", \"\");\n      const fullData   = base64ToU8(base64Data);\n      const salt      = fullData.slice(0, 16);\n      const iv        = fullData.slice(16, 28);\n      const encrypted = fullData.slice(28);\n      const key = await deriveKey(password, salt);\n      const decrypted = await crypto.subtle.decrypt(\n      { name: \"AES-GCM\", iv },\n      key,\n      encrypted\n      );\n      const decoder = new TextDecoder();\n      return decoder.decode(decrypted);\n      }\n       function isEncrypted(data) {\n        return typeof data === \"string\" && data.startsWith(\"ENCRYPTED:\");\n       }\n       function captureTheQuickening() {\n      const currentTab = documentTabs[currentTabIndex];\n      currentTab.nodes = JSON.parse(JSON.stringify(NODE_DATA));\n      currentTab.edges = JSON.parse(JSON.stringify(EDGE_DATA));\n      currentTab.positions = JSON.parse(JSON.stringify(savedPositions));\n      currentTab.sizes = JSON.parse(JSON.stringify(savedSizes));\n      currentTab.styles = JSON.parse(JSON.stringify(savedStyles));\n      currentTab.legend = JSON.parse(JSON.stringify(EDGE_LEGEND));\n      currentTab.rects = JSON.parse(JSON.stringify(RECT_DATA));\n      currentTab.texts = JSON.parse(JSON.stringify(TEXT_DATA));\n      currentTab.pageState = JSON.parse(JSON.stringify(PAGE_STATE));\n      return {\n      nodeData: NODE_DATA,\n       edgeData: EDGE_DATA,\n       rectData: RECT_DATA,\n       textData: TEXT_DATA,\n       edgeLegend: EDGE_LEGEND,\n       zoneLegend: ZONE_LEGEND,\n       zonePresets: ZONE_PRESETS,\n       nodePositions: savedPositions,\n       nodeSizes: savedSizes,\n       nodeStyles: savedStyles,\n       iconCache: IconLibrary.iconCache,\n       page: PAGE_STATE,\n       autoPingEnabled: autoPingEnabled,\n       autoPingInterval: autoPingInterval,\n       canvas: {\n         zoom: canvasState.zoom,\n         panX: canvasState.panX,\n         panY: canvasState.panY,\n       },\n       savedTopologyView: savedTopologyView,\n       documentTabs: documentTabs,\n       currentTabIndex: currentTabIndex,\n       encryptedSections: encryptedSections,\n\t   auditLog: auditLog,\n       savedStyleSets: savedStyleSets,\n       };\n      }\n       function assembleTheImmortalForm() {\n\t   const canvasHintEl = document.getElementById(\"canvas-hint\");\n       const savedHintHTML = canvasHintEl ? canvasHintEl.innerHTML : \"\";\n       if (canvasHintEl) canvasHintEl.innerHTML = DEFAULT_CANVAS_HINT;\n       const clone = document.documentElement.cloneNode(true);\n       [\"edge-legend-mini\", \"minimap-mini\", \"draw-toolbar-mini\", \"topology-toolbar-mini\"].forEach(id => {\n        const all = clone.querySelectorAll(\"#\" + id);\n        for (let i = 1; i < all.length; i++) {\n         all[i].remove();\n        }\n       });\n       const addLineSelect = clone.querySelector(\"#add-line-select\");\n       if (addLineSelect) addLineSelect.innerHTML = \"\";\n       const minimapSvg = clone.querySelector(\"#minimap\");\n       if (minimapSvg) {\n        minimapSvg.querySelectorAll(\".minimap-node, .minimap-edge\").forEach(el => el.remove());\n       }\n       const canvasGrid = clone.querySelector(\"#canvas-grid\");\n       if (canvasGrid) canvasGrid.innerHTML = \"\";\n       const mainSvg = clone.querySelector(\"#map\");\n       if (mainSvg) mainSvg.innerHTML = \"\";\n       const nodeScript = clone.querySelector(\"#nodes-json\");\n        if (nodeScript) {\n         nodeScript.textContent = JSON.stringify(NODE_DATA, null, 2);\n        }\n        let stateScript = clone.querySelector(\"#topology-state\");\n        if (!stateScript) {\n         stateScript = document.createElement(\"script\");\n         stateScript.id = \"topology-state\";\n         stateScript.type = \"application/json\";\n         const body = clone.querySelector(\"body\") || clone;\n         body.appendChild(stateScript);\n        }\n        stateScript.textContent = JSON.stringify(captureTheQuickening(), null, 2);\n        return \" <!DOCTYPE html> \\n \" + clone.outerHTML;\n       }\n       async function becomeImmortal() {\n        const encryptEnabled = document.getElementById(\"encrypt-toggle\").checked;\n        let stateData = JSON.stringify(captureTheQuickening(), null, 2);\n        if (encryptEnabled) {\n         const password = prompt(\"Enter a password to encrypt your data:\\n(Remember this password! You will need it to open this file and its non recoverable!)\");\n         if (!password) {\n          alert(\"Encryption cancelled. File not saved.\");\n          return;\n         }\n         const confirmPassword = prompt(\"Confirm your password:\");\n         if (password !== confirmPassword) {\n          alert(\"Passwords do not match. File not saved.\");\n          return;\n         }\n         try {\n          stateData = await encryptData(stateData, password);\n         } catch (e) {\n          alert(\"Encryption failed: \" + e.message);\n          return;\n         }\n        }\n       const canvasHintEl = document.getElementById(\"canvas-hint\");\n       const savedHintHTML = canvasHintEl ? canvasHintEl.innerHTML : \"\";\n       if (canvasHintEl) canvasHintEl.innerHTML = DEFAULT_CANVAS_HINT;\n       const clone = document.documentElement.cloneNode(true);\n       [\"edge-legend-mini\", \"minimap-mini\", \"draw-toolbar-mini\", \"topology-toolbar-mini\"].forEach(id => {\n        const all = clone.querySelectorAll(\"#\" + id);\n        for (let i = 1; i < all.length; i++) {\n         all[i].remove();\n        }\n       });\n       const selectsToClear = [\"#add-line-select\", \"#new-edge-from\", \"#new-edge-to\", \"#assigned-rack-select\"];\n       selectsToClear.forEach(sel => {\n        const el = clone.querySelector(sel);\n        if (el) el.innerHTML = \"\";\n       });\n       const minimapSvg = clone.querySelector(\"#minimap\");\n       if (minimapSvg) minimapSvg.querySelectorAll(\".minimap-node, .minimap-edge\").forEach(el => el.remove());\n       const mainSvg = clone.querySelector(\"#map\");\n       if (mainSvg) mainSvg.innerHTML = \"\";\n       const edgeLegendContent = clone.querySelector(\"#edge-legend-content\");\n       if (edgeLegendContent) edgeLegendContent.innerHTML = \"\";\n       const nodeTags = clone.querySelector(\"#node-tags\");\n       if (nodeTags) nodeTags.innerHTML = \"\";\n       const nodeNotes = clone.querySelector(\"#node-notes\");\n       if (nodeNotes) nodeNotes.innerHTML = \"\";\n       const edgeNotes = clone.querySelector(\"#edge-notes\");\n       if (edgeNotes) edgeNotes.innerHTML = \"\";\n       const tabBar = clone.querySelector(\"#tab-bar\");\n       if (tabBar) tabBar.innerHTML = \"\";\n       const rollbackList = clone.querySelector(\"#rollback-list\");\n       if (rollbackList) rollbackList.innerHTML = \"\";\n       const auditList = clone.querySelector(\"#audit-log-list\");\n       if (auditList) auditList.innerHTML = \"\";\n       clone.querySelectorAll(\".ping-indicator\").forEach(el => el.remove());\n       const nodeScript = clone.querySelector(\"#nodes-json\");\n        if (nodeScript) {\n         if (encryptEnabled) {\n          nodeScript.textContent = JSON.stringify({}, null, 2);\n         } else {\n          nodeScript.textContent = JSON.stringify(NODE_DATA, null, 2);\n         }\n        }\n        let stateScript = clone.querySelector(\"#topology-state\");\n        if (!stateScript) {\n         stateScript = document.createElement(\"script\");\n         stateScript.id = \"topology-state\";\n         stateScript.type = \"application/json\";\n         const body = clone.querySelector(\"body\") || clone;\n         body.appendChild(stateScript);\n        }\n        stateScript.textContent = stateData;\n        const html = \"<!DOCTYPE html> \\n \" + clone.outerHTML;\n        const blob = new Blob([html], {\n         type: \"text/html\"\n        });\n        const url = URL.createObjectURL(blob);\n        const a = document.createElement(\"a\");\n        a.href = url;\n        const safeTitle = (PAGE_STATE.title || document.title || \"network-topology\").toLowerCase().replace(/[^a-z0-9\\-]+/g, \"-\");\n        a.download = safeTitle + \".html\";\n        document.body.appendChild(a);\n        a.click();\n        document.body.removeChild(a);\n        URL.revokeObjectURL(url);\n\t\tif (canvasHintEl) canvasHintEl.innerHTML = savedHintHTML;\n        saveRollbackVersion(\"Manual save\");\n\t\tlogAuditEvent(\"save\", `File saved: ${safeTitle}.html`);\n       }\n      function captureState() {\n\t\t const clone = typeof structuredClone === 'function' \n\t\t   ? (o) => structuredClone(o)\n\t\t   : (o) => JSON.parse(JSON.stringify(o));\n\t\t return {\n\t\t  nodes: clone(NODE_DATA),\n\t\t  edges: clone(EDGE_DATA),\n\t\t  positions: clone(savedPositions),\n\t\t  sizes: clone(savedSizes),\n\t\t  styles: clone(savedStyles),\n\t\t  legend: clone(EDGE_LEGEND),\n\t\t  rects: clone(RECT_DATA),\n\t\t  texts: clone(TEXT_DATA)\n\t\t };\n\t\t}\n      let lastUndoPush = 0;\n\t   function pushUndo(action = \"\") {\n\t   const now = Date.now();\n\t   if (now - lastUndoPush < 100 && undoStack.length > 0) {\n\t    return;\n\t   }\n\t   lastUndoPush = now;\n\t   const state = captureState();\n\t   undoStack.push(state);\n       if (undoStack.length > MAX_UNDO_STACK) {\n        undoStack.shift();\n       }\n       redoStack = [];\n       updateUndoButtons();\n       if (action) {\n        const actionTypeMap = {\n          \"create node\": \"node\",\n          \"delete node\": \"node\",\n          \"add node\": \"node\",\n          \"edit\": \"node\",\n          \"clone node\": \"node\",\n          \"paste node\": \"node\",\n          \"move nodes\": \"node\",\n          \"nudge\": \"node\",\n          \"nudge nodes\": \"node\",\n          \"align nodes\": \"node\",\n          \"distribute nodes\": \"node\",\n          \"snap to grid\": \"node\",\n          \"toggle group\": \"node\",\n          \"toggle lock\": \"node\",\n          \"create rack\": \"rack\",\n          \"add rack\": \"rack\",\n          \"edit rack\": \"rack\",\n          \"edit mac\": \"rack\",\n          \"edit U height\": \"rack\",\n          \"change rack capacity\": \"rack\",\n          \"change assigned rack\": \"rack\",\n          \"add connection\": \"connection\",\n          \"delete connection\": \"connection\",\n          \"delete edge\": \"connection\",\n          \"clone edge\": \"connection\",\n          \"paste edge\": \"connection\",\n          \"style change\": \"style\",\n          \"change layer\": \"layer\",\n          \"add text\": \"text\",\n          \"edit text\": \"text\",\n          \"delete text\": \"text\",\n          \"clone text\": \"text\",\n          \"paste text\": \"text\",\n          \"draw zone\": \"zone\",\n          \"delete zone\": \"zone\",\n          \"delete rect\": \"zone\",\n          \"clone rect\": \"zone\",\n          \"paste rect\": \"zone\",\n          \"change zone line style\": \"zone\",\n          \"delete selected\": \"bulk\",\n          \"clone selected\": \"bulk\",\n        };\n        const type = actionTypeMap[action] || \"edit\";\n        logAuditEvent(type, action);\n       }\n      }\n      function undo() {\n       if (undoStack.length === 0) return;\n       const currentState = captureState();\n       redoStack.push(currentState);\n       const previousState = undoStack.pop();\n       restoreState(previousState);\n       updateUndoButtons();\n       logAuditEvent(\"undo\", \"Undo action performed\");\n      }\n      function redo() {\n       if (redoStack.length === 0) return;\n       logAuditEvent(\"redo\", \"Redo action performed\");\n       const currentState = captureState();\n       undoStack.push(currentState);\n       const nextState = redoStack.pop();\n       restoreState(nextState);\n       updateUndoButtons();\n      }\n      function restoreState(state) {\n      NODE_DATA = state.nodes;\n       EDGE_DATA = state.edges;\n       savedPositions = state.positions;\n       savedSizes = state.sizes;\n       savedStyles = state.styles;\n       EDGE_LEGEND = state.legend;\n       RECT_DATA = state.rects || { list: [] };\n       TEXT_DATA = state.texts || { list: [] };\n       forgeTheTopology();\n       if (currentNodeId && NODE_DATA[currentNodeId]) {\n       claimTheImmortal(currentNodeId);\n       } else if (currentEdgeId) {\n       selectTheConnection(currentEdgeId);\n       }\n      }\n      function updateUndoButtons() {\n       const undoBtn = document.getElementById(\"undo-btn\");\n       const redoBtn = document.getElementById(\"redo-btn\");\n       if (undoBtn) {\n        undoBtn.disabled = undoStack.length === 0;\n        undoBtn.style.opacity = undoStack.length === 0 ? \"0.5\" : \"1\";\n       }\n       if (redoBtn) {\n        redoBtn.disabled = redoStack.length === 0;\n        redoBtn.style.opacity = redoStack.length === 0 ? \"0.5\" : \"1\";\n       }\n      }\n      function clearSelection() {\n       selectedNodes.clear();\n       selectedEdges.clear();\n       selectedRects.clear();\n       selectedTexts.clear();\n       updateAllSelections();\n      }\n      function updateAllSelections() {\n      updateNodeSelection();\n      clearSearchHighlight();\n      document.querySelectorAll(\".edge\").forEach(el => {\n      const edgeId = el.dataset.edgeId;\n      if (selectedEdges.has(edgeId)) {\n      el.style.filter = \"drop-shadow(0 0 8px #4fd1c5)\";\n      } else {\n      el.style.filter = \"\";\n      }\n      });\n      document.querySelectorAll(\".rect-group\").forEach(el => {\n      const rectId = el.dataset.rectId;\n      if (selectedRects.has(rectId)) {\n      el.style.filter = \"drop-shadow(0 0 8px #4fd1c5)\";\n      } else {\n      el.style.filter = \"\";\n      }\n      });\n      document.querySelectorAll(\".text-group\").forEach(el => {\n      const textId = el.dataset.textId;\n      if (selectedTexts.has(textId)) {\n      el.style.filter = \"drop-shadow(0 0 8px #4fd1c5)\";\n      } else {\n      el.style.filter = \"\";\n      }\n      });\n      const total = selectedNodes.size + selectedEdges.size + selectedRects.size + selectedTexts.size;\n      const bulkToolbar = document.getElementById(\"bulk-toolbar\");\n      const bulkToolbarMobile = document.getElementById(\"bulk-toolbar-mobile\");\n      const bulkCount = document.getElementById(\"bulk-count\");\n      const bulkCountMobile = document.getElementById(\"bulk-count-mobile\");\n      if (total > 0) {\n      if (bulkToolbar) bulkToolbar.style.display = \"flex\";\n      if (bulkToolbarMobile) bulkToolbarMobile.style.display = \"flex\";\n      if (bulkCount) bulkCount.textContent = total;\n      if (bulkCountMobile) bulkCountMobile.textContent = total;\n      } else {\n      if (bulkToolbar) bulkToolbar.style.display = \"none\";\n      if (bulkToolbarMobile) bulkToolbarMobile.style.display = \"none\";\n      }\n      }\n      function updateNodeSelection() {\n       if (isViewOnly()) {\n        selectedNodes.clear();\n        selectedRects.clear();\n        selectedTexts.clear();\n        const bulkToolbar = document.getElementById(\"bulk-toolbar\");\n        const bulkToolbarMobile = document.getElementById(\"bulk-toolbar-mobile\");\n        if (bulkToolbar) bulkToolbar.style.display = \"none\";\n        if (bulkToolbarMobile) bulkToolbarMobile.style.display = \"none\";\n        return;\n       }\n       document.querySelectorAll(\".node-group\").forEach(node => {\n        const nodeId = node.dataset.nodeId;\n        if (selectedNodes.has(nodeId)) {\n         node.classList.add(\"selected\");\n        } else {\n         node.classList.remove(\"selected\");\n        }\n       });\n       const bulkToolbar = document.getElementById(\"bulk-toolbar\");\n       const bulkToolbarMobile = document.getElementById(\"bulk-toolbar-mobile\");\n       const bulkCount = document.getElementById(\"bulk-count\");\n       const bulkCountMobile = document.getElementById(\"bulk-count-mobile\");\n       const bulkCountModal = document.getElementById(\"bulk-count-modal\");\n       if (selectedNodes.size > 0) {\n        if (bulkToolbar) bulkToolbar.style.display = \"flex\";\n        if (bulkToolbarMobile) bulkToolbarMobile.style.display = \"flex\";\n        if (bulkCount) bulkCount.textContent = selectedNodes.size;\n        if (bulkCountMobile) bulkCountMobile.textContent = selectedNodes.size;\n        if (bulkCountModal) bulkCountModal.textContent = selectedNodes.size;\n       } else {\n        if (bulkToolbar) bulkToolbar.style.display = \"none\";\n        if (bulkToolbarMobile) bulkToolbarMobile.style.display = \"none\";\n        const modal = document.getElementById(\"bulk-actions-modal\");\n        if (modal) modal.style.display = \"none\";\n       }\n      }\n      function deleteSelected() {\n       const total = selectedNodes.size + selectedEdges.size + selectedRects.size + selectedTexts.size;\n       if (total === 0) return;\n       let nodesInsideRacks = [];\n       selectedNodes.forEach(nodeId => {\n        if (NODE_DATA[nodeId]?.isRack) {\n         Object.entries(NODE_DATA).forEach(([id, n]) => {\n          if (n.assignedRack === nodeId) nodesInsideRacks.push(n.name || id);\n         });\n        }\n       });\n       let message = `Delete ${total} selected item(s)?`;\n       if (nodesInsideRacks.length > 0) {\n        message += `\\n\\nThis will also delete ${nodesInsideRacks.length} node(s) inside rack(s):\\n• ${nodesInsideRacks.join('\\n• ')}`;\n       }\n       challengeTheImmortal(message, () => {\n        pushUndo(\"delete selected\");\n        selectedNodes.forEach(nodeId => {\n         if (NODE_DATA[nodeId]?.isRack) {\n          Object.keys(NODE_DATA).forEach(id => {\n           if (NODE_DATA[id]?.assignedRack === nodeId) {\n            EDGE_DATA.list = EDGE_DATA.list.filter(e => e.from !== id && e.to !== id);\n            delete NODE_DATA[id];\n            delete savedPositions[id];\n            delete savedSizes[id];\n            delete savedStyles[id];\n           }\n          });\n         }\n         delete NODE_DATA[nodeId];\n         delete savedPositions[nodeId];\n         delete savedSizes[nodeId];\n         delete savedStyles[nodeId];\n         EDGE_DATA.list = EDGE_DATA.list.filter(e => e.from !== nodeId && e.to !== nodeId);\n        });\n        selectedEdges.forEach(edgeId => {\n         EDGE_DATA.list = EDGE_DATA.list.filter(e => e.id !== edgeId);\n        });\n        selectedRects.forEach(rectId => {\n         RECT_DATA.list = RECT_DATA.list.filter(r => r.id !== rectId);\n        });\n        selectedTexts.forEach(textId => {\n         TEXT_DATA.list = TEXT_DATA.list.filter(t => t.id !== textId);\n        });\n        clearSelection();\n        forgeTheTopology();\n       });\n      }\n      function startSelection(event) {\n       const svgEl = document.getElementById(\"map\");\n       const pt = svgEl.createSVGPoint();\n       pt.x = event.clientX;\n       pt.y = event.clientY;\n       const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n       isSelecting = true;\n       selectionStart = { x: svgP.x, y: svgP.y };\n       if (!selectionRect || !selectionRect.parentNode) {\n        selectionRect = document.createElementNS(\"http://www.w3.org/2000/svg\", \"rect\");\n        selectionRect.setAttribute(\"fill\", selectionBoxStyle.fillColor);\n        selectionRect.setAttribute(\"fill-opacity\", selectionBoxStyle.fillOpacity);\n        selectionRect.setAttribute(\"stroke\", selectionBoxStyle.strokeColor);\n        selectionRect.setAttribute(\"stroke-width\", selectionBoxStyle.strokeWidth);\n        selectionRect.setAttribute(\"stroke-dasharray\", selectionBoxStyle.strokeDasharray);\n        selectionRect.style.pointerEvents = \"none\";\n        svgEl.appendChild(selectionRect);\n       }\n       clearSelection();\n       preDragSelectedNodes = new Set(selectedNodes);\n       preDragSelectedEdges = new Set(selectedEdges);\n       preDragSelectedRects = new Set(selectedRects);\n       preDragSelectedTexts = new Set(selectedTexts);\n      }\n      function updateSelection(event) {\n       if (!isSelecting || !selectionStart) return;\n       const svgEl = document.getElementById(\"map\");\n       const pt = svgEl.createSVGPoint();\n       pt.x = event.clientX;\n       pt.y = event.clientY;\n       const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n       const x = Math.min(selectionStart.x, svgP.x);\n       const y = Math.min(selectionStart.y, svgP.y);\n       const width = Math.abs(svgP.x - selectionStart.x);\n       const height = Math.abs(svgP.y - selectionStart.y);\n       selectionRect.setAttribute(\"x\", x);\n       selectionRect.setAttribute(\"y\", y);\n       selectionRect.setAttribute(\"width\", width);\n       selectionRect.setAttribute(\"height\", height);\n       selectionRect.style.display = \"block\";\n       const box = { x, y, width, height };\n       Object.entries(savedPositions).forEach(([nodeId, pos]) => {\n        const size = savedSizes[nodeId] || 50;\n        const halfSize = size / 2;\n        const nodeBox = { x: pos.x - halfSize, y: pos.y - halfSize, width: size, height: size };\n        if (boxesIntersect(box, nodeBox)) {\n         selectedNodes.add(nodeId);\n        } else if (!preDragSelectedNodes.has(nodeId)) {\n         selectedNodes.delete(nodeId);\n        }\n       });\n       EDGE_DATA.list.forEach(edge => {\n        if (!edge.points || edge.points.length === 0) return;\n        for (let i = 0; i < edge.points.length - 1; i++) {\n         const p1 = edge.points[i];\n         const p2 = edge.points[i + 1];\n         if (lineIntersectsBox(p1.x, p1.y, p2.x, p2.y, box)) {\n          selectedEdges.add(edge.id);\n          return;\n         }\n        }\n        if (!preDragSelectedEdges.has(edge.id)) {\n         selectedEdges.delete(edge.id);\n        }\n       });\n       RECT_DATA.list.forEach(rect => {\n        const rectBox = { x: rect.x, y: rect.y, width: rect.width, height: rect.height };\n        if (boxesIntersect(box, rectBox)) {\n         selectedRects.add(rect.id);\n        } else if (!preDragSelectedRects.has(rect.id)) {\n         selectedRects.delete(rect.id);\n        }\n       });\n       TEXT_DATA.list.forEach(text => {\n        const fontSize = text.fontSize || 18;\n        const textBox = { x: text.x - 50, y: text.y - fontSize, width: 100, height: fontSize * 1.5 };\n        if (boxesIntersect(box, textBox)) {\n         selectedTexts.add(text.id);\n        } else if (!preDragSelectedTexts.has(text.id)) {\n         selectedTexts.delete(text.id);\n        }\n       });\n       updateAllSelectionVisuals();\n      }\n      function endSelection() {\n       isSelecting = false;\n       selectionStart = null;\n       if (selectionRect) {\n        selectionRect.style.display = \"none\";\n       }\n      }\n      function boxesIntersect(a, b) {\n       return !(a.x + a.width < b.x || b.x + b.width < a.x || a.y + a.height < b.y || b.y + b.height < a.y);\n      }\n      function lineIntersectsBox(x1, y1, x2, y2, box) {\n       if (pointInBox(x1, y1, box) || pointInBox(x2, y2, box)) return true;\n       const lines = [\n        [box.x, box.y, box.x + box.width, box.y],\n        [box.x + box.width, box.y, box.x + box.width, box.y + box.height],\n        [box.x + box.width, box.y + box.height, box.x, box.y + box.height],\n        [box.x, box.y + box.height, box.x, box.y]\n       ];\n       for (const [bx1, by1, bx2, by2] of lines) {\n        if (linesIntersect(x1, y1, x2, y2, bx1, by1, bx2, by2)) return true;\n       }\n       return false;\n      }\n      function pointInBox(x, y, box) {\n       return x >= box.x && x <= box.x + box.width && y >= box.y && y <= box.y + box.height;\n      }\n      function linesIntersect(x1, y1, x2, y2, x3, y3, x4, y4) {\n       const denom = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1);\n       if (Math.abs(denom) < 0.0001) return false;\n       const ua = ((x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)) / denom;\n       const ub = ((x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)) / denom;\n       return ua >= 0 && ua <= 1 && ub >= 0 && ub <= 1;\n      }\n      function updateAllSelectionVisuals() {\n       document.querySelectorAll(\".node-group\").forEach(node => {\n        const nodeId = node.dataset.nodeId;\n        if (selectedNodes.has(nodeId)) {\n         node.classList.add(\"selected\");\n        } else {\n         node.classList.remove(\"selected\");\n        }\n       });\n       document.querySelectorAll(\".edge\").forEach(edge => {\n        const edgeId = edge.dataset.edgeId;\n        if (selectedEdges.has(edgeId)) {\n         edge.style.filter = \"drop-shadow(0 0 6px #4fd1c5)\";\n        } else {\n         edge.style.filter = \"\";\n        }\n       });\n       document.querySelectorAll(\".rect-group rect\").forEach(el => {\n        const rectId = el.closest(\".rect-group\")?.dataset?.rectId;\n        if (selectedRects.has(rectId)) {\n         el.style.filter = \"drop-shadow(0 0 8px #4fd1c5)\";\n        } else {\n         el.style.filter = \"\";\n        }\n       });\n       document.querySelectorAll(\".text-group, .text-element\").forEach(el => {\n        const textId = el.dataset?.textId;\n        if (selectedTexts.has(textId)) {\n         el.style.filter = \"drop-shadow(0 0 8px #4fd1c5)\";\n        } else {\n         el.style.filter = \"\";\n        }\n       });\n       const total = selectedNodes.size + selectedEdges.size + selectedRects.size + selectedTexts.size;\n       const bulkToolbar = document.getElementById(\"bulk-toolbar\");\n       const bulkToolbarMobile = document.getElementById(\"bulk-toolbar-mobile\");\n       const bulkCount = document.getElementById(\"bulk-count\");\n       const bulkCountMobile = document.getElementById(\"bulk-count-mobile\");\n       if (total > 0) {\n        if (bulkToolbar) bulkToolbar.style.display = \"flex\";\n        if (bulkToolbarMobile) bulkToolbarMobile.style.display = \"flex\";\n        if (bulkCount) bulkCount.textContent = total;\n        if (bulkCountMobile) bulkCountMobile.textContent = total;\n       } else {\n        if (bulkToolbar) bulkToolbar.style.display = \"none\";\n        if (bulkToolbarMobile) bulkToolbarMobile.style.display = \"none\";\n       }\n      }\n      function cloneNode(sourceId, skipUndo) {\n       const source = NODE_DATA[sourceId];\n       if (!source) return;\n       if (!skipUndo) pushUndo(\"clone node\");\n       let baseName = source.name;\n       let copyNum = 0;\n       let newName = baseName + \" copy\";\n       while (Object.values(NODE_DATA).some(n => n.name === newName)) {\n        copyNum++;\n        newName = baseName + \" copy \" + copyNum;\n       }\n       const newId = newName.toLowerCase().replace(/[^a-z0-9]+/g, \"-\");\n       NODE_DATA[newId] = {\n        shape: source.shape,\n        name: newName,\n        ip: source.ip,\n        role: source.role,\n        tags: [...source.tags],\n        notes: [...source.notes],\n        mac: source.mac || \"\",\n        rackUnit: source.rackUnit || \"\",\n        uHeight: source.uHeight || \"1\",\n        ping: source.ping ? JSON.parse(JSON.stringify(source.ping)) : {\n         enabled: false,\n         protocol: 'http',\n         customUrl: '',\n         timeout: 3000,\n         status: 'unknown',\n         lastCheck: null\n        },\n        layer: source.layer || \"physical\",\n        assignedRack: source.assignedRack || \"\",\n        rackCapacity: source.rackCapacity || \"42\",\n        isRack: source.isRack || false,\n        fovEnabled: source.fovEnabled || false,\n        fovAngle: source.fovAngle || 90,\n        fovDistance: source.fovDistance || 150,\n        fovInnerRadius: source.fovInnerRadius || 0,\n        fovRotation: source.fovRotation || 0,\n        fovColor: source.fovColor || \"#f59e0b\",\n        fovOpacity: source.fovOpacity || 20,\n        fovGradient: source.fovGradient || false,\n        fovBorderColor: source.fovBorderColor || \"#f59e0b\",\n        fovBorderWidth: source.fovBorderWidth !== undefined ? source.fovBorderWidth : 2,\n        fovBorderStyle: source.fovBorderStyle || \"solid\",\n        fovBorderOpacity: source.fovBorderOpacity !== undefined ? source.fovBorderOpacity : 100,\n        fovLabel: source.fovLabel || \"\",\n        fovLabelPosition: source.fovLabelPosition || \"center\",\n        fovLabelSize: source.fovLabelSize || 14,\n        fovLabelColor: source.fovLabelColor || \"#ffffff\",\n        fovLabelBold: source.fovLabelBold || false,\n        fovLabelBg: source.fovLabelBg || false,\n        fovLabelBgColor: source.fovLabelBgColor || \"#000000\",\n        fovLabelOffsetX: source.fovLabelOffsetX || 0,\n        fovLabelOffsetY: source.fovLabelOffsetY || 0,\n        fovAnimate: source.fovAnimate || false,\n        fovAnimationType: source.fovAnimationType || \"sweep\",\n        fovSweep: source.fovSweep || 120,\n        fovSpeed: source.fovSpeed || 4\n       };\n       if (source.isRack) {\n        const childNodes = Object.entries(NODE_DATA).filter(([id, n]) =>\n         id !== newId && n.assignedRack === sourceId\n        );\n        childNodes.forEach(([childId, childNode]) => {\n         let childBaseName = childNode.name;\n         let c = 0;\n         let childNewName = childBaseName + \" copy\";\n         while (Object.values(NODE_DATA).some(n => n.name === childNewName)) {\n          c++;\n          childNewName = childBaseName + \" copy \" + c;\n         }\n         const childNewId = childNewName.toLowerCase().replace(/[^a-z0-9]+/g, \"-\");\n         NODE_DATA[childNewId] = {\n          ...JSON.parse(JSON.stringify(childNode)),\n          name: childNewName,\n          assignedRack: newId\n         };\n         if (savedPositions[childId]) {\n          savedPositions[childNewId] = { ...savedPositions[childId] };\n         }\n         if (savedSizes[childId]) {\n          savedSizes[childNewId] = savedSizes[childId];\n         }\n         if (savedStyles[childId]) {\n          savedStyles[childNewId] = JSON.parse(JSON.stringify(savedStyles[childId]));\n         }\n        });\n       }\n       if (savedSizes[sourceId]) {\n        savedSizes[newId] = savedSizes[sourceId];\n       }\n       if (savedStyles[sourceId]) {\n        savedStyles[newId] = JSON.parse(JSON.stringify(savedStyles[sourceId]));\n       }\n      if (currentView.mode === \"rack\" && currentView.rackId) {\n        NODE_DATA[newId].assignedRack = currentView.rackId;\n       }\n       const sourcePos = savedPositions[sourceId];\n       savedPositions[newId] = {\n        x: sourcePos.x + 100,\n        y: sourcePos.y + 100\n       };\n       forgeTheTopology();\n       claimTheImmortal(newId);\n      }\n      function alignSelectedNodes(direction) {\n       if (selectedNodes.size < 2) return;\n       pushUndo(\"align nodes\");\n       const nodeIds = Array.from(selectedNodes);\n       const positions = nodeIds.map(id => ({ id, pos: savedPositions[id] }));\n       switch(direction) {\n        case \"left\":\n         const minX = Math.min(...positions.map(p => p.pos.x));\n         positions.forEach(p => savedPositions[p.id].x = minX);\n         break;\n        case \"right\":\n         const maxX = Math.max(...positions.map(p => p.pos.x));\n         positions.forEach(p => savedPositions[p.id].x = maxX);\n         break;\n        case \"top\":\n         const minY = Math.min(...positions.map(p => p.pos.y));\n         positions.forEach(p => savedPositions[p.id].y = minY);\n         break;\n        case \"bottom\":\n         const maxY = Math.max(...positions.map(p => p.pos.y));\n         positions.forEach(p => savedPositions[p.id].y = maxY);\n         break;\n        case \"center-h\":\n         const avgX = positions.reduce((sum, p) => sum + p.pos.x, 0) / positions.length;\n         positions.forEach(p => savedPositions[p.id].x = avgX);\n         break;\n        case \"center-v\":\n         const avgY = positions.reduce((sum, p) => sum + p.pos.y, 0) / positions.length;\n         positions.forEach(p => savedPositions[p.id].y = avgY);\n         break;\n       }\n       forgeTheTopology();\n      }\n      function distributeSelectedNodes(direction) {\n       if (selectedNodes.size < 3) return;\n       pushUndo(\"distribute nodes\");\n       const nodeIds = Array.from(selectedNodes);\n       const positions = nodeIds.map(id => ({ id, pos: savedPositions[id] }));\n       if (direction === \"horizontal\") {\n        positions.sort((a, b) => a.pos.x - b.pos.x);\n        const minX = positions[0].pos.x;\n        const maxX = positions[positions.length - 1].pos.x;\n        const gap = (maxX - minX) / (positions.length - 1);\n        positions.forEach((p, i) => {\n         savedPositions[p.id].x = minX + (gap * i);\n        });\n       } else if (direction === \"vertical\") {\n        positions.sort((a, b) => a.pos.y - b.pos.y);\n        const minY = positions[0].pos.y;\n        const maxY = positions[positions.length - 1].pos.y;\n        const gap = (maxY - minY) / (positions.length - 1);\n        positions.forEach((p, i) => {\n         savedPositions[p.id].y = minY + (gap * i);\n        });\n       }\n       forgeTheTopology();\n      }\n      function snapToGrid(nodeId, gridSize = 50) {\n       if (!savedPositions[nodeId]) return;\n       pushUndo(\"snap to grid\");\n       const pos = savedPositions[nodeId];\n       pos.x = Math.round(pos.x / gridSize) * gridSize;\n       pos.y = Math.round(pos.y / gridSize) * gridSize;\n       forgeTheTopology();\n      }\n      function nudgeSelectedNodes(direction, distance) {\n        const nodesToNudge = selectedNodes.size > 0\n          ? Array.from(selectedNodes)\n          : (currentNodeId ? [currentNodeId] : []);\n        if (nodesToNudge.length === 0) return;\n        const unlockedNodes = nodesToNudge.filter(id => !NODE_DATA[id]?.locked);\n        if (unlockedNodes.length === 0) {\n          return;\n        }\n        pushUndo(\"nudge nodes\");\n        unlockedNodes.forEach(id => {\n          if (!savedPositions[id]) {\n            savedPositions[id] = { x: 0, y: 0 };\n          }\n          switch(direction) {\n            case \"ArrowUp\":\n              savedPositions[id].y -= distance;\n              break;\n            case \"ArrowDown\":\n              savedPositions[id].y += distance;\n              break;\n            case \"ArrowLeft\":\n              savedPositions[id].x -= distance;\n              break;\n            case \"ArrowRight\":\n              savedPositions[id].x += distance;\n              break;\n          }\n        });\n        forgeTheTopology();\n      }\n      function cycleNodes(reverse = false) {\n        const nodeIds = Object.keys(NODE_DATA).filter(id => {\n          if (currentView.mode === \"rack\") {\n           const node = NODE_DATA[id];\n           return node && node.assignedRack === currentView.rackId;\n          }\n          return isNodeVisible(id);\n         });\n        if (nodeIds.length === 0) return;\n        let currentIndex = nodeIds.indexOf(currentNodeId);\n        if (reverse) {\n          currentIndex = currentIndex <= 0 ? nodeIds.length - 1 : currentIndex - 1;\n        } else {\n          currentIndex = currentIndex >= nodeIds.length - 1 ? 0 : currentIndex + 1;\n        }\n        const nextNodeId = nodeIds[currentIndex];\n        claimTheImmortal(nextNodeId);\n        selectedNodes.clear();\n        updateNodeSelection();\n      }\n      function focusOnSelected() {\n        let minX = Infinity, minY = Infinity;\n        let maxX = -Infinity, maxY = -Infinity;\n        let hasItems = false;\n        const nodesToFocus = selectedNodes.size > 0\n          ? Array.from(selectedNodes)\n          : (currentNodeId ? [currentNodeId] : []);\n        nodesToFocus.forEach(id => {\n          const pos = savedPositions[id];\n          if (pos) {\n            hasItems = true;\n            const size = savedSizes[id] || 50;\n            minX = Math.min(minX, pos.x - size/2);\n            minY = Math.min(minY, pos.y - size/2);\n            maxX = Math.max(maxX, pos.x + size/2);\n            maxY = Math.max(maxY, pos.y + size/2);\n          }\n        });\n        const rectsToFocus = selectedRects.size > 0 ? Array.from(selectedRects) : (currentRectId ? [currentRectId] : []);\n        rectsToFocus.forEach(id => {\n          const r = RECT_DATA.list.find(x => x.id === id);\n          if (r) {\n            hasItems = true;\n            minX = Math.min(minX, r.x);\n            minY = Math.min(minY, r.y);\n            maxX = Math.max(maxX, r.x + r.width);\n            maxY = Math.max(maxY, r.y + r.height);\n          }\n        });\n        const textsToFocus = selectedTexts.size > 0 ? Array.from(selectedTexts) : (currentTextId ? [currentTextId] : []);\n        textsToFocus.forEach(id => {\n          const t = TEXT_DATA.list.find(x => x.id === id);\n          if (t) {\n            hasItems = true;\n            minX = Math.min(minX, t.x - 50);\n            minY = Math.min(minY, t.y - 20);\n            maxX = Math.max(maxX, t.x + 50);\n            maxY = Math.max(maxY, t.y + 20);\n          }\n        });\n        const edgesToFocus = selectedEdges.size > 0 ? Array.from(selectedEdges) : (currentEdgeId ? [currentEdgeId] : []);\n        edgesToFocus.forEach(id => {\n          const e = EDGE_DATA.list.find(x => x.id === id);\n          if (e) {\n            const fromPos = savedPositions[e.from];\n            const toPos = savedPositions[e.to];\n            if (fromPos && toPos) {\n              hasItems = true;\n              minX = Math.min(minX, fromPos.x, toPos.x);\n              minY = Math.min(minY, fromPos.y, toPos.y);\n              maxX = Math.max(maxX, fromPos.x, toPos.x);\n              maxY = Math.max(maxY, fromPos.y, toPos.y);\n            }\n          }\n        });\n        if (!hasItems || !isFinite(minX)) return;\n        const padding = 100;\n        const centerX = (minX + maxX) / 2;\n        const centerY = (minY + maxY) / 2;\n        const width = maxX - minX + padding * 2;\n        const height = maxY - minY + padding * 2;\n        const zoomX = CANVAS_WIDTH / width;\n        const zoomY = CANVAS_HEIGHT / height;\n        const targetZoom = Math.min(zoomX, zoomY, 2);\n        canvasState.zoom = targetZoom;\n        canvasState.panX = centerX - (CANVAS_WIDTH / targetZoom) / 2;\n        canvasState.panY = centerY - (CANVAS_HEIGHT / targetZoom) / 2;\n        updateViewBox();\n      }\n      function toggleLockSelected() {\n        const nodesToToggle = selectedNodes.size > 0\n          ? Array.from(selectedNodes)\n          : (currentNodeId ? [currentNodeId] : []);\n        const rectsToToggle = selectedRects.size > 0\n          ? Array.from(selectedRects)\n          : (currentRectId ? [currentRectId] : []);\n        const textsToToggle = selectedTexts.size > 0\n          ? Array.from(selectedTexts)\n          : (currentTextId ? [currentTextId] : []);\n        if (nodesToToggle.length === 0 && rectsToToggle.length === 0 && textsToToggle.length === 0) return;\n        pushUndo(\"toggle lock\");\n        let hasUnlocked = nodesToToggle.some(id => !NODE_DATA[id]?.locked);\n        hasUnlocked = hasUnlocked || rectsToToggle.some(id => { const r = RECT_DATA.list.find(x => x.id === id); return r && !r.locked; });\n        hasUnlocked = hasUnlocked || textsToToggle.some(id => { const t = TEXT_DATA.list.find(x => x.id === id); return t && !t.locked; });\n        nodesToToggle.forEach(id => {\n          if (NODE_DATA[id]) NODE_DATA[id].locked = hasUnlocked;\n        });\n        rectsToToggle.forEach(id => {\n          const r = RECT_DATA.list.find(x => x.id === id);\n          if (r) r.locked = hasUnlocked;\n        });\n        textsToToggle.forEach(id => {\n          const t = TEXT_DATA.list.find(x => x.id === id);\n          if (t) t.locked = hasUnlocked;\n        });\n        forgeTheTopology();\n      }\n      function toggleGroupSelected() {\n      const nodesToGroup = Array.from(selectedNodes);\n      const rectsToGroup = Array.from(selectedRects);\n      const textsToGroup = Array.from(selectedTexts);\n      const edgesToGroup = Array.from(selectedEdges);\n      const totalItems = nodesToGroup.length + rectsToGroup.length + textsToGroup.length + edgesToGroup.length;\n      if (totalItems < 2) return;\n      pushUndo(\"toggle group\");\n      const allGroupIds = [];\n      nodesToGroup.forEach(id => { if (NODE_DATA[id]?.groupId) allGroupIds.push(NODE_DATA[id].groupId); });\n      rectsToGroup.forEach(id => { const r = RECT_DATA.list.find(x => x.id === id); if (r?.groupId) allGroupIds.push(r.groupId); });\n      textsToGroup.forEach(id => { const t = TEXT_DATA.list.find(x => x.id === id); if (t?.groupId) allGroupIds.push(t.groupId); });\n      edgesToGroup.forEach(id => { const e = EDGE_DATA.list.find(x => x.id === id); if (e?.groupId) allGroupIds.push(e.groupId); });\n      const uniqueGroups = [...new Set(allGroupIds)];\n      if (uniqueGroups.length === 1 && allGroupIds.length === totalItems) {\n      nodesToGroup.forEach(id => { if (NODE_DATA[id]) NODE_DATA[id].groupId = null; });\n      rectsToGroup.forEach(id => { const r = RECT_DATA.list.find(x => x.id === id); if (r) r.groupId = null; });\n      textsToGroup.forEach(id => { const t = TEXT_DATA.list.find(x => x.id === id); if (t) t.groupId = null; });\n      edgesToGroup.forEach(id => { const e = EDGE_DATA.list.find(x => x.id === id); if (e) e.groupId = null; });\n      } else {\n      const newGroupId = \"group-\" + Date.now();\n      nodesToGroup.forEach(id => { if (NODE_DATA[id]) NODE_DATA[id].groupId = newGroupId; });\n      rectsToGroup.forEach(id => { const r = RECT_DATA.list.find(x => x.id === id); if (r) r.groupId = newGroupId; });\n      textsToGroup.forEach(id => { const t = TEXT_DATA.list.find(x => x.id === id); if (t) t.groupId = newGroupId; });\n      edgesToGroup.forEach(id => { const e = EDGE_DATA.list.find(x => x.id === id); if (e) e.groupId = newGroupId; });\n      }\n      forgeTheTopology();\n      }\n      function handleKeyDown(event) {\n       if (event.metaKey && !event.ctrlKey) event.ctrlKey = true;\n       if (event.target.tagName === \"INPUT\" || event.target.tagName === \"TEXTAREA\" || event.target.isContentEditable) {\n        return;\n       }\n       if ([\"ArrowUp\", \"ArrowDown\", \"ArrowLeft\", \"ArrowRight\"].includes(event.key)) {\n        event.preventDefault();\n        const distance = event.shiftKey ? 10 : 1;\n        nudgeSelectedNodes(event.key, distance);\n       }\n       if (event.key === \"Tab\") {\n        event.preventDefault();\n        cycleNodes(event.shiftKey);\n       }\n       if (event.key === \"f\" || event.key === \"F\") {\n        event.preventDefault();\n        focusOnSelected();\n       }\n       if (event.key === \"l\" || event.key === \"L\") {\n        event.preventDefault();\n        toggleLockSelected();\n       }\n       if (event.key === \"g\" || event.key === \"G\") {\n        event.preventDefault();\n        toggleGroupSelected();\n       }\n       if (event.ctrlKey && event.key === \"z\" && !event.shiftKey) {\n        event.preventDefault();\n        undo();\n       }\n       if ((event.ctrlKey && event.key === \"y\") || (event.ctrlKey && event.shiftKey && event.key === \"z\")) {\n        event.preventDefault();\n        redo();\n       }\n       if (event.ctrlKey && event.key === \"c\") {\n        event.preventDefault();\n        clipboard = null;\n        if (currentNodeId && NODE_DATA[currentNodeId]) {\n         clipboard = {\n          type: \"node\",\n          data: JSON.parse(JSON.stringify(NODE_DATA[currentNodeId])),\n          size: savedSizes[currentNodeId],\n          style: savedStyles[currentNodeId] ? JSON.parse(JSON.stringify(savedStyles[currentNodeId])) : null\n         };\n        } else if (currentEdgeId) {\n         const edge = EDGE_DATA.list.find(e => e.id === currentEdgeId);\n         if (edge) clipboard = { type: \"edge\", data: JSON.parse(JSON.stringify(edge)) };\n        } else if (currentRectId) {\n         const rect = RECT_DATA.list.find(r => r.id === currentRectId);\n         if (rect) clipboard = { type: \"rect\", data: JSON.parse(JSON.stringify(rect)) };\n        } else if (currentTextId) {\n         const text = TEXT_DATA.list.find(t => t.id === currentTextId);\n         if (text) clipboard = { type: \"text\", data: JSON.parse(JSON.stringify(text)) };\n        }\n       }\n       if (event.ctrlKey && event.key === \"v\") {\n        event.preventDefault();\n        if (!clipboard) return;\n        const svgEl = document.getElementById(\"map\");\n        const rect = svgEl.getBoundingClientRect();\n        const pt = svgEl.createSVGPoint();\n        pt.x = rect.left + rect.width / 2;\n        pt.y = rect.top + rect.height / 2;\n        const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n        const centerX = svgP.x;\n        const centerY = svgP.y;\n        if (clipboard.type === \"node\") {\n         let newName = clipboard.data.name + \" copy\";\n         let counter = 1;\n         while (Object.values(NODE_DATA).some(n => n.name === newName)) {\n          newName = clipboard.data.name + \" copy \" + counter;\n          counter++;\n         }\n         let newId = newName.toLowerCase().replace(/[^a-z0-9]+/g, \"-\");\n         counter = 1;\n         while (NODE_DATA[newId]) {\n          newId = newName.toLowerCase().replace(/[^a-z0-9]+/g, \"-\") + \"-\" + counter;\n          counter++;\n         }\n         pushUndo(\"paste node\");\n         NODE_DATA[newId] = { ...JSON.parse(JSON.stringify(clipboard.data)), name: newName };\n         savedPositions[newId] = { x: centerX, y: centerY };\n         if (clipboard.size) savedSizes[newId] = clipboard.size;\n         if (clipboard.style) savedStyles[newId] = JSON.parse(JSON.stringify(clipboard.style));\n         forgeTheTopology();\n         claimTheImmortal(newId);\n        } else if (clipboard.type === \"edge\") {\n         pushUndo(\"paste edge\");\n         const newEdge = { ...clipboard.data, id: \"edge-\" + Date.now() };\n         EDGE_DATA.list.push(newEdge);\n         forgeTheTopology();\n         selectTheConnection(newEdge.id);\n        } else if (clipboard.type === \"rect\") {\n         pushUndo(\"paste rect\");\n         const newRect = { ...clipboard.data, id: \"rect-\" + Date.now(), x: centerX - (clipboard.data.width || 100) / 2, y: centerY - (clipboard.data.height || 100) / 2 };\n         RECT_DATA.list.push(newRect);\n         forgeTheTopology();\n         selectTheRect(newRect.id);\n        } else if (clipboard.type === \"text\") {\n         pushUndo(\"paste text\");\n         const newText = { ...clipboard.data, id: \"text-\" + Date.now(), x: centerX, y: centerY };\n         TEXT_DATA.list.push(newText);\n         forgeTheTopology();\n         showTextPanel(newText.id);\n        }\n       }\n       if (event.ctrlKey && event.key === \"d\") {\n        event.preventDefault();\n        if (currentNodeId && NODE_DATA[currentNodeId]) {\n         cloneNode(currentNodeId);\n        } else if (currentEdgeId) {\n         const edge = EDGE_DATA.list.find(e => e.id === currentEdgeId);\n         if (edge) {\n          pushUndo(\"clone edge\");\n          const newEdge = { ...JSON.parse(JSON.stringify(edge)), id: \"edge-\" + Date.now() };\n          EDGE_DATA.list.push(newEdge);\n          forgeTheTopology();\n          selectTheConnection(newEdge.id);\n         }\n        } else if (currentRectId) {\n         const rect = RECT_DATA.list.find(r => r.id === currentRectId);\n         if (rect) {\n          pushUndo(\"clone rect\");\n          const newRect = { ...JSON.parse(JSON.stringify(rect)), id: \"rect-\" + Date.now(), x: rect.x + 50, y: rect.y + 50 };\n          RECT_DATA.list.push(newRect);\n          forgeTheTopology();\n          selectTheRect(newRect.id);\n         }\n        } else if (currentTextId) {\n         const text = TEXT_DATA.list.find(t => t.id === currentTextId);\n         if (text) {\n          pushUndo(\"clone text\");\n          const newText = { ...JSON.parse(JSON.stringify(text)), id: \"text-\" + Date.now(), x: text.x + 50, y: text.y + 50 };\n          TEXT_DATA.list.push(newText);\n          forgeTheTopology();\n          showTextPanel(newText.id);\n         }\n        }\n       }\n       if (event.key === \"Delete\") {\n        event.preventDefault();\n        const totalSelected = selectedNodes.size + selectedEdges.size + selectedRects.size + selectedTexts.size;\n        if (totalSelected > 0) {\n         deleteSelected();\n        } else if (currentNodeId && NODE_DATA[currentNodeId]) {\n         challengeTheImmortal(`Delete node \"${NODE_DATA[currentNodeId].name}\"?`, () => {\n          pushUndo(\"delete node\");\n          delete NODE_DATA[currentNodeId];\n          delete savedPositions[currentNodeId];\n          delete savedSizes[currentNodeId];\n          delete savedStyles[currentNodeId];\n          EDGE_DATA.list = EDGE_DATA.list.filter(e => e.from !== currentNodeId && e.to !== currentNodeId);\n          currentNodeId = null;\n          forgeTheTopology();\n          document.getElementById(\"node-panel\").style.display = \"none\";\n         });\n        } else if (currentEdgeId) {\n         challengeTheImmortal(\"Delete this line?\", () => {\n          pushUndo(\"delete edge\");\n          EDGE_DATA.list = EDGE_DATA.list.filter(e => e.id !== currentEdgeId);\n          currentEdgeId = null;\n          forgeTheTopology();\n          document.getElementById(\"edge-panel\").style.display = \"none\";\n         });\n        } else if (currentRectId) {\n         challengeTheImmortal(\"Delete this zone?\", () => {\n          pushUndo(\"delete rect\");\n          RECT_DATA.list = RECT_DATA.list.filter(r => r.id !== currentRectId);\n          currentRectId = null;\n          forgeTheTopology();\n          document.getElementById(\"rect-panel\").style.display = \"none\";\n         });\n        } else if (currentTextId) {\n         const textItem = TEXT_DATA.list.find(t => t.id === currentTextId);\n         if (textItem) {\n          challengeTheImmortal(`Delete text \"${(textItem.content || '').substring(0, 30)}...\"?`, () => {\n           pushUndo(\"delete text\");\n           TEXT_DATA.list = TEXT_DATA.list.filter(t => t.id !== currentTextId);\n           currentTextId = null;\n           forgeTheTopology();\n           document.getElementById(\"text-panel\").style.display = \"none\";\n          });\n         }\n        }\n       }\n       if (event.ctrlKey && event.key === \"a\") {\n        event.preventDefault();\n        Object.keys(NODE_DATA).forEach(id => selectedNodes.add(id));\n        EDGE_DATA.list.forEach(e => selectedEdges.add(e.id));\n        RECT_DATA.list.forEach(r => selectedRects.add(r.id));\n        TEXT_DATA.list.forEach(t => selectedTexts.add(t.id));\n        updateAllSelections();\n       }\n       if (event.key === \"Escape\") {\n        clearSelection();\n       }\n      }\n\t\tfunction searchNodes(query) {\n\t\t   if (!query) {\n\t\t\tcurrentSearchQuery = \"\";\n\t\t\tcurrentSearchResults = [];\n\t\t\tclearSearchHighlight();\n\t\t\treturn [];\n\t\t   }\n\t\t   currentSearchQuery = query;\n\t\t   query = query.toLowerCase();\n\t\t   const results = [];\n\t\t   Object.entries(NODE_DATA).forEach(([id, data]) => {\n\t\t\ttry {\n\t\t\t const nameMatch = data.name && data.name.toLowerCase().includes(query);\n\t\t\t const ipMatch = data.ip && data.ip.toLowerCase().includes(query);\n\t\t\t const roleMatch = data.role && data.role.toLowerCase().includes(query);\n\t\t\t const tagsMatch = data.tags && Array.isArray(data.tags) && data.tags.some(tag => tag && tag.toLowerCase().includes(query));\n\t\t\t const macMatch = data.mac && data.mac.toLowerCase().includes(query);\n\t\t\t const rackUnitMatch = data.rackUnit && String(data.rackUnit).toLowerCase().includes(query);\n\t\t\t if (nameMatch || ipMatch || roleMatch || tagsMatch || macMatch || rackUnitMatch) {\n\t\t\t  results.push(id);\n\t\t\t }\n\t\t\t} catch (e) {\n\t\t\t console.warn(\"Search error for node:\", id, e);\n\t\t\t}\n\t\t   });\n\t\t   currentSearchResults = results;\n\t\t   highlightSearchResults(results, true);\n\t\t   if (results.length > 0) {\n\t\t\tfocusOnNodes(results);\n\t\t   }\n\t\t   return results;\n\t\t}\n\t\tfunction highlightSearchResults(nodeIds, hasQuery = true) {\n\t\t   document.querySelectorAll(\".node-group\").forEach(node => {\n\t\t\tconst nodeId = node.dataset.nodeId;\n\t\t\tif (nodeIds.includes(nodeId)) {\n\t\t\t node.classList.add(\"search-highlight\");\n\t\t\t node.classList.remove(\"search-faded\");\n\t\t\t} else {\n\t\t\t node.classList.remove(\"search-highlight\");\n\t\t\t if (hasQuery) {\n\t\t\t  node.classList.add(\"search-faded\");\n\t\t\t } else {\n\t\t\t  node.classList.remove(\"search-faded\");\n\t\t\t }\n\t\t\t}\n\t\t   });\n\t\t   document.querySelectorAll(\".edge-group, .edge\").forEach(edge => {\n\t\t\tif (hasQuery) {\n\t\t\t edge.classList.add(\"search-faded\");\n\t\t\t} else {\n\t\t\t edge.classList.remove(\"search-faded\");\n\t\t\t}\n\t\t   });\n\t\t}\n\t\tfunction clearSearchHighlight() {\n\t\t   document.querySelectorAll(\".search-highlight\").forEach(node => {\n\t\t\tnode.classList.remove(\"search-highlight\");\n\t\t   });\n\t\t   document.querySelectorAll(\".search-faded\").forEach(el => {\n\t\t\tel.classList.remove(\"search-faded\");\n\t\t   });\n\t\t}\n      function editNodeMac(id) {\n       const node = NODE_DATA[id];\n       if (!node) return;\n       document.getElementById(\"modal-title\").textContent = \"Edit MAC Address\";\n       document.getElementById(\"modal-input\").value = node.mac || \"\";\n       document.getElementById(\"edit-modal\").style.display = \"\";\n\t   document.getElementById(\"edit-modal\").classList.add(\"active\");\n       document.getElementById(\"modal-input\").focus();\n       const modal = document.getElementById(\"edit-modal\");\n       const cleanup = () => {\n        modal.classList.remove(\"active\");\n        document.getElementById(\"modal-save\").removeEventListener(\"click\", saveHandler);\n        document.getElementById(\"modal-cancel\").removeEventListener(\"click\", cancelHandler);\n        modal.removeEventListener(\"click\", bgHandler);\n       };\n       const saveHandler = () => {\n        pushUndo(\"edit mac\");\n        const value = document.getElementById(\"modal-input\").value.trim();\n        node.mac = value;\n        cleanup();\n        claimTheImmortal(id);\n       };\n       const cancelHandler = () => {\n        cleanup();\n       };\n       const bgHandler = (e) => {\n        if (e.target === modal) cancelHandler();\n       };\n       document.getElementById(\"modal-save\").addEventListener(\"click\", saveHandler);\n       document.getElementById(\"modal-cancel\").addEventListener(\"click\", cancelHandler);\n       modal.addEventListener(\"click\", bgHandler);\n      }\n      function editNodeRack(id) {\n       const node = NODE_DATA[id];\n       if (!node) return;\n       document.getElementById(\"modal-title\").textContent = \"Edit Rack Unit\";\n       document.getElementById(\"modal-input\").value = node.rackUnit || \"\";\n       document.getElementById(\"edit-modal\").style.display = \"\";\n       document.getElementById(\"edit-modal\").classList.add(\"active\");\n       document.getElementById(\"modal-input\").focus();\n\t   const modal = document.getElementById(\"edit-modal\");\n       const cleanup = () => {\n        modal.classList.remove(\"active\");\n        document.getElementById(\"modal-save\").removeEventListener(\"click\", saveHandler);\n        document.getElementById(\"modal-cancel\").removeEventListener(\"click\", cancelHandler);\n        modal.removeEventListener(\"click\", bgHandler);\n        document.removeEventListener(\"keydown\", escHandler);\n       };\n       const escHandler = (e) => {\n        if (e.key === \"Escape\") cleanup();\n       };\n       document.addEventListener(\"keydown\", escHandler);\n       const saveHandler = () => {\n        pushUndo(\"edit rack\");\n        const value = document.getElementById(\"modal-input\").value.trim();\n        node.rackUnit = value;\n        cleanup();\n        claimTheImmortal(id);\n       };\n       const cancelHandler = () => {\n        cleanup();\n       };\n       const bgHandler = (e) => {\n        if (e.target === modal) cancelHandler();\n       };\n       document.getElementById(\"modal-save\").addEventListener(\"click\", saveHandler);\n       document.getElementById(\"modal-cancel\").addEventListener(\"click\", cancelHandler);\n       modal.addEventListener(\"click\", bgHandler);\n      }\n      function editNodeUHeight(id) {\n       const node = NODE_DATA[id];\n       if (!node) return;\n       document.getElementById(\"modal-title\").textContent = \"Edit U Height\";\n       document.getElementById(\"modal-input\").value = node.uHeight || \"1\";\n       document.getElementById(\"edit-modal\").style.display = \"\";\n       document.getElementById(\"edit-modal\").classList.add(\"active\");\n       document.getElementById(\"modal-input\").focus();\n\t   const modal = document.getElementById(\"edit-modal\");\n       const cleanup = () => {\n        modal.classList.remove(\"active\");\n        document.getElementById(\"modal-save\").removeEventListener(\"click\", saveHandler);\n        document.getElementById(\"modal-cancel\").removeEventListener(\"click\", cancelHandler);\n        modal.removeEventListener(\"click\", bgHandler);\n        document.removeEventListener(\"keydown\", escHandler);\n       };\n       const escHandler = (e) => {\n        if (e.key === \"Escape\") cleanup();\n       };\n       document.addEventListener(\"keydown\", escHandler);\n       const saveHandler = () => {\n        pushUndo(\"edit U height\");\n        const value = document.getElementById(\"modal-input\").value.trim();\n        node.uHeight = value;\n        cleanup();\n        claimTheImmortal(id);\n        forgeTheTopology();\n       };\n       const cancelHandler = () => {\n        cleanup();\n       };\n       const bgHandler = (e) => {\n        if (e.target === modal) cancelHandler();\n       };\n       document.getElementById(\"modal-save\").addEventListener(\"click\", saveHandler);\n       document.getElementById(\"modal-cancel\").addEventListener(\"click\", cancelHandler);\n       modal.addEventListener(\"click\", bgHandler);\n      }\n      function updateEdgePortLabels(edgeId) {\n       const edge = EDGE_DATA.list.find(e => e.id === edgeId);\n       if (!edge) return;\n       const fromPortInput = document.getElementById(\"edge-from-port\");\n       const toPortInput = document.getElementById(\"edge-to-port\");\n       edge.fromPort = fromPortInput ? fromPortInput.value.trim() : \"\";\n       edge.toPort = toPortInput ? toPortInput.value.trim() : \"\";\n       forgeTheTopology();\n      }\n      document.addEventListener(\"keydown\", handleKeyDown);\n      const undoBtn = document.getElementById(\"undo-btn\");\n      const redoBtn = document.getElementById(\"redo-btn\");\n      if (undoBtn) undoBtn.addEventListener(\"click\", undo);\n      if (redoBtn) redoBtn.addEventListener(\"click\", redo);\n      const backToTopologyBtn = document.getElementById(\"back-to-topology-btn\");\n      if (backToTopologyBtn) {\n       backToTopologyBtn.addEventListener(\"click\", exitRack);\n      }\n      const bulkToolbarClose = document.getElementById(\"bulk-toolbar-close\");\n      if (bulkToolbarClose) {\n       bulkToolbarClose.addEventListener(\"click\", clearSelection);\n      }\n      const bulkAlignLeft = document.getElementById(\"bulk-align-left\");\n      if (bulkAlignLeft) {\n       bulkAlignLeft.addEventListener(\"click\", () => alignSelectedNodes(\"left\"));\n      }\n      const bulkAlignRight = document.getElementById(\"bulk-align-right\");\n      if (bulkAlignRight) {\n       bulkAlignRight.addEventListener(\"click\", () => alignSelectedNodes(\"right\"));\n      }\n      const bulkAlignTop = document.getElementById(\"bulk-align-top\");\n      if (bulkAlignTop) {\n       bulkAlignTop.addEventListener(\"click\", () => alignSelectedNodes(\"top\"));\n      }\n      const bulkAlignBottom = document.getElementById(\"bulk-align-bottom\");\n      if (bulkAlignBottom) {\n       bulkAlignBottom.addEventListener(\"click\", () => alignSelectedNodes(\"bottom\"));\n      }\n      const bulkDistributeH = document.getElementById(\"bulk-distribute-h\");\n      if (bulkDistributeH) {\n       bulkDistributeH.addEventListener(\"click\", () => distributeSelectedNodes(\"horizontal\"));\n      }\n      const bulkDistributeV = document.getElementById(\"bulk-distribute-v\");\n      if (bulkDistributeV) {\n       bulkDistributeV.addEventListener(\"click\", () => distributeSelectedNodes(\"vertical\"));\n      }\n      const bulkClone = document.getElementById(\"bulk-clone\");\n      if (bulkClone) {\n       bulkClone.addEventListener(\"click\", () => {\n        if (selectedNodes.size === 0) return;\n        pushUndo(\"clone selected\");\n        const nodesToClone = Array.from(selectedNodes);\n        selectedNodes.clear();\n       nodesToClone.forEach(id => {\n         cloneNode(id, true);\n         selectedNodes.add(currentNodeId);\n        });\n        updateNodeSelection();\n       });\n      }\n      const bulkDelete = document.getElementById(\"bulk-delete\");\n      if (bulkDelete) {\n       bulkDelete.addEventListener(\"click\", deleteSelected);\n      }\n      const bulkMobileBtn = document.getElementById(\"bulk-mobile-btn\");\n      const bulkActionsModal = document.getElementById(\"bulk-actions-modal\");\n      const bulkModalClose = document.getElementById(\"bulk-modal-close\");\n      if (bulkMobileBtn) {\n       bulkMobileBtn.addEventListener(\"click\", () => {\n        if (bulkActionsModal) bulkActionsModal.style.display = \"block\";\n       });\n      }\n      if (bulkModalClose) {\n       bulkModalClose.addEventListener(\"click\", () => {\n        if (bulkActionsModal) bulkActionsModal.style.display = \"none\";\n       });\n      }\n      const closeModalAfterAction = () => {\n       if (bulkActionsModal) bulkActionsModal.style.display = \"none\";\n      };\n      const bulkAlignLeftMobile = document.getElementById(\"bulk-align-left-mobile\");\n      if (bulkAlignLeftMobile) {\n       bulkAlignLeftMobile.addEventListener(\"click\", () => {\n        alignSelectedNodes(\"left\");\n        closeModalAfterAction();\n       });\n      }\n      const bulkAlignRightMobile = document.getElementById(\"bulk-align-right-mobile\");\n      if (bulkAlignRightMobile) {\n       bulkAlignRightMobile.addEventListener(\"click\", () => {\n        alignSelectedNodes(\"right\");\n        closeModalAfterAction();\n       });\n      }\n      const bulkAlignTopMobile = document.getElementById(\"bulk-align-top-mobile\");\n      if (bulkAlignTopMobile) {\n       bulkAlignTopMobile.addEventListener(\"click\", () => {\n        alignSelectedNodes(\"top\");\n        closeModalAfterAction();\n       });\n      }\n      const bulkAlignBottomMobile = document.getElementById(\"bulk-align-bottom-mobile\");\n      if (bulkAlignBottomMobile) {\n       bulkAlignBottomMobile.addEventListener(\"click\", () => {\n        alignSelectedNodes(\"bottom\");\n        closeModalAfterAction();\n       });\n      }\n      const bulkDistributeHMobile = document.getElementById(\"bulk-distribute-h-mobile\");\n      if (bulkDistributeHMobile) {\n       bulkDistributeHMobile.addEventListener(\"click\", () => {\n        distributeSelectedNodes(\"horizontal\");\n        closeModalAfterAction();\n       });\n      }\n      const bulkDistributeVMobile = document.getElementById(\"bulk-distribute-v-mobile\");\n      if (bulkDistributeVMobile) {\n       bulkDistributeVMobile.addEventListener(\"click\", () => {\n        distributeSelectedNodes(\"vertical\");\n        closeModalAfterAction();\n       });\n      }\n      const bulkCloneMobile = document.getElementById(\"bulk-clone-mobile\");\n      if (bulkCloneMobile) {\n       bulkCloneMobile.addEventListener(\"click\", () => {\n        if (selectedNodes.size === 0) return;\n        pushUndo(\"clone selected\");\n        const nodesToClone = Array.from(selectedNodes);\n        selectedNodes.clear();\n        nodesToClone.forEach(id => {\n         cloneNode(id, true);\n         selectedNodes.add(currentNodeId);\n        });\n        updateNodeSelection();\n        closeModalAfterAction();\n       });\n      }\n      const bulkDeleteMobile = document.getElementById(\"bulk-delete-mobile\");\n      if (bulkDeleteMobile) {\n       bulkDeleteMobile.addEventListener(\"click\", () => {\n        deleteSelected();\n        closeModalAfterAction();\n       });\n      }\n      const bulkLockMobile = document.getElementById(\"bulk-lock-mobile\");\n      if (bulkLockMobile) {\n       bulkLockMobile.addEventListener(\"click\", () => {\n        toggleLockSelected();\n        closeModalAfterAction();\n       });\n      }\n      const bulkGroupMobile = document.getElementById(\"bulk-group-mobile\");\n      if (bulkGroupMobile) {\n       bulkGroupMobile.addEventListener(\"click\", () => {\n        toggleGroupSelected();\n        closeModalAfterAction();\n       });\n      }\n      const bulkClearMobile = document.getElementById(\"bulk-clear-mobile\");\n      if (bulkClearMobile) {\n       bulkClearMobile.addEventListener(\"click\", () => {\n        clearSelection();\n        closeModalAfterAction();\n       });\n      }\n      const searchInput = document.getElementById(\"search-nodes\");\n      let searchTimeout = null;\n      if (searchInput) {\n       searchInput.addEventListener(\"input\", (e) => {\n        clearTimeout(searchTimeout);\n        searchTimeout = setTimeout(() => { searchNodes(e.target.value); }, 150);\n\t\t });\n\t\t}\n       document.getElementById(\"save-file-btn\").addEventListener(\"click\", becomeImmortal);\n       document.getElementById(\"check-all-ping-btn\").addEventListener(\"click\", checkAllNodesStatus);\n       const addNodeBtn = document.getElementById(\"add-node-btn\");\n       const addNodeModal = document.getElementById(\"add-node-modal\");\n       const addNodeCancel = document.getElementById(\"add-node-cancel\");\n       const addNodeSave = document.getElementById(\"add-node-save\");\n       addNodeBtn.addEventListener(\"click\", () => {\n\t    if (isViewOnly()) return;\n        document.getElementById(\"new-node-name\").value = \"\";\n        document.getElementById(\"new-node-ip\").value = \"\";\n        document.getElementById(\"new-node-tags\").value = \"\";\n        document.getElementById(\"new-node-shape\").value = \"circle\";\n        document.getElementById(\"new-node-pingable\").checked = false;\n        document.getElementById(\"new-node-ping-protocol\").value = \"http\";\n        document.getElementById(\"new-node-custom-url\").value = \"\";\n        document.getElementById(\"new-node-ping-timeout\").value = \"3000\";\n        document.getElementById(\"new-node-ping-options\").style.display = \"none\";\n        document.getElementById(\"new-node-custom-url-container\").style.display = \"none\";\n        newNodeIconTags = [];\n        document.getElementById(\"new-node-icon-tags\").style.display = \"none\";\n        document.getElementById(\"new-node-icon-tags-list\").innerHTML = \"\";\n        addNodeModal.classList.add(\"active\");\n        document.getElementById(\"new-node-name\").focus();\n       });\n       const canvasViewport = document.getElementById(\"canvas-viewport\");\n       if (canvasViewport) {\n        canvasViewport.addEventListener(\"dblclick\", (e) => {\n         if (currentView.mode === \"rack\" && e.target.id === \"map\") {\n          exitRack();\n         }\n        });\n       }\n       const layersBtn = document.getElementById(\"layers-btn\");\n       const layerModal = document.getElementById(\"layer-modal\");\n       const layerModalClose = document.getElementById(\"layer-modal-close\");\n       if (layersBtn && layerModal) {\n        layersBtn.addEventListener(\"click\", () => {\n         layerModal.classList.add(\"active\");\n        });\n       }\n       if (layerModalClose && layerModal) {\n        layerModalClose.addEventListener(\"click\", () => {\n         layerModal.classList.remove(\"active\");\n        });\n       }\n       if (layerModal) {\n        layerModal.addEventListener(\"click\", (e) => {\n         if (e.target === layerModal) {\n          layerModal.classList.remove(\"active\");\n         }\n        });\n       }\n       const tabsBtn = document.getElementById(\"tabs-btn\");\n       const tabsModal = document.getElementById(\"tabs-modal\");\n       const tabsModalClose = document.getElementById(\"tabs-modal-close\");\n       if (tabsBtn && tabsModal) {\n         tabsBtn.addEventListener(\"click\", () => {\n           displayTabs();\n           tabsModal.classList.add(\"active\");\n         });\n       }\n       if (tabsModalClose && tabsModal) {\n         tabsModalClose.addEventListener(\"click\", () => {\n           tabsModal.classList.remove(\"active\");\n         });\n       }\n       if (tabsModal) {\n         tabsModal.addEventListener(\"click\", (e) => {\n           if (e.target === tabsModal) {\n             tabsModal.classList.remove(\"active\");\n           }\n         });\n       }\n       const rollbackBtn = document.getElementById(\"rollback-btn\");\n       const rollbackModal = document.getElementById(\"rollback-modal\");\n       const rollbackModalClose = document.getElementById(\"rollback-modal-close\");\n       if (rollbackBtn && rollbackModal) {\n         rollbackBtn.addEventListener(\"click\", () => {\n           loadRollbackVersions();\n           rollbackModal.classList.add(\"active\");\n         });\n       }\n       if (rollbackModalClose && rollbackModal) {\n         rollbackModalClose.addEventListener(\"click\", () => {\n           rollbackModal.classList.remove(\"active\");\n         });\n       }\n       if (rollbackModal) {\n         rollbackModal.addEventListener(\"click\", (e) => {\n           if (e.target === rollbackModal) {\n             rollbackModal.classList.remove(\"active\");\n           }\n         });\n       }\n       const auditLogBtn = document.getElementById(\"audit-log-btn\");\n       const auditLogModal = document.getElementById(\"audit-log-modal\");\n       const auditLogModalClose = document.getElementById(\"audit-log-modal-close\");\n       if (auditLogBtn && auditLogModal) {\n         auditLogBtn.addEventListener(\"click\", () => {\n           loadAuditLog();\n           displayAuditLog();\n           auditLogModal.classList.add(\"active\");\n         });\n       }\n       if (auditLogModalClose && auditLogModal) {\n         auditLogModalClose.addEventListener(\"click\", () => {\n           auditLogModal.classList.remove(\"active\");\n         });\n       }\n\t   const portMapBtn = document.getElementById(\"port-map-btn\");\n      const portMapModal = document.getElementById(\"port-map-modal\");\n      const portMapClose = document.getElementById(\"port-map-modal-close\");\n      const portMapSearch = document.getElementById(\"port-map-search\");\n      const portMapFilter = document.getElementById(\"port-map-filter\");\n      function renderPortMap() {\n        const container = document.getElementById(\"port-map-table\");\n        const search = (document.getElementById(\"port-map-search\")?.value || \"\").toLowerCase();\n        const filter = document.getElementById(\"port-map-filter\")?.value || \"all\";\n        let edges = EDGE_DATA.list || [];\n        if (filter === \"with-ports\") edges = edges.filter(e => e.fromPort || e.toPort);\n        if (filter === \"without-ports\") edges = edges.filter(e => !e.fromPort && !e.toPort);\n        if (search) {\n          edges = edges.filter(e => {\n            const fromName = (NODE_DATA[e.from]?.name || e.from || \"\").toLowerCase();\n            const toName = (NODE_DATA[e.to]?.name || e.to || \"\").toLowerCase();\n            return fromName.includes(search) || toName.includes(search) || (e.fromPort || \"\").toLowerCase().includes(search) || (e.toPort || \"\").toLowerCase().includes(search) || (e.notes || []).join(\" \").toLowerCase().includes(search);\n          });\n        }\n        if (edges.length === 0) {\n          container.innerHTML = '<div style=\"text-align: center; padding: 40px; color: var(--text-soft);\">No connections found</div>';\n          return;\n        }\n        let html = '<table style=\"width: 100%; border-collapse: collapse; font-size: 13px;\">';\n        html += '<thead><tr style=\"background: var(--panel-alt); position: sticky; top: 0;\"><th style=\"padding: 10px; text-align: left; border-bottom: 1px solid var(--edge-main);\">From Device</th><th style=\"padding: 10px; text-align: left; border-bottom: 1px solid var(--edge-main);\">From Port</th><th style=\"padding: 10px; text-align: left; border-bottom: 1px solid var(--edge-main);\">To Device</th><th style=\"padding: 10px; text-align: left; border-bottom: 1px solid var(--edge-main);\">To Port</th><th style=\"padding: 10px; text-align: left; border-bottom: 1px solid var(--edge-main);\">Notes</th></tr></thead><tbody>';\n        edges.forEach(e => {\n          const fromName = NODE_DATA[e.from]?.name || e.from;\n          const toName = NODE_DATA[e.to]?.name || e.to;\n          const notes = (e.notes || []).join(\", \");\n          const edgeColor = e.color || EDGE_LEGEND[e.type]?.color || 'var(--edge-main)';\n          const fromPortDisplay = e.fromPort ? escapeHtml(e.fromPort) : \"-\";\n          const toPortDisplay = e.toPort ? escapeHtml(e.toPort) : \"-\";\n          const goToEdge = `document.getElementById('port-map-modal').classList.remove('active'); selectTheConnection('${escapeHtml(e.id)}'); focusOnSelected()`;\n          html += `<tr style=\"border-bottom: 1px solid var(--edge-main);\"><td style=\"padding: 8px;\"><span style=\"display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: ${edgeColor}; margin-right: 6px;\"></span><button onclick=\"document.getElementById('port-map-modal').classList.remove('active'); claimTheImmortal('${escapeHtml(e.from)}'); focusOnSelected()\" style=\"background: var(--panel-alt); color: var(--text-main); border: 1px solid var(--edge-main); border-radius: 4px; padding: 6px 10px; cursor: pointer; font-size: 13px; min-height: 36px;\">${escapeHtml(fromName)}</button></td><td style=\"padding: 8px;\"><button onclick=\"editPortFromMap('${escapeHtml(e.id)}', 'from')\" style=\"background: ${e.fromPort ? 'var(--accent)' : 'var(--panel)'}; color: ${e.fromPort ? 'var(--bg)' : 'var(--text-soft)'}; border: 1px solid var(--edge-main); border-radius: 4px; padding: 6px 10px; cursor: pointer; font-family: monospace; font-size: 13px; min-height: 36px;\">${fromPortDisplay}</button></td><td style=\"padding: 8px;\"><button onclick=\"document.getElementById('port-map-modal').classList.remove('active'); claimTheImmortal('${escapeHtml(e.to)}'); focusOnSelected()\" style=\"background: var(--panel-alt); color: var(--text-main); border: 1px solid var(--edge-main); border-radius: 4px; padding: 6px 10px; cursor: pointer; font-size: 13px; min-height: 36px;\">${escapeHtml(toName)}</button></td><td style=\"padding: 8px;\"><button onclick=\"editPortFromMap('${escapeHtml(e.id)}', 'to')\" style=\"background: ${e.toPort ? 'var(--accent)' : 'var(--panel)'}; color: ${e.toPort ? 'var(--bg)' : 'var(--text-soft)'}; border: 1px solid var(--edge-main); border-radius: 4px; padding: 6px 10px; cursor: pointer; font-family: monospace; font-size: 13px; min-height: 36px;\">${toPortDisplay}</button></td><td style=\"padding: 8px;\"><button onclick=\"${goToEdge}\" style=\"background: var(--panel); color: var(--text-main); border: 1px solid var(--edge-main); border-radius: 4px; padding: 6px 10px; cursor: pointer; font-size: 13px; min-height: 36px;\" title=\"Go to connection\">Go</button></td></tr>`;\n        });\n        html += '</tbody></table>';\n        container.innerHTML = html;\n      }\n      window.editPortFromMap = function(edgeId, which) {\n        const edge = EDGE_DATA.list.find(e => e.id === edgeId);\n        if (!edge) return;\n        const fromName = NODE_DATA[edge.from]?.name || edge.from;\n        const toName = NODE_DATA[edge.to]?.name || edge.to;\n        const nodeId = which === 'from' ? edge.from : edge.to;\n        const nodeName = which === 'from' ? fromName : toName;\n        const label = `Port on ${nodeName}:`;\n        const currentVal = which === 'from' ? (edge.fromPort || '') : (edge.toPort || '');\n        const newVal = prompt(label, currentVal);\n        if (newVal !== null && newVal !== '') {\n          const isDuplicate = (EDGE_DATA.list || []).some(e => {\n            if (e.id === edgeId) return false;\n            if (e.from === nodeId && e.fromPort === newVal) return true;\n            if (e.to === nodeId && e.toPort === newVal) return true;\n            return false;\n          });\n          if (isDuplicate && !confirm(`Warning: Port \"${newVal}\" is already used on ${nodeName}. Use anyway?`)) {\n            return;\n          }\n          if (which === 'from') edge.fromPort = newVal;\n          else edge.toPort = newVal;\n          renderPortMap();\n          if (currentNodeId && (edge.from === currentNodeId || edge.to === currentNodeId)) {\n            claimTheImmortal(currentNodeId);\n          }\n        } else if (newVal === '') {\n          if (which === 'from') edge.fromPort = '';\n          else edge.toPort = '';\n          renderPortMap();\n          if (currentNodeId && (edge.from === currentNodeId || edge.to === currentNodeId)) {\n            claimTheImmortal(currentNodeId);\n          }\n        }\n      };\n      window.exportPortMap = function() {\n        let csv = \"From Device,From Port,To Device,To Port,Notes\\n\";\n        (EDGE_DATA.list || []).forEach(e => {\n          const fromName = NODE_DATA[e.from]?.name || e.from || \"\";\n          const toName = NODE_DATA[e.to]?.name || e.to || \"\";\n          const notes = (e.notes || []).join(\"; \");\n          csv += `${csvEscape(fromName)},${csvEscape(e.fromPort || \"\")},${csvEscape(toName)},${csvEscape(e.toPort || \"\")},${csvEscape(notes)}\\n`;\n        });\n        const blob = new Blob([csv], { type: \"text/csv\" });\n        const url = URL.createObjectURL(blob);\n        const a = document.createElement(\"a\");\n        a.href = url;\n        a.download = \"port-map.csv\";\n        a.click();\n        URL.revokeObjectURL(url);\n      };\n      if (portMapBtn && portMapModal) {\n        portMapBtn.addEventListener(\"click\", () => { renderPortMap(); portMapModal.classList.add(\"active\"); });\n      }\n      if (portMapClose && portMapModal) {\n        portMapClose.addEventListener(\"click\", () => { portMapModal.classList.remove(\"active\"); });\n      }\n      if (portMapSearch) { portMapSearch.addEventListener(\"input\", renderPortMap); }\n      if (portMapFilter) { portMapFilter.addEventListener(\"change\", renderPortMap); }\n      if (portMapModal) {\n        portMapModal.addEventListener(\"click\", (e) => { if (e.target === portMapModal) portMapModal.classList.remove(\"active\"); });\n      }\n       if (auditLogModal) {\n         auditLogModal.addEventListener(\"click\", (e) => {\n           if (e.target === auditLogModal) {\n             auditLogModal.classList.remove(\"active\");\n           }\n         });\n       }\n       const auditFilter = document.getElementById(\"audit-filter\");\n       if (auditFilter) {\n         auditFilter.addEventListener(\"change\", (e) => {\n           displayAuditLog(e.target.value);\n         });\n       }\n       const secretsBtn = document.getElementById(\"secrets-btn\");\n       const secretsModal = document.getElementById(\"secrets-modal\");\n       const secretsModalClose = document.getElementById(\"secrets-modal-close\");\n       if (secretsBtn && secretsModal) {\n         secretsBtn.addEventListener(\"click\", () => {\n           displaySecrets();\n           secretsModal.classList.add(\"active\");\n         });\n       }\n       if (secretsModalClose && secretsModal) {\n         secretsModalClose.addEventListener(\"click\", () => {\n           secretsModal.classList.remove(\"active\");\n         });\n       }\n       if (secretsModal) {\n         secretsModal.addEventListener(\"click\", (e) => {\n           if (e.target === secretsModal) {\n             secretsModal.classList.remove(\"active\");\n           }\n         });\n       }\n       const secretEditorModal = document.getElementById(\"secret-editor-modal\");\n       if (secretEditorModal) {\n         secretEditorModal.addEventListener(\"click\", (e) => {\n           if (e.target === secretEditorModal) {\n             closeSecretEditor();\n           }\n         });\n       }\n       [\"physical\", \"logical\", \"security\", \"application\"].forEach(layer => {\n        const checkbox = document.getElementById(`layer-${layer}`);\n        if (checkbox) {\n         checkbox.addEventListener(\"change\", applyLayerFilter);\n        }\n       });\n       const layerSelect = document.getElementById(\"node-layer\");\n       if (layerSelect) {\n        layerSelect.addEventListener(\"change\", (e) => {\n         if (currentNodeId) {\n          pushUndo(\"change layer\");\n          NODE_DATA[currentNodeId].layer = e.target.value;\n          forgeTheTopology();\n         }\n        });\n       }\n       const assignedRackSelect = document.getElementById(\"node-assigned-rack\");\n       if (assignedRackSelect) {\n        assignedRackSelect.addEventListener(\"change\", (e) => {\n      if (currentNodeId) {\n      pushUndo(\"change assigned rack\");\n      EDGE_DATA.list = EDGE_DATA.list.filter(edge => edge.from !== currentNodeId && edge.to !== currentNodeId);\n      NODE_DATA[currentNodeId].assignedRack = e.target.value;\n      forgeTheTopology();\n      claimTheImmortal(currentNodeId);\n      }\n      });\n       }\n       const rackCapacitySelect = document.getElementById(\"node-rack-capacity\");\n       if (rackCapacitySelect) {\n        rackCapacitySelect.addEventListener(\"change\", (e) => {\n         if (currentNodeId) {\n          pushUndo(\"change rack capacity\");\n          NODE_DATA[currentNodeId].rackCapacity = e.target.value;\n          forgeTheTopology();\n         }\n        });\n       }\n       const addRackBtn = document.getElementById(\"add-rack-btn\");\n       const addRackModal = document.getElementById(\"add-rack-modal\");\n       const addRackCancel = document.getElementById(\"add-rack-cancel\");\n       const addRackSave = document.getElementById(\"add-rack-save\");\n       if (addRackBtn && addRackModal) {\n        addRackBtn.addEventListener(\"click\", () => {\n\t\tif (isViewOnly()) return;\n         document.getElementById(\"new-rack-name\").value = \"\";\n         document.getElementById(\"new-rack-ip\").value = \"\";\n         document.getElementById(\"new-rack-tags\").value = \"\";\n         document.getElementById(\"new-rack-shape\").value = \"server\";\n         document.getElementById(\"new-rack-capacity\").value = \"42\";\n         selectedRackIconData = null;\n         document.getElementById('selected-rack-icon').style.display = 'none';\n         addRackModal.classList.add(\"active\");\n         document.getElementById(\"new-rack-name\").focus();\n        });\n       }\n       if (addRackCancel && addRackModal) {\n        addRackCancel.addEventListener(\"click\", () => {\n         addRackModal.classList.remove(\"active\");\n        });\n       }\n       if (addRackModal) {\n        addRackModal.addEventListener(\"click\", (e) => {\n         if (e.target === addRackModal) {\n          addRackModal.classList.remove(\"active\");\n         }\n        });\n       }\n       if (addRackSave && addRackModal) {\n        addRackSave.addEventListener(\"click\", () => {\n         const name = document.getElementById(\"new-rack-name\").value.trim();\n         const ip = document.getElementById(\"new-rack-ip\").value.trim();\n         const tagsStr = document.getElementById(\"new-rack-tags\").value.trim();\n         const shape = document.getElementById(\"new-rack-shape\").value;\n         const capacity = document.getElementById(\"new-rack-capacity\").value;\n         if (!name) {\n          alert(\"Please enter a rack name.\");\n          return;\n         }\n         const tags = tagsStr ? tagsStr.split(\",\").map((t) => t.trim()).filter((t) => t) : [];\n         let baseId = name.toLowerCase().replace(/[^a-z0-9]+/g, \"-\").replace(/^-|-$/g, \"\");\n         if (!baseId) baseId = \"rack\";\n         let nodeId = baseId;\n         let counter = 1;\n         while (NODE_DATA[nodeId]) {\n          nodeId = baseId + \"-\" + counter;\n          counter++;\n         }\n         pushUndo(\"add rack\");\n         NODE_DATA[nodeId] = {\n          shape: shape,\n          name: name,\n          ip: ip || \"\",\n          role: \"Rack\",\n          tags: tags,\n          notes: [],\n          mac: \"\",\n          rackUnit: \"\",\n          uHeight: \"1\",\n          layer: \"physical\",\n          assignedRack: \"\",\n          rackCapacity: capacity,\n          isRack: true,\n          locked: false,\n          groupId: null\n         };\n         const viewWidth = CANVAS_WIDTH / canvasState.zoom;\n         const viewHeight = CANVAS_HEIGHT / canvasState.zoom;\n         const centerX = canvasState.panX + (viewWidth / 2);\n         const centerY = canvasState.panY + (viewHeight / 2);\n         savedPositions[nodeId] = {\n          x: centerX,\n          y: centerY\n         };\n         if (selectedRackIconData) {\n          if (!savedStyles[nodeId]) {\n           savedStyles[nodeId] = {};\n          }\n          if (!savedStyles[nodeId]['all']) {\n           savedStyles[nodeId]['all'] = {};\n          }\n          savedStyles[nodeId]['all'].icon = {\n           library: selectedRackIconData.library,\n           name: selectedRackIconData.name\n          };\n          selectedRackIconData = null;\n          document.getElementById('selected-rack-icon').style.display = 'none';\n         }\n         addRackModal.classList.remove(\"active\");\n         forgeTheTopology();\n         claimTheImmortal(nodeId);\n        });\n        [\"new-rack-name\", \"new-rack-ip\", \"new-rack-tags\"].forEach((inputId) => {\n         const input = document.getElementById(inputId);\n         if (input) {\n          input.addEventListener(\"keypress\", (e) => {\n           if (e.key === \"Enter\") {\n            addRackSave.click();\n           }\n          });\n         }\n        });\n       }\n       addNodeCancel.addEventListener(\"click\", () => {\n        addNodeModal.classList.remove(\"active\");\n       });\n       addNodeModal.addEventListener(\"click\", (e) => {\n        if (e.target === addNodeModal) {\n         addNodeModal.classList.remove(\"active\");\n        }\n       });\n       addNodeSave.addEventListener(\"click\", () => {\n        const name = document.getElementById(\"new-node-name\").value.trim();\n        const ip = document.getElementById(\"new-node-ip\").value.trim();\n        const tagsStr = document.getElementById(\"new-node-tags\").value.trim();\n        const shape = document.getElementById(\"new-node-shape\").value;\n        const pingable = document.getElementById(\"new-node-pingable\").checked;\n        const pingProtocol = document.getElementById(\"new-node-ping-protocol\").value;\n        const pingCustomUrl = document.getElementById(\"new-node-custom-url\").value.trim();\n        const pingTimeout = parseInt(document.getElementById(\"new-node-ping-timeout\").value) || 3000;\n        if (!name) {\n         alert(\"Please enter a node name.\");\n         return;\n        }\n        const tags = tagsStr ? tagsStr.split(\",\").map((t) => t.trim()).filter((t) => t) : [];\n        if (newNodeIconTags.length > 0) {\n         tags.push(...newNodeIconTags);\n        }\n        let baseId = name.toLowerCase().replace(/[^a-z0-9]+/g, \"-\").replace(/^-|-$/g, \"\");\n        if (!baseId) baseId = \"node\";\n        let nodeId = baseId;\n        let counter = 1;\n        while (NODE_DATA[nodeId]) {\n         nodeId = baseId + \"-\" + counter;\n         counter++;\n        }\n\t\tpushUndo(\"add node\");\n        NODE_DATA[nodeId] = {\n         shape: shape || \"circle\",\n         name: name,\n         ip: ip || \"0.0.0.0\",\n         role: \"\",\n         tags: tags,\n         notes: [],\n         mac: \"\",\n         rackUnit: \"\",\n         uHeight: \"1\",\n         ping: {\n          enabled: pingable,\n          protocol: pingProtocol,\n          customUrl: pingCustomUrl,\n          timeout: pingTimeout,\n          status: 'unknown',\n          lastCheck: null\n         },\n         locked: false,\n         groupId: null\n        };\n        if (currentView.mode === \"rack\" && currentView.rackId) {\n         NODE_DATA[nodeId].assignedRack = currentView.rackId;\n         NODE_DATA[nodeId].layer = \"physical\";\n         const rackCapacity = getRackCapacity(currentView.rackId);\n         const rackUHeight = getRackUHeight(currentView.rackId);\n         const viewHeight = CANVAS_HEIGHT / canvasState.zoom;\n         const centerY = canvasState.panY + (viewHeight / 2);\n         let unit = rackCapacity - Math.round((centerY - RACK_START_Y) / rackUHeight);\n         unit = Math.max(1, Math.min(rackCapacity, unit));\n         NODE_DATA[nodeId].rackUnit = String(unit);\n        }\n        if (selectedNodeIconData) {\n         if (!savedStyles[nodeId]) savedStyles[nodeId] = {};\n         if (!savedStyles[nodeId]['all']) savedStyles[nodeId]['all'] = {};\n         savedStyles[nodeId]['all'].icon = {\n          library: selectedNodeIconData.library,\n          name: selectedNodeIconData.name\n         };\n         selectedNodeIconData = null;\n         document.getElementById('selected-node-icon').style.display = 'none';\n        }\n        newNodeIconTags = [];\n      const viewWidth = CANVAS_WIDTH / canvasState.zoom;\n      const viewHeight = CANVAS_HEIGHT / canvasState.zoom;\n      const centerX = canvasState.panX + (viewWidth / 2);\n      const centerY = canvasState.panY + (viewHeight / 2);\n      savedPositions[nodeId] = {\n      x: centerX,\n      y: centerY\n      };\n        addNodeModal.classList.remove(\"active\");\n        forgeTheTopology();\n        claimTheImmortal(nodeId);\n       });\n       [\"new-node-name\", \"new-node-ip\", \"new-node-tags\"].forEach(\n        (inputId) => {\n         document.getElementById(inputId).addEventListener(\"keypress\", (e) => {\n          if (e.key === \"Enter\") {\n           addNodeSave.click();\n          }\n         });\n        });\n       document.getElementById('new-node-pingable').addEventListener('change', (e) => {\n        const pingOptions = document.getElementById('new-node-ping-options');\n        pingOptions.style.display = e.target.checked ? 'block' : 'none';\n       });\n       document.getElementById('new-node-ping-protocol').addEventListener('change', (e) => {\n        const customUrlContainer = document.getElementById('new-node-custom-url-container');\n        customUrlContainer.style.display = e.target.value === 'custom' ? 'block' : 'none';\n       });\n       document.getElementById('pick-rack-icon-btn').addEventListener('click', () => {\n        openIconPicker((iconData) => {\n         selectedRackIconData = iconData;\n         const preview = document.getElementById('selected-rack-icon-preview');\n         const container = document.getElementById('selected-rack-icon');\n         const parser = new DOMParser();\n         const doc = parser.parseFromString(iconData.svg, 'image/svg+xml');\n         const svgEl = doc.querySelector('svg');\n         preview.innerHTML = '';\n         if (svgEl) {\n          preview.appendChild(svgEl.cloneNode(true));\n         }\n         const nameSpan = document.createElement('span');\n         nameSpan.textContent = iconData.name;\n         preview.appendChild(nameSpan);\n         container.style.display = 'block';\n        });\n       });\n       document.getElementById('pick-node-icon-btn').addEventListener('click', () => {\n        openIconPicker((iconData) => {\n         selectedNodeIconData = iconData;\n         const preview = document.getElementById('selected-node-icon-preview');\n         const container = document.getElementById('selected-node-icon');\n         const parser = new DOMParser();\n         const doc = parser.parseFromString(iconData.svg, 'image/svg+xml');\n         const svgEl = doc.querySelector('svg');\n         preview.innerHTML = '';\n         if (svgEl) {\n          preview.appendChild(svgEl.cloneNode(true));\n         }\n         const nameSpan = document.createElement('span');\n         nameSpan.textContent = iconData.name;\n         preview.appendChild(nameSpan);\n         container.style.display = 'block';\n        });\n       });\n       document.getElementById('pick-tag-icon-btn').addEventListener('click', () => {\n        if (!currentNodeId) return;\n        openIconPicker((iconData) => {\n         if (!currentNodeId || !NODE_DATA[currentNodeId]) return;\n         if (!NODE_DATA[currentNodeId].tags) {\n          NODE_DATA[currentNodeId].tags = [];\n         }\n         NODE_DATA[currentNodeId].tags.push({\n          type: 'icon',\n          library: iconData.library,\n          name: iconData.name\n         });\n         forgeTheTopology();\n         claimTheImmortal(currentNodeId);\n        });\n       });\n       document.getElementById('pick-new-node-tag-icon-btn').addEventListener('click', () => {\n        openIconPicker((iconData) => {\n         newNodeIconTags.push({\n          type: 'icon',\n          library: iconData.library,\n          name: iconData.name\n         });\n         const container = document.getElementById('new-node-icon-tags');\n         const list = document.getElementById('new-node-icon-tags-list');\n         const badge = document.createElement('div');\n         badge.className = 'icon-badge';\n         badge.style.cssText = 'display: inline-flex; align-items: center; gap: 6px; padding: 6px 10px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 6px; font-size: 13px;';\n         const iconPreview = document.createElement('div');\n         iconPreview.style.cssText = 'width: 16px; height: 16px; display: flex; align-items: center; justify-content: center;';\n         IconLibrary.getIcon(iconData.library, iconData.name).then(svg => {\n          if (svg) {\n           const parser = new DOMParser();\n           const doc = parser.parseFromString(svg, 'image/svg+xml');\n           const svgEl = doc.querySelector('svg');\n           if (svgEl) {\n            svgEl.setAttribute('width', '16');\n            svgEl.setAttribute('height', '16');\n            svgEl.style.fill = 'var(--text-main)';\n            iconPreview.appendChild(svgEl);\n           }\n          }\n         });\n         const name = document.createElement('span');\n         name.textContent = iconData.name;\n         name.style.color = 'var(--text-soft)';\n         const removeBtn = document.createElement('button');\n         removeBtn.textContent = '×';\n         removeBtn.style.cssText = 'background: none; border: none; color: var(--danger); cursor: pointer; font-size: 18px; line-height: 1; padding: 0 4px;';\n         removeBtn.addEventListener('click', () => {\n          const index = newNodeIconTags.findIndex(t => t.type === 'icon' && t.library === iconData.library && t.name === iconData.name);\n          if (index > -1) {\n           newNodeIconTags.splice(index, 1);\n          }\n          badge.remove();\n          if (list.children.length === 0) {\n           container.style.display = 'none';\n          }\n         });\n         badge.appendChild(iconPreview);\n         badge.appendChild(name);\n         badge.appendChild(removeBtn);\n         list.appendChild(badge);\n         container.style.display = 'block';\n        });\n       });\n       document.getElementById('pick-shape-icon-btn').addEventListener('click', () => {\n        if (!currentNodeId) return;\n        openIconPicker((iconData) => {\n         if (!savedStyles[currentNodeId]) {\n          savedStyles[currentNodeId] = {};\n         }\n         if (!savedStyles[currentNodeId][currentStyleScope]) {\n          savedStyles[currentNodeId][currentStyleScope] = {};\n         }\n         savedStyles[currentNodeId][currentStyleScope].icon = {\n          library: iconData.library,\n          name: iconData.name\n         };\n\t\t delete savedStyles[currentNodeId][currentStyleScope].circleColor;\n         delete savedStyles[currentNodeId][currentStyleScope].circleBorder;\n         const shapeSelect = document.getElementById('shape-select');\n         if (!shapeSelect.querySelector('option[value=\"custom-icon\"]')) {\n          const opt = document.createElement('option');\n          opt.value = 'custom-icon';\n          opt.textContent = 'Custom Icon';\n          shapeSelect.insertBefore(opt, shapeSelect.firstChild);\n         }\n         shapeSelect.value = 'custom-icon';\n         forgeTheTopology();\n        });\n       });\n       document.getElementById('add-tag-btn').addEventListener('click', () => {\n        if (!currentNodeId) return;\n        if (!NODE_DATA[currentNodeId]) return;\n        const input = document.getElementById('new-tag-input');\n        const tagText = input.value.trim();\n        if (!tagText) return;\n        if (!NODE_DATA[currentNodeId].tags) {\n         NODE_DATA[currentNodeId].tags = [];\n        }\n        NODE_DATA[currentNodeId].tags.push(tagText);\n        input.value = '';\n        forgeTheTopology();\n        claimTheImmortal(currentNodeId);\n       });\n      function saveRollbackVersion(description = \"Auto-save\") {\n        const version = {\n          timestamp: Date.now(),\n          description,\n          data: captureTheQuickening()\n        };\n        try {\n          const stored = localStorage.getItem(ROLLBACK_STORAGE_KEY);\n          rollbackVersions = stored ? JSON.parse(stored) : [];\n        } catch (e) {\n          rollbackVersions = [];\n        }\n        rollbackVersions.unshift(version);\n        if (rollbackVersions.length > MAX_ROLLBACK_VERSIONS) {\n          rollbackVersions = rollbackVersions.slice(0, MAX_ROLLBACK_VERSIONS);\n        }\n        try {\n          localStorage.setItem(ROLLBACK_STORAGE_KEY, JSON.stringify(rollbackVersions));\n        } catch (e) {\n          console.warn(\"Failed to save rollback version:\", e);\n        }\n      }\n      function loadRollbackVersions() {\n        try {\n          const stored = localStorage.getItem(ROLLBACK_STORAGE_KEY);\n          rollbackVersions = stored ? JSON.parse(stored) : [];\n          displayRollbackVersions();\n        } catch (e) {\n          console.warn(\"Failed to load rollback versions:\", e);\n          rollbackVersions = [];\n        }\n      }\n      function displayRollbackVersions() {\n        const listEl = document.getElementById(\"rollback-list\");\n        if (!listEl) return;\n        if (rollbackVersions.length === 0) {\n          listEl.innerHTML = '<div style=\"text-align: center; padding: 40px; color: var(--text-soft);\">No version history yet. Versions are saved automatically when you save the file.</div>';\n          return;\n        }\n        listEl.innerHTML = rollbackVersions.map((version, index) => {\n          const date = new Date(version.timestamp);\n          const timeStr = date.toLocaleString();\n          const nodeCount = Object.keys(version.data.nodeData || {}).length;\n          const edgeCount = (version.data.edgeData?.list || []).length;\n          return `\n            <div class=\"version-item\" onclick=\"restoreRollbackVersion(${index})\">\n              <div class=\"version-info\">\n                <div class=\"timestamp\">${escapeHtml(timeStr)}</div>\n                <div class=\"details\">${escapeHtml(version.description)} • ${nodeCount} nodes • ${edgeCount} connections</div>\n              </div>\n              <div class=\"version-actions\">\n                <button class=\"btn-cancel\" onclick=\"event.stopPropagation(); deleteRollbackVersion(${index})\" title=\"Delete this version\">🗑️</button>\n              </div>\n            </div>\n          `;\n        }).join('');\n      }\n      function restoreRollbackVersion(index) {\n        if (!confirm(`Restore version from ${new Date(rollbackVersions[index].timestamp).toLocaleString()}?\\n\\nYour current work will be lost unless you save first.`)) {\n          return;\n        }\n        const version = rollbackVersions[index];\n        const data = version.data;\n        NODE_DATA = data.nodeData || {};\n        EDGE_DATA = data.edgeData || { list: [] };\n        RECT_DATA = data.rectData || { list: [] };\n        TEXT_DATA = data.textData || { list: [] };\n        EDGE_LEGEND = data.edgeLegend || {};\n        savedPositions = data.nodePositions || {};\n        savedSizes = data.nodeSizes || {};\n        savedStyles = data.nodeStyles || {};\n        PAGE_STATE = data.page || PAGE_STATE;\n        if (data.canvas) {\n          canvasState.zoom = data.canvas.zoom;\n          canvasState.panX = data.canvas.panX;\n          canvasState.panY = data.canvas.panY;\n        }\n        wieldThePower();\n        forgeTheTopology();\n        document.getElementById(\"rollback-modal\").classList.remove(\"active\");\n        logAuditEvent(\"rollback\", `Restored version from ${new Date(version.timestamp).toLocaleString()}`);\n      }\n      function deleteRollbackVersion(index) {\n        if (!confirm(\"Delete this version from history?\")) return;\n        rollbackVersions.splice(index, 1);\n        try {\n          localStorage.setItem(ROLLBACK_STORAGE_KEY, JSON.stringify(rollbackVersions));\n        } catch (e) {\n          console.warn(\"Failed to delete version:\", e);\n        }\n        displayRollbackVersions();\n      }\n      function clearRollbackHistory() {\n        if (!confirm(\"Clear all version history?\\n\\nThis cannot be undone.\")) return;\n        rollbackVersions = [];\n        localStorage.removeItem(ROLLBACK_STORAGE_KEY);\n        displayRollbackVersions();\n      }\n      function createManualSnapshot() {\n        const description = prompt(\"Enter a description for this snapshot:\", \"Manual snapshot\");\n        if (!description) return;\n        saveRollbackVersion(description);\n        displayRollbackVersions();\n      }\n      function switchTab(index) {\n        if (index === currentTabIndex) return;\n        const currentTab = documentTabs[currentTabIndex];\n        currentTab.nodes = JSON.parse(JSON.stringify(NODE_DATA));\n        currentTab.edges = JSON.parse(JSON.stringify(EDGE_DATA));\n        currentTab.positions = JSON.parse(JSON.stringify(savedPositions));\n        currentTab.sizes = JSON.parse(JSON.stringify(savedSizes));\n        currentTab.styles = JSON.parse(JSON.stringify(savedStyles));\n        currentTab.legend = JSON.parse(JSON.stringify(EDGE_LEGEND));\n        currentTab.rects = JSON.parse(JSON.stringify(RECT_DATA));\n        currentTab.texts = JSON.parse(JSON.stringify(TEXT_DATA));\n        currentTab.pageState = JSON.parse(JSON.stringify(PAGE_STATE));\n        currentTabIndex = index;\n        const newTab = documentTabs[index];\n        NODE_DATA = JSON.parse(JSON.stringify(newTab.nodes));\n        EDGE_DATA = JSON.parse(JSON.stringify(newTab.edges));\n        savedPositions = JSON.parse(JSON.stringify(newTab.positions));\n        savedSizes = JSON.parse(JSON.stringify(newTab.sizes));\n        savedStyles = JSON.parse(JSON.stringify(newTab.styles));\n        EDGE_LEGEND = JSON.parse(JSON.stringify(newTab.legend));\n        RECT_DATA = JSON.parse(JSON.stringify(newTab.rects));\n        TEXT_DATA = JSON.parse(JSON.stringify(newTab.texts));\n        PAGE_STATE = Object.assign({}, DEFAULT_PAGE_STATE, newTab.pageState || {});\n        wieldThePower();\n        document.title = PAGE_STATE.title || newTab.name;\n        document.getElementById(\"page-title\").textContent = PAGE_STATE.title || newTab.name;\n        forgeTheTopology();\n        currentNodeId = null;\n        currentEdgeId = null;\n        currentTextId = null;\n        document.getElementById(\"node-panel\").style.display = \"none\";\n        document.getElementById(\"edge-panel\").style.display = \"none\";\n        document.getElementById(\"text-panel\").style.display = \"none\";\n        displayTabs();\n        logAuditEvent(\"tab\", `Switched to tab: ${newTab.name}`);\n      }\n      function createNewTab() {\n        const nameInput = document.getElementById(\"new-tab-name\");\n        const name = nameInput.value.trim();\n        if (!name) {\n          alert(\"Please enter a tab name\");\n          return;\n        }\n        const newTab = {\n          id: `tab-${Date.now()}`,\n          name,\n          nodes: {},\n          edges: { list: [] },\n          positions: {},\n          sizes: {},\n          styles: {},\n          legend: {},\n          rects: { list: [] },\n          texts: { list: [] },\n      pageState: null\n        };\n        documentTabs.push(newTab);\n        nameInput.value = \"\";\n        displayTabs();\n        logAuditEvent(\"tab\", `Created new tab: ${name}`);\n      }\n      function renameTab(index) {\n        const tab = documentTabs[index];\n        const newName = prompt(\"Enter new name:\", tab.name);\n        if (!newName || newName === tab.name) return;\n        tab.name = newName;\n        displayTabs();\n        logAuditEvent(\"tab\", `Renamed tab to: ${newName}`);\n      }\n      function deleteTab(index) {\n        if (documentTabs.length === 1) {\n          alert(\"Cannot delete the last tab\");\n          return;\n        }\n        if (!confirm(`Delete tab \"${documentTabs[index].name}\"?`)) return;\n        const wasCurrentTab = (index === currentTabIndex);\n        documentTabs.splice(index, 1);\n        if (currentTabIndex >= documentTabs.length) {\n          currentTabIndex = documentTabs.length - 1;\n        } else if (index < currentTabIndex) {\n          currentTabIndex--;\n        }\n        if (wasCurrentTab) {\n          const newTab = documentTabs[currentTabIndex];\n          NODE_DATA = JSON.parse(JSON.stringify(newTab.nodes));\n          EDGE_DATA = JSON.parse(JSON.stringify(newTab.edges));\n          savedPositions = JSON.parse(JSON.stringify(newTab.positions));\n          savedSizes = JSON.parse(JSON.stringify(newTab.sizes));\n          savedStyles = JSON.parse(JSON.stringify(newTab.styles));\n          EDGE_LEGEND = JSON.parse(JSON.stringify(newTab.legend));\n          RECT_DATA = JSON.parse(JSON.stringify(newTab.rects));\n          TEXT_DATA = JSON.parse(JSON.stringify(newTab.texts));\n          PAGE_STATE = Object.assign({}, DEFAULT_PAGE_STATE, newTab.pageState || {});\n          wieldThePower();\n          forgeTheTopology();\n          currentNodeId = null;\n          currentEdgeId = null;\n          currentTextId = null;\n          document.getElementById(\"node-panel\").style.display = \"none\";\n          document.getElementById(\"edge-panel\").style.display = \"none\";\n          document.getElementById(\"text-panel\").style.display = \"none\";\n        }\n        displayTabs();\n        logAuditEvent(\"tab\", `Deleted tab`);\n      }\n      function saveCurrentTheme() {\n        const name = prompt(\"Enter a name for this theme:\", \"My Theme \" + (savedStyleSets.length + 1));\n        if (!name || !name.trim()) return;\n        const existingIndex = savedStyleSets.findIndex(s => s.name.toLowerCase() === name.trim().toLowerCase());\n        if (existingIndex !== -1) {\n          if (!confirm(\"A theme named \\\"\" + name + \"\\\" already exists. Replace it?\")) return;\n          savedStyleSets.splice(existingIndex, 1);\n        }\n        const styleSet = {\n          id: \"mytheme-\" + Date.now(),\n          name: name.trim(),\n          styles: JSON.parse(JSON.stringify(PAGE_STATE))\n        };\n        delete styleSet.styles.title;\n        delete styleSet.styles.viewOnly;\n        savedStyleSets.push(styleSet);\n        rebuildThemeDropdown();\n        document.getElementById(\"theme-preset\").value = styleSet.id;\n        updateDeleteButton();\n        logAuditEvent(\"style\", \"Saved theme: \" + name);\n      }\n      function deleteCurrentTheme() {\n        const select = document.getElementById(\"theme-preset\");\n        const val = select.value;\n        if (!val.startsWith(\"mytheme-\")) return;\n        const index = savedStyleSets.findIndex(s => s.id === val);\n        if (index === -1) return;\n        if (!confirm(\"Delete theme \\\"\" + savedStyleSets[index].name + \"\\\"?\")) return;\n        savedStyleSets.splice(index, 1);\n        rebuildThemeDropdown();\n        select.value = \"defaulted\";\n        updateDeleteButton();\n        logAuditEvent(\"style\", \"Deleted theme\");\n      }\n      function rebuildThemeDropdown() {\n        const group = document.getElementById(\"my-themes-group\");\n        if (!group) return;\n        const select = document.getElementById(\"theme-preset\");\n        const currentValue = select ? select.value : \"\";\n        group.innerHTML = \"\";\n        savedStyleSets.forEach(function(s) {\n          const opt = document.createElement(\"option\");\n          opt.value = s.id;\n          opt.textContent = s.name;\n          group.appendChild(opt);\n        });\n        if (select && currentValue) {\n          select.value = currentValue;\n        }\n      }\n      function updateDeleteButton() {\n        const select = document.getElementById(\"theme-preset\");\n        const btn = document.getElementById(\"delete-theme-btn\");\n        if (!btn) return;\n\t\tbtn.style.display = \"block\";\n\t\tbtn.disabled = !select.value.startsWith(\"mytheme-\");\n      }\n      function displayTabs() {\n        const listEl = document.getElementById(\"tabs-list\");\n        if (!listEl) return;\n        listEl.innerHTML = documentTabs.map((tab, index) => {\n          const nodeCount = Object.keys(tab.nodes).length;\n          const edgeCount = tab.edges.list.length;\n          const isActive = index === currentTabIndex;\n          return `\n            <div class=\"tab-item ${isActive ? 'active' : ''}\" onclick=\"switchTab(${index})\">\n              <div class=\"tab-name\">${escapeHtml(tab.name)}</div>\n              <div class=\"tab-stats\">${nodeCount} nodes • ${edgeCount} connections</div>\n              <div class=\"tab-actions\">\n                <button class=\"btn-cancel\" onclick=\"event.stopPropagation(); renameTab(${index})\" title=\"Rename tab\">✏️</button>\n                ${documentTabs.length > 1 ? '<button class=\"btn-cancel\" onclick=\"event.stopPropagation(); deleteTab(' + index + ')\" title=\"Delete tab\">🗑️</button>' : ''}\n              </div>\n            </div>\n          `;\n        }).join('');\n      }\n      function logAuditEvent(type, description, details = {}) {\n        const event = {\n          timestamp: Date.now(),\n          type,\n          description,\n          details,\n          tab: documentTabs[currentTabIndex]?.name || \"Main\"\n        };\n        auditLog.unshift(event);\n        if (auditLog.length > MAX_AUDIT_ENTRIES) {\n          auditLog = auditLog.slice(0, MAX_AUDIT_ENTRIES);\n        }\n        try {\n          localStorage.setItem(AUDIT_STORAGE_KEY, JSON.stringify(auditLog));\n        } catch (e) {\n          console.warn(\"Failed to save audit log:\", e);\n        }\n      }\n      function loadAuditLog() {\n        let embeddedLog = [];\n        let localLog = [];\n        try {\n          const stateScript = document.getElementById(\"topology-state\");\n          if (stateScript) {\n            const stateData = JSON.parse(stateScript.textContent);\n            if (stateData.auditLog && Array.isArray(stateData.auditLog)) {\n              embeddedLog = stateData.auditLog;\n            }\n          }\n        } catch (e) {\n          console.warn(\"Failed to load embedded audit log:\", e);\n        }\n        try {\n          const stored = localStorage.getItem(AUDIT_STORAGE_KEY);\n          if (stored) {\n            localLog = JSON.parse(stored);\n          }\n        } catch (e) {\n          console.warn(\"Failed to load localStorage audit log:\", e);\n        }\n        const merged = [...embeddedLog, ...localLog];\n        const seen = new Set();\n        auditLog = merged.filter(entry => {\n          const key = entry.timestamp + entry.description;\n          if (seen.has(key)) return false;\n          seen.add(key);\n          return true;\n        }).sort((a, b) => b.timestamp - a.timestamp);\n        if (auditLog.length > MAX_AUDIT_ENTRIES) {\n          auditLog = auditLog.slice(0, MAX_AUDIT_ENTRIES);\n        }\n        try {\n          localStorage.setItem(AUDIT_STORAGE_KEY, JSON.stringify(auditLog));\n        } catch (e) {\n          console.warn(\"Failed to sync audit log:\", e);\n        }\n      }\n      function displayAuditLog(filter = \"all\") {\n        const listEl = document.getElementById(\"audit-log-list\");\n        if (!listEl) return;\n        const filtered = filter === \"all\" ? auditLog : auditLog.filter(e => e.type === filter);\n        if (filtered.length === 0) {\n          listEl.innerHTML = '<div style=\"text-align: center; padding: 40px; color: var(--text-soft);\">No audit entries yet</div>';\n          return;\n        }\n        listEl.innerHTML = filtered.map(event => {\n          const date = new Date(event.timestamp);\n          const timeStr = date.toLocaleString();\n          return `\n            <div class=\"audit-entry ${escapeHtml(event.type)}\">\n              <div class=\"time\">[${escapeHtml(timeStr)}] ${escapeHtml(event.tab)}</div>\n              <div class=\"action\">[${escapeHtml(event.type.toUpperCase())}] ${escapeHtml(event.description)}</div>\n            </div>\n          `;\n        }).join('');\n      }\n      function clearAuditLog() {\n        if (!confirm(\"Clear all audit log entries?\\n\\nThis cannot be undone.\")) return;\n        auditLog = [];\n        localStorage.removeItem(AUDIT_STORAGE_KEY);\n        displayAuditLog();\n      }\n      function exportAuditLog() {\n        if (auditLog.length === 0) {\n          alert(\"No audit entries to export\");\n          return;\n        }\n        const csv = [\n          [\"Timestamp\", \"Tab\", \"Type\", \"Description\"],\n          ...auditLog.map(e => [\n            new Date(e.timestamp).toISOString(),\n            e.tab,\n            e.type,\n            e.description\n          ])\n        ].map(row => row.map(cell => `\"${cell}\"`).join(\",\")).join(\"\\n\");\n        const blob = new Blob([csv], { type: \"text/csv\" });\n        const url = URL.createObjectURL(blob);\n        const a = document.createElement(\"a\");\n        a.href = url;\n        a.download = `audit-log-${Date.now()}.csv`;\n        document.body.appendChild(a);\n        a.click();\n        document.body.removeChild(a);\n        URL.revokeObjectURL(url);\n      }\n      let currentSecretName = null;\n      function createNewSecret() {\n        const nameInput = document.getElementById(\"new-secret-name\");\n        const name = nameInput.value.trim();\n        if (!name) {\n          alert(\"Please enter a note name\");\n          return;\n        }\n        if (encryptedSections[name]) {\n          alert(\"A note note with this name already exists\");\n          return;\n        }\n        currentSecretName = name;\n        encryptedSections[name] = { encrypted: false, data: \"\" };\n        nameInput.value = \"\";\n        document.getElementById(\"secret-editor-title\").textContent = `New note: ${name}`;\n        document.getElementById(\"secret-editor-content\").value = \"\";\n        document.getElementById(\"secrets-modal\").classList.remove(\"active\");\n        document.getElementById(\"secret-editor-modal\").classList.add(\"active\");\n        displaySecrets();\n      }\n      function editSecret(name) {\n        currentSecretName = name;\n        const section = encryptedSections[name];\n        if (section.encrypted) {\n          const password = prompt(`Enter password to decrypt \"${name}\":`);\n          if (!password) return;\n          try {\n            decryptData(section.data, password).then(decrypted => {\n              document.getElementById(\"secret-editor-title\").textContent = `Edit Secret: ${name}`;\n              document.getElementById(\"secret-editor-content\").value = decrypted;\n              document.getElementById(\"secrets-modal\").classList.remove(\"active\");\n              document.getElementById(\"secret-editor-modal\").classList.add(\"active\");\n            }).catch(e => {\n              alert(\"Failed to decrypt. Wrong password?\");\n            });\n          } catch (e) {\n            alert(\"Failed to decrypt. Wrong password?\");\n          }\n        } else {\n          document.getElementById(\"secret-editor-title\").textContent = `Edit note: ${name}`;\n          document.getElementById(\"secret-editor-content\").value = section.data;\n          document.getElementById(\"secrets-modal\").classList.remove(\"active\");\n          document.getElementById(\"secret-editor-modal\").classList.add(\"active\");\n        }\n      }\n      async function saveSecret() {\n        if (!currentSecretName) return;\n        const content = document.getElementById(\"secret-editor-content\").value;\n        const autoEncrypt = document.getElementById(\"secret-auto-encrypt\").checked;\n        if (autoEncrypt) {\n          const password = prompt(`Enter password to encrypt \"${currentSecretName}\":`);\n          if (!password) return;\n          const confirmPassword = prompt(\"Confirm password:\");\n          if (password !== confirmPassword) {\n            alert(\"Passwords do not match\");\n            return;\n          }\n          try {\n            const encrypted = await encryptData(content, password);\n            encryptedSections[currentSecretName] = { encrypted: true, data: encrypted };\n          } catch (e) {\n            alert(\"Encryption failed: \" + e.message);\n            return;\n          }\n        } else {\n          encryptedSections[currentSecretName] = { encrypted: false, data: content };\n        }\n        closeSecretEditor();\n        displaySecrets();\n        logAuditEvent(\"secret\", `Saved note section: ${currentSecretName}`);\n      }\n      function closeSecretEditor() {\n        document.getElementById(\"secret-editor-modal\").classList.remove(\"active\");\n        document.getElementById(\"secrets-modal\").classList.add(\"active\");\n        currentSecretName = null;\n      }\n      function deleteSecret(name) {\n        if (!confirm(`Delete note \"${name}\"?`)) return;\n        delete encryptedSections[name];\n        displaySecrets();\n        logAuditEvent(\"secret\", `Deleted note: ${name}`);\n      }\n      function displaySecrets() {\n        const listEl = document.getElementById(\"secrets-list\");\n        if (!listEl) return;\n        const secrets = Object.keys(encryptedSections);\n        if (secrets.length === 0) {\n          listEl.innerHTML = '<div style=\"text-align: center; padding: 40px; color: var(--text-soft);\">No notes yet</div>';\n          return;\n        }\n        listEl.innerHTML = secrets.map(name => {\n          const section = encryptedSections[name];\n          const status = section.encrypted ? \"🔒 Encrypted\" : \"🔓 Plaintext\";\n          return `\n            <div class=\"secret-item\">\n              <div class=\"secret-name\">${escapeHtml(name)}</div>\n              <div class=\"secret-status\">${status}</div>\n              <div class=\"tab-actions\">\n                <button class=\"btn-cancel\" onclick=\"editSecret('${escapeHtml(name)}')\" title=\"Edit note\">✏️</button>\n                <button class=\"btn-cancel\" onclick=\"deleteSecret('${escapeHtml(name)}')\" title=\"Delete note\">🗑️</button>\n              </div>\n            </div>\n          `;\n        }).join('');\n      }\n       const clearAllBtn = document.getElementById(\"clear-all-btn\");\n       const clearAllModal = document.getElementById(\"clear-all-modal\");\n       const clearAllCancel = document.getElementById(\"clear-all-cancel\");\n       const clearAllConfirm = document.getElementById(\"clear-all-confirm\");\n       clearAllBtn.addEventListener(\"click\", () => {\n        clearAllModal.classList.add(\"active\");\n       });\n       clearAllCancel.addEventListener(\"click\", () => {\n        clearAllModal.classList.remove(\"active\");\n       });\n       clearAllModal.addEventListener(\"click\", (e) => {\n        if (e.target === clearAllModal) {\n         clearAllModal.classList.remove(\"active\");\n        }\n       });\n       clearAllConfirm.addEventListener(\"click\", () => {\n        NODE_DATA = {};\n        EDGE_DATA = { list: [] };\n        EDGE_LEGEND = {};\n        RECT_DATA = { list: [] };\n        TEXT_DATA = { list: [] };\n        savedPositions = {};\n        savedSizes = {};\n        savedStyles = {};\n        undoStack = [];\n        redoStack = [];\n        updateUndoButtons();\n        auditLog = [];\n        localStorage.removeItem(AUDIT_STORAGE_KEY);\n        rollbackVersions = [];\n        currentRollbackIndex = -1;\n        localStorage.removeItem(ROLLBACK_STORAGE_KEY);\n        clipboard = null;\n        selectedNodes.clear();\n        selectedEdges.clear();\n        selectedRects.clear();\n        selectedTexts.clear();\n        documentTabs = [{\n          id: \"main\",\n          name: \"Main Topology\",\n          nodes: {},\n          edges: { list: [] },\n          positions: {},\n          sizes: {},\n          styles: {},\n          legend: {},\n          rects: { list: [] },\n          texts: { list: [] },\n          pageState: null\n        }];\n        currentTabIndex = 0;\n        displayTabs();\n        clearAutosave();\n        try {\n          Object.keys(localStorage).forEach(key => {\n            if (key.startsWith(\"theonefile\")) {\n              localStorage.removeItem(key);\n            }\n          });\n        } catch(e) {}\n        const addLineSelect = document.getElementById(\"add-line-select\");\n        if (addLineSelect) addLineSelect.innerHTML = \"\";\n        const edgeLegendContent = document.getElementById(\"edge-legend-content\");\n        if (edgeLegendContent) edgeLegendContent.innerHTML = \"\";\n        clearAllModal.classList.remove(\"active\");\n        forgeTheTopology();\n        document.getElementById(\"node-panel\").style.display = \"none\";\n        document.getElementById(\"edge-panel\").style.display = \"none\";\n        document.getElementById(\"text-panel\").style.display = \"none\";\n        document.getElementById(\"topology-toolbar\").style.display = \"none\";\n        currentNodeId = null;\n        currentEdgeId = null;\n        currentTextId = null;\n        if (typeof displayRollbackVersions === \"function\") displayRollbackVersions();\n        if (typeof displayAuditLog === \"function\") displayAuditLog();\n        if (typeof forgeTheLegend === \"function\") forgeTheLegend();\n        if (typeof updateZoneLegend === \"function\") updateZoneLegend();\n      });\n       (function addDeleteNodeButton() {\n       const nodePanel = document.getElementById(\"node-panel\");\n       if (!nodePanel) return;\n       let deleteBtn = document.getElementById(\"delete-node-btn\");\n       if (!deleteBtn) {\n        deleteBtn = document.createElement(\"button\");\n        deleteBtn.id = \"delete-node-btn\";\n        deleteBtn.textContent = \"Delete Node\";\n        deleteBtn.style.cssText = \"margin-top:15px; padding:10px 16px; background:var(--danger); color:#fff; border:none; border-radius:6px; cursor:pointer; font-size:clamp(14px,1.6vw,18px); font-weight:600; width:100%;\";\n        nodePanel.appendChild(deleteBtn);\n       }\n       deleteBtn.addEventListener(\"click\", () => {\n        if (!currentNodeId) return;\n        const nodeData = NODE_DATA[currentNodeId];\n        let confirmMessage;\n        let nodesInsideRack = [];\n        if (nodeData?.isRack) {\n         nodesInsideRack = Object.entries(NODE_DATA)\n          .filter(([id, n]) => n.assignedRack === currentNodeId)\n          .map(([id, n]) => n.name || id);\n         if (nodesInsideRack.length > 0) {\n          confirmMessage = `Delete rack \"${nodeData.name}\"?\\n\\nThis will also delete ${nodesInsideRack.length} node(s) inside:\\n• ${nodesInsideRack.join('\\n• ')}`;\n         } else {\n          confirmMessage = `Delete rack \"${nodeData.name}\"? (empty rack)`;\n         }\n        } else {\n         confirmMessage = `Delete node \"${nodeData?.name || currentNodeId}\" and all its connections?`;\n        }\n        challengeTheImmortal(confirmMessage, () => {\n         pushUndo(\"delete node\");\n         if (nodeData?.isRack) {\n          Object.keys(NODE_DATA).forEach(id => {\n           if (NODE_DATA[id]?.assignedRack === currentNodeId) {\n            EDGE_DATA.list = EDGE_DATA.list.filter(e => e.from !== id && e.to !== id);\n            delete NODE_DATA[id];\n            delete savedPositions[id];\n            delete savedSizes[id];\n            delete savedStyles[id];\n           }\n          });\n         }\n         EDGE_DATA.list = EDGE_DATA.list.filter(e => e.from !== currentNodeId && e.to !== currentNodeId);\n         delete NODE_DATA[currentNodeId];\n         delete savedPositions[currentNodeId];\n         delete savedSizes[currentNodeId];\n         delete savedStyles[currentNodeId];\n         currentNodeId = null;\n         currentEdgeId = null;\n         forgeTheTopology();\n         const remainingNodes = Object.keys(NODE_DATA);\n         if (remainingNodes.length > 0) {\n          claimTheImmortal(remainingNodes[0]);\n         } else {\n          document.getElementById(\"node-panel\").style.display = \"none\";\n          document.getElementById(\"edge-panel\").style.display = \"none\";\n          document.getElementById(\"topology-toolbar\").style.display = \"none\";\n         }\n        });\n       });\n      })();\n       function screenshotCanvas() {\n        const svg = document.getElementById(\"map\");\n        const svgClone = svg.cloneNode(true);\n        const viewBox = svg.getAttribute(\"viewBox\").split(\" \").map(Number);\n        const [x, y, width, height] = viewBox;\n        svgClone.setAttribute(\"width\", width);\n        svgClone.setAttribute(\"height\", height);\n        svgClone.setAttribute(\"viewBox\", `${x} ${y} ${width} ${height}`);\n        const wrapper = document.createElement(\"div\");\n        wrapper.style.position = \"absolute\";\n        wrapper.style.left = \"-9999px\";\n        wrapper.appendChild(svgClone);\n        document.body.appendChild(wrapper);\n        function inlineStyles(original, clone) {\n         const elements = original.querySelectorAll(\"*\");\n         const clonedElements = clone.querySelectorAll(\"*\");\n         const rootStyles = getComputedStyle(document.documentElement);\n         const bgColor = rootStyles.getPropertyValue(\"--bg\").trim() || \"#050608\";\n         const bgRect = document.createElementNS(\"http://www.w3.org/2000/svg\", \"rect\");\n         bgRect.setAttribute(\"x\", x);\n         bgRect.setAttribute(\"y\", y);\n         bgRect.setAttribute(\"width\", width);\n         bgRect.setAttribute(\"height\", height);\n         bgRect.setAttribute(\"fill\", bgColor);\n         clone.insertBefore(bgRect, clone.firstChild);\n         elements.forEach((el, index) => {\n          const clonedEl = clonedElements[index];\n          if (!clonedEl) return;\n          const computedStyle = getComputedStyle(el);\n          const svgProps = [\"fill\", \"stroke\", \"stroke-width\", \"stroke-dasharray\", \"stroke-linecap\", \"stroke-linejoin\", \"opacity\", \"font-family\", \"font-size\", \"font-weight\", \"font-style\", \"text-anchor\", \"dominant-baseline\", \"marker-start\", \"marker-end\", ];\n          svgProps.forEach((prop) => {\n           const value = computedStyle.getPropertyValue(prop);\n           if (value && value !== \"none\" && value !== \"normal\") {\n            clonedEl.style[prop] = value;\n           }\n          });\n          clonedEl.removeAttribute(\"class\");\n         });\n        }\n        inlineStyles(svg, svgClone);\n        const svgData = new XMLSerializer().serializeToString(svgClone);\n        document.body.removeChild(wrapper);\n        const svgBlob = new Blob([svgData], {\n         type: \"image/svg+xml;charset=utf-8\",\n        });\n        const url = URL.createObjectURL(svgBlob);\n        const img = new Image();\n        img.onload = function() {\n         const canvas = document.createElement(\"canvas\");\n         canvas.width = width;\n         canvas.height = height;\n         const ctx = canvas.getContext(\"2d\");\n         ctx.drawImage(img, 0, 0);\n         canvas.toBlob(function(blob) {\n          const link = document.createElement(\"a\");\n          const timestamp = new Date().toISOString().slice(0, 10);\n          link.download = `topology-${timestamp}.png`;\n          link.href = URL.createObjectURL(blob);\n          link.click();\n          URL.revokeObjectURL(url);\n          URL.revokeObjectURL(link.href);\n         }, \"image/png\");\n        };\n        img.onerror = function() {\n         console.error(\"Failed to load SVG image\");\n         alert(\"Screenshot failed. Please try again.\");\n         URL.revokeObjectURL(url);\n        };\n        img.src = url;\n       }\n       function exportCanvasSVG() {\n        const svg = document.getElementById(\"map\");\n        const svgClone = svg.cloneNode(true);\n        const viewBox = svg.getAttribute(\"viewBox\").split(\" \").map(Number);\n        const [x, y, width, height] = viewBox;\n        svgClone.setAttribute(\"width\", width);\n        svgClone.setAttribute(\"height\", height);\n        const rootStyles = getComputedStyle(document.documentElement);\n        const bgColor = rootStyles.getPropertyValue(\"--bg\").trim() || \"#050608\";\n        const bgRect = document.createElementNS(\"http://www.w3.org/2000/svg\", \"rect\");\n        bgRect.setAttribute(\"x\", x);\n        bgRect.setAttribute(\"y\", y);\n        bgRect.setAttribute(\"width\", width);\n        bgRect.setAttribute(\"height\", height);\n        bgRect.setAttribute(\"fill\", bgColor);\n        svgClone.insertBefore(bgRect, svgClone.firstChild);\n        const wrapper = document.createElement(\"div\");\n        wrapper.style.position = \"absolute\";\n        wrapper.style.left = \"-9999px\";\n        wrapper.appendChild(svgClone);\n        document.body.appendChild(wrapper);\n        const elements = svg.querySelectorAll(\"*\");\n        const clonedElements = svgClone.querySelectorAll(\"*\");\n        elements.forEach((el, index) => {\n         const clonedEl = clonedElements[index];\n         if (!clonedEl) return;\n         const computedStyle = getComputedStyle(el);\n         const svgProps = [\"fill\", \"stroke\", \"stroke-width\", \"stroke-dasharray\", \"stroke-linecap\", \"stroke-linejoin\", \"opacity\", \"font-family\", \"font-size\", \"font-weight\", \"font-style\", \"text-anchor\", \"dominant-baseline\", \"marker-start\", \"marker-end\", ];\n         svgProps.forEach((prop) => {\n          const value = computedStyle.getPropertyValue(prop);\n          if (value && value !== \"none\" && value !== \"normal\") {\n           clonedEl.setAttribute(prop, value);\n          }\n         });\n         clonedEl.removeAttribute(\"class\");\n        });\n        const svgData = new XMLSerializer().serializeToString(svgClone);\n        document.body.removeChild(wrapper);\n        const blob = new Blob([svgData], {\n         type: \"image/svg+xml;charset=utf-8\",\n        });\n        const url = URL.createObjectURL(blob);\n        const link = document.createElement(\"a\");\n        const timestamp = new Date().toISOString().slice(0, 10);\n        link.download = `topology-${timestamp}.svg`;\n        link.href = url;\n        link.click();\n        URL.revokeObjectURL(url);\n       }\n      let resizeTimeout;\n       window.addEventListener('resize', () => {\n       clearTimeout(resizeTimeout);\n       resizeTimeout = setTimeout(() => {\n       updateDrawToolbarVisibility();\n       updateTopologyToolbarVisibility();\n       }, 100);\n      });\ndocument.addEventListener('DOMContentLoaded', () => {\n  document.querySelectorAll('.dropdown').forEach(dropdown => {\n    const btn = dropdown.querySelector('.dropdown-btn');\n    const menu = dropdown.querySelector('.dropdown-menu');\n    if (!btn || !menu) return;\n    btn.addEventListener('click', (e) => {\n      e.stopPropagation();\n      document.querySelectorAll('.dropdown-menu.open').forEach(m => {\n        if (m !== menu) m.classList.remove('open');\n      });\n      menu.classList.toggle('open');\n    });\n  });\n  document.addEventListener('click', (e) => {\n    if (!e.target.closest('.dropdown')) {\n      document.querySelectorAll('.dropdown-menu.open').forEach(m => m.classList.remove('open'));\n    }\n  });\n  document.querySelectorAll('.dropdown-menu button').forEach(btn => {\n    btn.addEventListener('click', () => {\n      setTimeout(() => {\n        document.querySelectorAll('.dropdown-menu.open').forEach(m => m.classList.remove('open'));\n      }, 100);\n    });\n  });\n  });\nfunction showFormatHelp() {\n  document.getElementById('save-info-modal').classList.add('active');\n  document.querySelectorAll('.help-tab').forEach(t => {\n    t.style.background = 'var(--panel)';\n    t.style.color = 'var(--text-main)';\n  });\n  document.querySelectorAll('.help-tab-content').forEach(c => c.style.display = 'none');\n  const formatsTab = document.querySelector('.help-tab[data-tab=\"formats\"]');\n  if (formatsTab) {\n    formatsTab.style.background = 'var(--accent)';\n    formatsTab.style.color = 'var(--bg)';\n  }\n  document.getElementById('help-tab-formats').style.display = 'block';\n}\nfunction printTopology() {\n  const svg = document.getElementById('map');\n  if (!svg) { window.print(); return; }\n  const originalViewBox = svg.getAttribute('viewBox');\n  let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;\n  let hasContent = false;\n  Object.entries(savedPositions).forEach(([id, pos]) => {\n    const node = NODE_DATA[id];\n    if (!node) return;\n    if (currentView.mode !== 'rack' && node.assignedRack) return;\n    const size = savedSizes[id] || 50;\n    hasContent = true;\n    minX = Math.min(minX, pos.x - size);\n    minY = Math.min(minY, pos.y - size);\n    maxX = Math.max(maxX, pos.x + size);\n    maxY = Math.max(maxY, pos.y + size);\n  });\n  RECT_DATA.list.forEach(rect => {\n    hasContent = true;\n    minX = Math.min(minX, rect.x);\n    minY = Math.min(minY, rect.y);\n    maxX = Math.max(maxX, rect.x + rect.width);\n    maxY = Math.max(maxY, rect.y + rect.height);\n  });\n  TEXT_DATA.list.forEach(text => {\n    hasContent = true;\n    minX = Math.min(minX, text.x - 100);\n    minY = Math.min(minY, text.y - 50);\n    maxX = Math.max(maxX, text.x + 300);\n    maxY = Math.max(maxY, text.y + 50);\n  });\n  EDGE_DATA.list.forEach(edge => {\n    if (edge.points && edge.points.length > 0) {\n      edge.points.forEach(p => {\n        hasContent = true;\n        minX = Math.min(minX, p.x - 10);\n        minY = Math.min(minY, p.y - 10);\n        maxX = Math.max(maxX, p.x + 10);\n        maxY = Math.max(maxY, p.y + 10);\n      });\n    }\n  });\n  if (!hasContent) { window.print(); return; }\n  const padding = 100;\n  minX -= padding; minY -= padding; maxX += padding; maxY += padding;\n  const width = maxX - minX;\n  const height = maxY - minY;\n  const grid = document.getElementById('canvas-grid');\n  const gridDisplay = grid ? grid.style.display : '';\n  if (grid) grid.style.display = 'none';\n  svg.setAttribute('viewBox', `${minX} ${minY} ${width} ${height}`);\n  const originalWidth = svg.style.width;\n  const originalHeight = svg.style.height;\n  svg.style.width = '100%';\n  svg.style.height = '100%';\n  setTimeout(() => {\n    window.print();\n    setTimeout(() => {\n      svg.setAttribute('viewBox', originalViewBox);\n      svg.style.width = originalWidth;\n      svg.style.height = originalHeight;\n      if (grid) grid.style.display = gridDisplay;\n    }, 500);\n  }, 100);\n}\nfunction exportJSONFile() {\n  const data = captureTheQuickening();\n  const jsonStr = JSON.stringify(data, null, 2);\n  const blob = new Blob([jsonStr], { type: \"application/json\" });\n  const url = URL.createObjectURL(blob);\n  const a = document.createElement(\"a\");\n  a.href = url;\n  const safeTitle = (PAGE_STATE.title || \"network-topology\").toLowerCase().replace(/[^a-z0-9\\-]+/g, \"-\");\n  a.download = `${safeTitle}.json`;\n  a.click();\n  URL.revokeObjectURL(url);\n  logAuditEvent(\"export\", `Exported JSON: ${a.download}`);\n}\nfunction exportCSV() {\n  const safeTitle = (PAGE_STATE.title || 'topology').toLowerCase().replace(/[^a-z0-9]+/g, '-');\n  const timestamp = new Date().toISOString();\n  const config = captureTheQuickening();\n  let csv = `#THEONEFILE_CONFIG:${JSON.stringify(config)}\\n`;\n  csv += `#\\n# ${PAGE_STATE.title || 'Network Topology'} - Node List\\n`;\n  csv += `# Exported from The One File on ${timestamp}\\n`;\n  const headers = ['name','ip','role','shape','tags','layer','mac','rackUnit','uHeight','assignedRack','rackCapacity','isRack','locked','groupId','x','y','size','notes','styles'];\n  csv += headers.join(',') + '\\n';\n  Object.entries(NODE_DATA).forEach(([id, node]) => {\n    const pos = savedPositions[id] || { x: 0, y: 0 };\n    const size = savedSizes[id] || 50;\n    const styles = savedStyles[id] ? JSON.stringify(savedStyles[id]) : '';\n    const row = [\n      csvEscape(node.name || ''), csvEscape(node.ip || ''), csvEscape(node.role || ''),\n      node.shape || 'circle', csvEscape((node.tags || []).join(';')), node.layer || 'physical',\n      csvEscape(node.mac || ''), csvEscape(node.rackUnit || ''), node.uHeight || '1',\n      node.assignedRack || '', node.rackCapacity || '', node.isRack ? 'true' : 'false',\n      node.locked ? 'true' : 'false', node.groupId || '', Math.round(pos.x), Math.round(pos.y),\n      size, csvEscape((node.notes || []).join('|')), csvEscape(styles)\n    ];\n    csv += row.join(',') + '\\n';\n  });\n  const blob = new Blob([csv], { 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 = `${safeTitle}.csv`;\n  a.click();\n  URL.revokeObjectURL(url);\n  logAuditEvent('export', `Exported CSV: ${a.download}`);\n}\nfunction csvEscape(val) {\n  if (val === null || val === undefined) return '';\n  const str = String(val);\n  if (str.includes(',') || str.includes('\"') || str.includes('\\n') || str.includes('\\r')) {\n    return '\"' + str.replace(/\"/g, '\"\"') + '\"';\n  }\n  return str;\n}\ndocument.getElementById('import-csv-file')?.addEventListener('change', async (e) => {\n  const file = e.target.files[0];\n  if (!file) return;\n  e.target.value = '';\n  try {\n    const text = await file.text();\n    const lines = text.split(/\\r?\\n/);\n    let config = null;\n    let dataLines = [];\n    let headers = null;\n    for (const line of lines) {\n      const trimmed = line.trim();\n      if (!trimmed) continue;\n      if (trimmed.startsWith('#THEONEFILE_CONFIG:')) {\n        try { config = JSON.parse(trimmed.substring(19)); } catch (err) { console.warn('Failed to parse CSV config:', err); }\n        continue;\n      }\n      if (trimmed.startsWith('#')) continue;\n      if (!headers) { headers = parseCSVLine(trimmed).map(h => h.toLowerCase().trim()); continue; }\n      dataLines.push(trimmed);\n    }\n    if (!headers || dataLines.length === 0) { alert('CSV file has no data rows'); return; }\n    const nameIdx = headers.indexOf('name');\n    if (nameIdx === -1) { alert('CSV must have a \"name\" column'); return; }\n    const nodes = dataLines.map(line => {\n      const values = parseCSVLine(line);\n      const node = {};\n      headers.forEach((h, idx) => { node[h] = values[idx] || ''; });\n      return node;\n    });\n    const hasFullBackup = config && config.documentTabs;\n    const hasConfig = config && (config.pageState || config.page);\n    \n    let importMode = 'add';\n    \n    if (hasFullBackup) {\n      const choice = confirm(\n        `This CSV contains a full backup.\\n\\n` +\n        `• ${nodes.length} nodes in CSV data\\n` +\n        `• ${config.documentTabs?.length || 1} tab(s) in backup\\n` +\n        `• ${config.edgeData?.list?.length || 0} connections in backup\\n\\n` +\n        `Click OK for FULL RESTORE (replace all data)\\n` +\n        `Click Cancel to just ADD the ${nodes.length} nodes`\n      );\n      if (choice) {\n        importMode = 'full';\n      }\n    } else {\n      let confirmMsg = `Import ${nodes.length} nodes from CSV?\\n\\n`;\n      confirmMsg += hasConfig ? 'This will ADD nodes and RESTORE settings/theme.\\n' : 'This will ADD nodes to your existing topology.\\n';\n      confirmMsg += '\\nNote: CSV does not include connections, zones, or text labels.';\n      if (!confirm(confirmMsg)) return;\n    }\n    \n    pushUndo('import csv');\n    \n    if (importMode === 'full' && hasFullBackup) {\n      NODE_DATA = config.nodeData || {};\n      EDGE_DATA = config.edgeData || { list: [] };\n      EDGE_LEGEND = config.edgeLegend || {};\n      RECT_DATA = config.rectData || { list: [] };\n      TEXT_DATA = config.textData || { list: [] };\n      savedPositions = config.nodePositions || {};\n      savedSizes = config.nodeSizes || {};\n      savedStyles = config.nodeStyles || {};\n      if (config.page) {\n        PAGE_STATE = Object.assign({}, DEFAULT_PAGE_STATE, config.page);\n        wieldThePower();\n      }\n      if (config.canvas) {\n        canvasState.zoom = config.canvas.zoom || 1;\n        canvasState.panX = config.canvas.panX || 0;\n        canvasState.panY = config.canvas.panY || 0;\n      }\n      if (config.page?.title) {\n        document.title = config.page.title;\n        document.querySelector(\".editable-page-title\").textContent = config.page.title;\n      }\n      if (config.savedStyleSets) {\n        savedStyleSets = config.savedStyleSets;\n      }\n      if (config.documentTabs) {\n        documentTabs = config.documentTabs;\n        currentTabIndex = config.currentTabIndex || 0;\n      }\n      if (config.savedTopologyView) {\n        savedTopologyView = config.savedTopologyView;\n      }\n      if (config.encryptedSections) {\n        encryptedSections = config.encryptedSections;\n      }\n      if (config.auditLog && Array.isArray(config.auditLog)) {\n        auditLog = config.auditLog;\n        try {\n          localStorage.setItem(AUDIT_STORAGE_KEY, JSON.stringify(auditLog));\n        } catch (e) {\n          console.warn(\"Failed to sync audit log to localStorage:\", e);\n        }\n      }\n      Object.values(NODE_DATA).forEach(node => {\n        if (!node.tags) node.tags = [];\n        if (!node.notes) node.notes = [];\n      });\n      EDGE_DATA.list.forEach(edge => {\n        if (!edge.notes) edge.notes = [];\n      });\n      forgeTheTopology();\n      if (typeof forgeTheLegend === 'function') forgeTheLegend();\n      if (typeof updateZoneLegend === 'function') updateZoneLegend();\n      updateViewBox();\n      logAuditEvent('import', `Imported CSV (full restore): ${file.name}`);\n      alert(`Full restore complete from CSV backup`);\n      return;\n    }\n    if (hasConfig) {\n      Object.assign(PAGE_STATE, config.pageState || config.page);\n      if (config.canvasView || config.canvas) {\n        const canvasConfig = config.canvasView || config.canvas;\n        canvasState.zoom = canvasConfig.zoom || 1;\n        canvasState.panX = canvasConfig.panX || 0;\n        canvasState.panY = canvasConfig.panY || 0;\n      }\n      if (config.legend || config.edgeLegend) Object.assign(EDGE_LEGEND, config.legend || config.edgeLegend);\n      wieldThePower();\n    }\n    let gridX = 200, gridY = 200;\n    const spacing = 150;\n    const perRow = Math.ceil(Math.sqrt(nodes.length));\n    let gridIndex = 0;\n    nodes.forEach((n) => {\n      let baseId = (n.name || 'node').toLowerCase().replace(/[^a-z0-9]+/g, '-');\n      if (!baseId) baseId = 'node';\n      let nodeId = baseId;\n      let counter = 1;\n      while (NODE_DATA[nodeId]) { nodeId = `${baseId}-${counter}`; counter++; }\n      NODE_DATA[nodeId] = {\n        name: n.name || 'Unnamed', ip: n.ip || '', role: n.role || '', shape: n.shape || 'circle',\n        tags: n.tags ? n.tags.split(';').map(t => t.trim()).filter(t => t) : [],\n        notes: n.notes ? n.notes.split('|').map(t => t.trim()).filter(t => t) : [],\n        layer: n.layer || 'physical', mac: n.mac || '', rackUnit: n.rackunit || '',\n        uHeight: n.uheight || '1', assignedRack: n.assignedrack || '', rackCapacity: n.rackcapacity || '',\n        isRack: n.israck === 'true', locked: n.locked === 'true', groupId: n.groupid || null\n      };\n      const hasPosition = n.x && n.y && !isNaN(parseFloat(n.x)) && !isNaN(parseFloat(n.y));\n      if (hasPosition) {\n        savedPositions[nodeId] = { x: parseFloat(n.x), y: parseFloat(n.y) };\n      } else {\n        const row = Math.floor(gridIndex / perRow);\n        const col = gridIndex % perRow;\n        savedPositions[nodeId] = { x: gridX + col * spacing, y: gridY + row * spacing };\n        gridIndex++;\n      }\n      if (n.size && !isNaN(parseFloat(n.size))) savedSizes[nodeId] = parseFloat(n.size);\n      if (n.styles) { try { savedStyles[nodeId] = JSON.parse(n.styles); } catch (err) {} }\n    });\n    forgeTheTopology();\n    updateViewBox();\n    logAuditEvent('import', `Imported CSV: ${file.name} (${nodes.length} nodes)`);\n    alert(`Successfully imported ${nodes.length} nodes`);\n  } catch (err) {\n    console.error('CSV import error:', err);\n    alert('Failed to import CSV: ' + err.message);\n  }\n});\nfunction parseCSVLine(line) {\n  const result = [];\n  let current = '';\n  let inQuotes = false;\n  for (let i = 0; i < line.length; i++) {\n    const char = line[i];\n    if (char === '\"') {\n      if (inQuotes && line[i + 1] === '\"') { current += '\"'; i++; }\n      else { inQuotes = !inQuotes; }\n    } else if (char === ',' && !inQuotes) { result.push(current); current = ''; }\n    else { current += char; }\n  }\n  result.push(current);\n  return result;\n}\n\nfunction exportMarkdown() {\n  const safeTitle = (PAGE_STATE.title || 'topology').toLowerCase().replace(/[^a-z0-9]+/g, '-');\n  const timestamp = new Date().toISOString();\n  const title = PAGE_STATE.title || 'Network Topology';\n  const fullData = captureTheQuickening();\n  const config = fullData;\n  let md = `<!--THEONEFILE_CONFIG\\n${JSON.stringify(config, null, 2)}\\nTHEONEFILE_CONFIG-->\\n\\n`;\n  md += `# ${title}\\n\\n> Exported from The One File on ${timestamp}\\n\\n`;\n  md += `## Legend\\n\\n`;\n  if (Object.keys(EDGE_LEGEND).length > 0) {\n    Object.entries(EDGE_LEGEND).forEach(([color, label]) => { md += `- ${color}: ${label}\\n`; });\n  } else { md += `_No legend entries_\\n`; }\n  md += '\\n## Nodes\\n\\n';\n  Object.entries(NODE_DATA).forEach(([id, node]) => {\n    const pos = savedPositions[id] || { x: 0, y: 0 };\n    const size = savedSizes[id] || 50;\n    const styles = savedStyles[id] || null;\n    md += `### ${id}\\n`;\n    md += `- **Name:** ${node.name || ''}\\n- **IP:** ${node.ip || ''}\\n- **Role:** ${node.role || ''}\\n`;\n    md += `- **Shape:** ${node.shape || 'circle'}\\n- **Tags:** ${(node.tags || []).join('; ') || '_none_'}\\n`;\n    md += `- **Layer:** ${node.layer || 'physical'}\\n- **MAC:** ${node.mac || ''}\\n`;\n    md += `- **Rack Unit:** ${node.rackUnit || ''}\\n- **U Height:** ${node.uHeight || '1'}\\n`;\n    md += `- **Assigned Rack:** ${node.assignedRack || ''}\\n- **Rack Capacity:** ${node.rackCapacity || ''}\\n`;\n    md += `- **Is Rack:** ${node.isRack ? 'true' : 'false'}\\n- **Locked:** ${node.locked ? 'true' : 'false'}\\n`;\n    md += `- **Group ID:** ${node.groupId || ''}\\n- **Position:** ${Math.round(pos.x)}, ${Math.round(pos.y)}\\n- **Size:** ${size}\\n`;\n    if (node.notes && node.notes.length > 0) { md += `- **Notes:**\\n`; node.notes.forEach(note => { md += `  - ${note.replace(/\\n/g, '<br>')}\\n`; }); }\n    if (styles) { md += `- **Styles:** \\`${JSON.stringify(styles)}\\`\\n`; }\n    md += '\\n';\n  });\n  md += `## Connections\\n\\n`;\n  if (EDGE_DATA.list && EDGE_DATA.list.length > 0) {\n    EDGE_DATA.list.forEach(edge => {\n      const fromPort = edge.fromPort ? ` (${edge.fromPort})` : '';\n      const toPort = edge.toPort ? ` (${edge.toPort})` : '';\n      md += `- ${edge.from}${fromPort} --> ${edge.to}${toPort}\\n`;\n      md += `  - **ID:** ${edge.id}\\n  - **Label:** ${(edge.label || '').replace(/\\n/g, '<br>')}\\n  - **Color:** ${edge.color || ''}\\n`;\n      md += `  - **Width:** ${edge.width || 4}\\n  - **Direction:** ${edge.direction || 'none'}\\n`;\n      md += `  - **Routing:** ${edge.routing || 'curved'}\\n  - **Type:** ${edge.type || 'main'}\\n`;\n      md += `  - **Line Style:** ${edge.lineStyle || 'solid'}\\n  - **Group ID:** ${edge.groupId || ''}\\n`;\n      if (edge.points && edge.points.length > 0) { md += `  - **Points:** ${edge.points.map(p => `${Math.round(p.x)},${Math.round(p.y)}`).join(' ')}\\n`; }\n      if (edge.notes && edge.notes.length > 0) { md += `  - **Notes:**\\n`; edge.notes.forEach(note => { md += `    - ${note.replace(/\\n/g, '<br>')}\\n`; }); }\n      md += '\\n';\n    });\n  } else { md += `_No connections_\\n\\n`; }\n  md += `## Zones\\n\\n`;\n  if (RECT_DATA.list && RECT_DATA.list.length > 0) {\n    RECT_DATA.list.forEach(rect => {\n      md += `### ${rect.id}\\n`;\n      md += `- **Position:** ${Math.round(rect.x)}, ${Math.round(rect.y)}\\n- **Size:** ${Math.round(rect.width)} x ${Math.round(rect.height)}\\n`;\n      md += `- **Color:** ${rect.color || ''}\\n- **Style:** ${rect.style || 'filled'}\\n- **Line Style:** ${rect.lineStyle || 'solid'}\\n`;\n      md += `- **Border Color:** ${rect.borderColor || ''}\\n- **Border Width:** ${rect.borderWidth !== undefined ? rect.borderWidth : 2}\\n`;\n      if (rect.notes && rect.notes.length > 0) { md += `- **Notes:**\\n`; rect.notes.forEach(note => { md += `  - ${note.replace(/\\n/g, '<br>')}\\n`; }); }\n      md += '\\n';\n    });\n  } else { md += `_No zones_\\n\\n`; }\n  md += `## Text Labels\\n\\n`;\n  if (TEXT_DATA.list && TEXT_DATA.list.length > 0) {\n    TEXT_DATA.list.forEach(text => {\n      md += `### ${text.id}\\n`;\n      md += `- **Content:** ${(text.content || '').replace(/\\n/g, '<br>')}\\n- **Position:** ${Math.round(text.x)}, ${Math.round(text.y)}\\n`;\n      md += `- **Font Size:** ${text.fontSize || 18}\\n- **Color:** ${text.color || '#e2e8f0'}\\n`;\n      md += `- **Font Weight:** ${text.fontWeight || 'normal'}\\n- **Font Style:** ${text.fontStyle || 'normal'}\\n`;\n      md += `- **Text Align:** ${text.textAlign || 'start'}\\n- **Text Decoration:** ${text.textDecoration || 'none'}\\n`;\n      md += `- **Background Color:** ${text.bgColor || '#000000'}\\n- **Background Enabled:** ${text.bgEnabled ? 'true' : 'false'}\\n`;\n      md += `- **Opacity:** ${text.opacity !== undefined ? text.opacity : 1}\\n\\n`;\n    });\n  } else { md += `_No text labels_\\n\\n`; }\n  const blob = new Blob([md], { type: 'text/markdown;charset=utf-8;' });\n  const url = URL.createObjectURL(blob);\n  const a = document.createElement('a');\n  a.href = url;\n  a.download = `${safeTitle}.md`;\n  a.click();\n  URL.revokeObjectURL(url);\n  logAuditEvent('export', `Exported Markdown: ${a.download}`);\n}\n\ndocument.getElementById('import-markdown-file')?.addEventListener('change', async (e) => {\n  const file = e.target.files[0];\n  if (!file) return;\n  e.target.value = '';\n  try {\n    const text = await file.text();\n    let config = null;\n    const configMatch = text.match(/<!--THEONEFILE_CONFIG\\s*([\\s\\S]*?)\\s*THEONEFILE_CONFIG-->/);\n    if (configMatch) { try { config = JSON.parse(configMatch[1].trim()); } catch (err) { console.warn('Failed to parse Markdown config:', err); } }\n    const nodes = {}, positions = {}, sizes = {}, styles = {}, edges = [], rects = [], texts = [], legend = {};\n    const sections = text.split(/^## /m);\n    sections.forEach(section => {\n      const lines = section.trim().split('\\n');\n      const sectionTitle = lines[0]?.trim().toLowerCase();\n      if (sectionTitle === 'legend') {\n        lines.slice(1).forEach(line => {\n          const match = line.match(/^- (#[a-fA-F0-9]{3,8}):\\s*(.+)$/);\n          if (match) legend[match[1]] = match[2].trim();\n        });\n      } else if (sectionTitle === 'nodes') {\n        let currentNodeId = null, currentNode = null, inNotes = false;\n        lines.slice(1).forEach(line => {\n          const nodeMatch = line.match(/^### (.+)$/);\n          if (nodeMatch) {\n            if (currentNodeId && currentNode) nodes[currentNodeId] = currentNode;\n            currentNodeId = nodeMatch[1].trim();\n            currentNode = { tags: [], notes: [] };\n            inNotes = false;\n            return;\n          }\n          if (!currentNode) return;\n          if (inNotes && line.match(/^\\s+-\\s+(.+)$/)) {\n            const noteMatch = line.match(/^\\s+-\\s+(.+)$/);\n            if (noteMatch) currentNode.notes.push(noteMatch[1].trim().replace(/<br>/g, '\\n'));\n            return;\n          }\n          inNotes = false;\n          const propMatch = line.match(/^- \\*\\*(.+?):\\*\\*\\s*(.*)$/);\n          if (propMatch) {\n            const key = propMatch[1].trim().toLowerCase(), val = propMatch[2].trim();\n            switch (key) {\n              case 'name': currentNode.name = val; break;\n              case 'ip': currentNode.ip = val; break;\n              case 'role': currentNode.role = val; break;\n              case 'shape': currentNode.shape = val; break;\n              case 'tags': currentNode.tags = val === '_none_' ? [] : val.split(';').map(t => t.trim()).filter(t => t); break;\n              case 'layer': currentNode.layer = val; break;\n              case 'mac': currentNode.mac = val; break;\n              case 'rack unit': currentNode.rackUnit = val; break;\n              case 'u height': currentNode.uHeight = val; break;\n              case 'assigned rack': currentNode.assignedRack = val; break;\n              case 'rack capacity': currentNode.rackCapacity = val; break;\n              case 'is rack': currentNode.isRack = val === 'true'; break;\n              case 'locked': currentNode.locked = val === 'true'; break;\n              case 'group id': currentNode.groupId = val || null; break;\n              case 'position':\n                const posMatch = val.match(/(-?\\d+),\\s*(-?\\d+)/);\n                if (posMatch) positions[currentNodeId] = { x: parseFloat(posMatch[1]), y: parseFloat(posMatch[2]) };\n                break;\n              case 'size': sizes[currentNodeId] = parseFloat(val) || 50; break;\n              case 'notes': inNotes = true; break;\n              case 'styles':\n                const stylesMatch = val.match(/`(.+)`/);\n                if (stylesMatch) { try { styles[currentNodeId] = JSON.parse(stylesMatch[1]); } catch (err) {} }\n                break;\n            }\n          }\n        });\n        if (currentNodeId && currentNode) nodes[currentNodeId] = currentNode;\n      } else if (sectionTitle === 'connections') {\n        let currentEdge = null, inNotes = false;\n        lines.slice(1).forEach(line => {\n          const connMatch = line.match(/^- (.+?)\\s*(?:\\(([^)]*)\\))?\\s*-->\\s*(.+?)\\s*(?:\\(([^)]*)\\))?$/);\n          if (connMatch) {\n            if (currentEdge) edges.push(currentEdge);\n            currentEdge = { from: connMatch[1].trim(), to: connMatch[3].trim(), fromPort: connMatch[2] || '', toPort: connMatch[4] || '', notes: [] };\n            inNotes = false;\n            return;\n          }\n          if (!currentEdge) return;\n          if (inNotes && line.match(/^\\s+-\\s+(.+)$/)) {\n            const noteMatch = line.match(/^\\s+-\\s+(.+)$/);\n            if (noteMatch) currentEdge.notes.push(noteMatch[1].trim().replace(/<br>/g, '\\n'));\n            return;\n          }\n          inNotes = false;\n          const propMatch = line.match(/^\\s+- \\*\\*(.+?):\\*\\*\\s*(.*)$/);\n          if (propMatch) {\n            const key = propMatch[1].trim().toLowerCase(), val = propMatch[2].trim();\n            switch (key) {\n              case 'id': currentEdge.id = val; break;\n              case 'label': currentEdge.label = val.replace(/<br>/g, '\\n'); break;\n              case 'color': currentEdge.color = val; break;\n              case 'width': currentEdge.width = parseFloat(val) || 4; break;\n              case 'direction': currentEdge.direction = val; break;\n              case 'routing': currentEdge.routing = val; break;\n              case 'type': currentEdge.type = val; break;\n              case 'line style': currentEdge.lineStyle = val; break;\n              case 'group id': currentEdge.groupId = val || null; break;\n              case 'points':\n                currentEdge.points = val.split(' ').map(p => { const [x, y] = p.split(',').map(Number); return { x, y }; }).filter(p => !isNaN(p.x) && !isNaN(p.y));\n                break;\n              case 'notes': inNotes = true; break;\n            }\n          }\n        });\n        if (currentEdge) edges.push(currentEdge);\n      } else if (sectionTitle === 'zones') {\n        let currentRect = null, inNotes = false;\n        lines.slice(1).forEach(line => {\n          const rectMatch = line.match(/^### (.+)$/);\n          if (rectMatch) {\n            if (currentRect) rects.push(currentRect);\n            currentRect = { id: rectMatch[1].trim(), notes: [] };\n            inNotes = false;\n            return;\n          }\n          if (!currentRect) return;\n          if (inNotes && line.match(/^\\s+-\\s+(.+)$/)) {\n            const noteMatch = line.match(/^\\s+-\\s+(.+)$/);\n            if (noteMatch) currentRect.notes.push(noteMatch[1].trim().replace(/<br>/g, '\\n'));\n            return;\n          }\n          inNotes = false;\n          const propMatch = line.match(/^- \\*\\*(.+?):\\*\\*\\s*(.*)$/);\n          if (propMatch) {\n            const key = propMatch[1].trim().toLowerCase(), val = propMatch[2].trim();\n            switch (key) {\n              case 'position': const posMatch = val.match(/(-?\\d+),\\s*(-?\\d+)/); if (posMatch) { currentRect.x = parseFloat(posMatch[1]); currentRect.y = parseFloat(posMatch[2]); } break;\n              case 'size': const sizeMatch = val.match(/(\\d+)\\s*x\\s*(\\d+)/); if (sizeMatch) { currentRect.width = parseFloat(sizeMatch[1]); currentRect.height = parseFloat(sizeMatch[2]); } break;\n              case 'color': currentRect.color = val; break;\n              case 'style': currentRect.style = val; break;\n              case 'line style': currentRect.lineStyle = val; break;\n              case 'border color': currentRect.borderColor = val; break;\n              case 'border width': currentRect.borderWidth = parseFloat(val) || 2; break;\n              case 'notes': inNotes = true; break;\n            }\n          }\n        });\n        if (currentRect) rects.push(currentRect);\n      } else if (sectionTitle === 'text labels') {\n        let currentText = null;\n        lines.slice(1).forEach(line => {\n          const textMatch = line.match(/^### (.+)$/);\n          if (textMatch) {\n            if (currentText) texts.push(currentText);\n            currentText = { id: textMatch[1].trim() };\n            return;\n          }\n          if (!currentText) return;\n          const propMatch = line.match(/^- \\*\\*(.+?):\\*\\*\\s*(.*)$/);\n          if (propMatch) {\n            const key = propMatch[1].trim().toLowerCase(), val = propMatch[2].trim();\n            switch (key) {\n              case 'content': currentText.content = val.replace(/<br>/g, '\\n'); break;\n              case 'position': const posMatch = val.match(/(-?\\d+),\\s*(-?\\d+)/); if (posMatch) { currentText.x = parseFloat(posMatch[1]); currentText.y = parseFloat(posMatch[2]); } break;\n              case 'font size': currentText.fontSize = parseFloat(val) || 18; break;\n              case 'color': currentText.color = val; break;\n              case 'font weight': currentText.fontWeight = val; break;\n              case 'font style': currentText.fontStyle = val; break;\n              case 'text align': currentText.textAlign = val; break;\n              case 'text decoration': currentText.textDecoration = val; break;\n              case 'background color': currentText.bgColor = val; break;\n              case 'background enabled': currentText.bgEnabled = val === 'true'; break;\n              case 'opacity': currentText.opacity = parseFloat(val) || 1; break;\n            }\n          }\n        });\n        if (currentText) texts.push(currentText);\n      }\n    });\n    const nodeCount = Object.keys(nodes).length, edgeCount = edges.length, rectCount = rects.length, textCount = texts.length;\n    if (nodeCount === 0 && edgeCount === 0 && rectCount === 0 && textCount === 0) {\n      alert('No valid topology data found in Markdown file.\\n\\nMake sure the file was exported from The One File.');\n      return;\n    }\n    let confirmMsg = `Import Markdown topology?\\n\\n- ${nodeCount} nodes\\n- ${edgeCount} connections\\n- ${rectCount} zones\\n- ${textCount} text labels\\n\\nThis will REPLACE all current data.`;\n    if (!confirm(confirmMsg)) return;\n    pushUndo('import markdown');\n    if (config && config.documentTabs) {\n      if (config.page) { Object.assign(PAGE_STATE, config.page); wieldThePower(); }\n      if (config.canvas) {\n        canvasState.zoom = config.canvas.zoom || 1;\n        canvasState.panX = config.canvas.panX || 0;\n        canvasState.panY = config.canvas.panY || 0;\n      }\n      if (config.savedStyleSets) {\n        savedStyleSets = config.savedStyleSets;\n      }\n      if (config.documentTabs) {\n        documentTabs = config.documentTabs;\n        currentTabIndex = config.currentTabIndex || 0;\n      }\n      if (config.savedTopologyView) {\n        savedTopologyView = config.savedTopologyView;\n      }\n      if (config.encryptedSections) {\n        encryptedSections = config.encryptedSections;\n      }\n      if (config.auditLog && Array.isArray(config.auditLog)) {\n        auditLog = config.auditLog;\n        try {\n          localStorage.setItem(AUDIT_STORAGE_KEY, JSON.stringify(auditLog));\n        } catch (e) {\n          console.warn(\"Failed to sync audit log to localStorage:\", e);\n        }\n      }\n    } else if (config && config.pageState) {\n      Object.assign(PAGE_STATE, config.pageState);\n      wieldThePower();\n      if (config.canvasView) {\n        canvasState.zoom = config.canvasView.zoom || 1;\n        canvasState.panX = config.canvasView.panX || 0;\n        canvasState.panY = config.canvasView.panY || 0;\n      }\n    }\n    Object.keys(NODE_DATA).forEach(k => delete NODE_DATA[k]);\n    Object.keys(savedPositions).forEach(k => delete savedPositions[k]);\n    Object.keys(savedSizes).forEach(k => delete savedSizes[k]);\n    Object.keys(savedStyles).forEach(k => delete savedStyles[k]);\n    EDGE_DATA.list = [];\n    RECT_DATA.list = [];\n    TEXT_DATA.list = [];\n    Object.keys(EDGE_LEGEND).forEach(k => delete EDGE_LEGEND[k]);\n    Object.assign(EDGE_LEGEND, legend);\n    Object.entries(nodes).forEach(([id, node]) => {\n      NODE_DATA[id] = { name: node.name || '', ip: node.ip || '', role: node.role || '', shape: node.shape || 'circle', tags: node.tags || [], notes: node.notes || [], mac: node.mac || '', rackUnit: node.rackUnit || '', uHeight: node.uHeight || '1', layer: node.layer || 'physical', assignedRack: node.assignedRack || '', rackCapacity: node.rackCapacity || '', isRack: node.isRack || false, locked: node.locked || false, groupId: node.groupId || null };\n      savedPositions[id] = positions[id] || { x: 500, y: 300 };\n      if (sizes[id]) savedSizes[id] = sizes[id];\n      if (styles[id]) savedStyles[id] = styles[id];\n    });\n    let skippedEdges = 0;\n    edges.forEach(edge => {\n      if (!NODE_DATA[edge.from] || !NODE_DATA[edge.to]) {\n        console.warn(`Skipping orphan edge: ${edge.from} -> ${edge.to}`);\n        skippedEdges++;\n        return;\n      }\n      EDGE_DATA.list.push({ id: edge.id || `edge-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, from: edge.from, to: edge.to, fromPort: edge.fromPort || '', toPort: edge.toPort || '', label: edge.label || '', color: edge.color || '', width: edge.width || 4, direction: edge.direction || 'none', routing: edge.routing || 'curved', type: edge.type || 'main', lineStyle: edge.lineStyle || 'solid', groupId: edge.groupId || null, points: edge.points || [], notes: edge.notes || [] });\n    });\n    if (skippedEdges > 0) console.warn(`Total skipped orphan edges: ${skippedEdges}`);\n    rects.forEach(rect => {\n      RECT_DATA.list.push({ id: rect.id, x: rect.x || 0, y: rect.y || 0, width: rect.width || 100, height: rect.height || 100, color: rect.color || '#f97316', style: rect.style || 'filled', lineStyle: rect.lineStyle || 'solid', borderColor: rect.borderColor || '', borderWidth: rect.borderWidth !== undefined ? rect.borderWidth : 2, notes: rect.notes || [] });\n    });\n    texts.forEach(text => {\n      TEXT_DATA.list.push({ id: text.id, x: text.x || 0, y: text.y || 0, content: text.content || '', fontSize: text.fontSize || 18, color: text.color || '#e2e8f0', fontWeight: text.fontWeight || 'normal', fontStyle: text.fontStyle || 'normal', textAlign: text.textAlign || 'start', textDecoration: text.textDecoration || 'none', bgColor: text.bgColor || '#000000', bgEnabled: text.bgEnabled || false, opacity: text.opacity !== undefined ? text.opacity : 1 });\n    });\n    forgeTheTopology();\n    if (typeof forgeTheLegend === 'function') forgeTheLegend();\n    if (typeof updateZoneLegend === 'function') updateZoneLegend();\n    updateViewBox();\n    logAuditEvent('import', `Imported Markdown: ${file.name} (${nodeCount} nodes, ${edgeCount} connections)`);\n    alert(`Successfully imported:\\n- ${nodeCount} nodes\\n- ${edgeCount} connections\\n- ${rectCount} zones\\n- ${textCount} text labels`);\n  } catch (err) {\n    console.error('Markdown import error:', err);\n    alert('Failed to import Markdown: ' + err.message);\n  }\n});\n\ndocument.getElementById('mobile-export-btn')?.addEventListener('click', () => {\n  document.getElementById('mobile-export-modal').classList.add('active');\n  document.getElementById('topbar-menu').classList.remove('open');\n});\ndocument.getElementById('mobile-import-btn')?.addEventListener('click', () => {\n  document.getElementById('mobile-import-modal').classList.add('active');\n  document.getElementById('topbar-menu').classList.remove('open');\n});\ndocument.getElementById('mobile-export-modal')?.addEventListener('click', (e) => {\n  if (e.target.id === 'mobile-export-modal') e.target.classList.remove('active');\n});\ndocument.getElementById('mobile-import-modal')?.addEventListener('click', (e) => {\n  if (e.target.id === 'mobile-import-modal') e.target.classList.remove('active');\n});\t  \n    </script>\n</body></html>"
  },
  {
    "path": "import-export-save.md",
    "content": "# Export and Import\n\n| Format | Export | Import | Full Backup | Editable  |\n|--------|--------|--------|-------------|---------------------|\n| **HTML/Default** | Yes | Yes | Yes | Yes |\n| **JSON** | Yes | Yes | Yes | Yes |\n| **Markdown** | Yes | Yes | Yes | Yes |\n| **CSV** | Yes | Yes | Yes | Yes |\n| **PNG** | Yes | No | No | No |\n| **SVG** | Yes | No | No | Yes |\n\n## HTML (Save File)\n\n### Use Cases\n\n| Scenario | Recommendation |\n|----------|----------------|\n| Daily work and saving progress | Save HTML |\n| Sharing with others | Save HTML |\n| Offline use | Save HTML |\n| Archival backup | Save HTML plus JSON |\n\n## JSON Format\n\n### Structure\n```\n{\n  \"nodeData\": { ... },\n  \"edgeData\": { \"list\": [ ... ] },\n  \"rectData\": { \"list\": [ ... ] },\n  \"textData\": { \"list\": [ ... ] },\n  \"edgeLegend\": { ... },\n  \"nodePositions\": { ... },\n  \"nodeSizes\": { ... },\n  \"nodeStyles\": { ... },\n  \"page\": { ... },\n  \"canvas\": { \"zoom\": 1, \"panX\": 0, \"panY\": 0 },\n  \"savedTopologyView\": { ... },\n  \"documentTabs\": [ ... ],\n  \"currentTabIndex\": 0,\n  \"encryptedSections\": { ... },\n  \"auditLog\": [ ... ]\n}\n```\n\n### Use Cases\n\n| Scenario | Recommendation |\n|----------|----------------|\n| Full backup | JSON |\n| Transfer between files | JSON |\n| Programmatic access | JSON |\n| API integration | JSON |\n\n## Markdown Format\n\n### Structure\n```\n<!--THEONEFILE_CONFIG\n{ ... complete JSON backup ... }\nTHEONEFILE_CONFIG-->\n\n# Topology Title\n\n> Exported from The One File on 2025-01-15T10:30:00.000Z\n\n## Legend\n\n- #4fd1c5: Primary Links\n- #f97316: Secondary Links\n\n## Nodes\n\n### node-id-here\n- **Name:** Display Name\n- **IP:** 10.0.0.1\n- **Role:** Core Router\n- **Shape:** router\n- **Tags:** network; critical\n- **Layer:** physical\n- **MAC:** 00:11:22:33:44:55\n- **Rack Unit:** \n- **U Height:** 1\n- **Assigned Rack:** \n- **Rack Capacity:** \n- **Is Rack:** false\n- **Locked:** false\n- **Group ID:** \n- **Position:** 500, 300\n- **Size:** 55\n- **Notes:**\n  - First note here\n  - Second note here\n- **Styles:** `{\"all\":{\"circleColor\":\"#1e293b\"}}`\n\n## Connections\n\n- source-node (port1) --> target-node (port2)\n  - **ID:** edge-1234567890\n  - **Label:** Connection Label\n  - **Color:** #4fd1c5\n  - **Width:** 4\n  - **Direction:** none\n  - **Routing:** curved\n  - **Type:** main\n  - **Line Style:** solid\n  - **Group ID:** \n  - **Points:** 100,200 150,250 200,300\n  - **Notes:**\n    - Edge note here\n\n## Zones\n\n### rect-1234567890\n- **Position:** 100, 100\n- **Size:** 400 x 300\n- **Color:** #f97316\n- **Style:** filled\n- **Line Style:** solid\n- **Border Color:** \n- **Border Width:** 2\n- **Notes:**\n  - Zone note here\n\n## Text Labels\n\n### text-1234567890\n- **Content:** Label text here\n- **Position:** 500, 200\n- **Font Size:** 18\n- **Color:** #e2e8f0\n- **Font Weight:** normal\n- **Font Style:** normal\n- **Text Align:** start\n- **Text Decoration:** none\n- **Background Color:** #000000\n- **Background Enabled:** false\n- **Opacity:** 1\n```\n\n### Special Characters\n\n| Character | Export Format | Import Conversion |\n|-----------|---------------|-------------------|\n| Newline in text | `<br>` | Converted back to newline |\n| Tags separator | Semicolon | Split into array |\n| Notes separator | Indented list | Array of strings |\n\n### Use Cases\n\n| Scenario | Recommendation |\n|----------|----------------|\n| Version control (git) | Markdown |\n| Text editor viewing | Markdown |\n| Manual editing of topology | Markdown |\n| Documentation generation | Markdown |\n| Diff comparison between versions | Markdown |\n\n## CSV\n\n### Structure\n```\n#THEONEFILE_CONFIG:{ ... complete JSON backup ... }\n#\n# Topology Title - Node List\n# Exported from The One File on 2025-01-15T10:30:00.000Z\n# NOTE: CSV contains nodes only. Use Markdown or JSON for full topology.\n#\nname,ip,role,shape,tags,layer,mac,rackUnit,uHeight,assignedRack,rackCapacity,isRack,locked,groupId,x,y,size,notes,styles\nCore Router,10.0.0.1,Router,router,network;critical,physical,00:11:22:33:44:55,,,,,false,false,,500,300,55,Note 1|Note 2,\"{...}\"\nWeb Server,10.0.1.10,Web Server,server,production,physical,,,,,,false,false,,700,400,50,,\n```\n\n### Columns\n\n| Column | Required | Description | Format |\n|--------|----------|-------------|--------|\n| name | Yes | Display name of node | Text |\n| ip | No | IP address | Text |\n| role | No | Role or description | Text |\n| shape | No | Node shape | circle, server, router, switch, firewall, cloud, database |\n| tags | No | Tags for filtering | Semicolon separated |\n| layer | No | Network layer | physical, logical, security, application |\n| mac | No | MAC address | Text |\n| rackUnit | No | Rack unit position | Text |\n| uHeight | No | Height in rack units | Number |\n| assignedRack | No | Rack node ID | Text |\n| rackCapacity | No | Rack capacity | Number |\n| isRack | No | Is this a rack node | true or false |\n| locked | No | Movement locked | true or false |\n| groupId | No | Group identifier | Text |\n| x | No | Horizontal position | Number |\n| y | No | Vertical position | Number |\n| size | No | Node size | Number |\n| notes | No | Node notes | Pipe separated |\n| styles | No | Custom styles | JSON string |\n\n### Use Cases\n\n| Scenario | Recommendation |\n|----------|----------------|\n| Bulk node creation | CSV |\n| Spreadsheet editing of nodes | CSV |\n| Import from asset database | CSV |\n| Quick node list for editing | CSV |\n| Full backup with editable nodes | CSV |\n\n### Minimal CSV Example\n```\nname,ip,role,shape\nRouter 1,10.0.0.1,Core Router,router\nSwitch 1,10.0.0.2,Distribution,switch\nServer 1,10.0.1.10,Web Server,server\nFirewall,10.0.0.254,Edge Firewall,firewall\n```\n\n### CSV with Positions\n```\nname,ip,shape,x,y,size\nRouter 1,10.0.0.1,router,500,200,60\nSwitch 1,10.0.0.2,switch,500,400,50\nServer 1,10.0.1.10,server,300,600,50\nServer 2,10.0.1.11,server,500,600,50\nServer 3,10.0.1.12,server,700,600,50\n```\n\n### CSV with Tags and Notes\n```\nname,ip,role,shape,tags,layer,notes\nCore Router,10.0.0.1,Primary Router,router,network;critical;core,physical,Main gateway|Managed by NetOps\nWeb Server 1,10.0.1.10,Production Web,server,production;web;critical,application,Apache 2.4|Ubuntu 22.04\nDB Server,10.0.2.10,Primary Database,database,production;database,application,PostgreSQL 15\nDev Server,10.0.3.10,Development,server,development;non-critical,application,\n```\n\n## PNG Export\n\n### Use Cases\n\n| Scenario | Recommendation |\n|----------|----------------|\n| Documentation screenshots | PNG |\n| Presentations | PNG |\n| Email attachments | PNG |\n| Quick sharing | PNG |\n\n## SVG Export\n\n### Use Cases\n\n| Scenario | Recommendation |\n|----------|----------------|\n| Design tool editing | SVG |\n| Large format printing | SVG |\n| Scalable documentation | SVG |\n| Custom styling | SVG |\n\n## Print\n\nOptimized print layout for physical documentation.\n\n### Use Cases\n\n| Scenario | Recommendation |\n|----------|----------------|\n| Physical documentation | Print |\n| Meeting handouts | Print |\n| Wall diagrams | Print |\n| Offline reference | Print |"
  },
  {
    "path": "keyboard-shortcuts.md",
    "content": "# Keyboard Shortcuts\n\n## Navigation & Movement\n| Shortcut | Action |\n|----------|--------|\n| `Arrow Keys` | Move selected node(s) 1 pixel in arrow direction |\n| `Shift + Arrow Keys` | Move selected node(s) 10 pixels for faster positioning |\n| `Tab` | Cycle to next node in current view |\n| `Shift + Tab` | Cycle to previous node in current view |\n| `F` | Focus camera on selected node(s) with auto zoom |\n| `Shift + Click/Drag` | Multiple Select (marquee selection) |\n\n## Node Management\n| Shortcut | Action |\n|----------|--------|\n| `L` | Lock or unlock selected node(s) to prevent movement |\n| `G` | Group or ungroup selected nodes (requires 2+ nodes selected) |\n| `Ctrl/Cmd + C` | Copy selected node |\n| `Ctrl/Cmd + V` | Paste node at center of view |\n| `Ctrl/Cmd + D` | Duplicate selected node |\n| `Ctrl/Cmd + A` | Select all nodes in current view |\n| `Delete` | Delete selected item(s) |\n| `Escape` | Clear selection |\n\n## Editing\n| Shortcut | Action |\n|----------|--------|\n| `Ctrl/Cmd + Z` | Undo last action |\n| `Ctrl/Cmd + Y` | Redo last undone action |\n| `Ctrl/Cmd + Shift + Z` | Redo (alternative) |\n\n## View Controls\n| Shortcut | Action |\n|----------|--------|\n| `Ctrl/Cmd + Plus` | Zoom in |\n| `Ctrl/Cmd + Minus` | Zoom out |\n| `Ctrl/Cmd + 0` | Reset view to default zoom and position |\n| `Space + Drag` | Pan canvas (hold space and drag with mouse) |\n| `Scroll` | Zoom in/out at cursor position |\n\n## Recording\n\n| Key | Action |\n|-----|--------|\n| R | Start/stop real time recording |\n| Shift+R | Start/stop step by step recording |\n| Space | Add step (step recording) / Play/Pause (playback) |\n| P | Play recording |"
  },
  {
    "path": "mobile-gestures.md",
    "content": "# Mobile Gestures & Touch Controls\n\n| Gesture | Action | Context |\n|---------|--------|---------|\n| **Tap node** | Select node and open properties panel | Any view |\n| **Tap empty space** | Deselect all nodes | Any view |\n| **Drag node** | Move node to new position | Any view (unless locked) |\n| **Drag empty space** | Pan canvas | Any view |\n| **Pinch** | Zoom in/out | Any view |\n| **Triple Tap** | Undo | Any view |\n| **Long Press** | Add Waypoint to line | Any view |\n\n## Multi-Selection\n\n| Gesture | Action | Context |\n|---------|--------|---------|\n| **Double tap node** | Add node to multi selection | Any view |\n| **Double tap selected node** | Remove node from multi selection | Any view |\n| **Tap \"Multi Select\" button** | Open bulk operations menu | When 2+ nodes selected |\n\n## Rack View Navigation\n\n| Gesture | Action | Context |\n|---------|--------|---------|\n| **Long press rack node** | Enter rack view to see devices inside | Topology view |\n| **Double tap empty space** | Exit rack view and return to topology | Rack view only |\n| **Tap \"Back to Topology\" button** | Exit rack view (alternative to double tap) | Rack view only |\n\n## Bulk Operations Menu\n\nWhen you have multiple nodes selected, tap the bottom button to open the mobile menu with these actions:\n\n| Button | Action |\n|--------|--------|\n| **Align Left** | Align all selected nodes to leftmost position |\n| **Align Right** | Align all selected nodes to rightmost position |\n| **Align Top** | Align all selected nodes to topmost position |\n| **Align Bottom** | Align all selected nodes to bottommost position |\n| **Distribute H** | Evenly space selected nodes horizontally |\n| **Distribute V** | Evenly space selected nodes vertically |\n| **Lock Toggle** | Lock or unlock all selected nodes (prevents movement) |\n| **Group Toggle** | Create or dissolve a group from selected nodes |\n| **Clone All** | Duplicate all selected nodes |\n| **Delete All** | Remove all selected nodes |\n| **Clear Selection** | Deselect all nodes and close menu |\n\n## Panel Controls\n\n| Gesture | Action |\n|---------|--------|\n| **Drag footer handle** | Resize mobile footer panel (bottom panel on phones/tablets) |\n| **Swipe up on handle** | Maximize footer panel |\n| **Swipe down on handle** | Minimize footer panel |\n"
  },
  {
    "path": "the-one-file.html",
    "content": "<!DOCTYPE html> \n <html lang=\"en\" style=\"--panel: #0b0e13; --panel-alt: #10141b; --sidebar-bg: #10141b; --btn-bg: #0b0e13; --accent: #4fd1c5; --danger: #f56565; --text-main: #e2e8f0; --text-soft: #94a3b8; --topbar-bg: rgba(9, 12, 20, 0.9); --topbar-border: #1f2533; --topbar-height: 103px; --sidebar-width: 350px; --mobile-footer-height: 40vh; --draw-toolbar-height: 0px; --btn-text: #e2e8f0; --tag-fill: #1e293b; --tag-text: #e2e8f0; --tag-border: #475569; --input-bg: #0b0e13; --input-text: #e2e8f0; --input-border: #1f2937; --input-font: Inter, system-ui, sans-serif; --input-font-size: 14px; --toolbar-bg: #0f172a; --toolbar-border: #1f2937; --toolbar-text: #94a3b8; --toolbar-btn-bg: #0b0e13; --toolbar-btn-text: #e2e8f0; --minimap-dots: #94a3b8; --canvas-hint-bg: #0f172a; --canvas-hint-color: #94a3b8; --node-fill: #1e293b; --node-stroke: #475569; --node-title: #e2e8f0; --node-sub: #94a3b8; --node-title-size: 41px; --node-sub-size: 27px; --node-font: monospace; --default-edge: #475569; --selection-handle: #f59e0b; --selection-handle-size: 8px; --group-indicator: #4fd1c5;\"><head>\n    <meta charset=\"UTF-8\">\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>The One File</title>\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <!--\n      * ====================================================================================\n      * THE ONE FILE\n      * \"There can be only one\". A all in one file topology maker for enterprise or homelab\n      *\n      * This is your last backup when all others fail. A completely self contained\n      * network topology visualization tool that works as a single HTML file.\n      * Open it anywhere, anytime and the idea lives forever.\n      * ====================================================================================\n      -->\n<style>\n      :root {\n      color-scheme: dark;\n      --bg: #050608;\n      --panel: #0b0e13;\n      --panel-alt: #10141b;\n      --accent: #4fd1c5;\n      --danger: #f56565;\n      --text-main: #e2e8f0;\n      --text-soft: #94a3b8;\n      --edge-main: #475569;\n      --node-min: 35px;\n      --node-max: 70px;\n      --topbar-bg: rgba(9, 12, 20, 0.9);\n      --topbar-border: #1f2533;\n      }\n\t  html, body, svg, .map-container {\n      touch-action: none;\n      }\n      * {\n      box-sizing: border-box;\n      user-select: none;\n      }\n      input,\n      textarea,\n      [contenteditable=\"true\"] {\n      user-select: text;\n      -webkit-user-select: text;\n      }\n      body {\n      margin: 0;\n      font-family: system-ui, sans-serif;\n      background: radial-gradient(circle at top, #1e2532 0, #050608 70%);\n      color: var(--text-main);\n      display: flex;\n      flex-direction: column;\n      height: 100vh;\n      overflow: hidden;\n      }\n      header {\n      padding: 0 20px;\n      height: var(--topbar-height, 52px);\n      min-height: var(--topbar-height, 52px);\n      background: var(--topbar-bg);\n      backdrop-filter: blur(6px);\n      display: flex;\n      justify-content: space-between;\n      align-items: center;\n      border-bottom: 1px solid var(--topbar-border);\n      gap: 16px;\n      }\n      .title-block {\n      display: flex;\n      flex-direction: column;\n      gap: 6px;\n      min-width: 0;\n      }\n      header h1 {\n      font-size: clamp(22px, 3vw, 32px);\n      margin: 0;\n      white-space: nowrap;\n      text-overflow: ellipsis;\n      overflow: hidden;\n      }\n      .editable-page-title {\n      cursor: pointer;\n      transition: opacity 0.2s;\n      }\n      .editable-page-title:hover {\n      opacity: 0.7;\n      }\n      .save-row {\n      display: flex;\n      align-items: center;\n      gap: 8px;\n      flex-wrap: wrap;\n      }\n\t  .toggle-switch{position:relative;display:inline-block;min-width:44px !important;height:24px;flex-shrink:0;vertical-align:middle;}\n\t\t.toggle-switch input{opacity:0;width:0;height:0;position:absolute;}\n\t\t.toggle-slider{position:absolute;cursor:pointer;top:0;left:0;right:0;bottom:0;background:#475569;transition:.25s;border-radius:24px;}\n\t\t.toggle-slider:before{position:absolute;content:\"\";height:18px;width:18px;left:3px;bottom:3px;background:#e2e8f0;transition:.25s;border-radius:50%;}\n\t\t.toggle-switch input:checked+.toggle-slider{background:var(--accent);}\n\t\t.toggle-switch input:checked+.toggle-slider:before{transform:translateX(20px);}\n\t\t.anim-zone-row{display:flex;justify-content:space-between;align-items:center;padding:6px 0;}\n\t\t.anim-zone-row label{color:var(--text-main);font-size:14px;}\n\t\t.anim-zone-section{font-size:11px;color:var(--text-soft);margin:12px 0 6px;text-transform:uppercase;letter-spacing:0.05em;border-bottom:1px solid var(--edge-main);padding-bottom:4px;}\n\t\t.anim-zone-header{font-size:12px;color:var(--accent);margin-bottom:10px;text-transform:uppercase;letter-spacing:0.05em;font-weight:600;}\n      .save-btn {\n      padding: 6px 12px;\n      background: var(--accent);\n      color: var(--bg);\n      border: none;\n      border-radius: 6px;\n      cursor: pointer;\n      font-size: 14px;\n      font-weight: 600;\n      white-space: nowrap;\n      }\n      .save-btn:hover {\n      opacity: 0.9;\n      }\n      .help-icon {\n      width: 22px;\n      height: 22px;\n      border-radius: 50%;\n      border: 1px solid var(--edge-main);\n      display: inline-flex;\n      align-items: center;\n      justify-content: center;\n      font-size: 14px;\n      cursor: pointer;\n      color: var(--text-soft);\n      background: rgba(15, 23, 42, 0.9);\n      flex-shrink: 0;\n      }\n      .help-icon:hover {\n      color: var(--accent);\n      border-color: var(--accent);\n      }\n      #settings-btn {\n      background: var(--btn-bg, var(--panel));\n      color: var(--btn-text, var(--text-main));\n      border: 1px solid var(--edge-main);\n      padding: 6px 12px;\n      border-radius: 6px;\n      cursor: pointer;\n      font-size: 16px;\n      flex-shrink: 0;\n      }\n      #settings-btn:hover {\n      background: var(--accent);\n      color: var(--bg);\n      }\n      .header-resizer {\n      position: absolute;\n      bottom: 0;\n      left: 0;\n      right: 0;\n      height: 6px;\n      background: transparent;\n      cursor: row-resize;\n      z-index: 1000;\n      transition: background 0.2s;\n      }\n      .header-resizer:hover {\n      background: rgba(79, 209, 197, 0.3);\n      }\n      .header-resizer.resizing {\n      background: rgba(79, 209, 197, 0.5);\n      }\n      .sidebar-resizer {\n      position: absolute;\n      left: 0;\n      top: 0;\n      bottom: 0;\n      width: 6px;\n      background: transparent;\n      cursor: col-resize;\n      z-index: 1000;\n      transition: background 0.2s;\n      }\n      .sidebar-resizer:hover {\n      background: rgba(79, 209, 197, 0.3);\n      }\n      .sidebar-resizer.resizing {\n      background: rgba(79, 209, 197, 0.5);\n      }\n      .mobile-footer-resizer {\n      position: absolute;\n      top: 0;\n      left: 0;\n      right: 0;\n      height: 6px;\n      background: transparent;\n      cursor: row-resize;\n      z-index: 1000;\n      transition: background 0.2s;\n      display: none;\n      }\n      .mobile-footer-resizer:hover {\n      background: rgba(79, 209, 197, 0.3);\n      }\n      .mobile-footer-resizer.resizing {\n      background: rgba(79, 209, 197, 0.5);\n      }\n      @media (max-width: 900px) {\n      .mobile-footer-resizer {\n      display: block;\n      height: 12px;\n      }\n      .sidebar-resizer {\n      display: none;\n      }\n      .header-resizer {\n      height: 12px;\n      }\n      }\n      @media (pointer: coarse) {\n      .header-resizer {\n      height: 16px;\n      }\n      .mobile-footer-resizer {\n      height: 16px;\n      }\n      .sidebar-resizer {\n      width: 16px;\n      }\n      }\n      .resizer-icon {\n      position: absolute;\n      opacity: 0.5;\n      transition: opacity 0.2s, transform 0.2s;\n      pointer-events: none;\n      fill: var(--accent);\n      }\n      .header-resizer .resizer-icon {\n      left: 50%;\n      top: 50%;\n      transform: translate(-50%, -50%);\n      }\n      .sidebar-resizer .resizer-icon {\n      left: 50%;\n      top: 50%;\n      transform: translate(-50%, -50%);\n      }\n      .mobile-footer-resizer .resizer-icon {\n      left: 50%;\n      top: 50%;\n      transform: translate(-50%, -50%);\n      }\n      .header-resizer:hover .resizer-icon,\n      .sidebar-resizer:hover .resizer-icon,\n      .mobile-footer-resizer:hover .resizer-icon {\n      opacity: 1;\n      }\n      .header-resizer.resizing .resizer-icon,\n      .sidebar-resizer.resizing .resizer-icon,\n      .mobile-footer-resizer.resizing .resizer-icon {\n      opacity: 1;\n      transform: translate(-50%, -50%) scale(1.2);\n      }\n      body.resizing {\n      user-select: none;\n      }\n      body.resizing * {\n      cursor: inherit !important;\n      pointer-events: none;\n      }\n      header {\n      position: relative;\n      }\n      .details-panel {\n      position: relative;\n      }\n      main {\n      display: grid;\n      grid-template-columns: 1fr var(--sidebar-width, 350px);\n      flex: 1;\n      }\n      main.sidebar-collapsed {\n      grid-template-columns: 1fr 0;\n      }\n      @media (max-width: 900px) {\n      main {\n      grid-template-columns: 1fr;\n      grid-template-rows: calc(100vh - var(--topbar-height, 52px) - var(--mobile-footer-height, 40vh)) var(--mobile-footer-height, 40vh);\n      }\n      main.sidebar-collapsed {\n      grid-template-rows: 1fr 0;\n      }\n      .details-panel {\n      max-height: var(--mobile-footer-height, 40vh);\n      height: 100%;\n      }\n      }\n      .topology-panel {\n      background: var(--panel);\n      border-right: 1px solid #111827;\n      position: relative;\n      overflow: hidden;\n      }\n      .topology-toolbar {\n      position: absolute;\n      top: 10px;\n      right: 10px;\n      display: none;\n      align-items: center;\n      gap: 8px;\n      padding: 6px 10px;\n      background: var(--toolbar-bg, rgba(15, 23, 42, 0.92));\n      border: 1px solid var(--toolbar-border, #1f2937);\n      border-radius: 6px;\n      z-index: 99;\n      font-size: 13px;\n      color: var(--toolbar-text, #94a3b8);\n      }\n      .topology-toolbar label {\n      color: var(--toolbar-text, var(--text-soft));\n      }\n      .topology-toolbar select {\n      padding: 4px 6px;\n      background: var(--toolbar-btn-bg, var(--panel));\n      border: 1px solid var(--toolbar-border, var(--edge-main));\n      border-radius: 4px;\n      color: var(--toolbar-btn-text, var(--text-main));\n      font-size: 13px;\n      cursor: pointer;\n      }\n      .topology-toolbar button {\n      padding: 4px 10px;\n      background: var(--toolbar-btn-bg, var(--panel));\n      color: var(--toolbar-btn-text, var(--text-main));\n      border: 1px solid var(--toolbar-border, var(--edge-main));\n      border-radius: 4px;\n      cursor: pointer;\n      font-size: 13px;\n      font-weight: 600;\n      }\n      .topology-toolbar button:hover {\n      background: var(--accent);\n      color: var(--bg);\n      }\n      .bulk-toolbar-desktop,\n      .bulk-toolbar-mobile {\n      position: fixed;\n      bottom: 20px;\n      left: 50%;\n      transform: translateX(-50%);\n      z-index: 9999;\n      }\n      .bulk-toolbar-desktop {\n      position: absolute;\n      top: 10px;\n      right: 10px;\n      bottom: auto;\n      left: auto;\n      transform: none;\n      }\n      @media (min-width: 768px) {\n      .bulk-toolbar-mobile {\n      display: none !important;\n      }\n      }\n      .bulk-action-btn {\n      padding: 16px;\n      background: var(--panel-main);\n      border: 1px solid var(--edge-main);\n      border-radius: 8px;\n      color: var(--text-main);\n      font-size: 24px;\n      cursor: pointer;\n      text-align: center;\n      line-height: 1.2;\n      }\n      .bulk-action-btn:active {\n      transform: scale(0.95);\n      background: var(--accent);\n      }\n      .draw-toolbar {\n      position: absolute;\n      top: 10px;\n      left: 10px;\n      display: flex;\n      align-items: center;\n      gap: 6px;\n      padding: 6px 8px;\n      background: var(--toolbar-bg, rgba(15, 23, 42, 0.92));\n      border: 1px solid var(--toolbar-border, #1f2937);\n      border-radius: 6px;\n      z-index: 99;\n      font-size: 13px;\n      pointer-events: auto;\n      color: var(--toolbar-text, #94a3b8);\n      }\n      .draw-toolbar button {\n      padding: 4px 8px;\n      background: var(--toolbar-btn-bg, var(--panel));\n      color: var(--toolbar-btn-text, var(--text-main));\n      border: 1px solid var(--toolbar-border, var(--edge-main));\n      border-radius: 4px;\n      cursor: pointer;\n      font-size: 13px;\n      }\n      .draw-toolbar button:hover {\n      background: var(--accent);\n      color: var(--bg);\n      }\n      .draw-toolbar input[type=\"color\"] {\n      width: 30px;\n      height: 22px;\n      border-radius: 4px;\n      border: 1px solid var(--edge-main);\n      padding: 0;\n      background: transparent;\n      cursor: pointer;\n      }\n      .draw-toolbar select {\n      padding: 4px 6px;\n      background: var(--toolbar-btn-bg, var(--panel));\n      border: 1px solid var(--toolbar-border, var(--edge-main));\n      border-radius: 4px;\n      color: var(--toolbar-btn-text, var(--text-main));\n      font-size: 13px;\n      cursor: pointer;\n      }\n      .legend-container {\n      position: absolute;\n      left: 10px;\n      bottom: 10px;\n      display: flex;\n      flex-direction: column;\n      gap: 6px;\n      padding: 8px 10px;\n      background: var(--toolbar-bg, rgba(15, 23, 42, 0.92));\n      border: 1px solid var(--toolbar-border, #1f2937);\n      border-radius: 6px;\n      font-size: 12px;\n      z-index: 20;\n      max-width: 260px;\n      pointer-events: auto;\n      color: var(--toolbar-text, #94a3b8);\n      }\n      .legend-container {\n      padding-right: 22px;\n      }\n      @media (max-width: 900px) {\n      .legend-container {\n      height:250px;\n      overflow-y: auto;\n      z-index:99;\n      }\n      }\n      @media (max-width: 900px) {\n      .bulk-toolbar-desktop,\n      .topology-toolbar {\n      display: none !important;\n      }\n      }\n      .legend-close-btn {\n      position: absolute;\n      top: 5px;\n      right: 5px;\n      width: 18px;\n      height: 18px;\n      border-radius: 999px;\n      border: 1px solid var(--edge-main);\n      background: var(--panel-alt);\n      color: var(--text-soft);\n      font-size: 11px;\n      display: flex;\n      align-items: center;\n      justify-content: center;\n      cursor: pointer;\n      }\n      .legend-close-btn:hover {\n      background: var(--danger);\n      color: #fff;\n      }\n      .legend-mini-btn {\n      position: absolute;\n      left: 10px;\n      padding: 4px 8px;\n      border-radius: 4px;\n      border: 1px solid var(--toolbar-border, #1f2937);\n      background: var(--toolbar-bg, rgba(15, 23, 42, 0.92));\n      color: var(--toolbar-btn-text, var(--text-main));\n      font-size: 11px;\n      cursor: pointer;\n      z-index: 20;\n      display: none;\n      transition: all 0.2s;\n      }\n      .legend-mini-btn:hover,\n      .legend-mini-btn:active {\n      background: var(--accent);\n      color: var(--bg);\n      border-color: var(--accent);\n      }\n      .legend-title {\n      font-size: 11px;\n      text-transform: uppercase;\n      letter-spacing: 0.06em;\n      color: var(--toolbar-text, var(--text-soft));\n      margin-bottom: 2px;\n      }\n\t  .fov-group {\n        transition: opacity 0.3s ease;\n      }\n      g[data-node-id]:not(:hover) .fov-group {\n        opacity: 0.7;\n      }\n      g[data-node-id]:hover .fov-group {\n        opacity: 1;\n      }\n      .legend-item {\n      display: flex;\n      align-items: center;\n      gap: 8px;\n      }\n      .legend-swatch {\n      width: 14px;\n      height: 14px;\n      border-radius: 3px;\n      border: 1px solid #020617;\n      flex-shrink: 0;\n      }\n      .legend-label {\n      outline: none;\n      cursor: text;\n      flex: 1;\n      min-width: 60px;\n      user-select: text;\n      -webkit-user-select: text;\n      }\n      .legend-label.editing {\n      border-bottom: 1px dashed var(--accent);\n      }\n      .canvas-viewport {\n      position: absolute;\n      top: 0;\n      left: 0;\n      right: 0;\n      bottom: 0;\n      overflow: hidden;\n      }\n      .canvas-viewport.panning {\n      cursor: grabbing !important;\n      }\n      .canvas-viewport.panning * {\n      cursor: grabbing !important;\n      }\n      .canvas-viewport svg {\n      width: 100%;\n      height: 100%;\n      display: block; \n      }\n      .zoom-toolbar {\n      display: flex;\n      gap: 4px;\n      padding: 6px;\n      background: var(--toolbar-bg, rgba(15, 23, 42, 0.92));\n      border: 1px solid var(--toolbar-border, #1f2937);\n      border-radius: 6px;\n      }\n      .zoom-toolbar button {\n      width: 32px;\n      height: 32px;\n      padding: 0;\n      background: var(--toolbar-btn-bg, var(--panel));\n      color: var(--toolbar-btn-text, var(--text-main));\n      border: 1px solid var(--toolbar-border, var(--edge-main));\n      border-radius: 4px;\n      cursor: pointer;\n      font-size: 18px;\n      font-weight: bold;\n      display: flex;\n      align-items: center;\n      justify-content: center;\n      transition: all 0.15s;\n      }\n      .zoom-toolbar button:hover {\n      background: var(--accent);\n      color: var(--bg);\n      }\n      .zoom-toolbar .zoom-level {\n      font-size: 11px;\n      color: var(--toolbar-text, var(--text-soft));\n      text-align: center;\n      padding: 2px 0;\n      min-width: 32px;\n      }\n      .zoom-toolbar .divider {\n      height: 1px;\n      background: var(--toolbar-border, var(--edge-main));\n      margin: 2px 0;\n      }\n      .minimap-zoom-wrapper {\n      position: absolute;\n      bottom: 10px;\n      right: 10px;\n      z-index: 99;\n      }\n      .minimap-container {\n      background: var(--toolbar-bg, rgba(15, 23, 42, 0.92));\n      border: 1px solid var(--toolbar-border, #1f2937);\n      border-radius: 6px;\n      overflow: hidden;\n      margin-bottom: 4px;\n      }\n      .minimap-container svg {\n      width: 100%;\n      height: 100%;\n      }\n\t.minimap-container,\n\t.node-panel,\n\t.edge-panel,\n\t#canvas-viewport {\n\t  contain: layout paint;\n\t}\t  \n\t.low-zoom .node-group .node-circle,\n\t.low-zoom .node-group.active .node-circle,\n\t.low-zoom .node-group.selected .node-circle {\n\t  filter: none !important;\n\t  animation: none !important;\n\t}\t\n      .minimap-viewport {\n      fill: rgba(79, 209, 197, 0.2);\n      stroke: var(--accent);\n      stroke-width: 2;\n      cursor: move;\n      }\n      .minimap-node {\n      fill: var(--minimap-dots, var(--text-soft));\n      }\n\t  .minimap-edge {\n      stroke: var(--edge-main);\n      stroke-width: 8px;\n      fill: none;\n      }\n\t  .minimap-wall {\n      pointer-events: none;\n      }\n      .minimap-rect {\n      pointer-events: none;\n      }\t  \n      .minimap-close-btn {\n      position: absolute;\n      top: 2px;\n      right: 2px;\n      width: 18px;\n      height: 18px;\n      background: var(--toolbar-btn-bg, rgba(15, 23, 42, 0.9));\n      border: 1px solid var(--toolbar-border, var(--edge-main));\n      border-radius: 3px;\n      color: var(--toolbar-text, var(--text-soft));\n      font-size: 11px;\n      cursor: pointer;\n      display: flex;\n      align-items: center;\n      justify-content: center;\n      z-index: 10;\n      padding: 0;\n      line-height: 1;\n      }\n      .minimap-close-btn:hover {\n      background: var(--danger);\n      color: white;\n      border-color: var(--danger);\n      }\n      .toolbar-close-btn {\n      width: 24px;\n      height: 24px;\n      background: var(--toolbar-btn-bg, rgba(15, 23, 42, 0.9));\n      border: 1px solid var(--toolbar-border, var(--edge-main));\n      border-radius: 4px;\n      color: var(--toolbar-text, var(--text-soft));\n      font-size: 12px;\n      cursor: pointer;\n      display: flex;\n      align-items: center;\n      justify-content: center;\n      padding: 0;\n      line-height: 1;\n      flex-shrink: 0;\n      }\n      .toolbar-close-btn:hover {\n      background: var(--danger);\n      color: white;\n      border-color: var(--danger);\n      }\n      .canvas-hint {\n      position: absolute;\n      top: 50px;\n      left: 50%;\n      transform: translateX(-50%);\n      padding: 8px 16px;\n      background: rgba(15, 23, 42, 0.92);\n      border: 1px solid #1f2937;\n      border-radius: 6px;\n      font-size: 12px;\n      color: var(--text-soft);\n      z-index: 18;\n      pointer-events: none;\n      opacity: 0;\n      transition: opacity 0.3s;\n      }\n      .canvas-hint.visible {\n      opacity: 1;\n      pointer-events: auto;\n      }\n      .edge {\n      stroke: var(--edge-main);\n      stroke-width: 4;\n      opacity: 0.75;\n      transition: 0.25s ease-in-out;\n      cursor: pointer;\n      }\n      .edge.backup {\n      stroke: var(--danger);\n      stroke-width: 5;\n      }\n      .edge.active {\n      opacity: 1;\n      stroke-width: 7;\n      filter: drop-shadow(0 0 8px var(--accent, #4fd1c5));\n      }\n      .rect-group.active .rect-shape {\n      filter: drop-shadow(0 0 10px var(--accent, #4fd1c5));\n      animation: pulse 1.2s infinite ease-in-out;\n      }\n      .text-element.active {\n      filter: drop-shadow(0 0 10px var(--accent, #4fd1c5));\n      animation: pulse 1.2s infinite ease-in-out;\n      }\n      .free-preview {\n      fill: none;\n      stroke-dasharray: 4 4;\n      pointer-events: none;\n      }\n@keyframes edge-flow-arrow {\n  0% { offset-distance: 0%; }\n  100% { offset-distance: 100%; }\n}\n@keyframes edge-flow-arrow-reverse {\n  0% { offset-distance: 100%; }\n  100% { offset-distance: 0%; }\n}\n.edge-arrow-forward {\n  offset-rotate: auto;\n  animation: edge-flow-arrow 1.5s linear infinite;\n}\n.edge-arrow-backward {\n  offset-rotate: auto 180deg;\n  animation: edge-flow-arrow-reverse 1.5s linear infinite;\n}\n      .free-point {\n      fill: #e5e7eb;\n      stroke: #0f172a;\n      stroke-width: 1.5;\n      cursor: grab;\n      }\n      .node-circle {\n      fill: var(--node-fill, #1e293b);\n      stroke: var(--node-stroke, #475569);\n      stroke-width: 2;\n      transition: 0.25s ease;\n      transform-origin: center center;\n      }\n      .node-hit-area {\n      cursor: grab;\n      pointer-events: all;\n      }\n      .node-group:hover .node-circle {\n      filter: drop-shadow(0 0 10px rgba(79, 209, 197, 0.45));\n      }\n      .node-group.active .node-circle {\n      stroke: var(--accent);\n      animation: pulse 1.2s infinite ease-in-out;\n      }\n      .node-group.selected .node-circle {\n      stroke: var(--selection-handle, #f59e0b);\n      stroke-width: 3;\n      }\n      .node-group.search-highlight .node-circle {\n      stroke: #10b981;\n      stroke-width: 3;\n      filter: drop-shadow(0 0 8px #10b981);\n      }\n\t  .node-group.search-faded {\n\t\topacity: 0.15;\n\t\tpointer-events: none;\n\t\t}\n\t\t.edge-group.search-faded {\n\t\topacity: 0.1;\n\t\t}\n\t  .node-group.search-faded {\n      opacity: 0.15;\n      pointer-events: none;\n      }\n      .edge-group.search-faded {\n      opacity: 0.1;\n      }\n      @keyframes pulse {\n      0% {\n      filter: drop-shadow(0 0 4px #4fd1c5);\n      }\n      50% {\n      filter: drop-shadow(0 0 14px #4fd1c5);\n      }\n      100% {\n      filter: drop-shadow(0 0 4px #4fd1c5);\n      }\n      }\n      @keyframes done-pulse {\n        0%, 100% {\n          transform: scale(1);\n          box-shadow: 0 0 0 0 rgba(79, 209, 197, 0.7);\n        }\n        50% {\n          transform: scale(1.05);\n          box-shadow: 0 0 0 8px rgba(79, 209, 197, 0);\n        }\n      }\n      .done-btn-active {\n        animation: done-pulse 1.5s ease-in-out infinite;\n        background: var(--accent) !important;\n        color: var(--bg) !important;\n      }\n      .node-label {\n      fill: var(--text-main);\n      font-size: 18px;\n      text-anchor: middle;\n      font-weight: 600;\n      }\n      .node-sub {\n      fill: var(--text-soft);\n      font-size: 13px;\n      text-anchor: middle;\n      }\n      @media (max-width: 1024px) {\n      .node-label {\n      font-size: 28px;\n      }\n      .node-sub {\n      font-size: 20px;\n      }\n      }\n      @media (max-width: 768px) {\n      .node-label {\n      font-size: 70px;\n      }\n      .node-sub {\n      font-size: 50px;\n      }\n      }\n      @media (max-width: 380px) {\n      .node-label {\n      font-size: 60px;\n      }\n      .node-sub {\n      font-size: 42px;\n      }\n      }\n      .details-panel {\n      background: var(--sidebar-bg, var(--panel-alt));\n      padding: 22px;\n      padding-bottom: 80px;\n      overflow-y: auto;\n      display: flex;\n      flex-direction: column;\n      gap: 15px;\n      position: relative;\n      transition:\n      width 0.3s ease,\n      min-width 0.3s ease,\n      padding 0.3s ease,\n      opacity 0.3s ease;\n      }\n      .details-panel {\n      min-width: 260px !important;\n      }\n      .details-panel {\n      overflow-y: auto !important;\n      overflow-x: hidden;\n      }\n      .details-panel.collapsed {\n      width: 0 !important;\n      min-width: 0 !important;\n      padding: 0 !important;\n      overflow: hidden;\n      opacity: 0;\n      }\n      body {\n      overflow-x: hidden;\n      overflow-y: hidden;\n      }\n      main {\n      overflow-x: hidden;\n      overflow-y: visible;\n      }\n      .details-panel {\n      overflow-y: auto !important;\n      overflow-x: hidden;\n      }\n      .details-panel.collapsed {\n      min-width: 0 !important;\n      }\n      .sidebar-toggle {\n      position: absolute;\n      left: -16px;\n      top: 50%;\n      transform: translateY(-50%);\n      width: 16px;\n      height: 60px;\n      background: var(--sidebar-bg, var(--panel-alt));\n      border: 1px solid var(--edge-main);\n      border-right: none;\n      border-radius: 8px 0 0 8px;\n      cursor: pointer;\n      display: flex;\n      align-items: center;\n      justify-content: center;\n      color: var(--text-soft);\n      font-size: 10px;\n      z-index: 25;\n      transition:\n      background 0.2s,\n      color 0.2s;\n      }\n      .sidebar-toggle:hover {\n      background: var(--accent);\n      color: var(--bg);\n      }\n      .sidebar-toggle.collapsed {\n      left: 0;\n      border-right: 1px solid var(--edge-main);\n      border-radius: 0 8px 8px 0;\n      position: fixed;\n      right: 0;\n      left: auto;\n      }\n      .details-name {\n      font-size: clamp(22px, 2.5vw, 30px);\n      font-weight: 700;\n      }\n      .details-ip {\n      font-size: clamp(16px, 2vw, 22px);\n      color: var(--text-soft);\n      }\n      .details-role {\n      font-size: clamp(16px, 2vw, 22px);\n      color: var(--accent);\n      }\n      .details-info-row {\n      display: flex;\n      align-items: center;\n      margin: 6px 0;\n      }\n      .size-controls {\n      display: flex;\n      gap: 10px;\n      align-items: center;\n      margin-top: 10px;\n      }\n      .size-controls label {\n      font-size: clamp(14px, 1.6vw, 18px);\n      color: var(--text-soft);\n      }\n      .size-controls input[type=\"range\"] {\n      flex: 1;\n      accent-color: var(--accent);\n      }\n      .size-controls button {\n      padding: 6px 12px;\n      background: var(--panel);\n      color: var(--text-main);\n      border: 1px solid var(--edge-main);\n      border-radius: 4px;\n      cursor: pointer;\n      font-size: clamp(12px, 1.4vw, 16px);\n      }\n      .size-controls button:hover {\n      background: var(--accent);\n      color: var(--bg);\n      }\n      .style-section {\n      margin-top: 15px;\n      padding-top: 15px;\n      border-top: 1px solid var(--edge-main);\n      }\n      .style-section summary {\n      cursor: pointer;\n      font-size: clamp(14px, 1.6vw, 20px);\n      text-transform: uppercase;\n      color: var(--text-soft);\n      list-style: none;\n      display: flex;\n      justify-content: space-between;\n      align-items: center;\n      user-select: none;\n      }\n      .style-section summary::-webkit-details-marker {\n      display: none;\n      }\n      .password-overlay {\n      position: fixed;\n      top: 0;\n      left: 0;\n      width: 100%;\n      height: 100%;\n      background: #ffffff;\n      z-index: 9999;\n      }\n      .style-section summary::after {\n      content: \"▼\";\n      transition: transform 0.2s;\n      }\n      .style-section[open] summary::after {\n      transform: rotate(180deg);\n      }\n.style-content {\n  display: grid;\n  grid-template-columns: 1fr auto;\n  gap: 8px 12px;\n  align-items: center;\n  justify-items: end;\n}\n.style-content label {\n  justify-self: start;\n}\n.style-row {\n  display: contents;\n}\n#edge-panel .style-row,\n#rect-panel .style-row,\n#text-panel .style-row {\n  display: flex;\n  align-items: center;\n  gap: 12px;\n  margin-bottom: 8px;\n}\n#edge-panel .style-row label,\n#rect-panel .style-row label,\n#text-panel .style-row label {\n  min-width: 80px;\n}\nbutton:disabled {\n  opacity: 0.5;\n  cursor: not-allowed;\n}\n      .style-row label {\n      font-size: clamp(13px, 1.5vw, 17px);\n      color: var(--text-soft);\n      min-width: 80px;\n      }\n      .style-row input[type=\"color\"] {\n      width: 50px;\n      height: 30px;\n      border: 1px solid var(--edge-main);\n      border-radius: 4px;\n      cursor: pointer;\n      background: transparent;\n      }\n      .style-row input[type=\"number\"] {\n      width: 70px;\n      padding: 4px 8px;\n      background: var(--panel);\n      border: 1px solid var(--edge-main);\n      border-radius: 4px;\n      color: var(--text-main);\n      font-size: clamp(12px, 1.4vw, 16px);\n      }\n      .style-row select {\n      flex: 1;\n      padding: 4px 8px;\n      background: var(--panel);\n      border: 1px solid var(--edge-main);\n      border-radius: 4px;\n      color: var(--text-main);\n      font-size: clamp(12px, 1.4vw, 16px);\n      cursor: pointer;\n      }\n      .editable-text {\n      cursor: pointer;\n      transition: opacity 0.2s;\n      }\n      .editable-text:hover {\n      opacity: 0.7;\n      }\n      .modal {\n\t  display: none;\n\t  position: fixed;\n\t  top: 0;\n\t  left: 0;\n\t  width: 100%;\n\t  height: 100%;\n\t  z-index: 999999;\n\t  justify-content: center;\n\t  align-items: center;\n\t  overflow: auto;\n\t  }\n\t  .modal.active {\n\t  display: inline-grid;\n\t  touch-action: pan-y;\n\t  }\n\t  #dialog-modal {\n\t  z-index: 9999999;\n\t  background: rgba(0, 0, 0, 0.7);\n\t  }\n\t  .modal-content {\n\t  background: var(--panel-alt);\n\t  padding: 25px;\n\t  border-radius: 8px;\n\t  border: 1px solid var(--edge-main);\n\t  min-width: 300px;\n\t  max-width: 90%;\n\t  max-height: 85vh;\n\t  overflow-y: auto;\n\t  overscroll-behavior: contain;\n\t  }\n      .modal-content h3 {\n      margin: 0 0 15px 0;\n      color: var(--text-main);\n      font-size: clamp(18px, 2vw, 24px);\n      }\n      .modal-content p {\n      color: var(--text-soft);\n      font-size: clamp(14px, 1.6vw, 18px);\n      margin: 0 0 20px 0;\n      }\n      .modal-content input:not([type=\"color\"]),\n      .modal-content select {\n      width: 100%;\n      padding: 10px;\n      background: var(--panel);\n      border: 1px solid var(--edge-main);\n      border-radius: 4px;\n      color: var(--text-main);\n      font-size: clamp(14px, 1.6vw, 18px);\n      margin-bottom: 15px;\n      user-select: text;\n      -webkit-user-select: text;\n      }\n      .modal-content input[type=\"color\"] {\n      width: 60px;\n      height: 36px;\n      padding: 2px;\n      border: 2px solid var(--edge-main);\n      border-radius: 6px;\n      cursor: pointer;\n      background: none;\n      -webkit-appearance: none;\n      appearance: none;\n      }\n      .modal-content input[type=\"color\"]::-webkit-color-swatch-wrapper {\n      padding: 0;\n      }\n      .modal-content input[type=\"color\"]::-webkit-color-swatch {\n      border: none;\n      border-radius: 4px;\n      }\n      .modal-buttons {\n      display: flex;\n      gap: 10px;\n      justify-content: flex-end;\n      margin-top: 10px;\n      padding-top: 10px;\n      border-top: 1px solid var(--edge-main);\n      }\n      .modal-buttons button {\n      padding: 8px 16px;\n      border: none;\n      border-radius: 4px;\n      cursor: pointer;\n      font-size: clamp(14px, 1.6vw, 18px);\n      font-weight: 600;\n      }\n      .modal-buttons .btn-cancel {\n      background: var(--panel);\n      color: var(--text-main);\n      border: 1px solid var(--edge-main);\n      }\n      .modal-buttons .btn-save {\n      background: var(--accent);\n      color: var(--bg);\n      }\n      .modal-buttons .btn-delete {\n      background: var(--danger);\n      color: white;\n      }\n      .confirm-modal p {\n      color: var(--text-soft);\n      font-size: clamp(14px, 1.6vw, 18px);\n      margin: 0 0 20px 0;\n      }\n      #note-editor-modal {\n      z-index: 9999999;\n      }\n      #note-editor-modal .modal-content {\n      width: 600px;\n      max-width: 95vw;\n      max-height: 90vh;\n      display: flex;\n      flex-direction: column;\n      }\n      .note-editor-toolbar {\n      display: flex;\n      flex-wrap: wrap;\n      gap: 4px;\n      padding: 8px;\n      background: var(--panel);\n      border: 1px solid var(--edge-main);\n      border-bottom: none;\n      border-radius: 6px 6px 0 0;\n      }\n      .note-editor-toolbar button {\n      padding: 6px 10px;\n      background: var(--panel-alt);\n      border: 1px solid var(--edge-main);\n      border-radius: 4px;\n      color: var(--text-main);\n      cursor: pointer;\n      font-size: 14px;\n      min-width: 32px;\n      }\n      .note-editor-toolbar button:hover {\n      background: var(--accent);\n      color: var(--bg);\n      }\n      .note-editor-toolbar button.active {\n      background: var(--accent);\n      color: var(--bg);\n      }\n      .note-editor-toolbar .toolbar-separator {\n      width: 1px;\n      background: var(--edge-main);\n      margin: 0 4px;\n      }\n      .note-editor-toolbar select {\n      padding: 6px 8px;\n      background: var(--panel-alt);\n      border: 1px solid var(--edge-main);\n      border-radius: 4px;\n      color: var(--text-main);\n      cursor: pointer;\n      font-size: 14px;\n      }\n      .note-editor-content {\n      min-height: 200px;\n      max-height: 400px;\n      overflow-y: auto;\n      padding: 12px;\n      background: var(--panel);\n      border: 1px solid var(--edge-main);\n      border-radius: 0 0 6px 6px;\n      color: var(--text-main);\n      font-size: 14px;\n      line-height: 1.6;\n      outline: none;\n      }\n      .note-editor-content:focus {\n      border-color: var(--accent);\n      }\n      .note-editor-content h1 { font-size: 1.8em; margin: 0.5em 0; color: var(--text-main); }\n      .note-editor-content h2 { font-size: 1.5em; margin: 0.5em 0; color: var(--text-main); }\n      .note-editor-content h3 { font-size: 1.2em; margin: 0.5em 0; color: var(--text-main); }\n      .note-editor-content p { margin: 0.5em 0; }\n      .note-editor-content ul, .note-editor-content ol { margin: 0.5em 0; padding-left: 1.5em; }\n      .note-editor-content li { margin: 0.25em 0; }\n      .note-editor-content code {\n      background: var(--panel-alt);\n      padding: 2px 6px;\n      border-radius: 4px;\n      font-family: monospace;\n      font-size: 0.9em;\n      }\n      .note-editor-content pre {\n      background: var(--panel-alt);\n      padding: 12px;\n      border-radius: 6px;\n      overflow-x: auto;\n      margin: 0.5em 0;\n      }\n      .note-editor-content pre code {\n      background: none;\n      padding: 0;\n      }\n      .note-editor-content a { color: var(--accent); }\n      .note-editor-content blockquote {\n      border-left: 3px solid var(--accent);\n      margin: 0.5em 0;\n      padding-left: 12px;\n      color: var(--text-soft);\n      }\n      .notes-feed {\n      max-height: 200px;\n      overflow-y: auto;\n      display: flex;\n      flex-direction: column;\n      gap: 8px;\n      margin-bottom: 10px;\n      }\n      .note-card {\n      background: var(--panel);\n      border: 1px solid var(--edge-main);\n      border-radius: 6px;\n      padding: 10px;\n      cursor: pointer;\n      transition: border-color 0.2s;\n      }\n      .note-card:hover {\n      border-color: var(--accent);\n      }\n      .note-card-content {\n      font-size: 13px;\n      line-height: 1.5;\n      color: var(--text-main);\n      max-height: 60px;\n      overflow: hidden;\n      word-break: break-word;\n      }\n      .note-card-content h1, .note-card-content h2, .note-card-content h3 {\n      font-size: 14px;\n      margin: 0;\n      }\n      .note-card-content p { margin: 0; }\n      .note-card-content pre { margin: 0; font-size: 12px; }\n      .note-card-actions {\n      display: flex;\n      gap: 8px;\n      margin-top: 8px;\n      justify-content: flex-end;\n      }\n      .note-card-actions button {\n      padding: 4px 10px;\n      font-size: 12px;\n      border-radius: 4px;\n      border: 1px solid var(--edge-main);\n      background: var(--panel-alt);\n      color: var(--text-main);\n      cursor: pointer;\n      }\n      .note-card-actions button:hover {\n      background: var(--accent);\n      color: var(--bg);\n      }\n      .note-card-actions button.delete-note:hover {\n      background: var(--danger);\n      color: white;\n      }\n      .context-menu-item {\n      transition: background 0.15s;\n      }\n      .context-menu-item:hover {\n      background: var(--panel);\n      }\n      .badge-row {\n      display: flex;\n      flex-wrap: wrap;\n      gap: 8px;\n      }\n      .badge {\n      background: var(--tag-fill, #1e293b);\n      color: var(--tag-text, #e2e8f0);\n      border: 1px solid var(--tag-border, var(--edge-main));\n      padding: 5px 12px;\n      border-radius: 22px;\n      font-size: clamp(12px, 1.4vw, 18px);\n      }\n      .badge.wg {\n      color: var(--accent);\n      border-color: var(--accent);\n      }\n      input[type=\"text\"],\n      input[type=\"number\"],\n      input[type=\"password\"],\n      textarea,\n      select {\n      background: var(--input-bg, #0b0e13) !important;\n      color: var(--input-text, #e2e8f0) !important;\n      border-color: var(--input-border, #1f2937) !important;\n      font-family: var(--input-font, Inter, system-ui, sans-serif) !important;\n      font-size: var(--input-font-size, 14px) !important;\n      }\n      .section-label {\n      margin-top: 8px;\n      font-size: clamp(14px, 1.6vw, 20px);\n      text-transform: uppercase;\n      color: var(--text-soft);\n      }\n      .list li {\n      margin: 6px 0;\n      font-size: clamp(14px, 1.6vw, 20px);\n      cursor: pointer;\n      display: flex;\n      justify-content: space-between;\n      align-items: center;\n      gap: 10px;\n      }\n      .list li:hover {\n      color: var(--accent);\n      }\n      .delete-note {\n      color: var(--danger);\n      font-size: 18px;\n      cursor: pointer;\n      opacity: 0;\n      transition: opacity 0.2s;\n      flex-shrink: 0;\n      }\n      .list li:hover .delete-note {\n      opacity: 1;\n      }\n      .editing {\n      outline: 2px solid var(--accent);\n      background: #0d141f;\n      padding: 4px;\n      border-radius: 6px;\n      user-select: text;\n      -webkit-user-select: text;\n      }\n\t  .fov-group {\n  transition: opacity 0.3s ease;\n}\ng[data-node-id]:not(:hover) .fov-group {\n  opacity: 0.6;\n}\ng[data-node-id]:hover .fov-group {\n  opacity: 1;\n}\n      .version-item {\n      display: flex;\n      justify-content: space-between;\n      align-items: center;\n      padding: 12px;\n      background: var(--panel);\n      border: 1px solid var(--edge-main);\n      border-radius: 6px;\n      cursor: pointer;\n      transition: all 0.2s;\n      }\n      .version-item:hover {\n      background: var(--panel-alt);\n      border-color: var(--accent);\n      }\n      .version-item.current {\n      border-color: var(--accent);\n      background: rgba(79, 209, 197, 0.1);\n      }\n      .version-info {\n      flex: 1;\n      }\n      .version-info .timestamp {\n      font-size: 13px;\n      color: var(--text-main);\n      font-weight: 600;\n      }\n      .version-info .details {\n      font-size: 11px;\n      color: var(--text-soft);\n      margin-top: 4px;\n      }\n      .version-actions {\n      display: flex;\n      gap: 6px;\n      }\n      .audit-entry {\n      padding: 8px 12px;\n      background: var(--panel);\n      border-left: 3px solid var(--edge-main);\n      border-radius: 4px;\n      font-size: 12px;\n      }\n      .audit-entry.node { border-left-color: #4fd1c5; }\n      .audit-entry.connection { border-left-color: #9f7aea; }\n      .audit-entry.style { border-left-color: #ed8936; }\n      .audit-entry.rack { border-left-color: #48bb78; }\n      .audit-entry.layer { border-left-color: #4299e1; }\n      .audit-entry .time {\n      color: var(--text-soft);\n      font-size: 10px;\n      }\n      .audit-entry .action {\n      color: var(--text-main);\n      font-weight: 500;\n      }\n      .tab-item {\n      display: flex;\n      justify-content: space-between;\n      align-items: center;\n      padding: 12px;\n      background: var(--panel);\n      border: 1px solid var(--edge-main);\n      border-radius: 6px;\n      cursor: pointer;\n      transition: all 0.2s;\n      }\n      .tab-item:hover {\n      background: var(--panel-alt);\n      }\n      .tab-item.active {\n      border-color: var(--accent);\n      background: rgba(79, 209, 197, 0.1);\n      }\n      .tab-item .tab-name {\n      font-weight: 600;\n      color: var(--text-main);\n      flex: 1;\n      }\n      .tab-item .tab-stats {\n      font-size: 11px;\n      color: var(--text-soft);\n      margin-right: 10px;\n      }\n      .tab-actions {\n      display: flex;\n      gap: 6px;\n      }\n      .secret-item {\n      display: flex;\n      justify-content: space-between;\n      align-items: center;\n      padding: 12px;\n      background: var(--panel);\n      border: 1px solid var(--edge-main);\n      border-radius: 6px;\n      border-left: 3px solid var(--danger);\n      }\n      .secret-item.note-type-global { border-left-color: var(--danger); }\n      .secret-item.note-type-node { border-left-color: var(--accent); }\n      .secret-item.note-type-edge { border-left-color: #22c55e; }\n      .secret-item.note-type-rect { border-left-color: #f97316; }\n      .secret-item.note-type-image { border-left-color: #8b5cf6; }\n      .secret-item .secret-name {\n      font-weight: 600;\n      color: var(--text-main);\n      flex: 1;\n      }\n      .secret-item .secret-status {\n      font-size: 11px;\n      color: var(--text-soft);\n      margin-right: 10px;\n      }\n      .secret-item .note-source {\n      font-size: 11px;\n      color: var(--text-soft);\n      background: var(--panel-alt);\n      padding: 2px 8px;\n      border-radius: 4px;\n      margin-right: 10px;\n      cursor: pointer;\n      }\n      .secret-item .note-source:hover {\n      background: var(--accent);\n      color: var(--bg);\n      }\n      .secret-item .note-preview {\n      font-size: 12px;\n      color: var(--text-soft);\n      max-width: 300px;\n      overflow: hidden;\n      text-overflow: ellipsis;\n      white-space: nowrap;\n      margin-right: 10px;\n      }\n      .mobile-menu-btn {\n      display: none;\n      background: var(--panel);\n      border: 1px solid var(--edge-main);\n      color: var(--text-main);\n      font-size: 22px;\n      padding: 6px 12px;\n      border-radius: 6px;\n      cursor: pointer;\n      }\n      #topbar-menu {\n      gap: 8px;\n      align-items: center;\n      }\n      @media (min-width: 721px) {\n      #topbar-menu {\n      display: flex !important;\n      }\n      }\n      @media (max-width: 720px) {\n      #topbar-menu {\n      display: none;\n      }\n      #topbar-menu.open {\n      display: flex;\n      }\n      }\n      @media (max-width: 720px) {\n      .mobile-menu-btn {\n      display: block;\n      }\n      #topbar-menu {\n      position: absolute;\n      top: var(--topbar-height);\n      right: 0;\n      background: var(--panel-alt);\n      border-left: 1px solid var(--topbar-border);\n      border-bottom: 1px solid var(--topbar-border);\n      padding: 12px;\n      display: none;\n      flex-direction: column;\n      max-height: calc(100vh - var(--topbar-height));\n      overflow-y: auto;\n      overscroll-behavior: contain;\n      z-index: 999;\n      }\n      #topbar-menu.open {\n      display: flex;\n      touch-action: pan-y;\n      }\n      header {\n      position: relative;\n      z-index: 9999;\n      }\n      }\n      @media (max-width: 720px) {\n      .draw-toolbar {\n      position: absolute;\n      top: auto;\n      bottom: auto;\n      left: 10px !important;\n      right: auto !important;\n      width: auto;\n      gap: 6px;\n      padding: 8px;\n      }\n      .topology-toolbar {\n      position: absolute;\n      top: auto;\n      bottom: auto;\n      width: auto;\n      gap: 6px;\n      padding: 8px;\n      }\n      .draw-toolbar {\n      top: 10px !important;\n      z-index:99;\n      }\n      .topology-toolbar {\n      z-index:99;\n      top: calc(10px + var(--draw-toolbar-height, 50px)) !important;\n      }\n      .canvas-hint {\n      top: calc(10px + 120px);\n      }\n      #search-input {\n      width: 100%;\n      }\n      }\n\t  body.view-only-mode:not(.view-only-inspect) #node-panel,\n      body.view-only-mode:not(.view-only-inspect) #edge-panel,\n      body.view-only-mode:not(.view-only-inspect) #text-panel,\n      body.view-only-mode:not(.view-only-inspect) #rect-panel {\n        display: none !important;\n      }\n      body.view-only-mode .node-group,\n      body.view-only-mode .edge,\n      body.view-only-mode .rect-group,\n      body.view-only-mode .text-element {\n        cursor: default !important;\n      }\n      body.view-only-mode #bulk-toolbar,\n      body.view-only-mode #bulk-toolbar-mobile,\n      body.view-only-mode #bulk-actions-modal {\n        display: none !important;\n      }\n      body.view-only-mode::after {\n        content: \"VIEW ONLY • tap 5× to inspect\";\n        position: fixed;\n        bottom: 20px;\n        left: 50%;\n        transform: translateX(-50%);\n        background: rgba(245, 101, 101, 0.9);\n        color: white;\n        padding: 6px 16px;\n        border-radius: 20px;\n        font-size: 12px;\n        font-weight: 600;\n        letter-spacing: 1px;\n        z-index: 9999;\n        pointer-events: none;\n      }\n.dropdown {\n  position: relative;\n  display: inline-block;\n}\n.dropdown-btn {\n  padding: 6px 12px;\n  background: var(--btn-bg, var(--panel));\n  color: var(--btn-text, var(--text-main));\n  border: 1px solid var(--edge-main);\n  border-radius: 6px;\n  cursor: pointer;\n  font-size: 14px;\n  font-weight: 600;\n  white-space: nowrap;\n}\n.dropdown-btn:hover {\n  background: var(--accent);\n  color: var(--bg);\n}\n.dropdown-menu {\n  display: none;\n  position: absolute;\n  top: 100%;\n  left: 0;\n  min-width: 180px;\n  background: var(--panel);\n  border: 1px solid var(--edge-main);\n  border-radius: 6px;\n  box-shadow: 0 4px 20px rgba(0,0,0,0.4);\n  z-index: 100000;\n  margin-top: 4px;\n  overflow: hidden;\n}\nheader {\n  z-index: 99999;\n  position: relative;\n}\n.dropdown-menu.open {\n  display: block;\n}\n.dropdown-menu button {\n  display: block;\n  width: 100%;\n  padding: 10px 14px;\n  background: none;\n  border: none;\n  color: var(--text-main);\n  text-align: left;\n  cursor: pointer;\n  font-size: 14px;\n}\n.dropdown-menu button:hover {\n  background: var(--panel-alt);\n  color: var(--accent);\n}\n.dropdown-divider {\n  height: 1px;\n  background: var(--edge-main);\n  margin: 4px 0;\n}\n@media (max-width: 900px) {\n  .save-row .dropdown {\n    display: none !important;\n  }\n}\n\t@media print {\n\t  @page {\n\t\tsize: landscape;\n\t\tmargin: 0.5cm;\n\t  }\n\t  html, body {\n\t\twidth: 100%;\n\t\theight: 100%;\n\t\tmargin: 0;\n\t\tpadding: 0;\n\t\toverflow: visible !important;\n\t  }\n\t  body * {\n\t\tvisibility: hidden;\n\t  }\n\t  #canvas-viewport,\n\t  #canvas-viewport *,\n\t  #map,\n\t  #map * {\n\t\tvisibility: visible;\n\t  }\n\t  #canvas-viewport {\n\t\tposition: absolute !important;\n\t\tleft: 0 !important;\n\t\ttop: 0 !important;\n\t\tright: 0 !important;\n\t\tbottom: 0 !important;\n\t\twidth: 100vw !important;\n\t\theight: 100vh !important;\n\t\toverflow: visible !important;\n\t  }\n\t  #map {\n\t\tposition: absolute !important;\n\t\tleft: 0 !important;\n\t\ttop: 0 !important;\n\t\twidth: 100% !important;\n\t\theight: 100% !important;\n\t\tbackground: white !important;\n\t\tbackground-image: none !important;\n\t  }\n\t  #canvas-grid {\n\t\tdisplay: none !important;\n\t  }\n\t  main, .topology-panel {\n\t\tdisplay: block !important;\n\t\tposition: static !important;\n\t\toverflow: visible !important;\n\t  }\n\n\t  #map .node-hit-area,\n\t  #map .group-indicator,\n\t  #map .lock-indicator,\n\t  #map .fov-group,\n\t  #map .edge-arrow-forward,\n\t  #map .edge-arrow-backward {\n\t\tdisplay: none !important;\n\t  }\n\n\t  #map .node-circle,\n\t  #map .node-shape {\n\t\tfill: white !important;\n\t\tstroke: #000 !important;\n\t\tstroke-width: 2px !important;\n\t  }\n\n\t  #map text {\n\t\tfill: #000 !important;\n\t\tstroke: none !important;\n\t  }\n\n\t  #map .port-label rect {\n\t\tfill: white !important;\n\t\tstroke: #000 !important;\n\t\tstroke-width: 1px !important;\n\t\tstroke-opacity: 1 !important;\n\t  }\n\n\t  #map .edge,\n\t  #map polyline,\n\t  #map line:not([class*=\"grid\"]) {\n\t\tstroke: #333 !important;\n\t  }\n\n\t  #map .rect-group rect {\n\t\tstroke: #333 !important;\n\t  }\n\n\t  header, .sidebar, .mobile-footer, .minimap-zoom-wrapper,\n\t  .draw-toolbar, .topology-toolbar, .legend-container,\n\t  .bulk-toolbar, #bulk-toolbar-mobile, .dropdown-menu,\n\t  #canvas-hint, .node-panel, .edge-panel, .text-panel, .rect-panel {\n\t\tdisplay: none !important;\n\t  }\n\t}\n\t#mobile-export-btn,\n#mobile-import-btn {\n  display: none;\n}\n@media (max-width: 900px) {\n  #mobile-export-btn,\n  #mobile-import-btn {\n    display: inline-block;\n  }\n}\n\nhtml.rtl {\n  direction: rtl;\n}\nhtml.rtl .sidebar { left: auto; right: 0; }\nhtml.rtl .mobile-footer { direction: rtl; }\nhtml.rtl .modal-content { text-align: right; }\nhtml.rtl .modal-buttons { flex-direction: row-reverse; }\nhtml.rtl .style-row { flex-direction: row-reverse; }\nhtml.rtl .style-row label { text-align: right; }\nhtml.rtl input, html.rtl select, html.rtl textarea { text-align: right; }\nhtml.rtl .topbar { flex-direction: row-reverse; }\nhtml.rtl .topbar-left { flex-direction: row-reverse; }\nhtml.rtl .topbar-right { flex-direction: row-reverse; }\nhtml.rtl details summary { text-align: right; }\nhtml.rtl .node-panel-content { direction: rtl; }\nhtml.rtl .help-content li { text-align: right; }\nhtml.rtl table { direction: rtl; }\nhtml.rtl td:first-child { text-align: right; }\nhtml.rtl .lang-string-row { flex-direction: row-reverse; }\n    </style>\n  </head>\n  <body style=\"background: radial-gradient(circle at center top, rgb(30, 37, 50) 0px, rgb(5, 6, 8) 70%);\" class=\"\">\n    <div class=\"modal\" id=\"edit-modal\">\n      <div class=\"modal-content\">\n        <h3 id=\"modal-title\" data-lang=\"editModal.editTitle\">Edit Title</h3>\n        <input type=\"text\" id=\"modal-input\">\n        <div class=\"modal-buttons\">\n          <button class=\"btn-cancel\" id=\"modal-cancel\" data-lang=\"ui.buttons.cancel\">Cancel</button>\n          <button class=\"btn-save\" id=\"modal-save\" data-lang=\"ui.buttons.save\">Save</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"note-editor-modal\">\n      <div class=\"modal-content\">\n        <h3 id=\"note-editor-title\" data-lang=\"noteEditor.title\">Edit Note</h3>\n        <div class=\"note-editor-toolbar\">\n          <button type=\"button\" data-command=\"bold\" title=\"Bold (Ctrl+B)\"><b>B</b></button>\n          <button type=\"button\" data-command=\"italic\" title=\"Italic (Ctrl+I)\"><i>I</i></button>\n          <button type=\"button\" data-command=\"underline\" title=\"Underline (Ctrl+U)\"><u>U</u></button>\n          <div class=\"toolbar-separator\"></div>\n          <select id=\"note-heading-select\" title=\"Heading\">\n            <option value=\"\">Normal</option>\n            <option value=\"h1\">H1</option>\n            <option value=\"h2\">H2</option>\n            <option value=\"h3\">H3</option>\n          </select>\n          <div class=\"toolbar-separator\"></div>\n          <button type=\"button\" data-command=\"insertUnorderedList\" title=\"Bullet List\">&#8226;</button>\n          <button type=\"button\" data-command=\"insertOrderedList\" title=\"Numbered List\">1.</button>\n          <div class=\"toolbar-separator\"></div>\n          <button type=\"button\" data-command=\"code\" title=\"Inline Code\">&lt;/&gt;</button>\n          <button type=\"button\" data-command=\"codeblock\" title=\"Code Block\">[ ]</button>\n          <div class=\"toolbar-separator\"></div>\n          <button type=\"button\" data-command=\"createLink\" title=\"Insert Link\">&#128279;</button>\n          <button type=\"button\" data-command=\"blockquote\" title=\"Quote\">&#8220;</button>\n          <div class=\"toolbar-separator\"></div>\n          <button type=\"button\" data-command=\"removeFormat\" title=\"Clear Formatting\">&#10006;</button>\n        </div>\n        <div class=\"note-editor-content\" id=\"note-editor-content\" contenteditable=\"true\"></div>\n        <div class=\"modal-buttons\">\n          <button class=\"btn-cancel\" id=\"note-editor-cancel\" data-lang=\"ui.buttons.cancel\">Cancel</button>\n          <button class=\"btn-save\" id=\"note-editor-save\" data-lang=\"ui.buttons.save\">Save</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal confirm-modal\" id=\"confirm-modal\">\n      <div class=\"modal-content\">\n        <h3 data-lang=\"confirmModal.confirm\">Confirm</h3>\n        <p id=\"confirm-message\" data-lang=\"confirmModal.deleteLineQuestion\"> Are you sure you want to delete this line? </p>\n        <div class=\"modal-buttons\">\n          <button class=\"btn-cancel\" id=\"confirm-cancel\" data-lang=\"ui.buttons.cancel\">Cancel</button>\n          <button class=\"btn-delete\" id=\"confirm-delete\" data-lang=\"ui.buttons.delete\">Delete</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"error-modal\">\n      <div class=\"modal-content\">\n        <h3 id=\"error-modal-title\" style=\"color: #dc2626;\" data-lang=\"ui.modals.error\">Error</h3>\n        <p id=\"error-modal-message\" style=\"margin: 16px 0; line-height: 1.6;\"></p>\n        <div class=\"modal-buttons\">\n          <button class=\"btn-save\" id=\"error-modal-ok\" data-lang=\"ui.buttons.close\">OK</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"dialog-modal\">\n      <div class=\"modal-content\">\n        <h3 id=\"dialog-modal-title\" data-lang=\"dialogs.confirm\">Confirm</h3>\n        <p id=\"dialog-modal-message\" style=\"margin: 16px 0; line-height: 1.6; white-space: pre-wrap;\"></p>\n        <input type=\"text\" id=\"dialog-modal-input\" style=\"display: none; width: 100%; padding: 10px; margin-bottom: 16px; background: var(--input-bg); color: var(--input-text); border: 1px solid var(--input-border); border-radius: 6px; font-size: 14px;\">\n        <div class=\"modal-buttons\" id=\"dialog-modal-buttons\">\n          <button class=\"btn-cancel\" id=\"dialog-modal-cancel\" data-lang=\"dialogs.cancel\">Cancel</button>\n          <button class=\"btn-save\" id=\"dialog-modal-ok\" data-lang=\"dialogs.ok\">OK</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"save-info-modal\">\n      <div class=\"modal-content\" style=\"max-width: 700px; max-height: 80vh; overflow-y: auto;\">\n        <h3 style=\"margin-bottom: 16px;\" data-lang=\"help.title\">Help</h3>\n        <div style=\"display: flex; gap: 8px; margin-bottom: 16px; flex-wrap: wrap;\">\n          <button class=\"help-tab active\" data-tab=\"general\" style=\"padding: 8px 16px; border: 1px solid var(--edge-main); background: var(--accent); color: var(--bg); border-radius: 6px; cursor: pointer; font-weight: 600;\" data-lang=\"help.tabs.general\">General</button>\n          <button class=\"help-tab\" data-tab=\"desktop\" style=\"padding: 8px 16px; border: 1px solid var(--edge-main); background: var(--panel); color: var(--text-main); border-radius: 6px; cursor: pointer; font-weight: 600;\" data-lang=\"help.tabs.desktop\">Desktop</button>\n          <button class=\"help-tab\" data-tab=\"mobile\" style=\"padding: 8px 16px; border: 1px solid var(--edge-main); background: var(--panel); color: var(--text-main); border-radius: 6px; cursor: pointer; font-weight: 600;\" data-lang=\"help.tabs.mobile\">Mobile</button>\n\t\t  <button class=\"help-tab\" data-tab=\"formats\" style=\"padding: 8px 16px; border: 1px solid var(--edge-main); background: var(--panel); color: var(--text-main); border-radius: 6px; cursor: pointer; font-weight: 600;\" data-lang=\"help.tabs.formats\">Import/Export</button>\n          <button class=\"help-tab\" data-tab=\"recording\" style=\"padding: 8px 16px; border: 1px solid var(--edge-main); background: var(--panel); color: var(--text-main); border-radius: 6px; cursor: pointer; font-weight: 600;\" data-lang=\"help.tabs.recording\">Recording</button>\n        </div>\n        <div id=\"help-tab-general\" class=\"help-tab-content\" style=\"display: block;\">\n          <ul style=\"margin-left: 20px; margin-bottom: 12px; line-height: 1.8;\">\n            <li><strong data-lang=\"help.general.addNodes\">Add Nodes:</strong> <span data-lang=\"help.general.addNodesDesc\">Click \"+ Node\" or \"+ Rack\" in the top menu</span></li>\n            <li><strong data-lang=\"help.general.connectNodes\">Connect Nodes:</strong> <span data-lang=\"help.general.connectNodesDesc\">Select a node, then use \"Add Connection\" in the panel</span></li>\n            <li><strong data-lang=\"help.general.moveNodes\">Move Nodes:</strong> <span data-lang=\"help.general.moveNodesDesc\">Drag nodes to reposition them</span></li>\n            <li><strong data-lang=\"help.general.enterRackView\">Enter Rack View:</strong> <span data-lang=\"help.general.enterRackViewDesc\">Double click on desktop or Long press on mobile</span></li>\n            <li><strong data-lang=\"help.general.multiSelect\">Multi Select:</strong> <span data-lang=\"help.general.multiSelectDesc\">Right click (desktop) or double tap (mobile)</span></li>\n            <li><strong data-lang=\"help.general.panCanvas\">Pan Canvas:</strong> <span data-lang=\"help.general.panCanvasDesc\">Drag empty space or hold Space + drag</span></li>\n            <li><strong data-lang=\"help.general.zoom\">Zoom:</strong> <span data-lang=\"help.general.zoomDesc\">Scroll wheel or pinch gesture</span></li>\n            <li><strong data-lang=\"help.general.freeDraw\">Free Draw:</strong> <span data-lang=\"help.general.freeDrawDesc\">Use draw toolbar for drawing lines, boxes/zones, and text</span></li>\n          </ul>\n          <h4 style=\"color: var(--accent); margin-bottom: 8px;\" data-lang=\"help.general.savingEncryption\">Saving &amp; Encryption</h4>\n          <p style=\"margin-bottom: 8px;\" data-lang=\"help.general.savingNote\">Browsers cannot overwrite local files. Click Save File to download an updated HTML with all changes. Replace your old file to keep edits. An automatic backup is also saved to your browser's local storage and will prompt for recovery if you close without saving.</p>\n          <p style=\"margin-bottom: 8px;\"><strong data-lang=\"help.general.encryptionNote\">Encryption of data:</strong> <span data-lang=\"help.general.encryptionDesc\">Check \"Encrypt\" before saving to password protect your data. Beware! No recovery possible without password!!</span></p>\n          <p><strong data-lang=\"help.general.decryptionNote\">Decryption of data:</strong> <span data-lang=\"help.general.decryptionDesc\">Export via JSON in top settings menu after successful password validation to decrypt your data. You can then reimport back into the same file and save!</span></p>\n        </div>\n        <div id=\"help-tab-desktop\" class=\"help-tab-content\" style=\"display: none;\">\n          <h4 style=\"color: var(--accent); margin-bottom: 12px;\" data-lang=\"help.desktop.navigation\">Navigation</h4>\n          <table style=\"width: 100%; border-collapse: collapse; margin-bottom: 16px; font-size: 14px;\">\n            <tbody><tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\" data-lang=\"help.desktop.arrowKeys\">Arrow Keys</code></td>\n              <td style=\"padding: 8px;\" data-lang=\"help.desktop.moveSelected1px\">Move selected 1px</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\" data-lang=\"help.desktop.shiftArrows\">Shift + Arrows</code></td>\n              <td style=\"padding: 8px;\" data-lang=\"help.desktop.moveSelected10px\">Move selected 10px</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\" data-lang=\"help.desktop.tabShiftTab\">Tab / Shift+Tab</code></td>\n              <td style=\"padding: 8px;\" data-lang=\"help.desktop.cycleNodes\">Cycle through nodes</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\" data-lang=\"help.desktop.fKey\">F</code></td>\n              <td style=\"padding: 8px;\" data-lang=\"help.desktop.focusSelected\">Focus on selected</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\" data-lang=\"help.desktop.spaceDrag\">Space + Drag</code></td>\n              <td style=\"padding: 8px;\" data-lang=\"help.desktop.panCanvas\">Pan canvas</td>\n            </tr>\n          </tbody></table>\n          <h4 style=\"color: var(--accent); margin-bottom: 12px;\" data-lang=\"help.desktop.management\">Management</h4>\n          <table style=\"width: 100%; border-collapse: collapse; margin-bottom: 16px; font-size: 14px;\">\n            <tbody><tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\" data-lang=\"help.desktop.lKey\">L</code></td>\n              <td style=\"padding: 8px;\" data-lang=\"help.desktop.lockUnlock\">Lock/unlock selected</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\" data-lang=\"help.desktop.gKey\">G</code></td>\n              <td style=\"padding: 8px;\" data-lang=\"help.desktop.groupUngroup\">Group/ungroup selected</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\" data-lang=\"help.desktop.ctrlCV\">Ctrl+C / Ctrl+V</code></td>\n              <td style=\"padding: 8px;\" data-lang=\"help.desktop.copyPaste\">Copy / Paste</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\" data-lang=\"help.desktop.ctrlD\">Ctrl+D</code></td>\n              <td style=\"padding: 8px;\" data-lang=\"help.desktop.duplicate\">Duplicate</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\" data-lang=\"help.desktop.ctrlA\">Ctrl+A</code></td>\n              <td style=\"padding: 8px;\" data-lang=\"help.desktop.selectAll\">Select all</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\" data-lang=\"help.desktop.deleteKey\">Delete</code></td>\n              <td style=\"padding: 8px;\" data-lang=\"help.desktop.deleteSelected\">Delete selected</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\" data-lang=\"help.desktop.escapeKey\">Escape</code></td>\n              <td style=\"padding: 8px;\" data-lang=\"help.desktop.clearSelection\">Clear selection</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\" data-lang=\"help.desktop.ctrlZY\">Ctrl+Z / Ctrl+Y</code></td>\n              <td style=\"padding: 8px;\" data-lang=\"help.desktop.undoRedo\">Undo / Redo</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\" data-lang=\"help.desktop.rightClick\">Right Click</code></td>\n              <td style=\"padding: 8px;\" data-lang=\"help.desktop.multiSelectToggle\">Multi select toggle</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\" data-lang=\"help.desktop.dblClickRack\">Double click rack</code></td>\n              <td style=\"padding: 8px;\" data-lang=\"help.desktop.enterRackView\">Enter rack view</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\" data-lang=\"help.desktop.dblClickConnection\">Double click connection</code></td>\n              <td style=\"padding: 8px;\" data-lang=\"help.desktop.addWaypoint\">Add waypoint</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\" data-lang=\"help.desktop.dblClickEmpty\">Double click empty (in rack)</code></td>\n              <td style=\"padding: 8px;\" data-lang=\"help.desktop.exitRackView\">Exit rack view</td>\n            </tr>\n          </tbody></table>\n          <h4 style=\"color: var(--accent); margin-bottom: 12px;\" data-lang=\"help.desktop.recording\">Recording</h4>\n          <table style=\"width: 100%; border-collapse: collapse; margin-bottom: 16px; font-size: 14px;\">\n            <tbody><tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\" data-lang=\"help.desktop.rKey\">R</code></td>\n              <td style=\"padding: 8px;\" data-lang=\"help.desktop.startStopRecording\">Start/stop real time recording</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\" data-lang=\"help.desktop.shiftR\">Shift+R</code></td>\n              <td style=\"padding: 8px;\" data-lang=\"help.desktop.startStopStepRecording\">Start/stop step by step recording</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\" data-lang=\"help.desktop.spaceKey\">Space</code></td>\n              <td style=\"padding: 8px;\" data-lang=\"help.desktop.addStepOrPlayPause\">Add step (step recording) / Play/Pause (playback)</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\" data-lang=\"help.desktop.pKey\">P</code></td>\n              <td style=\"padding: 8px;\" data-lang=\"help.desktop.playRecording\">Play recording</td>\n            </tr>\n          </tbody></table>\n        </div>\n        <div id=\"help-tab-mobile\" class=\"help-tab-content\" style=\"display: none;\">\n          <h4 style=\"color: var(--accent); margin-bottom: 12px;\" data-lang=\"help.mobile.basicGestures\">Basic Gestures</h4>\n          <table style=\"width: 100%; border-collapse: collapse; margin-bottom: 16px; font-size: 14px;\">\n            <tbody><tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><strong data-lang=\"help.mobile.tapNode\">Tap node</strong></td>\n              <td style=\"padding: 8px;\" data-lang=\"help.mobile.selectOpenProperties\">Select &amp; open properties</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><strong data-lang=\"help.mobile.tapEmpty\">Tap empty</strong></td>\n              <td style=\"padding: 8px;\" data-lang=\"help.mobile.deselectAll\">Deselect all</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><strong data-lang=\"help.mobile.dragNode\">Drag node</strong></td>\n              <td style=\"padding: 8px;\" data-lang=\"help.mobile.moveNode\">Move node</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><strong data-lang=\"help.mobile.dragEmpty\">Drag empty</strong></td>\n              <td style=\"padding: 8px;\" data-lang=\"help.mobile.panCanvas\">Pan canvas</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><strong data-lang=\"help.mobile.pinch\">Pinch</strong></td>\n              <td style=\"padding: 8px;\" data-lang=\"help.mobile.zoomInOut\">Zoom in/out</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><strong data-lang=\"help.mobile.doubleTapNode\">Double tap node</strong></td>\n              <td style=\"padding: 8px;\" data-lang=\"help.mobile.addRemoveSelection\">Add/remove from selection</td>\n            </tr>\n          </tbody></table>\n          <h4 style=\"color: var(--accent); margin-bottom: 12px;\" data-lang=\"help.mobile.rackView\">Rack View</h4>\n          <table style=\"width: 100%; border-collapse: collapse; margin-bottom: 16px; font-size: 14px;\">\n            <tbody><tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><strong data-lang=\"help.mobile.longPressRack\">Long press rack</strong></td>\n              <td style=\"padding: 8px;\" data-lang=\"help.mobile.enterRackView\">Enter rack view</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><strong data-lang=\"help.mobile.longPressConnection\">Long press connection</strong></td>\n              <td style=\"padding: 8px;\" data-lang=\"help.mobile.addWaypoint\">Add waypoint</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><strong data-lang=\"help.mobile.doubleTapEmpty\">Double tap empty</strong></td>\n              <td style=\"padding: 8px;\" data-lang=\"help.mobile.exitRackView\">Exit rack view</td>\n            </tr>\n          </tbody></table>\n        </div>\n\t\t<div id=\"help-tab-formats\" class=\"help-tab-content\" style=\"display: none;\">\n          <h4 style=\"color: var(--accent); margin-bottom: 12px;\" data-lang=\"help.formats.exportHelp\">Export Help</h4>\n          <table style=\"width: 100%; border-collapse: collapse; margin-bottom: 16px; font-size: 14px;\">\n            <tbody>\n              <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n                <td style=\"padding: 8px;\"><strong data-lang=\"help.formats.html\">HTML</strong></td>\n                <td style=\"padding: 8px;\" data-lang=\"help.formats.htmlDesc\">Complete self contained file and the default save method. When in doubt, choose this. Use the Save HTML button for this</td>\n              </tr>\n              <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n                <td style=\"padding: 8px;\"><strong data-lang=\"help.formats.json\">JSON</strong></td>\n                <td style=\"padding: 8px;\" data-lang=\"help.formats.jsonExportDesc\">Full backup. Editable in your favorite text editor</td>\n              </tr>\n              <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n                <td style=\"padding: 8px;\"><strong data-lang=\"help.formats.markdown\">Markdown</strong></td>\n                <td style=\"padding: 8px;\" data-lang=\"help.formats.markdownExportDesc\">Full backup. Editable in your favorite text editor</td>\n              </tr>\n              <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n                <td style=\"padding: 8px;\"><strong data-lang=\"help.formats.csv\">CSV</strong></td>\n                <td style=\"padding: 8px;\" data-lang=\"help.formats.csvExportDesc\">Full backup in header, nodes in spreadsheet rows</td>\n              </tr>\n              <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n                <td style=\"padding: 8px;\"><strong data-lang=\"help.formats.png\">PNG</strong></td>\n                <td style=\"padding: 8px;\" data-lang=\"help.formats.pngDesc\">Standard image of current canvas tab</td>\n              </tr>\n              <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n                <td style=\"padding: 8px;\"><strong data-lang=\"help.formats.svg\">SVG</strong></td>\n                <td style=\"padding: 8px;\" data-lang=\"help.formats.svgDesc\">Vector image, scalable and editable image of current canvas tab</td>\n              </tr>\n            </tbody>\n          </table>\n          <h4 style=\"color: var(--accent); margin-bottom: 12px;\" data-lang=\"help.formats.importHelp\">Import Help</h4>\n          <table style=\"width: 100%; border-collapse: collapse; margin-bottom: 16px; font-size: 14px;\">\n            <tbody>\n              <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n                <td style=\"padding: 8px;\"><strong data-lang=\"help.formats.json\">JSON</strong></td>\n                <td style=\"padding: 8px;\" data-lang=\"help.formats.jsonImportDesc\">Replaces all data</td>\n              </tr>\n              <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n                <td style=\"padding: 8px;\"><strong data-lang=\"help.formats.markdown\">Markdown</strong></td>\n                <td style=\"padding: 8px;\" data-lang=\"help.formats.markdownImportDesc\">Replaces all data</td>\n              </tr>\n              <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n                <td style=\"padding: 8px;\"><strong data-lang=\"help.formats.csv\">CSV</strong></td>\n                <td style=\"padding: 8px;\" data-lang=\"help.formats.csvImportDesc\">Choose full restore or add nodes only. Allows quickly creating nodes with a spreadsheet and importing into an existing save.</td>\n              </tr>\n              <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n                <td style=\"padding: 8px;\"><strong data-lang=\"help.formats.html\">HTML</strong></td>\n                <td style=\"padding: 8px;\" data-lang=\"help.formats.htmlImportDesc\">Import data from another saved HTML file. Extracts and restores all topology data from a previously exported file.</td>\n              </tr>\n            </tbody>\n          </table>\n        </div>\n        <div id=\"help-tab-recording\" class=\"help-tab-content\" style=\"display: none;\">\n          <h4 style=\"color: var(--accent); margin-bottom: 12px;\" data-lang=\"help.recording.title\">Canvas Recording</h4>\n          <p style=\"margin-bottom: 12px;\" data-lang=\"help.recording.overview\">Record your canvas activity to create visual documentation, tutorials, or demonstrations of your network topology changes.</p>\n          <h4 style=\"color: var(--accent); margin-bottom: 8px; margin-top: 16px;\" data-lang=\"help.recording.twoModes\">Two Recording Modes</h4>\n          <p style=\"margin-bottom: 8px;\"><strong style=\"color: #f56565;\" data-lang=\"help.recording.realtimeTitle\">Real time Recording</strong> <span data-lang=\"help.recording.realtimeDesc\">(red ● button) captures the canvas continuously at 10 frames per second as you work. Good for demonstrating workflows.</span></p>\n          <p style=\"margin-bottom: 12px;\"><strong style=\"color: #48bb78;\" data-lang=\"help.recording.stepByStepTitle\">Step by Step Recording</strong> <span data-lang=\"help.recording.stepByStepDesc\">(green ●+ button) lets you manually capture each frame. Click the green button to start, then press + or Space to capture each step. Each frame plays for 1 second. Good for tutorials where you want precise control over what's shown.</span></p>\n          <ul style=\"margin-left: 20px; margin-bottom: 12px; line-height: 1.8;\">\n            <li data-lang=\"help.recording.savedInFile\">Recordings are saved within your data file for later playback</li>\n            <li data-lang=\"help.recording.exportVideo\">Can be exported as a video file (WebM format) for sharing</li>\n            <li data-lang=\"help.recording.playWhileRecording\">Pressing Play while recording will stop and immediately play the new recording</li>\n          </ul>\n          <h4 style=\"color: var(--accent); margin-bottom: 8px; margin-top: 16px;\" data-lang=\"help.recording.controls\">Controls</h4>\n          <table style=\"width: 100%; border-collapse: collapse; margin-bottom: 16px; font-size: 14px;\">\n            <tbody>\n              <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n                <td style=\"padding: 8px;\"><strong style=\"color: #f56565;\">●</strong> <span data-lang=\"help.recording.recordBtn\">Record</span></td>\n                <td style=\"padding: 8px;\" data-lang=\"help.recording.recordBtnDesc\">Start real time recording (captures 10 frames/sec). Press R</td>\n              </tr>\n              <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n                <td style=\"padding: 8px;\"><strong style=\"color: #48bb78;\">●+</strong> <span data-lang=\"help.recording.stepRecordBtn\">Step Record</span></td>\n                <td style=\"padding: 8px;\" data-lang=\"help.recording.stepRecordBtnDesc\">Start step by step recording. Press Shift+R</td>\n              </tr>\n              <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n                <td style=\"padding: 8px;\"><strong style=\"color: #48bb78;\">+</strong> <span data-lang=\"help.recording.addStepBtn\">Add Step</span></td>\n                <td style=\"padding: 8px;\" data-lang=\"help.recording.addStepBtnDesc\">Capture current state as next frame (during step recording). Press Space</td>\n              </tr>\n              <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n                <td style=\"padding: 8px;\"><strong>▶</strong> <span data-lang=\"help.recording.playBtn\">Play</span></td>\n                <td style=\"padding: 8px;\" data-lang=\"help.recording.playBtnDesc\">Play back a saved recording. Press P</td>\n              </tr>\n              <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n                <td style=\"padding: 8px;\"><strong>■</strong> <span data-lang=\"help.recording.stopBtn\">Stop</span></td>\n                <td style=\"padding: 8px;\" data-lang=\"help.recording.stopBtnDesc\">Stop recording or playback. Press R or Shift+R again</td>\n              </tr>\n              <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n                <td style=\"padding: 8px;\"><strong>❚❚</strong> <span data-lang=\"help.recording.pauseBtn\">Pause</span></td>\n                <td style=\"padding: 8px;\" data-lang=\"help.recording.pauseBtnDesc\">Pause playback. Press Space</td>\n              </tr>\n              <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n                <td style=\"padding: 8px;\"><strong data-lang=\"help.recording.scrubber\">Scrubber</strong></td>\n                <td style=\"padding: 8px;\" data-lang=\"help.recording.scrubberDesc\">Seek to any point in the recording</td>\n              </tr>\n              <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n                <td style=\"padding: 8px;\"><strong data-lang=\"help.recording.speed\">Speed</strong></td>\n                <td style=\"padding: 8px;\" data-lang=\"help.recording.speedDesc\">Adjust playback speed (0.5x, 1x, 2x, 4x)</td>\n              </tr>\n              <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n                <td style=\"padding: 8px;\"><strong data-lang=\"help.recording.loop\">Loop</strong></td>\n                <td style=\"padding: 8px;\" data-lang=\"help.recording.loopDesc\">Toggle continuous loop playback</td>\n              </tr>\n              <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n                <td style=\"padding: 8px;\"><strong>...</strong> <span data-lang=\"help.recording.manage\">Manage</span></td>\n                <td style=\"padding: 8px;\" data-lang=\"help.recording.manageDesc\">View, rename, export, or delete saved recordings</td>\n              </tr>\n            </tbody>\n          </table>\n        </div>\n        <div class=\"modal-buttons\" style=\"margin-top: 16px;\">\n          <button class=\"btn-cancel\" id=\"save-info-close\" data-lang=\"ui.buttons.close\">Close</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"settings-modal\">\n      <div class=\"modal-content\">\n        <h2 data-lang=\"ui.modals.settings\">Settings</h2>\n        <details class=\"style-section\" open>\n          <summary data-lang=\"ui.sections.mappingModeTheme\">Mapping Mode & Theme</summary>\n          <div class=\"style-content\">\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.mode\">Mode</label>\n              <select id=\"mapping-mode-select\">\n                <option value=\"network\" data-lang=\"modes.network.name\">Network Topology</option>\n                <option value=\"mindmap\" data-lang=\"modes.mindmap.name\">Mind Map</option>\n                <option value=\"sports\" data-lang=\"modes.sports.name\">Sports Playbook</option>\n                <option value=\"smarthome\" data-lang=\"modes.smarthome.name\">Smart Home</option>\n                <option value=\"floorplan\" data-lang=\"modes.floorplan.name\">Floor Plan / Blueprint</option>\n              </select>\n            </div>\n            <div class=\"style-row\" id=\"canvas-style-row\">\n              <label data-lang=\"ui.labels.canvasStyle\">Canvas Style</label>\n              <select id=\"canvas-style-select\">\n                <option value=\"grid\" data-lang=\"canvas.grid\">Standard Grid</option>\n                <option value=\"dots\" data-lang=\"canvas.dots\">Dot Grid</option>\n                <option value=\"blueprint\" data-lang=\"canvas.blueprint\">Blueprint Grid</option>\n                <option value=\"none\" data-lang=\"canvas.none\">No Grid</option>\n              </select>\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.themePreset\">Theme Preset</label>\n              <div style=\"display:flex;gap:6px;align-items:center;\">\n                <select id=\"theme-preset\" style=\"flex:1;\">\n                  <option value=\"defaulted\" data-lang=\"themes.default\">Default</option>\n                  <option value=\"\" data-lang=\"ui.options.custom\">Custom</option>\n                  <optgroup label=\"Corporate\" data-lang-label=\"themeGroups.corporate\">\n                    <option value=\"slate\" data-lang=\"themes.slate\">Slate</option>\n                    <option value=\"graphite\" data-lang=\"themes.graphite\">Graphite</option>\n                    <option value=\"frost\" data-lang=\"themes.frost\">Frost (Light)</option>\n                  </optgroup>\n                  <optgroup label=\"Homelab\" data-lang-label=\"themeGroups.homelab\">\n                    <option value=\"synthwave\" data-lang=\"themes.synthwave\">Synthwave</option>\n                    <option value=\"terminal\" data-lang=\"themes.terminal\">Terminal</option>\n                  </optgroup>\n                  <optgroup label=\"Dev\" data-lang-label=\"themeGroups.dev\">\n                    <option value=\"dracula\" data-lang=\"themes.dracula\">Dracula</option>\n                    <option value=\"cobalt\" data-lang=\"themes.cobalt\">Cobalt</option>\n                    <option value=\"solarized\" data-lang=\"themes.solarized\">Solarized</option>\n                  </optgroup>\n                  <optgroup label=\"Mind Map\" data-lang-label=\"themeGroups.mindMap\">\n                    <option value=\"brainwave\" data-lang=\"themes.brainwave\">Brainwave</option>\n                                        <option value=\"neonMint\" data-lang=\"themes.neonMint\">Neon Mint</option>\n                    <option value=\"velvetDusk\" data-lang=\"themes.velvetDusk\">Velvet Dusk</option>\n                    <option value=\"sunburst\" data-lang=\"themes.sunburst\">Sunburst</option>\n                  </optgroup>\n                  <optgroup id=\"my-themes-group\" label=\"My Themes\" data-lang-label=\"themeGroups.myThemes\"></optgroup>\n                </select>\n                <button onclick=\"saveCurrentTheme()\" data-lang=\"tooltips.saveCurrentTheme\" data-lang-attr=\"title\" style=\"padding:4px 8px;background:var(--accent);color:var(--bg);border:none;border-radius:4px;cursor:pointer;font-size:12px;\" data-lang-text=\"ui.buttons.save\">Save</button>\n                <button id=\"delete-theme-btn\" onclick=\"deleteCurrentTheme()\" data-lang=\"tooltips.deleteTheme\" data-lang-attr=\"title\" style=\"padding:4px 8px;background:var(--danger);color:#fff;border:none;border-radius:4px;cursor:pointer;font-size:12px;\" data-lang-text=\"ui.buttons.del\">Del</button>\n              </div>\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.mainBackground\">Main Background</label>\n              <input type=\"color\" id=\"panel-color\" value=\"#0b0e13\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.accentColor\">Accent Color</label>\n              <input type=\"color\" id=\"accent-color\" value=\"#4fd1c5\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.primaryText\">Primary Text</label>\n              <input type=\"color\" id=\"text-main-color\" value=\"#e2e8f0\">\n            </div>\n          </div>\n        </details>\n        <details class=\"style-section\">\n          <summary data-lang=\"ui.sections.viewOnlyMode\">View Only Mode</summary>\n          <div class=\"style-content\">\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.viewOnly\">View Only (disable editing)</label>\n              <label class=\"toggle-switch\"><input type=\"checkbox\" id=\"view-only-mode\"><span class=\"toggle-slider\"></span></label>\n            </div>\n            <p style=\"margin-top:4px;font-size:12px;color:var(--text-soft);\" data-lang=\"messages.viewOnlyNote\">When enabled, all editing of the canvas is disabled. Pan and zoom still work.</p>\n          </div>\n        </details>\n        <details class=\"style-section\">\n          <summary data-lang=\"ui.sections.moreThemeOptions\">More Theme Options</summary>\n          <div class=\"style-content\">\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.sidebar\">Sidebar / Mobile Footer</label>\n              <input type=\"color\" id=\"sidebar-bg-color\" value=\"#10141b\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.modalBg\">Modal Window Background</label>\n              <input type=\"color\" id=\"panel-alt-color\" value=\"#10141b\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.dangerButtons\">Danger Buttons</label>\n              <input type=\"color\" id=\"danger-color\" value=\"#f56565\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.secondaryText\">Secondary Text</label>\n              <input type=\"color\" id=\"text-soft-color\" value=\"#94a3b8\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.tagFill\">Tag(s) Fill</label>\n              <input type=\"color\" id=\"tag-fill-color\" value=\"#1e293b\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.tagText\">Tag(s) Text</label>\n              <input type=\"color\" id=\"tag-text-color\" value=\"#e2e8f0\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.tagBorder\">Tag(s) Border</label>\n              <input type=\"color\" id=\"tag-border-color\" value=\"#475569\">\n            </div>\n          </div>\n        </details>\n\t\t<details class=\"style-section\">\n          <summary data-lang=\"ui.sections.topBar\">Top Bar</summary>\n          <div class=\"style-content\">\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.background\">Background</label>\n              <input type=\"color\" id=\"topbar-bg-color\" value=\"#0b0e13\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.border\">Border</label>\n              <input type=\"color\" id=\"topbar-border-color\" value=\"#1f2533\">\n            </div>\n          </div>\n\t\t  <div class=\"style-content\">\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.buttonFill\">Button Fill</label>\n              <input type=\"color\" id=\"btn-bg-color\" value=\"#0b0e13\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.buttonText\">Button Text</label>\n              <input type=\"color\" id=\"btn-text-color\" value=\"#e2e8f0\">\n            </div>\n          </div>\n        </details>\n        <details class=\"style-section\">\n          <summary data-lang=\"ui.sections.mainCanvas\">Main Canvas</summary>\n          <div class=\"style-content\">\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.border\">Border</label>\n              <input type=\"color\" id=\"canvas-border-color\" value=\"#475569\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.gridLines\">Grid Lines</label>\n              <input type=\"color\" id=\"canvas-grid-color\" value=\"#475569\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.gridSize\">Grid Size</label>\n              <input type=\"number\" id=\"canvas-grid-size\" value=\"50\" min=\"20\" max=\"200\" style=\"width:60px;\">\n            </div>\n\t\t\t<div class=\"style-row\">\n\t\t\t<label data-lang=\"ui.labels.showGrid\">Show Grid</label>\n\t\t\t<label class=\"toggle-switch\"><input type=\"checkbox\" id=\"canvas-grid-enabled\" checked><span class=\"toggle-slider\"></span></label>\n\t\t\t</div>\n\t\t\t<div class=\"style-row\">\n              <label data-lang=\"ui.labels.bgTop\">Background Top</label>\n              <input type=\"color\" id=\"canvas-gradient-top\" value=\"#1e2532\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.bgBottom\">Background Bottom</label>\n              <input type=\"color\" id=\"canvas-gradient-bottom\" value=\"#050608\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.solidBg\">Solid Background</label>\n              <input type=\"color\" id=\"page-bg-color\" value=\"#050608\">\n            </div>\n            <p style=\"margin-top:4px;font-size:12px;color:var(--text-soft);\" data-lang=\"messages.setSolidBgNote\">Set solid background to override gradient.</p>\n          </div>\n\t\t\t</details>\n\t\t  <details class=\"style-section\">\n          <summary data-lang=\"ui.sections.rackCanvas\">Rack Canvas</summary>\n          <div class=\"style-content\">\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.frameFill\">Frame Fill</label>\n              <input type=\"color\" id=\"rack-frame-fill\" value=\"#0f172a\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.frameBorder\">Frame Border</label>\n              <input type=\"color\" id=\"rack-frame-stroke\" value=\"#4fd1c5\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.gridLines\">Grid Lines</label>\n              <input type=\"color\" id=\"rack-line-color\" value=\"#475569\">\n            </div>\n\t\t\t<div class=\"style-row\">\n\t\t\t  <label data-lang=\"ui.labels.showGrid\">Show Grid</label>\n\t\t\t  <label class=\"toggle-switch\"><input type=\"checkbox\" id=\"rack-grid-enabled\" checked><span class=\"toggle-slider\"></span></label>\n\t\t\t</div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.uLabels\">U Labels</label>\n              <input type=\"color\" id=\"rack-text-color\" value=\"#4fd1c5\">\n            </div>\n          </div>\n        </details>\n\t\t        <details class=\"style-section\">\n          <summary data-lang=\"ui.sections.canvasToolbars\">Canvas Toolbars</summary>\n          <div class=\"style-content\">\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.background\">Background</label>\n              <input type=\"color\" id=\"toolbar-bg-color\" value=\"#0f172a\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.border\">Border</label>\n              <input type=\"color\" id=\"toolbar-border-color\" value=\"#1f2937\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.text\">Text</label>\n              <input type=\"color\" id=\"toolbar-text-color\" value=\"#94a3b8\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.buttonFill\">Button Fill</label>\n              <input type=\"color\" id=\"toolbar-btn-bg-color\" value=\"#0b0e13\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.buttonText\">Button Text</label>\n              <input type=\"color\" id=\"toolbar-btn-text-color\" value=\"#e2e8f0\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.minimapDots\">Minimap Dots</label>\n              <input type=\"color\" id=\"minimap-dots-color\" value=\"#94a3b8\">\n            </div>\n          </div>\n        </details>\n        <details class=\"style-section\">\n          <summary data-lang=\"ui.sections.nodes\">Nodes</summary>\n          <div class=\"style-content\">\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.fill\">Fill</label>\n              <input type=\"color\" id=\"node-fill-color\" value=\"#1e293b\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.border\">Border</label>\n              <input type=\"color\" id=\"node-stroke-color\" value=\"#475569\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.titleColor\">Title Color</label>\n              <input type=\"color\" id=\"node-title-color\" value=\"#e2e8f0\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.titleSize\">Title Size</label>\n              <input type=\"number\" id=\"node-title-size\" value=\"18\" min=\"8\" max=\"48\" style=\"width:60px;\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.subtitleColor\">Subtitle Color</label>\n              <input type=\"color\" id=\"node-sub-color\" value=\"#94a3b8\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.subtitleSize\">Subtitle Size</label>\n              <input type=\"number\" id=\"node-sub-size\" value=\"13\" min=\"8\" max=\"48\" style=\"width:60px;\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.font\">Font</label>\n              <select id=\"node-font-family\" style=\"flex:1;padding:4px;background:var(--panel);color:var(--text-main);border:1px solid var(--topbar-border);border-radius:4px;\">\n                <option value=\"Inter, system-ui, sans-serif\" data-lang=\"ui.fonts.inter\">Inter</option>\n                <option value=\"Arial, sans-serif\" data-lang=\"ui.fonts.arial\">Arial</option>\n                <option value=\"Helvetica, sans-serif\" data-lang=\"ui.fonts.helvetica\">Helvetica</option>\n                <option value=\"Georgia, serif\" data-lang=\"ui.fonts.georgia\">Georgia</option>\n                <option value=\"monospace\" data-lang=\"ui.fonts.monospace\">Monospace</option>\n              </select>\n            </div>\n          </div>\n        </details>\n        <details class=\"style-section\">\n          <summary data-lang=\"ui.sections.connections\">Connections</summary>\n          <div class=\"style-content\">\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.defaultColor\">Default Color</label>\n              <input type=\"color\" id=\"default-edge-color\" value=\"#475569\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.defaultRouting\">Default Routing</label>\n              <select id=\"default-edge-routing\">\n                <option value=\"orthogonal\" data-lang=\"ui.options.orthogonal\">Orthogonal (90°)</option>\n                <option value=\"curved\" data-lang=\"ui.options.curved\">Curved</option>\n                <option value=\"straight\" data-lang=\"ui.options.straight\">Straight</option>\n              </select>\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.animationStyle\">Animation Style</label>\n              <select id=\"animation-style-select\">\n                <option value=\"arrows\" data-lang=\"ui.options.flowingArrows\">Flowing Arrows</option>\n                <option value=\"dots\" data-lang=\"ui.options.dotArrows\">Dot Arrows</option>\n              </select>\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.animateDirections\">Animate Directions</label>\n              <select id=\"animation-direction-select\">\n                <option value=\"all\" data-lang=\"ui.options.allDirections\">All Directions</option>\n                <option value=\"forward\" data-lang=\"ui.options.forwardOnly\">Forward Only</option>\n                <option value=\"backward\" data-lang=\"ui.options.backwardOnly\">Backward Only</option>\n                <option value=\"both\" data-lang=\"ui.options.bidirectionalOnly\">Bidirectional Only</option>\n              </select>\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.animationSpeed\">Animation Speed</label>\n              <select id=\"animation-speed-select\">\n                <option value=\"0.5\" data-lang=\"ui.options.veryFast\">Very Fast</option>\n                <option value=\"1\" data-lang=\"ui.options.fast\">Fast</option>\n                <option value=\"1.5\" data-lang=\"ui.options.normal\">Normal</option>\n                <option value=\"2.5\" data-lang=\"ui.options.slow\">Slow</option>\n                <option value=\"4\" data-lang=\"ui.options.verySlow\">Very Slow</option>\n              </select>\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.showPortLabels\">Show Port Labels</label>\n              <label class=\"toggle-switch\"><input type=\"checkbox\" id=\"show-port-labels-toggle\" checked><span class=\"toggle-slider\"></span></label>\n            </div>\n            <div class=\"style-row\" style=\"grid-column: 1 / -1;\">\n              <button id=\"apply-routing-all\" style=\"width:100%;padding:8px;background:var(--accent);color:var(--bg);border:none;border-radius:6px;cursor:pointer;font-weight:600;\" data-lang=\"ui.buttons.applyRoutingAll\">Apply Routing to All Connections</button>\n            </div>\n          </div>\n        </details>\n\t\t<details class=\"style-section\">\n          <summary data-lang=\"ui.sections.animationsZones\">Animations &amp; Zones</summary>\n          <div class=\"style-content\">\n            <div class=\"style-row\"><label data-lang=\"ui.labels.allAnimations\">All Animations</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"anim-master\" checked><span class=\"toggle-slider\"></span></label></div>\n            <div style=\"grid-column:1/-1;font-size:11px;color:var(--text-soft);text-transform:uppercase;border-bottom:1px solid var(--edge-main);padding-bottom:4px;margin-top:8px;\" data-lang=\"ui.sections.byType\">By Type</div>\n            <div class=\"style-row\"><label data-lang=\"ui.labels.sweepPan\">Sweep (Pan)</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"anim-type-sweep\" checked><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label data-lang=\"ui.labels.pulseBreathe\">Pulse (Breathe)</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"anim-type-pulse\" checked><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label data-lang=\"ui.labels.ringsEmanate\">Rings (Emanate)</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"anim-type-rings\" checked><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label data-lang=\"ui.labels.spinRotate\">Spin (Rotate)</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"anim-type-spin\" checked><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label data-lang=\"ui.labels.connectionsFlow\">Connections (Flow)</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"anim-type-connections\" checked><span class=\"toggle-slider\"></span></label></div>\n            <div style=\"grid-column:1/-1;font-size:11px;color:var(--text-soft);text-transform:uppercase;border-bottom:1px solid var(--edge-main);padding-bottom:4px;margin-top:8px;\" data-lang=\"ui.sections.byCategory\">By Category</div>\n            <div class=\"style-row\"><label data-lang=\"ui.labels.cameras\">Cameras</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"anim-cat-camera\" checked><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label data-lang=\"ui.labels.doorbells\">Doorbells</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"anim-cat-doorbell\" checked><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label data-lang=\"ui.labels.motionSensors\">Motion Sensors</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"anim-cat-motion\" checked><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label data-lang=\"ui.labels.smokeDetectors\">Smoke Detectors</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"anim-cat-smoke\" checked><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label data-lang=\"ui.labels.wifiApRouter\">WiFi/AP/Router</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"anim-cat-wifi\" checked><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label data-lang=\"ui.labels.sensorsIot\">Sensors/IoT</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"anim-cat-sensor\" checked><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label data-lang=\"ui.labels.sprinklers\">Sprinklers</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"anim-cat-sprinkler\" checked><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label data-lang=\"ui.sections.connections\">Connections</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"anim-cat-connections\" checked><span class=\"toggle-slider\"></span></label></div>\n            <div style=\"grid-column:1/-1;font-size:12px;color:var(--accent);text-transform:uppercase;letter-spacing:0.05em;font-weight:600;margin-top:16px;padding-top:12px;border-top:1px solid var(--edge-main);\" data-lang=\"ui.sections.zoneSettings\">Zone (Animation Cone) Settings</div>\n            <div class=\"style-row\"><label data-lang=\"ui.labels.allZones\">All Zones</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"zone-master\" checked><span class=\"toggle-slider\"></span></label></div>\n            <div style=\"grid-column:1/-1;font-size:11px;color:var(--text-soft);text-transform:uppercase;border-bottom:1px solid var(--edge-main);padding-bottom:4px;margin-top:8px;\" data-lang=\"ui.sections.byCategory\">By Category</div>\n            <div class=\"style-row\"><label data-lang=\"ui.labels.cameras\">Cameras</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"zone-cat-camera\" checked><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label data-lang=\"ui.labels.doorbells\">Doorbells</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"zone-cat-doorbell\" checked><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label data-lang=\"ui.labels.motionSensors\">Motion Sensors</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"zone-cat-motion\" checked><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label data-lang=\"ui.labels.smokeDetectors\">Smoke Detectors</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"zone-cat-smoke\" checked><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label data-lang=\"ui.labels.wifiApRouter\">WiFi/AP/Router</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"zone-cat-wifi\" checked><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label data-lang=\"ui.labels.sensorsIot\">Sensors/IoT</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"zone-cat-sensor\" checked><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label data-lang=\"ui.labels.sprinklers\">Sprinklers</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"zone-cat-sprinkler\" checked><span class=\"toggle-slider\"></span></label></div>\n          </div>\n        </details>\n        <details class=\"style-section\">\n          <summary data-lang=\"ui.sections.groupsEditing\">Groups &amp; Editing</summary>\n          <div class=\"style-content\">\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.resizeHandleColor\">Resize Handle Color</label>\n              <input type=\"color\" id=\"selection-handle-color\" value=\"#f59e0b\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.resizeHandleSize\">Resize Handle Size</label>\n              <input type=\"number\" id=\"selection-handle-size\" value=\"8\" min=\"4\" max=\"16\" style=\"width:60px;\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.groupedIconOutline\">Grouped Icon Outline</label>\n              <input type=\"color\" id=\"group-indicator-color\" value=\"#4fd1c5\">\n            </div>\n          </div>\n\t\t   <div class=\"style-content\">\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.multiselectFill\">Multiselect Fill Color (Desktop)</label>\n              <input type=\"color\" id=\"selection-fill-color\" value=\"#4fd1c5\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.multiselectFillOpacity\">Multiselect Fill Opacity (Desktop)</label>\n              <div style=\"display:flex;align-items:center;gap:8px;\">\n                <input type=\"range\" id=\"selection-fill-opacity\" min=\"0\" max=\"1\" step=\"0.05\" value=\"0.1\">\n                <span id=\"selection-fill-opacity-val\" style=\"min-width:35px;text-align:right;\">10%</span>\n              </div>\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.multiselectBorder\">Multiselect Border Color (Desktop)</label>\n              <input type=\"color\" id=\"selection-stroke-color\" value=\"#4fd1c5\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.multiselectBorderWidth\">Multiselect Border Width (Desktop)</label>\n              <input type=\"number\" id=\"selection-stroke-width\" min=\"1\" max=\"10\" value=\"2\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.multiselectBorderStyle\">Multiselect Border Style (Desktop)</label>\n              <select id=\"selection-stroke-style\">\n                <option value=\"5,5\" data-lang=\"ui.options.dashed\">Dashed</option>\n                <option value=\"2,4\" data-lang=\"ui.options.dotted\">Dotted</option>\n                <option value=\"none\" data-lang=\"ui.options.solid\">Solid</option>\n              </select>\n            </div>\n          </div>\n        </details>\n        <details class=\"style-section\">\n          <summary data-lang=\"ui.sections.inputsDropdowns\">Inputs &amp; Dropdowns</summary>\n          <div class=\"style-content\">\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.background\">Background</label>\n              <input type=\"color\" id=\"input-bg-color\" value=\"#0b0e13\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.text\">Text</label>\n              <input type=\"color\" id=\"input-text-color\" value=\"#e2e8f0\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.border\">Border</label>\n              <input type=\"color\" id=\"input-border-color\" value=\"#1f2937\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.font\">Font</label>\n              <select id=\"input-font-family\" style=\"flex:1;padding:4px;background:var(--panel);color:var(--text-main);border:1px solid var(--topbar-border);border-radius:4px;\">\n                <option value=\"Inter, system-ui, sans-serif\" data-lang=\"ui.fonts.inter\">Inter</option>\n                <option value=\"Arial, sans-serif\" data-lang=\"ui.fonts.arial\">Arial</option>\n                <option value=\"Helvetica, sans-serif\" data-lang=\"ui.fonts.helvetica\">Helvetica</option>\n                <option value=\"Georgia, serif\" data-lang=\"ui.fonts.georgia\">Georgia</option>\n                <option value=\"monospace\" data-lang=\"ui.fonts.monospace\">Monospace</option>\n              </select>\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.fontSize\">Font Size</label>\n              <input type=\"number\" id=\"input-font-size\" value=\"14\" min=\"10\" max=\"24\" style=\"width:60px;\">\n            </div>\n          </div>\n        </details>\n        <details class=\"style-section\">\n          <summary data-lang=\"ui.sections.welcomeMessage\">Welcome Message</summary>\n          <div class=\"style-content\">\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.enabled\">Enabled</label>\n              <label class=\"toggle-switch\"><input type=\"checkbox\" id=\"canvas-hint-enabled\" checked><span class=\"toggle-slider\"></span></label>\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.background\">Background</label>\n              <input type=\"color\" id=\"canvas-hint-bg-color\" value=\"#0f172a\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.textColor\">Text Color</label>\n              <input type=\"color\" id=\"canvas-hint-text-color\" value=\"#94a3b8\">\n            </div>\n            <div class=\"style-row\" style=\"flex-direction:column;align-items:stretch;\">\n              <label data-lang=\"ui.labels.customText\">Custom Text (HTML)</label>\n              <textarea id=\"canvas-hint-text\" rows=\"4\" style=\"width:100%;margin-top:4px;padding:6px;background:var(--panel);color:var(--text-main);border:1px solid var(--topbar-border);border-radius:4px;font-size:12px;resize:vertical;\" placeholder=\"Leave empty for default\" data-lang=\"ui.placeholders.leaveEmpty\" data-lang-attr=\"placeholder\"></textarea>\n            </div>\n          </div>\n        </details>\n        <details class=\"style-section\">\n          <summary data-lang=\"ui.labels.language\">Language</summary>\n          <div class=\"style-content\">\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.currentLanguage\">Current:</label>\n              <span id=\"current-lang-name\" style=\"color:var(--accent);font-weight:600;\">English</span>\n            </div>\n            <div class=\"style-row\" style=\"flex-direction:column;align-items:stretch;gap:8px;\">\n\t\t    <div class=\"style-row\">\n\t\t\t\t<button onclick=\"openLanguageEditor()\" style=\"width:100%;fflex:1;padding:6px 12px;background:var(--accent);color:var(--bg);border:1px solid var(--topbar-border);border-radius:6px;cursor:pointer;font-size:13px;\" data-lang=\"ui.buttons.editStrings\">Edit Strings</button>\n            </div>\n            <div class=\"style-row\">\n           <button onclick=\"exportLanguageFile()\" style=\"width:100%;flex:1;padding:6px 12px;background:var(--btn-bg);color:var(--btn-text);border:1px solid var(--topbar-border);border-radius:6px;cursor:pointer;font-size:13px;\" data-lang=\"ui.buttons.exportLang\">Export Language File</button>\n            </div>\n\t\t\t    <div class=\"style-row\">\n\t\t\t\t\t<button onclick=\"document.getElementById('import-lang-file').click()\" style=\"width:100%;fflex:1;padding:6px 12px;background:var(--btn-bg);color:var(--btn-text);border:1px solid var(--topbar-border);border-radius:6px;cursor:pointer;font-size:13px;\" data-lang=\"ui.buttons.importLang\">Import Language File</button>\n\t\t\t\t<input type=\"file\" id=\"import-lang-file\" accept=\".json\" style=\"display:none\">\n            </div>\n            <div class=\"style-row\">\n        <button onclick=\"resetToDefaultLanguage()\" style=\"width:100%;padding:6px 12px;background:var(--btn-bg);color:var(--text-soft);border:1px solid var(--topbar-border);border-radius:6px;cursor:pointer;font-size:13px;\" data-lang=\"ui.buttons.resetLang\">Reset to English</button>\n            </div>\t\t          \n            </div>\n          </div>\n        </details>\n        <details class=\"style-section\" open=\"\">\n          <summary data-lang=\"ui.sections.dangerZone\">Danger Zone</summary>\n          <div class=\"style-content\">\n            <p style=\"margin-bottom:12px;font-size:13px;color:var(--text-soft);\" data-lang=\"messages.dangerZoneNote\">Permanently delete everything on the canvas.</p>\n            <button id=\"clear-all-btn\" data-lang=\"tooltips.clearAllNodes\" data-lang-attr=\"title\" style=\"padding:6px 12px;background:var(--danger);color:#fff;border:none;border-radius:6px;cursor:pointer;font-size:14px;font-weight:600;\" data-lang-text=\"ui.buttons.clearAll\">Clear All</button>\n            <input type=\"file\" id=\"import-data-file\" accept=\".json\" style=\"display:none\">\n          </div>\n        </details>\n        <div style=\"text-align:center;padding:10px 0 2px;font-size:11px;color:var(--text-soft);opacity:0.6;\">The One File v<span id=\"version-display\"></span></div>\n        <div class=\"modal-buttons\">\n          <button class=\"btn-cancel\" id=\"settings-close\" data-lang=\"ui.buttons.close\">Close</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"language-editor-modal\">\n      <div class=\"modal-content\" style=\"max-width:900px;max-height:85vh;display:flex;flex-direction:column;\">\n        <h3 data-lang=\"ui.modals.languageEditor\">Language Editor</h3>\n        <div style=\"display:flex;gap:8px;flex-wrap:wrap;margin-bottom:12px;\" class=\"lang-editor-tabs\">\n          <button class=\"lang-tab active\" data-filter=\"all\" style=\"padding:6px 12px;border:1px solid var(--topbar-border);border-radius:4px;background:var(--accent);color:var(--bg);cursor:pointer;font-size:12px;\" data-lang=\"langEditor.tabs.all\">All</button>\n          <button class=\"lang-tab\" data-filter=\"modes\" style=\"padding:6px 12px;border:1px solid var(--topbar-border);border-radius:4px;background:var(--btn-bg);color:var(--btn-text);cursor:pointer;font-size:12px;\" data-lang=\"langEditor.tabs.modes\">Modes</button>\n          <button class=\"lang-tab\" data-filter=\"modes.network\" style=\"padding:6px 12px;border:1px solid var(--topbar-border);border-radius:4px;background:var(--btn-bg);color:var(--btn-text);cursor:pointer;font-size:12px;\" data-lang=\"langEditor.tabs.network\">Network</button>\n          <button class=\"lang-tab\" data-filter=\"modes.mindmap\" style=\"padding:6px 12px;border:1px solid var(--topbar-border);border-radius:4px;background:var(--btn-bg);color:var(--btn-text);cursor:pointer;font-size:12px;\" data-lang=\"langEditor.tabs.mindMap\">Mind Map</button>\n          <button class=\"lang-tab\" data-filter=\"modes.sports\" style=\"padding:6px 12px;border:1px solid var(--topbar-border);border-radius:4px;background:var(--btn-bg);color:var(--btn-text);cursor:pointer;font-size:12px;\" data-lang=\"langEditor.tabs.sports\">Sports</button>\n          <button class=\"lang-tab\" data-filter=\"modes.smarthome\" style=\"padding:6px 12px;border:1px solid var(--topbar-border);border-radius:4px;background:var(--btn-bg);color:var(--btn-text);cursor:pointer;font-size:12px;\" data-lang=\"langEditor.tabs.smartHome\">Smart Home</button>\n          <button class=\"lang-tab\" data-filter=\"modes.floorplan\" style=\"padding:6px 12px;border:1px solid var(--topbar-border);border-radius:4px;background:var(--btn-bg);color:var(--btn-text);cursor:pointer;font-size:12px;\" data-lang=\"langEditor.tabs.floorPlan\">Floor Plan</button>\n          <button class=\"lang-tab\" data-filter=\"ui\" style=\"padding:6px 12px;border:1px solid var(--topbar-border);border-radius:4px;background:var(--btn-bg);color:var(--btn-text);cursor:pointer;font-size:12px;\" data-lang=\"langEditor.tabs.ui\">UI</button>\n          <button class=\"lang-tab\" data-filter=\"shapes\" style=\"padding:6px 12px;border:1px solid var(--topbar-border);border-radius:4px;background:var(--btn-bg);color:var(--btn-text);cursor:pointer;font-size:12px;\" data-lang=\"langEditor.tabs.shapes\">Shapes</button>\n          <button class=\"lang-tab\" data-filter=\"messages\" style=\"padding:6px 12px;border:1px solid var(--topbar-border);border-radius:4px;background:var(--btn-bg);color:var(--btn-text);cursor:pointer;font-size:12px;\" data-lang=\"langEditor.tabs.messages\">Messages</button>\n        </div>\n        <input type=\"text\" id=\"lang-search\" placeholder=\"Search strings...\" style=\"width:100%;padding:8px 12px;margin-bottom:12px;background:var(--input-bg);color:var(--input-text);border:1px solid var(--input-border);border-radius:6px;\" data-lang=\"langEditor.searchPlaceholder\" data-lang-attr=\"placeholder\">\n        <div class=\"lang-editor-list\" style=\"flex:1;overflow-y:auto;border:1px solid var(--topbar-border);border-radius:6px;background:var(--panel-alt);\"></div>\n        <div class=\"modal-buttons\" style=\"margin-top:12px;\">\n          <button class=\"btn-cancel\" onclick=\"closeLanguageEditor()\" data-lang=\"ui.buttons.cancel\">Cancel</button>\n          <button class=\"btn-save\" onclick=\"saveLangEdits()\" data-lang=\"ui.buttons.saveChanges\">Save Changes</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"add-node-modal\">\n      <div class=\"modal-content\">\n        <h3 id=\"add-node-title\" data-lang=\"ui.modals.addNode\">Add New Node</h3>\n        <label style=\"display: block;margin-bottom: 4px;color: var(--text-soft);font-size: 13px;\" data-lang=\"ui.labels.name\">Name</label>\n        <input type=\"text\" id=\"new-node-name\" placeholder=\"e.g. web-server\" data-lang=\"ui.placeholders.nodeName\" data-lang-attr=\"placeholder\">\n        <label id=\"new-node-ip-label\" style=\"display: block;margin-bottom: 4px;color: var(--text-soft);font-size: 13px;\" data-lang=\"ui.labels.ip\">IP / Subtitle</label>\n        <input type=\"text\" id=\"new-node-ip\" placeholder=\"e.g. 192.168.1.100\" data-lang=\"ui.placeholders.nodeIp\" data-lang-attr=\"placeholder\">\n        <label id=\"new-node-tags-label\" style=\"display: block;margin-bottom: 4px;color: var(--text-soft);font-size: 13px;\" data-lang=\"ui.labels.tagsComma\">Tags (comma separated)</label>\n        <input type=\"text\" id=\"new-node-tags\" placeholder=\"e.g. Docker, nginx, WG: vpn\" data-lang=\"ui.placeholders.tags\" data-lang-attr=\"placeholder\">\n        <label style=\"display: block;margin-bottom: 4px;color: var(--text-soft);font-size: 13px;\" data-lang=\"ui.labels.category\">Category</label>\n        <select id=\"new-node-category\">\n          <option value=\"basic\" data-lang=\"shapes.categories.basic\">Basic Shapes</option>\n          <option value=\"computers\" data-lang=\"shapes.categories.computers\">Computers & Devices</option>\n          <option value=\"network\" data-lang=\"shapes.categories.network\">Network Equipment</option>\n          <option value=\"cloud\" data-lang=\"shapes.categories.cloud\">Cloud & Services</option>\n          <option value=\"security\" data-lang=\"shapes.categories.security\">Security & Monitoring</option>\n          <option value=\"smarthome\" data-lang=\"shapes.categories.smarthome\">Smart Home</option>\n          <option value=\"sports\" data-lang=\"shapes.categories.sports\">Sports</option>\n          <option value=\"rack\" data-lang=\"shapes.categories.rack\">Rack Equipment</option>\n        </select>\n        <label style=\"display: block;margin-bottom: 4px;color: var(--text-soft);font-size: 13px;\" data-lang=\"ui.labels.shape\">Shape</label>\n        <select id=\"new-node-shape\"></select>\n        <div id=\"new-node-shape-preview\" style=\"display:flex;align-items:center;justify-content:center;width:48px;height:48px;border:1px solid var(--edge-main);border-radius:6px;background:var(--panel);margin:8px 0\"></div>\n        <details class=\"style-section\" id=\"new-node-more-options\">\n          <summary>More Options</summary>\n          <div class=\"style-content\" style=\"margin-top: 10px;\">\n            <label>Fill Color:</label>\n            <input type=\"color\" id=\"new-node-fill-color\" value=\"#1e293b\">\n            <label>Border Color:</label>\n            <input type=\"color\" id=\"new-node-border-color\" value=\"#475569\">\n          </div>\n          <div class=\"size-controls\" style=\"margin-top: 10px;\">\n            <label>Size:</label>\n            <input type=\"range\" id=\"new-node-size\" min=\"20\" max=\"200\" value=\"55\">\n            <span id=\"new-node-size-value\">55</span>\n          </div>\n          <div style=\"margin-top: 10px;\">\n            <label style=\"display: block;margin-bottom: 4px;color: var(--text-soft);font-size: 13px;\">Layer</label>\n            <select id=\"new-node-layer\" style=\"width: 100%;padding: 10px;background: var(--panel);border: 1px solid var(--edge-main);border-radius: 4px;color: var(--text-main);font-size: clamp(14px, 1.6vw, 18px);margin-bottom: 15px;\">\n              <option value=\"layer1\">Physical</option>\n              <option value=\"layer2\">Logical</option>\n              <option value=\"layer3\">Security</option>\n              <option value=\"layer4\">Application</option>\n            </select>\n          </div>\n          <div style=\"margin-top: 10px;\">\n            <label style=\"display: block;margin-bottom: 4px;color: var(--text-soft);font-size: 13px;\">Connect to</label>\n            <select id=\"new-node-connect-to\" style=\"width: 100%;padding: 10px;background: var(--panel);border: 1px solid var(--edge-main);border-radius: 4px;color: var(--text-main);font-size: clamp(14px, 1.6vw, 18px);margin-bottom: 8px;\">\n              <option value=\"\">None</option>\n            </select>\n            <div style=\"display: flex; gap: 8px; align-items: center; margin-bottom: 15px;\">\n              <input type=\"color\" id=\"new-node-line-color\" value=\"#475569\" style=\"width: 36px; height: 30px;\">\n              <select id=\"new-node-line-direction\" style=\"flex: 1;padding: 6px;background: var(--panel);border: 1px solid var(--edge-main);border-radius: 4px;color: var(--text-main);font-size: 13px;\">\n                <option value=\"none\">No arrows</option>\n                <option value=\"forward\">&rarr; Forward</option>\n                <option value=\"backward\">&larr; Backward</option>\n                <option value=\"both\">&harr; Both</option>\n              </select>\n              <select id=\"new-node-line-routing\" style=\"flex: 1;padding: 6px;background: var(--panel);border: 1px solid var(--edge-main);border-radius: 4px;color: var(--text-main);font-size: 13px;\">\n                <option value=\"orthogonal\">Orthogonal</option>\n                <option value=\"curved\">Curved</option>\n                <option value=\"straight\">Straight</option>\n              </select>\n            </div>\n          </div>\n          <div>\n            <label style=\"display: block;margin-bottom: 4px;color: var(--text-soft);font-size: 13px;\">Notes</label>\n            <textarea id=\"new-node-notes\" rows=\"3\" placeholder=\"Add notes...\" style=\"width: 100%;padding: 10px;background: var(--panel);border: 1px solid var(--edge-main);border-radius: 4px;color: var(--text-main);font-size: clamp(14px, 1.6vw, 18px);resize: vertical;font-family: inherit;\"></textarea>\n          </div>\n        </details>\n        <div class=\"modal-buttons\">\n          <button class=\"btn-cancel\" id=\"add-node-cancel\" data-lang=\"ui.buttons.cancel\">Cancel</button>\n          <button class=\"btn-save\" id=\"add-node-save\" data-lang=\"ui.buttons.addNode\">Add Node</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"add-rack-modal\">\n      <div class=\"modal-content\">\n        <h3 id=\"add-rack-title\" data-lang=\"ui.modals.addRack\">Add New Rack</h3>\n        <label id=\"new-rack-name-label\" style=\"display: block;margin-bottom: 4px;color: var(--text-soft);font-size: 13px;\" data-lang=\"ui.labels.name\">Rack Name</label>\n        <input type=\"text\" id=\"new-rack-name\" placeholder=\"e.g. Rack-A, DC1-R01\" data-lang=\"ui.placeholders.rackName\" data-lang-attr=\"placeholder\">\n        <label id=\"new-rack-ip-label\" style=\"display: block;margin-bottom: 4px;color: var(--text-soft);font-size: 13px;\" data-lang=\"ui.labels.networkRange\">IP / Network Range (optional)</label>\n        <input type=\"text\" id=\"new-rack-ip\" placeholder=\"e.g. 192.168.1.0/24\" data-lang=\"ui.placeholders.rackIp\" data-lang-attr=\"placeholder\">\n        <label id=\"new-rack-tags-label\" style=\"display: block;margin-bottom: 4px;color: var(--text-soft);font-size: 13px;\" data-lang=\"ui.labels.tagsComma\">Tags (comma separated)</label>\n        <input type=\"text\" id=\"new-rack-tags\" placeholder=\"e.g. Production, Data Center 1\" data-lang=\"ui.placeholders.rackTags\" data-lang-attr=\"placeholder\">\n        <label style=\"display: block;margin-bottom: 4px;color: var(--text-soft);font-size: 13px;\" data-lang=\"ui.labels.category\">Category</label>\n        <select id=\"new-rack-category\">\n          <option value=\"basic\" data-lang=\"shapes.categories.basic\">Basic Shapes</option>\n          <option value=\"computers\" data-lang=\"shapes.categories.computers\">Computers & Devices</option>\n          <option value=\"network\" data-lang=\"shapes.categories.network\">Network Equipment</option>\n          <option value=\"cloud\" data-lang=\"shapes.categories.cloud\">Cloud & Services</option>\n          <option value=\"security\" data-lang=\"shapes.categories.security\">Security & Monitoring</option>\n          <option value=\"smarthome\" data-lang=\"shapes.categories.smarthome\">Smart Home</option>\n          <option value=\"sports\" data-lang=\"shapes.categories.sports\">Sports</option>\n          <option value=\"rack\" data-lang=\"shapes.categories.rack\">Rack Equipment</option>\n        </select>\n        <label style=\"display: block;margin-bottom: 4px;color: var(--text-soft);font-size: 13px;\" data-lang=\"ui.labels.icon\">Icon / Shape</label>\n        <select id=\"new-rack-shape\"></select>\n        <div id=\"new-rack-shape-preview\" style=\"display:flex;align-items:center;justify-content:center;width:48px;height:48px;border:1px solid var(--edge-main);border-radius:6px;background:var(--panel);margin:8px 0\"></div>\n        <label style=\"display: block;margin-bottom: 4px;color: var(--text-soft);font-size: 13px;\" data-lang=\"ui.labels.rackCapacity\">Rack Capacity</label>\n        <select id=\"new-rack-capacity\">\n          <option value=\"42\" selected=\"\" data-lang=\"rack.capacity42\">42U (Standard Full Rack)</option>\n          <option value=\"48\" data-lang=\"rack.capacity48\">48U (Large Rack)</option>\n          <option value=\"24\" data-lang=\"rack.capacity24\">24U (Half Rack)</option>\n          <option value=\"12\" data-lang=\"rack.capacity12\">12U (Small/Wall Mount)</option>\n          <option value=\"6\" data-lang=\"rack.capacity6\">6U (Mini Rack)</option>\n        </select>\n        <details class=\"style-section\" id=\"new-rack-more-options\">\n          <summary>More Options</summary>\n          <div class=\"style-content\" style=\"margin-top: 10px;\">\n            <label>Fill Color:</label>\n            <input type=\"color\" id=\"new-rack-fill-color\" value=\"#1e293b\">\n            <label>Border Color:</label>\n            <input type=\"color\" id=\"new-rack-border-color\" value=\"#475569\">\n          </div>\n          <div class=\"size-controls\" style=\"margin-top: 10px;\">\n            <label>Size:</label>\n            <input type=\"range\" id=\"new-rack-size\" min=\"20\" max=\"200\" value=\"55\">\n            <span id=\"new-rack-size-value\">55</span>\n          </div>\n          <div style=\"margin-top: 10px;\">\n            <label style=\"display: block;margin-bottom: 4px;color: var(--text-soft);font-size: 13px;\">Layer</label>\n            <select id=\"new-rack-layer\" style=\"width: 100%;padding: 10px;background: var(--panel);border: 1px solid var(--edge-main);border-radius: 4px;color: var(--text-main);font-size: clamp(14px, 1.6vw, 18px);margin-bottom: 15px;\">\n              <option value=\"layer1\">Physical</option>\n              <option value=\"layer2\">Logical</option>\n              <option value=\"layer3\">Security</option>\n              <option value=\"layer4\">Application</option>\n            </select>\n          </div>\n          <div style=\"margin-top: 10px;\">\n            <label style=\"display: block;margin-bottom: 4px;color: var(--text-soft);font-size: 13px;\">Assign Existing Nodes</label>\n            <div id=\"new-rack-assign-nodes\" style=\"max-height: 200px; overflow-y: auto; margin-bottom: 15px;\"></div>\n          </div>\n          <div>\n            <label style=\"display: block;margin-bottom: 4px;color: var(--text-soft);font-size: 13px;\">Notes</label>\n            <textarea id=\"new-rack-notes\" rows=\"3\" placeholder=\"Add notes...\" style=\"width: 100%;padding: 10px;background: var(--panel);border: 1px solid var(--edge-main);border-radius: 4px;color: var(--text-main);font-size: clamp(14px, 1.6vw, 18px);resize: vertical;font-family: inherit;\"></textarea>\n          </div>\n        </details>\n        <div class=\"modal-buttons\">\n          <button class=\"btn-cancel\" id=\"add-rack-cancel\" data-lang=\"ui.buttons.cancel\">Cancel</button>\n          <button class=\"btn-save\" id=\"add-rack-save\" data-lang=\"ui.buttons.addRack\">Add Rack</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal confirm-modal\" id=\"clear-all-modal\">\n      <div class=\"modal-content\">\n        <h3 data-lang=\"ui.modals.clearAllNodes\">Clear All Nodes</h3>\n        <p data-lang=\"messages.clearAllWarning\"> This will remove ALL nodes and connections. This cannot be undone until you reload without saving. Are you sure? </p>\n        <div class=\"modal-buttons\">\n          <button class=\"btn-cancel\" id=\"clear-all-cancel\" data-lang=\"ui.buttons.cancel\">Cancel</button>\n          <button class=\"btn-delete\" id=\"clear-all-confirm\" data-lang=\"messages.clearEverything\">Clear Everything</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"layer-modal\">\n      <div class=\"modal-content\" style=\"max-width: 400px;\">\n        <h3 data-lang=\"ui.modals.layerVisibility\">Layer Visibility</h3>\n        <p style=\"font-size: 13px; color: var(--text-soft); margin-bottom: 15px;\" data-lang=\"messages.layerToggleNote\">Toggle which layers are visible on the canvas</p>\n        <div id=\"layer-toggles-container\" style=\"display: flex; flex-direction: column; gap: 10px;\">\n          <div style=\"display: flex; align-items: center; gap: 8px; padding: 8px; background: var(--panel-alt); border-radius: 6px;\">\n          <label class=\"toggle-switch\"><input type=\"checkbox\" id=\"layer-1\" checked><span class=\"toggle-slider\"></span></label>\n          <span id=\"layer-1-label\" style=\"flex: 1; font-weight: 500;\">Physical Layer</span>\n          </div>\n          <div style=\"display: flex; align-items: center; gap: 8px; padding: 8px; background: var(--panel-alt); border-radius: 6px;\">\n          <label class=\"toggle-switch\"><input type=\"checkbox\" id=\"layer-2\" checked><span class=\"toggle-slider\"></span></label>\n          <span id=\"layer-2-label\" style=\"flex: 1; font-weight: 500;\">Logical Layer</span>\n          </div>\n          <div style=\"display: flex; align-items: center; gap: 8px; padding: 8px; background: var(--panel-alt); border-radius: 6px;\">\n          <label class=\"toggle-switch\"><input type=\"checkbox\" id=\"layer-3\" checked><span class=\"toggle-slider\"></span></label>\n          <span id=\"layer-3-label\" style=\"flex: 1; font-weight: 500;\">Security Layer</span>\n          </div>\n          <div style=\"display: flex; align-items: center; gap: 8px; padding: 8px; background: var(--panel-alt); border-radius: 6px;\">\n          <label class=\"toggle-switch\"><input type=\"checkbox\" id=\"layer-4\" checked><span class=\"toggle-slider\"></span></label>\n          <span id=\"layer-4-label\" style=\"flex: 1; font-weight: 500;\">Application Layer</span>\n          </div>\n        </div>\n        <div style=\"margin-top: 15px; display: flex; gap: 8px;\">\n          <button class=\"btn-cancel\" style=\"flex: 1;\" onclick=\"document.querySelectorAll('#layer-modal input[type=checkbox]').forEach(cb =&gt; cb.checked = false); applyLayerFilter();\" data-lang=\"ui.buttons.hideAll\">Hide All</button>\n          <button class=\"btn-save\" style=\"flex: 1;\" onclick=\"document.querySelectorAll('#layer-modal input[type=checkbox]').forEach(cb =&gt; cb.checked = true); applyLayerFilter();\" data-lang=\"ui.buttons.showAll\">Show All</button>\n        </div>\n        <div class=\"modal-buttons\" style=\"margin-top: 10px;\">\n          <button class=\"btn-cancel\" id=\"layer-modal-close\" data-lang=\"ui.buttons.close\">Close</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"tabs-modal\">\n      <div class=\"modal-content\" style=\"max-width: 600px;\">\n        <h3 data-lang=\"ui.modals.tabs\">Tabs</h3>\n        <p style=\"font-size: 13px; color: var(--text-soft); margin-bottom: 15px;\" data-lang=\"messages.manageTopologies\">Manage multiple topologies</p>\n        <div id=\"tabs-list\" style=\"display: flex; flex-direction: column; gap: 8px; max-height: 400px; overflow-y: auto; margin-bottom: 15px;\">\n            <div class=\"tab-item active\" onclick=\"switchTab(0)\">\n              <div class=\"tab-name\" data-lang=\"tabsModal.mainTopology\">Main Topology</div>\n              <div class=\"tab-stats\" data-lang=\"ui.stats.nodesConnections\">0 nodes • 0 connections</div>\n              <div class=\"tab-actions\">\n                <button class=\"btn-cancel\" onclick=\"event.stopPropagation(); renameTab(0)\" data-lang=\"tooltips.renameTab\" data-lang-attr=\"title\">✏️</button>\n                \n              </div>\n            </div>\n          </div>\n        <div style=\"display: flex; gap: 8px; margin-bottom: 10px;\">\n          <input type=\"text\" id=\"new-tab-name\" placeholder=\"New tab name...\" style=\"flex: 1; padding: 8px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 6px; color: var(--text-main);\" data-lang=\"ui.placeholders.newTabName\" data-lang-attr=\"placeholder\">\n          <button class=\"btn-save\" onclick=\"createNewTab()\" data-lang=\"ui.buttons.addTab\">+ Add Tab</button>\n        </div>\n        <div class=\"modal-buttons\">\n          <button class=\"btn-cancel\" id=\"tabs-modal-close\" data-lang=\"ui.buttons.close\">Close</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"rollback-modal\">\n      <div class=\"modal-content\" style=\"max-width: 700px;\">\n        <h3 data-lang=\"ui.modals.versionHistory\">Version History</h3>\n        <p style=\"font-size: 13px; color: var(--text-soft); margin-bottom: 15px;\" data-lang=\"messages.snapshotLimit\">Limit: Snapshots</p>\n        <div id=\"rollback-list\" style=\"display: flex; flex-direction: column; gap: 8px; max-height: 450px; overflow-y: auto; margin-bottom: 15px;\"></div>\n        <div style=\"display: flex; gap: 8px; margin-bottom: 10px;\">\n          <button class=\"btn-cancel\" onclick=\"clearRollbackHistory()\" data-lang=\"ui.buttons.clearHistory\">Clear History</button>\n          <button class=\"btn-save\" onclick=\"createManualSnapshot()\" data-lang=\"ui.buttons.createSnapshot\">Create Snapshot</button>\n        </div>\n        <div class=\"modal-buttons\">\n          <button class=\"btn-cancel\" id=\"rollback-modal-close\" data-lang=\"ui.buttons.close\">Close</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"audit-log-modal\">\n      <div class=\"modal-content\" style=\"max-width: 800px;\">\n        <h3 data-lang=\"ui.modals.auditLog\">Audit Log</h3>\n        <p style=\"font-size: 13px; color: var(--text-soft); margin-bottom: 15px;\" data-lang=\"messages.trackChanges\">Track all changes made to your topology</p>\n        <div style=\"display: flex; gap: 8px; margin-bottom: 10px;\">\n          <select id=\"audit-filter\" style=\"padding: 8px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 6px; color: var(--text-main);\">\n            <option value=\"all\" data-lang=\"auditLog.allEvents\">All Events</option>\n            <option value=\"node\" data-lang=\"auditLog.nodeOperations\">Node Operations</option>\n            <option value=\"connection\" data-lang=\"auditLog.connections\">Connections</option>\n            <option value=\"style\" data-lang=\"auditLog.styleChanges\">Style Changes</option>\n            <option value=\"rack\" data-lang=\"auditLog.rackOperations\">Rack Operations</option>\n            <option value=\"layer\" data-lang=\"auditLog.layerChanges\">Layer Changes</option>\n          </select>\n          <button class=\"btn-save\" onclick=\"exportAuditLog()\" data-lang=\"ui.buttons.export\">Export</button>\n        </div>\n        <div id=\"audit-log-list\" style=\"display: flex; flex-direction: column; gap: 4px; max-height: 450px; overflow-y: auto; margin-bottom: 15px; font-family: monospace; font-size: 12px;\"></div>\n        <div style=\"display: flex; gap: 8px;\">\n          <button class=\"btn-cancel\" onclick=\"clearAuditLog()\" data-lang=\"ui.buttons.clearLog\">Clear Log</button>\n          <button class=\"btn-cancel\" id=\"audit-log-modal-close\" data-lang=\"ui.buttons.close\">Close</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"port-map-modal\">\n      <div class=\"modal-content\" style=\"max-width: 900px;\">\n        <h3 data-lang=\"ui.modals.portMap\">Port Map</h3>\n        <p style=\"font-size: 13px; color: var(--text-soft); margin-bottom: 15px;\" data-lang=\"messages.portMapDesc\">All connections with port assignments</p>\n        <div style=\"display: flex; gap: 8px; margin-bottom: 10px;\">\n          <input type=\"text\" id=\"port-map-search\" placeholder=\"Search devices or ports...\" style=\"flex: 1; min-width: 200px; padding: 8px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 6px; color: var(--text-main);\" data-lang=\"portMap.searchPlaceholder\" data-lang-attr=\"placeholder\">\n          <select id=\"port-map-filter\" style=\"padding: 8px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 6px; color: var(--text-main);\">\n            <option value=\"all\" data-lang=\"portMap.allConnections\">All Connections</option>\n            <option value=\"with-ports\" data-lang=\"portMap.withPorts\">With Ports Only</option>\n            <option value=\"without-ports\" data-lang=\"portMap.missingPorts\">Missing Ports</option>\n          </select>\n        </div>\n        <div id=\"port-map-table\" style=\"max-height: 450px; overflow-y: auto; margin-bottom: 15px;\"><div style=\"text-align: center; padding: 40px; color: var(--text-soft);\" data-lang=\"portMap.noConnections\">No connections found</div></div>\n        <div style=\"display: flex; gap: 8px;\">\n          <button class=\"btn-save\" onclick=\"exportPortMap()\" data-lang=\"ui.buttons.exportCsv\">Export CSV</button>\n          <button class=\"btn-cancel\" id=\"port-map-modal-close\" data-lang=\"ui.buttons.close\">Close</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"secrets-modal\">\n      <div class=\"modal-content\" style=\"max-width: 700px;\">\n        <h3 data-lang=\"ui.modals.notesHub\">Notes Hub</h3>\n        <div style=\"display: flex; gap: 8px; margin-bottom: 12px;\">\n          <input type=\"text\" id=\"notes-hub-search\" placeholder=\"Search notes...\" style=\"flex: 1; min-width: 0; padding: 8px 12px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 6px; color: var(--text-main); font-size: 14px;\">\n          <select id=\"notes-hub-filter\" style=\"flex-shrink: 0; width: auto; padding: 8px 12px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 6px; color: var(--text-main); font-size: 14px;\">\n            <option value=\"all\">All Notes</option>\n            <option value=\"global\">Global Notes</option>\n            <option value=\"node\">Node Notes</option>\n            <option value=\"edge\">Line Notes</option>\n            <option value=\"rect\">Zone Notes</option>\n            <option value=\"image\">Image Notes</option>\n          </select>\n        </div>\n        <div id=\"secrets-list\" style=\"display: flex; flex-direction: column; gap: 8px; max-height: 400px; overflow-y: auto; margin-bottom: 15px;\"><div style=\"text-align: center; padding: 40px; color: var(--text-soft);\" data-lang=\"notes.noNotes\">No notes yet</div></div>\n        <div style=\"border-top: 1px solid var(--edge-main); padding-top: 12px; margin-top: 8px;\">\n          <p style=\"font-size: 12px; color: var(--text-soft); margin-bottom: 10px;\" data-lang=\"messages.notesEncryptionNote\">Global notes can be stored with AES 256 encryption</p>\n          <div style=\"display: flex; gap: 8px;\">\n            <input type=\"text\" id=\"new-secret-name\" placeholder=\"New global note name...\" style=\"flex: 1; padding: 8px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 6px; color: var(--text-main);\" data-lang=\"notes.namePlaceholder\" data-lang-attr=\"placeholder\">\n            <button class=\"btn-save\" onclick=\"createNewSecret()\" data-lang=\"ui.buttons.addNote\">+ Add Note</button>\n          </div>\n        </div>\n        <div class=\"modal-buttons\">\n          <button class=\"btn-cancel\" id=\"secrets-modal-close\" data-lang=\"ui.buttons.close\">Close</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"secret-editor-modal\">\n      <div class=\"modal-content\" style=\"max-width: 600px;\">\n        <h3 id=\"secret-editor-title\" data-lang=\"notes.editNote\">Edit Note</h3>\n        <div class=\"note-editor-toolbar\" id=\"secret-editor-toolbar\">\n          <button type=\"button\" data-command=\"bold\" title=\"Bold\"><b>B</b></button>\n          <button type=\"button\" data-command=\"italic\" title=\"Italic\"><i>I</i></button>\n          <button type=\"button\" data-command=\"underline\" title=\"Underline\"><u>U</u></button>\n          <span class=\"toolbar-separator\"></span>\n          <select id=\"secret-heading-select\" title=\"Heading\">\n            <option value=\"\">Normal</option>\n            <option value=\"h1\">H1</option>\n            <option value=\"h2\">H2</option>\n            <option value=\"h3\">H3</option>\n          </select>\n          <span class=\"toolbar-separator\"></span>\n          <button type=\"button\" data-command=\"insertUnorderedList\" title=\"Bullet List\">•</button>\n          <button type=\"button\" data-command=\"insertOrderedList\" title=\"Numbered List\">1.</button>\n          <span class=\"toolbar-separator\"></span>\n          <button type=\"button\" data-command=\"code\" title=\"Inline Code\">&lt;/&gt;</button>\n          <button type=\"button\" data-command=\"codeblock\" title=\"Code Block\">[ ]</button>\n          <button type=\"button\" data-command=\"createLink\" title=\"Link\">🔗</button>\n          <button type=\"button\" data-command=\"blockquote\" title=\"Quote\">\"</button>\n          <span class=\"toolbar-separator\"></span>\n          <button type=\"button\" data-command=\"removeFormat\" title=\"Clear Formatting\">✖</button>\n        </div>\n        <div class=\"note-editor-content\" id=\"secret-editor-content\" contenteditable=\"true\" style=\"height: 200px;\"></div>\n        <div style=\"margin: 15px 0; display: flex; align-items: center; gap: 8px;\">\n          <label class=\"toggle-switch\"><input type=\"checkbox\" id=\"secret-auto-encrypt\" checked><span class=\"toggle-slider\"></span></label>\n          <span data-lang=\"notes.encryptNote\">Encrypt Note</span>\n        </div>\n        <div class=\"modal-buttons\">\n          <button class=\"btn-cancel\" onclick=\"closeSecretEditor()\" data-lang=\"ui.buttons.cancel\">Cancel</button>\n          <button class=\"btn-save\" onclick=\"saveSecret()\" data-lang=\"ui.buttons.save\">Save</button>\n        </div>\n      </div>\n    </div>\n\t\n\t<div class=\"modal\" id=\"mobile-export-modal\">\n      <div class=\"modal-content\" style=\"max-width: 320px;\">\n        <h3 style=\"margin: 0 0 20px 0;\" data-lang=\"ui.modals.export\">Export</h3>\n        <div style=\"display: flex; flex-direction: column; gap: 12px;\">\n          <button onclick=\"screenshotCanvas(); document.getElementById('mobile-export-modal').classList.remove('active');\" style=\"padding: 14px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; color: var(--text-main); font-size: 16px; cursor: pointer;\" data-lang=\"mobileExport.pngImage\">PNG Image</button>\n          <button onclick=\"exportCanvasSVG(); document.getElementById('mobile-export-modal').classList.remove('active');\" style=\"padding: 14px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; color: var(--text-main); font-size: 16px; cursor: pointer;\" data-lang=\"mobileExport.svgVector\">SVG Vector</button>\n          <button onclick=\"exportJSONFile(); document.getElementById('mobile-export-modal').classList.remove('active');\" style=\"padding: 14px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; color: var(--text-main); font-size: 16px; cursor: pointer;\" data-lang=\"mobileExport.jsonBackup\">JSON (Full Backup)</button>\n          <button onclick=\"exportMarkdown(); document.getElementById('mobile-export-modal').classList.remove('active');\" style=\"padding: 14px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; color: var(--text-main); font-size: 16px; cursor: pointer;\" data-lang=\"mobileExport.markdown\">Markdown</button>\n          <button onclick=\"exportCSV(); document.getElementById('mobile-export-modal').classList.remove('active');\" style=\"padding: 14px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; color: var(--text-main); font-size: 16px; cursor: pointer;\" data-lang=\"mobileExport.csv\">CSV</button>\n          <button onclick=\"printTopology(); document.getElementById('mobile-export-modal').classList.remove('active');\" style=\"padding: 14px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; color: var(--text-main); font-size: 16px; cursor: pointer;\" data-lang=\"mobileExport.printBW\">Print B&W</button>\n          <button onclick=\"printTopologyColor(); document.getElementById('mobile-export-modal').classList.remove('active');\" style=\"padding: 14px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; color: var(--text-main); font-size: 16px; cursor: pointer;\" data-lang=\"mobileExport.printColorFull\">Full Color</button>\n          <button onclick=\"printTopologyColor('#ffffff'); document.getElementById('mobile-export-modal').classList.remove('active');\" style=\"padding: 14px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; color: var(--text-main); font-size: 16px; cursor: pointer;\" data-lang=\"mobileExport.printColorWhite\">White Background</button>\n          <button onclick=\"printTopologyColor('#000000'); document.getElementById('mobile-export-modal').classList.remove('active');\" style=\"padding: 14px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; color: var(--text-main); font-size: 16px; cursor: pointer;\" data-lang=\"mobileExport.printColorBlack\">Black Background</button>\n\t\t  <button onclick=\"showFormatHelp(); document.getElementById('mobile-import-modal').classList.remove('active');\" style=\"padding: 14px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; color: var(--text-main); font-size: 16px; cursor: pointer;\" data-lang=\"mobileExport.exportHelp\">Export Help</button>\n        </div>\n        <button onclick=\"document.getElementById('mobile-export-modal').classList.remove('active');\" style=\"margin-top: 20px; padding: 12px; background: var(--danger); border: none; border-radius: 8px; color: white; font-size: 16px; cursor: pointer; width: 100%;\" data-lang=\"ui.buttons.cancel\">Cancel</button>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"mobile-import-modal\">\n      <div class=\"modal-content\" style=\"max-width: 320px;\">\n        <h3 style=\"margin: 0 0 20px 0;\" data-lang=\"ui.modals.import\">Import</h3>\n        <div style=\"display: flex; flex-direction: column; gap: 12px;\">\n          <button onclick=\"document.getElementById('import-html-file').click(); document.getElementById('mobile-import-modal').classList.remove('active');\" style=\"padding: 14px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; color: var(--text-main); font-size: 16px; cursor: pointer;\" data-lang=\"mobileImport.html\">HTML</button>\n          <button onclick=\"document.getElementById('import-json-file').click(); document.getElementById('mobile-import-modal').classList.remove('active');\" style=\"padding: 14px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; color: var(--text-main); font-size: 16px; cursor: pointer;\" data-lang=\"mobileImport.json\">JSON</button>\n          <button onclick=\"document.getElementById('import-markdown-file').click(); document.getElementById('mobile-import-modal').classList.remove('active');\" style=\"padding: 14px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; color: var(--text-main); font-size: 16px; cursor: pointer;\" data-lang=\"mobileImport.markdown\">Markdown</button>\n          <button onclick=\"document.getElementById('import-csv-file').click(); document.getElementById('mobile-import-modal').classList.remove('active');\" style=\"padding: 14px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; color: var(--text-main); font-size: 16px; cursor: pointer;\" data-lang=\"mobileImport.csv\">CSV</button>\n          <button onclick=\"showFormatHelp(); document.getElementById('mobile-import-modal').classList.remove('active');\" style=\"padding: 14px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; color: var(--text-main); font-size: 16px; cursor: pointer;\" data-lang=\"mobileImport.importHelp\">Import Help</button>\n        </div>\n        <button onclick=\"document.getElementById('mobile-import-modal').classList.remove('active');\" style=\"margin-top: 20px; padding: 12px; background: var(--danger); border: none; border-radius: 8px; color: white; font-size: 16px; cursor: pointer; width: 100%;\" data-lang=\"ui.buttons.cancel\">Cancel</button>\n      </div>\n    </div>\n    <div id=\"context-menu\" style=\"display: none !important;\">\n    </div>\n    <div class=\"modal\" id=\"welcome-modal\">\n      <div class=\"modal-content\" style=\"max-width: 700px; max-height: 90vh; overflow-y: auto;\">\n        <h2 style=\"margin-bottom: 8px; text-align: center;\" data-lang=\"welcome.title\">Welcome to The One File</h2>\n        <p style=\"font-size: 14px; color: var(--text-soft); margin-bottom: 16px; text-align: center;\" data-lang=\"welcome.subtitle\">What are you mapping today?</p>\n        <div id=\"welcome-mode-buttons\" style=\"display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-bottom: 20px;\">\n          <button class=\"welcome-mode-btn\" data-mode=\"network\" style=\"padding: 12px 16px; background: var(--panel-alt); border: 2px solid var(--accent); border-radius: 8px; color: var(--text-main); font-size: 14px; cursor: pointer; text-align: left; transition: all 0.2s;\">\n            <strong data-lang=\"welcome.modes.network\">Network Topology</strong>\n            <span style=\"display: block; font-size: 11px; color: var(--text-soft); margin-top: 2px;\" data-lang=\"welcome.modes.networkDesc\">Servers, routers, switches</span>\n          </button>\n          <button class=\"welcome-mode-btn\" data-mode=\"mindmap\" style=\"padding: 12px 16px; background: var(--panel-alt); border: 2px solid var(--edge-main); border-radius: 8px; color: var(--text-main); font-size: 14px; cursor: pointer; text-align: left; transition: all 0.2s;\">\n            <strong data-lang=\"welcome.modes.mindmap\">Mind Map</strong>\n            <span style=\"display: block; font-size: 11px; color: var(--text-soft); margin-top: 2px;\" data-lang=\"welcome.modes.mindmapDesc\">Ideas, concepts, brainstorming</span>\n          </button>\n          <button class=\"welcome-mode-btn\" data-mode=\"sports\" style=\"padding: 12px 16px; background: var(--panel-alt); border: 2px solid var(--edge-main); border-radius: 8px; color: var(--text-main); font-size: 14px; cursor: pointer; text-align: left; transition: all 0.2s;\">\n            <strong data-lang=\"welcome.modes.sports\">Sports Playbook</strong>\n            <span style=\"display: block; font-size: 11px; color: var(--text-soft); margin-top: 2px;\" data-lang=\"welcome.modes.sportsDesc\">Plays, formations, positions</span>\n          </button>\n          <button class=\"welcome-mode-btn\" data-mode=\"smarthome\" style=\"padding: 12px 16px; background: var(--panel-alt); border: 2px solid var(--edge-main); border-radius: 8px; color: var(--text-main); font-size: 14px; cursor: pointer; text-align: left; transition: all 0.2s;\">\n            <strong data-lang=\"welcome.modes.smarthome\">Smart Home</strong>\n            <span style=\"display: block; font-size: 11px; color: var(--text-soft); margin-top: 2px;\" data-lang=\"welcome.modes.smarthomeDesc\">IoT devices, automation</span>\n          </button>\n          <button class=\"welcome-mode-btn\" data-mode=\"floorplan\" style=\"padding: 12px 16px; background: var(--panel-alt); border: 2px solid var(--edge-main); border-radius: 8px; color: var(--text-main); font-size: 14px; cursor: pointer; text-align: left; transition: all 0.2s; grid-column: span 2;\">\n            <strong data-lang=\"welcome.modes.floorplan\">Floor Plan / Blueprint</strong>\n            <span style=\"display: block; font-size: 11px; color: var(--text-soft); margin-top: 2px;\" data-lang=\"welcome.modes.floorplanDesc\">Rooms, layouts, physical spaces</span>\n          </button>\n        </div>\n        <div id=\"welcome-canvas-row\" style=\"display: flex; align-items: center; gap: 10px; margin-bottom: 8px;\">\n          <label style=\"font-size: 13px; color: var(--text-main); min-width: 80px;\" data-lang=\"welcome.canvasStyle\">Canvas Style</label>\n          <select id=\"welcome-canvas-select\" style=\"flex: 1; padding: 6px 10px; background: var(--input-bg); color: var(--input-text); border: 1px solid var(--input-border); border-radius: 4px;\">\n            <option value=\"grid\" data-lang=\"welcome.canvas.grid\">Standard Grid</option>\n            <option value=\"dots\" data-lang=\"welcome.canvas.dots\">Dot Grid</option>\n            <option value=\"blueprint\" data-lang=\"welcome.canvas.blueprint\">Blueprint Grid</option>\n            <option value=\"none\" data-lang=\"welcome.canvas.none\">No Grid</option>\n          </select>\n        </div>\n        <div id=\"welcome-canvas-preview\" style=\"height: 60px; margin-bottom: 12px; border: 1px solid var(--edge-main); border-radius: 4px; background: var(--bg); overflow: hidden;\"></div>\n        <div style=\"display: flex; align-items: center; gap: 10px; margin-bottom: 12px;\">\n          <label style=\"font-size: 13px; color: var(--text-main); min-width: 80px;\" data-lang=\"welcome.theme\">Theme</label>\n          <select id=\"welcome-theme-select\" style=\"flex: 1; padding: 6px 10px; background: var(--input-bg); color: var(--input-text); border: 1px solid var(--input-border); border-radius: 4px;\">\n            <option value=\"defaulted\" selected data-lang=\"themes.default\">Default</option>\n            <option value=\"\" data-lang=\"ui.options.customColors\">Custom Colors</option>\n            <optgroup label=\"Corporate\" data-lang-label=\"themeGroups.corporate\">\n              <option value=\"slate\" data-lang=\"themes.slate\">Slate</option>\n              <option value=\"graphite\" data-lang=\"themes.graphite\">Graphite</option>\n              <option value=\"frost\" data-lang=\"themes.frost\">Frost (Light)</option>\n            </optgroup>\n            <optgroup label=\"Homelab\" data-lang-label=\"themeGroups.homelab\">\n              <option value=\"synthwave\" data-lang=\"themes.synthwave\">Synthwave</option>\n              <option value=\"terminal\" data-lang=\"themes.terminal\">Terminal</option>\n            </optgroup>\n            <optgroup label=\"Dev\" data-lang-label=\"themeGroups.dev\">\n              <option value=\"dracula\" data-lang=\"themes.dracula\">Dracula</option>\n              <option value=\"cobalt\" data-lang=\"themes.cobalt\">Cobalt</option>\n              <option value=\"solarized\" data-lang=\"themes.solarized\">Solarized</option>\n            </optgroup>\n            <optgroup label=\"Mind Map\" data-lang-label=\"themeGroups.mindMap\">\n              <option value=\"brainwave\" data-lang=\"themes.brainwave\">Brainwave</option>\n                            <option value=\"neonMint\" data-lang=\"themes.neonMint\">Neon Mint</option>\n              <option value=\"velvetDusk\" data-lang=\"themes.velvetDusk\">Velvet Dusk</option>\n              <option value=\"sunburst\" data-lang=\"themes.sunburst\">Sunburst</option>\n            </optgroup>\n          </select>\n        </div>\n        <details style=\"margin-bottom: 12px; border: 1px solid var(--edge-main); border-radius: 6px; padding: 8px;\">\n          <summary style=\"cursor: pointer; font-size: 13px; color: var(--text-main); font-weight: 600;\" data-lang=\"welcome.colorCustomization\">Color Customization</summary>\n          <div style=\"display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-top: 10px;\">\n            <div style=\"display: flex; align-items: center; gap: 6px;\">\n              <input type=\"color\" id=\"welcome-bg-color\" value=\"#0b0e13\" style=\"width: 32px; height: 24px; border: 1px solid var(--input-border); border-radius: 3px; cursor: pointer;\">\n              <label style=\"font-size: 11px; color: var(--text-soft);\" data-lang=\"welcome.colors.mainBg\">Top Bar Background</label>\n            </div>\n            <div style=\"display: flex; align-items: center; gap: 6px;\">\n              <input type=\"color\" id=\"welcome-accent-color\" value=\"#4fd1c5\" style=\"width: 32px; height: 24px; border: 1px solid var(--input-border); border-radius: 3px; cursor: pointer;\">\n              <label style=\"font-size: 11px; color: var(--text-soft);\" data-lang=\"welcome.colors.accent\">Accent</label>\n            </div>\n            <div style=\"display: flex; align-items: center; gap: 6px;\">\n              <input type=\"color\" id=\"welcome-text-color\" value=\"#e2e8f0\" style=\"width: 32px; height: 24px; border: 1px solid var(--input-border); border-radius: 3px; cursor: pointer;\">\n              <label style=\"font-size: 11px; color: var(--text-soft);\" data-lang=\"welcome.colors.primaryText\">Primary Text</label>\n            </div>\n            <div style=\"display: flex; align-items: center; gap: 6px;\">\n              <input type=\"color\" id=\"welcome-text-soft-color\" value=\"#94a3b8\" style=\"width: 32px; height: 24px; border: 1px solid var(--input-border); border-radius: 3px; cursor: pointer;\">\n              <label style=\"font-size: 11px; color: var(--text-soft);\" data-lang=\"welcome.colors.secondaryText\">Secondary Text</label>\n            </div>\n            <div style=\"display: flex; align-items: center; gap: 6px;\">\n              <input type=\"color\" id=\"welcome-panel-color\" value=\"#0f1318\" style=\"width: 32px; height: 24px; border: 1px solid var(--input-border); border-radius: 3px; cursor: pointer;\">\n              <label style=\"font-size: 11px; color: var(--text-soft);\" data-lang=\"welcome.colors.sidebarPanel\">Main Background</label>\n            </div>\n            <div style=\"display: flex; align-items: center; gap: 6px;\">\n              <input type=\"color\" id=\"welcome-modal-color\" value=\"#141921\" style=\"width: 32px; height: 24px; border: 1px solid var(--input-border); border-radius: 3px; cursor: pointer;\">\n              <label style=\"font-size: 11px; color: var(--text-soft);\" data-lang=\"welcome.colors.modalWindows\">Modal Background</label>\n            </div>\n            <div style=\"display: flex; align-items: center; gap: 6px;\">\n              <input type=\"color\" id=\"welcome-danger-color\" value=\"#f56565\" style=\"width: 32px; height: 24px; border: 1px solid var(--input-border); border-radius: 3px; cursor: pointer;\">\n              <label style=\"font-size: 11px; color: var(--text-soft);\" data-lang=\"welcome.colors.dangerButtons\">Grid Color</label>\n            </div>\n            <div style=\"display: flex; align-items: center; gap: 6px;\">\n              <input type=\"color\" id=\"welcome-mobile-footer-color\" value=\"#0b0e13\" style=\"width: 32px; height: 24px; border: 1px solid var(--input-border); border-radius: 3px; cursor: pointer;\">\n              <label style=\"font-size: 11px; color: var(--text-soft);\" data-lang=\"welcome.colors.mobileFooter\">Sidebar Background</label>\n            </div>\n          </div>\n\t\t  <div style=\"text-align:center\">\n            <label style=\"font-size: 11px; color: var(--text-soft);\" data-lang=\"welcome.colors.moreStyles\">More styles available in main settings panel</label>\n          </div>\n        </details>\n        <div style=\"display: flex; gap: 10px; justify-content: center;\">\n          <button id=\"welcome-start-btn\" style=\"padding: 10px 24px; background: var(--accent); color: var(--bg); border: none; border-radius: 6px; font-size: 14px; font-weight: 600; cursor: pointer;\" data-lang=\"welcome.startMapping\">Start Mapping</button>\n          <button id=\"welcome-skip-btn\" style=\"padding: 10px 24px; background: transparent; border: 1px solid var(--edge-main); border-radius: 6px; color: var(--text-soft); font-size: 14px; cursor: pointer;\" data-lang=\"welcome.skip\">Skip</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"recordings-modal\">\n      <div class=\"modal-content\" style=\"max-width: 600px;\">\n        <h3 data-lang=\"ui.modals.recordings\">Recordings</h3>\n        <p style=\"font-size: 13px; color: var(--text-soft); margin-bottom: 15px;\" data-lang=\"messages.manageRecordings\">Manage recorded movement sequences</p>\n        <div id=\"recordings-list\" style=\"display: flex; flex-direction: column; gap: 8px; max-height: 250px; overflow-y: auto; margin-bottom: 15px;\"><div style=\"text-align: center; padding: 40px; color: var(--text-soft);\" data-lang=\"recordings.noRecordings\">No recordings yet. Press Record to start.</div></div>\n        <div style=\"display: flex; gap: 8px; flex-wrap: wrap; justify-content: flex-start; margin-bottom: 15px;\">\n          <button class=\"btn-save\" onclick=\"exportRecordingVideo()\" style=\"flex:0 0 auto;\" data-lang=\"recordings.exportVideo\">Export Video</button>\n          <button class=\"btn-cancel\" onclick=\"deleteSelectedRecording()\" style=\"background:var(--danger);color:white;flex:0 0 auto;\" data-lang=\"ui.buttons.delete\">Delete</button>\n        </div>\n        <details style=\"background: var(--panel-alt); border-radius: 6px; padding: 10px; margin-bottom: 15px;\">\n          <summary style=\"color: var(--accent); font-weight: 600; cursor: pointer;\" data-lang=\"trails.title\">Motion Trails</summary>\n          <div style=\"display: flex; flex-direction: column; gap: 10px; margin-top: 12px;\">\n            <div style=\"display: flex; align-items: center; justify-content: space-between;\">\n              <label style=\"color: var(--text-main); font-size: 14px;\" data-lang=\"trails.enableTrails\">Enable Trails:</label>\n              <label class=\"toggle-switch\"><input type=\"checkbox\" id=\"trail-enabled-toggle\" checked><span class=\"toggle-slider\"></span></label>\n            </div>\n            <div style=\"display: flex; align-items: center; justify-content: space-between;\">\n              <label style=\"color: var(--text-main); font-size: 14px;\" data-lang=\"trails.trailLength\">Trail Length:</label>\n              <div style=\"display: flex; align-items: center; gap: 8px;\">\n                <input type=\"range\" id=\"trail-length-slider\" min=\"2\" max=\"20\" value=\"8\" style=\"width: 100px;\">\n                <span id=\"trail-length-value\" style=\"min-width: 30px; color: var(--text-soft);\">8</span>\n              </div>\n            </div>\n            <div style=\"display: flex; align-items: center; justify-content: space-between;\">\n              <label style=\"color: var(--text-main); font-size: 14px;\" data-lang=\"trails.trailStyle\">Trail Style:</label>\n              <select id=\"trail-style-select\" style=\"padding: 4px 8px; background: var(--panel); color: var(--text-main); border: 1px solid var(--edge-main); border-radius: 4px;\">\n                <option value=\"solid\" data-lang=\"trails.styleSolid\">Solid Fade</option>\n                <option value=\"dashed\" data-lang=\"trails.styleDashed\">Dashed</option>\n                <option value=\"dots\" data-lang=\"trails.styleDots\">Dots</option>\n              </select>\n            </div>\n            <div style=\"display: flex; align-items: center; justify-content: space-between;\">\n              <label style=\"color: var(--text-main); font-size: 14px;\" data-lang=\"trails.showArrow\">Show Direction Arrow:</label>\n              <label class=\"toggle-switch\"><input type=\"checkbox\" id=\"trail-arrow-toggle\" checked><span class=\"toggle-slider\"></span></label>\n            </div>\n            <p style=\"font-size: 12px; color: var(--text-soft); margin: 0;\" data-lang=\"trails.perNodeHint\">Tip: Enable/disable trails per node in the node panel</p>\n          </div>\n        </details>\n        <div style=\"display: flex; gap: 8px; justify-content: flex-end;\">\n          <button class=\"btn-cancel\" id=\"recordings-modal-close\" data-lang=\"ui.buttons.close\">Close</button>\n        </div>\n      </div>\n    </div>\n    <header>\n      <div class=\"title-block\">\n        <h1 id=\"page-title\" class=\"editable-page-title\">The One File</h1>\n        <div class=\"save-row\">\n          <button id=\"save-file-btn\" class=\"save-btn\" type=\"button\" data-lang=\"topbar.saveHtml\">Save HTML</button>\n          <div style=\"display: flex;align-items: center;gap: 4px;font-size: 12px;color: var(--text-soft);user-select: none;\">\n            <label class=\"toggle-switch\"><input type=\"checkbox\" id=\"encrypt-toggle\"><span class=\"toggle-slider\"></span></label>\n            <span data-lang=\"ui.buttons.encrypt\" data-lang-attr=\"title\">🔒</span>\n          </div>\n          <div class=\"dropdown\" id=\"export-dropdown\">\n            <button class=\"dropdown-btn\" type=\"button\" data-lang=\"topbar.export\">Export ▾</button>\n            <div class=\"dropdown-menu\">\n              <button type=\"button\" onclick=\"screenshotCanvas()\" data-lang=\"mobileExport.pngImage\">PNG Image</button>\n              <button type=\"button\" onclick=\"exportCanvasSVG()\" data-lang=\"mobileExport.svgVector\">SVG Vector</button>\n              <div class=\"dropdown-divider\"></div>\n              <button type=\"button\" onclick=\"exportJSONFile()\" data-lang=\"help.formats.json\">JSON</button>\n              <button type=\"button\" onclick=\"exportMarkdown()\" data-lang=\"help.formats.markdown\">Markdown</button>\n              <button type=\"button\" onclick=\"exportCSV()\" data-lang=\"help.formats.csv\">CSV</button>\n              <div class=\"dropdown-divider\"></div>\n              <button type=\"button\" onclick=\"printTopology()\" data-lang=\"ui.buttons.printBW\">Print B&W</button>\n              <button type=\"button\" onclick=\"printTopologyColor()\" data-lang=\"ui.buttons.printColorFull\">Print Full Color</button>\n              <button type=\"button\" onclick=\"printTopologyColor('#ffffff')\" data-lang=\"ui.buttons.printColorWhite\">Print White BG</button>\n              <button type=\"button\" onclick=\"printTopologyColor('#000000')\" data-lang=\"ui.buttons.printColorBlack\">Print Black BG</button>\n\t\t\t  <div class=\"dropdown-divider\"></div>\n\t\t\t  <button type=\"button\" onclick=\"showFormatHelp()\" data-lang=\"ui.buttons.exportHelp\">Export Help</button>\n            </div>\n          </div>\n          <div class=\"dropdown\" id=\"import-dropdown\">\n            <button class=\"dropdown-btn\" type=\"button\" data-lang=\"topbar.import\">Import ▾</button>\n            <div class=\"dropdown-menu\">\n              <button type=\"button\" onclick=\"document.getElementById('import-html-file').click()\" data-lang=\"help.formats.html\">HTML</button>\n              <button type=\"button\" onclick=\"document.getElementById('import-json-file').click()\" data-lang=\"help.formats.json\">JSON</button>\n              <button type=\"button\" onclick=\"document.getElementById('import-markdown-file').click()\" data-lang=\"help.formats.markdown\">Markdown</button>\n              <button type=\"button\" onclick=\"document.getElementById('import-csv-file').click()\" data-lang=\"help.formats.csv\">CSV</button>\n              <div class=\"dropdown-divider\"></div>\n              <button type=\"button\" onclick=\"showFormatHelp()\" data-lang=\"ui.buttons.importHelp\">Import Help</button>\n            </div>\n          </div>\n          <input type=\"file\" id=\"import-html-file\" accept=\".html,.htm\" style=\"display:none\">\n          <input type=\"file\" id=\"import-json-file\" accept=\".json\" style=\"display:none\">\n          <input type=\"file\" id=\"import-markdown-file\" accept=\".md,.markdown,.txt\" style=\"display:none\">\n          <input type=\"file\" id=\"import-csv-file\" accept=\".csv,.txt\" style=\"display:none\">\n          <span id=\"save-help-btn\" class=\"help-icon\" data-lang=\"tooltips.whySave\" data-lang-attr=\"title\">?</span>\n        </div>\n      </div>\n      <div id=\"topbar-menu\">\n        <button id=\"back-to-topology-btn\" data-lang=\"tooltips.exitRackView\" data-lang-attr=\"title\" style=\"padding: 6px 12px;background: var(--accent);color: var(--bg);border: none;border-radius: 6px;cursor: pointer;font-size: 14px;font-weight: 600;display: none;\" data-lang-text=\"topbar.backToTopology\">← Back to Topology</button>\n        <button id=\"add-node-btn\" class=\"save-btn\" data-lang=\"tooltips.addNewNode\" data-lang-attr=\"title\" style=\"background: var(--accent)\" draggable=\"true\" data-lang-text=\"sidebar.addNode\">+ Node</button>\n        <button id=\"add-rack-btn\" class=\"save-btn\" data-lang=\"tooltips.addNewRack\" data-lang-attr=\"title\" style=\"background: var(--accent); margin-left: 8px;\" draggable=\"true\" data-lang-text=\"sidebar.addRack\">+ Rack</button>\n        <button id=\"undo-btn\" data-lang=\"ui.tooltips.undo\" data-lang-attr=\"title\" style=\"padding: 6px 12px; background: var(--btn-bg); color: var(--btn-text); border: 1px solid var(--edge-main); border-radius: 6px; cursor: pointer; font-size: 14px; font-weight: 600; opacity: 0.5;\" disabled=\"\">↶</button>\n        <button id=\"redo-btn\" data-lang=\"ui.tooltips.redo\" data-lang-attr=\"title\" style=\"padding: 6px 12px; background: var(--btn-bg); color: var(--btn-text); border: 1px solid var(--edge-main); border-radius: 6px; cursor: pointer; font-size: 14px; font-weight: 600; opacity: 0.5;\" disabled=\"\">↷</button>\n        <input type=\"text\" id=\"search-nodes\" placeholder=\"Search nodes...\" data-lang=\"tooltips.searchNodes\" data-lang-attr=\"title\" data-lang-placeholder=\"ui.placeholders.search\" style=\"padding: 6px 12px;background: var(--input-bg);color: var(--input-text);border: 1px solid var(--input-border);border-radius: 6px;font-family: var(--input-font);font-size: var(--input-font-size);width: 180px;\">\n        <button id=\"layers-btn\" data-lang=\"ui.tooltips.toggleLayers\" data-lang-attr=\"title\" style=\"padding: 6px 12px;background: var(--btn-bg);color: var(--btn-text);border: 1px solid var(--edge-main);border-radius: 6px;cursor: pointer;font-size: 14px;font-weight: 600;\" data-lang-text=\"ui.buttons.layers\">Layers</button>\n        <button id=\"tabs-btn\" data-lang=\"tooltips.manageTabs\" data-lang-attr=\"title\" style=\"padding: 6px 12px;background: var(--btn-bg);color: var(--btn-text);border: 1px solid var(--edge-main);border-radius: 6px;cursor: pointer;font-size: 14px;font-weight: 600;\" data-lang-text=\"topbar.tabs\">Tabs</button>\n        <button id=\"rollback-btn\" data-lang=\"tooltips.viewVersionHistory\" data-lang-attr=\"title\" style=\"padding: 6px 12px;background: var(--btn-bg);color: var(--btn-text);border: 1px solid var(--edge-main);border-radius: 6px;cursor: pointer;font-size: 14px;font-weight: 600;\" data-lang-text=\"topbar.snapshots\">Snapshots</button>\n        <button id=\"audit-log-btn\" data-lang=\"ui.tooltips.viewAuditLog\" data-lang-attr=\"title\" style=\"padding: 6px 12px;background: var(--btn-bg);color: var(--btn-text);border: 1px solid var(--edge-main);border-radius: 6px;cursor: pointer;font-size: 14px;font-weight: 600;\" data-lang-text=\"ui.buttons.auditLog\">Audit</button>\n        <button id=\"port-map-btn\" data-lang=\"ui.tooltips.viewPortMap\" data-lang-attr=\"title\" style=\"padding: 6px 12px;background: var(--btn-bg);color: var(--btn-text);border: 1px solid var(--edge-main);border-radius: 6px;cursor: pointer;font-size: 14px;font-weight: 600;\" data-lang-text=\"ui.buttons.portMap\">Ports</button>\n        <button id=\"secrets-btn\" data-lang=\"ui.tooltips.manageNotes\" data-lang-attr=\"title\" style=\"padding: 6px 12px;background: var(--btn-bg);color: var(--btn-text);border: 1px solid var(--edge-main);border-radius: 6px;cursor: pointer;font-size: 14px;font-weight: 600;\" data-lang-text=\"ui.buttons.notes\">Notes</button>\n\t\t<div id=\"recording-controls\" style=\"display:inline-flex;align-items:center;gap:4px;margin-left:8px;padding:4px 8px;background:var(--panel-alt);border:1px solid var(--edge-main);border-radius:6px;\">\n          <button id=\"record-btn\" data-lang=\"recording.startRecording\" data-lang-attr=\"title\" style=\"padding:4px 8px;background:var(--btn-bg);color:#f56565;border:1px solid var(--edge-main);border-radius:4px;cursor:pointer;font-size:14px;\">●</button>\n          <button id=\"step-record-btn\" title=\"Step by step recording\" style=\"padding:4px 8px;background:var(--btn-bg);color:#48bb78;border:1px solid var(--edge-main);border-radius:4px;cursor:pointer;font-size:14px;\">●+</button>\n          <button id=\"play-btn\" data-lang=\"recording.playRecording\" data-lang-attr=\"title\" style=\"padding:4px 8px;background:var(--btn-bg);color:var(--btn-text);border:1px solid var(--edge-main);border-radius:4px;cursor:pointer;font-size:14px;\">▶</button>\n          <div id=\"recording-expanded\" style=\"display:none;align-items:center;gap:4px;\">\n            <button id=\"stop-btn\" data-lang=\"recording.stop\" data-lang-attr=\"title\" style=\"padding:4px 8px;background:var(--btn-bg);color:var(--btn-text);border:1px solid var(--edge-main);border-radius:4px;cursor:pointer;font-size:14px;\">■</button>\n            <button id=\"pause-btn\" data-lang=\"recording.pause\" data-lang-attr=\"title\" style=\"padding:4px 8px;background:var(--btn-bg);color:var(--btn-text);border:1px solid var(--edge-main);border-radius:4px;cursor:pointer;font-size:14px;display:none;\">❚❚</button>\n            <span id=\"recording-time\" style=\"font-size:11px;color:var(--text-soft);min-width:40px;text-align:center;\">0:00</span>\n            <input type=\"range\" id=\"playback-scrubber\" min=\"0\" max=\"100\" value=\"0\" style=\"width:80px;cursor:pointer;\" data-lang=\"recording.playbackPosition\" data-lang-attr=\"title\">\n            <select id=\"playback-speed\" data-lang=\"recording.playbackSpeed\" data-lang-attr=\"title\" style=\"padding:2px 4px;background:var(--input-bg);color:var(--input-text);border:1px solid var(--input-border);border-radius:4px;font-size:12px;\">\n              <option value=\"0.5\">0.5x</option>\n              <option value=\"1\" selected>1x</option>\n              <option value=\"2\">2x</option>\n              <option value=\"4\">4x</option>\n            </select>\n            <label class=\"toggle-switch\" data-lang=\"recording.loop\" data-lang-attr=\"title\"><input type=\"checkbox\" id=\"loop-playback\"><span class=\"toggle-slider\"></span></label>\n            <button id=\"recordings-btn\" data-lang=\"recording.manageRecordings\" data-lang-attr=\"title\" style=\"padding:4px 8px;background:var(--btn-bg);color:var(--btn-text);border:1px solid var(--edge-main);border-radius:4px;cursor:pointer;font-size:12px;\">...</button>\n          </div>\n        </div>\n\t\t<button id=\"mobile-export-btn\" data-lang=\"tooltips.exportTopology\" data-lang-attr=\"title\" style=\"padding: 6px 12px;background: var(--btn-bg);color: var(--btn-text);border: 1px solid var(--edge-main);border-radius: 6px;cursor: pointer;font-size: 14px;font-weight: 600;\" data-lang-text=\"topbar.export\">Export</button>\n        <button id=\"mobile-import-btn\" data-lang=\"tooltips.importTopology\" data-lang-attr=\"title\" style=\"padding: 6px 12px;background: var(--btn-bg);color: var(--btn-text);border: 1px solid var(--edge-main);border-radius: 6px;cursor: pointer;font-size: 14px;font-weight: 600;\" data-lang-text=\"topbar.import\">Import</button>\n        <button id=\"settings-btn\" data-lang=\"ui.buttons.settings\" data-lang-attr=\"title\">⚙️</button>\n      </div>\n      <button id=\"mobile-menu-toggle\" class=\"mobile-menu-btn\">☰</button>\n      <div class=\"header-resizer\" id=\"header-resizer\">\n        <svg class=\"resizer-icon\" width=\"40\" height=\"6\" viewBox=\"0 0 40 6\" xmlns=\"http://www.w3.org/2000/svg\">\n          <circle cx=\"14\" cy=\"3\" r=\"2\"></circle>\n          <circle cx=\"20\" cy=\"3\" r=\"2\"></circle>\n          <circle cx=\"26\" cy=\"3\" r=\"2\"></circle>\n        </svg>\n      </div>\n    </header>\n    <main>\n      <section class=\"topology-panel\">\n        <div class=\"draw-toolbar\" id=\"draw-toolbar\" style=\"display: flex !important;\">\n          <button type=\"button\" class=\"toolbar-close-btn\" id=\"draw-toolbar-close-btn\" data-lang=\"tooltips.hideDrawToolbar\" data-lang-attr=\"title\">✕</button>\n          <button id=\"draw-toggle\" data-lang=\"tooltips.drawCustomLine\" data-lang-attr=\"title\">✏️</button>\n          <button id=\"rect-toggle\" data-lang=\"tooltips.drawZone\" data-lang-attr=\"title\">▭</button>\n          <button id=\"text-toggle\" data-lang=\"tooltips.addText\" data-lang-attr=\"title\">T</button>\n          <button id=\"image-toggle\" data-lang=\"tooltips.addImage\" data-lang-attr=\"title\">🖼️</button>\n          <input type=\"file\" id=\"image-file-input\" accept=\"image/*\" style=\"display: none;\">\n          <span style=\"border-left: 1px solid var(--edge-main); height: 20px; margin: 0 4px;\"></span>\n          <select id=\"rect-style\" data-lang=\"tooltips.zoneStyle\" data-lang-attr=\"title\">\n            <option value=\"filled\" data-lang=\"drawToolbar.filled\">Filled</option>\n            <option value=\"outlined\" data-lang=\"drawToolbar.outlined\">Outlined</option>\n          </select>\n          <input type=\"color\" id=\"draw-color\" value=\"#f97316\" data-lang=\"tooltips.lineColor\" data-lang-attr=\"title\">\n          <select id=\"draw-style\" data-lang=\"tooltips.lineStyle\" data-lang-attr=\"title\">\n  <option value=\"solid\" data-lang=\"drawToolbar.solid\">Solid</option>\n  <option value=\"dashed\" data-lang=\"drawToolbar.dashed\">Dashed</option>\n  <option value=\"dotted\" data-lang=\"drawToolbar.dotted\">Dotted</option>\n  <option value=\"wall\" data-lang=\"drawToolbar.wall\">Wall</option>\n</select>\n          <select id=\"draw-arrow\" data-lang=\"tooltips.arrowDirection\" data-lang-attr=\"title\">\n            <option value=\"none\" data-lang=\"drawToolbar.noArrows\">No arrows</option>\n            <option value=\"forward\" data-lang=\"drawToolbar.arrowRight\">→ Right</option>\n            <option value=\"backward\" data-lang=\"drawToolbar.arrowLeft\">← Left</option>\n            <option value=\"both\" data-lang=\"drawToolbar.arrowBoth\">↔ Both</option>\n          </select>\n          <button id=\"draw-undo\" style=\"display: none\" data-lang=\"tooltips.undoLastPoint\" data-lang-attr=\"title\" data-lang-text=\"drawToolbar.undoLastPoint\">Undo</button>\n        </div>\n        <div class=\"topology-toolbar\" id=\"topology-toolbar\" style=\"display: flex !important;\">\n          <button type=\"button\" class=\"toolbar-close-btn\" id=\"topology-toolbar-close-btn\" data-lang=\"tooltips.hideAddLineToolbar\" data-lang-attr=\"title\">✕</button>\n          <label for=\"add-line-select\" data-lang=\"topologyToolbar.addLineTo\">Add line to:</label>\n          <select id=\"add-line-select\"></select>\n          <input type=\"color\" id=\"add-line-color\" value=\"#475569\" data-lang=\"tooltips.lineColor\" data-lang-attr=\"title\" style=\"width: 30px;height: 24px;border: 1px solid var(--toolbar-border);border-radius: 4px;cursor: pointer;background: transparent;padding: 0;\">\n          <select id=\"add-line-direction\" data-lang=\"tooltips.lineDirection\" data-lang-attr=\"title\">\n            <option value=\"none\" data-lang=\"topologyToolbar.noArrows\">No arrows</option>\n            <option value=\"forward\" data-lang=\"topologyToolbar.forward\">→ Forward</option>\n            <option value=\"backward\" data-lang=\"topologyToolbar.backward\">← Backward</option>\n            <option value=\"both\" data-lang=\"topologyToolbar.bidirectional\">↔ Bidirectional</option>\n          </select>\n          <select id=\"add-line-routing\" data-lang=\"tooltips.lineRouting\" data-lang-attr=\"title\">\n            <option value=\"orthogonal\" data-lang=\"ui.options.orthogonal\">Orthogonal</option>\n            <option value=\"curved\" data-lang=\"ui.options.curved\">Curved</option>\n            <option value=\"straight\" data-lang=\"ui.options.straight\">Straight</option>\n          </select>\n          <button id=\"add-line-btn\" data-lang=\"ui.buttons.add\">Add</button>\n        </div>\n        <div class=\"topology-toolbar bulk-toolbar-desktop\" id=\"bulk-toolbar\" style=\"display: none\">\n          <button type=\"button\" class=\"toolbar-close-btn\" id=\"bulk-toolbar-close\" data-lang=\"tooltips.clearSelection\" data-lang-attr=\"title\">✕</button>\n          <label style=\"font-weight: 600; color: var(--accent)\" data-lang=\"bulkToolbar.selected\">Selected: <span id=\"bulk-count\">0</span></label>\n          <button id=\"bulk-align-left\" data-lang=\"tooltips.alignLeft\" data-lang-attr=\"title\" data-lang-text=\"bulkToolbar.alignLeft\">⬅ Left</button>\n          <button id=\"bulk-align-right\" data-lang=\"tooltips.alignRight\" data-lang-attr=\"title\" data-lang-text=\"bulkToolbar.alignRight\">➡ Right</button>\n          <button id=\"bulk-align-top\" data-lang=\"tooltips.alignTop\" data-lang-attr=\"title\" data-lang-text=\"bulkToolbar.alignTop\">⬆ Top</button>\n          <button id=\"bulk-align-bottom\" data-lang=\"tooltips.alignBottom\" data-lang-attr=\"title\" data-lang-text=\"bulkToolbar.alignBottom\">⬇ Bottom</button>\n          <button id=\"bulk-distribute-h\" data-lang=\"tooltips.distributeH\" data-lang-attr=\"title\" data-lang-text=\"bulkToolbar.distributeH\">↔ Distribute H</button>\n          <button id=\"bulk-distribute-v\" data-lang=\"tooltips.distributeV\" data-lang-attr=\"title\" data-lang-text=\"bulkToolbar.distributeV\">↕ Distribute V</button>\n          <button id=\"bulk-clone\" data-lang=\"tooltips.cloneSelected\" data-lang-attr=\"title\" data-lang-text=\"bulkToolbar.clone\">📋 Clone</button>\n          <button id=\"bulk-zone-copy\" data-lang=\"tooltips.copyZone\" data-lang-attr=\"title\" data-lang-text=\"bulkToolbar.copyZone\">📡 Copy Zone</button>\n          <button id=\"bulk-zone-paste\" data-lang=\"tooltips.pasteZone\" data-lang-attr=\"title\" data-lang-text=\"bulkToolbar.pasteZone\">📡 Paste Zone</button>\n          <button id=\"bulk-zone-toggle\" data-lang=\"tooltips.toggleZones\" data-lang-attr=\"title\" data-lang-text=\"bulkToolbar.toggleZones\">📡 Toggle Zones</button>\n          <button id=\"bulk-delete\" data-lang=\"tooltips.deleteSelected\" data-lang-attr=\"title\" style=\"background: var(--danger); color: white;\" data-lang-text=\"ui.buttons.delete\">Delete</button>\n        </div>\n        <div class=\"bulk-toolbar-mobile\" id=\"bulk-toolbar-mobile\" style=\"display: none;\">\n          <button type=\"button\" id=\"bulk-mobile-btn\" style=\"background: var(--accent);color: white;padding: 12px 20px;border-radius: 25px;font-weight: 600;font-size: 16px;box-shadow: 0 4px 12px rgba(0,0,0,0.3);border: none;cursor: pointer;display: flex;align-items: center;gap: 8px;\">\n          <span id=\"bulk-count-mobile\">0</span><span data-lang=\"bulkToolbar.selected\">Selected</span></button>\n        </div>\n        <div id=\"bulk-actions-modal\" style=\"display: none;position: fixed;bottom: 0;left: 0;right: 0;background: var(--panel-alt);border-top-left-radius: 20px;border-top-right-radius: 20px;padding: 20px;padding-bottom: env(safe-area-inset-bottom, 20px);box-shadow: 0 -4px 20px rgba(0,0,0,0.5);z-index: 10000;max-height: calc(100vh - 80px);overflow-y: auto;\">\n          <div style=\"display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;\">\n            <h3 style=\"margin: 0; color: var(--accent); font-size: 20px;\">\n              <span id=\"bulk-count-modal\">0</span> <span data-lang=\"bulkToolbar.nodesSelected\">Nodes Selected</span>\n            </h3>\n            <button id=\"bulk-modal-close\" style=\"background: none;border: none;font-size: 24px;cursor: pointer;color: var(--text-main);padding: 0;width: 32px;height: 32px;\">✕</button>\n          </div>\n          <div style=\"display: grid; grid-template-columns: 1fr 1fr; gap: 12px;\">\n            <button id=\"bulk-align-left-mobile\" class=\"bulk-action-btn\">⬅<br><span style=\"font-size: 12px;\" data-lang=\"bulkToolbar.alignLeftShort\">Align Left</span></button>\n            <button id=\"bulk-align-right-mobile\" class=\"bulk-action-btn\">➡<br><span style=\"font-size: 12px;\" data-lang=\"bulkToolbar.alignRightShort\">Align Right</span></button>\n            <button id=\"bulk-align-top-mobile\" class=\"bulk-action-btn\">⬆<br><span style=\"font-size: 12px;\" data-lang=\"bulkToolbar.alignTopShort\">Align Top</span></button>\n            <button id=\"bulk-align-bottom-mobile\" class=\"bulk-action-btn\">⬇<br><span style=\"font-size: 12px;\" data-lang=\"bulkToolbar.alignBottomShort\">Align Bottom</span></button>\n            <button id=\"bulk-distribute-h-mobile\" class=\"bulk-action-btn\">↔<br><span style=\"font-size: 12px;\" data-lang=\"bulkToolbar.distributeHShort\">Distribute H</span></button>\n            <button id=\"bulk-distribute-v-mobile\" class=\"bulk-action-btn\">↕<br><span style=\"font-size: 12px;\" data-lang=\"bulkToolbar.distributeVShort\">Distribute V</span></button>\n            <button id=\"bulk-lock-mobile\" class=\"bulk-action-btn\">🔒<br><span style=\"font-size: 12px;\" data-lang=\"bulkToolbar.lockToggle\">Lock Toggle</span></button>\n            <button id=\"bulk-group-mobile\" class=\"bulk-action-btn\">⭕<br><span style=\"font-size: 12px;\" data-lang=\"bulkToolbar.groupToggle\">Group Toggle</span></button>\n            <button id=\"bulk-clone-mobile\" class=\"bulk-action-btn\">📋<br><span style=\"font-size: 12px;\" data-lang=\"bulkToolbar.cloneAll\">Clone All</span></button>\n            <button id=\"bulk-delete-mobile\" class=\"bulk-action-btn\" style=\"background: var(--danger); color: white;\">🗑<br><span style=\"font-size: 12px;\" data-lang=\"bulkToolbar.deleteAll\">Delete All</span></button>\n          </div>\n          <div style=\"margin-top: 16px; padding-top: 16px; border-top: 1px solid var(--edge-main);\">\n            <div style=\"font-size: 12px; color: var(--text-soft); margin-bottom: 12px; text-transform: uppercase; letter-spacing: 0.05em;\" data-lang=\"bulkToolbar.coverageZones\">Coverage Zones</div>\n            <div style=\"display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 12px;\">\n              <button id=\"bulk-zone-copy-mobile\" class=\"bulk-action-btn\">📡<br><span style=\"font-size: 12px;\" data-lang=\"bulkToolbar.copyZoneShort\">Copy Zone</span></button>\n              <button id=\"bulk-zone-paste-mobile\" class=\"bulk-action-btn\">📡<br><span style=\"font-size: 12px;\" data-lang=\"bulkToolbar.pasteZoneShort\">Paste Zone</span></button>\n              <button id=\"bulk-zone-toggle-mobile\" class=\"bulk-action-btn\">📡<br><span style=\"font-size: 12px;\" data-lang=\"bulkToolbar.toggleZonesShort\">Toggle Zones</span></button>\n            </div>\n          </div>\n          <button id=\"bulk-clear-mobile\" style=\"width: 100%;margin-top: 16px;padding: 14px;background: var(--panel-main);border: 1px solid var(--edge-main);border-radius: 8px;color: var(--text-main);font-size: 16px;cursor: pointer;\" data-lang=\"bulkToolbar.clearSelection\">Clear Selection</button>\n        </div>\n        <div class=\"canvas-hint visible\" id=\"canvas-hint\">\n          <ul>\n            <li data-lang=\"canvasHints.scrollZoom\">Scroll to zoom</li>\n            <li data-lang=\"canvasHints.dragPan\">Drag to pan</li>\n            <li data-lang=\"canvasHints.rightClickClone\">Right click to clone and align</li>\n            <li data-lang=\"canvasHints.rightClickMulti\">Right click to select multiple</li>\n\t\t\t<li data-lang=\"canvasHints.shiftMarquee\">Hold Shift + drag mouse for marquee selection</li>\n            <li data-lang=\"canvasHints.power\">You have the power</li>\n            <li data-lang=\"canvasHints.timeNow\">Your time is NOW!</li>\n          </ul>\n        </div>\n        <div class=\"legend-container\" id=\"edge-legend\" style=\"display: none;\"><div class=\"legend-title\" data-lang=\"legend.connectionLegend\">Connection Legend</div><button type=\"button\" class=\"legend-close-btn\">✕</button></div>\n        <div class=\"canvas-viewport\" id=\"canvas-viewport\">\n          <svg id=\"map\" viewBox=\"0 0 4000 3000\" style=\"\"></svg>\n        </div>\n        <div class=\"minimap-zoom-wrapper\" id=\"minimap-zoom-wrapper\" style=\"display: block;\">\n          <button type=\"button\" class=\"minimap-close-btn\" id=\"minimap-close-btn\" data-lang=\"tooltips.hideMapZoom\" data-lang-attr=\"title\">✕</button>\n          <div class=\"minimap-container\" id=\"minimap-container\">\n            <svg id=\"minimap\" viewBox=\"0 0 4000 3000\">\n              <rect class=\"minimap-viewport\" id=\"minimap-viewport\" x=\"0\" y=\"0\" width=\"4000\" height=\"3000\"></rect>\n            </svg>\n          </div>\n          <div class=\"zoom-toolbar\" id=\"zoom-toolbar\">\n            <button id=\"zoom-in-btn\" data-lang=\"tooltips.zoomIn\" data-lang-attr=\"title\">+</button>\n            <div class=\"zoom-level\" id=\"zoom-level\">100%</div>\n            <button id=\"zoom-out-btn\" data-lang=\"tooltips.zoomOut\" data-lang-attr=\"title\">-</button>\n            <div class=\"divider\"></div>\n            <button id=\"zoom-fit-btn\" data-lang=\"tooltips.fitToView\" data-lang-attr=\"title\">[ ]</button>\n            <button id=\"zoom-reset-btn\" data-lang=\"tooltips.resetView\" data-lang-attr=\"title\">R</button>\n          </div>\n        </div>\n        <button type=\"button\" id=\"edge-legend-mini\" class=\"legend-mini-btn\" style=\"bottom: 10px; display: none;\" data-lang=\"toolbar.legendMini\">Connection & Zone Legend</button>\n        <button type=\"button\" id=\"minimap-mini\" class=\"legend-mini-btn\" style=\"right: 10px; left: auto; bottom: 10px; display: none;\" data-lang=\"toolbar.minimapMini\">Map</button>\n        <button type=\"button\" id=\"draw-toolbar-mini\" class=\"legend-mini-btn\" style=\"top: 10px; left: 10px; right: auto; display: none;\" data-lang=\"toolbar.drawMini\">Draw</button>\n        <button type=\"button\" id=\"topology-toolbar-mini\" class=\"legend-mini-btn\" style=\"top: 10px; left: auto; right: 40px; display: none;\" data-lang=\"toolbar.topologyMini\">Add Connection</button></section>\n      <aside class=\"details-panel\" id=\"details-panel\">\n        <div class=\"sidebar-resizer\" id=\"sidebar-resizer\">\n          <svg class=\"resizer-icon\" width=\"6\" height=\"40\" viewBox=\"0 0 6 40\" xmlns=\"http://www.w3.org/2000/svg\">\n            <circle cx=\"3\" cy=\"14\" r=\"2\"></circle>\n            <circle cx=\"3\" cy=\"20\" r=\"2\"></circle>\n            <circle cx=\"3\" cy=\"26\" r=\"2\"></circle>\n          </svg>\n        </div>\n        <div class=\"mobile-footer-resizer\" id=\"mobile-footer-resizer\">\n          <svg class=\"resizer-icon\" width=\"40\" height=\"6\" viewBox=\"0 0 40 6\" xmlns=\"http://www.w3.org/2000/svg\">\n            <circle cx=\"14\" cy=\"3\" r=\"2\"></circle>\n            <circle cx=\"20\" cy=\"3\" r=\"2\"></circle>\n            <circle cx=\"26\" cy=\"3\" r=\"2\"></circle>\n          </svg>\n        </div>\n        <div id=\"node-panel\" style=\"display: none;\">\n          <div class=\"details-name editable-text\" id=\"node-name\">Core Router 1</div>\n          <div class=\"details-ip editable-text\" id=\"node-ip\">10.0.0.1</div>\n          <div class=\"details-role\" id=\"node-role\">Core Routing</div>\n          <div class=\"details-info-row\" id=\"mac-row\">\n            <span style=\"color: var(--text-soft); font-size: 14px;\" data-lang=\"nodePanel.mac\">MAC:</span>\n            <span class=\"editable-text\" id=\"node-mac\" style=\"margin-left: 8px; font-size: 14px;\">00:1A:2B:3C:4D:01</span>\n          </div>\n          <div class=\"details-info-row\" id=\"rack-unit-row\">\n            <span style=\"color: var(--text-soft); font-size: 14px;\" data-lang=\"nodePanel.rackUnit\">Rack Unit:</span>\n            <span class=\"editable-text\" id=\"node-rack\" style=\"margin-left: 8px; font-size: 14px;\">--</span>\n          </div>\n          <div class=\"details-info-row\" id=\"uheight-row\" style=\"display: none;\">\n            <span style=\"color: var(--text-soft); font-size: 14px;\" data-lang=\"nodePanel.uHeight\">U Height:</span>\n            <span class=\"editable-text\" id=\"node-uheight\" style=\"margin-left: 8px; font-size: 14px;\">2U</span>\n          </div>\n          <div class=\"details-info-row\">\n            <span style=\"color: var(--text-soft); font-size: 14px;\" data-lang=\"nodePanel.layer\">Layer:</span>\n            <select id=\"node-layer\" style=\"margin-left: 8px; padding: 4px 8px; background: var(--panel); color: var(--text-main); border: 1px solid var(--edge-main); border-radius: 4px; cursor: pointer; font-size: 14px;\">\n              <option value=\"layer1\" id=\"node-layer-opt1\">Physical</option>\n              <option value=\"layer2\" id=\"node-layer-opt2\">Logical</option>\n              <option value=\"layer3\" id=\"node-layer-opt3\">Security</option>\n              <option value=\"layer4\" id=\"node-layer-opt4\">Application</option>\n            </select>\n          </div>\n          <div class=\"details-info-row\" id=\"node-trail-row\">\n            <span style=\"color: var(--text-soft); font-size: 14px;\" data-lang=\"nodePanel.motionTrail\">Motion Trail:</span>\n            <label class=\"toggle-switch\" style=\"margin-left: 8px;\"><input type=\"checkbox\" id=\"node-trail-enabled\" checked><span class=\"toggle-slider\"></span></label>\n          </div>\n          <div class=\"details-info-row\" id=\"assigned-rack-row\" style=\"display: flex;\">\n            <span id=\"assigned-rack-label\" style=\"color: var(--text-soft); font-size: 14px;\" data-lang=\"nodePanel.assignedRack\">Assigned Rack:</span>\n            <select id=\"node-assigned-rack\" style=\"margin-left: 8px; padding: 4px 8px; background: var(--panel); color: var(--text-main); border: 1px solid var(--edge-main); border-radius: 4px; cursor: pointer; font-size: 14px;\"><option value=\"\" data-lang=\"ui.options.none\">None</option></select>\n          </div>\n          <div class=\"details-info-row\" id=\"rack-capacity-row\" style=\"display: none;\">\n            <span style=\"color: var(--text-soft); font-size: 14px;\" data-lang=\"nodePanel.rackCapacity\">Rack Capacity:</span>\n            <select id=\"node-rack-capacity\" style=\"margin-left: 8px; padding: 4px 8px; background: var(--panel); color: var(--text-main); border: 1px solid var(--edge-main); border-radius: 4px; cursor: pointer; font-size: 14px;\">\n              <option value=\"42\">42U</option>\n              <option value=\"48\">48U</option>\n              <option value=\"24\">24U</option>\n              <option value=\"12\">12U</option>\n\t\t\t  <option value=\"6\">6U</option>\n            </select>\n          </div>\n\t\t  <details id=\"rack-contents-section\" style=\"display: none; margin-top: 12px; margin-bottom: 12px; padding: 10px; background: var(--panel-alt); border-radius: 6px;\" open=\"\">\n            <summary style=\"color: var(--accent); font-weight: 600; font-size: 14px; cursor: pointer;\" data-lang=\"nodePanel.nodesInRack\">Nodes in Rack (<span id=\"rack-contents-count\">0</span>)</summary>\n            <div id=\"rack-contents-list\" style=\"max-height: 200px; overflow-y: auto; margin-top: 8px;\"></div>\n          </details>\n\t\t  </details>\n          <details id=\"fov-section\" style=\"display: none; margin-top: 12px; margin-bottom: 12px; padding: 10px; background: var(--panel-alt); border-radius: 6px;\">\n            <summary style=\"color: var(--accent); font-weight: 600; font-size: 14px; cursor: pointer;\" data-lang=\"zone.coverageZone\">Coverage Zone</summary>\n            <div style=\"padding: 10px 0;\">\n              <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px; gap: 8px;\">\n                <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\" data-lang=\"zone.preset\">Preset:</label>\n                <select id=\"fov-preset\" style=\"flex: 1; padding: 4px 8px; background: var(--panel); border: 1px solid var(--edge-main); border-radius: 4px; color: var(--text-main);\">\n                  <option value=\"\" data-lang=\"zone.applyPreset\">-- Apply Preset --</option>\n                  <option value=\"security-cam\" data-lang=\"zone.presets.securityCam\">Security Camera</option>\n                  <option value=\"ptz-cam\" data-lang=\"zone.presets.ptzCam\">PTZ Camera</option>\n                  <option value=\"motion-detect\" data-lang=\"zone.presets.motionDetector\">Motion Detector</option>\n                  <option value=\"wifi-strong\" data-lang=\"zone.presets.wifi\">WiFi</option>\n                  <option value=\"wifi-extended\" data-lang=\"zone.presets.wifiExtender\">WiFi Extender</option>\n                  <option value=\"smoke-alarm\" data-lang=\"zone.presets.smokeAlarm\">Smoke Alarm</option>\n                  <option value=\"sprinkler-arc\" data-lang=\"zone.presets.sprinklerArc\">Sprinkler Arc</option>\n                </select>\n                <button id=\"fov-save-preset\" data-lang=\"tooltips.saveAsPreset\" data-lang-attr=\"title\" style=\"padding: 4px 8px; background: var(--panel); border: 1px solid var(--edge-main); border-radius: 4px; color: var(--text-main); cursor: pointer;\">💾</button>\n                <button id=\"fov-copy-style\" data-lang=\"tooltips.copyZoneStyle\" data-lang-attr=\"title\" style=\"padding: 4px 8px; background: var(--panel); border: 1px solid var(--edge-main); border-radius: 4px; color: var(--text-main); cursor: pointer;\">📋</button>\n                <button id=\"fov-paste-style\" data-lang=\"tooltips.pasteZoneStyle\" data-lang-attr=\"title\" style=\"padding: 4px 8px; background: var(--panel); border: 1px solid var(--edge-main); border-radius: 4px; color: var(--text-main); cursor: pointer;\">📥</button>\n              </div>\n              <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\" data-lang=\"zone.showZone\">Show Zone:</label>\n                <label class=\"toggle-switch\"><input type=\"checkbox\" id=\"fov-enabled\"><span class=\"toggle-slider\"></span></label>\n              </div>\n              <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\" data-lang=\"zone.angle\">Angle:</label>\n                <input type=\"range\" id=\"fov-angle\" min=\"10\" max=\"360\" value=\"90\" style=\"flex: 1;\">\n                <span id=\"fov-angle-value\" style=\"min-width: 40px; text-align: right; color: var(--text-main);\">90°</span>\n              </div>\n              <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\" data-lang=\"zone.distance\">Distance:</label>\n                <input type=\"range\" id=\"fov-distance\" min=\"50\" max=\"500\" value=\"150\" style=\"flex: 1;\">\n                <span id=\"fov-distance-value\" style=\"min-width: 40px; text-align: right; color: var(--text-main);\">150</span>\n              </div>\n              <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\" data-lang=\"zone.innerRadius\">Inner Radius:</label>\n                <input type=\"range\" id=\"fov-inner-radius\" min=\"0\" max=\"200\" value=\"0\" style=\"flex: 1;\">\n                <span id=\"fov-inner-radius-value\" style=\"min-width: 40px; text-align: right; color: var(--text-main);\">0</span>\n              </div>\n              <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\" data-lang=\"zone.rotation\">Rotation:</label>\n                <input type=\"range\" id=\"fov-rotation\" min=\"0\" max=\"360\" value=\"0\" style=\"flex: 1;\">\n                <span id=\"fov-rotation-value\" style=\"min-width: 40px; text-align: right; color: var(--text-main);\">0°</span>\n              </div>\n              <div style=\"border-top: 1px solid var(--edge-main); margin-top: 10px; padding-top: 10px;\">\n                <div style=\"font-size: 12px; color: var(--text-soft); margin-bottom: 8px; text-transform: uppercase; letter-spacing: 0.05em;\" data-lang=\"zone.fill\">Fill</div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\" data-lang=\"zone.color\">Color:</label>\n                  <input type=\"color\" id=\"fov-color\" value=\"#f59e0b\" style=\"width: 50px; height: 30px; border: 1px solid var(--edge-main); border-radius: 4px; cursor: pointer;\">\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\" data-lang=\"zone.opacity\">Opacity:</label>\n                  <input type=\"range\" id=\"fov-opacity\" min=\"5\" max=\"80\" value=\"20\" style=\"flex: 1;\">\n                  <span id=\"fov-opacity-value\" style=\"min-width: 40px; text-align: right; color: var(--text-main);\">20%</span>\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\" data-lang=\"zone.gradient\">Gradient:</label>\n                  <label class=\"toggle-switch\"><input type=\"checkbox\" id=\"fov-gradient\"><span class=\"toggle-slider\"></span></label>\n                  <span style=\"color: var(--text-soft); font-size: 12px; margin-left: 8px;\" data-lang=\"zone.fadeTowardEdge\">Fade toward edge</span>\n                </div>\n              </div>\n              <div style=\"border-top: 1px solid var(--edge-main); margin-top: 10px; padding-top: 10px;\">\n                <div style=\"font-size: 12px; color: var(--text-soft); margin-bottom: 8px; text-transform: uppercase; letter-spacing: 0.05em;\" data-lang=\"zone.border\">Border</div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\" data-lang=\"zone.color\">Color:</label>\n                  <input type=\"color\" id=\"fov-border-color\" value=\"#f59e0b\" style=\"width: 50px; height: 30px; border: 1px solid var(--edge-main); border-radius: 4px; cursor: pointer;\">\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\" data-lang=\"zone.width\">Width:</label>\n                  <input type=\"range\" id=\"fov-border-width\" min=\"0\" max=\"10\" value=\"2\" style=\"flex: 1;\">\n                  <span id=\"fov-border-width-value\" style=\"min-width: 40px; text-align: right; color: var(--text-main);\">2</span>\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\" data-lang=\"zone.style\">Style:</label>\n                  <select id=\"fov-border-style\" style=\"flex: 1; padding: 4px 8px; background: var(--panel); border: 1px solid var(--edge-main); border-radius: 4px; color: var(--text-main);\">\n                    <option value=\"solid\" data-lang=\"ui.options.solid\">Solid</option>\n                    <option value=\"dashed\" data-lang=\"ui.options.dashed\">Dashed</option>\n                    <option value=\"dotted\" data-lang=\"ui.options.dotted\">Dotted</option>\n                  </select>\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\" data-lang=\"zone.opacity\">Opacity:</label>\n                  <input type=\"range\" id=\"fov-border-opacity\" min=\"0\" max=\"100\" value=\"100\" style=\"flex: 1;\">\n                  <span id=\"fov-border-opacity-value\" style=\"min-width: 40px; text-align: right; color: var(--text-main);\">100%</span>\n                </div>\n              </div>\n              <div style=\"border-top: 1px solid var(--edge-main); margin-top: 10px; padding-top: 10px;\">\n                <div style=\"font-size: 12px; color: var(--text-soft); margin-bottom: 8px; text-transform: uppercase; letter-spacing: 0.05em;\" data-lang=\"zone.label\">Label</div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\" data-lang=\"zone.text\">Text:</label>\n                  <input type=\"text\" id=\"fov-label\" placeholder=\"e.g. Detection Zone\" style=\"flex: 1; padding: 4px 8px; background: var(--panel); border: 1px solid var(--edge-main); border-radius: 4px; color: var(--text-main);\" data-lang=\"zone.labelPlaceholder\" data-lang-attr=\"placeholder\">\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\" data-lang=\"zone.position\">Position:</label>\n                  <select id=\"fov-label-position\" style=\"flex: 1; padding: 4px 8px; background: var(--panel); border: 1px solid var(--edge-main); border-radius: 4px; color: var(--text-main);\">\n                    <option value=\"center\" data-lang=\"ui.options.center\">Center</option>\n                    <option value=\"edge\" data-lang=\"zone.edge\">Edge</option>\n                    <option value=\"outside\" data-lang=\"zone.outside\">Outside</option>\n                  </select>\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\" data-lang=\"zone.fontSize\">Font Size:</label>\n                  <input type=\"range\" id=\"fov-label-size\" min=\"8\" max=\"32\" value=\"14\" style=\"flex: 1;\">\n                  <span id=\"fov-label-size-value\" style=\"min-width: 40px; text-align: right; color: var(--text-main);\">14px</span>\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\" data-lang=\"zone.fontColor\">Font Color:</label>\n                  <input type=\"color\" id=\"fov-label-color\" value=\"#ffffff\" style=\"width: 50px; height: 30px; border: 1px solid var(--edge-main); border-radius: 4px; cursor: pointer;\">\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\" data-lang=\"zone.bold\">Bold:</label>\n                  <label class=\"toggle-switch\"><input type=\"checkbox\" id=\"fov-label-bold\"><span class=\"toggle-slider\"></span></label>\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\" data-lang=\"zone.background\">Background:</label>\n                  <label class=\"toggle-switch\"><input type=\"checkbox\" id=\"fov-label-bg\"><span class=\"toggle-slider\"></span></label>\n                  <input type=\"color\" id=\"fov-label-bg-color\" value=\"#000000\" style=\"width: 50px; height: 30px; border: 1px solid var(--edge-main); border-radius: 4px; cursor: pointer; margin-left: 8px;\">\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\" data-lang=\"zone.offsetX\">Offset X:</label>\n                  <input type=\"range\" id=\"fov-label-offset-x\" min=\"-200\" max=\"200\" value=\"0\" style=\"flex: 1;\">\n                  <span id=\"fov-label-offset-x-value\" style=\"min-width: 40px; text-align: right; color: var(--text-main);\">0</span>\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\" data-lang=\"zone.offsetY\">Offset Y:</label>\n                  <input type=\"range\" id=\"fov-label-offset-y\" min=\"-200\" max=\"200\" value=\"0\" style=\"flex: 1;\">\n                  <span id=\"fov-label-offset-y-value\" style=\"min-width: 40px; text-align: right; color: var(--text-main);\">0</span>\n                </div>\n              </div>\n              <div style=\"border-top: 1px solid var(--edge-main); margin-top: 10px; padding-top: 10px;\">\n                <div style=\"font-size: 12px; color: var(--text-soft); margin-bottom: 8px; text-transform: uppercase; letter-spacing: 0.05em;\" data-lang=\"zone.animation\">Animation</div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\" data-lang=\"zone.animate\">Animate:</label>\n                  <label class=\"toggle-switch\"><input type=\"checkbox\" id=\"fov-animate\"><span class=\"toggle-slider\"></span></label>\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\" data-lang=\"zone.type\">Type:</label>\n                  <select id=\"fov-animation-type\" style=\"flex: 1; padding: 4px 8px; background: var(--panel); border: 1px solid var(--edge-main); border-radius: 4px; color: var(--text-main);\">\n                    <option value=\"sweep\" data-lang=\"ui.labels.sweepPan\">Sweep (Pan)</option>\n                    <option value=\"pulse\" data-lang=\"ui.labels.pulseBreathe\">Pulse (Breathe)</option>\n                    <option value=\"rings\" data-lang=\"ui.labels.ringsEmanate\">Rings (Emanate)</option>\n                    <option value=\"spin\" data-lang=\"ui.labels.spinRotate\">Spin (Rotate)</option>\n                  </select>\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\" data-lang=\"zone.sweep\">Sweep:</label>\n                  <input type=\"range\" id=\"fov-sweep\" min=\"30\" max=\"360\" value=\"120\" style=\"flex: 1;\">\n                  <span id=\"fov-sweep-value\" style=\"min-width: 40px; text-align: right; color: var(--text-main);\">120°</span>\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\" data-lang=\"zone.speed\">Speed:</label>\n                  <input type=\"range\" id=\"fov-speed\" min=\"1\" max=\"60\" value=\"4\" style=\"flex: 1;\">\n                  <span id=\"fov-speed-value\" style=\"min-width: 40px; text-align: right; color: var(--text-main);\">4s</span>\n                </div>\n              </div>\n            </div>\n          </details>\n          <details id=\"node-connections-section\" style=\"margin-top: 12px; margin-bottom: 12px; padding: 10px; background: var(--panel-alt); border-radius: 6px;\">\n            <summary style=\"color: var(--accent); font-weight: 600; font-size: 14px; cursor: pointer;\"><span data-lang=\"nodePanel.connections\">Connections</span> (<span id=\"node-connections-count\">0</span>)</summary>\n            <div id=\"node-connections-list\" style=\"max-height: 200px; overflow-y: auto; margin-top: 8px;\"></div>\n          </details>\n          <div class=\"badge-row\" id=\"node-tags\"></div>\n          <div class=\"size-controls\">\n            <label data-lang=\"ui.labels.size\">Size:</label>\n            <input type=\"range\" id=\"size-slider\" min=\"20\" max=\"200\" value=\"55\">\n            <span id=\"size-value\">55</span>\n            <button id=\"reset-size\" data-lang=\"ui.buttons.reset\">Reset</button>\n          </div>\n          <div class=\"size-controls\">\n            <label data-lang=\"ui.labels.rotation\">Rotation:</label>\n            <input type=\"range\" id=\"rotation-slider\" min=\"-360\" max=\"360\" value=\"0\">\n            <input type=\"number\" id=\"rotation-value\" value=\"0\" style=\"width: 60px; padding: 4px; background: var(--panel); border: 1px solid var(--edge-main); border-radius: 4px; color: var(--text-main); text-align: center;\">\n            <span style=\"color: var(--text-soft);\">°</span>\n            <button id=\"reset-rotation\" data-lang=\"ui.buttons.reset\">Reset</button>\n          </div>\n          <details class=\"style-section\">\n            <summary data-lang=\"nodePanel.styling\">Styling</summary>\n            <div class=\"style-content\">\n              <div class=\"style-row\">\n                <label data-lang=\"nodePanel.screen\">Screen:</label>\n                <select id=\"style-scope\">\n                  <option value=\"all\" data-lang=\"ui.tabs.all\">All</option>\n                  <option value=\"desktop\" data-lang=\"help.tabs.desktop\">Desktop</option>\n                  <option value=\"tablet\" data-lang=\"nodePanel.tablet\">Tablet</option>\n                  <option value=\"mobile\" data-lang=\"help.tabs.mobile\">Mobile</option>\n                  <option value=\"fold\" data-lang=\"nodePanel.fold\">Fold</option>\n                </select>\n              </div>\n              <div class=\"style-row\">\n                <label data-lang=\"ui.labels.category\">Category:</label>\n                <select id=\"shape-category-select\">\n                  <option value=\"basic\" data-lang=\"shapes.categories.basic\">Basic Shapes</option>\n                  <option value=\"computers\" data-lang=\"shapes.categories.computers\">Computers & Devices</option>\n                  <option value=\"network\" data-lang=\"shapes.categories.network\">Network Equipment</option>\n                  <option value=\"cloud\" data-lang=\"shapes.categories.cloud\">Cloud & Services</option>\n                  <option value=\"security\" data-lang=\"shapes.categories.security\">Security & Monitoring</option>\n                  <option value=\"smarthome\" data-lang=\"shapes.categories.smarthome\">Smart Home</option>\n                  <option value=\"sports\" data-lang=\"shapes.categories.sports\">Sports</option>\n                  <option value=\"rack\" data-lang=\"shapes.categories.rack\">Rack Equipment</option>\n                </select>\n              </div>\n              <div class=\"style-row\">\n                <label data-lang=\"ui.labels.shape\">Shape:</label>\n                <select id=\"shape-select\"></select>\n              </div>\n              <div class=\"style-row\">\n                <label data-lang=\"nodePanel.fillColor\">Fill Color:</label>\n                <input type=\"color\" id=\"circle-color\" value=\"#1e293b\">\n              </div>\n              <div class=\"style-row\">\n                <label data-lang=\"nodePanel.borderColor\">Border Color:</label>\n                <input type=\"color\" id=\"circle-border\" value=\"#475569\">\n              </div>\n              <div class=\"style-row\">\n                <label data-lang=\"ui.labels.titleColor\">Title Color:</label>\n                <input type=\"color\" id=\"title-color\" value=\"#e2e8f0\">\n              </div>\n              <div class=\"style-row\">\n                <label data-lang=\"nodePanel.titleFont\">Title Font:</label>\n                <select id=\"title-font\">\n                  <option value=\"system-ui, sans-serif\" data-lang=\"nodePanel.systemUI\"> System UI </option>\n                  <option value=\"monospace\" data-lang=\"ui.fonts.monospace\">Monospace</option>\n                  <option value=\"serif\" data-lang=\"nodePanel.serif\">Serif</option>\n                  <option value=\"cursive\" data-lang=\"nodePanel.cursive\">Cursive</option>\n                  <option value=\"'Courier New', monospace\" data-lang=\"nodePanel.courier\"> Courier </option>\n                  <option value=\"Arial, sans-serif\" data-lang=\"ui.fonts.arial\">Arial</option>\n                  <option value=\"'Times New Roman', serif\" data-lang=\"nodePanel.times\"> Times </option>\n                </select>\n              </div>\n              <div class=\"style-row\">\n                <label data-lang=\"ui.labels.titleSize\">Title Size:</label>\n                <input type=\"number\" id=\"title-size\" min=\"8\" max=\"100\" value=\"18\">\n              </div>\n              <div class=\"style-row\">\n                <label data-lang=\"nodePanel.subColor\">Sub Color:</label>\n                <input type=\"color\" id=\"sub-color\" value=\"#94a3b8\">\n              </div>\n              <div class=\"style-row\">\n                <label data-lang=\"nodePanel.subFont\">Sub Font:</label>\n                <select id=\"sub-font\">\n                  <option value=\"system-ui, sans-serif\" data-lang=\"nodePanel.systemUI\"> System UI </option>\n                  <option value=\"monospace\" data-lang=\"ui.fonts.monospace\">Monospace</option>\n                  <option value=\"serif\" data-lang=\"nodePanel.serif\">Serif</option>\n                  <option value=\"cursive\" data-lang=\"nodePanel.cursive\">Cursive</option>\n                  <option value=\"'Courier New', monospace\" data-lang=\"nodePanel.courier\"> Courier </option>\n                  <option value=\"Arial, sans-serif\" data-lang=\"ui.fonts.arial\">Arial</option>\n                  <option value=\"'Times New Roman', serif\" data-lang=\"nodePanel.times\"> Times </option>\n                </select>\n              </div>\n              <div class=\"style-row\">\n                <label data-lang=\"nodePanel.subSize\">Sub Size:</label>\n                <input type=\"number\" id=\"sub-size\" min=\"6\" max=\"80\" value=\"13\">\n              </div>\n                <div class=\"style-row\">\n                  <label data-lang=\"nodePanel.nameY\">Name Y:</label>\n                  <input type=\"number\" id=\"title-offset-y\" min=\"-100\" max=\"100\" value=\"0\" style=\"width: 60px\">\n                </div>\n                <div class=\"style-row\">\n                  <label data-lang=\"nodePanel.nameX\">Name X:</label>\n                  <input type=\"number\" id=\"title-offset-x\" min=\"-100\" max=\"100\" value=\"0\" style=\"width: 60px\">\n                </div>\n                <div class=\"style-row\">\n                  <label data-lang=\"nodePanel.ipY\">IP Y:</label>\n                  <input type=\"number\" id=\"sub-offset-y\" min=\"-100\" max=\"100\" value=\"0\" style=\"width: 60px\">\n                </div>\n                <div class=\"style-row\">\n                  <label data-lang=\"nodePanel.ipX\">IP X:</label>\n                  <input type=\"number\" id=\"sub-offset-x\" min=\"-100\" max=\"100\" value=\"0\" style=\"width: 60px\">\n                </div>\n              <button id=\"reset-styles\" style=\"width: 100%;margin-top: 10px;padding: 8px;background: var(--panel);color: var(--text-main);border: 1px solid var(--edge-main);border-radius: 6px;cursor: pointer;font-size: clamp(13px, 1.5vw, 17px);\" data-lang=\"nodePanel.resetStyles\">Reset Styles</button>\n            </div>\n          </details>\n          <div class=\"section-label\" data-lang=\"ui.labels.notes\">Notes</div>\n          <div class=\"notes-feed\" id=\"node-notes\"></div>\n          <button id=\"add-note-btn\" style=\"margin-top: 10px;padding: 8px 16px;background: var(--accent);color: var(--bg);border: none;border-radius: 6px;cursor: pointer;font-size: clamp(14px, 1.6vw, 18px);font-weight: 600;width: 100%;\" data-lang=\"ui.buttons.addNote\">+ Add Note</button>\n          <button id=\"delete-node-btn\" style=\"margin-top: 15px;padding: 10px 16px;background: var(--danger);color: rgb(255, 255, 255);border: none;border-radius: 6px;cursor: pointer;font-size: clamp(14px, 1.6vw, 18px);font-weight: 600;width: 100%;\" data-lang=\"ui.buttons.deleteNode\">Delete Node</button>\n        </div>\n        <div id=\"edge-panel\" style=\"display: none\">\n          <div class=\"details-name\" id=\"edge-title\" data-lang=\"edgePanel.customLine\">Custom line</div>\n          <div class=\"style-row\" style=\"margin-top: 10px\">\n            <label data-lang=\"ui.labels.width\">Width:</label>\n            <input type=\"number\" id=\"edge-width\" min=\"1\" max=\"20\" value=\"4\">\n          </div>\n          <div class=\"style-row\">\n            <label data-lang=\"ui.labels.color\">Color:</label>\n            <input type=\"color\" id=\"edge-color\" value=\"#475569\">\n          </div>\n          <div class=\"style-row\">\n            <label data-lang=\"ui.labels.style\">Line Style:</label>\n            <select id=\"edge-line-style\">\n  <option value=\"solid\" data-lang=\"ui.options.solid\">Solid</option>\n  <option value=\"dashed\" data-lang=\"ui.options.dashed\">Dashed</option>\n  <option value=\"dotted\" data-lang=\"ui.options.dotted\">Dotted</option>\n  <option value=\"wall\" data-lang=\"drawToolbar.wall\">Wall</option>\n</select>\n          </div>\n          <div class=\"style-row\">\n            <label data-lang=\"ui.labels.routing\">Routing:</label>\n            <select id=\"edge-routing\">\n              <option value=\"orthogonal\" data-lang=\"ui.options.orthogonal\">Orthogonal (90°)</option>\n              <option value=\"curved\" data-lang=\"ui.options.curved\">Curved</option>\n              <option value=\"straight\" data-lang=\"ui.options.straight\">Straight</option>\n            </select>\n          </div>\n          <div class=\"style-row\" id=\"waypoints-row\" style=\"display: none;\">\n            <label id=\"waypoints-label\" data-lang=\"ui.labels.waypoints\">Waypoints</label>\n            <button id=\"clear-waypoints-btn\" style=\"padding: 6px 12px;background: var(--panel);border: 1px solid var(--edge-main);border-radius: 6px;cursor: pointer;font-size: 14px;color: var(--text-main);\" data-lang=\"ui.buttons.clearAll\">Clear All</button>\n          </div>\n          <div class=\"style-row\">\n            <label data-lang=\"ui.labels.direction\">Direction:</label>\n            <select id=\"edge-direction\">\n              <option value=\"none\" data-lang=\"topologyToolbar.noArrows\">No arrows</option>\n              <option value=\"forward\" data-lang=\"topologyToolbar.forward\">→ Forward</option>\n              <option value=\"backward\" data-lang=\"topologyToolbar.backward\">← Backward</option>\n              <option value=\"both\" data-lang=\"topologyToolbar.bidirectional\">↔ Bidirectional</option>\n            </select>\n          </div>\n          <div class=\"style-row\">\n            <label data-lang=\"edgePanel.animate\">Animate:</label>\n            <label class=\"toggle-switch\"><input type=\"checkbox\" id=\"edge-animate\"><span class=\"toggle-slider\"></span></label>\n          </div>\n          <div class=\"style-row\">\n            <label data-lang=\"ui.labels.style\">Style:</label>\n            <select id=\"edge-animation-style\">\n              <option value=\"\" data-lang=\"ui.options.default\">Default</option>\n              <option value=\"arrows\" data-lang=\"ui.options.flowingArrows\">Flowing Arrows</option>\n              <option value=\"dots\" data-lang=\"ui.options.dotArrows\">Dot Arrows</option>\n            </select>\n          </div>\n          <div class=\"style-row\">\n            <label data-lang=\"ui.labels.animationSpeed\">Speed:</label>\n            <select id=\"edge-animation-speed\">\n              <option value=\"\" data-lang=\"ui.options.default\">Default</option>\n              <option value=\"0.5\" data-lang=\"ui.options.veryFast\">Very Fast</option>\n              <option value=\"1\" data-lang=\"ui.options.fast\">Fast</option>\n              <option value=\"1.5\" data-lang=\"ui.options.normal\">Normal</option>\n              <option value=\"2.5\" data-lang=\"ui.options.slow\">Slow</option>\n              <option value=\"4\" data-lang=\"ui.options.verySlow\">Very Slow</option>\n            </select>\n          </div>\n          <div class=\"style-row\" id=\"edge-from-port-row\" style=\"display: none;\">\n            <label data-lang=\"ui.labels.fromPort\">From Port:</label>\n            <input type=\"text\" id=\"edge-from-port\" placeholder=\"e.g. eth0, gi0/1\" data-lang=\"ui.placeholders.fromPort\" data-lang-attr=\"placeholder\" style=\"flex: 1;\">\n          </div>\n          <div class=\"style-row\" id=\"edge-to-port-row\" style=\"display: none;\">\n            <label data-lang=\"ui.labels.toPort\">To Port:</label>\n            <input type=\"text\" id=\"edge-to-port\" placeholder=\"e.g. eth1, gi0/2\" data-lang=\"ui.placeholders.toPort\" data-lang-attr=\"placeholder\" style=\"flex: 1;\">\n          </div>\n          <div class=\"section-label\" data-lang=\"ui.labels.lineNotes\">Line Notes</div>\n          <div class=\"notes-feed\" id=\"edge-notes\"></div>\n          <button id=\"add-edge-note\" style=\"margin-top: 10px;padding: 8px 16px;background: var(--accent);color: var(--bg);border: none;border-radius: 6px;cursor: pointer;font-size: clamp(14px, 1.6vw, 18px);font-weight: 600;width: 100%;\" data-lang=\"ui.buttons.addNote\">+ Add Note</button>\n          <button id=\"delete-edge\" style=\"margin-top: 15px;padding: 10px 16px;background: var(--danger);color: #fff;border: none;border-radius: 6px;cursor: pointer;font-size: clamp(14px, 1.6vw, 18px);font-weight: 600;\" data-lang=\"ui.buttons.delete\">Delete Line</button>\n        </div>\n        <div id=\"rect-panel\" style=\"display: none\">\n          <div class=\"details-name\" id=\"rect-title\" data-lang=\"rectPanel.zone\">Zone</div>\n          <div class=\"style-row\" id=\"rect-fill-row\" style=\"margin-top: 10px\">\n            <label data-lang=\"rectPanel.fillColor\">Fill Color:</label>\n            <input type=\"color\" id=\"rect-color\" value=\"#f97316\">\n          </div>\n          <div class=\"style-row\" id=\"rect-opacity-row\">\n            <label data-lang=\"rectPanel.fillOpacity\">Fill Opacity:</label>\n            <input type=\"range\" id=\"rect-fill-opacity\" min=\"0\" max=\"100\" value=\"30\" style=\"flex: 1;\">\n            <span id=\"rect-fill-opacity-value\" style=\"min-width: 40px; text-align: right; color: var(--text-soft);\">30%</span>\n          </div>\n          <div class=\"style-row\">\n            <label data-lang=\"rectPanel.borderColor\">Border Color:</label>\n            <input type=\"color\" id=\"rect-border-color\" value=\"#f97316\">\n          </div>\n          <div class=\"style-row\">\n            <label data-lang=\"rectPanel.borderWidth\">Border Width:</label>\n            <input type=\"number\" id=\"rect-border-width\" min=\"0\" max=\"20\" value=\"2\">\n          </div>\n          <div class=\"style-row\">\n            <label data-lang=\"ui.labels.style\">Style:</label>\n            <select id=\"rect-style-select\">\n              <option value=\"filled\" data-lang=\"drawToolbar.filled\">Filled</option>\n              <option value=\"outlined\" data-lang=\"drawToolbar.outlined\">Outlined</option>\n            </select>\n          </div>\n          <div class=\"style-row\">\n            <label data-lang=\"rectPanel.lineStyle\">Line Style:</label>\n            <select id=\"rect-line-style\">\n  <option value=\"solid\" data-lang=\"ui.options.solid\">Solid</option>\n  <option value=\"dashed\" data-lang=\"ui.options.dashed\">Dashed</option>\n  <option value=\"dotted\" data-lang=\"ui.options.dotted\">Dotted</option>\n  <option value=\"wall\" data-lang=\"drawToolbar.wall\">Wall</option>\n</select>\n          </div>\n          <div class=\"style-row\">\n            <label data-lang=\"ui.labels.rotation\">Rotation:</label>\n            <input type=\"range\" id=\"rect-rotation\" min=\"-360\" max=\"360\" value=\"0\" style=\"flex: 1;\">\n            <input type=\"number\" id=\"rect-rotation-value\" value=\"0\" style=\"width: 60px; padding: 4px; background: var(--panel); border: 1px solid var(--edge-main); border-radius: 4px; color: var(--text-main); text-align: center;\">\n            <span style=\"color: var(--text-soft);\">°</span>\n          </div>\n          <div class=\"section-label\" data-lang=\"ui.labels.zoneNotes\">Zone Notes</div>\n          <div class=\"notes-feed\" id=\"rect-notes\"></div>\n          <button id=\"add-rect-note\" style=\"margin-top: 10px;padding: 8px 16px;background: var(--accent);color: var(--bg);border: none;border-radius: 6px;cursor: pointer;font-size: clamp(14px, 1.6vw, 18px);font-weight: 600;width: 100%;\" data-lang=\"ui.buttons.addNote\">+ Add Note</button>\n          <button id=\"delete-rect\" style=\"margin-top: 15px;padding: 10px 16px;background: var(--danger);color: #fff;border: none;border-radius: 6px;cursor: pointer;font-size: clamp(14px, 1.6vw, 18px);font-weight: 600;\" data-lang=\"ui.buttons.delete\">Delete Zone</button>\n        </div>\n        <div id=\"text-panel\" style=\"display: none\">\n          <div class=\"details-name\" id=\"text-title\" data-lang=\"textPanel.textElement\">Text Element</div>\n          <div class=\"style-row\" style=\"margin-top: 10px\">\n            <label data-lang=\"textPanel.content\">Content:</label>\n            <textarea id=\"text-content\" placeholder=\"Enter text...\" data-lang=\"ui.placeholders.enterText\" data-lang-attr=\"placeholder\" style=\"width: 100%;padding: 8px 12px;background: var(--panel);border: 1px solid var(--edge-main);border-radius: 6px;color: var(--text-main);font-size: 14px;min-height: 80px;resize: vertical;font-family: inherit;\"></textarea>\n          </div>\n          <div class=\"style-row\">\n            <label data-lang=\"textPanel.fontSizeLabel\">Font Size:</label>\n            <input type=\"number\" id=\"text-font-size\" min=\"8\" max=\"200\" value=\"18\" style=\"flex: 1;padding: 6px 10px;background: var(--panel);border: 1px solid var(--edge-main);border-radius: 4px;color: var(--text-main);\">\n          </div>\n          <div class=\"style-row\">\n            <label data-lang=\"textPanel.colorLabel\">Color:</label>\n            <input type=\"color\" id=\"text-color\" value=\"#e2e8f0\">\n          </div>\n          <div class=\"style-row\">\n            <label data-lang=\"textPanel.fontWeight\">Font Weight:</label>\n            <select id=\"text-font-weight\" style=\"flex: 1;padding: 6px 10px;background: var(--panel);border: 1px solid var(--edge-main);border-radius: 4px;color: var(--text-main);cursor: pointer;\">\n              <option value=\"normal\" data-lang=\"ui.options.normal\">Normal</option>\n              <option value=\"bold\" data-lang=\"textPanel.bold\">Bold</option>\n              <option value=\"600\" data-lang=\"textPanel.semiBold\">Semi-Bold</option>\n              <option value=\"300\" data-lang=\"textPanel.light\">Light</option>\n            </select>\n          </div>\n          <div class=\"style-row\">\n            <label data-lang=\"textPanel.fontStyle\">Font Style:</label>\n            <select id=\"text-font-style\" style=\"flex: 1;padding: 6px 10px;background: var(--panel);border: 1px solid var(--edge-main);border-radius: 4px;color: var(--text-main);cursor: pointer;\">\n              <option value=\"normal\" data-lang=\"ui.options.normal\">Normal</option>\n              <option value=\"italic\" data-lang=\"textPanel.italic\">Italic</option>\n            </select>\n          </div>\n          <div class=\"style-row\">\n            <label data-lang=\"textPanel.textAlign\">Text Align:</label>\n            <select id=\"text-align\" style=\"flex: 1;padding: 6px 10px;background: var(--panel);border: 1px solid var(--edge-main);border-radius: 4px;color: var(--text-main);cursor: pointer;\">\n              <option value=\"start\" data-lang=\"ui.options.left\">Left</option>\n              <option value=\"middle\" data-lang=\"ui.options.center\">Center</option>\n              <option value=\"end\" data-lang=\"ui.options.right\">Right</option>\n            </select>\n          </div>\n          <div class=\"style-row\">\n            <label data-lang=\"textPanel.textDecoration\">Text Decoration:</label>\n            <select id=\"text-decoration\" style=\"flex: 1;padding: 6px 10px;background: var(--panel);border: 1px solid var(--edge-main);border-radius: 4px;color: var(--text-main);cursor: pointer;\">\n              <option value=\"none\" data-lang=\"ui.options.none\">None</option>\n              <option value=\"underline\" data-lang=\"textPanel.underline\">Underline</option>\n              <option value=\"line-through\" data-lang=\"textPanel.strikeThrough\">Strike Through</option>\n            </select>\n          </div>\n          <div class=\"style-row\">\n            <label data-lang=\"ui.labels.background\">Background:</label>\n            <input type=\"color\" id=\"text-bg-color\" value=\"#000000\">\n            <div style=\"margin-left: 10px; display: flex; align-items: center; gap: 6px;\">\n            <label class=\"toggle-switch\"><input type=\"checkbox\" id=\"text-bg-enabled\"><span class=\"toggle-slider\"></span></label>\n            <span style=\"font-size: 13px;\" data-lang=\"textPanel.enable\">Enable</span>\n            </div>\n          </div>\n          <div class=\"style-row\">\n            <label data-lang=\"ui.labels.opacity\">Opacity:</label>\n            <input type=\"range\" id=\"text-opacity\" min=\"0\" max=\"1\" step=\"0.1\" value=\"1\" style=\"flex: 1\">\n            <span id=\"text-opacity-val\" style=\"min-width: 35px; text-align: right\">100%</span>\n          </div>\n          <div class=\"style-row\">\n            <label data-lang=\"ui.labels.rotation\">Rotation:</label>\n            <input type=\"range\" id=\"text-rotation\" min=\"-360\" max=\"360\" value=\"0\" style=\"flex: 1\">\n            <input type=\"number\" id=\"text-rotation-val\" value=\"0\" style=\"width: 60px; padding: 4px; background: var(--panel); border: 1px solid var(--edge-main); border-radius: 4px; color: var(--text-main); text-align: center;\">\n            <span style=\"color: var(--text-soft);\">°</span>\n          </div>\n          <button id=\"delete-text\" style=\"margin-top: 15px;padding: 10px 16px;background: var(--danger);color: #fff;border: none;border-radius: 6px;cursor: pointer;font-size: clamp(14px, 1.6vw, 18px);font-weight: 600;width: 100%;\" data-lang=\"textPanel.deleteText\"> Delete Text </button>\n        </div>\n        <div id=\"image-panel\" style=\"display: none\">\n          <div class=\"details-name editable-text\" id=\"image-title\">Image</div>\n          <div class=\"style-row\" style=\"margin-top: 10px\">\n            <label data-lang=\"images.opacity\">Opacity:</label>\n            <input type=\"range\" id=\"image-panel-opacity\" min=\"10\" max=\"100\" value=\"100\" style=\"flex: 1\">\n            <span id=\"image-panel-opacity-val\" style=\"min-width: 40px; text-align: right\">100%</span>\n          </div>\n          <div class=\"style-row\">\n            <label data-lang=\"images.size\">Size:</label>\n            <input type=\"range\" id=\"image-panel-size\" min=\"20\" max=\"200\" value=\"100\" style=\"flex: 1\">\n            <span id=\"image-panel-size-val\" style=\"min-width: 40px; text-align: right\">100%</span>\n          </div>\n          <div class=\"style-row\">\n            <label data-lang=\"images.border\">Border:</label>\n            <select id=\"image-panel-border\" style=\"flex: 1;padding: 6px 10px;background: var(--panel);border: 1px solid var(--edge-main);border-radius: 4px;color: var(--text-main);cursor: pointer;\">\n              <option value=\"none\" data-lang=\"images.none\">None</option>\n              <option value=\"thin\" data-lang=\"images.thin\">Thin</option>\n              <option value=\"medium\" data-lang=\"images.medium\">Medium</option>\n              <option value=\"thick\" data-lang=\"images.thick\">Thick</option>\n            </select>\n          </div>\n          <div class=\"style-row\">\n            <label data-lang=\"images.shadow\">Shadow:</label>\n            <select id=\"image-panel-shadow\" style=\"flex: 1;padding: 6px 10px;background: var(--panel);border: 1px solid var(--edge-main);border-radius: 4px;color: var(--text-main);cursor: pointer;\">\n              <option value=\"none\" data-lang=\"images.none\">None</option>\n              <option value=\"small\" data-lang=\"images.small\">Small</option>\n              <option value=\"medium\" data-lang=\"images.medium\">Medium</option>\n              <option value=\"large\" data-lang=\"images.large\">Large</option>\n            </select>\n          </div>\n          <div class=\"section-label\" data-lang=\"images.imageNotes\">Image Notes</div>\n          <div class=\"notes-feed\" id=\"image-notes\"></div>\n          <button id=\"add-image-note\" style=\"margin-top: 10px;padding: 8px 16px;background: var(--accent);color: var(--bg);border: none;border-radius: 6px;cursor: pointer;font-size: clamp(14px, 1.6vw, 18px);font-weight: 600;width: 100%;\" data-lang=\"ui.buttons.addNote\">+ Add Note</button>\n          <button id=\"delete-image-panel\" style=\"margin-top: 15px;padding: 10px 16px;background: var(--danger);color: #fff;border: none;border-radius: 6px;cursor: pointer;font-size: clamp(14px, 1.6vw, 18px);font-weight: 600;\" data-lang=\"images.delete\">Delete Image</button>\n        </div>\n        <details class=\"style-section\" open=\"\">\n          <summary data-lang=\"ui.sections.pageLayout\">Page Layout</summary>\n          <div class=\"style-content\">\n            <div style=\"padding: 12px; background: var(--panel-alt); border-radius: 6px; color: var(--text-soft); font-size: 13px; line-height: 1.6;\">\n              <strong style=\"color: var(--accent); display: block; margin-bottom: 8px;\" data-lang=\"pageLayoutHelp.dragToResize\">Drag to Resize:</strong>\n              • <strong data-lang=\"pageLayoutHelp.header\">Header:</strong> <span data-lang=\"pageLayoutHelp.headerDesc\">Drag the bottom edge of the header bar</span><br>\n              • <strong data-lang=\"pageLayoutHelp.sidebar\">Sidebar:</strong> <span data-lang=\"pageLayoutHelp.sidebarDesc\">Drag the left edge of the sidebar panel</span><br>\n              • <strong data-lang=\"pageLayoutHelp.mobile\">Mobile:</strong> <span data-lang=\"pageLayoutHelp.mobileDesc\">Drag the top edge of the footer panel</span><br>\n              <div style=\"margin-top: 8px; padding-top: 8px; border-top: 1px solid var(--edge-main); font-size: 12px;\" data-lang=\"pageLayoutHelp.hoverNote\">\n                Hover over panel edges to see resize handles\n              </div>\n            </div>\n          </div>\n        </details>\n      </aside>\n    </main>\n    <script id=\"nodes-json\" type=\"application/json\">{}</script>\n    <script id=\"topology-state\" type=\"application/json\">{\n  \"nodeData\": {},\n  \"edgeData\": {\n    \"list\": []\n  },\n  \"rectData\": {\n    \"list\": []\n  },\n  \"textData\": {\n    \"list\": []\n  },\n  \"edgeLegend\": {},\n  \"zoneLegend\": {},\n  \"zonePresets\": {},\n  \"nodePositions\": {},\n  \"nodeSizes\": {},\n  \"nodeStyles\": {},\n  \"page\": {\n    \"title\": \"The One File\",\n    \"background\": \"\",\n    \"topbarBg\": \"rgba(9, 12, 20, 0.9)\",\n    \"topbarBorder\": \"#1f2533\",\n    \"panel\": \"#0b0e13\",\n    \"panelAlt\": \"#10141b\",\n    \"accent\": \"#4fd1c5\",\n    \"sidebarBg\": \"#10141b\",\n    \"btnBg\": \"#0b0e13\",\n    \"btnText\": \"#e2e8f0\",\n    \"tagFill\": \"#1e293b\",\n    \"tagText\": \"#e2e8f0\",\n    \"tagBorder\": \"#475569\",\n    \"inputBg\": \"#0b0e13\",\n    \"inputText\": \"#e2e8f0\",\n    \"inputBorder\": \"#1f2937\",\n    \"inputFont\": \"Inter, system-ui, sans-serif\",\n    \"inputFontSize\": 14,\n    \"toolbarBg\": \"#0f172a\",\n    \"toolbarBorder\": \"#1f2937\",\n    \"toolbarText\": \"#94a3b8\",\n    \"toolbarBtnBg\": \"#0b0e13\",\n    \"toolbarBtnText\": \"#e2e8f0\",\n    \"minimapDots\": \"#94a3b8\",\n    \"canvasHintEnabled\": true,\n    \"canvasHintText\": \"\",\n    \"canvasHintBg\": \"#0f172a\",\n    \"canvasHintColor\": \"#94a3b8\",\n    \"danger\": \"#f56565\",\n    \"textMain\": \"#e2e8f0\",\n    \"textSoft\": \"#94a3b8\",\n    \"topbarHeight\": 103,\n    \"sidebarWidth\": 350,\n    \"mobileFooterHeight\": 40,\n    \"sidebarCollapsed\": false,\n    \"nodeFill\": \"#1e293b\",\n    \"nodeStroke\": \"#475569\",\n    \"nodeTitle\": \"#e2e8f0\",\n    \"nodeSub\": \"#94a3b8\",\n    \"nodeTitleSize\": 41,\n    \"nodeSubSize\": 27,\n    \"nodeFont\": \"monospace\",\n    \"defaultEdge\": \"#475569\",\n    \"selectionHandle\": \"#f59e0b\",\n    \"selectionHandleSize\": 8,\n    \"groupIndicator\": \"#4fd1c5\",\n    \"canvasGradientTop\": \"#1e2532\",\n    \"canvasGradientBottom\": \"#050608\",\n    \"canvasBorder\": \"#475569\",\n    \"canvasGrid\": \"#475569\",\n    \"canvasGridSize\": 50,\n    \"rackFrameFill\": \"#0f172a\",\n    \"rackFrameStroke\": \"#4fd1c5\",\n    \"rackLineColor\": \"#475569\",\n    \"rackTextColor\": \"#4fd1c5\",\n    \"viewOnly\": false\n  },\n  \"canvas\": {\n    \"zoom\": 1,\n    \"panX\": 0,\n    \"panY\": 0\n  },\n  \"savedTopologyView\": {\n    \"zoom\": 0.9325110211947125,\n    \"panX\": -563.7108933103631,\n    \"panY\": -561.6887674556383\n  },\n  \"documentTabs\": [\n    {\n      \"id\": \"main\",\n      \"name\": \"Main Topology\",\n      \"nodes\": {},\n      \"edges\": {\n        \"list\": []\n      },\n      \"positions\": {},\n      \"sizes\": {},\n      \"styles\": {},\n      \"legend\": {},\n      \"rects\": {\n        \"list\": []\n      },\n      \"texts\": {\n        \"list\": []\n      },\n      \"pageState\": {\n        \"title\": \"The One File\",\n        \"background\": \"\",\n        \"topbarBg\": \"rgba(9, 12, 20, 0.9)\",\n        \"topbarBorder\": \"#1f2533\",\n        \"panel\": \"#0b0e13\",\n        \"panelAlt\": \"#10141b\",\n        \"accent\": \"#4fd1c5\",\n        \"sidebarBg\": \"#10141b\",\n        \"btnBg\": \"#0b0e13\",\n        \"btnText\": \"#e2e8f0\",\n        \"tagFill\": \"#1e293b\",\n        \"tagText\": \"#e2e8f0\",\n        \"tagBorder\": \"#475569\",\n        \"inputBg\": \"#0b0e13\",\n        \"inputText\": \"#e2e8f0\",\n        \"inputBorder\": \"#1f2937\",\n        \"inputFont\": \"Inter, system-ui, sans-serif\",\n        \"inputFontSize\": 14,\n        \"toolbarBg\": \"#0f172a\",\n        \"toolbarBorder\": \"#1f2937\",\n        \"toolbarText\": \"#94a3b8\",\n        \"toolbarBtnBg\": \"#0b0e13\",\n        \"toolbarBtnText\": \"#e2e8f0\",\n        \"minimapDots\": \"#94a3b8\",\n        \"canvasHintEnabled\": true,\n        \"canvasHintText\": \"\",\n        \"canvasHintBg\": \"#0f172a\",\n        \"canvasHintColor\": \"#94a3b8\",\n        \"danger\": \"#f56565\",\n        \"textMain\": \"#e2e8f0\",\n        \"textSoft\": \"#94a3b8\",\n        \"topbarHeight\": 103,\n        \"sidebarWidth\": 350,\n        \"mobileFooterHeight\": 40,\n        \"sidebarCollapsed\": false,\n        \"nodeFill\": \"#1e293b\",\n        \"nodeStroke\": \"#475569\",\n        \"nodeTitle\": \"#e2e8f0\",\n        \"nodeSub\": \"#94a3b8\",\n        \"nodeTitleSize\": 41,\n        \"nodeSubSize\": 27,\n        \"nodeFont\": \"monospace\",\n        \"defaultEdge\": \"#475569\",\n        \"selectionHandle\": \"#f59e0b\",\n        \"selectionHandleSize\": 8,\n        \"groupIndicator\": \"#4fd1c5\",\n        \"canvasGradientTop\": \"#1e2532\",\n        \"canvasGradientBottom\": \"#050608\",\n        \"canvasBorder\": \"#475569\",\n        \"canvasGrid\": \"#475569\",\n        \"canvasGridSize\": 50,\n        \"rackFrameFill\": \"#0f172a\",\n        \"rackFrameStroke\": \"#4fd1c5\",\n        \"rackLineColor\": \"#475569\",\n        \"rackTextColor\": \"#4fd1c5\",\n        \"viewOnly\": false\n      }\n    }\n  ],\n  \"currentTabIndex\": 0,\n  \"encryptedSections\": {},\n  \"auditLog\": []\n}</script>\n    <script id=\"lang-json\" type=\"application/json\">{\n  \"_meta\": {\n    \"name\": \"English\",\n    \"code\": \"en\",\n    \"version\": \"1.0\",\n    \"rtl\": false,\n    \"edition\": \"core\"\n  },\n  \"modes\": {\n    \"network\": { \"name\": \"Network Topology\", \"node\": \"Node\", \"nodes\": \"Nodes\", \"connection\": \"Connection\", \"connections\": \"Connections\", \"add\": \"Add Node\", \"addRack\": \"Add Rack\", \"rack\": \"Rack\", \"racks\": \"Racks\", \"subtitle\": \"IP / Subtitle\", \"subtitlePlaceholder\": \"e.g. 192.168.1.100\", \"tags\": \"Tags\", \"tagsPlaceholder\": \"e.g. Docker, nginx, WG: vpn\", \"rackSubtitle\": \"IP / Network Range\", \"rackSubtitlePlaceholder\": \"e.g. 192.168.1.0/24\", \"rackTags\": \"Tags\", \"rackTagsPlaceholder\": \"e.g. Production, Data Center 1\" },\n    \"mindmap\": { \"name\": \"Mind Map\", \"node\": \"Idea\", \"nodes\": \"Ideas\", \"connection\": \"Link\", \"connections\": \"Links\", \"add\": \"Add Idea\", \"addRack\": \"Add Folder\", \"rack\": \"Folder\", \"racks\": \"Folders\", \"subtitle\": \"Description\", \"subtitlePlaceholder\": \"e.g. Main concept\", \"tags\": \"Keywords\", \"tagsPlaceholder\": \"e.g. important, research, todo\", \"rackSubtitle\": \"Description\", \"rackSubtitlePlaceholder\": \"e.g. Folder description\", \"rackTags\": \"Keywords\", \"rackTagsPlaceholder\": \"e.g. category, priority\" },\n    \"sports\": { \"name\": \"Sports Playbook\", \"node\": \"Player\", \"nodes\": \"Players\", \"connection\": \"Route\", \"connections\": \"Routes\", \"add\": \"Add Player\", \"addRack\": \"Add Folder\", \"rack\": \"Folder\", \"racks\": \"Folders\", \"subtitle\": \"Position / Number\", \"subtitlePlaceholder\": \"e.g. QB, #12\", \"tags\": \"Attributes\", \"tagsPlaceholder\": \"e.g. offense, starter, captain\", \"rackSubtitle\": \"Description\", \"rackSubtitlePlaceholder\": \"e.g. Formation name\", \"rackTags\": \"Attributes\", \"rackTagsPlaceholder\": \"e.g. offense, defense\" },\n    \"smarthome\": { \"name\": \"Smart Home\", \"node\": \"Device\", \"nodes\": \"Devices\", \"connection\": \"Connection\", \"connections\": \"Connections\", \"add\": \"Add Device\", \"addRack\": \"Add Room View\", \"rack\": \"Room View\", \"racks\": \"Room Views\", \"subtitle\": \"Model / Location\", \"subtitlePlaceholder\": \"e.g. Nest Thermostat, Living Room\", \"tags\": \"Groups\", \"tagsPlaceholder\": \"e.g. lighting, security, automation\", \"rackSubtitle\": \"Description\", \"rackSubtitlePlaceholder\": \"e.g. Room description\", \"rackTags\": \"Groups\", \"rackTagsPlaceholder\": \"e.g. upstairs, main floor\" },\n    \"floorplan\": { \"name\": \"Floor Plan\", \"node\": \"Element\", \"nodes\": \"Elements\", \"connection\": \"Connection\", \"connections\": \"Connections\", \"add\": \"Add Element\", \"addRack\": \"Add Room View\", \"rack\": \"Room View\", \"racks\": \"Room Views\", \"subtitle\": \"Label / Dimensions\", \"subtitlePlaceholder\": \"e.g. 12x14 ft\", \"tags\": \"Categories\", \"tagsPlaceholder\": \"e.g. furniture, fixture, wall\", \"rackSubtitle\": \"Description\", \"rackSubtitlePlaceholder\": \"e.g. Room dimensions\", \"rackTags\": \"Categories\", \"rackTagsPlaceholder\": \"e.g. bedroom, bathroom\" }\n  },\n  \"canvas\": {\n    \"grid\": \"Standard Grid\",\n    \"dots\": \"Dot Grid\",\n    \"blueprint\": \"Blueprint Grid\",\n    \"basketball\": \"Basketball Court\",\n    \"football\": \"Football Field\",\n    \"soccer\": \"Soccer Pitch\",\n    \"hockey\": \"Hockey Rink\",\n    \"baseball\": \"Baseball Diamond\",\n    \"tennis\": \"Tennis Court\",\n    \"none\": \"No Grid\"\n  },\n  \"shapes\": {\n    \"categories\": {\n      \"basic\": \"Basic Shapes\",\n      \"computers\": \"Computers & Devices\",\n      \"network\": \"Network Equipment\",\n      \"cloud\": \"Cloud & Services\",\n      \"security\": \"Security & Monitoring\",\n      \"smarthome\": \"Smart Home\",\n      \"sports\": \"Sports\",\n      \"rack\": \"Rack Equipment\"\n    },\n    \"items\": {\n      \"circle\": \"Circle\", \"square\": \"Square\", \"rectangle\": \"Rectangle\", \"triangle\": \"Triangle\",\n      \"hexagon\": \"Hexagon\", \"diamond\": \"Diamond\", \"star\": \"Star\", \"stop-sign\": \"Stop Sign\",\n      \"octagon\": \"Octagon\", \"pentagon\": \"Pentagon\", \"cross\": \"Cross\", \"rounded-square\": \"Rounded Square\",\n      \"pill\": \"Pill\", \"parallelogram\": \"Parallelogram\", \"trapezoid\": \"Trapezoid\",\n      \"server\": \"Server\", \"pc\": \"PC / Desktop\", \"laptop\": \"Laptop\", \"phone\": \"Phone / Mobile\",\n      \"printer\": \"Printer\", \"pi\": \"Raspberry Pi\", \"sensor\": \"Sensor / IoT\",\n      \"router\": \"Router\", \"switch\": \"Switch\", \"firewall\": \"Firewall\", \"access-point\": \"Access Point\",\n      \"load-balancer\": \"Load Balancer\", \"gateway\": \"Gateway\", \"vpn\": \"VPN / Tunnel\", \"nas\": \"NAS / Storage\",\n      \"cloud\": \"Cloud\", \"database\": \"Database\", \"docker\": \"Docker\", \"container\": \"Container\",\n      \"vm\": \"Virtual Machine\", \"kubernetes\": \"Kubernetes\", \"api\": \"API / Endpoint\",\n      \"queue\": \"Queue / Message\", \"lambda\": \"Lambda / Function\", \"bucket\": \"Bucket / S3\",\n      \"shield\": \"Shield\", \"camera\": \"Camera / CCTV\", \"monitor\": \"Monitor / Dashboard\",\n      \"thermostat\": \"Thermostat\", \"doorbell\": \"Video Doorbell\", \"smart-lock\": \"Smart Lock\",\n      \"smart-bulb\": \"Smart Bulb\", \"smart-plug\": \"Smart Plug\", \"smart-speaker\": \"Smart Speaker\",\n      \"smart-tv\": \"Smart TV\", \"hub\": \"Smart Hub\", \"smoke-detector\": \"Smoke Detector\",\n      \"motion-sensor\": \"Motion Sensor\", \"garage\": \"Garage Door\", \"sprinkler\": \"Sprinkler\", \"vacuum\": \"Robot Vacuum\",\n      \"basketball-ball\": \"Basketball\", \"football-ball\": \"Football\", \"soccer-ball\": \"Soccer Ball\",\n      \"hockey-puck\": \"Hockey Puck\", \"baseball-ball\": \"Baseball\", \"tennis-ball\": \"Tennis Ball\",\n      \"volleyball\": \"Volleyball\", \"rugby-ball\": \"Rugby Ball\", \"golf-ball\": \"Golf Ball\",\n      \"frisbee\": \"Frisbee\", \"cricket-ball\": \"Cricket Ball\", \"lacrosse-stick\": \"Lacrosse Stick\",\n      \"golf-flag\": \"Golf Hole Flag\",\n      \"tactical-x\": \"X (Defender)\", \"tactical-o\": \"O (Offensive)\",\n      \"tactical-star\": \"Star (Key Player)\",\n      \"patch-panel\": \"Patch Panel\", \"ups\": \"UPS\", \"pdu\": \"PDU\", \"rack-shelf\": \"Shelf\",\n      \"blank-panel\": \"Blank Panel\", \"cable-management\": \"Cable Management\", \"kvm\": \"KVM\"\n    }\n  },\n  \"ui\": {\n    \"defaults\": {\n      \"newNode\": \"New {node}\",\n      \"newRack\": \"New {rack}\",\n      \"newText\": \"New Text\"\n    },\n    \"buttons\": {\n      \"save\": \"Save\", \"cancel\": \"Cancel\", \"delete\": \"Delete\", \"close\": \"Close\",\n      \"add\": \"Add\", \"addNote\": \"+ Add Note\", \"edit\": \"Edit\", \"undo\": \"Undo\", \"redo\": \"Redo\",\n      \"import\": \"Import\", \"export\": \"Export\", \"settings\": \"Settings\",\n      \"startMapping\": \"Start Mapping\", \"skip\": \"Skip\", \"apply\": \"Apply\",\n      \"clearAll\": \"Clear All\", \"reset\": \"Reset\", \"saveChanges\": \"Save Changes\",\n      \"addNode\": \"Add {node}\", \"addRack\": \"Add {rack}\", \"editNode\": \"Edit {node}\",\n      \"deleteNode\": \"Delete {node}\", \"exportLang\": \"Export Language File\",\n      \"importLang\": \"Import Language File\", \"resetLang\": \"Reset to English\",\n      \"editStrings\": \"Edit Strings\", \"applyRoutingAll\": \"Apply Routing to All Connections\",\n      \"saveHtml\": \"Save HTML\", \"saveFile\": \"Save File\", \"printBW\": \"Print B&W\",\n      \"printColorFull\": \"Print Full Color\", \"printColorWhite\": \"Print White BG\", \"printColorBlack\": \"Print Black BG\",\n      \"exportJson\": \"Export JSON\", \"exportCsv\": \"Export CSV\", \"exportMd\": \"Export Markdown\",\n      \"exportPng\": \"Export PNG\", \"exportSvg\": \"Export SVG\", \"exportVideo\": \"Export Video\",\n      \"importJson\": \"Import JSON\", \"importMd\": \"Import Markdown\",\n      \"exportHelp\": \"Export Help\", \"importHelp\": \"Import Help\",\n      \"layers\": \"Layers\", \"notes\": \"Notes\", \"auditLog\": \"Audit Log\", \"portMap\": \"Port Map\",\n      \"hideAll\": \"Hide All\", \"showAll\": \"Show All\", \"clearLog\": \"Clear Log\",\n      \"addNote\": \"+ Add Note\", \"addTab\": \"+ Add Tab\", \"addTag\": \"+ Add Tag\", \"encrypt\": \"Encrypt\", \"decrypt\": \"Decrypt\",\n      \"recording\": \"Recording\", \"play\": \"Play\", \"pause\": \"Pause\", \"stop\": \"Stop\",\n      \"drawMode\": \"Draw\", \"textMode\": \"Text\", \"rectMode\": \"Rect\", \"freeDrawMode\": \"Draw\",\n      \"zoomIn\": \"Zoom In\", \"zoomOut\": \"Zoom Out\", \"fitView\": \"Fit View\", \"resetView\": \"Reset View\",\n      \"group\": \"Group\", \"ungroup\": \"Ungroup\", \"bringToFront\": \"Bring to Front\", \"sendToBack\": \"Send to Back\",\n      \"duplicate\": \"Duplicate\", \"selectAll\": \"Select All\", \"deselectAll\": \"Deselect All\",\n      \"copyStyle\": \"Copy Style\", \"pasteStyle\": \"Paste Style\",\n      \"clearHistory\": \"Clear History\", \"createSnapshot\": \"Create Snapshot\",\n      \"del\": \"Del\", \"done\": \"Done\"\n    },\n    \"labels\": {\n      \"name\": \"Name\", \"ip\": \"IP / Subtitle\", \"tags\": \"Tags\", \"category\": \"Category\",\n      \"shape\": \"Shape\", \"icon\": \"Icon / Shape\", \"theme\": \"Theme\", \"mode\": \"Mode\",\n      \"canvasStyle\": \"Canvas Style\", \"themePreset\": \"Theme Preset\",\n      \"mainBackground\": \"Main Background\", \"accentColor\": \"Accent Color\",\n      \"primaryText\": \"Primary Text\", \"secondaryText\": \"Secondary Text\",\n      \"viewOnly\": \"View Only (disable editing)\", \"sidebar\": \"Sidebar / Mobile Footer\",\n      \"modalBg\": \"Modal Window Background\", \"dangerButtons\": \"Danger Buttons\",\n      \"tagFill\": \"Tag(s) Fill\", \"tagText\": \"Tag(s) Text\", \"tagBorder\": \"Tag(s) Border\",\n      \"background\": \"Background\", \"border\": \"Border\", \"buttonFill\": \"Button Fill\",\n      \"buttonText\": \"Button Text\", \"gridLines\": \"Grid Lines\", \"gridSize\": \"Grid Size\",\n      \"showGrid\": \"Show Grid\", \"bgTop\": \"Background Top\", \"bgBottom\": \"Background Bottom\",\n      \"solidBg\": \"Solid Background\", \"frameFill\": \"Frame Fill\", \"frameBorder\": \"Frame Border\",\n      \"uLabels\": \"U Labels\", \"text\": \"Text\", \"minimapDots\": \"Minimap Dots\",\n      \"fill\": \"Fill\", \"titleColor\": \"Title Color\", \"titleSize\": \"Title Size\",\n      \"subtitleColor\": \"Subtitle Color\", \"subtitleSize\": \"Subtitle Size\", \"font\": \"Font\",\n      \"defaultColor\": \"Default Color\", \"defaultRouting\": \"Default Routing\",\n      \"animationStyle\": \"Animation Style\", \"animateDirections\": \"Animate Directions\",\n      \"animationSpeed\": \"Animation Speed\", \"showPortLabels\": \"Show Port Labels\", \"allAnimations\": \"All Animations\",\n      \"sweepPan\": \"Sweep (Pan)\", \"pulseBreathe\": \"Pulse (Breathe)\", \"ringsEmanate\": \"Rings (Emanate)\",\n      \"spinRotate\": \"Spin (Rotate)\", \"connectionsFlow\": \"Connections (Flow)\",\n      \"cameras\": \"Cameras\", \"doorbells\": \"Doorbells\", \"motionSensors\": \"Motion Sensors\",\n      \"smokeDetectors\": \"Smoke Detectors\", \"wifiApRouter\": \"WiFi/AP/Router\",\n      \"sensorsIot\": \"Sensors/IoT\", \"sprinklers\": \"Sprinklers\",\n      \"allZones\": \"All Zones\", \"resizeHandleColor\": \"Resize Handle Color\",\n      \"resizeHandleSize\": \"Resize Handle Size\", \"groupedIconOutline\": \"Grouped Icon Outline\",\n      \"multiselectFill\": \"Multiselect Fill Color (Desktop)\",\n      \"multiselectFillOpacity\": \"Multiselect Fill Opacity (Desktop)\",\n      \"multiselectBorder\": \"Multiselect Border Color (Desktop)\",\n      \"multiselectBorderWidth\": \"Multiselect Border Width (Desktop)\",\n      \"multiselectBorderStyle\": \"Multiselect Border Style (Desktop)\",\n      \"fontSize\": \"Font Size\", \"enabled\": \"Enabled\", \"textColor\": \"Text Color\",\n      \"customText\": \"Custom Text (HTML)\", \"language\": \"Language\", \"currentLanguage\": \"Current:\",\n      \"tagsComma\": \"Tags (comma separated)\", \"networkRange\": \"IP / Network Range (optional)\",\n      \"layer\": \"Layer\", \"mac\": \"MAC Address\", \"vendor\": \"Vendor\", \"model\": \"Model\",\n      \"serialNumber\": \"Serial Number\", \"firmware\": \"Firmware\", \"location\": \"Location\",\n      \"owner\": \"Owner\", \"purchaseDate\": \"Purchase Date\", \"warranty\": \"Warranty\",\n      \"notes\": \"Notes\", \"lineNotes\": \"Line Notes\", \"zoneNotes\": \"Zone Notes\",\n      \"color\": \"Color\", \"width\": \"Width\", \"style\": \"Style\", \"routing\": \"Routing\",\n      \"direction\": \"Direction\", \"label\": \"Label\", \"labelPosition\": \"Label Position\",\n      \"fromPort\": \"From Port\", \"toPort\": \"To Port\", \"waypoints\": \"Waypoints\", \"bandwidth\": \"Bandwidth\",\n      \"latency\": \"Latency\", \"protocol\": \"Protocol\", \"vlan\": \"VLAN\",\n      \"rackCapacity\": \"Rack Capacity\", \"rackPosition\": \"Rack Position (U)\",\n      \"size\": \"Size\", \"sizeHeight\": \"Height (U)\", \"opacity\": \"Opacity\",\n      \"rotation\": \"Rotation\", \"zIndex\": \"Z-Index\", \"locked\": \"Locked\",\n      \"visible\": \"Visible\", \"animated\": \"Animated\", \"animationType\": \"Animation Type\",\n      \"zoneAngle\": \"Zone Angle\", \"zoneRange\": \"Zone Range\", \"zoneColor\": \"Zone Color\",\n      \"zoneOpacity\": \"Zone Opacity\", \"showZone\": \"Show Zone\",\n      \"pageLayout\": \"Page Layout\", \"pageWidth\": \"Page Width\", \"pageHeight\": \"Page Height\",\n      \"orientation\": \"Orientation\", \"landscape\": \"Landscape\", \"portrait\": \"Portrait\",\n      \"margin\": \"Margin\", \"padding\": \"Padding\", \"scale\": \"Scale\",\n      \"timestamp\": \"Timestamp\", \"action\": \"Action\", \"details\": \"Details\",\n      \"fromDevice\": \"From Device\", \"toDevice\": \"To Device\", \"description\": \"Description\"\n    },\n    \"sections\": {\n      \"mappingModeTheme\": \"Mapping Mode & Theme\", \"viewOnlyMode\": \"View Only Mode\",\n      \"moreThemeOptions\": \"More Theme Options\", \"topBar\": \"Top Bar\",\n      \"mainCanvas\": \"Main Canvas\", \"rackCanvas\": \"Rack Canvas\", \"canvasToolbars\": \"Canvas Toolbars\",\n      \"nodes\": \"Nodes\", \"connections\": \"Connections\", \"animationsZones\": \"Animations & Zones\",\n      \"groupsEditing\": \"Groups & Editing\", \"inputsDropdowns\": \"Inputs & Dropdowns\",\n      \"welcomeMessage\": \"Welcome Message\", \"dangerZone\": \"Danger Zone\",\n      \"byType\": \"By Type\", \"byCategory\": \"By Category\", \"zoneSettings\": \"Zone (Animation Cone) Settings\",\n      \"pageLayout\": \"Page Layout\", \"nodeDetails\": \"Node Details\", \"connectionDetails\": \"Connection Details\",\n      \"appearance\": \"Appearance\", \"position\": \"Position\", \"advanced\": \"Advanced\",\n      \"metadata\": \"Metadata\", \"networkInfo\": \"Network Info\", \"deviceInfo\": \"Device Info\"\n    },\n    \"modals\": {\n      \"settings\": \"Settings\", \"addNode\": \"Add New {node}\", \"addRack\": \"Add New {rack}\",\n      \"editNode\": \"Edit {node}\", \"editRack\": \"Edit {rack}\", \"confirmDelete\": \"Confirm Delete\",\n      \"languageEditor\": \"Language Editor\", \"welcome\": \"Welcome\",\n      \"layerVisibility\": \"Layer Visibility\", \"notes\": \"Notes\", \"notesHub\": \"Notes Hub\", \"auditLog\": \"Audit Log\",\n      \"portMap\": \"Port Map\", \"export\": \"Export\", \"import\": \"Import\",\n      \"clearAllNodes\": \"Clear All Nodes\", \"recording\": \"Recording\", \"recordings\": \"Recordings\",\n      \"saveInfo\": \"Save Info\", \"howToSave\": \"How to Save\", \"keyboardShortcuts\": \"Keyboard Shortcuts\",\n      \"importExport\": \"Import/Export\", \"help\": \"Help\", \"tabs\": \"Tabs\", \"versionHistory\": \"Version History\",\n      \"error\": \"Error\", \"rackPlacementError\": \"Rack Placement Error\"\n    },\n    \"placeholders\": {\n      \"nodeName\": \"e.g. web-server\", \"nodeIp\": \"e.g. 192.168.1.100\",\n      \"tags\": \"e.g. Docker, nginx, WG: vpn\", \"rackName\": \"e.g. Rack-A, DC1-R01\",\n      \"rackIp\": \"e.g. 192.168.1.0/24\", \"rackTags\": \"e.g. Production, Data Center 1\",\n      \"search\": \"Search...\", \"searchStrings\": \"Search strings...\",\n      \"leaveEmpty\": \"Leave empty for default\", \"enterNote\": \"Enter note...\",\n      \"filterAudit\": \"Filter audit log...\", \"filterNodes\": \"Filter nodes...\",\n      \"newTabName\": \"New tab name...\",\n      \"fromPort\": \"e.g. eth0, gi0/1\", \"toPort\": \"e.g. eth1, gi0/2\", \"enterText\": \"Enter text...\"\n    },\n    \"tooltips\": {\n      \"addNode\": \"Add new {node}\", \"addRack\": \"Add new {rack}\",\n      \"undo\": \"Undo (Ctrl+Z)\", \"redo\": \"Redo (Ctrl+Y)\",\n      \"settings\": \"Settings\", \"saveTheme\": \"Save current theme\", \"deleteTheme\": \"Delete theme\",\n      \"toggleLayers\": \"Toggle layer visibility\", \"manageNotes\": \"Manage encrypted notes\",\n      \"viewAuditLog\": \"View audit log\", \"viewPortMap\": \"View port map\",\n      \"zoomIn\": \"Zoom in\", \"zoomOut\": \"Zoom out\", \"fitView\": \"Fit all to view\",\n      \"resetView\": \"Reset zoom and pan\", \"toggleMinimap\": \"Toggle minimap\",\n      \"toggleLegend\": \"Toggle legend\", \"toggleToolbar\": \"Toggle toolbar\"\n    },\n    \"options\": {\n      \"orthogonal\": \"Orthogonal (90°)\", \"curved\": \"Curved\", \"straight\": \"Straight\",\n      \"flowingArrows\": \"Flowing Arrows\", \"dotArrows\": \"Dot Arrows\",\n      \"allDirections\": \"All Directions\", \"forwardOnly\": \"Forward Only\",\n      \"backwardOnly\": \"Backward Only\", \"bidirectionalOnly\": \"Bidirectional Only\",\n      \"veryFast\": \"Very Fast\", \"fast\": \"Fast\", \"normal\": \"Normal\", \"slow\": \"Slow\", \"verySlow\": \"Very Slow\",\n      \"dashed\": \"Dashed\", \"dotted\": \"Dotted\", \"solid\": \"Solid\",\n      \"default\": \"Default\", \"custom\": \"Custom\", \"customColors\": \"Custom Colors\",\n      \"none\": \"None\", \"auto\": \"Auto\", \"manual\": \"Manual\",\n      \"top\": \"Top\", \"bottom\": \"Bottom\", \"left\": \"Left\", \"right\": \"Right\", \"center\": \"Center\",\n      \"forward\": \"Forward\", \"backward\": \"Backward\", \"both\": \"Both\"\n    },\n    \"stats\": {\n      \"nodesConnections\": \"{nodeCount} {nodes} • {edgeCount} {connections}\"\n    },\n    \"tabs\": {\n      \"all\": \"All\", \"uiGeneral\": \"UI/General\", \"howToSave\": \"How to Save\",\n      \"keyboardShortcuts\": \"Keyboard Shortcuts\", \"importExport\": \"Import/Export\"\n    },\n    \"layers\": {\n      \"network\": {\n        \"layer1\": \"Physical Layer\", \"layer2\": \"Logical Layer\",\n        \"layer3\": \"Security Layer\", \"layer4\": \"Application Layer\"\n      },\n      \"sports\": {\n        \"layer1\": \"Players\", \"layer2\": \"Equipment\",\n        \"layer3\": \"Coaching/Strategy\", \"layer4\": \"Officials\"\n      },\n      \"smarthome\": {\n        \"layer1\": \"Devices\", \"layer2\": \"Security\",\n        \"layer3\": \"Climate\", \"layer4\": \"Entertainment\"\n      },\n      \"floorplan\": {\n        \"layer1\": \"Furniture\", \"layer2\": \"Fixtures\",\n        \"layer3\": \"Utilities\", \"layer4\": \"People/Workstations\"\n      },\n      \"mindmap\": {\n        \"layer1\": \"Ideas\", \"layer2\": \"Tasks\",\n        \"layer3\": \"Notes\", \"layer4\": \"Links\"\n      }\n    },\n    \"rackSizes\": {\n      \"42u\": \"42U (Standard Full Rack)\", \"48u\": \"48U (Large Rack)\",\n      \"24u\": \"24U (Half Rack)\", \"12u\": \"12U (Small/Wall Mount)\", \"6u\": \"6U (Mini Rack)\"\n    },\n    \"auditTypes\": {\n      \"all\": \"All Events\", \"create\": \"Create\", \"update\": \"Update\", \"delete\": \"Delete\",\n      \"import\": \"Import\", \"export\": \"Export\", \"layer\": \"Layer Changes\"\n    },\n    \"fonts\": {\n      \"inter\": \"Inter\", \"arial\": \"Arial\", \"helvetica\": \"Helvetica\",\n      \"georgia\": \"Georgia\", \"monospace\": \"Monospace\"\n    }\n  },\n  \"messages\": {\n    \"confirmClearAll\": \"This will permanently delete everything on the canvas. Continue?\",\n    \"confirmDelete\": \"Are you sure you want to delete this {node}?\",\n    \"confirmDeleteMultiple\": \"Delete {count} selected items?\",\n    \"confirmImport\": \"This will replace all current data. Continue?\",\n    \"importSuccess\": \"Imported successfully\",\n    \"exportSuccess\": \"File exported successfully\",\n    \"invalidFile\": \"Invalid file format\",\n    \"noSelection\": \"Select at least one {node} first\",\n    \"langImportSuccess\": \"Language file imported successfully\",\n    \"langResetSuccess\": \"Language reset to English\",\n    \"langExportSuccess\": \"Language file exported\",\n    \"invalidLangFile\": \"Invalid language file structure\",\n    \"setSolidBgNote\": \"Set solid background to override gradient.\",\n    \"dangerZoneNote\": \"Permanently delete everything on the canvas.\",\n    \"viewOnlyNote\": \"When enabled, all editing of the canvas is disabled. Pan and zoom still work.\",\n    \"welcomeHint\": \"Double click canvas to add {nodes}.<br>Drag from {node} edge to connect.<br>Right-click for more options.\",\n    \"clearAllWarning\": \"This will remove ALL nodes and connections. This cannot be undone until you reload without saving. Are you sure?\",\n    \"clearEverything\": \"Clear Everything\",\n    \"layerToggleNote\": \"Toggle which layers are visible on the canvas\",\n    \"notesEncryptionNote\": \"Notes can also be stored with AES 256 encryption\",\n    \"howToSaveNote\": \"Browsers cannot overwrite local files. Click Save File to download an updated HTML with all changes. Replace your old file to keep edits. An automatic backup is also saved to your browser's local storage and will prompt for recovery if you close without saving.\",\n    \"decryptionNote\": \"Export via JSON in top settings menu after successful password validation to decrypt your data. You can then reimport back into the same file and save!\",\n    \"noNodesYet\": \"No {nodes} yet. Double click canvas or use sidebar to add.\",\n    \"selectRecording\": \"Select a recording to export.\",\n    \"pleaseWait\": \"This may take a moment, please wait...\",\n    \"recoverWork\": \"Recover unsaved work from {date}?\",\n    \"replaceCurrentData\": \"This will replace current data. Continue?\",\n    \"invalidDataFile\": \"Invalid data file. Missing required fields.\",\n    \"noStringsFound\": \"No strings found\",\n    \"manageTopologies\": \"Manage multiple topologies\",\n    \"snapshotLimit\": \"Limit: Snapshots\",\n    \"trackChanges\": \"Track all changes made to your topology\",\n    \"portMapDesc\": \"All connections with port assignments\",\n    \"manageRecordings\": \"Manage recorded movement sequences\",\n    \"rackViewHint\": \"Viewing: {name} | Double click empty space to exit\",\n    \"slotCollisionWarning\": \"Warning: {count} slot collision(s) detected\"\n  },\n  \"themes\": {\n    \"default\": \"Default\", \"slate\": \"Slate\", \"graphite\": \"Graphite\", \"frost\": \"Frost (Light)\",\n    \"synthwave\": \"Synthwave\", \"terminal\": \"Terminal\", \"dracula\": \"Dracula\",\n    \"cobalt\": \"Cobalt\", \"solarized\": \"Solarized\", \"brainwave\": \"Brainwave\",\n    \"neonMint\": \"Neon Mint\", \"velvetDusk\": \"Velvet Dusk\", \"sunburst\": \"Sunburst\"\n  },\n  \"themeGroups\": {\n    \"corporate\": \"Corporate\", \"homelab\": \"Homelab\", \"dev\": \"Dev\", \"mindMap\": \"Mind Map\", \"myThemes\": \"My Themes\"\n  },\n  \"sidebar\": {\n    \"addNode\": \"+ {node}\", \"addRack\": \"+ {rack}\",\n    \"nodePanel\": \"{node} Details\", \"edgePanel\": \"{connection} Details\",\n    \"properties\": \"Properties\", \"style\": \"Style\", \"actions\": \"Actions\"\n  },\n  \"topbar\": {\n    \"untitledTopology\": \"Untitled Topology\", \"saveHtml\": \"Save HTML\",\n    \"export\": \"Export\", \"import\": \"Import\",\n    \"backToTopology\": \"← Back to Topology\", \"tabs\": \"Tabs\", \"snapshots\": \"Snapshots\",\n    \"audit\": \"Audit\", \"ports\": \"Ports\"\n  },\n  \"toolbar\": {\n    \"legend\": \"Legend\", \"legendMini\": \"Connection & Zone Legend\", \"minimap\": \"Minimap\", \"minimapMini\": \"Map\", \"draw\": \"Draw Tools\", \"drawMini\": \"Draw\",\n    \"topology\": \"Topology Tools\", \"topologyMini\": \"Add Connection\"\n  },\n  \"help\": {\n    \"title\": \"Help\",\n    \"tabs\": {\n      \"general\": \"General\", \"desktop\": \"Desktop\", \"mobile\": \"Mobile\", \"formats\": \"Import/Export\", \"recording\": \"Recording\"\n    },\n    \"general\": {\n      \"addNodes\": \"Add Nodes:\", \"addNodesDesc\": \"Click \\\"+ Node\\\" or \\\"+ Rack\\\" in the top menu\",\n      \"connectNodes\": \"Connect Nodes:\", \"connectNodesDesc\": \"Select a node, then use \\\"Add Connection\\\" in the panel\",\n      \"moveNodes\": \"Move Nodes:\", \"moveNodesDesc\": \"Drag nodes to reposition them\",\n      \"enterRackView\": \"Enter Rack View:\", \"enterRackViewDesc\": \"Double click on desktop or Long press on mobile\",\n      \"multiSelect\": \"Multi Select:\", \"multiSelectDesc\": \"Right click (desktop) or double tap (mobile)\",\n      \"panCanvas\": \"Pan Canvas:\", \"panCanvasDesc\": \"Drag empty space or hold Space + drag\",\n      \"zoom\": \"Zoom:\", \"zoomDesc\": \"Scroll wheel or pinch gesture\",\n      \"freeDraw\": \"Free Draw:\", \"freeDrawDesc\": \"Use draw toolbar for drawing lines, boxes/zones, and text\",\n      \"savingEncryption\": \"Saving & Encryption\",\n      \"savingNote\": \"Browsers cannot overwrite local files. Click Save File to download an updated HTML with all changes. Replace your old file to keep edits. An automatic backup is also saved to your browser's local storage and will prompt for recovery if you close without saving.\",\n      \"encryptionNote\": \"Encryption of data:\", \"encryptionDesc\": \"Check \\\"Encrypt\\\" before saving to password protect your data. Beware! No recovery possible without password!!\",\n      \"decryptionNote\": \"Decryption of data:\", \"decryptionDesc\": \"Export via JSON in top settings menu after successful password validation to decrypt your data. You can then reimport back into the same file and save!\"\n    },\n    \"desktop\": {\n      \"navigation\": \"Navigation\", \"management\": \"Management\",\n      \"arrowKeys\": \"Arrow Keys\", \"moveSelected1px\": \"Move selected 1px\",\n      \"shiftArrows\": \"Shift + Arrows\", \"moveSelected10px\": \"Move selected 10px\",\n      \"tabShiftTab\": \"Tab / Shift+Tab\", \"cycleNodes\": \"Cycle through nodes\",\n      \"fKey\": \"F\", \"focusSelected\": \"Focus on selected\",\n      \"spaceDrag\": \"Space + Drag\", \"panCanvas\": \"Pan canvas\",\n      \"lKey\": \"L\", \"lockUnlock\": \"Lock/unlock selected\",\n      \"gKey\": \"G\", \"groupUngroup\": \"Group/ungroup selected\",\n      \"ctrlCV\": \"Ctrl+C / Ctrl+V\", \"copyPaste\": \"Copy / Paste\",\n      \"ctrlD\": \"Ctrl+D\", \"duplicate\": \"Duplicate\",\n      \"ctrlA\": \"Ctrl+A\", \"selectAll\": \"Select all\",\n      \"deleteKey\": \"Delete\", \"deleteSelected\": \"Delete selected\",\n      \"escapeKey\": \"Escape\", \"clearSelection\": \"Clear selection\",\n      \"ctrlZY\": \"Ctrl+Z / Ctrl+Y\", \"undoRedo\": \"Undo / Redo\",\n      \"rightClick\": \"Right Click\", \"multiSelectToggle\": \"Multi select toggle\",\n      \"dblClickRack\": \"Double click rack\", \"enterRackView\": \"Enter rack view\",\n      \"dblClickConnection\": \"Double click connection\", \"addWaypoint\": \"Add waypoint\",\n      \"dblClickEmpty\": \"Double click empty (in rack)\", \"exitRackView\": \"Exit rack view\",\n      \"recording\": \"Recording\",\n      \"rKey\": \"R\", \"startStopRecording\": \"Start/stop real time recording\",\n      \"shiftR\": \"Shift+R\", \"startStopStepRecording\": \"Start/stop step by step recording\",\n      \"spaceKey\": \"Space\", \"addStepOrPlayPause\": \"Add step (step recording) / Play/Pause (playback)\",\n      \"pKey\": \"P\", \"playRecording\": \"Play recording\"\n    },\n    \"mobile\": {\n      \"basicGestures\": \"Basic Gestures\", \"rackView\": \"Rack View\",\n      \"tapNode\": \"Tap node\", \"selectOpenProperties\": \"Select & open properties\",\n      \"tapEmpty\": \"Tap empty\", \"deselectAll\": \"Deselect all\",\n      \"dragNode\": \"Drag node\", \"moveNode\": \"Move node\",\n      \"dragEmpty\": \"Drag empty\", \"panCanvas\": \"Pan canvas\",\n      \"pinch\": \"Pinch\", \"zoomInOut\": \"Zoom in/out\",\n      \"doubleTapNode\": \"Double tap node\", \"addRemoveSelection\": \"Add/remove from selection\",\n      \"longPressRack\": \"Long press rack\", \"enterRackView\": \"Enter rack view\",\n      \"longPressConnection\": \"Long press connection\", \"addWaypoint\": \"Add waypoint\",\n      \"doubleTapEmpty\": \"Double tap empty\", \"exitRackView\": \"Exit rack view\"\n    },\n    \"formats\": {\n      \"exportHelp\": \"Export Help\", \"importHelp\": \"Import Help\",\n      \"html\": \"HTML\", \"htmlDesc\": \"Complete self contained file and the default save method. When in doubt, choose this. Use the Save HTML button for this\", \"htmlImportDesc\": \"Import data from another saved HTML file. Extracts and restores all topology data from a previously exported file.\",\n      \"json\": \"JSON\", \"jsonExportDesc\": \"Full backup. Editable in your favorite text editor\", \"jsonImportDesc\": \"Replaces all data\",\n      \"markdown\": \"Markdown\", \"markdownExportDesc\": \"Full backup. Editable in your favorite text editor\", \"markdownImportDesc\": \"Replaces all data\",\n      \"csv\": \"CSV\", \"csvExportDesc\": \"Full backup in header, nodes in spreadsheet rows\", \"csvImportDesc\": \"Choose full restore or add nodes only. Allows quickly creating nodes with a spreadsheet and importing into an existing save.\",\n      \"png\": \"PNG\", \"pngDesc\": \"Standard image of current canvas tab\",\n      \"svg\": \"SVG\", \"svgDesc\": \"Vector image, scalable and editable image of current canvas tab\"\n    },\n    \"recording\": {\n      \"title\": \"Canvas Recording\",\n      \"overview\": \"Record your canvas activity to create visual documentation, tutorials, or demonstrations of your network topology changes.\",\n      \"twoModes\": \"Two Recording Modes\",\n      \"realtimeTitle\": \"Real time Recording\",\n      \"realtimeDesc\": \"(red ● button) captures the canvas continuously at 10 frames per second as you work. Good for demonstrating workflows.\",\n      \"stepByStepTitle\": \"Step by Step Recording\",\n      \"stepByStepDesc\": \"(green ●+ button) lets you manually capture each frame. Click the green button to start, then press + or Space to capture each step. Each frame plays for 1 second. Good for tutorials where you want precise control over what's shown.\",\n      \"savedInFile\": \"Recordings are saved within your data file for later playback\",\n      \"exportVideo\": \"Can be exported as a video file (WebM format) for sharing\",\n      \"playWhileRecording\": \"Pressing Play while recording will stop and immediately play the new recording\",\n      \"controls\": \"Controls\",\n      \"recordBtn\": \"Record\", \"recordBtnDesc\": \"Start real time recording (captures 10 frames/sec). Press R\",\n      \"stepRecordBtn\": \"Step Record\", \"stepRecordBtnDesc\": \"Start step by step recording. Press Shift+R\",\n      \"addStepBtn\": \"Add Step\", \"addStepBtnDesc\": \"Capture current state as next frame (during step recording). Press Space\",\n      \"playBtn\": \"Play\", \"playBtnDesc\": \"Play back a saved recording. Press P\",\n      \"stopBtn\": \"Stop\", \"stopBtnDesc\": \"Stop recording or playback. Press R or Shift+R again\",\n      \"pauseBtn\": \"Pause\", \"pauseBtnDesc\": \"Pause playback. Press Space\",\n      \"scrubber\": \"Scrubber\", \"scrubberDesc\": \"Seek to any point in the recording\",\n      \"speed\": \"Speed\", \"speedDesc\": \"Adjust playback speed (0.5x, 1x, 2x, 4x)\",\n      \"loop\": \"Loop\", \"loopDesc\": \"Toggle continuous loop playback\",\n      \"manage\": \"Manage\", \"manageDesc\": \"View, rename, export, or delete saved recordings\"\n    }\n  },\n  \"pageLayoutHelp\": {\n    \"dragToResize\": \"Drag to Resize:\",\n    \"header\": \"Header:\", \"headerDesc\": \"Drag the bottom edge of the header bar\",\n    \"sidebar\": \"Sidebar:\", \"sidebarDesc\": \"Drag the left edge of the sidebar panel\",\n    \"mobile\": \"Mobile:\", \"mobileDesc\": \"Drag the top edge of the footer panel\",\n    \"hoverNote\": \"Hover over panel edges to see resize handles\"\n  },\n  \"confirmModal\": {\n    \"confirm\": \"Confirm\",\n    \"deleteLineQuestion\": \"Are you sure you want to delete this line?\"\n  },\n  \"editModal\": {\n    \"editTitle\": \"Edit Title\",\n    \"editName\": \"Edit Name\",\n    \"editIpSubtitle\": \"Edit IP/Subtitle\",\n    \"editTag\": \"Edit Tag\",\n    \"editMacAddress\": \"Edit MAC Address\",\n    \"addTags\": \"Add Tag(s) : comma separated\",\n    \"editRackUnit\": \"Edit Rack Unit\",\n    \"editUHeight\": \"Edit U Height\",\n    \"rackPlacementError\": \"Rack Placement Error\"\n  },\n  \"noteEditor\": {\n    \"title\": \"Edit Note\",\n    \"addNote\": \"Add Note\",\n    \"editNote\": \"Edit Note\",\n    \"viewNote\": \"View Note\"\n  },\n  \"drawToolbar\": {\n    \"filled\": \"Filled\", \"outlined\": \"Outlined\",\n    \"solid\": \"Solid\", \"dashed\": \"Dashed\", \"dotted\": \"Dotted\", \"wall\": \"Wall\",\n    \"noArrows\": \"No arrows\", \"arrowRight\": \"→ Right\", \"arrowLeft\": \"← Left\", \"arrowBoth\": \"↔ Both\",\n    \"undoLastPoint\": \"Undo last point\"\n  },\n  \"topologyToolbar\": {\n    \"addLineTo\": \"Add line to:\",\n    \"noArrows\": \"No arrows\", \"forward\": \"→ Forward\", \"backward\": \"← Backward\", \"bidirectional\": \"↔ Bidirectional\"\n  },\n  \"edgePanel\": {\n    \"customLine\": \"Custom line\",\n    \"animate\": \"Animate\"\n  },\n  \"rectPanel\": {\n    \"zone\": \"Zone\", \"fillColor\": \"Fill Color:\", \"fillOpacity\": \"Fill Opacity:\", \"borderColor\": \"Border Color:\", \"borderWidth\": \"Border Width:\",\n    \"lineStyle\": \"Line Style:\", \"deleteZone\": \"Delete Zone\"\n  },\n  \"textPanel\": {\n    \"textElement\": \"Text Element\", \"content\": \"Content:\", \"fontSizeLabel\": \"Font Size:\", \"colorLabel\": \"Color:\",\n    \"fontWeight\": \"Font Weight:\", \"bold\": \"Bold\", \"semiBold\": \"Semi-Bold\", \"light\": \"Light\",\n    \"fontStyle\": \"Font Style:\", \"italic\": \"Italic\",\n    \"textAlign\": \"Text Align:\", \"textDecoration\": \"Text Decoration:\", \"underline\": \"Underline\", \"strikeThrough\": \"Strike Through\",\n    \"enable\": \"Enable\", \"deleteText\": \"Delete Text\"\n  },\n  \"tabsModal\": {\n    \"manageTopologies\": \"Manage multiple topologies\",\n    \"mainTopology\": \"Main Topology\",\n    \"nodes\": \"nodes\", \"connections\": \"connections\",\n    \"newTabPlaceholder\": \"New tab name...\",\n    \"addTab\": \"+ Add Tab\"\n  },\n  \"auditModal\": {\n    \"auditLogTitle\": \"Audit Log\",\n    \"allEvents\": \"All Events\", \"create\": \"Create\", \"update\": \"Update\", \"delete\": \"Delete\",\n    \"importEvent\": \"Import\", \"exportEvent\": \"Export\", \"layerChanges\": \"Layer Changes\",\n    \"noEvents\": \"No audit events recorded yet\"\n  },\n  \"portMapModal\": {\n    \"portMapTitle\": \"Port Map\",\n    \"exportCsv\": \"Export CSV\"\n  },\n  \"secretEditor\": {\n    \"editNote\": \"Edit Note\",\n    \"enterSensitiveInfo\": \"Enter sensitive information here...\",\n    \"encryptNote\": \"Encrypt Note\",\n    \"noNotesYet\": \"No notes yet\"\n  },\n  \"mobileExport\": {\n    \"pngImage\": \"PNG Image\", \"svgVector\": \"SVG Vector\", \"jsonFullBackup\": \"JSON (Full Backup)\",\n    \"markdownExport\": \"Markdown\", \"csvExport\": \"CSV\"\n  },\n  \"mobileImport\": {\n    \"html\": \"HTML\", \"jsonImport\": \"JSON\", \"markdownImport\": \"Markdown\"\n  },\n  \"recording\": {\n    \"startRecording\": \"Start recording\", \"stop\": \"Stop\", \"playRecording\": \"Play recording\", \"pause\": \"Pause\",\n    \"playbackSpeed\": \"Playback speed\", \"loop\": \"Loop\", \"manageRecordings\": \"Manage recordings\",\n    \"playbackPosition\": \"Playback position\", \"exportVideo\": \"Export Video\",\n    \"selectOrCreate\": \"Select a recording or create a new one\",\n    \"newRecordingPlaceholder\": \"New recording name...\",\n    \"recording\": \"Recording\",\n    \"trailSettings\": \"Motion trail settings\"\n  },\n  \"welcome\": {\n    \"title\": \"Welcome to The One File\",\n    \"subtitle\": \"What are you mapping today?\",\n    \"chooseMode\": \"Choose your mode:\",\n    \"chooseCanvas\": \"Choose canvas style:\",\n    \"chooseTheme\": \"Choose a theme:\",\n    \"customColors\": \"Customize colors:\",\n    \"startMapping\": \"Start Mapping\",\n    \"skip\": \"Skip\",\n    \"canvasStyle\": \"Canvas Style\",\n    \"theme\": \"Theme\",\n    \"colorCustomization\": \"Color Customization\",\n    \"canvas\": {\n      \"grid\": \"Standard Grid\", \"dots\": \"Dot Grid\",\n      \"blueprint\": \"Blueprint Grid\", \"none\": \"No Grid\"\n    },\n    \"modes\": {\n      \"network\": \"Network Topology\", \"networkDesc\": \"Servers, routers, switches\",\n      \"mindmap\": \"Mind Map\", \"mindmapDesc\": \"Ideas, concepts, brainstorming\",\n      \"sports\": \"Sports Playbook\", \"sportsDesc\": \"Plays, formations, positions\",\n      \"smarthome\": \"Smart Home\", \"smarthomeDesc\": \"IoT devices, automation\",\n      \"floorplan\": \"Floor Plan / Blueprint\", \"floorplanDesc\": \"Rooms, layouts, physical spaces\"\n    },\n    \"colors\": {\n      \"mainBg\": \"Top Bar Background\", \"accent\": \"Accent\",\n      \"primaryText\": \"Primary Text\", \"secondaryText\": \"Secondary Text\",\n      \"sidebarPanel\": \"Main Background\", \"modalWindows\": \"Modal Background\",\n      \"dangerButtons\": \"Grid Color\", \"mobileFooter\": \"Sidebar Background\",\n      \"moreStyles\": \"More styles available in main settings panel\"\n    }\n  },\n  \"bulkToolbar\": {\n    \"selected\": \"Selected:\",\n    \"alignLeft\": \"⬅ Left\", \"alignRight\": \"➡ Right\", \"alignTop\": \"⬆ Top\", \"alignBottom\": \"⬇ Bottom\",\n    \"alignLeftShort\": \"Align Left\", \"alignRightShort\": \"Align Right\", \"alignTopShort\": \"Align Top\", \"alignBottomShort\": \"Align Bottom\",\n    \"distributeH\": \"↔ Distribute H\", \"distributeV\": \"↕ Distribute V\",\n    \"distributeHShort\": \"Distribute H\", \"distributeVShort\": \"Distribute V\",\n    \"clone\": \"📋 Clone\", \"cloneAll\": \"Clone All\",\n    \"copyZone\": \"📡 Copy Zone\", \"pasteZone\": \"📡 Paste Zone\", \"toggleZones\": \"📡 Toggle Zones\",\n    \"copyZoneShort\": \"Copy Zone\", \"pasteZoneShort\": \"Paste Zone\", \"toggleZonesShort\": \"Toggle Zones\",\n    \"lockToggle\": \"Lock Toggle\", \"groupToggle\": \"Group Toggle\",\n    \"deleteAll\": \"Delete All\", \"clearSelection\": \"Clear Selection\",\n    \"coverageZones\": \"Coverage Zones\",\n    \"nodesSelected\": \"Nodes Selected\"\n  },\n  \"canvasHints\": {\n    \"scrollZoom\": \"Scroll to zoom\",\n    \"dragPan\": \"Drag to pan\",\n    \"rightClickClone\": \"Right click to clone and align\",\n    \"rightClickMulti\": \"Right click to select multiple\",\n    \"shiftMarquee\": \"Hold Shift + drag mouse for marquee selection\",\n    \"power\": \"You have the power\",\n    \"timeNow\": \"Your time is NOW!\"\n  },\n  \"legend\": {\n    \"connectionLegend\": \"Connection Legend\"\n  },\n  \"nodePanel\": {\n    \"connections\": \"Connections\",\n    \"styling\": \"Styling\",\n    \"screen\": \"Screen:\",\n    \"tablet\": \"Tablet\", \"fold\": \"Fold\",\n    \"fillColor\": \"Fill Color:\", \"borderColor\": \"Border Color:\",\n    \"titleFont\": \"Title Font:\", \"subColor\": \"Sub Color:\", \"subFont\": \"Sub Font:\", \"subSize\": \"Sub Size:\",\n    \"systemUI\": \"System UI\", \"serif\": \"Serif\", \"cursive\": \"Cursive\", \"courier\": \"Courier\", \"times\": \"Times\",\n    \"textPosition\": \"Text Position\",\n    \"nameY\": \"Name Y:\", \"nameX\": \"Name X:\", \"ipY\": \"IP Y:\", \"ipX\": \"IP X:\",\n    \"resetStyles\": \"Reset Styles\",\n    \"nodesInRack\": \"Nodes in Rack\",\n    \"uHeight\": \"U Height:\",\n    \"layer\": \"Layer:\",\n    \"motionTrail\": \"Motion Trail:\",\n    \"assignedRack\": \"Assigned Rack:\",\n    \"rackCapacity\": \"Rack Capacity:\",\n    \"physical\": \"Physical\",\n    \"logical\": \"Logical\",\n    \"security\": \"Security\",\n    \"application\": \"Application\",\n    \"mac\": \"MAC:\",\n    \"rackUnit\": \"Rack Unit:\"\n  },\n  \"langEditor\": {\n    \"tabs\": {\n      \"all\": \"All\", \"modes\": \"Modes\", \"network\": \"Network\", \"mindMap\": \"Mind Map\",\n      \"sports\": \"Sports\", \"smartHome\": \"Smart Home\", \"floorPlan\": \"Floor Plan\",\n      \"ui\": \"UI\", \"shapes\": \"Shapes\", \"messages\": \"Messages\"\n    },\n    \"searchPlaceholder\": \"Search strings...\"\n  },\n  \"auditLog\": {\n    \"allEvents\": \"All Events\", \"nodeOperations\": \"Node Operations\",\n    \"connections\": \"Connections\", \"styleChanges\": \"Style Changes\",\n    \"rackOperations\": \"Rack Operations\", \"layerChanges\": \"Layer Changes\"\n  },\n  \"portMap\": {\n    \"searchPlaceholder\": \"Search devices or ports...\",\n    \"allConnections\": \"All Connections\", \"withPorts\": \"With Ports Only\",\n    \"missingPorts\": \"Missing Ports\", \"noConnections\": \"No connections found\"\n  },\n  \"notes\": {\n    \"noNotes\": \"No notes yet\",\n    \"namePlaceholder\": \"Note name (e.g., 'Root Passwords')...\",\n    \"editNote\": \"Edit Note\",\n    \"contentPlaceholder\": \"Enter sensitive information here...\",\n    \"encryptNote\": \"Encrypt Note\",\n    \"newNoteTitle\": \"New note: {name}\",\n    \"editNoteTitle\": \"Edit note: {name}\"\n  },\n  \"mobileExport\": {\n    \"pngImage\": \"PNG Image\", \"svgVector\": \"SVG Vector\",\n    \"jsonBackup\": \"JSON (Full Backup)\", \"markdown\": \"Markdown\",\n    \"csv\": \"CSV\", \"printBW\": \"Print B&W\", \"printColorFull\": \"Print Full Color\", \"printColorWhite\": \"Print White BG\", \"printColorBlack\": \"Print Black BG\", \"exportHelp\": \"Export Help\"\n  },\n  \"mobileImport\": {\n    \"html\": \"HTML\", \"json\": \"JSON\", \"markdown\": \"Markdown\", \"csv\": \"CSV\", \"importHelp\": \"Import Help\"\n  },\n  \"recordings\": {\n    \"noRecordings\": \"No recordings yet. Press Record to start.\",\n    \"exportVideo\": \"Export Video\"\n  },\n  \"trails\": {\n    \"title\": \"Motion Trails\",\n    \"description\": \"Show movement paths during playback (sports mode recommended)\",\n    \"enableTrails\": \"Enable Trails:\",\n    \"trailLength\": \"Trail Length:\",\n    \"trailStyle\": \"Trail Style:\",\n    \"styleSolid\": \"Solid Fade\",\n    \"styleDashed\": \"Dashed\",\n    \"styleDots\": \"Dots\",\n    \"showArrow\": \"Show Direction Arrow:\",\n    \"perNodeHint\": \"Tip: Enable/disable trails per node in the node panel\"\n  },\n  \"images\": {\n    \"title\": \"Image\",\n    \"addImage\": \"Add Image\",\n    \"dropHint\": \"Drop image here or click to browse\",\n    \"maxImages\": \"Maximum images reached (40)\",\n    \"name\": \"Name:\",\n    \"opacity\": \"Opacity:\",\n    \"border\": \"Border:\",\n    \"shadow\": \"Shadow:\",\n    \"size\": \"Size:\",\n    \"delete\": \"Delete Image\",\n    \"imageNotes\": \"Image Notes\",\n    \"none\": \"None\",\n    \"thin\": \"Thin\",\n    \"medium\": \"Medium\",\n    \"thick\": \"Thick\",\n    \"small\": \"Small\",\n    \"large\": \"Large\"\n  },\n  \"rack\": {\n    \"capacity42\": \"42U (Standard Full Rack)\",\n    \"capacity48\": \"48U (Large Rack)\",\n    \"capacity24\": \"24U (Half Rack)\",\n    \"capacity12\": \"12U (Small/Wall Mount)\",\n    \"capacity6\": \"6U (Mini Rack)\"\n  },\n  \"zone\": {\n    \"coverageZone\": \"Coverage Zone\",\n    \"preset\": \"Preset:\",\n    \"applyPreset\": \"-- Apply Preset --\",\n    \"presets\": {\n      \"securityCam\": \"Security Camera\",\n      \"ptzCam\": \"PTZ Camera\",\n      \"motionDetector\": \"Motion Detector\",\n      \"wifi\": \"WiFi\",\n      \"wifiExtender\": \"WiFi Extender\",\n      \"smokeAlarm\": \"Smoke Alarm\",\n      \"sprinklerArc\": \"Sprinkler Arc\"\n    },\n    \"showZone\": \"Show Zone:\",\n    \"angle\": \"Angle:\",\n    \"distance\": \"Distance:\",\n    \"innerRadius\": \"Inner Radius:\",\n    \"rotation\": \"Rotation:\",\n    \"fill\": \"Fill\",\n    \"border\": \"Border\",\n    \"color\": \"Color:\",\n    \"opacity\": \"Opacity:\",\n    \"gradient\": \"Gradient:\",\n    \"fadeTowardEdge\": \"Fade toward edge\",\n    \"width\": \"Width:\",\n    \"style\": \"Style:\",\n    \"label\": \"Label\",\n    \"text\": \"Text:\",\n    \"labelPlaceholder\": \"e.g. Detection Zone\",\n    \"position\": \"Position:\",\n    \"edge\": \"Edge\",\n    \"outside\": \"Outside\",\n    \"fontSize\": \"Font Size:\",\n    \"fontColor\": \"Font Color:\",\n    \"bold\": \"Bold:\",\n    \"background\": \"Background:\",\n    \"offsetX\": \"Offset X:\",\n    \"offsetY\": \"Offset Y:\",\n    \"animation\": \"Animation\",\n    \"animate\": \"Animate:\",\n    \"type\": \"Type:\",\n    \"sweep\": \"Sweep:\",\n    \"speed\": \"Speed:\"\n  },\n  \"tooltips\": {\n    \"renameTab\": \"Rename tab\",\n    \"deleteTab\": \"Delete tab\",\n    \"whySave\": \"Why save?\",\n    \"hideDrawToolbar\": \"Hide draw toolbar\",\n    \"drawCustomLine\": \"Draw custom line\",\n    \"drawZone\": \"Draw zone\",\n    \"addText\": \"Add text\",\n    \"addImage\": \"Add image\",\n    \"zoneStyle\": \"Zone style\",\n    \"lineColor\": \"Line color\",\n    \"lineStyle\": \"Line style\",\n    \"arrowDirection\": \"Arrow direction\",\n    \"hideAddLineToolbar\": \"Hide add line toolbar\",\n    \"lineDirection\": \"Line direction\",\n    \"lineRouting\": \"Line routing\",\n    \"clearSelection\": \"Clear selection\",\n    \"hideMapZoom\": \"Hide map & zoom\",\n    \"zoomIn\": \"Zoom in\",\n    \"zoomOut\": \"Zoom out\",\n    \"fitToView\": \"Fit to view\",\n    \"resetView\": \"Reset view\",\n    \"saveAsPreset\": \"Save as preset\",\n    \"copyZoneStyle\": \"Copy zone style\",\n    \"pasteZoneStyle\": \"Paste zone style\",\n    \"deleteVersion\": \"Delete this version\",\n    \"editNote\": \"Edit note\",\n    \"deleteNote\": \"Delete note\",\n    \"saveCurrentTheme\": \"Save current theme\",\n    \"deleteTheme\": \"Delete theme\",\n    \"clearAllNodes\": \"Clear all {nodes}\",\n    \"addNewNode\": \"Add new {node}\",\n    \"addNewRack\": \"Add new {rack}\",\n    \"searchNodes\": \"Search by name, IP, MAC, role, or tag\",\n    \"manageTabs\": \"Manage document tabs\",\n    \"viewVersionHistory\": \"View version history\",\n    \"exportTopology\": \"Export topology\",\n    \"importTopology\": \"Import topology\",\n    \"undoLastPoint\": \"Undo last point\",\n    \"exitRackView\": \"Exit {rack} view and return to topology\",\n    \"alignLeft\": \"Align left\",\n    \"alignRight\": \"Align right\",\n    \"alignTop\": \"Align top\",\n    \"alignBottom\": \"Align bottom\",\n    \"distributeH\": \"Distribute horizontally\",\n    \"distributeV\": \"Distribute vertically\",\n    \"cloneSelected\": \"Clone selected\",\n    \"copyZone\": \"Copy zone style\",\n    \"pasteZone\": \"Paste zone style\",\n    \"toggleZones\": \"Toggle zones on selected\",\n    \"deleteSelected\": \"Delete selected\",\n    \"goToConnection\": \"Go to {connection}\"\n  },\n  \"dialogs\": {\n    \"recoverWork\": \"Recover unsaved work from {date}?\\n({nodeCount} nodes{tabInfo})\\n\\nClick OK to recover, Cancel to start fresh.\",\n    \"replaceData\": \"This will replace current data. Continue?\",\n    \"enableZoneFirst\": \"Enable the zone first before saving as preset\",\n    \"enterPresetName\": \"Enter preset name:\",\n    \"presetSaved\": \"Preset saved: {name}\",\n    \"selectNodeFirst\": \"Select at least one node first\",\n    \"zoneStyleCopied\": \"Zone style copied from first selected node\",\n    \"selectNodesFirst\": \"Select nodes first\",\n    \"copyZoneStyleFirst\": \"Copy a zone style first\",\n    \"zoneStylePasted\": \"Zone style pasted to {count} node(s)\",\n    \"zonesToggled\": \"Toggled zones on {count} node(s) to {state}\",\n    \"enterDecryptPassword\": \"This file is encrypted. Enter password to decrypt:\\n(Attempt {attempt} of {max})\",\n    \"decryptionCancelled\": \"Decryption cancelled. The file will not be loaded.\",\n    \"incorrectPassword\": \"Incorrect password. Please try again.\",\n    \"maxAttemptsReached\": \"Maximum attempts reached. The file will not be loaded.\",\n    \"editLegendLabel\": \"Edit legend label:\",\n    \"enterPort\": \"Port on {nodeName}:\",\n    \"portAlreadyUsed\": \"Warning: Port \\\"{port}\\\" is already used on {nodeName}. Use anyway?\",\n    \"zoneStyleCopiedShort\": \"Zone style copied!\",\n    \"drawingDisabledInRack\": \"Drawing tools are disabled in rack view. Exit to topology view to use drawing tools.\",\n    \"applyRoutingToAll\": \"Apply \\\"{routing}\\\" routing to all {count} connections?\",\n    \"invalidDataFile\": \"Invalid data file. Missing required fields.\",\n    \"enterEncryptPassword\": \"Enter a password to encrypt your data:\\n(Remember this password! You will need it to open this file and it's not recoverable!)\",\n    \"encryptionCancelled\": \"Encryption cancelled. File not saved.\",\n    \"confirmEncryptPassword\": \"Confirm your password:\",\n    \"passwordsDoNotMatch\": \"Passwords do not match. File not saved.\",\n    \"encryptionFailed\": \"Encryption failed: {error}\",\n    \"restoreVersion\": \"Restore version from {date}?\\n\\nYour current work will be lost unless you save first.\",\n    \"deleteVersionConfirm\": \"Delete this version from history?\",\n    \"clearVersionHistory\": \"Clear all version history?\\n\\nThis cannot be undone.\",\n    \"enterSnapshotDescription\": \"Enter a description for this snapshot:\",\n    \"enterTabName\": \"Please enter a tab name\",\n    \"noAuditEntries\": \"No audit entries to export\",\n    \"enterDecryptPasswordFor\": \"Enter password to decrypt \\\"{name}\\\":\",\n    \"decryptionFailed\": \"Failed to decrypt. Wrong password?\",\n    \"on\": \"ON\",\n    \"off\": \"OFF\",\n    \"ok\": \"OK\",\n    \"cancel\": \"Cancel\",\n    \"yes\": \"Yes\",\n    \"no\": \"No\",\n    \"confirm\": \"Confirm\",\n    \"alert\": \"Alert\",\n    \"prompt\": \"Input Required\",\n    \"invalidDataFile\": \"Invalid data file. Missing required fields.\",\n    \"importDataFailed\": \"Failed to import data: {error}\",\n    \"encryptionCancelledNotSaved\": \"Encryption cancelled. File not saved.\",\n    \"passwordsMismatchNotSaved\": \"Passwords do not match. File not saved.\",\n    \"encryptionFailedError\": \"Encryption failed: {error}\",\n    \"enterTabName\": \"Please enter a tab name\",\n    \"cannotDeleteLastTab\": \"Cannot delete the last tab\",\n    \"noAuditEntriesToExport\": \"No audit entries to export\",\n    \"enterNoteName\": \"Please enter a note name\",\n    \"noteAlreadyExists\": \"A note with this name already exists\",\n    \"passwordsMismatch\": \"Passwords do not match\",\n    \"enterRackName\": \"Please enter a {rack} name.\",\n    \"enterNodeName\": \"Please enter a {node} name.\",\n    \"screenshotFailed\": \"Screenshot failed. Please try again.\",\n    \"csvNoDataRows\": \"CSV file has no data rows\",\n    \"csvNeedsNameColumn\": \"CSV must have a \\\"name\\\" column\",\n    \"fullRestoreComplete\": \"Full restore complete from CSV backup\",\n    \"importedNodesCount\": \"Successfully imported {count} {nodes}\",\n    \"importCsvFailed\": \"Failed to import CSV: {error}\",\n    \"noValidTopologyInMarkdown\": \"No valid topology data found in Markdown file.\\n\\nMake sure the file was exported from The One File.\",\n    \"markdownImportSuccess\": \"Successfully imported:\\n- {nodeCount} {nodes}\\n- {edgeCount} {connections}\\n- {rectCount} zones\\n- {textCount} text labels\",\n    \"jsonImportSuccess\": \"Successfully imported:\\n- {nodeCount} nodes\\n- {edgeCount} connections\\n- {rectCount} zones\\n- {textCount} text labels\",\n    \"importMarkdownFailed\": \"Failed to import Markdown: {error}\",\n    \"noRecordingAvailable\": \"No recording available. Press Record to create one.\",\n    \"selectRecordingToDelete\": \"Select a recording to delete.\",\n    \"selectRecordingToExport\": \"Select a recording to export.\",\n    \"recordingImported\": \"Imported: {name}\",\n    \"importRecordingFailed\": \"Failed to import recording: {error}\",\n    \"selectRecordingForVideo\": \"Select a recording to export as video.\",\n    \"deleteTab\": \"Delete tab \\\"{name}\\\"?\",\n    \"themeExists\": \"A theme named \\\"{name}\\\" already exists. Replace it?\",\n    \"deleteTheme\": \"Delete theme \\\"{name}\\\"?\",\n    \"clearAuditLog\": \"Clear all audit log entries?\\n\\nThis cannot be undone.\",\n    \"deleteNote\": \"Delete note \\\"{name}\\\"?\",\n    \"deleteRecording\": \"Delete \\\"{name}\\\"?\",\n    \"confirmImportJson\": \"This will replace all current data with the imported data.\\n\\nImporting:\\n- {nodeCount} nodes\\n- {edgeCount} connections\\n- {tabCount} tab(s)\\n\\nContinue?\",\n    \"confirmImportHtml\": \"Import data from HTML file?\\n\\n- {nodeCount} nodes\\n- {edgeCount} connections\\n- {tabCount} tab(s)\\n\\nThis will REPLACE all current data.\",\n    \"invalidHtmlFile\": \"This HTML file does not contain valid topology data.\",\n    \"htmlImportSuccess\": \"Successfully imported {nodeCount} nodes and {edgeCount} connections from HTML.\",\n    \"confirmImportCsvFull\": \"This CSV contains a full backup.\\n\\n- {nodeCount} nodes in CSV data\\n- {tabCount} tab(s) in backup\\n- {edgeCount} connections in backup\\n\\nClick OK for FULL RESTORE (replace all data)\\nClick Cancel to just ADD the {csvNodeCount} nodes\",\n    \"confirmImportCsvAdd\": \"Import {count} nodes from CSV?\\n\\n{settingsNote}\\n\\nNote: CSV does not include connections, zones, or text labels.\",\n    \"csvSettingsRestore\": \"This will ADD nodes and RESTORE settings/theme.\",\n    \"csvAddOnly\": \"This will ADD nodes to your existing topology.\",\n    \"confirmImportMarkdown\": \"Import Markdown topology?\\n\\n- {nodeCount} nodes\\n- {edgeCount} connections\\n- {rectCount} zones\\n- {textCount} text labels\\n\\nThis will REPLACE all current data.\",\n    \"enterNewTabName\": \"Enter new name:\",\n    \"saveThemeName\": \"Enter a name for this theme:\",\n    \"defaultThemeName\": \"My Theme {number}\",\n    \"enterEncryptPasswordFor\": \"Enter password to encrypt \\\"{name}\\\":\",\n    \"confirmPasswordFor\": \"Confirm password:\",\n    \"deleteNodeConfirm\": \"Delete {node} \\\"{name}\\\"?\",\n    \"deleteLineConfirm\": \"Delete this line?\",\n    \"deleteLineNote\": \"Delete this line note?\",\n    \"deleteRackWithNodes\": \"Delete {rack} \\\"{name}\\\"?\\n\\nThis will also delete {count} {node}(s) inside:\\n• {nodeList}\",\n    \"deleteEmptyRack\": \"Delete {rack} \\\"{name}\\\"? (empty {rack})\",\n    \"deleteNodeWithConnections\": \"Delete {node} \\\"{name}\\\" and all its {connections}?\",\n    \"rackCapacityExceeded\": \"Device requires U{startU}-U{endU} but {rack} only has {capacity}U capacity. Device would exceed {rack} by {excess}U.\",\n    \"invalidRackPosition\": \"Invalid {rack} position. U position must be at least 1.\"\n  },\n  \"emptyStates\": {\n    \"noStringsFound\": \"No strings found\",\n    \"noNodesAssigned\": \"No nodes assigned\",\n    \"noConnections\": \"No connections\",\n    \"noVersionHistory\": \"No version history yet. Versions are saved automatically when you save the file.\",\n    \"noAuditEntries\": \"No audit entries yet\",\n    \"noNotesYet\": \"No notes yet\",\n    \"noConnectionsFound\": \"No connections found\",\n    \"noRecordingsYet\": \"No recordings yet. Press Record to start.\",\n    \"generatingVideo\": \"Generating Video...\"\n  },\n  \"legends\": {\n    \"legend\": \"Legend\",\n    \"zoneLegend\": \"Zone Legend\",\n    \"lineLegend\": \"Line Legend\",\n    \"coverageZone\": \"Coverage Zone\",\n    \"defaultLineLabel\": \"you can edit me too\"\n  }\n}</script>\n    <script>\n      const DEFAULT_LANG = JSON.parse(document.getElementById(\"lang-json\").textContent);\n      let LANG = JSON.parse(JSON.stringify(DEFAULT_LANG));\n      let CUSTOM_LANG = null;\n\n      function t(key, replacements = {}) {\n        const mode = (typeof PAGE_STATE !== 'undefined' && PAGE_STATE.mappingMode) || 'network';\n        const modeLabels = LANG.modes[mode] || LANG.modes.network;\n        let text = key.split('.').reduce((obj, k) => obj?.[k], LANG);\n        if (text === undefined || text === null || typeof text === 'object') return key;\n        text = text.replace(/\\{node\\}/g, modeLabels.node);\n        text = text.replace(/\\{nodes\\}/g, modeLabels.nodes);\n        text = text.replace(/\\{connection\\}/g, modeLabels.connection);\n        text = text.replace(/\\{connections\\}/g, modeLabels.connections);\n        text = text.replace(/\\{rack\\}/g, modeLabels.rack);\n        text = text.replace(/\\{racks\\}/g, modeLabels.racks);\n        for (const [k, v] of Object.entries(replacements)) {\n          text = text.replace(new RegExp(`\\\\{${k}\\\\}`, 'g'), v);\n        }\n        return text;\n      }\n\n      function setNestedValue(obj, key, value) {\n        const keys = key.split('.');\n        let current = obj;\n        for (let i = 0; i < keys.length - 1; i++) {\n          if (!current[keys[i]]) current[keys[i]] = {};\n          current = current[keys[i]];\n        }\n        current[keys[keys.length - 1]] = value;\n      }\n\n      function deepMerge(target, source) {\n        const result = JSON.parse(JSON.stringify(target));\n        for (const key of Object.keys(source)) {\n          if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {\n            result[key] = deepMerge(result[key] || {}, source[key]);\n          } else {\n            result[key] = source[key];\n          }\n        }\n        return result;\n      }\n\n      function flattenLangObject(obj, prefix = '') {\n        const result = [];\n        for (const [key, value] of Object.entries(obj)) {\n          if (key === '_meta') continue;\n          const fullKey = prefix ? `${prefix}.${key}` : key;\n          if (typeof value === 'object' && value !== null) {\n            result.push(...flattenLangObject(value, fullKey));\n          } else {\n            result.push({ key: fullKey, value: String(value), category: fullKey.split('.')[0] });\n          }\n        }\n        return result;\n      }\n\n      function applyLanguage() {\n        const isRTL = LANG._meta?.rtl === true;\n        document.documentElement.dir = isRTL ? 'rtl' : 'ltr';\n        document.documentElement.classList.toggle('rtl', isRTL);\n\n        document.querySelectorAll('[data-lang]').forEach(el => {\n          const key = el.dataset.lang;\n          const attr = el.dataset.langAttr || 'textContent';\n          const translated = t(key);\n          if (attr === 'placeholder') {\n            el.placeholder = translated;\n          } else if (attr === 'title') {\n            el.title = translated;\n          } else if (attr === 'innerHTML') {\n            el.innerHTML = translated;\n          } else if (attr === 'label') {\n            el.label = translated;\n          } else {\n            el.textContent = translated;\n          }\n        });\n        document.querySelectorAll('[data-lang-text]').forEach(el => {\n          el.textContent = t(el.dataset.langText);\n        });\n        document.querySelectorAll('[data-lang-placeholder]').forEach(el => {\n          el.placeholder = t(el.dataset.langPlaceholder);\n        });\n        document.querySelectorAll('[data-lang-label]').forEach(el => {\n          el.label = t(el.dataset.langLabel);\n        });\n        if (typeof applyMappingModeLabels === 'function') {\n          applyMappingModeLabels();\n        }\n        if (typeof updateLayerLabels === 'function') {\n          updateLayerLabels();\n        }\n      }\n\n      function saveLanguageToStorage() {\n        if (CUSTOM_LANG) {\n          localStorage.setItem('theOneFileCustomLang', JSON.stringify(CUSTOM_LANG));\n        } else {\n          localStorage.removeItem('theOneFileCustomLang');\n        }\n      }\n\n      function loadLanguageFromStorage() {\n        const stored = localStorage.getItem('theOneFileCustomLang');\n        if (stored) {\n          try {\n            CUSTOM_LANG = JSON.parse(stored);\n            LANG = deepMerge(DEFAULT_LANG, CUSTOM_LANG);\n          } catch (e) {\n            console.warn('Failed to load custom language:', e);\n          }\n        }\n      }\n\n      function exportLanguageFile() {\n        const langToExport = CUSTOM_LANG || LANG;\n        const blob = new Blob([JSON.stringify(langToExport, null, 2)], { type: 'application/json' });\n        const url = URL.createObjectURL(blob);\n        const a = document.createElement('a');\n        a.href = url;\n        a.download = `language-${langToExport._meta?.code || 'custom'}.json`;\n        a.click();\n        URL.revokeObjectURL(url);\n      }\n\n      function importLanguageFile(file) {\n        const reader = new FileReader();\n        reader.onload = (e) => {\n          try {\n            const imported = JSON.parse(e.target.result);\n            if (!imported._meta && !imported.modes && !imported.ui) {\n              throw new Error('Invalid language file structure');\n            }\n            const currentEdition = DEFAULT_LANG._meta?.edition || 'core';\n            const importedEdition = imported._meta?.edition;\n            if (importedEdition && importedEdition !== currentEdition) {\n              throw new Error(`Edition mismatch: This language file is for \"${importedEdition}\" edition but you are using \"${currentEdition}\" edition`);\n            }\n            CUSTOM_LANG = imported;\n            LANG = deepMerge(DEFAULT_LANG, imported);\n            applyLanguage();\n            saveLanguageToStorage();\n            showAlert(t('messages.langImportSuccess'));\n          } catch (err) {\n            showAlert(t('messages.invalidLangFile') + ': ' + err.message);\n          }\n        };\n        reader.readAsText(file);\n      }\n\n      function resetToDefaultLanguage() {\n        LANG = JSON.parse(JSON.stringify(DEFAULT_LANG));\n        CUSTOM_LANG = null;\n        localStorage.removeItem('theOneFileCustomLang');\n        applyLanguage();\n        updateCurrentLangDisplay();\n        showAlert(t('messages.langResetSuccess'));\n      }\n\n      function updateCurrentLangDisplay() {\n        const langNameEl = document.getElementById('current-lang-name');\n        if (langNameEl) {\n          langNameEl.textContent = LANG._meta?.name || 'English';\n          if (CUSTOM_LANG) {\n            langNameEl.textContent += ' (Custom)';\n          }\n        }\n      }\n\n      function escapeHtml(str) {\n        const div = document.createElement('div');\n        div.textContent = str;\n        return div.innerHTML;\n      }\n\n      let langEditorStrings = [];\n      let langEditorFilter = 'all';\n      let langEditorSearch = '';\n\n      function openLanguageEditor() {\n        langEditorStrings = flattenLangObject(LANG);\n        langEditorFilter = 'all';\n        langEditorSearch = '';\n        renderLangEditorList();\n\n        document.querySelectorAll('.lang-editor-tabs .lang-tab').forEach(tab => {\n          tab.onclick = () => {\n            document.querySelectorAll('.lang-editor-tabs .lang-tab').forEach(t => {\n              t.style.background = 'var(--btn-bg)';\n              t.style.color = 'var(--btn-text)';\n              t.classList.remove('active');\n            });\n            tab.style.background = 'var(--accent)';\n            tab.style.color = 'var(--bg)';\n            tab.classList.add('active');\n            langEditorFilter = tab.dataset.filter;\n            renderLangEditorList();\n          };\n        });\n        const searchInput = document.getElementById('lang-search');\n        if (searchInput) {\n          searchInput.value = '';\n          searchInput.oninput = () => {\n            langEditorSearch = searchInput.value.toLowerCase();\n            renderLangEditorList();\n          };\n        }\n        document.getElementById('language-editor-modal').style.display = 'flex';\n      }\n\n      function renderLangEditorList() {\n        const list = document.querySelector('.lang-editor-list');\n        if (!list) return;\n\n        const filtered = langEditorStrings.filter(s => {\n          if (langEditorFilter !== 'all') {\n            if (!s.key.startsWith(langEditorFilter)) return false;\n          }\n          if (langEditorSearch) {\n            const searchLower = langEditorSearch.toLowerCase();\n            if (!s.key.toLowerCase().includes(searchLower) &&\n                !s.value.toLowerCase().includes(searchLower)) {\n              return false;\n            }\n          }\n          return true;\n        });\n\n        if (filtered.length === 0) {\n          list.innerHTML = '<div style=\"padding:20px;text-align:center;color:var(--text-soft);\">' + t(\"emptyStates.noStringsFound\") + '</div>';\n          return;\n        }\n\n        list.innerHTML = filtered.map(s => `\n          <div style=\"display:flex;gap:12px;padding:8px 12px;border-bottom:1px solid var(--topbar-border);align-items:center;\">\n            <span style=\"flex:0 0 280px;font-size:11px;color:var(--text-soft);font-family:monospace;word-break:break-all;\">${escapeHtml(s.key)}</span>\n            <input type=\"text\" class=\"lang-value\" data-key=\"${escapeHtml(s.key)}\" value=\"${escapeHtml(s.value)}\"\n              style=\"flex:1;padding:6px 10px;background:var(--input-bg);color:var(--input-text);border:1px solid var(--input-border);border-radius:4px;font-size:13px;\">\n          </div>\n        `).join('');\n      }\n\n      function closeLanguageEditor() {\n        document.getElementById('language-editor-modal').style.display = 'none';\n      }\n\n      function saveLangEdits() {\n        const inputs = document.querySelectorAll('.lang-editor-list .lang-value');\n        let hasChanges = false;\n\n        inputs.forEach(input => {\n          const key = input.dataset.key;\n          const newValue = input.value;\n\n          const original = langEditorStrings.find(s => s.key === key);\n          if (original && original.value !== newValue) {\n            setNestedValue(LANG, key, newValue);\n            original.value = newValue;\n            hasChanges = true;\n          }\n        });\n\n        if (hasChanges) {\n          CUSTOM_LANG = JSON.parse(JSON.stringify(LANG));\n          saveLanguageToStorage();\n          applyLanguage();\n          updateCurrentLangDisplay();\n        }\n\n        closeLanguageEditor();\n      }\n\n      const LABELS = {\n        get mappingModes() { return LANG.modes; },\n        get canvasTemplates() {\n          const result = {};\n          for (const [k, v] of Object.entries(LANG.canvas)) {\n            result[k] = { name: v };\n          }\n          return result;\n        },\n        get defaultShapeCategories() {\n          return {\n            network: LANG.shapes.categories.network,\n            mindmap: LANG.shapes.categories.basic,\n            sports: LANG.shapes.categories.basic,\n            smarthome: LANG.shapes.categories.smarthome,\n            floorplan: LANG.shapes.categories.basic\n          };\n        }\n      };\n      const BASE_NODE_DATA = JSON.parse(document.getElementById(\"nodes-json\").textContent);\n\n      const SHAPE_CATEGORIES = {\n        basic: [\n          { value: 'circle', label: 'Circle' },\n          { value: 'square', label: 'Square' },\n          { value: 'rectangle', label: 'Rectangle' },\n          { value: 'triangle', label: 'Triangle' },\n          { value: 'hexagon', label: 'Hexagon' },\n          { value: 'diamond', label: 'Diamond' },\n          { value: 'star', label: 'Star' },\n          { value: 'stop-sign', label: 'Stop Sign' },\n          { value: 'octagon', label: 'Octagon' },\n          { value: 'pentagon', label: 'Pentagon' },\n          { value: 'cross', label: 'Cross' },\n          { value: 'rounded-square', label: 'Rounded Square' },\n          { value: 'pill', label: 'Pill' },\n          { value: 'parallelogram', label: 'Parallelogram' },\n          { value: 'trapezoid', label: 'Trapezoid' }\n        ],\n        computers: [\n          { value: 'server', label: 'Server' },\n          { value: 'pc', label: 'PC / Desktop' },\n          { value: 'laptop', label: 'Laptop' },\n          { value: 'phone', label: 'Phone / Mobile' },\n          { value: 'printer', label: 'Printer' },\n          { value: 'pi', label: 'Raspberry Pi' },\n          { value: 'sensor', label: 'Sensor / IoT' }\n        ],\n        network: [\n          { value: 'router', label: 'Router' },\n          { value: 'switch', label: 'Switch' },\n          { value: 'firewall', label: 'Firewall' },\n          { value: 'access-point', label: 'Access Point' },\n          { value: 'load-balancer', label: 'Load Balancer' },\n          { value: 'gateway', label: 'Gateway' },\n          { value: 'vpn', label: 'VPN / Tunnel' },\n          { value: 'nas', label: 'NAS / Storage' }\n        ],\n        cloud: [\n          { value: 'cloud', label: 'Cloud' },\n          { value: 'database', label: 'Database' },\n          { value: 'docker', label: 'Docker' },\n          { value: 'container', label: 'Container' },\n          { value: 'vm', label: 'Virtual Machine' },\n          { value: 'kubernetes', label: 'Kubernetes' },\n          { value: 'api', label: 'API / Endpoint' },\n          { value: 'queue', label: 'Queue / Message' },\n          { value: 'lambda', label: 'Lambda / Function' },\n          { value: 'bucket', label: 'Bucket / S3' }\n        ],\n        security: [\n          { value: 'shield', label: 'Shield' },\n          { value: 'camera', label: 'Camera / CCTV' },\n          { value: 'monitor', label: 'Monitor / Dashboard' }\n        ],\n        smarthome: [\n          { value: 'thermostat', label: 'Thermostat' },\n          { value: 'doorbell', label: 'Video Doorbell' },\n          { value: 'smart-lock', label: 'Smart Lock' },\n          { value: 'smart-bulb', label: 'Smart Bulb' },\n          { value: 'smart-plug', label: 'Smart Plug' },\n          { value: 'smart-speaker', label: 'Smart Speaker' },\n          { value: 'smart-tv', label: 'Smart TV' },\n          { value: 'hub', label: 'Smart Hub' },\n          { value: 'smoke-detector', label: 'Smoke Detector' },\n          { value: 'motion-sensor', label: 'Motion Sensor' },\n          { value: 'garage', label: 'Garage Door' },\n          { value: 'sprinkler', label: 'Sprinkler' },\n          { value: 'vacuum', label: 'Robot Vacuum' }\n        ],\n        sports: [\n          { value: 'basketball-ball', label: 'Basketball' },\n          { value: 'football-ball', label: 'Football' },\n          { value: 'soccer-ball', label: 'Soccer Ball' },\n          { value: 'hockey-puck', label: 'Hockey Puck' },\n          { value: 'baseball-ball', label: 'Baseball' },\n          { value: 'tennis-ball', label: 'Tennis Ball' },\n          { value: 'volleyball', label: 'Volleyball' },\n          { value: 'rugby-ball', label: 'Rugby Ball' },\n          { value: 'golf-ball', label: 'Golf Ball' },\n          { value: 'frisbee', label: 'Frisbee' },\n          { value: 'cricket-ball', label: 'Cricket Ball' },\n          { value: 'lacrosse-stick', label: 'Lacrosse Stick' },\n          { value: 'golf-flag', label: 'Golf Hole Flag' },\n          { value: 'tactical-x', label: 'X (Defender)' },\n          { value: 'tactical-o', label: 'O (Offensive)' },\n          { value: 'tactical-star', label: 'Star (Key Player)' }\n        ],\n        rack: [\n          { value: 'patch-panel', label: 'Patch Panel' },\n          { value: 'ups', label: 'UPS' },\n          { value: 'pdu', label: 'PDU' },\n          { value: 'rack-shelf', label: 'Shelf' },\n          { value: 'blank-panel', label: 'Blank Panel' },\n          { value: 'cable-management', label: 'Cable Management' },\n          { value: 'kvm', label: 'KVM' }\n        ]\n      };\n\n      const CANVAS_OPTIONS = {\n        network: [\n          { value: 'grid', label: 'Standard Grid' },\n          { value: 'dots', label: 'Dot Grid' },\n          { value: 'blueprint', label: 'Blueprint Grid' },\n          { value: 'none', label: 'No Grid' }\n        ],\n        mindmap: [],\n        sports: [\n          { value: 'basketball', label: 'Basketball Court' },\n          { value: 'football', label: 'Football Field' },\n          { value: 'soccer', label: 'Soccer Pitch' },\n          { value: 'hockey', label: 'Hockey Rink' },\n          { value: 'baseball', label: 'Baseball Diamond' },\n          { value: 'tennis', label: 'Tennis Court' }\n        ],\n        smarthome: [\n          { value: 'blueprint', label: 'Blueprint Grid' },\n          { value: 'grid', label: 'Standard Grid' },\n          { value: 'dots', label: 'Dot Grid' },\n          { value: 'none', label: 'No Grid' }\n        ],\n        floorplan: [] \n      };\n\n      const MODE_DEFAULT_CATEGORIES = {\n        network: 'network',\n        mindmap: 'basic',\n        sports: 'sports',\n        smarthome: 'smarthome',\n        floorplan: 'basic'\n      };\n\n      function findCategoryForShape(shape) {\n        if (!shape) return MODE_DEFAULT_CATEGORIES[PAGE_STATE.mappingMode || 'network'] || 'basic';\n        for (const [category, shapes] of Object.entries(SHAPE_CATEGORIES)) {\n          if (shapes.some(s => s.value === shape)) {\n            return category;\n          }\n        }\n        return MODE_DEFAULT_CATEGORIES[PAGE_STATE.mappingMode || 'network'] || 'basic';\n      }\n\n      let currentNodeId = null;\n      let textDrawMode = false;\n      let currentEdgeId = null;\n      let currentStyleScope = \"all\";\n      let NODE_DATA = {};\n      let EDGE_DATA = {\n       list: []\n      };\n      let currentRectId = null;\n      let currentTextId = null;\n      let rectDrawMode = false;\n      let RECT_DATA = {\n       list: []\n      };\n      let TEXT_DATA = {\n       list: []\n      };\n      let IMAGE_DATA = {\n       list: []\n      };\n      const MAX_CANVAS_IMAGES = 40;\n      const MAX_IMAGE_SIZE = 250;\n      let EDGE_LEGEND = {};\n      let freeDrawMode = false;\n      let savedPositions = {};\n      let savedSizes = {};\n      let savedStyles = {};\n      let legendCollapsed = false;\n      let minimapCollapsed = false;\n      let drawToolbarCollapsed = false;\n      let activeLayers = new Set([\"layer1\", \"layer2\", \"layer3\", \"layer4\"]);\n      let currentView = {\n       mode: \"topology\",\n       rackId: null\n      };\n      let savedTopologyView = null;\n      let topologyToolbarCollapsed = false;\n      let legendMiniBtn = null;\n      let minimapMiniBtn = null;\n      let drawToolbarMiniBtn = null;\n      let topologyToolbarMiniBtn = null;\n      let undoStack = [];\n      let redoStack = [];\n      const MAX_UNDO_STACK = 50;\n      let forgeDebounceTimer = null;\n      let forgeImmediate = false;\n      let currentSearchQuery = \"\";\n      let currentSearchResults = [];\n      const AUTOSAVE_DB_NAME = \"TheOneFileAutosave\";\n      const AUTOSAVE_STORE_NAME = \"drafts\";\n      const AUTOSAVE_KEY = \"currentDraft\";\n      const AUTOSAVE_INTERVAL = 30000;\n      let autosaveTimer = null;\n      let lastAutosaveTime = 0;\n      let selectedNodes = new Set();\n      let selectedEdges = new Set();\n      let selectedRects = new Set();\n      let selectedTexts = new Set();\n      let isSelecting = false;\n      let selectionStart = null;\n      let preDragSelectedNodes = new Set();\n      let preDragSelectedEdges = new Set();\n      let preDragSelectedRects = new Set();\n      let preDragSelectedTexts = new Set();\n      let selectionRect = null;\n      let selectionBoxStyle = {\n       fillColor: \"#4fd1c5\",\n       fillOpacity: 0.1,\n       strokeColor: \"#4fd1c5\",\n       strokeWidth: 2,\n       strokeDasharray: \"5,5\"\n      };\n      let isDraggingSelection = false;\n      let dragSelectionStart = null;\n      let clipboard = null;\n      const ROLLBACK_STORAGE_KEY = \"theonefile_rollbacks\";\n      const MAX_ROLLBACK_VERSIONS = 50;\n      let rollbackVersions = [];\n      let currentRollbackIndex = -1;\n      const AUDIT_STORAGE_KEY = \"theonefile_audit_log\";\n      let auditLog = [];\n      const MAX_AUDIT_ENTRIES = 1000;\n      let savedStyleSets = [];\n      let documentTabs = [{\n        id: \"main\",\n        name: \"Main Topology\",\n        nodes: {},\n        edges: { list: [] },\n        positions: {},\n        sizes: {},\n        styles: {},\n        legend: {},\n        rects: { list: [] },\n        texts: { list: [] },\n      pageState: null\n      }];\n      let currentTabIndex = 0;\n      let encryptedSections = {};\n      let performanceMode = \"auto\";\n      let cullOffscreenNodes = true;\n      let minimapNeedsUpdate = true;\n      let lastMinimapUpdate = 0;\n      const MobileManager = {\n        isMobile: false,\n        detect() {\n          if (navigator.userAgentData?.mobile === true) {\n            this.isMobile = true;\n            return true;\n          }\n          const coarse = matchMedia(\"(pointer: coarse)\").matches;\n          const width = window.innerWidth <= 900;\n          const portrait = matchMedia(\"(orientation: portrait)\").matches;\n          this.isMobile = coarse;\n          return this.isMobile;\n        },\n        applyInitialCollapse() {\n          if (!this.isMobile) return;\n          legendCollapsed = true;\n          minimapCollapsed = true;\n          drawToolbarCollapsed = true;\n          topologyToolbarCollapsed = true;\n          if (typeof updateLegendVisibility === \"function\") updateLegendVisibility();\n          if (typeof updateMinimapVisibility === \"function\") updateMinimapVisibility();\n          if (typeof updateDrawToolbarVisibility === \"function\") updateDrawToolbarVisibility();\n          if (typeof updateTopologyToolbarVisibility === \"function\") updateTopologyToolbarVisibility();\n        },\n        updateBulkToolbar() {\n          const desktop = document.getElementById(\"bulk-toolbar\");\n          const mobile = document.getElementById(\"bulk-toolbar-mobile\");\n          if (!desktop || !mobile) return;\n          if (typeof selectedNodes === 'undefined' || selectedNodes.size === 0) {\n           desktop.style.display = \"none\";\n           mobile.style.display = \"none\";\n           return;\n          }\n          const isVisible = desktop.style.display !== \"none\" || mobile.style.display !== \"none\";\n          if (this.isMobile) {\n            desktop.style.display = \"none\";\n            if (isVisible) {\n              mobile.style.display = \"flex\";\n            }\n          } else {\n            mobile.style.display = \"none\";\n            if (isVisible) {\n              desktop.style.display = \"flex\";\n            }\n          }\n        },\n        updateCanvasHint() {\n      const hint = document.getElementById(\"canvas-hint\");\n      if (!hint) return;\n      if (PAGE_STATE.canvasHintText !== undefined && PAGE_STATE.canvasHintText !== \"\") {\n        hint.textContent = PAGE_STATE.canvasHintText;\n        return;\n      }\n      const items = this.isMobile\n      ? [\n        \"Pinch to zoom\",\n        \"Drag to pan\",\n        \"Add node from top menu\",\n        \"Double tap to clone and align\",\n        \"Double tap to select multiple\",\n        \"You have the power\",\n        \"Your time is NOW!\",\n      ]\n      : [\n        \"Scroll to zoom\",\n        \"Drag to pan\",\n        \"Right click to clone and align\",\n        \"Right click to select multiple\",\n\t    \"Hold Shift + drag mouse for marquee selection\",\n        \"You have the power\",\n        \"Your time is NOW!\",\n      ];\n      const list = document.createElement(\"ul\");\n      for (const item of items) {\n      const li = document.createElement(\"li\");\n      li.textContent = item;\n      list.appendChild(li);\n      }\n      hint.replaceChildren(list);\n      },\n        autoSelectStyleScope() {\n          const scope = document.getElementById(\"style-scope\");\n          if (!scope) return;\n          if (this.isMobile) scope.value = \"mobile\";\n        },\n        updateToolbarStack() {\n          if (!this.isMobile) return;\n          const draw = document.getElementById(\"draw-toolbar\");\n          const topo = document.getElementById(\"topology-toolbar\");\n          if (!draw || !topo) return;\n          const h = draw.getBoundingClientRect().height;\n          document.documentElement.style.setProperty(\"--draw-toolbar-height\", h + \"px\");\n        },\n        updateLayoutControls() {\n          const sidebarRow = document.getElementById(\"sidebar-width-row\");\n          const footerRow = document.getElementById(\"mobile-footer-row\");\n          if (sidebarRow && footerRow) {\n            if (this.isMobile) {\n              sidebarRow.style.display = \"none\";\n              footerRow.style.display = \"flex\";\n            } else {\n              sidebarRow.style.display = \"flex\";\n              footerRow.style.display = \"none\";\n            }\n          }\n        },\n        applyAll() {\n          this.detect();\n          this.applyInitialCollapse();\n          this.updateBulkToolbar();\n\t\t  this.updateCanvasHint();\n          this.autoSelectStyleScope();\n          this.updateToolbarStack();\n          this.updateLayoutControls();\n        }\n      };\n      function isMobileDevice() {\n        return MobileManager.isMobile;\n      }\n\t  function escapeHtml(str) {\n       if (!str) return '';\n       return String(str).replace(/[&<>\"']/g, c => ({\n        '&': '&amp;', '<': '&lt;', '>': '&gt;', '\"': '&quot;', \"'\": '&#39;'\n       }[c]));\n      }\n      function u8ToBase64(u8) {\n      let binary = \"\";\n      for (let i = 0; i < u8.length; i++) binary += String.fromCharCode(u8[i]);\n      return btoa(binary);\n      }\n      function base64ToU8(base64) {\n      const binary = atob(base64);\n      const bytes = new Uint8Array(binary.length);\n      for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);\n      return bytes;\n      }\n      function openAutosaveDB() {\n       return new Promise((resolve, reject) => {\n        const request = indexedDB.open(AUTOSAVE_DB_NAME, 1);\n        request.onerror = () => reject(request.error);\n        request.onsuccess = () => resolve(request.result);\n        request.onupgradeneeded = (e) => {\n         const db = e.target.result;\n         if (!db.objectStoreNames.contains(AUTOSAVE_STORE_NAME)) {\n          db.createObjectStore(AUTOSAVE_STORE_NAME, { keyPath: \"id\" });\n         }\n        };\n       });\n      }\n      async function saveToIndexedDB() {\n       if (window.DEMO_MODE) return;\n       try {\n        const db = await openAutosaveDB();\n        const currentTab = documentTabs[currentTabIndex];\n        currentTab.nodes = JSON.parse(JSON.stringify(NODE_DATA));\n        currentTab.edges = JSON.parse(JSON.stringify(EDGE_DATA));\n        currentTab.positions = JSON.parse(JSON.stringify(savedPositions));\n        currentTab.sizes = JSON.parse(JSON.stringify(savedSizes));\n        currentTab.styles = JSON.parse(JSON.stringify(savedStyles));\n        currentTab.legend = JSON.parse(JSON.stringify(EDGE_LEGEND));\n        currentTab.rects = JSON.parse(JSON.stringify(RECT_DATA));\n        currentTab.texts = JSON.parse(JSON.stringify(TEXT_DATA));\n        currentTab.pageState = JSON.parse(JSON.stringify(PAGE_STATE));\n        const state = {\n         id: AUTOSAVE_KEY,\n         timestamp: Date.now(),\n         pageTitle: document.getElementById(\"page-title\")?.textContent || \"The One File\",\n         nodeData: NODE_DATA,\n         edgeData: EDGE_DATA,\n         rectData: RECT_DATA,\n         textData: TEXT_DATA,\n         imageData: IMAGE_DATA,\n         edgeLegend: EDGE_LEGEND,\n         zoneLegend: ZONE_LEGEND,\n         zonePresets: ZONE_PRESETS,\n         nodePositions: savedPositions,\n         nodeSizes: savedSizes,\n         nodeStyles: savedStyles,\n         page: PAGE_STATE,\n         canvas: {\n          zoom: canvasState.zoom,\n          panX: canvasState.panX,\n          panY: canvasState.panY,\n         },\n         savedTopologyView: savedTopologyView,\n         documentTabs: documentTabs,\n         currentTabIndex: currentTabIndex,\n         encryptedSections: encryptedSections,\n         auditLog: auditLog,\n\t\t savedStyleSets: savedStyleSets,\n\t\t selectedTheme: document.getElementById(\"theme-preset\")?.value || \"defaulted\",\n\t\t recordings: RECORDING_STATE.recordings,\n        };\n        const tx = db.transaction(AUTOSAVE_STORE_NAME, \"readwrite\");\n        const store = tx.objectStore(AUTOSAVE_STORE_NAME);\n        store.put(state);\n        await new Promise((resolve, reject) => {\n         tx.oncomplete = resolve;\n         tx.onerror = () => reject(tx.error);\n        });\n        lastAutosaveTime = Date.now();\n        console.log(\"Autosaved to IndexedDB at\", new Date().toLocaleTimeString());\n       } catch (e) {\n        console.warn(\"Auto-save failed:\", e);\n       }\n      }\n      async function loadFromIndexedDB() {\n       if (window.DEMO_MODE) return null;\n       try {\n        const db = await openAutosaveDB();\n        const tx = db.transaction(AUTOSAVE_STORE_NAME, \"readonly\");\n        const store = tx.objectStore(AUTOSAVE_STORE_NAME);\n        const request = store.get(AUTOSAVE_KEY);\n        return new Promise((resolve, reject) => {\n         request.onsuccess = () => resolve(request.result);\n         request.onerror = () => reject(request.error);\n        });\n       } catch (e) {\n        console.warn(\"Load from IndexedDB failed:\", e);\n        return null;\n       }\n      }\n      async function clearAutosave() {\n       try {\n        const db = await openAutosaveDB();\n        const tx = db.transaction(AUTOSAVE_STORE_NAME, \"readwrite\");\n        const store = tx.objectStore(AUTOSAVE_STORE_NAME);\n        store.delete(AUTOSAVE_KEY);\n       } catch (e) {\n        console.warn(\"Clear autosave failed:\", e);\n       }\n      }\n      function startAutosave() {\n       if (autosaveTimer) clearInterval(autosaveTimer);\n       autosaveTimer = setInterval(() => {\n        if (Object.keys(NODE_DATA).length > 0 || EDGE_DATA.list.length > 0 || documentTabs.length > 1) {\n         saveToIndexedDB();\n        }\n       }, AUTOSAVE_INTERVAL);\n      }\n      async function checkForAutosaveRecovery() {\n       if (window.DEMO_MODE) return;\n       try {\n        const saved = await loadFromIndexedDB();\n        if (saved && saved.timestamp) {\n         const age = Date.now() - saved.timestamp;\n         const hasCurrentData = Object.keys(NODE_DATA).length > 0 || EDGE_DATA.list.length > 0;\n         const savedNodeCount = saved.nodeData ? Object.keys(saved.nodeData).length : 0;\n         const savedTabCount = saved.documentTabs ? saved.documentTabs.length : 1;\n         const hasPageState = saved.page && Object.keys(saved.page).length > 0;\n         if (age < 300000 && (savedNodeCount > 0 || savedTabCount > 1 || hasPageState)) {\n          const savedDate = new Date(saved.timestamp).toLocaleString();\n          const tabInfo = savedTabCount > 1 ? ` across ${savedTabCount} tabs` : \"\";\n          if (!hasCurrentData || await showConfirm(t(\"dialogs.recoverWork\", { date: savedDate, nodeCount: savedNodeCount, tabInfo: tabInfo }))) {\n           if (!hasCurrentData || await showConfirm(t(\"dialogs.replaceData\"))) {\n            NODE_DATA = saved.nodeData || {};\n            EDGE_DATA = saved.edgeData || { list: [] };\n            RECT_DATA = saved.rectData || { list: [] };\n            TEXT_DATA = saved.textData || { list: [] };\n            IMAGE_DATA = saved.imageData || { list: [] };\n            EDGE_LEGEND = saved.edgeLegend || {};\n\t\t\tZONE_LEGEND = saved.zoneLegend || {};\n            ZONE_PRESETS = saved.zonePresets || {};\n            loadCustomPresets();\n            savedPositions = saved.nodePositions || {};\n            savedSizes = saved.nodeSizes || {};\n            savedStyles = saved.nodeStyles || {};\n            if (saved.page) PAGE_STATE = Object.assign({}, DEFAULT_PAGE_STATE, saved.page);\n            if (saved.canvas) {\n             canvasState.zoom = saved.canvas.zoom || 1;\n             canvasState.panX = saved.canvas.panX || 0;\n             canvasState.panY = saved.canvas.panY || 0;\n            }\n            if (saved.savedTopologyView) savedTopologyView = saved.savedTopologyView;\n            if (saved.savedStyleSets) savedStyleSets = saved.savedStyleSets;\n            if (saved.selectedTheme) {\n              rebuildThemeDropdown();\n              document.getElementById(\"theme-preset\").value = saved.selectedTheme;\n              updateDeleteButton();\n            }\n            if (saved.documentTabs) documentTabs = saved.documentTabs;\n            if (saved.currentTabIndex !== undefined) currentTabIndex = saved.currentTabIndex;\n            if (saved.encryptedSections) encryptedSections = saved.encryptedSections;\n            if (saved.auditLog) auditLog = saved.auditLog;\n            if (saved.recordings) {\n              RECORDING_STATE.recordings = saved.recordings;\n              RECORDING_STATE.currentRecording = saved.recordings[0] || null;\n            }\n            if (saved.pageTitle) {\n             const titleEl = document.getElementById(\"page-title\");\n             if (titleEl) titleEl.textContent = saved.pageTitle;\n            }\n            const layerMap = { \"physical\": \"layer1\", \"logical\": \"layer2\", \"security\": \"layer3\", \"application\": \"layer4\" };\n            Object.values(NODE_DATA).forEach(node => {\n              if (!node.tags) node.tags = [];\n              if (!node.notes) node.notes = [];\n              if (!node.layer || layerMap[node.layer]) node.layer = layerMap[node.layer] || 'layer1';\n            });\n            wieldThePower();\n            forgeTheTopology();\n            updateViewBox();\n            console.log(\"Recovered from autosave:\", savedNodeCount, \"nodes,\", savedTabCount, \"tabs\");\n            return true;\n           }\n          }\n         }\n        }\n       } catch (e) {\n        console.warn(\"Autosave recovery check failed:\", e);\n       }\n       return false;\n      }\n\t\t\t  function ensureLegendMiniButton() {\n\t\t   if (legendMiniBtn) return legendMiniBtn;\n\t\t   const handleClick = (e) => {\n\t\t\te.stopPropagation();\n\t\t\te.preventDefault();\n\t\t\tlegendCollapsed = false;\n\t\t\tupdateLegendVisibility();\n\t\t   };\n\t\t   const preventTouch = (e) => { e.preventDefault(); };\n\t\t   const existing = document.getElementById(\"edge-legend-mini\");\n\t\t   if (existing) {\n\t\t\texisting.addEventListener(\"click\", handleClick);\n\t\t\texisting.addEventListener(\"touchstart\", preventTouch, { passive: false });\n\t\t\texisting.addEventListener(\"touchend\", handleClick);\n\t\t\tlegendMiniBtn = existing;\n\t\t\treturn existing;\n\t\t   }\n\t\t   const panel = document.querySelector(\".topology-panel\");\n\t\t   if (!panel) return null;\n\t\t   const btn = document.createElement(\"button\");\n\t\t   btn.type = \"button\";\n\t\t   btn.id = \"edge-legend-mini\";\n\t\t   btn.className = \"legend-mini-btn\";\n\t\t   btn.textContent = t(\"toolbar.legendMini\");\n\t\t   btn.style.bottom = \"10px\";\n\t\t   btn.addEventListener(\"click\", handleClick);\n\t\t   btn.addEventListener(\"touchstart\", preventTouch, { passive: false });\n\t\t   btn.addEventListener(\"touchend\", handleClick);\n\t\t   panel.appendChild(btn);\n\t\t   legendMiniBtn = btn;\n\t\t   return btn;\n\t\t}\n\t\t\t  function ensureMinimapMiniButton() {\n\t\t   if (minimapMiniBtn) return minimapMiniBtn;\n\t\t   const handleClick = (e) => {\n\t\t\te.stopPropagation();\n\t\t\te.preventDefault();\n\t\t\tminimapCollapsed = false;\n\t\t\tupdateMinimapVisibility();\n\t\t   };\n\t\t   const preventTouch = (e) => { e.preventDefault(); };\n\t\t   const existing = document.getElementById(\"minimap-mini\");\n\t\t   if (existing) {\n\t\t\texisting.addEventListener(\"click\", handleClick);\n\t\t\texisting.addEventListener(\"touchstart\", preventTouch, { passive: false });\n\t\t\texisting.addEventListener(\"touchend\", handleClick);\n\t\t\tminimapMiniBtn = existing;\n\t\t\treturn existing;\n\t\t   }\n\t\t   const panel = document.querySelector(\".topology-panel\");\n\t\t   if (!panel) return null;\n\t\t   const btn = document.createElement(\"button\");\n\t\t   btn.type = \"button\";\n\t\t   btn.id = \"minimap-mini\";\n\t\t   btn.className = \"legend-mini-btn\";\n\t\t   btn.textContent = t(\"toolbar.minimapMini\");\n\t\t   btn.style.right = \"10px\";\n\t\t   btn.style.left = \"auto\";\n\t\t   btn.style.bottom = \"10px\";\n\t\t   btn.addEventListener(\"click\", handleClick);\n\t\t   btn.addEventListener(\"touchstart\", preventTouch, { passive: false });\n\t\t   btn.addEventListener(\"touchend\", handleClick);\n\t\t   panel.appendChild(btn);\n\t\t   minimapMiniBtn = btn;\n\t\t   return btn;\n\t\t}\n\t\t\t  function ensureDrawToolbarMiniButton() {\n\t\t   if (drawToolbarMiniBtn) return drawToolbarMiniBtn;\n\t\t   const handleClick = (e) => {\n\t\t\te.stopPropagation();\n\t\t\te.preventDefault();\n\t\t\tdrawToolbarCollapsed = false;\n\t\t\tupdateDrawToolbarVisibility();\n\t\t   };\n\t\t   const preventTouch = (e) => { e.preventDefault(); };\n\t\t   const existing = document.getElementById(\"draw-toolbar-mini\");\n\t\t   if (existing) {\n\t\t\texisting.addEventListener(\"click\", handleClick);\n\t\t\texisting.addEventListener(\"touchstart\", preventTouch, { passive: false });\n\t\t\texisting.addEventListener(\"touchend\", handleClick);\n\t\t\tdrawToolbarMiniBtn = existing;\n\t\t\treturn existing;\n\t\t   }\n\t\t   const panel = document.querySelector(\".topology-panel\");\n\t\t   if (!panel) return null;\n\t\t   const btn = document.createElement(\"button\");\n\t\t   btn.type = \"button\";\n\t\t   btn.id = \"draw-toolbar-mini\";\n\t\t   btn.className = \"legend-mini-btn\";\n\t\t   btn.textContent = t(\"toolbar.drawMini\");\n\t\t   btn.style.top = \"10px\";\n\t\t   btn.style.left = \"10px\";\n\t\t   btn.style.right = \"auto\";\n\t\t   btn.addEventListener(\"click\", handleClick);\n\t\t   btn.addEventListener(\"touchstart\", preventTouch, { passive: false });\n\t\t   btn.addEventListener(\"touchend\", handleClick);\n\t\t   panel.appendChild(btn);\n\t\t   drawToolbarMiniBtn = btn;\n\t\t   return btn;\n\t\t}\n\t\t\t  function ensureTopologyToolbarMiniButton() {\n\t\t   if (topologyToolbarMiniBtn) return topologyToolbarMiniBtn;\n\t\t   const handleClick = (e) => {\n\t\t\te.stopPropagation();\n\t\t\te.preventDefault();\n\t\t\ttopologyToolbarCollapsed = false;\n\t\t\tupdateTopologyToolbarVisibility();\n\t\t   };\n\t\t   const preventTouch = (e) => { e.preventDefault(); };\n\t\t   const existing = document.getElementById(\"topology-toolbar-mini\");\n\t\t   if (existing) {\n\t\t\texisting.addEventListener(\"click\", handleClick);\n\t\t\texisting.addEventListener(\"touchstart\", preventTouch, { passive: false });\n\t\t\texisting.addEventListener(\"touchend\", handleClick);\n\t\t\ttopologyToolbarMiniBtn = existing;\n\t\t\treturn existing;\n\t\t   }\n\t\t   const panel = document.querySelector(\".topology-panel\");\n\t\t   if (!panel) return null;\n\t\t   const btn = document.createElement(\"button\");\n\t\t   btn.type = \"button\";\n\t\t   btn.id = \"topology-toolbar-mini\";\n\t\t   btn.className = \"legend-mini-btn\";\n\t\t   btn.textContent = t(\"toolbar.topologyMini\");\n\t\t   btn.style.top = \"10px\";\n\t\t   btn.style.left = \"auto\";\n\t\t   btn.style.right = \"40px\";\n\t\t   btn.addEventListener(\"click\", handleClick);\n\t\t   btn.addEventListener(\"touchstart\", preventTouch, { passive: false });\n\t\t   btn.addEventListener(\"touchend\", handleClick);\n\t\t   panel.appendChild(btn);\n\t\t   topologyToolbarMiniBtn = btn;\n\t\t   return btn;\n\t\t}\n      function updateToolbarStack() {\n       if (!isMobileDevice()) return;\n       const draw = document.getElementById(\"draw-toolbar\");\n       const topo = document.getElementById(\"topology-toolbar\");\n       if (!draw || !topo) return;\n       const h = draw.getBoundingClientRect().height;\n       document.documentElement.style.setProperty(\"--draw-toolbar-height\", h + \"px\");\n      }\n      window.addEventListener(\"resize\", updateToolbarStack);\n      window.addEventListener(\"DOMContentLoaded\", updateToolbarStack);\n      updateToolbarStack();\n      function updateLegendVisibility() {\n       const legend = document.getElementById(\"edge-legend\");\n       const mini = ensureLegendMiniButton();\n       if (!legend || !mini) return;\n       const hasItems = legend.querySelectorAll(\".legend-item\").length > 0;\n       if (!hasItems) {\n        legend.style.display = \"none\";\n        mini.style.display = \"none\";\n        return;\n       }\n       if (legendCollapsed) {\n        legend.style.display = \"none\";\n        mini.style.display = \"inline-flex\";\n       } else {\n        legend.style.display = \"flex\";\n        mini.style.display = \"none\";\n       }\n      }\n      function updateMinimapVisibility() {\n       const wrapper = document.getElementById(\"minimap-zoom-wrapper\");\n       const mini = ensureMinimapMiniButton();\n       if (!wrapper || !mini) return;\n       if (minimapCollapsed) {\n        wrapper.style.display = \"none\";\n        mini.style.display = \"inline-flex\";\n       } else {\n        wrapper.style.display = \"block\";\n        mini.style.display = \"none\";\n       }\n      }\n      function updateDrawToolbarVisibility() {\n       const toolbar = document.getElementById(\"draw-toolbar\");\n       const mini = ensureDrawToolbarMiniButton();\n       if (isViewOnly()) {\n        if (toolbar) toolbar.style.setProperty('display', 'none', 'important');\n        if (mini) mini.style.display = \"none\";\n        return;\n       }\n       if (!toolbar || !mini) return;\n       if (drawToolbarCollapsed) {\n        toolbar.style.setProperty('display', 'none', 'important');\n        mini.style.display = \"inline-flex\";\n       } else {\n      if (isMobileDevice()) {\n        toolbar.style.setProperty('display', 'grid', 'important');\n        mini.style.display = \"none\";\n      } else {\n        toolbar.style.setProperty('display', 'flex', 'important');\n        mini.style.display = \"none\";\n      }\n       }\n      }\n      function updateTopologyToolbarVisibility() {\n       const toolbar = document.getElementById(\"topology-toolbar\");\n       const mini = ensureTopologyToolbarMiniButton();\n       if (isViewOnly()) {\n        if (toolbar) toolbar.style.setProperty('display', 'none', 'important');\n        if (mini) mini.style.display = \"none\";\n        return;\n       }\n       if (!toolbar || !mini) return;\n       const hasSelectedNode = currentNodeId !== null;\n       if (!hasSelectedNode) {\n        toolbar.style.setProperty('display', 'none', 'important');\n        mini.style.display = \"none\";\n       } else if (topologyToolbarCollapsed) {\n        toolbar.style.setProperty('display', 'none', 'important');\n        mini.style.display = \"inline-flex\";\n       } else {\n      if (isMobileDevice()) {\n        toolbar.style.setProperty('display', 'grid', 'important');\n        mini.style.display = \"none\";\n        } else {\n        toolbar.style.setProperty('display', 'flex', 'important');\n        mini.style.display = \"none\";\n      }\n       }\n      }\n      const THE_ONE_FILE_VERSION = \"4.1.5\";\n      const DEFAULT_CANVAS_HINT = document.getElementById(\"canvas-hint\")?.innerHTML || \"\";\n      const DEFAULT_PAGE_STATE = {\n       title: \"The One File\",\n       background: \"\",\n       topbarBg: \"rgba(9, 12, 20, 0.9)\",\n       topbarBorder: \"#1f2533\",\n       panel: \"#0b0e13\",\n       panelAlt: \"#10141b\",\n       accent: \"#4fd1c5\",\n       sidebarBg: \"#10141b\",\n       btnBg: \"#0b0e13\",\n       btnText: \"#e2e8f0\",\n       tagFill: \"#1e293b\",\n       tagText: \"#e2e8f0\",\n       tagBorder: \"#475569\",\n       inputBg: \"#0b0e13\",\n       inputText: \"#e2e8f0\",\n       inputBorder: \"#1f2937\",\n       inputFont: \"Inter, system-ui, sans-serif\",\n       inputFontSize: 14,\n       toolbarBg: \"#0f172a\",\n       toolbarBorder: \"#1f2937\",\n       toolbarText: \"#94a3b8\",\n       toolbarBtnBg: \"#0b0e13\",\n       toolbarBtnText: \"#e2e8f0\",\n       minimapDots: \"#94a3b8\",\n       canvasHintEnabled: true,\n       canvasHintText: \"\",\n       canvasHintBg: \"#0f172a\",\n       canvasHintColor: \"#94a3b8\",\n       danger: \"#f56565\",\n       textMain: \"#e2e8f0\",\n       textSoft: \"#94a3b8\",\n       topbarHeight: 52,\n       sidebarWidth: 350,\n       mobileFooterHeight: 40,\n       sidebarCollapsed: false,\n       nodeFill: \"#1e293b\",\n       nodeStroke: \"#475569\",\n       nodeTitle: \"#e2e8f0\",\n       nodeSub: \"#94a3b8\",\n       nodeTitleSize: 18,\n       nodeSubSize: 13,\n       nodeFont: \"Inter, system-ui, sans-serif\",\n       defaultEdge: \"#475569\",\n       selectionHandle: \"#f59e0b\",\n       selectionHandleSize: 8,\n       groupIndicator: \"#4fd1c5\",\n      canvasGradientTop: \"#1e2532\",\n       canvasGradientBottom: \"#050608\",\n       canvasBorder: \"#475569\",\n       canvasGrid: \"#475569\",\n       canvasGridSize: 50,\n       canvasGridEnabled: true,\n       rackFrameFill: \"#0f172a\",\n       rackGridEnabled: true,\n       rackFrameStroke: \"#4fd1c5\",\n       rackLineColor: \"#475569\",\n       rackTextColor: \"#4fd1c5\",\n\t   viewOnly: false,\n\t   defaultEdgeRouting: \"curved\",\n\t   animateConnections: false,\n\t   animationStyle: \"arrows\",\n\t   animationDirection: \"all\",\n\t   animationSpeed: 1.5,\n\t   mappingMode: \"network\",\n\t   canvasTemplate: \"grid\",\n\t   motionTrailsEnabled: true,\n\t   motionTrailLength: 8,\n\t   motionTrailStyle: \"solid\",\n\t   motionTrailArrow: true,\n\t   showPortLabels: true,\n      };\n      let PAGE_STATE = Object.assign({}, DEFAULT_PAGE_STATE);\n\t  const FOV_ANIMATION_START = Date.now();\n\t  let ZONES_VISIBLE = true;\n\t  let RECORDING_STATE = {\n\t    isRecording: false,\n\t    isPlaying: false,\n\t    isPaused: false,\n\t    currentRecording: null,\n\t    recordings: [],\n\t    startTime: 0,\n\t    frames: [],\n\t    playbackIndex: 0,\n\t    playbackSpeed: 1,\n\t    loopPlayback: false,\n\t    captureInterval: null,\n\t    playbackTimer: null,\n\t    CAPTURE_FPS: 10,\n\t    videoRecorder: null,\n\t    videoChunks: [],\n\t    videoCanvas: null,\n\t    videoCtx: null,\n\t    videoAnimFrame: null,\n\t    isVideoRecording: false,\n\t    isStepRecording: false,\n\t    stepFrames: [],\n\t    stepCount: 0,\n\t    trailHistory: {}\n\t  };\n\t  \n\t\tconst ANIM_SETTINGS = {\n\t\t  masterAnim: true,\n\t\t  masterZones: true,\n\t\t  animTypes: { sweep: true, pulse: true, rings: true, spin: true, connections: true },\n\t\t  animCategories: { camera: true, doorbell: true, motion: true, smoke: true, wifi: true, sensor: true, sprinkler: true, connections: true },\n\t\t  zoneCategories: { camera: true, doorbell: true, motion: true, smoke: true, wifi: true, sensor: true, sprinkler: true }\n\t\t};\n\t\t\nfunction getShapeCategory(shape) {\n  const map = {\n    \"camera\": \"camera\", \"cctv\": \"camera\", \"ptz-cam\": \"camera\",\n    \"doorbell\": \"doorbell\",\n    \"motion-sensor\": \"motion\", \"motion-detect\": \"motion\",\n    \"smoke-detector\": \"smoke\", \"smoke-alarm\": \"smoke\",\n    \"access-point\": \"wifi\", \"wifi\": \"wifi\", \"router\": \"wifi\", \"wifi-strong\": \"wifi\", \"wifi-weak\": \"wifi\",\n    \"sensor\": \"sensor\", \"iot\": \"sensor\",\n    \"sprinkler\": \"sprinkler\", \"sprinkler-arc\": \"sprinkler\"\n  };\n  return map[shape] || null;\n}\nfunction isAnimationAllowed(shape, animType) {\n  if (!ANIM_SETTINGS.masterAnim) return false;\n  if (!ANIM_SETTINGS.animTypes[animType]) return false;\n  const cat = getShapeCategory(shape);\n  if (cat && !ANIM_SETTINGS.animCategories[cat]) return false;\n  return true;\n}\nfunction isZoneAllowed(shape) {\n  if (!ANIM_SETTINGS.masterZones) return false;\n  const cat = getShapeCategory(shape);\n  if (cat && !ANIM_SETTINGS.zoneCategories[cat]) return false;\n  return true;\n}\nfunction applyAnimZoneSettings() {\n  document.querySelectorAll(\".fov-group\").forEach(g => {\n    const nodeEl = g.closest(\"g[data-node-id]\");\n    if (!nodeEl) return;\n    const nodeId = nodeEl.dataset.nodeId;\n    const node = NODE_DATA[nodeId];\n    if (!node) return;\n    const cat = getShapeCategory(node.shape);\n    const zoneVisible = ANIM_SETTINGS.masterZones && (!cat || ANIM_SETTINGS.zoneCategories[cat]);\n    g.style.display = zoneVisible ? \"\" : \"none\";\n    if (zoneVisible && node.fovAnimate) {\n      const animType = node.fovAnimationType || \"sweep\";\n      const animAllowed = ANIM_SETTINGS.masterAnim && ANIM_SETTINGS.animTypes[animType] && (!cat || ANIM_SETTINGS.animCategories[cat]);\n      g.style.animationPlayState = animAllowed ? \"running\" : \"paused\";\n      g.querySelectorAll(\"circle\").forEach(c => c.style.animationPlayState = animAllowed ? \"running\" : \"paused\");\n    }\n  });\n  const connAnimAllowed = ANIM_SETTINGS.masterAnim && ANIM_SETTINGS.animTypes.connections && ANIM_SETTINGS.animCategories.connections;\n  document.querySelectorAll(\".edge-arrow-forward, .edge-arrow-backward\").forEach(a => {\n    a.style.animationPlayState = connAnimAllowed ? \"running\" : \"paused\";\n  });\n}\nlet ZONE_LEGEND = {};\nlet ZONE_PRESETS = {};\nlet copiedZoneStyle = null;\n\t  function hasCoverageZone(shape) {\n\t\t  const supportedShapes = [\n\t\t\t\"camera\", \"cctv\", \"doorbell\",\n\t\t\t\"motion-sensor\", \"smoke-detector\",\n\t\t\t\"access-point\", \"wifi\", \"router\",\n\t\t\t\"sensor\", \"iot\", \"sprinkler\"\n\t\t  ];\n\t\t  return supportedShapes.includes(shape);\n\t\t}\n\n\t\tfunction getCoverageDefaults(shape) {\n\t\t  const defaults = {\n\t\t\t\"camera\": { angle: 90, distance: 150, animationType: \"sweep\" },\n\t\t\t\"cctv\": { angle: 90, distance: 150, animationType: \"sweep\" },\n\t\t\t\"doorbell\": { angle: 120, distance: 100, animationType: \"sweep\" },\n\t\t\t\"motion-sensor\": { angle: 120, distance: 100, animationType: \"pulse\" },\n\t\t\t\"smoke-detector\": { angle: 360, distance: 80, animationType: \"pulse\" },\n\t\t\t\"access-point\": { angle: 360, distance: 200, animationType: \"rings\" },\n\t\t\t\"wifi\": { angle: 360, distance: 200, animationType: \"rings\" },\n\t\t\t\"router\": { angle: 360, distance: 200, animationType: \"rings\" },\n\t\t\t\"sensor\": { angle: 90, distance: 100, animationType: \"pulse\" },\n\t\t\t\"iot\": { angle: 90, distance: 100, animationType: \"pulse\" },\n\t\t\t\"sprinkler\": { angle: 90, distance: 120, animationType: \"spin\" }\n\t\t  };\n\t\t  return defaults[shape] || { angle: 90, distance: 150, animationType: \"sweep\" };\n\t\t}\n\t\tfunction toggleAllZones() {\n  ANIM_SETTINGS.masterZones = !ANIM_SETTINGS.masterZones;\n  const masterCheckbox = document.getElementById(\"zone-master\");\n  if (masterCheckbox) masterCheckbox.checked = ANIM_SETTINGS.masterZones;\n  applyAnimZoneSettings();\n}\n\nfunction copyZoneStyle(nodeId) {\n  const node = NODE_DATA[nodeId];\n  if (!node || !hasCoverageZone(node.shape)) return;\n  copiedZoneStyle = {\n    fovEnabled: node.fovEnabled,\n    fovAngle: node.fovAngle,\n    fovDistance: node.fovDistance,\n    fovInnerRadius: node.fovInnerRadius,\n    fovRotation: node.fovRotation,\n    fovColor: node.fovColor,\n    fovOpacity: node.fovOpacity,\n    fovGradient: node.fovGradient,\n    fovBorderColor: node.fovBorderColor,\n    fovBorderWidth: node.fovBorderWidth,\n    fovBorderStyle: node.fovBorderStyle,\n    fovBorderOpacity: node.fovBorderOpacity,\n    fovLabel: node.fovLabel,\n    fovLabelPosition: node.fovLabelPosition,\n    fovLabelSize: node.fovLabelSize,\n    fovLabelColor: node.fovLabelColor,\n    fovLabelBold: node.fovLabelBold,\n    fovLabelBg: node.fovLabelBg,\n    fovLabelBgColor: node.fovLabelBgColor,\n    fovLabelOffsetX: node.fovLabelOffsetX,\n    fovLabelOffsetY: node.fovLabelOffsetY,\n    fovAnimate: node.fovAnimate,\n    fovAnimationType: node.fovAnimationType,\n    fovSweep: node.fovSweep,\n    fovSpeed: node.fovSpeed\n  };\n}\n\nfunction pasteZoneStyle(nodeId) {\n  if (!copiedZoneStyle) return;\n  const node = NODE_DATA[nodeId];\n  if (!node || !hasCoverageZone(node.shape)) return;\n  pushUndo(\"paste zone style\");\n  Object.assign(node, copiedZoneStyle);\n  updateFovCone(nodeId);\n  if (currentNodeId === nodeId) {\n    claimTheImmortal(nodeId);\n  }\n}\n\nfunction applyZonePreset(preset) {\n  if (!currentNodeId) return;\n  const presets = {\n    \"security-cam\": { fovAngle: 90, fovDistance: 150, fovColor: \"#f59e0b\", fovOpacity: 20, fovAnimationType: \"sweep\", fovAnimate: false },\n    \"ptz-cam\": { fovAngle: 60, fovDistance: 200, fovColor: \"#f59e0b\", fovOpacity: 25, fovAnimationType: \"sweep\", fovAnimate: true, fovSweep: 180, fovSpeed: 8 },\n    \"motion-detect\": { fovAngle: 120, fovDistance: 100, fovColor: \"#10b981\", fovOpacity: 15, fovAnimationType: \"pulse\", fovAnimate: true, fovSpeed: 3 },\n    \"wifi-strong\": { fovAngle: 360, fovDistance: 150, fovColor: \"#3b82f6\", fovOpacity: 10, fovAnimationType: \"rings\", fovAnimate: true, fovSpeed: 4 },\n    \"wifi-weak\": { fovAngle: 360, fovDistance: 250, fovColor: \"#3b82f6\", fovOpacity: 8, fovGradient: true, fovAnimationType: \"rings\", fovAnimate: true, fovSpeed: 5 },\n    \"smoke-alarm\": { fovAngle: 360, fovDistance: 80, fovColor: \"#ef4444\", fovOpacity: 15, fovAnimationType: \"pulse\", fovAnimate: false },\n    \"sprinkler-arc\": { fovAngle: 90, fovDistance: 120, fovColor: \"#06b6d4\", fovOpacity: 20, fovAnimationType: \"spin\", fovAnimate: true, fovSpeed: 6 }\n  };\n  const allPresets = { ...presets, ...ZONE_PRESETS };\n  const settings = allPresets[preset];\n  if (!settings) return;\n  \n  pushUndo(\"apply zone preset\");\n  const node = NODE_DATA[currentNodeId];\n  node.fovEnabled = true;\n  Object.assign(node, settings);\n  updateFovCone(currentNodeId);\n  claimTheImmortal(currentNodeId);\n}\n\nfunction saveCustomZonePreset() {\n  if (!currentNodeId) return;\n  const node = NODE_DATA[currentNodeId];\n  if (!node.fovEnabled) {\n    showAlert(t(\"dialogs.enableZoneFirst\"));\n    return;\n  }\n  showPrompt(t(\"dialogs.enterPresetName\"), \"\").then(name => {\n    if (!name || !name.trim()) return;\n\n    const presetId = name.toLowerCase().replace(/[^a-z0-9]+/g, \"-\");\n    ZONE_PRESETS[presetId] = {\n      fovAngle: node.fovAngle,\n      fovDistance: node.fovDistance,\n      fovInnerRadius: node.fovInnerRadius,\n      fovColor: node.fovColor,\n      fovOpacity: node.fovOpacity,\n      fovGradient: node.fovGradient,\n      fovBorderColor: node.fovBorderColor,\n      fovBorderWidth: node.fovBorderWidth,\n      fovBorderStyle: node.fovBorderStyle,\n      fovBorderOpacity: node.fovBorderOpacity,\n      fovAnimationType: node.fovAnimationType,\n      fovAnimate: node.fovAnimate,\n      fovSweep: node.fovSweep,\n      fovSpeed: node.fovSpeed\n    };\n    const select = document.getElementById(\"fov-preset\");\n    if (select && !select.querySelector(`option[value=\"${presetId}\"]`)) {\n      const opt = document.createElement(\"option\");\n      opt.value = presetId;\n      opt.textContent = name.trim() + \" (Custom)\";\n      select.appendChild(opt);\n    }\n    showAlert(t(\"dialogs.presetSaved\", { name: name.trim() }));\n  });\n}\n\nfunction bulkCopyZoneStyle() {\n  if (selectedNodes.size === 0) {\n    showAlert(t(\"dialogs.selectNodeFirst\"));\n    return;\n  }\n  const firstId = [...selectedNodes][0];\n  if (copyZoneStyle(firstId)) {\n    showAlert(t(\"dialogs.zoneStyleCopied\"));\n  }\n}\n\nfunction bulkPasteZoneStyle() {\n  if (selectedNodes.size === 0) {\n    showAlert(t(\"dialogs.selectNodesFirst\"));\n    return;\n  }\n  if (!copiedZoneStyle) {\n    showAlert(t(\"dialogs.copyZoneStyleFirst\"));\n    return;\n  }\n  pushUndo(\"bulk paste zone style\");\n  let count = 0;\n  selectedNodes.forEach(id => {\n    const node = NODE_DATA[id];\n    if (node && hasCoverageZone(node.shape)) {\n      Object.assign(node, copiedZoneStyle);\n      updateFovCone(id);\n      count++;\n    }\n  });\n  forgeTheTopology();\n  showAlert(t(\"dialogs.zoneStylePasted\", { count }));\n}\n\nfunction bulkToggleZones() {\n  if (selectedNodes.size === 0) {\n    showAlert(t(\"dialogs.selectNodesFirst\"));\n    return;\n  }\n  pushUndo(\"bulk toggle zones\");\n  const firstNode = NODE_DATA[[...selectedNodes][0]];\n  const newState = !(firstNode?.fovEnabled);\n  let count = 0;\n  selectedNodes.forEach(id => {\n    const node = NODE_DATA[id];\n    if (node && hasCoverageZone(node.shape)) {\n      node.fovEnabled = newState;\n      updateFovCone(id);\n      count++;\n    }\n  });\n  forgeTheTopology();\n  showAlert(t(\"dialogs.zonesToggled\", { count, state: newState ? t(\"dialogs.on\") : t(\"dialogs.off\") }));\n}\n\nfunction loadCustomPresets() {\n  const select = document.getElementById(\"fov-preset\");\n  if (!select) return;\n  Object.keys(ZONE_PRESETS).forEach(id => {\n    if (!select.querySelector(`option[value=\"${id}\"]`)) {\n      const opt = document.createElement(\"option\");\n      opt.value = id;\n      opt.textContent = id.replace(/-/g, \" \") + \" ★\";\n      select.appendChild(opt);\n    }\n  });\n}\n\nfunction updateZoneLegend() {\n  const container = document.getElementById(\"edge-legend\");\n  if (!container) return;\n  container.querySelectorAll(\".zone-legend-item\").forEach(el => el.remove());\n  container.querySelectorAll(\".zone-legend-title\").forEach(el => el.remove());\n  const zoneColors = new Set();\n  Object.values(NODE_DATA).forEach(node => {\n    if (hasCoverageZone(node.shape) && node.fovEnabled && node.fovColor) {\n      zoneColors.add(node.fovColor);\n    }\n  });\n  \n  if (zoneColors.size === 0) return;\n  const zoneTitle = document.createElement(\"div\");\n  zoneTitle.className = \"legend-title zone-legend-title\";\n  zoneTitle.textContent = t(\"legends.zoneLegend\");\n  zoneTitle.style.marginTop = \"12px\";\n  zoneTitle.style.paddingTop = \"8px\";\n  zoneTitle.style.borderTop = \"1px solid var(--edge-main)\";\n  container.appendChild(zoneTitle);\n  zoneColors.forEach(color => {\n    if (!ZONE_LEGEND[color]) {\n      ZONE_LEGEND[color] = t(\"legends.coverageZone\");\n    }\n    const item = document.createElement(\"div\");\n    item.className = \"legend-item zone-legend-item\";\n    item.addEventListener(\"mousedown\", (e) => e.stopPropagation());\n    item.addEventListener(\"click\", (e) => e.stopPropagation());\n    \n    const swatch = document.createElement(\"span\");\n    swatch.className = \"legend-swatch\";\n    swatch.style.backgroundColor = color;\n    swatch.style.opacity = \"0.5\";\n    swatch.style.cursor = \"pointer\";\n    swatch.addEventListener(\"click\", (e) => {\n      e.stopPropagation();\n      const nodeWithColor = Object.entries(NODE_DATA).find(([id, n]) => \n        hasCoverageZone(n.shape) && n.fovEnabled && n.fovColor === color\n      );\n      if (nodeWithColor) {\n        claimTheImmortal(nodeWithColor[0]);\n      }\n    });\n    \n    const label = document.createElement(\"span\");\n    label.className = \"legend-label\";\n    label.textContent = ZONE_LEGEND[color];\n    label.contentEditable = true;\n    label.addEventListener(\"focus\", () => label.classList.add(\"editing\"));\n    label.addEventListener(\"blur\", () => {\n      label.classList.remove(\"editing\");\n      ZONE_LEGEND[color] = label.textContent.trim() || \"Coverage Zone\";\n    });\n    label.addEventListener(\"keydown\", (e) => {\n      if (e.key === \"Enter\") {\n        e.preventDefault();\n        label.blur();\n      }\n    });\n    \n    item.append(swatch, label);\n    container.appendChild(item);\n  });\n  \n  updateLegendVisibility();\n}\n\t  function isViewOnly() {\n       return PAGE_STATE.viewOnly === true;\n      }\n\t  let viewOnlyClickCount = 0;\n      let viewOnlyClickTimer = null;\n      let viewOnlyClickTarget = null;\n      function handleViewOnlyClick(id, type) {\n       if (!isViewOnly()) return false;\n       if (viewOnlyClickTarget !== id) {\n        viewOnlyClickCount = 0;\n        viewOnlyClickTarget = id;\n       }\n       viewOnlyClickCount++;\n       clearTimeout(viewOnlyClickTimer);\n       viewOnlyClickTimer = setTimeout(() => {\n        viewOnlyClickCount = 0;\n        viewOnlyClickTarget = null;\n       }, 2000);\n       if (viewOnlyClickCount >= 5) {\n        viewOnlyClickCount = 0;\n        viewOnlyClickTarget = null;\n        document.body.classList.add(\"view-only-inspect\");\n        if (type === 'node') {\n         currentNodeId = id;\n         const data = NODE_DATA[id];\n         if (data) {\n          document.querySelectorAll(\".node-group\").forEach((n) => {\n           n.classList.toggle(\"active\", n.dataset.nodeId === id);\n          });\n          document.getElementById(\"node-panel\").style.display = \"block\";\n          document.getElementById(\"edge-panel\").style.display = \"none\";\n          document.getElementById(\"rect-panel\").style.display = \"none\";\n          document.getElementById(\"text-panel\").style.display = \"none\";\n          document.getElementById(\"image-panel\").style.display = \"none\";\n          document.getElementById(\"node-name\").textContent = data.name;\n          const nodeIpEl = document.getElementById(\"node-ip\");\n          const modeLabels = LANG.modes[PAGE_STATE.mappingMode] || LANG.modes.network;\n          if (data.ip) {\n            nodeIpEl.textContent = data.ip;\n            nodeIpEl.style.opacity = \"1\";\n          } else {\n            nodeIpEl.textContent = \"Click to add \" + modeLabels.subtitle;\n            nodeIpEl.style.opacity = \"0.5\";\n          }\n\t\t\tconst fovSection = document.getElementById(\"fov-section\");\n\t\t\tif (fovSection) {\n\t\t\t  if (hasCoverageZone(data.shape)) {\n\t\t\t\tconst defaults = getCoverageDefaults(data.shape);\n\t\t\t\tfovSection.style.display = \"block\";\n\t\t\t\tdocument.getElementById(\"fov-enabled\").checked = data.fovEnabled || false;\n\t\t\t\tdocument.getElementById(\"fov-angle\").value = data.fovAngle || defaults.angle;\n\t\t\t\tdocument.getElementById(\"fov-angle-value\").textContent = (data.fovAngle || defaults.angle) + \"°\";\n\t\t\t\tdocument.getElementById(\"fov-distance\").value = data.fovDistance || defaults.distance;\n\t\t\t\tdocument.getElementById(\"fov-distance-value\").textContent = data.fovDistance || defaults.distance;\n\t\t\t\tdocument.getElementById(\"fov-inner-radius\").value = data.fovInnerRadius || 0;\n\t\t\t\tdocument.getElementById(\"fov-inner-radius-value\").textContent = data.fovInnerRadius || 0;\n\t\t\t\tdocument.getElementById(\"fov-rotation\").value = data.fovRotation || 0;\n\t\t\t\tdocument.getElementById(\"fov-rotation-value\").textContent = (data.fovRotation || 0) + \"°\";\n\t\t\t\tdocument.getElementById(\"fov-color\").value = data.fovColor || \"#f59e0b\";\n\t\t\t\tdocument.getElementById(\"fov-opacity\").value = data.fovOpacity || 20;\n\t\t\t\tdocument.getElementById(\"fov-opacity-value\").textContent = (data.fovOpacity || 20) + \"%\";\n\t\t\t\tdocument.getElementById(\"fov-gradient\").checked = data.fovGradient || false;\n\t\t\t\tdocument.getElementById(\"fov-border-color\").value = data.fovBorderColor || \"#f59e0b\";\n\t\t\t\tdocument.getElementById(\"fov-border-width\").value = data.fovBorderWidth ?? 2;\n\t\t\t\tdocument.getElementById(\"fov-border-width-value\").textContent = data.fovBorderWidth ?? 2;\n\t\t\t\tdocument.getElementById(\"fov-border-style\").value = data.fovBorderStyle || \"solid\";\n\t\t\t\tdocument.getElementById(\"fov-border-opacity\").value = data.fovBorderOpacity ?? 100;\n\t\t\t\tdocument.getElementById(\"fov-border-opacity-value\").textContent = (data.fovBorderOpacity ?? 100) + \"%\";\n\t\t\t\tdocument.getElementById(\"fov-label\").value = data.fovLabel || \"\";\n\t\t\t\tdocument.getElementById(\"fov-label-position\").value = data.fovLabelPosition || \"center\";\n\t\t\t\tdocument.getElementById(\"fov-label-size\").value = data.fovLabelSize || 14;\n\t\t\t\tdocument.getElementById(\"fov-label-size-value\").textContent = (data.fovLabelSize || 14) + \"px\";\n\t\t\t\tdocument.getElementById(\"fov-label-color\").value = data.fovLabelColor || \"#ffffff\";\n\t\t\t\tdocument.getElementById(\"fov-label-bold\").checked = data.fovLabelBold || false;\n\t\t\t\tdocument.getElementById(\"fov-label-bg\").checked = data.fovLabelBg || false;\n\t\t\t\tdocument.getElementById(\"fov-label-bg-color\").value = data.fovLabelBgColor || \"#000000\";\n\t\t\t\tdocument.getElementById(\"fov-label-offset-x\").value = data.fovLabelOffsetX || 0;\n\t\t\t\tdocument.getElementById(\"fov-label-offset-x-value\").textContent = data.fovLabelOffsetX || 0;\n\t\t\t\tdocument.getElementById(\"fov-label-offset-y\").value = data.fovLabelOffsetY || 0;\n\t\t\t\tdocument.getElementById(\"fov-label-offset-y-value\").textContent = data.fovLabelOffsetY || 0;\n\t\t\t\tdocument.getElementById(\"fov-animate\").checked = data.fovAnimate || false;\n\t\t\t\tdocument.getElementById(\"fov-animation-type\").value = data.fovAnimationType || defaults.animationType;\n\t\t\t\tdocument.getElementById(\"fov-sweep\").value = data.fovSweep || 120;\n\t\t\t\tdocument.getElementById(\"fov-sweep-value\").textContent = (data.fovSweep || 120) + \"°\";\n\t\t\t\tdocument.getElementById(\"fov-speed\").value = data.fovSpeed || 4;\n\t\t\t\tdocument.getElementById(\"fov-speed-value\").textContent = (data.fovSpeed || 4) + \"s\";\n\t\t\t\tdocument.getElementById(\"fov-opacity\").value = data.fovOpacity || 20;\n\t\t\t\tdocument.getElementById(\"fov-opacity-value\").textContent = (data.fovOpacity || 20) + \"%\";\n\t\t\t  } else {\n\t\t\t\tfovSection.style.display = \"none\";\n\t\t\t  }\n\t\t\t}\n          document.getElementById(\"node-role\").textContent = data.role;\n          document.getElementById(\"node-mac\").textContent = data.mac || \"--\";\n          document.getElementById(\"node-rack\").textContent = data.rackUnit || \"--\";\n          document.getElementById(\"node-uheight\").textContent = (data.uHeight || \"1\") + \"U\";\n          const tagList = document.getElementById(\"node-tags\");\n          if (tagList) {\n           tagList.innerHTML = \"\";\n           if (data.tags && data.tags.length > 0) {\n            data.tags.forEach((tag) => {\n             const li = document.createElement(\"li\");\n             li.textContent = tag;\n             tagList.appendChild(li);\n            });\n           }\n          }\n          renderNotesFeed(data.notes || [], \"node-notes\", \"node\", id);\n         }\n        } else if (type === 'edge') {\n         currentEdgeId = id;\n         const edge = EDGE_DATA.list.find(e => e.id === id);\n         if (edge) {\n          document.querySelectorAll(\".edge\").forEach((e) => {\n           e.classList.toggle(\"active\", e.dataset.edgeId === id);\n          });\n          document.getElementById(\"edge-panel\").style.display = \"block\";\n          document.getElementById(\"node-panel\").style.display = \"none\";\n          document.getElementById(\"rect-panel\").style.display = \"none\";\n          document.getElementById(\"text-panel\").style.display = \"none\";\n          document.getElementById(\"image-panel\").style.display = \"none\";\n          document.getElementById(\"edge-from\").textContent = NODE_DATA[edge.from]?.name || edge.from;\n          document.getElementById(\"edge-to\").textContent = NODE_DATA[edge.to]?.name || edge.to;\n          document.getElementById(\"edge-label\").value = edge.label || \"\";\n         }\n        }\n        return true;\n       }\n       return false;\n      }\n      const CANVAS_WIDTH = 4000;\n      let CANVAS_HEIGHT = 3000;\n      const BASE_CANVAS_HEIGHT = 3000;\n      const CANVAS_PADDING = 100;\n      const RACK_U_HEIGHT = 70;\n      const RACK_WIDTH = 600;\n      const RACK_START_X = CANVAS_WIDTH / 2;\n      const RACK_START_Y = CANVAS_PADDING + 100;\n      function getRackUHeight(rackId) {\n      const capacity = getRackCapacity(rackId);\n      const availableHeight = CANVAS_HEIGHT - RACK_START_Y - 200;\n      return Math.floor(availableHeight / capacity);\n      }\n      let canvasState = {\n       zoom: 1,\n       panX: 0,\n       panY: 0,\n       minZoom: 0.25,\n       maxZoom: 4,\n       isPanning: false,\n       panStartX: 0,\n       panStartY: 0,\n       spacePressed: false,\n      };\n      function getViewBox() {\n       const viewWidth = CANVAS_WIDTH / canvasState.zoom;\n       const viewHeight = CANVAS_HEIGHT / canvasState.zoom;\n       return {\n        x: canvasState.panX,\n        y: canvasState.panY,\n        width: viewWidth,\n        height: viewHeight,\n       };\n      }\nfunction updateViewBox() {\n  const svg = document.getElementById(\"map\");\n  const vb = getViewBox();\n  svg.setAttribute(\"viewBox\", `${vb.x} ${vb.y} ${vb.width} ${vb.height}`);\n  const zoomLevel = document.getElementById(\"zoom-level\");\n  if (zoomLevel) {\n    zoomLevel.textContent = Math.round(canvasState.zoom * 100) + \"%\";\n  }\n  if (canvasState.zoom < 0.5) {\n    svg.classList.add(\"low-zoom\");\n  } else {\n    svg.classList.remove(\"low-zoom\");\n  }\n  updateMinimap();\n  populateRackDropdown();\n}\n\t  \nlet lastMinimapRender = 0;\nconst MINIMAP_THROTTLE = 100;\n\nfunction updateMinimap() {\n  const now = performance.now();\n  if (now - lastMinimapRender < MINIMAP_THROTTLE) return;\n  lastMinimapRender = now;\n  \n  const minimapViewport = document.getElementById(\"minimap-viewport\");\n  const minimapSvg = document.getElementById(\"minimap\");\n  if (!minimapViewport || !minimapSvg) return;\n  const vb = getViewBox();\n  minimapViewport.setAttribute(\"x\", vb.x);\n  minimapViewport.setAttribute(\"y\", vb.y);\n  minimapViewport.setAttribute(\"width\", vb.width);\n  minimapViewport.setAttribute(\"height\", vb.height);\n  minimapSvg.querySelectorAll(\".minimap-node, .minimap-edge, .minimap-wall, .minimap-rect\").forEach(el => el.remove());\n  const frag = document.createDocumentFragment();\n  const ns = \"http://www.w3.org/2000/svg\";\n  const dotColor = PAGE_STATE.minimapDots || \"#94a3b8\";\n\n  if (RECT_DATA && RECT_DATA.list && currentView.mode !== \"rack\") {\n    RECT_DATA.list.forEach((rect) => {\n      if (rect.lineStyle === \"wall\") {\n        const wallRect = document.createElementNS(ns, \"rect\");\n        wallRect.setAttribute(\"x\", rect.x);\n        wallRect.setAttribute(\"y\", rect.y);\n        wallRect.setAttribute(\"width\", rect.width);\n        wallRect.setAttribute(\"height\", rect.height);\n        wallRect.style.fill = rect.color || \"#666\";\n        wallRect.style.fillOpacity = \"0.6\";\n        wallRect.style.stroke = rect.borderColor || rect.color || \"#666\";\n        wallRect.style.strokeWidth = \"4\";\n        wallRect.classList.add(\"minimap-wall\");\n        frag.appendChild(wallRect);\n      }\n    });\n  }\n\n  EDGE_DATA.list.forEach((edge) => {\n    if (edge.type === \"custom\") {\n      if (Array.isArray(edge.points) && edge.points.length >= 2) {\n        const polyline = document.createElementNS(ns, \"polyline\");\n        polyline.setAttribute(\"points\", edge.points.map(p => `${p.x},${p.y}`).join(\" \"));\n        polyline.classList.add(\"minimap-edge\");\n        frag.appendChild(polyline);\n      }\n      return;\n    }\n    const fromNode = NODE_DATA[edge.from];\n    const toNode = NODE_DATA[edge.to];\n    if (!fromNode || !toNode) return;\n    if (currentView.mode === \"rack\") {\n      if (fromNode.assignedRack !== currentView.rackId || toNode.assignedRack !== currentView.rackId) return;\n    } else {\n      if (fromNode.assignedRack || toNode.assignedRack) return;\n    }\n    const p1 = savedPositions[edge.from];\n    const p2 = savedPositions[edge.to];\n    if (!p1 || !p2) return;\n    const routing = edge.routing || PAGE_STATE.defaultEdgeRouting || \"curved\";\n    \n    if (routing === \"orthogonal\") {\n      const dx = p2.x - p1.x;\n      const dy = p2.y - p1.y;\n      const polyline = document.createElementNS(ns, \"polyline\");\n      let points;\n      if (Math.abs(dx) > Math.abs(dy)) {\n        const midX = p1.x + dx / 2;\n        points = `${p1.x},${p1.y} ${midX},${p1.y} ${midX},${p2.y} ${p2.x},${p2.y}`;\n      } else {\n        const midY = p1.y + dy / 2;\n        points = `${p1.x},${p1.y} ${p1.x},${midY} ${p2.x},${midY} ${p2.x},${p2.y}`;\n      }\n      polyline.setAttribute(\"points\", points);\n      polyline.classList.add(\"minimap-edge\");\n      frag.appendChild(polyline);\n    } else {\n      const line = document.createElementNS(ns, \"line\");\n      line.setAttribute(\"x1\", p1.x);\n      line.setAttribute(\"y1\", p1.y);\n      line.setAttribute(\"x2\", p2.x);\n      line.setAttribute(\"y2\", p2.y);\n      line.classList.add(\"minimap-edge\");\n      frag.appendChild(line);\n    }\n  });\n\n  Object.entries(savedPositions).forEach(([id, pos]) => {\n    const node = NODE_DATA[id];\n    if (!node) return;\n    if (currentView.mode === \"rack\") {\n      if (node.assignedRack !== currentView.rackId) return;\n    } else {\n      if (node.assignedRack) return;\n    }\n    const nodeSize = savedSizes[id] || 55;\n    const s = nodeSize * 0.5;\n    \n    if (node.isRack) {\n      const rect = document.createElementNS(ns, \"rect\");\n      rect.setAttribute(\"x\", pos.x - s);\n      rect.setAttribute(\"y\", pos.y - s);\n      rect.setAttribute(\"width\", s * 2);\n      rect.setAttribute(\"height\", s * 2);\n      rect.style.fill = dotColor;\n      rect.classList.add(\"minimap-node\");\n      frag.appendChild(rect);\n    } else if (hasCoverageZone(node.shape)) {\n      const diamond = document.createElementNS(ns, \"polygon\");\n      const points = `${pos.x},${pos.y - s} ${pos.x + s},${pos.y} ${pos.x},${pos.y + s} ${pos.x - s},${pos.y}`;\n      diamond.setAttribute(\"points\", points);\n      diamond.style.fill = \"none\";\n      diamond.style.stroke = dotColor;\n      diamond.style.strokeWidth = \"6\";\n      diamond.classList.add(\"minimap-node\");\n      frag.appendChild(diamond);\n    } else {\n      const circle = document.createElementNS(ns, \"circle\");\n      circle.setAttribute(\"cx\", pos.x);\n      circle.setAttribute(\"cy\", pos.y);\n      circle.setAttribute(\"r\", s);\n      circle.style.fill = dotColor;\n      circle.classList.add(\"minimap-node\");\n      frag.appendChild(circle);\n    }\n  });\n\n  minimapSvg.insertBefore(frag, minimapViewport);\n}\n\n      function zoomTo(newZoom, centerX, centerY) {\n       const oldZoom = canvasState.zoom;\n       newZoom = Math.max(canvasState.minZoom, Math.min(canvasState.maxZoom, newZoom));\n       if (centerX !== undefined && centerY !== undefined) {\n        const oldWidth = CANVAS_WIDTH / oldZoom;\n        const oldHeight = CANVAS_HEIGHT / oldZoom;\n        const newWidth = CANVAS_WIDTH / newZoom;\n        const newHeight = CANVAS_HEIGHT / newZoom;\n        const pointX = canvasState.panX + centerX * oldWidth;\n        const pointY = canvasState.panY + centerY * oldHeight;\n        canvasState.panX = pointX - centerX * newWidth;\n        canvasState.panY = pointY - centerY * newHeight;\n       }\n       canvasState.zoom = newZoom;\n       constrainPan();\n       updateViewBox();\n      }\n      function constrainPan() {\n       const viewWidth = CANVAS_WIDTH / canvasState.zoom;\n       const viewHeight = CANVAS_HEIGHT / canvasState.zoom;\n       const minVisiblePortion = 0.1;\n       const maxPanX = CANVAS_WIDTH - viewWidth * minVisiblePortion;\n       const maxPanY = CANVAS_HEIGHT - viewHeight * minVisiblePortion;\n       const minPanX = -viewWidth * (1 - minVisiblePortion);\n       const minPanY = -viewHeight * (1 - minVisiblePortion);\n       canvasState.panX = Math.max(minPanX, Math.min(maxPanX, canvasState.panX));\n       canvasState.panY = Math.max(minPanY, Math.min(maxPanY, canvasState.panY));\n      }\n      function fitToContent() {\n       const positions = Object.values(savedPositions);\n       if (positions.length === 0) {\n        resetView();\n        return;\n       }\n       let minX = Infinity,\n        minY = Infinity,\n        maxX = -Infinity,\n        maxY = -Infinity;\n       positions.forEach((pos) => {\n        minX = Math.min(minX, pos.x - 100);\n        minY = Math.min(minY, pos.y - 100);\n        maxX = Math.max(maxX, pos.x + 100);\n        maxY = Math.max(maxY, pos.y + 100);\n       });\n       const contentWidth = maxX - minX + 200;\n       const contentHeight = maxY - minY + 200;\n       const viewport = document.getElementById(\"canvas-viewport\");\n       const vpRect = viewport.getBoundingClientRect();\n       const aspectRatio = vpRect.width / vpRect.height;\n       const contentAspect = contentWidth / contentHeight;\n       let newZoom;\n       if (contentAspect > aspectRatio) {\n        newZoom = (vpRect.width / contentWidth) * (CANVAS_WIDTH / vpRect.width);\n       } else {\n        newZoom = (vpRect.height / contentHeight) * (CANVAS_HEIGHT / vpRect.height);\n       }\n       newZoom = Math.max(canvasState.minZoom, Math.min(2, newZoom));\n       const viewWidth = CANVAS_WIDTH / newZoom;\n       const viewHeight = CANVAS_HEIGHT / newZoom;\n       canvasState.zoom = newZoom;\n       canvasState.panX = minX - 100 - (viewWidth - contentWidth) / 2;\n       canvasState.panY = minY - 100 - (viewHeight - contentHeight) / 2;\n       constrainPan();\n       updateViewBox();\n      }\n      function resetView() {\n       canvasState.zoom = 1;\n       canvasState.panX = 0;\n       canvasState.panY = 0;\n       updateViewBox();\n      }\n      function focusOnNodes(nodeIds) {\n       if (!nodeIds || nodeIds.length === 0) return;\n       const positions = nodeIds.map(id => savedPositions[id]).filter(Boolean);\n       if (positions.length === 0) return;\n       let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;\n       positions.forEach(pos => {\n        minX = Math.min(minX, pos.x);\n        minY = Math.min(minY, pos.y);\n        maxX = Math.max(maxX, pos.x);\n        maxY = Math.max(maxY, pos.y);\n       });\n       const padding = 150;\n       const contentWidth = Math.max(maxX - minX + padding * 2, 300);\n       const contentHeight = Math.max(maxY - minY + padding * 2, 300);\n       const centerX = (minX + maxX) / 2;\n       const centerY = (minY + maxY) / 2;\n       const viewport = document.getElementById(\"canvas-viewport\");\n       const vpRect = viewport.getBoundingClientRect();\n       const aspectRatio = vpRect.width / vpRect.height;\n       const contentAspect = contentWidth / contentHeight;\n       let newZoom;\n       if (contentAspect > aspectRatio) {\n        newZoom = (vpRect.width / contentWidth) * (CANVAS_WIDTH / vpRect.width);\n       } else {\n        newZoom = (vpRect.height / contentHeight) * (CANVAS_HEIGHT / vpRect.height);\n       }\n       newZoom = Math.max(canvasState.minZoom, Math.min(1.5, newZoom));\n       const viewWidth = CANVAS_WIDTH / newZoom;\n       const viewHeight = CANVAS_HEIGHT / newZoom;\n       canvasState.zoom = newZoom;\n       canvasState.panX = centerX - viewWidth / 2;\n       canvasState.panY = centerY - viewHeight / 2;\n       constrainPan();\n       updateViewBox();\n      }\n      window.addEventListener(\"DOMContentLoaded\", () => {\n       const toggle = document.getElementById(\"mobile-menu-toggle\");\n       const menu = document.getElementById(\"topbar-menu\");\n       if (!toggle || !menu) return;\n       toggle.addEventListener(\"click\", () => {\n        menu.classList.toggle(\"open\");\n       });\n       menu.addEventListener(\"click\", (e) => {\n        const target = e.target.closest(\"a, button\");\n        if (!target) return;\n        if (isMobileDevice()) {\n       menu.classList.remove(\"open\");\n      }\n       });\n       const minimapCloseBtn = document.getElementById(\"minimap-close-btn\");\n       if (minimapCloseBtn) {\n        const handleMinimapClose = (e) => {\n         e.stopPropagation();\n         e.preventDefault();\n         minimapCollapsed = true;\n         updateMinimapVisibility();\n        };\n        minimapCloseBtn.addEventListener(\"click\", handleMinimapClose);\n        minimapCloseBtn.addEventListener(\"touchend\", handleMinimapClose);\n       }\n       const drawToolbarCloseBtn = document.getElementById(\"draw-toolbar-close-btn\");\n       if (drawToolbarCloseBtn) {\n        const handleDrawClose = (e) => {\n         e.stopPropagation();\n         e.preventDefault();\n         drawToolbarCollapsed = true;\n         updateDrawToolbarVisibility();\n        };\n        drawToolbarCloseBtn.addEventListener(\"click\", handleDrawClose);\n        drawToolbarCloseBtn.addEventListener(\"touchend\", handleDrawClose);\n       }\n       const topologyToolbarCloseBtn = document.getElementById(\"topology-toolbar-close-btn\");\n       if (topologyToolbarCloseBtn) {\n        const handleTopologyClose = (e) => {\n         e.stopPropagation();\n         e.preventDefault();\n         topologyToolbarCollapsed = true;\n         updateTopologyToolbarVisibility();\n        };\n        topologyToolbarCloseBtn.addEventListener(\"click\", handleTopologyClose);\n        topologyToolbarCloseBtn.addEventListener(\"touchstart\", (e) => e.preventDefault(), {\n         passive: false\n        });\n        topologyToolbarCloseBtn.addEventListener(\"touchend\", handleTopologyClose);\n       }\n       updateMinimapVisibility();\n       updateDrawToolbarVisibility();\n       updateTopologyToolbarVisibility();\n      });\n      function applyLayerFilter() {\n       activeLayers.clear();\n       if (document.getElementById(\"layer-1\").checked) activeLayers.add(\"layer1\");\n       if (document.getElementById(\"layer-2\").checked) activeLayers.add(\"layer2\");\n       if (document.getElementById(\"layer-3\").checked) activeLayers.add(\"layer3\");\n       if (document.getElementById(\"layer-4\").checked) activeLayers.add(\"layer4\");\n       forgeTheTopology();\n      }\n      function isNodeVisible(nodeId) {\n       const node = NODE_DATA[nodeId];\n       if (!node) return false;\n       let nodeLayer = node.layer || \"layer1\";\n       const layerMap = { \"physical\": \"layer1\", \"logical\": \"layer2\", \"security\": \"layer3\", \"application\": \"layer4\" };\n       if (layerMap[nodeLayer]) nodeLayer = layerMap[nodeLayer];\n       return activeLayers.has(nodeLayer);\n      }\n      function updateLayerLabels() {\n       const mode = PAGE_STATE.mappingMode || 'network';\n       const layers = LANG.ui?.layers?.[mode] || LANG.ui?.layers?.network;\n       if (layers) {\n        const label1 = document.getElementById('layer-1-label');\n        const label2 = document.getElementById('layer-2-label');\n        const label3 = document.getElementById('layer-3-label');\n        const label4 = document.getElementById('layer-4-label');\n        if (label1) label1.textContent = layers.layer1 || 'Layer 1';\n        if (label2) label2.textContent = layers.layer2 || 'Layer 2';\n        if (label3) label3.textContent = layers.layer3 || 'Layer 3';\n        if (label4) label4.textContent = layers.layer4 || 'Layer 4';\n        const opt1 = document.getElementById('node-layer-opt1');\n        const opt2 = document.getElementById('node-layer-opt2');\n        const opt3 = document.getElementById('node-layer-opt3');\n        const opt4 = document.getElementById('node-layer-opt4');\n        if (opt1) opt1.textContent = layers.layer1 || 'Layer 1';\n        if (opt2) opt2.textContent = layers.layer2 || 'Layer 2';\n        if (opt3) opt3.textContent = layers.layer3 || 'Layer 3';\n        if (opt4) opt4.textContent = layers.layer4 || 'Layer 4';\n       }\n      }\n      function enterRack(rackId) {\n      if (!NODE_DATA[rackId] || !NODE_DATA[rackId].isRack) return;\n      const rackCapacity = getRackCapacity(rackId);\n      const neededHeight = RACK_START_Y + (rackCapacity * RACK_U_HEIGHT) + 200;\n      if (neededHeight > BASE_CANVAS_HEIGHT) {\n      CANVAS_HEIGHT = neededHeight;\n      }\n       savedTopologyView = {\n        zoom: canvasState.zoom,\n        panX: canvasState.panX,\n        panY: canvasState.panY\n       };\n       currentView.mode = \"rack\";\n       currentView.rackId = rackId;\n       const backBtn = document.getElementById(\"back-to-topology-btn\");\n       if (backBtn) {\n        backBtn.style.display = \"inline-block\";\n        const modeLabels = {\n         'network': 'Topology',\n         'sports': 'Sports',\n         'smart-home': 'Smart Home',\n         'floor-plan': 'Floor Plan'\n        };\n        const currentMode = PAGE_STATE.mappingMode || 'network';\n        backBtn.textContent = '← Back to ' + (modeLabels[currentMode] || 'Topology');\n       }\n       const hint = document.getElementById(\"canvas-hint\");\n       if (hint) {\n        hint.textContent = t(\"messages.rackViewHint\", { name: NODE_DATA[rackId]?.name || t(\"modes.network.rack\") });\n        hint.classList.add(\"visible\");\n       }\n      const rackUHeight = getRackUHeight(rackId);\n      const rackHeight = rackCapacity * rackUHeight;\n      const rackCenterX = RACK_START_X;\n      const rackCenterY = RACK_START_Y + (rackHeight / 2);\n       const viewWidth = CANVAS_WIDTH / canvasState.zoom;\n       const viewHeight = CANVAS_HEIGHT / canvasState.zoom;\n       canvasState.panX = rackCenterX - (viewWidth / 2);\n       canvasState.panY = rackCenterY - (viewHeight / 2);\n       constrainPan();\n       updateViewBox();\n       forgeTheTopology();\n      }\n      function exitRack() {\n      CANVAS_HEIGHT = BASE_CANVAS_HEIGHT;\n      currentView.mode = \"topology\";\n      currentView.rackId = null;\n       const backBtn = document.getElementById(\"back-to-topology-btn\");\n       if (backBtn) backBtn.style.display = \"none\";\n       const hint = document.getElementById(\"canvas-hint\");\n       if (hint) {\n        hint.classList.remove(\"visible\");\n       }\n       if (savedTopologyView) {\n        canvasState.zoom = savedTopologyView.zoom;\n        canvasState.panX = savedTopologyView.panX;\n        canvasState.panY = savedTopologyView.panY;\n        updateViewBox();\n       }\n       forgeTheTopology();\n      }\n      function getRackCapacity(rackId) {\n       const node = NODE_DATA[rackId];\n       return node && node.rackCapacity ? parseInt(node.rackCapacity) : 42;\n      }\n      function checkRackSlotCollisions(rackId) {\n       const collisions = [];\n       const nodesInRack = Object.entries(NODE_DATA).filter(([id, node]) => node.assignedRack === rackId && !node.isRack);\n       for (let i = 0; i < nodesInRack.length; i++) {\n        const [id1, node1] = nodesInRack[i];\n        const unit1 = parseInt(node1.rackUnit) || 1;\n        const height1 = parseInt(node1.uHeight) || 1;\n        const start1 = unit1;\n        const end1 = unit1 + height1 - 1;\n        for (let j = i + 1; j < nodesInRack.length; j++) {\n         const [id2, node2] = nodesInRack[j];\n         const unit2 = parseInt(node2.rackUnit) || 1;\n         const height2 = parseInt(node2.uHeight) || 1;\n         const start2 = unit2;\n         const end2 = unit2 + height2 - 1;\n         if (start1 <= end2 && start2 <= end1) {\n          collisions.push({ node1: id1, node2: id2, name1: node1.name, name2: node2.name, slots1: `U${start1}-U${end1}`, slots2: `U${start2}-U${end2}` });\n         }\n        }\n       }\n       return collisions;\n      }\n      function validateRackPlacement(nodeId, rackId, proposedUnit, uHeight) {\n       const rackCapacity = getRackCapacity(rackId);\n       const endUnit = proposedUnit + uHeight - 1;\n       if (endUnit > rackCapacity) {\n        return { valid: false, message: t(\"dialogs.rackCapacityExceeded\", { startU: proposedUnit, endU: endUnit, capacity: rackCapacity, excess: endUnit - rackCapacity }) };\n       }\n       if (proposedUnit < 1) {\n        return { valid: false, message: t(\"dialogs.invalidRackPosition\") };\n       }\n       return { valid: true };\n      }\n      function populateRackDropdown() {\n       const dropdown = document.getElementById(\"node-assigned-rack\");\n       if (!dropdown) return;\n       const prev = dropdown.value;\n       dropdown.innerHTML = '<option value=\"\">' + t(\"ui.options.none\") + '</option>';\n       Object.keys(NODE_DATA).forEach(id => {\n        if (NODE_DATA[id].isRack) {\n         const option = document.createElement(\"option\");\n         option.value = id;\n         option.textContent = NODE_DATA[id].name;\n         dropdown.appendChild(option);\n        }\n       });\n       if (prev) dropdown.value = prev;\n      }\n      function wieldThePower() {\n       const root = document.documentElement;\n       root.style.setProperty(\"--panel\", PAGE_STATE.panel);\n       root.style.setProperty(\"--panel-alt\", PAGE_STATE.panelAlt);\n       root.style.setProperty(\"--sidebar-bg\", PAGE_STATE.sidebarBg || PAGE_STATE.panelAlt);\n       root.style.setProperty(\"--btn-bg\", PAGE_STATE.btnBg || PAGE_STATE.panel);\n       root.style.setProperty(\"--btn-text\", PAGE_STATE.btnText || \"#e2e8f0\");\n       root.style.setProperty(\"--tag-fill\", PAGE_STATE.tagFill || \"#1e293b\");\n       root.style.setProperty(\"--tag-text\", PAGE_STATE.tagText || \"#e2e8f0\");\n       root.style.setProperty(\"--tag-border\", PAGE_STATE.tagBorder || \"#475569\");\n       root.style.setProperty(\"--input-bg\", PAGE_STATE.inputBg || \"#0b0e13\");\n       root.style.setProperty(\"--input-text\", PAGE_STATE.inputText || \"#e2e8f0\");\n       root.style.setProperty(\"--input-border\", PAGE_STATE.inputBorder || \"#1f2937\");\n       root.style.setProperty(\"--input-font\", PAGE_STATE.inputFont || \"Inter, system-ui, sans-serif\");\n       root.style.setProperty(\"--input-font-size\", (PAGE_STATE.inputFontSize || 14) + \"px\");\n       root.style.setProperty(\"--toolbar-bg\", PAGE_STATE.toolbarBg || \"#0f172a\");\n       root.style.setProperty(\"--toolbar-border\", PAGE_STATE.toolbarBorder || \"#1f2937\");\n       root.style.setProperty(\"--toolbar-text\", PAGE_STATE.toolbarText || \"#94a3b8\");\n       root.style.setProperty(\"--toolbar-btn-bg\", PAGE_STATE.toolbarBtnBg || \"#0b0e13\");\n       root.style.setProperty(\"--toolbar-btn-text\", PAGE_STATE.toolbarBtnText || \"#e2e8f0\");\n       root.style.setProperty(\"--minimap-dots\", PAGE_STATE.minimapDots || \"#94a3b8\");\n       root.style.setProperty(\"--canvas-hint-bg\", PAGE_STATE.canvasHintBg || \"#0f172a\");\n       root.style.setProperty(\"--canvas-hint-color\", PAGE_STATE.canvasHintColor || \"#94a3b8\");\n       const canvasHint = document.getElementById(\"canvas-hint\");\n       if (canvasHint) {\n        canvasHint.style.display = PAGE_STATE.canvasHintEnabled === false ? \"none\" : \"\";\n        if (PAGE_STATE.canvasHintText !== undefined && PAGE_STATE.canvasHintText !== \"\") {\n         canvasHint.textContent = PAGE_STATE.canvasHintText;\n        } else {\n         canvasHint.innerHTML = DEFAULT_CANVAS_HINT;\n        }\n       }\n       root.style.setProperty(\"--accent\", PAGE_STATE.accent);\n       root.style.setProperty(\"--danger\", PAGE_STATE.danger);\n       root.style.setProperty(\"--text-main\", PAGE_STATE.textMain);\n       root.style.setProperty(\"--text-soft\", PAGE_STATE.textSoft);\n       root.style.setProperty(\"--topbar-bg\", PAGE_STATE.topbarBg);\n       root.style.setProperty(\"--topbar-border\", PAGE_STATE.topbarBorder);\n       root.style.setProperty(\"--node-fill\", PAGE_STATE.nodeFill || \"#1e293b\");\n       root.style.setProperty(\"--node-stroke\", PAGE_STATE.nodeStroke || \"#475569\");\n       root.style.setProperty(\"--node-title\", PAGE_STATE.nodeTitle || \"#e2e8f0\");\n       root.style.setProperty(\"--node-sub\", PAGE_STATE.nodeSub || \"#94a3b8\");\n       root.style.setProperty(\"--node-title-size\", (PAGE_STATE.nodeTitleSize || 18) + \"px\");\n       root.style.setProperty(\"--node-sub-size\", (PAGE_STATE.nodeSubSize || 13) + \"px\");\n       root.style.setProperty(\"--node-font\", PAGE_STATE.nodeFont || \"Inter, system-ui, sans-serif\");\n       root.style.setProperty(\"--default-edge\", PAGE_STATE.defaultEdge || \"#475569\");\n       root.style.setProperty(\"--selection-handle\", PAGE_STATE.selectionHandle || \"#f59e0b\");\n       root.style.setProperty(\"--selection-handle-size\", (PAGE_STATE.selectionHandleSize || 8) + \"px\");\n       root.style.setProperty(\"--group-indicator\", PAGE_STATE.groupIndicator || \"#4fd1c5\");\n       const topbarHeight = PAGE_STATE.topbarHeight || 52;\n       const sidebarWidth = PAGE_STATE.sidebarWidth || 350;\n       const mobileFooterHeight = PAGE_STATE.mobileFooterHeight || 40;\n       root.style.setProperty(\"--topbar-height\", topbarHeight + \"px\");\n       root.style.setProperty(\"--sidebar-width\", sidebarWidth + \"px\");\n       root.style.setProperty(\"--mobile-footer-height\", mobileFooterHeight + \"vh\");\n       const mainEl = document.querySelector(\"main\");\n       const detailsPanel = document.getElementById(\"details-panel\");\n       const sidebarToggle = document.getElementById(\"sidebar-toggle\");\n       if (PAGE_STATE.sidebarCollapsed) {\n        if (mainEl) mainEl.classList.add(\"sidebar-collapsed\");\n        if (detailsPanel) detailsPanel.classList.add(\"collapsed\");\n        if (sidebarToggle) {\n         sidebarToggle.classList.add(\"collapsed\");\n         sidebarToggle.textContent = \"▶\";\n        }\n       } else {\n        if (mainEl) mainEl.classList.remove(\"sidebar-collapsed\");\n        if (detailsPanel) detailsPanel.classList.remove(\"collapsed\");\n        if (sidebarToggle) {\n         sidebarToggle.classList.remove(\"collapsed\");\n         sidebarToggle.textContent = \"◀\";\n        }\n       }\n       if (PAGE_STATE.background) {\n        document.body.style.background = PAGE_STATE.background;\n       } else {\n        document.body.style.background = `radial-gradient(circle at top, ${PAGE_STATE.canvasGradientTop || \"#1e2532\"} 0, ${PAGE_STATE.canvasGradientBottom || \"#050608\"} 70%)`;\n       }\n       document.title = PAGE_STATE.title || DEFAULT_PAGE_STATE.title;\n       const titleEl = document.getElementById(\"page-title\");\n       if (titleEl) {\n        titleEl.textContent = PAGE_STATE.title || DEFAULT_PAGE_STATE.title;\n       }\n       const sidebarToggleEl = document.getElementById(\"sidebar-toggle\");\n       const isMobile = isMobileDevice();\n       if (sidebarToggleEl) {\n        sidebarToggleEl.style.display = isMobile ? \"none\" : \"flex\";\n       }\n       const viewOnlyMode = PAGE_STATE.viewOnly === true;\n       const addNodeBtn = document.getElementById(\"add-node-btn\");\n       const addRackBtn = document.getElementById(\"add-rack-btn\");\n       const drawToolbar = document.getElementById(\"draw-toolbar\");\n       const topologyToolbar = document.getElementById(\"topology-toolbar\");\n       const bulkToolbar = document.getElementById(\"bulk-toolbar\");\n       if (addNodeBtn) addNodeBtn.style.display = viewOnlyMode ? \"none\" : \"\";\n       if (addRackBtn) addRackBtn.style.display = viewOnlyMode ? \"none\" : \"\";\n       if (viewOnlyMode) {\n        if (drawToolbar) drawToolbar.style.setProperty('display', 'none', 'important');\n        if (topologyToolbar) topologyToolbar.style.setProperty('display', 'none', 'important');\n        if (bulkToolbar) bulkToolbar.style.display = \"none\";\n        const bulkToolbarMobile = document.getElementById(\"bulk-toolbar-mobile\");\n        if (bulkToolbarMobile) bulkToolbarMobile.style.display = \"none\";\n       }\n       const bulkToolbarMobile = document.getElementById(\"bulk-toolbar-mobile\");\n       if (bulkToolbarMobile) bulkToolbarMobile.style.display = viewOnlyMode ? \"none\" : \"\";\n       [\"draw-toolbar-mini\", \"topology-toolbar-mini\"].forEach(id => {\n        const el = document.getElementById(id);\n        if (el) el.style.display = viewOnlyMode ? \"none\" : \"\";\n       });\n       document.body.classList.toggle(\"view-only-mode\", viewOnlyMode);\n       const mappingModeSelect = document.getElementById(\"mapping-mode-select\");\n       if (mappingModeSelect) mappingModeSelect.value = PAGE_STATE.mappingMode || \"network\";\n       const canvasStyleSelect = document.getElementById(\"canvas-style-select\");\n       if (canvasStyleSelect && PAGE_STATE.canvasTemplate) {\n         canvasStyleSelect.value = PAGE_STATE.canvasTemplate;\n       }\n\t   \n       const mindmapBgColor = document.getElementById(\"mindmap-bg-color\");\n       if (mindmapBgColor && PAGE_STATE.background) {\n         mindmapBgColor.value = PAGE_STATE.background;\n       }\n       if (typeof applyMappingModeLabels === \"function\") applyMappingModeLabels();\n       if (typeof applyLanguage === \"function\") applyLanguage();\n       if (typeof updateCanvasStyleOptions === \"function\") updateCanvasStyleOptions();\n      }\n      (async function awakeTheImmortal() {\n       let initialState = {};\n       let decryptionCancelled = false;\n       const stateEl = document.getElementById(\"topology-state\");\n       if (stateEl && stateEl.textContent.trim()) {\n        try {\n         let stateText = stateEl.textContent.trim();\n         if (isEncrypted(stateText)) {\n          let decrypted = false;\n          let attempts = 0;\n          const maxAttempts = 3;\n          while (!decrypted && attempts < maxAttempts) {\n           const password = await showPrompt(t(\"dialogs.enterDecryptPassword\", { attempt: attempts + 1, max: maxAttempts }));\n           if (!password) {\n            showAlert(t(\"dialogs.decryptionCancelled\"));\n            decryptionCancelled = true;\n            break;\n           }\n           try {\n            stateText = await decryptData(stateText, password);\n            decrypted = true;\n           } catch (e) {\n            attempts++;\n            if (attempts < maxAttempts) {\n             showAlert(t(\"dialogs.incorrectPassword\"));\n            } else {\n             showAlert(t(\"dialogs.maxAttemptsReached\"));\n             decryptionCancelled = true;\n            }\n           }\n          }\n          if (!decrypted) {\n           stateText = \"{}\";\n          }\n         }\n         initialState = JSON.parse(stateText);\n        } catch (e) {\n         console.error(\"Failed to load state:\", e);\n         initialState = {};\n        }\n       }\n       if (decryptionCancelled) {\n        NODE_DATA = {};\n        EDGE_DATA = {\n         list: []\n        };\n        RECT_DATA = { list: [] };\n        TEXT_DATA = { list: [] };\n        IMAGE_DATA = { list: [] };\n        EDGE_LEGEND = {};\n        savedPositions = {};\n        savedSizes = {};\n        savedStyles = {};\n       } else {\n        NODE_DATA = initialState.nodeData ? initialState.nodeData : BASE_NODE_DATA;\n        EDGE_DATA = initialState.edgeData ? initialState.edgeData : {\n         list: [],\n        };\n        RECT_DATA = initialState.rectData ? initialState.rectData : { list: [] };\n        IMAGE_DATA = initialState.imageData ? initialState.imageData : { list: [] };\n        TEXT_DATA = initialState.textData ? initialState.textData : { list: [] };\n        EDGE_LEGEND = initialState.edgeLegend || {};\n        savedPositions = initialState.nodePositions || {};\n        savedSizes = initialState.nodeSizes || {};\n        savedStyles = initialState.nodeStyles || {};\n       }\n       if (initialState.page) {\n        PAGE_STATE = Object.assign({}, DEFAULT_PAGE_STATE, initialState.page);\n       }\n       if (initialState.canvas) {\n        canvasState.zoom = initialState.canvas.zoom || 1;\n        canvasState.panX = initialState.canvas.panX || 0;\n        canvasState.panY = initialState.canvas.panY || 0;\n       }\n       if (initialState.savedTopologyView) {\n        savedTopologyView = initialState.savedTopologyView;\n       }\n       if (initialState.documentTabs) {\n        documentTabs = initialState.documentTabs;\n        currentTabIndex = initialState.currentTabIndex || 0;\n        const currentTab = documentTabs[currentTabIndex];\n        if (currentTab) {\n          NODE_DATA = currentTab.nodes || NODE_DATA;\n          EDGE_DATA = currentTab.edges || EDGE_DATA;\n          savedPositions = currentTab.positions || savedPositions;\n          savedSizes = currentTab.sizes || savedSizes;\n          savedStyles = currentTab.styles || savedStyles;\n          EDGE_LEGEND = currentTab.legend || EDGE_LEGEND;\n          RECT_DATA = currentTab.rects || RECT_DATA;\n          TEXT_DATA = currentTab.texts || TEXT_DATA;\n      if (currentTab.pageState) PAGE_STATE = Object.assign({}, DEFAULT_PAGE_STATE, currentTab.pageState);\n        }\n       }\n       if (initialState.encryptedSections) {\n        encryptedSections = initialState.encryptedSections;\n       }\n       if (initialState.recordings && Array.isArray(initialState.recordings)) {\n        RECORDING_STATE.recordings = initialState.recordings;\n        RECORDING_STATE.currentRecording = initialState.recordings[0] || null;\n       }\n       loadLanguageFromStorage();\n       applyLanguage();\n       updateCurrentLangDisplay();\n       const layerMap = { \"physical\": \"layer1\", \"logical\": \"layer2\", \"security\": \"layer3\", \"application\": \"layer4\" };\n       Object.values(NODE_DATA).forEach(node => {\n         if (!node.tags) node.tags = [];\n         if (!node.notes) node.notes = [];\n         if (!node.layer || layerMap[node.layer]) node.layer = layerMap[node.layer] || 'layer1';\n       });\n       wieldThePower();\n       forgeTheTopology();\n       updateViewBox();\n       MobileManager.applyAll();\n       const initialNodes = Object.keys(NODE_DATA);\n       if (initialNodes.length > 0) {\n        claimTheImmortal(initialNodes.includes(\"host\") ? \"host\" : initialNodes[0]);\n       } else {\n        document.getElementById(\"node-panel\").style.display = \"none\";\n        document.getElementById(\"topology-toolbar\").style.display = \"none\";\n       }\n       if (Object.keys(NODE_DATA).length === 0 && EDGE_DATA.list.length === 0) {\n        checkForAutosaveRecovery().then(recovered => {\n          if (recovered) return;\n          if (Object.keys(NODE_DATA).length > 0 || EDGE_DATA.list.length > 0) return;\n          if (PAGE_STATE.background && PAGE_STATE.background !== \"\") return;\n          if (typeof showWelcomeModal === 'function') showWelcomeModal();\n        });\n       }\n       startAutosave();\n      })();\n\n      function showWelcomeModal() {\n       const modal = document.getElementById('welcome-modal');\n       if (!modal) return;\n       modal.classList.add('active');\n\n       let selectedMode = 'network';\n       const canvasRow = document.getElementById('welcome-canvas-row');\n       const canvasSelect = document.getElementById('welcome-canvas-select');\n       const canvasPreview = document.getElementById('welcome-canvas-preview');\n       const themeSelect = document.getElementById('welcome-theme-select');\n       const bgColor = document.getElementById('welcome-bg-color');\n       const accentColor = document.getElementById('welcome-accent-color');\n       const textColor = document.getElementById('welcome-text-color');\n       const textSoftColor = document.getElementById('welcome-text-soft-color');\n       const panelColor = document.getElementById('welcome-panel-color');\n       const modalColor = document.getElementById('welcome-modal-color');\n       const dangerColor = document.getElementById('welcome-danger-color');\n       const mobileFooterColor = document.getElementById('welcome-mobile-footer-color');\n       const tagFillColor = document.getElementById('welcome-tag-fill-color');\n       const tagTextColor = document.getElementById('welcome-tag-text-color');\n       const tagBorderColor = document.getElementById('welcome-tag-border-color');\n\n       function renderWelcomeCanvasPreview() {\n        if (!canvasPreview) return;\n        const template = canvasSelect ? canvasSelect.value : 'grid';\n        const previewBg = bgColor ? bgColor.value : '#0b0e13';\n        const gridColor = '#475569';\n        canvasPreview.style.background = previewBg;\n        canvasPreview.innerHTML = '';\n\n        if (template === 'none') return;\n\n        const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');\n        svg.setAttribute('width', '100%');\n        svg.setAttribute('height', '100%');\n        svg.setAttribute('viewBox', '0 0 300 60');\n        svg.style.display = 'block';\n\n        if (template === 'grid') {\n         for (let x = 0; x <= 300; x += 20) {\n          const line = document.createElementNS('http://www.w3.org/2000/svg', 'line');\n          line.setAttribute('x1', x); line.setAttribute('y1', 0);\n          line.setAttribute('x2', x); line.setAttribute('y2', 60);\n          line.setAttribute('stroke', gridColor + '33');\n          line.setAttribute('stroke-width', x % 100 === 0 ? '1.5' : '0.5');\n          svg.appendChild(line);\n         }\n         for (let y = 0; y <= 60; y += 20) {\n          const line = document.createElementNS('http://www.w3.org/2000/svg', 'line');\n          line.setAttribute('x1', 0); line.setAttribute('y1', y);\n          line.setAttribute('x2', 300); line.setAttribute('y2', y);\n          line.setAttribute('stroke', gridColor + '33');\n          line.setAttribute('stroke-width', y % 100 === 0 ? '1.5' : '0.5');\n          svg.appendChild(line);\n         }\n        } else if (template === 'dots') {\n         for (let x = 10; x <= 290; x += 20) {\n          for (let y = 10; y <= 50; y += 20) {\n           const dot = document.createElementNS('http://www.w3.org/2000/svg', 'circle');\n           dot.setAttribute('cx', x); dot.setAttribute('cy', y); dot.setAttribute('r', 1.5);\n           dot.setAttribute('fill', gridColor + '66');\n           svg.appendChild(dot);\n          }\n         }\n        } else if (template === 'blueprint') {\n         for (let x = 0; x <= 300; x += 10) {\n          const line = document.createElementNS('http://www.w3.org/2000/svg', 'line');\n          line.setAttribute('x1', x); line.setAttribute('y1', 0);\n          line.setAttribute('x2', x); line.setAttribute('y2', 60);\n          line.setAttribute('stroke', x % 40 === 0 ? gridColor + '66' : gridColor + '33');\n          line.setAttribute('stroke-width', x % 40 === 0 ? '1' : '0.3');\n          svg.appendChild(line);\n         }\n         for (let y = 0; y <= 60; y += 10) {\n          const line = document.createElementNS('http://www.w3.org/2000/svg', 'line');\n          line.setAttribute('x1', 0); line.setAttribute('y1', y);\n          line.setAttribute('x2', 300); line.setAttribute('y2', y);\n          line.setAttribute('stroke', y % 40 === 0 ? gridColor + '66' : gridColor + '33');\n          line.setAttribute('stroke-width', y % 40 === 0 ? '1' : '0.3');\n          svg.appendChild(line);\n         }\n        } else if (template === 'basketball') {\n         const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');\n         rect.setAttribute('x', 20); rect.setAttribute('y', 10);\n         rect.setAttribute('width', 260); rect.setAttribute('height', 40);\n         rect.setAttribute('fill', '#cd853f22'); rect.setAttribute('stroke', gridColor + '66');\n         rect.setAttribute('stroke-width', '2');\n         svg.appendChild(rect);\n         const center = document.createElementNS('http://www.w3.org/2000/svg', 'line');\n         center.setAttribute('x1', 150); center.setAttribute('y1', 10);\n         center.setAttribute('x2', 150); center.setAttribute('y2', 50);\n         center.setAttribute('stroke', gridColor + '66'); center.setAttribute('stroke-width', '1.5');\n         svg.appendChild(center);\n         const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');\n         circle.setAttribute('cx', 150); circle.setAttribute('cy', 30); circle.setAttribute('r', 10);\n         circle.setAttribute('fill', 'none'); circle.setAttribute('stroke', gridColor + '66');\n         svg.appendChild(circle);\n        } else if (template === 'football') {\n         const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');\n         rect.setAttribute('x', 20); rect.setAttribute('y', 10);\n         rect.setAttribute('width', 260); rect.setAttribute('height', 40);\n         rect.setAttribute('fill', '#22803322'); rect.setAttribute('stroke', '#ffffff44');\n         rect.setAttribute('stroke-width', '2');\n         svg.appendChild(rect);\n         for (let x = 46; x <= 254; x += 26) {\n          const line = document.createElementNS('http://www.w3.org/2000/svg', 'line');\n          line.setAttribute('x1', x); line.setAttribute('y1', 10);\n          line.setAttribute('x2', x); line.setAttribute('y2', 50);\n          line.setAttribute('stroke', '#ffffff44'); line.setAttribute('stroke-width', '1');\n          svg.appendChild(line);\n         }\n        } else if (template === 'soccer') {\n         const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');\n         rect.setAttribute('x', 20); rect.setAttribute('y', 10);\n         rect.setAttribute('width', 260); rect.setAttribute('height', 40);\n         rect.setAttribute('fill', '#1a802022'); rect.setAttribute('stroke', '#ffffff55');\n         rect.setAttribute('stroke-width', '2');\n         svg.appendChild(rect);\n         const center = document.createElementNS('http://www.w3.org/2000/svg', 'line');\n         center.setAttribute('x1', 150); center.setAttribute('y1', 10);\n         center.setAttribute('x2', 150); center.setAttribute('y2', 50);\n         center.setAttribute('stroke', '#ffffff44'); center.setAttribute('stroke-width', '1');\n         svg.appendChild(center);\n         const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');\n         circle.setAttribute('cx', 150); circle.setAttribute('cy', 30); circle.setAttribute('r', 8);\n         circle.setAttribute('fill', 'none'); circle.setAttribute('stroke', '#ffffff44');\n         svg.appendChild(circle);\n        } else if (template === 'hockey') {\n         const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');\n         rect.setAttribute('x', 20); rect.setAttribute('y', 10);\n         rect.setAttribute('width', 260); rect.setAttribute('height', 40);\n         rect.setAttribute('rx', 10); rect.setAttribute('ry', 10);\n         rect.setAttribute('fill', '#e8f4f844'); rect.setAttribute('stroke', '#ff000066');\n         rect.setAttribute('stroke-width', '2');\n         svg.appendChild(rect);\n         const center = document.createElementNS('http://www.w3.org/2000/svg', 'line');\n         center.setAttribute('x1', 150); center.setAttribute('y1', 10);\n         center.setAttribute('x2', 150); center.setAttribute('y2', 50);\n         center.setAttribute('stroke', '#ff000055'); center.setAttribute('stroke-width', '2');\n         svg.appendChild(center);\n         const blueL = document.createElementNS('http://www.w3.org/2000/svg', 'line');\n         blueL.setAttribute('x1', 95); blueL.setAttribute('y1', 10);\n         blueL.setAttribute('x2', 95); blueL.setAttribute('y2', 50);\n         blueL.setAttribute('stroke', '#0066ff55'); blueL.setAttribute('stroke-width', '2');\n         svg.appendChild(blueL);\n         const blueR = document.createElementNS('http://www.w3.org/2000/svg', 'line');\n         blueR.setAttribute('x1', 205); blueR.setAttribute('y1', 10);\n         blueR.setAttribute('x2', 205); blueR.setAttribute('y2', 50);\n         blueR.setAttribute('stroke', '#0066ff55'); blueR.setAttribute('stroke-width', '2');\n         svg.appendChild(blueR);\n        } else if (template === 'baseball') {\n         const diamond = document.createElementNS('http://www.w3.org/2000/svg', 'polygon');\n         diamond.setAttribute('points', '150,15 185,30 150,45 115,30');\n         diamond.setAttribute('fill', '#cd853f22'); diamond.setAttribute('stroke', '#ffffff55');\n         diamond.setAttribute('stroke-width', '1.5');\n         svg.appendChild(diamond);\n         const arc = document.createElementNS('http://www.w3.org/2000/svg', 'path');\n         arc.setAttribute('d', 'M 60,50 Q 150,5 240,50');\n         arc.setAttribute('fill', '#22803322'); arc.setAttribute('stroke', '#ffffff44');\n         arc.setAttribute('stroke-width', '1');\n         svg.appendChild(arc);\n        } else if (template === 'tennis') {\n         const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');\n         rect.setAttribute('x', 30); rect.setAttribute('y', 10);\n         rect.setAttribute('width', 240); rect.setAttribute('height', 40);\n         rect.setAttribute('fill', '#0066aa22'); rect.setAttribute('stroke', '#ffffff66');\n         rect.setAttribute('stroke-width', '2');\n         svg.appendChild(rect);\n         const net = document.createElementNS('http://www.w3.org/2000/svg', 'line');\n         net.setAttribute('x1', 150); net.setAttribute('y1', 10);\n         net.setAttribute('x2', 150); net.setAttribute('y2', 50);\n         net.setAttribute('stroke', '#ffffff88'); net.setAttribute('stroke-width', '1');\n         svg.appendChild(net);\n         const svcL = document.createElementNS('http://www.w3.org/2000/svg', 'rect');\n         svcL.setAttribute('x', 70); svcL.setAttribute('y', 18);\n         svcL.setAttribute('width', 80); svcL.setAttribute('height', 24);\n         svcL.setAttribute('fill', 'none'); svcL.setAttribute('stroke', '#ffffff44');\n         svg.appendChild(svcL);\n         const svcR = document.createElementNS('http://www.w3.org/2000/svg', 'rect');\n         svcR.setAttribute('x', 150); svcR.setAttribute('y', 18);\n         svcR.setAttribute('width', 80); svcR.setAttribute('height', 24);\n         svcR.setAttribute('fill', 'none'); svcR.setAttribute('stroke', '#ffffff44');\n         svg.appendChild(svcR);\n        }\n        canvasPreview.appendChild(svg);\n       }\n\n       function updateWelcomeCanvasOptions(mode) {\n        if (!canvasSelect || !canvasRow) return;\n        const options = CANVAS_OPTIONS[mode] || CANVAS_OPTIONS.network;\n        const previewRow = document.getElementById('welcome-canvas-preview');\n        const colorDetails = modal.querySelector('details');\n        if (mode === 'mindmap') {\n         canvasRow.style.display = 'none';\n         if (previewRow) previewRow.style.display = 'none';\n         if (colorDetails) colorDetails.open = true;\n        } else if (mode === 'floorplan') {\n         canvasRow.style.display = 'none';\n         if (previewRow) {\n          previewRow.style.display = 'block';\n          const networkOptions = CANVAS_OPTIONS.network;\n          canvasSelect.innerHTML = networkOptions.map(o => `<option value=\"${o.value}\">${o.label}</option>`).join('');\n          canvasSelect.value = 'blueprint';\n          renderWelcomeCanvasPreview();\n         }\n         if (colorDetails) colorDetails.open = false;\n        } else if (options.length > 0) {\n         canvasRow.style.display = 'flex';\n         if (previewRow) previewRow.style.display = 'block';\n         canvasSelect.innerHTML = options.map(o => `<option value=\"${o.value}\">${o.label}</option>`).join('');\n         renderWelcomeCanvasPreview();\n         if (colorDetails) colorDetails.open = false;\n        }\n       }\n\n       modal.querySelectorAll('.welcome-mode-btn').forEach(btn => {\n        btn.addEventListener('click', () => {\n         selectedMode = btn.dataset.mode;\n         modal.querySelectorAll('.welcome-mode-btn').forEach(b => {\n          b.style.borderColor = 'var(--edge-main)';\n         });\n         btn.style.borderColor = 'var(--accent)';\n         updateWelcomeCanvasOptions(selectedMode);\n         PAGE_STATE.mappingMode = selectedMode;\n         applyMappingModeLabels();\n         updateLayerLabels();\n         applyLanguage();\n         displayTabs();\n         if (selectedMode === 'mindmap') {\n          PAGE_STATE.canvasTemplate = 'none';\n          PAGE_STATE.canvasGridEnabled = false;\n         } else if (selectedMode === 'floorplan') {\n          PAGE_STATE.canvasTemplate = 'blueprint';\n          PAGE_STATE.canvasGridEnabled = true;\n         } else if (canvasSelect) {\n          PAGE_STATE.canvasTemplate = canvasSelect.value;\n          PAGE_STATE.canvasGridEnabled = canvasSelect.value !== 'none';\n         }\n         forgeTheTopology();\n        });\n        btn.addEventListener('mouseenter', () => {\n         if (btn.style.borderColor !== 'var(--accent)') {\n          btn.style.background = 'var(--panel)';\n         }\n        });\n        btn.addEventListener('mouseleave', () => {\n         btn.style.background = 'var(--panel-alt)';\n        });\n       });\n\n       if (canvasSelect) {\n        canvasSelect.addEventListener('change', () => {\n         renderWelcomeCanvasPreview();\n         PAGE_STATE.canvasTemplate = canvasSelect.value;\n         PAGE_STATE.canvasGridEnabled = canvasSelect.value !== 'none';\n         forgeTheTopology();\n        });\n       }\n\n       if (themeSelect) {\n        themeSelect.addEventListener('change', () => {\n         const presetKey = themeSelect.value;\n         const colorDetails = modal.querySelector('details');\n         if (presetKey === '') {\n          if (colorDetails) colorDetails.open = true;\n          return;\n         }\n         if (typeof THEME_PRESETS === 'undefined') return;\n         const p = THEME_PRESETS[presetKey];\n         if (!p) return;\n         Object.assign(PAGE_STATE, p);\n         document.body.style.background = p.panel;\n         document.documentElement.style.setProperty('--bg', p.panel);\n         document.documentElement.style.setProperty('--panel', p.panel);\n         document.documentElement.style.setProperty('--panel-alt', p.panelAlt);\n         document.documentElement.style.setProperty('--accent', p.accent);\n         document.documentElement.style.setProperty('--danger', p.danger);\n         document.documentElement.style.setProperty('--text-main', p.textMain);\n         document.documentElement.style.setProperty('--text-soft', p.textSoft);\n         document.documentElement.style.setProperty('--modal-bg', p.panelAlt);\n         document.documentElement.style.setProperty('--tag-bg', p.tagFill);\n         document.documentElement.style.setProperty('--tag-text', p.tagText);\n         document.documentElement.style.setProperty('--tag-border', p.tagBorder);\n         if (bgColor) bgColor.value = p.panel;\n         if (accentColor) accentColor.value = p.accent;\n         if (textColor) textColor.value = p.textMain;\n         if (textSoftColor) textSoftColor.value = p.textSoft;\n         if (panelColor) panelColor.value = p.panel;\n         if (modalColor) modalColor.value = p.panelAlt;\n         if (dangerColor) dangerColor.value = p.canvasGrid || '#475569';\n         if (tagFillColor) tagFillColor.value = p.tagFill;\n         if (tagTextColor) tagTextColor.value = p.tagText;\n         if (tagBorderColor) tagBorderColor.value = p.tagBorder;\n         PAGE_STATE.background = p.panel;\n         renderWelcomeCanvasPreview();\n         wieldThePower();\n        });\n       }\n\n       function setupColorPicker(picker, cssVar, pageStateKey) {\n        if (!picker) return;\n        picker.addEventListener('input', (e) => {\n         document.documentElement.style.setProperty(cssVar, e.target.value);\n         if (pageStateKey) PAGE_STATE[pageStateKey] = e.target.value;\n         if (cssVar === '--bg' || pageStateKey === 'background') {\n          document.body.style.background = e.target.value;\n          renderWelcomeCanvasPreview();\n         }\n        });\n       }\n\n       setupColorPicker(bgColor, '--bg', 'background');\n       setupColorPicker(accentColor, '--accent', 'accent');\n       setupColorPicker(textColor, '--text-main', 'textMain');\n       setupColorPicker(textSoftColor, '--text-soft', 'textSoft');\n       setupColorPicker(panelColor, '--panel', 'panel');\n       setupColorPicker(modalColor, '--panel-alt', 'panelAlt');\n       if (dangerColor) {\n        dangerColor.addEventListener('input', (e) => {\n         PAGE_STATE.canvasGrid = e.target.value;\n         if (typeof forgeTheTopology === 'function') forgeTheTopology();\n         renderWelcomeCanvasPreview();\n        });\n       }\n       setupColorPicker(mobileFooterColor, '--sidebar-bg', 'sidebarBg');\n       setupColorPicker(tagFillColor, '--tag-bg', 'tagFill');\n       setupColorPicker(tagTextColor, '--tag-text', 'tagText');\n       setupColorPicker(tagBorderColor, '--tag-border', 'tagBorder');\n\n       renderWelcomeCanvasPreview();\n\n       const startBtn = document.getElementById('welcome-start-btn');\n       if (startBtn) {\n        startBtn.addEventListener('click', () => {\n         PAGE_STATE.mappingMode = selectedMode;\n         const modeSelect = document.getElementById('mapping-mode-select');\n         if (modeSelect) modeSelect.value = selectedMode;\n\n         if (selectedMode === 'mindmap') {\n          PAGE_STATE.canvasTemplate = 'none';\n          PAGE_STATE.canvasGridEnabled = false;\n         } else if (selectedMode === 'floorplan') {\n          PAGE_STATE.canvasTemplate = 'blueprint';\n         } else if (canvasSelect) {\n          PAGE_STATE.canvasTemplate = canvasSelect.value;\n          PAGE_STATE.canvasGridEnabled = canvasSelect.value !== 'none';\n         }\n\n         if (bgColor) {\n          PAGE_STATE.background = bgColor.value;\n          document.body.style.background = bgColor.value;\n          const el = document.getElementById('panel-color');\n          if (el) el.value = bgColor.value;\n         }\n         if (accentColor) {\n          PAGE_STATE.accent = accentColor.value;\n          const el = document.getElementById('accent-color');\n          if (el) el.value = accentColor.value;\n         }\n\n         if (typeof updateCanvasStyleOptions === 'function') updateCanvasStyleOptions();\n         if (typeof applyMappingModeLabels === 'function') applyMappingModeLabels();\n         if (typeof updateLayerLabels === 'function') updateLayerLabels();\n\n         const catSelect = document.getElementById('shape-category-select');\n         if (catSelect) {\n          catSelect.value = MODE_DEFAULT_CATEGORIES[selectedMode] || 'network';\n          if (typeof populateShapeSelect === 'function') populateShapeSelect();\n         }\n\n         modal.classList.remove('active');\n         wieldThePower();\n         forgeTheTopology();\n        });\n       }\n\n       const skipBtn = document.getElementById('welcome-skip-btn');\n       if (skipBtn) {\n        skipBtn.addEventListener('click', () => {\n         modal.classList.remove('active');\n        });\n       }\n\n       modal.addEventListener('click', (e) => {\n        if (e.target === modal) {\n         modal.classList.remove('active');\n        }\n       });\n      }\n      let resizeDebounceTimer = null;\n      window.addEventListener(\"resize\", () => {\n        clearTimeout(resizeDebounceTimer);\n        resizeDebounceTimer = setTimeout(() => {\n          MobileManager.applyAll();\n          if (typeof updateDrawToolbarVisibility === 'function') updateDrawToolbarVisibility();\n          if (typeof updateTopologyToolbarVisibility === 'function') updateTopologyToolbarVisibility();\n        }, 100);\n      });\n      function getBreakpointKey() {\n       const w = window.innerWidth;\n       if (w <= 380) return \"fold\";\n       if (w <= 768) return \"mobile\";\n       if (w <= 1024) return \"tablet\";\n       return \"desktop\";\n      }\n      function resolveStylesEntry(styleEntry) {\n       if (!styleEntry) return {};\n       if (styleEntry.circleColor || styleEntry.titleColor || styleEntry.titleFont || styleEntry.titleSize || styleEntry.subColor || styleEntry.subFont || styleEntry.subSize) {\n        return styleEntry;\n       }\n       const bp = getBreakpointKey();\n       const base = styleEntry.all || {};\n       const bpStyles = styleEntry[bp] || {};\n       return Object.assign({}, base, bpStyles);\n      }\n      function resolveStylesForNode(id) {\n       const styleEntry = savedStyles[id];\n       if (!styleEntry) return {};\n       return resolveStylesEntry(styleEntry);\n      }\n      function ensureStyleEntry(id) {\n       if (!savedStyles[id]) savedStyles[id] = {};\n       const entry = savedStyles[id];\n       const flatProps = [\"circleColor\", \"circleBorder\", \"titleColor\", \"titleFont\", \"titleSize\", \"subColor\", \"subFont\", \"subSize\", ];\n       const isFlat = flatProps.some((p) => Object.prototype.hasOwnProperty.call(entry, p));\n       if (isFlat) {\n        const all = entry.all || {};\n        flatProps.forEach((p) => {\n         if (entry[p] !== undefined) {\n          all[p] = entry[p];\n          delete entry[p];\n         }\n        });\n        entry.all = all;\n       }\n       return entry;\n      }\n      function getDefaultSize() {\n       if (window.innerWidth <= 380) return 120;\n       if (window.innerWidth <= 768) return 140;\n       if (window.innerWidth <= 1024) return 70;\n       return 55;\n      }\n      function createShapeElement(shape, size) {\n       const ns = \"http://www.w3.org/2000/svg\";\n       if (shape === \"circle\") {\n        const c = document.createElementNS(ns, \"circle\");\n        c.setAttribute(\"r\", size);\n        return c;\n       }\n       if (shape === \"square\") {\n        const r = document.createElementNS(ns, \"rect\");\n        const s = size * 2;\n        r.setAttribute(\"x\", -size);\n        r.setAttribute(\"y\", -size);\n        r.setAttribute(\"width\", s);\n        r.setAttribute(\"height\", s);\n        r.setAttribute(\"rx\", 4);\n        return r;\n       }\n       if (shape === \"rectangle\") {\n        const r = document.createElementNS(ns, \"rect\");\n        r.setAttribute(\"x\", -size * 1.4);\n        r.setAttribute(\"y\", -size * 0.8);\n        r.setAttribute(\"width\", size * 2.8);\n        r.setAttribute(\"height\", size * 1.6);\n        r.setAttribute(\"rx\", 6);\n        return r;\n       }\n       if (shape === \"triangle\") {\n        const p = document.createElementNS(ns, \"polygon\");\n        const h = size * 1.8;\n        const w = size * 2;\n        p.setAttribute(\"points\", `0,${-h} ${w / 2},${h / 2} ${-w / 2},${h / 2}`);\n        return p;\n       }\n       if (shape === \"hexagon\") {\n        const p = document.createElementNS(ns, \"polygon\");\n        const s = size;\n        const pts = [\n         [0, -s],\n         [s * 0.86, -s * 0.5],\n         [s * 0.86, s * 0.5],\n         [0, s],\n         [-s * 0.86, s * 0.5],\n         [-s * 0.86, -s * 0.5],\n        ].map((pt) => pt.join(\",\")).join(\" \");\n        p.setAttribute(\"points\", pts);\n        return p;\n       }\n       if (shape === \"stop-sign\") {\n        const p = document.createElementNS(ns, \"polygon\");\n        const r = size;\n        const ptsArr = [];\n        for (let i = 0; i < 8; i++) {\n         const a = (Math.PI / 4) * i + Math.PI / 8;\n         ptsArr.push([Math.cos(a) * r, Math.sin(a) * r]);\n        }\n        const pts = ptsArr.map((pt) => pt.join(\",\")).join(\" \");\n        p.setAttribute(\"points\", pts);\n        return p;\n       }\n       if (shape === \"star\") {\n        const p = document.createElementNS(ns, \"polygon\");\n        const outer = size;\n        const inner = size * 0.45;\n        let pts = \"\";\n        for (let i = 0; i < 10; i++) {\n         const a = (Math.PI / 5) * i - Math.PI / 2;\n         const r = i % 2 === 0 ? outer : inner;\n         pts += `${Math.cos(a) * r},${Math.sin(a) * r} `;\n        }\n        p.setAttribute(\"points\", pts.trim());\n        return p;\n       }\n       if (shape === \"diamond\") {\n        const p = document.createElementNS(ns, \"polygon\");\n        const s = size;\n        p.setAttribute(\"points\", `0,${-s} ${s},0 0,${s} ${-s},0`);\n        return p;\n       }\n       if (shape === \"server\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 1.2);\n        body.setAttribute(\"y\", -size * 0.6);\n        body.setAttribute(\"width\", size * 2.4);\n        body.setAttribute(\"height\", size * 1.2);\n        body.setAttribute(\"rx\", 4);\n        g.appendChild(body);\n        for (let i = 0; i < 3; i++) {\n         const line = document.createElementNS(ns, \"line\");\n         line.setAttribute(\"x1\", -size * 0.9 + i * size * 0.5);\n         line.setAttribute(\"y1\", -size * 0.3);\n         line.setAttribute(\"x2\", -size * 0.9 + i * size * 0.5);\n         line.setAttribute(\"y2\", size * 0.3);\n         line.style.stroke = \"currentColor\";\n         line.style.strokeWidth = \"2\";\n         line.style.opacity = \"0.5\";\n         g.appendChild(line);\n        }\n        const led = document.createElementNS(ns, \"circle\");\n        led.setAttribute(\"cx\", size * 0.9);\n        led.setAttribute(\"cy\", 0);\n        led.setAttribute(\"r\", size * 0.1);\n        led.style.fill = \"#4ade80\";\n        g.appendChild(led);\n        return g;\n       }\n       if (shape === \"pc\" || shape === \"desktop\") {\n        const g = document.createElementNS(ns, \"g\");\n        const monitor = document.createElementNS(ns, \"rect\");\n        monitor.setAttribute(\"x\", -size * 0.9);\n        monitor.setAttribute(\"y\", -size * 0.8);\n        monitor.setAttribute(\"width\", size * 1.8);\n        monitor.setAttribute(\"height\", size * 1.2);\n        monitor.setAttribute(\"rx\", 4);\n        g.appendChild(monitor);\n        const screen = document.createElementNS(ns, \"rect\");\n        screen.setAttribute(\"x\", -size * 0.75);\n        screen.setAttribute(\"y\", -size * 0.65);\n        screen.setAttribute(\"width\", size * 1.5);\n        screen.setAttribute(\"height\", size * 0.9);\n        screen.setAttribute(\"rx\", 2);\n        screen.style.fill = \"#1e293b\";\n        g.appendChild(screen);\n        const neck = document.createElementNS(ns, \"rect\");\n        neck.setAttribute(\"x\", -size * 0.15);\n        neck.setAttribute(\"y\", size * 0.4);\n        neck.setAttribute(\"width\", size * 0.3);\n        neck.setAttribute(\"height\", size * 0.3);\n        g.appendChild(neck);\n        const base = document.createElementNS(ns, \"rect\");\n        base.setAttribute(\"x\", -size * 0.5);\n        base.setAttribute(\"y\", size * 0.7);\n        base.setAttribute(\"width\", size * 1);\n        base.setAttribute(\"height\", size * 0.15);\n        base.setAttribute(\"rx\", 2);\n        g.appendChild(base);\n        return g;\n       }\n       if (shape === \"laptop\") {\n        const g = document.createElementNS(ns, \"g\");\n        const screen = document.createElementNS(ns, \"rect\");\n        screen.setAttribute(\"x\", -size * 0.8);\n        screen.setAttribute(\"y\", -size * 0.9);\n        screen.setAttribute(\"width\", size * 1.6);\n        screen.setAttribute(\"height\", size * 1.1);\n        screen.setAttribute(\"rx\", 4);\n        g.appendChild(screen);\n        const base = document.createElementNS(ns, \"rect\");\n        base.setAttribute(\"x\", -size * 0.9);\n        base.setAttribute(\"y\", size * 0.25);\n        base.setAttribute(\"width\", size * 1.8);\n        base.setAttribute(\"height\", size * 0.6);\n        base.setAttribute(\"rx\", 4);\n        g.appendChild(base);\n        const pad = document.createElementNS(ns, \"rect\");\n        pad.setAttribute(\"x\", -size * 0.25);\n        pad.setAttribute(\"y\", size * 0.45);\n        pad.setAttribute(\"width\", size * 0.5);\n        pad.setAttribute(\"height\", size * 0.25);\n        pad.setAttribute(\"rx\", 2);\n        pad.style.fill = \"#1e293b\";\n        g.appendChild(pad);\n        return g;\n       }\n       if (shape === \"phone\" || shape === \"mobile\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 0.45);\n        body.setAttribute(\"y\", -size);\n        body.setAttribute(\"width\", size * 0.9);\n        body.setAttribute(\"height\", size * 2);\n        body.setAttribute(\"rx\", 8);\n        g.appendChild(body);\n        const screen = document.createElementNS(ns, \"rect\");\n        screen.setAttribute(\"x\", -size * 0.38);\n        screen.setAttribute(\"y\", -size * 0.85);\n        screen.setAttribute(\"width\", size * 0.76);\n        screen.setAttribute(\"height\", size * 1.6);\n        screen.setAttribute(\"rx\", 4);\n        screen.style.fill = \"#1e293b\";\n        g.appendChild(screen);\n        const btn = document.createElementNS(ns, \"rect\");\n        btn.setAttribute(\"x\", -size * 0.15);\n        btn.setAttribute(\"y\", size * 0.82);\n        btn.setAttribute(\"width\", size * 0.3);\n        btn.setAttribute(\"height\", size * 0.06);\n        btn.setAttribute(\"rx\", 2);\n        btn.style.fill = \"#475569\";\n        g.appendChild(btn);\n        return g;\n       }\n       if (shape === \"router\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 1.1);\n        body.setAttribute(\"y\", -size * 0.3);\n        body.setAttribute(\"width\", size * 2.2);\n        body.setAttribute(\"height\", size * 0.8);\n        body.setAttribute(\"rx\", 6);\n        g.appendChild(body);\n        for (let i = -1; i <= 1; i++) {\n         const ant = document.createElementNS(ns, \"rect\");\n         ant.setAttribute(\"x\", i * size * 0.6 - size * 0.05);\n         ant.setAttribute(\"y\", -size * 0.9);\n         ant.setAttribute(\"width\", size * 0.1);\n         ant.setAttribute(\"height\", size * 0.6);\n         ant.setAttribute(\"rx\", 2);\n         g.appendChild(ant);\n         const tip = document.createElementNS(ns, \"circle\");\n         tip.setAttribute(\"cx\", i * size * 0.6);\n         tip.setAttribute(\"cy\", -size * 0.95);\n         tip.setAttribute(\"r\", size * 0.08);\n         g.appendChild(tip);\n        }\n        for (let i = 0; i < 4; i++) {\n         const led = document.createElementNS(ns, \"circle\");\n         led.setAttribute(\"cx\", -size * 0.7 + i * size * 0.35);\n         led.setAttribute(\"cy\", size * 0.1);\n         led.setAttribute(\"r\", size * 0.06);\n         led.style.fill = i < 2 ? \"#4ade80\" : \"#facc15\";\n         g.appendChild(led);\n        }\n        return g;\n       }\n       if (shape === \"switch\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 1.4);\n        body.setAttribute(\"y\", -size * 0.4);\n        body.setAttribute(\"width\", size * 2.8);\n        body.setAttribute(\"height\", size * 0.8);\n        body.setAttribute(\"rx\", 4);\n        g.appendChild(body);\n        for (let i = 0; i < 8; i++) {\n         const port = document.createElementNS(ns, \"rect\");\n         port.setAttribute(\"x\", -size * 1.2 + i * size * 0.32);\n         port.setAttribute(\"y\", -size * 0.15);\n         port.setAttribute(\"width\", size * 0.22);\n         port.setAttribute(\"height\", size * 0.3);\n         port.setAttribute(\"rx\", 1);\n         port.style.fill = \"#1e293b\";\n         g.appendChild(port);\n        }\n        return g;\n       }\n       if (shape === \"firewall\") {\n        const g = document.createElementNS(ns, \"g\");\n        const wall = document.createElementNS(ns, \"rect\");\n        wall.setAttribute(\"x\", -size);\n        wall.setAttribute(\"y\", -size * 0.8);\n        wall.setAttribute(\"width\", size * 2);\n        wall.setAttribute(\"height\", size * 1.6);\n        wall.setAttribute(\"rx\", 4);\n        g.appendChild(wall);\n        for (let row = 0; row < 3; row++) {\n         const line = document.createElementNS(ns, \"line\");\n         line.setAttribute(\"x1\", -size * 0.85);\n         line.setAttribute(\"y1\", -size * 0.5 + row * size * 0.45);\n         line.setAttribute(\"x2\", size * 0.85);\n         line.setAttribute(\"y2\", -size * 0.5 + row * size * 0.45);\n         line.style.stroke = \"#475569\";\n         line.style.strokeWidth = \"2\";\n         g.appendChild(line);\n        }\n        for (let row = 0; row < 4; row++) {\n         const offset = row % 2 === 0 ? 0 : size * 0.35;\n         for (let col = 0; col < 3; col++) {\n          const line = document.createElementNS(ns, \"line\");\n          const x = -size * 0.5 + col * size * 0.7 + offset;\n          if (x > -size * 0.85 && x < size * 0.85) {\n           line.setAttribute(\"x1\", x);\n           line.setAttribute(\"y1\", -size * 0.8 + row * size * 0.45);\n           line.setAttribute(\"x2\", x);\n           line.setAttribute(\"y2\", -size * 0.8 + row * size * 0.45 + size * 0.45);\n           line.style.stroke = \"#475569\";\n           line.style.strokeWidth = \"2\";\n           g.appendChild(line);\n          }\n         }\n        }\n        return g;\n       }\n       if (shape === \"cloud\") {\n        const p = document.createElementNS(ns, \"path\");\n        const s = size;\n        p.setAttribute(\"d\", `\n             M ${-s * 0.8} ${s * 0.2}\n             Q ${-s * 1.1} ${s * 0.2} ${-s * 1.1} ${-s * 0.1}\n             Q ${-s * 1.1} ${-s * 0.5} ${-s * 0.7} ${-s * 0.5}\n             Q ${-s * 0.7} ${-s * 0.9} ${-s * 0.2} ${-s * 0.9}\n             Q ${s * 0.1} ${-s * 1.1} ${s * 0.5} ${-s * 0.8}\n             Q ${s * 1} ${-s * 0.8} ${s * 1.1} ${-s * 0.3}\n             Q ${s * 1.3} ${-s * 0.1} ${s * 1.1} ${s * 0.2}\n             Q ${s * 1.1} ${s * 0.5} ${s * 0.7} ${s * 0.5}\n             L ${-s * 0.5} ${s * 0.5}\n             Q ${-s * 0.9} ${s * 0.5} ${-s * 0.9} ${s * 0.2}\n             Z\n            `);\n        return p;\n       }\n       if (shape === \"database\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 0.7);\n        body.setAttribute(\"y\", -size * 0.6);\n        body.setAttribute(\"width\", size * 1.4);\n        body.setAttribute(\"height\", size * 1.4);\n        g.appendChild(body);\n        const top = document.createElementNS(ns, \"ellipse\");\n        top.setAttribute(\"cx\", 0);\n        top.setAttribute(\"cy\", -size * 0.6);\n        top.setAttribute(\"rx\", size * 0.7);\n        top.setAttribute(\"ry\", size * 0.25);\n        g.appendChild(top);\n        const bottom = document.createElementNS(ns, \"ellipse\");\n        bottom.setAttribute(\"cx\", 0);\n        bottom.setAttribute(\"cy\", size * 0.8);\n        bottom.setAttribute(\"rx\", size * 0.7);\n        bottom.setAttribute(\"ry\", size * 0.25);\n        g.appendChild(bottom);\n        const mid1 = document.createElementNS(ns, \"ellipse\");\n        mid1.setAttribute(\"cx\", 0);\n        mid1.setAttribute(\"cy\", -size * 0.15);\n        mid1.setAttribute(\"rx\", size * 0.7);\n        mid1.setAttribute(\"ry\", size * 0.2);\n        mid1.style.fill = \"none\";\n        mid1.style.stroke = \"#475569\";\n        mid1.style.strokeWidth = \"2\";\n        g.appendChild(mid1);\n        const mid2 = document.createElementNS(ns, \"ellipse\");\n        mid2.setAttribute(\"cx\", 0);\n        mid2.setAttribute(\"cy\", size * 0.35);\n        mid2.setAttribute(\"rx\", size * 0.7);\n        mid2.setAttribute(\"ry\", size * 0.2);\n        mid2.style.fill = \"none\";\n        mid2.style.stroke = \"#475569\";\n        mid2.style.strokeWidth = \"2\";\n        g.appendChild(mid2);\n        return g;\n       }\n       if (shape === \"printer\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size);\n        body.setAttribute(\"y\", -size * 0.4);\n        body.setAttribute(\"width\", size * 2);\n        body.setAttribute(\"height\", size * 0.9);\n        body.setAttribute(\"rx\", 4);\n        g.appendChild(body);\n        const trayTop = document.createElementNS(ns, \"rect\");\n        trayTop.setAttribute(\"x\", -size * 0.7);\n        trayTop.setAttribute(\"y\", -size * 0.8);\n        trayTop.setAttribute(\"width\", size * 1.4);\n        trayTop.setAttribute(\"height\", size * 0.4);\n        trayTop.setAttribute(\"rx\", 2);\n        trayTop.style.fill = \"#1e293b\";\n        g.appendChild(trayTop);\n        const trayOut = document.createElementNS(ns, \"rect\");\n        trayOut.setAttribute(\"x\", -size * 0.6);\n        trayOut.setAttribute(\"y\", size * 0.5);\n        trayOut.setAttribute(\"width\", size * 1.2);\n        trayOut.setAttribute(\"height\", size * 0.35);\n        trayOut.setAttribute(\"rx\", 2);\n        g.appendChild(trayOut);\n        const paper = document.createElementNS(ns, \"rect\");\n        paper.setAttribute(\"x\", -size * 0.5);\n        paper.setAttribute(\"y\", size * 0.3);\n        paper.setAttribute(\"width\", size * 1);\n        paper.setAttribute(\"height\", size * 0.5);\n        paper.style.fill = \"#e2e8f0\";\n        g.appendChild(paper);\n        return g;\n       }\n       if (shape === \"access-point\" || shape === \"wifi\") {\n        const g = document.createElementNS(ns, \"g\");\n        const base = document.createElementNS(ns, \"rect\");\n        base.setAttribute(\"x\", -size * 0.3);\n        base.setAttribute(\"y\", size * 0.2);\n        base.setAttribute(\"width\", size * 0.6);\n        base.setAttribute(\"height\", size * 0.3);\n        base.setAttribute(\"rx\", 3);\n        g.appendChild(base);\n        for (let i = 1; i <= 3; i++) {\n         const arc = document.createElementNS(ns, \"path\");\n         const r = size * 0.3 * i;\n         arc.setAttribute(\"d\", `M ${-r * 0.7} ${size * 0.1 - r * 0.5} A ${r} ${r} 0 0 1 ${r * 0.7} ${size * 0.1 - r * 0.5}`);\n         arc.style.fill = \"none\";\n         arc.style.stroke = \"currentColor\";\n         arc.style.strokeWidth = \"2\";\n         arc.style.opacity = 1 - (i * 0.2);\n         g.appendChild(arc);\n        }\n        return g;\n       }\n       if (shape === \"load-balancer\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size);\n        body.setAttribute(\"y\", -size * 0.5);\n        body.setAttribute(\"width\", size * 2);\n        body.setAttribute(\"height\", size);\n        body.setAttribute(\"rx\", 6);\n        g.appendChild(body);\n        const bar = document.createElementNS(ns, \"line\");\n        bar.setAttribute(\"x1\", -size * 0.6);\n        bar.setAttribute(\"y1\", 0);\n        bar.setAttribute(\"x2\", size * 0.6);\n        bar.setAttribute(\"y2\", 0);\n        bar.style.stroke = \"#4ade80\";\n        bar.style.strokeWidth = \"3\";\n        g.appendChild(bar);\n        [-1, 1].forEach(dir => {\n         const arrow = document.createElementNS(ns, \"path\");\n         arrow.setAttribute(\"d\", `M ${dir * size * 0.3} ${-size * 0.2} L ${dir * size * 0.6} 0 L ${dir * size * 0.3} ${size * 0.2}`);\n         arrow.style.fill = \"none\";\n         arrow.style.stroke = \"#4ade80\";\n         arrow.style.strokeWidth = \"2\";\n         g.appendChild(arrow);\n        });\n        return g;\n       }\n       if (shape === \"nas\" || shape === \"storage\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 0.8);\n        body.setAttribute(\"y\", -size * 0.9);\n        body.setAttribute(\"width\", size * 1.6);\n        body.setAttribute(\"height\", size * 1.8);\n        body.setAttribute(\"rx\", 4);\n        g.appendChild(body);\n        for (let i = 0; i < 4; i++) {\n         const bay = document.createElementNS(ns, \"rect\");\n         bay.setAttribute(\"x\", -size * 0.6);\n         bay.setAttribute(\"y\", -size * 0.7 + i * size * 0.4);\n         bay.setAttribute(\"width\", size * 1.2);\n         bay.setAttribute(\"height\", size * 0.3);\n         bay.setAttribute(\"rx\", 2);\n         bay.style.fill = \"#1e293b\";\n         g.appendChild(bay);\n        }\n        return g;\n       }\n       if (shape === \"gateway\" || shape === \"modem\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 0.5);\n        body.setAttribute(\"y\", -size * 0.9);\n        body.setAttribute(\"width\", size);\n        body.setAttribute(\"height\", size * 1.8);\n        body.setAttribute(\"rx\", 4);\n        g.appendChild(body);\n        for (let i = 0; i < 4; i++) {\n         const led = document.createElementNS(ns, \"circle\");\n         led.setAttribute(\"cx\", 0);\n         led.setAttribute(\"cy\", -size * 0.6 + i * size * 0.35);\n         led.setAttribute(\"r\", size * 0.08);\n         led.style.fill = [\"#4ade80\", \"#4ade80\", \"#facc15\", \"#60a5fa\"][i];\n         g.appendChild(led);\n        }\n        return g;\n       }\n       if (shape === \"vpn\" || shape === \"tunnel\") {\n        const g = document.createElementNS(ns, \"g\");\n        const outer = document.createElementNS(ns, \"ellipse\");\n        outer.setAttribute(\"cx\", 0);\n        outer.setAttribute(\"cy\", 0);\n        outer.setAttribute(\"rx\", size);\n        outer.setAttribute(\"ry\", size * 0.6);\n        g.appendChild(outer);\n        const inner = document.createElementNS(ns, \"ellipse\");\n        inner.setAttribute(\"cx\", 0);\n        inner.setAttribute(\"cy\", 0);\n        inner.setAttribute(\"rx\", size * 0.5);\n        inner.setAttribute(\"ry\", size * 0.3);\n        inner.style.fill = \"#1e293b\";\n        g.appendChild(inner);\n        const lock = document.createElementNS(ns, \"rect\");\n        lock.setAttribute(\"x\", -size * 0.15);\n        lock.setAttribute(\"y\", -size * 0.1);\n        lock.setAttribute(\"width\", size * 0.3);\n        lock.setAttribute(\"height\", size * 0.25);\n        lock.setAttribute(\"rx\", 2);\n        lock.style.fill = \"#4ade80\";\n        g.appendChild(lock);\n        return g;\n       }\n       if (shape === \"container\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size);\n        body.setAttribute(\"y\", -size * 0.5);\n        body.setAttribute(\"width\", size * 2);\n        body.setAttribute(\"height\", size);\n        body.setAttribute(\"rx\", 4);\n        g.appendChild(body);\n        for (let i = 0; i < 3; i++) {\n         const block = document.createElementNS(ns, \"rect\");\n         block.setAttribute(\"x\", -size * 0.8 + i * size * 0.55);\n         block.setAttribute(\"y\", -size * 0.3);\n         block.setAttribute(\"width\", size * 0.45);\n         block.setAttribute(\"height\", size * 0.6);\n         block.setAttribute(\"rx\", 2);\n         block.style.fill = \"#1e293b\";\n         g.appendChild(block);\n        }\n        return g;\n       }\n       if (shape === \"vm\" || shape === \"virtual\") {\n        const g = document.createElementNS(ns, \"g\");\n        const shadow = document.createElementNS(ns, \"rect\");\n        shadow.setAttribute(\"x\", -size * 0.85 + 4);\n        shadow.setAttribute(\"y\", -size * 0.65 + 4);\n        shadow.setAttribute(\"width\", size * 1.7);\n        shadow.setAttribute(\"height\", size * 1.3);\n        shadow.setAttribute(\"rx\", 4);\n        shadow.style.opacity = \"0.3\";\n        g.appendChild(shadow);\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 0.85);\n        body.setAttribute(\"y\", -size * 0.65);\n        body.setAttribute(\"width\", size * 1.7);\n        body.setAttribute(\"height\", size * 1.3);\n        body.setAttribute(\"rx\", 4);\n        g.appendChild(body);\n        const screen = document.createElementNS(ns, \"rect\");\n        screen.setAttribute(\"x\", -size * 0.65);\n        screen.setAttribute(\"y\", -size * 0.45);\n        screen.setAttribute(\"width\", size * 1.3);\n        screen.setAttribute(\"height\", size * 0.7);\n        screen.setAttribute(\"rx\", 2);\n        screen.style.fill = \"#1e293b\";\n        g.appendChild(screen);\n        return g;\n       }\n       if (shape === \"kubernetes\" || shape === \"k8s\") {\n        const g = document.createElementNS(ns, \"g\");\n        const outer = document.createElementNS(ns, \"circle\");\n        outer.setAttribute(\"cx\", 0);\n        outer.setAttribute(\"cy\", 0);\n        outer.setAttribute(\"r\", size);\n        g.appendChild(outer);\n        const inner = document.createElementNS(ns, \"circle\");\n        inner.setAttribute(\"cx\", 0);\n        inner.setAttribute(\"cy\", 0);\n        inner.setAttribute(\"r\", size * 0.4);\n        inner.style.fill = \"#1e293b\";\n        g.appendChild(inner);\n        for (let i = 0; i < 7; i++) {\n         const angle = (Math.PI * 2 / 7) * i - Math.PI / 2;\n         const spoke = document.createElementNS(ns, \"line\");\n         spoke.setAttribute(\"x1\", Math.cos(angle) * size * 0.4);\n         spoke.setAttribute(\"y1\", Math.sin(angle) * size * 0.4);\n         spoke.setAttribute(\"x2\", Math.cos(angle) * size * 0.85);\n         spoke.setAttribute(\"y2\", Math.sin(angle) * size * 0.85);\n         spoke.style.stroke = \"#326ce5\";\n         spoke.style.strokeWidth = \"3\";\n         g.appendChild(spoke);\n        }\n        return g;\n       }\n       if (shape === \"shield\" || shape === \"security\") {\n        const p = document.createElementNS(ns, \"path\");\n        const s = size;\n        p.setAttribute(\"d\", `M 0 ${-s} L ${s * 0.85} ${-s * 0.5} L ${s * 0.85} ${s * 0.2} Q ${s * 0.7} ${s * 0.9} 0 ${s} Q ${-s * 0.7} ${s * 0.9} ${-s * 0.85} ${s * 0.2} L ${-s * 0.85} ${-s * 0.5} Z`);\n        return p;\n       }\n       if (shape === \"camera\" || shape === \"cctv\") {\n        const g = document.createElementNS(ns, \"g\");\n        const mount = document.createElementNS(ns, \"rect\");\n        mount.setAttribute(\"x\", -size * 1.1);\n        mount.setAttribute(\"y\", -size * 0.5);\n        mount.setAttribute(\"width\", size * 0.25);\n        mount.setAttribute(\"height\", size * 0.6);\n        mount.setAttribute(\"rx\", 2);\n        g.appendChild(mount);\n        const arm = document.createElementNS(ns, \"rect\");\n        arm.setAttribute(\"x\", -size * 0.9);\n        arm.setAttribute(\"y\", -size * 0.15);\n        arm.setAttribute(\"width\", size * 0.5);\n        arm.setAttribute(\"height\", size * 0.15);\n        g.appendChild(arm);\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 0.45);\n        body.setAttribute(\"y\", -size * 0.35);\n        body.setAttribute(\"width\", size * 1.1);\n        body.setAttribute(\"height\", size * 0.7);\n        body.setAttribute(\"rx\", 4);\n        g.appendChild(body);\n        const lensHousing = document.createElementNS(ns, \"circle\");\n        lensHousing.setAttribute(\"cx\", size * 0.85);\n        lensHousing.setAttribute(\"cy\", 0);\n        lensHousing.setAttribute(\"r\", size * 0.4);\n        g.appendChild(lensHousing);\n        const lensOuter = document.createElementNS(ns, \"circle\");\n        lensOuter.setAttribute(\"cx\", size * 0.85);\n        lensOuter.setAttribute(\"cy\", 0);\n        lensOuter.setAttribute(\"r\", size * 0.28);\n        lensOuter.style.fill = \"#1e293b\";\n        g.appendChild(lensOuter);\n        const lensInner = document.createElementNS(ns, \"circle\");\n        lensInner.setAttribute(\"cx\", size * 0.85);\n        lensInner.setAttribute(\"cy\", 0);\n        lensInner.setAttribute(\"r\", size * 0.15);\n        lensInner.style.fill = \"#3b82f6\";\n        g.appendChild(lensInner);\n        const reflection = document.createElementNS(ns, \"circle\");\n        reflection.setAttribute(\"cx\", size * 0.8);\n        reflection.setAttribute(\"cy\", -size * 0.05);\n        reflection.setAttribute(\"r\", size * 0.05);\n        reflection.style.fill = \"#93c5fd\";\n        g.appendChild(reflection);\n        const led = document.createElementNS(ns, \"circle\");\n        led.setAttribute(\"cx\", -size * 0.2);\n        led.setAttribute(\"cy\", -size * 0.15);\n        led.setAttribute(\"r\", size * 0.06);\n        led.style.fill = \"#ef4444\";\n        g.appendChild(led);\n        return g;\n       }\n       if (shape === \"monitor\" || shape === \"dashboard\") {\n        const g = document.createElementNS(ns, \"g\");\n        const screen = document.createElementNS(ns, \"rect\");\n        screen.setAttribute(\"x\", -size);\n        screen.setAttribute(\"y\", -size * 0.7);\n        screen.setAttribute(\"width\", size * 2);\n        screen.setAttribute(\"height\", size * 1.2);\n        screen.setAttribute(\"rx\", 4);\n        g.appendChild(screen);\n        const graph = document.createElementNS(ns, \"polyline\");\n        graph.setAttribute(\"points\", `${-size * 0.8},${size * 0.2} ${-size * 0.3},${-size * 0.2} ${size * 0.2},${size * 0.1} ${size * 0.7},${-size * 0.3}`);\n        graph.style.fill = \"none\";\n        graph.style.stroke = \"#4ade80\";\n        graph.style.strokeWidth = \"3\";\n        g.appendChild(graph);\n        return g;\n       }\n       if (shape === \"docker\" || shape === \"whale\") {\n        const g = document.createElementNS(ns, \"g\");\n        const s = size;\n        const body = document.createElementNS(ns, \"path\");\n        body.setAttribute(\"d\", `\n         M ${-s * 0.9} ${s * 0.2}\n         Q ${-s * 1.1} ${s * 0.5} ${-s * 0.7} ${s * 0.7}\n         Q ${-s * 0.3} ${s * 0.85} ${s * 0.3} ${s * 0.75}\n         Q ${s * 0.8} ${s * 0.6} ${s * 1.0} ${s * 0.3}\n         Q ${s * 1.1} ${s * 0.1} ${s * 1.0} ${-s * 0.1}\n         L ${s * 0.85} ${-s * 0.1}\n         Q ${s * 0.7} ${-s * 0.15} ${s * 0.4} ${-s * 0.2}\n         L ${-s * 0.5} ${-s * 0.2}\n         Q ${-s * 0.8} ${-s * 0.1} ${-s * 0.9} ${s * 0.2}\n         Z\n        `);\n        body.style.fill = \"#0db7ed\";\n        g.appendChild(body);\n        const tail = document.createElementNS(ns, \"path\");\n        tail.setAttribute(\"d\", `\n         M ${-s * 0.85} ${s * 0.1}\n         Q ${-s * 1.2} ${-s * 0.2} ${-s * 1.0} ${-s * 0.55}\n         Q ${-s * 0.95} ${-s * 0.35} ${-s * 0.8} ${-s * 0.15}\n         Z\n        `);\n        tail.style.fill = \"#0db7ed\";\n        g.appendChild(tail);\n        const belly = document.createElementNS(ns, \"path\");\n        belly.setAttribute(\"d\", `\n         M ${-s * 0.5} ${s * 0.65}\n         Q ${s * 0.1} ${s * 0.75} ${s * 0.6} ${s * 0.55}\n         Q ${s * 0.8} ${s * 0.45} ${s * 0.9} ${s * 0.25}\n         Q ${s * 0.7} ${s * 0.5} ${s * 0.3} ${s * 0.6}\n         Q ${-s * 0.1} ${s * 0.7} ${-s * 0.5} ${s * 0.65}\n         Z\n        `);\n        belly.style.fill = \"#ffffff\";\n        belly.style.opacity = \"0.3\";\n        g.appendChild(belly);\n        for (let row = 0; row < 2; row++) {\n         for (let col = 0; col < 3; col++) {\n          const container = document.createElementNS(ns, \"rect\");\n          container.setAttribute(\"x\", -s * 0.45 + col * s * 0.35);\n          container.setAttribute(\"y\", -s * 0.7 + row * s * 0.28);\n          container.setAttribute(\"width\", s * 0.3);\n          container.setAttribute(\"height\", s * 0.23);\n          container.setAttribute(\"rx\", 2);\n          container.style.fill = \"#0db7ed\";\n          container.style.stroke = \"#0a9ed8\";\n          container.style.strokeWidth = \"1.5\";\n          g.appendChild(container);\n         }\n        }\n        const topContainer = document.createElementNS(ns, \"rect\");\n        topContainer.setAttribute(\"x\", -s * 0.1);\n        topContainer.setAttribute(\"y\", -s * 0.95);\n        topContainer.setAttribute(\"width\", s * 0.3);\n        topContainer.setAttribute(\"height\", s * 0.23);\n        topContainer.setAttribute(\"rx\", 2);\n        topContainer.style.fill = \"#0db7ed\";\n        topContainer.style.stroke = \"#0a9ed8\";\n        topContainer.style.strokeWidth = \"1.5\";\n        g.appendChild(topContainer);\n        const eye = document.createElementNS(ns, \"circle\");\n        eye.setAttribute(\"cx\", s * 0.65);\n        eye.setAttribute(\"cy\", s * 0.25);\n        eye.setAttribute(\"r\", s * 0.08);\n        eye.style.fill = \"#0a5f7a\";\n        g.appendChild(eye);\n        return g;\n       }\n       if (shape === \"rounded-square\") {\n        const r = document.createElementNS(ns, \"rect\");\n        const s = size * 1.6;\n        r.setAttribute(\"x\", -s / 2);\n        r.setAttribute(\"y\", -s / 2);\n        r.setAttribute(\"width\", s);\n        r.setAttribute(\"height\", s);\n        r.setAttribute(\"rx\", size * 0.4);\n        return r;\n       }\n       if (shape === \"pill\" || shape === \"capsule\") {\n        const r = document.createElementNS(ns, \"rect\");\n        r.setAttribute(\"x\", -size * 1.2);\n        r.setAttribute(\"y\", -size * 0.5);\n        r.setAttribute(\"width\", size * 2.4);\n        r.setAttribute(\"height\", size);\n        r.setAttribute(\"rx\", size * 0.5);\n        return r;\n       }\n       if (shape === \"octagon\") {\n        const p = document.createElementNS(ns, \"polygon\");\n        const r = size;\n        const pts = [];\n        for (let i = 0; i < 8; i++) {\n         const a = (Math.PI / 4) * i - Math.PI / 8;\n         pts.push([Math.cos(a) * r, Math.sin(a) * r]);\n        }\n        p.setAttribute(\"points\", pts.map(pt => pt.join(\",\")).join(\" \"));\n        return p;\n       }\n       if (shape === \"pentagon\") {\n        const p = document.createElementNS(ns, \"polygon\");\n        const pts = [];\n        for (let i = 0; i < 5; i++) {\n         const a = (Math.PI * 2 / 5) * i - Math.PI / 2;\n         pts.push([Math.cos(a) * size, Math.sin(a) * size]);\n        }\n        p.setAttribute(\"points\", pts.map(pt => pt.join(\",\")).join(\" \"));\n        return p;\n       }\n       if (shape === \"cross\" || shape === \"plus\") {\n        const p = document.createElementNS(ns, \"polygon\");\n        const s = size;\n        const t = size * 0.35;\n        p.setAttribute(\"points\", `${-t},${-s} ${t},${-s} ${t},${-t} ${s},${-t} ${s},${t} ${t},${t} ${t},${s} ${-t},${s} ${-t},${t} ${-s},${t} ${-s},${-t} ${-t},${-t}`);\n        return p;\n       }\n       if (shape === \"parallelogram\") {\n        const p = document.createElementNS(ns, \"polygon\");\n        const s = size;\n        p.setAttribute(\"points\", `${-s * 0.6},${-s * 0.6} ${s * 1.2},${-s * 0.6} ${s * 0.6},${s * 0.6} ${-s * 1.2},${s * 0.6}`);\n        return p;\n       }\n       if (shape === \"trapezoid\") {\n        const p = document.createElementNS(ns, \"polygon\");\n        const s = size;\n        p.setAttribute(\"points\", `${-s * 0.6},${-s * 0.6} ${s * 0.6},${-s * 0.6} ${s},${s * 0.6} ${-s},${s * 0.6}`);\n        return p;\n       }\n       if (shape === \"sensor\" || shape === \"iot\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"circle\");\n        body.setAttribute(\"cx\", 0);\n        body.setAttribute(\"cy\", 0);\n        body.setAttribute(\"r\", size * 0.7);\n        g.appendChild(body);\n        const ant = document.createElementNS(ns, \"line\");\n        ant.setAttribute(\"x1\", 0);\n        ant.setAttribute(\"y1\", -size * 0.7);\n        ant.setAttribute(\"x2\", 0);\n        ant.setAttribute(\"y2\", -size);\n        ant.style.stroke = \"currentColor\";\n        ant.style.strokeWidth = \"2\";\n        g.appendChild(ant);\n        const tip = document.createElementNS(ns, \"circle\");\n        tip.setAttribute(\"cx\", 0);\n        tip.setAttribute(\"cy\", -size);\n        tip.setAttribute(\"r\", size * 0.1);\n        tip.style.fill = \"#4ade80\";\n        g.appendChild(tip);\n        return g;\n       }\n       if (shape === \"pi\" || shape === \"sbc\" || shape === \"raspberry\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size);\n        body.setAttribute(\"y\", -size * 0.65);\n        body.setAttribute(\"width\", size * 2);\n        body.setAttribute(\"height\", size * 1.3);\n        body.setAttribute(\"rx\", 4);\n        g.appendChild(body);\n        for (let i = 0; i < 10; i++) {\n         const pin = document.createElementNS(ns, \"rect\");\n         pin.setAttribute(\"x\", -size * 0.85 + i * size * 0.19);\n         pin.setAttribute(\"y\", -size * 0.8);\n         pin.setAttribute(\"width\", size * 0.08);\n         pin.setAttribute(\"height\", size * 0.15);\n         pin.style.fill = \"#facc15\";\n         g.appendChild(pin);\n        }\n        const port = document.createElementNS(ns, \"rect\");\n        port.setAttribute(\"x\", size * 0.6);\n        port.setAttribute(\"y\", -size * 0.2);\n        port.setAttribute(\"width\", size * 0.35);\n        port.setAttribute(\"height\", size * 0.4);\n        port.style.fill = \"#1e293b\";\n        g.appendChild(port);\n        return g;\n       }\n       if (shape === \"api\" || shape === \"endpoint\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 0.9);\n        body.setAttribute(\"y\", -size * 0.7);\n        body.setAttribute(\"width\", size * 1.8);\n        body.setAttribute(\"height\", size * 1.4);\n        body.setAttribute(\"rx\", 6);\n        g.appendChild(body);\n        const left = document.createElementNS(ns, \"text\");\n        left.setAttribute(\"x\", -size * 0.5);\n        left.setAttribute(\"y\", size * 0.15);\n        left.setAttribute(\"font-size\", size * 0.9);\n        left.setAttribute(\"fill\", \"#4ade80\");\n        left.setAttribute(\"font-family\", \"monospace\");\n        left.textContent = \"{\";\n        g.appendChild(left);\n        const right = document.createElementNS(ns, \"text\");\n        right.setAttribute(\"x\", size * 0.15);\n        right.setAttribute(\"y\", size * 0.15);\n        right.setAttribute(\"font-size\", size * 0.9);\n        right.setAttribute(\"fill\", \"#4ade80\");\n        right.setAttribute(\"font-family\", \"monospace\");\n        right.textContent = \"}\";\n        g.appendChild(right);\n        return g;\n       }\n       if (shape === \"queue\" || shape === \"message\") {\n        const g = document.createElementNS(ns, \"g\");\n        for (let i = 2; i >= 0; i--) {\n         const card = document.createElementNS(ns, \"rect\");\n         card.setAttribute(\"x\", -size * 0.7 + i * 4);\n         card.setAttribute(\"y\", -size * 0.5 + i * 4);\n         card.setAttribute(\"width\", size * 1.4);\n         card.setAttribute(\"height\", size * 0.8);\n         card.setAttribute(\"rx\", 3);\n         card.style.opacity = 1 - i * 0.25;\n         g.appendChild(card);\n        }\n        return g;\n       }\n       if (shape === \"lambda\" || shape === \"function\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 0.8);\n        body.setAttribute(\"y\", -size * 0.8);\n        body.setAttribute(\"width\", size * 1.6);\n        body.setAttribute(\"height\", size * 1.6);\n        body.setAttribute(\"rx\", 4);\n        g.appendChild(body);\n        const lambda = document.createElementNS(ns, \"text\");\n        lambda.setAttribute(\"x\", 0);\n        lambda.setAttribute(\"y\", size * 0.2);\n        lambda.setAttribute(\"font-size\", size * 1.2);\n        lambda.setAttribute(\"fill\", \"#f59e0b\");\n        lambda.setAttribute(\"text-anchor\", \"middle\");\n        lambda.setAttribute(\"font-family\", \"serif\");\n        lambda.textContent = \"λ\";\n        g.appendChild(lambda);\n        return g;\n       }\n       if (shape === \"bucket\" || shape === \"s3\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"path\");\n        body.setAttribute(\"d\", `M ${-size * 0.8} ${-size * 0.7} L ${-size * 0.6} ${size * 0.8} Q ${-size * 0.5} ${size} 0 ${size} Q ${size * 0.5} ${size} ${size * 0.6} ${size * 0.8} L ${size * 0.8} ${-size * 0.7} Z`);\n        g.appendChild(body);\n        const rim = document.createElementNS(ns, \"ellipse\");\n        rim.setAttribute(\"cx\", 0);\n        rim.setAttribute(\"cy\", -size * 0.7);\n        rim.setAttribute(\"rx\", size * 0.8);\n        rim.setAttribute(\"ry\", size * 0.25);\n        g.appendChild(rim);\n        return g;\n       }\n\n       if (shape === \"thermostat\") {\n        const g = document.createElementNS(ns, \"g\");\n        const outer = document.createElementNS(ns, \"circle\");\n        outer.setAttribute(\"r\", size);\n        g.appendChild(outer);\n        const inner = document.createElementNS(ns, \"circle\");\n        inner.setAttribute(\"r\", size * 0.75);\n        inner.style.fill = \"#1e293b\";\n        g.appendChild(inner);\n        const temp = document.createElementNS(ns, \"text\");\n        temp.setAttribute(\"x\", 0);\n        temp.setAttribute(\"y\", size * 0.15);\n        temp.setAttribute(\"font-size\", size * 0.5);\n        temp.setAttribute(\"fill\", \"#4ade80\");\n        temp.setAttribute(\"text-anchor\", \"middle\");\n        temp.setAttribute(\"font-family\", \"monospace\");\n        temp.textContent = \"72°\";\n        g.appendChild(temp);\n        return g;\n       }\n       if (shape === \"doorbell\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 0.5);\n        body.setAttribute(\"y\", -size);\n        body.setAttribute(\"width\", size);\n        body.setAttribute(\"height\", size * 2);\n        body.setAttribute(\"rx\", size * 0.25);\n        g.appendChild(body);\n        const lens = document.createElementNS(ns, \"circle\");\n        lens.setAttribute(\"cx\", 0);\n        lens.setAttribute(\"cy\", -size * 0.35);\n        lens.setAttribute(\"r\", size * 0.3);\n        lens.style.fill = \"#1e293b\";\n        g.appendChild(lens);\n        const lensDot = document.createElementNS(ns, \"circle\");\n        lensDot.setAttribute(\"cx\", 0);\n        lensDot.setAttribute(\"cy\", -size * 0.35);\n        lensDot.setAttribute(\"r\", size * 0.12);\n        lensDot.style.fill = \"#3b82f6\";\n        g.appendChild(lensDot);\n        const btn = document.createElementNS(ns, \"circle\");\n        btn.setAttribute(\"cx\", 0);\n        btn.setAttribute(\"cy\", size * 0.5);\n        btn.setAttribute(\"r\", size * 0.2);\n        btn.style.fill = \"#f59e0b\";\n        g.appendChild(btn);\n        return g;\n       }\n       if (shape === \"smart-lock\") {\n        const g = document.createElementNS(ns, \"g\");\n        const shackle = document.createElementNS(ns, \"path\");\n        shackle.setAttribute(\"d\", `M ${-size * 0.4} ${-size * 0.1} L ${-size * 0.4} ${-size * 0.6} A ${size * 0.4} ${size * 0.4} 0 1 1 ${size * 0.4} ${-size * 0.6} L ${size * 0.4} ${-size * 0.1}`);\n        shackle.style.fill = \"none\";\n        shackle.style.strokeWidth = size * 0.2;\n        g.appendChild(shackle);\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 0.6);\n        body.setAttribute(\"y\", -size * 0.15);\n        body.setAttribute(\"width\", size * 1.2);\n        body.setAttribute(\"height\", size * 1);\n        body.setAttribute(\"rx\", 4);\n        g.appendChild(body);\n        const keyhole = document.createElementNS(ns, \"circle\");\n        keyhole.setAttribute(\"cx\", 0);\n        keyhole.setAttribute(\"cy\", size * 0.3);\n        keyhole.setAttribute(\"r\", size * 0.15);\n        keyhole.style.fill = \"#4ade80\";\n        g.appendChild(keyhole);\n        return g;\n       }\n       if (shape === \"smart-bulb\") {\n        const g = document.createElementNS(ns, \"g\");\n        const bulb = document.createElementNS(ns, \"path\");\n        bulb.setAttribute(\"d\", `M ${-size * 0.5} ${size * 0.2} Q ${-size * 0.8} ${-size * 0.3} ${-size * 0.5} ${-size * 0.7} Q 0 ${-size * 1.1} ${size * 0.5} ${-size * 0.7} Q ${size * 0.8} ${-size * 0.3} ${size * 0.5} ${size * 0.2} Z`);\n        g.appendChild(bulb);\n        const base1 = document.createElementNS(ns, \"rect\");\n        base1.setAttribute(\"x\", -size * 0.35);\n        base1.setAttribute(\"y\", size * 0.2);\n        base1.setAttribute(\"width\", size * 0.7);\n        base1.setAttribute(\"height\", size * 0.15);\n        base1.style.fill = \"#94a3b8\";\n        g.appendChild(base1);\n        const base2 = document.createElementNS(ns, \"rect\");\n        base2.setAttribute(\"x\", -size * 0.3);\n        base2.setAttribute(\"y\", size * 0.35);\n        base2.setAttribute(\"width\", size * 0.6);\n        base2.setAttribute(\"height\", size * 0.15);\n        base2.style.fill = \"#64748b\";\n        g.appendChild(base2);\n        const base3 = document.createElementNS(ns, \"rect\");\n        base3.setAttribute(\"x\", -size * 0.25);\n        base3.setAttribute(\"y\", size * 0.5);\n        base3.setAttribute(\"width\", size * 0.5);\n        base3.setAttribute(\"height\", size * 0.2);\n        base3.setAttribute(\"rx\", 2);\n        base3.style.fill = \"#475569\";\n        g.appendChild(base3);\n        return g;\n       }\n       if (shape === \"smart-plug\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 0.7);\n        body.setAttribute(\"y\", -size * 0.6);\n        body.setAttribute(\"width\", size * 1.4);\n        body.setAttribute(\"height\", size * 1.2);\n        body.setAttribute(\"rx\", 6);\n        g.appendChild(body);\n        const hole1 = document.createElementNS(ns, \"rect\");\n        hole1.setAttribute(\"x\", -size * 0.35);\n        hole1.setAttribute(\"y\", -size * 0.3);\n        hole1.setAttribute(\"width\", size * 0.15);\n        hole1.setAttribute(\"height\", size * 0.4);\n        hole1.setAttribute(\"rx\", 2);\n        hole1.style.fill = \"#1e293b\";\n        g.appendChild(hole1);\n        const hole2 = document.createElementNS(ns, \"rect\");\n        hole2.setAttribute(\"x\", size * 0.2);\n        hole2.setAttribute(\"y\", -size * 0.3);\n        hole2.setAttribute(\"width\", size * 0.15);\n        hole2.setAttribute(\"height\", size * 0.4);\n        hole2.setAttribute(\"rx\", 2);\n        hole2.style.fill = \"#1e293b\";\n        g.appendChild(hole2);\n        const led = document.createElementNS(ns, \"circle\");\n        led.setAttribute(\"cx\", 0);\n        led.setAttribute(\"cy\", size * 0.35);\n        led.setAttribute(\"r\", size * 0.1);\n        led.style.fill = \"#4ade80\";\n        g.appendChild(led);\n        return g;\n       }\n       if (shape === \"smart-speaker\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 0.6);\n        body.setAttribute(\"y\", -size);\n        body.setAttribute(\"width\", size * 1.2);\n        body.setAttribute(\"height\", size * 2);\n        body.setAttribute(\"rx\", size * 0.3);\n        g.appendChild(body);\n        const mesh = document.createElementNS(ns, \"rect\");\n        mesh.setAttribute(\"x\", -size * 0.5);\n        mesh.setAttribute(\"y\", -size * 0.3);\n        mesh.setAttribute(\"width\", size);\n        mesh.setAttribute(\"height\", size * 1.1);\n        mesh.setAttribute(\"rx\", 4);\n        mesh.style.fill = \"#1e293b\";\n        g.appendChild(mesh);\n        const ring = document.createElementNS(ns, \"circle\");\n        ring.setAttribute(\"cx\", 0);\n        ring.setAttribute(\"cy\", -size * 0.65);\n        ring.setAttribute(\"r\", size * 0.2);\n        ring.style.fill = \"#3b82f6\";\n        g.appendChild(ring);\n        return g;\n       }\n       if (shape === \"smart-tv\") {\n        const g = document.createElementNS(ns, \"g\");\n        const frame = document.createElementNS(ns, \"rect\");\n        frame.setAttribute(\"x\", -size * 1.4);\n        frame.setAttribute(\"y\", -size * 0.85);\n        frame.setAttribute(\"width\", size * 2.8);\n        frame.setAttribute(\"height\", size * 1.6);\n        frame.setAttribute(\"rx\", 4);\n        g.appendChild(frame);\n        const screen = document.createElementNS(ns, \"rect\");\n        screen.setAttribute(\"x\", -size * 1.3);\n        screen.setAttribute(\"y\", -size * 0.75);\n        screen.setAttribute(\"width\", size * 2.6);\n        screen.setAttribute(\"height\", size * 1.4);\n        screen.setAttribute(\"rx\", 2);\n        screen.style.fill = \"#1e293b\";\n        g.appendChild(screen);\n        const stand = document.createElementNS(ns, \"rect\");\n        stand.setAttribute(\"x\", -size * 0.8);\n        stand.setAttribute(\"y\", size * 0.75);\n        stand.setAttribute(\"width\", size * 1.6);\n        stand.setAttribute(\"height\", size * 0.12);\n        stand.setAttribute(\"rx\", 2);\n        g.appendChild(stand);\n        return g;\n       }\n       if (shape === \"hub\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 0.9);\n        body.setAttribute(\"y\", -size * 0.5);\n        body.setAttribute(\"width\", size * 1.8);\n        body.setAttribute(\"height\", size);\n        body.setAttribute(\"rx\", 8);\n        g.appendChild(body);\n        for (let i = 0; i < 4; i++) {\n         const led = document.createElementNS(ns, \"circle\");\n         led.setAttribute(\"cx\", -size * 0.5 + i * size * 0.35);\n         led.setAttribute(\"cy\", 0);\n         led.setAttribute(\"r\", size * 0.08);\n         led.style.fill = [\"#4ade80\", \"#3b82f6\", \"#f59e0b\", \"#ef4444\"][i];\n         g.appendChild(led);\n        }\n        return g;\n       }\n       if (shape === \"smoke-detector\") {\n        const g = document.createElementNS(ns, \"g\");\n        const outer = document.createElementNS(ns, \"circle\");\n        outer.setAttribute(\"r\", size);\n        g.appendChild(outer);\n        const inner = document.createElementNS(ns, \"circle\");\n        inner.setAttribute(\"r\", size * 0.6);\n        inner.style.fill = \"#1e293b\";\n        g.appendChild(inner);\n        for (let i = 0; i < 6; i++) {\n         const slot = document.createElementNS(ns, \"rect\");\n         const angle = (i * 60 - 90) * Math.PI / 180;\n         slot.setAttribute(\"x\", Math.cos(angle) * size * 0.35 - size * 0.08);\n         slot.setAttribute(\"y\", Math.sin(angle) * size * 0.35 - size * 0.03);\n         slot.setAttribute(\"width\", size * 0.16);\n         slot.setAttribute(\"height\", size * 0.06);\n         slot.setAttribute(\"rx\", 1);\n         slot.style.fill = \"#475569\";\n         slot.setAttribute(\"transform\", `rotate(${i * 60}, ${Math.cos(angle) * size * 0.35}, ${Math.sin(angle) * size * 0.35})`);\n         g.appendChild(slot);\n        }\n        const led = document.createElementNS(ns, \"circle\");\n        led.setAttribute(\"cx\", 0);\n        led.setAttribute(\"cy\", 0);\n        led.setAttribute(\"r\", size * 0.1);\n        led.style.fill = \"#ef4444\";\n        g.appendChild(led);\n        return g;\n       }\n       if (shape === \"motion-sensor\") {\n        const g = document.createElementNS(ns, \"g\");\n        const dome = document.createElementNS(ns, \"path\");\n        dome.setAttribute(\"d\", `M ${-size * 0.8} ${size * 0.3} Q ${-size * 0.8} ${-size * 0.8} 0 ${-size * 0.8} Q ${size * 0.8} ${-size * 0.8} ${size * 0.8} ${size * 0.3} Z`);\n        g.appendChild(dome);\n        const base = document.createElementNS(ns, \"rect\");\n        base.setAttribute(\"x\", -size * 0.8);\n        base.setAttribute(\"y\", size * 0.3);\n        base.setAttribute(\"width\", size * 1.6);\n        base.setAttribute(\"height\", size * 0.35);\n        base.setAttribute(\"rx\", 3);\n        g.appendChild(base);\n        const lens = document.createElementNS(ns, \"ellipse\");\n        lens.setAttribute(\"cx\", 0);\n        lens.setAttribute(\"cy\", -size * 0.15);\n        lens.setAttribute(\"rx\", size * 0.35);\n        lens.setAttribute(\"ry\", size * 0.25);\n        lens.style.fill = \"#1e293b\";\n        g.appendChild(lens);\n        const led = document.createElementNS(ns, \"circle\");\n        led.setAttribute(\"cx\", 0);\n        led.setAttribute(\"cy\", size * 0.45);\n        led.setAttribute(\"r\", size * 0.08);\n        led.style.fill = \"#4ade80\";\n        g.appendChild(led);\n        return g;\n       }\n       if (shape === \"garage\") {\n        const g = document.createElementNS(ns, \"g\");\n        const frame = document.createElementNS(ns, \"rect\");\n        frame.setAttribute(\"x\", -size);\n        frame.setAttribute(\"y\", -size * 0.9);\n        frame.setAttribute(\"width\", size * 2);\n        frame.setAttribute(\"height\", size * 1.8);\n        frame.setAttribute(\"rx\", 4);\n        g.appendChild(frame);\n        for (let i = 0; i < 4; i++) {\n         const panel = document.createElementNS(ns, \"rect\");\n         panel.setAttribute(\"x\", -size * 0.9);\n         panel.setAttribute(\"y\", -size * 0.8 + i * size * 0.42);\n         panel.setAttribute(\"width\", size * 1.8);\n         panel.setAttribute(\"height\", size * 0.35);\n         panel.setAttribute(\"rx\", 2);\n         panel.style.fill = \"#1e293b\";\n         g.appendChild(panel);\n        }\n        return g;\n       }\n       if (shape === \"sprinkler\") {\n        const g = document.createElementNS(ns, \"g\");\n        const head = document.createElementNS(ns, \"circle\");\n        head.setAttribute(\"r\", size * 0.5);\n        g.appendChild(head);\n        const nozzle = document.createElementNS(ns, \"rect\");\n        nozzle.setAttribute(\"x\", -size * 0.15);\n        nozzle.setAttribute(\"y\", size * 0.3);\n        nozzle.setAttribute(\"width\", size * 0.3);\n        nozzle.setAttribute(\"height\", size * 0.5);\n        g.appendChild(nozzle);\n        for (let i = 0; i < 5; i++) {\n         const spray = document.createElementNS(ns, \"line\");\n         const angle = (-60 + i * 30) * Math.PI / 180;\n         spray.setAttribute(\"x1\", 0);\n         spray.setAttribute(\"y1\", -size * 0.3);\n         spray.setAttribute(\"x2\", Math.cos(angle) * size * 0.8);\n         spray.setAttribute(\"y2\", Math.sin(angle) * size * 0.8 - size * 0.3);\n         spray.style.stroke = \"#3b82f6\";\n         spray.style.strokeWidth = \"2\";\n         spray.style.strokeDasharray = \"3,3\";\n         g.appendChild(spray);\n        }\n        return g;\n       }\n       if (shape === \"vacuum\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"circle\");\n        body.setAttribute(\"r\", size);\n        g.appendChild(body);\n        const top = document.createElementNS(ns, \"circle\");\n        top.setAttribute(\"r\", size * 0.7);\n        top.style.fill = \"#1e293b\";\n        g.appendChild(top);\n        const bumper = document.createElementNS(ns, \"path\");\n        bumper.setAttribute(\"d\", `M ${-size * 0.7} ${-size * 0.4} A ${size * 0.8} ${size * 0.8} 0 0 1 ${size * 0.7} ${-size * 0.4}`);\n        bumper.style.fill = \"none\";\n        bumper.style.strokeWidth = size * 0.15;\n        g.appendChild(bumper);\n        const btn = document.createElementNS(ns, \"circle\");\n        btn.setAttribute(\"cx\", 0);\n        btn.setAttribute(\"cy\", size * 0.1);\n        btn.setAttribute(\"r\", size * 0.2);\n        btn.style.fill = \"#4ade80\";\n        g.appendChild(btn);\n        return g;\n       }\n       if (shape === \"basketball-ball\") {\n        const g = document.createElementNS(ns, \"g\");\n        const ball = document.createElementNS(ns, \"circle\");\n        ball.setAttribute(\"r\", size);\n        g.appendChild(ball);\n        const hLine = document.createElementNS(ns, \"line\");\n        hLine.setAttribute(\"x1\", -size);\n        hLine.setAttribute(\"y1\", 0);\n        hLine.setAttribute(\"x2\", size);\n        hLine.setAttribute(\"y2\", 0);\n        hLine.style.stroke = \"currentColor\";\n        hLine.style.strokeWidth = \"2\";\n        hLine.style.opacity = \"0.5\";\n        g.appendChild(hLine);\n        const vLine = document.createElementNS(ns, \"line\");\n        vLine.setAttribute(\"x1\", 0);\n        vLine.setAttribute(\"y1\", -size);\n        vLine.setAttribute(\"x2\", 0);\n        vLine.setAttribute(\"y2\", size);\n        vLine.style.stroke = \"currentColor\";\n        vLine.style.strokeWidth = \"2\";\n        vLine.style.opacity = \"0.5\";\n        g.appendChild(vLine);\n        const curve1 = document.createElementNS(ns, \"path\");\n        curve1.setAttribute(\"d\", `M ${-size * 0.5} ${-size} Q ${-size * 0.8} 0 ${-size * 0.5} ${size}`);\n        curve1.style.fill = \"none\";\n        curve1.style.stroke = \"currentColor\";\n        curve1.style.strokeWidth = \"2\";\n        curve1.style.opacity = \"0.5\";\n        g.appendChild(curve1);\n        const curve2 = document.createElementNS(ns, \"path\");\n        curve2.setAttribute(\"d\", `M ${size * 0.5} ${-size} Q ${size * 0.8} 0 ${size * 0.5} ${size}`);\n        curve2.style.fill = \"none\";\n        curve2.style.stroke = \"currentColor\";\n        curve2.style.strokeWidth = \"2\";\n        curve2.style.opacity = \"0.5\";\n        g.appendChild(curve2);\n        return g;\n       }\n       if (shape === \"football-ball\") {\n        const g = document.createElementNS(ns, \"g\");\n        const ball = document.createElementNS(ns, \"ellipse\");\n        ball.setAttribute(\"rx\", size * 1.3);\n        ball.setAttribute(\"ry\", size * 0.7);\n        g.appendChild(ball);\n        const laces = document.createElementNS(ns, \"line\");\n        laces.setAttribute(\"x1\", -size * 0.4);\n        laces.setAttribute(\"y1\", 0);\n        laces.setAttribute(\"x2\", size * 0.4);\n        laces.setAttribute(\"y2\", 0);\n        laces.style.stroke = \"currentColor\";\n        laces.style.strokeWidth = \"3\";\n        laces.style.opacity = \"0.5\";\n        g.appendChild(laces);\n        for (let i = -2; i <= 2; i++) {\n         const stitch = document.createElementNS(ns, \"line\");\n         stitch.setAttribute(\"x1\", i * size * 0.15);\n         stitch.setAttribute(\"y1\", -size * 0.15);\n         stitch.setAttribute(\"x2\", i * size * 0.15);\n         stitch.setAttribute(\"y2\", size * 0.15);\n         stitch.style.stroke = \"currentColor\";\n         stitch.style.strokeWidth = \"2\";\n         stitch.style.opacity = \"0.5\";\n         g.appendChild(stitch);\n        }\n        return g;\n       }\n       if (shape === \"soccer-ball\") {\n        const g = document.createElementNS(ns, \"g\");\n        const ball = document.createElementNS(ns, \"circle\");\n        ball.setAttribute(\"r\", size);\n        g.appendChild(ball);\n        const pentagon = document.createElementNS(ns, \"polygon\");\n        const pts = [];\n        for (let i = 0; i < 5; i++) {\n         const a = (Math.PI * 2 / 5) * i - Math.PI / 2;\n         pts.push([Math.cos(a) * size * 0.4, Math.sin(a) * size * 0.4]);\n        }\n        pentagon.setAttribute(\"points\", pts.map(pt => pt.join(\",\")).join(\" \"));\n        pentagon.style.fill = \"currentColor\";\n        pentagon.style.opacity = \"0.5\";\n        g.appendChild(pentagon);\n        return g;\n       }\n       if (shape === \"hockey-puck\") {\n        const g = document.createElementNS(ns, \"g\");\n        const side = document.createElementNS(ns, \"ellipse\");\n        side.setAttribute(\"rx\", size);\n        side.setAttribute(\"ry\", size * 0.3);\n        side.setAttribute(\"cy\", size * 0.15);\n        side.style.opacity = \"0.7\";\n        g.appendChild(side);\n        const top = document.createElementNS(ns, \"ellipse\");\n        top.setAttribute(\"rx\", size);\n        top.setAttribute(\"ry\", size * 0.3);\n        top.setAttribute(\"cy\", -size * 0.15);\n        g.appendChild(top);\n        return g;\n       }\n       if (shape === \"baseball-ball\") {\n        const g = document.createElementNS(ns, \"g\");\n        const ball = document.createElementNS(ns, \"circle\");\n        ball.setAttribute(\"r\", size);\n        g.appendChild(ball);\n        const stitch1 = document.createElementNS(ns, \"path\");\n        stitch1.setAttribute(\"d\", `M ${-size * 0.7} ${-size * 0.5} Q ${-size * 0.3} ${-size * 0.8} 0 ${-size * 0.9} Q ${size * 0.3} ${-size * 0.8} ${size * 0.7} ${-size * 0.5}`);\n        stitch1.style.fill = \"none\";\n        stitch1.style.stroke = \"currentColor\";\n        stitch1.style.strokeWidth = \"2\";\n        stitch1.style.opacity = \"0.5\";\n        g.appendChild(stitch1);\n        const stitch2 = document.createElementNS(ns, \"path\");\n        stitch2.setAttribute(\"d\", `M ${-size * 0.7} ${size * 0.5} Q ${-size * 0.3} ${size * 0.8} 0 ${size * 0.9} Q ${size * 0.3} ${size * 0.8} ${size * 0.7} ${size * 0.5}`);\n        stitch2.style.fill = \"none\";\n        stitch2.style.stroke = \"currentColor\";\n        stitch2.style.strokeWidth = \"2\";\n        stitch2.style.opacity = \"0.5\";\n        g.appendChild(stitch2);\n        return g;\n       }\n       if (shape === \"tennis-ball\") {\n        const g = document.createElementNS(ns, \"g\");\n        const ball = document.createElementNS(ns, \"circle\");\n        ball.setAttribute(\"r\", size);\n        g.appendChild(ball);\n        const curve1 = document.createElementNS(ns, \"path\");\n        curve1.setAttribute(\"d\", `M ${-size * 0.2} ${-size} Q ${-size * 0.8} 0 ${-size * 0.2} ${size}`);\n        curve1.style.fill = \"none\";\n        curve1.style.stroke = \"currentColor\";\n        curve1.style.strokeWidth = \"3\";\n        curve1.style.opacity = \"0.5\";\n        g.appendChild(curve1);\n        const curve2 = document.createElementNS(ns, \"path\");\n        curve2.setAttribute(\"d\", `M ${size * 0.2} ${-size} Q ${size * 0.8} 0 ${size * 0.2} ${size}`);\n        curve2.style.fill = \"none\";\n        curve2.style.stroke = \"currentColor\";\n        curve2.style.strokeWidth = \"3\";\n        curve2.style.opacity = \"0.5\";\n        g.appendChild(curve2);\n        return g;\n       }\n       if (shape === \"volleyball\") {\n        const g = document.createElementNS(ns, \"g\");\n        const ball = document.createElementNS(ns, \"circle\");\n        ball.setAttribute(\"r\", size);\n        g.appendChild(ball);\n        for (let i = 0; i < 3; i++) {\n         const curve = document.createElementNS(ns, \"path\");\n         const angle = (Math.PI * 2 / 3) * i;\n         const x1 = Math.cos(angle) * size;\n         const y1 = Math.sin(angle) * size;\n         const x2 = Math.cos(angle + Math.PI) * size;\n         const y2 = Math.sin(angle + Math.PI) * size;\n         curve.setAttribute(\"d\", `M ${x1} ${y1} Q 0 0 ${x2} ${y2}`);\n         curve.style.fill = \"none\";\n         curve.style.stroke = \"currentColor\";\n         curve.style.strokeWidth = \"2\";\n         curve.style.opacity = \"0.5\";\n         g.appendChild(curve);\n        }\n        return g;\n       }\n       if (shape === \"rugby-ball\") {\n        const g = document.createElementNS(ns, \"g\");\n        const ball = document.createElementNS(ns, \"ellipse\");\n        ball.setAttribute(\"rx\", size * 1.4);\n        ball.setAttribute(\"ry\", size * 0.6);\n        g.appendChild(ball);\n        const stripe = document.createElementNS(ns, \"line\");\n        stripe.setAttribute(\"x1\", 0);\n        stripe.setAttribute(\"y1\", -size * 0.6);\n        stripe.setAttribute(\"x2\", 0);\n        stripe.setAttribute(\"y2\", size * 0.6);\n        stripe.style.stroke = \"currentColor\";\n        stripe.style.strokeWidth = \"3\";\n        stripe.style.opacity = \"0.5\";\n        g.appendChild(stripe);\n        return g;\n       }\n       if (shape === \"golf-ball\") {\n        const g = document.createElementNS(ns, \"g\");\n        const ball = document.createElementNS(ns, \"circle\");\n        ball.setAttribute(\"r\", size);\n        g.appendChild(ball);\n        for (let row = -2; row <= 2; row++) {\n         for (let col = -2; col <= 2; col++) {\n          if (Math.abs(row) + Math.abs(col) <= 2) {\n           const dimple = document.createElementNS(ns, \"circle\");\n           dimple.setAttribute(\"cx\", col * size * 0.3);\n           dimple.setAttribute(\"cy\", row * size * 0.3);\n           dimple.setAttribute(\"r\", size * 0.08);\n           dimple.style.fill = \"currentColor\";\n           dimple.style.opacity = \"0.3\";\n           g.appendChild(dimple);\n          }\n         }\n        }\n        return g;\n       }\n       if (shape === \"frisbee\") {\n        const g = document.createElementNS(ns, \"g\");\n        const outer = document.createElementNS(ns, \"ellipse\");\n        outer.setAttribute(\"rx\", size * 1.2);\n        outer.setAttribute(\"ry\", size * 0.4);\n        g.appendChild(outer);\n        const inner = document.createElementNS(ns, \"ellipse\");\n        inner.setAttribute(\"rx\", size * 0.7);\n        inner.setAttribute(\"ry\", size * 0.25);\n        inner.style.fill = \"currentColor\";\n        inner.style.opacity = \"0.5\";\n        g.appendChild(inner);\n        return g;\n       }\n       if (shape === \"cricket-ball\") {\n        const g = document.createElementNS(ns, \"g\");\n        const ball = document.createElementNS(ns, \"circle\");\n        ball.setAttribute(\"r\", size);\n        g.appendChild(ball);\n        const seam = document.createElementNS(ns, \"ellipse\");\n        seam.setAttribute(\"rx\", size * 0.9);\n        seam.setAttribute(\"ry\", size * 0.15);\n        seam.style.fill = \"none\";\n        seam.style.stroke = \"currentColor\";\n        seam.style.strokeWidth = \"3\";\n        seam.style.opacity = \"0.5\";\n        g.appendChild(seam);\n        return g;\n       }\n       if (shape === \"lacrosse-stick\") {\n        const g = document.createElementNS(ns, \"g\");\n        const handle = document.createElementNS(ns, \"rect\");\n        handle.setAttribute(\"x\", -size * 0.1);\n        handle.setAttribute(\"y\", -size * 0.2);\n        handle.setAttribute(\"width\", size * 0.2);\n        handle.setAttribute(\"height\", size * 1.5);\n        handle.setAttribute(\"rx\", 2);\n        g.appendChild(handle);\n        const head = document.createElementNS(ns, \"ellipse\");\n        head.setAttribute(\"cy\", -size * 0.6);\n        head.setAttribute(\"rx\", size * 0.5);\n        head.setAttribute(\"ry\", size * 0.7);\n        head.style.fill = \"none\";\n        head.style.stroke = \"currentColor\";\n        head.style.strokeWidth = size * 0.15;\n        head.style.opacity = \"0.7\";\n        g.appendChild(head);\n        const net = document.createElementNS(ns, \"ellipse\");\n        net.setAttribute(\"cy\", -size * 0.5);\n        net.setAttribute(\"rx\", size * 0.35);\n        net.setAttribute(\"ry\", size * 0.5);\n        net.style.fill = \"currentColor\";\n        net.style.opacity = \"0.3\";\n        g.appendChild(net);\n        return g;\n       }\n       if (shape === \"golf-flag\") {\n        const g = document.createElementNS(ns, \"g\");\n        const hole = document.createElementNS(ns, \"ellipse\");\n        hole.setAttribute(\"cy\", size * 0.7);\n        hole.setAttribute(\"rx\", size * 0.5);\n        hole.setAttribute(\"ry\", size * 0.15);\n        hole.style.opacity = \"0.5\";\n        g.appendChild(hole);\n        const pole = document.createElementNS(ns, \"line\");\n        pole.setAttribute(\"x1\", 0);\n        pole.setAttribute(\"y1\", size * 0.7);\n        pole.setAttribute(\"x2\", 0);\n        pole.setAttribute(\"y2\", -size);\n        pole.style.stroke = \"currentColor\";\n        pole.style.strokeWidth = \"3\";\n        pole.style.opacity = \"0.6\";\n        g.appendChild(pole);\n        const flag = document.createElementNS(ns, \"polygon\");\n        flag.setAttribute(\"points\", `0,${-size} ${size * 0.8},${-size * 0.7} 0,${-size * 0.4}`);\n        g.appendChild(flag);\n        return g;\n       }\n       if (shape === \"tactical-x\") {\n        const g = document.createElementNS(ns, \"g\");\n        const line1 = document.createElementNS(ns, \"line\");\n        line1.setAttribute(\"x1\", -size * 0.7);\n        line1.setAttribute(\"y1\", -size * 0.7);\n        line1.setAttribute(\"x2\", size * 0.7);\n        line1.setAttribute(\"y2\", size * 0.7);\n        line1.style.stroke = \"currentColor\";\n        line1.style.strokeWidth = size * 0.3;\n        line1.style.strokeLinecap = \"round\";\n        g.appendChild(line1);\n        const line2 = document.createElementNS(ns, \"line\");\n        line2.setAttribute(\"x1\", size * 0.7);\n        line2.setAttribute(\"y1\", -size * 0.7);\n        line2.setAttribute(\"x2\", -size * 0.7);\n        line2.setAttribute(\"y2\", size * 0.7);\n        line2.style.stroke = \"currentColor\";\n        line2.style.strokeWidth = size * 0.3;\n        line2.style.strokeLinecap = \"round\";\n        g.appendChild(line2);\n        return g;\n       }\n       if (shape === \"tactical-o\") {\n        const g = document.createElementNS(ns, \"g\");\n        const circle = document.createElementNS(ns, \"circle\");\n        circle.setAttribute(\"r\", size * 0.7);\n        circle.style.fill = \"none\";\n        circle.style.stroke = \"currentColor\";\n        circle.style.strokeWidth = size * 0.3;\n        g.appendChild(circle);\n        return g;\n       }\n       if (shape === \"tactical-star\") {\n        const g = document.createElementNS(ns, \"g\");\n        const outer = size * 0.9;\n        const inner = size * 0.4;\n        let pts = \"\";\n        for (let i = 0; i < 10; i++) {\n         const a = (Math.PI / 5) * i - Math.PI / 2;\n         const r = i % 2 === 0 ? outer : inner;\n         pts += `${Math.cos(a) * r},${Math.sin(a) * r} `;\n        }\n        const star = document.createElementNS(ns, \"polygon\");\n        star.setAttribute(\"points\", pts.trim());\n        g.appendChild(star);\n        return g;\n       }\n       if (shape === \"patch-panel\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 1.4);\n        body.setAttribute(\"y\", -size * 0.35);\n        body.setAttribute(\"width\", size * 2.8);\n        body.setAttribute(\"height\", size * 0.7);\n        body.setAttribute(\"rx\", 2);\n        g.appendChild(body);\n        for (let i = 0; i < 12; i++) {\n         const port = document.createElementNS(ns, \"rect\");\n         port.setAttribute(\"x\", -size * 1.25 + i * size * 0.22);\n         port.setAttribute(\"y\", -size * 0.15);\n         port.setAttribute(\"width\", size * 0.18);\n         port.setAttribute(\"height\", size * 0.3);\n         port.setAttribute(\"rx\", 1);\n         port.style.fill = \"currentColor\";\n         port.style.opacity = \"0.4\";\n         g.appendChild(port);\n        }\n        return g;\n       }\n       if (shape === \"ups\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size);\n        body.setAttribute(\"y\", -size);\n        body.setAttribute(\"width\", size * 2);\n        body.setAttribute(\"height\", size * 2);\n        body.setAttribute(\"rx\", 4);\n        g.appendChild(body);\n        const screen = document.createElementNS(ns, \"rect\");\n        screen.setAttribute(\"x\", -size * 0.6);\n        screen.setAttribute(\"y\", -size * 0.7);\n        screen.setAttribute(\"width\", size * 1.2);\n        screen.setAttribute(\"height\", size * 0.5);\n        screen.setAttribute(\"rx\", 2);\n        screen.style.fill = \"currentColor\";\n        screen.style.opacity = \"0.4\";\n        g.appendChild(screen);\n        const battery = document.createElementNS(ns, \"rect\");\n        battery.setAttribute(\"x\", -size * 0.4);\n        battery.setAttribute(\"y\", -size * 0.55);\n        battery.setAttribute(\"width\", size * 0.8);\n        battery.setAttribute(\"height\", size * 0.2);\n        battery.style.fill = \"#4ade80\";\n        g.appendChild(battery);\n        for (let i = 0; i < 3; i++) {\n         const led = document.createElementNS(ns, \"circle\");\n         led.setAttribute(\"cx\", -size * 0.4 + i * size * 0.4);\n         led.setAttribute(\"cy\", size * 0.2);\n         led.setAttribute(\"r\", size * 0.08);\n         led.style.fill = i < 2 ? \"#4ade80\" : \"#facc15\";\n         g.appendChild(led);\n        }\n        const outlet = document.createElementNS(ns, \"rect\");\n        outlet.setAttribute(\"x\", -size * 0.7);\n        outlet.setAttribute(\"y\", size * 0.5);\n        outlet.setAttribute(\"width\", size * 1.4);\n        outlet.setAttribute(\"height\", size * 0.3);\n        outlet.setAttribute(\"rx\", 2);\n        outlet.style.fill = \"currentColor\";\n        outlet.style.opacity = \"0.4\";\n        g.appendChild(outlet);\n        return g;\n       }\n       if (shape === \"pdu\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 0.4);\n        body.setAttribute(\"y\", -size * 1.2);\n        body.setAttribute(\"width\", size * 0.8);\n        body.setAttribute(\"height\", size * 2.4);\n        body.setAttribute(\"rx\", 3);\n        g.appendChild(body);\n        for (let i = 0; i < 6; i++) {\n         const outlet = document.createElementNS(ns, \"rect\");\n         outlet.setAttribute(\"x\", -size * 0.25);\n         outlet.setAttribute(\"y\", -size + i * size * 0.35);\n         outlet.setAttribute(\"width\", size * 0.5);\n         outlet.setAttribute(\"height\", size * 0.25);\n         outlet.setAttribute(\"rx\", 2);\n         outlet.style.fill = \"currentColor\";\n         outlet.style.opacity = \"0.4\";\n         g.appendChild(outlet);\n        }\n        const led = document.createElementNS(ns, \"circle\");\n        led.setAttribute(\"cy\", -size * 1.05);\n        led.setAttribute(\"r\", size * 0.08);\n        led.style.fill = \"#4ade80\";\n        g.appendChild(led);\n        return g;\n       }\n       if (shape === \"rack-shelf\") {\n        const g = document.createElementNS(ns, \"g\");\n        const shelf = document.createElementNS(ns, \"rect\");\n        shelf.setAttribute(\"x\", -size * 1.3);\n        shelf.setAttribute(\"y\", -size * 0.15);\n        shelf.setAttribute(\"width\", size * 2.6);\n        shelf.setAttribute(\"height\", size * 0.3);\n        shelf.setAttribute(\"rx\", 2);\n        g.appendChild(shelf);\n        const lip = document.createElementNS(ns, \"rect\");\n        lip.setAttribute(\"x\", -size * 1.3);\n        lip.setAttribute(\"y\", size * 0.15);\n        lip.setAttribute(\"width\", size * 2.6);\n        lip.setAttribute(\"height\", size * 0.15);\n        lip.style.opacity = \"0.7\";\n        g.appendChild(lip);\n        const ear1 = document.createElementNS(ns, \"rect\");\n        ear1.setAttribute(\"x\", -size * 1.4);\n        ear1.setAttribute(\"y\", -size * 0.2);\n        ear1.setAttribute(\"width\", size * 0.1);\n        ear1.setAttribute(\"height\", size * 0.4);\n        g.appendChild(ear1);\n        const ear2 = document.createElementNS(ns, \"rect\");\n        ear2.setAttribute(\"x\", size * 1.3);\n        ear2.setAttribute(\"y\", -size * 0.2);\n        ear2.setAttribute(\"width\", size * 0.1);\n        ear2.setAttribute(\"height\", size * 0.4);\n        g.appendChild(ear2);\n        return g;\n       }\n       if (shape === \"blank-panel\") {\n        const g = document.createElementNS(ns, \"g\");\n        const panel = document.createElementNS(ns, \"rect\");\n        panel.setAttribute(\"x\", -size * 1.4);\n        panel.setAttribute(\"y\", -size * 0.25);\n        panel.setAttribute(\"width\", size * 2.8);\n        panel.setAttribute(\"height\", size * 0.5);\n        panel.setAttribute(\"rx\", 2);\n        g.appendChild(panel);\n        const vent1 = document.createElementNS(ns, \"rect\");\n        vent1.setAttribute(\"x\", -size * 0.8);\n        vent1.setAttribute(\"y\", -size * 0.1);\n        vent1.setAttribute(\"width\", size * 0.5);\n        vent1.setAttribute(\"height\", size * 0.04);\n        vent1.style.fill = \"currentColor\";\n        vent1.style.opacity = \"0.4\";\n        g.appendChild(vent1);\n        const vent2 = document.createElementNS(ns, \"rect\");\n        vent2.setAttribute(\"x\", -size * 0.8);\n        vent2.setAttribute(\"y\", size * 0.02);\n        vent2.setAttribute(\"width\", size * 0.5);\n        vent2.setAttribute(\"height\", size * 0.04);\n        vent2.style.fill = \"currentColor\";\n        vent2.style.opacity = \"0.4\";\n        g.appendChild(vent2);\n        const vent3 = document.createElementNS(ns, \"rect\");\n        vent3.setAttribute(\"x\", size * 0.3);\n        vent3.setAttribute(\"y\", -size * 0.1);\n        vent3.setAttribute(\"width\", size * 0.5);\n        vent3.setAttribute(\"height\", size * 0.04);\n        vent3.style.fill = \"currentColor\";\n        vent3.style.opacity = \"0.4\";\n        g.appendChild(vent3);\n        const vent4 = document.createElementNS(ns, \"rect\");\n        vent4.setAttribute(\"x\", size * 0.3);\n        vent4.setAttribute(\"y\", size * 0.02);\n        vent4.setAttribute(\"width\", size * 0.5);\n        vent4.setAttribute(\"height\", size * 0.04);\n        vent4.style.fill = \"currentColor\";\n        vent4.style.opacity = \"0.4\";\n        g.appendChild(vent4);\n        return g;\n       }\n       if (shape === \"cable-management\") {\n        const g = document.createElementNS(ns, \"g\");\n        const panel = document.createElementNS(ns, \"rect\");\n        panel.setAttribute(\"x\", -size * 1.4);\n        panel.setAttribute(\"y\", -size * 0.3);\n        panel.setAttribute(\"width\", size * 2.8);\n        panel.setAttribute(\"height\", size * 0.6);\n        panel.setAttribute(\"rx\", 2);\n        g.appendChild(panel);\n        for (let i = 0; i < 5; i++) {\n         const ring = document.createElementNS(ns, \"ellipse\");\n         ring.setAttribute(\"cx\", -size + i * size * 0.5);\n         ring.setAttribute(\"rx\", size * 0.12);\n         ring.setAttribute(\"ry\", size * 0.18);\n         ring.style.fill = \"none\";\n         ring.style.stroke = \"currentColor\";\n         ring.style.strokeWidth = size * 0.08;\n         ring.style.opacity = \"0.5\";\n         g.appendChild(ring);\n        }\n        return g;\n       }\n       if (shape === \"kvm\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 1.2);\n        body.setAttribute(\"y\", -size * 0.4);\n        body.setAttribute(\"width\", size * 2.4);\n        body.setAttribute(\"height\", size * 0.8);\n        body.setAttribute(\"rx\", 3);\n        g.appendChild(body);\n        const screen = document.createElementNS(ns, \"rect\");\n        screen.setAttribute(\"x\", -size * 0.9);\n        screen.setAttribute(\"y\", -size * 0.25);\n        screen.setAttribute(\"width\", size * 0.8);\n        screen.setAttribute(\"height\", size * 0.5);\n        screen.setAttribute(\"rx\", 2);\n        screen.style.fill = \"currentColor\";\n        screen.style.opacity = \"0.4\";\n        g.appendChild(screen);\n        for (let i = 0; i < 4; i++) {\n         const btn = document.createElementNS(ns, \"rect\");\n         btn.setAttribute(\"x\", size * 0.1 + i * size * 0.25);\n         btn.setAttribute(\"y\", -size * 0.15);\n         btn.setAttribute(\"width\", size * 0.18);\n         btn.setAttribute(\"height\", size * 0.3);\n         btn.setAttribute(\"rx\", 2);\n         btn.style.fill = \"currentColor\";\n         btn.style.opacity = \"0.5\";\n         g.appendChild(btn);\n         const led = document.createElementNS(ns, \"circle\");\n         led.setAttribute(\"cx\", size * 0.19 + i * size * 0.25);\n         led.setAttribute(\"cy\", -size * 0.25);\n         led.setAttribute(\"r\", size * 0.04);\n         led.style.fill = i === 0 ? \"#4ade80\" : \"currentColor\";\n         led.style.opacity = i === 0 ? \"1\" : \"0.3\";\n         g.appendChild(led);\n        }\n        return g;\n       }\n       const c = document.createElementNS(ns, \"circle\");\n       c.setAttribute(\"r\", size);\n       return c;\n      }\n      function createNodeShape(id, size) {\n       const shapeType = (NODE_DATA[id] && NODE_DATA[id].shape) || \"circle\";\n       const shapeEl = createShapeElement(shapeType, size);\n       shapeEl.classList.add(\"node-circle\");\n       const styles = resolveStylesForNode(id);\n       shapeEl.style.fill = styles.circleColor || PAGE_STATE.nodeFill || \"#1e293b\";\n       shapeEl.style.stroke = styles.circleBorder || PAGE_STATE.nodeStroke || \"#475569\";\n       return shapeEl;\n      }\n      function forgeTheLegend() {\n       const container = document.getElementById(\"edge-legend\");\n       if (!container) return;\n       container.innerHTML = \"\";\n       const title = document.createElement(\"div\");\n       title.className = \"legend-title\";\n       title.textContent = t(\"legends.lineLegend\");\n       container.appendChild(title);\n       const closeBtn = document.createElement(\"button\");\n       closeBtn.type = \"button\";\n       closeBtn.className = \"legend-close-btn\";\n       closeBtn.textContent = \"✕\";\n       closeBtn.addEventListener(\"click\", (e) => {\n        e.stopPropagation();\n        legendCollapsed = true;\n        updateLegendVisibility();\n       });\n       container.appendChild(closeBtn);\n       const colors = [...new Set(EDGE_DATA.list.map((e) => e.color).filter(Boolean))];\n       if (colors.length === 0) {\n        updateLegendVisibility();\n        return;\n       }\n       colors.forEach((color) => {\n        if (!EDGE_LEGEND[color]) {\n         EDGE_LEGEND[color] = t(\"legends.defaultLineLabel\");\n        }\n        const item = document.createElement(\"div\");\n        item.className = \"legend-item\";\n        item.addEventListener(\"mousedown\", (e) => e.stopPropagation());\n        item.addEventListener(\"click\", (e) => e.stopPropagation());\n        const swatch = document.createElement(\"span\");\n        swatch.className = \"legend-swatch\";\n        swatch.style.backgroundColor = color;\n        swatch.style.cursor = \"pointer\";\n        swatch.addEventListener(\"click\", (e) => {\n         e.stopPropagation();\n         const edgeWithColor = EDGE_DATA.list.find(edge => edge.color === color);\n         if (edgeWithColor) {\n          selectTheConnection(edgeWithColor.id);\n         }\n        });\n        let swatchTouchStart = null;\n        let swatchTouchMoved = false;\n        swatch.addEventListener(\"touchstart\", (e) => {\n         swatchTouchStart = Date.now();\n         swatchTouchMoved = false;\n         if (e.touches[0]) {\n          swatchTouchStartX = e.touches[0].clientX;\n          swatchTouchStartY = e.touches[0].clientY;\n         }\n        }, {\n         passive: false\n        });\n        let swatchTouchStartX = 0, swatchTouchStartY = 0;\n        swatch.addEventListener(\"touchmove\", (e) => {\n         if (e.touches[0]) {\n          const dx = Math.abs(e.touches[0].clientX - swatchTouchStartX);\n          const dy = Math.abs(e.touches[0].clientY - swatchTouchStartY);\n          if (dx > 10 || dy > 10) swatchTouchMoved = true;\n         }\n        }, {\n         passive: false\n        });\n        swatch.addEventListener(\"touchend\", (e) => {\n         if (swatchTouchStart && !swatchTouchMoved && Date.now() - swatchTouchStart < 400) {\n          e.stopPropagation();\n          e.preventDefault();\n          const edgeWithColor = EDGE_DATA.list.find(edge => edge.color === color);\n          if (edgeWithColor) {\n           selectTheConnection(edgeWithColor.id);\n          }\n         }\n         swatchTouchStart = null;\n         swatchTouchMoved = false;\n        }, {\n         passive: false\n        });\n        const label = document.createElement(\"span\");\n        label.className = \"legend-label\";\n        label.textContent = EDGE_LEGEND[color];\n        if (isMobileDevice()) {\n         label.style.cursor = \"pointer\";\n         let labelTapStart = null;\n         let labelTapMoved = false;\n         label.addEventListener(\"touchstart\", (e) => {\n          labelTapStart = Date.now();\n          labelTapMoved = false;\n          if (e.touches[0]) {\n           labelTapStartX = e.touches[0].clientX;\n           labelTapStartY = e.touches[0].clientY;\n          }\n          e.stopPropagation();\n         });\n         let labelTapStartX = 0, labelTapStartY = 0;\n         label.addEventListener(\"touchmove\", (e) => {\n          if (e.touches[0]) {\n           const dx = Math.abs(e.touches[0].clientX - labelTapStartX);\n           const dy = Math.abs(e.touches[0].clientY - labelTapStartY);\n           if (dx > 10 || dy > 10) labelTapMoved = true;\n          }\n         });\n         label.addEventListener(\"touchend\", async (e) => {\n          if (labelTapStart && !labelTapMoved && Date.now() - labelTapStart < 400) {\n           e.preventDefault();\n           e.stopPropagation();\n           const currentText = label.textContent;\n           const newText = await showPrompt(t(\"dialogs.editLegendLabel\"), currentText);\n           if (newText !== null && newText.trim()) {\n            label.textContent = newText.trim();\n            EDGE_LEGEND[color] = newText.trim();\n           }\n          }\n          labelTapStart = null;\n          labelTapMoved = false;\n         });\n        } else {\n         label.contentEditable = true;\n         label.addEventListener(\"focus\", () => {\n          label.classList.add(\"editing\");\n         });\n         label.addEventListener(\"blur\", () => {\n          label.classList.remove(\"editing\");\n          const text = label.textContent.trim() || t(\"legends.defaultLineLabel\");\n          EDGE_LEGEND[color] = text;\n         });\n         label.addEventListener(\"keydown\", (e) => {\n          if (e.key === \"Enter\") {\n           e.preventDefault();\n           label.blur();\n          }\n         });\n        }\n        item.append(swatch, label);\n        container.appendChild(item);\n       });\n       updateLegendVisibility();\n      }\n       function deleteRectangle(rectId) {\n      pushUndo(\"delete zone\");\n        RECT_DATA.list = RECT_DATA.list.filter(r => r.id !== rectId);\n        forgeTheTopology();\n       }\n       function updateRectangleDeleteButtons() {\n        const deleteButtons = document.querySelectorAll('.rect-delete-btn');\n        deleteButtons.forEach(btn => {\n         btn.style.display = rectDrawMode ? 'block' : 'none';\n        });\n       }\nfunction updateFovCone(nodeId) {\n  const node = NODE_DATA[nodeId];\n  if (!node) return;\n  \n  const nodeGroup = document.querySelector(`g[data-node-id=\"${nodeId}\"]`);\n  if (!nodeGroup) return;\n  const existingFov = nodeGroup.querySelector(\".fov-group\");\n  if (existingFov) existingFov.remove();\n  if (!hasCoverageZone(node.shape) || !node.fovEnabled) return;\n  \n  const ns = \"http://www.w3.org/2000/svg\";\n  const defaults = getCoverageDefaults(node.shape);\n  const fovAngle = node.fovAngle || defaults.angle;\n  const fovDistance = node.fovDistance || defaults.distance;\n  const fovInnerRadius = node.fovInnerRadius || 0;\n  const fovRotation = node.fovRotation || 0;\n  const fovColor = node.fovColor || \"#f59e0b\";\n  const fovOpacity = node.fovOpacity || 20;\n  const fovGradient = node.fovGradient || false;\n  const fovBorderColor = node.fovBorderColor || \"#f59e0b\";\n  const fovBorderWidth = node.fovBorderWidth ?? 2;\n  const fovBorderStyle = node.fovBorderStyle || \"solid\";\n  const fovBorderOpacity = node.fovBorderOpacity ?? 100;\n  const fovLabel = node.fovLabel || \"\";\n  const fovAnimate = node.fovAnimate || false;\n  const fovAnimationType = node.fovAnimationType || defaults.animationType;\n  const fovSweep = node.fovSweep || 120;\n  const fovSpeed = node.fovSpeed || 4;\n  \n  const fovGroup = document.createElementNS(ns, \"g\");\n  fovGroup.classList.add(\"fov-group\");\n  \n  if (fovGradient) {\n    const gradientId = `fov-gradient-${nodeId}`;\n    const defs = document.createElementNS(ns, \"defs\");\n    const gradient = document.createElementNS(ns, \"radialGradient\");\n    gradient.id = gradientId;\n    gradient.setAttribute(\"cx\", \"0\");\n    gradient.setAttribute(\"cy\", \"0\");\n    gradient.setAttribute(\"r\", fovDistance);\n    gradient.setAttribute(\"gradientUnits\", \"userSpaceOnUse\");\n    \n    const stop1 = document.createElementNS(ns, \"stop\");\n    stop1.setAttribute(\"offset\", fovInnerRadius / fovDistance);\n    stop1.setAttribute(\"stop-color\", fovColor);\n    stop1.setAttribute(\"stop-opacity\", fovOpacity / 100);\n    \n    const stop2 = document.createElementNS(ns, \"stop\");\n    stop2.setAttribute(\"offset\", \"1\");\n    stop2.setAttribute(\"stop-color\", fovColor);\n    stop2.setAttribute(\"stop-opacity\", \"0\");\n    \n    gradient.appendChild(stop1);\n    gradient.appendChild(stop2);\n    defs.appendChild(gradient);\n    fovGroup.appendChild(defs);\n  }\n  \n  const fovPath = document.createElementNS(ns, \"path\");\n  \n  if (fovAngle >= 360) {\n    if (fovInnerRadius > 0) {\n      fovPath.setAttribute(\"d\", `M ${fovDistance},0 A ${fovDistance},${fovDistance} 0 1,1 ${-fovDistance},0 A ${fovDistance},${fovDistance} 0 1,1 ${fovDistance},0 M ${fovInnerRadius},0 A ${fovInnerRadius},${fovInnerRadius} 0 1,0 ${-fovInnerRadius},0 A ${fovInnerRadius},${fovInnerRadius} 0 1,0 ${fovInnerRadius},0`);\n      fovPath.setAttribute(\"fill-rule\", \"evenodd\");\n    } else {\n      fovPath.setAttribute(\"d\", `M 0,0 m -${fovDistance},0 a ${fovDistance},${fovDistance} 0 1,0 ${fovDistance * 2},0 a ${fovDistance},${fovDistance} 0 1,0 -${fovDistance * 2},0`);\n    }\n  } else {\n    const angleRad = (fovAngle * Math.PI) / 180;\n    const rotationRad = ((fovRotation - 90) * Math.PI) / 180;\n    \n    const startAngle = rotationRad - angleRad / 2;\n    const endAngle = rotationRad + angleRad / 2;\n    \n    const x1 = Math.cos(startAngle) * fovDistance;\n    const y1 = Math.sin(startAngle) * fovDistance;\n    const x2 = Math.cos(endAngle) * fovDistance;\n    const y2 = Math.sin(endAngle) * fovDistance;\n    \n    const largeArc = fovAngle > 180 ? 1 : 0;\n    \n    if (fovInnerRadius > 0) {\n      const ix1 = Math.cos(startAngle) * fovInnerRadius;\n      const iy1 = Math.sin(startAngle) * fovInnerRadius;\n      const ix2 = Math.cos(endAngle) * fovInnerRadius;\n      const iy2 = Math.sin(endAngle) * fovInnerRadius;\n      fovPath.setAttribute(\"d\", `M ${ix1},${iy1} L ${x1},${y1} A ${fovDistance},${fovDistance} 0 ${largeArc},1 ${x2},${y2} L ${ix2},${iy2} A ${fovInnerRadius},${fovInnerRadius} 0 ${largeArc},0 ${ix1},${iy1} Z`);\n    } else {\n      fovPath.setAttribute(\"d\", `M 0,0 L ${x1},${y1} A ${fovDistance},${fovDistance} 0 ${largeArc},1 ${x2},${y2} Z`);\n    }\n  }\n  \n  if (fovGradient) {\n    fovPath.style.fill = `url(#fov-gradient-${nodeId})`;\n  } else {\n    const opacityHex = Math.round((fovOpacity / 100) * 255).toString(16).padStart(2, '0');\n    fovPath.style.fill = fovColor + opacityHex;\n  }\n  \n  const borderOpacityHex = Math.round((fovBorderOpacity / 100) * 255).toString(16).padStart(2, '0');\n  fovPath.style.stroke = fovBorderColor + borderOpacityHex;\n  fovPath.style.strokeWidth = fovBorderWidth;\n  if (fovBorderStyle === \"dashed\") {\n    fovPath.style.strokeDasharray = \"10,5\";\n  } else if (fovBorderStyle === \"dotted\") {\n    fovPath.style.strokeDasharray = \"3,3\";\n  }\n  fovPath.style.pointerEvents = \"none\";\n  fovPath.classList.add(\"fov-cone\");\n  \n  fovGroup.appendChild(fovPath);\n  \n  if (fovLabel) {\n    const fovLabelPosition = node.fovLabelPosition || \"center\";\n    const fovLabelSize = node.fovLabelSize || 14;\n    const fovLabelColor = node.fovLabelColor || \"#ffffff\";\n    const fovLabelBold = node.fovLabelBold || false;\n    const fovLabelBg = node.fovLabelBg || false;\n    const fovLabelBgColor = node.fovLabelBgColor || \"#000000\";\n    \n    let labelDistance;\n    if (fovLabelPosition === \"center\") {\n      labelDistance = fovAngle >= 360 ? 0 : fovDistance * 0.4;\n    } else if (fovLabelPosition === \"edge\") {\n      labelDistance = fovDistance * 0.75;\n    } else {\n      labelDistance = fovDistance + fovLabelSize + 8;\n    }\n    \n    const labelAngle = ((fovRotation - 90) * Math.PI) / 180;\n    const fovLabelOffsetX = node.fovLabelOffsetX || 0;\n    const fovLabelOffsetY = node.fovLabelOffsetY || 0;\n    const labelX = (fovAngle >= 360 && fovLabelPosition === \"center\" ? 0 : Math.cos(labelAngle) * labelDistance) + fovLabelOffsetX;\n    const labelY = (fovAngle >= 360 && fovLabelPosition === \"center\" ? 0 : Math.sin(labelAngle) * labelDistance) + fovLabelOffsetY;\n    \n    if (fovLabelBg) {\n      const bgRect = document.createElementNS(ns, \"rect\");\n      const textWidth = fovLabel.length * fovLabelSize * 0.6;\n      const textHeight = fovLabelSize * 1.4;\n      bgRect.setAttribute(\"x\", labelX - textWidth / 2 - 6);\n      bgRect.setAttribute(\"y\", labelY - textHeight / 2);\n      bgRect.setAttribute(\"width\", textWidth + 12);\n      bgRect.setAttribute(\"height\", textHeight);\n      bgRect.setAttribute(\"rx\", \"4\");\n      bgRect.style.fill = fovLabelBgColor;\n      bgRect.style.opacity = \"0.8\";\n      bgRect.style.pointerEvents = \"none\";\n      fovGroup.appendChild(bgRect);\n    }\n    \n    const labelEl = document.createElementNS(ns, \"text\");\n    labelEl.setAttribute(\"x\", labelX);\n    labelEl.setAttribute(\"y\", labelY);\n    labelEl.setAttribute(\"text-anchor\", \"middle\");\n    labelEl.setAttribute(\"dominant-baseline\", \"middle\");\n    labelEl.style.fill = fovLabelColor;\n    labelEl.style.fontSize = fovLabelSize + \"px\";\n    labelEl.style.fontWeight = fovLabelBold ? \"bold\" : \"normal\";\n    labelEl.style.fontFamily = \"system-ui, sans-serif\";\n    labelEl.style.pointerEvents = \"none\";\n    labelEl.textContent = fovLabel;\n    fovGroup.appendChild(labelEl);\n  }\n  \n  if (fovAnimate) {\n    const animationName = `fov-anim-${nodeId}`;\n    const styleEl = document.createElementNS(\"http://www.w3.org/1999/xhtml\", \"style\");\n    \n    if (fovAnimationType === \"sweep\" && fovAngle < 360) {\n      styleEl.textContent = `\n        @keyframes ${animationName} {\n          0%, 100% { transform: rotate(0deg); }\n          50% { transform: rotate(${fovSweep}deg); }\n        }\n      `;\n      fovGroup.style.animation = `${animationName} ${fovSpeed}s ease-in-out infinite`;\n      fovGroup.style.transformOrigin = \"0 0\";\n    } else if (fovAnimationType === \"pulse\") {\n      styleEl.textContent = `\n        @keyframes ${animationName} {\n          0%, 100% { transform: scale(1); opacity: 1; }\n          50% { transform: scale(1.1); opacity: 0.7; }\n        }\n      `;\n      fovGroup.style.animation = `${animationName} ${fovSpeed}s ease-in-out infinite`;\n      fovGroup.style.transformOrigin = \"0 0\";\n    } else if (fovAnimationType === \"rings\") {\n\n      for (let i = 1; i <= 3; i++) {\n        const ring = document.createElementNS(ns, \"circle\");\n        ring.setAttribute(\"cx\", \"0\");\n        ring.setAttribute(\"cy\", \"0\");\n        ring.setAttribute(\"r\", fovDistance * 0.3 * i);\n        ring.style.fill = \"none\";\n        ring.style.stroke = fovBorderColor;\n        ring.style.strokeWidth = \"2\";\n        ring.style.opacity = \"0\";\n        const ringAnimName = `${animationName}-ring-${i}`;\n        const ringStyle = document.createElementNS(\"http://www.w3.org/1999/xhtml\", \"style\");\n        ringStyle.textContent = `\n          @keyframes ${ringAnimName} {\n            0% { r: ${fovInnerRadius || 10}; opacity: 0.8; }\n            100% { r: ${fovDistance}; opacity: 0; }\n          }\n        `;\n        ring.style.animation = `${ringAnimName} ${fovSpeed}s ease-out infinite`;\n        ring.style.animationDelay = `${(i - 1) * (fovSpeed / 3)}s`;\n        fovGroup.appendChild(ringStyle);\n        fovGroup.appendChild(ring);\n      }\n    } else if (fovAnimationType === \"spin\") {\n      styleEl.textContent = `\n        @keyframes ${animationName} {\n          0% { transform: rotate(0deg); }\n          100% { transform: rotate(360deg); }\n        }\n      `;\n      fovGroup.style.animation = `${animationName} ${fovSpeed}s linear infinite`;\n      fovGroup.style.transformOrigin = \"0 0\";\n    }\n    \n    if (fovAnimationType !== \"rings\") {\n      fovGroup.appendChild(styleEl);\n      const elapsedSeconds = (Date.now() - FOV_ANIMATION_START) / 1000;\n      const animationOffset = elapsedSeconds % fovSpeed;\n      fovGroup.style.animationDelay = `-${animationOffset}s`;\n    }\n  }\n  \n  nodeGroup.insertBefore(fovGroup, nodeGroup.firstChild);\n}\n      function forgeTheTopology() {\n       if (!NODE_DATA || !EDGE_DATA) {\n        console.warn(\"forgeTheTopology called before data initialized\");\n        return;\n       }\n       const svg = document.getElementById(\"map\");\n       svg.innerHTML = \"\";\n       const ns = \"http://www.w3.org/2000/svg\";\n       const defs = document.createElementNS(ns, \"defs\");\n       const flowArrowBig = document.createElementNS(ns, \"path\");\n       flowArrowBig.id = \"flow-arrow-big\";\n       flowArrowBig.setAttribute(\"d\", \"M-6,-4 L6,0 L-6,4 L-3,0 Z\");\n       defs.appendChild(flowArrowBig);\n       const flowArrowSmall = document.createElementNS(ns, \"path\");\n       flowArrowSmall.id = \"flow-arrow-small\";\n       flowArrowSmall.setAttribute(\"d\", \"M-4,-3 L4,0 L-4,3 Z\");\n       defs.appendChild(flowArrowSmall);\n       const markerForward = document.createElementNS(ns, \"marker\");\n       markerForward.id = \"arrow-forward\";\n       markerForward.setAttribute(\"markerWidth\", \"10\");\n       markerForward.setAttribute(\"markerHeight\", \"10\");\n       markerForward.setAttribute(\"refX\", \"9\");\n       markerForward.setAttribute(\"refY\", \"3\");\n       markerForward.setAttribute(\"orient\", \"auto\");\n       markerForward.setAttribute(\"markerUnits\", \"strokeWidth\");\n       const pathForward = document.createElementNS(ns, \"path\");\n       pathForward.setAttribute(\"d\", \"M0,0 L0,6 L9,3 z\");\n       pathForward.setAttribute(\"fill\", \"context-stroke\");\n       markerForward.appendChild(pathForward);\n       defs.appendChild(markerForward);\n       const markerBackward = document.createElementNS(ns, \"marker\");\n       markerBackward.id = \"arrow-backward\";\n       markerBackward.setAttribute(\"markerWidth\", \"10\");\n       markerBackward.setAttribute(\"markerHeight\", \"10\");\n       markerBackward.setAttribute(\"refX\", \"0\");\n       markerBackward.setAttribute(\"refY\", \"3\");\n       markerBackward.setAttribute(\"orient\", \"auto\");\n       markerBackward.setAttribute(\"markerUnits\", \"strokeWidth\");\n       const pathBackward = document.createElementNS(ns, \"path\");\n       pathBackward.setAttribute(\"d\", \"M9,0 L9,6 L0,3 z\");\n       pathBackward.setAttribute(\"fill\", \"context-stroke\");\n       markerBackward.appendChild(pathBackward);\ndefs.appendChild(markerBackward);\nconst wallPattern = document.createElementNS(ns, \"pattern\");\nwallPattern.id = \"wall-hatch\";\nwallPattern.setAttribute(\"patternUnits\", \"userSpaceOnUse\");\nwallPattern.setAttribute(\"width\", \"8\");\nwallPattern.setAttribute(\"height\", \"8\");\nwallPattern.setAttribute(\"patternTransform\", \"rotate(45)\");\nconst wallLine = document.createElementNS(ns, \"line\");\nwallLine.setAttribute(\"x1\", \"0\");\nwallLine.setAttribute(\"y1\", \"0\");\nwallLine.setAttribute(\"x2\", \"0\");\nwallLine.setAttribute(\"y2\", \"8\");\nwallLine.setAttribute(\"stroke\", \"#666\");\nwallLine.setAttribute(\"stroke-width\", \"2\");\nwallPattern.appendChild(wallLine);\ndefs.appendChild(wallPattern);\n\nsvg.appendChild(defs);\n       const boundary = document.createElementNS(ns, \"rect\");\n       boundary.setAttribute(\"x\", CANVAS_PADDING);\n       boundary.setAttribute(\"y\", CANVAS_PADDING);\n       boundary.setAttribute(\"width\", CANVAS_WIDTH - CANVAS_PADDING * 2);\n       boundary.setAttribute(\"height\", CANVAS_HEIGHT - CANVAS_PADDING * 2);\n       boundary.setAttribute(\"fill\", \"none\");\n       boundary.setAttribute(\"stroke\", (PAGE_STATE.canvasBorder || \"#475569\") + \"4D\");\n       boundary.setAttribute(\"stroke-width\", \"20\");\n       boundary.setAttribute(\"stroke-dasharray\", \"10 5\");\n       boundary.setAttribute(\"rx\", \"8\");\n       svg.appendChild(boundary);\n       const currentMappingModeForCanvas = PAGE_STATE.mappingMode || 'network';\n      const shouldDrawCanvasTemplate = currentView.mode !== \"rack\" || currentMappingModeForCanvas !== 'network';\n      if (shouldDrawCanvasTemplate && PAGE_STATE.canvasGridEnabled !== false) {\n const template = PAGE_STATE.canvasTemplate || \"grid\";\n if (template === \"grid\") {\n  const gridGroup = document.createElementNS(ns, \"g\");\n  gridGroup.id = \"canvas-grid\";\n  const gridSize = PAGE_STATE.canvasGridSize || 50;\n  const gridColor = (PAGE_STATE.canvasGrid || \"#475569\") + \"33\";\n  const majorGridColor = (PAGE_STATE.canvasGrid || \"#475569\") + \"66\";\n  for (let x = CANVAS_PADDING; x <= CANVAS_WIDTH - CANVAS_PADDING; x += gridSize) {\n   const line = document.createElementNS(ns, \"line\");\n   line.setAttribute(\"x1\", x);\n   line.setAttribute(\"y1\", CANVAS_PADDING);\n   line.setAttribute(\"x2\", x);\n   line.setAttribute(\"y2\", CANVAS_HEIGHT - CANVAS_PADDING);\n   line.setAttribute(\"stroke\", (x - CANVAS_PADDING) % (gridSize * 5) === 0 ? majorGridColor : gridColor);\n   line.setAttribute(\"stroke-width\", (x - CANVAS_PADDING) % (gridSize * 5) === 0 ? \"2\" : \"1\");\n   gridGroup.appendChild(line);\n  }\n  for (let y = CANVAS_PADDING; y <= CANVAS_HEIGHT - CANVAS_PADDING; y += gridSize) {\n   const line = document.createElementNS(ns, \"line\");\n   line.setAttribute(\"x1\", CANVAS_PADDING);\n   line.setAttribute(\"y1\", y);\n   line.setAttribute(\"x2\", CANVAS_WIDTH - CANVAS_PADDING);\n   line.setAttribute(\"y2\", y);\n   line.setAttribute(\"stroke\", (y - CANVAS_PADDING) % (gridSize * 5) === 0 ? majorGridColor : gridColor);\n   line.setAttribute(\"stroke-width\", (y - CANVAS_PADDING) % (gridSize * 5) === 0 ? \"2\" : \"1\");\n   gridGroup.appendChild(line);\n  }\n  svg.appendChild(gridGroup);\n } else if (template !== \"none\") {\n  const templateGroup = renderCanvasTemplate(svg, ns, template, CANVAS_WIDTH, CANVAS_HEIGHT, CANVAS_PADDING, PAGE_STATE.canvasTemplateColor);\n  if (templateGroup) svg.appendChild(templateGroup);\n }\n}\n       if (currentView.mode === \"rack\" && currentView.rackId) {\n        const rackCapacity = getRackCapacity(currentView.rackId);\n      const rackUHeight = getRackUHeight(currentView.rackId);\n        const rackGroup = document.createElementNS(ns, \"g\");\n        rackGroup.id = \"rack-visualization\";\n        const currentMappingMode = PAGE_STATE.mappingMode || 'network';\n        const showUSlotRack = currentMappingMode === 'network';\n        if (showUSlotRack) {\n        const rackFrame = document.createElementNS(ns, \"rect\");\n        rackFrame.setAttribute(\"x\", RACK_START_X - RACK_WIDTH / 2);\n        rackFrame.setAttribute(\"y\", RACK_START_Y);\n        rackFrame.setAttribute(\"width\", RACK_WIDTH);\n      rackFrame.setAttribute(\"height\", rackCapacity * rackUHeight);\n        rackFrame.setAttribute(\"fill\", (PAGE_STATE.rackFrameFill || \"#0f172a\") + \"4D\");\n        rackFrame.setAttribute(\"stroke\", PAGE_STATE.rackFrameStroke || \"#4fd1c5\");\n        rackFrame.setAttribute(\"stroke-width\", \"3\");\n        rackFrame.setAttribute(\"rx\", \"4\");\n        rackGroup.appendChild(rackFrame);\n        for (let u = 0; u <= rackCapacity; u++) {\n const y = RACK_START_Y + u * rackUHeight;\n if (PAGE_STATE.rackGridEnabled !== false) {\n const line = document.createElementNS(ns, \"line\");\n         line.setAttribute(\"x1\", RACK_START_X - RACK_WIDTH / 2);\n         line.setAttribute(\"y1\", y);\n         line.setAttribute(\"x2\", RACK_START_X + RACK_WIDTH / 2);\n         line.setAttribute(\"y2\", y);\n         line.setAttribute(\"stroke\", (PAGE_STATE.rackLineColor || \"#475569\") + \"66\");\n         line.setAttribute(\"stroke-width\", u % 5 === 0 ? \"2\" : \"1\");\n         line.setAttribute(\"stroke-dasharray\", u % 5 === 0 ? \"none\" : \"5,5\");\n         rackGroup.appendChild(line);\n }\n if (u < rackCapacity) {\n          const uNumber = rackCapacity - u;\n          const text = document.createElementNS(ns, \"text\");\n          text.setAttribute(\"x\", RACK_START_X - RACK_WIDTH / 2 - 30);\n          text.setAttribute(\"y\", y + rackUHeight / 2);\n          text.setAttribute(\"text-anchor\", \"middle\");\n          text.setAttribute(\"dominant-baseline\", \"middle\");\n          text.style.fill = PAGE_STATE.rackTextColor || \"#4fd1c5\";\n          text.style.fontSize = \"14px\";\n          text.style.fontWeight = \"bold\";\n          text.textContent = `U${uNumber}`;\n          rackGroup.appendChild(text);\n          const textRight = document.createElementNS(ns, \"text\");\n          textRight.setAttribute(\"x\", RACK_START_X + RACK_WIDTH / 2 + 30);\n          textRight.setAttribute(\"y\", y + rackUHeight / 2);\n          textRight.setAttribute(\"text-anchor\", \"middle\");\n          textRight.setAttribute(\"dominant-baseline\", \"middle\");\n          textRight.style.fill = PAGE_STATE.rackTextColor || \"#4fd1c5\";\n          textRight.style.fontSize = \"14px\";\n          textRight.style.fontWeight = \"bold\";\n          textRight.textContent = `U${uNumber}`;\n          rackGroup.appendChild(textRight);\n         }\n        }\n        const collisions = checkRackSlotCollisions(currentView.rackId);\n        if (collisions.length > 0) {\n         const occupiedSlots = {};\n         Object.entries(NODE_DATA).forEach(([id, node]) => {\n          if (node.assignedRack === currentView.rackId && !node.isRack) {\n           const unit = parseInt(node.rackUnit) || 1;\n           const height = parseInt(node.uHeight) || 1;\n           for (let u = unit; u < unit + height; u++) {\n            if (!occupiedSlots[u]) occupiedSlots[u] = [];\n            occupiedSlots[u].push(id);\n           }\n          }\n         });\n         Object.entries(occupiedSlots).forEach(([slot, nodeIds]) => {\n          if (nodeIds.length > 1) {\n           const slotNum = parseInt(slot);\n           const y = RACK_START_Y + (rackCapacity - slotNum) * rackUHeight;\n           const warningRect = document.createElementNS(ns, \"rect\");\n           warningRect.setAttribute(\"x\", RACK_START_X - RACK_WIDTH / 2);\n           warningRect.setAttribute(\"y\", y);\n           warningRect.setAttribute(\"width\", RACK_WIDTH);\n           warningRect.setAttribute(\"height\", rackUHeight);\n           warningRect.style.fill = \"#dc2626\";\n           warningRect.style.fillOpacity = \"0.3\";\n           warningRect.style.stroke = \"#dc2626\";\n           warningRect.style.strokeWidth = \"2\";\n           warningRect.style.strokeDasharray = \"5,5\";\n           rackGroup.appendChild(warningRect);\n          }\n         });\n         const warningText = document.createElementNS(ns, \"text\");\n         warningText.setAttribute(\"x\", RACK_START_X);\n         warningText.setAttribute(\"y\", RACK_START_Y - 30);\n         warningText.setAttribute(\"text-anchor\", \"middle\");\n         warningText.style.fill = \"#dc2626\";\n         warningText.style.fontSize = \"14px\";\n         warningText.style.fontWeight = \"bold\";\n         warningText.textContent = t(\"messages.slotCollisionWarning\", { count: collisions.length });\n         rackGroup.appendChild(warningText);\n        }\n        }\n        svg.appendChild(rackGroup);\n       }\n        if (RECT_DATA && RECT_DATA.list) {\n         RECT_DATA.list.forEach((rect) => {\n          if (currentView.mode === \"rack\") return;\n          if (rect.style === \"filled\" || rect.style === \"outlined\") {\n           const g = document.createElementNS(ns, \"g\");\n           g.classList.add(\"rect-group\");\n           g.dataset.rectId = rect.id;\n           const rectCenterX = rect.x + rect.width / 2;\n           const rectCenterY = rect.y + rect.height / 2;\n           const rectRotation = rect.rotation || 0;\n           if (rectRotation !== 0) {\n             g.setAttribute(\"transform\", `rotate(${rectRotation}, ${rectCenterX}, ${rectCenterY})`);\n           }\n           const rectEl = document.createElementNS(ns, \"rect\");\n           rectEl.classList.add(\"rect-shape\");\n           rectEl.setAttribute(\"x\", rect.x);\n           rectEl.setAttribute(\"y\", rect.y);\n           rectEl.setAttribute(\"width\", rect.width);\n           rectEl.setAttribute(\"height\", rect.height);\n           if (rect.style === \"filled\") {\n             rectEl.style.fill = rect.color;\n             rectEl.style.fillOpacity = rect.fillOpacity !== undefined ? rect.fillOpacity : 0.3;\n           } else {\n             rectEl.style.fill = \"none\";\n           }\n           rectEl.style.stroke = rect.borderColor || rect.color;\n           rectEl.style.strokeWidth = (rect.borderWidth !== undefined ? rect.borderWidth : 2) + \"px\";\n           if (rect.lineStyle === \"dashed\") { rectEl.style.strokeDasharray = \"10,5\"; }\nelse if (rect.lineStyle === \"dotted\") { rectEl.style.strokeDasharray = \"2,4\"; }\nelse if (rect.lineStyle === \"wall\") {\n  rectEl.style.fill = rect.color;\n  rectEl.style.fillOpacity = rect.fillOpacity !== undefined ? rect.fillOpacity : 0.5;\n  rectEl.style.stroke = rect.borderColor || rect.color;\n  rectEl.style.strokeWidth = (rect.borderWidth !== undefined ? rect.borderWidth : 2) + \"px\";\n  const hatchGroup = document.createElementNS(ns, \"g\");\n  hatchGroup.classList.add(\"wall-hatch-lines\");\n  hatchGroup.style.pointerEvents = \"none\";\n  const spacing = 12;\n  const hatchColor = rect.borderColor || rect.color;\n  for (let i = -rect.height; i < rect.width + rect.height; i += spacing) {\n    const line = document.createElementNS(ns, \"line\");\n    line.setAttribute(\"x1\", rect.x + i);\n    line.setAttribute(\"y1\", rect.y);\n    line.setAttribute(\"x2\", rect.x + i - rect.height);\n    line.setAttribute(\"y2\", rect.y + rect.height);\n    line.style.stroke = hatchColor;\n    line.style.strokeWidth = \"2\";\n    hatchGroup.appendChild(line);\n  }\n  const clipId = \"clip-\" + rect.id;\n  const clipPath = document.createElementNS(ns, \"clipPath\");\n  clipPath.id = clipId;\n  const clipRect = document.createElementNS(ns, \"rect\");\n  clipRect.setAttribute(\"x\", rect.x);\n  clipRect.setAttribute(\"y\", rect.y);\n  clipRect.setAttribute(\"width\", rect.width);\n  clipRect.setAttribute(\"height\", rect.height);\n  clipPath.appendChild(clipRect);\n  defs.appendChild(clipPath);\n  hatchGroup.setAttribute(\"clip-path\", \"url(#\" + clipId + \")\");\n  g.appendChild(hatchGroup);\n}\n           else { rectEl.style.strokeDasharray = \"none\"; }\n           rectEl.style.cursor = \"move\";\n          rectEl.addEventListener(\"click\", (e) => {\n\t\t  if (isViewOnly()) return;\n             if (rectDrawMode) return;\n             e.stopPropagation();\n             currentRectId = rect.id;\n             selectTheRect(rect.id);\n           });\n           rectEl.addEventListener(\"contextmenu\", (e) => {\n\t\t   if (isViewOnly()) return;\n             e.preventDefault();\n             e.stopPropagation();\n             if (selectedRects.has(rect.id)) {\n               selectedRects.delete(rect.id);\n             } else {\n               selectedRects.add(rect.id);\n             }\n             updateAllSelections();\n           });\n           let rectLastTap = 0;\n           rectEl.addEventListener(\"touchend\", (e) => {\n             const now = Date.now();\n             if (now - rectLastTap < 300) {\n               e.preventDefault();\n               if (selectedRects.has(rect.id)) {\n                 selectedRects.delete(rect.id);\n               } else {\n                 selectedRects.add(rect.id);\n               }\n               updateAllSelections();\n               if (navigator.vibrate) navigator.vibrate(50);\n               rectLastTap = 0;\n             } else {\n               rectLastTap = now;\n             }\n           }, { passive: false });\n           const deleteBtn = document.createElementNS(ns, \"g\");\n           deleteBtn.classList.add(\"rect-delete-btn\");\n           deleteBtn.style.cursor = \"pointer\";\n           deleteBtn.style.display = rectDrawMode ? \"block\" : \"none\";\n           const deleteBg = document.createElementNS(ns, \"circle\");\n           deleteBg.setAttribute(\"cx\", rect.x + rect.width - 10);\n           deleteBg.setAttribute(\"cy\", rect.y + 10);\n           deleteBg.setAttribute(\"r\", 12);\n           deleteBg.style.fill = \"#f56565\";\n           deleteBg.style.stroke = \"white\";\n           deleteBg.style.strokeWidth = \"2\";\n           const deleteX = document.createElementNS(ns, \"text\");\n           deleteX.setAttribute(\"x\", rect.x + rect.width - 10);\n           deleteX.setAttribute(\"y\", rect.y + 10);\n           deleteX.setAttribute(\"text-anchor\", \"middle\");\n           deleteX.setAttribute(\"dominant-baseline\", \"middle\");\n           deleteX.style.fill = \"white\";\n           deleteX.style.fontSize = \"16px\";\n           deleteX.style.fontWeight = \"bold\";\n           deleteX.style.pointerEvents = \"none\";\n           deleteX.textContent = \"×\";\n           deleteBtn.appendChild(deleteBg);\n           deleteBtn.appendChild(deleteX);\n           deleteBtn.addEventListener(\"click\", (e) => {\n            e.stopPropagation();\n            e.preventDefault();\n            deleteRectangle(rect.id);\n           });\n           deleteBtn.addEventListener(\"touchend\", (e) => {\n      e.stopPropagation();\n           e.preventDefault();\n           deleteRectangle(rect.id);\n           });\n           let isDragging = false;\n           let dragStartX, dragStartY;\n           let rectStartX, rectStartY;\n         rectEl.addEventListener(\"mousedown\", (e) => {\n\t\t if (isViewOnly()) return;\n      if (rectDrawMode) return;\n      e.preventDefault();\n      e.stopPropagation();\n      isDragging = true;\n      dragStartX = e.clientX;\n      dragStartY = e.clientY;\n      rectStartX = rect.x;\n      rectStartY = rect.y;\n      rectEl.style.cursor = \"grabbing\";\n      if (selectedRects.has(rect.id)) {\n      initialPositions = {};\n      selectedNodes.forEach(nodeId => {\n      const nodePos = savedPositions[nodeId];\n      if (nodePos) initialPositions[nodeId] = { x: nodePos.x, y: nodePos.y };\n      });\n      selectedRects.forEach(rectId => {\n      const r = RECT_DATA.list.find(x => x.id === rectId);\n      if (r) { r._dragStartX = r.x; r._dragStartY = r.y; }\n      });\n      selectedTexts.forEach(textId => {\n      const t = TEXT_DATA.list.find(x => x.id === textId);\n      if (t) { t._dragStartX = t.x; t._dragStartY = t.y; }\n      });\n      selectedEdges.forEach(edgeId => {\n      const ed = EDGE_DATA.list.find(x => x.id === edgeId);\n      if (ed && ed.points) { ed._dragStartPoints = ed.points.map(p => ({x: p.x, y: p.y})); }\n      });\n      }\n      });\n           const moveHandler = (e) => {\n      if (!isDragging || rectDrawMode) return;\n      const svgEl = svg;\n      const pt1 = svgEl.createSVGPoint();\n      pt1.x = dragStartX;\n      pt1.y = dragStartY;\n      const svgP1 = pt1.matrixTransform(svgEl.getScreenCTM().inverse());\n      const pt2 = svgEl.createSVGPoint();\n      pt2.x = e.clientX;\n      pt2.y = e.clientY;\n      const svgP2 = pt2.matrixTransform(svgEl.getScreenCTM().inverse());\n      const dx = svgP2.x - svgP1.x;\n      const dy = svgP2.y - svgP1.y;\n      rect.x = rectStartX + dx;\n      rect.y = rectStartY + dy;\n      if (selectedRects.has(rect.id)) {\n      selectedNodes.forEach(nodeId => {\n      if (initialPositions[nodeId]) {\n       const newX = initialPositions[nodeId].x + dx;\n       const newY = initialPositions[nodeId].y + dy;\n       savedPositions[nodeId] = { x: newX, y: newY };\n      }\n      });\n      selectedRects.forEach(rectId => {\n      if (rectId === rect.id) return;\n      const r = RECT_DATA.list.find(x => x.id === rectId);\n      if (r && r._dragStartX !== undefined) { r.x = r._dragStartX + dx; r.y = r._dragStartY + dy; }\n      });\n      selectedTexts.forEach(textId => {\n      const t = TEXT_DATA.list.find(x => x.id === textId);\n      if (t && t._dragStartX !== undefined) { t.x = t._dragStartX + dx; t.y = t._dragStartY + dy; }\n      });\n      selectedEdges.forEach(edgeId => {\n      const ed = EDGE_DATA.list.find(x => x.id === edgeId);\n      if (ed && ed._dragStartPoints) { ed.points.forEach((p, i) => { p.x = ed._dragStartPoints[i].x + dx; p.y = ed._dragStartPoints[i].y + dy; }); }\n      });\n      }\n      forgeTheTopology();\n      };\n           const upHandler = () => {\n            if (isDragging) {\n             isDragging = false;\n             rectEl.style.cursor = \"move\";\n            }\n           };\n           document.addEventListener(\"mousemove\", moveHandler);\n           document.addEventListener(\"mouseup\", upHandler);\n           let touchStartX, touchStartY;\n           let touchRectStartX, touchRectStartY;\n           rectEl.addEventListener(\"touchstart\", (e) => {\n\t\t   if (isViewOnly()) return;\n            if (rectDrawMode) return;\n            e.preventDefault();\n            e.stopPropagation();\n            isDragging = true;\n            const touch = e.touches[0];\n            touchStartX = touch.clientX;\n            touchStartY = touch.clientY;\n            touchRectStartX = rect.x;\n            touchRectStartY = rect.y;\n            rectEl.style.cursor = \"grabbing\";\n           }, { passive: false });\n           rectEl.addEventListener(\"touchmove\", (e) => {\n\t\t    if (isViewOnly()) return;\n            if (!isDragging || rectDrawMode) return;\n            if (!e.touches[0]) return;\n            e.preventDefault();\n            e.stopPropagation();\n            const svgEl = svg;\n            const pt1 = svgEl.createSVGPoint();\n            pt1.x = touchStartX;\n            pt1.y = touchStartY;\n            const svgP1 = pt1.matrixTransform(svgEl.getScreenCTM().inverse());\n            const touch = e.touches[0];\n            const pt2 = svgEl.createSVGPoint();\n            pt2.x = touch.clientX;\n            pt2.y = touch.clientY;\n            const svgP2 = pt2.matrixTransform(svgEl.getScreenCTM().inverse());\n            const dx = svgP2.x - svgP1.x;\n            const dy = svgP2.y - svgP1.y;\n            rect.x = touchRectStartX + dx;\n            rect.y = touchRectStartY + dy;\n            forgeTheTopology();\n           }, { passive: false });\n           rectEl.addEventListener(\"touchend\", (e) => {\n            if (isDragging) {\n             e.preventDefault();\n             isDragging = false;\n             rectEl.style.cursor = \"move\";\n            }\n           }, { passive: false });\n            if (currentRectId === rect.id) {\n      const corners = [\n      { cx: rect.x, cy: rect.y, cursor: 'nwse-resize', dx: -1, dy: -1 },\n      { cx: rect.x + rect.width, cy: rect.y, cursor: 'nesw-resize', dx: 1, dy: -1 },\n      { cx: rect.x, cy: rect.y + rect.height, cursor: 'nesw-resize', dx: -1, dy: 1 },\n      { cx: rect.x + rect.width, cy: rect.y + rect.height, cursor: 'nwse-resize', dx: 1, dy: 1 }\n      ];\n      corners.forEach((corner, idx) => {\n      const handle = document.createElementNS(ns, \"circle\");\n      handle.setAttribute(\"cx\", corner.cx);\n      handle.setAttribute(\"cy\", corner.cy);\n      const borderW = rect.borderWidth !== undefined ? rect.borderWidth : 2;\n      const handleSize = Math.max(PAGE_STATE.selectionHandleSize || 8, borderW + 4);\n      handle.setAttribute(\"r\", handleSize);\n      handle.style.fill = PAGE_STATE.selectionHandle || \"#f59e0b\";\n      handle.style.stroke = \"#fff\";\n      handle.style.strokeWidth = \"2\";\n      handle.style.cursor = corner.cursor;\n      handle.addEventListener(\"mousedown\", (e) => {\n\t  if (isViewOnly()) return;\n      e.preventDefault();\n      e.stopPropagation();\n\t  pushUndo(\"resize zone\");\n      let dragging = true;\n      const startX = e.clientX, startY = e.clientY;\n      const origX = rect.x, origY = rect.y, origW = rect.width, origH = rect.height;\n      const moveHandler = (ev) => {\n        if (!dragging) return;\n        const pt1 = svg.createSVGPoint(); pt1.x = startX; pt1.y = startY;\n        const pt2 = svg.createSVGPoint(); pt2.x = ev.clientX; pt2.y = ev.clientY;\n        const svgP1 = pt1.matrixTransform(svg.getScreenCTM().inverse());\n        const svgP2 = pt2.matrixTransform(svg.getScreenCTM().inverse());\n        const dx = svgP2.x - svgP1.x, dy = svgP2.y - svgP1.y;\n        if (corner.dx < 0) { rect.x = origX + dx; rect.width = origW - dx; }\n        else { rect.width = origW + dx; }\n        if (corner.dy < 0) { rect.y = origY + dy; rect.height = origH - dy; }\n        else { rect.height = origH + dy; }\n        if (rect.width < 20) rect.width = 20;\n        if (rect.height < 20) rect.height = 20;\n        forgeTheTopology();\n      };\n      const upHandler = () => { dragging = false; document.removeEventListener(\"mousemove\", moveHandler); document.removeEventListener(\"mouseup\", upHandler); };\n      document.addEventListener(\"mousemove\", moveHandler);\n      document.addEventListener(\"mouseup\", upHandler);\n      });\n      g.appendChild(handle);\n      });\n      }\n          if (rect.groupId) {\n      const groupIndicator = document.createElementNS(ns, \"rect\");\n      groupIndicator.setAttribute(\"x\", rect.x - 4);\n      groupIndicator.setAttribute(\"y\", rect.y - 4);\n      groupIndicator.setAttribute(\"width\", rect.width + 8);\n      groupIndicator.setAttribute(\"height\", rect.height + 8);\n      groupIndicator.style.fill = \"none\";\n      groupIndicator.style.stroke = \"#4fd1c5\";\n      groupIndicator.style.strokeWidth = \"3\";\n      groupIndicator.style.strokeDasharray = \"5,5\";\n      groupIndicator.style.pointerEvents = \"none\";\n      g.insertBefore(groupIndicator, g.firstChild);\n      }\n      g.appendChild(rectEl);\n      g.appendChild(deleteBtn);\n      svg.appendChild(g);\n          }\n         });\n        }\n       const centerX = CANVAS_WIDTH / 2;\n       const centerY = CANVAS_HEIGHT / 2;\n       let positions = {};\n              Object.keys(NODE_DATA).forEach((id) => {\n        if (currentView.mode === \"rack\") {\n         const node = NODE_DATA[id];\n         if (!node || node.assignedRack !== currentView.rackId) {\n          return;\n         }\n        }\n        positions[id] = savedPositions[id] || {\n         x: centerX,\n         y: centerY\n        };\n       });\n       if (Object.keys(savedPositions).length === 0) {\n        const nodeIds = Object.keys(NODE_DATA).filter(id => {\n         if (currentView.mode === \"rack\") {\n          const node = NODE_DATA[id];\n          return node && node.assignedRack === currentView.rackId;\n         }\n         return true;\n        });\n        const baseY = centerY - 300;\n        if (nodeIds.length > 0) {\n         positions[nodeIds[0]] = {\n          x: centerX,\n          y: baseY\n         };\n         const remaining = nodeIds.slice(1);\n         const radius = 350;\n         const startAngle = Math.PI * 0.3;\n         const endAngle = Math.PI * 0.7;\n         remaining.forEach((id, i) => {\n          const angle = startAngle + (endAngle - startAngle) * (i / Math.max(1, remaining.length - 1));\n          positions[id] = {\n           x: centerX + Math.cos(angle) * radius * (i % 2 === 0 ? 1 : 1.3),\n           y: baseY + 200 + Math.sin(angle) * radius * 0.8 + i * 80,\n          };\n         });\n        }\n       }\n       Object.keys(positions).forEach((id) => {\n        let pos = savedPositions[id] || positions[id];\n        const nodeSize = savedSizes[id] || 55;\n        pos.x = Math.max(nodeSize + CANVAS_PADDING, Math.min(CANVAS_WIDTH - nodeSize - CANVAS_PADDING, pos.x));\n        pos.y = Math.max(nodeSize + CANVAS_PADDING, Math.min(CANVAS_HEIGHT - nodeSize - CANVAS_PADDING, pos.y));\n        positions[id] = {\n         x: pos.x,\n         y: pos.y\n        };\n        savedPositions[id] = {\n         x: pos.x,\n         y: pos.y\n        };\n       });\n       const edgePairCount = {};\n       const edgePairIndex = {};\n       EDGE_DATA.list.forEach((edge) => {\n        if (edge.type === \"custom\") return;\n        const key = [edge.from, edge.to].sort().join(\"||\");\n        edgePairCount[key] = (edgePairCount[key] || 0) + 1;\n       });\n       EDGE_DATA.list.forEach((edge) => {\n        if (edge.type === \"custom\") return;\n        const key = [edge.from, edge.to].sort().join(\"||\");\n        if (!edgePairIndex[key]) edgePairIndex[key] = 0;\n        edge._pairIndex = edgePairIndex[key];\n        edge._pairTotal = edgePairCount[key];\n        edgePairIndex[key]++;\n       });\n       \n       const orthoGaps = (function() {\n         const segments = [];\n         const GAP_SIZE = 12;\n         EDGE_DATA.list.forEach((edge, edgeIndex) => {\n           if ((edge.routing || PAGE_STATE.defaultEdgeRouting || \"curved\") !== \"orthogonal\") return;\n           if (edge.type === \"custom\") return;\n           const p1 = positions[edge.from];\n           const p2 = positions[edge.to];\n           if (!p1 || !p2) return;\n           const waypoints = edge.waypoints || [];\n           const pairIndex = edge._pairIndex || 0;\n           const pairTotal = edge._pairTotal || 1;\n           const offset = (pairIndex - (pairTotal - 1) / 2) * 40;\n           if (waypoints.length > 0) {\n             const allPoints = [p1, ...waypoints, p2];\n             for (let i = 1; i < allPoints.length; i++) {\n               const prev = allPoints[i - 1];\n               const curr = allPoints[i];\n               const dx = curr.x - prev.x;\n               const dy = curr.y - prev.y;\n               if (Math.abs(dx) > Math.abs(dy)) {\n                 const midX = prev.x + dx / 2;\n                 segments.push({ edgeId: edge.id, x1: prev.x, y1: prev.y, x2: midX, y2: prev.y, idx: edgeIndex });\n                 segments.push({ edgeId: edge.id, x1: midX, y1: prev.y, x2: midX, y2: curr.y, idx: edgeIndex });\n                 segments.push({ edgeId: edge.id, x1: midX, y1: curr.y, x2: curr.x, y2: curr.y, idx: edgeIndex });\n               } else {\n                 const midY = prev.y + dy / 2;\n                 segments.push({ edgeId: edge.id, x1: prev.x, y1: prev.y, x2: prev.x, y2: midY, idx: edgeIndex });\n                 segments.push({ edgeId: edge.id, x1: prev.x, y1: midY, x2: curr.x, y2: midY, idx: edgeIndex });\n                 segments.push({ edgeId: edge.id, x1: curr.x, y1: midY, x2: curr.x, y2: curr.y, idx: edgeIndex });\n               }\n             }\n           } else {\n             const dx = p2.x - p1.x;\n             const dy = p2.y - p1.y;\n             if (Math.abs(dx) > Math.abs(dy)) {\n               const midX = p1.x + dx / 2 + offset;\n               segments.push({ edgeId: edge.id, x1: p1.x, y1: p1.y, x2: midX, y2: p1.y, idx: edgeIndex });\n               segments.push({ edgeId: edge.id, x1: midX, y1: p1.y, x2: midX, y2: p2.y, idx: edgeIndex });\n               segments.push({ edgeId: edge.id, x1: midX, y1: p2.y, x2: p2.x, y2: p2.y, idx: edgeIndex });\n             } else {\n               const midY = p1.y + dy / 2 + offset;\n               segments.push({ edgeId: edge.id, x1: p1.x, y1: p1.y, x2: p1.x, y2: midY, idx: edgeIndex });\n               segments.push({ edgeId: edge.id, x1: p1.x, y1: midY, x2: p2.x, y2: midY, idx: edgeIndex });\n               segments.push({ edgeId: edge.id, x1: p2.x, y1: midY, x2: p2.x, y2: p2.y, idx: edgeIndex });\n             }\n           }\n         });\n         const gaps = {};\n         for (let i = 0; i < segments.length; i++) {\n           for (let j = i + 1; j < segments.length; j++) {\n             const s1 = segments[i];\n             const s2 = segments[j];\n             if (s1.edgeId === s2.edgeId) continue;\n             const s1Horiz = Math.abs(s1.y1 - s1.y2) < 1;\n             const s2Horiz = Math.abs(s2.y1 - s2.y2) < 1;\n             if (s1Horiz === s2Horiz) continue;\n             const horiz = s1Horiz ? s1 : s2;\n             const vert = s1Horiz ? s2 : s1;\n             const hY = horiz.y1;\n             const vX = vert.x1;\n             const hMinX = Math.min(horiz.x1, horiz.x2);\n             const hMaxX = Math.max(horiz.x1, horiz.x2);\n             const vMinY = Math.min(vert.y1, vert.y2);\n             const vMaxY = Math.max(vert.y1, vert.y2);\n             if (vX > hMinX && vX < hMaxX && hY > vMinY && hY < vMaxY) {\n               const gapEdge = s1.idx > s2.idx ? s1.edgeId : s2.edgeId;\n               if (!gaps[gapEdge]) gaps[gapEdge] = [];\n               gaps[gapEdge].push({ x: vX, y: hY });\n             }\n           }\n         }\n         return gaps;\n       })();\n\n       const _placedPortLabels = [];\n\n       EDGE_DATA.list.forEach((edge) => {\n        const fromNode = NODE_DATA[edge.from];\n        const toNode = NODE_DATA[edge.to];\n        if (currentView.mode === \"rack\") {\n         if (!fromNode || !toNode || fromNode.assignedRack !== currentView.rackId || toNode.assignedRack !== currentView.rackId) return;\n        } else {\n         if (fromNode?.assignedRack || toNode?.assignedRack) return;\n        }\n        if (edge.type === \"custom\" && Array.isArray(edge.points) && edge.points.length >= 2) {\n         const poly = document.createElementNS(ns, \"polyline\");\n         poly.classList.add(\"edge\");\n         poly.dataset.edgeId = edge.id;\n         poly.style.stroke = edge.color || PAGE_STATE.defaultEdge || \"#475569\";\n         poly.style.strokeWidth = edge.width || 4;\n         poly.setAttribute(\"fill\", \"none\");\n         const lineStyle = edge.lineStyle || \"solid\";\nif (lineStyle === \"dashed\") {\n poly.style.strokeDasharray = \"10,5\";\n} else if (lineStyle === \"dotted\") {\n poly.style.strokeDasharray = \"2,4\";\n} else if (lineStyle === \"wall\") {\n poly.style.stroke = \"url(#wall-hatch)\";\n poly.style.strokeWidth = (edge.width || 4) * 3;\n poly.style.strokeDasharray = \"none\";\n} else {\n poly.style.strokeDasharray = \"none\";\n}\n         const direction = edge.direction || \"none\";\n         if (direction === \"forward\") {\n          poly.setAttribute(\"marker-end\", \"url(#arrow-forward)\");\n         } else if (direction === \"backward\") {\n          poly.setAttribute(\"marker-start\", \"url(#arrow-backward)\");\n         } else if (direction === \"both\") {\n          poly.setAttribute(\"marker-start\", \"url(#arrow-backward)\");\n          poly.setAttribute(\"marker-end\", \"url(#arrow-forward)\");\n         }\n         const ptsStr = edge.points.map((p) => `${p.x},${p.y}`).join(\" \");\n         poly.setAttribute(\"points\", ptsStr);\n         const animDir = PAGE_STATE.animationDirection || \"all\";\n         const shouldAnimate = (PAGE_STATE.animateConnections && edge.animate !== false || edge.animate === true) && direction !== \"none\" && edge.points.length >= 2 && (animDir === \"all\" || animDir === direction);\n         if (shouldAnimate) {\n          poly.style.opacity = \"0.25\";\n          const polyPathD = \"M \" + edge.points.map(p => `${p.x} ${p.y}`).join(\" L \");\n          const edgeStyle = edge.animationStyle || PAGE_STATE.animationStyle || \"arrows\";\n          const arrowId = edgeStyle === \"dots\" ? \"#flow-arrow-small\" : \"#flow-arrow-big\";\n          const arrowColor = edge.color || PAGE_STATE.defaultEdge || \"#475569\";\n          const arrowCount = 3;\n          const animDuration = edge.animationSpeed ? parseFloat(edge.animationSpeed) : (PAGE_STATE.animationSpeed || 1.5);\n          if (direction === \"forward\" || direction === \"both\") {\n           for (let i = 0; i < arrowCount; i++) {\n            const arrow = document.createElementNS(ns, \"use\");\n            arrow.setAttribute(\"href\", arrowId);\n            arrow.style.fill = arrowColor;\n            arrow.style.offsetPath = `path('${polyPathD}')`;\n            arrow.style.animationDuration = animDuration + \"s\";\n            arrow.style.animationDelay = (i * (animDuration / arrowCount)) + \"s\";\n            arrow.classList.add(\"edge-arrow-forward\");\n            svg.appendChild(arrow);\n           }\n          }\n          if (direction === \"backward\" || direction === \"both\") {\n           for (let i = 0; i < arrowCount; i++) {\n            const arrow = document.createElementNS(ns, \"use\");\n            arrow.setAttribute(\"href\", arrowId);\n            arrow.style.fill = arrowColor;\n            arrow.style.offsetPath = `path('${polyPathD}')`;\n            arrow.style.animationDuration = animDuration + \"s\";\n            arrow.style.animationDelay = ((i * (animDuration / arrowCount)) + (direction === \"both\" ? animDuration / (arrowCount * 2) : 0)) + \"s\";\n            arrow.classList.add(\"edge-arrow-backward\");\n            svg.appendChild(arrow);\n           }\n          }\n         }\n         const polyHit = document.createElementNS(ns, \"polyline\");\n         polyHit.setAttribute(\"points\", ptsStr);\n         polyHit.style.fill = \"none\";\n         polyHit.style.stroke = \"transparent\";\n         polyHit.style.strokeWidth = \"20\";\n         polyHit.style.cursor = \"pointer\";\n         polyHit.dataset.edgeId = edge.id;\n         polyHit.addEventListener(\"click\", (e) => {\n          e.stopPropagation();\n          selectTheConnection(edge.id);\n         });\n         let edgeTouchStart = null;\n         let edgeTouchMoved = false;\n         polyHit.addEventListener(\"touchstart\", (e) => {\n          edgeTouchStart = Date.now();\n          edgeTouchMoved = false;\n          if (e.touches[0]) {\n           edgeTouchStartX = e.touches[0].clientX;\n           edgeTouchStartY = e.touches[0].clientY;\n          }\n         }, {\n          passive: false\n         });\n         let edgeTouchStartX = 0, edgeTouchStartY = 0;\n         polyHit.addEventListener(\"touchmove\", (e) => {\n          if (e.touches[0]) {\n           const dx = Math.abs(e.touches[0].clientX - edgeTouchStartX);\n           const dy = Math.abs(e.touches[0].clientY - edgeTouchStartY);\n           if (dx > 10 || dy > 10) edgeTouchMoved = true;\n          }\n         }, {\n          passive: false\n         });\n         polyHit.addEventListener(\"touchend\", (e) => {\n          if (edgeTouchStart && !edgeTouchMoved && Date.now() - edgeTouchStart < 400) {\n           e.stopPropagation();\n           e.preventDefault();\n           selectTheConnection(edge.id);\n          }\n          edgeTouchStart = null;\n          edgeTouchMoved = false;\n         }, {\n          passive: false\n         });\n         poly.addEventListener(\"click\", (e) => {\n          e.stopPropagation();\n          selectTheConnection(edge.id);\n         });\n         polyHit.addEventListener(\"contextmenu\", (e) => {\n          e.preventDefault();\n          e.stopPropagation();\n          if (selectedEdges.has(edge.id)) {\n           selectedEdges.delete(edge.id);\n          } else {\n           selectedEdges.add(edge.id);\n          }\n          updateAllSelections();\n         });\n         let customEdgeLastTap = 0;\n         polyHit.addEventListener(\"touchend\", (e) => {\n          const now = Date.now();\n          if (now - customEdgeLastTap < 300) {\n           e.preventDefault();\n           if (selectedEdges.has(edge.id)) {\n            selectedEdges.delete(edge.id);\n           } else {\n            selectedEdges.add(edge.id);\n           }\n           updateAllSelections();\n           if (navigator.vibrate) navigator.vibrate(50);\n           customEdgeLastTap = 0;\n          } else {\n           customEdgeLastTap = now;\n          }\n         }, { passive: false });\n         poly.addEventListener(\"contextmenu\", (e) => {\n          e.preventDefault();\n          e.stopPropagation();\n          if (selectedEdges.has(edge.id)) {\n           selectedEdges.delete(edge.id);\n          } else {\n           selectedEdges.add(edge.id);\n          }\n          updateAllSelections();\n         });\n         let edgeLastTap = 0;\n         poly.addEventListener(\"touchend\", (e) => {\n          const now = Date.now();\n          if (now - edgeLastTap < 300) {\n           e.preventDefault();\n           if (selectedEdges.has(edge.id)) {\n            selectedEdges.delete(edge.id);\n           } else {\n            selectedEdges.add(edge.id);\n           }\n           updateAllSelections();\n           if (navigator.vibrate) navigator.vibrate(50);\n           edgeLastTap = 0;\n          } else {\n           edgeLastTap = now;\n          }\n         });\n         if (currentView.mode === \"rack\") {\n          return;\n         }\n         if (edge.groupId) {\n      const bounds = edge.points.reduce((acc, p) => ({ minX: Math.min(acc.minX, p.x), minY: Math.min(acc.minY, p.y), maxX: Math.max(acc.maxX, p.x), maxY: Math.max(acc.maxY, p.y) }), { minX: Infinity, minY: Infinity, maxX: -Infinity, maxY: -Infinity });\n      const groupIndicator = document.createElementNS(ns, \"rect\");\n      groupIndicator.setAttribute(\"x\", bounds.minX - 8);\n      groupIndicator.setAttribute(\"y\", bounds.minY - 8);\n      groupIndicator.setAttribute(\"width\", bounds.maxX - bounds.minX + 16);\n      groupIndicator.setAttribute(\"height\", bounds.maxY - bounds.minY + 16);\n      groupIndicator.setAttribute(\"rx\", \"4\");\n      groupIndicator.style.fill = \"none\";\n      groupIndicator.style.stroke = \"#4fd1c5\";\n      groupIndicator.style.strokeWidth = \"3\";\n      groupIndicator.style.strokeDasharray = \"5,5\";\n      groupIndicator.style.pointerEvents = \"none\";\n      svg.appendChild(groupIndicator);\n      }\n         let lineDragging = false;\n      let lineDragStartX, lineDragStartY;\n      let linePointsStart = [];\n      polyHit.addEventListener(\"mousedown\", (e) => {\n\t  if (isViewOnly()) return;\n      if (freeDrawMode) return;\n      e.preventDefault();\n      e.stopPropagation();\n      lineDragging = true;\n      lineDragStartX = e.clientX;\n      lineDragStartY = e.clientY;\n      linePointsStart = edge.points.map(p => ({x: p.x, y: p.y}));\n      if (selectedEdges.has(edge.id)) {\n      initialPositions = {};\n      selectedNodes.forEach(nodeId => {\n      const nodePos = savedPositions[nodeId];\n      if (nodePos) initialPositions[nodeId] = { x: nodePos.x, y: nodePos.y };\n      });\n      selectedRects.forEach(rectId => {\n      const r = RECT_DATA.list.find(x => x.id === rectId);\n      if (r) { r._dragStartX = r.x; r._dragStartY = r.y; }\n      });\n      selectedTexts.forEach(textId => {\n      const t = TEXT_DATA.list.find(x => x.id === textId);\n      if (t) { t._dragStartX = t.x; t._dragStartY = t.y; }\n      });\n      selectedEdges.forEach(edgeId => {\n      const ed = EDGE_DATA.list.find(x => x.id === edgeId);\n      if (ed && ed.points) { ed._dragStartPoints = ed.points.map(p => ({x: p.x, y: p.y})); }\n      });\n      }\n      });\n      const lineMoveHandler = (e) => {\n      if (!lineDragging || freeDrawMode) return;\n      const svgEl = svg;\n      const pt1 = svgEl.createSVGPoint();\n      pt1.x = lineDragStartX;\n      pt1.y = lineDragStartY;\n      const svgP1 = pt1.matrixTransform(svgEl.getScreenCTM().inverse());\n      const pt2 = svgEl.createSVGPoint();\n      pt2.x = e.clientX;\n      pt2.y = e.clientY;\n      const svgP2 = pt2.matrixTransform(svgEl.getScreenCTM().inverse());\n      const dx = svgP2.x - svgP1.x;\n      const dy = svgP2.y - svgP1.y;\n      edge.points.forEach((p, i) => { p.x = linePointsStart[i].x + dx; p.y = linePointsStart[i].y + dy; });\n      if (selectedEdges.has(edge.id)) {\n      selectedNodes.forEach(nodeId => {\n      if (initialPositions[nodeId]) {\n       savedPositions[nodeId] = { x: initialPositions[nodeId].x + dx, y: initialPositions[nodeId].y + dy };\n      }\n      });\n      selectedRects.forEach(rectId => {\n      const r = RECT_DATA.list.find(x => x.id === rectId);\n      if (r && r._dragStartX !== undefined) { r.x = r._dragStartX + dx; r.y = r._dragStartY + dy; }\n      });\n      selectedTexts.forEach(textId => {\n      const t = TEXT_DATA.list.find(x => x.id === textId);\n      if (t && t._dragStartX !== undefined) { t.x = t._dragStartX + dx; t.y = t._dragStartY + dy; }\n      });\n      selectedEdges.forEach(edgeId => {\n      if (edgeId === edge.id) return;\n      const ed = EDGE_DATA.list.find(x => x.id === edgeId);\n      if (ed && ed._dragStartPoints) { ed.points.forEach((p, i) => { p.x = ed._dragStartPoints[i].x + dx; p.y = ed._dragStartPoints[i].y + dy; }); }\n      });\n      }\n      forgeTheTopology();\n      };\n      const lineUpHandler = () => {\n        lineDragging = false;\n        document.removeEventListener(\"mousemove\", lineMoveHandler);\n        document.removeEventListener(\"mouseup\", lineUpHandler);\n      };\n      document.addEventListener(\"mousemove\", lineMoveHandler);\n      document.addEventListener(\"mouseup\", lineUpHandler);\n      svg.appendChild(poly);\n      svg.appendChild(polyHit);\n         if (currentEdgeId === edge.id) {\n          edge.points.forEach((p, idx) => {\n           const c = document.createElementNS(ns, \"circle\");\n           c.classList.add(\"edge-edit-point\");\n           c.setAttribute(\"cx\", p.x);\n           c.setAttribute(\"cy\", p.y);\n      c.setAttribute(\"r\", PAGE_STATE.selectionHandleSize || 8);\n           c.style.fill = PAGE_STATE.selectionHandle || \"#f59e0b\";\n           c.style.stroke = \"#fff\";\n           c.style.strokeWidth = \"2\";\n           c.style.cursor = \"move\";\n           c.dataset.edgeId = edge.id;\n           c.dataset.pointIndex = String(idx);\n           c.addEventListener(\"mousedown\", (e) => {\n\t\t   if (isViewOnly()) return;\n            e.preventDefault();\n            e.stopPropagation();\n            pushUndo(\"edit edge point\");\n            let dragging = true;\n            const svgEl = svg;\n            const moveHandler = (ev) => {\n             if (!dragging) return;\n             const pt = svgEl.createSVGPoint();\n             pt.x = ev.clientX;\n             pt.y = ev.clientY;\n             const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n             edge.points[idx].x = svgP.x;\n             edge.points[idx].y = svgP.y;\n             forgeTheTopology();\n            };\n            const upHandler = () => {\n             dragging = false;\n             document.removeEventListener(\"mousemove\", moveHandler);\n             document.removeEventListener(\"mouseup\", upHandler);\n            };\n            document.addEventListener(\"mousemove\", moveHandler);\n            document.addEventListener(\"mouseup\", upHandler);\n           });\n           c.addEventListener(\"touchstart\", (e) => {\n\t\t   if (isViewOnly()) return;\n            e.preventDefault();\n            e.stopPropagation();\n            pushUndo(\"edit edge point\");\n            let dragging = true;\n            const svgEl = svg;\n            const touchMoveHandler = (ev) => {\n             if (!dragging || !ev.touches[0]) return;\n             const pt = svgEl.createSVGPoint();\n             pt.x = ev.touches[0].clientX;\n             pt.y = ev.touches[0].clientY;\n             const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n             edge.points[idx].x = svgP.x;\n             edge.points[idx].y = svgP.y;\n             forgeTheTopology();\n            };\n            const touchUpHandler = () => {\n             dragging = false;\n             document.removeEventListener(\"touchmove\", touchMoveHandler);\n             document.removeEventListener(\"touchend\", touchUpHandler);\n            };\n            document.addEventListener(\"touchmove\", touchMoveHandler);\n            document.addEventListener(\"touchend\", touchUpHandler);\n           }, { passive: false });\n           svg.appendChild(c);\n          });\n         }\n         return;\n        }\n        const p1 = positions[edge.from];\n        const p2 = positions[edge.to];\n        if (!p1 || !p2) return;\n        const pairTotal = edge._pairTotal || 1;\n        const pairIndex = edge._pairIndex || 0;\n        const routing = edge.routing || \"curved\";\n        const waypoints = edge.waypoints || [];\n        let pathD;\n        if (waypoints.length > 0) {\n         const allPoints = [p1, ...waypoints, p2];\n         if (routing === \"straight\") {\n          pathD = `M ${allPoints[0].x} ${allPoints[0].y}`;\n          for (let i = 1; i < allPoints.length; i++) {\n           pathD += ` L ${allPoints[i].x} ${allPoints[i].y}`;\n          }\n         } else if (routing === \"orthogonal\") {\n          const GAP = 10;\n          const edgeGaps = orthoGaps[edge.id] || [];\n          const segs = [];\n          for (let i = 1; i < allPoints.length; i++) {\n           const prev = allPoints[i - 1];\n           const curr = allPoints[i];\n           const dx = curr.x - prev.x;\n           const dy = curr.y - prev.y;\n           if (Math.abs(dx) > Math.abs(dy)) {\n            const midX = prev.x + dx / 2;\n            segs.push({ x1: prev.x, y1: prev.y, x2: midX, y2: prev.y });\n            segs.push({ x1: midX, y1: prev.y, x2: midX, y2: curr.y });\n            segs.push({ x1: midX, y1: curr.y, x2: curr.x, y2: curr.y });\n           } else {\n            const midY = prev.y + dy / 2;\n            segs.push({ x1: prev.x, y1: prev.y, x2: prev.x, y2: midY });\n            segs.push({ x1: prev.x, y1: midY, x2: curr.x, y2: midY });\n            segs.push({ x1: curr.x, y1: midY, x2: curr.x, y2: curr.y });\n           }\n          }\n          pathD = `M ${allPoints[0].x} ${allPoints[0].y}`;\n          segs.forEach(seg => {\n           const isHoriz = Math.abs(seg.y1 - seg.y2) < 1;\n           const segGaps = edgeGaps.filter(g => {\n            if (isHoriz) {\n             const minX = Math.min(seg.x1, seg.x2), maxX = Math.max(seg.x1, seg.x2);\n             return Math.abs(g.y - seg.y1) < 1 && g.x > minX + GAP && g.x < maxX - GAP;\n            } else {\n             const minY = Math.min(seg.y1, seg.y2), maxY = Math.max(seg.y1, seg.y2);\n             return Math.abs(g.x - seg.x1) < 1 && g.y > minY + GAP && g.y < maxY - GAP;\n            }\n           }).sort((a, b) => isHoriz ? (seg.x2 > seg.x1 ? a.x - b.x : b.x - a.x) : (seg.y2 > seg.y1 ? a.y - b.y : b.y - a.y));\n           let cx = seg.x1, cy = seg.y1;\n           segGaps.forEach(g => {\n            if (isHoriz) {\n             const gx = g.x;\n             pathD += ` L ${gx - GAP} ${cy} M ${gx + GAP} ${cy}`;\n             cx = gx + GAP;\n            } else {\n             const gy = g.y;\n             pathD += ` L ${cx} ${gy - GAP} M ${cx} ${gy + GAP}`;\n             cy = gy + GAP;\n            }\n           });\n           pathD += ` L ${seg.x2} ${seg.y2}`;\n          });\n         } else {\n          if (allPoints.length === 2) {\n           pathD = `M ${allPoints[0].x} ${allPoints[0].y} L ${allPoints[1].x} ${allPoints[1].y}`;\n          } else if (allPoints.length === 3) {\n           pathD = `M ${allPoints[0].x} ${allPoints[0].y} Q ${allPoints[1].x} ${allPoints[1].y} ${allPoints[2].x} ${allPoints[2].y}`;\n          } else {\n           pathD = `M ${allPoints[0].x} ${allPoints[0].y}`;\n           for (let i = 1; i < allPoints.length - 1; i++) {\n            const prev = allPoints[i - 1];\n            const curr = allPoints[i];\n            const next = allPoints[i + 1];\n            const cpX = curr.x;\n            const cpY = curr.y;\n            const endX = (curr.x + next.x) / 2;\n            const endY = (curr.y + next.y) / 2;\n            if (i === 1) {\n             pathD += ` Q ${cpX} ${cpY} ${endX} ${endY}`;\n            } else {\n             pathD += ` T ${endX} ${endY}`;\n            }\n           }\n           const last = allPoints[allPoints.length - 1];\n           const secondLast = allPoints[allPoints.length - 2];\n           pathD += ` Q ${secondLast.x} ${secondLast.y} ${last.x} ${last.y}`;\n          }\n         }\n        } else if (routing === \"straight\") {\n         pathD = `M ${p1.x} ${p1.y} L ${p2.x} ${p2.y}`;\n        } else if (routing === \"orthogonal\") {\n         const dx = p2.x - p1.x;\n         const dy = p2.y - p1.y;\n         const offset = (pairIndex - (pairTotal - 1) / 2) * 40;\n         const GAP = 10;\n         const edgeGaps = orthoGaps[edge.id] || [];\n\n         if (Math.abs(dx) > Math.abs(dy)) {\n          const midX = p1.x + dx / 2 + offset;\n          const segs = [\n           { x1: p1.x, y1: p1.y, x2: midX, y2: p1.y },\n           { x1: midX, y1: p1.y, x2: midX, y2: p2.y },\n           { x1: midX, y1: p2.y, x2: p2.x, y2: p2.y }\n          ];\n          pathD = `M ${p1.x} ${p1.y}`;\n          segs.forEach(seg => {\n           const isHoriz = Math.abs(seg.y1 - seg.y2) < 1;\n           const segGaps = edgeGaps.filter(g => {\n            if (isHoriz) {\n             const minX = Math.min(seg.x1, seg.x2), maxX = Math.max(seg.x1, seg.x2);\n             return Math.abs(g.y - seg.y1) < 1 && g.x > minX + GAP && g.x < maxX - GAP;\n            } else {\n             const minY = Math.min(seg.y1, seg.y2), maxY = Math.max(seg.y1, seg.y2);\n             return Math.abs(g.x - seg.x1) < 1 && g.y > minY + GAP && g.y < maxY - GAP;\n            }\n           }).sort((a, b) => isHoriz ? (seg.x2 > seg.x1 ? a.x - b.x : b.x - a.x) : (seg.y2 > seg.y1 ? a.y - b.y : b.y - a.y));\n           let cx = seg.x1, cy = seg.y1;\n           segGaps.forEach(g => {\n            if (isHoriz) {\n             const gx = g.x;\n             pathD += ` L ${gx - GAP} ${cy} M ${gx + GAP} ${cy}`;\n             cx = gx + GAP;\n            } else {\n             const gy = g.y;\n             pathD += ` L ${cx} ${gy - GAP} M ${cx} ${gy + GAP}`;\n             cy = gy + GAP;\n            }\n           });\n           pathD += ` L ${seg.x2} ${seg.y2}`;\n          });\n         } else {\n          const midY = p1.y + dy / 2 + offset;\n          const segs = [\n           { x1: p1.x, y1: p1.y, x2: p1.x, y2: midY },\n           { x1: p1.x, y1: midY, x2: p2.x, y2: midY },\n           { x1: p2.x, y1: midY, x2: p2.x, y2: p2.y }\n          ];\n          pathD = `M ${p1.x} ${p1.y}`;\n          segs.forEach(seg => {\n           const isHoriz = Math.abs(seg.y1 - seg.y2) < 1;\n           const segGaps = edgeGaps.filter(g => {\n            if (isHoriz) {\n             const minX = Math.min(seg.x1, seg.x2), maxX = Math.max(seg.x1, seg.x2);\n             return Math.abs(g.y - seg.y1) < 1 && g.x > minX + GAP && g.x < maxX - GAP;\n            } else {\n             const minY = Math.min(seg.y1, seg.y2), maxY = Math.max(seg.y1, seg.y2);\n             return Math.abs(g.x - seg.x1) < 1 && g.y > minY + GAP && g.y < maxY - GAP;\n            }\n           }).sort((a, b) => isHoriz ? (seg.x2 > seg.x1 ? a.x - b.x : b.x - a.x) : (seg.y2 > seg.y1 ? a.y - b.y : b.y - a.y));\n           let cx = seg.x1, cy = seg.y1;\n           segGaps.forEach(g => {\n            if (isHoriz) {\n             const gx = g.x;\n             pathD += ` L ${gx - GAP} ${cy} M ${gx + GAP} ${cy}`;\n             cx = gx + GAP;\n            } else {\n             const gy = g.y;\n             pathD += ` L ${cx} ${gy - GAP} M ${cx} ${gy + GAP}`;\n             cy = gy + GAP;\n            }\n           });\n           pathD += ` L ${seg.x2} ${seg.y2}`;\n          });\n         }\n        } else {\n         const midX = (p1.x + p2.x) / 2;\n         const midY = (p1.y + p2.y) / 2;\n         const dx = p2.x - p1.x;\n         const dy = p2.y - p1.y;\n         const len = Math.sqrt(dx * dx + dy * dy) || 1;\n         const perpX = -dy / len;\n         const perpY = dx / len;\n         let offsetAmount = 0;\n         if (pairTotal > 1) {\n          offsetAmount = (pairIndex - (pairTotal - 1) / 2) * 40;\n         }\n         const ctrlX = midX + perpX * offsetAmount;\n         const ctrlY = midY + perpY * offsetAmount;\n         pathD = `M ${p1.x} ${p1.y} Q ${ctrlX} ${ctrlY} ${p2.x} ${p2.y}`;\n        }\n        const path = document.createElementNS(ns, \"path\");\n        path.setAttribute(\"d\", pathD);\n        path.setAttribute(\"fill\", \"none\");\n        path.classList.add(\"edge\");\n        if (edge.type === \"backup\") path.classList.add(\"backup\");\n        path.dataset.edgeId = edge.id;\n        path.dataset.from = edge.from;\n        path.dataset.to = edge.to;\n        path.style.stroke = edge.color;\n        path.style.strokeWidth = edge.width;\n        const edgeDirection = edge.direction || \"none\";\n        const edgeLineStyle = edge.lineStyle || \"solid\";\n        if (edgeLineStyle === \"dashed\") { path.style.strokeDasharray = \"10,5\"; }\n        else if (edgeLineStyle === \"dotted\") { path.style.strokeDasharray = \"2,4\"; }\n        if (edgeDirection === \"forward\") {\n         path.setAttribute(\"marker-end\", \"url(#arrow-forward)\");\n        } else if (edgeDirection === \"backward\") {\n         path.setAttribute(\"marker-start\", \"url(#arrow-backward)\");\n        } else if (edgeDirection === \"both\") {\n         path.setAttribute(\"marker-start\", \"url(#arrow-backward)\");\n         path.setAttribute(\"marker-end\", \"url(#arrow-forward)\");\n        }\n        const animDir = PAGE_STATE.animationDirection || \"all\";\n        const shouldAnimate = (PAGE_STATE.animateConnections && edge.animate !== false || edge.animate === true) && edgeDirection !== \"none\" && (animDir === \"all\" || animDir === edgeDirection);\n        if (shouldAnimate) {\n         path.style.opacity = \"0.25\";\n         const edgeStyle = edge.animationStyle || PAGE_STATE.animationStyle || \"arrows\";\n         const arrowId = edgeStyle === \"dots\" ? \"#flow-arrow-small\" : \"#flow-arrow-big\";\n         const arrowColor = edge.color || PAGE_STATE.defaultEdge || \"#475569\";\n         const arrowCount = 3;\n         const animDuration = edge.animationSpeed ? parseFloat(edge.animationSpeed) : (PAGE_STATE.animationSpeed || 1.5);\n         if (edgeDirection === \"forward\" || edgeDirection === \"both\") {\n          for (let i = 0; i < arrowCount; i++) {\n           const arrow = document.createElementNS(ns, \"use\");\n           arrow.setAttribute(\"href\", arrowId);\n           arrow.style.fill = arrowColor;\n           arrow.style.offsetPath = `path('${pathD}')`;\n           arrow.style.animationDuration = animDuration + \"s\";\n           arrow.style.animationDelay = (i * (animDuration / arrowCount)) + \"s\";\n           arrow.classList.add(\"edge-arrow-forward\");\n           svg.appendChild(arrow);\n          }\n         }\n         if (edgeDirection === \"backward\" || edgeDirection === \"both\") {\n          for (let i = 0; i < arrowCount; i++) {\n           const arrow = document.createElementNS(ns, \"use\");\n           arrow.setAttribute(\"href\", arrowId);\n           arrow.style.fill = arrowColor;\n           arrow.style.offsetPath = `path('${pathD}')`;\n           arrow.style.animationDuration = animDuration + \"s\";\n           arrow.style.animationDelay = ((i * (animDuration / arrowCount)) + (edgeDirection === \"both\" ? animDuration / (arrowCount * 2) : 0)) + \"s\";\n           arrow.classList.add(\"edge-arrow-backward\");\n           svg.appendChild(arrow);\n          }\n         }\n        }\n        const pathHit = document.createElementNS(ns, \"path\");\n        pathHit.setAttribute(\"d\", pathD);\n        pathHit.setAttribute(\"fill\", \"none\");\n        pathHit.style.stroke = \"transparent\";\n        pathHit.style.strokeWidth = \"20\";\n        pathHit.style.cursor = \"pointer\";\n        pathHit.dataset.edgeId = edge.id;\n        pathHit.addEventListener(\"click\", (e) => {\n         e.stopPropagation();\n         selectTheConnection(edge.id);\n        });\n        let pathTouchStart = null;\n        let pathTouchMoved = false;\n        pathHit.addEventListener(\"touchstart\", (e) => {\n         pathTouchStart = Date.now();\n         pathTouchMoved = false;\n         if (e.touches[0]) {\n          pathTouchStartX = e.touches[0].clientX;\n          pathTouchStartY = e.touches[0].clientY;\n         }\n        }, {\n         passive: false\n        });\n        let pathTouchStartX = 0, pathTouchStartY = 0;\n        pathHit.addEventListener(\"touchmove\", (e) => {\n         if (e.touches[0]) {\n          const dx = Math.abs(e.touches[0].clientX - pathTouchStartX);\n          const dy = Math.abs(e.touches[0].clientY - pathTouchStartY);\n          if (dx > 10 || dy > 10) pathTouchMoved = true;\n         }\n        }, {\n         passive: false\n        });\n        pathHit.addEventListener(\"touchend\", (e) => {\n         if (pathTouchStart && !pathTouchMoved && Date.now() - pathTouchStart < 400) {\n          e.stopPropagation();\n          e.preventDefault();\n          selectTheConnection(edge.id);\n         }\n         pathTouchStart = null;\n         pathTouchMoved = false;\n        }, {\n         passive: false\n        });\n        path.addEventListener(\"click\", (e) => {\n         e.stopPropagation();\n         selectTheConnection(edge.id);\n        });\n        const fromVisible = isNodeVisible(edge.from);\n        const toVisible = isNodeVisible(edge.to);\n        if (!fromVisible || !toVisible) {\n         path.style.opacity = \"0.1\";\n         path.style.pointerEvents = \"none\";\n         pathHit.style.pointerEvents = \"none\";\n        }\n        svg.appendChild(path);\n        svg.appendChild(pathHit);\n        if (PAGE_STATE.showPortLabels && (edge.fromPort || edge.toPort)) {\n         const plNs = \"http://www.w3.org/2000/svg\";\n         const plWaypoints = edge.waypoints || [];\n         const plPad = { x: 5, y: 3 };\n         function plHitsAny(px, py, halfW, halfH) {\n          for (const nid in positions) {\n           const np = positions[nid];\n           if (!np) continue;\n           const nr = (savedSizes[nid] || 55) + 8;\n           const nd = NODE_DATA[nid];\n           const tFs = PAGE_STATE.nodeTitleSize || 18;\n           const sFs = PAGE_STATE.nodeSubSize || 13;\n           if (px + halfW > np.x - nr && px - halfW < np.x + nr && py + halfH > np.y - nr && py - halfH < np.y + nr) return true;\n           if (nd && nd.name) {\n            const tw = (nd.name.length * tFs * 0.38) + 6;\n            const ty = np.y - nr - 5 - tFs / 2;\n            const th = tFs / 2 + 6;\n            if (px + halfW > np.x - tw && px - halfW < np.x + tw && py + halfH > ty - th && py - halfH < ty + th) return true;\n           }\n           if (nd && nd.subtitle) {\n            const sw = (nd.subtitle.length * sFs * 0.38) + 6;\n            const sy = np.y + nr + 5 + sFs / 2;\n            const sh = sFs / 2 + 6;\n            if (px + halfW > np.x - sw && px - halfW < np.x + sw && py + halfH > sy - sh && py - halfH < sy + sh) return true;\n           }\n          }\n          for (const pl of _placedPortLabels) {\n           if (Math.abs(px - pl.x) < (halfW + pl.hw + 4) && Math.abs(py - pl.y) < (halfH + pl.hh + 4)) return true;\n          }\n          return false;\n         }\n         function plFindClear(origin, target, nodeId, fromEnd) {\n          const wps = fromEnd ? plWaypoints : [...plWaypoints].reverse();\n          const pts = [origin, ...wps, target];\n          const estW = 40, estH = 12;\n          for (let t = 0.12; t <= 0.5; t += 0.04) {\n           const totalLen = pts.reduce((s, pt, i) => i === 0 ? 0 : s + Math.sqrt((pt.x - pts[i-1].x)**2 + (pt.y - pts[i-1].y)**2), 0);\n           let target_d = totalLen * t, accum = 0;\n           for (let i = 1; i < pts.length; i++) {\n            const dx = pts[i].x - pts[i-1].x, dy = pts[i].y - pts[i-1].y;\n            const segLen = Math.sqrt(dx*dx + dy*dy);\n            if (segLen === 0) continue;\n            if (accum + segLen >= target_d) {\n             const frac = (target_d - accum) / segLen;\n             const cx = pts[i-1].x + dx * frac, cy = pts[i-1].y + dy * frac;\n             const fHitsOnPath = plHitsAny(cx, cy, estW, estH);\n             if (!fHitsOnPath) return { x: cx, y: cy };\n             const nx = -dy / segLen, ny = dx / segLen;\n             for (const off of [22, -22, 36, -36]) {\n              const ox = cx + nx * off, oy = cy + ny * off;\n              if (!plHitsAny(ox, oy, estW, estH)) return { x: ox, y: oy };\n             }\n            }\n            accum += segLen;\n           }\n          }\n          const mx = (origin.x + target.x) / 2, my = (origin.y + target.y) / 2;\n          const dx = target.x - origin.x, dy = target.y - origin.y;\n          const fLen = Math.sqrt(dx*dx + dy*dy) || 1;\n          const perpX = -dy / fLen, perpY = dx / fLen;\n          for (const fo of [0, 20, -20, 40, -40]) {\n           const fx = mx + perpX * fo, fy = my - 15 + perpY * fo;\n           if (!plHitsAny(fx, fy, 40, 12)) return { x: fx, y: fy };\n          }\n          return { x: mx, y: my - 15 };\n         }\n         function plRenderLabel(text, pos) {\n          const g = document.createElementNS(plNs, \"g\");\n          g.classList.add(\"port-label\");\n          g.style.pointerEvents = \"none\";\n          const tmp = document.createElementNS(plNs, \"text\");\n          tmp.textContent = text;\n          tmp.setAttribute(\"x\", pos.x);\n          tmp.setAttribute(\"y\", pos.y);\n          tmp.setAttribute(\"text-anchor\", \"middle\");\n          tmp.setAttribute(\"dominant-baseline\", \"central\");\n          tmp.style.fontSize = \"13px\";\n          tmp.style.fontFamily = \"Inter, system-ui, sans-serif\";\n          tmp.style.fontWeight = \"600\";\n          svg.appendChild(tmp);\n          const bbox = tmp.getBBox();\n          tmp.remove();\n          const bg = document.createElementNS(plNs, \"rect\");\n          bg.setAttribute(\"x\", bbox.x - plPad.x);\n          bg.setAttribute(\"y\", bbox.y - plPad.y);\n          bg.setAttribute(\"width\", bbox.width + plPad.x * 2);\n          bg.setAttribute(\"height\", bbox.height + plPad.y * 2);\n          bg.setAttribute(\"rx\", 4);\n          bg.setAttribute(\"ry\", 4);\n          bg.style.fill = \"rgba(15, 23, 42, 0.88)\";\n          bg.style.stroke = edge.color || PAGE_STATE.defaultEdge || \"#475569\";\n          bg.style.strokeWidth = \"1\";\n          bg.style.strokeOpacity = \"0.5\";\n          g.appendChild(bg);\n          const label = document.createElementNS(plNs, \"text\");\n          label.textContent = text;\n          label.setAttribute(\"x\", pos.x);\n          label.setAttribute(\"y\", pos.y);\n          label.setAttribute(\"text-anchor\", \"middle\");\n          label.setAttribute(\"dominant-baseline\", \"central\");\n          label.style.fill = \"#e2e8f0\";\n          label.style.fontSize = \"13px\";\n          label.style.fontFamily = \"Inter, system-ui, sans-serif\";\n          label.style.fontWeight = \"600\";\n          label.style.pointerEvents = \"none\";\n          g.appendChild(label);\n          svg.appendChild(g);\n          _placedPortLabels.push({ x: pos.x, y: pos.y, hw: (bbox.width / 2) + plPad.x, hh: (bbox.height / 2) + plPad.y });\n         }\n         if (edge.fromPort) {\n          const fromPos = plFindClear(p1, p2, edge.from, true);\n          plRenderLabel(edge.fromPort, fromPos);\n         }\n         if (edge.toPort) {\n          const toPos = plFindClear(p2, p1, edge.to, false);\n          plRenderLabel(edge.toPort, toPos);\n         }\n        }\n        if (currentEdgeId === edge.id && !isViewOnly()) {\n         const waypoints = edge.waypoints || [];\n         waypoints.forEach((wp, idx) => {\n          const c = document.createElementNS(ns, \"circle\");\n          c.classList.add(\"waypoint-handle\");\n          c.setAttribute(\"cx\", wp.x);\n          c.setAttribute(\"cy\", wp.y);\n          c.setAttribute(\"r\", 8);\n          c.style.fill = \"#3b82f6\";\n          c.style.stroke = \"#fff\";\n          c.style.strokeWidth = \"2\";\n          c.style.cursor = \"grab\";\n          c.addEventListener(\"mousedown\", (e) => {\n           if (isViewOnly()) return;\n           e.preventDefault();\n           e.stopPropagation();\n           pushUndo(\"move waypoint\");\n           let dragging = true;\n           const svgEl = svg;\n           c.style.cursor = \"grabbing\";\n           const moveHandler = (ev) => {\n            if (!dragging) return;\n            const pt = svgEl.createSVGPoint();\n            pt.x = ev.clientX;\n            pt.y = ev.clientY;\n            const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n            edge.waypoints[idx].x = svgP.x;\n            edge.waypoints[idx].y = svgP.y;\n            forgeTheTopology();\n           };\n           const upHandler = () => {\n            dragging = false;\n            c.style.cursor = \"grab\";\n            document.removeEventListener(\"mousemove\", moveHandler);\n            document.removeEventListener(\"mouseup\", upHandler);\n           };\n           document.addEventListener(\"mousemove\", moveHandler);\n           document.addEventListener(\"mouseup\", upHandler);\n          });\n          let waypointMoved = false;\n          c.addEventListener(\"touchstart\", (e) => {\n           if (isViewOnly()) return;\n           e.preventDefault();\n           e.stopPropagation();\n           waypointMoved = false;\n           pushUndo(\"move waypoint\");\n           let dragging = true;\n           const svgEl = svg;\n           const touchMoveHandler = (ev) => {\n            if (!dragging || !ev.touches[0]) return;\n            waypointMoved = true;\n            const pt = svgEl.createSVGPoint();\n            pt.x = ev.touches[0].clientX;\n            pt.y = ev.touches[0].clientY;\n            const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n            edge.waypoints[idx].x = svgP.x;\n            edge.waypoints[idx].y = svgP.y;\n            forgeTheTopology();\n           };\n           const touchUpHandler = () => {\n            dragging = false;\n            document.removeEventListener(\"touchmove\", touchMoveHandler);\n            document.removeEventListener(\"touchend\", touchUpHandler);\n           };\n           document.addEventListener(\"touchmove\", touchMoveHandler);\n           document.addEventListener(\"touchend\", touchUpHandler);\n          }, { passive: false });\n          c.addEventListener(\"contextmenu\", (e) => {\n           if (isViewOnly()) return;\n           e.preventDefault();\n           e.stopPropagation();\n           pushUndo(\"delete waypoint\");\n           edge.waypoints.splice(idx, 1);\n           forgeTheTopology();\n          });\n          let lastTap = 0;\n          c.addEventListener(\"touchend\", (e) => {\n           if (waypointMoved) {\n            waypointMoved = false;\n            lastTap = 0;\n            return;\n           }\n           const now = Date.now();\n           if (now - lastTap < 300) {\n            if (isViewOnly()) return;\n            e.preventDefault();\n            pushUndo(\"delete waypoint\");\n            edge.waypoints.splice(idx, 1);\n            forgeTheTopology();\n           }\n           lastTap = now;\n          });\n\n          svg.appendChild(c);\n         });\n        }\n        pathHit.addEventListener(\"dblclick\", (e) => {\n         if (isViewOnly()) return;\n         e.stopPropagation();\n         const svgEl = svg;\n         const pt = svgEl.createSVGPoint();\n         pt.x = e.clientX;\n         pt.y = e.clientY;\n         const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n         pushUndo(\"add waypoint\");\n         if (!edge.waypoints) edge.waypoints = [];\n         const newWp = { x: svgP.x, y: svgP.y };\n         if (edge.waypoints.length === 0) {\n          edge.waypoints.push(newWp);\n         } else {\n          const allPts = [p1, ...edge.waypoints, p2];\n          let bestIdx = 0;\n          let bestDist = Infinity;\n          for (let i = 0; i < allPts.length - 1; i++) {\n           const segMidX = (allPts[i].x + allPts[i + 1].x) / 2;\n           const segMidY = (allPts[i].y + allPts[i + 1].y) / 2;\n           const dist = Math.hypot(svgP.x - segMidX, svgP.y - segMidY);\n           if (dist < bestDist) {\n            bestDist = dist;\n            bestIdx = i;\n           }\n          }\n          edge.waypoints.splice(bestIdx, 0, newWp);\n         }\n         selectTheConnection(edge.id);\n         forgeTheTopology();\n        });\n        let longPressTimer = null;\n        let longPressTriggered = false;\n        pathHit.addEventListener(\"touchstart\", (e) => {\n         if (isViewOnly() || !e.touches[0]) return;\n         longPressTriggered = false;\n         const touch = e.touches[0];\n         const startX = touch.clientX;\n         const startY = touch.clientY;\n         longPressTimer = setTimeout(() => {\n          longPressTriggered = true;\n          if (navigator.vibrate) navigator.vibrate(50);\n          const svgEl = svg;\n          const pt = svgEl.createSVGPoint();\n          pt.x = startX;\n          pt.y = startY;\n          const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n          pushUndo(\"add waypoint\");\n          if (!edge.waypoints) edge.waypoints = [];\n          const newWp = { x: svgP.x, y: svgP.y };\n          if (edge.waypoints.length === 0) {\n           edge.waypoints.push(newWp);\n          } else {\n           const allPts = [p1, ...edge.waypoints, p2];\n           let bestIdx = 0;\n           let bestDist = Infinity;\n           for (let i = 0; i < allPts.length - 1; i++) {\n            const segMidX = (allPts[i].x + allPts[i + 1].x) / 2;\n            const segMidY = (allPts[i].y + allPts[i + 1].y) / 2;\n            const dist = Math.hypot(svgP.x - segMidX, svgP.y - segMidY);\n            if (dist < bestDist) {\n             bestDist = dist;\n             bestIdx = i;\n            }\n           }\n           edge.waypoints.splice(bestIdx, 0, newWp);\n          }\n          selectTheConnection(edge.id);\n          forgeTheTopology();\n         }, 500);\n        }, { passive: true });\n        pathHit.addEventListener(\"touchmove\", () => {\n         if (longPressTimer) {\n          clearTimeout(longPressTimer);\n          longPressTimer = null;\n         }\n        }, { passive: true });\n        pathHit.addEventListener(\"touchend\", () => {\n         if (longPressTimer) {\n          clearTimeout(longPressTimer);\n          longPressTimer = null;\n         }\n        }, { passive: true });\n       });\n       Object.entries(positions).forEach(([id, pos]) => {\n        const node = NODE_DATA[id];\n        if (!node) return;\n        if (currentView.mode === \"rack\") {\n         if (node.assignedRack !== currentView.rackId) return;\n        } else {\n         if (node.assignedRack) return;\n        }\n        const g = document.createElementNS(\"http://www.w3.org/2000/svg\", \"g\");\n        g.classList.add(\"node-group\");\n        g.dataset.nodeId = id;\n        const nodeRotation = NODE_DATA[id].rotation || 0;\n        g.setAttribute(\"transform\", `translate(${pos.x},${pos.y}) rotate(${nodeRotation})`);\n\t\tlet r = savedSizes[id] || 55;\n\t\tif (!savedSizes[id]) {\n\t\t if (window.innerWidth <= 480) r = 45;\n\t\t else if (window.innerWidth <= 768) r = 50;\n\t\t}\n        const styles = resolveStylesForNode(id);\n        const ns = \"http://www.w3.org/2000/svg\";\n        const hitArea = document.createElementNS(ns, \"circle\");\n        hitArea.setAttribute(\"r\", r * 1.5);\n        hitArea.style.fill = \"transparent\";\n        hitArea.style.stroke = \"none\";\n        hitArea.style.cursor = \"grab\";\n        hitArea.classList.add(\"node-hit-area\");\n        const shapeEl = createNodeShape(id, r);\n        const titleOffsetX = styles.titleOffsetX || 0;\n        const titleOffsetY = styles.titleOffsetY || 0;\n        const subOffsetX = styles.subOffsetX || 0;\n        const subOffsetY = styles.subOffsetY || 0;\n        const label = document.createElementNS(\"http://www.w3.org/2000/svg\", \"text\");\n        label.classList.add(\"node-label\");\n        label.setAttribute(\"x\", titleOffsetX);\n        label.setAttribute(\"y\", -r - 5 + titleOffsetY);\n      const labelFontSize = styles.titleSize || PAGE_STATE.nodeTitleSize || r * 0.33;\n        label.style.fontSize = labelFontSize + \"px\";\n        label.textContent = NODE_DATA[id].name;\n      label.style.fill = styles.titleColor || PAGE_STATE.nodeTitle || \"#e2e8f0\";\n        label.style.fontFamily = styles.titleFont || PAGE_STATE.nodeFont || \"Inter, system-ui, sans-serif\";\n        label.style.pointerEvents = \"none\";\n        const sub = document.createElementNS(\"http://www.w3.org/2000/svg\", \"text\");\n        sub.classList.add(\"node-sub\");\n        sub.setAttribute(\"x\", subOffsetX);\n        sub.setAttribute(\"y\", r + 20 + subOffsetY);\n      const subFontSize = styles.subSize || PAGE_STATE.nodeSubSize || r * 0.24;\n        sub.style.fontSize = subFontSize + \"px\";\n        sub.textContent = NODE_DATA[id].ip;\n      sub.style.fill = styles.subColor || PAGE_STATE.nodeSub || \"#94a3b8\";\n        sub.style.fontFamily = styles.subFont || PAGE_STATE.nodeFont || \"Inter, system-ui, sans-serif\";\n        sub.style.pointerEvents = \"none\";\nif (hasCoverageZone(node.shape) && node.fovEnabled) {\n  const defaults = getCoverageDefaults(node.shape);\n  const fovAngle = node.fovAngle || defaults.angle;\n  const fovDistance = node.fovDistance || defaults.distance;\n  const fovInnerRadius = node.fovInnerRadius || 0;\n  const fovRotation = node.fovRotation || 0;\n  const fovColor = node.fovColor || \"#f59e0b\";\n  const fovOpacity = node.fovOpacity || 20;\n  const fovGradient = node.fovGradient || false;\n  const fovBorderColor = node.fovBorderColor || \"#f59e0b\";\n  const fovBorderWidth = node.fovBorderWidth ?? 2;\n  const fovBorderStyle = node.fovBorderStyle || \"solid\";\n  const fovBorderOpacity = node.fovBorderOpacity ?? 100;\n  const fovLabel = node.fovLabel || \"\";\n  const fovAnimate = node.fovAnimate || false;\n  const fovAnimationType = node.fovAnimationType || defaults.animationType;\n  const fovSweep = node.fovSweep || 120;\n  const fovSpeed = node.fovSpeed || 4;\n  \n  const fovGroup = document.createElementNS(ns, \"g\");\n  fovGroup.classList.add(\"fov-group\");\n  \n  if (fovGradient) {\n    const gradientId = `fov-gradient-${id}`;\n    const defs = document.createElementNS(ns, \"defs\");\n    const gradient = document.createElementNS(ns, \"radialGradient\");\n    gradient.id = gradientId;\n    gradient.setAttribute(\"cx\", \"0\");\n    gradient.setAttribute(\"cy\", \"0\");\n    gradient.setAttribute(\"r\", fovDistance);\n    gradient.setAttribute(\"gradientUnits\", \"userSpaceOnUse\");\n    const stop1 = document.createElementNS(ns, \"stop\");\n    stop1.setAttribute(\"offset\", fovInnerRadius / fovDistance);\n    stop1.setAttribute(\"stop-color\", fovColor);\n    stop1.setAttribute(\"stop-opacity\", fovOpacity / 100);\n    const stop2 = document.createElementNS(ns, \"stop\");\n    stop2.setAttribute(\"offset\", \"1\");\n    stop2.setAttribute(\"stop-color\", fovColor);\n    stop2.setAttribute(\"stop-opacity\", \"0\");\n    gradient.appendChild(stop1);\n    gradient.appendChild(stop2);\n    defs.appendChild(gradient);\n    fovGroup.appendChild(defs);\n  }\n  \n  const fovPath = document.createElementNS(ns, \"path\");\n  \n  if (fovAngle >= 360) {\n    if (fovInnerRadius > 0) {\n      fovPath.setAttribute(\"d\", `M ${fovDistance},0 A ${fovDistance},${fovDistance} 0 1,1 ${-fovDistance},0 A ${fovDistance},${fovDistance} 0 1,1 ${fovDistance},0 M ${fovInnerRadius},0 A ${fovInnerRadius},${fovInnerRadius} 0 1,0 ${-fovInnerRadius},0 A ${fovInnerRadius},${fovInnerRadius} 0 1,0 ${fovInnerRadius},0`);\n      fovPath.setAttribute(\"fill-rule\", \"evenodd\");\n    } else {\n      fovPath.setAttribute(\"d\", `M 0,0 m -${fovDistance},0 a ${fovDistance},${fovDistance} 0 1,0 ${fovDistance * 2},0 a ${fovDistance},${fovDistance} 0 1,0 -${fovDistance * 2},0`);\n    }\n  } else {\n    const angleRad = (fovAngle * Math.PI) / 180;\n    const rotationRad = ((fovRotation - 90) * Math.PI) / 180;\n    const startAngle = rotationRad - angleRad / 2;\n    const endAngle = rotationRad + angleRad / 2;\n    const x1 = Math.cos(startAngle) * fovDistance;\n    const y1 = Math.sin(startAngle) * fovDistance;\n    const x2 = Math.cos(endAngle) * fovDistance;\n    const y2 = Math.sin(endAngle) * fovDistance;\n    const largeArc = fovAngle > 180 ? 1 : 0;\n    \n    if (fovInnerRadius > 0) {\n      const ix1 = Math.cos(startAngle) * fovInnerRadius;\n      const iy1 = Math.sin(startAngle) * fovInnerRadius;\n      const ix2 = Math.cos(endAngle) * fovInnerRadius;\n      const iy2 = Math.sin(endAngle) * fovInnerRadius;\n      fovPath.setAttribute(\"d\", `M ${ix1},${iy1} L ${x1},${y1} A ${fovDistance},${fovDistance} 0 ${largeArc},1 ${x2},${y2} L ${ix2},${iy2} A ${fovInnerRadius},${fovInnerRadius} 0 ${largeArc},0 ${ix1},${iy1} Z`);\n    } else {\n      fovPath.setAttribute(\"d\", `M 0,0 L ${x1},${y1} A ${fovDistance},${fovDistance} 0 ${largeArc},1 ${x2},${y2} Z`);\n    }\n  }\n  \n  if (fovGradient) {\n    fovPath.style.fill = `url(#fov-gradient-${id})`;\n  } else {\n    const opacityHex = Math.round((fovOpacity / 100) * 255).toString(16).padStart(2, '0');\n    fovPath.style.fill = fovColor + opacityHex;\n  }\n  \n  const borderOpacityHex = Math.round((fovBorderOpacity / 100) * 255).toString(16).padStart(2, '0');\n  fovPath.style.stroke = fovBorderColor + borderOpacityHex;\n  fovPath.style.strokeWidth = fovBorderWidth;\n  if (fovBorderStyle === \"dashed\") {\n    fovPath.style.strokeDasharray = \"10,5\";\n  } else if (fovBorderStyle === \"dotted\") {\n    fovPath.style.strokeDasharray = \"3,3\";\n  }\n  fovPath.style.pointerEvents = \"none\";\n  fovPath.classList.add(\"fov-cone\");\n  \n  fovGroup.appendChild(fovPath);\n  \n  if (fovLabel) {\n    const fovLabelPosition = node.fovLabelPosition || \"center\";\n    const fovLabelSize = node.fovLabelSize || 14;\n    const fovLabelColor = node.fovLabelColor || \"#ffffff\";\n    const fovLabelBold = node.fovLabelBold || false;\n    const fovLabelBg = node.fovLabelBg || false;\n    const fovLabelBgColor = node.fovLabelBgColor || \"#000000\";\n    \n    let labelDistance;\n    if (fovLabelPosition === \"center\") {\n      labelDistance = fovAngle >= 360 ? 0 : fovDistance * 0.4;\n    } else if (fovLabelPosition === \"edge\") {\n      labelDistance = fovDistance * 0.75;\n    } else {\n      labelDistance = fovDistance + fovLabelSize + 8;\n    }\n    \n    const labelAngle = ((fovRotation - 90) * Math.PI) / 180;\n    const labelX = fovAngle >= 360 && fovLabelPosition === \"center\" ? 0 : Math.cos(labelAngle) * labelDistance;\n    const labelY = fovAngle >= 360 && fovLabelPosition === \"center\" ? 0 : Math.sin(labelAngle) * labelDistance;\n    \n    if (fovLabelBg) {\n      const bgRect = document.createElementNS(ns, \"rect\");\n      const textWidth = fovLabel.length * fovLabelSize * 0.6;\n      const textHeight = fovLabelSize * 1.4;\n      bgRect.setAttribute(\"x\", labelX - textWidth / 2 - 6);\n      bgRect.setAttribute(\"y\", labelY - textHeight / 2);\n      bgRect.setAttribute(\"width\", textWidth + 12);\n      bgRect.setAttribute(\"height\", textHeight);\n      bgRect.setAttribute(\"rx\", \"4\");\n      bgRect.style.fill = fovLabelBgColor;\n      bgRect.style.opacity = \"0.8\";\n      bgRect.style.pointerEvents = \"none\";\n      fovGroup.appendChild(bgRect);\n    }\n    \n    const labelEl = document.createElementNS(ns, \"text\");\n    labelEl.setAttribute(\"x\", labelX);\n    labelEl.setAttribute(\"y\", labelY);\n    labelEl.setAttribute(\"text-anchor\", \"middle\");\n    labelEl.setAttribute(\"dominant-baseline\", \"middle\");\n    labelEl.style.fill = fovLabelColor;\n    labelEl.style.fontSize = fovLabelSize + \"px\";\n    labelEl.style.fontWeight = fovLabelBold ? \"bold\" : \"normal\";\n    labelEl.style.fontFamily = \"system-ui, sans-serif\";\n    labelEl.style.pointerEvents = \"none\";\n    labelEl.textContent = fovLabel;\n    fovGroup.appendChild(labelEl);\n  }\n  \n  if (fovAnimate) {\n    const animationName = `fov-anim-${id}`;\n    const styleEl = document.createElementNS(\"http://www.w3.org/1999/xhtml\", \"style\");\n    \n    if (fovAnimationType === \"sweep\" && fovAngle < 360) {\n      styleEl.textContent = `\n        @keyframes ${animationName} {\n          0%, 100% { transform: rotate(0deg); }\n          50% { transform: rotate(${fovSweep}deg); }\n        }\n      `;\n      fovGroup.style.animation = `${animationName} ${fovSpeed}s ease-in-out infinite`;\n      fovGroup.style.transformOrigin = \"0 0\";\n    } else if (fovAnimationType === \"pulse\") {\n      styleEl.textContent = `\n        @keyframes ${animationName} {\n          0%, 100% { transform: scale(1); opacity: 1; }\n          50% { transform: scale(1.1); opacity: 0.7; }\n        }\n      `;\n      fovGroup.style.animation = `${animationName} ${fovSpeed}s ease-in-out infinite`;\n      fovGroup.style.transformOrigin = \"0 0\";\n    } else if (fovAnimationType === \"rings\") {\n      for (let i = 1; i <= 3; i++) {\n        const ring = document.createElementNS(ns, \"circle\");\n        ring.setAttribute(\"cx\", \"0\");\n        ring.setAttribute(\"cy\", \"0\");\n        ring.setAttribute(\"r\", fovDistance * 0.3 * i);\n        ring.style.fill = \"none\";\n        ring.style.stroke = fovBorderColor;\n        ring.style.strokeWidth = \"2\";\n        ring.style.opacity = \"0\";\n        const ringAnimName = `${animationName}-ring-${i}`;\n        const ringStyle = document.createElementNS(\"http://www.w3.org/1999/xhtml\", \"style\");\n        ringStyle.textContent = `\n          @keyframes ${ringAnimName} {\n            0% { r: ${fovInnerRadius || 10}; opacity: 0.8; }\n            100% { r: ${fovDistance}; opacity: 0; }\n          }\n        `;\n        ring.style.animation = `${ringAnimName} ${fovSpeed}s ease-out infinite`;\n        ring.style.animationDelay = `${(i - 1) * (fovSpeed / 3)}s`;\n        fovGroup.appendChild(ringStyle);\n        fovGroup.appendChild(ring);\n      }\n    } else if (fovAnimationType === \"spin\") {\n      styleEl.textContent = `\n        @keyframes ${animationName} {\n          0% { transform: rotate(0deg); }\n          100% { transform: rotate(360deg); }\n        }\n      `;\n      fovGroup.style.animation = `${animationName} ${fovSpeed}s linear infinite`;\n      fovGroup.style.transformOrigin = \"0 0\";\n    }\n    \n    if (fovAnimationType !== \"rings\") {\n      fovGroup.appendChild(styleEl);\n      const elapsedSeconds = (Date.now() - FOV_ANIMATION_START) / 1000;\n      const animationOffset = elapsedSeconds % fovSpeed;\n      fovGroup.style.animationDelay = `-${animationOffset}s`;\n    }\n  }\n  \n  g.appendChild(fovGroup);\n}\n        g.append(hitArea, shapeEl, label);\n        if (NODE_DATA[id].ip && NODE_DATA[id].ip !== \"0.0.0.0\") g.appendChild(sub);\n        if (NODE_DATA[id]?.locked) {\n          const lockIcon = document.createElementNS(ns, \"text\");\n          lockIcon.textContent = \"🔒\";\n          lockIcon.setAttribute(\"x\", r * 0.6);\n          lockIcon.setAttribute(\"y\", -r * 0.6);\n          lockIcon.style.fontSize = (r * 0.4) + \"px\";\n          lockIcon.style.pointerEvents = \"none\";\n          lockIcon.style.userSelect = \"none\";\n          lockIcon.classList.add(\"lock-indicator\");\n          g.appendChild(lockIcon);\n        }\n        if (NODE_DATA[id]?.groupId) {\n          const groupIndicator = document.createElementNS(ns, \"circle\");\n          groupIndicator.setAttribute(\"r\", r + 4);\n          groupIndicator.style.fill = \"none\";\n      groupIndicator.style.stroke = PAGE_STATE.groupIndicator || \"#4fd1c5\";\n          groupIndicator.style.strokeWidth = \"3\";\n          groupIndicator.style.strokeDasharray = \"5,5\";\n          groupIndicator.style.pointerEvents = \"none\";\n          groupIndicator.classList.add(\"group-indicator\");\n          g.insertBefore(groupIndicator, g.firstChild);\n        }\n        let isDragging = false;\n        let startX, startY;\n        let initialPositions = {};\n        let longPressTimer = null;\n        let longPressTriggered = false;\n        g.addEventListener(\"contextmenu\", (e) => {\n         e.preventDefault();\n         e.stopPropagation();\n         if (isViewOnly()) return;\n         if (selectedNodes.has(id)) {\n          selectedNodes.delete(id);\n         } else {\n          selectedNodes.add(id);\n         }\n         updateNodeSelection();\n         return false;\n        });\n        g.addEventListener(\"touchstart\", (e) => {\n         if (NODE_DATA[id].isRack) {\n          const touch = e.touches[0];\n          longPressStartX = touch.clientX;\n          longPressStartY = touch.clientY;\n          longPressTimer = setTimeout(() => {\n           longPressTriggered = true;\n           if (navigator.vibrate) {\n            navigator.vibrate(100);\n           }\n           enterRack(id);\n          }, 500);\n         }\n        }, { passive: true });\n        let lastTapTime = 0;\n        let lastTapNode = null;\n        g.addEventListener(\"touchend\", (e) => {\n         const currentTime = new Date().getTime();\n         const tapLength = currentTime - lastTapTime;\n         if (tapLength < 300 && tapLength > 0 && lastTapNode === id) {\n          e.preventDefault();\n          e.stopPropagation();\n          if (isViewOnly()) return;\n          if (selectedNodes.has(id)) {\n           selectedNodes.delete(id);\n          } else {\n           selectedNodes.add(id);\n          }\n          updateNodeSelection();\n          if (navigator.vibrate) {\n           navigator.vibrate(50);\n          }\n          lastTapTime = 0;\n          lastTapNode = null;\n         } else {\n          lastTapTime = currentTime;\n          lastTapNode = id;\n         }\n        });\n        g.addEventListener(\"touchend\", (e) => {\n         if (longPressTimer) {\n          clearTimeout(longPressTimer);\n          longPressTimer = null;\n         }\n         if (longPressTriggered) {\n          e.preventDefault();\n          e.stopPropagation();\n          longPressTriggered = false;\n         }\n        });\n        let longPressStartX = 0;\n        let longPressStartY = 0;\n        g.addEventListener(\"touchmove\", (e) => {\n         if (longPressTimer) {\n          const touch = e.touches[0];\n          const dx = Math.abs(touch.clientX - longPressStartX);\n          const dy = Math.abs(touch.clientY - longPressStartY);\n          if (dx > 15 || dy > 15) {\n           clearTimeout(longPressTimer);\n           longPressTimer = null;\n           longPressTriggered = false;\n          }\n         }\n        }, { passive: true });\n        g.addEventListener(\"mousedown\", (e) => {\n\t\t if (isViewOnly()) return;\n         if (e.button === 2) {\n          return;\n         }\n         if (NODE_DATA[id]?.locked) {\n          return;\n         }\n         e.preventDefault();\n         isDragging = true;\n         const svgEl = document.getElementById(\"map\");\n         const pt = svgEl.createSVGPoint();\n         pt.x = e.clientX;\n         pt.y = e.clientY;\n         const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n         startX = svgP.x;\n         startY = svgP.y;\n      if (selectedNodes.has(id)) {\n      initialPositions = {};\n      const allSelectedRects = Array.from(selectedRects);\n      const allSelectedTexts = Array.from(selectedTexts);\n      const allSelectedEdges = Array.from(selectedEdges).map(eid => EDGE_DATA.list.find(e => e.id === eid)).filter(Boolean);\n      selectedNodes.forEach(nodeId => {\n      const nodePos = savedPositions[nodeId];\n      if (nodePos) {\n      initialPositions[nodeId] = { x: nodePos.x, y: nodePos.y };\n      }\n      });\n      Array.from(selectedRects).forEach(rectId => {\n        const rect = RECT_DATA.list.find(r => r.id === rectId);\n        if (rect) { rect._dragStartX = rect.x; rect._dragStartY = rect.y; }\n      });\n      Array.from(selectedTexts).forEach(textId => {\n        const text = TEXT_DATA.list.find(t => t.id === textId);\n        if (text) { text._dragStartX = text.x; text._dragStartY = text.y; }\n      });\n      Array.from(selectedEdges).forEach(edgeId => {\n        const edge = EDGE_DATA.list.find(e => e.id === edgeId);\n        if (edge && edge.points) { edge._dragStartPoints = edge.points.map(p => ({x: p.x, y: p.y})); }\n      });\n      } else {\n      initialPositions = { [id]: { x: pos.x, y: pos.y } };\n      }\n      if (currentView.mode === \"rack\") {\n      initialPositions[id] = { x: pos.x, y: pos.y };\n      }\n         g.style.cursor = \"grabbing\";\n         hitArea.style.cursor = \"grabbing\";\n         e.stopPropagation();\n        });\n        const handleMouseMove = (e) => {\n         if (!isDragging) return;\n         e.preventDefault();\n         const svgEl = document.getElementById(\"map\");\n         const pt = svgEl.createSVGPoint();\n         pt.x = e.clientX;\n         pt.y = e.clientY;\n         const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n         const dx = svgP.x - startX;\n         const dy = svgP.y - startY;\n         let nodesToMove = selectedNodes.has(id) ? Array.from(selectedNodes) : [id];\n         const groupIds = new Set();\n         nodesToMove.forEach(nodeId => {\n          const groupId = NODE_DATA[nodeId]?.groupId;\n          if (groupId) {\n            groupIds.add(groupId);\n          }\n         });\n         if (groupIds.size > 0) {\n          Object.keys(NODE_DATA).forEach(nodeId => {\n            const nodeGroupId = NODE_DATA[nodeId]?.groupId;\n            if (nodeGroupId && groupIds.has(nodeGroupId)) {\n              if (!nodesToMove.includes(nodeId)) {\n                nodesToMove.push(nodeId);\n                if (!initialPositions[nodeId]) {\n                  const nodePos = savedPositions[nodeId];\n                  if (nodePos) {\n                    initialPositions[nodeId] = { x: nodePos.x, y: nodePos.y };\n                  }\n                }\n              }\n            }\n          });\n         }\n         nodesToMove = nodesToMove.filter(nodeId => !NODE_DATA[nodeId]?.locked);\n         nodesToMove.forEach(nodeId => {\n          if (!initialPositions[nodeId]) return;\n          const initialPos = initialPositions[nodeId];\n          let newX = initialPos.x + dx;\n          let newY = initialPos.y + dy;\n          const nodeSize = savedSizes[nodeId] || 55;\n          newX = Math.max(nodeSize + CANVAS_PADDING, Math.min(CANVAS_WIDTH - nodeSize - CANVAS_PADDING, newX));\n          newY = Math.max(nodeSize + CANVAS_PADDING, Math.min(CANVAS_HEIGHT - nodeSize - CANVAS_PADDING, newY));\n          savedPositions[nodeId] = { x: newX, y: newY };\n          positions[nodeId] = { x: newX, y: newY };\n          if (nodeId === id) {\n           pos.x = newX;\n           pos.y = newY;\n          }\n          const nodeGroup = document.querySelector(`g[data-node-id=\"${nodeId}\"]`);\n          if (nodeGroup) {\n           nodeGroup.setAttribute(\"transform\", `translate(${newX},${newY})`);\n          }\n         });\n         Array.from(selectedRects).forEach(rectId => {\n           const rect = RECT_DATA.list.find(r => r.id === rectId);\n           if (rect && rect._dragStartX !== undefined) {\n             rect.x = rect._dragStartX + dx;\n             rect.y = rect._dragStartY + dy;\n           }\n         });\n         Array.from(selectedTexts).forEach(textId => {\n           const text = TEXT_DATA.list.find(t => t.id === textId);\n           if (text && text._dragStartX !== undefined) {\n             text.x = text._dragStartX + dx;\n             text.y = text._dragStartY + dy;\n           }\n         });\n         Array.from(selectedEdges).forEach(edgeId => {\n           const edge = EDGE_DATA.list.find(e => e.id === edgeId);\n           if (edge && edge._dragStartPoints) {\n             edge.points.forEach((p, i) => {\n               p.x = edge._dragStartPoints[i].x + dx;\n               p.y = edge._dragStartPoints[i].y + dy;\n             });\n           }\n         });\n         forgeTheTopology();\n         updateMinimap();\n         document.querySelectorAll(\".edge\").forEach((edgeEl) => {\n          const fromId = edgeEl.dataset.from;\n          const toId = edgeEl.dataset.to;\n          if (nodesToMove.includes(fromId) || nodesToMove.includes(toId)) {\n           const p1 = savedPositions[fromId] || positions[fromId] || {\n            x: 600,\n            y: 350\n           };\n           const p2 = savedPositions[toId] || positions[toId] || {\n            x: 600,\n            y: 350\n           };\n           if (edgeEl.tagName === \"line\") {\n            edgeEl.setAttribute(\"x1\", p1.x);\n            edgeEl.setAttribute(\"y1\", p1.y);\n            edgeEl.setAttribute(\"x2\", p2.x);\n            edgeEl.setAttribute(\"y2\", p2.y);\n           } else if (edgeEl.tagName === \"path\") {\n            const edgeId = edgeEl.dataset.edgeId;\n            const edge = EDGE_DATA.list.find(\n             (e) => e.id === edgeId);\n            if (edge) {\n             const pairTotal = edge._pairTotal || 1;\n             const pairIndex = edge._pairIndex || 0;\n             const midX = (p1.x + p2.x) / 2;\n             const midY = (p1.y + p2.y) / 2;\n             const dx = p2.x - p1.x;\n             const dy = p2.y - p1.y;\n             const len = Math.sqrt(dx * dx + dy * dy) || 1;\n             const perpX = -dy / len;\n             const perpY = dx / len;\n             let offsetAmount = 0;\n             if (pairTotal > 1) {\n              offsetAmount = (pairIndex - (pairTotal - 1) / 2) * 40;\n             }\n             const ctrlX = midX + perpX * offsetAmount;\n             const ctrlY = midY + perpY * offsetAmount;\n             edgeEl.setAttribute(\"d\", `M ${p1.x} ${p1.y} Q ${ctrlX} ${ctrlY} ${p2.x} ${p2.y}`);\n            }\n           }\n          }\n         });\n        };\n      const handleMouseUp = () => {\n      if (isDragging) {\n      isDragging = false;\n      g.style.cursor = \"grab\";\n      hitArea.style.cursor = \"grab\";\n      if (currentView.mode === \"rack\" && NODE_DATA[id]?.assignedRack === currentView.rackId) {\n      const currentMappingMode = PAGE_STATE.mappingMode || 'network';\n      if (currentMappingMode === 'network') {\n      const draggedX = savedPositions[id]?.x || pos.x;\n      const draggedY = savedPositions[id]?.y || pos.y;\n      const rackCapacity = getRackCapacity(currentView.rackId);\n      const rackUHeight = getRackUHeight(currentView.rackId);\n      const rackLeft = RACK_START_X - RACK_WIDTH / 2;\n      const rackRight = RACK_START_X + RACK_WIDTH / 2;\n      const rackTop = RACK_START_Y;\n      const rackBottom = RACK_START_Y + rackCapacity * rackUHeight;\n      if (draggedX >= rackLeft && draggedX <= rackRight && draggedY >= rackTop && draggedY <= rackBottom) {\n        let newUnit = rackCapacity - Math.round((draggedY - RACK_START_Y) / rackUHeight);\n        newUnit = Math.max(1, Math.min(newUnit, rackCapacity));\n        const uHeight = parseInt(NODE_DATA[id].uHeight) || 1;\n        const validation = validateRackPlacement(id, currentView.rackId, newUnit, uHeight);\n        if (!validation.valid) {\n         showErrorModal(t(\"editModal.rackPlacementError\"), validation.message);\n         forgeTheTopology();\n         return;\n        }\n        NODE_DATA[id].rackUnit = newUnit;\n      }\n      forgeTheTopology();\n      claimTheImmortal(id);\n      }\n      }\n      }\n      };\n        document.addEventListener(\"mousemove\", handleMouseMove);\n        document.addEventListener(\"mouseup\", handleMouseUp);\n        let touchStartTime = 0;\n        let touchStartX = 0;\n        let touchStartY = 0;\n        let touchMoved = false;\n        g.addEventListener(\"touchstart\",\n         (e) => {\n          if (isViewOnly()) {\n           touchStartTime = Date.now();\n           touchMoved = false;\n           e.stopPropagation();\n           return;\n          }\n          if (NODE_DATA[id]?.locked) {\n           return;\n          }\n          e.preventDefault();\n          touchStartTime = Date.now();\n          touchMoved = false;\n          const svgEl = document.getElementById(\"map\");\n          const pt = svgEl.createSVGPoint();\n          const touch = e.touches[0];\n          pt.x = touch.clientX;\n          pt.y = touch.clientY;\n          const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n          touchStartX = svgP.x;\n          touchStartY = svgP.y;\n          startX = svgP.x;\n          startY = svgP.y;\n          if (selectedNodes.has(id)) {\n           initialPositions = {};\n           selectedNodes.forEach(nodeId => {\n            const nodePos = savedPositions[nodeId];\n            if (nodePos) {\n             initialPositions[nodeId] = { x: nodePos.x, y: nodePos.y };\n            }\n           });\n          } else {\n           initialPositions = { [id]: { x: pos.x, y: pos.y } };\n          }\n          e.stopPropagation();\n         }, {\n          passive: false\n         });\n        g.addEventListener(\"touchmove\", (e) => {\n\t\tif (isViewOnly()) return;\n         e.preventDefault();\n         const svgEl = document.getElementById(\"map\");\n         const pt = svgEl.createSVGPoint();\n         const touch = e.touches[0];\n         pt.x = touch.clientX;\n         pt.y = touch.clientY;\n         const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n         const dx = Math.abs(svgP.x - touchStartX);\n         const dy = Math.abs(svgP.y - touchStartY);\n         if (dx > (isMobileDevice() ? 4 : 10) || dy > (isMobileDevice() ? 4 : 10)) {\n      touchMoved = true;\n      isDragging = true;\n      }\n         if (!isDragging) return;\n         const deltaX = svgP.x - startX;\n         const deltaY = svgP.y - startY;\n         let nodesToMove = selectedNodes.has(id) ? Array.from(selectedNodes) : [id];\n         const groupIds = new Set();\n         nodesToMove.forEach(nodeId => {\n          const groupId = NODE_DATA[nodeId]?.groupId;\n          if (groupId) {\n            groupIds.add(groupId);\n          }\n         });\n         if (groupIds.size > 0) {\n          Object.keys(NODE_DATA).forEach(nodeId => {\n            const nodeGroupId = NODE_DATA[nodeId]?.groupId;\n            if (nodeGroupId && groupIds.has(nodeGroupId)) {\n              if (!nodesToMove.includes(nodeId)) {\n                nodesToMove.push(nodeId);\n                if (!initialPositions[nodeId]) {\n                  const nodePos = savedPositions[nodeId];\n                  if (nodePos) {\n                    initialPositions[nodeId] = { x: nodePos.x, y: nodePos.y };\n                  }\n                }\n              }\n            }\n          });\n         }\n         nodesToMove = nodesToMove.filter(nodeId => !NODE_DATA[nodeId]?.locked);\n         nodesToMove.forEach(nodeId => {\n          if (!initialPositions[nodeId]) return;\n          const initialPos = initialPositions[nodeId];\n          let newX = initialPos.x + deltaX;\n          let newY = initialPos.y + deltaY;\n          const nodeSize = savedSizes[nodeId] || 55;\n          newX = Math.max(nodeSize + CANVAS_PADDING, Math.min(CANVAS_WIDTH - nodeSize - CANVAS_PADDING, newX));\n          newY = Math.max(nodeSize + CANVAS_PADDING, Math.min(CANVAS_HEIGHT - nodeSize - CANVAS_PADDING, newY));\n          savedPositions[nodeId] = { x: newX, y: newY };\n          positions[nodeId] = { x: newX, y: newY };\n          if (nodeId === id) {\n           pos.x = newX;\n           pos.y = newY;\n          }\n          const nodeGroup = document.querySelector(`g[data-node-id=\"${nodeId}\"]`);\n          if (nodeGroup) {\n           nodeGroup.setAttribute(\"transform\", `translate(${newX},${newY})`);\n          }\n         });\n         updateMinimap();\n         document.querySelectorAll(\".edge\").forEach((edgeEl) => {\n          const fromId = edgeEl.dataset.from;\n          const toId = edgeEl.dataset.to;\n          if (nodesToMove.includes(fromId) || nodesToMove.includes(toId)) {\n           const p1 = savedPositions[fromId] || positions[fromId] || {\n            x: 600,\n            y: 350\n           };\n           const p2 = savedPositions[toId] || positions[toId] || {\n            x: 600,\n            y: 350\n           };\n           if (edgeEl.tagName === \"line\") {\n            edgeEl.setAttribute(\"x1\", p1.x);\n            edgeEl.setAttribute(\"y1\", p1.y);\n            edgeEl.setAttribute(\"x2\", p2.x);\n            edgeEl.setAttribute(\"y2\", p2.y);\n           } else if (edgeEl.tagName === \"path\") {\n            const edgeId = edgeEl.dataset.edgeId;\n            const edge = EDGE_DATA.list.find(\n             (e) => e.id === edgeId);\n            if (edge) {\n             const pairTotal = edge._pairTotal || 1;\n             const pairIndex = edge._pairIndex || 0;\n             const midX = (p1.x + p2.x) / 2;\n             const midY = (p1.y + p2.y) / 2;\n             const dx = p2.x - p1.x;\n             const dy = p2.y - p1.y;\n             const len = Math.sqrt(dx * dx + dy * dy) || 1;\n             const perpX = -dy / len;\n             const perpY = dx / len;\n             let offsetAmount = 0;\n             if (pairTotal > 1) {\n              offsetAmount = (pairIndex - (pairTotal - 1) / 2) * 40;\n             }\n             const ctrlX = midX + perpX * offsetAmount;\n             const ctrlY = midY + perpY * offsetAmount;\n             edgeEl.setAttribute(\"d\", `M ${p1.x} ${p1.y} Q ${ctrlX} ${ctrlY} ${p2.x} ${p2.y}`);\n            }\n           }\n          }\n         });\n        }, {\n         passive: false\n        });\n      g.addEventListener(\"touchend\", (e) => {\n      const touchDuration = Date.now() - touchStartTime;\n      if (!touchMoved && touchDuration < 400) {\n       if (isViewOnly()) {\n        handleViewOnlyClick(id, 'node');\n        return;\n       }\n       claimTheImmortal(id);\n      }\n      if (isDragging) {\n      const draggedX = pos.x;\n      const draggedY = pos.y;\n      isDragging = false;\n      savedPositions[id] = {\n      x: pos.x,\n      y: pos.y\n      };\n      if (currentView.mode === \"rack\" && NODE_DATA[id]?.assignedRack === currentView.rackId) {\n      const currentMappingMode = PAGE_STATE.mappingMode || 'network';\n      if (currentMappingMode === 'network') {\n      const rackCapacity = getRackCapacity(currentView.rackId);\n      const rackUHeight = getRackUHeight(currentView.rackId);\n      const rackLeft = RACK_START_X - RACK_WIDTH / 2;\n      const rackRight = RACK_START_X + RACK_WIDTH / 2;\n      const rackTop = RACK_START_Y;\n      const rackBottom = RACK_START_Y + rackCapacity * rackUHeight;\n      if (draggedX >= rackLeft && draggedX <= rackRight && draggedY >= rackTop && draggedY <= rackBottom) {\n        let newUnit = rackCapacity - Math.round((draggedY - RACK_START_Y) / rackUHeight);\n        newUnit = Math.max(1, Math.min(newUnit, rackCapacity));\n        const uHeight = parseInt(NODE_DATA[id].uHeight) || 1;\n        const validation = validateRackPlacement(id, currentView.rackId, newUnit, uHeight);\n        if (!validation.valid) {\n         showErrorModal(t(\"editModal.rackPlacementError\"), validation.message);\n         forgeTheTopology();\n         return;\n        }\n        NODE_DATA[id].rackUnit = newUnit;\n      }\n      forgeTheTopology();\n      claimTheImmortal(id);\n      }\n      }\n      }\n      touchMoved = false;\n      });\n        g.style.cursor = \"grab\";\n        g.addEventListener(\"click\", (e) => {\n         if (!isDragging) {\n          if (isViewOnly()) {\n           handleViewOnlyClick(id, 'node');\n           return;\n          }\n          claimTheImmortal(id);\n         }\n        });\n        g.addEventListener(\"dblclick\", (e) => {\n         e.preventDefault();\n         e.stopPropagation();\n         if (NODE_DATA[id].isRack) {\n          enterRack(id);\n         }\n        });\n        if (!isNodeVisible(id)) {\n         g.style.opacity = \"0.1\";\n         g.style.pointerEvents = \"none\";\n        }\n        svg.appendChild(g);\n       });\n        if (TEXT_DATA && TEXT_DATA.list) {\n         TEXT_DATA.list.forEach((textItem) => {\n          if (currentView.mode === \"rack\") return;\n          const g = document.createElementNS(ns, \"g\");\n          g.classList.add(\"text-group\");\n          g.dataset.textId = textItem.id;\n          const textRotation = textItem.rotation || 0;\n          if (textRotation !== 0) {\n            g.setAttribute(\"transform\", `rotate(${textRotation}, ${textItem.x}, ${textItem.y})`);\n          }\n          if (textItem.bgEnabled) {\n           const bgRect = document.createElementNS(ns, \"rect\");\n           bgRect.classList.add(\"text-bg\");\n           bgRect.setAttribute(\"x\", textItem.x - 5);\n           bgRect.setAttribute(\"y\", textItem.y - textItem.fontSize - 2);\n           bgRect.setAttribute(\"width\", 100);\n           bgRect.setAttribute(\"height\", textItem.fontSize + 10);\n           bgRect.style.fill = textItem.bgColor;\n           bgRect.style.opacity = \"0.7\";\n           bgRect.style.rx = \"4\";\n           g.appendChild(bgRect);\n          }\n          const textEl = document.createElementNS(ns, \"text\");\n          textEl.classList.add(\"text-element\");\n          textEl.setAttribute(\"x\", textItem.x);\n          textEl.setAttribute(\"y\", textItem.y);\n          textEl.style.fill = textItem.color;\n          textEl.style.fontSize = textItem.fontSize + \"px\";\n          textEl.style.fontWeight = textItem.fontWeight;\n          textEl.style.fontStyle = textItem.fontStyle;\n          textEl.style.textAnchor = textItem.textAlign;\n          textEl.style.textDecoration = textItem.textDecoration;\n          textEl.style.opacity = textItem.opacity;\n          textEl.style.cursor = \"move\";\n          textEl.style.userSelect = \"none\";\n          textEl.setAttribute(\"dominant-baseline\", \"middle\");\n          const lines = textItem.content.split('\\n');\n          if (lines.length === 1) {\n           textEl.textContent = textItem.content;\n          } else {\n           lines.forEach((line, i) => {\n            const tspan = document.createElementNS(ns, \"tspan\");\n            tspan.textContent = line;\n            tspan.setAttribute(\"x\", textItem.x);\n            tspan.setAttribute(\"dy\", i === 0 ? 0 : textItem.fontSize * 1.2);\n            textEl.appendChild(tspan);\n           });\n          }\n          g.appendChild(textEl);\n          if (textItem.bgEnabled) {\n           setTimeout(() => {\n            try {\n             const bbox = textEl.getBBox();\n             const bgRect = g.querySelector('.text-bg');\n             if (bgRect && bbox) {\n              bgRect.setAttribute(\"x\", bbox.x - 5);\n              bgRect.setAttribute(\"y\", bbox.y - 2);\n              bgRect.setAttribute(\"width\", bbox.width + 10);\n              bgRect.setAttribute(\"height\", bbox.height + 4);\n             }\n            } catch (e) {\n            }\n           }, 0);\n          }\n          const deleteBtn = document.createElementNS(ns, \"g\");\n          deleteBtn.classList.add(\"text-delete-btn\");\n          deleteBtn.style.cursor = \"pointer\";\n          deleteBtn.style.display = textDrawMode ? \"block\" : \"none\";\n          const deleteBg = document.createElementNS(ns, \"circle\");\n          deleteBg.setAttribute(\"cx\", textItem.x + 20);\n          deleteBg.setAttribute(\"cy\", textItem.y - textItem.fontSize);\n          deleteBg.setAttribute(\"r\", 12);\n          deleteBg.style.fill = \"#f56565\";\n          deleteBg.style.stroke = \"white\";\n          deleteBg.style.strokeWidth = \"2\";\n          const deleteX = document.createElementNS(ns, \"text\");\n          deleteX.setAttribute(\"x\", textItem.x + 20);\n          deleteX.setAttribute(\"y\", textItem.y - textItem.fontSize);\n          deleteX.setAttribute(\"text-anchor\", \"middle\");\n          deleteX.setAttribute(\"dominant-baseline\", \"middle\");\n          deleteX.style.fill = \"white\";\n          deleteX.style.fontSize = \"16px\";\n          deleteX.style.fontWeight = \"bold\";\n          deleteX.style.pointerEvents = \"none\";\n          deleteX.textContent = \"×\";\n          deleteBtn.appendChild(deleteBg);\n          deleteBtn.appendChild(deleteX);\n          deleteBtn.addEventListener(\"click\", (e) => {\n           e.stopPropagation();\n           e.preventDefault();\n           deleteText(textItem.id);\n          });\n          let isDragging = false;\n          let dragStartX, dragStartY;\n          let textStartX, textStartY;\n          textEl.addEventListener(\"mousedown\", (e) => {\n\t\t  if (isViewOnly()) return;\n      if (textDrawMode) return;\n      e.preventDefault();\n      e.stopPropagation();\n      isDragging = true;\n      dragStartX = e.clientX;\n      dragStartY = e.clientY;\n      textStartX = textItem.x;\n      textStartY = textItem.y;\n      textEl.style.cursor = \"grabbing\";\n      showTextPanel(textItem.id);\n      if (selectedTexts.has(textItem.id)) {\n      initialPositions = {};\n      selectedNodes.forEach(nodeId => {\n      const nodePos = savedPositions[nodeId];\n      if (nodePos) initialPositions[nodeId] = { x: nodePos.x, y: nodePos.y };\n      });\n      selectedRects.forEach(rectId => {\n      const r = RECT_DATA.list.find(x => x.id === rectId);\n      if (r) { r._dragStartX = r.x; r._dragStartY = r.y; }\n      });\n      selectedTexts.forEach(textId => {\n      const t = TEXT_DATA.list.find(x => x.id === textId);\n      if (t) { t._dragStartX = t.x; t._dragStartY = t.y; }\n      });\n      selectedEdges.forEach(edgeId => {\n      const ed = EDGE_DATA.list.find(x => x.id === edgeId);\n      if (ed && ed.points) { ed._dragStartPoints = ed.points.map(p => ({x: p.x, y: p.y})); }\n      });\n      }\n      });\n          const moveHandler = (e) => {\n      if (!isDragging || textDrawMode) return;\n      const svgEl = svg;\n      const pt1 = svgEl.createSVGPoint();\n      pt1.x = dragStartX;\n      pt1.y = dragStartY;\n      const svgP1 = pt1.matrixTransform(svgEl.getScreenCTM().inverse());\n      const pt2 = svgEl.createSVGPoint();\n      pt2.x = e.clientX;\n      pt2.y = e.clientY;\n      const svgP2 = pt2.matrixTransform(svgEl.getScreenCTM().inverse());\n      const dx = svgP2.x - svgP1.x;\n      const dy = svgP2.y - svgP1.y;\n      textItem.x = textStartX + dx;\n      textItem.y = textStartY + dy;\n      if (selectedTexts.has(textItem.id)) {\n      selectedNodes.forEach(nodeId => {\n      if (initialPositions[nodeId]) {\n       const newX = initialPositions[nodeId].x + dx;\n       const newY = initialPositions[nodeId].y + dy;\n       savedPositions[nodeId] = { x: newX, y: newY };\n      }\n      });\n      selectedRects.forEach(rectId => {\n      const r = RECT_DATA.list.find(x => x.id === rectId);\n      if (r && r._dragStartX !== undefined) { r.x = r._dragStartX + dx; r.y = r._dragStartY + dy; }\n      });\n      selectedTexts.forEach(textId => {\n      if (textId === textItem.id) return;\n      const t = TEXT_DATA.list.find(x => x.id === textId);\n      if (t && t._dragStartX !== undefined) { t.x = t._dragStartX + dx; t.y = t._dragStartY + dy; }\n      });\n      selectedEdges.forEach(edgeId => {\n      const ed = EDGE_DATA.list.find(x => x.id === edgeId);\n      if (ed && ed._dragStartPoints) { ed.points.forEach((p, i) => { p.x = ed._dragStartPoints[i].x + dx; p.y = ed._dragStartPoints[i].y + dy; }); }\n      });\n      }\n      forgeTheTopology();\n      };\n          const upHandler = () => {\n           if (isDragging) {\n            isDragging = false;\n            textEl.style.cursor = \"move\";\n           }\n          };\n          document.addEventListener(\"mousemove\", moveHandler);\n          document.addEventListener(\"mouseup\", upHandler);\n          let touchStartX = 0, touchStartY = 0;\n          let textTouchStartX = 0, textTouchStartY = 0;\n          textEl.addEventListener(\"touchstart\", (e) => {\n\t\t   if (isViewOnly()) return;\n           if (textDrawMode) return;\n           if (e.touches.length !== 1) return;\n           e.preventDefault();\n           e.stopPropagation();\n           const touch = e.touches[0];\n           isDragging = true;\n           touchStartX = touch.clientX;\n           touchStartY = touch.clientY;\n           textTouchStartX = textItem.x;\n           textTouchStartY = textItem.y;\n           showTextPanel(textItem.id);\n          }, { passive: false });\n          textEl.addEventListener(\"touchmove\", (e) => {\n           if (!isDragging || textDrawMode) return;\n           if (e.touches.length !== 1) return;\n           e.preventDefault();\n           const touch = e.touches[0];\n           const svgEl = svg;\n           const pt1 = svgEl.createSVGPoint();\n           pt1.x = touchStartX;\n           pt1.y = touchStartY;\n           const svgP1 = pt1.matrixTransform(svgEl.getScreenCTM().inverse());\n           const pt2 = svgEl.createSVGPoint();\n           pt2.x = touch.clientX;\n           pt2.y = touch.clientY;\n           const svgP2 = pt2.matrixTransform(svgEl.getScreenCTM().inverse());\n           const dx = svgP2.x - svgP1.x;\n           const dy = svgP2.y - svgP1.y;\n           textItem.x = textTouchStartX + dx;\n           textItem.y = textTouchStartY + dy;\n           forgeTheTopology();\n          }, { passive: false });\n          textEl.addEventListener(\"touchend\", () => {\n           if (isDragging) {\n            isDragging = false;\n           }\n          }, { passive: false });\n          textEl.addEventListener(\"contextmenu\", (e) => {\n\t\t  if (isViewOnly()) return;\n           e.preventDefault();\n           e.stopPropagation();\n           if (selectedTexts.has(textItem.id)) {\n            selectedTexts.delete(textItem.id);\n           } else {\n            selectedTexts.add(textItem.id);\n           }\n           updateAllSelections();\n          });\n          let textLastTap = 0;\n          g.addEventListener(\"touchend\", (e) => {\n           const now = Date.now();\n           if (now - textLastTap < 300) {\n            e.preventDefault();\n            if (selectedTexts.has(textItem.id)) {\n             selectedTexts.delete(textItem.id);\n            } else {\n             selectedTexts.add(textItem.id);\n            }\n            updateAllSelections();\n            if (navigator.vibrate) navigator.vibrate(50);\n            textLastTap = 0;\n           } else {\n            textLastTap = now;\n           }\n          }, { passive: false });\n          if (textItem.groupId) {\n            const groupIndicator = document.createElementNS(ns, \"rect\");\n            groupIndicator.setAttribute(\"x\", textItem.x - 54);\n            groupIndicator.setAttribute(\"y\", textItem.y - 24);\n            groupIndicator.setAttribute(\"width\", 108);\n            groupIndicator.setAttribute(\"height\", 48);\n            groupIndicator.setAttribute(\"rx\", \"8\");\n            groupIndicator.style.fill = \"none\";\n            groupIndicator.style.stroke = \"#4fd1c5\";\n            groupIndicator.style.strokeWidth = \"3\";\n            groupIndicator.style.strokeDasharray = \"5,5\";\n            groupIndicator.style.pointerEvents = \"none\";\n            g.insertBefore(groupIndicator, g.firstChild);\n          }\n          g.appendChild(deleteBtn);\n          svg.appendChild(g);\n         });\n        }\n        if (IMAGE_DATA && IMAGE_DATA.list) {\n         IMAGE_DATA.list.forEach((img) => {\n          if (currentView.mode === \"rack\") return;\n          const g = document.createElementNS(ns, \"g\");\n          g.classList.add(\"image-group\");\n          g.dataset.imageId = img.id;\n          const imgEl = document.createElementNS(ns, \"image\");\n          imgEl.classList.add(\"canvas-image\");\n          imgEl.setAttribute(\"x\", img.x);\n          imgEl.setAttribute(\"y\", img.y);\n          imgEl.setAttribute(\"width\", img.width);\n          imgEl.setAttribute(\"height\", img.height);\n          imgEl.setAttributeNS(\"http://www.w3.org/1999/xlink\", \"href\", img.src);\n          imgEl.style.opacity = img.opacity !== undefined ? img.opacity : 1;\n          imgEl.style.cursor = \"move\";\n          if (img.border && img.border !== \"none\") {\n           const borderWidth = img.border === \"thin\" ? 1 : img.border === \"medium\" ? 2 : 4;\n           const borderRect = document.createElementNS(ns, \"rect\");\n           borderRect.setAttribute(\"x\", img.x);\n           borderRect.setAttribute(\"y\", img.y);\n           borderRect.setAttribute(\"width\", img.width);\n           borderRect.setAttribute(\"height\", img.height);\n           borderRect.style.fill = \"none\";\n           borderRect.style.stroke = \"#475569\";\n           borderRect.style.strokeWidth = borderWidth + \"px\";\n           borderRect.style.pointerEvents = \"none\";\n           g.appendChild(borderRect);\n          }\n          if (img.shadow && img.shadow !== \"none\") {\n           const shadowSize = img.shadow === \"small\" ? 4 : img.shadow === \"medium\" ? 8 : 16;\n           imgEl.style.filter = `drop-shadow(${shadowSize/2}px ${shadowSize/2}px ${shadowSize}px rgba(0,0,0,0.5))`;\n          }\n          g.appendChild(imgEl);\n          let isDragging = false;\n          let dragStartX, dragStartY;\n          let imgStartX, imgStartY;\n          imgEl.addEventListener(\"mousedown\", (e) => {\n           if (isViewOnly()) return;\n           e.preventDefault();\n           e.stopPropagation();\n           isDragging = true;\n           dragStartX = e.clientX;\n           dragStartY = e.clientY;\n           imgStartX = img.x;\n           imgStartY = img.y;\n           imgEl.style.cursor = \"grabbing\";\n           showImagePanel(img.id);\n          });\n          const moveHandler = (e) => {\n           if (!isDragging) return;\n           const svgEl = svg;\n           const pt1 = svgEl.createSVGPoint();\n           pt1.x = dragStartX;\n           pt1.y = dragStartY;\n           const svgP1 = pt1.matrixTransform(svgEl.getScreenCTM().inverse());\n           const pt2 = svgEl.createSVGPoint();\n           pt2.x = e.clientX;\n           pt2.y = e.clientY;\n           const svgP2 = pt2.matrixTransform(svgEl.getScreenCTM().inverse());\n           const dx = svgP2.x - svgP1.x;\n           const dy = svgP2.y - svgP1.y;\n           img.x = imgStartX + dx;\n           img.y = imgStartY + dy;\n           forgeTheTopology();\n          };\n          const upHandler = () => {\n           if (isDragging) {\n            isDragging = false;\n            imgEl.style.cursor = \"move\";\n           }\n          };\n          document.addEventListener(\"mousemove\", moveHandler);\n          document.addEventListener(\"mouseup\", upHandler);\n          let touchStartX = 0, touchStartY = 0;\n          let imgTouchStartX = 0, imgTouchStartY = 0;\n          let lastTap = 0;\n          imgEl.addEventListener(\"touchstart\", (e) => {\n           if (isViewOnly()) return;\n           if (e.touches.length !== 1) return;\n           e.preventDefault();\n           e.stopPropagation();\n           const touch = e.touches[0];\n           isDragging = true;\n           touchStartX = touch.clientX;\n           touchStartY = touch.clientY;\n           imgTouchStartX = img.x;\n           imgTouchStartY = img.y;\n           showImagePanel(img.id);\n          }, { passive: false });\n          imgEl.addEventListener(\"touchmove\", (e) => {\n           if (!isDragging) return;\n           if (e.touches.length !== 1) return;\n           e.preventDefault();\n           const touch = e.touches[0];\n           const svgEl = svg;\n           const pt1 = svgEl.createSVGPoint();\n           pt1.x = touchStartX;\n           pt1.y = touchStartY;\n           const svgP1 = pt1.matrixTransform(svgEl.getScreenCTM().inverse());\n           const pt2 = svgEl.createSVGPoint();\n           pt2.x = touch.clientX;\n           pt2.y = touch.clientY;\n           const svgP2 = pt2.matrixTransform(svgEl.getScreenCTM().inverse());\n           img.x = imgTouchStartX + (svgP2.x - svgP1.x);\n           img.y = imgTouchStartY + (svgP2.y - svgP1.y);\n           forgeTheTopology();\n          }, { passive: false });\n          imgEl.addEventListener(\"touchend\", () => {\n           isDragging = false;\n          }, { passive: false });\n          svg.appendChild(g);\n         });\n        }\n       forgeTheLegend();\n       updateZoneLegend();\n       updateMinimap();\n       populateRackDropdown();\n       if (currentSearchQuery && currentSearchResults.length > 0) {\n        highlightSearchResults(currentSearchResults, true);\n       }\n       if (typeof renderMotionTrails === 'function') {\n        renderMotionTrails(svg);\n       }\n      }\n      const _forgeTheTopologyImpl = forgeTheTopology;\n      forgeTheTopology = function(immediate = false) {\n       if (immediate || forgeImmediate) {\n        forgeImmediate = false;\n        clearTimeout(forgeDebounceTimer);\n        _forgeTheTopologyImpl();\n        return;\n       }\n       clearTimeout(forgeDebounceTimer);\n       forgeDebounceTimer = setTimeout(() => {\n        _forgeTheTopologyImpl();\n       }, 16);\n      };\n      function forgeTheTopologyImmediate() {\n       forgeImmediate = true;\n       forgeTheTopology();\n      }\n      function showEditModal(title, currentValue, onSave) {\n       const modal = document.getElementById(\"edit-modal\");\n       const input = document.getElementById(\"modal-input\");\n       const titleEl = document.getElementById(\"modal-title\");\n       const saveBtn = document.getElementById(\"modal-save\");\n       const cancelBtn = document.getElementById(\"modal-cancel\");\n       titleEl.textContent = title;\n       input.value = currentValue;\n       modal.classList.add(\"active\");\n       input.focus();\n       input.select();\n       const cleanup = () => {\n        modal.classList.remove(\"active\");\n        saveBtn.removeEventListener(\"click\", handleSave);\n        cancelBtn.removeEventListener(\"click\", handleCancel);\n        input.removeEventListener(\"keypress\", handleEnter);\n        modal.removeEventListener(\"click\", bgHandler);\n       };\n       const handleSave = () => {\n        if (input.value.trim()) {\n         onSave(input.value.trim());\n        }\n        cleanup();\n       };\n       const handleCancel = () => {\n        cleanup();\n       };\n       const handleEnter = (e) => {\n        if (e.key === \"Enter\") handleSave();\n       };\n       const bgHandler = (e) => {\n        if (e.target === modal) handleCancel();\n       };\n       saveBtn.addEventListener(\"click\", handleSave);\n       cancelBtn.addEventListener(\"click\", handleCancel);\n       input.addEventListener(\"keypress\", handleEnter);\n       modal.addEventListener(\"click\", bgHandler);\n      }\n\n      let noteEditorCallback = null;\n      let noteEditorEditIndex = null;\n      function showNoteEditor(title, currentContent, onSave, editIndex = null) {\n       const modal = document.getElementById(\"note-editor-modal\");\n       const content = document.getElementById(\"note-editor-content\");\n       const titleEl = document.getElementById(\"note-editor-title\");\n       const saveBtn = document.getElementById(\"note-editor-save\");\n       const cancelBtn = document.getElementById(\"note-editor-cancel\");\n       titleEl.textContent = title || t(\"noteEditor.title\");\n       content.innerHTML = currentContent || \"<p><br></p>\";\n       noteEditorCallback = onSave;\n       noteEditorEditIndex = editIndex;\n       modal.classList.add(\"active\");\n       content.focus();\n\n       const range = document.createRange();\n       const sel = window.getSelection();\n       range.selectNodeContents(content);\n       range.collapse(false);\n       sel.removeAllRanges();\n       sel.addRange(range);\n      }\n      function closeNoteEditor() {\n       const modal = document.getElementById(\"note-editor-modal\");\n       modal.classList.remove(\"active\");\n       noteEditorCallback = null;\n       noteEditorEditIndex = null;\n       document.getElementById(\"note-heading-select\").value = \"\";\n      }\n      function saveNoteEditor() {\n       const content = document.getElementById(\"note-editor-content\");\n       let html = content.innerHTML.trim();\n\n       if (html === \"<p><br></p>\" || html === \"<br>\" || html === \"\") {\n        closeNoteEditor();\n        return;\n       }\n       if (noteEditorCallback) {\n        noteEditorCallback(html, noteEditorEditIndex);\n       }\n       closeNoteEditor();\n      }\n      function execNoteCommand(command, value = null) {\n       const content = document.getElementById(\"note-editor-content\");\n       content.focus();\n       if (command === \"code\") {\n\n        const sel = window.getSelection();\n        if (sel.rangeCount > 0) {\n         const range = sel.getRangeAt(0);\n         const selectedText = range.toString();\n         if (selectedText) {\n          const code = document.createElement(\"code\");\n          code.textContent = selectedText;\n          range.deleteContents();\n          range.insertNode(code);\n          range.setStartAfter(code);\n          range.collapse(true);\n          sel.removeAllRanges();\n          sel.addRange(range);\n         }\n        }\n       } else if (command === \"codeblock\") {\n\n        const sel = window.getSelection();\n        if (sel.rangeCount > 0) {\n         const range = sel.getRangeAt(0);\n         const selectedText = range.toString() || \"code here\";\n         const pre = document.createElement(\"pre\");\n         const code = document.createElement(\"code\");\n         code.textContent = selectedText;\n         pre.appendChild(code);\n         range.deleteContents();\n         range.insertNode(pre);\n\n         const p = document.createElement(\"p\");\n         p.innerHTML = \"<br>\";\n         pre.parentNode.insertBefore(p, pre.nextSibling);\n         range.setStart(p, 0);\n         range.collapse(true);\n         sel.removeAllRanges();\n         sel.addRange(range);\n        }\n       } else if (command === \"blockquote\") {\n        document.execCommand(\"formatBlock\", false, \"blockquote\");\n       } else if (command === \"createLink\") {\n        const url = prompt(\"Enter URL:\", \"https://\");\n        if (url) {\n         document.execCommand(\"createLink\", false, url);\n        }\n       } else if (command === \"heading\") {\n        if (value) {\n         document.execCommand(\"formatBlock\", false, value);\n        } else {\n         document.execCommand(\"formatBlock\", false, \"p\");\n        }\n       } else {\n        document.execCommand(command, false, value);\n       }\n      }\n\n      document.addEventListener(\"DOMContentLoaded\", () => {\n       const modal = document.getElementById(\"note-editor-modal\");\n       const saveBtn = document.getElementById(\"note-editor-save\");\n       const cancelBtn = document.getElementById(\"note-editor-cancel\");\n       const headingSelect = document.getElementById(\"note-heading-select\");\n       const content = document.getElementById(\"note-editor-content\");\n       saveBtn?.addEventListener(\"click\", saveNoteEditor);\n       cancelBtn?.addEventListener(\"click\", closeNoteEditor);\n       modal?.addEventListener(\"click\", (e) => {\n        if (e.target === modal) closeNoteEditor();\n       });\n\n       document.querySelectorAll(\".note-editor-toolbar:not(#secret-editor-toolbar) button[data-command]\").forEach(btn => {\n        btn.addEventListener(\"click\", (e) => {\n         e.preventDefault();\n         execNoteCommand(btn.dataset.command);\n        });\n       });\n\n       headingSelect?.addEventListener(\"change\", (e) => {\n        execNoteCommand(\"heading\", e.target.value);\n        e.target.value = \"\";\n       });\n\n       content?.addEventListener(\"keydown\", (e) => {\n        if (e.ctrlKey || e.metaKey) {\n         if (e.key === \"b\") { e.preventDefault(); execNoteCommand(\"bold\"); }\n         if (e.key === \"i\") { e.preventDefault(); execNoteCommand(\"italic\"); }\n         if (e.key === \"u\") { e.preventDefault(); execNoteCommand(\"underline\"); }\n        }\n       });\n\n       content?.addEventListener(\"paste\", (e) => {\n        const sel = window.getSelection();\n        if (sel.anchorNode) {\n         const parent = sel.anchorNode.parentElement;\n         if (parent && (parent.tagName === \"CODE\" || parent.tagName === \"PRE\" || parent.closest(\"pre\"))) {\n          e.preventDefault();\n          const text = e.clipboardData.getData(\"text/plain\");\n          document.execCommand(\"insertText\", false, text);\n         }\n        }\n       });\n      });\n\n      function showNoteViewer(content) {\n       const modal = document.getElementById(\"note-editor-modal\");\n       const titleEl = document.getElementById(\"note-editor-title\");\n       const contentEl = document.getElementById(\"note-editor-content\");\n       const toolbar = modal.querySelector(\".note-editor-toolbar\");\n       const saveBtn = document.getElementById(\"note-editor-save\");\n       const cancelBtn = document.getElementById(\"note-editor-cancel\");\n       titleEl.textContent = t(\"noteEditor.viewNote\");\n       contentEl.innerHTML = content;\n       contentEl.contentEditable = \"false\";\n       toolbar.style.display = \"none\";\n       saveBtn.style.display = \"none\";\n       cancelBtn.textContent = t(\"ui.buttons.close\");\n       modal.classList.add(\"active\");\n       const closeViewer = () => {\n        modal.classList.remove(\"active\");\n        contentEl.contentEditable = \"true\";\n        toolbar.style.display = \"\";\n        saveBtn.style.display = \"\";\n        cancelBtn.textContent = t(\"ui.buttons.cancel\");\n        cancelBtn.removeEventListener(\"click\", closeViewer);\n        modal.removeEventListener(\"click\", bgClose);\n       };\n       const bgClose = (e) => { if (e.target === modal) closeViewer(); };\n       cancelBtn.addEventListener(\"click\", closeViewer);\n       modal.addEventListener(\"click\", bgClose);\n      }\n      function renderNotesFeed(notes, containerId, elementType, elementId, onUpdate) {\n       const container = document.getElementById(containerId);\n       if (!container) return;\n       container.innerHTML = \"\";\n       container.className = \"notes-feed\";\n       if (!notes || notes.length === 0) {\n        container.innerHTML = '<div style=\"color: var(--text-soft); font-size: 13px; text-align: center; padding: 10px;\">No notes yet</div>';\n        return;\n       }\n       notes.forEach((note, idx) => {\n        const noteContent = typeof note === \"string\" ? note : (note.content || \"\");\n        const card = document.createElement(\"div\");\n        card.className = \"note-card\";\n        card.innerHTML = `\n         <div class=\"note-card-content\">${noteContent}</div>\n         <div class=\"note-card-actions\">\n          <button class=\"view-note\" data-index=\"${idx}\">View</button>\n          <button class=\"edit-note\" data-index=\"${idx}\">Edit</button>\n          <button class=\"delete-note\" data-index=\"${idx}\">Delete</button>\n         </div>\n        `;\n        card.querySelector(\".view-note\").addEventListener(\"click\", (e) => {\n         e.stopPropagation();\n         showNoteViewer(noteContent);\n        });\n        card.querySelector(\".edit-note\").addEventListener(\"click\", (e) => {\n         e.stopPropagation();\n         showNoteEditor(t(\"noteEditor.editNote\"), noteContent, (newContent, editIdx) => {\n          pushUndo(\"edit note\");\n          if (typeof notes[editIdx] === \"string\") {\n           notes[editIdx] = newContent;\n          } else {\n           notes[editIdx].content = newContent;\n          }\n          renderNotesFeed(notes, containerId, elementType, elementId, onUpdate);\n          if (onUpdate) onUpdate();\n         }, idx);\n        });\n        card.querySelector(\".delete-note\").addEventListener(\"click\", (e) => {\n         e.stopPropagation();\n         pushUndo(\"delete note\");\n         notes.splice(idx, 1);\n         renderNotesFeed(notes, containerId, elementType, elementId, onUpdate);\n         if (onUpdate) onUpdate();\n        });\n        container.appendChild(card);\n       });\n      }\n      function showErrorModal(title, message) {\n       const modal = document.getElementById(\"error-modal\");\n       const titleEl = document.getElementById(\"error-modal-title\");\n       const messageEl = document.getElementById(\"error-modal-message\");\n       const okBtn = document.getElementById(\"error-modal-ok\");\n       titleEl.textContent = title || t(\"ui.modals.error\");\n       messageEl.textContent = message;\n       modal.classList.add(\"active\");\n       const cleanup = () => {\n        modal.classList.remove(\"active\");\n        okBtn.removeEventListener(\"click\", handleOk);\n        modal.removeEventListener(\"click\", bgHandler);\n       };\n       const handleOk = () => { cleanup(); };\n       const bgHandler = (e) => { if (e.target === modal) cleanup(); };\n       okBtn.addEventListener(\"click\", handleOk);\n       modal.addEventListener(\"click\", bgHandler);\n      }\n      function showAlert(message, title) {\n       return new Promise((resolve) => {\n        const modal = document.getElementById(\"dialog-modal\");\n        const titleEl = document.getElementById(\"dialog-modal-title\");\n        const messageEl = document.getElementById(\"dialog-modal-message\");\n        const inputEl = document.getElementById(\"dialog-modal-input\");\n        const okBtn = document.getElementById(\"dialog-modal-ok\");\n        const cancelBtn = document.getElementById(\"dialog-modal-cancel\");\n        titleEl.textContent = title || t(\"dialogs.alert\");\n        messageEl.textContent = message;\n        inputEl.style.display = \"none\";\n        cancelBtn.style.display = \"none\";\n        okBtn.textContent = t(\"dialogs.ok\");\n        modal.classList.add(\"active\");\n        const cleanup = () => {\n         modal.classList.remove(\"active\");\n         okBtn.removeEventListener(\"click\", handleOk);\n         modal.removeEventListener(\"click\", bgHandler);\n        };\n        const handleOk = () => { cleanup(); resolve(true); };\n        const bgHandler = (e) => { if (e.target === modal) { cleanup(); resolve(true); } };\n        okBtn.addEventListener(\"click\", handleOk);\n        modal.addEventListener(\"click\", bgHandler);\n       });\n      }\n      function showConfirm(message, title) {\n       return new Promise((resolve) => {\n        const modal = document.getElementById(\"dialog-modal\");\n        const titleEl = document.getElementById(\"dialog-modal-title\");\n        const messageEl = document.getElementById(\"dialog-modal-message\");\n        const inputEl = document.getElementById(\"dialog-modal-input\");\n        const okBtn = document.getElementById(\"dialog-modal-ok\");\n        const cancelBtn = document.getElementById(\"dialog-modal-cancel\");\n        titleEl.textContent = title || t(\"dialogs.confirm\");\n        messageEl.textContent = message;\n        inputEl.style.display = \"none\";\n        cancelBtn.style.display = \"\";\n        okBtn.textContent = t(\"dialogs.ok\");\n        cancelBtn.textContent = t(\"dialogs.cancel\");\n        modal.classList.add(\"active\");\n        const cleanup = () => {\n         modal.classList.remove(\"active\");\n         okBtn.removeEventListener(\"click\", handleOk);\n         cancelBtn.removeEventListener(\"click\", handleCancel);\n         modal.removeEventListener(\"click\", bgHandler);\n        };\n        const handleOk = () => { cleanup(); resolve(true); };\n        const handleCancel = () => { cleanup(); resolve(false); };\n        const bgHandler = (e) => { if (e.target === modal) handleCancel(); };\n        okBtn.addEventListener(\"click\", handleOk);\n        cancelBtn.addEventListener(\"click\", handleCancel);\n        modal.addEventListener(\"click\", bgHandler);\n       });\n      }\n      function showPrompt(message, defaultValue, title) {\n       return new Promise((resolve) => {\n        const modal = document.getElementById(\"dialog-modal\");\n        const titleEl = document.getElementById(\"dialog-modal-title\");\n        const messageEl = document.getElementById(\"dialog-modal-message\");\n        const inputEl = document.getElementById(\"dialog-modal-input\");\n        const okBtn = document.getElementById(\"dialog-modal-ok\");\n        const cancelBtn = document.getElementById(\"dialog-modal-cancel\");\n        titleEl.textContent = title || t(\"dialogs.prompt\");\n        messageEl.textContent = message;\n        inputEl.style.display = \"\";\n        inputEl.value = defaultValue || \"\";\n        cancelBtn.style.display = \"\";\n        okBtn.textContent = t(\"dialogs.ok\");\n        cancelBtn.textContent = t(\"dialogs.cancel\");\n        modal.classList.add(\"active\");\n        inputEl.focus();\n        inputEl.select();\n        const cleanup = () => {\n         modal.classList.remove(\"active\");\n         okBtn.removeEventListener(\"click\", handleOk);\n         cancelBtn.removeEventListener(\"click\", handleCancel);\n         inputEl.removeEventListener(\"keypress\", handleEnter);\n         modal.removeEventListener(\"click\", bgHandler);\n        };\n        const handleOk = () => { cleanup(); resolve(inputEl.value); };\n        const handleCancel = () => { cleanup(); resolve(null); };\n        const handleEnter = (e) => { if (e.key === \"Enter\") handleOk(); };\n        const bgHandler = (e) => { if (e.target === modal) handleCancel(); };\n        okBtn.addEventListener(\"click\", handleOk);\n        cancelBtn.addEventListener(\"click\", handleCancel);\n        inputEl.addEventListener(\"keypress\", handleEnter);\n        modal.addEventListener(\"click\", bgHandler);\n       });\n      }\n      function challengeTheImmortal(message, onConfirm) {\n       const modal = document.getElementById(\"confirm-modal\");\n       const messageEl = document.getElementById(\"confirm-message\");\n       const deleteBtn = document.getElementById(\"confirm-delete\");\n       const cancelBtn = document.getElementById(\"confirm-cancel\");\n       messageEl.textContent = message;\n       modal.classList.add(\"active\");\n\t   const cleanup = () => {\n        modal.classList.remove(\"active\");\n        deleteBtn.removeEventListener(\"click\", handleConfirm);\n        cancelBtn.removeEventListener(\"click\", handleCancel);\n        modal.removeEventListener(\"click\", bgHandler);\n       };\n       const handleConfirm = () => {\n        onConfirm();\n        cleanup();\n       };\n       const handleCancel = () => {\n        cleanup();\n       };\n       const bgHandler = (e) => {\n        if (e.target === modal) handleCancel();\n       };\n       deleteBtn.addEventListener(\"click\", handleConfirm);\n       cancelBtn.addEventListener(\"click\", handleCancel);\n       modal.addEventListener(\"click\", bgHandler);\n      }\n      const pageTitleEl = document.getElementById(\"page-title\");\n      if (pageTitleEl) {\n       pageTitleEl.addEventListener(\"click\", () => {\n        showEditModal(t(\"editModal.editTitle\"), PAGE_STATE.title || DEFAULT_PAGE_STATE.title,\n         (newTitle) => {\n          PAGE_STATE.title = newTitle;\n          wieldThePower();\n         });\n       });\n      }\n      function editNodeName(id) {\n       showEditModal(t(\"editModal.editName\"), NODE_DATA[id].name, (newName) => {\n        pushUndo(\"edit node name\");\n        NODE_DATA[id].name = newName;\n        const nodeGroup = document.querySelector(`g[data-node-id=\"${id}\"]`);\n        if (nodeGroup) {\n         const label = nodeGroup.querySelector(\".node-label\");\n         if (label) label.textContent = newName;\n        }\n        if (currentNodeId === id) {\n         document.getElementById(\"node-name\").textContent = newName;\n        }\n       });\n      }\n      function editNodeIp(id) {\n       const labels = LANG.modes[PAGE_STATE.mappingMode] || LANG.modes.network;\n       showEditModal(\"Edit \" + labels.subtitle, NODE_DATA[id].ip, (newIp) => {\n        pushUndo(\"edit node ip\");\n        NODE_DATA[id].ip = newIp;\n        const nodeGroup = document.querySelector(`g[data-node-id=\"${id}\"]`);\n        if (nodeGroup) {\n         const sub = nodeGroup.querySelector(\".node-sub\");\n         if (sub) sub.textContent = newIp;\n        }\n        if (currentNodeId === id) {\n         const nodeIpEl = document.getElementById(\"node-ip\");\n         if (newIp) {\n           nodeIpEl.textContent = newIp;\n           nodeIpEl.style.opacity = \"1\";\n         } else {\n           nodeIpEl.textContent = \"Click to add \" + labels.subtitle;\n           nodeIpEl.style.opacity = \"0.5\";\n         }\n        }\n       });\n      }\n      function claimTheImmortal(id) {\n\t   if (isViewOnly()) return;\n\t   if (!NODE_DATA[id]) return;\n       currentNodeId = id;\n       currentEdgeId = null;\n       currentRectId = null;\n       currentTextId = null;\n       if (isMobileDevice()) {\n        const currentHeight = PAGE_STATE.mobileFooterHeight || 40;\n        if (currentHeight <= 20) {\n         PAGE_STATE.mobileFooterHeight = 80;\n         const root = document.documentElement;\n         root.style.setProperty(\"--mobile-footer-height\", \"80vh\");\n         const mobileFooterHeightInput = document.getElementById(\"mobile-footer-height\");\n         const mobileFooterHeightVal = document.getElementById(\"mobile-footer-height-val\");\n         if (mobileFooterHeightInput) mobileFooterHeightInput.value = 80;\n         if (mobileFooterHeightVal) mobileFooterHeightVal.textContent = \"80%\";\n        }\n       }\n       const data = NODE_DATA[id];\n       document.querySelectorAll(\".node-group\").forEach((n) => {\n        n.classList.toggle(\"active\", n.dataset.nodeId === id);\n       });\n       document.querySelectorAll(\".edge\").forEach((e) => {\n        const active = e.dataset.from === id || e.dataset.to === id;\n        e.classList.toggle(\"active\", active);\n       });\n       document.querySelectorAll(\".rect-group\").forEach((r) => r.classList.remove(\"active\"));\n       document.querySelectorAll(\".text-element\").forEach((t) => t.classList.remove(\"active\"));\n       document.getElementById(\"node-panel\").style.display = \"block\";\n       document.getElementById(\"edge-panel\").style.display = \"none\";\n       document.getElementById(\"text-panel\").style.display = \"none\";\n       document.getElementById(\"rect-panel\").style.display = \"none\";\n       document.getElementById(\"image-panel\").style.display = \"none\";\n       const toolbar = document.getElementById(\"topology-toolbar\");\n       if (!topologyToolbarCollapsed) {\n        toolbar.style.display = \"flex\";\n       }\n       updateTopologyToolbarVisibility();\n       document.getElementById(\"node-name\").textContent = data.name;\n       const nodeIpEl2 = document.getElementById(\"node-ip\");\n       const modeLabels2 = LANG.modes[PAGE_STATE.mappingMode] || LANG.modes.network;\n       if (data.ip) {\n         nodeIpEl2.textContent = data.ip;\n         nodeIpEl2.style.opacity = \"1\";\n       } else {\n         nodeIpEl2.textContent = \"Click to add \" + modeLabels2.subtitle;\n         nodeIpEl2.style.opacity = \"0.5\";\n       }\nconst fovSection = document.getElementById(\"fov-section\");\nif (fovSection) {\n  if (hasCoverageZone(data.shape)) {\n    const defaults = getCoverageDefaults(data.shape);\n    fovSection.style.display = \"block\";\n    document.getElementById(\"fov-enabled\").checked = data.fovEnabled || false;\n    document.getElementById(\"fov-angle\").value = data.fovAngle || defaults.angle;\n    document.getElementById(\"fov-angle-value\").textContent = (data.fovAngle || defaults.angle) + \"°\";\n    document.getElementById(\"fov-distance\").value = data.fovDistance || defaults.distance;\n    document.getElementById(\"fov-distance-value\").textContent = data.fovDistance || defaults.distance;\n    document.getElementById(\"fov-inner-radius\").value = data.fovInnerRadius || 0;\n    document.getElementById(\"fov-inner-radius-value\").textContent = data.fovInnerRadius || 0;\n    document.getElementById(\"fov-rotation\").value = data.fovRotation || 0;\n    document.getElementById(\"fov-rotation-value\").textContent = (data.fovRotation || 0) + \"°\";\n    document.getElementById(\"fov-color\").value = data.fovColor || \"#f59e0b\";\n    document.getElementById(\"fov-opacity\").value = data.fovOpacity || 20;\n    document.getElementById(\"fov-opacity-value\").textContent = (data.fovOpacity || 20) + \"%\";\n    document.getElementById(\"fov-gradient\").checked = data.fovGradient || false;\n    document.getElementById(\"fov-border-color\").value = data.fovBorderColor || \"#f59e0b\";\n    document.getElementById(\"fov-border-width\").value = data.fovBorderWidth ?? 2;\n    document.getElementById(\"fov-border-width-value\").textContent = data.fovBorderWidth ?? 2;\n    document.getElementById(\"fov-border-style\").value = data.fovBorderStyle || \"solid\";\n    document.getElementById(\"fov-border-opacity\").value = data.fovBorderOpacity ?? 100;\n    document.getElementById(\"fov-border-opacity-value\").textContent = (data.fovBorderOpacity ?? 100) + \"%\";\n    document.getElementById(\"fov-label\").value = data.fovLabel || \"\";\n    document.getElementById(\"fov-label-position\").value = data.fovLabelPosition || \"center\";\n    document.getElementById(\"fov-label-size\").value = data.fovLabelSize || 14;\n    document.getElementById(\"fov-label-size-value\").textContent = (data.fovLabelSize || 14) + \"px\";\n    document.getElementById(\"fov-label-color\").value = data.fovLabelColor || \"#ffffff\";\n    document.getElementById(\"fov-label-bold\").checked = data.fovLabelBold || false;\n    document.getElementById(\"fov-label-bg\").checked = data.fovLabelBg || false;\n    document.getElementById(\"fov-label-bg-color\").value = data.fovLabelBgColor || \"#000000\";\n    document.getElementById(\"fov-label-offset-x\").value = data.fovLabelOffsetX || 0;\n    document.getElementById(\"fov-label-offset-x-value\").textContent = data.fovLabelOffsetX || 0;\n    document.getElementById(\"fov-label-offset-y\").value = data.fovLabelOffsetY || 0;\n    document.getElementById(\"fov-label-offset-y-value\").textContent = data.fovLabelOffsetY || 0;\n    document.getElementById(\"fov-animate\").checked = data.fovAnimate || false;\n    document.getElementById(\"fov-animation-type\").value = data.fovAnimationType || defaults.animationType;\n    document.getElementById(\"fov-sweep\").value = data.fovSweep || 120;\n    document.getElementById(\"fov-sweep-value\").textContent = (data.fovSweep || 120) + \"°\";\n    document.getElementById(\"fov-speed\").value = data.fovSpeed || 4;\n    document.getElementById(\"fov-speed-value\").textContent = (data.fovSpeed || 4) + \"s\";\n  } else {\n    fovSection.style.display = \"none\";\n  }\n}\n       document.getElementById(\"node-role\").textContent = data.role;\n       document.getElementById(\"node-mac\").textContent = data.mac || \"--\";\n       document.getElementById(\"node-rack\").textContent = data.rackUnit || \"--\";\n       document.getElementById(\"node-uheight\").textContent = (data.uHeight || \"1\") + \"U\";\n       document.getElementById(\"node-layer\").value = data.layer || \"layer1\";\n       const nodeTrailEnabled = document.getElementById(\"node-trail-enabled\");\n       if (nodeTrailEnabled) {\n         nodeTrailEnabled.checked = data.trailEnabled !== false;\n       }\n       populateRackDropdown();\n       const assignedRackSelect = document.getElementById(\"node-assigned-rack\");\n       if (assignedRackSelect) {\n        assignedRackSelect.value = data.assignedRack || \"\";\n       }\n       const rackCapacitySelect = document.getElementById(\"node-rack-capacity\");\n       if (rackCapacitySelect) {\n        rackCapacitySelect.value = data.rackCapacity || \"42\";\n       }\n       const isRack = data.isRack === true;\n       const isAssignedToRack = !!data.assignedRack;\n       const assignedRackRow = document.getElementById(\"assigned-rack-row\");\n       const rackCapacityRow = document.getElementById(\"rack-capacity-row\");\n       const uheightRow = document.getElementById(\"uheight-row\");\n       if (assignedRackRow) assignedRackRow.style.display = isRack ? \"none\" : \"flex\";\n       if (rackCapacityRow) rackCapacityRow.style.display = isRack ? \"flex\" : \"none\";\n       if (uheightRow) uheightRow.style.display = isAssignedToRack ? \"flex\" : \"none\";\n       const rackContentsSection = document.getElementById(\"rack-contents-section\");\n       const rackContentsList = document.getElementById(\"rack-contents-list\");\n       if (rackContentsSection && rackContentsList) {\n        if (isRack) {\n         const nodesInRack = Object.entries(NODE_DATA).filter(([nid, n]) => n.assignedRack === id);\n         if (nodesInRack.length > 0) {\n          rackContentsList.innerHTML = '';\n          nodesInRack.forEach(([nid, n]) => {\n           const div = document.createElement('div');\n           div.style.cssText = 'padding: 6px 0; border-bottom: 1px solid var(--edge-main); cursor: pointer;';\n           div.onclick = () => claimTheImmortal(nid);\n           const nameSpan = document.createElement('span');\n           nameSpan.style.color = 'var(--text-main)';\n           nameSpan.textContent = n.name;\n           div.appendChild(nameSpan);\n           (n.tags || []).forEach(t => {\n            const tagSpan = document.createElement('span');\n            tagSpan.style.cssText = 'background: var(--accent); color: var(--bg); padding: 2px 6px; border-radius: 4px; font-size: 11px; margin-left: 4px;';\n            tagSpan.textContent = t;\n            div.appendChild(tagSpan);\n           });\n           rackContentsList.appendChild(div);\n          });\n          rackContentsSection.style.display = \"block\";\n         } else {\n          rackContentsList.innerHTML = '<div style=\"color: var(--text-soft); font-style: italic;\">' + t(\"emptyStates.noNodesAssigned\") + '</div>';\n          rackContentsSection.style.display = \"block\";\n         }\n        } else {\n         rackContentsSection.style.display = \"none\";\n        }\n       }\n       const connectionsSection = document.getElementById(\"node-connections-section\");\n       const connectionsList = document.getElementById(\"node-connections-list\");\n       const connectionsCount = document.getElementById(\"node-connections-count\");\n       if (connectionsSection && connectionsList && connectionsCount) {\n        const connectedEdges = (EDGE_DATA.list || []).filter(e => e.from === id || e.to === id);\n        connectionsCount.textContent = connectedEdges.length;\n        if (connectedEdges.length > 0) {\n         connectionsList.innerHTML = '';\n         connectedEdges.forEach(e => {\n          const isFrom = e.from === id;\n          const localPort = isFrom ? e.fromPort : e.toPort;\n          const remoteNodeId = isFrom ? e.to : e.from;\n          const remotePort = isFrom ? e.toPort : e.fromPort;\n          const remoteName = NODE_DATA[remoteNodeId]?.name || remoteNodeId;\n          const div = document.createElement('div');\n          div.style.cssText = 'padding: 6px 0; border-bottom: 1px solid var(--edge-main); display: flex; align-items: center; font-size: 13px;';\n          const localSpan = document.createElement('span');\n          localSpan.style.cssText = localPort ? 'color: var(--accent); font-family: monospace; cursor: pointer;' : 'color: var(--text-soft); cursor: pointer;';\n          localSpan.textContent = localPort || '-';\n          localSpan.title = localPort ? 'Click to view connection' : 'Click to set port';\n          localSpan.onclick = async () => { if (localPort) { selectTheConnection(e.id); } else { const nodeName = NODE_DATA[id]?.name || id; const newVal = await showPrompt(t(\"dialogs.enterPort\", { nodeName: nodeName }), ''); if (newVal !== null && newVal !== '') { const isDupe = (EDGE_DATA.list || []).some(x => x.id !== e.id && ((x.from === id && x.fromPort === newVal) || (x.to === id && x.toPort === newVal))); if (isDupe && !await showConfirm(t(\"dialogs.portAlreadyUsed\", { port: newVal, nodeName: nodeName }))) return; if (isFrom) e.fromPort = newVal; else e.toPort = newVal; claimTheImmortal(id); } } };\n          const arrow = document.createElement('span');\n          arrow.style.cssText = 'color: var(--text-soft); margin: 0 8px;';\n          arrow.textContent = '↔';\n          const remoteSpan = document.createElement('span');\n          remoteSpan.style.cssText = 'color: var(--text-main); cursor: pointer;';\n          remoteSpan.textContent = remoteName;\n          remoteSpan.title = 'Click to view connection';\n          remoteSpan.onclick = () => { claimTheImmortal(remoteNodeId); focusOnSelected(); };\n          const remotePortSpan = document.createElement('span');\n          remotePortSpan.style.cssText = remotePort ? 'color: var(--accent); font-family: monospace; margin-left: 6px; cursor: pointer;' : 'color: var(--text-soft); margin-left: 6px; cursor: pointer;';\n          remotePortSpan.textContent = '(' + (remotePort || '-') + ')';\n          remotePortSpan.title = remotePort ? 'Click to view connection' : 'Click to set port';\n          remotePortSpan.onclick = async () => { if (remotePort) { selectTheConnection(e.id); } else { const newVal = await showPrompt(t(\"dialogs.enterPort\", { nodeName: remoteName }), ''); if (newVal !== null && newVal !== '') { const isDupe = (EDGE_DATA.list || []).some(x => x.id !== e.id && ((x.from === remoteNodeId && x.fromPort === newVal) || (x.to === remoteNodeId && x.toPort === newVal))); if (isDupe && !await showConfirm(t(\"dialogs.portAlreadyUsed\", { port: newVal, nodeName: remoteName }))) return; if (isFrom) e.toPort = newVal; else e.fromPort = newVal; claimTheImmortal(id); } } };\n          div.appendChild(localSpan);\n          div.appendChild(arrow);\n          div.appendChild(remoteSpan);\n          div.appendChild(remotePortSpan);\n          connectionsList.appendChild(div);\n         });\n         connectionsSection.style.display = \"block\";\n        } else {\n         connectionsList.innerHTML = '<div style=\"color: var(--text-soft); font-style: italic;\">' + t(\"emptyStates.noConnections\") + '</div>';\n         connectionsSection.style.display = \"block\";\n        }\n       }\n       document.getElementById(\"node-name\").onclick = () => editNodeName(id);\n       document.getElementById(\"node-ip\").onclick = () => editNodeIp(id);\ndocument.getElementById(\"fov-enabled\").onchange = function() {\n  if (!currentNodeId) return;\n  pushUndo(\"toggle coverage zone\");\n  NODE_DATA[currentNodeId].fovEnabled = this.checked;\n  updateFovCone(currentNodeId);\n};\ndocument.getElementById(\"fov-angle\").oninput = function() {\n  document.getElementById(\"fov-angle-value\").textContent = this.value + \"°\";\n  if (!currentNodeId) return;\n  NODE_DATA[currentNodeId].fovAngle = parseInt(this.value);\n  updateFovCone(currentNodeId);\n};\ndocument.getElementById(\"fov-distance\").oninput = function() {\n  document.getElementById(\"fov-distance-value\").textContent = this.value;\n  if (!currentNodeId) return;\n  NODE_DATA[currentNodeId].fovDistance = parseInt(this.value);\n  updateFovCone(currentNodeId);\n};\ndocument.getElementById(\"fov-inner-radius\").oninput = function() {\n  document.getElementById(\"fov-inner-radius-value\").textContent = this.value;\n  if (!currentNodeId) return;\n  NODE_DATA[currentNodeId].fovInnerRadius = parseInt(this.value);\n  updateFovCone(currentNodeId);\n};\ndocument.getElementById(\"fov-rotation\").oninput = function() {\n  document.getElementById(\"fov-rotation-value\").textContent = this.value + \"°\";\n  if (!currentNodeId) return;\n  NODE_DATA[currentNodeId].fovRotation = parseInt(this.value);\n  updateFovCone(currentNodeId);\n};\ndocument.getElementById(\"fov-color\").oninput = function() {\n  if (!currentNodeId) return;\n  pushUndo(\"change coverage color\");\n  NODE_DATA[currentNodeId].fovColor = this.value;\n  updateFovCone(currentNodeId);\n  updateZoneLegend();\n};\ndocument.getElementById(\"fov-opacity\").oninput = function() {\n  document.getElementById(\"fov-opacity-value\").textContent = this.value + \"%\";\n  if (!currentNodeId) return;\n  NODE_DATA[currentNodeId].fovOpacity = parseInt(this.value);\n  updateFovCone(currentNodeId);\n};\ndocument.getElementById(\"fov-gradient\").onchange = function() {\n  if (!currentNodeId) return;\n  pushUndo(\"toggle coverage gradient\");\n  NODE_DATA[currentNodeId].fovGradient = this.checked;\n  updateFovCone(currentNodeId);\n};\ndocument.getElementById(\"fov-border-color\").oninput = function() {\n  if (!currentNodeId) return;\n  pushUndo(\"change coverage border color\");\n  NODE_DATA[currentNodeId].fovBorderColor = this.value;\n  updateFovCone(currentNodeId);\n};\ndocument.getElementById(\"fov-border-width\").oninput = function() {\n  document.getElementById(\"fov-border-width-value\").textContent = this.value;\n  if (!currentNodeId) return;\n  NODE_DATA[currentNodeId].fovBorderWidth = parseInt(this.value);\n  updateFovCone(currentNodeId);\n};\ndocument.getElementById(\"fov-border-style\").onchange = function() {\n  if (!currentNodeId) return;\n  pushUndo(\"change coverage border style\");\n  NODE_DATA[currentNodeId].fovBorderStyle = this.value;\n  updateFovCone(currentNodeId);\n};\ndocument.getElementById(\"fov-border-opacity\").oninput = function() {\n  document.getElementById(\"fov-border-opacity-value\").textContent = this.value + \"%\";\n  if (!currentNodeId) return;\n  NODE_DATA[currentNodeId].fovBorderOpacity = parseInt(this.value);\n  updateFovCone(currentNodeId);\n};\ndocument.getElementById(\"fov-label\").oninput = function() {\n  if (!currentNodeId) return;\n  NODE_DATA[currentNodeId].fovLabel = this.value;\n  updateFovCone(currentNodeId);\n};\ndocument.getElementById(\"fov-label-position\").onchange = function() {\n  if (!currentNodeId) return;\n  NODE_DATA[currentNodeId].fovLabelPosition = this.value;\n  updateFovCone(currentNodeId);\n};\ndocument.getElementById(\"fov-label-size\").oninput = function() {\n  document.getElementById(\"fov-label-size-value\").textContent = this.value + \"px\";\n  if (!currentNodeId) return;\n  NODE_DATA[currentNodeId].fovLabelSize = parseInt(this.value);\n  updateFovCone(currentNodeId);\n};\ndocument.getElementById(\"fov-label-color\").oninput = function() {\n  if (!currentNodeId) return;\n  NODE_DATA[currentNodeId].fovLabelColor = this.value;\n  updateFovCone(currentNodeId);\n};\ndocument.getElementById(\"fov-label-bold\").onchange = function() {\n  if (!currentNodeId) return;\n  NODE_DATA[currentNodeId].fovLabelBold = this.checked;\n  updateFovCone(currentNodeId);\n};\ndocument.getElementById(\"fov-label-bg\").onchange = function() {\n  if (!currentNodeId) return;\n  NODE_DATA[currentNodeId].fovLabelBg = this.checked;\n  updateFovCone(currentNodeId);\n};\ndocument.getElementById(\"fov-label-bg-color\").oninput = function() {\n  if (!currentNodeId) return;\n  NODE_DATA[currentNodeId].fovLabelBgColor = this.value;\n  updateFovCone(currentNodeId);\n};\ndocument.getElementById(\"fov-label-offset-x\").oninput = function() {\n  document.getElementById(\"fov-label-offset-x-value\").textContent = this.value;\n  if (!currentNodeId) return;\n  NODE_DATA[currentNodeId].fovLabelOffsetX = parseInt(this.value);\n  updateFovCone(currentNodeId);\n};\ndocument.getElementById(\"fov-label-offset-y\").oninput = function() {\n  document.getElementById(\"fov-label-offset-y-value\").textContent = this.value;\n  if (!currentNodeId) return;\n  NODE_DATA[currentNodeId].fovLabelOffsetY = parseInt(this.value);\n  updateFovCone(currentNodeId);\n};\ndocument.getElementById(\"fov-preset\").addEventListener(\"change\", function() {\n  if (this.value) {\n    applyZonePreset(this.value);\n    this.value = \"\";\n  }\n});\ndocument.getElementById(\"fov-save-preset\").addEventListener(\"click\", saveCustomZonePreset);\ndocument.getElementById(\"fov-copy-style\").addEventListener(\"click\", function() {\n  if (currentNodeId && copyZoneStyle(currentNodeId)) {\n    showAlert(t(\"dialogs.zoneStyleCopiedShort\"));\n  }\n});\ndocument.getElementById(\"fov-paste-style\").addEventListener(\"click\", function() {\n  if (currentNodeId && pasteZoneStyle(currentNodeId)) {\n    claimTheImmortal(currentNodeId);\n  }\n});\ndocument.getElementById(\"bulk-zone-copy\").addEventListener(\"click\", bulkCopyZoneStyle);\ndocument.getElementById(\"bulk-zone-paste\").addEventListener(\"click\", bulkPasteZoneStyle);\ndocument.getElementById(\"bulk-zone-toggle\").addEventListener(\"click\", bulkToggleZones);\ndocument.getElementById(\"bulk-zone-copy-mobile\").addEventListener(\"click\", bulkCopyZoneStyle);\ndocument.getElementById(\"bulk-zone-paste-mobile\").addEventListener(\"click\", bulkPasteZoneStyle);\ndocument.getElementById(\"bulk-zone-toggle-mobile\").addEventListener(\"click\", bulkToggleZones);\ndocument.getElementById(\"fov-animate\").onchange = function() {\n  if (!currentNodeId) return;\n  pushUndo(\"toggle coverage animation\");\n  NODE_DATA[currentNodeId].fovAnimate = this.checked;\n  updateFovCone(currentNodeId);\n};\ndocument.getElementById(\"fov-animation-type\").onchange = function() {\n  if (!currentNodeId) return;\n  pushUndo(\"change animation type\");\n  NODE_DATA[currentNodeId].fovAnimationType = this.value;\n  updateFovCone(currentNodeId);\n};\ndocument.getElementById(\"fov-sweep\").oninput = function() {\n  document.getElementById(\"fov-sweep-value\").textContent = this.value + \"°\";\n  if (!currentNodeId) return;\n  NODE_DATA[currentNodeId].fovSweep = parseInt(this.value);\n  updateFovCone(currentNodeId);\n};\ndocument.getElementById(\"fov-speed\").oninput = function() {\n  document.getElementById(\"fov-speed-value\").textContent = this.value + \"s\";\n  if (!currentNodeId) return;\n  NODE_DATA[currentNodeId].fovSpeed = parseInt(this.value);\n  updateFovCone(currentNodeId);\n};\n       document.getElementById(\"node-mac\").onclick = () => editNodeMac(id);\n       document.getElementById(\"node-rack\").onclick = () => editNodeRack(id);\n       document.getElementById(\"node-uheight\").onclick = () => editNodeUHeight(id);\n       const currentSize = savedSizes[id] || getDefaultSize();\n       document.getElementById(\"size-slider\").value = currentSize;\n       document.getElementById(\"size-value\").textContent = currentSize;\n       const currentRotation = NODE_DATA[id].rotation || 0;\n       document.getElementById(\"rotation-slider\").value = Math.max(-360, Math.min(360, currentRotation));\n       document.getElementById(\"rotation-value\").value = currentRotation;\n       const styleEntry = savedStyles[id] || {};\n       const resolvedStyles = resolveStylesEntry(styleEntry);\n       const scopeKey = currentStyleScope || \"all\";\n       const flatProps = [\"circleColor\", \"circleBorder\", \"titleColor\", \"titleFont\", \"titleSize\", \"subColor\", \"subFont\", \"subSize\", ];\n       const isFlat = flatProps.some((p) => Object.prototype.hasOwnProperty.call(styleEntry, p));\n       const scopedStyles = isFlat ? styleEntry : styleEntry[scopeKey] || {};\n       const circleColorInput = document.getElementById(\"circle-color\");\n       const titleColorInput = document.getElementById(\"title-color\");\n       const titleFontSelect = document.getElementById(\"title-font\");\n       const titleSizeInput = document.getElementById(\"title-size\");\n       const subColorInput = document.getElementById(\"sub-color\");\n       const subFontSelect = document.getElementById(\"sub-font\");\n       const subSizeInput = document.getElementById(\"sub-size\");\n       const shapeSelect = document.getElementById(\"shape-select\");\n       const scopeSelect = document.getElementById(\"style-scope\");\n       const catSelect = document.getElementById(\"shape-category-select\");\n       const nodeShape = data.shape || \"circle\";\n       const shapeCategory = findCategoryForShape(nodeShape);\n       if (catSelect) {\n         catSelect.value = shapeCategory;\n         populateShapeSelect(nodeShape);\n       }\n      circleColorInput.value = scopedStyles.circleColor || resolvedStyles.circleColor || PAGE_STATE.nodeFill || \"#1e293b\";\n       const circleBorderInput = document.getElementById(\"circle-border\");\n       circleBorderInput.value = scopedStyles.circleBorder || resolvedStyles.circleBorder || PAGE_STATE.nodeStroke || \"#475569\";\n        titleColorInput.value = scopedStyles.titleColor || resolvedStyles.titleColor || PAGE_STATE.textMain || \"#e2e8f0\";\n        titleFontSelect.value = scopedStyles.titleFont || resolvedStyles.titleFont || \"system-ui, sans-serif\";\n        titleSizeInput.value = scopedStyles.titleSize || resolvedStyles.titleSize || 18;\n        subColorInput.value = scopedStyles.subColor || resolvedStyles.subColor || PAGE_STATE.textSoft || \"#94a3b8\";\n       subFontSelect.value = scopedStyles.subFont || resolvedStyles.subFont || \"system-ui, sans-serif\";\n       subSizeInput.value = scopedStyles.subSize || resolvedStyles.subSize || 13;\n       scopeSelect.value = currentStyleScope || \"all\";\n       document.getElementById(\"title-offset-y\").value = scopedStyles.titleOffsetY || resolvedStyles.titleOffsetY || 0;\n       document.getElementById(\"title-offset-x\").value = scopedStyles.titleOffsetX || resolvedStyles.titleOffsetX || 0;\n       document.getElementById(\"sub-offset-y\").value = scopedStyles.subOffsetY || resolvedStyles.subOffsetY || 0;\n       document.getElementById(\"sub-offset-x\").value = scopedStyles.subOffsetX || resolvedStyles.subOffsetX || 0;\n       const tagEl = document.getElementById(\"node-tags\");\n       tagEl.innerHTML = \"\";\n       data.tags.forEach((tag, i) => {\n        if (typeof tag !== 'string') return;\n        const b = document.createElement(\"span\");\n        b.className = \"badge\";\n        if (tag.toLowerCase().includes(\"wg\")) b.classList.add(\"wg\");\n        b.style.cursor = \"pointer\";\n        b.style.position = \"relative\";\n        const tagText = document.createElement(\"span\");\n        tagText.textContent = tag;\n        tagText.addEventListener(\"click\", (e) => {\n         e.stopPropagation();\n         showEditModal(t(\"editModal.editTag\"), tag, (newTag) => {\n          if (newTag) {\n           pushUndo(\"edit tag\");\n           data.tags[i] = newTag;\n           claimTheImmortal(id);\n          }\n         });\n        });\n        const deleteTag = document.createElement(\"span\");\n        deleteTag.textContent = \" ✕\";\n        deleteTag.style.opacity = \"0.6\";\n        deleteTag.style.marginLeft = \"4px\";\n        deleteTag.style.fontSize = \"10px\";\n        deleteTag.addEventListener(\"click\", (e) => {\n         e.stopPropagation();\n         pushUndo(\"delete tag\");\n         data.tags.splice(i, 1);\n         claimTheImmortal(id);\n        });\n        b.append(tagText, deleteTag);\n        tagEl.append(b);\n       });\n       const addTagBtn = document.createElement(\"span\");\n       addTagBtn.className = \"badge\";\n       addTagBtn.style.cursor = \"pointer\";\n       addTagBtn.style.opacity = \"0.6\";\n       addTagBtn.style.borderStyle = \"dashed\";\n       addTagBtn.textContent = t(\"ui.buttons.addTag\");\n       addTagBtn.addEventListener(\"click\", () => {\n        showEditModal(t(\"editModal.addTags\"), \"\",\n         (newTagStr) => {\n          if (newTagStr) {\n           pushUndo(\"add tags\");\n           const newTags = newTagStr.split(\",\").map((t) => t.trim()).filter((t) => t);\n           newTags.forEach((t) => data.tags.push(t));\n           claimTheImmortal(id);\n          }\n         });\n       });\n       tagEl.append(addTagBtn);\n       renderNotesFeed(data.notes, \"node-notes\", \"node\", id);\n      const addLineSelect = document.getElementById(\"add-line-select\");\n      addLineSelect.innerHTML = \"\";\n      const currentRack = data.assignedRack || \"\";\n      Object.entries(NODE_DATA).forEach(([nodeId, node]) => {\n      if (nodeId !== id) {\n      const nodeRack = node.assignedRack || \"\";\n      if (currentRack === nodeRack) {\n      const opt = document.createElement(\"option\");\n      opt.value = nodeId;\n      opt.textContent = node.name;\n      addLineSelect.appendChild(opt);\n      }\n      }\n      });\n      }\n      function selectTheConnection(id) {\n\t   if (isViewOnly()) return;\n       currentEdgeId = id;\n       currentNodeId = null;\n       currentRectId = null;\n       currentTextId = null;\n       if (isMobileDevice()) {\n        const currentHeight = PAGE_STATE.mobileFooterHeight || 40;\n        if (currentHeight <= 20) {\n         PAGE_STATE.mobileFooterHeight = 80;\n         const root = document.documentElement;\n         root.style.setProperty(\"--mobile-footer-height\", \"80vh\");\n         const mobileFooterHeightInput = document.getElementById(\"mobile-footer-height\");\n         const mobileFooterHeightVal = document.getElementById(\"mobile-footer-height-val\");\n         if (mobileFooterHeightInput) mobileFooterHeightInput.value = 80;\n         if (mobileFooterHeightVal) mobileFooterHeightVal.textContent = \"80%\";\n        }\n       }\n       document.getElementById(\"node-panel\").style.display = \"none\";\n       document.getElementById(\"edge-panel\").style.display = \"block\";\n       document.getElementById(\"text-panel\").style.display = \"none\";\n       document.getElementById(\"rect-panel\").style.display = \"none\";\n       document.getElementById(\"image-panel\").style.display = \"none\";\n       document.getElementById(\"topology-toolbar\").style.display = \"none\";\n       document.querySelectorAll(\".node-group\").forEach((n) => n.classList.remove(\"active\"));\n       document.querySelectorAll(\".rect-group\").forEach((r) => r.classList.remove(\"active\"));\n       document.querySelectorAll(\".text-element\").forEach((t) => t.classList.remove(\"active\"));\n       document.querySelectorAll(\".edge\").forEach((e) => {\n        e.classList.toggle(\"active\", e.dataset.edgeId === id);\n       });\n       const edge = EDGE_DATA.list.find((e) => e.id === id);\n       if (!edge) return;\n       const directionSymbols = {\n        none: \"⇄\",\n        forward: \"→\",\n        backward: \"←\",\n        both: \"↔\",\n       };\n       const dirSymbol = directionSymbols[edge.direction] || \"⇄\";\n       let titleText = \"Custom line\";\n       if (edge.from || edge.to) {\n        const fromName = edge.from ? NODE_DATA[edge.from]?.name || edge.from : \"\";\n        const toName = edge.to ? NODE_DATA[edge.to]?.name || edge.to : \"\";\n        titleText = `${fromName || \"?\"} ${dirSymbol} ${toName || \"?\"}`;\n       }\n       document.getElementById(\"edge-title\").textContent = titleText;\n       const widthInput = document.getElementById(\"edge-width\");\n       const colorInput = document.getElementById(\"edge-color\");\n       const directionSelect = document.getElementById(\"edge-direction\");\n       const lineStyleSelect = document.getElementById(\"edge-line-style\");\n       const routingSelect = document.getElementById(\"edge-routing\");\n       widthInput.value = edge.width;\n       colorInput.value = edge.color;\n       directionSelect.value = edge.direction || \"none\";\n       lineStyleSelect.value = edge.lineStyle || \"solid\";\n       routingSelect.value = edge.routing || \"curved\";\n       const waypointsRow = document.getElementById(\"waypoints-row\");\n       const waypoints = edge.waypoints || [];\n       if (edge.type !== \"custom\" && waypoints.length > 0) {\n        waypointsRow.style.display = \"flex\";\n        document.getElementById(\"waypoints-label\").textContent = `${t(\"ui.labels.waypoints\")} (${waypoints.length}):`;\n       } else {\n        waypointsRow.style.display = \"none\";\n       }\n       document.getElementById(\"edge-animate\").checked = edge.animate === true;\n       document.getElementById(\"edge-animation-style\").value = edge.animationStyle || \"\";\n       document.getElementById(\"edge-animation-speed\").value = edge.animationSpeed || \"\";\n       const fromPortRow = document.getElementById(\"edge-from-port-row\");\n       const toPortRow = document.getElementById(\"edge-to-port-row\");\n       const fromPortInput = document.getElementById(\"edge-from-port\");\n       const toPortInput = document.getElementById(\"edge-to-port\");\n       if (edge.type === \"custom\") {\n        fromPortRow.style.display = \"none\";\n        toPortRow.style.display = \"none\";\n       } else {\n        fromPortRow.style.display = \"flex\";\n        toPortRow.style.display = \"flex\";\n        fromPortInput.value = edge.fromPort || \"\";\n        toPortInput.value = edge.toPort || \"\";\n        fromPortInput.onchange = () => updateEdgePortLabels(id);\n        toPortInput.onchange = () => updateEdgePortLabels(id);\n       }\n       renderNotesFeed(edge.notes, \"edge-notes\", \"edge\", id);\n       if (edge.type === \"custom\" && Array.isArray(edge.points)) {\n        forgeTheTopology();\n       }\n      }\n      window.addEventListener(\"resize\", () => {\n       forgeTheTopology();\n       if (currentEdgeId) {\n        selectTheConnection(currentEdgeId);\n       } else if (currentNodeId && NODE_DATA[currentNodeId]) {\n        claimTheImmortal(currentNodeId);\n       } else {\n        const availableNodes = Object.keys(NODE_DATA);\n        if (availableNodes.length > 0) {\n         claimTheImmortal(availableNodes[0]);\n        }\n       }\n      });\n      (function initZoomPan() {\n       const viewport = document.getElementById(\"canvas-viewport\");\n       const svg = document.getElementById(\"map\");\n       const hint = document.getElementById(\"canvas-hint\");\n       hint.style.cursor = \"pointer\";\n       let hintDismissed = false;\n       const dismissHint = () => { hintDismissed = true; hint.classList.remove(\"visible\"); };\n       hint.addEventListener(\"click\", dismissHint);\n       hint.addEventListener(\"touchend\", (e) => { e.preventDefault(); dismissHint(); });\n       setTimeout(() => {\n        if (hintDismissed) return;\n        hint.classList.add(\"visible\");\n        setTimeout(() => { if (!hintDismissed) hint.classList.remove(\"visible\"); }, 4000);\n       }, 1000);\n       viewport.addEventListener(\"wheel\",\n        (e) => {\n         e.preventDefault();\n         const rect = viewport.getBoundingClientRect();\n         const mouseX = (e.clientX - rect.left) / rect.width;\n         const mouseY = (e.clientY - rect.top) / rect.height;\n         const delta = e.deltaY > 0 ? 0.9 : 1.1;\n         zoomTo(canvasState.zoom * delta, mouseX, mouseY);\n        }, {\n         passive: false\n        });\n       let initialPinchDistance = 0;\n       let initialPinchZoom = 1;\n       let pinchCenter = {\n        x: 0.5,\n        y: 0.5\n       };\n       let threeFingerTapStart = 0;\n       viewport.addEventListener(\"touchstart\",\n        (e) => {\n         if (e.touches.length === 3) {\n          e.preventDefault();\n          threeFingerTapStart = Date.now();\n         }\n         if (e.touches.length === 2) {\n          e.preventDefault();\n          const touch1 = e.touches[0];\n          const touch2 = e.touches[1];\n          initialPinchDistance = Math.hypot(touch2.clientX - touch1.clientX, touch2.clientY - touch1.clientY);\n          initialPinchZoom = canvasState.zoom;\n          const rect = viewport.getBoundingClientRect();\n          const centerX = (touch1.clientX + touch2.clientX) / 2;\n          const centerY = (touch1.clientY + touch2.clientY) / 2;\n          pinchCenter.x = (centerX - rect.left) / rect.width;\n          pinchCenter.y = (centerY - rect.top) / rect.height;\n         }\n        }, {\n         passive: false\n        });\n       viewport.addEventListener(\"touchend\", (e) => {\n        if (e.touches.length === 0 && threeFingerTapStart > 0) {\n         const duration = Date.now() - threeFingerTapStart;\n         if (duration < 500) {\n          e.preventDefault();\n          undo();\n          if (navigator.vibrate) navigator.vibrate([50, 30, 50]);\n         }\n         threeFingerTapStart = 0;\n        }\n       }, { passive: false });\n       viewport.addEventListener(\"touchmove\",\n        (e) => {\n         if (e.touches.length === 2) {\n          e.preventDefault();\n          const touch1 = e.touches[0];\n          const touch2 = e.touches[1];\n          const currentDistance = Math.hypot(touch2.clientX - touch1.clientX, touch2.clientY - touch1.clientY);\n          if (initialPinchDistance > 0) {\n           const scale = currentDistance / initialPinchDistance;\n           const newZoom = initialPinchZoom * scale;\n           zoomTo(newZoom, pinchCenter.x, pinchCenter.y);\n          }\n         }\n        }, {\n         passive: false\n        });\n       let panStartViewX = 0;\n       let panStartViewY = 0;\n       let lastEmptyTapTime = 0;\n       let emptyTapTimeout = null;\n       let emptyTapMoved = false;\n       let emptyTapStartX = 0;\n       let emptyTapStartY = 0;\n       viewport.addEventListener(\"touchend\", (e) => {\n         if (currentView.mode !== \"rack\") return;\n         if (e.changedTouches.length !== 1) return;\n      const isNodeOrEdge = e.target.closest(\".node-group\") || e.target.closest(\".edge-group\");\n         if (isNodeOrEdge) return;\n         if (emptyTapMoved) {\n           emptyTapMoved = false;\n           return;\n         }\n         const currentTime = new Date().getTime();\n         const tapGap = currentTime - lastEmptyTapTime;\n         if (tapGap < 300 && tapGap > 0) {\n           e.preventDefault();\n           exitRack();\n           if (navigator.vibrate) {\n             navigator.vibrate(50);\n           }\n           lastEmptyTapTime = 0;\n           if (emptyTapTimeout) {\n             clearTimeout(emptyTapTimeout);\n             emptyTapTimeout = null;\n           }\n         } else {\n           lastEmptyTapTime = currentTime;\n           if (emptyTapTimeout) clearTimeout(emptyTapTimeout);\n           emptyTapTimeout = setTimeout(() => {\n             lastEmptyTapTime = 0;\n           }, 300);\n         }\n       }, { passive: false });\n       viewport.addEventListener(\"mousedown\", (e) => {\n        if (e.target.closest(\".draw-toolbar\") || e.target.closest(\".topology-toolbar\") || e.target.closest(\".legend-container\")) {\n         return;\n        }\n        if (freeDrawMode || rectDrawMode) {\n         return;\n        }\n      const isEmptySpace = e.target === svg || e.target === viewport || e.target.tagName === \"svg\";\n      const isNodeElement = e.target.closest('.node-group');\n      const isEdgeElement = e.target.closest('.edge') || e.target.classList.contains('edge-edit-point') || e.target.dataset.edgeId;\n      const isTextElement = e.target.closest('.text-group') || e.target.closest('.text-element');\n      const isRectElement = e.target.closest('.rect-group');\n      const isImageElement = e.target.closest('.image-group');\n      if (isEmptySpace) {\n        if (currentNodeId) {\n          currentNodeId = null;\n          document.getElementById(\"node-panel\").style.display = \"none\";\n          document.querySelectorAll(\".node-group\").forEach(n => n.classList.remove(\"active\"));\n        }\n        if (currentEdgeId) {\n          currentEdgeId = null;\n          document.getElementById(\"edge-panel\").style.display = \"none\";\n        }\n        if (currentRectId) {\n          currentRectId = null;\n          document.getElementById(\"rect-panel\").style.display = \"none\";\n        }\n        if (currentTextId) {\n          currentTextId = null;\n          document.getElementById(\"text-panel\").style.display = \"none\";\n        }\n        if (currentImageId) {\n          currentImageId = null;\n          document.getElementById(\"image-panel\").style.display = \"none\";\n        }\n        forgeTheTopology();\n        updateTopologyToolbarVisibility();\n      }\n\t  if (isViewOnly()) {\n         document.body.classList.remove(\"view-only-inspect\");\n         document.getElementById(\"node-panel\").style.display = \"none\";\n         document.getElementById(\"edge-panel\").style.display = \"none\";\n        }\n        if (isEmptySpace && e.shiftKey && e.button === 0) {\n         if (isViewOnly()) return;\n         e.preventDefault();\n         startSelection(e);\n         return;\n        }\n        if (isEmptySpace || e.button === 2 || e.button === 1) {\n         e.preventDefault();\n         canvasState.isPanning = true;\n         canvasState.panStartX = e.clientX;\n         canvasState.panStartY = e.clientY;\n         panStartViewX = canvasState.panX;\n         panStartViewY = canvasState.panY;\n         viewport.classList.add(\"panning\");\n        }\n       });\n       viewport.addEventListener(\"touchstart\",\n        (e) => {\n         if (e.target.closest(\".draw-toolbar\") || e.target.closest(\".topology-toolbar\") || e.target.closest(\".legend-container\")) {\n          return;\n         }\n         if (freeDrawMode || rectDrawMode) {\n          return;\n         }\n      const isEmptySpace = e.target === svg || e.target === viewport || e.target.tagName === \"svg\";\n      if (isEmptySpace) {\n        if (currentNodeId) {\n          currentNodeId = null;\n          document.getElementById(\"node-panel\").style.display = \"none\";\n          document.querySelectorAll(\".node-group\").forEach(n => n.classList.remove(\"active\"));\n        }\n        if (currentEdgeId) {\n          currentEdgeId = null;\n          document.getElementById(\"edge-panel\").style.display = \"none\";\n        }\n        if (currentRectId) {\n          currentRectId = null;\n          document.getElementById(\"rect-panel\").style.display = \"none\";\n        }\n        if (currentTextId) {\n          currentTextId = null;\n          document.getElementById(\"text-panel\").style.display = \"none\";\n        }\n        if (currentImageId) {\n          currentImageId = null;\n          document.getElementById(\"image-panel\").style.display = \"none\";\n        }\n        forgeTheTopology();\n        updateTopologyToolbarVisibility();\n      }\n         if (e.touches.length === 1) {\n           e.preventDefault();\n           emptyTapMoved = false;\n           emptyTapStartX = e.touches[0].clientX;\n           emptyTapStartY = e.touches[0].clientY;\n           canvasState.isPanning = true;\n           canvasState.panStartX = e.touches[0].clientX;\n           canvasState.panStartY = e.touches[0].clientY;\n           panStartViewX = canvasState.panX;\n           panStartViewY = canvasState.panY;\n           viewport.classList.add(\"panning\");\n         }\n        }, {\n         passive: false\n        });\n       let panRAFPending = false;\n       let lastPanEvent = null;\n       document.addEventListener(\"mousemove\", (e) => {\n        if (isSelecting) {\n         updateSelection(e);\n         return;\n        }\n        if (!canvasState.isPanning) return;\n        lastPanEvent = e;\n        if (!panRAFPending) {\n         panRAFPending = true;\n         requestAnimationFrame(() => {\n          if (lastPanEvent && canvasState.isPanning) {\n           const dx = lastPanEvent.clientX - canvasState.panStartX;\n           const dy = lastPanEvent.clientY - canvasState.panStartY;\n           const rect = viewport.getBoundingClientRect();\n           const viewWidth = CANVAS_WIDTH / canvasState.zoom;\n           const viewHeight = CANVAS_HEIGHT / canvasState.zoom;\n           const canvasDx = (dx / rect.width) * viewWidth;\n           const canvasDy = (dy / rect.height) * viewHeight;\n           canvasState.panX = panStartViewX - canvasDx;\n           canvasState.panY = panStartViewY - canvasDy;\n           constrainPan();\n           updateViewBox();\n          }\n          panRAFPending = false;\n         });\n        }\n       });\n       document.addEventListener(\"touchmove\", (e) => {\n        if (!canvasState.isPanning || !e.touches[0]) return;\n        const touchX = e.touches[0].clientX;\n        const touchY = e.touches[0].clientY;\n        const moveDx = Math.abs(touchX - emptyTapStartX);\n        const moveDy = Math.abs(touchY - emptyTapStartY);\n        if (moveDx > 15 || moveDy > 15) {\n         emptyTapMoved = true;\n        }\n        if (!panRAFPending) {\n         panRAFPending = true;\n         requestAnimationFrame(() => {\n          if (canvasState.isPanning) {\n           const dx = touchX - canvasState.panStartX;\n           const dy = touchY - canvasState.panStartY;\n           const rect = viewport.getBoundingClientRect();\n           const viewWidth = CANVAS_WIDTH / canvasState.zoom;\n           const viewHeight = CANVAS_HEIGHT / canvasState.zoom;\n           const canvasDx = (dx / rect.width) * viewWidth;\n           const canvasDy = (dy / rect.height) * viewHeight;\n           canvasState.panX = panStartViewX - canvasDx;\n           canvasState.panY = panStartViewY - canvasDy;\n           constrainPan();\n           updateViewBox();\n          }\n          panRAFPending = false;\n         });\n        }\n       });\n       document.addEventListener(\"mouseup\", () => {\n        if (isSelecting) {\n         endSelection();\n        }\n        if (canvasState.isPanning) {\n         canvasState.isPanning = false;\n         viewport.classList.remove(\"panning\");\n        }\n       });\n       document.addEventListener(\"touchend\", () => {\n        if (canvasState.isPanning) {\n         canvasState.isPanning = false;\n         viewport.classList.remove(\"panning\");\n        }\n       });\n       document.addEventListener(\"keydown\", (e) => {\n        const isEditing = document.activeElement.tagName === \"INPUT\" || document.activeElement.tagName === \"TEXTAREA\" || document.activeElement.isContentEditable;\n        if (e.code === \"Space\" && !e.repeat && !isEditing) {\n         e.preventDefault();\n         canvasState.spacePressed = true;\n         viewport.style.cursor = \"grab\";\n        }\n       });\n       document.addEventListener(\"keyup\", (e) => {\n        if (e.code === \"Space\") {\n         canvasState.spacePressed = false;\n         viewport.style.cursor = \"\";\n        }\n       });\n       document.getElementById(\"zoom-in-btn\").addEventListener(\"click\", () => {\n        zoomTo(canvasState.zoom * 1.25, 0.5, 0.5);\n       });\n       document.getElementById(\"zoom-out-btn\").addEventListener(\"click\", () => {\n        zoomTo(canvasState.zoom * 0.8, 0.5, 0.5);\n       });\n       document.getElementById(\"zoom-fit-btn\").addEventListener(\"click\", fitToContent);\n       document.getElementById(\"zoom-reset-btn\").addEventListener(\"click\", resetView);\n       const minimapContainer = document.getElementById(\"minimap-container\");\n       const minimapSvg = document.getElementById(\"minimap\");\n       let minimapDragging = false;\n       minimapContainer.addEventListener(\"mousedown\", (e) => {\n        e.preventDefault();\n        minimapDragging = true;\n        updatePanFromMinimap(e);\n       });\n       minimapContainer.addEventListener(\"touchstart\",\n        (e) => {\n         e.preventDefault();\n         minimapDragging = true;\n         updatePanFromMinimapTouch(e);\n        }, {\n         passive: false\n        });\n       document.addEventListener(\"mousemove\", (e) => {\n        if (minimapDragging) {\n         updatePanFromMinimap(e);\n        }\n       });\n       document.addEventListener(\"touchmove\", (e) => {\n        if (minimapDragging && e.touches[0]) {\n         updatePanFromMinimapTouch(e);\n        }\n       });\n       document.addEventListener(\"mouseup\", () => {\n        minimapDragging = false;\n       });\n       document.addEventListener(\"touchend\", () => {\n        minimapDragging = false;\n       });\n       function updatePanFromMinimap(e) {\n        const rect = minimapContainer.getBoundingClientRect();\n        const x = (e.clientX - rect.left) / rect.width;\n        const y = (e.clientY - rect.top) / rect.height;\n        const viewWidth = CANVAS_WIDTH / canvasState.zoom;\n        const viewHeight = CANVAS_HEIGHT / canvasState.zoom;\n        canvasState.panX = x * CANVAS_WIDTH - viewWidth / 2;\n        canvasState.panY = y * CANVAS_HEIGHT - viewHeight / 2;\n        constrainPan();\n        updateViewBox();\n       }\n       function updatePanFromMinimapTouch(e) {\n        const rect = minimapContainer.getBoundingClientRect();\n        const touch = e.touches[0];\n        const x = (touch.clientX - rect.left) / rect.width;\n        const y = (touch.clientY - rect.top) / rect.height;\n        const viewWidth = CANVAS_WIDTH / canvasState.zoom;\n        const viewHeight = CANVAS_HEIGHT / canvasState.zoom;\n        canvasState.panX = x * CANVAS_WIDTH - viewWidth / 2;\n        canvasState.panY = y * CANVAS_HEIGHT - viewHeight / 2;\n        constrainPan();\n        updateViewBox();\n       }\n       document.addEventListener(\"keydown\", (e) => {\n        if (document.activeElement.tagName === \"INPUT\" || document.activeElement.tagName === \"TEXTAREA\") return;\n        if (\n         (e.key === \"+\" || e.key === \"=\") && (e.ctrlKey || e.metaKey)) {\n         e.preventDefault();\n         zoomTo(canvasState.zoom * 1.25, 0.5, 0.5);\n        } else if (e.key === \"-\" && (e.ctrlKey || e.metaKey)) {\n         e.preventDefault();\n         zoomTo(canvasState.zoom * 0.8, 0.5, 0.5);\n        } else if (e.key === \"0\" && (e.ctrlKey || e.metaKey)) {\n         e.preventDefault();\n         resetView();\n        }\n       });\n       setTimeout(() => {\n        fitToContent();\n       }, 100);\n      })();\n      const sizeSlider = document.getElementById(\"size-slider\");\n      const sizeValue = document.getElementById(\"size-value\");\n      const resetSizeBtn = document.getElementById(\"reset-size\");\n      sizeSlider.addEventListener(\"input\", () => {\n       const newSize = parseInt(sizeSlider.value, 10);\n       sizeValue.textContent = newSize;\n       pushUndo(\"resize node\");\n       savedSizes[currentNodeId] = newSize;\n       const nodeGroup = document.querySelector(`g[data-node-id=\"${currentNodeId}\"]`);\n       if (nodeGroup) {\n        const oldShape = nodeGroup.querySelector(\".node-circle\");\n        if (oldShape) oldShape.remove();\n        const shapeType = (NODE_DATA[currentNodeId] && NODE_DATA[currentNodeId].shape) || \"circle\";\n        const newShape = createShapeElement(shapeType, newSize);\n        newShape.classList.add(\"node-circle\");\n        const styles = resolveStylesForNode(currentNodeId);\n        if (styles.circleColor) newShape.style.fill = styles.circleColor;\n        if (styles.circleBorder) newShape.style.stroke = styles.circleBorder;\n        nodeGroup.insertBefore(newShape, nodeGroup.firstChild);\n        const label = nodeGroup.querySelector(\".node-label\");\n        const sub = nodeGroup.querySelector(\".node-sub\");\n        if (label) {\n         label.setAttribute(\"y\", -newSize - 5);\n         const labelSize = styles.titleSize || newSize * 0.33;\n         label.style.fontSize = labelSize + \"px\";\n        }\n        if (sub) {\n         sub.setAttribute(\"y\", newSize + 20);\n         const subSize = styles.subSize || newSize * 0.24;\n         sub.style.fontSize = subSize + \"px\";\n        }\n       }\n       updateMinimap();\n      });\n      resetSizeBtn.addEventListener(\"click\", () => {\n       pushUndo(\"reset size\");\n       delete savedSizes[currentNodeId];\n       const defaultSize = getDefaultSize();\n       sizeSlider.value = defaultSize;\n       sizeValue.textContent = defaultSize;\n       const nodeGroup = document.querySelector(`g[data-node-id=\"${currentNodeId}\"]`);\n       if (nodeGroup) {\n        const oldShape = nodeGroup.querySelector(\".node-circle\");\n        if (oldShape) oldShape.remove();\n        const shapeType = (NODE_DATA[currentNodeId] && NODE_DATA[currentNodeId].shape) || \"circle\";\n        const newShape = createNodeShape(currentNodeId, defaultSize);\n        nodeGroup.insertBefore(newShape, nodeGroup.firstChild);\n        const styles = resolveStylesForNode(currentNodeId);\n        if (styles.circleColor) newShape.style.fill = styles.circleColor;\n        if (styles.circleBorder) newShape.style.stroke = styles.circleBorder;\n        const label = nodeGroup.querySelector(\".node-label\");\n        const sub = nodeGroup.querySelector(\".node-sub\");\n        if (label) {\n         label.setAttribute(\"y\", -defaultSize - 5);\n         const labelSize = styles.titleSize || defaultSize * 0.33;\n         label.style.fontSize = labelSize + \"px\";\n        }\n        if (sub) {\n         sub.setAttribute(\"y\", defaultSize + 20);\n         const subSize = styles.subSize || defaultSize * 0.24;\n         sub.style.fontSize = subSize + \"px\";\n        }\n       }\n       updateMinimap();\n      });\n      const rotationSlider = document.getElementById(\"rotation-slider\");\n      const rotationInput = document.getElementById(\"rotation-value\");\n      const resetRotationBtn = document.getElementById(\"reset-rotation\");\n      rotationSlider.addEventListener(\"input\", () => {\n        const newRotation = parseInt(rotationSlider.value, 10);\n        rotationInput.value = newRotation;\n        pushUndo(\"rotate node\");\n        NODE_DATA[currentNodeId].rotation = newRotation;\n        const nodeGroup = document.querySelector(`g[data-node-id=\"${currentNodeId}\"]`);\n        if (nodeGroup) {\n          const pos = savedPositions[currentNodeId] || { x: 0, y: 0 };\n          nodeGroup.setAttribute(\"transform\", `translate(${pos.x},${pos.y}) rotate(${newRotation})`);\n        }\n      });\n      rotationInput.addEventListener(\"input\", () => {\n        const newRotation = parseInt(rotationInput.value, 10) || 0;\n        rotationSlider.value = Math.max(-360, Math.min(360, newRotation));\n        pushUndo(\"rotate node\");\n        NODE_DATA[currentNodeId].rotation = newRotation;\n        const nodeGroup = document.querySelector(`g[data-node-id=\"${currentNodeId}\"]`);\n        if (nodeGroup) {\n          const pos = savedPositions[currentNodeId] || { x: 0, y: 0 };\n          nodeGroup.setAttribute(\"transform\", `translate(${pos.x},${pos.y}) rotate(${newRotation})`);\n        }\n      });\n      resetRotationBtn.addEventListener(\"click\", () => {\n        pushUndo(\"reset rotation\");\n        NODE_DATA[currentNodeId].rotation = 0;\n        rotationSlider.value = 0;\n        rotationInput.value = 0;\n        const nodeGroup = document.querySelector(`g[data-node-id=\"${currentNodeId}\"]`);\n        if (nodeGroup) {\n          const pos = savedPositions[currentNodeId] || { x: 0, y: 0 };\n          nodeGroup.setAttribute(\"transform\", `translate(${pos.x},${pos.y}) rotate(0)`);\n        }\n      });\n      const applyStyle = (property, value) => {\n       pushUndo(\"style change\");\n       const styleEntry = ensureStyleEntry(currentNodeId);\n       const scopeKey = currentStyleScope || \"all\";\n       if (!styleEntry[scopeKey]) styleEntry[scopeKey] = {};\n       styleEntry[scopeKey][property] = value;\n       const nodeGroup = document.querySelector(`g[data-node-id=\"${currentNodeId}\"]`);\n       if (!nodeGroup) return;\n       const shapeEl = nodeGroup.querySelector(\".node-circle\");\n       const label = nodeGroup.querySelector(\".node-label\");\n       const sub = nodeGroup.querySelector(\".node-sub\");\n       if (property === \"circleColor\" && shapeEl) shapeEl.style.fill = value;\n      else if (property === \"circleBorder\" && shapeEl) shapeEl.style.stroke = value;\n       else if (property === \"titleColor\" && label) label.style.fill = value;\n       else if (property === \"titleFont\" && label) label.style.fontFamily = value;\n       else if (property === \"titleSize\" && label) label.style.fontSize = value + \"px\";\n       else if (property === \"subColor\" && sub) sub.style.fill = value;\n       else if (property === \"subFont\" && sub) sub.style.fontFamily = value;\n       else if (property === \"subSize\" && sub) sub.style.fontSize = value + \"px\";\n      };\n      document.getElementById(\"circle-color\").addEventListener(\"input\", (e) => applyStyle(\"circleColor\", e.target.value));\n      document.getElementById(\"circle-border\").addEventListener(\"input\", (e) => applyStyle(\"circleBorder\", e.target.value));\n      document.getElementById(\"title-color\").addEventListener(\"input\", (e) => applyStyle(\"titleColor\", e.target.value));\n      document.getElementById(\"title-font\").addEventListener(\"change\", (e) => applyStyle(\"titleFont\", e.target.value));\n      document.getElementById(\"title-size\").addEventListener(\"input\", (e) => applyStyle(\"titleSize\", parseInt(e.target.value, 10)));\n      document.getElementById(\"sub-color\").addEventListener(\"input\", (e) => applyStyle(\"subColor\", e.target.value));\n      document.getElementById(\"sub-font\").addEventListener(\"change\", (e) => applyStyle(\"subFont\", e.target.value));\n      document.getElementById(\"sub-size\").addEventListener(\"input\", (e) => applyStyle(\"subSize\", parseInt(e.target.value, 10)));\n      document.getElementById(\"title-offset-y\").addEventListener(\"input\", (e) => {\n       applyStyle(\"titleOffsetY\", parseInt(e.target.value, 10) || 0);\n       forgeTheTopology();\n       if (currentNodeId) claimTheImmortal(currentNodeId);\n      });\n      document.getElementById(\"title-offset-x\").addEventListener(\"input\", (e) => {\n       applyStyle(\"titleOffsetX\", parseInt(e.target.value, 10) || 0);\n       forgeTheTopology();\n       if (currentNodeId) claimTheImmortal(currentNodeId);\n      });\n      document.getElementById(\"sub-offset-y\").addEventListener(\"input\", (e) => {\n       applyStyle(\"subOffsetY\", parseInt(e.target.value, 10) || 0);\n       forgeTheTopology();\n       if (currentNodeId) claimTheImmortal(currentNodeId);\n      });\n      document.getElementById(\"sub-offset-x\").addEventListener(\"input\", (e) => {\n       applyStyle(\"subOffsetX\", parseInt(e.target.value, 10) || 0);\n       forgeTheTopology();\n       if (currentNodeId) claimTheImmortal(currentNodeId);\n      });\n      document.getElementById(\"reset-styles\").addEventListener(\"click\", () => {\n       delete savedStyles[currentNodeId];\n       forgeTheTopology();\n       claimTheImmortal(currentNodeId);\n      });\n      document.getElementById(\"style-scope\").addEventListener(\"change\", (e) => {\n       currentStyleScope = e.target.value || \"all\";\n       claimTheImmortal(currentNodeId);\n      });\n      document.getElementById(\"add-note-btn\")?.addEventListener(\"click\", () => {\n       if (!currentNodeId) return;\n       showNoteEditor(t(\"noteEditor.addNote\"), \"\", (content) => {\n        pushUndo(\"add note\");\n        NODE_DATA[currentNodeId].notes.push(content);\n        renderNotesFeed(NODE_DATA[currentNodeId].notes, \"node-notes\", \"node\", currentNodeId);\n       });\n      });\n      document.getElementById(\"edge-width\").addEventListener(\"input\", () => {\n       if (!currentEdgeId) return;\n       const edge = EDGE_DATA.list.find((e) => e.id === currentEdgeId);\n       if (!edge) return;\n       const v = parseInt(document.getElementById(\"edge-width\").value, 10);\n       if (Number.isNaN(v) || v <= 0) return;\n       pushUndo(\"edit edge\");\n       edge.width = v;\n       const el = document.querySelector(`.edge[data-edge-id=\"${currentEdgeId}\"]`);\n       if (el) el.style.strokeWidth = v;\n      });\n      document.getElementById(\"edge-color\").addEventListener(\"input\", () => {\n       if (!currentEdgeId) return;\n       const edge = EDGE_DATA.list.find((e) => e.id === currentEdgeId);\n       if (!edge) return;\n       const color = document.getElementById(\"edge-color\").value;\n       pushUndo(\"edit edge\");\n       edge.color = color;\n       const el = document.querySelector(`.edge[data-edge-id=\"${currentEdgeId}\"]`);\n       if (el) el.style.stroke = color;\n       forgeTheLegend();\n      });\n      document.getElementById(\"edge-direction\").addEventListener(\"change\", () => {\n       if (!currentEdgeId) return;\n       const edge = EDGE_DATA.list.find((e) => e.id === currentEdgeId);\n       if (!edge) return;\n       pushUndo(\"edit edge\");\n       edge.direction = document.getElementById(\"edge-direction\").value;\n       forgeTheTopology();\n       selectTheConnection(currentEdgeId);\n      });\n      document.getElementById(\"edge-line-style\").addEventListener(\"change\", () => {\n       if (!currentEdgeId) return;\n       const edge = EDGE_DATA.list.find((e) => e.id === currentEdgeId);\n       if (!edge) return;\n       pushUndo(\"edit edge\");\n       edge.lineStyle = document.getElementById(\"edge-line-style\").value;\n       forgeTheTopology();\n       selectTheConnection(currentEdgeId);\n      });\n      document.getElementById(\"edge-routing\").addEventListener(\"change\", () => {\n       if (!currentEdgeId) return;\n       const edge = EDGE_DATA.list.find((e) => e.id === currentEdgeId);\n       if (!edge) return;\n       pushUndo(\"edit edge routing\");\n       edge.routing = document.getElementById(\"edge-routing\").value;\n       forgeTheTopology();\n       selectTheConnection(currentEdgeId);\n      });\n      document.getElementById(\"edge-animate\").addEventListener(\"change\", (e) => {\n       if (!currentEdgeId) return;\n       const edge = EDGE_DATA.list.find((ed) => ed.id === currentEdgeId);\n       if (!edge) return;\n       pushUndo(\"edit edge animate\");\n       edge.animate = e.target.checked;\n       forgeTheTopology();\n       selectTheConnection(currentEdgeId);\n      });\n      document.getElementById(\"edge-animation-speed\").addEventListener(\"change\", (e) => {\n       if (!currentEdgeId) return;\n       const edge = EDGE_DATA.list.find((ed) => ed.id === currentEdgeId);\n       if (!edge) return;\n       pushUndo(\"edit edge animation speed\");\n       edge.animationSpeed = e.target.value || \"\";\n       if (PAGE_STATE.animateConnections && edge.animate !== false) forgeTheTopology();\n       selectTheConnection(currentEdgeId);\n      });\n      document.getElementById(\"add-edge-note\")?.addEventListener(\"click\", () => {\n       if (!currentEdgeId) return;\n       const edge = EDGE_DATA.list.find((e) => e.id === currentEdgeId);\n       if (!edge) return;\n       showNoteEditor(t(\"noteEditor.addNote\"), \"\", (content) => {\n        pushUndo(\"add edge note\");\n        edge.notes.push(content);\n        renderNotesFeed(edge.notes, \"edge-notes\", \"edge\", currentEdgeId);\n       });\n      });\n      function selectTheRect(id) {\n\t  if (isViewOnly()) return;\n      currentRectId = id;\n      currentNodeId = null;\n      currentEdgeId = null;\n      currentTextId = null;\n      document.getElementById(\"node-panel\").style.display = \"none\";\n      document.getElementById(\"edge-panel\").style.display = \"none\";\n      document.getElementById(\"text-panel\").style.display = \"none\";\n      document.getElementById(\"image-panel\").style.display = \"none\";\n      document.getElementById(\"rect-panel\").style.display = \"block\";\n      document.getElementById(\"topology-toolbar\").style.display = \"none\";\n      document.querySelectorAll(\".node-group\").forEach(n => n.classList.remove(\"active\"));\n      document.querySelectorAll(\".edge\").forEach(e => e.classList.remove(\"active\"));\n      document.querySelectorAll(\".text-element\").forEach(t => t.classList.remove(\"active\"));\n      document.querySelectorAll(\".rect-group\").forEach(r => r.classList.toggle(\"active\", r.dataset.rectId === id));\n      const rect = RECT_DATA.list.find(r => r.id === id);\n      if (!rect) return;\n      document.getElementById(\"rect-title\").textContent = \"\";\n      document.getElementById(\"rect-color\").value = rect.color || \"#f97316\";\n      document.getElementById(\"rect-border-color\").value = rect.borderColor || rect.color || \"#f97316\";\n      document.getElementById(\"rect-border-width\").value = rect.borderWidth !== undefined ? rect.borderWidth : 2;\n      document.getElementById(\"rect-style-select\").value = rect.style || \"filled\";\n      document.getElementById(\"rect-line-style\").value = rect.lineStyle || \"solid\";\n\t  document.getElementById(\"rect-rotation\").value = Math.max(-360, Math.min(360, rect.rotation || 0));\n      document.getElementById(\"rect-rotation-value\").value = rect.rotation || 0;\n      const opacityVal = rect.fillOpacity !== undefined ? Math.round(rect.fillOpacity * 100) : 30;\n      document.getElementById(\"rect-fill-opacity\").value = opacityVal;\n      document.getElementById(\"rect-fill-opacity-value\").textContent = opacityVal + \"%\";\n      document.getElementById(\"rect-fill-row\").style.display = (rect.style === \"outlined\") ? \"none\" : \"flex\";\n      document.getElementById(\"rect-opacity-row\").style.display = (rect.style === \"outlined\") ? \"none\" : \"flex\";\n      renderNotesFeed(rect.notes || [], \"rect-notes\", \"rect\", id);\n      forgeTheTopology();\n      }\n      document.getElementById(\"rect-color\").addEventListener(\"input\", () => {\n      if (!currentRectId) return;\n      const rect = RECT_DATA.list.find(r => r.id === currentRectId);\n      if (!rect) return;\n      pushUndo(\"edit zone\");\n      rect.color = document.getElementById(\"rect-color\").value;\n      forgeTheTopology();\n      });\n      document.getElementById(\"rect-border-color\").addEventListener(\"input\", () => {\n      if (!currentRectId) return;\n      const rect = RECT_DATA.list.find(r => r.id === currentRectId);\n      if (!rect) return;\n      pushUndo(\"edit zone\");\n      rect.borderColor = document.getElementById(\"rect-border-color\").value;\n      forgeTheTopology();\n      });\n      document.getElementById(\"rect-border-width\").addEventListener(\"input\", () => {\n      if (!currentRectId) return;\n      const rect = RECT_DATA.list.find(r => r.id === currentRectId);\n      if (!rect) return;\n      pushUndo(\"edit zone\");\n      rect.borderWidth = parseInt(document.getElementById(\"rect-border-width\").value) || 2;\n      forgeTheTopology();\n      });\n      document.getElementById(\"rect-style-select\").addEventListener(\"change\", () => {\n      if (!currentRectId) return;\n      const rect = RECT_DATA.list.find(r => r.id === currentRectId);\n      if (!rect) return;\n      pushUndo(\"edit zone\");\n      rect.style = document.getElementById(\"rect-style-select\").value;\n      forgeTheTopology();\n      selectTheRect(currentRectId);\n      });\ndocument.getElementById(\"rect-rotation\").addEventListener(\"input\", () => {\n        if (!currentRectId) return;\n        const rect = RECT_DATA.list.find(r => r.id === currentRectId);\n        if (!rect) return;\n        pushUndo(\"rotate zone\");\n        rect.rotation = parseInt(document.getElementById(\"rect-rotation\").value) || 0;\n        document.getElementById(\"rect-rotation-value\").value = rect.rotation;\n        forgeTheTopology();\n      });\n      document.getElementById(\"rect-rotation-value\").addEventListener(\"input\", () => {\n        if (!currentRectId) return;\n        const rect = RECT_DATA.list.find(r => r.id === currentRectId);\n        if (!rect) return;\n        pushUndo(\"rotate zone\");\n        const val = parseInt(document.getElementById(\"rect-rotation-value\").value) || 0;\n        rect.rotation = val;\n        document.getElementById(\"rect-rotation\").value = Math.max(-360, Math.min(360, val));\n        forgeTheTopology();\n      });\n      document.getElementById(\"rect-line-style\").addEventListener(\"change\", () => {\n      if (!currentRectId) return;\n      pushUndo(\"change zone line style\");\n      const rect = RECT_DATA.list.find(r => r.id === currentRectId);\n      if (!rect) return;\n      rect.lineStyle = document.getElementById(\"rect-line-style\").value;\n      forgeTheTopology();\n      });\n      document.getElementById(\"rect-fill-opacity\").addEventListener(\"input\", () => {\n      if (!currentRectId) return;\n      const rect = RECT_DATA.list.find(r => r.id === currentRectId);\n      if (!rect) return;\n      pushUndo(\"change zone fill opacity\");\n      const val = parseInt(document.getElementById(\"rect-fill-opacity\").value);\n      rect.fillOpacity = val / 100;\n      document.getElementById(\"rect-fill-opacity-value\").textContent = val + \"%\";\n      forgeTheTopology();\n      });\n      document.getElementById(\"add-rect-note\")?.addEventListener(\"click\", () => {\n       if (!currentRectId) return;\n       const rect = RECT_DATA.list.find(r => r.id === currentRectId);\n       if (!rect) return;\n       if (!rect.notes) rect.notes = [];\n       showNoteEditor(t(\"noteEditor.addNote\"), \"\", (content) => {\n        pushUndo(\"add zone note\");\n        rect.notes.push(content);\n        renderNotesFeed(rect.notes, \"rect-notes\", \"rect\", currentRectId);\n       });\n      });\n      document.getElementById(\"delete-rect\").addEventListener(\"click\", () => {\n      if (!currentRectId) return;\n      challengeTheImmortal(\"Delete this zone?\", () => {\n      pushUndo(\"delete zone\");\n      RECT_DATA.list = RECT_DATA.list.filter(r => r.id !== currentRectId);\n      currentRectId = null;\n      document.getElementById(\"rect-panel\").style.display = \"none\";\n      forgeTheTopology();\n      });\n      });\n      document.getElementById(\"delete-edge\").addEventListener(\"click\", () => {\n       if (!currentEdgeId) return;\n       challengeTheImmortal(\"Are you sure you want to delete this line?\",\n        () => {\n         pushUndo(\"delete edge\");\n         EDGE_DATA.list = EDGE_DATA.list.filter(\n          (e) => e.id !== currentEdgeId);\n         currentEdgeId = null;\n         forgeTheTopology();\n         const availableNodes = Object.keys(NODE_DATA);\n         if (availableNodes.length > 0) {\n          claimTheImmortal(availableNodes[0]);\n         } else {\n          document.getElementById(\"node-panel\").style.display = \"none\";\n          document.getElementById(\"edge-panel\").style.display = \"none\";\n          document.getElementById(\"topology-toolbar\", ).style.display = \"none\";\n         }\n        });\n      });\n      document.getElementById(\"clear-waypoints-btn\").addEventListener(\"click\", () => {\n       if (!currentEdgeId) return;\n       const edge = EDGE_DATA.list.find(e => e.id === currentEdgeId);\n       if (!edge || !edge.waypoints || edge.waypoints.length === 0) return;\n       pushUndo(\"clear waypoints\");\n       edge.waypoints = [];\n       selectTheConnection(currentEdgeId);\n       forgeTheTopology();\n      });\n      document.getElementById(\"add-line-btn\").addEventListener(\"click\", () => {\n       if (!currentNodeId) return;\n       const select = document.getElementById(\"add-line-select\");\n       const directionSelect = document.getElementById(\"add-line-direction\");\n       const colorInput = document.getElementById(\"add-line-color\");\n       const routingSelect = document.getElementById(\"add-line-routing\");\n       const targetId = select.value;\n       if (!targetId || targetId === currentNodeId) return;\n       const direction = directionSelect.value || \"none\";\n       const lineColor = colorInput.value || \"#475569\";\n       const routing = routingSelect.value || PAGE_STATE.defaultEdgeRouting || \"orthogonal\";\n       const newId = `${currentNodeId}-${targetId}-${Date.now()}`;\n       const newEdge = {\n        id: newId,\n        from: currentNodeId,\n        to: targetId,\n        width: 4,\n        color: lineColor,\n        direction: direction,\n        routing: routing,\n        type: \"main\",\n        notes: [],\n        fromPort: \"\",\n        toPort: \"\",\n        lineStyle: \"solid\",\n        waypoints: [],\n       };\n       pushUndo(\"add edge\");\n       EDGE_DATA.list.push(newEdge);\n       forgeTheTopology();\n       claimTheImmortal(currentNodeId);\n      });\n      let freeDrawPoints = [];\n      let freeDrawPolylineEl = null;\n      let freeDrawPointEls = [];\n       let rectStartPoint = null;\n       let rectPreviewEl = null;\n       let rectStyle = \"filled\";\n      const drawToggleBtn = document.getElementById(\"draw-toggle\");\n      const drawUndoBtn = document.getElementById(\"draw-undo\");\n      const drawColorInput = document.getElementById(\"draw-color\");\n      const drawStyleSelect = document.getElementById(\"draw-style\");\n      const drawArrowSelect = document.getElementById(\"draw-arrow\");\n      const svgMap = document.getElementById(\"map\");\n      function updateFreeDrawGraphics() {\n       const ns = \"http://www.w3.org/2000/svg\";\n       const svg = svgMap;\n       if (!freeDrawPolylineEl && freeDrawPoints.length > 0) {\n        freeDrawPolylineEl = document.createElementNS(ns, \"polyline\");\n        freeDrawPolylineEl.classList.add(\"edge\", \"free-preview\");\n        freeDrawPolylineEl.setAttribute(\"fill\", \"none\");\n        svg.appendChild(freeDrawPolylineEl);\n       }\n       if (freeDrawPolylineEl) {\n        if (freeDrawPoints.length === 0) {\n         freeDrawPolylineEl.remove();\n         freeDrawPolylineEl = null;\n        } else {\n         const ptsStr = freeDrawPoints.map((p) => `${p.x},${p.y}`).join(\" \");\n         freeDrawPolylineEl.setAttribute(\"points\", ptsStr);\n         freeDrawPolylineEl.style.stroke = drawColorInput.value || \"#475569\";\n         freeDrawPolylineEl.style.strokeWidth = 3;\n         const lineStyle = drawStyleSelect.value || \"solid\";\n         if (lineStyle === \"dashed\") {\n          freeDrawPolylineEl.style.strokeDasharray = \"10,5\";\n         } else if (lineStyle === \"dotted\") {\n          freeDrawPolylineEl.style.strokeDasharray = \"2,4\";\n         } else if (lineStyle === \"wall\") {\n  freeDrawPolylineEl.style.stroke = \"url(#wall-hatch)\";\n  freeDrawPolylineEl.style.strokeWidth = \"12\";\n  freeDrawPolylineEl.style.strokeDasharray = \"none\"; \n  } else {\n          freeDrawPolylineEl.style.strokeDasharray = \"none\";\n         }\n        }\n       }\n       freeDrawPointEls.forEach((el) => el.remove());\n       freeDrawPointEls = [];\n       freeDrawPoints.forEach((p, idx) => {\n        const c = document.createElementNS(ns, \"circle\");\n        c.classList.add(\"free-point\");\n        c.setAttribute(\"cx\", p.x);\n        c.setAttribute(\"cy\", p.y);\n        c.setAttribute(\"r\", 5);\n        c.dataset.index = String(idx);\n        c.addEventListener(\"mousedown\", (e) => {\n         if (!freeDrawMode) return;\n         e.preventDefault();\n         e.stopPropagation();\n         let dragging = true;\n         const svgEl = svgMap;\n         const moveHandler = (ev) => {\n          if (!dragging) return;\n          const pt = svgEl.createSVGPoint();\n          pt.x = ev.clientX;\n          pt.y = ev.clientY;\n          const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n          const i = parseInt(c.dataset.index, 10);\n          if (Number.isNaN(i) || !freeDrawPoints[i]) return;\n          freeDrawPoints[i].x = svgP.x;\n          freeDrawPoints[i].y = svgP.y;\n          updateFreeDrawGraphics();\n         };\n         const upHandler = () => {\n          dragging = false;\n          document.removeEventListener(\"mousemove\", moveHandler);\n          document.removeEventListener(\"mouseup\", upHandler);\n         };\n         document.addEventListener(\"mousemove\", moveHandler);\n         document.addEventListener(\"mouseup\", upHandler);\n        });\n        c.addEventListener(\"touchstart\",\n         (e) => {\n          if (!freeDrawMode) return;\n          e.preventDefault();\n          e.stopPropagation();\n          let dragging = true;\n          const svgEl = svgMap;\n          const touchMoveHandler = (ev) => {\n           if (!dragging || !ev.touches[0]) return;\n           const pt = svgEl.createSVGPoint();\n           pt.x = ev.touches[0].clientX;\n           pt.y = ev.touches[0].clientY;\n           const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n           const i = parseInt(c.dataset.index, 10);\n           if (Number.isNaN(i) || !freeDrawPoints[i]) return;\n           freeDrawPoints[i].x = svgP.x;\n           freeDrawPoints[i].y = svgP.y;\n           updateFreeDrawGraphics();\n          };\n          const touchUpHandler = () => {\n           dragging = false;\n           document.removeEventListener(\"touchmove\", touchMoveHandler);\n           document.removeEventListener(\"touchend\", touchUpHandler);\n          };\n          document.addEventListener(\"touchmove\", touchMoveHandler);\n          document.addEventListener(\"touchend\", touchUpHandler);\n         }, {\n          passive: false\n         });\n        svg.appendChild(c);\n        freeDrawPointEls.push(c);\n       });\n       drawUndoBtn.style.display = freeDrawPoints.length ? \"inline-block\" : \"none\";\n      }\n      function addFreeDrawPoint(x, y) {\n       freeDrawPoints.push({\n        x,\n        y\n       });\n       updateFreeDrawGraphics();\n      }\n      function startFreeDraw() {\n       freeDrawMode = true;\n       freeDrawPoints = [];\n       if (freeDrawPolylineEl) {\n        freeDrawPolylineEl.remove();\n        freeDrawPolylineEl = null;\n       }\n       freeDrawPointEls.forEach((el) => el.remove());\n       freeDrawPointEls = [];\n       svgMap.style.cursor = \"crosshair\";\n       drawToggleBtn.textContent = t(\"ui.buttons.done\");\n       drawToggleBtn.classList.add(\"done-btn-active\");\n       drawUndoBtn.style.display = \"none\";\n      }\n      function finishFreeDraw() {\n       freeDrawMode = false;\n       svgMap.style.cursor = \"\";\n       drawToggleBtn.textContent = \"✏️\";\n       drawToggleBtn.classList.remove(\"done-btn-active\");\n       if (freeDrawPoints.length >= 2) {\n        const color = drawColorInput.value || \"#475569\";\n        const lineStyle = drawStyleSelect.value || \"solid\";\n        const arrowDir = drawArrowSelect.value || \"none\";\n        const newId = \"custom-\" + Date.now();\n        const pointsCopy = freeDrawPoints.map((p) => ({\n         x: p.x,\n         y: p.y,\n        }));\n        EDGE_DATA.list.push({\n         id: newId,\n         type: \"custom\",\n         color,\n         width: 4,\n         lineStyle: lineStyle,\n         direction: arrowDir,\n         points: pointsCopy,\n         notes: [],\n        });\n        freeDrawPoints = [];\n        if (freeDrawPolylineEl) {\n         freeDrawPolylineEl.remove();\n         freeDrawPolylineEl = null;\n        }\n        freeDrawPointEls.forEach((el) => el.remove());\n        freeDrawPointEls = [];\n        forgeTheTopology();\n        selectTheConnection(newId);\n       } else {\n        freeDrawPoints = [];\n        if (freeDrawPolylineEl) {\n         freeDrawPolylineEl.remove();\n         freeDrawPolylineEl = null;\n        }\n        freeDrawPointEls.forEach((el) => el.remove());\n        freeDrawPointEls = [];\n        forgeTheLegend();\n       }\n       drawUndoBtn.style.display = \"none\";\n      }\n      drawToggleBtn.addEventListener(\"click\", () => {\n       if (currentView.mode === \"rack\") {\n        showAlert(t(\"dialogs.drawingDisabledInRack\"));\n        return;\n       }\n       if (freeDrawMode) {\n        finishFreeDraw();\n       } else {\n        startFreeDraw();\n       }\n      });\n      drawUndoBtn.addEventListener(\"click\", () => {\n       if (!freeDrawMode || !freeDrawPoints.length) return;\n       freeDrawPoints.pop();\n       updateFreeDrawGraphics();\n      });\n      const drawToolbar = document.getElementById(\"draw-toolbar\");\n      drawToolbar.addEventListener(\"mousedown\", (e) => {\n       e.stopPropagation();\n      });\n      drawToolbar.addEventListener(\"click\", (e) => {\n       if (e.target !== drawToggleBtn && e.target !== drawUndoBtn) {\n        e.stopPropagation();\n       }\n      });\n      drawStyleSelect.addEventListener(\"change\", () => {\n       if (freeDrawMode) {\n        updateFreeDrawGraphics();\n       }\n      });\n      drawArrowSelect.addEventListener(\"change\", () => {\n       if (freeDrawMode) {\n        updateFreeDrawGraphics();\n       }\n      });\n      drawColorInput.addEventListener(\"input\", () => {\n       if (freeDrawMode) {\n        updateFreeDrawGraphics();\n       }\n      });\n      drawStyleSelect.addEventListener(\"mousedown\", (e) => {\n       e.stopPropagation();\n      });\n      drawStyleSelect.addEventListener(\"click\", (e) => {\n       e.stopPropagation();\n      });\n      drawArrowSelect.addEventListener(\"mousedown\", (e) => {\n       e.stopPropagation();\n      });\n      drawArrowSelect.addEventListener(\"click\", (e) => {\n       e.stopPropagation();\n      });\n      drawColorInput.addEventListener(\"mousedown\", (e) => {\n       e.stopPropagation();\n      });\n      drawColorInput.addEventListener(\"click\", (e) => {\n       e.stopPropagation();\n      });\n      svgMap.addEventListener(\"click\", (e) => {\n       if (!freeDrawMode) return;\n       if (e.button !== 0) return;\n       const target = e.target;\n       if (target && target.classList && target.classList.contains(\"free-point\")) return;\n       const svgEl = svgMap;\n       const pt = svgEl.createSVGPoint();\n       pt.x = e.clientX;\n       pt.y = e.clientY;\n       const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n       addFreeDrawPoint(svgP.x, svgP.y);\n      });\n      svgMap.addEventListener(\"touchend\",\n       (e) => {\n        if (!freeDrawMode) return;\n        const target = e.target;\n        if (target && target.classList && target.classList.contains(\"free-point\")) return;\n        if (e.changedTouches && e.changedTouches[0]) {\n         e.preventDefault();\n         const svgEl = svgMap;\n         const pt = svgEl.createSVGPoint();\n         pt.x = e.changedTouches[0].clientX;\n         pt.y = e.changedTouches[0].clientY;\n         const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n         addFreeDrawPoint(svgP.x, svgP.y);\n        }\n       }, {\n        passive: false\n       });\n       const rectToggleBtn = document.getElementById(\"rect-toggle\");\n       const rectStyleSelect = document.getElementById(\"rect-style\");\n       function startRectDraw() {\n        rectDrawMode = true;\n        rectStartPoint = null;\n        rectPreviewEl = null;\n        svgMap.style.cursor = \"crosshair\";\n        rectToggleBtn.textContent = t(\"ui.buttons.done\");\n        rectToggleBtn.classList.add(\"done-btn-active\");\n        rectStyle = rectStyleSelect.value || \"filled\";\n        if (freeDrawMode) {\n         finishFreeDraw();\n        }\n        updateRectangleDeleteButtons();\n       }\n       function finishRectDraw() {\n        rectDrawMode = false;\n        svgMap.style.cursor = \"\";\n        rectToggleBtn.textContent = \"▭\";\n        rectToggleBtn.classList.remove(\"done-btn-active\");\n        if (rectPreviewEl) {\n         rectPreviewEl.remove();\n         rectPreviewEl = null;\n        }\n        rectStartPoint = null;\n        updateRectangleDeleteButtons();\n       }\n       rectToggleBtn.addEventListener(\"click\", () => {\n        if (currentView.mode === \"rack\") {\n         showAlert(t(\"dialogs.drawingDisabledInRack\"));\n         return;\n        }\n        if (rectDrawMode) {\n         finishRectDraw();\n        } else {\n         startRectDraw();\n        }\n       });\n       rectStyleSelect.addEventListener(\"mousedown\", (e) => {\n        e.stopPropagation();\n       });\n       rectStyleSelect.addEventListener(\"click\", (e) => {\n        e.stopPropagation();\n       });\n       rectStyleSelect.addEventListener(\"change\", () => {\n        if (rectDrawMode) {\n         rectStyle = rectStyleSelect.value || \"filled\";\n        }\n       });\n       svgMap.addEventListener(\"mousedown\", (e) => {\n        if (!rectDrawMode) return;\n        if (e.button !== 0) return;\n        e.preventDefault();\n        e.stopPropagation();\n        const svgEl = svgMap;\n        const pt = svgEl.createSVGPoint();\n        pt.x = e.clientX;\n        pt.y = e.clientY;\n        const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n        rectStartPoint = { x: svgP.x, y: svgP.y };\n        const ns = \"http://www.w3.org/2000/svg\";\n        rectPreviewEl = document.createElementNS(ns, \"rect\");\n        rectPreviewEl.classList.add(\"rect-preview\");\n        rectPreviewEl.setAttribute(\"x\", svgP.x);\n        rectPreviewEl.setAttribute(\"y\", svgP.y);\n        rectPreviewEl.setAttribute(\"width\", 0);\n        rectPreviewEl.setAttribute(\"height\", 0);\n        const color = drawColorInput.value || \"#f97316\";\n        rectPreviewEl.style.stroke = color;\n        rectPreviewEl.style.strokeWidth = 2;\n        rectPreviewEl.style.strokeDasharray = \"5,5\";\n        if (rectStyle === \"filled\") {\n         rectPreviewEl.style.fill = color;\n         rectPreviewEl.style.fillOpacity = \"0.3\";\n        } else {\n         rectPreviewEl.style.fill = \"none\";\n        }\n        svgEl.appendChild(rectPreviewEl);\n       });\n       svgMap.addEventListener(\"mousemove\", (e) => {\n        if (!rectDrawMode || !rectStartPoint || !rectPreviewEl) return;\n        const svgEl = svgMap;\n        const pt = svgEl.createSVGPoint();\n        pt.x = e.clientX;\n        pt.y = e.clientY;\n        const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n        const x = Math.min(rectStartPoint.x, svgP.x);\n        const y = Math.min(rectStartPoint.y, svgP.y);\n        const width = Math.abs(svgP.x - rectStartPoint.x);\n        const height = Math.abs(svgP.y - rectStartPoint.y);\n        rectPreviewEl.setAttribute(\"x\", x);\n        rectPreviewEl.setAttribute(\"y\", y);\n        rectPreviewEl.setAttribute(\"width\", width);\n        rectPreviewEl.setAttribute(\"height\", height);\n       });\n       svgMap.addEventListener(\"mouseup\", (e) => {\n        if (!rectDrawMode || !rectStartPoint || !rectPreviewEl) return;\n        const svgEl = svgMap;\n        const pt = svgEl.createSVGPoint();\n        pt.x = e.clientX;\n        pt.y = e.clientY;\n        const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n        const x = Math.min(rectStartPoint.x, svgP.x);\n        const y = Math.min(rectStartPoint.y, svgP.y);\n        const width = Math.abs(svgP.x - rectStartPoint.x);\n        const height = Math.abs(svgP.y - rectStartPoint.y);\n        if (width > 10 && height > 10) {\n      pushUndo(\"draw zone\");\n         const color = drawColorInput.value || \"#f97316\";\n         const newId = \"rect-\" + Date.now();\n          const lineStyle = document.getElementById(\"draw-style\").value || \"solid\";\n         RECT_DATA.list.push({\n          id: newId,\n          x: x,\n          y: y,\n          width: width,\n          height: height,\n          color: color,\n          style: rectStyle,\n          lineStyle: lineStyle,\n          notes: []\n         });\n         finishRectDraw();\n         forgeTheTopology();\n        }\n        if (rectPreviewEl) {\n         rectPreviewEl.remove();\n         rectPreviewEl = null;\n        }\n        rectStartPoint = null;\n       });\n       let rectTouchStart = null;\n       svgMap.addEventListener(\"touchstart\", (e) => {\n        if (!rectDrawMode) return;\n        if (e.touches.length !== 1) return;\n        e.preventDefault();\n        const touch = e.touches[0];\n        const svgEl = svgMap;\n        const pt = svgEl.createSVGPoint();\n        pt.x = touch.clientX;\n        pt.y = touch.clientY;\n        const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n        rectStartPoint = { x: svgP.x, y: svgP.y };\n        rectTouchStart = { x: svgP.x, y: svgP.y };\n        const ns = \"http://www.w3.org/2000/svg\";\n        rectPreviewEl = document.createElementNS(ns, \"rect\");\n        rectPreviewEl.classList.add(\"rect-preview\");\n        rectPreviewEl.setAttribute(\"x\", svgP.x);\n        rectPreviewEl.setAttribute(\"y\", svgP.y);\n        rectPreviewEl.setAttribute(\"width\", 0);\n        rectPreviewEl.setAttribute(\"height\", 0);\n        const color = drawColorInput.value || \"#f97316\";\n        rectPreviewEl.style.stroke = color;\n        rectPreviewEl.style.strokeWidth = 2;\n        rectPreviewEl.style.strokeDasharray = \"5,5\";\n        if (rectStyle === \"filled\") {\n         rectPreviewEl.style.fill = color;\n         rectPreviewEl.style.fillOpacity = \"0.3\";\n        } else {\n         rectPreviewEl.style.fill = \"none\";\n        }\n        svgEl.appendChild(rectPreviewEl);\n       }, { passive: false });\n       svgMap.addEventListener(\"touchmove\", (e) => {\n        if (!rectDrawMode || !rectStartPoint || !rectPreviewEl) return;\n        if (e.touches.length !== 1) return;\n        e.preventDefault();\n        const touch = e.touches[0];\n        const svgEl = svgMap;\n        const pt = svgEl.createSVGPoint();\n        pt.x = touch.clientX;\n        pt.y = touch.clientY;\n        const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n        const x = Math.min(rectStartPoint.x, svgP.x);\n        const y = Math.min(rectStartPoint.y, svgP.y);\n        const width = Math.abs(svgP.x - rectStartPoint.x);\n        const height = Math.abs(svgP.y - rectStartPoint.y);\n        rectPreviewEl.setAttribute(\"x\", x);\n        rectPreviewEl.setAttribute(\"y\", y);\n        rectPreviewEl.setAttribute(\"width\", width);\n        rectPreviewEl.setAttribute(\"height\", height);\n       }, { passive: false });\n       svgMap.addEventListener(\"touchend\", (e) => {\n        if (!rectDrawMode || !rectStartPoint || !rectPreviewEl) return;\n        if (!rectTouchStart) return;\n        e.preventDefault();\n        const touch = e.changedTouches[0];\n        const svgEl = svgMap;\n        const pt = svgEl.createSVGPoint();\n        pt.x = touch.clientX;\n        pt.y = touch.clientY;\n        const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n        const x = Math.min(rectStartPoint.x, svgP.x);\n        const y = Math.min(rectStartPoint.y, svgP.y);\n        const width = Math.abs(svgP.x - rectStartPoint.x);\n        const height = Math.abs(svgP.y - rectStartPoint.y);\n        if (width > 10 && height > 10) {\n      pushUndo(\"draw zone\");\n         const color = drawColorInput.value || \"#f97316\";\n         const newId = \"rect-\" + Date.now();\n        const lineStyle = document.getElementById(\"draw-style\").value || \"solid\";\n         RECT_DATA.list.push({\n          id: newId,\n          x: x,\n          y: y,\n          width: width,\n          height: height,\n          color: color,\n          style: rectStyle,\n          lineStyle: lineStyle,\n          notes: []\n         });\n         finishRectDraw();\n         forgeTheTopology();\n        }\n        if (rectPreviewEl) {\n         rectPreviewEl.remove();\n         rectPreviewEl = null;\n        }\n        rectStartPoint = null;\n        rectTouchStart = null;\n       }, { passive: false });\n      const textToggleBtn = document.getElementById(\"text-toggle\");\n      function startTextMode() {\n       textDrawMode = true;\n       svgMap.style.cursor = \"crosshair\";\n       textToggleBtn.textContent = t(\"ui.buttons.done\");\n       textToggleBtn.classList.add(\"done-btn-active\");\n       if (freeDrawMode) {\n        finishFreeDraw();\n       }\n       if (rectDrawMode) {\n        finishRectDraw();\n       }\n       updateTextDeleteButtons();\n      }\n      function finishTextMode() {\n       textDrawMode = false;\n       svgMap.style.cursor = \"\";\n       textToggleBtn.textContent = \"T\";\n       textToggleBtn.classList.remove(\"done-btn-active\");\n       updateTextDeleteButtons();\n      }\n      textToggleBtn.addEventListener(\"click\", () => {\n       if (currentView.mode === \"rack\") {\n        showAlert(t(\"dialogs.drawingDisabledInRack\"));\n        return;\n       }\n       if (textDrawMode) {\n        finishTextMode();\n       } else {\n        startTextMode();\n       }\n      });\n      function handleTextPlacement(e) {\n       if (!textDrawMode) return;\n       const svgEl = svgMap;\n       const pt = svgEl.createSVGPoint();\n       pt.x = e.clientX;\n       pt.y = e.clientY;\n       const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n       const newId = \"text-\" + Date.now();\n      pushUndo(\"add text\");\n       TEXT_DATA.list.push({\n        id: newId,\n        x: svgP.x,\n        y: svgP.y,\n        content: t(\"ui.defaults.newText\"),\n        fontSize: 18,\n        color: \"#e2e8f0\",\n        fontWeight: \"normal\",\n        fontStyle: \"normal\",\n        textAlign: \"start\",\n        textDecoration: \"none\",\n        bgColor: \"#000000\",\n        bgEnabled: false,\n        opacity: 1\n       });\n       finishTextMode();\n       forgeTheTopology();\n       showTextPanel(newId);\n       const textInput = document.getElementById(\"text-content\");\n       if (textInput) {\n        textInput.focus();\n        textInput.select();\n       }\n      }\n      svgMap.addEventListener(\"click\", (e) => {\n       if (!textDrawMode) return;\n       if (e.target.closest('.text-delete-btn')) return;\n       if (e.target.closest('.text-group')) return;\n       e.preventDefault();\n       e.stopPropagation();\n       handleTextPlacement(e);\n      });\n      svgMap.addEventListener(\"touchend\", (e) => {\n       if (!textDrawMode) return;\n       if (e.target.closest('.text-delete-btn')) return;\n       if (e.target.closest('.text-group')) return;\n       if (e.touches.length > 0) return;\n       e.preventDefault();\n       const touch = e.changedTouches[0];\n       const fakeEvent = {\n        clientX: touch.clientX,\n        clientY: touch.clientY,\n        preventDefault: () => {},\n        stopPropagation: () => {}\n       };\n       handleTextPlacement(fakeEvent);\n      }, { passive: false });\n      function showTextPanel(textId) {\n\t  if (isViewOnly()) return;\n       currentTextId = textId;\n       currentNodeId = null;\n       currentEdgeId = null;\n       currentRectId = null;\n       const textItem = TEXT_DATA.list.find(t => t.id === textId);\n       if (!textItem) return;\n       document.getElementById(\"node-panel\").style.display = \"none\";\n       document.getElementById(\"edge-panel\").style.display = \"none\";\n       document.getElementById(\"rect-panel\").style.display = \"none\";\n       document.getElementById(\"image-panel\").style.display = \"none\";\n       const textPanel = document.getElementById(\"text-panel\");\n       textPanel.style.display = \"block\";\n       document.querySelectorAll(\".node-group\").forEach(n => n.classList.remove(\"active\"));\n       document.querySelectorAll(\".edge\").forEach(e => e.classList.remove(\"active\"));\n       document.querySelectorAll(\".rect-group\").forEach(r => r.classList.remove(\"active\"));\n       document.querySelectorAll(\".text-element\").forEach(t => t.classList.toggle(\"active\", t.dataset.textId === textId));\n       document.getElementById(\"text-content\").value = textItem.content;\n       document.getElementById(\"text-font-size\").value = textItem.fontSize;\n       document.getElementById(\"text-color\").value = textItem.color;\n       document.getElementById(\"text-font-weight\").value = textItem.fontWeight;\n       document.getElementById(\"text-font-style\").value = textItem.fontStyle;\n       document.getElementById(\"text-align\").value = textItem.textAlign;\n       document.getElementById(\"text-decoration\").value = textItem.textDecoration;\n       document.getElementById(\"text-bg-color\").value = textItem.bgColor;\n       document.getElementById(\"text-bg-enabled\").checked = textItem.bgEnabled;\n       document.getElementById(\"text-opacity\").value = textItem.opacity;\n       document.getElementById(\"text-opacity-val\").textContent = Math.round(textItem.opacity * 100) + \"%\";\n       document.getElementById(\"text-rotation\").value = Math.max(-360, Math.min(360, textItem.rotation || 0));\n       document.getElementById(\"text-rotation-val\").value = textItem.rotation || 0;\n      }\n      function updateTextDeleteButtons() {\n       const deleteButtons = document.querySelectorAll('.text-delete-btn');\n       deleteButtons.forEach(btn => {\n        btn.style.display = textDrawMode ? 'block' : 'none';\n       });\n      }\n      function deleteText(textId) {\n      pushUndo(\"delete text\");\n       TEXT_DATA.list = TEXT_DATA.list.filter(t => t.id !== textId);\n       forgeTheTopology();\n       if (currentTextId === textId) {\n        document.getElementById(\"text-panel\").style.display = \"none\";\n        currentTextId = null;\n       }\n      }\n      let currentImageId = null;\n      function processAndAddImage(file, canvasX, canvasY, filename = null) {\n       if (IMAGE_DATA.list.length >= MAX_CANVAS_IMAGES) {\n        showAlert(t(\"images.maxImages\"));\n        return;\n       }\n       const imageName = filename || file.name.replace(/\\.[^/.]+$/, \"\") || \"Image\";\n       const reader = new FileReader();\n       reader.onload = (e) => {\n        const img = new Image();\n        img.onload = () => {\n         let width = img.width;\n         let height = img.height;\n         if (width > MAX_IMAGE_SIZE || height > MAX_IMAGE_SIZE) {\n          const ratio = Math.min(MAX_IMAGE_SIZE / width, MAX_IMAGE_SIZE / height);\n          width = Math.round(width * ratio);\n          height = Math.round(height * ratio);\n         }\n         const canvas = document.createElement(\"canvas\");\n         canvas.width = width;\n         canvas.height = height;\n         const ctx = canvas.getContext(\"2d\");\n         ctx.drawImage(img, 0, 0, width, height);\n         const imageData = ctx.getImageData(0, 0, width, height);\n         const data = imageData.data;\n         let hasTransparency = false;\n         for (let i = 3; i < data.length; i += 4) {\n          if (data[i] < 255) { hasTransparency = true; break; }\n         }\n         let compressedSrc;\n         const webpTest = canvas.toDataURL(\"image/webp\", 0.8);\n         const supportsWebP = webpTest.startsWith(\"data:image/webp\");\n         if (supportsWebP) {\n          compressedSrc = canvas.toDataURL(\"image/webp\", 0.8);\n         } else if (hasTransparency) {\n          compressedSrc = canvas.toDataURL(\"image/png\");\n         } else {\n          compressedSrc = canvas.toDataURL(\"image/jpeg\", 0.8);\n         }\n         const newId = \"img-\" + Date.now();\n         pushUndo(\"add image\");\n         IMAGE_DATA.list.push({\n          id: newId,\n          name: imageName,\n          x: canvasX - width / 2,\n          y: canvasY - height / 2,\n          width: width,\n          height: height,\n          src: compressedSrc,\n          opacity: 1,\n          border: \"none\",\n          shadow: \"none\",\n          notes: []\n         });\n         forgeTheTopology();\n        };\n        img.src = e.target.result;\n       };\n       reader.readAsDataURL(file);\n      }\n      function showImagePanel(imageId) {\n       const img = IMAGE_DATA.list.find(i => i.id === imageId);\n       if (!img) return;\n       currentImageId = imageId;\n       currentNodeId = null;\n       currentEdgeId = null;\n       currentRectId = null;\n       currentTextId = null;\n       document.getElementById(\"node-panel\").style.display = \"none\";\n       document.getElementById(\"edge-panel\").style.display = \"none\";\n       document.getElementById(\"rect-panel\").style.display = \"none\";\n       document.getElementById(\"text-panel\").style.display = \"none\";\n       document.getElementById(\"image-panel\").style.display = \"block\";\n       document.querySelectorAll(\".node-group\").forEach(n => n.classList.remove(\"active\"));\n       document.querySelectorAll(\".edge\").forEach(e => e.classList.remove(\"active\"));\n       document.querySelectorAll(\".rect-group\").forEach(r => r.classList.remove(\"active\"));\n       document.querySelectorAll(\".text-element\").forEach(t => t.classList.remove(\"active\"));\n       document.getElementById(\"image-title\").textContent = img.name || \"Image\";\n       document.getElementById(\"image-title\").onclick = () => editImageName(imageId);\n       document.getElementById(\"image-panel-opacity\").value = Math.round((img.opacity || 1) * 100);\n       document.getElementById(\"image-panel-opacity-val\").textContent = Math.round((img.opacity || 1) * 100) + \"%\";\n       document.getElementById(\"image-panel-border\").value = img.border || \"none\";\n       document.getElementById(\"image-panel-shadow\").value = img.shadow || \"none\";\n       const sizePercent = img.baseWidth ? Math.round((img.width / img.baseWidth) * 100) : 100;\n       document.getElementById(\"image-panel-size\").value = sizePercent;\n       document.getElementById(\"image-panel-size-val\").textContent = sizePercent + \"%\";\n       renderNotesFeed(img.notes || [], \"image-notes\", \"image\", imageId);\n      }\n      function editImageName(imageId) {\n       const img = IMAGE_DATA.list.find(i => i.id === imageId);\n       if (!img) return;\n       showEditModal(t(\"editModal.editName\"), img.name || \"\", (newName) => {\n        pushUndo(\"edit image name\");\n        img.name = newName;\n        document.getElementById(\"image-title\").textContent = newName || \"Image\";\n       });\n      }\n      function hideImagePanel() {\n       document.getElementById(\"image-panel\").style.display = \"none\";\n       currentImageId = null;\n      }\n      function deleteImage(imageId) {\n       pushUndo(\"delete image\");\n       IMAGE_DATA.list = IMAGE_DATA.list.filter(i => i.id !== imageId);\n       forgeTheTopology();\n       hideImagePanel();\n      }\n      document.getElementById(\"image-toggle\")?.addEventListener(\"click\", () => {\n       document.getElementById(\"image-file-input\").click();\n      });\n      document.getElementById(\"image-file-input\")?.addEventListener(\"change\", (e) => {\n       const file = e.target.files[0];\n       if (!file) return;\n       const svg = document.getElementById(\"map\");\n       const viewBox = svg.viewBox.baseVal;\n       const centerX = viewBox.x + viewBox.width / 2;\n       const centerY = viewBox.y + viewBox.height / 2;\n       processAndAddImage(file, centerX, centerY);\n       e.target.value = \"\";\n      });\n      document.getElementById(\"image-panel-opacity\")?.addEventListener(\"input\", (e) => {\n       if (!currentImageId) return;\n       const img = IMAGE_DATA.list.find(i => i.id === currentImageId);\n       if (img) {\n        img.opacity = parseInt(e.target.value) / 100;\n        document.getElementById(\"image-panel-opacity-val\").textContent = e.target.value + \"%\";\n        forgeTheTopology();\n       }\n      });\n      document.getElementById(\"image-panel-border\")?.addEventListener(\"change\", (e) => {\n       if (!currentImageId) return;\n       const img = IMAGE_DATA.list.find(i => i.id === currentImageId);\n       if (img) {\n        img.border = e.target.value;\n        forgeTheTopology();\n       }\n      });\n      document.getElementById(\"image-panel-shadow\")?.addEventListener(\"change\", (e) => {\n       if (!currentImageId) return;\n       const img = IMAGE_DATA.list.find(i => i.id === currentImageId);\n       if (img) {\n        img.shadow = e.target.value;\n        forgeTheTopology();\n       }\n      });\n      document.getElementById(\"image-panel-size\")?.addEventListener(\"input\", (e) => {\n       if (!currentImageId) return;\n       const img = IMAGE_DATA.list.find(i => i.id === currentImageId);\n       if (img) {\n        const scale = parseInt(e.target.value) / 100;\n        const baseWidth = img.baseWidth || img.width;\n        const baseHeight = img.baseHeight || img.height;\n        if (!img.baseWidth) img.baseWidth = img.width;\n        if (!img.baseHeight) img.baseHeight = img.height;\n        img.width = Math.round(baseWidth * scale);\n        img.height = Math.round(baseHeight * scale);\n        document.getElementById(\"image-panel-size-val\").textContent = e.target.value + \"%\";\n        forgeTheTopology();\n       }\n      });\n      document.getElementById(\"delete-image-panel\")?.addEventListener(\"click\", () => {\n       if (!currentImageId) return;\n       deleteImage(currentImageId);\n      });\n      document.getElementById(\"add-image-note\")?.addEventListener(\"click\", () => {\n       if (!currentImageId) return;\n       const img = IMAGE_DATA.list.find(i => i.id === currentImageId);\n       if (!img) return;\n       if (!img.notes) img.notes = [];\n       showNoteEditor(t(\"noteEditor.addNote\"), \"\", (content) => {\n        pushUndo(\"add image note\");\n        img.notes.push(content);\n        renderNotesFeed(img.notes, \"image-notes\", \"image\", currentImageId);\n       });\n      });\n      (function() {\n       const viewport = document.getElementById(\"canvas-viewport\");\n       viewport?.addEventListener(\"dragover\", (e) => {\n        e.preventDefault();\n        e.dataTransfer.dropEffect = \"copy\";\n       });\n       viewport?.addEventListener(\"drop\", (e) => {\n        e.preventDefault();\n        if (isViewOnly()) return;\n        const files = e.dataTransfer.files;\n        if (files.length === 0) return;\n        const file = files[0];\n        if (!file.type.startsWith(\"image/\")) return;\n        const svg = document.getElementById(\"map\");\n        const pt = svg.createSVGPoint();\n        pt.x = e.clientX;\n        pt.y = e.clientY;\n        const svgP = pt.matrixTransform(svg.getScreenCTM().inverse());\n        processAndAddImage(file, svgP.x, svgP.y);\n       });\n      })();\n      document.getElementById(\"text-content\").addEventListener(\"input\", (e) => {\n       if (!currentTextId) return;\n       const textItem = TEXT_DATA.list.find(t => t.id === currentTextId);\n       if (textItem) {\n      pushUndo(\"edit text\");\n        textItem.content = e.target.value;\n        forgeTheTopology();\n       }\n      });\n      document.getElementById(\"text-font-size\").addEventListener(\"input\", (e) => {\n       if (!currentTextId) return;\n       const textItem = TEXT_DATA.list.find(t => t.id === currentTextId);\n       if (textItem) {\n        pushUndo(\"edit text\");\n        textItem.fontSize = parseInt(e.target.value);\n        forgeTheTopology();\n       }\n      });\n      document.getElementById(\"text-color\").addEventListener(\"input\", (e) => {\n       if (!currentTextId) return;\n       const textItem = TEXT_DATA.list.find(t => t.id === currentTextId);\n       if (textItem) {\n        pushUndo(\"edit text\");\n        textItem.color = e.target.value;\n        forgeTheTopology();\n       }\n      });\n      document.getElementById(\"text-font-weight\").addEventListener(\"change\", (e) => {\n       if (!currentTextId) return;\n       const textItem = TEXT_DATA.list.find(t => t.id === currentTextId);\n       if (textItem) {\n        pushUndo(\"edit text\");\n        textItem.fontWeight = e.target.value;\n        forgeTheTopology();\n       }\n      });\n      document.getElementById(\"text-font-style\").addEventListener(\"change\", (e) => {\n       if (!currentTextId) return;\n       const textItem = TEXT_DATA.list.find(t => t.id === currentTextId);\n       if (textItem) {\n        pushUndo(\"edit text\");\n        textItem.fontStyle = e.target.value;\n        forgeTheTopology();\n       }\n      });\n      document.getElementById(\"text-align\").addEventListener(\"change\", (e) => {\n       if (!currentTextId) return;\n       const textItem = TEXT_DATA.list.find(t => t.id === currentTextId);\n       if (textItem) {\n        pushUndo(\"edit text\");\n        textItem.textAlign = e.target.value;\n        forgeTheTopology();\n       }\n      });\n      document.getElementById(\"text-decoration\").addEventListener(\"change\", (e) => {\n       if (!currentTextId) return;\n       const textItem = TEXT_DATA.list.find(t => t.id === currentTextId);\n       if (textItem) {\n        pushUndo(\"edit text\");\n        textItem.textDecoration = e.target.value;\n        forgeTheTopology();\n       }\n      });\n      document.getElementById(\"text-bg-color\").addEventListener(\"input\", (e) => {\n       if (!currentTextId) return;\n       const textItem = TEXT_DATA.list.find(t => t.id === currentTextId);\n       if (textItem) {\n        pushUndo(\"edit text\");\n        textItem.bgColor = e.target.value;\n        forgeTheTopology();\n       }\n      });\n      document.getElementById(\"text-bg-enabled\").addEventListener(\"change\", (e) => {\n       if (!currentTextId) return;\n       const textItem = TEXT_DATA.list.find(t => t.id === currentTextId);\n       if (textItem) {\n        pushUndo(\"edit text\");\n        textItem.bgEnabled = e.target.checked;\n        forgeTheTopology();\n       }\n      });\n      document.getElementById(\"text-rotation\").addEventListener(\"input\", (e) => {\n        if (!currentTextId) return;\n        const textItem = TEXT_DATA.list.find(t => t.id === currentTextId);\n        if (textItem) {\n          pushUndo(\"rotate text\");\n          textItem.rotation = parseInt(e.target.value) || 0;\n          document.getElementById(\"text-rotation-val\").value = textItem.rotation;\n          forgeTheTopology();\n        }\n      });\n      document.getElementById(\"text-rotation-val\").addEventListener(\"input\", (e) => {\n        if (!currentTextId) return;\n        const textItem = TEXT_DATA.list.find(t => t.id === currentTextId);\n        if (textItem) {\n          pushUndo(\"rotate text\");\n          const val = parseInt(e.target.value) || 0;\n          textItem.rotation = val;\n          document.getElementById(\"text-rotation\").value = Math.max(-360, Math.min(360, val));\n          forgeTheTopology();\n        }\n      });\n      document.getElementById(\"delete-text\").addEventListener(\"click\", () => {\n       if (!currentTextId) return;\n       const textItem = TEXT_DATA.list.find(t => t.id === currentTextId);\n       if (textItem) {\n        challengeTheImmortal(`Delete text \"${(textItem.content || '').substring(0, 30)}...\"?`, () => {\n         deleteText(currentTextId);\n        });\n       }\n      });\n      const settingsBtn = document.getElementById(\"settings-btn\");\n      const settingsModal = document.getElementById(\"settings-modal\");\n      const settingsClose = document.getElementById(\"settings-close\");\n      settingsBtn.addEventListener(\"click\", () => {\n       document.getElementById(\"page-bg-color\").value = PAGE_STATE.background || \"#050608\";\n       document.getElementById(\"topbar-bg-color\").value = PAGE_STATE.topbarBg || \"#0b0e13\";\n       document.getElementById(\"topbar-border-color\").value = PAGE_STATE.topbarBorder || \"#1f2533\";\n       document.getElementById(\"panel-color\").value = PAGE_STATE.panel || \"#0b0e13\";\n       document.getElementById(\"panel-alt-color\").value = PAGE_STATE.panelAlt || \"#10141b\";\n       document.getElementById(\"sidebar-bg-color\").value = PAGE_STATE.sidebarBg || \"#10141b\";\n       document.getElementById(\"btn-bg-color\").value = PAGE_STATE.btnBg || \"#0b0e13\";\n       document.getElementById(\"btn-text-color\").value = PAGE_STATE.btnText || \"#e2e8f0\";\n       document.getElementById(\"tag-fill-color\").value = PAGE_STATE.tagFill || \"#1e293b\";\n       document.getElementById(\"tag-text-color\").value = PAGE_STATE.tagText || \"#e2e8f0\";\n       document.getElementById(\"tag-border-color\").value = PAGE_STATE.tagBorder || \"#475569\";\n       document.getElementById(\"input-bg-color\").value = PAGE_STATE.inputBg || \"#0b0e13\";\n       document.getElementById(\"input-text-color\").value = PAGE_STATE.inputText || \"#e2e8f0\";\n       document.getElementById(\"input-border-color\").value = PAGE_STATE.inputBorder || \"#1f2937\";\n       document.getElementById(\"input-font-family\").value = PAGE_STATE.inputFont || \"Inter, system-ui, sans-serif\";\n       document.getElementById(\"input-font-size\").value = PAGE_STATE.inputFontSize || 14;\n       document.getElementById(\"toolbar-bg-color\").value = PAGE_STATE.toolbarBg || \"#0f172a\";\n       document.getElementById(\"toolbar-border-color\").value = PAGE_STATE.toolbarBorder || \"#1f2937\";\n       document.getElementById(\"toolbar-text-color\").value = PAGE_STATE.toolbarText || \"#94a3b8\";\n       document.getElementById(\"toolbar-btn-bg-color\").value = PAGE_STATE.toolbarBtnBg || \"#0b0e13\";\n       document.getElementById(\"toolbar-btn-text-color\").value = PAGE_STATE.toolbarBtnText || \"#e2e8f0\";\n       document.getElementById(\"minimap-dots-color\").value = PAGE_STATE.minimapDots || \"#94a3b8\";\n       document.getElementById(\"canvas-hint-enabled\").checked = PAGE_STATE.canvasHintEnabled !== false;\n       document.getElementById(\"canvas-hint-bg-color\").value = PAGE_STATE.canvasHintBg || \"#0f172a\";\n       document.getElementById(\"canvas-hint-text-color\").value = PAGE_STATE.canvasHintColor || \"#94a3b8\";\n       document.getElementById(\"canvas-hint-text\").value = PAGE_STATE.canvasHintText || \"\";\n       document.getElementById(\"accent-color\").value = PAGE_STATE.accent || \"#4fd1c5\";\n       document.getElementById(\"danger-color\").value = PAGE_STATE.danger || \"#f56565\";\n       document.getElementById(\"text-main-color\").value = PAGE_STATE.textMain || \"#e2e8f0\";\n       document.getElementById(\"text-soft-color\").value = PAGE_STATE.textSoft || \"#94a3b8\";\n       document.getElementById(\"node-fill-color\").value = PAGE_STATE.nodeFill || \"#1e293b\";\n       document.getElementById(\"node-stroke-color\").value = PAGE_STATE.nodeStroke || \"#475569\";\n       document.getElementById(\"node-title-color\").value = PAGE_STATE.nodeTitle || \"#e2e8f0\";\n       document.getElementById(\"node-sub-color\").value = PAGE_STATE.nodeSub || \"#94a3b8\";\n       document.getElementById(\"node-title-size\").value = PAGE_STATE.nodeTitleSize || 18;\n       document.getElementById(\"node-sub-size\").value = PAGE_STATE.nodeSubSize || 13;\n       document.getElementById(\"node-font-family\").value = PAGE_STATE.nodeFont || \"Inter, system-ui, sans-serif\";\n       document.getElementById(\"default-edge-color\").value = PAGE_STATE.defaultEdge || \"#475569\";\n       document.getElementById(\"add-line-color\").value = PAGE_STATE.defaultEdge || \"#475569\";\n       document.getElementById(\"default-edge-routing\").value = PAGE_STATE.defaultEdgeRouting || \"curved\";\n\t   document.getElementById(\"anim-master\").addEventListener(\"change\", e => { ANIM_SETTINGS.masterAnim = e.target.checked; applyAnimZoneSettings(); });\ndocument.getElementById(\"anim-type-sweep\").addEventListener(\"change\", e => { ANIM_SETTINGS.animTypes.sweep = e.target.checked; applyAnimZoneSettings(); });\ndocument.getElementById(\"anim-type-pulse\").addEventListener(\"change\", e => { ANIM_SETTINGS.animTypes.pulse = e.target.checked; applyAnimZoneSettings(); });\ndocument.getElementById(\"anim-type-rings\").addEventListener(\"change\", e => { ANIM_SETTINGS.animTypes.rings = e.target.checked; applyAnimZoneSettings(); });\ndocument.getElementById(\"anim-type-spin\").addEventListener(\"change\", e => { ANIM_SETTINGS.animTypes.spin = e.target.checked; applyAnimZoneSettings(); });\ndocument.getElementById(\"anim-type-connections\").addEventListener(\"change\", e => { ANIM_SETTINGS.animTypes.connections = e.target.checked; applyAnimZoneSettings(); });\ndocument.getElementById(\"anim-cat-camera\").addEventListener(\"change\", e => { ANIM_SETTINGS.animCategories.camera = e.target.checked; applyAnimZoneSettings(); });\ndocument.getElementById(\"anim-cat-doorbell\").addEventListener(\"change\", e => { ANIM_SETTINGS.animCategories.doorbell = e.target.checked; applyAnimZoneSettings(); });\ndocument.getElementById(\"anim-cat-motion\").addEventListener(\"change\", e => { ANIM_SETTINGS.animCategories.motion = e.target.checked; applyAnimZoneSettings(); });\ndocument.getElementById(\"anim-cat-smoke\").addEventListener(\"change\", e => { ANIM_SETTINGS.animCategories.smoke = e.target.checked; applyAnimZoneSettings(); });\ndocument.getElementById(\"anim-cat-wifi\").addEventListener(\"change\", e => { ANIM_SETTINGS.animCategories.wifi = e.target.checked; applyAnimZoneSettings(); });\ndocument.getElementById(\"anim-cat-sensor\").addEventListener(\"change\", e => { ANIM_SETTINGS.animCategories.sensor = e.target.checked; applyAnimZoneSettings(); });\ndocument.getElementById(\"anim-cat-sprinkler\").addEventListener(\"change\", e => { ANIM_SETTINGS.animCategories.sprinkler = e.target.checked; applyAnimZoneSettings(); });\ndocument.getElementById(\"anim-cat-connections\").addEventListener(\"change\", e => { ANIM_SETTINGS.animCategories.connections = e.target.checked; applyAnimZoneSettings(); });\ndocument.getElementById(\"zone-master\").addEventListener(\"change\", e => { ANIM_SETTINGS.masterZones = e.target.checked; applyAnimZoneSettings(); });\ndocument.getElementById(\"zone-cat-camera\").addEventListener(\"change\", e => { ANIM_SETTINGS.zoneCategories.camera = e.target.checked; applyAnimZoneSettings(); });\ndocument.getElementById(\"zone-cat-doorbell\").addEventListener(\"change\", e => { ANIM_SETTINGS.zoneCategories.doorbell = e.target.checked; applyAnimZoneSettings(); });\ndocument.getElementById(\"zone-cat-motion\").addEventListener(\"change\", e => { ANIM_SETTINGS.zoneCategories.motion = e.target.checked; applyAnimZoneSettings(); });\ndocument.getElementById(\"zone-cat-smoke\").addEventListener(\"change\", e => { ANIM_SETTINGS.zoneCategories.smoke = e.target.checked; applyAnimZoneSettings(); });\ndocument.getElementById(\"zone-cat-wifi\").addEventListener(\"change\", e => { ANIM_SETTINGS.zoneCategories.wifi = e.target.checked; applyAnimZoneSettings(); });\ndocument.getElementById(\"zone-cat-sensor\").addEventListener(\"change\", e => { ANIM_SETTINGS.zoneCategories.sensor = e.target.checked; applyAnimZoneSettings(); });\ndocument.getElementById(\"zone-cat-sprinkler\").addEventListener(\"change\", e => { ANIM_SETTINGS.zoneCategories.sprinkler = e.target.checked; applyAnimZoneSettings(); });\n\n      document.getElementById(\"animation-style-select\").value = PAGE_STATE.animationStyle || \"arrows\";\n      document.getElementById(\"animation-direction-select\").value = PAGE_STATE.animationDirection || \"all\";\n      document.getElementById(\"animation-speed-select\").value = PAGE_STATE.animationSpeed || 1.5;\n\n      document.getElementById(\"animation-style-select\").addEventListener(\"change\", (e) => {\n       PAGE_STATE.animationStyle = e.target.value;\n       if (PAGE_STATE.animateConnections) forgeTheTopology();\n      });\n      document.getElementById(\"animation-direction-select\").addEventListener(\"change\", (e) => {\n       PAGE_STATE.animationDirection = e.target.value;\n       if (PAGE_STATE.animateConnections) forgeTheTopology();\n      });\n      document.getElementById(\"animation-speed-select\").addEventListener(\"change\", (e) => {\n       PAGE_STATE.animationSpeed = parseFloat(e.target.value);\n       if (PAGE_STATE.animateConnections) forgeTheTopology();\n      });\n       document.getElementById(\"add-line-routing\").value = PAGE_STATE.defaultEdgeRouting || \"orthogonal\";\n       document.getElementById(\"selection-handle-color\").value = PAGE_STATE.selectionHandle || \"#f59e0b\";\n       document.getElementById(\"selection-handle-size\").value = PAGE_STATE.selectionHandleSize || 8;\n       document.getElementById(\"group-indicator-color\").value = PAGE_STATE.groupIndicator || \"#4fd1c5\";\n       document.getElementById(\"canvas-gradient-top\").value = PAGE_STATE.canvasGradientTop || \"#1e2532\";\n       document.getElementById(\"canvas-gradient-bottom\").value = PAGE_STATE.canvasGradientBottom || \"#050608\";\n       document.getElementById(\"canvas-border-color\").value = PAGE_STATE.canvasBorder || \"#475569\";\n       document.getElementById(\"canvas-grid-color\").value = PAGE_STATE.canvasGrid || \"#475569\";\n       document.getElementById(\"canvas-grid-size\").value = PAGE_STATE.canvasGridSize || 50;\n\t   document.getElementById(\"canvas-grid-enabled\").checked = PAGE_STATE.canvasGridEnabled !== false;\n       document.getElementById(\"rack-frame-fill\").value = PAGE_STATE.rackFrameFill || \"#0f172a\";\n       document.getElementById(\"rack-frame-stroke\").value = PAGE_STATE.rackFrameStroke || \"#4fd1c5\";\n       document.getElementById(\"rack-line-color\").value = PAGE_STATE.rackLineColor || \"#475569\";\n\t   document.getElementById(\"rack-grid-enabled\").checked = PAGE_STATE.rackGridEnabled !== false;\n       document.getElementById(\"rack-text-color\").value = PAGE_STATE.rackTextColor || \"#4fd1c5\";\n\t   document.getElementById(\"view-only-mode\").checked = PAGE_STATE.viewOnly === true;\n\t   document.getElementById(\"show-port-labels-toggle\").checked = PAGE_STATE.showPortLabels !== false;\n\t   document.getElementById(\"version-display\").textContent = THE_ONE_FILE_VERSION;\n       rebuildThemeDropdown();\n       updateDeleteButton();\n       settingsModal.classList.add(\"active\");\n      });\n      settingsClose.addEventListener(\"click\", () => {\n       settingsModal.classList.remove(\"active\");\n      });\n\t  document.getElementById(\"view-only-mode\").addEventListener(\"change\", (e) => {\n       PAGE_STATE.viewOnly = e.target.checked;\n       if (e.target.checked) {\n        document.getElementById(\"node-panel\").style.display = \"none\";\n        document.getElementById(\"edge-panel\").style.display = \"none\";\n        document.getElementById(\"text-panel\").style.display = \"none\";\n        document.getElementById(\"rect-panel\").style.display = \"none\";\n        document.getElementById(\"image-panel\").style.display = \"none\";\n        document.getElementById(\"topology-toolbar\").style.setProperty('display', 'none', 'important');\n        document.getElementById(\"draw-toolbar\").style.setProperty('display', 'none', 'important');\n        currentNodeId = null;\n        currentEdgeId = null;\n        currentRectId = null;\n        currentTextId = null;\n        selectedNodes.clear();\n        selectedRects.clear();\n        selectedTexts.clear();\n        selectedEdges.clear();\n        const bulkToolbar = document.getElementById(\"bulk-toolbar\");\n        const bulkToolbarMobile = document.getElementById(\"bulk-toolbar-mobile\");\n        if (bulkToolbar) bulkToolbar.style.display = \"none\";\n        if (bulkToolbarMobile) bulkToolbarMobile.style.display = \"none\";\n        document.querySelectorAll(\".node-group\").forEach(n => n.classList.remove(\"active\", \"selected\"));\n        document.querySelectorAll(\".edge\").forEach(e => e.classList.remove(\"active\"));\n        document.querySelectorAll(\".rect-group\").forEach(r => r.classList.remove(\"active\"));\n        document.querySelectorAll(\".text-element\").forEach(t => t.classList.remove(\"active\"));\n       }\n       wieldThePower();\n       if (!e.target.checked) {\n        updateDrawToolbarVisibility();\n        updateTopologyToolbarVisibility();\n       }\n       forgeTheTopology();\n      });\n      settingsModal.addEventListener(\"click\", (e) => {\n       if (e.target === settingsModal) {\n        settingsModal.classList.remove(\"active\");\n       }\n      });\n      document.getElementById(\"page-bg-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.background = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"topbar-bg-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.topbarBg = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"topbar-border-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.topbarBorder = e.target.value;\n       wieldThePower();\n      });\nconst THEME_PRESETS = {\n  defaulted: { panel:\"#0b0e13\",panelAlt:\"#10141b\",sidebarBg:\"#10141b\",btnBg:\"#0b0e13\",btnText:\"#e2e8f0\",accent:\"#4fd1c5\",danger:\"#f56565\",textMain:\"#e2e8f0\",textSoft:\"#94a3b8\",topbarBg:\"rgba(9,12,20,0.9)\",topbarBorder:\"#1f2533\",nodeFill:\"#1e293b\",nodeStroke:\"#475569\",nodeTitle:\"#e2e8f0\",nodeSub:\"#94a3b8\",defaultEdge:\"#475569\",canvasGradientTop:\"#1e2532\",canvasGradientBottom:\"#050608\",tagFill:\"#1e293b\",tagText:\"#e2e8f0\",tagBorder:\"#475569\",inputBg:\"#0b0e13\",inputText:\"#e2e8f0\",inputBorder:\"#1f2937\",toolbarBg:\"#0f172a\",toolbarBorder:\"#1f2937\",toolbarText:\"#94a3b8\",toolbarBtnBg:\"#0b0e13\",toolbarBtnText:\"#e2e8f0\",selectionHandle:\"#f59e0b\",groupIndicator:\"#4fd1c5\",minimapDots:\"#94a3b8\",canvasHintBg:\"#0f172a\",canvasHintColor:\"#94a3b8\",canvasBorder:\"#475569\",canvasGrid:\"#475569\",rackFrameFill:\"#0f172a\",rackFrameStroke:\"#4fd1c5\",rackLineColor:\"#475569\",rackTextColor:\"#4fd1c5\" },\n  slate: { panel:\"#1e293b\",panelAlt:\"#334155\",sidebarBg:\"#1e293b\",btnBg:\"#334155\",btnText:\"#f1f5f9\",accent:\"#3b82f6\",danger:\"#ef4444\",textMain:\"#f1f5f9\",textSoft:\"#94a3b8\",topbarBg:\"#0f172a\",topbarBorder:\"#334155\",nodeFill:\"#334155\",nodeStroke:\"#3b82f6\",nodeTitle:\"#f1f5f9\",nodeSub:\"#94a3b8\",defaultEdge:\"#64748b\",canvasGradientTop:\"#1e293b\",canvasGradientBottom:\"#0f172a\",tagFill:\"#1e3a5f\",tagText:\"#93c5fd\",tagBorder:\"#3b82f6\",inputBg:\"#0f172a\",inputText:\"#f1f5f9\",inputBorder:\"#475569\",toolbarBg:\"#2563eb\",toolbarBorder:\"#3b82f6\",toolbarText:\"#ffffff\",toolbarBtnBg:\"#3b82f6\",toolbarBtnText:\"#ffffff\",selectionHandle:\"#f59e0b\",groupIndicator:\"#22d3ee\",minimapDots:\"#64748b\",canvasHintBg:\"#1e293b\",canvasHintColor:\"#94a3b8\",canvasBorder:\"#334155\",canvasGrid:\"#334155\",rackFrameFill:\"#1e293b\",rackFrameStroke:\"#3b82f6\",rackLineColor:\"#475569\",rackTextColor:\"#3b82f6\" },\n  graphite: { panel:\"#1f2937\",panelAlt:\"#374151\",sidebarBg:\"#111827\",btnBg:\"#374151\",btnText:\"#f9fafb\",accent:\"#f59e0b\",danger:\"#ef4444\",textMain:\"#f9fafb\",textSoft:\"#9ca3af\",topbarBg:\"#111827\",topbarBorder:\"#4b5563\",nodeFill:\"#374151\",nodeStroke:\"#f59e0b\",nodeTitle:\"#f9fafb\",nodeSub:\"#9ca3af\",defaultEdge:\"#6b7280\",canvasGradientTop:\"#1f2937\",canvasGradientBottom:\"#111827\",tagFill:\"#44403c\",tagText:\"#fbbf24\",tagBorder:\"#f59e0b\",inputBg:\"#111827\",inputText:\"#f9fafb\",inputBorder:\"#4b5563\",toolbarBg:\"#b45309\",toolbarBorder:\"#f59e0b\",toolbarText:\"#ffffff\",toolbarBtnBg:\"#f59e0b\",toolbarBtnText:\"#000000\",selectionHandle:\"#10b981\",groupIndicator:\"#06b6d4\",minimapDots:\"#6b7280\",canvasHintBg:\"#1f2937\",canvasHintColor:\"#9ca3af\",canvasBorder:\"#4b5563\",canvasGrid:\"#374151\",rackFrameFill:\"#1f2937\",rackFrameStroke:\"#f59e0b\",rackLineColor:\"#4b5563\",rackTextColor:\"#fbbf24\" },\n  frost: { panel:\"#f8fafc\",panelAlt:\"#e2e8f0\",sidebarBg:\"#f1f5f9\",btnBg:\"#e2e8f0\",btnText:\"#1e293b\",accent:\"#1e40af\",danger:\"#dc2626\",textMain:\"#0f172a\",textSoft:\"#475569\",topbarBg:\"#1e40af\",topbarBorder:\"#1e3a8a\",nodeFill:\"#ffffff\",nodeStroke:\"#1e40af\",nodeTitle:\"#0f172a\",nodeSub:\"#475569\",defaultEdge:\"#64748b\",canvasGradientTop:\"#e0e7ef\",canvasGradientBottom:\"#f8fafc\",tagFill:\"#dbeafe\",tagText:\"#1e40af\",tagBorder:\"#3b82f6\",inputBg:\"#ffffff\",inputText:\"#0f172a\",inputBorder:\"#cbd5e1\",toolbarBg:\"#1e40af\",toolbarBorder:\"#1e3a8a\",toolbarText:\"#ffffff\",toolbarBtnBg:\"#3b82f6\",toolbarBtnText:\"#ffffff\",selectionHandle:\"#ea580c\",groupIndicator:\"#059669\",minimapDots:\"#64748b\",canvasHintBg:\"#e2e8f0\",canvasHintColor:\"#475569\",canvasBorder:\"#cbd5e1\",canvasGrid:\"#cbd5e1\",rackFrameFill:\"#f1f5f9\",rackFrameStroke:\"#1e40af\",rackLineColor:\"#94a3b8\",rackTextColor:\"#1e40af\" },\n  synthwave: { panel:\"#87366d\",panelAlt:\"#10141b\",sidebarBg:\"#340934\",btnBg:\"#0b0e13\",btnText:\"#e2e8f0\",accent:\"#4fd1c5\",danger:\"#f56565\",textMain:\"#e2e8f0\",textSoft:\"#94a3b8\",topbarBg:\"#781c67\",topbarBorder:\"#1f2533\",nodeFill:\"#1e293b\",nodeStroke:\"#475569\",nodeTitle:\"#e2e8f0\",nodeSub:\"#94a3b8\",defaultEdge:\"#475569\",canvasGradientTop:\"#1e2532\",canvasGradientBottom:\"#050608\",tagFill:\"#1e293b\",tagText:\"#e2e8f0\",tagBorder:\"#475569\",inputBg:\"#0b0e13\",inputText:\"#e2e8f0\",inputBorder:\"#1f2937\",toolbarBg:\"#b95aed\",toolbarBorder:\"#b800eb\",toolbarText:\"#000000\",toolbarBtnBg:\"#ed01fe\",toolbarBtnText:\"#000000\",selectionHandle:\"#f59e0b\",groupIndicator:\"#4fd1c5\",minimapDots:\"#000000\",canvasHintBg:\"#0f172a\",canvasHintColor:\"#94a3b8\",canvasBorder:\"#000000\",canvasGrid:\"#000000\",rackFrameFill:\"#0f172a\",rackFrameStroke:\"#4fd1c5\",rackLineColor:\"#475569\",rackTextColor:\"#4fd1c5\" },\n  terminal: { panel:\"#000000\",panelAlt:\"#0a0a0a\",sidebarBg:\"#050505\",btnBg:\"#0a0a0a\",btnText:\"#33ff33\",accent:\"#33ff33\",danger:\"#ffaa00\",textMain:\"#33ff33\",textSoft:\"#1a9a1a\",topbarBg:\"#000000\",topbarBorder:\"#33ff33\",nodeFill:\"#0a0a0a\",nodeStroke:\"#33ff33\",nodeTitle:\"#33ff33\",nodeSub:\"#1a9a1a\",defaultEdge:\"#1a9a1a\",canvasGradientTop:\"#0a0f0a\",canvasGradientBottom:\"#000000\",tagFill:\"#0a1a0a\",tagText:\"#33ff33\",tagBorder:\"#33ff33\",inputBg:\"#000000\",inputText:\"#33ff33\",inputBorder:\"#1a9a1a\",toolbarBg:\"#1a9a1a\",toolbarBorder:\"#33ff33\",toolbarText:\"#000000\",toolbarBtnBg:\"#33ff33\",toolbarBtnText:\"#000000\",selectionHandle:\"#ffaa00\",groupIndicator:\"#00ffff\",minimapDots:\"#33ff33\",canvasHintBg:\"#0a0a0a\",canvasHintColor:\"#1a9a1a\",canvasBorder:\"#1a9a1a\",canvasGrid:\"#0f1f0f\",rackFrameFill:\"#050505\",rackFrameStroke:\"#33ff33\",rackLineColor:\"#1a9a1a\",rackTextColor:\"#33ff33\" },\n  dracula: { panel:\"#282a36\",panelAlt:\"#44475a\",sidebarBg:\"#21222c\",btnBg:\"#44475a\",btnText:\"#f8f8f2\",accent:\"#bd93f9\",danger:\"#ff5555\",textMain:\"#f8f8f2\",textSoft:\"#6272a4\",topbarBg:\"#21222c\",topbarBorder:\"#6272a4\",nodeFill:\"#44475a\",nodeStroke:\"#ff79c6\",nodeTitle:\"#f8f8f2\",nodeSub:\"#8be9fd\",defaultEdge:\"#bd93f9\",canvasGradientTop:\"#282a36\",canvasGradientBottom:\"#1a1b23\",tagFill:\"#3d3f4a\",tagText:\"#50fa7b\",tagBorder:\"#50fa7b\",inputBg:\"#21222c\",inputText:\"#f8f8f2\",inputBorder:\"#6272a4\",toolbarBg:\"#6272a4\",toolbarBorder:\"#bd93f9\",toolbarText:\"#f8f8f2\",toolbarBtnBg:\"#bd93f9\",toolbarBtnText:\"#282a36\",selectionHandle:\"#f1fa8c\",groupIndicator:\"#ff79c6\",minimapDots:\"#bd93f9\",canvasHintBg:\"#282a36\",canvasHintColor:\"#6272a4\",canvasBorder:\"#44475a\",canvasGrid:\"#44475a\",rackFrameFill:\"#282a36\",rackFrameStroke:\"#ff79c6\",rackLineColor:\"#6272a4\",rackTextColor:\"#8be9fd\" },\n  cobalt: { panel:\"#002240\",panelAlt:\"#003366\",sidebarBg:\"#001b33\",btnBg:\"#003366\",btnText:\"#ffffff\",accent:\"#ffc600\",danger:\"#ff628c\",textMain:\"#ffffff\",textSoft:\"#8090a0\",topbarBg:\"#001525\",topbarBorder:\"#0088ff\",nodeFill:\"#003366\",nodeStroke:\"#0088ff\",nodeTitle:\"#ffffff\",nodeSub:\"#80ffbb\",defaultEdge:\"#0088ff\",canvasGradientTop:\"#002240\",canvasGradientBottom:\"#00111f\",tagFill:\"#004080\",tagText:\"#ffc600\",tagBorder:\"#0088ff\",inputBg:\"#001525\",inputText:\"#ffffff\",inputBorder:\"#0066cc\",toolbarBg:\"#0066cc\",toolbarBorder:\"#0088ff\",toolbarText:\"#ffffff\",toolbarBtnBg:\"#0088ff\",toolbarBtnText:\"#ffffff\",selectionHandle:\"#ffc600\",groupIndicator:\"#3ad900\",minimapDots:\"#0088ff\",canvasHintBg:\"#002240\",canvasHintColor:\"#8090a0\",canvasBorder:\"#0066cc\",canvasGrid:\"#003366\",rackFrameFill:\"#002240\",rackFrameStroke:\"#0088ff\",rackLineColor:\"#0066cc\",rackTextColor:\"#80ffbb\" },\n  solarized: { panel:\"#073642\",panelAlt:\"#002b36\",sidebarBg:\"#002b36\",btnBg:\"#073642\",btnText:\"#93a1a1\",accent:\"#268bd2\",danger:\"#dc322f\",textMain:\"#93a1a1\",textSoft:\"#657b83\",topbarBg:\"#002b36\",topbarBorder:\"#586e75\",nodeFill:\"#073642\",nodeStroke:\"#2aa198\",nodeTitle:\"#93a1a1\",nodeSub:\"#839496\",defaultEdge:\"#657b83\",canvasGradientTop:\"#073642\",canvasGradientBottom:\"#002b36\",tagFill:\"#0a4050\",tagText:\"#b58900\",tagBorder:\"#b58900\",inputBg:\"#002b36\",inputText:\"#93a1a1\",inputBorder:\"#586e75\",toolbarBg:\"#268bd2\",toolbarBorder:\"#2aa198\",toolbarText:\"#fdf6e3\",toolbarBtnBg:\"#2aa198\",toolbarBtnText:\"#002b36\",selectionHandle:\"#cb4b16\",groupIndicator:\"#d33682\",minimapDots:\"#657b83\",canvasHintBg:\"#073642\",canvasHintColor:\"#657b83\",canvasBorder:\"#586e75\",canvasGrid:\"#094050\",rackFrameFill:\"#002b36\",rackFrameStroke:\"#2aa198\",rackLineColor:\"#586e75\",rackTextColor:\"#859900\" },\n  brainwave: { panel:\"#1a1625\",panelAlt:\"#2d2640\",sidebarBg:\"#12101c\",btnBg:\"#2d2640\",btnText:\"#e8dff5\",accent:\"#9d4edd\",danger:\"#ff6b9d\",textMain:\"#e8dff5\",textSoft:\"#a89cc8\",topbarBg:\"#12101c\",topbarBorder:\"#5a189a\",nodeFill:\"#240046\",nodeStroke:\"#7b2cbf\",nodeTitle:\"#e0aaff\",nodeSub:\"#c77dff\",defaultEdge:\"#9d4edd\",canvasGradientTop:\"#1a1625\",canvasGradientBottom:\"#0d0a14\",tagFill:\"#3c096c\",tagText:\"#e0aaff\",tagBorder:\"#9d4edd\",inputBg:\"#12101c\",inputText:\"#e8dff5\",inputBorder:\"#5a189a\",toolbarBg:\"#7b2cbf\",toolbarBorder:\"#9d4edd\",toolbarText:\"#ffffff\",toolbarBtnBg:\"#9d4edd\",toolbarBtnText:\"#ffffff\",selectionHandle:\"#ff9e00\",groupIndicator:\"#00f5d4\",minimapDots:\"#9d4edd\",canvasHintBg:\"#1a1625\",canvasHintColor:\"#a89cc8\",canvasBorder:\"#5a189a\",canvasGrid:\"#2d2640\",rackFrameFill:\"#1a1625\",rackFrameStroke:\"#9d4edd\",rackLineColor:\"#5a189a\",rackTextColor:\"#e0aaff\" },\n  neonMint: { panel:\"#0d1117\",panelAlt:\"#161b22\",sidebarBg:\"#010409\",btnBg:\"#161b22\",btnText:\"#7ee8c7\",accent:\"#00ffc8\",danger:\"#ff6b6b\",textMain:\"#7ee8c7\",textSoft:\"#3fb68a\",topbarBg:\"#010409\",topbarBorder:\"#00ffc8\",nodeFill:\"#0d1f17\",nodeStroke:\"#00ffc8\",nodeTitle:\"#7ee8c7\",nodeSub:\"#3fb68a\",defaultEdge:\"#00d9a7\",canvasGradientTop:\"#0d1117\",canvasGradientBottom:\"#010409\",tagFill:\"#0d2818\",tagText:\"#00ffc8\",tagBorder:\"#00d9a7\",inputBg:\"#010409\",inputText:\"#7ee8c7\",inputBorder:\"#1a4d3a\",toolbarBg:\"#00a67e\",toolbarBorder:\"#00ffc8\",toolbarText:\"#010409\",toolbarBtnBg:\"#00ffc8\",toolbarBtnText:\"#010409\",selectionHandle:\"#ffd000\",groupIndicator:\"#ff79c6\",minimapDots:\"#00ffc8\",canvasHintBg:\"#0d1117\",canvasHintColor:\"#3fb68a\",canvasBorder:\"#1a4d3a\",canvasGrid:\"#0a2920\",rackFrameFill:\"#0d1117\",rackFrameStroke:\"#00ffc8\",rackLineColor:\"#1a4d3a\",rackTextColor:\"#00ffc8\" },\n  velvetDusk: { panel:\"#1f1d2e\",panelAlt:\"#26233a\",sidebarBg:\"#191724\",btnBg:\"#26233a\",btnText:\"#e0def4\",accent:\"#eb6f92\",danger:\"#eb6f92\",textMain:\"#e0def4\",textSoft:\"#908caa\",topbarBg:\"#191724\",topbarBorder:\"#524f67\",nodeFill:\"#26233a\",nodeStroke:\"#c4a7e7\",nodeTitle:\"#e0def4\",nodeSub:\"#9ccfd8\",defaultEdge:\"#ebbcba\",canvasGradientTop:\"#1f1d2e\",canvasGradientBottom:\"#191724\",tagFill:\"#31283d\",tagText:\"#f6c177\",tagBorder:\"#c4a7e7\",inputBg:\"#191724\",inputText:\"#e0def4\",inputBorder:\"#524f67\",toolbarBg:\"#c4a7e7\",toolbarBorder:\"#eb6f92\",toolbarText:\"#191724\",toolbarBtnBg:\"#eb6f92\",toolbarBtnText:\"#191724\",selectionHandle:\"#f6c177\",groupIndicator:\"#31748f\",minimapDots:\"#c4a7e7\",canvasHintBg:\"#1f1d2e\",canvasHintColor:\"#908caa\",canvasBorder:\"#524f67\",canvasGrid:\"#312e45\",rackFrameFill:\"#1f1d2e\",rackFrameStroke:\"#c4a7e7\",rackLineColor:\"#524f67\",rackTextColor:\"#9ccfd8\" },\n  sunburst: { panel:\"#fff7ed\",panelAlt:\"#ffedd5\",sidebarBg:\"#fffbf7\",btnBg:\"#ffedd5\",btnText:\"#9a3412\",accent:\"#f97316\",danger:\"#dc2626\",textMain:\"#431407\",textSoft:\"#9a3412\",topbarBg:\"#ea580c\",topbarBorder:\"#c2410c\",nodeFill:\"#fff7ed\",nodeStroke:\"#fb923c\",nodeTitle:\"#431407\",nodeSub:\"#9a3412\",defaultEdge:\"#fdba74\",canvasGradientTop:\"#fff7ed\",canvasGradientBottom:\"#fffbf7\",tagFill:\"#fed7aa\",tagText:\"#c2410c\",tagBorder:\"#f97316\",inputBg:\"#fffbf7\",inputText:\"#431407\",inputBorder:\"#fed7aa\",toolbarBg:\"#f97316\",toolbarBorder:\"#ea580c\",toolbarText:\"#ffffff\",toolbarBtnBg:\"#fb923c\",toolbarBtnText:\"#431407\",selectionHandle:\"#0891b2\",groupIndicator:\"#059669\",minimapDots:\"#f97316\",canvasHintBg:\"#fff7ed\",canvasHintColor:\"#9a3412\",canvasBorder:\"#fed7aa\",canvasGrid:\"#fdba74\",rackFrameFill:\"#fff7ed\",rackFrameStroke:\"#f97316\",rackLineColor:\"#fed7aa\",rackTextColor:\"#c2410c\" }\n};\ndocument.getElementById(\"theme-preset\").addEventListener(\"change\", function() {\n  updateDeleteButton();\n  var p = THEME_PRESETS[this.value];\n  if (!p && this.value.startsWith(\"mytheme-\")) {\n    var found = savedStyleSets.find(function(s) { return s.id === document.getElementById(\"theme-preset\").value; });\n    if (found) p = found.styles;\n  }\n  if (!p) return;\n  Object.assign(PAGE_STATE, p);\n  document.getElementById(\"panel-color\").value = p.panel;\n  document.getElementById(\"panel-alt-color\").value = p.panelAlt;\n  document.getElementById(\"sidebar-bg-color\").value = p.sidebarBg;\n  document.getElementById(\"btn-bg-color\").value = p.btnBg;\n  document.getElementById(\"btn-text-color\").value = p.btnText;\n  document.getElementById(\"accent-color\").value = p.accent;\n  document.getElementById(\"danger-color\").value = p.danger;\n  document.getElementById(\"text-main-color\").value = p.textMain;\n  document.getElementById(\"text-soft-color\").value = p.textSoft;\n  document.getElementById(\"topbar-border-color\").value = p.topbarBorder;\n  document.getElementById(\"node-fill-color\").value = p.nodeFill;\n  document.getElementById(\"node-stroke-color\").value = p.nodeStroke;\n  document.getElementById(\"node-title-color\").value = p.nodeTitle;\n  document.getElementById(\"node-sub-color\").value = p.nodeSub;\n  document.getElementById(\"default-edge-color\").value = p.defaultEdge;\n  document.getElementById(\"canvas-gradient-top\").value = p.canvasGradientTop;\n  document.getElementById(\"canvas-gradient-bottom\").value = p.canvasGradientBottom;\n  document.getElementById(\"tag-fill-color\").value = p.tagFill;\n  document.getElementById(\"tag-text-color\").value = p.tagText;\n  document.getElementById(\"tag-border-color\").value = p.tagBorder;\n  document.getElementById(\"input-bg-color\").value = p.inputBg;\n  document.getElementById(\"input-text-color\").value = p.inputText;\n  document.getElementById(\"input-border-color\").value = p.inputBorder;\n  document.getElementById(\"toolbar-bg-color\").value = p.toolbarBg;\n  document.getElementById(\"toolbar-border-color\").value = p.toolbarBorder;\n  document.getElementById(\"toolbar-text-color\").value = p.toolbarText;\n  document.getElementById(\"toolbar-btn-bg-color\").value = p.toolbarBtnBg;\n  document.getElementById(\"toolbar-btn-text-color\").value = p.toolbarBtnText;\n  document.getElementById(\"selection-handle-color\").value = p.selectionHandle;\n  document.getElementById(\"group-indicator-color\").value = p.groupIndicator;\n  document.getElementById(\"minimap-dots-color\").value = p.minimapDots;\n  document.getElementById(\"canvas-hint-bg-color\").value = p.canvasHintBg;\n  document.getElementById(\"canvas-hint-text-color\").value = p.canvasHintColor;\n  document.getElementById(\"canvas-border-color\").value = p.canvasBorder;\n  document.getElementById(\"canvas-grid-color\").value = p.canvasGrid;\n  document.getElementById(\"rack-frame-fill\").value = p.rackFrameFill;\n  document.getElementById(\"rack-frame-stroke\").value = p.rackFrameStroke;\n  document.getElementById(\"rack-line-color\").value = p.rackLineColor;\n  document.getElementById(\"rack-text-color\").value = p.rackTextColor;\n  wieldThePower();\n  forgeTheTopology();\n});\ndocument.querySelectorAll('#settings-modal .style-content input, #settings-modal .style-content select').forEach(el => {\n  if (el.id === 'theme-preset') return;\n  el.addEventListener('input', () => document.getElementById('theme-preset').value = '');\n  el.addEventListener('change', () => document.getElementById('theme-preset').value = '');\n});\n      document.getElementById(\"panel-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.panel = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"panel-alt-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.panelAlt = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"sidebar-bg-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.sidebarBg = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"btn-bg-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.btnBg = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"btn-text-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.btnText = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"tag-fill-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.tagFill = e.target.value;\n       wieldThePower();\n       forgeTheTopology();\n      });\n      document.getElementById(\"tag-text-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.tagText = e.target.value;\n       wieldThePower();\n       forgeTheTopology();\n      });\n      document.getElementById(\"tag-border-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.tagBorder = e.target.value;\n       wieldThePower();\n       forgeTheTopology();\n      });\n\t  document.getElementById(\"selection-fill-color\").addEventListener(\"input\", function() {\n       selectionBoxStyle.fillColor = this.value;\n      });\n      document.getElementById(\"selection-fill-opacity\").addEventListener(\"input\", function() {\n       selectionBoxStyle.fillOpacity = parseFloat(this.value);\n       document.getElementById(\"selection-fill-opacity-val\").textContent = Math.round(this.value * 100) + \"%\";\n      });\n      document.getElementById(\"selection-stroke-color\").addEventListener(\"input\", function() {\n       selectionBoxStyle.strokeColor = this.value;\n      });\n      document.getElementById(\"selection-stroke-width\").addEventListener(\"input\", function() {\n       selectionBoxStyle.strokeWidth = parseInt(this.value) || 2;\n      });\n      document.getElementById(\"selection-stroke-style\").addEventListener(\"change\", function() {\n       selectionBoxStyle.strokeDasharray = this.value;\n      });\n      document.getElementById(\"input-bg-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.inputBg = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"input-text-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.inputText = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"input-border-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.inputBorder = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"input-font-family\").addEventListener(\"change\", (e) => {\n       PAGE_STATE.inputFont = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"input-font-size\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.inputFontSize = parseInt(e.target.value) || 14;\n       wieldThePower();\n      });\n      document.getElementById(\"toolbar-bg-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.toolbarBg = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"toolbar-border-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.toolbarBorder = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"toolbar-text-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.toolbarText = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"toolbar-btn-bg-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.toolbarBtnBg = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"toolbar-btn-text-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.toolbarBtnText = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"minimap-dots-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.minimapDots = e.target.value;\n       wieldThePower();\n       forgeTheTopology();\n      });\n      document.getElementById(\"canvas-hint-enabled\").addEventListener(\"change\", (e) => {\n       PAGE_STATE.canvasHintEnabled = e.target.checked;\n       wieldThePower();\n      });\n      document.getElementById(\"canvas-hint-bg-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.canvasHintBg = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"canvas-hint-text-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.canvasHintColor = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"canvas-hint-text\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.canvasHintText = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"accent-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.accent = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"danger-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.danger = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"text-main-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.textMain = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"text-soft-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.textSoft = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"node-fill-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.nodeFill = e.target.value;\n       forgeTheTopology();\n      });\n      document.getElementById(\"node-stroke-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.nodeStroke = e.target.value;\n       forgeTheTopology();\n      });\n      document.getElementById(\"node-title-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.nodeTitle = e.target.value;\n       forgeTheTopology();\n      });\n      document.getElementById(\"node-sub-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.nodeSub = e.target.value;\n       forgeTheTopology();\n      });\n      document.getElementById(\"node-title-size\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.nodeTitleSize = parseInt(e.target.value) || 18;\n       forgeTheTopology();\n      });\n      document.getElementById(\"node-sub-size\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.nodeSubSize = parseInt(e.target.value) || 13;\n       forgeTheTopology();\n      });\n      document.getElementById(\"node-font-family\").addEventListener(\"change\", (e) => {\n       PAGE_STATE.nodeFont = e.target.value;\n       forgeTheTopology();\n      });\n\t  document.getElementById(\"show-port-labels-toggle\").addEventListener(\"change\", (e) => {\n       PAGE_STATE.showPortLabels = e.target.checked;\n       forgeTheTopology();\n      });\n\t  document.getElementById(\"default-edge-routing\").addEventListener(\"change\", (e) => {\n       PAGE_STATE.defaultEdgeRouting = e.target.value;\n       document.getElementById(\"add-line-routing\").value = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"apply-routing-all\").addEventListener(\"click\", async () => {\n       const routing = document.getElementById(\"default-edge-routing\").value;\n       if (!await showConfirm(t(\"dialogs.applyRoutingToAll\", { routing: routing, count: EDGE_DATA.list.length }))) return;\n       pushUndo(\"apply routing to all\");\n       EDGE_DATA.list.forEach(edge => {\n        edge.routing = routing;\n       });\n       forgeTheTopology();\n      });\n      document.getElementById(\"default-edge-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.defaultEdge = e.target.value;\n       document.getElementById(\"add-line-color\").value = e.target.value;\n       forgeTheTopology();\n      });\n      document.getElementById(\"selection-handle-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.selectionHandle = e.target.value;\n       forgeTheTopology();\n      });\n      document.getElementById(\"selection-handle-size\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.selectionHandleSize = parseInt(e.target.value) || 8;\n       forgeTheTopology();\n      });\n      document.getElementById(\"group-indicator-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.groupIndicator = e.target.value;\n       forgeTheTopology();\n      });\n      document.getElementById(\"canvas-gradient-top\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.canvasGradientTop = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"canvas-gradient-bottom\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.canvasGradientBottom = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"canvas-border-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.canvasBorder = e.target.value;\n       forgeTheTopology();\n      });\n      document.getElementById(\"canvas-grid-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.canvasGrid = e.target.value;\n       forgeTheTopology();\n      });\n      document.getElementById(\"canvas-grid-size\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.canvasGridSize = parseInt(e.target.value) || 50;\n       forgeTheTopology();\n      });\n\t  document.getElementById(\"canvas-grid-enabled\").addEventListener(\"change\", (e) => {\n PAGE_STATE.canvasGridEnabled = e.target.checked;\n forgeTheTopology();\n});\n      document.getElementById(\"rack-frame-fill\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.rackFrameFill = e.target.value;\n       forgeTheTopology();\n      });\n      document.getElementById(\"rack-frame-stroke\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.rackFrameStroke = e.target.value;\n       forgeTheTopology();\n      });\n      document.getElementById(\"rack-line-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.rackLineColor = e.target.value;\n       forgeTheTopology();\n      });\n\t  document.getElementById(\"rack-grid-enabled\").addEventListener(\"change\", (e) => {\n PAGE_STATE.rackGridEnabled = e.target.checked;\n forgeTheTopology();\n});\n      document.getElementById(\"rack-text-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.rackTextColor = e.target.value;\n       forgeTheTopology();\n      });\n      (function initializeResizers() {\n        const headerResizer = document.getElementById('header-resizer');\n        const sidebarResizer = document.getElementById('sidebar-resizer');\n        const mobileFooterResizer = document.getElementById('mobile-footer-resizer');\n        let isResizing = false;\n        let currentResizer = null;\n        let startY = 0;\n        let startX = 0;\n        let startHeight = 0;\n        let startWidth = 0;\n        function getClientPos(e) {\n          if (e.touches && e.touches.length > 0) {\n            return { x: e.touches[0].clientX, y: e.touches[0].clientY };\n          }\n          return { x: e.clientX, y: e.clientY };\n        }\n        function startResize(resizer, type, e) {\n          isResizing = true;\n          currentResizer = type;\n          const pos = getClientPos(e);\n          if (type === 'header') {\n            startY = pos.y;\n            startHeight = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--topbar-height'));\n          } else if (type === 'sidebar') {\n            startX = pos.x;\n            startWidth = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--sidebar-width'));\n          } else if (type === 'mobile-footer') {\n            startY = pos.y;\n            const currentVh = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--mobile-footer-height'));\n            startHeight = (currentVh / 100) * window.innerHeight;\n          }\n          resizer.classList.add('resizing');\n          document.body.classList.add('resizing');\n          document.body.style.cursor = (type === 'sidebar') ? 'col-resize' : 'row-resize';\n          e.preventDefault();\n        }\n        if (headerResizer) {\n          headerResizer.addEventListener('mousedown', (e) => startResize(headerResizer, 'header', e));\n          headerResizer.addEventListener('touchstart', (e) => startResize(headerResizer, 'header', e), { passive: false });\n        }\n        if (sidebarResizer) {\n          sidebarResizer.addEventListener('mousedown', (e) => startResize(sidebarResizer, 'sidebar', e));\n          sidebarResizer.addEventListener('touchstart', (e) => startResize(sidebarResizer, 'sidebar', e), { passive: false });\n        }\n        if (mobileFooterResizer) {\n          mobileFooterResizer.addEventListener('mousedown', (e) => startResize(mobileFooterResizer, 'mobile-footer', e));\n          mobileFooterResizer.addEventListener('touchstart', (e) => startResize(mobileFooterResizer, 'mobile-footer', e), { passive: false });\n        }\n        function handleMove(e) {\n          if (!isResizing) return;\n          const pos = getClientPos(e);\n          if (currentResizer === 'header') {\n            const deltaY = pos.y - startY;\n            const newHeight = Math.max(40, Math.min(150, startHeight + deltaY));\n            document.documentElement.style.setProperty('--topbar-height', newHeight + 'px');\n          } else if (currentResizer === 'sidebar') {\n            const deltaX = startX - pos.x;\n            const newWidth = Math.max(200, Math.min(800, startWidth + deltaX));\n            document.documentElement.style.setProperty('--sidebar-width', newWidth + 'px');\n          } else if (currentResizer === 'mobile-footer') {\n            const deltaY = startY - pos.y;\n            const newHeight = startHeight + deltaY;\n            const newVh = Math.max(15, Math.min(80, (newHeight / window.innerHeight) * 100));\n            document.documentElement.style.setProperty('--mobile-footer-height', newVh + 'vh');\n          }\n          e.preventDefault();\n        }\n        document.addEventListener('mousemove', handleMove);\n        document.addEventListener('touchmove', handleMove, { passive: false });\n        function handleEnd() {\n          if (isResizing) {\n            isResizing = false;\n            if (currentResizer === 'header') {\n              PAGE_STATE.topbarHeight = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--topbar-height'));\n              headerResizer.classList.remove('resizing');\n            } else if (currentResizer === 'sidebar') {\n              PAGE_STATE.sidebarWidth = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--sidebar-width'));\n              sidebarResizer.classList.remove('resizing');\n            } else if (currentResizer === 'mobile-footer') {\n              PAGE_STATE.mobileFooterHeight = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--mobile-footer-height'));\n              mobileFooterResizer.classList.remove('resizing');\n            }\n            document.body.classList.remove('resizing');\n            document.body.style.cursor = '';\n            currentResizer = null;\n          }\n        }\n        document.addEventListener('mouseup', handleEnd);\n        document.addEventListener('touchend', handleEnd);\n        document.addEventListener('touchcancel', handleEnd);\n      })();\n      document.getElementById(\"import-data-file\").addEventListener(\"change\", async (e) => {\n       const file = e.target.files[0];\n       if (!file) return;\n       try {\n        const text = await file.text();\n        const data = JSON.parse(text);\n        if (!data.nodeData || !data.edgeData) {\n         showAlert(t(\"dialogs.invalidDataFile\"));\n         return;\n        }\n        if (!await showConfirm(t(\"dialogs.confirmImportJson\", { nodeCount: Object.keys(data.nodeData).length, edgeCount: data.edgeData.list?.length || 0, tabCount: data.documentTabs?.length || 1 }))) {\n         e.target.value = \"\";\n         return;\n        }\n\t\tpushUndo('import json');\n        NODE_DATA = data.nodeData || {};\n        EDGE_DATA = data.edgeData || {\n         list: []\n        };\n        EDGE_LEGEND = data.edgeLegend || {};\n        RECT_DATA = data.rectData || { list: [] };\n        TEXT_DATA = data.textData || { list: [] };\n        IMAGE_DATA = data.imageData || { list: [] };\n        savedPositions = data.nodePositions || {};\n        savedSizes = data.nodeSizes || {};\n        savedStyles = data.nodeStyles || {};\n        if (data.page) {\n         PAGE_STATE = Object.assign({}, DEFAULT_PAGE_STATE, data.page);\n         wieldThePower();\n        }\n        if (data.canvas) {\n         canvasState.zoom = data.canvas.zoom || 1;\n         canvasState.panX = data.canvas.panX || 0;\n         canvasState.panY = data.canvas.panY || 0;\n        }\n        if (data.page?.title) {\n         document.title = data.page.title;\n         document.querySelector(\".editable-page-title\", ).textContent = data.page.title;\n        }\n        if (data.savedStyleSets) {\n          savedStyleSets = data.savedStyleSets;\n        }\n        if (data.documentTabs) {\n         documentTabs = data.documentTabs;\n         currentTabIndex = data.currentTabIndex || 0;\n        }\n        if (data.savedTopologyView) {\n         savedTopologyView = data.savedTopologyView;\n        }\n        if (data.encryptedSections) {\n         encryptedSections = data.encryptedSections;\n        }\n        if (data.auditLog && Array.isArray(data.auditLog)) {\n         auditLog = data.auditLog;\n         try {\n           localStorage.setItem(AUDIT_STORAGE_KEY, JSON.stringify(auditLog));\n         } catch (e) {\n           console.warn(\"Failed to sync audit log to localStorage:\", e);\n         }\n        }\n        if (data.customLanguage) {\n          CUSTOM_LANG = data.customLanguage;\n          LANG = deepMerge(DEFAULT_LANG, CUSTOM_LANG);\n          saveLanguageToStorage();\n          applyLanguage();\n          updateCurrentLangDisplay();\n        }\n        if (data.recordings && Array.isArray(data.recordings)) {\n          RECORDING_STATE.recordings = data.recordings;\n          RECORDING_STATE.currentRecording = data.recordings[0] || null;\n        }\n        const layerMap = { \"physical\": \"layer1\", \"logical\": \"layer2\", \"security\": \"layer3\", \"application\": \"layer4\" };\n        Object.values(NODE_DATA).forEach(node => {\n          if (!node.tags) node.tags = [];\n          if (!node.notes) node.notes = [];\n          if (!node.layer || layerMap[node.layer]) node.layer = layerMap[node.layer] || 'layer1';\n        });\n        EDGE_DATA.list.forEach(edge => {\n          if (!edge.notes) edge.notes = [];\n        });\n        forgeTheTopology();\n        if (typeof forgeTheLegend === 'function') forgeTheLegend();\n        const nodeCount = Object.keys(data.nodeData).length;\n        const edgeCount = data.edgeData.list?.length || 0;\n        const rectCount = data.rectData?.list?.length || 0;\n        const textCount = data.textData?.list?.length || 0;\n\t\tlogAuditEvent(\"import\", `Imported JSON: ${file.name} (${nodeCount} nodes, ${edgeCount} connections)`);\n        updateViewBox();\n        const nodeIds = Object.keys(NODE_DATA);\n        if (nodeIds.length > 0) {\n         claimTheImmortal(nodeIds[0]);\n        } else {\n         document.getElementById(\"node-panel\").style.display = \"none\";\n         document.getElementById(\"edge-panel\").style.display = \"none\";\n         document.getElementById(\"topology-toolbar\").style.display = \"none\";\n        }\n        updateDrawToolbarVisibility();\n        showAlert(t(\"dialogs.jsonImportSuccess\", { nodeCount, edgeCount, rectCount, textCount }));\n        e.target.value = \"\";\n       } catch (err) {\n        console.error(\"Import error:\", err);\n        showAlert(t(\"dialogs.importDataFailed\", { error: err.message }));\n        e.target.value = \"\";\n       }\n      });\n      document.getElementById(\"import-lang-file\").addEventListener(\"change\", (e) => {\n        const file = e.target.files[0];\n        if (!file) return;\n        importLanguageFile(file);\n        e.target.value = \"\";\n      });\n      const saveHelpBtn = document.getElementById(\"save-help-btn\");\n      const saveInfoModal = document.getElementById(\"save-info-modal\");\n      const saveInfoClose = document.getElementById(\"save-info-close\");\n      saveHelpBtn.addEventListener(\"click\", () => {\n       saveInfoModal.classList.add(\"active\");\n      });\n      saveInfoClose.addEventListener(\"click\", () => {\n       saveInfoModal.classList.remove(\"active\");\n      });\n      saveInfoModal.addEventListener(\"click\", (e) => {\n       if (e.target === saveInfoModal) {\n        saveInfoModal.classList.remove(\"active\");\n       }\n      });\n      document.querySelectorAll(\".help-tab\").forEach(tab => {\n        tab.addEventListener(\"click\", () => {\n          document.querySelectorAll(\".help-tab\").forEach(t => { t.style.background = \"var(--panel)\"; t.style.color = \"var(--text-main)\"; });\n          tab.style.background = \"var(--accent)\"; tab.style.color = \"var(--bg)\";\n          document.querySelectorAll(\".help-tab-content\").forEach(c => c.style.display = \"none\");\n          document.getElementById(\"help-tab-\" + tab.dataset.tab).style.display = \"block\";\n        });\n      });\n      async function deriveKey(password, salt) {\n      const encoder = new TextEncoder();\n      const keyMaterial = await crypto.subtle.importKey(\n      \"raw\",\n      encoder.encode(password),\n      \"PBKDF2\",\n      false,\n      [\"deriveKey\"]\n      );\n      return crypto.subtle.deriveKey(\n      {\n      name: \"PBKDF2\",\n      salt: salt,\n      iterations: 200000,\n      hash: \"SHA-256\"\n      },\n      keyMaterial,\n      {\n      name: \"AES-GCM\",\n      length: 256\n      },\n      false,\n      [\"encrypt\", \"decrypt\"]\n      );\n      }\n      async function encryptData(data, password) {\n      const encoder = new TextEncoder();\n      const salt = crypto.getRandomValues(new Uint8Array(16));\n      const iv   = crypto.getRandomValues(new Uint8Array(12));\n      const key = await deriveKey(password, salt);\n      const encrypted = await crypto.subtle.encrypt(\n      { name: \"AES-GCM\", iv },\n      key,\n      encoder.encode(data)\n      );\n      const encryptedU8 = new Uint8Array(encrypted);\n      const result = new Uint8Array(salt.length + iv.length + encryptedU8.length);\n      result.set(salt, 0);\n      result.set(iv, salt.length);\n      result.set(encryptedU8, salt.length + iv.length);\n      return \"ENCRYPTED:\" + u8ToBase64(result);\n      }\n      async function decryptData(encryptedData, password) {\n      const base64Data = encryptedData.replace(\"ENCRYPTED:\", \"\");\n      const fullData   = base64ToU8(base64Data);\n      const salt      = fullData.slice(0, 16);\n      const iv        = fullData.slice(16, 28);\n      const encrypted = fullData.slice(28);\n      const key = await deriveKey(password, salt);\n      const decrypted = await crypto.subtle.decrypt(\n      { name: \"AES-GCM\", iv },\n      key,\n      encrypted\n      );\n      const decoder = new TextDecoder();\n      return decoder.decode(decrypted);\n      }\n      function isEncrypted(data) {\n       return typeof data === \"string\" && data.startsWith(\"ENCRYPTED:\");\n      }\n      function captureTheQuickening() {\n       const currentTab = documentTabs[currentTabIndex];\n       currentTab.nodes = JSON.parse(JSON.stringify(NODE_DATA));\n       currentTab.edges = JSON.parse(JSON.stringify(EDGE_DATA));\n       currentTab.positions = JSON.parse(JSON.stringify(savedPositions));\n       currentTab.sizes = JSON.parse(JSON.stringify(savedSizes));\n       currentTab.styles = JSON.parse(JSON.stringify(savedStyles));\n       currentTab.legend = JSON.parse(JSON.stringify(EDGE_LEGEND));\n       currentTab.rects = JSON.parse(JSON.stringify(RECT_DATA));\n       currentTab.texts = JSON.parse(JSON.stringify(TEXT_DATA));\n       currentTab.pageState = JSON.parse(JSON.stringify(PAGE_STATE));\n       return {\n        nodeData: NODE_DATA,\n        edgeData: EDGE_DATA,\n        rectData: RECT_DATA,\n        textData: TEXT_DATA,\n        imageData: IMAGE_DATA,\n        edgeLegend: EDGE_LEGEND,\n        nodePositions: savedPositions,\n        nodeSizes: savedSizes,\n        nodeStyles: savedStyles,\n        page: PAGE_STATE,\n        canvas: {\n         zoom: canvasState.zoom,\n         panX: canvasState.panX,\n         panY: canvasState.panY,\n        },\n        savedTopologyView: savedTopologyView,\n        documentTabs: documentTabs,\n        currentTabIndex: currentTabIndex,\n        encryptedSections: encryptedSections,\n        auditLog: auditLog,\n\t\tsavedStyleSets: savedStyleSets,\n\t\trecordings: RECORDING_STATE.recordings,\n        customLanguage: CUSTOM_LANG || null,\n       };\n      }\n      function assembleTheImmortalForm() {\n      const canvasHintEl = document.getElementById(\"canvas-hint\");\n       const savedHintHTML = canvasHintEl ? canvasHintEl.innerHTML : \"\";\n       if (canvasHintEl) canvasHintEl.innerHTML = DEFAULT_CANVAS_HINT;\n       const clone = document.documentElement.cloneNode(true);\n       [\"edge-legend-mini\", \"minimap-mini\", \"draw-toolbar-mini\", \"topology-toolbar-mini\"].forEach(id => {\n        const all = clone.querySelectorAll(\"#\" + id);\n        for (let i = 1; i < all.length; i++) {\n         all[i].remove();\n        }\n       });\n       const addLineSelect = clone.querySelector(\"#add-line-select\");\n       if (addLineSelect) addLineSelect.innerHTML = \"\";\n       const minimapSvg = clone.querySelector(\"#minimap\");\n       if (minimapSvg) {\n        minimapSvg.querySelectorAll(\".minimap-node, .minimap-edge\").forEach(el => el.remove());\n       }\n       const canvasGrid = clone.querySelector(\"#canvas-grid\");\n       if (canvasGrid) canvasGrid.innerHTML = \"\";\n       const mainSvg = clone.querySelector(\"#map\");\n       if (mainSvg) mainSvg.innerHTML = \"\";\n       const nodeScript = clone.querySelector(\"#nodes-json\");\n       if (nodeScript) {\n        nodeScript.textContent = JSON.stringify(NODE_DATA, null, 2);\n       }\n       let stateScript = clone.querySelector(\"#topology-state\");\n       if (!stateScript) {\n        stateScript = document.createElement(\"script\");\n        stateScript.id = \"topology-state\";\n        stateScript.type = \"application/json\";\n        const body = clone.querySelector(\"body\") || clone;\n        body.appendChild(stateScript);\n       }\n       stateScript.textContent = JSON.stringify(captureTheQuickening(), null, 2);\n       return \"<!DOCTYPE html> \\n \" + clone.outerHTML;\n      }\n      async function becomeImmortal() {\n       if (RECORDING_STATE.isRecording) stopRecording();\n       if (RECORDING_STATE.isStepRecording) stopStepRecording();\n       saveRollbackVersion(\"Auto-save\");\n       const encryptEnabled = document.getElementById(\"encrypt-toggle\").checked;\n       let stateData = JSON.stringify(captureTheQuickening(), null, 2);\n       if (encryptEnabled) {\n        const password = await showPrompt(t(\"dialogs.enterEncryptPassword\"));\n        if (!password) {\n         showAlert(t(\"dialogs.encryptionCancelledNotSaved\"));\n         return;\n        }\n        const confirmPassword = await showPrompt(t(\"dialogs.confirmEncryptPassword\"));\n        if (password !== confirmPassword) {\n         showAlert(t(\"dialogs.passwordsMismatchNotSaved\"));\n         return;\n        }\n        try {\n         stateData = await encryptData(stateData, password);\n        } catch (e) {\n         showAlert(t(\"dialogs.encryptionFailedError\", { error: e.message }));\n         return;\n        }\n       }\n       const canvasHintEl = document.getElementById(\"canvas-hint\");\n       const savedHintHTML = canvasHintEl ? canvasHintEl.innerHTML : \"\";\n       if (canvasHintEl) canvasHintEl.innerHTML = DEFAULT_CANVAS_HINT;\n       const clone = document.documentElement.cloneNode(true);\n       [\"edge-legend-mini\", \"minimap-mini\", \"draw-toolbar-mini\", \"topology-toolbar-mini\"].forEach(id => {\n        const all = clone.querySelectorAll(\"#\" + id);\n        for (let i = 1; i < all.length; i++) {\n         all[i].remove();\n        }\n       });\n       const selectsToClear = [\"#add-line-select\", \"#new-edge-from\", \"#new-edge-to\", \"#assigned-rack-select\"];\n       selectsToClear.forEach(sel => {\n        const el = clone.querySelector(sel);\n        if (el) el.innerHTML = \"\";\n       });\n       const minimapSvg = clone.querySelector(\"#minimap\");\n       if (minimapSvg) minimapSvg.querySelectorAll(\".minimap-node, .minimap-edge\").forEach(el => el.remove());\n       const mainSvg = clone.querySelector(\"#map\");\n       if (mainSvg) mainSvg.innerHTML = \"\";\n       const edgeLegendContent = clone.querySelector(\"#edge-legend-content\");\n       if (edgeLegendContent) edgeLegendContent.innerHTML = \"\";\n       const nodeTags = clone.querySelector(\"#node-tags\");\n       if (nodeTags) nodeTags.innerHTML = \"\";\n       const nodeNotes = clone.querySelector(\"#node-notes\");\n       if (nodeNotes) nodeNotes.innerHTML = \"\";\n       const edgeNotes = clone.querySelector(\"#edge-notes\");\n       if (edgeNotes) edgeNotes.innerHTML = \"\";\n       const tabBar = clone.querySelector(\"#tab-bar\");\n       if (tabBar) tabBar.innerHTML = \"\";\n       const rollbackList = clone.querySelector(\"#rollback-list\");\n       if (rollbackList) rollbackList.innerHTML = \"\";\n       const auditList = clone.querySelector(\"#audit-log-list\");\n       if (auditList) auditList.innerHTML = \"\";\n       const nodeScript = clone.querySelector(\"#nodes-json\");\n       if (nodeScript) {\n        if (encryptEnabled) {\n         nodeScript.textContent = JSON.stringify({}, null, 2);\n        } else {\n         nodeScript.textContent = JSON.stringify(NODE_DATA, null, 2);\n        }\n       }\n       let stateScript = clone.querySelector(\"#topology-state\");\n       if (!stateScript) {\n        stateScript = document.createElement(\"script\");\n        stateScript.id = \"topology-state\";\n        stateScript.type = \"application/json\";\n        const body = clone.querySelector(\"body\") || clone;\n        body.appendChild(stateScript);\n       }\n       stateScript.textContent = stateData;\n       const html = \"<!DOCTYPE html> \\n \" + clone.outerHTML;\n       const blob = new Blob([html], {\n        type: \"text/html\"\n       });\n       const url = URL.createObjectURL(blob);\n       const a = document.createElement(\"a\");\n       a.href = url;\n       const safeTitle = (PAGE_STATE.title || document.title || \"network-topology\").toLowerCase().replace(/[^a-z0-9\\-]+/g, \"-\");\n       a.download = safeTitle + \".html\";\n       document.body.appendChild(a);\n       a.click();\n       document.body.removeChild(a);\n       URL.revokeObjectURL(url);\n       if (canvasHintEl) canvasHintEl.innerHTML = savedHintHTML;\n       logAuditEvent(\"save\", `File saved: ${safeTitle}.html`);\n      }\n      function captureState(scope = \"all\") {\n       const clone = typeof structuredClone === 'function' \n         ? (o) => structuredClone(o)\n         : (o) => JSON.parse(JSON.stringify(o));\n       \n       if (scope === \"all\") {\n        return {\n         scope: \"all\",\n         nodes: clone(NODE_DATA),\n         edges: clone(EDGE_DATA),\n         positions: clone(savedPositions),\n         sizes: clone(savedSizes),\n         styles: clone(savedStyles),\n         legend: clone(EDGE_LEGEND),\n         rects: clone(RECT_DATA),\n         texts: clone(TEXT_DATA)\n        };\n       }\n       \n       const state = { scope };\n       if (scope.includes(\"nodes\")) state.nodes = clone(NODE_DATA);\n       if (scope.includes(\"edges\")) state.edges = clone(EDGE_DATA);\n       if (scope.includes(\"positions\")) state.positions = clone(savedPositions);\n       if (scope.includes(\"sizes\")) state.sizes = clone(savedSizes);\n       if (scope.includes(\"styles\")) state.styles = clone(savedStyles);\n       if (scope.includes(\"legend\")) state.legend = clone(EDGE_LEGEND);\n       if (scope.includes(\"rects\")) state.rects = clone(RECT_DATA);\n       if (scope.includes(\"texts\")) state.texts = clone(TEXT_DATA);\n       return state;\n      }\n      let lastUndoPush = 0;\n\t  function pushUndo(action = \"\") {\n\t   const now = Date.now();\n\t   if (now - lastUndoPush < 100 && undoStack.length > 0) {\n\t     return;\n    \t }\n\t   lastUndoPush = now;\n       const actionScopes = {\n        \"move nodes\": \"positions\",\n        \"nudge\": \"positions\",\n        \"align nodes\": \"positions\",\n        \"distribute nodes\": \"positions\",\n        \"snap to grid\": \"positions\",\n        \"resize node\": \"sizes\",\n        \"reset size\": \"sizes\",\n        \"style change\": \"styles\",\n        \"edit edge\": \"edges\",\n        \"edit edge routing\": \"edges\",\n        \"edit edge point\": \"edges\",\n        \"add edge\": \"edges,positions\",\n        \"delete edge\": \"edges\",\n        \"add edge note\": \"edges\",\n        \"edit edge note\": \"edges\",\n        \"delete edge note\": \"edges\",\n        \"draw zone\": \"rects\",\n        \"delete zone\": \"rects\",\n        \"resize zone\": \"rects\",\n        \"edit zone\": \"rects\",\n        \"add zone note\": \"rects\",\n        \"delete zone note\": \"rects\",\n        \"change zone line style\": \"rects\",\n        \"add text\": \"texts\",\n        \"edit text\": \"texts\",\n        \"delete text\": \"texts\",\n       };\n       const scope = actionScopes[action] || \"all\";\n       const state = captureState(scope);\n\t   undoStack.push(state);\n        if (undoStack.length > MAX_UNDO_STACK) {\n        undoStack.shift();\n       }\n       redoStack = [];\n       updateUndoButtons();\n       if (action) {\n        const actionTypeMap = {\n  \"create node\": \"node\",\n  \"delete node\": \"node\",\n  \"add node\": \"node\",\n  \"edit\": \"node\",\n  \"clone node\": \"node\",\n  \"paste node\": \"node\",\n  \"move nodes\": \"node\",\n  \"nudge\": \"node\",\n  \"align nodes\": \"node\",\n  \"distribute nodes\": \"node\",\n  \"snap to grid\": \"node\",\n  \"toggle group\": \"node\",\n  \"toggle lock\": \"node\",\n  \"create rack\": \"rack\",\n  \"add rack\": \"rack\",\n  \"edit rack\": \"rack\",\n  \"edit mac\": \"rack\",\n  \"edit U height\": \"rack\",\n  \"change rack capacity\": \"rack\",\n  \"change assigned rack\": \"rack\",\n  \"add connection\": \"connection\",\n  \"delete connection\": \"connection\",\n  \"delete edge\": \"connection\",\n  \"clone edge\": \"connection\",\n  \"paste edge\": \"connection\",\n  \"style change\": \"style\",\n  \"change layer\": \"layer\",\n  \"add text\": \"text\",\n  \"edit text\": \"text\",\n  \"delete text\": \"text\",\n  \"clone text\": \"text\",\n  \"paste text\": \"text\",\n  \"draw zone\": \"zone\",\n  \"delete zone\": \"zone\",\n  \"delete rect\": \"zone\",\n  \"clone rect\": \"zone\",\n  \"paste rect\": \"zone\",\n  \"change zone line style\": \"zone\",\n  \"delete selected\": \"bulk\",\n  \"clone selected\": \"bulk\",\n};\n        const type = actionTypeMap[action] || \"node\";\n        logAuditEvent(type, action);\n       }\n      }\n      function undo() {\n       if (undoStack.length === 0) return;\n       const currentState = captureState();\n       redoStack.push(currentState);\n       const previousState = undoStack.pop();\n       restoreState(previousState);\n       updateUndoButtons();\n       logAuditEvent(\"undo\", \"Undo action performed\");\n      }\n      function redo() {\n       if (redoStack.length === 0) return;\n       logAuditEvent(\"redo\", \"Redo action performed\");\n       const currentState = captureState();\n       undoStack.push(currentState);\n       const nextState = redoStack.pop();\n       restoreState(nextState);\n       updateUndoButtons();\n      }\n      function restoreState(state) {\n      if (state.nodes) NODE_DATA = state.nodes;\n      if (state.edges) EDGE_DATA = state.edges;\n      if (state.positions) savedPositions = state.positions;\n      if (state.sizes) savedSizes = state.sizes;\n      if (state.styles) savedStyles = state.styles;\n      if (state.legend) EDGE_LEGEND = state.legend;\n      if (state.rects) RECT_DATA = state.rects;\n      if (state.texts) TEXT_DATA = state.texts;\n      forgeTheTopology();\n      if (currentNodeId && NODE_DATA[currentNodeId]) {\n       claimTheImmortal(currentNodeId);\n      } else if (currentEdgeId) {\n       selectTheConnection(currentEdgeId);\n       }\n      }\n      function updateUndoButtons() {\n       const undoBtn = document.getElementById(\"undo-btn\");\n       const redoBtn = document.getElementById(\"redo-btn\");\n       if (undoBtn) {\n        undoBtn.disabled = undoStack.length === 0;\n        undoBtn.style.opacity = undoStack.length === 0 ? \"0.5\" : \"1\";\n       }\n       if (redoBtn) {\n        redoBtn.disabled = redoStack.length === 0;\n        redoBtn.style.opacity = redoStack.length === 0 ? \"0.5\" : \"1\";\n       }\n      }\n      function editNodeMac(id) {\n       const currentMac = NODE_DATA[id].mac || \"\";\n       showEditModal(t(\"editModal.editMacAddress\"), currentMac, (newMac) => {\n        pushUndo(\"edit mac\");\n        NODE_DATA[id].mac = newMac;\n        if (currentNodeId === id) {\n         document.getElementById(\"node-mac\").textContent = newMac || \"--\";\n        }\n       });\n      }\n      function editNodeRack(id) {\n       const node = NODE_DATA[id];\n       if (!node) return;\n       document.getElementById(\"modal-title\").textContent = t(\"editModal.editRackUnit\");\n       document.getElementById(\"modal-input\").value = node.rackUnit || \"\";\n       document.getElementById(\"edit-modal\").style.display = \"\";\n       document.getElementById(\"edit-modal\").classList.add(\"active\");\n       document.getElementById(\"modal-input\").focus();\n\t   const modal = document.getElementById(\"edit-modal\");\n       const cleanup = () => {\n        modal.classList.remove(\"active\");\n        document.getElementById(\"modal-save\").removeEventListener(\"click\", saveHandler);\n        document.getElementById(\"modal-cancel\").removeEventListener(\"click\", cancelHandler);\n        modal.removeEventListener(\"click\", bgHandler);\n        document.removeEventListener(\"keydown\", escHandler);\n       };\n       const escHandler = (e) => {\n        if (e.key === \"Escape\") cleanup();\n       };\n       document.addEventListener(\"keydown\", escHandler);\n       const saveHandler = () => {\n        pushUndo(\"edit rack\");\n        const value = document.getElementById(\"modal-input\").value.trim();\n        node.rackUnit = value;\n        cleanup();\n        claimTheImmortal(id);\n       };\n       const cancelHandler = () => {\n        cleanup();\n       };\n       const bgHandler = (e) => {\n        if (e.target === modal) cancelHandler();\n       };\n       document.getElementById(\"modal-save\").addEventListener(\"click\", saveHandler);\n       document.getElementById(\"modal-cancel\").addEventListener(\"click\", cancelHandler);\n       modal.addEventListener(\"click\", bgHandler);\n      }\n      function editNodeUHeight(id) {\n       const node = NODE_DATA[id];\n       if (!node) return;\n       document.getElementById(\"modal-title\").textContent = t(\"editModal.editUHeight\");\n       document.getElementById(\"modal-input\").value = node.uHeight || \"1\";\n       document.getElementById(\"edit-modal\").style.display = \"\";\n       document.getElementById(\"edit-modal\").classList.add(\"active\");\n       document.getElementById(\"modal-input\").focus();\n\t   const modal = document.getElementById(\"edit-modal\");\n       const cleanup = () => {\n        modal.classList.remove(\"active\");\n        document.getElementById(\"modal-save\").removeEventListener(\"click\", saveHandler);\n        document.getElementById(\"modal-cancel\").removeEventListener(\"click\", cancelHandler);\n        modal.removeEventListener(\"click\", bgHandler);\n        document.removeEventListener(\"keydown\", escHandler);\n       };\n       const escHandler = (e) => {\n        if (e.key === \"Escape\") cleanup();\n       };\n       document.addEventListener(\"keydown\", escHandler);\n       const saveHandler = () => {\n        pushUndo(\"edit U height\");\n        const value = document.getElementById(\"modal-input\").value.trim();\n        node.uHeight = value;\n        cleanup();\n        claimTheImmortal(id);\n        forgeTheTopology();\n       };\n       const cancelHandler = () => {\n        cleanup();\n       };\n       const bgHandler = (e) => {\n        if (e.target === modal) cancelHandler();\n       };\n       document.getElementById(\"modal-save\").addEventListener(\"click\", saveHandler);\n       document.getElementById(\"modal-cancel\").addEventListener(\"click\", cancelHandler);\n       modal.addEventListener(\"click\", bgHandler);\n      }\n      function updateEdgePortLabels(edgeId) {\n       const edge = EDGE_DATA.list.find(e => e.id === edgeId);\n       if (!edge || edge.type === \"custom\") return;\n       const fromPortInput = document.getElementById(\"edge-from-port\");\n       const toPortInput = document.getElementById(\"edge-to-port\");\n       if (fromPortInput && toPortInput) {\n        edge.fromPort = fromPortInput.value || \"\";\n        edge.toPort = toPortInput.value || \"\";\n        forgeTheTopology();\n       }\n      }\n      function clearSelection() {\n       selectedNodes.clear();\n       selectedEdges.clear();\n       selectedRects.clear();\n       selectedTexts.clear();\n       updateAllSelections();\n      }\n      function updateAllSelections() {\n      updateNodeSelection();\n      document.querySelectorAll(\".edge\").forEach(el => {\n      const edgeId = el.dataset.edgeId;\n      if (selectedEdges.has(edgeId)) {\n      el.style.filter = \"drop-shadow(0 0 8px #4fd1c5)\";\n      } else {\n      el.style.filter = \"\";\n      }\n      });\n      document.querySelectorAll(\".rect-group\").forEach(el => {\n      const rectId = el.dataset.rectId;\n      if (selectedRects.has(rectId)) {\n      el.style.filter = \"drop-shadow(0 0 8px #4fd1c5)\";\n      } else {\n      el.style.filter = \"\";\n      }\n      });\n      document.querySelectorAll(\".text-group\").forEach(el => {\n      const textId = el.dataset.textId;\n      if (selectedTexts.has(textId)) {\n      el.style.filter = \"drop-shadow(0 0 8px #4fd1c5)\";\n      } else {\n      el.style.filter = \"\";\n      }\n      });\n      const total = selectedNodes.size + selectedEdges.size + selectedRects.size + selectedTexts.size;\n      const bulkToolbar = document.getElementById(\"bulk-toolbar\");\n      const bulkToolbarMobile = document.getElementById(\"bulk-toolbar-mobile\");\n      const bulkCount = document.getElementById(\"bulk-count\");\n      const bulkCountMobile = document.getElementById(\"bulk-count-mobile\");\n      if (total > 0) {\n      if (bulkToolbar) bulkToolbar.style.display = \"flex\";\n      if (bulkToolbarMobile) bulkToolbarMobile.style.display = \"flex\";\n      if (bulkCount) bulkCount.textContent = total;\n      if (bulkCountMobile) bulkCountMobile.textContent = total;\n      } else {\n      if (bulkToolbar) bulkToolbar.style.display = \"none\";\n      if (bulkToolbarMobile) bulkToolbarMobile.style.display = \"none\";\n      }\n      }\n      function updateNodeSelection() {\n       if (isViewOnly()) {\n        selectedNodes.clear();\n        selectedRects.clear();\n        selectedTexts.clear();\n        const bulkToolbar = document.getElementById(\"bulk-toolbar\");\n        const bulkToolbarMobile = document.getElementById(\"bulk-toolbar-mobile\");\n        if (bulkToolbar) bulkToolbar.style.display = \"none\";\n        if (bulkToolbarMobile) bulkToolbarMobile.style.display = \"none\";\n        return;\n       }\n       document.querySelectorAll(\".node-group\").forEach(node => {\n        const nodeId = node.dataset.nodeId;\n        if (selectedNodes.has(nodeId)) {\n         node.classList.add(\"selected\");\n        } else {\n         node.classList.remove(\"selected\");\n        }\n       });\n       const bulkToolbar = document.getElementById(\"bulk-toolbar\");\n       const bulkToolbarMobile = document.getElementById(\"bulk-toolbar-mobile\");\n       const bulkCount = document.getElementById(\"bulk-count\");\n       const bulkCountMobile = document.getElementById(\"bulk-count-mobile\");\n       const bulkCountModal = document.getElementById(\"bulk-count-modal\");\n       if (selectedNodes.size > 0) {\n        if (bulkToolbar) bulkToolbar.style.display = \"flex\";\n        if (bulkToolbarMobile) bulkToolbarMobile.style.display = \"flex\";\n        if (bulkCount) bulkCount.textContent = selectedNodes.size;\n        if (bulkCountMobile) bulkCountMobile.textContent = selectedNodes.size;\n        if (bulkCountModal) bulkCountModal.textContent = selectedNodes.size;\n       } else {\n        if (bulkToolbar) bulkToolbar.style.display = \"none\";\n        if (bulkToolbarMobile) bulkToolbarMobile.style.display = \"none\";\n        const modal = document.getElementById(\"bulk-actions-modal\");\n        if (modal) modal.style.display = \"none\";\n       }\n      }\n      function deleteSelected() {\n       const total = selectedNodes.size + selectedEdges.size + selectedRects.size + selectedTexts.size;\n       if (total === 0) return;\n       let nodesInsideRacks = [];\n       selectedNodes.forEach(nodeId => {\n        if (NODE_DATA[nodeId]?.isRack) {\n         Object.entries(NODE_DATA).forEach(([id, n]) => {\n          if (n.assignedRack === nodeId) nodesInsideRacks.push(n.name || id);\n         });\n        }\n       });\n       let message = `Delete ${total} selected item(s)?`;\n       if (nodesInsideRacks.length > 0) {\n        message += `\\n\\nThis will also delete ${nodesInsideRacks.length} node(s) inside rack(s):\\n• ${nodesInsideRacks.join('\\n• ')}`;\n       }\n       challengeTheImmortal(message, () => {\n        pushUndo(\"delete selected\");\n        selectedNodes.forEach(nodeId => {\n         if (NODE_DATA[nodeId]?.isRack) {\n          Object.keys(NODE_DATA).forEach(id => {\n           if (NODE_DATA[id]?.assignedRack === nodeId) {\n            EDGE_DATA.list = EDGE_DATA.list.filter(e => e.from !== id && e.to !== id);\n            delete NODE_DATA[id];\n            delete savedPositions[id];\n            delete savedSizes[id];\n            delete savedStyles[id];\n           }\n          });\n         }\n         delete NODE_DATA[nodeId];\n         delete savedPositions[nodeId];\n         delete savedSizes[nodeId];\n         delete savedStyles[nodeId];\n         EDGE_DATA.list = EDGE_DATA.list.filter(e => e.from !== nodeId && e.to !== nodeId);\n        });\n        selectedEdges.forEach(edgeId => {\n         EDGE_DATA.list = EDGE_DATA.list.filter(e => e.id !== edgeId);\n        });\n        selectedRects.forEach(rectId => {\n         RECT_DATA.list = RECT_DATA.list.filter(r => r.id !== rectId);\n        });\n        selectedTexts.forEach(textId => {\n         TEXT_DATA.list = TEXT_DATA.list.filter(t => t.id !== textId);\n        });\n        clearSelection();\n        forgeTheTopology();\n       });\n      }\n      function startSelection(event) {\n       if (event.button !== 0) return;\n       if (event.target.closest(\".node-group\")) return;\n       isSelecting = true;\n       const svg = document.getElementById(\"map\");\n       const pt = svg.createSVGPoint();\n       pt.x = event.clientX;\n       pt.y = event.clientY;\n       const svgP = pt.matrixTransform(svg.getScreenCTM().inverse());\n       selectionStart = { x: svgP.x, y: svgP.y };\n       clearSelection();\n       preDragSelectedNodes = new Set(selectedNodes);\n       preDragSelectedEdges = new Set(selectedEdges);\n       preDragSelectedRects = new Set(selectedRects);\n       preDragSelectedTexts = new Set(selectedTexts);\n       if (!selectionRect || !selectionRect.parentNode) {\n        selectionRect = document.createElementNS(\"http://www.w3.org/2000/svg\", \"rect\");\n        selectionRect.setAttribute(\"fill\", selectionBoxStyle.fillColor);\n        selectionRect.setAttribute(\"fill-opacity\", selectionBoxStyle.fillOpacity);\n        selectionRect.setAttribute(\"stroke\", selectionBoxStyle.strokeColor);\n        selectionRect.setAttribute(\"stroke-width\", selectionBoxStyle.strokeWidth);\n        selectionRect.setAttribute(\"stroke-dasharray\", selectionBoxStyle.strokeDasharray);\n        svg.appendChild(selectionRect);\n       }\n       selectionRect.style.display = \"block\";\n      }\n      function updateSelection(event) {\n       if (!isSelecting || !selectionStart) return;\n       const svg = document.getElementById(\"map\");\n       const pt = svg.createSVGPoint();\n       pt.x = event.clientX;\n       pt.y = event.clientY;\n       const svgP = pt.matrixTransform(svg.getScreenCTM().inverse());\n       const x = Math.min(selectionStart.x, svgP.x);\n       const y = Math.min(selectionStart.y, svgP.y);\n       const width = Math.abs(svgP.x - selectionStart.x);\n       const height = Math.abs(svgP.y - selectionStart.y);\n       selectionRect.setAttribute(\"x\", x);\n       selectionRect.setAttribute(\"y\", y);\n       selectionRect.setAttribute(\"width\", width);\n       selectionRect.setAttribute(\"height\", height);\n       const box = { x, y, width, height };\n       Object.entries(savedPositions).forEach(([nodeId, pos]) => {\n        const size = savedSizes[nodeId] || 50;\n        const halfSize = size / 2;\n        const nodeBox = { x: pos.x - halfSize, y: pos.y - halfSize, width: size, height: size };\n        if (boxesIntersect(box, nodeBox)) {\n         selectedNodes.add(nodeId);\n        } else if (!preDragSelectedNodes.has(nodeId)) {\n         selectedNodes.delete(nodeId);\n        }\n       });\n      EDGE_DATA.list.forEach(edge => {\n        if (!edge.points || edge.points.length === 0) return;\n        for (let i = 0; i < edge.points.length - 1; i++) {\n         const p1 = edge.points[i];\n         const p2 = edge.points[i + 1];\n         if (lineIntersectsBox(p1.x, p1.y, p2.x, p2.y, box)) {\n          selectedEdges.add(edge.id);\n          return;\n         }\n        }\n        if (!preDragSelectedEdges.has(edge.id)) {\n         selectedEdges.delete(edge.id);\n        }\n       });\n       RECT_DATA.list.forEach(rect => {\n        const rectBox = { x: rect.x, y: rect.y, width: rect.width, height: rect.height };\n        if (boxesIntersect(box, rectBox)) {\n         selectedRects.add(rect.id);\n        } else if (!preDragSelectedRects.has(rect.id)) {\n         selectedRects.delete(rect.id);\n        }\n       });\n       TEXT_DATA.list.forEach(text => {\n        const fontSize = text.fontSize || 18;\n        const textBox = { x: text.x - 50, y: text.y - fontSize, width: 100, height: fontSize * 1.5 };\n        if (boxesIntersect(box, textBox)) {\n         selectedTexts.add(text.id);\n        } else if (!preDragSelectedTexts.has(text.id)) {\n         selectedTexts.delete(text.id);\n        }\n       });\n       updateAllSelectionVisuals();\n      }\n      function endSelection() {\n       isSelecting = false;\n       if (selectionRect) {\n        selectionRect.style.display = \"none\";\n       }\n      }\n      function boxesIntersect(a, b) {\n       return !(a.x + a.width < b.x || b.x + b.width < a.x || a.y + a.height < b.y || b.y + b.height < a.y);\n      }\n      function lineIntersectsBox(x1, y1, x2, y2, box) {\n       if (pointInBox(x1, y1, box) || pointInBox(x2, y2, box)) return true;\n       const lines = [\n        [box.x, box.y, box.x + box.width, box.y],\n        [box.x + box.width, box.y, box.x + box.width, box.y + box.height],\n        [box.x + box.width, box.y + box.height, box.x, box.y + box.height],\n        [box.x, box.y + box.height, box.x, box.y]\n       ];\n       for (const [bx1, by1, bx2, by2] of lines) {\n        if (linesIntersect(x1, y1, x2, y2, bx1, by1, bx2, by2)) return true;\n       }\n       return false;\n      }\n      function pointInBox(x, y, box) {\n       return x >= box.x && x <= box.x + box.width && y >= box.y && y <= box.y + box.height;\n      }\n      function linesIntersect(x1, y1, x2, y2, x3, y3, x4, y4) {\n       const denom = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1);\n       if (Math.abs(denom) < 0.0001) return false;\n       const ua = ((x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)) / denom;\n       const ub = ((x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)) / denom;\n       return ua >= 0 && ua <= 1 && ub >= 0 && ub <= 1;\n      }\n      function updateAllSelectionVisuals() {\n       document.querySelectorAll(\".node-group\").forEach(node => {\n        const nodeId = node.dataset.nodeId;\n        if (selectedNodes.has(nodeId)) {\n         node.classList.add(\"selected\");\n        } else {\n         node.classList.remove(\"selected\");\n        }\n       });\n       document.querySelectorAll(\".edge\").forEach(edge => {\n        const edgeId = edge.dataset.edgeId;\n        if (selectedEdges.has(edgeId)) {\n         edge.style.filter = \"drop-shadow(0 0 6px #4fd1c5)\";\n        } else {\n         edge.style.filter = \"\";\n        }\n       });\n       document.querySelectorAll(\".rect-group rect\").forEach(el => {\n        const rectId = el.closest(\".rect-group\")?.dataset?.rectId;\n        if (selectedRects.has(rectId)) {\n         el.style.filter = \"drop-shadow(0 0 8px #4fd1c5)\";\n        } else {\n         el.style.filter = \"\";\n        }\n       });\n       document.querySelectorAll(\".text-group, .text-element\").forEach(el => {\n        const textId = el.dataset?.textId;\n        if (selectedTexts.has(textId)) {\n         el.style.filter = \"drop-shadow(0 0 8px #4fd1c5)\";\n        } else {\n         el.style.filter = \"\";\n        }\n       });\n       const total = selectedNodes.size + selectedEdges.size + selectedRects.size + selectedTexts.size;\n       const bulkToolbar = document.getElementById(\"bulk-toolbar\");\n       const bulkToolbarMobile = document.getElementById(\"bulk-toolbar-mobile\");\n       const bulkCount = document.getElementById(\"bulk-count\");\n       const bulkCountMobile = document.getElementById(\"bulk-count-mobile\");\n       if (total > 0) {\n        if (bulkToolbar) bulkToolbar.style.display = \"flex\";\n        if (bulkToolbarMobile) bulkToolbarMobile.style.display = \"flex\";\n        if (bulkCount) bulkCount.textContent = total;\n        if (bulkCountMobile) bulkCountMobile.textContent = total;\n       } else {\n        if (bulkToolbar) bulkToolbar.style.display = \"none\";\n        if (bulkToolbarMobile) bulkToolbarMobile.style.display = \"none\";\n       }\n      }\n      function cloneNode(sourceId, skipUndo) {\n       if (!NODE_DATA[sourceId]) return;\n       if (!skipUndo) pushUndo(\"clone node\");\n       const source = NODE_DATA[sourceId];\n       const baseName = source.name + \" copy\";\n       let newName = baseName;\n       let counter = 1;\n       while (Object.values(NODE_DATA).some(n => n.name === newName)) {\n        newName = `${baseName} ${counter}`;\n        counter++;\n       }\n       const baseId = newName.toLowerCase().replace(/[^a-z0-9]+/g, \"-\");\n       let newId = baseId;\n       counter = 1;\n       while (NODE_DATA[newId]) {\n        newId = `${baseId}-${counter}`;\n        counter++;\n       }\n\t\tNODE_DATA[newId] = {\n\t\t shape: source.shape,\n\t\t name: newName,\n\t\t ip: source.ip,\n\t\t role: source.role,\n\t\t tags: [...source.tags],\n\t\t notes: [...source.notes],\n\t\t mac: source.mac || \"\",\n\t\t rackUnit: source.rackUnit || \"\",\n\t\t uHeight: source.uHeight || \"1\",\n\t\t layer: source.layer || \"layer1\",\n\t\t assignedRack: source.assignedRack || \"\",\n\t\t rackCapacity: source.rackCapacity || \"42\",\n\t\t isRack: source.isRack || false,\nfovEnabled: source.fovEnabled || false,\n fovAngle: source.fovAngle || 90,\n fovDistance: source.fovDistance || 150,\n fovInnerRadius: source.fovInnerRadius || 0,\n fovRotation: source.fovRotation || 0,\n fovColor: source.fovColor || \"#f59e0b\",\n fovOpacity: source.fovOpacity || 20,\n fovGradient: source.fovGradient || false,\n fovBorderColor: source.fovBorderColor || \"#f59e0b\",\n fovBorderWidth: source.fovBorderWidth ?? 2,\n fovBorderStyle: source.fovBorderStyle || \"solid\",\n fovBorderOpacity: source.fovBorderOpacity ?? 100,\n fovLabel: source.fovLabel || \"\",\n fovLabelPosition: source.fovLabelPosition || \"center\",\n fovLabelSize: source.fovLabelSize || 14,\n fovLabelColor: source.fovLabelColor || \"#ffffff\",\n fovLabelBold: source.fovLabelBold || false,\n fovLabelBg: source.fovLabelBg || false,\n fovLabelBgColor: source.fovLabelBgColor || \"#000000\",\n fovLabelOffsetX: source.fovLabelOffsetX || 0,\n fovLabelOffsetY: source.fovLabelOffsetY || 0,\n fovAnimate: source.fovAnimate || false,\n fovAnimationType: source.fovAnimationType || \"sweep\",\n fovSweep: source.fovSweep || 120,\n fovSpeed: source.fovSpeed || 4\n\t\t};\n       if (source.isRack) {\n        const childNodes = Object.entries(NODE_DATA).filter(([id, n]) =>\n         id !== newId && n.assignedRack === sourceId\n        );\n        childNodes.forEach(([childId, childNode]) => {\n         const childBaseName = childNode.name + \" copy\";\n         let childNewName = childBaseName;\n         let c = 1;\n         while (Object.values(NODE_DATA).some(n => n.name === childNewName)) {\n          childNewName = `${childBaseName} ${c}`;\n          c++;\n         }\n         const childBaseId = childNewName.toLowerCase().replace(/[^a-z0-9]+/g, \"-\");\n         let childNewId = childBaseId;\n         c = 1;\n         while (NODE_DATA[childNewId]) {\n          childNewId = `${childBaseId}-${c}`;\n          c++;\n         }\n         NODE_DATA[childNewId] = {\n          ...JSON.parse(JSON.stringify(childNode)),\n          name: childNewName,\n          assignedRack: newId\n         };\n         if (savedPositions[childId]) {\n          savedPositions[childNewId] = { ...savedPositions[childId] };\n         }\n         if (savedSizes[childId]) {\n          savedSizes[childNewId] = savedSizes[childId];\n         }\n         if (savedStyles[childId]) {\n          savedStyles[childNewId] = JSON.parse(JSON.stringify(savedStyles[childId]));\n         }\n        });\n       }\n       if (currentView.mode === \"rack\" && currentView.rackId) {\n        NODE_DATA[newId].assignedRack = currentView.rackId;\n       }\n       const sourcePos = savedPositions[sourceId];\n       savedPositions[newId] = {\n        x: sourcePos.x + 100,\n        y: sourcePos.y + 100\n       };\n       if (savedSizes[sourceId]) {\n        savedSizes[newId] = savedSizes[sourceId];\n       }\n       if (savedStyles[sourceId]) {\n        savedStyles[newId] = JSON.parse(JSON.stringify(savedStyles[sourceId]));\n       }\n       forgeTheTopology();\n       claimTheImmortal(newId);\n      }\n      function alignSelectedNodes(direction) {\n       if (selectedNodes.size < 2) return;\n       pushUndo(\"align nodes\");\n       const nodes = Array.from(selectedNodes).map(id => ({\n        id,\n        pos: savedPositions[id]\n       }));\n       switch(direction) {\n        case \"left\":\n         const minX = Math.min(...nodes.map(n => n.pos.x));\n         nodes.forEach(n => savedPositions[n.id].x = minX);\n         break;\n        case \"right\":\n         const maxX = Math.max(...nodes.map(n => n.pos.x));\n         nodes.forEach(n => savedPositions[n.id].x = maxX);\n         break;\n        case \"top\":\n         const minY = Math.min(...nodes.map(n => n.pos.y));\n         nodes.forEach(n => savedPositions[n.id].y = minY);\n         break;\n        case \"bottom\":\n         const maxY = Math.max(...nodes.map(n => n.pos.y));\n         nodes.forEach(n => savedPositions[n.id].y = maxY);\n         break;\n        case \"center-h\":\n         const avgX = nodes.reduce((sum, n) => sum + n.pos.x, 0) / nodes.length;\n         nodes.forEach(n => savedPositions[n.id].x = avgX);\n         break;\n        case \"center-v\":\n         const avgY = nodes.reduce((sum, n) => sum + n.pos.y, 0) / nodes.length;\n         nodes.forEach(n => savedPositions[n.id].y = avgY);\n         break;\n       }\n       forgeTheTopology();\n      }\n      function distributeSelectedNodes(direction) {\n       if (selectedNodes.size < 3) return;\n       pushUndo(\"distribute nodes\");\n       const nodes = Array.from(selectedNodes).map(id => ({\n        id,\n        pos: savedPositions[id]\n       }));\n       if (direction === \"horizontal\") {\n        nodes.sort((a, b) => a.pos.x - b.pos.x);\n        const minX = nodes[0].pos.x;\n        const maxX = nodes[nodes.length - 1].pos.x;\n        const gap = (maxX - minX) / (nodes.length - 1);\n        nodes.forEach((n, i) => {\n         savedPositions[n.id].x = minX + (gap * i);\n        });\n       } else {\n        nodes.sort((a, b) => a.pos.y - b.pos.y);\n        const minY = nodes[0].pos.y;\n        const maxY = nodes[nodes.length - 1].pos.y;\n        const gap = (maxY - minY) / (nodes.length - 1);\n        nodes.forEach((n, i) => {\n         savedPositions[n.id].y = minY + (gap * i);\n        });\n       }\n       forgeTheTopology();\n      }\n      function snapToGrid(nodeId, gridSize = 50) {\n       if (!savedPositions[nodeId]) return;\n       pushUndo(\"snap to grid\");\n       const pos = savedPositions[nodeId];\n       pos.x = Math.round(pos.x / gridSize) * gridSize;\n       pos.y = Math.round(pos.y / gridSize) * gridSize;\n       forgeTheTopology();\n      }\n\t\tfunction searchNodes(query) {\n\t\t   if (!query) {\n\t\t\tcurrentSearchQuery = \"\";\n\t\t\tcurrentSearchResults = [];\n\t\t\tclearSearchHighlight();\n\t\t\treturn [];\n\t\t   }\n\t\t   currentSearchQuery = query;\n\t\t   query = query.toLowerCase();\n\t\t   const results = [];\n\t\t   Object.entries(NODE_DATA).forEach(([id, data]) => {\n\t\t\ttry {\n\t\t\t const nameMatch = data.name && data.name.toLowerCase().includes(query);\n\t\t\t const ipMatch = data.ip && data.ip.toLowerCase().includes(query);\n\t\t\t const roleMatch = data.role && data.role.toLowerCase().includes(query);\n\t\t\t const tagsMatch = data.tags && Array.isArray(data.tags) && data.tags.some(tag => tag && tag.toLowerCase().includes(query));\n\t\t\t const macMatch = data.mac && data.mac.toLowerCase().includes(query);\n\t\t\t const rackUnitMatch = data.rackUnit && String(data.rackUnit).toLowerCase().includes(query);\n\t\t\t if (nameMatch || ipMatch || roleMatch || tagsMatch || macMatch || rackUnitMatch) {\n\t\t\t  results.push(id);\n\t\t\t }\n\t\t\t} catch (e) {\n\t\t\t console.warn(\"Search error for node:\", id, e);\n\t\t\t}\n\t\t   });\n\t\t   currentSearchResults = results;\n\t\t   highlightSearchResults(results, true);\n\t\t   if (results.length > 0) {\n\t\t    focusOnNodes(results);\n\t\t   }\n\t\t   return results;\n\t\t}\n\t\tfunction highlightSearchResults(nodeIds, hasQuery = false) {\n\t\t   document.querySelectorAll(\".node-group\").forEach(node => {\n\t\t\tconst nodeId = node.dataset.nodeId;\n\t\t\tif (nodeIds.includes(nodeId)) {\n\t\t\t node.classList.add(\"search-highlight\");\n\t\t\t node.classList.remove(\"search-faded\");\n\t\t\t} else {\n\t\t\t node.classList.remove(\"search-highlight\");\n\t\t\t if (hasQuery) {\n\t\t\t  node.classList.add(\"search-faded\");\n\t\t\t } else {\n\t\t\t  node.classList.remove(\"search-faded\");\n\t\t\t }\n\t\t\t}\n\t\t   });\n\t\t   document.querySelectorAll(\".edge-group\").forEach(edge => {\n\t\t\tconst fromId = edge.dataset.from;\n\t\t\tconst toId = edge.dataset.to;\n\t\t\tif (hasQuery && !nodeIds.includes(fromId) && !nodeIds.includes(toId)) {\n\t\t\t edge.classList.add(\"search-faded\");\n\t\t\t} else {\n\t\t\t edge.classList.remove(\"search-faded\");\n\t\t\t}\n\t\t   });\n\t\t}\n\t\tfunction clearSearchHighlight() {\n   document.querySelectorAll(\".search-highlight\").forEach(node => {\n    node.classList.remove(\"search-highlight\");\n   });\n   document.querySelectorAll(\".search-faded\").forEach(el => {\n    el.classList.remove(\"search-faded\");\n   });\n}\n      function nudgeSelectedNodes(direction, distance) {\n        const nodesToNudge = selectedNodes.size > 0\n          ? Array.from(selectedNodes)\n          : (currentNodeId ? [currentNodeId] : []);\n        const rectsToNudge = selectedRects.size > 0\n          ? Array.from(selectedRects)\n          : (currentRectId ? [currentRectId] : []);\n        const textsToNudge = selectedTexts.size > 0\n          ? Array.from(selectedTexts)\n          : (currentTextId ? [currentTextId] : []);\n        if (nodesToNudge.length === 0 && rectsToNudge.length === 0 && textsToNudge.length === 0) return;\n        const unlockedNodes = nodesToNudge.filter(id => !NODE_DATA[id]?.locked);\n        const unlockedRects = rectsToNudge.filter(id => { const r = RECT_DATA.list.find(x => x.id === id); return r && !r.locked; });\n        const unlockedTexts = textsToNudge.filter(id => { const t = TEXT_DATA.list.find(x => x.id === id); return t && !t.locked; });\n        if (unlockedNodes.length === 0 && unlockedRects.length === 0 && unlockedTexts.length === 0) return;\n        pushUndo(\"nudge\");\n        const dx = direction === \"ArrowLeft\" ? -distance : direction === \"ArrowRight\" ? distance : 0;\n        const dy = direction === \"ArrowUp\" ? -distance : direction === \"ArrowDown\" ? distance : 0;\n        unlockedNodes.forEach(id => {\n          if (!savedPositions[id]) savedPositions[id] = { x: 0, y: 0 };\n          savedPositions[id].x += dx;\n          savedPositions[id].y += dy;\n        });\n        unlockedRects.forEach(id => {\n          const r = RECT_DATA.list.find(x => x.id === id);\n          if (r) { r.x += dx; r.y += dy; }\n        });\n        unlockedTexts.forEach(id => {\n          const t = TEXT_DATA.list.find(x => x.id === id);\n          if (t) { t.x += dx; t.y += dy; }\n        });\n        forgeTheTopology();\n      }\n      function cycleNodes(reverse = false) {\n        const nodeIds = Object.keys(NODE_DATA).filter(id => {\n          if (currentView.mode === \"rack\" && currentView.rackId) {\n            return NODE_DATA[id].assignedRack === currentView.rackId;\n          }\n          return !NODE_DATA[id].assignedRack;\n        });\n        if (nodeIds.length === 0) return;\n        let currentIndex = nodeIds.indexOf(currentNodeId);\n        if (reverse) {\n          currentIndex = currentIndex <= 0 ? nodeIds.length - 1 : currentIndex - 1;\n        } else {\n          currentIndex = currentIndex >= nodeIds.length - 1 ? 0 : currentIndex + 1;\n        }\n        const nextNodeId = nodeIds[currentIndex];\n        claimTheImmortal(nextNodeId);\n        selectedNodes.clear();\n        updateNodeSelection();\n      }\n      function focusOnSelected() {\n        let minX = Infinity, minY = Infinity;\n        let maxX = -Infinity, maxY = -Infinity;\n        let hasItems = false;\n        const nodesToFocus = selectedNodes.size > 0\n          ? Array.from(selectedNodes)\n          : (currentNodeId ? [currentNodeId] : []);\n        nodesToFocus.forEach(id => {\n          const pos = savedPositions[id];\n          if (pos) {\n            hasItems = true;\n            const size = savedSizes[id] || 50;\n            minX = Math.min(minX, pos.x - size/2);\n            minY = Math.min(minY, pos.y - size/2);\n            maxX = Math.max(maxX, pos.x + size/2);\n            maxY = Math.max(maxY, pos.y + size/2);\n          }\n        });\n        const rectsToFocus = selectedRects.size > 0 ? Array.from(selectedRects) : (currentRectId ? [currentRectId] : []);\n        rectsToFocus.forEach(id => {\n          const r = RECT_DATA.list.find(x => x.id === id);\n          if (r) {\n            hasItems = true;\n            minX = Math.min(minX, r.x);\n            minY = Math.min(minY, r.y);\n            maxX = Math.max(maxX, r.x + r.width);\n            maxY = Math.max(maxY, r.y + r.height);\n          }\n        });\n        const textsToFocus = selectedTexts.size > 0 ? Array.from(selectedTexts) : (currentTextId ? [currentTextId] : []);\n        textsToFocus.forEach(id => {\n          const t = TEXT_DATA.list.find(x => x.id === id);\n          if (t) {\n            hasItems = true;\n            minX = Math.min(minX, t.x - 50);\n            minY = Math.min(minY, t.y - 20);\n            maxX = Math.max(maxX, t.x + 50);\n            maxY = Math.max(maxY, t.y + 20);\n          }\n        });\n        const edgesToFocus = selectedEdges.size > 0 ? Array.from(selectedEdges) : (currentEdgeId ? [currentEdgeId] : []);\n        edgesToFocus.forEach(id => {\n          const e = EDGE_DATA.list.find(x => x.id === id);\n          if (e) {\n            const fromPos = savedPositions[e.from];\n            const toPos = savedPositions[e.to];\n            if (fromPos && toPos) {\n              hasItems = true;\n              minX = Math.min(minX, fromPos.x, toPos.x);\n              minY = Math.min(minY, fromPos.y, toPos.y);\n              maxX = Math.max(maxX, fromPos.x, toPos.x);\n              maxY = Math.max(maxY, fromPos.y, toPos.y);\n            }\n          }\n        });\n        if (!hasItems || !isFinite(minX)) return;\n        const padding = 100;\n        const centerX = (minX + maxX) / 2;\n        const centerY = (minY + maxY) / 2;\n        const width = maxX - minX + padding * 2;\n        const height = maxY - minY + padding * 2;\n        const zoomX = CANVAS_WIDTH / width;\n        const zoomY = CANVAS_HEIGHT / height;\n        const targetZoom = Math.min(zoomX, zoomY, 2);\n        canvasState.zoom = targetZoom;\n        canvasState.panX = centerX - (CANVAS_WIDTH / targetZoom) / 2;\n        canvasState.panY = centerY - (CANVAS_HEIGHT / targetZoom) / 2;\n\t\tupdateViewBox();\n      }\n      function toggleLockSelected() {\n        const nodesToToggle = selectedNodes.size > 0\n          ? Array.from(selectedNodes)\n          : (currentNodeId ? [currentNodeId] : []);\n        const rectsToToggle = selectedRects.size > 0\n          ? Array.from(selectedRects)\n          : (currentRectId ? [currentRectId] : []);\n        const textsToToggle = selectedTexts.size > 0\n          ? Array.from(selectedTexts)\n          : (currentTextId ? [currentTextId] : []);\n        if (nodesToToggle.length === 0 && rectsToToggle.length === 0 && textsToToggle.length === 0) return;\n        pushUndo(\"toggle lock\");\n        let hasUnlocked = nodesToToggle.some(id => !NODE_DATA[id]?.locked);\n        hasUnlocked = hasUnlocked || rectsToToggle.some(id => { const r = RECT_DATA.list.find(x => x.id === id); return r && !r.locked; });\n        hasUnlocked = hasUnlocked || textsToToggle.some(id => { const t = TEXT_DATA.list.find(x => x.id === id); return t && !t.locked; });\n        nodesToToggle.forEach(id => {\n          if (NODE_DATA[id]) NODE_DATA[id].locked = hasUnlocked;\n        });\n        rectsToToggle.forEach(id => {\n          const r = RECT_DATA.list.find(x => x.id === id);\n          if (r) r.locked = hasUnlocked;\n        });\n        textsToToggle.forEach(id => {\n          const t = TEXT_DATA.list.find(x => x.id === id);\n          if (t) t.locked = hasUnlocked;\n        });\n        forgeTheTopology();\n      }\n      function toggleGroupSelected() {\n      const nodesToGroup = Array.from(selectedNodes);\n      const rectsToGroup = Array.from(selectedRects);\n      const textsToGroup = Array.from(selectedTexts);\n      const edgesToGroup = Array.from(selectedEdges);\n      const totalItems = nodesToGroup.length + rectsToGroup.length + textsToGroup.length + edgesToGroup.length;\n      if (totalItems < 2) return;\n      pushUndo(\"toggle group\");\n      const allGroupIds = [];\n      nodesToGroup.forEach(id => { if (NODE_DATA[id]?.groupId) allGroupIds.push(NODE_DATA[id].groupId); });\n      rectsToGroup.forEach(id => { const r = RECT_DATA.list.find(x => x.id === id); if (r?.groupId) allGroupIds.push(r.groupId); });\n      textsToGroup.forEach(id => { const t = TEXT_DATA.list.find(x => x.id === id); if (t?.groupId) allGroupIds.push(t.groupId); });\n      edgesToGroup.forEach(id => { const e = EDGE_DATA.list.find(x => x.id === id); if (e?.groupId) allGroupIds.push(e.groupId); });\n      const uniqueGroups = [...new Set(allGroupIds)];\n      if (uniqueGroups.length === 1 && allGroupIds.length === totalItems) {\n      nodesToGroup.forEach(id => { if (NODE_DATA[id]) NODE_DATA[id].groupId = null; });\n      rectsToGroup.forEach(id => { const r = RECT_DATA.list.find(x => x.id === id); if (r) r.groupId = null; });\n      textsToGroup.forEach(id => { const t = TEXT_DATA.list.find(x => x.id === id); if (t) t.groupId = null; });\n      edgesToGroup.forEach(id => { const e = EDGE_DATA.list.find(x => x.id === id); if (e) e.groupId = null; });\n      } else {\n      const newGroupId = \"group-\" + Date.now();\n      nodesToGroup.forEach(id => { if (NODE_DATA[id]) NODE_DATA[id].groupId = newGroupId; });\n      rectsToGroup.forEach(id => { const r = RECT_DATA.list.find(x => x.id === id); if (r) r.groupId = newGroupId; });\n      textsToGroup.forEach(id => { const t = TEXT_DATA.list.find(x => x.id === id); if (t) t.groupId = newGroupId; });\n      edgesToGroup.forEach(id => { const e = EDGE_DATA.list.find(x => x.id === id); if (e) e.groupId = newGroupId; });\n      }\n      forgeTheTopology();\n      }\n      function handleKeyDown(event) {\n       if (event.metaKey && !event.ctrlKey) event.ctrlKey = true;\n       if (event.target.tagName === \"INPUT\" || event.target.tagName === \"TEXTAREA\" || event.target.isContentEditable) {\n        return;\n       }\n       if ([\"ArrowUp\", \"ArrowDown\", \"ArrowLeft\", \"ArrowRight\"].includes(event.key)) {\n        event.preventDefault();\n        const distance = event.shiftKey ? 10 : 1;\n        nudgeSelectedNodes(event.key, distance);\n       }\n       if (event.key === \"Tab\") {\n        event.preventDefault();\n        cycleNodes(event.shiftKey);\n       }\n       if (event.key === \"f\" || event.key === \"F\") {\n        event.preventDefault();\n        focusOnSelected();\n       }\n       if (event.key === \"l\" || event.key === \"L\") {\n        event.preventDefault();\n        toggleLockSelected();\n       }\n       if (event.key === \"g\" || event.key === \"G\") {\n        event.preventDefault();\n        toggleGroupSelected();\n       }\n       if (event.ctrlKey && event.key === \"z\") {\n        event.preventDefault();\n        undo();\n       }\n       if ((event.ctrlKey && event.key === \"y\") || (event.ctrlKey && event.shiftKey && event.key === \"z\")) {\n        event.preventDefault();\n        redo();\n       }\n if (event.ctrlKey && event.key === \"c\") {\n        event.preventDefault();\n        clipboard = null;\n        if (currentNodeId && NODE_DATA[currentNodeId]) {\n         clipboard = {\n          type: \"node\",\n          data: JSON.parse(JSON.stringify(NODE_DATA[currentNodeId])),\n          size: savedSizes[currentNodeId],\n          style: savedStyles[currentNodeId] ? JSON.parse(JSON.stringify(savedStyles[currentNodeId])) : null\n         };\n        } else if (currentEdgeId) {\n         const edge = EDGE_DATA.list.find(e => e.id === currentEdgeId);\n         if (edge) clipboard = { type: \"edge\", data: JSON.parse(JSON.stringify(edge)) };\n        } else if (currentRectId) {\n         const rect = RECT_DATA.list.find(r => r.id === currentRectId);\n         if (rect) clipboard = { type: \"rect\", data: JSON.parse(JSON.stringify(rect)) };\n        } else if (currentTextId) {\n         const text = TEXT_DATA.list.find(t => t.id === currentTextId);\n         if (text) clipboard = { type: \"text\", data: JSON.parse(JSON.stringify(text)) };\n        }\n       }\n       if (event.ctrlKey && event.key === \"v\") {\n        event.preventDefault();\n        if (!clipboard) return;\n        const viewWidth = CANVAS_WIDTH / canvasState.zoom;\n        const viewHeight = CANVAS_HEIGHT / canvasState.zoom;\n        const centerX = canvasState.panX + (viewWidth / 2);\n        const centerY = canvasState.panY + (viewHeight / 2);\n        if (clipboard.type === \"node\") {\n         const data = clipboard.data;\n         let newName = data.name + \" copy\";\n         let counter = 1;\n         while (Object.values(NODE_DATA).some(n => n.name === newName)) {\n          newName = data.name + \" copy \" + counter;\n          counter++;\n         }\n         let newId = newName.toLowerCase().replace(/[^a-z0-9]+/g, \"-\");\n         counter = 1;\n         while (NODE_DATA[newId]) {\n          newId = newName.toLowerCase().replace(/[^a-z0-9]+/g, \"-\") + \"-\" + counter;\n          counter++;\n         }\n         pushUndo(\"paste node\");\n         NODE_DATA[newId] = { ...data, name: newName };\n         savedPositions[newId] = { x: centerX, y: centerY };\n         if (clipboard.size) savedSizes[newId] = clipboard.size;\n         if (clipboard.style) savedStyles[newId] = JSON.parse(JSON.stringify(clipboard.style));\n         forgeTheTopology();\n         claimTheImmortal(newId);\n        } else if (clipboard.type === \"edge\") {\n         pushUndo(\"paste edge\");\n         const newEdge = { ...clipboard.data, id: \"edge-\" + Date.now() };\n         EDGE_DATA.list.push(newEdge);\n         forgeTheTopology();\n         selectTheConnection(newEdge.id);\n        } else if (clipboard.type === \"rect\") {\n         pushUndo(\"paste rect\");\n         const newRect = { ...clipboard.data, id: \"rect-\" + Date.now(), x: centerX - (clipboard.data.width || 100) / 2, y: centerY - (clipboard.data.height || 100) / 2 };\n         RECT_DATA.list.push(newRect);\n         forgeTheTopology();\n         selectTheRect(newRect.id);\n        } else if (clipboard.type === \"text\") {\n         pushUndo(\"paste text\");\n         const newText = { ...clipboard.data, id: \"text-\" + Date.now(), x: centerX, y: centerY };\n         TEXT_DATA.list.push(newText);\n         forgeTheTopology();\n         showTextPanel(newText.id);\n        }\n       }\n       if (event.key === \"Delete\") {\n        event.preventDefault();\n        const totalSelected = selectedNodes.size + selectedEdges.size + selectedRects.size + selectedTexts.size;\n        if (totalSelected > 0) {\n         deleteSelected();\n        } else if (currentNodeId && NODE_DATA[currentNodeId]) {\n         challengeTheImmortal(t(\"dialogs.deleteNodeConfirm\", { name: NODE_DATA[currentNodeId].name }), () => {\n          pushUndo(\"delete node\");\n          delete NODE_DATA[currentNodeId];\n          delete savedPositions[currentNodeId];\n          delete savedSizes[currentNodeId];\n          delete savedStyles[currentNodeId];\n          EDGE_DATA.list = EDGE_DATA.list.filter(e => e.from !== currentNodeId && e.to !== currentNodeId);\n          currentNodeId = null;\n          forgeTheTopology();\n          document.getElementById(\"node-panel\").style.display = \"none\";\n         });\n        } else if (currentEdgeId) {\n         challengeTheImmortal(t(\"dialogs.deleteLineConfirm\"), () => {\n          pushUndo(\"delete edge\");\n          EDGE_DATA.list = EDGE_DATA.list.filter(e => e.id !== currentEdgeId);\n          currentEdgeId = null;\n          forgeTheTopology();\n          document.getElementById(\"edge-panel\").style.display = \"none\";\n         });\n        } else if (currentRectId) {\n         challengeTheImmortal(\"Delete this zone?\", () => {\n          pushUndo(\"delete rect\");\n          RECT_DATA.list = RECT_DATA.list.filter(r => r.id !== currentRectId);\n          currentRectId = null;\n          forgeTheTopology();\n          document.getElementById(\"rect-panel\").style.display = \"none\";\n         });\n        } else if (currentTextId) {\n         const textItem = TEXT_DATA.list.find(t => t.id === currentTextId);\n         if (textItem) {\n          challengeTheImmortal(`Delete text \"${(textItem.content || '').substring(0, 30)}...\"?`, () => {\n           pushUndo(\"delete text\");\n           TEXT_DATA.list = TEXT_DATA.list.filter(t => t.id !== currentTextId);\n           currentTextId = null;\n           forgeTheTopology();\n           document.getElementById(\"text-panel\").style.display = \"none\";\n          });\n         }\n        } else if (currentImageId) {\n         const imgItem = IMAGE_DATA.list.find(i => i.id === currentImageId);\n         if (imgItem) {\n          challengeTheImmortal(\"Delete this image?\", () => {\n           deleteImage(currentImageId);\n          });\n         }\n        }\n       }\n       if (event.ctrlKey && event.key === \"a\") {\n        event.preventDefault();\n        Object.keys(NODE_DATA).forEach(id => selectedNodes.add(id));\n        EDGE_DATA.list.forEach(e => selectedEdges.add(e.id));\n        RECT_DATA.list.forEach(r => selectedRects.add(r.id));\n        TEXT_DATA.list.forEach(t => selectedTexts.add(t.id));\n        updateAllSelections();\n       }\n       if (event.key === \"Escape\") {\n        clearSelection();\n        clearSearchHighlight();\n        currentImageId = null;\n        document.getElementById(\"image-panel\").style.display = \"none\";\n       }\n       if (event.key === \"r\" && !event.ctrlKey && !event.shiftKey) {\n        event.preventDefault();\n        if (RECORDING_STATE.isRecording) {\n          stopRecording();\n        } else if (!RECORDING_STATE.isStepRecording) {\n          startRecording();\n        }\n       }\n       if (event.key === \"R\" && event.shiftKey && !event.ctrlKey) {\n        event.preventDefault();\n        if (RECORDING_STATE.isStepRecording) {\n          stopStepRecording();\n        } else if (!RECORDING_STATE.isRecording) {\n          startStepRecording();\n        }\n       }\n       if (event.key === \" \" && !event.ctrlKey) {\n        event.preventDefault();\n        if (RECORDING_STATE.isStepRecording) {\n          captureStepFrame();\n        } else if (RECORDING_STATE.isPlaying) {\n          pauseRecording();\n        } else if (RECORDING_STATE.isPaused) {\n          playRecording();\n        }\n       }\n       if ((event.key === \"p\" || event.key === \"P\") && !event.ctrlKey) {\n        event.preventDefault();\n        if (!RECORDING_STATE.isRecording && !RECORDING_STATE.isStepRecording) {\n          playRecording();\n        }\n       }\n       if (event.ctrlKey && event.key === \"d\") {\n        event.preventDefault();\n        if (currentNodeId && NODE_DATA[currentNodeId]) {\n         cloneNode(currentNodeId);\n        } else if (currentEdgeId) {\n         const edge = EDGE_DATA.list.find(e => e.id === currentEdgeId);\n         if (edge) {\n          pushUndo(\"clone edge\");\n          const newEdge = { ...JSON.parse(JSON.stringify(edge)), id: \"edge-\" + Date.now() };\n          EDGE_DATA.list.push(newEdge);\n          forgeTheTopology();\n          selectTheConnection(newEdge.id);\n         }\n        } else if (currentRectId) {\n         const rect = RECT_DATA.list.find(r => r.id === currentRectId);\n         if (rect) {\n          pushUndo(\"clone rect\");\n          const newRect = { ...JSON.parse(JSON.stringify(rect)), id: \"rect-\" + Date.now(), x: rect.x + 50, y: rect.y + 50 };\n          RECT_DATA.list.push(newRect);\n          forgeTheTopology();\n          selectTheRect(newRect.id);\n         }\n        } else if (currentTextId) {\n         const text = TEXT_DATA.list.find(t => t.id === currentTextId);\n         if (text) {\n          pushUndo(\"clone text\");\n          const newText = { ...JSON.parse(JSON.stringify(text)), id: \"text-\" + Date.now(), x: text.x + 50, y: text.y + 50 };\n          TEXT_DATA.list.push(newText);\n          forgeTheTopology();\n          showTextPanel(newText.id);\n         }\n        }\n       }\n      }\n      function saveRollbackVersion(description = \"Auto-save\") {\n        const version = {\n          timestamp: Date.now(),\n          description,\n          data: captureTheQuickening()\n        };\n        try {\n          const stored = localStorage.getItem(ROLLBACK_STORAGE_KEY);\n          rollbackVersions = stored ? JSON.parse(stored) : [];\n        } catch (e) {\n          rollbackVersions = [];\n        }\n        rollbackVersions.unshift(version);\n        if (rollbackVersions.length > MAX_ROLLBACK_VERSIONS) {\n          rollbackVersions = rollbackVersions.slice(0, MAX_ROLLBACK_VERSIONS);\n        }\n        try {\n          localStorage.setItem(ROLLBACK_STORAGE_KEY, JSON.stringify(rollbackVersions));\n        } catch (e) {\n          console.warn(\"Failed to save rollback version:\", e);\n        }\n      }\n      function loadRollbackVersions() {\n        try {\n          const stored = localStorage.getItem(ROLLBACK_STORAGE_KEY);\n          rollbackVersions = stored ? JSON.parse(stored) : [];\n          displayRollbackVersions();\n        } catch (e) {\n          console.warn(\"Failed to load rollback versions:\", e);\n          rollbackVersions = [];\n        }\n      }\n      function displayRollbackVersions() {\n        const listEl = document.getElementById(\"rollback-list\");\n        if (!listEl) return;\n        if (rollbackVersions.length === 0) {\n          listEl.innerHTML = '<div style=\"text-align: center; padding: 40px; color: var(--text-soft);\">' + t(\"emptyStates.noVersionHistory\") + '</div>';\n          return;\n        }\n        listEl.innerHTML = rollbackVersions.map((version, index) => {\n          const date = new Date(version.timestamp);\n          const timeStr = date.toLocaleString();\n          const nodeCount = Object.keys(version.data.nodeData || {}).length;\n          const edgeCount = (version.data.edgeData?.list || []).length;\n          return `\n            <div class=\"version-item\" onclick=\"restoreRollbackVersion(${index})\">\n              <div class=\"version-info\">\n                <div class=\"timestamp\">${escapeHtml(timeStr)}</div>\n                <div class=\"details\">${escapeHtml(version.description)} • ${t(\"ui.stats.nodesConnections\", { nodeCount, edgeCount })}</div>\n              </div>\n              <div class=\"version-actions\">\n                <button class=\"btn-cancel\" onclick=\"event.stopPropagation(); deleteRollbackVersion(${index})\" data-lang=\"tooltips.deleteVersion\" data-lang-attr=\"title\">🗑️</button>\n              </div>\n            </div>\n          `;\n        }).join('');\n      }\n      async function restoreRollbackVersion(index) {\n        if (!await showConfirm(t(\"dialogs.restoreVersion\", { date: new Date(rollbackVersions[index].timestamp).toLocaleString() }))) {\n          return;\n        }\n        const version = rollbackVersions[index];\n        const data = version.data;\n        NODE_DATA = data.nodeData || {};\n        EDGE_DATA = data.edgeData || { list: [] };\n        RECT_DATA = data.rectData || { list: [] };\n        TEXT_DATA = data.textData || { list: [] };\n        IMAGE_DATA = data.imageData || { list: [] };\n        EDGE_LEGEND = data.edgeLegend || {};\n        savedPositions = data.nodePositions || {};\n        savedSizes = data.nodeSizes || {};\n        savedStyles = data.nodeStyles || {};\n        PAGE_STATE = data.page || PAGE_STATE;\n        if (data.canvas) {\n          canvasState.zoom = data.canvas.zoom;\n          canvasState.panX = data.canvas.panX;\n          canvasState.panY = data.canvas.panY;\n        }\n        wieldThePower();\n        forgeTheTopology();\n        document.getElementById(\"rollback-modal\").classList.remove(\"active\");\n        logAuditEvent(\"rollback\", `Restored version from ${new Date(version.timestamp).toLocaleString()}`);\n      }\n      async function deleteRollbackVersion(index) {\n        if (!await showConfirm(t(\"dialogs.deleteVersionConfirm\"))) return;\n        rollbackVersions.splice(index, 1);\n        try {\n          localStorage.setItem(ROLLBACK_STORAGE_KEY, JSON.stringify(rollbackVersions));\n        } catch (e) {\n          console.warn(\"Failed to delete version:\", e);\n        }\n        displayRollbackVersions();\n      }\n      async function clearRollbackHistory() {\n        if (!await showConfirm(t(\"dialogs.clearVersionHistory\"))) return;\n        rollbackVersions = [];\n        localStorage.removeItem(ROLLBACK_STORAGE_KEY);\n        displayRollbackVersions();\n      }\n      async function createManualSnapshot() {\n        const description = await showPrompt(t(\"dialogs.enterSnapshotDescription\"), \"Manual snapshot\");\n        if (!description) return;\n        saveRollbackVersion(description);\n        displayRollbackVersions();\n      }\n      function switchTab(index) {\n        if (index === currentTabIndex) return;\n        const currentTab = documentTabs[currentTabIndex];\n        currentTab.nodes = JSON.parse(JSON.stringify(NODE_DATA));\n        currentTab.edges = JSON.parse(JSON.stringify(EDGE_DATA));\n        currentTab.positions = JSON.parse(JSON.stringify(savedPositions));\n        currentTab.sizes = JSON.parse(JSON.stringify(savedSizes));\n        currentTab.styles = JSON.parse(JSON.stringify(savedStyles));\n        currentTab.legend = JSON.parse(JSON.stringify(EDGE_LEGEND));\n        currentTab.rects = JSON.parse(JSON.stringify(RECT_DATA));\n        currentTab.texts = JSON.parse(JSON.stringify(TEXT_DATA));\n        currentTab.pageState = JSON.parse(JSON.stringify(PAGE_STATE));\n        currentTabIndex = index;\n        const newTab = documentTabs[index];\n        NODE_DATA = JSON.parse(JSON.stringify(newTab.nodes));\n        EDGE_DATA = JSON.parse(JSON.stringify(newTab.edges));\n        savedPositions = JSON.parse(JSON.stringify(newTab.positions));\n        savedSizes = JSON.parse(JSON.stringify(newTab.sizes));\n        savedStyles = JSON.parse(JSON.stringify(newTab.styles));\n        EDGE_LEGEND = JSON.parse(JSON.stringify(newTab.legend));\n        RECT_DATA = JSON.parse(JSON.stringify(newTab.rects));\n        TEXT_DATA = JSON.parse(JSON.stringify(newTab.texts));\n        PAGE_STATE = Object.assign({}, DEFAULT_PAGE_STATE, newTab.pageState || {});\n        wieldThePower();\n        document.title = PAGE_STATE.title || newTab.name;\n        document.getElementById(\"page-title\").textContent = PAGE_STATE.title || newTab.name;\n        forgeTheTopology();\n        currentNodeId = null;\n        currentEdgeId = null;\n        currentTextId = null;\n        currentRectId = null;\n        currentImageId = null;\n        document.getElementById(\"node-panel\").style.display = \"none\";\n        document.getElementById(\"edge-panel\").style.display = \"none\";\n        document.getElementById(\"text-panel\").style.display = \"none\";\n        document.getElementById(\"rect-panel\").style.display = \"none\";\n        document.getElementById(\"image-panel\").style.display = \"none\";\n        displayTabs();\n        updateDrawToolbarVisibility();\n        updateTopologyToolbarVisibility();\n        logAuditEvent(\"tab\", `Switched to tab: ${newTab.name}`);\n        const hasData = Object.keys(NODE_DATA).length > 0 || EDGE_DATA.list.length > 0;\n        const hasPageState = newTab.pageState !== null && newTab.pageState !== undefined;\n        if (!hasData && !hasPageState) {\n          if (typeof showWelcomeModal === 'function') showWelcomeModal();\n        }\n      }\n      function createNewTab() {\n        const nameInput = document.getElementById(\"new-tab-name\");\n        const name = nameInput.value.trim();\n        if (!name) {\n          showAlert(t(\"dialogs.enterTabName\"));\n          return;\n        }\n        const newTab = {\n          id: `tab-${Date.now()}`,\n          name,\n          nodes: {},\n          edges: { list: [] },\n          positions: {},\n          sizes: {},\n          styles: {},\n          legend: {},\n          rects: { list: [] },\n          texts: { list: [] },\n      pageState: null\n        };\n        documentTabs.push(newTab);\n        nameInput.value = \"\";\n        displayTabs();\n        logAuditEvent(\"tab\", `Created new tab: ${name}`);\n      }\n      async function renameTab(index) {\n        const tab = documentTabs[index];\n        const newName = await showPrompt(t(\"dialogs.enterNewTabName\"), tab.name);\n        if (!newName || newName === tab.name) return;\n        tab.name = newName;\n        displayTabs();\n        logAuditEvent(\"tab\", `Renamed tab to: ${newName}`);\n      }\n      async function deleteTab(index) {\n        if (documentTabs.length === 1) {\n          showAlert(t(\"dialogs.cannotDeleteLastTab\"));\n          return;\n        }\n        if (!await showConfirm(t(\"dialogs.deleteTab\", { name: documentTabs[index].name }))) return;\n        const wasCurrentTab = (index === currentTabIndex);\n        documentTabs.splice(index, 1);\n        if (currentTabIndex >= documentTabs.length) {\n          currentTabIndex = documentTabs.length - 1;\n        } else if (index < currentTabIndex) {\n          currentTabIndex--;\n        }\n        if (wasCurrentTab) {\n          const newTab = documentTabs[currentTabIndex];\n          NODE_DATA = JSON.parse(JSON.stringify(newTab.nodes));\n          EDGE_DATA = JSON.parse(JSON.stringify(newTab.edges));\n          savedPositions = JSON.parse(JSON.stringify(newTab.positions));\n          savedSizes = JSON.parse(JSON.stringify(newTab.sizes));\n          savedStyles = JSON.parse(JSON.stringify(newTab.styles));\n          EDGE_LEGEND = JSON.parse(JSON.stringify(newTab.legend));\n          RECT_DATA = JSON.parse(JSON.stringify(newTab.rects));\n          TEXT_DATA = JSON.parse(JSON.stringify(newTab.texts));\n          PAGE_STATE = Object.assign({}, DEFAULT_PAGE_STATE, newTab.pageState || {});\n          wieldThePower();\n          forgeTheTopology();\n          currentNodeId = null;\n          currentEdgeId = null;\n          currentTextId = null;\n          currentRectId = null;\n          currentImageId = null;\n          document.getElementById(\"node-panel\").style.display = \"none\";\n          document.getElementById(\"edge-panel\").style.display = \"none\";\n          document.getElementById(\"text-panel\").style.display = \"none\";\n          document.getElementById(\"rect-panel\").style.display = \"none\";\n          document.getElementById(\"image-panel\").style.display = \"none\";\n        }\n        displayTabs();\n        logAuditEvent(\"tab\", `Deleted tab`);\n      }\n      async function saveCurrentTheme() {\n        const name = await showPrompt(t(\"dialogs.saveThemeName\"), t(\"dialogs.defaultThemeName\", { number: savedStyleSets.length + 1 }));\n        if (!name || !name.trim()) return;\n        const existingIndex = savedStyleSets.findIndex(s => s.name.toLowerCase() === name.trim().toLowerCase());\n        if (existingIndex !== -1) {\n          if (!await showConfirm(t(\"dialogs.themeExists\", { name: name }))) return;\n          savedStyleSets.splice(existingIndex, 1);\n        }\n        const styleSet = {\n          id: \"mytheme-\" + Date.now(),\n          name: name.trim(),\n          styles: JSON.parse(JSON.stringify(PAGE_STATE))\n        };\n        delete styleSet.styles.title;\n        delete styleSet.styles.viewOnly;\n        savedStyleSets.push(styleSet);\n        rebuildThemeDropdown();\n        document.getElementById(\"theme-preset\").value = styleSet.id;\n        updateDeleteButton();\n        logAuditEvent(\"style\", \"Saved theme: \" + name);\n      }\n      async function deleteCurrentTheme() {\n        const select = document.getElementById(\"theme-preset\");\n        const val = select.value;\n        if (!val.startsWith(\"mytheme-\")) return;\n        const index = savedStyleSets.findIndex(s => s.id === val);\n        if (index === -1) return;\n        if (!await showConfirm(t(\"dialogs.deleteTheme\", { name: savedStyleSets[index].name }))) return;\n        savedStyleSets.splice(index, 1);\n        rebuildThemeDropdown();\n        select.value = \"defaulted\";\n        updateDeleteButton();\n        logAuditEvent(\"style\", \"Deleted theme\");\n      }\n      function rebuildThemeDropdown() {\n        const group = document.getElementById(\"my-themes-group\");\n        if (!group) return;\n        const select = document.getElementById(\"theme-preset\");\n        const currentValue = select ? select.value : \"\";\n        group.innerHTML = \"\";\n        savedStyleSets.forEach(function(s) {\n          const opt = document.createElement(\"option\");\n          opt.value = s.id;\n          opt.textContent = s.name;\n          group.appendChild(opt);\n        });\n        if (select && currentValue) {\n          select.value = currentValue;\n        }\n      }\n      function updateDeleteButton() {\n        const select = document.getElementById(\"theme-preset\");\n        const btn = document.getElementById(\"delete-theme-btn\");\n        if (!btn) return;\n\t\tbtn.style.display = \"block\";\n\t\tbtn.disabled = !select.value.startsWith(\"mytheme-\");\n      }\n      function displayTabs() {\n        const listEl = document.getElementById(\"tabs-list\");\n        if (!listEl) return;\n        listEl.innerHTML = documentTabs.map((tab, index) => {\n          const nodeCount = Object.keys(tab.nodes).length;\n          const edgeCount = tab.edges.list.length;\n          const isActive = index === currentTabIndex;\n          const tabMode = tab.pageState?.mappingMode || (isActive ? PAGE_STATE.mappingMode : 'network') || 'network';\n          const modeLabel = LANG.modes[tabMode]?.name || tabMode;\n          return `\n            <div class=\"tab-item ${isActive ? 'active' : ''}\" onclick=\"switchTab(${index})\">\n              <div class=\"tab-name\">${escapeHtml(tab.name)}</div>\n              <div class=\"tab-stats\">${t(\"ui.stats.nodesConnections\", { nodeCount, edgeCount })} · ${modeLabel}</div>\n              <div class=\"tab-actions\">\n                <button class=\"btn-cancel\" onclick=\"event.stopPropagation(); renameTab(${index})\" data-lang=\"tooltips.renameTab\" data-lang-attr=\"title\">✏️</button>\n                ${documentTabs.length > 1 ? '<button class=\"btn-cancel\" onclick=\"event.stopPropagation(); deleteTab(' + index + ')\" data-lang=\"tooltips.deleteTab\" data-lang-attr=\"title\">🗑️</button>' : ''}\n              </div>\n            </div>\n          `;\n        }).join('');\n      }\n      function logAuditEvent(type, description, details = {}) {\n        const event = {\n          timestamp: Date.now(),\n          type,\n          description,\n          details,\n          tab: documentTabs[currentTabIndex]?.name || \"Main\"\n        };\n        auditLog.unshift(event);\n        if (auditLog.length > MAX_AUDIT_ENTRIES) {\n          auditLog = auditLog.slice(0, MAX_AUDIT_ENTRIES);\n        }\n        try {\n          localStorage.setItem(AUDIT_STORAGE_KEY, JSON.stringify(auditLog));\n        } catch (e) {\n          console.warn(\"Failed to save audit log:\", e);\n        }\n      }\n      function loadAuditLog() {\n        let embeddedLog = [];\n        let localLog = [];\n        try {\n          const stateScript = document.getElementById(\"topology-state\");\n          if (stateScript) {\n            const stateData = JSON.parse(stateScript.textContent);\n            if (stateData.auditLog && Array.isArray(stateData.auditLog)) {\n              embeddedLog = stateData.auditLog;\n            }\n          }\n        } catch (e) {\n          console.warn(\"Failed to load embedded audit log:\", e);\n        }\n        try {\n          const stored = localStorage.getItem(AUDIT_STORAGE_KEY);\n          if (stored) {\n            localLog = JSON.parse(stored);\n          }\n        } catch (e) {\n          console.warn(\"Failed to load localStorage audit log:\", e);\n        }\n        const merged = [...embeddedLog, ...localLog];\n        const seen = new Set();\n        auditLog = merged.filter(entry => {\n          const key = entry.timestamp + entry.description;\n          if (seen.has(key)) return false;\n          seen.add(key);\n          return true;\n        }).sort((a, b) => b.timestamp - a.timestamp);\n        if (auditLog.length > MAX_AUDIT_ENTRIES) {\n          auditLog = auditLog.slice(0, MAX_AUDIT_ENTRIES);\n        }\n        try {\n          localStorage.setItem(AUDIT_STORAGE_KEY, JSON.stringify(auditLog));\n        } catch (e) {\n          console.warn(\"Failed to sync audit log:\", e);\n        }\n      }\n      function displayAuditLog(filter = \"all\") {\n        const listEl = document.getElementById(\"audit-log-list\");\n        if (!listEl) return;\n        const filtered = filter === \"all\" ? auditLog : auditLog.filter(e => e.type === filter);\n        if (filtered.length === 0) {\n          listEl.innerHTML = '<div style=\"text-align: center; padding: 40px; color: var(--text-soft);\">' + t(\"emptyStates.noAuditEntries\") + '</div>';\n          return;\n        }\n        listEl.innerHTML = filtered.map(event => {\n          const date = new Date(event.timestamp);\n          const timeStr = date.toLocaleString();\n          return `\n            <div class=\"audit-entry ${escapeHtml(event.type)}\">\n              <div class=\"time\">[${escapeHtml(timeStr)}] ${escapeHtml(event.tab)}</div>\n              <div class=\"action\">[${escapeHtml(event.type.toUpperCase())}] ${escapeHtml(event.description)}</div>\n            </div>\n          `;\n        }).join('');\n      }\n      async function clearAuditLog() {\n        if (!await showConfirm(t(\"dialogs.clearAuditLog\"))) return;\n        auditLog = [];\n        localStorage.removeItem(AUDIT_STORAGE_KEY);\n        displayAuditLog();\n      }\n      function exportAuditLog() {\n        if (auditLog.length === 0) {\n          showAlert(t(\"dialogs.noAuditEntriesToExport\"));\n          return;\n        }\n        const csv = [\n          [\"Timestamp\", \"Tab\", \"Type\", \"Description\"],\n          ...auditLog.map(e => [\n            new Date(e.timestamp).toISOString(),\n            e.tab,\n            e.type,\n            e.description\n          ])\n        ].map(row => row.map(cell => `\"${cell}\"`).join(\",\")).join(\"\\n\");\n        const blob = new Blob([csv], { type: \"text/csv\" });\n        const url = URL.createObjectURL(blob);\n        const a = document.createElement(\"a\");\n        a.href = url;\n        a.download = `audit-log-${Date.now()}.csv`;\n        document.body.appendChild(a);\n        a.click();\n        document.body.removeChild(a);\n        URL.revokeObjectURL(url);\n      }\n      let currentSecretName = null;\n      function createNewSecret() {\n        const nameInput = document.getElementById(\"new-secret-name\");\n        const name = nameInput.value.trim();\n        if (!name) {\n          showAlert(t(\"dialogs.enterNoteName\"));\n          return;\n        }\n        if (encryptedSections[name]) {\n          showAlert(t(\"dialogs.noteAlreadyExists\"));\n          return;\n        }\n        currentSecretName = name;\n        encryptedSections[name] = { encrypted: false, data: \"\" };\n        nameInput.value = \"\";\n        document.getElementById(\"secret-editor-title\").textContent = t(\"notes.newNoteTitle\", { name });\n        document.getElementById(\"secret-editor-content\").value = \"\";\n        document.getElementById(\"secrets-modal\").classList.remove(\"active\");\n        document.getElementById(\"secret-editor-modal\").classList.add(\"active\");\n        displaySecrets();\n      }\n      async function editSecret(name) {\n        currentSecretName = name;\n        const section = encryptedSections[name];\n        if (section.encrypted) {\n          const password = await showPrompt(t(\"dialogs.enterDecryptPasswordFor\", { name: name }));\n          if (!password) return;\n          try {\n            const decrypted = await decryptData(section.data, password);\n            document.getElementById(\"secret-editor-title\").textContent = t(\"notes.editNoteTitle\", { name });\n            document.getElementById(\"secret-editor-content\").innerHTML = decrypted;\n            document.getElementById(\"secrets-modal\").classList.remove(\"active\");\n            document.getElementById(\"secret-editor-modal\").classList.add(\"active\");\n          } catch (e) {\n            showAlert(t(\"dialogs.decryptionFailed\"));\n          }\n        } else {\n          document.getElementById(\"secret-editor-title\").textContent = t(\"notes.editNoteTitle\", { name });\n          document.getElementById(\"secret-editor-content\").innerHTML = section.data;\n          document.getElementById(\"secrets-modal\").classList.remove(\"active\");\n          document.getElementById(\"secret-editor-modal\").classList.add(\"active\");\n        }\n      }\n      async function saveSecret() {\n        if (!currentSecretName) return;\n        const content = document.getElementById(\"secret-editor-content\").innerHTML;\n        const autoEncrypt = document.getElementById(\"secret-auto-encrypt\").checked;\n        if (autoEncrypt) {\n          const password = await showPrompt(t(\"dialogs.enterEncryptPasswordFor\", { name: currentSecretName }));\n          if (!password) return;\n          const confirmPassword = await showPrompt(t(\"dialogs.confirmPasswordFor\"));\n          if (password !== confirmPassword) {\n            showAlert(t(\"dialogs.passwordsMismatch\"));\n            return;\n          }\n          try {\n            const encrypted = await encryptData(content, password);\n            encryptedSections[currentSecretName] = { encrypted: true, data: encrypted };\n          } catch (e) {\n            showAlert(t(\"dialogs.encryptionFailedError\", { error: e.message }));\n            return;\n          }\n        } else {\n          encryptedSections[currentSecretName] = { encrypted: false, data: content };\n        }\n        closeSecretEditor();\n        displaySecrets();\n        logAuditEvent(\"secret\", `Saved note: ${currentSecretName}`);\n      }\n      function closeSecretEditor() {\n        document.getElementById(\"secret-editor-modal\").classList.remove(\"active\");\n        document.getElementById(\"secrets-modal\").classList.add(\"active\");\n        currentSecretName = null;\n      }\n      async function deleteSecret(name) {\n        if (!await showConfirm(t(\"dialogs.deleteNote\", { name: name }))) return;\n        delete encryptedSections[name];\n        displaySecrets();\n        logAuditEvent(\"secret\", `Deleted note: ${name}`);\n      }\n      function getAllNotesAggregated() {\n        const allNotes = [];\n        Object.keys(encryptedSections).forEach(name => {\n          const section = encryptedSections[name];\n          allNotes.push({\n            type: \"global\",\n            name: name,\n            content: section.encrypted ? \"[Encrypted]\" : section.content,\n            encrypted: section.encrypted,\n            elementId: null,\n            elementName: null\n          });\n        });\n        Object.entries(NODE_DATA).forEach(([nodeId, node]) => {\n          (node.notes || []).forEach((note, idx) => {\n            const noteContent = typeof note === \"string\" ? note : (note.content || \"\");\n            allNotes.push({\n              type: \"node\",\n              name: `Note ${idx + 1}`,\n              content: noteContent,\n              encrypted: false,\n              elementId: nodeId,\n              elementName: node.name,\n              noteIndex: idx\n            });\n          });\n        });\n        (EDGE_DATA.list || []).forEach(edge => {\n          (edge.notes || []).forEach((note, idx) => {\n            const noteContent = typeof note === \"string\" ? note : (note.content || \"\");\n            const fromName = NODE_DATA[edge.from]?.name || edge.from;\n            const toName = NODE_DATA[edge.to]?.name || edge.to;\n            allNotes.push({\n              type: \"edge\",\n              name: `Note ${idx + 1}`,\n              content: noteContent,\n              encrypted: false,\n              elementId: edge.id,\n              elementName: `${fromName} ↔ ${toName}`,\n              noteIndex: idx\n            });\n          });\n        });\n        (RECT_DATA.list || []).forEach(rect => {\n          (rect.notes || []).forEach((note, idx) => {\n            const noteContent = typeof note === \"string\" ? note : (note.content || \"\");\n            allNotes.push({\n              type: \"rect\",\n              name: `Note ${idx + 1}`,\n              content: noteContent,\n              encrypted: false,\n              elementId: rect.id,\n              elementName: `Zone`,\n              noteIndex: idx\n            });\n          });\n        });\n        (IMAGE_DATA.list || []).forEach(img => {\n          (img.notes || []).forEach((note, idx) => {\n            const noteContent = typeof note === \"string\" ? note : (note.content || \"\");\n            allNotes.push({\n              type: \"image\",\n              name: `Note ${idx + 1}`,\n              content: noteContent,\n              encrypted: false,\n              elementId: img.id,\n              elementName: img.name || \"Image\",\n              noteIndex: idx\n            });\n          });\n        });\n        return allNotes;\n      }\n      function displaySecrets() {\n        const listEl = document.getElementById(\"secrets-list\");\n        if (!listEl) return;\n        const searchInput = document.getElementById(\"notes-hub-search\");\n        const filterSelect = document.getElementById(\"notes-hub-filter\");\n        const searchTerm = (searchInput?.value || \"\").toLowerCase();\n        const filterType = filterSelect?.value || \"all\";\n        let allNotes = getAllNotesAggregated();\n        if (filterType !== \"all\") {\n          allNotes = allNotes.filter(n => n.type === filterType);\n        }\n        if (searchTerm) {\n          allNotes = allNotes.filter(n =>\n            n.name.toLowerCase().includes(searchTerm) ||\n            n.content.toLowerCase().includes(searchTerm) ||\n            (n.elementName && n.elementName.toLowerCase().includes(searchTerm))\n          );\n        }\n        if (allNotes.length === 0) {\n          listEl.innerHTML = '<div style=\"text-align: center; padding: 40px; color: var(--text-soft);\">' + t(\"emptyStates.noNotesYet\") + '</div>';\n          return;\n        }\n        listEl.innerHTML = allNotes.map((note, i) => {\n          const typeLabels = { global: \"Global\", node: \"Node\", edge: \"Line\", rect: \"Zone\", image: \"Image\" };\n          const typeLabel = typeLabels[note.type] || note.type;\n          if (note.type === \"global\") {\n            const status = note.encrypted ? \"🔒\" : \"🔓\";\n            return `\n              <div class=\"secret-item note-type-${note.type}\">\n                <div class=\"secret-name\">${escapeHtml(note.name)}</div>\n                <div class=\"secret-status\">${status} ${typeLabel}</div>\n                <div class=\"tab-actions\">\n                  <button class=\"btn-cancel\" onclick=\"viewGlobalNote('${escapeHtml(note.name)}')\" title=\"View\">👁️</button>\n                  <button class=\"btn-cancel\" onclick=\"editSecret('${escapeHtml(note.name)}')\" title=\"Edit\">✏️</button>\n                  <button class=\"btn-cancel\" onclick=\"deleteSecret('${escapeHtml(note.name)}')\" title=\"Delete\">🗑️</button>\n                </div>\n              </div>\n            `;\n          } else {\n            const preview = note.content.replace(/<[^>]*>/g, '').substring(0, 60) + (note.content.length > 60 ? '...' : '');\n            return `\n              <div class=\"secret-item note-type-${note.type}\">\n                <div class=\"note-source\" onclick=\"navigateToNoteSource('${note.type}', '${escapeHtml(note.elementId)}')\">${typeLabel}: ${escapeHtml(note.elementName)}</div>\n                <div class=\"note-preview\">${escapeHtml(preview)}</div>\n                <div class=\"tab-actions\">\n                  <button class=\"btn-cancel\" onclick=\"viewElementNote('${note.type}', '${escapeHtml(note.elementId)}', ${note.noteIndex})\" title=\"View\">👁️</button>\n                  <button class=\"btn-cancel\" onclick=\"editElementNote('${note.type}', '${escapeHtml(note.elementId)}', ${note.noteIndex})\" title=\"Edit\">✏️</button>\n                </div>\n              </div>\n            `;\n          }\n        }).join('');\n      }\n      function navigateToNoteSource(type, elementId) {\n        document.getElementById(\"secrets-modal\").classList.remove(\"active\");\n        if (type === \"node\") {\n          claimTheImmortal(elementId);\n          focusOnSelected();\n        } else if (type === \"edge\") {\n          selectTheConnection(elementId);\n        } else if (type === \"rect\") {\n          selectTheRect(elementId);\n        } else if (type === \"image\") {\n          showImagePanel(elementId);\n        }\n      }\n      function viewElementNote(type, elementId, noteIndex) {\n        let notes = [];\n        if (type === \"node\" && NODE_DATA[elementId]) {\n          notes = NODE_DATA[elementId].notes || [];\n        } else if (type === \"edge\") {\n          const edge = EDGE_DATA.list.find(e => e.id === elementId);\n          if (edge) notes = edge.notes || [];\n        } else if (type === \"rect\") {\n          const rect = RECT_DATA.list.find(r => r.id === elementId);\n          if (rect) notes = rect.notes || [];\n        } else if (type === \"image\") {\n          const img = IMAGE_DATA.list.find(i => i.id === elementId);\n          if (img) notes = img.notes || [];\n        }\n        if (notes[noteIndex] !== undefined) {\n          const noteContent = typeof notes[noteIndex] === \"string\" ? notes[noteIndex] : (notes[noteIndex].content || \"\");\n          showNoteViewer(noteContent);\n        }\n      }\n      function viewGlobalNote(name) {\n        const section = encryptedSections[name];\n        if (!section) return;\n        if (section.encrypted) {\n          const pwd = prompt(t(\"notes.enterPassword\"));\n          if (!pwd) return;\n          try {\n            const decrypted = CryptoJS.AES.decrypt(section.data, pwd).toString(CryptoJS.enc.Utf8);\n            if (!decrypted) { alert(t(\"notes.wrongPassword\")); return; }\n            showNoteViewer(decrypted);\n          } catch { alert(t(\"notes.wrongPassword\")); }\n        } else {\n          showNoteViewer(section.data);\n        }\n      }\n      function editElementNote(type, elementId, noteIndex) {\n        let notes = [];\n        let updateFn = null;\n        if (type === \"node\" && NODE_DATA[elementId]) {\n          notes = NODE_DATA[elementId].notes || [];\n          updateFn = () => { if (currentNodeId === elementId) renderNotesFeed(notes, \"node-notes\", \"node\", elementId); };\n        } else if (type === \"edge\") {\n          const edge = EDGE_DATA.list.find(e => e.id === elementId);\n          if (edge) {\n            notes = edge.notes || [];\n            updateFn = () => { if (currentEdgeId === elementId) renderNotesFeed(notes, \"edge-notes\", \"edge\", elementId); };\n          }\n        } else if (type === \"rect\") {\n          const rect = RECT_DATA.list.find(r => r.id === elementId);\n          if (rect) {\n            notes = rect.notes || [];\n            updateFn = () => { if (currentRectId === elementId) renderNotesFeed(notes, \"rect-notes\", \"rect\", elementId); };\n          }\n        } else if (type === \"image\") {\n          const img = IMAGE_DATA.list.find(i => i.id === elementId);\n          if (img) {\n            notes = img.notes || [];\n            updateFn = () => { if (currentImageId === elementId) renderNotesFeed(notes, \"image-notes\", \"image\", elementId); };\n          }\n        }\n        if (notes[noteIndex] !== undefined) {\n          const noteContent = typeof notes[noteIndex] === \"string\" ? notes[noteIndex] : (notes[noteIndex].content || \"\");\n          showNoteEditor(t(\"noteEditor.editNote\"), noteContent, (newContent) => {\n            pushUndo(\"edit note\");\n            if (typeof notes[noteIndex] === \"string\") {\n              notes[noteIndex] = newContent;\n            } else {\n              notes[noteIndex].content = newContent;\n            }\n            displaySecrets();\n            if (updateFn) updateFn();\n          }, noteIndex);\n        }\n      }\n      let rafId = null;\n      let lastRender = 0;\n      const RENDER_THROTTLE = 16;\n      function setupDragToCreate() {\n        const addNodeBtn = document.getElementById(\"add-node-btn\");\n        const addRackBtn = document.getElementById(\"add-rack-btn\");\n        const canvas = document.getElementById(\"map\");\n        if (!addNodeBtn || !addRackBtn || !canvas) return;\n        let dragType = null;\n        [addNodeBtn, addRackBtn].forEach(btn => {\n          btn.setAttribute(\"draggable\", \"true\");\n          btn.addEventListener(\"dragstart\", e => {\n            dragType = btn.id === \"add-node-btn\" ? \"node\" : \"rack\";\n            e.dataTransfer.effectAllowed = \"copy\";\n            e.dataTransfer.setData(\"text/plain\", dragType);\n          });\n        });\n        canvas.addEventListener(\"dragover\", e => {\n          if (dragType) {\n            e.preventDefault();\n            e.dataTransfer.dropEffect = \"copy\";\n          }\n        });\n        canvas.addEventListener(\"drop\", e => {\n          if (!dragType) return;\n          e.preventDefault();\n          const rect = canvas.getBoundingClientRect();\n          const x = (e.clientX - rect.left) / canvasState.zoom + canvasState.panX;\n          const y = (e.clientY - rect.top) / canvasState.zoom + canvasState.panY;\n          if (dragType === \"node\") {\n            createNodeAtPosition(x, y);\n          } else if (dragType === \"rack\") {\n            createRackAtPosition(x, y);\n          }\n          dragType = null;\n        });\n      }\n      function createNodeAtPosition(x, y) {\n        const timestamp = Date.now();\n        const newId = `host-${timestamp}`;\n\t\tpushUndo(\"create node\");\n        NODE_DATA[newId] = {\n          name: t(\"ui.defaults.newNode\"),\n          ip: \"\",\n          shape: \"server\",\n          role: \"\",\n          tags: [],\n          notes: [],\n          layer: \"layer1\",\n          isRack: false\n        };\n        savedPositions[newId] = { x, y };\n        savedSizes[newId] = 55;\n        forgeTheTopology();\n        claimTheImmortal(newId);\n        logAuditEvent(\"node\", `Created node at (${Math.round(x)}, ${Math.round(y)})`);\n      }\n      function createRackAtPosition(x, y) {\n        const timestamp = Date.now();\n        const newId = `rack-${timestamp}`;\n\t\tpushUndo(\"create rack\");\n        NODE_DATA[newId] = {\n          name: t(\"ui.defaults.newRack\"),\n          ip: \"\",\n          shape: \"server\",\n          role: \"rack\",\n          tags: [],\n          notes: [],\n          layer: \"layer1\",\n          isRack: true,\n          rackCapacity: 42\n        };\n        savedPositions[newId] = { x, y };\n        savedSizes[newId] = 55;\n        populateRackDropdown();\n        forgeTheTopology();\n        claimTheImmortal(newId);\n        logAuditEvent(\"rack\", `Created rack at (${Math.round(x)}, ${Math.round(y)})`);\n      }\n      document.addEventListener(\"keydown\", handleKeyDown);\n      document.getElementById(\"save-file-btn\").addEventListener(\"click\", becomeImmortal);\n      const addNodeBtn = document.getElementById(\"add-node-btn\");\n      const addNodeModal = document.getElementById(\"add-node-modal\");\n      const addNodeCancel = document.getElementById(\"add-node-cancel\");\n      const addNodeSave = document.getElementById(\"add-node-save\");\n      const undoBtn = document.getElementById(\"undo-btn\");\n      const redoBtn = document.getElementById(\"redo-btn\");\n      if (undoBtn) undoBtn.addEventListener(\"click\", undo);\n      if (redoBtn) redoBtn.addEventListener(\"click\", redo);\n      const backToTopologyBtn = document.getElementById(\"back-to-topology-btn\");\n      if (backToTopologyBtn) {\n       backToTopologyBtn.addEventListener(\"click\", exitRack);\n      }\n      const canvasViewport = document.getElementById(\"canvas-viewport\");\n      if (canvasViewport) {\n       canvasViewport.addEventListener(\"dblclick\", (e) => {\n        if (currentView.mode === \"rack\" && e.target.id === \"map\") {\n         exitRack();\n        }\n       });\n      }\n      const layersBtn = document.getElementById(\"layers-btn\");\n      const layerModal = document.getElementById(\"layer-modal\");\n      const layerModalClose = document.getElementById(\"layer-modal-close\");\n      if (layersBtn && layerModal) {\n       layersBtn.addEventListener(\"click\", () => {\n        layerModal.classList.add(\"active\");\n       });\n      }\n      if (layerModalClose && layerModal) {\n       layerModalClose.addEventListener(\"click\", () => {\n        layerModal.classList.remove(\"active\");\n       });\n      }\n      if (layerModal) {\n       layerModal.addEventListener(\"click\", (e) => {\n        if (e.target === layerModal) {\n         layerModal.classList.remove(\"active\");\n        }\n       });\n      }\n      [\"1\", \"2\", \"3\", \"4\"].forEach(num => {\n       const checkbox = document.getElementById(`layer-${num}`);\n       if (checkbox) {\n        checkbox.addEventListener(\"change\", applyLayerFilter);\n       }\n      });\n      const tabsBtn = document.getElementById(\"tabs-btn\");\n      const tabsModal = document.getElementById(\"tabs-modal\");\n      const tabsModalClose = document.getElementById(\"tabs-modal-close\");\n      if (tabsBtn && tabsModal) {\n        tabsBtn.addEventListener(\"click\", () => {\n          displayTabs();\n          tabsModal.classList.add(\"active\");\n        });\n      }\n      if (tabsModalClose && tabsModal) {\n        tabsModalClose.addEventListener(\"click\", () => {\n          tabsModal.classList.remove(\"active\");\n        });\n      }\n      if (tabsModal) {\n        tabsModal.addEventListener(\"click\", (e) => {\n          if (e.target === tabsModal) {\n            tabsModal.classList.remove(\"active\");\n          }\n        });\n      }\n      const rollbackBtn = document.getElementById(\"rollback-btn\");\n      const rollbackModal = document.getElementById(\"rollback-modal\");\n      const rollbackModalClose = document.getElementById(\"rollback-modal-close\");\n      if (rollbackBtn && rollbackModal) {\n        rollbackBtn.addEventListener(\"click\", () => {\n          loadRollbackVersions();\n          rollbackModal.classList.add(\"active\");\n        });\n      }\n      if (rollbackModalClose && rollbackModal) {\n        rollbackModalClose.addEventListener(\"click\", () => {\n          rollbackModal.classList.remove(\"active\");\n        });\n      }\n      if (rollbackModal) {\n        rollbackModal.addEventListener(\"click\", (e) => {\n          if (e.target === rollbackModal) {\n            rollbackModal.classList.remove(\"active\");\n          }\n        });\n      }\n      const auditLogBtn = document.getElementById(\"audit-log-btn\");\n      const auditLogModal = document.getElementById(\"audit-log-modal\");\n      const auditLogModalClose = document.getElementById(\"audit-log-modal-close\");\n      const auditFilter = document.getElementById(\"audit-filter\");\n      if (auditLogBtn && auditLogModal) {\n        auditLogBtn.addEventListener(\"click\", () => {\n          loadAuditLog();\n          displayAuditLog();\n          auditLogModal.classList.add(\"active\");\n        });\n      }\n      if (auditFilter) {\n        auditFilter.addEventListener(\"change\", (e) => {\n          displayAuditLog(e.target.value);\n        });\n      }\n      if (auditLogModalClose && auditLogModal) {\n        auditLogModalClose.addEventListener(\"click\", () => {\n          auditLogModal.classList.remove(\"active\");\n        });\n      }\n\t  const portMapBtn = document.getElementById(\"port-map-btn\");\n      const portMapModal = document.getElementById(\"port-map-modal\");\n      const portMapClose = document.getElementById(\"port-map-modal-close\");\n      const portMapSearch = document.getElementById(\"port-map-search\");\n      const portMapFilter = document.getElementById(\"port-map-filter\");\n      function renderPortMap() {\n        const container = document.getElementById(\"port-map-table\");\n        const search = (document.getElementById(\"port-map-search\")?.value || \"\").toLowerCase();\n        const filter = document.getElementById(\"port-map-filter\")?.value || \"all\";\n        let edges = EDGE_DATA.list || [];\n        if (filter === \"with-ports\") edges = edges.filter(e => e.fromPort || e.toPort);\n        if (filter === \"without-ports\") edges = edges.filter(e => !e.fromPort && !e.toPort);\n        if (search) {\n          edges = edges.filter(e => {\n            const fromName = (NODE_DATA[e.from]?.name || e.from || \"\").toLowerCase();\n            const toName = (NODE_DATA[e.to]?.name || e.to || \"\").toLowerCase();\n            return fromName.includes(search) || toName.includes(search) || (e.fromPort || \"\").toLowerCase().includes(search) || (e.toPort || \"\").toLowerCase().includes(search) || (e.notes || []).join(\" \").toLowerCase().includes(search);\n          });\n        }\n        if (edges.length === 0) {\n          container.innerHTML = '<div style=\"text-align: center; padding: 40px; color: var(--text-soft);\">' + t(\"emptyStates.noConnectionsFound\") + '</div>';\n          return;\n        }\n        let html = '<table style=\"width: 100%; border-collapse: collapse; font-size: 13px;\">';\n        html += '<thead><tr style=\"background: var(--panel-alt); position: sticky; top: 0;\"><th style=\"padding: 10px; text-align: left; border-bottom: 1px solid var(--edge-main);\">From Device</th><th style=\"padding: 10px; text-align: left; border-bottom: 1px solid var(--edge-main);\">From Port</th><th style=\"padding: 10px; text-align: left; border-bottom: 1px solid var(--edge-main);\">To Device</th><th style=\"padding: 10px; text-align: left; border-bottom: 1px solid var(--edge-main);\">To Port</th><th style=\"padding: 10px; text-align: left; border-bottom: 1px solid var(--edge-main);\">Notes</th></tr></thead><tbody>';\n        edges.forEach(e => {\n          const fromName = NODE_DATA[e.from]?.name || e.from;\n          const toName = NODE_DATA[e.to]?.name || e.to;\n          const notes = (e.notes || []).join(\", \");\n          const edgeColor = e.color || EDGE_LEGEND[e.type]?.color || 'var(--edge-main)';\n          const fromPortDisplay = e.fromPort ? escapeHtml(e.fromPort) : \"-\";\n          const toPortDisplay = e.toPort ? escapeHtml(e.toPort) : \"-\";\n          const goToEdge = `document.getElementById('port-map-modal').classList.remove('active'); selectTheConnection('${escapeHtml(e.id)}'); focusOnSelected()`;\n          html += `<tr style=\"border-bottom: 1px solid var(--edge-main);\"><td style=\"padding: 8px;\"><span style=\"display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: ${edgeColor}; margin-right: 6px;\"></span><button onclick=\"document.getElementById('port-map-modal').classList.remove('active'); claimTheImmortal('${escapeHtml(e.from)}'); focusOnSelected()\" style=\"background: var(--panel-alt); color: var(--text-main); border: 1px solid var(--edge-main); border-radius: 4px; padding: 6px 10px; cursor: pointer; font-size: 13px; min-height: 36px;\">${escapeHtml(fromName)}</button></td><td style=\"padding: 8px;\"><button onclick=\"editPortFromMap('${escapeHtml(e.id)}', 'from')\" style=\"background: ${e.fromPort ? 'var(--accent)' : 'var(--panel)'}; color: ${e.fromPort ? 'var(--bg)' : 'var(--text-soft)'}; border: 1px solid var(--edge-main); border-radius: 4px; padding: 6px 10px; cursor: pointer; font-family: monospace; font-size: 13px; min-height: 36px;\">${fromPortDisplay}</button></td><td style=\"padding: 8px;\"><button onclick=\"document.getElementById('port-map-modal').classList.remove('active'); claimTheImmortal('${escapeHtml(e.to)}'); focusOnSelected()\" style=\"background: var(--panel-alt); color: var(--text-main); border: 1px solid var(--edge-main); border-radius: 4px; padding: 6px 10px; cursor: pointer; font-size: 13px; min-height: 36px;\">${escapeHtml(toName)}</button></td><td style=\"padding: 8px;\"><button onclick=\"editPortFromMap('${escapeHtml(e.id)}', 'to')\" style=\"background: ${e.toPort ? 'var(--accent)' : 'var(--panel)'}; color: ${e.toPort ? 'var(--bg)' : 'var(--text-soft)'}; border: 1px solid var(--edge-main); border-radius: 4px; padding: 6px 10px; cursor: pointer; font-family: monospace; font-size: 13px; min-height: 36px;\">${toPortDisplay}</button></td><td style=\"padding: 8px;\"><button onclick=\"${goToEdge}\" style=\"background: var(--panel); color: var(--text-main); border: 1px solid var(--edge-main); border-radius: 4px; padding: 6px 10px; cursor: pointer; font-size: 13px; min-height: 36px;\" data-lang=\"tooltips.goToConnection\" data-lang-attr=\"title\">Go</button></td></tr>`;\n        });\n        html += '</tbody></table>';\n        container.innerHTML = html;\n      }\n      window.editPortFromMap = async function(edgeId, which) {\n        const edge = EDGE_DATA.list.find(e => e.id === edgeId);\n        if (!edge) return;\n        const fromName = NODE_DATA[edge.from]?.name || edge.from;\n        const toName = NODE_DATA[edge.to]?.name || edge.to;\n        const nodeId = which === 'from' ? edge.from : edge.to;\n        const nodeName = which === 'from' ? fromName : toName;\n        const currentVal = which === 'from' ? (edge.fromPort || '') : (edge.toPort || '');\n        const newVal = await showPrompt(t(\"dialogs.enterPort\", { nodeName: nodeName }), currentVal);\n        if (newVal !== null && newVal !== '') {\n          const isDuplicate = (EDGE_DATA.list || []).some(e => {\n            if (e.id === edgeId) return false;\n            if (e.from === nodeId && e.fromPort === newVal) return true;\n            if (e.to === nodeId && e.toPort === newVal) return true;\n            return false;\n          });\n          if (isDuplicate && !await showConfirm(t(\"dialogs.portAlreadyUsed\", { port: newVal, nodeName: nodeName }))) {\n            return;\n          }\n          if (which === 'from') edge.fromPort = newVal;\n          else edge.toPort = newVal;\n          renderPortMap();\n          if (currentNodeId && (edge.from === currentNodeId || edge.to === currentNodeId)) {\n            claimTheImmortal(currentNodeId);\n          }\n        } else if (newVal === '') {\n          if (which === 'from') edge.fromPort = '';\n          else edge.toPort = '';\n          renderPortMap();\n          if (currentNodeId && (edge.from === currentNodeId || edge.to === currentNodeId)) {\n            claimTheImmortal(currentNodeId);\n          }\n        }\n      };\n      window.exportPortMap = function() {\n        let csv = \"From Device,From Port,To Device,To Port,Notes\\n\";\n        (EDGE_DATA.list || []).forEach(e => {\n          const fromName = NODE_DATA[e.from]?.name || e.from || \"\";\n          const toName = NODE_DATA[e.to]?.name || e.to || \"\";\n          const notes = (e.notes || []).join(\"; \");\n          csv += `${csvEscape(fromName)},${csvEscape(e.fromPort || \"\")},${csvEscape(toName)},${csvEscape(e.toPort || \"\")},${csvEscape(notes)}\\n`;\n        });\n        const blob = new Blob([csv], { type: \"text/csv\" });\n        const url = URL.createObjectURL(blob);\n        const a = document.createElement(\"a\");\n        a.href = url;\n        a.download = \"port-map.csv\";\n        a.click();\n        URL.revokeObjectURL(url);\n      };\n      if (portMapBtn && portMapModal) {\n        portMapBtn.addEventListener(\"click\", () => { renderPortMap(); portMapModal.classList.add(\"active\"); });\n      }\n      if (portMapClose && portMapModal) {\n        portMapClose.addEventListener(\"click\", () => { portMapModal.classList.remove(\"active\"); });\n      }\n      if (portMapSearch) { portMapSearch.addEventListener(\"input\", renderPortMap); }\n      if (portMapFilter) { portMapFilter.addEventListener(\"change\", renderPortMap); }\n      if (portMapModal) {\n        portMapModal.addEventListener(\"click\", (e) => { if (e.target === portMapModal) portMapModal.classList.remove(\"active\"); });\n      }\n      if (auditLogModal) {\n        auditLogModal.addEventListener(\"click\", (e) => {\n          if (e.target === auditLogModal) {\n            auditLogModal.classList.remove(\"active\");\n          }\n        });\n      }\n      const secretsBtn = document.getElementById(\"secrets-btn\");\n      const secretsModal = document.getElementById(\"secrets-modal\");\n      const secretsModalClose = document.getElementById(\"secrets-modal-close\");\n      const secretEditorModal = document.getElementById(\"secret-editor-modal\");\n      if (secretsBtn && secretsModal) {\n        secretsBtn.addEventListener(\"click\", () => {\n          displaySecrets();\n          secretsModal.classList.add(\"active\");\n        });\n      }\n      if (secretsModalClose && secretsModal) {\n        secretsModalClose.addEventListener(\"click\", () => {\n          secretsModal.classList.remove(\"active\");\n        });\n      }\n      if (secretsModal) {\n        secretsModal.addEventListener(\"click\", (e) => {\n          if (e.target === secretsModal) {\n            secretsModal.classList.remove(\"active\");\n          }\n        });\n      }\n      if (secretEditorModal) {\n        secretEditorModal.addEventListener(\"click\", (e) => {\n          if (e.target === secretEditorModal) {\n            closeSecretEditor();\n          }\n        });\n      }\n      function execSecretCommand(command, value = null) {\n        const content = document.getElementById(\"secret-editor-content\");\n        content.focus();\n        if (command === \"code\") {\n          const sel = window.getSelection();\n          if (sel.rangeCount > 0) {\n            const range = sel.getRangeAt(0);\n            const selectedText = range.toString();\n            if (selectedText) {\n              const code = document.createElement(\"code\");\n              code.textContent = selectedText;\n              range.deleteContents();\n              range.insertNode(code);\n              range.setStartAfter(code);\n              range.collapse(true);\n              sel.removeAllRanges();\n              sel.addRange(range);\n            }\n          }\n        } else if (command === \"codeblock\") {\n          const sel = window.getSelection();\n          if (sel.rangeCount > 0) {\n            const range = sel.getRangeAt(0);\n            const selectedText = range.toString() || \"code here\";\n            const pre = document.createElement(\"pre\");\n            const code = document.createElement(\"code\");\n            code.textContent = selectedText;\n            pre.appendChild(code);\n            range.deleteContents();\n            range.insertNode(pre);\n            const p = document.createElement(\"p\");\n            p.innerHTML = \"<br>\";\n            pre.parentNode.insertBefore(p, pre.nextSibling);\n            range.setStart(p, 0);\n            range.collapse(true);\n            sel.removeAllRanges();\n            sel.addRange(range);\n          }\n        } else if (command === \"blockquote\") {\n          document.execCommand(\"formatBlock\", false, \"blockquote\");\n        } else if (command === \"createLink\") {\n          const url = prompt(\"Enter URL:\", \"https://\");\n          if (url) document.execCommand(\"createLink\", false, url);\n        } else if (command === \"heading\") {\n          if (value) {\n            document.execCommand(\"formatBlock\", false, value);\n          } else {\n            document.execCommand(\"formatBlock\", false, \"p\");\n          }\n        } else {\n          document.execCommand(command, false, null);\n        }\n      }\n      document.querySelectorAll(\"#secret-editor-toolbar button[data-command]\").forEach(btn => {\n        btn.addEventListener(\"click\", (e) => {\n          e.preventDefault();\n          execSecretCommand(btn.dataset.command);\n        });\n      });\n      const secretHeadingSelect = document.getElementById(\"secret-heading-select\");\n      if (secretHeadingSelect) {\n        secretHeadingSelect.addEventListener(\"change\", (e) => {\n          execSecretCommand(\"heading\", e.target.value);\n          e.target.value = \"\";\n        });\n      }\n      const notesHubSearch = document.getElementById(\"notes-hub-search\");\n      const notesHubFilter = document.getElementById(\"notes-hub-filter\");\n      if (notesHubSearch) {\n        notesHubSearch.addEventListener(\"input\", () => displaySecrets());\n      }\n      if (notesHubFilter) {\n        notesHubFilter.addEventListener(\"change\", () => displaySecrets());\n      }\n      loadAuditLog();\n      setupDragToCreate();\n      [\"1\", \"2\", \"3\", \"4\"].forEach(num => {\n       const checkbox = document.getElementById(`layer-${num}`);\n       if (checkbox) {\n        checkbox.addEventListener(\"change\", applyLayerFilter);\n       }\n      });\n      const layerSelect = document.getElementById(\"node-layer\");\n      if (layerSelect) {\n       layerSelect.addEventListener(\"change\", (e) => {\n        if (currentNodeId) {\n         pushUndo(\"change layer\");\n         NODE_DATA[currentNodeId].layer = e.target.value;\n         forgeTheTopology();\n        }\n       });\n      }\n       const assignedRackSelect = document.getElementById(\"node-assigned-rack\");\n       if (assignedRackSelect) {\n      assignedRackSelect.addEventListener(\"change\", (e) => {\n      if (currentNodeId) {\n      pushUndo(\"change assigned rack\");\n      EDGE_DATA.list = EDGE_DATA.list.filter(edge => edge.from !== currentNodeId && edge.to !== currentNodeId);\n      NODE_DATA[currentNodeId].assignedRack = e.target.value;\n      forgeTheTopology();\n      claimTheImmortal(currentNodeId);\n      }\n      });\n       }\n      const rackCapacitySelect = document.getElementById(\"node-rack-capacity\");\n      if (rackCapacitySelect) {\n       rackCapacitySelect.addEventListener(\"change\", (e) => {\n        if (currentNodeId) {\n         pushUndo(\"change rack capacity\");\n         NODE_DATA[currentNodeId].rackCapacity = e.target.value;\n         forgeTheTopology();\n        }\n       });\n      }\n      const bulkToolbarClose = document.getElementById(\"bulk-toolbar-close\");\n      if (bulkToolbarClose) {\n       bulkToolbarClose.addEventListener(\"click\", clearSelection);\n      }\n      const bulkAlignLeft = document.getElementById(\"bulk-align-left\");\n      if (bulkAlignLeft) {\n       bulkAlignLeft.addEventListener(\"click\", () => alignSelectedNodes(\"left\"));\n      }\n      const bulkAlignRight = document.getElementById(\"bulk-align-right\");\n      if (bulkAlignRight) {\n       bulkAlignRight.addEventListener(\"click\", () => alignSelectedNodes(\"right\"));\n      }\n      const bulkAlignTop = document.getElementById(\"bulk-align-top\");\n      if (bulkAlignTop) {\n       bulkAlignTop.addEventListener(\"click\", () => alignSelectedNodes(\"top\"));\n      }\n      const bulkAlignBottom = document.getElementById(\"bulk-align-bottom\");\n      if (bulkAlignBottom) {\n       bulkAlignBottom.addEventListener(\"click\", () => alignSelectedNodes(\"bottom\"));\n      }\n      const bulkDistributeH = document.getElementById(\"bulk-distribute-h\");\n      if (bulkDistributeH) {\n       bulkDistributeH.addEventListener(\"click\", () => distributeSelectedNodes(\"horizontal\"));\n      }\n      const bulkDistributeV = document.getElementById(\"bulk-distribute-v\");\n      if (bulkDistributeV) {\n       bulkDistributeV.addEventListener(\"click\", () => distributeSelectedNodes(\"vertical\"));\n      }\n      const bulkClone = document.getElementById(\"bulk-clone\");\n      if (bulkClone) {\n       bulkClone.addEventListener(\"click\", () => {\n        if (selectedNodes.size === 0) return;\n        pushUndo(\"clone selected\");\n        const nodesToClone = Array.from(selectedNodes);\n        selectedNodes.clear();\n        nodesToClone.forEach(id => {\n         cloneNode(id, true);\n         selectedNodes.add(currentNodeId);\n        });\n        updateNodeSelection();\n       });\n      }\n      const bulkDelete = document.getElementById(\"bulk-delete\");\n      if (bulkDelete) {\n       bulkDelete.addEventListener(\"click\", deleteSelected);\n      }\n      const bulkMobileBtn = document.getElementById(\"bulk-mobile-btn\");\n      const bulkActionsModal = document.getElementById(\"bulk-actions-modal\");\n      const bulkModalClose = document.getElementById(\"bulk-modal-close\");\n      if (bulkMobileBtn) {\n       bulkMobileBtn.addEventListener(\"click\", () => {\n        if (bulkActionsModal) bulkActionsModal.style.display = \"block\";\n       });\n      }\n      if (bulkModalClose) {\n       bulkModalClose.addEventListener(\"click\", () => {\n        if (bulkActionsModal) bulkActionsModal.style.display = \"none\";\n       });\n      }\n      const closeModalAfterAction = () => {\n       if (bulkActionsModal) bulkActionsModal.style.display = \"none\";\n      };\n      const bulkAlignLeftMobile = document.getElementById(\"bulk-align-left-mobile\");\n      if (bulkAlignLeftMobile) {\n       bulkAlignLeftMobile.addEventListener(\"click\", () => {\n        alignSelectedNodes(\"left\");\n        closeModalAfterAction();\n       });\n      }\n      const bulkAlignRightMobile = document.getElementById(\"bulk-align-right-mobile\");\n      if (bulkAlignRightMobile) {\n       bulkAlignRightMobile.addEventListener(\"click\", () => {\n        alignSelectedNodes(\"right\");\n        closeModalAfterAction();\n       });\n      }\n      const bulkAlignTopMobile = document.getElementById(\"bulk-align-top-mobile\");\n      if (bulkAlignTopMobile) {\n       bulkAlignTopMobile.addEventListener(\"click\", () => {\n        alignSelectedNodes(\"top\");\n        closeModalAfterAction();\n       });\n      }\n      const bulkAlignBottomMobile = document.getElementById(\"bulk-align-bottom-mobile\");\n      if (bulkAlignBottomMobile) {\n       bulkAlignBottomMobile.addEventListener(\"click\", () => {\n        alignSelectedNodes(\"bottom\");\n        closeModalAfterAction();\n       });\n      }\n      const bulkDistributeHMobile = document.getElementById(\"bulk-distribute-h-mobile\");\n      if (bulkDistributeHMobile) {\n       bulkDistributeHMobile.addEventListener(\"click\", () => {\n        distributeSelectedNodes(\"horizontal\");\n        closeModalAfterAction();\n       });\n      }\n      const bulkDistributeVMobile = document.getElementById(\"bulk-distribute-v-mobile\");\n      if (bulkDistributeVMobile) {\n       bulkDistributeVMobile.addEventListener(\"click\", () => {\n        distributeSelectedNodes(\"vertical\");\n        closeModalAfterAction();\n       });\n      }\n      const bulkCloneMobile = document.getElementById(\"bulk-clone-mobile\");\n      if (bulkCloneMobile) {\n       bulkCloneMobile.addEventListener(\"click\", () => {\n        if (selectedNodes.size === 0) return;\n        pushUndo(\"clone selected\");\n        const nodesToClone = Array.from(selectedNodes);\n        selectedNodes.clear();\n        nodesToClone.forEach(id => {\n         cloneNode(id);\n         selectedNodes.add(currentNodeId);\n        });\n        updateNodeSelection();\n        closeModalAfterAction();\n       });\n      }\n      const bulkDeleteMobile = document.getElementById(\"bulk-delete-mobile\");\n      if (bulkDeleteMobile) {\n       bulkDeleteMobile.addEventListener(\"click\", () => {\n        deleteSelected();\n        closeModalAfterAction();\n       });\n      }\n      const bulkLockMobile = document.getElementById(\"bulk-lock-mobile\");\n      if (bulkLockMobile) {\n       bulkLockMobile.addEventListener(\"click\", () => {\n        toggleLockSelected();\n        closeModalAfterAction();\n       });\n      }\n      const bulkGroupMobile = document.getElementById(\"bulk-group-mobile\");\n      if (bulkGroupMobile) {\n       bulkGroupMobile.addEventListener(\"click\", () => {\n        toggleGroupSelected();\n        closeModalAfterAction();\n       });\n      }\n      const bulkClearMobile = document.getElementById(\"bulk-clear-mobile\");\n      if (bulkClearMobile) {\n       bulkClearMobile.addEventListener(\"click\", () => {\n        clearSelection();\n        closeModalAfterAction();\n       });\n      }\n      const searchInput = document.getElementById(\"search-nodes\");\n      let searchTimeout = null;\n      if (searchInput) {\n       searchInput.addEventListener(\"input\", (e) => {\n        clearTimeout(searchTimeout);\n        searchTimeout = setTimeout(() => { searchNodes(e.target.value); }, 150);\n       });\n      }\n      addNodeBtn.addEventListener(\"click\", () => {\n\t  if (isViewOnly()) return;\n       document.getElementById(\"new-node-name\").value = \"\";\n       document.getElementById(\"new-node-ip\").value = \"\";\n       document.getElementById(\"new-node-tags\").value = \"\";\n       document.getElementById(\"new-node-category\").value = \"basic\";\n       populateModalShapeSelect(\"new-node-category\", \"new-node-shape\", \"new-node-shape-preview\");\n       document.getElementById(\"new-node-fill-color\").value = \"#1e293b\";\n       document.getElementById(\"new-node-border-color\").value = \"#475569\";\n       document.getElementById(\"new-node-size\").value = \"55\";\n       document.getElementById(\"new-node-size-value\").textContent = \"55\";\n       document.getElementById(\"new-node-layer\").value = \"layer1\";\n       document.getElementById(\"new-node-line-color\").value = \"#475569\";\n       document.getElementById(\"new-node-line-direction\").value = \"none\";\n       document.getElementById(\"new-node-line-routing\").value = \"orthogonal\";\n       document.getElementById(\"new-node-notes\").value = \"\";\n       const moreOpts = document.getElementById(\"new-node-more-options\");\n       if (moreOpts) moreOpts.removeAttribute(\"open\");\n       const connectSelect = document.getElementById(\"new-node-connect-to\");\n       connectSelect.innerHTML = '<option value=\"\">None</option>';\n       Object.entries(NODE_DATA).forEach(([nid, n]) => {\n        if (n.assignedRack) return;\n        const opt = document.createElement(\"option\");\n        opt.value = nid;\n        opt.textContent = n.name || nid;\n        connectSelect.appendChild(opt);\n       });\n       addNodeModal.classList.add(\"active\");\n       document.getElementById(\"new-node-name\").focus();\n      });\n      const addRackBtn = document.getElementById(\"add-rack-btn\");\n      const addRackModal = document.getElementById(\"add-rack-modal\");\n      const addRackCancel = document.getElementById(\"add-rack-cancel\");\n      const addRackSave = document.getElementById(\"add-rack-save\");\n      if (addRackBtn && addRackModal) {\n       addRackBtn.addEventListener(\"click\", () => {\n\t   if (isViewOnly()) return;\n        document.getElementById(\"new-rack-name\").value = \"\";\n        document.getElementById(\"new-rack-ip\").value = \"\";\n        document.getElementById(\"new-rack-tags\").value = \"\";\n        document.getElementById(\"new-rack-shape\").value = \"server\";\n        document.getElementById(\"new-rack-capacity\").value = \"42\";\n        document.getElementById(\"new-rack-fill-color\").value = \"#1e293b\";\n        document.getElementById(\"new-rack-border-color\").value = \"#475569\";\n        document.getElementById(\"new-rack-size\").value = \"55\";\n        document.getElementById(\"new-rack-size-value\").textContent = \"55\";\n        document.getElementById(\"new-rack-layer\").value = \"layer1\";\n        document.getElementById(\"new-rack-notes\").value = \"\";\n        const moreOpts = document.getElementById(\"new-rack-more-options\");\n        if (moreOpts) moreOpts.removeAttribute(\"open\");\n        const assignContainer = document.getElementById(\"new-rack-assign-nodes\");\n        assignContainer.innerHTML = \"\";\n        Object.entries(NODE_DATA).forEach(([nid, n]) => {\n         if (n.isRack) return;\n         if (n.assignedRack) return;\n         const row = document.createElement(\"div\");\n         row.style.cssText = \"display:flex;align-items:center;gap:8px;padding:6px 10px;border:1px solid var(--edge-main);border-radius:4px;margin-bottom:4px;background:var(--panel)\";\n         const cb = document.createElement(\"input\");\n         cb.type = \"checkbox\";\n         cb.style.cssText = \"flex-shrink:0;width:16px;height:16px;margin:0;cursor:pointer\";\n         cb.dataset.nodeId = nid;\n         cb.className = \"new-rack-assign-cb\";\n         const lbl = document.createElement(\"span\");\n         lbl.style.cssText = \"color:var(--text-main);font-size:13px;flex:1;text-align:left\";\n         lbl.textContent = n.name || nid;\n         row.appendChild(cb);\n         row.appendChild(lbl);\n         assignContainer.appendChild(row);\n        });\n        if (!assignContainer.children.length) {\n         assignContainer.innerHTML = '<div style=\"color:var(--text-soft);font-size:13px;text-align:center;padding:10px\">No unassigned nodes</div>';\n        }\n        addRackModal.classList.add(\"active\");\n        document.getElementById(\"new-rack-name\").focus();\n       });\n      }\n      if (addRackCancel && addRackModal) {\n       addRackCancel.addEventListener(\"click\", () => {\n        addRackModal.classList.remove(\"active\");\n       });\n      }\n      if (addRackModal) {\n       addRackModal.addEventListener(\"click\", (e) => {\n        if (e.target === addRackModal) {\n         addRackModal.classList.remove(\"active\");\n        }\n       });\n      }\n      document.getElementById(\"new-node-size\").addEventListener(\"input\", (e) => {\n       document.getElementById(\"new-node-size-value\").textContent = e.target.value;\n      });\n      document.getElementById(\"new-rack-size\").addEventListener(\"input\", (e) => {\n       document.getElementById(\"new-rack-size-value\").textContent = e.target.value;\n      });\n      if (addRackSave && addRackModal) {\n       addRackSave.addEventListener(\"click\", () => {\n        const name = document.getElementById(\"new-rack-name\").value.trim();\n        const ip = document.getElementById(\"new-rack-ip\").value.trim();\n        const tagsStr = document.getElementById(\"new-rack-tags\").value.trim();\n        const shape = document.getElementById(\"new-rack-shape\").value;\n        const capacity = document.getElementById(\"new-rack-capacity\").value;\n        if (!name) {\n         showAlert(t(\"dialogs.enterRackName\"));\n         return;\n        }\n        const tags = tagsStr ? tagsStr.split(\",\").map((t) => t.trim()).filter((t) => t) : [];\n        let baseId = name.toLowerCase().replace(/[^a-z0-9]+/g, \"-\").replace(/^-|-$/g, \"\");\n        if (!baseId) baseId = \"rack\";\n        let nodeId = baseId;\n        let counter = 1;\n        while (NODE_DATA[nodeId]) {\n         nodeId = baseId + \"-\" + counter;\n         counter++;\n        }\n        const rackLayer = document.getElementById(\"new-rack-layer\").value;\n        const rackFillColor = document.getElementById(\"new-rack-fill-color\").value;\n        const rackBorderColor = document.getElementById(\"new-rack-border-color\").value;\n        const rackSize = parseInt(document.getElementById(\"new-rack-size\").value, 10);\n        const rackNotesVal = document.getElementById(\"new-rack-notes\").value.trim();\n        pushUndo(\"add rack\");\n        NODE_DATA[nodeId] = {\n         shape: shape,\n         name: name,\n         ip: ip || \"\",\n         role: \"Rack\",\n         tags: tags,\n         notes: rackNotesVal ? [rackNotesVal] : [],\n         mac: \"\",\n         rackUnit: \"\",\n         uHeight: \"1\",\n         layer: rackLayer,\n         assignedRack: \"\",\n         rackCapacity: capacity,\n         isRack: true,\n         locked: false,\n         groupId: null\n        };\n        const viewWidth = CANVAS_WIDTH / canvasState.zoom;\n        const viewHeight = CANVAS_HEIGHT / canvasState.zoom;\n        const centerX = canvasState.panX + (viewWidth / 2);\n        const centerY = canvasState.panY + (viewHeight / 2);\n        savedPositions[nodeId] = {\n         x: centerX,\n         y: centerY\n        };\n        if (rackFillColor !== \"#1e293b\" || rackBorderColor !== \"#475569\") {\n         const styleEntry = ensureStyleEntry(nodeId);\n         if (!styleEntry[\"all\"]) styleEntry[\"all\"] = {};\n         if (rackFillColor !== \"#1e293b\") styleEntry[\"all\"].circleColor = rackFillColor;\n         if (rackBorderColor !== \"#475569\") styleEntry[\"all\"].circleBorder = rackBorderColor;\n        }\n        if (rackSize !== 55) {\n         savedSizes[nodeId] = rackSize;\n        }\n        document.querySelectorAll(\".new-rack-assign-cb:checked\").forEach((cb) => {\n         const assignId = cb.dataset.nodeId;\n         if (NODE_DATA[assignId]) {\n          NODE_DATA[assignId].assignedRack = nodeId;\n         }\n        });\n        addRackModal.classList.remove(\"active\");\n        forgeTheTopology();\n        claimTheImmortal(nodeId);\n       });\n       [\"new-rack-name\", \"new-rack-ip\", \"new-rack-tags\"].forEach((inputId) => {\n        const input = document.getElementById(inputId);\n        if (input) {\n         input.addEventListener(\"keypress\", (e) => {\n          if (e.key === \"Enter\") {\n           addRackSave.click();\n          }\n         });\n        }\n       });\n      }\n      addNodeCancel.addEventListener(\"click\", () => {\n       addNodeModal.classList.remove(\"active\");\n      });\n      addNodeModal.addEventListener(\"click\", (e) => {\n       if (e.target === addNodeModal) {\n        addNodeModal.classList.remove(\"active\");\n       }\n      });\n      addNodeSave.addEventListener(\"click\", () => {\n       const name = document.getElementById(\"new-node-name\").value.trim();\n       const ip = document.getElementById(\"new-node-ip\").value.trim();\n       const tagsStr = document.getElementById(\"new-node-tags\").value.trim();\n       const shape = document.getElementById(\"new-node-shape\").value;\n       if (!name) {\n        showAlert(t(\"dialogs.enterNodeName\"));\n        return;\n       }\n       const tags = tagsStr ? tagsStr.split(\",\").map((t) => t.trim()).filter((t) => t) : [];\n       let baseId = name.toLowerCase().replace(/[^a-z0-9]+/g, \"-\").replace(/^-|-$/g, \"\");\n       if (!baseId) baseId = \"node\";\n       let nodeId = baseId;\n       let counter = 1;\n       while (NODE_DATA[nodeId]) {\n        nodeId = baseId + \"-\" + counter;\n        counter++;\n       }\n       const nodeLayer = document.getElementById(\"new-node-layer\").value;\n       const nodeFillColor = document.getElementById(\"new-node-fill-color\").value;\n       const nodeBorderColor = document.getElementById(\"new-node-border-color\").value;\n       const nodeSize = parseInt(document.getElementById(\"new-node-size\").value, 10);\n       const nodeConnectTo = document.getElementById(\"new-node-connect-to\").value;\n       const nodeLineColor = document.getElementById(\"new-node-line-color\").value;\n       const nodeLineDirection = document.getElementById(\"new-node-line-direction\").value;\n       const nodeLineRouting = document.getElementById(\"new-node-line-routing\").value;\n       const nodeNotesVal = document.getElementById(\"new-node-notes\").value.trim();\n\t   pushUndo(\"add node\");\n       NODE_DATA[nodeId] = {\n        shape: shape || \"circle\",\n        name: name,\n        ip: ip || \"\",\n        role: \"\",\n        tags: tags,\n        notes: nodeNotesVal ? [nodeNotesVal] : [],\n        mac: \"\",\n        rackUnit: \"\",\n        uHeight: \"1\",\n        layer: nodeLayer,\n        assignedRack: \"\",\n        rackCapacity: \"42\",\n        isRack: false,\n        locked: false,\n        groupId: null\n       };\n       if (currentView.mode === \"rack\" && currentView.rackId) {\n        NODE_DATA[nodeId].assignedRack = currentView.rackId;\n        NODE_DATA[nodeId].layer = \"layer1\";\n        const rackCapacity = getRackCapacity(currentView.rackId);\n        const rackUHeight = getRackUHeight(currentView.rackId);\n        const viewHeight = CANVAS_HEIGHT / canvasState.zoom;\n        const centerY = canvasState.panY + (viewHeight / 2);\n        let unit = rackCapacity - Math.round((centerY - RACK_START_Y) / rackUHeight);\n        unit = Math.max(1, Math.min(rackCapacity, unit));\n        NODE_DATA[nodeId].rackUnit = String(unit);\n       }\n      const viewWidth = CANVAS_WIDTH / canvasState.zoom;\n      const viewHeight = CANVAS_HEIGHT / canvasState.zoom;\n      const centerX = canvasState.panX + (viewWidth / 2);\n      const centerY = canvasState.panY + (viewHeight / 2);\n      savedPositions[nodeId] = {\n      x: centerX,\n      y: centerY\n      };\n       if (nodeFillColor !== \"#1e293b\" || nodeBorderColor !== \"#475569\") {\n        const styleEntry = ensureStyleEntry(nodeId);\n        if (!styleEntry[\"all\"]) styleEntry[\"all\"] = {};\n        if (nodeFillColor !== \"#1e293b\") styleEntry[\"all\"].circleColor = nodeFillColor;\n        if (nodeBorderColor !== \"#475569\") styleEntry[\"all\"].circleBorder = nodeBorderColor;\n       }\n       if (nodeSize !== 55) {\n        savedSizes[nodeId] = nodeSize;\n       }\n       if (nodeConnectTo && NODE_DATA[nodeConnectTo]) {\n        EDGE_DATA.list.push({\n         id: nodeId + \"-\" + nodeConnectTo + \"-\" + Date.now(),\n         from: nodeId,\n         to: nodeConnectTo,\n         width: 4,\n         color: nodeLineColor || \"#475569\",\n         direction: nodeLineDirection || \"none\",\n         routing: nodeLineRouting || \"orthogonal\",\n         type: \"main\",\n         notes: [],\n         fromPort: \"\",\n         toPort: \"\",\n         lineStyle: \"solid\",\n         waypoints: []\n        });\n       }\n       addNodeModal.classList.remove(\"active\");\n       forgeTheTopology();\n       claimTheImmortal(nodeId);\n      });\n      [\"new-node-name\", \"new-node-ip\", \"new-node-tags\"].forEach(\n       (inputId) => {\n        document.getElementById(inputId).addEventListener(\"keypress\", (e) => {\n         if (e.key === \"Enter\") {\n          addNodeSave.click();\n         }\n        });\n       });\n      const clearAllBtn = document.getElementById(\"clear-all-btn\");\n      const clearAllModal = document.getElementById(\"clear-all-modal\");\n      const clearAllCancel = document.getElementById(\"clear-all-cancel\");\n      const clearAllConfirm = document.getElementById(\"clear-all-confirm\");\n      clearAllBtn.addEventListener(\"click\", () => {\n       clearAllModal.classList.add(\"active\");\n      });\n      clearAllCancel.addEventListener(\"click\", () => {\n       clearAllModal.classList.remove(\"active\");\n      });\n      clearAllModal.addEventListener(\"click\", (e) => {\n       if (e.target === clearAllModal) {\n        clearAllModal.classList.remove(\"active\");\n       }\n      });\n      clearAllConfirm.addEventListener(\"click\", () => {\n        NODE_DATA = {};\n        EDGE_DATA = { list: [] };\n        EDGE_LEGEND = {};\n        RECT_DATA = { list: [] };\n        TEXT_DATA = { list: [] };\n        IMAGE_DATA = { list: [] };\n        savedPositions = {};\n        savedSizes = {};\n        savedStyles = {};\n        undoStack = [];\n        redoStack = [];\n        updateUndoButtons();\n        auditLog = [];\n        localStorage.removeItem(AUDIT_STORAGE_KEY);\n        rollbackVersions = [];\n        currentRollbackIndex = -1;\n        localStorage.removeItem(ROLLBACK_STORAGE_KEY);   \n        clipboard = null;      \n        selectedNodes.clear();\n        selectedEdges.clear();\n        selectedRects.clear();\n        selectedTexts.clear();\n        documentTabs = [{\n          id: \"main\",\n          name: \"Main Topology\",\n          nodes: {},\n          edges: { list: [] },\n          positions: {},\n          sizes: {},\n          styles: {},\n          legend: {},\n          rects: { list: [] },\n          texts: { list: [] },\n          pageState: null\n        }];\n        currentTabIndex = 0;\n        displayTabs();\n        clearAutosave();\n        try {\n          Object.keys(localStorage).forEach(key => {\n            if (key.startsWith(\"theonefile\") || key.startsWith(\"theOneFile\")) {\n              localStorage.removeItem(key);\n            }\n          });\n        } catch(e) {}\n        CUSTOM_LANG = null;\n        LANG = JSON.parse(JSON.stringify(DEFAULT_LANG));\n        applyLanguage();\n        const addLineSelect = document.getElementById(\"add-line-select\");\n        if (addLineSelect) addLineSelect.innerHTML = \"\";\n        const edgeLegendContent = document.getElementById(\"edge-legend-content\");\n        if (edgeLegendContent) edgeLegendContent.innerHTML = \"\";\n        clearAllModal.classList.remove(\"active\");\n        forgeTheTopology();\n        document.getElementById(\"node-panel\").style.display = \"none\";\n        document.getElementById(\"edge-panel\").style.display = \"none\";\n        document.getElementById(\"text-panel\").style.display = \"none\";\n        document.getElementById(\"rect-panel\").style.display = \"none\";\n        document.getElementById(\"image-panel\").style.display = \"none\";\n        document.getElementById(\"topology-toolbar\").style.display = \"none\";\n        currentNodeId = null;\n        currentEdgeId = null;\n        currentTextId = null;\n        currentRectId = null;\n        currentImageId = null;\n        if (typeof displayRollbackVersions === \"function\") displayRollbackVersions();\n        if (typeof displayAuditLog === \"function\") displayAuditLog();\n        if (typeof forgeTheLegend === \"function\") forgeTheLegend();\n      });\n      (function addDeleteNodeButton() {\n       const nodePanel = document.getElementById(\"node-panel\");\n       if (!nodePanel) return;\n       let deleteBtn = document.getElementById(\"delete-node-btn\");\n       if (!deleteBtn) {\n        deleteBtn = document.createElement(\"button\");\n        deleteBtn.id = \"delete-node-btn\";\n        deleteBtn.textContent = t(\"ui.buttons.deleteNode\");\n        deleteBtn.style.cssText = \"margin-top:15px; padding:10px 16px; background:var(--danger); color:#fff; border:none; border-radius:6px; cursor:pointer; font-size:clamp(14px,1.6vw,18px); font-weight:600; width:100%;\";\n        nodePanel.appendChild(deleteBtn);\n       }\n       deleteBtn.addEventListener(\"click\", () => {\n        if (!currentNodeId) return;\n        const nodeData = NODE_DATA[currentNodeId];\n        let confirmMessage;\n        let nodesInsideRack = [];\n        if (nodeData?.isRack) {\n         nodesInsideRack = Object.entries(NODE_DATA)\n          .filter(([id, n]) => n.assignedRack === currentNodeId)\n          .map(([id, n]) => n.name || id);\n         if (nodesInsideRack.length > 0) {\n          confirmMessage = t(\"dialogs.deleteRackWithNodes\", { name: nodeData.name, count: nodesInsideRack.length, nodeList: nodesInsideRack.join('\\n• ') });\n         } else {\n          confirmMessage = t(\"dialogs.deleteEmptyRack\", { name: nodeData.name });\n         }\n        } else {\n         confirmMessage = t(\"dialogs.deleteNodeWithConnections\", { name: nodeData?.name || currentNodeId });\n        }\n        challengeTheImmortal(confirmMessage, () => {\n         pushUndo(\"delete node\");\n         if (nodeData?.isRack) {\n          Object.keys(NODE_DATA).forEach(id => {\n           if (NODE_DATA[id]?.assignedRack === currentNodeId) {\n            EDGE_DATA.list = EDGE_DATA.list.filter(e => e.from !== id && e.to !== id);\n            delete NODE_DATA[id];\n            delete savedPositions[id];\n            delete savedSizes[id];\n            delete savedStyles[id];\n           }\n          });\n         }\n         EDGE_DATA.list = EDGE_DATA.list.filter(e => e.from !== currentNodeId && e.to !== currentNodeId);\n         delete NODE_DATA[currentNodeId];\n         delete savedPositions[currentNodeId];\n         delete savedSizes[currentNodeId];\n         delete savedStyles[currentNodeId];\n         currentNodeId = null;\n         currentEdgeId = null;\n         forgeTheTopology();\n         const remainingNodes = Object.keys(NODE_DATA);\n         if (remainingNodes.length > 0) {\n          claimTheImmortal(remainingNodes[0]);\n         } else {\n          document.getElementById(\"node-panel\").style.display = \"none\";\n          document.getElementById(\"edge-panel\").style.display = \"none\";\n          document.getElementById(\"text-panel\").style.display = \"none\";\n          document.getElementById(\"rect-panel\").style.display = \"none\";\n          document.getElementById(\"image-panel\").style.display = \"none\";\n          document.getElementById(\"topology-toolbar\").style.display = \"none\";\n         }\n        });\n       });\n      })();\n      function screenshotCanvas() {\n       const svg = document.getElementById(\"map\");\n       const svgClone = svg.cloneNode(true);\n       const viewBox = svg.getAttribute(\"viewBox\").split(\" \").map(Number);\n       const [x, y, width, height] = viewBox;\n       svgClone.setAttribute(\"width\", width);\n       svgClone.setAttribute(\"height\", height);\n       svgClone.setAttribute(\"viewBox\", `${x} ${y} ${width} ${height}`);\n       const wrapper = document.createElement(\"div\");\n       wrapper.style.position = \"absolute\";\n       wrapper.style.left = \"-9999px\";\n       wrapper.appendChild(svgClone);\n       document.body.appendChild(wrapper);\n       function inlineStyles(original, clone) {\n        const elements = original.querySelectorAll(\"*\");\n        const clonedElements = clone.querySelectorAll(\"*\");\n        const rootStyles = getComputedStyle(document.documentElement);\n        const bgColor = rootStyles.getPropertyValue(\"--bg\").trim() || \"#050608\";\n        const bgRect = document.createElementNS(\"http://www.w3.org/2000/svg\", \"rect\");\n        bgRect.setAttribute(\"x\", x);\n        bgRect.setAttribute(\"y\", y);\n        bgRect.setAttribute(\"width\", width);\n        bgRect.setAttribute(\"height\", height);\n        bgRect.setAttribute(\"fill\", bgColor);\n        clone.insertBefore(bgRect, clone.firstChild);\n        elements.forEach((el, index) => {\n         const clonedEl = clonedElements[index];\n         if (!clonedEl) return;\n         const computedStyle = getComputedStyle(el);\n         const svgProps = [\"fill\", \"stroke\", \"stroke-width\", \"stroke-dasharray\", \"stroke-linecap\", \"stroke-linejoin\", \"opacity\", \"font-family\", \"font-size\", \"font-weight\", \"font-style\", \"text-anchor\", \"dominant-baseline\", \"marker-start\", \"marker-end\", ];\n         svgProps.forEach((prop) => {\n          const value = computedStyle.getPropertyValue(prop);\n          if (value && value !== \"none\" && value !== \"normal\") {\n           clonedEl.style[prop] = value;\n          }\n         });\n         clonedEl.removeAttribute(\"class\");\n        });\n       }\n       inlineStyles(svg, svgClone);\n       const svgData = new XMLSerializer().serializeToString(svgClone);\n       document.body.removeChild(wrapper);\n       const svgBlob = new Blob([svgData], {\n        type: \"image/svg+xml;charset=utf-8\",\n       });\n       const url = URL.createObjectURL(svgBlob);\n       const img = new Image();\n       img.onload = function() {\n        const canvas = document.createElement(\"canvas\");\n        canvas.width = width;\n        canvas.height = height;\n        const ctx = canvas.getContext(\"2d\");\n        ctx.drawImage(img, 0, 0);\n        canvas.toBlob(function(blob) {\n         const link = document.createElement(\"a\");\n         const timestamp = new Date().toISOString().slice(0, 10);\n         link.download = `topology-${timestamp}.png`;\n         link.href = URL.createObjectURL(blob);\n         link.click();\n         URL.revokeObjectURL(url);\n         URL.revokeObjectURL(link.href);\n        }, \"image/png\");\n       };\n       img.onerror = function() {\n        console.error(\"Failed to load SVG image\");\n        showAlert(t(\"dialogs.screenshotFailed\"));\n        URL.revokeObjectURL(url);\n       };\n       img.src = url;\n      }\n      function exportCanvasSVG() {\n       const svg = document.getElementById(\"map\");\n       const svgClone = svg.cloneNode(true);\n       const viewBox = svg.getAttribute(\"viewBox\").split(\" \").map(Number);\n       const [x, y, width, height] = viewBox;\n       svgClone.setAttribute(\"width\", width);\n       svgClone.setAttribute(\"height\", height);\n       const rootStyles = getComputedStyle(document.documentElement);\n       const bgColor = rootStyles.getPropertyValue(\"--bg\").trim() || \"#050608\";\n       const bgRect = document.createElementNS(\"http://www.w3.org/2000/svg\", \"rect\");\n       bgRect.setAttribute(\"x\", x);\n       bgRect.setAttribute(\"y\", y);\n       bgRect.setAttribute(\"width\", width);\n       bgRect.setAttribute(\"height\", height);\n       bgRect.setAttribute(\"fill\", bgColor);\n       svgClone.insertBefore(bgRect, svgClone.firstChild);\n       const wrapper = document.createElement(\"div\");\n       wrapper.style.position = \"absolute\";\n       wrapper.style.left = \"-9999px\";\n       wrapper.appendChild(svgClone);\n       document.body.appendChild(wrapper);\n       const elements = svg.querySelectorAll(\"*\");\n       const clonedElements = svgClone.querySelectorAll(\"*\");\n       elements.forEach((el, index) => {\n        const clonedEl = clonedElements[index];\n        if (!clonedEl) return;\n        const computedStyle = getComputedStyle(el);\n        const svgProps = [\"fill\", \"stroke\", \"stroke-width\", \"stroke-dasharray\", \"stroke-linecap\", \"stroke-linejoin\", \"opacity\", \"font-family\", \"font-size\", \"font-weight\", \"font-style\", \"text-anchor\", \"dominant-baseline\", \"marker-start\", \"marker-end\", ];\n        svgProps.forEach((prop) => {\n         const value = computedStyle.getPropertyValue(prop);\n         if (value && value !== \"none\" && value !== \"normal\") {\n          clonedEl.setAttribute(prop, value);\n         }\n        });\n        clonedEl.removeAttribute(\"class\");\n       });\n       const svgData = new XMLSerializer().serializeToString(svgClone);\n       document.body.removeChild(wrapper);\n       const blob = new Blob([svgData], {\n        type: \"image/svg+xml;charset=utf-8\",\n       });\n       const url = URL.createObjectURL(blob);\n       const link = document.createElement(\"a\");\n       const timestamp = new Date().toISOString().slice(0, 10);\n       link.download = `topology-${timestamp}.svg`;\n       link.href = url;\n       link.click();\n       URL.revokeObjectURL(url);\n      }\n      let resizeTimeout;\n       window.addEventListener('resize', () => {\n       clearTimeout(resizeTimeout);\n       resizeTimeout = setTimeout(() => {\n       updateDrawToolbarVisibility();\n       updateTopologyToolbarVisibility();\n       }, 100);\n      });\n\tdocument.addEventListener('DOMContentLoaded', () => {\n\t  document.querySelectorAll('.dropdown').forEach(dropdown => {\n\t\tconst btn = dropdown.querySelector('.dropdown-btn');\n\t\tconst menu = dropdown.querySelector('.dropdown-menu');\n\t\tif (!btn || !menu) return;\n\t\tbtn.addEventListener('click', (e) => {\n\t\t  e.stopPropagation();\n\t\t  document.querySelectorAll('.dropdown-menu.open').forEach(m => {\n\t\t\tif (m !== menu) m.classList.remove('open');\n\t\t  });\n\t\t  menu.classList.toggle('open');\n\t\t});\n\t  });\n\t  document.addEventListener('click', (e) => {\n\t\tif (!e.target.closest('.dropdown')) {\n\t\t  document.querySelectorAll('.dropdown-menu.open').forEach(m => m.classList.remove('open'));\n\t\t}\n\t  });\n\t  document.querySelectorAll('.dropdown-menu button').forEach(btn => {\n\t\tbtn.addEventListener('click', () => {\n\t\t  setTimeout(() => {\n\t\t\tdocument.querySelectorAll('.dropdown-menu.open').forEach(m => m.classList.remove('open'));\n\t\t  }, 100);\n\t\t});\n\t  });\n\t  });\nfunction showFormatHelp() {\n  document.getElementById('save-info-modal').classList.add('active');\n  document.querySelectorAll('.help-tab').forEach(t => {\n    t.style.background = 'var(--panel)';\n    t.style.color = 'var(--text-main)';\n  });\n  document.querySelectorAll('.help-tab-content').forEach(c => c.style.display = 'none');\n  const formatsTab = document.querySelector('.help-tab[data-tab=\"formats\"]');\n  if (formatsTab) {\n    formatsTab.style.background = 'var(--accent)';\n    formatsTab.style.color = 'var(--bg)';\n  }\n  document.getElementById('help-tab-formats').style.display = 'block';\n}\n\tfunction printTopology() {\n\t  const svg = document.getElementById('map');\n\t  if (!svg) { window.print(); return; }\n\t  const originalViewBox = svg.getAttribute('viewBox');\n\t  let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;\n\t  \n\t  Object.values(savedPositions).forEach(pos => {\n\t\tminX = Math.min(minX, pos.x - 100);\n\t\tminY = Math.min(minY, pos.y - 100);\n\t\tmaxX = Math.max(maxX, pos.x + 100);\n\t\tmaxY = Math.max(maxY, pos.y + 100);\n\t  });\n\t  \n\t  RECT_DATA.list.forEach(rect => {\n\t\tminX = Math.min(minX, rect.x);\n\t\tminY = Math.min(minY, rect.y);\n\t\tmaxX = Math.max(maxX, rect.x + rect.width);\n\t\tmaxY = Math.max(maxY, rect.y + rect.height);\n\t  });\n\t  \n\t  TEXT_DATA.list.forEach(text => {\n\t\tminX = Math.min(minX, text.x - 50);\n\t\tminY = Math.min(minY, text.y - 50);\n\t\tmaxX = Math.max(maxX, text.x + 200);\n\t\tmaxY = Math.max(maxY, text.y + 50);\n\t  });\n\n\t  const padding = 50;\n\t  minX -= padding;\n\t  minY -= padding;\n\t  maxX += padding;\n\t  maxY += padding;\n\n\t  if (minX !== Infinity) {\n\t\tsvg.setAttribute('viewBox', `${minX} ${minY} ${maxX - minX} ${maxY - minY}`);\n\t  }\n\n\t  window.print();\n\n\t  setTimeout(() => {\n\t\tsvg.setAttribute('viewBox', originalViewBox);\n\t  }, 1000);\n\t}\n\tfunction printTopologyColor(bgOverride) {\n\t  const svg = document.getElementById(\"map\");\n\t  if (!svg) { window.print(); return; }\n\t  const svgClone = svg.cloneNode(true);\n\t  const viewBox = svg.getAttribute(\"viewBox\").split(\" \").map(Number);\n\t  const [x, y, width, height] = viewBox;\n\t  svgClone.setAttribute(\"width\", width);\n\t  svgClone.setAttribute(\"height\", height);\n\t  svgClone.setAttribute(\"viewBox\", `${x} ${y} ${width} ${height}`);\n\t  const wrapper = document.createElement(\"div\");\n\t  wrapper.style.position = \"absolute\";\n\t  wrapper.style.left = \"-9999px\";\n\t  wrapper.appendChild(svgClone);\n\t  document.body.appendChild(wrapper);\n\t  function inlineStyles(original, clone) {\n\t\tconst elements = original.querySelectorAll(\"*\");\n\t\tconst clonedElements = clone.querySelectorAll(\"*\");\n\t\tconst rootStyles = getComputedStyle(document.documentElement);\n\t\tconst bgColor = bgOverride || rootStyles.getPropertyValue(\"--bg\").trim() || \"#050608\";\n\t\tconst bgRect = document.createElementNS(\"http://www.w3.org/2000/svg\", \"rect\");\n\t\tbgRect.setAttribute(\"x\", x);\n\t\tbgRect.setAttribute(\"y\", y);\n\t\tbgRect.setAttribute(\"width\", width);\n\t\tbgRect.setAttribute(\"height\", height);\n\t\tbgRect.setAttribute(\"fill\", bgColor);\n\t\tclone.insertBefore(bgRect, clone.firstChild);\n\t\telements.forEach((el, index) => {\n\t\t  const clonedEl = clonedElements[index];\n\t\t  if (!clonedEl) return;\n\t\t  const computedStyle = getComputedStyle(el);\n\t\t  const svgProps = [\"fill\", \"stroke\", \"stroke-width\", \"stroke-dasharray\", \"stroke-linecap\", \"stroke-linejoin\", \"opacity\", \"font-family\", \"font-size\", \"font-weight\", \"font-style\", \"text-anchor\", \"dominant-baseline\", \"marker-start\", \"marker-end\"];\n\t\t  svgProps.forEach((prop) => {\n\t\t\tconst value = computedStyle.getPropertyValue(prop);\n\t\t\tif (value && value !== \"none\" && value !== \"normal\") {\n\t\t\t  clonedEl.style[prop] = value;\n\t\t\t}\n\t\t  });\n\t\t  clonedEl.removeAttribute(\"class\");\n\t\t});\n\t  }\n\t  inlineStyles(svg, svgClone);\n\t  const svgData = new XMLSerializer().serializeToString(svgClone);\n\t  document.body.removeChild(wrapper);\n\t  const svgBlob = new Blob([svgData], { type: \"image/svg+xml;charset=utf-8\" });\n\t  const url = URL.createObjectURL(svgBlob);\n\t  const img = new Image();\n\t  img.onload = function() {\n\t\tconst canvas = document.createElement(\"canvas\");\n\t\tcanvas.width = width;\n\t\tcanvas.height = height;\n\t\tconst ctx = canvas.getContext(\"2d\");\n\t\tctx.drawImage(img, 0, 0);\n\t\tconst dataUrl = canvas.toDataURL(\"image/png\");\n\t\tURL.revokeObjectURL(url);\n\t\tconst printWindow = window.open(\"\", \"_blank\");\n\t\tprintWindow.document.write(`\n\t\t  <html><head><title>Print Topology</title>\n\t\t  <style>@media print { @page { size: landscape; margin: 0; } body { margin: 0; } img { width: 100vw; height: 100vh; object-fit: contain; } }</style>\n\t\t  </head><body><img src=\"${dataUrl}\" onload=\"window.print(); window.close();\"></body></html>\n\t\t`);\n\t\tprintWindow.document.close();\n\t  };\n\t  img.onerror = function() {\n\t\tconsole.error(\"Failed to load SVG image for print\");\n\t\tshowAlert(t(\"dialogs.screenshotFailed\"));\n\t\tURL.revokeObjectURL(url);\n\t  };\n\t  img.src = url;\n\t}\n\tfunction exportJSONFile() {\n\t  const data = captureTheQuickening();\n\t  const jsonStr = JSON.stringify(data, null, 2);\n\t  const blob = new Blob([jsonStr], { type: \"application/json\" });\n\t  const url = URL.createObjectURL(blob);\n\t  const a = document.createElement(\"a\");\n\t  a.href = url;\n\t  const safeTitle = (PAGE_STATE.title || \"network-topology\").toLowerCase().replace(/[^a-z0-9\\-]+/g, \"-\");\n\t  a.download = `${safeTitle}.json`;\n\t  a.click();\n\t  URL.revokeObjectURL(url);\n\t  logAuditEvent(\"export\", `Exported JSON: ${a.download}`);\n\t}\n\tfunction exportCSV() {\n\t  const safeTitle = (PAGE_STATE.title || 'topology').toLowerCase().replace(/[^a-z0-9]+/g, '-');\n\t  const timestamp = new Date().toISOString();\n  const config = captureTheQuickening();\n\t  let csv = `#THEONEFILE_CONFIG:${JSON.stringify(config)}\\n`;\n\t  csv += `#\\n# ${PAGE_STATE.title || 'Network Topology'} - Node List\\n`;\n\t  csv += `# Exported from The One File on ${timestamp}\\n`;\n\t  const headers = ['name','ip','role','shape','tags','layer','mac','rackUnit','uHeight','assignedRack','rackCapacity','isRack','locked','groupId','x','y','size','notes','styles'];\n\t  csv += headers.join(',') + '\\n';\n\t  Object.entries(NODE_DATA).forEach(([id, node]) => {\n\t\tconst pos = savedPositions[id] || { x: 0, y: 0 };\n\t\tconst size = savedSizes[id] || 50;\n\t\tconst styles = savedStyles[id] ? JSON.stringify(savedStyles[id]) : '';\n\t\tconst row = [\n\t\t  csvEscape(node.name || ''), csvEscape(node.ip || ''), csvEscape(node.role || ''),\n\t\t  node.shape || 'circle', csvEscape((node.tags || []).join(';')), node.layer || 'layer1',\n\t\t  csvEscape(node.mac || ''), csvEscape(node.rackUnit || ''), node.uHeight || '1',\n\t\t  node.assignedRack || '', node.rackCapacity || '', node.isRack ? 'true' : 'false',\n\t\t  node.locked ? 'true' : 'false', node.groupId || '', Math.round(pos.x), Math.round(pos.y),\n\t\t  size, csvEscape((node.notes || []).join('|')), csvEscape(styles)\n\t\t];\n\t\tcsv += row.join(',') + '\\n';\n\t  });\n\t  const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });\n\t  const url = URL.createObjectURL(blob);\n\t  const a = document.createElement('a');\n\t  a.href = url;\n\t  a.download = `${safeTitle}.csv`;\n\t  a.click();\n\t  URL.revokeObjectURL(url);\n\t  logAuditEvent('export', `Exported CSV: ${a.download}`);\n\t}\n\tfunction csvEscape(val) {\n\t  if (val === null || val === undefined) return '';\n\t  const str = String(val);\n\t  if (str.includes(',') || str.includes('\"') || str.includes('\\n') || str.includes('\\r')) {\n\t\treturn '\"' + str.replace(/\"/g, '\"\"') + '\"';\n\t  }\n\t  return str;\n\t}\n\tdocument.getElementById('import-csv-file').addEventListener('change', async (e) => {\n\t  const file = e.target.files[0];\n\t  if (!file) return;\n\t  e.target.value = '';\n\t  try {\n\t\tconst text = await file.text();\n\t\tconst lines = text.split(/\\r?\\n/);\n\t\tlet config = null;\n\t\tlet dataLines = [];\n\t\tlet headers = null;\n\t\tfor (const line of lines) {\n\t\t  const trimmed = line.trim();\n\t\t  if (!trimmed) continue;\n\t\t  if (trimmed.startsWith('#THEONEFILE_CONFIG:')) {\n\t\t\ttry { config = JSON.parse(trimmed.substring(19)); } catch (err) { console.warn('Failed to parse CSV config:', err); }\n\t\t\tcontinue;\n\t\t  }\n\t\t  if (trimmed.startsWith('#')) continue;\n\t\t  if (!headers) { headers = parseCSVLine(trimmed).map(h => h.toLowerCase().trim()); continue; }\n\t\t  dataLines.push(trimmed);\n\t\t}\n\t\tif (!headers || dataLines.length === 0) { showAlert(t(\"dialogs.csvNoDataRows\")); return; }\n\t\tconst nameIdx = headers.indexOf('name');\n\t\tif (nameIdx === -1) { showAlert(t(\"dialogs.csvNeedsNameColumn\")); return; }\n\t\tconst nodes = dataLines.map(line => {\n\t\t  const values = parseCSVLine(line);\n\t\t  const node = {};\n\t\t  headers.forEach((h, idx) => { node[h] = values[idx] || ''; });\n\t\t  return node;\n\t\t});\n\t\tconst hasFullBackup = config && config.documentTabs;\n    const hasConfig = config && (config.pageState || config.page);\n    \n    let importMode = 'add'; \n    \n    if (hasFullBackup) {\n      const choice = await showConfirm(\n        t(\"dialogs.confirmImportCsvFull\", {\n          nodeCount: nodes.length,\n          tabCount: config.documentTabs?.length || 1,\n          edgeCount: config.edgeData?.list?.length || 0,\n          csvNodeCount: nodes.length\n        })\n      );\n      if (choice) {\n        importMode = 'full';\n      }\n    } else {\n      const settingsNote = hasConfig ? t(\"dialogs.csvSettingsRestore\") : t(\"dialogs.csvAddOnly\");\n      if (!await showConfirm(t(\"dialogs.confirmImportCsvAdd\", { count: nodes.length, settingsNote: settingsNote }))) return;\n    }\n    \n    pushUndo('import csv');\n    \n    if (importMode === 'full' && hasFullBackup) {\n      NODE_DATA = config.nodeData || {};\n      EDGE_DATA = config.edgeData || { list: [] };\n      EDGE_LEGEND = config.edgeLegend || {};\n      RECT_DATA = config.rectData || { list: [] };\n      TEXT_DATA = config.textData || { list: [] };\n      savedPositions = config.nodePositions || {};\n      savedSizes = config.nodeSizes || {};\n      savedStyles = config.nodeStyles || {};\n      if (config.page) {\n        PAGE_STATE = Object.assign({}, DEFAULT_PAGE_STATE, config.page);\n        wieldThePower();\n      }\n      if (config.canvas) {\n        canvasState.zoom = config.canvas.zoom || 1;\n        canvasState.panX = config.canvas.panX || 0;\n        canvasState.panY = config.canvas.panY || 0;\n      }\n      if (config.page?.title) {\n        document.title = config.page.title;\n        document.querySelector(\".editable-page-title\").textContent = config.page.title;\n      }\n      if (config.documentTabs) {\n        documentTabs = config.documentTabs;\n        currentTabIndex = config.currentTabIndex || 0;\n      }\n      if (config.savedStyleSets) {\n        savedStyleSets = config.savedStyleSets;\n      }\n      if (config.savedTopologyView) {\n        savedTopologyView = config.savedTopologyView;\n      }\n      if (config.encryptedSections) {\n        encryptedSections = config.encryptedSections;\n      }\n      if (config.auditLog && Array.isArray(config.auditLog)) {\n        auditLog = config.auditLog;\n        try {\n          localStorage.setItem(AUDIT_STORAGE_KEY, JSON.stringify(auditLog));\n        } catch (e) {\n          console.warn(\"Failed to sync audit log to localStorage:\", e);\n        }\n      }\n      if (config.recordings && Array.isArray(config.recordings)) {\n        RECORDING_STATE.recordings = config.recordings;\n        RECORDING_STATE.currentRecording = config.recordings[0] || null;\n      }\n      const layerMap = { \"physical\": \"layer1\", \"logical\": \"layer2\", \"security\": \"layer3\", \"application\": \"layer4\" };\n      Object.values(NODE_DATA).forEach(node => {\n        if (!node.tags) node.tags = [];\n        if (!node.notes) node.notes = [];\n        if (!node.layer || layerMap[node.layer]) node.layer = layerMap[node.layer] || 'layer1';\n      });\n      EDGE_DATA.list.forEach(edge => {\n        if (!edge.notes) edge.notes = [];\n      });\n      forgeTheTopology();\n      if (typeof forgeTheLegend === 'function') forgeTheLegend();\n      updateViewBox();\n      updateDrawToolbarVisibility();\n      updateTopologyToolbarVisibility();\n      logAuditEvent('import', `Imported CSV (full restore): ${file.name}`);\n      showAlert(t(\"dialogs.fullRestoreComplete\"));\n      return;\n    }\n\t\n\t\tif (hasConfig) {\n\t\t  Object.assign(PAGE_STATE, config.pageState || config.page);\n\t\t  if (config.canvasView || config.canvas) {\n\t\t\tcanvasState.zoom = (config.canvasView || config.canvas).zoom || 1;\n\t\t\tcanvasState.panX = (config.canvasView || config.canvas).panX || 0;\n\t\t\tcanvasState.panY = (config.canvasView || config.canvas).panY || 0;\n\t\t  }\n\t\t  if (config.legend || config.edgeLegend) Object.assign(EDGE_LEGEND, config.legend || config.edgeLegend);\n\t\t  wieldThePower();\n\t\t}\n\t\tlet gridX = 200, gridY = 200;\n\t\tconst spacing = 150;\n\t\tconst perRow = Math.ceil(Math.sqrt(nodes.length));\n\t\tlet gridIndex = 0;\n\t\tnodes.forEach((n) => {\n\t\t  let baseId = (n.name || 'node').toLowerCase().replace(/[^a-z0-9]+/g, '-');\n\t\t  if (!baseId) baseId = 'node';\n\t\t  let nodeId = baseId;\n\t\t  let counter = 1;\n\t\t  while (NODE_DATA[nodeId]) { nodeId = `${baseId}-${counter}`; counter++; }\n\t\t  NODE_DATA[nodeId] = {\n\t\t\tname: n.name || 'Unnamed', ip: n.ip || '', role: n.role || '', shape: n.shape || 'circle',\n\t\t\ttags: n.tags ? n.tags.split(';').map(t => t.trim()).filter(t => t) : [],\n\t\t\tnotes: n.notes ? n.notes.split('|').map(t => t.trim()).filter(t => t) : [],\n\t\t\tlayer: n.layer || 'layer1', mac: n.mac || '', rackUnit: n.rackunit || '',\n\t\t\tuHeight: n.uheight || '1', assignedRack: n.assignedrack || '', rackCapacity: n.rackcapacity || '',\n\t\t\tisRack: n.israck === 'true', locked: n.locked === 'true', groupId: n.groupid || null\n\t\t  };\n\t\t  const hasPosition = n.x && n.y && !isNaN(parseFloat(n.x)) && !isNaN(parseFloat(n.y));\n\t\t  if (hasPosition) {\n\t\t\tsavedPositions[nodeId] = { x: parseFloat(n.x), y: parseFloat(n.y) };\n\t\t  } else {\n\t\t\tconst row = Math.floor(gridIndex / perRow);\n\t\t\tconst col = gridIndex % perRow;\n\t\t\tsavedPositions[nodeId] = { x: gridX + col * spacing, y: gridY + row * spacing };\n\t\t\tgridIndex++;\n\t\t  }\n\t\t  if (n.size && !isNaN(parseFloat(n.size))) savedSizes[nodeId] = parseFloat(n.size);\n\t\t  if (n.styles) { try { savedStyles[nodeId] = JSON.parse(n.styles); } catch (err) {} }\n\t\t});\n\t\tforgeTheTopology();\n\t\tupdateViewBox();\n\t\tupdateDrawToolbarVisibility();\n\t\tupdateTopologyToolbarVisibility();\n\t\tlogAuditEvent('import', `Imported CSV: ${file.name} (${nodes.length} nodes)`);\n\t\tshowAlert(t(\"dialogs.importedNodesCount\", { count: nodes.length }));\n\t  } catch (err) {\n\t\tconsole.error('CSV import error:', err);\n\t\tshowAlert(t(\"dialogs.importCsvFailed\", { error: err.message }));\n\t  }\n\t});\n\tfunction parseCSVLine(line) {\n\t  const result = [];\n\t  let current = '';\n\t  let inQuotes = false;\n\t  for (let i = 0; i < line.length; i++) {\n\t\tconst char = line[i];\n\t\tif (char === '\"') {\n\t\t  if (inQuotes && line[i + 1] === '\"') { current += '\"'; i++; }\n\t\t  else { inQuotes = !inQuotes; }\n\t\t} else if (char === ',' && !inQuotes) { result.push(current); current = ''; }\n\t\telse { current += char; }\n\t  }\n\t  result.push(current);\n\t  return result;\n\t}\n\tfunction exportMarkdown() {\n  const safeTitle = (PAGE_STATE.title || 'topology').toLowerCase().replace(/[^a-z0-9]+/g, '-');\n  const timestamp = new Date().toISOString();\n  const title = PAGE_STATE.title || 'Network Topology';\n  const fullData = captureTheQuickening();\n  const config = fullData;\n\t  let md = `<!--THEONEFILE_CONFIG\\n${JSON.stringify(config, null, 2)}\\nTHEONEFILE_CONFIG-->\\n\\n`;\n\t  md += `# ${title}\\n\\n> Exported from The One File on ${timestamp}\\n\\n`;\n\t  md += `## Legend\\n\\n`;\n\t  if (Object.keys(EDGE_LEGEND).length > 0) {\n\t\tObject.entries(EDGE_LEGEND).forEach(([color, label]) => { md += `- ${color}: ${label}\\n`; });\n\t  } else { md += `_No legend entries_\\n`; }\n\t  md += '\\n## Nodes\\n\\n';\n\t  Object.entries(NODE_DATA).forEach(([id, node]) => {\n\t\tconst pos = savedPositions[id] || { x: 0, y: 0 };\n\t\tconst size = savedSizes[id] || 50;\n\t\tconst styles = savedStyles[id] || null;\n\t\tmd += `### ${id}\\n`;\n\t\tmd += `- **Name:** ${node.name || ''}\\n- **IP:** ${node.ip || ''}\\n- **Role:** ${node.role || ''}\\n`;\n\t\tmd += `- **Shape:** ${node.shape || 'circle'}\\n- **Tags:** ${(node.tags || []).join('; ') || '_none_'}\\n`;\n\t\tmd += `- **Layer:** ${node.layer || 'layer1'}\\n- **MAC:** ${node.mac || ''}\\n`;\n\t\tmd += `- **Rack Unit:** ${node.rackUnit || ''}\\n- **U Height:** ${node.uHeight || '1'}\\n`;\n\t\tmd += `- **Assigned Rack:** ${node.assignedRack || ''}\\n- **Rack Capacity:** ${node.rackCapacity || ''}\\n`;\n\t\tmd += `- **Is Rack:** ${node.isRack ? 'true' : 'false'}\\n- **Locked:** ${node.locked ? 'true' : 'false'}\\n`;\n\t\tmd += `- **Group ID:** ${node.groupId || ''}\\n- **Position:** ${Math.round(pos.x)}, ${Math.round(pos.y)}\\n- **Size:** ${size}\\n`;\n\t\tif (node.notes && node.notes.length > 0) { md += `- **Notes:**\\n`; node.notes.forEach(note => { md += `  - ${note.replace(/\\n/g, '<br>')}\\n`; }); }\n\t\tif (styles) { md += `- **Styles:** \\`${JSON.stringify(styles)}\\`\\n`; }\n\t\tmd += '\\n';\n\t  });\n\t  md += `## Connections\\n\\n`;\n\t  if (EDGE_DATA.list && EDGE_DATA.list.length > 0) {\n\t\tEDGE_DATA.list.forEach(edge => {\n\t\t  const fromPort = edge.fromPort ? ` (${edge.fromPort})` : '';\n\t\t  const toPort = edge.toPort ? ` (${edge.toPort})` : '';\n\t\t  md += `- ${edge.from}${fromPort} --> ${edge.to}${toPort}\\n`;\n\t\t  md += `  - **ID:** ${edge.id}\\n  - **Label:** ${(edge.label || '').replace(/\\n/g, '<br>')}\\n  - **Color:** ${edge.color || ''}\\n`;\n\t\t  md += `  - **Width:** ${edge.width || 4}\\n  - **Direction:** ${edge.direction || 'none'}\\n`;\n\t\t  md += `  - **Routing:** ${edge.routing || 'curved'}\\n  - **Type:** ${edge.type || 'main'}\\n`;\n\t\t  md += `  - **Line Style:** ${edge.lineStyle || 'solid'}\\n  - **Group ID:** ${edge.groupId || ''}\\n`;\n\t\t  if (edge.points && edge.points.length > 0) { md += `  - **Points:** ${edge.points.map(p => `${Math.round(p.x)},${Math.round(p.y)}`).join(' ')}\\n`; }\n\t\t  if (edge.notes && edge.notes.length > 0) { md += `  - **Notes:**\\n`; edge.notes.forEach(note => { md += `    - ${note.replace(/\\n/g, '<br>')}\\n`; }); }\n\t\t  md += '\\n';\n\t\t});\n\t  } else { md += `_No connections_\\n\\n`; }\n\t  md += `## Zones\\n\\n`;\n\t  if (RECT_DATA.list && RECT_DATA.list.length > 0) {\n\t\tRECT_DATA.list.forEach(rect => {\n\t\t  md += `### ${rect.id}\\n`;\n\t\t  md += `- **Position:** ${Math.round(rect.x)}, ${Math.round(rect.y)}\\n- **Size:** ${Math.round(rect.width)} x ${Math.round(rect.height)}\\n`;\n\t\t  md += `- **Color:** ${rect.color || ''}\\n- **Style:** ${rect.style || 'filled'}\\n- **Line Style:** ${rect.lineStyle || 'solid'}\\n`;\n\t\t  md += `- **Border Color:** ${rect.borderColor || ''}\\n- **Border Width:** ${rect.borderWidth !== undefined ? rect.borderWidth : 2}\\n`;\n\t\t  if (rect.notes && rect.notes.length > 0) { md += `- **Notes:**\\n`; rect.notes.forEach(note => { md += `  - ${note.replace(/\\n/g, '<br>')}\\n`; }); }\n\t\t  md += '\\n';\n\t\t});\n\t  } else { md += `_No zones_\\n\\n`; }\n\t  md += `## Text Labels\\n\\n`;\n\t  if (TEXT_DATA.list && TEXT_DATA.list.length > 0) {\n\t\tTEXT_DATA.list.forEach(text => {\n\t\t  md += `### ${text.id}\\n`;\n\t\t  md += `- **Content:** ${(text.content || '').replace(/\\n/g, '<br>')}\\n- **Position:** ${Math.round(text.x)}, ${Math.round(text.y)}\\n`;\n\t\t  md += `- **Font Size:** ${text.fontSize || 18}\\n- **Color:** ${text.color || '#e2e8f0'}\\n`;\n\t\t  md += `- **Font Weight:** ${text.fontWeight || 'normal'}\\n- **Font Style:** ${text.fontStyle || 'normal'}\\n`;\n\t\t  md += `- **Text Align:** ${text.textAlign || 'start'}\\n- **Text Decoration:** ${text.textDecoration || 'none'}\\n`;\n\t\t  md += `- **Background Color:** ${text.bgColor || '#000000'}\\n- **Background Enabled:** ${text.bgEnabled ? 'true' : 'false'}\\n`;\n\t\t  md += `- **Opacity:** ${text.opacity !== undefined ? text.opacity : 1}\\n\\n`;\n\t\t});\n\t  } else { md += `_No text labels_\\n\\n`; }\n\t  const blob = new Blob([md], { type: 'text/markdown;charset=utf-8;' });\n\t  const url = URL.createObjectURL(blob);\n\t  const a = document.createElement('a');\n\t  a.href = url;\n\t  a.download = `${safeTitle}.md`;\n\t  a.click();\n\t  URL.revokeObjectURL(url);\n\t  logAuditEvent('export', `Exported Markdown: ${a.download}`);\n\t}\n\t\tdocument.getElementById('import-markdown-file').addEventListener('change', async (e) => {\n\t\t  const file = e.target.files[0];\n\t\t  if (!file) return;\n\t\t  e.target.value = '';\n\t\t  try {\n    const text = (await file.text()).replace(/\\r\\n?/g, '\\n');\n    let config = null;\n    const configMatch = text.match(/<!--THEONEFILE_CONFIG\\s*([\\s\\S]*?)\\s*THEONEFILE_CONFIG-->/);\n    if (configMatch) { try { config = JSON.parse(configMatch[1].trim()); } catch (err) { console.warn('Failed to parse Markdown config:', err); } }\n    const nodes = {}, positions = {}, sizes = {}, styles = {}, edges = [], rects = [], texts = [], legend = {};\n    const sections = text.split(/^## /m);\n    sections.forEach(section => {\n      const lines = section.trim().split('\\n');\n      const sectionTitle = lines[0]?.trim().toLowerCase();\n      if (sectionTitle === 'legend') {\n        lines.slice(1).forEach(line => {\n          const match = line.match(/^- (#[a-fA-F0-9]{3,8}):\\s*(.+)$/);\n          if (match) legend[match[1]] = match[2].trim();\n        });\n      } else if (sectionTitle === 'nodes') {\n        let currentNodeId = null, currentNode = null, inNotes = false;\n        lines.slice(1).forEach(line => {\n          const nodeMatch = line.match(/^### (.+)$/);\n          if (nodeMatch) {\n            if (currentNodeId && currentNode) nodes[currentNodeId] = currentNode;\n            currentNodeId = nodeMatch[1].trim();\n            currentNode = { tags: [], notes: [] };\n            inNotes = false;\n            return;\n          }\n          if (!currentNode) return;\n          if (inNotes && line.match(/^\\s+-\\s+(.+)$/)) {\n            const noteMatch = line.match(/^\\s+-\\s+(.+)$/);\n            if (noteMatch) currentNode.notes.push(noteMatch[1].trim().replace(/<br>/g, '\\n'));\n            return;\n          }\n          inNotes = false;\n          const propMatch = line.match(/^- \\*\\*(.+?):\\*\\*\\s*(.*)$/);\n          if (propMatch) {\n            const key = propMatch[1].trim().toLowerCase(), val = propMatch[2].trim();\n            switch (key) {\n              case 'name': currentNode.name = val; break;\n              case 'ip': currentNode.ip = val; break;\n              case 'role': currentNode.role = val; break;\n              case 'shape': currentNode.shape = val; break;\n              case 'tags': currentNode.tags = val === '_none_' ? [] : val.split(';').map(t => t.trim()).filter(t => t); break;\n              case 'layer': currentNode.layer = val; break;\n              case 'mac': currentNode.mac = val; break;\n              case 'rack unit': currentNode.rackUnit = val; break;\n              case 'u height': currentNode.uHeight = val; break;\n              case 'assigned rack': currentNode.assignedRack = val; break;\n              case 'rack capacity': currentNode.rackCapacity = val; break;\n              case 'is rack': currentNode.isRack = val === 'true'; break;\n              case 'locked': currentNode.locked = val === 'true'; break;\n              case 'group id': currentNode.groupId = val || null; break;\n              case 'position':\n                const posMatch = val.match(/(-?\\d+),\\s*(-?\\d+)/);\n                if (posMatch) positions[currentNodeId] = { x: parseFloat(posMatch[1]), y: parseFloat(posMatch[2]) };\n                break;\n              case 'size': sizes[currentNodeId] = parseFloat(val) || 50; break;\n              case 'notes': inNotes = true; break;\n              case 'styles':\n                const stylesMatch = val.match(/`(.+)`/);\n                if (stylesMatch) { try { styles[currentNodeId] = JSON.parse(stylesMatch[1]); } catch (err) {} }\n                break;\n            }\n          }\n        });\n        if (currentNodeId && currentNode) nodes[currentNodeId] = currentNode;\n         } else if (sectionTitle === 'connections') {\n        let currentEdge = null, inNotes = false;\n        lines.slice(1).forEach(line => {\n          const connMatch = line.match(/^- (.+?)\\s*(?:\\(([^)]*)\\))?\\s*-->\\s*(.+?)\\s*(?:\\(([^)]*)\\))?$/);\n          if (connMatch) {\n            if (currentEdge) edges.push(currentEdge);\n            currentEdge = { from: connMatch[1].trim(), to: connMatch[3].trim(), fromPort: connMatch[2] || '', toPort: connMatch[4] || '', notes: [] };\n            inNotes = false;\n            return;\n          }\n          if (!currentEdge) return;\n          if (inNotes && line.match(/^\\s+-\\s+(.+)$/)) {\n            const noteMatch = line.match(/^\\s+-\\s+(.+)$/);\n            if (noteMatch) currentEdge.notes.push(noteMatch[1].trim().replace(/<br>/g, '\\n'));\n            return;\n          }\n          inNotes = false;\n          const propMatch = line.match(/^\\s+- \\*\\*(.+?):\\*\\*\\s*(.*)$/);\n          if (propMatch) {\n            const key = propMatch[1].trim().toLowerCase(), val = propMatch[2].trim();\n            switch (key) {\n              case 'id': currentEdge.id = val; break;\n              case 'label': currentEdge.label = val.replace(/<br>/g, '\\n'); break;\n              case 'color': currentEdge.color = val; break;\n              case 'width': currentEdge.width = parseFloat(val) || 4; break;\n              case 'direction': currentEdge.direction = val; break;\n              case 'routing': currentEdge.routing = val; break;\n              case 'type': currentEdge.type = val; break;\n              case 'line style': currentEdge.lineStyle = val; break;\n              case 'group id': currentEdge.groupId = val || null; break;\n              case 'points':\n                currentEdge.points = val.split(' ').map(p => { const [x, y] = p.split(',').map(Number); return { x, y }; }).filter(p => !isNaN(p.x) && !isNaN(p.y));\n                break;\n              case 'notes': inNotes = true; break;\n            }\n          }\n        });\n        if (currentEdge) edges.push(currentEdge);\n      } else if (sectionTitle === 'zones') {\n        let currentRect = null, inNotes = false;\n        lines.slice(1).forEach(line => {\n          const rectMatch = line.match(/^### (.+)$/);\n          if (rectMatch) {\n            if (currentRect) rects.push(currentRect);\n            currentRect = { id: rectMatch[1].trim(), notes: [] };\n            inNotes = false;\n            return;\n          }\n          if (!currentRect) return;\n          if (inNotes && line.match(/^\\s+-\\s+(.+)$/)) {\n            const noteMatch = line.match(/^\\s+-\\s+(.+)$/);\n            if (noteMatch) currentRect.notes.push(noteMatch[1].trim().replace(/<br>/g, '\\n'));\n            return;\n          }\n          inNotes = false;\n          const propMatch = line.match(/^- \\*\\*(.+?):\\*\\*\\s*(.*)$/);\n          if (propMatch) {\n            const key = propMatch[1].trim().toLowerCase(), val = propMatch[2].trim();\n            switch (key) {\n              case 'position': const posMatch = val.match(/(-?\\d+),\\s*(-?\\d+)/); if (posMatch) { currentRect.x = parseFloat(posMatch[1]); currentRect.y = parseFloat(posMatch[2]); } break;\n              case 'size': const sizeMatch = val.match(/(\\d+)\\s*x\\s*(\\d+)/); if (sizeMatch) { currentRect.width = parseFloat(sizeMatch[1]); currentRect.height = parseFloat(sizeMatch[2]); } break;\n              case 'color': currentRect.color = val; break;\n              case 'style': currentRect.style = val; break;\n              case 'line style': currentRect.lineStyle = val; break;\n              case 'border color': currentRect.borderColor = val; break;\n              case 'border width': currentRect.borderWidth = parseFloat(val) || 2; break;\n              case 'notes': inNotes = true; break;\n            }\n          }\n        });\n        if (currentRect) rects.push(currentRect);\n      } else if (sectionTitle === 'text labels') {\n        let currentText = null;\n        lines.slice(1).forEach(line => {\n          const textMatch = line.match(/^### (.+)$/);\n          if (textMatch) {\n            if (currentText) texts.push(currentText);\n            currentText = { id: textMatch[1].trim() };\n            return;\n          }\n          if (!currentText) return;\n          const propMatch = line.match(/^- \\*\\*(.+?):\\*\\*\\s*(.*)$/);\n          if (propMatch) {\n            const key = propMatch[1].trim().toLowerCase(), val = propMatch[2].trim();\n            switch (key) {\n              case 'content': currentText.content = val.replace(/<br>/g, '\\n'); break;\n              case 'position': const posMatch = val.match(/(-?\\d+),\\s*(-?\\d+)/); if (posMatch) { currentText.x = parseFloat(posMatch[1]); currentText.y = parseFloat(posMatch[2]); } break;\n              case 'font size': currentText.fontSize = parseFloat(val) || 18; break;\n              case 'color': currentText.color = val; break;\n              case 'font weight': currentText.fontWeight = val; break;\n              case 'font style': currentText.fontStyle = val; break;\n              case 'text align': currentText.textAlign = val; break;\n              case 'text decoration': currentText.textDecoration = val; break;\n              case 'background color': currentText.bgColor = val; break;\n              case 'background enabled': currentText.bgEnabled = val === 'true'; break;\n              case 'opacity': currentText.opacity = parseFloat(val) || 1; break;\n            }\n          }\n        });\n        if (currentText) texts.push(currentText);\n      }\n    });\n    const nodeCount = Object.keys(nodes).length, edgeCount = edges.length, rectCount = rects.length, textCount = texts.length;\n    if (nodeCount === 0 && edgeCount === 0 && rectCount === 0 && textCount === 0) {\n      showAlert(t(\"dialogs.noValidTopologyInMarkdown\"));\n      return;\n    }\n    if (!await showConfirm(t(\"dialogs.confirmImportMarkdown\", { nodeCount: nodeCount, edgeCount: edgeCount, rectCount: rectCount, textCount: textCount }))) return;\n    pushUndo('import markdown');\n    if (config && config.documentTabs) {\n      if (config.page) { Object.assign(PAGE_STATE, config.page); wieldThePower(); }\n      if (config.canvas) {\n        canvasState.zoom = config.canvas.zoom || 1;\n        canvasState.panX = config.canvas.panX || 0;\n        canvasState.panY = config.canvas.panY || 0;\n      }\n      if (config.documentTabs) {\n        documentTabs = config.documentTabs;\n        currentTabIndex = config.currentTabIndex || 0;\n      }\n      if (config.savedTopologyView) {\n        savedTopologyView = config.savedTopologyView;\n      }\n      if (config.encryptedSections) {\n        encryptedSections = config.encryptedSections;\n      }\n      if (config.auditLog && Array.isArray(config.auditLog)) {\n        auditLog = config.auditLog;\n        try {\n          localStorage.setItem(AUDIT_STORAGE_KEY, JSON.stringify(auditLog));\n        } catch (e) {\n          console.warn(\"Failed to sync audit log to localStorage:\", e);\n        }\n      }\n      if (config.recordings && Array.isArray(config.recordings)) {\n        RECORDING_STATE.recordings = config.recordings;\n        RECORDING_STATE.currentRecording = config.recordings[0] || null;\n      }\n    } else if (config && config.pageState) {\n      Object.assign(PAGE_STATE, config.pageState);\n      wieldThePower();\n      if (config.canvasView) {\n        canvasState.zoom = config.canvasView.zoom || 1;\n        canvasState.panX = config.canvasView.panX || 0;\n        canvasState.panY = config.canvasView.panY || 0;\n      }\n    }\n    Object.keys(NODE_DATA).forEach(k => delete NODE_DATA[k]);\n    Object.keys(savedPositions).forEach(k => delete savedPositions[k]);\n    Object.keys(savedSizes).forEach(k => delete savedSizes[k]);\n    Object.keys(savedStyles).forEach(k => delete savedStyles[k]);\n    EDGE_DATA.list = [];\n    RECT_DATA.list = [];\n    TEXT_DATA.list = [];\n    Object.keys(EDGE_LEGEND).forEach(k => delete EDGE_LEGEND[k]);\n    Object.assign(EDGE_LEGEND, legend);\n    Object.entries(nodes).forEach(([id, node]) => {\n      NODE_DATA[id] = { name: node.name || '', ip: node.ip || '', role: node.role || '', shape: node.shape || 'circle', tags: node.tags || [], notes: node.notes || [], mac: node.mac || '', rackUnit: node.rackUnit || '', uHeight: node.uHeight || '1', layer: node.layer || 'layer1', assignedRack: node.assignedRack || '', rackCapacity: node.rackCapacity || '', isRack: node.isRack || false, locked: node.locked || false, groupId: node.groupId || null };\n      savedPositions[id] = positions[id] || { x: 500, y: 300 };\n      if (sizes[id]) savedSizes[id] = sizes[id];\n      if (styles[id]) savedStyles[id] = styles[id];\n    });\n    let skippedEdges = 0;\n    edges.forEach(edge => {\n      if (!NODE_DATA[edge.from] || !NODE_DATA[edge.to]) {\n        console.warn(`Skipping orphan edge: ${edge.from} -> ${edge.to}`);\n        skippedEdges++;\n        return;\n      }\n      EDGE_DATA.list.push({ id: edge.id || `edge-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, from: edge.from, to: edge.to, fromPort: edge.fromPort || '', toPort: edge.toPort || '', label: edge.label || '', color: edge.color || '', width: edge.width || 4, direction: edge.direction || 'none', routing: edge.routing || 'curved', type: edge.type || 'main', lineStyle: edge.lineStyle || 'solid', groupId: edge.groupId || null, points: edge.points || [], notes: edge.notes || [], waypoints: edge.waypoints || [] });\n    });\n    if (skippedEdges > 0) console.warn(`Total skipped orphan edges: ${skippedEdges}`);\n    rects.forEach(rect => {\n      RECT_DATA.list.push({ id: rect.id, x: rect.x || 0, y: rect.y || 0, width: rect.width || 100, height: rect.height || 100, color: rect.color || '#f97316', style: rect.style || 'filled', lineStyle: rect.lineStyle || 'solid', borderColor: rect.borderColor || '', borderWidth: rect.borderWidth !== undefined ? rect.borderWidth : 2, notes: rect.notes || [] });\n    });\n    texts.forEach(text => {\n      TEXT_DATA.list.push({ id: text.id, x: text.x || 0, y: text.y || 0, content: text.content || '', fontSize: text.fontSize || 18, color: text.color || '#e2e8f0', fontWeight: text.fontWeight || 'normal', fontStyle: text.fontStyle || 'normal', textAlign: text.textAlign || 'start', textDecoration: text.textDecoration || 'none', bgColor: text.bgColor || '#000000', bgEnabled: text.bgEnabled || false, opacity: text.opacity !== undefined ? text.opacity : 1 });\n    });\n    forgeTheTopology();\n    if (typeof forgeTheLegend === 'function') forgeTheLegend();\n    updateViewBox();\n    updateDrawToolbarVisibility();\n    updateTopologyToolbarVisibility();\n    logAuditEvent('import', `Imported Markdown: ${file.name} (${nodeCount} nodes, ${edgeCount} connections)`);\n    showAlert(t(\"dialogs.markdownImportSuccess\", { nodeCount, edgeCount, rectCount, textCount }));\n     } catch (err) {\n    console.error('Markdown import error:', err);\n    showAlert(t(\"dialogs.importMarkdownFailed\", { error: err.message }));\n     }\n\t});\n\tdocument.getElementById('import-json-file').addEventListener('change', async (e) => {\n\t  const file = e.target.files[0];\n\t  if (!file) return;\n\t  e.target.value = '';\n\t  const existingInput = document.getElementById('import-data-file');\n\t  if (existingInput) {\n\t\tconst dt = new DataTransfer();\n\t\tdt.items.add(file);\n\t\texistingInput.files = dt.files;\n\t\texistingInput.dispatchEvent(new Event('change'));\n\t  }\n\t});\n\tdocument.getElementById('import-html-file')?.addEventListener('change', async (e) => {\n\t  const file = e.target.files[0];\n\t  if (!file) return;\n\t  e.target.value = '';\n\t  try {\n\t\tconst text = await file.text();\n\t\tconst parser = new DOMParser();\n\t\tconst doc = parser.parseFromString(text, 'text/html');\n\t\tconst stateScript = doc.getElementById('topology-state');\n\t\tif (!stateScript) {\n\t\t  showAlert(t(\"dialogs.invalidHtmlFile\"));\n\t\t  return;\n\t\t}\n\t\tconst data = JSON.parse(stateScript.textContent);\n\t\tif (!data.nodeData || !data.edgeData) {\n\t\t  showAlert(t(\"dialogs.invalidHtmlFile\"));\n\t\t  return;\n\t\t}\n\t\tconst nodeCount = Object.keys(data.nodeData).length;\n\t\tconst edgeCount = data.edgeData.list?.length || 0;\n\t\tconst tabCount = data.documentTabs?.length || 1;\n\t\tif (!await showConfirm(t(\"dialogs.confirmImportHtml\", { nodeCount, edgeCount, tabCount }))) {\n\t\t  return;\n\t\t}\n\t\tpushUndo('import html');\n\t\tNODE_DATA = data.nodeData || {};\n\t\tEDGE_DATA = data.edgeData || { list: [] };\n\t\tEDGE_LEGEND = data.edgeLegend || {};\n\t\tRECT_DATA = data.rectData || { list: [] };\n\t\tTEXT_DATA = data.textData || { list: [] };\n\t\tIMAGE_DATA = data.imageData || { list: [] };\n\t\tsavedPositions = data.nodePositions || {};\n\t\tsavedSizes = data.nodeSizes || {};\n\t\tsavedStyles = data.nodeStyles || {};\n\t\tif (data.page) {\n\t\t  PAGE_STATE = Object.assign({}, DEFAULT_PAGE_STATE, data.page);\n\t\t  wieldThePower();\n\t\t}\n\t\tif (data.canvas) {\n\t\t  canvasState.zoom = data.canvas.zoom || 1;\n\t\t  canvasState.panX = data.canvas.panX || 0;\n\t\t  canvasState.panY = data.canvas.panY || 0;\n\t\t}\n\t\tif (data.page?.title) {\n\t\t  document.title = data.page.title;\n\t\t  document.querySelector(\".editable-page-title\").textContent = data.page.title;\n\t\t}\n\t\tif (data.savedStyleSets) {\n\t\t  savedStyleSets = data.savedStyleSets;\n\t\t}\n\t\tif (data.documentTabs) {\n\t\t  documentTabs = data.documentTabs;\n\t\t  currentTabIndex = data.currentTabIndex || 0;\n\t\t}\n\t\tif (data.savedTopologyView) {\n\t\t  savedTopologyView = data.savedTopologyView;\n\t\t}\n\t\tif (data.encryptedSections) {\n\t\t  encryptedSections = data.encryptedSections;\n\t\t}\n\t\tif (data.auditLog && Array.isArray(data.auditLog)) {\n\t\t  auditLog = data.auditLog;\n\t\t  try {\n\t\t\tlocalStorage.setItem(AUDIT_STORAGE_KEY, JSON.stringify(auditLog));\n\t\t  } catch (err) {\n\t\t\tconsole.warn(\"Failed to sync audit log to localStorage:\", err);\n\t\t  }\n\t\t}\n\t\tif (data.customLanguage) {\n\t\t  CUSTOM_LANG = data.customLanguage;\n\t\t  LANG = deepMerge(DEFAULT_LANG, CUSTOM_LANG);\n\t\t  saveLanguageToStorage();\n\t\t  applyLanguage();\n\t\t  updateCurrentLangDisplay();\n\t\t}\n\t\tif (data.recordings && Array.isArray(data.recordings)) {\n\t\t  RECORDING_STATE.recordings = data.recordings;\n\t\t  RECORDING_STATE.currentRecording = data.recordings[0] || null;\n\t\t}\n\t\tconst layerMap = { \"physical\": \"layer1\", \"logical\": \"layer2\", \"security\": \"layer3\", \"application\": \"layer4\" };\n\t\tObject.values(NODE_DATA).forEach(node => {\n\t\t  if (!node.tags) node.tags = [];\n\t\t  if (!node.notes) node.notes = [];\n\t\t  if (!node.layer || layerMap[node.layer]) node.layer = layerMap[node.layer] || 'layer1';\n\t\t});\n\t\tEDGE_DATA.list.forEach(edge => {\n\t\t  if (!edge.notes) edge.notes = [];\n\t\t});\n\t\tforgeTheTopology();\n\t\tif (typeof forgeTheLegend === 'function') forgeTheLegend();\n\t\tlogAuditEvent(\"import\", `Imported HTML: ${file.name} (${nodeCount} nodes, ${edgeCount} connections)`);\n\t\tupdateViewBox();\n\t\tconst nodeIds = Object.keys(NODE_DATA);\n\t\tif (nodeIds.length > 0) {\n\t\t  claimTheImmortal(nodeIds[0]);\n\t\t} else {\n\t\t  document.getElementById(\"node-panel\").style.display = \"none\";\n\t\t  document.getElementById(\"edge-panel\").style.display = \"none\";\n\t\t  document.getElementById(\"text-panel\").style.display = \"none\";\n\t\t  document.getElementById(\"rect-panel\").style.display = \"none\";\n\t\t  document.getElementById(\"image-panel\").style.display = \"none\";\n\t\t  document.getElementById(\"topology-toolbar\").style.display = \"none\";\n\t\t}\n\t\tupdateDrawToolbarVisibility();\n\t\tshowAlert(t(\"dialogs.htmlImportSuccess\", { nodeCount, edgeCount }));\n\t  } catch (err) {\n\t\tconsole.error(\"HTML import error:\", err);\n\t\tshowAlert(t(\"dialogs.invalidHtmlFile\"));\n\t  }\n\t});\n\tdocument.getElementById('mobile-export-btn')?.addEventListener('click', () => {\n\t  document.getElementById('mobile-export-modal').classList.add('active');\n\t  document.getElementById('topbar-menu').classList.remove('open');\n\t});\n\tdocument.getElementById('mobile-import-btn')?.addEventListener('click', () => {\n\t  document.getElementById('mobile-import-modal').classList.add('active');\n\t  document.getElementById('topbar-menu').classList.remove('open');\n\t});\n\tdocument.getElementById('mobile-export-modal')?.addEventListener('click', (e) => {\n\t  if (e.target.id === 'mobile-export-modal') e.target.classList.remove('active');\n\t});\n\tdocument.getElementById('mobile-import-modal')?.addEventListener('click', (e) => {\n\t  if (e.target.id === 'mobile-import-modal') e.target.classList.remove('active');\n\t});\n\n\tfunction renderCanvasTemplate(svg, ns, template, width, height, padding, templateColor) {\n\t  const color = (templateColor || PAGE_STATE.canvasGrid || \"#475569\") + \"66\";\n\t  const majorColor = (templateColor || PAGE_STATE.canvasGrid || \"#475569\") + \"99\";\n\t  const w = width - padding * 2;\n\t  const h = height - padding * 2;\n\t  const cx = padding + w / 2;\n\t  const cy = padding + h / 2;\n\t  const templateGroup = document.createElementNS(ns, \"g\");\n\t  templateGroup.id = \"canvas-template\";\n\n\t  switch (template) {\n\t    case \"dots\": {\n\t      const spacing = PAGE_STATE.canvasGridSize || 50;\n\t      for (let x = padding; x <= width - padding; x += spacing) {\n\t        for (let y = padding; y <= height - padding; y += spacing) {\n\t          const dot = document.createElementNS(ns, \"circle\");\n\t          dot.setAttribute(\"cx\", x);\n\t          dot.setAttribute(\"cy\", y);\n\t          dot.setAttribute(\"r\", 2);\n\t          dot.setAttribute(\"fill\", color);\n\t          templateGroup.appendChild(dot);\n\t        }\n\t      }\n\t      break;\n\t    }\n\t    case \"blueprint\": {\n\t      const spacing = 25;\n\t      const majorSpacing = 100;\n\t      for (let x = padding; x <= width - padding; x += spacing) {\n\t        const line = document.createElementNS(ns, \"line\");\n\t        line.setAttribute(\"x1\", x);\n\t        line.setAttribute(\"y1\", padding);\n\t        line.setAttribute(\"x2\", x);\n\t        line.setAttribute(\"y2\", height - padding);\n\t        line.setAttribute(\"stroke\", (x - padding) % majorSpacing === 0 ? majorColor : color);\n\t        line.setAttribute(\"stroke-width\", (x - padding) % majorSpacing === 0 ? \"1.5\" : \"0.5\");\n\t        templateGroup.appendChild(line);\n\t      }\n\t      for (let y = padding; y <= height - padding; y += spacing) {\n\t        const line = document.createElementNS(ns, \"line\");\n\t        line.setAttribute(\"x1\", padding);\n\t        line.setAttribute(\"y1\", y);\n\t        line.setAttribute(\"x2\", width - padding);\n\t        line.setAttribute(\"y2\", y);\n\t        line.setAttribute(\"stroke\", (y - padding) % majorSpacing === 0 ? majorColor : color);\n\t        line.setAttribute(\"stroke-width\", (y - padding) % majorSpacing === 0 ? \"1.5\" : \"0.5\");\n\t        templateGroup.appendChild(line);\n\t      }\n\n\t      const border = document.createElementNS(ns, \"rect\");\n\t      border.setAttribute(\"x\", padding + 20);\n\t      border.setAttribute(\"y\", padding + 20);\n\t      border.setAttribute(\"width\", w - 40);\n\t      border.setAttribute(\"height\", h - 40);\n\t      border.setAttribute(\"fill\", \"none\");\n\t      border.setAttribute(\"stroke\", majorColor);\n\t      border.setAttribute(\"stroke-width\", \"2\");\n\t      templateGroup.appendChild(border);\n\t      break;\n\t    }\n\t    case \"basketball\": {\n\t      const courtW = Math.min(w * 0.9, h * 0.9 * 1.88);\n\t      const courtH = courtW / 1.88;\n\t      const courtX = cx - courtW / 2;\n\t      const courtY = cy - courtH / 2;\n\t      const court = document.createElementNS(ns, \"rect\");\n\t      court.setAttribute(\"x\", courtX);\n\t      court.setAttribute(\"y\", courtY);\n\t      court.setAttribute(\"width\", courtW);\n\t      court.setAttribute(\"height\", courtH);\n\t      court.setAttribute(\"fill\", \"none\");\n\t      court.setAttribute(\"stroke\", majorColor);\n\t      court.setAttribute(\"stroke-width\", \"3\");\n\t      templateGroup.appendChild(court);\n\t      const centerCircle = document.createElementNS(ns, \"circle\");\n\t      centerCircle.setAttribute(\"cx\", cx);\n\t      centerCircle.setAttribute(\"cy\", cy);\n\t      centerCircle.setAttribute(\"r\", courtH * 0.12);\n\t      centerCircle.setAttribute(\"fill\", \"none\");\n\t      centerCircle.setAttribute(\"stroke\", color);\n\t      centerCircle.setAttribute(\"stroke-width\", \"2\");\n\t      templateGroup.appendChild(centerCircle);\n\t      const centerLine = document.createElementNS(ns, \"line\");\n\t      centerLine.setAttribute(\"x1\", cx);\n\t      centerLine.setAttribute(\"y1\", courtY);\n\t      centerLine.setAttribute(\"x2\", cx);\n\t      centerLine.setAttribute(\"y2\", courtY + courtH);\n\t      centerLine.setAttribute(\"stroke\", color);\n\t      centerLine.setAttribute(\"stroke-width\", \"2\");\n\t      templateGroup.appendChild(centerLine);\n\t      const keyW = courtW * 0.19;\n\t      const keyH = courtH * 0.38;\n\t      [courtX, courtX + courtW - keyW].forEach(kx => {\n\t        const key = document.createElementNS(ns, \"rect\");\n\t        key.setAttribute(\"x\", kx);\n\t        key.setAttribute(\"y\", cy - keyH / 2);\n\t        key.setAttribute(\"width\", keyW);\n\t        key.setAttribute(\"height\", keyH);\n\t        key.setAttribute(\"fill\", \"none\");\n\t        key.setAttribute(\"stroke\", color);\n\t        key.setAttribute(\"stroke-width\", \"2\");\n\t        templateGroup.appendChild(key);\n\t        const ftCircle = document.createElementNS(ns, \"circle\");\n\t        ftCircle.setAttribute(\"cx\", kx === courtX ? kx + keyW : kx);\n\t        ftCircle.setAttribute(\"cy\", cy);\n\t        ftCircle.setAttribute(\"r\", keyH * 0.32);\n\t        ftCircle.setAttribute(\"fill\", \"none\");\n\t        ftCircle.setAttribute(\"stroke\", color);\n\t        ftCircle.setAttribute(\"stroke-width\", \"2\");\n\t        templateGroup.appendChild(ftCircle);\n\t      });\n\t      const arcRadius = courtH * 0.48;\n\t      const leftArc = document.createElementNS(ns, \"path\");\n\t      const leftArcX = courtX + courtW * 0.055;\n\t      leftArc.setAttribute(\"d\", `M ${courtX} ${courtY + courtH * 0.06} L ${courtX} ${cy - arcRadius} A ${arcRadius} ${arcRadius} 0 0 1 ${courtX} ${cy + arcRadius} L ${courtX} ${courtY + courtH * 0.94}`);\n\t      leftArc.setAttribute(\"fill\", \"none\");\n\t      leftArc.setAttribute(\"stroke\", color);\n\t      leftArc.setAttribute(\"stroke-width\", \"2\");\n\t      const leftCenterX = courtX + courtW * 0.055;\n\t      leftArc.setAttribute(\"d\", `M ${courtX} ${courtY + courtH * 0.06} L ${leftCenterX} ${cy - arcRadius} A ${arcRadius} ${arcRadius} 0 0 1 ${leftCenterX} ${cy + arcRadius} L ${courtX} ${courtY + courtH * 0.94}`);\n\t      templateGroup.appendChild(leftArc);\n\t      const rightArc = document.createElementNS(ns, \"path\");\n\t      const rightCenterX = courtX + courtW * 0.945;\n\t      rightArc.setAttribute(\"d\", `M ${courtX + courtW} ${courtY + courtH * 0.06} L ${rightCenterX} ${cy - arcRadius} A ${arcRadius} ${arcRadius} 0 0 0 ${rightCenterX} ${cy + arcRadius} L ${courtX + courtW} ${courtY + courtH * 0.94}`);\n\t      rightArc.setAttribute(\"fill\", \"none\");\n\t      rightArc.setAttribute(\"stroke\", color);\n\t      rightArc.setAttribute(\"stroke-width\", \"2\");\n\t      templateGroup.appendChild(rightArc);\n\t      break;\n\t    }\n\t    case \"football\": {\n\t      const fieldW = Math.min(w * 0.9, h * 0.9 * 2.25);\n\t      const fieldH = fieldW / 2.25;\n\t      const fieldX = cx - fieldW / 2;\n\t      const fieldY = cy - fieldH / 2;\n\t      const field = document.createElementNS(ns, \"rect\");\n\t      field.setAttribute(\"x\", fieldX);\n\t      field.setAttribute(\"y\", fieldY);\n\t      field.setAttribute(\"width\", fieldW);\n\t      field.setAttribute(\"height\", fieldH);\n\t      field.setAttribute(\"fill\", \"none\");\n\t      field.setAttribute(\"stroke\", majorColor);\n\t      field.setAttribute(\"stroke-width\", \"3\");\n\t      templateGroup.appendChild(field);\n\t      const endZoneW = fieldW / 12;\n\t      [fieldX, fieldX + fieldW - endZoneW].forEach(ex => {\n\t        const ez = document.createElementNS(ns, \"rect\");\n\t        ez.setAttribute(\"x\", ex);\n\t        ez.setAttribute(\"y\", fieldY);\n\t        ez.setAttribute(\"width\", endZoneW);\n\t        ez.setAttribute(\"height\", fieldH);\n\t        ez.setAttribute(\"fill\", color);\n\t        ez.setAttribute(\"fill-opacity\", \"0.2\");\n\t        ez.setAttribute(\"stroke\", color);\n\t        ez.setAttribute(\"stroke-width\", \"2\");\n\t        templateGroup.appendChild(ez);\n\t      });\n\t      const playFieldW = fieldW - 2 * endZoneW;\n\t      for (let i = 1; i < 10; i++) {\n\t        const x = fieldX + endZoneW + (playFieldW * i / 10);\n\t        const line = document.createElementNS(ns, \"line\");\n\t        line.setAttribute(\"x1\", x);\n\t        line.setAttribute(\"y1\", fieldY);\n\t        line.setAttribute(\"x2\", x);\n\t        line.setAttribute(\"y2\", fieldY + fieldH);\n\t        line.setAttribute(\"stroke\", color);\n\t        line.setAttribute(\"stroke-width\", i === 5 ? \"3\" : \"1.5\");\n\t        templateGroup.appendChild(line);\n\t      }\n\t      const hashY1 = fieldY + fieldH * 0.3;\n\t      const hashY2 = fieldY + fieldH * 0.7;\n\t      for (let i = 0; i <= 100; i += 5) {\n\t        const x = fieldX + endZoneW + (playFieldW * i / 100);\n\t        [hashY1, hashY2].forEach(hy => {\n\t          const hash = document.createElementNS(ns, \"line\");\n\t          hash.setAttribute(\"x1\", x - 5);\n\t          hash.setAttribute(\"y1\", hy);\n\t          hash.setAttribute(\"x2\", x + 5);\n\t          hash.setAttribute(\"y2\", hy);\n\t          hash.setAttribute(\"stroke\", color);\n\t          hash.setAttribute(\"stroke-width\", \"1\");\n\t          templateGroup.appendChild(hash);\n\t        });\n\t      }\n\t      break;\n\t    }\n\t    case \"soccer\": {\n\t      const pitchW = Math.min(w * 0.9, h * 0.9 * 1.54);\n\t      const pitchH = pitchW / 1.54;\n\t      const pitchX = cx - pitchW / 2;\n\t      const pitchY = cy - pitchH / 2;\n\t      const pitch = document.createElementNS(ns, \"rect\");\n\t      pitch.setAttribute(\"x\", pitchX);\n\t      pitch.setAttribute(\"y\", pitchY);\n\t      pitch.setAttribute(\"width\", pitchW);\n\t      pitch.setAttribute(\"height\", pitchH);\n\t      pitch.setAttribute(\"fill\", \"none\");\n\t      pitch.setAttribute(\"stroke\", majorColor);\n\t      pitch.setAttribute(\"stroke-width\", \"3\");\n\t      templateGroup.appendChild(pitch);\n\t      const centerLine = document.createElementNS(ns, \"line\");\n\t      centerLine.setAttribute(\"x1\", cx);\n\t      centerLine.setAttribute(\"y1\", pitchY);\n\t      centerLine.setAttribute(\"x2\", cx);\n\t      centerLine.setAttribute(\"y2\", pitchY + pitchH);\n\t      centerLine.setAttribute(\"stroke\", color);\n\t      centerLine.setAttribute(\"stroke-width\", \"2\");\n\t      templateGroup.appendChild(centerLine);\n\t      const centerCircle = document.createElementNS(ns, \"circle\");\n\t      centerCircle.setAttribute(\"cx\", cx);\n\t      centerCircle.setAttribute(\"cy\", cy);\n\t      centerCircle.setAttribute(\"r\", pitchH * 0.14);\n\t      centerCircle.setAttribute(\"fill\", \"none\");\n\t      centerCircle.setAttribute(\"stroke\", color);\n\t      centerCircle.setAttribute(\"stroke-width\", \"2\");\n\t      templateGroup.appendChild(centerCircle);\n\t      const penW = pitchW * 0.157;\n\t      const penH = pitchH * 0.62;\n\t      [pitchX, pitchX + pitchW - penW].forEach(px => {\n\t        const pen = document.createElementNS(ns, \"rect\");\n\t        pen.setAttribute(\"x\", px);\n\t        pen.setAttribute(\"y\", cy - penH / 2);\n\t        pen.setAttribute(\"width\", penW);\n\t        pen.setAttribute(\"height\", penH);\n\t        pen.setAttribute(\"fill\", \"none\");\n\t        pen.setAttribute(\"stroke\", color);\n\t        pen.setAttribute(\"stroke-width\", \"2\");\n\t        templateGroup.appendChild(pen);\n\t        const goalW = penW * 0.35;\n\t        const goalH = penH * 0.45;\n\t        const goal = document.createElementNS(ns, \"rect\");\n\t        goal.setAttribute(\"x\", px === pitchX ? px : px + penW - goalW);\n\t        goal.setAttribute(\"y\", cy - goalH / 2);\n\t        goal.setAttribute(\"width\", goalW);\n\t        goal.setAttribute(\"height\", goalH);\n\t        goal.setAttribute(\"fill\", \"none\");\n\t        goal.setAttribute(\"stroke\", color);\n\t        goal.setAttribute(\"stroke-width\", \"2\");\n\t        templateGroup.appendChild(goal);\n\t      });\n\t      break;\n\t    }\n\t    case \"hockey\": {\n\t      const rinkW = Math.min(w * 0.9, h * 0.9 * 2.35);\n\t      const rinkH = rinkW / 2.35;\n\t      const rinkX = cx - rinkW / 2;\n\t      const rinkY = cy - rinkH / 2;\n\t      const cornerR = rinkH * 0.33;\n\t      const rink = document.createElementNS(ns, \"rect\");\n\t      rink.setAttribute(\"x\", rinkX);\n\t      rink.setAttribute(\"y\", rinkY);\n\t      rink.setAttribute(\"width\", rinkW);\n\t      rink.setAttribute(\"height\", rinkH);\n\t      rink.setAttribute(\"rx\", cornerR);\n\t      rink.setAttribute(\"fill\", \"none\");\n\t      rink.setAttribute(\"stroke\", majorColor);\n\t      rink.setAttribute(\"stroke-width\", \"3\");\n\t      templateGroup.appendChild(rink);\n\t      const centerLine = document.createElementNS(ns, \"line\");\n\t      centerLine.setAttribute(\"x1\", cx);\n\t      centerLine.setAttribute(\"y1\", rinkY);\n\t      centerLine.setAttribute(\"x2\", cx);\n\t      centerLine.setAttribute(\"y2\", rinkY + rinkH);\n\t      centerLine.setAttribute(\"stroke\", \"#cc3333\" + \"99\");\n\t      centerLine.setAttribute(\"stroke-width\", \"3\");\n\t      templateGroup.appendChild(centerLine);\n\t      const blueLineOffset = rinkW * 0.25;\n\t      [cx - blueLineOffset, cx + blueLineOffset].forEach(bx => {\n\t        const blueLine = document.createElementNS(ns, \"line\");\n\t        blueLine.setAttribute(\"x1\", bx);\n\t        blueLine.setAttribute(\"y1\", rinkY);\n\t        blueLine.setAttribute(\"x2\", bx);\n\t        blueLine.setAttribute(\"y2\", rinkY + rinkH);\n\t        blueLine.setAttribute(\"stroke\", \"#3366cc\" + \"99\");\n\t        blueLine.setAttribute(\"stroke-width\", \"3\");\n\t        templateGroup.appendChild(blueLine);\n\t      });\n\t      const centerCircle = document.createElementNS(ns, \"circle\");\n\t      centerCircle.setAttribute(\"cx\", cx);\n\t      centerCircle.setAttribute(\"cy\", cy);\n\t      centerCircle.setAttribute(\"r\", rinkH * 0.18);\n\t      centerCircle.setAttribute(\"fill\", \"none\");\n\t      centerCircle.setAttribute(\"stroke\", \"#3366cc\" + \"99\");\n\t      centerCircle.setAttribute(\"stroke-width\", \"2\");\n\t      templateGroup.appendChild(centerCircle);\n\t      const faceoffR = rinkH * 0.18;\n\t      const faceoffPositions = [\n\t        [rinkX + rinkW * 0.15, cy - rinkH * 0.25],\n\t        [rinkX + rinkW * 0.15, cy + rinkH * 0.25],\n\t        [rinkX + rinkW * 0.85, cy - rinkH * 0.25],\n\t        [rinkX + rinkW * 0.85, cy + rinkH * 0.25]\n\t      ];\n\t      faceoffPositions.forEach(([fx, fy]) => {\n\t        const circle = document.createElementNS(ns, \"circle\");\n\t        circle.setAttribute(\"cx\", fx);\n\t        circle.setAttribute(\"cy\", fy);\n\t        circle.setAttribute(\"r\", faceoffR);\n\t        circle.setAttribute(\"fill\", \"none\");\n\t        circle.setAttribute(\"stroke\", \"#cc3333\" + \"99\");\n\t        circle.setAttribute(\"stroke-width\", \"2\");\n\t        templateGroup.appendChild(circle);\n\t      });\n\t      [rinkX + rinkW * 0.045, rinkX + rinkW * 0.955].forEach((gx, i) => {\n\t        const crease = document.createElementNS(ns, \"path\");\n\t        const creaseR = rinkH * 0.08;\n\t        if (i === 0) {\n\t          crease.setAttribute(\"d\", `M ${gx} ${cy - creaseR} A ${creaseR} ${creaseR} 0 0 1 ${gx} ${cy + creaseR}`);\n\t        } else {\n\t          crease.setAttribute(\"d\", `M ${gx} ${cy - creaseR} A ${creaseR} ${creaseR} 0 0 0 ${gx} ${cy + creaseR}`);\n\t        }\n\t        crease.setAttribute(\"fill\", \"#3366cc\" + \"33\");\n\t        crease.setAttribute(\"stroke\", \"#cc3333\" + \"99\");\n\t        crease.setAttribute(\"stroke-width\", \"2\");\n\t        templateGroup.appendChild(crease);\n\t      });\n\t      break;\n\t    }\n\t    case \"baseball\": {\n\t      const diamondSize = Math.min(w * 0.7, h * 0.7);\n\t      const baseX = cx;\n\t      const baseY = cy + diamondSize * 0.3;\n\t      const baseDistance = diamondSize * 0.35;\n\t      const diamond = document.createElementNS(ns, \"polygon\");\n\t      const homeX = baseX, homeY = baseY;\n\t      const firstX = baseX + baseDistance, firstY = baseY - baseDistance;\n\t      const secondX = baseX, secondY = baseY - baseDistance * 2;\n\t      const thirdX = baseX - baseDistance, thirdY = baseY - baseDistance;\n\t      diamond.setAttribute(\"points\", `${homeX},${homeY} ${firstX},${firstY} ${secondX},${secondY} ${thirdX},${thirdY}`);\n\t      diamond.setAttribute(\"fill\", \"none\");\n\t      diamond.setAttribute(\"stroke\", color);\n\t      diamond.setAttribute(\"stroke-width\", \"2\");\n\t      templateGroup.appendChild(diamond);\n\t      [[homeX, homeY], [firstX, firstY], [secondX, secondY], [thirdX, thirdY]].forEach(([bx, by], i) => {\n\t        const base = document.createElementNS(ns, \"rect\");\n\t        const baseSize = i === 0 ? 15 : 12;\n\t        base.setAttribute(\"x\", bx - baseSize / 2);\n\t        base.setAttribute(\"y\", by - baseSize / 2);\n\t        base.setAttribute(\"width\", baseSize);\n\t        base.setAttribute(\"height\", baseSize);\n\t        base.setAttribute(\"transform\", `rotate(45 ${bx} ${by})`);\n\t        base.setAttribute(\"fill\", i === 0 ? majorColor : color);\n\t        base.setAttribute(\"stroke\", majorColor);\n\t        base.setAttribute(\"stroke-width\", \"2\");\n\t        templateGroup.appendChild(base);\n\t      });\n\t      const moundX = baseX;\n\t      const moundY = baseY - baseDistance * 0.67;\n\t      const mound = document.createElementNS(ns, \"circle\");\n\t      mound.setAttribute(\"cx\", moundX);\n\t      mound.setAttribute(\"cy\", moundY);\n\t      mound.setAttribute(\"r\", 20);\n\t      mound.setAttribute(\"fill\", color);\n\t      mound.setAttribute(\"fill-opacity\", \"0.3\");\n\t      mound.setAttribute(\"stroke\", color);\n\t      mound.setAttribute(\"stroke-width\", \"2\");\n\t      templateGroup.appendChild(mound);\n\t      const outfieldR = diamondSize * 0.6;\n\t      const outfield = document.createElementNS(ns, \"path\");\n\t      outfield.setAttribute(\"d\", `M ${baseX - outfieldR * 0.95} ${baseY - outfieldR * 0.3} A ${outfieldR} ${outfieldR} 0 0 1 ${baseX + outfieldR * 0.95} ${baseY - outfieldR * 0.3}`);\n\t      outfield.setAttribute(\"fill\", \"none\");\n\t      outfield.setAttribute(\"stroke\", color);\n\t      outfield.setAttribute(\"stroke-width\", \"2\");\n\t      outfield.setAttribute(\"stroke-dasharray\", \"10,5\");\n\t      templateGroup.appendChild(outfield);\n\t      const foulLength = diamondSize * 0.8;\n\t      [[-1, -1], [1, -1]].forEach(([dx, dy]) => {\n\t        const foul = document.createElementNS(ns, \"line\");\n\t        foul.setAttribute(\"x1\", homeX);\n\t        foul.setAttribute(\"y1\", homeY);\n\t        foul.setAttribute(\"x2\", homeX + dx * foulLength);\n\t        foul.setAttribute(\"y2\", homeY + dy * foulLength * 0.7);\n\t        foul.setAttribute(\"stroke\", color);\n\t        foul.setAttribute(\"stroke-width\", \"2\");\n\t        templateGroup.appendChild(foul);\n\t      });\n\t      break;\n\t    }\n\t    case \"tennis\": {\n\t      const courtW = Math.min(w * 0.9, h * 0.9 * 2.17);\n\t      const courtH = courtW / 2.17;\n\t      const courtX = cx - courtW / 2;\n\t      const courtY = cy - courtH / 2;\n\t      const court = document.createElementNS(ns, \"rect\");\n\t      court.setAttribute(\"x\", courtX);\n\t      court.setAttribute(\"y\", courtY);\n\t      court.setAttribute(\"width\", courtW);\n\t      court.setAttribute(\"height\", courtH);\n\t      court.setAttribute(\"fill\", \"none\");\n\t      court.setAttribute(\"stroke\", majorColor);\n\t      court.setAttribute(\"stroke-width\", \"3\");\n\t      templateGroup.appendChild(court);\n\t      const singlesInset = courtH * 0.125;\n\t      const singles = document.createElementNS(ns, \"rect\");\n\t      singles.setAttribute(\"x\", courtX);\n\t      singles.setAttribute(\"y\", courtY + singlesInset);\n\t      singles.setAttribute(\"width\", courtW);\n\t      singles.setAttribute(\"height\", courtH - singlesInset * 2);\n\t      singles.setAttribute(\"fill\", \"none\");\n\t      singles.setAttribute(\"stroke\", color);\n\t      singles.setAttribute(\"stroke-width\", \"2\");\n\t      templateGroup.appendChild(singles);\n\t      const net = document.createElementNS(ns, \"line\");\n\t      net.setAttribute(\"x1\", cx);\n\t      net.setAttribute(\"y1\", courtY);\n\t      net.setAttribute(\"x2\", cx);\n\t      net.setAttribute(\"y2\", courtY + courtH);\n\t      net.setAttribute(\"stroke\", majorColor);\n\t      net.setAttribute(\"stroke-width\", \"3\");\n\t      templateGroup.appendChild(net);\n\t      const serviceW = courtW * 0.27;\n\t      const serviceH = (courtH - singlesInset * 2) / 2;\n\t      [courtX, courtX + courtW - serviceW].forEach(sx => {\n\t        const topBox = document.createElementNS(ns, \"rect\");\n\t        topBox.setAttribute(\"x\", sx === courtX ? cx - serviceW : cx);\n\t        topBox.setAttribute(\"y\", courtY + singlesInset);\n\t        topBox.setAttribute(\"width\", serviceW);\n\t        topBox.setAttribute(\"height\", serviceH);\n\t        topBox.setAttribute(\"fill\", \"none\");\n\t        topBox.setAttribute(\"stroke\", color);\n\t        topBox.setAttribute(\"stroke-width\", \"2\");\n\t        templateGroup.appendChild(topBox);\n\t        const botBox = document.createElementNS(ns, \"rect\");\n\t        botBox.setAttribute(\"x\", sx === courtX ? cx - serviceW : cx);\n\t        botBox.setAttribute(\"y\", cy);\n\t        botBox.setAttribute(\"width\", serviceW);\n\t        botBox.setAttribute(\"height\", serviceH);\n\t        botBox.setAttribute(\"fill\", \"none\");\n\t        botBox.setAttribute(\"stroke\", color);\n\t        botBox.setAttribute(\"stroke-width\", \"2\");\n\t        templateGroup.appendChild(botBox);\n\t      });\n\t      const centerService = document.createElementNS(ns, \"line\");\n\t      centerService.setAttribute(\"x1\", cx - serviceW);\n\t      centerService.setAttribute(\"y1\", cy);\n\t      centerService.setAttribute(\"x2\", cx + serviceW);\n\t      centerService.setAttribute(\"y2\", cy);\n\t      centerService.setAttribute(\"stroke\", color);\n\t      centerService.setAttribute(\"stroke-width\", \"2\");\n\t      templateGroup.appendChild(centerService);\n\t      [courtX, courtX + courtW].forEach(bx => {\n\t        const mark = document.createElementNS(ns, \"line\");\n\t        mark.setAttribute(\"x1\", bx);\n\t        mark.setAttribute(\"y1\", cy - 10);\n\t        mark.setAttribute(\"x2\", bx);\n\t        mark.setAttribute(\"y2\", cy + 10);\n\t        mark.setAttribute(\"stroke\", color);\n\t        mark.setAttribute(\"stroke-width\", \"2\");\n\t        templateGroup.appendChild(mark);\n\t      });\n\t      break;\n\t    }\n\t    case \"none\":\n\t    default:\n\t      break;\n\t  }\n\t  return templateGroup;\n\t}\n\n\tfunction formatRecordingTime(ms) {\n\t  const seconds = Math.floor(ms / 1000);\n\t  const minutes = Math.floor(seconds / 60);\n\t  const secs = seconds % 60;\n\t  return `${minutes}:${secs.toString().padStart(2, '0')}`;\n\t}\n\n\tfunction captureRecordingFrame() {\n\t  const frame = {\n\t    time: Date.now() - RECORDING_STATE.startTime,\n\t    positions: {},\n\t    waypoints: {},\n\t    rects: [],\n\t    texts: []\n\t  };\n\t  Object.keys(savedPositions).forEach(id => {\n\t    frame.positions[id] = { x: savedPositions[id].x, y: savedPositions[id].y };\n\t  });\n\t  EDGE_DATA.list.forEach(edge => {\n\t    if (edge.waypoints && edge.waypoints.length > 0) {\n\t      frame.waypoints[edge.id] = edge.waypoints.map(wp => ({ x: wp.x, y: wp.y }));\n\t    }\n\t  });\n\t  if (RECT_DATA && RECT_DATA.list) {\n\t    frame.rects = RECT_DATA.list.map(r => ({ id: r.id, x: r.x, y: r.y, width: r.width, height: r.height, rotation: r.rotation || 0 }));\n\t  }\n\t  if (TEXT_DATA && TEXT_DATA.list) {\n\t    frame.texts = TEXT_DATA.list.map(t => ({ id: t.id, x: t.x, y: t.y }));\n\t  }\n\t  RECORDING_STATE.frames.push(frame);\n\t  document.getElementById('recording-time').textContent = formatRecordingTime(frame.time);\n\t}\n\n\tfunction startRecording() {\n\t  if (RECORDING_STATE.isRecording) return;\n\t  RECORDING_STATE.isRecording = true;\n\t  RECORDING_STATE.frames = [];\n\t  RECORDING_STATE.startTime = Date.now();\n\t  captureRecordingFrame();\n\t  RECORDING_STATE.captureInterval = setInterval(captureRecordingFrame, 1000 / RECORDING_STATE.CAPTURE_FPS);\n\t  document.getElementById('record-btn').style.background = '#f56565';\n\t  document.getElementById('record-btn').style.color = 'white';\n\t  document.getElementById('recording-expanded').style.display = 'inline-flex';\n\t  document.getElementById('pause-btn').style.display = 'none';\n\t  logAuditEvent('recording', 'Started recording');\n\t}\n\n\tfunction stopRecording() {\n\t  if (!RECORDING_STATE.isRecording && !RECORDING_STATE.isPlaying) return;\n\t  const wasPlaying = RECORDING_STATE.isPlaying;\n\t  if (RECORDING_STATE.isRecording) {\n\t    clearInterval(RECORDING_STATE.captureInterval);\n\t    RECORDING_STATE.isRecording = false;\n\t    if (RECORDING_STATE.frames.length > 1) {\n\t      const recording = {\n\t        id: 'rec-' + Date.now(),\n\t        name: 'Recording ' + (RECORDING_STATE.recordings.length + 1),\n\t        created: new Date().toISOString(),\n\t        duration: RECORDING_STATE.frames[RECORDING_STATE.frames.length - 1].time,\n\t        frames: RECORDING_STATE.frames\n\t      };\n\t      RECORDING_STATE.recordings.push(recording);\n\t      RECORDING_STATE.currentRecording = recording;\n\t      logAuditEvent('recording', `Saved recording: ${recording.name} (${formatRecordingTime(recording.duration)})`);\n\t    }\n\t    document.getElementById('record-btn').style.background = 'var(--btn-bg)';\n\t    document.getElementById('record-btn').style.color = '#f56565';\n\t  }\n\t  if (RECORDING_STATE.isPlaying) {\n\t    clearTimeout(RECORDING_STATE.playbackTimer);\n\t    RECORDING_STATE.isPlaying = false;\n\t    RECORDING_STATE.isPaused = false;\n\t    RECORDING_STATE.trailHistory = {};\n\t    forgeTheTopology();\n\t  }\n\t  if (wasPlaying) {\n\t    document.getElementById('recording-expanded').style.display = 'none';\n\t  }\n\t  document.getElementById('play-btn').style.display = 'inline-block';\n\t  document.getElementById('pause-btn').style.display = 'none';\n\t  document.getElementById('recording-time').textContent = '0:00';\n\t}\n\n\tfunction playRecording() {\n\t  if (!RECORDING_STATE.currentRecording || RECORDING_STATE.currentRecording.frames.length < 2) {\n\t    showAlert(t(\"dialogs.noRecordingAvailable\"));\n\t    const expanded = document.getElementById('recording-expanded');\n\t    if (expanded) expanded.style.display = 'none';\n\t    return;\n\t  }\n\t  if (RECORDING_STATE.isPaused) {\n\t    RECORDING_STATE.isPaused = false;\n\t    RECORDING_STATE.isPlaying = true;\n\t    document.getElementById('play-btn').style.display = 'none';\n\t    document.getElementById('pause-btn').style.display = 'inline-block';\n\t    playNextFrame();\n\t    return;\n\t  }\n\t  RECORDING_STATE.isPlaying = true;\n\t  RECORDING_STATE.playbackIndex = 0;\n\t  document.getElementById('play-btn').style.display = 'none';\n\t  document.getElementById('pause-btn').style.display = 'inline-block';\n\t  document.getElementById('recording-expanded').style.display = 'inline-flex';\n\t  playNextFrame();\n\t}\n\n\tfunction pauseRecording() {\n\t  if (!RECORDING_STATE.isPlaying) return;\n\t  RECORDING_STATE.isPaused = true;\n\t  RECORDING_STATE.isPlaying = false;\n\t  clearTimeout(RECORDING_STATE.playbackTimer);\n\t  document.getElementById('play-btn').style.display = 'inline-block';\n\t  document.getElementById('pause-btn').style.display = 'none';\n\t}\n\n\tfunction startStepRecording() {\n\t  if (RECORDING_STATE.isStepRecording || RECORDING_STATE.isRecording) return;\n\t  RECORDING_STATE.isStepRecording = true;\n\t  RECORDING_STATE.stepFrames = [];\n\t  RECORDING_STATE.stepCount = 0;\n\t  captureStepFrame();\n\t  const stepBtn = document.getElementById('step-record-btn');\n\t  stepBtn.textContent = '+';\n\t  stepBtn.style.background = '#48bb78';\n\t  stepBtn.style.color = 'white';\n\t  document.getElementById('recording-expanded').style.display = 'inline-flex';\n\t  document.getElementById('pause-btn').style.display = 'none';\n\t  document.getElementById('record-btn').style.display = 'none';\n\t  updateStepRecordingTime();\n\t  logAuditEvent('recording', 'Started step by step recording');\n\t}\n\n\tfunction captureStepFrame() {\n\t  const frame = {\n\t    time: RECORDING_STATE.stepCount * 1000,\n\t    positions: {},\n\t    waypoints: {},\n\t    rects: [],\n\t    texts: []\n\t  };\n\t  Object.keys(savedPositions).forEach(id => {\n\t    frame.positions[id] = { x: savedPositions[id].x, y: savedPositions[id].y };\n\t  });\n\t  EDGE_DATA.list.forEach(edge => {\n\t    if (edge.waypoints && edge.waypoints.length > 0) {\n\t      frame.waypoints[edge.id] = edge.waypoints.map(wp => ({ x: wp.x, y: wp.y }));\n\t    }\n\t  });\n\t  if (RECT_DATA && RECT_DATA.list) {\n\t    frame.rects = RECT_DATA.list.map(r => ({ id: r.id, x: r.x, y: r.y, width: r.width, height: r.height, rotation: r.rotation || 0 }));\n\t  }\n\t  if (TEXT_DATA && TEXT_DATA.list) {\n\t    frame.texts = TEXT_DATA.list.map(t => ({ id: t.id, x: t.x, y: t.y }));\n\t  }\n\t  RECORDING_STATE.stepFrames.push(frame);\n\t  RECORDING_STATE.stepCount++;\n\t  updateStepRecordingTime();\n\t}\n\n\tfunction updateStepRecordingTime() {\n\t  const timeEl = document.getElementById('recording-time');\n\t  if (timeEl) {\n\t    timeEl.textContent = RECORDING_STATE.stepCount + ' frames';\n\t  }\n\t}\n\n\tfunction stopStepRecording() {\n\t  if (!RECORDING_STATE.isStepRecording) return;\n\t  RECORDING_STATE.isStepRecording = false;\n\t  if (RECORDING_STATE.stepFrames.length > 1) {\n\t    const recording = {\n\t      id: 'rec-' + Date.now(),\n\t      name: 'Step Recording ' + (RECORDING_STATE.recordings.length + 1),\n\t      created: new Date().toISOString(),\n\t      duration: (RECORDING_STATE.stepCount - 1) * 1000,\n\t      frames: RECORDING_STATE.stepFrames\n\t    };\n\t    RECORDING_STATE.recordings.push(recording);\n\t    RECORDING_STATE.currentRecording = recording;\n\t    logAuditEvent('recording', `Saved step recording: ${recording.name} (${RECORDING_STATE.stepCount} frames)`);\n\t  }\n\t  const stepBtn = document.getElementById('step-record-btn');\n\t  stepBtn.textContent = '●+';\n\t  stepBtn.style.background = 'var(--btn-bg)';\n\t  stepBtn.style.color = '#48bb78';\n\t  document.getElementById('record-btn').style.display = 'inline-block';\n\t  document.getElementById('recording-time').textContent = '0:00';\n\t}\n\n\tfunction playNextFrame() {\n\t  if (!RECORDING_STATE.isPlaying || !RECORDING_STATE.currentRecording) return;\n\t  const frames = RECORDING_STATE.currentRecording.frames;\n\t  if (RECORDING_STATE.playbackIndex >= frames.length) {\n\t    if (RECORDING_STATE.loopPlayback) {\n\t      RECORDING_STATE.playbackIndex = 0;\n\t      RECORDING_STATE.trailHistory = {};\n\t    } else {\n\t      stopRecording();\n\t      return;\n\t    }\n\t  }\n\t  const frame = frames[RECORDING_STATE.playbackIndex];\n\t  const trailLength = PAGE_STATE.motionTrailLength || 8;\n\t  Object.keys(frame.positions).forEach(id => {\n\t    if (savedPositions[id]) {\n\t      if (PAGE_STATE.motionTrailsEnabled) {\n\t        if (!RECORDING_STATE.trailHistory[id]) RECORDING_STATE.trailHistory[id] = [];\n\t        RECORDING_STATE.trailHistory[id].push({ x: frame.positions[id].x, y: frame.positions[id].y });\n\t        if (RECORDING_STATE.trailHistory[id].length > trailLength) {\n\t          RECORDING_STATE.trailHistory[id].shift();\n\t        }\n\t      }\n\t      savedPositions[id].x = frame.positions[id].x;\n\t      savedPositions[id].y = frame.positions[id].y;\n\t    }\n\t  });\n\t  if (frame.waypoints) {\n\t    EDGE_DATA.list.forEach(edge => {\n\t      if (frame.waypoints[edge.id]) {\n\t        edge.waypoints = frame.waypoints[edge.id].map(wp => ({ x: wp.x, y: wp.y }));\n\t      } else {\n\t        edge.waypoints = [];\n\t      }\n\t    });\n\t  }\n\t  if (frame.rects && RECT_DATA && RECT_DATA.list) {\n\t    frame.rects.forEach(frameRect => {\n\t      const rect = RECT_DATA.list.find(r => r.id === frameRect.id);\n\t      if (rect) {\n\t        rect.x = frameRect.x;\n\t        rect.y = frameRect.y;\n\t        rect.width = frameRect.width;\n\t        rect.height = frameRect.height;\n\t        rect.rotation = frameRect.rotation || 0;\n\t      }\n\t    });\n\t  }\n\t  if (frame.texts && TEXT_DATA && TEXT_DATA.list) {\n\t    frame.texts.forEach(frameText => {\n\t      const text = TEXT_DATA.list.find(t => t.id === frameText.id);\n\t      if (text) {\n\t        text.x = frameText.x;\n\t        text.y = frameText.y;\n\t      }\n\t    });\n\t  }\n\t  forgeTheTopology();\n\t  updateMinimap();\n\t  document.getElementById('recording-time').textContent = formatRecordingTime(frame.time);\n\t  const progress = (RECORDING_STATE.playbackIndex / (frames.length - 1)) * 100;\n\t  document.getElementById('playback-scrubber').value = progress;\n\t  RECORDING_STATE.playbackIndex++;\n\t  const nextFrame = frames[RECORDING_STATE.playbackIndex];\n\t  if (nextFrame) {\n\t    const delay = (nextFrame.time - frame.time) / RECORDING_STATE.playbackSpeed;\n\t    RECORDING_STATE.playbackTimer = setTimeout(playNextFrame, Math.max(16, delay));\n\t  } else if (RECORDING_STATE.loopPlayback) {\n\t    RECORDING_STATE.playbackTimer = setTimeout(playNextFrame, 100);\n\t  } else {\n\t    stopRecording();\n\t  }\n\t}\n\n\tfunction renderMotionTrails(svg) {\n\t  if (!PAGE_STATE.motionTrailsEnabled || !RECORDING_STATE.isPlaying) return;\n\t  if (Object.keys(RECORDING_STATE.trailHistory).length === 0) return;\n\t  const ns = \"http://www.w3.org/2000/svg\";\n\t  const trailGroup = document.createElementNS(ns, \"g\");\n\t  trailGroup.id = \"motion-trails\";\n\t  const trailStyle = PAGE_STATE.motionTrailStyle || \"solid\";\n\t  const showArrow = PAGE_STATE.motionTrailArrow !== false;\n\t  Object.keys(RECORDING_STATE.trailHistory).forEach(nodeId => {\n\t    const node = NODE_DATA[nodeId];\n\t    if (!node) return;\n\t    if (node.trailEnabled === false) return;\n\t    const trail = RECORDING_STATE.trailHistory[nodeId];\n\t    if (!trail || trail.length < 2) return;\n\t    const nodeSize = (savedSizes[nodeId] && savedSizes[nodeId].size) || 55;\n\t    const nodeColor = (savedStyles[nodeId] && savedStyles[nodeId].stroke) || PAGE_STATE.nodeStroke || \"#475569\";\n\t    if (trailStyle === \"dots\") {\n\t      trail.forEach((point, idx) => {\n\t        const opacity = (idx + 1) / trail.length * 0.7;\n\t        const dotSize = 3 + (idx / trail.length) * 4;\n\t        const dot = document.createElementNS(ns, \"circle\");\n\t        dot.setAttribute(\"cx\", point.x);\n\t        dot.setAttribute(\"cy\", point.y);\n\t        dot.setAttribute(\"r\", dotSize);\n\t        dot.setAttribute(\"fill\", nodeColor);\n\t        dot.setAttribute(\"opacity\", opacity);\n\t        trailGroup.appendChild(dot);\n\t      });\n\t    } else {\n\t      const path = document.createElementNS(ns, \"polyline\");\n\t      const points = trail.map(p => `${p.x},${p.y}`).join(\" \");\n\t      path.setAttribute(\"points\", points);\n\t      path.setAttribute(\"fill\", \"none\");\n\t      path.setAttribute(\"stroke\", nodeColor);\n\t      path.setAttribute(\"stroke-width\", \"3\");\n\t      path.setAttribute(\"stroke-linecap\", \"round\");\n\t      path.setAttribute(\"stroke-linejoin\", \"round\");\n\t      path.setAttribute(\"opacity\", \"0.6\");\n\t      if (trailStyle === \"dashed\") {\n\t        path.setAttribute(\"stroke-dasharray\", \"8,4\");\n\t      }\n\t      trailGroup.appendChild(path);\n\t    }\n\t    if (showArrow && trail.length >= 2) {\n\t      const last = trail[trail.length - 1];\n\t      const prev = trail[trail.length - 2];\n\t      const angle = Math.atan2(last.y - prev.y, last.x - prev.x);\n\t      const arrowSize = 10;\n\t      const arrowX = last.x + Math.cos(angle) * (nodeSize / 2 + 5);\n\t      const arrowY = last.y + Math.sin(angle) * (nodeSize / 2 + 5);\n\t      const arrow = document.createElementNS(ns, \"polygon\");\n\t      const p1x = arrowX + Math.cos(angle) * arrowSize;\n\t      const p1y = arrowY + Math.sin(angle) * arrowSize;\n\t      const p2x = arrowX + Math.cos(angle + 2.5) * arrowSize * 0.7;\n\t      const p2y = arrowY + Math.sin(angle + 2.5) * arrowSize * 0.7;\n\t      const p3x = arrowX + Math.cos(angle - 2.5) * arrowSize * 0.7;\n\t      const p3y = arrowY + Math.sin(angle - 2.5) * arrowSize * 0.7;\n\t      arrow.setAttribute(\"points\", `${p1x},${p1y} ${p2x},${p2y} ${p3x},${p3y}`);\n\t      arrow.setAttribute(\"fill\", nodeColor);\n\t      arrow.setAttribute(\"opacity\", \"0.8\");\n\t      trailGroup.appendChild(arrow);\n\t    }\n\t  });\n\t  const firstChild = svg.firstChild;\n\t  if (firstChild) {\n\t    svg.insertBefore(trailGroup, firstChild);\n\t  } else {\n\t    svg.appendChild(trailGroup);\n\t  }\n\t}\n\n\tfunction seekRecording(percent) {\n\t  if (!RECORDING_STATE.currentRecording) return;\n\t  const frames = RECORDING_STATE.currentRecording.frames;\n\t  const targetIndex = Math.floor((percent / 100) * (frames.length - 1));\n\t  RECORDING_STATE.playbackIndex = targetIndex;\n\t  const frame = frames[targetIndex];\n\t  if (frame) {\n\t    Object.keys(frame.positions).forEach(id => {\n\t      if (savedPositions[id]) {\n\t        savedPositions[id].x = frame.positions[id].x;\n\t        savedPositions[id].y = frame.positions[id].y;\n\t      }\n\t    });\n\t    if (frame.waypoints) {\n\t      EDGE_DATA.list.forEach(edge => {\n\t        if (frame.waypoints[edge.id]) {\n\t          edge.waypoints = frame.waypoints[edge.id].map(wp => ({ x: wp.x, y: wp.y }));\n\t        } else {\n\t          edge.waypoints = [];\n\t        }\n\t      });\n\t    }\n\t    if (frame.rects && RECT_DATA && RECT_DATA.list) {\n\t      frame.rects.forEach(frameRect => {\n\t        const rect = RECT_DATA.list.find(r => r.id === frameRect.id);\n\t        if (rect) {\n\t          rect.x = frameRect.x;\n\t          rect.y = frameRect.y;\n\t          rect.width = frameRect.width;\n\t          rect.height = frameRect.height;\n\t          rect.rotation = frameRect.rotation || 0;\n\t        }\n\t      });\n\t    }\n\t    if (frame.texts && TEXT_DATA && TEXT_DATA.list) {\n\t      frame.texts.forEach(frameText => {\n\t        const text = TEXT_DATA.list.find(t => t.id === frameText.id);\n\t        if (text) {\n\t          text.x = frameText.x;\n\t          text.y = frameText.y;\n\t        }\n\t      });\n\t    }\n\t    forgeTheTopology();\n\t    updateMinimap();\n\t    document.getElementById('recording-time').textContent = formatRecordingTime(frame.time);\n\t  }\n\t}\n\n\tfunction renderRecordingsList() {\n\t  const list = document.getElementById('recordings-list');\n\t  if (RECORDING_STATE.recordings.length === 0) {\n\t    list.innerHTML = '<div style=\"text-align: center; padding: 40px; color: var(--text-soft);\">' + t(\"emptyStates.noRecordingsYet\") + '</div>';\n\t    return;\n\t  }\n\t  list.innerHTML = RECORDING_STATE.recordings.map((rec, i) => `\n\t    <div style=\"display:flex;align-items:center;gap:10px;padding:10px;background:var(--panel);border-radius:6px;border:1px solid ${rec.id === RECORDING_STATE.currentRecording?.id ? 'var(--accent)' : 'var(--edge-main)'};\">\n\t      <input type=\"radio\" name=\"recording-select\" value=\"${rec.id}\" ${rec.id === RECORDING_STATE.currentRecording?.id ? 'checked' : ''} onchange=\"selectRecording('${rec.id}')\">\n\t      <div style=\"flex:1;\">\n\t        <input type=\"text\" value=\"${rec.name}\" style=\"background:transparent;border:none;color:var(--text-main);font-weight:600;width:100%;\" onchange=\"renameRecording('${rec.id}', this.value)\">\n\t        <div style=\"font-size:11px;color:var(--text-soft);\">${formatRecordingTime(rec.duration)} | ${rec.frames.length} frames | ${new Date(rec.created).toLocaleDateString()}</div>\n\t      </div>\n\t      <button onclick=\"playSelectedRecording('${rec.id}')\" style=\"padding:4px 8px;background:var(--btn-bg);border:1px solid var(--edge-main);border-radius:4px;cursor:pointer;\">▶</button>\n\t    </div>\n\t  `).join('');\n\t}\n\n\tfunction selectRecording(id) {\n\t  RECORDING_STATE.currentRecording = RECORDING_STATE.recordings.find(r => r.id === id) || null;\n\t  renderRecordingsList();\n\t}\n\n\tfunction playSelectedRecording(id) {\n\t  selectRecording(id);\n\t  document.getElementById('recordings-modal').classList.remove('active');\n\t  playRecording();\n\t}\n\n\tfunction renameRecording(id, newName) {\n\t  const rec = RECORDING_STATE.recordings.find(r => r.id === id);\n\t  if (rec) rec.name = newName;\n\t}\n\n\tasync function deleteSelectedRecording() {\n\t  if (!RECORDING_STATE.currentRecording) {\n\t    showAlert(t(\"dialogs.selectRecordingToDelete\"));\n\t    return;\n\t  }\n\t  if (!await showConfirm(t(\"dialogs.deleteRecording\", { name: RECORDING_STATE.currentRecording.name }))) return;\n\t  RECORDING_STATE.recordings = RECORDING_STATE.recordings.filter(r => r.id !== RECORDING_STATE.currentRecording.id);\n\t  RECORDING_STATE.currentRecording = RECORDING_STATE.recordings[0] || null;\n\t  renderRecordingsList();\n\t  logAuditEvent('recording', 'Deleted recording');\n\t}\n\n\tfunction exportRecordingJSON() {\n\t  if (!RECORDING_STATE.currentRecording) {\n\t    showAlert(t(\"dialogs.selectRecordingToExport\"));\n\t    return;\n\t  }\n\t  const json = JSON.stringify(RECORDING_STATE.currentRecording, null, 2);\n\t  const blob = new Blob([json], { type: 'application/json' });\n\t  const url = URL.createObjectURL(blob);\n\t  const a = document.createElement('a');\n\t  a.href = url;\n\t  a.download = (RECORDING_STATE.currentRecording.name || 'recording').replace(/[^a-z0-9]/gi, '-') + '.json';\n\t  a.click();\n\t  URL.revokeObjectURL(url);\n\t  logAuditEvent('export', `Exported recording: ${RECORDING_STATE.currentRecording.name}`);\n\t}\n\n\tfunction importRecordingJSON(input) {\n\t  const file = input.files[0];\n\t  if (!file) return;\n\t  const reader = new FileReader();\n\t  reader.onload = (e) => {\n\t    try {\n\t      const rec = JSON.parse(e.target.result);\n\t      if (!rec.frames || !Array.isArray(rec.frames)) {\n\t        throw new Error('Invalid recording format');\n\t      }\n\t      rec.id = 'rec-' + Date.now();\n\t      rec.name = rec.name || 'Imported Recording';\n\t      RECORDING_STATE.recordings.push(rec);\n\t      RECORDING_STATE.currentRecording = rec;\n\t      renderRecordingsList();\n\t      logAuditEvent('import', `Imported recording: ${rec.name}`);\n\t      showAlert(t(\"dialogs.recordingImported\", { name: rec.name }));\n\t    } catch (err) {\n\t      showAlert(t(\"dialogs.importRecordingFailed\", { error: err.message }));\n\t    }\n\t  };\n\t  reader.readAsText(file);\n\t  input.value = '';\n\t}\n\n\tfunction updateCanvasStyleOptions() {\n\t  const mode = PAGE_STATE.mappingMode || 'network';\n\t  const canvasStyleRow = document.getElementById('canvas-style-row');\n\t  const canvasStyleSelect = document.getElementById('canvas-style-select');\n\t  const canvasGridCheckbox = document.getElementById('canvas-grid-enabled');\n\n\t  if (mode === 'mindmap') {\n\t    if (canvasStyleRow) canvasStyleRow.style.display = 'none';\n\t    PAGE_STATE.canvasTemplate = 'none';\n\t    PAGE_STATE.canvasGridEnabled = false;\n\t    if (canvasGridCheckbox) canvasGridCheckbox.checked = false;\n\t  } else if (mode === 'floorplan') {\n\t    if (canvasStyleRow) canvasStyleRow.style.display = 'none';\n\t    PAGE_STATE.canvasTemplate = 'blueprint';\n\t    PAGE_STATE.canvasGridEnabled = true;\n\t    if (canvasGridCheckbox) canvasGridCheckbox.checked = true;\n\t  } else {\n\t    if (canvasStyleRow) canvasStyleRow.style.display = 'contents';\n\t    const options = CANVAS_OPTIONS[mode] || CANVAS_OPTIONS.network;\n\t    if (canvasStyleSelect && options.length > 0) {\n\t      canvasStyleSelect.innerHTML = options.map(o =>\n\t        `<option value=\"${o.value}\">${o.label}</option>`\n\t      ).join('');\n\t      const validValues = options.map(o => o.value);\n\t      if (!validValues.includes(PAGE_STATE.canvasTemplate)) {\n\t        PAGE_STATE.canvasTemplate = options[0]?.value || 'grid';\n\t      }\n\t      canvasStyleSelect.value = PAGE_STATE.canvasTemplate;\n\t      PAGE_STATE.canvasGridEnabled = PAGE_STATE.canvasTemplate !== 'none';\n\t      if (canvasGridCheckbox) canvasGridCheckbox.checked = PAGE_STATE.canvasGridEnabled;\n\t    }\n\t  }\n\t  forgeTheTopology();\n\t}\n\n\tfunction updateShapeCategoryOptions() {\n\t  const mode = PAGE_STATE.mappingMode || 'network';\n\t  const defaultCat = MODE_DEFAULT_CATEGORIES[mode] || 'network';\n\t  const catSelect = document.getElementById('shape-category-select');\n\t  if (catSelect && !catSelect.value) {\n\t    catSelect.value = defaultCat;\n\t  }\n\t  populateShapeSelect();\n\t}\n\n\tfunction populateShapeSelect(currentValue) {\n\t  const catSelect = document.getElementById('shape-category-select');\n\t  const shapeSelect = document.getElementById('shape-select');\n\t  if (!catSelect || !shapeSelect) return;\n\n\t  const category = catSelect.value || 'basic';\n\t  const shapes = SHAPE_CATEGORIES[category] || SHAPE_CATEGORIES.basic;\n\n\t  shapeSelect.innerHTML = shapes.map(s =>\n\t    `<option value=\"${s.value}\">${s.label}</option>`\n\t  ).join('');\n\n\t  if (currentValue) {\n\t    const exists = shapes.some(s => s.value === currentValue);\n\t    if (exists) {\n\t      shapeSelect.value = currentValue;\n\t    }\n\t  }\n\t}\n\n\tdocument.getElementById('mapping-mode-select')?.addEventListener('change', (e) => {\n\t  PAGE_STATE.mappingMode = e.target.value;\n\t  updateCanvasStyleOptions();\n\t  applyMappingModeLabels();\n\t  updateLayerLabels();\n\t  const catSelect = document.getElementById('shape-category-select');\n\t  if (catSelect) {\n\t    catSelect.value = MODE_DEFAULT_CATEGORIES[e.target.value] || 'network';\n\t    populateShapeSelect();\n\t  }\n\t  wieldThePower();\n\t  displayTabs();\n\t});\n\n\tdocument.getElementById('canvas-style-select')?.addEventListener('change', (e) => {\n\t  PAGE_STATE.canvasTemplate = e.target.value;\n\t  PAGE_STATE.canvasGridEnabled = e.target.value !== 'none';\n\t  forgeTheTopology();\n\t});\n\n\tdocument.getElementById('shape-category-select')?.addEventListener('change', (e) => {\n\t  populateShapeSelect();\n\t});\n\n\tdocument.getElementById('shape-select')?.addEventListener('change', (e) => {\n\t  const shape = e.target.value || 'circle';\n\t  if (!currentNodeId || !NODE_DATA[currentNodeId]) return;\n\t  pushUndo('change shape');\n\t  NODE_DATA[currentNodeId].shape = shape;\n\t  const fovSection = document.getElementById(\"fov-section\");\n\t  if (fovSection) {\n\t    if (typeof hasCoverageZone === 'function' && hasCoverageZone(shape)) {\n\t      const defaults = getCoverageDefaults(shape);\n\t      fovSection.style.display = \"block\";\n\t      document.getElementById(\"fov-angle\").value = defaults.angle;\n\t      document.getElementById(\"fov-angle-value\").textContent = defaults.angle + \"°\";\n\t      document.getElementById(\"fov-distance\").value = defaults.distance;\n\t      document.getElementById(\"fov-distance-value\").textContent = defaults.distance;\n\t      document.getElementById(\"fov-animation-type\").value = defaults.animationType;\n\t    } else {\n\t      fovSection.style.display = \"none\";\n\t    }\n\t  }\n\t  const nodeGroup = document.querySelector(`g[data-node-id=\"${currentNodeId}\"]`);\n\t  if (nodeGroup) {\n\t    const oldShape = nodeGroup.querySelector(\".node-circle\");\n\t    if (oldShape) oldShape.remove();\n\t    const size = savedSizes[currentNodeId] || getDefaultSize();\n\t    const newShape = createNodeShape(currentNodeId, size);\n\t    nodeGroup.insertBefore(newShape, nodeGroup.firstChild);\n\t  }\n\t});\n\n\tfunction applyMappingModeLabels() {\n\t  const mode = PAGE_STATE.mappingMode || 'network';\n\t  const labels = LANG.modes[mode] || LANG.modes.network;\n\n\t  const addNodeBtn = document.getElementById('add-node-btn');\n\t  const addRackBtn = document.getElementById('add-rack-btn');\n\t  if (addNodeBtn) addNodeBtn.textContent = '+ ' + labels.node;\n\t  if (addRackBtn) {\n\t    if (!labels.rack) {\n\t      addRackBtn.style.display = 'none';\n\t    } else {\n\t      addRackBtn.style.display = '';\n\t      addRackBtn.textContent = '+ ' + labels.rack;\n\t    }\n\t  }\n\n\t  const addNodeTitle = document.getElementById('add-node-title');\n\t  const addRackTitle = document.getElementById('add-rack-title');\n\t  if (addNodeTitle) addNodeTitle.textContent = t('ui.modals.addNode');\n\t  if (addRackTitle) addRackTitle.textContent = t('ui.modals.addRack');\n\n\t  const addNodeSave = document.getElementById('add-node-save');\n\t  const addRackSave = document.getElementById('add-rack-save');\n\t  if (addNodeSave) addNodeSave.textContent = t('ui.buttons.addNode');\n\t  if (addRackSave) addRackSave.textContent = t('ui.buttons.addRack');\n\n\t  const nodeIpLabel = document.getElementById('new-node-ip-label');\n\t  const nodeTagsLabel = document.getElementById('new-node-tags-label');\n\t  const nodeIpInput = document.getElementById('new-node-ip');\n\t  const nodeTagsInput = document.getElementById('new-node-tags');\n\t  if (nodeIpLabel && labels.subtitle) nodeIpLabel.textContent = labels.subtitle;\n\t  if (nodeTagsLabel && labels.tags) nodeTagsLabel.textContent = labels.tags + ' (comma separated)';\n\t  if (nodeIpInput && labels.subtitlePlaceholder) nodeIpInput.placeholder = labels.subtitlePlaceholder;\n\t  if (nodeTagsInput && labels.tagsPlaceholder) nodeTagsInput.placeholder = labels.tagsPlaceholder;\n\n\t  const rackNameLabel = document.getElementById('new-rack-name-label');\n\t  const rackIpLabel = document.getElementById('new-rack-ip-label');\n\t  const rackTagsLabel = document.getElementById('new-rack-tags-label');\n\t  const rackIpInput = document.getElementById('new-rack-ip');\n\t  const rackTagsInput = document.getElementById('new-rack-tags');\n\t  if (rackNameLabel && labels.rack) rackNameLabel.textContent = labels.rack + ' Name';\n\t  if (rackIpLabel && labels.rackSubtitle) rackIpLabel.textContent = labels.rackSubtitle;\n\t  if (rackTagsLabel && labels.rackTags) rackTagsLabel.textContent = labels.rackTags + ' (comma separated)';\n\t  if (rackIpInput && labels.rackSubtitlePlaceholder) rackIpInput.placeholder = labels.rackSubtitlePlaceholder;\n\t  if (rackTagsInput && labels.rackTagsPlaceholder) rackTagsInput.placeholder = labels.rackTagsPlaceholder;\n\n\t  const nodeCatSelect = document.getElementById('new-node-category');\n\t  const rackCatSelect = document.getElementById('new-rack-category');\n\t  const defaultCat = MODE_DEFAULT_CATEGORIES[mode] || 'network';\n\t  if (nodeCatSelect) {\n\t    nodeCatSelect.value = defaultCat;\n\t    if (typeof populateModalShapeSelect === 'function') populateModalShapeSelect('new-node-category', 'new-node-shape');\n\t  }\n\t  if (rackCatSelect) {\n\t    rackCatSelect.value = defaultCat;\n\t    if (typeof populateModalShapeSelect === 'function') populateModalShapeSelect('new-rack-category', 'new-rack-shape');\n\t  }\n\n\t  const isNetworkMode = mode === 'network';\n\t  const macRow = document.getElementById('mac-row');\n\t  const rackUnitRow = document.getElementById('rack-unit-row');\n\t  const assignedRackLabel = document.getElementById('assigned-rack-label');\n\t  if (macRow) macRow.style.display = isNetworkMode ? 'flex' : 'none';\n\t  if (rackUnitRow) rackUnitRow.style.display = isNetworkMode ? 'flex' : 'none';\n\t  if (assignedRackLabel && labels.rack) assignedRackLabel.textContent = 'Assigned ' + labels.rack + ':';\n\t}\n\n\tsetTimeout(() => {\n\t  const catSelect = document.getElementById('shape-category-select');\n\t  if (catSelect) {\n\t    catSelect.value = MODE_DEFAULT_CATEGORIES[PAGE_STATE.mappingMode || 'network'] || 'network';\n\t    populateShapeSelect();\n\t  }\n\t  updateCanvasStyleOptions();\n\t}, 100);\n\n\tfunction startVideoRecording() {\n\t  if (RECORDING_STATE.isVideoRecording) return;\n\n\t  const svg = document.getElementById('map');\n\t  const svgRect = svg.getBoundingClientRect();\n\t  const width = Math.min(1280, svgRect.width);\n\t  const height = Math.round(width * svgRect.height / svgRect.width);\n\n\t  const canvas = document.createElement('canvas');\n\t  canvas.width = width;\n\t  canvas.height = height;\n\t  const ctx = canvas.getContext('2d');\n\n\t  RECORDING_STATE.videoCanvas = canvas;\n\t  RECORDING_STATE.videoCtx = ctx;\n\t  RECORDING_STATE.videoChunks = [];\n\n\t  const stream = canvas.captureStream(30);\n\t  const mimeType = MediaRecorder.isTypeSupported('video/webm;codecs=vp9') ? 'video/webm;codecs=vp9' :\n\t                   MediaRecorder.isTypeSupported('video/webm;codecs=vp8') ? 'video/webm;codecs=vp8' :\n\t                   MediaRecorder.isTypeSupported('video/webm') ? 'video/webm' : 'video/mp4';\n\n\t  const recorder = new MediaRecorder(stream, { mimeType, videoBitsPerSecond: 10000000 });\n\n\t  recorder.ondataavailable = (e) => {\n\t    if (e.data.size > 0) {\n\t      RECORDING_STATE.videoChunks.push(e.data);\n\t    }\n\t  };\n\n\t  recorder.onstop = () => {\n\t    const blob = new Blob(RECORDING_STATE.videoChunks, { type: mimeType });\n\t    const url = URL.createObjectURL(blob);\n\t    const a = document.createElement('a');\n\t    a.href = url;\n\t    const ext = mimeType.includes('webm') ? 'webm' : 'mp4';\n\t    a.download = `topology-recording-${Date.now()}.${ext}`;\n\t    a.click();\n\t    URL.revokeObjectURL(url);\n\t    logAuditEvent('export', `Exported video recording`);\n\t  };\n\n\t  RECORDING_STATE.videoRecorder = recorder;\n\t  RECORDING_STATE.isVideoRecording = true;\n\t  recorder.start(100);\n\n\t  function renderFrame() {\n\t    if (!RECORDING_STATE.isVideoRecording) return;\n\n\t    const svg = document.getElementById('map');\n\t    const svgData = new XMLSerializer().serializeToString(svg);\n\t    const svgBlob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' });\n\t    const url = URL.createObjectURL(svgBlob);\n\n\t    const img = new Image();\n\t    img.onload = () => {\n\t      ctx.fillStyle = PAGE_STATE.background || '#050608';\n\t      ctx.fillRect(0, 0, width, height);\n\t      ctx.drawImage(img, 0, 0, width, height);\n\t      URL.revokeObjectURL(url);\n\t      if (RECORDING_STATE.isVideoRecording) {\n\t        RECORDING_STATE.videoAnimFrame = requestAnimationFrame(renderFrame);\n\t      }\n\t    };\n\t    img.onerror = () => {\n\t      URL.revokeObjectURL(url);\n\t      if (RECORDING_STATE.isVideoRecording) {\n\t        RECORDING_STATE.videoAnimFrame = requestAnimationFrame(renderFrame);\n\t      }\n\t    };\n\t    img.src = url;\n\t  }\n\n\t  renderFrame();\n\n\t  document.getElementById('record-btn').style.background = '#f56565';\n\t  document.getElementById('record-btn').style.color = 'white';\n\t  document.getElementById('stop-btn').style.display = 'inline-block';\n\t  logAuditEvent('recording', 'Started video recording');\n\t}\n\n\tfunction stopVideoRecording() {\n\t  if (!RECORDING_STATE.isVideoRecording) return;\n\n\t  RECORDING_STATE.isVideoRecording = false;\n\t  if (RECORDING_STATE.videoAnimFrame) {\n\t    cancelAnimationFrame(RECORDING_STATE.videoAnimFrame);\n\t  }\n\t  if (RECORDING_STATE.videoRecorder && RECORDING_STATE.videoRecorder.state !== 'inactive') {\n\t    RECORDING_STATE.videoRecorder.stop();\n\t  }\n\n\t  document.getElementById('record-btn').style.background = 'var(--btn-bg)';\n\t  document.getElementById('record-btn').style.color = '#f56565';\n\t  document.getElementById('stop-btn').style.display = 'none';\n\t  logAuditEvent('recording', 'Stopped video recording');\n\t}\n\n\tasync function exportRecordingVideo() {\n\t  if (!RECORDING_STATE.currentRecording || RECORDING_STATE.currentRecording.frames.length < 2) {\n\t    showAlert(t(\"dialogs.selectRecordingForVideo\"));\n\t    return;\n\t  }\n\n\t  const rec = RECORDING_STATE.currentRecording;\n\t  const frames = rec.frames;\n\t  const svg = document.getElementById('map');\n\t  const viewBox = svg.getAttribute('viewBox').split(' ').map(Number);\n\t  const width = 1920;\n\t  const height = Math.round(width * viewBox[3] / viewBox[2]);\n\n\t  const canvas = document.createElement('canvas');\n\t  canvas.width = width;\n\t  canvas.height = height;\n\t  const ctx = canvas.getContext('2d');\n\n\t  const progressDiv = document.createElement('div');\n\t  progressDiv.style.cssText = 'position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);background:var(--panel);padding:20px 40px;border-radius:8px;border:1px solid var(--edge-main);z-index:99999999;text-align:center;';\n\t  progressDiv.innerHTML = '<div style=\"color:var(--text-main);margin-bottom:10px;\">' + t(\"emptyStates.generatingVideo\") + '</div><div id=\"video-progress\" style=\"color:var(--accent);margin-bottom:8px;\">0%</div><div style=\"color:var(--text-soft);font-size:12px;\">' + t(\"messages.pleaseWait\") + '</div>';\n\t  document.body.appendChild(progressDiv);\n\n\t  const stream = canvas.captureStream(30);\n\t  const mimeType = MediaRecorder.isTypeSupported('video/webm;codecs=vp9') ? 'video/webm;codecs=vp9' :\n\t                   MediaRecorder.isTypeSupported('video/webm') ? 'video/webm' : 'video/mp4';\n\t  const chunks = [];\n\t  const recorder = new MediaRecorder(stream, { mimeType, videoBitsPerSecond: 10000000 });\n\n\t  recorder.ondataavailable = (e) => {\n\t    if (e.data.size > 0) chunks.push(e.data);\n\t  };\n\n\t  const recorderReady = new Promise(resolve => {\n\t    recorder.onstop = resolve;\n\t  });\n\n\t  recorder.start(100);\n\n\t  const originalPositions = JSON.parse(JSON.stringify(savedPositions));\n\t  const originalWaypoints = {};\n\t  EDGE_DATA.list.forEach(edge => {\n\t    originalWaypoints[edge.id] = edge.waypoints ? edge.waypoints.map(wp => ({ x: wp.x, y: wp.y })) : [];\n\t  });\n\n\t  for (let i = 0; i < frames.length; i++) {\n\t    const frame = frames[i];\n\t    Object.keys(frame.positions).forEach(id => {\n\t      if (savedPositions[id]) {\n\t        savedPositions[id].x = frame.positions[id].x;\n\t        savedPositions[id].y = frame.positions[id].y;\n\t      }\n\t    });\n\t    if (frame.waypoints) {\n\t      EDGE_DATA.list.forEach(edge => {\n\t        if (frame.waypoints[edge.id]) {\n\t          edge.waypoints = frame.waypoints[edge.id].map(wp => ({ x: wp.x, y: wp.y }));\n\t        } else {\n\t          edge.waypoints = [];\n\t        }\n\t      });\n\t    }\n\t    forgeTheTopology();\n\n\t    const svgData = new XMLSerializer().serializeToString(svg);\n\t    const svgBlob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' });\n\t    const url = URL.createObjectURL(svgBlob);\n\n\t    await new Promise((resolve) => {\n\t      const img = new Image();\n\t      img.onload = () => {\n\t        ctx.fillStyle = PAGE_STATE.panel || '#0b0e13';\n\t        ctx.fillRect(0, 0, width, height);\n\t        ctx.drawImage(img, 0, 0, width, height);\n\t        URL.revokeObjectURL(url);\n\t        document.getElementById('video-progress').textContent = Math.round((i / frames.length) * 100) + '%';\n\t        resolve();\n\t      };\n\t      img.onerror = () => {\n\t        URL.revokeObjectURL(url);\n\t        resolve();\n\t      };\n\t      img.src = url;\n\t    });\n\n\t    const frameDelay = i < frames.length - 1 ? (frames[i + 1].time - frame.time) : 100;\n\t    await new Promise(r => setTimeout(r, Math.max(33, frameDelay / 2)));\n\t  }\n\n\t  recorder.stop();\n\t  await recorderReady;\n\n\t  Object.keys(originalPositions).forEach(id => {\n\t    if (savedPositions[id]) {\n\t      savedPositions[id].x = originalPositions[id].x;\n\t      savedPositions[id].y = originalPositions[id].y;\n\t    }\n\t  });\n\t  EDGE_DATA.list.forEach(edge => {\n\t    if (originalWaypoints[edge.id]) {\n\t      edge.waypoints = originalWaypoints[edge.id].map(wp => ({ x: wp.x, y: wp.y }));\n\t    }\n\t  });\n\t  forgeTheTopology();\n\n\t  const blob = new Blob(chunks, { type: mimeType });\n\t  const url = URL.createObjectURL(blob);\n\t  const a = document.createElement('a');\n\t  a.href = url;\n\t  const ext = mimeType.includes('webm') ? 'webm' : 'mp4';\n\t  a.download = (rec.name || 'recording').replace(/[^a-z0-9]/gi, '-') + '.' + ext;\n\t  a.click();\n\t  URL.revokeObjectURL(url);\n\n\t  progressDiv.remove();\n\t  logAuditEvent('export', `Exported video: ${rec.name}`);\n\t}\n\n\tfunction exportRecordingGIF() {\n\t  exportRecordingVideo();\n\t}\n\n\tdocument.getElementById('record-btn')?.addEventListener('click', () => {\n\t  const expanded = document.getElementById('recording-expanded');\n\t  if (expanded.style.display === 'none') {\n\t    expanded.style.display = 'inline-flex';\n\t  } else {\n\t    startRecording();\n\t  }\n\t});\n\tdocument.getElementById('step-record-btn')?.addEventListener('click', () => {\n\t  if (RECORDING_STATE.isStepRecording) {\n\t    captureStepFrame();\n\t  } else {\n\t    startStepRecording();\n\t  }\n\t});\n\tdocument.getElementById('stop-btn')?.addEventListener('click', () => {\n\t  if (RECORDING_STATE.isStepRecording) {\n\t    stopStepRecording();\n\t  } else {\n\t    stopRecording();\n\t  }\n\t});\n\tdocument.getElementById('play-btn')?.addEventListener('click', () => {\n\t  if (RECORDING_STATE.isRecording) {\n\t    stopRecording();\n\t    playRecording();\n\t    return;\n\t  }\n\t  if (RECORDING_STATE.isStepRecording) {\n\t    stopStepRecording();\n\t    playRecording();\n\t    return;\n\t  }\n\t  const expanded = document.getElementById('recording-expanded');\n\t  if (expanded.style.display === 'none') {\n\t    expanded.style.display = 'inline-flex';\n\t  } else {\n\t    playRecording();\n\t  }\n\t});\n\tdocument.getElementById('pause-btn')?.addEventListener('click', pauseRecording);\n\tdocument.getElementById('playback-scrubber')?.addEventListener('input', (e) => {\n\t  seekRecording(parseFloat(e.target.value));\n\t});\n\tdocument.getElementById('playback-speed')?.addEventListener('change', (e) => {\n\t  RECORDING_STATE.playbackSpeed = parseFloat(e.target.value);\n\t});\n\tdocument.getElementById('loop-playback')?.addEventListener('change', (e) => {\n\t  RECORDING_STATE.loopPlayback = e.target.checked;\n\t});\n\tdocument.getElementById('recordings-btn')?.addEventListener('click', () => {\n\t  renderRecordingsList();\n\t  document.getElementById('trail-enabled-toggle').checked = PAGE_STATE.motionTrailsEnabled !== false;\n\t  document.getElementById('trail-length-slider').value = PAGE_STATE.motionTrailLength || 8;\n\t  document.getElementById('trail-length-value').textContent = PAGE_STATE.motionTrailLength || 8;\n\t  document.getElementById('trail-style-select').value = PAGE_STATE.motionTrailStyle || 'solid';\n\t  document.getElementById('trail-arrow-toggle').checked = PAGE_STATE.motionTrailArrow !== false;\n\t  document.getElementById('recordings-modal').classList.add('active');\n\t});\n\tdocument.getElementById('recordings-modal-close')?.addEventListener('click', () => {\n\t  document.getElementById('recordings-modal').classList.remove('active');\n\t});\n\tdocument.getElementById('recordings-modal')?.addEventListener('click', (e) => {\n\t  if (e.target.id === 'recordings-modal') e.target.classList.remove('active');\n\t});\n\n\tdocument.getElementById('trail-enabled-toggle')?.addEventListener('change', (e) => {\n\t  PAGE_STATE.motionTrailsEnabled = e.target.checked;\n\t  if (!e.target.checked) {\n\t    RECORDING_STATE.trailHistory = {};\n\t    forgeTheTopology();\n\t  }\n\t});\n\tdocument.getElementById('trail-length-slider')?.addEventListener('input', (e) => {\n\t  PAGE_STATE.motionTrailLength = parseInt(e.target.value);\n\t  document.getElementById('trail-length-value').textContent = e.target.value;\n\t});\n\tdocument.getElementById('trail-style-select')?.addEventListener('change', (e) => {\n\t  PAGE_STATE.motionTrailStyle = e.target.value;\n\t  forgeTheTopology();\n\t});\n\tdocument.getElementById('trail-arrow-toggle')?.addEventListener('change', (e) => {\n\t  PAGE_STATE.motionTrailArrow = e.target.checked;\n\t  forgeTheTopology();\n\t});\n\n\tdocument.getElementById('node-trail-enabled')?.addEventListener('change', (e) => {\n\t  if (currentNodeId && NODE_DATA[currentNodeId]) {\n\t    NODE_DATA[currentNodeId].trailEnabled = e.target.checked;\n\t  }\n\t});\n\n\tconst MODAL_SHAPE_PREVIEWS = {\n\t  'circle': '<circle cx=\"16\" cy=\"16\" r=\"11\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"/>',\n\t  'square': '<rect x=\"5\" y=\"5\" width=\"22\" height=\"22\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"/>',\n\t  'rectangle': '<rect x=\"3\" y=\"8\" width=\"26\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"/>',\n\t  'triangle': '<polygon points=\"16,4 28,28 4,28\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"/>',\n\t  'hexagon': '<polygon points=\"16,3 27,9 27,22 16,28 5,22 5,9\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"/>',\n\t  'diamond': '<polygon points=\"16,3 29,16 16,29 3,16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"/>',\n\t  'star': '<polygon points=\"16,3 19,12 29,12 21,18 24,28 16,22 8,28 11,18 3,12 13,12\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"/>',\n\t  'stop-sign': '<polygon points=\"11,3 21,3 29,11 29,21 21,29 11,29 3,21 3,11\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"/>',\n\t  'octagon': '<polygon points=\"11,3 21,3 29,11 29,21 21,29 11,29 3,21 3,11\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"/>',\n\t  'pentagon': '<polygon points=\"16,3 29,13 24,28 8,28 3,13\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"/>',\n\t  'cross': '<path d=\"M12,4 h8 v8 h8 v8 h-8 v8 h-8 v-8 h-8 v-8 h8 z\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"/>',\n\t  'rounded-square': '<rect x=\"5\" y=\"5\" width=\"22\" height=\"22\" rx=\"5\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"/>',\n\t  'pill': '<rect x=\"4\" y=\"10\" width=\"24\" height=\"12\" rx=\"6\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"/>',\n\t  'parallelogram': '<polygon points=\"8,6 28,6 24,26 4,26\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"/>',\n\t  'trapezoid': '<polygon points=\"8,6 24,6 28,26 4,26\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"/>',\n\t  'server': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"6\" y=\"4\" width=\"20\" height=\"24\" rx=\"2\"/><line x1=\"6\" y1=\"12\" x2=\"26\" y2=\"12\"/><line x1=\"6\" y1=\"20\" x2=\"26\" y2=\"20\"/><circle cx=\"22\" cy=\"8\" r=\"1.5\" fill=\"currentColor\"/><circle cx=\"22\" cy=\"16\" r=\"1.5\" fill=\"currentColor\"/><circle cx=\"22\" cy=\"24\" r=\"1.5\" fill=\"currentColor\"/></g>',\n\t  'pc': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"5\" y=\"4\" width=\"22\" height=\"16\" rx=\"1\"/><line x1=\"12\" y1=\"24\" x2=\"20\" y2=\"24\"/><line x1=\"16\" y1=\"20\" x2=\"16\" y2=\"24\"/></g>',\n\t  'laptop': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"6\" y=\"6\" width=\"20\" height=\"14\" rx=\"1\"/><path d=\"M3,22 h26 l-2,4 h-22 z\"/></g>',\n\t  'phone': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"9\" y=\"3\" width=\"14\" height=\"26\" rx=\"2\"/><line x1=\"13\" y1=\"25\" x2=\"19\" y2=\"25\"/></g>',\n\t  'printer': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"8\" y=\"12\" width=\"16\" height=\"10\" rx=\"1\"/><rect x=\"10\" y=\"4\" width=\"12\" height=\"8\"/><rect x=\"10\" y=\"22\" width=\"12\" height=\"6\"/></g>',\n\t  'pi': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"5\" y=\"8\" width=\"22\" height=\"16\" rx=\"2\"/><circle cx=\"10\" cy=\"13\" r=\"1.5\" fill=\"currentColor\"/><rect x=\"18\" y=\"11\" width=\"6\" height=\"4\" rx=\"0.5\"/><line x1=\"14\" y1=\"20\" x2=\"22\" y2=\"20\"/></g>',\n\t  'sensor': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><circle cx=\"16\" cy=\"16\" r=\"6\"/><circle cx=\"16\" cy=\"16\" r=\"2\" fill=\"currentColor\"/><path d=\"M8,8 Q4,16 8,24\"/><path d=\"M24,8 Q28,16 24,24\"/></g>',\n\t  'router': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"4\" y=\"14\" width=\"24\" height=\"12\" rx=\"2\"/><line x1=\"10\" y1=\"14\" x2=\"10\" y2=\"8\"/><line x1=\"16\" y1=\"14\" x2=\"16\" y2=\"6\"/><line x1=\"22\" y1=\"14\" x2=\"22\" y2=\"8\"/><circle cx=\"10\" cy=\"7\" r=\"1.5\" fill=\"currentColor\"/><circle cx=\"16\" cy=\"5\" r=\"1.5\" fill=\"currentColor\"/><circle cx=\"22\" cy=\"7\" r=\"1.5\" fill=\"currentColor\"/></g>',\n\t  'switch': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"3\" y=\"11\" width=\"26\" height=\"10\" rx=\"2\"/><circle cx=\"8\" cy=\"16\" r=\"1.5\" fill=\"currentColor\"/><circle cx=\"13\" cy=\"16\" r=\"1.5\" fill=\"currentColor\"/><circle cx=\"18\" cy=\"16\" r=\"1.5\" fill=\"currentColor\"/><circle cx=\"23\" cy=\"16\" r=\"1.5\" fill=\"currentColor\"/></g>',\n\t  'firewall': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"5\" y=\"5\" width=\"22\" height=\"22\" rx=\"1\"/><line x1=\"5\" y1=\"11\" x2=\"27\" y2=\"11\"/><line x1=\"5\" y1=\"17\" x2=\"27\" y2=\"17\"/><line x1=\"11\" y1=\"5\" x2=\"11\" y2=\"27\"/><line x1=\"17\" y1=\"5\" x2=\"17\" y2=\"27\"/></g>',\n\t  'access-point': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><path d=\"M16,18 L10,26 h12 z\"/><circle cx=\"16\" cy=\"16\" r=\"2\" fill=\"currentColor\"/><path d=\"M9,10 Q16,4 23,10\" stroke-linecap=\"round\"/><path d=\"M6,7 Q16,0 26,7\" stroke-linecap=\"round\"/></g>',\n\t  'load-balancer': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"6\" y=\"12\" width=\"20\" height=\"8\" rx=\"2\"/><line x1=\"16\" y1=\"8\" x2=\"16\" y2=\"12\"/><line x1=\"16\" y1=\"20\" x2=\"8\" y2=\"26\"/><line x1=\"16\" y1=\"20\" x2=\"16\" y2=\"26\"/><line x1=\"16\" y1=\"20\" x2=\"24\" y2=\"26\"/><circle cx=\"16\" cy=\"7\" r=\"2\" fill=\"currentColor\"/></g>',\n\t  'gateway': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"4\" y=\"10\" width=\"24\" height=\"12\" rx=\"2\"/><line x1=\"4\" y1=\"16\" x2=\"0\" y2=\"16\"/><line x1=\"28\" y1=\"16\" x2=\"32\" y2=\"16\"/><circle cx=\"16\" cy=\"16\" r=\"3\"/></g>',\n\t  'vpn': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"5\" y=\"8\" width=\"22\" height=\"16\" rx=\"2\"/><path d=\"M16,12 v6 M13,14 h6\" stroke-width=\"2.5\" stroke-linecap=\"round\"/><circle cx=\"11\" cy=\"20\" r=\"1\" fill=\"currentColor\"/><circle cx=\"21\" cy=\"20\" r=\"1\" fill=\"currentColor\"/></g>',\n\t  'nas': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"6\" y=\"4\" width=\"20\" height=\"24\" rx=\"2\"/><line x1=\"6\" y1=\"10\" x2=\"26\" y2=\"10\"/><line x1=\"6\" y1=\"16\" x2=\"26\" y2=\"16\"/><line x1=\"6\" y1=\"22\" x2=\"26\" y2=\"22\"/><circle cx=\"22\" cy=\"7\" r=\"1\" fill=\"currentColor\"/><circle cx=\"22\" cy=\"13\" r=\"1\" fill=\"currentColor\"/><circle cx=\"22\" cy=\"19\" r=\"1\" fill=\"currentColor\"/><circle cx=\"22\" cy=\"25\" r=\"1\" fill=\"currentColor\"/></g>',\n\t  'cloud': '<path d=\"M8,22 Q2,22 2,17 Q2,13 6,12 Q6,7 11,6 Q16,5 19,8 Q21,6 24,7 Q28,8 28,12 Q30,13 30,17 Q30,22 24,22 z\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"/>',\n\t  'database': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><ellipse cx=\"16\" cy=\"8\" rx=\"10\" ry=\"4\"/><path d=\"M6,8 v16 Q6,28 16,28 Q26,28 26,24 v-16\"/><path d=\"M6,14 Q6,18 16,18 Q26,18 26,14\"/></g>',\n\t  'docker': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><path d=\"M2,16 h6 v-6 h4 v-4 h4 v4 h4 v-4 h4 v8 Q28,24 20,26 Q10,28 4,22 z\"/><rect x=\"10\" y=\"12\" width=\"3\" height=\"3\"/><rect x=\"14\" y=\"12\" width=\"3\" height=\"3\"/><rect x=\"14\" y=\"8\" width=\"3\" height=\"3\"/></g>',\n\t  'container': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><path d=\"M4,12 L16,6 L28,12 L16,18 z\"/><path d=\"M4,12 v8 L16,26 v-8\"/><path d=\"M28,12 v8 L16,26 v-8\"/></g>',\n\t  'vm': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"4\" y=\"4\" width=\"24\" height=\"24\" rx=\"2\"/><rect x=\"7\" y=\"7\" width=\"18\" height=\"14\" rx=\"1\"/><line x1=\"12\" y1=\"24\" x2=\"20\" y2=\"24\"/></g>',\n\t  'kubernetes': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><polygon points=\"16,2 28,9 28,23 16,30 4,23 4,9\"/><circle cx=\"16\" cy=\"16\" r=\"4\"/><line x1=\"16\" y1=\"12\" x2=\"16\" y2=\"4\"/><line x1=\"19\" y1=\"14\" x2=\"26\" y2=\"10\"/><line x1=\"19\" y1=\"18\" x2=\"26\" y2=\"22\"/><line x1=\"16\" y1=\"20\" x2=\"16\" y2=\"28\"/><line x1=\"13\" y1=\"18\" x2=\"6\" y2=\"22\"/><line x1=\"13\" y1=\"14\" x2=\"6\" y2=\"10\"/></g>',\n\t  'api': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"4\" y=\"6\" width=\"24\" height=\"20\" rx=\"2\"/><path d=\"M10,16 l2,-6 2,6 M10.5,14 h3\" stroke-linecap=\"round\"/><path d=\"M17,10 h3 Q22,10 22,13 Q22,16 20,16 h-3 M17,16 v6\" stroke-linecap=\"round\"/><line x1=\"25\" y1=\"10\" x2=\"25\" y2=\"22\"/></g>',\n\t  'queue': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"4\" y=\"8\" width=\"24\" height=\"16\" rx=\"2\"/><line x1=\"12\" y1=\"8\" x2=\"12\" y2=\"24\"/><line x1=\"20\" y1=\"8\" x2=\"20\" y2=\"24\"/><path d=\"M6,16 h3 M14,16 h4 M22,16 h4\" stroke-linecap=\"round\"/></g>',\n\t  'lambda': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M8,6 L16,26 L24,6\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/><line x1=\"6\" y1=\"16\" x2=\"12\" y2=\"16\" stroke-linecap=\"round\"/></g>',\n\t  'bucket': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><path d=\"M6,8 h20 l-2,18 h-16 z\"/><ellipse cx=\"16\" cy=\"8\" rx=\"10\" ry=\"3\"/></g>',\n\t  'shield': '<path d=\"M16,3 L27,8 v10 Q27,26 16,29 Q5,26 5,18 v-10 z\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"/>',\n\t  'camera': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"4\" y=\"10\" width=\"24\" height=\"16\" rx=\"2\"/><circle cx=\"16\" cy=\"18\" r=\"5\"/><circle cx=\"16\" cy=\"18\" r=\"2\" fill=\"currentColor\"/><rect x=\"10\" y=\"6\" width=\"12\" height=\"4\" rx=\"1\"/></g>',\n\t  'monitor': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"4\" y=\"4\" width=\"24\" height=\"16\" rx=\"2\"/><line x1=\"12\" y1=\"24\" x2=\"20\" y2=\"24\"/><line x1=\"16\" y1=\"20\" x2=\"16\" y2=\"24\"/><path d=\"M8,8 h4 M8,11 h6 M8,14 h5 M18,8 Q20,8 20,14 Q20,16 18,16\" stroke-width=\"1\"/></g>',\n\t  'thermostat': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><circle cx=\"16\" cy=\"16\" r=\"11\"/><circle cx=\"16\" cy=\"16\" r=\"7\"/><line x1=\"16\" y1=\"9\" x2=\"16\" y2=\"16\" stroke-width=\"2\" stroke-linecap=\"round\"/></g>',\n\t  'doorbell': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"9\" y=\"3\" width=\"14\" height=\"26\" rx=\"4\"/><circle cx=\"16\" cy=\"14\" r=\"4\"/><circle cx=\"16\" cy=\"22\" r=\"1.5\" fill=\"currentColor\"/></g>',\n\t  'smart-lock': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"7\" y=\"14\" width=\"18\" height=\"14\" rx=\"2\"/><path d=\"M11,14 v-4 Q11,4 16,4 Q21,4 21,10 v4\"/><circle cx=\"16\" cy=\"21\" r=\"2\" fill=\"currentColor\"/></g>',\n\t  'smart-bulb': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><path d=\"M11,18 Q6,12 8,7 Q10,3 16,3 Q22,3 24,7 Q26,12 21,18 z\"/><rect x=\"11\" y=\"18\" width=\"10\" height=\"4\" rx=\"1\"/><line x1=\"12\" y1=\"24\" x2=\"20\" y2=\"24\"/><line x1=\"13\" y1=\"26\" x2=\"19\" y2=\"26\"/></g>',\n\t  'smart-plug': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"6\" y=\"8\" width=\"20\" height=\"16\" rx=\"3\"/><circle cx=\"12\" cy=\"16\" r=\"2\" fill=\"currentColor\"/><circle cx=\"20\" cy=\"16\" r=\"2\" fill=\"currentColor\"/><line x1=\"16\" y1=\"24\" x2=\"16\" y2=\"28\" stroke-width=\"2\"/></g>',\n\t  'smart-speaker': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><path d=\"M8,28 Q8,8 16,4 Q24,8 24,28 z\"/><circle cx=\"16\" cy=\"20\" r=\"4\"/><circle cx=\"16\" cy=\"20\" r=\"1.5\" fill=\"currentColor\"/></g>',\n\t  'smart-tv': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"3\" y=\"5\" width=\"26\" height=\"18\" rx=\"1\"/><line x1=\"10\" y1=\"27\" x2=\"22\" y2=\"27\"/><line x1=\"12\" y1=\"23\" x2=\"12\" y2=\"27\"/><line x1=\"20\" y1=\"23\" x2=\"20\" y2=\"27\"/></g>',\n\t  'hub': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><circle cx=\"16\" cy=\"16\" r=\"5\"/><circle cx=\"16\" cy=\"16\" r=\"2\" fill=\"currentColor\"/><line x1=\"16\" y1=\"5\" x2=\"16\" y2=\"11\"/><line x1=\"16\" y1=\"21\" x2=\"16\" y2=\"27\"/><line x1=\"5\" y1=\"16\" x2=\"11\" y2=\"16\"/><line x1=\"21\" y1=\"16\" x2=\"27\" y2=\"16\"/></g>',\n\t  'smoke-detector': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><circle cx=\"16\" cy=\"16\" r=\"11\"/><circle cx=\"16\" cy=\"16\" r=\"3\"/><line x1=\"16\" y1=\"5\" x2=\"16\" y2=\"7\"/><line x1=\"16\" y1=\"25\" x2=\"16\" y2=\"27\"/><line x1=\"5\" y1=\"16\" x2=\"7\" y2=\"16\"/><line x1=\"25\" y1=\"16\" x2=\"27\" y2=\"16\"/></g>',\n\t  'motion-sensor': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><path d=\"M8,24 Q4,12 16,6 Q28,12 24,24\"/><circle cx=\"16\" cy=\"16\" r=\"3\" fill=\"currentColor\"/><path d=\"M12,20 Q10,16 14,12\"/><path d=\"M20,20 Q22,16 18,12\"/></g>',\n\t  'garage': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><path d=\"M4,28 v-14 L16,4 L28,14 v14 z\"/><rect x=\"8\" y=\"16\" width=\"16\" height=\"12\"/><line x1=\"8\" y1=\"20\" x2=\"24\" y2=\"20\"/><line x1=\"8\" y1=\"24\" x2=\"24\" y2=\"24\"/></g>',\n\t  'sprinkler': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><line x1=\"16\" y1=\"4\" x2=\"16\" y2=\"14\"/><path d=\"M10,14 h12 v3 Q16,22 10,17 z\"/><path d=\"M8,20 Q6,24 4,26\" stroke-linecap=\"round\"/><path d=\"M16,22 v6\" stroke-linecap=\"round\"/><path d=\"M24,20 Q26,24 28,26\" stroke-linecap=\"round\"/></g>',\n\t  'vacuum': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><circle cx=\"16\" cy=\"16\" r=\"11\"/><circle cx=\"16\" cy=\"16\" r=\"6\"/><circle cx=\"16\" cy=\"16\" r=\"2\" fill=\"currentColor\"/><line x1=\"16\" y1=\"5\" x2=\"20\" y2=\"3\" stroke-linecap=\"round\"/></g>',\n\t  'basketball-ball': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><circle cx=\"16\" cy=\"16\" r=\"12\"/><path d=\"M4,16 h24\"/><path d=\"M16,4 v24\"/><path d=\"M6,7 Q16,14 6,25\"/><path d=\"M26,7 Q16,14 26,25\"/></g>',\n\t  'football-ball': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><ellipse cx=\"16\" cy=\"16\" rx=\"12\" ry=\"8\" transform=\"rotate(-30,16,16)\"/><path d=\"M10,10 L22,22 M12,8 L24,20\" stroke-linecap=\"round\"/></g>',\n\t  'soccer-ball': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><circle cx=\"16\" cy=\"16\" r=\"12\"/><polygon points=\"16,8 20,12 18,17 14,17 12,12\" fill=\"currentColor\" stroke=\"none\"/></g>',\n\t  'hockey-puck': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><ellipse cx=\"16\" cy=\"14\" rx=\"12\" ry=\"5\"/><path d=\"M4,14 v4 Q4,23 16,23 Q28,23 28,18 v-4\"/></g>',\n\t  'baseball-ball': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><circle cx=\"16\" cy=\"16\" r=\"12\"/><path d=\"M8,6 Q14,12 8,26\"/><path d=\"M24,6 Q18,12 24,26\"/></g>',\n\t  'tennis-ball': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><circle cx=\"16\" cy=\"16\" r=\"12\"/><path d=\"M6,6 Q16,16 6,26\"/><path d=\"M26,6 Q16,16 26,26\"/></g>',\n\t  'volleyball': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><circle cx=\"16\" cy=\"16\" r=\"12\"/><path d=\"M16,4 Q12,16 16,28\"/><path d=\"M5,10 Q16,14 27,10\"/><path d=\"M5,22 Q16,18 27,22\"/></g>',\n\t  'rugby-ball': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><ellipse cx=\"16\" cy=\"16\" rx=\"13\" ry=\"8\" transform=\"rotate(-30,16,16)\"/><path d=\"M10,10 L22,22\"/></g>',\n\t  'golf-ball': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><circle cx=\"16\" cy=\"16\" r=\"10\"/><circle cx=\"14\" cy=\"14\" r=\"0.8\" fill=\"currentColor\"/><circle cx=\"18\" cy=\"12\" r=\"0.8\" fill=\"currentColor\"/><circle cx=\"16\" cy=\"16\" r=\"0.8\" fill=\"currentColor\"/></g>',\n\t  'frisbee': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><ellipse cx=\"16\" cy=\"16\" rx=\"13\" ry=\"5\"/><ellipse cx=\"16\" cy=\"15\" rx=\"8\" ry=\"3\"/></g>',\n\t  'cricket-ball': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><circle cx=\"16\" cy=\"16\" r=\"11\"/><path d=\"M10,6 Q16,16 10,26\" stroke-dasharray=\"2,2\"/></g>',\n\t  'lacrosse-stick': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><line x1=\"8\" y1=\"28\" x2=\"20\" y2=\"6\" stroke-width=\"2\"/><path d=\"M18,8 Q26,4 26,12 Q26,16 20,14\"/><line x1=\"20\" y1=\"9\" x2=\"24\" y2=\"13\"/></g>',\n\t  'golf-flag': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><line x1=\"10\" y1=\"4\" x2=\"10\" y2=\"28\" stroke-width=\"2\"/><polygon points=\"10,4 26,10 10,16\" fill=\"currentColor\" opacity=\"0.3\" stroke=\"currentColor\"/><ellipse cx=\"16\" cy=\"28\" rx=\"10\" ry=\"2\"/></g>',\n\t  'tactical-x': '<g stroke=\"currentColor\" stroke-width=\"3\" stroke-linecap=\"round\"><line x1=\"6\" y1=\"6\" x2=\"26\" y2=\"26\"/><line x1=\"26\" y1=\"6\" x2=\"6\" y2=\"26\"/></g>',\n\t  'tactical-o': '<circle cx=\"16\" cy=\"16\" r=\"10\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"3\"/>',\n\t  'tactical-star': '<polygon points=\"16,3 19,12 29,12 21,18 24,28 16,22 8,28 11,18 3,12 13,12\" fill=\"currentColor\" opacity=\"0.3\" stroke=\"currentColor\" stroke-width=\"1.5\"/>',\n\t  'patch-panel': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"3\" y=\"10\" width=\"26\" height=\"12\" rx=\"1\"/><circle cx=\"8\" cy=\"14\" r=\"1.5\"/><circle cx=\"13\" cy=\"14\" r=\"1.5\"/><circle cx=\"18\" cy=\"14\" r=\"1.5\"/><circle cx=\"23\" cy=\"14\" r=\"1.5\"/><circle cx=\"8\" cy=\"19\" r=\"1.5\"/><circle cx=\"13\" cy=\"19\" r=\"1.5\"/><circle cx=\"18\" cy=\"19\" r=\"1.5\"/><circle cx=\"23\" cy=\"19\" r=\"1.5\"/></g>',\n\t  'ups': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"6\" y=\"4\" width=\"20\" height=\"24\" rx=\"2\"/><rect x=\"10\" y=\"8\" width=\"12\" height=\"6\" rx=\"1\"/><circle cx=\"12\" cy=\"20\" r=\"1.5\" fill=\"currentColor\"/><circle cx=\"16\" cy=\"20\" r=\"1.5\"/><circle cx=\"20\" cy=\"20\" r=\"1.5\"/></g>',\n\t  'pdu': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"11\" y=\"2\" width=\"10\" height=\"28\" rx=\"1\"/><circle cx=\"16\" cy=\"7\" r=\"2\"/><circle cx=\"16\" cy=\"13\" r=\"2\"/><circle cx=\"16\" cy=\"19\" r=\"2\"/><circle cx=\"16\" cy=\"25\" r=\"2\" fill=\"currentColor\"/></g>',\n\t  'rack-shelf': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"3\" y=\"12\" width=\"26\" height=\"8\" rx=\"1\"/><line x1=\"3\" y1=\"17\" x2=\"29\" y2=\"17\" stroke-dasharray=\"3,2\"/><circle cx=\"7\" cy=\"14.5\" r=\"1\" fill=\"currentColor\"/></g>',\n\t  'blank-panel': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"3\" y=\"12\" width=\"26\" height=\"8\" rx=\"1\"/><circle cx=\"6\" cy=\"16\" r=\"1\"/><circle cx=\"26\" cy=\"16\" r=\"1\"/></g>',\n\t  'cable-management': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"3\" y=\"11\" width=\"26\" height=\"10\" rx=\"1\"/><path d=\"M7,14 Q10,18 13,14 Q16,10 19,14 Q22,18 25,14\" stroke-linecap=\"round\"/></g>',\n\t  'kvm': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"4\" y=\"8\" width=\"24\" height=\"16\" rx=\"2\"/><rect x=\"8\" y=\"11\" width=\"10\" height=\"7\" rx=\"1\"/><circle cx=\"23\" cy=\"14.5\" r=\"2\"/><line x1=\"12\" y1=\"20\" x2=\"20\" y2=\"20\"/></g>'\n\t};\n\n\tfunction getModalShapePreview(shape) {\n\t  const svg = MODAL_SHAPE_PREVIEWS[shape];\n\t  if (svg) return '<svg width=\"36\" height=\"36\" viewBox=\"0 0 32 32\" style=\"color:var(--accent)\">' + svg + '</svg>';\n\t  return '<span style=\"font-size:10px;color:var(--text-soft);text-align:center;line-height:1.2\">' + (shape || 'circle') + '</span>';\n\t}\n\n\tfunction populateModalShapeSelect(categorySelectId, shapeSelectId, previewId) {\n\t  const catSelect = document.getElementById(categorySelectId);\n\t  const shapeSelect = document.getElementById(shapeSelectId);\n\t  if (!catSelect || !shapeSelect) return;\n\t  const category = catSelect.value || 'basic';\n\t  const shapes = SHAPE_CATEGORIES[category] || SHAPE_CATEGORIES.basic;\n\t  shapeSelect.innerHTML = shapes.map(s => `<option value=\"${s.value}\">${s.label}</option>`).join('');\n\t  if (previewId) {\n\t    const preview = document.getElementById(previewId);\n\t    if (preview && shapes.length > 0) preview.innerHTML = getModalShapePreview(shapes[0].value);\n\t  }\n\t}\n\n\tdocument.getElementById('new-node-category')?.addEventListener('change', () => {\n\t  populateModalShapeSelect('new-node-category', 'new-node-shape', 'new-node-shape-preview');\n\t});\n\tdocument.getElementById('new-rack-category')?.addEventListener('change', () => {\n\t  populateModalShapeSelect('new-rack-category', 'new-rack-shape', 'new-rack-shape-preview');\n\t});\n\tdocument.getElementById('new-node-shape')?.addEventListener('change', (e) => {\n\t  const preview = document.getElementById('new-node-shape-preview');\n\t  if (preview) preview.innerHTML = getModalShapePreview(e.target.value);\n\t});\n\tdocument.getElementById('new-rack-shape')?.addEventListener('change', (e) => {\n\t  const preview = document.getElementById('new-rack-shape-preview');\n\t  if (preview) preview.innerHTML = getModalShapePreview(e.target.value);\n\t});\n\n\tpopulateModalShapeSelect('new-node-category', 'new-node-shape', 'new-node-shape-preview');\n\tpopulateModalShapeSelect('new-rack-category', 'new-rack-shape', 'new-rack-shape-preview');\n    </script>\n  </body>\n</html>"
  },
  {
    "path": "theonefile-networkening.html",
    "content": "<!DOCTYPE html> \n <html lang=\"en\" style=\"--panel: #0b0e13; --panel-alt: #10141b; --accent: #4fd1c5; --danger: #f56565; --text-main: #e2e8f0; --text-soft: #94a3b8; --topbar-bg: rgba(9, 12, 20, 0.9); --topbar-border: #1f2533; --topbar-height: 100px; --sidebar-width: 435px; --mobile-footer-height: 20vh; --draw-toolbar-height: 45px; --sidebar-bg: #10141b; --btn-bg: #0b0e13; --btn-text: #e2e8f0; --tag-fill: #1e293b; --tag-text: #e2e8f0; --tag-border: #475569; --input-bg: #0b0e13; --input-text: #e2e8f0; --input-border: #1f2937; --input-font: Inter, system-ui, sans-serif; --input-font-size: 14px; --toolbar-bg: #0f172a; --toolbar-border: #1f2937; --toolbar-text: #94a3b8; --toolbar-btn-bg: #0b0e13; --toolbar-btn-text: #e2e8f0; --minimap-dots: #94a3b8; --canvas-hint-bg: #0f172a; --canvas-hint-color: #94a3b8; --node-fill: #1e293b; --node-stroke: #475569; --node-title: #e2e8f0; --node-sub: #94a3b8; --node-title-size: 18px; --node-sub-size: 13px; --node-font: Inter, system-ui, sans-serif; --default-edge: #475569; --selection-handle: #f59e0b; --selection-handle-size: 8px; --group-indicator: #4fd1c5;\"><head>\n    <meta charset=\"UTF-8\">\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>The One File: The Networkening</title>\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <!--\n      * ==================================================================================\n      * The One File: The Networkening\n      * !!!!!!!!!!!!!!!!!!!NOTE: THIS IS THE ONLINE VERSION!!!!!!!!!!!!!!!!!!!!!!\n      * Online version uses 3 cdn calls from cdn.jsdelivr.net to display additional icons\n      * Since 3.0 Online version uses http as a form of ping to display uptime\n      * \"There can be only one\". A all in one file topology maker.\n      *\n      * This is your last backup when all others fail. A completely self contained\n      * network topology visualization tool that works as a single HTML file.\n      * Open it anywhere, anytime and the idea lives forever.\n      * ==================================================================================\n      -->\n    <style>\n      :root {\n      color-scheme: dark;\n      --bg: #050608;\n      --panel: #0b0e13;\n      --panel-alt: #10141b;\n      --accent: #4fd1c5;\n      --danger: #f56565;\n      --text-main: #e2e8f0;\n      --text-soft: #94a3b8;\n      --edge-main: #475569;\n      --node-min: 35px;\n      --node-max: 70px;\n      --topbar-bg: rgba(9, 12, 20, 0.9);\n      --topbar-border: #1f2533;\n      }\n\t  html, body, svg, .map-container {\n      touch-action: none;\n      }\n      * {\n      box-sizing: border-box;\n      user-select: none;\n      }\n      input,\n      textarea,\n      [contenteditable=\"true\"] {\n      user-select: text;\n      -webkit-user-select: text;\n      }\n      body {\n      margin: 0;\n      font-family: system-ui, sans-serif;\n      background: radial-gradient(circle at top, #1e2532 0, #050608 70%);\n      color: var(--text-main);\n      display: flex;\n      flex-direction: column;\n      height: 100vh;\n      overflow: hidden;\n      }\n      header {\n      padding: 0 20px;\n      height: var(--topbar-height, 52px);\n      min-height: var(--topbar-height, 52px);\n      background: var(--topbar-bg);\n      backdrop-filter: blur(6px);\n      display: flex;\n      justify-content: space-between;\n      align-items: center;\n      border-bottom: 1px solid var(--topbar-border);\n      gap: 16px;\n      }\n      .title-block {\n      display: flex;\n      flex-direction: column;\n      gap: 6px;\n      min-width: 0;\n      }\n      header h1 {\n      font-size: clamp(22px, 3vw, 32px);\n      margin: 0;\n      white-space: nowrap;\n      text-overflow: ellipsis;\n      overflow: hidden;\n      }\n      .editable-page-title {\n      cursor: pointer;\n      transition: opacity 0.2s;\n      }\n      .editable-page-title:hover {\n      opacity: 0.7;\n      }\n      .save-row {\n      display: flex;\n      align-items: center;\n      gap: 8px;\n      flex-wrap: wrap;\n      }\n\t  .toggle-switch{position:relative;display:inline-block;min-width:44px !important;height:24px;flex-shrink:0;vertical-align:middle;}\n\t\t.toggle-switch input{opacity:0;width:0;height:0;position:absolute;}\n\t\t.toggle-slider{position:absolute;cursor:pointer;top:0;left:0;right:0;bottom:0;background:#475569;transition:.25s;border-radius:24px;}\n\t\t.toggle-slider:before{position:absolute;content:\"\";height:18px;width:18px;left:3px;bottom:3px;background:#e2e8f0;transition:.25s;border-radius:50%;}\n\t\t.toggle-switch input:checked+.toggle-slider{background:var(--accent);}\n\t\t.toggle-switch input:checked+.toggle-slider:before{transform:translateX(20px);}\n\t\t.anim-zone-row{display:flex;justify-content:space-between;align-items:center;padding:6px 0;}\n\t\t.anim-zone-row label{color:var(--text-main);font-size:14px;}\n\t\t.anim-zone-section{font-size:11px;color:var(--text-soft);margin:12px 0 6px;text-transform:uppercase;letter-spacing:0.05em;border-bottom:1px solid var(--edge-main);padding-bottom:4px;}\n\t\t.anim-zone-header{font-size:12px;color:var(--accent);margin-bottom:10px;text-transform:uppercase;letter-spacing:0.05em;font-weight:600;}\n      .save-btn {\n      padding: 6px 12px;\n      background: var(--accent);\n      color: var(--bg);\n      border: none;\n      border-radius: 6px;\n      cursor: pointer;\n      font-size: 14px;\n      font-weight: 600;\n      white-space: nowrap;\n      }\n      .save-btn:hover {\n      opacity: 0.9;\n      }\n      .help-icon {\n      width: 22px;\n      height: 22px;\n      border-radius: 50%;\n      border: 1px solid var(--edge-main);\n      display: inline-flex;\n      align-items: center;\n      justify-content: center;\n      font-size: 14px;\n      cursor: pointer;\n      color: var(--text-soft);\n      background: rgba(15, 23, 42, 0.9);\n      flex-shrink: 0;\n      }\n      .help-icon:hover {\n      color: var(--accent);\n      border-color: var(--accent);\n      }\n      #settings-btn {\n      background: var(--btn-bg, var(--panel));\n      color: var(--btn-text, var(--text-main));\n      border: 1px solid var(--edge-main);\n      padding: 6px 12px;\n      border-radius: 6px;\n      cursor: pointer;\n      font-size: 16px;\n      flex-shrink: 0;\n      }\n      #settings-btn:hover {\n      background: var(--accent);\n      color: var(--bg);\n      }\n      .header-resizer {\n      position: absolute;\n      bottom: 0;\n      left: 0;\n      right: 0;\n      height: 6px;\n      background: transparent;\n      cursor: row-resize;\n      z-index: 1000;\n      transition: background 0.2s;\n      }\n      .header-resizer:hover {\n      background: rgba(79, 209, 197, 0.3);\n      }\n      .header-resizer.resizing {\n      background: rgba(79, 209, 197, 0.5);\n      }\n      .sidebar-resizer {\n      position: absolute;\n      left: 0;\n      top: 0;\n      bottom: 0;\n      width: 6px;\n      background: transparent;\n      cursor: col-resize;\n      z-index: 1000;\n      transition: background 0.2s;\n      }\n      .sidebar-resizer:hover {\n      background: rgba(79, 209, 197, 0.3);\n      }\n      .sidebar-resizer.resizing {\n      background: rgba(79, 209, 197, 0.5);\n      }\n      .mobile-footer-resizer {\n      position: absolute;\n      top: 0;\n      left: 0;\n      right: 0;\n      height: 6px;\n      background: transparent;\n      cursor: row-resize;\n      z-index: 1000;\n      transition: background 0.2s;\n      display: none;\n      }\n      .mobile-footer-resizer:hover {\n      background: rgba(79, 209, 197, 0.3);\n      }\n      .mobile-footer-resizer.resizing {\n      background: rgba(79, 209, 197, 0.5);\n      }\n      @media (max-width: 900px) {\n      .mobile-footer-resizer {\n      display: block;\n      height: 12px;\n      }\n      .sidebar-resizer {\n      display: none;\n      }\n      .header-resizer {\n      height: 12px;\n      }\n      }\n      @media (pointer: coarse) {\n      .header-resizer {\n      height: 16px;\n      }\n      .mobile-footer-resizer {\n      height: 16px;\n      }\n      .sidebar-resizer {\n      width: 16px;\n      }\n      }\n      .resizer-icon {\n      position: absolute;\n      opacity: 0.5;\n      transition: opacity 0.2s, transform 0.2s;\n      pointer-events: none;\n      fill: var(--accent);\n      }\n      .header-resizer .resizer-icon {\n      left: 50%;\n      top: 50%;\n      transform: translate(-50%, -50%);\n      }\n      .sidebar-resizer .resizer-icon {\n      left: 50%;\n      top: 50%;\n      transform: translate(-50%, -50%);\n      }\n      .mobile-footer-resizer .resizer-icon {\n      left: 50%;\n      top: 50%;\n      transform: translate(-50%, -50%);\n      }\n      .header-resizer:hover .resizer-icon,\n      .sidebar-resizer:hover .resizer-icon,\n      .mobile-footer-resizer:hover .resizer-icon {\n      opacity: 1;\n      }\n      .header-resizer.resizing .resizer-icon,\n      .sidebar-resizer.resizing .resizer-icon,\n      .mobile-footer-resizer.resizing .resizer-icon {\n      opacity: 1;\n      transform: translate(-50%, -50%) scale(1.2);\n      }\n      body.resizing {\n      user-select: none;\n      }\n      body.resizing * {\n      cursor: inherit !important;\n      pointer-events: none;\n      }\n      header {\n      position: relative;\n      }\n      .details-panel {\n      position: relative;\n      }\n      main {\n      display: grid;\n      grid-template-columns: 1fr var(--sidebar-width, 350px);\n      flex: 1;\n      }\n      main.sidebar-collapsed {\n      grid-template-columns: 1fr 0;\n      }\n      @media (max-width: 900px) {\n      main {\n      grid-template-columns: 1fr;\n      grid-template-rows: calc(100vh - var(--topbar-height, 52px) - var(--mobile-footer-height, 40vh)) var(--mobile-footer-height, 40vh);\n      }\n      main.sidebar-collapsed {\n      grid-template-rows: 1fr 0;\n      }\n      .details-panel {\n      max-height: var(--mobile-footer-height, 40vh);\n      height: 100%;\n      }\n      }\n      .topology-panel {\n      background: var(--panel);\n      border-right: 1px solid #111827;\n      position: relative;\n      overflow: hidden;\n      }\n      .topology-toolbar {\n      position: absolute;\n      top: 10px;\n      right: 10px;\n      display: none;\n      align-items: center;\n      gap: 8px;\n      padding: 6px 10px;\n      background: var(--toolbar-bg, rgba(15, 23, 42, 0.92));\n      border: 1px solid var(--toolbar-border, #1f2937);\n      border-radius: 6px;\n      z-index: 99;\n      font-size: 13px;\n      color: var(--toolbar-text, #94a3b8);\n      }\n      .topology-toolbar label {\n      color: var(--toolbar-text, var(--text-soft));\n      }\n      .topology-toolbar select {\n      padding: 4px 6px;\n      background: var(--toolbar-btn-bg, var(--panel));\n      border: 1px solid var(--toolbar-border, var(--edge-main));\n      border-radius: 4px;\n      color: var(--toolbar-btn-text, var(--text-main));\n      font-size: 13px;\n      cursor: pointer;\n      }\n      .topology-toolbar button {\n      padding: 4px 10px;\n      background: var(--toolbar-btn-bg, var(--panel));\n      color: var(--toolbar-btn-text, var(--text-main));\n      border: 1px solid var(--toolbar-border, var(--edge-main));\n      border-radius: 4px;\n      cursor: pointer;\n      font-size: 13px;\n      font-weight: 600;\n      }\n      .topology-toolbar button:hover {\n      background: var(--accent);\n      color: var(--bg);\n      }\n      .bulk-toolbar-desktop,\n      .bulk-toolbar-mobile {\n      position: fixed;\n      bottom: 20px;\n      left: 50%;\n      transform: translateX(-50%);\n      z-index: 9999;\n      }\n      .bulk-toolbar-desktop {\n      position: absolute;\n      top: 10px;\n      right: 10px;\n      bottom: auto;\n      left: auto;\n      transform: none;\n      }\n      @media (min-width: 768px) {\n      .bulk-toolbar-mobile {\n      display: none !important;\n      }\n      }\n      .bulk-action-btn {\n      padding: 16px;\n      background: var(--panel-main);\n      border: 1px solid var(--edge-main);\n      border-radius: 8px;\n      color: var(--text-main);\n      font-size: 24px;\n      cursor: pointer;\n      text-align: center;\n      line-height: 1.2;\n      }\n      .bulk-action-btn:active {\n      transform: scale(0.95);\n      background: var(--accent);\n      }\n      .draw-toolbar {\n      position: absolute;\n      top: 10px;\n      left: 10px;\n      display: flex;\n      align-items: center;\n      gap: 6px;\n      padding: 6px 8px;\n      background: var(--toolbar-bg, rgba(15, 23, 42, 0.92));\n      border: 1px solid var(--toolbar-border, #1f2937);\n      border-radius: 6px;\n      z-index: 99;\n      font-size: 13px;\n      pointer-events: auto;\n      color: var(--toolbar-text, #94a3b8);\n      }\n      .draw-toolbar button {\n      padding: 4px 8px;\n      background: var(--toolbar-btn-bg, var(--panel));\n      color: var(--toolbar-btn-text, var(--text-main));\n      border: 1px solid var(--toolbar-border, var(--edge-main));\n      border-radius: 4px;\n      cursor: pointer;\n      font-size: 13px;\n      }\n      .draw-toolbar button:hover {\n      background: var(--accent);\n      color: var(--bg);\n      }\n      .draw-toolbar input[type=\"color\"] {\n      width: 30px;\n      height: 22px;\n      border-radius: 4px;\n      border: 1px solid var(--edge-main);\n      padding: 0;\n      background: transparent;\n      cursor: pointer;\n      }\n      .draw-toolbar select {\n      padding: 4px 6px;\n      background: var(--toolbar-btn-bg, var(--panel));\n      border: 1px solid var(--toolbar-border, var(--edge-main));\n      border-radius: 4px;\n      color: var(--toolbar-btn-text, var(--text-main));\n      font-size: 13px;\n      cursor: pointer;\n      }\n      .legend-container {\n      position: absolute;\n      left: 10px;\n      bottom: 10px;\n      display: flex;\n      flex-direction: column;\n      gap: 6px;\n      padding: 8px 10px;\n      background: var(--toolbar-bg, rgba(15, 23, 42, 0.92));\n      border: 1px solid var(--toolbar-border, #1f2937);\n      border-radius: 6px;\n      font-size: 12px;\n      z-index: 20;\n      max-width: 260px;\n      pointer-events: auto;\n      color: var(--toolbar-text, #94a3b8);\n      }\n      .legend-container {\n      padding-right: 22px;\n      }\n      @media (max-width: 900px) {\n      .legend-container {\n      max-height: calc(100vh - var(--topbar-height, 100px) - 120px);\n      overflow-y: auto;\n      }\n      }\n      @media (max-width: 900px) {\n      .bulk-toolbar-desktop,\n      .topology-toolbar {\n      display: none !important;\n      }\n      }\n      .legend-close-btn {\n      position: absolute;\n      top: 5px;\n      right: 5px;\n      width: 18px;\n      height: 18px;\n      border-radius: 999px;\n      border: 1px solid var(--edge-main);\n      background: var(--panel-alt);\n      color: var(--text-soft);\n      font-size: 11px;\n      display: flex;\n      align-items: center;\n      justify-content: center;\n      cursor: pointer;\n      }\n      .legend-close-btn:hover {\n      background: var(--danger);\n      color: #fff;\n      }\n      .legend-mini-btn {\n      position: absolute;\n      left: 10px;\n      padding: 4px 8px;\n      border-radius: 4px;\n      border: 1px solid var(--toolbar-border, #1f2937);\n      background: var(--toolbar-bg, rgba(15, 23, 42, 0.92));\n      color: var(--toolbar-btn-text, var(--text-main));\n      font-size: 11px;\n      cursor: pointer;\n      z-index: 20;\n      display: none;\n      transition: all 0.2s;\n      }\n      .legend-mini-btn:hover,\n      .legend-mini-btn:active {\n      background: var(--accent);\n      color: var(--bg);\n      border-color: var(--accent);\n      }\n      .legend-title {\n      font-size: 11px;\n      text-transform: uppercase;\n      letter-spacing: 0.06em;\n      color: var(--toolbar-text, var(--text-soft));\n      margin-bottom: 2px;\n      }\n\t  .fov-group {\n        transition: opacity 0.3s ease;\n      }\n      g[data-node-id]:not(:hover) .fov-group {\n        opacity: 0.7;\n      }\n      g[data-node-id]:hover .fov-group {\n        opacity: 1;\n      }\n      .legend-item {\n      display: flex;\n      align-items: center;\n      gap: 8px;\n      }\n      .legend-swatch {\n      width: 14px;\n      height: 14px;\n      border-radius: 3px;\n      border: 1px solid #020617;\n      flex-shrink: 0;\n      }\n      .legend-label {\n      outline: none;\n      cursor: text;\n      flex: 1;\n      min-width: 60px;\n      user-select: text;\n      -webkit-user-select: text;\n      }\n      .legend-label.editing {\n      border-bottom: 1px dashed var(--accent);\n      }\n      .canvas-viewport {\n      position: absolute;\n      top: 0;\n      left: 0;\n      right: 0;\n      bottom: 0;\n      overflow: hidden;\n      }\n      .canvas-viewport.panning {\n      cursor: grabbing !important;\n      }\n      .canvas-viewport.panning * {\n      cursor: grabbing !important;\n      }\n      .canvas-viewport svg {\n      width: 100%;\n      height: 100%;\n      display: block; \n      }\n      .zoom-toolbar {\n      display: flex;\n      gap: 4px;\n      padding: 6px;\n      background: var(--toolbar-bg, rgba(15, 23, 42, 0.92));\n      border: 1px solid var(--toolbar-border, #1f2937);\n      border-radius: 6px;\n      }\n      .zoom-toolbar button {\n      width: 32px;\n      height: 32px;\n      padding: 0;\n      background: var(--toolbar-btn-bg, var(--panel));\n      color: var(--toolbar-btn-text, var(--text-main));\n      border: 1px solid var(--toolbar-border, var(--edge-main));\n      border-radius: 4px;\n      cursor: pointer;\n      font-size: 18px;\n      font-weight: bold;\n      display: flex;\n      align-items: center;\n      justify-content: center;\n      transition: all 0.15s;\n      }\n      .zoom-toolbar button:hover {\n      background: var(--accent);\n      color: var(--bg);\n      }\n      .zoom-toolbar .zoom-level {\n      font-size: 11px;\n      color: var(--toolbar-text, var(--text-soft));\n      text-align: center;\n      padding: 2px 0;\n      min-width: 32px;\n      }\n      .zoom-toolbar .divider {\n      height: 1px;\n      background: var(--toolbar-border, var(--edge-main));\n      margin: 2px 0;\n      }\n      .minimap-zoom-wrapper {\n      position: absolute;\n      bottom: 10px;\n      right: 10px;\n      z-index: 99;\n      }\n      .minimap-container {\n      background: var(--toolbar-bg, rgba(15, 23, 42, 0.92));\n      border: 1px solid var(--toolbar-border, #1f2937);\n      border-radius: 6px;\n      overflow: hidden;\n      margin-bottom: 4px;\n      }\n      .minimap-container svg {\n      width: 100%;\n      height: 100%;\n      }\n\t.minimap-container,\n\t.node-panel,\n\t.edge-panel,\n\t#canvas-viewport {\n\t  contain: layout paint;\n\t}\t\n\t.low-zoom .node-group .node-circle,\n\t.low-zoom .node-group.active .node-circle,\n\t.low-zoom .node-group.selected .node-circle {\n\t  filter: none !important;\n\t  animation: none !important;\n\t}\t\n      .minimap-viewport {\n      fill: rgba(79, 209, 197, 0.2);\n      stroke: var(--accent);\n      stroke-width: 2;\n      cursor: move;\n      }\n      .minimap-node {\n      fill: var(--minimap-dots, var(--text-soft));\n      }\n      .minimap-edge {\n      stroke: var(--edge-main);\n      stroke-width: 8px;\n      fill: none;\n      }\n      .minimap-wall {\n      pointer-events: none;\n      }\n      .minimap-rect {\n      pointer-events: none;\n      }\n\t  .minimap-close-btn {\n      position: absolute;\n      top: 2px;\n      right: 2px;\n      width: 18px;\n      height: 18px;\n      background: var(--toolbar-btn-bg, rgba(15, 23, 42, 0.9));\n      border: 1px solid var(--toolbar-border, var(--edge-main));\n      border-radius: 3px;\n      color: var(--toolbar-text, var(--text-soft));\n      font-size: 11px;\n      cursor: pointer;\n      display: flex;\n      align-items: center;\n      justify-content: center;\n      z-index: 10;\n      padding: 0;\n      line-height: 1;\n      }\n      .minimap-close-btn:hover {\n      background: var(--danger);\n      color: white;\n      border-color: var(--danger);\n      }\n\t  .toolbar-close-btn {\n      width: 24px;\n      height: 24px;\n      background: var(--toolbar-btn-bg, rgba(15, 23, 42, 0.9));\n      border: 1px solid var(--toolbar-border, var(--edge-main));\n      border-radius: 4px;\n      color: var(--toolbar-text, var(--text-soft));\n      font-size: 12px;\n      cursor: pointer;\n      display: flex;\n      align-items: center;\n      justify-content: center;\n      padding: 0;\n      line-height: 1;\n      flex-shrink: 0;\n      }\n      .toolbar-close-btn:hover {\n      background: var(--danger);\n      color: white;\n      border-color: var(--danger);\n      }\n\t  .canvas-hint {\n      position: absolute;\n      top: 50px;\n      left: 50%;\n      transform: translateX(-50%);\n      padding: 8px 16px;\n      background: var(--canvas-hint-bg, rgba(15, 23, 42, 0.92));\n      border: 1px solid var(--toolbar-border, #1f2937);\n      border-radius: 6px;\n      font-size: 12px;\n      color: var(--canvas-hint-color, var(--text-soft));\n      z-index: 18;\n      pointer-events: none;\n      opacity: 0;\n      transition: opacity 0.3s;\n      }\n      .canvas-hint.visible {\n      opacity: 1;\n      pointer-events: auto;\n      }\n      .edge {\n      stroke: var(--edge-main);\n      stroke-width: 4;\n      opacity: 0.75;\n      transition: 0.25s ease-in-out;\n      cursor: pointer;\n      }\n      .edge.backup {\n      stroke: var(--danger);\n      stroke-width: 5;\n      }\n      .edge.active {\n      opacity: 1;\n      stroke-width: 7;\n      filter: drop-shadow(0 0 8px var(--accent, #4fd1c5));\n      }\n      .rect-group.active .rect-shape {\n      filter: drop-shadow(0 0 10px var(--accent, #4fd1c5));\n      animation: pulse 1.2s infinite ease-in-out;\n      }\n      .text-element.active {\n      filter: drop-shadow(0 0 10px var(--accent, #4fd1c5));\n      animation: pulse 1.2s infinite ease-in-out;\n      }\n      .free-preview {\n      fill: none;\n      stroke-dasharray: 4 4;\n      pointer-events: none;\n      }\n@keyframes edge-flow-arrow {\n  0% { offset-distance: 0%; }\n  100% { offset-distance: 100%; }\n}\n@keyframes edge-flow-arrow-reverse {\n  0% { offset-distance: 100%; }\n  100% { offset-distance: 0%; }\n}\n.edge-arrow-forward {\n  offset-rotate: auto;\n  animation: edge-flow-arrow 1.5s linear infinite;\n}\n.edge-arrow-backward {\n  offset-rotate: auto 180deg;\n  animation: edge-flow-arrow-reverse 1.5s linear infinite;\n}\n      .free-point {\n      fill: #e5e7eb;\n      stroke: #0f172a;\n      stroke-width: 1.5;\n      cursor: grab;\n      }\n      .node-circle {\n      fill: var(--node-fill, #1e293b);\n      stroke: var(--node-stroke, #475569);\n      stroke-width: 2;\n      transition: 0.25s ease;\n      transform-origin: center center;\n      }\n      .node-hit-area {\n      cursor: grab;\n      pointer-events: all;\n      }\n      .node-group:hover .node-circle {\n      filter: drop-shadow(0 0 10px rgba(79, 209, 197, 0.45));\n      }\n      .node-group.active .node-circle {\n      stroke: var(--accent);\n      animation: pulse 1.2s infinite ease-in-out;\n      }\n      @keyframes pulse {\n      0% {\n      filter: drop-shadow(0 0 4px #4fd1c5);\n      }\n      50% {\n      filter: drop-shadow(0 0 14px #4fd1c5);\n      }\n      100% {\n      filter: drop-shadow(0 0 4px #4fd1c5);\n      }\n      }\n      @keyframes done-pulse {\n        0%, 100% {\n          transform: scale(1);\n          box-shadow: 0 0 0 0 rgba(79, 209, 197, 0.7);\n        }\n        50% {\n          transform: scale(1.05);\n          box-shadow: 0 0 0 8px rgba(79, 209, 197, 0);\n        }\n      }\n      .done-btn-active {\n        animation: done-pulse 1.5s ease-in-out infinite;\n        background: var(--accent) !important;\n        color: var(--bg) !important;\n      }\n      .node-label {\n      fill: var(--text-main);\n      font-size: 18px;\n      text-anchor: middle;\n      font-weight: 600;\n      }\n      .node-sub {\n      fill: var(--text-soft);\n      font-size: 13px;\n      text-anchor: middle;\n      }\n      .ping-indicator {\n      fill: #6b7280;\n      stroke: #4b5563;\n      stroke-width: 1;\n      }\n      .ping-indicator.online {\n      fill: #10b981;\n      stroke: #059669;\n      }\n      .ping-indicator.offline {\n      fill: #ef4444;\n      stroke: #dc2626;\n      }\n      .ping-indicator.checking {\n      fill: #f59e0b;\n      stroke: #d97706;\n      }\n      @media (max-width: 1024px) {\n      .node-label {\n      font-size: 28px;\n      }\n      .node-sub {\n      font-size: 20px;\n      }\n      }\n      @media (max-width: 768px) {\n      .node-label {\n      font-size: 70px;\n      }\n      .node-sub {\n      font-size: 50px;\n      }\n      }\n      @media (max-width: 380px) {\n      .node-label {\n      font-size: 60px;\n      }\n      .node-sub {\n      font-size: 42px;\n      }\n      }\n      .details-panel {\n      background: var(--sidebar-bg, var(--panel-alt));\n      padding: 22px;\n      padding-bottom: 80px;\n      overflow-y: auto;\n      display: flex;\n      flex-direction: column;\n      gap: 15px;\n      position: relative;\n      transition:\n      width 0.3s ease,\n      min-width 0.3s ease,\n      padding 0.3s ease,\n      opacity 0.3s ease;\n      }\n      .details-panel {\n      min-width: 260px !important;\n      }\n      .details-panel {\n      overflow-y: auto !important;\n      overflow-x: hidden;\n      }\n      .details-panel.collapsed {\n      width: 0 !important;\n      min-width: 0 !important;\n      padding: 0 !important;\n      overflow: hidden;\n      opacity: 0;\n      }\n      body {\n      overflow-x: hidden;\n      overflow-y: hidden;\n      }\n      main {\n      overflow-x: hidden;\n      overflow-y: visible;\n      }\n      .details-panel {\n      overflow-y: auto !important;\n      overflow-x: hidden;\n      }\n      .details-panel.collapsed {\n      min-width: 0 !important;\n      }\n      .sidebar-toggle {\n      position: absolute;\n      left: -16px;\n      top: 50%;\n      transform: translateY(-50%);\n      width: 16px;\n      height: 60px;\n      background: var(--sidebar-bg, var(--panel-alt));\n      border: 1px solid var(--edge-main);\n      border-right: none;\n      border-radius: 8px 0 0 8px;\n      cursor: pointer;\n      display: flex;\n      align-items: center;\n      justify-content: center;\n      color: var(--text-soft);\n      font-size: 10px;\n      z-index: 25;\n      transition:\n      background 0.2s,\n      color 0.2s;\n      }\n      .sidebar-toggle:hover {\n      background: var(--accent);\n      color: var(--bg);\n      }\n      .sidebar-toggle.collapsed {\n      left: 0;\n      border-right: 1px solid var(--edge-main);\n      border-radius: 0 8px 8px 0;\n      position: fixed;\n      right: 0;\n      left: auto;\n      }\n      .details-name {\n      font-size: clamp(22px, 2.5vw, 30px);\n      font-weight: 700;\n      }\n      .details-ip {\n      font-size: clamp(16px, 2vw, 22px);\n      color: var(--text-soft);\n      }\n      .details-role {\n      font-size: clamp(16px, 2vw, 22px);\n      color: var(--accent);\n      }\n      .size-controls {\n      display: flex;\n      gap: 10px;\n      align-items: center;\n      margin-top: 10px;\n      }\n      .size-controls label {\n      font-size: clamp(14px, 1.6vw, 18px);\n      color: var(--text-soft);\n      }\n      .size-controls input[type=\"range\"] {\n      flex: 1;\n      accent-color: var(--accent);\n      }\n      .size-controls button {\n      padding: 6px 12px;\n      background: var(--panel);\n      color: var(--text-main);\n      border: 1px solid var(--edge-main);\n      border-radius: 4px;\n      cursor: pointer;\n      font-size: clamp(12px, 1.4vw, 16px);\n      }\n      .size-controls button:hover {\n      background: var(--accent);\n      color: var(--bg);\n      }\n      .style-section {\n      margin-top: 15px;\n      padding-top: 15px;\n      border-top: 1px solid var(--edge-main);\n      }\n      .style-section summary {\n      cursor: pointer;\n      font-size: clamp(14px, 1.6vw, 20px);\n      text-transform: uppercase;\n      color: var(--text-soft);\n      list-style: none;\n      display: flex;\n      justify-content: space-between;\n      align-items: center;\n      user-select: none;\n      }\n      .style-section summary::-webkit-details-marker {\n      display: none;\n      }\n      .password-overlay {\n      position: fixed;\n      top: 0;\n      left: 0;\n      width: 100%;\n      height: 100%;\n      background: #ffffff;\n      z-index: 9999;\n      }\n      .style-section summary::after {\n      content: \"▼\";\n      transition: transform 0.2s;\n      }\n      .style-section[open] summary::after {\n      transform: rotate(180deg);\n      }\n      .style-content {\n  display: grid;\n  grid-template-columns: 1fr auto;\n  gap: 8px 12px;\n  align-items: center;\n  justify-items: end;\n}\n.style-content label {\n  justify-self: start;\n}\n      .style-row {\n      display: contents;\n      }\n#edge-panel .style-row,\n#rect-panel .style-row,\n#text-panel .style-row {\n  display: flex;\n  align-items: center;\n  gap: 12px;\n  margin-bottom: 8px;\n}\n#edge-panel .style-row label,\n#rect-panel .style-row label,\n#text-panel .style-row label {\n  min-width: 80px;\n}\nbutton:disabled {\n  opacity: 0.5;\n  cursor: not-allowed;\n}\n      .style-row label {\n      font-size: clamp(13px, 1.5vw, 17px);\n      color: var(--text-soft);\n      min-width: 80px;\n      }\n      .style-row input[type=\"color\"] {\n      width: 50px;\n      height: 30px;\n      border: 1px solid var(--edge-main);\n      border-radius: 4px;\n      cursor: pointer;\n      background: transparent;\n      }\n      .style-row input[type=\"number\"] {\n      width: 70px;\n      padding: 4px 8px;\n      background: var(--panel);\n      border: 1px solid var(--edge-main);\n      border-radius: 4px;\n      color: var(--text-main);\n      font-size: clamp(12px, 1.4vw, 16px);\n      }\n      .style-row select {\n      flex: 1;\n      padding: 4px 8px;\n      background: var(--panel);\n      border: 1px solid var(--edge-main);\n      border-radius: 4px;\n      color: var(--text-main);\n      font-size: clamp(12px, 1.4vw, 16px);\n      cursor: pointer;\n      }\n      .editable-text {\n      cursor: pointer;\n      transition: opacity 0.2s;\n      }\n      .editable-text:hover {\n      opacity: 0.7;\n      }\n      .modal {\n      display: none;\n      position: fixed;\n      top: 0;\n      left: 0;\n      width: 100%;\n      height: 100%;\n      z-index: 999999;\n      justify-content: center;\n      align-items: center;\n      overflow: auto;\n      }\n      .modal.active {\n      display: inline-grid;\n      touch-action: pan-y;\n      }\n      #dialog-modal {\n      z-index: 9999999;\n      background: rgba(0, 0, 0, 0.7);\n      }\n      .modal-content {\n      background: var(--panel-alt);\n      padding: 25px;\n      border-radius: 8px;\n      border: 1px solid var(--edge-main);\n      min-width: 300px;\n      max-width: 90%;\n      max-height: 85vh;\n      overflow-y: auto;\n      overscroll-behavior: contain;\n      }\n      .modal-content h3 {\n      margin: 0 0 15px 0;\n      color: var(--text-main);\n      font-size: clamp(18px, 2vw, 24px);\n      }\n      .modal-content p {\n      color: var(--text-soft);\n      font-size: clamp(14px, 1.6vw, 18px);\n      margin: 0 0 20px 0;\n      }\n      .modal-content input:not([type=\"color\"]),\n      .modal-content select {\n      width: 100%;\n      padding: 10px;\n      background: var(--panel);\n      border: 1px solid var(--edge-main);\n      border-radius: 4px;\n      color: var(--text-main);\n      font-size: clamp(14px, 1.6vw, 18px);\n      margin-bottom: 15px;\n      user-select: text;\n      -webkit-user-select: text;\n      }\n      .modal-content input[type=\"color\"] {\n      width: 60px;\n      height: 36px;\n      padding: 2px;\n      border: 2px solid var(--edge-main);\n      border-radius: 6px;\n      cursor: pointer;\n      background: none;\n      -webkit-appearance: none;\n      appearance: none;\n      }\n      .modal-content input[type=\"color\"]::-webkit-color-swatch-wrapper {\n      padding: 0;\n      }\n      .modal-content input[type=\"color\"]::-webkit-color-swatch {\n      border: none;\n      border-radius: 4px;\n      }\n      .modal-buttons {\n      display: flex;\n      gap: 10px;\n      justify-content: flex-end;\n      margin-top: 10px;\n      padding-top: 10px;\n      border-top: 1px solid var(--edge-main);\n      }\n      .modal-buttons button {\n      padding: 8px 16px;\n      border: none;\n      border-radius: 4px;\n      cursor: pointer;\n      font-size: clamp(14px, 1.6vw, 18px);\n      font-weight: 600;\n      }\n      .modal-buttons .btn-cancel {\n      background: var(--panel);\n      color: var(--text-main);\n      border: 1px solid var(--edge-main);\n      }\n      .modal-buttons .btn-save {\n      background: var(--accent);\n      color: var(--bg);\n      }\n      .modal-buttons .btn-delete {\n      background: var(--danger);\n      color: white;\n      }\n      .confirm-modal p {\n      color: var(--text-soft);\n      font-size: clamp(14px, 1.6vw, 18px);\n      margin: 0 0 20px 0;\n      }\n      #note-editor-modal .modal-content {\n      width: 600px;\n      max-width: 95vw;\n      max-height: 90vh;\n      display: flex;\n      flex-direction: column;\n      }\n      .note-editor-toolbar {\n      display: flex;\n      flex-wrap: wrap;\n      gap: 4px;\n      padding: 8px;\n      background: var(--panel);\n      border: 1px solid var(--edge-main);\n      border-bottom: none;\n      border-radius: 6px 6px 0 0;\n      }\n      .note-editor-toolbar button {\n      padding: 6px 10px;\n      background: var(--panel-alt);\n      border: 1px solid var(--edge-main);\n      border-radius: 4px;\n      color: var(--text-main);\n      cursor: pointer;\n      font-size: 14px;\n      min-width: 32px;\n      }\n      .note-editor-toolbar button:hover {\n      background: var(--accent);\n      color: var(--bg);\n      }\n      .note-editor-toolbar button.active {\n      background: var(--accent);\n      color: var(--bg);\n      }\n      .note-editor-toolbar .toolbar-separator {\n      width: 1px;\n      background: var(--edge-main);\n      margin: 0 4px;\n      }\n      .note-editor-toolbar select {\n      padding: 6px 8px;\n      background: var(--panel-alt);\n      border: 1px solid var(--edge-main);\n      border-radius: 4px;\n      color: var(--text-main);\n      cursor: pointer;\n      font-size: 14px;\n      }\n      .note-editor-content {\n      min-height: 200px;\n      max-height: 400px;\n      overflow-y: auto;\n      padding: 12px;\n      background: var(--panel);\n      border: 1px solid var(--edge-main);\n      border-radius: 0 0 6px 6px;\n      color: var(--text-main);\n      font-size: 14px;\n      line-height: 1.6;\n      outline: none;\n      }\n      .note-editor-content:focus {\n      border-color: var(--accent);\n      }\n      .note-editor-content h1 { font-size: 1.8em; margin: 0.5em 0; color: var(--text-main); }\n      .note-editor-content h2 { font-size: 1.5em; margin: 0.5em 0; color: var(--text-main); }\n      .note-editor-content h3 { font-size: 1.2em; margin: 0.5em 0; color: var(--text-main); }\n      .note-editor-content p { margin: 0.5em 0; }\n      .note-editor-content ul, .note-editor-content ol { margin: 0.5em 0; padding-left: 1.5em; }\n      .note-editor-content li { margin: 0.25em 0; }\n      .note-editor-content code {\n      background: var(--panel-alt);\n      padding: 2px 6px;\n      border-radius: 4px;\n      font-family: monospace;\n      font-size: 0.9em;\n      }\n      .note-editor-content pre {\n      background: var(--panel-alt);\n      padding: 12px;\n      border-radius: 6px;\n      overflow-x: auto;\n      margin: 0.5em 0;\n      }\n      .note-editor-content pre code {\n      background: none;\n      padding: 0;\n      }\n      .note-editor-content a { color: var(--accent); }\n      .note-editor-content blockquote {\n      border-left: 3px solid var(--accent);\n      margin: 0.5em 0;\n      padding-left: 12px;\n      color: var(--text-soft);\n      }\n      .notes-feed {\n      max-height: 200px;\n      overflow-y: auto;\n      display: flex;\n      flex-direction: column;\n      gap: 8px;\n      margin-bottom: 10px;\n      }\n      .note-card {\n      background: var(--panel);\n      border: 1px solid var(--edge-main);\n      border-radius: 6px;\n      padding: 10px;\n      cursor: pointer;\n      transition: border-color 0.2s;\n      }\n      .note-card:hover {\n      border-color: var(--accent);\n      }\n      .note-card-content {\n      font-size: 13px;\n      line-height: 1.5;\n      color: var(--text-main);\n      max-height: 60px;\n      overflow: hidden;\n      word-break: break-word;\n      }\n      .note-card-content h1, .note-card-content h2, .note-card-content h3 {\n      font-size: 14px;\n      margin: 0;\n      }\n      .note-card-content p { margin: 0; }\n      .note-card-content pre { margin: 0; font-size: 12px; }\n      .note-card-actions {\n      display: flex;\n      gap: 8px;\n      margin-top: 8px;\n      justify-content: flex-end;\n      }\n      .note-card-actions button {\n      padding: 4px 10px;\n      font-size: 12px;\n      border-radius: 4px;\n      border: 1px solid var(--edge-main);\n      background: var(--panel-alt);\n      color: var(--text-main);\n      cursor: pointer;\n      }\n      .note-card-actions button:hover {\n      background: var(--accent);\n      color: var(--bg);\n      }\n      .note-card-actions button.delete-note:hover {\n      background: var(--danger);\n      color: white;\n      }\n      .context-menu-item {\n      transition: background 0.15s;\n      }\n      .context-menu-item:hover {\n      background: var(--panel);\n      }\n      .details-info-row {\n      display: flex;\n      align-items: center;\n      margin: 6px 0;\n      }\n\t .node-group.selected .node-circle {\n      stroke: var(--selection-handle, #f59e0b);\n      stroke-width: 3;\n      }\n\t  .node-group.layer-faded {\n      pointer-events: none;\n      }\n      .edge.layer-faded {\n      pointer-events: none;\n      }\n      .node-group.search-highlight .node-circle,\n      .node-group.search-highlight rect,\n      .node-group.search-highlight polygon {\n      stroke: #10b981;\n      stroke-width: 3;\n      filter: drop-shadow(0 0 8px #10b981);\n      }\n\t  .node-group.search-faded {\n  opacity: 0.15;\n  pointer-events: none;\n}\n.node-group.search-faded .node-label,\n.node-group.search-faded .node-sub {\n  opacity: 0.3;\n}\n.edge-group.search-faded,\n.edge.search-faded {\n  opacity: 0.1;\n}\n      .badge-row {\n      display: flex;\n      flex-wrap: wrap;\n      gap: 8px;\n      }\n     .badge {\n      background: var(--tag-fill, #1e293b);\n      color: var(--tag-text, #e2e8f0);\n      border: 1px solid var(--tag-border, var(--edge-main));\n      padding: 5px 12px;\n      border-radius: 22px;\n      font-size: clamp(12px, 1.4vw, 18px);\n      }\n\t  .badge.wg {\n      color: var(--accent);\n      border-color: var(--accent);\n      }\n\t  input[type=\"text\"],\n      input[type=\"number\"],\n      input[type=\"password\"],\n      textarea,\n      select {\n      background: var(--input-bg, #0b0e13) !important;\n      color: var(--input-text, #e2e8f0) !important;\n      border-color: var(--input-border, #1f2937) !important;\n      font-family: var(--input-font, Inter, system-ui, sans-serif) !important;\n      font-size: var(--input-font-size, 14px) !important;\n      }\n      .section-label {\n      margin-top: 8px;\n      font-size: clamp(14px, 1.6vw, 20px);\n      text-transform: uppercase;\n      color: var(--text-soft);\n      }\n      .list li {\n      margin: 6px 0;\n      font-size: clamp(14px, 1.6vw, 20px);\n      cursor: pointer;\n      display: flex;\n      justify-content: space-between;\n      align-items: center;\n      gap: 10px;\n      }\n      .list li:hover {\n      color: var(--accent);\n      }\n      .delete-note {\n      color: var(--danger);\n      font-size: 18px;\n      cursor: pointer;\n      opacity: 0;\n      transition: opacity 0.2s;\n      flex-shrink: 0;\n      }\n      .list li:hover .delete-note {\n      opacity: 1;\n      }\n      .editing {\n      outline: 2px solid var(--accent);\n      background: #0d141f;\n      padding: 4px;\n      border-radius: 6px;\n      user-select: text;\n      -webkit-user-select: text;\n      }\n      .mobile-menu-btn {\n      display: none;\n      background: var(--panel);\n      border: 1px solid var(--edge-main);\n      color: var(--text-main);\n      font-size: 22px;\n      padding: 6px 12px;\n      border-radius: 6px;\n      cursor: pointer;\n      }\n      #topbar-menu {\n      gap: 8px;\n      align-items: center;\n      }\n      @media (min-width: 721px) {\n      #topbar-menu {\n      display: flex !important;\n      }\n      }\n      @media (max-width: 720px) {\n      #topbar-menu {\n      display: none;\n      }\n      #topbar-menu.open {\n      display: flex;\n      }\n      }\n      @media (max-width: 720px) {\n      .mobile-menu-btn {\n      display: block;\n      }\n      #topbar-menu {\n      position: absolute;\n      top: var(--topbar-height);\n      right: 0;\n      background: var(--panel-alt);\n      border-left: 1px solid var(--topbar-border);\n      border-bottom: 1px solid var(--topbar-border);\n      padding: 12px;\n      display: none;\n      flex-direction: column;\n      max-height: calc(100vh - var(--topbar-height));\n      overflow-y: auto;\n      overscroll-behavior: contain;\n      z-index: 999;\n      }\n      #topbar-menu.open {\n      display: flex;\n      touch-action: pan-y;\n      }\n      header {\n      position: relative;\n      z-index: 9999;\n      }\n      }\n      @media (max-width: 720px) {\n      .draw-toolbar {\n      position: absolute;\n      top: auto;\n      bottom: auto;\n      left: 10px !important;\n      right: auto !important;\n      width: auto;\n      gap: 6px;\n      padding: 8px;\n      }\n      .topology-toolbar {\n      position: absolute;\n      top: auto;\n      bottom: auto;\n      width: auto;\n      gap: 6px;\n      padding: 8px;\n      }\n      .draw-toolbar {\n      top: 10px !important;\n      }\n      .topology-toolbar {\n      top: calc(10px + var(--draw-toolbar-height, 50px)) !important;\n      }\n      .canvas-hint {\n      top: calc(10px + 120px);\n      }\n      }\n      .icon-picker-modal {\n      display: none;\n      position: fixed;\n      top: 0;\n      left: 0;\n      width: 100%;\n      height: 100%;\n      background: rgba(0, 0, 0, 0.8);\n      z-index: 999999999;\n      justify-content: center;\n      align-items: center;\n      }\n      .icon-picker-modal.active {\n      display: flex;\n      }\n      .icon-picker-content {\n      background: var(--panel);\n      border-radius: 12px;\n      width: 90%;\n      max-width: 800px;\n      max-height: 80vh;\n      display: flex;\n      flex-direction: column;\n      border: 1px solid var(--edge-main);\n      }\n      .icon-picker-header {\n      padding: 20px;\n      border-bottom: 1px solid var(--edge-main);\n      }\n      .icon-picker-header h3 {\n      margin: 0 0 15px 0;\n      color: var(--text-main);\n      }\n      .icon-picker-tabs {\n      display: flex;\n      gap: 10px;\n      margin-bottom: 15px;\n      flex-wrap: wrap;\n      }\n      .icon-picker-tab {\n      padding: 8px 16px;\n      background: var(--panel-alt);\n      border: 1px solid var(--edge-main);\n      border-radius: 6px;\n      cursor: pointer;\n      color: var(--text-soft);\n      font-size: 14px;\n      transition: all 0.2s;\n      }\n      .icon-picker-tab:hover {\n      background: var(--accent);\n      color: var(--bg);\n      }\n      .icon-picker-tab.active {\n      background: var(--accent);\n      color: var(--bg);\n      border-color: var(--accent);\n      }\n      .icon-picker-search {\n      width: 100%;\n      padding: 10px 15px;\n      background: var(--panel-alt);\n      border: 1px solid var(--edge-main);\n      border-radius: 6px;\n      color: var(--text-main);\n      font-size: 14px;\n      }\n      .icon-picker-search::placeholder {\n      color: var(--text-soft);\n      }\n      .icon-picker-body {\n      padding: 20px;\n      overflow-y: auto;\n      flex: 1;\n      }\n      .icon-grid {\n      display: grid;\n      grid-template-columns: repeat(auto-fill, minmax(80px, 1fr));\n      gap: 10px;\n      }\n      .icon-item {\n      display: flex;\n      flex-direction: column;\n      align-items: center;\n      justify-content: center;\n      padding: 15px 10px;\n      background: var(--panel-alt);\n      border: 1px solid var(--edge-main);\n      border-radius: 6px;\n      cursor: pointer;\n      transition: all 0.2s;\n      }\n      .icon-item:hover {\n      background: var(--accent);\n      border-color: var(--accent);\n      transform: scale(1.05);\n      }\n      .icon-item svg {\n      width: 32px;\n      height: 32px;\n      fill: var(--text-main);\n      }\n      .icon-item:hover svg {\n      fill: var(--bg);\n      }\n      .icon-item-name {\n      margin-top: 8px;\n      font-size: 10px;\n      color: var(--text-soft);\n      text-align: center;\n      word-break: break-word;\n      }\n      .icon-item:hover .icon-item-name {\n      color: var(--bg);\n      }\n      .icon-picker-loading {\n      text-align: center;\n      padding: 40px;\n      color: var(--text-soft);\n      }\n      .icon-picker-footer {\n      padding: 15px 20px;\n      border-top: 1px solid var(--edge-main);\n      display: flex;\n      justify-content: flex-end;\n      }\n      .icon-btn-cancel {\n      padding: 8px 20px;\n      background: var(--panel-alt);\n      color: var(--text-main);\n      border: 1px solid var(--edge-main);\n      border-radius: 6px;\n      cursor: pointer;\n      font-size: 14px;\n      }\n      .icon-btn-cancel:hover {\n      background: var(--edge-main);\n      }\n      .icon-badge {\n      display: inline-flex;\n      align-items: center;\n      gap: 5px;\n      padding: 4px 8px;\n      background: var(--panel-alt);\n      border-radius: 4px;\n      font-size: 12px;\n      margin: 2px;\n      }\n      .icon-badge svg {\n      width: 16px;\n      height: 16px;\n      fill: currentColor;\n      }\n      .pick-icon-btn {\n      padding: 6px 12px;\n      background: var(--accent);\n      color: var(--bg);\n      border: none;\n      border-radius: 6px;\n      cursor: pointer;\n      font-size: 13px;\n      font-weight: 600;\n      margin-top: 8px;\n      width: 100%;\n      }\n      .pick-icon-btn:hover {\n      opacity: 0.9;\n      }\n      @media (max-width: 768px) {\n      .icon-picker-content {\n      width: 95%;\n      max-height: 90vh;\n      }\n      .modal-content {\n      background: var(--panel-alt);\n      }\n      #search-input {\n      width: 100%;\n      }\n      }\n      .version-item {\n      display: flex;\n      justify-content: space-between;\n      align-items: center;\n      padding: 12px;\n      background: var(--panel);\n      border: 1px solid var(--edge-main);\n      border-radius: 6px;\n      cursor: pointer;\n      transition: all 0.2s;\n      }\n      .version-item:hover {\n      background: var(--panel-alt);\n      border-color: var(--accent);\n      }\n      .version-item.current {\n      border-color: var(--accent);\n      background: rgba(79, 209, 197, 0.1);\n      }\n      .version-info {\n      flex: 1;\n      }\n      .version-info .timestamp {\n      font-size: 13px;\n      color: var(--text-main);\n      font-weight: 600;\n      }\n      .version-info .details {\n      font-size: 11px;\n      color: var(--text-soft);\n      margin-top: 4px;\n      }\n      .version-actions {\n      display: flex;\n      gap: 6px;\n      }\n      .audit-entry {\n      padding: 8px 12px;\n      background: var(--panel);\n      border-left: 3px solid var(--edge-main);\n      border-radius: 4px;\n      font-size: 12px;\n      }\n      .audit-entry.node { border-left-color: #4fd1c5; }\n      .audit-entry.connection { border-left-color: #9f7aea; }\n      .audit-entry.style { border-left-color: #ed8936; }\n      .audit-entry.rack { border-left-color: #48bb78; }\n      .audit-entry.layer { border-left-color: #4299e1; }\n      .audit-entry .time {\n      color: var(--text-soft);\n      font-size: 10px;\n      }\n      .audit-entry .action {\n      color: var(--text-main);\n      font-weight: 500;\n      }\n      .tab-item {\n      display: flex;\n      justify-content: space-between;\n      align-items: center;\n      padding: 12px;\n      background: var(--panel);\n      border: 1px solid var(--edge-main);\n      border-radius: 6px;\n      cursor: pointer;\n      transition: all 0.2s;\n      }\n      .tab-item:hover {\n      background: var(--panel-alt);\n      }\n      .tab-item.active {\n      border-color: var(--accent);\n      background: rgba(79, 209, 197, 0.1);\n      }\n      .tab-item .tab-name {\n      font-weight: 600;\n      color: var(--text-main);\n      flex: 1;\n      }\n      .tab-item .tab-stats {\n      font-size: 11px;\n      color: var(--text-soft);\n      margin-right: 10px;\n      }\n      .tab-actions {\n      display: flex;\n      gap: 6px;\n      }\n      .secret-item {\n      display: flex;\n      justify-content: space-between;\n      align-items: center;\n      padding: 12px;\n      background: var(--panel);\n      border: 1px solid var(--edge-main);\n      border-radius: 6px;\n      border-left: 3px solid var(--danger);\n      }\n      .secret-item.note-type-global { border-left-color: var(--danger); }\n      .secret-item.note-type-node { border-left-color: var(--accent); }\n      .secret-item.note-type-edge { border-left-color: #22c55e; }\n      .secret-item.note-type-rect { border-left-color: #f97316; }\n      .secret-item.note-type-image { border-left-color: #8b5cf6; }\n      .secret-item .secret-name {\n      font-weight: 600;\n      color: var(--text-main);\n      flex: 1;\n      }\n      .secret-item .secret-status {\n      font-size: 11px;\n      color: var(--text-soft);\n      margin-right: 10px;\n      }\n      .secret-item .note-source {\n      font-size: 11px;\n      color: var(--text-soft);\n      background: var(--panel-alt);\n      padding: 2px 8px;\n      border-radius: 4px;\n      margin-right: 10px;\n      cursor: pointer;\n      }\n      .secret-item .note-source:hover {\n      background: var(--accent);\n      color: var(--bg);\n      }\n      .secret-item .note-preview {\n      font-size: 12px;\n      color: var(--text-soft);\n      max-width: 300px;\n      overflow: hidden;\n      text-overflow: ellipsis;\n      white-space: nowrap;\n      margin-right: 10px;\n      }\n\t  body.view-only-mode:not(.view-only-inspect) #node-panel,\n      body.view-only-mode:not(.view-only-inspect) #edge-panel,\n      body.view-only-mode:not(.view-only-inspect) #text-panel,\n      body.view-only-mode:not(.view-only-inspect) #rect-panel {\n        display: none !important;\n      }\n      body.view-only-mode .node-group,\n      body.view-only-mode .edge,\n      body.view-only-mode .rect-group,\n      body.view-only-mode .text-element {\n        cursor: default !important;\n      }\n      body.view-only-mode .resize-handle,\n      body.view-only-mode .edge-control-point {\n        display: none !important;\n      }\n      body.view-only-mode #bulk-toolbar,\n      body.view-only-mode #bulk-toolbar-mobile,\n      body.view-only-mode #bulk-actions-modal {\n        display: none !important;\n      }\n      body.view-only-mode::after {\n        content: \"VIEW ONLY • click 5× to inspect\";\n        position: fixed;\n        bottom: 20px;\n        left: 50%;\n        transform: translateX(-50%);\n        background: rgba(245, 101, 101, 0.9);\n        color: white;\n        padding: 6px 16px;\n        border-radius: 20px;\n        font-size: 12px;\n        font-weight: 600;\n        letter-spacing: 1px;\n        z-index: 9999;\n        pointer-events: none;\n      }\n\n.dropdown {\n  position: relative;\n  display: inline-block;\n}\n.dropdown-btn {\n  padding: 6px 12px;\n  background: var(--btn-bg, var(--panel));\n  color: var(--btn-text, var(--text-main));\n  border: 1px solid var(--edge-main);\n  border-radius: 6px;\n  cursor: pointer;\n  font-size: 14px;\n  font-weight: 600;\n  white-space: nowrap;\n}\n.dropdown-btn:hover {\n  background: var(--accent);\n  color: var(--bg);\n}\n.dropdown-menu {\n  display: none;\n  position: absolute;\n  top: 100%;\n  left: 0;\n  min-width: 180px;\n  background: var(--panel);\n  border: 1px solid var(--edge-main);\n  border-radius: 6px;\n  box-shadow: 0 4px 20px rgba(0,0,0,0.4);\n  z-index: 100000;\n  margin-top: 4px;\n  overflow: hidden;\n}\nheader {\n  z-index: 99999;\n  position: relative;\n}\n.dropdown-menu.open {\n  display: block;\n}\n.dropdown-menu button {\n  display: block;\n  width: 100%;\n  padding: 10px 14px;\n  background: none;\n  border: none;\n  color: var(--text-main);\n  text-align: left;\n  cursor: pointer;\n  font-size: 14px;\n}\n.dropdown-menu button:hover {\n  background: var(--panel-alt);\n  color: var(--accent);\n}\n.dropdown-divider {\n  height: 1px;\n  background: var(--edge-main);\n  margin: 4px 0;\n}\n#mobile-export-btn,\n#mobile-import-btn {\n  display: none;\n}\n@media (max-width: 900px) {\n  .save-row .dropdown {\n    display: none !important;\n  }\n  #mobile-export-btn,\n  #mobile-import-btn {\n    display: inline-block;\n  }\n}\n\n@media print {\n  @page {\n    size: landscape;\n    margin: 0.5cm;\n  }\n  html, body {\n    width: 100%;\n    height: 100%;\n    margin: 0;\n    padding: 0;\n    overflow: visible !important;\n  }\n  body * {\n    visibility: hidden;\n  }\n  #canvas-viewport,\n  #canvas-viewport *,\n  #map,\n  #map * {\n    visibility: visible;\n  }\n  #canvas-viewport {\n    position: absolute !important;\n    left: 0 !important;\n    top: 0 !important;\n    right: 0 !important;\n    bottom: 0 !important;\n    width: 100vw !important;\n    height: 100vh !important;\n    overflow: visible !important;\n  }\n  #map {\n    position: absolute !important;\n    left: 0 !important;\n    top: 0 !important;\n    width: 100% !important;\n    height: 100% !important;\n    background: white !important;\n    background-image: none !important;\n  }\n  #canvas-grid {\n    display: none !important;\n  }\n  main, .topology-panel {\n    display: block !important;\n    position: static !important;\n    overflow: visible !important;\n  }\n\n  #map .node-hit-area,\n  #map .group-indicator,\n  #map .lock-indicator,\n  #map .ping-indicator,\n  #map .fov-group,\n  #map .edge-arrow-forward,\n  #map .edge-arrow-backward {\n    display: none !important;\n  }\n\n  #map .node-circle,\n  #map .node-shape {\n    fill: white !important;\n    stroke: #000 !important;\n    stroke-width: 2px !important;\n  }\n\n  #map .node-circle svg,\n  #map .node-circle svg path,\n  #map .node-circle svg circle,\n  #map .node-circle svg rect,\n  #map .node-circle svg polygon {\n    fill: #000 !important;\n    stroke: none !important;\n  }\n\n  #map text {\n    fill: #000 !important;\n    stroke: none !important;\n  }\n\n  #map .port-label rect {\n    fill: white !important;\n    stroke: #000 !important;\n    stroke-width: 1px !important;\n    stroke-opacity: 1 !important;\n  }\n\n  #map .edge,\n  #map polyline,\n  #map line:not([class*=\"grid\"]) {\n    stroke: #333 !important;\n  }\n  #map .rect-group rect {\n    stroke: #333 !important;\n  }\n  header, .sidebar, .mobile-footer, .minimap-zoom-wrapper,\n  .draw-toolbar, .topology-toolbar, .legend-container,\n  .bulk-toolbar, #bulk-toolbar-mobile, .dropdown-menu,\n  #canvas-hint, .node-panel, .edge-panel, .text-panel, .rect-panel {\n    display: none !important;\n  }\n}\n\nhtml.rtl {\n  direction: rtl;\n}\nhtml.rtl .sidebar { left: auto; right: 0; }\nhtml.rtl .mobile-footer { direction: rtl; }\nhtml.rtl .modal-content { text-align: right; }\nhtml.rtl .modal-buttons { flex-direction: row-reverse; }\nhtml.rtl .style-row { flex-direction: row-reverse; }\nhtml.rtl .style-row label { text-align: right; }\nhtml.rtl input, html.rtl select, html.rtl textarea { text-align: right; }\nhtml.rtl header { flex-direction: row-reverse; }\nhtml.rtl .title-block { text-align: right; }\nhtml.rtl .save-row { flex-direction: row-reverse; }\nhtml.rtl details summary { text-align: right; }\nhtml.rtl .node-panel-content { direction: rtl; }\nhtml.rtl table { direction: rtl; }\nhtml.rtl td:first-child { text-align: right; }\nhtml.rtl .lang-string-row { flex-direction: row-reverse; }\n    </style>\n  </head>\n  <body style=\"background: radial-gradient(circle at center top, rgb(30, 37, 50) 0px, rgb(5, 6, 8) 70%);\" class=\"\">\n    <div class=\"modal\" id=\"language-editor-modal\" style=\"z-index:9999999999\">\n      <div class=\"modal-content\" style=\"max-width:900px;max-height:85vh;display:flex;flex-direction:column;\">\n        <h3 data-lang=\"ui.modals.languageEditor\">Language Editor</h3>\n        <div style=\"display:flex;gap:8px;flex-wrap:wrap;margin-bottom:12px;\" class=\"lang-editor-tabs\">\n          <button class=\"lang-tab active\" data-filter=\"all\" style=\"padding:6px 12px;border:1px solid var(--edge-main);border-radius:4px;background:var(--accent);color:var(--bg);cursor:pointer;font-size:12px;\" data-lang=\"langEditor.tabs.all\">All</button>\n          <button class=\"lang-tab\" data-filter=\"modes\" style=\"padding:6px 12px;border:1px solid var(--edge-main);border-radius:4px;background:var(--panel);color:var(--text-main);cursor:pointer;font-size:12px;\" data-lang=\"langEditor.tabs.modes\">Modes</button>\n          <button class=\"lang-tab\" data-filter=\"modes.network\" style=\"padding:6px 12px;border:1px solid var(--edge-main);border-radius:4px;background:var(--panel);color:var(--text-main);cursor:pointer;font-size:12px;\" data-lang=\"langEditor.tabs.network\">Network</button>\n          <button class=\"lang-tab\" data-filter=\"modes.mindmap\" style=\"padding:6px 12px;border:1px solid var(--edge-main);border-radius:4px;background:var(--panel);color:var(--text-main);cursor:pointer;font-size:12px;\" data-lang=\"langEditor.tabs.mindMap\">Mind Map</button>\n          <button class=\"lang-tab\" data-filter=\"modes.sports\" style=\"padding:6px 12px;border:1px solid var(--edge-main);border-radius:4px;background:var(--panel);color:var(--text-main);cursor:pointer;font-size:12px;\" data-lang=\"langEditor.tabs.sports\">Sports</button>\n          <button class=\"lang-tab\" data-filter=\"modes.smarthome\" style=\"padding:6px 12px;border:1px solid var(--edge-main);border-radius:4px;background:var(--panel);color:var(--text-main);cursor:pointer;font-size:12px;\" data-lang=\"langEditor.tabs.smartHome\">Smart Home</button>\n          <button class=\"lang-tab\" data-filter=\"modes.floorplan\" style=\"padding:6px 12px;border:1px solid var(--edge-main);border-radius:4px;background:var(--panel);color:var(--text-main);cursor:pointer;font-size:12px;\" data-lang=\"langEditor.tabs.floorPlan\">Floor Plan</button>\n          <button class=\"lang-tab\" data-filter=\"ui\" style=\"padding:6px 12px;border:1px solid var(--edge-main);border-radius:4px;background:var(--panel);color:var(--text-main);cursor:pointer;font-size:12px;\" data-lang=\"langEditor.tabs.ui\">UI</button>\n          <button class=\"lang-tab\" data-filter=\"shapes\" style=\"padding:6px 12px;border:1px solid var(--edge-main);border-radius:4px;background:var(--panel);color:var(--text-main);cursor:pointer;font-size:12px;\" data-lang=\"langEditor.tabs.shapes\">Shapes</button>\n          <button class=\"lang-tab\" data-filter=\"messages\" style=\"padding:6px 12px;border:1px solid var(--edge-main);border-radius:4px;background:var(--panel);color:var(--text-main);cursor:pointer;font-size:12px;\" data-lang=\"langEditor.tabs.messages\">Messages</button>\n        </div>\n        <input type=\"text\" id=\"lang-search\" placeholder=\"Search strings...\" style=\"width:100%;padding:8px 12px;margin-bottom:12px;background:var(--panel-alt);color:var(--text-main);border:1px solid var(--edge-main);border-radius:6px;\" data-lang=\"langEditor.searchPlaceholder\" data-lang-attr=\"placeholder\">\n        <div class=\"lang-editor-list\" style=\"flex:1;overflow-y:auto;border:1px solid var(--edge-main);border-radius:6px;background:var(--panel-alt);\"></div>\n        <div class=\"modal-buttons\" style=\"margin-top:12px;\">\n          <button class=\"btn-cancel\" onclick=\"closeLanguageEditor()\" data-lang=\"ui.buttons.cancel\">Cancel</button>\n          <button class=\"btn-save\" onclick=\"saveLangEdits()\" data-lang=\"ui.buttons.saveChanges\">Save Changes</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"icon-picker-modal\" id=\"icon-picker-modal\">\n      <div class=\"icon-picker-content\">\n        <div class=\"icon-picker-header\">\n          <h3 data-lang=\"ui.modals.selectIcon\">Select Icon</h3>\n          <div class=\"icon-picker-tabs\">\n            <button class=\"icon-picker-tab\" data-library=\"mdi\" data-lang=\"iconPicker.mdi\">MDI</button>\n            <button class=\"icon-picker-tab\" data-library=\"simple\" data-lang=\"iconPicker.simpleIcons\">Simple Icons</button>\n            <button class=\"icon-picker-tab active\" data-library=\"selfhst\" data-lang=\"iconPicker.selfhst\">selfh.st/icons</button>\n          </div>\n          <input type=\"text\" class=\"icon-picker-search\" id=\"icon-search\" data-lang=\"ui.placeholders.searchIcons\" data-lang-attr=\"placeholder\" placeholder=\"Search icons...\" style=\"display: none;\">\n        </div>\n        <div class=\"icon-picker-body\" id=\"icon-picker-body\">\n        </div>\n        <div class=\"icon-picker-footer\">\n          <button class=\"icon-btn-cancel\" id=\"icon-picker-cancel\" data-lang=\"ui.buttons.cancel\">Cancel</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"tabs-modal\">\n      <div class=\"modal-content\" style=\"max-width: 600px;\">\n        <p style=\"font-size: 13px; color: var(--text-soft); margin-bottom: 15px;\" data-lang=\"messages.manageTopologies\">Manage multiple topologies</p>\n        <div id=\"tabs-list\" style=\"display: flex; flex-direction: column; gap: 8px; max-height: 400px; overflow-y: auto; margin-bottom: 15px;\">\n            <div class=\"tab-item active\" onclick=\"switchTab(0)\">\n              <div class=\"tab-name\">Main Topology</div>\n              <div class=\"tab-stats\">0 nodes • 0 connections</div>\n              <div class=\"tab-actions\">\n                <button class=\"btn-cancel\" onclick=\"event.stopPropagation(); renameTab(0)\" title=\"Rename tab\">✏️</button>\n                \n              </div>\n            </div>\n          </div>\n        <div style=\"display: flex; gap: 8px; margin-bottom: 10px;\">\n          <input type=\"text\" id=\"new-tab-name\" data-lang=\"ui.placeholders.newTabName\" data-lang-attr=\"placeholder\" placeholder=\"New tab name...\" style=\"flex: 1; padding: 8px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 6px; color: var(--text-main);\">\n          <button class=\"btn-save\" onclick=\"createNewTab()\" data-lang=\"ui.buttons.addTab\">+ Add Tab</button>\n        </div>\n        <div class=\"modal-buttons\">\n          <button class=\"btn-cancel\" id=\"tabs-modal-close\" data-lang=\"ui.buttons.close\">Close</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"rollback-modal\">\n      <div class=\"modal-content\" style=\"max-width: 700px;\">\n        <h3 data-lang=\"ui.modals.versionHistory\">Version History</h3>\n        <p style=\"font-size: 13px; color: var(--text-soft); margin-bottom: 15px;\" data-lang=\"messages.snapshotLimit\">Limit: Snapshots</p>\n        <div id=\"rollback-list\" style=\"display: flex; flex-direction: column; gap: 8px; max-height: 450px; overflow-y: auto; margin-bottom: 15px;\"></div>\n        <div style=\"display: flex; gap: 8px; margin-bottom: 10px;\">\n          <button class=\"btn-cancel\" onclick=\"clearRollbackHistory()\" data-lang=\"ui.buttons.clearHistory\">Clear History</button>\n          <button class=\"btn-save\" onclick=\"createManualSnapshot()\" data-lang=\"ui.buttons.createSnapshot\">Create Snapshot</button>\n        </div>\n        <div class=\"modal-buttons\">\n          <button class=\"btn-cancel\" id=\"rollback-modal-close\" data-lang=\"ui.buttons.close\">Close</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"audit-log-modal\">\n      <div class=\"modal-content\" style=\"max-width: 800px;\">\n        <h3 data-lang=\"ui.modals.auditLog\">Audit Log</h3>\n        <p style=\"font-size: 13px; color: var(--text-soft); margin-bottom: 15px;\" data-lang=\"messages.trackChanges\">Track all changes made to your topology</p>\n        <div style=\"display: flex; gap: 8px; margin-bottom: 10px;\">\n          <select id=\"audit-filter\" style=\"padding: 8px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 6px; color: var(--text-main);\">\n            <option value=\"all\" data-lang=\"ui.options.allEvents\">All Events</option>\n            <option value=\"node\" data-lang=\"ui.options.nodeOperations\">Node Operations</option>\n            <option value=\"connection\" data-lang=\"ui.sections.connections\">Connections</option>\n            <option value=\"style\" data-lang=\"ui.options.styleChanges\">Style Changes</option>\n            <option value=\"rack\" data-lang=\"ui.options.rackOperations\">Rack Operations</option>\n            <option value=\"layer\" data-lang=\"ui.options.layerChanges\">Layer Changes</option>\n          </select>\n          <button class=\"btn-save\" onclick=\"exportAuditLog()\" data-lang=\"ui.buttons.export\">Export</button>\n        </div>\n        <div id=\"audit-log-list\" style=\"display: flex; flex-direction: column; gap: 4px; max-height: 450px; overflow-y: auto; margin-bottom: 15px; font-family: monospace; font-size: 12px;\"></div>\n        <div style=\"display: flex; gap: 8px;\">\n          <button class=\"btn-cancel\" onclick=\"clearAuditLog()\" data-lang=\"ui.buttons.clearLog\">Clear Log</button>\n          <button class=\"btn-cancel\" id=\"audit-log-modal-close\" data-lang=\"ui.buttons.close\">Close</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"port-map-modal\">\n      <div class=\"modal-content\" style=\"max-width: 900px;\">\n        <h3 data-lang=\"ui.modals.portMap\">Port Map</h3>\n        <p style=\"font-size: 13px; color: var(--text-soft); margin-bottom: 15px;\" data-lang=\"messages.portMapDesc\">All connections with port assignments</p>\n        <div style=\"display: flex; gap: 8px; margin-bottom: 10px;\">\n          <input type=\"text\" id=\"port-map-search\" data-lang=\"ui.placeholders.searchDevices\" data-lang-attr=\"placeholder\" placeholder=\"Search devices or ports...\" style=\"flex: 1; min-width: 200px; padding: 8px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 6px; color: var(--text-main);\">\n          <select id=\"port-map-filter\" style=\"padding: 8px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 6px; color: var(--text-main);\">\n            <option value=\"all\" data-lang=\"ui.options.allConnections\">All Connections</option>\n            <option value=\"with-ports\" data-lang=\"ui.options.withPortsOnly\">With Ports Only</option>\n            <option value=\"without-ports\" data-lang=\"ui.options.missingPorts\">Missing Ports</option>\n          </select>\n        </div>\n        <div id=\"port-map-table\" style=\"max-height: 450px; overflow-y: auto; margin-bottom: 15px;\"><div style=\"text-align: center; padding: 40px; color: var(--text-soft);\" data-lang=\"messages.noConnections\">No connections found</div></div>\n        <div style=\"display: flex; gap: 8px;\">\n          <button class=\"btn-save\" onclick=\"exportPortMap()\" data-lang=\"ui.buttons.csv\">Export CSV</button>\n          <button class=\"btn-cancel\" id=\"port-map-modal-close\" data-lang=\"ui.buttons.close\">Close</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"secrets-modal\">\n      <div class=\"modal-content\" style=\"max-width: 700px;\">\n        <h3 data-lang=\"ui.modals.notesHub\">Notes Hub</h3>\n        <div style=\"display: flex; gap: 8px; margin-bottom: 12px;\">\n          <input type=\"text\" id=\"notes-hub-search\" placeholder=\"Search notes...\" style=\"flex: 1; min-width: 0; padding: 8px 12px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 6px; color: var(--text-main); font-size: 14px;\">\n          <select id=\"notes-hub-filter\" style=\"flex-shrink: 0; width: auto; padding: 8px 12px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 6px; color: var(--text-main); font-size: 14px;\">\n            <option value=\"all\">All Notes</option>\n            <option value=\"global\">Global Notes</option>\n            <option value=\"node\">Node Notes</option>\n            <option value=\"edge\">Line Notes</option>\n            <option value=\"rect\">Zone Notes</option>\n            <option value=\"image\">Image Notes</option>\n          </select>\n        </div>\n        <div id=\"secrets-list\" style=\"display: flex; flex-direction: column; gap: 8px; max-height: 400px; overflow-y: auto; margin-bottom: 15px;\"><div style=\"text-align: center; padding: 40px; color: var(--text-soft);\" data-lang=\"notes.noNotes\">No notes yet</div></div>\n        <div style=\"border-top: 1px solid var(--edge-main); padding-top: 12px; margin-top: 8px;\">\n          <p style=\"font-size: 12px; color: var(--text-soft); margin-bottom: 10px;\" data-lang=\"messages.notesEncryptionNote\">Global notes can be stored with AES 256 encryption</p>\n          <div style=\"display: flex; gap: 8px;\">\n            <input type=\"text\" id=\"new-secret-name\" placeholder=\"New global note name...\" style=\"flex: 1; padding: 8px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 6px; color: var(--text-main);\" data-lang=\"notes.namePlaceholder\" data-lang-attr=\"placeholder\">\n            <button class=\"btn-save\" onclick=\"createNewSecret()\" data-lang=\"ui.buttons.addNote\">+ Add Note</button>\n          </div>\n        </div>\n        <div class=\"modal-buttons\">\n          <button class=\"btn-cancel\" id=\"secrets-modal-close\" data-lang=\"ui.buttons.close\">Close</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"secret-editor-modal\">\n      <div class=\"modal-content\" style=\"max-width: 600px;\">\n        <h3 id=\"secret-editor-title\" data-lang=\"ui.modals.editNote\">Edit Note</h3>\n        <div class=\"note-editor-toolbar\" id=\"secret-editor-toolbar\">\n          <button type=\"button\" data-command=\"bold\" title=\"Bold\"><b>B</b></button>\n          <button type=\"button\" data-command=\"italic\" title=\"Italic\"><i>I</i></button>\n          <button type=\"button\" data-command=\"underline\" title=\"Underline\"><u>U</u></button>\n          <span class=\"toolbar-separator\"></span>\n          <select id=\"secret-heading-select\" title=\"Heading\">\n            <option value=\"\">Normal</option>\n            <option value=\"h1\">H1</option>\n            <option value=\"h2\">H2</option>\n            <option value=\"h3\">H3</option>\n          </select>\n          <span class=\"toolbar-separator\"></span>\n          <button type=\"button\" data-command=\"insertUnorderedList\" title=\"Bullet List\">•</button>\n          <button type=\"button\" data-command=\"insertOrderedList\" title=\"Numbered List\">1.</button>\n          <span class=\"toolbar-separator\"></span>\n          <button type=\"button\" data-command=\"code\" title=\"Inline Code\">&lt;/&gt;</button>\n          <button type=\"button\" data-command=\"codeblock\" title=\"Code Block\">[ ]</button>\n          <button type=\"button\" data-command=\"createLink\" title=\"Link\">🔗</button>\n          <button type=\"button\" data-command=\"blockquote\" title=\"Quote\">\"</button>\n          <span class=\"toolbar-separator\"></span>\n          <button type=\"button\" data-command=\"removeFormat\" title=\"Clear Formatting\">✖</button>\n        </div>\n        <div class=\"note-editor-content\" id=\"secret-editor-content\" contenteditable=\"true\" style=\"height: 200px;\"></div>\n        <div style=\"margin: 15px 0; display: flex; align-items: center; gap: 8px;\">\n          <label class=\"toggle-switch\"><input type=\"checkbox\" id=\"secret-auto-encrypt\" checked><span class=\"toggle-slider\"></span></label>\n          <span data-lang=\"ui.labels.encryptNote\">Encrypt Note</span>\n        </div>\n        <div class=\"modal-buttons\">\n          <button class=\"btn-cancel\" onclick=\"closeSecretEditor()\" data-lang=\"ui.buttons.cancel\">Cancel</button>\n          <button class=\"btn-save\" onclick=\"saveSecret()\" data-lang=\"ui.buttons.save\">Save</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"mobile-export-modal\">\n      <div class=\"modal-content\" style=\"max-width: 320px;\">\n        <h3 style=\"margin: 0 0 20px 0;\" data-lang=\"ui.modals.export\">Export</h3>\n        <div style=\"display: flex; flex-direction: column; gap: 12px;\">\n          <button onclick=\"screenshotCanvas(); document.getElementById('mobile-export-modal').classList.remove('active');\" style=\"padding: 14px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; color: var(--text-main); font-size: 16px; cursor: pointer;\" data-lang=\"ui.buttons.pngImage\">PNG Image</button>\n          <button onclick=\"exportCanvasSVG(); document.getElementById('mobile-export-modal').classList.remove('active');\" style=\"padding: 14px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; color: var(--text-main); font-size: 16px; cursor: pointer;\" data-lang=\"ui.buttons.svgVector\">SVG Vector</button>\n          <button onclick=\"exportJSONFile(); document.getElementById('mobile-export-modal').classList.remove('active');\" style=\"padding: 14px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; color: var(--text-main); font-size: 16px; cursor: pointer;\" data-lang=\"ui.buttons.json\">JSON</button>\n          <button onclick=\"exportMarkdown(); document.getElementById('mobile-export-modal').classList.remove('active');\" style=\"padding: 14px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; color: var(--text-main); font-size: 16px; cursor: pointer;\" data-lang=\"ui.buttons.markdown\">Markdown</button>\n          <button onclick=\"exportCSV(); document.getElementById('mobile-export-modal').classList.remove('active');\" style=\"padding: 14px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; color: var(--text-main); font-size: 16px; cursor: pointer;\" data-lang=\"ui.buttons.csv\">CSV</button>\n          <button onclick=\"printTopology(); document.getElementById('mobile-export-modal').classList.remove('active');\" style=\"padding: 14px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; color: var(--text-main); font-size: 16px; cursor: pointer;\" data-lang=\"ui.buttons.printBW\">Print B&W</button>\n          <button onclick=\"printTopologyColor(); document.getElementById('mobile-export-modal').classList.remove('active');\" style=\"padding: 14px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; color: var(--text-main); font-size: 16px; cursor: pointer;\" data-lang=\"mobileExport.printColorFull\">Print Full Color</button>\n          <button onclick=\"printTopologyColor('#ffffff'); document.getElementById('mobile-export-modal').classList.remove('active');\" style=\"padding: 14px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; color: var(--text-main); font-size: 16px; cursor: pointer;\" data-lang=\"mobileExport.printColorWhite\">Print White BG</button>\n          <button onclick=\"printTopologyColor('#000000'); document.getElementById('mobile-export-modal').classList.remove('active');\" style=\"padding: 14px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; color: var(--text-main); font-size: 16px; cursor: pointer;\" data-lang=\"mobileExport.printColorBlack\">Print Black BG</button>\n\t\t  <button onclick=\"showFormatHelp(); document.getElementById('mobile-import-modal').classList.remove('active');\" style=\"padding: 14px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; color: var(--text-main); font-size: 16px; cursor: pointer;\" data-lang=\"ui.buttons.importHelp\">Import Help</button>\n        </div>\n        <button onclick=\"document.getElementById('mobile-export-modal').classList.remove('active');\" style=\"margin-top: 20px; padding: 12px; background: var(--danger); border: none; border-radius: 8px; color: white; font-size: 16px; cursor: pointer; width: 100%;\" data-lang=\"ui.buttons.cancel\">Cancel</button>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"mobile-import-modal\">\n      <div class=\"modal-content\" style=\"max-width: 320px;\">\n        <h3 style=\"margin: 0 0 20px 0;\" data-lang=\"ui.modals.import\">Import</h3>\n        <div style=\"display: flex; flex-direction: column; gap: 12px;\">\n          <button onclick=\"document.getElementById('import-html-file').click(); document.getElementById('mobile-import-modal').classList.remove('active');\" style=\"padding: 14px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; color: var(--text-main); font-size: 16px; cursor: pointer;\" data-lang=\"ui.buttons.html\">HTML</button>\n          <button onclick=\"document.getElementById('import-json-file').click(); document.getElementById('mobile-import-modal').classList.remove('active');\" style=\"padding: 14px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; color: var(--text-main); font-size: 16px; cursor: pointer;\" data-lang=\"ui.buttons.json\">JSON</button>\n          <button onclick=\"document.getElementById('import-markdown-file').click(); document.getElementById('mobile-import-modal').classList.remove('active');\" style=\"padding: 14px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; color: var(--text-main); font-size: 16px; cursor: pointer;\" data-lang=\"ui.buttons.markdown\">Markdown</button>\n          <button onclick=\"document.getElementById('import-csv-file').click(); document.getElementById('mobile-import-modal').classList.remove('active');\" style=\"padding: 14px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; color: var(--text-main); font-size: 16px; cursor: pointer;\" data-lang=\"ui.buttons.csv\">CSV</button>\n          <button onclick=\"showFormatHelp(); document.getElementById('mobile-import-modal').classList.remove('active');\" style=\"padding: 14px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; color: var(--text-main); font-size: 16px; cursor: pointer;\" data-lang=\"ui.buttons.exportHelp\">Export Help</button>\n        </div>\n        <button onclick=\"document.getElementById('mobile-import-modal').classList.remove('active');\" style=\"margin-top: 20px; padding: 12px; background: var(--danger); border: none; border-radius: 8px; color: white; font-size: 16px; cursor: pointer; width: 100%;\" data-lang=\"ui.buttons.cancel\">Cancel</button>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"edit-modal\">\n      <div class=\"modal-content\">\n        <h3 id=\"modal-title\" data-lang=\"ui.modals.editName\">Edit Name</h3>\n        <input type=\"text\" id=\"modal-input\">\n        <div class=\"modal-buttons\">\n          <button class=\"btn-cancel\" id=\"modal-cancel\" data-lang=\"ui.buttons.cancel\">Cancel</button>\n          <button class=\"btn-save\" id=\"modal-save\" data-lang=\"ui.buttons.save\">Save</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"note-editor-modal\">\n      <div class=\"modal-content\">\n        <h3 id=\"note-editor-title\" data-lang=\"noteEditor.title\">Edit Note</h3>\n        <div class=\"note-editor-toolbar\">\n          <button type=\"button\" data-command=\"bold\" title=\"Bold (Ctrl+B)\"><b>B</b></button>\n          <button type=\"button\" data-command=\"italic\" title=\"Italic (Ctrl+I)\"><i>I</i></button>\n          <button type=\"button\" data-command=\"underline\" title=\"Underline (Ctrl+U)\"><u>U</u></button>\n          <div class=\"toolbar-separator\"></div>\n          <select id=\"note-heading-select\" title=\"Heading\">\n            <option value=\"\">Normal</option>\n            <option value=\"h1\">H1</option>\n            <option value=\"h2\">H2</option>\n            <option value=\"h3\">H3</option>\n          </select>\n          <div class=\"toolbar-separator\"></div>\n          <button type=\"button\" data-command=\"insertUnorderedList\" title=\"Bullet List\">&#8226;</button>\n          <button type=\"button\" data-command=\"insertOrderedList\" title=\"Numbered List\">1.</button>\n          <div class=\"toolbar-separator\"></div>\n          <button type=\"button\" data-command=\"code\" title=\"Inline Code\">&lt;/&gt;</button>\n          <button type=\"button\" data-command=\"codeblock\" title=\"Code Block\">[ ]</button>\n          <div class=\"toolbar-separator\"></div>\n          <button type=\"button\" data-command=\"createLink\" title=\"Insert Link\">&#128279;</button>\n          <button type=\"button\" data-command=\"blockquote\" title=\"Quote\">&#8220;</button>\n          <div class=\"toolbar-separator\"></div>\n          <button type=\"button\" data-command=\"removeFormat\" title=\"Clear Formatting\">&#10006;</button>\n        </div>\n        <div class=\"note-editor-content\" id=\"note-editor-content\" contenteditable=\"true\"></div>\n        <div class=\"modal-buttons\">\n          <button class=\"btn-cancel\" id=\"note-editor-cancel\" data-lang=\"ui.buttons.cancel\">Cancel</button>\n          <button class=\"btn-save\" id=\"note-editor-save\" data-lang=\"ui.buttons.save\">Save</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal confirm-modal\" id=\"confirm-modal\">\n      <div class=\"modal-content\">\n        <h3 data-lang=\"ui.modals.confirm\">Confirm</h3>\n        <p id=\"confirm-message\" data-lang=\"messages.confirmDelete\"> Are you sure you want to delete this line? </p>\n        <div class=\"modal-buttons\">\n          <button class=\"btn-cancel\" id=\"confirm-cancel\" data-lang=\"ui.buttons.cancel\">Cancel</button>\n          <button class=\"btn-delete\" id=\"confirm-delete\" data-lang=\"ui.buttons.delete\">Delete</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"save-info-modal\">\n      <div class=\"modal-content\" style=\"max-width: 700px; max-height: 80vh; overflow-y: auto;\">\n        <h3 style=\"margin-bottom: 16px;\" data-lang=\"help.title\">Help</h3>\n        <div style=\"display: flex; gap: 8px; margin-bottom: 16px; flex-wrap: wrap;\">\n          <button class=\"help-tab active\" data-tab=\"general\" style=\"padding: 8px 16px; border: 1px solid var(--edge-main); background: var(--accent); color: var(--bg); border-radius: 6px; cursor: pointer; font-weight: 600;\" data-lang=\"help.tabs.general\">General</button>\n          <button class=\"help-tab\" data-tab=\"desktop\" style=\"padding: 8px 16px; border: 1px solid var(--edge-main); background: var(--panel); color: var(--text-main); border-radius: 6px; cursor: pointer; font-weight: 600;\" data-lang=\"help.tabs.desktop\">Desktop</button>\n          <button class=\"help-tab\" data-tab=\"mobile\" style=\"padding: 8px 16px; border: 1px solid var(--edge-main); background: var(--panel); color: var(--text-main); border-radius: 6px; cursor: pointer; font-weight: 600;\" data-lang=\"help.tabs.mobile\">Mobile</button>\n\t\t  <button class=\"help-tab\" data-tab=\"formats\" style=\"padding: 8px 16px; border: 1px solid var(--edge-main); background: var(--panel); color: var(--text-main); border-radius: 6px; cursor: pointer; font-weight: 600;\" data-lang=\"help.tabs.formats\">Import/Export</button>\n          <button class=\"help-tab\" data-tab=\"recording\" style=\"padding: 8px 16px; border: 1px solid var(--edge-main); background: var(--panel); color: var(--text-main); border-radius: 6px; cursor: pointer; font-weight: 600;\" data-lang=\"help.tabs.recording\">Recording</button>\n        </div>\n        <div id=\"help-tab-general\" class=\"help-tab-content\" style=\"display: block;\">\n          <ul style=\"margin-left: 20px; margin-bottom: 12px; line-height: 1.8;\">\n            <li><strong data-lang=\"help.general.addNodes\">Add Nodes:</strong> <span data-lang=\"help.general.addNodesDesc\">Click \"+ Node\" or \"+ Rack\" in the top menu</span></li>\n            <li><strong data-lang=\"help.general.connectNodes\">Connect Nodes:</strong> <span data-lang=\"help.general.connectNodesDesc\">Select a node, then use \"Add Connection\" in the panel</span></li>\n            <li><strong data-lang=\"help.general.moveNodes\">Move Nodes:</strong> <span data-lang=\"help.general.moveNodesDesc\">Drag nodes to reposition them</span></li>\n            <li><strong data-lang=\"help.general.enterRackView\">Enter Rack View:</strong> <span data-lang=\"help.general.enterRackViewDesc\">Double click on desktop or Long press on mobile</span></li>\n            <li><strong data-lang=\"help.general.multiSelect\">Multi Select:</strong> <span data-lang=\"help.general.multiSelectDesc\">Right click (desktop) or double tap (mobile)</span></li>\n            <li><strong data-lang=\"help.general.panCanvas\">Pan Canvas:</strong> <span data-lang=\"help.general.panCanvasDesc\">Drag empty space or hold Space + drag</span></li>\n            <li><strong data-lang=\"help.general.zoom\">Zoom:</strong> <span data-lang=\"help.general.zoomDesc\">Scroll wheel or pinch gesture</span></li>\n            <li><strong data-lang=\"help.general.freeDraw\">Free Draw:</strong> <span data-lang=\"help.general.freeDrawDesc\">Use draw toolbar for drawing lines, boxes/zones, and text</span></li>\n          </ul>\n          <h4 style=\"color: var(--accent); margin-bottom: 8px;\" data-lang=\"help.general.savingEncryption\">Saving &amp; Encryption</h4>\n          <p style=\"margin-bottom: 8px;\" data-lang=\"help.general.savingNote\">Browsers cannot overwrite local files. Click Save File to download an updated HTML with all changes. Replace your old file to keep edits. An automatic backup is also saved to your browser's local storage and will prompt for recovery if you close without saving.</p>\n          <p style=\"margin-bottom: 8px;\"><strong data-lang=\"help.general.encryptionNote\">Encryption of data:</strong> <span data-lang=\"help.general.encryptionDesc\">Check \"Encrypt\" before saving to password protect your data. Beware! No recovery possible without password!!</span></p>\n          <p><strong data-lang=\"help.general.decryptionNote\">Decryption of data:</strong> <span data-lang=\"help.general.decryptionDesc\">Export via JSON in top settings menu after successful password validation to decrypt your data. You can then reimport back into the same file and save!</span></p>\n        </div>\n        <div id=\"help-tab-desktop\" class=\"help-tab-content\" style=\"display: none;\">\n          <h4 style=\"color: var(--accent); margin-bottom: 12px;\" data-lang=\"help.desktop.navigation\">Navigation</h4>\n          <table style=\"width: 100%; border-collapse: collapse; margin-bottom: 16px; font-size: 14px;\">\n            <tbody><tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\" data-lang=\"help.desktop.arrowKeys\">Arrow Keys</code></td>\n              <td style=\"padding: 8px;\" data-lang=\"help.desktop.moveSelected1px\">Move selected 1px</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\" data-lang=\"help.desktop.shiftArrows\">Shift + Arrows</code></td>\n              <td style=\"padding: 8px;\" data-lang=\"help.desktop.moveSelected10px\">Move selected 10px</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\" data-lang=\"help.desktop.tabShiftTab\">Tab / Shift+Tab</code></td>\n              <td style=\"padding: 8px;\" data-lang=\"help.desktop.cycleNodes\">Cycle through nodes</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\" data-lang=\"help.desktop.fKey\">F</code></td>\n              <td style=\"padding: 8px;\" data-lang=\"help.desktop.focusSelected\">Focus on selected</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\" data-lang=\"help.desktop.spaceDrag\">Space + Drag</code></td>\n              <td style=\"padding: 8px;\" data-lang=\"help.desktop.panCanvas\">Pan canvas</td>\n            </tr>\n          </tbody></table>\n          <h4 style=\"color: var(--accent); margin-bottom: 12px;\" data-lang=\"help.desktop.management\">Management</h4>\n          <table style=\"width: 100%; border-collapse: collapse; margin-bottom: 16px; font-size: 14px;\">\n            <tbody><tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\" data-lang=\"help.desktop.lKey\">L</code></td>\n              <td style=\"padding: 8px;\" data-lang=\"help.desktop.lockUnlock\">Lock/unlock selected</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\" data-lang=\"help.desktop.gKey\">G</code></td>\n              <td style=\"padding: 8px;\" data-lang=\"help.desktop.groupUngroup\">Group/ungroup selected</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\" data-lang=\"help.desktop.ctrlCV\">Ctrl+C / Ctrl+V</code></td>\n              <td style=\"padding: 8px;\" data-lang=\"help.desktop.copyPaste\">Copy / Paste</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\" data-lang=\"help.desktop.ctrlD\">Ctrl+D</code></td>\n              <td style=\"padding: 8px;\" data-lang=\"help.desktop.duplicate\">Duplicate</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\" data-lang=\"help.desktop.ctrlA\">Ctrl+A</code></td>\n              <td style=\"padding: 8px;\" data-lang=\"help.desktop.selectAll\">Select all</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\" data-lang=\"help.desktop.deleteKey\">Delete</code></td>\n              <td style=\"padding: 8px;\" data-lang=\"help.desktop.deleteSelected\">Delete selected</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\" data-lang=\"help.desktop.escapeKey\">Escape</code></td>\n              <td style=\"padding: 8px;\" data-lang=\"help.desktop.clearSelection\">Clear selection</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\" data-lang=\"help.desktop.ctrlZY\">Ctrl+Z / Ctrl+Y</code></td>\n              <td style=\"padding: 8px;\" data-lang=\"help.desktop.undoRedo\">Undo / Redo</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\" data-lang=\"help.desktop.rightClick\">Right Click</code></td>\n              <td style=\"padding: 8px;\" data-lang=\"help.desktop.multiSelectToggle\">Multi select toggle</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\" data-lang=\"help.desktop.dblClickRack\">Double click rack</code></td>\n              <td style=\"padding: 8px;\" data-lang=\"help.desktop.enterRackView\">Enter rack view</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\" data-lang=\"help.desktop.dblClickConnection\">Double click connection</code></td>\n              <td style=\"padding: 8px;\" data-lang=\"help.desktop.addWaypoint\">Add waypoint</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\" data-lang=\"help.desktop.dblClickEmpty\">Double click empty (in rack)</code></td>\n              <td style=\"padding: 8px;\" data-lang=\"help.desktop.exitRackView\">Exit rack view</td>\n            </tr>\n          </tbody></table>\n          <h4 style=\"color: var(--accent); margin-bottom: 12px;\" data-lang=\"help.desktop.recording\">Recording</h4>\n          <table style=\"width: 100%; border-collapse: collapse; margin-bottom: 16px; font-size: 14px;\">\n            <tbody><tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\" data-lang=\"help.desktop.rKey\">R</code></td>\n              <td style=\"padding: 8px;\" data-lang=\"help.desktop.startStopRecording\">Start/stop real time recording</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\" data-lang=\"help.desktop.shiftR\">Shift+R</code></td>\n              <td style=\"padding: 8px;\" data-lang=\"help.desktop.startStopStepRecording\">Start/stop step by step recording</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\" data-lang=\"help.desktop.spaceKey\">Space</code></td>\n              <td style=\"padding: 8px;\" data-lang=\"help.desktop.addStepOrPlayPause\">Add step (step recording) / Play/Pause (playback)</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\" data-lang=\"help.desktop.pKey\">P</code></td>\n              <td style=\"padding: 8px;\" data-lang=\"help.desktop.playRecording\">Play recording</td>\n            </tr>\n          </tbody></table>\n        </div>\n        <div id=\"help-tab-mobile\" class=\"help-tab-content\" style=\"display: none;\">\n          <h4 style=\"color: var(--accent); margin-bottom: 12px;\" data-lang=\"help.mobile.basicGestures\">Basic Gestures</h4>\n          <table style=\"width: 100%; border-collapse: collapse; margin-bottom: 16px; font-size: 14px;\">\n            <tbody><tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><strong data-lang=\"help.mobile.tapNode\">Tap node</strong></td>\n              <td style=\"padding: 8px;\" data-lang=\"help.mobile.selectOpenProperties\">Select &amp; open properties</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><strong data-lang=\"help.mobile.tapEmpty\">Tap empty</strong></td>\n              <td style=\"padding: 8px;\" data-lang=\"help.mobile.deselectAll\">Deselect all</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><strong data-lang=\"help.mobile.dragNode\">Drag node</strong></td>\n              <td style=\"padding: 8px;\" data-lang=\"help.mobile.moveNode\">Move node</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><strong data-lang=\"help.mobile.dragEmpty\">Drag empty</strong></td>\n              <td style=\"padding: 8px;\" data-lang=\"help.mobile.panCanvas\">Pan canvas</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><strong data-lang=\"help.mobile.pinch\">Pinch</strong></td>\n              <td style=\"padding: 8px;\" data-lang=\"help.mobile.zoomInOut\">Zoom in/out</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><strong data-lang=\"help.mobile.doubleTapNode\">Double tap node</strong></td>\n              <td style=\"padding: 8px;\" data-lang=\"help.mobile.addRemoveSelection\">Add/remove from selection</td>\n            </tr>\t\t\n          </tbody></table>\n          <h4 style=\"color: var(--accent); margin-bottom: 12px;\" data-lang=\"help.mobile.rackView\">Rack View</h4>\n          <table style=\"width: 100%; border-collapse: collapse; margin-bottom: 16px; font-size: 14px;\">\n            <tbody><tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><strong data-lang=\"help.mobile.longPressRack\">Long press rack</strong></td>\n              <td style=\"padding: 8px;\" data-lang=\"help.mobile.enterRackView\">Enter rack view</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><strong data-lang=\"help.mobile.longPressConnection\">Long press connection</strong></td>\n              <td style=\"padding: 8px;\" data-lang=\"help.mobile.addWaypoint\">Add waypoint</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><strong data-lang=\"help.mobile.doubleTapEmpty\">Double tap empty</strong></td>\n              <td style=\"padding: 8px;\" data-lang=\"help.mobile.exitRackView\">Exit rack view</td>\n            </tr>\n          </tbody></table>\n        </div>\n\t\t<div id=\"help-tab-formats\" class=\"help-tab-content\" style=\"display: none;\">\n          <h4 style=\"color: var(--accent); margin-bottom: 12px;\" data-lang=\"help.formats.exportHelp\">Export Help</h4>\n          <table style=\"width: 100%; border-collapse: collapse; margin-bottom: 16px; font-size: 14px;\">\n            <tbody>\n              <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n                <td style=\"padding: 8px;\"><strong data-lang=\"help.formats.html\">HTML</strong></td>\n                <td style=\"padding: 8px;\" data-lang=\"help.formats.htmlDesc\">Complete self contained file and the default save method. When in doubt, choose this. Use the Save HTML button for this</td>\n              </tr>\n              <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n                <td style=\"padding: 8px;\"><strong data-lang=\"help.formats.json\">JSON</strong></td>\n                <td style=\"padding: 8px;\" data-lang=\"help.formats.jsonExportDesc\">Full backup. Editable in your favorite text editor</td>\n              </tr>\n              <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n                <td style=\"padding: 8px;\"><strong data-lang=\"help.formats.markdown\">Markdown</strong></td>\n                <td style=\"padding: 8px;\" data-lang=\"help.formats.markdownExportDesc\">Full backup. Editable in your favorite text editor</td>\n              </tr>\n              <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n                <td style=\"padding: 8px;\"><strong data-lang=\"help.formats.csv\">CSV</strong></td>\n                <td style=\"padding: 8px;\" data-lang=\"help.formats.csvExportDesc\">Full backup in header, nodes in spreadsheet rows</td>\n              </tr>\n              <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n                <td style=\"padding: 8px;\"><strong data-lang=\"help.formats.png\">PNG</strong></td>\n                <td style=\"padding: 8px;\" data-lang=\"help.formats.pngDesc\">Standard image of current canvas tab</td>\n              </tr>\n              <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n                <td style=\"padding: 8px;\"><strong data-lang=\"help.formats.svg\">SVG</strong></td>\n                <td style=\"padding: 8px;\" data-lang=\"help.formats.svgDesc\">Vector image, scalable and editable image of current canvas tab</td>\n              </tr>\n            </tbody>\n          </table>\n          <h4 style=\"color: var(--accent); margin-bottom: 12px;\" data-lang=\"help.formats.importHelp\">Import Help</h4>\n          <table style=\"width: 100%; border-collapse: collapse; margin-bottom: 16px; font-size: 14px;\">\n            <tbody>\n              <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n                <td style=\"padding: 8px;\"><strong data-lang=\"help.formats.json\">JSON</strong></td>\n                <td style=\"padding: 8px;\" data-lang=\"help.formats.jsonImportDesc\">Replaces all data</td>\n              </tr>\n              <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n                <td style=\"padding: 8px;\"><strong data-lang=\"help.formats.markdown\">Markdown</strong></td>\n                <td style=\"padding: 8px;\" data-lang=\"help.formats.markdownImportDesc\">Replaces all data</td>\n              </tr>\n              <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n                <td style=\"padding: 8px;\"><strong data-lang=\"help.formats.csv\">CSV</strong></td>\n                <td style=\"padding: 8px;\" data-lang=\"help.formats.csvImportDesc\">Choose full restore or add nodes only. Allows quickly creating nodes with a spreadsheet and importing into an existing save.</td>\n              </tr>\n              <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n                <td style=\"padding: 8px;\"><strong data-lang=\"help.formats.html\">HTML</strong></td>\n                <td style=\"padding: 8px;\" data-lang=\"help.formats.htmlImportDesc\">Import data from another saved HTML file. Extracts and restores all topology data from a previously exported file.</td>\n              </tr>\n            </tbody>\n          </table>\n        </div>\n        <div id=\"help-tab-recording\" class=\"help-tab-content\" style=\"display: none;\">\n          <h4 style=\"color: var(--accent); margin-bottom: 12px;\" data-lang=\"help.recording.title\">Canvas Recording</h4>\n          <p style=\"margin-bottom: 12px;\" data-lang=\"help.recording.overview\">Record your canvas activity to create visual documentation, tutorials, or demonstrations of your network topology changes.</p>\n          <h4 style=\"color: var(--accent); margin-bottom: 8px; margin-top: 16px;\" data-lang=\"help.recording.twoModes\">Two Recording Modes</h4>\n          <p style=\"margin-bottom: 8px;\"><strong style=\"color: #f56565;\" data-lang=\"help.recording.realtimeTitle\">Real time Recording</strong> <span data-lang=\"help.recording.realtimeDesc\">(red ● button) captures the canvas continuously at 10 frames per second as you work. Good for demonstrating workflows.</span></p>\n          <p style=\"margin-bottom: 12px;\"><strong style=\"color: #48bb78;\" data-lang=\"help.recording.stepByStepTitle\">Step by Step Recording</strong> <span data-lang=\"help.recording.stepByStepDesc\">(green ●+ button) lets you manually capture each frame. Click the green button to start, then press + or Space to capture each step. Each frame plays for 1 second. Good for tutorials where you want precise control over what's shown.</span></p>\n          <ul style=\"margin-left: 20px; margin-bottom: 12px; line-height: 1.8;\">\n            <li data-lang=\"help.recording.savedInFile\">Recordings are saved within your data file for later playback</li>\n            <li data-lang=\"help.recording.exportVideo\">Can be exported as a video file (WebM format) for sharing</li>\n            <li data-lang=\"help.recording.playWhileRecording\">Pressing Play while recording will stop and immediately play the new recording</li>\n          </ul>\n          <h4 style=\"color: var(--accent); margin-bottom: 8px; margin-top: 16px;\" data-lang=\"help.recording.controls\">Controls</h4>\n          <table style=\"width: 100%; border-collapse: collapse; margin-bottom: 16px; font-size: 14px;\">\n            <tbody>\n              <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n                <td style=\"padding: 8px;\"><strong style=\"color: #f56565;\">●</strong> <span data-lang=\"help.recording.recordBtn\">Record</span></td>\n                <td style=\"padding: 8px;\" data-lang=\"help.recording.recordBtnDesc\">Start real time recording (captures 10 frames/sec). Press R</td>\n              </tr>\n              <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n                <td style=\"padding: 8px;\"><strong style=\"color: #48bb78;\">●+</strong> <span data-lang=\"help.recording.stepRecordBtn\">Step Record</span></td>\n                <td style=\"padding: 8px;\" data-lang=\"help.recording.stepRecordBtnDesc\">Start step by step recording. Press Shift+R</td>\n              </tr>\n              <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n                <td style=\"padding: 8px;\"><strong style=\"color: #48bb78;\">+</strong> <span data-lang=\"help.recording.addStepBtn\">Add Step</span></td>\n                <td style=\"padding: 8px;\" data-lang=\"help.recording.addStepBtnDesc\">Capture current state as next frame (during step recording). Press Space</td>\n              </tr>\n              <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n                <td style=\"padding: 8px;\"><strong>▶</strong> <span data-lang=\"help.recording.playBtn\">Play</span></td>\n                <td style=\"padding: 8px;\" data-lang=\"help.recording.playBtnDesc\">Play back a saved recording. Press P</td>\n              </tr>\n              <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n                <td style=\"padding: 8px;\"><strong>■</strong> <span data-lang=\"help.recording.stopBtn\">Stop</span></td>\n                <td style=\"padding: 8px;\" data-lang=\"help.recording.stopBtnDesc\">Stop recording or playback. Press R or Shift+R again</td>\n              </tr>\n              <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n                <td style=\"padding: 8px;\"><strong>❚❚</strong> <span data-lang=\"help.recording.pauseBtn\">Pause</span></td>\n                <td style=\"padding: 8px;\" data-lang=\"help.recording.pauseBtnDesc\">Pause playback. Press Space</td>\n              </tr>\n              <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n                <td style=\"padding: 8px;\"><strong data-lang=\"help.recording.scrubber\">Scrubber</strong></td>\n                <td style=\"padding: 8px;\" data-lang=\"help.recording.scrubberDesc\">Seek to any point in the recording</td>\n              </tr>\n              <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n                <td style=\"padding: 8px;\"><strong data-lang=\"help.recording.speed\">Speed</strong></td>\n                <td style=\"padding: 8px;\" data-lang=\"help.recording.speedDesc\">Adjust playback speed (0.5x, 1x, 2x, 4x)</td>\n              </tr>\n              <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n                <td style=\"padding: 8px;\"><strong data-lang=\"help.recording.loop\">Loop</strong></td>\n                <td style=\"padding: 8px;\" data-lang=\"help.recording.loopDesc\">Toggle continuous loop playback</td>\n              </tr>\n              <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n                <td style=\"padding: 8px;\"><strong>...</strong> <span data-lang=\"help.recording.manage\">Manage</span></td>\n                <td style=\"padding: 8px;\" data-lang=\"help.recording.manageDesc\">View, rename, export, or delete saved recordings</td>\n              </tr>\n            </tbody>\n          </table>\n        </div>\n        <div class=\"modal-buttons\" style=\"margin-top: 16px;\">\n          <button class=\"btn-cancel\" id=\"save-info-close\" data-lang=\"ui.buttons.close\">Close</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"dialog-modal\">\n      <div class=\"modal-content\">\n        <h3 id=\"dialog-modal-title\">Dialog</h3>\n        <p id=\"dialog-modal-message\" style=\"margin: 16px 0; line-height: 1.6; white-space: pre-wrap;\"></p>\n        <input type=\"text\" id=\"dialog-modal-input\" style=\"display: none; width: 100%; padding: 10px; margin-bottom: 16px; background: var(--input-bg); color: var(--input-text); border: 1px solid var(--input-border); border-radius: 6px; font-size: 14px;\">\n        <div class=\"modal-buttons\" id=\"dialog-modal-buttons\">\n          <button class=\"btn-cancel\" id=\"dialog-modal-cancel\" data-lang=\"ui.buttons.cancel\">Cancel</button>\n          <button class=\"btn-save\" id=\"dialog-modal-ok\" data-lang=\"dialogs.ok\">OK</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"settings-modal\">\n      <div class=\"modal-content\">\n        <h2 data-lang=\"ui.modals.settings\">Settings</h2>\n        <details class=\"style-section\" open>\n          <summary data-lang=\"ui.sections.mappingModeTheme\">Mapping Mode & Theme</summary>\n          <div class=\"style-content\">\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.mode\">Mode</label>\n              <select id=\"mapping-mode-select\">\n                <option value=\"network\" data-lang=\"modes.network.name\">Network Topology</option>\n                <option value=\"mindmap\" data-lang=\"modes.mindmap.name\">Mind Map</option>\n                <option value=\"sports\" data-lang=\"modes.sports.name\">Sports Playbook</option>\n                <option value=\"smarthome\" data-lang=\"modes.smarthome.name\">Smart Home</option>\n                <option value=\"floorplan\" data-lang=\"modes.floorplan.name\">Floor Plan / Blueprint</option>\n              </select>\n            </div>\n            <div class=\"style-row\" id=\"canvas-style-row\">\n              <label data-lang=\"ui.labels.canvasStyle\">Canvas Style</label>\n              <select id=\"canvas-style-select\">\n                <option value=\"grid\" data-lang=\"canvas.grid\">Standard Grid</option>\n                <option value=\"dots\" data-lang=\"canvas.dots\">Dot Grid</option>\n                <option value=\"blueprint\" data-lang=\"canvas.blueprint\">Blueprint Grid</option>\n                <option value=\"none\" data-lang=\"canvas.none\">No Grid</option>\n              </select>\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.themePreset\">Theme Preset</label>\n              <div style=\"display:flex;gap:6px;align-items:center;\">\n                <select id=\"theme-preset\" style=\"flex:1;\">\n                  <option value=\"defaulted\" data-lang=\"themes.default\">Default</option>\n                  <option value=\"\" data-lang=\"ui.options.custom\">Custom</option>\n                  <optgroup label=\"Corporate\" data-lang-label=\"themeGroups.corporate\">\n                    <option value=\"slate\" data-lang=\"themes.slate\">Slate</option>\n                    <option value=\"graphite\" data-lang=\"themes.graphite\">Graphite</option>\n                    <option value=\"frost\" data-lang=\"themes.frost\">Frost (Light)</option>\n                  </optgroup>\n                  <optgroup label=\"Homelab\" data-lang-label=\"themeGroups.homelab\">\n                    <option value=\"synthwave\" data-lang=\"themes.synthwave\">Synthwave</option>\n                    <option value=\"terminal\" data-lang=\"themes.terminal\">Terminal</option>\n                  </optgroup>\n                  <optgroup label=\"Dev\" data-lang-label=\"themeGroups.dev\">\n                    <option value=\"dracula\" data-lang=\"themes.dracula\">Dracula</option>\n                    <option value=\"cobalt\" data-lang=\"themes.cobalt\">Cobalt</option>\n                    <option value=\"solarized\" data-lang=\"themes.solarized\">Solarized</option>\n                  </optgroup>\n                  <optgroup label=\"Mind Map\" data-lang-label=\"themeGroups.mindmap\">\n                    <option value=\"brainwave\" data-lang=\"themes.brainwave\">Brainwave</option>\n                                        <option value=\"neonMint\" data-lang=\"themes.neonMint\">Neon Mint</option>\n                    <option value=\"velvetDusk\" data-lang=\"themes.velvetDusk\">Velvet Dusk</option>\n                    <option value=\"sunburst\" data-lang=\"themes.sunburst\">Sunburst</option>\n                  </optgroup>\n                  <optgroup id=\"my-themes-group\" label=\"My Themes\" data-lang-label=\"themeGroups.myThemes\"></optgroup>\n                </select>\n                <button onclick=\"saveCurrentTheme()\" data-lang=\"ui.tooltips.saveTheme\" data-lang-attr=\"title\" title=\"Save current theme\" style=\"padding:4px 8px;background:var(--accent);color:var(--bg);border:none;border-radius:4px;cursor:pointer;font-size:12px;\" data-lang=\"ui.buttons.save\">Save</button>\n                <button id=\"delete-theme-btn\" onclick=\"deleteCurrentTheme()\" data-lang=\"ui.tooltips.deleteTheme\" data-lang-attr=\"title\" title=\"Delete theme\" style=\"padding:4px 8px;background:var(--danger);color:#fff;border:none;border-radius:4px;cursor:pointer;font-size:12px;\" data-lang=\"ui.buttons.del\">Del</button>\n              </div>\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.mainBackground\">Main Background</label>\n              <input type=\"color\" id=\"panel-color\" value=\"#0b0e13\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.accentColor\">Accent Color</label>\n              <input type=\"color\" id=\"accent-color\" value=\"#4fd1c5\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.primaryText\">Primary Text</label>\n              <input type=\"color\" id=\"text-main-color\" value=\"#e2e8f0\">\n            </div>\n          </div>\n        </details>\n        <details class=\"style-section\">\n          <summary data-lang=\"ui.sections.viewOnlyMode\">View Only Mode</summary>\n          <div class=\"style-content\">\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.viewOnly\">View Only (disable editing)</label>\n              <label class=\"toggle-switch\"><input type=\"checkbox\" id=\"view-only-mode\"><span class=\"toggle-slider\"></span></label>\n            </div>\n            <p style=\"margin-top:4px;font-size:12px;color:var(--text-soft);\" data-lang=\"messages.viewOnlyNote\">When enabled, all editing of the canvas is disabled. Pan and zoom still work.</p>\n          </div>\n        </details>\n        <details class=\"style-section\">\n          <summary data-lang=\"ui.sections.moreThemeOptions\">More Theme Options</summary>\n          <div class=\"style-content\">\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.sidebar\">Sidebar / Mobile Footer</label>\n              <input type=\"color\" id=\"sidebar-bg-color\" value=\"#10141b\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.modalBg\">Modal Window Background</label>\n              <input type=\"color\" id=\"panel-alt-color\" value=\"#10141b\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.dangerButtons\">Danger Buttons</label>\n              <input type=\"color\" id=\"danger-color\" value=\"#f56565\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.secondaryText\">Secondary Text</label>\n              <input type=\"color\" id=\"text-soft-color\" value=\"#94a3b8\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.tagFill\">Tag(s) Fill</label>\n              <input type=\"color\" id=\"tag-fill-color\" value=\"#1e293b\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.tagText\">Tag(s) Text</label>\n              <input type=\"color\" id=\"tag-text-color\" value=\"#e2e8f0\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.tagBorder\">Tag(s) Border</label>\n              <input type=\"color\" id=\"tag-border-color\" value=\"#475569\">\n            </div>\n          </div>\n        </details>\n        <details class=\"style-section\">\n          <summary data-lang=\"ui.sections.topBar\">Top Bar</summary>\n          <div class=\"style-content\">\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.background\">Background</label>\n              <input type=\"color\" id=\"topbar-bg-color\" value=\"#0b0e13\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.border\">Border</label>\n              <input type=\"color\" id=\"topbar-border-color\" value=\"#1f2533\">\n            </div>\n          </div>\n\t\t  <div class=\"style-content\">\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.buttonFill\">Button Fill</label>\n              <input type=\"color\" id=\"btn-bg-color\" value=\"#0b0e13\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.buttonText\">Button Text</label>\n              <input type=\"color\" id=\"btn-text-color\" value=\"#e2e8f0\">\n            </div>\n          </div>\n        </details>\n        <details class=\"style-section\">\n          <summary data-lang=\"ui.sections.mainCanvas\">Main Canvas</summary>\n          <div class=\"style-content\">\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.border\">Border</label>\n              <input type=\"color\" id=\"canvas-border-color\" value=\"#475569\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.gridLines\">Grid Lines</label>\n              <input type=\"color\" id=\"canvas-grid-color\" value=\"#475569\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.gridSize\">Grid Size</label>\n              <input type=\"number\" id=\"canvas-grid-size\" value=\"50\" min=\"20\" max=\"200\" style=\"width:60px;\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.showGrid\">Show Grid</label>\n              <label class=\"toggle-switch\"><input type=\"checkbox\" id=\"canvas-grid-enabled\" checked><span class=\"toggle-slider\"></span></label>\n            </div>\n\t\t    <div class=\"style-row\">\n              <label data-lang=\"ui.labels.bgTop\">Background Top</label>\n              <input type=\"color\" id=\"canvas-gradient-top\" value=\"#1e2532\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.bgBottom\">Background Bottom</label>\n              <input type=\"color\" id=\"canvas-gradient-bottom\" value=\"#050608\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.solidBg\">Solid Background</label>\n              <input type=\"color\" id=\"page-bg-color\" value=\"#050608\">\n            </div>\n            <p style=\"margin-top:4px;font-size:12px;color:var(--text-soft);\" data-lang=\"messages.setSolidBgNote\">Set solid background to override gradient.</p>\n          </div>\n        </details>\n        <details class=\"style-section\">\n          <summary data-lang=\"ui.sections.rackCanvas\">Rack Canvas</summary>\n          <div class=\"style-content\">\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.frameFill\">Frame Fill</label>\n              <input type=\"color\" id=\"rack-frame-fill\" value=\"#0f172a\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.frameBorder\">Frame Border</label>\n              <input type=\"color\" id=\"rack-frame-stroke\" value=\"#4fd1c5\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.gridLines\">Grid Lines</label>\n              <input type=\"color\" id=\"rack-line-color\" value=\"#475569\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.showGrid\">Show Grid</label>\n              <label class=\"toggle-switch\"><input type=\"checkbox\" id=\"rack-grid-enabled\" checked><span class=\"toggle-slider\"></span></label>\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.uLabels\">U Labels</label>\n              <input type=\"color\" id=\"rack-text-color\" value=\"#4fd1c5\">\n            </div>\n          </div>\n       </details>\n\t   <details class=\"style-section\">\n          <summary data-lang=\"ui.sections.canvasToolbars\">Canvas Toolbars</summary>\n          <div class=\"style-content\">\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.background\">Background</label>\n              <input type=\"color\" id=\"toolbar-bg-color\" value=\"#0f172a\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.border\">Border</label>\n              <input type=\"color\" id=\"toolbar-border-color\" value=\"#1f2937\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.text\">Text</label>\n              <input type=\"color\" id=\"toolbar-text-color\" value=\"#94a3b8\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.buttonFill\">Button Fill</label>\n              <input type=\"color\" id=\"toolbar-btn-bg-color\" value=\"#0b0e13\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.buttonText\">Button Text</label>\n              <input type=\"color\" id=\"toolbar-btn-text-color\" value=\"#e2e8f0\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.minimapDots\">Minimap Dots</label>\n              <input type=\"color\" id=\"minimap-dots-color\" value=\"#94a3b8\">\n            </div>\n          </div>\n        </details>\n        <details class=\"style-section\">\n          <summary data-lang=\"ui.sections.nodes\">Nodes</summary>\n          <div class=\"style-content\">\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.fill\">Fill</label>\n              <input type=\"color\" id=\"node-fill-color\" value=\"#1e293b\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.border\">Border</label>\n              <input type=\"color\" id=\"node-stroke-color\" value=\"#475569\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.titleColor\">Title Color</label>\n              <input type=\"color\" id=\"node-title-color\" value=\"#e2e8f0\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.titleSize\">Title Size</label>\n              <input type=\"number\" id=\"node-title-size\" value=\"18\" min=\"8\" max=\"48\" style=\"width:60px;\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.subtitleColor\">Subtitle Color</label>\n              <input type=\"color\" id=\"node-sub-color\" value=\"#94a3b8\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.subtitleSize\">Subtitle Size</label>\n              <input type=\"number\" id=\"node-sub-size\" value=\"13\" min=\"8\" max=\"48\" style=\"width:60px;\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.font\">Font</label>\n              <select id=\"node-font-family\" style=\"flex:1;padding:4px;background:var(--panel);color:var(--text-main);border:1px solid var(--topbar-border);border-radius:4px;\">\n                <option value=\"Inter, system-ui, sans-serif\" data-lang=\"ui.fonts.inter\">Inter</option>\n                <option value=\"Arial, sans-serif\" data-lang=\"ui.fonts.arial\">Arial</option>\n                <option value=\"Helvetica, sans-serif\" data-lang=\"ui.fonts.helvetica\">Helvetica</option>\n                <option value=\"Georgia, serif\" data-lang=\"ui.fonts.georgia\">Georgia</option>\n                <option value=\"monospace\" data-lang=\"ui.fonts.monospace\">Monospace</option>\n              </select>\n            </div>\n          </div>\n        </details>\n        <details class=\"style-section\">\n          <summary data-lang=\"ui.sections.connections\">Connections</summary>\n          <div class=\"style-content\">\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.defaultColor\">Default Color</label>\n              <input type=\"color\" id=\"default-edge-color\" value=\"#475569\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.defaultRouting\">Default Routing</label>\n              <select id=\"default-edge-routing\">\n                <option value=\"orthogonal\" data-lang=\"ui.options.orthogonal\">Orthogonal (90°)</option>\n                <option value=\"curved\" data-lang=\"ui.options.curved\">Curved</option>\n                <option value=\"straight\" data-lang=\"ui.options.straight\">Straight</option>\n              </select>\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.animationStyle\">Animation Style</label>\n              <select id=\"animation-style-select\">\n                <option value=\"arrows\" data-lang=\"ui.options.flowingArrows\">Flowing Arrows</option>\n                <option value=\"dots\" data-lang=\"ui.options.dotArrows\">Dot Arrows</option>\n              </select>\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.animateDirections\">Animate Directions</label>\n              <select id=\"animation-direction-select\">\n                <option value=\"all\" data-lang=\"ui.options.allDirections\">All Directions</option>\n                <option value=\"forward\" data-lang=\"ui.options.forwardOnly\">Forward Only</option>\n                <option value=\"backward\" data-lang=\"ui.options.backwardOnly\">Backward Only</option>\n                <option value=\"both\" data-lang=\"ui.options.bidirectionalOnly\">Bidirectional Only</option>\n              </select>\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.animationSpeed\">Animation Speed</label>\n              <select id=\"animation-speed-select\">\n                <option value=\"0.5\" data-lang=\"ui.options.veryFast\">Very Fast</option>\n                <option value=\"1\">Fast</option>\n                <option value=\"1.5\">Normal</option>\n                <option value=\"2.5\">Slow</option>\n                <option value=\"4\">Very Slow</option>\n              </select>\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.showPortLabels\">Show Port Labels</label>\n              <label class=\"toggle-switch\"><input type=\"checkbox\" id=\"show-port-labels-toggle\" checked><span class=\"toggle-slider\"></span></label>\n            </div>\n            <div class=\"style-row\" style=\"grid-column: 1 / -1;\">\n              <button id=\"apply-routing-all\" style=\"width:100%;padding:8px;background:var(--accent);color:var(--bg);border:none;border-radius:6px;cursor:pointer;font-weight:600;\" data-lang=\"ui.buttons.applyRoutingAll\">Apply Routing to All Connections</button>\n            </div>\n          </div>\n        </details>\n\t\t<details class=\"style-section\">\n          <summary data-lang=\"ui.sections.animationsZones\">Animations &amp; Zones</summary>\n          <div class=\"style-content\">\n            <div class=\"style-row\"><label data-lang=\"ui.labels.allAnimations\">All Animations</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"anim-master\" checked><span class=\"toggle-slider\"></span></label></div>\n            <div style=\"grid-column:1/-1;font-size:11px;color:var(--text-soft);text-transform:uppercase;border-bottom:1px solid var(--edge-main);padding-bottom:4px;margin-top:8px;\" data-lang=\"ui.sections.byType\">By Type</div>\n            <div class=\"style-row\"><label data-lang=\"ui.labels.sweepPan\">Sweep (Pan)</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"anim-type-sweep\" checked><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label data-lang=\"ui.labels.pulseBreathe\">Pulse (Breathe)</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"anim-type-pulse\" checked><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label data-lang=\"ui.labels.ringsEmanate\">Rings (Emanate)</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"anim-type-rings\" checked><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label data-lang=\"ui.labels.spinRotate\">Spin (Rotate)</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"anim-type-spin\" checked><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label data-lang=\"ui.labels.connectionsFlow\">Connections (Flow)</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"anim-type-connections\" checked><span class=\"toggle-slider\"></span></label></div>\n            <div style=\"grid-column:1/-1;font-size:11px;color:var(--text-soft);text-transform:uppercase;border-bottom:1px solid var(--edge-main);padding-bottom:4px;margin-top:8px;\" data-lang=\"ui.sections.byCategory\">By Category</div>\n            <div class=\"style-row\"><label data-lang=\"ui.labels.cameras\">Cameras</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"anim-cat-camera\" checked><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label data-lang=\"ui.labels.doorbells\">Doorbells</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"anim-cat-doorbell\" checked><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label data-lang=\"ui.labels.motionSensors\">Motion Sensors</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"anim-cat-motion\" checked><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label data-lang=\"ui.labels.smokeDetectors\">Smoke Detectors</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"anim-cat-smoke\" checked><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label data-lang=\"ui.labels.wifiApRouter\">WiFi/AP/Router</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"anim-cat-wifi\" checked><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label data-lang=\"ui.labels.sensorsIot\">Sensors/IoT</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"anim-cat-sensor\" checked><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label data-lang=\"ui.labels.sprinklers\">Sprinklers</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"anim-cat-sprinkler\" checked><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label data-lang=\"ui.sections.connections\">Connections</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"anim-cat-connections\" checked><span class=\"toggle-slider\"></span></label></div>\n            <div style=\"grid-column:1/-1;font-size:12px;color:var(--accent);text-transform:uppercase;letter-spacing:0.05em;font-weight:600;margin-top:16px;padding-top:12px;border-top:1px solid var(--edge-main);\" data-lang=\"ui.sections.zoneSettings\">Zone (Animation Cone) Settings</div>\n            <div class=\"style-row\"><label data-lang=\"ui.labels.allZones\">All Zones</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"zone-master\" checked><span class=\"toggle-slider\"></span></label></div>\n            <div style=\"grid-column:1/-1;font-size:11px;color:var(--text-soft);text-transform:uppercase;border-bottom:1px solid var(--edge-main);padding-bottom:4px;margin-top:8px;\" data-lang=\"ui.sections.byCategory\">By Category</div>\n            <div class=\"style-row\"><label data-lang=\"ui.labels.cameras\">Cameras</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"zone-cat-camera\" checked><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label data-lang=\"ui.labels.doorbells\">Doorbells</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"zone-cat-doorbell\" checked><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label data-lang=\"ui.labels.motionSensors\">Motion Sensors</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"zone-cat-motion\" checked><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label data-lang=\"ui.labels.smokeDetectors\">Smoke Detectors</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"zone-cat-smoke\" checked><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label data-lang=\"ui.labels.wifiApRouter\">WiFi/AP/Router</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"zone-cat-wifi\" checked><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label data-lang=\"ui.labels.sensorsIot\">Sensors/IoT</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"zone-cat-sensor\" checked><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label data-lang=\"ui.labels.sprinklers\">Sprinklers</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"zone-cat-sprinkler\" checked><span class=\"toggle-slider\"></span></label></div>\n          </div>\n        </details>\n        <details class=\"style-section\">\n          <summary data-lang=\"ui.sections.groupsEditing\">Groups &amp; Editing</summary>\n          <div class=\"style-content\">\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.resizeHandleColor\">Resize Handle Color</label>\n              <input type=\"color\" id=\"selection-handle-color\" value=\"#f59e0b\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.resizeHandleSize\">Resize Handle Size</label>\n              <input type=\"number\" id=\"selection-handle-size\" value=\"8\" min=\"4\" max=\"16\" style=\"width:60px;\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.groupedIconOutline\">Grouped Icon Outline</label>\n              <input type=\"color\" id=\"group-indicator-color\" value=\"#4fd1c5\">\n            </div>\n          </div>\n\t\t  <div class=\"style-content\">\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.multiselectFill\">Multiselect Fill Color (Desktop)</label>\n              <input type=\"color\" id=\"selection-fill-color\" value=\"#4fd1c5\">\n            </div>\n           <div class=\"style-row\">\n              <label data-lang=\"ui.labels.multiselectFillOpacity\">Multiselect Fill Opacity (Desktop)</label>\n              <div style=\"display:flex;align-items:center;gap:8px;\">\n                <input type=\"range\" id=\"selection-fill-opacity\" min=\"0\" max=\"1\" step=\"0.05\" value=\"0.1\">\n                <span id=\"selection-fill-opacity-val\" style=\"min-width:35px;text-align:right;\">10%</span>\n              </div>\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.multiselectBorder\">Multiselect Border Color (Desktop)</label>\n              <input type=\"color\" id=\"selection-stroke-color\" value=\"#4fd1c5\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.multiselectBorderWidth\">Multiselect Border Width (Desktop)</label>\n              <input type=\"number\" id=\"selection-stroke-width\" min=\"1\" max=\"10\" value=\"2\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.multiselectBorderStyle\">Multiselect Border Style (Desktop)</label>\n              <select id=\"selection-stroke-style\">\n                <option value=\"5,5\" data-lang=\"ui.options.dashed\">Dashed</option>\n                <option value=\"2,4\" data-lang=\"ui.options.dotted\">Dotted</option>\n                <option value=\"none\" data-lang=\"ui.options.solid\">Solid</option>\n              </select>\n            </div>\n          </div>\n        </details>\n\t\t<details class=\"style-section\">\n          <summary data-lang=\"ui.sections.inputsDropdowns\">Inputs &amp; Dropdowns</summary>\n          <div class=\"style-content\">\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.background\">Background</label>\n              <input type=\"color\" id=\"input-bg-color\" value=\"#0b0e13\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.text\">Text</label>\n              <input type=\"color\" id=\"input-text-color\" value=\"#e2e8f0\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.border\">Border</label>\n              <input type=\"color\" id=\"input-border-color\" value=\"#1f2937\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.font\">Font</label>\n              <select id=\"input-font-family\" style=\"flex:1;padding:4px;background:var(--panel);color:var(--text-main);border:1px solid var(--topbar-border);border-radius:4px;\">\n                <option value=\"Inter, system-ui, sans-serif\" data-lang=\"ui.fonts.inter\">Inter</option>\n                <option value=\"Arial, sans-serif\" data-lang=\"ui.fonts.arial\">Arial</option>\n                <option value=\"Helvetica, sans-serif\" data-lang=\"ui.fonts.helvetica\">Helvetica</option>\n                <option value=\"Georgia, serif\" data-lang=\"ui.fonts.georgia\">Georgia</option>\n                <option value=\"monospace\" data-lang=\"ui.fonts.monospace\">Monospace</option>\n              </select>\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.fontSize\">Font Size</label>\n              <input type=\"number\" id=\"input-font-size\" value=\"14\" min=\"10\" max=\"24\" style=\"width:60px;\">\n            </div>\n          </div>\n        </details>\n        <details class=\"style-section\">\n          <summary data-lang=\"ui.sections.welcomeMessage\">Welcome Message</summary>\n          <div class=\"style-content\">\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.enabled\">Enabled</label>\n              <label class=\"toggle-switch\"><input type=\"checkbox\" id=\"canvas-hint-enabled\" checked><span class=\"toggle-slider\"></span></label>\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.background\">Background</label>\n              <input type=\"color\" id=\"canvas-hint-bg-color\" value=\"#0f172a\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.textColor\">Text Color</label>\n              <input type=\"color\" id=\"canvas-hint-text-color\" value=\"#94a3b8\">\n            </div>\n            <div class=\"style-row\" style=\"flex-direction:column;align-items:stretch;\">\n              <label data-lang=\"ui.labels.customTextHtml\">Custom Text (HTML)</label>\n              <textarea id=\"canvas-hint-text\" rows=\"4\" style=\"width:100%;margin-top:4px;padding:6px;background:var(--panel);color:var(--text-main);border:1px solid var(--topbar-border);border-radius:4px;font-size:12px;resize:vertical;\" placeholder=\"Leave empty for default\" data-lang=\"ui.placeholders.leaveEmpty\" data-lang-attr=\"placeholder\"></textarea>\n            </div>\n          </div>\n        </details>\n         <details class=\"style-section\">\n          <summary data-lang=\"ui.sections.autoStatusChecking\">Auto Status Checking</summary>\n          <div class=\"style-content\">\n            <div style=\"display: flex; align-items: center; gap: 8px; margin-bottom: 16px;\">\n            <label class=\"toggle-switch\"><input type=\"checkbox\" id=\"auto-ping-enabled\"><span class=\"toggle-slider\"></span></label>\n            <span style=\"font-size: 14px; font-weight: 600;\" data-lang=\"ui.labels.enableAutoStatusChecking\">Enable automatic status checking</span>\n            </div>\n            <div id=\"auto-ping-settings\" style=\"display: none; padding-left: 20px; border-left: 2px solid var(--edge-main);\">\n              <div class=\"style-row\" style=\"margin-bottom: 12px;\">\n                <label data-lang=\"ui.labels.checkInterval\">Check Interval (seconds):</label>\n                <input type=\"number\" id=\"auto-ping-interval\" min=\"5\" max=\"3600\" value=\"30\" style=\"width: 80px; padding: 6px; background: var(--panel); border: 1px solid var(--edge-main); border-radius: 4px; color: var(--text-main);\">\n              </div>\n              <div style=\"padding: 10px; background: var(--panel); border-radius: 6px; border: 1px solid var(--edge-main); font-size: 12px; color: var(--text-soft);\">\n                <div style=\"margin-bottom: 4px;\"><span id=\"auto-ping-next-check\" data-lang=\"ui.labels.nextCheckIn\">Next check in: --</span></div>\n                <div><span id=\"auto-ping-last-run\" data-lang=\"ui.labels.lastRun\">Last run: 1:15:07 PM</span></div>\n              </div>\n            </div>\n            <p style=\"margin-top: 12px; font-size: 12px; color: var(--text-soft); font-style: italic;\" data-lang=\"messages.autoStatusCheckingNote\">\n              Automatically checks all ping enabled nodes at the specified interval. I recommend 30 to 60 seconds for local networks.\n            </p>\n\t\t\t</div>\n        </details>\n        <details class=\"style-section\">\n          <summary data-lang=\"ui.labels.language\">Language</summary>\n          <div class=\"style-content\">\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.currentLanguage\">Current:</label>\n              <span id=\"current-lang-name\" style=\"color:var(--accent);font-weight:600;\">English</span>\n            </div>\n            <div class=\"style-row\" style=\"flex-direction:column;align-items:stretch;gap:8px;\">\n\t\t    <div class=\"style-row\">\n\t\t\t\t<button onclick=\"openLanguageEditor()\" style=\"width:100%;padding:6px 12px;background:var(--accent);color:var(--bg);border:none;border-radius:6px;cursor:pointer;font-size:13px;font-weight:600;\" data-lang=\"language.editStrings\">Edit Strings</button>\n            </div>\n            <div class=\"style-row\">\n           <button onclick=\"exportLanguageFile()\" style=\"width:100%;padding:6px 12px;background:var(--panel-alt);border:1px solid var(--edge-main);border-radius:6px;color:var(--text-main);cursor:pointer;font-size:13px;\" data-lang=\"language.exportLang\">Export Language File</button>\n            </div>\n\t\t\t    <div class=\"style-row\">\n\t\t\t\t\t<button onclick=\"document.getElementById('import-lang-file').click()\" style=\"width:100%;padding:6px 12px;background:var(--panel-alt);border:1px solid var(--edge-main);border-radius:6px;color:var(--text-main);cursor:pointer;font-size:13px;\" data-lang=\"language.importLang\">Import Language File</button>\n\t\t\t\t\t<input type=\"file\" id=\"import-lang-file\" accept=\".json\" style=\"display:none\" onchange=\"if(this.files[0]) importLanguageFile(this.files[0])\">\n\t\t\t\t<input type=\"file\" id=\"import-lang-file\" accept=\".json\" style=\"display:none\">\n            </div>\n            <div class=\"style-row\">\n        <button onclick=\"resetToDefaultLanguage()\" style=\"width:100%;padding:6px 12px;background:var(--danger);color:var(--text-main);border:none;border-radius:6px;cursor:pointer;font-size:13px;\" data-lang=\"language.resetLang\">Reset to English</button>\n            </div>\t\t          \n            </div>\n          </div>\n        </details>\t\t\t\n        <details class=\"style-section\" open=\"\">\n          <summary data-lang=\"ui.sections.dangerZone\">Danger Zone</summary>\n          <div class=\"style-content\">\n            <p style=\"margin-bottom:12px;font-size:13px;color:var(--text-soft);\" data-lang=\"messages.dangerZoneNote\">Permanently delete everything on the canvas.</p>\n            <button id=\"clear-all-btn\" data-lang=\"tooltips.clearAllNodes\" data-lang-attr=\"title\" title=\"Clear all nodes\" style=\"padding:6px 12px;background:var(--danger);color:var(--text-main);border:none;border-radius:6px;cursor:pointer;font-size:14px;font-weight:600;\" data-lang-text=\"ui.buttons.clearAll\">Clear All</button>\n            <input type=\"file\" id=\"import-data-file\" accept=\".json\" style=\"display:none\">\n          </div>\n        </details>\n        <div style=\"text-align:center;padding:10px 0 2px;font-size:11px;color:var(--text-soft);opacity:0.6;\">The One File: Networkening v<span id=\"version-display\"></span></div>\n        <div class=\"modal-buttons\">\n          <button class=\"btn-cancel\" id=\"settings-close\" data-lang=\"ui.buttons.close\">Close</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"add-node-modal\">\n      <div class=\"modal-content\">\n        <h3 data-lang=\"ui.modals.addNode\">Add New Node</h3>\n        <div style=\"background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; padding: 15px; margin-bottom: 15px;\">\n          <div style=\"font-weight: 600; color: var(--text-main); margin-bottom: 12px; font-size: 14px;\" data-lang=\"ui.sections.basicInfo\">Basic Information</div>\n          <label style=\"display: block;margin-bottom: 4px;color: var(--text-soft);font-size: 13px;\" data-lang=\"ui.labels.name\">Name</label>\n          <input type=\"text\" id=\"new-node-name\" placeholder=\"e.g. web server, jellyfin\">\n          <label id=\"new-node-ip-label\" style=\"display: block;margin-bottom: 4px;margin-top: 12px;color: var(--text-soft);font-size: 13px;\" data-lang=\"ui.labels.ip\">IP / Subtitle</label>\n          <input type=\"text\" id=\"new-node-ip\" placeholder=\"e.g. 192.168.1.100\" data-lang=\"ui.placeholders.nodeIp\" data-lang-attr=\"placeholder\">\n        </div>\n        <div style=\"background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; padding: 15px; margin-bottom: 15px;\">\n          <div style=\"font-weight: 600; color: var(--text-main); margin-bottom: 12px; font-size: 14px;\" data-lang=\"ui.sections.tags\">Tags</div>\n          <label id=\"new-node-tags-label\" style=\"display: block;margin-bottom: 4px;color: var(--text-soft);font-size: 13px;\" data-lang=\"ui.labels.tagsComma\">Tags (comma separated)</label>\n          <input type=\"text\" id=\"new-node-tags\" placeholder=\"e.g. Docker, nginx, WG: vpn\" data-lang=\"ui.placeholders.tags\" data-lang-attr=\"placeholder\">\n          <button class=\"pick-icon-btn\" id=\"pick-new-node-tag-icon-btn\" style=\"margin-top: 10px;\" data-lang=\"ui.labels.addIconTag\">Add Web Icon Tag</button>\n          <div id=\"new-node-icon-tags\" style=\"margin-top: 10px; display: none;\">\n            <label style=\"display: block; margin-bottom: 8px; color: var(--text-soft); font-size: 13px;\" data-lang=\"ui.labels.iconTags\">Icon Tags:</label>\n            <div id=\"new-node-icon-tags-list\" style=\"display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 10px;\"></div>\n          </div>\n        </div>\n        <div style=\"background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; padding: 15px; margin-bottom: 15px;\">\n          <div id=\"new-node-appearance-label\" style=\"font-weight: 600; color: var(--text-main); margin-bottom: 12px; font-size: 14px;\" data-lang=\"ui.sections.nodeAppearance\">Node Appearance</div>\n          <label style=\"display: block;margin-bottom: 4px;color: var(--text-soft);font-size: 13px;\" data-lang=\"ui.labels.category\">Category</label>\n          <select id=\"new-node-category\">\n            <option value=\"basic\" data-lang=\"shapes.categories.basic\">Basic Shapes</option>\n            <option value=\"computers\" data-lang=\"shapes.categories.computers\">Computers & Devices</option>\n            <option value=\"network\" data-lang=\"shapes.categories.network\">Network Equipment</option>\n            <option value=\"cloud\" data-lang=\"shapes.categories.cloud\">Cloud & Services</option>\n            <option value=\"security\" data-lang=\"shapes.categories.security\">Security & Monitoring</option>\n            <option value=\"smarthome\" data-lang=\"shapes.categories.smarthome\">Smart Home</option>\n            <option value=\"sports\" data-lang=\"shapes.categories.sports\">Sports</option>\n            <option value=\"rack\" data-lang=\"shapes.categories.rack\">Rack Equipment</option>\n          </select>\n          <label style=\"display: block;margin-bottom: 4px;margin-top: 12px;color: var(--text-soft);font-size: 13px;\" data-lang=\"ui.labels.shape\">Shape</label>\n          <select id=\"new-node-shape\"></select>\n          <div id=\"new-node-shape-preview\" style=\"display:flex;align-items:center;justify-content:center;width:48px;height:48px;border:1px solid var(--edge-main);border-radius:6px;background:var(--panel);margin:8px 0\"></div>\n          <button class=\"pick-icon-btn\" id=\"pick-node-icon-btn\" style=\"margin-top: 10px;\" data-lang=\"ui.labels.searchWebIcons\"> Or search web icons</button>\n          <div id=\"selected-node-icon\" style=\"margin-top: 10px; display: none;\">\n            <label style=\"display: block; margin-bottom: 8px; color: var(--text-soft); font-size: 13px;\" data-lang=\"ui.labels.selectedIcon\">Selected Icon:</label>\n            <div class=\"icon-badge\" id=\"selected-node-icon-preview\">\n              <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" width=\"24\" height=\"24\">\n                <image href=\"https://cdn.jsdelivr.net/gh/selfhst/icons@master/png/docker.png\" width=\"24\" height=\"24\"></image>\n              </svg>\n              <span>docker</span>\n            </div>\n          </div>\n        </div>\n        <div id=\"new-node-ping-section\" style=\"background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; padding: 15px; margin-bottom: 15px;\">\n          <div style=\"font-weight: 600; color: var(--text-main); margin-bottom: 12px; font-size: 14px;\" data-lang=\"ui.sections.pingStatus\">Ping / Status Monitoring</div>\n          <div style=\"display: flex; align-items: center; gap: 8px; margin-bottom: 12px;\">\n          <label class=\"toggle-switch\"><input type=\"checkbox\" id=\"new-node-pingable\"><span class=\"toggle-slider\"></span></label>\n          <span style=\"color: var(--text-soft); font-size: 13px;\" data-lang=\"ui.labels.enablePing\">Enable ping/status check for this node</span>\n          </div>\n          <div id=\"new-node-ping-options\" style=\"display: block; padding-left: 24px; border-left: 2px solid var(--edge-main);\">\n            <label style=\"display: block; margin-bottom: 4px; color: var(--text-soft); font-size: 13px;\" data-lang=\"ui.labels.protocol\">Protocol</label>\n            <select id=\"new-node-ping-protocol\" style=\"margin-bottom: 12px;\">\n              <option value=\"http\" data-lang=\"ping.httpPort80\">HTTP (port 80) uses IP field</option>\n              <option value=\"https\" data-lang=\"ping.httpsPort443\">HTTPS (port 443) uses IP field</option>\n              <option value=\"custom\" data-lang=\"ping.customUrl\">Custom URL</option>\n            </select>\n            <div id=\"new-node-custom-url-container\" style=\"display: block;\">\n              <label style=\"display: block; margin-bottom: 4px; color: var(--text-soft); font-size: 13px;\" data-lang=\"ui.labels.customUrl\">Custom URL</label>\n              <input type=\"text\" id=\"new-node-custom-url\" placeholder=\"e.g. http://192.168.1.1:8080\" data-lang=\"ui.placeholders.customUrl\" data-lang-attr=\"placeholder\">\n            </div>\n            <label style=\"display: block; margin-bottom: 4px; margin-top: 8px; color: var(--text-soft); font-size: 13px;\" data-lang=\"ui.labels.timeoutMs\">\n            Timeout (ms)\n            </label>\n            <input type=\"number\" id=\"new-node-ping-timeout\" value=\"3000\" min=\"1000\" max=\"10000\" step=\"500\">\n          </div>\n        </div>\n        <details class=\"style-section\" id=\"new-node-more-options\">\n          <summary>More Options</summary>\n          <div style=\"background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; padding: 15px; margin-top: 10px;\">\n            <div style=\"font-weight: 600; color: var(--text-main); margin-bottom: 12px; font-size: 14px;\">Appearance</div>\n            <div class=\"style-content\">\n              <label>Fill Color:</label>\n              <input type=\"color\" id=\"new-node-fill-color\" value=\"#1e293b\">\n              <label>Border Color:</label>\n              <input type=\"color\" id=\"new-node-border-color\" value=\"#475569\">\n            </div>\n            <div class=\"size-controls\" style=\"margin-top: 10px;\">\n              <label>Size:</label>\n              <input type=\"range\" id=\"new-node-size\" min=\"20\" max=\"200\" value=\"55\">\n              <span id=\"new-node-size-value\">55</span>\n            </div>\n          </div>\n          <div style=\"background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; padding: 15px; margin-top: 10px;\">\n            <div style=\"font-weight: 600; color: var(--text-main); margin-bottom: 12px; font-size: 14px;\">Configuration</div>\n            <label style=\"display: block;margin-bottom: 4px;color: var(--text-soft);font-size: 13px;\">Layer</label>\n            <select id=\"new-node-layer\" style=\"margin-bottom: 12px;\">\n              <option value=\"layer1\">Physical</option>\n              <option value=\"layer2\">Logical</option>\n              <option value=\"layer3\">Security</option>\n              <option value=\"layer4\">Application</option>\n            </select>\n            <label style=\"display: block;margin-bottom: 4px;color: var(--text-soft);font-size: 13px;\">Connect to</label>\n            <select id=\"new-node-connect-to\" style=\"margin-bottom: 8px;\">\n              <option value=\"\">None</option>\n            </select>\n            <div style=\"display: flex; gap: 8px; align-items: center; margin-bottom: 12px;\">\n              <input type=\"color\" id=\"new-node-line-color\" value=\"#475569\" style=\"width: 36px; height: 30px;\">\n              <select id=\"new-node-line-direction\" style=\"flex: 1;padding: 6px;background: var(--panel);border: 1px solid var(--edge-main);border-radius: 4px;color: var(--text-main);font-size: 13px;\">\n                <option value=\"none\">No arrows</option>\n                <option value=\"forward\">&rarr; Forward</option>\n                <option value=\"backward\">&larr; Backward</option>\n                <option value=\"both\">&harr; Both</option>\n              </select>\n              <select id=\"new-node-line-routing\" style=\"flex: 1;padding: 6px;background: var(--panel);border: 1px solid var(--edge-main);border-radius: 4px;color: var(--text-main);font-size: 13px;\">\n                <option value=\"orthogonal\">Orthogonal</option>\n                <option value=\"curved\">Curved</option>\n                <option value=\"straight\">Straight</option>\n              </select>\n            </div>\n            <label style=\"display: block;margin-bottom: 4px;color: var(--text-soft);font-size: 13px;\">Notes</label>\n            <textarea id=\"new-node-notes\" rows=\"3\" placeholder=\"Add notes...\" style=\"width: 100%;padding: 10px;background: var(--panel);border: 1px solid var(--edge-main);border-radius: 4px;color: var(--text-main);font-size: 14px;resize: vertical;font-family: inherit;\"></textarea>\n          </div>\n        </details>\n        <div class=\"modal-buttons\">\n          <button class=\"btn-cancel\" id=\"add-node-cancel\" data-lang=\"ui.buttons.cancel\">Cancel</button>\n          <button class=\"btn-save\" id=\"add-node-save\" data-lang=\"ui.buttons.addNode\">Add Node</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"add-rack-modal\">\n      <div class=\"modal-content\">\n        <h3 data-lang=\"ui.modals.addRack\">Add New Rack</h3>\n        <div style=\"background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; padding: 15px; margin-bottom: 15px;\">\n          <div style=\"font-weight: 600; color: var(--text-main); margin-bottom: 12px; font-size: 14px;\" data-lang=\"ui.sections.basicInfo\">Basic Information</div>\n          <label id=\"new-rack-name-label\" style=\"display: block;margin-bottom: 4px;color: var(--text-soft);font-size: 13px;\" data-lang=\"ui.labels.name\">Rack Name</label>\n          <input type=\"text\" id=\"new-rack-name\" placeholder=\"e.g. Rack-A, DC1-R01, Production-01\" data-lang=\"ui.placeholders.rackName\" data-lang-attr=\"placeholder\">\n          <label id=\"new-rack-ip-label\" style=\"display: block;margin-bottom: 4px;margin-top: 12px;color: var(--text-soft);font-size: 13px;\" data-lang=\"ui.labels.networkRange\">IP / Network Range (optional)</label>\n          <input type=\"text\" id=\"new-rack-ip\" placeholder=\"e.g. 192.168.1.0/24\" data-lang=\"ui.placeholders.rackIp\" data-lang-attr=\"placeholder\">\n        </div>\n        <div style=\"background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; padding: 15px; margin-bottom: 15px;\">\n          <div style=\"font-weight: 600; color: var(--text-main); margin-bottom: 12px; font-size: 14px;\" data-lang=\"ui.sections.tags\">Tags</div>\n          <label id=\"new-rack-tags-label\" style=\"display: block;margin-bottom: 4px;color: var(--text-soft);font-size: 13px;\" data-lang=\"ui.labels.tagsComma\">Tags (comma separated)</label>\n          <input type=\"text\" id=\"new-rack-tags\" placeholder=\"e.g. Production, Data Center 1, Row A\" data-lang=\"ui.placeholders.rackTags\" data-lang-attr=\"placeholder\">\n        </div>\n        <div style=\"background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; padding: 15px; margin-bottom: 15px;\">\n          <div id=\"new-rack-appearance-label\" style=\"font-weight: 600; color: var(--text-main); margin-bottom: 12px; font-size: 14px;\" data-lang=\"ui.sections.rackAppearance\">Rack Appearance</div>\n          <label style=\"display: block;margin-bottom: 4px;color: var(--text-soft);font-size: 13px;\" data-lang=\"ui.labels.category\">Category</label>\n          <select id=\"new-rack-category\">\n            <option value=\"basic\" data-lang=\"shapes.categories.basic\">Basic Shapes</option>\n            <option value=\"computers\" data-lang=\"shapes.categories.computers\">Computers & Devices</option>\n            <option value=\"network\" data-lang=\"shapes.categories.network\">Network Equipment</option>\n            <option value=\"cloud\" data-lang=\"shapes.categories.cloud\">Cloud & Services</option>\n            <option value=\"security\" data-lang=\"shapes.categories.security\">Security & Monitoring</option>\n            <option value=\"smarthome\" data-lang=\"shapes.categories.smarthome\">Smart Home</option>\n            <option value=\"sports\" data-lang=\"shapes.categories.sports\">Sports</option>\n            <option value=\"rack\" data-lang=\"shapes.categories.rack\">Rack Equipment</option>\n          </select>\n          <label style=\"display: block;margin-bottom: 4px;margin-top: 12px;color: var(--text-soft);font-size: 13px;\" data-lang=\"ui.labels.shape\">Shape</label>\n          <select id=\"new-rack-shape\"></select>\n          <div id=\"new-rack-shape-preview\" style=\"display:flex;align-items:center;justify-content:center;width:48px;height:48px;border:1px solid var(--edge-main);border-radius:6px;background:var(--panel);margin:8px 0\"></div>\n          <button class=\"pick-icon-btn\" id=\"pick-rack-icon-btn\" style=\"margin-top: 10px;\" data-lang=\"ui.labels.searchWebIcons\">Or Search Web Icons</button>\n          <div id=\"selected-rack-icon\" style=\"margin-top: 10px; display: none;\">\n            <label style=\"display: block; margin-bottom: 8px; color: var(--text-soft); font-size: 13px;\" data-lang=\"ui.labels.selectedIcon\">Selected Icon:</label>\n            <div class=\"icon-badge\" id=\"selected-rack-icon-preview\">\n              <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" width=\"24\" height=\"24\">\n                <image href=\"\" width=\"24\" height=\"24\"></image>\n              </svg>\n              <span></span>\n            </div>\n          </div>\n        </div>\n        <div style=\"background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; padding: 15px; margin-bottom: 15px;\">\n          <div style=\"font-weight: 600; color: var(--text-main); margin-bottom: 12px; font-size: 14px;\" data-lang=\"ui.sections.rackConfiguration\">Rack Configuration</div>\n          <label style=\"display: block;margin-bottom: 4px;color: var(--text-soft);font-size: 13px;\" data-lang=\"ui.labels.rackCapacity\">Rack Capacity</label>\n          <select id=\"new-rack-capacity\">\n            <option value=\"42\" selected=\"\" data-lang=\"ui.labels.u42Standard\">42U (Standard Full Rack)</option>\n            <option value=\"48\" data-lang=\"ui.labels.u48Large\">48U (Large Rack)</option>\n            <option value=\"24\" data-lang=\"ui.labels.u24Half\">24U (Half Rack)</option>\n            <option value=\"12\" data-lang=\"ui.labels.u12Small\">12U (Small/Wall Mount)</option>\n            <option value=\"6\" data-lang=\"ui.labels.u6Mini\">6U (Mini Rack)</option>\n          </select>\n        </div>\n        <details class=\"style-section\" id=\"new-rack-more-options\">\n          <summary>More Options</summary>\n          <div style=\"background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; padding: 15px; margin-top: 10px;\">\n            <div style=\"font-weight: 600; color: var(--text-main); margin-bottom: 12px; font-size: 14px;\">Appearance</div>\n            <div class=\"style-content\">\n              <label>Fill Color:</label>\n              <input type=\"color\" id=\"new-rack-fill-color\" value=\"#1e293b\">\n              <label>Border Color:</label>\n              <input type=\"color\" id=\"new-rack-border-color\" value=\"#475569\">\n            </div>\n            <div class=\"size-controls\" style=\"margin-top: 10px;\">\n              <label>Size:</label>\n              <input type=\"range\" id=\"new-rack-size\" min=\"20\" max=\"200\" value=\"55\">\n              <span id=\"new-rack-size-value\">55</span>\n            </div>\n          </div>\n          <div style=\"background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; padding: 15px; margin-top: 10px;\">\n            <div style=\"font-weight: 600; color: var(--text-main); margin-bottom: 12px; font-size: 14px;\">Configuration</div>\n            <label style=\"display: block;margin-bottom: 4px;color: var(--text-soft);font-size: 13px;\">Layer</label>\n            <select id=\"new-rack-layer\" style=\"margin-bottom: 12px;\">\n              <option value=\"layer1\">Physical</option>\n              <option value=\"layer2\">Logical</option>\n              <option value=\"layer3\">Security</option>\n              <option value=\"layer4\">Application</option>\n            </select>\n            <label style=\"display: block;margin-bottom: 4px;color: var(--text-soft);font-size: 13px;\">Assign Existing Nodes</label>\n            <div id=\"new-rack-assign-nodes\" style=\"max-height: 200px; overflow-y: auto; margin-bottom: 12px;\"></div>\n            <label style=\"display: block;margin-bottom: 4px;color: var(--text-soft);font-size: 13px;\">Notes</label>\n            <textarea id=\"new-rack-notes\" rows=\"3\" placeholder=\"Add notes...\" style=\"width: 100%;padding: 10px;background: var(--panel);border: 1px solid var(--edge-main);border-radius: 4px;color: var(--text-main);font-size: 14px;resize: vertical;font-family: inherit;\"></textarea>\n          </div>\n        </details>\n        <div class=\"modal-buttons\">\n          <button class=\"btn-cancel\" id=\"add-rack-cancel\" data-lang=\"ui.buttons.cancel\">Cancel</button>\n          <button class=\"btn-save\" id=\"add-rack-save\" data-lang=\"ui.buttons.addRack\">Add Rack</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal confirm-modal\" id=\"clear-all-modal\">\n      <div class=\"modal-content\">\n        <h3 data-lang=\"ui.modals.clearAllNodes\">Clear All Nodes</h3>\n        <p data-lang=\"messages.clearAllWarning\"> This will remove ALL nodes and connections. This cannot be undone until you reload without saving. Are you sure? </p>\n        <div class=\"modal-buttons\">\n          <button class=\"btn-cancel\" id=\"clear-all-cancel\" data-lang=\"ui.buttons.cancel\">Cancel</button>\n          <button class=\"btn-delete\" id=\"clear-all-confirm\" data-lang=\"messages.clearEverything\">Clear Everything</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"layer-modal\">\n      <div class=\"modal-content\" style=\"max-width: 400px;\">\n        <h3 data-lang=\"ui.modals.layerVisibility\">Layer Visibility</h3>\n        <p style=\"font-size: 13px; color: var(--text-soft); margin-bottom: 15px;\" data-lang=\"tooltips.toggleLayersDesc\">Toggle which layers are visible on the canvas</p>\n        <div style=\"display: flex; flex-direction: column; gap: 10px;\">\n          <div style=\"display: flex; align-items: center; gap: 8px; padding: 8px; background: var(--panel-alt); border-radius: 6px;\">\n          <label class=\"toggle-switch\"><input type=\"checkbox\" id=\"layer-1\" checked><span class=\"toggle-slider\"></span></label>\n          <span id=\"layer-1-label\" style=\"flex: 1; font-weight: 500;\">Layer 1</span>\n          </div>\n          <div style=\"display: flex; align-items: center; gap: 8px; padding: 8px; background: var(--panel-alt); border-radius: 6px;\">\n          <label class=\"toggle-switch\"><input type=\"checkbox\" id=\"layer-2\" checked><span class=\"toggle-slider\"></span></label>\n          <span id=\"layer-2-label\" style=\"flex: 1; font-weight: 500;\">Layer 2</span>\n          </div>\n          <div style=\"display: flex; align-items: center; gap: 8px; padding: 8px; background: var(--panel-alt); border-radius: 6px;\">\n          <label class=\"toggle-switch\"><input type=\"checkbox\" id=\"layer-3\" checked><span class=\"toggle-slider\"></span></label>\n          <span id=\"layer-3-label\" style=\"flex: 1; font-weight: 500;\">Layer 3</span>\n          </div>\n          <div style=\"display: flex; align-items: center; gap: 8px; padding: 8px; background: var(--panel-alt); border-radius: 6px;\">\n          <label class=\"toggle-switch\"><input type=\"checkbox\" id=\"layer-4\" checked><span class=\"toggle-slider\"></span></label>\n          <span id=\"layer-4-label\" style=\"flex: 1; font-weight: 500;\">Layer 4</span>\n          </div>\n        </div>\n        <div style=\"margin-top: 15px; display: flex; gap: 8px;\">\n          <button class=\"btn-cancel\" style=\"flex: 1;\" onclick=\"document.querySelectorAll('#layer-modal input[type=checkbox]').forEach(cb =&gt; cb.checked = false); applyLayerFilter();\" data-lang=\"ui.buttons.hideAll\">Hide All</button>\n          <button class=\"btn-save\" style=\"flex: 1;\" onclick=\"document.querySelectorAll('#layer-modal input[type=checkbox]').forEach(cb =&gt; cb.checked = true); applyLayerFilter();\" data-lang=\"ui.buttons.showAll\">Show All</button>\n        </div>\n        <div class=\"modal-buttons\" style=\"margin-top: 10px;\">\n          <button class=\"btn-cancel\" id=\"layer-modal-close\" data-lang=\"ui.buttons.close\">Close</button>\n        </div>\n      </div>\n    </div>\n    <div id=\"context-menu\" style=\"display: none !important;\">\n    </div>\n    <div class=\"modal\" id=\"recordings-modal\">\n      <div class=\"modal-content\" style=\"max-width: 600px;\">\n        <h3 data-lang=\"ui.modals.recordings\">Recordings</h3>\n        <p style=\"font-size: 13px; color: var(--text-soft); margin-bottom: 15px;\" data-lang=\"messages.manageRecordings\">Manage recorded movement sequences</p>\n        <div id=\"recordings-list\" style=\"display: flex; flex-direction: column; gap: 8px; max-height: 250px; overflow-y: auto; margin-bottom: 15px;\"><div style=\"text-align: center; padding: 40px; color: var(--text-soft);\" data-lang=\"messages.noRecordingsYet\">No recordings yet. Press Record to start.</div></div>\n        <div style=\"display: flex; gap: 8px; flex-wrap: wrap; justify-content: flex-start; margin-bottom: 15px;\">\n          <button class=\"btn-save\" onclick=\"exportRecordingVideo()\" style=\"flex:0 0 auto;\" data-lang=\"recordings.exportVideo\">Export Video</button>\n          <button class=\"btn-cancel\" onclick=\"deleteSelectedRecording()\" style=\"background:var(--danger);color:white;flex:0 0 auto;\" data-lang=\"ui.buttons.del\">Delete</button>\n        </div>\n        <details style=\"background: var(--panel-alt); border-radius: 6px; padding: 10px; margin-bottom: 15px;\">\n          <summary style=\"color: var(--accent); font-weight: 600; cursor: pointer;\" data-lang=\"trails.title\">Motion Trails</summary>\n          <div style=\"display: flex; flex-direction: column; gap: 10px; margin-top: 12px;\">\n            <div style=\"display: flex; align-items: center; justify-content: space-between;\">\n              <label style=\"color: var(--text-main); font-size: 14px;\" data-lang=\"trails.enableTrails\">Enable Trails:</label>\n              <label class=\"toggle-switch\"><input type=\"checkbox\" id=\"trail-enabled-toggle\" checked><span class=\"toggle-slider\"></span></label>\n            </div>\n            <div style=\"display: flex; align-items: center; justify-content: space-between;\">\n              <label style=\"color: var(--text-main); font-size: 14px;\" data-lang=\"trails.trailLength\">Trail Length:</label>\n              <div style=\"display: flex; align-items: center; gap: 8px;\">\n                <input type=\"range\" id=\"trail-length-slider\" min=\"2\" max=\"20\" value=\"8\" style=\"width: 100px;\">\n                <span id=\"trail-length-value\" style=\"min-width: 30px; color: var(--text-soft);\">8</span>\n              </div>\n            </div>\n            <div style=\"display: flex; align-items: center; justify-content: space-between;\">\n              <label style=\"color: var(--text-main); font-size: 14px;\" data-lang=\"trails.trailStyle\">Trail Style:</label>\n              <select id=\"trail-style-select\" style=\"padding: 4px 8px; background: var(--panel); color: var(--text-main); border: 1px solid var(--edge-main); border-radius: 4px;\">\n                <option value=\"solid\" data-lang=\"trails.styleSolid\">Solid Fade</option>\n                <option value=\"dashed\" data-lang=\"trails.styleDashed\">Dashed</option>\n                <option value=\"dots\" data-lang=\"trails.styleDots\">Dots</option>\n              </select>\n            </div>\n            <div style=\"display: flex; align-items: center; justify-content: space-between;\">\n              <label style=\"color: var(--text-main); font-size: 14px;\" data-lang=\"trails.showArrow\">Show Direction Arrow:</label>\n              <label class=\"toggle-switch\"><input type=\"checkbox\" id=\"trail-arrow-toggle\" checked><span class=\"toggle-slider\"></span></label>\n            </div>\n            <p style=\"font-size: 12px; color: var(--text-soft); margin: 0;\" data-lang=\"trails.perNodeHint\">Tip: Enable/disable trails per node in the node panel</p>\n          </div>\n        </details>\n        <div style=\"display: flex; gap: 8px; justify-content: flex-end;\">\n          <button class=\"btn-cancel\" id=\"recordings-modal-close\" data-lang=\"ui.buttons.close\">Close</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"welcome-modal\">\n      <div class=\"modal-content\" style=\"max-width: 700px; max-height: 90vh; overflow-y: auto;\">\n        <h2 style=\"margin-bottom: 8px; text-align: center;\" data-lang=\"welcome.title\">Welcome to The One File</h2>\n        <p style=\"font-size: 14px; color: var(--text-soft); margin-bottom: 16px; text-align: center;\" data-lang=\"welcome.subtitle\">What are you mapping today?</p>\n        <div id=\"welcome-mode-buttons\" style=\"display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-bottom: 20px;\">\n          <button class=\"welcome-mode-btn\" data-mode=\"network\" style=\"padding: 12px 16px; background: var(--panel-alt); border: 2px solid var(--accent); border-radius: 8px; color: var(--text-main); font-size: 14px; cursor: pointer; text-align: left; transition: all 0.2s;\">\n            <strong data-lang=\"welcome.modes.network\">Network Topology</strong>\n            <span style=\"display: block; font-size: 11px; color: var(--text-soft); margin-top: 2px;\" data-lang=\"welcome.modes.networkDesc\">Servers, routers, switches</span>\n          </button>\n          <button class=\"welcome-mode-btn\" data-mode=\"mindmap\" style=\"padding: 12px 16px; background: var(--panel-alt); border: 2px solid var(--edge-main); border-radius: 8px; color: var(--text-main); font-size: 14px; cursor: pointer; text-align: left; transition: all 0.2s;\">\n            <strong data-lang=\"welcome.modes.mindmap\">Mind Map</strong>\n            <span style=\"display: block; font-size: 11px; color: var(--text-soft); margin-top: 2px;\" data-lang=\"welcome.modes.mindmapDesc\">Ideas, concepts, brainstorming</span>\n          </button>\n          <button class=\"welcome-mode-btn\" data-mode=\"sports\" style=\"padding: 12px 16px; background: var(--panel-alt); border: 2px solid var(--edge-main); border-radius: 8px; color: var(--text-main); font-size: 14px; cursor: pointer; text-align: left; transition: all 0.2s;\">\n            <strong data-lang=\"welcome.modes.sports\">Sports Playbook</strong>\n            <span style=\"display: block; font-size: 11px; color: var(--text-soft); margin-top: 2px;\" data-lang=\"welcome.modes.sportsDesc\">Plays, formations, positions</span>\n          </button>\n          <button class=\"welcome-mode-btn\" data-mode=\"smarthome\" style=\"padding: 12px 16px; background: var(--panel-alt); border: 2px solid var(--edge-main); border-radius: 8px; color: var(--text-main); font-size: 14px; cursor: pointer; text-align: left; transition: all 0.2s;\">\n            <strong data-lang=\"welcome.modes.smarthome\">Smart Home</strong>\n            <span style=\"display: block; font-size: 11px; color: var(--text-soft); margin-top: 2px;\" data-lang=\"welcome.modes.smarthomeDesc\">IoT devices, automation</span>\n          </button>\n          <button class=\"welcome-mode-btn\" data-mode=\"floorplan\" style=\"padding: 12px 16px; background: var(--panel-alt); border: 2px solid var(--edge-main); border-radius: 8px; color: var(--text-main); font-size: 14px; cursor: pointer; text-align: left; transition: all 0.2s; grid-column: span 2;\">\n            <strong data-lang=\"welcome.modes.floorplan\">Floor Plan / Blueprint</strong>\n            <span style=\"display: block; font-size: 11px; color: var(--text-soft); margin-top: 2px;\" data-lang=\"welcome.modes.floorplanDesc\">Rooms, layouts, physical spaces</span>\n          </button>\n        </div>\n        <div id=\"welcome-canvas-row\" style=\"display: flex; align-items: center; gap: 10px; margin-bottom: 8px;\">\n          <label style=\"font-size: 13px; color: var(--text-main); min-width: 80px;\" data-lang=\"welcome.canvasStyle\">Canvas Style</label>\n          <select id=\"welcome-canvas-select\" style=\"flex: 1; padding: 6px 10px; background: var(--input-bg); color: var(--input-text); border: 1px solid var(--input-border); border-radius: 4px;\">\n            <option value=\"grid\" data-lang=\"welcome.canvas.grid\">Standard Grid</option>\n            <option value=\"dots\" data-lang=\"welcome.canvas.dots\">Dot Grid</option>\n            <option value=\"blueprint\" data-lang=\"welcome.canvas.blueprint\">Blueprint Grid</option>\n            <option value=\"none\" data-lang=\"welcome.canvas.none\">No Grid</option>\n          </select>\n        </div>\n        <div id=\"welcome-canvas-preview\" style=\"height: 60px; margin-bottom: 12px; border: 1px solid var(--edge-main); border-radius: 4px; background: var(--bg); overflow: hidden;\"></div>\n        <div style=\"display: flex; align-items: center; gap: 10px; margin-bottom: 12px;\">\n          <label style=\"font-size: 13px; color: var(--text-main); min-width: 80px;\" data-lang=\"welcome.theme\">Theme</label>\n          <select id=\"welcome-theme-select\" style=\"flex: 1; padding: 6px 10px; background: var(--input-bg); color: var(--input-text); border: 1px solid var(--input-border); border-radius: 4px;\">\n            <option value=\"defaulted\" selected data-lang=\"themes.default\">Default</option>\n            <option value=\"\" data-lang=\"ui.options.customColors\">Custom Colors</option>\n            <optgroup label=\"Corporate\" data-lang-label=\"themeGroups.corporate\">\n              <option value=\"slate\" data-lang=\"themes.slate\">Slate</option>\n              <option value=\"graphite\" data-lang=\"themes.graphite\">Graphite</option>\n              <option value=\"frost\" data-lang=\"themes.frost\">Frost (Light)</option>\n            </optgroup>\n            <optgroup label=\"Homelab\" data-lang-label=\"themeGroups.homelab\">\n              <option value=\"synthwave\" data-lang=\"themes.synthwave\">Synthwave</option>\n              <option value=\"terminal\" data-lang=\"themes.terminal\">Terminal</option>\n            </optgroup>\n            <optgroup label=\"Dev\" data-lang-label=\"themeGroups.dev\">\n              <option value=\"dracula\" data-lang=\"themes.dracula\">Dracula</option>\n              <option value=\"cobalt\" data-lang=\"themes.cobalt\">Cobalt</option>\n              <option value=\"solarized\" data-lang=\"themes.solarized\">Solarized</option>\n            </optgroup>\n            <optgroup label=\"Mind Map\" data-lang-label=\"themeGroups.mindMap\">\n              <option value=\"brainwave\" data-lang=\"themes.brainwave\">Brainwave</option>\n                            <option value=\"neonMint\" data-lang=\"themes.neonMint\">Neon Mint</option>\n              <option value=\"velvetDusk\" data-lang=\"themes.velvetDusk\">Velvet Dusk</option>\n              <option value=\"sunburst\" data-lang=\"themes.sunburst\">Sunburst</option>\n            </optgroup>\n          </select>\n        </div>\n        <details style=\"margin-bottom: 12px; border: 1px solid var(--edge-main); border-radius: 6px; padding: 8px;\">\n          <summary style=\"cursor: pointer; font-size: 13px; color: var(--text-main); font-weight: 600;\" data-lang=\"welcome.colorCustomization\">Color Customization</summary>\n          <div style=\"display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-top: 10px;\">\n            <div style=\"display: flex; align-items: center; gap: 6px;\">\n              <input type=\"color\" id=\"welcome-bg-color\" value=\"#0b0e13\" style=\"width: 32px; height: 24px; border: 1px solid var(--input-border); border-radius: 3px; cursor: pointer;\">\n              <label style=\"font-size: 11px; color: var(--text-soft);\" data-lang=\"welcome.colors.mainBg\">Top Bar Background</label>\n            </div>\n            <div style=\"display: flex; align-items: center; gap: 6px;\">\n              <input type=\"color\" id=\"welcome-accent-color\" value=\"#4fd1c5\" style=\"width: 32px; height: 24px; border: 1px solid var(--input-border); border-radius: 3px; cursor: pointer;\">\n              <label style=\"font-size: 11px; color: var(--text-soft);\" data-lang=\"welcome.colors.accent\">Accent</label>\n            </div>\n            <div style=\"display: flex; align-items: center; gap: 6px;\">\n              <input type=\"color\" id=\"welcome-text-color\" value=\"#e2e8f0\" style=\"width: 32px; height: 24px; border: 1px solid var(--input-border); border-radius: 3px; cursor: pointer;\">\n              <label style=\"font-size: 11px; color: var(--text-soft);\" data-lang=\"welcome.colors.primaryText\">Primary Text</label>\n            </div>\n            <div style=\"display: flex; align-items: center; gap: 6px;\">\n              <input type=\"color\" id=\"welcome-text-soft-color\" value=\"#94a3b8\" style=\"width: 32px; height: 24px; border: 1px solid var(--input-border); border-radius: 3px; cursor: pointer;\">\n              <label style=\"font-size: 11px; color: var(--text-soft);\" data-lang=\"welcome.colors.secondaryText\">Secondary Text</label>\n            </div>\n            <div style=\"display: flex; align-items: center; gap: 6px;\">\n              <input type=\"color\" id=\"welcome-panel-color\" value=\"#0f1318\" style=\"width: 32px; height: 24px; border: 1px solid var(--input-border); border-radius: 3px; cursor: pointer;\">\n              <label style=\"font-size: 11px; color: var(--text-soft);\" data-lang=\"welcome.colors.sidebarPanel\">Main Background</label>\n            </div>\n            <div style=\"display: flex; align-items: center; gap: 6px;\">\n              <input type=\"color\" id=\"welcome-modal-color\" value=\"#141921\" style=\"width: 32px; height: 24px; border: 1px solid var(--input-border); border-radius: 3px; cursor: pointer;\">\n              <label style=\"font-size: 11px; color: var(--text-soft);\" data-lang=\"welcome.colors.modalWindows\">Modal Background</label>\n            </div>\n            <div style=\"display: flex; align-items: center; gap: 6px;\">\n              <input type=\"color\" id=\"welcome-danger-color\" value=\"#f56565\" style=\"width: 32px; height: 24px; border: 1px solid var(--input-border); border-radius: 3px; cursor: pointer;\">\n              <label style=\"font-size: 11px; color: var(--text-soft);\" data-lang=\"welcome.colors.dangerButtons\">Grid Color</label>\n            </div>\n            <div style=\"display: flex; align-items: center; gap: 6px;\">\n              <input type=\"color\" id=\"welcome-mobile-footer-color\" value=\"#0b0e13\" style=\"width: 32px; height: 24px; border: 1px solid var(--input-border); border-radius: 3px; cursor: pointer;\">\n              <label style=\"font-size: 11px; color: var(--text-soft);\" data-lang=\"welcome.colors.mobileFooter\">Sidebar Background</label>\n            </div>\n          </div>\n\t\t  <div style=\"text-align:center\">\n            <label style=\"font-size: 11px; color: var(--text-soft);\" data-lang=\"welcome.colors.moreStyles\">More styles available in main settings panel</label>\n          </div>\n        </details>\n        <div style=\"display: flex; gap: 10px; justify-content: center;\">\n          <button id=\"welcome-start-btn\" style=\"padding: 10px 24px; background: var(--accent); color: var(--bg); border: none; border-radius: 6px; font-size: 14px; font-weight: 600; cursor: pointer;\" data-lang=\"welcome.startMapping\">Start Mapping</button>\n          <button id=\"welcome-skip-btn\" style=\"padding: 10px 24px; background: transparent; border: 1px solid var(--edge-main); border-radius: 6px; color: var(--text-soft); font-size: 14px; cursor: pointer;\" data-lang=\"welcome.skip\">Skip</button>\n        </div>\n      </div>\n    </div>\n    <header>\n      <div class=\"title-block\">\n        <h1 id=\"page-title\" class=\"editable-page-title\">The One File: The Networkening</h1>\n        <div class=\"save-row\">\n          <button id=\"save-file-btn\" class=\"save-btn\" type=\"button\" data-lang=\"topbar.saveHtml\">Save HTML</button>\n          <div style=\"display: flex;align-items: center;gap: 4px;font-size: 12px;color: var(--text-soft);user-select: none;\">\n            <label class=\"toggle-switch\"><input type=\"checkbox\" id=\"encrypt-toggle\"><span class=\"toggle-slider\"></span></label>\n            <span title=\"Encrypt data with password\">🔒</span>\n          </div>\n          <div class=\"dropdown\" id=\"export-dropdown\">\n            <button class=\"dropdown-btn\" type=\"button\" data-lang=\"topbar.export\">Export ▾</button>\n            <div class=\"dropdown-menu\">\n              <button type=\"button\" onclick=\"screenshotCanvas()\" data-lang=\"help.formats.pngImage\">PNG Image</button>\n              <button type=\"button\" onclick=\"exportCanvasSVG()\" data-lang=\"help.formats.svgVector\">SVG Vector</button>\n              <div class=\"dropdown-divider\"></div>\n              <button type=\"button\" onclick=\"exportJSONFile()\" data-lang=\"help.formats.json\">JSON</button>\n              <button type=\"button\" onclick=\"exportMarkdown()\" data-lang=\"help.formats.markdown\">Markdown</button>\n              <button type=\"button\" onclick=\"exportCSV()\" data-lang=\"help.formats.csv\">CSV</button>\n              <div class=\"dropdown-divider\"></div>\n              <button type=\"button\" onclick=\"printTopology()\" data-lang=\"ui.buttons.printBW\">Print B&W</button>\n              <button type=\"button\" onclick=\"printTopologyColor()\" data-lang=\"ui.buttons.printColorFull\">Print Full Color</button>\n              <button type=\"button\" onclick=\"printTopologyColor('#ffffff')\" data-lang=\"ui.buttons.printColorWhite\">Print White BG</button>\n              <button type=\"button\" onclick=\"printTopologyColor('#000000')\" data-lang=\"ui.buttons.printColorBlack\">Print Black BG</button>\n\t\t\t  <div class=\"dropdown-divider\"></div>\n\t\t\t  <button type=\"button\" onclick=\"showFormatHelp()\" data-lang=\"ui.buttons.exportHelp\">Export Help</button>\n            </div>\n          </div>\n          <div class=\"dropdown\" id=\"import-dropdown\">\n            <button class=\"dropdown-btn\" type=\"button\" data-lang=\"topbar.import\">Import ▾</button>\n            <div class=\"dropdown-menu\">\n              <button type=\"button\" onclick=\"document.getElementById('import-html-file').click()\" data-lang=\"help.formats.html\">HTML</button>\n              <button type=\"button\" onclick=\"document.getElementById('import-data-file').click()\" data-lang=\"help.formats.json\">JSON</button>\n              <button type=\"button\" onclick=\"document.getElementById('import-markdown-file').click()\" data-lang=\"help.formats.markdown\">Markdown</button>\n              <button type=\"button\" onclick=\"document.getElementById('import-csv-file').click()\" data-lang=\"help.formats.csv\">CSV</button>\n              <div class=\"dropdown-divider\"></div>\n              <button type=\"button\" onclick=\"showFormatHelp()\" data-lang=\"ui.buttons.importHelp\">Import Help</button>\n            </div>\n          </div>\n          <input type=\"file\" id=\"import-html-file\" accept=\".html,.htm\" style=\"display:none\">\n          <input type=\"file\" id=\"import-json-file\" accept=\".json\" style=\"display:none\">\n          <input type=\"file\" id=\"import-markdown-file\" accept=\".md,.markdown,.txt\" style=\"display:none\">\n          <input type=\"file\" id=\"import-csv-file\" accept=\".csv,.txt\" style=\"display:none\">\n          <span id=\"save-help-btn\" class=\"help-icon\" title=\"Why save?\">?</span>\n        </div>\n      </div>\n      <div id=\"topbar-menu\">\n        <button id=\"back-to-topology-btn\" title=\"Exit rack view and return to topology\" style=\"padding: 6px 12px;background: var(--accent);color: var(--bg);border: none;border-radius: 6px;cursor: pointer;font-size: 14px;font-weight: 600;display: none;\">← Back to Topology</button>\n        <button id=\"add-node-btn\" class=\"save-btn\" title=\"Add new node\" style=\"background: var(--accent)\">+ Node</button>\n        <button id=\"add-rack-btn\" class=\"save-btn\" title=\"Add new rack\" style=\"background: var(--accent); margin-left: 8px;\">+ Rack</button>\n        <button id=\"undo-btn\" title=\"Undo (Ctrl+Z)\" style=\"padding: 6px 12px; background: var(--btn-bg); color: var(--btn-text); border: 1px solid var(--edge-main); border-radius: 6px; cursor: pointer; font-size: 14px; font-weight: 600; opacity: 0.5;\" disabled=\"\">↶</button>\n        <button id=\"redo-btn\" title=\"Redo (Ctrl+Y)\" style=\"padding: 6px 12px; background: var(--btn-bg); color: var(--btn-text); border: 1px solid var(--edge-main); border-radius: 6px; cursor: pointer; font-size: 14px; font-weight: 600; opacity: 0.5;\" disabled=\"\">↷</button>\n<input type=\"text\" id=\"search-nodes\" placeholder=\"Search nodes...\" title=\"Search by name, IP, MAC, role, or tag\" style=\"padding: 6px 12px;background: var(--input-bg);color: var(--input-text);border: 1px solid var(--input-border);border-radius: 6px;font-family: var(--input-font);font-size: var(--input-font-size);width: 180px;\" data-lang=\"ui.placeholders.searchNodes\" data-lang-attr=\"placeholder\">\n        <button id=\"check-all-ping-btn\" data-lang=\"tooltips.checkAllStatus\" data-lang-attr=\"title\" title=\"Check status of all enabled nodes\" style=\"padding: 6px 12px;background: var(--btn-bg);color: var(--btn-text);border: 1px solid var(--edge-main);border-radius: 6px;cursor: pointer;font-size: 14px;font-weight: 600\" data-lang-text=\"ui.buttons.checkPings\">Check Pings</button>\n        <button id=\"layers-btn\" data-lang=\"ui.tooltips.toggleLayers\" data-lang-attr=\"title\" title=\"Toggle layer visibility\" style=\"padding: 6px 12px;background: var(--btn-bg);color: var(--btn-text);border: 1px solid var(--edge-main);border-radius: 6px;cursor: pointer;font-size: 14px;font-weight: 600;\" data-lang-text=\"ui.buttons.layers\">Layers</button>\n        <button id=\"tabs-btn\" data-lang=\"tooltips.manageTabs\" data-lang-attr=\"title\" title=\"Manage document tabs\" style=\"padding: 6px 12px;background: var(--btn-bg);color: var(--btn-text);border: 1px solid var(--edge-main);border-radius: 6px;cursor: pointer;font-size: 14px;font-weight: 600;\" data-lang-text=\"topbar.tabs\">Tabs</button>\n        <button id=\"rollback-btn\" data-lang=\"tooltips.viewVersionHistory\" data-lang-attr=\"title\" title=\"View version history\" style=\"padding: 6px 12px;background: var(--btn-bg);color: var(--btn-text);border: 1px solid var(--edge-main);border-radius: 6px;cursor: pointer;font-size: 14px;font-weight: 600;\" data-lang-text=\"topbar.snapshots\">Snapshots</button>\n        <button id=\"audit-log-btn\" data-lang=\"ui.tooltips.viewAuditLog\" data-lang-attr=\"title\" title=\"View audit log\" style=\"padding: 6px 12px;background: var(--btn-bg);color: var(--btn-text);border: 1px solid var(--edge-main);border-radius: 6px;cursor: pointer;font-size: 14px;font-weight: 600;\" data-lang-text=\"ui.buttons.auditLog\">Audit</button>\n        <button id=\"port-map-btn\" data-lang=\"ui.tooltips.viewPortMap\" data-lang-attr=\"title\" title=\"View all port connections\" style=\"padding: 6px 12px;background: var(--btn-bg);color: var(--btn-text);border: 1px solid var(--edge-main);border-radius: 6px;cursor: pointer;font-size: 14px;font-weight: 600;\" data-lang-text=\"ui.buttons.portMap\">Ports</button>\n        <button id=\"secrets-btn\" data-lang=\"ui.tooltips.manageNotes\" data-lang-attr=\"title\" title=\"Manage encrypted notes\" style=\"padding: 6px 12px;background: var(--btn-bg);color: var(--btn-text);border: 1px solid var(--edge-main);border-radius: 6px;cursor: pointer;font-size: 14px;font-weight: 600;\" data-lang-text=\"ui.buttons.notes\">Notes</button>\n\t\t<div id=\"recording-controls\" style=\"display:inline-flex;align-items:center;gap:4px;margin-left:8px;padding:4px 8px;background:var(--panel-alt);border:1px solid var(--edge-main);border-radius:6px;\">\n          <button id=\"record-btn\" title=\"Start recording\" style=\"padding:4px 8px;background:var(--btn-bg);color:#f56565;border:1px solid var(--edge-main);border-radius:4px;cursor:pointer;font-size:14px;\">●</button>\n          <button id=\"step-record-btn\" title=\"Step by step recording\" style=\"padding:4px 8px;background:var(--btn-bg);color:#48bb78;border:1px solid var(--edge-main);border-radius:4px;cursor:pointer;font-size:14px;\">●+</button>\n          <button id=\"play-btn\" title=\"Play recording\" style=\"padding:4px 8px;background:var(--btn-bg);color:var(--btn-text);border:1px solid var(--edge-main);border-radius:4px;cursor:pointer;font-size:14px;\">▶</button>\n          <div id=\"recording-expanded\" style=\"display:none;align-items:center;gap:4px;\">\n            <button id=\"stop-btn\" title=\"Stop\" style=\"padding:4px 8px;background:var(--btn-bg);color:var(--btn-text);border:1px solid var(--edge-main);border-radius:4px;cursor:pointer;font-size:14px;\">■</button>\n            <button id=\"pause-btn\" title=\"Pause\" style=\"padding:4px 8px;background:var(--btn-bg);color:var(--btn-text);border:1px solid var(--edge-main);border-radius:4px;cursor:pointer;font-size:14px;display:none;\">❚❚</button>\n            <span id=\"recording-time\" style=\"font-size:11px;color:var(--text-soft);min-width:40px;text-align:center;\">0:00</span>\n            <input type=\"range\" id=\"playback-scrubber\" min=\"0\" max=\"100\" value=\"0\" style=\"width:80px;cursor:pointer;\" title=\"Playback position\">\n            <select id=\"playback-speed\" title=\"Playback speed\" style=\"padding:2px 4px;background:var(--input-bg);color:var(--input-text);border:1px solid var(--input-border);border-radius:4px;font-size:12px;\">\n              <option value=\"0.5\">0.5x</option>\n              <option value=\"1\" selected>1x</option>\n              <option value=\"2\">2x</option>\n              <option value=\"4\">4x</option>\n            </select>\n            <label class=\"toggle-switch\" title=\"Loop playback\"><input type=\"checkbox\" id=\"loop-playback\"><span class=\"toggle-slider\"></span></label>\n            <button id=\"recordings-btn\" title=\"Manage recordings\" style=\"padding:4px 8px;background:var(--btn-bg);color:var(--btn-text);border:1px solid var(--edge-main);border-radius:4px;cursor:pointer;font-size:12px;\">...</button>\n          </div>\n        </div>\n\t\t<button id=\"mobile-export-btn\" data-lang=\"tooltips.exportTopology\" data-lang-attr=\"title\" title=\"Export topology\" style=\"padding: 6px 12px;background: var(--btn-bg);color: var(--btn-text);border: 1px solid var(--edge-main);border-radius: 6px;cursor: pointer;font-size: 14px;font-weight: 600;\" data-lang-text=\"topbar.export\">Export</button>\n        <button id=\"mobile-import-btn\" data-lang=\"tooltips.importTopology\" data-lang-attr=\"title\" title=\"Import topology\" style=\"padding: 6px 12px;background: var(--btn-bg);color: var(--btn-text);border: 1px solid var(--edge-main);border-radius: 6px;cursor: pointer;font-size: 14px;font-weight: 600;\" data-lang-text=\"topbar.import\">Import</button>\n        <button id=\"settings-btn\" title=\"Page settings\">⚙️</button>\n      </div>\n      <button id=\"mobile-menu-toggle\" class=\"mobile-menu-btn\">☰</button>\n      <div class=\"header-resizer\" id=\"header-resizer\">\n        <svg class=\"resizer-icon\" width=\"40\" height=\"6\" viewBox=\"0 0 40 6\" xmlns=\"http://www.w3.org/2000/svg\">\n          <circle cx=\"14\" cy=\"3\" r=\"2\"></circle>\n          <circle cx=\"20\" cy=\"3\" r=\"2\"></circle>\n          <circle cx=\"26\" cy=\"3\" r=\"2\"></circle>\n        </svg>\n      </div>\n    </header>\n    <main>\n      <section class=\"topology-panel\">\n        <div class=\"draw-toolbar\" id=\"draw-toolbar\" style=\"display: flex !important;\">\n          <button type=\"button\" class=\"toolbar-close-btn\" id=\"draw-toolbar-close-btn\" title=\"Hide draw toolbar\">✕</button>\n          <button id=\"draw-toggle\" title=\"Draw custom line\">✏️</button>\n          <button id=\"rect-toggle\" title=\"Draw zone\">▭</button>\n          <button id=\"text-toggle\" title=\"Add text\">T</button>\n          <button id=\"image-toggle\" data-lang=\"tooltips.addImage\" data-lang-attr=\"title\">🖼️</button>\n          <input type=\"file\" id=\"image-file-input\" accept=\"image/*\" style=\"display: none;\">\n          <span style=\"border-left: 1px solid var(--edge-main); height: 20px; margin: 0 4px;\"></span>\n          <select id=\"rect-style\" title=\"zone style\">\n            <option value=\"filled\">Filled</option>\n            <option value=\"outlined\">Outlined</option>\n          </select>\n          <input type=\"color\" id=\"draw-color\" value=\"#f97316\" title=\"Line color\">\n          <select id=\"draw-style\" title=\"Line style\">\n            <option value=\"solid\">Solid</option>\n            <option value=\"dashed\">Dashed</option>\n            <option value=\"dotted\">Dotted</option>\n            <option value=\"wall\">Wall</option>\n          </select>\n          <select id=\"draw-arrow\" title=\"Arrow direction\">\n            <option value=\"none\">No arrows</option>\n            <option value=\"forward\">→ Right</option>\n            <option value=\"backward\">← Left</option>\n            <option value=\"both\">↔ Both</option>\n          </select>\n          <button id=\"draw-undo\" style=\"display: none\" data-lang=\"tooltips.undoLastPoint\" data-lang-attr=\"title\" title=\"Undo last point\" data-lang-text=\"drawToolbar.undoLastPoint\">Undo</button>\n        </div>\n        <div class=\"topology-toolbar\" id=\"topology-toolbar\" style=\"display: flex !important;\">\n          <button type=\"button\" class=\"toolbar-close-btn\" id=\"topology-toolbar-close-btn\" title=\"Hide add line toolbar\">✕</button>\n          <label for=\"add-line-select\" data-lang=\"topologyToolbar.addLineTo\">Add line to:</label>\n          <select id=\"add-line-select\"></select>\n\t      <input type=\"color\" id=\"add-line-color\" value=\"#475569\" title=\"Line color\" style=\"width: 30px;height: 24px;border: 1px solid var(--toolbar-border);border-radius: 4px;cursor: pointer;background: transparent;padding: 0;\">     \n\t\t <select id=\"add-line-direction\" title=\"Line direction\">\n            <option value=\"none\">No arrows</option>\n            <option value=\"forward\">→ Forward</option>\n            <option value=\"backward\">← Backward</option>\n            <option value=\"both\">↔ Both</option>\n          </select>\n          <select id=\"add-line-routing\" title=\"Line routing\">\n            <option value=\"orthogonal\">Orthogonal</option>\n            <option value=\"curved\">Curved</option>\n            <option value=\"straight\">Straight</option>\n          </select>\n          <button id=\"add-line-btn\" data-lang=\"ui.buttons.add\">Add</button>\n        </div>\n        <div class=\"topology-toolbar bulk-toolbar-desktop\" id=\"bulk-toolbar\" style=\"display: none\">\n          <button type=\"button\" class=\"toolbar-close-btn\" id=\"bulk-toolbar-close\" title=\"Clear selection\">✕</button>\n          <label style=\"font-weight: 600; color: var(--accent)\" data-lang=\"bulkToolbar.selected\">Selected: <span id=\"bulk-count\">0</span></label>\n          <button id=\"bulk-align-left\" title=\"Align left\">⬅ Left</button>\n          <button id=\"bulk-align-right\" title=\"Align right\">➡ Right</button>\n          <button id=\"bulk-align-top\" title=\"Align top\">⬆ Top</button>\n          <button id=\"bulk-align-bottom\" title=\"Align bottom\">⬇ Bottom</button>\n          <button id=\"bulk-distribute-h\" title=\"Distribute horizontally\">↔ Distribute H</button>\n          <button id=\"bulk-distribute-v\" title=\"Distribute vertically\">↕ Distribute V</button>\n          <button id=\"bulk-clone\" title=\"Clone selected\">📋 Clone</button>\n          <button id=\"bulk-zone-copy\" title=\"Copy zone style\">📡 Copy Zone</button>\n          <button id=\"bulk-zone-paste\" title=\"Paste zone style\">📡 Paste Zone</button>\n          <button id=\"bulk-zone-toggle\" title=\"Toggle zones on selected\">📡 Toggle Zones</button>\n          <button id=\"bulk-delete\" title=\"Delete selected\" style=\"background: var(--danger); color: white;\" data-lang=\"ui.buttons.delete\">Delete</button>\n        </div>\n        <div class=\"bulk-toolbar-mobile\" id=\"bulk-toolbar-mobile\" style=\"display: none\">\n          <button type=\"button\" id=\"bulk-mobile-btn\" style=\"background: var(--accent);color: white;padding: 12px 20px;border-radius: 25px;font-weight: 600;font-size: 16px;box-shadow: 0 4px 12px rgba(0,0,0,0.3);border: none;cursor: pointer;display: flex;align-items: center;gap: 8px;\">\n          <span id=\"bulk-count-mobile\">0</span>Selected</button>\n        </div>\n        <div id=\"bulk-actions-modal\" style=\"display: none;position: fixed;bottom: 0;left: 0;right: 0;background: var(--panel-alt);border-top-left-radius:20px;border-top-right-radius:20px;padding:20px;padding-bottom:(safe-area-inset-bottom, 20px);box-shadow: 0 -4px 20px rgba(0,0,0,0.5);z-index: 10000;max-height: calc(100vh - 80px);overflow-y: auto;\">\n          <div style=\"display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;\">\n            <h3 style=\"margin: 0; color: var(--accent); font-size: 20px;\">\n              <span id=\"bulk-count-modal\">0</span> Nodes Selected\n            </h3>\n            <button id=\"bulk-modal-close\" style=\"background: none;border: none;font-size: 24px;cursor: pointer;color: var(--text-main);padding: 0;width: 32px;height: 32px;\">✕</button>\n          </div>\n          <div style=\"display: grid; grid-template-columns: 1fr 1fr; gap: 12px;\">\n            <button id=\"bulk-align-left-mobile\" class=\"bulk-action-btn\">⬅<br><span style=\"font-size: 12px;\">Align Left</span></button>\n            <button id=\"bulk-align-right-mobile\" class=\"bulk-action-btn\">➡<br><span style=\"font-size: 12px;\">Align Right</span></button>\n            <button id=\"bulk-align-top-mobile\" class=\"bulk-action-btn\">⬆<br><span style=\"font-size: 12px;\">Align Top</span></button>\n            <button id=\"bulk-align-bottom-mobile\" class=\"bulk-action-btn\">⬇<br><span style=\"font-size: 12px;\">Align Bottom</span></button>\n            <button id=\"bulk-distribute-h-mobile\" class=\"bulk-action-btn\">↔<br><span style=\"font-size: 12px;\">Distribute H</span></button>\n            <button id=\"bulk-distribute-v-mobile\" class=\"bulk-action-btn\">↕<br><span style=\"font-size: 12px;\">Distribute V</span></button>\n            <button id=\"bulk-lock-mobile\" class=\"bulk-action-btn\">🔒<br><span style=\"font-size: 12px;\">Lock Toggle</span></button>\n            <button id=\"bulk-group-mobile\" class=\"bulk-action-btn\">⭕<br><span style=\"font-size: 12px;\">Group Toggle</span></button>\n            <button id=\"bulk-clone-mobile\" class=\"bulk-action-btn\">📋<br><span style=\"font-size: 12px;\">Clone All</span></button>\n            <button id=\"bulk-delete-mobile\" class=\"bulk-action-btn\" style=\"background: var(--danger); color: white;\">🗑<br><span style=\"font-size: 12px;\">Delete All</span></button>\n          </div>\n          <div style=\"margin-top: 16px; padding-top: 16px; border-top: 1px solid var(--edge-main);\">\n            <div style=\"font-size: 12px; color: var(--text-soft); margin-bottom: 12px; text-transform: uppercase; letter-spacing: 0.05em;\">Coverage Zones</div>\n            <div style=\"display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 12px;\">\n              <button id=\"bulk-zone-copy-mobile\" class=\"bulk-action-btn\">📡<br><span style=\"font-size: 12px;\">Copy Zone</span></button>\n              <button id=\"bulk-zone-paste-mobile\" class=\"bulk-action-btn\">📡<br><span style=\"font-size: 12px;\">Paste Zone</span></button>\n              <button id=\"bulk-zone-toggle-mobile\" class=\"bulk-action-btn\">📡<br><span style=\"font-size: 12px;\">Toggle Zones</span></button>\n            </div>\n          </div>\n          <button id=\"bulk-clear-mobile\" style=\"width: 100%;margin-top: 16px;padding: 14px;background: var(--panel-main);border: 1px solid var(--edge-main);border-radius: 8px;color: var(--text-main);font-size: 16px;cursor: pointer;\" data-lang=\"bulkToolbar.clearSelection\">Clear Selection</button>\n        </div>\n        <div class=\"canvas-hint visible\" id=\"canvas-hint\"></div>\n        <div class=\"legend-container\" id=\"edge-legend\" style=\"display: none;\"><div class=\"legend-title\">Connection Legend</div><button type=\"button\" class=\"legend-close-btn\">✕</button></div>\n        <div class=\"canvas-viewport\" id=\"canvas-viewport\">\n          <svg id=\"map\" viewBox=\"0 0 4000 3000\" style=\"\"></svg>\n        </div>\n        <div class=\"minimap-zoom-wrapper\" id=\"minimap-zoom-wrapper\" style=\"display: block;\">\n          <button type=\"button\" class=\"minimap-close-btn\" id=\"minimap-close-btn\" title=\"Hide map &amp; zoom\">✕</button>\n          <div class=\"minimap-container\" id=\"minimap-container\">\n            <svg id=\"minimap\" viewBox=\"0 0 4000 3000\">\n              <rect class=\"minimap-viewport\" id=\"minimap-viewport\" x=\"0\" y=\"0\" width=\"4000\" height=\"3000\"></rect>\n            </svg>\n          </div>\n          <div class=\"zoom-toolbar\" id=\"zoom-toolbar\">\n            <button id=\"zoom-in-btn\" title=\"Zoom in\">+</button>\n            <div class=\"zoom-level\" id=\"zoom-level\">100%</div>\n            <button id=\"zoom-out-btn\" title=\"Zoom out\">-</button>\n            <div class=\"divider\"></div>\n            <button id=\"zoom-fit-btn\" title=\"Fit to view\">[ ]</button>\n            <button id=\"zoom-reset-btn\" title=\"Reset view\">R</button>\n          </div>\n        </div>\n        <button type=\"button\" id=\"edge-legend-mini\" class=\"legend-mini-btn\" style=\"bottom: 10px; display: none;\" data-lang=\"toolbar.legendMini\">Connection & Zone Legend</button>\n        <button type=\"button\" id=\"minimap-mini\" class=\"legend-mini-btn\" style=\"right: 10px; left: auto; bottom: 10px; display: none;\" data-lang=\"toolbar.minimapMini\">Map</button>\n        <button type=\"button\" id=\"draw-toolbar-mini\" class=\"legend-mini-btn\" style=\"top: 10px; left: 10px; right: auto; display: none;\" data-lang=\"toolbar.drawMini\">Draw</button>\n        <button type=\"button\" id=\"topology-toolbar-mini\" class=\"legend-mini-btn\" style=\"top: 10px; left: auto; right: 40px; display: none;\" data-lang=\"toolbar.topologyMini\">Add Connection</button>\n     </section>\n      <aside class=\"details-panel\" id=\"details-panel\">\n        <div class=\"sidebar-resizer\" id=\"sidebar-resizer\">\n          <svg class=\"resizer-icon\" width=\"6\" height=\"40\" viewBox=\"0 0 6 40\" xmlns=\"http://www.w3.org/2000/svg\">\n            <circle cx=\"3\" cy=\"14\" r=\"2\"></circle>\n            <circle cx=\"3\" cy=\"20\" r=\"2\"></circle>\n            <circle cx=\"3\" cy=\"26\" r=\"2\"></circle>\n          </svg>\n        </div>\n        <div class=\"mobile-footer-resizer\" id=\"mobile-footer-resizer\">\n          <svg class=\"resizer-icon\" width=\"40\" height=\"6\" viewBox=\"0 0 40 6\" xmlns=\"http://www.w3.org/2000/svg\">\n            <circle cx=\"14\" cy=\"3\" r=\"2\"></circle>\n            <circle cx=\"20\" cy=\"3\" r=\"2\"></circle>\n            <circle cx=\"26\" cy=\"3\" r=\"2\"></circle>\n          </svg>\n        </div>\n        <div id=\"node-panel\" style=\"display: none;\">\n          <div class=\"details-name editable-text\" id=\"node-name\">OPNSENSE</div>\n          <div class=\"details-ip editable-text\" id=\"node-ip\">192.168.100.1</div>\n          <div class=\"details-info-row\" id=\"mac-row\">\n            <span style=\"color: var(--text-soft); font-size: 14px; width: 80px;\">MAC:</span>\n            <span class=\"editable-text\" id=\"node-mac\" style=\"flex: 1; cursor: pointer; color: var(--text-main);\">--</span>\n          </div>\n          <div class=\"details-info-row\" id=\"rack-unit-row\">\n            <span style=\"color: var(--text-soft); font-size: 14px; width: 80px;\">Rack Unit:</span>\n            <span class=\"editable-text\" id=\"node-rack\" style=\"flex: 1; cursor: pointer; color: var(--text-main);\">--</span>\n          </div>\n          <div class=\"details-info-row\" id=\"uheight-row\" style=\"display: none;\">\n            <span style=\"color: var(--text-soft); font-size: 14px; width: 80px;\">U Height:</span>\n            <span class=\"editable-text\" id=\"node-uheight\" style=\"flex: 1; cursor: pointer; color: var(--text-main);\">1U</span>\n          </div>\n          <div class=\"details-info-row\" id=\"assigned-rack-row\" style=\"display: flex;\">\n            <span id=\"assigned-rack-label\" style=\"color: var(--text-soft); font-size: 14px; width: 80px;\">Assigned Rack:</span>\n            <select id=\"node-assigned-rack\" style=\"flex: 1; padding: 4px 8px; background: var(--panel); color: var(--text-main); border: 1px solid var(--edge-main); border-radius: 4px; cursor: pointer; font-size: 14px;\"><option value=\"\">None</option></select>\n          </div>\n          <div class=\"details-info-row\" id=\"hosted-on-row\" style=\"display: flex;\">\n            <span style=\"color: var(--text-soft); font-size: 14px;\" data-lang=\"nodePanel.hostedOn\">Hosted On:</span>\n            <select id=\"node-hosted-on\" style=\"margin-left: 8px; padding: 4px 8px; background: var(--panel); color: var(--text-main); border: 1px solid var(--edge-main); border-radius: 4px; cursor: pointer; font-size: 14px;\"><option value=\"\" data-lang=\"ui.options.none\">None</option></select>\n          </div>\n          <div class=\"details-info-row\" id=\"rack-capacity-row\" style=\"display: none;\">\n            <span style=\"color: var(--text-soft); font-size: 14px; width: 80px;\">Rack Capacity:</span>\n            <select id=\"node-rack-capacity\" style=\"flex: 1; padding: 4px 8px; background: var(--panel); color: var(--text-main); border: 1px solid var(--edge-main); border-radius: 4px; cursor: pointer; font-size: 14px;\">\n              <option value=\"42\">42U</option>\n              <option value=\"48\">48U</option>\n              <option value=\"24\">24U</option>\n              <option value=\"12\">12U</option>\n              <option value=\"6\">6U</option>\n            </select>\n          </div>\n          <div class=\"details-info-row\">\n            <span style=\"color: var(--text-soft); font-size: 14px; width: 80px;\">Layer:</span>\n            <select id=\"node-layer\" style=\"flex: 1; padding: 4px 8px; background: var(--panel); color: var(--text-main); border: 1px solid var(--edge-main); border-radius: 4px; cursor: pointer; font-size: 14px;\">\n              <option id=\"node-layer-opt1\" value=\"layer1\">Layer 1</option>\n              <option id=\"node-layer-opt2\" value=\"layer2\">Layer 2</option>\n              <option id=\"node-layer-opt3\" value=\"layer3\">Layer 3</option>\n              <option id=\"node-layer-opt4\" value=\"layer4\">Layer 4</option>\n            </select>\n          </div>\n          <div class=\"details-info-row\" id=\"node-trail-row\">\n            <span style=\"color: var(--text-soft); font-size: 14px;\" data-lang=\"nodePanel.motionTrail\">Motion Trail:</span>\n            <label class=\"toggle-switch\" style=\"margin-left: 8px;\"><input type=\"checkbox\" id=\"node-trail-enabled\" checked><span class=\"toggle-slider\"></span></label>\n          </div>\n          <div class=\"details-role\" id=\"node-role\">WAN</div>\n\t\t  <details id=\"rack-contents-section\" style=\"display: none; margin-top: 12px; margin-bottom: 12px; padding: 10px; background: var(--panel-alt); border-radius: 6px;\" open=\"\">\n            <summary style=\"color: var(--accent); font-weight: 600; font-size: 14px; cursor: pointer;\">Nodes in Rack (<span id=\"rack-contents-count\">0</span>)</summary>\n            <div id=\"rack-contents-list\" style=\"max-height: 200px; overflow-y: auto; margin-top: 8px;\"></div>\n          </details>\n          <details id=\"fov-section\" style=\"display: none; margin-top: 12px; margin-bottom: 12px; padding: 10px; background: var(--panel-alt); border-radius: 6px;\">\n            <summary style=\"color: var(--accent); font-weight: 600; font-size: 14px; cursor: pointer;\">Coverage Zone</summary>\n            <div style=\"padding: 10px 0;\">\n              <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px; gap: 8px;\">\n                <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Preset:</label>\n                <select id=\"fov-preset\" style=\"flex: 1; padding: 4px 8px; background: var(--panel); border: 1px solid var(--edge-main); border-radius: 4px; color: var(--text-main);\">\n                  <option value=\"\">-- Apply Preset --</option>\n                  <option value=\"security-cam\">Security Camera</option>\n                  <option value=\"ptz-cam\">PTZ Camera</option>\n                  <option value=\"motion-detect\">Motion Detector</option>\n                  <option value=\"wifi-strong\">WiFi</option>\n                  <option value=\"wifi-extended\">WiFi Extender</option>\n                  <option value=\"smoke-alarm\">Smoke Alarm</option>\n                  <option value=\"sprinkler-arc\">Sprinkler Arc</option>\n                </select>\n                <button id=\"fov-save-preset\" title=\"Save as preset\" style=\"padding: 4px 8px; background: var(--panel); border: 1px solid var(--edge-main); border-radius: 4px; color: var(--text-main); cursor: pointer;\">💾</button>\n                <button id=\"fov-copy-style\" title=\"Copy zone style\" style=\"padding: 4px 8px; background: var(--panel); border: 1px solid var(--edge-main); border-radius: 4px; color: var(--text-main); cursor: pointer;\">📋</button>\n                <button id=\"fov-paste-style\" title=\"Paste zone style\" style=\"padding: 4px 8px; background: var(--panel); border: 1px solid var(--edge-main); border-radius: 4px; color: var(--text-main); cursor: pointer;\">📥</button>\n              </div>\n              <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Show Zone:</label>\n                <label class=\"toggle-switch\"><input type=\"checkbox\" id=\"fov-enabled\"><span class=\"toggle-slider\"></span></label>\n              </div>\n              <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Angle:</label>\n                <input type=\"range\" id=\"fov-angle\" min=\"10\" max=\"360\" value=\"90\" style=\"flex: 1;\">\n                <span id=\"fov-angle-value\" style=\"min-width: 40px; text-align: right; color: var(--text-main);\">90°</span>\n              </div>\n              <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Distance:</label>\n                <input type=\"range\" id=\"fov-distance\" min=\"50\" max=\"500\" value=\"150\" style=\"flex: 1;\">\n                <span id=\"fov-distance-value\" style=\"min-width: 40px; text-align: right; color: var(--text-main);\">150</span>\n              </div>\n              <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Inner Radius:</label>\n                <input type=\"range\" id=\"fov-inner-radius\" min=\"0\" max=\"200\" value=\"0\" style=\"flex: 1;\">\n                <span id=\"fov-inner-radius-value\" style=\"min-width: 40px; text-align: right; color: var(--text-main);\">0</span>\n              </div>\n              <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Rotation:</label>\n                <input type=\"range\" id=\"fov-rotation\" min=\"0\" max=\"360\" value=\"0\" style=\"flex: 1;\">\n                <span id=\"fov-rotation-value\" style=\"min-width: 40px; text-align: right; color: var(--text-main);\">0°</span>\n              </div>\n              <div style=\"border-top: 1px solid var(--edge-main); margin-top: 10px; padding-top: 10px;\">\n                <div style=\"font-size: 12px; color: var(--text-soft); margin-bottom: 8px; text-transform: uppercase; letter-spacing: 0.05em;\">Fill</div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Color:</label>\n                  <input type=\"color\" id=\"fov-color\" value=\"#f59e0b\" style=\"width: 50px; height: 30px; border: 1px solid var(--edge-main); border-radius: 4px; cursor: pointer;\">\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Opacity:</label>\n                  <input type=\"range\" id=\"fov-opacity\" min=\"5\" max=\"80\" value=\"20\" style=\"flex: 1;\">\n                  <span id=\"fov-opacity-value\" style=\"min-width: 40px; text-align: right; color: var(--text-main);\">20%</span>\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Gradient:</label>\n                  <label class=\"toggle-switch\"><input type=\"checkbox\" id=\"fov-gradient\"><span class=\"toggle-slider\"></span></label>\n                  <span style=\"color: var(--text-soft); font-size: 12px; margin-left: 8px;\">Fade toward edge</span>\n                </div>\n              </div>\n              <div style=\"border-top: 1px solid var(--edge-main); margin-top: 10px; padding-top: 10px;\">\n                <div style=\"font-size: 12px; color: var(--text-soft); margin-bottom: 8px; text-transform: uppercase; letter-spacing: 0.05em;\">Border</div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Color:</label>\n                  <input type=\"color\" id=\"fov-border-color\" value=\"#f59e0b\" style=\"width: 50px; height: 30px; border: 1px solid var(--edge-main); border-radius: 4px; cursor: pointer;\">\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Width:</label>\n                  <input type=\"range\" id=\"fov-border-width\" min=\"0\" max=\"10\" value=\"2\" style=\"flex: 1;\">\n                  <span id=\"fov-border-width-value\" style=\"min-width: 40px; text-align: right; color: var(--text-main);\">2</span>\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Style:</label>\n                  <select id=\"fov-border-style\" style=\"flex: 1; padding: 4px 8px; background: var(--panel); border: 1px solid var(--edge-main); border-radius: 4px; color: var(--text-main);\">\n                    <option value=\"solid\">Solid</option>\n                    <option value=\"dashed\">Dashed</option>\n                    <option value=\"dotted\">Dotted</option>\n                  </select>\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Opacity:</label>\n                  <input type=\"range\" id=\"fov-border-opacity\" min=\"0\" max=\"100\" value=\"100\" style=\"flex: 1;\">\n                  <span id=\"fov-border-opacity-value\" style=\"min-width: 40px; text-align: right; color: var(--text-main);\">100%</span>\n                </div>\n              </div>\n              <div style=\"border-top: 1px solid var(--edge-main); margin-top: 10px; padding-top: 10px;\">\n                <div style=\"font-size: 12px; color: var(--text-soft); margin-bottom: 8px; text-transform: uppercase; letter-spacing: 0.05em;\">Label</div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Text:</label>\n                  <input type=\"text\" id=\"fov-label\" placeholder=\"e.g. Detection Zone\" style=\"flex: 1; padding: 4px 8px; background: var(--panel); border: 1px solid var(--edge-main); border-radius: 4px; color: var(--text-main);\">\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Position:</label>\n                  <select id=\"fov-label-position\" style=\"flex: 1; padding: 4px 8px; background: var(--panel); border: 1px solid var(--edge-main); border-radius: 4px; color: var(--text-main);\">\n                    <option value=\"center\">Center</option>\n                    <option value=\"edge\">Edge</option>\n                    <option value=\"outside\">Outside</option>\n                  </select>\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Font Size:</label>\n                  <input type=\"range\" id=\"fov-label-size\" min=\"8\" max=\"32\" value=\"14\" style=\"flex: 1;\">\n                  <span id=\"fov-label-size-value\" style=\"min-width: 40px; text-align: right; color: var(--text-main);\">14px</span>\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Font Color:</label>\n                  <input type=\"color\" id=\"fov-label-color\" value=\"#ffffff\" style=\"width: 50px; height: 30px; border: 1px solid var(--edge-main); border-radius: 4px; cursor: pointer;\">\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Bold:</label>\n                  <label class=\"toggle-switch\"><input type=\"checkbox\" id=\"fov-label-bold\"><span class=\"toggle-slider\"></span></label>\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Background:</label>\n                  <label class=\"toggle-switch\"><input type=\"checkbox\" id=\"fov-label-bg\"><span class=\"toggle-slider\"></span></label>\n                  <input type=\"color\" id=\"fov-label-bg-color\" value=\"#000000\" style=\"width: 50px; height: 30px; border: 1px solid var(--edge-main); border-radius: 4px; cursor: pointer; margin-left: 8px;\">\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Offset X:</label>\n                  <input type=\"range\" id=\"fov-label-offset-x\" min=\"-200\" max=\"200\" value=\"0\" style=\"flex: 1;\">\n                  <span id=\"fov-label-offset-x-value\" style=\"min-width: 40px; text-align: right; color: var(--text-main);\">0</span>\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Offset Y:</label>\n                  <input type=\"range\" id=\"fov-label-offset-y\" min=\"-200\" max=\"200\" value=\"0\" style=\"flex: 1;\">\n                  <span id=\"fov-label-offset-y-value\" style=\"min-width: 40px; text-align: right; color: var(--text-main);\">0</span>\n                </div>\n              </div>\n              <div style=\"border-top: 1px solid var(--edge-main); margin-top: 10px; padding-top: 10px;\">\n                <div style=\"font-size: 12px; color: var(--text-soft); margin-bottom: 8px; text-transform: uppercase; letter-spacing: 0.05em;\">Animation</div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Animate:</label>\n                  <label class=\"toggle-switch\"><input type=\"checkbox\" id=\"fov-animate\"><span class=\"toggle-slider\"></span></label>\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Type:</label>\n                  <select id=\"fov-animation-type\" style=\"flex: 1; padding: 4px 8px; background: var(--panel); border: 1px solid var(--edge-main); border-radius: 4px; color: var(--text-main);\">\n                    <option value=\"sweep\">Sweep (Pan)</option>\n                    <option value=\"pulse\">Pulse (Breathe)</option>\n                    <option value=\"rings\">Rings (Emanate)</option>\n                    <option value=\"spin\">Spin (Rotate)</option>\n                  </select>\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Sweep:</label>\n                  <input type=\"range\" id=\"fov-sweep\" min=\"30\" max=\"360\" value=\"120\" style=\"flex: 1;\">\n                  <span id=\"fov-sweep-value\" style=\"min-width: 40px; text-align: right; color: var(--text-main);\">120°</span>\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Speed:</label>\n                  <input type=\"range\" id=\"fov-speed\" min=\"1\" max=\"60\" value=\"4\" style=\"flex: 1;\">\n                  <span id=\"fov-speed-value\" style=\"min-width: 40px; text-align: right; color: var(--text-main);\">4s</span>\n                </div>\n              </div>\n            </div>\n          </details>\n          <details id=\"node-connections-section\" style=\"margin-top: 12px; margin-bottom: 12px; padding: 10px; background: var(--panel-alt); border-radius: 6px;\">\n            <summary style=\"color: var(--accent); font-weight: 600; font-size: 14px; cursor: pointer;\">Connections (<span id=\"node-connections-count\">0</span>)</summary>\n            <div id=\"node-connections-list\" style=\"max-height: 200px; overflow-y: auto; margin-top: 8px;\"></div>\n          </details>\n          <div class=\"badge-row\" id=\"node-tags\"></div>\n          <div style=\"margin-top: 10px; display: flex; gap: 8px\">\n            <input type=\"text\" id=\"new-tag-input\" placeholder=\"Add tag...\" style=\"flex: 1;padding: 8px 12px;background: var(--panel);border: 1px solid var(--edge-main);border-radius: 6px;color: var(--text-main);font-size: clamp(14px, 1.6vw, 18px);\">\n            <button id=\"add-tag-btn\" style=\"padding: 8px 16px;background: var(--accent);color: var(--bg);border: none;border-radius: 6px;cursor: pointer;font-size: clamp(14px, 1.6vw, 18px);font-weight: 600;\" data-lang=\"ui.buttons.add\">Add</button>\n          </div>\n          <button class=\"pick-icon-btn\" id=\"pick-tag-icon-btn\" data-lang=\"ui.labels.addIconTag\">Add Web Icon Tag</button>\n          <div class=\"section-label\" data-lang=\"ui.labels.size\">Size</div>\n          <div class=\"size-controls\">\n            <label data-lang=\"ui.labels.size\">Size:</label>\n            <input type=\"range\" id=\"size-slider\" min=\"20\" max=\"200\" value=\"55\">\n            <span id=\"size-value\">127</span>\n            <button id=\"reset-size\" data-lang=\"ui.buttons.reset\">Reset</button>\n          </div>\n          <div class=\"size-controls\">\n            <label data-lang=\"ui.labels.rotation\">Rotation:</label>\n            <input type=\"range\" id=\"rotation-slider\" min=\"-360\" max=\"360\" value=\"0\">\n            <input type=\"number\" id=\"rotation-value\" value=\"0\" style=\"width: 60px; padding: 4px; background: var(--panel); border: 1px solid var(--edge-main); border-radius: 4px; color: var(--text-main); text-align: center;\">\n            <span style=\"color: var(--text-soft);\">°</span>\n            <button id=\"reset-rotation\" data-lang=\"ui.buttons.reset\">Reset</button>\n          </div>\n          <details class=\"style-section\">\n            <summary data-lang=\"nodePanel.styling\">Styling</summary>\n            <div class=\"style-content\">\n              <div class=\"style-row\">\n                <label data-lang=\"nodePanel.screen\">Screen:</label>\n                <select id=\"style-scope\">\n                  <option value=\"all\" data-lang=\"ui.tabs.all\">All</option>\n                  <option value=\"desktop\" data-lang=\"help.tabs.desktop\">Desktop</option>\n                  <option value=\"tablet\" data-lang=\"nodePanel.tablet\">Tablet</option>\n                  <option value=\"mobile\" data-lang=\"help.tabs.mobile\">Mobile</option>\n                  <option value=\"fold\" data-lang=\"nodePanel.fold\">Fold</option>\n                </select>\n              </div>\n              <div class=\"style-row\">\n                <label data-lang=\"ui.labels.category\">Category:</label>\n                <select id=\"shape-category-select\">\n                  <option value=\"basic\" data-lang=\"shapes.categories.basic\">Basic Shapes</option>\n                  <option value=\"computers\" data-lang=\"shapes.categories.computers\">Computers & Devices</option>\n                  <option value=\"network\" data-lang=\"shapes.categories.network\">Network Equipment</option>\n                  <option value=\"cloud\" data-lang=\"shapes.categories.cloud\">Cloud & Services</option>\n                  <option value=\"security\" data-lang=\"shapes.categories.security\">Security & Monitoring</option>\n                  <option value=\"smarthome\" data-lang=\"shapes.categories.smarthome\">Smart Home</option>\n                  <option value=\"sports\" data-lang=\"shapes.categories.sports\">Sports</option>\n                  <option value=\"rack\" data-lang=\"shapes.categories.rack\">Rack Equipment</option>\n                </select>\n              </div>\n              <div class=\"style-row\">\n                <label data-lang=\"ui.labels.shape\">Shape:</label>\n                <select id=\"shape-select\"></select>\n              </div>\n              <div class=\"style-row\">\n               <button style=\"width:100%\" class=\"pick-icon-btn\" id=\"pick-shape-icon-btn\" data-lang=\"nodePanel.searchWebIcons\">Or Search Web Icons</button>\n              </div>\t\n              <div class=\"style-row\" style=\"display:unset !important;\">\n               \n              </div>\t\t\t\t  \n              <div class=\"style-row\">\n                <label data-lang=\"nodePanel.fillColor\">Fill Color:</label>\n                <input type=\"color\" id=\"circle-color\" value=\"#1e293b\">\n              </div>\n              <div class=\"style-row\">\n                <label data-lang=\"nodePanel.borderColor\">Border Color:</label>\n                <input type=\"color\" id=\"circle-border\" value=\"#475569\">\n              </div>\n              <div class=\"style-row\">\n                <label data-lang=\"ui.labels.titleColor\">Title Color:</label>\n                <input type=\"color\" id=\"title-color\" value=\"#e2e8f0\">\n              </div>\n              <div class=\"style-row\">\n                <label data-lang=\"nodePanel.titleFont\">Title Font:</label>\n                <select id=\"title-font\">\n                  <option value=\"system-ui, sans-serif\" data-lang=\"nodePanel.systemUI\">System UI</option>\n                  <option value=\"monospace\" data-lang=\"ui.fonts.monospace\">Monospace</option>\n                  <option value=\"serif\" data-lang=\"nodePanel.serif\">Serif</option>\n                  <option value=\"cursive\" data-lang=\"nodePanel.cursive\">Cursive</option>\n                  <option value=\"'Courier New', monospace\" data-lang=\"nodePanel.courier\">Courier</option>\n                  <option value=\"Arial, sans-serif\" data-lang=\"ui.fonts.arial\">Arial</option>\n                  <option value=\"'Times New Roman', serif\" data-lang=\"nodePanel.times\">Times</option>\n                </select>\n              </div>\n              <div class=\"style-row\">\n                <label data-lang=\"ui.labels.titleSize\">Title Size:</label>\n                <input type=\"number\" id=\"title-size\" min=\"8\" max=\"100\" value=\"18\">\n              </div>\n              <div class=\"style-row\">\n                <label data-lang=\"nodePanel.subColor\">Sub Color:</label>\n                <input type=\"color\" id=\"sub-color\" value=\"#94a3b8\">\n              </div>\n              <div class=\"style-row\">\n                <label data-lang=\"nodePanel.subFont\">Sub Font:</label>\n                <select id=\"sub-font\">\n                  <option value=\"system-ui, sans-serif\" data-lang=\"nodePanel.systemUI\">System UI</option>\n                  <option value=\"monospace\" data-lang=\"ui.fonts.monospace\">Monospace</option>\n                  <option value=\"serif\" data-lang=\"nodePanel.serif\">Serif</option>\n                  <option value=\"cursive\" data-lang=\"nodePanel.cursive\">Cursive</option>\n                  <option value=\"'Courier New', monospace\" data-lang=\"nodePanel.courier\">Courier</option>\n                  <option value=\"Arial, sans-serif\" data-lang=\"ui.fonts.arial\">Arial</option>\n                  <option value=\"'Times New Roman', serif\" data-lang=\"nodePanel.times\">Times</option>\n                </select>\n              </div>\n              <div class=\"style-row\">\n                <label data-lang=\"nodePanel.subSize\">Sub Size:</label>\n                <input type=\"number\" id=\"sub-size\" min=\"6\" max=\"80\" value=\"13\">\n              </div>\n              \n                <div class=\"style-row\">\n                  <label data-lang=\"nodePanel.nameY\">Name Y:</label>\n                  <input type=\"number\" id=\"title-offset-y\" min=\"-100\" max=\"100\" value=\"0\" style=\"width: 60px\">\n                </div>\n                <div class=\"style-row\">\n                  <label data-lang=\"nodePanel.nameX\">Name X:</label>\n                  <input type=\"number\" id=\"title-offset-x\" min=\"-100\" max=\"100\" value=\"0\" style=\"width: 60px\">\n                </div>\n                <div class=\"style-row\">\n                  <label data-lang=\"nodePanel.ipY\">IP Y:</label>\n                  <input type=\"number\" id=\"sub-offset-y\" min=\"-100\" max=\"100\" value=\"0\" style=\"width: 60px\">\n                </div>\n                <div class=\"style-row\">\n                  <label data-lang=\"nodePanel.ipX\">IP X:</label>\n                  <input type=\"number\" id=\"sub-offset-x\" min=\"-100\" max=\"100\" value=\"0\" style=\"width: 60px\">\n                </div>\n                <div class=\"style-row\">\n                  <label data-lang=\"nodePanel.pingX\">Ping X:</label>\n                  <input type=\"number\" id=\"ping-offset-x\" min=\"-200\" max=\"200\" value=\"0\" style=\"width: 60px;\">\n                </div>\n                <div class=\"style-row\">\n                  <label data-lang=\"nodePanel.pingY\">Ping Y:</label>\n                  <input type=\"number\" id=\"ping-offset-y\" min=\"-200\" max=\"200\" value=\"0\" style=\"width: 60px;\">\n                </div>\n            \n              <button id=\"reset-styles\" style=\"width: 100%;margin-top: 10px;padding: 8px;background: var(--panel);color: var(--text-main);border: 1px solid var(--edge-main);border-radius: 6px;cursor: pointer;font-size: clamp(13px, 1.5vw, 17px);\" data-lang=\"nodePanel.resetStyles\">Reset Styles</button>\n            </div>\n          </details>\n          <details id=\"node-ping-section\" class=\"style-section\">\n            <summary data-lang=\"nodePanel.pingStatusMonitoring\">Ping / Status Monitoring</summary>\n            <div class=\"style-content\">\n              <div style=\"display: flex; align-items: center; gap: 8px; margin-bottom: 12px;\">\n              <label class=\"toggle-switch\"><input type=\"checkbox\" id=\"node-pingable\"><span class=\"toggle-slider\"></span></label>\n              <span style=\"font-size: 14px;\" data-lang=\"nodePanel.enableStatusCheck\">Enable status check for this node</span>\n              </div>\n              <div id=\"node-ping-options\" style=\"display: block;\">\n                <div class=\"style-row\">\n                  <label data-lang=\"nodePanel.protocol\">Protocol:</label>\n                  <select id=\"node-ping-protocol\">\n                    <option value=\"http\" data-lang=\"nodePanel.httpPort80\">HTTP (port 80)</option>\n                    <option value=\"https\" data-lang=\"nodePanel.httpsPort443\">HTTPS (port 443)</option>\n                    <option value=\"custom\" data-lang=\"nodePanel.customUrl\">Custom URL</option>\n                  </select>\n                </div>\n                <div id=\"node-custom-url-container\" style=\"display: block; margin-top: 8px;\">\n                  <label style=\"display: block; margin-bottom: 4px; font-size: 13px;\" data-lang=\"nodePanel.customUrlLabel\">Custom URL:</label>\n                  <input type=\"text\" id=\"node-custom-url\" placeholder=\"e.g. http://192.168.1.1:8080\" style=\"width: 100%;padding: 8px 12px;background: var(--panel);border: 1px solid var(--edge-main);border-radius: 6px;color: var(--text-main);font-size: 14px;\">\n                </div>\n                <div class=\"style-row\" style=\"margin-top: 8px;\">\n                  <label data-lang=\"nodePanel.timeoutMs\">Timeout (ms):</label>\n                  <input type=\"number\" id=\"node-ping-timeout\" value=\"3000\" min=\"1000\" max=\"10000\" step=\"500\" style=\"width: 100px;\">\n                </div>\n                <div style=\"margin-top: 12px; padding: 10px; background: var(--panel); border-radius: 6px; border: 1px solid var(--edge-main);\">\n                  <div style=\"font-size: 12px; color: var(--text-soft); margin-bottom: 8px;\" data-lang=\"nodePanel.currentStatus\">Current Status:</div>\n                  <div id=\"node-ping-status\" style=\"font-size: 14px; font-weight: 600; color: var(--accent);\">● Online</div>\n                  <div id=\"node-ping-last-check\" style=\"font-size: 11px; color: var(--text-soft); margin-top: 4px;\" data-lang=\"nodePanel.lastChecked\">Last checked: 2:25:19 PM</div>\n                </div>\n                <button id=\"check-ping-now\" style=\"width: 100%;margin-top: 10px;padding: 8px;background: var(--accent);color: var(--bg);border: none;border-radius: 6px;cursor: pointer;font-size: 14px;font-weight: 600;\" data-lang=\"nodePanel.checkStatusNow\">Check Status Now</button>\n              </div>\n            </div>\n          </details>\n          <div class=\"section-label\" data-lang=\"ui.sections.notes\">Notes</div>\n          <div class=\"notes-feed\" id=\"node-notes\"></div>\n          <button id=\"add-note-btn\" style=\"margin-top: 10px;padding: 8px 16px;background: var(--accent);color: var(--bg);border: none;border-radius: 6px;cursor: pointer;font-size: clamp(14px, 1.6vw, 18px);font-weight: 600;width: 100%;\" data-lang=\"ui.buttons.addNote\">+ Add Note</button>\n          <button id=\"delete-node-btn\" style=\"margin-top: 15px;padding: 10px 16px;background: var(--danger);color: rgb(255, 255, 255);border: none;border-radius: 6px;cursor: pointer;font-size: clamp(14px, 1.6vw, 18px);font-weight: 600;width: 100%;\" data-lang=\"ui.buttons.deleteNode\">Delete Node</button>\n        </div>\n        <div id=\"edge-panel\" style=\"display: none\">\n          <div class=\"details-name\" id=\"edge-title\" data-lang=\"edgePanel.customLine\">Custom line</div>\n          <div class=\"style-row\" style=\"margin-top: 10px\">\n            <label data-lang=\"ui.labels.width\">Width:</label>\n            <input type=\"number\" id=\"edge-width\" min=\"1\" max=\"20\" value=\"4\">\n          </div>\n          <div class=\"style-row\">\n            <label data-lang=\"ui.labels.color\">Color:</label>\n            <input type=\"color\" id=\"edge-color\" value=\"#475569\">\n          </div>\n          <div class=\"style-row\">\n            <label data-lang=\"ui.labels.style\">Line Style:</label>\n            <select id=\"edge-line-style\">\n              <option value=\"solid\" data-lang=\"ui.options.solid\">Solid</option>\n              <option value=\"dashed\" data-lang=\"ui.options.dashed\">Dashed</option>\n              <option value=\"dotted\" data-lang=\"ui.options.dotted\">Dotted</option>\n              <option value=\"wall\" data-lang=\"drawToolbar.wall\">Wall</option>\n            </select>\n          </div>\n          <div class=\"style-row\">\n            <label data-lang=\"ui.labels.routing\">Routing:</label>\n            <select id=\"edge-routing\">\n              <option value=\"orthogonal\" data-lang=\"ui.options.orthogonal\">Orthogonal (90°)</option>\n              <option value=\"curved\" data-lang=\"ui.options.curved\">Curved</option>\n              <option value=\"straight\" data-lang=\"ui.options.straight\">Straight</option>\n            </select>\n          </div>\n          <div class=\"style-row\" id=\"waypoints-row\" style=\"display: none;\">\n            <label id=\"waypoints-label\" data-lang=\"ui.labels.waypoints\">Waypoints</label>\n            <button id=\"clear-waypoints-btn\" style=\"padding: 6px 12px;background: var(--panel);border: 1px solid var(--edge-main);border-radius: 6px;cursor: pointer;font-size: 14px;color: var(--text-main);\" data-lang=\"ui.buttons.clearAll\">Clear All</button>\n          </div>\n          <div class=\"style-row\">\n            <label data-lang=\"ui.labels.direction\">Direction:</label>\n            <select id=\"edge-direction\">\n              <option value=\"none\" data-lang=\"topologyToolbar.noArrows\">No arrows</option>\n              <option value=\"forward\" data-lang=\"topologyToolbar.forward\">→ Forward</option>\n              <option value=\"backward\" data-lang=\"topologyToolbar.backward\">← Backward</option>\n              <option value=\"both\" data-lang=\"topologyToolbar.bidirectional\">↔ Bidirectional</option>\n            </select>\n          </div>\n          <div class=\"style-row\">\n            <label data-lang=\"edgePanel.animate\">Animate:</label>\n            <label class=\"toggle-switch\"><input type=\"checkbox\" id=\"edge-animate\"><span class=\"toggle-slider\"></span></label>\n          </div>\n          <div class=\"style-row\">\n            <label data-lang=\"ui.labels.style\">Style:</label>\n            <select id=\"edge-animation-style\">\n              <option value=\"\" data-lang=\"ui.options.default\">Default</option>\n              <option value=\"arrows\" data-lang=\"ui.options.flowingArrows\">Flowing Arrows</option>\n              <option value=\"dots\" data-lang=\"ui.options.dotArrows\">Dot Arrows</option>\n            </select>\n          </div>\n          <div class=\"style-row\">\n            <label data-lang=\"ui.labels.animationSpeed\">Speed:</label>\n            <select id=\"edge-animation-speed\">\n              <option value=\"\" data-lang=\"ui.options.default\">Default</option>\n              <option value=\"0.5\" data-lang=\"ui.options.veryFast\">Very Fast</option>\n              <option value=\"1\" data-lang=\"ui.options.fast\">Fast</option>\n              <option value=\"1.5\" data-lang=\"ui.options.normal\">Normal</option>\n              <option value=\"2.5\" data-lang=\"ui.options.slow\">Slow</option>\n              <option value=\"4\" data-lang=\"ui.options.verySlow\">Very Slow</option>\n            </select>\n          </div>\n          <div class=\"style-row\" id=\"edge-port-fields\">\n            <label data-lang=\"ui.labels.fromPort\">From Port:</label>\n            <input type=\"text\" id=\"edge-from-port\" placeholder=\"e.g. eth0, gi0/1\" data-lang=\"ui.placeholders.fromPort\" data-lang-attr=\"placeholder\" style=\"flex: 1;padding: 6px 10px;background: var(--panel);border: 1px solid var(--edge-main);border-radius: 4px;color: var(--text-main);font-size: 14px;\">\n          </div>\n          <div class=\"style-row\" id=\"edge-port-fields-to\">\n            <label data-lang=\"ui.labels.toPort\">To Port:</label>\n            <input type=\"text\" id=\"edge-to-port\" placeholder=\"e.g. eth1, gi0/2\" data-lang=\"ui.placeholders.toPort\" data-lang-attr=\"placeholder\" style=\"flex: 1;padding: 6px 10px;background: var(--panel);border: 1px solid var(--edge-main);border-radius: 4px;color: var(--text-main);font-size: 14px;\">\n          </div>\n          <div class=\"section-label\" data-lang=\"ui.labels.lineNotes\">Line Notes</div>\n          <div class=\"notes-feed\" id=\"edge-notes\"></div>\n          <button id=\"add-edge-note\" style=\"margin-top: 10px;padding: 8px 16px;background: var(--accent);color: var(--bg);border: none;border-radius: 6px;cursor: pointer;font-size: clamp(14px, 1.6vw, 18px);font-weight: 600;width: 100%;\" data-lang=\"ui.buttons.addNote\">+ Add Note</button>\n          <button id=\"delete-edge\" style=\"margin-top: 15px;padding: 10px 16px;background: var(--danger);color: #fff;border: none;border-radius: 6px;cursor: pointer;font-size: clamp(14px, 1.6vw, 18px);font-weight: 600;\" data-lang=\"ui.buttons.delete\">Delete Line</button>\n        </div>\n        <div id=\"rect-panel\" style=\"display: none\">\n          <div class=\"details-name\" id=\"rect-title\" data-lang=\"rectPanel.zone\">Zone</div>\n          <div class=\"style-row\" id=\"rect-fill-row\" style=\"margin-top: 10px\">\n            <label data-lang=\"rectPanel.fillColor\">Fill Color:</label>\n            <input type=\"color\" id=\"rect-color\" value=\"#f97316\">\n          </div>\n          <div class=\"style-row\" id=\"rect-opacity-row\">\n            <label data-lang=\"rectPanel.fillOpacity\">Fill Opacity:</label>\n            <input type=\"range\" id=\"rect-fill-opacity\" min=\"0\" max=\"100\" value=\"30\" style=\"flex: 1;\">\n            <span id=\"rect-fill-opacity-value\" style=\"min-width: 40px; text-align: right; color: var(--text-soft);\">30%</span>\n          </div>\n          <div class=\"style-row\">\n            <label data-lang=\"rectPanel.borderColor\">Border Color:</label>\n            <input type=\"color\" id=\"rect-border-color\" value=\"#f97316\">\n          </div>\n          <div class=\"style-row\">\n            <label data-lang=\"rectPanel.borderWidth\">Border Width:</label>\n            <input type=\"number\" id=\"rect-border-width\" min=\"0\" max=\"20\" value=\"2\">\n          </div>\n          <div class=\"style-row\">\n            <label data-lang=\"ui.labels.style\">Style:</label>\n            <select id=\"rect-style-select\">\n              <option value=\"filled\" data-lang=\"drawToolbar.filled\">Filled</option>\n              <option value=\"outlined\" data-lang=\"drawToolbar.outlined\">Outlined</option>\n            </select>\n          </div>\n\t\t  <div class=\"style-row\">\n            <label data-lang=\"rectPanel.lineStyle\">Line Style:</label>\n            <select id=\"rect-line-style\">\n              <option value=\"solid\" data-lang=\"ui.options.solid\">Solid</option>\n              <option value=\"dashed\" data-lang=\"ui.options.dashed\">Dashed</option>\n              <option value=\"dotted\" data-lang=\"ui.options.dotted\">Dotted</option>\n              <option value=\"wall\" data-lang=\"drawToolbar.wall\">Wall</option>\n            </select>\n          </div>\n          <div class=\"style-row\">\n            <label data-lang=\"ui.labels.rotation\">Rotation:</label>\n            <input type=\"range\" id=\"rect-rotation\" min=\"-360\" max=\"360\" value=\"0\" style=\"flex: 1;\">\n            <input type=\"number\" id=\"rect-rotation-value\" value=\"0\" style=\"width: 60px; padding: 4px; background: var(--panel); border: 1px solid var(--edge-main); border-radius: 4px; color: var(--text-main); text-align: center;\">\n            <span style=\"color: var(--text-soft);\">°</span>\n          </div>\n          <div class=\"section-label\" data-lang=\"ui.labels.zoneNotes\">Zone Notes</div>\n          <div class=\"notes-feed\" id=\"rect-notes\"></div>\n          <button id=\"add-rect-note\" style=\"margin-top: 10px;padding: 8px 16px;background: var(--accent);color: var(--bg);border: none;border-radius: 6px;cursor: pointer;font-size: clamp(14px, 1.6vw, 18px);font-weight: 600;width: 100%;\" data-lang=\"ui.buttons.addNote\">+ Add Note</button>\n          <button id=\"delete-rect\" style=\"margin-top: 15px;padding: 10px 16px;background: var(--danger);color: #fff;border: none;border-radius: 6px;cursor: pointer;font-size: clamp(14px, 1.6vw, 18px);font-weight: 600;\" data-lang=\"ui.buttons.delete\">Delete Zone</button>\n        </div>\n        <div id=\"text-panel\" style=\"display: none\">\n          <div class=\"details-name\" id=\"text-title\" data-lang=\"textPanel.textElement\">Text Element</div>\n          <div class=\"style-row\" style=\"margin-top: 10px\">\n            <label data-lang=\"textPanel.content\">Content:</label>\n            <textarea id=\"text-content\" placeholder=\"Enter text...\" data-lang=\"ui.placeholders.enterText\" data-lang-attr=\"placeholder\" style=\"width: 100%;padding: 8px 12px;background: var(--panel);border: 1px solid var(--edge-main);border-radius: 6px;color: var(--text-main);font-size: 14px;min-height: 80px;resize: vertical;font-family: inherit;\"></textarea>\n          </div>\n          <div class=\"style-row\">\n            <label data-lang=\"textPanel.fontSizeLabel\">Font Size:</label>\n            <input type=\"number\" id=\"text-font-size\" min=\"8\" max=\"200\" value=\"18\" style=\"flex: 1;padding: 6px 10px;background: var(--panel);border: 1px solid var(--edge-main);border-radius: 4px;color: var(--text-main);\">\n          </div>\n          <div class=\"style-row\">\n            <label data-lang=\"textPanel.colorLabel\">Color:</label>\n            <input type=\"color\" id=\"text-color\" value=\"#e2e8f0\">\n          </div>\n          <div class=\"style-row\">\n            <label data-lang=\"textPanel.fontWeight\">Font Weight:</label>\n            <select id=\"text-font-weight\" style=\"flex: 1;padding: 6px 10px;background: var(--panel);border: 1px solid var(--edge-main);border-radius: 4px;color: var(--text-main);cursor: pointer;\">\n              <option value=\"normal\" data-lang=\"ui.options.normal\">Normal</option>\n              <option value=\"bold\" data-lang=\"textPanel.bold\">Bold</option>\n              <option value=\"600\" data-lang=\"textPanel.semiBold\">Semi-Bold</option>\n              <option value=\"300\" data-lang=\"textPanel.light\">Light</option>\n            </select>\n          </div>\n          <div class=\"style-row\">\n            <label data-lang=\"textPanel.fontStyle\">Font Style:</label>\n            <select id=\"text-font-style\" style=\"flex: 1;padding: 6px 10px;background: var(--panel);border: 1px solid var(--edge-main);border-radius: 4px;color: var(--text-main);cursor: pointer;\">\n              <option value=\"normal\" data-lang=\"ui.options.normal\">Normal</option>\n              <option value=\"italic\" data-lang=\"textPanel.italic\">Italic</option>\n            </select>\n          </div>\n          <div class=\"style-row\">\n            <label data-lang=\"textPanel.textAlign\">Text Align:</label>\n            <select id=\"text-align\" style=\"flex: 1;padding: 6px 10px;background: var(--panel);border: 1px solid var(--edge-main);border-radius: 4px;color: var(--text-main);cursor: pointer;\">\n              <option value=\"start\" data-lang=\"ui.options.left\">Left</option>\n              <option value=\"middle\" data-lang=\"ui.options.center\">Center</option>\n              <option value=\"end\" data-lang=\"ui.options.right\">Right</option>\n            </select>\n          </div>\n          <div class=\"style-row\">\n            <label data-lang=\"textPanel.textDecoration\">Text Decoration:</label>\n            <select id=\"text-decoration\" style=\"flex: 1;padding: 6px 10px;background: var(--panel);border: 1px solid var(--edge-main);border-radius: 4px;color: var(--text-main);cursor: pointer;\">\n              <option value=\"none\" data-lang=\"ui.options.none\">None</option>\n              <option value=\"underline\" data-lang=\"textPanel.underline\">Underline</option>\n              <option value=\"line-through\" data-lang=\"textPanel.strikeThrough\">Strike Through</option>\n            </select>\n          </div>\n          <div class=\"style-row\">\n            <label data-lang=\"ui.labels.background\">Background:</label>\n            <input type=\"color\" id=\"text-bg-color\" value=\"#000000\">\n            <div style=\"margin-left: 10px; display: flex; align-items: center; gap: 6px;\">\n            <label class=\"toggle-switch\"><input type=\"checkbox\" id=\"text-bg-enabled\"><span class=\"toggle-slider\"></span></label>\n            <span style=\"font-size: 13px;\" data-lang=\"textPanel.enable\">Enable</span>\n            </div>\n          </div>\n          <div class=\"style-row\">\n            <label data-lang=\"ui.labels.opacity\">Opacity:</label>\n            <input type=\"range\" id=\"text-opacity\" min=\"0\" max=\"1\" step=\"0.1\" value=\"1\" style=\"flex: 1\">\n            <span id=\"text-opacity-val\" style=\"min-width: 35px; text-align: right\">100%</span>\n          </div>\n          <div class=\"style-row\">\n            <label data-lang=\"ui.labels.rotation\">Rotation:</label>\n            <input type=\"range\" id=\"text-rotation\" min=\"-360\" max=\"360\" value=\"0\" style=\"flex: 1\">\n            <input type=\"number\" id=\"text-rotation-val\" value=\"0\" style=\"width: 60px; padding: 4px; background: var(--panel); border: 1px solid var(--edge-main); border-radius: 4px; color: var(--text-main); text-align: center;\">\n            <span style=\"color: var(--text-soft);\">°</span>\n          </div>\n          <button id=\"delete-text\" style=\"margin-top: 15px;padding: 10px 16px;background: var(--danger);color: #fff;border: none;border-radius: 6px;cursor: pointer;font-size: clamp(14px, 1.6vw, 18px);font-weight: 600;width: 100%;\" data-lang=\"textPanel.deleteText\">Delete Text</button>\n        </div>\n        <div id=\"image-panel\" style=\"display: none\">\n          <div class=\"details-name editable-text\" id=\"image-title\">Image</div>\n          <div class=\"style-row\" style=\"margin-top: 10px\">\n            <label data-lang=\"images.opacity\">Opacity:</label>\n            <input type=\"range\" id=\"image-panel-opacity\" min=\"10\" max=\"100\" value=\"100\" style=\"flex: 1\">\n            <span id=\"image-panel-opacity-val\" style=\"min-width: 40px; text-align: right\">100%</span>\n          </div>\n          <div class=\"style-row\">\n            <label data-lang=\"images.size\">Size:</label>\n            <input type=\"range\" id=\"image-panel-size\" min=\"20\" max=\"200\" value=\"100\" style=\"flex: 1\">\n            <span id=\"image-panel-size-val\" style=\"min-width: 40px; text-align: right\">100%</span>\n          </div>\n          <div class=\"style-row\">\n            <label data-lang=\"images.border\">Border:</label>\n            <select id=\"image-panel-border\" style=\"flex: 1;padding: 6px 10px;background: var(--panel);border: 1px solid var(--edge-main);border-radius: 4px;color: var(--text-main);cursor: pointer;\">\n              <option value=\"none\" data-lang=\"images.none\">None</option>\n              <option value=\"thin\" data-lang=\"images.thin\">Thin</option>\n              <option value=\"medium\" data-lang=\"images.medium\">Medium</option>\n              <option value=\"thick\" data-lang=\"images.thick\">Thick</option>\n            </select>\n          </div>\n          <div class=\"style-row\">\n            <label data-lang=\"images.shadow\">Shadow:</label>\n            <select id=\"image-panel-shadow\" style=\"flex: 1;padding: 6px 10px;background: var(--panel);border: 1px solid var(--edge-main);border-radius: 4px;color: var(--text-main);cursor: pointer;\">\n              <option value=\"none\" data-lang=\"images.none\">None</option>\n              <option value=\"small\" data-lang=\"images.small\">Small</option>\n              <option value=\"medium\" data-lang=\"images.medium\">Medium</option>\n              <option value=\"large\" data-lang=\"images.large\">Large</option>\n            </select>\n          </div>\n          <div class=\"section-label\" data-lang=\"images.imageNotes\">Image Notes</div>\n          <div class=\"notes-feed\" id=\"image-notes\"></div>\n          <button id=\"add-image-note\" style=\"margin-top: 10px;padding: 8px 16px;background: var(--accent);color: var(--bg);border: none;border-radius: 6px;cursor: pointer;font-size: clamp(14px, 1.6vw, 18px);font-weight: 600;width: 100%;\" data-lang=\"ui.buttons.addNote\">+ Add Note</button>\n          <button id=\"delete-image-panel\" style=\"margin-top: 15px;padding: 10px 16px;background: var(--danger);color: #fff;border: none;border-radius: 6px;cursor: pointer;font-size: clamp(14px, 1.6vw, 18px);font-weight: 600;\" data-lang=\"images.delete\">Delete Image</button>\n        </div>\n     <details class=\"style-section\" open=\"\">\n          <summary data-lang=\"ui.sections.pageLayout\">Page Layout</summary>\n          <div class=\"style-content\">\n            <div style=\"padding: 12px; background: var(--panel-alt); border-radius: 6px; color: var(--text-soft); font-size: 13px; line-height: 1.6;\">\n              <strong style=\"color: var(--accent); display: block; margin-bottom: 8px;\" data-lang=\"pageLayoutHelp.dragToResize\">Drag to Resize:</strong>\n              • <strong data-lang=\"pageLayoutHelp.header\">Header:</strong> <span data-lang=\"pageLayoutHelp.headerDesc\">Drag the bottom edge of the header bar</span><br>\n              • <strong data-lang=\"pageLayoutHelp.sidebar\">Sidebar:</strong> <span data-lang=\"pageLayoutHelp.sidebarDesc\">Drag the left edge of the sidebar panel</span><br>\n              • <strong data-lang=\"pageLayoutHelp.mobile\">Mobile:</strong> <span data-lang=\"pageLayoutHelp.mobileDesc\">Drag the top edge of the footer panel</span><br>\n              <div style=\"margin-top: 8px; padding-top: 8px; border-top: 1px solid var(--edge-main); font-size: 12px;\" data-lang=\"pageLayoutHelp.hoverNote\">\n                Hover over panel edges to see resize handles\n              </div>\n            </div>\n          </div>\n        </details>\n      </aside>\n    </main>\n    <script id=\"nodes-json\" type=\"application/json\">{}</script>\n    <script id=\"labels-json\" type=\"application/json\">{\n  \"mappingModes\": {\n    \"network\": { \"name\": \"Network Topology\", \"node\": \"Node\", \"nodes\": \"Nodes\", \"connection\": \"Connection\", \"connections\": \"Connections\", \"add\": \"Add Node\", \"addRack\": \"Add Rack\" },\n    \"mindmap\": { \"name\": \"Mind Map\", \"node\": \"Idea\", \"nodes\": \"Ideas\", \"connection\": \"Link\", \"connections\": \"Links\", \"add\": \"Add Idea\", \"addRack\": \"Add Group\" },\n    \"sports\": { \"name\": \"Sports Playbook\", \"node\": \"Player\", \"nodes\": \"Players\", \"connection\": \"Route\", \"connections\": \"Routes\", \"add\": \"Add Player\", \"addRack\": \"Add Formation\" },\n    \"smarthome\": { \"name\": \"Smart Home\", \"node\": \"Device\", \"nodes\": \"Devices\", \"connection\": \"Connection\", \"connections\": \"Connections\", \"add\": \"Add Device\", \"addRack\": \"Add Zone\" },\n    \"floorplan\": { \"name\": \"Floor Plan\", \"node\": \"Element\", \"nodes\": \"Elements\", \"connection\": \"Connection\", \"connections\": \"Connections\", \"add\": \"Add Element\", \"addRack\": \"Add Room\" }\n  },\n  \"canvasTemplates\": {\n    \"grid\": { \"name\": \"Standard Grid\" },\n    \"dots\": { \"name\": \"Dot Grid\" },\n    \"blueprint\": { \"name\": \"Blueprint Grid\" },\n    \"basketball\": { \"name\": \"Basketball Court\" },\n    \"football\": { \"name\": \"Football Field\" },\n    \"soccer\": { \"name\": \"Soccer Pitch\" },\n    \"hockey\": { \"name\": \"Hockey Rink\" },\n    \"baseball\": { \"name\": \"Baseball Diamond\" },\n    \"tennis\": { \"name\": \"Tennis Court\" },\n    \"none\": { \"name\": \"No Grid\" }\n  },\n  \"defaultShapeCategories\": {\n    \"network\": \"Network Equipment\",\n    \"mindmap\": \"Basic Shapes\",\n    \"sports\": \"Basic Shapes\",\n    \"smarthome\": \"Smart Home\",\n    \"floorplan\": \"Basic Shapes\"\n  }\n}</script>\n    <script id=\"lang-json\" type=\"application/json\">{\n  \"_meta\": {\n    \"name\": \"English\",\n    \"code\": \"en\",\n    \"version\": \"1.0\",\n    \"rtl\": false,\n    \"edition\": \"networkening\"\n  },\n  \"modes\": {\n    \"network\": { \"name\": \"Network Topology\", \"node\": \"Node\", \"nodes\": \"Nodes\", \"connection\": \"Connection\", \"connections\": \"Connections\", \"add\": \"Add Node\", \"addRack\": \"Add Rack\", \"rack\": \"Rack\", \"racks\": \"Racks\", \"subtitle\": \"IP / Subtitle\", \"subtitlePlaceholder\": \"e.g. 192.168.1.100\", \"tags\": \"Tags\", \"tagsPlaceholder\": \"e.g. Docker, nginx, WG: vpn\", \"rackSubtitle\": \"IP / Network Range\", \"rackSubtitlePlaceholder\": \"e.g. 192.168.1.0/24\", \"rackTags\": \"Tags\", \"rackTagsPlaceholder\": \"e.g. Production, Data Center 1\" },\n    \"mindmap\": { \"name\": \"Mind Map\", \"node\": \"Idea\", \"nodes\": \"Ideas\", \"connection\": \"Link\", \"connections\": \"Links\", \"add\": \"Add Idea\", \"addRack\": \"Add Folder\", \"rack\": \"Folder\", \"racks\": \"Folders\", \"subtitle\": \"Description\", \"subtitlePlaceholder\": \"e.g. Main concept\", \"tags\": \"Keywords\", \"tagsPlaceholder\": \"e.g. important, research, todo\", \"rackSubtitle\": \"Description\", \"rackSubtitlePlaceholder\": \"e.g. Folder description\", \"rackTags\": \"Keywords\", \"rackTagsPlaceholder\": \"e.g. category, priority\" },\n    \"sports\": { \"name\": \"Sports Playbook\", \"node\": \"Player\", \"nodes\": \"Players\", \"connection\": \"Route\", \"connections\": \"Routes\", \"add\": \"Add Player\", \"addRack\": \"Add Folder\", \"rack\": \"Folder\", \"racks\": \"Folders\", \"subtitle\": \"Position / Number\", \"subtitlePlaceholder\": \"e.g. QB, #12\", \"tags\": \"Attributes\", \"tagsPlaceholder\": \"e.g. offense, starter, captain\", \"rackSubtitle\": \"Description\", \"rackSubtitlePlaceholder\": \"e.g. Formation name\", \"rackTags\": \"Attributes\", \"rackTagsPlaceholder\": \"e.g. offense, defense\" },\n    \"smarthome\": { \"name\": \"Smart Home\", \"node\": \"Device\", \"nodes\": \"Devices\", \"connection\": \"Connection\", \"connections\": \"Connections\", \"add\": \"Add Device\", \"addRack\": \"Add Room View\", \"rack\": \"Room View\", \"racks\": \"Room Views\", \"subtitle\": \"Model / Location\", \"subtitlePlaceholder\": \"e.g. Nest Thermostat, Living Room\", \"tags\": \"Groups\", \"tagsPlaceholder\": \"e.g. lighting, security, automation\", \"rackSubtitle\": \"Description\", \"rackSubtitlePlaceholder\": \"e.g. Room description\", \"rackTags\": \"Groups\", \"rackTagsPlaceholder\": \"e.g. upstairs, main floor\" },\n    \"floorplan\": { \"name\": \"Floor Plan\", \"node\": \"Element\", \"nodes\": \"Elements\", \"connection\": \"Connection\", \"connections\": \"Connections\", \"add\": \"Add Element\", \"addRack\": \"Add Room View\", \"rack\": \"Room View\", \"racks\": \"Room Views\", \"subtitle\": \"Label / Dimensions\", \"subtitlePlaceholder\": \"e.g. 12x14 ft\", \"tags\": \"Categories\", \"tagsPlaceholder\": \"e.g. furniture, fixture, wall\", \"rackSubtitle\": \"Description\", \"rackSubtitlePlaceholder\": \"e.g. Room dimensions\", \"rackTags\": \"Categories\", \"rackTagsPlaceholder\": \"e.g. bedroom, bathroom\" }\n  },\n  \"canvas\": {\n    \"grid\": \"Standard Grid\",\n    \"dots\": \"Dot Grid\",\n    \"blueprint\": \"Blueprint Grid\",\n    \"basketball\": \"Basketball Court\",\n    \"football\": \"Football Field\",\n    \"soccer\": \"Soccer Pitch\",\n    \"hockey\": \"Hockey Rink\",\n    \"baseball\": \"Baseball Diamond\",\n    \"tennis\": \"Tennis Court\",\n    \"none\": \"No Grid\"\n  },\n  \"ping\": {\n    \"httpPort80\": \"HTTP (port 80) uses IP field\",\n    \"httpsPort443\": \"HTTPS (port 443) uses IP field\",\n    \"customUrl\": \"Custom URL\"\n  },\n  \"shapes\": {\n    \"categories\": {\n      \"basic\": \"Basic Shapes\",\n      \"computers\": \"Computers & Devices\",\n      \"network\": \"Network Equipment\",\n      \"cloud\": \"Cloud & Services\",\n      \"security\": \"Security & Monitoring\",\n      \"smarthome\": \"Smart Home\",\n      \"sports\": \"Sports\",\n      \"rack\": \"Rack Equipment\"\n    },\n    \"items\": {\n      \"circle\": \"Circle\", \"square\": \"Square\", \"rectangle\": \"Rectangle\", \"triangle\": \"Triangle\",\n      \"hexagon\": \"Hexagon\", \"diamond\": \"Diamond\", \"star\": \"Star\", \"stop-sign\": \"Stop Sign\",\n      \"octagon\": \"Octagon\", \"pentagon\": \"Pentagon\", \"cross\": \"Cross\", \"rounded-square\": \"Rounded Square\",\n      \"pill\": \"Pill\", \"parallelogram\": \"Parallelogram\", \"trapezoid\": \"Trapezoid\",\n      \"server\": \"Server\", \"pc\": \"PC / Desktop\", \"laptop\": \"Laptop\", \"phone\": \"Phone / Mobile\",\n      \"printer\": \"Printer\", \"pi\": \"Raspberry Pi\", \"sensor\": \"Sensor / IoT\",\n      \"router\": \"Router\", \"switch\": \"Switch\", \"firewall\": \"Firewall\", \"access-point\": \"Access Point\",\n      \"load-balancer\": \"Load Balancer\", \"gateway\": \"Gateway\", \"vpn\": \"VPN / Tunnel\", \"nas\": \"NAS / Storage\",\n      \"cloud\": \"Cloud\", \"database\": \"Database\", \"docker\": \"Docker\", \"container\": \"Container\",\n      \"vm\": \"Virtual Machine\", \"kubernetes\": \"Kubernetes\", \"api\": \"API / Endpoint\",\n      \"queue\": \"Queue / Message\", \"lambda\": \"Lambda / Function\", \"bucket\": \"Bucket / S3\",\n      \"shield\": \"Shield\", \"camera\": \"Camera / CCTV\", \"monitor\": \"Monitor / Dashboard\",\n      \"thermostat\": \"Thermostat\", \"doorbell\": \"Video Doorbell\", \"smart-lock\": \"Smart Lock\",\n      \"smart-bulb\": \"Smart Bulb\", \"smart-plug\": \"Smart Plug\", \"smart-speaker\": \"Smart Speaker\",\n      \"smart-tv\": \"Smart TV\", \"hub\": \"Smart Hub\", \"smoke-detector\": \"Smoke Detector\",\n      \"motion-sensor\": \"Motion Sensor\", \"garage\": \"Garage Door\", \"sprinkler\": \"Sprinkler\", \"vacuum\": \"Robot Vacuum\",\n      \"basketball-ball\": \"Basketball\", \"football-ball\": \"Football\", \"soccer-ball\": \"Soccer Ball\",\n      \"hockey-puck\": \"Hockey Puck\", \"baseball-ball\": \"Baseball\", \"tennis-ball\": \"Tennis Ball\",\n      \"volleyball\": \"Volleyball\", \"rugby-ball\": \"Rugby Ball\", \"golf-ball\": \"Golf Ball\",\n      \"frisbee\": \"Frisbee\", \"cricket-ball\": \"Cricket Ball\", \"lacrosse-stick\": \"Lacrosse Stick\",\n      \"golf-flag\": \"Golf Hole Flag\",\n      \"tactical-x\": \"X (Defender)\", \"tactical-o\": \"O (Offensive)\",\n      \"tactical-star\": \"Star (Key Player)\",\n      \"patch-panel\": \"Patch Panel\", \"ups\": \"UPS\", \"pdu\": \"PDU\", \"rack-shelf\": \"Shelf\",\n      \"blank-panel\": \"Blank Panel\", \"cable-management\": \"Cable Management\", \"kvm\": \"KVM\"\n    }\n  },\n  \"ui\": {\n    \"defaults\": {\n      \"newNode\": \"New {node}\",\n      \"newRack\": \"New {rack}\",\n      \"newText\": \"New Text\"\n    },\n    \"buttons\": {\n      \"save\": \"Save\", \"cancel\": \"Cancel\", \"delete\": \"Delete\", \"close\": \"Close\",\n      \"add\": \"Add\", \"edit\": \"Edit\", \"undo\": \"Undo\", \"redo\": \"Redo\",\n      \"import\": \"Import\", \"export\": \"Export\", \"settings\": \"Settings\",\n      \"startMapping\": \"Start Mapping\", \"skip\": \"Skip\", \"apply\": \"Apply\",\n      \"clearAll\": \"Clear All\", \"reset\": \"Reset\", \"saveChanges\": \"Save Changes\",\n      \"addNode\": \"Add {node}\", \"addRack\": \"Add {rack}\", \"editNode\": \"Edit {node}\",\n      \"deleteNode\": \"Delete {node}\", \"exportLang\": \"Export Language File\",\n      \"importLang\": \"Import Language File\", \"resetLang\": \"Reset to English\",\n      \"editStrings\": \"Edit Strings\", \"applyRoutingAll\": \"Apply Routing to All Connections\",\n      \"saveHtml\": \"Save HTML\", \"saveFile\": \"Save File\", \"printBW\": \"Print B&W\",\n      \"printColorFull\": \"Print Full Color\", \"printColorWhite\": \"Print White BG\", \"printColorBlack\": \"Print Black BG\",\n      \"exportJson\": \"Export JSON\", \"exportCsv\": \"Export CSV\", \"exportMd\": \"Export Markdown\",\n      \"exportPng\": \"Export PNG\", \"exportSvg\": \"Export SVG\", \"exportVideo\": \"Export Video\",\n      \"importJson\": \"Import JSON\", \"importMd\": \"Import Markdown\",\n      \"exportHelp\": \"Export Help\", \"importHelp\": \"Import Help\",\n      \"layers\": \"Layers\", \"notes\": \"Notes\", \"auditLog\": \"Audit Log\", \"portMap\": \"Port Map\",\n      \"hideAll\": \"Hide All\", \"showAll\": \"Show All\", \"clearLog\": \"Clear Log\",\n      \"addNote\": \"+ Add Note\", \"addTab\": \"+ Add Tab\", \"addTag\": \"+ Add Tag\", \"encrypt\": \"Encrypt\", \"decrypt\": \"Decrypt\",\n      \"recording\": \"Recording\", \"play\": \"Play\", \"pause\": \"Pause\", \"stop\": \"Stop\",\n      \"drawMode\": \"Draw\", \"textMode\": \"Text\", \"rectMode\": \"Rect\", \"freeDrawMode\": \"Draw\",\n      \"zoomIn\": \"Zoom In\", \"zoomOut\": \"Zoom Out\", \"fitView\": \"Fit View\", \"resetView\": \"Reset View\",\n      \"group\": \"Group\", \"ungroup\": \"Ungroup\", \"bringToFront\": \"Bring to Front\", \"sendToBack\": \"Send to Back\",\n      \"duplicate\": \"Duplicate\", \"selectAll\": \"Select All\", \"deselectAll\": \"Deselect All\",\n      \"copyStyle\": \"Copy Style\", \"pasteStyle\": \"Paste Style\",\n      \"clearHistory\": \"Clear History\", \"createSnapshot\": \"Create Snapshot\",\n      \"del\": \"Del\", \"done\": \"Done\", \"pngImage\": \"PNG Image\", \"svgVector\": \"SVG Vector\",\n      \"html\": \"HTML\", \"json\": \"JSON\", \"markdown\": \"Markdown\", \"csv\": \"CSV\"\n    },\n    \"labels\": {\n      \"name\": \"Name\", \"ip\": \"IP / Subtitle\", \"tags\": \"Tags\", \"category\": \"Category\",\n      \"addIconTag\": \"Add Web Icon Tag\", \"iconTags\": \"Icon Tags:\", \"searchWebIcons\": \"Or search web icons\",\n      \"selectedIcon\": \"Selected Icon:\", \"enablePing\": \"Enable ping/status check for this {node}\",\n      \"protocol\": \"Protocol\", \"customUrl\": \"Custom URL\", \"timeoutMs\": \"Timeout (ms)\",\n      \"rackCapacity\": \"Rack Capacity\", \"u42Standard\": \"42U (Standard Full Rack)\",\n      \"u48Large\": \"48U (Large Rack)\", \"u24Half\": \"24U (Half Rack)\",\n      \"u12Small\": \"12U (Small/Wall Mount)\", \"u6Mini\": \"6U (Mini Rack)\",\n      \"shape\": \"Shape\", \"icon\": \"Icon / Shape\", \"theme\": \"Theme\", \"mode\": \"Mode\",\n      \"canvasStyle\": \"Canvas Style\", \"themePreset\": \"Theme Preset\",\n      \"mainBackground\": \"Main Background\", \"accentColor\": \"Accent Color\",\n      \"primaryText\": \"Primary Text\", \"secondaryText\": \"Secondary Text\",\n      \"viewOnly\": \"View Only (disable editing)\", \"sidebar\": \"Sidebar / Mobile Footer\",\n      \"modalBg\": \"Modal Window Background\", \"dangerButtons\": \"Danger Buttons\",\n      \"tagFill\": \"Tag(s) Fill\", \"tagText\": \"Tag(s) Text\", \"tagBorder\": \"Tag(s) Border\",\n      \"background\": \"Background\", \"border\": \"Border\", \"buttonFill\": \"Button Fill\",\n      \"buttonText\": \"Button Text\", \"gridLines\": \"Grid Lines\", \"gridSize\": \"Grid Size\",\n      \"showGrid\": \"Show Grid\", \"bgTop\": \"Background Top\", \"bgBottom\": \"Background Bottom\",\n      \"solidBg\": \"Solid Background\", \"frameFill\": \"Frame Fill\", \"frameBorder\": \"Frame Border\",\n      \"uLabels\": \"U Labels\", \"text\": \"Text\", \"minimapDots\": \"Minimap Dots\",\n      \"fill\": \"Fill\", \"titleColor\": \"Title Color\", \"titleSize\": \"Title Size\",\n      \"subtitleColor\": \"Subtitle Color\", \"subtitleSize\": \"Subtitle Size\", \"font\": \"Font\",\n      \"defaultColor\": \"Default Color\", \"defaultRouting\": \"Default Routing\",\n      \"animationStyle\": \"Animation Style\", \"animateDirections\": \"Animate Directions\",\n      \"animationSpeed\": \"Animation Speed\", \"showPortLabels\": \"Show Port Labels\", \"allAnimations\": \"All Animations\",\n      \"sweepPan\": \"Sweep (Pan)\", \"pulseBreathe\": \"Pulse (Breathe)\", \"ringsEmanate\": \"Rings (Emanate)\",\n      \"spinRotate\": \"Spin (Rotate)\", \"connectionsFlow\": \"Connections (Flow)\",\n      \"cameras\": \"Cameras\", \"doorbells\": \"Doorbells\", \"motionSensors\": \"Motion Sensors\",\n      \"smokeDetectors\": \"Smoke Detectors\", \"wifiApRouter\": \"WiFi/AP/Router\",\n      \"sensorsIot\": \"Sensors/IoT\", \"sprinklers\": \"Sprinklers\",\n      \"allZones\": \"All Zones\", \"resizeHandleColor\": \"Resize Handle Color\",\n      \"resizeHandleSize\": \"Resize Handle Size\", \"groupedIconOutline\": \"Grouped Icon Outline\",\n      \"multiselectFill\": \"Multiselect Fill Color (Desktop)\",\n      \"multiselectFillOpacity\": \"Multiselect Fill Opacity (Desktop)\",\n      \"multiselectBorder\": \"Multiselect Border Color (Desktop)\",\n      \"multiselectBorderWidth\": \"Multiselect Border Width (Desktop)\",\n      \"multiselectBorderStyle\": \"Multiselect Border Style (Desktop)\",\n      \"fontSize\": \"Font Size\", \"enabled\": \"Enabled\", \"textColor\": \"Text Color\",\n      \"customText\": \"Custom Text (HTML)\", \"language\": \"Language\", \"currentLanguage\": \"Current:\",\n      \"tagsComma\": \"Tags (comma separated)\", \"networkRange\": \"IP / Network Range (optional)\",\n      \"layer\": \"Layer\", \"mac\": \"MAC Address\", \"vendor\": \"Vendor\", \"model\": \"Model\",\n      \"serialNumber\": \"Serial Number\", \"firmware\": \"Firmware\", \"location\": \"Location\",\n      \"owner\": \"Owner\", \"purchaseDate\": \"Purchase Date\", \"warranty\": \"Warranty\",\n      \"notes\": \"Notes\", \"lineNotes\": \"Line Notes\", \"zoneNotes\": \"Zone Notes\",\n      \"color\": \"Color\", \"width\": \"Width\", \"style\": \"Style\", \"routing\": \"Routing\",\n      \"direction\": \"Direction\", \"label\": \"Label\", \"labelPosition\": \"Label Position\",\n      \"fromPort\": \"From Port\", \"toPort\": \"To Port\", \"waypoints\": \"Waypoints\", \"bandwidth\": \"Bandwidth\",\n      \"latency\": \"Latency\", \"protocol\": \"Protocol\", \"vlan\": \"VLAN\",\n      \"rackCapacity\": \"Rack Capacity\", \"rackPosition\": \"Rack Position (U)\",\n      \"size\": \"Size\", \"sizeHeight\": \"Height (U)\", \"opacity\": \"Opacity\",\n      \"rotation\": \"Rotation\", \"zIndex\": \"Z-Index\", \"locked\": \"Locked\",\n      \"visible\": \"Visible\", \"animated\": \"Animated\", \"animationType\": \"Animation Type\",\n      \"zoneAngle\": \"Zone Angle\", \"zoneRange\": \"Zone Range\", \"zoneColor\": \"Zone Color\",\n      \"zoneOpacity\": \"Zone Opacity\", \"showZone\": \"Show Zone\",\n      \"pageLayout\": \"Page Layout\", \"pageWidth\": \"Page Width\", \"pageHeight\": \"Page Height\",\n      \"orientation\": \"Orientation\", \"landscape\": \"Landscape\", \"portrait\": \"Portrait\",\n      \"margin\": \"Margin\", \"padding\": \"Padding\", \"scale\": \"Scale\",\n      \"timestamp\": \"Timestamp\", \"action\": \"Action\", \"details\": \"Details\",\n      \"fromDevice\": \"From Device\", \"toDevice\": \"To Device\", \"description\": \"Description\",\n      \"encryptNote\": \"Encrypt Note\",\n      \"enableAutoStatusChecking\": \"Enable automatic status checking\",\n      \"checkInterval\": \"Check Interval (seconds):\",\n      \"nextCheckIn\": \"Next check in:\",\n      \"lastRun\": \"Last run:\"\n    },\n    \"sections\": {\n      \"basicInfo\": \"Basic Information\", \"tags\": \"Tags\", \"nodeAppearance\": \"{node} Appearance\",\n      \"rackAppearance\": \"{rack} Appearance\", \"pingStatus\": \"Ping / Status Monitoring\",\n      \"rackConfiguration\": \"Rack Configuration\",\n      \"mappingModeTheme\": \"Mapping Mode & Theme\", \"viewOnlyMode\": \"View Only Mode\",\n      \"moreThemeOptions\": \"More Theme Options\", \"topBar\": \"Top Bar\",\n      \"mainCanvas\": \"Main Canvas\", \"rackCanvas\": \"Rack Canvas\", \"canvasToolbars\": \"Canvas Toolbars\",\n      \"nodes\": \"Nodes\", \"connections\": \"Connections\", \"animationsZones\": \"Animations & Zones\",\n      \"groupsEditing\": \"Groups & Editing\", \"inputsDropdowns\": \"Inputs & Dropdowns\",\n      \"welcomeMessage\": \"Welcome Message\", \"dangerZone\": \"Danger Zone\",\n      \"byType\": \"By Type\", \"byCategory\": \"By Category\", \"zoneSettings\": \"Zone (Animation Cone) Settings\",\n      \"pageLayout\": \"Page Layout\", \"nodeDetails\": \"Node Details\", \"connectionDetails\": \"Connection Details\",\n      \"appearance\": \"Appearance\", \"position\": \"Position\", \"advanced\": \"Advanced\",\n      \"metadata\": \"Metadata\", \"networkInfo\": \"Network Info\", \"deviceInfo\": \"Device Info\",\n      \"inputsFont\": \"Inputs & Font\", \"exportOptions\": \"Export Options\", \"language\": \"Language\",\n      \"autoStatusChecking\": \"Auto Status Checking\", \"notes\": \"Notes\"\n    },\n    \"modals\": {\n      \"settings\": \"Settings\", \"addNode\": \"Add New {node}\", \"addRack\": \"Add New {rack}\",\n      \"editNode\": \"Edit {node}\", \"editRack\": \"Edit {rack}\", \"confirmDelete\": \"Confirm Delete\",\n      \"languageEditor\": \"Language Editor\", \"welcome\": \"Welcome\",\n      \"layerVisibility\": \"Layer Visibility\", \"notes\": \"Notes\", \"notesHub\": \"Notes Hub\", \"auditLog\": \"Audit Log\",\n      \"portMap\": \"Port Map\", \"export\": \"Export\", \"import\": \"Import\",\n      \"clearAllNodes\": \"Clear All Nodes\", \"recording\": \"Recording\", \"recordings\": \"Recordings\",\n      \"saveInfo\": \"Save Info\", \"howToSave\": \"How to Save\", \"keyboardShortcuts\": \"Keyboard Shortcuts\",\n      \"importExport\": \"Import/Export\", \"help\": \"Help\", \"tabs\": \"Tabs\", \"versionHistory\": \"Version History\",\n      \"selectIcon\": \"Select Icon\", \"confirm\": \"Confirm\", \"editName\": \"Edit Name\", \"editNote\": \"Edit Note\",\n      \"layers\": \"Layers\"\n    },\n    \"placeholders\": {\n      \"nodeName\": \"e.g. web-server\", \"nodeIp\": \"e.g. 192.168.1.100\", \"customUrl\": \"e.g. http://192.168.1.1:8080\",\n      \"tags\": \"e.g. Docker, nginx, WG: vpn\", \"rackName\": \"e.g. Rack-A, DC1-R01\",\n      \"rackIp\": \"e.g. 192.168.1.0/24\", \"rackTags\": \"e.g. Production, Data Center 1\",\n      \"search\": \"Search...\", \"searchStrings\": \"Search strings...\", \"searchNodes\": \"Search nodes...\",\n      \"leaveEmpty\": \"Leave empty for default\", \"enterNote\": \"Enter note...\",\n      \"filterAudit\": \"Filter audit log...\", \"filterNodes\": \"Filter nodes...\",\n      \"newTabName\": \"New tab name...\", \"searchIcons\": \"Search icons...\",\n      \"sectionName\": \"Section name (e.g., 'Root Passwords')...\",\n      \"searchDevices\": \"Search devices or ports...\",\n      \"enterInfo\": \"Enter sensitive information here...\",\n      \"fromPort\": \"e.g. eth0, gi0/1\", \"toPort\": \"e.g. eth1, gi0/2\", \"enterText\": \"Enter text...\"\n    },\n    \"tooltips\": {\n      \"addNode\": \"Add new {node}\", \"addRack\": \"Add new {rack}\",\n      \"undo\": \"Undo (Ctrl+Z)\", \"redo\": \"Redo (Ctrl+Y)\",\n      \"settings\": \"Settings\", \"saveTheme\": \"Save current theme\", \"deleteTheme\": \"Delete theme\",\n      \"toggleLayers\": \"Toggle layer visibility\", \"toggleLayersDesc\": \"Toggle which layers are visible on the canvas\",\n      \"manageNotes\": \"Manage encrypted notes\",\n      \"viewAuditLog\": \"View audit log\", \"viewPortMap\": \"View port map\",\n      \"zoomIn\": \"Zoom in\", \"zoomOut\": \"Zoom out\", \"fitView\": \"Fit all to view\",\n      \"resetView\": \"Reset zoom and pan\", \"toggleMinimap\": \"Toggle minimap\",\n      \"toggleLegend\": \"Toggle legend\", \"toggleToolbar\": \"Toggle toolbar\",\n      \"renameTab\": \"Rename tab\"\n    },\n    \"stats\": {\n      \"nodesConnections\": \"{nodeCount} {nodes} • {edgeCount} {connections}\"\n    },\n    \"options\": {\n      \"orthogonal\": \"Orthogonal (90°)\", \"curved\": \"Curved\", \"straight\": \"Straight\",\n      \"flowingArrows\": \"Flowing Arrows\", \"dotArrows\": \"Dot Arrows\",\n      \"allDirections\": \"All Directions\", \"forwardOnly\": \"Forward Only\",\n      \"backwardOnly\": \"Backward Only\", \"bidirectionalOnly\": \"Bidirectional Only\",\n      \"veryFast\": \"Very Fast\", \"fast\": \"Fast\", \"normal\": \"Normal\", \"slow\": \"Slow\", \"verySlow\": \"Very Slow\",\n      \"dashed\": \"Dashed\", \"dotted\": \"Dotted\", \"solid\": \"Solid\",\n      \"default\": \"Default\", \"custom\": \"Custom\", \"customColors\": \"Custom Colors\",\n      \"none\": \"None\", \"auto\": \"Auto\", \"manual\": \"Manual\",\n      \"top\": \"Top\", \"bottom\": \"Bottom\", \"left\": \"Left\", \"right\": \"Right\", \"center\": \"Center\",\n      \"forward\": \"Forward\", \"backward\": \"Backward\", \"both\": \"Both\",\n      \"allEvents\": \"All Events\", \"nodeOperations\": \"Node Operations\",\n      \"styleChanges\": \"Style Changes\", \"rackOperations\": \"Rack Operations\",\n      \"layerChanges\": \"Layer Changes\", \"allConnections\": \"All Connections\",\n      \"withPortsOnly\": \"With Ports Only\", \"missingPorts\": \"Missing Ports\"\n    },\n    \"tabs\": {\n      \"all\": \"All\", \"uiGeneral\": \"UI/General\", \"howToSave\": \"How to Save\",\n      \"keyboardShortcuts\": \"Keyboard Shortcuts\", \"importExport\": \"Import/Export\"\n    },\n    \"layers\": {\n      \"network\": {\n        \"layer1\": \"Physical Layer\", \"layer2\": \"Logical Layer\",\n        \"layer3\": \"Security Layer\", \"layer4\": \"Application Layer\"\n      },\n      \"sports\": {\n        \"layer1\": \"Players\", \"layer2\": \"Equipment\",\n        \"layer3\": \"Coaching/Strategy\", \"layer4\": \"Officials\"\n      },\n      \"smarthome\": {\n        \"layer1\": \"Devices\", \"layer2\": \"Security\",\n        \"layer3\": \"Climate\", \"layer4\": \"Entertainment\"\n      },\n      \"floorplan\": {\n        \"layer1\": \"Furniture\", \"layer2\": \"Fixtures\",\n        \"layer3\": \"Utilities\", \"layer4\": \"People/Workstations\"\n      },\n      \"mindmap\": {\n        \"layer1\": \"Ideas\", \"layer2\": \"Tasks\",\n        \"layer3\": \"Notes\", \"layer4\": \"Links\"\n      }\n    },\n    \"rackSizes\": {\n      \"42u\": \"42U (Standard Full Rack)\", \"48u\": \"48U (Large Rack)\",\n      \"24u\": \"24U (Half Rack)\", \"12u\": \"12U (Small/Wall Mount)\", \"6u\": \"6U (Mini Rack)\"\n    },\n    \"auditTypes\": {\n      \"all\": \"All Events\", \"create\": \"Create\", \"update\": \"Update\", \"delete\": \"Delete\",\n      \"import\": \"Import\", \"export\": \"Export\", \"layer\": \"Layer Changes\"\n    },\n    \"fonts\": {\n      \"inter\": \"Inter\", \"arial\": \"Arial\", \"helvetica\": \"Helvetica\",\n      \"georgia\": \"Georgia\", \"monospace\": \"Monospace\"\n    }\n  },\n  \"messages\": {\n    \"confirmClearAll\": \"This will permanently delete everything on the canvas. Continue?\",\n    \"confirmDelete\": \"Are you sure you want to delete this {node}?\",\n    \"confirmDeleteMultiple\": \"Delete {count} selected items?\",\n    \"confirmImport\": \"This will replace all current data. Continue?\",\n    \"importSuccess\": \"Imported successfully\",\n    \"exportSuccess\": \"File exported successfully\",\n    \"invalidFile\": \"Invalid file format\",\n    \"noSelection\": \"Select at least one {node} first\",\n    \"langImportSuccess\": \"Language file imported successfully\",\n    \"langResetSuccess\": \"Language reset to English\",\n    \"langExportSuccess\": \"Language file exported\",\n    \"invalidLangFile\": \"Invalid language file structure\",\n    \"setSolidBgNote\": \"Set solid background to override gradient.\",\n    \"dangerZoneNote\": \"Permanently delete everything on the canvas.\",\n    \"viewOnlyNote\": \"When enabled, all editing of the canvas is disabled. Pan and zoom still work.\",\n    \"welcomeHint\": \"Double click canvas to add {nodes}.<br>Drag from {node} edge to connect.<br>Right-click for more options.\",\n    \"clearAllWarning\": \"This will remove ALL nodes and connections. This cannot be undone until you reload without saving. Are you sure?\",\n    \"clearEverything\": \"Clear Everything\",\n    \"layerToggleNote\": \"Toggle which layers are visible on the canvas\",\n    \"notesEncryptionNote\": \"Notes can also be stored with AES 256 encryption\",\n    \"howToSaveNote\": \"Browsers cannot overwrite local files. Click Save File to download an updated HTML with all changes. Replace your old file to keep edits. An automatic backup is also saved to your browser's local storage and will prompt for recovery if you close without saving.\",\n    \"decryptionNote\": \"Export via JSON in top settings menu after successful password validation to decrypt your data. You can then reimport back into the same file and save!\",\n    \"noNodesYet\": \"No {nodes} yet. Double click canvas or use sidebar to add.\",\n    \"selectRecording\": \"Select a recording to export.\",\n    \"pleaseWait\": \"This may take a moment, please wait...\",\n    \"recoverWork\": \"Recover unsaved work from {date}?\",\n    \"replaceCurrentData\": \"This will replace current data. Continue?\",\n    \"invalidDataFile\": \"Invalid data file. Missing required fields.\",\n    \"noStringsFound\": \"No strings found\",\n    \"manageTopologies\": \"Manage multiple topologies\",\n    \"snapshotLimit\": \"Limit: Snapshots\",\n    \"trackChanges\": \"Track all changes made to your topology\",\n    \"portMapDesc\": \"All connections with port assignments\",\n    \"manageRecordings\": \"Manage recorded movement sequences\",\n    \"noConnections\": \"No connections found\",\n    \"rackViewHint\": \"Viewing: {name} | Double click empty space to exit\",\n    \"slotCollisionWarning\": \"Warning: {count} slot collision(s) detected\",\n    \"autoStatusCheckingNote\": \"Automatically checks all ping enabled nodes at the specified interval. I recommend 30 to 60 seconds for local networks.\"\n  },\n  \"dialogs\": {\n    \"recoverWork\": \"Recover unsaved work from {date}?\\n({nodeCount} nodes{tabInfo})\\n\\nClick OK to recover, Cancel to start fresh.\",\n    \"replaceData\": \"This will replace current data. Continue?\",\n    \"enableZoneFirst\": \"Enable the zone first before saving as preset\",\n    \"enterPresetName\": \"Enter preset name:\",\n    \"presetSaved\": \"Preset saved: {name}\",\n    \"selectNodeFirst\": \"Select at least one node first\",\n    \"zoneStyleCopied\": \"Zone style copied from first selected node\",\n    \"selectNodesFirst\": \"Select nodes first\",\n    \"copyZoneStyleFirst\": \"Copy a zone style first\",\n    \"zoneStylePasted\": \"Zone style pasted to {count} node(s)\",\n    \"zonesToggled\": \"Toggled zones on {count} node(s) to {state}\",\n    \"enterDecryptPassword\": \"This file is encrypted. Enter password to decrypt:\\n(Attempt {attempt} of {max})\",\n    \"decryptionCancelled\": \"Decryption cancelled. The file will not be loaded.\",\n    \"incorrectPassword\": \"Incorrect password. Please try again.\",\n    \"maxAttemptsReached\": \"Maximum attempts reached. The file will not be loaded.\",\n    \"editLegendLabel\": \"Edit legend label:\",\n    \"enterPort\": \"Port on {nodeName}:\",\n    \"portAlreadyUsed\": \"Warning: Port \\\"{port}\\\" is already used on {nodeName}. Use anyway?\",\n    \"zoneStyleCopiedShort\": \"Zone style copied!\",\n    \"drawingDisabledInRack\": \"Drawing tools are disabled in rack view. Exit to topology view to use drawing tools.\",\n    \"applyRoutingToAll\": \"Apply \\\"{routing}\\\" routing to all {count} connections?\",\n    \"invalidDataFile\": \"Invalid data file. Missing required fields.\",\n    \"enterEncryptPassword\": \"Enter a password to encrypt your data:\\n(Remember this password! You will need it to open this file and it's not recoverable!)\",\n    \"encryptionCancelled\": \"Encryption cancelled. File not saved.\",\n    \"confirmEncryptPassword\": \"Confirm your password:\",\n    \"passwordsDoNotMatch\": \"Passwords do not match. File not saved.\",\n    \"encryptionFailed\": \"Encryption failed: {error}\",\n    \"restoreVersion\": \"Restore version from {date}?\\n\\nYour current work will be lost unless you save first.\",\n    \"deleteVersionConfirm\": \"Delete this version from history?\",\n    \"clearVersionHistory\": \"Clear all version history?\\n\\nThis cannot be undone.\",\n    \"enterSnapshotDescription\": \"Enter a description for this snapshot:\",\n    \"enterTabName\": \"Please enter a tab name\",\n    \"noAuditEntries\": \"No audit entries to export\",\n    \"enterDecryptPasswordFor\": \"Enter password to decrypt \\\"{name}\\\":\",\n    \"decryptionFailed\": \"Failed to decrypt. Wrong password?\",\n    \"on\": \"ON\",\n    \"off\": \"OFF\",\n    \"ok\": \"OK\",\n    \"cancel\": \"Cancel\",\n    \"yes\": \"Yes\",\n    \"no\": \"No\",\n    \"confirm\": \"Confirm\",\n    \"alert\": \"Alert\",\n    \"prompt\": \"Input Required\",\n    \"nodeTypeNoZones\": \"This {node} type doesn't support coverage zones\",\n    \"noZoneStyleCopied\": \"No zone style copied. Copy a zone style first.\",\n    \"dataImportSuccess\": \"Data imported successfully!\",\n    \"importDataFailed\": \"Failed to import data: {error}\",\n    \"encryptionCancelledNotSaved\": \"Encryption cancelled. File not saved.\",\n    \"passwordsMismatchNotSaved\": \"Passwords do not match. File not saved.\",\n    \"encryptionFailedError\": \"Encryption failed: {error}\",\n    \"enterRackName\": \"Please enter a {rack} name.\",\n    \"enterNodeName\": \"Please enter a {node} name.\",\n    \"cannotDeleteLastTab\": \"Cannot delete the last tab\",\n    \"noAuditEntriesToExport\": \"No audit entries to export\",\n    \"enterNoteName\": \"Please enter a note name\",\n    \"noteAlreadyExists\": \"A note with this name already exists\",\n    \"passwordsMismatch\": \"Passwords do not match\",\n    \"screenshotFailed\": \"Screenshot failed. Please try again.\",\n    \"csvNoDataRows\": \"CSV file has no data rows\",\n    \"csvNeedsNameColumn\": \"CSV must have a \\\"name\\\" column\",\n    \"confirmImportCsvFull\": \"This CSV contains a full backup.\\n\\n- {nodeCount} nodes in CSV data\\n- {tabCount} tab(s) in backup\\n- {edgeCount} connections in backup\\n\\nClick OK for FULL RESTORE (replace all data)\\nClick Cancel to just ADD the {csvNodeCount} nodes\",\n    \"confirmImportCsvAdd\": \"Import {count} nodes from CSV?\\n\\n{settingsNote}\\n\\nNote: CSV does not include connections, zones, or text labels.\",\n    \"csvSettingsRestore\": \"This will ADD nodes and RESTORE settings/theme.\",\n    \"csvAddOnly\": \"This will ADD nodes to your existing topology.\",\n    \"fullRestoreComplete\": \"Full restore complete from CSV backup\",\n    \"importedNodesCount\": \"Successfully imported {count} {nodes}\",\n    \"importCsvFailed\": \"Failed to import CSV: {error}\",\n    \"confirmImportMarkdown\": \"Import Markdown topology?\\n\\n- {nodeCount} nodes\\n- {edgeCount} connections\\n- {rectCount} zones\\n- {textCount} text labels\\n\\nThis will REPLACE all current data.\",\n    \"noValidTopologyInMarkdown\": \"No valid topology data found in Markdown file.\\n\\nMake sure the file was exported from The One File.\",\n    \"markdownImportSuccess\": \"Successfully imported:\\n- {nodeCount} {nodes}\\n- {edgeCount} {connections}\\n- {rectCount} zones\\n- {textCount} text labels\",\n    \"jsonImportSuccess\": \"Successfully imported:\\n- {nodeCount} nodes\\n- {edgeCount} connections\\n- {rectCount} zones\\n- {textCount} text labels\",\n    \"importMarkdownFailed\": \"Failed to import Markdown: {error}\",\n    \"noRecordingAvailable\": \"No recording available. Press Record to create one.\",\n    \"selectRecordingToDelete\": \"Select a recording to delete.\",\n    \"deleteRecording\": \"Delete \\\"{name}\\\"?\",\n    \"selectRecordingToExport\": \"Select a recording to export.\",\n    \"recordingImported\": \"Imported: {name}\",\n    \"importRecordingFailed\": \"Failed to import recording: {error}\",\n    \"selectRecordingForVideo\": \"Select a recording to export as video.\",\n    \"deleteTab\": \"Delete tab \\\"{name}\\\"?\",\n    \"themeExists\": \"A theme named \\\"{name}\\\" already exists. Replace it?\",\n    \"deleteTheme\": \"Delete theme \\\"{name}\\\"?\",\n    \"clearAuditLog\": \"Clear all audit log entries?\\n\\nThis cannot be undone.\",\n    \"deleteNote\": \"Delete note \\\"{name}\\\"?\",\n    \"confirmImportJson\": \"This will replace all current data with the imported data.\\n\\nImporting:\\n- {nodeCount} nodes\\n- {edgeCount} connections\\n- {tabCount} tab(s)\\n\\nContinue?\",\n    \"confirmImportHtml\": \"Import data from HTML file?\\n\\n- {nodeCount} nodes\\n- {edgeCount} connections\\n- {tabCount} tab(s)\\n\\nThis will REPLACE all current data.\",\n    \"invalidHtmlFile\": \"This HTML file does not contain valid topology data.\",\n    \"htmlImportSuccess\": \"Successfully imported {nodeCount} nodes and {edgeCount} connections from HTML.\",\n    \"enterNewTabName\": \"Enter new name:\",\n    \"saveThemeName\": \"Enter a name for this theme:\",\n    \"defaultThemeName\": \"My Theme {number}\",\n    \"enterEncryptPasswordFor\": \"Enter password to encrypt \\\"{name}\\\":\",\n    \"confirmPasswordFor\": \"Confirm password:\",\n    \"deleteNodeConfirm\": \"Delete {node} \\\"{name}\\\"?\",\n    \"deleteLineConfirm\": \"Delete this line?\",\n    \"deleteLineNote\": \"Delete this line note?\",\n    \"deleteRackWithNodes\": \"Delete {rack} \\\"{name}\\\"?\\n\\nThis will also delete {count} {node}(s) inside:\\n• {nodeList}\",\n    \"deleteEmptyRack\": \"Delete {rack} \\\"{name}\\\"? (empty {rack})\",\n    \"deleteNodeWithConnections\": \"Delete {node} \\\"{name}\\\" and all its {connections}?\",\n    \"rackCapacityExceeded\": \"Device requires U{startU}-U{endU} but {rack} only has {capacity}U capacity. Device would exceed {rack} by {excess}U.\",\n    \"invalidRackPosition\": \"Invalid {rack} position. U position must be at least 1.\"\n  },\n  \"emptyStates\": {\n    \"noStringsFound\": \"No strings found\",\n    \"noNodesAssigned\": \"No nodes assigned\",\n    \"noConnections\": \"No connections\",\n    \"noVersionHistory\": \"No version history yet. Versions are saved automatically when you save the file.\",\n    \"noAuditEntries\": \"No audit entries yet\",\n    \"noNotesYet\": \"No notes yet\",\n    \"noConnectionsFound\": \"No connections found\",\n    \"noRecordingsYet\": \"No recordings yet. Press Record to start.\",\n    \"generatingVideo\": \"Generating Video...\"\n  },\n  \"legends\": {\n    \"legend\": \"Legend\",\n    \"zoneLegend\": \"Zone Legend\",\n    \"lineLegend\": \"Line Legend\",\n    \"coverageZone\": \"Coverage Zone\",\n    \"defaultLineLabel\": \"you can edit me too\"\n  },\n  \"editModal\": {\n    \"editTitle\": \"Edit Title\",\n    \"editName\": \"Edit Name\",\n    \"editIpSubtitle\": \"Edit IP/Subtitle\",\n    \"editTag\": \"Edit Tag\",\n    \"editMacAddress\": \"Edit MAC Address\",\n    \"addTags\": \"Add Tag(s) : comma separated\",\n    \"editRackUnit\": \"Edit Rack Unit\",\n    \"editUHeight\": \"Edit U Height\",\n    \"rackPlacementError\": \"Rack Placement Error\"\n  },\n  \"noteEditor\": {\n    \"title\": \"Edit Note\",\n    \"addNote\": \"Add Note\",\n    \"editNote\": \"Edit Note\",\n    \"viewNote\": \"View Note\"\n  },\n  \"notes\": {\n    \"noNotes\": \"No notes yet\",\n    \"namePlaceholder\": \"Note name (e.g., 'Root Passwords')...\",\n    \"editNote\": \"Edit Note\",\n    \"contentPlaceholder\": \"Enter sensitive information here...\",\n    \"encryptNote\": \"Encrypt Note\",\n    \"newNoteTitle\": \"New note: {name}\",\n    \"editNoteTitle\": \"Edit note: {name}\"\n  },\n  \"themes\": {\n    \"default\": \"Default\", \"slate\": \"Slate\", \"graphite\": \"Graphite\", \"frost\": \"Frost (Light)\",\n    \"synthwave\": \"Synthwave\", \"terminal\": \"Terminal\", \"dracula\": \"Dracula\",\n    \"cobalt\": \"Cobalt\", \"solarized\": \"Solarized\", \"brainwave\": \"Brainwave\",\n    \"neonMint\": \"Neon Mint\", \"velvetDusk\": \"Velvet Dusk\", \"sunburst\": \"Sunburst\"\n  },\n  \"themeGroups\": {\n    \"corporate\": \"Corporate\", \"homelab\": \"Homelab\", \"dev\": \"Dev\", \"mindMap\": \"Mind Map\", \"myThemes\": \"My Themes\"\n  },\n  \"sidebar\": {\n    \"addNode\": \"+ {node}\", \"addRack\": \"+ {rack}\",\n    \"nodePanel\": \"{node} Details\", \"edgePanel\": \"{connection} Details\",\n    \"properties\": \"Properties\", \"style\": \"Style\", \"actions\": \"Actions\"\n  },\n  \"topbar\": {\n    \"untitledTopology\": \"Untitled Topology\", \"saveHtml\": \"Save HTML\",\n    \"export\": \"Export\", \"import\": \"Import\",\n    \"backToTopology\": \"← Back to Topology\", \"tabs\": \"Tabs\", \"snapshots\": \"Snapshots\",\n    \"audit\": \"Audit\", \"ports\": \"Ports\"\n  },\n  \"toolbar\": {\n    \"legend\": \"Legend\", \"legendMini\": \"Connection & Zone Legend\", \"minimap\": \"Minimap\", \"minimapMini\": \"Map\", \"draw\": \"Draw Tools\", \"drawMini\": \"Draw\",\n    \"topology\": \"Topology Tools\", \"topologyMini\": \"Add Connection\",\n    \"addNode\": \"+ {node}\", \"addRack\": \"+ {rack}\",\n    \"undo\": \"Undo\", \"redo\": \"Redo\"\n  },\n  \"help\": {\n    \"title\": \"Help\",\n    \"tabs\": {\n      \"general\": \"General\", \"desktop\": \"Desktop\", \"mobile\": \"Mobile\", \"formats\": \"Import/Export\", \"recording\": \"Recording\"\n    },\n    \"general\": {\n      \"addNodes\": \"Add Nodes:\", \"addNodesDesc\": \"Click \\\"+ Node\\\" or \\\"+ Rack\\\" in the top menu\",\n      \"connectNodes\": \"Connect Nodes:\", \"connectNodesDesc\": \"Select a node, then use \\\"Add Connection\\\" in the panel\",\n      \"moveNodes\": \"Move Nodes:\", \"moveNodesDesc\": \"Drag nodes to reposition them\",\n      \"enterRackView\": \"Enter Rack View:\", \"enterRackViewDesc\": \"Double click on desktop or Long press on mobile\",\n      \"multiSelect\": \"Multi Select:\", \"multiSelectDesc\": \"Right click (desktop) or double tap (mobile)\",\n      \"panCanvas\": \"Pan Canvas:\", \"panCanvasDesc\": \"Drag empty space or hold Space + drag\",\n      \"zoom\": \"Zoom:\", \"zoomDesc\": \"Scroll wheel or pinch gesture\",\n      \"freeDraw\": \"Free Draw:\", \"freeDrawDesc\": \"Use draw toolbar for drawing lines, boxes/zones, and text\",\n      \"savingEncryption\": \"Saving & Encryption\",\n      \"savingNote\": \"Browsers cannot overwrite local files. Click Save File to download an updated HTML with all changes. Replace your old file to keep edits. An automatic backup is also saved to your browser's local storage and will prompt for recovery if you close without saving.\",\n      \"savingDesc\": \"Browsers cannot overwrite local files. Click Save File to download an updated HTML with all changes. Replace your old file to keep edits. An automatic backup is also saved to your browser's local storage and will prompt for recovery if you close without saving.\",\n      \"encryptionNote\": \"Encryption of data:\", \"encryptionDesc\": \"Check \\\"Encrypt\\\" before saving to password protect your data. Beware! No recovery possible without password!!\",\n      \"decryptionNote\": \"Decryption of data:\", \"decryptionDesc\": \"Export via JSON in top settings menu after successful password validation to decrypt your data. You can then reimport back into the same file and save!\"\n    },\n    \"desktop\": {\n      \"navigation\": \"Navigation\", \"management\": \"Management\",\n      \"arrowKeys\": \"Arrow Keys\", \"moveSelected1px\": \"Move selected 1px\",\n      \"shiftArrows\": \"Shift + Arrows\", \"moveSelected10px\": \"Move selected 10px\",\n      \"tabShiftTab\": \"Tab / Shift+Tab\", \"cycleNodes\": \"Cycle through nodes\",\n      \"fKey\": \"F\", \"focusSelected\": \"Focus on selected\",\n      \"spaceDrag\": \"Space + Drag\", \"panCanvas\": \"Pan canvas\",\n      \"lKey\": \"L\", \"lockUnlock\": \"Lock/unlock selected\",\n      \"gKey\": \"G\", \"groupUngroup\": \"Group/ungroup selected\",\n      \"ctrlCV\": \"Ctrl+C / Ctrl+V\", \"copyPaste\": \"Copy / Paste\",\n      \"ctrlD\": \"Ctrl+D\", \"duplicate\": \"Duplicate\",\n      \"ctrlA\": \"Ctrl+A\", \"selectAll\": \"Select all\",\n      \"deleteKey\": \"Delete\", \"deleteSelected\": \"Delete selected\",\n      \"escapeKey\": \"Escape\", \"clearSelection\": \"Clear selection\",\n      \"ctrlZY\": \"Ctrl+Z / Ctrl+Y\", \"undoRedo\": \"Undo / Redo\",\n      \"rightClick\": \"Right Click\", \"multiSelectToggle\": \"Multi select toggle\",\n      \"dblClickRack\": \"Double click rack\", \"enterRackView\": \"Enter rack view\",\n      \"dblClickConnection\": \"Double click connection\", \"addWaypoint\": \"Add waypoint\",\n      \"dblClickEmpty\": \"Double click empty (in rack)\", \"exitRackView\": \"Exit rack view\",\n      \"recording\": \"Recording\",\n      \"rKey\": \"R\", \"startStopRecording\": \"Start/stop real time recording\",\n      \"shiftR\": \"Shift+R\", \"startStopStepRecording\": \"Start/stop step by step recording\",\n      \"spaceKey\": \"Space\", \"addStepOrPlayPause\": \"Add step (step recording) / Play/Pause (playback)\",\n      \"pKey\": \"P\", \"playRecording\": \"Play recording\"\n    },\n    \"mobile\": {\n      \"basicGestures\": \"Basic Gestures\", \"rackView\": \"Rack View\",\n      \"tapNode\": \"Tap node\", \"selectOpenProperties\": \"Select & open properties\",\n      \"tapEmpty\": \"Tap empty\", \"deselectAll\": \"Deselect all\",\n      \"dragNode\": \"Drag node\", \"moveNode\": \"Move node\",\n      \"dragEmpty\": \"Drag empty\", \"panCanvas\": \"Pan canvas\",\n      \"pinch\": \"Pinch\", \"zoomInOut\": \"Zoom in/out\",\n      \"doubleTapNode\": \"Double tap node\", \"addRemoveSelection\": \"Add/remove from selection\",\n      \"longPressRack\": \"Long press rack\", \"enterRackView\": \"Enter rack view\",\n      \"longPressConnection\": \"Long press connection\", \"addWaypoint\": \"Add waypoint\",\n      \"doubleTapEmpty\": \"Double tap empty\", \"exitRackView\": \"Exit rack view\"\n    },\n    \"formats\": {\n      \"exportHelp\": \"Export Help\", \"importHelp\": \"Import Help\",\n      \"exportFormats\": \"Export Formats\", \"importFormats\": \"Import Formats\",\n      \"html\": \"HTML\", \"htmlDesc\": \"Complete self contained file and the default save method. When in doubt, choose this. Use the Save HTML button for this\", \"htmlImportDesc\": \"Import data from another saved HTML file. Extracts and restores all topology data from a previously exported file.\",\n      \"json\": \"JSON\", \"jsonExportDesc\": \"Full backup. Editable in your favorite text editor\", \"jsonImportDesc\": \"Replaces all data\",\n      \"markdown\": \"Markdown\", \"markdownExportDesc\": \"Full backup. Editable in your favorite text editor\", \"markdownImportDesc\": \"Replaces all data\",\n      \"csv\": \"CSV\", \"csvExportDesc\": \"Full backup in header, nodes in spreadsheet rows\", \"csvImportDesc\": \"Choose full restore or add nodes only. Allows quickly creating nodes with a spreadsheet and importing into an existing save.\",\n      \"png\": \"PNG\", \"pngDesc\": \"Standard image of current canvas tab\", \"pngImage\": \"PNG Image\",\n      \"svg\": \"SVG\", \"svgDesc\": \"Vector image, scalable and editable image of current canvas tab\", \"svgVector\": \"SVG Vector\"\n    },\n    \"recording\": {\n      \"title\": \"Canvas Recording\",\n      \"overview\": \"Record your canvas activity to create visual documentation, tutorials, or demonstrations of your network topology changes.\",\n      \"twoModes\": \"Two Recording Modes\",\n      \"realtimeTitle\": \"Real time Recording\",\n      \"realtimeDesc\": \"(red ● button) captures the canvas continuously at 10 frames per second as you work. Good for demonstrating workflows.\",\n      \"stepByStepTitle\": \"Step by Step Recording\",\n      \"stepByStepDesc\": \"(green ●+ button) lets you manually capture each frame. Click the green button to start, then press + or Space to capture each step. Each frame plays for 1 second. Good for tutorials where you want precise control over what's shown.\",\n      \"savedInFile\": \"Recordings are saved within your data file for later playback\",\n      \"exportVideo\": \"Can be exported as a video file (WebM format) for sharing\",\n      \"playWhileRecording\": \"Pressing Play while recording will stop and immediately play the new recording\",\n      \"controls\": \"Controls\",\n      \"recordBtn\": \"Record\", \"recordBtnDesc\": \"Start real time recording (captures 10 frames/sec). Press R\",\n      \"stepRecordBtn\": \"Step Record\", \"stepRecordBtnDesc\": \"Start step by step recording. Press Shift+R\",\n      \"addStepBtn\": \"Add Step\", \"addStepBtnDesc\": \"Capture current state as next frame (during step recording). Press Space\",\n      \"playBtn\": \"Play\", \"playBtnDesc\": \"Play back a saved recording. Press P\",\n      \"stopBtn\": \"Stop\", \"stopBtnDesc\": \"Stop recording or playback. Press R or Shift+R again\",\n      \"pauseBtn\": \"Pause\", \"pauseBtnDesc\": \"Pause playback. Press Space\",\n      \"scrubber\": \"Scrubber\", \"scrubberDesc\": \"Seek to any point in the recording\",\n      \"speed\": \"Speed\", \"speedDesc\": \"Adjust playback speed (0.5x, 1x, 2x, 4x)\",\n      \"loop\": \"Loop\", \"loopDesc\": \"Toggle continuous loop playback\",\n      \"manage\": \"Manage\", \"manageDesc\": \"View, rename, export, or delete saved recordings\"\n    }\n  },\n  \"pageLayoutHelp\": {\n    \"dragToResize\": \"Drag to Resize:\",\n    \"header\": \"Header:\", \"headerDesc\": \"Drag the bottom edge of the header bar\",\n    \"sidebar\": \"Sidebar:\", \"sidebarDesc\": \"Drag the left edge of the sidebar panel\",\n    \"mobile\": \"Mobile:\", \"mobileDesc\": \"Drag the top edge of the footer panel\",\n    \"hoverNote\": \"Hover over panel edges to see resize handles\"\n  },\n  \"confirmModal\": {\n    \"confirm\": \"Confirm\",\n    \"deleteLineQuestion\": \"Are you sure you want to delete this line?\"\n  },\n  \"drawToolbar\": {\n    \"filled\": \"Filled\", \"outlined\": \"Outlined\",\n    \"solid\": \"Solid\", \"dashed\": \"Dashed\", \"dotted\": \"Dotted\", \"wall\": \"Wall\",\n    \"noArrows\": \"No arrows\", \"arrowRight\": \"→ Right\", \"arrowLeft\": \"← Left\", \"arrowBoth\": \"↔ Both\",\n    \"undoLastPoint\": \"Undo last point\"\n  },\n  \"topologyToolbar\": {\n    \"addLineTo\": \"Add line to:\",\n    \"noArrows\": \"No arrows\", \"forward\": \"→ Forward\", \"backward\": \"← Backward\", \"bidirectional\": \"↔ Bidirectional\"\n  },\n  \"edgePanel\": {\n    \"customLine\": \"Custom line\",\n    \"animate\": \"Animate\"\n  },\n  \"rectPanel\": {\n    \"zone\": \"Zone\", \"fillColor\": \"Fill Color:\", \"fillOpacity\": \"Fill Opacity:\", \"borderColor\": \"Border Color:\", \"borderWidth\": \"Border Width:\",\n    \"lineStyle\": \"Line Style:\", \"deleteZone\": \"Delete Zone\"\n  },\n  \"textPanel\": {\n    \"textElement\": \"Text Element\", \"content\": \"Content:\", \"fontSizeLabel\": \"Font Size:\", \"colorLabel\": \"Color:\",\n    \"fontWeight\": \"Font Weight:\", \"bold\": \"Bold\", \"semiBold\": \"Semi-Bold\", \"light\": \"Light\",\n    \"fontStyle\": \"Font Style:\", \"italic\": \"Italic\",\n    \"textAlign\": \"Text Align:\", \"textDecoration\": \"Text Decoration:\", \"underline\": \"Underline\", \"strikeThrough\": \"Strike Through\",\n    \"enable\": \"Enable\", \"deleteText\": \"Delete Text\"\n  },\n  \"tabsModal\": {\n    \"manageTopologies\": \"Manage multiple topologies\",\n    \"mainTopology\": \"Main Topology\",\n    \"nodes\": \"nodes\", \"connections\": \"connections\",\n    \"newTabPlaceholder\": \"New tab name...\",\n    \"addTab\": \"+ Add Tab\"\n  },\n  \"auditModal\": {\n    \"auditLogTitle\": \"Audit Log\",\n    \"allEvents\": \"All Events\", \"create\": \"Create\", \"update\": \"Update\", \"delete\": \"Delete\",\n    \"importEvent\": \"Import\", \"exportEvent\": \"Export\", \"layerChanges\": \"Layer Changes\",\n    \"noEvents\": \"No audit events recorded yet\"\n  },\n  \"portMapModal\": {\n    \"portMapTitle\": \"Port Map\",\n    \"exportCsv\": \"Export CSV\"\n  },\n  \"secretEditor\": {\n    \"editNote\": \"Edit Note\",\n    \"enterSensitiveInfo\": \"Enter sensitive information here...\",\n    \"encryptNote\": \"Encrypt Note\",\n    \"noNotesYet\": \"No notes yet\"\n  },\n  \"mobileExport\": {\n    \"pngImage\": \"PNG Image\", \"svgVector\": \"SVG Vector\", \"jsonFullBackup\": \"JSON (Full Backup)\",\n    \"markdownExport\": \"Markdown\", \"csvExport\": \"CSV\",\n    \"jsonBackup\": \"JSON (Full Backup)\", \"markdown\": \"Markdown\",\n    \"csv\": \"CSV\", \"printBW\": \"Print B&W\", \"printColorFull\": \"Print Full Color\", \"printColorWhite\": \"Print White BG\", \"printColorBlack\": \"Print Black BG\", \"exportHelp\": \"Export Help\"\n  },\n  \"mobileImport\": {\n    \"jsonImport\": \"JSON\", \"markdownImport\": \"Markdown\",\n    \"json\": \"JSON\", \"markdown\": \"Markdown\", \"csv\": \"CSV\", \"importHelp\": \"Import Help\"\n  },\n  \"recording\": {\n    \"startRecording\": \"Start recording\", \"stop\": \"Stop\", \"playRecording\": \"Play recording\", \"pause\": \"Pause\",\n    \"playbackSpeed\": \"Playback speed\", \"loop\": \"Loop\", \"manageRecordings\": \"Manage recordings\",\n    \"playbackPosition\": \"Playback position\", \"exportVideo\": \"Export Video\",\n    \"selectOrCreate\": \"Select a recording or create a new one\",\n    \"newRecordingPlaceholder\": \"New recording name...\",\n    \"recording\": \"Recording\"\n  },\n  \"welcome\": {\n    \"title\": \"Welcome to The One File\",\n    \"subtitle\": \"What are you mapping today?\",\n    \"chooseMode\": \"Choose your mode:\",\n    \"chooseCanvas\": \"Choose canvas style:\",\n    \"chooseTheme\": \"Choose a theme:\",\n    \"customColors\": \"Customize colors:\",\n    \"startMapping\": \"Start Mapping\",\n    \"skip\": \"Skip\",\n    \"canvasStyle\": \"Canvas Style\",\n    \"theme\": \"Theme\",\n    \"colorCustomization\": \"Color Customization\",\n    \"canvas\": {\n      \"grid\": \"Standard Grid\", \"dots\": \"Dot Grid\",\n      \"blueprint\": \"Blueprint Grid\", \"none\": \"No Grid\"\n    },\n    \"modes\": {\n      \"network\": \"Network Topology\", \"networkDesc\": \"Servers, routers, switches\",\n      \"mindmap\": \"Mind Map\", \"mindmapDesc\": \"Ideas, concepts, brainstorming\",\n      \"sports\": \"Sports Playbook\", \"sportsDesc\": \"Plays, formations, positions\",\n      \"smarthome\": \"Smart Home\", \"smarthomeDesc\": \"IoT devices, automation\",\n      \"floorplan\": \"Floor Plan / Blueprint\", \"floorplanDesc\": \"Rooms, layouts, physical spaces\"\n    },\n    \"colors\": {\n      \"mainBg\": \"Top Bar Background\", \"accent\": \"Accent\",\n      \"primaryText\": \"Primary Text\", \"secondaryText\": \"Secondary Text\",\n      \"sidebarPanel\": \"Main Background\", \"modalWindows\": \"Modal Background\",\n      \"dangerButtons\": \"Grid Color\", \"mobileFooter\": \"Sidebar Background\",\n      \"moreStyles\": \"More styles available in main settings panel\"\n    }\n  },\n  \"bulkToolbar\": {\n    \"selected\": \"Selected:\",\n    \"alignLeft\": \"⬅ Left\", \"alignRight\": \"➡ Right\", \"alignTop\": \"⬆ Top\", \"alignBottom\": \"⬇ Bottom\",\n    \"alignLeftShort\": \"Align Left\", \"alignRightShort\": \"Align Right\", \"alignTopShort\": \"Align Top\", \"alignBottomShort\": \"Align Bottom\",\n    \"distributeH\": \"↔ Distribute H\", \"distributeV\": \"↕ Distribute V\",\n    \"distributeHShort\": \"Distribute H\", \"distributeVShort\": \"Distribute V\",\n    \"clone\": \"📋 Clone\", \"cloneAll\": \"Clone All\",\n    \"copyZone\": \"📡 Copy Zone\", \"pasteZone\": \"📡 Paste Zone\", \"toggleZones\": \"📡 Toggle Zones\",\n    \"copyZoneShort\": \"Copy Zone\", \"pasteZoneShort\": \"Paste Zone\", \"toggleZonesShort\": \"Toggle Zones\",\n    \"lockToggle\": \"Lock Toggle\", \"groupToggle\": \"Group Toggle\",\n    \"deleteAll\": \"Delete All\", \"clearSelection\": \"Clear Selection\",\n    \"coverageZones\": \"Coverage Zones\",\n    \"nodesSelected\": \"Nodes Selected\"\n  },\n  \"canvasHints\": {\n    \"scrollZoom\": \"Scroll to zoom\",\n    \"dragPan\": \"Drag to pan\",\n    \"rightClickClone\": \"Right click to clone and align\",\n    \"rightClickMulti\": \"Right click to select multiple\",\n    \"shiftMarquee\": \"Hold Shift + drag mouse for marquee selection\",\n    \"power\": \"You have the power\",\n    \"timeNow\": \"Your time is NOW!\"\n  },\n  \"legend\": {\n    \"connectionLegend\": \"Connection Legend\"\n  },\n  \"nodePanel\": {\n    \"connections\": \"Connections\",\n    \"styling\": \"Styling\",\n    \"screen\": \"Screen:\",\n    \"tablet\": \"Tablet\", \"fold\": \"Fold\",\n    \"fillColor\": \"Fill Color:\", \"borderColor\": \"Border Color:\",\n    \"titleFont\": \"Title Font:\", \"subColor\": \"Sub Color:\", \"subFont\": \"Sub Font:\", \"subSize\": \"Sub Size:\",\n    \"systemUI\": \"System UI\", \"serif\": \"Serif\", \"cursive\": \"Cursive\", \"courier\": \"Courier\", \"times\": \"Times\",\n    \"textPosition\": \"Text Position\",\n    \"nameY\": \"Name Y:\", \"nameX\": \"Name X:\", \"ipY\": \"IP Y:\", \"ipX\": \"IP X:\",\n    \"resetStyles\": \"Reset Styles\",\n    \"nodesInRack\": \"Nodes in Rack\",\n    \"uHeight\": \"U Height:\",\n    \"layer\": \"Layer:\",\n    \"assignedRack\": \"Assigned Rack:\",\n    \"hostedOn\": \"Hosted On:\",\n    \"rackCapacity\": \"Rack Capacity:\",\n    \"physical\": \"Physical\",\n    \"logical\": \"Logical\",\n    \"security\": \"Security\",\n    \"application\": \"Application\",\n    \"mac\": \"MAC:\",\n    \"rackUnit\": \"Rack Unit:\",\n    \"searchWebIcons\": \"Or Search Web Icons\",\n    \"monitorUrl\": \"Monitor URL:\",\n    \"status\": \"Status:\",\n    \"pingStatusMonitoring\": \"Ping / Status Monitoring\",\n    \"pingIndicatorPosition\": \"Ping Indicator Position\",\n    \"pingX\": \"Ping X:\",\n    \"pingY\": \"Ping Y:\",\n    \"enableStatusCheck\": \"Enable status check for this node\",\n    \"protocol\": \"Protocol:\",\n    \"httpPort80\": \"HTTP (port 80)\",\n    \"httpsPort443\": \"HTTPS (port 443)\",\n    \"customUrl\": \"Custom URL\",\n    \"customUrlLabel\": \"Custom URL:\",\n    \"timeoutMs\": \"Timeout (ms):\",\n    \"currentStatus\": \"Current Status:\",\n    \"lastChecked\": \"Last checked:\",\n    \"checkStatusNow\": \"Check Status Now\",\n    \"motionTrail\": \"Motion Trail:\"\n  },\n  \"langEditor\": {\n    \"tabs\": {\n      \"all\": \"All\", \"modes\": \"Modes\", \"network\": \"Network\", \"mindMap\": \"Mind Map\",\n      \"sports\": \"Sports\", \"smartHome\": \"Smart Home\", \"floorPlan\": \"Floor Plan\",\n      \"ui\": \"UI\", \"shapes\": \"Shapes\", \"messages\": \"Messages\"\n    },\n    \"searchPlaceholder\": \"Search strings...\"\n  },\n  \"auditLog\": {\n    \"allEvents\": \"All Events\", \"nodeOperations\": \"Node Operations\",\n    \"connections\": \"Connections\", \"styleChanges\": \"Style Changes\",\n    \"rackOperations\": \"Rack Operations\", \"layerChanges\": \"Layer Changes\"\n  },\n  \"portMap\": {\n    \"searchPlaceholder\": \"Search devices or ports...\",\n    \"allConnections\": \"All Connections\", \"withPorts\": \"With Ports Only\",\n    \"missingPorts\": \"Missing Ports\", \"noConnections\": \"No connections found\"\n  },\n  \"notes\": {\n    \"noNotes\": \"No notes yet\",\n    \"namePlaceholder\": \"Note name (e.g., 'Root Passwords')...\",\n    \"editNote\": \"Edit Note\",\n    \"contentPlaceholder\": \"Enter sensitive information here...\",\n    \"encryptNote\": \"Encrypt Note\"\n  },\n  \"recordings\": {\n    \"noRecordings\": \"No recordings yet. Press Record to start.\",\n    \"exportVideo\": \"Export Video\"\n  },\n  \"trails\": {\n    \"title\": \"Motion Trails\",\n    \"enableTrails\": \"Enable Trails:\",\n    \"trailLength\": \"Trail Length:\",\n    \"trailStyle\": \"Trail Style:\",\n    \"styleSolid\": \"Solid Fade\",\n    \"styleDashed\": \"Dashed\",\n    \"styleDots\": \"Dots\",\n    \"showArrow\": \"Show Direction Arrow:\",\n    \"perNodeHint\": \"Tip: Enable/disable trails per node in the node panel\"\n  },\n  \"images\": {\n    \"title\": \"Canvas Image\",\n    \"name\": \"Name:\",\n    \"addImage\": \"Add Image\",\n    \"dropHint\": \"Drop image here or click to browse\",\n    \"maxImages\": \"Maximum images reached (40)\",\n    \"opacity\": \"Opacity:\",\n    \"border\": \"Border:\",\n    \"shadow\": \"Shadow:\",\n    \"size\": \"Size:\",\n    \"delete\": \"Delete Image\",\n    \"none\": \"None\",\n    \"thin\": \"Thin\",\n    \"medium\": \"Medium\",\n    \"thick\": \"Thick\",\n    \"small\": \"Small\",\n    \"large\": \"Large\",\n    \"imageNotes\": \"Image Notes\"\n  },\n  \"rack\": {\n    \"capacity42\": \"42U (Standard Full Rack)\",\n    \"capacity48\": \"48U (Large Rack)\",\n    \"capacity24\": \"24U (Half Rack)\",\n    \"capacity12\": \"12U (Small/Wall Mount)\",\n    \"capacity6\": \"6U (Mini Rack)\"\n  },\n  \"zone\": {\n    \"coverageZone\": \"Coverage Zone\",\n    \"preset\": \"Preset:\",\n    \"applyPreset\": \"-- Apply Preset --\",\n    \"presets\": {\n      \"securityCam\": \"Security Camera\",\n      \"ptzCam\": \"PTZ Camera\",\n      \"motionDetector\": \"Motion Detector\",\n      \"wifi\": \"WiFi\",\n      \"wifiExtender\": \"WiFi Extender\",\n      \"smokeAlarm\": \"Smoke Alarm\",\n      \"sprinklerArc\": \"Sprinkler Arc\"\n    },\n    \"showZone\": \"Show Zone:\",\n    \"angle\": \"Angle:\",\n    \"distance\": \"Distance:\",\n    \"innerRadius\": \"Inner Radius:\",\n    \"rotation\": \"Rotation:\",\n    \"fill\": \"Fill\",\n    \"border\": \"Border\",\n    \"color\": \"Color:\",\n    \"opacity\": \"Opacity:\",\n    \"gradient\": \"Gradient:\",\n    \"fadeTowardEdge\": \"Fade toward edge\",\n    \"width\": \"Width:\",\n    \"style\": \"Style:\",\n    \"label\": \"Label\",\n    \"text\": \"Text:\",\n    \"labelPlaceholder\": \"e.g. Detection Zone\",\n    \"position\": \"Position:\",\n    \"edge\": \"Edge\",\n    \"outside\": \"Outside\",\n    \"fontSize\": \"Font Size:\",\n    \"fontColor\": \"Font Color:\",\n    \"bold\": \"Bold:\",\n    \"background\": \"Background:\",\n    \"offsetX\": \"Offset X:\",\n    \"offsetY\": \"Offset Y:\",\n    \"animation\": \"Animation\",\n    \"animate\": \"Animate:\",\n    \"type\": \"Type:\",\n    \"sweep\": \"Sweep:\",\n    \"speed\": \"Speed:\"\n  },\n  \"iconPicker\": {\n    \"mdi\": \"MDI\",\n    \"simpleIcons\": \"Simple Icons\",\n    \"selfhst\": \"selfh.st/icons\"\n  },\n  \"language\": {\n    \"exportLang\": \"Export Language File\",\n    \"importLang\": \"Import Language File\",\n    \"resetLang\": \"Reset to English\",\n    \"editStrings\": \"Edit Strings\",\n    \"editorTitle\": \"Language Editor\",\n    \"searchStrings\": \"Search strings...\",\n    \"saveChanges\": \"Save Changes\",\n    \"all\": \"All\",\n    \"uiGeneral\": \"UI/General\"\n  }\n}</script>\n    <script id=\"topology-state\" type=\"application/json\">{\n  \"nodeData\": {},\n  \"edgeData\": {\n    \"list\": []\n  },\n  \"rectData\": {\n    \"list\": []\n  },\n  \"textData\": {\n    \"list\": []\n  },\n  \"edgeLegend\": {},\n  \"zoneLegend\": {},\n  \"zonePresets\": {},\n  \"nodePositions\": {},\n  \"nodeSizes\": {},\n  \"nodeStyles\": {},\n  \"iconCache\": {},\n  \"page\": {\n    \"title\": \"The One File: The Networkening\",\n    \"background\": \"\",\n    \"topbarBg\": \"rgba(9, 12, 20, 0.9)\",\n    \"topbarBorder\": \"#1f2533\",\n    \"panel\": \"#0b0e13\",\n    \"panelAlt\": \"#10141b\",\n    \"accent\": \"#4fd1c5\",\n    \"sidebarBg\": \"#10141b\",\n    \"btnBg\": \"#0b0e13\",\n    \"btnText\": \"#e2e8f0\",\n    \"tagFill\": \"#1e293b\",\n    \"tagText\": \"#e2e8f0\",\n    \"tagBorder\": \"#475569\",\n    \"inputBg\": \"#0b0e13\",\n    \"inputText\": \"#e2e8f0\",\n    \"inputBorder\": \"#1f2937\",\n    \"inputFont\": \"Inter, system-ui, sans-serif\",\n    \"inputFontSize\": 14,\n    \"toolbarBg\": \"#0f172a\",\n    \"toolbarBorder\": \"#1f2937\",\n    \"toolbarText\": \"#94a3b8\",\n    \"toolbarBtnBg\": \"#0b0e13\",\n    \"toolbarBtnText\": \"#e2e8f0\",\n    \"minimapDots\": \"#94a3b8\",\n    \"canvasHintEnabled\": true,\n    \"canvasHintText\": \"\",\n    \"canvasHintBg\": \"#0f172a\",\n    \"canvasHintColor\": \"#94a3b8\",\n    \"danger\": \"#f56565\",\n    \"textMain\": \"#e2e8f0\",\n    \"textSoft\": \"#94a3b8\",\n    \"topbarHeight\": 100,\n    \"sidebarWidth\": 435,\n    \"mobileFooterHeight\": 20,\n    \"sidebarCollapsed\": false,\n    \"nodeFill\": \"#1e293b\",\n    \"nodeStroke\": \"#475569\",\n    \"nodeTitle\": \"#e2e8f0\",\n    \"nodeSub\": \"#94a3b8\",\n    \"nodeTitleSize\": 18,\n    \"nodeSubSize\": 13,\n    \"nodeFont\": \"Inter, system-ui, sans-serif\",\n    \"defaultEdge\": \"#475569\",\n    \"selectionHandle\": \"#f59e0b\",\n    \"selectionHandleSize\": 8,\n    \"groupIndicator\": \"#4fd1c5\",\n    \"canvasGradientTop\": \"#1e2532\",\n    \"canvasGradientBottom\": \"#050608\",\n    \"canvasBorder\": \"#475569\",\n    \"canvasGrid\": \"#475569\",\n    \"canvasGridSize\": 50,\n    \"rackFrameFill\": \"#0f172a\",\n    \"rackFrameStroke\": \"#4fd1c5\",\n    \"rackLineColor\": \"#475569\",\n    \"rackTextColor\": \"#4fd1c5\",\n    \"viewOnly\": false,\n    \"autoPingEnabled\": false,\n    \"autoPingInterval\": 30\n  },\n  \"autoPingEnabled\": false,\n  \"autoPingInterval\": 30,\n  \"canvas\": {\n    \"zoom\": 1,\n    \"panX\": 0,\n    \"panY\": 0\n  },\n  \"savedTopologyView\": null,\n  \"documentTabs\": [\n    {\n      \"id\": \"main\",\n      \"name\": \"Main Topology\",\n      \"nodes\": {},\n      \"edges\": {\n        \"list\": []\n      },\n      \"positions\": {},\n      \"sizes\": {},\n      \"styles\": {},\n      \"legend\": {},\n      \"rects\": {\n        \"list\": []\n      },\n      \"texts\": {\n        \"list\": []\n      },\n      \"pageState\": {\n        \"title\": \"The One File: The Networkening\",\n        \"background\": \"\",\n        \"topbarBg\": \"rgba(9, 12, 20, 0.9)\",\n        \"topbarBorder\": \"#1f2533\",\n        \"panel\": \"#0b0e13\",\n        \"panelAlt\": \"#10141b\",\n        \"accent\": \"#4fd1c5\",\n        \"sidebarBg\": \"#10141b\",\n        \"btnBg\": \"#0b0e13\",\n        \"btnText\": \"#e2e8f0\",\n        \"tagFill\": \"#1e293b\",\n        \"tagText\": \"#e2e8f0\",\n        \"tagBorder\": \"#475569\",\n        \"inputBg\": \"#0b0e13\",\n        \"inputText\": \"#e2e8f0\",\n        \"inputBorder\": \"#1f2937\",\n        \"inputFont\": \"Inter, system-ui, sans-serif\",\n        \"inputFontSize\": 14,\n        \"toolbarBg\": \"#0f172a\",\n        \"toolbarBorder\": \"#1f2937\",\n        \"toolbarText\": \"#94a3b8\",\n        \"toolbarBtnBg\": \"#0b0e13\",\n        \"toolbarBtnText\": \"#e2e8f0\",\n        \"minimapDots\": \"#94a3b8\",\n        \"canvasHintEnabled\": true,\n        \"canvasHintText\": \"\",\n        \"canvasHintBg\": \"#0f172a\",\n        \"canvasHintColor\": \"#94a3b8\",\n        \"danger\": \"#f56565\",\n        \"textMain\": \"#e2e8f0\",\n        \"textSoft\": \"#94a3b8\",\n        \"topbarHeight\": 100,\n        \"sidebarWidth\": 435,\n        \"mobileFooterHeight\": 20,\n        \"sidebarCollapsed\": false,\n        \"nodeFill\": \"#1e293b\",\n        \"nodeStroke\": \"#475569\",\n        \"nodeTitle\": \"#e2e8f0\",\n        \"nodeSub\": \"#94a3b8\",\n        \"nodeTitleSize\": 18,\n        \"nodeSubSize\": 13,\n        \"nodeFont\": \"Inter, system-ui, sans-serif\",\n        \"defaultEdge\": \"#475569\",\n        \"selectionHandle\": \"#f59e0b\",\n        \"selectionHandleSize\": 8,\n        \"groupIndicator\": \"#4fd1c5\",\n        \"canvasGradientTop\": \"#1e2532\",\n        \"canvasGradientBottom\": \"#050608\",\n        \"canvasBorder\": \"#475569\",\n        \"canvasGrid\": \"#475569\",\n        \"canvasGridSize\": 50,\n        \"rackFrameFill\": \"#0f172a\",\n        \"rackFrameStroke\": \"#4fd1c5\",\n        \"rackLineColor\": \"#475569\",\n        \"rackTextColor\": \"#4fd1c5\",\n        \"viewOnly\": false,\n        \"autoPingEnabled\": false,\n        \"autoPingInterval\": 30\n      }\n    }\n  ],\n  \"currentTabIndex\": 0,\n  \"encryptedSections\": {},\n  \"auditLog\": []\n}</script>\n    <script>\n\tObject.keys(localStorage).forEach(key => {\n        if (key.startsWith('icon-')) {\n          localStorage.removeItem(key);\n        }\n      });\n      const IconLibrary = {\n       libraries: {\n        mdi: {\n         name: 'Material Design Icons',\n         cdnBase: 'https://cdn.jsdelivr.net/npm/@mdi/svg@latest/svg/',\n         metaUrl: 'https://cdn.jsdelivr.net/npm/@mdi/svg@latest/meta.json',\n         icons: []\n        },\n        simple: {\n         name: 'Simple Icons',\n         cdnBase: 'https://cdn.jsdelivr.net/npm/simple-icons@latest/icons/',\n         indexUrl: 'https://cdn.jsdelivr.net/npm/@iconify-json/simple-icons@latest/icons.json',\n         icons: []\n        },\n        selfhst: {\n         name: 'selfh.st/icons',\n         cdnBase: 'https://cdn.jsdelivr.net/gh/selfhst/icons@master/svg/',\n         indexUrl: 'https://raw.githubusercontent.com/selfhst/icons/refs/heads/main/index.json',\n         icons: []\n        }\n       },\n       currentLibrary: 'selfhst',\n       iconCache: {},\n       indexCache: {},\n       indexLoading: {},\n       async loadLibraryIndex(library) {\n        if (this.indexCache[library]) {\n         return this.indexCache[library];\n        }\n        if (this.indexLoading[library]) {\n         return this.indexLoading[library];\n        }\n        const lib = this.libraries[library];\n        this.indexLoading[library] = (async () => {\n         try {\n          if (library === 'selfhst') {\n           const response = await fetch(lib.indexUrl);\n           const data = await response.json();\n           const icons = data.filter(item => item.SVG === \"Yes\").map(item => ({\n            name: item.Reference,\n            displayName: item.Name,\n            tags: item.Tags ? item.Tags.split(',').map(t => t.trim()).filter(t => t) : [],\n            category: item.Category\n           }));\n           this.indexCache[library] = icons;\n           lib.icons = icons;\n           return icons;\n          } else if (library === 'simple') {\n           const response = await fetch('https://cdn.jsdelivr.net/npm/@iconify-json/simple-icons@latest/icons.json');\n           const data = await response.json();\n           const icons = Object.keys(data.icons).map(slug => ({\n            name: slug,\n            displayName: data.icons[slug].title || slug,\n            tags: data.aliases && data.aliases[slug] ? [data.aliases[slug].parent] : [],\n            hex: data.icons[slug].hex\n           }));\n           this.indexCache[library] = icons;\n           lib.icons = icons;\n           return icons;\n          } else if (library === 'mdi') {\n           const response = await fetch(lib.metaUrl);\n           const data = await response.json();\n           const icons = data.map(item => ({\n            name: item.name,\n            displayName: item.name,\n            tags: item.tags || [],\n            author: item.author\n           }));\n           this.indexCache[library] = icons;\n           lib.icons = icons;\n           return icons;\n          }\n         } catch (error) {\n          console.error(`Failed to load index for ${library}:`, error);\n          this.indexCache[library] = [];\n          lib.icons = [];\n          return [];\n         } finally {\n          delete this.indexLoading[library];\n         }\n        })();\n        return this.indexLoading[library];\n       },\n       async getIcon(library, name) {\n        const cacheKey = `${library}-${name}`;\n        if (this.iconCache[cacheKey]) {\n         return this.iconCache[cacheKey];\n        }\n        const lib = this.libraries[library];\n        const url = `${lib.cdnBase}${name}.svg`;\n        try {\n         const response = await fetch(url);\n         if (!response.ok) {\n          throw new Error(`HTTP ${response.status}`);\n         }\n         const svg = await response.text();\n         this.iconCache[cacheKey] = svg;\n         return svg;\n        } catch (error) {\n         console.error(`Failed to fetch icon ${cacheKey}:`, error);\n         return null;\n        }\n       },\n       searchIcons(library, query) {\n        const lib = this.libraries[library];\n        if (!lib.icons.length) return [];\n        const q = query.toLowerCase();\n        return lib.icons.filter(icon => {\n         const nameMatch = icon.name.toLowerCase().includes(q);\n         const displayMatch = icon.displayName && icon.displayName.toLowerCase().includes(q);\n         const tagMatch = icon.tags && icon.tags.some(t => t.toLowerCase().includes(q));\n         const categoryMatch = icon.category && icon.category.toLowerCase().includes(q);\n         return nameMatch || displayMatch || tagMatch || categoryMatch;\n        }).slice(0, 50);\n       }\n      };\n      let iconPickerCallback = null;\n      let selectedNodeIconData = null;\n      let selectedRackIconData = null;\n      let newNodeIconTags = [];\n      let freeDrawMode = false;\n      async function checkNodeStatus(nodeId) {\n       const data = NODE_DATA[nodeId];\n       if (!data || !data.ping || !data.ping.enabled) return;\n       data.ping.status = 'checking';\n       data.ping.lastCheck = new Date().toISOString();\n       data.ping.responseTime = null;\n       updatePingIndicator(nodeId);\n       if (currentNodeId === nodeId) {\n        updatePingStatusDisplay(nodeId);\n       }\n       let url;\n       if (data.ping.protocol === 'custom') {\n        url = data.ping.customUrl;\n       } else {\n        const ip = data.ip || '0.0.0.0';\n        const protocol = data.ping.protocol || 'http';\n        url = `${protocol}://${ip}`;\n       }\n       if (!url) {\n        data.ping.status = 'unknown';\n        updatePingIndicator(nodeId);\n        if (currentNodeId === nodeId) {\n         updatePingStatusDisplay(nodeId);\n        }\n        return;\n       }\n       const timeout = data.ping.timeout || 3000;\n       const startTime = performance.now();\n       try {\n        const result = await Promise.race([\n         new Promise((resolve, reject) => {\n          const img = new Image();\n          img.onload = () => resolve('online');\n          img.onerror = () => resolve('check-fetch');\n          img.src = `${url}/favicon.ico?_=${Date.now()}`;\n         }),\n         new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), timeout))\n        ]);\n        if (result === 'check-fetch') {\n         await Promise.race([\n          fetch(url, { method: 'HEAD', mode: 'no-cors' }),\n          new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), timeout))\n         ]);\n        }\n        data.ping.status = 'online';\n        data.ping.responseTime = Math.round(performance.now() - startTime);\n       } catch (error) {\n        data.ping.status = 'offline';\n        data.ping.responseTime = null;\n       }\n       data.ping.lastCheck = new Date().toISOString();\n       updatePingIndicator(nodeId);\n       if (currentNodeId === nodeId) {\n        updatePingStatusDisplay(nodeId);\n       }\n      }\n      function rgbaToHex(val) {\n      if (!val) return \"#000000\";\n      if (val.startsWith(\"#\")) return val;\n      const m = val.match(/rgba?\\((\\d+),\\s*(\\d+),\\s*(\\d+)/);\n      if (!m) return \"#000000\";\n      const r = Number(m[1]).toString(16).padStart(2, \"0\");\n      const g = Number(m[2]).toString(16).padStart(2, \"0\");\n      const b = Number(m[3]).toString(16).padStart(2, \"0\");\n      return `#${r}${g}${b}`;\n      }\n      function updatePingIndicator(nodeId) {\n      const nodeGroup = document.querySelector(`g[data-node-id=\"${nodeId}\"]`);\n      if (!nodeGroup) return;\n      const data = NODE_DATA[nodeId];\n      if (!data || !data.ping || !data.ping.enabled) {\n       const existingIndicator = nodeGroup.querySelector('.ping-indicator');\n       if (existingIndicator) existingIndicator.remove();\n       return;\n      }\n      let indicator = nodeGroup.querySelector('.ping-indicator');\n      const label = nodeGroup.querySelector('.node-label');\n      if (!indicator && label) {\n       indicator = document.createElementNS('http://www.w3.org/2000/svg', 'circle');\n       indicator.classList.add('ping-indicator');\n       nodeGroup.appendChild(indicator);\n      }\n      if (indicator && label) {\n       const size = savedSizes[nodeId] || getDefaultSize();\n       const radius = Math.max(4, size * 0.06);\n       indicator.setAttribute('r', radius);\n       const labelBBox = label.getBBox();\n       const labelX = parseFloat(label.getAttribute('x') || 0);\n       const labelY = parseFloat(label.getAttribute('y') || 0);\n       const styles = resolveStylesForNode(nodeId);\n      const offX = styles.pingOffsetX || 0;\n      const offY = styles.pingOffsetY || 0;\n      indicator.setAttribute('cx', (labelX - labelBBox.width / 2 - radius * 1.1) + offX);\n      indicator.setAttribute('cy', (labelY - radius * 0.7) + offY);\n      }\n      if (indicator) {\n       indicator.classList.remove('online', 'offline', 'checking');\n       if (data.ping.status) indicator.classList.add(data.ping.status);\n      }\n      }\n      async function checkAllNodesStatus() {\n       const nodesToCheck = Object.keys(NODE_DATA).filter(nodeId => {\n        const data = NODE_DATA[nodeId];\n        return data && data.ping && data.ping.enabled;\n       });\n       const batchSize = 5;\n       for (let i = 0; i < nodesToCheck.length; i += batchSize) {\n        const batch = nodesToCheck.slice(i, i + batchSize);\n        await Promise.all(batch.map(nodeId => checkNodeStatus(nodeId)));\n       }\n      }\n      function startAutoPing() {\n       stopAutoPing();\n       checkAllNodesStatus();\n       updateAutoPingLastRun();\n       autoPingSecondsRemaining = autoPingInterval;\n       autoPingTimer = setInterval(() => {\n        checkAllNodesStatus();\n        updateAutoPingLastRun();\n        autoPingSecondsRemaining = autoPingInterval;\n       }, autoPingInterval * 1000);\n       autoPingCountdown = setInterval(() => {\n        autoPingSecondsRemaining--;\n        updateAutoPingCountdown();\n        if (autoPingSecondsRemaining <= 0) {\n         autoPingSecondsRemaining = autoPingInterval;\n        }\n       }, 1000);\n       updateAutoPingCountdown();\n      }\n      function stopAutoPing() {\n       if (autoPingTimer) {\n        clearInterval(autoPingTimer);\n        autoPingTimer = null;\n       }\n       if (autoPingCountdown) {\n        clearInterval(autoPingCountdown);\n        autoPingCountdown = null;\n       }\n       autoPingSecondsRemaining = 0;\n       updateAutoPingCountdown();\n      }\n      function updateAutoPingCountdown() {\n       const nextCheckEl = document.getElementById('auto-ping-next-check');\n       if (nextCheckEl) {\n        if (autoPingSecondsRemaining > 0 && autoPingEnabled) {\n         const mins = Math.floor(autoPingSecondsRemaining / 60);\n         const secs = autoPingSecondsRemaining % 60;\n         if (mins > 0) {\n          nextCheckEl.textContent = `Next check in: ${mins}m ${secs}s`;\n         } else {\n          nextCheckEl.textContent = `Next check in: ${secs}s`;\n         }\n        } else {\n         nextCheckEl.textContent = 'Next check in: --';\n        }\n       }\n      }\n      function updateAutoPingLastRun() {\n       const lastRunEl = document.getElementById('auto-ping-last-run');\n       if (lastRunEl) {\n        const now = new Date();\n        lastRunEl.textContent = `Last run: ${now.toLocaleTimeString()}`;\n       }\n      }\n      function openIconPicker(callback) {\n       iconPickerCallback = callback;\n       const modal = document.getElementById('icon-picker-modal');\n       modal.classList.add('active');\n       const searchInput = document.getElementById('icon-search');\n       searchInput.style.display = 'none';\n       loadIconsForCurrentLibrary();\n      }\n      function closeIconPicker() {\n       const modal = document.getElementById('icon-picker-modal');\n       modal.classList.remove('active');\n       iconPickerCallback = null;\n      }\n      async function loadIconsForCurrentLibrary() {\n       const body = document.getElementById('icon-picker-body');\n       const libNames = {\n        mdi: 'MDI (Material Design Icons)',\n        simple: 'Simple Icons',\n        selfhst: 'selfh.st/icons'\n       };\n       body.innerHTML = `<div style=\"padding: 20px;\"><p style=\"color: var(--text-soft); margin-bottom: 15px; text-align: center;\">Search ${libNames[IconLibrary.currentLibrary]}:</p><input type=\"text\" id=\"icon-search-field\" placeholder=\"Search icons...\" style=\"width: 100%; padding: 12px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 6px; color: var(--text-main); font-size: 16px; margin-bottom: 20px;\"><div id=\"icon-grid-container\" style=\"max-height: 400px; overflow-y: auto;\"><div style=\"text-align: center; color: var(--text-soft); padding: 40px;\">Loading icons...</div></div></div>`;\n       const searchField = document.getElementById('icon-search-field');\n       const gridContainer = document.getElementById('icon-grid-container');\n       await IconLibrary.loadLibraryIndex(IconLibrary.currentLibrary);\n       const renderIcons = (icons) => {\n        if (!icons || icons.length === 0) {\n         gridContainer.innerHTML = '<div style=\"text-align: center; color: var(--text-soft); padding: 40px;\">No icons found</div>';\n         return;\n        }\n        const grid = document.createElement('div');\n        grid.style.cssText = 'display: grid; grid-template-columns: repeat(auto-fill, minmax(100px, 1fr)); gap: 15px; padding: 10px;';\n        icons.forEach(icon => {\n         const item = document.createElement('div');\n         item.style.cssText = 'display: flex; flex-direction: column; align-items: center; gap: 8px; padding: 15px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 6px; cursor: pointer; transition: all 0.2s;';\n         item.onmouseover = () => {\n          item.style.background = 'var(--panel)';\n          item.style.borderColor = 'var(--accent)';\n         };\n         item.onmouseout = () => {\n          item.style.background = 'var(--panel-alt)';\n          item.style.borderColor = 'var(--edge-main)';\n         };\n         const iconPreview = document.createElement('div');\n         iconPreview.style.cssText = 'width: 48px; height: 48px; display: flex; align-items: center; justify-content: center;';\n         iconPreview.innerHTML = '<div style=\"color: var(--text-soft); font-size: 12px;\">...</div>';\n         IconLibrary.getIcon(IconLibrary.currentLibrary, icon.name).then(svg => {\n          if (svg) {\n           const parser = new DOMParser();\n           const doc = parser.parseFromString(svg, 'image/svg+xml');\n           const svgEl = doc.querySelector('svg');\n           if (svgEl) {\n            svgEl.setAttribute('width', '48');\n            svgEl.setAttribute('height', '48');\n            svgEl.style.fill = 'var(--text-main)';\n            iconPreview.innerHTML = '';\n            iconPreview.appendChild(svgEl);\n           }\n          }\n         });\n         const name = document.createElement('div');\n         name.textContent = icon.displayName || icon.name;\n         name.style.cssText = 'font-size: 11px; color: var(--text-soft); text-align: center; word-break: break-word; max-width: 100%;';\n         item.appendChild(iconPreview);\n         item.appendChild(name);\n         item.addEventListener('click', async () => {\n          const svg = await IconLibrary.getIcon(IconLibrary.currentLibrary, icon.name);\n          if (iconPickerCallback && svg) {\n           iconPickerCallback({\n            library: IconLibrary.currentLibrary,\n            name: icon.name,\n            svg: svg\n           });\n          }\n          closeIconPicker();\n         });\n         grid.appendChild(item);\n        });\n        gridContainer.innerHTML = '';\n        gridContainer.appendChild(grid);\n       };\n       gridContainer.innerHTML = '<div style=\"text-align: center; color: var(--text-soft); padding: 40px;\">Type to search icons</div>';\n       let searchTimeout;\n       searchField.addEventListener('input', (e) => {\n        clearTimeout(searchTimeout);\n        const query = e.target.value.trim();\n        searchTimeout = setTimeout(() => {\n         if (!query) {\n          gridContainer.innerHTML = '<div style=\"text-align: center; color: var(--text-soft); padding: 40px;\">Type to search icons</div>';\n          return;\n         }\n         const results = IconLibrary.searchIcons(IconLibrary.currentLibrary, query);\n         renderIcons(results);\n        }, 300);\n       });\n       searchField.focus();\n      }\n      async function displayIcons(icons) {\n       const body = document.getElementById('icon-picker-body');\n       const grid = document.createElement('div');\n       grid.className = 'icon-grid';\n       for (const icon of icons) {\n        const item = document.createElement('div');\n        item.className = 'icon-item';\n        const svg = await IconLibrary.getIcon(icon.library, icon.name);\n        if (svg) {\n         const parser = new DOMParser();\n         const doc = parser.parseFromString(svg, 'image/svg+xml');\n         const svgEl = doc.querySelector('svg');\n         if (svgEl) {\n          item.innerHTML = svgEl.outerHTML;\n         }\n        } else {\n         item.innerHTML = '<svg width = \"32\" height = \"32\"><rect width = \"32\" height = \"32\" fill = \"currentColor\"/> </svg>';\n        }\n        const name = document.createElement('div');\n        name.className = 'icon-item-name';\n        name.textContent = icon.name;\n        item.appendChild(name);\n        item.addEventListener('click', () => {\n         if (iconPickerCallback) {\n          iconPickerCallback({\n           library: icon.library,\n           name: icon.name,\n           svg: svg\n          });\n         }\n         closeIconPicker();\n        });\n        grid.appendChild(item);\n       }\n       body.innerHTML = '';\n       body.appendChild(grid);\n      }\n      window.addEventListener('DOMContentLoaded', () => {\n       document.querySelectorAll('.icon-picker-tab').forEach(tab => {\n        tab.addEventListener('click', () => {\n         document.querySelectorAll('.icon-picker-tab').forEach(t => t.classList.remove('active'));\n         tab.classList.add('active');\n         IconLibrary.currentLibrary = tab.dataset.library;\n         loadIconsForCurrentLibrary();\n        });\n       });\n       document.getElementById('icon-picker-cancel').addEventListener('click', closeIconPicker);\n       document.getElementById('icon-picker-modal').addEventListener('click', (e) => {\n        if (e.target.id === 'icon-picker-modal') {\n         closeIconPicker();\n        }\n       });\n      });\n      let textDrawMode = false;\n      const BASE_NODE_DATA = JSON.parse(document.getElementById(\"nodes-json\").textContent);\n      const LABELS = JSON.parse(document.getElementById(\"labels-json\").textContent);\n\n      const DEFAULT_LANG = JSON.parse(document.getElementById(\"lang-json\").textContent);\n      let LANG = JSON.parse(JSON.stringify(DEFAULT_LANG));\n      let CUSTOM_LANG = null;\n\n      function deepMerge(target, source) {\n        const result = { ...target };\n        for (const key in source) {\n          if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {\n            result[key] = deepMerge(target[key] || {}, source[key]);\n          } else {\n            result[key] = source[key];\n          }\n        }\n        return result;\n      }\n\n      function t(key, replacements = {}) {\n        const mode = PAGE_STATE?.mappingMode || 'network';\n        const modeLabels = LANG.modes?.[mode] || LANG.modes?.network || {};\n\n        let text = key.split('.').reduce((obj, k) => obj?.[k], LANG);\n        if (text === undefined || text === null) return key;\n        if (typeof text === 'object') return key;\n\n        text = text.replace(/\\{node\\}/g, modeLabels.node || 'Node');\n        text = text.replace(/\\{nodes\\}/g, modeLabels.nodes || 'Nodes');\n        text = text.replace(/\\{connection\\}/g, modeLabels.connection || 'Connection');\n        text = text.replace(/\\{connections\\}/g, modeLabels.connections || 'Connections');\n        text = text.replace(/\\{rack\\}/g, modeLabels.rack || 'Rack');\n        text = text.replace(/\\{racks\\}/g, modeLabels.racks || 'Racks');\n\n        for (const [k, v] of Object.entries(replacements)) {\n          text = text.replace(new RegExp(`\\\\{${k}\\\\}`, 'g'), v);\n        }\n\n        return text;\n      }\n\n      function applyLanguage() {\n        const isRTL = LANG._meta?.rtl === true;\n        document.documentElement.dir = isRTL ? 'rtl' : 'ltr';\n        document.documentElement.classList.toggle('rtl', isRTL);\n\n        document.querySelectorAll('[data-lang]').forEach(el => {\n          const key = el.dataset.lang;\n          const attr = el.dataset.langAttr || 'textContent';\n          const translated = t(key);\n          if (attr === 'placeholder') {\n            el.placeholder = translated;\n          } else if (attr === 'title') {\n            el.title = translated;\n          } else if (attr === 'innerHTML') {\n            el.innerHTML = translated;\n          } else if (attr === 'label') {\n            el.label = translated;\n          } else {\n            el.textContent = translated;\n          }\n        });\n        document.querySelectorAll('[data-lang-label]').forEach(el => {\n          el.label = t(el.dataset.langLabel);\n        });\n        if (typeof applyMappingModeLabels === 'function') {\n          applyMappingModeLabels();\n        }\n        if (typeof updateLayerLabels === 'function') {\n          updateLayerLabels();\n        }\n      }\n\n      function saveLanguageToStorage() {\n        if (CUSTOM_LANG) {\n          localStorage.setItem('theNetworkeningCustomLang', JSON.stringify(CUSTOM_LANG));\n        } else {\n          localStorage.removeItem('theNetworkeningCustomLang');\n        }\n      }\n\n      function loadLanguageFromStorage() {\n        const stored = localStorage.getItem('theNetworkeningCustomLang');\n        if (stored) {\n          try {\n            CUSTOM_LANG = JSON.parse(stored);\n            LANG = deepMerge(DEFAULT_LANG, CUSTOM_LANG);\n          } catch (e) {\n            console.warn('Failed to load custom language:', e);\n          }\n        }\n      }\n\n      function exportLanguageFile() {\n        const langToExport = CUSTOM_LANG || LANG;\n        const blob = new Blob([JSON.stringify(langToExport, null, 2)], { type: 'application/json' });\n        const url = URL.createObjectURL(blob);\n        const a = document.createElement('a');\n        a.href = url;\n        a.download = `language-${langToExport._meta?.code || 'custom'}.json`;\n        a.click();\n        URL.revokeObjectURL(url);\n      }\n\n      function importLanguageFile(file) {\n        const reader = new FileReader();\n        reader.onload = (e) => {\n          try {\n            const imported = JSON.parse(e.target.result);\n            if (!imported._meta || !imported.modes) {\n              throw new Error('Invalid language file structure');\n            }\n            const currentEdition = DEFAULT_LANG._meta?.edition || 'networkening';\n            const importedEdition = imported._meta?.edition;\n            if (importedEdition && importedEdition !== currentEdition) {\n              throw new Error(`Edition mismatch: This language file is for \"${importedEdition}\" edition but you are using \"${currentEdition}\" edition`);\n            }\n            CUSTOM_LANG = imported;\n            LANG = deepMerge(DEFAULT_LANG, CUSTOM_LANG);\n            applyLanguage();\n            saveLanguageToStorage();\n            updateCurrentLangDisplay();\n            showAlert(t('messages.langImportSuccess'));\n          } catch (err) {\n            showAlert(t('messages.invalidLangFile') + ': ' + err.message);\n          }\n        };\n        reader.readAsText(file);\n      }\n\n      function resetToDefaultLanguage() {\n        LANG = JSON.parse(JSON.stringify(DEFAULT_LANG));\n        CUSTOM_LANG = null;\n        localStorage.removeItem('theNetworkeningCustomLang');\n        applyLanguage();\n        updateCurrentLangDisplay();\n        showAlert(t('messages.langResetSuccess'));\n      }\n\n      function updateCurrentLangDisplay() {\n        const langNameEl = document.getElementById('current-lang-name');\n        if (langNameEl) {\n          langNameEl.textContent = LANG._meta?.name || 'English';\n          if (CUSTOM_LANG) {\n            langNameEl.textContent += ' (Custom)';\n          }\n        }\n      }\n\n      function escapeHtml(str) {\n        const div = document.createElement('div');\n        div.textContent = str;\n        return div.innerHTML;\n      }\n\n      function flattenLangObject(obj, prefix = '') {\n        const result = [];\n        for (const [key, value] of Object.entries(obj)) {\n          if (key === '_meta') continue;\n          const fullKey = prefix ? `${prefix}.${key}` : key;\n          if (typeof value === 'object' && value !== null) {\n            result.push(...flattenLangObject(value, fullKey));\n          } else {\n            result.push({ key: fullKey, value: String(value), category: fullKey.split('.')[0] });\n          }\n        }\n        return result;\n      }\n\n      function setNestedValue(obj, path, value) {\n        const keys = path.split('.');\n        let current = obj;\n        for (let i = 0; i < keys.length - 1; i++) {\n          if (!current[keys[i]]) current[keys[i]] = {};\n          current = current[keys[i]];\n        }\n        current[keys[keys.length - 1]] = value;\n      }\n\n      let langEditorStrings = [];\n      let langEditorFilter = 'all';\n      let langEditorSearch = '';\n\n      function openLanguageEditor() {\n        langEditorStrings = flattenLangObject(LANG);\n        langEditorFilter = 'all';\n        langEditorSearch = '';\n        renderLangEditorList();\n\n        document.querySelectorAll('.lang-editor-tabs .lang-tab').forEach(tab => {\n          tab.onclick = () => {\n            document.querySelectorAll('.lang-editor-tabs .lang-tab').forEach(t => {\n              t.style.background = 'var(--panel)';\n              t.style.color = 'var(--text-main)';\n              t.classList.remove('active');\n            });\n            tab.style.background = 'var(--accent)';\n            tab.style.color = 'var(--bg)';\n            tab.classList.add('active');\n            langEditorFilter = tab.dataset.filter;\n            renderLangEditorList();\n          };\n        });\n        const searchInput = document.getElementById('lang-search');\n        if (searchInput) {\n          searchInput.value = '';\n          searchInput.oninput = () => {\n            langEditorSearch = searchInput.value.toLowerCase();\n            renderLangEditorList();\n          };\n        }\n        document.getElementById('language-editor-modal').style.display = 'flex';\n      }\n\n      function closeLanguageEditor() {\n        document.getElementById('language-editor-modal').style.display = 'none';\n      }\n\n      function renderLangEditorList() {\n        const list = document.querySelector('.lang-editor-list');\n        if (!list) return;\n\n        const filtered = langEditorStrings.filter(s => {\n          if (langEditorFilter !== 'all') {\n            if (!s.key.startsWith(langEditorFilter)) return false;\n          }\n          if (langEditorSearch) {\n            const searchLower = langEditorSearch.toLowerCase();\n            if (!s.key.toLowerCase().includes(searchLower) &&\n                !s.value.toLowerCase().includes(searchLower)) {\n              return false;\n            }\n          }\n          return true;\n        });\n\n        if (filtered.length === 0) {\n          list.innerHTML = '<div style=\"padding:20px;text-align:center;color:var(--text-soft);\">' + t(\"emptyStates.noStringsFound\") + '</div>';\n          return;\n        }\n\n        list.innerHTML = filtered.map(s => `\n          <div style=\"display:flex;gap:12px;padding:8px 12px;border-bottom:1px solid var(--edge-main);align-items:center;\">\n            <span style=\"flex:0 0 280px;font-size:11px;color:var(--text-soft);font-family:monospace;word-break:break-all;\">${escapeHtml(s.key)}</span>\n            <input type=\"text\" class=\"lang-value\" data-key=\"${escapeHtml(s.key)}\" value=\"${escapeHtml(s.value)}\"\n              style=\"flex:1;padding:6px 10px;background:var(--panel-alt);color:var(--text-main);border:1px solid var(--edge-main);border-radius:4px;font-size:13px;\">\n          </div>\n        `).join('');\n      }\n\n      function saveLangEdits() {\n        const inputs = document.querySelectorAll('.lang-editor-list .lang-value');\n        let hasChanges = false;\n\n        inputs.forEach(input => {\n          const key = input.dataset.key;\n          const newValue = input.value;\n\n          const original = langEditorStrings.find(s => s.key === key);\n          if (original && original.value !== newValue) {\n            setNestedValue(LANG, key, newValue);\n            original.value = newValue;\n            hasChanges = true;\n          }\n        });\n\n        if (hasChanges) {\n          CUSTOM_LANG = JSON.parse(JSON.stringify(LANG));\n          saveLanguageToStorage();\n          applyLanguage();\n          updateCurrentLangDisplay();\n        }\n\n        closeLanguageEditor();\n      }\n\n      const SHAPE_CATEGORIES = {\n        basic: [\n          { value: 'circle', label: 'Circle' },\n          { value: 'square', label: 'Square' },\n          { value: 'rectangle', label: 'Rectangle' },\n          { value: 'triangle', label: 'Triangle' },\n          { value: 'hexagon', label: 'Hexagon' },\n          { value: 'diamond', label: 'Diamond' },\n          { value: 'star', label: 'Star' },\n          { value: 'stop-sign', label: 'Stop Sign' },\n          { value: 'octagon', label: 'Octagon' },\n          { value: 'pentagon', label: 'Pentagon' },\n          { value: 'cross', label: 'Cross' },\n          { value: 'rounded-square', label: 'Rounded Square' },\n          { value: 'pill', label: 'Pill' },\n          { value: 'parallelogram', label: 'Parallelogram' },\n          { value: 'trapezoid', label: 'Trapezoid' }\n        ],\n        computers: [\n          { value: 'server', label: 'Server' },\n          { value: 'pc', label: 'PC / Desktop' },\n          { value: 'laptop', label: 'Laptop' },\n          { value: 'phone', label: 'Phone / Mobile' },\n          { value: 'printer', label: 'Printer' },\n          { value: 'pi', label: 'Raspberry Pi' },\n          { value: 'sensor', label: 'Sensor / IoT' }\n        ],\n        network: [\n          { value: 'router', label: 'Router' },\n          { value: 'switch', label: 'Switch' },\n          { value: 'firewall', label: 'Firewall' },\n          { value: 'access-point', label: 'Access Point' },\n          { value: 'load-balancer', label: 'Load Balancer' },\n          { value: 'gateway', label: 'Gateway' },\n          { value: 'vpn', label: 'VPN / Tunnel' },\n          { value: 'nas', label: 'NAS / Storage' }\n        ],\n        cloud: [\n          { value: 'cloud', label: 'Cloud' },\n          { value: 'database', label: 'Database' },\n          { value: 'docker', label: 'Docker' },\n          { value: 'container', label: 'Container' },\n          { value: 'vm', label: 'Virtual Machine' },\n          { value: 'kubernetes', label: 'Kubernetes' },\n          { value: 'api', label: 'API / Endpoint' },\n          { value: 'queue', label: 'Queue / Message' },\n          { value: 'lambda', label: 'Lambda / Function' },\n          { value: 'bucket', label: 'Bucket / S3' }\n        ],\n        security: [\n          { value: 'shield', label: 'Shield' },\n          { value: 'camera', label: 'Camera / CCTV' },\n          { value: 'monitor', label: 'Monitor / Dashboard' }\n        ],\n        smarthome: [\n          { value: 'thermostat', label: 'Thermostat' },\n          { value: 'doorbell', label: 'Video Doorbell' },\n          { value: 'smart-lock', label: 'Smart Lock' },\n          { value: 'smart-bulb', label: 'Smart Bulb' },\n          { value: 'smart-plug', label: 'Smart Plug' },\n          { value: 'smart-speaker', label: 'Smart Speaker' },\n          { value: 'smart-tv', label: 'Smart TV' },\n          { value: 'hub', label: 'Smart Hub' },\n          { value: 'smoke-detector', label: 'Smoke Detector' },\n          { value: 'motion-sensor', label: 'Motion Sensor' },\n          { value: 'garage', label: 'Garage Door' },\n          { value: 'sprinkler', label: 'Sprinkler' },\n          { value: 'vacuum', label: 'Robot Vacuum' }\n        ],\n        sports: [\n          { value: 'basketball-ball', label: 'Basketball' },\n          { value: 'football-ball', label: 'Football' },\n          { value: 'soccer-ball', label: 'Soccer Ball' },\n          { value: 'hockey-puck', label: 'Hockey Puck' },\n          { value: 'baseball-ball', label: 'Baseball' },\n          { value: 'tennis-ball', label: 'Tennis Ball' },\n          { value: 'volleyball', label: 'Volleyball' },\n          { value: 'rugby-ball', label: 'Rugby Ball' },\n          { value: 'golf-ball', label: 'Golf Ball' },\n          { value: 'frisbee', label: 'Frisbee' },\n          { value: 'cricket-ball', label: 'Cricket Ball' },\n          { value: 'lacrosse-stick', label: 'Lacrosse Stick' },\n          { value: 'golf-flag', label: 'Golf Flag' },\n          { value: 'tactical-x', label: 'Tactical X' },\n          { value: 'tactical-o', label: 'Tactical O' },\n          { value: 'tactical-star', label: 'Tactical Star' }\n        ],\n        rack: [\n          { value: 'patch-panel', label: 'Patch Panel' },\n          { value: 'ups', label: 'UPS' },\n          { value: 'pdu', label: 'PDU' },\n          { value: 'rack-shelf', label: 'Rack Shelf' },\n          { value: 'blank-panel', label: 'Blank Panel' },\n          { value: 'cable-management', label: 'Cable Management' },\n          { value: 'kvm', label: 'KVM Switch' }\n        ]\n      };\n\n      const CANVAS_OPTIONS = {\n        network: [\n          { value: 'grid', label: 'Standard Grid' },\n          { value: 'dots', label: 'Dot Grid' },\n          { value: 'blueprint', label: 'Blueprint Grid' },\n          { value: 'none', label: 'No Grid' }\n        ],\n        mindmap: [],\n        sports: [\n          { value: 'basketball', label: 'Basketball Court' },\n          { value: 'football', label: 'Football Field' },\n          { value: 'soccer', label: 'Soccer Pitch' },\n          { value: 'hockey', label: 'Hockey Rink' },\n          { value: 'baseball', label: 'Baseball Diamond' },\n          { value: 'tennis', label: 'Tennis Court' }\n        ],\n        smarthome: [\n          { value: 'blueprint', label: 'Blueprint Grid' },\n          { value: 'grid', label: 'Standard Grid' },\n          { value: 'dots', label: 'Dot Grid' },\n          { value: 'none', label: 'No Grid' }\n        ],\n        floorplan: []\n      };\n\n      const MODE_DEFAULT_CATEGORIES = {\n        network: 'network',\n        mindmap: 'basic',\n        sports: 'sports',\n        smarthome: 'smarthome',\n        floorplan: 'basic'\n      };\n\n      function findCategoryForShape(shape) {\n        if (!shape) return MODE_DEFAULT_CATEGORIES[PAGE_STATE.mappingMode || 'network'] || 'basic';\n        for (const [category, shapes] of Object.entries(SHAPE_CATEGORIES)) {\n          if (shapes.some(s => s.value === shape)) {\n            return category;\n          }\n        }\n        return MODE_DEFAULT_CATEGORIES[PAGE_STATE.mappingMode || 'network'] || 'basic';\n      }\n\n      let currentNodeId = null;\n      let currentEdgeId = null;\n      let currentStyleScope = \"all\";\n      let NODE_DATA = {};\n      let EDGE_DATA = {\n       list: []\n      };\n      let currentRectId = null;\n      let currentTextId = null;\n      let currentImageId = null;\n      let rectDrawMode = false;\n      let RECT_DATA = {\n       list: []\n      };\n      let TEXT_DATA = {\n       list: []\n      };\n      let IMAGE_DATA = {\n       list: []\n      };\n      const MAX_CANVAS_IMAGES = 40;\n      const MAX_IMAGE_SIZE = 250;\n      let EDGE_LEGEND = {};\n      let savedPositions = {};\n      let savedSizes = {};\n      let savedStyles = {};\n      let legendCollapsed = false;\n      let minimapCollapsed = false;\n      let drawToolbarCollapsed = false;\n      let currentView = {\n       mode: \"topology\",\n       rackId: null\n      };\n      let savedTopologyView = null;\n      let activeLayers = new Set([\"layer1\", \"layer2\", \"layer3\", \"layer4\"]);\n      let topologyToolbarCollapsed = false;\n      let legendMiniBtn = null;\n      let minimapMiniBtn = null;\n      let drawToolbarMiniBtn = null;\n      let topologyToolbarMiniBtn = null;\n      let autoPingEnabled = false;\n      let autoPingInterval = 30;\n      let autoPingTimer = null;\n      let autoPingCountdown = null;\n      let autoPingSecondsRemaining = 0;\n      const ROLLBACK_STORAGE_KEY = \"theonefile_rollback_history\";\n      let rollbackVersions = [];\n      const MAX_ROLLBACK_VERSIONS = 50;\n      let currentRollbackIndex = -1;\n      const AUDIT_STORAGE_KEY = \"theonefile_audit_log\";\n      let auditLog = [];\n      const MAX_AUDIT_ENTRIES = 1000;\n      let savedStyleSets = [];\n      let documentTabs = [{\n        id: \"main\",\n        name: \"Main Topology\",\n        nodes: {},\n        edges: { list: [] },\n        positions: {},\n        sizes: {},\n        styles: {},\n        legend: {},\n        rects: { list: [] },\n        texts: { list: [] },\n      pageState: null\n      }];\n      let currentTabIndex = 0;\n      let encryptedSections = {};\n      let undoStack = [];\n      let redoStack = [];\n      const MAX_UNDO_STACK = 50;\n      let forgeDebounceTimer = null;\n      let forgeImmediate = false;\n      let currentSearchQuery = \"\";\n      let currentSearchResults = [];\n      const AUTOSAVE_DB_NAME = \"TheOneFileNetworkeningAutosave\";\n      const AUTOSAVE_STORE_NAME = \"drafts\";\n      const AUTOSAVE_KEY = \"currentDraft\";\n      const AUTOSAVE_INTERVAL = 30000;\n      let autosaveTimer = null;\n      let lastAutosaveTime = 0;\n      let selectedNodes = new Set();\n      let selectedEdges = new Set();\n      let selectedRects = new Set();\n      let selectedTexts = new Set();\n      let isSelecting = false;\n      let selectionStart = null;\n      let preDragSelectedNodes = new Set();\n      let preDragSelectedEdges = new Set();\n      let preDragSelectedRects = new Set();\n      let preDragSelectedTexts = new Set();\n      let selectionRect = null;\n      let isDraggingSelection = false;\n      let dragSelectionStart = null;\n      let selectionBoxStyle = {\n       fillColor: \"#4fd1c5\",\n       fillOpacity: 0.1,\n       strokeColor: \"#4fd1c5\",\n       strokeWidth: 2,\n       strokeDasharray: \"5,5\"\n      };\n      let clipboard = null;\n\t  function escapeHtml(str) {\n       if (!str) return '';\n       return String(str).replace(/[&<>\"']/g, c => ({\n        '&': '&amp;', '<': '&lt;', '>': '&gt;', '\"': '&quot;', \"'\": '&#39;'\n       }[c]));\n      }\n      function showAlert(message, title) {\n       return new Promise((resolve) => {\n        const modal = document.getElementById(\"dialog-modal\");\n        const titleEl = document.getElementById(\"dialog-modal-title\");\n        const messageEl = document.getElementById(\"dialog-modal-message\");\n        const inputEl = document.getElementById(\"dialog-modal-input\");\n        const okBtn = document.getElementById(\"dialog-modal-ok\");\n        const cancelBtn = document.getElementById(\"dialog-modal-cancel\");\n        titleEl.textContent = title || t(\"dialogs.alert\");\n        messageEl.textContent = message;\n        inputEl.style.display = \"none\";\n        cancelBtn.style.display = \"none\";\n        okBtn.textContent = t(\"dialogs.ok\");\n        modal.classList.add(\"active\");\n        const cleanup = () => {\n         modal.classList.remove(\"active\");\n         okBtn.removeEventListener(\"click\", handleOk);\n         modal.removeEventListener(\"click\", bgHandler);\n        };\n        const handleOk = () => { cleanup(); resolve(true); };\n        const bgHandler = (e) => { if (e.target === modal) { cleanup(); resolve(true); } };\n        okBtn.addEventListener(\"click\", handleOk);\n        modal.addEventListener(\"click\", bgHandler);\n       });\n      }\n      function showConfirm(message, title) {\n       return new Promise((resolve) => {\n        const modal = document.getElementById(\"dialog-modal\");\n        const titleEl = document.getElementById(\"dialog-modal-title\");\n        const messageEl = document.getElementById(\"dialog-modal-message\");\n        const inputEl = document.getElementById(\"dialog-modal-input\");\n        const okBtn = document.getElementById(\"dialog-modal-ok\");\n        const cancelBtn = document.getElementById(\"dialog-modal-cancel\");\n        titleEl.textContent = title || t(\"dialogs.confirm\");\n        messageEl.textContent = message;\n        inputEl.style.display = \"none\";\n        cancelBtn.style.display = \"\";\n        okBtn.textContent = t(\"dialogs.ok\");\n        cancelBtn.textContent = t(\"dialogs.cancel\");\n        modal.classList.add(\"active\");\n        const cleanup = () => {\n         modal.classList.remove(\"active\");\n         okBtn.removeEventListener(\"click\", handleOk);\n         cancelBtn.removeEventListener(\"click\", handleCancel);\n         modal.removeEventListener(\"click\", bgHandler);\n        };\n        const handleOk = () => { cleanup(); resolve(true); };\n        const handleCancel = () => { cleanup(); resolve(false); };\n        const bgHandler = (e) => { if (e.target === modal) handleCancel(); };\n        okBtn.addEventListener(\"click\", handleOk);\n        cancelBtn.addEventListener(\"click\", handleCancel);\n        modal.addEventListener(\"click\", bgHandler);\n       });\n      }\n      function showPrompt(message, defaultValue, title) {\n       return new Promise((resolve) => {\n        const modal = document.getElementById(\"dialog-modal\");\n        const titleEl = document.getElementById(\"dialog-modal-title\");\n        const messageEl = document.getElementById(\"dialog-modal-message\");\n        const inputEl = document.getElementById(\"dialog-modal-input\");\n        const okBtn = document.getElementById(\"dialog-modal-ok\");\n        const cancelBtn = document.getElementById(\"dialog-modal-cancel\");\n        titleEl.textContent = title || t(\"dialogs.prompt\");\n        messageEl.textContent = message;\n        inputEl.style.display = \"\";\n        inputEl.value = defaultValue || \"\";\n        cancelBtn.style.display = \"\";\n        okBtn.textContent = t(\"dialogs.ok\");\n        cancelBtn.textContent = t(\"dialogs.cancel\");\n        modal.classList.add(\"active\");\n        inputEl.focus();\n        inputEl.select();\n        const cleanup = () => {\n         modal.classList.remove(\"active\");\n         okBtn.removeEventListener(\"click\", handleOk);\n         cancelBtn.removeEventListener(\"click\", handleCancel);\n         inputEl.removeEventListener(\"keypress\", handleEnter);\n         modal.removeEventListener(\"click\", bgHandler);\n        };\n        const handleOk = () => { cleanup(); resolve(inputEl.value); };\n        const handleCancel = () => { cleanup(); resolve(null); };\n        const handleEnter = (e) => { if (e.key === \"Enter\") handleOk(); };\n        const bgHandler = (e) => { if (e.target === modal) handleCancel(); };\n        okBtn.addEventListener(\"click\", handleOk);\n        cancelBtn.addEventListener(\"click\", handleCancel);\n        inputEl.addEventListener(\"keypress\", handleEnter);\n        modal.addEventListener(\"click\", bgHandler);\n       });\n      }\n      const MobileManager = {\n        isMobile: false,\n        detect() {\n          if (navigator.userAgentData?.mobile === true) {\n            this.isMobile = true;\n            return true;\n          }\n          const coarse = matchMedia(\"(pointer: coarse)\").matches;\n          const width = window.innerWidth <= 900;\n          const portrait = matchMedia(\"(orientation: portrait)\").matches;\n          this.isMobile = coarse;\n          return this.isMobile;\n        },\n        applyInitialCollapse() {\n          if (!this.isMobile) return;\n          legendCollapsed = true;\n          minimapCollapsed = true;\n          drawToolbarCollapsed = true;\n          topologyToolbarCollapsed = true;\n          if (typeof updateLegendVisibility === \"function\") updateLegendVisibility();\n          if (typeof updateMinimapVisibility === \"function\") updateMinimapVisibility();\n          if (typeof updateDrawToolbarVisibility === \"function\") updateDrawToolbarVisibility();\n          if (typeof updateTopologyToolbarVisibility === \"function\") updateTopologyToolbarVisibility();\n        },\n        updateBulkToolbar() {\n          const desktop = document.getElementById(\"bulk-toolbar\");\n          const mobile = document.getElementById(\"bulk-toolbar-mobile\");\n          if (!desktop || !mobile) return;\n          if (typeof isViewOnly === 'function' && isViewOnly()) {\n           desktop.style.display = \"none\";\n           mobile.style.display = \"none\";\n           return;\n          }\n          if (typeof selectedNodes === 'undefined' || selectedNodes.size === 0) {\n           desktop.style.display = \"none\";\n           mobile.style.display = \"none\";\n           return;\n          }\n          const isVisible = desktop.style.display !== \"none\" || mobile.style.display !== \"none\";\n          if (this.isMobile) {\n            desktop.style.display = \"none\";\n            if (isVisible) {\n              mobile.style.display = \"flex\";\n            }\n          } else {\n            mobile.style.display = \"none\";\n            if (isVisible) {\n              desktop.style.display = \"flex\";\n            }\n          }\n        },\n        updateCanvasHint() {\n      const hint = document.getElementById(\"canvas-hint\");\n      if (!hint) return;\n      if (PAGE_STATE.canvasHintText !== undefined && PAGE_STATE.canvasHintText !== \"\") {\n        hint.textContent = PAGE_STATE.canvasHintText;\n        return;\n      }\n      const items = this.isMobile\n      ? [\n        \"Pinch to zoom\",\n        \"Drag to pan\",\n        \"Add node from top menu\",\n        \"Double tap to clone and align\",\n        \"Double tap to select multiple\",\n        \"You have the power\",\n        \"Your time is NOW!\",\n      ]\n      : [\n        \"Scroll to zoom\",\n        \"Drag to pan\",\n        \"Right click to clone and align\",\n        \"Right click to select multiple\",\n        \"You have the power\",\n        \"Your time is NOW!\",\n      ];\n      const list = document.createElement(\"ul\");\n      for (const item of items) {\n      const li = document.createElement(\"li\");\n      li.textContent = item;\n      list.appendChild(li);\n      }\n      hint.replaceChildren(list);\n      },\n        autoSelectStyleScope() {\n          const scope = document.getElementById(\"style-scope\");\n          if (!scope) return;\n          if (this.isMobile) scope.value = \"mobile\";\n        },\n        updateToolbarStack() {\n          if (!this.isMobile) return;\n          const draw = document.getElementById(\"draw-toolbar\");\n          const topo = document.getElementById(\"topology-toolbar\");\n          if (!draw || !topo) return;\n          const h = draw.getBoundingClientRect().height;\n          document.documentElement.style.setProperty(\"--draw-toolbar-height\", h + \"px\");\n        },\n        updateLayoutControls() {\n          const sidebarRow = document.getElementById(\"sidebar-width-row\");\n          const footerRow = document.getElementById(\"mobile-footer-row\");\n          if (sidebarRow && footerRow) {\n            if (this.isMobile) {\n              sidebarRow.style.display = \"none\";\n              footerRow.style.display = \"flex\";\n            } else {\n              sidebarRow.style.display = \"flex\";\n              footerRow.style.display = \"none\";\n            }\n          }\n        },\n        applyAll() {\n          this.detect();\n          this.applyInitialCollapse();\n          this.updateBulkToolbar();\n          this.updateCanvasHint();\n          this.autoSelectStyleScope();\n          this.updateToolbarStack();\n          this.updateLayoutControls();\n        }\n      };\n      function isMobileDevice() {\n        return MobileManager.isMobile;\n      }\n      function ensureLegendMiniButton() {\n\t\tif (legendMiniBtn) return legendMiniBtn;\n\t\tconst handleClick = (e) => {\n\t\t\te.stopPropagation();\n\t\t\te.preventDefault();\n\t\t\tlegendCollapsed = false;\n\t\t\tupdateLegendVisibility();\n\t\t};\n\t\tconst preventTouch = (e) => { e.preventDefault(); };\n\t\tconst existing = document.getElementById(\"edge-legend-mini\");\n\t\tif (existing) {\n\t\t\texisting.addEventListener(\"click\", handleClick);\n\t\t\texisting.addEventListener(\"touchstart\", preventTouch, { passive: false });\n\t\t\texisting.addEventListener(\"touchend\", handleClick);\n\t\t\tlegendMiniBtn = existing;\n\t\t\treturn existing;\n\t\t}\n\t\tconst panel = document.querySelector(\".topology-panel\");\n\t\tif (!panel) return null;\n\t\t   const btn = document.createElement(\"button\");\n\t\t   btn.type = \"button\";\n\t\t   btn.id = \"edge-legend-mini\";\n\t\t   btn.className = \"legend-mini-btn\";\n\t\t   btn.textContent = t(\"toolbar.legendMini\");\n\t\t   btn.style.bottom = \"10px\";\n\t\t   btn.addEventListener(\"click\", handleClick);\n\t\t   btn.addEventListener(\"touchstart\", preventTouch, { passive: false });\n\t\t   btn.addEventListener(\"touchend\", handleClick);\n\t\t   panel.appendChild(btn);\n\t\t   legendMiniBtn = btn;\n\t\t   return btn;\n\t\t}\n\t\t\t function ensureMinimapMiniButton() {\n\t\t   if (minimapMiniBtn) return minimapMiniBtn;\n\t\t   const handleClick = (e) => {\n\t\t\te.stopPropagation();\n\t\t\te.preventDefault();\n\t\t\tminimapCollapsed = false;\n\t\t\tupdateMinimapVisibility();\n\t\t   };\n\t\t   const preventTouch = (e) => { e.preventDefault(); };\n\t\t   const existing = document.getElementById(\"minimap-mini\");\n\t\t   if (existing) {\n\t\t\texisting.addEventListener(\"click\", handleClick);\n\t\t\texisting.addEventListener(\"touchstart\", preventTouch, { passive: false });\n\t\t\texisting.addEventListener(\"touchend\", handleClick);\n\t\t\tminimapMiniBtn = existing;\n\t\t\treturn existing;\n\t\t   }\n\t\t   const panel = document.querySelector(\".topology-panel\");\n\t\t   if (!panel) return null;\n\t\t   const btn = document.createElement(\"button\");\n\t\t   btn.type = \"button\";\n\t\t   btn.id = \"minimap-mini\";\n\t\t   btn.className = \"legend-mini-btn\";\n\t\t   btn.textContent = t(\"toolbar.minimapMini\");\n\t\t   btn.style.right = \"10px\";\n\t\t   btn.style.left = \"auto\";\n\t\t   btn.style.bottom = \"10px\";\n\t\t   btn.addEventListener(\"click\", handleClick);\n\t\t   btn.addEventListener(\"touchstart\", preventTouch, { passive: false });\n\t\t   btn.addEventListener(\"touchend\", handleClick);\n\t\t   panel.appendChild(btn);\n\t\t   minimapMiniBtn = btn;\n\t\t   return btn;\n\t\t}\n\t\t\t  function ensureDrawToolbarMiniButton() {\n\t\t   if (drawToolbarMiniBtn) return drawToolbarMiniBtn;\n\t\t   const handleClick = (e) => {\n\t\t\te.stopPropagation();\n\t\t\te.preventDefault();\n\t\t\tdrawToolbarCollapsed = false;\n\t\t\tupdateDrawToolbarVisibility();\n\t\t   };\n\t\t   const preventTouch = (e) => { e.preventDefault(); };\n\t\t   const existing = document.getElementById(\"draw-toolbar-mini\");\n\t\t   if (existing) {\n\t\t\texisting.addEventListener(\"click\", handleClick);\n\t\t\texisting.addEventListener(\"touchstart\", preventTouch, { passive: false });\n\t\t\texisting.addEventListener(\"touchend\", handleClick);\n\t\t\tdrawToolbarMiniBtn = existing;\n\t\t\treturn existing;\n\t\t   }\n\t\t   const panel = document.querySelector(\".topology-panel\");\n\t\t   if (!panel) return null;\n\t\t   const btn = document.createElement(\"button\");\n\t\t   btn.type = \"button\";\n\t\t   btn.id = \"draw-toolbar-mini\";\n\t\t   btn.className = \"legend-mini-btn\";\n\t\t   btn.textContent = t(\"toolbar.drawMini\");\n\t\t   btn.style.top = \"10px\";\n\t\t   btn.style.left = \"10px\";\n\t\t   btn.style.right = \"auto\";\n\t\t   btn.addEventListener(\"click\", handleClick);\n\t\t   btn.addEventListener(\"touchstart\", preventTouch, { passive: false });\n\t\t   btn.addEventListener(\"touchend\", handleClick);\n\t\t   panel.appendChild(btn);\n\t\t   drawToolbarMiniBtn = btn;\n\t\t   return btn;\n\t\t}\n\t\t  function ensureTopologyToolbarMiniButton() {\n\t   if (topologyToolbarMiniBtn) return topologyToolbarMiniBtn;\n\t   const handleClick = (e) => {\n\t\te.stopPropagation();\n\t\te.preventDefault();\n\t\ttopologyToolbarCollapsed = false;\n\t\tupdateTopologyToolbarVisibility();\n\t   };\n\t   const preventTouch = (e) => { e.preventDefault(); };\n\t   const existing = document.getElementById(\"topology-toolbar-mini\");\n\t   if (existing) {\n\t\texisting.addEventListener(\"click\", handleClick);\n\t\texisting.addEventListener(\"touchstart\", preventTouch, { passive: false });\n\t\texisting.addEventListener(\"touchend\", handleClick);\n\t\ttopologyToolbarMiniBtn = existing;\n\t\treturn existing;\n\t   }\n\t   const panel = document.querySelector(\".topology-panel\");\n\t   if (!panel) return null;\n\t   const btn = document.createElement(\"button\");\n\t   btn.type = \"button\";\n\t   btn.id = \"topology-toolbar-mini\";\n\t   btn.className = \"legend-mini-btn\";\n\t   btn.textContent = t(\"toolbar.topologyMini\");\n\t   btn.style.top = \"10px\";\n\t   btn.style.left = \"auto\";\n\t   btn.style.right = \"40px\";\n\t   btn.addEventListener(\"click\", handleClick);\n\t   btn.addEventListener(\"touchstart\", preventTouch, { passive: false });\n\t   btn.addEventListener(\"touchend\", handleClick);\n\t   panel.appendChild(btn);\n\t   topologyToolbarMiniBtn = btn;\n\t   return btn;\n\t}\n      function updateToolbarStack() {\n       if (!isMobileDevice()) return;\n       const draw = document.getElementById(\"draw-toolbar\");\n       const topo = document.getElementById(\"topology-toolbar\");\n       if (!draw || !topo) return;\n       const h = draw.getBoundingClientRect().height;\n       document.documentElement.style.setProperty(\"--draw-toolbar-height\", h + \"px\");\n      }\n      window.addEventListener(\"resize\", updateToolbarStack);\n      window.addEventListener(\"DOMContentLoaded\", updateToolbarStack);\n      updateToolbarStack();\n      function updateLegendVisibility() {\n       const legend = document.getElementById(\"edge-legend\");\n       const mini = ensureLegendMiniButton();\n       if (!legend || !mini) return;\n       const hasItems = legend.querySelectorAll(\".legend-item\").length > 0;\n       if (!hasItems) {\n        legend.style.display = \"none\";\n        mini.style.display = \"none\";\n        return;\n       }\n       if (legendCollapsed) {\n        legend.style.display = \"none\";\n        mini.style.display = \"inline-flex\";\n       } else {\n        legend.style.display = \"flex\";\n        mini.style.display = \"none\";\n       }\n      }\n      function updateMinimapVisibility() {\n       const wrapper = document.getElementById(\"minimap-zoom-wrapper\");\n       const mini = ensureMinimapMiniButton();\n       if (!wrapper || !mini) return;\n       if (minimapCollapsed) {\n        wrapper.style.display = \"none\";\n        mini.style.display = \"inline-flex\";\n       } else {\n        wrapper.style.display = \"block\";\n        mini.style.display = \"none\";\n       }\n      }\n      function updateDrawToolbarVisibility() {\n       const toolbar = document.getElementById(\"draw-toolbar\");\n       const mini = ensureDrawToolbarMiniButton();\n       if (!toolbar || !mini) return;\n       if (typeof isViewOnly === 'function' && isViewOnly()) {\n        toolbar.style.setProperty('display', 'none', 'important');\n        mini.style.display = \"none\";\n        return;\n       }\n       if (drawToolbarCollapsed) {\n        toolbar.style.setProperty('display', 'none', 'important');\n        mini.style.display = \"inline-flex\";\n       } else {\n      if (isMobileDevice()) {\n        toolbar.style.setProperty('display', 'grid', 'important');\n        mini.style.display = \"none\";\n        } else {\n        toolbar.style.setProperty('display', 'flex', 'important');\n        mini.style.display = \"none\";\n      }\n       }\n      }\n      function u8ToBase64(u8) {\n      let binary = \"\";\n      for (let i = 0; i < u8.length; i++) binary += String.fromCharCode(u8[i]);\n      return btoa(binary);\n      }\n      function base64ToU8(base64) {\n      const binary = atob(base64);\n      const bytes = new Uint8Array(binary.length);\n      for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);\n      return bytes;\n      }\n      function openAutosaveDB() {\n       return new Promise((resolve, reject) => {\n        const request = indexedDB.open(AUTOSAVE_DB_NAME, 1);\n        request.onerror = () => reject(request.error);\n        request.onsuccess = () => resolve(request.result);\n        request.onupgradeneeded = (e) => {\n         const db = e.target.result;\n         if (!db.objectStoreNames.contains(AUTOSAVE_STORE_NAME)) {\n          db.createObjectStore(AUTOSAVE_STORE_NAME, { keyPath: \"id\" });\n         }\n        };\n       });\n      }\n      async function saveToIndexedDB() {\n       if (window.DEMO_MODE) return;\n       try {\n        const db = await openAutosaveDB();\n        const currentTab = documentTabs[currentTabIndex];\n        currentTab.nodes = JSON.parse(JSON.stringify(NODE_DATA));\n        currentTab.edges = JSON.parse(JSON.stringify(EDGE_DATA));\n        currentTab.positions = JSON.parse(JSON.stringify(savedPositions));\n        currentTab.sizes = JSON.parse(JSON.stringify(savedSizes));\n        currentTab.styles = JSON.parse(JSON.stringify(savedStyles));\n        currentTab.legend = JSON.parse(JSON.stringify(EDGE_LEGEND));\n        currentTab.rects = JSON.parse(JSON.stringify(RECT_DATA));\n        currentTab.texts = JSON.parse(JSON.stringify(TEXT_DATA));\n        currentTab.pageState = JSON.parse(JSON.stringify(PAGE_STATE));\n        const state = {\n         id: AUTOSAVE_KEY,\n         timestamp: Date.now(),\n         pageTitle: document.getElementById(\"page-title\")?.textContent || \"The One File\",\n         nodeData: NODE_DATA,\n         edgeData: EDGE_DATA,\n         rectData: RECT_DATA,\n         textData: TEXT_DATA,\n         imageData: IMAGE_DATA,\n         edgeLegend: EDGE_LEGEND,\n         zoneLegend: ZONE_LEGEND,\n         zonePresets: ZONE_PRESETS,\n         nodePositions: savedPositions,\n         nodeSizes: savedSizes,\n         nodeStyles: savedStyles,\n         page: PAGE_STATE,\n         canvas: {\n          zoom: canvasState.zoom,\n          panX: canvasState.panX,\n          panY: canvasState.panY,\n         },\n         savedTopologyView: savedTopologyView,\n         documentTabs: documentTabs,\n         currentTabIndex: currentTabIndex,\n         encryptedSections: encryptedSections,\n         auditLog: auditLog,\n\t\t savedStyleSets: savedStyleSets,\n\t\t selectedTheme: document.getElementById(\"theme-preset\")?.value || \"defaulted\",\n         recordings: RECORDING_STATE.recordings,\n        };\n        const tx = db.transaction(AUTOSAVE_STORE_NAME, \"readwrite\");\n        const store = tx.objectStore(AUTOSAVE_STORE_NAME);\n        store.put(state);\n        await new Promise((resolve, reject) => {\n         tx.oncomplete = resolve;\n         tx.onerror = () => reject(tx.error);\n        });\n        lastAutosaveTime = Date.now();\n        console.log(\"Autosaved to IndexedDB at\", new Date().toLocaleTimeString());\n       } catch (e) {\n        console.warn(\"Auto-save failed:\", e);\n       }\n      }\n      async function loadFromIndexedDB() {\n       if (window.DEMO_MODE) return null;\n       try {\n        const db = await openAutosaveDB();\n        const tx = db.transaction(AUTOSAVE_STORE_NAME, \"readonly\");\n        const store = tx.objectStore(AUTOSAVE_STORE_NAME);\n        const request = store.get(AUTOSAVE_KEY);\n        return new Promise((resolve, reject) => {\n         request.onsuccess = () => resolve(request.result);\n         request.onerror = () => reject(request.error);\n        });\n       } catch (e) {\n        console.warn(\"Load from IndexedDB failed:\", e);\n        return null;\n       }\n      }\n      async function clearAutosave() {\n       try {\n        const db = await openAutosaveDB();\n        const tx = db.transaction(AUTOSAVE_STORE_NAME, \"readwrite\");\n        const store = tx.objectStore(AUTOSAVE_STORE_NAME);\n        store.delete(AUTOSAVE_KEY);\n       } catch (e) {\n        console.warn(\"Clear autosave failed:\", e);\n       }\n      }\n      function startAutosave() {\n       if (autosaveTimer) clearInterval(autosaveTimer);\n       autosaveTimer = setInterval(() => {\n        if (Object.keys(NODE_DATA).length > 0 || EDGE_DATA.list.length > 0 || documentTabs.length > 1) {\n         saveToIndexedDB();\n        }\n       }, AUTOSAVE_INTERVAL);\n      }\n      async function checkForAutosaveRecovery() {\n       if (window.DEMO_MODE) return;\n       try {\n        const saved = await loadFromIndexedDB();\n        if (saved && saved.timestamp) {\n         const age = Date.now() - saved.timestamp;\n         const hasCurrentData = Object.keys(NODE_DATA).length > 0 || EDGE_DATA.list.length > 0;\n         const savedNodeCount = saved.nodeData ? Object.keys(saved.nodeData).length : 0;\n         const savedTabCount = saved.documentTabs ? saved.documentTabs.length : 1;\n         const hasPageState = saved.page && Object.keys(saved.page).length > 0;\n         if (age < 300000 && (savedNodeCount > 0 || savedTabCount > 1 || hasPageState)) {\n          const savedDate = new Date(saved.timestamp).toLocaleString();\n          const tabInfo = savedTabCount > 1 ? ` across ${savedTabCount} tabs` : \"\";\n          if (!hasCurrentData || await showConfirm(t(\"dialogs.recoverWork\", { date: savedDate, nodeCount: savedNodeCount, tabInfo: tabInfo }))) {\n           if (!hasCurrentData || await showConfirm(t(\"dialogs.replaceData\"))) {\n            NODE_DATA = saved.nodeData || {};\n            EDGE_DATA = saved.edgeData || { list: [] };\n            RECT_DATA = saved.rectData || { list: [] };\n            TEXT_DATA = saved.textData || { list: [] };\n            IMAGE_DATA = saved.imageData || { list: [] };\n            EDGE_LEGEND = saved.edgeLegend || {};\n            ZONE_LEGEND = saved.zoneLegend || {};\n            ZONE_PRESETS = saved.zonePresets || {};\n            loadCustomPresets();\n            savedPositions = saved.nodePositions || {};\n            savedSizes = saved.nodeSizes || {};\n            savedStyles = saved.nodeStyles || {};\n            if (saved.page) PAGE_STATE = Object.assign({}, DEFAULT_PAGE_STATE, saved.page);\n            if (saved.canvas) {\n             canvasState.zoom = saved.canvas.zoom || 1;\n             canvasState.panX = saved.canvas.panX || 0;\n             canvasState.panY = saved.canvas.panY || 0;\n            }\n            if (saved.savedTopologyView) savedTopologyView = saved.savedTopologyView;\n            if (saved.savedStyleSets) savedStyleSets = saved.savedStyleSets;\n            if (saved.selectedTheme) {\n              rebuildThemeDropdown();\n              document.getElementById(\"theme-preset\").value = saved.selectedTheme;\n              updateDeleteButton();\n            }\n            if (saved.documentTabs) documentTabs = saved.documentTabs;\n            if (saved.currentTabIndex !== undefined) currentTabIndex = saved.currentTabIndex;\n            if (saved.encryptedSections) encryptedSections = saved.encryptedSections;\n            if (saved.auditLog) auditLog = saved.auditLog;\n            if (saved.recordings) RECORDING_STATE.recordings = saved.recordings;\n            if (saved.pageTitle) {\n             const titleEl = document.getElementById(\"page-title\");\n             if (titleEl) titleEl.textContent = saved.pageTitle;\n            }\n            const layerMap = { \"physical\": \"layer1\", \"logical\": \"layer2\", \"security\": \"layer3\", \"application\": \"layer4\" };\n            Object.values(NODE_DATA).forEach(node => {\n              if (!node.tags) node.tags = [];\n              if (!node.notes) node.notes = [];\n              if (!node.layer || layerMap[node.layer]) node.layer = layerMap[node.layer] || 'layer1';\n            });\n            wieldThePower();\n            forgeTheTopology();\n            updateViewBox();\n            console.log(\"Recovered from autosave:\", savedNodeCount, \"nodes,\", savedTabCount, \"tabs\");\n            return true;\n           }\n          }\n         }\n        }\n       } catch (e) {\n        console.warn(\"Autosave recovery check failed:\", e);\n       }\n       return false;\n      }\n      function updateTopologyToolbarVisibility() {\n       const toolbar = document.getElementById(\"topology-toolbar\");\n       const mini = ensureTopologyToolbarMiniButton();\n       if (!toolbar || !mini) return;\n       if (typeof isViewOnly === 'function' && isViewOnly()) {\n        toolbar.style.setProperty('display', 'none', 'important');\n        mini.style.display = \"none\";\n        return;\n       }\n       const hasSelectedNode = currentNodeId !== null;\n       if (!hasSelectedNode) {\n        toolbar.style.setProperty('display', 'none', 'important');\n        mini.style.display = \"none\";\n       } else if (topologyToolbarCollapsed) {\n        toolbar.style.setProperty('display', 'none', 'important');\n        mini.style.display = \"inline-flex\";\n       } else {\n      if (isMobileDevice()) {\n        toolbar.style.setProperty('display', 'grid', 'important');\n        mini.style.display = \"none\";\n        } else {\n        toolbar.style.setProperty('display', 'flex', 'important');\n        mini.style.display = \"none\";\n      }\n       }\n      }\n      const THE_ONE_FILE_VERSION = \"4.1.5\";\n      const DEFAULT_CANVAS_HINT = document.getElementById(\"canvas-hint\")?.innerHTML || \"\";\n      const DEFAULT_PAGE_STATE = {\n       title: \"The One File: The Networkening\",\n       background: \"\",\n       topbarBg: \"rgba(9, 12, 20, 0.9)\",\n       topbarBorder: \"#1f2533\",\n       panel: \"#0b0e13\",\n       panelAlt: \"#10141b\",\n       accent: \"#4fd1c5\",\n       sidebarBg: \"#10141b\",\n       btnBg: \"#0b0e13\",\n       btnText: \"#e2e8f0\",\n       tagFill: \"#1e293b\",\n       tagText: \"#e2e8f0\",\n       tagBorder: \"#475569\",\n       inputBg: \"#0b0e13\",\n       inputText: \"#e2e8f0\",\n       inputBorder: \"#1f2937\",\n       inputFont: \"Inter, system-ui, sans-serif\",\n       inputFontSize: 14,\n       toolbarBg: \"#0f172a\",\n       toolbarBorder: \"#1f2937\",\n       toolbarText: \"#94a3b8\",\n       toolbarBtnBg: \"#0b0e13\",\n       toolbarBtnText: \"#e2e8f0\",\n       minimapDots: \"#94a3b8\",\n       canvasHintEnabled: true,\n       canvasHintText: \"\",\n       canvasHintBg: \"#0f172a\",\n       canvasHintColor: \"#94a3b8\",\n       danger: \"#f56565\",\n       textMain: \"#e2e8f0\",\n       textSoft: \"#94a3b8\",\n       topbarHeight: 52,\n       sidebarWidth: 350,\n       mobileFooterHeight: 40,\n       sidebarCollapsed: false,\n       nodeFill: \"#1e293b\",\n       nodeStroke: \"#475569\",\n       nodeTitle: \"#e2e8f0\",\n       nodeSub: \"#94a3b8\",\n       nodeTitleSize: 18,\n       nodeSubSize: 13,\n       nodeFont: \"Inter, system-ui, sans-serif\",\n       defaultEdge: \"#475569\",\n       selectionHandle: \"#f59e0b\",\n       selectionHandleSize: 8,\n       groupIndicator: \"#4fd1c5\",\n       canvasGradientTop: \"#1e2532\",\n       canvasGradientBottom: \"#050608\",\n       canvasBorder: \"#475569\",\n       canvasGrid: \"#475569\",\n       canvasGridSize: 50,\n       canvasGridEnabled: true,\n       rackFrameFill: \"#0f172a\",\n       rackFrameStroke: \"#4fd1c5\",\n       rackLineColor: \"#475569\",\n       rackTextColor: \"#4fd1c5\",\n       rackGridEnabled: true,\n\t   viewOnly: false,\n\t   defaultEdgeRouting: \"curved\",\n\t   animateConnections: false,\n\t   animationStyle: \"arrows\",\n\t   animationDirection: \"all\",\n\t   animationSpeed: 1.5,\n\t   motionTrailsEnabled: true,\n\t   motionTrailLength: 8,\n\t   motionTrailStyle: \"solid\",\n\t   motionTrailArrow: true,\n\t   showPortLabels: true,\n      };\n      let PAGE_STATE = Object.assign({}, DEFAULT_PAGE_STATE);\n\t  const FOV_ANIMATION_START = Date.now();\n\t  let ZONES_VISIBLE = true;\n\t  let RECORDING_STATE = {\n\t    isRecording: false,\n\t    isPlaying: false,\n\t    isPaused: false,\n\t    currentRecording: null,\n\t    recordings: [],\n\t    startTime: 0,\n\t    frames: [],\n\t    playbackIndex: 0,\n\t    playbackSpeed: 1,\n\t    loopPlayback: false,\n\t    captureInterval: null,\n\t    playbackTimer: null,\n\t    CAPTURE_FPS: 10,\n\t    videoRecorder: null,\n\t    videoChunks: [],\n\t    videoCanvas: null,\n\t    videoCtx: null,\n\t    videoAnimFrame: null,\n\t    isVideoRecording: false,\n\t    isStepRecording: false,\n\t    stepFrames: [],\n\t    stepCount: 0,\n\t    trailHistory: {}\n\t  };\nconst ANIM_SETTINGS = {\n  masterAnim: true,\n  masterZones: true,\n  animTypes: { sweep: true, pulse: true, rings: true, spin: true, connections: true },\n  animCategories: { camera: true, doorbell: true, motion: true, smoke: true, wifi: true, sensor: true, sprinkler: true, connections: true },\n  zoneCategories: { camera: true, doorbell: true, motion: true, smoke: true, wifi: true, sensor: true, sprinkler: true }\n};\nfunction getShapeCategory(shape) {\n  const map = {\n    \"camera\": \"camera\", \"cctv\": \"camera\", \"ptz-cam\": \"camera\",\n    \"doorbell\": \"doorbell\",\n    \"motion-sensor\": \"motion\", \"motion-detect\": \"motion\",\n    \"smoke-detector\": \"smoke\", \"smoke-alarm\": \"smoke\",\n    \"access-point\": \"wifi\", \"wifi\": \"wifi\", \"router\": \"wifi\", \"wifi-strong\": \"wifi\", \"wifi-weak\": \"wifi\",\n    \"sensor\": \"sensor\", \"iot\": \"sensor\",\n    \"sprinkler\": \"sprinkler\", \"sprinkler-arc\": \"sprinkler\"\n  };\n  return map[shape] || null;\n}\nfunction isAnimationAllowed(shape, animType) {\n  if (!ANIM_SETTINGS.masterAnim) return false;\n  if (!ANIM_SETTINGS.animTypes[animType]) return false;\n  const cat = getShapeCategory(shape);\n  if (cat && !ANIM_SETTINGS.animCategories[cat]) return false;\n  return true;\n}\nfunction isZoneAllowed(shape) {\n  if (!ANIM_SETTINGS.masterZones) return false;\n  const cat = getShapeCategory(shape);\n  if (cat && !ANIM_SETTINGS.zoneCategories[cat]) return false;\n  return true;\n}\nfunction applyAnimZoneSettings() {\n  document.querySelectorAll(\".fov-group\").forEach(g => {\n    const nodeEl = g.closest(\"g[data-node-id]\");\n    if (!nodeEl) return;\n    const nodeId = nodeEl.dataset.nodeId;\n    const node = NODE_DATA[nodeId];\n    if (!node) return;\n    const cat = getShapeCategory(node.shape);\n    const zoneVisible = ANIM_SETTINGS.masterZones && (!cat || ANIM_SETTINGS.zoneCategories[cat]);\n    g.style.display = zoneVisible ? \"\" : \"none\";\n    if (zoneVisible && node.fovAnimate) {\n      const animType = node.fovAnimationType || \"sweep\";\n      const animAllowed = ANIM_SETTINGS.masterAnim && ANIM_SETTINGS.animTypes[animType] && (!cat || ANIM_SETTINGS.animCategories[cat]);\n      g.style.animationPlayState = animAllowed ? \"running\" : \"paused\";\n      g.querySelectorAll(\"circle\").forEach(c => c.style.animationPlayState = animAllowed ? \"running\" : \"paused\");\n    }\n  });\n  const connAnimAllowed = ANIM_SETTINGS.masterAnim && ANIM_SETTINGS.animTypes.connections && ANIM_SETTINGS.animCategories.connections;\n  document.querySelectorAll(\".edge-arrow-forward, .edge-arrow-backward\").forEach(a => {\n    a.style.animationPlayState = connAnimAllowed ? \"running\" : \"paused\";\n  });\n}\nlet ZONE_LEGEND = {};\nlet ZONE_PRESETS = {};\nlet copiedZoneStyle = null;\n\nfunction hasCoverageZone(shape) {\n  const supportedShapes = [\n    \"camera\", \"cctv\", \"doorbell\",\n    \"motion-sensor\", \"smoke-detector\",\n    \"access-point\", \"wifi\", \"router\",\n    \"sensor\", \"iot\", \"sprinkler\"\n  ];\n  return supportedShapes.includes(shape);\n}\n\nfunction getCoverageDefaults(shape) {\n  const defaults = {\n    \"camera\": { angle: 90, distance: 150, animationType: \"sweep\" },\n    \"cctv\": { angle: 90, distance: 150, animationType: \"sweep\" },\n    \"doorbell\": { angle: 120, distance: 100, animationType: \"sweep\" },\n    \"motion-sensor\": { angle: 120, distance: 100, animationType: \"pulse\" },\n    \"smoke-detector\": { angle: 360, distance: 80, animationType: \"pulse\" },\n    \"access-point\": { angle: 360, distance: 200, animationType: \"rings\" },\n    \"wifi\": { angle: 360, distance: 200, animationType: \"rings\" },\n    \"router\": { angle: 360, distance: 200, animationType: \"rings\" },\n    \"sensor\": { angle: 90, distance: 100, animationType: \"pulse\" },\n    \"iot\": { angle: 90, distance: 100, animationType: \"pulse\" },\n    \"sprinkler\": { angle: 90, distance: 120, animationType: \"spin\" }\n  };\n  return defaults[shape] || { angle: 90, distance: 150, animationType: \"sweep\" };\n}\n\nfunction toggleAllZones() {\n  ANIM_SETTINGS.masterZones = !ANIM_SETTINGS.masterZones;\n  const masterCheckbox = document.getElementById(\"zone-master\");\n  if (masterCheckbox) masterCheckbox.checked = ANIM_SETTINGS.masterZones;\n  applyAnimZoneSettings();\n}\n\nfunction copyZoneStyle(nodeId) {\n  const node = NODE_DATA[nodeId];\n  if (!node) return false;\n  if (!hasCoverageZone(node.shape)) {\n    showAlert(t(\"dialogs.nodeTypeNoZones\"));\n    return false;\n  }\n  copiedZoneStyle = {\n    fovEnabled: node.fovEnabled,\n    fovAngle: node.fovAngle,\n    fovDistance: node.fovDistance,\n    fovInnerRadius: node.fovInnerRadius,\n    fovRotation: node.fovRotation,\n    fovColor: node.fovColor,\n    fovOpacity: node.fovOpacity,\n    fovGradient: node.fovGradient,\n    fovBorderColor: node.fovBorderColor,\n    fovBorderWidth: node.fovBorderWidth,\n    fovBorderStyle: node.fovBorderStyle,\n    fovBorderOpacity: node.fovBorderOpacity,\n    fovLabel: node.fovLabel,\n    fovLabelPosition: node.fovLabelPosition,\n    fovLabelSize: node.fovLabelSize,\n    fovLabelColor: node.fovLabelColor,\n    fovLabelBold: node.fovLabelBold,\n    fovLabelBg: node.fovLabelBg,\n    fovLabelBgColor: node.fovLabelBgColor,\n    fovLabelOffsetX: node.fovLabelOffsetX,\n    fovLabelOffsetY: node.fovLabelOffsetY,\n    fovAnimate: node.fovAnimate,\n    fovAnimationType: node.fovAnimationType,\n    fovSweep: node.fovSweep,\n    fovSpeed: node.fovSpeed\n  };\n  return true;\n}\n\nfunction pasteZoneStyle(nodeId) {\n  if (!copiedZoneStyle) {\n    showAlert(t(\"dialogs.noZoneStyleCopied\"));\n    return false;\n  }\n  const node = NODE_DATA[nodeId];\n  if (!node) return false;\n  if (!hasCoverageZone(node.shape)) {\n    showAlert(t(\"dialogs.nodeTypeNoZones\"));\n    return false;\n  }\n  pushUndo(\"paste zone style\");\n  Object.assign(node, copiedZoneStyle);\n  updateFovCone(nodeId);\n  return true;\n}\n\nfunction applyZonePreset(preset) {\n  if (!currentNodeId) return;\n  const presets = {\n    \"security-cam\": { fovEnabled: true, fovAngle: 90, fovDistance: 150, fovColor: \"#f59e0b\", fovOpacity: 20, fovAnimationType: \"sweep\", fovAnimate: false },\n    \"ptz-cam\": { fovEnabled: true, fovAngle: 60, fovDistance: 200, fovColor: \"#f59e0b\", fovOpacity: 25, fovAnimationType: \"sweep\", fovAnimate: true, fovSweep: 180, fovSpeed: 8 },\n    \"motion-detect\": { fovEnabled: true, fovAngle: 120, fovDistance: 100, fovColor: \"#10b981\", fovOpacity: 15, fovAnimationType: \"pulse\", fovAnimate: true, fovSpeed: 3 },\n    \"wifi-strong\": { fovEnabled: true, fovAngle: 360, fovDistance: 150, fovColor: \"#3b82f6\", fovOpacity: 10, fovAnimationType: \"rings\", fovAnimate: true, fovSpeed: 4 },\n    \"wifi-extended\": { fovEnabled: true, fovAngle: 360, fovDistance: 250, fovColor: \"#3b82f6\", fovOpacity: 8, fovGradient: true, fovAnimationType: \"rings\", fovAnimate: true, fovSpeed: 5 },\n    \"smoke-alarm\": { fovEnabled: true, fovAngle: 360, fovDistance: 80, fovColor: \"#ef4444\", fovOpacity: 15, fovAnimationType: \"pulse\", fovAnimate: false },\n    \"sprinkler-arc\": { fovEnabled: true, fovAngle: 90, fovDistance: 120, fovColor: \"#06b6d4\", fovOpacity: 20, fovAnimationType: \"spin\", fovAnimate: true, fovSpeed: 6 }\n  };\n  const allPresets = { ...presets, ...ZONE_PRESETS };\n  const settings = allPresets[preset];\n  if (!settings) return;\n  pushUndo(\"apply zone preset\");\n  Object.assign(NODE_DATA[currentNodeId], settings);\n  updateFovCone(currentNodeId);\n  claimTheImmortal(currentNodeId);\n}\n\nasync function saveCustomZonePreset() {\n  if (!currentNodeId) return;\n  const node = NODE_DATA[currentNodeId];\n  if (!node.fovEnabled) {\n    showAlert(t(\"dialogs.enableZoneFirst\"));\n    return;\n  }\n  const name = await showPrompt(t(\"dialogs.enterPresetName\"), \"\");\n  if (!name || !name.trim()) return;\n  const presetId = \"custom-\" + name.toLowerCase().replace(/[^a-z0-9]+/g, \"-\");\n  ZONE_PRESETS[presetId] = {\n    fovEnabled: true,\n    fovAngle: node.fovAngle,\n    fovDistance: node.fovDistance,\n    fovInnerRadius: node.fovInnerRadius,\n    fovColor: node.fovColor,\n    fovOpacity: node.fovOpacity,\n    fovGradient: node.fovGradient,\n    fovBorderColor: node.fovBorderColor,\n    fovBorderWidth: node.fovBorderWidth,\n    fovBorderStyle: node.fovBorderStyle,\n    fovBorderOpacity: node.fovBorderOpacity,\n    fovAnimationType: node.fovAnimationType,\n    fovAnimate: node.fovAnimate,\n    fovSweep: node.fovSweep,\n    fovSpeed: node.fovSpeed\n  };\n  const select = document.getElementById(\"fov-preset\");\n  if (select && !select.querySelector(`option[value=\"${presetId}\"]`)) {\n    const opt = document.createElement(\"option\");\n    opt.value = presetId;\n    opt.textContent = name.trim() + \" ★\";\n    select.appendChild(opt);\n  }\n  showAlert(t(\"dialogs.presetSaved\", { name: name.trim() }));\n}\n\nfunction bulkCopyZoneStyle() {\n  if (selectedNodes.size === 0) {\n    showAlert(t(\"dialogs.selectNodeFirst\"));\n    return;\n  }\n  const firstId = [...selectedNodes][0];\n  if (copyZoneStyle(firstId)) {\n    showAlert(t(\"dialogs.zoneStyleCopied\"));\n  }\n}\n\nfunction bulkPasteZoneStyle() {\n  if (selectedNodes.size === 0) {\n    showAlert(t(\"dialogs.selectNodesFirst\"));\n    return;\n  }\n  if (!copiedZoneStyle) {\n    showAlert(t(\"dialogs.copyZoneStyleFirst\"));\n    return;\n  }\n  pushUndo(\"bulk paste zone style\");\n  let count = 0;\n  selectedNodes.forEach(id => {\n    const node = NODE_DATA[id];\n    if (node && hasCoverageZone(node.shape)) {\n      Object.assign(node, copiedZoneStyle);\n      updateFovCone(id);\n      count++;\n    }\n  });\n  forgeTheTopology();\n  showAlert(t(\"dialogs.zoneStylePasted\", { count }));\n}\n\nfunction bulkToggleZones() {\n  if (selectedNodes.size === 0) {\n    showAlert(t(\"dialogs.selectNodesFirst\"));\n    return;\n  }\n  pushUndo(\"bulk toggle zones\");\n  const firstNode = NODE_DATA[[...selectedNodes][0]];\n  const newState = !(firstNode?.fovEnabled);\n  let count = 0;\n  selectedNodes.forEach(id => {\n    const node = NODE_DATA[id];\n    if (node && hasCoverageZone(node.shape)) {\n      node.fovEnabled = newState;\n      updateFovCone(id);\n      count++;\n    }\n  });\n  forgeTheTopology();\n  showAlert(t(\"dialogs.zonesToggled\", { count, state: newState ? t(\"dialogs.on\") : t(\"dialogs.off\") }));\n}\n\nfunction loadCustomPresets() {\n  const select = document.getElementById(\"fov-preset\");\n  if (!select) return;\n  Object.keys(ZONE_PRESETS).forEach(id => {\n    if (!select.querySelector(`option[value=\"${id}\"]`)) {\n      const opt = document.createElement(\"option\");\n      opt.value = id;\n      opt.textContent = id.replace(/-/g, \" \").replace(\"custom \", \"\") + \" ★\";\n      select.appendChild(opt);\n    }\n  });\n}\n\nfunction updateZoneLegend() {\n  const container = document.getElementById(\"edge-legend\");\n  if (!container) return;\n  container.querySelectorAll(\".zone-legend-item\").forEach(el => el.remove());\n  container.querySelectorAll(\".zone-legend-title\").forEach(el => el.remove());\n  const zoneColors = new Set();\n  Object.values(NODE_DATA).forEach(node => {\n    if (hasCoverageZone(node.shape) && node.fovEnabled && node.fovColor) {\n      zoneColors.add(node.fovColor);\n    }\n  });\n  \n  if (zoneColors.size === 0) return;\n  const zoneTitle = document.createElement(\"div\");\n  zoneTitle.className = \"legend-title zone-legend-title\";\n  zoneTitle.textContent = t(\"legends.zoneLegend\");\n  zoneTitle.style.marginTop = \"12px\";\n  zoneTitle.style.paddingTop = \"8px\";\n  zoneTitle.style.borderTop = \"1px solid var(--edge-main)\";\n  container.appendChild(zoneTitle);\n  zoneColors.forEach(color => {\n    if (!ZONE_LEGEND[color]) {\n      ZONE_LEGEND[color] = t(\"legends.coverageZone\");\n    }\n    const item = document.createElement(\"div\");\n    item.className = \"legend-item zone-legend-item\";\n    item.addEventListener(\"mousedown\", (e) => e.stopPropagation());\n    item.addEventListener(\"click\", (e) => e.stopPropagation());\n    \n    const swatch = document.createElement(\"span\");\n    swatch.className = \"legend-swatch\";\n    swatch.style.backgroundColor = color;\n    swatch.style.opacity = \"0.5\";\n    swatch.style.cursor = \"pointer\";\n    swatch.addEventListener(\"click\", (e) => {\n      e.stopPropagation();\n      const nodeWithColor = Object.entries(NODE_DATA).find(([id, n]) => \n        hasCoverageZone(n.shape) && n.fovEnabled && n.fovColor === color\n      );\n      if (nodeWithColor) {\n        claimTheImmortal(nodeWithColor[0]);\n      }\n    });\n    \n    const label = document.createElement(\"span\");\n    label.className = \"legend-label\";\n    label.textContent = ZONE_LEGEND[color];\n    label.contentEditable = true;\n    label.addEventListener(\"focus\", () => label.classList.add(\"editing\"));\n    label.addEventListener(\"blur\", () => {\n      label.classList.remove(\"editing\");\n      ZONE_LEGEND[color] = label.textContent.trim() || \"Coverage Zone\";\n    });\n    label.addEventListener(\"keydown\", (e) => {\n      if (e.key === \"Enter\") {\n        e.preventDefault();\n        label.blur();\n      }\n    });\n    \n    item.append(swatch, label);\n    container.appendChild(item);\n  });\n  \n  updateLegendVisibility();\n}\n\t  function isViewOnly() {\n       return PAGE_STATE.viewOnly === true;\n      }\n      let viewOnlyClickCount = 0;\n      let viewOnlyClickTimer = null;\n      let viewOnlyClickTarget = null;\n      function handleViewOnlyClick(id, type) {\n       if (!isViewOnly()) return false;\n       if (viewOnlyClickTarget !== id) {\n        viewOnlyClickCount = 0;\n        viewOnlyClickTarget = id;\n       }\n       viewOnlyClickCount++;\n       clearTimeout(viewOnlyClickTimer);\n       viewOnlyClickTimer = setTimeout(() => {\n        viewOnlyClickCount = 0;\n        viewOnlyClickTarget = null;\n       }, 2000);\n       if (viewOnlyClickCount >= 5) {\n        viewOnlyClickCount = 0;\n        viewOnlyClickTarget = null;\n        document.body.classList.add(\"view-only-inspect\");\n        if (type === 'node') {\n         currentNodeId = id;\n         const data = NODE_DATA[id];\n         if (data) {\n          document.querySelectorAll(\".node-group\").forEach((n) => {\n           n.classList.toggle(\"active\", n.dataset.nodeId === id);\n          });\n          document.getElementById(\"node-panel\").style.display = \"block\";\n          document.getElementById(\"edge-panel\").style.display = \"none\";\n          document.getElementById(\"node-name\").textContent = data.name;\n          const nodeIpEl = document.getElementById(\"node-ip\");\n          const modeLabels = LANG.modes[PAGE_STATE.mappingMode] || LANG.modes.network;\n          if (data.ip) {\n            nodeIpEl.textContent = data.ip;\n            nodeIpEl.style.opacity = \"1\";\n          } else {\n            nodeIpEl.textContent = \"Click to add \" + modeLabels.subtitle;\n            nodeIpEl.style.opacity = \"0.5\";\n          }\n          const fovSection = document.getElementById(\"fov-section\");\n          if (fovSection) {\n            if (hasCoverageZone(data.shape)) {\n              fovSection.style.display = \"block\";\n              const defaults = getCoverageDefaults(data.shape);\n              document.getElementById(\"fov-enabled\").checked = data.fovEnabled || false;\n              document.getElementById(\"fov-angle\").value = data.fovAngle || defaults.angle;\n              document.getElementById(\"fov-angle-value\").textContent = (data.fovAngle || defaults.angle) + \"°\";\n              document.getElementById(\"fov-distance\").value = data.fovDistance || defaults.distance;\n              document.getElementById(\"fov-distance-value\").textContent = data.fovDistance || defaults.distance;\n              document.getElementById(\"fov-inner-radius\").value = data.fovInnerRadius || 0;\n              document.getElementById(\"fov-inner-radius-value\").textContent = data.fovInnerRadius || 0;\n              document.getElementById(\"fov-rotation\").value = data.fovRotation || 0;\n              document.getElementById(\"fov-rotation-value\").textContent = (data.fovRotation || 0) + \"°\";\n              document.getElementById(\"fov-color\").value = data.fovColor || \"#f59e0b\";\n              document.getElementById(\"fov-opacity\").value = data.fovOpacity || 20;\n              document.getElementById(\"fov-opacity-value\").textContent = (data.fovOpacity || 20) + \"%\";\n              document.getElementById(\"fov-gradient\").checked = data.fovGradient || false;\n              document.getElementById(\"fov-border-color\").value = data.fovBorderColor || \"#f59e0b\";\n              document.getElementById(\"fov-border-width\").value = data.fovBorderWidth !== undefined ? data.fovBorderWidth : 2;\n              document.getElementById(\"fov-border-width-value\").textContent = data.fovBorderWidth !== undefined ? data.fovBorderWidth : 2;\n              document.getElementById(\"fov-border-style\").value = data.fovBorderStyle || \"solid\";\n              document.getElementById(\"fov-border-opacity\").value = data.fovBorderOpacity !== undefined ? data.fovBorderOpacity : 100;\n              document.getElementById(\"fov-border-opacity-value\").textContent = (data.fovBorderOpacity !== undefined ? data.fovBorderOpacity : 100) + \"%\";\n              document.getElementById(\"fov-label\").value = data.fovLabel || \"\";\n              document.getElementById(\"fov-label-position\").value = data.fovLabelPosition || \"center\";\n              document.getElementById(\"fov-label-size\").value = data.fovLabelSize || 14;\n              document.getElementById(\"fov-label-size-value\").textContent = (data.fovLabelSize || 14) + \"px\";\n              document.getElementById(\"fov-label-color\").value = data.fovLabelColor || \"#ffffff\";\n              document.getElementById(\"fov-label-bold\").checked = data.fovLabelBold || false;\n              document.getElementById(\"fov-label-bg\").checked = data.fovLabelBg || false;\n              document.getElementById(\"fov-label-bg-color\").value = data.fovLabelBgColor || \"#000000\";\n              document.getElementById(\"fov-label-offset-x\").value = data.fovLabelOffsetX || 0;\n              document.getElementById(\"fov-label-offset-x-value\").textContent = data.fovLabelOffsetX || 0;\n              document.getElementById(\"fov-label-offset-y\").value = data.fovLabelOffsetY || 0;\n              document.getElementById(\"fov-label-offset-y-value\").textContent = data.fovLabelOffsetY || 0;\n              document.getElementById(\"fov-animate\").checked = data.fovAnimate || false;\n              document.getElementById(\"fov-animation-type\").value = data.fovAnimationType || defaults.animationType;\n              document.getElementById(\"fov-sweep\").value = data.fovSweep || 120;\n              document.getElementById(\"fov-sweep-value\").textContent = (data.fovSweep || 120) + \"°\";\n              document.getElementById(\"fov-speed\").value = data.fovSpeed || 4;\n              document.getElementById(\"fov-speed-value\").textContent = (data.fovSpeed || 4) + \"s\";\n            } else {\n              fovSection.style.display = \"none\";\n            }\n          }\n          document.getElementById(\"node-role\").textContent = data.role;\n          document.getElementById(\"node-mac\").textContent = data.mac || \"--\";\n          document.getElementById(\"node-rack\").textContent = data.rackUnit || \"--\";\n          document.getElementById(\"node-uheight\").textContent = (data.uHeight || \"1\") + \"U\";\n          const tagList = document.getElementById(\"node-tags\");\n          if (tagList) {\n           tagList.innerHTML = \"\";\n           if (data.tags && data.tags.length > 0) {\n            data.tags.forEach((tag) => {\n             const li = document.createElement(\"li\");\n             li.textContent = tag;\n             tagList.appendChild(li);\n            });\n           }\n          }\n          const noteList = document.getElementById(\"node-notes\");\n          if (noteList) {\n           noteList.innerHTML = \"\";\n           if (data.notes && data.notes.length > 0) {\n            data.notes.forEach((note) => {\n             const li = document.createElement(\"li\");\n             li.textContent = note;\n             noteList.appendChild(li);\n            });\n           }\n          }\n         }\n        } else if (type === 'edge') {\n         currentEdgeId = id;\n         const edge = EDGE_DATA.list.find(e => e.id === id);\n         if (edge) {\n          document.querySelectorAll(\".edge\").forEach((e) => {\n           e.classList.toggle(\"active\", e.dataset.edgeId === id);\n          });\n          document.getElementById(\"edge-panel\").style.display = \"block\";\n          document.getElementById(\"node-panel\").style.display = \"none\";\n          document.getElementById(\"edge-from\").textContent = NODE_DATA[edge.from]?.name || edge.from;\n          document.getElementById(\"edge-to\").textContent = NODE_DATA[edge.to]?.name || edge.to;\n          document.getElementById(\"edge-label\").value = edge.label || \"\";\n         }\n        }\n        return true;\n       }\n       return false;\n      }\n      const CANVAS_WIDTH = 4000;\n      let CANVAS_HEIGHT = 3000;\n      const BASE_CANVAS_HEIGHT = 3000;\n      const CANVAS_PADDING = 100;\n      const RACK_U_HEIGHT = 70;\n      const RACK_WIDTH = 600;\n      const RACK_START_X = CANVAS_WIDTH / 2;\n      const RACK_START_Y = CANVAS_PADDING + 100;\n      function getRackUHeight(rackId) {\n      const capacity = getRackCapacity(rackId);\n      const availableHeight = CANVAS_HEIGHT - RACK_START_Y - 200;\n      return Math.floor(availableHeight / capacity);\n      }\n      let canvasState = {\n       zoom: 1,\n       panX: 0,\n       panY: 0,\n       minZoom: 0.25,\n       maxZoom: 4,\n       isPanning: false,\n       panStartX: 0,\n       panStartY: 0,\n       spacePressed: false,\n      };\n      function getViewBox() {\n       const viewWidth = CANVAS_WIDTH / canvasState.zoom;\n       const viewHeight = CANVAS_HEIGHT / canvasState.zoom;\n       return {\n        x: canvasState.panX,\n        y: canvasState.panY,\n        width: viewWidth,\n        height: viewHeight,\n       };\n      }\n\t  \n\tfunction updateViewBox() {\n\t  const svg = document.getElementById(\"map\");\n\t  const vb = getViewBox();\n\t  svg.setAttribute(\"viewBox\", `${vb.x} ${vb.y} ${vb.width} ${vb.height}`);\n\t  const zoomLevel = document.getElementById(\"zoom-level\");\n\t  if (zoomLevel) {\n\t\tzoomLevel.textContent = Math.round(canvasState.zoom * 100) + \"%\";\n\t  }\n\t  if (canvasState.zoom < 0.5) {\n\t\tsvg.classList.add(\"low-zoom\");\n\t  } else {\n\t\tsvg.classList.remove(\"low-zoom\");\n\t  }\n\t  updateMinimap();\n\t  populateRackDropdown();\n\t}\n\t  \n\tlet lastMinimapRender = 0;\n\tconst MINIMAP_THROTTLE = 100;\n\n\tfunction updateMinimap() {\n  const now = performance.now();\n  if (now - lastMinimapRender < MINIMAP_THROTTLE) return;\n  lastMinimapRender = now;\n  \n  const minimapViewport = document.getElementById(\"minimap-viewport\");\n  const minimapSvg = document.getElementById(\"minimap\");\n  if (!minimapViewport || !minimapSvg) return;\n  const vb = getViewBox();\n  minimapViewport.setAttribute(\"x\", vb.x);\n  minimapViewport.setAttribute(\"y\", vb.y);\n  minimapViewport.setAttribute(\"width\", vb.width);\n  minimapViewport.setAttribute(\"height\", vb.height);\n  minimapSvg.querySelectorAll(\".minimap-node, .minimap-edge, .minimap-wall, .minimap-rect\").forEach(el => el.remove());\n  const frag = document.createDocumentFragment();\n  const ns = \"http://www.w3.org/2000/svg\";\n  const dotColor = PAGE_STATE.minimapDots || \"#94a3b8\";\n\n  if (RECT_DATA && RECT_DATA.list && currentView.mode !== \"rack\") {\n    RECT_DATA.list.forEach((rect) => {\n      if (rect.lineStyle === \"wall\") {\n        const wallRect = document.createElementNS(ns, \"rect\");\n        wallRect.setAttribute(\"x\", rect.x);\n        wallRect.setAttribute(\"y\", rect.y);\n        wallRect.setAttribute(\"width\", rect.width);\n        wallRect.setAttribute(\"height\", rect.height);\n        wallRect.style.fill = rect.color || \"#666\";\n        wallRect.style.fillOpacity = \"0.6\";\n        wallRect.style.stroke = rect.borderColor || rect.color || \"#666\";\n        wallRect.style.strokeWidth = \"4\";\n        wallRect.classList.add(\"minimap-wall\");\n        frag.appendChild(wallRect);\n      }\n    });\n  }\n\n  EDGE_DATA.list.forEach((edge) => {\n    if (edge.type === \"custom\") {\n      if (Array.isArray(edge.points) && edge.points.length >= 2) {\n        const polyline = document.createElementNS(ns, \"polyline\");\n        polyline.setAttribute(\"points\", edge.points.map(p => `${p.x},${p.y}`).join(\" \"));\n        polyline.classList.add(\"minimap-edge\");\n        frag.appendChild(polyline);\n      }\n      return;\n    }\n    const fromNode = NODE_DATA[edge.from];\n    const toNode = NODE_DATA[edge.to];\n    if (!fromNode || !toNode) return;\n    if (currentView.mode === \"rack\") {\n      if (fromNode.assignedRack !== currentView.rackId || toNode.assignedRack !== currentView.rackId) return;\n    } else {\n      if (fromNode.assignedRack || toNode.assignedRack) return;\n    }\n    const p1 = savedPositions[edge.from];\n    const p2 = savedPositions[edge.to];\n    if (!p1 || !p2) return;\n    const routing = edge.routing || PAGE_STATE.defaultEdgeRouting || \"curved\";\n    \n    if (routing === \"orthogonal\") {\n      const dx = p2.x - p1.x;\n      const dy = p2.y - p1.y;\n      const polyline = document.createElementNS(ns, \"polyline\");\n      let points;\n      if (Math.abs(dx) > Math.abs(dy)) {\n        const midX = p1.x + dx / 2;\n        points = `${p1.x},${p1.y} ${midX},${p1.y} ${midX},${p2.y} ${p2.x},${p2.y}`;\n      } else {\n        const midY = p1.y + dy / 2;\n        points = `${p1.x},${p1.y} ${p1.x},${midY} ${p2.x},${midY} ${p2.x},${p2.y}`;\n      }\n      polyline.setAttribute(\"points\", points);\n      polyline.classList.add(\"minimap-edge\");\n      frag.appendChild(polyline);\n    } else {\n      const line = document.createElementNS(ns, \"line\");\n      line.setAttribute(\"x1\", p1.x);\n      line.setAttribute(\"y1\", p1.y);\n      line.setAttribute(\"x2\", p2.x);\n      line.setAttribute(\"y2\", p2.y);\n      line.classList.add(\"minimap-edge\");\n      frag.appendChild(line);\n    }\n  });\n\n  Object.entries(savedPositions).forEach(([id, pos]) => {\n    const node = NODE_DATA[id];\n    if (!node) return;\n    if (currentView.mode === \"rack\") {\n      if (node.assignedRack !== currentView.rackId) return;\n    } else {\n      if (node.assignedRack) return;\n    }\n    const nodeSize = savedSizes[id] || 55;\n    const s = nodeSize * 0.5;\n    \n    if (node.isRack) {\n      const rect = document.createElementNS(ns, \"rect\");\n      rect.setAttribute(\"x\", pos.x - s);\n      rect.setAttribute(\"y\", pos.y - s);\n      rect.setAttribute(\"width\", s * 2);\n      rect.setAttribute(\"height\", s * 2);\n      rect.style.fill = dotColor;\n      rect.classList.add(\"minimap-node\");\n      frag.appendChild(rect);\n    } else if (hasCoverageZone(node.shape)) {\n      const diamond = document.createElementNS(ns, \"polygon\");\n      const points = `${pos.x},${pos.y - s} ${pos.x + s},${pos.y} ${pos.x},${pos.y + s} ${pos.x - s},${pos.y}`;\n      diamond.setAttribute(\"points\", points);\n      diamond.style.fill = \"none\";\n      diamond.style.stroke = dotColor;\n      diamond.style.strokeWidth = \"6\";\n      diamond.classList.add(\"minimap-node\");\n      frag.appendChild(diamond);\n    } else {\n      const circle = document.createElementNS(ns, \"circle\");\n      circle.setAttribute(\"cx\", pos.x);\n      circle.setAttribute(\"cy\", pos.y);\n      circle.setAttribute(\"r\", s);\n      circle.style.fill = dotColor;\n      circle.classList.add(\"minimap-node\");\n      frag.appendChild(circle);\n    }\n  });\n\n  minimapSvg.insertBefore(frag, minimapViewport);\n}\n\t  \n\t  \n      function zoomTo(newZoom, centerX, centerY) {\n       const oldZoom = canvasState.zoom;\n       newZoom = Math.max(canvasState.minZoom, Math.min(canvasState.maxZoom, newZoom));\n       if (centerX !== undefined && centerY !== undefined) {\n        const oldWidth = CANVAS_WIDTH / oldZoom;\n        const oldHeight = CANVAS_HEIGHT / oldZoom;\n        const newWidth = CANVAS_WIDTH / newZoom;\n        const newHeight = CANVAS_HEIGHT / newZoom;\n        const pointX = canvasState.panX + centerX * oldWidth;\n        const pointY = canvasState.panY + centerY * oldHeight;\n        canvasState.panX = pointX - centerX * newWidth;\n        canvasState.panY = pointY - centerY * newHeight;\n       }\n       canvasState.zoom = newZoom;\n       constrainPan();\n       updateViewBox();\n      }\n      function constrainPan() {\n       const viewWidth = CANVAS_WIDTH / canvasState.zoom;\n       const viewHeight = CANVAS_HEIGHT / canvasState.zoom;\n       const minVisiblePortion = 0.1;\n       const maxPanX = CANVAS_WIDTH - viewWidth * minVisiblePortion;\n       const maxPanY = CANVAS_HEIGHT - viewHeight * minVisiblePortion;\n       const minPanX = -viewWidth * (1 - minVisiblePortion);\n       const minPanY = -viewHeight * (1 - minVisiblePortion);\n       canvasState.panX = Math.max(minPanX, Math.min(maxPanX, canvasState.panX));\n       canvasState.panY = Math.max(minPanY, Math.min(maxPanY, canvasState.panY));\n      }\n      function fitToContent() {\n       const positions = Object.values(savedPositions);\n       if (positions.length === 0) {\n        resetView();\n        return;\n       }\n       let minX = Infinity,\n        minY = Infinity,\n        maxX = -Infinity,\n        maxY = -Infinity;\n       positions.forEach((pos) => {\n        minX = Math.min(minX, pos.x - 100);\n        minY = Math.min(minY, pos.y - 100);\n        maxX = Math.max(maxX, pos.x + 100);\n        maxY = Math.max(maxY, pos.y + 100);\n       });\n       const contentWidth = maxX - minX + 200;\n       const contentHeight = maxY - minY + 200;\n       const viewport = document.getElementById(\"canvas-viewport\");\n       const vpRect = viewport.getBoundingClientRect();\n       const aspectRatio = vpRect.width / vpRect.height;\n       const contentAspect = contentWidth / contentHeight;\n       let newZoom;\n       if (contentAspect > aspectRatio) {\n        newZoom = (vpRect.width / contentWidth) * (CANVAS_WIDTH / vpRect.width);\n       } else {\n        newZoom = (vpRect.height / contentHeight) * (CANVAS_HEIGHT / vpRect.height);\n       }\n       newZoom = Math.max(canvasState.minZoom, Math.min(2, newZoom));\n       const viewWidth = CANVAS_WIDTH / newZoom;\n       const viewHeight = CANVAS_HEIGHT / newZoom;\n       canvasState.zoom = newZoom;\n       canvasState.panX = minX - 100 - (viewWidth - contentWidth) / 2;\n       canvasState.panY = minY - 100 - (viewHeight - contentHeight) / 2;\n       constrainPan();\n       updateViewBox();\n      }\n      function resetView() {\n       canvasState.zoom = 1;\n       canvasState.panX = 0;\n       canvasState.panY = 0;\n       updateViewBox();\n      }\n      function focusOnNodes(nodeIds) {\n       if (!nodeIds || nodeIds.length === 0) return;\n       const positions = nodeIds.map(id => savedPositions[id]).filter(Boolean);\n       if (positions.length === 0) return;\n       let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;\n       positions.forEach(pos => {\n        minX = Math.min(minX, pos.x);\n        minY = Math.min(minY, pos.y);\n        maxX = Math.max(maxX, pos.x);\n        maxY = Math.max(maxY, pos.y);\n       });\n       const padding = 150;\n       const contentWidth = Math.max(maxX - minX + padding * 2, 300);\n       const contentHeight = Math.max(maxY - minY + padding * 2, 300);\n       const centerX = (minX + maxX) / 2;\n       const centerY = (minY + maxY) / 2;\n       const viewport = document.getElementById(\"canvas-viewport\");\n       const vpRect = viewport.getBoundingClientRect();\n       const aspectRatio = vpRect.width / vpRect.height;\n       const contentAspect = contentWidth / contentHeight;\n       let newZoom;\n       if (contentAspect > aspectRatio) {\n        newZoom = (vpRect.width / contentWidth) * (CANVAS_WIDTH / vpRect.width);\n       } else {\n        newZoom = (vpRect.height / contentHeight) * (CANVAS_HEIGHT / vpRect.height);\n       }\n       newZoom = Math.max(canvasState.minZoom, Math.min(1.5, newZoom));\n       const viewWidth = CANVAS_WIDTH / newZoom;\n       const viewHeight = CANVAS_HEIGHT / newZoom;\n       canvasState.zoom = newZoom;\n       canvasState.panX = centerX - viewWidth / 2;\n       canvasState.panY = centerY - viewHeight / 2;\n       constrainPan();\n       updateViewBox();\n      }\n      window.addEventListener(\"DOMContentLoaded\", () => {\n       const toggle = document.getElementById(\"mobile-menu-toggle\");\n       const menu = document.getElementById(\"topbar-menu\");\n       if (!toggle || !menu) return;\n       toggle.addEventListener(\"click\", () => {\n        menu.classList.toggle(\"open\");\n       });\n       menu.addEventListener(\"click\", (e) => {\n        const target = e.target.closest(\"a, button\");\n        if (!target) return;\n        if (isMobileDevice()) {\n       menu.classList.remove(\"open\");\n      }\n       });\n       const minimapCloseBtn = document.getElementById(\"minimap-close-btn\");\n       if (minimapCloseBtn) {\n        const handleMinimapClose = (e) => {\n         e.stopPropagation();\n         e.preventDefault();\n         minimapCollapsed = true;\n         updateMinimapVisibility();\n        };\n        minimapCloseBtn.addEventListener(\"click\", handleMinimapClose);\n        minimapCloseBtn.addEventListener(\"touchend\", handleMinimapClose);\n       }\n       const drawToolbarCloseBtn = document.getElementById(\"draw-toolbar-close-btn\");\n       if (drawToolbarCloseBtn) {\n        const handleDrawClose = (e) => {\n         e.stopPropagation();\n         e.preventDefault();\n         drawToolbarCollapsed = true;\n         updateDrawToolbarVisibility();\n        };\n        drawToolbarCloseBtn.addEventListener(\"click\", handleDrawClose);\n        drawToolbarCloseBtn.addEventListener(\"touchend\", handleDrawClose);\n       }\n       const topologyToolbarCloseBtn = document.getElementById(\"topology-toolbar-close-btn\");\n       if (topologyToolbarCloseBtn) {\n        const handleTopologyClose = (e) => {\n         e.stopPropagation();\n         e.preventDefault();\n         topologyToolbarCollapsed = true;\n         updateTopologyToolbarVisibility();\n        };\n        topologyToolbarCloseBtn.addEventListener(\"click\", handleTopologyClose);\n        topologyToolbarCloseBtn.addEventListener(\"touchstart\", (e) => e.preventDefault(), {\n         passive: false\n        });\n        topologyToolbarCloseBtn.addEventListener(\"touchend\", handleTopologyClose);\n       }\n       updateMinimapVisibility();\n       updateDrawToolbarVisibility();\n       updateTopologyToolbarVisibility();\n      });\n      function applyLayerFilter() {\n       activeLayers.clear();\n       if (document.getElementById(\"layer-1\").checked) activeLayers.add(\"layer1\");\n       if (document.getElementById(\"layer-2\").checked) activeLayers.add(\"layer2\");\n       if (document.getElementById(\"layer-3\").checked) activeLayers.add(\"layer3\");\n       if (document.getElementById(\"layer-4\").checked) activeLayers.add(\"layer4\");\n       forgeTheTopology();\n      }\n      function isNodeVisible(nodeId) {\n       const node = NODE_DATA[nodeId];\n       if (!node) return false;\n       let nodeLayer = node.layer || \"layer1\";\n       const layerMap = { \"physical\": \"layer1\", \"logical\": \"layer2\", \"security\": \"layer3\", \"application\": \"layer4\" };\n       if (layerMap[nodeLayer]) nodeLayer = layerMap[nodeLayer];\n       return activeLayers.has(nodeLayer);\n      }\n      function enterRack(rackId) {\n      if (!NODE_DATA[rackId] || !NODE_DATA[rackId].isRack) return;\n      const rackCapacity = getRackCapacity(rackId);\n      const neededHeight = RACK_START_Y + (rackCapacity * RACK_U_HEIGHT) + 200;\n      if (neededHeight > BASE_CANVAS_HEIGHT) {\n      CANVAS_HEIGHT = neededHeight;\n      }\n       currentView.mode = \"rack\";\n       currentView.rackId = rackId;\n       const backBtn = document.getElementById(\"back-to-topology-btn\");\n       if (backBtn) {\n        backBtn.style.display = \"inline-block\";\n        const modeLabels = {\n         'network': 'Topology',\n         'sports': 'Sports',\n         'smart-home': 'Smart Home',\n         'floor-plan': 'Floor Plan'\n        };\n        const currentMode = PAGE_STATE.mappingMode || 'network';\n        backBtn.textContent = '← Back to ' + (modeLabels[currentMode] || 'Topology');\n       }\n       const hint = document.getElementById(\"canvas-hint\");\n       if (hint) {\n        hint.textContent = `Viewing: ${NODE_DATA[rackId]?.name || 'Rack'} | Double click empty space to exit`;\n        hint.classList.add(\"visible\");\n       }\n      const rackUHeight = getRackUHeight(rackId);\n      const rackHeight = rackCapacity * rackUHeight;\n      const rackCenterX = RACK_START_X;\n      const rackCenterY = RACK_START_Y + (rackHeight / 2);\n       const viewWidth = CANVAS_WIDTH / canvasState.zoom;\n       const viewHeight = CANVAS_HEIGHT / canvasState.zoom;\n       canvasState.panX = rackCenterX - (viewWidth / 2);\n       canvasState.panY = rackCenterY - (viewHeight / 2);\n       constrainPan();\n       updateViewBox();\n       forgeTheTopology();\n      }\n      function exitRack() {\n      CANVAS_HEIGHT = BASE_CANVAS_HEIGHT;\n      currentView.mode = \"topology\";\n      currentView.rackId = null;\n       const backBtn = document.getElementById(\"back-to-topology-btn\");\n       if (backBtn) backBtn.style.display = \"none\";\n       const hint = document.getElementById(\"canvas-hint\");\n       if (hint) {\n        hint.classList.remove(\"visible\");\n       }\n       if (savedTopologyView) {\n        canvasState.zoom = savedTopologyView.zoom;\n        canvasState.panX = savedTopologyView.panX;\n        canvasState.panY = savedTopologyView.panY;\n        updateViewBox();\n       }\n       forgeTheTopology();\n      }\n      function getRackCapacity(rackId) {\n       const node = NODE_DATA[rackId];\n       return node && node.rackCapacity ? parseInt(node.rackCapacity) : 42;\n      }\n      function populateRackDropdown() {\n       const dropdown = document.getElementById(\"node-assigned-rack\");\n       if (!dropdown) return;\n       const prev = dropdown.value;\n       dropdown.innerHTML = '<option value=\"\">None</option>';\n       Object.keys(NODE_DATA).forEach(id => {\n        if (NODE_DATA[id].isRack) {\n         const option = document.createElement(\"option\");\n         option.value = id;\n         option.textContent = NODE_DATA[id].name;\n         dropdown.appendChild(option);\n        }\n       });\n       if (prev) dropdown.value = prev;\n      }\n      function populateHostedOnDropdown() {\n       const dropdown = document.getElementById(\"node-hosted-on\");\n       if (!dropdown) return;\n       dropdown.innerHTML = '<option value=\"\">None</option>';\n       const currentRack = currentNodeId ? NODE_DATA[currentNodeId]?.assignedRack : null;\n       Object.keys(NODE_DATA).forEach(id => {\n        if (id === currentNodeId) return;\n        const node = NODE_DATA[id];\n        if (node.isRack) return;\n        if (currentRack && node.assignedRack !== currentRack) return;\n        const option = document.createElement(\"option\");\n        option.value = id;\n        option.textContent = node.name + (node.ip ? ` (${node.ip})` : '');\n        dropdown.appendChild(option);\n       });\n      }\n      function wieldThePower() {\n       const root = document.documentElement;\n       root.style.setProperty(\"--panel\", PAGE_STATE.panel);\n       root.style.setProperty(\"--panel-alt\", PAGE_STATE.panelAlt);\n       root.style.setProperty(\"--sidebar-bg\", PAGE_STATE.sidebarBg || PAGE_STATE.panelAlt);\n       root.style.setProperty(\"--btn-bg\", PAGE_STATE.btnBg || PAGE_STATE.panel);\n       root.style.setProperty(\"--btn-text\", PAGE_STATE.btnText || \"#e2e8f0\");\n       root.style.setProperty(\"--tag-fill\", PAGE_STATE.tagFill || \"#1e293b\");\n       root.style.setProperty(\"--tag-text\", PAGE_STATE.tagText || \"#e2e8f0\");\n       root.style.setProperty(\"--tag-border\", PAGE_STATE.tagBorder || \"#475569\");\n       root.style.setProperty(\"--input-bg\", PAGE_STATE.inputBg || \"#0b0e13\");\n       root.style.setProperty(\"--input-text\", PAGE_STATE.inputText || \"#e2e8f0\");\n       root.style.setProperty(\"--input-border\", PAGE_STATE.inputBorder || \"#1f2937\");\n       root.style.setProperty(\"--input-font\", PAGE_STATE.inputFont || \"Inter, system-ui, sans-serif\");\n       root.style.setProperty(\"--input-font-size\", (PAGE_STATE.inputFontSize || 14) + \"px\");\n       root.style.setProperty(\"--toolbar-bg\", PAGE_STATE.toolbarBg || \"#0f172a\");\n       root.style.setProperty(\"--toolbar-border\", PAGE_STATE.toolbarBorder || \"#1f2937\");\n       root.style.setProperty(\"--toolbar-text\", PAGE_STATE.toolbarText || \"#94a3b8\");\n       root.style.setProperty(\"--toolbar-btn-bg\", PAGE_STATE.toolbarBtnBg || \"#0b0e13\");\n       root.style.setProperty(\"--toolbar-btn-text\", PAGE_STATE.toolbarBtnText || \"#e2e8f0\");\n       root.style.setProperty(\"--minimap-dots\", PAGE_STATE.minimapDots || \"#94a3b8\");\n       root.style.setProperty(\"--canvas-hint-bg\", PAGE_STATE.canvasHintBg || \"#0f172a\");\n       root.style.setProperty(\"--canvas-hint-color\", PAGE_STATE.canvasHintColor || \"#94a3b8\");\n       const canvasHint = document.getElementById(\"canvas-hint\");\n       if (canvasHint) {\n        canvasHint.style.display = PAGE_STATE.canvasHintEnabled === false ? \"none\" : \"\";\n        if (PAGE_STATE.canvasHintText !== undefined && PAGE_STATE.canvasHintText !== \"\") {\n         canvasHint.textContent = PAGE_STATE.canvasHintText;\n        } else {\n         MobileManager.updateCanvasHint();\n        }\n       }\n       root.style.setProperty(\"--accent\", PAGE_STATE.accent);\n       root.style.setProperty(\"--danger\", PAGE_STATE.danger);\n       root.style.setProperty(\"--text-main\", PAGE_STATE.textMain);\n       root.style.setProperty(\"--text-soft\", PAGE_STATE.textSoft);\n       root.style.setProperty(\"--topbar-bg\", PAGE_STATE.topbarBg);\n       root.style.setProperty(\"--topbar-border\", PAGE_STATE.topbarBorder);\n       root.style.setProperty(\"--node-fill\", PAGE_STATE.nodeFill || \"#1e293b\");\n       root.style.setProperty(\"--node-stroke\", PAGE_STATE.nodeStroke || \"#475569\");\n       root.style.setProperty(\"--node-title\", PAGE_STATE.nodeTitle || \"#e2e8f0\");\n       root.style.setProperty(\"--node-sub\", PAGE_STATE.nodeSub || \"#94a3b8\");\n       root.style.setProperty(\"--node-title-size\", (PAGE_STATE.nodeTitleSize || 18) + \"px\");\n       root.style.setProperty(\"--node-sub-size\", (PAGE_STATE.nodeSubSize || 13) + \"px\");\n       root.style.setProperty(\"--node-font\", PAGE_STATE.nodeFont || \"Inter, system-ui, sans-serif\");\n       root.style.setProperty(\"--default-edge\", PAGE_STATE.defaultEdge || \"#475569\");\n       root.style.setProperty(\"--selection-handle\", PAGE_STATE.selectionHandle || \"#f59e0b\");\n       root.style.setProperty(\"--selection-handle-size\", (PAGE_STATE.selectionHandleSize || 8) + \"px\");\n       root.style.setProperty(\"--group-indicator\", PAGE_STATE.groupIndicator || \"#4fd1c5\");\n       const topbarHeight = PAGE_STATE.topbarHeight || 52;\n       const sidebarWidth = PAGE_STATE.sidebarWidth || 350;\n       const mobileFooterHeight = PAGE_STATE.mobileFooterHeight || 40;\n       root.style.setProperty(\"--topbar-height\", topbarHeight + \"px\");\n       root.style.setProperty(\"--sidebar-width\", sidebarWidth + \"px\");\n       root.style.setProperty(\"--mobile-footer-height\", mobileFooterHeight + \"vh\");\n       const mainEl = document.querySelector(\"main\");\n       const detailsPanel = document.getElementById(\"details-panel\");\n       const sidebarToggle = document.getElementById(\"sidebar-toggle\");\n       if (PAGE_STATE.sidebarCollapsed) {\n        if (mainEl) mainEl.classList.add(\"sidebar-collapsed\");\n        if (detailsPanel) detailsPanel.classList.add(\"collapsed\");\n        if (sidebarToggle) {\n         sidebarToggle.classList.add(\"collapsed\");\n         sidebarToggle.textContent = \"▶\";\n        }\n       } else {\n        if (mainEl) mainEl.classList.remove(\"sidebar-collapsed\");\n        if (detailsPanel) detailsPanel.classList.remove(\"collapsed\");\n        if (sidebarToggle) {\n         sidebarToggle.classList.remove(\"collapsed\");\n         sidebarToggle.textContent = \"◀\";\n        }\n       }\n       if (PAGE_STATE.background) {\n        document.body.style.background = PAGE_STATE.background;\n       } else {\n        document.body.style.background = `radial-gradient(circle at top, ${PAGE_STATE.canvasGradientTop || \"#1e2532\"} 0, ${PAGE_STATE.canvasGradientBottom || \"#050608\"} 70%)`;\n       }\n       document.title = PAGE_STATE.title || DEFAULT_PAGE_STATE.title;\n       const titleEl = document.getElementById(\"page-title\");\n       if (titleEl) {\n        titleEl.textContent = PAGE_STATE.title || DEFAULT_PAGE_STATE.title;\n       }\n       const viewOnlyMode = PAGE_STATE.viewOnly === true;\n       const addNodeBtn = document.getElementById(\"add-node-btn\");\n       const addRackBtn = document.getElementById(\"add-rack-btn\");\n       const bulkToolbar = document.getElementById(\"bulk-toolbar\");\n       const bulkToolbarMobile = document.getElementById(\"bulk-toolbar-mobile\");\n       if (viewOnlyMode) {\n        if (addNodeBtn) addNodeBtn.style.display = \"none\";\n        if (addRackBtn) addRackBtn.style.display = \"none\";\n        if (bulkToolbar) bulkToolbar.style.display = \"none\";\n        if (bulkToolbarMobile) bulkToolbarMobile.style.display = \"none\";\n        [\"draw-toolbar-mini\", \"topology-toolbar-mini\"].forEach(id => {\n         const el = document.getElementById(id);\n         if (el) el.style.display = \"none\";\n        });\n        document.body.classList.remove(\"view-only-inspect\");\n       } else {\n        if (addNodeBtn) addNodeBtn.style.display = \"\";\n        if (addRackBtn) addRackBtn.style.display = \"\";\n       }\n       document.body.classList.toggle(\"view-only-mode\", viewOnlyMode);\n       if (!viewOnlyMode) {\n        if (typeof updateDrawToolbarVisibility === 'function') updateDrawToolbarVisibility();\n        if (typeof updateTopologyToolbarVisibility === 'function') updateTopologyToolbarVisibility();\n       }\n       const mappingModeSelect = document.getElementById(\"mapping-mode-select\");\n       if (mappingModeSelect) mappingModeSelect.value = PAGE_STATE.mappingMode || \"network\";\n       const canvasStyleSelect = document.getElementById(\"canvas-style-select\");\n       if (canvasStyleSelect && PAGE_STATE.canvasTemplate) {\n         canvasStyleSelect.value = PAGE_STATE.canvasTemplate;\n       }\n       if (typeof applyMappingModeLabels === \"function\") applyMappingModeLabels();\n       if (typeof applyLanguage === \"function\") applyLanguage();\n       if (typeof updateCanvasStyleOptions === \"function\") updateCanvasStyleOptions();\n      }\n      (async function awakeTheImmortal() {\n       let initialState = {};\n       let decryptionCancelled = false;\n       const stateEl = document.getElementById(\"topology-state\");\n       if (stateEl && stateEl.textContent.trim()) {\n        try {\n         let stateText = stateEl.textContent.trim();\n         if (isEncrypted(stateText)) {\n          let decrypted = false;\n          let attempts = 0;\n          const maxAttempts = 3;\n          while (!decrypted && attempts < maxAttempts) {\n           const password = await showPrompt(t(\"dialogs.enterDecryptPassword\", { attempt: attempts + 1, max: maxAttempts }));\n           if (!password) {\n            showAlert(t(\"dialogs.decryptionCancelled\"));\n            decryptionCancelled = true;\n            break;\n           }\n           try {\n            stateText = await decryptData(stateText, password);\n            decrypted = true;\n           } catch (e) {\n            attempts++;\n            if (attempts < maxAttempts) {\n             showAlert(t(\"dialogs.incorrectPassword\"));\n            } else {\n             showAlert(t(\"dialogs.maxAttemptsReached\"));\n             decryptionCancelled = true;\n            }\n           }\n          }\n          if (!decrypted) {\n           stateText = \"{}\";\n          }\n         }\n         initialState = JSON.parse(stateText);\n        } catch (e) {\n         console.error(\"Failed to load state:\", e);\n         initialState = {};\n        }\n       }\n       if (decryptionCancelled) {\n        NODE_DATA = {};\n        EDGE_DATA = {\n         list: []\n        };\n        RECT_DATA = { list: [] };\n        TEXT_DATA = { list: [] };\n        IMAGE_DATA = { list: [] };\n        EDGE_LEGEND = {};\n        savedPositions = {};\n        savedSizes = {};\n        savedStyles = {};\n       } else {\n        NODE_DATA = initialState.nodeData ? initialState.nodeData : BASE_NODE_DATA;\n        Object.keys(NODE_DATA).forEach(nodeId => {\n         if (!NODE_DATA[nodeId].ping) {\n          NODE_DATA[nodeId].ping = {\n           enabled: false,\n           protocol: 'http',\n           customUrl: '',\n           timeout: 3000,\n           status: 'unknown',\n           lastCheck: null\n          };\n         }\n        });\n        EDGE_DATA = initialState.edgeData ? initialState.edgeData : {\n         list: [],\n        };\n        RECT_DATA = initialState.rectData ? initialState.rectData : { list: [] };\n        TEXT_DATA = initialState.textData ? initialState.textData : { list: [] };\n        IMAGE_DATA = initialState.imageData ? initialState.imageData : { list: [] };\n        EDGE_LEGEND = initialState.edgeLegend || {};\n        savedPositions = initialState.nodePositions || {};\n        savedSizes = initialState.nodeSizes || {};\n        savedStyles = initialState.nodeStyles || {};\n        if (initialState.iconCache) {\n         IconLibrary.iconCache = initialState.iconCache;\n        }\n       }\n       if (initialState.page) {\n        PAGE_STATE = Object.assign({}, DEFAULT_PAGE_STATE, initialState.page);\n       }\n       if (PAGE_STATE.autoPingEnabled !== undefined) {\n        autoPingEnabled = PAGE_STATE.autoPingEnabled;\n       }\n       if (PAGE_STATE.autoPingInterval !== undefined) {\n        autoPingInterval = PAGE_STATE.autoPingInterval;\n       }\n       if (initialState.canvas) {\n        canvasState.zoom = initialState.canvas.zoom || 1;\n        canvasState.panX = initialState.canvas.panX || 0;\n        canvasState.panY = initialState.canvas.panY || 0;\n       }\n       if (initialState.savedTopologyView) {\n        savedTopologyView = initialState.savedTopologyView;\n       }\n       if (initialState.documentTabs) {\n        documentTabs = initialState.documentTabs;\n        if (initialState.currentTabIndex !== undefined) {\n          currentTabIndex = initialState.currentTabIndex;\n          const currentTab = documentTabs[currentTabIndex];\n          if (currentTab) {\n            NODE_DATA = currentTab.nodes || NODE_DATA;\n            EDGE_DATA = currentTab.edges || EDGE_DATA;\n            savedPositions = currentTab.positions || savedPositions;\n            savedSizes = currentTab.sizes || savedSizes;\n            savedStyles = currentTab.styles || savedStyles;\n            EDGE_LEGEND = currentTab.legend || EDGE_LEGEND;\n            RECT_DATA = currentTab.rects || RECT_DATA;\n            TEXT_DATA = currentTab.texts || TEXT_DATA;\n            if (currentTab.pageState) PAGE_STATE = Object.assign({}, DEFAULT_PAGE_STATE, currentTab.pageState);\n          }\n        }\n       }\n       if (initialState.encryptedSections) {\n        encryptedSections = initialState.encryptedSections;\n       }\n       if (initialState.recordings && Array.isArray(initialState.recordings)) {\n        RECORDING_STATE.recordings = initialState.recordings;\n        RECORDING_STATE.currentRecording = initialState.recordings[0] || null;\n       }\n       if (initialState.customLanguage) {\n        CUSTOM_LANG = initialState.customLanguage;\n        applyLanguage();\n       }\n       const layerMap = { \"physical\": \"layer1\", \"logical\": \"layer2\", \"security\": \"layer3\", \"application\": \"layer4\" };\n       Object.values(NODE_DATA).forEach(node => {\n         if (!node.tags) node.tags = [];\n         if (!node.notes) node.notes = [];\n         if (!node.layer || layerMap[node.layer]) node.layer = layerMap[node.layer] || 'layer1';\n       });\n       wieldThePower();\n       forgeTheTopology();\n       updateViewBox();\n       MobileManager.applyAll();\n       if (autoPingEnabled) {\n        startAutoPing();\n       }\n       const initialNodes = Object.keys(NODE_DATA);\n       if (initialNodes.length > 0) {\n        claimTheImmortal(initialNodes.includes(\"host\") ? \"host\" : initialNodes[0]);\n       } else {\n        document.getElementById(\"node-panel\").style.display = \"none\";\n        document.getElementById(\"topology-toolbar\").style.display = \"none\";\n       }\n       if (Object.keys(NODE_DATA).length === 0 && EDGE_DATA.list.length === 0) {\n        checkForAutosaveRecovery().then(recovered => {\n          if (recovered) return;\n          if (Object.keys(NODE_DATA).length > 0 || EDGE_DATA.list.length > 0) return;\n          if (PAGE_STATE.background && PAGE_STATE.background !== \"\") return;\n          if (typeof showWelcomeModal === 'function') showWelcomeModal();\n        });\n       }\n       startAutosave();\n      })();\n      let resizeDebounceTimer = null;\n      window.addEventListener(\"resize\", () => {\n        clearTimeout(resizeDebounceTimer);\n        resizeDebounceTimer = setTimeout(() => {\n          MobileManager.applyAll();\n          if (typeof updateDrawToolbarVisibility === 'function') updateDrawToolbarVisibility();\n          if (typeof updateTopologyToolbarVisibility === 'function') updateTopologyToolbarVisibility();\n        }, 100);\n      });\n      function getBreakpointKey() {\n       const w = window.innerWidth;\n       if (w <= 380) return \"fold\";\n       if (w <= 768) return \"mobile\";\n       if (w <= 1024) return \"tablet\";\n       return \"desktop\";\n      }\n      function resolveStylesEntry(styleEntry) {\n       if (!styleEntry) return {};\n       if (styleEntry.circleColor || styleEntry.titleColor || styleEntry.titleFont || styleEntry.titleSize || styleEntry.subColor || styleEntry.subFont || styleEntry.subSize) {\n        return styleEntry;\n       }\n       const bp = getBreakpointKey();\n       const base = styleEntry.all || {};\n       const bpStyles = styleEntry[bp] || {};\n       return Object.assign({}, base, bpStyles);\n      }\n      function resolveStylesForNode(id) {\n       const styleEntry = savedStyles[id];\n       if (!styleEntry) return {};\n       return resolveStylesEntry(styleEntry);\n      }\n      function ensureStyleEntry(id) {\n       if (!savedStyles[id]) savedStyles[id] = {};\n       const entry = savedStyles[id];\n       const flatProps = [\"circleColor\", \"circleBorder\", \"titleColor\", \"titleFont\", \"titleSize\", \"subColor\", \"subFont\", \"subSize\", ];\n       const isFlat = flatProps.some((p) => Object.prototype.hasOwnProperty.call(entry, p));\n       if (isFlat) {\n        const all = entry.all || {};\n        flatProps.forEach((p) => {\n         if (entry[p] !== undefined) {\n          all[p] = entry[p];\n          delete entry[p];\n         }\n        });\n        entry.all = all;\n       }\n       return entry;\n      }\n      function getDefaultSize() {\n       if (window.innerWidth <= 380) return 120;\n       if (window.innerWidth <= 768) return 140;\n       if (window.innerWidth <= 1024) return 70;\n       return 55;\n      }\n      function createShapeElement(shape, size) {\n       const ns = \"http://www.w3.org/2000/svg\";\n       if (shape === \"circle\") {\n        const c = document.createElementNS(ns, \"circle\");\n        c.setAttribute(\"r\", size);\n        return c;\n       }\n       if (shape === \"square\") {\n        const r = document.createElementNS(ns, \"rect\");\n        const s = size * 2;\n        r.setAttribute(\"x\", -size);\n        r.setAttribute(\"y\", -size);\n        r.setAttribute(\"width\", s);\n        r.setAttribute(\"height\", s);\n        r.setAttribute(\"rx\", 4);\n        return r;\n       }\n       if (shape === \"rectangle\") {\n        const r = document.createElementNS(ns, \"rect\");\n        r.setAttribute(\"x\", -size * 1.4);\n        r.setAttribute(\"y\", -size * 0.8);\n        r.setAttribute(\"width\", size * 2.8);\n        r.setAttribute(\"height\", size * 1.6);\n        r.setAttribute(\"rx\", 6);\n        return r;\n       }\n       if (shape === \"triangle\") {\n        const p = document.createElementNS(ns, \"polygon\");\n        const h = size * 1.8;\n        const w = size * 2;\n        p.setAttribute(\"points\", `0,${-h} ${w / 2},${h / 2} ${-w / 2},${h / 2}`);\n        return p;\n       }\n       if (shape === \"hexagon\") {\n        const p = document.createElementNS(ns, \"polygon\");\n        const s = size;\n        const pts = [\n         [0, -s],\n         [s * 0.86, -s * 0.5],\n         [s * 0.86, s * 0.5],\n         [0, s],\n         [-s * 0.86, s * 0.5],\n         [-s * 0.86, -s * 0.5],\n        ].map((pt) => pt.join(\",\")).join(\" \");\n        p.setAttribute(\"points\", pts);\n        return p;\n       }\n       if (shape === \"stop-sign\") {\n        const p = document.createElementNS(ns, \"polygon\");\n        const r = size;\n        const ptsArr = [];\n        for (let i = 0; i < 8; i++) {\n         const a = (Math.PI / 4) * i + Math.PI / 8;\n         ptsArr.push([Math.cos(a) * r, Math.sin(a) * r]);\n        }\n        const pts = ptsArr.map((pt) => pt.join(\",\")).join(\" \");\n        p.setAttribute(\"points\", pts);\n        return p;\n       }\n       if (shape === \"star\") {\n        const p = document.createElementNS(ns, \"polygon\");\n        const outer = size;\n        const inner = size * 0.45;\n        let pts = \"\";\n        for (let i = 0; i < 10; i++) {\n         const a = (Math.PI / 5) * i - Math.PI / 2;\n         const r = i % 2 === 0 ? outer : inner;\n         pts += `${Math.cos(a) * r},${Math.sin(a) * r} `;\n        }\n        p.setAttribute(\"points\", pts.trim());\n        return p;\n       }\n       if (shape === \"diamond\") {\n        const p = document.createElementNS(ns, \"polygon\");\n        const s = size;\n        p.setAttribute(\"points\", `0,${-s} ${s},0 0,${s} ${-s},0`);\n        return p;\n       }\n       if (shape === \"server\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 1.2);\n        body.setAttribute(\"y\", -size * 0.6);\n        body.setAttribute(\"width\", size * 2.4);\n        body.setAttribute(\"height\", size * 1.2);\n        body.setAttribute(\"rx\", 4);\n        g.appendChild(body);\n        for (let i = 0; i < 3; i++) {\n         const line = document.createElementNS(ns, \"line\");\n         line.setAttribute(\"x1\", -size * 0.9 + i * size * 0.5);\n         line.setAttribute(\"y1\", -size * 0.3);\n         line.setAttribute(\"x2\", -size * 0.9 + i * size * 0.5);\n         line.setAttribute(\"y2\", size * 0.3);\n         line.style.stroke = \"currentColor\";\n         line.style.strokeWidth = \"2\";\n         line.style.opacity = \"0.5\";\n         g.appendChild(line);\n        }\n        const led = document.createElementNS(ns, \"circle\");\n        led.setAttribute(\"cx\", size * 0.9);\n        led.setAttribute(\"cy\", 0);\n        led.setAttribute(\"r\", size * 0.1);\n        led.style.fill = \"#4ade80\";\n        g.appendChild(led);\n        return g;\n       }\n       if (shape === \"pc\" || shape === \"desktop\") {\n        const g = document.createElementNS(ns, \"g\");\n        const monitor = document.createElementNS(ns, \"rect\");\n        monitor.setAttribute(\"x\", -size * 0.9);\n        monitor.setAttribute(\"y\", -size * 0.8);\n        monitor.setAttribute(\"width\", size * 1.8);\n        monitor.setAttribute(\"height\", size * 1.2);\n        monitor.setAttribute(\"rx\", 4);\n        g.appendChild(monitor);\n        const screen = document.createElementNS(ns, \"rect\");\n        screen.setAttribute(\"x\", -size * 0.75);\n        screen.setAttribute(\"y\", -size * 0.65);\n        screen.setAttribute(\"width\", size * 1.5);\n        screen.setAttribute(\"height\", size * 0.9);\n        screen.setAttribute(\"rx\", 2);\n        screen.style.fill = \"#1e293b\";\n        g.appendChild(screen);\n        const neck = document.createElementNS(ns, \"rect\");\n        neck.setAttribute(\"x\", -size * 0.15);\n        neck.setAttribute(\"y\", size * 0.4);\n        neck.setAttribute(\"width\", size * 0.3);\n        neck.setAttribute(\"height\", size * 0.3);\n        g.appendChild(neck);\n        const base = document.createElementNS(ns, \"rect\");\n        base.setAttribute(\"x\", -size * 0.5);\n        base.setAttribute(\"y\", size * 0.7);\n        base.setAttribute(\"width\", size * 1);\n        base.setAttribute(\"height\", size * 0.15);\n        base.setAttribute(\"rx\", 2);\n        g.appendChild(base);\n        return g;\n       }\n       if (shape === \"laptop\") {\n        const g = document.createElementNS(ns, \"g\");\n        const screen = document.createElementNS(ns, \"rect\");\n        screen.setAttribute(\"x\", -size * 0.8);\n        screen.setAttribute(\"y\", -size * 0.9);\n        screen.setAttribute(\"width\", size * 1.6);\n        screen.setAttribute(\"height\", size * 1.1);\n        screen.setAttribute(\"rx\", 4);\n        g.appendChild(screen);\n        const base = document.createElementNS(ns, \"rect\");\n        base.setAttribute(\"x\", -size * 0.9);\n        base.setAttribute(\"y\", size * 0.25);\n        base.setAttribute(\"width\", size * 1.8);\n        base.setAttribute(\"height\", size * 0.6);\n        base.setAttribute(\"rx\", 4);\n        g.appendChild(base);\n        const pad = document.createElementNS(ns, \"rect\");\n        pad.setAttribute(\"x\", -size * 0.25);\n        pad.setAttribute(\"y\", size * 0.45);\n        pad.setAttribute(\"width\", size * 0.5);\n        pad.setAttribute(\"height\", size * 0.25);\n        pad.setAttribute(\"rx\", 2);\n        pad.style.fill = \"#1e293b\";\n        g.appendChild(pad);\n        return g;\n       }\n       if (shape === \"phone\" || shape === \"mobile\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 0.45);\n        body.setAttribute(\"y\", -size);\n        body.setAttribute(\"width\", size * 0.9);\n        body.setAttribute(\"height\", size * 2);\n        body.setAttribute(\"rx\", 8);\n        g.appendChild(body);\n        const screen = document.createElementNS(ns, \"rect\");\n        screen.setAttribute(\"x\", -size * 0.38);\n        screen.setAttribute(\"y\", -size * 0.85);\n        screen.setAttribute(\"width\", size * 0.76);\n        screen.setAttribute(\"height\", size * 1.6);\n        screen.setAttribute(\"rx\", 4);\n        screen.style.fill = \"#1e293b\";\n        g.appendChild(screen);\n        const btn = document.createElementNS(ns, \"rect\");\n        btn.setAttribute(\"x\", -size * 0.15);\n        btn.setAttribute(\"y\", size * 0.82);\n        btn.setAttribute(\"width\", size * 0.3);\n        btn.setAttribute(\"height\", size * 0.06);\n        btn.setAttribute(\"rx\", 2);\n        btn.style.fill = \"#475569\";\n        g.appendChild(btn);\n        return g;\n       }\n       if (shape === \"router\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 1.1);\n        body.setAttribute(\"y\", -size * 0.3);\n        body.setAttribute(\"width\", size * 2.2);\n        body.setAttribute(\"height\", size * 0.8);\n        body.setAttribute(\"rx\", 6);\n        g.appendChild(body);\n        for (let i = -1; i <= 1; i++) {\n         const ant = document.createElementNS(ns, \"rect\");\n         ant.setAttribute(\"x\", i * size * 0.6 - size * 0.05);\n         ant.setAttribute(\"y\", -size * 0.9);\n         ant.setAttribute(\"width\", size * 0.1);\n         ant.setAttribute(\"height\", size * 0.6);\n         ant.setAttribute(\"rx\", 2);\n         g.appendChild(ant);\n         const tip = document.createElementNS(ns, \"circle\");\n         tip.setAttribute(\"cx\", i * size * 0.6);\n         tip.setAttribute(\"cy\", -size * 0.95);\n         tip.setAttribute(\"r\", size * 0.08);\n         g.appendChild(tip);\n        }\n        for (let i = 0; i < 4; i++) {\n         const led = document.createElementNS(ns, \"circle\");\n         led.setAttribute(\"cx\", -size * 0.7 + i * size * 0.35);\n         led.setAttribute(\"cy\", size * 0.1);\n         led.setAttribute(\"r\", size * 0.06);\n         led.style.fill = i < 2 ? \"#4ade80\" : \"#facc15\";\n         g.appendChild(led);\n        }\n        return g;\n       }\n       if (shape === \"switch\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 1.4);\n        body.setAttribute(\"y\", -size * 0.4);\n        body.setAttribute(\"width\", size * 2.8);\n        body.setAttribute(\"height\", size * 0.8);\n        body.setAttribute(\"rx\", 4);\n        g.appendChild(body);\n        for (let i = 0; i < 8; i++) {\n         const port = document.createElementNS(ns, \"rect\");\n         port.setAttribute(\"x\", -size * 1.2 + i * size * 0.32);\n         port.setAttribute(\"y\", -size * 0.15);\n         port.setAttribute(\"width\", size * 0.22);\n         port.setAttribute(\"height\", size * 0.3);\n         port.setAttribute(\"rx\", 1);\n         port.style.fill = \"#1e293b\";\n         g.appendChild(port);\n        }\n        return g;\n       }\n       if (shape === \"firewall\") {\n        const g = document.createElementNS(ns, \"g\");\n        const wall = document.createElementNS(ns, \"rect\");\n        wall.setAttribute(\"x\", -size);\n        wall.setAttribute(\"y\", -size * 0.8);\n        wall.setAttribute(\"width\", size * 2);\n        wall.setAttribute(\"height\", size * 1.6);\n        wall.setAttribute(\"rx\", 4);\n        g.appendChild(wall);\n        for (let row = 0; row < 3; row++) {\n         const line = document.createElementNS(ns, \"line\");\n         line.setAttribute(\"x1\", -size * 0.85);\n         line.setAttribute(\"y1\", -size * 0.5 + row * size * 0.45);\n         line.setAttribute(\"x2\", size * 0.85);\n         line.setAttribute(\"y2\", -size * 0.5 + row * size * 0.45);\n         line.style.stroke = \"#475569\";\n         line.style.strokeWidth = \"2\";\n         g.appendChild(line);\n        }\n        for (let row = 0; row < 4; row++) {\n         const offset = row % 2 === 0 ? 0 : size * 0.35;\n         for (let col = 0; col < 3; col++) {\n          const line = document.createElementNS(ns, \"line\");\n          const x = -size * 0.5 + col * size * 0.7 + offset;\n          if (x > -size * 0.85 && x < size * 0.85) {\n           line.setAttribute(\"x1\", x);\n           line.setAttribute(\"y1\", -size * 0.8 + row * size * 0.45);\n           line.setAttribute(\"x2\", x);\n           line.setAttribute(\"y2\", -size * 0.8 + row * size * 0.45 + size * 0.45);\n           line.style.stroke = \"#475569\";\n           line.style.strokeWidth = \"2\";\n           g.appendChild(line);\n          }\n         }\n        }\n        return g;\n       }\n       if (shape === \"cloud\") {\n        const p = document.createElementNS(ns, \"path\");\n        const s = size;\n        p.setAttribute(\"d\", `\n             M ${-s * 0.8} ${s * 0.2}\n             Q ${-s * 1.1} ${s * 0.2} ${-s * 1.1} ${-s * 0.1}\n             Q ${-s * 1.1} ${-s * 0.5} ${-s * 0.7} ${-s * 0.5}\n             Q ${-s * 0.7} ${-s * 0.9} ${-s * 0.2} ${-s * 0.9}\n             Q ${s * 0.1} ${-s * 1.1} ${s * 0.5} ${-s * 0.8}\n             Q ${s * 1} ${-s * 0.8} ${s * 1.1} ${-s * 0.3}\n             Q ${s * 1.3} ${-s * 0.1} ${s * 1.1} ${s * 0.2}\n             Q ${s * 1.1} ${s * 0.5} ${s * 0.7} ${s * 0.5}\n             L ${-s * 0.5} ${s * 0.5}\n             Q ${-s * 0.9} ${s * 0.5} ${-s * 0.9} ${s * 0.2}\n             Z\n            `);\n        return p;\n       }\n       if (shape === \"database\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 0.7);\n        body.setAttribute(\"y\", -size * 0.6);\n        body.setAttribute(\"width\", size * 1.4);\n        body.setAttribute(\"height\", size * 1.4);\n        g.appendChild(body);\n        const top = document.createElementNS(ns, \"ellipse\");\n        top.setAttribute(\"cx\", 0);\n        top.setAttribute(\"cy\", -size * 0.6);\n        top.setAttribute(\"rx\", size * 0.7);\n        top.setAttribute(\"ry\", size * 0.25);\n        g.appendChild(top);\n        const bottom = document.createElementNS(ns, \"ellipse\");\n        bottom.setAttribute(\"cx\", 0);\n        bottom.setAttribute(\"cy\", size * 0.8);\n        bottom.setAttribute(\"rx\", size * 0.7);\n        bottom.setAttribute(\"ry\", size * 0.25);\n        g.appendChild(bottom);\n        const mid1 = document.createElementNS(ns, \"ellipse\");\n        mid1.setAttribute(\"cx\", 0);\n        mid1.setAttribute(\"cy\", -size * 0.15);\n        mid1.setAttribute(\"rx\", size * 0.7);\n        mid1.setAttribute(\"ry\", size * 0.2);\n        mid1.style.fill = \"none\";\n        mid1.style.stroke = \"#475569\";\n        mid1.style.strokeWidth = \"2\";\n        g.appendChild(mid1);\n        const mid2 = document.createElementNS(ns, \"ellipse\");\n        mid2.setAttribute(\"cx\", 0);\n        mid2.setAttribute(\"cy\", size * 0.35);\n        mid2.setAttribute(\"rx\", size * 0.7);\n        mid2.setAttribute(\"ry\", size * 0.2);\n        mid2.style.fill = \"none\";\n        mid2.style.stroke = \"#475569\";\n        mid2.style.strokeWidth = \"2\";\n        g.appendChild(mid2);\n        return g;\n       }\n       if (shape === \"printer\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size);\n        body.setAttribute(\"y\", -size * 0.4);\n        body.setAttribute(\"width\", size * 2);\n        body.setAttribute(\"height\", size * 0.9);\n        body.setAttribute(\"rx\", 4);\n        g.appendChild(body);\n        const trayTop = document.createElementNS(ns, \"rect\");\n        trayTop.setAttribute(\"x\", -size * 0.7);\n        trayTop.setAttribute(\"y\", -size * 0.8);\n        trayTop.setAttribute(\"width\", size * 1.4);\n        trayTop.setAttribute(\"height\", size * 0.4);\n        trayTop.setAttribute(\"rx\", 2);\n        trayTop.style.fill = \"#1e293b\";\n        g.appendChild(trayTop);\n        const trayOut = document.createElementNS(ns, \"rect\");\n        trayOut.setAttribute(\"x\", -size * 0.6);\n        trayOut.setAttribute(\"y\", size * 0.5);\n        trayOut.setAttribute(\"width\", size * 1.2);\n        trayOut.setAttribute(\"height\", size * 0.35);\n        trayOut.setAttribute(\"rx\", 2);\n        g.appendChild(trayOut);\n        const paper = document.createElementNS(ns, \"rect\");\n        paper.setAttribute(\"x\", -size * 0.5);\n        paper.setAttribute(\"y\", size * 0.3);\n        paper.setAttribute(\"width\", size * 1);\n        paper.setAttribute(\"height\", size * 0.5);\n        paper.style.fill = \"#e2e8f0\";\n        g.appendChild(paper);\n        return g;\n       }\n       if (shape === \"access-point\" || shape === \"wifi\") {\n        const g = document.createElementNS(ns, \"g\");\n        const base = document.createElementNS(ns, \"rect\");\n        base.setAttribute(\"x\", -size * 0.3);\n        base.setAttribute(\"y\", size * 0.2);\n        base.setAttribute(\"width\", size * 0.6);\n        base.setAttribute(\"height\", size * 0.3);\n        base.setAttribute(\"rx\", 3);\n        g.appendChild(base);\n        for (let i = 1; i <= 3; i++) {\n         const arc = document.createElementNS(ns, \"path\");\n         const r = size * 0.3 * i;\n         arc.setAttribute(\"d\", `M ${-r * 0.7} ${size * 0.1 - r * 0.5} A ${r} ${r} 0 0 1 ${r * 0.7} ${size * 0.1 - r * 0.5}`);\n         arc.style.fill = \"none\";\n         arc.style.stroke = \"currentColor\";\n         arc.style.strokeWidth = \"2\";\n         arc.style.opacity = 1 - (i * 0.2);\n         g.appendChild(arc);\n        }\n        return g;\n       }\n       if (shape === \"load-balancer\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size);\n        body.setAttribute(\"y\", -size * 0.5);\n        body.setAttribute(\"width\", size * 2);\n        body.setAttribute(\"height\", size);\n        body.setAttribute(\"rx\", 6);\n        g.appendChild(body);\n        const bar = document.createElementNS(ns, \"line\");\n        bar.setAttribute(\"x1\", -size * 0.6);\n        bar.setAttribute(\"y1\", 0);\n        bar.setAttribute(\"x2\", size * 0.6);\n        bar.setAttribute(\"y2\", 0);\n        bar.style.stroke = \"#4ade80\";\n        bar.style.strokeWidth = \"3\";\n        g.appendChild(bar);\n        [-1, 1].forEach(dir => {\n         const arrow = document.createElementNS(ns, \"path\");\n         arrow.setAttribute(\"d\", `M ${dir * size * 0.3} ${-size * 0.2} L ${dir * size * 0.6} 0 L ${dir * size * 0.3} ${size * 0.2}`);\n         arrow.style.fill = \"none\";\n         arrow.style.stroke = \"#4ade80\";\n         arrow.style.strokeWidth = \"2\";\n         g.appendChild(arrow);\n        });\n        return g;\n       }\n       if (shape === \"nas\" || shape === \"storage\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 0.8);\n        body.setAttribute(\"y\", -size * 0.9);\n        body.setAttribute(\"width\", size * 1.6);\n        body.setAttribute(\"height\", size * 1.8);\n        body.setAttribute(\"rx\", 4);\n        g.appendChild(body);\n        for (let i = 0; i < 4; i++) {\n         const bay = document.createElementNS(ns, \"rect\");\n         bay.setAttribute(\"x\", -size * 0.6);\n         bay.setAttribute(\"y\", -size * 0.7 + i * size * 0.4);\n         bay.setAttribute(\"width\", size * 1.2);\n         bay.setAttribute(\"height\", size * 0.3);\n         bay.setAttribute(\"rx\", 2);\n         bay.style.fill = \"#1e293b\";\n         g.appendChild(bay);\n        }\n        return g;\n       }\n       if (shape === \"gateway\" || shape === \"modem\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 0.5);\n        body.setAttribute(\"y\", -size * 0.9);\n        body.setAttribute(\"width\", size);\n        body.setAttribute(\"height\", size * 1.8);\n        body.setAttribute(\"rx\", 4);\n        g.appendChild(body);\n        for (let i = 0; i < 4; i++) {\n         const led = document.createElementNS(ns, \"circle\");\n         led.setAttribute(\"cx\", 0);\n         led.setAttribute(\"cy\", -size * 0.6 + i * size * 0.35);\n         led.setAttribute(\"r\", size * 0.08);\n         led.style.fill = [\"#4ade80\", \"#4ade80\", \"#facc15\", \"#60a5fa\"][i];\n         g.appendChild(led);\n        }\n        return g;\n       }\n       if (shape === \"vpn\" || shape === \"tunnel\") {\n        const g = document.createElementNS(ns, \"g\");\n        const outer = document.createElementNS(ns, \"ellipse\");\n        outer.setAttribute(\"cx\", 0);\n        outer.setAttribute(\"cy\", 0);\n        outer.setAttribute(\"rx\", size);\n        outer.setAttribute(\"ry\", size * 0.6);\n        g.appendChild(outer);\n        const inner = document.createElementNS(ns, \"ellipse\");\n        inner.setAttribute(\"cx\", 0);\n        inner.setAttribute(\"cy\", 0);\n        inner.setAttribute(\"rx\", size * 0.5);\n        inner.setAttribute(\"ry\", size * 0.3);\n        inner.style.fill = \"#1e293b\";\n        g.appendChild(inner);\n        const lock = document.createElementNS(ns, \"rect\");\n        lock.setAttribute(\"x\", -size * 0.15);\n        lock.setAttribute(\"y\", -size * 0.1);\n        lock.setAttribute(\"width\", size * 0.3);\n        lock.setAttribute(\"height\", size * 0.25);\n        lock.setAttribute(\"rx\", 2);\n        lock.style.fill = \"#4ade80\";\n        g.appendChild(lock);\n        return g;\n       }\n       if (shape === \"container\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size);\n        body.setAttribute(\"y\", -size * 0.5);\n        body.setAttribute(\"width\", size * 2);\n        body.setAttribute(\"height\", size);\n        body.setAttribute(\"rx\", 4);\n        g.appendChild(body);\n        for (let i = 0; i < 3; i++) {\n         const block = document.createElementNS(ns, \"rect\");\n         block.setAttribute(\"x\", -size * 0.8 + i * size * 0.55);\n         block.setAttribute(\"y\", -size * 0.3);\n         block.setAttribute(\"width\", size * 0.45);\n         block.setAttribute(\"height\", size * 0.6);\n         block.setAttribute(\"rx\", 2);\n         block.style.fill = \"#1e293b\";\n         g.appendChild(block);\n        }\n        return g;\n       }\n       if (shape === \"vm\" || shape === \"virtual\") {\n        const g = document.createElementNS(ns, \"g\");\n        const shadow = document.createElementNS(ns, \"rect\");\n        shadow.setAttribute(\"x\", -size * 0.85 + 4);\n        shadow.setAttribute(\"y\", -size * 0.65 + 4);\n        shadow.setAttribute(\"width\", size * 1.7);\n        shadow.setAttribute(\"height\", size * 1.3);\n        shadow.setAttribute(\"rx\", 4);\n        shadow.style.opacity = \"0.3\";\n        g.appendChild(shadow);\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 0.85);\n        body.setAttribute(\"y\", -size * 0.65);\n        body.setAttribute(\"width\", size * 1.7);\n        body.setAttribute(\"height\", size * 1.3);\n        body.setAttribute(\"rx\", 4);\n        g.appendChild(body);\n        const screen = document.createElementNS(ns, \"rect\");\n        screen.setAttribute(\"x\", -size * 0.65);\n        screen.setAttribute(\"y\", -size * 0.45);\n        screen.setAttribute(\"width\", size * 1.3);\n        screen.setAttribute(\"height\", size * 0.7);\n        screen.setAttribute(\"rx\", 2);\n        screen.style.fill = \"#1e293b\";\n        g.appendChild(screen);\n        return g;\n       }\n       if (shape === \"kubernetes\" || shape === \"k8s\") {\n        const g = document.createElementNS(ns, \"g\");\n        const outer = document.createElementNS(ns, \"circle\");\n        outer.setAttribute(\"cx\", 0);\n        outer.setAttribute(\"cy\", 0);\n        outer.setAttribute(\"r\", size);\n        g.appendChild(outer);\n        const inner = document.createElementNS(ns, \"circle\");\n        inner.setAttribute(\"cx\", 0);\n        inner.setAttribute(\"cy\", 0);\n        inner.setAttribute(\"r\", size * 0.4);\n        inner.style.fill = \"#1e293b\";\n        g.appendChild(inner);\n        for (let i = 0; i < 7; i++) {\n         const angle = (Math.PI * 2 / 7) * i - Math.PI / 2;\n         const spoke = document.createElementNS(ns, \"line\");\n         spoke.setAttribute(\"x1\", Math.cos(angle) * size * 0.4);\n         spoke.setAttribute(\"y1\", Math.sin(angle) * size * 0.4);\n         spoke.setAttribute(\"x2\", Math.cos(angle) * size * 0.85);\n         spoke.setAttribute(\"y2\", Math.sin(angle) * size * 0.85);\n         spoke.style.stroke = \"#326ce5\";\n         spoke.style.strokeWidth = \"3\";\n         g.appendChild(spoke);\n        }\n        return g;\n       }\n       if (shape === \"shield\" || shape === \"security\") {\n        const p = document.createElementNS(ns, \"path\");\n        const s = size;\n        p.setAttribute(\"d\", `M 0 ${-s} L ${s * 0.85} ${-s * 0.5} L ${s * 0.85} ${s * 0.2} Q ${s * 0.7} ${s * 0.9} 0 ${s} Q ${-s * 0.7} ${s * 0.9} ${-s * 0.85} ${s * 0.2} L ${-s * 0.85} ${-s * 0.5} Z`);\n        return p;\n       }\n       if (shape === \"camera\" || shape === \"cctv\") {\n        const g = document.createElementNS(ns, \"g\");\n        const mount = document.createElementNS(ns, \"rect\");\n        mount.setAttribute(\"x\", -size * 1.1);\n        mount.setAttribute(\"y\", -size * 0.5);\n        mount.setAttribute(\"width\", size * 0.25);\n        mount.setAttribute(\"height\", size * 0.6);\n        mount.setAttribute(\"rx\", 2);\n        g.appendChild(mount);\n        const arm = document.createElementNS(ns, \"rect\");\n        arm.setAttribute(\"x\", -size * 0.9);\n        arm.setAttribute(\"y\", -size * 0.15);\n        arm.setAttribute(\"width\", size * 0.5);\n        arm.setAttribute(\"height\", size * 0.15);\n        g.appendChild(arm);\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 0.45);\n        body.setAttribute(\"y\", -size * 0.35);\n        body.setAttribute(\"width\", size * 1.1);\n        body.setAttribute(\"height\", size * 0.7);\n        body.setAttribute(\"rx\", 4);\n        g.appendChild(body);\n        const lensHousing = document.createElementNS(ns, \"circle\");\n        lensHousing.setAttribute(\"cx\", size * 0.85);\n        lensHousing.setAttribute(\"cy\", 0);\n        lensHousing.setAttribute(\"r\", size * 0.4);\n        g.appendChild(lensHousing);\n        const lensOuter = document.createElementNS(ns, \"circle\");\n        lensOuter.setAttribute(\"cx\", size * 0.85);\n        lensOuter.setAttribute(\"cy\", 0);\n        lensOuter.setAttribute(\"r\", size * 0.28);\n        lensOuter.style.fill = \"#1e293b\";\n        g.appendChild(lensOuter);\n        const lensInner = document.createElementNS(ns, \"circle\");\n        lensInner.setAttribute(\"cx\", size * 0.85);\n        lensInner.setAttribute(\"cy\", 0);\n        lensInner.setAttribute(\"r\", size * 0.15);\n        lensInner.style.fill = \"#3b82f6\";\n        g.appendChild(lensInner);\n        const reflection = document.createElementNS(ns, \"circle\");\n        reflection.setAttribute(\"cx\", size * 0.8);\n        reflection.setAttribute(\"cy\", -size * 0.05);\n        reflection.setAttribute(\"r\", size * 0.05);\n        reflection.style.fill = \"#93c5fd\";\n        g.appendChild(reflection);\n        const led = document.createElementNS(ns, \"circle\");\n        led.setAttribute(\"cx\", -size * 0.2);\n        led.setAttribute(\"cy\", -size * 0.15);\n        led.setAttribute(\"r\", size * 0.06);\n        led.style.fill = \"#ef4444\";\n        g.appendChild(led);\n        return g;\n       }\n       if (shape === \"monitor\" || shape === \"dashboard\") {\n        const g = document.createElementNS(ns, \"g\");\n        const screen = document.createElementNS(ns, \"rect\");\n        screen.setAttribute(\"x\", -size);\n        screen.setAttribute(\"y\", -size * 0.7);\n        screen.setAttribute(\"width\", size * 2);\n        screen.setAttribute(\"height\", size * 1.2);\n        screen.setAttribute(\"rx\", 4);\n        g.appendChild(screen);\n        const graph = document.createElementNS(ns, \"polyline\");\n        graph.setAttribute(\"points\", `${-size * 0.8},${size * 0.2} ${-size * 0.3},${-size * 0.2} ${size * 0.2},${size * 0.1} ${size * 0.7},${-size * 0.3}`);\n        graph.style.fill = \"none\";\n        graph.style.stroke = \"#4ade80\";\n        graph.style.strokeWidth = \"3\";\n        g.appendChild(graph);\n        return g;\n       }\n       if (shape === \"docker\" || shape === \"whale\") {\n        const g = document.createElementNS(ns, \"g\");\n        const s = size;\n        const body = document.createElementNS(ns, \"path\");\n        body.setAttribute(\"d\", `\n         M ${-s * 0.9} ${s * 0.2}\n         Q ${-s * 1.1} ${s * 0.5} ${-s * 0.7} ${s * 0.7}\n         Q ${-s * 0.3} ${s * 0.85} ${s * 0.3} ${s * 0.75}\n         Q ${s * 0.8} ${s * 0.6} ${s * 1.0} ${s * 0.3}\n         Q ${s * 1.1} ${s * 0.1} ${s * 1.0} ${-s * 0.1}\n         L ${s * 0.85} ${-s * 0.1}\n         Q ${s * 0.7} ${-s * 0.15} ${s * 0.4} ${-s * 0.2}\n         L ${-s * 0.5} ${-s * 0.2}\n         Q ${-s * 0.8} ${-s * 0.1} ${-s * 0.9} ${s * 0.2}\n         Z\n        `);\n        body.style.fill = \"#0db7ed\";\n        g.appendChild(body);\n        const tail = document.createElementNS(ns, \"path\");\n        tail.setAttribute(\"d\", `\n         M ${-s * 0.85} ${s * 0.1}\n         Q ${-s * 1.2} ${-s * 0.2} ${-s * 1.0} ${-s * 0.55}\n         Q ${-s * 0.95} ${-s * 0.35} ${-s * 0.8} ${-s * 0.15}\n         Z\n        `);\n        tail.style.fill = \"#0db7ed\";\n        g.appendChild(tail);\n        const belly = document.createElementNS(ns, \"path\");\n        belly.setAttribute(\"d\", `\n         M ${-s * 0.5} ${s * 0.65}\n         Q ${s * 0.1} ${s * 0.75} ${s * 0.6} ${s * 0.55}\n         Q ${s * 0.8} ${s * 0.45} ${s * 0.9} ${s * 0.25}\n         Q ${s * 0.7} ${s * 0.5} ${s * 0.3} ${s * 0.6}\n         Q ${-s * 0.1} ${s * 0.7} ${-s * 0.5} ${s * 0.65}\n         Z\n        `);\n        belly.style.fill = \"#ffffff\";\n        belly.style.opacity = \"0.3\";\n        g.appendChild(belly);\n        for (let row = 0; row < 2; row++) {\n         for (let col = 0; col < 3; col++) {\n          const container = document.createElementNS(ns, \"rect\");\n          container.setAttribute(\"x\", -s * 0.45 + col * s * 0.35);\n          container.setAttribute(\"y\", -s * 0.7 + row * s * 0.28);\n          container.setAttribute(\"width\", s * 0.3);\n          container.setAttribute(\"height\", s * 0.23);\n          container.setAttribute(\"rx\", 2);\n          container.style.fill = \"#0db7ed\";\n          container.style.stroke = \"#0a9ed8\";\n          container.style.strokeWidth = \"1.5\";\n          g.appendChild(container);\n         }\n        }\n        const topContainer = document.createElementNS(ns, \"rect\");\n        topContainer.setAttribute(\"x\", -s * 0.1);\n        topContainer.setAttribute(\"y\", -s * 0.95);\n        topContainer.setAttribute(\"width\", s * 0.3);\n        topContainer.setAttribute(\"height\", s * 0.23);\n        topContainer.setAttribute(\"rx\", 2);\n        topContainer.style.fill = \"#0db7ed\";\n        topContainer.style.stroke = \"#0a9ed8\";\n        topContainer.style.strokeWidth = \"1.5\";\n        g.appendChild(topContainer);\n        const eye = document.createElementNS(ns, \"circle\");\n        eye.setAttribute(\"cx\", s * 0.65);\n        eye.setAttribute(\"cy\", s * 0.25);\n        eye.setAttribute(\"r\", s * 0.08);\n        eye.style.fill = \"#0a5f7a\";\n        g.appendChild(eye);\n        return g;\n       }\n       if (shape === \"rounded-square\") {\n        const r = document.createElementNS(ns, \"rect\");\n        const s = size * 1.6;\n        r.setAttribute(\"x\", -s / 2);\n        r.setAttribute(\"y\", -s / 2);\n        r.setAttribute(\"width\", s);\n        r.setAttribute(\"height\", s);\n        r.setAttribute(\"rx\", size * 0.4);\n        return r;\n       }\n       if (shape === \"pill\" || shape === \"capsule\") {\n        const r = document.createElementNS(ns, \"rect\");\n        r.setAttribute(\"x\", -size * 1.2);\n        r.setAttribute(\"y\", -size * 0.5);\n        r.setAttribute(\"width\", size * 2.4);\n        r.setAttribute(\"height\", size);\n        r.setAttribute(\"rx\", size * 0.5);\n        return r;\n       }\n       if (shape === \"octagon\") {\n        const p = document.createElementNS(ns, \"polygon\");\n        const r = size;\n        const pts = [];\n        for (let i = 0; i < 8; i++) {\n         const a = (Math.PI / 4) * i - Math.PI / 8;\n         pts.push([Math.cos(a) * r, Math.sin(a) * r]);\n        }\n        p.setAttribute(\"points\", pts.map(pt => pt.join(\",\")).join(\" \"));\n        return p;\n       }\n       if (shape === \"pentagon\") {\n        const p = document.createElementNS(ns, \"polygon\");\n        const pts = [];\n        for (let i = 0; i < 5; i++) {\n         const a = (Math.PI * 2 / 5) * i - Math.PI / 2;\n         pts.push([Math.cos(a) * size, Math.sin(a) * size]);\n        }\n        p.setAttribute(\"points\", pts.map(pt => pt.join(\",\")).join(\" \"));\n        return p;\n       }\n       if (shape === \"cross\" || shape === \"plus\") {\n        const p = document.createElementNS(ns, \"polygon\");\n        const s = size;\n        const t = size * 0.35;\n        p.setAttribute(\"points\", `${-t},${-s} ${t},${-s} ${t},${-t} ${s},${-t} ${s},${t} ${t},${t} ${t},${s} ${-t},${s} ${-t},${t} ${-s},${t} ${-s},${-t} ${-t},${-t}`);\n        return p;\n       }\n       if (shape === \"parallelogram\") {\n        const p = document.createElementNS(ns, \"polygon\");\n        const s = size;\n        p.setAttribute(\"points\", `${-s * 0.6},${-s * 0.6} ${s * 1.2},${-s * 0.6} ${s * 0.6},${s * 0.6} ${-s * 1.2},${s * 0.6}`);\n        return p;\n       }\n       if (shape === \"trapezoid\") {\n        const p = document.createElementNS(ns, \"polygon\");\n        const s = size;\n        p.setAttribute(\"points\", `${-s * 0.6},${-s * 0.6} ${s * 0.6},${-s * 0.6} ${s},${s * 0.6} ${-s},${s * 0.6}`);\n        return p;\n       }\n       if (shape === \"sensor\" || shape === \"iot\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"circle\");\n        body.setAttribute(\"cx\", 0);\n        body.setAttribute(\"cy\", 0);\n        body.setAttribute(\"r\", size * 0.7);\n        g.appendChild(body);\n        const ant = document.createElementNS(ns, \"line\");\n        ant.setAttribute(\"x1\", 0);\n        ant.setAttribute(\"y1\", -size * 0.7);\n        ant.setAttribute(\"x2\", 0);\n        ant.setAttribute(\"y2\", -size);\n        ant.style.stroke = \"currentColor\";\n        ant.style.strokeWidth = \"2\";\n        g.appendChild(ant);\n        const tip = document.createElementNS(ns, \"circle\");\n        tip.setAttribute(\"cx\", 0);\n        tip.setAttribute(\"cy\", -size);\n        tip.setAttribute(\"r\", size * 0.1);\n        tip.style.fill = \"#4ade80\";\n        g.appendChild(tip);\n        return g;\n       }\n       if (shape === \"pi\" || shape === \"sbc\" || shape === \"raspberry\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size);\n        body.setAttribute(\"y\", -size * 0.65);\n        body.setAttribute(\"width\", size * 2);\n        body.setAttribute(\"height\", size * 1.3);\n        body.setAttribute(\"rx\", 4);\n        g.appendChild(body);\n        for (let i = 0; i < 10; i++) {\n         const pin = document.createElementNS(ns, \"rect\");\n         pin.setAttribute(\"x\", -size * 0.85 + i * size * 0.19);\n         pin.setAttribute(\"y\", -size * 0.8);\n         pin.setAttribute(\"width\", size * 0.08);\n         pin.setAttribute(\"height\", size * 0.15);\n         pin.style.fill = \"#facc15\";\n         g.appendChild(pin);\n        }\n        const port = document.createElementNS(ns, \"rect\");\n        port.setAttribute(\"x\", size * 0.6);\n        port.setAttribute(\"y\", -size * 0.2);\n        port.setAttribute(\"width\", size * 0.35);\n        port.setAttribute(\"height\", size * 0.4);\n        port.style.fill = \"#1e293b\";\n        g.appendChild(port);\n        return g;\n       }\n       if (shape === \"api\" || shape === \"endpoint\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 0.9);\n        body.setAttribute(\"y\", -size * 0.7);\n        body.setAttribute(\"width\", size * 1.8);\n        body.setAttribute(\"height\", size * 1.4);\n        body.setAttribute(\"rx\", 6);\n        g.appendChild(body);\n        const left = document.createElementNS(ns, \"text\");\n        left.setAttribute(\"x\", -size * 0.5);\n        left.setAttribute(\"y\", size * 0.15);\n        left.setAttribute(\"font-size\", size * 0.9);\n        left.setAttribute(\"fill\", \"#4ade80\");\n        left.setAttribute(\"font-family\", \"monospace\");\n        left.textContent = \"{\";\n        g.appendChild(left);\n        const right = document.createElementNS(ns, \"text\");\n        right.setAttribute(\"x\", size * 0.15);\n        right.setAttribute(\"y\", size * 0.15);\n        right.setAttribute(\"font-size\", size * 0.9);\n        right.setAttribute(\"fill\", \"#4ade80\");\n        right.setAttribute(\"font-family\", \"monospace\");\n        right.textContent = \"}\";\n        g.appendChild(right);\n        return g;\n       }\n       if (shape === \"queue\" || shape === \"message\") {\n        const g = document.createElementNS(ns, \"g\");\n        for (let i = 2; i >= 0; i--) {\n         const card = document.createElementNS(ns, \"rect\");\n         card.setAttribute(\"x\", -size * 0.7 + i * 4);\n         card.setAttribute(\"y\", -size * 0.5 + i * 4);\n         card.setAttribute(\"width\", size * 1.4);\n         card.setAttribute(\"height\", size * 0.8);\n         card.setAttribute(\"rx\", 3);\n         card.style.opacity = 1 - i * 0.25;\n         g.appendChild(card);\n        }\n        return g;\n       }\n       if (shape === \"lambda\" || shape === \"function\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 0.8);\n        body.setAttribute(\"y\", -size * 0.8);\n        body.setAttribute(\"width\", size * 1.6);\n        body.setAttribute(\"height\", size * 1.6);\n        body.setAttribute(\"rx\", 4);\n        g.appendChild(body);\n        const lambda = document.createElementNS(ns, \"text\");\n        lambda.setAttribute(\"x\", 0);\n        lambda.setAttribute(\"y\", size * 0.2);\n        lambda.setAttribute(\"font-size\", size * 1.2);\n        lambda.setAttribute(\"fill\", \"#f59e0b\");\n        lambda.setAttribute(\"text-anchor\", \"middle\");\n        lambda.setAttribute(\"font-family\", \"serif\");\n        lambda.textContent = \"λ\";\n        g.appendChild(lambda);\n        return g;\n       }\n       if (shape === \"bucket\" || shape === \"s3\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"path\");\n        body.setAttribute(\"d\", `M ${-size * 0.8} ${-size * 0.7} L ${-size * 0.6} ${size * 0.8} Q ${-size * 0.5} ${size} 0 ${size} Q ${size * 0.5} ${size} ${size * 0.6} ${size * 0.8} L ${size * 0.8} ${-size * 0.7} Z`);\n        g.appendChild(body);\n        const rim = document.createElementNS(ns, \"ellipse\");\n        rim.setAttribute(\"cx\", 0);\n        rim.setAttribute(\"cy\", -size * 0.7);\n        rim.setAttribute(\"rx\", size * 0.8);\n        rim.setAttribute(\"ry\", size * 0.25);\n        g.appendChild(rim);\n        return g;\n       }\n       if (shape === \"thermostat\") {\n        const g = document.createElementNS(ns, \"g\");\n        const outer = document.createElementNS(ns, \"circle\");\n        outer.setAttribute(\"r\", size);\n        g.appendChild(outer);\n        const inner = document.createElementNS(ns, \"circle\");\n        inner.setAttribute(\"r\", size * 0.75);\n        inner.style.fill = \"#1e293b\";\n        g.appendChild(inner);\n        const temp = document.createElementNS(ns, \"text\");\n        temp.setAttribute(\"x\", 0);\n        temp.setAttribute(\"y\", size * 0.15);\n        temp.setAttribute(\"font-size\", size * 0.5);\n        temp.setAttribute(\"fill\", \"#4ade80\");\n        temp.setAttribute(\"text-anchor\", \"middle\");\n        temp.setAttribute(\"font-family\", \"monospace\");\n        temp.textContent = \"72°\";\n        g.appendChild(temp);\n        return g;\n       }\n       if (shape === \"doorbell\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 0.5);\n        body.setAttribute(\"y\", -size);\n        body.setAttribute(\"width\", size);\n        body.setAttribute(\"height\", size * 2);\n        body.setAttribute(\"rx\", size * 0.25);\n        g.appendChild(body);\n        const lens = document.createElementNS(ns, \"circle\");\n        lens.setAttribute(\"cx\", 0);\n        lens.setAttribute(\"cy\", -size * 0.35);\n        lens.setAttribute(\"r\", size * 0.3);\n        lens.style.fill = \"#1e293b\";\n        g.appendChild(lens);\n        const lensDot = document.createElementNS(ns, \"circle\");\n        lensDot.setAttribute(\"cx\", 0);\n        lensDot.setAttribute(\"cy\", -size * 0.35);\n        lensDot.setAttribute(\"r\", size * 0.12);\n        lensDot.style.fill = \"#3b82f6\";\n        g.appendChild(lensDot);\n        const btn = document.createElementNS(ns, \"circle\");\n        btn.setAttribute(\"cx\", 0);\n        btn.setAttribute(\"cy\", size * 0.5);\n        btn.setAttribute(\"r\", size * 0.2);\n        btn.style.fill = \"#f59e0b\";\n        g.appendChild(btn);\n        return g;\n       }\n       if (shape === \"smart-lock\") {\n        const g = document.createElementNS(ns, \"g\");\n        const shackle = document.createElementNS(ns, \"path\");\n        shackle.setAttribute(\"d\", `M ${-size * 0.4} ${-size * 0.1} L ${-size * 0.4} ${-size * 0.6} A ${size * 0.4} ${size * 0.4} 0 1 1 ${size * 0.4} ${-size * 0.6} L ${size * 0.4} ${-size * 0.1}`);\n        shackle.style.fill = \"none\";\n        shackle.style.strokeWidth = size * 0.2;\n        g.appendChild(shackle);\n        const lockBody = document.createElementNS(ns, \"rect\");\n        lockBody.setAttribute(\"x\", -size * 0.6);\n        lockBody.setAttribute(\"y\", -size * 0.15);\n        lockBody.setAttribute(\"width\", size * 1.2);\n        lockBody.setAttribute(\"height\", size * 1);\n        lockBody.setAttribute(\"rx\", 4);\n        g.appendChild(lockBody);\n        const keyhole = document.createElementNS(ns, \"circle\");\n        keyhole.setAttribute(\"cx\", 0);\n        keyhole.setAttribute(\"cy\", size * 0.3);\n        keyhole.setAttribute(\"r\", size * 0.15);\n        keyhole.style.fill = \"#4ade80\";\n        g.appendChild(keyhole);\n        return g;\n       }\n       if (shape === \"smart-bulb\") {\n        const g = document.createElementNS(ns, \"g\");\n        const bulb = document.createElementNS(ns, \"path\");\n        bulb.setAttribute(\"d\", `M ${-size * 0.5} ${size * 0.2} Q ${-size * 0.8} ${-size * 0.3} ${-size * 0.5} ${-size * 0.7} Q 0 ${-size * 1.1} ${size * 0.5} ${-size * 0.7} Q ${size * 0.8} ${-size * 0.3} ${size * 0.5} ${size * 0.2} Z`);\n        g.appendChild(bulb);\n        const base1 = document.createElementNS(ns, \"rect\");\n        base1.setAttribute(\"x\", -size * 0.35);\n        base1.setAttribute(\"y\", size * 0.2);\n        base1.setAttribute(\"width\", size * 0.7);\n        base1.setAttribute(\"height\", size * 0.15);\n        base1.style.fill = \"#94a3b8\";\n        g.appendChild(base1);\n        const base2 = document.createElementNS(ns, \"rect\");\n        base2.setAttribute(\"x\", -size * 0.3);\n        base2.setAttribute(\"y\", size * 0.35);\n        base2.setAttribute(\"width\", size * 0.6);\n        base2.setAttribute(\"height\", size * 0.15);\n        base2.style.fill = \"#64748b\";\n        g.appendChild(base2);\n        const base3 = document.createElementNS(ns, \"rect\");\n        base3.setAttribute(\"x\", -size * 0.25);\n        base3.setAttribute(\"y\", size * 0.5);\n        base3.setAttribute(\"width\", size * 0.5);\n        base3.setAttribute(\"height\", size * 0.2);\n        base3.setAttribute(\"rx\", 2);\n        base3.style.fill = \"#475569\";\n        g.appendChild(base3);\n        return g;\n       }\n       if (shape === \"smart-plug\") {\n        const g = document.createElementNS(ns, \"g\");\n        const plugBody = document.createElementNS(ns, \"rect\");\n        plugBody.setAttribute(\"x\", -size * 0.7);\n        plugBody.setAttribute(\"y\", -size * 0.6);\n        plugBody.setAttribute(\"width\", size * 1.4);\n        plugBody.setAttribute(\"height\", size * 1.2);\n        plugBody.setAttribute(\"rx\", 6);\n        g.appendChild(plugBody);\n        const hole1 = document.createElementNS(ns, \"rect\");\n        hole1.setAttribute(\"x\", -size * 0.35);\n        hole1.setAttribute(\"y\", -size * 0.3);\n        hole1.setAttribute(\"width\", size * 0.15);\n        hole1.setAttribute(\"height\", size * 0.4);\n        hole1.setAttribute(\"rx\", 2);\n        hole1.style.fill = \"#1e293b\";\n        g.appendChild(hole1);\n        const hole2 = document.createElementNS(ns, \"rect\");\n        hole2.setAttribute(\"x\", size * 0.2);\n        hole2.setAttribute(\"y\", -size * 0.3);\n        hole2.setAttribute(\"width\", size * 0.15);\n        hole2.setAttribute(\"height\", size * 0.4);\n        hole2.setAttribute(\"rx\", 2);\n        hole2.style.fill = \"#1e293b\";\n        g.appendChild(hole2);\n        const led = document.createElementNS(ns, \"circle\");\n        led.setAttribute(\"cx\", 0);\n        led.setAttribute(\"cy\", size * 0.35);\n        led.setAttribute(\"r\", size * 0.1);\n        led.style.fill = \"#4ade80\";\n        g.appendChild(led);\n        return g;\n       }\n       if (shape === \"smart-speaker\") {\n        const g = document.createElementNS(ns, \"g\");\n        const speakerBody = document.createElementNS(ns, \"rect\");\n        speakerBody.setAttribute(\"x\", -size * 0.6);\n        speakerBody.setAttribute(\"y\", -size);\n        speakerBody.setAttribute(\"width\", size * 1.2);\n        speakerBody.setAttribute(\"height\", size * 2);\n        speakerBody.setAttribute(\"rx\", size * 0.3);\n        g.appendChild(speakerBody);\n        const mesh = document.createElementNS(ns, \"rect\");\n        mesh.setAttribute(\"x\", -size * 0.5);\n        mesh.setAttribute(\"y\", -size * 0.3);\n        mesh.setAttribute(\"width\", size);\n        mesh.setAttribute(\"height\", size * 1.1);\n        mesh.setAttribute(\"rx\", 4);\n        mesh.style.fill = \"#1e293b\";\n        g.appendChild(mesh);\n        const ring = document.createElementNS(ns, \"circle\");\n        ring.setAttribute(\"cx\", 0);\n        ring.setAttribute(\"cy\", -size * 0.65);\n        ring.setAttribute(\"r\", size * 0.2);\n        ring.style.fill = \"#3b82f6\";\n        g.appendChild(ring);\n        return g;\n       }\n       if (shape === \"smart-tv\") {\n        const g = document.createElementNS(ns, \"g\");\n        const frame = document.createElementNS(ns, \"rect\");\n        frame.setAttribute(\"x\", -size * 1.4);\n        frame.setAttribute(\"y\", -size * 0.85);\n        frame.setAttribute(\"width\", size * 2.8);\n        frame.setAttribute(\"height\", size * 1.6);\n        frame.setAttribute(\"rx\", 4);\n        g.appendChild(frame);\n        const screen = document.createElementNS(ns, \"rect\");\n        screen.setAttribute(\"x\", -size * 1.3);\n        screen.setAttribute(\"y\", -size * 0.75);\n        screen.setAttribute(\"width\", size * 2.6);\n        screen.setAttribute(\"height\", size * 1.4);\n        screen.setAttribute(\"rx\", 2);\n        screen.style.fill = \"#1e293b\";\n        g.appendChild(screen);\n        const stand = document.createElementNS(ns, \"rect\");\n        stand.setAttribute(\"x\", -size * 0.8);\n        stand.setAttribute(\"y\", size * 0.75);\n        stand.setAttribute(\"width\", size * 1.6);\n        stand.setAttribute(\"height\", size * 0.12);\n        stand.setAttribute(\"rx\", 2);\n        g.appendChild(stand);\n        return g;\n       }\n       if (shape === \"hub\") {\n        const g = document.createElementNS(ns, \"g\");\n        const hubBody = document.createElementNS(ns, \"rect\");\n        hubBody.setAttribute(\"x\", -size * 0.9);\n        hubBody.setAttribute(\"y\", -size * 0.5);\n        hubBody.setAttribute(\"width\", size * 1.8);\n        hubBody.setAttribute(\"height\", size);\n        hubBody.setAttribute(\"rx\", 8);\n        g.appendChild(hubBody);\n        for (let i = 0; i < 4; i++) {\n         const led = document.createElementNS(ns, \"circle\");\n         led.setAttribute(\"cx\", -size * 0.5 + i * size * 0.35);\n         led.setAttribute(\"cy\", 0);\n         led.setAttribute(\"r\", size * 0.08);\n         led.style.fill = [\"#4ade80\", \"#3b82f6\", \"#f59e0b\", \"#ef4444\"][i];\n         g.appendChild(led);\n        }\n        return g;\n       }\n       if (shape === \"smoke-detector\") {\n        const g = document.createElementNS(ns, \"g\");\n        const outer = document.createElementNS(ns, \"circle\");\n        outer.setAttribute(\"r\", size);\n        g.appendChild(outer);\n        const inner = document.createElementNS(ns, \"circle\");\n        inner.setAttribute(\"r\", size * 0.6);\n        inner.style.fill = \"#1e293b\";\n        g.appendChild(inner);\n        for (let i = 0; i < 6; i++) {\n         const slot = document.createElementNS(ns, \"rect\");\n         const angle = (i * 60 - 90) * Math.PI / 180;\n         slot.setAttribute(\"x\", Math.cos(angle) * size * 0.35 - size * 0.08);\n         slot.setAttribute(\"y\", Math.sin(angle) * size * 0.35 - size * 0.03);\n         slot.setAttribute(\"width\", size * 0.16);\n         slot.setAttribute(\"height\", size * 0.06);\n         slot.setAttribute(\"rx\", 1);\n         slot.style.fill = \"#475569\";\n         slot.setAttribute(\"transform\", `rotate(${i * 60}, ${Math.cos(angle) * size * 0.35}, ${Math.sin(angle) * size * 0.35})`);\n         g.appendChild(slot);\n        }\n        const led = document.createElementNS(ns, \"circle\");\n        led.setAttribute(\"cx\", 0);\n        led.setAttribute(\"cy\", 0);\n        led.setAttribute(\"r\", size * 0.1);\n        led.style.fill = \"#ef4444\";\n        g.appendChild(led);\n        return g;\n       }\n       if (shape === \"motion-sensor\") {\n        const g = document.createElementNS(ns, \"g\");\n        const dome = document.createElementNS(ns, \"path\");\n        dome.setAttribute(\"d\", `M ${-size * 0.8} ${size * 0.3} Q ${-size * 0.8} ${-size * 0.8} 0 ${-size * 0.8} Q ${size * 0.8} ${-size * 0.8} ${size * 0.8} ${size * 0.3} Z`);\n        g.appendChild(dome);\n        const base = document.createElementNS(ns, \"rect\");\n        base.setAttribute(\"x\", -size * 0.8);\n        base.setAttribute(\"y\", size * 0.3);\n        base.setAttribute(\"width\", size * 1.6);\n        base.setAttribute(\"height\", size * 0.35);\n        base.setAttribute(\"rx\", 3);\n        g.appendChild(base);\n        const lens = document.createElementNS(ns, \"ellipse\");\n        lens.setAttribute(\"cx\", 0);\n        lens.setAttribute(\"cy\", -size * 0.15);\n        lens.setAttribute(\"rx\", size * 0.35);\n        lens.setAttribute(\"ry\", size * 0.25);\n        lens.style.fill = \"#1e293b\";\n        g.appendChild(lens);\n        const led = document.createElementNS(ns, \"circle\");\n        led.setAttribute(\"cx\", 0);\n        led.setAttribute(\"cy\", size * 0.45);\n        led.setAttribute(\"r\", size * 0.08);\n        led.style.fill = \"#4ade80\";\n        g.appendChild(led);\n        return g;\n       }\n       if (shape === \"garage\") {\n        const g = document.createElementNS(ns, \"g\");\n        const frame = document.createElementNS(ns, \"rect\");\n        frame.setAttribute(\"x\", -size);\n        frame.setAttribute(\"y\", -size * 0.9);\n        frame.setAttribute(\"width\", size * 2);\n        frame.setAttribute(\"height\", size * 1.8);\n        frame.setAttribute(\"rx\", 4);\n        g.appendChild(frame);\n        for (let i = 0; i < 4; i++) {\n         const panel = document.createElementNS(ns, \"rect\");\n         panel.setAttribute(\"x\", -size * 0.9);\n         panel.setAttribute(\"y\", -size * 0.8 + i * size * 0.42);\n         panel.setAttribute(\"width\", size * 1.8);\n         panel.setAttribute(\"height\", size * 0.35);\n         panel.setAttribute(\"rx\", 2);\n         panel.style.fill = \"#1e293b\";\n         g.appendChild(panel);\n        }\n        return g;\n       }\n       if (shape === \"sprinkler\") {\n        const g = document.createElementNS(ns, \"g\");\n        const head = document.createElementNS(ns, \"circle\");\n        head.setAttribute(\"r\", size * 0.5);\n        g.appendChild(head);\n        const nozzle = document.createElementNS(ns, \"rect\");\n        nozzle.setAttribute(\"x\", -size * 0.15);\n        nozzle.setAttribute(\"y\", size * 0.3);\n        nozzle.setAttribute(\"width\", size * 0.3);\n        nozzle.setAttribute(\"height\", size * 0.5);\n        g.appendChild(nozzle);\n        for (let i = 0; i < 5; i++) {\n         const spray = document.createElementNS(ns, \"line\");\n         const angle = (-60 + i * 30) * Math.PI / 180;\n         spray.setAttribute(\"x1\", 0);\n         spray.setAttribute(\"y1\", -size * 0.3);\n         spray.setAttribute(\"x2\", Math.cos(angle) * size * 0.8);\n         spray.setAttribute(\"y2\", Math.sin(angle) * size * 0.8 - size * 0.3);\n         spray.style.stroke = \"#3b82f6\";\n         spray.style.strokeWidth = \"2\";\n         spray.style.strokeDasharray = \"3,3\";\n         g.appendChild(spray);\n        }\n        return g;\n       }\n       if (shape === \"vacuum\") {\n        const g = document.createElementNS(ns, \"g\");\n        const vacBody = document.createElementNS(ns, \"circle\");\n        vacBody.setAttribute(\"r\", size);\n        g.appendChild(vacBody);\n        const top = document.createElementNS(ns, \"circle\");\n        top.setAttribute(\"r\", size * 0.7);\n        top.style.fill = \"#1e293b\";\n        g.appendChild(top);\n        const bumper = document.createElementNS(ns, \"path\");\n        bumper.setAttribute(\"d\", `M ${-size * 0.7} ${-size * 0.4} A ${size * 0.8} ${size * 0.8} 0 0 1 ${size * 0.7} ${-size * 0.4}`);\n        bumper.style.fill = \"none\";\n        bumper.style.strokeWidth = size * 0.15;\n        g.appendChild(bumper);\n        const btn = document.createElementNS(ns, \"circle\");\n        btn.setAttribute(\"cx\", 0);\n        btn.setAttribute(\"cy\", size * 0.1);\n        btn.setAttribute(\"r\", size * 0.2);\n        btn.style.fill = \"#4ade80\";\n        g.appendChild(btn);\n        return g;\n       }\n       if (shape === \"basketball-ball\") {\n        const g = document.createElementNS(ns, \"g\");\n        const ball = document.createElementNS(ns, \"circle\");\n        ball.setAttribute(\"r\", size);\n        g.appendChild(ball);\n        const hLine = document.createElementNS(ns, \"line\");\n        hLine.setAttribute(\"x1\", -size);\n        hLine.setAttribute(\"y1\", 0);\n        hLine.setAttribute(\"x2\", size);\n        hLine.setAttribute(\"y2\", 0);\n        hLine.style.stroke = \"currentColor\";\n        hLine.style.strokeWidth = \"2\";\n        hLine.style.opacity = \"0.5\";\n        g.appendChild(hLine);\n        const vLine = document.createElementNS(ns, \"line\");\n        vLine.setAttribute(\"x1\", 0);\n        vLine.setAttribute(\"y1\", -size);\n        vLine.setAttribute(\"x2\", 0);\n        vLine.setAttribute(\"y2\", size);\n        vLine.style.stroke = \"currentColor\";\n        vLine.style.strokeWidth = \"2\";\n        vLine.style.opacity = \"0.5\";\n        g.appendChild(vLine);\n        const curve1 = document.createElementNS(ns, \"path\");\n        curve1.setAttribute(\"d\", `M ${-size * 0.5} ${-size} Q ${-size * 0.8} 0 ${-size * 0.5} ${size}`);\n        curve1.style.fill = \"none\";\n        curve1.style.stroke = \"currentColor\";\n        curve1.style.strokeWidth = \"2\";\n        curve1.style.opacity = \"0.5\";\n        g.appendChild(curve1);\n        const curve2 = document.createElementNS(ns, \"path\");\n        curve2.setAttribute(\"d\", `M ${size * 0.5} ${-size} Q ${size * 0.8} 0 ${size * 0.5} ${size}`);\n        curve2.style.fill = \"none\";\n        curve2.style.stroke = \"currentColor\";\n        curve2.style.strokeWidth = \"2\";\n        curve2.style.opacity = \"0.5\";\n        g.appendChild(curve2);\n        return g;\n       }\n       if (shape === \"football-ball\") {\n        const g = document.createElementNS(ns, \"g\");\n        const ball = document.createElementNS(ns, \"ellipse\");\n        ball.setAttribute(\"rx\", size * 1.3);\n        ball.setAttribute(\"ry\", size * 0.7);\n        g.appendChild(ball);\n        const laces = document.createElementNS(ns, \"line\");\n        laces.setAttribute(\"x1\", -size * 0.4);\n        laces.setAttribute(\"y1\", 0);\n        laces.setAttribute(\"x2\", size * 0.4);\n        laces.setAttribute(\"y2\", 0);\n        laces.style.stroke = \"currentColor\";\n        laces.style.strokeWidth = \"3\";\n        laces.style.opacity = \"0.5\";\n        g.appendChild(laces);\n        for (let i = -2; i <= 2; i++) {\n         const stitch = document.createElementNS(ns, \"line\");\n         stitch.setAttribute(\"x1\", i * size * 0.15);\n         stitch.setAttribute(\"y1\", -size * 0.15);\n         stitch.setAttribute(\"x2\", i * size * 0.15);\n         stitch.setAttribute(\"y2\", size * 0.15);\n         stitch.style.stroke = \"currentColor\";\n         stitch.style.strokeWidth = \"2\";\n         stitch.style.opacity = \"0.5\";\n         g.appendChild(stitch);\n        }\n        return g;\n       }\n       if (shape === \"soccer-ball\") {\n        const g = document.createElementNS(ns, \"g\");\n        const ball = document.createElementNS(ns, \"circle\");\n        ball.setAttribute(\"r\", size);\n        g.appendChild(ball);\n        const pentagon = document.createElementNS(ns, \"polygon\");\n        const pts = [];\n        for (let i = 0; i < 5; i++) {\n         const a = (Math.PI * 2 / 5) * i - Math.PI / 2;\n         pts.push([Math.cos(a) * size * 0.4, Math.sin(a) * size * 0.4]);\n        }\n        pentagon.setAttribute(\"points\", pts.map(pt => pt.join(\",\")).join(\" \"));\n        pentagon.style.fill = \"currentColor\";\n        pentagon.style.opacity = \"0.5\";\n        g.appendChild(pentagon);\n        return g;\n       }\n       if (shape === \"hockey-puck\") {\n        const g = document.createElementNS(ns, \"g\");\n        const side = document.createElementNS(ns, \"ellipse\");\n        side.setAttribute(\"rx\", size);\n        side.setAttribute(\"ry\", size * 0.3);\n        side.setAttribute(\"cy\", size * 0.15);\n        side.style.opacity = \"0.7\";\n        g.appendChild(side);\n        const top = document.createElementNS(ns, \"ellipse\");\n        top.setAttribute(\"rx\", size);\n        top.setAttribute(\"ry\", size * 0.3);\n        top.setAttribute(\"cy\", -size * 0.15);\n        g.appendChild(top);\n        return g;\n       }\n       if (shape === \"baseball-ball\") {\n        const g = document.createElementNS(ns, \"g\");\n        const ball = document.createElementNS(ns, \"circle\");\n        ball.setAttribute(\"r\", size);\n        g.appendChild(ball);\n        const stitch1 = document.createElementNS(ns, \"path\");\n        stitch1.setAttribute(\"d\", `M ${-size * 0.7} ${-size * 0.5} Q ${-size * 0.3} ${-size * 0.8} 0 ${-size * 0.9} Q ${size * 0.3} ${-size * 0.8} ${size * 0.7} ${-size * 0.5}`);\n        stitch1.style.fill = \"none\";\n        stitch1.style.stroke = \"currentColor\";\n        stitch1.style.strokeWidth = \"2\";\n        stitch1.style.opacity = \"0.5\";\n        g.appendChild(stitch1);\n        const stitch2 = document.createElementNS(ns, \"path\");\n        stitch2.setAttribute(\"d\", `M ${-size * 0.7} ${size * 0.5} Q ${-size * 0.3} ${size * 0.8} 0 ${size * 0.9} Q ${size * 0.3} ${size * 0.8} ${size * 0.7} ${size * 0.5}`);\n        stitch2.style.fill = \"none\";\n        stitch2.style.stroke = \"currentColor\";\n        stitch2.style.strokeWidth = \"2\";\n        stitch2.style.opacity = \"0.5\";\n        g.appendChild(stitch2);\n        return g;\n       }\n       if (shape === \"tennis-ball\") {\n        const g = document.createElementNS(ns, \"g\");\n        const ball = document.createElementNS(ns, \"circle\");\n        ball.setAttribute(\"r\", size);\n        g.appendChild(ball);\n        const curve1 = document.createElementNS(ns, \"path\");\n        curve1.setAttribute(\"d\", `M ${-size * 0.2} ${-size} Q ${-size * 0.8} 0 ${-size * 0.2} ${size}`);\n        curve1.style.fill = \"none\";\n        curve1.style.stroke = \"currentColor\";\n        curve1.style.strokeWidth = \"3\";\n        curve1.style.opacity = \"0.5\";\n        g.appendChild(curve1);\n        const curve2 = document.createElementNS(ns, \"path\");\n        curve2.setAttribute(\"d\", `M ${size * 0.2} ${-size} Q ${size * 0.8} 0 ${size * 0.2} ${size}`);\n        curve2.style.fill = \"none\";\n        curve2.style.stroke = \"currentColor\";\n        curve2.style.strokeWidth = \"3\";\n        curve2.style.opacity = \"0.5\";\n        g.appendChild(curve2);\n        return g;\n       }\n       if (shape === \"volleyball\") {\n        const g = document.createElementNS(ns, \"g\");\n        const ball = document.createElementNS(ns, \"circle\");\n        ball.setAttribute(\"r\", size);\n        g.appendChild(ball);\n        for (let i = 0; i < 3; i++) {\n         const curve = document.createElementNS(ns, \"path\");\n         const angle = (Math.PI * 2 / 3) * i;\n         const x1 = Math.cos(angle) * size;\n         const y1 = Math.sin(angle) * size;\n         const x2 = Math.cos(angle + Math.PI) * size;\n         const y2 = Math.sin(angle + Math.PI) * size;\n         curve.setAttribute(\"d\", `M ${x1} ${y1} Q 0 0 ${x2} ${y2}`);\n         curve.style.fill = \"none\";\n         curve.style.stroke = \"currentColor\";\n         curve.style.strokeWidth = \"2\";\n         curve.style.opacity = \"0.5\";\n         g.appendChild(curve);\n        }\n        return g;\n       }\n       if (shape === \"rugby-ball\") {\n        const g = document.createElementNS(ns, \"g\");\n        const ball = document.createElementNS(ns, \"ellipse\");\n        ball.setAttribute(\"rx\", size * 1.4);\n        ball.setAttribute(\"ry\", size * 0.6);\n        g.appendChild(ball);\n        const stripe = document.createElementNS(ns, \"line\");\n        stripe.setAttribute(\"x1\", 0);\n        stripe.setAttribute(\"y1\", -size * 0.6);\n        stripe.setAttribute(\"x2\", 0);\n        stripe.setAttribute(\"y2\", size * 0.6);\n        stripe.style.stroke = \"currentColor\";\n        stripe.style.strokeWidth = \"3\";\n        stripe.style.opacity = \"0.5\";\n        g.appendChild(stripe);\n        return g;\n       }\n       if (shape === \"golf-ball\") {\n        const g = document.createElementNS(ns, \"g\");\n        const ball = document.createElementNS(ns, \"circle\");\n        ball.setAttribute(\"r\", size);\n        g.appendChild(ball);\n        for (let row = -2; row <= 2; row++) {\n         for (let col = -2; col <= 2; col++) {\n          if (Math.abs(row) + Math.abs(col) <= 2) {\n           const dimple = document.createElementNS(ns, \"circle\");\n           dimple.setAttribute(\"cx\", col * size * 0.3);\n           dimple.setAttribute(\"cy\", row * size * 0.3);\n           dimple.setAttribute(\"r\", size * 0.08);\n           dimple.style.fill = \"currentColor\";\n           dimple.style.opacity = \"0.3\";\n           g.appendChild(dimple);\n          }\n         }\n        }\n        return g;\n       }\n       if (shape === \"frisbee\") {\n        const g = document.createElementNS(ns, \"g\");\n        const outer = document.createElementNS(ns, \"ellipse\");\n        outer.setAttribute(\"rx\", size * 1.2);\n        outer.setAttribute(\"ry\", size * 0.4);\n        g.appendChild(outer);\n        const inner = document.createElementNS(ns, \"ellipse\");\n        inner.setAttribute(\"rx\", size * 0.7);\n        inner.setAttribute(\"ry\", size * 0.25);\n        inner.style.fill = \"currentColor\";\n        inner.style.opacity = \"0.5\";\n        g.appendChild(inner);\n        return g;\n       }\n       if (shape === \"cricket-ball\") {\n        const g = document.createElementNS(ns, \"g\");\n        const ball = document.createElementNS(ns, \"circle\");\n        ball.setAttribute(\"r\", size);\n        g.appendChild(ball);\n        const seam = document.createElementNS(ns, \"ellipse\");\n        seam.setAttribute(\"rx\", size * 0.9);\n        seam.setAttribute(\"ry\", size * 0.15);\n        seam.style.fill = \"none\";\n        seam.style.stroke = \"currentColor\";\n        seam.style.strokeWidth = \"3\";\n        seam.style.opacity = \"0.5\";\n        g.appendChild(seam);\n        return g;\n       }\n       if (shape === \"lacrosse-stick\") {\n        const g = document.createElementNS(ns, \"g\");\n        const handle = document.createElementNS(ns, \"rect\");\n        handle.setAttribute(\"x\", -size * 0.1);\n        handle.setAttribute(\"y\", -size * 0.2);\n        handle.setAttribute(\"width\", size * 0.2);\n        handle.setAttribute(\"height\", size * 1.5);\n        handle.setAttribute(\"rx\", 2);\n        g.appendChild(handle);\n        const head = document.createElementNS(ns, \"ellipse\");\n        head.setAttribute(\"cy\", -size * 0.6);\n        head.setAttribute(\"rx\", size * 0.5);\n        head.setAttribute(\"ry\", size * 0.7);\n        head.style.fill = \"none\";\n        head.style.stroke = \"currentColor\";\n        head.style.strokeWidth = size * 0.15;\n        head.style.opacity = \"0.7\";\n        g.appendChild(head);\n        const net = document.createElementNS(ns, \"ellipse\");\n        net.setAttribute(\"cy\", -size * 0.5);\n        net.setAttribute(\"rx\", size * 0.35);\n        net.setAttribute(\"ry\", size * 0.5);\n        net.style.fill = \"currentColor\";\n        net.style.opacity = \"0.3\";\n        g.appendChild(net);\n        return g;\n       }\n       if (shape === \"golf-flag\") {\n        const g = document.createElementNS(ns, \"g\");\n        const hole = document.createElementNS(ns, \"ellipse\");\n        hole.setAttribute(\"cy\", size * 0.7);\n        hole.setAttribute(\"rx\", size * 0.5);\n        hole.setAttribute(\"ry\", size * 0.15);\n        hole.style.opacity = \"0.5\";\n        g.appendChild(hole);\n        const pole = document.createElementNS(ns, \"line\");\n        pole.setAttribute(\"x1\", 0);\n        pole.setAttribute(\"y1\", size * 0.7);\n        pole.setAttribute(\"x2\", 0);\n        pole.setAttribute(\"y2\", -size);\n        pole.style.stroke = \"currentColor\";\n        pole.style.strokeWidth = \"3\";\n        pole.style.opacity = \"0.6\";\n        g.appendChild(pole);\n        const flag = document.createElementNS(ns, \"polygon\");\n        flag.setAttribute(\"points\", `0,${-size} ${size * 0.8},${-size * 0.7} 0,${-size * 0.4}`);\n        g.appendChild(flag);\n        return g;\n       }\n       if (shape === \"tactical-x\") {\n        const g = document.createElementNS(ns, \"g\");\n        const line1 = document.createElementNS(ns, \"line\");\n        line1.setAttribute(\"x1\", -size * 0.7);\n        line1.setAttribute(\"y1\", -size * 0.7);\n        line1.setAttribute(\"x2\", size * 0.7);\n        line1.setAttribute(\"y2\", size * 0.7);\n        line1.style.stroke = \"currentColor\";\n        line1.style.strokeWidth = size * 0.3;\n        line1.style.strokeLinecap = \"round\";\n        g.appendChild(line1);\n        const line2 = document.createElementNS(ns, \"line\");\n        line2.setAttribute(\"x1\", size * 0.7);\n        line2.setAttribute(\"y1\", -size * 0.7);\n        line2.setAttribute(\"x2\", -size * 0.7);\n        line2.setAttribute(\"y2\", size * 0.7);\n        line2.style.stroke = \"currentColor\";\n        line2.style.strokeWidth = size * 0.3;\n        line2.style.strokeLinecap = \"round\";\n        g.appendChild(line2);\n        return g;\n       }\n       if (shape === \"tactical-o\") {\n        const g = document.createElementNS(ns, \"g\");\n        const circle = document.createElementNS(ns, \"circle\");\n        circle.setAttribute(\"r\", size * 0.7);\n        circle.style.fill = \"none\";\n        circle.style.stroke = \"currentColor\";\n        circle.style.strokeWidth = size * 0.3;\n        g.appendChild(circle);\n        return g;\n       }\n       if (shape === \"tactical-star\") {\n        const g = document.createElementNS(ns, \"g\");\n        const outer = size * 0.9;\n        const inner = size * 0.4;\n        let pts = \"\";\n        for (let i = 0; i < 10; i++) {\n         const a = (Math.PI / 5) * i - Math.PI / 2;\n         const r = i % 2 === 0 ? outer : inner;\n         pts += `${Math.cos(a) * r},${Math.sin(a) * r} `;\n        }\n        const star = document.createElementNS(ns, \"polygon\");\n        star.setAttribute(\"points\", pts.trim());\n        g.appendChild(star);\n        return g;\n       }\n       if (shape === \"patch-panel\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 1.4);\n        body.setAttribute(\"y\", -size * 0.35);\n        body.setAttribute(\"width\", size * 2.8);\n        body.setAttribute(\"height\", size * 0.7);\n        body.setAttribute(\"rx\", 2);\n        g.appendChild(body);\n        for (let i = 0; i < 12; i++) {\n         const port = document.createElementNS(ns, \"rect\");\n         port.setAttribute(\"x\", -size * 1.25 + i * size * 0.22);\n         port.setAttribute(\"y\", -size * 0.15);\n         port.setAttribute(\"width\", size * 0.18);\n         port.setAttribute(\"height\", size * 0.3);\n         port.setAttribute(\"rx\", 1);\n         port.style.fill = \"currentColor\";\n         port.style.opacity = \"0.4\";\n         g.appendChild(port);\n        }\n        return g;\n       }\n       if (shape === \"ups\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size);\n        body.setAttribute(\"y\", -size);\n        body.setAttribute(\"width\", size * 2);\n        body.setAttribute(\"height\", size * 2);\n        body.setAttribute(\"rx\", 4);\n        g.appendChild(body);\n        const screen = document.createElementNS(ns, \"rect\");\n        screen.setAttribute(\"x\", -size * 0.6);\n        screen.setAttribute(\"y\", -size * 0.7);\n        screen.setAttribute(\"width\", size * 1.2);\n        screen.setAttribute(\"height\", size * 0.5);\n        screen.setAttribute(\"rx\", 2);\n        screen.style.fill = \"currentColor\";\n        screen.style.opacity = \"0.4\";\n        g.appendChild(screen);\n        const battery = document.createElementNS(ns, \"rect\");\n        battery.setAttribute(\"x\", -size * 0.4);\n        battery.setAttribute(\"y\", -size * 0.55);\n        battery.setAttribute(\"width\", size * 0.8);\n        battery.setAttribute(\"height\", size * 0.2);\n        battery.style.fill = \"#4ade80\";\n        g.appendChild(battery);\n        for (let i = 0; i < 3; i++) {\n         const led = document.createElementNS(ns, \"circle\");\n         led.setAttribute(\"cx\", -size * 0.4 + i * size * 0.4);\n         led.setAttribute(\"cy\", size * 0.2);\n         led.setAttribute(\"r\", size * 0.08);\n         led.style.fill = i < 2 ? \"#4ade80\" : \"#facc15\";\n         g.appendChild(led);\n        }\n        const outlet = document.createElementNS(ns, \"rect\");\n        outlet.setAttribute(\"x\", -size * 0.7);\n        outlet.setAttribute(\"y\", size * 0.5);\n        outlet.setAttribute(\"width\", size * 1.4);\n        outlet.setAttribute(\"height\", size * 0.3);\n        outlet.setAttribute(\"rx\", 2);\n        outlet.style.fill = \"currentColor\";\n        outlet.style.opacity = \"0.4\";\n        g.appendChild(outlet);\n        return g;\n       }\n       if (shape === \"pdu\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 0.4);\n        body.setAttribute(\"y\", -size * 1.2);\n        body.setAttribute(\"width\", size * 0.8);\n        body.setAttribute(\"height\", size * 2.4);\n        body.setAttribute(\"rx\", 3);\n        g.appendChild(body);\n        for (let i = 0; i < 6; i++) {\n         const outlet = document.createElementNS(ns, \"rect\");\n         outlet.setAttribute(\"x\", -size * 0.25);\n         outlet.setAttribute(\"y\", -size + i * size * 0.35);\n         outlet.setAttribute(\"width\", size * 0.5);\n         outlet.setAttribute(\"height\", size * 0.25);\n         outlet.setAttribute(\"rx\", 2);\n         outlet.style.fill = \"currentColor\";\n         outlet.style.opacity = \"0.4\";\n         g.appendChild(outlet);\n        }\n        const led = document.createElementNS(ns, \"circle\");\n        led.setAttribute(\"cy\", -size * 1.05);\n        led.setAttribute(\"r\", size * 0.08);\n        led.style.fill = \"#4ade80\";\n        g.appendChild(led);\n        return g;\n       }\n       if (shape === \"rack-shelf\") {\n        const g = document.createElementNS(ns, \"g\");\n        const shelf = document.createElementNS(ns, \"rect\");\n        shelf.setAttribute(\"x\", -size * 1.3);\n        shelf.setAttribute(\"y\", -size * 0.15);\n        shelf.setAttribute(\"width\", size * 2.6);\n        shelf.setAttribute(\"height\", size * 0.3);\n        shelf.setAttribute(\"rx\", 2);\n        g.appendChild(shelf);\n        const lip = document.createElementNS(ns, \"rect\");\n        lip.setAttribute(\"x\", -size * 1.3);\n        lip.setAttribute(\"y\", size * 0.15);\n        lip.setAttribute(\"width\", size * 2.6);\n        lip.setAttribute(\"height\", size * 0.15);\n        lip.style.opacity = \"0.7\";\n        g.appendChild(lip);\n        const ear1 = document.createElementNS(ns, \"rect\");\n        ear1.setAttribute(\"x\", -size * 1.4);\n        ear1.setAttribute(\"y\", -size * 0.2);\n        ear1.setAttribute(\"width\", size * 0.1);\n        ear1.setAttribute(\"height\", size * 0.4);\n        g.appendChild(ear1);\n        const ear2 = document.createElementNS(ns, \"rect\");\n        ear2.setAttribute(\"x\", size * 1.3);\n        ear2.setAttribute(\"y\", -size * 0.2);\n        ear2.setAttribute(\"width\", size * 0.1);\n        ear2.setAttribute(\"height\", size * 0.4);\n        g.appendChild(ear2);\n        return g;\n       }\n       if (shape === \"blank-panel\") {\n        const g = document.createElementNS(ns, \"g\");\n        const panel = document.createElementNS(ns, \"rect\");\n        panel.setAttribute(\"x\", -size * 1.4);\n        panel.setAttribute(\"y\", -size * 0.25);\n        panel.setAttribute(\"width\", size * 2.8);\n        panel.setAttribute(\"height\", size * 0.5);\n        panel.setAttribute(\"rx\", 2);\n        g.appendChild(panel);\n        const vent1 = document.createElementNS(ns, \"rect\");\n        vent1.setAttribute(\"x\", -size * 0.8);\n        vent1.setAttribute(\"y\", -size * 0.1);\n        vent1.setAttribute(\"width\", size * 0.5);\n        vent1.setAttribute(\"height\", size * 0.04);\n        vent1.style.fill = \"currentColor\";\n        vent1.style.opacity = \"0.4\";\n        g.appendChild(vent1);\n        const vent2 = document.createElementNS(ns, \"rect\");\n        vent2.setAttribute(\"x\", -size * 0.8);\n        vent2.setAttribute(\"y\", size * 0.02);\n        vent2.setAttribute(\"width\", size * 0.5);\n        vent2.setAttribute(\"height\", size * 0.04);\n        vent2.style.fill = \"currentColor\";\n        vent2.style.opacity = \"0.4\";\n        g.appendChild(vent2);\n        const vent3 = document.createElementNS(ns, \"rect\");\n        vent3.setAttribute(\"x\", size * 0.3);\n        vent3.setAttribute(\"y\", -size * 0.1);\n        vent3.setAttribute(\"width\", size * 0.5);\n        vent3.setAttribute(\"height\", size * 0.04);\n        vent3.style.fill = \"currentColor\";\n        vent3.style.opacity = \"0.4\";\n        g.appendChild(vent3);\n        const vent4 = document.createElementNS(ns, \"rect\");\n        vent4.setAttribute(\"x\", size * 0.3);\n        vent4.setAttribute(\"y\", size * 0.02);\n        vent4.setAttribute(\"width\", size * 0.5);\n        vent4.setAttribute(\"height\", size * 0.04);\n        vent4.style.fill = \"currentColor\";\n        vent4.style.opacity = \"0.4\";\n        g.appendChild(vent4);\n        return g;\n       }\n       if (shape === \"cable-management\") {\n        const g = document.createElementNS(ns, \"g\");\n        const panel = document.createElementNS(ns, \"rect\");\n        panel.setAttribute(\"x\", -size * 1.4);\n        panel.setAttribute(\"y\", -size * 0.3);\n        panel.setAttribute(\"width\", size * 2.8);\n        panel.setAttribute(\"height\", size * 0.6);\n        panel.setAttribute(\"rx\", 2);\n        g.appendChild(panel);\n        for (let i = 0; i < 5; i++) {\n         const ring = document.createElementNS(ns, \"ellipse\");\n         ring.setAttribute(\"cx\", -size + i * size * 0.5);\n         ring.setAttribute(\"rx\", size * 0.12);\n         ring.setAttribute(\"ry\", size * 0.18);\n         ring.style.fill = \"none\";\n         ring.style.stroke = \"currentColor\";\n         ring.style.strokeWidth = size * 0.08;\n         ring.style.opacity = \"0.5\";\n         g.appendChild(ring);\n        }\n        return g;\n       }\n       if (shape === \"kvm\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 1.2);\n        body.setAttribute(\"y\", -size * 0.4);\n        body.setAttribute(\"width\", size * 2.4);\n        body.setAttribute(\"height\", size * 0.8);\n        body.setAttribute(\"rx\", 3);\n        g.appendChild(body);\n        const screen = document.createElementNS(ns, \"rect\");\n        screen.setAttribute(\"x\", -size * 0.9);\n        screen.setAttribute(\"y\", -size * 0.25);\n        screen.setAttribute(\"width\", size * 0.8);\n        screen.setAttribute(\"height\", size * 0.5);\n        screen.setAttribute(\"rx\", 2);\n        screen.style.fill = \"currentColor\";\n        screen.style.opacity = \"0.4\";\n        g.appendChild(screen);\n        for (let i = 0; i < 4; i++) {\n         const btn = document.createElementNS(ns, \"rect\");\n         btn.setAttribute(\"x\", size * 0.1 + i * size * 0.25);\n         btn.setAttribute(\"y\", -size * 0.15);\n         btn.setAttribute(\"width\", size * 0.18);\n         btn.setAttribute(\"height\", size * 0.3);\n         btn.setAttribute(\"rx\", 2);\n         btn.style.fill = \"currentColor\";\n         btn.style.opacity = \"0.5\";\n         g.appendChild(btn);\n         const led = document.createElementNS(ns, \"circle\");\n         led.setAttribute(\"cx\", size * 0.19 + i * size * 0.25);\n         led.setAttribute(\"cy\", -size * 0.25);\n         led.setAttribute(\"r\", size * 0.04);\n         led.style.fill = i === 0 ? \"#4ade80\" : \"currentColor\";\n         led.style.opacity = i === 0 ? \"1\" : \"0.3\";\n         g.appendChild(led);\n        }\n        return g;\n       }\n       const c = document.createElementNS(ns, \"circle\");\n       c.setAttribute(\"r\", size);\n       return c;\n      }\n      function createNodeShape(id, size) {\n       const styles = resolveStylesForNode(id);\n       if (styles.icon && styles.icon.library && styles.icon.name) {\n        const g = document.createElementNS(\"http://www.w3.org/2000/svg\", \"g\");\n        g.classList.add(\"node-circle\");\n        IconLibrary.getIcon(styles.icon.library, styles.icon.name).then(svgText => {\n         if (svgText) {\n          const parser = new DOMParser();\n          const doc = parser.parseFromString(svgText, 'image/svg+xml');\n          const svgEl = doc.querySelector('svg');\n          if (svgEl) {\n           svgEl.setAttribute('width', size * 2);\n           svgEl.setAttribute('height', size * 2);\n           svgEl.setAttribute('x', -size);\n           svgEl.setAttribute('y', -size);\n           if (styles.circleColor) {\n            svgEl.style.fill = styles.circleColor;\n            svgEl.querySelectorAll('path, circle, rect, polygon, ellipse').forEach(el => {\n             el.style.fill = styles.circleColor;\n            });\n           }\n           if (styles.circleBorder) {\n            svgEl.style.stroke = styles.circleBorder;\n            svgEl.querySelectorAll('path, circle, rect, polygon, ellipse').forEach(el => {\n             el.style.stroke = styles.circleBorder;\n            });\n           }\n           g.innerHTML = svgEl.outerHTML;\n          }\n         }\n        });\n        return g;\n       }\n       const shapeType = (NODE_DATA[id] && NODE_DATA[id].shape) || \"circle\";\n       const shapeEl = createShapeElement(shapeType, size);\n       shapeEl.classList.add(\"node-circle\");\n       shapeEl.style.fill = styles.circleColor || PAGE_STATE.nodeFill || \"#1e293b\";\n       shapeEl.style.stroke = styles.circleBorder || PAGE_STATE.nodeStroke || \"#475569\";\n       return shapeEl;\n      }\n      function forgeTheLegend() {\n       const container = document.getElementById(\"edge-legend\");\n       if (!container) return;\n       container.innerHTML = \"\";\n       const title = document.createElement(\"div\");\n       title.className = \"legend-title\";\n       title.textContent = t(\"legends.lineLegend\");\n       container.appendChild(title);\n       const closeBtn = document.createElement(\"button\");\n       closeBtn.type = \"button\";\n       closeBtn.className = \"legend-close-btn\";\n       closeBtn.textContent = \"✕\";\n       closeBtn.addEventListener(\"click\", (e) => {\n        e.stopPropagation();\n        legendCollapsed = true;\n        updateLegendVisibility();\n       });\n       container.appendChild(closeBtn);\n       const colors = [...new Set(EDGE_DATA.list.map((e) => e.color).filter(Boolean))];\n       if (colors.length === 0) {\n        updateLegendVisibility();\n        return;\n       }\n       colors.forEach((color) => {\n         if (!EDGE_LEGEND[color]) {\n          EDGE_LEGEND[color] = t(\"legends.defaultLineLabel\");\n         }\n         const item = document.createElement(\"div\");\n         item.className = \"legend-item\";\n         item.addEventListener(\"mousedown\", (e) => e.stopPropagation());\n         item.addEventListener(\"click\", (e) => e.stopPropagation());\n         const swatch = document.createElement(\"span\");\n         swatch.className = \"legend-swatch\";\n         swatch.style.backgroundColor = color;\n         swatch.style.cursor = \"pointer\";\n         swatch.addEventListener(\"click\", (e) => {\n          e.stopPropagation();\n          const edgeWithColor = EDGE_DATA.list.find(edge => edge.color === color);\n          if (edgeWithColor) {\n           selectTheConnection(edgeWithColor.id);\n          }\n         });\n         let swatchTouchStart = null;\n         let swatchTouchMoved = false;\n         swatch.addEventListener(\"touchstart\", (e) => {\n         swatchTouchStart = Date.now();\n         swatchTouchMoved = false;\n         if (e.touches[0]) {\n          swatchTouchStartX = e.touches[0].clientX;\n          swatchTouchStartY = e.touches[0].clientY;\n         }\n        }, {\n          passive: false\n         });\n         let swatchTouchStartX = 0, swatchTouchStartY = 0;\n        swatch.addEventListener(\"touchmove\", (e) => {\n         if (e.touches[0]) {\n          const dx = Math.abs(e.touches[0].clientX - swatchTouchStartX);\n          const dy = Math.abs(e.touches[0].clientY - swatchTouchStartY);\n          if (dx > 10 || dy > 10) swatchTouchMoved = true;\n         }\n        }, {\n          passive: false\n         });\n         swatch.addEventListener(\"touchend\", (e) => {\n          if (swatchTouchStart && !swatchTouchMoved && Date.now() - swatchTouchStart < 400) {\n           e.stopPropagation();\n           e.preventDefault();\n           const edgeWithColor = EDGE_DATA.list.find(edge => edge.color === color);\n           if (edgeWithColor) {\n            selectTheConnection(edgeWithColor.id);\n           }\n          }\n          swatchTouchStart = null;\n          swatchTouchMoved = false;\n         }, {\n          passive: false\n         });\n        const label = document.createElement(\"span\");\n        label.className = \"legend-label\";\n        label.textContent = EDGE_LEGEND[color];\n        if (isMobileDevice()) {\n         label.style.cursor = \"pointer\";\n         let labelTapStart = null;\n         let labelTapMoved = false;\n         label.addEventListener(\"touchstart\", (e) => {\n          labelTapStart = Date.now();\n          labelTapMoved = false;\n          if (e.touches[0]) {\n           labelTapStartX = e.touches[0].clientX;\n           labelTapStartY = e.touches[0].clientY;\n          }\n          e.stopPropagation();\n         });\n         let labelTapStartX = 0, labelTapStartY = 0;\n         label.addEventListener(\"touchmove\", (e) => {\n          if (e.touches[0]) {\n           const dx = Math.abs(e.touches[0].clientX - labelTapStartX);\n           const dy = Math.abs(e.touches[0].clientY - labelTapStartY);\n           if (dx > 10 || dy > 10) labelTapMoved = true;\n          }\n         });\n         label.addEventListener(\"touchend\", async (e) => {\n          if (labelTapStart && !labelTapMoved && Date.now() - labelTapStart < 400) {\n           e.preventDefault();\n           e.stopPropagation();\n           const currentText = label.textContent;\n           const newText = await showPrompt(t(\"dialogs.editLegendLabel\"), currentText);\n           if (newText !== null && newText.trim()) {\n            label.textContent = newText.trim();\n            EDGE_LEGEND[color] = newText.trim();\n           }\n          }\n          labelTapStart = null;\n          labelTapMoved = false;\n         });\n        } else {\n           label.contentEditable = true;\n           label.addEventListener(\"focus\", () => {\n            label.classList.add(\"editing\");\n           });\n           label.addEventListener(\"blur\", () => {\n            label.classList.remove(\"editing\");\n            const text = label.textContent.trim() || t(\"legends.defaultLineLabel\");\n            EDGE_LEGEND[color] = text;\n           });\n           label.addEventListener(\"keydown\", (e) => {\n            if (e.key === \"Enter\") {\n             e.preventDefault();\n             label.blur();\n            }\n           });\n          }\n          item.append(swatch, label); container.appendChild(item);\n         }); updateLegendVisibility();\n       }\n       function deleteRectangle(rectId) {\n      pushUndo(\"delete zone\");\n        RECT_DATA.list = RECT_DATA.list.filter(r => r.id !== rectId);\n        forgeTheTopology();\n       }\n       function updateRectangleDeleteButtons() {\n        const deleteButtons = document.querySelectorAll('.rect-delete-btn');\n        deleteButtons.forEach(btn => {\n         btn.style.display = rectDrawMode ? 'block' : 'none';\n        });\n       }\n       function updateFovCone(nodeId) {\n        const node = NODE_DATA[nodeId];\n        if (!node) return;\n        \n        const nodeGroup = document.querySelector(`g[data-node-id=\"${nodeId}\"]`);\n        if (!nodeGroup) return;\n        const existingFov = nodeGroup.querySelector(\".fov-group\");\n        if (existingFov) existingFov.remove();\n        if (!hasCoverageZone(node.shape) || !node.fovEnabled) return;\n        \n        const ns = \"http://www.w3.org/2000/svg\";\n        const defaults = getCoverageDefaults(node.shape);\n        const fovAngle = node.fovAngle || defaults.angle;\n        const fovDistance = node.fovDistance || defaults.distance;\n        const fovInnerRadius = node.fovInnerRadius || 0;\n        const fovRotation = node.fovRotation || 0;\n        const fovColor = node.fovColor || \"#f59e0b\";\n        const fovOpacity = node.fovOpacity || 20;\n        const fovGradient = node.fovGradient || false;\n        const fovBorderColor = node.fovBorderColor || \"#f59e0b\";\n        const fovBorderWidth = node.fovBorderWidth ?? 2;\n        const fovBorderStyle = node.fovBorderStyle || \"solid\";\n        const fovBorderOpacity = node.fovBorderOpacity ?? 100;\n        const fovLabel = node.fovLabel || \"\";\n        const fovAnimate = node.fovAnimate || false;\n        const fovAnimationType = node.fovAnimationType || defaults.animationType;\n        const fovSweep = node.fovSweep || 120;\n        const fovSpeed = node.fovSpeed || 4;\n        \n        const fovGroup = document.createElementNS(ns, \"g\");\n        fovGroup.classList.add(\"fov-group\");\n        if (!ZONES_VISIBLE) fovGroup.style.display = \"none\";\n        \n        if (fovGradient) {\n          const gradientId = `fov-gradient-${nodeId}`;\n          const defs = document.createElementNS(ns, \"defs\");\n          const gradient = document.createElementNS(ns, \"radialGradient\");\n          gradient.id = gradientId;\n          gradient.setAttribute(\"cx\", \"0\");\n          gradient.setAttribute(\"cy\", \"0\");\n          gradient.setAttribute(\"r\", fovDistance);\n          gradient.setAttribute(\"gradientUnits\", \"userSpaceOnUse\");\n          const stop1 = document.createElementNS(ns, \"stop\");\n          stop1.setAttribute(\"offset\", fovInnerRadius / fovDistance);\n          stop1.setAttribute(\"stop-color\", fovColor);\n          stop1.setAttribute(\"stop-opacity\", fovOpacity / 100);\n          const stop2 = document.createElementNS(ns, \"stop\");\n          stop2.setAttribute(\"offset\", \"1\");\n          stop2.setAttribute(\"stop-color\", fovColor);\n          stop2.setAttribute(\"stop-opacity\", \"0\");\n          gradient.appendChild(stop1);\n          gradient.appendChild(stop2);\n          defs.appendChild(gradient);\n          fovGroup.appendChild(defs);\n        }\n        \n        const fovPath = document.createElementNS(ns, \"path\");\n        \n        if (fovAngle >= 360) {\n          if (fovInnerRadius > 0) {\n            fovPath.setAttribute(\"d\", `M ${fovDistance},0 A ${fovDistance},${fovDistance} 0 1,1 ${-fovDistance},0 A ${fovDistance},${fovDistance} 0 1,1 ${fovDistance},0 M ${fovInnerRadius},0 A ${fovInnerRadius},${fovInnerRadius} 0 1,0 ${-fovInnerRadius},0 A ${fovInnerRadius},${fovInnerRadius} 0 1,0 ${fovInnerRadius},0`);\n            fovPath.setAttribute(\"fill-rule\", \"evenodd\");\n          } else {\n            fovPath.setAttribute(\"d\", `M 0,0 m -${fovDistance},0 a ${fovDistance},${fovDistance} 0 1,0 ${fovDistance * 2},0 a ${fovDistance},${fovDistance} 0 1,0 -${fovDistance * 2},0`);\n          }\n        } else {\n          const angleRad = (fovAngle * Math.PI) / 180;\n          const rotationRad = ((fovRotation - 90) * Math.PI) / 180;\n          const startAngle = rotationRad - angleRad / 2;\n          const endAngle = rotationRad + angleRad / 2;\n          const x1 = Math.cos(startAngle) * fovDistance;\n          const y1 = Math.sin(startAngle) * fovDistance;\n          const x2 = Math.cos(endAngle) * fovDistance;\n          const y2 = Math.sin(endAngle) * fovDistance;\n          const largeArc = fovAngle > 180 ? 1 : 0;\n          if (fovInnerRadius > 0) {\n            const ix1 = Math.cos(startAngle) * fovInnerRadius;\n            const iy1 = Math.sin(startAngle) * fovInnerRadius;\n            const ix2 = Math.cos(endAngle) * fovInnerRadius;\n            const iy2 = Math.sin(endAngle) * fovInnerRadius;\n            fovPath.setAttribute(\"d\", `M ${ix1},${iy1} L ${x1},${y1} A ${fovDistance},${fovDistance} 0 ${largeArc},1 ${x2},${y2} L ${ix2},${iy2} A ${fovInnerRadius},${fovInnerRadius} 0 ${largeArc},0 ${ix1},${iy1} Z`);\n          } else {\n            fovPath.setAttribute(\"d\", `M 0,0 L ${x1},${y1} A ${fovDistance},${fovDistance} 0 ${largeArc},1 ${x2},${y2} Z`);\n          }\n        }\n        \n        if (fovGradient) {\n          fovPath.style.fill = `url(#fov-gradient-${nodeId})`;\n        } else {\n          const opacityHex = Math.round((fovOpacity / 100) * 255).toString(16).padStart(2, '0');\n          fovPath.style.fill = fovColor + opacityHex;\n        }\n        \n        const borderOpacityHex = Math.round((fovBorderOpacity / 100) * 255).toString(16).padStart(2, '0');\n        fovPath.style.stroke = fovBorderColor + borderOpacityHex;\n        fovPath.style.strokeWidth = fovBorderWidth;\n        if (fovBorderStyle === \"dashed\") {\n          fovPath.style.strokeDasharray = \"10,5\";\n        } else if (fovBorderStyle === \"dotted\") {\n          fovPath.style.strokeDasharray = \"3,3\";\n        }\n        fovPath.style.pointerEvents = \"none\";\n        fovPath.classList.add(\"fov-cone\");\n        \n        fovGroup.appendChild(fovPath);\n        \n        if (fovLabel) {\n          const fovLabelPosition = node.fovLabelPosition || \"center\";\n          const fovLabelSize = node.fovLabelSize || 14;\n          const fovLabelColor = node.fovLabelColor || \"#ffffff\";\n          const fovLabelBold = node.fovLabelBold || false;\n          const fovLabelBg = node.fovLabelBg || false;\n          const fovLabelBgColor = node.fovLabelBgColor || \"#000000\";\n          let labelDistance;\n          if (fovLabelPosition === \"center\") {\n            labelDistance = fovAngle >= 360 ? 0 : fovDistance * 0.4;\n          } else if (fovLabelPosition === \"edge\") {\n            labelDistance = fovDistance * 0.75;\n          } else {\n            labelDistance = fovDistance + fovLabelSize + 8;\n          }\n          const labelAngle = ((fovRotation - 90) * Math.PI) / 180;\n          const fovLabelOffsetX = node.fovLabelOffsetX || 0;\n          const fovLabelOffsetY = node.fovLabelOffsetY || 0;\n          const labelX = (fovAngle >= 360 && fovLabelPosition === \"center\" ? 0 : Math.cos(labelAngle) * labelDistance) + fovLabelOffsetX;\n          const labelY = (fovAngle >= 360 && fovLabelPosition === \"center\" ? 0 : Math.sin(labelAngle) * labelDistance) + fovLabelOffsetY;\n          if (fovLabelBg) {\n            const bgRect = document.createElementNS(ns, \"rect\");\n            const textWidth = fovLabel.length * fovLabelSize * 0.6;\n            const textHeight = fovLabelSize * 1.4;\n            bgRect.setAttribute(\"x\", labelX - textWidth / 2 - 6);\n            bgRect.setAttribute(\"y\", labelY - textHeight / 2);\n            bgRect.setAttribute(\"width\", textWidth + 12);\n            bgRect.setAttribute(\"height\", textHeight);\n            bgRect.setAttribute(\"rx\", \"4\");\n            bgRect.style.fill = fovLabelBgColor;\n            bgRect.style.opacity = \"0.8\";\n            bgRect.style.pointerEvents = \"none\";\n            fovGroup.appendChild(bgRect);\n          }\n          const labelEl = document.createElementNS(ns, \"text\");\n          labelEl.setAttribute(\"x\", labelX);\n          labelEl.setAttribute(\"y\", labelY);\n          labelEl.setAttribute(\"text-anchor\", \"middle\");\n          labelEl.setAttribute(\"dominant-baseline\", \"middle\");\n          labelEl.style.fill = fovLabelColor;\n          labelEl.style.fontSize = fovLabelSize + \"px\";\n          labelEl.style.fontWeight = fovLabelBold ? \"bold\" : \"normal\";\n          labelEl.style.fontFamily = \"system-ui, sans-serif\";\n          labelEl.style.pointerEvents = \"none\";\n          labelEl.textContent = fovLabel;\n          fovGroup.appendChild(labelEl);\n        }\n        \n        if (fovAnimate) {\n          const animationName = `fov-anim-${nodeId}`;\n          const styleEl = document.createElementNS(\"http://www.w3.org/1999/xhtml\", \"style\");\n          \n          if (fovAnimationType === \"sweep\" && fovAngle < 360) {\n            styleEl.textContent = `\n              @keyframes ${animationName} {\n                0%, 100% { transform: rotate(0deg); }\n                50% { transform: rotate(${fovSweep}deg); }\n              }\n            `;\n            fovGroup.style.animation = `${animationName} ${fovSpeed}s ease-in-out infinite`;\n            fovGroup.style.transformOrigin = \"0 0\";\n          } else if (fovAnimationType === \"pulse\") {\n            styleEl.textContent = `\n              @keyframes ${animationName} {\n                0%, 100% { transform: scale(1); opacity: 1; }\n                50% { transform: scale(1.1); opacity: 0.7; }\n              }\n            `;\n            fovGroup.style.animation = `${animationName} ${fovSpeed}s ease-in-out infinite`;\n            fovGroup.style.transformOrigin = \"0 0\";\n          } else if (fovAnimationType === \"rings\") {\n            for (let i = 1; i <= 3; i++) {\n              const ring = document.createElementNS(ns, \"circle\");\n              ring.setAttribute(\"cx\", \"0\");\n              ring.setAttribute(\"cy\", \"0\");\n              ring.setAttribute(\"r\", fovDistance * 0.3 * i);\n              ring.style.fill = \"none\";\n              ring.style.stroke = fovBorderColor;\n              ring.style.strokeWidth = \"2\";\n              ring.style.opacity = \"0\";\n              const ringAnimName = `${animationName}-ring-${i}`;\n              const ringStyle = document.createElementNS(\"http://www.w3.org/1999/xhtml\", \"style\");\n              ringStyle.textContent = `\n                @keyframes ${ringAnimName} {\n                  0% { r: ${fovInnerRadius || 10}; opacity: 0.8; }\n                  100% { r: ${fovDistance}; opacity: 0; }\n                }\n              `;\n              ring.style.animation = `${ringAnimName} ${fovSpeed}s ease-out infinite`;\n              ring.style.animationDelay = `${(i - 1) * (fovSpeed / 3)}s`;\n              fovGroup.appendChild(ringStyle);\n              fovGroup.appendChild(ring);\n            }\n          } else if (fovAnimationType === \"spin\") {\n            styleEl.textContent = `\n              @keyframes ${animationName} {\n                0% { transform: rotate(0deg); }\n                100% { transform: rotate(360deg); }\n              }\n            `;\n            fovGroup.style.animation = `${animationName} ${fovSpeed}s linear infinite`;\n            fovGroup.style.transformOrigin = \"0 0\";\n          }\n          \n          if (fovAnimationType !== \"rings\") {\n            fovGroup.appendChild(styleEl);\n            const elapsedSeconds = (Date.now() - FOV_ANIMATION_START) / 1000;\n            const animationOffset = elapsedSeconds % fovSpeed;\n            fovGroup.style.animationDelay = `-${animationOffset}s`;\n          }\n        }\n        \n        nodeGroup.insertBefore(fovGroup, nodeGroup.firstChild);\n      }\n       \n      function forgeTheTopology() {\n        if (!NODE_DATA || !EDGE_DATA) {\n         console.warn(\"forgeTheTopology called before data initialized\");\n         return;\n        }\n        const svg = document.getElementById(\"map\");\n        svg.innerHTML = \"\";\n        const ns = \"http://www.w3.org/2000/svg\";\n        const defs = document.createElementNS(ns, \"defs\");\n        const flowArrowBig = document.createElementNS(ns, \"path\");\n        flowArrowBig.id = \"flow-arrow-big\";\n        flowArrowBig.setAttribute(\"d\", \"M-6,-4 L6,0 L-6,4 L-3,0 Z\");\n        defs.appendChild(flowArrowBig);\n        const flowArrowSmall = document.createElementNS(ns, \"path\");\n        flowArrowSmall.id = \"flow-arrow-small\";\n        flowArrowSmall.setAttribute(\"d\", \"M-4,-3 L4,0 L-4,3 Z\");\n        defs.appendChild(flowArrowSmall);\n        const markerForward = document.createElementNS(ns, \"marker\");\n        markerForward.id = \"arrow-forward\";\n        markerForward.setAttribute(\"markerWidth\", \"10\");\n        markerForward.setAttribute(\"markerHeight\", \"10\");\n        markerForward.setAttribute(\"refX\", \"9\");\n        markerForward.setAttribute(\"refY\", \"3\");\n        markerForward.setAttribute(\"orient\", \"auto\");\n        markerForward.setAttribute(\"markerUnits\", \"strokeWidth\");\n        const pathForward = document.createElementNS(ns, \"path\");\n        pathForward.setAttribute(\"d\", \"M0,0 L0,6 L9,3 z\");\n        pathForward.setAttribute(\"fill\", \"context-stroke\");\n        markerForward.appendChild(pathForward);\n        defs.appendChild(markerForward);\n        const markerBackward = document.createElementNS(ns, \"marker\");\n        markerBackward.id = \"arrow-backward\";\n        markerBackward.setAttribute(\"markerWidth\", \"10\");\n        markerBackward.setAttribute(\"markerHeight\", \"10\");\n        markerBackward.setAttribute(\"refX\", \"0\");\n        markerBackward.setAttribute(\"refY\", \"3\");\n        markerBackward.setAttribute(\"orient\", \"auto\");\n        markerBackward.setAttribute(\"markerUnits\", \"strokeWidth\");\n        const pathBackward = document.createElementNS(ns, \"path\");\n        pathBackward.setAttribute(\"d\", \"M9,0 L9,6 L0,3 z\");\n        pathBackward.setAttribute(\"fill\", \"context-stroke\");\n        markerBackward.appendChild(pathBackward);\n        defs.appendChild(markerBackward);\n\n        const wallPattern = document.createElementNS(ns, \"pattern\");\n        wallPattern.id = \"wall-hatch\";\n        wallPattern.setAttribute(\"patternUnits\", \"userSpaceOnUse\");\n        wallPattern.setAttribute(\"width\", \"8\");\n        wallPattern.setAttribute(\"height\", \"8\");\n        wallPattern.setAttribute(\"patternTransform\", \"rotate(45)\");\n        const wallLine = document.createElementNS(ns, \"line\");\n        wallLine.setAttribute(\"x1\", \"0\");\n        wallLine.setAttribute(\"y1\", \"0\");\n        wallLine.setAttribute(\"x2\", \"0\");\n        wallLine.setAttribute(\"y2\", \"8\");\n        wallLine.setAttribute(\"stroke\", \"#666\");\n        wallLine.setAttribute(\"stroke-width\", \"2\");\n        wallPattern.appendChild(wallLine);\n        defs.appendChild(wallPattern);\n\n        svg.appendChild(defs);\n        const boundary = document.createElementNS(ns, \"rect\");\n        boundary.setAttribute(\"x\", CANVAS_PADDING);\n        boundary.setAttribute(\"y\", CANVAS_PADDING);\n        boundary.setAttribute(\"width\", CANVAS_WIDTH - CANVAS_PADDING * 2);\n        boundary.setAttribute(\"height\", CANVAS_HEIGHT - CANVAS_PADDING * 2);\n        boundary.setAttribute(\"fill\", \"none\");\n        boundary.setAttribute(\"stroke\", (PAGE_STATE.canvasBorder || \"#475569\") + \"4D\");\n        boundary.setAttribute(\"stroke-width\", \"20\");\n        boundary.setAttribute(\"stroke-dasharray\", \"10 5\");\n        boundary.setAttribute(\"rx\", \"8\");\n        svg.appendChild(boundary);\n\t\tconst currentMappingModeForCanvas = PAGE_STATE.mappingMode || 'network';\n\t\tconst shouldDrawCanvasTemplate = currentView.mode !== \"rack\" || currentMappingModeForCanvas !== 'network';\n\t\tif (shouldDrawCanvasTemplate) {\n\t\t const template = PAGE_STATE.canvasTemplate || 'grid';\n\t\t if (template !== 'none' && template !== 'grid' && typeof renderCanvasTemplate === 'function') {\n\t\t  const templateGroup = renderCanvasTemplate(svg, ns, template, CANVAS_WIDTH, CANVAS_HEIGHT, CANVAS_PADDING, PAGE_STATE.canvasGrid);\n\t\t  if (templateGroup) svg.appendChild(templateGroup);\n\t\t } else if (PAGE_STATE.canvasGridEnabled !== false && template !== 'none') {\n\t\t  const gridGroup = document.createElementNS(ns, \"g\");\n\t\t  gridGroup.id = \"canvas-grid\";\n\t\t  const gridSize = PAGE_STATE.canvasGridSize || 50;\n\t\t  const gridColor = (PAGE_STATE.canvasGrid || \"#475569\") + \"33\";\n\t\t  const majorGridColor = (PAGE_STATE.canvasGrid || \"#475569\") + \"66\";\n\t\t  for (let x = CANVAS_PADDING; x <= CANVAS_WIDTH - CANVAS_PADDING; x += gridSize) {\n\t\t   const line = document.createElementNS(ns, \"line\");\n\t\t   line.setAttribute(\"x1\", x);\n\t\t   line.setAttribute(\"y1\", CANVAS_PADDING);\n\t\t   line.setAttribute(\"x2\", x);\n\t\t   line.setAttribute(\"y2\", CANVAS_HEIGHT - CANVAS_PADDING);\n\t\t   line.setAttribute(\"stroke\", (x - CANVAS_PADDING) % (gridSize * 5) === 0 ? majorGridColor : gridColor);\n\t\t   line.setAttribute(\"stroke-width\", (x - CANVAS_PADDING) % (gridSize * 5) === 0 ? \"2\" : \"1\");\n\t\t   gridGroup.appendChild(line);\n\t\t  }\n\t\t  for (let y = CANVAS_PADDING; y <= CANVAS_HEIGHT - CANVAS_PADDING; y += gridSize) {\n\t\t   const line = document.createElementNS(ns, \"line\");\n\t\t   line.setAttribute(\"x1\", CANVAS_PADDING);\n\t\t   line.setAttribute(\"y1\", y);\n\t\t   line.setAttribute(\"x2\", CANVAS_WIDTH - CANVAS_PADDING);\n\t\t   line.setAttribute(\"y2\", y);\n\t\t   line.setAttribute(\"stroke\", (y - CANVAS_PADDING) % (gridSize * 5) === 0 ? majorGridColor : gridColor);\n\t\t   line.setAttribute(\"stroke-width\", (y - CANVAS_PADDING) % (gridSize * 5) === 0 ? \"2\" : \"1\");\n\t\t   gridGroup.appendChild(line);\n\t\t  }\n\t\t  svg.appendChild(gridGroup);\n\t\t }\n\t\t}\n       if (currentView.mode === \"rack\" && currentView.rackId) {\n         const rackCapacity = getRackCapacity(currentView.rackId);\n      const rackUHeight = getRackUHeight(currentView.rackId);\n         const rackGroup = document.createElementNS(ns, \"g\");\n         rackGroup.id = \"rack-visualization\";\n         const showUSlotRack = (PAGE_STATE.mappingMode || 'network') === 'network';\n         if (showUSlotRack) {\n         const rackFrame = document.createElementNS(ns, \"rect\");\n         rackFrame.setAttribute(\"x\", RACK_START_X - RACK_WIDTH / 2);\n         rackFrame.setAttribute(\"y\", RACK_START_Y);\n         rackFrame.setAttribute(\"width\", RACK_WIDTH);\n         rackFrame.setAttribute(\"height\", rackCapacity * rackUHeight);\n         rackFrame.setAttribute(\"fill\", (PAGE_STATE.rackFrameFill || \"#0f172a\") + \"4D\");\n         rackFrame.setAttribute(\"stroke\", PAGE_STATE.rackFrameStroke || \"#4fd1c5\");\n         rackFrame.setAttribute(\"stroke-width\", \"3\");\n         rackFrame.setAttribute(\"rx\", \"4\");\n         rackGroup.appendChild(rackFrame);\n         if (PAGE_STATE.rackGridEnabled !== false) {\n          for (let u = 0; u <= rackCapacity; u++) {\n           const y = RACK_START_Y + u * rackUHeight;\n           const line = document.createElementNS(ns, \"line\");\n           line.setAttribute(\"x1\", RACK_START_X - RACK_WIDTH / 2);\n           line.setAttribute(\"y1\", y);\n           line.setAttribute(\"x2\", RACK_START_X + RACK_WIDTH / 2);\n           line.setAttribute(\"y2\", y);\n           line.setAttribute(\"stroke\", (PAGE_STATE.rackLineColor || \"#475569\") + \"66\");\n           line.setAttribute(\"stroke-width\", u % 5 === 0 ? \"2\" : \"1\");\n           line.setAttribute(\"stroke-dasharray\", u % 5 === 0 ? \"none\" : \"5,5\");\n           rackGroup.appendChild(line);\n           if (u < rackCapacity) {\n            const uNumber = rackCapacity - u;\n            const text = document.createElementNS(ns, \"text\");\n            text.setAttribute(\"x\", RACK_START_X - RACK_WIDTH / 2 - 30);\n            text.setAttribute(\"y\", y + rackUHeight / 2);\n            text.setAttribute(\"text-anchor\", \"middle\");\n            text.setAttribute(\"dominant-baseline\", \"middle\");\n            text.style.fill = PAGE_STATE.rackTextColor || \"#4fd1c5\";\n            text.style.fontSize = \"14px\";\n            text.style.fontWeight = \"bold\";\n            text.textContent = `U${uNumber}`;\n            rackGroup.appendChild(text);\n            const textRight = document.createElementNS(ns, \"text\");\n            textRight.setAttribute(\"x\", RACK_START_X + RACK_WIDTH / 2 + 30);\n            textRight.setAttribute(\"y\", y + rackUHeight / 2);\n            textRight.setAttribute(\"text-anchor\", \"middle\");\n            textRight.setAttribute(\"dominant-baseline\", \"middle\");\n            textRight.style.fill = PAGE_STATE.rackTextColor || \"#4fd1c5\";\n            textRight.style.fontSize = \"14px\";\n            textRight.style.fontWeight = \"bold\";\n            textRight.textContent = `U${uNumber}`;\n            rackGroup.appendChild(textRight);\n           }\n          }\n         }\n         }\n         svg.appendChild(rackGroup);\n        }\n        const centerX = CANVAS_WIDTH / 2;\n        if (RECT_DATA && RECT_DATA.list) {\n         RECT_DATA.list.forEach((rect) => {\n          if (currentView.mode === \"rack\") return;\n          if (rect.style === \"filled\" || rect.style === \"outlined\") {\n           const g = document.createElementNS(ns, \"g\");\n           g.classList.add(\"rect-group\");\n           g.dataset.rectId = rect.id;\n           const rectCenterX = rect.x + rect.width / 2;\n           const rectCenterY = rect.y + rect.height / 2;\n           const rectRotation = rect.rotation || 0;\n           if (rectRotation !== 0) {\n             g.setAttribute(\"transform\", `rotate(${rectRotation}, ${rectCenterX}, ${rectCenterY})`);\n           }\n           const rectEl = document.createElementNS(ns, \"rect\");\n           rectEl.classList.add(\"rect-shape\");\n           rectEl.setAttribute(\"x\", rect.x);\n           rectEl.setAttribute(\"y\", rect.y);\n           rectEl.setAttribute(\"width\", rect.width);\n           rectEl.setAttribute(\"height\", rect.height);\n           if (rect.style === \"filled\") {\n             rectEl.style.fill = rect.color;\n             rectEl.style.fillOpacity = rect.fillOpacity !== undefined ? rect.fillOpacity : 0.3;\n           } else {\n             rectEl.style.fill = \"none\";\n           }\n           rectEl.style.stroke = rect.borderColor || rect.color;\n           rectEl.style.strokeWidth = (rect.borderWidth !== undefined ? rect.borderWidth : 2) + \"px\";\n           if (rect.lineStyle === \"dashed\") { rectEl.style.strokeDasharray = \"10,5\"; }\n           else if (rect.lineStyle === \"dotted\") { rectEl.style.strokeDasharray = \"2,4\"; }\n           else if (rect.lineStyle === \"wall\") {\n             rectEl.style.fill = rect.color;\n             rectEl.style.fillOpacity = rect.fillOpacity !== undefined ? rect.fillOpacity : 0.5;\n             rectEl.style.stroke = rect.borderColor || rect.color;\n             rectEl.style.strokeWidth = (rect.borderWidth !== undefined ? rect.borderWidth : 2) + \"px\";\n             const hatchGroup = document.createElementNS(ns, \"g\");\n             hatchGroup.classList.add(\"wall-hatch-lines\");\n             hatchGroup.style.pointerEvents = \"none\";\n             const spacing = 12;\n             const hatchColor = rect.borderColor || rect.color;\n             for (let i = -rect.height; i < rect.width + rect.height; i += spacing) {\n               const line = document.createElementNS(ns, \"line\");\n               line.setAttribute(\"x1\", rect.x + i);\n               line.setAttribute(\"y1\", rect.y);\n               line.setAttribute(\"x2\", rect.x + i - rect.height);\n               line.setAttribute(\"y2\", rect.y + rect.height);\n               line.style.stroke = hatchColor;\n               line.style.strokeWidth = \"2\";\n               hatchGroup.appendChild(line);\n             }\n             const clipId = \"clip-\" + rect.id;\n             const clipPath = document.createElementNS(ns, \"clipPath\");\n             clipPath.id = clipId;\n             const clipRect = document.createElementNS(ns, \"rect\");\n             clipRect.setAttribute(\"x\", rect.x);\n             clipRect.setAttribute(\"y\", rect.y);\n             clipRect.setAttribute(\"width\", rect.width);\n             clipRect.setAttribute(\"height\", rect.height);\n             clipPath.appendChild(clipRect);\n             defs.appendChild(clipPath);\n             hatchGroup.setAttribute(\"clip-path\", \"url(#\" + clipId + \")\");\n             g.appendChild(hatchGroup);\n           }\n           else { rectEl.style.strokeDasharray = \"none\"; }\n           rectEl.style.cursor = \"move\";\n           rectEl.addEventListener(\"click\", (e) => {\n\t\t   if (isViewOnly()) return;\n             if (rectDrawMode) return;\n             e.stopPropagation();\n             currentRectId = rect.id;\n             selectTheRect(rect.id);\n           });\n           rectEl.addEventListener(\"contextmenu\", (e) => {\n\t\t    if (isViewOnly()) return;\n             e.preventDefault();\n             e.stopPropagation();\n             if (selectedRects.has(rect.id)) {\n               selectedRects.delete(rect.id);\n             } else {\n               selectedRects.add(rect.id);\n             }\n             updateAllSelections();\n           });\n           let rectLastTap = 0;\n           rectEl.addEventListener(\"touchend\", (e) => {\n             const now = Date.now();\n             if (now - rectLastTap < 300) {\n               e.preventDefault();\n               if (selectedRects.has(rect.id)) {\n                 selectedRects.delete(rect.id);\n               } else {\n                 selectedRects.add(rect.id);\n               }\n               updateAllSelections();\n               if (navigator.vibrate) navigator.vibrate(50);\n               rectLastTap = 0;\n             } else {\n               rectLastTap = now;\n             }\n           }, { passive: false });\n           const deleteBtn = document.createElementNS(ns, \"g\");\n           deleteBtn.classList.add(\"rect-delete-btn\");\n           deleteBtn.style.cursor = \"pointer\";\n           deleteBtn.style.display = rectDrawMode ? \"block\" : \"none\";\n           const deleteBg = document.createElementNS(ns, \"circle\");\n           deleteBg.setAttribute(\"cx\", rect.x + rect.width - 10);\n           deleteBg.setAttribute(\"cy\", rect.y + 10);\n           deleteBg.setAttribute(\"r\", 12);\n           deleteBg.style.fill = \"#f56565\";\n           deleteBg.style.stroke = \"white\";\n           deleteBg.style.strokeWidth = \"2\";\n           const deleteX = document.createElementNS(ns, \"text\");\n           deleteX.setAttribute(\"x\", rect.x + rect.width - 10);\n           deleteX.setAttribute(\"y\", rect.y + 10);\n           deleteX.setAttribute(\"text-anchor\", \"middle\");\n           deleteX.setAttribute(\"dominant-baseline\", \"middle\");\n           deleteX.style.fill = \"white\";\n           deleteX.style.fontSize = \"16px\";\n           deleteX.style.fontWeight = \"bold\";\n           deleteX.style.pointerEvents = \"none\";\n           deleteX.textContent = \"×\";\n           deleteBtn.appendChild(deleteBg);\n           deleteBtn.appendChild(deleteX);\n           deleteBtn.addEventListener(\"click\", (e) => {\n            e.stopPropagation();\n            e.preventDefault();\n            deleteRectangle(rect.id);\n           });\n           deleteBtn.addEventListener(\"touchend\", (e) => {\n      e.stopPropagation();\n           e.preventDefault();\n           deleteRectangle(rect.id);\n           });\n           let isDragging = false;\n           let dragStartX, dragStartY;\n           let rectStartX, rectStartY;\n           rectEl.addEventListener(\"mousedown\", (e) => {\n\t\t   if (isViewOnly()) return;\n      if (rectDrawMode) return;\n      e.preventDefault();\n      e.stopPropagation();\n      isDragging = true;\n      dragStartX = e.clientX;\n      dragStartY = e.clientY;\n      rectStartX = rect.x;\n      rectStartY = rect.y;\n      rectEl.style.cursor = \"grabbing\";\n      if (selectedRects.has(rect.id)) {\n      initialPositions = {};\n      selectedNodes.forEach(nodeId => {\n      const nodePos = savedPositions[nodeId];\n      if (nodePos) initialPositions[nodeId] = { x: nodePos.x, y: nodePos.y };\n      });\n      selectedRects.forEach(rectId => {\n      const r = RECT_DATA.list.find(x => x.id === rectId);\n      if (r) { r._dragStartX = r.x; r._dragStartY = r.y; }\n      });\n      selectedTexts.forEach(textId => {\n      const t = TEXT_DATA.list.find(x => x.id === textId);\n      if (t) { t._dragStartX = t.x; t._dragStartY = t.y; }\n      });\n      selectedEdges.forEach(edgeId => {\n      const ed = EDGE_DATA.list.find(x => x.id === edgeId);\n      if (ed && ed.points) { ed._dragStartPoints = ed.points.map(p => ({x: p.x, y: p.y})); }\n      });\n      }\n      });\n      const moveHandler = (e) => {\n      if (!isDragging || rectDrawMode) return;\n      const svgEl = svg;\n      const pt1 = svgEl.createSVGPoint();\n      pt1.x = dragStartX;\n      pt1.y = dragStartY;\n      const svgP1 = pt1.matrixTransform(svgEl.getScreenCTM().inverse());\n      const pt2 = svgEl.createSVGPoint();\n      pt2.x = e.clientX;\n      pt2.y = e.clientY;\n      const svgP2 = pt2.matrixTransform(svgEl.getScreenCTM().inverse());\n      const dx = svgP2.x - svgP1.x;\n      const dy = svgP2.y - svgP1.y;\n      rect.x = rectStartX + dx;\n      rect.y = rectStartY + dy;\n      if (selectedRects.has(rect.id)) {\n      selectedNodes.forEach(nodeId => { if (initialPositions[nodeId]) { savedPositions[nodeId] = { x: initialPositions[nodeId].x + dx, y: initialPositions[nodeId].y + dy }; } });\n      selectedRects.forEach(rectId => { if (rectId === rect.id) return; const r = RECT_DATA.list.find(x => x.id === rectId); if (r && r._dragStartX !== undefined) { r.x = r._dragStartX + dx; r.y = r._dragStartY + dy; } });\n      selectedTexts.forEach(textId => { const t = TEXT_DATA.list.find(x => x.id === textId); if (t && t._dragStartX !== undefined) { t.x = t._dragStartX + dx; t.y = t._dragStartY + dy; } });\n      selectedEdges.forEach(edgeId => { const ed = EDGE_DATA.list.find(x => x.id === edgeId); if (ed && ed._dragStartPoints) { ed.points.forEach((p, i) => { p.x = ed._dragStartPoints[i].x + dx; p.y = ed._dragStartPoints[i].y + dy; }); } });\n      }\n      forgeTheTopology();\n      };\n           const upHandler = () => {\n            if (isDragging) {\n             isDragging = false;\n             rectEl.style.cursor = \"move\";\n            }\n           };\n           document.addEventListener(\"mousemove\", moveHandler);\n           document.addEventListener(\"mouseup\", upHandler);\n           let touchStartX, touchStartY;\n           let touchRectStartX, touchRectStartY;\n           rectEl.addEventListener(\"touchstart\", (e) => {\n\t\t   if (isViewOnly()) return;\n            if (rectDrawMode) return;\n            e.preventDefault();\n            e.stopPropagation();\n            isDragging = true;\n            const touch = e.touches[0];\n            touchStartX = touch.clientX;\n            touchStartY = touch.clientY;\n            touchRectStartX = rect.x;\n            touchRectStartY = rect.y;\n            rectEl.style.cursor = \"grabbing\";\n           }, { passive: false });\n           rectEl.addEventListener(\"touchmove\", (e) => {\n\t\t   if (isViewOnly()) return;\n            if (!isDragging || rectDrawMode) return;\n            if (!e.touches[0]) return;\n            e.preventDefault();\n            e.stopPropagation();\n            const svgEl = svg;\n            const pt1 = svgEl.createSVGPoint();\n            pt1.x = touchStartX;\n            pt1.y = touchStartY;\n            const svgP1 = pt1.matrixTransform(svgEl.getScreenCTM().inverse());\n            const touch = e.touches[0];\n            const pt2 = svgEl.createSVGPoint();\n            pt2.x = touch.clientX;\n            pt2.y = touch.clientY;\n            const svgP2 = pt2.matrixTransform(svgEl.getScreenCTM().inverse());\n            const dx = svgP2.x - svgP1.x;\n            const dy = svgP2.y - svgP1.y;\n            rect.x = touchRectStartX + dx;\n            rect.y = touchRectStartY + dy;\n            forgeTheTopology();\n           }, { passive: false });\n           rectEl.addEventListener(\"touchend\", (e) => {\n            if (isDragging) {\n             e.preventDefault();\n             isDragging = false;\n             rectEl.style.cursor = \"move\";\n            }\n           }, { passive: false });\n            if (currentRectId === rect.id) {\n      const corners = [\n      { cx: rect.x, cy: rect.y, cursor: 'nwse-resize', dx: -1, dy: -1 },\n      { cx: rect.x + rect.width, cy: rect.y, cursor: 'nesw-resize', dx: 1, dy: -1 },\n      { cx: rect.x, cy: rect.y + rect.height, cursor: 'nesw-resize', dx: -1, dy: 1 },\n      { cx: rect.x + rect.width, cy: rect.y + rect.height, cursor: 'nwse-resize', dx: 1, dy: 1 }\n      ];\n      corners.forEach((corner, idx) => {\n      const handle = document.createElementNS(ns, \"circle\");\n      handle.setAttribute(\"cx\", corner.cx);\n      handle.setAttribute(\"cy\", corner.cy);\n      const borderW = rect.borderWidth !== undefined ? rect.borderWidth : 2;\n      const handleSize = Math.max(PAGE_STATE.selectionHandleSize || 8, borderW + 4);\n      handle.setAttribute(\"r\", handleSize);\n      handle.style.fill = PAGE_STATE.selectionHandle || \"#f59e0b\";\n      handle.style.stroke = \"#fff\";\n      handle.style.strokeWidth = \"2\";\n      handle.style.cursor = corner.cursor;\n      handle.addEventListener(\"mousedown\", (e) => {\n\t  if (isViewOnly()) return;\n      e.preventDefault();\n      e.stopPropagation();\n\t  pushUndo(\"resize zone\");\n      let dragging = true;\n      const startX = e.clientX, startY = e.clientY;\n      const origX = rect.x, origY = rect.y, origW = rect.width, origH = rect.height;\n      const moveHandler = (ev) => {\n        if (!dragging) return;\n        const pt1 = svg.createSVGPoint(); pt1.x = startX; pt1.y = startY;\n        const pt2 = svg.createSVGPoint(); pt2.x = ev.clientX; pt2.y = ev.clientY;\n        const svgP1 = pt1.matrixTransform(svg.getScreenCTM().inverse());\n        const svgP2 = pt2.matrixTransform(svg.getScreenCTM().inverse());\n        const dx = svgP2.x - svgP1.x, dy = svgP2.y - svgP1.y;\n        if (corner.dx < 0) { rect.x = origX + dx; rect.width = origW - dx; }\n        else { rect.width = origW + dx; }\n        if (corner.dy < 0) { rect.y = origY + dy; rect.height = origH - dy; }\n        else { rect.height = origH + dy; }\n        if (rect.width < 20) rect.width = 20;\n        if (rect.height < 20) rect.height = 20;\n        forgeTheTopology();\n      };\n      const upHandler = () => { dragging = false; document.removeEventListener(\"mousemove\", moveHandler); document.removeEventListener(\"mouseup\", upHandler); };\n      document.addEventListener(\"mousemove\", moveHandler);\n      document.addEventListener(\"mouseup\", upHandler);\n      });\n      g.appendChild(handle);\n      });\n      }\n            if (rect.groupId) {\n             const groupIndicator = document.createElementNS(ns, \"rect\");\n             groupIndicator.setAttribute(\"x\", rect.x - 4);\n             groupIndicator.setAttribute(\"y\", rect.y - 4);\n             groupIndicator.setAttribute(\"width\", rect.width + 8);\n             groupIndicator.setAttribute(\"height\", rect.height + 8);\n             groupIndicator.style.fill = \"none\";\n        groupIndicator.style.stroke = PAGE_STATE.groupIndicator || \"#4fd1c5\";\n             groupIndicator.style.strokeWidth = \"3\";\n             groupIndicator.style.strokeDasharray = \"5,5\";\n             groupIndicator.style.pointerEvents = \"none\";\n             g.insertBefore(groupIndicator, g.firstChild);\n           }\n           g.appendChild(rectEl);\n           g.appendChild(deleteBtn);\n           svg.appendChild(g);\n          }\n         });\n        }\n        const centerY = CANVAS_HEIGHT / 2;\n        let positions = {};\n        Object.keys(NODE_DATA).forEach((id) => {\n         if (currentView.mode === \"rack\") {\n          const node = NODE_DATA[id];\n          if (!node || node.assignedRack !== currentView.rackId) {\n           return;\n          }\n         }\n         positions[id] = savedPositions[id] || {\n          x: centerX,\n          y: centerY\n         };\n        });\n        if (Object.keys(savedPositions).length === 0) {\n         const nodeIds = Object.keys(NODE_DATA).filter(id => {\n          if (currentView.mode === \"rack\") {\n           const node = NODE_DATA[id];\n           return node && node.assignedRack === currentView.rackId;\n          }\n          return true;\n         });\n         const baseY = centerY - 300;\n         if (nodeIds.length > 0) {\n          positions[nodeIds[0]] = {\n           x: centerX,\n           y: baseY\n          };\n          const remaining = nodeIds.slice(1);\n          const radius = 350;\n          const startAngle = Math.PI * 0.3;\n          const endAngle = Math.PI * 0.7;\n          remaining.forEach((id, i) => {\n           const angle = startAngle + (endAngle - startAngle) * (i / Math.max(1, remaining.length - 1));\n           positions[id] = {\n            x: centerX + Math.cos(angle) * radius * (i % 2 === 0 ? 1 : 1.3),\n            y: baseY + 200 + Math.sin(angle) * radius * 0.8 + i * 80,\n           };\n          });\n         }\n        }\n        Object.keys(positions).forEach((id) => {\n         let pos = savedPositions[id] || positions[id];\n         const nodeSize = savedSizes[id] || 55;\n         pos.x = Math.max(nodeSize + CANVAS_PADDING, Math.min(CANVAS_WIDTH - nodeSize - CANVAS_PADDING, pos.x));\n         pos.y = Math.max(nodeSize + CANVAS_PADDING, Math.min(CANVAS_HEIGHT - nodeSize - CANVAS_PADDING, pos.y));\n         positions[id] = {\n          x: pos.x,\n          y: pos.y\n         };\n         savedPositions[id] = {\n          x: pos.x,\n          y: pos.y\n         };\n        });\n        const edgePairCount = {};\n        const edgePairIndex = {};\n        EDGE_DATA.list.forEach((edge) => {\n         if (edge.type === \"custom\") return;\n         const key = [edge.from, edge.to].sort().join(\"||\");\n         edgePairCount[key] = (edgePairCount[key] || 0) + 1;\n        });\n        EDGE_DATA.list.forEach((edge) => {\n         if (edge.type === \"custom\") return;\n         const key = [edge.from, edge.to].sort().join(\"||\");\n         if (!edgePairIndex[key]) edgePairIndex[key] = 0;\n         edge._pairIndex = edgePairIndex[key];\n         edge._pairTotal = edgePairCount[key];\n         edgePairIndex[key]++;\n       });\n       \n       const orthoGaps = (function() {\n         const segments = [];\n         const GAP_SIZE = 12;\n         EDGE_DATA.list.forEach((edge, edgeIndex) => {\n           if ((edge.routing || PAGE_STATE.defaultEdgeRouting || \"curved\") !== \"orthogonal\") return;\n           if (edge.type === \"custom\") return;\n           const p1 = positions[edge.from];\n           const p2 = positions[edge.to];\n           if (!p1 || !p2) return;\n           const waypoints = edge.waypoints || [];\n           const pairIndex = edge._pairIndex || 0;\n           const pairTotal = edge._pairTotal || 1;\n           const offset = (pairIndex - (pairTotal - 1) / 2) * 40;\n           if (waypoints.length > 0) {\n             const allPoints = [p1, ...waypoints, p2];\n             for (let i = 1; i < allPoints.length; i++) {\n               const prev = allPoints[i - 1];\n               const curr = allPoints[i];\n               const dx = curr.x - prev.x;\n               const dy = curr.y - prev.y;\n               if (Math.abs(dx) > Math.abs(dy)) {\n                 const midX = prev.x + dx / 2;\n                 segments.push({ edgeId: edge.id, x1: prev.x, y1: prev.y, x2: midX, y2: prev.y, idx: edgeIndex });\n                 segments.push({ edgeId: edge.id, x1: midX, y1: prev.y, x2: midX, y2: curr.y, idx: edgeIndex });\n                 segments.push({ edgeId: edge.id, x1: midX, y1: curr.y, x2: curr.x, y2: curr.y, idx: edgeIndex });\n               } else {\n                 const midY = prev.y + dy / 2;\n                 segments.push({ edgeId: edge.id, x1: prev.x, y1: prev.y, x2: prev.x, y2: midY, idx: edgeIndex });\n                 segments.push({ edgeId: edge.id, x1: prev.x, y1: midY, x2: curr.x, y2: midY, idx: edgeIndex });\n                 segments.push({ edgeId: edge.id, x1: curr.x, y1: midY, x2: curr.x, y2: curr.y, idx: edgeIndex });\n               }\n             }\n           } else {\n             const dx = p2.x - p1.x;\n             const dy = p2.y - p1.y;\n             if (Math.abs(dx) > Math.abs(dy)) {\n               const midX = p1.x + dx / 2 + offset;\n               segments.push({ edgeId: edge.id, x1: p1.x, y1: p1.y, x2: midX, y2: p1.y, idx: edgeIndex });\n               segments.push({ edgeId: edge.id, x1: midX, y1: p1.y, x2: midX, y2: p2.y, idx: edgeIndex });\n               segments.push({ edgeId: edge.id, x1: midX, y1: p2.y, x2: p2.x, y2: p2.y, idx: edgeIndex });\n             } else {\n               const midY = p1.y + dy / 2 + offset;\n               segments.push({ edgeId: edge.id, x1: p1.x, y1: p1.y, x2: p1.x, y2: midY, idx: edgeIndex });\n               segments.push({ edgeId: edge.id, x1: p1.x, y1: midY, x2: p2.x, y2: midY, idx: edgeIndex });\n               segments.push({ edgeId: edge.id, x1: p2.x, y1: midY, x2: p2.x, y2: p2.y, idx: edgeIndex });\n             }\n           }\n         });\n         const gaps = {};\n         for (let i = 0; i < segments.length; i++) {\n           for (let j = i + 1; j < segments.length; j++) {\n             const s1 = segments[i];\n             const s2 = segments[j];\n             if (s1.edgeId === s2.edgeId) continue;\n             const s1Horiz = Math.abs(s1.y1 - s1.y2) < 1;\n             const s2Horiz = Math.abs(s2.y1 - s2.y2) < 1;\n             if (s1Horiz === s2Horiz) continue;\n             const horiz = s1Horiz ? s1 : s2;\n             const vert = s1Horiz ? s2 : s1;\n             const hY = horiz.y1;\n             const vX = vert.x1;\n             const hMinX = Math.min(horiz.x1, horiz.x2);\n             const hMaxX = Math.max(horiz.x1, horiz.x2);\n             const vMinY = Math.min(vert.y1, vert.y2);\n             const vMaxY = Math.max(vert.y1, vert.y2);\n             if (vX > hMinX && vX < hMaxX && hY > vMinY && hY < vMaxY) {\n               const gapEdge = s1.idx > s2.idx ? s1.edgeId : s2.edgeId;\n               if (!gaps[gapEdge]) gaps[gapEdge] = [];\n               gaps[gapEdge].push({ x: vX, y: hY });\n             }\n           }\n         }\n         return gaps;\n       })();\n\n       const _placedPortLabels = [];\n\n       EDGE_DATA.list.forEach((edge) => {\n        const fromNode = NODE_DATA[edge.from];\n        const toNode = NODE_DATA[edge.to];\n        if (currentView.mode === \"rack\") {\n         if (!fromNode || !toNode || fromNode.assignedRack !== currentView.rackId || toNode.assignedRack !== currentView.rackId) return;\n        } else {\n         if (fromNode?.assignedRack || toNode?.assignedRack) return;\n        }\n        if (edge.type === \"custom\" && Array.isArray(edge.points) && edge.points.length >= 2) {\n          const customEdgeFaded = currentView.mode !== \"rack\" && edge.from && edge.to && (!isNodeVisible(edge.from) || !isNodeVisible(edge.to));\n          const poly = document.createElementNS(ns, \"polyline\");\n          poly.classList.add(\"edge\");\n          if (customEdgeFaded) {\n           poly.style.opacity = \"0.25\";\n           poly.classList.add(\"layer-faded\");\n          }\n          poly.dataset.edgeId = edge.id;\n          poly.style.stroke = edge.color || PAGE_STATE.defaultEdge || \"#475569\";\n          poly.style.strokeWidth = edge.width || 4;\n          poly.setAttribute(\"fill\", \"none\");\n          const lineStyle = edge.lineStyle || \"solid\";\n          if (lineStyle === \"dashed\") {\n           poly.style.strokeDasharray = \"10,5\";\n          } else if (lineStyle === \"dotted\") {\n           poly.style.strokeDasharray = \"2,4\";\n          } else if (lineStyle === \"wall\") {\n           poly.style.stroke = \"url(#wall-hatch)\";\n           poly.style.strokeWidth = (edge.width || 4) * 3;\n           poly.style.strokeDasharray = \"none\";\n          } else {\n           poly.style.strokeDasharray = \"none\";\n          }\n          const direction = edge.direction || \"none\";\n          if (direction === \"forward\") {\n           poly.setAttribute(\"marker-end\", \"url(#arrow-forward)\");\n          } else if (direction === \"backward\") {\n           poly.setAttribute(\"marker-start\", \"url(#arrow-backward)\");\n          } else if (direction === \"both\") {\n           poly.setAttribute(\"marker-start\", \"url(#arrow-backward)\");\n           poly.setAttribute(\"marker-end\", \"url(#arrow-forward)\");\n          }\n          const ptsStr = edge.points.map((p) => `${p.x},${p.y}`).join(\" \");\n          poly.setAttribute(\"points\", ptsStr);\n          const animDir = PAGE_STATE.animationDirection || \"all\";\n          const shouldAnimatePoly = (PAGE_STATE.animateConnections && edge.animate !== false || edge.animate === true) && direction !== \"none\" && edge.points.length >= 2 && (animDir === \"all\" || animDir === direction);\n          if (shouldAnimatePoly) {\n           poly.style.opacity = \"0.25\";\n           const polyPathD = \"M \" + edge.points.map(p => `${p.x} ${p.y}`).join(\" L \");\n           const edgeStyle = edge.animationStyle || PAGE_STATE.animationStyle || \"arrows\";\n           const arrowId = edgeStyle === \"dots\" ? \"#flow-arrow-small\" : \"#flow-arrow-big\";\n           const arrowColor = edge.color || PAGE_STATE.defaultEdge || \"#475569\";\n           const arrowCount = 3;\n           const animDuration = edge.animationSpeed ? parseFloat(edge.animationSpeed) : (PAGE_STATE.animationSpeed || 1.5);\n           if (direction === \"forward\" || direction === \"both\") {\n            for (let i = 0; i < arrowCount; i++) {\n             const arrow = document.createElementNS(ns, \"use\");\n             arrow.setAttribute(\"href\", arrowId);\n             arrow.style.fill = arrowColor;\n             arrow.style.offsetPath = `path('${polyPathD}')`;\n             arrow.style.animationDuration = animDuration + \"s\";\n             arrow.style.animationDelay = (i * (animDuration / arrowCount)) + \"s\";\n             arrow.classList.add(\"edge-arrow-forward\");\n             svg.appendChild(arrow);\n            }\n           }\n           if (direction === \"backward\" || direction === \"both\") {\n            for (let i = 0; i < arrowCount; i++) {\n             const arrow = document.createElementNS(ns, \"use\");\n             arrow.setAttribute(\"href\", arrowId);\n             arrow.style.fill = arrowColor;\n             arrow.style.offsetPath = `path('${polyPathD}')`;\n             arrow.style.animationDuration = animDuration + \"s\";\n             arrow.style.animationDelay = ((i * (animDuration / arrowCount)) + (direction === \"both\" ? animDuration / (arrowCount * 2) : 0)) + \"s\";\n             arrow.classList.add(\"edge-arrow-backward\");\n             svg.appendChild(arrow);\n            }\n           }\n          }\n          const polyHit = document.createElementNS(ns, \"polyline\");\n          polyHit.setAttribute(\"points\", ptsStr);\n          polyHit.style.fill = \"none\";\n          polyHit.style.stroke = \"transparent\";\n          polyHit.style.strokeWidth = \"20\";\n          polyHit.style.cursor = \"pointer\";\n          polyHit.dataset.edgeId = edge.id;\n          polyHit.addEventListener(\"click\", (e) => {\n           e.stopPropagation();\n           selectTheConnection(edge.id);\n          });\n          let edgeTouchStart = null;\n          let edgeTouchMoved = false;\n          polyHit.addEventListener(\"touchstart\", (e) => {\n          edgeTouchStart = Date.now();\n          edgeTouchMoved = false;\n          if (e.touches[0]) {\n           edgeTouchStartX = e.touches[0].clientX;\n           edgeTouchStartY = e.touches[0].clientY;\n          }\n         }, {\n           passive: false\n          });\n          let edgeTouchStartX = 0, edgeTouchStartY = 0;\n         polyHit.addEventListener(\"touchmove\", (e) => {\n          if (e.touches[0]) {\n           const dx = Math.abs(e.touches[0].clientX - edgeTouchStartX);\n           const dy = Math.abs(e.touches[0].clientY - edgeTouchStartY);\n           if (dx > 10 || dy > 10) edgeTouchMoved = true;\n          }\n         }, {\n           passive: false\n          });\n          polyHit.addEventListener(\"touchend\", (e) => {\n           if (edgeTouchStart && !edgeTouchMoved && Date.now() - edgeTouchStart < 400) {\n            e.stopPropagation();\n            e.preventDefault();\n            selectTheConnection(edge.id);\n           }\n           edgeTouchStart = null;\n           edgeTouchMoved = false;\n          }, {\n           passive: false\n          });\n          poly.addEventListener(\"click\", (e) => {\n          e.stopPropagation();\n          selectTheConnection(edge.id);\n         });\n         polyHit.addEventListener(\"contextmenu\", (e) => {\n          e.preventDefault();\n          e.stopPropagation();\n          if (selectedEdges.has(edge.id)) {\n           selectedEdges.delete(edge.id);\n          } else {\n           selectedEdges.add(edge.id);\n          }\n          updateAllSelections();\n         });\n         let customEdgeLastTap = 0;\n         polyHit.addEventListener(\"touchend\", (e) => {\n          const now = Date.now();\n          if (now - customEdgeLastTap < 300) {\n           e.preventDefault();\n           if (selectedEdges.has(edge.id)) {\n            selectedEdges.delete(edge.id);\n           } else {\n            selectedEdges.add(edge.id);\n           }\n           updateAllSelections();\n           if (navigator.vibrate) navigator.vibrate(50);\n           customEdgeLastTap = 0;\n          } else {\n           customEdgeLastTap = now;\n          }\n         }, { passive: false });\n         poly.addEventListener(\"contextmenu\", (e) => {\n          e.preventDefault();\n          e.stopPropagation();\n          if (selectedEdges.has(edge.id)) {\n           selectedEdges.delete(edge.id);\n          } else {\n           selectedEdges.add(edge.id);\n          }\n          updateAllSelections();\n         });\n         let edgeLastTap = 0;\n         poly.addEventListener(\"touchend\", (e) => {\n          const now = Date.now();\n          if (now - edgeLastTap < 300) {\n           e.preventDefault();\n           if (selectedEdges.has(edge.id)) {\n            selectedEdges.delete(edge.id);\n           } else {\n            selectedEdges.add(edge.id);\n           }\n           updateAllSelections();\n           if (navigator.vibrate) navigator.vibrate(50);\n           edgeLastTap = 0;\n          } else {\n           edgeLastTap = now;\n          }\n         });\n          if (currentView.mode === \"rack\") {\n           return;\n          }\n          if (edge.groupId) {\n      const bounds = edge.points.reduce((acc, p) => ({ minX: Math.min(acc.minX, p.x), minY: Math.min(acc.minY, p.y), maxX: Math.max(acc.maxX, p.x), maxY: Math.max(acc.maxY, p.y) }), { minX: Infinity, minY: Infinity, maxX: -Infinity, maxY: -Infinity });\n      const groupIndicator = document.createElementNS(ns, \"rect\");\n      groupIndicator.setAttribute(\"x\", bounds.minX - 8);\n      groupIndicator.setAttribute(\"y\", bounds.minY - 8);\n      groupIndicator.setAttribute(\"width\", bounds.maxX - bounds.minX + 16);\n      groupIndicator.setAttribute(\"height\", bounds.maxY - bounds.minY + 16);\n      groupIndicator.setAttribute(\"rx\", \"4\");\n      groupIndicator.style.fill = \"none\";\n  groupIndicator.style.stroke = PAGE_STATE.groupIndicator || \"#4fd1c5\";\n      groupIndicator.style.strokeWidth = \"3\";\n      groupIndicator.style.strokeDasharray = \"5,5\";\n      groupIndicator.style.pointerEvents = \"none\";\n      svg.appendChild(groupIndicator);\n      }\n         let lineDragging = false;\n      let lineDragStartX, lineDragStartY;\n      let linePointsStart = [];\n      polyHit.addEventListener(\"mousedown\", (e) => {\n\t  if (isViewOnly()) return;\n      if (freeDrawMode) return;\n      e.preventDefault();\n      e.stopPropagation();\n      lineDragging = true;\n      lineDragStartX = e.clientX;\n      lineDragStartY = e.clientY;\n      linePointsStart = edge.points.map(p => ({x: p.x, y: p.y}));\n      if (selectedEdges.has(edge.id)) {\n      initialPositions = {};\n      selectedNodes.forEach(nodeId => {\n      const nodePos = savedPositions[nodeId];\n      if (nodePos) initialPositions[nodeId] = { x: nodePos.x, y: nodePos.y };\n      });\n      selectedRects.forEach(rectId => {\n      const r = RECT_DATA.list.find(x => x.id === rectId);\n      if (r) { r._dragStartX = r.x; r._dragStartY = r.y; }\n      });\n      selectedTexts.forEach(textId => {\n      const t = TEXT_DATA.list.find(x => x.id === textId);\n      if (t) { t._dragStartX = t.x; t._dragStartY = t.y; }\n      });\n      selectedEdges.forEach(edgeId => {\n      const ed = EDGE_DATA.list.find(x => x.id === edgeId);\n      if (ed && ed.points) { ed._dragStartPoints = ed.points.map(p => ({x: p.x, y: p.y})); }\n      });\n      }\n      });\n      const lineMoveHandler = (e) => {\n      if (!lineDragging || freeDrawMode) return;\n      const svgEl = svg;\n      const pt1 = svgEl.createSVGPoint();\n      pt1.x = lineDragStartX;\n      pt1.y = lineDragStartY;\n      const svgP1 = pt1.matrixTransform(svgEl.getScreenCTM().inverse());\n      const pt2 = svgEl.createSVGPoint();\n      pt2.x = e.clientX;\n      pt2.y = e.clientY;\n      const svgP2 = pt2.matrixTransform(svgEl.getScreenCTM().inverse());\n      const dx = svgP2.x - svgP1.x;\n      const dy = svgP2.y - svgP1.y;\n      edge.points.forEach((p, i) => { p.x = linePointsStart[i].x + dx; p.y = linePointsStart[i].y + dy; });\n      if (selectedEdges.has(edge.id)) {\n      selectedNodes.forEach(nodeId => {\n      if (initialPositions[nodeId]) {\n       savedPositions[nodeId] = { x: initialPositions[nodeId].x + dx, y: initialPositions[nodeId].y + dy };\n      }\n      });\n      selectedRects.forEach(rectId => {\n      const r = RECT_DATA.list.find(x => x.id === rectId);\n      if (r && r._dragStartX !== undefined) { r.x = r._dragStartX + dx; r.y = r._dragStartY + dy; }\n      });\n      selectedTexts.forEach(textId => {\n      const t = TEXT_DATA.list.find(x => x.id === textId);\n      if (t && t._dragStartX !== undefined) { t.x = t._dragStartX + dx; t.y = t._dragStartY + dy; }\n      });\n      selectedEdges.forEach(edgeId => {\n      if (edgeId === edge.id) return;\n      const ed = EDGE_DATA.list.find(x => x.id === edgeId);\n      if (ed && ed._dragStartPoints) { ed.points.forEach((p, i) => { p.x = ed._dragStartPoints[i].x + dx; p.y = ed._dragStartPoints[i].y + dy; }); }\n      });\n      }\n      forgeTheTopology();\n      };\n      const lineUpHandler = () => {\n        lineDragging = false;\n        document.removeEventListener(\"mousemove\", lineMoveHandler);\n        document.removeEventListener(\"mouseup\", lineUpHandler);\n      };\n      document.addEventListener(\"mousemove\", lineMoveHandler);\n      document.addEventListener(\"mouseup\", lineUpHandler);\n      svg.appendChild(poly);\n      svg.appendChild(polyHit);\n         if (currentEdgeId === edge.id) {\n          edge.points.forEach((p, idx) => {\n           const c = document.createElementNS(ns, \"circle\");\n           c.classList.add(\"edge-edit-point\");\n           c.setAttribute(\"cx\", p.x);\n           c.setAttribute(\"cy\", p.y);\n\t\t   c.setAttribute(\"r\", PAGE_STATE.selectionHandleSize || 8);\n           c.style.fill = PAGE_STATE.selectionHandle || \"#f59e0b\";\n           c.style.stroke = \"#fff\";\n           c.style.strokeWidth = \"2\";\n           c.style.cursor = \"move\";\n           c.dataset.edgeId = edge.id;\n           c.dataset.pointIndex = String(idx);\n           c.addEventListener(\"mousedown\", (e) => {\n\t\t   if (isViewOnly()) return;\n            e.preventDefault();\n            e.stopPropagation();\n            pushUndo(\"edit edge point\");\n            let dragging = true;\n            const svgEl = svg;\n            const moveHandler = (ev) => {\n             if (!dragging) return;\n             const pt = svgEl.createSVGPoint();\n             pt.x = ev.clientX;\n             pt.y = ev.clientY;\n             const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n             edge.points[idx].x = svgP.x;\n             edge.points[idx].y = svgP.y;\n             forgeTheTopology();\n            };\n            const upHandler = () => {\n             dragging = false;\n             document.removeEventListener(\"mousemove\", moveHandler);\n             document.removeEventListener(\"mouseup\", upHandler);\n            };\n            document.addEventListener(\"mousemove\", moveHandler);\n            document.addEventListener(\"mouseup\", upHandler);\n           });\n           c.addEventListener(\"touchstart\", (e) => {\n\t\t   if (isViewOnly()) return;\n            e.preventDefault();\n            e.stopPropagation();\n            pushUndo(\"edit edge point\");\n            let dragging = true;\n            const svgEl = svg;\n            const touchMoveHandler = (ev) => {\n             if (!dragging || !ev.touches[0]) return;\n             const pt = svgEl.createSVGPoint();\n             pt.x = ev.touches[0].clientX;\n             pt.y = ev.touches[0].clientY;\n             const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n             edge.points[idx].x = svgP.x;\n             edge.points[idx].y = svgP.y;\n             forgeTheTopology();\n            };\n            const touchUpHandler = () => {\n             dragging = false;\n             document.removeEventListener(\"touchmove\", touchMoveHandler);\n             document.removeEventListener(\"touchend\", touchUpHandler);\n            };\n            document.addEventListener(\"touchmove\", touchMoveHandler);\n            document.addEventListener(\"touchend\", touchUpHandler);\n           }, { passive: false });\n           svg.appendChild(c);\n          });\n         }\n         return;\n         }\n         const p1 = positions[edge.from];\n         const p2 = positions[edge.to];\n         if (!p1 || !p2) return;\n         const edgeFaded = currentView.mode !== \"rack\" && (!isNodeVisible(edge.from) || !isNodeVisible(edge.to));\n         const pairTotal = edge._pairTotal || 1;\n         const pairIndex = edge._pairIndex || 0;\n         const routing = edge.routing || \"curved\";\n         const waypoints = edge.waypoints || [];\n         let pathD;\n         if (waypoints.length > 0) {\n          const allPoints = [p1, ...waypoints, p2];\n          if (routing === \"straight\") {\n           pathD = `M ${allPoints[0].x} ${allPoints[0].y}`;\n           for (let i = 1; i < allPoints.length; i++) {\n            pathD += ` L ${allPoints[i].x} ${allPoints[i].y}`;\n           }\n          } else if (routing === \"orthogonal\") {\n          const GAP = 10;\n          const edgeGaps = orthoGaps[edge.id] || [];\n          const segs = [];\n          for (let i = 1; i < allPoints.length; i++) {\n           const prev = allPoints[i - 1];\n           const curr = allPoints[i];\n           const dx = curr.x - prev.x;\n           const dy = curr.y - prev.y;\n           if (Math.abs(dx) > Math.abs(dy)) {\n            const midX = prev.x + dx / 2;\n            segs.push({ x1: prev.x, y1: prev.y, x2: midX, y2: prev.y });\n            segs.push({ x1: midX, y1: prev.y, x2: midX, y2: curr.y });\n            segs.push({ x1: midX, y1: curr.y, x2: curr.x, y2: curr.y });\n           } else {\n            const midY = prev.y + dy / 2;\n            segs.push({ x1: prev.x, y1: prev.y, x2: prev.x, y2: midY });\n            segs.push({ x1: prev.x, y1: midY, x2: curr.x, y2: midY });\n            segs.push({ x1: curr.x, y1: midY, x2: curr.x, y2: curr.y });\n           }\n          }\n          pathD = `M ${allPoints[0].x} ${allPoints[0].y}`;\n          segs.forEach(seg => {\n           const isHoriz = Math.abs(seg.y1 - seg.y2) < 1;\n           const segGaps = edgeGaps.filter(g => {\n            if (isHoriz) {\n             const minX = Math.min(seg.x1, seg.x2), maxX = Math.max(seg.x1, seg.x2);\n             return Math.abs(g.y - seg.y1) < 1 && g.x > minX + GAP && g.x < maxX - GAP;\n            } else {\n             const minY = Math.min(seg.y1, seg.y2), maxY = Math.max(seg.y1, seg.y2);\n             return Math.abs(g.x - seg.x1) < 1 && g.y > minY + GAP && g.y < maxY - GAP;\n            }\n           }).sort((a, b) => isHoriz ? (seg.x2 > seg.x1 ? a.x - b.x : b.x - a.x) : (seg.y2 > seg.y1 ? a.y - b.y : b.y - a.y));\n           let cx = seg.x1, cy = seg.y1;\n           segGaps.forEach(g => {\n            if (isHoriz) {\n             const gx = g.x;\n             pathD += ` L ${gx - GAP} ${cy} M ${gx + GAP} ${cy}`;\n             cx = gx + GAP;\n            } else {\n             const gy = g.y;\n             pathD += ` L ${cx} ${gy - GAP} M ${cx} ${gy + GAP}`;\n             cy = gy + GAP;\n            }\n           });\n           pathD += ` L ${seg.x2} ${seg.y2}`;\n          });\n         } else {\n           if (allPoints.length === 2) {\n            pathD = `M ${allPoints[0].x} ${allPoints[0].y} L ${allPoints[1].x} ${allPoints[1].y}`;\n           } else if (allPoints.length === 3) {\n            pathD = `M ${allPoints[0].x} ${allPoints[0].y} Q ${allPoints[1].x} ${allPoints[1].y} ${allPoints[2].x} ${allPoints[2].y}`;\n           } else {\n            pathD = `M ${allPoints[0].x} ${allPoints[0].y}`;\n            for (let i = 1; i < allPoints.length - 1; i++) {\n             const curr = allPoints[i];\n             const next = allPoints[i + 1];\n             const cpX = curr.x;\n             const cpY = curr.y;\n             const endX = (curr.x + next.x) / 2;\n             const endY = (curr.y + next.y) / 2;\n             if (i === 1) {\n              pathD += ` Q ${cpX} ${cpY} ${endX} ${endY}`;\n             } else {\n              pathD += ` T ${endX} ${endY}`;\n             }\n            }\n            const last = allPoints[allPoints.length - 1];\n            const secondLast = allPoints[allPoints.length - 2];\n            pathD += ` Q ${secondLast.x} ${secondLast.y} ${last.x} ${last.y}`;\n           }\n          }\n         } else if (routing === \"straight\") {\n          pathD = `M ${p1.x} ${p1.y} L ${p2.x} ${p2.y}`;\n         } else if (routing === \"orthogonal\") {\n         const dx = p2.x - p1.x;\n         const dy = p2.y - p1.y;\n         const offset = (pairIndex - (pairTotal - 1) / 2) * 40;\n         const GAP = 10;\n         const edgeGaps = orthoGaps[edge.id] || [];\n\n         if (Math.abs(dx) > Math.abs(dy)) {\n          const midX = p1.x + dx / 2 + offset;\n          const segs = [\n           { x1: p1.x, y1: p1.y, x2: midX, y2: p1.y },\n           { x1: midX, y1: p1.y, x2: midX, y2: p2.y },\n           { x1: midX, y1: p2.y, x2: p2.x, y2: p2.y }\n          ];\n          pathD = `M ${p1.x} ${p1.y}`;\n          segs.forEach(seg => {\n           const isHoriz = Math.abs(seg.y1 - seg.y2) < 1;\n           const segGaps = edgeGaps.filter(g => {\n            if (isHoriz) {\n             const minX = Math.min(seg.x1, seg.x2), maxX = Math.max(seg.x1, seg.x2);\n             return Math.abs(g.y - seg.y1) < 1 && g.x > minX + GAP && g.x < maxX - GAP;\n            } else {\n             const minY = Math.min(seg.y1, seg.y2), maxY = Math.max(seg.y1, seg.y2);\n             return Math.abs(g.x - seg.x1) < 1 && g.y > minY + GAP && g.y < maxY - GAP;\n            }\n           }).sort((a, b) => isHoriz ? (seg.x2 > seg.x1 ? a.x - b.x : b.x - a.x) : (seg.y2 > seg.y1 ? a.y - b.y : b.y - a.y));\n           let cx = seg.x1, cy = seg.y1;\n           segGaps.forEach(g => {\n            if (isHoriz) {\n             const gx = g.x;\n             pathD += ` L ${gx - GAP} ${cy} M ${gx + GAP} ${cy}`;\n             cx = gx + GAP;\n            } else {\n             const gy = g.y;\n             pathD += ` L ${cx} ${gy - GAP} M ${cx} ${gy + GAP}`;\n             cy = gy + GAP;\n            }\n           });\n           pathD += ` L ${seg.x2} ${seg.y2}`;\n          });\n         } else {\n          const midY = p1.y + dy / 2 + offset;\n          const segs = [\n           { x1: p1.x, y1: p1.y, x2: p1.x, y2: midY },\n           { x1: p1.x, y1: midY, x2: p2.x, y2: midY },\n           { x1: p2.x, y1: midY, x2: p2.x, y2: p2.y }\n          ];\n          pathD = `M ${p1.x} ${p1.y}`;\n          segs.forEach(seg => {\n           const isHoriz = Math.abs(seg.y1 - seg.y2) < 1;\n           const segGaps = edgeGaps.filter(g => {\n            if (isHoriz) {\n             const minX = Math.min(seg.x1, seg.x2), maxX = Math.max(seg.x1, seg.x2);\n             return Math.abs(g.y - seg.y1) < 1 && g.x > minX + GAP && g.x < maxX - GAP;\n            } else {\n             const minY = Math.min(seg.y1, seg.y2), maxY = Math.max(seg.y1, seg.y2);\n             return Math.abs(g.x - seg.x1) < 1 && g.y > minY + GAP && g.y < maxY - GAP;\n            }\n           }).sort((a, b) => isHoriz ? (seg.x2 > seg.x1 ? a.x - b.x : b.x - a.x) : (seg.y2 > seg.y1 ? a.y - b.y : b.y - a.y));\n           let cx = seg.x1, cy = seg.y1;\n           segGaps.forEach(g => {\n            if (isHoriz) {\n             const gx = g.x;\n             pathD += ` L ${gx - GAP} ${cy} M ${gx + GAP} ${cy}`;\n             cx = gx + GAP;\n            } else {\n             const gy = g.y;\n             pathD += ` L ${cx} ${gy - GAP} M ${cx} ${gy + GAP}`;\n             cy = gy + GAP;\n            }\n           });\n           pathD += ` L ${seg.x2} ${seg.y2}`;\n          });\n         }\n        } else {\n          const midX = (p1.x + p2.x) / 2;\n          const midY = (p1.y + p2.y) / 2;\n          const dx = p2.x - p1.x;\n          const dy = p2.y - p1.y;\n          const len = Math.sqrt(dx * dx + dy * dy) || 1;\n          const perpX = -dy / len;\n          const perpY = dx / len;\n          let offsetAmount = 0;\n          if (pairTotal > 1) {\n           offsetAmount = (pairIndex - (pairTotal - 1) / 2) * 40;\n          }\n          const ctrlX = midX + perpX * offsetAmount;\n          const ctrlY = midY + perpY * offsetAmount;\n          pathD = `M ${p1.x} ${p1.y} Q ${ctrlX} ${ctrlY} ${p2.x} ${p2.y}`;\n         }\n         const path = document.createElementNS(ns, \"path\");\n         path.setAttribute(\"d\", pathD);\n         path.setAttribute(\"fill\", \"none\");\n         path.classList.add(\"edge\");\n         if (edgeFaded) {\n          path.style.opacity = \"0.25\";\n          path.classList.add(\"layer-faded\");\n         }\n         if (edge.type === \"backup\") path.classList.add(\"backup\");\n         path.dataset.edgeId = edge.id;\n         path.dataset.from = edge.from;\n         path.dataset.to = edge.to;\n         path.style.stroke = edge.color;\n         path.style.strokeWidth = edge.width;\n         const edgeDirection = edge.direction || \"none\";\n         const edgeLineStyle = edge.lineStyle || \"solid\";\n         if (edgeLineStyle === \"dashed\") { path.style.strokeDasharray = \"10,5\"; }\n         else if (edgeLineStyle === \"dotted\") { path.style.strokeDasharray = \"2,4\"; }\n         if (edgeDirection === \"forward\") {\n          path.setAttribute(\"marker-end\", \"url(#arrow-forward)\");\n         } else if (edgeDirection === \"backward\") {\n          path.setAttribute(\"marker-start\", \"url(#arrow-backward)\");\n         } else if (edgeDirection === \"both\") {\n          path.setAttribute(\"marker-start\", \"url(#arrow-backward)\");\n          path.setAttribute(\"marker-end\", \"url(#arrow-forward)\");\n         }\n         const animDir = PAGE_STATE.animationDirection || \"all\";\n         const shouldAnimate = (PAGE_STATE.animateConnections && edge.animate !== false || edge.animate === true) && edgeDirection !== \"none\" && (animDir === \"all\" || animDir === edgeDirection);\n         if (shouldAnimate && !edgeFaded) {\n          path.style.opacity = \"0.25\";\n          const edgeStyle = edge.animationStyle || PAGE_STATE.animationStyle || \"arrows\";\n          const arrowId = edgeStyle === \"dots\" ? \"#flow-arrow-small\" : \"#flow-arrow-big\";\n          const arrowColor = edge.color || PAGE_STATE.defaultEdge || \"#475569\";\n          const arrowCount = 3;\n          const animDuration = edge.animationSpeed ? parseFloat(edge.animationSpeed) : (PAGE_STATE.animationSpeed || 1.5);\n          if (edgeDirection === \"forward\" || edgeDirection === \"both\") {\n           for (let i = 0; i < arrowCount; i++) {\n            const arrow = document.createElementNS(ns, \"use\");\n            arrow.setAttribute(\"href\", arrowId);\n            arrow.style.fill = arrowColor;\n            arrow.style.offsetPath = `path('${pathD}')`;\n            arrow.style.animationDuration = animDuration + \"s\";\n            arrow.style.animationDelay = (i * (animDuration / arrowCount)) + \"s\";\n            arrow.classList.add(\"edge-arrow-forward\");\n            svg.appendChild(arrow);\n           }\n          }\n          if (edgeDirection === \"backward\" || edgeDirection === \"both\") {\n           for (let i = 0; i < arrowCount; i++) {\n            const arrow = document.createElementNS(ns, \"use\");\n            arrow.setAttribute(\"href\", arrowId);\n            arrow.style.fill = arrowColor;\n            arrow.style.offsetPath = `path('${pathD}')`;\n            arrow.style.animationDuration = animDuration + \"s\";\n            arrow.style.animationDelay = ((i * (animDuration / arrowCount)) + (edgeDirection === \"both\" ? animDuration / (arrowCount * 2) : 0)) + \"s\";\n            arrow.classList.add(\"edge-arrow-backward\");\n            svg.appendChild(arrow);\n           }\n          }\n         }\n         const pathHit = document.createElementNS(ns, \"path\");\n         pathHit.setAttribute(\"d\", pathD);\n         pathHit.setAttribute(\"fill\", \"none\");\n         pathHit.style.stroke = \"transparent\";\n         pathHit.style.strokeWidth = \"20\";\n         pathHit.style.cursor = \"pointer\";\n         pathHit.dataset.edgeId = edge.id;\n         pathHit.addEventListener(\"click\", (e) => {\n          e.stopPropagation();\n          selectTheConnection(edge.id);\n         });\n         let pathTouchStart = null;\n         let pathTouchMoved = false;\n         pathHit.addEventListener(\"touchstart\", (e) => {\n         pathTouchStart = Date.now();\n         pathTouchMoved = false;\n         if (e.touches[0]) {\n          pathTouchStartX = e.touches[0].clientX;\n          pathTouchStartY = e.touches[0].clientY;\n         }\n        }, {\n          passive: false\n         });\n         let pathTouchStartX = 0, pathTouchStartY = 0;\n        pathHit.addEventListener(\"touchmove\", (e) => {\n         if (e.touches[0]) {\n          const dx = Math.abs(e.touches[0].clientX - pathTouchStartX);\n          const dy = Math.abs(e.touches[0].clientY - pathTouchStartY);\n          if (dx > 10 || dy > 10) pathTouchMoved = true;\n         }\n        }, {\n          passive: false\n         });\n         pathHit.addEventListener(\"touchend\", (e) => {\n          if (pathTouchStart && !pathTouchMoved && Date.now() - pathTouchStart < 400) {\n           e.stopPropagation();\n           e.preventDefault();\n           selectTheConnection(edge.id);\n          }\n          pathTouchStart = null;\n          pathTouchMoved = false;\n         }, {\n          passive: false\n         });\n         path.addEventListener(\"click\", (e) => {\n          e.stopPropagation();\n          selectTheConnection(edge.id);\n         });\n         svg.appendChild(path);\n         svg.appendChild(pathHit);\n         if (PAGE_STATE.showPortLabels && (edge.fromPort || edge.toPort)) {\n          const plNs = \"http://www.w3.org/2000/svg\";\n          const plWaypoints = edge.waypoints || [];\n          const plPad = { x: 5, y: 3 };\n          function plHitsAny(px, py, halfW, halfH) {\n           for (const nid in positions) {\n            const np = positions[nid];\n            if (!np) continue;\n            const nr = (savedSizes[nid] || 55) + 8;\n            const nd = NODE_DATA[nid];\n            const tFs = PAGE_STATE.nodeTitleSize || 18;\n            const sFs = PAGE_STATE.nodeSubSize || 13;\n            if (px + halfW > np.x - nr && px - halfW < np.x + nr && py + halfH > np.y - nr && py - halfH < np.y + nr) return true;\n            if (nd && nd.name) {\n             const tw = (nd.name.length * tFs * 0.38) + 6;\n             const ty = np.y - nr - 5 - tFs / 2;\n             const th = tFs / 2 + 6;\n             if (px + halfW > np.x - tw && px - halfW < np.x + tw && py + halfH > ty - th && py - halfH < ty + th) return true;\n            }\n            if (nd && nd.subtitle) {\n             const sw = (nd.subtitle.length * sFs * 0.38) + 6;\n             const sy = np.y + nr + 5 + sFs / 2;\n             const sh = sFs / 2 + 6;\n             if (px + halfW > np.x - sw && px - halfW < np.x + sw && py + halfH > sy - sh && py - halfH < sy + sh) return true;\n            }\n           }\n           for (const pl of _placedPortLabels) {\n            if (Math.abs(px - pl.x) < (halfW + pl.hw + 4) && Math.abs(py - pl.y) < (halfH + pl.hh + 4)) return true;\n           }\n           return false;\n          }\n          function plFindClear(origin, target, nodeId, fromEnd) {\n           const wps = fromEnd ? plWaypoints : [...plWaypoints].reverse();\n           const pts = [origin, ...wps, target];\n           const estW = 40, estH = 12;\n           for (let t = 0.12; t <= 0.5; t += 0.04) {\n            const totalLen = pts.reduce((s, pt, i) => i === 0 ? 0 : s + Math.sqrt((pt.x - pts[i-1].x)**2 + (pt.y - pts[i-1].y)**2), 0);\n            let target_d = totalLen * t, accum = 0;\n            for (let i = 1; i < pts.length; i++) {\n             const dx = pts[i].x - pts[i-1].x, dy = pts[i].y - pts[i-1].y;\n             const segLen = Math.sqrt(dx*dx + dy*dy);\n             if (segLen === 0) continue;\n             if (accum + segLen >= target_d) {\n              const frac = (target_d - accum) / segLen;\n              const cx = pts[i-1].x + dx * frac, cy = pts[i-1].y + dy * frac;\n              const fHitsOnPath = plHitsAny(cx, cy, estW, estH);\n              if (!fHitsOnPath) return { x: cx, y: cy };\n              const nx = -dy / segLen, ny = dx / segLen;\n              for (const off of [22, -22, 36, -36]) {\n               const ox = cx + nx * off, oy = cy + ny * off;\n               if (!plHitsAny(ox, oy, estW, estH)) return { x: ox, y: oy };\n              }\n             }\n             accum += segLen;\n            }\n           }\n           const mx = (origin.x + target.x) / 2, my = (origin.y + target.y) / 2;\n           const dx = target.x - origin.x, dy = target.y - origin.y;\n           const fLen = Math.sqrt(dx*dx + dy*dy) || 1;\n           const perpX = -dy / fLen, perpY = dx / fLen;\n           for (const fo of [0, 20, -20, 40, -40]) {\n            const fx = mx + perpX * fo, fy = my - 15 + perpY * fo;\n            if (!plHitsAny(fx, fy, 40, 12)) return { x: fx, y: fy };\n           }\n           return { x: mx, y: my - 15 };\n          }\n          function plRenderLabel(text, pos) {\n           const g = document.createElementNS(plNs, \"g\");\n           g.classList.add(\"port-label\");\n           g.style.pointerEvents = \"none\";\n           const tmp = document.createElementNS(plNs, \"text\");\n           tmp.textContent = text;\n           tmp.setAttribute(\"x\", pos.x);\n           tmp.setAttribute(\"y\", pos.y);\n           tmp.setAttribute(\"text-anchor\", \"middle\");\n           tmp.setAttribute(\"dominant-baseline\", \"central\");\n           tmp.style.fontSize = \"13px\";\n           tmp.style.fontFamily = \"Inter, system-ui, sans-serif\";\n           tmp.style.fontWeight = \"600\";\n           svg.appendChild(tmp);\n           const bbox = tmp.getBBox();\n           tmp.remove();\n           const bg = document.createElementNS(plNs, \"rect\");\n           bg.setAttribute(\"x\", bbox.x - plPad.x);\n           bg.setAttribute(\"y\", bbox.y - plPad.y);\n           bg.setAttribute(\"width\", bbox.width + plPad.x * 2);\n           bg.setAttribute(\"height\", bbox.height + plPad.y * 2);\n           bg.setAttribute(\"rx\", 4);\n           bg.setAttribute(\"ry\", 4);\n           bg.style.fill = \"rgba(15, 23, 42, 0.88)\";\n           bg.style.stroke = edge.color || PAGE_STATE.defaultEdge || \"#475569\";\n           bg.style.strokeWidth = \"1\";\n           bg.style.strokeOpacity = \"0.5\";\n           g.appendChild(bg);\n           const label = document.createElementNS(plNs, \"text\");\n           label.textContent = text;\n           label.setAttribute(\"x\", pos.x);\n           label.setAttribute(\"y\", pos.y);\n           label.setAttribute(\"text-anchor\", \"middle\");\n           label.setAttribute(\"dominant-baseline\", \"central\");\n           label.style.fill = \"#e2e8f0\";\n           label.style.fontSize = \"13px\";\n           label.style.fontFamily = \"Inter, system-ui, sans-serif\";\n           label.style.fontWeight = \"600\";\n           label.style.pointerEvents = \"none\";\n           g.appendChild(label);\n           svg.appendChild(g);\n           _placedPortLabels.push({ x: pos.x, y: pos.y, hw: (bbox.width / 2) + plPad.x, hh: (bbox.height / 2) + plPad.y });\n          }\n          if (edge.fromPort) {\n           const fromPos = plFindClear(p1, p2, edge.from, true);\n           plRenderLabel(edge.fromPort, fromPos);\n          }\n          if (edge.toPort) {\n           const toPos = plFindClear(p2, p1, edge.to, false);\n           plRenderLabel(edge.toPort, toPos);\n          }\n         }\n         if (currentEdgeId === edge.id && !isViewOnly()) {\n          const waypoints = edge.waypoints || [];\n          waypoints.forEach((wp, idx) => {\n           const c = document.createElementNS(ns, \"circle\");\n           c.classList.add(\"waypoint-handle\");\n           c.setAttribute(\"cx\", wp.x);\n           c.setAttribute(\"cy\", wp.y);\n           c.setAttribute(\"r\", 8);\n           c.style.fill = \"#3b82f6\";\n           c.style.stroke = \"#fff\";\n           c.style.strokeWidth = \"2\";\n           c.style.cursor = \"grab\";\n           c.addEventListener(\"mousedown\", (e) => {\n            if (isViewOnly()) return;\n            e.preventDefault();\n            e.stopPropagation();\n            pushUndo(\"move waypoint\");\n            let dragging = true;\n            const svgEl = svg;\n            c.style.cursor = \"grabbing\";\n            const moveHandler = (ev) => {\n             if (!dragging) return;\n             const pt = svgEl.createSVGPoint();\n             pt.x = ev.clientX;\n             pt.y = ev.clientY;\n             const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n             edge.waypoints[idx].x = svgP.x;\n             edge.waypoints[idx].y = svgP.y;\n             forgeTheTopology();\n            };\n            const upHandler = () => {\n             dragging = false;\n             c.style.cursor = \"grab\";\n             document.removeEventListener(\"mousemove\", moveHandler);\n             document.removeEventListener(\"mouseup\", upHandler);\n            };\n            document.addEventListener(\"mousemove\", moveHandler);\n            document.addEventListener(\"mouseup\", upHandler);\n           });\n           let waypointMoved = false;\n           c.addEventListener(\"touchstart\", (e) => {\n            if (isViewOnly()) return;\n            e.preventDefault();\n            e.stopPropagation();\n            waypointMoved = false;\n            pushUndo(\"move waypoint\");\n            let dragging = true;\n            const svgEl = svg;\n            const touchMoveHandler = (ev) => {\n             if (!dragging || !ev.touches[0]) return;\n             waypointMoved = true;\n             const pt = svgEl.createSVGPoint();\n             pt.x = ev.touches[0].clientX;\n             pt.y = ev.touches[0].clientY;\n             const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n             edge.waypoints[idx].x = svgP.x;\n             edge.waypoints[idx].y = svgP.y;\n             forgeTheTopology();\n            };\n            const touchUpHandler = () => {\n             dragging = false;\n             document.removeEventListener(\"touchmove\", touchMoveHandler);\n             document.removeEventListener(\"touchend\", touchUpHandler);\n            };\n            document.addEventListener(\"touchmove\", touchMoveHandler);\n            document.addEventListener(\"touchend\", touchUpHandler);\n           }, { passive: false });\n           c.addEventListener(\"contextmenu\", (e) => {\n            if (isViewOnly()) return;\n            e.preventDefault();\n            e.stopPropagation();\n            pushUndo(\"delete waypoint\");\n            edge.waypoints.splice(idx, 1);\n            forgeTheTopology();\n           });\n           let lastTap = 0;\n           c.addEventListener(\"touchend\", (e) => {\n            if (waypointMoved) {\n             waypointMoved = false;\n             lastTap = 0;\n             return;\n            }\n            const now = Date.now();\n            if (now - lastTap < 300) {\n             if (isViewOnly()) return;\n             e.preventDefault();\n             pushUndo(\"delete waypoint\");\n             edge.waypoints.splice(idx, 1);\n             forgeTheTopology();\n            }\n            lastTap = now;\n           });\n           svg.appendChild(c);\n          });\n         }\n         pathHit.addEventListener(\"dblclick\", (e) => {\n          if (isViewOnly()) return;\n          e.stopPropagation();\n          const svgEl = svg;\n          const pt = svgEl.createSVGPoint();\n          pt.x = e.clientX;\n          pt.y = e.clientY;\n          const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n          pushUndo(\"add waypoint\");\n          if (!edge.waypoints) edge.waypoints = [];\n          const newWp = { x: svgP.x, y: svgP.y };\n          if (edge.waypoints.length === 0) {\n           edge.waypoints.push(newWp);\n          } else {\n           const allPts = [p1, ...edge.waypoints, p2];\n           let bestIdx = 0;\n           let bestDist = Infinity;\n           for (let i = 0; i < allPts.length - 1; i++) {\n            const segMidX = (allPts[i].x + allPts[i + 1].x) / 2;\n            const segMidY = (allPts[i].y + allPts[i + 1].y) / 2;\n            const dist = Math.hypot(svgP.x - segMidX, svgP.y - segMidY);\n            if (dist < bestDist) {\n             bestDist = dist;\n             bestIdx = i;\n            }\n           }\n           edge.waypoints.splice(bestIdx, 0, newWp);\n          }\n          selectTheConnection(edge.id);\n          forgeTheTopology();\n         });\n         let longPressTimer = null;\n         let longPressTriggered = false;\n         pathHit.addEventListener(\"touchstart\", (e) => {\n          if (isViewOnly() || !e.touches[0]) return;\n          longPressTriggered = false;\n          const touch = e.touches[0];\n          const startX = touch.clientX;\n          const startY = touch.clientY;\n          longPressTimer = setTimeout(() => {\n           longPressTriggered = true;\n           if (navigator.vibrate) navigator.vibrate(50);\n           const svgEl = svg;\n           const pt = svgEl.createSVGPoint();\n           pt.x = startX;\n           pt.y = startY;\n           const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n           pushUndo(\"add waypoint\");\n           if (!edge.waypoints) edge.waypoints = [];\n           const newWp = { x: svgP.x, y: svgP.y };\n           if (edge.waypoints.length === 0) {\n            edge.waypoints.push(newWp);\n           } else {\n            const allPts = [p1, ...edge.waypoints, p2];\n            let bestIdx = 0;\n            let bestDist = Infinity;\n            for (let i = 0; i < allPts.length - 1; i++) {\n             const segMidX = (allPts[i].x + allPts[i + 1].x) / 2;\n             const segMidY = (allPts[i].y + allPts[i + 1].y) / 2;\n             const dist = Math.hypot(svgP.x - segMidX, svgP.y - segMidY);\n             if (dist < bestDist) {\n              bestDist = dist;\n              bestIdx = i;\n             }\n            }\n            edge.waypoints.splice(bestIdx, 0, newWp);\n           }\n           selectTheConnection(edge.id);\n           forgeTheTopology();\n          }, 500);\n         }, { passive: true });\n         pathHit.addEventListener(\"touchmove\", () => {\n          if (longPressTimer) {\n           clearTimeout(longPressTimer);\n           longPressTimer = null;\n          }\n         }, { passive: true });\n         pathHit.addEventListener(\"touchend\", () => {\n          if (longPressTimer) {\n           clearTimeout(longPressTimer);\n           longPressTimer = null;\n          }\n         }, { passive: true });\n        });\n        Object.entries(positions).forEach(([id, pos]) => {\n         const node = NODE_DATA[id];\n         if (!node) return;\n         if (currentView.mode === \"rack\") {\n          if (node.assignedRack !== currentView.rackId) return;\n         } else {\n          if (node.assignedRack) return;\n         }\n         const g = document.createElementNS(\"http://www.w3.org/2000/svg\", \"g\");\n         g.classList.add(\"node-group\");\n         g.dataset.nodeId = id;\n         const nodeRotation = NODE_DATA[id].rotation || 0;\n         g.setAttribute(\"transform\", `translate(${pos.x},${pos.y}) rotate(${nodeRotation})`);\n         if (currentView.mode !== \"rack\" && !isNodeVisible(id)) {\n          g.style.opacity = \"0.25\";\n          g.classList.add(\"layer-faded\");\n         }\n         let r = savedSizes[id] || 55;\n\t\tif (!savedSizes[id]) {\n\t\t if (window.innerWidth <= 480) r = 45;\n\t\t else if (window.innerWidth <= 768) r = 50;\n\t\t}\n         const styles = resolveStylesForNode(id);\n         const ns = \"http://www.w3.org/2000/svg\";\n         const hitArea = document.createElementNS(ns, \"circle\");\n         hitArea.setAttribute(\"r\", r * 1.5);\n         hitArea.style.fill = \"transparent\";\n         hitArea.style.stroke = \"none\";\n         hitArea.style.cursor = \"grab\";\n         hitArea.classList.add(\"node-hit-area\");\n         const shapeEl = createNodeShape(id, r);\n         const titleOffsetX = styles.titleOffsetX || 0;\n         const titleOffsetY = styles.titleOffsetY || 0;\n         const subOffsetX = styles.subOffsetX || 0;\n         const subOffsetY = styles.subOffsetY || 0;\n         const label = document.createElementNS(\"http://www.w3.org/2000/svg\", \"text\");\n         label.classList.add(\"node-label\");\n         label.setAttribute(\"x\", titleOffsetX);\n         label.setAttribute(\"y\", -r - 5 + titleOffsetY);\n         const labelFontSize = styles.titleSize || PAGE_STATE.nodeTitleSize || r * 0.33;\n        label.style.fontSize = labelFontSize + \"px\";\n         label.textContent = NODE_DATA[id].name;\n\t\tlabel.style.fill = styles.titleColor || PAGE_STATE.nodeTitle || \"#e2e8f0\";\n        label.style.fontFamily = styles.titleFont || PAGE_STATE.nodeFont || \"Inter, system-ui, sans-serif\";\n         label.style.pointerEvents = \"none\";\n         const sub = document.createElementNS(\"http://www.w3.org/2000/svg\", \"text\");\n         sub.classList.add(\"node-sub\");\n         sub.setAttribute(\"x\", subOffsetX);\n         sub.setAttribute(\"y\", r + 20 + subOffsetY);\n         const subFontSize = styles.subSize || PAGE_STATE.nodeSubSize || r * 0.24;\n        sub.style.fontSize = subFontSize + \"px\";\n         sub.textContent = NODE_DATA[id].ip;\n\t\tsub.style.fill = styles.subColor || PAGE_STATE.nodeSub || \"#94a3b8\";\n        sub.style.fontFamily = styles.subFont || PAGE_STATE.nodeFont || \"Inter, system-ui, sans-serif\";\n         sub.style.pointerEvents = \"none\";\n         if (hasCoverageZone(node.shape) && node.fovEnabled) {\n           const defaults = getCoverageDefaults(node.shape);\n           const fovAngle = node.fovAngle || defaults.angle;\n           const fovDistance = node.fovDistance || defaults.distance;\n           const fovInnerRadius = node.fovInnerRadius || 0;\n           const fovRotation = node.fovRotation || 0;\n           const fovColor = node.fovColor || \"#f59e0b\";\n           const fovOpacity = node.fovOpacity || 20;\n           const fovGradient = node.fovGradient || false;\n           const fovBorderColor = node.fovBorderColor || \"#f59e0b\";\n           const fovBorderWidth = node.fovBorderWidth ?? 2;\n           const fovBorderStyle = node.fovBorderStyle || \"solid\";\n           const fovBorderOpacity = node.fovBorderOpacity ?? 100;\n           const fovLabel = node.fovLabel || \"\";\n           const fovAnimate = node.fovAnimate || false;\n           const fovAnimationType = node.fovAnimationType || defaults.animationType;\n           const fovSweep = node.fovSweep || 120;\n           const fovSpeed = node.fovSpeed || 4;\n           \n           const fovGroup = document.createElementNS(ns, \"g\");\n           fovGroup.classList.add(\"fov-group\");\n           if (!ZONES_VISIBLE) fovGroup.style.display = \"none\";\n           \n           if (fovGradient) {\n             const gradientId = `fov-gradient-${id}`;\n             const defs = document.createElementNS(ns, \"defs\");\n             const gradient = document.createElementNS(ns, \"radialGradient\");\n             gradient.id = gradientId;\n             gradient.setAttribute(\"cx\", \"0\");\n             gradient.setAttribute(\"cy\", \"0\");\n             gradient.setAttribute(\"r\", fovDistance);\n             gradient.setAttribute(\"gradientUnits\", \"userSpaceOnUse\");\n             const stop1 = document.createElementNS(ns, \"stop\");\n             stop1.setAttribute(\"offset\", fovInnerRadius / fovDistance);\n             stop1.setAttribute(\"stop-color\", fovColor);\n             stop1.setAttribute(\"stop-opacity\", fovOpacity / 100);\n             const stop2 = document.createElementNS(ns, \"stop\");\n             stop2.setAttribute(\"offset\", \"1\");\n             stop2.setAttribute(\"stop-color\", fovColor);\n             stop2.setAttribute(\"stop-opacity\", \"0\");\n             gradient.appendChild(stop1);\n             gradient.appendChild(stop2);\n             defs.appendChild(gradient);\n             fovGroup.appendChild(defs);\n           }\n           \n           const fovPath = document.createElementNS(ns, \"path\");\n           \n           if (fovAngle >= 360) {\n             if (fovInnerRadius > 0) {\n               fovPath.setAttribute(\"d\", `M ${fovDistance},0 A ${fovDistance},${fovDistance} 0 1,1 ${-fovDistance},0 A ${fovDistance},${fovDistance} 0 1,1 ${fovDistance},0 M ${fovInnerRadius},0 A ${fovInnerRadius},${fovInnerRadius} 0 1,0 ${-fovInnerRadius},0 A ${fovInnerRadius},${fovInnerRadius} 0 1,0 ${fovInnerRadius},0`);\n               fovPath.setAttribute(\"fill-rule\", \"evenodd\");\n             } else {\n               fovPath.setAttribute(\"d\", `M 0,0 m -${fovDistance},0 a ${fovDistance},${fovDistance} 0 1,0 ${fovDistance * 2},0 a ${fovDistance},${fovDistance} 0 1,0 -${fovDistance * 2},0`);\n             }\n           } else {\n             const angleRad = (fovAngle * Math.PI) / 180;\n             const rotationRad = ((fovRotation - 90) * Math.PI) / 180;\n             const startAngle = rotationRad - angleRad / 2;\n             const endAngle = rotationRad + angleRad / 2;\n             const x1 = Math.cos(startAngle) * fovDistance;\n             const y1 = Math.sin(startAngle) * fovDistance;\n             const x2 = Math.cos(endAngle) * fovDistance;\n             const y2 = Math.sin(endAngle) * fovDistance;\n             const largeArc = fovAngle > 180 ? 1 : 0;\n             if (fovInnerRadius > 0) {\n               const ix1 = Math.cos(startAngle) * fovInnerRadius;\n               const iy1 = Math.sin(startAngle) * fovInnerRadius;\n               const ix2 = Math.cos(endAngle) * fovInnerRadius;\n               const iy2 = Math.sin(endAngle) * fovInnerRadius;\n               fovPath.setAttribute(\"d\", `M ${ix1},${iy1} L ${x1},${y1} A ${fovDistance},${fovDistance} 0 ${largeArc},1 ${x2},${y2} L ${ix2},${iy2} A ${fovInnerRadius},${fovInnerRadius} 0 ${largeArc},0 ${ix1},${iy1} Z`);\n             } else {\n               fovPath.setAttribute(\"d\", `M 0,0 L ${x1},${y1} A ${fovDistance},${fovDistance} 0 ${largeArc},1 ${x2},${y2} Z`);\n             }\n           }\n           \n           if (fovGradient) {\n             fovPath.style.fill = `url(#fov-gradient-${id})`;\n           } else {\n             const opacityHex = Math.round((fovOpacity / 100) * 255).toString(16).padStart(2, '0');\n             fovPath.style.fill = fovColor + opacityHex;\n           }\n           \n           const borderOpacityHex = Math.round((fovBorderOpacity / 100) * 255).toString(16).padStart(2, '0');\n           fovPath.style.stroke = fovBorderColor + borderOpacityHex;\n           fovPath.style.strokeWidth = fovBorderWidth;\n           if (fovBorderStyle === \"dashed\") {\n             fovPath.style.strokeDasharray = \"10,5\";\n           } else if (fovBorderStyle === \"dotted\") {\n             fovPath.style.strokeDasharray = \"3,3\";\n           }\n           fovPath.style.pointerEvents = \"none\";\n           fovPath.classList.add(\"fov-cone\");\n           \n           fovGroup.appendChild(fovPath);\n           \n           if (fovLabel) {\n             const fovLabelPosition = node.fovLabelPosition || \"center\";\n             const fovLabelSize = node.fovLabelSize || 14;\n             const fovLabelColor = node.fovLabelColor || \"#ffffff\";\n             const fovLabelBold = node.fovLabelBold || false;\n             const fovLabelBg = node.fovLabelBg || false;\n             const fovLabelBgColor = node.fovLabelBgColor || \"#000000\";\n             let labelDistance;\n             if (fovLabelPosition === \"center\") {\n               labelDistance = fovAngle >= 360 ? 0 : fovDistance * 0.4;\n             } else if (fovLabelPosition === \"edge\") {\n               labelDistance = fovDistance * 0.75;\n             } else {\n               labelDistance = fovDistance + fovLabelSize + 8;\n             }\n             const labelAngle = ((fovRotation - 90) * Math.PI) / 180;\n             const fovLabelOffsetX = node.fovLabelOffsetX || 0;\n             const fovLabelOffsetY = node.fovLabelOffsetY || 0;\n             const labelX = (fovAngle >= 360 && fovLabelPosition === \"center\" ? 0 : Math.cos(labelAngle) * labelDistance) + fovLabelOffsetX;\n             const labelY = (fovAngle >= 360 && fovLabelPosition === \"center\" ? 0 : Math.sin(labelAngle) * labelDistance) + fovLabelOffsetY;\n             if (fovLabelBg) {\n               const bgRect = document.createElementNS(ns, \"rect\");\n               const textWidth = fovLabel.length * fovLabelSize * 0.6;\n               const textHeight = fovLabelSize * 1.4;\n               bgRect.setAttribute(\"x\", labelX - textWidth / 2 - 6);\n               bgRect.setAttribute(\"y\", labelY - textHeight / 2);\n               bgRect.setAttribute(\"width\", textWidth + 12);\n               bgRect.setAttribute(\"height\", textHeight);\n               bgRect.setAttribute(\"rx\", \"4\");\n               bgRect.style.fill = fovLabelBgColor;\n               bgRect.style.opacity = \"0.8\";\n               bgRect.style.pointerEvents = \"none\";\n               fovGroup.appendChild(bgRect);\n             }\n             const labelEl = document.createElementNS(ns, \"text\");\n             labelEl.setAttribute(\"x\", labelX);\n             labelEl.setAttribute(\"y\", labelY);\n             labelEl.setAttribute(\"text-anchor\", \"middle\");\n             labelEl.setAttribute(\"dominant-baseline\", \"middle\");\n             labelEl.style.fill = fovLabelColor;\n             labelEl.style.fontSize = fovLabelSize + \"px\";\n             labelEl.style.fontWeight = fovLabelBold ? \"bold\" : \"normal\";\n             labelEl.style.fontFamily = \"system-ui, sans-serif\";\n             labelEl.style.pointerEvents = \"none\";\n             labelEl.textContent = fovLabel;\n             fovGroup.appendChild(labelEl);\n           }\n           \n           if (fovAnimate) {\n             const animationName = `fov-anim-${id}`;\n             const styleEl = document.createElementNS(\"http://www.w3.org/1999/xhtml\", \"style\");\n             \n             if (fovAnimationType === \"sweep\" && fovAngle < 360) {\n               styleEl.textContent = `\n                 @keyframes ${animationName} {\n                   0%, 100% { transform: rotate(0deg); }\n                   50% { transform: rotate(${fovSweep}deg); }\n                 }\n               `;\n               fovGroup.style.animation = `${animationName} ${fovSpeed}s ease-in-out infinite`;\n               fovGroup.style.transformOrigin = \"0 0\";\n             } else if (fovAnimationType === \"pulse\") {\n               styleEl.textContent = `\n                 @keyframes ${animationName} {\n                   0%, 100% { transform: scale(1); opacity: 1; }\n                   50% { transform: scale(1.1); opacity: 0.7; }\n                 }\n               `;\n               fovGroup.style.animation = `${animationName} ${fovSpeed}s ease-in-out infinite`;\n               fovGroup.style.transformOrigin = \"0 0\";\n             } else if (fovAnimationType === \"rings\") {\n               for (let i = 1; i <= 3; i++) {\n                 const ring = document.createElementNS(ns, \"circle\");\n                 ring.setAttribute(\"cx\", \"0\");\n                 ring.setAttribute(\"cy\", \"0\");\n                 ring.setAttribute(\"r\", fovDistance * 0.3 * i);\n                 ring.style.fill = \"none\";\n                 ring.style.stroke = fovBorderColor;\n                 ring.style.strokeWidth = \"2\";\n                 ring.style.opacity = \"0\";\n                 const ringAnimName = `${animationName}-ring-${i}`;\n                 const ringStyle = document.createElementNS(\"http://www.w3.org/1999/xhtml\", \"style\");\n                 ringStyle.textContent = `\n                   @keyframes ${ringAnimName} {\n                     0% { r: ${fovInnerRadius || 10}; opacity: 0.8; }\n                     100% { r: ${fovDistance}; opacity: 0; }\n                   }\n                 `;\n                 ring.style.animation = `${ringAnimName} ${fovSpeed}s ease-out infinite`;\n                 ring.style.animationDelay = `${(i - 1) * (fovSpeed / 3)}s`;\n                 fovGroup.appendChild(ringStyle);\n                 fovGroup.appendChild(ring);\n               }\n             } else if (fovAnimationType === \"spin\") {\n               styleEl.textContent = `\n                 @keyframes ${animationName} {\n                   0% { transform: rotate(0deg); }\n                   100% { transform: rotate(360deg); }\n                 }\n               `;\n               fovGroup.style.animation = `${animationName} ${fovSpeed}s linear infinite`;\n               fovGroup.style.transformOrigin = \"0 0\";\n             }\n             \n             if (fovAnimationType !== \"rings\") {\n               fovGroup.appendChild(styleEl);\n               const elapsedSeconds = (Date.now() - FOV_ANIMATION_START) / 1000;\n               const animationOffset = elapsedSeconds % fovSpeed;\n               fovGroup.style.animationDelay = `-${animationOffset}s`;\n             }\n           }\n           \n           g.appendChild(fovGroup);\n         }\n        g.append(hitArea, shapeEl, label);\n        if (NODE_DATA[id].ip && NODE_DATA[id].ip !== \"0.0.0.0\") g.appendChild(sub);\n         if (NODE_DATA[id]?.locked) {\n           const lockIndicator = document.createElementNS(ns, \"text\");\n           lockIndicator.textContent = \"🔒\";\n           lockIndicator.setAttribute(\"x\", r * 0.7);\n           lockIndicator.setAttribute(\"y\", -r * 0.7);\n           lockIndicator.style.fontSize = (r * 0.3) + \"px\";\n           lockIndicator.style.pointerEvents = \"none\";\n           lockIndicator.classList.add(\"lock-indicator\");\n           g.appendChild(lockIndicator);\n         }\n         if (NODE_DATA[id]?.groupId) {\n           const groupIndicator = document.createElementNS(ns, \"circle\");\n           groupIndicator.setAttribute(\"r\", r + 4);\n           groupIndicator.style.fill = \"none\";\n         groupIndicator.style.stroke = PAGE_STATE.groupIndicator || \"#4fd1c5\";\n           groupIndicator.style.strokeWidth = \"3\";\n           groupIndicator.style.strokeDasharray = \"5,5\";\n           groupIndicator.style.pointerEvents = \"none\";\n           groupIndicator.classList.add(\"group-indicator\");\n           g.insertBefore(groupIndicator, g.firstChild);\n         }\n         let isDragging = false;\n         let startX, startY;\n         let initialPositions = {};\n         let longPressTimer = null;\n         let longPressTriggered = false;\n         g.addEventListener(\"contextmenu\", (e) => {\n          e.preventDefault();\n          e.stopPropagation();\n          if (isViewOnly()) return;\n          if (selectedNodes.has(id)) {\n           selectedNodes.delete(id);\n          } else {\n           selectedNodes.add(id);\n          }\n          updateNodeSelection();\n          return false;\n         });\n        g.addEventListener(\"touchstart\", (e) => {\n         if (NODE_DATA[id].isRack) {\n          const touch = e.touches[0];\n          longPressStartX = touch.clientX;\n          longPressStartY = touch.clientY;\n          longPressTimer = setTimeout(() => {\n           longPressTriggered = true;\n           if (navigator.vibrate) {\n            navigator.vibrate(100);\n           }\n           enterRack(id);\n          }, 500);\n         }\n        }, { passive: true });\n        g.addEventListener(\"dblclick\", (e) => {\n         e.preventDefault();\n         e.stopPropagation();\n         if (NODE_DATA[id].isRack) {\n          enterRack(id);\n         }\n        });\n        let lastTapTime = 0;\n        let lastTapNode = null;\n        g.addEventListener(\"touchend\", (e) => {\n         const currentTime = new Date().getTime();\n         const tapLength = currentTime - lastTapTime;\n         if (tapLength < 300 && tapLength > 0 && lastTapNode === id) {\n          e.preventDefault();\n          e.stopPropagation();\n          if (isViewOnly()) return;\n          if (selectedNodes.has(id)) {\n           selectedNodes.delete(id);\n          } else {\n           selectedNodes.add(id);\n          }\n          updateNodeSelection();\n          if (navigator.vibrate) {\n           navigator.vibrate(50);\n          }\n          lastTapTime = 0;\n          lastTapNode = null;\n         } else {\n          lastTapTime = currentTime;\n          lastTapNode = id;\n         }\n        });\n         g.addEventListener(\"touchend\", (e) => {\n          if (longPressTimer) {\n           clearTimeout(longPressTimer);\n           longPressTimer = null;\n          }\n          if (longPressTriggered) {\n           e.preventDefault();\n           e.stopPropagation();\n           longPressTriggered = false;\n          }\n         });\n         let longPressStartX = 0;\n        let longPressStartY = 0;\n        g.addEventListener(\"touchmove\", (e) => {\n         if (longPressTimer) {\n          const touch = e.touches[0];\n          const dx = Math.abs(touch.clientX - longPressStartX);\n          const dy = Math.abs(touch.clientY - longPressStartY);\n          if (dx > 15 || dy > 15) {\n           clearTimeout(longPressTimer);\n           longPressTimer = null;\n           longPressTriggered = false;\n          }\n         }\n        }, { passive: true });\n         g.addEventListener(\"mousedown\", (e) => {\n          if (isViewOnly()) return;\n          if (e.button === 2) {\n           return;\n          }\n          if (NODE_DATA[id]?.locked) {\n           return;\n          }\n          e.preventDefault();\n          isDragging = true;\n\t\t  pushUndo(\"move nodes\");\n          const svgEl = document.getElementById(\"map\");\n          const pt = svgEl.createSVGPoint();\n          pt.x = e.clientX;\n          pt.y = e.clientY;\n          const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n          startX = svgP.x;\n          startY = svgP.y;\n          let nodesToCollect = [];\n      if (selectedNodes.has(id)) {\n      initialPositions = {};\n      const allSelectedRects = Array.from(selectedRects);\n      const allSelectedTexts = Array.from(selectedTexts);\n      const allSelectedEdges = Array.from(selectedEdges).map(eid => EDGE_DATA.list.find(e => e.id === eid)).filter(Boolean);\n      selectedNodes.forEach(nodeId => {\n      const nodePos = savedPositions[nodeId];\n      if (nodePos) {\n      initialPositions[nodeId] = { x: nodePos.x, y: nodePos.y };\n      }\n      });\n      Array.from(selectedRects).forEach(rectId => {\n        const rect = RECT_DATA.list.find(r => r.id === rectId);\n        if (rect) { rect._dragStartX = rect.x; rect._dragStartY = rect.y; }\n      });\n      Array.from(selectedTexts).forEach(textId => {\n        const text = TEXT_DATA.list.find(t => t.id === textId);\n        if (text) { text._dragStartX = text.x; text._dragStartY = text.y; }\n      });\n      Array.from(selectedEdges).forEach(edgeId => {\n        const edge = EDGE_DATA.list.find(e => e.id === edgeId);\n        if (edge && edge.points) { edge._dragStartPoints = edge.points.map(p => ({x: p.x, y: p.y})); }\n      });\n      } else {\n      initialPositions = { [id]: { x: pos.x, y: pos.y } };\n      }\n          const groupIds = new Set();\n          nodesToCollect.forEach(nodeId => {\n           const groupId = NODE_DATA[nodeId]?.groupId;\n           if (groupId) {\n             groupIds.add(groupId);\n           }\n          });\n          if (groupIds.size > 0) {\n           Object.keys(NODE_DATA).forEach(nodeId => {\n             const nodeGroupId = NODE_DATA[nodeId]?.groupId;\n             if (nodeGroupId && groupIds.has(nodeGroupId)) {\n               if (!nodesToCollect.includes(nodeId)) {\n                 nodesToCollect.push(nodeId);\n               }\n             }\n           });\n          }\n          nodesToCollect = nodesToCollect.filter(nodeId => !NODE_DATA[nodeId]?.locked);\n          if (nodesToCollect.length === 0) {\n           return;\n          }\n          initialPositions = {};\n          nodesToCollect.forEach(nodeId => {\n           const nodePos = savedPositions[nodeId];\n           if (nodePos) {\n            initialPositions[nodeId] = { x: nodePos.x, y: nodePos.y };\n           }\n          });\n          g.style.cursor = \"grabbing\";\n          hitArea.style.cursor = \"grabbing\";\n          e.stopPropagation();\n         });\n         const handleMouseMove = (e) => {\n          if (!isDragging) return;\n          e.preventDefault();\n          const svgEl = document.getElementById(\"map\");\n          const pt = svgEl.createSVGPoint();\n          pt.x = e.clientX;\n          pt.y = e.clientY;\n          const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n          const dx = svgP.x - startX;\n          const dy = svgP.y - startY;\n          const nodesToMove = selectedNodes.has(id) ? Array.from(selectedNodes) : [id];\n          nodesToMove.forEach(nodeId => {\n           if (!initialPositions[nodeId]) return;\n           const initialPos = initialPositions[nodeId];\n           let newX = initialPos.x + dx;\n           let newY = initialPos.y + dy;\n           const nodeSize = savedSizes[nodeId] || 55;\n           newX = Math.max(nodeSize + CANVAS_PADDING, Math.min(CANVAS_WIDTH - nodeSize - CANVAS_PADDING, newX));\n           newY = Math.max(nodeSize + CANVAS_PADDING, Math.min(CANVAS_HEIGHT - nodeSize - CANVAS_PADDING, newY));\n           savedPositions[nodeId] = { x: newX, y: newY };\n           positions[nodeId] = { x: newX, y: newY };\n           if (nodeId === id) {\n            pos.x = newX;\n            pos.y = newY;\n           }\n           const nodeGroup = document.querySelector(`g[data-node-id=\"${nodeId}\"]`);\n          if (nodeGroup) {\n           nodeGroup.setAttribute(\"transform\", `translate(${newX},${newY})`);\n          }\n         });\n         Array.from(selectedRects).forEach(rectId => {\n           const rect = RECT_DATA.list.find(r => r.id === rectId);\n           if (rect && rect._dragStartX !== undefined) {\n             rect.x = rect._dragStartX + dx;\n             rect.y = rect._dragStartY + dy;\n           }\n         });\n         Array.from(selectedTexts).forEach(textId => {\n           const text = TEXT_DATA.list.find(t => t.id === textId);\n           if (text && text._dragStartX !== undefined) {\n             text.x = text._dragStartX + dx;\n             text.y = text._dragStartY + dy;\n           }\n         });\n         Array.from(selectedEdges).forEach(edgeId => {\n           const edge = EDGE_DATA.list.find(e => e.id === edgeId);\n           if (edge && edge._dragStartPoints) {\n             edge.points.forEach((p, i) => {\n               p.x = edge._dragStartPoints[i].x + dx;\n               p.y = edge._dragStartPoints[i].y + dy;\n             });\n           }\n         });\n         forgeTheTopology();\n         updateMinimap();\n          document.querySelectorAll(\".edge\").forEach((edgeEl) => {\n           const fromId = edgeEl.dataset.from;\n           const toId = edgeEl.dataset.to;\n           if (nodesToMove.includes(fromId) || nodesToMove.includes(toId)) {\n            const p1 = savedPositions[fromId] || positions[fromId] || {\n             x: 600,\n             y: 350\n            };\n            const p2 = savedPositions[toId] || positions[toId] || {\n             x: 600,\n             y: 350\n            };\n            if (edgeEl.tagName === \"line\") {\n             edgeEl.setAttribute(\"x1\", p1.x);\n             edgeEl.setAttribute(\"y1\", p1.y);\n             edgeEl.setAttribute(\"x2\", p2.x);\n             edgeEl.setAttribute(\"y2\", p2.y);\n            } else if (edgeEl.tagName === \"path\") {\n             const edgeId = edgeEl.dataset.edgeId;\n             const edge = EDGE_DATA.list.find(\n              (e) => e.id === edgeId);\n             if (edge) {\n              const pairTotal = edge._pairTotal || 1;\n              const pairIndex = edge._pairIndex || 0;\n              const midX = (p1.x + p2.x) / 2;\n              const midY = (p1.y + p2.y) / 2;\n              const dx = p2.x - p1.x;\n              const dy = p2.y - p1.y;\n              const len = Math.sqrt(dx * dx + dy * dy) || 1;\n              const perpX = -dy / len;\n              const perpY = dx / len;\n              let offsetAmount = 0;\n              if (pairTotal > 1) {\n               offsetAmount = (pairIndex - (pairTotal - 1) / 2) * 40;\n              }\n              const ctrlX = midX + perpX * offsetAmount;\n              const ctrlY = midY + perpY * offsetAmount;\n              edgeEl.setAttribute(\"d\", `M ${p1.x} ${p1.y} Q ${ctrlX} ${ctrlY} ${p2.x} ${p2.y}`);\n             }\n            }\n           }\n          });\n         };\n      const handleMouseUp = () => {\n      if (isDragging) {\n      isDragging = false;\n      g.style.cursor = \"grab\";\n      hitArea.style.cursor = \"grab\";\n      if (currentView.mode === \"rack\" && NODE_DATA[id]?.assignedRack === currentView.rackId) {\n      const draggedX = savedPositions[id]?.x || pos.x;\n      const draggedY = savedPositions[id]?.y || pos.y;\n      const rackCapacity = getRackCapacity(currentView.rackId);\n      const rackUHeight = getRackUHeight(currentView.rackId);\n      const rackLeft = RACK_START_X - RACK_WIDTH / 2;\n      const rackRight = RACK_START_X + RACK_WIDTH / 2;\n      const rackTop = RACK_START_Y;\n      const rackBottom = RACK_START_Y + rackCapacity * rackUHeight;\n      if (draggedX >= rackLeft && draggedX <= rackRight && draggedY >= rackTop && draggedY <= rackBottom) {\n        let newUnit = rackCapacity - Math.round((draggedY - RACK_START_Y) / rackUHeight);\n        newUnit = Math.max(1, Math.min(newUnit, rackCapacity));\n        NODE_DATA[id].rackUnit = newUnit;\n      }\n      forgeTheTopology();\n      claimTheImmortal(id);\n      }\n      }\n      };\n         document.addEventListener(\"mousemove\", handleMouseMove);\n         document.addEventListener(\"mouseup\", handleMouseUp);\n         let touchStartTime = 0;\n         let touchStartX = 0;\n         let touchStartY = 0;\n         let touchMoved = false;\n       g.addEventListener(\"touchstart\",\n         (e) => {\n          if (NODE_DATA[id]?.locked) {\n           return;\n          }\n          e.preventDefault();\n          touchStartTime = Date.now();\n          touchMoved = false;\n          const svgEl = document.getElementById(\"map\");\n          const pt = svgEl.createSVGPoint();\n          const touch = e.touches[0];\n          pt.x = touch.clientX;\n          pt.y = touch.clientY;\n          const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n          touchStartX = svgP.x;\n          touchStartY = svgP.y;\n          startX = svgP.x;\n          startY = svgP.y;\n          let nodesToCollect = [];\n          if (selectedNodes.has(id)) {\n           nodesToCollect = Array.from(selectedNodes);\n          } else {\n           nodesToCollect = [id];\n          }\n          const groupIds = new Set();\n          nodesToCollect.forEach(nodeId => {\n           const groupId = NODE_DATA[nodeId]?.groupId;\n           if (groupId) {\n             groupIds.add(groupId);\n           }\n          });\n          if (groupIds.size > 0) {\n           Object.keys(NODE_DATA).forEach(nodeId => {\n             const nodeGroupId = NODE_DATA[nodeId]?.groupId;\n             if (nodeGroupId && groupIds.has(nodeGroupId)) {\n               if (!nodesToCollect.includes(nodeId)) {\n                 nodesToCollect.push(nodeId);\n               }\n             }\n           });\n          }\n          nodesToCollect = nodesToCollect.filter(nodeId => !NODE_DATA[nodeId]?.locked);\n          if (nodesToCollect.length === 0) {\n           return;\n          }\n          initialPositions = {};\n          nodesToCollect.forEach(nodeId => {\n           const nodePos = savedPositions[nodeId];\n           if (nodePos) {\n            initialPositions[nodeId] = { x: nodePos.x, y: nodePos.y };\n           }\n          });\n          e.stopPropagation();\n         }, {\n          passive: false\n         });\n         g.addEventListener(\"touchmove\", (e) => {\n\t\t  if (isViewOnly()) return;\n         e.preventDefault();\n         const svgEl = document.getElementById(\"map\");\n         const pt = svgEl.createSVGPoint();\n         const touch = e.touches[0];\n         pt.x = touch.clientX;\n         pt.y = touch.clientY;\n         const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n         const dx = Math.abs(svgP.x - touchStartX);\n         const dy = Math.abs(svgP.y - touchStartY);\n         if (dx > (isMobileDevice() ? 4 : 10) || dy > (isMobileDevice() ? 4 : 10)) {\n      touchMoved = true;\n      isDragging = true;\n      }\n         if (!isDragging) return;\n         const deltaX = svgP.x - startX;\n         const deltaY = svgP.y - startY;\n         const nodesToMove = selectedNodes.has(id) ? Array.from(selectedNodes) : [id];\n         nodesToMove.forEach(nodeId => {\n          if (!initialPositions[nodeId]) return;\n          const initialPos = initialPositions[nodeId];\n          let newX = initialPos.x + deltaX;\n          let newY = initialPos.y + deltaY;\n          const nodeSize = savedSizes[nodeId] || 55;\n          newX = Math.max(nodeSize + CANVAS_PADDING, Math.min(CANVAS_WIDTH - nodeSize - CANVAS_PADDING, newX));\n          newY = Math.max(nodeSize + CANVAS_PADDING, Math.min(CANVAS_HEIGHT - nodeSize - CANVAS_PADDING, newY));\n          savedPositions[nodeId] = { x: newX, y: newY };\n          positions[nodeId] = { x: newX, y: newY };\n          if (nodeId === id) {\n           pos.x = newX;\n           pos.y = newY;\n          }\n          const nodeGroup = document.querySelector(`g[data-node-id=\"${nodeId}\"]`);\n          if (nodeGroup) {\n           nodeGroup.setAttribute(\"transform\", `translate(${newX},${newY})`);\n          }\n         });\n         updateMinimap();\n         document.querySelectorAll(\".edge\").forEach((edgeEl) => {\n          const fromId = edgeEl.dataset.from;\n          const toId = edgeEl.dataset.to;\n          if (nodesToMove.includes(fromId) || nodesToMove.includes(toId)) {\n            const p1 = savedPositions[fromId] || positions[fromId] || {\n             x: 600,\n             y: 350\n            };\n            const p2 = savedPositions[toId] || positions[toId] || {\n             x: 600,\n             y: 350\n            };\n            if (edgeEl.tagName === \"line\") {\n             edgeEl.setAttribute(\"x1\", p1.x);\n             edgeEl.setAttribute(\"y1\", p1.y);\n             edgeEl.setAttribute(\"x2\", p2.x);\n             edgeEl.setAttribute(\"y2\", p2.y);\n            } else if (edgeEl.tagName === \"path\") {\n             const edgeId = edgeEl.dataset.edgeId;\n             const edge = EDGE_DATA.list.find(\n              (e) => e.id === edgeId);\n             if (edge) {\n              const pairTotal = edge._pairTotal || 1;\n              const pairIndex = edge._pairIndex || 0;\n              const midX = (p1.x + p2.x) / 2;\n              const midY = (p1.y + p2.y) / 2;\n              const dx = p2.x - p1.x;\n              const dy = p2.y - p1.y;\n              const len = Math.sqrt(dx * dx + dy * dy) || 1;\n              const perpX = -dy / len;\n              const perpY = dx / len;\n              let offsetAmount = 0;\n              if (pairTotal > 1) {\n               offsetAmount = (pairIndex - (pairTotal - 1) / 2) * 40;\n              }\n              const ctrlX = midX + perpX * offsetAmount;\n              const ctrlY = midY + perpY * offsetAmount;\n              edgeEl.setAttribute(\"d\", `M ${p1.x} ${p1.y} Q ${ctrlX} ${ctrlY} ${p2.x} ${p2.y}`);\n             }\n            }\n           }\n          });\n         }, {\n          passive: false\n         });\n      g.addEventListener(\"touchend\", (e) => {\n      const touchDuration = Date.now() - touchStartTime;\n      if (!touchMoved && touchDuration < 400) {\n       if (isViewOnly()) {\n        handleViewOnlyClick(id, 'node');\n        return;\n       }\n       claimTheImmortal(id);\n      }\n      if (isDragging) {\n      const draggedX = pos.x;\n      const draggedY = pos.y;\n      isDragging = false;\n      savedPositions[id] = {\n      x: pos.x,\n      y: pos.y\n      };\n      if (currentView.mode === \"rack\" && NODE_DATA[id]?.assignedRack === currentView.rackId) {\n      const rackCapacity = getRackCapacity(currentView.rackId);\n      const rackUHeight = getRackUHeight(currentView.rackId);\n      const rackLeft = RACK_START_X - RACK_WIDTH / 2;\n      const rackRight = RACK_START_X + RACK_WIDTH / 2;\n      const rackTop = RACK_START_Y;\n      const rackBottom = RACK_START_Y + rackCapacity * rackUHeight;\n      if (draggedX >= rackLeft && draggedX <= rackRight && draggedY >= rackTop && draggedY <= rackBottom) {\n        let newUnit = rackCapacity - Math.round((draggedY - RACK_START_Y) / rackUHeight);\n        newUnit = Math.max(1, Math.min(newUnit, rackCapacity));\n        NODE_DATA[id].rackUnit = newUnit;\n      }\n      forgeTheTopology();\n      claimTheImmortal(id);\n      }\n      }\n      touchMoved = false;\n      });\n         g.style.cursor = \"grab\";\n         g.addEventListener(\"click\", (e) => {\n          if (!isDragging) {\n           if (isViewOnly()) {\n            handleViewOnlyClick(id, 'node');\n            return;\n           }\n           claimTheImmortal(id);\n          }\n         });\n         svg.appendChild(g);\n        });\n        if (TEXT_DATA && TEXT_DATA.list) {\n         TEXT_DATA.list.forEach((textItem) => {\n          if (currentView.mode === \"rack\") return;\n          const g = document.createElementNS(ns, \"g\");\n          g.classList.add(\"text-group\");\n          g.dataset.textId = textItem.id;\n          const textRotation = textItem.rotation || 0;\n          if (textRotation !== 0) {\n            g.setAttribute(\"transform\", `rotate(${textRotation}, ${textItem.x}, ${textItem.y})`);\n          }\n          if (textItem.bgEnabled) {\n           const bgRect = document.createElementNS(ns, \"rect\");\n           bgRect.classList.add(\"text-bg\");\n           bgRect.setAttribute(\"x\", textItem.x - 5);\n           bgRect.setAttribute(\"y\", textItem.y - textItem.fontSize - 2);\n           bgRect.setAttribute(\"width\", 100);\n           bgRect.setAttribute(\"height\", textItem.fontSize + 10);\n           bgRect.style.fill = textItem.bgColor;\n           bgRect.style.opacity = \"0.7\";\n           bgRect.style.rx = \"4\";\n           g.appendChild(bgRect);\n          }\n          const textEl = document.createElementNS(ns, \"text\");\n          textEl.classList.add(\"text-element\");\n          textEl.setAttribute(\"x\", textItem.x);\n          textEl.setAttribute(\"y\", textItem.y);\n          textEl.style.fill = textItem.color;\n          textEl.style.fontSize = textItem.fontSize + \"px\";\n          textEl.style.fontWeight = textItem.fontWeight;\n          textEl.style.fontStyle = textItem.fontStyle;\n          textEl.style.textAnchor = textItem.textAlign;\n          textEl.style.textDecoration = textItem.textDecoration;\n          textEl.style.opacity = textItem.opacity;\n          textEl.style.cursor = \"move\";\n          textEl.style.userSelect = \"none\";\n          textEl.setAttribute(\"dominant-baseline\", \"middle\");\n          const lines = textItem.content.split('\\n');\n          if (lines.length === 1) {\n           textEl.textContent = textItem.content;\n          } else {\n           lines.forEach((line, i) => {\n            const tspan = document.createElementNS(ns, \"tspan\");\n            tspan.textContent = line;\n            tspan.setAttribute(\"x\", textItem.x);\n            tspan.setAttribute(\"dy\", i === 0 ? 0 : textItem.fontSize * 1.2);\n            textEl.appendChild(tspan);\n           });\n          }\n          g.appendChild(textEl);\n          if (textItem.bgEnabled) {\n           setTimeout(() => {\n            try {\n             const bbox = textEl.getBBox();\n             const bgRect = g.querySelector('.text-bg');\n             if (bgRect && bbox) {\n              bgRect.setAttribute(\"x\", bbox.x - 5);\n              bgRect.setAttribute(\"y\", bbox.y - 2);\n              bgRect.setAttribute(\"width\", bbox.width + 10);\n              bgRect.setAttribute(\"height\", bbox.height + 4);\n             }\n            } catch (e) {\n            }\n           }, 0);\n          }\n          const deleteBtn = document.createElementNS(ns, \"g\");\n          deleteBtn.classList.add(\"text-delete-btn\");\n          deleteBtn.style.cursor = \"pointer\";\n          deleteBtn.style.display = textDrawMode ? \"block\" : \"none\";\n          const deleteBg = document.createElementNS(ns, \"circle\");\n          deleteBg.setAttribute(\"cx\", textItem.x + 20);\n          deleteBg.setAttribute(\"cy\", textItem.y - textItem.fontSize);\n          deleteBg.setAttribute(\"r\", 12);\n          deleteBg.style.fill = \"#f56565\";\n          deleteBg.style.stroke = \"white\";\n          deleteBg.style.strokeWidth = \"2\";\n          const deleteX = document.createElementNS(ns, \"text\");\n          deleteX.setAttribute(\"x\", textItem.x + 20);\n          deleteX.setAttribute(\"y\", textItem.y - textItem.fontSize);\n          deleteX.setAttribute(\"text-anchor\", \"middle\");\n          deleteX.setAttribute(\"dominant-baseline\", \"middle\");\n          deleteX.style.fill = \"white\";\n          deleteX.style.fontSize = \"16px\";\n          deleteX.style.fontWeight = \"bold\";\n          deleteX.style.pointerEvents = \"none\";\n          deleteX.textContent = \"×\";\n          deleteBtn.appendChild(deleteBg);\n          deleteBtn.appendChild(deleteX);\n          deleteBtn.addEventListener(\"click\", (e) => {\n           e.stopPropagation();\n           e.preventDefault();\n           deleteText(textItem.id);\n          });\n          let isDragging = false;\n          let dragStartX, dragStartY;\n          let textStartX, textStartY;\n          textEl.addEventListener(\"mousedown\", (e) => {\n\t\t  if (isViewOnly()) return;\n      if (textDrawMode) return;\n      e.preventDefault();\n      e.stopPropagation();\n      isDragging = true;\n      dragStartX = e.clientX;\n      dragStartY = e.clientY;\n      textStartX = textItem.x;\n      textStartY = textItem.y;\n      textEl.style.cursor = \"grabbing\";\n      showTextPanel(textItem.id);\n      if (selectedTexts.has(textItem.id)) {\n      initialPositions = {};\n      selectedNodes.forEach(nodeId => {\n      const nodePos = savedPositions[nodeId];\n      if (nodePos) initialPositions[nodeId] = { x: nodePos.x, y: nodePos.y };\n      });\n      selectedRects.forEach(rectId => {\n      const r = RECT_DATA.list.find(x => x.id === rectId);\n      if (r) { r._dragStartX = r.x; r._dragStartY = r.y; }\n      });\n      selectedTexts.forEach(textId => {\n      const t = TEXT_DATA.list.find(x => x.id === textId);\n      if (t) { t._dragStartX = t.x; t._dragStartY = t.y; }\n      });\n      selectedEdges.forEach(edgeId => {\n      const ed = EDGE_DATA.list.find(x => x.id === edgeId);\n      if (ed && ed.points) { ed._dragStartPoints = ed.points.map(p => ({x: p.x, y: p.y})); }\n      });\n      }\n      });\n          const moveHandler = (e) => {\n      if (!isDragging || textDrawMode) return;\n      const svgEl = svg;\n      const pt1 = svgEl.createSVGPoint();\n      pt1.x = dragStartX;\n      pt1.y = dragStartY;\n      const svgP1 = pt1.matrixTransform(svgEl.getScreenCTM().inverse());\n      const pt2 = svgEl.createSVGPoint();\n      pt2.x = e.clientX;\n      pt2.y = e.clientY;\n      const svgP2 = pt2.matrixTransform(svgEl.getScreenCTM().inverse());\n      const dx = svgP2.x - svgP1.x;\n      const dy = svgP2.y - svgP1.y;\n      textItem.x = textStartX + dx;\n      textItem.y = textStartY + dy;\n      if (selectedTexts.has(textItem.id)) {\n      selectedNodes.forEach(nodeId => {\n      if (initialPositions[nodeId]) {\n       const newX = initialPositions[nodeId].x + dx;\n       const newY = initialPositions[nodeId].y + dy;\n       savedPositions[nodeId] = { x: newX, y: newY };\n      }\n      });\n      selectedRects.forEach(rectId => {\n      const r = RECT_DATA.list.find(x => x.id === rectId);\n      if (r && r._dragStartX !== undefined) { r.x = r._dragStartX + dx; r.y = r._dragStartY + dy; }\n      });\n      selectedTexts.forEach(textId => {\n      if (textId === textItem.id) return;\n      const t = TEXT_DATA.list.find(x => x.id === textId);\n      if (t && t._dragStartX !== undefined) { t.x = t._dragStartX + dx; t.y = t._dragStartY + dy; }\n      });\n      selectedEdges.forEach(edgeId => {\n      const ed = EDGE_DATA.list.find(x => x.id === edgeId);\n      if (ed && ed._dragStartPoints) { ed.points.forEach((p, i) => { p.x = ed._dragStartPoints[i].x + dx; p.y = ed._dragStartPoints[i].y + dy; }); }\n      });\n      }\n      forgeTheTopology();\n      };\n          const upHandler = () => {\n           if (isDragging) {\n            isDragging = false;\n            textEl.style.cursor = \"move\";\n           }\n          };\n          document.addEventListener(\"mousemove\", moveHandler);\n          document.addEventListener(\"mouseup\", upHandler);\n          let touchStartX = 0, touchStartY = 0;\n          let textTouchStartX = 0, textTouchStartY = 0;\n          textEl.addEventListener(\"touchstart\", (e) => {\n\t\t   if (isViewOnly()) return;\n           if (textDrawMode) return;\n           if (e.touches.length !== 1) return;\n           e.preventDefault();\n           e.stopPropagation();\n           const touch = e.touches[0];\n           isDragging = true;\n           touchStartX = touch.clientX;\n           touchStartY = touch.clientY;\n           textTouchStartX = textItem.x;\n           textTouchStartY = textItem.y;\n           showTextPanel(textItem.id);\n          }, { passive: false });\n          textEl.addEventListener(\"touchmove\", (e) => {\n           if (!isDragging || textDrawMode) return;\n           if (e.touches.length !== 1) return;\n           e.preventDefault();\n           const touch = e.touches[0];\n           const svgEl = svg;\n           const pt1 = svgEl.createSVGPoint();\n           pt1.x = touchStartX;\n           pt1.y = touchStartY;\n           const svgP1 = pt1.matrixTransform(svgEl.getScreenCTM().inverse());\n           const pt2 = svgEl.createSVGPoint();\n           pt2.x = touch.clientX;\n           pt2.y = touch.clientY;\n           const svgP2 = pt2.matrixTransform(svgEl.getScreenCTM().inverse());\n           const dx = svgP2.x - svgP1.x;\n           const dy = svgP2.y - svgP1.y;\n           textItem.x = textTouchStartX + dx;\n           textItem.y = textTouchStartY + dy;\n           forgeTheTopology();\n          }, { passive: false });\n          textEl.addEventListener(\"touchend\", () => {\n           if (isDragging) {\n            isDragging = false;\n           }\n          }, { passive: false });\n          textEl.addEventListener(\"contextmenu\", (e) => {\n           e.preventDefault();\n           e.stopPropagation();\n           if (selectedTexts.has(textItem.id)) {\n            selectedTexts.delete(textItem.id);\n           } else {\n            selectedTexts.add(textItem.id);\n           }\n           updateAllSelections();\n          });\n          let textLastTap = 0;\n          g.addEventListener(\"touchend\", (e) => {\n           const now = Date.now();\n           if (now - textLastTap < 300) {\n            e.preventDefault();\n            if (selectedTexts.has(textItem.id)) {\n             selectedTexts.delete(textItem.id);\n            } else {\n             selectedTexts.add(textItem.id);\n            }\n            updateAllSelections();\n            if (navigator.vibrate) navigator.vibrate(50);\n            textLastTap = 0;\n           } else {\n            textLastTap = now;\n           }\n          }, { passive: false });\n          if (textItem.groupId) {\n            const groupIndicator = document.createElementNS(ns, \"rect\");\n            groupIndicator.setAttribute(\"x\", textItem.x - 54);\n            groupIndicator.setAttribute(\"y\", textItem.y - 24);\n            groupIndicator.setAttribute(\"width\", 108);\n            groupIndicator.setAttribute(\"height\", 48);\n            groupIndicator.setAttribute(\"rx\", \"8\");\n            groupIndicator.style.fill = \"none\";\n       groupIndicator.style.stroke = PAGE_STATE.groupIndicator || \"#4fd1c5\";\n            groupIndicator.style.strokeWidth = \"3\";\n            groupIndicator.style.strokeDasharray = \"5,5\";\n            groupIndicator.style.pointerEvents = \"none\";\n            g.insertBefore(groupIndicator, g.firstChild);\n          }\n          g.appendChild(deleteBtn);\n          svg.appendChild(g);\n         });\n        }\n        if (IMAGE_DATA && IMAGE_DATA.list) {\n         IMAGE_DATA.list.forEach((img) => {\n          if (currentView.mode === \"rack\") return;\n          const g = document.createElementNS(ns, \"g\");\n          g.classList.add(\"image-group\");\n          g.dataset.imageId = img.id;\n          const imgEl = document.createElementNS(ns, \"image\");\n          imgEl.setAttribute(\"x\", img.x);\n          imgEl.setAttribute(\"y\", img.y);\n          imgEl.setAttribute(\"width\", img.width);\n          imgEl.setAttribute(\"height\", img.height);\n          imgEl.setAttribute(\"href\", img.src);\n          imgEl.setAttribute(\"preserveAspectRatio\", \"xMidYMid meet\");\n          imgEl.style.opacity = img.opacity || 1;\n          imgEl.style.cursor = \"move\";\n          if (img.border && img.border !== \"none\") {\n           const borderWidth = img.border === \"thin\" ? 1 : img.border === \"medium\" ? 2 : 4;\n           const borderRect = document.createElementNS(ns, \"rect\");\n           borderRect.setAttribute(\"x\", img.x);\n           borderRect.setAttribute(\"y\", img.y);\n           borderRect.setAttribute(\"width\", img.width);\n           borderRect.setAttribute(\"height\", img.height);\n           borderRect.style.fill = \"none\";\n           borderRect.style.stroke = \"#fff\";\n           borderRect.style.strokeWidth = borderWidth;\n           g.appendChild(borderRect);\n          }\n          if (img.shadow && img.shadow !== \"none\") {\n           const shadowSize = img.shadow === \"small\" ? 4 : img.shadow === \"medium\" ? 8 : 16;\n           imgEl.style.filter = `drop-shadow(${shadowSize/2}px ${shadowSize/2}px ${shadowSize}px rgba(0,0,0,0.5))`;\n          }\n          g.appendChild(imgEl);\n          let isDragging = false;\n          let dragStartX, dragStartY;\n          let imgStartX, imgStartY;\n          imgEl.addEventListener(\"mousedown\", (e) => {\n           if (isViewOnly()) return;\n           e.preventDefault();\n           e.stopPropagation();\n           isDragging = true;\n           dragStartX = e.clientX;\n           dragStartY = e.clientY;\n           imgStartX = img.x;\n           imgStartY = img.y;\n           imgEl.style.cursor = \"grabbing\";\n           showImagePanel(img.id);\n          });\n          const moveHandler = (e) => {\n           if (!isDragging) return;\n           const svgEl = svg;\n           const pt1 = svgEl.createSVGPoint();\n           pt1.x = dragStartX;\n           pt1.y = dragStartY;\n           const svgP1 = pt1.matrixTransform(svgEl.getScreenCTM().inverse());\n           const pt2 = svgEl.createSVGPoint();\n           pt2.x = e.clientX;\n           pt2.y = e.clientY;\n           const svgP2 = pt2.matrixTransform(svgEl.getScreenCTM().inverse());\n           const dx = svgP2.x - svgP1.x;\n           const dy = svgP2.y - svgP1.y;\n           img.x = imgStartX + dx;\n           img.y = imgStartY + dy;\n           forgeTheTopology();\n          };\n          const upHandler = () => {\n           if (isDragging) {\n            isDragging = false;\n            imgEl.style.cursor = \"move\";\n           }\n          };\n          document.addEventListener(\"mousemove\", moveHandler);\n          document.addEventListener(\"mouseup\", upHandler);\n          let touchStartX = 0, touchStartY = 0;\n          let imgTouchStartX = 0, imgTouchStartY = 0;\n          imgEl.addEventListener(\"touchstart\", (e) => {\n           if (isViewOnly()) return;\n           if (e.touches.length !== 1) return;\n           e.preventDefault();\n           e.stopPropagation();\n           const touch = e.touches[0];\n           isDragging = true;\n           touchStartX = touch.clientX;\n           touchStartY = touch.clientY;\n           imgTouchStartX = img.x;\n           imgTouchStartY = img.y;\n           showImagePanel(img.id);\n          }, { passive: false });\n          imgEl.addEventListener(\"touchmove\", (e) => {\n           if (!isDragging) return;\n           if (e.touches.length !== 1) return;\n           e.preventDefault();\n           const touch = e.touches[0];\n           const svgEl = svg;\n           const pt1 = svgEl.createSVGPoint();\n           pt1.x = touchStartX;\n           pt1.y = touchStartY;\n           const svgP1 = pt1.matrixTransform(svgEl.getScreenCTM().inverse());\n           const pt2 = svgEl.createSVGPoint();\n           pt2.x = touch.clientX;\n           pt2.y = touch.clientY;\n           const svgP2 = pt2.matrixTransform(svgEl.getScreenCTM().inverse());\n           img.x = imgTouchStartX + (svgP2.x - svgP1.x);\n           img.y = imgTouchStartY + (svgP2.y - svgP1.y);\n           forgeTheTopology();\n          }, { passive: false });\n          imgEl.addEventListener(\"touchend\", () => {\n           isDragging = false;\n          }, { passive: false });\n          svg.appendChild(g);\n         });\n        }\n        Object.keys(NODE_DATA).forEach(nodeId => {\n         updatePingIndicator(nodeId);\n        });\n        forgeTheLegend();\n\t\tupdateZoneLegend();\n        updateMinimap();\n        if (currentSearchQuery && currentSearchResults.length > 0) {\n         highlightSearchResults(currentSearchResults, true);\n        }\n        if (typeof renderMotionTrails === 'function') {\n         renderMotionTrails(svg);\n        }\n       }\n       const _forgeTheTopologyImpl = forgeTheTopology;\n       forgeTheTopology = function(immediate = false) {\n        if (immediate || forgeImmediate) {\n         forgeImmediate = false;\n         clearTimeout(forgeDebounceTimer);\n         _forgeTheTopologyImpl();\n         return;\n        }\n        clearTimeout(forgeDebounceTimer);\n        forgeDebounceTimer = setTimeout(() => {\n         _forgeTheTopologyImpl();\n        }, 16);\n       };\n       function forgeTheTopologyImmediate() {\n        forgeImmediate = true;\n        forgeTheTopology();\n       }\n       function showEditModal(title, currentValue, onSave) {\n        const modal = document.getElementById(\"edit-modal\");\n        const input = document.getElementById(\"modal-input\");\n        const titleEl = document.getElementById(\"modal-title\");\n        const saveBtn = document.getElementById(\"modal-save\");\n        const cancelBtn = document.getElementById(\"modal-cancel\");\n        titleEl.textContent = title;\n        input.value = currentValue;\n        modal.classList.add(\"active\");\n        input.focus();\n        input.select();\n        const cleanup = () => {\n         modal.classList.remove(\"active\");\n         saveBtn.removeEventListener(\"click\", handleSave);\n         cancelBtn.removeEventListener(\"click\", handleCancel);\n         input.removeEventListener(\"keypress\", handleEnter);\n         modal.removeEventListener(\"click\", bgHandler);\n        };\n        const handleSave = () => {\n         if (input.value.trim()) {\n          onSave(input.value.trim());\n         }\n         cleanup();\n        };\n        const handleCancel = () => {\n         cleanup();\n        };\n        const handleEnter = (e) => {\n         if (e.key === \"Enter\") handleSave();\n        };\n        const bgHandler = (e) => {\n         if (e.target === modal) handleCancel();\n        };\n        saveBtn.addEventListener(\"click\", handleSave);\n        cancelBtn.addEventListener(\"click\", handleCancel);\n        input.addEventListener(\"keypress\", handleEnter);\n        modal.addEventListener(\"click\", bgHandler);\n       }\n\n      let noteEditorCallback = null;\n      let noteEditorEditIndex = null;\n      function showNoteEditor(title, currentContent, onSave, editIndex = null) {\n       const modal = document.getElementById(\"note-editor-modal\");\n       const content = document.getElementById(\"note-editor-content\");\n       const titleEl = document.getElementById(\"note-editor-title\");\n       const saveBtn = document.getElementById(\"note-editor-save\");\n       const cancelBtn = document.getElementById(\"note-editor-cancel\");\n       titleEl.textContent = title || t(\"noteEditor.title\");\n       content.innerHTML = currentContent || \"<p><br></p>\";\n       noteEditorCallback = onSave;\n       noteEditorEditIndex = editIndex;\n       modal.classList.add(\"active\");\n       content.focus();\n       const range = document.createRange();\n       const sel = window.getSelection();\n       range.selectNodeContents(content);\n       range.collapse(false);\n       sel.removeAllRanges();\n       sel.addRange(range);\n      }\n      function closeNoteEditor() {\n       const modal = document.getElementById(\"note-editor-modal\");\n       modal.classList.remove(\"active\");\n       noteEditorCallback = null;\n       noteEditorEditIndex = null;\n       document.getElementById(\"note-heading-select\").value = \"\";\n      }\n      function saveNoteEditor() {\n       const content = document.getElementById(\"note-editor-content\");\n       let html = content.innerHTML.trim();\n       if (html === \"<p><br></p>\" || html === \"<br>\" || html === \"\") {\n        closeNoteEditor();\n        return;\n       }\n       if (noteEditorCallback) {\n        noteEditorCallback(html, noteEditorEditIndex);\n       }\n       closeNoteEditor();\n      }\n      function execNoteCommand(command, value = null) {\n       const content = document.getElementById(\"note-editor-content\");\n       content.focus();\n       if (command === \"code\") {\n        const sel = window.getSelection();\n        if (sel.rangeCount > 0) {\n         const range = sel.getRangeAt(0);\n         const selectedText = range.toString();\n         if (selectedText) {\n          const code = document.createElement(\"code\");\n          code.textContent = selectedText;\n          range.deleteContents();\n          range.insertNode(code);\n          range.setStartAfter(code);\n          range.collapse(true);\n          sel.removeAllRanges();\n          sel.addRange(range);\n         }\n        }\n       } else if (command === \"codeblock\") {\n        const sel = window.getSelection();\n        if (sel.rangeCount > 0) {\n         const range = sel.getRangeAt(0);\n         const selectedText = range.toString() || \"code here\";\n         const pre = document.createElement(\"pre\");\n         const code = document.createElement(\"code\");\n         code.textContent = selectedText;\n         pre.appendChild(code);\n         range.deleteContents();\n         range.insertNode(pre);\n         const p = document.createElement(\"p\");\n         p.innerHTML = \"<br>\";\n         pre.parentNode.insertBefore(p, pre.nextSibling);\n         range.setStart(p, 0);\n         range.collapse(true);\n         sel.removeAllRanges();\n         sel.addRange(range);\n        }\n       } else if (command === \"blockquote\") {\n        document.execCommand(\"formatBlock\", false, \"blockquote\");\n       } else if (command === \"createLink\") {\n        const url = prompt(\"Enter URL:\", \"https://\");\n        if (url) {\n         document.execCommand(\"createLink\", false, url);\n        }\n       } else if (command === \"heading\") {\n        if (value) {\n         document.execCommand(\"formatBlock\", false, value);\n        } else {\n         document.execCommand(\"formatBlock\", false, \"p\");\n        }\n       } else {\n        document.execCommand(command, false, value);\n       }\n      }\n      document.addEventListener(\"DOMContentLoaded\", () => {\n       const modal = document.getElementById(\"note-editor-modal\");\n       const saveBtn = document.getElementById(\"note-editor-save\");\n       const cancelBtn = document.getElementById(\"note-editor-cancel\");\n       const headingSelect = document.getElementById(\"note-heading-select\");\n       const content = document.getElementById(\"note-editor-content\");\n       saveBtn?.addEventListener(\"click\", saveNoteEditor);\n       cancelBtn?.addEventListener(\"click\", closeNoteEditor);\n       modal?.addEventListener(\"click\", (e) => {\n        if (e.target === modal) closeNoteEditor();\n       });\n\n       document.querySelectorAll(\".note-editor-toolbar:not(#secret-editor-toolbar) button[data-command]\").forEach(btn => {\n        btn.addEventListener(\"click\", (e) => {\n         e.preventDefault();\n         execNoteCommand(btn.dataset.command);\n        });\n       });\n       headingSelect?.addEventListener(\"change\", (e) => {\n        execNoteCommand(\"heading\", e.target.value);\n        e.target.value = \"\";\n       });\n       content?.addEventListener(\"keydown\", (e) => {\n        if (e.ctrlKey || e.metaKey) {\n         if (e.key === \"b\") { e.preventDefault(); execNoteCommand(\"bold\"); }\n         if (e.key === \"i\") { e.preventDefault(); execNoteCommand(\"italic\"); }\n         if (e.key === \"u\") { e.preventDefault(); execNoteCommand(\"underline\"); }\n        }\n       });\n       content?.addEventListener(\"paste\", (e) => {\n        const sel = window.getSelection();\n        if (sel.anchorNode) {\n         const parent = sel.anchorNode.parentElement;\n         if (parent && (parent.tagName === \"CODE\" || parent.tagName === \"PRE\" || parent.closest(\"pre\"))) {\n          e.preventDefault();\n          const text = e.clipboardData.getData(\"text/plain\");\n          document.execCommand(\"insertText\", false, text);\n         }\n        }\n       });\n      });\n      function showNoteViewer(content) {\n       const modal = document.getElementById(\"note-editor-modal\");\n       const titleEl = document.getElementById(\"note-editor-title\");\n       const contentEl = document.getElementById(\"note-editor-content\");\n       const toolbar = modal.querySelector(\".note-editor-toolbar\");\n       const saveBtn = document.getElementById(\"note-editor-save\");\n       const cancelBtn = document.getElementById(\"note-editor-cancel\");\n       titleEl.textContent = t(\"noteEditor.viewNote\");\n       contentEl.innerHTML = content;\n       contentEl.contentEditable = \"false\";\n       toolbar.style.display = \"none\";\n       saveBtn.style.display = \"none\";\n       cancelBtn.textContent = t(\"ui.buttons.close\");\n       modal.classList.add(\"active\");\n       const closeViewer = () => {\n        modal.classList.remove(\"active\");\n        contentEl.contentEditable = \"true\";\n        toolbar.style.display = \"\";\n        saveBtn.style.display = \"\";\n        cancelBtn.textContent = t(\"ui.buttons.cancel\");\n        cancelBtn.removeEventListener(\"click\", closeViewer);\n        modal.removeEventListener(\"click\", bgClose);\n       };\n       const bgClose = (e) => { if (e.target === modal) closeViewer(); };\n       cancelBtn.addEventListener(\"click\", closeViewer);\n       modal.addEventListener(\"click\", bgClose);\n      }\n      function renderNotesFeed(notes, containerId, elementType, elementId, onUpdate) {\n       const container = document.getElementById(containerId);\n       if (!container) return;\n       container.innerHTML = \"\";\n       container.className = \"notes-feed\";\n       if (!notes || notes.length === 0) {\n        container.innerHTML = '<div style=\"color: var(--text-soft); font-size: 13px; text-align: center; padding: 10px;\">No notes yet</div>';\n        return;\n       }\n       notes.forEach((note, idx) => {\n        const noteContent = typeof note === \"string\" ? note : (note.content || \"\");\n        const card = document.createElement(\"div\");\n        card.className = \"note-card\";\n        card.innerHTML = `\n         <div class=\"note-card-content\">${noteContent}</div>\n         <div class=\"note-card-actions\">\n          <button class=\"view-note\" data-index=\"${idx}\">View</button>\n          <button class=\"edit-note\" data-index=\"${idx}\">Edit</button>\n          <button class=\"delete-note\" data-index=\"${idx}\">Delete</button>\n         </div>\n        `;\n\n        card.querySelector(\".view-note\").addEventListener(\"click\", (e) => {\n         e.stopPropagation();\n         showNoteViewer(noteContent);\n        });\n\n        card.querySelector(\".edit-note\").addEventListener(\"click\", (e) => {\n         e.stopPropagation();\n         showNoteEditor(t(\"noteEditor.editNote\"), noteContent, (newContent, editIdx) => {\n          pushUndo(\"edit note\");\n          if (typeof notes[editIdx] === \"string\") {\n           notes[editIdx] = newContent;\n          } else {\n           notes[editIdx].content = newContent;\n          }\n          renderNotesFeed(notes, containerId, elementType, elementId, onUpdate);\n          if (onUpdate) onUpdate();\n         }, idx);\n        });\n\n        card.querySelector(\".delete-note\").addEventListener(\"click\", (e) => {\n         e.stopPropagation();\n         pushUndo(\"delete note\");\n         notes.splice(idx, 1);\n         renderNotesFeed(notes, containerId, elementType, elementId, onUpdate);\n         if (onUpdate) onUpdate();\n        });\n        container.appendChild(card);\n       });\n      }\n       function challengeTheImmortal(message, onConfirm) {\n        const modal = document.getElementById(\"confirm-modal\");\n        const messageEl = document.getElementById(\"confirm-message\");\n        const deleteBtn = document.getElementById(\"confirm-delete\");\n        const cancelBtn = document.getElementById(\"confirm-cancel\");\n        messageEl.textContent = message;\n        modal.classList.add(\"active\");\n\t    const cleanup = () => {\n         modal.classList.remove(\"active\");\n         deleteBtn.removeEventListener(\"click\", handleConfirm);\n         cancelBtn.removeEventListener(\"click\", handleCancel);\n         modal.removeEventListener(\"click\", bgHandler);\n        };\n        const handleConfirm = () => {\n         onConfirm();\n         cleanup();\n        };\n        const handleCancel = () => {\n         cleanup();\n        };\n        const bgHandler = (e) => {\n         if (e.target === modal) handleCancel();\n        };\n        deleteBtn.addEventListener(\"click\", handleConfirm);\n        cancelBtn.addEventListener(\"click\", handleCancel);\n        modal.addEventListener(\"click\", bgHandler);\n       }\n       const pageTitleEl = document.getElementById(\"page-title\");\n       if (pageTitleEl) {\n        pageTitleEl.addEventListener(\"click\", () => {\n         showEditModal(t(\"editModal.editTitle\"), PAGE_STATE.title || DEFAULT_PAGE_STATE.title,\n          (newTitle) => {\n           PAGE_STATE.title = newTitle;\n           wieldThePower();\n          });\n        });\n       }\n       function editNodeName(id) {\n        if (!NODE_DATA[id]) return;\n        showEditModal(t(\"editModal.editName\"), NODE_DATA[id].name, (newName) => {\n         if (!NODE_DATA[id]) return;\n         pushUndo(\"edit node name\");\n         NODE_DATA[id].name = newName;\n         const nodeGroup = document.querySelector(`g[data-node-id=\"${id}\"]`);\n         if (nodeGroup) {\n          const label = nodeGroup.querySelector(\".node-label\");\n          if (label) label.textContent = newName;\n         }\n         if (currentNodeId === id) {\n          document.getElementById(\"node-name\").textContent = newName;\n         }\n        });\n       }\n       function editNodeIp(id) {\n        if (!NODE_DATA[id]) return;\n        const labels = LANG.modes[PAGE_STATE.mappingMode] || LANG.modes.network;\n        showEditModal(\"Edit \" + labels.subtitle, NODE_DATA[id].ip, (newIp) => {\n         if (!NODE_DATA[id]) return;\n         pushUndo(\"edit node ip\");\n         NODE_DATA[id].ip = newIp;\n         const nodeGroup = document.querySelector(`g[data-node-id=\"${id}\"]`);\n         if (nodeGroup) {\n          const sub = nodeGroup.querySelector(\".node-sub\");\n          if (sub) sub.textContent = newIp;\n         }\n         if (currentNodeId === id) {\n          const nodeIpEl = document.getElementById(\"node-ip\");\n          if (newIp) {\n            nodeIpEl.textContent = newIp;\n            nodeIpEl.style.opacity = \"1\";\n          } else {\n            nodeIpEl.textContent = \"Click to add \" + labels.subtitle;\n            nodeIpEl.style.opacity = \"0.5\";\n          }\n         }\n        });\n       }\n       function claimTheImmortal(id) {\n\t   if (isViewOnly()) return;\n        if (!NODE_DATA[id]) return;\n        currentNodeId = id;\n        currentEdgeId = null;\n        currentRectId = null;\n        currentTextId = null;\n        const data = NODE_DATA[id];\n        document.querySelectorAll(\".node-group\").forEach((n) => {\n         n.classList.toggle(\"active\", n.dataset.nodeId === id);\n        });\n        document.querySelectorAll(\".edge\").forEach((e) => {\n        const active = e.dataset.from === id || e.dataset.to === id;\n        e.classList.toggle(\"active\", active);\n       });\n       document.querySelectorAll(\".rect-group\").forEach((r) => r.classList.remove(\"active\"));\n       document.querySelectorAll(\".text-element\").forEach((t) => t.classList.remove(\"active\"));\n       document.getElementById(\"node-panel\").style.display = \"block\";\n        document.getElementById(\"edge-panel\").style.display = \"none\";\n        document.getElementById(\"text-panel\").style.display = \"none\";\n        const toolbar = document.getElementById(\"topology-toolbar\");\n        if (!topologyToolbarCollapsed) {\n         toolbar.style.display = \"flex\";\n        }\n        updateTopologyToolbarVisibility();\n        document.getElementById(\"node-name\").textContent = data.name;\n        const nodeIpEl2 = document.getElementById(\"node-ip\");\n        const modeLabels2 = LANG.modes[PAGE_STATE.mappingMode] || LANG.modes.network;\n        if (data.ip) {\n          nodeIpEl2.textContent = data.ip;\n          nodeIpEl2.style.opacity = \"1\";\n        } else {\n          nodeIpEl2.textContent = \"Click to add \" + modeLabels2.subtitle;\n          nodeIpEl2.style.opacity = \"0.5\";\n        }\n        const fovSection = document.getElementById(\"fov-section\");\n        if (fovSection) {\n            if (hasCoverageZone(data.shape)) {\n              fovSection.style.display = \"block\";\n              const defaults = getCoverageDefaults(data.shape);\n              document.getElementById(\"fov-enabled\").checked = data.fovEnabled || false;\n              document.getElementById(\"fov-angle\").value = data.fovAngle || defaults.angle;\n              document.getElementById(\"fov-angle-value\").textContent = (data.fovAngle || defaults.angle) + \"°\";\n              document.getElementById(\"fov-distance\").value = data.fovDistance || defaults.distance;\n              document.getElementById(\"fov-distance-value\").textContent = data.fovDistance || defaults.distance;\n              document.getElementById(\"fov-inner-radius\").value = data.fovInnerRadius || 0;\n              document.getElementById(\"fov-inner-radius-value\").textContent = data.fovInnerRadius || 0;\n              document.getElementById(\"fov-rotation\").value = data.fovRotation || 0;\n              document.getElementById(\"fov-rotation-value\").textContent = (data.fovRotation || 0) + \"°\";\n              document.getElementById(\"fov-color\").value = data.fovColor || \"#f59e0b\";\n              document.getElementById(\"fov-opacity\").value = data.fovOpacity || 20;\n              document.getElementById(\"fov-opacity-value\").textContent = (data.fovOpacity || 20) + \"%\";\n              document.getElementById(\"fov-gradient\").checked = data.fovGradient || false;\n              document.getElementById(\"fov-border-color\").value = data.fovBorderColor || \"#f59e0b\";\n              document.getElementById(\"fov-border-width\").value = data.fovBorderWidth !== undefined ? data.fovBorderWidth : 2;\n              document.getElementById(\"fov-border-width-value\").textContent = data.fovBorderWidth !== undefined ? data.fovBorderWidth : 2;\n              document.getElementById(\"fov-border-style\").value = data.fovBorderStyle || \"solid\";\n              document.getElementById(\"fov-border-opacity\").value = data.fovBorderOpacity !== undefined ? data.fovBorderOpacity : 100;\n              document.getElementById(\"fov-border-opacity-value\").textContent = (data.fovBorderOpacity !== undefined ? data.fovBorderOpacity : 100) + \"%\";\n              document.getElementById(\"fov-label\").value = data.fovLabel || \"\";\n              document.getElementById(\"fov-label-position\").value = data.fovLabelPosition || \"center\";\n              document.getElementById(\"fov-label-size\").value = data.fovLabelSize || 14;\n              document.getElementById(\"fov-label-size-value\").textContent = (data.fovLabelSize || 14) + \"px\";\n              document.getElementById(\"fov-label-color\").value = data.fovLabelColor || \"#ffffff\";\n              document.getElementById(\"fov-label-bold\").checked = data.fovLabelBold || false;\n              document.getElementById(\"fov-label-bg\").checked = data.fovLabelBg || false;\n              document.getElementById(\"fov-label-bg-color\").value = data.fovLabelBgColor || \"#000000\";\n              document.getElementById(\"fov-label-offset-x\").value = data.fovLabelOffsetX || 0;\n              document.getElementById(\"fov-label-offset-x-value\").textContent = data.fovLabelOffsetX || 0;\n              document.getElementById(\"fov-label-offset-y\").value = data.fovLabelOffsetY || 0;\n              document.getElementById(\"fov-label-offset-y-value\").textContent = data.fovLabelOffsetY || 0;\n              document.getElementById(\"fov-animate\").checked = data.fovAnimate || false;\n              document.getElementById(\"fov-animation-type\").value = data.fovAnimationType || defaults.animationType;\n              document.getElementById(\"fov-sweep\").value = data.fovSweep || 120;\n              document.getElementById(\"fov-sweep-value\").textContent = (data.fovSweep || 120) + \"°\";\n              document.getElementById(\"fov-speed\").value = data.fovSpeed || 4;\n              document.getElementById(\"fov-speed-value\").textContent = (data.fovSpeed || 4) + \"s\";\n            } else {\n              fovSection.style.display = \"none\";\n            }\n          }\n        document.getElementById(\"node-mac\").textContent = data.mac || \"--\";\n        document.getElementById(\"node-rack\").textContent = data.rackUnit || \"--\";\n        document.getElementById(\"node-uheight\").textContent = (data.uHeight || \"1\") + \"U\";\n        document.getElementById(\"node-role\").textContent = data.role;\n        populateRackDropdown();\n        populateHostedOnDropdown();\n        const assignedRackSelect = document.getElementById(\"node-assigned-rack\");\n        if (assignedRackSelect) {\n         assignedRackSelect.value = data.assignedRack || \"\";\n        }\n        const hostedOnSelect = document.getElementById(\"node-hosted-on\");\n        if (hostedOnSelect) {\n         hostedOnSelect.value = data.hostedOn || \"\";\n        }\n        const rackCapacitySelect = document.getElementById(\"node-rack-capacity\");\n        if (rackCapacitySelect) {\n         rackCapacitySelect.value = data.rackCapacity || \"42\";\n        }\n        const isRack = data.isRack === true;\n        const isAssignedToRack = !!data.assignedRack;\n        const assignedRackRow = document.getElementById(\"assigned-rack-row\");\n        const hostedOnRow = document.getElementById(\"hosted-on-row\");\n        const rackCapacityRow = document.getElementById(\"rack-capacity-row\");\n        const uheightRow = document.getElementById(\"uheight-row\");\n        if (assignedRackRow) assignedRackRow.style.display = isRack ? \"none\" : \"flex\";\n        if (hostedOnRow) hostedOnRow.style.display = isRack ? \"none\" : (isAssignedToRack ? \"flex\" : \"none\");\n        if (rackCapacityRow) rackCapacityRow.style.display = isRack ? \"flex\" : \"none\";\n        if (uheightRow) uheightRow.style.display = isAssignedToRack ? \"flex\" : \"none\";\n\t\tconst delBtn = document.getElementById(\"delete-node-btn\");\n        if (delBtn) delBtn.textContent = isRack ? \"Delete Rack\" : \"Delete Node\";\n\t\tconst rackContentsSection = document.getElementById(\"rack-contents-section\");\n        const rackContentsList = document.getElementById(\"rack-contents-list\");\n        const rackContentsCount = document.getElementById(\"rack-contents-count\");\n        if (rackContentsSection && rackContentsList) {\n        if (isRack) {\n         const nodesInRack = Object.entries(NODE_DATA).filter(([nid, n]) => n.assignedRack === id);\n         rackContentsCount.textContent = nodesInRack.length;\n         if (nodesInRack.length > 0) {\n          rackContentsList.innerHTML = '';\n          nodesInRack.forEach(([nid, n]) => {\n           const div = document.createElement('div');\n           div.style.cssText = 'padding: 8px 4px; border-bottom: 1px solid var(--edge-main); cursor: pointer;';\n           div.onclick = () => claimTheImmortal(nid);\n           const nameSpan = document.createElement('span');\n           nameSpan.style.color = 'var(--text-main)';\n           nameSpan.textContent = n.name;\n           div.appendChild(nameSpan);\n           (n.tags || []).forEach(t => {\n            const tagSpan = document.createElement('span');\n            tagSpan.style.cssText = 'background: var(--accent); color: var(--bg); padding: 2px 6px; border-radius: 4px; font-size: 11px; margin-left: 4px;';\n            tagSpan.textContent = t;\n            div.appendChild(tagSpan);\n           });\n           rackContentsList.appendChild(div);\n          });\n         } else {\n          rackContentsList.innerHTML = '<div style=\"color: var(--text-soft); font-style: italic; padding: 8px 4px;\">' + t(\"emptyStates.noNodesAssigned\") + '</div>';\n         }\n         rackContentsSection.style.display = \"block\";\n         } else {\n         rackContentsSection.style.display = \"none\";\n         }\n        }\n       const connectionsSection = document.getElementById(\"node-connections-section\");\n       const connectionsList = document.getElementById(\"node-connections-list\");\n       const connectionsCount = document.getElementById(\"node-connections-count\");\n       if (connectionsSection && connectionsList && connectionsCount) {\n        const connectedEdges = (EDGE_DATA.list || []).filter(e => e.from === id || e.to === id);\n        connectionsCount.textContent = connectedEdges.length;\n        if (connectedEdges.length > 0) {\n         connectionsList.innerHTML = '';\n         connectedEdges.forEach(e => {\n          const isFrom = e.from === id;\n          const localPort = isFrom ? e.fromPort : e.toPort;\n          const remoteNodeId = isFrom ? e.to : e.from;\n          const remotePort = isFrom ? e.toPort : e.fromPort;\n          const remoteName = NODE_DATA[remoteNodeId]?.name || remoteNodeId;\n          const div = document.createElement('div');\n          div.style.cssText = 'padding: 6px 0; border-bottom: 1px solid var(--edge-main); display: flex; align-items: center; font-size: 13px;';\n          const localSpan = document.createElement('span');\n          localSpan.style.cssText = localPort ? 'color: var(--accent); font-family: monospace; cursor: pointer;' : 'color: var(--text-soft); cursor: pointer;';\n          localSpan.textContent = localPort || '-';\n          localSpan.title = localPort ? 'Click to view connection' : 'Click to set port';\n          localSpan.onclick = async () => { if (localPort) { selectTheConnection(e.id); } else { const nodeName = NODE_DATA[id]?.name || id; const newVal = await showPrompt(t(\"dialogs.enterPort\", { nodeName: nodeName }), ''); if (newVal !== null && newVal !== '') { const isDupe = (EDGE_DATA.list || []).some(x => x.id !== e.id && ((x.from === id && x.fromPort === newVal) || (x.to === id && x.toPort === newVal))); if (isDupe && !await showConfirm(t(\"dialogs.portAlreadyUsed\", { port: newVal, nodeName: nodeName }))) return; if (isFrom) e.fromPort = newVal; else e.toPort = newVal; claimTheImmortal(id); } } };\n          const arrow = document.createElement('span');\n          arrow.style.cssText = 'color: var(--text-soft); margin: 0 8px;';\n          arrow.textContent = '↔';\n          const remoteSpan = document.createElement('span');\n          remoteSpan.style.cssText = 'color: var(--text-main); cursor: pointer;';\n          remoteSpan.textContent = remoteName;\n          remoteSpan.title = 'Click to view connection';\n          remoteSpan.onclick = () => { claimTheImmortal(remoteNodeId); focusOnSelected(); };\n          const remotePortSpan = document.createElement('span');\n          remotePortSpan.style.cssText = remotePort ? 'color: var(--accent); font-family: monospace; margin-left: 6px; cursor: pointer;' : 'color: var(--text-soft); margin-left: 6px; cursor: pointer;';\n          remotePortSpan.textContent = '(' + (remotePort || '-') + ')';\n          remotePortSpan.title = remotePort ? 'Click to view connection' : 'Click to set port';\n          remotePortSpan.onclick = async () => { if (remotePort) { selectTheConnection(e.id); } else { const newVal = await showPrompt(t(\"dialogs.enterPort\", { nodeName: remoteName }), ''); if (newVal !== null && newVal !== '') { const isDupe = (EDGE_DATA.list || []).some(x => x.id !== e.id && ((x.from === remoteNodeId && x.fromPort === newVal) || (x.to === remoteNodeId && x.toPort === newVal))); if (isDupe && !await showConfirm(t(\"dialogs.portAlreadyUsed\", { port: newVal, nodeName: remoteName }))) return; if (isFrom) e.toPort = newVal; else e.fromPort = newVal; claimTheImmortal(id); } } };\n          div.appendChild(localSpan);\n          div.appendChild(arrow);\n          div.appendChild(remoteSpan);\n          div.appendChild(remotePortSpan);\n          connectionsList.appendChild(div);\n         });\n         connectionsSection.style.display = \"block\";\n        } else {\n         connectionsList.innerHTML = '<div style=\"color: var(--text-soft); font-style: italic;\">' + t(\"emptyStates.noConnections\") + '</div>';\n         connectionsSection.style.display = \"block\";\n        }\n       }\n       document.getElementById(\"node-name\").onclick = () => editNodeName(id);\n        document.getElementById(\"node-ip\").onclick = () => editNodeIp(id);\n        document.getElementById(\"node-mac\").onclick = () => editNodeMac(id);\n        document.getElementById(\"node-rack\").onclick = () => editNodeRack(id);\n        document.getElementById(\"node-uheight\").onclick = () => editNodeUHeight(id);\n        document.getElementById(\"fov-enabled\").onchange = function() {\n  if (!currentNodeId) return;\n  pushUndo(\"toggle coverage zone\");\n  NODE_DATA[currentNodeId].fovEnabled = this.checked;\n  if (this.checked && !NODE_DATA[currentNodeId].fovColor) {\n    NODE_DATA[currentNodeId].fovColor = document.getElementById(\"fov-color\").value || \"#f59e0b\";\n  }\n  updateFovCone(currentNodeId);\n  updateZoneLegend();\n};\n        document.getElementById(\"fov-angle\").oninput = function() {\n          document.getElementById(\"fov-angle-value\").textContent = this.value + \"°\";\n          if (!currentNodeId) return;\n          NODE_DATA[currentNodeId].fovAngle = parseInt(this.value);\n          updateFovCone(currentNodeId);\n        };\n        document.getElementById(\"fov-distance\").oninput = function() {\n          document.getElementById(\"fov-distance-value\").textContent = this.value;\n          if (!currentNodeId) return;\n          NODE_DATA[currentNodeId].fovDistance = parseInt(this.value);\n          updateFovCone(currentNodeId);\n        };\n        document.getElementById(\"fov-rotation\").oninput = function() {\n          document.getElementById(\"fov-rotation-value\").textContent = this.value + \"°\";\n          if (!currentNodeId) return;\n          NODE_DATA[currentNodeId].fovRotation = parseInt(this.value);\n          updateFovCone(currentNodeId);\n        };\n        document.getElementById(\"fov-color\").oninput = function() {\n          if (!currentNodeId) return;\n          NODE_DATA[currentNodeId].fovColor = this.value;\n          updateFovCone(currentNodeId);\n          updateZoneLegend();\n        };\n        document.getElementById(\"fov-animate\").onchange = function() {\n          if (!currentNodeId) return;\n          pushUndo(\"toggle fov animation\");\n          NODE_DATA[currentNodeId].fovAnimate = this.checked;\n          updateFovCone(currentNodeId);\n        };\n        document.getElementById(\"fov-sweep\").oninput = function() {\n          document.getElementById(\"fov-sweep-value\").textContent = this.value + \"°\";\n          if (!currentNodeId) return;\n          NODE_DATA[currentNodeId].fovSweep = parseInt(this.value);\n          updateFovCone(currentNodeId);\n        };\n        document.getElementById(\"fov-speed\").oninput = function() {\n          document.getElementById(\"fov-speed-value\").textContent = this.value + \"s\";\n          if (!currentNodeId) return;\n          NODE_DATA[currentNodeId].fovSpeed = parseInt(this.value);\n          updateFovCone(currentNodeId);\n        };\n        document.getElementById(\"fov-inner-radius\").oninput = function() {\n          document.getElementById(\"fov-inner-radius-value\").textContent = this.value;\n          if (!currentNodeId) return;\n          NODE_DATA[currentNodeId].fovInnerRadius = parseInt(this.value);\n          updateFovCone(currentNodeId);\n        };\n        document.getElementById(\"fov-opacity\").oninput = function() {\n          document.getElementById(\"fov-opacity-value\").textContent = this.value + \"%\";\n          if (!currentNodeId) return;\n          NODE_DATA[currentNodeId].fovOpacity = parseInt(this.value);\n          updateFovCone(currentNodeId);\n        };\n        document.getElementById(\"fov-gradient\").onchange = function() {\n          if (!currentNodeId) return;\n          NODE_DATA[currentNodeId].fovGradient = this.checked;\n          updateFovCone(currentNodeId);\n        };\n        document.getElementById(\"fov-border-color\").oninput = function() {\n          if (!currentNodeId) return;\n          NODE_DATA[currentNodeId].fovBorderColor = this.value;\n          updateFovCone(currentNodeId);\n        };\n        document.getElementById(\"fov-border-width\").oninput = function() {\n          document.getElementById(\"fov-border-width-value\").textContent = this.value;\n          if (!currentNodeId) return;\n          NODE_DATA[currentNodeId].fovBorderWidth = parseInt(this.value);\n          updateFovCone(currentNodeId);\n        };\n        document.getElementById(\"fov-border-style\").onchange = function() {\n          if (!currentNodeId) return;\n          NODE_DATA[currentNodeId].fovBorderStyle = this.value;\n          updateFovCone(currentNodeId);\n        };\n        document.getElementById(\"fov-border-opacity\").oninput = function() {\n          document.getElementById(\"fov-border-opacity-value\").textContent = this.value + \"%\";\n          if (!currentNodeId) return;\n          NODE_DATA[currentNodeId].fovBorderOpacity = parseInt(this.value);\n          updateFovCone(currentNodeId);\n        };\n        document.getElementById(\"fov-label\").oninput = function() {\n          if (!currentNodeId) return;\n          NODE_DATA[currentNodeId].fovLabel = this.value;\n          updateFovCone(currentNodeId);\n        };\n        document.getElementById(\"fov-label-position\").onchange = function() {\n          if (!currentNodeId) return;\n          NODE_DATA[currentNodeId].fovLabelPosition = this.value;\n          updateFovCone(currentNodeId);\n        };\n        document.getElementById(\"fov-label-size\").oninput = function() {\n          document.getElementById(\"fov-label-size-value\").textContent = this.value + \"px\";\n          if (!currentNodeId) return;\n          NODE_DATA[currentNodeId].fovLabelSize = parseInt(this.value);\n          updateFovCone(currentNodeId);\n        };\n        document.getElementById(\"fov-label-color\").oninput = function() {\n          if (!currentNodeId) return;\n          NODE_DATA[currentNodeId].fovLabelColor = this.value;\n          updateFovCone(currentNodeId);\n        };\n        document.getElementById(\"fov-label-bold\").onchange = function() {\n          if (!currentNodeId) return;\n          NODE_DATA[currentNodeId].fovLabelBold = this.checked;\n          updateFovCone(currentNodeId);\n        };\n        document.getElementById(\"fov-label-bg\").onchange = function() {\n          if (!currentNodeId) return;\n          NODE_DATA[currentNodeId].fovLabelBg = this.checked;\n          updateFovCone(currentNodeId);\n        };\n        document.getElementById(\"fov-label-bg-color\").oninput = function() {\n          if (!currentNodeId) return;\n          NODE_DATA[currentNodeId].fovLabelBgColor = this.value;\n          updateFovCone(currentNodeId);\n        };\n        document.getElementById(\"fov-label-offset-x\").oninput = function() {\n          document.getElementById(\"fov-label-offset-x-value\").textContent = this.value;\n          if (!currentNodeId) return;\n          NODE_DATA[currentNodeId].fovLabelOffsetX = parseInt(this.value);\n          updateFovCone(currentNodeId);\n        };\n        document.getElementById(\"fov-label-offset-y\").oninput = function() {\n          document.getElementById(\"fov-label-offset-y-value\").textContent = this.value;\n          if (!currentNodeId) return;\n          NODE_DATA[currentNodeId].fovLabelOffsetY = parseInt(this.value);\n          updateFovCone(currentNodeId);\n        };\n        document.getElementById(\"fov-animation-type\").onchange = function() {\n          if (!currentNodeId) return;\n          NODE_DATA[currentNodeId].fovAnimationType = this.value;\n          updateFovCone(currentNodeId);\n        };\n        document.getElementById(\"fov-preset\").addEventListener(\"change\", function() {\n          if (this.value) {\n            applyZonePreset(this.value);\n            this.value = \"\";\n          }\n        });\n        document.getElementById(\"fov-save-preset\").addEventListener(\"click\", saveCustomZonePreset);\n        document.getElementById(\"fov-copy-style\").addEventListener(\"click\", function() {\n          if (currentNodeId && copyZoneStyle(currentNodeId)) {\n            showAlert(t(\"dialogs.zoneStyleCopiedShort\"));\n          }\n        });\n        document.getElementById(\"fov-paste-style\").addEventListener(\"click\", function() {\n          if (currentNodeId && pasteZoneStyle(currentNodeId)) {\n            claimTheImmortal(currentNodeId);\n          }\n        });\n        document.getElementById(\"bulk-zone-copy\").addEventListener(\"click\", bulkCopyZoneStyle);\n        document.getElementById(\"bulk-zone-paste\").addEventListener(\"click\", bulkPasteZoneStyle);\n        document.getElementById(\"bulk-zone-toggle\").addEventListener(\"click\", bulkToggleZones);\n        document.getElementById(\"bulk-zone-copy-mobile\").addEventListener(\"click\", bulkCopyZoneStyle);\n        document.getElementById(\"bulk-zone-paste-mobile\").addEventListener(\"click\", bulkPasteZoneStyle);\n        document.getElementById(\"bulk-zone-toggle-mobile\").addEventListener(\"click\", bulkToggleZones);\n        const currentSize = savedSizes[id] || getDefaultSize();\n        document.getElementById(\"size-slider\").value = currentSize;\n        const currentRotation = NODE_DATA[id].rotation || 0;\n        document.getElementById(\"rotation-slider\").value = Math.max(-360, Math.min(360, currentRotation));\n        document.getElementById(\"rotation-value\").value = currentRotation;\n        const styleEntry = savedStyles[id] || {};\n        const resolvedStyles = resolveStylesEntry(styleEntry);\n        const scopeKey = currentStyleScope || \"all\";\n        const flatProps = [\"circleColor\", \"circleBorder\", \"titleColor\", \"titleFont\", \"titleSize\", \"subColor\", \"subFont\", \"subSize\", ];\n        const isFlat = flatProps.some((p) => Object.prototype.hasOwnProperty.call(styleEntry, p));\n        const scopedStyles = isFlat ? styleEntry : styleEntry[scopeKey] || {};\n        const circleColorInput = document.getElementById(\"circle-color\");\n        const titleColorInput = document.getElementById(\"title-color\");\n        const titleFontSelect = document.getElementById(\"title-font\");\n        const titleSizeInput = document.getElementById(\"title-size\");\n        const subColorInput = document.getElementById(\"sub-color\");\n        const subFontSelect = document.getElementById(\"sub-font\");\n        const subSizeInput = document.getElementById(\"sub-size\");\n        const shapeSelect = document.getElementById(\"shape-select\");\n        const scopeSelect = document.getElementById(\"style-scope\");\n        const catSelect = document.getElementById(\"shape-category-select\");\n        const nodeShape = data.shape || \"circle\";\n        const shapeCategory = findCategoryForShape(nodeShape);\n        if (catSelect) {\n          catSelect.value = shapeCategory;\n          populateShapeSelect(nodeShape);\n        }\n        circleColorInput.value = scopedStyles.circleColor || resolvedStyles.circleColor || PAGE_STATE.nodeFill || \"#1e293b\";\n       const circleBorderInput = document.getElementById(\"circle-border\");\n       circleBorderInput.value = scopedStyles.circleBorder || resolvedStyles.circleBorder || PAGE_STATE.nodeStroke || \"#475569\";\n        titleColorInput.value = scopedStyles.titleColor || resolvedStyles.titleColor || PAGE_STATE.textMain || \"#e2e8f0\";\n        titleFontSelect.value = scopedStyles.titleFont || resolvedStyles.titleFont || \"system-ui, sans-serif\";\n        titleSizeInput.value = scopedStyles.titleSize || resolvedStyles.titleSize || 18;\n        subColorInput.value = scopedStyles.subColor || resolvedStyles.subColor || PAGE_STATE.textSoft || \"#94a3b8\";\n        subFontSelect.value = scopedStyles.subFont || resolvedStyles.subFont || \"system-ui, sans-serif\";\n        subSizeInput.value = scopedStyles.subSize || resolvedStyles.subSize || 13;\n        const hasWebIcon = resolvedStyles.icon && resolvedStyles.icon.library;\n        const customOpt = shapeSelect.querySelector('option[value=\"custom-icon\"]');\n        if (hasWebIcon) {\n         if (!customOpt) {\n          const opt = document.createElement('option');\n          opt.value = 'custom-icon';\n          opt.textContent = 'Custom Icon';\n          shapeSelect.insertBefore(opt, shapeSelect.firstChild);\n         }\n         shapeSelect.value = 'custom-icon';\n        } else {\n         if (customOpt) customOpt.remove();\n        }\n        const layerSelect = document.getElementById(\"node-layer\");\n        if (layerSelect) {\n         layerSelect.value = data.layer || \"layer1\";\n        }\n        const trailToggle = document.getElementById(\"node-trail-enabled\");\n        if (trailToggle) {\n         trailToggle.checked = data.trailEnabled !== false;\n        }\n        scopeSelect.value = currentStyleScope || \"all\";\n        document.getElementById(\"title-offset-y\").value = scopedStyles.titleOffsetY || resolvedStyles.titleOffsetY || 0;\n        document.getElementById(\"title-offset-x\").value = scopedStyles.titleOffsetX || resolvedStyles.titleOffsetX || 0;\n        document.getElementById(\"sub-offset-y\").value = scopedStyles.subOffsetY || resolvedStyles.subOffsetY || 0;\n        document.getElementById(\"sub-offset-x\").value = scopedStyles.subOffsetX || resolvedStyles.subOffsetX || 0;\n      const pingOffsetXInput = document.getElementById(\"ping-offset-x\");\n      const pingOffsetYInput = document.getElementById(\"ping-offset-y\");\n      if (pingOffsetXInput && pingOffsetYInput) {\n      pingOffsetXInput.value =\n       (scopedStyles.pingOffsetX !== undefined\n         ? scopedStyles.pingOffsetX\n         : (resolvedStyles.pingOffsetX !== undefined\n             ? resolvedStyles.pingOffsetX\n             : 0));\n      pingOffsetYInput.value =\n       (scopedStyles.pingOffsetY !== undefined\n         ? scopedStyles.pingOffsetY\n         : (resolvedStyles.pingOffsetY !== undefined\n             ? resolvedStyles.pingOffsetY\n             : 0));\n      }\n        const tagEl = document.getElementById(\"node-tags\");\n        tagEl.innerHTML = \"\";\n        data.tags.forEach((tag, i) => {\n         const b = document.createElement(\"span\");\n         b.className = \"badge\";\n         const isIconTag = typeof tag === 'object' && tag.type === 'icon';\n         if (!isIconTag && typeof tag === 'string' && tag.toLowerCase().includes(\"wg\")) b.classList.add(\"wg\");\n         b.style.cursor = \"pointer\";\n         b.style.position = \"relative\";\n         const tagContent = document.createElement(\"span\");\n         if (isIconTag) {\n          b.classList.add(\"icon-badge\");\n          IconLibrary.getIcon(tag.library, tag.name).then(svgText => {\n           if (svgText) {\n            const parser = new DOMParser();\n            const doc = parser.parseFromString(svgText, 'image/svg+xml');\n            const svgEl = doc.querySelector('svg');\n            if (svgEl) {\n             svgEl.setAttribute('width', '16');\n             svgEl.setAttribute('height', '16');\n             tagContent.innerHTML = '';\n             tagContent.appendChild(svgEl);\n             const nameSpan = document.createElement('span');\n             nameSpan.textContent = tag.name;\n             nameSpan.style.marginLeft = '4px';\n             tagContent.appendChild(nameSpan);\n            }\n           }\n          });\n         } else {\n          tagContent.textContent = tag;\n          tagContent.addEventListener(\"click\", (e) => {\n           e.stopPropagation();\n           showEditModal(t(\"editModal.editTag\"), tag, (newTag) => {\n            if (newTag) {\n             pushUndo(\"edit tag\");\n             data.tags[i] = newTag;\n             claimTheImmortal(id);\n            }\n           });\n          });\n         }\n         const deleteTag = document.createElement(\"span\");\n         deleteTag.textContent = \" ✕\";\n         deleteTag.style.opacity = \"0.6\";\n         deleteTag.style.marginLeft = \"4px\";\n         deleteTag.style.fontSize = \"10px\";\n         deleteTag.addEventListener(\"click\", (e) => {\n          e.stopPropagation();\n          pushUndo(\"delete tag\");\n          data.tags.splice(i, 1);\n          claimTheImmortal(id);\n         });\n         b.append(tagContent, deleteTag);\n         tagEl.append(b);\n        });\n        const addTagBtn = document.createElement(\"span\");\n        addTagBtn.className = \"badge\";\n        addTagBtn.style.cursor = \"pointer\";\n        addTagBtn.style.opacity = \"0.6\";\n        addTagBtn.style.borderStyle = \"dashed\";\n        addTagBtn.textContent = t(\"ui.buttons.addTag\");\n        addTagBtn.addEventListener(\"click\", () => {\n         showEditModal(t(\"editModal.addTags\"), \"\",\n          (newTagStr) => {\n           if (newTagStr) {\n            pushUndo(\"add tags\");\n            const newTags = newTagStr.split(\",\").map((t) => t.trim()).filter((t) => t);\n            newTags.forEach((t) => data.tags.push(t));\n            claimTheImmortal(id);\n           }\n          });\n        });\n        tagEl.append(addTagBtn);\n        renderNotesFeed(data.notes, \"node-notes\", \"node\", id);\n        const addLineSelect = document.getElementById(\"add-line-select\");\n      addLineSelect.innerHTML = \"\";\n      const currentRack = data.assignedRack || \"\";\n      Object.entries(NODE_DATA).forEach(([nodeId, node]) => {\n      if (nodeId !== id) {\n      const nodeRack = node.assignedRack || \"\";\n      if (currentRack === nodeRack) {\n      const opt = document.createElement(\"option\");\n      opt.value = nodeId;\n      opt.textContent = node.name;\n      addLineSelect.appendChild(opt);\n      }\n      }\n      });\n        const pingEnabled = data.ping && data.ping.enabled;\n        document.getElementById('node-pingable').checked = pingEnabled;\n        document.getElementById('node-ping-options').style.display = pingEnabled ? 'block' : 'none';\n        if (data.ping) {\n         document.getElementById('node-ping-protocol').value = data.ping.protocol || 'http';\n         document.getElementById('node-custom-url').value = data.ping.customUrl || '';\n         document.getElementById('node-ping-timeout').value = data.ping.timeout || 3000;\n         document.getElementById('node-custom-url-container').style.display =\n          data.ping.protocol === 'custom' ? 'block' : 'none';\n         updatePingStatusDisplay(id);\n        }\n       }\n       function updatePingStatusDisplay(nodeId) {\n        const data = NODE_DATA[nodeId];\n        if (!data || !data.ping) return;\n        const statusEl = document.getElementById('node-ping-status');\n        const lastCheckEl = document.getElementById('node-ping-last-check');\n        const statusColors = {\n         online: 'var(--accent)',\n         offline: 'var(--danger)',\n         checking: '#f59e0b',\n         unknown: 'var(--text-soft)'\n        };\n        const statusTexts = {\n         online: '● Online',\n         offline: '● Offline',\n         checking: '● Checking...',\n         unknown: '● Unknown'\n        };\n        const statusText = statusTexts[data.ping.status] || statusTexts.unknown;\n\t\tstatusEl.textContent = data.ping.responseTime ? `${statusText} (${data.ping.responseTime}ms)` : statusText;\n        statusEl.style.color = statusColors[data.ping.status] || statusColors.unknown;\n        if (data.ping.lastCheck) {\n         const checkTime = new Date(data.ping.lastCheck);\n         lastCheckEl.textContent = `Last checked: ${checkTime.toLocaleTimeString()}`;\n        } else {\n         lastCheckEl.textContent = 'Never checked';\n        }\n       }\n       function selectTheConnection(id) {\n\t   if (isViewOnly()) return;\n        currentEdgeId = id;\n        currentNodeId = null;\n        currentRectId = null;\n        currentTextId = null;\n\t\tif (isMobileDevice()) {\n        const currentHeight = PAGE_STATE.mobileFooterHeight || 40;\n        if (currentHeight <= 20) {\n         PAGE_STATE.mobileFooterHeight = 80;\n         const root = document.documentElement;\n         root.style.setProperty(\"--mobile-footer-height\", \"80vh\");\n         const mobileFooterHeightInput = document.getElementById(\"mobile-footer-height\");\n         const mobileFooterHeightVal = document.getElementById(\"mobile-footer-height-val\");\n         if (mobileFooterHeightInput) mobileFooterHeightInput.value = 80;\n         if (mobileFooterHeightVal) mobileFooterHeightVal.textContent = \"80%\";\n        }\n       }\n        document.getElementById(\"node-panel\").style.display = \"none\";\n        document.getElementById(\"edge-panel\").style.display = \"block\";\n        document.getElementById(\"text-panel\").style.display = \"none\";\n       document.getElementById(\"rect-panel\").style.display = \"none\";\n       document.getElementById(\"topology-toolbar\").style.display = \"none\";\n       document.querySelectorAll(\".node-group\").forEach((n) => n.classList.remove(\"active\"));\n       document.querySelectorAll(\".rect-group\").forEach((r) => r.classList.remove(\"active\"));\n       document.querySelectorAll(\".text-element\").forEach((t) => t.classList.remove(\"active\"));\n       document.querySelectorAll(\".edge\").forEach((e) => {\n        e.classList.toggle(\"active\", e.dataset.edgeId === id);\n       });\n        const edge = EDGE_DATA.list.find((e) => e.id === id);\n        if (!edge) return;\n        const directionSymbols = {\n         none: \"⇄\",\n         forward: \"→\",\n         backward: \"←\",\n         both: \"↔\",\n        };\n        const dirSymbol = directionSymbols[edge.direction] || \"⇄\";\n        let titleText = \"Custom line\";\n        if (edge.from || edge.to) {\n         const fromName = edge.from ? NODE_DATA[edge.from]?.name || edge.from : \"\";\n         const toName = edge.to ? NODE_DATA[edge.to]?.name || edge.to : \"\";\n         titleText = `${fromName || \"?\"} ${dirSymbol} ${toName || \"?\"}`;\n        }\n        document.getElementById(\"edge-title\").textContent = titleText;\n        const widthInput = document.getElementById(\"edge-width\");\n        const colorInput = document.getElementById(\"edge-color\");\n        const directionSelect = document.getElementById(\"edge-direction\");\n        const lineStyleSelect = document.getElementById(\"edge-line-style\");\n        const routingSelect = document.getElementById(\"edge-routing\");\n        widthInput.value = edge.width;\n        colorInput.value = edge.color;\n        directionSelect.value = edge.direction || \"none\";\n        lineStyleSelect.value = edge.lineStyle || \"solid\";\n        routingSelect.value = edge.routing || \"curved\";\n        const waypointsRow = document.getElementById(\"waypoints-row\");\n        const waypoints = edge.waypoints || [];\n        if (edge.type !== \"custom\" && waypoints.length > 0) {\n         waypointsRow.style.display = \"flex\";\n         document.getElementById(\"waypoints-label\").textContent = `${t(\"ui.labels.waypoints\")} (${waypoints.length}):`;\n        } else {\n         waypointsRow.style.display = \"none\";\n        }\n        document.getElementById(\"edge-animate\").checked = edge.animate === true;\n        document.getElementById(\"edge-animation-style\").value = edge.animationStyle || \"\";\n        document.getElementById(\"edge-animation-speed\").value = edge.animationSpeed || \"\";\n        const fromPortInput = document.getElementById(\"edge-from-port\");\n        const toPortInput = document.getElementById(\"edge-to-port\");\n        const portFieldsFrom = document.getElementById(\"edge-port-fields\");\n        const portFieldsTo = document.getElementById(\"edge-port-fields-to\");\n        if (edge.type === \"custom\") {\n         if (portFieldsFrom) portFieldsFrom.style.display = \"none\";\n         if (portFieldsTo) portFieldsTo.style.display = \"none\";\n        } else {\n         if (portFieldsFrom) portFieldsFrom.style.display = \"flex\";\n         if (portFieldsTo) portFieldsTo.style.display = \"flex\";\n         if (fromPortInput) {\n          fromPortInput.value = edge.fromPort || \"\";\n          fromPortInput.onchange = () => updateEdgePortLabels(id);\n         }\n         if (toPortInput) {\n          toPortInput.value = edge.toPort || \"\";\n          toPortInput.onchange = () => updateEdgePortLabels(id);\n         }\n        }\n        renderNotesFeed(edge.notes, \"edge-notes\", \"edge\", id);\n       if (edge.type === \"custom\" && Array.isArray(edge.points)) {\n        forgeTheTopology();\n       }\n      }\n       window.addEventListener(\"resize\", () => {\n        forgeTheTopology();\n        if (currentEdgeId) {\n         selectTheConnection(currentEdgeId);\n        } else if (currentNodeId && NODE_DATA[currentNodeId]) {\n         claimTheImmortal(currentNodeId);\n        } else {\n         const availableNodes = Object.keys(NODE_DATA);\n         if (availableNodes.length > 0) {\n          claimTheImmortal(availableNodes[0]);\n         }\n        }\n       });\n       (function initZoomPan() {\n       const viewport = document.getElementById(\"canvas-viewport\");\n       const svg = document.getElementById(\"map\");\n       const hint = document.getElementById(\"canvas-hint\");\n       hint.style.cursor = \"pointer\";\n       let hintDismissed = false;\n       const dismissHint = () => { hintDismissed = true; hint.classList.remove(\"visible\"); };\n       hint.addEventListener(\"click\", dismissHint);\n       hint.addEventListener(\"touchend\", (e) => { e.preventDefault(); dismissHint(); });\n       setTimeout(() => {\n        if (hintDismissed) return;\n        hint.classList.add(\"visible\");\n        setTimeout(() => { if (!hintDismissed) hint.classList.remove(\"visible\"); }, 4000);\n       }, 1000);\n        viewport.addEventListener(\"wheel\",\n         (e) => {\n          e.preventDefault();\n          const rect = viewport.getBoundingClientRect();\n          const mouseX = (e.clientX - rect.left) / rect.width;\n          const mouseY = (e.clientY - rect.top) / rect.height;\n          const delta = e.deltaY > 0 ? 0.9 : 1.1;\n          zoomTo(canvasState.zoom * delta, mouseX, mouseY);\n         }, {\n          passive: false\n         });\n        let initialPinchDistance = 0;\n        let initialPinchZoom = 1;\n        let pinchCenter = {\n         x: 0.5,\n         y: 0.5\n        };\n        let threeFingerTapStart = 0;\n        viewport.addEventListener(\"touchstart\",\n         (e) => {\n          if (e.touches.length === 3) {\n           e.preventDefault();\n           threeFingerTapStart = Date.now();\n          }\n          if (e.touches.length === 2) {\n           e.preventDefault();\n           const touch1 = e.touches[0];\n           const touch2 = e.touches[1];\n           initialPinchDistance = Math.hypot(touch2.clientX - touch1.clientX, touch2.clientY - touch1.clientY);\n           initialPinchZoom = canvasState.zoom;\n           const rect = viewport.getBoundingClientRect();\n           const centerX = (touch1.clientX + touch2.clientX) / 2;\n           const centerY = (touch1.clientY + touch2.clientY) / 2;\n           pinchCenter.x = (centerX - rect.left) / rect.width;\n           pinchCenter.y = (centerY - rect.top) / rect.height;\n          }\n         }, {\n          passive: false\n         });\n        viewport.addEventListener(\"touchend\", (e) => {\n         if (e.touches.length === 0 && threeFingerTapStart > 0) {\n          const duration = Date.now() - threeFingerTapStart;\n          if (duration < 500) {\n           e.preventDefault();\n           undo();\n           if (navigator.vibrate) navigator.vibrate([50, 30, 50]);\n          }\n          threeFingerTapStart = 0;\n         }\n        }, { passive: false });\n        viewport.addEventListener(\"touchmove\",\n         (e) => {\n          if (e.touches.length === 2) {\n           e.preventDefault();\n           const touch1 = e.touches[0];\n           const touch2 = e.touches[1];\n           const currentDistance = Math.hypot(touch2.clientX - touch1.clientX, touch2.clientY - touch1.clientY);\n           if (initialPinchDistance > 0) {\n            const scale = currentDistance / initialPinchDistance;\n            const newZoom = initialPinchZoom * scale;\n            zoomTo(newZoom, pinchCenter.x, pinchCenter.y);\n           }\n          }\n         }, {\n          passive: false\n         });\n        let panStartViewX = 0;\n        let panStartViewY = 0;\n        let lastEmptyTapTime = 0;\n        let emptyTapTimeout = null;\n        let emptyTapMoved = false;\n        let emptyTapStartX = 0;\n        let emptyTapStartY = 0;\n        viewport.addEventListener(\"touchend\", (e) => {\n          if (currentView.mode !== \"rack\") return;\n          if (e.changedTouches.length !== 1) return;\n      const isNodeOrEdge = e.target.closest(\".node-group\") || e.target.closest(\".edge-group\");\n         if (isNodeOrEdge) return;\n          if (emptyTapMoved) {\n            emptyTapMoved = false;\n            return;\n          }\n          const currentTime = new Date().getTime();\n          const tapGap = currentTime - lastEmptyTapTime;\n          if (tapGap < 300 && tapGap > 0) {\n            e.preventDefault();\n            exitRack();\n            if (navigator.vibrate) {\n              navigator.vibrate(50);\n            }\n            lastEmptyTapTime = 0;\n            if (emptyTapTimeout) {\n              clearTimeout(emptyTapTimeout);\n              emptyTapTimeout = null;\n            }\n          } else {\n            lastEmptyTapTime = currentTime;\n            if (emptyTapTimeout) clearTimeout(emptyTapTimeout);\n            emptyTapTimeout = setTimeout(() => {\n              lastEmptyTapTime = 0;\n            }, 300);\n          }\n        }, { passive: false });\n        viewport.addEventListener(\"mousedown\", (e) => {\n         if (e.target.closest(\".draw-toolbar\") || e.target.closest(\".topology-toolbar\") || e.target.closest(\".legend-container\")) {\n          return;\n         }\n         if (freeDrawMode || rectDrawMode) {\n          return;\n         }\n      const isEmptySpace = e.target === svg || e.target === viewport || e.target.tagName === \"svg\";\n      if (isEmptySpace) {\n        if (currentNodeId) {\n          currentNodeId = null;\n          document.getElementById(\"node-panel\").style.display = \"none\";\n          document.querySelectorAll(\".node-group\").forEach(n => n.classList.remove(\"active\"));\n        }\n        if (currentEdgeId) {\n          currentEdgeId = null;\n          document.getElementById(\"edge-panel\").style.display = \"none\";\n        }\n        if (currentRectId) {\n          currentRectId = null;\n          document.getElementById(\"rect-panel\").style.display = \"none\";\n        }\n        if (currentTextId) {\n          currentTextId = null;\n          document.getElementById(\"text-panel\").style.display = \"none\";\n        }\n        if (currentImageId) {\n          currentImageId = null;\n          document.getElementById(\"image-panel\").style.display = \"none\";\n        }\n        forgeTheTopology();\n        updateTopologyToolbarVisibility();\n      }\n         if (isViewOnly()) {\n         document.body.classList.remove(\"view-only-inspect\");\n         document.getElementById(\"node-panel\").style.display = \"none\";\n         document.getElementById(\"edge-panel\").style.display = \"none\";\n        }\n         if (isEmptySpace && e.shiftKey && e.button === 0) {\n         if (isViewOnly()) return;\n         e.preventDefault();\n         startSelection(e);\n         return;\n        }\n        if (isEmptySpace || e.button === 2 || e.button === 1) {\n          e.preventDefault();\n          canvasState.isPanning = true;\n          canvasState.panStartX = e.clientX;\n          canvasState.panStartY = e.clientY;\n          panStartViewX = canvasState.panX;\n          panStartViewY = canvasState.panY;\n          viewport.classList.add(\"panning\");\n         }\n        });\n        viewport.addEventListener(\"touchstart\",\n         (e) => {\n          if (e.target.closest(\".draw-toolbar\") || e.target.closest(\".topology-toolbar\") || e.target.closest(\".legend-container\")) {\n           return;\n          }\n          if (freeDrawMode || rectDrawMode) {\n           return;\n          }\n      const isEmptySpace = e.target === svg || e.target === viewport || e.target.tagName === \"svg\";\n      if (isEmptySpace) {\n        if (currentNodeId) {\n          currentNodeId = null;\n          document.getElementById(\"node-panel\").style.display = \"none\";\n          document.querySelectorAll(\".node-group\").forEach(n => n.classList.remove(\"active\"));\n        }\n        if (currentEdgeId) {\n          currentEdgeId = null;\n          document.getElementById(\"edge-panel\").style.display = \"none\";\n        }\n        if (currentRectId) {\n          currentRectId = null;\n          document.getElementById(\"rect-panel\").style.display = \"none\";\n        }\n        if (currentTextId) {\n          currentTextId = null;\n          document.getElementById(\"text-panel\").style.display = \"none\";\n        }\n        if (currentImageId) {\n          currentImageId = null;\n          document.getElementById(\"image-panel\").style.display = \"none\";\n        }\n        forgeTheTopology();\n        updateTopologyToolbarVisibility();\n      }\n          if (isEmptySpace && e.touches.length === 1) {\n           e.preventDefault();\n           emptyTapMoved = false;\n           emptyTapStartX = e.touches[0].clientX;\n           emptyTapStartY = e.touches[0].clientY;\n           canvasState.isPanning = true;\n           canvasState.panStartX = e.touches[0].clientX;\n           canvasState.panStartY = e.touches[0].clientY;\n           panStartViewX = canvasState.panX;\n           panStartViewY = canvasState.panY;\n           viewport.classList.add(\"panning\");\n          }\n         }, {\n          passive: false\n         });\n        let panRAFPending = false;\n       let lastPanEvent = null;\n       document.addEventListener(\"mousemove\", (e) => {\n        if (isSelecting) {\n         updateSelection(e);\n         return;\n        }\n        if (!canvasState.isPanning) return;\n        lastPanEvent = e;\n        if (!panRAFPending) {\n         panRAFPending = true;\n         requestAnimationFrame(() => {\n          if (lastPanEvent && canvasState.isPanning) {\n           const dx = lastPanEvent.clientX - canvasState.panStartX;\n           const dy = lastPanEvent.clientY - canvasState.panStartY;\n           const rect = viewport.getBoundingClientRect();\n           const viewWidth = CANVAS_WIDTH / canvasState.zoom;\n           const viewHeight = CANVAS_HEIGHT / canvasState.zoom;\n           const canvasDx = (dx / rect.width) * viewWidth;\n           const canvasDy = (dy / rect.height) * viewHeight;\n           canvasState.panX = panStartViewX - canvasDx;\n           canvasState.panY = panStartViewY - canvasDy;\n           constrainPan();\n           updateViewBox();\n          }\n          panRAFPending = false;\n         });\n        }\n       });\n       document.addEventListener(\"touchmove\", (e) => {\n        if (!canvasState.isPanning || !e.touches[0]) return;\n        const touchX = e.touches[0].clientX;\n        const touchY = e.touches[0].clientY;\n        const moveDx = Math.abs(touchX - emptyTapStartX);\n        const moveDy = Math.abs(touchY - emptyTapStartY);\n        if (moveDx > 15 || moveDy > 15) {\n         emptyTapMoved = true;\n        }\n        if (!panRAFPending) {\n         panRAFPending = true;\n         requestAnimationFrame(() => {\n          if (canvasState.isPanning) {\n           const dx = touchX - canvasState.panStartX;\n           const dy = touchY - canvasState.panStartY;\n           const rect = viewport.getBoundingClientRect();\n           const viewWidth = CANVAS_WIDTH / canvasState.zoom;\n           const viewHeight = CANVAS_HEIGHT / canvasState.zoom;\n           const canvasDx = (dx / rect.width) * viewWidth;\n           const canvasDy = (dy / rect.height) * viewHeight;\n           canvasState.panX = panStartViewX - canvasDx;\n           canvasState.panY = panStartViewY - canvasDy;\n           constrainPan();\n           updateViewBox();\n          }\n          panRAFPending = false;\n         });\n        }\n       });\n        document.addEventListener(\"mouseup\", () => {\n         if (isSelecting) {\n          endSelection();\n         }\n         if (canvasState.isPanning) {\n          canvasState.isPanning = false;\n          viewport.classList.remove(\"panning\");\n         }\n        });\n        document.addEventListener(\"touchend\", () => {\n         if (canvasState.isPanning) {\n          canvasState.isPanning = false;\n          viewport.classList.remove(\"panning\");\n         }\n        });\n        document.addEventListener(\"keydown\", (e) => {\n         const isEditing = document.activeElement.tagName === \"INPUT\" || document.activeElement.tagName === \"TEXTAREA\" || document.activeElement.isContentEditable;\n         if (e.code === \"Space\" && !e.repeat && !isEditing) {\n          e.preventDefault();\n          canvasState.spacePressed = true;\n          viewport.style.cursor = \"grab\";\n         }\n        });\n        document.addEventListener(\"keyup\", (e) => {\n         if (e.code === \"Space\") {\n          canvasState.spacePressed = false;\n          viewport.style.cursor = \"\";\n         }\n        });\n        document.getElementById(\"zoom-in-btn\").addEventListener(\"click\", () => {\n         zoomTo(canvasState.zoom * 1.25, 0.5, 0.5);\n        });\n        document.getElementById(\"zoom-out-btn\").addEventListener(\"click\", () => {\n         zoomTo(canvasState.zoom * 0.8, 0.5, 0.5);\n        });\n        document.getElementById(\"zoom-fit-btn\").addEventListener(\"click\", fitToContent);\n        document.getElementById(\"zoom-reset-btn\").addEventListener(\"click\", resetView);\n        const minimapContainer = document.getElementById(\"minimap-container\");\n        const minimapSvg = document.getElementById(\"minimap\");\n        let minimapDragging = false;\n        minimapContainer.addEventListener(\"mousedown\", (e) => {\n         e.preventDefault();\n         minimapDragging = true;\n         updatePanFromMinimap(e);\n        });\n        minimapContainer.addEventListener(\"touchstart\",\n         (e) => {\n          e.preventDefault();\n          minimapDragging = true;\n          updatePanFromMinimapTouch(e);\n         }, {\n          passive: false\n         });\n        document.addEventListener(\"mousemove\", (e) => {\n         if (minimapDragging) {\n          updatePanFromMinimap(e);\n         }\n        });\n        document.addEventListener(\"touchmove\", (e) => {\n         if (minimapDragging && e.touches[0]) {\n          updatePanFromMinimapTouch(e);\n         }\n        });\n        document.addEventListener(\"mouseup\", () => {\n         minimapDragging = false;\n        });\n        document.addEventListener(\"touchend\", () => {\n         minimapDragging = false;\n        });\n        function updatePanFromMinimap(e) {\n         const rect = minimapContainer.getBoundingClientRect();\n         const x = (e.clientX - rect.left) / rect.width;\n         const y = (e.clientY - rect.top) / rect.height;\n         const viewWidth = CANVAS_WIDTH / canvasState.zoom;\n         const viewHeight = CANVAS_HEIGHT / canvasState.zoom;\n         canvasState.panX = x * CANVAS_WIDTH - viewWidth / 2;\n         canvasState.panY = y * CANVAS_HEIGHT - viewHeight / 2;\n         constrainPan();\n         updateViewBox();\n        }\n        function updatePanFromMinimapTouch(e) {\n         const rect = minimapContainer.getBoundingClientRect();\n         const touch = e.touches[0];\n         const x = (touch.clientX - rect.left) / rect.width;\n         const y = (touch.clientY - rect.top) / rect.height;\n         const viewWidth = CANVAS_WIDTH / canvasState.zoom;\n         const viewHeight = CANVAS_HEIGHT / canvasState.zoom;\n         canvasState.panX = x * CANVAS_WIDTH - viewWidth / 2;\n         canvasState.panY = y * CANVAS_HEIGHT - viewHeight / 2;\n         constrainPan();\n         updateViewBox();\n        }\n        document.addEventListener(\"keydown\", (e) => {\n         if (document.activeElement.tagName === \"INPUT\" || document.activeElement.tagName === \"TEXTAREA\") return;\n         if (\n          (e.key === \"+\" || e.key === \"=\") && (e.ctrlKey || e.metaKey)) {\n          e.preventDefault();\n          zoomTo(canvasState.zoom * 1.25, 0.5, 0.5);\n         } else if (e.key === \"-\" && (e.ctrlKey || e.metaKey)) {\n          e.preventDefault();\n          zoomTo(canvasState.zoom * 0.8, 0.5, 0.5);\n         } else if (e.key === \"0\" && (e.ctrlKey || e.metaKey)) {\n          e.preventDefault();\n          resetView();\n         }\n        });\n        setTimeout(() => {\n         fitToContent();\n        }, 100);\n       })();\n       const sizeSlider = document.getElementById(\"size-slider\");\n       const sizeValue = document.getElementById(\"size-value\");\n       const resetSizeBtn = document.getElementById(\"reset-size\");\n       sizeSlider.addEventListener(\"input\", () => {\n        const newSize = parseInt(sizeSlider.value, 10);\n        sizeValue.textContent = newSize;\n        pushUndo(\"resize node\");\n        savedSizes[currentNodeId] = newSize;\n        const nodeGroup = document.querySelector(`g[data-node-id=\"${currentNodeId}\"]`);\n        if (nodeGroup) {\n         const oldShape = nodeGroup.querySelector(\".node-circle\");\n         if (oldShape) oldShape.remove();\n         const newShape = createNodeShape(currentNodeId, newSize);\n         nodeGroup.insertBefore(newShape, nodeGroup.firstChild);\n         const styles = resolveStylesForNode(currentNodeId);\n         if (styles.circleColor) newShape.style.fill = styles.circleColor;\n      if (styles.circleBorder) newShape.style.stroke = styles.circleBorder;\n         const label = nodeGroup.querySelector(\".node-label\");\n         const sub = nodeGroup.querySelector(\".node-sub\");\n         if (label) {\n          label.setAttribute(\"y\", -newSize - 5);\n          const labelSize = styles.titleSize || newSize * 0.33;\n          label.style.fontSize = labelSize + \"px\";\n         }\n         if (sub) {\n          sub.setAttribute(\"y\", newSize + 20);\n          const subSize = styles.subSize || newSize * 0.24;\n          sub.style.fontSize = subSize + \"px\";\n         }\n       updatePingIndicator(currentNodeId);\n        }\n\t\tupdateMinimap();\n       });\n       resetSizeBtn.addEventListener(\"click\", () => {\n        pushUndo(\"reset size\");\n        delete savedSizes[currentNodeId];\n        const defaultSize = getDefaultSize();\n        sizeSlider.value = defaultSize;\n        sizeValue.textContent = defaultSize;\n        const nodeGroup = document.querySelector(`g[data-node-id=\"${currentNodeId}\"]`);\n        if (nodeGroup) {\n         const oldShape = nodeGroup.querySelector(\".node-circle\");\n         if (oldShape) oldShape.remove();\n         const shapeType = (NODE_DATA[currentNodeId] && NODE_DATA[currentNodeId].shape) || \"circle\";\n         const newShape = createNodeShape(currentNodeId, defaultSize);\n         nodeGroup.insertBefore(newShape, nodeGroup.firstChild);\n         const styles = resolveStylesForNode(currentNodeId);\n         if (styles.circleColor) newShape.style.fill = styles.circleColor;\n      if (styles.circleBorder) newShape.style.stroke = styles.circleBorder;\n         const label = nodeGroup.querySelector(\".node-label\");\n         const sub = nodeGroup.querySelector(\".node-sub\");\n         if (label) {\n          label.setAttribute(\"y\", -defaultSize - 5);\n          const labelSize = styles.titleSize || defaultSize * 0.33;\n          label.style.fontSize = labelSize + \"px\";\n         }\n         if (sub) {\n          sub.setAttribute(\"y\", defaultSize + 20);\n          const subSize = styles.subSize || defaultSize * 0.24;\n          sub.style.fontSize = subSize + \"px\";\n         }\n          updatePingIndicator(currentNodeId);\n        }\n\t\tupdateMinimap();\n       });\n       const rotationSlider = document.getElementById(\"rotation-slider\");\n       const rotationInput = document.getElementById(\"rotation-value\");\n       const resetRotationBtn = document.getElementById(\"reset-rotation\");\n       rotationSlider.addEventListener(\"input\", () => {\n         const newRotation = parseInt(rotationSlider.value, 10);\n         rotationInput.value = newRotation;\n         pushUndo(\"rotate node\");\n         NODE_DATA[currentNodeId].rotation = newRotation;\n         const nodeGroup = document.querySelector(`g[data-node-id=\"${currentNodeId}\"]`);\n         if (nodeGroup) {\n           const pos = savedPositions[currentNodeId] || { x: 0, y: 0 };\n           nodeGroup.setAttribute(\"transform\", `translate(${pos.x},${pos.y}) rotate(${newRotation})`);\n         }\n       });\n       rotationInput.addEventListener(\"input\", () => {\n         const newRotation = parseInt(rotationInput.value, 10) || 0;\n         rotationSlider.value = Math.max(-360, Math.min(360, newRotation));\n         pushUndo(\"rotate node\");\n         NODE_DATA[currentNodeId].rotation = newRotation;\n         const nodeGroup = document.querySelector(`g[data-node-id=\"${currentNodeId}\"]`);\n         if (nodeGroup) {\n           const pos = savedPositions[currentNodeId] || { x: 0, y: 0 };\n           nodeGroup.setAttribute(\"transform\", `translate(${pos.x},${pos.y}) rotate(${newRotation})`);\n         }\n       });\n       resetRotationBtn.addEventListener(\"click\", () => {\n         pushUndo(\"reset rotation\");\n         NODE_DATA[currentNodeId].rotation = 0;\n         rotationSlider.value = 0;\n         rotationInput.value = 0;\n         const nodeGroup = document.querySelector(`g[data-node-id=\"${currentNodeId}\"]`);\n         if (nodeGroup) {\n           const pos = savedPositions[currentNodeId] || { x: 0, y: 0 };\n           nodeGroup.setAttribute(\"transform\", `translate(${pos.x},${pos.y}) rotate(0)`);\n         }\n       });\n       const applyStyle = (property, value) => {\n        pushUndo(\"style change\");\n        const styleEntry = ensureStyleEntry(currentNodeId);\n        const scopeKey = currentStyleScope || \"all\";\n        if (!styleEntry[scopeKey]) styleEntry[scopeKey] = {};\n        styleEntry[scopeKey][property] = value;\n        const nodeGroup = document.querySelector(`g[data-node-id=\"${currentNodeId}\"]`);\n        if (!nodeGroup) return;\n        const shapeEl = nodeGroup.querySelector(\".node-circle\");\n        const label = nodeGroup.querySelector(\".node-label\");\n        const sub = nodeGroup.querySelector(\".node-sub\");\n        const isWebIcon = shapeEl && shapeEl.querySelector('svg');\n        if (property === \"circleColor\" && shapeEl) {\n         if (isWebIcon) {\n          shapeEl.querySelectorAll('svg, path, circle, rect, polygon, ellipse').forEach(el => el.style.fill = value);\n         } else {\n          shapeEl.style.fill = value;\n         }\n        } else if (property === \"circleBorder\" && shapeEl) {\n         if (isWebIcon) {\n          shapeEl.querySelectorAll('svg, path, circle, rect, polygon, ellipse').forEach(el => el.style.stroke = value);\n         } else {\n          shapeEl.style.stroke = value;\n         }\n        } else if (property === \"titleColor\" && label) label.style.fill = value;\n        else if (property === \"titleFont\" && label) label.style.fontFamily = value;\n        else if (property === \"titleSize\" && label) label.style.fontSize = value + \"px\";\n        else if (property === \"subColor\" && sub) sub.style.fill = value;\n        else if (property === \"subFont\" && sub) sub.style.fontFamily = value;\n        else if (property === \"subSize\" && sub) sub.style.fontSize = value + \"px\";\n       };\n\n       document.getElementById(\"circle-color\").addEventListener(\"input\", (e) => applyStyle(\"circleColor\", e.target.value));\n      document.getElementById(\"circle-border\").addEventListener(\"input\", (e) => applyStyle(\"circleBorder\", e.target.value));\n       document.getElementById(\"title-color\").addEventListener(\"input\", (e) => applyStyle(\"titleColor\", e.target.value));\n       document.getElementById(\"title-font\").addEventListener(\"change\", (e) => applyStyle(\"titleFont\", e.target.value));\n       document.getElementById(\"title-size\").addEventListener(\"input\", (e) => applyStyle(\"titleSize\", parseInt(e.target.value, 10)));\n       document.getElementById(\"sub-color\").addEventListener(\"input\", (e) => applyStyle(\"subColor\", e.target.value));\n       document.getElementById(\"sub-font\").addEventListener(\"change\", (e) => applyStyle(\"subFont\", e.target.value));\n       document.getElementById(\"sub-size\").addEventListener(\"input\", (e) => applyStyle(\"subSize\", parseInt(e.target.value, 10)));\n       document.getElementById(\"title-offset-y\").addEventListener(\"input\", (e) => {\n        applyStyle(\"titleOffsetY\", parseInt(e.target.value, 10) || 0);\n        forgeTheTopology();\n        if (currentNodeId) claimTheImmortal(currentNodeId);\n       });\n       document.getElementById(\"title-offset-x\").addEventListener(\"input\", (e) => {\n        applyStyle(\"titleOffsetX\", parseInt(e.target.value, 10) || 0);\n        forgeTheTopology();\n        if (currentNodeId) claimTheImmortal(currentNodeId);\n       });\n       document.getElementById(\"sub-offset-y\").addEventListener(\"input\", (e) => {\n        applyStyle(\"subOffsetY\", parseInt(e.target.value, 10) || 0);\n        forgeTheTopology();\n        if (currentNodeId) claimTheImmortal(currentNodeId);\n       });\n       document.getElementById(\"sub-offset-x\").addEventListener(\"input\", (e) => {\n        applyStyle(\"subOffsetX\", parseInt(e.target.value, 10) || 0);\n        forgeTheTopology();\n        if (currentNodeId) claimTheImmortal(currentNodeId);\n       });\n      document.getElementById(\"ping-offset-x\").addEventListener(\"input\", (e) => {\n      applyStyle(\"pingOffsetX\", parseInt(e.target.value, 10) || 0);\n      updatePingIndicator(currentNodeId);\n      if (currentNodeId) claimTheImmortal(currentNodeId);\n      });\n      document.getElementById(\"ping-offset-y\").addEventListener(\"input\", (e) => {\n      applyStyle(\"pingOffsetY\", parseInt(e.target.value, 10) || 0);\n      updatePingIndicator(currentNodeId);\n      if (currentNodeId) claimTheImmortal(currentNodeId);\n      });\n       document.getElementById(\"reset-styles\").addEventListener(\"click\", () => {\n        delete savedStyles[currentNodeId];\n        forgeTheTopology();\n        claimTheImmortal(currentNodeId);\n       });\n       document.getElementById(\"style-scope\").addEventListener(\"change\", (e) => {\n        currentStyleScope = e.target.value || \"all\";\n        claimTheImmortal(currentNodeId);\n       });\n       document.getElementById(\"shape-select\").addEventListener(\"change\", (e) => {\n        if (!currentNodeId || !NODE_DATA[currentNodeId]) return;\n        const shape = e.target.value || \"circle\";\n        if (shape === 'custom-icon') return;\n        pushUndo(\"change shape\");\n        NODE_DATA[currentNodeId].shape = shape;\n        const fovSection = document.getElementById(\"fov-section\");\n        if (fovSection) {\n          const oldShape = NODE_DATA[currentNodeId].shape;\n          fovSection.style.display = hasCoverageZone(shape) ? \"block\" : \"none\";\n          if (hasCoverageZone(shape) && !hasCoverageZone(oldShape)) {\n            const defaults = getCoverageDefaults(shape);\n            document.getElementById(\"fov-angle\").value = defaults.angle;\n            document.getElementById(\"fov-angle-value\").textContent = defaults.angle + \"°\";\n            document.getElementById(\"fov-distance\").value = defaults.distance;\n            document.getElementById(\"fov-distance-value\").textContent = defaults.distance;\n            document.getElementById(\"fov-animation-type\").value = defaults.animationType;\n            NODE_DATA[currentNodeId].fovAngle = defaults.angle;\n            NODE_DATA[currentNodeId].fovDistance = defaults.distance;\n            NODE_DATA[currentNodeId].fovAnimationType = defaults.animationType;\n          }\n        }\n        if (savedStyles[currentNodeId]) {\n         Object.keys(savedStyles[currentNodeId]).forEach(scope => {\n          if (savedStyles[currentNodeId][scope] && savedStyles[currentNodeId][scope].icon) {\n           delete savedStyles[currentNodeId][scope].icon;\n          }\n         });\n        }\n        const customOpt = e.target.querySelector('option[value=\"custom-icon\"]');\n        if (customOpt) customOpt.remove();\n        forgeTheTopology();\n       });\n       document.getElementById(\"add-note-btn\")?.addEventListener(\"click\", () => {\n        if (!currentNodeId) return;\n        showNoteEditor(t(\"noteEditor.addNote\"), \"\", (content) => {\n         pushUndo(\"add note\");\n         NODE_DATA[currentNodeId].notes.push(content);\n         renderNotesFeed(NODE_DATA[currentNodeId].notes, \"node-notes\", \"node\", currentNodeId);\n        });\n       });\n       document.getElementById('node-pingable').addEventListener('change', (e) => {\n        if (!currentNodeId || !NODE_DATA[currentNodeId]) return;\n        if (!NODE_DATA[currentNodeId].ping) {\n         NODE_DATA[currentNodeId].ping = {\n          enabled: false,\n          protocol: 'http',\n          customUrl: '',\n          timeout: 3000,\n          status: 'unknown',\n          lastCheck: null\n         };\n        }\n        NODE_DATA[currentNodeId].ping.enabled = e.target.checked;\n        document.getElementById('node-ping-options').style.display = e.target.checked ? 'block' : 'none';\n        forgeTheTopology();\n       });\n       document.getElementById('node-ping-protocol').addEventListener('change', (e) => {\n        if (!currentNodeId || !NODE_DATA[currentNodeId].ping) return;\n        NODE_DATA[currentNodeId].ping.protocol = e.target.value;\n        document.getElementById('node-custom-url-container').style.display =\n         e.target.value === 'custom' ? 'block' : 'none';\n       });\n       document.getElementById('node-custom-url').addEventListener('change', (e) => {\n        if (!currentNodeId || !NODE_DATA[currentNodeId].ping) return;\n        NODE_DATA[currentNodeId].ping.customUrl = e.target.value.trim();\n       });\n       document.getElementById('node-ping-timeout').addEventListener('change', (e) => {\n        if (!currentNodeId || !NODE_DATA[currentNodeId].ping) return;\n        NODE_DATA[currentNodeId].ping.timeout = parseInt(e.target.value) || 3000;\n       });\n       document.getElementById('check-ping-now').addEventListener('click', () => {\n        if (!currentNodeId) return;\n        checkNodeStatus(currentNodeId);\n       });\n       document.getElementById(\"edge-width\").addEventListener(\"input\", () => {\n        if (!currentEdgeId) return;\n        const edge = EDGE_DATA.list.find((e) => e.id === currentEdgeId);\n        if (!edge) return;\n        const v = parseInt(document.getElementById(\"edge-width\").value, 10);\n        if (Number.isNaN(v) || v <= 0) return;\n        pushUndo(\"edit edge\");\n        edge.width = v;\n        const el = document.querySelector(`.edge[data-edge-id=\"${currentEdgeId}\"]`);\n        if (el) el.style.strokeWidth = v;\n       });\n       document.getElementById(\"edge-color\").addEventListener(\"input\", () => {\n        if (!currentEdgeId) return;\n        const edge = EDGE_DATA.list.find((e) => e.id === currentEdgeId);\n        if (!edge) return;\n        const color = document.getElementById(\"edge-color\").value;\n        pushUndo(\"edit edge\");\n        edge.color = color;\n        const el = document.querySelector(`.edge[data-edge-id=\"${currentEdgeId}\"]`);\n        if (el) el.style.stroke = color;\n        forgeTheLegend();\n\t\tupdateZoneLegend();\n       });\n       document.getElementById(\"edge-direction\").addEventListener(\"change\", () => {\n        if (!currentEdgeId) return;\n        const edge = EDGE_DATA.list.find((e) => e.id === currentEdgeId);\n        if (!edge) return;\n        pushUndo(\"edit edge\");\n        edge.direction = document.getElementById(\"edge-direction\").value;\n        forgeTheTopology();\n        selectTheConnection(currentEdgeId);\n       });\n       document.getElementById(\"edge-line-style\").addEventListener(\"change\", () => {\n        if (!currentEdgeId) return;\n        const edge = EDGE_DATA.list.find((e) => e.id === currentEdgeId);\n        if (!edge) return;\n        pushUndo(\"edit edge\");\n        edge.lineStyle = document.getElementById(\"edge-line-style\").value;\n        forgeTheTopology();\n        selectTheConnection(currentEdgeId);\n       });\n       document.getElementById(\"edge-routing\").addEventListener(\"change\", () => {\n        if (!currentEdgeId) return;\n        const edge = EDGE_DATA.list.find((e) => e.id === currentEdgeId);\n        if (!edge) return;\n        pushUndo(\"edit edge routing\");\n        edge.routing = document.getElementById(\"edge-routing\").value;\n        forgeTheTopology();\n        selectTheConnection(currentEdgeId);\n       });\n       document.getElementById(\"edge-animate\").addEventListener(\"change\", (e) => {\n        if (!currentEdgeId) return;\n        const edge = EDGE_DATA.list.find((ed) => ed.id === currentEdgeId);\n        if (!edge) return;\n        pushUndo(\"edit edge animate\");\n        edge.animate = e.target.checked;\n        forgeTheTopology();\n        selectTheConnection(currentEdgeId);\n       });\n       document.getElementById(\"animation-style-select\").addEventListener(\"change\", (e) => {\n        PAGE_STATE.animationStyle = e.target.value;\n        if (PAGE_STATE.animateConnections) forgeTheTopology();\n       });\n       document.getElementById(\"animation-direction-select\").addEventListener(\"change\", (e) => {\n        PAGE_STATE.animationDirection = e.target.value;\n        if (PAGE_STATE.animateConnections) forgeTheTopology();\n       });\n       document.getElementById(\"animation-speed-select\").addEventListener(\"change\", (e) => {\n        PAGE_STATE.animationSpeed = parseFloat(e.target.value);\n        if (PAGE_STATE.animateConnections) forgeTheTopology();\n       });\n       document.getElementById(\"edge-animation-style\").addEventListener(\"change\", (e) => {\n        if (!currentEdgeId) return;\n        const edge = EDGE_DATA.list.find((ed) => ed.id === currentEdgeId);\n        if (!edge) return;\n        pushUndo(\"edit edge animation style\");\n        edge.animationStyle = e.target.value || \"\";\n        if (PAGE_STATE.animateConnections || edge.animate === true) forgeTheTopology();\n        selectTheConnection(currentEdgeId);\n       });\n       document.getElementById(\"edge-animation-speed\").addEventListener(\"change\", (e) => {\n        if (!currentEdgeId) return;\n        const edge = EDGE_DATA.list.find((ed) => ed.id === currentEdgeId);\n        if (!edge) return;\n        pushUndo(\"edit edge animation speed\");\n        edge.animationSpeed = e.target.value || \"\";\n        if (PAGE_STATE.animateConnections || edge.animate === true) forgeTheTopology();\n        selectTheConnection(currentEdgeId);\n       });\n       document.getElementById(\"add-edge-note\")?.addEventListener(\"click\", () => {\n        if (!currentEdgeId) return;\n        const edge = EDGE_DATA.list.find((e) => e.id === currentEdgeId);\n        if (!edge) return;\n        showNoteEditor(t(\"noteEditor.addNote\"), \"\", (content) => {\n         pushUndo(\"add edge note\");\n         edge.notes.push(content);\n         renderNotesFeed(edge.notes, \"edge-notes\", \"edge\", currentEdgeId);\n        });\n       });\n      function selectTheRect(id) {\n\t  if (isViewOnly()) return;\n      currentRectId = id;\n      currentNodeId = null;\n      currentEdgeId = null;\n      currentTextId = null;\n      document.getElementById(\"node-panel\").style.display = \"none\";\n      document.getElementById(\"edge-panel\").style.display = \"none\";\n      document.getElementById(\"text-panel\").style.display = \"none\";\n      document.getElementById(\"rect-panel\").style.display = \"block\";\n      document.getElementById(\"topology-toolbar\").style.display = \"none\";\n      document.querySelectorAll(\".node-group\").forEach(n => n.classList.remove(\"active\"));\n      document.querySelectorAll(\".edge\").forEach(e => e.classList.remove(\"active\"));\n      document.querySelectorAll(\".text-element\").forEach(t => t.classList.remove(\"active\"));\n      document.querySelectorAll(\".rect-group\").forEach(r => r.classList.toggle(\"active\", r.dataset.rectId === id));\n      const rect = RECT_DATA.list.find(r => r.id === id);\n      if (!rect) return;\n      document.getElementById(\"rect-title\").textContent = \"\";\n      document.getElementById(\"rect-color\").value = rect.color || \"#f97316\";\n      document.getElementById(\"rect-border-color\").value = rect.borderColor || rect.color || \"#f97316\";\n      document.getElementById(\"rect-border-width\").value = rect.borderWidth !== undefined ? rect.borderWidth : 2;\n      document.getElementById(\"rect-style-select\").value = rect.style || \"filled\";\n      document.getElementById(\"rect-line-style\").value = rect.lineStyle || \"solid\";\n      document.getElementById(\"rect-rotation\").value = Math.max(-360, Math.min(360, rect.rotation || 0));\n      document.getElementById(\"rect-rotation-value\").value = rect.rotation || 0;\n      const opacityVal = rect.fillOpacity !== undefined ? Math.round(rect.fillOpacity * 100) : 30;\n      document.getElementById(\"rect-fill-opacity\").value = opacityVal;\n      document.getElementById(\"rect-fill-opacity-value\").textContent = opacityVal + \"%\";\n      document.getElementById(\"rect-fill-row\").style.display = (rect.style === \"outlined\") ? \"none\" : \"flex\";\n      document.getElementById(\"rect-opacity-row\").style.display = (rect.style === \"outlined\") ? \"none\" : \"flex\";\n      renderNotesFeed(rect.notes || [], \"rect-notes\", \"rect\", id);\n      forgeTheTopology();\n      }\n      document.getElementById(\"rect-color\").addEventListener(\"input\", () => {\n      if (!currentRectId) return;\n      const rect = RECT_DATA.list.find(r => r.id === currentRectId);\n      if (!rect) return;\n      pushUndo(\"edit zone\");\n      rect.color = document.getElementById(\"rect-color\").value;\n      forgeTheTopology();\n      });\n      document.getElementById(\"rect-border-color\").addEventListener(\"input\", () => {\n      if (!currentRectId) return;\n      const rect = RECT_DATA.list.find(r => r.id === currentRectId);\n      if (!rect) return;\n      pushUndo(\"edit zone\");\n      rect.borderColor = document.getElementById(\"rect-border-color\").value;\n      forgeTheTopology();\n      });\n      document.getElementById(\"rect-border-width\").addEventListener(\"input\", () => {\n      if (!currentRectId) return;\n      const rect = RECT_DATA.list.find(r => r.id === currentRectId);\n      if (!rect) return;\n      pushUndo(\"edit zone\");\n      rect.borderWidth = parseInt(document.getElementById(\"rect-border-width\").value) || 2;\n      forgeTheTopology();\n      });\n      document.getElementById(\"rect-style-select\").addEventListener(\"change\", () => {\n      if (!currentRectId) return;\n      const rect = RECT_DATA.list.find(r => r.id === currentRectId);\n      if (!rect) return;\n      pushUndo(\"edit zone\");\n      rect.style = document.getElementById(\"rect-style-select\").value;\n      forgeTheTopology();\n      selectTheRect(currentRectId);\n      });\n\t  document.getElementById(\"rect-rotation\").addEventListener(\"input\", () => {\n        if (!currentRectId) return;\n        const rect = RECT_DATA.list.find(r => r.id === currentRectId);\n        if (!rect) return;\n        pushUndo(\"rotate zone\");\n        rect.rotation = parseInt(document.getElementById(\"rect-rotation\").value) || 0;\n        document.getElementById(\"rect-rotation-value\").value = rect.rotation;\n        forgeTheTopology();\n      });\n      document.getElementById(\"rect-rotation-value\").addEventListener(\"input\", () => {\n        if (!currentRectId) return;\n        const rect = RECT_DATA.list.find(r => r.id === currentRectId);\n        if (!rect) return;\n        pushUndo(\"rotate zone\");\n        const val = parseInt(document.getElementById(\"rect-rotation-value\").value) || 0;\n        rect.rotation = val;\n        document.getElementById(\"rect-rotation\").value = Math.max(-360, Math.min(360, val));\n        forgeTheTopology();\n      });\n      document.getElementById(\"rect-line-style\").addEventListener(\"change\", () => {\n      if (!currentRectId) return;\n      pushUndo(\"change zone line style\");\n      const rect = RECT_DATA.list.find(r => r.id === currentRectId);\n      if (!rect) return;\n      rect.lineStyle = document.getElementById(\"rect-line-style\").value;\n      forgeTheTopology();\n      });\n      document.getElementById(\"rect-fill-opacity\").addEventListener(\"input\", () => {\n      if (!currentRectId) return;\n      const rect = RECT_DATA.list.find(r => r.id === currentRectId);\n      if (!rect) return;\n      pushUndo(\"change zone fill opacity\");\n      const val = parseInt(document.getElementById(\"rect-fill-opacity\").value);\n      rect.fillOpacity = val / 100;\n      document.getElementById(\"rect-fill-opacity-value\").textContent = val + \"%\";\n      forgeTheTopology();\n      });\n      document.getElementById(\"add-rect-note\").addEventListener(\"click\", () => {\n        if (!currentRectId) return;\n        const rect = RECT_DATA.list.find(r => r.id === currentRectId);\n        if (!rect) return;\n        if (!rect.notes) rect.notes = [];\n        showNoteEditor(t(\"noteEditor.addNote\"), \"\", (content) => {\n          pushUndo(\"add zone note\");\n          rect.notes.push(content);\n          renderNotesFeed(rect.notes, \"rect-notes\", \"rect\", currentRectId);\n        });\n      });\n      document.getElementById(\"delete-rect\").addEventListener(\"click\", () => {\n      if (!currentRectId) return;\n      challengeTheImmortal(\"Delete this box?\", () => {\n      pushUndo(\"delete zone\");\n      RECT_DATA.list = RECT_DATA.list.filter(r => r.id !== currentRectId);\n      currentRectId = null;\n      document.getElementById(\"rect-panel\").style.display = \"none\";\n      forgeTheTopology();\n      });\n      });\n       document.getElementById(\"delete-edge\").addEventListener(\"click\", () => {\n        if (!currentEdgeId) return;\n        challengeTheImmortal(\"Are you sure you want to delete this line?\",\n         () => {\n          pushUndo(\"delete edge\");\n          EDGE_DATA.list = EDGE_DATA.list.filter(\n           (e) => e.id !== currentEdgeId);\n          currentEdgeId = null;\n          forgeTheTopology();\n          const availableNodes = Object.keys(NODE_DATA);\n          if (availableNodes.length > 0) {\n           claimTheImmortal(availableNodes[0]);\n          } else {\n           document.getElementById(\"node-panel\").style.display = \"none\";\n           document.getElementById(\"edge-panel\").style.display = \"none\";\n           document.getElementById(\"topology-toolbar\", ).style.display = \"none\";\n          }\n         });\n       });\n       document.getElementById(\"clear-waypoints-btn\").addEventListener(\"click\", () => {\n        if (!currentEdgeId) return;\n        const edge = EDGE_DATA.list.find(e => e.id === currentEdgeId);\n        if (!edge || !edge.waypoints || edge.waypoints.length === 0) return;\n        pushUndo(\"clear waypoints\");\n        edge.waypoints = [];\n        selectTheConnection(currentEdgeId);\n        forgeTheTopology();\n       });\n       document.getElementById(\"add-line-btn\").addEventListener(\"click\", () => {\n        if (!currentNodeId) return;\n        const select = document.getElementById(\"add-line-select\");\n        const directionSelect = document.getElementById(\"add-line-direction\");\n        const colorInput = document.getElementById(\"add-line-color\");\n        const routingSelect = document.getElementById(\"add-line-routing\");\n        const targetId = select.value;\n        if (!targetId || targetId === currentNodeId) return;\n        const direction = directionSelect.value || \"none\";\n        const lineColor = colorInput.value || \"#475569\";\n        const routing = routingSelect.value || PAGE_STATE.defaultEdgeRouting || \"orthogonal\";\n        const newId = `${currentNodeId}-${targetId}-${Date.now()}`;\n        const newEdge = {\n         id: newId,\n         from: currentNodeId,\n         to: targetId,\n         width: 4,\n         color: lineColor,\n         direction: direction,\n         routing: routing,\n         type: \"main\",\n         notes: [],\n         fromPort: \"\",\n         toPort: \"\",\n         lineStyle: \"solid\",\n         waypoints: [],\n        };\n        pushUndo(\"add edge\");\n        EDGE_DATA.list.push(newEdge);\n        forgeTheTopology();\n        claimTheImmortal(currentNodeId);\n       });\n       let rectStartPoint = null;\n       let rectPreviewEl = null;\n       let rectStyle = \"filled\";\n       let freeDrawPoints = [];\n       let freeDrawPolylineEl = null;\n       let freeDrawPointEls = [];\n       const drawToggleBtn = document.getElementById(\"draw-toggle\");\n       const drawUndoBtn = document.getElementById(\"draw-undo\");\n       const drawColorInput = document.getElementById(\"draw-color\");\n       const drawStyleSelect = document.getElementById(\"draw-style\");\n       const drawArrowSelect = document.getElementById(\"draw-arrow\");\n       const svgMap = document.getElementById(\"map\");\n       function updateFreeDrawGraphics() {\n        const ns = \"http://www.w3.org/2000/svg\";\n        const svg = svgMap;\n        if (!freeDrawPolylineEl && freeDrawPoints.length > 0) {\n         freeDrawPolylineEl = document.createElementNS(ns, \"polyline\");\n         freeDrawPolylineEl.classList.add(\"edge\", \"free-preview\");\n         freeDrawPolylineEl.setAttribute(\"fill\", \"none\");\n         svg.appendChild(freeDrawPolylineEl);\n        }\n        if (freeDrawPolylineEl) {\n         if (freeDrawPoints.length === 0) {\n          freeDrawPolylineEl.remove();\n          freeDrawPolylineEl = null;\n         } else {\n          const ptsStr = freeDrawPoints.map((p) => `${p.x},${p.y}`).join(\" \");\n          freeDrawPolylineEl.setAttribute(\"points\", ptsStr);\n          freeDrawPolylineEl.style.stroke = drawColorInput.value || \"#475569\";\n          freeDrawPolylineEl.style.strokeWidth = 3;\n          const lineStyle = drawStyleSelect.value || \"solid\";\n          if (lineStyle === \"dashed\") {\n           freeDrawPolylineEl.style.strokeDasharray = \"10,5\";\n          } else if (lineStyle === \"dotted\") {\n           freeDrawPolylineEl.style.strokeDasharray = \"2,4\";\n          } else {\n           freeDrawPolylineEl.style.strokeDasharray = \"none\";\n          }\n         }\n        }\n        freeDrawPointEls.forEach((el) => el.remove());\n        freeDrawPointEls = [];\n        freeDrawPoints.forEach((p, idx) => {\n         const c = document.createElementNS(ns, \"circle\");\n         c.classList.add(\"free-point\");\n         c.setAttribute(\"cx\", p.x);\n         c.setAttribute(\"cy\", p.y);\n         c.setAttribute(\"r\", 5);\n         c.dataset.index = String(idx);\n         c.addEventListener(\"mousedown\", (e) => {\n          if (!freeDrawMode) return;\n          e.preventDefault();\n          e.stopPropagation();\n          let dragging = true;\n          const svgEl = svgMap;\n          const moveHandler = (ev) => {\n           if (!dragging) return;\n           const pt = svgEl.createSVGPoint();\n           pt.x = ev.clientX;\n           pt.y = ev.clientY;\n           const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n           const i = parseInt(c.dataset.index, 10);\n           if (Number.isNaN(i) || !freeDrawPoints[i]) return;\n           freeDrawPoints[i].x = svgP.x;\n           freeDrawPoints[i].y = svgP.y;\n           updateFreeDrawGraphics();\n          };\n          const upHandler = () => {\n           dragging = false;\n           document.removeEventListener(\"mousemove\", moveHandler);\n           document.removeEventListener(\"mouseup\", upHandler);\n          };\n          document.addEventListener(\"mousemove\", moveHandler);\n          document.addEventListener(\"mouseup\", upHandler);\n         });\n         c.addEventListener(\"touchstart\",\n          (e) => {\n           if (!freeDrawMode) return;\n           e.preventDefault();\n           e.stopPropagation();\n           let dragging = true;\n           const svgEl = svgMap;\n           const touchMoveHandler = (ev) => {\n            if (!dragging || !ev.touches[0]) return;\n            const pt = svgEl.createSVGPoint();\n            pt.x = ev.touches[0].clientX;\n            pt.y = ev.touches[0].clientY;\n            const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n            const i = parseInt(c.dataset.index, 10);\n            if (Number.isNaN(i) || !freeDrawPoints[i]) return;\n            freeDrawPoints[i].x = svgP.x;\n            freeDrawPoints[i].y = svgP.y;\n            updateFreeDrawGraphics();\n           };\n           const touchUpHandler = () => {\n            dragging = false;\n            document.removeEventListener(\"touchmove\", touchMoveHandler);\n            document.removeEventListener(\"touchend\", touchUpHandler);\n           };\n           document.addEventListener(\"touchmove\", touchMoveHandler);\n           document.addEventListener(\"touchend\", touchUpHandler);\n          }, {\n           passive: false\n          });\n         svg.appendChild(c);\n         freeDrawPointEls.push(c);\n        });\n        drawUndoBtn.style.display = freeDrawPoints.length ? \"inline-block\" : \"none\";\n       }\n       function addFreeDrawPoint(x, y) {\n        freeDrawPoints.push({\n         x,\n         y\n        });\n        updateFreeDrawGraphics();\n       }\n       function startFreeDraw() {\n        freeDrawMode = true;\n        freeDrawPoints = [];\n        if (freeDrawPolylineEl) {\n         freeDrawPolylineEl.remove();\n         freeDrawPolylineEl = null;\n        }\n        freeDrawPointEls.forEach((el) => el.remove());\n        freeDrawPointEls = [];\n        svgMap.style.cursor = \"crosshair\";\n        drawToggleBtn.textContent = t(\"ui.buttons.done\");\n        drawToggleBtn.classList.add(\"done-btn-active\");\n        drawUndoBtn.style.display = \"none\";\n       }\n       function finishFreeDraw() {\n        freeDrawMode = false;\n        svgMap.style.cursor = \"\";\n        drawToggleBtn.textContent = \"✏️\";\n        drawToggleBtn.classList.remove(\"done-btn-active\");\n        if (freeDrawPoints.length >= 2) {\n         const color = drawColorInput.value || \"#475569\";\n         const lineStyle = drawStyleSelect.value || \"solid\";\n         const arrowDir = drawArrowSelect.value || \"none\";\n         const newId = \"custom-\" + Date.now();\n         const pointsCopy = freeDrawPoints.map((p) => ({\n          x: p.x,\n          y: p.y,\n         }));\n         EDGE_DATA.list.push({\n          id: newId,\n          type: \"custom\",\n          color,\n          width: 4,\n          lineStyle: lineStyle,\n          direction: arrowDir,\n          points: pointsCopy,\n          notes: [],\n         });\n         freeDrawPoints = [];\n         if (freeDrawPolylineEl) {\n          freeDrawPolylineEl.remove();\n          freeDrawPolylineEl = null;\n         }\n         freeDrawPointEls.forEach((el) => el.remove());\n         freeDrawPointEls = [];\n         forgeTheTopology();\n         selectTheConnection(newId);\n        } else {\n         freeDrawPoints = [];\n         if (freeDrawPolylineEl) {\n          freeDrawPolylineEl.remove();\n          freeDrawPolylineEl = null;\n         }\n         freeDrawPointEls.forEach((el) => el.remove());\n         freeDrawPointEls = [];\n         forgeTheLegend();\n\t\t updateZoneLegend();\n        }\n        drawUndoBtn.style.display = \"none\";\n       }\n       drawToggleBtn.addEventListener(\"click\", () => {\n        if (currentView.mode === \"rack\") {\n         showAlert(t(\"dialogs.drawingDisabledInRack\"));\n         return;\n        }\n        if (freeDrawMode) {\n         finishFreeDraw();\n        } else {\n         startFreeDraw();\n        }\n       });\n       drawUndoBtn.addEventListener(\"click\", () => {\n        if (!freeDrawMode || !freeDrawPoints.length) return;\n        freeDrawPoints.pop();\n        updateFreeDrawGraphics();\n       });\n       const drawToolbar = document.getElementById(\"draw-toolbar\");\n       drawToolbar.addEventListener(\"mousedown\", (e) => {\n        e.stopPropagation();\n       });\n       drawToolbar.addEventListener(\"click\", (e) => {\n        if (e.target !== drawToggleBtn && e.target !== drawUndoBtn) {\n         e.stopPropagation();\n        }\n       });\n       drawStyleSelect.addEventListener(\"change\", () => {\n        if (freeDrawMode) {\n         updateFreeDrawGraphics();\n        }\n       });\n       drawArrowSelect.addEventListener(\"change\", () => {\n        if (freeDrawMode) {\n         updateFreeDrawGraphics();\n        }\n       });\n       drawColorInput.addEventListener(\"input\", () => {\n        if (freeDrawMode) {\n         updateFreeDrawGraphics();\n        }\n       });\n       drawStyleSelect.addEventListener(\"mousedown\", (e) => {\n        e.stopPropagation();\n       });\n       drawStyleSelect.addEventListener(\"click\", (e) => {\n        e.stopPropagation();\n       });\n       drawArrowSelect.addEventListener(\"mousedown\", (e) => {\n        e.stopPropagation();\n       });\n       drawArrowSelect.addEventListener(\"click\", (e) => {\n        e.stopPropagation();\n       });\n       drawColorInput.addEventListener(\"mousedown\", (e) => {\n        e.stopPropagation();\n       });\n       drawColorInput.addEventListener(\"click\", (e) => {\n        e.stopPropagation();\n       });\n       svgMap.addEventListener(\"click\", (e) => {\n        if (!freeDrawMode) return;\n        if (e.button !== 0) return;\n        const target = e.target;\n        if (target && target.classList && target.classList.contains(\"free-point\")) return;\n        const svgEl = svgMap;\n        const pt = svgEl.createSVGPoint();\n        pt.x = e.clientX;\n        pt.y = e.clientY;\n        const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n        addFreeDrawPoint(svgP.x, svgP.y);\n       });\n       svgMap.addEventListener(\"touchend\",\n        (e) => {\n         if (!freeDrawMode) return;\n         const target = e.target;\n         if (target && target.classList && target.classList.contains(\"free-point\")) return;\n         if (e.changedTouches && e.changedTouches[0]) {\n          e.preventDefault();\n          const svgEl = svgMap;\n          const pt = svgEl.createSVGPoint();\n          pt.x = e.changedTouches[0].clientX;\n          pt.y = e.changedTouches[0].clientY;\n          const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n          addFreeDrawPoint(svgP.x, svgP.y);\n         }\n        }, {\n         passive: false\n        });\n       const settingsBtn = document.getElementById(\"settings-btn\");\n       const rectToggleBtn = document.getElementById(\"rect-toggle\");\n       const rectStyleSelect = document.getElementById(\"rect-style\");\n       function updateRectPreview() {\n        if (!rectPreviewEl || !rectStartPoint) return;\n        const ns = \"http://www.w3.org/2000/svg\";\n        const svg = svgMap;\n        if (rectPreviewEl) {\n         rectPreviewEl.remove();\n         rectPreviewEl = null;\n        }\n       }\n       function startRectDraw() {\n        rectDrawMode = true;\n        rectStartPoint = null;\n        rectPreviewEl = null;\n        svgMap.style.cursor = \"crosshair\";\n        rectToggleBtn.textContent = t(\"ui.buttons.done\");\n        rectToggleBtn.classList.add(\"done-btn-active\");\n        rectStyle = rectStyleSelect.value || \"filled\";\n        if (freeDrawMode) {\n         finishFreeDraw();\n        }\n        updateRectangleDeleteButtons();\n       }\n       function finishRectDraw() {\n        rectDrawMode = false;\n        svgMap.style.cursor = \"\";\n        rectToggleBtn.textContent = \"▭\";\n        rectToggleBtn.classList.remove(\"done-btn-active\");\n        if (rectPreviewEl) {\n         rectPreviewEl.remove();\n         rectPreviewEl = null;\n        }\n        rectStartPoint = null;\n\t\tupdateRectangleDeleteButtons();\n       }\n       rectToggleBtn.addEventListener(\"click\", () => {\n        if (currentView.mode === \"rack\") {\n         showAlert(t(\"dialogs.drawingDisabledInRack\"));\n         return;\n        }\n        if (rectDrawMode) {\n         finishRectDraw();\n        } else {\n         startRectDraw();\n        }\n       });\n       rectStyleSelect.addEventListener(\"mousedown\", (e) => {\n        e.stopPropagation();\n       });\n       rectStyleSelect.addEventListener(\"click\", (e) => {\n        e.stopPropagation();\n       });\n       rectStyleSelect.addEventListener(\"change\", () => {\n        if (rectDrawMode) {\n         rectStyle = rectStyleSelect.value || \"filled\";\n        }\n       });\n       svgMap.addEventListener(\"mousedown\", (e) => {\n        if (!rectDrawMode) return;\n        if (e.button !== 0) return;\n        e.preventDefault();\n        e.stopPropagation();\n        const svgEl = svgMap;\n        const pt = svgEl.createSVGPoint();\n        pt.x = e.clientX;\n        pt.y = e.clientY;\n        const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n        rectStartPoint = { x: svgP.x, y: svgP.y };\n        const ns = \"http://www.w3.org/2000/svg\";\n        rectPreviewEl = document.createElementNS(ns, \"rect\");\n        rectPreviewEl.classList.add(\"rect-preview\");\n        rectPreviewEl.setAttribute(\"x\", svgP.x);\n        rectPreviewEl.setAttribute(\"y\", svgP.y);\n        rectPreviewEl.setAttribute(\"width\", 0);\n        rectPreviewEl.setAttribute(\"height\", 0);\n        const color = drawColorInput.value || \"#f97316\";\n        rectPreviewEl.style.stroke = color;\n        rectPreviewEl.style.strokeWidth = 2;\n        rectPreviewEl.style.strokeDasharray = \"5,5\";\n        if (rectStyle === \"filled\") {\n         rectPreviewEl.style.fill = color;\n         rectPreviewEl.style.fillOpacity = \"0.3\";\n        } else {\n         rectPreviewEl.style.fill = \"none\";\n        }\n        svgEl.appendChild(rectPreviewEl);\n       });\n       svgMap.addEventListener(\"mousemove\", (e) => {\n        if (!rectDrawMode || !rectStartPoint || !rectPreviewEl) return;\n        const svgEl = svgMap;\n        const pt = svgEl.createSVGPoint();\n        pt.x = e.clientX;\n        pt.y = e.clientY;\n        const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n        const x = Math.min(rectStartPoint.x, svgP.x);\n        const y = Math.min(rectStartPoint.y, svgP.y);\n        const width = Math.abs(svgP.x - rectStartPoint.x);\n        const height = Math.abs(svgP.y - rectStartPoint.y);\n        rectPreviewEl.setAttribute(\"x\", x);\n        rectPreviewEl.setAttribute(\"y\", y);\n        rectPreviewEl.setAttribute(\"width\", width);\n        rectPreviewEl.setAttribute(\"height\", height);\n       });\n       svgMap.addEventListener(\"mouseup\", (e) => {\n        if (!rectDrawMode || !rectStartPoint || !rectPreviewEl) return;\n        const svgEl = svgMap;\n        const pt = svgEl.createSVGPoint();\n        pt.x = e.clientX;\n        pt.y = e.clientY;\n        const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n        const x = Math.min(rectStartPoint.x, svgP.x);\n        const y = Math.min(rectStartPoint.y, svgP.y);\n        const width = Math.abs(svgP.x - rectStartPoint.x);\n        const height = Math.abs(svgP.y - rectStartPoint.y);\n        if (width > 10 && height > 10) {\n      pushUndo(\"draw zone\");\n         const color = drawColorInput.value || \"#f97316\";\n         const newId = \"rect-\" + Date.now();\n         const lineStyle = document.getElementById(\"draw-style\").value || \"solid\";\n         RECT_DATA.list.push({\n          id: newId,\n          x: x,\n          y: y,\n          width: width,\n          height: height,\n          color: color,\n          style: rectStyle,\n          lineStyle: lineStyle,\n          notes: []\n         });\n         finishRectDraw();\n         forgeTheTopology();\n        }\n        if (rectPreviewEl) {\n         rectPreviewEl.remove();\n         rectPreviewEl = null;\n        }\n        rectStartPoint = null;\n       });\n       let rectTouchStart = null;\n       svgMap.addEventListener(\"touchstart\", (e) => {\n        if (!rectDrawMode) return;\n        if (e.touches.length !== 1) return;\n        e.preventDefault();\n        const touch = e.touches[0];\n        const svgEl = svgMap;\n        const pt = svgEl.createSVGPoint();\n        pt.x = touch.clientX;\n        pt.y = touch.clientY;\n        const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n        rectStartPoint = { x: svgP.x, y: svgP.y };\n        rectTouchStart = { x: svgP.x, y: svgP.y };\n        const ns = \"http://www.w3.org/2000/svg\";\n        rectPreviewEl = document.createElementNS(ns, \"rect\");\n        rectPreviewEl.classList.add(\"rect-preview\");\n        rectPreviewEl.setAttribute(\"x\", svgP.x);\n        rectPreviewEl.setAttribute(\"y\", svgP.y);\n        rectPreviewEl.setAttribute(\"width\", 0);\n        rectPreviewEl.setAttribute(\"height\", 0);\n        const color = drawColorInput.value || \"#f97316\";\n        rectPreviewEl.style.stroke = color;\n        rectPreviewEl.style.strokeWidth = 2;\n        rectPreviewEl.style.strokeDasharray = \"5,5\";\n        if (rectStyle === \"filled\") {\n         rectPreviewEl.style.fill = color;\n         rectPreviewEl.style.fillOpacity = \"0.3\";\n        } else {\n         rectPreviewEl.style.fill = \"none\";\n        }\n        svgEl.appendChild(rectPreviewEl);\n       }, { passive: false });\n       svgMap.addEventListener(\"touchmove\", (e) => {\n        if (!rectDrawMode || !rectStartPoint || !rectPreviewEl) return;\n        if (e.touches.length !== 1) return;\n        e.preventDefault();\n        const touch = e.touches[0];\n        const svgEl = svgMap;\n        const pt = svgEl.createSVGPoint();\n        pt.x = touch.clientX;\n        pt.y = touch.clientY;\n        const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n        const x = Math.min(rectStartPoint.x, svgP.x);\n        const y = Math.min(rectStartPoint.y, svgP.y);\n        const width = Math.abs(svgP.x - rectStartPoint.x);\n        const height = Math.abs(svgP.y - rectStartPoint.y);\n        rectPreviewEl.setAttribute(\"x\", x);\n        rectPreviewEl.setAttribute(\"y\", y);\n        rectPreviewEl.setAttribute(\"width\", width);\n        rectPreviewEl.setAttribute(\"height\", height);\n       }, { passive: false });\n       svgMap.addEventListener(\"touchend\", (e) => {\n        if (!rectDrawMode || !rectStartPoint || !rectPreviewEl) return;\n        if (!rectTouchStart) return;\n        e.preventDefault();\n        const touch = e.changedTouches[0];\n        const svgEl = svgMap;\n        const pt = svgEl.createSVGPoint();\n        pt.x = touch.clientX;\n        pt.y = touch.clientY;\n        const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n        const x = Math.min(rectStartPoint.x, svgP.x);\n        const y = Math.min(rectStartPoint.y, svgP.y);\n        const width = Math.abs(svgP.x - rectStartPoint.x);\n        const height = Math.abs(svgP.y - rectStartPoint.y);\n        if (width > 10 && height > 10) {\n      pushUndo(\"draw zone\");\n         const color = drawColorInput.value || \"#f97316\";\n         const newId = \"rect-\" + Date.now();\n         const lineStyle = document.getElementById(\"draw-style\").value || \"solid\";\n         RECT_DATA.list.push({\n          id: newId,\n          x: x,\n          y: y,\n          width: width,\n          height: height,\n          color: color,\n          style: rectStyle,\n          lineStyle: lineStyle,\n          notes: []\n         });\n         finishRectDraw();\n         forgeTheTopology();\n        }\n        if (rectPreviewEl) {\n         rectPreviewEl.remove();\n         rectPreviewEl = null;\n        }\n        rectStartPoint = null;\n        rectTouchStart = null;\n       }, { passive: false });\n       const textToggleBtn = document.getElementById(\"text-toggle\");\n       function startTextMode() {\n        textDrawMode = true;\n        svgMap.style.cursor = \"crosshair\";\n        textToggleBtn.textContent = t(\"ui.buttons.done\");\n        textToggleBtn.classList.add(\"done-btn-active\");\n        if (freeDrawMode) {\n         finishFreeDraw();\n        }\n        if (rectDrawMode) {\n         finishRectDraw();\n        }\n        updateTextDeleteButtons();\n       }\n       function finishTextMode() {\n        textDrawMode = false;\n        svgMap.style.cursor = \"\";\n        textToggleBtn.textContent = \"T\";\n        textToggleBtn.classList.remove(\"done-btn-active\");\n        updateTextDeleteButtons();\n       }\n       textToggleBtn.addEventListener(\"click\", () => {\n        if (currentView.mode === \"rack\") {\n         showAlert(t(\"dialogs.drawingDisabledInRack\"));\n         return;\n        }\n        if (textDrawMode) {\n         finishTextMode();\n        } else {\n         startTextMode();\n        }\n       });\n       function handleTextPlacement(e) {\n        if (!textDrawMode) return;\n        const svgEl = svgMap;\n        const pt = svgEl.createSVGPoint();\n        pt.x = e.clientX;\n        pt.y = e.clientY;\n        const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n        const newId = \"text-\" + Date.now();\n      pushUndo(\"add text\");\n        TEXT_DATA.list.push({\n         id: newId,\n         x: svgP.x,\n         y: svgP.y,\n         content: t(\"ui.defaults.newText\"),\n         fontSize: 18,\n         color: \"#e2e8f0\",\n         fontWeight: \"normal\",\n         fontStyle: \"normal\",\n         textAlign: \"start\",\n         textDecoration: \"none\",\n         bgColor: \"#000000\",\n         bgEnabled: false,\n         opacity: 1\n        });\n        finishTextMode();\n        forgeTheTopology();\n        showTextPanel(newId);\n        const textInput = document.getElementById(\"text-content\");\n        if (textInput) {\n         textInput.focus();\n         textInput.select();\n        }\n       }\n       svgMap.addEventListener(\"click\", (e) => {\n        if (!textDrawMode) return;\n        if (e.target.closest('.text-delete-btn')) return;\n        if (e.target.closest('.text-group')) return;\n        e.preventDefault();\n        e.stopPropagation();\n        handleTextPlacement(e);\n       });\n       svgMap.addEventListener(\"touchend\", (e) => {\n        if (!textDrawMode) return;\n        if (e.target.closest('.text-delete-btn')) return;\n        if (e.target.closest('.text-group')) return;\n        if (e.touches.length > 0) return;\n        e.preventDefault();\n        const touch = e.changedTouches[0];\n        const fakeEvent = {\n         clientX: touch.clientX,\n         clientY: touch.clientY,\n         preventDefault: () => {},\n         stopPropagation: () => {}\n        };\n        handleTextPlacement(fakeEvent);\n       }, { passive: false });\n       function showTextPanel(textId) {\n\t   if (isViewOnly()) return;\n       currentTextId = textId;\n       currentNodeId = null;\n       currentEdgeId = null;\n       currentRectId = null;\n       const textItem = TEXT_DATA.list.find(t => t.id === textId);\n       if (!textItem) return;\n       document.getElementById(\"node-panel\").style.display = \"none\";\n       document.getElementById(\"edge-panel\").style.display = \"none\";\n       document.getElementById(\"rect-panel\").style.display = \"none\";\n       const textPanel = document.getElementById(\"text-panel\");\n       textPanel.style.display = \"block\";\n       document.querySelectorAll(\".node-group\").forEach(n => n.classList.remove(\"active\"));\n       document.querySelectorAll(\".edge\").forEach(e => e.classList.remove(\"active\"));\n       document.querySelectorAll(\".rect-group\").forEach(r => r.classList.remove(\"active\"));\n       document.querySelectorAll(\".text-element\").forEach(t => t.classList.toggle(\"active\", t.dataset.textId === textId));\n        document.getElementById(\"text-content\").value = textItem.content;\n        document.getElementById(\"text-font-size\").value = textItem.fontSize;\n        document.getElementById(\"text-color\").value = textItem.color;\n        document.getElementById(\"text-font-weight\").value = textItem.fontWeight;\n        document.getElementById(\"text-font-style\").value = textItem.fontStyle;\n        document.getElementById(\"text-align\").value = textItem.textAlign;\n        document.getElementById(\"text-decoration\").value = textItem.textDecoration;\n        document.getElementById(\"text-bg-color\").value = textItem.bgColor;\n        document.getElementById(\"text-bg-enabled\").checked = textItem.bgEnabled;\n        document.getElementById(\"text-opacity\").value = textItem.opacity;\n        document.getElementById(\"text-opacity-val\").textContent = Math.round(textItem.opacity * 100) + \"%\";\n        document.getElementById(\"text-rotation\").value = Math.max(-360, Math.min(360, textItem.rotation || 0));\n        document.getElementById(\"text-rotation-val\").value = textItem.rotation || 0;\n       }\n       function updateTextDeleteButtons() {\n        const deleteButtons = document.querySelectorAll('.text-delete-btn');\n        deleteButtons.forEach(btn => {\n         btn.style.display = textDrawMode ? 'block' : 'none';\n        });\n       }\n       function deleteText(textId) {\n      pushUndo(\"delete text\");\n        TEXT_DATA.list = TEXT_DATA.list.filter(t => t.id !== textId);\n        forgeTheTopology();\n        if (currentTextId === textId) {\n         document.getElementById(\"text-panel\").style.display = \"none\";\n         currentTextId = null;\n        }\n       }\n      function processAndAddImage(file, canvasX, canvasY, filename = null) {\n       if (IMAGE_DATA.list.length >= MAX_CANVAS_IMAGES) {\n        showAlert(t(\"images.maxImages\"));\n        return;\n       }\n       const imageName = filename || file.name.replace(/\\.[^/.]+$/, \"\") || \"Image\";\n       const reader = new FileReader();\n       reader.onload = (e) => {\n        const img = new Image();\n        img.onload = () => {\n         let width = img.width;\n         let height = img.height;\n         if (width > MAX_IMAGE_SIZE || height > MAX_IMAGE_SIZE) {\n          const ratio = Math.min(MAX_IMAGE_SIZE / width, MAX_IMAGE_SIZE / height);\n          width = Math.round(width * ratio);\n          height = Math.round(height * ratio);\n         }\n         const canvas = document.createElement(\"canvas\");\n         canvas.width = width;\n         canvas.height = height;\n         const ctx = canvas.getContext(\"2d\");\n         ctx.drawImage(img, 0, 0, width, height);\n         const imageData = ctx.getImageData(0, 0, width, height);\n         const data = imageData.data;\n         let hasTransparency = false;\n         for (let i = 3; i < data.length; i += 4) {\n          if (data[i] < 255) { hasTransparency = true; break; }\n         }\n         let compressedSrc;\n         const webpTest = canvas.toDataURL(\"image/webp\", 0.8);\n         const supportsWebP = webpTest.startsWith(\"data:image/webp\");\n         if (supportsWebP) {\n          compressedSrc = canvas.toDataURL(\"image/webp\", 0.8);\n         } else if (hasTransparency) {\n          compressedSrc = canvas.toDataURL(\"image/png\");\n         } else {\n          compressedSrc = canvas.toDataURL(\"image/jpeg\", 0.8);\n         }\n         const newId = \"img-\" + Date.now();\n         pushUndo(\"add image\");\n         IMAGE_DATA.list.push({\n          id: newId,\n          name: imageName,\n          x: canvasX - width / 2,\n          y: canvasY - height / 2,\n          width: width,\n          height: height,\n          src: compressedSrc,\n          opacity: 1,\n          border: \"none\",\n          shadow: \"none\",\n          notes: [],\n          baseWidth: width,\n          baseHeight: height\n         });\n         forgeTheTopology();\n        };\n        img.src = e.target.result;\n       };\n       reader.readAsDataURL(file);\n      }\n      function showImagePanel(imageId) {\n       const img = IMAGE_DATA.list.find(i => i.id === imageId);\n       if (!img) return;\n       currentImageId = imageId;\n       currentNodeId = null;\n       currentEdgeId = null;\n       currentRectId = null;\n       currentTextId = null;\n       document.getElementById(\"node-panel\").style.display = \"none\";\n       document.getElementById(\"edge-panel\").style.display = \"none\";\n       document.getElementById(\"rect-panel\").style.display = \"none\";\n       document.getElementById(\"text-panel\").style.display = \"none\";\n       document.getElementById(\"image-panel\").style.display = \"block\";\n       document.querySelectorAll(\".node-group\").forEach(n => n.classList.remove(\"active\"));\n       document.querySelectorAll(\".edge\").forEach(e => e.classList.remove(\"active\"));\n       document.querySelectorAll(\".rect-group\").forEach(r => r.classList.remove(\"active\"));\n       document.querySelectorAll(\".text-element\").forEach(t => t.classList.remove(\"active\"));\n       document.getElementById(\"image-title\").textContent = img.name || \"Image\";\n       document.getElementById(\"image-title\").onclick = () => editImageName(imageId);\n       document.getElementById(\"image-panel-opacity\").value = Math.round((img.opacity || 1) * 100);\n       document.getElementById(\"image-panel-opacity-val\").textContent = Math.round((img.opacity || 1) * 100) + \"%\";\n       document.getElementById(\"image-panel-border\").value = img.border || \"none\";\n       document.getElementById(\"image-panel-shadow\").value = img.shadow || \"none\";\n       const sizePercent = img.baseWidth ? Math.round((img.width / img.baseWidth) * 100) : 100;\n       document.getElementById(\"image-panel-size\").value = sizePercent;\n       document.getElementById(\"image-panel-size-val\").textContent = sizePercent + \"%\";\n       renderNotesFeed(img.notes || [], \"image-notes\", \"image\", imageId);\n      }\n      function editImageName(imageId) {\n       const img = IMAGE_DATA.list.find(i => i.id === imageId);\n       if (!img) return;\n       showEditModal(t(\"editModal.editName\"), img.name || \"\", (newName) => {\n        pushUndo(\"edit image name\");\n        img.name = newName;\n        document.getElementById(\"image-title\").textContent = newName || \"Image\";\n       });\n      }\n      function hideImagePanel() {\n       document.getElementById(\"image-panel\").style.display = \"none\";\n       currentImageId = null;\n      }\n      function deleteImage(imageId) {\n       pushUndo(\"delete image\");\n       IMAGE_DATA.list = IMAGE_DATA.list.filter(i => i.id !== imageId);\n       forgeTheTopology();\n       hideImagePanel();\n      }\n      document.getElementById(\"image-toggle\")?.addEventListener(\"click\", () => {\n       document.getElementById(\"image-file-input\").click();\n      });\n      document.getElementById(\"image-file-input\")?.addEventListener(\"change\", (e) => {\n       const file = e.target.files[0];\n       if (!file) return;\n       const svg = document.getElementById(\"map\");\n       const viewBox = svg.viewBox.baseVal;\n       const centerX = viewBox.x + viewBox.width / 2;\n       const centerY = viewBox.y + viewBox.height / 2;\n       processAndAddImage(file, centerX, centerY);\n       e.target.value = \"\";\n      });\n      document.getElementById(\"image-panel-opacity\")?.addEventListener(\"input\", (e) => {\n       if (!currentImageId) return;\n       const img = IMAGE_DATA.list.find(i => i.id === currentImageId);\n       if (img) {\n        img.opacity = parseInt(e.target.value) / 100;\n        document.getElementById(\"image-panel-opacity-val\").textContent = e.target.value + \"%\";\n        forgeTheTopology();\n       }\n      });\n      document.getElementById(\"image-panel-border\")?.addEventListener(\"change\", (e) => {\n       if (!currentImageId) return;\n       const img = IMAGE_DATA.list.find(i => i.id === currentImageId);\n       if (img) {\n        img.border = e.target.value;\n        forgeTheTopology();\n       }\n      });\n      document.getElementById(\"image-panel-shadow\")?.addEventListener(\"change\", (e) => {\n       if (!currentImageId) return;\n       const img = IMAGE_DATA.list.find(i => i.id === currentImageId);\n       if (img) {\n        img.shadow = e.target.value;\n        forgeTheTopology();\n       }\n      });\n      document.getElementById(\"image-panel-size\")?.addEventListener(\"input\", (e) => {\n       if (!currentImageId) return;\n       const img = IMAGE_DATA.list.find(i => i.id === currentImageId);\n       if (img) {\n        const scale = parseInt(e.target.value) / 100;\n        const baseWidth = img.baseWidth || img.width;\n        const baseHeight = img.baseHeight || img.height;\n        if (!img.baseWidth) img.baseWidth = img.width;\n        if (!img.baseHeight) img.baseHeight = img.height;\n        img.width = Math.round(baseWidth * scale);\n        img.height = Math.round(baseHeight * scale);\n        document.getElementById(\"image-panel-size-val\").textContent = e.target.value + \"%\";\n        forgeTheTopology();\n       }\n      });\n      document.getElementById(\"add-image-note\")?.addEventListener(\"click\", () => {\n        if (!currentImageId) return;\n        const img = IMAGE_DATA.list.find(i => i.id === currentImageId);\n        if (!img) return;\n        if (!img.notes) img.notes = [];\n        showNoteEditor(t(\"noteEditor.addNote\"), \"\", (content) => {\n          pushUndo(\"add image note\");\n          img.notes.push(content);\n          renderNotesFeed(img.notes, \"image-notes\", \"image\", currentImageId);\n        });\n      });\n      document.getElementById(\"delete-image-panel\")?.addEventListener(\"click\", () => {\n       if (!currentImageId) return;\n       deleteImage(currentImageId);\n      });\n      (function() {\n       const viewport = document.getElementById(\"canvas-viewport\");\n       viewport?.addEventListener(\"dragover\", (e) => {\n        e.preventDefault();\n        e.dataTransfer.dropEffect = \"copy\";\n       });\n       viewport?.addEventListener(\"drop\", (e) => {\n        e.preventDefault();\n        if (isViewOnly()) return;\n        const files = e.dataTransfer.files;\n        if (files.length === 0) return;\n        const file = files[0];\n        if (!file.type.startsWith(\"image/\")) return;\n        const svg = document.getElementById(\"map\");\n        const pt = svg.createSVGPoint();\n        pt.x = e.clientX;\n        pt.y = e.clientY;\n        const svgP = pt.matrixTransform(svg.getScreenCTM().inverse());\n        processAndAddImage(file, svgP.x, svgP.y);\n       });\n      })();\n       document.getElementById(\"text-content\").addEventListener(\"input\", (e) => {\n        if (!currentTextId) return;\n        const textItem = TEXT_DATA.list.find(t => t.id === currentTextId);\n        if (textItem) {\n      pushUndo(\"edit text\");\n         textItem.content = e.target.value;\n         forgeTheTopology();\n        }\n       });\n       document.getElementById(\"text-font-size\").addEventListener(\"input\", (e) => {\n        if (!currentTextId) return;\n        const textItem = TEXT_DATA.list.find(t => t.id === currentTextId);\n        if (textItem) {\n         pushUndo(\"edit text\");\n         textItem.fontSize = parseInt(e.target.value);\n         forgeTheTopology();\n        }\n       });\n       document.getElementById(\"text-color\").addEventListener(\"input\", (e) => {\n        if (!currentTextId) return;\n        const textItem = TEXT_DATA.list.find(t => t.id === currentTextId);\n        if (textItem) {\n         pushUndo(\"edit text\");\n         textItem.color = e.target.value;\n         forgeTheTopology();\n        }\n       });\n       document.getElementById(\"text-font-weight\").addEventListener(\"change\", (e) => {\n        if (!currentTextId) return;\n        const textItem = TEXT_DATA.list.find(t => t.id === currentTextId);\n        if (textItem) {\n         pushUndo(\"edit text\");\n         textItem.fontWeight = e.target.value;\n         forgeTheTopology();\n        }\n       });\n       document.getElementById(\"text-font-style\").addEventListener(\"change\", (e) => {\n        if (!currentTextId) return;\n        const textItem = TEXT_DATA.list.find(t => t.id === currentTextId);\n        if (textItem) {\n         pushUndo(\"edit text\");\n         textItem.fontStyle = e.target.value;\n         forgeTheTopology();\n        }\n       });\n       document.getElementById(\"text-align\").addEventListener(\"change\", (e) => {\n        if (!currentTextId) return;\n        const textItem = TEXT_DATA.list.find(t => t.id === currentTextId);\n        if (textItem) {\n         pushUndo(\"edit text\");\n         textItem.textAlign = e.target.value;\n         forgeTheTopology();\n        }\n       });\n        document.getElementById(\"text-decoration\").addEventListener(\"change\", (e) => {\n        if (!currentTextId) return;\n        const textItem = TEXT_DATA.list.find(t => t.id === currentTextId);\n        if (textItem) {\n         pushUndo(\"edit text\");\n         textItem.textDecoration = e.target.value;\n         forgeTheTopology();\n        }\n       });\n       document.getElementById(\"text-bg-color\").addEventListener(\"input\", (e) => {\n        if (!currentTextId) return;\n        const textItem = TEXT_DATA.list.find(t => t.id === currentTextId);\n        if (textItem) {\n         pushUndo(\"edit text\");\n         textItem.bgColor = e.target.value;\n         forgeTheTopology();\n        }\n       });\n       document.getElementById(\"text-bg-enabled\").addEventListener(\"change\", (e) => {\n        if (!currentTextId) return;\n        const textItem = TEXT_DATA.list.find(t => t.id === currentTextId);\n        if (textItem) {\n         pushUndo(\"edit text\");\n         textItem.bgEnabled = e.target.checked;\n         forgeTheTopology();\n        }\n       });\n       document.getElementById(\"text-opacity\").addEventListener(\"input\", (e) => {\n        if (!currentTextId) return;\n        const textItem = TEXT_DATA.list.find(t => t.id === currentTextId);\n        if (textItem) {\n         pushUndo(\"edit text\");\n         textItem.opacity = parseFloat(e.target.value);\n         document.getElementById(\"text-opacity-val\").textContent = Math.round(textItem.opacity * 100) + \"%\";\n         forgeTheTopology();\n        }\n       });\n       document.getElementById(\"text-rotation\").addEventListener(\"input\", (e) => {\n         if (!currentTextId) return;\n         const textItem = TEXT_DATA.list.find(t => t.id === currentTextId);\n         if (textItem) {\n           pushUndo(\"rotate text\");\n           textItem.rotation = parseInt(e.target.value) || 0;\n           document.getElementById(\"text-rotation-val\").value = textItem.rotation;\n           forgeTheTopology();\n         }\n       });\n       document.getElementById(\"text-rotation-val\").addEventListener(\"input\", (e) => {\n         if (!currentTextId) return;\n         const textItem = TEXT_DATA.list.find(t => t.id === currentTextId);\n         if (textItem) {\n           pushUndo(\"rotate text\");\n           const val = parseInt(e.target.value) || 0;\n           textItem.rotation = val;\n           document.getElementById(\"text-rotation\").value = Math.max(-360, Math.min(360, val));\n           forgeTheTopology();\n         }\n       });\n       document.getElementById(\"delete-text\").addEventListener(\"click\", () => {\n        if (!currentTextId) return;\n        const textItem = TEXT_DATA.list.find(t => t.id === currentTextId);\n        if (textItem) {\n         challengeTheImmortal(`Delete text \"${(textItem.content || '').substring(0, 30)}...\"?`, () => {\n          deleteText(currentTextId);\n         });\n        }\n       });\n       const settingsModal = document.getElementById(\"settings-modal\");\n       const settingsClose = document.getElementById(\"settings-close\");\n       settingsBtn.addEventListener(\"click\", () => {\n       document.getElementById(\"page-bg-color\").value = rgbaToHex(PAGE_STATE.background) || \"#050608\";\n       document.getElementById(\"topbar-bg-color\").value = rgbaToHex(PAGE_STATE.topbarBg) || \"#0b0e13\";\n       document.getElementById(\"topbar-border-color\").value = rgbaToHex(PAGE_STATE.topbarBorder) || \"#1f2533\";\n       document.getElementById(\"panel-color\").value = rgbaToHex(PAGE_STATE.panel) || \"#0b0e13\";\n       document.getElementById(\"panel-alt-color\").value = PAGE_STATE.panelAlt || \"#10141b\";\n       document.getElementById(\"sidebar-bg-color\").value = PAGE_STATE.sidebarBg || \"#10141b\";\n       document.getElementById(\"btn-bg-color\").value = PAGE_STATE.btnBg || \"#0b0e13\";\n       document.getElementById(\"btn-text-color\").value = PAGE_STATE.btnText || \"#e2e8f0\";\n       document.getElementById(\"tag-fill-color\").value = PAGE_STATE.tagFill || \"#1e293b\";\n       document.getElementById(\"tag-text-color\").value = PAGE_STATE.tagText || \"#e2e8f0\";\n       document.getElementById(\"tag-border-color\").value = PAGE_STATE.tagBorder || \"#475569\";\n       document.getElementById(\"input-bg-color\").value = PAGE_STATE.inputBg || \"#0b0e13\";\n       document.getElementById(\"input-text-color\").value = PAGE_STATE.inputText || \"#e2e8f0\";\n\t   document.getElementById(\"input-border-color\").value = PAGE_STATE.inputBorder || \"#1f2937\";\n       document.getElementById(\"input-font-family\").value = PAGE_STATE.inputFont || \"Inter, system-ui, sans-serif\";\n       document.getElementById(\"input-font-size\").value = PAGE_STATE.inputFontSize || 14;\n       document.getElementById(\"toolbar-bg-color\").value = PAGE_STATE.toolbarBg || \"#0f172a\";\n       document.getElementById(\"toolbar-border-color\").value = PAGE_STATE.toolbarBorder || \"#1f2937\";\n       document.getElementById(\"toolbar-text-color\").value = PAGE_STATE.toolbarText || \"#94a3b8\";\n       document.getElementById(\"toolbar-btn-bg-color\").value = PAGE_STATE.toolbarBtnBg || \"#0b0e13\";\n       document.getElementById(\"toolbar-btn-text-color\").value = PAGE_STATE.toolbarBtnText || \"#e2e8f0\";\n       document.getElementById(\"minimap-dots-color\").value = PAGE_STATE.minimapDots || \"#94a3b8\";\n       document.getElementById(\"canvas-hint-enabled\").checked = PAGE_STATE.canvasHintEnabled !== false;\n       document.getElementById(\"canvas-hint-bg-color\").value = PAGE_STATE.canvasHintBg || \"#0f172a\";\n       document.getElementById(\"canvas-hint-text-color\").value = PAGE_STATE.canvasHintColor || \"#94a3b8\";\n       document.getElementById(\"canvas-hint-text\").value = PAGE_STATE.canvasHintText || \"\";\n       document.getElementById(\"accent-color\").value = rgbaToHex(PAGE_STATE.accent) || \"#4fd1c5\";\n       document.getElementById(\"danger-color\").value = rgbaToHex(PAGE_STATE.danger) || \"#f56565\";\n       document.getElementById(\"text-main-color\").value = rgbaToHex(PAGE_STATE.textMain) || \"#e2e8f0\";\n       document.getElementById(\"text-soft-color\").value = PAGE_STATE.textSoft || \"#94a3b8\";\n       document.getElementById(\"node-fill-color\").value = PAGE_STATE.nodeFill || \"#1e293b\";\n       document.getElementById(\"node-stroke-color\").value = PAGE_STATE.nodeStroke || \"#475569\";\n       document.getElementById(\"node-title-color\").value = PAGE_STATE.nodeTitle || \"#e2e8f0\";\n       document.getElementById(\"node-sub-color\").value = PAGE_STATE.nodeSub || \"#94a3b8\";\n       document.getElementById(\"node-title-size\").value = PAGE_STATE.nodeTitleSize || 18;\n       document.getElementById(\"node-sub-size\").value = PAGE_STATE.nodeSubSize || 13;\n       document.getElementById(\"node-font-family\").value = PAGE_STATE.nodeFont || \"Inter, system-ui, sans-serif\";\n       document.getElementById(\"default-edge-color\").value = PAGE_STATE.defaultEdge || \"#475569\";\n       document.getElementById(\"add-line-color\").value = PAGE_STATE.defaultEdge || \"#475569\";\n       document.getElementById(\"default-edge-routing\").value = PAGE_STATE.defaultEdgeRouting || \"curved\";\n\t   document.getElementById(\"anim-master\").addEventListener(\"change\", e => { ANIM_SETTINGS.masterAnim = e.target.checked; applyAnimZoneSettings(); });\n\t\tdocument.getElementById(\"anim-type-sweep\").addEventListener(\"change\", e => { ANIM_SETTINGS.animTypes.sweep = e.target.checked; applyAnimZoneSettings(); });\n\t\tdocument.getElementById(\"anim-type-pulse\").addEventListener(\"change\", e => { ANIM_SETTINGS.animTypes.pulse = e.target.checked; applyAnimZoneSettings(); });\n\t\tdocument.getElementById(\"anim-type-rings\").addEventListener(\"change\", e => { ANIM_SETTINGS.animTypes.rings = e.target.checked; applyAnimZoneSettings(); });\n\t\tdocument.getElementById(\"anim-type-spin\").addEventListener(\"change\", e => { ANIM_SETTINGS.animTypes.spin = e.target.checked; applyAnimZoneSettings(); });\n\t\tdocument.getElementById(\"anim-type-connections\").addEventListener(\"change\", e => { ANIM_SETTINGS.animTypes.connections = e.target.checked; applyAnimZoneSettings(); });\n\t\tdocument.getElementById(\"anim-cat-camera\").addEventListener(\"change\", e => { ANIM_SETTINGS.animCategories.camera = e.target.checked; applyAnimZoneSettings(); });\n\t\tdocument.getElementById(\"anim-cat-doorbell\").addEventListener(\"change\", e => { ANIM_SETTINGS.animCategories.doorbell = e.target.checked; applyAnimZoneSettings(); });\n\t\tdocument.getElementById(\"anim-cat-motion\").addEventListener(\"change\", e => { ANIM_SETTINGS.animCategories.motion = e.target.checked; applyAnimZoneSettings(); });\n\t\tdocument.getElementById(\"anim-cat-smoke\").addEventListener(\"change\", e => { ANIM_SETTINGS.animCategories.smoke = e.target.checked; applyAnimZoneSettings(); });\n\t\tdocument.getElementById(\"anim-cat-wifi\").addEventListener(\"change\", e => { ANIM_SETTINGS.animCategories.wifi = e.target.checked; applyAnimZoneSettings(); });\n\t\tdocument.getElementById(\"anim-cat-sensor\").addEventListener(\"change\", e => { ANIM_SETTINGS.animCategories.sensor = e.target.checked; applyAnimZoneSettings(); });\n\t\tdocument.getElementById(\"anim-cat-sprinkler\").addEventListener(\"change\", e => { ANIM_SETTINGS.animCategories.sprinkler = e.target.checked; applyAnimZoneSettings(); });\n\t\tdocument.getElementById(\"anim-cat-connections\").addEventListener(\"change\", e => { ANIM_SETTINGS.animCategories.connections = e.target.checked; applyAnimZoneSettings(); });\n\t\tdocument.getElementById(\"zone-master\").addEventListener(\"change\", e => { ANIM_SETTINGS.masterZones = e.target.checked; applyAnimZoneSettings(); });\n\t\tdocument.getElementById(\"zone-cat-camera\").addEventListener(\"change\", e => { ANIM_SETTINGS.zoneCategories.camera = e.target.checked; applyAnimZoneSettings(); });\n\t\tdocument.getElementById(\"zone-cat-doorbell\").addEventListener(\"change\", e => { ANIM_SETTINGS.zoneCategories.doorbell = e.target.checked; applyAnimZoneSettings(); });\n\t\tdocument.getElementById(\"zone-cat-motion\").addEventListener(\"change\", e => { ANIM_SETTINGS.zoneCategories.motion = e.target.checked; applyAnimZoneSettings(); });\n\t\tdocument.getElementById(\"zone-cat-smoke\").addEventListener(\"change\", e => { ANIM_SETTINGS.zoneCategories.smoke = e.target.checked; applyAnimZoneSettings(); });\n\t\tdocument.getElementById(\"zone-cat-wifi\").addEventListener(\"change\", e => { ANIM_SETTINGS.zoneCategories.wifi = e.target.checked; applyAnimZoneSettings(); });\n\t\tdocument.getElementById(\"zone-cat-sensor\").addEventListener(\"change\", e => { ANIM_SETTINGS.zoneCategories.sensor = e.target.checked; applyAnimZoneSettings(); });\n\t\tdocument.getElementById(\"zone-cat-sprinkler\").addEventListener(\"change\", e => { ANIM_SETTINGS.zoneCategories.sprinkler = e.target.checked; applyAnimZoneSettings(); });\n       document.getElementById(\"animation-style-select\").value = PAGE_STATE.animationStyle || \"arrows\";\n       document.getElementById(\"animation-direction-select\").value = PAGE_STATE.animationDirection || \"all\";\n       document.getElementById(\"animation-speed-select\").value = PAGE_STATE.animationSpeed || 1.5;\n       document.getElementById(\"add-line-routing\").value = PAGE_STATE.defaultEdgeRouting || \"orthogonal\";\n       document.getElementById(\"selection-handle-color\").value = PAGE_STATE.selectionHandle || \"#f59e0b\";\n       document.getElementById(\"selection-handle-size\").value = PAGE_STATE.selectionHandleSize || 8;\n       document.getElementById(\"group-indicator-color\").value = PAGE_STATE.groupIndicator || \"#4fd1c5\";\n       document.getElementById(\"canvas-gradient-top\").value = PAGE_STATE.canvasGradientTop || \"#1e2532\";\n       document.getElementById(\"canvas-gradient-bottom\").value = PAGE_STATE.canvasGradientBottom || \"#050608\";\n       document.getElementById(\"canvas-border-color\").value = PAGE_STATE.canvasBorder || \"#475569\";\n       document.getElementById(\"canvas-grid-color\").value = PAGE_STATE.canvasGrid || \"#475569\";\n       document.getElementById(\"canvas-grid-size\").value = PAGE_STATE.canvasGridSize || 50;\n       document.getElementById(\"canvas-grid-enabled\").checked = PAGE_STATE.canvasGridEnabled !== false;\n       document.getElementById(\"rack-frame-fill\").value = PAGE_STATE.rackFrameFill || \"#0f172a\";\n       document.getElementById(\"rack-frame-stroke\").value = PAGE_STATE.rackFrameStroke || \"#4fd1c5\";\n       document.getElementById(\"rack-line-color\").value = PAGE_STATE.rackLineColor || \"#475569\";\n       document.getElementById(\"rack-grid-enabled\").checked = PAGE_STATE.rackGridEnabled !== false;\n       document.getElementById(\"rack-text-color\").value = PAGE_STATE.rackTextColor || \"#4fd1c5\";\n        document.getElementById(\"auto-ping-enabled\").checked = autoPingEnabled;\n        document.getElementById(\"auto-ping-interval\").value = autoPingInterval;\n        document.getElementById(\"auto-ping-settings\").style.display = autoPingEnabled ? 'block' : 'none';\n        document.getElementById(\"view-only-mode\").checked = PAGE_STATE.viewOnly === true;\n        document.getElementById(\"show-port-labels-toggle\").checked = PAGE_STATE.showPortLabels !== false;\n        document.getElementById(\"version-display\").textContent = THE_ONE_FILE_VERSION;\n        rebuildThemeDropdown();\n        updateDeleteButton();\n        settingsModal.classList.add(\"active\");\n       });\n       settingsClose.addEventListener(\"click\", () => {\n        settingsModal.classList.remove(\"active\");\n       });\n\t   document.getElementById(\"view-only-mode\").addEventListener(\"change\", (e) => {\n       PAGE_STATE.viewOnly = e.target.checked;\n       if (e.target.checked) {\n        document.getElementById(\"node-panel\").style.display = \"none\";\n        document.getElementById(\"edge-panel\").style.display = \"none\";\n        document.getElementById(\"text-panel\").style.display = \"none\";\n        document.getElementById(\"rect-panel\").style.display = \"none\";\n        document.getElementById(\"topology-toolbar\").style.setProperty('display', 'none', 'important');\n        document.getElementById(\"draw-toolbar\").style.setProperty('display', 'none', 'important');\n        currentNodeId = null;\n        currentEdgeId = null;\n        currentRectId = null;\n        currentTextId = null;\n        selectedNodes.clear();\n        selectedRects.clear();\n        selectedTexts.clear();\n        selectedEdges.clear();\n        const bulkToolbar = document.getElementById(\"bulk-toolbar\");\n        const bulkToolbarMobile = document.getElementById(\"bulk-toolbar-mobile\");\n        if (bulkToolbar) bulkToolbar.style.display = \"none\";\n        if (bulkToolbarMobile) bulkToolbarMobile.style.display = \"none\";\n        document.querySelectorAll(\".node-group\").forEach(n => n.classList.remove(\"active\", \"selected\"));\n        document.querySelectorAll(\".edge\").forEach(e => e.classList.remove(\"active\"));\n        document.querySelectorAll(\".rect-group\").forEach(r => r.classList.remove(\"active\"));\n        document.querySelectorAll(\".text-element\").forEach(t => t.classList.remove(\"active\"));\n       }\n       wieldThePower();\n       if (!e.target.checked) {\n        updateDrawToolbarVisibility();\n        updateTopologyToolbarVisibility();\n       }\n       forgeTheTopology();\n      });\n       settingsModal.addEventListener(\"click\", (e) => {\n        if (e.target === settingsModal) {\n         settingsModal.classList.remove(\"active\");\n        }\n       });\n       document.getElementById(\"page-bg-color\").addEventListener(\"input\", (e) => {\n        PAGE_STATE.background = e.target.value;\n        wieldThePower();\n       });\n       document.getElementById(\"topbar-bg-color\").addEventListener(\"input\", (e) => {\n        PAGE_STATE.topbarBg = e.target.value;\n        wieldThePower();\n       });\n       document.getElementById(\"topbar-border-color\").addEventListener(\"input\", (e) => {\n        PAGE_STATE.topbarBorder = e.target.value;\n        wieldThePower();\n       });\nconst THEME_PRESETS = {\n  defaulted: { panel:\"#0b0e13\",panelAlt:\"#10141b\",sidebarBg:\"#10141b\",btnBg:\"#0b0e13\",btnText:\"#e2e8f0\",accent:\"#4fd1c5\",danger:\"#f56565\",textMain:\"#e2e8f0\",textSoft:\"#94a3b8\",topbarBg:\"rgba(9,12,20,0.9)\",topbarBorder:\"#1f2533\",nodeFill:\"#1e293b\",nodeStroke:\"#475569\",nodeTitle:\"#e2e8f0\",nodeSub:\"#94a3b8\",defaultEdge:\"#475569\",canvasGradientTop:\"#1e2532\",canvasGradientBottom:\"#050608\",tagFill:\"#1e293b\",tagText:\"#e2e8f0\",tagBorder:\"#475569\",inputBg:\"#0b0e13\",inputText:\"#e2e8f0\",inputBorder:\"#1f2937\",toolbarBg:\"#0f172a\",toolbarBorder:\"#1f2937\",toolbarText:\"#94a3b8\",toolbarBtnBg:\"#0b0e13\",toolbarBtnText:\"#e2e8f0\",selectionHandle:\"#f59e0b\",groupIndicator:\"#4fd1c5\",minimapDots:\"#94a3b8\",canvasHintBg:\"#0f172a\",canvasHintColor:\"#94a3b8\",canvasBorder:\"#475569\",canvasGrid:\"#475569\",rackFrameFill:\"#0f172a\",rackFrameStroke:\"#4fd1c5\",rackLineColor:\"#475569\",rackTextColor:\"#4fd1c5\" },\n  slate: { panel:\"#1e293b\",panelAlt:\"#334155\",sidebarBg:\"#1e293b\",btnBg:\"#334155\",btnText:\"#f1f5f9\",accent:\"#3b82f6\",danger:\"#ef4444\",textMain:\"#f1f5f9\",textSoft:\"#94a3b8\",topbarBg:\"#0f172a\",topbarBorder:\"#334155\",nodeFill:\"#334155\",nodeStroke:\"#3b82f6\",nodeTitle:\"#f1f5f9\",nodeSub:\"#94a3b8\",defaultEdge:\"#64748b\",canvasGradientTop:\"#1e293b\",canvasGradientBottom:\"#0f172a\",tagFill:\"#1e3a5f\",tagText:\"#93c5fd\",tagBorder:\"#3b82f6\",inputBg:\"#0f172a\",inputText:\"#f1f5f9\",inputBorder:\"#475569\",toolbarBg:\"#2563eb\",toolbarBorder:\"#3b82f6\",toolbarText:\"#ffffff\",toolbarBtnBg:\"#3b82f6\",toolbarBtnText:\"#ffffff\",selectionHandle:\"#f59e0b\",groupIndicator:\"#22d3ee\",minimapDots:\"#64748b\",canvasHintBg:\"#1e293b\",canvasHintColor:\"#94a3b8\",canvasBorder:\"#334155\",canvasGrid:\"#334155\",rackFrameFill:\"#1e293b\",rackFrameStroke:\"#3b82f6\",rackLineColor:\"#475569\",rackTextColor:\"#3b82f6\" },\n  graphite: { panel:\"#1f2937\",panelAlt:\"#374151\",sidebarBg:\"#111827\",btnBg:\"#374151\",btnText:\"#f9fafb\",accent:\"#f59e0b\",danger:\"#ef4444\",textMain:\"#f9fafb\",textSoft:\"#9ca3af\",topbarBg:\"#111827\",topbarBorder:\"#4b5563\",nodeFill:\"#374151\",nodeStroke:\"#f59e0b\",nodeTitle:\"#f9fafb\",nodeSub:\"#9ca3af\",defaultEdge:\"#6b7280\",canvasGradientTop:\"#1f2937\",canvasGradientBottom:\"#111827\",tagFill:\"#44403c\",tagText:\"#fbbf24\",tagBorder:\"#f59e0b\",inputBg:\"#111827\",inputText:\"#f9fafb\",inputBorder:\"#4b5563\",toolbarBg:\"#b45309\",toolbarBorder:\"#f59e0b\",toolbarText:\"#ffffff\",toolbarBtnBg:\"#f59e0b\",toolbarBtnText:\"#000000\",selectionHandle:\"#10b981\",groupIndicator:\"#06b6d4\",minimapDots:\"#6b7280\",canvasHintBg:\"#1f2937\",canvasHintColor:\"#9ca3af\",canvasBorder:\"#4b5563\",canvasGrid:\"#374151\",rackFrameFill:\"#1f2937\",rackFrameStroke:\"#f59e0b\",rackLineColor:\"#4b5563\",rackTextColor:\"#fbbf24\" },\n  frost: { panel:\"#f8fafc\",panelAlt:\"#e2e8f0\",sidebarBg:\"#f1f5f9\",btnBg:\"#e2e8f0\",btnText:\"#1e293b\",accent:\"#1e40af\",danger:\"#dc2626\",textMain:\"#0f172a\",textSoft:\"#475569\",topbarBg:\"#1e40af\",topbarBorder:\"#1e3a8a\",nodeFill:\"#ffffff\",nodeStroke:\"#1e40af\",nodeTitle:\"#0f172a\",nodeSub:\"#475569\",defaultEdge:\"#64748b\",canvasGradientTop:\"#e0e7ef\",canvasGradientBottom:\"#f8fafc\",tagFill:\"#dbeafe\",tagText:\"#1e40af\",tagBorder:\"#3b82f6\",inputBg:\"#ffffff\",inputText:\"#0f172a\",inputBorder:\"#cbd5e1\",toolbarBg:\"#1e40af\",toolbarBorder:\"#1e3a8a\",toolbarText:\"#ffffff\",toolbarBtnBg:\"#3b82f6\",toolbarBtnText:\"#ffffff\",selectionHandle:\"#ea580c\",groupIndicator:\"#059669\",minimapDots:\"#64748b\",canvasHintBg:\"#e2e8f0\",canvasHintColor:\"#475569\",canvasBorder:\"#cbd5e1\",canvasGrid:\"#cbd5e1\",rackFrameFill:\"#f1f5f9\",rackFrameStroke:\"#1e40af\",rackLineColor:\"#94a3b8\",rackTextColor:\"#1e40af\" },\n  synthwave: { panel:\"#87366d\",panelAlt:\"#10141b\",sidebarBg:\"#340934\",btnBg:\"#0b0e13\",btnText:\"#e2e8f0\",accent:\"#4fd1c5\",danger:\"#f56565\",textMain:\"#e2e8f0\",textSoft:\"#94a3b8\",topbarBg:\"#781c67\",topbarBorder:\"#1f2533\",nodeFill:\"#1e293b\",nodeStroke:\"#475569\",nodeTitle:\"#e2e8f0\",nodeSub:\"#94a3b8\",defaultEdge:\"#475569\",canvasGradientTop:\"#1e2532\",canvasGradientBottom:\"#050608\",tagFill:\"#1e293b\",tagText:\"#e2e8f0\",tagBorder:\"#475569\",inputBg:\"#0b0e13\",inputText:\"#e2e8f0\",inputBorder:\"#1f2937\",toolbarBg:\"#b95aed\",toolbarBorder:\"#b800eb\",toolbarText:\"#000000\",toolbarBtnBg:\"#ed01fe\",toolbarBtnText:\"#000000\",selectionHandle:\"#f59e0b\",groupIndicator:\"#4fd1c5\",minimapDots:\"#000000\",canvasHintBg:\"#0f172a\",canvasHintColor:\"#94a3b8\",canvasBorder:\"#000000\",canvasGrid:\"#000000\",rackFrameFill:\"#0f172a\",rackFrameStroke:\"#4fd1c5\",rackLineColor:\"#475569\",rackTextColor:\"#4fd1c5\" },\n  terminal: { panel:\"#000000\",panelAlt:\"#0a0a0a\",sidebarBg:\"#050505\",btnBg:\"#0a0a0a\",btnText:\"#33ff33\",accent:\"#33ff33\",danger:\"#ffaa00\",textMain:\"#33ff33\",textSoft:\"#1a9a1a\",topbarBg:\"#000000\",topbarBorder:\"#33ff33\",nodeFill:\"#0a0a0a\",nodeStroke:\"#33ff33\",nodeTitle:\"#33ff33\",nodeSub:\"#1a9a1a\",defaultEdge:\"#1a9a1a\",canvasGradientTop:\"#0a0f0a\",canvasGradientBottom:\"#000000\",tagFill:\"#0a1a0a\",tagText:\"#33ff33\",tagBorder:\"#33ff33\",inputBg:\"#000000\",inputText:\"#33ff33\",inputBorder:\"#1a9a1a\",toolbarBg:\"#1a9a1a\",toolbarBorder:\"#33ff33\",toolbarText:\"#000000\",toolbarBtnBg:\"#33ff33\",toolbarBtnText:\"#000000\",selectionHandle:\"#ffaa00\",groupIndicator:\"#00ffff\",minimapDots:\"#33ff33\",canvasHintBg:\"#0a0a0a\",canvasHintColor:\"#1a9a1a\",canvasBorder:\"#1a9a1a\",canvasGrid:\"#0f1f0f\",rackFrameFill:\"#050505\",rackFrameStroke:\"#33ff33\",rackLineColor:\"#1a9a1a\",rackTextColor:\"#33ff33\" },\n  dracula: { panel:\"#282a36\",panelAlt:\"#44475a\",sidebarBg:\"#21222c\",btnBg:\"#44475a\",btnText:\"#f8f8f2\",accent:\"#bd93f9\",danger:\"#ff5555\",textMain:\"#f8f8f2\",textSoft:\"#6272a4\",topbarBg:\"#21222c\",topbarBorder:\"#6272a4\",nodeFill:\"#44475a\",nodeStroke:\"#ff79c6\",nodeTitle:\"#f8f8f2\",nodeSub:\"#8be9fd\",defaultEdge:\"#bd93f9\",canvasGradientTop:\"#282a36\",canvasGradientBottom:\"#1a1b23\",tagFill:\"#3d3f4a\",tagText:\"#50fa7b\",tagBorder:\"#50fa7b\",inputBg:\"#21222c\",inputText:\"#f8f8f2\",inputBorder:\"#6272a4\",toolbarBg:\"#6272a4\",toolbarBorder:\"#bd93f9\",toolbarText:\"#f8f8f2\",toolbarBtnBg:\"#bd93f9\",toolbarBtnText:\"#282a36\",selectionHandle:\"#f1fa8c\",groupIndicator:\"#ff79c6\",minimapDots:\"#bd93f9\",canvasHintBg:\"#282a36\",canvasHintColor:\"#6272a4\",canvasBorder:\"#44475a\",canvasGrid:\"#44475a\",rackFrameFill:\"#282a36\",rackFrameStroke:\"#ff79c6\",rackLineColor:\"#6272a4\",rackTextColor:\"#8be9fd\" },\n  cobalt: { panel:\"#002240\",panelAlt:\"#003366\",sidebarBg:\"#001b33\",btnBg:\"#003366\",btnText:\"#ffffff\",accent:\"#ffc600\",danger:\"#ff628c\",textMain:\"#ffffff\",textSoft:\"#8090a0\",topbarBg:\"#001525\",topbarBorder:\"#0088ff\",nodeFill:\"#003366\",nodeStroke:\"#0088ff\",nodeTitle:\"#ffffff\",nodeSub:\"#80ffbb\",defaultEdge:\"#0088ff\",canvasGradientTop:\"#002240\",canvasGradientBottom:\"#00111f\",tagFill:\"#004080\",tagText:\"#ffc600\",tagBorder:\"#0088ff\",inputBg:\"#001525\",inputText:\"#ffffff\",inputBorder:\"#0066cc\",toolbarBg:\"#0066cc\",toolbarBorder:\"#0088ff\",toolbarText:\"#ffffff\",toolbarBtnBg:\"#0088ff\",toolbarBtnText:\"#ffffff\",selectionHandle:\"#ffc600\",groupIndicator:\"#3ad900\",minimapDots:\"#0088ff\",canvasHintBg:\"#002240\",canvasHintColor:\"#8090a0\",canvasBorder:\"#0066cc\",canvasGrid:\"#003366\",rackFrameFill:\"#002240\",rackFrameStroke:\"#0088ff\",rackLineColor:\"#0066cc\",rackTextColor:\"#80ffbb\" },\n  solarized: { panel:\"#073642\",panelAlt:\"#002b36\",sidebarBg:\"#002b36\",btnBg:\"#073642\",btnText:\"#93a1a1\",accent:\"#268bd2\",danger:\"#dc322f\",textMain:\"#93a1a1\",textSoft:\"#657b83\",topbarBg:\"#002b36\",topbarBorder:\"#586e75\",nodeFill:\"#073642\",nodeStroke:\"#2aa198\",nodeTitle:\"#93a1a1\",nodeSub:\"#839496\",defaultEdge:\"#657b83\",canvasGradientTop:\"#073642\",canvasGradientBottom:\"#002b36\",tagFill:\"#0a4050\",tagText:\"#b58900\",tagBorder:\"#b58900\",inputBg:\"#002b36\",inputText:\"#93a1a1\",inputBorder:\"#586e75\",toolbarBg:\"#268bd2\",toolbarBorder:\"#2aa198\",toolbarText:\"#fdf6e3\",toolbarBtnBg:\"#2aa198\",toolbarBtnText:\"#002b36\",selectionHandle:\"#cb4b16\",groupIndicator:\"#d33682\",minimapDots:\"#657b83\",canvasHintBg:\"#073642\",canvasHintColor:\"#657b83\",canvasBorder:\"#586e75\",canvasGrid:\"#094050\",rackFrameFill:\"#002b36\",rackFrameStroke:\"#2aa198\",rackLineColor:\"#586e75\",rackTextColor:\"#859900\" },\n  brainwave: { panel:\"#1a1625\",panelAlt:\"#2d2640\",sidebarBg:\"#12101c\",btnBg:\"#2d2640\",btnText:\"#e8dff5\",accent:\"#9d4edd\",danger:\"#ff6b9d\",textMain:\"#e8dff5\",textSoft:\"#a89cc8\",topbarBg:\"#12101c\",topbarBorder:\"#5a189a\",nodeFill:\"#240046\",nodeStroke:\"#7b2cbf\",nodeTitle:\"#e0aaff\",nodeSub:\"#c77dff\",defaultEdge:\"#9d4edd\",canvasGradientTop:\"#1a1625\",canvasGradientBottom:\"#0d0a14\",tagFill:\"#3c096c\",tagText:\"#e0aaff\",tagBorder:\"#9d4edd\",inputBg:\"#12101c\",inputText:\"#e8dff5\",inputBorder:\"#5a189a\",toolbarBg:\"#7b2cbf\",toolbarBorder:\"#9d4edd\",toolbarText:\"#ffffff\",toolbarBtnBg:\"#9d4edd\",toolbarBtnText:\"#ffffff\",selectionHandle:\"#ff9e00\",groupIndicator:\"#00f5d4\",minimapDots:\"#9d4edd\",canvasHintBg:\"#1a1625\",canvasHintColor:\"#a89cc8\",canvasBorder:\"#5a189a\",canvasGrid:\"#2d2640\",rackFrameFill:\"#1a1625\",rackFrameStroke:\"#9d4edd\",rackLineColor:\"#5a189a\",rackTextColor:\"#e0aaff\" },\n  neonMint: { panel:\"#0d1117\",panelAlt:\"#161b22\",sidebarBg:\"#010409\",btnBg:\"#161b22\",btnText:\"#7ee8c7\",accent:\"#00ffc8\",danger:\"#ff6b6b\",textMain:\"#7ee8c7\",textSoft:\"#3fb68a\",topbarBg:\"#010409\",topbarBorder:\"#00ffc8\",nodeFill:\"#0d1f17\",nodeStroke:\"#00ffc8\",nodeTitle:\"#7ee8c7\",nodeSub:\"#3fb68a\",defaultEdge:\"#00d9a7\",canvasGradientTop:\"#0d1117\",canvasGradientBottom:\"#010409\",tagFill:\"#0d2818\",tagText:\"#00ffc8\",tagBorder:\"#00d9a7\",inputBg:\"#010409\",inputText:\"#7ee8c7\",inputBorder:\"#1a4d3a\",toolbarBg:\"#00a67e\",toolbarBorder:\"#00ffc8\",toolbarText:\"#010409\",toolbarBtnBg:\"#00ffc8\",toolbarBtnText:\"#010409\",selectionHandle:\"#ffd000\",groupIndicator:\"#ff79c6\",minimapDots:\"#00ffc8\",canvasHintBg:\"#0d1117\",canvasHintColor:\"#3fb68a\",canvasBorder:\"#1a4d3a\",canvasGrid:\"#0a2920\",rackFrameFill:\"#0d1117\",rackFrameStroke:\"#00ffc8\",rackLineColor:\"#1a4d3a\",rackTextColor:\"#00ffc8\" },\n  velvetDusk: { panel:\"#1f1d2e\",panelAlt:\"#26233a\",sidebarBg:\"#191724\",btnBg:\"#26233a\",btnText:\"#e0def4\",accent:\"#eb6f92\",danger:\"#eb6f92\",textMain:\"#e0def4\",textSoft:\"#908caa\",topbarBg:\"#191724\",topbarBorder:\"#524f67\",nodeFill:\"#26233a\",nodeStroke:\"#c4a7e7\",nodeTitle:\"#e0def4\",nodeSub:\"#9ccfd8\",defaultEdge:\"#ebbcba\",canvasGradientTop:\"#1f1d2e\",canvasGradientBottom:\"#191724\",tagFill:\"#31283d\",tagText:\"#f6c177\",tagBorder:\"#c4a7e7\",inputBg:\"#191724\",inputText:\"#e0def4\",inputBorder:\"#524f67\",toolbarBg:\"#c4a7e7\",toolbarBorder:\"#eb6f92\",toolbarText:\"#191724\",toolbarBtnBg:\"#eb6f92\",toolbarBtnText:\"#191724\",selectionHandle:\"#f6c177\",groupIndicator:\"#31748f\",minimapDots:\"#c4a7e7\",canvasHintBg:\"#1f1d2e\",canvasHintColor:\"#908caa\",canvasBorder:\"#524f67\",canvasGrid:\"#312e45\",rackFrameFill:\"#1f1d2e\",rackFrameStroke:\"#c4a7e7\",rackLineColor:\"#524f67\",rackTextColor:\"#9ccfd8\" },\n  sunburst: { panel:\"#fff7ed\",panelAlt:\"#ffedd5\",sidebarBg:\"#fffbf7\",btnBg:\"#ffedd5\",btnText:\"#9a3412\",accent:\"#f97316\",danger:\"#dc2626\",textMain:\"#431407\",textSoft:\"#9a3412\",topbarBg:\"#ea580c\",topbarBorder:\"#c2410c\",nodeFill:\"#fff7ed\",nodeStroke:\"#fb923c\",nodeTitle:\"#431407\",nodeSub:\"#9a3412\",defaultEdge:\"#fdba74\",canvasGradientTop:\"#fff7ed\",canvasGradientBottom:\"#fffbf7\",tagFill:\"#fed7aa\",tagText:\"#c2410c\",tagBorder:\"#f97316\",inputBg:\"#fffbf7\",inputText:\"#431407\",inputBorder:\"#fed7aa\",toolbarBg:\"#f97316\",toolbarBorder:\"#ea580c\",toolbarText:\"#ffffff\",toolbarBtnBg:\"#fb923c\",toolbarBtnText:\"#431407\",selectionHandle:\"#0891b2\",groupIndicator:\"#059669\",minimapDots:\"#f97316\",canvasHintBg:\"#fff7ed\",canvasHintColor:\"#9a3412\",canvasBorder:\"#fed7aa\",canvasGrid:\"#fdba74\",rackFrameFill:\"#fff7ed\",rackFrameStroke:\"#f97316\",rackLineColor:\"#fed7aa\",rackTextColor:\"#c2410c\" }\n};\ndocument.getElementById(\"theme-preset\").addEventListener(\"change\", function() {\n  updateDeleteButton();\n  var p = THEME_PRESETS[this.value];\n  if (!p && this.value.startsWith(\"mytheme-\")) {\n    var found = savedStyleSets.find(function(s) { return s.id === document.getElementById(\"theme-preset\").value; });\n    if (found) p = found.styles;\n  }\n  if (!p) return;\n  Object.assign(PAGE_STATE, p);\n  document.getElementById(\"panel-color\").value = p.panel;\n  document.getElementById(\"panel-alt-color\").value = p.panelAlt;\n  document.getElementById(\"sidebar-bg-color\").value = p.sidebarBg;\n  document.getElementById(\"btn-bg-color\").value = p.btnBg;\n  document.getElementById(\"btn-text-color\").value = p.btnText;\n  document.getElementById(\"accent-color\").value = p.accent;\n  document.getElementById(\"danger-color\").value = p.danger;\n  document.getElementById(\"text-main-color\").value = p.textMain;\n  document.getElementById(\"text-soft-color\").value = p.textSoft;\n  document.getElementById(\"topbar-border-color\").value = p.topbarBorder;\n  document.getElementById(\"node-fill-color\").value = p.nodeFill;\n  document.getElementById(\"node-stroke-color\").value = p.nodeStroke;\n  document.getElementById(\"node-title-color\").value = p.nodeTitle;\n  document.getElementById(\"node-sub-color\").value = p.nodeSub;\n  document.getElementById(\"default-edge-color\").value = p.defaultEdge;\n  document.getElementById(\"canvas-gradient-top\").value = p.canvasGradientTop;\n  document.getElementById(\"canvas-gradient-bottom\").value = p.canvasGradientBottom;\n  document.getElementById(\"tag-fill-color\").value = p.tagFill;\n  document.getElementById(\"tag-text-color\").value = p.tagText;\n  document.getElementById(\"tag-border-color\").value = p.tagBorder;\n  document.getElementById(\"input-bg-color\").value = p.inputBg;\n  document.getElementById(\"input-text-color\").value = p.inputText;\n  document.getElementById(\"input-border-color\").value = p.inputBorder;\n  document.getElementById(\"toolbar-bg-color\").value = p.toolbarBg;\n  document.getElementById(\"toolbar-border-color\").value = p.toolbarBorder;\n  document.getElementById(\"toolbar-text-color\").value = p.toolbarText;\n  document.getElementById(\"toolbar-btn-bg-color\").value = p.toolbarBtnBg;\n  document.getElementById(\"toolbar-btn-text-color\").value = p.toolbarBtnText;\n  document.getElementById(\"selection-handle-color\").value = p.selectionHandle;\n  document.getElementById(\"group-indicator-color\").value = p.groupIndicator;\n  document.getElementById(\"minimap-dots-color\").value = p.minimapDots;\n  document.getElementById(\"canvas-hint-bg-color\").value = p.canvasHintBg;\n  document.getElementById(\"canvas-hint-text-color\").value = p.canvasHintColor;\n  document.getElementById(\"canvas-border-color\").value = p.canvasBorder;\n  document.getElementById(\"canvas-grid-color\").value = p.canvasGrid;\n  document.getElementById(\"rack-frame-fill\").value = p.rackFrameFill;\n  document.getElementById(\"rack-frame-stroke\").value = p.rackFrameStroke;\n  document.getElementById(\"rack-line-color\").value = p.rackLineColor;\n  document.getElementById(\"rack-text-color\").value = p.rackTextColor;\n  wieldThePower();\n  forgeTheTopology();\n});\ndocument.querySelectorAll('#settings-modal .style-content input, #settings-modal .style-content select').forEach(el => {\n  if (el.id === 'theme-preset') return;\n  el.addEventListener('input', () => document.getElementById('theme-preset').value = '');\n  el.addEventListener('change', () => document.getElementById('theme-preset').value = '');\n});\n       document.getElementById(\"panel-color\").addEventListener(\"input\", (e) => {\n        PAGE_STATE.panel = e.target.value;\n        wieldThePower();\n       });\n       document.getElementById(\"panel-alt-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.panelAlt = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"sidebar-bg-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.sidebarBg = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"btn-bg-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.btnBg = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"btn-text-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.btnText = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"tag-fill-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.tagFill = e.target.value;\n       wieldThePower();\n       forgeTheTopology();\n      });\n      document.getElementById(\"tag-text-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.tagText = e.target.value;\n       wieldThePower();\n       forgeTheTopology();\n      });\n      document.getElementById(\"tag-border-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.tagBorder = e.target.value;\n       wieldThePower();\n       forgeTheTopology();\n      });\n      document.getElementById(\"selection-fill-color\").addEventListener(\"input\", function() {\n       selectionBoxStyle.fillColor = this.value;\n      });\n      document.getElementById(\"selection-fill-opacity\").addEventListener(\"input\", function() {\n       selectionBoxStyle.fillOpacity = parseFloat(this.value);\n       document.getElementById(\"selection-fill-opacity-val\").textContent = Math.round(this.value * 100) + \"%\";\n      });\n      document.getElementById(\"selection-stroke-color\").addEventListener(\"input\", function() {\n       selectionBoxStyle.strokeColor = this.value;\n      });\n      document.getElementById(\"selection-stroke-width\").addEventListener(\"input\", function() {\n       selectionBoxStyle.strokeWidth = parseInt(this.value) || 2;\n      });\n      document.getElementById(\"selection-stroke-style\").addEventListener(\"change\", function() {\n       selectionBoxStyle.strokeDasharray = this.value;\n      });\n      document.getElementById(\"input-bg-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.inputBg = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"input-text-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.inputText = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"input-border-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.inputBorder = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"input-font-family\").addEventListener(\"change\", (e) => {\n       PAGE_STATE.inputFont = e.target.value;\n       wieldThePower();\n      });\n\t  document.getElementById(\"input-font-size\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.inputFontSize = parseInt(e.target.value) || 14;\n       wieldThePower();\n      });\n      document.getElementById(\"toolbar-bg-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.toolbarBg = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"toolbar-border-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.toolbarBorder = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"toolbar-text-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.toolbarText = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"toolbar-btn-bg-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.toolbarBtnBg = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"toolbar-btn-text-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.toolbarBtnText = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"minimap-dots-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.minimapDots = e.target.value;\n       wieldThePower();\n       forgeTheTopology();\n      });\n      document.getElementById(\"canvas-hint-enabled\").addEventListener(\"change\", (e) => {\n       PAGE_STATE.canvasHintEnabled = e.target.checked;\n       wieldThePower();\n      });\n      document.getElementById(\"canvas-hint-bg-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.canvasHintBg = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"canvas-hint-text-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.canvasHintColor = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"canvas-hint-text\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.canvasHintText = e.target.value;\n       wieldThePower();\n      });\n       document.getElementById(\"accent-color\").addEventListener(\"input\", (e) => {\n        PAGE_STATE.accent = e.target.value;\n        wieldThePower();\n       });\n       document.getElementById(\"danger-color\").addEventListener(\"input\", (e) => {\n        PAGE_STATE.danger = e.target.value;\n        wieldThePower();\n       });\n       document.getElementById(\"text-main-color\").addEventListener(\"input\", (e) => {\n        PAGE_STATE.textMain = e.target.value;\n        wieldThePower();\n       });\n       document.getElementById(\"text-soft-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.textSoft = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"node-fill-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.nodeFill = e.target.value;\n       forgeTheTopology();\n      });\n      document.getElementById(\"node-stroke-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.nodeStroke = e.target.value;\n       forgeTheTopology();\n      });\n      document.getElementById(\"node-title-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.nodeTitle = e.target.value;\n       forgeTheTopology();\n      });\n      document.getElementById(\"node-sub-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.nodeSub = e.target.value;\n       forgeTheTopology();\n      });\n      document.getElementById(\"node-title-size\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.nodeTitleSize = parseInt(e.target.value) || 18;\n       forgeTheTopology();\n      });\n      document.getElementById(\"node-sub-size\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.nodeSubSize = parseInt(e.target.value) || 13;\n       forgeTheTopology();\n      });\n      document.getElementById(\"node-font-family\").addEventListener(\"change\", (e) => {\n       PAGE_STATE.nodeFont = e.target.value;\n       forgeTheTopology();\n      });\n      document.getElementById(\"default-edge-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.defaultEdge = e.target.value;\n       document.getElementById(\"add-line-color\").value = e.target.value;\n       forgeTheTopology();\n      });\n      document.getElementById(\"show-port-labels-toggle\").addEventListener(\"change\", (e) => {\n       PAGE_STATE.showPortLabels = e.target.checked;\n       forgeTheTopology();\n      });\n      document.getElementById(\"default-edge-routing\").addEventListener(\"change\", (e) => {\n       PAGE_STATE.defaultEdgeRouting = e.target.value;\n       document.getElementById(\"add-line-routing\").value = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"apply-routing-all\").addEventListener(\"click\", async () => {\n       const routing = document.getElementById(\"default-edge-routing\").value;\n       if (!await showConfirm(t(\"dialogs.applyRoutingToAll\", { routing: routing, count: EDGE_DATA.list.length }))) return;\n       pushUndo(\"apply routing to all\");\n       EDGE_DATA.list.forEach(edge => {\n        edge.routing = routing;\n       });\n       forgeTheTopology();\n      });\n      document.getElementById(\"selection-handle-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.selectionHandle = e.target.value;\n       forgeTheTopology();\n      });\n      document.getElementById(\"selection-handle-size\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.selectionHandleSize = parseInt(e.target.value) || 8;\n       forgeTheTopology();\n      });\n      document.getElementById(\"group-indicator-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.groupIndicator = e.target.value;\n       forgeTheTopology();\n      });\n      document.getElementById(\"canvas-gradient-top\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.canvasGradientTop = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"canvas-gradient-bottom\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.canvasGradientBottom = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"canvas-border-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.canvasBorder = e.target.value;\n       forgeTheTopology();\n      });\n      document.getElementById(\"canvas-grid-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.canvasGrid = e.target.value;\n       forgeTheTopology();\n      });\n      document.getElementById(\"canvas-grid-size\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.canvasGridSize = parseInt(e.target.value) || 50;\n       forgeTheTopology();\n      });\n      document.getElementById(\"canvas-grid-enabled\").addEventListener(\"change\", (e) => {\n       PAGE_STATE.canvasGridEnabled = e.target.checked;\n       forgeTheTopology();\n      });\n      document.getElementById(\"rack-frame-fill\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.rackFrameFill = e.target.value;\n       forgeTheTopology();\n      });\n      document.getElementById(\"rack-frame-stroke\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.rackFrameStroke = e.target.value;\n       forgeTheTopology();\n      });\n      document.getElementById(\"rack-line-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.rackLineColor = e.target.value;\n       forgeTheTopology();\n      });\n      document.getElementById(\"rack-grid-enabled\").addEventListener(\"change\", (e) => {\n       PAGE_STATE.rackGridEnabled = e.target.checked;\n       forgeTheTopology();\n      });\n      document.getElementById(\"rack-text-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.rackTextColor = e.target.value;\n       forgeTheTopology();\n      });\n      (function initializeResizers() {\n         const headerResizer = document.getElementById('header-resizer');\n         const sidebarResizer = document.getElementById('sidebar-resizer');\n         const mobileFooterResizer = document.getElementById('mobile-footer-resizer');\n         let isResizing = false;\n         let currentResizer = null;\n         let startY = 0;\n         let startX = 0;\n         let startHeight = 0;\n         let startWidth = 0;\n         function getClientPos(e) {\n           if (e.touches && e.touches.length > 0) {\n             return { x: e.touches[0].clientX, y: e.touches[0].clientY };\n           }\n           return { x: e.clientX, y: e.clientY };\n         }\n         function startResize(resizer, type, e) {\n           isResizing = true;\n           currentResizer = type;\n           const pos = getClientPos(e);\n           if (type === 'header') {\n             startY = pos.y;\n             startHeight = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--topbar-height'));\n           } else if (type === 'sidebar') {\n             startX = pos.x;\n             startWidth = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--sidebar-width'));\n           } else if (type === 'mobile-footer') {\n             startY = pos.y;\n             const currentVh = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--mobile-footer-height'));\n             startHeight = (currentVh / 100) * window.innerHeight;\n           }\n           resizer.classList.add('resizing');\n           document.body.classList.add('resizing');\n           document.body.style.cursor = (type === 'sidebar') ? 'col-resize' : 'row-resize';\n           e.preventDefault();\n         }\n         if (headerResizer) {\n           headerResizer.addEventListener('mousedown', (e) => startResize(headerResizer, 'header', e));\n           headerResizer.addEventListener('touchstart', (e) => startResize(headerResizer, 'header', e), { passive: false });\n         }\n         if (sidebarResizer) {\n           sidebarResizer.addEventListener('mousedown', (e) => startResize(sidebarResizer, 'sidebar', e));\n           sidebarResizer.addEventListener('touchstart', (e) => startResize(sidebarResizer, 'sidebar', e), { passive: false });\n         }\n         if (mobileFooterResizer) {\n           mobileFooterResizer.addEventListener('mousedown', (e) => startResize(mobileFooterResizer, 'mobile-footer', e));\n           mobileFooterResizer.addEventListener('touchstart', (e) => startResize(mobileFooterResizer, 'mobile-footer', e), { passive: false });\n         }\n         function handleMove(e) {\n           if (!isResizing) return;\n           const pos = getClientPos(e);\n           if (currentResizer === 'header') {\n             const deltaY = pos.y - startY;\n             const newHeight = Math.max(40, Math.min(150, startHeight + deltaY));\n             document.documentElement.style.setProperty('--topbar-height', newHeight + 'px');\n           } else if (currentResizer === 'sidebar') {\n             const deltaX = startX - pos.x;\n             const newWidth = Math.max(200, Math.min(800, startWidth + deltaX));\n             document.documentElement.style.setProperty('--sidebar-width', newWidth + 'px');\n           } else if (currentResizer === 'mobile-footer') {\n             const deltaY = startY - pos.y;\n             const newHeight = startHeight + deltaY;\n             const newVh = Math.max(15, Math.min(80, (newHeight / window.innerHeight) * 100));\n             document.documentElement.style.setProperty('--mobile-footer-height', newVh + 'vh');\n           }\n           e.preventDefault();\n         }\n         document.addEventListener('mousemove', handleMove);\n         document.addEventListener('touchmove', handleMove, { passive: false });\n         function handleEnd() {\n           if (isResizing) {\n             isResizing = false;\n             if (currentResizer === 'header') {\n               PAGE_STATE.topbarHeight = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--topbar-height'));\n               headerResizer.classList.remove('resizing');\n             } else if (currentResizer === 'sidebar') {\n               PAGE_STATE.sidebarWidth = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--sidebar-width'));\n               sidebarResizer.classList.remove('resizing');\n             } else if (currentResizer === 'mobile-footer') {\n               PAGE_STATE.mobileFooterHeight = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--mobile-footer-height'));\n               mobileFooterResizer.classList.remove('resizing');\n             }\n             document.body.classList.remove('resizing');\n             document.body.style.cursor = '';\n             currentResizer = null;\n           }\n         }\n         document.addEventListener('mouseup', handleEnd);\n         document.addEventListener('touchend', handleEnd);\n         document.addEventListener('touchcancel', handleEnd);\n       })();\n       document.getElementById(\"auto-ping-enabled\").addEventListener(\"change\", (e) => {\n        autoPingEnabled = e.target.checked;\n        PAGE_STATE.autoPingEnabled = autoPingEnabled;\n        document.getElementById(\"auto-ping-settings\").style.display = autoPingEnabled ? 'block' : 'none';\n        if (autoPingEnabled) {\n         startAutoPing();\n        } else {\n         stopAutoPing();\n        }\n       });\n       document.getElementById(\"auto-ping-interval\").addEventListener(\"change\", (e) => {\n        const newInterval = parseInt(e.target.value, 10);\n        if (newInterval >= 5 && newInterval <= 3600) {\n         autoPingInterval = newInterval;\n         PAGE_STATE.autoPingInterval = autoPingInterval;\n         if (autoPingEnabled) {\n          startAutoPing();\n         }\n        }\n       });\n       document.getElementById(\"import-data-file\").addEventListener(\"change\", async (e) => {\n        const file = e.target.files[0];\n        if (!file) return;\n        try {\n         const text = await file.text();\n         const data = JSON.parse(text);\n         if (!data.nodeData || !data.edgeData) {\n          showAlert(t(\"dialogs.invalidDataFile\"));\n          return;\n         }\n         if (!await showConfirm(t(\"dialogs.confirmImportJson\", { nodeCount: Object.keys(data.nodeData).length, edgeCount: data.edgeData.list?.length || 0, tabCount: data.documentTabs?.length || 1 }))) {\n          e.target.value = \"\";\n          return;\n         }\n\t\t pushUndo('import json');\n         NODE_DATA = data.nodeData || {};\n         EDGE_DATA = data.edgeData || {\n          list: []\n         };\n         EDGE_LEGEND = data.edgeLegend || {};\n         RECT_DATA = data.rectData || { list: [] };\n         TEXT_DATA = data.textData || { list: [] };\n         IMAGE_DATA = data.imageData || { list: [] };\n         savedPositions = data.nodePositions || {};\n         savedSizes = data.nodeSizes || {};\n         savedStyles = data.nodeStyles || {};\n         if (data.page) {\n          PAGE_STATE = Object.assign({}, DEFAULT_PAGE_STATE, data.page);\n          wieldThePower();\n         }\n      if (data.autoPingEnabled !== undefined) {\n      autoPingEnabled = data.autoPingEnabled;\n      PAGE_STATE.autoPingEnabled = autoPingEnabled;\n      document.getElementById(\"auto-ping-enabled\").checked = autoPingEnabled;\n      }\n      if (data.autoPingInterval !== undefined) {\n      autoPingInterval = data.autoPingInterval;\n      PAGE_STATE.autoPingInterval = autoPingInterval;\n      document.getElementById(\"auto-ping-interval\").value = autoPingInterval;\n      }\n         if (data.canvas) {\n          canvasState.zoom = data.canvas.zoom || 1;\n          canvasState.panX = data.canvas.panX || 0;\n          canvasState.panY = data.canvas.panY || 0;\n         }\n         if (data.page?.title) {\n         document.title = data.page.title;\n         document.querySelector(\".editable-page-title\", ).textContent = data.page.title;\n        }\n        if (data.savedStyleSets) {\n          savedStyleSets = data.savedStyleSets;\n        }\n        if (data.documentTabs) {\n         documentTabs = data.documentTabs;\n         currentTabIndex = data.currentTabIndex || 0;\n        }\n        if (data.savedTopologyView) {\n         savedTopologyView = data.savedTopologyView;\n        }\n        if (data.encryptedSections) {\n         encryptedSections = data.encryptedSections;\n        }\n        if (data.auditLog && Array.isArray(data.auditLog)) {\n         auditLog = data.auditLog;\n         try {\n           localStorage.setItem(AUDIT_STORAGE_KEY, JSON.stringify(auditLog));\n         } catch (e) {\n           console.warn(\"Failed to sync audit log to localStorage:\", e);\n         }\n        }\n        if (data.recordings && Array.isArray(data.recordings)) {\n          RECORDING_STATE.recordings = data.recordings;\n          RECORDING_STATE.currentRecording = data.recordings[0] || null;\n        }\n        const layerMap = { \"physical\": \"layer1\", \"logical\": \"layer2\", \"security\": \"layer3\", \"application\": \"layer4\" };\n        Object.values(NODE_DATA).forEach(node => {\n          if (!node.tags) node.tags = [];\n          if (!node.notes) node.notes = [];\n          if (!node.layer || layerMap[node.layer]) node.layer = layerMap[node.layer] || 'layer1';\n        });\n        EDGE_DATA.list.forEach(edge => {\n          if (!edge.notes) edge.notes = [];\n        });\n        forgeTheTopology();\n        if (typeof forgeTheLegend === 'function') forgeTheLegend();\n        if (typeof updateZoneLegend === 'function') updateZoneLegend();\n        const nodeCount = Object.keys(data.nodeData).length;\n        const edgeCount = data.edgeData.list?.length || 0;\n        const rectCount = data.rectData?.list?.length || 0;\n        const textCount = data.textData?.list?.length || 0;\n        logAuditEvent(\"import\", `Imported JSON: ${file.name} (${nodeCount} nodes, ${edgeCount} connections)`);\n        updateViewBox();\n      if (autoPingEnabled) {\n      startAutoPing();\n      } else {\n      stopAutoPing();\n      }\n         const nodeIds = Object.keys(NODE_DATA);\n         if (nodeIds.length > 0) {\n          claimTheImmortal(nodeIds[0]);\n         } else {\n          document.getElementById(\"node-panel\").style.display = \"none\";\n          document.getElementById(\"edge-panel\").style.display = \"none\";\n          document.getElementById(\"topology-toolbar\").style.display = \"none\";\n         }\n         updateDrawToolbarVisibility();\n         showAlert(t(\"dialogs.jsonImportSuccess\", { nodeCount, edgeCount, rectCount, textCount }));\n         e.target.value = \"\";\n        } catch (err) {\n         console.error(\"Import error:\", err);\n         showAlert(t(\"dialogs.importDataFailed\", { error: err.message }));\n         e.target.value = \"\";\n        }\n       });\n       const saveHelpBtn = document.getElementById(\"save-help-btn\");\n       const saveInfoModal = document.getElementById(\"save-info-modal\");\n       const saveInfoClose = document.getElementById(\"save-info-close\");\n       saveHelpBtn.addEventListener(\"click\", () => {\n        saveInfoModal.classList.add(\"active\");\n       });\n       saveInfoClose.addEventListener(\"click\", () => {\n        saveInfoModal.classList.remove(\"active\");\n       });\n       saveInfoModal.addEventListener(\"click\", (e) => {\n       if (e.target === saveInfoModal) {\n        saveInfoModal.classList.remove(\"active\");\n       }\n      });\n      document.querySelectorAll(\".help-tab\").forEach(tab => {\n        tab.addEventListener(\"click\", () => {\n          document.querySelectorAll(\".help-tab\").forEach(t => { t.style.background = \"var(--panel)\"; t.style.color = \"var(--text-main)\"; });\n          tab.style.background = \"var(--accent)\"; tab.style.color = \"var(--bg)\";\n          document.querySelectorAll(\".help-tab-content\").forEach(c => c.style.display = \"none\");\n          document.getElementById(\"help-tab-\" + tab.dataset.tab).style.display = \"block\";\n        });\n      });\n      async function deriveKey(password, salt) {\n      const encoder = new TextEncoder();\n      const keyMaterial = await crypto.subtle.importKey(\n      \"raw\",\n      encoder.encode(password),\n      \"PBKDF2\",\n      false,\n      [\"deriveKey\"]\n      );\n      return crypto.subtle.deriveKey(\n      {\n      name: \"PBKDF2\",\n      salt: salt,\n      iterations: 200000,\n      hash: \"SHA-256\"\n      },\n      keyMaterial,\n      {\n      name: \"AES-GCM\",\n      length: 256\n      },\n      false,\n      [\"encrypt\", \"decrypt\"]\n      );\n      }\n       async function encryptData(data, password) {\n      const encoder = new TextEncoder();\n      const salt = crypto.getRandomValues(new Uint8Array(16));\n      const iv   = crypto.getRandomValues(new Uint8Array(12));\n      const key = await deriveKey(password, salt);\n      const encrypted = await crypto.subtle.encrypt(\n      { name: \"AES-GCM\", iv },\n      key,\n      encoder.encode(data)\n      );\n      const encryptedU8 = new Uint8Array(encrypted);\n      const result = new Uint8Array(salt.length + iv.length + encryptedU8.length);\n      result.set(salt, 0);\n      result.set(iv, salt.length);\n      result.set(encryptedU8, salt.length + iv.length);\n      return \"ENCRYPTED:\" + u8ToBase64(result);\n      }\n       async function decryptData(encryptedData, password) {\n      const base64Data = encryptedData.replace(\"ENCRYPTED:\", \"\");\n      const fullData   = base64ToU8(base64Data);\n      const salt      = fullData.slice(0, 16);\n      const iv        = fullData.slice(16, 28);\n      const encrypted = fullData.slice(28);\n      const key = await deriveKey(password, salt);\n      const decrypted = await crypto.subtle.decrypt(\n      { name: \"AES-GCM\", iv },\n      key,\n      encrypted\n      );\n      const decoder = new TextDecoder();\n      return decoder.decode(decrypted);\n      }\n       function isEncrypted(data) {\n        return typeof data === \"string\" && data.startsWith(\"ENCRYPTED:\");\n       }\n       function captureTheQuickening() {\n      const currentTab = documentTabs[currentTabIndex];\n      currentTab.nodes = JSON.parse(JSON.stringify(NODE_DATA));\n      currentTab.edges = JSON.parse(JSON.stringify(EDGE_DATA));\n      currentTab.positions = JSON.parse(JSON.stringify(savedPositions));\n      currentTab.sizes = JSON.parse(JSON.stringify(savedSizes));\n      currentTab.styles = JSON.parse(JSON.stringify(savedStyles));\n      currentTab.legend = JSON.parse(JSON.stringify(EDGE_LEGEND));\n      currentTab.rects = JSON.parse(JSON.stringify(RECT_DATA));\n      currentTab.texts = JSON.parse(JSON.stringify(TEXT_DATA));\n      currentTab.images = JSON.parse(JSON.stringify(IMAGE_DATA));\n      currentTab.pageState = JSON.parse(JSON.stringify(PAGE_STATE));\n      return {\n      nodeData: NODE_DATA,\n       edgeData: EDGE_DATA,\n       rectData: RECT_DATA,\n       textData: TEXT_DATA,\n       imageData: IMAGE_DATA,\n       edgeLegend: EDGE_LEGEND,\n       zoneLegend: ZONE_LEGEND,\n       zonePresets: ZONE_PRESETS,\n       nodePositions: savedPositions,\n       nodeSizes: savedSizes,\n       nodeStyles: savedStyles,\n       iconCache: IconLibrary.iconCache,\n       page: PAGE_STATE,\n       autoPingEnabled: autoPingEnabled,\n       autoPingInterval: autoPingInterval,\n       canvas: {\n         zoom: canvasState.zoom,\n         panX: canvasState.panX,\n         panY: canvasState.panY,\n       },\n       savedTopologyView: savedTopologyView,\n       documentTabs: documentTabs,\n       currentTabIndex: currentTabIndex,\n       encryptedSections: encryptedSections,\n\t   auditLog: auditLog,\n       savedStyleSets: savedStyleSets,\n       recordings: RECORDING_STATE.recordings,\n       customLanguage: CUSTOM_LANG || null,\n       };\n      }\n       function assembleTheImmortalForm() {\n\t   const canvasHintEl = document.getElementById(\"canvas-hint\");\n       const savedHintHTML = canvasHintEl ? canvasHintEl.innerHTML : \"\";\n       if (canvasHintEl) canvasHintEl.innerHTML = DEFAULT_CANVAS_HINT;\n       const clone = document.documentElement.cloneNode(true);\n       [\"edge-legend-mini\", \"minimap-mini\", \"draw-toolbar-mini\", \"topology-toolbar-mini\"].forEach(id => {\n        const all = clone.querySelectorAll(\"#\" + id);\n        for (let i = 1; i < all.length; i++) {\n         all[i].remove();\n        }\n       });\n       const addLineSelect = clone.querySelector(\"#add-line-select\");\n       if (addLineSelect) addLineSelect.innerHTML = \"\";\n       const minimapSvg = clone.querySelector(\"#minimap\");\n       if (minimapSvg) {\n        minimapSvg.querySelectorAll(\".minimap-node, .minimap-edge\").forEach(el => el.remove());\n       }\n       const canvasGrid = clone.querySelector(\"#canvas-grid\");\n       if (canvasGrid) canvasGrid.innerHTML = \"\";\n       const mainSvg = clone.querySelector(\"#map\");\n       if (mainSvg) mainSvg.innerHTML = \"\";\n       const nodeScript = clone.querySelector(\"#nodes-json\");\n        if (nodeScript) {\n         nodeScript.textContent = JSON.stringify(NODE_DATA, null, 2);\n        }\n        let stateScript = clone.querySelector(\"#topology-state\");\n        if (!stateScript) {\n         stateScript = document.createElement(\"script\");\n         stateScript.id = \"topology-state\";\n         stateScript.type = \"application/json\";\n         const body = clone.querySelector(\"body\") || clone;\n         body.appendChild(stateScript);\n        }\n        stateScript.textContent = JSON.stringify(captureTheQuickening(), null, 2);\n        return \" <!DOCTYPE html> \\n \" + clone.outerHTML;\n       }\n       async function becomeImmortal() {\n        if (RECORDING_STATE.isRecording) stopRecording();\n        if (RECORDING_STATE.isStepRecording) stopStepRecording();\n        const encryptEnabled = document.getElementById(\"encrypt-toggle\").checked;\n        let stateData = JSON.stringify(captureTheQuickening(), null, 2);\n        if (encryptEnabled) {\n         const password = await showPrompt(t(\"dialogs.enterEncryptPassword\"));\n         if (!password) {\n          showAlert(t(\"dialogs.encryptionCancelledNotSaved\"));\n          return;\n         }\n         const confirmPassword = await showPrompt(t(\"dialogs.confirmEncryptPassword\"));\n         if (password !== confirmPassword) {\n          showAlert(t(\"dialogs.passwordsMismatchNotSaved\"));\n          return;\n         }\n         try {\n          stateData = await encryptData(stateData, password);\n         } catch (e) {\n          showAlert(t(\"dialogs.encryptionFailedError\", { error: e.message }));\n          return;\n         }\n        }\n       const canvasHintEl = document.getElementById(\"canvas-hint\");\n       const savedHintHTML = canvasHintEl ? canvasHintEl.innerHTML : \"\";\n       if (canvasHintEl) canvasHintEl.innerHTML = DEFAULT_CANVAS_HINT;\n       const clone = document.documentElement.cloneNode(true);\n       [\"edge-legend-mini\", \"minimap-mini\", \"draw-toolbar-mini\", \"topology-toolbar-mini\"].forEach(id => {\n        const all = clone.querySelectorAll(\"#\" + id);\n        for (let i = 1; i < all.length; i++) {\n         all[i].remove();\n        }\n       });\n       const selectsToClear = [\"#add-line-select\", \"#new-edge-from\", \"#new-edge-to\", \"#assigned-rack-select\"];\n       selectsToClear.forEach(sel => {\n        const el = clone.querySelector(sel);\n        if (el) el.innerHTML = \"\";\n       });\n       const minimapSvg = clone.querySelector(\"#minimap\");\n       if (minimapSvg) minimapSvg.querySelectorAll(\".minimap-node, .minimap-edge\").forEach(el => el.remove());\n       const mainSvg = clone.querySelector(\"#map\");\n       if (mainSvg) mainSvg.innerHTML = \"\";\n       const edgeLegendContent = clone.querySelector(\"#edge-legend-content\");\n       if (edgeLegendContent) edgeLegendContent.innerHTML = \"\";\n       const nodeTags = clone.querySelector(\"#node-tags\");\n       if (nodeTags) nodeTags.innerHTML = \"\";\n       const nodeNotes = clone.querySelector(\"#node-notes\");\n       if (nodeNotes) nodeNotes.innerHTML = \"\";\n       const edgeNotes = clone.querySelector(\"#edge-notes\");\n       if (edgeNotes) edgeNotes.innerHTML = \"\";\n       const tabBar = clone.querySelector(\"#tab-bar\");\n       if (tabBar) tabBar.innerHTML = \"\";\n       const rollbackList = clone.querySelector(\"#rollback-list\");\n       if (rollbackList) rollbackList.innerHTML = \"\";\n       const auditList = clone.querySelector(\"#audit-log-list\");\n       if (auditList) auditList.innerHTML = \"\";\n       clone.querySelectorAll(\".ping-indicator\").forEach(el => el.remove());\n       const nodeScript = clone.querySelector(\"#nodes-json\");\n        if (nodeScript) {\n         if (encryptEnabled) {\n          nodeScript.textContent = JSON.stringify({}, null, 2);\n         } else {\n          nodeScript.textContent = JSON.stringify(NODE_DATA, null, 2);\n         }\n        }\n        let stateScript = clone.querySelector(\"#topology-state\");\n        if (!stateScript) {\n         stateScript = document.createElement(\"script\");\n         stateScript.id = \"topology-state\";\n         stateScript.type = \"application/json\";\n         const body = clone.querySelector(\"body\") || clone;\n         body.appendChild(stateScript);\n        }\n        stateScript.textContent = stateData;\n        const html = \"<!DOCTYPE html> \\n \" + clone.outerHTML;\n        const blob = new Blob([html], {\n         type: \"text/html\"\n        });\n        const url = URL.createObjectURL(blob);\n        const a = document.createElement(\"a\");\n        a.href = url;\n        const safeTitle = (PAGE_STATE.title || document.title || \"network-topology\").toLowerCase().replace(/[^a-z0-9\\-]+/g, \"-\");\n        a.download = safeTitle + \".html\";\n        document.body.appendChild(a);\n        a.click();\n        document.body.removeChild(a);\n        URL.revokeObjectURL(url);\n\t\tif (canvasHintEl) canvasHintEl.innerHTML = savedHintHTML;\n        saveRollbackVersion(\"Manual save\");\n\t\tlogAuditEvent(\"save\", `File saved: ${safeTitle}.html`);\n       }\n      function captureState() {\n\t\t const clone = typeof structuredClone === 'function' \n\t\t   ? (o) => structuredClone(o)\n\t\t   : (o) => JSON.parse(JSON.stringify(o));\n\t\t return {\n\t\t  nodes: clone(NODE_DATA),\n\t\t  edges: clone(EDGE_DATA),\n\t\t  positions: clone(savedPositions),\n\t\t  sizes: clone(savedSizes),\n\t\t  styles: clone(savedStyles),\n\t\t  legend: clone(EDGE_LEGEND),\n\t\t  rects: clone(RECT_DATA),\n\t\t  texts: clone(TEXT_DATA)\n\t\t };\n\t\t}\n      let lastUndoPush = 0;\n\t   function pushUndo(action = \"\") {\n\t   const now = Date.now();\n\t   if (now - lastUndoPush < 100 && undoStack.length > 0) {\n\t    return;\n\t   }\n\t   lastUndoPush = now;\n\t   const state = captureState();\n\t   undoStack.push(state);\n       if (undoStack.length > MAX_UNDO_STACK) {\n        undoStack.shift();\n       }\n       redoStack = [];\n       updateUndoButtons();\n       if (action) {\n        const actionTypeMap = {\n          \"create node\": \"node\",\n          \"delete node\": \"node\",\n          \"add node\": \"node\",\n          \"edit\": \"node\",\n          \"clone node\": \"node\",\n          \"paste node\": \"node\",\n          \"move nodes\": \"node\",\n          \"nudge\": \"node\",\n          \"nudge nodes\": \"node\",\n          \"align nodes\": \"node\",\n          \"distribute nodes\": \"node\",\n          \"snap to grid\": \"node\",\n          \"toggle group\": \"node\",\n          \"toggle lock\": \"node\",\n          \"create rack\": \"rack\",\n          \"add rack\": \"rack\",\n          \"edit rack\": \"rack\",\n          \"edit mac\": \"rack\",\n          \"edit U height\": \"rack\",\n          \"change rack capacity\": \"rack\",\n          \"change assigned rack\": \"rack\",\n          \"add connection\": \"connection\",\n          \"delete connection\": \"connection\",\n          \"delete edge\": \"connection\",\n          \"clone edge\": \"connection\",\n          \"paste edge\": \"connection\",\n          \"style change\": \"style\",\n          \"change layer\": \"layer\",\n          \"add text\": \"text\",\n          \"edit text\": \"text\",\n          \"delete text\": \"text\",\n          \"clone text\": \"text\",\n          \"paste text\": \"text\",\n          \"draw zone\": \"zone\",\n          \"delete zone\": \"zone\",\n          \"delete rect\": \"zone\",\n          \"clone rect\": \"zone\",\n          \"paste rect\": \"zone\",\n          \"change zone line style\": \"zone\",\n          \"delete selected\": \"bulk\",\n          \"clone selected\": \"bulk\",\n        };\n        const type = actionTypeMap[action] || \"edit\";\n        logAuditEvent(type, action);\n       }\n      }\n      function undo() {\n       if (undoStack.length === 0) return;\n       const currentState = captureState();\n       redoStack.push(currentState);\n       const previousState = undoStack.pop();\n       restoreState(previousState);\n       updateUndoButtons();\n       logAuditEvent(\"undo\", \"Undo action performed\");\n      }\n      function redo() {\n       if (redoStack.length === 0) return;\n       logAuditEvent(\"redo\", \"Redo action performed\");\n       const currentState = captureState();\n       undoStack.push(currentState);\n       const nextState = redoStack.pop();\n       restoreState(nextState);\n       updateUndoButtons();\n      }\n      function restoreState(state) {\n      NODE_DATA = state.nodes;\n       EDGE_DATA = state.edges;\n       savedPositions = state.positions;\n       savedSizes = state.sizes;\n       savedStyles = state.styles;\n       EDGE_LEGEND = state.legend;\n       RECT_DATA = state.rects || { list: [] };\n       TEXT_DATA = state.texts || { list: [] };\n       forgeTheTopology();\n       if (currentNodeId && NODE_DATA[currentNodeId]) {\n       claimTheImmortal(currentNodeId);\n       } else if (currentEdgeId) {\n       selectTheConnection(currentEdgeId);\n       }\n      }\n      function updateUndoButtons() {\n       const undoBtn = document.getElementById(\"undo-btn\");\n       const redoBtn = document.getElementById(\"redo-btn\");\n       if (undoBtn) {\n        undoBtn.disabled = undoStack.length === 0;\n        undoBtn.style.opacity = undoStack.length === 0 ? \"0.5\" : \"1\";\n       }\n       if (redoBtn) {\n        redoBtn.disabled = redoStack.length === 0;\n        redoBtn.style.opacity = redoStack.length === 0 ? \"0.5\" : \"1\";\n       }\n      }\n      function clearSelection() {\n       selectedNodes.clear();\n       selectedEdges.clear();\n       selectedRects.clear();\n       selectedTexts.clear();\n       updateAllSelections();\n      }\n      function updateAllSelections() {\n      updateNodeSelection();\n      clearSearchHighlight();\n      document.querySelectorAll(\".edge\").forEach(el => {\n      const edgeId = el.dataset.edgeId;\n      if (selectedEdges.has(edgeId)) {\n      el.style.filter = \"drop-shadow(0 0 8px #4fd1c5)\";\n      } else {\n      el.style.filter = \"\";\n      }\n      });\n      document.querySelectorAll(\".rect-group\").forEach(el => {\n      const rectId = el.dataset.rectId;\n      if (selectedRects.has(rectId)) {\n      el.style.filter = \"drop-shadow(0 0 8px #4fd1c5)\";\n      } else {\n      el.style.filter = \"\";\n      }\n      });\n      document.querySelectorAll(\".text-group\").forEach(el => {\n      const textId = el.dataset.textId;\n      if (selectedTexts.has(textId)) {\n      el.style.filter = \"drop-shadow(0 0 8px #4fd1c5)\";\n      } else {\n      el.style.filter = \"\";\n      }\n      });\n      const total = selectedNodes.size + selectedEdges.size + selectedRects.size + selectedTexts.size;\n      const bulkToolbar = document.getElementById(\"bulk-toolbar\");\n      const bulkToolbarMobile = document.getElementById(\"bulk-toolbar-mobile\");\n      const bulkCount = document.getElementById(\"bulk-count\");\n      const bulkCountMobile = document.getElementById(\"bulk-count-mobile\");\n      if (total > 0) {\n      if (bulkToolbar) bulkToolbar.style.display = \"flex\";\n      if (bulkToolbarMobile) bulkToolbarMobile.style.display = \"flex\";\n      if (bulkCount) bulkCount.textContent = total;\n      if (bulkCountMobile) bulkCountMobile.textContent = total;\n      } else {\n      if (bulkToolbar) bulkToolbar.style.display = \"none\";\n      if (bulkToolbarMobile) bulkToolbarMobile.style.display = \"none\";\n      }\n      }\n      function updateNodeSelection() {\n       if (isViewOnly()) {\n        selectedNodes.clear();\n        selectedRects.clear();\n        selectedTexts.clear();\n        const bulkToolbar = document.getElementById(\"bulk-toolbar\");\n        const bulkToolbarMobile = document.getElementById(\"bulk-toolbar-mobile\");\n        if (bulkToolbar) bulkToolbar.style.display = \"none\";\n        if (bulkToolbarMobile) bulkToolbarMobile.style.display = \"none\";\n        return;\n       }\n       document.querySelectorAll(\".node-group\").forEach(node => {\n        const nodeId = node.dataset.nodeId;\n        if (selectedNodes.has(nodeId)) {\n         node.classList.add(\"selected\");\n        } else {\n         node.classList.remove(\"selected\");\n        }\n       });\n       const bulkToolbar = document.getElementById(\"bulk-toolbar\");\n       const bulkToolbarMobile = document.getElementById(\"bulk-toolbar-mobile\");\n       const bulkCount = document.getElementById(\"bulk-count\");\n       const bulkCountMobile = document.getElementById(\"bulk-count-mobile\");\n       const bulkCountModal = document.getElementById(\"bulk-count-modal\");\n       if (selectedNodes.size > 0) {\n        if (bulkToolbar) bulkToolbar.style.display = \"flex\";\n        if (bulkToolbarMobile) bulkToolbarMobile.style.display = \"flex\";\n        if (bulkCount) bulkCount.textContent = selectedNodes.size;\n        if (bulkCountMobile) bulkCountMobile.textContent = selectedNodes.size;\n        if (bulkCountModal) bulkCountModal.textContent = selectedNodes.size;\n       } else {\n        if (bulkToolbar) bulkToolbar.style.display = \"none\";\n        if (bulkToolbarMobile) bulkToolbarMobile.style.display = \"none\";\n        const modal = document.getElementById(\"bulk-actions-modal\");\n        if (modal) modal.style.display = \"none\";\n       }\n      }\n      function deleteSelected() {\n       const total = selectedNodes.size + selectedEdges.size + selectedRects.size + selectedTexts.size;\n       if (total === 0) return;\n       let nodesInsideRacks = [];\n       selectedNodes.forEach(nodeId => {\n        if (NODE_DATA[nodeId]?.isRack) {\n         Object.entries(NODE_DATA).forEach(([id, n]) => {\n          if (n.assignedRack === nodeId) nodesInsideRacks.push(n.name || id);\n         });\n        }\n       });\n       let message = `Delete ${total} selected item(s)?`;\n       if (nodesInsideRacks.length > 0) {\n        message += `\\n\\nThis will also delete ${nodesInsideRacks.length} node(s) inside rack(s):\\n• ${nodesInsideRacks.join('\\n• ')}`;\n       }\n       challengeTheImmortal(message, () => {\n        pushUndo(\"delete selected\");\n        selectedNodes.forEach(nodeId => {\n         if (NODE_DATA[nodeId]?.isRack) {\n          Object.keys(NODE_DATA).forEach(id => {\n           if (NODE_DATA[id]?.assignedRack === nodeId) {\n            EDGE_DATA.list = EDGE_DATA.list.filter(e => e.from !== id && e.to !== id);\n            delete NODE_DATA[id];\n            delete savedPositions[id];\n            delete savedSizes[id];\n            delete savedStyles[id];\n           }\n          });\n         }\n         delete NODE_DATA[nodeId];\n         delete savedPositions[nodeId];\n         delete savedSizes[nodeId];\n         delete savedStyles[nodeId];\n         EDGE_DATA.list = EDGE_DATA.list.filter(e => e.from !== nodeId && e.to !== nodeId);\n        });\n        selectedEdges.forEach(edgeId => {\n         EDGE_DATA.list = EDGE_DATA.list.filter(e => e.id !== edgeId);\n        });\n        selectedRects.forEach(rectId => {\n         RECT_DATA.list = RECT_DATA.list.filter(r => r.id !== rectId);\n        });\n        selectedTexts.forEach(textId => {\n         TEXT_DATA.list = TEXT_DATA.list.filter(t => t.id !== textId);\n        });\n        clearSelection();\n        forgeTheTopology();\n       });\n      }\n      function startSelection(event) {\n       const svgEl = document.getElementById(\"map\");\n       const pt = svgEl.createSVGPoint();\n       pt.x = event.clientX;\n       pt.y = event.clientY;\n       const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n       isSelecting = true;\n       selectionStart = { x: svgP.x, y: svgP.y };\n       if (!selectionRect || !selectionRect.parentNode) {\n        selectionRect = document.createElementNS(\"http://www.w3.org/2000/svg\", \"rect\");\n        selectionRect.setAttribute(\"fill\", selectionBoxStyle.fillColor);\n        selectionRect.setAttribute(\"fill-opacity\", selectionBoxStyle.fillOpacity);\n        selectionRect.setAttribute(\"stroke\", selectionBoxStyle.strokeColor);\n        selectionRect.setAttribute(\"stroke-width\", selectionBoxStyle.strokeWidth);\n        selectionRect.setAttribute(\"stroke-dasharray\", selectionBoxStyle.strokeDasharray);\n        selectionRect.style.pointerEvents = \"none\";\n        svgEl.appendChild(selectionRect);\n       }\n       clearSelection();\n       preDragSelectedNodes = new Set(selectedNodes);\n       preDragSelectedEdges = new Set(selectedEdges);\n       preDragSelectedRects = new Set(selectedRects);\n       preDragSelectedTexts = new Set(selectedTexts);\n      }\n      function updateSelection(event) {\n       if (!isSelecting || !selectionStart) return;\n       const svgEl = document.getElementById(\"map\");\n       const pt = svgEl.createSVGPoint();\n       pt.x = event.clientX;\n       pt.y = event.clientY;\n       const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n       const x = Math.min(selectionStart.x, svgP.x);\n       const y = Math.min(selectionStart.y, svgP.y);\n       const width = Math.abs(svgP.x - selectionStart.x);\n       const height = Math.abs(svgP.y - selectionStart.y);\n       selectionRect.setAttribute(\"x\", x);\n       selectionRect.setAttribute(\"y\", y);\n       selectionRect.setAttribute(\"width\", width);\n       selectionRect.setAttribute(\"height\", height);\n       selectionRect.style.display = \"block\";\n       const box = { x, y, width, height };\n       Object.entries(savedPositions).forEach(([nodeId, pos]) => {\n        const size = savedSizes[nodeId] || 50;\n        const halfSize = size / 2;\n        const nodeBox = { x: pos.x - halfSize, y: pos.y - halfSize, width: size, height: size };\n        if (boxesIntersect(box, nodeBox)) {\n         selectedNodes.add(nodeId);\n        } else if (!preDragSelectedNodes.has(nodeId)) {\n         selectedNodes.delete(nodeId);\n        }\n       });\n       EDGE_DATA.list.forEach(edge => {\n        if (!edge.points || edge.points.length === 0) return;\n        for (let i = 0; i < edge.points.length - 1; i++) {\n         const p1 = edge.points[i];\n         const p2 = edge.points[i + 1];\n         if (lineIntersectsBox(p1.x, p1.y, p2.x, p2.y, box)) {\n          selectedEdges.add(edge.id);\n          return;\n         }\n        }\n        if (!preDragSelectedEdges.has(edge.id)) {\n         selectedEdges.delete(edge.id);\n        }\n       });\n       RECT_DATA.list.forEach(rect => {\n        const rectBox = { x: rect.x, y: rect.y, width: rect.width, height: rect.height };\n        if (boxesIntersect(box, rectBox)) {\n         selectedRects.add(rect.id);\n        } else if (!preDragSelectedRects.has(rect.id)) {\n         selectedRects.delete(rect.id);\n        }\n       });\n       TEXT_DATA.list.forEach(text => {\n        const fontSize = text.fontSize || 18;\n        const textBox = { x: text.x - 50, y: text.y - fontSize, width: 100, height: fontSize * 1.5 };\n        if (boxesIntersect(box, textBox)) {\n         selectedTexts.add(text.id);\n        } else if (!preDragSelectedTexts.has(text.id)) {\n         selectedTexts.delete(text.id);\n        }\n       });\n       updateAllSelectionVisuals();\n      }\n      function endSelection() {\n       isSelecting = false;\n       selectionStart = null;\n       if (selectionRect) {\n        selectionRect.style.display = \"none\";\n       }\n      }\n      function boxesIntersect(a, b) {\n       return !(a.x + a.width < b.x || b.x + b.width < a.x || a.y + a.height < b.y || b.y + b.height < a.y);\n      }\n      function lineIntersectsBox(x1, y1, x2, y2, box) {\n       if (pointInBox(x1, y1, box) || pointInBox(x2, y2, box)) return true;\n       const lines = [\n        [box.x, box.y, box.x + box.width, box.y],\n        [box.x + box.width, box.y, box.x + box.width, box.y + box.height],\n        [box.x + box.width, box.y + box.height, box.x, box.y + box.height],\n        [box.x, box.y + box.height, box.x, box.y]\n       ];\n       for (const [bx1, by1, bx2, by2] of lines) {\n        if (linesIntersect(x1, y1, x2, y2, bx1, by1, bx2, by2)) return true;\n       }\n       return false;\n      }\n      function pointInBox(x, y, box) {\n       return x >= box.x && x <= box.x + box.width && y >= box.y && y <= box.y + box.height;\n      }\n      function linesIntersect(x1, y1, x2, y2, x3, y3, x4, y4) {\n       const denom = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1);\n       if (Math.abs(denom) < 0.0001) return false;\n       const ua = ((x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)) / denom;\n       const ub = ((x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)) / denom;\n       return ua >= 0 && ua <= 1 && ub >= 0 && ub <= 1;\n      }\n      function updateAllSelectionVisuals() {\n       document.querySelectorAll(\".node-group\").forEach(node => {\n        const nodeId = node.dataset.nodeId;\n        if (selectedNodes.has(nodeId)) {\n         node.classList.add(\"selected\");\n        } else {\n         node.classList.remove(\"selected\");\n        }\n       });\n       document.querySelectorAll(\".edge\").forEach(edge => {\n        const edgeId = edge.dataset.edgeId;\n        if (selectedEdges.has(edgeId)) {\n         edge.style.filter = \"drop-shadow(0 0 6px #4fd1c5)\";\n        } else {\n         edge.style.filter = \"\";\n        }\n       });\n       document.querySelectorAll(\".rect-group rect\").forEach(el => {\n        const rectId = el.closest(\".rect-group\")?.dataset?.rectId;\n        if (selectedRects.has(rectId)) {\n         el.style.filter = \"drop-shadow(0 0 8px #4fd1c5)\";\n        } else {\n         el.style.filter = \"\";\n        }\n       });\n       document.querySelectorAll(\".text-group, .text-element\").forEach(el => {\n        const textId = el.dataset?.textId;\n        if (selectedTexts.has(textId)) {\n         el.style.filter = \"drop-shadow(0 0 8px #4fd1c5)\";\n        } else {\n         el.style.filter = \"\";\n        }\n       });\n       const total = selectedNodes.size + selectedEdges.size + selectedRects.size + selectedTexts.size;\n       const bulkToolbar = document.getElementById(\"bulk-toolbar\");\n       const bulkToolbarMobile = document.getElementById(\"bulk-toolbar-mobile\");\n       const bulkCount = document.getElementById(\"bulk-count\");\n       const bulkCountMobile = document.getElementById(\"bulk-count-mobile\");\n       if (total > 0) {\n        if (bulkToolbar) bulkToolbar.style.display = \"flex\";\n        if (bulkToolbarMobile) bulkToolbarMobile.style.display = \"flex\";\n        if (bulkCount) bulkCount.textContent = total;\n        if (bulkCountMobile) bulkCountMobile.textContent = total;\n       } else {\n        if (bulkToolbar) bulkToolbar.style.display = \"none\";\n        if (bulkToolbarMobile) bulkToolbarMobile.style.display = \"none\";\n       }\n      }\n      function cloneNode(sourceId, skipUndo) {\n       const source = NODE_DATA[sourceId];\n       if (!source) return;\n       if (!skipUndo) pushUndo(\"clone node\");\n       let baseName = source.name;\n       let copyNum = 0;\n       let newName = baseName + \" copy\";\n       while (Object.values(NODE_DATA).some(n => n.name === newName)) {\n        copyNum++;\n        newName = baseName + \" copy \" + copyNum;\n       }\n       const newId = newName.toLowerCase().replace(/[^a-z0-9]+/g, \"-\");\n       NODE_DATA[newId] = {\n        shape: source.shape,\n        name: newName,\n        ip: source.ip,\n        role: source.role,\n        tags: [...source.tags],\n        notes: [...source.notes],\n        mac: source.mac || \"\",\n        rackUnit: source.rackUnit || \"\",\n        uHeight: source.uHeight || \"1\",\n        ping: source.ping ? JSON.parse(JSON.stringify(source.ping)) : {\n         enabled: false,\n         protocol: 'http',\n         customUrl: '',\n         timeout: 3000,\n         status: 'unknown',\n         lastCheck: null\n        },\n        layer: source.layer || \"layer1\",\n        assignedRack: source.assignedRack || \"\",\n        hostedOn: source.hostedOn || \"\",\n        rackCapacity: source.rackCapacity || \"42\",\n        isRack: source.isRack || false,\n        fovEnabled: source.fovEnabled || false,\n        fovAngle: source.fovAngle || 90,\n        fovDistance: source.fovDistance || 150,\n        fovInnerRadius: source.fovInnerRadius || 0,\n        fovRotation: source.fovRotation || 0,\n        fovColor: source.fovColor || \"#f59e0b\",\n        fovOpacity: source.fovOpacity || 20,\n        fovGradient: source.fovGradient || false,\n        fovBorderColor: source.fovBorderColor || \"#f59e0b\",\n        fovBorderWidth: source.fovBorderWidth !== undefined ? source.fovBorderWidth : 2,\n        fovBorderStyle: source.fovBorderStyle || \"solid\",\n        fovBorderOpacity: source.fovBorderOpacity !== undefined ? source.fovBorderOpacity : 100,\n        fovLabel: source.fovLabel || \"\",\n        fovLabelPosition: source.fovLabelPosition || \"center\",\n        fovLabelSize: source.fovLabelSize || 14,\n        fovLabelColor: source.fovLabelColor || \"#ffffff\",\n        fovLabelBold: source.fovLabelBold || false,\n        fovLabelBg: source.fovLabelBg || false,\n        fovLabelBgColor: source.fovLabelBgColor || \"#000000\",\n        fovLabelOffsetX: source.fovLabelOffsetX || 0,\n        fovLabelOffsetY: source.fovLabelOffsetY || 0,\n        fovAnimate: source.fovAnimate || false,\n        fovAnimationType: source.fovAnimationType || \"sweep\",\n        fovSweep: source.fovSweep || 120,\n        fovSpeed: source.fovSpeed || 4\n       };\n       if (source.isRack) {\n        const childNodes = Object.entries(NODE_DATA).filter(([id, n]) =>\n         id !== newId && n.assignedRack === sourceId\n        );\n        childNodes.forEach(([childId, childNode]) => {\n         let childBaseName = childNode.name;\n         let c = 0;\n         let childNewName = childBaseName + \" copy\";\n         while (Object.values(NODE_DATA).some(n => n.name === childNewName)) {\n          c++;\n          childNewName = childBaseName + \" copy \" + c;\n         }\n         const childNewId = childNewName.toLowerCase().replace(/[^a-z0-9]+/g, \"-\");\n         NODE_DATA[childNewId] = {\n          ...JSON.parse(JSON.stringify(childNode)),\n          name: childNewName,\n          assignedRack: newId\n         };\n         if (savedPositions[childId]) {\n          savedPositions[childNewId] = { ...savedPositions[childId] };\n         }\n         if (savedSizes[childId]) {\n          savedSizes[childNewId] = savedSizes[childId];\n         }\n         if (savedStyles[childId]) {\n          savedStyles[childNewId] = JSON.parse(JSON.stringify(savedStyles[childId]));\n         }\n        });\n       }\n       if (savedSizes[sourceId]) {\n        savedSizes[newId] = savedSizes[sourceId];\n       }\n       if (savedStyles[sourceId]) {\n        savedStyles[newId] = JSON.parse(JSON.stringify(savedStyles[sourceId]));\n       }\n      if (currentView.mode === \"rack\" && currentView.rackId) {\n        NODE_DATA[newId].assignedRack = currentView.rackId;\n       }\n       const sourcePos = savedPositions[sourceId];\n       savedPositions[newId] = {\n        x: sourcePos.x + 100,\n        y: sourcePos.y + 100\n       };\n       forgeTheTopology();\n       claimTheImmortal(newId);\n      }\n      function alignSelectedNodes(direction) {\n       if (selectedNodes.size < 2) return;\n       pushUndo(\"align nodes\");\n       const nodeIds = Array.from(selectedNodes);\n       const positions = nodeIds.map(id => ({ id, pos: savedPositions[id] }));\n       switch(direction) {\n        case \"left\":\n         const minX = Math.min(...positions.map(p => p.pos.x));\n         positions.forEach(p => savedPositions[p.id].x = minX);\n         break;\n        case \"right\":\n         const maxX = Math.max(...positions.map(p => p.pos.x));\n         positions.forEach(p => savedPositions[p.id].x = maxX);\n         break;\n        case \"top\":\n         const minY = Math.min(...positions.map(p => p.pos.y));\n         positions.forEach(p => savedPositions[p.id].y = minY);\n         break;\n        case \"bottom\":\n         const maxY = Math.max(...positions.map(p => p.pos.y));\n         positions.forEach(p => savedPositions[p.id].y = maxY);\n         break;\n        case \"center-h\":\n         const avgX = positions.reduce((sum, p) => sum + p.pos.x, 0) / positions.length;\n         positions.forEach(p => savedPositions[p.id].x = avgX);\n         break;\n        case \"center-v\":\n         const avgY = positions.reduce((sum, p) => sum + p.pos.y, 0) / positions.length;\n         positions.forEach(p => savedPositions[p.id].y = avgY);\n         break;\n       }\n       forgeTheTopology();\n      }\n      function distributeSelectedNodes(direction) {\n       if (selectedNodes.size < 3) return;\n       pushUndo(\"distribute nodes\");\n       const nodeIds = Array.from(selectedNodes);\n       const positions = nodeIds.map(id => ({ id, pos: savedPositions[id] }));\n       if (direction === \"horizontal\") {\n        positions.sort((a, b) => a.pos.x - b.pos.x);\n        const minX = positions[0].pos.x;\n        const maxX = positions[positions.length - 1].pos.x;\n        const gap = (maxX - minX) / (positions.length - 1);\n        positions.forEach((p, i) => {\n         savedPositions[p.id].x = minX + (gap * i);\n        });\n       } else if (direction === \"vertical\") {\n        positions.sort((a, b) => a.pos.y - b.pos.y);\n        const minY = positions[0].pos.y;\n        const maxY = positions[positions.length - 1].pos.y;\n        const gap = (maxY - minY) / (positions.length - 1);\n        positions.forEach((p, i) => {\n         savedPositions[p.id].y = minY + (gap * i);\n        });\n       }\n       forgeTheTopology();\n      }\n      function snapToGrid(nodeId, gridSize = 50) {\n       if (!savedPositions[nodeId]) return;\n       pushUndo(\"snap to grid\");\n       const pos = savedPositions[nodeId];\n       pos.x = Math.round(pos.x / gridSize) * gridSize;\n       pos.y = Math.round(pos.y / gridSize) * gridSize;\n       forgeTheTopology();\n      }\n      function nudgeSelectedNodes(direction, distance) {\n        const nodesToNudge = selectedNodes.size > 0\n          ? Array.from(selectedNodes)\n          : (currentNodeId ? [currentNodeId] : []);\n        if (nodesToNudge.length === 0) return;\n        const unlockedNodes = nodesToNudge.filter(id => !NODE_DATA[id]?.locked);\n        if (unlockedNodes.length === 0) {\n          return;\n        }\n        pushUndo(\"nudge nodes\");\n        unlockedNodes.forEach(id => {\n          if (!savedPositions[id]) {\n            savedPositions[id] = { x: 0, y: 0 };\n          }\n          switch(direction) {\n            case \"ArrowUp\":\n              savedPositions[id].y -= distance;\n              break;\n            case \"ArrowDown\":\n              savedPositions[id].y += distance;\n              break;\n            case \"ArrowLeft\":\n              savedPositions[id].x -= distance;\n              break;\n            case \"ArrowRight\":\n              savedPositions[id].x += distance;\n              break;\n          }\n        });\n        forgeTheTopology();\n      }\n      function cycleNodes(reverse = false) {\n        const nodeIds = Object.keys(NODE_DATA).filter(id => {\n          if (currentView.mode === \"rack\") {\n           const node = NODE_DATA[id];\n           return node && node.assignedRack === currentView.rackId;\n          }\n          return isNodeVisible(id);\n         });\n        if (nodeIds.length === 0) return;\n        let currentIndex = nodeIds.indexOf(currentNodeId);\n        if (reverse) {\n          currentIndex = currentIndex <= 0 ? nodeIds.length - 1 : currentIndex - 1;\n        } else {\n          currentIndex = currentIndex >= nodeIds.length - 1 ? 0 : currentIndex + 1;\n        }\n        const nextNodeId = nodeIds[currentIndex];\n        claimTheImmortal(nextNodeId);\n        selectedNodes.clear();\n        updateNodeSelection();\n      }\n      function focusOnSelected() {\n        let minX = Infinity, minY = Infinity;\n        let maxX = -Infinity, maxY = -Infinity;\n        let hasItems = false;\n        const nodesToFocus = selectedNodes.size > 0\n          ? Array.from(selectedNodes)\n          : (currentNodeId ? [currentNodeId] : []);\n        nodesToFocus.forEach(id => {\n          const pos = savedPositions[id];\n          if (pos) {\n            hasItems = true;\n            const size = savedSizes[id] || 50;\n            minX = Math.min(minX, pos.x - size/2);\n            minY = Math.min(minY, pos.y - size/2);\n            maxX = Math.max(maxX, pos.x + size/2);\n            maxY = Math.max(maxY, pos.y + size/2);\n          }\n        });\n        const rectsToFocus = selectedRects.size > 0 ? Array.from(selectedRects) : (currentRectId ? [currentRectId] : []);\n        rectsToFocus.forEach(id => {\n          const r = RECT_DATA.list.find(x => x.id === id);\n          if (r) {\n            hasItems = true;\n            minX = Math.min(minX, r.x);\n            minY = Math.min(minY, r.y);\n            maxX = Math.max(maxX, r.x + r.width);\n            maxY = Math.max(maxY, r.y + r.height);\n          }\n        });\n        const textsToFocus = selectedTexts.size > 0 ? Array.from(selectedTexts) : (currentTextId ? [currentTextId] : []);\n        textsToFocus.forEach(id => {\n          const t = TEXT_DATA.list.find(x => x.id === id);\n          if (t) {\n            hasItems = true;\n            minX = Math.min(minX, t.x - 50);\n            minY = Math.min(minY, t.y - 20);\n            maxX = Math.max(maxX, t.x + 50);\n            maxY = Math.max(maxY, t.y + 20);\n          }\n        });\n        const edgesToFocus = selectedEdges.size > 0 ? Array.from(selectedEdges) : (currentEdgeId ? [currentEdgeId] : []);\n        edgesToFocus.forEach(id => {\n          const e = EDGE_DATA.list.find(x => x.id === id);\n          if (e) {\n            const fromPos = savedPositions[e.from];\n            const toPos = savedPositions[e.to];\n            if (fromPos && toPos) {\n              hasItems = true;\n              minX = Math.min(minX, fromPos.x, toPos.x);\n              minY = Math.min(minY, fromPos.y, toPos.y);\n              maxX = Math.max(maxX, fromPos.x, toPos.x);\n              maxY = Math.max(maxY, fromPos.y, toPos.y);\n            }\n          }\n        });\n        if (!hasItems || !isFinite(minX)) return;\n        const padding = 100;\n        const centerX = (minX + maxX) / 2;\n        const centerY = (minY + maxY) / 2;\n        const width = maxX - minX + padding * 2;\n        const height = maxY - minY + padding * 2;\n        const zoomX = CANVAS_WIDTH / width;\n        const zoomY = CANVAS_HEIGHT / height;\n        const targetZoom = Math.min(zoomX, zoomY, 2);\n        canvasState.zoom = targetZoom;\n        canvasState.panX = centerX - (CANVAS_WIDTH / targetZoom) / 2;\n        canvasState.panY = centerY - (CANVAS_HEIGHT / targetZoom) / 2;\n        updateViewBox();\n      }\n      function toggleLockSelected() {\n        const nodesToToggle = selectedNodes.size > 0\n          ? Array.from(selectedNodes)\n          : (currentNodeId ? [currentNodeId] : []);\n        const rectsToToggle = selectedRects.size > 0\n          ? Array.from(selectedRects)\n          : (currentRectId ? [currentRectId] : []);\n        const textsToToggle = selectedTexts.size > 0\n          ? Array.from(selectedTexts)\n          : (currentTextId ? [currentTextId] : []);\n        if (nodesToToggle.length === 0 && rectsToToggle.length === 0 && textsToToggle.length === 0) return;\n        pushUndo(\"toggle lock\");\n        let hasUnlocked = nodesToToggle.some(id => !NODE_DATA[id]?.locked);\n        hasUnlocked = hasUnlocked || rectsToToggle.some(id => { const r = RECT_DATA.list.find(x => x.id === id); return r && !r.locked; });\n        hasUnlocked = hasUnlocked || textsToToggle.some(id => { const t = TEXT_DATA.list.find(x => x.id === id); return t && !t.locked; });\n        nodesToToggle.forEach(id => {\n          if (NODE_DATA[id]) NODE_DATA[id].locked = hasUnlocked;\n        });\n        rectsToToggle.forEach(id => {\n          const r = RECT_DATA.list.find(x => x.id === id);\n          if (r) r.locked = hasUnlocked;\n        });\n        textsToToggle.forEach(id => {\n          const t = TEXT_DATA.list.find(x => x.id === id);\n          if (t) t.locked = hasUnlocked;\n        });\n        forgeTheTopology();\n      }\n      function toggleGroupSelected() {\n      const nodesToGroup = Array.from(selectedNodes);\n      const rectsToGroup = Array.from(selectedRects);\n      const textsToGroup = Array.from(selectedTexts);\n      const edgesToGroup = Array.from(selectedEdges);\n      const totalItems = nodesToGroup.length + rectsToGroup.length + textsToGroup.length + edgesToGroup.length;\n      if (totalItems < 2) return;\n      pushUndo(\"toggle group\");\n      const allGroupIds = [];\n      nodesToGroup.forEach(id => { if (NODE_DATA[id]?.groupId) allGroupIds.push(NODE_DATA[id].groupId); });\n      rectsToGroup.forEach(id => { const r = RECT_DATA.list.find(x => x.id === id); if (r?.groupId) allGroupIds.push(r.groupId); });\n      textsToGroup.forEach(id => { const t = TEXT_DATA.list.find(x => x.id === id); if (t?.groupId) allGroupIds.push(t.groupId); });\n      edgesToGroup.forEach(id => { const e = EDGE_DATA.list.find(x => x.id === id); if (e?.groupId) allGroupIds.push(e.groupId); });\n      const uniqueGroups = [...new Set(allGroupIds)];\n      if (uniqueGroups.length === 1 && allGroupIds.length === totalItems) {\n      nodesToGroup.forEach(id => { if (NODE_DATA[id]) NODE_DATA[id].groupId = null; });\n      rectsToGroup.forEach(id => { const r = RECT_DATA.list.find(x => x.id === id); if (r) r.groupId = null; });\n      textsToGroup.forEach(id => { const t = TEXT_DATA.list.find(x => x.id === id); if (t) t.groupId = null; });\n      edgesToGroup.forEach(id => { const e = EDGE_DATA.list.find(x => x.id === id); if (e) e.groupId = null; });\n      } else {\n      const newGroupId = \"group-\" + Date.now();\n      nodesToGroup.forEach(id => { if (NODE_DATA[id]) NODE_DATA[id].groupId = newGroupId; });\n      rectsToGroup.forEach(id => { const r = RECT_DATA.list.find(x => x.id === id); if (r) r.groupId = newGroupId; });\n      textsToGroup.forEach(id => { const t = TEXT_DATA.list.find(x => x.id === id); if (t) t.groupId = newGroupId; });\n      edgesToGroup.forEach(id => { const e = EDGE_DATA.list.find(x => x.id === id); if (e) e.groupId = newGroupId; });\n      }\n      forgeTheTopology();\n      }\n      function handleKeyDown(event) {\n       if (event.metaKey && !event.ctrlKey) event.ctrlKey = true;\n       if (event.target.tagName === \"INPUT\" || event.target.tagName === \"TEXTAREA\" || event.target.isContentEditable) {\n        return;\n       }\n       if ([\"ArrowUp\", \"ArrowDown\", \"ArrowLeft\", \"ArrowRight\"].includes(event.key)) {\n        event.preventDefault();\n        const distance = event.shiftKey ? 10 : 1;\n        nudgeSelectedNodes(event.key, distance);\n       }\n       if (event.key === \"Tab\") {\n        event.preventDefault();\n        cycleNodes(event.shiftKey);\n       }\n       if (event.key === \"f\" || event.key === \"F\") {\n        event.preventDefault();\n        focusOnSelected();\n       }\n       if (event.key === \"l\" || event.key === \"L\") {\n        event.preventDefault();\n        toggleLockSelected();\n       }\n       if (event.key === \"g\" || event.key === \"G\") {\n        event.preventDefault();\n        toggleGroupSelected();\n       }\n       if (event.ctrlKey && event.key === \"z\" && !event.shiftKey) {\n        event.preventDefault();\n        undo();\n       }\n       if ((event.ctrlKey && event.key === \"y\") || (event.ctrlKey && event.shiftKey && event.key === \"z\")) {\n        event.preventDefault();\n        redo();\n       }\n       if (event.ctrlKey && event.key === \"c\") {\n        event.preventDefault();\n        clipboard = null;\n        if (currentNodeId && NODE_DATA[currentNodeId]) {\n         clipboard = {\n          type: \"node\",\n          data: JSON.parse(JSON.stringify(NODE_DATA[currentNodeId])),\n          size: savedSizes[currentNodeId],\n          style: savedStyles[currentNodeId] ? JSON.parse(JSON.stringify(savedStyles[currentNodeId])) : null\n         };\n        } else if (currentEdgeId) {\n         const edge = EDGE_DATA.list.find(e => e.id === currentEdgeId);\n         if (edge) clipboard = { type: \"edge\", data: JSON.parse(JSON.stringify(edge)) };\n        } else if (currentRectId) {\n         const rect = RECT_DATA.list.find(r => r.id === currentRectId);\n         if (rect) clipboard = { type: \"rect\", data: JSON.parse(JSON.stringify(rect)) };\n        } else if (currentTextId) {\n         const text = TEXT_DATA.list.find(t => t.id === currentTextId);\n         if (text) clipboard = { type: \"text\", data: JSON.parse(JSON.stringify(text)) };\n        }\n       }\n       if (event.ctrlKey && event.key === \"v\") {\n        event.preventDefault();\n        if (!clipboard) return;\n        const svgEl = document.getElementById(\"map\");\n        const rect = svgEl.getBoundingClientRect();\n        const pt = svgEl.createSVGPoint();\n        pt.x = rect.left + rect.width / 2;\n        pt.y = rect.top + rect.height / 2;\n        const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n        const centerX = svgP.x;\n        const centerY = svgP.y;\n        if (clipboard.type === \"node\") {\n         let newName = clipboard.data.name + \" copy\";\n         let counter = 1;\n         while (Object.values(NODE_DATA).some(n => n.name === newName)) {\n          newName = clipboard.data.name + \" copy \" + counter;\n          counter++;\n         }\n         let newId = newName.toLowerCase().replace(/[^a-z0-9]+/g, \"-\");\n         counter = 1;\n         while (NODE_DATA[newId]) {\n          newId = newName.toLowerCase().replace(/[^a-z0-9]+/g, \"-\") + \"-\" + counter;\n          counter++;\n         }\n         pushUndo(\"paste node\");\n         NODE_DATA[newId] = { ...JSON.parse(JSON.stringify(clipboard.data)), name: newName };\n         savedPositions[newId] = { x: centerX, y: centerY };\n         if (clipboard.size) savedSizes[newId] = clipboard.size;\n         if (clipboard.style) savedStyles[newId] = JSON.parse(JSON.stringify(clipboard.style));\n         forgeTheTopology();\n         claimTheImmortal(newId);\n        } else if (clipboard.type === \"edge\") {\n         pushUndo(\"paste edge\");\n         const newEdge = { ...clipboard.data, id: \"edge-\" + Date.now() };\n         EDGE_DATA.list.push(newEdge);\n         forgeTheTopology();\n         selectTheConnection(newEdge.id);\n        } else if (clipboard.type === \"rect\") {\n         pushUndo(\"paste rect\");\n         const newRect = { ...clipboard.data, id: \"rect-\" + Date.now(), x: centerX - (clipboard.data.width || 100) / 2, y: centerY - (clipboard.data.height || 100) / 2 };\n         RECT_DATA.list.push(newRect);\n         forgeTheTopology();\n         selectTheRect(newRect.id);\n        } else if (clipboard.type === \"text\") {\n         pushUndo(\"paste text\");\n         const newText = { ...clipboard.data, id: \"text-\" + Date.now(), x: centerX, y: centerY };\n         TEXT_DATA.list.push(newText);\n         forgeTheTopology();\n         showTextPanel(newText.id);\n        }\n       }\n       if (event.ctrlKey && event.key === \"d\") {\n        event.preventDefault();\n        if (currentNodeId && NODE_DATA[currentNodeId]) {\n         cloneNode(currentNodeId);\n        } else if (currentEdgeId) {\n         const edge = EDGE_DATA.list.find(e => e.id === currentEdgeId);\n         if (edge) {\n          pushUndo(\"clone edge\");\n          const newEdge = { ...JSON.parse(JSON.stringify(edge)), id: \"edge-\" + Date.now() };\n          EDGE_DATA.list.push(newEdge);\n          forgeTheTopology();\n          selectTheConnection(newEdge.id);\n         }\n        } else if (currentRectId) {\n         const rect = RECT_DATA.list.find(r => r.id === currentRectId);\n         if (rect) {\n          pushUndo(\"clone rect\");\n          const newRect = { ...JSON.parse(JSON.stringify(rect)), id: \"rect-\" + Date.now(), x: rect.x + 50, y: rect.y + 50 };\n          RECT_DATA.list.push(newRect);\n          forgeTheTopology();\n          selectTheRect(newRect.id);\n         }\n        } else if (currentTextId) {\n         const text = TEXT_DATA.list.find(t => t.id === currentTextId);\n         if (text) {\n          pushUndo(\"clone text\");\n          const newText = { ...JSON.parse(JSON.stringify(text)), id: \"text-\" + Date.now(), x: text.x + 50, y: text.y + 50 };\n          TEXT_DATA.list.push(newText);\n          forgeTheTopology();\n          showTextPanel(newText.id);\n         }\n        }\n       }\n       if (event.key === \"Delete\") {\n        event.preventDefault();\n        const totalSelected = selectedNodes.size + selectedEdges.size + selectedRects.size + selectedTexts.size;\n        if (totalSelected > 0) {\n         deleteSelected();\n        } else if (currentNodeId && NODE_DATA[currentNodeId]) {\n         challengeTheImmortal(`Delete node \"${NODE_DATA[currentNodeId].name}\"?`, () => {\n          pushUndo(\"delete node\");\n          delete NODE_DATA[currentNodeId];\n          delete savedPositions[currentNodeId];\n          delete savedSizes[currentNodeId];\n          delete savedStyles[currentNodeId];\n          EDGE_DATA.list = EDGE_DATA.list.filter(e => e.from !== currentNodeId && e.to !== currentNodeId);\n          currentNodeId = null;\n          forgeTheTopology();\n          document.getElementById(\"node-panel\").style.display = \"none\";\n         });\n        } else if (currentEdgeId) {\n         challengeTheImmortal(\"Delete this line?\", () => {\n          pushUndo(\"delete edge\");\n          EDGE_DATA.list = EDGE_DATA.list.filter(e => e.id !== currentEdgeId);\n          currentEdgeId = null;\n          forgeTheTopology();\n          document.getElementById(\"edge-panel\").style.display = \"none\";\n         });\n        } else if (currentRectId) {\n         challengeTheImmortal(\"Delete this zone?\", () => {\n          pushUndo(\"delete rect\");\n          RECT_DATA.list = RECT_DATA.list.filter(r => r.id !== currentRectId);\n          currentRectId = null;\n          forgeTheTopology();\n          document.getElementById(\"rect-panel\").style.display = \"none\";\n         });\n        } else if (currentTextId) {\n         const textItem = TEXT_DATA.list.find(t => t.id === currentTextId);\n         if (textItem) {\n          challengeTheImmortal(`Delete text \"${(textItem.content || '').substring(0, 30)}...\"?`, () => {\n           pushUndo(\"delete text\");\n           TEXT_DATA.list = TEXT_DATA.list.filter(t => t.id !== currentTextId);\n           currentTextId = null;\n           forgeTheTopology();\n           document.getElementById(\"text-panel\").style.display = \"none\";\n          });\n         }\n        }\n       }\n       if (event.ctrlKey && event.key === \"a\") {\n        event.preventDefault();\n        Object.keys(NODE_DATA).forEach(id => selectedNodes.add(id));\n        EDGE_DATA.list.forEach(e => selectedEdges.add(e.id));\n        RECT_DATA.list.forEach(r => selectedRects.add(r.id));\n        TEXT_DATA.list.forEach(t => selectedTexts.add(t.id));\n        updateAllSelections();\n       }\n       if (event.key === \"Escape\") {\n        clearSelection();\n       }\n       if (event.key === \"r\" && !event.ctrlKey && !event.shiftKey) {\n        event.preventDefault();\n        if (RECORDING_STATE.isRecording) {\n          stopRecording();\n        } else if (!RECORDING_STATE.isStepRecording) {\n          startRecording();\n        }\n       }\n       if (event.key === \"R\" && event.shiftKey && !event.ctrlKey) {\n        event.preventDefault();\n        if (RECORDING_STATE.isStepRecording) {\n          stopStepRecording();\n        } else if (!RECORDING_STATE.isRecording) {\n          startStepRecording();\n        }\n       }\n       if (event.key === \" \" && !event.ctrlKey) {\n        event.preventDefault();\n        if (RECORDING_STATE.isStepRecording) {\n          captureStepFrame();\n        } else if (RECORDING_STATE.isPlaying) {\n          pauseRecording();\n        } else if (RECORDING_STATE.isPaused) {\n          playRecording();\n        }\n       }\n       if ((event.key === \"p\" || event.key === \"P\") && !event.ctrlKey) {\n        event.preventDefault();\n        if (!RECORDING_STATE.isRecording && !RECORDING_STATE.isStepRecording) {\n          playRecording();\n        }\n       }\n      }\n\t\tfunction searchNodes(query) {\n\t\t   if (!query) {\n\t\t\tcurrentSearchQuery = \"\";\n\t\t\tcurrentSearchResults = [];\n\t\t\tclearSearchHighlight();\n\t\t\treturn [];\n\t\t   }\n\t\t   currentSearchQuery = query;\n\t\t   query = query.toLowerCase();\n\t\t   const results = [];\n\t\t   Object.entries(NODE_DATA).forEach(([id, data]) => {\n\t\t\ttry {\n\t\t\t const nameMatch = data.name && data.name.toLowerCase().includes(query);\n\t\t\t const ipMatch = data.ip && data.ip.toLowerCase().includes(query);\n\t\t\t const roleMatch = data.role && data.role.toLowerCase().includes(query);\n\t\t\t const tagsMatch = data.tags && Array.isArray(data.tags) && data.tags.some(tag => tag && tag.toLowerCase().includes(query));\n\t\t\t const macMatch = data.mac && data.mac.toLowerCase().includes(query);\n\t\t\t const rackUnitMatch = data.rackUnit && String(data.rackUnit).toLowerCase().includes(query);\n\t\t\t if (nameMatch || ipMatch || roleMatch || tagsMatch || macMatch || rackUnitMatch) {\n\t\t\t  results.push(id);\n\t\t\t }\n\t\t\t} catch (e) {\n\t\t\t console.warn(\"Search error for node:\", id, e);\n\t\t\t}\n\t\t   });\n\t\t   currentSearchResults = results;\n\t\t   highlightSearchResults(results, true);\n\t\t   if (results.length > 0) {\n\t\t\tfocusOnNodes(results);\n\t\t   }\n\t\t   return results;\n\t\t}\n\t\tfunction highlightSearchResults(nodeIds, hasQuery = true) {\n\t\t   document.querySelectorAll(\".node-group\").forEach(node => {\n\t\t\tconst nodeId = node.dataset.nodeId;\n\t\t\tif (nodeIds.includes(nodeId)) {\n\t\t\t node.classList.add(\"search-highlight\");\n\t\t\t node.classList.remove(\"search-faded\");\n\t\t\t} else {\n\t\t\t node.classList.remove(\"search-highlight\");\n\t\t\t if (hasQuery) {\n\t\t\t  node.classList.add(\"search-faded\");\n\t\t\t } else {\n\t\t\t  node.classList.remove(\"search-faded\");\n\t\t\t }\n\t\t\t}\n\t\t   });\n\t\t   document.querySelectorAll(\".edge-group, .edge\").forEach(edge => {\n\t\t\tif (hasQuery) {\n\t\t\t edge.classList.add(\"search-faded\");\n\t\t\t} else {\n\t\t\t edge.classList.remove(\"search-faded\");\n\t\t\t}\n\t\t   });\n\t\t}\n\t\tfunction clearSearchHighlight() {\n\t\t   document.querySelectorAll(\".search-highlight\").forEach(node => {\n\t\t\tnode.classList.remove(\"search-highlight\");\n\t\t   });\n\t\t   document.querySelectorAll(\".search-faded\").forEach(el => {\n\t\t\tel.classList.remove(\"search-faded\");\n\t\t   });\n\t\t}\n      function editNodeMac(id) {\n       const node = NODE_DATA[id];\n       if (!node) return;\n       document.getElementById(\"modal-title\").textContent = t(\"editModal.editMacAddress\");\n       document.getElementById(\"modal-input\").value = node.mac || \"\";\n       document.getElementById(\"edit-modal\").style.display = \"\";\n\t   document.getElementById(\"edit-modal\").classList.add(\"active\");\n       document.getElementById(\"modal-input\").focus();\n       const modal = document.getElementById(\"edit-modal\");\n       const cleanup = () => {\n        modal.classList.remove(\"active\");\n        document.getElementById(\"modal-save\").removeEventListener(\"click\", saveHandler);\n        document.getElementById(\"modal-cancel\").removeEventListener(\"click\", cancelHandler);\n        modal.removeEventListener(\"click\", bgHandler);\n       };\n       const saveHandler = () => {\n        pushUndo(\"edit mac\");\n        const value = document.getElementById(\"modal-input\").value.trim();\n        node.mac = value;\n        cleanup();\n        claimTheImmortal(id);\n       };\n       const cancelHandler = () => {\n        cleanup();\n       };\n       const bgHandler = (e) => {\n        if (e.target === modal) cancelHandler();\n       };\n       document.getElementById(\"modal-save\").addEventListener(\"click\", saveHandler);\n       document.getElementById(\"modal-cancel\").addEventListener(\"click\", cancelHandler);\n       modal.addEventListener(\"click\", bgHandler);\n      }\n      function editNodeRack(id) {\n       const node = NODE_DATA[id];\n       if (!node) return;\n       document.getElementById(\"modal-title\").textContent = t(\"editModal.editRackUnit\");\n       document.getElementById(\"modal-input\").value = node.rackUnit || \"\";\n       document.getElementById(\"edit-modal\").style.display = \"\";\n       document.getElementById(\"edit-modal\").classList.add(\"active\");\n       document.getElementById(\"modal-input\").focus();\n\t   const modal = document.getElementById(\"edit-modal\");\n       const cleanup = () => {\n        modal.classList.remove(\"active\");\n        document.getElementById(\"modal-save\").removeEventListener(\"click\", saveHandler);\n        document.getElementById(\"modal-cancel\").removeEventListener(\"click\", cancelHandler);\n        modal.removeEventListener(\"click\", bgHandler);\n        document.removeEventListener(\"keydown\", escHandler);\n       };\n       const escHandler = (e) => {\n        if (e.key === \"Escape\") cleanup();\n       };\n       document.addEventListener(\"keydown\", escHandler);\n       const saveHandler = () => {\n        pushUndo(\"edit rack\");\n        const value = document.getElementById(\"modal-input\").value.trim();\n        node.rackUnit = value;\n        cleanup();\n        claimTheImmortal(id);\n       };\n       const cancelHandler = () => {\n        cleanup();\n       };\n       const bgHandler = (e) => {\n        if (e.target === modal) cancelHandler();\n       };\n       document.getElementById(\"modal-save\").addEventListener(\"click\", saveHandler);\n       document.getElementById(\"modal-cancel\").addEventListener(\"click\", cancelHandler);\n       modal.addEventListener(\"click\", bgHandler);\n      }\n      function editNodeUHeight(id) {\n       const node = NODE_DATA[id];\n       if (!node) return;\n       document.getElementById(\"modal-title\").textContent = t(\"editModal.editUHeight\");\n       document.getElementById(\"modal-input\").value = node.uHeight || \"1\";\n       document.getElementById(\"edit-modal\").style.display = \"\";\n       document.getElementById(\"edit-modal\").classList.add(\"active\");\n       document.getElementById(\"modal-input\").focus();\n\t   const modal = document.getElementById(\"edit-modal\");\n       const cleanup = () => {\n        modal.classList.remove(\"active\");\n        document.getElementById(\"modal-save\").removeEventListener(\"click\", saveHandler);\n        document.getElementById(\"modal-cancel\").removeEventListener(\"click\", cancelHandler);\n        modal.removeEventListener(\"click\", bgHandler);\n        document.removeEventListener(\"keydown\", escHandler);\n       };\n       const escHandler = (e) => {\n        if (e.key === \"Escape\") cleanup();\n       };\n       document.addEventListener(\"keydown\", escHandler);\n       const saveHandler = () => {\n        pushUndo(\"edit U height\");\n        const value = document.getElementById(\"modal-input\").value.trim();\n        node.uHeight = value;\n        cleanup();\n        claimTheImmortal(id);\n        forgeTheTopology();\n       };\n       const cancelHandler = () => {\n        cleanup();\n       };\n       const bgHandler = (e) => {\n        if (e.target === modal) cancelHandler();\n       };\n       document.getElementById(\"modal-save\").addEventListener(\"click\", saveHandler);\n       document.getElementById(\"modal-cancel\").addEventListener(\"click\", cancelHandler);\n       modal.addEventListener(\"click\", bgHandler);\n      }\n      function updateEdgePortLabels(edgeId) {\n       const edge = EDGE_DATA.list.find(e => e.id === edgeId);\n       if (!edge) return;\n       const fromPortInput = document.getElementById(\"edge-from-port\");\n       const toPortInput = document.getElementById(\"edge-to-port\");\n       edge.fromPort = fromPortInput ? fromPortInput.value.trim() : \"\";\n       edge.toPort = toPortInput ? toPortInput.value.trim() : \"\";\n       forgeTheTopology();\n      }\n      document.addEventListener(\"keydown\", handleKeyDown);\n      const undoBtn = document.getElementById(\"undo-btn\");\n      const redoBtn = document.getElementById(\"redo-btn\");\n      if (undoBtn) undoBtn.addEventListener(\"click\", undo);\n      if (redoBtn) redoBtn.addEventListener(\"click\", redo);\n      const backToTopologyBtn = document.getElementById(\"back-to-topology-btn\");\n      if (backToTopologyBtn) {\n       backToTopologyBtn.addEventListener(\"click\", exitRack);\n      }\n      const bulkToolbarClose = document.getElementById(\"bulk-toolbar-close\");\n      if (bulkToolbarClose) {\n       bulkToolbarClose.addEventListener(\"click\", clearSelection);\n      }\n      const bulkAlignLeft = document.getElementById(\"bulk-align-left\");\n      if (bulkAlignLeft) {\n       bulkAlignLeft.addEventListener(\"click\", () => alignSelectedNodes(\"left\"));\n      }\n      const bulkAlignRight = document.getElementById(\"bulk-align-right\");\n      if (bulkAlignRight) {\n       bulkAlignRight.addEventListener(\"click\", () => alignSelectedNodes(\"right\"));\n      }\n      const bulkAlignTop = document.getElementById(\"bulk-align-top\");\n      if (bulkAlignTop) {\n       bulkAlignTop.addEventListener(\"click\", () => alignSelectedNodes(\"top\"));\n      }\n      const bulkAlignBottom = document.getElementById(\"bulk-align-bottom\");\n      if (bulkAlignBottom) {\n       bulkAlignBottom.addEventListener(\"click\", () => alignSelectedNodes(\"bottom\"));\n      }\n      const bulkDistributeH = document.getElementById(\"bulk-distribute-h\");\n      if (bulkDistributeH) {\n       bulkDistributeH.addEventListener(\"click\", () => distributeSelectedNodes(\"horizontal\"));\n      }\n      const bulkDistributeV = document.getElementById(\"bulk-distribute-v\");\n      if (bulkDistributeV) {\n       bulkDistributeV.addEventListener(\"click\", () => distributeSelectedNodes(\"vertical\"));\n      }\n      const bulkClone = document.getElementById(\"bulk-clone\");\n      if (bulkClone) {\n       bulkClone.addEventListener(\"click\", () => {\n        if (selectedNodes.size === 0) return;\n        pushUndo(\"clone selected\");\n        const nodesToClone = Array.from(selectedNodes);\n        selectedNodes.clear();\n       nodesToClone.forEach(id => {\n         cloneNode(id, true);\n         selectedNodes.add(currentNodeId);\n        });\n        updateNodeSelection();\n       });\n      }\n      const bulkDelete = document.getElementById(\"bulk-delete\");\n      if (bulkDelete) {\n       bulkDelete.addEventListener(\"click\", deleteSelected);\n      }\n      const bulkMobileBtn = document.getElementById(\"bulk-mobile-btn\");\n      const bulkActionsModal = document.getElementById(\"bulk-actions-modal\");\n      const bulkModalClose = document.getElementById(\"bulk-modal-close\");\n      if (bulkMobileBtn) {\n       bulkMobileBtn.addEventListener(\"click\", () => {\n        if (bulkActionsModal) bulkActionsModal.style.display = \"block\";\n       });\n      }\n      if (bulkModalClose) {\n       bulkModalClose.addEventListener(\"click\", () => {\n        if (bulkActionsModal) bulkActionsModal.style.display = \"none\";\n       });\n      }\n      const closeModalAfterAction = () => {\n       if (bulkActionsModal) bulkActionsModal.style.display = \"none\";\n      };\n      const bulkAlignLeftMobile = document.getElementById(\"bulk-align-left-mobile\");\n      if (bulkAlignLeftMobile) {\n       bulkAlignLeftMobile.addEventListener(\"click\", () => {\n        alignSelectedNodes(\"left\");\n        closeModalAfterAction();\n       });\n      }\n      const bulkAlignRightMobile = document.getElementById(\"bulk-align-right-mobile\");\n      if (bulkAlignRightMobile) {\n       bulkAlignRightMobile.addEventListener(\"click\", () => {\n        alignSelectedNodes(\"right\");\n        closeModalAfterAction();\n       });\n      }\n      const bulkAlignTopMobile = document.getElementById(\"bulk-align-top-mobile\");\n      if (bulkAlignTopMobile) {\n       bulkAlignTopMobile.addEventListener(\"click\", () => {\n        alignSelectedNodes(\"top\");\n        closeModalAfterAction();\n       });\n      }\n      const bulkAlignBottomMobile = document.getElementById(\"bulk-align-bottom-mobile\");\n      if (bulkAlignBottomMobile) {\n       bulkAlignBottomMobile.addEventListener(\"click\", () => {\n        alignSelectedNodes(\"bottom\");\n        closeModalAfterAction();\n       });\n      }\n      const bulkDistributeHMobile = document.getElementById(\"bulk-distribute-h-mobile\");\n      if (bulkDistributeHMobile) {\n       bulkDistributeHMobile.addEventListener(\"click\", () => {\n        distributeSelectedNodes(\"horizontal\");\n        closeModalAfterAction();\n       });\n      }\n      const bulkDistributeVMobile = document.getElementById(\"bulk-distribute-v-mobile\");\n      if (bulkDistributeVMobile) {\n       bulkDistributeVMobile.addEventListener(\"click\", () => {\n        distributeSelectedNodes(\"vertical\");\n        closeModalAfterAction();\n       });\n      }\n      const bulkCloneMobile = document.getElementById(\"bulk-clone-mobile\");\n      if (bulkCloneMobile) {\n       bulkCloneMobile.addEventListener(\"click\", () => {\n        if (selectedNodes.size === 0) return;\n        pushUndo(\"clone selected\");\n        const nodesToClone = Array.from(selectedNodes);\n        selectedNodes.clear();\n        nodesToClone.forEach(id => {\n         cloneNode(id, true);\n         selectedNodes.add(currentNodeId);\n        });\n        updateNodeSelection();\n        closeModalAfterAction();\n       });\n      }\n      const bulkDeleteMobile = document.getElementById(\"bulk-delete-mobile\");\n      if (bulkDeleteMobile) {\n       bulkDeleteMobile.addEventListener(\"click\", () => {\n        deleteSelected();\n        closeModalAfterAction();\n       });\n      }\n      const bulkLockMobile = document.getElementById(\"bulk-lock-mobile\");\n      if (bulkLockMobile) {\n       bulkLockMobile.addEventListener(\"click\", () => {\n        toggleLockSelected();\n        closeModalAfterAction();\n       });\n      }\n      const bulkGroupMobile = document.getElementById(\"bulk-group-mobile\");\n      if (bulkGroupMobile) {\n       bulkGroupMobile.addEventListener(\"click\", () => {\n        toggleGroupSelected();\n        closeModalAfterAction();\n       });\n      }\n      const bulkClearMobile = document.getElementById(\"bulk-clear-mobile\");\n      if (bulkClearMobile) {\n       bulkClearMobile.addEventListener(\"click\", () => {\n        clearSelection();\n        closeModalAfterAction();\n       });\n      }\n      const searchInput = document.getElementById(\"search-nodes\");\n      let searchTimeout = null;\n      if (searchInput) {\n       searchInput.addEventListener(\"input\", (e) => {\n        clearTimeout(searchTimeout);\n        searchTimeout = setTimeout(() => { searchNodes(e.target.value); }, 150);\n\t\t });\n\t\t}\n       document.getElementById(\"save-file-btn\").addEventListener(\"click\", becomeImmortal);\n       document.getElementById(\"check-all-ping-btn\").addEventListener(\"click\", checkAllNodesStatus);\n       const addNodeBtn = document.getElementById(\"add-node-btn\");\n       const addNodeModal = document.getElementById(\"add-node-modal\");\n       const addNodeCancel = document.getElementById(\"add-node-cancel\");\n       const addNodeSave = document.getElementById(\"add-node-save\");\n       addNodeBtn.addEventListener(\"click\", () => {\n\t    if (isViewOnly()) return;\n        document.getElementById(\"new-node-name\").value = \"\";\n        document.getElementById(\"new-node-ip\").value = \"\";\n        document.getElementById(\"new-node-tags\").value = \"\";\n        document.getElementById(\"new-node-category\").value = \"basic\";\n        populateModalShapeSelect(\"new-node-category\", \"new-node-shape\", \"new-node-shape-preview\");\n        selectedNodeIconData = null;\n        document.getElementById('selected-node-icon').style.display = 'none';\n        const nodeShapePreview = document.getElementById('new-node-shape-preview');\n        if (nodeShapePreview) nodeShapePreview.style.display = 'flex';\n        document.getElementById(\"new-node-pingable\").checked = false;\n        document.getElementById(\"new-node-ping-protocol\").value = \"http\";\n        document.getElementById(\"new-node-custom-url\").value = \"\";\n        document.getElementById(\"new-node-ping-timeout\").value = \"3000\";\n        document.getElementById(\"new-node-ping-options\").style.display = \"none\";\n        document.getElementById(\"new-node-custom-url-container\").style.display = \"none\";\n        newNodeIconTags = [];\n        document.getElementById(\"new-node-icon-tags\").style.display = \"none\";\n        document.getElementById(\"new-node-icon-tags-list\").innerHTML = \"\";\n        document.getElementById(\"new-node-fill-color\").value = \"#1e293b\";\n        document.getElementById(\"new-node-border-color\").value = \"#475569\";\n        document.getElementById(\"new-node-size\").value = \"55\";\n        document.getElementById(\"new-node-size-value\").textContent = \"55\";\n        document.getElementById(\"new-node-layer\").value = \"layer1\";\n        document.getElementById(\"new-node-line-color\").value = \"#475569\";\n        document.getElementById(\"new-node-line-direction\").value = \"none\";\n        document.getElementById(\"new-node-line-routing\").value = \"orthogonal\";\n        document.getElementById(\"new-node-notes\").value = \"\";\n        const moreOpts = document.getElementById(\"new-node-more-options\");\n        if (moreOpts) moreOpts.removeAttribute(\"open\");\n        const connectSelect = document.getElementById(\"new-node-connect-to\");\n        connectSelect.innerHTML = '<option value=\"\">None</option>';\n        Object.entries(NODE_DATA).forEach(([nid, n]) => {\n         if (n.assignedRack) return;\n         const opt = document.createElement(\"option\");\n         opt.value = nid;\n         opt.textContent = n.name || nid;\n         connectSelect.appendChild(opt);\n        });\n        addNodeModal.classList.add(\"active\");\n        document.getElementById(\"new-node-name\").focus();\n       });\n       const canvasViewport = document.getElementById(\"canvas-viewport\");\n       if (canvasViewport) {\n        canvasViewport.addEventListener(\"dblclick\", (e) => {\n         if (currentView.mode === \"rack\" && e.target.id === \"map\") {\n          exitRack();\n         }\n        });\n       }\n       const layersBtn = document.getElementById(\"layers-btn\");\n       const layerModal = document.getElementById(\"layer-modal\");\n       const layerModalClose = document.getElementById(\"layer-modal-close\");\n       if (layersBtn && layerModal) {\n        layersBtn.addEventListener(\"click\", () => {\n         layerModal.classList.add(\"active\");\n        });\n       }\n       if (layerModalClose && layerModal) {\n        layerModalClose.addEventListener(\"click\", () => {\n         layerModal.classList.remove(\"active\");\n        });\n       }\n       if (layerModal) {\n        layerModal.addEventListener(\"click\", (e) => {\n         if (e.target === layerModal) {\n          layerModal.classList.remove(\"active\");\n         }\n        });\n       }\n       const tabsBtn = document.getElementById(\"tabs-btn\");\n       const tabsModal = document.getElementById(\"tabs-modal\");\n       const tabsModalClose = document.getElementById(\"tabs-modal-close\");\n       if (tabsBtn && tabsModal) {\n         tabsBtn.addEventListener(\"click\", () => {\n           displayTabs();\n           tabsModal.classList.add(\"active\");\n         });\n       }\n       if (tabsModalClose && tabsModal) {\n         tabsModalClose.addEventListener(\"click\", () => {\n           tabsModal.classList.remove(\"active\");\n         });\n       }\n       if (tabsModal) {\n         tabsModal.addEventListener(\"click\", (e) => {\n           if (e.target === tabsModal) {\n             tabsModal.classList.remove(\"active\");\n           }\n         });\n       }\n       const rollbackBtn = document.getElementById(\"rollback-btn\");\n       const rollbackModal = document.getElementById(\"rollback-modal\");\n       const rollbackModalClose = document.getElementById(\"rollback-modal-close\");\n       if (rollbackBtn && rollbackModal) {\n         rollbackBtn.addEventListener(\"click\", () => {\n           loadRollbackVersions();\n           rollbackModal.classList.add(\"active\");\n         });\n       }\n       if (rollbackModalClose && rollbackModal) {\n         rollbackModalClose.addEventListener(\"click\", () => {\n           rollbackModal.classList.remove(\"active\");\n         });\n       }\n       if (rollbackModal) {\n         rollbackModal.addEventListener(\"click\", (e) => {\n           if (e.target === rollbackModal) {\n             rollbackModal.classList.remove(\"active\");\n           }\n         });\n       }\n       const auditLogBtn = document.getElementById(\"audit-log-btn\");\n       const auditLogModal = document.getElementById(\"audit-log-modal\");\n       const auditLogModalClose = document.getElementById(\"audit-log-modal-close\");\n       if (auditLogBtn && auditLogModal) {\n         auditLogBtn.addEventListener(\"click\", () => {\n           loadAuditLog();\n           displayAuditLog();\n           auditLogModal.classList.add(\"active\");\n         });\n       }\n       if (auditLogModalClose && auditLogModal) {\n         auditLogModalClose.addEventListener(\"click\", () => {\n           auditLogModal.classList.remove(\"active\");\n         });\n       }\n\t   const portMapBtn = document.getElementById(\"port-map-btn\");\n      const portMapModal = document.getElementById(\"port-map-modal\");\n      const portMapClose = document.getElementById(\"port-map-modal-close\");\n      const portMapSearch = document.getElementById(\"port-map-search\");\n      const portMapFilter = document.getElementById(\"port-map-filter\");\n      function renderPortMap() {\n        const container = document.getElementById(\"port-map-table\");\n        const search = (document.getElementById(\"port-map-search\")?.value || \"\").toLowerCase();\n        const filter = document.getElementById(\"port-map-filter\")?.value || \"all\";\n        let edges = EDGE_DATA.list || [];\n        if (filter === \"with-ports\") edges = edges.filter(e => e.fromPort || e.toPort);\n        if (filter === \"without-ports\") edges = edges.filter(e => !e.fromPort && !e.toPort);\n        if (search) {\n          edges = edges.filter(e => {\n            const fromName = (NODE_DATA[e.from]?.name || e.from || \"\").toLowerCase();\n            const toName = (NODE_DATA[e.to]?.name || e.to || \"\").toLowerCase();\n            return fromName.includes(search) || toName.includes(search) || (e.fromPort || \"\").toLowerCase().includes(search) || (e.toPort || \"\").toLowerCase().includes(search) || (e.notes || []).join(\" \").toLowerCase().includes(search);\n          });\n        }\n        if (edges.length === 0) {\n          container.innerHTML = '<div style=\"text-align: center; padding: 40px; color: var(--text-soft);\">' + t(\"emptyStates.noConnectionsFound\") + '</div>';\n          return;\n        }\n        let html = '<table style=\"width: 100%; border-collapse: collapse; font-size: 13px;\">';\n        html += '<thead><tr style=\"background: var(--panel-alt); position: sticky; top: 0;\"><th style=\"padding: 10px; text-align: left; border-bottom: 1px solid var(--edge-main);\">From Device</th><th style=\"padding: 10px; text-align: left; border-bottom: 1px solid var(--edge-main);\">From Port</th><th style=\"padding: 10px; text-align: left; border-bottom: 1px solid var(--edge-main);\">To Device</th><th style=\"padding: 10px; text-align: left; border-bottom: 1px solid var(--edge-main);\">To Port</th><th style=\"padding: 10px; text-align: left; border-bottom: 1px solid var(--edge-main);\">Notes</th></tr></thead><tbody>';\n        edges.forEach(e => {\n          const fromName = NODE_DATA[e.from]?.name || e.from;\n          const toName = NODE_DATA[e.to]?.name || e.to;\n          const notes = (e.notes || []).join(\", \");\n          const edgeColor = e.color || EDGE_LEGEND[e.type]?.color || 'var(--edge-main)';\n          const fromPortDisplay = e.fromPort ? escapeHtml(e.fromPort) : \"-\";\n          const toPortDisplay = e.toPort ? escapeHtml(e.toPort) : \"-\";\n          const goToEdge = `document.getElementById('port-map-modal').classList.remove('active'); selectTheConnection('${escapeHtml(e.id)}'); focusOnSelected()`;\n          html += `<tr style=\"border-bottom: 1px solid var(--edge-main);\"><td style=\"padding: 8px;\"><span style=\"display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: ${edgeColor}; margin-right: 6px;\"></span><button onclick=\"document.getElementById('port-map-modal').classList.remove('active'); claimTheImmortal('${escapeHtml(e.from)}'); focusOnSelected()\" style=\"background: var(--panel-alt); color: var(--text-main); border: 1px solid var(--edge-main); border-radius: 4px; padding: 6px 10px; cursor: pointer; font-size: 13px; min-height: 36px;\">${escapeHtml(fromName)}</button></td><td style=\"padding: 8px;\"><button onclick=\"editPortFromMap('${escapeHtml(e.id)}', 'from')\" style=\"background: ${e.fromPort ? 'var(--accent)' : 'var(--panel)'}; color: ${e.fromPort ? 'var(--bg)' : 'var(--text-soft)'}; border: 1px solid var(--edge-main); border-radius: 4px; padding: 6px 10px; cursor: pointer; font-family: monospace; font-size: 13px; min-height: 36px;\">${fromPortDisplay}</button></td><td style=\"padding: 8px;\"><button onclick=\"document.getElementById('port-map-modal').classList.remove('active'); claimTheImmortal('${escapeHtml(e.to)}'); focusOnSelected()\" style=\"background: var(--panel-alt); color: var(--text-main); border: 1px solid var(--edge-main); border-radius: 4px; padding: 6px 10px; cursor: pointer; font-size: 13px; min-height: 36px;\">${escapeHtml(toName)}</button></td><td style=\"padding: 8px;\"><button onclick=\"editPortFromMap('${escapeHtml(e.id)}', 'to')\" style=\"background: ${e.toPort ? 'var(--accent)' : 'var(--panel)'}; color: ${e.toPort ? 'var(--bg)' : 'var(--text-soft)'}; border: 1px solid var(--edge-main); border-radius: 4px; padding: 6px 10px; cursor: pointer; font-family: monospace; font-size: 13px; min-height: 36px;\">${toPortDisplay}</button></td><td style=\"padding: 8px;\"><button onclick=\"${goToEdge}\" style=\"background: var(--panel); color: var(--text-main); border: 1px solid var(--edge-main); border-radius: 4px; padding: 6px 10px; cursor: pointer; font-size: 13px; min-height: 36px;\" title=\"Go to connection\">Go</button></td></tr>`;\n        });\n        html += '</tbody></table>';\n        container.innerHTML = html;\n      }\n      window.editPortFromMap = async function(edgeId, which) {\n        const edge = EDGE_DATA.list.find(e => e.id === edgeId);\n        if (!edge) return;\n        const fromName = NODE_DATA[edge.from]?.name || edge.from;\n        const toName = NODE_DATA[edge.to]?.name || edge.to;\n        const nodeId = which === 'from' ? edge.from : edge.to;\n        const nodeName = which === 'from' ? fromName : toName;\n        const currentVal = which === 'from' ? (edge.fromPort || '') : (edge.toPort || '');\n        const newVal = await showPrompt(t(\"dialogs.enterPort\", { nodeName: nodeName }), currentVal);\n        if (newVal !== null && newVal !== '') {\n          const isDuplicate = (EDGE_DATA.list || []).some(e => {\n            if (e.id === edgeId) return false;\n            if (e.from === nodeId && e.fromPort === newVal) return true;\n            if (e.to === nodeId && e.toPort === newVal) return true;\n            return false;\n          });\n          if (isDuplicate && !await showConfirm(t(\"dialogs.portAlreadyUsed\", { port: newVal, nodeName: nodeName }))) {\n            return;\n          }\n          if (which === 'from') edge.fromPort = newVal;\n          else edge.toPort = newVal;\n          renderPortMap();\n          if (currentNodeId && (edge.from === currentNodeId || edge.to === currentNodeId)) {\n            claimTheImmortal(currentNodeId);\n          }\n        } else if (newVal === '') {\n          if (which === 'from') edge.fromPort = '';\n          else edge.toPort = '';\n          renderPortMap();\n          if (currentNodeId && (edge.from === currentNodeId || edge.to === currentNodeId)) {\n            claimTheImmortal(currentNodeId);\n          }\n        }\n      };\n      window.exportPortMap = function() {\n        let csv = \"From Device,From Port,To Device,To Port,Notes\\n\";\n        (EDGE_DATA.list || []).forEach(e => {\n          const fromName = NODE_DATA[e.from]?.name || e.from || \"\";\n          const toName = NODE_DATA[e.to]?.name || e.to || \"\";\n          const notes = (e.notes || []).join(\"; \");\n          csv += `${csvEscape(fromName)},${csvEscape(e.fromPort || \"\")},${csvEscape(toName)},${csvEscape(e.toPort || \"\")},${csvEscape(notes)}\\n`;\n        });\n        const blob = new Blob([csv], { type: \"text/csv\" });\n        const url = URL.createObjectURL(blob);\n        const a = document.createElement(\"a\");\n        a.href = url;\n        a.download = \"port-map.csv\";\n        a.click();\n        URL.revokeObjectURL(url);\n      };\n      if (portMapBtn && portMapModal) {\n        portMapBtn.addEventListener(\"click\", () => { renderPortMap(); portMapModal.classList.add(\"active\"); });\n      }\n      if (portMapClose && portMapModal) {\n        portMapClose.addEventListener(\"click\", () => { portMapModal.classList.remove(\"active\"); });\n      }\n      if (portMapSearch) { portMapSearch.addEventListener(\"input\", renderPortMap); }\n      if (portMapFilter) { portMapFilter.addEventListener(\"change\", renderPortMap); }\n      if (portMapModal) {\n        portMapModal.addEventListener(\"click\", (e) => { if (e.target === portMapModal) portMapModal.classList.remove(\"active\"); });\n      }\n       if (auditLogModal) {\n         auditLogModal.addEventListener(\"click\", (e) => {\n           if (e.target === auditLogModal) {\n             auditLogModal.classList.remove(\"active\");\n           }\n         });\n       }\n       const auditFilter = document.getElementById(\"audit-filter\");\n       if (auditFilter) {\n         auditFilter.addEventListener(\"change\", (e) => {\n           displayAuditLog(e.target.value);\n         });\n       }\n       const secretsBtn = document.getElementById(\"secrets-btn\");\n       const secretsModal = document.getElementById(\"secrets-modal\");\n       const secretsModalClose = document.getElementById(\"secrets-modal-close\");\n       if (secretsBtn && secretsModal) {\n         secretsBtn.addEventListener(\"click\", () => {\n           displaySecrets();\n           secretsModal.classList.add(\"active\");\n         });\n       }\n       if (secretsModalClose && secretsModal) {\n         secretsModalClose.addEventListener(\"click\", () => {\n           secretsModal.classList.remove(\"active\");\n         });\n       }\n       if (secretsModal) {\n         secretsModal.addEventListener(\"click\", (e) => {\n           if (e.target === secretsModal) {\n             secretsModal.classList.remove(\"active\");\n           }\n         });\n       }\n       const secretEditorModal = document.getElementById(\"secret-editor-modal\");\n       if (secretEditorModal) {\n         secretEditorModal.addEventListener(\"click\", (e) => {\n           if (e.target === secretEditorModal) {\n             closeSecretEditor();\n           }\n         });\n       }\n\n       function execSecretCommand(command, value = null) {\n         const content = document.getElementById(\"secret-editor-content\");\n         content.focus();\n         if (command === \"code\") {\n           const sel = window.getSelection();\n           if (sel.rangeCount > 0) {\n             const range = sel.getRangeAt(0);\n             const selectedText = range.toString();\n             if (selectedText) {\n               const code = document.createElement(\"code\");\n               code.textContent = selectedText;\n               range.deleteContents();\n               range.insertNode(code);\n               range.setStartAfter(code);\n               range.collapse(true);\n               sel.removeAllRanges();\n               sel.addRange(range);\n             }\n           }\n         } else if (command === \"codeblock\") {\n           const sel = window.getSelection();\n           if (sel.rangeCount > 0) {\n             const range = sel.getRangeAt(0);\n             const selectedText = range.toString() || \"code here\";\n             const pre = document.createElement(\"pre\");\n             const code = document.createElement(\"code\");\n             code.textContent = selectedText;\n             pre.appendChild(code);\n             range.deleteContents();\n             range.insertNode(pre);\n             const p = document.createElement(\"p\");\n             p.innerHTML = \"<br>\";\n             pre.parentNode.insertBefore(p, pre.nextSibling);\n             range.setStart(p, 0);\n             range.collapse(true);\n             sel.removeAllRanges();\n             sel.addRange(range);\n           }\n         } else if (command === \"blockquote\") {\n           document.execCommand(\"formatBlock\", false, \"blockquote\");\n         } else if (command === \"createLink\") {\n           const url = prompt(\"Enter URL:\", \"https://\");\n           if (url) document.execCommand(\"createLink\", false, url);\n         } else if (command === \"heading\") {\n           if (value) {\n             document.execCommand(\"formatBlock\", false, value);\n           } else {\n             document.execCommand(\"formatBlock\", false, \"p\");\n           }\n         } else {\n           document.execCommand(command, false, null);\n         }\n       }\n       document.querySelectorAll(\"#secret-editor-toolbar button[data-command]\").forEach(btn => {\n         btn.addEventListener(\"click\", (e) => {\n           e.preventDefault();\n           execSecretCommand(btn.dataset.command);\n         });\n       });\n       const secretHeadingSelect = document.getElementById(\"secret-heading-select\");\n       if (secretHeadingSelect) {\n         secretHeadingSelect.addEventListener(\"change\", (e) => {\n           execSecretCommand(\"heading\", e.target.value);\n           e.target.value = \"\";\n         });\n       }\n\n       const notesHubSearch = document.getElementById(\"notes-hub-search\");\n       const notesHubFilter = document.getElementById(\"notes-hub-filter\");\n       if (notesHubSearch) {\n         notesHubSearch.addEventListener(\"input\", () => displaySecrets());\n       }\n       if (notesHubFilter) {\n         notesHubFilter.addEventListener(\"change\", () => displaySecrets());\n       }\n       [\"1\", \"2\", \"3\", \"4\"].forEach(num => {\n        const checkbox = document.getElementById(`layer-${num}`);\n        if (checkbox) {\n         checkbox.addEventListener(\"change\", applyLayerFilter);\n        }\n       });\n       const layerSelect = document.getElementById(\"node-layer\");\n       if (layerSelect) {\n        layerSelect.addEventListener(\"change\", (e) => {\n         if (currentNodeId) {\n          pushUndo(\"change layer\");\n          NODE_DATA[currentNodeId].layer = e.target.value;\n          forgeTheTopology();\n         }\n        });\n       }\n       const assignedRackSelect = document.getElementById(\"node-assigned-rack\");\n       if (assignedRackSelect) {\n        assignedRackSelect.addEventListener(\"change\", (e) => {\n      if (currentNodeId) {\n      pushUndo(\"change assigned rack\");\n      EDGE_DATA.list = EDGE_DATA.list.filter(edge => edge.from !== currentNodeId && edge.to !== currentNodeId);\n      NODE_DATA[currentNodeId].assignedRack = e.target.value;\n      forgeTheTopology();\n      claimTheImmortal(currentNodeId);\n      }\n      });\n       }\n       const hostedOnSelect = document.getElementById(\"node-hosted-on\");\n       if (hostedOnSelect) {\n        hostedOnSelect.addEventListener(\"change\", (e) => {\n         if (currentNodeId) {\n          pushUndo(\"change hosted on\");\n          NODE_DATA[currentNodeId].hostedOn = e.target.value;\n          forgeTheTopology();\n         }\n        });\n       }\n       const rackCapacitySelect = document.getElementById(\"node-rack-capacity\");\n       if (rackCapacitySelect) {\n        rackCapacitySelect.addEventListener(\"change\", (e) => {\n         if (currentNodeId) {\n          pushUndo(\"change rack capacity\");\n          NODE_DATA[currentNodeId].rackCapacity = e.target.value;\n          forgeTheTopology();\n         }\n        });\n       }\n       const addRackBtn = document.getElementById(\"add-rack-btn\");\n       const addRackModal = document.getElementById(\"add-rack-modal\");\n       const addRackCancel = document.getElementById(\"add-rack-cancel\");\n       const addRackSave = document.getElementById(\"add-rack-save\");\n       if (addRackBtn && addRackModal) {\n        addRackBtn.addEventListener(\"click\", () => {\n\t\tif (isViewOnly()) return;\n         document.getElementById(\"new-rack-name\").value = \"\";\n         document.getElementById(\"new-rack-ip\").value = \"\";\n         document.getElementById(\"new-rack-tags\").value = \"\";\n         document.getElementById(\"new-rack-shape\").value = \"server\";\n         document.getElementById(\"new-rack-capacity\").value = \"42\";\n         selectedRackIconData = null;\n         document.getElementById('selected-rack-icon').style.display = 'none';\n         const rackShapePreview = document.getElementById('new-rack-shape-preview');\n         if (rackShapePreview) rackShapePreview.style.display = 'flex';\n         document.getElementById(\"new-rack-fill-color\").value = \"#1e293b\";\n         document.getElementById(\"new-rack-border-color\").value = \"#475569\";\n         document.getElementById(\"new-rack-size\").value = \"55\";\n         document.getElementById(\"new-rack-size-value\").textContent = \"55\";\n         document.getElementById(\"new-rack-layer\").value = \"layer1\";\n         document.getElementById(\"new-rack-notes\").value = \"\";\n         const moreOpts = document.getElementById(\"new-rack-more-options\");\n         if (moreOpts) moreOpts.removeAttribute(\"open\");\n         const assignContainer = document.getElementById(\"new-rack-assign-nodes\");\n         assignContainer.innerHTML = \"\";\n         Object.entries(NODE_DATA).forEach(([nid, n]) => {\n          if (n.isRack) return;\n          if (n.assignedRack) return;\n          const row = document.createElement(\"div\");\n          row.style.cssText = \"display:flex;align-items:center;gap:8px;padding:6px 10px;border:1px solid var(--edge-main);border-radius:4px;margin-bottom:4px;background:var(--panel)\";\n          const cb = document.createElement(\"input\");\n          cb.type = \"checkbox\";\n          cb.style.cssText = \"flex-shrink:0;width:16px;height:16px;margin:0;cursor:pointer\";\n          cb.dataset.nodeId = nid;\n          cb.className = \"new-rack-assign-cb\";\n          const lbl = document.createElement(\"span\");\n          lbl.style.cssText = \"color:var(--text-main);font-size:13px;flex:1;text-align:left\";\n          lbl.textContent = n.name || nid;\n          row.appendChild(cb);\n          row.appendChild(lbl);\n          assignContainer.appendChild(row);\n         });\n         if (!assignContainer.children.length) {\n          assignContainer.innerHTML = '<div style=\"color:var(--text-soft);font-size:13px;text-align:center;padding:10px\">No unassigned nodes</div>';\n         }\n         addRackModal.classList.add(\"active\");\n         document.getElementById(\"new-rack-name\").focus();\n        });\n       }\n       if (addRackCancel && addRackModal) {\n        addRackCancel.addEventListener(\"click\", () => {\n         addRackModal.classList.remove(\"active\");\n        });\n       }\n       if (addRackModal) {\n        addRackModal.addEventListener(\"click\", (e) => {\n         if (e.target === addRackModal) {\n          addRackModal.classList.remove(\"active\");\n         }\n        });\n       }\n       document.getElementById(\"new-node-size\").addEventListener(\"input\", (e) => {\n        document.getElementById(\"new-node-size-value\").textContent = e.target.value;\n       });\n       document.getElementById(\"new-rack-size\").addEventListener(\"input\", (e) => {\n        document.getElementById(\"new-rack-size-value\").textContent = e.target.value;\n       });\n       if (addRackSave && addRackModal) {\n        addRackSave.addEventListener(\"click\", () => {\n         const name = document.getElementById(\"new-rack-name\").value.trim();\n         const ip = document.getElementById(\"new-rack-ip\").value.trim();\n         const tagsStr = document.getElementById(\"new-rack-tags\").value.trim();\n         const shape = document.getElementById(\"new-rack-shape\").value;\n         const capacity = document.getElementById(\"new-rack-capacity\").value;\n         if (!name) {\n          showAlert(t(\"dialogs.enterRackName\"));\n          return;\n         }\n         const tags = tagsStr ? tagsStr.split(\",\").map((t) => t.trim()).filter((t) => t) : [];\n         let baseId = name.toLowerCase().replace(/[^a-z0-9]+/g, \"-\").replace(/^-|-$/g, \"\");\n         if (!baseId) baseId = \"rack\";\n         let nodeId = baseId;\n         let counter = 1;\n         while (NODE_DATA[nodeId]) {\n          nodeId = baseId + \"-\" + counter;\n          counter++;\n         }\n         const rackLayer = document.getElementById(\"new-rack-layer\").value;\n         const rackFillColor = document.getElementById(\"new-rack-fill-color\").value;\n         const rackBorderColor = document.getElementById(\"new-rack-border-color\").value;\n         const rackSize = parseInt(document.getElementById(\"new-rack-size\").value, 10);\n         const rackNotesVal = document.getElementById(\"new-rack-notes\").value.trim();\n         pushUndo(\"add rack\");\n         NODE_DATA[nodeId] = {\n          shape: shape,\n          name: name,\n          ip: ip || \"\",\n          role: \"Rack\",\n          tags: tags,\n          notes: rackNotesVal ? [rackNotesVal] : [],\n          mac: \"\",\n          rackUnit: \"\",\n          uHeight: \"1\",\n          layer: rackLayer,\n          assignedRack: \"\",\n          hostedOn: \"\",\n          rackCapacity: capacity,\n          isRack: true,\n          locked: false,\n          groupId: null\n         };\n         const viewWidth = CANVAS_WIDTH / canvasState.zoom;\n         const viewHeight = CANVAS_HEIGHT / canvasState.zoom;\n         const centerX = canvasState.panX + (viewWidth / 2);\n         const centerY = canvasState.panY + (viewHeight / 2);\n         savedPositions[nodeId] = {\n          x: centerX,\n          y: centerY\n         };\n         if (selectedRackIconData) {\n          if (!savedStyles[nodeId]) {\n           savedStyles[nodeId] = {};\n          }\n          if (!savedStyles[nodeId]['all']) {\n           savedStyles[nodeId]['all'] = {};\n          }\n          savedStyles[nodeId]['all'].icon = {\n           library: selectedRackIconData.library,\n           name: selectedRackIconData.name\n          };\n          selectedRackIconData = null;\n          document.getElementById('selected-rack-icon').style.display = 'none';\n         }\n         if (rackFillColor !== \"#1e293b\" || rackBorderColor !== \"#475569\") {\n          const styleEntry = ensureStyleEntry(nodeId);\n          if (!styleEntry[\"all\"]) styleEntry[\"all\"] = {};\n          if (rackFillColor !== \"#1e293b\") styleEntry[\"all\"].circleColor = rackFillColor;\n          if (rackBorderColor !== \"#475569\") styleEntry[\"all\"].circleBorder = rackBorderColor;\n         }\n         if (rackSize !== 55) {\n          savedSizes[nodeId] = rackSize;\n         }\n         document.querySelectorAll(\".new-rack-assign-cb:checked\").forEach((cb) => {\n          const assignId = cb.dataset.nodeId;\n          if (NODE_DATA[assignId]) {\n           NODE_DATA[assignId].assignedRack = nodeId;\n          }\n         });\n         addRackModal.classList.remove(\"active\");\n         forgeTheTopology();\n         claimTheImmortal(nodeId);\n        });\n        [\"new-rack-name\", \"new-rack-ip\", \"new-rack-tags\"].forEach((inputId) => {\n         const input = document.getElementById(inputId);\n         if (input) {\n          input.addEventListener(\"keypress\", (e) => {\n           if (e.key === \"Enter\") {\n            addRackSave.click();\n           }\n          });\n         }\n        });\n       }\n       addNodeCancel.addEventListener(\"click\", () => {\n        addNodeModal.classList.remove(\"active\");\n       });\n       addNodeModal.addEventListener(\"click\", (e) => {\n        if (e.target === addNodeModal) {\n         addNodeModal.classList.remove(\"active\");\n        }\n       });\n       addNodeSave.addEventListener(\"click\", () => {\n        const name = document.getElementById(\"new-node-name\").value.trim();\n        const ip = document.getElementById(\"new-node-ip\").value.trim();\n        const tagsStr = document.getElementById(\"new-node-tags\").value.trim();\n        const shape = document.getElementById(\"new-node-shape\").value;\n        const pingable = document.getElementById(\"new-node-pingable\").checked;\n        const pingProtocol = document.getElementById(\"new-node-ping-protocol\").value;\n        const pingCustomUrl = document.getElementById(\"new-node-custom-url\").value.trim();\n        const pingTimeout = parseInt(document.getElementById(\"new-node-ping-timeout\").value) || 3000;\n        if (!name) {\n         showAlert(t(\"dialogs.enterNodeName\"));\n         return;\n        }\n        const tags = tagsStr ? tagsStr.split(\",\").map((t) => t.trim()).filter((t) => t) : [];\n        if (newNodeIconTags.length > 0) {\n         tags.push(...newNodeIconTags);\n        }\n        let baseId = name.toLowerCase().replace(/[^a-z0-9]+/g, \"-\").replace(/^-|-$/g, \"\");\n        if (!baseId) baseId = \"node\";\n        let nodeId = baseId;\n        let counter = 1;\n        while (NODE_DATA[nodeId]) {\n         nodeId = baseId + \"-\" + counter;\n         counter++;\n        }\n        const nodeLayer = document.getElementById(\"new-node-layer\").value;\n        const nodeFillColor = document.getElementById(\"new-node-fill-color\").value;\n        const nodeBorderColor = document.getElementById(\"new-node-border-color\").value;\n        const nodeSize = parseInt(document.getElementById(\"new-node-size\").value, 10);\n        const nodeConnectTo = document.getElementById(\"new-node-connect-to\").value;\n        const nodeLineColor = document.getElementById(\"new-node-line-color\").value;\n        const nodeLineDirection = document.getElementById(\"new-node-line-direction\").value;\n        const nodeLineRouting = document.getElementById(\"new-node-line-routing\").value;\n        const nodeNotesVal = document.getElementById(\"new-node-notes\").value.trim();\n\t\tpushUndo(\"add node\");\n        NODE_DATA[nodeId] = {\n         shape: shape || \"circle\",\n         name: name,\n         ip: ip || \"\",\n         role: \"\",\n         tags: tags,\n         notes: nodeNotesVal ? [nodeNotesVal] : [],\n         mac: \"\",\n         rackUnit: \"\",\n         uHeight: \"1\",\n         layer: nodeLayer,\n         assignedRack: \"\",\n         hostedOn: \"\",\n         ping: {\n          enabled: pingable,\n          protocol: pingProtocol,\n          customUrl: pingCustomUrl,\n          timeout: pingTimeout,\n          status: 'unknown',\n          lastCheck: null\n         },\n         locked: false,\n         groupId: null\n        };\n        if (currentView.mode === \"rack\" && currentView.rackId) {\n         NODE_DATA[nodeId].assignedRack = currentView.rackId;\n         NODE_DATA[nodeId].layer = \"layer1\";\n         const rackCapacity = getRackCapacity(currentView.rackId);\n         const rackUHeight = getRackUHeight(currentView.rackId);\n         const viewHeight = CANVAS_HEIGHT / canvasState.zoom;\n         const centerY = canvasState.panY + (viewHeight / 2);\n         let unit = rackCapacity - Math.round((centerY - RACK_START_Y) / rackUHeight);\n         unit = Math.max(1, Math.min(rackCapacity, unit));\n         NODE_DATA[nodeId].rackUnit = String(unit);\n        }\n        if (selectedNodeIconData) {\n         if (!savedStyles[nodeId]) savedStyles[nodeId] = {};\n         if (!savedStyles[nodeId]['all']) savedStyles[nodeId]['all'] = {};\n         savedStyles[nodeId]['all'].icon = {\n          library: selectedNodeIconData.library,\n          name: selectedNodeIconData.name\n         };\n         selectedNodeIconData = null;\n         document.getElementById('selected-node-icon').style.display = 'none';\n        }\n        newNodeIconTags = [];\n      const viewWidth = CANVAS_WIDTH / canvasState.zoom;\n      const viewHeight = CANVAS_HEIGHT / canvasState.zoom;\n      const centerX = canvasState.panX + (viewWidth / 2);\n      const centerY = canvasState.panY + (viewHeight / 2);\n      savedPositions[nodeId] = {\n      x: centerX,\n      y: centerY\n      };\n        if (nodeFillColor !== \"#1e293b\" || nodeBorderColor !== \"#475569\") {\n         const styleEntry = ensureStyleEntry(nodeId);\n         if (!styleEntry[\"all\"]) styleEntry[\"all\"] = {};\n         if (nodeFillColor !== \"#1e293b\") styleEntry[\"all\"].circleColor = nodeFillColor;\n         if (nodeBorderColor !== \"#475569\") styleEntry[\"all\"].circleBorder = nodeBorderColor;\n        }\n        if (nodeSize !== 55) {\n         savedSizes[nodeId] = nodeSize;\n        }\n        if (nodeConnectTo && NODE_DATA[nodeConnectTo]) {\n         EDGE_DATA.list.push({\n          id: nodeId + \"-\" + nodeConnectTo + \"-\" + Date.now(),\n          from: nodeId,\n          to: nodeConnectTo,\n          width: 4,\n          color: nodeLineColor || \"#475569\",\n          direction: nodeLineDirection || \"none\",\n          routing: nodeLineRouting || \"orthogonal\",\n          type: \"main\",\n          notes: [],\n          fromPort: \"\",\n          toPort: \"\",\n          lineStyle: \"solid\",\n          waypoints: []\n         });\n        }\n        addNodeModal.classList.remove(\"active\");\n        forgeTheTopology();\n        claimTheImmortal(nodeId);\n       });\n       [\"new-node-name\", \"new-node-ip\", \"new-node-tags\"].forEach(\n        (inputId) => {\n         document.getElementById(inputId).addEventListener(\"keypress\", (e) => {\n          if (e.key === \"Enter\") {\n           addNodeSave.click();\n          }\n         });\n        });\n       document.getElementById('new-node-pingable').addEventListener('change', (e) => {\n        const pingOptions = document.getElementById('new-node-ping-options');\n        pingOptions.style.display = e.target.checked ? 'block' : 'none';\n       });\n       document.getElementById('new-node-ping-protocol').addEventListener('change', (e) => {\n        const customUrlContainer = document.getElementById('new-node-custom-url-container');\n        customUrlContainer.style.display = e.target.value === 'custom' ? 'block' : 'none';\n       });\n       document.getElementById('pick-rack-icon-btn').addEventListener('click', () => {\n        openIconPicker((iconData) => {\n         selectedRackIconData = iconData;\n         const preview = document.getElementById('selected-rack-icon-preview');\n         const container = document.getElementById('selected-rack-icon');\n         const parser = new DOMParser();\n         const doc = parser.parseFromString(iconData.svg, 'image/svg+xml');\n         const svgEl = doc.querySelector('svg');\n         preview.innerHTML = '';\n         if (svgEl) {\n          preview.appendChild(svgEl.cloneNode(true));\n         }\n         const nameSpan = document.createElement('span');\n         nameSpan.textContent = iconData.name;\n         preview.appendChild(nameSpan);\n         container.style.display = 'block';\n         const shapePreview = document.getElementById('new-rack-shape-preview');\n         if (shapePreview) shapePreview.style.display = 'none';\n        });\n       });\n       document.getElementById('pick-node-icon-btn').addEventListener('click', () => {\n        openIconPicker((iconData) => {\n         selectedNodeIconData = iconData;\n         const preview = document.getElementById('selected-node-icon-preview');\n         const container = document.getElementById('selected-node-icon');\n         const parser = new DOMParser();\n         const doc = parser.parseFromString(iconData.svg, 'image/svg+xml');\n         const svgEl = doc.querySelector('svg');\n         preview.innerHTML = '';\n         if (svgEl) {\n          preview.appendChild(svgEl.cloneNode(true));\n         }\n         const nameSpan = document.createElement('span');\n         nameSpan.textContent = iconData.name;\n         preview.appendChild(nameSpan);\n         container.style.display = 'block';\n         const shapePreview = document.getElementById('new-node-shape-preview');\n         if (shapePreview) shapePreview.style.display = 'none';\n        });\n       });\n       document.getElementById('pick-tag-icon-btn').addEventListener('click', () => {\n        if (!currentNodeId) return;\n        openIconPicker((iconData) => {\n         if (!currentNodeId || !NODE_DATA[currentNodeId]) return;\n         if (!NODE_DATA[currentNodeId].tags) {\n          NODE_DATA[currentNodeId].tags = [];\n         }\n         NODE_DATA[currentNodeId].tags.push({\n          type: 'icon',\n          library: iconData.library,\n          name: iconData.name\n         });\n         forgeTheTopology();\n         claimTheImmortal(currentNodeId);\n        });\n       });\n       document.getElementById('pick-new-node-tag-icon-btn').addEventListener('click', () => {\n        openIconPicker((iconData) => {\n         newNodeIconTags.push({\n          type: 'icon',\n          library: iconData.library,\n          name: iconData.name\n         });\n         const container = document.getElementById('new-node-icon-tags');\n         const list = document.getElementById('new-node-icon-tags-list');\n         const badge = document.createElement('div');\n         badge.className = 'icon-badge';\n         badge.style.cssText = 'display: inline-flex; align-items: center; gap: 6px; padding: 6px 10px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 6px; font-size: 13px;';\n         const iconPreview = document.createElement('div');\n         iconPreview.style.cssText = 'width: 16px; height: 16px; display: flex; align-items: center; justify-content: center;';\n         IconLibrary.getIcon(iconData.library, iconData.name).then(svg => {\n          if (svg) {\n           const parser = new DOMParser();\n           const doc = parser.parseFromString(svg, 'image/svg+xml');\n           const svgEl = doc.querySelector('svg');\n           if (svgEl) {\n            svgEl.setAttribute('width', '16');\n            svgEl.setAttribute('height', '16');\n            svgEl.style.fill = 'var(--text-main)';\n            iconPreview.appendChild(svgEl);\n           }\n          }\n         });\n         const name = document.createElement('span');\n         name.textContent = iconData.name;\n         name.style.color = 'var(--text-soft)';\n         const removeBtn = document.createElement('button');\n         removeBtn.textContent = '×';\n         removeBtn.style.cssText = 'background: none; border: none; color: var(--danger); cursor: pointer; font-size: 18px; line-height: 1; padding: 0 4px;';\n         removeBtn.addEventListener('click', () => {\n          const index = newNodeIconTags.findIndex(t => t.type === 'icon' && t.library === iconData.library && t.name === iconData.name);\n          if (index > -1) {\n           newNodeIconTags.splice(index, 1);\n          }\n          badge.remove();\n          if (list.children.length === 0) {\n           container.style.display = 'none';\n          }\n         });\n         badge.appendChild(iconPreview);\n         badge.appendChild(name);\n         badge.appendChild(removeBtn);\n         list.appendChild(badge);\n         container.style.display = 'block';\n        });\n       });\n       document.getElementById('pick-shape-icon-btn').addEventListener('click', () => {\n        if (!currentNodeId) return;\n        openIconPicker((iconData) => {\n         if (!savedStyles[currentNodeId]) {\n          savedStyles[currentNodeId] = {};\n         }\n         if (!savedStyles[currentNodeId][currentStyleScope]) {\n          savedStyles[currentNodeId][currentStyleScope] = {};\n         }\n         savedStyles[currentNodeId][currentStyleScope].icon = {\n          library: iconData.library,\n          name: iconData.name\n         };\n\t\t delete savedStyles[currentNodeId][currentStyleScope].circleColor;\n         delete savedStyles[currentNodeId][currentStyleScope].circleBorder;\n         const shapeSelect = document.getElementById('shape-select');\n         if (!shapeSelect.querySelector('option[value=\"custom-icon\"]')) {\n          const opt = document.createElement('option');\n          opt.value = 'custom-icon';\n          opt.textContent = 'Custom Icon';\n          shapeSelect.insertBefore(opt, shapeSelect.firstChild);\n         }\n         shapeSelect.value = 'custom-icon';\n         forgeTheTopology();\n        });\n       });\n       document.getElementById('add-tag-btn').addEventListener('click', () => {\n        if (!currentNodeId) return;\n        if (!NODE_DATA[currentNodeId]) return;\n        const input = document.getElementById('new-tag-input');\n        const tagText = input.value.trim();\n        if (!tagText) return;\n        if (!NODE_DATA[currentNodeId].tags) {\n         NODE_DATA[currentNodeId].tags = [];\n        }\n        NODE_DATA[currentNodeId].tags.push(tagText);\n        input.value = '';\n        forgeTheTopology();\n        claimTheImmortal(currentNodeId);\n       });\n      function saveRollbackVersion(description = \"Auto-save\") {\n        const version = {\n          timestamp: Date.now(),\n          description,\n          data: captureTheQuickening()\n        };\n        try {\n          const stored = localStorage.getItem(ROLLBACK_STORAGE_KEY);\n          rollbackVersions = stored ? JSON.parse(stored) : [];\n        } catch (e) {\n          rollbackVersions = [];\n        }\n        rollbackVersions.unshift(version);\n        if (rollbackVersions.length > MAX_ROLLBACK_VERSIONS) {\n          rollbackVersions = rollbackVersions.slice(0, MAX_ROLLBACK_VERSIONS);\n        }\n        try {\n          localStorage.setItem(ROLLBACK_STORAGE_KEY, JSON.stringify(rollbackVersions));\n        } catch (e) {\n          console.warn(\"Failed to save rollback version:\", e);\n        }\n      }\n      function loadRollbackVersions() {\n        try {\n          const stored = localStorage.getItem(ROLLBACK_STORAGE_KEY);\n          rollbackVersions = stored ? JSON.parse(stored) : [];\n          displayRollbackVersions();\n        } catch (e) {\n          console.warn(\"Failed to load rollback versions:\", e);\n          rollbackVersions = [];\n        }\n      }\n      function displayRollbackVersions() {\n        const listEl = document.getElementById(\"rollback-list\");\n        if (!listEl) return;\n        if (rollbackVersions.length === 0) {\n          listEl.innerHTML = '<div style=\"text-align: center; padding: 40px; color: var(--text-soft);\">' + t(\"emptyStates.noVersionHistory\") + '</div>';\n          return;\n        }\n        listEl.innerHTML = rollbackVersions.map((version, index) => {\n          const date = new Date(version.timestamp);\n          const timeStr = date.toLocaleString();\n          const nodeCount = Object.keys(version.data.nodeData || {}).length;\n          const edgeCount = (version.data.edgeData?.list || []).length;\n          return `\n            <div class=\"version-item\" onclick=\"restoreRollbackVersion(${index})\">\n              <div class=\"version-info\">\n                <div class=\"timestamp\">${escapeHtml(timeStr)}</div>\n                <div class=\"details\">${escapeHtml(version.description)} • ${t(\"ui.stats.nodesConnections\", { nodeCount, edgeCount })}</div>\n              </div>\n              <div class=\"version-actions\">\n                <button class=\"btn-cancel\" onclick=\"event.stopPropagation(); deleteRollbackVersion(${index})\" title=\"Delete this version\">🗑️</button>\n              </div>\n            </div>\n          `;\n        }).join('');\n      }\n      async function restoreRollbackVersion(index) {\n        if (!await showConfirm(t(\"dialogs.restoreVersion\", { date: new Date(rollbackVersions[index].timestamp).toLocaleString() }))) {\n          return;\n        }\n        const version = rollbackVersions[index];\n        const data = version.data;\n        NODE_DATA = data.nodeData || {};\n        EDGE_DATA = data.edgeData || { list: [] };\n        RECT_DATA = data.rectData || { list: [] };\n        TEXT_DATA = data.textData || { list: [] };\n        EDGE_LEGEND = data.edgeLegend || {};\n        savedPositions = data.nodePositions || {};\n        savedSizes = data.nodeSizes || {};\n        savedStyles = data.nodeStyles || {};\n        PAGE_STATE = data.page || PAGE_STATE;\n        if (data.canvas) {\n          canvasState.zoom = data.canvas.zoom;\n          canvasState.panX = data.canvas.panX;\n          canvasState.panY = data.canvas.panY;\n        }\n        wieldThePower();\n        forgeTheTopology();\n        document.getElementById(\"rollback-modal\").classList.remove(\"active\");\n        logAuditEvent(\"rollback\", `Restored version from ${new Date(version.timestamp).toLocaleString()}`);\n      }\n      async function deleteRollbackVersion(index) {\n        if (!await showConfirm(t(\"dialogs.deleteVersionConfirm\"))) return;\n        rollbackVersions.splice(index, 1);\n        try {\n          localStorage.setItem(ROLLBACK_STORAGE_KEY, JSON.stringify(rollbackVersions));\n        } catch (e) {\n          console.warn(\"Failed to delete version:\", e);\n        }\n        displayRollbackVersions();\n      }\n      async function clearRollbackHistory() {\n        if (!await showConfirm(t(\"dialogs.clearVersionHistory\"))) return;\n        rollbackVersions = [];\n        localStorage.removeItem(ROLLBACK_STORAGE_KEY);\n        displayRollbackVersions();\n      }\n      async function createManualSnapshot() {\n        const description = await showPrompt(t(\"dialogs.enterSnapshotDescription\"), \"Manual snapshot\");\n        if (!description) return;\n        saveRollbackVersion(description);\n        displayRollbackVersions();\n      }\n      function switchTab(index) {\n        if (index === currentTabIndex) return;\n        const currentTab = documentTabs[currentTabIndex];\n        currentTab.nodes = JSON.parse(JSON.stringify(NODE_DATA));\n        currentTab.edges = JSON.parse(JSON.stringify(EDGE_DATA));\n        currentTab.positions = JSON.parse(JSON.stringify(savedPositions));\n        currentTab.sizes = JSON.parse(JSON.stringify(savedSizes));\n        currentTab.styles = JSON.parse(JSON.stringify(savedStyles));\n        currentTab.legend = JSON.parse(JSON.stringify(EDGE_LEGEND));\n        currentTab.rects = JSON.parse(JSON.stringify(RECT_DATA));\n        currentTab.texts = JSON.parse(JSON.stringify(TEXT_DATA));\n        currentTab.images = JSON.parse(JSON.stringify(IMAGE_DATA));\n        currentTab.pageState = JSON.parse(JSON.stringify(PAGE_STATE));\n        currentTabIndex = index;\n        const newTab = documentTabs[index];\n        NODE_DATA = JSON.parse(JSON.stringify(newTab.nodes));\n        EDGE_DATA = JSON.parse(JSON.stringify(newTab.edges));\n        savedPositions = JSON.parse(JSON.stringify(newTab.positions));\n        savedSizes = JSON.parse(JSON.stringify(newTab.sizes));\n        savedStyles = JSON.parse(JSON.stringify(newTab.styles));\n        EDGE_LEGEND = JSON.parse(JSON.stringify(newTab.legend));\n        RECT_DATA = JSON.parse(JSON.stringify(newTab.rects));\n        TEXT_DATA = JSON.parse(JSON.stringify(newTab.texts));\n        IMAGE_DATA = JSON.parse(JSON.stringify(newTab.images || { list: [] }));\n        PAGE_STATE = Object.assign({}, DEFAULT_PAGE_STATE, newTab.pageState || {});\n        wieldThePower();\n        document.title = PAGE_STATE.title || newTab.name;\n        document.getElementById(\"page-title\").textContent = PAGE_STATE.title || newTab.name;\n        forgeTheTopology();\n        currentNodeId = null;\n        currentEdgeId = null;\n        currentTextId = null;\n        currentRectId = null;\n        currentImageId = null;\n        document.getElementById(\"node-panel\").style.display = \"none\";\n        document.getElementById(\"edge-panel\").style.display = \"none\";\n        document.getElementById(\"text-panel\").style.display = \"none\";\n        document.getElementById(\"rect-panel\").style.display = \"none\";\n        document.getElementById(\"image-panel\").style.display = \"none\";\n        displayTabs();\n        updateDrawToolbarVisibility();\n        updateTopologyToolbarVisibility();\n        logAuditEvent(\"tab\", `Switched to tab: ${newTab.name}`);\n        const hasData = Object.keys(NODE_DATA).length > 0 || EDGE_DATA.list.length > 0;\n        const hasPageState = newTab.pageState !== null && newTab.pageState !== undefined;\n        if (!hasData && !hasPageState) {\n          if (typeof showWelcomeModal === 'function') showWelcomeModal();\n        }\n      }\n      function createNewTab() {\n        const nameInput = document.getElementById(\"new-tab-name\");\n        const name = nameInput.value.trim();\n        if (!name) {\n          showAlert(t(\"dialogs.enterTabName\"));\n          return;\n        }\n        const newTab = {\n          id: `tab-${Date.now()}`,\n          name,\n          nodes: {},\n          edges: { list: [] },\n          positions: {},\n          sizes: {},\n          styles: {},\n          legend: {},\n          rects: { list: [] },\n          texts: { list: [] },\n      pageState: null\n        };\n        documentTabs.push(newTab);\n        nameInput.value = \"\";\n        displayTabs();\n        logAuditEvent(\"tab\", `Created new tab: ${name}`);\n      }\n      async function renameTab(index) {\n        const tab = documentTabs[index];\n        const newName = await showPrompt(t(\"dialogs.enterNewTabName\"), tab.name);\n        if (!newName || newName === tab.name) return;\n        tab.name = newName;\n        displayTabs();\n        logAuditEvent(\"tab\", `Renamed tab to: ${newName}`);\n      }\n      async function deleteTab(index) {\n        if (documentTabs.length === 1) {\n          showAlert(t(\"dialogs.cannotDeleteLastTab\"));\n          return;\n        }\n        if (!await showConfirm(t(\"dialogs.deleteTab\", { name: documentTabs[index].name }))) return;\n        const wasCurrentTab = (index === currentTabIndex);\n        documentTabs.splice(index, 1);\n        if (currentTabIndex >= documentTabs.length) {\n          currentTabIndex = documentTabs.length - 1;\n        } else if (index < currentTabIndex) {\n          currentTabIndex--;\n        }\n        if (wasCurrentTab) {\n          const newTab = documentTabs[currentTabIndex];\n          NODE_DATA = JSON.parse(JSON.stringify(newTab.nodes));\n          EDGE_DATA = JSON.parse(JSON.stringify(newTab.edges));\n          savedPositions = JSON.parse(JSON.stringify(newTab.positions));\n          savedSizes = JSON.parse(JSON.stringify(newTab.sizes));\n          savedStyles = JSON.parse(JSON.stringify(newTab.styles));\n          EDGE_LEGEND = JSON.parse(JSON.stringify(newTab.legend));\n          RECT_DATA = JSON.parse(JSON.stringify(newTab.rects));\n          TEXT_DATA = JSON.parse(JSON.stringify(newTab.texts));\n          IMAGE_DATA = JSON.parse(JSON.stringify(newTab.images || { list: [] }));\n          PAGE_STATE = Object.assign({}, DEFAULT_PAGE_STATE, newTab.pageState || {});\n          wieldThePower();\n          forgeTheTopology();\n          currentNodeId = null;\n          currentEdgeId = null;\n          currentTextId = null;\n          currentRectId = null;\n          currentImageId = null;\n          document.getElementById(\"node-panel\").style.display = \"none\";\n          document.getElementById(\"edge-panel\").style.display = \"none\";\n          document.getElementById(\"text-panel\").style.display = \"none\";\n          document.getElementById(\"rect-panel\").style.display = \"none\";\n          document.getElementById(\"image-panel\").style.display = \"none\";\n        }\n        displayTabs();\n        logAuditEvent(\"tab\", `Deleted tab`);\n      }\n      async function saveCurrentTheme() {\n        const name = await showPrompt(t(\"dialogs.saveThemeName\"), t(\"dialogs.defaultThemeName\", { number: savedStyleSets.length + 1 }));\n        if (!name || !name.trim()) return;\n        const existingIndex = savedStyleSets.findIndex(s => s.name.toLowerCase() === name.trim().toLowerCase());\n        if (existingIndex !== -1) {\n          if (!await showConfirm(t(\"dialogs.themeExists\", { name: name }))) return;\n          savedStyleSets.splice(existingIndex, 1);\n        }\n        const styleSet = {\n          id: \"mytheme-\" + Date.now(),\n          name: name.trim(),\n          styles: JSON.parse(JSON.stringify(PAGE_STATE))\n        };\n        delete styleSet.styles.title;\n        delete styleSet.styles.viewOnly;\n        savedStyleSets.push(styleSet);\n        rebuildThemeDropdown();\n        document.getElementById(\"theme-preset\").value = styleSet.id;\n        updateDeleteButton();\n        logAuditEvent(\"style\", \"Saved theme: \" + name);\n      }\n      async function deleteCurrentTheme() {\n        const select = document.getElementById(\"theme-preset\");\n        const val = select.value;\n        if (!val.startsWith(\"mytheme-\")) return;\n        const index = savedStyleSets.findIndex(s => s.id === val);\n        if (index === -1) return;\n        if (!await showConfirm(t(\"dialogs.deleteTheme\", { name: savedStyleSets[index].name }))) return;\n        savedStyleSets.splice(index, 1);\n        rebuildThemeDropdown();\n        select.value = \"defaulted\";\n        updateDeleteButton();\n        logAuditEvent(\"style\", \"Deleted theme\");\n      }\n      function rebuildThemeDropdown() {\n        const group = document.getElementById(\"my-themes-group\");\n        if (!group) return;\n        const select = document.getElementById(\"theme-preset\");\n        const currentValue = select ? select.value : \"\";\n        group.innerHTML = \"\";\n        savedStyleSets.forEach(function(s) {\n          const opt = document.createElement(\"option\");\n          opt.value = s.id;\n          opt.textContent = s.name;\n          group.appendChild(opt);\n        });\n        if (select && currentValue) {\n          select.value = currentValue;\n        }\n      }\n      function updateDeleteButton() {\n        const select = document.getElementById(\"theme-preset\");\n        const btn = document.getElementById(\"delete-theme-btn\");\n        if (!btn) return;\n\t\tbtn.style.display = \"block\";\n\t\tbtn.disabled = !select.value.startsWith(\"mytheme-\");\n      }\n      function displayTabs() {\n        const listEl = document.getElementById(\"tabs-list\");\n        if (!listEl) return;\n        listEl.innerHTML = documentTabs.map((tab, index) => {\n          const nodeCount = Object.keys(tab.nodes).length;\n          const edgeCount = tab.edges.list.length;\n          const isActive = index === currentTabIndex;\n          const tabMode = tab.pageState?.mappingMode || (isActive ? PAGE_STATE.mappingMode : 'network') || 'network';\n          const modeLabel = LANG.modes[tabMode]?.name || tabMode;\n          return `\n            <div class=\"tab-item ${isActive ? 'active' : ''}\" onclick=\"switchTab(${index})\">\n              <div class=\"tab-name\">${escapeHtml(tab.name)}</div>\n              <div class=\"tab-stats\">${t(\"ui.stats.nodesConnections\", { nodeCount, edgeCount })} · ${modeLabel}</div>\n              <div class=\"tab-actions\">\n                <button class=\"btn-cancel\" onclick=\"event.stopPropagation(); renameTab(${index})\" data-lang=\"tooltips.renameTab\" data-lang-attr=\"title\">✏️</button>\n                ${documentTabs.length > 1 ? '<button class=\"btn-cancel\" onclick=\"event.stopPropagation(); deleteTab(' + index + ')\" data-lang=\"tooltips.deleteTab\" data-lang-attr=\"title\">🗑️</button>' : ''}\n              </div>\n            </div>\n          `;\n        }).join('');\n      }\n      function logAuditEvent(type, description, details = {}) {\n        const event = {\n          timestamp: Date.now(),\n          type,\n          description,\n          details,\n          tab: documentTabs[currentTabIndex]?.name || \"Main\"\n        };\n        auditLog.unshift(event);\n        if (auditLog.length > MAX_AUDIT_ENTRIES) {\n          auditLog = auditLog.slice(0, MAX_AUDIT_ENTRIES);\n        }\n        try {\n          localStorage.setItem(AUDIT_STORAGE_KEY, JSON.stringify(auditLog));\n        } catch (e) {\n          console.warn(\"Failed to save audit log:\", e);\n        }\n      }\n      function loadAuditLog() {\n        let embeddedLog = [];\n        let localLog = [];\n        try {\n          const stateScript = document.getElementById(\"topology-state\");\n          if (stateScript) {\n            const stateData = JSON.parse(stateScript.textContent);\n            if (stateData.auditLog && Array.isArray(stateData.auditLog)) {\n              embeddedLog = stateData.auditLog;\n            }\n          }\n        } catch (e) {\n          console.warn(\"Failed to load embedded audit log:\", e);\n        }\n        try {\n          const stored = localStorage.getItem(AUDIT_STORAGE_KEY);\n          if (stored) {\n            localLog = JSON.parse(stored);\n          }\n        } catch (e) {\n          console.warn(\"Failed to load localStorage audit log:\", e);\n        }\n        const merged = [...embeddedLog, ...localLog];\n        const seen = new Set();\n        auditLog = merged.filter(entry => {\n          const key = entry.timestamp + entry.description;\n          if (seen.has(key)) return false;\n          seen.add(key);\n          return true;\n        }).sort((a, b) => b.timestamp - a.timestamp);\n        if (auditLog.length > MAX_AUDIT_ENTRIES) {\n          auditLog = auditLog.slice(0, MAX_AUDIT_ENTRIES);\n        }\n        try {\n          localStorage.setItem(AUDIT_STORAGE_KEY, JSON.stringify(auditLog));\n        } catch (e) {\n          console.warn(\"Failed to sync audit log:\", e);\n        }\n      }\n      function displayAuditLog(filter = \"all\") {\n        const listEl = document.getElementById(\"audit-log-list\");\n        if (!listEl) return;\n        const filtered = filter === \"all\" ? auditLog : auditLog.filter(e => e.type === filter);\n        if (filtered.length === 0) {\n          listEl.innerHTML = '<div style=\"text-align: center; padding: 40px; color: var(--text-soft);\">' + t(\"emptyStates.noAuditEntries\") + '</div>';\n          return;\n        }\n        listEl.innerHTML = filtered.map(event => {\n          const date = new Date(event.timestamp);\n          const timeStr = date.toLocaleString();\n          return `\n            <div class=\"audit-entry ${escapeHtml(event.type)}\">\n              <div class=\"time\">[${escapeHtml(timeStr)}] ${escapeHtml(event.tab)}</div>\n              <div class=\"action\">[${escapeHtml(event.type.toUpperCase())}] ${escapeHtml(event.description)}</div>\n            </div>\n          `;\n        }).join('');\n      }\n      async function clearAuditLog() {\n        if (!await showConfirm(t(\"dialogs.clearAuditLog\"))) return;\n        auditLog = [];\n        localStorage.removeItem(AUDIT_STORAGE_KEY);\n        displayAuditLog();\n      }\n      function exportAuditLog() {\n        if (auditLog.length === 0) {\n          showAlert(t(\"dialogs.noAuditEntriesToExport\"));\n          return;\n        }\n        const csv = [\n          [\"Timestamp\", \"Tab\", \"Type\", \"Description\"],\n          ...auditLog.map(e => [\n            new Date(e.timestamp).toISOString(),\n            e.tab,\n            e.type,\n            e.description\n          ])\n        ].map(row => row.map(cell => `\"${cell}\"`).join(\",\")).join(\"\\n\");\n        const blob = new Blob([csv], { type: \"text/csv\" });\n        const url = URL.createObjectURL(blob);\n        const a = document.createElement(\"a\");\n        a.href = url;\n        a.download = `audit-log-${Date.now()}.csv`;\n        document.body.appendChild(a);\n        a.click();\n        document.body.removeChild(a);\n        URL.revokeObjectURL(url);\n      }\n      let currentSecretName = null;\n      function createNewSecret() {\n        const nameInput = document.getElementById(\"new-secret-name\");\n        const name = nameInput.value.trim();\n        if (!name) {\n          showAlert(t(\"dialogs.enterNoteName\"));\n          return;\n        }\n        if (encryptedSections[name]) {\n          showAlert(t(\"dialogs.noteAlreadyExists\"));\n          return;\n        }\n        currentSecretName = name;\n        encryptedSections[name] = { encrypted: false, data: \"\" };\n        nameInput.value = \"\";\n        document.getElementById(\"secret-editor-title\").textContent = `New note: ${name}`;\n        document.getElementById(\"secret-editor-content\").value = \"\";\n        document.getElementById(\"secrets-modal\").classList.remove(\"active\");\n        document.getElementById(\"secret-editor-modal\").classList.add(\"active\");\n        displaySecrets();\n      }\n      async function editSecret(name) {\n        currentSecretName = name;\n        const section = encryptedSections[name];\n        if (section.encrypted) {\n          const password = await showPrompt(t(\"dialogs.enterDecryptPasswordFor\", { name: name }));\n          if (!password) return;\n          try {\n            const decrypted = await decryptData(section.data, password);\n            document.getElementById(\"secret-editor-title\").textContent = `Edit Secret: ${name}`;\n            document.getElementById(\"secret-editor-content\").innerHTML = decrypted;\n            document.getElementById(\"secrets-modal\").classList.remove(\"active\");\n            document.getElementById(\"secret-editor-modal\").classList.add(\"active\");\n          } catch (e) {\n            showAlert(t(\"dialogs.decryptionFailed\"));\n          }\n        } else {\n          document.getElementById(\"secret-editor-title\").textContent = `Edit note: ${name}`;\n          document.getElementById(\"secret-editor-content\").innerHTML = section.data;\n          document.getElementById(\"secrets-modal\").classList.remove(\"active\");\n          document.getElementById(\"secret-editor-modal\").classList.add(\"active\");\n        }\n      }\n      async function saveSecret() {\n        if (!currentSecretName) return;\n        const content = document.getElementById(\"secret-editor-content\").innerHTML;\n        const autoEncrypt = document.getElementById(\"secret-auto-encrypt\").checked;\n        if (autoEncrypt) {\n          const password = await showPrompt(t(\"dialogs.enterEncryptPasswordFor\", { name: currentSecretName }));\n          if (!password) return;\n          const confirmPassword = await showPrompt(t(\"dialogs.confirmPasswordFor\"));\n          if (password !== confirmPassword) {\n            showAlert(t(\"dialogs.passwordsMismatch\"));\n            return;\n          }\n          try {\n            const encrypted = await encryptData(content, password);\n            encryptedSections[currentSecretName] = { encrypted: true, data: encrypted };\n          } catch (e) {\n            showAlert(t(\"dialogs.encryptionFailedError\", { error: e.message }));\n            return;\n          }\n        } else {\n          encryptedSections[currentSecretName] = { encrypted: false, data: content };\n        }\n        closeSecretEditor();\n        displaySecrets();\n        logAuditEvent(\"secret\", `Saved note section: ${currentSecretName}`);\n      }\n      function closeSecretEditor() {\n        document.getElementById(\"secret-editor-modal\").classList.remove(\"active\");\n        document.getElementById(\"secrets-modal\").classList.add(\"active\");\n        currentSecretName = null;\n      }\n      async function deleteSecret(name) {\n        if (!await showConfirm(t(\"dialogs.deleteNote\", { name: name }))) return;\n        delete encryptedSections[name];\n        displaySecrets();\n        logAuditEvent(\"secret\", `Deleted note: ${name}`);\n      }\n      function getAllNotesAggregated() {\n        const allNotes = [];\n\n        Object.keys(encryptedSections).forEach(name => {\n          const section = encryptedSections[name];\n          allNotes.push({\n            type: \"global\",\n            name: name,\n            content: section.encrypted ? \"[Encrypted]\" : section.content,\n            encrypted: section.encrypted,\n            elementId: null,\n            elementName: null\n          });\n        });\n\n        Object.entries(NODE_DATA).forEach(([nodeId, node]) => {\n          (node.notes || []).forEach((note, idx) => {\n            const noteContent = typeof note === \"string\" ? note : (note.content || \"\");\n            allNotes.push({\n              type: \"node\",\n              name: `Note ${idx + 1}`,\n              content: noteContent,\n              encrypted: false,\n              elementId: nodeId,\n              elementName: node.name,\n              noteIndex: idx\n            });\n          });\n        });\n\n        (EDGE_DATA.list || []).forEach(edge => {\n          (edge.notes || []).forEach((note, idx) => {\n            const noteContent = typeof note === \"string\" ? note : (note.content || \"\");\n            const fromName = NODE_DATA[edge.from]?.name || edge.from;\n            const toName = NODE_DATA[edge.to]?.name || edge.to;\n            allNotes.push({\n              type: \"edge\",\n              name: `Note ${idx + 1}`,\n              content: noteContent,\n              encrypted: false,\n              elementId: edge.id,\n              elementName: `${fromName} ↔ ${toName}`,\n              noteIndex: idx\n            });\n          });\n        });\n\n        (RECT_DATA.list || []).forEach(rect => {\n          (rect.notes || []).forEach((note, idx) => {\n            const noteContent = typeof note === \"string\" ? note : (note.content || \"\");\n            allNotes.push({\n              type: \"rect\",\n              name: `Note ${idx + 1}`,\n              content: noteContent,\n              encrypted: false,\n              elementId: rect.id,\n              elementName: `Zone`,\n              noteIndex: idx\n            });\n          });\n        });\n\n        (IMAGE_DATA.list || []).forEach(img => {\n          (img.notes || []).forEach((note, idx) => {\n            const noteContent = typeof note === \"string\" ? note : (note.content || \"\");\n            allNotes.push({\n              type: \"image\",\n              name: `Note ${idx + 1}`,\n              content: noteContent,\n              encrypted: false,\n              elementId: img.id,\n              elementName: img.name || \"Image\",\n              noteIndex: idx\n            });\n          });\n        });\n        return allNotes;\n      }\n      function displaySecrets() {\n        const listEl = document.getElementById(\"secrets-list\");\n        if (!listEl) return;\n        const searchInput = document.getElementById(\"notes-hub-search\");\n        const filterSelect = document.getElementById(\"notes-hub-filter\");\n        const searchTerm = (searchInput?.value || \"\").toLowerCase();\n        const filterType = filterSelect?.value || \"all\";\n        let allNotes = getAllNotesAggregated();\n\n        if (filterType !== \"all\") {\n          allNotes = allNotes.filter(n => n.type === filterType);\n        }\n\n        if (searchTerm) {\n          allNotes = allNotes.filter(n =>\n            n.name.toLowerCase().includes(searchTerm) ||\n            n.content.toLowerCase().includes(searchTerm) ||\n            (n.elementName && n.elementName.toLowerCase().includes(searchTerm))\n          );\n        }\n        if (allNotes.length === 0) {\n          listEl.innerHTML = '<div style=\"text-align: center; padding: 40px; color: var(--text-soft);\">' + t(\"emptyStates.noNotesYet\") + '</div>';\n          return;\n        }\n        listEl.innerHTML = allNotes.map((note, i) => {\n          const typeLabels = { global: \"Global\", node: \"Node\", edge: \"Line\", rect: \"Zone\", image: \"Image\" };\n          const typeLabel = typeLabels[note.type] || note.type;\n          if (note.type === \"global\") {\n            const status = note.encrypted ? \"🔒\" : \"🔓\";\n            return `\n              <div class=\"secret-item note-type-${note.type}\">\n                <div class=\"secret-name\">${escapeHtml(note.name)}</div>\n                <div class=\"secret-status\">${status} ${typeLabel}</div>\n                <div class=\"tab-actions\">\n                  <button class=\"btn-cancel\" onclick=\"viewGlobalNote('${escapeHtml(note.name)}')\" title=\"View\">👁️</button>\n                  <button class=\"btn-cancel\" onclick=\"editSecret('${escapeHtml(note.name)}')\" title=\"Edit\">✏️</button>\n                  <button class=\"btn-cancel\" onclick=\"deleteSecret('${escapeHtml(note.name)}')\" title=\"Delete\">🗑️</button>\n                </div>\n              </div>\n            `;\n          } else {\n            const preview = note.content.replace(/<[^>]*>/g, '').substring(0, 60) + (note.content.length > 60 ? '...' : '');\n            return `\n              <div class=\"secret-item note-type-${note.type}\">\n                <div class=\"note-source\" onclick=\"navigateToNoteSource('${note.type}', '${escapeHtml(note.elementId)}')\">${typeLabel}: ${escapeHtml(note.elementName)}</div>\n                <div class=\"note-preview\">${escapeHtml(preview)}</div>\n                <div class=\"tab-actions\">\n                  <button class=\"btn-cancel\" onclick=\"viewElementNote('${note.type}', '${escapeHtml(note.elementId)}', ${note.noteIndex})\" title=\"View\">👁️</button>\n                  <button class=\"btn-cancel\" onclick=\"editElementNote('${note.type}', '${escapeHtml(note.elementId)}', ${note.noteIndex})\" title=\"Edit\">✏️</button>\n                </div>\n              </div>\n            `;\n          }\n        }).join('');\n      }\n      function navigateToNoteSource(type, elementId) {\n        document.getElementById(\"secrets-modal\").classList.remove(\"active\");\n        if (type === \"node\") {\n          claimTheImmortal(elementId);\n          focusOnSelected();\n        } else if (type === \"edge\") {\n          selectTheConnection(elementId);\n        } else if (type === \"rect\") {\n          selectTheRect(elementId);\n        } else if (type === \"image\") {\n          showImagePanel(elementId);\n        }\n      }\n      function viewElementNote(type, elementId, noteIndex) {\n        let notes = [];\n        if (type === \"node\" && NODE_DATA[elementId]) {\n          notes = NODE_DATA[elementId].notes || [];\n        } else if (type === \"edge\") {\n          const edge = EDGE_DATA.list.find(e => e.id === elementId);\n          if (edge) notes = edge.notes || [];\n        } else if (type === \"rect\") {\n          const rect = RECT_DATA.list.find(r => r.id === elementId);\n          if (rect) notes = rect.notes || [];\n        } else if (type === \"image\") {\n          const img = IMAGE_DATA.list.find(i => i.id === elementId);\n          if (img) notes = img.notes || [];\n        }\n        if (notes[noteIndex] !== undefined) {\n          const noteContent = typeof notes[noteIndex] === \"string\" ? notes[noteIndex] : (notes[noteIndex].content || \"\");\n          showNoteViewer(noteContent);\n        }\n      }\n      function viewGlobalNote(name) {\n        const section = encryptedSections[name];\n        if (!section) return;\n        if (section.encrypted) {\n          const pwd = prompt(t(\"notes.enterPassword\"));\n          if (!pwd) return;\n          try {\n            const decrypted = CryptoJS.AES.decrypt(section.data, pwd).toString(CryptoJS.enc.Utf8);\n            if (!decrypted) { alert(t(\"notes.wrongPassword\")); return; }\n            showNoteViewer(decrypted);\n          } catch { alert(t(\"notes.wrongPassword\")); }\n        } else {\n          showNoteViewer(section.data);\n        }\n      }\n      function editElementNote(type, elementId, noteIndex) {\n        let notes = [];\n        let updateFn = null;\n        if (type === \"node\" && NODE_DATA[elementId]) {\n          notes = NODE_DATA[elementId].notes || [];\n          updateFn = () => { if (currentNodeId === elementId) renderNotesFeed(notes, \"node-notes\", \"node\", elementId); };\n        } else if (type === \"edge\") {\n          const edge = EDGE_DATA.list.find(e => e.id === elementId);\n          if (edge) {\n            notes = edge.notes || [];\n            updateFn = () => { if (currentEdgeId === elementId) renderNotesFeed(notes, \"edge-notes\", \"edge\", elementId); };\n          }\n        } else if (type === \"rect\") {\n          const rect = RECT_DATA.list.find(r => r.id === elementId);\n          if (rect) {\n            notes = rect.notes || [];\n            updateFn = () => { if (currentRectId === elementId) renderNotesFeed(notes, \"rect-notes\", \"rect\", elementId); };\n          }\n        } else if (type === \"image\") {\n          const img = IMAGE_DATA.list.find(i => i.id === elementId);\n          if (img) {\n            notes = img.notes || [];\n            updateFn = () => { if (currentImageId === elementId) renderNotesFeed(notes, \"image-notes\", \"image\", elementId); };\n          }\n        }\n        if (notes[noteIndex] !== undefined) {\n          const noteContent = typeof notes[noteIndex] === \"string\" ? notes[noteIndex] : (notes[noteIndex].content || \"\");\n          showNoteEditor(t(\"noteEditor.editNote\"), noteContent, (newContent) => {\n            pushUndo(\"edit note\");\n            if (typeof notes[noteIndex] === \"string\") {\n              notes[noteIndex] = newContent;\n            } else {\n              notes[noteIndex].content = newContent;\n            }\n            displaySecrets();\n            if (updateFn) updateFn();\n          }, noteIndex);\n        }\n      }\n       const clearAllBtn = document.getElementById(\"clear-all-btn\");\n       const clearAllModal = document.getElementById(\"clear-all-modal\");\n       const clearAllCancel = document.getElementById(\"clear-all-cancel\");\n       const clearAllConfirm = document.getElementById(\"clear-all-confirm\");\n       clearAllBtn.addEventListener(\"click\", () => {\n        clearAllModal.classList.add(\"active\");\n       });\n       clearAllCancel.addEventListener(\"click\", () => {\n        clearAllModal.classList.remove(\"active\");\n       });\n       clearAllModal.addEventListener(\"click\", (e) => {\n        if (e.target === clearAllModal) {\n         clearAllModal.classList.remove(\"active\");\n        }\n       });\n       clearAllConfirm.addEventListener(\"click\", () => {\n        NODE_DATA = {};\n        EDGE_DATA = { list: [] };\n        EDGE_LEGEND = {};\n        RECT_DATA = { list: [] };\n        TEXT_DATA = { list: [] };\n        IMAGE_DATA = { list: [] };\n        savedPositions = {};\n        savedSizes = {};\n        savedStyles = {};\n        undoStack = [];\n        redoStack = [];\n        updateUndoButtons();\n        auditLog = [];\n        localStorage.removeItem(AUDIT_STORAGE_KEY);\n        rollbackVersions = [];\n        currentRollbackIndex = -1;\n        localStorage.removeItem(ROLLBACK_STORAGE_KEY);\n        clipboard = null;\n        selectedNodes.clear();\n        selectedEdges.clear();\n        selectedRects.clear();\n        selectedTexts.clear();\n        documentTabs = [{\n          id: \"main\",\n          name: \"Main Topology\",\n          nodes: {},\n          edges: { list: [] },\n          positions: {},\n          sizes: {},\n          styles: {},\n          legend: {},\n          rects: { list: [] },\n          texts: { list: [] },\n          images: { list: [] },\n          pageState: null\n        }];\n        currentTabIndex = 0;\n        displayTabs();\n        clearAutosave();\n        try {\n          Object.keys(localStorage).forEach(key => {\n            if (key.startsWith(\"theonefile\") || key.startsWith(\"theOneFile\")) {\n              localStorage.removeItem(key);\n            }\n          });\n        } catch(e) {}\n        CUSTOM_LANG = null;\n        LANG = JSON.parse(JSON.stringify(DEFAULT_LANG));\n        applyLanguage();\n        const addLineSelect = document.getElementById(\"add-line-select\");\n        if (addLineSelect) addLineSelect.innerHTML = \"\";\n        const edgeLegendContent = document.getElementById(\"edge-legend-content\");\n        if (edgeLegendContent) edgeLegendContent.innerHTML = \"\";\n        clearAllModal.classList.remove(\"active\");\n        forgeTheTopology();\n        document.getElementById(\"node-panel\").style.display = \"none\";\n        document.getElementById(\"edge-panel\").style.display = \"none\";\n        document.getElementById(\"text-panel\").style.display = \"none\";\n        document.getElementById(\"rect-panel\").style.display = \"none\";\n        document.getElementById(\"image-panel\").style.display = \"none\";\n        document.getElementById(\"topology-toolbar\").style.display = \"none\";\n        currentNodeId = null;\n        currentEdgeId = null;\n        currentTextId = null;\n        currentRectId = null;\n        currentImageId = null;\n        if (typeof displayRollbackVersions === \"function\") displayRollbackVersions();\n        if (typeof displayAuditLog === \"function\") displayAuditLog();\n        if (typeof forgeTheLegend === \"function\") forgeTheLegend();\n        if (typeof updateZoneLegend === \"function\") updateZoneLegend();\n      });\n       (function addDeleteNodeButton() {\n       const nodePanel = document.getElementById(\"node-panel\");\n       if (!nodePanel) return;\n       let deleteBtn = document.getElementById(\"delete-node-btn\");\n       if (!deleteBtn) {\n        deleteBtn = document.createElement(\"button\");\n        deleteBtn.id = \"delete-node-btn\";\n        deleteBtn.textContent = t(\"ui.buttons.deleteNode\");\n        deleteBtn.style.cssText = \"margin-top:15px; padding:10px 16px; background:var(--danger); color:#fff; border:none; border-radius:6px; cursor:pointer; font-size:clamp(14px,1.6vw,18px); font-weight:600; width:100%;\";\n        nodePanel.appendChild(deleteBtn);\n       }\n       deleteBtn.addEventListener(\"click\", () => {\n        if (!currentNodeId) return;\n        const nodeData = NODE_DATA[currentNodeId];\n        let confirmMessage;\n        let nodesInsideRack = [];\n        if (nodeData?.isRack) {\n         nodesInsideRack = Object.entries(NODE_DATA)\n          .filter(([id, n]) => n.assignedRack === currentNodeId)\n          .map(([id, n]) => n.name || id);\n         if (nodesInsideRack.length > 0) {\n          confirmMessage = `Delete rack \"${nodeData.name}\"?\\n\\nThis will also delete ${nodesInsideRack.length} node(s) inside:\\n• ${nodesInsideRack.join('\\n• ')}`;\n         } else {\n          confirmMessage = `Delete rack \"${nodeData.name}\"? (empty rack)`;\n         }\n        } else {\n         confirmMessage = `Delete node \"${nodeData?.name || currentNodeId}\" and all its connections?`;\n        }\n        challengeTheImmortal(confirmMessage, () => {\n         pushUndo(\"delete node\");\n         if (nodeData?.isRack) {\n          Object.keys(NODE_DATA).forEach(id => {\n           if (NODE_DATA[id]?.assignedRack === currentNodeId) {\n            EDGE_DATA.list = EDGE_DATA.list.filter(e => e.from !== id && e.to !== id);\n            delete NODE_DATA[id];\n            delete savedPositions[id];\n            delete savedSizes[id];\n            delete savedStyles[id];\n           }\n          });\n         }\n         EDGE_DATA.list = EDGE_DATA.list.filter(e => e.from !== currentNodeId && e.to !== currentNodeId);\n         delete NODE_DATA[currentNodeId];\n         delete savedPositions[currentNodeId];\n         delete savedSizes[currentNodeId];\n         delete savedStyles[currentNodeId];\n         currentNodeId = null;\n         currentEdgeId = null;\n         forgeTheTopology();\n         const remainingNodes = Object.keys(NODE_DATA);\n         if (remainingNodes.length > 0) {\n          claimTheImmortal(remainingNodes[0]);\n         } else {\n          document.getElementById(\"node-panel\").style.display = \"none\";\n          document.getElementById(\"edge-panel\").style.display = \"none\";\n          document.getElementById(\"topology-toolbar\").style.display = \"none\";\n         }\n        });\n       });\n      })();\n       function screenshotCanvas() {\n        const svg = document.getElementById(\"map\");\n        const svgClone = svg.cloneNode(true);\n        const viewBox = svg.getAttribute(\"viewBox\").split(\" \").map(Number);\n        const [x, y, width, height] = viewBox;\n        svgClone.setAttribute(\"width\", width);\n        svgClone.setAttribute(\"height\", height);\n        svgClone.setAttribute(\"viewBox\", `${x} ${y} ${width} ${height}`);\n        const wrapper = document.createElement(\"div\");\n        wrapper.style.position = \"absolute\";\n        wrapper.style.left = \"-9999px\";\n        wrapper.appendChild(svgClone);\n        document.body.appendChild(wrapper);\n        function inlineStyles(original, clone) {\n         const elements = original.querySelectorAll(\"*\");\n         const clonedElements = clone.querySelectorAll(\"*\");\n         const rootStyles = getComputedStyle(document.documentElement);\n         const bgColor = rootStyles.getPropertyValue(\"--bg\").trim() || \"#050608\";\n         const bgRect = document.createElementNS(\"http://www.w3.org/2000/svg\", \"rect\");\n         bgRect.setAttribute(\"x\", x);\n         bgRect.setAttribute(\"y\", y);\n         bgRect.setAttribute(\"width\", width);\n         bgRect.setAttribute(\"height\", height);\n         bgRect.setAttribute(\"fill\", bgColor);\n         clone.insertBefore(bgRect, clone.firstChild);\n         elements.forEach((el, index) => {\n          const clonedEl = clonedElements[index];\n          if (!clonedEl) return;\n          const computedStyle = getComputedStyle(el);\n          const svgProps = [\"fill\", \"stroke\", \"stroke-width\", \"stroke-dasharray\", \"stroke-linecap\", \"stroke-linejoin\", \"opacity\", \"font-family\", \"font-size\", \"font-weight\", \"font-style\", \"text-anchor\", \"dominant-baseline\", \"marker-start\", \"marker-end\", ];\n          svgProps.forEach((prop) => {\n           const value = computedStyle.getPropertyValue(prop);\n           if (value && value !== \"none\" && value !== \"normal\") {\n            clonedEl.style[prop] = value;\n           }\n          });\n          clonedEl.removeAttribute(\"class\");\n         });\n        }\n        inlineStyles(svg, svgClone);\n        const svgData = new XMLSerializer().serializeToString(svgClone);\n        document.body.removeChild(wrapper);\n        const svgBlob = new Blob([svgData], {\n         type: \"image/svg+xml;charset=utf-8\",\n        });\n        const url = URL.createObjectURL(svgBlob);\n        const img = new Image();\n        img.onload = function() {\n         const canvas = document.createElement(\"canvas\");\n         canvas.width = width;\n         canvas.height = height;\n         const ctx = canvas.getContext(\"2d\");\n         ctx.drawImage(img, 0, 0);\n         canvas.toBlob(function(blob) {\n          const link = document.createElement(\"a\");\n          const timestamp = new Date().toISOString().slice(0, 10);\n          link.download = `topology-${timestamp}.png`;\n          link.href = URL.createObjectURL(blob);\n          link.click();\n          URL.revokeObjectURL(url);\n          URL.revokeObjectURL(link.href);\n         }, \"image/png\");\n        };\n        img.onerror = function() {\n         console.error(\"Failed to load SVG image\");\n         showAlert(t(\"dialogs.screenshotFailed\"));\n         URL.revokeObjectURL(url);\n        };\n        img.src = url;\n       }\n       function exportCanvasSVG() {\n        const svg = document.getElementById(\"map\");\n        const svgClone = svg.cloneNode(true);\n        const viewBox = svg.getAttribute(\"viewBox\").split(\" \").map(Number);\n        const [x, y, width, height] = viewBox;\n        svgClone.setAttribute(\"width\", width);\n        svgClone.setAttribute(\"height\", height);\n        const rootStyles = getComputedStyle(document.documentElement);\n        const bgColor = rootStyles.getPropertyValue(\"--bg\").trim() || \"#050608\";\n        const bgRect = document.createElementNS(\"http://www.w3.org/2000/svg\", \"rect\");\n        bgRect.setAttribute(\"x\", x);\n        bgRect.setAttribute(\"y\", y);\n        bgRect.setAttribute(\"width\", width);\n        bgRect.setAttribute(\"height\", height);\n        bgRect.setAttribute(\"fill\", bgColor);\n        svgClone.insertBefore(bgRect, svgClone.firstChild);\n        const wrapper = document.createElement(\"div\");\n        wrapper.style.position = \"absolute\";\n        wrapper.style.left = \"-9999px\";\n        wrapper.appendChild(svgClone);\n        document.body.appendChild(wrapper);\n        const elements = svg.querySelectorAll(\"*\");\n        const clonedElements = svgClone.querySelectorAll(\"*\");\n        elements.forEach((el, index) => {\n         const clonedEl = clonedElements[index];\n         if (!clonedEl) return;\n         const computedStyle = getComputedStyle(el);\n         const svgProps = [\"fill\", \"stroke\", \"stroke-width\", \"stroke-dasharray\", \"stroke-linecap\", \"stroke-linejoin\", \"opacity\", \"font-family\", \"font-size\", \"font-weight\", \"font-style\", \"text-anchor\", \"dominant-baseline\", \"marker-start\", \"marker-end\", ];\n         svgProps.forEach((prop) => {\n          const value = computedStyle.getPropertyValue(prop);\n          if (value && value !== \"none\" && value !== \"normal\") {\n           clonedEl.setAttribute(prop, value);\n          }\n         });\n         clonedEl.removeAttribute(\"class\");\n        });\n        const svgData = new XMLSerializer().serializeToString(svgClone);\n        document.body.removeChild(wrapper);\n        const blob = new Blob([svgData], {\n         type: \"image/svg+xml;charset=utf-8\",\n        });\n        const url = URL.createObjectURL(blob);\n        const link = document.createElement(\"a\");\n        const timestamp = new Date().toISOString().slice(0, 10);\n        link.download = `topology-${timestamp}.svg`;\n        link.href = url;\n        link.click();\n        URL.revokeObjectURL(url);\n       }\n      let resizeTimeout;\n       window.addEventListener('resize', () => {\n       clearTimeout(resizeTimeout);\n       resizeTimeout = setTimeout(() => {\n       updateDrawToolbarVisibility();\n       updateTopologyToolbarVisibility();\n       }, 100);\n      });\ndocument.addEventListener('DOMContentLoaded', () => {\n  loadLanguageFromStorage();\n  applyLanguage();\n  updateCurrentLangDisplay();\n\n  document.querySelectorAll('.dropdown').forEach(dropdown => {\n    const btn = dropdown.querySelector('.dropdown-btn');\n    const menu = dropdown.querySelector('.dropdown-menu');\n    if (!btn || !menu) return;\n    btn.addEventListener('click', (e) => {\n      e.stopPropagation();\n      document.querySelectorAll('.dropdown-menu.open').forEach(m => {\n        if (m !== menu) m.classList.remove('open');\n      });\n      menu.classList.toggle('open');\n    });\n  });\n  document.addEventListener('click', (e) => {\n    if (!e.target.closest('.dropdown')) {\n      document.querySelectorAll('.dropdown-menu.open').forEach(m => m.classList.remove('open'));\n    }\n  });\n  document.querySelectorAll('.dropdown-menu button').forEach(btn => {\n    btn.addEventListener('click', () => {\n      setTimeout(() => {\n        document.querySelectorAll('.dropdown-menu.open').forEach(m => m.classList.remove('open'));\n      }, 100);\n    });\n  });\n  });\nfunction showFormatHelp() {\n  document.getElementById('save-info-modal').classList.add('active');\n  document.querySelectorAll('.help-tab').forEach(t => {\n    t.style.background = 'var(--panel)';\n    t.style.color = 'var(--text-main)';\n  });\n  document.querySelectorAll('.help-tab-content').forEach(c => c.style.display = 'none');\n  const formatsTab = document.querySelector('.help-tab[data-tab=\"formats\"]');\n  if (formatsTab) {\n    formatsTab.style.background = 'var(--accent)';\n    formatsTab.style.color = 'var(--bg)';\n  }\n  document.getElementById('help-tab-formats').style.display = 'block';\n}\nfunction printTopology() {\n  const svg = document.getElementById('map');\n  if (!svg) { window.print(); return; }\n  const originalViewBox = svg.getAttribute('viewBox');\n  let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;\n  let hasContent = false;\n  Object.entries(savedPositions).forEach(([id, pos]) => {\n    const node = NODE_DATA[id];\n    if (!node) return;\n    if (currentView.mode !== 'rack' && node.assignedRack) return;\n    const size = savedSizes[id] || 50;\n    hasContent = true;\n    minX = Math.min(minX, pos.x - size);\n    minY = Math.min(minY, pos.y - size);\n    maxX = Math.max(maxX, pos.x + size);\n    maxY = Math.max(maxY, pos.y + size);\n  });\n  RECT_DATA.list.forEach(rect => {\n    hasContent = true;\n    minX = Math.min(minX, rect.x);\n    minY = Math.min(minY, rect.y);\n    maxX = Math.max(maxX, rect.x + rect.width);\n    maxY = Math.max(maxY, rect.y + rect.height);\n  });\n  TEXT_DATA.list.forEach(text => {\n    hasContent = true;\n    minX = Math.min(minX, text.x - 100);\n    minY = Math.min(minY, text.y - 50);\n    maxX = Math.max(maxX, text.x + 300);\n    maxY = Math.max(maxY, text.y + 50);\n  });\n  EDGE_DATA.list.forEach(edge => {\n    if (edge.points && edge.points.length > 0) {\n      edge.points.forEach(p => {\n        hasContent = true;\n        minX = Math.min(minX, p.x - 10);\n        minY = Math.min(minY, p.y - 10);\n        maxX = Math.max(maxX, p.x + 10);\n        maxY = Math.max(maxY, p.y + 10);\n      });\n    }\n  });\n  if (!hasContent) { window.print(); return; }\n  const padding = 100;\n  minX -= padding; minY -= padding; maxX += padding; maxY += padding;\n  const width = maxX - minX;\n  const height = maxY - minY;\n  const grid = document.getElementById('canvas-grid');\n  const gridDisplay = grid ? grid.style.display : '';\n  if (grid) grid.style.display = 'none';\n  svg.setAttribute('viewBox', `${minX} ${minY} ${width} ${height}`);\n  const originalWidth = svg.style.width;\n  const originalHeight = svg.style.height;\n  svg.style.width = '100%';\n  svg.style.height = '100%';\n  setTimeout(() => {\n    window.print();\n    setTimeout(() => {\n      svg.setAttribute('viewBox', originalViewBox);\n      svg.style.width = originalWidth;\n      svg.style.height = originalHeight;\n      if (grid) grid.style.display = gridDisplay;\n    }, 500);\n  }, 100);\n}\nfunction printTopologyColor(bgOverride) {\n  const svg = document.getElementById(\"map\");\n  if (!svg) { window.print(); return; }\n  const svgClone = svg.cloneNode(true);\n  const viewBox = svg.getAttribute(\"viewBox\").split(\" \").map(Number);\n  const [x, y, width, height] = viewBox;\n  svgClone.setAttribute(\"width\", width);\n  svgClone.setAttribute(\"height\", height);\n  svgClone.setAttribute(\"viewBox\", `${x} ${y} ${width} ${height}`);\n  const wrapper = document.createElement(\"div\");\n  wrapper.style.position = \"absolute\";\n  wrapper.style.left = \"-9999px\";\n  wrapper.appendChild(svgClone);\n  document.body.appendChild(wrapper);\n  function inlineStyles(original, clone) {\n    const elements = original.querySelectorAll(\"*\");\n    const clonedElements = clone.querySelectorAll(\"*\");\n    const rootStyles = getComputedStyle(document.documentElement);\n    const bgColor = bgOverride || rootStyles.getPropertyValue(\"--bg\").trim() || \"#050608\";\n    const bgRect = document.createElementNS(\"http://www.w3.org/2000/svg\", \"rect\");\n    bgRect.setAttribute(\"x\", x);\n    bgRect.setAttribute(\"y\", y);\n    bgRect.setAttribute(\"width\", width);\n    bgRect.setAttribute(\"height\", height);\n    bgRect.setAttribute(\"fill\", bgColor);\n    clone.insertBefore(bgRect, clone.firstChild);\n    elements.forEach((el, index) => {\n      const clonedEl = clonedElements[index];\n      if (!clonedEl) return;\n      const computedStyle = getComputedStyle(el);\n      const svgProps = [\"fill\", \"stroke\", \"stroke-width\", \"stroke-dasharray\", \"stroke-linecap\", \"stroke-linejoin\", \"opacity\", \"font-family\", \"font-size\", \"font-weight\", \"font-style\", \"text-anchor\", \"dominant-baseline\", \"marker-start\", \"marker-end\"];\n      svgProps.forEach((prop) => {\n        const value = computedStyle.getPropertyValue(prop);\n        if (value && value !== \"none\" && value !== \"normal\") {\n          clonedEl.style[prop] = value;\n        }\n      });\n      clonedEl.removeAttribute(\"class\");\n    });\n  }\n  inlineStyles(svg, svgClone);\n  const svgData = new XMLSerializer().serializeToString(svgClone);\n  document.body.removeChild(wrapper);\n  const svgBlob = new Blob([svgData], { type: \"image/svg+xml;charset=utf-8\" });\n  const url = URL.createObjectURL(svgBlob);\n  const img = new Image();\n  img.onload = function() {\n    const canvas = document.createElement(\"canvas\");\n    canvas.width = width;\n    canvas.height = height;\n    const ctx = canvas.getContext(\"2d\");\n    ctx.drawImage(img, 0, 0);\n    const dataUrl = canvas.toDataURL(\"image/png\");\n    URL.revokeObjectURL(url);\n    const printWindow = window.open(\"\", \"_blank\");\n    printWindow.document.write(`\n      <html><head><title>Print Topology</title>\n      <style>@media print { @page { size: landscape; margin: 0; } body { margin: 0; } img { width: 100vw; height: 100vh; object-fit: contain; } }</style>\n      </head><body><img src=\"${dataUrl}\" onload=\"window.print(); window.close();\"></body></html>\n    `);\n    printWindow.document.close();\n  };\n  img.onerror = function() {\n    console.error(\"Failed to load SVG image for print\");\n    showAlert(t(\"dialogs.screenshotFailed\"));\n    URL.revokeObjectURL(url);\n  };\n  img.src = url;\n}\nfunction exportJSONFile() {\n  const data = captureTheQuickening();\n  const jsonStr = JSON.stringify(data, null, 2);\n  const blob = new Blob([jsonStr], { type: \"application/json\" });\n  const url = URL.createObjectURL(blob);\n  const a = document.createElement(\"a\");\n  a.href = url;\n  const safeTitle = (PAGE_STATE.title || \"network-topology\").toLowerCase().replace(/[^a-z0-9\\-]+/g, \"-\");\n  a.download = `${safeTitle}.json`;\n  a.click();\n  URL.revokeObjectURL(url);\n  logAuditEvent(\"export\", `Exported JSON: ${a.download}`);\n}\nfunction exportCSV() {\n  const safeTitle = (PAGE_STATE.title || 'topology').toLowerCase().replace(/[^a-z0-9]+/g, '-');\n  const timestamp = new Date().toISOString();\n  const config = captureTheQuickening();\n  let csv = `#THEONEFILE_CONFIG:${JSON.stringify(config)}\\n`;\n  csv += `#\\n# ${PAGE_STATE.title || 'Network Topology'} - Node List\\n`;\n  csv += `# Exported from The One File on ${timestamp}\\n`;\n  const headers = ['name','ip','role','shape','tags','layer','mac','rackUnit','uHeight','assignedRack','rackCapacity','isRack','locked','groupId','x','y','size','notes','styles'];\n  csv += headers.join(',') + '\\n';\n  Object.entries(NODE_DATA).forEach(([id, node]) => {\n    const pos = savedPositions[id] || { x: 0, y: 0 };\n    const size = savedSizes[id] || 50;\n    const styles = savedStyles[id] ? JSON.stringify(savedStyles[id]) : '';\n    const row = [\n      csvEscape(node.name || ''), csvEscape(node.ip || ''), csvEscape(node.role || ''),\n      node.shape || 'circle', csvEscape((node.tags || []).join(';')), node.layer || 'layer1',\n      csvEscape(node.mac || ''), csvEscape(node.rackUnit || ''), node.uHeight || '1',\n      node.assignedRack || '', node.rackCapacity || '', node.isRack ? 'true' : 'false',\n      node.locked ? 'true' : 'false', node.groupId || '', Math.round(pos.x), Math.round(pos.y),\n      size, csvEscape((node.notes || []).join('|')), csvEscape(styles)\n    ];\n    csv += row.join(',') + '\\n';\n  });\n  const blob = new Blob([csv], { 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 = `${safeTitle}.csv`;\n  a.click();\n  URL.revokeObjectURL(url);\n  logAuditEvent('export', `Exported CSV: ${a.download}`);\n}\nfunction csvEscape(val) {\n  if (val === null || val === undefined) return '';\n  const str = String(val);\n  if (str.includes(',') || str.includes('\"') || str.includes('\\n') || str.includes('\\r')) {\n    return '\"' + str.replace(/\"/g, '\"\"') + '\"';\n  }\n  return str;\n}\ndocument.getElementById('import-csv-file')?.addEventListener('change', async (e) => {\n  const file = e.target.files[0];\n  if (!file) return;\n  e.target.value = '';\n  try {\n    const text = await file.text();\n    const lines = text.split(/\\r?\\n/);\n    let config = null;\n    let dataLines = [];\n    let headers = null;\n    for (const line of lines) {\n      const trimmed = line.trim();\n      if (!trimmed) continue;\n      if (trimmed.startsWith('#THEONEFILE_CONFIG:')) {\n        try { config = JSON.parse(trimmed.substring(19)); } catch (err) { console.warn('Failed to parse CSV config:', err); }\n        continue;\n      }\n      if (trimmed.startsWith('#')) continue;\n      if (!headers) { headers = parseCSVLine(trimmed).map(h => h.toLowerCase().trim()); continue; }\n      dataLines.push(trimmed);\n    }\n    if (!headers || dataLines.length === 0) { showAlert(t(\"dialogs.csvNoDataRows\")); return; }\n    const nameIdx = headers.indexOf('name');\n    if (nameIdx === -1) { showAlert(t(\"dialogs.csvNeedsNameColumn\")); return; }\n    const nodes = dataLines.map(line => {\n      const values = parseCSVLine(line);\n      const node = {};\n      headers.forEach((h, idx) => { node[h] = values[idx] || ''; });\n      return node;\n    });\n    const hasFullBackup = config && config.documentTabs;\n    const hasConfig = config && (config.pageState || config.page);\n    \n    let importMode = 'add';\n\n    if (hasFullBackup) {\n      const choice = await showConfirm(\n        t(\"dialogs.confirmImportCsvFull\", {\n          nodeCount: nodes.length,\n          tabCount: config.documentTabs?.length || 1,\n          edgeCount: config.edgeData?.list?.length || 0,\n          csvNodeCount: nodes.length\n        })\n      );\n      if (choice) {\n        importMode = 'full';\n      }\n    } else {\n      const settingsNote = hasConfig ? t(\"dialogs.csvSettingsRestore\") : t(\"dialogs.csvAddOnly\");\n      if (!await showConfirm(t(\"dialogs.confirmImportCsvAdd\", { count: nodes.length, settingsNote: settingsNote }))) return;\n    }\n    \n    pushUndo('import csv');\n    \n    if (importMode === 'full' && hasFullBackup) {\n      NODE_DATA = config.nodeData || {};\n      EDGE_DATA = config.edgeData || { list: [] };\n      EDGE_LEGEND = config.edgeLegend || {};\n      RECT_DATA = config.rectData || { list: [] };\n      TEXT_DATA = config.textData || { list: [] };\n      savedPositions = config.nodePositions || {};\n      savedSizes = config.nodeSizes || {};\n      savedStyles = config.nodeStyles || {};\n      if (config.page) {\n        PAGE_STATE = Object.assign({}, DEFAULT_PAGE_STATE, config.page);\n        wieldThePower();\n      }\n      if (config.canvas) {\n        canvasState.zoom = config.canvas.zoom || 1;\n        canvasState.panX = config.canvas.panX || 0;\n        canvasState.panY = config.canvas.panY || 0;\n      }\n      if (config.page?.title) {\n        document.title = config.page.title;\n        document.querySelector(\".editable-page-title\").textContent = config.page.title;\n      }\n      if (config.savedStyleSets) {\n        savedStyleSets = config.savedStyleSets;\n      }\n      if (config.documentTabs) {\n        documentTabs = config.documentTabs;\n        currentTabIndex = config.currentTabIndex || 0;\n      }\n      if (config.savedTopologyView) {\n        savedTopologyView = config.savedTopologyView;\n      }\n      if (config.encryptedSections) {\n        encryptedSections = config.encryptedSections;\n      }\n      if (config.auditLog && Array.isArray(config.auditLog)) {\n        auditLog = config.auditLog;\n        try {\n          localStorage.setItem(AUDIT_STORAGE_KEY, JSON.stringify(auditLog));\n        } catch (e) {\n          console.warn(\"Failed to sync audit log to localStorage:\", e);\n        }\n      }\n      if (config.recordings && Array.isArray(config.recordings)) {\n        RECORDING_STATE.recordings = config.recordings;\n        RECORDING_STATE.currentRecording = config.recordings[0] || null;\n      }\n      const layerMap = { \"physical\": \"layer1\", \"logical\": \"layer2\", \"security\": \"layer3\", \"application\": \"layer4\" };\n      Object.values(NODE_DATA).forEach(node => {\n        if (!node.tags) node.tags = [];\n        if (!node.notes) node.notes = [];\n        if (!node.layer || layerMap[node.layer]) node.layer = layerMap[node.layer] || 'layer1';\n      });\n      EDGE_DATA.list.forEach(edge => {\n        if (!edge.notes) edge.notes = [];\n      });\n      forgeTheTopology();\n      if (typeof forgeTheLegend === 'function') forgeTheLegend();\n      if (typeof updateZoneLegend === 'function') updateZoneLegend();\n      updateViewBox();\n      updateDrawToolbarVisibility();\n      updateTopologyToolbarVisibility();\n      logAuditEvent('import', `Imported CSV (full restore): ${file.name}`);\n      showAlert(t(\"dialogs.fullRestoreComplete\"));\n      return;\n    }\n    if (hasConfig) {\n      Object.assign(PAGE_STATE, config.pageState || config.page);\n      if (config.canvasView || config.canvas) {\n        const canvasConfig = config.canvasView || config.canvas;\n        canvasState.zoom = canvasConfig.zoom || 1;\n        canvasState.panX = canvasConfig.panX || 0;\n        canvasState.panY = canvasConfig.panY || 0;\n      }\n      if (config.legend || config.edgeLegend) Object.assign(EDGE_LEGEND, config.legend || config.edgeLegend);\n      wieldThePower();\n    }\n    let gridX = 200, gridY = 200;\n    const spacing = 150;\n    const perRow = Math.ceil(Math.sqrt(nodes.length));\n    let gridIndex = 0;\n    nodes.forEach((n) => {\n      let baseId = (n.name || 'node').toLowerCase().replace(/[^a-z0-9]+/g, '-');\n      if (!baseId) baseId = 'node';\n      let nodeId = baseId;\n      let counter = 1;\n      while (NODE_DATA[nodeId]) { nodeId = `${baseId}-${counter}`; counter++; }\n      NODE_DATA[nodeId] = {\n        name: n.name || 'Unnamed', ip: n.ip || '', role: n.role || '', shape: n.shape || 'circle',\n        tags: n.tags ? n.tags.split(';').map(t => t.trim()).filter(t => t) : [],\n        notes: n.notes ? n.notes.split('|').map(t => t.trim()).filter(t => t) : [],\n        layer: n.layer || 'layer1', mac: n.mac || '', rackUnit: n.rackunit || '',\n        uHeight: n.uheight || '1', assignedRack: n.assignedrack || '', rackCapacity: n.rackcapacity || '',\n        isRack: n.israck === 'true', locked: n.locked === 'true', groupId: n.groupid || null\n      };\n      const hasPosition = n.x && n.y && !isNaN(parseFloat(n.x)) && !isNaN(parseFloat(n.y));\n      if (hasPosition) {\n        savedPositions[nodeId] = { x: parseFloat(n.x), y: parseFloat(n.y) };\n      } else {\n        const row = Math.floor(gridIndex / perRow);\n        const col = gridIndex % perRow;\n        savedPositions[nodeId] = { x: gridX + col * spacing, y: gridY + row * spacing };\n        gridIndex++;\n      }\n      if (n.size && !isNaN(parseFloat(n.size))) savedSizes[nodeId] = parseFloat(n.size);\n      if (n.styles) { try { savedStyles[nodeId] = JSON.parse(n.styles); } catch (err) {} }\n    });\n    forgeTheTopology();\n    updateViewBox();\n    updateDrawToolbarVisibility();\n    updateTopologyToolbarVisibility();\n    logAuditEvent('import', `Imported CSV: ${file.name} (${nodes.length} nodes)`);\n    showAlert(t(\"dialogs.importedNodesCount\", { count: nodes.length }));\n  } catch (err) {\n    console.error('CSV import error:', err);\n    showAlert(t(\"dialogs.importCsvFailed\", { error: err.message }));\n  }\n});\nfunction parseCSVLine(line) {\n  const result = [];\n  let current = '';\n  let inQuotes = false;\n  for (let i = 0; i < line.length; i++) {\n    const char = line[i];\n    if (char === '\"') {\n      if (inQuotes && line[i + 1] === '\"') { current += '\"'; i++; }\n      else { inQuotes = !inQuotes; }\n    } else if (char === ',' && !inQuotes) { result.push(current); current = ''; }\n    else { current += char; }\n  }\n  result.push(current);\n  return result;\n}\n\nfunction exportMarkdown() {\n  const safeTitle = (PAGE_STATE.title || 'topology').toLowerCase().replace(/[^a-z0-9]+/g, '-');\n  const timestamp = new Date().toISOString();\n  const title = PAGE_STATE.title || 'Network Topology';\n  const fullData = captureTheQuickening();\n  const config = fullData;\n  let md = `<!--THEONEFILE_CONFIG\\n${JSON.stringify(config, null, 2)}\\nTHEONEFILE_CONFIG-->\\n\\n`;\n  md += `# ${title}\\n\\n> Exported from The One File on ${timestamp}\\n\\n`;\n  md += `## Legend\\n\\n`;\n  if (Object.keys(EDGE_LEGEND).length > 0) {\n    Object.entries(EDGE_LEGEND).forEach(([color, label]) => { md += `- ${color}: ${label}\\n`; });\n  } else { md += `_No legend entries_\\n`; }\n  md += '\\n## Nodes\\n\\n';\n  Object.entries(NODE_DATA).forEach(([id, node]) => {\n    const pos = savedPositions[id] || { x: 0, y: 0 };\n    const size = savedSizes[id] || 50;\n    const styles = savedStyles[id] || null;\n    md += `### ${id}\\n`;\n    md += `- **Name:** ${node.name || ''}\\n- **IP:** ${node.ip || ''}\\n- **Role:** ${node.role || ''}\\n`;\n    md += `- **Shape:** ${node.shape || 'circle'}\\n- **Tags:** ${(node.tags || []).join('; ') || '_none_'}\\n`;\n    md += `- **Layer:** ${node.layer || 'layer1'}\\n- **MAC:** ${node.mac || ''}\\n`;\n    md += `- **Rack Unit:** ${node.rackUnit || ''}\\n- **U Height:** ${node.uHeight || '1'}\\n`;\n    md += `- **Assigned Rack:** ${node.assignedRack || ''}\\n- **Rack Capacity:** ${node.rackCapacity || ''}\\n`;\n    md += `- **Is Rack:** ${node.isRack ? 'true' : 'false'}\\n- **Locked:** ${node.locked ? 'true' : 'false'}\\n`;\n    md += `- **Group ID:** ${node.groupId || ''}\\n- **Position:** ${Math.round(pos.x)}, ${Math.round(pos.y)}\\n- **Size:** ${size}\\n`;\n    if (node.notes && node.notes.length > 0) { md += `- **Notes:**\\n`; node.notes.forEach(note => { md += `  - ${note.replace(/\\n/g, '<br>')}\\n`; }); }\n    if (styles) { md += `- **Styles:** \\`${JSON.stringify(styles)}\\`\\n`; }\n    md += '\\n';\n  });\n  md += `## Connections\\n\\n`;\n  if (EDGE_DATA.list && EDGE_DATA.list.length > 0) {\n    EDGE_DATA.list.forEach(edge => {\n      const fromPort = edge.fromPort ? ` (${edge.fromPort})` : '';\n      const toPort = edge.toPort ? ` (${edge.toPort})` : '';\n      md += `- ${edge.from}${fromPort} --> ${edge.to}${toPort}\\n`;\n      md += `  - **ID:** ${edge.id}\\n  - **Label:** ${(edge.label || '').replace(/\\n/g, '<br>')}\\n  - **Color:** ${edge.color || ''}\\n`;\n      md += `  - **Width:** ${edge.width || 4}\\n  - **Direction:** ${edge.direction || 'none'}\\n`;\n      md += `  - **Routing:** ${edge.routing || 'curved'}\\n  - **Type:** ${edge.type || 'main'}\\n`;\n      md += `  - **Line Style:** ${edge.lineStyle || 'solid'}\\n  - **Group ID:** ${edge.groupId || ''}\\n`;\n      if (edge.points && edge.points.length > 0) { md += `  - **Points:** ${edge.points.map(p => `${Math.round(p.x)},${Math.round(p.y)}`).join(' ')}\\n`; }\n      if (edge.notes && edge.notes.length > 0) { md += `  - **Notes:**\\n`; edge.notes.forEach(note => { md += `    - ${note.replace(/\\n/g, '<br>')}\\n`; }); }\n      md += '\\n';\n    });\n  } else { md += `_No connections_\\n\\n`; }\n  md += `## Zones\\n\\n`;\n  if (RECT_DATA.list && RECT_DATA.list.length > 0) {\n    RECT_DATA.list.forEach(rect => {\n      md += `### ${rect.id}\\n`;\n      md += `- **Position:** ${Math.round(rect.x)}, ${Math.round(rect.y)}\\n- **Size:** ${Math.round(rect.width)} x ${Math.round(rect.height)}\\n`;\n      md += `- **Color:** ${rect.color || ''}\\n- **Style:** ${rect.style || 'filled'}\\n- **Line Style:** ${rect.lineStyle || 'solid'}\\n`;\n      md += `- **Border Color:** ${rect.borderColor || ''}\\n- **Border Width:** ${rect.borderWidth !== undefined ? rect.borderWidth : 2}\\n`;\n      if (rect.notes && rect.notes.length > 0) { md += `- **Notes:**\\n`; rect.notes.forEach(note => { md += `  - ${note.replace(/\\n/g, '<br>')}\\n`; }); }\n      md += '\\n';\n    });\n  } else { md += `_No zones_\\n\\n`; }\n  md += `## Text Labels\\n\\n`;\n  if (TEXT_DATA.list && TEXT_DATA.list.length > 0) {\n    TEXT_DATA.list.forEach(text => {\n      md += `### ${text.id}\\n`;\n      md += `- **Content:** ${(text.content || '').replace(/\\n/g, '<br>')}\\n- **Position:** ${Math.round(text.x)}, ${Math.round(text.y)}\\n`;\n      md += `- **Font Size:** ${text.fontSize || 18}\\n- **Color:** ${text.color || '#e2e8f0'}\\n`;\n      md += `- **Font Weight:** ${text.fontWeight || 'normal'}\\n- **Font Style:** ${text.fontStyle || 'normal'}\\n`;\n      md += `- **Text Align:** ${text.textAlign || 'start'}\\n- **Text Decoration:** ${text.textDecoration || 'none'}\\n`;\n      md += `- **Background Color:** ${text.bgColor || '#000000'}\\n- **Background Enabled:** ${text.bgEnabled ? 'true' : 'false'}\\n`;\n      md += `- **Opacity:** ${text.opacity !== undefined ? text.opacity : 1}\\n\\n`;\n    });\n  } else { md += `_No text labels_\\n\\n`; }\n  const blob = new Blob([md], { type: 'text/markdown;charset=utf-8;' });\n  const url = URL.createObjectURL(blob);\n  const a = document.createElement('a');\n  a.href = url;\n  a.download = `${safeTitle}.md`;\n  a.click();\n  URL.revokeObjectURL(url);\n  logAuditEvent('export', `Exported Markdown: ${a.download}`);\n}\n\ndocument.getElementById('import-markdown-file')?.addEventListener('change', async (e) => {\n  const file = e.target.files[0];\n  if (!file) return;\n  e.target.value = '';\n  try {\n    const text = (await file.text()).replace(/\\r\\n?/g, '\\n');\n    let config = null;\n    const configMatch = text.match(/<!--THEONEFILE_CONFIG\\s*([\\s\\S]*?)\\s*THEONEFILE_CONFIG-->/);\n    if (configMatch) { try { config = JSON.parse(configMatch[1].trim()); } catch (err) { console.warn('Failed to parse Markdown config:', err); } }\n    const nodes = {}, positions = {}, sizes = {}, styles = {}, edges = [], rects = [], texts = [], legend = {};\n    const sections = text.split(/^## /m);\n    sections.forEach(section => {\n      const lines = section.trim().split('\\n');\n      const sectionTitle = lines[0]?.trim().toLowerCase();\n      if (sectionTitle === 'legend') {\n        lines.slice(1).forEach(line => {\n          const match = line.match(/^- (#[a-fA-F0-9]{3,8}):\\s*(.+)$/);\n          if (match) legend[match[1]] = match[2].trim();\n        });\n      } else if (sectionTitle === 'nodes') {\n        let currentNodeId = null, currentNode = null, inNotes = false;\n        lines.slice(1).forEach(line => {\n          const nodeMatch = line.match(/^### (.+)$/);\n          if (nodeMatch) {\n            if (currentNodeId && currentNode) nodes[currentNodeId] = currentNode;\n            currentNodeId = nodeMatch[1].trim();\n            currentNode = { tags: [], notes: [] };\n            inNotes = false;\n            return;\n          }\n          if (!currentNode) return;\n          if (inNotes && line.match(/^\\s+-\\s+(.+)$/)) {\n            const noteMatch = line.match(/^\\s+-\\s+(.+)$/);\n            if (noteMatch) currentNode.notes.push(noteMatch[1].trim().replace(/<br>/g, '\\n'));\n            return;\n          }\n          inNotes = false;\n          const propMatch = line.match(/^- \\*\\*(.+?):\\*\\*\\s*(.*)$/);\n          if (propMatch) {\n            const key = propMatch[1].trim().toLowerCase(), val = propMatch[2].trim();\n            switch (key) {\n              case 'name': currentNode.name = val; break;\n              case 'ip': currentNode.ip = val; break;\n              case 'role': currentNode.role = val; break;\n              case 'shape': currentNode.shape = val; break;\n              case 'tags': currentNode.tags = val === '_none_' ? [] : val.split(';').map(t => t.trim()).filter(t => t); break;\n              case 'layer': currentNode.layer = val; break;\n              case 'mac': currentNode.mac = val; break;\n              case 'rack unit': currentNode.rackUnit = val; break;\n              case 'u height': currentNode.uHeight = val; break;\n              case 'assigned rack': currentNode.assignedRack = val; break;\n              case 'rack capacity': currentNode.rackCapacity = val; break;\n              case 'is rack': currentNode.isRack = val === 'true'; break;\n              case 'locked': currentNode.locked = val === 'true'; break;\n              case 'group id': currentNode.groupId = val || null; break;\n              case 'position':\n                const posMatch = val.match(/(-?\\d+),\\s*(-?\\d+)/);\n                if (posMatch) positions[currentNodeId] = { x: parseFloat(posMatch[1]), y: parseFloat(posMatch[2]) };\n                break;\n              case 'size': sizes[currentNodeId] = parseFloat(val) || 50; break;\n              case 'notes': inNotes = true; break;\n              case 'styles':\n                const stylesMatch = val.match(/`(.+)`/);\n                if (stylesMatch) { try { styles[currentNodeId] = JSON.parse(stylesMatch[1]); } catch (err) {} }\n                break;\n            }\n          }\n        });\n        if (currentNodeId && currentNode) nodes[currentNodeId] = currentNode;\n      } else if (sectionTitle === 'connections') {\n        let currentEdge = null, inNotes = false;\n        lines.slice(1).forEach(line => {\n          const connMatch = line.match(/^- (.+?)\\s*(?:\\(([^)]*)\\))?\\s*-->\\s*(.+?)\\s*(?:\\(([^)]*)\\))?$/);\n          if (connMatch) {\n            if (currentEdge) edges.push(currentEdge);\n            currentEdge = { from: connMatch[1].trim(), to: connMatch[3].trim(), fromPort: connMatch[2] || '', toPort: connMatch[4] || '', notes: [] };\n            inNotes = false;\n            return;\n          }\n          if (!currentEdge) return;\n          if (inNotes && line.match(/^\\s+-\\s+(.+)$/)) {\n            const noteMatch = line.match(/^\\s+-\\s+(.+)$/);\n            if (noteMatch) currentEdge.notes.push(noteMatch[1].trim().replace(/<br>/g, '\\n'));\n            return;\n          }\n          inNotes = false;\n          const propMatch = line.match(/^\\s+- \\*\\*(.+?):\\*\\*\\s*(.*)$/);\n          if (propMatch) {\n            const key = propMatch[1].trim().toLowerCase(), val = propMatch[2].trim();\n            switch (key) {\n              case 'id': currentEdge.id = val; break;\n              case 'label': currentEdge.label = val.replace(/<br>/g, '\\n'); break;\n              case 'color': currentEdge.color = val; break;\n              case 'width': currentEdge.width = parseFloat(val) || 4; break;\n              case 'direction': currentEdge.direction = val; break;\n              case 'routing': currentEdge.routing = val; break;\n              case 'type': currentEdge.type = val; break;\n              case 'line style': currentEdge.lineStyle = val; break;\n              case 'group id': currentEdge.groupId = val || null; break;\n              case 'points':\n                currentEdge.points = val.split(' ').map(p => { const [x, y] = p.split(',').map(Number); return { x, y }; }).filter(p => !isNaN(p.x) && !isNaN(p.y));\n                break;\n              case 'notes': inNotes = true; break;\n            }\n          }\n        });\n        if (currentEdge) edges.push(currentEdge);\n      } else if (sectionTitle === 'zones') {\n        let currentRect = null, inNotes = false;\n        lines.slice(1).forEach(line => {\n          const rectMatch = line.match(/^### (.+)$/);\n          if (rectMatch) {\n            if (currentRect) rects.push(currentRect);\n            currentRect = { id: rectMatch[1].trim(), notes: [] };\n            inNotes = false;\n            return;\n          }\n          if (!currentRect) return;\n          if (inNotes && line.match(/^\\s+-\\s+(.+)$/)) {\n            const noteMatch = line.match(/^\\s+-\\s+(.+)$/);\n            if (noteMatch) currentRect.notes.push(noteMatch[1].trim().replace(/<br>/g, '\\n'));\n            return;\n          }\n          inNotes = false;\n          const propMatch = line.match(/^- \\*\\*(.+?):\\*\\*\\s*(.*)$/);\n          if (propMatch) {\n            const key = propMatch[1].trim().toLowerCase(), val = propMatch[2].trim();\n            switch (key) {\n              case 'position': const posMatch = val.match(/(-?\\d+),\\s*(-?\\d+)/); if (posMatch) { currentRect.x = parseFloat(posMatch[1]); currentRect.y = parseFloat(posMatch[2]); } break;\n              case 'size': const sizeMatch = val.match(/(\\d+)\\s*x\\s*(\\d+)/); if (sizeMatch) { currentRect.width = parseFloat(sizeMatch[1]); currentRect.height = parseFloat(sizeMatch[2]); } break;\n              case 'color': currentRect.color = val; break;\n              case 'style': currentRect.style = val; break;\n              case 'line style': currentRect.lineStyle = val; break;\n              case 'border color': currentRect.borderColor = val; break;\n              case 'border width': currentRect.borderWidth = parseFloat(val) || 2; break;\n              case 'notes': inNotes = true; break;\n            }\n          }\n        });\n        if (currentRect) rects.push(currentRect);\n      } else if (sectionTitle === 'text labels') {\n        let currentText = null;\n        lines.slice(1).forEach(line => {\n          const textMatch = line.match(/^### (.+)$/);\n          if (textMatch) {\n            if (currentText) texts.push(currentText);\n            currentText = { id: textMatch[1].trim() };\n            return;\n          }\n          if (!currentText) return;\n          const propMatch = line.match(/^- \\*\\*(.+?):\\*\\*\\s*(.*)$/);\n          if (propMatch) {\n            const key = propMatch[1].trim().toLowerCase(), val = propMatch[2].trim();\n            switch (key) {\n              case 'content': currentText.content = val.replace(/<br>/g, '\\n'); break;\n              case 'position': const posMatch = val.match(/(-?\\d+),\\s*(-?\\d+)/); if (posMatch) { currentText.x = parseFloat(posMatch[1]); currentText.y = parseFloat(posMatch[2]); } break;\n              case 'font size': currentText.fontSize = parseFloat(val) || 18; break;\n              case 'color': currentText.color = val; break;\n              case 'font weight': currentText.fontWeight = val; break;\n              case 'font style': currentText.fontStyle = val; break;\n              case 'text align': currentText.textAlign = val; break;\n              case 'text decoration': currentText.textDecoration = val; break;\n              case 'background color': currentText.bgColor = val; break;\n              case 'background enabled': currentText.bgEnabled = val === 'true'; break;\n              case 'opacity': currentText.opacity = parseFloat(val) || 1; break;\n            }\n          }\n        });\n        if (currentText) texts.push(currentText);\n      }\n    });\n    const nodeCount = Object.keys(nodes).length, edgeCount = edges.length, rectCount = rects.length, textCount = texts.length;\n    if (nodeCount === 0 && edgeCount === 0 && rectCount === 0 && textCount === 0) {\n      showAlert(t(\"dialogs.noValidTopologyInMarkdown\"));\n      return;\n    }\n    if (!await showConfirm(t(\"dialogs.confirmImportMarkdown\", { nodeCount: nodeCount, edgeCount: edgeCount, rectCount: rectCount, textCount: textCount }))) return;\n    pushUndo('import markdown');\n    if (config && config.documentTabs) {\n      if (config.page) { Object.assign(PAGE_STATE, config.page); wieldThePower(); }\n      if (config.canvas) {\n        canvasState.zoom = config.canvas.zoom || 1;\n        canvasState.panX = config.canvas.panX || 0;\n        canvasState.panY = config.canvas.panY || 0;\n      }\n      if (config.savedStyleSets) {\n        savedStyleSets = config.savedStyleSets;\n      }\n      if (config.documentTabs) {\n        documentTabs = config.documentTabs;\n        currentTabIndex = config.currentTabIndex || 0;\n      }\n      if (config.savedTopologyView) {\n        savedTopologyView = config.savedTopologyView;\n      }\n      if (config.encryptedSections) {\n        encryptedSections = config.encryptedSections;\n      }\n      if (config.auditLog && Array.isArray(config.auditLog)) {\n        auditLog = config.auditLog;\n        try {\n          localStorage.setItem(AUDIT_STORAGE_KEY, JSON.stringify(auditLog));\n        } catch (e) {\n          console.warn(\"Failed to sync audit log to localStorage:\", e);\n        }\n      }\n      if (config.recordings && Array.isArray(config.recordings)) {\n        RECORDING_STATE.recordings = config.recordings;\n        RECORDING_STATE.currentRecording = config.recordings[0] || null;\n      }\n    } else if (config && config.pageState) {\n      Object.assign(PAGE_STATE, config.pageState);\n      wieldThePower();\n      if (config.canvasView) {\n        canvasState.zoom = config.canvasView.zoom || 1;\n        canvasState.panX = config.canvasView.panX || 0;\n        canvasState.panY = config.canvasView.panY || 0;\n      }\n    }\n    Object.keys(NODE_DATA).forEach(k => delete NODE_DATA[k]);\n    Object.keys(savedPositions).forEach(k => delete savedPositions[k]);\n    Object.keys(savedSizes).forEach(k => delete savedSizes[k]);\n    Object.keys(savedStyles).forEach(k => delete savedStyles[k]);\n    EDGE_DATA.list = [];\n    RECT_DATA.list = [];\n    TEXT_DATA.list = [];\n    Object.keys(EDGE_LEGEND).forEach(k => delete EDGE_LEGEND[k]);\n    Object.assign(EDGE_LEGEND, legend);\n    Object.entries(nodes).forEach(([id, node]) => {\n      NODE_DATA[id] = { name: node.name || '', ip: node.ip || '', role: node.role || '', shape: node.shape || 'circle', tags: node.tags || [], notes: node.notes || [], mac: node.mac || '', rackUnit: node.rackUnit || '', uHeight: node.uHeight || '1', layer: node.layer || 'layer1', assignedRack: node.assignedRack || '', hostedOn: node.hostedOn || '', rackCapacity: node.rackCapacity || '', isRack: node.isRack || false, locked: node.locked || false, groupId: node.groupId || null };\n      savedPositions[id] = positions[id] || { x: 500, y: 300 };\n      if (sizes[id]) savedSizes[id] = sizes[id];\n      if (styles[id]) savedStyles[id] = styles[id];\n    });\n    let skippedEdges = 0;\n    edges.forEach(edge => {\n      if (!NODE_DATA[edge.from] || !NODE_DATA[edge.to]) {\n        console.warn(`Skipping orphan edge: ${edge.from} -> ${edge.to}`);\n        skippedEdges++;\n        return;\n      }\n      EDGE_DATA.list.push({ id: edge.id || `edge-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, from: edge.from, to: edge.to, fromPort: edge.fromPort || '', toPort: edge.toPort || '', label: edge.label || '', color: edge.color || '', width: edge.width || 4, direction: edge.direction || 'none', routing: edge.routing || 'curved', type: edge.type || 'main', lineStyle: edge.lineStyle || 'solid', groupId: edge.groupId || null, points: edge.points || [], notes: edge.notes || [], waypoints: edge.waypoints || [] });\n    });\n    if (skippedEdges > 0) console.warn(`Total skipped orphan edges: ${skippedEdges}`);\n    rects.forEach(rect => {\n      RECT_DATA.list.push({ id: rect.id, x: rect.x || 0, y: rect.y || 0, width: rect.width || 100, height: rect.height || 100, color: rect.color || '#f97316', style: rect.style || 'filled', lineStyle: rect.lineStyle || 'solid', borderColor: rect.borderColor || '', borderWidth: rect.borderWidth !== undefined ? rect.borderWidth : 2, notes: rect.notes || [] });\n    });\n    texts.forEach(text => {\n      TEXT_DATA.list.push({ id: text.id, x: text.x || 0, y: text.y || 0, content: text.content || '', fontSize: text.fontSize || 18, color: text.color || '#e2e8f0', fontWeight: text.fontWeight || 'normal', fontStyle: text.fontStyle || 'normal', textAlign: text.textAlign || 'start', textDecoration: text.textDecoration || 'none', bgColor: text.bgColor || '#000000', bgEnabled: text.bgEnabled || false, opacity: text.opacity !== undefined ? text.opacity : 1 });\n    });\n    forgeTheTopology();\n    if (typeof forgeTheLegend === 'function') forgeTheLegend();\n    if (typeof updateZoneLegend === 'function') updateZoneLegend();\n    updateViewBox();\n    updateDrawToolbarVisibility();\n    updateTopologyToolbarVisibility();\n    logAuditEvent('import', `Imported Markdown: ${file.name} (${nodeCount} nodes, ${edgeCount} connections)`);\n    showAlert(t(\"dialogs.markdownImportSuccess\", { nodeCount, edgeCount, rectCount, textCount }));\n  } catch (err) {\n    console.error('Markdown import error:', err);\n    showAlert(t(\"dialogs.importMarkdownFailed\", { error: err.message }));\n  }\n});\n\ndocument.getElementById('mobile-export-btn')?.addEventListener('click', () => {\n  document.getElementById('mobile-export-modal').classList.add('active');\n  document.getElementById('topbar-menu').classList.remove('open');\n});\ndocument.getElementById('mobile-import-btn')?.addEventListener('click', () => {\n  document.getElementById('mobile-import-modal').classList.add('active');\n  document.getElementById('topbar-menu').classList.remove('open');\n});\ndocument.getElementById('mobile-export-modal')?.addEventListener('click', (e) => {\n  if (e.target.id === 'mobile-export-modal') e.target.classList.remove('active');\n});\ndocument.getElementById('mobile-import-modal')?.addEventListener('click', (e) => {\n  if (e.target.id === 'mobile-import-modal') e.target.classList.remove('active');\n});\ndocument.getElementById('import-html-file')?.addEventListener('change', async (e) => {\n  const file = e.target.files[0];\n  if (!file) return;\n  e.target.value = '';\n  try {\n    const text = await file.text();\n    const parser = new DOMParser();\n    const doc = parser.parseFromString(text, 'text/html');\n    const stateScript = doc.getElementById('topology-state');\n    if (!stateScript) {\n      showAlert(t(\"dialogs.invalidHtmlFile\"));\n      return;\n    }\n    const data = JSON.parse(stateScript.textContent);\n    if (!data.nodeData || !data.edgeData) {\n      showAlert(t(\"dialogs.invalidHtmlFile\"));\n      return;\n    }\n    const nodeCount = Object.keys(data.nodeData).length;\n    const edgeCount = data.edgeData.list?.length || 0;\n    const tabCount = data.documentTabs?.length || 1;\n    if (!await showConfirm(t(\"dialogs.confirmImportHtml\", { nodeCount, edgeCount, tabCount }))) {\n      return;\n    }\n    pushUndo('import html');\n    NODE_DATA = data.nodeData || {};\n    EDGE_DATA = data.edgeData || { list: [] };\n    EDGE_LEGEND = data.edgeLegend || {};\n    RECT_DATA = data.rectData || { list: [] };\n    TEXT_DATA = data.textData || { list: [] };\n    savedPositions = data.nodePositions || {};\n    savedSizes = data.nodeSizes || {};\n    savedStyles = data.nodeStyles || {};\n    if (data.page) {\n      PAGE_STATE = Object.assign({}, DEFAULT_PAGE_STATE, data.page);\n      wieldThePower();\n    }\n    if (data.autoPingEnabled !== undefined) {\n      autoPingEnabled = data.autoPingEnabled;\n      PAGE_STATE.autoPingEnabled = autoPingEnabled;\n      document.getElementById(\"auto-ping-enabled\").checked = autoPingEnabled;\n    }\n    if (data.autoPingInterval !== undefined) {\n      autoPingInterval = data.autoPingInterval;\n      PAGE_STATE.autoPingInterval = autoPingInterval;\n      document.getElementById(\"auto-ping-interval\").value = autoPingInterval;\n    }\n    if (data.canvas) {\n      canvasState.zoom = data.canvas.zoom || 1;\n      canvasState.panX = data.canvas.panX || 0;\n      canvasState.panY = data.canvas.panY || 0;\n    }\n    if (data.page?.title) {\n      document.title = data.page.title;\n      document.querySelector(\".editable-page-title\").textContent = data.page.title;\n    }\n    if (data.savedStyleSets) {\n      savedStyleSets = data.savedStyleSets;\n    }\n    if (data.documentTabs) {\n      documentTabs = data.documentTabs;\n      currentTabIndex = data.currentTabIndex || 0;\n    }\n    if (data.savedTopologyView) {\n      savedTopologyView = data.savedTopologyView;\n    }\n    if (data.encryptedSections) {\n      encryptedSections = data.encryptedSections;\n    }\n    if (data.auditLog && Array.isArray(data.auditLog)) {\n      auditLog = data.auditLog;\n      try {\n        localStorage.setItem(AUDIT_STORAGE_KEY, JSON.stringify(auditLog));\n      } catch (err) {\n        console.warn(\"Failed to sync audit log to localStorage:\", err);\n      }\n    }\n    if (data.recordings && Array.isArray(data.recordings)) {\n      RECORDING_STATE.recordings = data.recordings;\n      RECORDING_STATE.currentRecording = data.recordings[0] || null;\n    }\n    const layerMap = { \"physical\": \"layer1\", \"logical\": \"layer2\", \"security\": \"layer3\", \"application\": \"layer4\" };\n    Object.values(NODE_DATA).forEach(node => {\n      if (!node.tags) node.tags = [];\n      if (!node.notes) node.notes = [];\n      if (!node.layer || layerMap[node.layer]) node.layer = layerMap[node.layer] || 'layer1';\n    });\n    EDGE_DATA.list.forEach(edge => {\n      if (!edge.notes) edge.notes = [];\n    });\n    forgeTheTopology();\n    if (typeof forgeTheLegend === 'function') forgeTheLegend();\n    if (typeof updateZoneLegend === 'function') updateZoneLegend();\n    logAuditEvent(\"import\", `Imported HTML: ${file.name} (${nodeCount} nodes, ${edgeCount} connections)`);\n    updateViewBox();\n    if (autoPingEnabled) {\n      startAutoPing();\n    } else {\n      stopAutoPing();\n    }\n    const nodeIds = Object.keys(NODE_DATA);\n    if (nodeIds.length > 0) {\n      claimTheImmortal(nodeIds[0]);\n    } else {\n      document.getElementById(\"node-panel\").style.display = \"none\";\n      document.getElementById(\"edge-panel\").style.display = \"none\";\n      document.getElementById(\"topology-toolbar\").style.display = \"none\";\n    }\n    updateDrawToolbarVisibility();\n    showAlert(t(\"dialogs.htmlImportSuccess\", { nodeCount, edgeCount }));\n  } catch (err) {\n    console.error(\"HTML import error:\", err);\n    showAlert(t(\"dialogs.invalidHtmlFile\"));\n  }\n});\ndocument.getElementById('import-json-file')?.addEventListener('change', async (e) => {\n  const file = e.target.files[0];\n  if (!file) return;\n  e.target.value = '';\n  const existingInput = document.getElementById('import-data-file');\n  if (existingInput) {\n    const dt = new DataTransfer();\n    dt.items.add(file);\n    existingInput.files = dt.files;\n    existingInput.dispatchEvent(new Event('change'));\n  }\n});\n\nfunction formatRecordingTime(ms) {\n  const seconds = Math.floor(ms / 1000);\n  const minutes = Math.floor(seconds / 60);\n  const secs = seconds % 60;\n  return `${minutes}:${secs.toString().padStart(2, '0')}`;\n}\n\nfunction captureRecordingFrame() {\n  const frame = {\n    time: Date.now() - RECORDING_STATE.startTime,\n    positions: {},\n    waypoints: {},\n    rects: [],\n    texts: []\n  };\n  Object.keys(savedPositions).forEach(id => {\n    frame.positions[id] = { x: savedPositions[id].x, y: savedPositions[id].y };\n  });\n  EDGE_DATA.list.forEach(edge => {\n    if (edge.waypoints && edge.waypoints.length > 0) {\n      frame.waypoints[edge.id] = edge.waypoints.map(wp => ({ x: wp.x, y: wp.y }));\n    }\n  });\n  if (RECT_DATA && RECT_DATA.list) {\n    frame.rects = RECT_DATA.list.map(r => ({ id: r.id, x: r.x, y: r.y, width: r.width, height: r.height, rotation: r.rotation || 0 }));\n  }\n  if (TEXT_DATA && TEXT_DATA.list) {\n    frame.texts = TEXT_DATA.list.map(t => ({ id: t.id, x: t.x, y: t.y }));\n  }\n  RECORDING_STATE.frames.push(frame);\n  document.getElementById('recording-time').textContent = formatRecordingTime(frame.time);\n}\n\nfunction startRecording() {\n  if (RECORDING_STATE.isRecording) return;\n  const expanded = document.getElementById('recording-expanded');\n  if (expanded) expanded.style.display = 'inline-flex';\n  RECORDING_STATE.isRecording = true;\n  RECORDING_STATE.frames = [];\n  RECORDING_STATE.startTime = Date.now();\n  captureRecordingFrame();\n  RECORDING_STATE.captureInterval = setInterval(captureRecordingFrame, 1000 / RECORDING_STATE.CAPTURE_FPS);\n  document.getElementById('record-btn').style.background = '#f56565';\n  document.getElementById('record-btn').style.color = 'white';\n  document.getElementById('stop-btn').style.display = 'inline-block';\n  logAuditEvent('recording', 'Started recording');\n}\n\nfunction stopRecording() {\n  if (!RECORDING_STATE.isRecording && !RECORDING_STATE.isPlaying) return;\n  const wasPlaying = RECORDING_STATE.isPlaying;\n  if (RECORDING_STATE.isRecording) {\n    clearInterval(RECORDING_STATE.captureInterval);\n    RECORDING_STATE.isRecording = false;\n    if (RECORDING_STATE.frames.length > 1) {\n      const recording = {\n        id: 'rec-' + Date.now(),\n        name: 'Recording ' + (RECORDING_STATE.recordings.length + 1),\n        created: new Date().toISOString(),\n        duration: RECORDING_STATE.frames[RECORDING_STATE.frames.length - 1].time,\n        frames: RECORDING_STATE.frames\n      };\n      RECORDING_STATE.recordings.push(recording);\n      RECORDING_STATE.currentRecording = recording;\n      logAuditEvent('recording', `Saved recording: ${recording.name} (${formatRecordingTime(recording.duration)})`);\n    }\n    document.getElementById('record-btn').style.background = 'var(--btn-bg)';\n    document.getElementById('record-btn').style.color = '#f56565';\n  }\n  if (RECORDING_STATE.isPlaying) {\n    clearTimeout(RECORDING_STATE.playbackTimer);\n    RECORDING_STATE.isPlaying = false;\n    RECORDING_STATE.isPaused = false;\n    RECORDING_STATE.trailHistory = {};\n    forgeTheTopology();\n  }\n  document.getElementById('stop-btn').style.display = 'none';\n  document.getElementById('play-btn').style.display = 'inline-block';\n  document.getElementById('pause-btn').style.display = 'none';\n  document.getElementById('recording-time').textContent = '0:00';\n  if (wasPlaying) {\n    const expanded = document.getElementById('recording-expanded');\n    if (expanded) expanded.style.display = 'none';\n  }\n}\n\nfunction playRecording() {\n  if (!RECORDING_STATE.currentRecording || RECORDING_STATE.currentRecording.frames.length < 2) {\n    showAlert(t(\"dialogs.noRecordingAvailable\"));\n    const expanded = document.getElementById('recording-expanded');\n    if (expanded) expanded.style.display = 'none';\n    return;\n  }\n  const expanded = document.getElementById('recording-expanded');\n  if (expanded) expanded.style.display = 'inline-flex';\n  if (RECORDING_STATE.isPaused) {\n    RECORDING_STATE.isPaused = false;\n    RECORDING_STATE.isPlaying = true;\n    document.getElementById('play-btn').style.display = 'none';\n    document.getElementById('pause-btn').style.display = 'inline-block';\n    playNextFrame();\n    return;\n  }\n  RECORDING_STATE.isPlaying = true;\n  RECORDING_STATE.playbackIndex = 0;\n  document.getElementById('play-btn').style.display = 'none';\n  document.getElementById('pause-btn').style.display = 'inline-block';\n  document.getElementById('stop-btn').style.display = 'inline-block';\n  playNextFrame();\n}\n\nfunction pauseRecording() {\n  if (!RECORDING_STATE.isPlaying) return;\n  RECORDING_STATE.isPaused = true;\n  RECORDING_STATE.isPlaying = false;\n  clearTimeout(RECORDING_STATE.playbackTimer);\n  document.getElementById('play-btn').style.display = 'inline-block';\n  document.getElementById('pause-btn').style.display = 'none';\n}\n\nfunction startStepRecording() {\n  if (RECORDING_STATE.isStepRecording || RECORDING_STATE.isRecording) return;\n  RECORDING_STATE.isStepRecording = true;\n  RECORDING_STATE.stepFrames = [];\n  RECORDING_STATE.stepCount = 0;\n  captureStepFrame();\n  const stepBtn = document.getElementById('step-record-btn');\n  stepBtn.textContent = '+';\n  stepBtn.style.background = '#48bb78';\n  stepBtn.style.color = 'white';\n  document.getElementById('recording-expanded').style.display = 'inline-flex';\n  document.getElementById('pause-btn').style.display = 'none';\n  document.getElementById('record-btn').style.display = 'none';\n  updateStepRecordingTime();\n  logAuditEvent('recording', 'Started step by step recording');\n}\n\nfunction captureStepFrame() {\n  const frame = {\n    time: RECORDING_STATE.stepCount * 1000,\n    positions: {},\n    waypoints: {},\n    rects: [],\n    texts: []\n  };\n  Object.keys(savedPositions).forEach(id => {\n    frame.positions[id] = { x: savedPositions[id].x, y: savedPositions[id].y };\n  });\n  EDGE_DATA.list.forEach(edge => {\n    if (edge.waypoints && edge.waypoints.length > 0) {\n      frame.waypoints[edge.id] = edge.waypoints.map(wp => ({ x: wp.x, y: wp.y }));\n    }\n  });\n  if (RECT_DATA && RECT_DATA.list) {\n    frame.rects = RECT_DATA.list.map(r => ({ id: r.id, x: r.x, y: r.y, width: r.width, height: r.height, rotation: r.rotation || 0 }));\n  }\n  if (TEXT_DATA && TEXT_DATA.list) {\n    frame.texts = TEXT_DATA.list.map(t => ({ id: t.id, x: t.x, y: t.y }));\n  }\n  RECORDING_STATE.stepFrames.push(frame);\n  RECORDING_STATE.stepCount++;\n  updateStepRecordingTime();\n}\n\nfunction updateStepRecordingTime() {\n  const timeEl = document.getElementById('recording-time');\n  if (timeEl) {\n    timeEl.textContent = RECORDING_STATE.stepCount + ' frames';\n  }\n}\n\nfunction stopStepRecording() {\n  if (!RECORDING_STATE.isStepRecording) return;\n  RECORDING_STATE.isStepRecording = false;\n  if (RECORDING_STATE.stepFrames.length > 1) {\n    const recording = {\n      id: 'rec-' + Date.now(),\n      name: 'Step Recording ' + (RECORDING_STATE.recordings.length + 1),\n      created: new Date().toISOString(),\n      duration: (RECORDING_STATE.stepCount - 1) * 1000,\n      frames: RECORDING_STATE.stepFrames\n    };\n    RECORDING_STATE.recordings.push(recording);\n    RECORDING_STATE.currentRecording = recording;\n    logAuditEvent('recording', `Saved step recording: ${recording.name} (${RECORDING_STATE.stepCount} frames)`);\n  }\n  const stepBtn = document.getElementById('step-record-btn');\n  stepBtn.textContent = '●+';\n  stepBtn.style.background = 'var(--btn-bg)';\n  stepBtn.style.color = '#48bb78';\n  document.getElementById('record-btn').style.display = 'inline-block';\n  document.getElementById('recording-time').textContent = '0:00';\n}\n\nfunction playNextFrame() {\n  if (!RECORDING_STATE.isPlaying || !RECORDING_STATE.currentRecording) return;\n  const frames = RECORDING_STATE.currentRecording.frames;\n  if (RECORDING_STATE.playbackIndex >= frames.length) {\n    if (RECORDING_STATE.loopPlayback) {\n      RECORDING_STATE.playbackIndex = 0;\n      RECORDING_STATE.trailHistory = {};\n    } else {\n      stopRecording();\n      return;\n    }\n  }\n  const frame = frames[RECORDING_STATE.playbackIndex];\n  const trailLength = PAGE_STATE.motionTrailLength || 8;\n  Object.keys(frame.positions).forEach(id => {\n    if (savedPositions[id]) {\n      if (PAGE_STATE.motionTrailsEnabled) {\n        if (!RECORDING_STATE.trailHistory[id]) RECORDING_STATE.trailHistory[id] = [];\n        RECORDING_STATE.trailHistory[id].push({ x: frame.positions[id].x, y: frame.positions[id].y });\n        if (RECORDING_STATE.trailHistory[id].length > trailLength) {\n          RECORDING_STATE.trailHistory[id].shift();\n        }\n      }\n      savedPositions[id].x = frame.positions[id].x;\n      savedPositions[id].y = frame.positions[id].y;\n    }\n  });\n  if (frame.waypoints) {\n    EDGE_DATA.list.forEach(edge => {\n      if (frame.waypoints[edge.id]) {\n        edge.waypoints = frame.waypoints[edge.id].map(wp => ({ x: wp.x, y: wp.y }));\n      } else {\n        edge.waypoints = [];\n      }\n    });\n  }\n  if (frame.rects && RECT_DATA && RECT_DATA.list) {\n    frame.rects.forEach(frameRect => {\n      const rect = RECT_DATA.list.find(r => r.id === frameRect.id);\n      if (rect) {\n        rect.x = frameRect.x;\n        rect.y = frameRect.y;\n        rect.width = frameRect.width;\n        rect.height = frameRect.height;\n        rect.rotation = frameRect.rotation || 0;\n      }\n    });\n  }\n  if (frame.texts && TEXT_DATA && TEXT_DATA.list) {\n    frame.texts.forEach(frameText => {\n      const text = TEXT_DATA.list.find(t => t.id === frameText.id);\n      if (text) {\n        text.x = frameText.x;\n        text.y = frameText.y;\n      }\n    });\n  }\n  forgeTheTopology();\n  updateMinimap();\n  document.getElementById('recording-time').textContent = formatRecordingTime(frame.time);\n  const progress = (RECORDING_STATE.playbackIndex / (frames.length - 1)) * 100;\n  document.getElementById('playback-scrubber').value = progress;\n  RECORDING_STATE.playbackIndex++;\n  const nextFrame = frames[RECORDING_STATE.playbackIndex];\n  if (nextFrame) {\n    const delay = (nextFrame.time - frame.time) / RECORDING_STATE.playbackSpeed;\n    RECORDING_STATE.playbackTimer = setTimeout(playNextFrame, Math.max(16, delay));\n  } else if (RECORDING_STATE.loopPlayback) {\n    RECORDING_STATE.playbackTimer = setTimeout(playNextFrame, 100);\n  } else {\n    stopRecording();\n  }\n}\n\nfunction renderMotionTrails(svg) {\n  if (!PAGE_STATE.motionTrailsEnabled || !RECORDING_STATE.isPlaying) return;\n  if (Object.keys(RECORDING_STATE.trailHistory).length === 0) return;\n  const ns = \"http://www.w3.org/2000/svg\";\n  const trailGroup = document.createElementNS(ns, \"g\");\n  trailGroup.id = \"motion-trails\";\n  const trailStyle = PAGE_STATE.motionTrailStyle || \"solid\";\n  const showArrow = PAGE_STATE.motionTrailArrow !== false;\n  Object.keys(RECORDING_STATE.trailHistory).forEach(nodeId => {\n    const node = NODE_DATA[nodeId];\n    if (!node) return;\n    if (node.trailEnabled === false) return;\n    const trail = RECORDING_STATE.trailHistory[nodeId];\n    if (!trail || trail.length < 2) return;\n    const nodeSize = (savedSizes[nodeId] && savedSizes[nodeId].size) || 55;\n    const nodeColor = (savedStyles[nodeId] && savedStyles[nodeId].stroke) || PAGE_STATE.nodeStroke || \"#475569\";\n    if (trailStyle === \"dots\") {\n      trail.forEach((point, idx) => {\n        const opacity = (idx + 1) / trail.length * 0.7;\n        const dotSize = 3 + (idx / trail.length) * 4;\n        const dot = document.createElementNS(ns, \"circle\");\n        dot.setAttribute(\"cx\", point.x);\n        dot.setAttribute(\"cy\", point.y);\n        dot.setAttribute(\"r\", dotSize);\n        dot.setAttribute(\"fill\", nodeColor);\n        dot.setAttribute(\"opacity\", opacity);\n        trailGroup.appendChild(dot);\n      });\n    } else {\n      const path = document.createElementNS(ns, \"polyline\");\n      const points = trail.map(p => `${p.x},${p.y}`).join(\" \");\n      path.setAttribute(\"points\", points);\n      path.setAttribute(\"fill\", \"none\");\n      path.setAttribute(\"stroke\", nodeColor);\n      path.setAttribute(\"stroke-width\", \"3\");\n      path.setAttribute(\"stroke-linecap\", \"round\");\n      path.setAttribute(\"stroke-linejoin\", \"round\");\n      path.setAttribute(\"opacity\", \"0.6\");\n      if (trailStyle === \"dashed\") {\n        path.setAttribute(\"stroke-dasharray\", \"8,4\");\n      }\n      trailGroup.appendChild(path);\n    }\n    if (showArrow && trail.length >= 2) {\n      const last = trail[trail.length - 1];\n      const prev = trail[trail.length - 2];\n      const angle = Math.atan2(last.y - prev.y, last.x - prev.x);\n      const arrowSize = 10;\n      const arrowX = last.x + Math.cos(angle) * (nodeSize / 2 + 5);\n      const arrowY = last.y + Math.sin(angle) * (nodeSize / 2 + 5);\n      const arrow = document.createElementNS(ns, \"polygon\");\n      const p1x = arrowX + Math.cos(angle) * arrowSize;\n      const p1y = arrowY + Math.sin(angle) * arrowSize;\n      const p2x = arrowX + Math.cos(angle + 2.5) * arrowSize * 0.7;\n      const p2y = arrowY + Math.sin(angle + 2.5) * arrowSize * 0.7;\n      const p3x = arrowX + Math.cos(angle - 2.5) * arrowSize * 0.7;\n      const p3y = arrowY + Math.sin(angle - 2.5) * arrowSize * 0.7;\n      arrow.setAttribute(\"points\", `${p1x},${p1y} ${p2x},${p2y} ${p3x},${p3y}`);\n      arrow.setAttribute(\"fill\", nodeColor);\n      arrow.setAttribute(\"opacity\", \"0.8\");\n      trailGroup.appendChild(arrow);\n    }\n  });\n  const firstChild = svg.firstChild;\n  if (firstChild) {\n    svg.insertBefore(trailGroup, firstChild);\n  } else {\n    svg.appendChild(trailGroup);\n  }\n}\n\nfunction seekRecording(percent) {\n  if (!RECORDING_STATE.currentRecording) return;\n  const frames = RECORDING_STATE.currentRecording.frames;\n  const targetIndex = Math.floor((percent / 100) * (frames.length - 1));\n  RECORDING_STATE.playbackIndex = targetIndex;\n  const frame = frames[targetIndex];\n  if (frame) {\n    Object.keys(frame.positions).forEach(id => {\n      if (savedPositions[id]) {\n        savedPositions[id].x = frame.positions[id].x;\n        savedPositions[id].y = frame.positions[id].y;\n      }\n    });\n    if (frame.waypoints) {\n      EDGE_DATA.list.forEach(edge => {\n        if (frame.waypoints[edge.id]) {\n          edge.waypoints = frame.waypoints[edge.id].map(wp => ({ x: wp.x, y: wp.y }));\n        } else {\n          edge.waypoints = [];\n        }\n      });\n    }\n    if (frame.rects && RECT_DATA && RECT_DATA.list) {\n      frame.rects.forEach(frameRect => {\n        const rect = RECT_DATA.list.find(r => r.id === frameRect.id);\n        if (rect) {\n          rect.x = frameRect.x;\n          rect.y = frameRect.y;\n          rect.width = frameRect.width;\n          rect.height = frameRect.height;\n          rect.rotation = frameRect.rotation || 0;\n        }\n      });\n    }\n    if (frame.texts && TEXT_DATA && TEXT_DATA.list) {\n      frame.texts.forEach(frameText => {\n        const text = TEXT_DATA.list.find(t => t.id === frameText.id);\n        if (text) {\n          text.x = frameText.x;\n          text.y = frameText.y;\n        }\n      });\n    }\n    forgeTheTopology();\n    updateMinimap();\n    document.getElementById('recording-time').textContent = formatRecordingTime(frame.time);\n  }\n}\n\nfunction renderRecordingsList() {\n  const list = document.getElementById('recordings-list');\n  if (RECORDING_STATE.recordings.length === 0) {\n    list.innerHTML = '<div style=\"text-align: center; padding: 40px; color: var(--text-soft);\">' + t(\"emptyStates.noRecordingsYet\") + '</div>';\n    return;\n  }\n  list.innerHTML = RECORDING_STATE.recordings.map((rec, i) => `\n    <div style=\"display:flex;align-items:center;gap:10px;padding:10px;background:var(--panel);border-radius:6px;border:1px solid ${rec.id === RECORDING_STATE.currentRecording?.id ? 'var(--accent)' : 'var(--edge-main)'};\">\n      <input type=\"radio\" name=\"recording-select\" value=\"${rec.id}\" ${rec.id === RECORDING_STATE.currentRecording?.id ? 'checked' : ''} onchange=\"selectRecording('${rec.id}')\">\n      <div style=\"flex:1;\">\n        <input type=\"text\" value=\"${rec.name}\" style=\"background:transparent;border:none;color:var(--text-main);font-weight:600;width:100%;\" onchange=\"renameRecording('${rec.id}', this.value)\">\n        <div style=\"font-size:11px;color:var(--text-soft);\">${formatRecordingTime(rec.duration)} | ${rec.frames.length} frames | ${new Date(rec.created).toLocaleDateString()}</div>\n      </div>\n      <button onclick=\"playSelectedRecording('${rec.id}')\" style=\"padding:4px 8px;background:var(--btn-bg);border:1px solid var(--edge-main);border-radius:4px;cursor:pointer;\">▶</button>\n    </div>\n  `).join('');\n}\n\nfunction selectRecording(id) {\n  RECORDING_STATE.currentRecording = RECORDING_STATE.recordings.find(r => r.id === id) || null;\n  renderRecordingsList();\n}\n\nfunction playSelectedRecording(id) {\n  selectRecording(id);\n  document.getElementById('recordings-modal').classList.remove('active');\n  playRecording();\n}\n\nfunction renameRecording(id, newName) {\n  const rec = RECORDING_STATE.recordings.find(r => r.id === id);\n  if (rec) rec.name = newName;\n}\n\nasync function deleteSelectedRecording() {\n  if (!RECORDING_STATE.currentRecording) {\n    showAlert(t(\"dialogs.selectRecordingToDelete\"));\n    return;\n  }\n  if (!await showConfirm(t(\"dialogs.deleteRecording\", { name: RECORDING_STATE.currentRecording.name }))) return;\n  RECORDING_STATE.recordings = RECORDING_STATE.recordings.filter(r => r.id !== RECORDING_STATE.currentRecording.id);\n  RECORDING_STATE.currentRecording = RECORDING_STATE.recordings[0] || null;\n  renderRecordingsList();\n  logAuditEvent('recording', 'Deleted recording');\n}\n\nfunction exportRecordingJSON() {\n  if (!RECORDING_STATE.currentRecording) {\n    showAlert(t(\"dialogs.selectRecordingToExport\"));\n    return;\n  }\n  const json = JSON.stringify(RECORDING_STATE.currentRecording, null, 2);\n  const blob = new Blob([json], { type: 'application/json' });\n  const url = URL.createObjectURL(blob);\n  const a = document.createElement('a');\n  a.href = url;\n  a.download = (RECORDING_STATE.currentRecording.name || 'recording').replace(/[^a-z0-9]/gi, '-') + '.json';\n  a.click();\n  URL.revokeObjectURL(url);\n  logAuditEvent('export', `Exported recording: ${RECORDING_STATE.currentRecording.name}`);\n}\n\nfunction importRecordingJSON(input) {\n  const file = input.files[0];\n  if (!file) return;\n  const reader = new FileReader();\n  reader.onload = (e) => {\n    try {\n      const rec = JSON.parse(e.target.result);\n      if (!rec.frames || !Array.isArray(rec.frames)) {\n        throw new Error('Invalid recording format');\n      }\n      rec.id = 'rec-' + Date.now();\n      rec.name = rec.name || 'Imported Recording';\n      RECORDING_STATE.recordings.push(rec);\n      RECORDING_STATE.currentRecording = rec;\n      renderRecordingsList();\n      logAuditEvent('import', `Imported recording: ${rec.name}`);\n      showAlert(t(\"dialogs.recordingImported\", { name: rec.name }));\n    } catch (err) {\n      showAlert(t(\"dialogs.importRecordingFailed\", { error: err.message }));\n    }\n  };\n  reader.readAsText(file);\n  input.value = '';\n}\n\nfunction startVideoRecording() {\n  if (RECORDING_STATE.isVideoRecording) return;\n\n  const svg = document.getElementById('map');\n  const svgRect = svg.getBoundingClientRect();\n  const width = Math.min(1280, svgRect.width);\n  const height = Math.round(width * svgRect.height / svgRect.width);\n\n  const canvas = document.createElement('canvas');\n  canvas.width = width;\n  canvas.height = height;\n  const ctx = canvas.getContext('2d');\n\n  RECORDING_STATE.videoCanvas = canvas;\n  RECORDING_STATE.videoCtx = ctx;\n  RECORDING_STATE.videoChunks = [];\n\n  const stream = canvas.captureStream(30);\n  const mimeType = MediaRecorder.isTypeSupported('video/webm;codecs=vp9') ? 'video/webm;codecs=vp9' :\n                   MediaRecorder.isTypeSupported('video/webm;codecs=vp8') ? 'video/webm;codecs=vp8' :\n                   MediaRecorder.isTypeSupported('video/webm') ? 'video/webm' : 'video/mp4';\n\n  const recorder = new MediaRecorder(stream, { mimeType, videoBitsPerSecond: 10000000 });\n\n  recorder.ondataavailable = (e) => {\n    if (e.data.size > 0) {\n      RECORDING_STATE.videoChunks.push(e.data);\n    }\n  };\n\n  recorder.onstop = () => {\n    const blob = new Blob(RECORDING_STATE.videoChunks, { type: mimeType });\n    const url = URL.createObjectURL(blob);\n    const a = document.createElement('a');\n    a.href = url;\n    const ext = mimeType.includes('webm') ? 'webm' : 'mp4';\n    a.download = `topology-recording-${Date.now()}.${ext}`;\n    a.click();\n    URL.revokeObjectURL(url);\n    logAuditEvent('export', `Exported video recording`);\n  };\n\n  RECORDING_STATE.videoRecorder = recorder;\n  RECORDING_STATE.isVideoRecording = true;\n  recorder.start(100);\n\n  function renderFrame() {\n    if (!RECORDING_STATE.isVideoRecording) return;\n\n    const svg = document.getElementById('map');\n    const svgData = new XMLSerializer().serializeToString(svg);\n    const svgBlob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' });\n    const url = URL.createObjectURL(svgBlob);\n\n    const img = new Image();\n    img.onload = () => {\n      ctx.fillStyle = PAGE_STATE.background || '#050608';\n      ctx.fillRect(0, 0, width, height);\n      ctx.drawImage(img, 0, 0, width, height);\n      URL.revokeObjectURL(url);\n      if (RECORDING_STATE.isVideoRecording) {\n        RECORDING_STATE.videoAnimFrame = requestAnimationFrame(renderFrame);\n      }\n    };\n    img.onerror = () => {\n      URL.revokeObjectURL(url);\n      if (RECORDING_STATE.isVideoRecording) {\n        RECORDING_STATE.videoAnimFrame = requestAnimationFrame(renderFrame);\n      }\n    };\n    img.src = url;\n  }\n\n  renderFrame();\n\n  document.getElementById('record-btn').style.background = '#f56565';\n  document.getElementById('record-btn').style.color = 'white';\n  document.getElementById('stop-btn').style.display = 'inline-block';\n  logAuditEvent('recording', 'Started video recording');\n}\n\nfunction stopVideoRecording() {\n  if (!RECORDING_STATE.isVideoRecording) return;\n\n  RECORDING_STATE.isVideoRecording = false;\n  if (RECORDING_STATE.videoAnimFrame) {\n    cancelAnimationFrame(RECORDING_STATE.videoAnimFrame);\n  }\n  if (RECORDING_STATE.videoRecorder && RECORDING_STATE.videoRecorder.state !== 'inactive') {\n    RECORDING_STATE.videoRecorder.stop();\n  }\n\n  document.getElementById('record-btn').style.background = 'var(--btn-bg)';\n  document.getElementById('record-btn').style.color = '#f56565';\n  document.getElementById('stop-btn').style.display = 'none';\n  logAuditEvent('recording', 'Stopped video recording');\n}\n\nasync function exportRecordingVideo() {\n  if (!RECORDING_STATE.currentRecording || RECORDING_STATE.currentRecording.frames.length < 2) {\n    showAlert(t(\"dialogs.selectRecordingForVideo\"));\n    return;\n  }\n\n  const rec = RECORDING_STATE.currentRecording;\n  const frames = rec.frames;\n  const svg = document.getElementById('map');\n  const viewBox = svg.getAttribute('viewBox').split(' ').map(Number);\n  const width = 1920;\n  const height = Math.round(width * viewBox[3] / viewBox[2]);\n\n  const canvas = document.createElement('canvas');\n  canvas.width = width;\n  canvas.height = height;\n  const ctx = canvas.getContext('2d');\n\n  const progressDiv = document.createElement('div');\n  progressDiv.style.cssText = 'position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);background:var(--panel);padding:20px 40px;border-radius:8px;border:1px solid var(--edge-main);z-index:99999999;text-align:center;';\n  progressDiv.innerHTML = '<div style=\"color:var(--text-main);margin-bottom:10px;\">' + t(\"emptyStates.generatingVideo\") + '</div><div id=\"video-progress\" style=\"color:var(--accent);margin-bottom:8px;\">0%</div><div style=\"color:var(--text-soft);font-size:12px;\">' + t(\"messages.pleaseWait\") + '</div>';\n  document.body.appendChild(progressDiv);\n\n  const stream = canvas.captureStream(30);\n  const mimeType = MediaRecorder.isTypeSupported('video/webm;codecs=vp9') ? 'video/webm;codecs=vp9' :\n                   MediaRecorder.isTypeSupported('video/webm') ? 'video/webm' : 'video/mp4';\n  const chunks = [];\n  const recorder = new MediaRecorder(stream, { mimeType, videoBitsPerSecond: 10000000 });\n\n  recorder.ondataavailable = (e) => {\n    if (e.data.size > 0) chunks.push(e.data);\n  };\n\n  const recorderReady = new Promise(resolve => {\n    recorder.onstop = resolve;\n  });\n\n  recorder.start(100);\n\n  const originalPositions = JSON.parse(JSON.stringify(savedPositions));\n  const originalWaypoints = {};\n  EDGE_DATA.list.forEach(edge => {\n    originalWaypoints[edge.id] = edge.waypoints ? edge.waypoints.map(wp => ({ x: wp.x, y: wp.y })) : [];\n  });\n\n  for (let i = 0; i < frames.length; i++) {\n    const frame = frames[i];\n    Object.keys(frame.positions).forEach(id => {\n      if (savedPositions[id]) {\n        savedPositions[id].x = frame.positions[id].x;\n        savedPositions[id].y = frame.positions[id].y;\n      }\n    });\n    if (frame.waypoints) {\n      EDGE_DATA.list.forEach(edge => {\n        if (frame.waypoints[edge.id]) {\n          edge.waypoints = frame.waypoints[edge.id].map(wp => ({ x: wp.x, y: wp.y }));\n        } else {\n          edge.waypoints = [];\n        }\n      });\n    }\n    forgeTheTopology();\n\n    const svgData = new XMLSerializer().serializeToString(svg);\n    const svgBlob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' });\n    const url = URL.createObjectURL(svgBlob);\n\n    await new Promise((resolve) => {\n      const img = new Image();\n      img.onload = () => {\n        ctx.fillStyle = PAGE_STATE.panel || '#0b0e13';\n        ctx.fillRect(0, 0, width, height);\n        ctx.drawImage(img, 0, 0, width, height);\n        URL.revokeObjectURL(url);\n        document.getElementById('video-progress').textContent = Math.round((i / frames.length) * 100) + '%';\n        resolve();\n      };\n      img.onerror = () => {\n        URL.revokeObjectURL(url);\n        resolve();\n      };\n      img.src = url;\n    });\n\n    const frameDelay = i < frames.length - 1 ? (frames[i + 1].time - frame.time) : 100;\n    await new Promise(r => setTimeout(r, Math.max(33, frameDelay / 2)));\n  }\n\n  recorder.stop();\n  await recorderReady;\n\n  Object.keys(originalPositions).forEach(id => {\n    if (savedPositions[id]) {\n      savedPositions[id].x = originalPositions[id].x;\n      savedPositions[id].y = originalPositions[id].y;\n    }\n  });\n  EDGE_DATA.list.forEach(edge => {\n    if (originalWaypoints[edge.id]) {\n      edge.waypoints = originalWaypoints[edge.id].map(wp => ({ x: wp.x, y: wp.y }));\n    }\n  });\n  forgeTheTopology();\n\n  const blob = new Blob(chunks, { type: mimeType });\n  const url = URL.createObjectURL(blob);\n  const a = document.createElement('a');\n  a.href = url;\n  const ext = mimeType.includes('webm') ? 'webm' : 'mp4';\n  a.download = (rec.name || 'recording').replace(/[^a-z0-9]/gi, '-') + '.' + ext;\n  a.click();\n  URL.revokeObjectURL(url);\n\n  progressDiv.remove();\n  logAuditEvent('export', `Exported video: ${rec.name}`);\n}\n\nfunction exportRecordingGIF() {\n  exportRecordingVideo();\n}\n\ndocument.getElementById('record-btn')?.addEventListener('click', () => {\n  const expanded = document.getElementById('recording-expanded');\n  if (expanded.style.display === 'none') {\n    expanded.style.display = 'inline-flex';\n  } else {\n    startRecording();\n  }\n});\ndocument.getElementById('step-record-btn')?.addEventListener('click', () => {\n  if (RECORDING_STATE.isStepRecording) {\n    captureStepFrame();\n  } else {\n    startStepRecording();\n  }\n});\ndocument.getElementById('stop-btn')?.addEventListener('click', () => {\n  if (RECORDING_STATE.isStepRecording) {\n    stopStepRecording();\n  } else {\n    stopRecording();\n  }\n});\ndocument.getElementById('play-btn')?.addEventListener('click', () => {\n  if (RECORDING_STATE.isRecording) {\n    stopRecording();\n    playRecording();\n    return;\n  }\n  if (RECORDING_STATE.isStepRecording) {\n    stopStepRecording();\n    playRecording();\n    return;\n  }\n  const expanded = document.getElementById('recording-expanded');\n  if (expanded.style.display === 'none') {\n    expanded.style.display = 'inline-flex';\n  } else {\n    playRecording();\n  }\n});\ndocument.getElementById('pause-btn')?.addEventListener('click', pauseRecording);\ndocument.getElementById('playback-scrubber')?.addEventListener('input', (e) => {\n  seekRecording(parseFloat(e.target.value));\n});\ndocument.getElementById('playback-speed')?.addEventListener('change', (e) => {\n  RECORDING_STATE.playbackSpeed = parseFloat(e.target.value);\n});\ndocument.getElementById('loop-playback')?.addEventListener('change', (e) => {\n  RECORDING_STATE.loopPlayback = e.target.checked;\n});\ndocument.getElementById('recordings-btn')?.addEventListener('click', () => {\n  renderRecordingsList();\n  document.getElementById('trail-enabled-toggle').checked = PAGE_STATE.motionTrailsEnabled !== false;\n  document.getElementById('trail-length-slider').value = PAGE_STATE.motionTrailLength || 8;\n  document.getElementById('trail-length-value').textContent = PAGE_STATE.motionTrailLength || 8;\n  document.getElementById('trail-style-select').value = PAGE_STATE.motionTrailStyle || 'solid';\n  document.getElementById('trail-arrow-toggle').checked = PAGE_STATE.motionTrailArrow !== false;\n  document.getElementById('recordings-modal').classList.add('active');\n});\ndocument.getElementById('recordings-modal-close')?.addEventListener('click', () => {\n  document.getElementById('recordings-modal').classList.remove('active');\n});\ndocument.getElementById('recordings-modal')?.addEventListener('click', (e) => {\n  if (e.target.id === 'recordings-modal') e.target.classList.remove('active');\n});\n\ndocument.getElementById('trail-enabled-toggle')?.addEventListener('change', (e) => {\n  PAGE_STATE.motionTrailsEnabled = e.target.checked;\n  if (!e.target.checked) {\n    RECORDING_STATE.trailHistory = {};\n    forgeTheTopology();\n  }\n});\ndocument.getElementById('trail-length-slider')?.addEventListener('input', (e) => {\n  PAGE_STATE.motionTrailLength = parseInt(e.target.value);\n  document.getElementById('trail-length-value').textContent = e.target.value;\n});\ndocument.getElementById('trail-style-select')?.addEventListener('change', (e) => {\n  PAGE_STATE.motionTrailStyle = e.target.value;\n  forgeTheTopology();\n});\ndocument.getElementById('trail-arrow-toggle')?.addEventListener('change', (e) => {\n  PAGE_STATE.motionTrailArrow = e.target.checked;\n  forgeTheTopology();\n});\n\ndocument.getElementById('node-trail-enabled')?.addEventListener('change', (e) => {\n  if (currentNodeId && NODE_DATA[currentNodeId]) {\n    NODE_DATA[currentNodeId].trailEnabled = e.target.checked;\n  }\n});\n\nfunction renderCanvasTemplate(svg, ns, template, width, height, padding, templateColor) {\n  const color = (templateColor || PAGE_STATE.canvasGrid || \"#475569\") + \"66\";\n  const majorColor = (templateColor || PAGE_STATE.canvasGrid || \"#475569\") + \"99\";\n  const w = width - padding * 2;\n  const h = height - padding * 2;\n  const cx = padding + w / 2;\n  const cy = padding + h / 2;\n  const templateGroup = document.createElementNS(ns, \"g\");\n  templateGroup.id = \"canvas-template\";\n\n  switch (template) {\n    case \"dots\": {\n      const spacing = PAGE_STATE.canvasGridSize || 50;\n      for (let x = padding; x <= width - padding; x += spacing) {\n        for (let y = padding; y <= height - padding; y += spacing) {\n          const dot = document.createElementNS(ns, \"circle\");\n          dot.setAttribute(\"cx\", x);\n          dot.setAttribute(\"cy\", y);\n          dot.setAttribute(\"r\", 2);\n          dot.setAttribute(\"fill\", color);\n          templateGroup.appendChild(dot);\n        }\n      }\n      break;\n    }\n    case \"blueprint\": {\n      const spacing = 25;\n      const majorSpacing = 100;\n      for (let x = padding; x <= width - padding; x += spacing) {\n        const line = document.createElementNS(ns, \"line\");\n        line.setAttribute(\"x1\", x);\n        line.setAttribute(\"y1\", padding);\n        line.setAttribute(\"x2\", x);\n        line.setAttribute(\"y2\", height - padding);\n        line.setAttribute(\"stroke\", (x - padding) % majorSpacing === 0 ? majorColor : color);\n        line.setAttribute(\"stroke-width\", (x - padding) % majorSpacing === 0 ? \"1.5\" : \"0.5\");\n        templateGroup.appendChild(line);\n      }\n      for (let y = padding; y <= height - padding; y += spacing) {\n        const line = document.createElementNS(ns, \"line\");\n        line.setAttribute(\"x1\", padding);\n        line.setAttribute(\"y1\", y);\n        line.setAttribute(\"x2\", width - padding);\n        line.setAttribute(\"y2\", y);\n        line.setAttribute(\"stroke\", (y - padding) % majorSpacing === 0 ? majorColor : color);\n        line.setAttribute(\"stroke-width\", (y - padding) % majorSpacing === 0 ? \"1.5\" : \"0.5\");\n        templateGroup.appendChild(line);\n      }\n\n      const border = document.createElementNS(ns, \"rect\");\n      border.setAttribute(\"x\", padding + 20);\n      border.setAttribute(\"y\", padding + 20);\n      border.setAttribute(\"width\", w - 40);\n      border.setAttribute(\"height\", h - 40);\n      border.setAttribute(\"fill\", \"none\");\n      border.setAttribute(\"stroke\", majorColor);\n      border.setAttribute(\"stroke-width\", \"2\");\n      templateGroup.appendChild(border);\n      break;\n    }\n    case \"basketball\": {\n\n      const courtW = Math.min(w * 0.9, h * 0.9 * 1.88);\n      const courtH = courtW / 1.88;\n      const courtX = cx - courtW / 2;\n      const courtY = cy - courtH / 2;\n\n      const court = document.createElementNS(ns, \"rect\");\n      court.setAttribute(\"x\", courtX);\n      court.setAttribute(\"y\", courtY);\n      court.setAttribute(\"width\", courtW);\n      court.setAttribute(\"height\", courtH);\n      court.setAttribute(\"fill\", \"none\");\n      court.setAttribute(\"stroke\", majorColor);\n      court.setAttribute(\"stroke-width\", \"3\");\n      templateGroup.appendChild(court);\n\n      const centerCircle = document.createElementNS(ns, \"circle\");\n      centerCircle.setAttribute(\"cx\", cx);\n      centerCircle.setAttribute(\"cy\", cy);\n      centerCircle.setAttribute(\"r\", courtH * 0.12);\n      centerCircle.setAttribute(\"fill\", \"none\");\n      centerCircle.setAttribute(\"stroke\", color);\n      centerCircle.setAttribute(\"stroke-width\", \"2\");\n      templateGroup.appendChild(centerCircle);\n\n      const centerLine = document.createElementNS(ns, \"line\");\n      centerLine.setAttribute(\"x1\", cx);\n      centerLine.setAttribute(\"y1\", courtY);\n      centerLine.setAttribute(\"x2\", cx);\n      centerLine.setAttribute(\"y2\", courtY + courtH);\n      centerLine.setAttribute(\"stroke\", color);\n      centerLine.setAttribute(\"stroke-width\", \"2\");\n      templateGroup.appendChild(centerLine);\n\n      const keyW = courtW * 0.19;\n      const keyH = courtH * 0.38;\n      [courtX, courtX + courtW - keyW].forEach(kx => {\n        const key = document.createElementNS(ns, \"rect\");\n        key.setAttribute(\"x\", kx);\n        key.setAttribute(\"y\", cy - keyH / 2);\n        key.setAttribute(\"width\", keyW);\n        key.setAttribute(\"height\", keyH);\n        key.setAttribute(\"fill\", \"none\");\n        key.setAttribute(\"stroke\", color);\n        key.setAttribute(\"stroke-width\", \"2\");\n        templateGroup.appendChild(key);\n\n        const ftCircle = document.createElementNS(ns, \"circle\");\n        ftCircle.setAttribute(\"cx\", kx === courtX ? kx + keyW : kx);\n        ftCircle.setAttribute(\"cy\", cy);\n        ftCircle.setAttribute(\"r\", keyH * 0.32);\n        ftCircle.setAttribute(\"fill\", \"none\");\n        ftCircle.setAttribute(\"stroke\", color);\n        ftCircle.setAttribute(\"stroke-width\", \"2\");\n        templateGroup.appendChild(ftCircle);\n      });\n\n      const arcRadius = courtH * 0.48;\n\n      const leftArc = document.createElementNS(ns, \"path\");\n      const leftCenterX = courtX + courtW * 0.055;\n      leftArc.setAttribute(\"d\", `M ${courtX} ${courtY + courtH * 0.06} L ${leftCenterX} ${cy - arcRadius} A ${arcRadius} ${arcRadius} 0 0 1 ${leftCenterX} ${cy + arcRadius} L ${courtX} ${courtY + courtH * 0.94}`);\n      leftArc.setAttribute(\"fill\", \"none\");\n      leftArc.setAttribute(\"stroke\", color);\n      leftArc.setAttribute(\"stroke-width\", \"2\");\n      templateGroup.appendChild(leftArc);\n\n      const rightArc = document.createElementNS(ns, \"path\");\n      const rightCenterX = courtX + courtW * 0.945;\n      rightArc.setAttribute(\"d\", `M ${courtX + courtW} ${courtY + courtH * 0.06} L ${rightCenterX} ${cy - arcRadius} A ${arcRadius} ${arcRadius} 0 0 0 ${rightCenterX} ${cy + arcRadius} L ${courtX + courtW} ${courtY + courtH * 0.94}`);\n      rightArc.setAttribute(\"fill\", \"none\");\n      rightArc.setAttribute(\"stroke\", color);\n      rightArc.setAttribute(\"stroke-width\", \"2\");\n      templateGroup.appendChild(rightArc);\n      break;\n    }\n    case \"football\": {\n      const fieldW = Math.min(w * 0.9, h * 0.9 * 2.25);\n      const fieldH = fieldW / 2.25;\n      const fieldX = cx - fieldW / 2;\n      const fieldY = cy - fieldH / 2;\n      const field = document.createElementNS(ns, \"rect\");\n      field.setAttribute(\"x\", fieldX);\n      field.setAttribute(\"y\", fieldY);\n      field.setAttribute(\"width\", fieldW);\n      field.setAttribute(\"height\", fieldH);\n      field.setAttribute(\"fill\", \"none\");\n      field.setAttribute(\"stroke\", majorColor);\n      field.setAttribute(\"stroke-width\", \"3\");\n      templateGroup.appendChild(field);\n      const endZoneW = fieldW / 12;\n      [fieldX, fieldX + fieldW - endZoneW].forEach(ex => {\n        const ez = document.createElementNS(ns, \"rect\");\n        ez.setAttribute(\"x\", ex);\n        ez.setAttribute(\"y\", fieldY);\n        ez.setAttribute(\"width\", endZoneW);\n        ez.setAttribute(\"height\", fieldH);\n        ez.setAttribute(\"fill\", color);\n        ez.setAttribute(\"fill-opacity\", \"0.2\");\n        ez.setAttribute(\"stroke\", color);\n        ez.setAttribute(\"stroke-width\", \"2\");\n        templateGroup.appendChild(ez);\n      });\n\n      const playFieldW = fieldW - 2 * endZoneW;\n      for (let i = 1; i < 10; i++) {\n        const x = fieldX + endZoneW + (playFieldW * i / 10);\n        const line = document.createElementNS(ns, \"line\");\n        line.setAttribute(\"x1\", x);\n        line.setAttribute(\"y1\", fieldY);\n        line.setAttribute(\"x2\", x);\n        line.setAttribute(\"y2\", fieldY + fieldH);\n        line.setAttribute(\"stroke\", color);\n        line.setAttribute(\"stroke-width\", i === 5 ? \"3\" : \"1.5\");\n        templateGroup.appendChild(line);\n      }\n\n      const hashY1 = fieldY + fieldH * 0.3;\n      const hashY2 = fieldY + fieldH * 0.7;\n      for (let i = 0; i <= 100; i += 5) {\n        const x = fieldX + endZoneW + (playFieldW * i / 100);\n        [hashY1, hashY2].forEach(hy => {\n          const hash = document.createElementNS(ns, \"line\");\n          hash.setAttribute(\"x1\", x - 5);\n          hash.setAttribute(\"y1\", hy);\n          hash.setAttribute(\"x2\", x + 5);\n          hash.setAttribute(\"y2\", hy);\n          hash.setAttribute(\"stroke\", color);\n          hash.setAttribute(\"stroke-width\", \"1\");\n          templateGroup.appendChild(hash);\n        });\n      }\n      break;\n    }\n    case \"soccer\": {\n\n      const pitchW = Math.min(w * 0.9, h * 0.9 * 1.54);\n      const pitchH = pitchW / 1.54;\n      const pitchX = cx - pitchW / 2;\n      const pitchY = cy - pitchH / 2;\n\n      const pitch = document.createElementNS(ns, \"rect\");\n      pitch.setAttribute(\"x\", pitchX);\n      pitch.setAttribute(\"y\", pitchY);\n      pitch.setAttribute(\"width\", pitchW);\n      pitch.setAttribute(\"height\", pitchH);\n      pitch.setAttribute(\"fill\", \"none\");\n      pitch.setAttribute(\"stroke\", majorColor);\n      pitch.setAttribute(\"stroke-width\", \"3\");\n      templateGroup.appendChild(pitch);\n\n      const centerLine = document.createElementNS(ns, \"line\");\n      centerLine.setAttribute(\"x1\", cx);\n      centerLine.setAttribute(\"y1\", pitchY);\n      centerLine.setAttribute(\"x2\", cx);\n      centerLine.setAttribute(\"y2\", pitchY + pitchH);\n      centerLine.setAttribute(\"stroke\", color);\n      centerLine.setAttribute(\"stroke-width\", \"2\");\n      templateGroup.appendChild(centerLine);\n\n      const centerCircle = document.createElementNS(ns, \"circle\");\n      centerCircle.setAttribute(\"cx\", cx);\n      centerCircle.setAttribute(\"cy\", cy);\n      centerCircle.setAttribute(\"r\", pitchH * 0.14);\n      centerCircle.setAttribute(\"fill\", \"none\");\n      centerCircle.setAttribute(\"stroke\", color);\n      centerCircle.setAttribute(\"stroke-width\", \"2\");\n      templateGroup.appendChild(centerCircle);\n\n      const penW = pitchW * 0.157;\n      const penH = pitchH * 0.62;\n      [pitchX, pitchX + pitchW - penW].forEach(px => {\n        const pen = document.createElementNS(ns, \"rect\");\n        pen.setAttribute(\"x\", px);\n        pen.setAttribute(\"y\", cy - penH / 2);\n        pen.setAttribute(\"width\", penW);\n        pen.setAttribute(\"height\", penH);\n        pen.setAttribute(\"fill\", \"none\");\n        pen.setAttribute(\"stroke\", color);\n        pen.setAttribute(\"stroke-width\", \"2\");\n        templateGroup.appendChild(pen);\n\n        const goalW = penW * 0.35;\n        const goalH = penH * 0.45;\n        const goal = document.createElementNS(ns, \"rect\");\n        goal.setAttribute(\"x\", px === pitchX ? px : px + penW - goalW);\n        goal.setAttribute(\"y\", cy - goalH / 2);\n        goal.setAttribute(\"width\", goalW);\n        goal.setAttribute(\"height\", goalH);\n        goal.setAttribute(\"fill\", \"none\");\n        goal.setAttribute(\"stroke\", color);\n        goal.setAttribute(\"stroke-width\", \"2\");\n        templateGroup.appendChild(goal);\n      });\n      break;\n    }\n    case \"hockey\": {\n\n      const rinkW = Math.min(w * 0.9, h * 0.9 * 2.35);\n      const rinkH = rinkW / 2.35;\n      const rinkX = cx - rinkW / 2;\n      const rinkY = cy - rinkH / 2;\n      const cornerR = rinkH * 0.33;\n\n      const rink = document.createElementNS(ns, \"rect\");\n      rink.setAttribute(\"x\", rinkX);\n      rink.setAttribute(\"y\", rinkY);\n      rink.setAttribute(\"width\", rinkW);\n      rink.setAttribute(\"height\", rinkH);\n      rink.setAttribute(\"rx\", cornerR);\n      rink.setAttribute(\"fill\", \"none\");\n      rink.setAttribute(\"stroke\", majorColor);\n      rink.setAttribute(\"stroke-width\", \"3\");\n      templateGroup.appendChild(rink);\n\n      const centerLine = document.createElementNS(ns, \"line\");\n      centerLine.setAttribute(\"x1\", cx);\n      centerLine.setAttribute(\"y1\", rinkY);\n      centerLine.setAttribute(\"x2\", cx);\n      centerLine.setAttribute(\"y2\", rinkY + rinkH);\n      centerLine.setAttribute(\"stroke\", \"#cc3333\" + \"99\");\n      centerLine.setAttribute(\"stroke-width\", \"3\");\n      templateGroup.appendChild(centerLine);\n\n      const blueLineOffset = rinkW * 0.25;\n      [cx - blueLineOffset, cx + blueLineOffset].forEach(bx => {\n        const blueLine = document.createElementNS(ns, \"line\");\n        blueLine.setAttribute(\"x1\", bx);\n        blueLine.setAttribute(\"y1\", rinkY);\n        blueLine.setAttribute(\"x2\", bx);\n        blueLine.setAttribute(\"y2\", rinkY + rinkH);\n        blueLine.setAttribute(\"stroke\", \"#3366cc\" + \"99\");\n        blueLine.setAttribute(\"stroke-width\", \"3\");\n        templateGroup.appendChild(blueLine);\n      });\n\n      const centerCircle = document.createElementNS(ns, \"circle\");\n      centerCircle.setAttribute(\"cx\", cx);\n      centerCircle.setAttribute(\"cy\", cy);\n      centerCircle.setAttribute(\"r\", rinkH * 0.18);\n      centerCircle.setAttribute(\"fill\", \"none\");\n      centerCircle.setAttribute(\"stroke\", \"#3366cc\" + \"99\");\n      centerCircle.setAttribute(\"stroke-width\", \"2\");\n      templateGroup.appendChild(centerCircle);\n\n      const faceoffR = rinkH * 0.18;\n      const faceoffPositions = [\n        [rinkX + rinkW * 0.15, cy - rinkH * 0.25],\n        [rinkX + rinkW * 0.15, cy + rinkH * 0.25],\n        [rinkX + rinkW * 0.85, cy - rinkH * 0.25],\n        [rinkX + rinkW * 0.85, cy + rinkH * 0.25]\n      ];\n      faceoffPositions.forEach(([fx, fy]) => {\n        const circle = document.createElementNS(ns, \"circle\");\n        circle.setAttribute(\"cx\", fx);\n        circle.setAttribute(\"cy\", fy);\n        circle.setAttribute(\"r\", faceoffR);\n        circle.setAttribute(\"fill\", \"none\");\n        circle.setAttribute(\"stroke\", \"#cc3333\" + \"99\");\n        circle.setAttribute(\"stroke-width\", \"2\");\n        templateGroup.appendChild(circle);\n      });\n      [rinkX + rinkW * 0.045, rinkX + rinkW * 0.955].forEach((gx, i) => {\n        const crease = document.createElementNS(ns, \"path\");\n        const creaseR = rinkH * 0.08;\n        if (i === 0) {\n          crease.setAttribute(\"d\", `M ${gx} ${cy - creaseR} A ${creaseR} ${creaseR} 0 0 1 ${gx} ${cy + creaseR}`);\n        } else {\n          crease.setAttribute(\"d\", `M ${gx} ${cy - creaseR} A ${creaseR} ${creaseR} 0 0 0 ${gx} ${cy + creaseR}`);\n        }\n        crease.setAttribute(\"fill\", \"#3366cc\" + \"33\");\n        crease.setAttribute(\"stroke\", \"#cc3333\" + \"99\");\n        crease.setAttribute(\"stroke-width\", \"2\");\n        templateGroup.appendChild(crease);\n      });\n      break;\n    }\n    case \"baseball\": {\n      const diamondSize = Math.min(w * 0.7, h * 0.7);\n      const baseX = cx;\n      const baseY = cy + diamondSize * 0.3;\n      const baseDistance = diamondSize * 0.35;\n      const diamond = document.createElementNS(ns, \"polygon\");\n      const homeX = baseX, homeY = baseY;\n      const firstX = baseX + baseDistance, firstY = baseY - baseDistance;\n      const secondX = baseX, secondY = baseY - baseDistance * 2;\n      const thirdX = baseX - baseDistance, thirdY = baseY - baseDistance;\n      diamond.setAttribute(\"points\", `${homeX},${homeY} ${firstX},${firstY} ${secondX},${secondY} ${thirdX},${thirdY}`);\n      diamond.setAttribute(\"fill\", \"none\");\n      diamond.setAttribute(\"stroke\", color);\n      diamond.setAttribute(\"stroke-width\", \"2\");\n      templateGroup.appendChild(diamond);\n      [[homeX, homeY], [firstX, firstY], [secondX, secondY], [thirdX, thirdY]].forEach(([bx, by], i) => {\n        const base = document.createElementNS(ns, \"rect\");\n        const baseSize = i === 0 ? 15 : 12;\n        base.setAttribute(\"x\", bx - baseSize / 2);\n        base.setAttribute(\"y\", by - baseSize / 2);\n        base.setAttribute(\"width\", baseSize);\n        base.setAttribute(\"height\", baseSize);\n        base.setAttribute(\"transform\", `rotate(45 ${bx} ${by})`);\n        base.setAttribute(\"fill\", i === 0 ? majorColor : color);\n        base.setAttribute(\"stroke\", majorColor);\n        base.setAttribute(\"stroke-width\", \"2\");\n        templateGroup.appendChild(base);\n      });\n\n      const moundX = baseX;\n      const moundY = baseY - baseDistance * 0.67;\n      const mound = document.createElementNS(ns, \"circle\");\n      mound.setAttribute(\"cx\", moundX);\n      mound.setAttribute(\"cy\", moundY);\n      mound.setAttribute(\"r\", 20);\n      mound.setAttribute(\"fill\", color);\n      mound.setAttribute(\"fill-opacity\", \"0.3\");\n      mound.setAttribute(\"stroke\", color);\n      mound.setAttribute(\"stroke-width\", \"2\");\n      templateGroup.appendChild(mound);\n      const outfieldR = diamondSize * 0.6;\n      const outfield = document.createElementNS(ns, \"path\");\n      outfield.setAttribute(\"d\", `M ${baseX - outfieldR * 0.95} ${baseY - outfieldR * 0.3} A ${outfieldR} ${outfieldR} 0 0 1 ${baseX + outfieldR * 0.95} ${baseY - outfieldR * 0.3}`);\n      outfield.setAttribute(\"fill\", \"none\");\n      outfield.setAttribute(\"stroke\", color);\n      outfield.setAttribute(\"stroke-width\", \"2\");\n      outfield.setAttribute(\"stroke-dasharray\", \"10,5\");\n      templateGroup.appendChild(outfield);\n      const foulLength = diamondSize * 0.8;\n      [[-1, -1], [1, -1]].forEach(([dx, dy]) => {\n        const foul = document.createElementNS(ns, \"line\");\n        foul.setAttribute(\"x1\", homeX);\n        foul.setAttribute(\"y1\", homeY);\n        foul.setAttribute(\"x2\", homeX + dx * foulLength);\n        foul.setAttribute(\"y2\", homeY + dy * foulLength * 0.7);\n        foul.setAttribute(\"stroke\", color);\n        foul.setAttribute(\"stroke-width\", \"2\");\n        templateGroup.appendChild(foul);\n      });\n      break;\n    }\n    case \"tennis\": {\n      const courtW = Math.min(w * 0.9, h * 0.9 * 2.17);\n      const courtH = courtW / 2.17;\n      const courtX = cx - courtW / 2;\n      const courtY = cy - courtH / 2;\n      const court = document.createElementNS(ns, \"rect\");\n      court.setAttribute(\"x\", courtX);\n      court.setAttribute(\"y\", courtY);\n      court.setAttribute(\"width\", courtW);\n      court.setAttribute(\"height\", courtH);\n      court.setAttribute(\"fill\", \"none\");\n      court.setAttribute(\"stroke\", majorColor);\n      court.setAttribute(\"stroke-width\", \"3\");\n      templateGroup.appendChild(court);\n\n      const singlesInset = courtH * 0.125;\n      const singles = document.createElementNS(ns, \"rect\");\n      singles.setAttribute(\"x\", courtX);\n      singles.setAttribute(\"y\", courtY + singlesInset);\n      singles.setAttribute(\"width\", courtW);\n      singles.setAttribute(\"height\", courtH - singlesInset * 2);\n      singles.setAttribute(\"fill\", \"none\");\n      singles.setAttribute(\"stroke\", color);\n      singles.setAttribute(\"stroke-width\", \"2\");\n      templateGroup.appendChild(singles);\n\n      const net = document.createElementNS(ns, \"line\");\n      net.setAttribute(\"x1\", cx);\n      net.setAttribute(\"y1\", courtY);\n      net.setAttribute(\"x2\", cx);\n      net.setAttribute(\"y2\", courtY + courtH);\n      net.setAttribute(\"stroke\", majorColor);\n      net.setAttribute(\"stroke-width\", \"3\");\n      templateGroup.appendChild(net);\n\n      const serviceW = courtW * 0.27;\n      const serviceH = (courtH - singlesInset * 2) / 2;\n      [courtX, courtX + courtW - serviceW].forEach(sx => {\n\n        const topBox = document.createElementNS(ns, \"rect\");\n        topBox.setAttribute(\"x\", sx === courtX ? cx - serviceW : cx);\n        topBox.setAttribute(\"y\", courtY + singlesInset);\n        topBox.setAttribute(\"width\", serviceW);\n        topBox.setAttribute(\"height\", serviceH);\n        topBox.setAttribute(\"fill\", \"none\");\n        topBox.setAttribute(\"stroke\", color);\n        topBox.setAttribute(\"stroke-width\", \"2\");\n        templateGroup.appendChild(topBox);\n\n        const botBox = document.createElementNS(ns, \"rect\");\n        botBox.setAttribute(\"x\", sx === courtX ? cx - serviceW : cx);\n        botBox.setAttribute(\"y\", cy);\n        botBox.setAttribute(\"width\", serviceW);\n        botBox.setAttribute(\"height\", serviceH);\n        botBox.setAttribute(\"fill\", \"none\");\n        botBox.setAttribute(\"stroke\", color);\n        botBox.setAttribute(\"stroke-width\", \"2\");\n        templateGroup.appendChild(botBox);\n      });\n      const centerService = document.createElementNS(ns, \"line\");\n      centerService.setAttribute(\"x1\", cx - serviceW);\n      centerService.setAttribute(\"y1\", cy);\n      centerService.setAttribute(\"x2\", cx + serviceW);\n      centerService.setAttribute(\"y2\", cy);\n      centerService.setAttribute(\"stroke\", color);\n      centerService.setAttribute(\"stroke-width\", \"2\");\n      templateGroup.appendChild(centerService);\n      [courtX, courtX + courtW].forEach(bx => {\n        const mark = document.createElementNS(ns, \"line\");\n        mark.setAttribute(\"x1\", bx);\n        mark.setAttribute(\"y1\", cy - 10);\n        mark.setAttribute(\"x2\", bx);\n        mark.setAttribute(\"y2\", cy + 10);\n        mark.setAttribute(\"stroke\", color);\n        mark.setAttribute(\"stroke-width\", \"2\");\n        templateGroup.appendChild(mark);\n      });\n      break;\n    }\n    case \"none\":\n    default:\n      break;\n  }\n  return templateGroup;\n}\n\nfunction updateCanvasStyleOptions() {\n  const mode = PAGE_STATE.mappingMode || 'network';\n  const canvasStyleRow = document.getElementById('canvas-style-row');\n  const canvasStyleSelect = document.getElementById('canvas-style-select');\n  const canvasGridCheckbox = document.getElementById('canvas-grid-enabled');\n\n  if (mode === 'mindmap') {\n    if (canvasStyleRow) canvasStyleRow.style.display = 'none';\n    PAGE_STATE.canvasTemplate = 'none';\n    PAGE_STATE.canvasGridEnabled = false;\n    if (canvasGridCheckbox) canvasGridCheckbox.checked = false;\n  } else if (mode === 'floorplan') {\n    if (canvasStyleRow) canvasStyleRow.style.display = 'none';\n    PAGE_STATE.canvasTemplate = 'blueprint';\n    PAGE_STATE.canvasGridEnabled = true;\n    if (canvasGridCheckbox) canvasGridCheckbox.checked = true;\n  } else {\n    if (canvasStyleRow) canvasStyleRow.style.display = 'contents';\n    const options = CANVAS_OPTIONS[mode] || CANVAS_OPTIONS.network;\n    if (canvasStyleSelect && options.length > 0) {\n      canvasStyleSelect.innerHTML = options.map(o =>\n        `<option value=\"${o.value}\">${o.label}</option>`\n      ).join('');\n      const validValues = options.map(o => o.value);\n      if (!validValues.includes(PAGE_STATE.canvasTemplate)) {\n        PAGE_STATE.canvasTemplate = options[0]?.value || 'grid';\n      }\n      canvasStyleSelect.value = PAGE_STATE.canvasTemplate;\n      PAGE_STATE.canvasGridEnabled = PAGE_STATE.canvasTemplate !== 'none';\n      if (canvasGridCheckbox) canvasGridCheckbox.checked = PAGE_STATE.canvasGridEnabled;\n    }\n  }\n  forgeTheTopology();\n}\n\nfunction updateShapeCategoryOptions() {\n  const mode = PAGE_STATE.mappingMode || 'network';\n  const defaultCat = MODE_DEFAULT_CATEGORIES[mode] || 'network';\n  const catSelect = document.getElementById('shape-category-select');\n  if (catSelect && !catSelect.value) {\n    catSelect.value = defaultCat;\n  }\n  populateShapeSelect();\n}\n\nfunction populateShapeSelect(currentValue) {\n  const catSelect = document.getElementById('shape-category-select');\n  const shapeSelect = document.getElementById('shape-select');\n  if (!catSelect || !shapeSelect) return;\n\n  const category = catSelect.value || 'basic';\n  const shapes = SHAPE_CATEGORIES[category] || SHAPE_CATEGORIES.basic;\n\n  shapeSelect.innerHTML = shapes.map(s =>\n    `<option value=\"${s.value}\">${s.label}</option>`\n  ).join('');\n\n  if (currentValue) {\n    const exists = shapes.some(s => s.value === currentValue);\n    if (exists) {\n      shapeSelect.value = currentValue;\n    }\n  }\n}\n\ndocument.getElementById('mapping-mode-select')?.addEventListener('change', (e) => {\n  PAGE_STATE.mappingMode = e.target.value;\n  updateCanvasStyleOptions();\n  applyMappingModeLabels();\n  updateLayerLabels();\n  const catSelect = document.getElementById('shape-category-select');\n  if (catSelect) {\n    catSelect.value = MODE_DEFAULT_CATEGORIES[e.target.value] || 'network';\n    populateShapeSelect();\n  }\n  wieldThePower();\n  displayTabs();\n});\n\ndocument.getElementById('canvas-style-select')?.addEventListener('change', (e) => {\n  PAGE_STATE.canvasTemplate = e.target.value;\n  PAGE_STATE.canvasGridEnabled = e.target.value !== 'none';\n  forgeTheTopology();\n});\n\ndocument.getElementById('shape-category-select')?.addEventListener('change', (e) => {\n  populateShapeSelect();\n});\n\ndocument.getElementById('shape-select')?.addEventListener('change', (e) => {\n  const shape = e.target.value || 'circle';\n  if (!currentNodeId || !NODE_DATA[currentNodeId]) return;\n  pushUndo('change shape');\n  NODE_DATA[currentNodeId].shape = shape;\n  const fovSection = document.getElementById(\"fov-section\");\n  if (fovSection) {\n    if (typeof hasCoverageZone === 'function' && hasCoverageZone(shape)) {\n      const defaults = getCoverageDefaults(shape);\n      fovSection.style.display = \"block\";\n      document.getElementById(\"fov-angle\").value = defaults.angle;\n      document.getElementById(\"fov-angle-value\").textContent = defaults.angle + \"°\";\n      document.getElementById(\"fov-distance\").value = defaults.distance;\n      document.getElementById(\"fov-distance-value\").textContent = defaults.distance;\n      document.getElementById(\"fov-animation-type\").value = defaults.animationType;\n    } else {\n      fovSection.style.display = \"none\";\n    }\n  }\n  const nodeGroup = document.querySelector(`g[data-node-id=\"${currentNodeId}\"]`);\n  if (nodeGroup) {\n    const oldShape = nodeGroup.querySelector(\".node-circle\");\n    if (oldShape) oldShape.remove();\n    const size = savedSizes[currentNodeId] || getDefaultSize();\n    const newShape = createNodeShape(currentNodeId, size);\n    nodeGroup.insertBefore(newShape, nodeGroup.firstChild);\n  }\n});\n\nfunction applyMappingModeLabels() {\n  const mode = PAGE_STATE.mappingMode || 'network';\n  const labels = LANG.modes[mode] || LANG.modes.network;\n\n  const addNodeBtn = document.getElementById('add-node-btn');\n  const addRackBtn = document.getElementById('add-rack-btn');\n  if (addNodeBtn) addNodeBtn.textContent = '+ ' + labels.node;\n  if (addRackBtn) {\n    if (!labels.rack) {\n      addRackBtn.style.display = 'none';\n    } else {\n      addRackBtn.style.display = '';\n      addRackBtn.textContent = '+ ' + labels.rack;\n    }\n  }\n\n  const nodeIpLabel = document.getElementById('new-node-ip-label');\n  const nodeTagsLabel = document.getElementById('new-node-tags-label');\n  const nodeIpInput = document.getElementById('new-node-ip');\n  const nodeTagsInput = document.getElementById('new-node-tags');\n  if (nodeIpLabel && labels.subtitle) nodeIpLabel.textContent = labels.subtitle;\n  if (nodeTagsLabel && labels.tags) nodeTagsLabel.textContent = labels.tags + ' (comma separated)';\n  if (nodeIpInput && labels.subtitlePlaceholder) nodeIpInput.placeholder = labels.subtitlePlaceholder;\n  if (nodeTagsInput && labels.tagsPlaceholder) nodeTagsInput.placeholder = labels.tagsPlaceholder;\n\n  const rackNameLabel = document.getElementById('new-rack-name-label');\n  const rackIpLabel = document.getElementById('new-rack-ip-label');\n  const rackTagsLabel = document.getElementById('new-rack-tags-label');\n  const rackIpInput = document.getElementById('new-rack-ip');\n  const rackTagsInput = document.getElementById('new-rack-tags');\n  if (rackNameLabel && labels.rack) rackNameLabel.textContent = labels.rack + ' Name';\n  if (rackIpLabel && labels.rackSubtitle) rackIpLabel.textContent = labels.rackSubtitle;\n  if (rackTagsLabel && labels.rackTags) rackTagsLabel.textContent = labels.rackTags + ' (comma separated)';\n  if (rackIpInput && labels.rackSubtitlePlaceholder) rackIpInput.placeholder = labels.rackSubtitlePlaceholder;\n  if (rackTagsInput && labels.rackTagsPlaceholder) rackTagsInput.placeholder = labels.rackTagsPlaceholder;\n\n  const nodeCatSelect = document.getElementById('new-node-category');\n  const rackCatSelect = document.getElementById('new-rack-category');\n  const defaultCat = MODE_DEFAULT_CATEGORIES[mode] || 'basic';\n  if (nodeCatSelect) {\n    nodeCatSelect.value = defaultCat;\n    populateModalShapeSelect('new-node-category', 'new-node-shape');\n  }\n  if (rackCatSelect) {\n    rackCatSelect.value = defaultCat;\n    populateModalShapeSelect('new-rack-category', 'new-rack-shape');\n  }\n\n  const nodeAppearanceLabel = document.getElementById('new-node-appearance-label');\n  const rackAppearanceLabel = document.getElementById('new-rack-appearance-label');\n  if (nodeAppearanceLabel) nodeAppearanceLabel.textContent = labels.node + ' Appearance';\n  if (rackAppearanceLabel && labels.rack) rackAppearanceLabel.textContent = labels.rack + ' Appearance';\n\n  const isNetworkMode = mode === 'network';\n  const macRow = document.getElementById('mac-row');\n  const rackUnitRow = document.getElementById('rack-unit-row');\n  const assignedRackLabel = document.getElementById('assigned-rack-label');\n  if (macRow) macRow.style.display = isNetworkMode ? 'flex' : 'none';\n  if (rackUnitRow) rackUnitRow.style.display = isNetworkMode ? 'flex' : 'none';\n  if (assignedRackLabel && labels.rack) assignedRackLabel.textContent = 'Assigned ' + labels.rack + ':';\n\n  const showPing = mode === 'network' || mode === 'smarthome';\n  const newNodePingSection = document.getElementById('new-node-ping-section');\n  const nodePingSection = document.getElementById('node-ping-section');\n  if (newNodePingSection) newNodePingSection.style.display = showPing ? 'block' : 'none';\n  if (nodePingSection) nodePingSection.style.display = showPing ? 'block' : 'none';\n}\n\nfunction updateLayerLabels() {\n  const mode = PAGE_STATE.mappingMode || 'network';\n  const layers = LANG.ui?.layers?.[mode] || LANG.ui?.layers?.network;\n  if (layers) {\n    const label1 = document.getElementById('layer-1-label');\n    const label2 = document.getElementById('layer-2-label');\n    const label3 = document.getElementById('layer-3-label');\n    const label4 = document.getElementById('layer-4-label');\n    if (label1) label1.textContent = layers.layer1 || 'Layer 1';\n    if (label2) label2.textContent = layers.layer2 || 'Layer 2';\n    if (label3) label3.textContent = layers.layer3 || 'Layer 3';\n    if (label4) label4.textContent = layers.layer4 || 'Layer 4';\n    const opt1 = document.getElementById('node-layer-opt1');\n    const opt2 = document.getElementById('node-layer-opt2');\n    const opt3 = document.getElementById('node-layer-opt3');\n    const opt4 = document.getElementById('node-layer-opt4');\n    if (opt1) opt1.textContent = layers.layer1 || 'Layer 1';\n    if (opt2) opt2.textContent = layers.layer2 || 'Layer 2';\n    if (opt3) opt3.textContent = layers.layer3 || 'Layer 3';\n    if (opt4) opt4.textContent = layers.layer4 || 'Layer 4';\n  }\n}\n\nsetTimeout(() => {\n  const catSelect = document.getElementById('shape-category-select');\n  if (catSelect) {\n    catSelect.value = MODE_DEFAULT_CATEGORIES[PAGE_STATE.mappingMode || 'network'] || 'network';\n    populateShapeSelect();\n  }\n  updateCanvasStyleOptions();\n  updateLayerLabels();\n}, 100);\n\nconst MODAL_SHAPE_PREVIEWS = {\n  'circle': '<circle cx=\"16\" cy=\"16\" r=\"11\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"/>',\n  'square': '<rect x=\"5\" y=\"5\" width=\"22\" height=\"22\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"/>',\n  'rectangle': '<rect x=\"3\" y=\"8\" width=\"26\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"/>',\n  'triangle': '<polygon points=\"16,4 28,28 4,28\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"/>',\n  'hexagon': '<polygon points=\"16,3 27,9 27,22 16,28 5,22 5,9\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"/>',\n  'diamond': '<polygon points=\"16,3 29,16 16,29 3,16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"/>',\n  'star': '<polygon points=\"16,3 19,12 29,12 21,18 24,28 16,22 8,28 11,18 3,12 13,12\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"/>',\n  'stop-sign': '<polygon points=\"11,3 21,3 29,11 29,21 21,29 11,29 3,21 3,11\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"/>',\n  'octagon': '<polygon points=\"11,3 21,3 29,11 29,21 21,29 11,29 3,21 3,11\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"/>',\n  'pentagon': '<polygon points=\"16,3 29,13 24,28 8,28 3,13\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"/>',\n  'cross': '<path d=\"M12,4 h8 v8 h8 v8 h-8 v8 h-8 v-8 h-8 v-8 h8 z\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"/>',\n  'rounded-square': '<rect x=\"5\" y=\"5\" width=\"22\" height=\"22\" rx=\"5\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"/>',\n  'pill': '<rect x=\"4\" y=\"10\" width=\"24\" height=\"12\" rx=\"6\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"/>',\n  'parallelogram': '<polygon points=\"8,6 28,6 24,26 4,26\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"/>',\n  'trapezoid': '<polygon points=\"8,6 24,6 28,26 4,26\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"/>',\n  'server': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"6\" y=\"4\" width=\"20\" height=\"24\" rx=\"2\"/><line x1=\"6\" y1=\"12\" x2=\"26\" y2=\"12\"/><line x1=\"6\" y1=\"20\" x2=\"26\" y2=\"20\"/><circle cx=\"22\" cy=\"8\" r=\"1.5\" fill=\"currentColor\"/><circle cx=\"22\" cy=\"16\" r=\"1.5\" fill=\"currentColor\"/><circle cx=\"22\" cy=\"24\" r=\"1.5\" fill=\"currentColor\"/></g>',\n  'pc': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"5\" y=\"4\" width=\"22\" height=\"16\" rx=\"1\"/><line x1=\"12\" y1=\"24\" x2=\"20\" y2=\"24\"/><line x1=\"16\" y1=\"20\" x2=\"16\" y2=\"24\"/></g>',\n  'laptop': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"6\" y=\"6\" width=\"20\" height=\"14\" rx=\"1\"/><path d=\"M3,22 h26 l-2,4 h-22 z\"/></g>',\n  'phone': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"9\" y=\"3\" width=\"14\" height=\"26\" rx=\"2\"/><line x1=\"13\" y1=\"25\" x2=\"19\" y2=\"25\"/></g>',\n  'printer': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"8\" y=\"12\" width=\"16\" height=\"10\" rx=\"1\"/><rect x=\"10\" y=\"4\" width=\"12\" height=\"8\"/><rect x=\"10\" y=\"22\" width=\"12\" height=\"6\"/></g>',\n  'pi': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"5\" y=\"8\" width=\"22\" height=\"16\" rx=\"2\"/><circle cx=\"10\" cy=\"13\" r=\"1.5\" fill=\"currentColor\"/><rect x=\"18\" y=\"11\" width=\"6\" height=\"4\" rx=\"0.5\"/><line x1=\"14\" y1=\"20\" x2=\"22\" y2=\"20\"/></g>',\n  'sensor': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><circle cx=\"16\" cy=\"16\" r=\"6\"/><circle cx=\"16\" cy=\"16\" r=\"2\" fill=\"currentColor\"/><path d=\"M8,8 Q4,16 8,24\"/><path d=\"M24,8 Q28,16 24,24\"/></g>',\n  'router': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"4\" y=\"14\" width=\"24\" height=\"12\" rx=\"2\"/><line x1=\"10\" y1=\"14\" x2=\"10\" y2=\"8\"/><line x1=\"16\" y1=\"14\" x2=\"16\" y2=\"6\"/><line x1=\"22\" y1=\"14\" x2=\"22\" y2=\"8\"/><circle cx=\"10\" cy=\"7\" r=\"1.5\" fill=\"currentColor\"/><circle cx=\"16\" cy=\"5\" r=\"1.5\" fill=\"currentColor\"/><circle cx=\"22\" cy=\"7\" r=\"1.5\" fill=\"currentColor\"/></g>',\n  'switch': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"3\" y=\"11\" width=\"26\" height=\"10\" rx=\"2\"/><circle cx=\"8\" cy=\"16\" r=\"1.5\" fill=\"currentColor\"/><circle cx=\"13\" cy=\"16\" r=\"1.5\" fill=\"currentColor\"/><circle cx=\"18\" cy=\"16\" r=\"1.5\" fill=\"currentColor\"/><circle cx=\"23\" cy=\"16\" r=\"1.5\" fill=\"currentColor\"/></g>',\n  'firewall': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"5\" y=\"5\" width=\"22\" height=\"22\" rx=\"1\"/><line x1=\"5\" y1=\"11\" x2=\"27\" y2=\"11\"/><line x1=\"5\" y1=\"17\" x2=\"27\" y2=\"17\"/><line x1=\"11\" y1=\"5\" x2=\"11\" y2=\"27\"/><line x1=\"17\" y1=\"5\" x2=\"17\" y2=\"27\"/></g>',\n  'access-point': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><path d=\"M16,18 L10,26 h12 z\"/><circle cx=\"16\" cy=\"16\" r=\"2\" fill=\"currentColor\"/><path d=\"M9,10 Q16,4 23,10\" stroke-linecap=\"round\"/><path d=\"M6,7 Q16,0 26,7\" stroke-linecap=\"round\"/></g>',\n  'load-balancer': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"6\" y=\"12\" width=\"20\" height=\"8\" rx=\"2\"/><line x1=\"16\" y1=\"8\" x2=\"16\" y2=\"12\"/><line x1=\"16\" y1=\"20\" x2=\"8\" y2=\"26\"/><line x1=\"16\" y1=\"20\" x2=\"16\" y2=\"26\"/><line x1=\"16\" y1=\"20\" x2=\"24\" y2=\"26\"/><circle cx=\"16\" cy=\"7\" r=\"2\" fill=\"currentColor\"/></g>',\n  'gateway': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"4\" y=\"10\" width=\"24\" height=\"12\" rx=\"2\"/><line x1=\"4\" y1=\"16\" x2=\"0\" y2=\"16\"/><line x1=\"28\" y1=\"16\" x2=\"32\" y2=\"16\"/><circle cx=\"16\" cy=\"16\" r=\"3\"/></g>',\n  'vpn': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"5\" y=\"8\" width=\"22\" height=\"16\" rx=\"2\"/><path d=\"M16,12 v6 M13,14 h6\" stroke-width=\"2.5\" stroke-linecap=\"round\"/><circle cx=\"11\" cy=\"20\" r=\"1\" fill=\"currentColor\"/><circle cx=\"21\" cy=\"20\" r=\"1\" fill=\"currentColor\"/></g>',\n  'nas': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"6\" y=\"4\" width=\"20\" height=\"24\" rx=\"2\"/><line x1=\"6\" y1=\"10\" x2=\"26\" y2=\"10\"/><line x1=\"6\" y1=\"16\" x2=\"26\" y2=\"16\"/><line x1=\"6\" y1=\"22\" x2=\"26\" y2=\"22\"/><circle cx=\"22\" cy=\"7\" r=\"1\" fill=\"currentColor\"/><circle cx=\"22\" cy=\"13\" r=\"1\" fill=\"currentColor\"/><circle cx=\"22\" cy=\"19\" r=\"1\" fill=\"currentColor\"/><circle cx=\"22\" cy=\"25\" r=\"1\" fill=\"currentColor\"/></g>',\n  'cloud': '<path d=\"M8,22 Q2,22 2,17 Q2,13 6,12 Q6,7 11,6 Q16,5 19,8 Q21,6 24,7 Q28,8 28,12 Q30,13 30,17 Q30,22 24,22 z\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"/>',\n  'database': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><ellipse cx=\"16\" cy=\"8\" rx=\"10\" ry=\"4\"/><path d=\"M6,8 v16 Q6,28 16,28 Q26,28 26,24 v-16\"/><path d=\"M6,14 Q6,18 16,18 Q26,18 26,14\"/></g>',\n  'docker': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><path d=\"M2,16 h6 v-6 h4 v-4 h4 v4 h4 v-4 h4 v8 Q28,24 20,26 Q10,28 4,22 z\"/><rect x=\"10\" y=\"12\" width=\"3\" height=\"3\"/><rect x=\"14\" y=\"12\" width=\"3\" height=\"3\"/><rect x=\"14\" y=\"8\" width=\"3\" height=\"3\"/></g>',\n  'container': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><path d=\"M4,12 L16,6 L28,12 L16,18 z\"/><path d=\"M4,12 v8 L16,26 v-8\"/><path d=\"M28,12 v8 L16,26 v-8\"/></g>',\n  'vm': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"4\" y=\"4\" width=\"24\" height=\"24\" rx=\"2\"/><rect x=\"7\" y=\"7\" width=\"18\" height=\"14\" rx=\"1\"/><line x1=\"12\" y1=\"24\" x2=\"20\" y2=\"24\"/></g>',\n  'kubernetes': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><polygon points=\"16,2 28,9 28,23 16,30 4,23 4,9\"/><circle cx=\"16\" cy=\"16\" r=\"4\"/><line x1=\"16\" y1=\"12\" x2=\"16\" y2=\"4\"/><line x1=\"19\" y1=\"14\" x2=\"26\" y2=\"10\"/><line x1=\"19\" y1=\"18\" x2=\"26\" y2=\"22\"/><line x1=\"16\" y1=\"20\" x2=\"16\" y2=\"28\"/><line x1=\"13\" y1=\"18\" x2=\"6\" y2=\"22\"/><line x1=\"13\" y1=\"14\" x2=\"6\" y2=\"10\"/></g>',\n  'api': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"4\" y=\"6\" width=\"24\" height=\"20\" rx=\"2\"/><path d=\"M10,16 l2,-6 2,6 M10.5,14 h3\" stroke-linecap=\"round\"/><path d=\"M17,10 h3 Q22,10 22,13 Q22,16 20,16 h-3 M17,16 v6\" stroke-linecap=\"round\"/><line x1=\"25\" y1=\"10\" x2=\"25\" y2=\"22\"/></g>',\n  'queue': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"4\" y=\"8\" width=\"24\" height=\"16\" rx=\"2\"/><line x1=\"12\" y1=\"8\" x2=\"12\" y2=\"24\"/><line x1=\"20\" y1=\"8\" x2=\"20\" y2=\"24\"/><path d=\"M6,16 h3 M14,16 h4 M22,16 h4\" stroke-linecap=\"round\"/></g>',\n  'lambda': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M8,6 L16,26 L24,6\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/><line x1=\"6\" y1=\"16\" x2=\"12\" y2=\"16\" stroke-linecap=\"round\"/></g>',\n  'bucket': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><path d=\"M6,8 h20 l-2,18 h-16 z\"/><ellipse cx=\"16\" cy=\"8\" rx=\"10\" ry=\"3\"/></g>',\n  'shield': '<path d=\"M16,3 L27,8 v10 Q27,26 16,29 Q5,26 5,18 v-10 z\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"/>',\n  'camera': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"4\" y=\"10\" width=\"24\" height=\"16\" rx=\"2\"/><circle cx=\"16\" cy=\"18\" r=\"5\"/><circle cx=\"16\" cy=\"18\" r=\"2\" fill=\"currentColor\"/><rect x=\"10\" y=\"6\" width=\"12\" height=\"4\" rx=\"1\"/></g>',\n  'monitor': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"4\" y=\"4\" width=\"24\" height=\"16\" rx=\"2\"/><line x1=\"12\" y1=\"24\" x2=\"20\" y2=\"24\"/><line x1=\"16\" y1=\"20\" x2=\"16\" y2=\"24\"/><path d=\"M8,8 h4 M8,11 h6 M8,14 h5 M18,8 Q20,8 20,14 Q20,16 18,16\" stroke-width=\"1\"/></g>',\n  'thermostat': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><circle cx=\"16\" cy=\"16\" r=\"11\"/><circle cx=\"16\" cy=\"16\" r=\"7\"/><line x1=\"16\" y1=\"9\" x2=\"16\" y2=\"16\" stroke-width=\"2\" stroke-linecap=\"round\"/></g>',\n  'doorbell': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"9\" y=\"3\" width=\"14\" height=\"26\" rx=\"4\"/><circle cx=\"16\" cy=\"14\" r=\"4\"/><circle cx=\"16\" cy=\"22\" r=\"1.5\" fill=\"currentColor\"/></g>',\n  'smart-lock': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"7\" y=\"14\" width=\"18\" height=\"14\" rx=\"2\"/><path d=\"M11,14 v-4 Q11,4 16,4 Q21,4 21,10 v4\"/><circle cx=\"16\" cy=\"21\" r=\"2\" fill=\"currentColor\"/></g>',\n  'smart-bulb': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><path d=\"M11,18 Q6,12 8,7 Q10,3 16,3 Q22,3 24,7 Q26,12 21,18 z\"/><rect x=\"11\" y=\"18\" width=\"10\" height=\"4\" rx=\"1\"/><line x1=\"12\" y1=\"24\" x2=\"20\" y2=\"24\"/><line x1=\"13\" y1=\"26\" x2=\"19\" y2=\"26\"/></g>',\n  'smart-plug': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"6\" y=\"8\" width=\"20\" height=\"16\" rx=\"3\"/><circle cx=\"12\" cy=\"16\" r=\"2\" fill=\"currentColor\"/><circle cx=\"20\" cy=\"16\" r=\"2\" fill=\"currentColor\"/><line x1=\"16\" y1=\"24\" x2=\"16\" y2=\"28\" stroke-width=\"2\"/></g>',\n  'smart-speaker': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><path d=\"M8,28 Q8,8 16,4 Q24,8 24,28 z\"/><circle cx=\"16\" cy=\"20\" r=\"4\"/><circle cx=\"16\" cy=\"20\" r=\"1.5\" fill=\"currentColor\"/></g>',\n  'smart-tv': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"3\" y=\"5\" width=\"26\" height=\"18\" rx=\"1\"/><line x1=\"10\" y1=\"27\" x2=\"22\" y2=\"27\"/><line x1=\"12\" y1=\"23\" x2=\"12\" y2=\"27\"/><line x1=\"20\" y1=\"23\" x2=\"20\" y2=\"27\"/></g>',\n  'hub': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><circle cx=\"16\" cy=\"16\" r=\"5\"/><circle cx=\"16\" cy=\"16\" r=\"2\" fill=\"currentColor\"/><line x1=\"16\" y1=\"5\" x2=\"16\" y2=\"11\"/><line x1=\"16\" y1=\"21\" x2=\"16\" y2=\"27\"/><line x1=\"5\" y1=\"16\" x2=\"11\" y2=\"16\"/><line x1=\"21\" y1=\"16\" x2=\"27\" y2=\"16\"/></g>',\n  'smoke-detector': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><circle cx=\"16\" cy=\"16\" r=\"11\"/><circle cx=\"16\" cy=\"16\" r=\"3\"/><line x1=\"16\" y1=\"5\" x2=\"16\" y2=\"7\"/><line x1=\"16\" y1=\"25\" x2=\"16\" y2=\"27\"/><line x1=\"5\" y1=\"16\" x2=\"7\" y2=\"16\"/><line x1=\"25\" y1=\"16\" x2=\"27\" y2=\"16\"/></g>',\n  'motion-sensor': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><path d=\"M8,24 Q4,12 16,6 Q28,12 24,24\"/><circle cx=\"16\" cy=\"16\" r=\"3\" fill=\"currentColor\"/><path d=\"M12,20 Q10,16 14,12\"/><path d=\"M20,20 Q22,16 18,12\"/></g>',\n  'garage': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><path d=\"M4,28 v-14 L16,4 L28,14 v14 z\"/><rect x=\"8\" y=\"16\" width=\"16\" height=\"12\"/><line x1=\"8\" y1=\"20\" x2=\"24\" y2=\"20\"/><line x1=\"8\" y1=\"24\" x2=\"24\" y2=\"24\"/></g>',\n  'sprinkler': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><line x1=\"16\" y1=\"4\" x2=\"16\" y2=\"14\"/><path d=\"M10,14 h12 v3 Q16,22 10,17 z\"/><path d=\"M8,20 Q6,24 4,26\" stroke-linecap=\"round\"/><path d=\"M16,22 v6\" stroke-linecap=\"round\"/><path d=\"M24,20 Q26,24 28,26\" stroke-linecap=\"round\"/></g>',\n  'vacuum': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><circle cx=\"16\" cy=\"16\" r=\"11\"/><circle cx=\"16\" cy=\"16\" r=\"6\"/><circle cx=\"16\" cy=\"16\" r=\"2\" fill=\"currentColor\"/><line x1=\"16\" y1=\"5\" x2=\"20\" y2=\"3\" stroke-linecap=\"round\"/></g>',\n  'basketball-ball': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><circle cx=\"16\" cy=\"16\" r=\"12\"/><path d=\"M4,16 h24\"/><path d=\"M16,4 v24\"/><path d=\"M6,7 Q16,14 6,25\"/><path d=\"M26,7 Q16,14 26,25\"/></g>',\n  'football-ball': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><ellipse cx=\"16\" cy=\"16\" rx=\"12\" ry=\"8\" transform=\"rotate(-30,16,16)\"/><path d=\"M10,10 L22,22 M12,8 L24,20\" stroke-linecap=\"round\"/></g>',\n  'soccer-ball': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><circle cx=\"16\" cy=\"16\" r=\"12\"/><polygon points=\"16,8 20,12 18,17 14,17 12,12\" fill=\"currentColor\" stroke=\"none\"/></g>',\n  'hockey-puck': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><ellipse cx=\"16\" cy=\"14\" rx=\"12\" ry=\"5\"/><path d=\"M4,14 v4 Q4,23 16,23 Q28,23 28,18 v-4\"/></g>',\n  'baseball-ball': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><circle cx=\"16\" cy=\"16\" r=\"12\"/><path d=\"M8,6 Q14,12 8,26\"/><path d=\"M24,6 Q18,12 24,26\"/></g>',\n  'tennis-ball': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><circle cx=\"16\" cy=\"16\" r=\"12\"/><path d=\"M6,6 Q16,16 6,26\"/><path d=\"M26,6 Q16,16 26,26\"/></g>',\n  'volleyball': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><circle cx=\"16\" cy=\"16\" r=\"12\"/><path d=\"M16,4 Q12,16 16,28\"/><path d=\"M5,10 Q16,14 27,10\"/><path d=\"M5,22 Q16,18 27,22\"/></g>',\n  'rugby-ball': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><ellipse cx=\"16\" cy=\"16\" rx=\"13\" ry=\"8\" transform=\"rotate(-30,16,16)\"/><path d=\"M10,10 L22,22\"/></g>',\n  'golf-ball': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><circle cx=\"16\" cy=\"16\" r=\"10\"/><circle cx=\"14\" cy=\"14\" r=\"0.8\" fill=\"currentColor\"/><circle cx=\"18\" cy=\"12\" r=\"0.8\" fill=\"currentColor\"/><circle cx=\"16\" cy=\"16\" r=\"0.8\" fill=\"currentColor\"/></g>',\n  'frisbee': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><ellipse cx=\"16\" cy=\"16\" rx=\"13\" ry=\"5\"/><ellipse cx=\"16\" cy=\"15\" rx=\"8\" ry=\"3\"/></g>',\n  'cricket-ball': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><circle cx=\"16\" cy=\"16\" r=\"11\"/><path d=\"M10,6 Q16,16 10,26\" stroke-dasharray=\"2,2\"/></g>',\n  'lacrosse-stick': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><line x1=\"8\" y1=\"28\" x2=\"20\" y2=\"6\" stroke-width=\"2\"/><path d=\"M18,8 Q26,4 26,12 Q26,16 20,14\"/><line x1=\"20\" y1=\"9\" x2=\"24\" y2=\"13\"/></g>',\n  'golf-flag': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><line x1=\"10\" y1=\"4\" x2=\"10\" y2=\"28\" stroke-width=\"2\"/><polygon points=\"10,4 26,10 10,16\" fill=\"currentColor\" opacity=\"0.3\" stroke=\"currentColor\"/><ellipse cx=\"16\" cy=\"28\" rx=\"10\" ry=\"2\"/></g>',\n  'tactical-x': '<g stroke=\"currentColor\" stroke-width=\"3\" stroke-linecap=\"round\"><line x1=\"6\" y1=\"6\" x2=\"26\" y2=\"26\"/><line x1=\"26\" y1=\"6\" x2=\"6\" y2=\"26\"/></g>',\n  'tactical-o': '<circle cx=\"16\" cy=\"16\" r=\"10\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"3\"/>',\n  'tactical-star': '<polygon points=\"16,3 19,12 29,12 21,18 24,28 16,22 8,28 11,18 3,12 13,12\" fill=\"currentColor\" opacity=\"0.3\" stroke=\"currentColor\" stroke-width=\"1.5\"/>',\n  'patch-panel': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"3\" y=\"10\" width=\"26\" height=\"12\" rx=\"1\"/><circle cx=\"8\" cy=\"14\" r=\"1.5\"/><circle cx=\"13\" cy=\"14\" r=\"1.5\"/><circle cx=\"18\" cy=\"14\" r=\"1.5\"/><circle cx=\"23\" cy=\"14\" r=\"1.5\"/><circle cx=\"8\" cy=\"19\" r=\"1.5\"/><circle cx=\"13\" cy=\"19\" r=\"1.5\"/><circle cx=\"18\" cy=\"19\" r=\"1.5\"/><circle cx=\"23\" cy=\"19\" r=\"1.5\"/></g>',\n  'ups': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"6\" y=\"4\" width=\"20\" height=\"24\" rx=\"2\"/><rect x=\"10\" y=\"8\" width=\"12\" height=\"6\" rx=\"1\"/><circle cx=\"12\" cy=\"20\" r=\"1.5\" fill=\"currentColor\"/><circle cx=\"16\" cy=\"20\" r=\"1.5\"/><circle cx=\"20\" cy=\"20\" r=\"1.5\"/></g>',\n  'pdu': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"11\" y=\"2\" width=\"10\" height=\"28\" rx=\"1\"/><circle cx=\"16\" cy=\"7\" r=\"2\"/><circle cx=\"16\" cy=\"13\" r=\"2\"/><circle cx=\"16\" cy=\"19\" r=\"2\"/><circle cx=\"16\" cy=\"25\" r=\"2\" fill=\"currentColor\"/></g>',\n  'rack-shelf': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"3\" y=\"12\" width=\"26\" height=\"8\" rx=\"1\"/><line x1=\"3\" y1=\"17\" x2=\"29\" y2=\"17\" stroke-dasharray=\"3,2\"/><circle cx=\"7\" cy=\"14.5\" r=\"1\" fill=\"currentColor\"/></g>',\n  'blank-panel': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"3\" y=\"12\" width=\"26\" height=\"8\" rx=\"1\"/><circle cx=\"6\" cy=\"16\" r=\"1\"/><circle cx=\"26\" cy=\"16\" r=\"1\"/></g>',\n  'cable-management': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"3\" y=\"11\" width=\"26\" height=\"10\" rx=\"1\"/><path d=\"M7,14 Q10,18 13,14 Q16,10 19,14 Q22,18 25,14\" stroke-linecap=\"round\"/></g>',\n  'kvm': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"4\" y=\"8\" width=\"24\" height=\"16\" rx=\"2\"/><rect x=\"8\" y=\"11\" width=\"10\" height=\"7\" rx=\"1\"/><circle cx=\"23\" cy=\"14.5\" r=\"2\"/><line x1=\"12\" y1=\"20\" x2=\"20\" y2=\"20\"/></g>'\n};\n\nfunction getModalShapePreview(shape) {\n  const svg = MODAL_SHAPE_PREVIEWS[shape];\n  if (svg) return '<svg width=\"36\" height=\"36\" viewBox=\"0 0 32 32\" style=\"color:var(--accent)\">' + svg + '</svg>';\n  return '<span style=\"font-size:10px;color:var(--text-soft);text-align:center;line-height:1.2\">' + (shape || 'circle') + '</span>';\n}\n\nfunction populateModalShapeSelect(categorySelectId, shapeSelectId, previewId) {\n  const catSelect = document.getElementById(categorySelectId);\n  const shapeSelect = document.getElementById(shapeSelectId);\n  if (!catSelect || !shapeSelect) return;\n  const category = catSelect.value || 'basic';\n  const shapes = SHAPE_CATEGORIES[category] || SHAPE_CATEGORIES.basic;\n  shapeSelect.innerHTML = shapes.map(s => `<option value=\"${s.value}\">${s.label}</option>`).join('');\n  if (previewId) {\n    const preview = document.getElementById(previewId);\n    if (preview && shapes.length > 0) preview.innerHTML = getModalShapePreview(shapes[0].value);\n  }\n}\n\ndocument.getElementById('new-node-category')?.addEventListener('change', () => {\n  populateModalShapeSelect('new-node-category', 'new-node-shape', 'new-node-shape-preview');\n  selectedNodeIconData = null;\n  const iconContainer = document.getElementById('selected-node-icon');\n  if (iconContainer) iconContainer.style.display = 'none';\n  const shapePreview = document.getElementById('new-node-shape-preview');\n  if (shapePreview) shapePreview.style.display = 'flex';\n});\ndocument.getElementById('new-rack-category')?.addEventListener('change', () => {\n  populateModalShapeSelect('new-rack-category', 'new-rack-shape', 'new-rack-shape-preview');\n  selectedRackIconData = null;\n  const iconContainer = document.getElementById('selected-rack-icon');\n  if (iconContainer) iconContainer.style.display = 'none';\n  const shapePreview = document.getElementById('new-rack-shape-preview');\n  if (shapePreview) shapePreview.style.display = 'flex';\n});\ndocument.getElementById('new-node-shape')?.addEventListener('change', (e) => {\n  const preview = document.getElementById('new-node-shape-preview');\n  if (preview) {\n    preview.innerHTML = getModalShapePreview(e.target.value);\n    preview.style.display = 'flex';\n  }\n  selectedNodeIconData = null;\n  const iconContainer = document.getElementById('selected-node-icon');\n  if (iconContainer) iconContainer.style.display = 'none';\n});\ndocument.getElementById('new-rack-shape')?.addEventListener('change', (e) => {\n  const preview = document.getElementById('new-rack-shape-preview');\n  if (preview) {\n    preview.innerHTML = getModalShapePreview(e.target.value);\n    preview.style.display = 'flex';\n  }\n  selectedRackIconData = null;\n  const iconContainer = document.getElementById('selected-rack-icon');\n  if (iconContainer) iconContainer.style.display = 'none';\n});\n\npopulateModalShapeSelect('new-node-category', 'new-node-shape', 'new-node-shape-preview');\npopulateModalShapeSelect('new-rack-category', 'new-rack-shape', 'new-rack-shape-preview');\n\nfunction showWelcomeModal() {\n  const modal = document.getElementById('welcome-modal');\n  if (!modal) return;\n  modal.classList.add('active');\n\n  let selectedMode = 'network';\n  const canvasRow = document.getElementById('welcome-canvas-row');\n  const canvasSelect = document.getElementById('welcome-canvas-select');\n  const canvasPreview = document.getElementById('welcome-canvas-preview');\n  const themeSelect = document.getElementById('welcome-theme-select');\n  const bgColor = document.getElementById('welcome-bg-color');\n  const accentColor = document.getElementById('welcome-accent-color');\n  const textColor = document.getElementById('welcome-text-color');\n  const textSoftColor = document.getElementById('welcome-text-soft-color');\n  const panelColor = document.getElementById('welcome-panel-color');\n  const modalColor = document.getElementById('welcome-modal-color');\n  const dangerColor = document.getElementById('welcome-danger-color');\n  const mobileFooterColor = document.getElementById('welcome-mobile-footer-color');\n  const tagFillColor = document.getElementById('welcome-tag-fill-color');\n  const tagTextColor = document.getElementById('welcome-tag-text-color');\n  const tagBorderColor = document.getElementById('welcome-tag-border-color');\n\n  function renderWelcomeCanvasPreview() {\n    if (!canvasPreview) return;\n    const template = canvasSelect ? canvasSelect.value : 'grid';\n    const previewBg = bgColor ? bgColor.value : '#0b0e13';\n    const gridColor = '#475569';\n    canvasPreview.style.background = previewBg;\n    canvasPreview.innerHTML = '';\n\n    if (template === 'none') return;\n\n    const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');\n    svg.setAttribute('width', '100%');\n    svg.setAttribute('height', '100%');\n    svg.setAttribute('viewBox', '0 0 300 60');\n    svg.style.display = 'block';\n\n    if (template === 'grid') {\n      for (let x = 0; x <= 300; x += 20) {\n        const line = document.createElementNS('http://www.w3.org/2000/svg', 'line');\n        line.setAttribute('x1', x); line.setAttribute('y1', 0);\n        line.setAttribute('x2', x); line.setAttribute('y2', 60);\n        line.setAttribute('stroke', gridColor + '33');\n        line.setAttribute('stroke-width', x % 100 === 0 ? '1.5' : '0.5');\n        svg.appendChild(line);\n      }\n      for (let y = 0; y <= 60; y += 20) {\n        const line = document.createElementNS('http://www.w3.org/2000/svg', 'line');\n        line.setAttribute('x1', 0); line.setAttribute('y1', y);\n        line.setAttribute('x2', 300); line.setAttribute('y2', y);\n        line.setAttribute('stroke', gridColor + '33');\n        line.setAttribute('stroke-width', y % 100 === 0 ? '1.5' : '0.5');\n        svg.appendChild(line);\n      }\n    } else if (template === 'dots') {\n      for (let x = 10; x <= 290; x += 20) {\n        for (let y = 10; y <= 50; y += 20) {\n          const dot = document.createElementNS('http://www.w3.org/2000/svg', 'circle');\n          dot.setAttribute('cx', x); dot.setAttribute('cy', y); dot.setAttribute('r', 1.5);\n          dot.setAttribute('fill', gridColor + '66');\n          svg.appendChild(dot);\n        }\n      }\n    } else if (template === 'blueprint') {\n      for (let x = 0; x <= 300; x += 10) {\n        const line = document.createElementNS('http://www.w3.org/2000/svg', 'line');\n        line.setAttribute('x1', x); line.setAttribute('y1', 0);\n        line.setAttribute('x2', x); line.setAttribute('y2', 60);\n        line.setAttribute('stroke', x % 40 === 0 ? gridColor + '66' : gridColor + '33');\n        line.setAttribute('stroke-width', x % 40 === 0 ? '1' : '0.3');\n        svg.appendChild(line);\n      }\n      for (let y = 0; y <= 60; y += 10) {\n        const line = document.createElementNS('http://www.w3.org/2000/svg', 'line');\n        line.setAttribute('x1', 0); line.setAttribute('y1', y);\n        line.setAttribute('x2', 300); line.setAttribute('y2', y);\n        line.setAttribute('stroke', y % 40 === 0 ? gridColor + '66' : gridColor + '33');\n        line.setAttribute('stroke-width', y % 40 === 0 ? '1' : '0.3');\n        svg.appendChild(line);\n      }\n    } else if (template === 'basketball') {\n      const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');\n      rect.setAttribute('x', 20); rect.setAttribute('y', 10);\n      rect.setAttribute('width', 260); rect.setAttribute('height', 40);\n      rect.setAttribute('fill', '#cd853f22'); rect.setAttribute('stroke', gridColor + '66');\n      rect.setAttribute('stroke-width', '2');\n      svg.appendChild(rect);\n      const center = document.createElementNS('http://www.w3.org/2000/svg', 'line');\n      center.setAttribute('x1', 150); center.setAttribute('y1', 10);\n      center.setAttribute('x2', 150); center.setAttribute('y2', 50);\n      center.setAttribute('stroke', gridColor + '66'); center.setAttribute('stroke-width', '1.5');\n      svg.appendChild(center);\n      const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');\n      circle.setAttribute('cx', 150); circle.setAttribute('cy', 30); circle.setAttribute('r', 10);\n      circle.setAttribute('fill', 'none'); circle.setAttribute('stroke', gridColor + '66');\n      svg.appendChild(circle);\n    } else if (template === 'football') {\n      const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');\n      rect.setAttribute('x', 20); rect.setAttribute('y', 10);\n      rect.setAttribute('width', 260); rect.setAttribute('height', 40);\n      rect.setAttribute('fill', '#22803322'); rect.setAttribute('stroke', '#ffffff44');\n      rect.setAttribute('stroke-width', '2');\n      svg.appendChild(rect);\n      for (let x = 46; x <= 254; x += 26) {\n        const line = document.createElementNS('http://www.w3.org/2000/svg', 'line');\n        line.setAttribute('x1', x); line.setAttribute('y1', 10);\n        line.setAttribute('x2', x); line.setAttribute('y2', 50);\n        line.setAttribute('stroke', '#ffffff44'); line.setAttribute('stroke-width', '1');\n        svg.appendChild(line);\n      }\n    } else if (template === 'soccer') {\n      const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');\n      rect.setAttribute('x', 20); rect.setAttribute('y', 10);\n      rect.setAttribute('width', 260); rect.setAttribute('height', 40);\n      rect.setAttribute('fill', '#1a802022'); rect.setAttribute('stroke', '#ffffff55');\n      rect.setAttribute('stroke-width', '2');\n      svg.appendChild(rect);\n      const center = document.createElementNS('http://www.w3.org/2000/svg', 'line');\n      center.setAttribute('x1', 150); center.setAttribute('y1', 10);\n      center.setAttribute('x2', 150); center.setAttribute('y2', 50);\n      center.setAttribute('stroke', '#ffffff44'); center.setAttribute('stroke-width', '1');\n      svg.appendChild(center);\n      const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');\n      circle.setAttribute('cx', 150); circle.setAttribute('cy', 30); circle.setAttribute('r', 8);\n      circle.setAttribute('fill', 'none'); circle.setAttribute('stroke', '#ffffff44');\n      svg.appendChild(circle);\n    } else if (template === 'hockey') {\n      const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');\n      rect.setAttribute('x', 20); rect.setAttribute('y', 10);\n      rect.setAttribute('width', 260); rect.setAttribute('height', 40);\n      rect.setAttribute('rx', 10); rect.setAttribute('ry', 10);\n      rect.setAttribute('fill', '#e8f4f844'); rect.setAttribute('stroke', '#ff000066');\n      rect.setAttribute('stroke-width', '2');\n      svg.appendChild(rect);\n      const center = document.createElementNS('http://www.w3.org/2000/svg', 'line');\n      center.setAttribute('x1', 150); center.setAttribute('y1', 10);\n      center.setAttribute('x2', 150); center.setAttribute('y2', 50);\n      center.setAttribute('stroke', '#ff000055'); center.setAttribute('stroke-width', '2');\n      svg.appendChild(center);\n      const blueL = document.createElementNS('http://www.w3.org/2000/svg', 'line');\n      blueL.setAttribute('x1', 95); blueL.setAttribute('y1', 10);\n      blueL.setAttribute('x2', 95); blueL.setAttribute('y2', 50);\n      blueL.setAttribute('stroke', '#0066ff55'); blueL.setAttribute('stroke-width', '2');\n      svg.appendChild(blueL);\n      const blueR = document.createElementNS('http://www.w3.org/2000/svg', 'line');\n      blueR.setAttribute('x1', 205); blueR.setAttribute('y1', 10);\n      blueR.setAttribute('x2', 205); blueR.setAttribute('y2', 50);\n      blueR.setAttribute('stroke', '#0066ff55'); blueR.setAttribute('stroke-width', '2');\n      svg.appendChild(blueR);\n    } else if (template === 'baseball') {\n      const diamond = document.createElementNS('http://www.w3.org/2000/svg', 'polygon');\n      diamond.setAttribute('points', '150,15 185,30 150,45 115,30');\n      diamond.setAttribute('fill', '#cd853f22'); diamond.setAttribute('stroke', '#ffffff55');\n      diamond.setAttribute('stroke-width', '1.5');\n      svg.appendChild(diamond);\n      const arc = document.createElementNS('http://www.w3.org/2000/svg', 'path');\n      arc.setAttribute('d', 'M 60,50 Q 150,5 240,50');\n      arc.setAttribute('fill', '#22803322'); arc.setAttribute('stroke', '#ffffff44');\n      arc.setAttribute('stroke-width', '1');\n      svg.appendChild(arc);\n    } else if (template === 'tennis') {\n      const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');\n      rect.setAttribute('x', 30); rect.setAttribute('y', 10);\n      rect.setAttribute('width', 240); rect.setAttribute('height', 40);\n      rect.setAttribute('fill', '#0066aa22'); rect.setAttribute('stroke', '#ffffff66');\n      rect.setAttribute('stroke-width', '2');\n      svg.appendChild(rect);\n      const net = document.createElementNS('http://www.w3.org/2000/svg', 'line');\n      net.setAttribute('x1', 150); net.setAttribute('y1', 10);\n      net.setAttribute('x2', 150); net.setAttribute('y2', 50);\n      net.setAttribute('stroke', '#ffffff88'); net.setAttribute('stroke-width', '1');\n      svg.appendChild(net);\n      const svcL = document.createElementNS('http://www.w3.org/2000/svg', 'rect');\n      svcL.setAttribute('x', 70); svcL.setAttribute('y', 18);\n      svcL.setAttribute('width', 80); svcL.setAttribute('height', 24);\n      svcL.setAttribute('fill', 'none'); svcL.setAttribute('stroke', '#ffffff44');\n      svg.appendChild(svcL);\n      const svcR = document.createElementNS('http://www.w3.org/2000/svg', 'rect');\n      svcR.setAttribute('x', 150); svcR.setAttribute('y', 18);\n      svcR.setAttribute('width', 80); svcR.setAttribute('height', 24);\n      svcR.setAttribute('fill', 'none'); svcR.setAttribute('stroke', '#ffffff44');\n      svg.appendChild(svcR);\n    }\n    canvasPreview.appendChild(svg);\n  }\n\n  function updateWelcomeCanvasOptions(mode) {\n    if (!canvasSelect || !canvasRow) return;\n    const options = CANVAS_OPTIONS[mode] || CANVAS_OPTIONS.network;\n    const previewRow = document.getElementById('welcome-canvas-preview');\n    const colorDetails = modal.querySelector('details');\n    if (mode === 'mindmap') {\n      canvasRow.style.display = 'none';\n      if (previewRow) previewRow.style.display = 'none';\n      if (colorDetails) colorDetails.open = true;\n    } else if (mode === 'floorplan') {\n      canvasRow.style.display = 'none';\n      if (previewRow) {\n        previewRow.style.display = 'block';\n        const networkOptions = CANVAS_OPTIONS.network;\n        canvasSelect.innerHTML = networkOptions.map(o => `<option value=\"${o.value}\">${o.label}</option>`).join('');\n        canvasSelect.value = 'blueprint';\n        renderWelcomeCanvasPreview();\n      }\n      if (colorDetails) colorDetails.open = false;\n    } else if (options.length > 0) {\n      canvasRow.style.display = 'flex';\n      if (previewRow) previewRow.style.display = 'block';\n      canvasSelect.innerHTML = options.map(o => `<option value=\"${o.value}\">${o.label}</option>`).join('');\n      renderWelcomeCanvasPreview();\n      if (colorDetails) colorDetails.open = false;\n    }\n  }\n\n  modal.querySelectorAll('.welcome-mode-btn').forEach(btn => {\n    btn.addEventListener('click', () => {\n      selectedMode = btn.dataset.mode;\n      modal.querySelectorAll('.welcome-mode-btn').forEach(b => {\n        b.style.borderColor = 'var(--edge-main)';\n      });\n      btn.style.borderColor = 'var(--accent)';\n      updateWelcomeCanvasOptions(selectedMode);\n      PAGE_STATE.mappingMode = selectedMode;\n      applyMappingModeLabels();\n      updateLayerLabels();\n      applyLanguage();\n      displayTabs();\n      if (selectedMode === 'mindmap') {\n        PAGE_STATE.canvasTemplate = 'none';\n        PAGE_STATE.canvasGridEnabled = false;\n      } else if (selectedMode === 'floorplan') {\n        PAGE_STATE.canvasTemplate = 'blueprint';\n        PAGE_STATE.canvasGridEnabled = true;\n      } else if (canvasSelect) {\n        PAGE_STATE.canvasTemplate = canvasSelect.value;\n        PAGE_STATE.canvasGridEnabled = canvasSelect.value !== 'none';\n      }\n      forgeTheTopology();\n    });\n    btn.addEventListener('mouseenter', () => {\n      if (btn.style.borderColor !== 'var(--accent)') {\n        btn.style.background = 'var(--panel)';\n      }\n    });\n    btn.addEventListener('mouseleave', () => {\n      btn.style.background = 'var(--panel-alt)';\n    });\n  });\n\n  if (canvasSelect) {\n    canvasSelect.addEventListener('change', () => {\n      renderWelcomeCanvasPreview();\n      PAGE_STATE.canvasTemplate = canvasSelect.value;\n      PAGE_STATE.canvasGridEnabled = canvasSelect.value !== 'none';\n      forgeTheTopology();\n    });\n  }\n\n  if (themeSelect) {\n    themeSelect.addEventListener('change', () => {\n      const presetKey = themeSelect.value;\n      const colorDetails = modal.querySelector('details');\n      if (presetKey === '') {\n        if (colorDetails) colorDetails.open = true;\n        return;\n      }\n      if (typeof THEME_PRESETS === 'undefined') return;\n      const p = THEME_PRESETS[presetKey];\n      if (!p) return;\n      Object.assign(PAGE_STATE, p);\n      document.body.style.background = p.panel;\n      document.documentElement.style.setProperty('--bg', p.panel);\n      document.documentElement.style.setProperty('--panel', p.panel);\n      document.documentElement.style.setProperty('--panel-alt', p.panelAlt);\n      document.documentElement.style.setProperty('--accent', p.accent);\n      document.documentElement.style.setProperty('--danger', p.danger);\n      document.documentElement.style.setProperty('--text-main', p.textMain);\n      document.documentElement.style.setProperty('--text-soft', p.textSoft);\n      document.documentElement.style.setProperty('--modal-bg', p.panelAlt);\n      document.documentElement.style.setProperty('--tag-bg', p.tagFill);\n      document.documentElement.style.setProperty('--tag-text', p.tagText);\n      document.documentElement.style.setProperty('--tag-border', p.tagBorder);\n      if (bgColor) bgColor.value = p.panel;\n      if (accentColor) accentColor.value = p.accent;\n      if (textColor) textColor.value = p.textMain;\n      if (textSoftColor) textSoftColor.value = p.textSoft;\n      if (panelColor) panelColor.value = p.panel;\n      if (modalColor) modalColor.value = p.panelAlt;\n      if (dangerColor) dangerColor.value = p.canvasGrid || '#475569';\n      if (tagFillColor) tagFillColor.value = p.tagFill;\n      if (tagTextColor) tagTextColor.value = p.tagText;\n      if (tagBorderColor) tagBorderColor.value = p.tagBorder;\n      PAGE_STATE.background = p.panel;\n      renderWelcomeCanvasPreview();\n      wieldThePower();\n    });\n  }\n  \n  function setupColorPicker(picker, cssVar, pageStateKey) {\n    if (!picker) return;\n    picker.addEventListener('input', (e) => {\n      document.documentElement.style.setProperty(cssVar, e.target.value);\n      if (pageStateKey) PAGE_STATE[pageStateKey] = e.target.value;\n      if (cssVar === '--bg' || pageStateKey === 'background') {\n        document.body.style.background = e.target.value;\n        renderWelcomeCanvasPreview();\n      }\n    });\n  }\n\n  setupColorPicker(bgColor, '--bg', 'background');\n  setupColorPicker(accentColor, '--accent', 'accent');\n  setupColorPicker(textColor, '--text-main', 'textMain');\n  setupColorPicker(textSoftColor, '--text-soft', 'textSoft');\n  setupColorPicker(panelColor, '--panel', 'panel');\n  setupColorPicker(modalColor, '--panel-alt', 'panelAlt');\n  if (dangerColor) {\n    dangerColor.addEventListener('input', (e) => {\n      PAGE_STATE.canvasGrid = e.target.value;\n      if (typeof forgeTheTopology === 'function') forgeTheTopology();\n      renderWelcomeCanvasPreview();\n    });\n  }\n  setupColorPicker(mobileFooterColor, '--sidebar-bg', 'sidebarBg');\n  setupColorPicker(tagFillColor, '--tag-bg', 'tagFill');\n  setupColorPicker(tagTextColor, '--tag-text', 'tagText');\n  setupColorPicker(tagBorderColor, '--tag-border', 'tagBorder');\n\n  renderWelcomeCanvasPreview();\n\n  const startBtn = document.getElementById('welcome-start-btn');\n  if (startBtn) {\n    startBtn.addEventListener('click', () => {\n      PAGE_STATE.mappingMode = selectedMode;\n      const modeSelect = document.getElementById('mapping-mode-select');\n      if (modeSelect) modeSelect.value = selectedMode;\n\n      if (selectedMode === 'mindmap') {\n        PAGE_STATE.canvasTemplate = 'none';\n        PAGE_STATE.canvasGridEnabled = false;\n      } else if (selectedMode === 'floorplan') {\n        PAGE_STATE.canvasTemplate = 'blueprint';\n      } else if (canvasSelect) {\n        PAGE_STATE.canvasTemplate = canvasSelect.value;\n        PAGE_STATE.canvasGridEnabled = canvasSelect.value !== 'none';\n      }\n\n      if (bgColor) {\n        PAGE_STATE.background = bgColor.value;\n        document.body.style.background = bgColor.value;\n        const el = document.getElementById('panel-color');\n        if (el) el.value = bgColor.value;\n      }\n      if (accentColor) {\n        PAGE_STATE.accent = accentColor.value;\n        const el = document.getElementById('accent-color');\n        if (el) el.value = accentColor.value;\n      }\n\n      if (typeof updateCanvasStyleOptions === 'function') updateCanvasStyleOptions();\n      if (typeof applyMappingModeLabels === 'function') applyMappingModeLabels();\n      if (typeof updateLayerLabels === 'function') updateLayerLabels();\n\n      const catSelect = document.getElementById('shape-category-select');\n      if (catSelect) {\n        catSelect.value = MODE_DEFAULT_CATEGORIES[selectedMode] || 'network';\n        if (typeof populateShapeSelect === 'function') populateShapeSelect();\n      }\n\n      modal.classList.remove('active');\n      wieldThePower();\n      forgeTheTopology();\n    });\n  }\n\n  const skipBtn = document.getElementById('welcome-skip-btn');\n  if (skipBtn) {\n    skipBtn.addEventListener('click', () => {\n      modal.classList.remove('active');\n    });\n  }\n\n  modal.addEventListener('click', (e) => {\n    if (e.target === modal) {\n      modal.classList.remove('active');\n    }\n  });\n}\n    </script>\n</body></html>"
  },
  {
    "path": "theonefile_verse/.dockerignore",
    "content": "node_modules\n.git\n.gitignore\n*.md\ndata\n.env\n.env.*\ndocker-compose.yml\n.dockerignore\ndemo-admin.html\n"
  },
  {
    "path": "theonefile_verse/.gitattributes",
    "content": "*.sh text eol=lf\nDockerfile text eol=lf\n"
  },
  {
    "path": "theonefile_verse/Dockerfile",
    "content": "FROM oven/bun:alpine\nWORKDIR /app\nRUN apk add --no-cache su-exec iputils bind-tools samba-client avahi-tools net-snmp-tools\nCOPY package.json ./\nRUN bun install --production\nCOPY src ./src\nCOPY public/index.html public/theonefile.html public/collab.js public/collab.css public/collab-init.js public/collab-save-hook.js public/landing.js public/admin-dashboard.js public/admin-auth.js public/admin-pages.js public/qrcode.min.js ./public/\nCOPY entrypoint.sh ./\nRUN mkdir -p /app/data/rooms /app/data/backups /app/public && \\\n    addgroup -S appgroup && adduser -S appuser -G appgroup && \\\n    chown -R appuser:appgroup /app && \\\n    chmod +x /app/entrypoint.sh\nEXPOSE 10101\nENTRYPOINT [\"/app/entrypoint.sh\"]\n"
  },
  {
    "path": "theonefile_verse/README.md",
    "content": "### TheOneFile_Verse\n\n<p align=\"center\">\n  <img src=\"https://img.shields.io/badge/License-Unlicense-576169?style=for-the-badge&labelColor=01284b\" alt=\"License: Unlicense\">\n  <img src=\"https://img.shields.io/badge/TheOneFile-4.1.5-blue\" alt=\"TheOneFile 4.1.5\">\n  <a href=\"https://github.com/gelatinescreams/The-One-File/tree/main/theonefile_verse\">\n  <img src=\"https://img.shields.io/badge/TheOneFile_Verse-1.9.0-blue\" alt=\"Docker Version 1.9.0\">\n  </a>\n</p>\n\n*As it turns out, there can be more than one (in 3d soon :) )*\n\n![The One File Verse Mutli User Collaboration](https://raw.githubusercontent.com/gelatinescreams/The-One-File/refs/heads/main/assets/collab-preview.gif)\n\nAn easily deployable, Docker based, real time collaboration server with mutli network device discovery, auto service & selfh.st/icons tagging, user accounts, email authentication, SSO, role based access control and more. All configurable via a robust admin panel.\n\nWhen you're done collaborating, each person can save their own portable copy. That file works exactly like the original TheOneFile: fully offline, self contained, editable anywhere. Import it back into the TheOneFile_Verse anytime to continue collaborating.\n\n**AND/OR**\n\nRooms auto save your work, no manual exports required. Admins can run as many rooms as needed, a multiOneFileverse of parallel diagrams. Host it privately or open it to the internet (use tons of caution and a secure reverse proxy).\n\n* [TheOneFile_Verse online demo](https://multiverse.therecanonlybe.one/s/2ab95062-9d96-4e32-b373-d1994c210d82)\n* *join from different browsers to see real time changes*\n* [TheOneFile_Verse landing page](https://multiverse.therecanonlybe.one)\n* [TheOneFile_Verse admin demo](https://therecanonlybe.one/theonefile_verse/demo-admin.html)\n\n### Option 1: Easiest\n\n```bash\ndocker run -d -p 10101:10101 -v theonefile-data:/app/data ghcr.io/gelatinescreams/theonefile_verse:latest\n```\n\nOr with docker compose, create a `docker-compose.yml`:\n\n```yaml\nservices:\n  theonefile_verse:\n    image: ghcr.io/gelatinescreams/theonefile_verse:latest\n    ports:\n      - \"10101:10101\"\n    volumes:\n      - ./data:/app/data\n    restart: unless-stopped\n```\n\nThen run:\n```bash\ndocker compose up -d\n```\n\nOpen `http://localhost:10101`\n\n## Configuration\n\nAll settings are configured via the admin panel at `/admin`. On first run, you'll set up an admin account.\n\n### .env\n\n| Variable | Default | Description |\n|----------|---------|-------------|\n| `PORT` | `10101` | Server port |\n| `DATA_DIR` | `./data` | Where settings and room data are stored |\n| `REDIS_URL` | | Optional Redis connection for scaling |\n| `CORS_ORIGIN` | | Comma separated list of allowed origins |\n| `REQUIRE_WS_TOKEN` | `false` | Require WebSocket session tokens |\n| `TRUSTED_PROXY_COUNT` | | Number of trusted reverse proxies for X Forwarded For |\n| `TRUSTED_PROXIES` | | Comma separated list of trusted proxy IPs |\n| `DEBUG_OIDC` | `false` | Enable OIDC debug logging (dev only) |\n\n\n### TheOneFile_Verse Features\n* **Current Version 1.9.0 BETA 2** **Production ready hierarchical architecture + fixes**\n  * See [changelog](changelog.md) for full 1.9.0 list of changes\n\n#### Core Collaboration\n* Realtime sync via WebSocket\n* Realtime chat per room with message persistence\n* Typing indicators, message replies, @mentions\n* Emoji picker and sound notifications\n* Real time multi user cursor engine with smooth transitions\n* Room based sessions with optional passwords\n* Auto destruct rooms (time based or when empty) with countdown display\n* Guest access controls per room\n* All the functions of TheOneFile_Networkening\n\n#### Full User Account System\n* User registration with email verification\n* Secure password login with Argon2id hashing\n* Two factor authentication (TOTP) with backup codes **NEW 1.7**\n* Email change with verification **NEW 1.7**\n* Magic link login (passwordless authentication)\n* Session management with device tracking\n* Multiple active sessions per user\n* Account lockout protection\n\n#### Single Sign On (SSO/OIDC)\n* Sign in with Authentik, Google, GitHub, Microsoft, or any OIDC provider\n* Link multiple SSO providers to one account\n* Auto account linking by email\n* Configurable per provider settings\n* Full OIDC spec compliance (JWT algorithm mapping, discovery validation, sub claim enforcement)\n* Post login redirect persistence across SSO flows\n* Secure account linking re verification\n\n#### Real PING\n* Overrides built in TheOneFileNetworkening \"HTTP PING\" with real server side methods\n* New probe types: ICMP ping, TCP port check, HTTP/HTTPS, DNS and Multi Probe (all combined)\n* Custom user ports\n  \n#### etwork Discovery\n* Added \"Discover Network Hosts\" button to settings panel under Auto Status Checking section (at the bottom)\n* Full subnet discovery with CIDR range input and preset common ranges\n* Multiple range support\n* Port scanning on discovered hosts across 70+ common ports covering infrastructure and self hosted services (more soon)\n* Custom user ports\n* Export hosts to canvas as nodes and racks with all scanned and edited information\n* Admin only mode and public range restrictions available in Admin settings\n\n#### Popular Service + selfh.st/icons Detection\n* Reverse DNS hostname resolution\n* NetBIOS name resolution (Windows network names)\n* mDNS / Avahi multicast DNS resolution\n* HTTP server header detection (Server and X Powered By)\n* SNMPv2c system name and description queries with configurable community string\n* Automatic port to service mapping for 70+ common ports\n* Automatic icon detection and assignment for 70+ services via selfh.st icons\n* Smart icon tagging via popular ports\n* Self hosted media: Plex, Jellyfin, Emby, Sonarr, Radarr, Lidarr, Prowlarr, Bazarr, Overseerr, Tautulli, Ombi, Navidrome, Audiobookshelf, Komga, Jackett, Calibre web\n* Automation and IoT: Home Assistant, Node RED, ESPHome, n8n\n* +tons more\n\n#### DNS Detection\n* Hosts running port 53 are probed further:) to identify the DNS software\n* AdGuard Home detected via /control/status endpoint (should work with password protected instances)\n* Pi-hole detected via /admin/api.php (v5) and /api/ (v6), (should work with password protected instances)\n* Technitium detected via port 5380 presence\n\n#### Docker Detection\n* Looks for docker api, dockge, portainer etc\n* \"Deep Scan\" button will popup next to host\n* This will scan the host more indepth for containers running though docker IF api is not available\n* Docker container names from \"Deep Scan\" are also added as tags\n  \n#### Automated Service Tagging\n* Services column in Network Discovery table shows tags that will be saved with the host\n* Named services (Jellyfin, Grafana, Dockge, etc.) automatic detection\n* Generic port detection (Port 3003, Port 8810, etc.)\n* Docker container names from \"Deep Scan\" are also added as tags\n\n#### Email System\n* SMTP configuration with TLS/STARTTLS support\n* Email verification on signup\n* Password reset via email\n* Magic link authentication\n* Room invitation emails\n* Customizable email templates\n* Email delivery logging\n* Multiple SMTP configurations supported\n\n#### Admin Dashboard\n* Full user management (create, edit, deactivate, delete)\n* Role based access control (admin, user, guest)\n* OIDC provider configuration\n* SMTP configuration management\n* Email template customization\n* Comprehensive audit logging\n* Activity logs per room\n* Email delivery logs\n* System settings management\n\n#### Security & Protection\n* AES 256 GCM encryption for all secrets (including TOTP secrets and backup codes)\n* PBKDF2 key derivation (600,000 iterations) **Enhanced 1.7**\n* Argon2id password hashing\n* TOTP two factor authentication (RFC 6238 compliant) **NEW 1.7**\n* Secure HTTP only cookies with proper autocomplete attributes\n* SameSite cookie policy\n* HSTS headers on all responses\n* WebSocket session tokens\n* WebSocket connection rate limiting per IP\n* IP based rate limiting\n* Email rate limiting\n* Configurable trusted proxy support with environment variable overrides **NEW 1.7**\n* Custom admin path (security through obscurity but not everyone likes the default)\n* Constant time token comparison with length padding\n* SSRF protection on webhook URLs\n* Automatic admin token cleanup\n* Full HTML entity escaping on all user generated content\n* CRLF injection prevention in email headers\n* STARTTLS downgrade protection\n* File upload size limits\n* Generic error messages to prevent user enumeration\n\n#### Responsive & Mobile\n* Full responsive design across all pages (landing, auth, admin dashboard)\n\n#### Rate Limiting\n* Endpoint rate limiting (configurable window and max attempts)\n* Email action rate limiting (signup, password reset, magic link)\n* WebSocket token bucket rate limiting per message type\n\n#### Authentication Modes\n* Open registration\n* Email verification required\n* OIDC only (SSO required)\n* Invite only (admin must create accounts)\n* Closed (no new registrations)\n* Guest room access controls\n\n#### Full Api System [api.md](api.md)\n* REST API with authentication\n* API key management\n* Webhook notifications for events\n\n#### Backup & Recovery\n* Manual and automatic backups\n* Configurable backup intervals\n* Backup retention policies\n* One click restore\n\n## How It Works\n1. Server fetches the latest TheOneFile Networkening HTML from GitHub on startup or upload your own custom template.\n2. When users create or join rooms, the HTML is served with collaboration scripts injected.\n3. All edits sync in realtime via WebSocket.\n4. Data can be saved in the room and can be export into a fully editable and portable version of The One File.\n5. Or data can be exported in all popular editing formats.\n6. Bring it back later and import the HTML, CSV, JSON, or MD right back into your TheOneFile_Verse room."
  },
  {
    "path": "theonefile_verse/api.md",
    "content": "# TheOneFile Verse API Documentation\n\nBase URL: `http://localhost:10101` (or your deployed instance)\n\n## Authentication\n\n### API Key Authentication\n\n```\nAuthorization: Bearer yourapikey\n```\n\nAPI keys have granular permissions:\n- `read`: View rooms and data\n- `write`: Create and modify rooms\n- `admin`: Full administrative access\n\n---\n\n## Public Endpoints\n\n### Check Room Exists\n\n```\nGET /api/room/{roomId}/exists\n```\n\n**Response**\n```json\n{\n  \"exists\": true,\n  \"hasPassword\": false,\n  \"created\": \"2024-01-15T10:30:00.000Z\",\n  \"destruct\": {\n    \"mode\": \"time\",\n    \"value\": 86400000\n  }\n}\n```\n\n### Create Room\n\n```\nPOST /api/room\nContent-Type: application/json\n\n{\n  \"creatorId\": \"optional uuid\",\n  \"password\": \"optional room password\",\n  \"destructMode\": \"time\",\n  \"destructValue\": 86400000,\n  \"topology\": null\n}\n```\n\n**Parameters**\n| Field | Type | Description |\n|-------|------|-------------|\n| creatorId | string (UUID) | Optional. Auto generated if not provided |\n| password | string | Optional. Minimum 4 characters |\n| destructMode | string | `time`, `empty`, or `never` |\n| destructValue | number | Milliseconds until destruction (max 30 days) |\n| topology | object | Optional initial state |\n\n**Response**\n```json\n{\n  \"id\": \"550e8400-e29b-41d4-a716-446655440000\",\n  \"url\": \"/s/550e8400-e29b-41d4-a716-446655440000\",\n  \"hasPassword\": false\n}\n```\n\n### Verify Room Password\n\n```\nPOST /api/room/{roomId}/verify\nContent-Type: application/json\n\n{\n  \"password\": \"room password\"\n}\n```\n\n**Response**\n```json\n{\n  \"valid\": true\n}\n```\n\n### Delete Room (Creator Only)\n\n```\nDELETE /api/room/{roomId}\nContent-Type: application/json\n\n{\n  \"creatorId\": \"creator uuid\"\n}\n```\n\n**Response**\n```json\n{\n  \"deleted\": true\n}\n```\n\n### Get Theme Settings\n\n```\nGET /api/theme\n```\n\n**Response**\n```json\n{\n  \"forcedTheme\": \"user\",\n  \"chatEnabled\": true,\n  \"cursorSharingEnabled\": true,\n  \"nameChangeEnabled\": true\n}\n```\n\n---\n\n### Rooms\n\n**List All Rooms**\n```\nGET /api/admin/rooms\nGET /api/admin/rooms?q=searchterm\n```\n\n**Response**\n```json\n{\n  \"rooms\": [\n    {\n      \"id\": \"550e8400-e29b-41d4-a716-446655440000\",\n      \"created\": \"2024-01-15T10:30:00.000Z\",\n      \"lastActivity\": \"2024-01-15T12:00:00.000Z\",\n      \"hasPassword\": false,\n      \"connectedUsers\": 2,\n      \"destruct\": {\n        \"mode\": \"time\",\n        \"value\": 86400000\n      }\n    }\n  ],\n  \"total\": 1\n}\n```\n\n**Delete Room**\n```\nDELETE /api/admin/rooms/{roomId}\n```\n\n**Response**\n```json\n{\n  \"deleted\": true\n}\n```\n\n### Settings\n\n**Get Settings**\n```\nGET /api/admin/settings\n```\n\n**Response**\n```json\n{\n  \"instancePasswordEnabled\": false,\n  \"instancePasswordSet\": false,\n  \"updateIntervalHours\": 24,\n  \"skipUpdates\": false,\n  \"allowPublicRoomCreation\": true,\n  \"maxRoomsPerInstance\": 0,\n  \"defaultDestructMode\": \"time\",\n  \"defaultDestructHours\": 24,\n  \"forcedTheme\": \"user\",\n  \"rateLimitEnabled\": true,\n  \"rateLimitWindow\": 60,\n  \"rateLimitMaxAttempts\": 10,\n  \"chatEnabled\": true,\n  \"cursorSharingEnabled\": true,\n  \"nameChangeEnabled\": true,\n  \"webhookEnabled\": false,\n  \"webhookUrl\": null,\n  \"backupEnabled\": false,\n  \"backupIntervalHours\": 24,\n  \"backupRetentionCount\": 7\n}\n```\n\n**Update Settings**\n```\nPOST /api/admin/settings\nContent-Type: application/json\n\n{\n  \"chatEnabled\": false,\n  \"webhookEnabled\": true,\n  \"webhookUrl\": \"https://example.com/webhook\"\n}\n```\n\nOnly include fields you want to update.\n\n### Activity Logs\n\n**Get Activity Logs**\n```\nGET /api/admin/activity-logs\nGET /api/admin/activity-logs?room={roomId}\n```\n\n**Response**\n```json\n{\n  \"logs\": [\n    {\n      \"id\": 1,\n      \"timestamp\": \"2024-01-15T10:30:00.000Z\",\n      \"roomId\": \"550e8400-e29b-41d4-a716-446655440000\",\n      \"userId\": \"user uuid\",\n      \"userName\": \"Connor MacLeod\",\n      \"eventType\": \"join\",\n      \"ipAddress\": \"192.168.1.1\"\n    }\n  ]\n}\n```\n\n### Audit Logs\n\n**Get Audit Logs**\n```\nGET /api/admin/audit-logs\nGET /api/admin/audit-logs?q=searchterm\n```\n\n**Response**\n```json\n{\n  \"logs\": [\n    {\n      \"id\": 1,\n      \"timestamp\": \"2024-01-15T10:30:00.000Z\",\n      \"action\": \"room_deleted\",\n      \"actor\": \"admin\",\n      \"actorIp\": \"192.168.1.1\",\n      \"targetType\": \"room\",\n      \"targetId\": \"550e8400-e29b-41d4-a716-446655440000\",\n      \"details\": null\n    }\n  ]\n}\n```\n\n### Backups\n\n**List Backups**\n```\nGET /api/admin/backups\n```\n\n**Response**\n```json\n{\n  \"backups\": [\n    {\n      \"id\": \"backup uuid\",\n      \"filename\": \"backup_2024-01-15T10-30-00.json\",\n      \"createdAt\": \"2024-01-15T10:30:00.000Z\",\n      \"sizeBytes\": 15234,\n      \"roomCount\": 5,\n      \"autoGenerated\": false\n    }\n  ]\n}\n```\n\n**Create Backup**\n```\nPOST /api/admin/backups\n```\n\n**Response**\n```json\n{\n  \"success\": true,\n  \"backup\": {\n    \"id\": \"backup uuid\",\n    \"filename\": \"backup_2024-01-15T10-30-00.json\",\n    \"sizeBytes\": 15234,\n    \"roomCount\": 5\n  }\n}\n```\n\n**Download Backup**\n```\nGET /api/admin/backups/{backupId}/download\n```\n\nReturns the backup file as JSON download.\n\n**Restore Backup**\n```\nPOST /api/admin/backups/{backupId}/restore\n```\n\n**Response**\n```json\n{\n  \"success\": true,\n  \"roomsRestored\": 3\n}\n```\n\n**Delete Backup**\n```\nDELETE /api/admin/backups/{backupId}\n```\n\n**Response**\n```json\n{\n  \"deleted\": true\n}\n```\n\n### Export\n\n**Export All Data**\n```\nGET /api/admin/export\n```\n\nReturns a JSON file containing all rooms and settings.\n\n**Response Structure**\n```json\n{\n  \"version\": 1,\n  \"exportedAt\": \"2024-01-15T10:30:00.000Z\",\n  \"rooms\": [...],\n  \"settings\": {...}\n}\n```\n\n### API Keys\n\n**List API Keys**\n```\nGET /api/admin/api-keys\n```\n\n**Response**\n```json\n{\n  \"keys\": [\n    {\n      \"id\": \"key uuid\",\n      \"name\": \"My Integration\",\n      \"permissions\": [\"read\", \"write\"],\n      \"createdAt\": \"2024-01-15T10:30:00.000Z\",\n      \"lastUsed\": \"2024-01-15T12:00:00.000Z\",\n      \"expiresAt\": null,\n      \"active\": true\n    }\n  ]\n}\n```\n\n**Create API Key**\n```\nPOST /api/admin/api-keys\nContent-Type: application/json\n\n{\n  \"name\": \"My Integration\",\n  \"permissions\": [\"read\", \"write\"],\n  \"expiresInDays\": 30\n}\n```\n\n**Parameters**\n| Field | Type | Description |\n|-------|------|-------------|\n| name | string | Required. Display name for the key |\n| permissions | array | `read`, `write`, `admin` |\n| expiresInDays | number | Optional. 0 or null for no expiration |\n\n**Response**\n```json\n{\n  \"id\": \"key uuid\",\n  \"key\": \"tof_a1b2c3d4e5f6...\",\n  \"name\": \"My Integration\",\n  \"permissions\": [\"read\", \"write\"]\n}\n```\n\n> **Important**: The `key` value is only returned once at creation. Store it securely.\n\n**Revoke API Key**\n```\nDELETE /api/admin/api-keys/{keyId}\n```\n\n**Response**\n```json\n{\n  \"revoked\": true\n}\n```\n\n### Source Management\n\n**Change Source Mode**\n```\nPOST /api/admin/source-mode\nContent-Type: application/json\n\n{\n  \"mode\": \"github\"\n}\n```\n\nModes: `github` (auto update from GitHub) or `local` (manual upload)\n\n**Trigger GitHub Update**\n```\nPOST /api/admin/update\n```\n\n**Response**\n```json\n{\n  \"success\": true,\n  \"size\": 245678\n}\n```\n\n**Upload Local HTML**\n```\nPOST /api/admin/upload-html\nContent-Type: multipart/form-data\n\nfile: [HTML file]\n```\n\n**Response**\n```json\n{\n  \"success\": true,\n  \"size\": 245678,\n  \"edition\": \"networkening\"\n}\n```\n\n---\n\n## WebSocket API\n\n```\nws://localhost:10101/ws/{roomId}\n```\n\n### Message Types\n\n**Join Room**\n```json\n{\n  \"type\": \"join\",\n  \"user\": {\n    \"id\": \"user uuid\",\n    \"name\": \"Connor MacLeod\",\n    \"color\": \"#e63946\"\n  }\n}\n```\n\n**Leave Room**\n```json\n{\n  \"type\": \"leave\",\n  \"userId\": \"user uuid\"\n}\n```\n\n**Presence Update**\n```json\n{\n  \"type\": \"presence\",\n  \"userId\": \"user uuid\",\n  \"selectedNodes\": [\"node-1\", \"node-2\"],\n  \"editingNode\": \"node-1\",\n  \"currentTab\": \"Main\"\n}\n```\n\n**State Sync**\n```json\n{\n  \"type\": \"state\",\n  \"state\": {\n    \"nodeData\": {...},\n    \"edgeData\": {...},\n    \"imageData\": {...},\n    ...\n  }\n}\n```\n\n**Cursor Position**\n```json\n{\n  \"type\": \"cursor\",\n  \"userId\": \"user uuid\",\n  \"x\": 1500,\n  \"y\": 1200,\n  \"isCanvasCoords\": true\n}\n```\n\n**Chat Message**\n```json\n{\n  \"type\": \"chat\",\n  \"userId\": \"user uuid\",\n  \"userName\": \"Connor MacLeod\",\n  \"userColor\": \"#e63946\",\n  \"text\": \"Hello everyone!\",\n  \"timestamp\": 1705312200000\n}\n```\n\n### Server Messages\n\n**Initial State**\n```json\n{\n  \"type\": \"initial-state\",\n  \"state\": {...}\n}\n```\n\n**User List**\n```json\n{\n  \"type\": \"users\",\n  \"users\": [...]\n}\n```\n\n**Name Rejected**\n```json\n{\n  \"type\": \"name-rejected\",\n  \"reason\": \"Name already taken in this room\"\n}\n```\n\n---\n\n## Rate Limiting\n\nWhen rate limiting is enabled, endpoints return `429 Too Many Requests` if limits are exceeded.\n\nDefault limits:\n- 10 attempts per 60 seconds per IP per endpoint\n\nResponse:\n```json\n{\n  \"error\": \"Too many attempts. Try again later.\"\n}\n```\n\n---\n\n## Error Responses\n\nAll errors follow this format:\n\n```json\n{\n  \"error\": \"Error message description\"\n}\n```\n\nCommon HTTP status codes:\n- `400` Bad Request (invalid input)\n- `401` Unauthorized (missing or invalid auth)\n- `403` Forbidden (insufficient permissions)\n- `404` Not Found\n- `429` Too Many Requests (rate limited)\n- `500` Internal Server Error\n\n---\n\n## Webhook Events\n\nWhen webhooks are enabled, POST requests are sent to your configured URL.\n\n**Room Created**\n```json\n{\n  \"event\": \"room_created\",\n  \"timestamp\": \"2024-01-15T10:30:00.000Z\",\n  \"data\": {\n    \"roomId\": \"550e8400-e29b-41d4-a716-446655440000\",\n    \"hasPassword\": false,\n    \"destructMode\": \"time\",\n    \"creatorId\": \"creator uuid\"\n  }\n}\n```\n"
  },
  {
    "path": "theonefile_verse/changelog.md",
    "content": "### TheOneFile_Verse changelog\n\n**4/6/26 Theonefile_verse 1.9.0 Beta 2** : **Production ready hierarchical architecture + fixes**\n\n*Now that most of the core TheOneFile_Verse development is done, I have begun breaking the code into a more production friendly hierarchical structure. This will be completed by 2.0 Stable.*\n\n* **Complete index.ts Refactoring**\n  * Broke the monolithic index.ts into a hierarchical module structure\n  * 14 route handler files organized by domain (auth, admin, rooms, network, public)\n  * Dedicated WebSocket handler\n\n* **Further Production Hardening**\n  * Graceful shutdown with SIGTERM/SIGINT handlers\n  * Global error handling for uncaught exceptions and unhandled rejections\n  * Enhanced /api/health endpoint with database, Redis, and uptime status\n  * Database healthCheck() and Redis ping() for component level monitoring\n  * Proper cleanup of WebSocket connections, timers, and database on shutdown\n\n* **Reverse Proxy Fixes + Hardening**\n  * Fixed all OIDC callback, email verification, and password reset URLs\n  * Added a helper to prevent internal origin leakage behind reverse proxy\n  * CORS origins now parsed once at startup instead of per request\n  * Pre compiled cookie regex for room access checks\n\n* **API Key Authentication + fixes**\n  * Fixed validateApiKey() for programmatic access.\n  * Bearer token auth with tof_ prefix routes to API key validation, all other Bearer tokens route to session validation\n  * Permission enforcement: read, write, and admin permissions checked per endpoint\n  * CSRF validation are now skipped for API key requests\n  * Audit logs track API key identity for all actions\n\n* **Bug Fixes**\n  * Fixed a settings bug where partial settings changes were visible to concurrent requests before validation completed\n  * Fixed initializeTheOneFile() not being awaited on startup, which caused first room requests to fail with \"room unavailable\" before the HTML file finished loading\n  * Fixed XSS vulnerability in network discovery icon alt attributes where icon names were not escaped with escapeHtml()\n  * Fixed hardcoded /admin redirect in setup and migration pages that broke custom admin path configurations\n  * Fixed OIDC email matching not normalizing case before database lookup\n  * Static file serving simplified with lookup table\n  * All innerHTML usage audited and verified safe with escapeHtml()\n\n**3/26/26 Theonefile_verse 1.8.6** : **Further improvements to Network Auto Discovery scanner/editor one page setup**\n\n*Now that most of the core TheOneFile_Verse development is done, I have begun breaking the code into a more production friendly hierarchical structure. This will be completed by 2.0 Stable.*\n\n* **Network Discovery : Connections**\n  * Thanks to [ahmaddxb](https://github.com/ahmaddxb) [#44](https://github.com/gelatinescreams/The-One-File/issues/44)\n  * Added connections section to host editor cards\n  * Connect discovered hosts to each other or to existing canvas nodes\n  * From port and To port added to host editor cards\n  * Icon syling added to host editor cards\n  * Layer added to host editor cards\n  * Notes added to host editor cards\n\n**3/11/26 Theonefile_verse 1.8.5** : **Real ping, mutli network host discovery with selfh.st/icons matching and various other tidbits**\n\n*Now that most of the core TheOneFile_Verse development is done, I have begun breaking the code into a more production friendly hierarchical structure. This will be completed by 2.0 Stable.*\n\n* **Real PING**\n  * Overrides built in TheOneFileNetworkening \"HTTP PING\" with real server side methods\n  * New probe types: ICMP ping, TCP port check, HTTP/HTTPS, DNS and Multi Probe (all combined)\n  * Custom user ports\n  \n* **Network Discovery**\n  * Added \"Discover Network Hosts\" button to settings panel under Auto Status Checking section (at the bottom)\n  * Full subnet discovery with CIDR range input and preset common ranges\n  * Multiple range support\n  * Port scanning on discovered hosts across 70+ common ports covering infrastructure and self hosted services (more soon)\n  * Custom user ports\n  * Export hosts to canvas as nodes and racks with all scanned and edited information\n  * Admin only mode and public range restrictions available in Admin settings\n\n* **Popular Service + selfh.st/icons Detection**\n  * Reverse DNS hostname resolution\n  * NetBIOS name resolution (Windows network names)\n  * mDNS / Avahi multicast DNS resolution\n  * HTTP server header detection (Server and X Powered By)\n  * SNMPv2c system name and description queries with configurable community string\n  * Automatic port to service mapping for 70+ common ports\n  * Automatic icon detection and assignment for 70+ services via selfh.st icons\n  * Smart icon tagging via popular ports\n  * Self hosted media: Plex, Jellyfin, Emby, Sonarr, Radarr, Lidarr, Prowlarr, Bazarr, Overseerr, Tautulli, Ombi, Navidrome, Audiobookshelf, Komga, Jackett, Calibre web\n  * Automation and IoT: Home Assistant, Node RED, ESPHome, n8n\n  * +tons more\n\n* **DNS Detection**\n  * Hosts running port 53 are probed further:) to identify the DNS software\n  * AdGuard Home detected via /control/status endpoint (should work with password protected instances)\n  * Pi-hole detected via /admin/api.php (v5) and /api/ (v6), (should work with password protected instances)\n  * Technitium detected via port 5380 presence\n\n* **Docker Detection**\n  * Looks for docker api, dockge, portainer etc\n  * \"Deep Scan\" button will popup next to host\n  * This will scan the host more indepth for containers running though docker IF api is not available\n  * Docker container names from \"Deep Scan\" are also added as tags\n  \n* **Automated Service Tagging**\n  * Services column in Network Discovery table shows tags that will be saved with the host\n  * Named services (Jellyfin, Grafana, Dockge, etc.) automatic detection\n  * Generic port detection (Port 3003, Port 8810, etc.)\n  * Docker container names from \"Deep Scan\" are also added as tags\n\n**3/4/26 Theonefile_verse 1.8.0** : **Added a few settings, fixed some bugs, annoyances, security and production friendly hierarchical structure**\n\n*Now that most of the core TheOneFile_Verse development is done, I have begun breaking the code into a more production friendly hierarchical structure. This will be completed by 2.0 Stable.*\n\n* **New admin settings.**\n  * Added admin setting to set room themes as default. Custom themes from imported versions will also be able to set as default. Thanks to [ahmaddxb](https://github.com/ahmaddxb) [#45](https://github.com/gelatinescreams/The-One-File/issues/45)\n  * Added admin setting to show hide admin login link on homepage\n  * Added admin setting to force welcome modal to all users even if custom data present\n\n* **Changes + Bug Fixes** \n  * Fixed an issue where custom styles were not being applied after leaving the room\n  * Changed QR code library to local library\n  * Changed crypto.randomUUID() to oidc.generateSecureToken(32) for room ID creation\n  * Backup code login as fallback (single use)\n  * Password required to disable 2FA\n  * AES 256 GCM encrypted secret and backup code storage\n  * Removed all remaining innerHTML references\n  * XSS audit passed\n  * Added futher error logging to docker logs\n  * Tons of security + performance fixes\n  \n* **OIDC Fixes**\n  * Fixed an admin promotion issue when OIDC is the first user registered\n\n* **Redis Fixes**\n  * Changed Redis KEYS to SCAN.\n  * Added redis.conf with safe limits\n\n* **Docker Fixes** \n  * Added docker resource limits to default compose file\n\n**3/2/26 Theonefile_verse 1.7.0** : *2FA, responsive overhaul, email change, path to stable, further security improvements*\n\n*Now that most of the core TheOneFile_Verse development is done, I have begun breaking the code into a more production friendly hierarchical structure. This will be completed by 2.0 Stable.*\n\n* **Two Factor Authentication (TOTP)**\n  * Full TOTP implementation (RFC 6238, HMAC SHA1, 30 second window with ±1 tolerance)\n  * QR code setup flow with manual secret entry fallback\n  * 10 encrypted backup codes generated on enable\n  * 2FA verification on login for both user and admin login pages\n  * Backup code login as fallback (single use)\n  * Password required to disable 2FA\n  * AES 256 GCM encrypted secret and backup code storage\n  * 5 minute expiry on pending 2FA tokens with replay prevention\n\n* **Email Change**\n  * Request email change with password confirmation\n  * Verification email sent to new address\n  * Token hash verification pattern (raw token to user, SHA 256 hash stored)\n  * Uniqueness check on both request and confirmation\n  * 24 hour token expiry\n\n* **Responsive & Mobile Overhaul**\n  * Full responsive design on landing page, all 9 auth page templates, and admin dashboard\n\n* **User Settings Modal**\n  * New account settings modal accessible from user menu\n  * 2FA setup and disable UI with QR code display\n  * Backup codes display after 2FA enable\n  * Email change form with password confirmation\n  * Verification status feedback\n\n* **Security Hardening**\n  * Per token 2FA attempt limit (3 maximum) prevents brute force on TOTP codes\n  * PBKDF2 key derivation iterations increased from 100,000 to 600,000\n  * OIDC email matching defaults to disabled (requires explicit opt in)\n  * typeof input validation on all authentication API endpoints\n  * typeof validation on profile update fields\n  * CSRF validation added to all authenticated endpoints\n  * OIDC token values redacted from debug logs\n  * Proper autocomplete attributes on all auth inputs (email, current password, new password, one time code)\n  * HSTS header now sent unconditionally (removed production mode guard)\n  * WebSocket per message deflate compression enabled\n  * Trusted proxy count and trusted proxy list configurable via TRUSTED_PROXY_COUNT and TRUSTED_PROXIES env vars\n\n* **Bug Fixes**\n  * Fixed registration failing with \"Invalid security token\" in some instances. (CSRF token was never fetched on landing page)\n  * Fixed closeModal throwing error on modals without error elements (settings modal)\n  * Fixed error text elements not showing when set via textContent (added not empty CSS rule)\n  * Chat input alignment changed from flex end to center\n  * Chat emoji changed from text entity to actual emoji character\n  * Room creator_id database index added for search optimization\n\n**2/15/26 Theonefile_verse 1.6.0** : **Security hardening, chat overhaul, UX improvements**\n* **Security Hardening**\n  * Timing safe comparison for legacy password hashes using crypto.timingSafeEqual\n  * Length padded admin password comparison to prevent length oracle attacks\n  * Full HTML entity escaping on chat messages and usernames (& < > \" ')\n  * Content Security Policy headers applied to all pages\n  * OIDC provider name/type XSS protection with esc() in admin panel\n  * PUT method added to CORS allowed methods\n  * 50MB file upload size limit enforced before processing\n  * Generic error messages on registration and disabled accounts to prevent user enumeration\n  * CRLF injection prevention in all email headers (from, to, subject)\n  * STARTTLS downgrade now throws error instead of continuing in plaintext\n  * Template subject variable CRLF sanitization\n\n* **Chat System Overhaul**\n  * Chat message persistence sffrf\n  * Typing indicators with \"user is typing\" display\n  * Message replies with quoted reference\n  * @mention highlighting with notification sound\n  * 500 character counter with visual warning\n  * Emoji picker with most common emojis\n  * Relative timestamps (2m ago, 1h ago)\n\n* **UX Improvements**\n  * iOS safe area inset support on collab bar, modals, toasts\n  * User avatars with colored initials in user list\n  * Connection status indicator (green/yellow/red dot with pulse)\n  * Offline/reconnecting banner with manual reconnect button\n  * Stacking notification toasts with slide animations\n  * Full screen mobile chat at 640px breakpoint\n  * Smooth CSS transitions on remote cursors\n  * Room expiry countdown display for auto destruct rooms\n\n* **Server**\n  * Typing message type with dedicated rate limit (5 bucket, 1/sec refill)\n  * Chat history cleanup on room deletion\n\n**2/14/26 Theonefile_verse 1.5.2** : **Auth flow fixes, error logging, version tracking**\n* **Auth Flow Fixes**\n  * Fixed setup page JavaScript SyntaxError that prevented form submission\n  * Fixed missing SetCookie header on /api/setup route\n  * Fixed password reset form variable shadowing window.confirm\n  * Added defensive semicolons to OIDC button rendering across all forms\n* **Error Logging**\n  * All server side catch blocks now log errors with tagged prefixes\n  * Tags: [Setup], [Login], [Register], [AdminLogin], [API], [Backup], [Update], etc.\n* **Infrastructure**\n  * Docker entrypoint now handles volume permissions automatically via su exec\n  * Database migrations use safe column existence check before ALTER TABLE (BUN FIX)\n  * Added /api/version endpoint for build verification\n  * Version displayed in startup logs\n  * CSRF cookie now includes HttpOnly flag\n\n**2/14/26 Theonefile_verse 1.5.1** : **Fixes and further security hardening**\n* **OIDC/SSO**\n  * JWT signature verification now correctly maps hash algorithms (SHA 256/384/512) per token header\n  * Discovery document validation ensures authorization and token endpoints exist and issuer matches\n  * Sub claim enforcement on all ID tokens per OIDC spec\n  * Openid scope automatically enforced on all provider configurations\n  * Token type validation on token exchange responses\n  * Post login redirect persistence across SSO flows\n  * Account linking reverification ensures session is still valid before linking\n  * Increased entropy in random string generation for required values\n\n* **Security Hardening**\n  * Added CSRF token protection on password reset endpoint\n  * Constant time token comparison using crypto.timingSafeEqual\n  * SSRF protection on webhook URLs (blocks private/internal IP ranges)\n  * WebSocket connection rate limiting per IP address\n  * IP validation on all rate limited endpoints\n  * Automatic admin token cleanup on startup\n  * Rate limit store memory management improvements\n  * Token revocation on session termination\n  * Docker Redis password security via --requirepass\n  * Dockerfile now uses non root USER directive\n\n**1/26/26 Theonefile_verse 1.5.0** *The Identity Update* \n* **Full User Account System**\n  * User registration with email verification\n  * Magic link login for passwordless authentication\n  * Session management with device tracking (browser, OS, IP)\n  * Multiple active sessions per user\n  * Account lockout protection\n  * User profile management (display name, avatar)\n  * User preferences (theme, email notifications)\n  * Self service account deletion\n\n* **Single Sign On (SSO/OIDC)**\n  * Sign in with Authentik, Google, GitHub, Microsoft, or any OpenID Connect provider\n  * Link multiple SSO providers to a single account\n  * Auto account linking by email\n  * Configurable provider settings (scopes, display order, icons)\n  * Secure encrypted token storage\n\n* **Email System**\n  * SMTP configuration with TLS/STARTTLS support\n  * Email verification on signup\n  * Password reset via email\n  * Magic link authentication\n  * Room invitation emails\n  * Customizable email templates with variables\n  * Email delivery logging with status tracking\n  * Multiple SMTP configurations supported\n\n* **Enhanced Security**\n  * AES 256 GCM encryption for all stored secrets\n  * PBKDF2 key derivation (100,000 iterations)\n  * CSRF token protection on all forms\n  * Secure HTTP only cookies\n  * SameSite cookie policy\n  * Production mode with HSTS headers\n  * WebSocket session tokens for authenticated connections\n  * Custom admin path option\n  * Configurable trusted proxy support\n  * Content Security Policy headers\n\n* **Rate Limiting**\n  * Endpoint rate limiting (configurable window and max attempts)\n  * Email action rate limiting (signup, password reset, magic link)\n  * WebSocket token bucket rate limiting per message type\n  * Brute force protection on login attempts\n\n* **Authentication Modes**\n  * Open registration \n  * Email verification required mode\n  * OIDC only\n  * Invite only\n  * Closed\n  * Guest room join controls\n\n* **Admin Dashboard Enhancements**\n  * Full user management (create, edit, deactivate, delete)\n  * Role based access control (admin, user, guest)\n  * OIDC provider configuration UI\n  * SMTP configuration management\n  * Email template customization\n  * Email delivery logs\n  * Auth settings configuration\n\n* **Bug Fixes**\n  * Many bug and security fixes. As above and so below\n\n**1/18/26 Theonefile_verse 1.4.0** *From Alpha to Beta status*\n*  **New Features**\n*  **Database & Storage Migration**\n   * Migrated from flat JSON files to SQLite with WAL mode for better performance\n*  **Redis Integration**\n   * Added Redis support for data with graceful fallback to in memory\n   * Rate limiting via Redis with automatic expiration\n   * Session token storage with TTL\n   * User presence tracking per room\n   * Room state caching\n   * Pub/sub messaging infrastructure for future scaling\n   * Admin Dashboard Enhancements\n*  **Full Api System** [api.md](api.md)\n*  **New Tabs**\n   * Logs tab: View activity logs (joins/leaves) and audit logs (admin actions)\n   * Backups tab: Create, download, restore, and delete backups\n   * API Keys tab: Create and revoke API keys with granular permissions\n\n*  **New Admin Settings**\n   * Toggle chat\n   * Toggle cursor sharing\n   * Toggle name changes globally\n   * Webhooks: Enable webhook notifications with configurable URL\n   * Automatic Backups: Enable scheduled backups with interval and retention settings\n   * Room search/filter by ID or creator\n   * Activity logging (who joined which room, when)\n   * Audit logging for all admin actions\n   * Backup & Recovery System\n   * Manual backup creation via admin panel\n   * Automatic scheduled backups with configurable interval (hours)\n   * Backup retention policy (keeps N most recent auto backups)\n   * One click restore from any backup\n   * Export all rooms as JSON with settings\n   * API Key System\n   * Create API keys with custom names\n   * Granular permissions: read, write, admin\n   * Optional expiration (days)\n   * Last used tracking\n   * Revoke keys instantly\n   * Webhook Notifications\n   * POST notifications to configured URL\n   * Events: room creation (with room ID, password status, destruct mode)\n   * JSON payload with timestamp\n*  **Bug Fixes**\n   * Fixed a rare bug affecting custom image persistence\n   * Fixed rare instances where images would not be restored on tab change\n   * Further Cursor Precision Improvements\n   * Polling every 100ms detects canvas state changes\n   * Reduced throttle from 50ms to 25ms (20Hz → 40Hz)\n   * Various performance and security improvements\n\n**1/17/26 Theonefile_verse 1.3.1** Updated for Image and Notes update\n\n**1/12/26 Theonefile_verse 1.3** Its all coming together now\n  * Completely rewrote the cross user mouse logic. It is much more reliable now.\n  * Fixed an issue where styles and some variables were not saving across user instances\n  * Various bug fixes\n\n**1/6/26 Theonefile_verse 1.2** Flip it and reverse it\n  * Fixed an issue where revere proxies did not pick us WSS correctly\n  * Fixed an issue where username did not persist in some rare cases\n  * various fixes\n\n**1/6/26 Theonefile_verse 1.1** Getting chatty\n  * 1.1 gitea bleeding edge version main lined\n  * adds instant message chat per room\n  * adds duplciate name detection\n  * adds real time mutli user cursor engine\n  * adds current tab to the users top bar name plate\n  * various fixes\n\n**1/6/26 Theonefile_verse intitial upload 1.0**\n  * initial upload\n\n"
  },
  {
    "path": "theonefile_verse/demo-admin.html",
    "content": "<!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\">\n  <title>Admin Demo | TheOneFile_Verse</title>\n  <style>\n    *{box-sizing:border-box;margin:0;padding:0}\n    button,a{-webkit-tap-highlight-color:transparent}\n    :root{\n      --bg:#0d0d0d;\n      --bg-alt:#1a1a1a;\n      --surface:#242424;\n      --border:#333;\n      --text:#e8e8e8;\n      --text-soft:#999;\n      --accent:#c9a227;\n      --accent-hover:#d4b23a;\n      --accent-bg:rgba(201,162,39,0.1);\n      --accent-bg-hover:rgba(201,162,39,0.15);\n      --card-bg:#1a1f2e;\n    }\n    [data-theme=\"light\"]{\n      --bg:#f5f3ef;\n      --bg-alt:#eae7e0;\n      --surface:#fff;\n      --border:#d4d0c8;\n      --text:#1a1a1a;\n      --text-soft:#666;\n      --accent:#996b1f;\n      --accent-hover:#7a5518;\n      --accent-bg:rgba(153,107,31,0.1);\n      --accent-bg-hover:rgba(153,107,31,0.15);\n      --card-bg:#e8e5de;\n    }\n    body{font-family:Inter,system-ui,sans-serif;background:var(--bg);color:var(--text);min-height:100vh;padding:20px;padding-bottom:80px;padding-left:max(20px,env(safe-area-inset-left,20px));padding-right:max(20px,env(safe-area-inset-right,20px));padding-bottom:max(80px,env(safe-area-inset-bottom,80px))}\n    .container{max-width:1200px;margin:0 auto}\n    header{display:flex;justify-content:space-between;align-items:center;margin-bottom:24px;padding-bottom:20px;border-bottom:1px solid var(--border);flex-wrap:wrap;gap:12px}\n    h1{font-size:24px;display:flex;align-items:center;gap:12px}\n    .demo-badge{background:linear-gradient(135deg,#c9a227,#f59e0b);color:#000;padding:4px 12px;border-radius:12px;font-size:12px;font-weight:600;text-transform:uppercase;letter-spacing:0.5px}\n    .header-actions{display:flex;gap:8px;flex-wrap:wrap}\n    .theme-toggle{background:var(--surface);border:1px solid var(--border);color:var(--text);padding:8px 12px;border-radius:6px;cursor:pointer;font-size:14px;min-height:44px}\n    .theme-toggle:hover{background:var(--bg-alt)}\n    .btn{padding:10px 20px;border-radius:8px;font-size:14px;font-weight:500;cursor:pointer;border:none;transition:all 0.15s;text-decoration:none;display:inline-flex;align-items:center;justify-content:center;gap:6px;min-height:44px}\n    .btn-primary{background:var(--accent);color:#fff}.btn-primary:hover{background:var(--accent-hover)}\n    .btn-secondary{background:var(--surface);color:var(--text);border:1px solid var(--border)}.btn-secondary:hover{background:var(--bg-alt)}\n    .btn-danger{background:#dc2626;color:white}.btn-danger:hover{background:#b91c1c}\n    .btn-success{background:#22c55e;color:white}.btn-success:hover{background:#16a34a}\n    .btn-sm{padding:6px 12px;font-size:12px;min-height:36px}\n    .btn:disabled{opacity:0.5;cursor:not-allowed}\n    .tabs{display:flex;gap:4px;margin-bottom:24px;border-bottom:1px solid var(--border);padding-bottom:0;overflow-x:auto;scrollbar-width:none;-webkit-overflow-scrolling:touch}\n    .tabs::-webkit-scrollbar{display:none}\n    .tab{padding:12px 20px;background:transparent;border:none;color:var(--text-soft);font-size:14px;font-weight:500;cursor:pointer;border-bottom:2px solid transparent;margin-bottom:-1px;white-space:nowrap;min-height:44px}\n    .tab:hover{color:var(--text)}\n    .tab.active{color:var(--accent);border-bottom-color:var(--accent)}\n    .tab-content{display:none}\n    .tab-content.active{display:block}\n    .stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:12px;margin-bottom:24px}\n    .stat-card{background:var(--surface);border:1px solid var(--border);border-radius:12px;padding:16px}\n    .stat-value{font-size:28px;font-weight:700;color:var(--accent)}\n    .stat-label{font-size:13px;color:var(--text-soft);margin-top:4px}\n    .section-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:16px;flex-wrap:wrap;gap:12px}\n    .section-title{font-size:18px;font-weight:600;display:flex;align-items:center;gap:8px}\n    .bulk-actions{display:none;gap:8px;align-items:center;flex-wrap:wrap}\n    .bulk-actions.active{display:flex}\n    .selected-count{font-size:13px;color:var(--text-soft);padding:6px 12px;background:var(--surface);border-radius:6px}\n    .room-list{background:var(--surface);border:1px solid var(--border);border-radius:12px;overflow:hidden}\n    .room-header{display:grid;grid-template-columns:40px 1fr 100px 100px 80px 140px;padding:12px 16px;background:var(--bg-alt);font-size:12px;font-weight:600;color:var(--text-soft);text-transform:uppercase;gap:8px;align-items:center}\n    .room-row{display:grid;grid-template-columns:40px 1fr 100px 100px 80px 140px;padding:12px 16px;border-bottom:1px solid var(--border);align-items:center;gap:8px;transition:background 0.15s}\n    .room-row:last-child{border-bottom:none}\n    .room-row:hover{background:var(--accent-bg)}\n    .room-row.selected{background:var(--accent-bg-hover)}\n    .room-checkbox{width:20px;height:20px;cursor:pointer;accent-color:var(--accent)}\n    .room-id{font-family:monospace;font-size:11px;color:var(--text-soft);word-break:break-all}\n    .room-name{font-weight:500;font-size:14px}\n    .badge{display:inline-block;padding:4px 8px;border-radius:12px;font-size:11px;font-weight:500}\n    .badge-green{background:rgba(34,197,94,0.15);color:#22c55e}\n    .badge-yellow{background:rgba(201,162,39,0.15);color:#c9a227}\n    .badge-gray{background:rgba(100,116,139,0.15);color:#94a3b8}\n    .badge-red{background:rgba(239,68,68,0.15);color:#ef4444}\n    .badge-blue{background:rgba(59,130,246,0.15);color:#3b82f6}\n    .room-actions{display:flex;gap:6px;flex-wrap:wrap}\n    #user-list .room-header,#user-list .room-row{grid-template-columns:1fr 100px 120px 100px 140px}\n    .empty-state{text-align:center;padding:60px 20px;color:var(--text-soft)}\n    .empty-state h3{font-size:18px;margin-bottom:8px;color:var(--text)}\n    .settings-section{background:var(--surface);border:1px solid var(--border);border-radius:12px;padding:20px;margin-bottom:16px}\n    .settings-section h3{font-size:16px;font-weight:600;margin-bottom:16px;padding-bottom:12px;border-bottom:1px solid var(--border)}\n    .setting-row{display:flex;justify-content:space-between;align-items:center;padding:12px 0;border-bottom:1px solid var(--border);flex-wrap:wrap;gap:12px}\n    .setting-row:last-child{border-bottom:none}\n    .setting-info{flex:1;min-width:200px}\n    .setting-label{font-size:14px;font-weight:500;margin-bottom:4px}\n    .setting-desc{font-size:12px;color:var(--text-soft)}\n    .setting-control{display:flex;align-items:center;gap:8px}\n    .toggle{position:relative;width:48px;height:26px;background:var(--border);border-radius:13px;cursor:pointer;transition:background 0.2s}\n    .toggle.active{background:var(--accent)}\n    .toggle::after{content:'';position:absolute;top:3px;left:3px;width:20px;height:20px;background:var(--text);border-radius:50%;transition:transform 0.2s}\n    .toggle.active::after{transform:translateX(22px)}\n    input[type=\"text\"],input[type=\"password\"],input[type=\"number\"],input[type=\"email\"],select,textarea{padding:10px 12px;background:var(--bg);border:1px solid var(--border);border-radius:8px;color:var(--text);font-size:14px;min-width:120px}\n    input:focus,select:focus,textarea:focus{outline:none;border-color:var(--accent)}\n    .modal-overlay{display:none;position:fixed;inset:0;background:rgba(0,0,0,0.85);z-index:1000;align-items:center;justify-content:center;padding:20px;padding-bottom:max(20px,env(safe-area-inset-bottom,20px))}\n    .modal-overlay.active{display:flex}\n    .modal{background:var(--surface);border:1px solid var(--border);border-radius:16px;width:100%;max-width:500px;max-height:90vh;overflow-y:auto}\n    .modal-header{display:flex;justify-content:space-between;align-items:center;padding:16px 20px;border-bottom:1px solid var(--border)}\n    .modal-header h3{font-size:18px}\n    .modal-close{background:none;border:none;color:var(--text-soft);font-size:24px;cursor:pointer;padding:4px;min-height:44px;min-width:44px;display:flex;align-items:center;justify-content:center}\n    .modal-body{padding:20px}\n    .info-row{display:flex;justify-content:space-between;padding:12px 0;border-bottom:1px solid var(--border);flex-wrap:wrap;gap:8px}\n    .info-row:last-child{border-bottom:none}\n    .info-label{color:var(--text-soft);font-size:14px}\n    .info-value{font-weight:500;font-size:14px;word-break:break-all}\n    .status-msg{padding:12px;border-radius:8px;margin-bottom:16px;font-size:14px}\n    .status-msg.success{background:rgba(34,197,94,0.15);color:#22c55e}\n    .status-msg.error{background:rgba(239,68,68,0.15);color:#ef4444}\n    .status-msg.info{background:rgba(59,130,246,0.15);color:#3b82f6}\n    .new-key-display{background:var(--bg-alt);border:1px solid var(--accent);border-radius:8px;padding:16px;margin-top:12px}\n    .new-key-display code{background:var(--bg);padding:8px 12px;border-radius:6px;display:block;margin:8px 0;font-size:13px;word-break:break-all;color:var(--accent)}\n    @media(max-width:900px){.room-header,.room-row{grid-template-columns:32px 1fr 80px 100px}.room-header>*:nth-child(4),.room-header>*:nth-child(5),.room-row>*:nth-child(4),.room-row>*:nth-child(5){display:none}#user-list .room-header,#user-list .room-row{grid-template-columns:1fr 80px 100px}#user-list .room-header>*:nth-child(3),#user-list .room-header>*:nth-child(4),#user-list .room-row>*:nth-child(3),#user-list .room-row>*:nth-child(4){display:none}}\n    @media(max-width:600px){body{padding:12px;padding-bottom:80px}header{flex-direction:column;align-items:flex-start}h1{font-size:20px}.stats{grid-template-columns:repeat(2,1fr);gap:8px}.stat-card{padding:12px}.stat-value{font-size:22px}.stat-label{font-size:11px}.room-header,.room-row{grid-template-columns:32px 1fr 90px}.room-header>*:nth-child(3),.room-header>*:nth-child(4),.room-header>*:nth-child(5),.room-row>*:nth-child(3),.room-row>*:nth-child(4),.room-row>*:nth-child(5){display:none}#user-list .room-header,#user-list .room-row{grid-template-columns:1fr 90px}#user-list .room-header>*:nth-child(2),#user-list .room-header>*:nth-child(3),#user-list .room-header>*:nth-child(4),#user-list .room-row>*:nth-child(2),#user-list .room-row>*:nth-child(3),#user-list .room-row>*:nth-child(4){display:none}.room-actions{justify-content:flex-end}.btn{padding:8px 12px;font-size:12px}.btn-sm{padding:6px 10px;font-size:11px}.section-header{flex-direction:column;align-items:flex-start}.bulk-actions{width:100%;justify-content:flex-start}.tabs{overflow-x:auto}.setting-row{flex-direction:column;align-items:flex-start}.setting-control{width:100%}}\n  </style>\n</head>\n<body>\n  <div class=\"container\">\n    <header>\n      <h1>Admin Dashboard <span class=\"demo-badge\">Demo Mode</span></h1>\n      <div class=\"header-actions\">\n        <button class=\"theme-toggle\" id=\"theme-toggle\" onclick=\"toggleTheme()\"><span id=\"theme-icon\"></span></button>\n        <a href=\"/\" class=\"btn btn-secondary\">Back to App</a>\n        <button class=\"btn btn-secondary\" onclick=\"demoAlert('Logout')\">Logout</button>\n      </div>\n    </header>\n\n    <div class=\"status-msg info\" style=\"margin-bottom:20px\">\n      This is a demo admin panel with sample data. All buttons are non functional for security.\n    </div>\n\n    <div class=\"tabs\">\n      <button class=\"tab active\" onclick=\"showTab('rooms')\">Rooms</button>\n      <button class=\"tab\" onclick=\"showTab('users')\">Users</button>\n      <button class=\"tab\" onclick=\"showTab('auth')\">Authentication</button>\n      <button class=\"tab\" onclick=\"showTab('settings')\">Settings</button>\n      <button class=\"tab\" onclick=\"showTab('logs')\">Logs</button>\n      <button class=\"tab\" onclick=\"showTab('backups')\">Backups</button>\n      <button class=\"tab\" onclick=\"showTab('apikeys')\">API Keys</button>\n    </div>\n\n\n    <div id=\"tab-rooms\" class=\"tab-content active\">\n      <div class=\"stats\">\n        <div class=\"stat-card\"><div class=\"stat-value\">12</div><div class=\"stat-label\">Total Rooms</div></div>\n        <div class=\"stat-card\"><div class=\"stat-value\">5</div><div class=\"stat-label\">Active</div></div>\n        <div class=\"stat-card\"><div class=\"stat-value\">8</div><div class=\"stat-label\">Protected</div></div>\n        <div class=\"stat-card\"><div class=\"stat-value\">23</div><div class=\"stat-label\">Users Online</div></div>\n      </div>\n      <div class=\"section-header\">\n        <div style=\"display:flex;align-items:center;gap:12px;flex-wrap:wrap\">\n          <h2 class=\"section-title\">All Rooms</h2>\n          <input type=\"text\" placeholder=\"Search rooms...\" style=\"padding:8px 12px;width:200px\">\n        </div>\n        <div class=\"bulk-actions\" id=\"bulk-actions\">\n          <span class=\"selected-count\">0 selected</span>\n          <button class=\"btn btn-danger btn-sm\" onclick=\"demoAlert('Delete Selected')\">Delete Selected</button>\n          <button class=\"btn btn-secondary btn-sm\">Clear</button>\n        </div>\n      </div>\n      <div class=\"room-list\" id=\"room-list\">\n        <div class=\"room-header\">\n          <input type=\"checkbox\" class=\"room-checkbox\">\n          <span>Room</span>\n          <span>Users</span>\n          <span>Created</span>\n          <span>Password</span>\n          <span>Actions</span>\n        </div>\n        <div class=\"room-row\">\n          <input type=\"checkbox\" class=\"room-checkbox\">\n          <div><div class=\"room-name\">Room</div><div class=\"room-id\">a1b2c3d4-e5f6-7890-abcd-ef1234567890</div></div>\n          <div><span class=\"badge badge-green\">4</span></div>\n          <div>Jan 15, 2026</div>\n          <div><span class=\"badge badge-yellow\">Yes</span></div>\n          <div class=\"room-actions\">\n            <button class=\"btn btn-secondary btn-sm\" onclick=\"showRoomModal('a1b2c3d4-e5f6-7890-abcd-ef1234567890', 'Jan 15, 2026', 'Jan 26, 2026 11:45 AM', 4, true, 'time', 24)\">View</button>\n            <button class=\"btn btn-primary btn-sm\" onclick=\"demoAlert('Join Room')\">Join</button>\n            <button class=\"btn btn-danger btn-sm\" onclick=\"demoAlert('Delete Room')\">Del</button>\n          </div>\n        </div>\n        <div class=\"room-row\">\n          <input type=\"checkbox\" class=\"room-checkbox\">\n          <div><div class=\"room-name\">Room</div><div class=\"room-id\">b208667b-7a9e-4a18-ac98-5cb6e73bb669</div></div>\n          <div><span class=\"badge badge-green\">7</span></div>\n          <div>Jan 18, 2026</div>\n          <div><span class=\"badge badge-yellow\">Yes</span></div>\n          <div class=\"room-actions\">\n            <button class=\"btn btn-secondary btn-sm\" onclick=\"showRoomModal('b208667b-7a9e-4a18-ac98-5cb6e73bb669', 'Jan 18, 2026', 'Jan 26, 2026 11:30 AM', 7, true, 'empty', 0)\">View</button>\n            <button class=\"btn btn-primary btn-sm\" onclick=\"demoAlert('Join Room')\">Join</button>\n            <button class=\"btn btn-danger btn-sm\" onclick=\"demoAlert('Delete Room')\">Del</button>\n          </div>\n        </div>\n        <div class=\"room-row\">\n          <input type=\"checkbox\" class=\"room-checkbox\">\n          <div><div class=\"room-name\">Room</div><div class=\"room-id\">c3d4e5f6-7890-abcd-ef12-345678901234</div></div>\n          <div><span class=\"badge badge-green\">2</span></div>\n          <div>Jan 20, 2026</div>\n          <div><span class=\"badge badge-gray\">No</span></div>\n          <div class=\"room-actions\">\n            <button class=\"btn btn-secondary btn-sm\" onclick=\"showRoomModal('c3d4e5f6-7890-abcd-ef12-345678901234', 'Jan 20, 2026', 'Jan 26, 2026 10:15 AM', 2, false, 'never', 0)\">View</button>\n            <button class=\"btn btn-primary btn-sm\" onclick=\"demoAlert('Join Room')\">Join</button>\n            <button class=\"btn btn-danger btn-sm\" onclick=\"demoAlert('Delete Room')\">Del</button>\n          </div>\n        </div>\n        <div class=\"room-row\">\n          <input type=\"checkbox\" class=\"room-checkbox\">\n          <div><div class=\"room-name\">Room</div><div class=\"room-id\">d4e5f678-90ab-cdef-1234-567890123456</div></div>\n          <div><span class=\"badge badge-green\">5</span></div>\n          <div>Jan 22, 2026</div>\n          <div><span class=\"badge badge-yellow\">Yes</span></div>\n          <div class=\"room-actions\">\n            <button class=\"btn btn-secondary btn-sm\" onclick=\"showRoomModal('d4e5f678-90ab-cdef-1234-567890123456', 'Jan 22, 2026', 'Jan 26, 2026 9:00 AM', 5, true, 'time', 48)\">View</button>\n            <button class=\"btn btn-primary btn-sm\" onclick=\"demoAlert('Join Room')\">Join</button>\n            <button class=\"btn btn-danger btn-sm\" onclick=\"demoAlert('Delete Room')\">Del</button>\n          </div>\n        </div>\n        <div class=\"room-row\">\n          <input type=\"checkbox\" class=\"room-checkbox\">\n          <div><div class=\"room-name\">Room</div><div class=\"room-id\">e5f67890-abcd-ef12-3456-789012345678</div></div>\n          <div><span class=\"badge badge-green\">5</span></div>\n          <div>Jan 24, 2026</div>\n          <div><span class=\"badge badge-yellow\">Yes</span></div>\n          <div class=\"room-actions\">\n            <button class=\"btn btn-secondary btn-sm\" onclick=\"showRoomModal('e5f67890-abcd-ef12-3456-789012345678', 'Jan 24, 2026', 'Jan 25, 2026 4:00 PM', 5, true, 'time', 24)\">View</button>\n            <button class=\"btn btn-primary btn-sm\" onclick=\"demoAlert('Join Room')\">Join</button>\n            <button class=\"btn btn-danger btn-sm\" onclick=\"demoAlert('Delete Room')\">Del</button>\n          </div>\n        </div>\n        <div class=\"room-row\">\n          <input type=\"checkbox\" class=\"room-checkbox\">\n          <div><div class=\"room-name\">Room</div><div class=\"room-id\">f6789012-cdef-3456-7890-abcdef123456</div></div>\n          <div><span class=\"badge badge-gray\">0</span></div>\n          <div>Jan 25, 2026</div>\n          <div><span class=\"badge badge-yellow\">Yes</span></div>\n          <div class=\"room-actions\">\n            <button class=\"btn btn-secondary btn-sm\" onclick=\"showRoomModal('f6789012-cdef-3456-7890-abcdef123456', 'Jan 25, 2026', 'Jan 25, 2026 2:00 PM', 0, true, 'empty', 0)\">View</button>\n            <button class=\"btn btn-primary btn-sm\" onclick=\"demoAlert('Join Room')\">Join</button>\n            <button class=\"btn btn-danger btn-sm\" onclick=\"demoAlert('Delete Room')\">Del</button>\n          </div>\n        </div>\n      </div>\n    </div>\n\n\n    <div id=\"tab-users\" class=\"tab-content\">\n      <div class=\"section-header\">\n        <div style=\"display:flex;align-items:center;gap:12px;flex-wrap:wrap\">\n          <h2 class=\"section-title\">User Management</h2>\n          <input type=\"text\" placeholder=\"Search users...\" style=\"padding:8px 12px;width:200px\">\n        </div>\n        <button class=\"btn btn-primary\" onclick=\"showModal('user-modal')\">Create User</button>\n      </div>\n      <div class=\"room-list\" id=\"user-list\">\n        <div class=\"room-header\">\n          <span>User</span>\n          <span>Role</span>\n          <span>Status</span>\n          <span>Created</span>\n          <span>Actions</span>\n        </div>\n        <div class=\"room-row\">\n          <div><div class=\"room-name\">Administrator</div><div class=\"room-id\">admin@example.com</div></div>\n          <div><span class=\"badge badge-yellow\">Admin</span></div>\n          <div><span class=\"badge badge-green\">Active</span></div>\n          <div>Jan 1, 2026</div>\n          <div class=\"room-actions\">\n            <button class=\"btn btn-secondary btn-sm\" onclick=\"showEditUserModal('admin@example.com', 'Administrator', 'admin', true, true)\">Edit</button>\n            <button class=\"btn btn-danger btn-sm\" onclick=\"demoAlert('Delete User')\">Del</button>\n          </div>\n        </div>\n        <div class=\"room-row\">\n          <div><div class=\"room-name\">John Doe</div><div class=\"room-id\">john.doe@company.com</div></div>\n          <div><span class=\"badge badge-gray\">User</span></div>\n          <div><span class=\"badge badge-green\">Active</span></div>\n          <div>Jan 5, 2026</div>\n          <div class=\"room-actions\">\n            <button class=\"btn btn-secondary btn-sm\" onclick=\"showEditUserModal('john.doe@company.com', 'John Doe', 'user', true, true)\">Edit</button>\n            <button class=\"btn btn-danger btn-sm\" onclick=\"demoAlert('Delete User')\">Del</button>\n          </div>\n        </div>\n        <div class=\"room-row\">\n          <div><div class=\"room-name\">Jane Smith</div><div class=\"room-id\">jane.smith@company.com</div></div>\n          <div><span class=\"badge badge-gray\">User</span></div>\n          <div><span class=\"badge badge-green\">Active</span></div>\n          <div>Jan 8, 2026</div>\n          <div class=\"room-actions\">\n            <button class=\"btn btn-secondary btn-sm\" onclick=\"showEditUserModal('jane.smith@company.com', 'Jane Smith', 'user', true, true)\">Edit</button>\n            <button class=\"btn btn-danger btn-sm\" onclick=\"demoAlert('Delete User')\">Del</button>\n          </div>\n        </div>\n        <div class=\"room-row\">\n          <div><div class=\"room-name\">Bob Wilson</div><div class=\"room-id\">bob.wilson@external.org</div></div>\n          <div><span class=\"badge badge-gray\">User</span></div>\n          <div><span class=\"badge badge-green\">Active</span> <span class=\"badge badge-yellow\">Unverified</span></div>\n          <div>Jan 12, 2026</div>\n          <div class=\"room-actions\">\n            <button class=\"btn btn-secondary btn-sm\" onclick=\"showEditUserModal('bob.wilson@external.org', 'Bob Wilson', 'user', true, false)\">Edit</button>\n            <button class=\"btn btn-danger btn-sm\" onclick=\"demoAlert('Delete User')\">Del</button>\n          </div>\n        </div>\n        <div class=\"room-row\">\n          <div><div class=\"room-name\">Sarah Connor</div><div class=\"room-id\">sarah@skynet.io</div></div>\n          <div><span class=\"badge badge-yellow\">Admin</span></div>\n          <div><span class=\"badge badge-green\">Active</span></div>\n          <div>Jan 15, 2026</div>\n          <div class=\"room-actions\">\n            <button class=\"btn btn-secondary btn-sm\" onclick=\"showEditUserModal('sarah@skynet.io', 'Sarah Connor', 'admin', true, true)\">Edit</button>\n            <button class=\"btn btn-danger btn-sm\" onclick=\"demoAlert('Delete User')\">Del</button>\n          </div>\n        </div>\n        <div class=\"room-row\">\n          <div><div class=\"room-name\">Mike Chen</div><div class=\"room-id\">mike.chen@startup.co</div></div>\n          <div><span class=\"badge badge-gray\">User</span></div>\n          <div><span class=\"badge badge-gray\">Inactive</span></div>\n          <div>Jan 18, 2026</div>\n          <div class=\"room-actions\">\n            <button class=\"btn btn-secondary btn-sm\" onclick=\"showEditUserModal('mike.chen@startup.co', 'Mike Chen', 'user', false, true)\">Edit</button>\n            <button class=\"btn btn-danger btn-sm\" onclick=\"demoAlert('Delete User')\">Del</button>\n          </div>\n        </div>\n      </div>\n    </div>\n\n\n    <div id=\"tab-auth\" class=\"tab-content\">\n      <div class=\"settings-section\">\n        <h3>Authentication Mode</h3>\n        <div class=\"setting-row\">\n          <div class=\"setting-info\"><div class=\"setting-label\">Default Auth Mode</div><div class=\"setting-desc\">How users can access the system</div></div>\n          <div class=\"setting-control\">\n            <select onchange=\"demoAlert('Change Auth Mode')\">\n              <option value=\"open\">Open (Anyone can register)</option>\n              <option value=\"registration\" selected>Registration Required</option>\n              <option value=\"oidc_only\">OIDC Only</option>\n              <option value=\"invite_only\">Invite Only</option>\n              <option value=\"closed\">Closed (No new users)</option>\n            </select>\n          </div>\n        </div>\n        <div class=\"setting-row\">\n          <div class=\"setting-info\"><div class=\"setting-label\">Require Email Verification</div><div class=\"setting-desc\">Users must verify email before accessing</div></div>\n          <div class=\"setting-control\"><div class=\"toggle active\" onclick=\"demoAlert('Toggle Email Verification')\"></div></div>\n        </div>\n        <div class=\"setting-row\">\n          <div class=\"setting-info\"><div class=\"setting-label\">Allow Magic Link Login</div><div class=\"setting-desc\">Users can login via email link</div></div>\n          <div class=\"setting-control\"><div class=\"toggle active\" onclick=\"demoAlert('Toggle Magic Link')\"></div></div>\n        </div>\n        <div class=\"setting-row\">\n          <div class=\"setting-info\"><div class=\"setting-label\">Match OIDC Emails</div><div class=\"setting-desc\">Auto link OIDC accounts with matching email. <span style=\"color:#f59e0b\">Only enable with trusted providers</span></div></div>\n          <div class=\"setting-control\"><div class=\"toggle\" onclick=\"demoAlert('Toggle OIDC Email Match')\"></div></div>\n        </div>\n      </div>\n\n      <div class=\"settings-section\">\n        <h3>Security</h3>\n        <div class=\"setting-row\">\n          <div class=\"setting-info\"><div class=\"setting-label\">Production Mode</div><div class=\"setting-desc\">Enable HTTPS secure cookies. <span style=\"color:#ef4444\">Required for production</span></div></div>\n          <div class=\"setting-control\"><div class=\"toggle active\" onclick=\"demoAlert('Toggle Production Mode')\"></div></div>\n        </div>\n        <div class=\"setting-row\">\n          <div class=\"setting-info\"><div class=\"setting-label\">ID Token Max Age (hours)</div><div class=\"setting-desc\">Maximum age for OIDC ID tokens before they are considered too old</div></div>\n          <div class=\"setting-control\"><input type=\"number\" value=\"2\" min=\"1\" max=\"168\" style=\"width:80px\" onchange=\"demoAlert('Update Token Age')\"></div>\n        </div>\n        <div class=\"setting-row\">\n          <div class=\"setting-info\"><div class=\"setting-label\">Email Rate Limit Window (seconds)</div><div class=\"setting-desc\">Time window for email rate limiting</div></div>\n          <div class=\"setting-control\"><input type=\"number\" value=\"300\" min=\"60\" max=\"3600\" style=\"width:80px\" onchange=\"demoAlert('Update Rate Limit')\"></div>\n        </div>\n        <div class=\"setting-row\">\n          <div class=\"setting-info\"><div class=\"setting-label\">Email Rate Limit Max Attempts</div><div class=\"setting-desc\">Maximum email requests per address within window</div></div>\n          <div class=\"setting-control\"><input type=\"number\" value=\"3\" min=\"1\" max=\"20\" style=\"width:80px\" onchange=\"demoAlert('Update Max Attempts')\"></div>\n        </div>\n      </div>\n\n      <div class=\"settings-section\">\n        <h3>Guest Access</h3>\n        <div class=\"setting-row\">\n          <div class=\"setting-info\"><div class=\"setting-label\">Allow Guest Room Creation</div><div class=\"setting-desc\">Unregistered users can create rooms</div></div>\n          <div class=\"setting-control\"><div class=\"toggle active\" onclick=\"demoAlert('Toggle Guest Room Creation')\"></div></div>\n        </div>\n        <div class=\"setting-row\">\n          <div class=\"setting-info\"><div class=\"setting-label\">Allow Guest Room Join</div><div class=\"setting-desc\">Unregistered users can join rooms</div></div>\n          <div class=\"setting-control\"><div class=\"toggle active\" onclick=\"demoAlert('Toggle Guest Room Join')\"></div></div>\n        </div>\n        <div class=\"setting-row\">\n          <div class=\"setting-info\"><div class=\"setting-label\">Room Creator Guest Setting</div><div class=\"setting-desc\">Let room creators choose guest access per room</div></div>\n          <div class=\"setting-control\"><div class=\"toggle active\" onclick=\"demoAlert('Toggle Room Creator Guest Setting')\"></div></div>\n        </div>\n        <div class=\"setting-row\">\n          <div class=\"setting-info\"><div class=\"setting-label\">Share Button</div><div class=\"setting-desc\">Show share button in rooms</div></div>\n          <div class=\"setting-control\"><div class=\"toggle active\" onclick=\"demoAlert('Toggle Share Button')\"></div></div>\n        </div>\n      </div>\n\n      <div class=\"settings-section\">\n        <h3>OIDC Providers</h3>\n        <div style=\"display:flex;justify-content:space-between;align-items:center;margin-bottom:16px\">\n          <p style=\"color:var(--text-soft);font-size:13px\">Configure OpenID Connect providers for SSO</p>\n          <button class=\"btn btn-primary btn-sm\" onclick=\"showOidcModal()\">Add Provider</button>\n        </div>\n        <div>\n          <div style=\"display:flex;justify-content:space-between;align-items:center;padding:12px 0;border-bottom:1px solid var(--border)\">\n            <div>\n              <div style=\"font-weight:500\">Google</div>\n              <div style=\"font-size:12px;color:var(--text-soft)\">generic | <span style=\"color:#22c55e\">Active</span></div>\n            </div>\n            <div style=\"display:flex;gap:6px\">\n              <button class=\"btn btn-sm btn-secondary\" onclick=\"showOidcModal('Google', 'generic', 'google-client-id-xxx', '', 'https://accounts.google.com', 'https://accounts.google.com/o/oauth2/v2/auth', 'https://oauth2.googleapis.com/token', 'https://openidconnect.googleapis.com/v1/userinfo', 'openid email profile', true)\">Edit</button>\n              <button class=\"btn btn-sm btn-danger\" onclick=\"demoAlert('Delete Google Provider')\">Delete</button>\n            </div>\n          </div>\n          <div style=\"display:flex;justify-content:space-between;align-items:center;padding:12px 0;border-bottom:1px solid var(--border)\">\n            <div>\n              <div style=\"font-weight:500\">Authentik (Internal)</div>\n              <div style=\"font-size:12px;color:var(--text-soft)\">authentik | <span style=\"color:#22c55e\">Active</span></div>\n            </div>\n            <div style=\"display:flex;gap:6px\">\n              <button class=\"btn btn-sm btn-secondary\" onclick=\"showOidcModal('Authentik (Internal)', 'authentik', 'authentik-client-xxx', '', 'https://auth.internal.lan', '', '', '', 'openid email profile', true)\">Edit</button>\n              <button class=\"btn btn-sm btn-danger\" onclick=\"demoAlert('Delete Authentik Provider')\">Delete</button>\n            </div>\n          </div>\n          <div style=\"display:flex;justify-content:space-between;align-items:center;padding:12px 0\">\n            <div>\n              <div style=\"font-weight:500\">GitHub</div>\n              <div style=\"font-size:12px;color:var(--text-soft)\">generic | <span style=\"color:#94a3b8\">Inactive</span></div>\n            </div>\n            <div style=\"display:flex;gap:6px\">\n              <button class=\"btn btn-sm btn-secondary\" onclick=\"showOidcModal('GitHub', 'generic', 'github-client-xxx', '', 'https://github.com', 'https://github.com/login/oauth/authorize', 'https://github.com/login/oauth/access_token', 'https://api.github.com/user', 'read:user user:email', false)\">Edit</button>\n              <button class=\"btn btn-sm btn-danger\" onclick=\"demoAlert('Delete GitHub Provider')\">Delete</button>\n            </div>\n          </div>\n        </div>\n      </div>\n\n      <div class=\"settings-section\">\n        <h3>SMTP Configuration</h3>\n        <div style=\"display:flex;justify-content:space-between;align-items:center;margin-bottom:16px\">\n          <p style=\"color:var(--text-soft);font-size:13px\">Configure email delivery for verification and notifications</p>\n          <button class=\"btn btn-primary btn-sm\" onclick=\"showSmtpModal()\">Add SMTP</button>\n        </div>\n        <div>\n          <div style=\"display:flex;justify-content:space-between;align-items:center;padding:12px 0;border-bottom:1px solid var(--border)\">\n            <div>\n              <div style=\"font-weight:500\">Primary SMTP <span class=\"badge badge-green\">Default</span></div>\n              <div style=\"font-size:12px;color:var(--text-soft)\">smtp.sendgrid.net:587 (starttls) | <span style=\"color:#22c55e\">Active</span></div>\n            </div>\n            <div style=\"display:flex;gap:6px\">\n              <button class=\"btn btn-sm btn-secondary\" onclick=\"showSmtpModal('Primary SMTP', 'smtp.sendgrid.net', 587, 'starttls', 'apikey', '', 'noreply@example.com', 'TheOneFile_Verse', true, true)\">Edit</button>\n              <button class=\"btn btn-sm btn-danger\" onclick=\"demoAlert('Delete Primary SMTP')\">Delete</button>\n            </div>\n          </div>\n          <div style=\"display:flex;justify-content:space-between;align-items:center;padding:12px 0\">\n            <div>\n              <div style=\"font-weight:500\">Backup SMTP</div>\n              <div style=\"font-size:12px;color:var(--text-soft)\">smtp.mailgun.org:465 (tls) | <span style=\"color:#22c55e\">Active</span></div>\n            </div>\n            <div style=\"display:flex;gap:6px\">\n              <button class=\"btn btn-sm btn-secondary\" onclick=\"showSmtpModal('Backup SMTP', 'smtp.mailgun.org', 465, 'tls', 'postmaster@mg.example.com', '', 'noreply@mg.example.com', 'TheOneFile_Verse', false, true)\">Edit</button>\n              <button class=\"btn btn-sm btn-danger\" onclick=\"demoAlert('Delete Backup SMTP')\">Delete</button>\n            </div>\n          </div>\n        </div>\n      </div>\n\n      <div class=\"settings-section\">\n        <h3>Email Templates</h3>\n        <div>\n          <div style=\"display:flex;justify-content:space-between;align-items:center;padding:12px 0;border-bottom:1px solid var(--border)\">\n            <div>\n              <div style=\"font-weight:500\">Email Verification</div>\n              <div style=\"font-size:12px;color:var(--text-soft)\">Subject: Verify your email | TheOneFile_Verse</div>\n            </div>\n            <button class=\"btn btn-sm btn-secondary\" onclick=\"showTemplateModal('Email Verification', 'Verify your email | {{appName}}')\">Edit</button>\n          </div>\n          <div style=\"display:flex;justify-content:space-between;align-items:center;padding:12px 0;border-bottom:1px solid var(--border)\">\n            <div>\n              <div style=\"font-weight:500\">Password Reset</div>\n              <div style=\"font-size:12px;color:var(--text-soft)\">Subject: Reset your password | TheOneFile_Verse</div>\n            </div>\n            <button class=\"btn btn-sm btn-secondary\" onclick=\"showTemplateModal('Password Reset', 'Reset your password | {{appName}}')\">Edit</button>\n          </div>\n          <div style=\"display:flex;justify-content:space-between;align-items:center;padding:12px 0;border-bottom:1px solid var(--border)\">\n            <div>\n              <div style=\"font-weight:500\">Magic Link</div>\n              <div style=\"font-size:12px;color:var(--text-soft)\">Subject: Your login link | TheOneFile_Verse</div>\n            </div>\n            <button class=\"btn btn-sm btn-secondary\" onclick=\"showTemplateModal('Magic Link', 'Your login link | {{appName}}')\">Edit</button>\n          </div>\n          <div style=\"display:flex;justify-content:space-between;align-items:center;padding:12px 0\">\n            <div>\n              <div style=\"font-weight:500\">Room Invitation</div>\n              <div style=\"font-size:12px;color:var(--text-soft)\">Subject: You've been invited to collaborate | TheOneFile_Verse</div>\n            </div>\n            <button class=\"btn btn-sm btn-secondary\" onclick=\"showTemplateModal('Room Invitation', 'You\\\\\\'ve been invited to collaborate | {{appName}}')\">Edit</button>\n          </div>\n        </div>\n      </div>\n\n      <div class=\"settings-section\">\n        <h3>Email Logs</h3>\n        <div style=\"margin-bottom:12px;display:flex;gap:12px;align-items:center\">\n          <input type=\"text\" placeholder=\"Filter by email...\" style=\"width:250px\">\n          <button class=\"btn btn-sm btn-secondary\" onclick=\"demoAlert('Clear Email Logs')\">Clear All</button>\n        </div>\n        <div style=\"max-height:200px;overflow-y:auto;scrollbar-width:none\">\n          <div style=\"padding:8px 0;border-bottom:1px solid var(--border);font-size:13px\">\n            <span style=\"color:var(--text-soft)\">1/26/2026, 10:32 AM</span> <span class=\"badge badge-green\">sent</span> john.doe@company.com <span style=\"color:var(--text-soft)\">Verify your email | TheOneFile_Verse</span>\n          </div>\n          <div style=\"padding:8px 0;border-bottom:1px solid var(--border);font-size:13px\">\n            <span style=\"color:var(--text-soft)\">1/26/2026, 9:15 AM</span> <span class=\"badge badge-green\">sent</span> jane.smith@company.com <span style=\"color:var(--text-soft)\">Your login link | TheOneFile_Verse</span>\n          </div>\n          <div style=\"padding:8px 0;border-bottom:1px solid var(--border);font-size:13px\">\n            <span style=\"color:var(--text-soft)\">1/25/2026, 4:45 PM</span> <span class=\"badge badge-green\">sent</span> bob.wilson@external.org <span style=\"color:var(--text-soft)\">Verify your email | TheOneFile_Verse</span>\n          </div>\n          <div style=\"padding:8px 0;border-bottom:1px solid var(--border);font-size:13px\">\n            <span style=\"color:var(--text-soft)\">1/25/2026, 2:20 PM</span> <span class=\"badge badge-red\">failed</span> invalid@nowhere.xyz <span style=\"color:var(--text-soft)\">Verify your email | TheOneFile_Verse</span> <span style=\"color:#ef4444\">Recipient rejected</span>\n          </div>\n          <div style=\"padding:8px 0;font-size:13px\">\n            <span style=\"color:var(--text-soft)\">1/24/2026, 11:00 AM</span> <span class=\"badge badge-green\">sent</span> sarah@skynet.io <span style=\"color:var(--text-soft)\">You've been invited to collaborate | TheOneFile_Verse</span>\n          </div>\n        </div>\n      </div>\n    </div>\n\n\n    <div id=\"tab-settings\" class=\"tab-content\">\n      <div class=\"settings-section\">\n        <h3>Instance Access</h3>\n        <div class=\"setting-row\">\n          <div class=\"setting-info\"><div class=\"setting-label\">Password Lock</div><div class=\"setting-desc\">Require password to access the entire instance</div></div>\n          <div class=\"setting-control\"><div class=\"toggle\" onclick=\"demoAlert('Toggle Password Lock')\"></div></div>\n        </div>\n        <div class=\"setting-row\">\n          <div class=\"setting-info\"><div class=\"setting-label\">Instance Password</div><div class=\"setting-desc\">Set a password for instance access</div></div>\n          <div class=\"setting-control\">\n            <input type=\"password\" placeholder=\"New password\" style=\"width:180px\">\n            <button class=\"btn btn-sm btn-primary\" onclick=\"demoAlert('Set Instance Password')\">Set</button>\n          </div>\n        </div>\n        <div class=\"setting-row\">\n          <div class=\"setting-info\"><div class=\"setting-label\">Admin Panel Path</div><div class=\"setting-desc\">Custom URL path for the admin panel (default: admin)</div></div>\n          <div class=\"setting-control\">\n            <span style=\"color:#8892a0;font-size:12px\">/</span>\n            <input type=\"text\" value=\"admin\" placeholder=\"admin\" style=\"width:150px\">\n            <button class=\"btn btn-sm btn-primary\" onclick=\"demoAlert('Save Admin Path')\">Save</button>\n          </div>\n        </div>\n        <div class=\"setting-row\">\n          <div class=\"setting-info\"><div class=\"setting-label\"></div><div class=\"setting-desc\" style=\"color:#4ade80\">Current path: /admin</div></div>\n        </div>\n        <div class=\"setting-row\">\n          <div class=\"setting-info\"><div class=\"setting-label\">Show Admin Link</div><div class=\"setting-desc\">Show admin panel link in the landing page footer</div></div>\n          <div class=\"setting-control\"><div class=\"toggle active\" onclick=\"demoAlert('Toggle Show Admin Link')\"></div></div>\n        </div>\n      </div>\n\n      <div class=\"settings-section\">\n        <h3>TheOneFile Source</h3>\n        <div class=\"setting-row\">\n          <div class=\"setting-info\"><div class=\"setting-label\">Source Mode</div><div class=\"setting-desc\">Choose where to load TheOneFile from</div></div>\n          <div class=\"setting-control\">\n            <select onchange=\"demoAlert('Change Source Mode')\">\n              <option value=\"github\" selected>GitHub (Auto-Update)</option>\n              <option value=\"local\">Local (Manual Upload)</option>\n            </select>\n          </div>\n        </div>\n        <div class=\"setting-row\">\n          <div class=\"setting-info\"><div class=\"setting-label\">Update Interval</div><div class=\"setting-desc\">Hours between auto updates (0 = manual only)</div></div>\n          <div class=\"setting-control\">\n            <input type=\"number\" value=\"24\" min=\"0\" max=\"168\" style=\"width:80px\">\n            <button class=\"btn btn-sm btn-primary\" onclick=\"demoAlert('Save Update Interval')\">Save</button>\n          </div>\n        </div>\n        <div class=\"setting-row\" id=\"upload-row\">\n          <div class=\"setting-info\"><div class=\"setting-label\">Upload Local File</div><div class=\"setting-desc\">Upload your own TheOneFile HTML</div></div>\n          <div class=\"setting-control\">\n            <button class=\"btn btn-sm btn-primary\" onclick=\"demoAlert('Choose File')\">Choose File</button>\n          </div>\n        </div>\n        <div style=\"padding:12px;background:var(--card-bg);border-radius:8px;margin-top:8px;\">\n          <div style=\"display:flex;align-items:center;justify-content:space-between;margin-bottom:8px;\">\n            <div>\n              <span style=\"font-size:18px;font-weight:700;color:var(--accent);\">v2.4.1</span>\n              <span style=\"font-size:12px;color:#8892a0;margin-left:8px;\">networkening edition</span>\n            </div>\n            <span style=\"font-size:11px;padding:3px 8px;border-radius:4px;font-weight:600;background:rgba(34,197,94,0.15);color:#22c55e;\">Up to date</span>\n          </div>\n          <div style=\"font-size:12px;color:#8892a0;margin-bottom:10px;\">\n            <span>Last updated: Jan 26, 2026, 11:00 AM</span>\n            <span style=\"margin-left:12px;\">245 KB</span>\n          </div>\n          <div style=\"display:flex;gap:8px;\">\n            <button class=\"btn btn-sm btn-success\" onclick=\"demoAlert('Update Now')\">Update Now</button>\n            <button class=\"btn btn-sm\" onclick=\"demoAlert('Check for Updates')\" style=\"background:#2a3040;color:#c8cdd5;\">Check for Updates</button>\n            <a href=\"https://github.com/gelatinescreams/The-One-File/releases\" target=\"_blank\" rel=\"noopener\" style=\"font-size:12px;color:var(--accent);align-self:center;margin-left:auto;text-decoration:none;\">Changelog</a>\n          </div>\n        </div>\n      </div>\n\n      <div class=\"settings-section\">\n        <h3>Appearance</h3>\n        <div class=\"setting-row\">\n          <div class=\"setting-info\"><div class=\"setting-label\">Theme</div><div class=\"setting-desc\">Force a theme for all users or let them choose</div></div>\n          <div class=\"setting-control\">\n            <select onchange=\"demoAlert('Change Theme')\">\n              <option value=\"user\" selected>User Choice</option>\n              <option value=\"dark\">Force Dark</option>\n              <option value=\"light\">Force Light</option>\n            </select>\n          </div>\n        </div>\n      </div>\n\n      <div class=\"settings-section\">\n        <h3>Room Defaults</h3>\n        <div class=\"setting-row\">\n          <div class=\"setting-info\"><div class=\"setting-label\">Default Self Destruct</div><div class=\"setting-desc\">Default expiration for new rooms</div></div>\n          <div class=\"setting-control\">\n            <select>\n              <option value=\"time\" selected>After time</option>\n              <option value=\"empty\">When empty</option>\n              <option value=\"never\">Never</option>\n            </select>\n            <input type=\"number\" value=\"24\" min=\"1\" max=\"720\" style=\"width:70px\">\n            <span style=\"color:#8892a0;font-size:12px\">hours</span>\n          </div>\n        </div>\n        <div class=\"setting-row\">\n          <div class=\"setting-info\"><div class=\"setting-label\">Max Rooms</div><div class=\"setting-desc\">Maximum rooms allowed (0 = unlimited)</div></div>\n          <div class=\"setting-control\"><input type=\"number\" value=\"100\" min=\"0\" max=\"1000\" style=\"width:80px\"></div>\n        </div>\n        <div class=\"setting-row\">\n          <div class=\"setting-info\"><div class=\"setting-label\">Default Room Theme</div><div class=\"setting-desc\">Theme preset for new rooms (from loaded file)</div></div>\n          <div class=\"setting-control\">\n            <select onchange=\"demoAlert('Change Default Room Theme')\">\n              <option value=\"\">Default (from file)</option>\n              <option value=\"monokai\">Monokai</option>\n              <option value=\"solarized\">Solarized</option>\n              <option value=\"dracula\">Dracula</option>\n              <option value=\"nord\">Nord</option>\n            </select>\n          </div>\n        </div>\n        <div class=\"setting-row\">\n          <div class=\"setting-info\"><div class=\"setting-label\">Public Room Creation</div><div class=\"setting-desc\">Allow anyone to create rooms</div></div>\n          <div class=\"setting-control\"><div class=\"toggle active\" onclick=\"demoAlert('Toggle Public Room Creation')\"></div></div>\n        </div>\n        <div style=\"margin-top:16px\"><button class=\"btn btn-primary\" onclick=\"demoAlert('Save Room Settings')\">Save Room Settings</button></div>\n      </div>\n\n      <div class=\"settings-section\">\n        <h3>Network Probing</h3>\n        <div class=\"setting-row\">\n          <div class=\"setting-info\"><div class=\"setting-label\">Server-Side Probing</div><div class=\"setting-desc\">Enable real ICMP/TCP/HTTP/DNS probes from server</div></div>\n          <div class=\"setting-control\"><div class=\"toggle active\" onclick=\"demoAlert('Toggle Server-Side Probing')\"></div></div>\n        </div>\n        <div class=\"setting-row\">\n          <div class=\"setting-info\"><div class=\"setting-label\">Network Discovery</div><div class=\"setting-desc\">Allow subnet scanning to discover hosts</div></div>\n          <div class=\"setting-control\"><div class=\"toggle active\" onclick=\"demoAlert('Toggle Network Discovery')\"></div></div>\n        </div>\n        <div class=\"setting-row\">\n          <div class=\"setting-info\"><div class=\"setting-label\">Discovery Admin Only</div><div class=\"setting-desc\">Restrict network discovery to admin users</div></div>\n          <div class=\"setting-control\"><div class=\"toggle\" onclick=\"demoAlert('Toggle Discovery Admin Only')\"></div></div>\n        </div>\n        <div class=\"setting-row\">\n          <div class=\"setting-info\"><div class=\"setting-label\">Allow Public Ranges</div><div class=\"setting-desc\">Allow scanning non-private (public) IP ranges</div></div>\n          <div class=\"setting-control\"><div class=\"toggle\" onclick=\"demoAlert('Toggle Allow Public Ranges')\"></div></div>\n        </div>\n        <div class=\"setting-row\">\n          <div class=\"setting-info\"><div class=\"setting-label\">Max Scan Size</div><div class=\"setting-desc\">Minimum CIDR prefix (larger = smaller range)</div></div>\n          <div class=\"setting-control\"><span style=\"color:var(--text-soft);font-size:13px\">/</span><input type=\"number\" value=\"24\" min=\"20\" max=\"32\" style=\"width:70px\" onclick=\"demoAlert('Change Max Scan Size')\"><button class=\"btn btn-primary btn-sm\" onclick=\"demoAlert('Save Max Scan Size')\">Save</button></div>\n        </div>\n      </div>\n\n      <div class=\"settings-section\">\n        <h3>Rate Limiting</h3>\n        <div class=\"setting-row\">\n          <div class=\"setting-info\"><div class=\"setting-label\">Enable Rate Limiting</div><div class=\"setting-desc\">Protect against brute force attacks</div></div>\n          <div class=\"setting-control\"><div class=\"toggle active\" onclick=\"demoAlert('Toggle Rate Limiting')\"></div></div>\n        </div>\n        <div class=\"setting-row\">\n          <div class=\"setting-info\"><div class=\"setting-label\">Limit Settings</div><div class=\"setting-desc\">Max attempts per time window</div></div>\n          <div class=\"setting-control\">\n            <input type=\"number\" value=\"10\" min=\"1\" max=\"100\" style=\"width:60px\">\n            <span style=\"color:#8892a0;font-size:12px\">attempts per</span>\n            <input type=\"number\" value=\"60\" min=\"10\" max=\"3600\" style=\"width:70px\">\n            <span style=\"color:#8892a0;font-size:12px\">seconds</span>\n          </div>\n        </div>\n        <div style=\"margin-top:16px\"><button class=\"btn btn-primary\" onclick=\"demoAlert('Save Rate Limit Settings')\">Save Rate Limit Settings</button></div>\n      </div>\n\n      <div class=\"settings-section\">\n        <h3>Collaboration Features</h3>\n        <div class=\"setting-row\">\n          <div class=\"setting-info\"><div class=\"setting-label\">Chat</div><div class=\"setting-desc\">Enable chat in rooms</div></div>\n          <div class=\"setting-control\"><div class=\"toggle active\" onclick=\"demoAlert('Toggle Chat')\"></div></div>\n        </div>\n        <div class=\"setting-row\">\n          <div class=\"setting-info\"><div class=\"setting-label\">Cursor Sharing</div><div class=\"setting-desc\">Show other users cursors</div></div>\n          <div class=\"setting-control\"><div class=\"toggle active\" onclick=\"demoAlert('Toggle Cursor Sharing')\"></div></div>\n        </div>\n        <div class=\"setting-row\">\n          <div class=\"setting-info\"><div class=\"setting-label\">Name Changes</div><div class=\"setting-desc\">Allow users to change name after joining</div></div>\n          <div class=\"setting-control\"><div class=\"toggle active\" onclick=\"demoAlert('Toggle Name Changes')\"></div></div>\n        </div>\n        <div class=\"setting-row\">\n          <div class=\"setting-info\"><div class=\"setting-label\">Welcome Modal</div><div class=\"setting-desc\">Always show welcome setup when joining rooms</div></div>\n          <div class=\"setting-control\"><div class=\"toggle\" onclick=\"demoAlert('Toggle Welcome Modal')\"></div></div>\n        </div>\n      </div>\n\n      <div class=\"settings-section\">\n        <h3>Webhooks</h3>\n        <div class=\"setting-row\">\n          <div class=\"setting-info\"><div class=\"setting-label\">Enable Webhooks</div><div class=\"setting-desc\">Send notifications for events</div></div>\n          <div class=\"setting-control\"><div class=\"toggle\" onclick=\"demoAlert('Toggle Webhooks')\"></div></div>\n        </div>\n        <div class=\"setting-row\">\n          <div class=\"setting-info\"><div class=\"setting-label\">Webhook URL</div><div class=\"setting-desc\">POST endpoint for notifications</div></div>\n          <div class=\"setting-control\">\n            <input type=\"text\" placeholder=\"https://...\" style=\"width:250px\">\n            <button class=\"btn btn-sm btn-primary\" onclick=\"demoAlert('Save Webhook URL')\">Save</button>\n          </div>\n        </div>\n      </div>\n\n      <div class=\"settings-section\">\n        <h3>Automatic Backups</h3>\n        <div class=\"setting-row\">\n          <div class=\"setting-info\"><div class=\"setting-label\">Enable Auto Backup</div><div class=\"setting-desc\">Automatically backup data</div></div>\n          <div class=\"setting-control\"><div class=\"toggle active\" onclick=\"demoAlert('Toggle Auto Backup')\"></div></div>\n        </div>\n        <div class=\"setting-row\">\n          <div class=\"setting-info\"><div class=\"setting-label\">Backup Settings</div><div class=\"setting-desc\">Interval and retention</div></div>\n          <div class=\"setting-control\">\n            <span style=\"color:#8892a0;font-size:12px\">Every</span>\n            <input type=\"number\" value=\"24\" min=\"1\" max=\"168\" style=\"width:60px\">\n            <span style=\"color:#8892a0;font-size:12px\">hours, keep</span>\n            <input type=\"number\" value=\"7\" min=\"1\" max=\"100\" style=\"width:60px\">\n            <span style=\"color:#8892a0;font-size:12px\">backups</span>\n          </div>\n        </div>\n        <div style=\"margin-top:16px\"><button class=\"btn btn-primary\" onclick=\"demoAlert('Save Backup Settings')\">Save Backup Settings</button></div>\n      </div>\n    </div>\n\n\n    <div id=\"tab-logs\" class=\"tab-content\">\n      <div class=\"settings-section\">\n        <h3>Activity Log</h3>\n        <div style=\"margin-bottom:12px;display:flex;gap:12px;align-items:center\">\n          <input type=\"text\" placeholder=\"Filter by room ID...\" style=\"width:250px\">\n          <button class=\"btn btn-sm btn-secondary\" onclick=\"demoAlert('Clear Activity Logs')\">Clear All</button>\n        </div>\n        <div style=\"max-height:300px;overflow-y:auto;scrollbar-width:none\">\n          <div style=\"padding:8px 0;border-bottom:1px solid var(--border);font-size:13px\">\n            <span style=\"color:var(--text-soft)\">1/26/2026, 11:45 AM</span> <span class=\"badge badge-green\">join</span> John Doe <span style=\"color:var(--text-soft);font-size:11px\">a1b2c3d4</span>\n          </div>\n          <div style=\"padding:8px 0;border-bottom:1px solid var(--border);font-size:13px\">\n            <span style=\"color:var(--text-soft)\">1/26/2026, 11:42 AM</span> <span class=\"badge badge-gray\">leave</span> Jane Smith <span style=\"color:var(--text-soft);font-size:11px\">b208667b</span>\n          </div>\n          <div style=\"padding:8px 0;border-bottom:1px solid var(--border);font-size:13px\">\n            <span style=\"color:var(--text-soft)\">1/26/2026, 11:30 AM</span> <span class=\"badge badge-green\">join</span> Guest_7842 <span style=\"color:var(--text-soft);font-size:11px\">c3d4e5f6</span>\n          </div>\n          <div style=\"padding:8px 0;border-bottom:1px solid var(--border);font-size:13px\">\n            <span style=\"color:var(--text-soft)\">1/26/2026, 11:15 AM</span> <span class=\"badge badge-green\">join</span> Sarah Connor <span style=\"color:var(--text-soft);font-size:11px\">a1b2c3d4</span>\n          </div>\n          <div style=\"padding:8px 0;border-bottom:1px solid var(--border);font-size:13px\">\n            <span style=\"color:var(--text-soft)\">1/26/2026, 10:58 AM</span> <span class=\"badge badge-gray\">leave</span> Bob Wilson <span style=\"color:var(--text-soft);font-size:11px\">d4e5f678</span>\n          </div>\n          <div style=\"padding:8px 0;font-size:13px\">\n            <span style=\"color:var(--text-soft)\">1/26/2026, 10:45 AM</span> <span class=\"badge badge-green\">join</span> Mike Chen <span style=\"color:var(--text-soft);font-size:11px\">e5f67890</span>\n          </div>\n        </div>\n      </div>\n\n      <div class=\"settings-section\">\n        <h3>Audit Log</h3>\n        <div style=\"margin-bottom:12px;display:flex;gap:12px;align-items:center\">\n          <input type=\"text\" placeholder=\"Search...\" style=\"width:250px\">\n          <button class=\"btn btn-sm btn-secondary\" onclick=\"demoAlert('Clear Audit Logs')\">Clear All</button>\n        </div>\n        <div style=\"max-height:300px;overflow-y:auto;scrollbar-width:none\">\n          <div style=\"padding:8px 0;border-bottom:1px solid var(--border);font-size:13px\">\n            <span style=\"color:var(--text-soft)\">1/26/2026, 11:30 AM</span> <span class=\"badge badge-yellow\">settings_update</span> admin@example.com\n          </div>\n          <div style=\"padding:8px 0;border-bottom:1px solid var(--border);font-size:13px\">\n            <span style=\"color:var(--text-soft)\">1/26/2026, 11:15 AM</span> <span class=\"badge badge-yellow\">user_create</span> admin@example.com <span style=\"color:var(--text-soft);font-size:11px\">abc123de</span>\n          </div>\n          <div style=\"padding:8px 0;border-bottom:1px solid var(--border);font-size:13px\">\n            <span style=\"color:var(--text-soft)\">1/26/2026, 10:45 AM</span> <span class=\"badge badge-yellow\">backup_create</span> system\n          </div>\n          <div style=\"padding:8px 0;border-bottom:1px solid var(--border);font-size:13px\">\n            <span style=\"color:var(--text-soft)\">1/26/2026, 9:30 AM</span> <span class=\"badge badge-yellow\">oidc_update</span> admin@example.com <span style=\"color:var(--text-soft);font-size:11px\">google_01</span>\n          </div>\n          <div style=\"padding:8px 0;font-size:13px\">\n            <span style=\"color:var(--text-soft)\">1/26/2026, 8:00 AM</span> <span class=\"badge badge-yellow\">admin_login</span> admin@example.com\n          </div>\n        </div>\n      </div>\n    </div>\n\n\n    <div id=\"tab-backups\" class=\"tab-content\">\n      <div class=\"settings-section\">\n        <div style=\"display:flex;justify-content:space-between;align-items:center;margin-bottom:16px\">\n          <h3 style=\"margin:0;border:none;padding:0\">Backups</h3>\n          <div style=\"display:flex;gap:8px\">\n            <button class=\"btn btn-primary btn-sm\" onclick=\"demoAlert('Create Backup')\">Create Backup</button>\n            <button class=\"btn btn-secondary btn-sm\" onclick=\"demoAlert('Export All')\">Export All</button>\n          </div>\n        </div>\n        <div>\n          <div style=\"display:flex;justify-content:space-between;align-items:center;padding:12px 0;border-bottom:1px solid var(--border)\">\n            <div>\n              <div style=\"font-weight:500\">backup_2026-01-26_110000.json</div>\n              <div style=\"font-size:12px;color:var(--text-soft)\">1/26/2026, 11:00:00 AM | 156KB | 12 rooms | Auto</div>\n            </div>\n            <div style=\"display:flex;gap:6px\">\n              <button class=\"btn btn-sm btn-secondary\" onclick=\"demoAlert('Download Backup')\">Download</button>\n              <button class=\"btn btn-sm btn-success\" onclick=\"demoAlert('Restore Backup')\">Restore</button>\n              <button class=\"btn btn-sm btn-danger\" onclick=\"demoAlert('Delete Backup')\">Delete</button>\n            </div>\n          </div>\n          <div style=\"display:flex;justify-content:space-between;align-items:center;padding:12px 0;border-bottom:1px solid var(--border)\">\n            <div>\n              <div style=\"font-weight:500\">backup_2026-01-25_110000.json</div>\n              <div style=\"font-size:12px;color:var(--text-soft)\">1/25/2026, 11:00:00 AM | 148KB | 11 rooms | Auto</div>\n            </div>\n            <div style=\"display:flex;gap:6px\">\n              <button class=\"btn btn-sm btn-secondary\" onclick=\"demoAlert('Download Backup')\">Download</button>\n              <button class=\"btn btn-sm btn-success\" onclick=\"demoAlert('Restore Backup')\">Restore</button>\n              <button class=\"btn btn-sm btn-danger\" onclick=\"demoAlert('Delete Backup')\">Delete</button>\n            </div>\n          </div>\n          <div style=\"display:flex;justify-content:space-between;align-items:center;padding:12px 0;border-bottom:1px solid var(--border)\">\n            <div>\n              <div style=\"font-weight:500\">backup_2026-01-24_143022.json</div>\n              <div style=\"font-size:12px;color:var(--text-soft)\">1/24/2026, 2:30:22 PM | 142KB | 10 rooms</div>\n            </div>\n            <div style=\"display:flex;gap:6px\">\n              <button class=\"btn btn-sm btn-secondary\" onclick=\"demoAlert('Download Backup')\">Download</button>\n              <button class=\"btn btn-sm btn-success\" onclick=\"demoAlert('Restore Backup')\">Restore</button>\n              <button class=\"btn btn-sm btn-danger\" onclick=\"demoAlert('Delete Backup')\">Delete</button>\n            </div>\n          </div>\n          <div style=\"display:flex;justify-content:space-between;align-items:center;padding:12px 0\">\n            <div>\n              <div style=\"font-weight:500\">backup_2026-01-23_110000.json</div>\n              <div style=\"font-size:12px;color:var(--text-soft)\">1/23/2026, 11:00:00 AM | 128KB | 8 rooms | Auto</div>\n            </div>\n            <div style=\"display:flex;gap:6px\">\n              <button class=\"btn btn-sm btn-secondary\" onclick=\"demoAlert('Download Backup')\">Download</button>\n              <button class=\"btn btn-sm btn-success\" onclick=\"demoAlert('Restore Backup')\">Restore</button>\n              <button class=\"btn btn-sm btn-danger\" onclick=\"demoAlert('Delete Backup')\">Delete</button>\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n\n\n    <div id=\"tab-apikeys\" class=\"tab-content\">\n      <div class=\"settings-section\">\n        <div style=\"display:flex;justify-content:space-between;align-items:center;margin-bottom:16px\">\n          <h3 style=\"margin:0;border:none;padding:0\">API Keys</h3>\n          <button class=\"btn btn-primary btn-sm\" onclick=\"showModal('apikey-modal')\">Create API Key</button>\n        </div>\n        <div id=\"apikey-new-key\" style=\"display:none\" class=\"new-key-display\">\n          <div style=\"font-weight:600;color:#f59e0b;margin-bottom:4px\">New API Key Created</div>\n          <div style=\"font-size:12px;color:var(--text-soft);margin-bottom:8px\">Copy this key now. You won't be able to see it again.</div>\n          <code id=\"apikey-value\">tofv_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0</code>\n          <div style=\"margin-top:8px;display:flex;gap:8px\">\n            <button class=\"btn btn-sm btn-primary\" onclick=\"demoAlert('Copy to Clipboard')\">Copy</button>\n            <button class=\"btn btn-sm btn-secondary\" onclick=\"document.getElementById('apikey-new-key').style.display='none'\">Dismiss</button>\n          </div>\n        </div>\n        <div>\n          <div style=\"display:flex;justify-content:space-between;align-items:center;padding:12px 0;border-bottom:1px solid var(--border)\">\n            <div>\n              <div style=\"font-weight:500\">Production API</div>\n              <div style=\"font-size:12px;color:var(--text-soft)\">read, write | Created: 1/15/2026 | Last used: 1/26/2026</div>\n            </div>\n            <button class=\"btn btn-sm btn-danger\" onclick=\"demoAlert('Revoke API Key')\">Revoke</button>\n          </div>\n          <div style=\"display:flex;justify-content:space-between;align-items:center;padding:12px 0;border-bottom:1px solid var(--border)\">\n            <div>\n              <div style=\"font-weight:500\">Monitoring Service</div>\n              <div style=\"font-size:12px;color:var(--text-soft)\">read | Created: 1/10/2026 | Last used: 1/26/2026</div>\n            </div>\n            <button class=\"btn btn-sm btn-danger\" onclick=\"demoAlert('Revoke API Key')\">Revoke</button>\n          </div>\n          <div style=\"display:flex;justify-content:space-between;align-items:center;padding:12px 0\">\n            <div>\n              <div style=\"font-weight:500\">Admin CLI Tool</div>\n              <div style=\"font-size:12px;color:var(--text-soft)\">read, write, admin | Created: 1/1/2026 | Expires: 2/1/2026</div>\n            </div>\n            <button class=\"btn btn-sm btn-danger\" onclick=\"demoAlert('Revoke API Key')\">Revoke</button>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n\n\n  <div class=\"modal-overlay\" id=\"room-modal\">\n    <div class=\"modal\">\n      <div class=\"modal-header\"><h3>Room Details</h3><button class=\"modal-close\" onclick=\"closeModal('room-modal')\">&times;</button></div>\n      <div class=\"modal-body\" id=\"room-modal-body\">\n        <div class=\"info-row\"><span class=\"info-label\">Room ID</span><span class=\"info-value\" id=\"rm-id\">-</span></div>\n        <div class=\"info-row\"><span class=\"info-label\">Created</span><span class=\"info-value\" id=\"rm-created\">-</span></div>\n        <div class=\"info-row\"><span class=\"info-label\">Last Activity</span><span class=\"info-value\" id=\"rm-activity\">-</span></div>\n        <div class=\"info-row\"><span class=\"info-label\">Connected Users</span><span class=\"info-value\" id=\"rm-users\">-</span></div>\n        <div class=\"info-row\"><span class=\"info-label\">Password Protected</span><span class=\"info-value\" id=\"rm-password\">-</span></div>\n        <div class=\"info-row\"><span class=\"info-label\">Self-Destruct</span><span class=\"info-value\" id=\"rm-destruct\">-</span></div>\n        <div style=\"display:flex;gap:8px;margin-top:16px\">\n          <button class=\"btn btn-primary\" style=\"flex:1\" onclick=\"demoAlert('Join Room')\">Join Room</button>\n          <button class=\"btn btn-danger\" style=\"flex:1\" onclick=\"demoAlert('Delete Room')\">Delete Room</button>\n        </div>\n      </div>\n    </div>\n  </div>\n\n\n  <div class=\"modal-overlay\" id=\"user-modal\">\n    <div class=\"modal\">\n      <div class=\"modal-header\"><h3>Create User</h3><button class=\"modal-close\" onclick=\"closeModal('user-modal')\">&times;</button></div>\n      <div class=\"modal-body\">\n        <div style=\"margin-bottom:16px\"><label style=\"display:block;margin-bottom:4px;font-size:14px\">Email</label><input type=\"email\" placeholder=\"user@example.com\" style=\"width:100%\"></div>\n        <div style=\"margin-bottom:16px\"><label style=\"display:block;margin-bottom:4px;font-size:14px\">Display Name</label><input type=\"text\" placeholder=\"John Doe\" style=\"width:100%\"></div>\n        <div style=\"margin-bottom:16px\"><label style=\"display:block;margin-bottom:4px;font-size:14px\">Password (leave blank for invite email)</label><input type=\"password\" placeholder=\"Optional\" style=\"width:100%\"></div>\n        <div style=\"margin-bottom:16px\"><label style=\"display:block;margin-bottom:4px;font-size:14px\">Role</label>\n          <select style=\"width:100%\"><option value=\"user\">User</option><option value=\"admin\">Admin</option></select>\n        </div>\n        <button class=\"btn btn-primary\" style=\"width:100%\" onclick=\"demoAlert('Create User')\">Create User</button>\n      </div>\n    </div>\n  </div>\n\n\n  <div class=\"modal-overlay\" id=\"edit-user-modal\">\n    <div class=\"modal\">\n      <div class=\"modal-header\"><h3>Edit User</h3><button class=\"modal-close\" onclick=\"closeModal('edit-user-modal')\">&times;</button></div>\n      <div class=\"modal-body\">\n        <div style=\"margin-bottom:16px\"><label style=\"display:block;margin-bottom:4px;font-size:14px\">Email</label><input type=\"email\" id=\"eu-email\" style=\"width:100%;background:var(--bg-alt)\" readonly></div>\n        <div style=\"margin-bottom:16px\"><label style=\"display:block;margin-bottom:4px;font-size:14px\">Display Name</label><input type=\"text\" id=\"eu-name\" placeholder=\"Display Name\" style=\"width:100%\"></div>\n        <div style=\"margin-bottom:16px\"><label style=\"display:block;margin-bottom:4px;font-size:14px\">New Password (leave blank to keep current)</label><input type=\"password\" placeholder=\"Optional\" style=\"width:100%\"></div>\n        <div style=\"margin-bottom:16px\"><label style=\"display:block;margin-bottom:4px;font-size:14px\">Role</label>\n          <select id=\"eu-role\" style=\"width:100%\"><option value=\"user\">User</option><option value=\"admin\">Admin</option></select>\n        </div>\n        <div style=\"margin-bottom:16px\">\n          <label style=\"display:flex;align-items:center;gap:8px\"><input type=\"checkbox\" id=\"eu-active\"> Active</label>\n          <label style=\"display:flex;align-items:center;gap:8px;margin-top:8px\"><input type=\"checkbox\" id=\"eu-verified\"> Email Verified</label>\n        </div>\n        <div style=\"display:flex;gap:8px\">\n          <button class=\"btn btn-secondary\" style=\"flex:1\" onclick=\"demoAlert('Send Password Reset')\">Send Password Reset</button>\n          <button class=\"btn btn-primary\" style=\"flex:1\" onclick=\"demoAlert('Save User')\">Save</button>\n        </div>\n      </div>\n    </div>\n  </div>\n\n\n  <div class=\"modal-overlay\" id=\"apikey-modal\">\n    <div class=\"modal\">\n      <div class=\"modal-header\"><h3>Create API Key</h3><button class=\"modal-close\" onclick=\"closeModal('apikey-modal')\">&times;</button></div>\n      <div class=\"modal-body\">\n        <div style=\"margin-bottom:16px\"><label style=\"display:block;margin-bottom:4px;font-size:14px\">Name</label><input type=\"text\" placeholder=\"My API Key\" style=\"width:100%\"></div>\n        <div style=\"margin-bottom:16px\"><label style=\"display:block;margin-bottom:4px;font-size:14px\">Expires In (days, 0=never)</label><input type=\"number\" value=\"0\" min=\"0\" style=\"width:100%\"></div>\n        <div style=\"margin-bottom:16px\"><label style=\"display:block;margin-bottom:4px;font-size:14px\">Permissions</label>\n          <label style=\"display:flex;align-items:center;gap:8px;margin:8px 0\"><input type=\"checkbox\" checked> Read rooms</label>\n          <label style=\"display:flex;align-items:center;gap:8px;margin:8px 0\"><input type=\"checkbox\"> Write rooms</label>\n          <label style=\"display:flex;align-items:center;gap:8px;margin:8px 0\"><input type=\"checkbox\"> Admin access</label>\n        </div>\n        <button class=\"btn btn-primary\" style=\"width:100%\" onclick=\"closeModal('apikey-modal');document.getElementById('apikey-new-key').style.display='block';demoAlert('Create Key')\">Create Key</button>\n      </div>\n    </div>\n  </div>\n\n\n  <div class=\"modal-overlay\" id=\"oidc-modal\">\n    <div class=\"modal\" style=\"max-width:550px\">\n      <div class=\"modal-header\"><h3 id=\"oidc-modal-title\">Add OIDC Provider</h3><button class=\"modal-close\" onclick=\"closeModal('oidc-modal')\">&times;</button></div>\n      <div class=\"modal-body\">\n        <div style=\"margin-bottom:16px\"><label style=\"display:block;margin-bottom:4px;font-size:14px\">Name</label><input type=\"text\" id=\"oidc-name\" placeholder=\"My Provider\" style=\"width:100%\"></div>\n        <div style=\"margin-bottom:16px\"><label style=\"display:block;margin-bottom:4px;font-size:14px\">Provider Type</label>\n          <select id=\"oidc-type\" style=\"width:100%\"><option value=\"generic\">Generic OIDC</option><option value=\"authentik\">Authentik</option><option value=\"keycloak\">Keycloak</option><option value=\"auth0\">Auth0</option><option value=\"okta\">Okta</option></select>\n        </div>\n        <div style=\"margin-bottom:16px\"><label style=\"display:block;margin-bottom:4px;font-size:14px\">Client ID</label><input type=\"text\" id=\"oidc-client-id\" placeholder=\"client-id\" style=\"width:100%\"></div>\n        <div style=\"margin-bottom:16px\"><label style=\"display:block;margin-bottom:4px;font-size:14px\">Client Secret</label><input type=\"password\" id=\"oidc-client-secret\" placeholder=\"client-secret\" style=\"width:100%\"></div>\n        <div style=\"margin-bottom:16px\"><label style=\"display:block;margin-bottom:4px;font-size:14px\">Issuer URL</label><input type=\"text\" id=\"oidc-issuer\" placeholder=\"https://auth.example.com\" style=\"width:100%\"></div>\n        <div style=\"margin-bottom:16px\"><label style=\"display:block;margin-bottom:4px;font-size:14px\">Authorization URL</label><input type=\"text\" id=\"oidc-auth-url\" placeholder=\"https://auth.example.com/authorize\" style=\"width:100%\"></div>\n        <div style=\"margin-bottom:16px\"><label style=\"display:block;margin-bottom:4px;font-size:14px\">Token URL</label><input type=\"text\" id=\"oidc-token-url\" placeholder=\"https://auth.example.com/token\" style=\"width:100%\"></div>\n        <div style=\"margin-bottom:16px\"><label style=\"display:block;margin-bottom:4px;font-size:14px\">Userinfo URL</label><input type=\"text\" id=\"oidc-userinfo-url\" placeholder=\"https://auth.example.com/userinfo\" style=\"width:100%\"></div>\n        <div style=\"margin-bottom:16px\"><label style=\"display:block;margin-bottom:4px;font-size:14px\">Scopes</label><input type=\"text\" id=\"oidc-scopes\" value=\"openid email profile\" style=\"width:100%\"></div>\n        <div style=\"margin-bottom:16px\"><label style=\"display:flex;align-items:center;gap:8px\"><input type=\"checkbox\" id=\"oidc-active\" checked> Active</label></div>\n        <button class=\"btn btn-primary\" style=\"width:100%\" onclick=\"demoAlert('Save OIDC Provider')\">Save Provider</button>\n      </div>\n    </div>\n  </div>\n\n\n  <div class=\"modal-overlay\" id=\"smtp-modal\">\n    <div class=\"modal\" style=\"max-width:550px\">\n      <div class=\"modal-header\"><h3 id=\"smtp-modal-title\">Add SMTP Configuration</h3><button class=\"modal-close\" onclick=\"closeModal('smtp-modal')\">&times;</button></div>\n      <div class=\"modal-body\">\n        <div style=\"margin-bottom:16px\"><label style=\"display:block;margin-bottom:4px;font-size:14px\">Name</label><input type=\"text\" id=\"smtp-name\" placeholder=\"Primary SMTP\" style=\"width:100%\"></div>\n        <div style=\"margin-bottom:16px\"><label style=\"display:block;margin-bottom:4px;font-size:14px\">Host</label><input type=\"text\" id=\"smtp-host\" placeholder=\"smtp.example.com\" style=\"width:100%\"></div>\n        <div style=\"margin-bottom:16px\"><label style=\"display:block;margin-bottom:4px;font-size:14px\">Port</label><input type=\"number\" id=\"smtp-port\" value=\"587\" style=\"width:100%\"></div>\n        <div style=\"margin-bottom:16px\"><label style=\"display:block;margin-bottom:4px;font-size:14px\">Security Mode</label>\n          <select id=\"smtp-secure\" style=\"width:100%\"><option value=\"starttls\">STARTTLS (587)</option><option value=\"tls\">TLS/SSL (465)</option><option value=\"none\">None (25)</option></select>\n        </div>\n        <div style=\"margin-bottom:16px\"><label style=\"display:block;margin-bottom:4px;font-size:14px\">Username</label><input type=\"text\" id=\"smtp-username\" placeholder=\"user@example.com\" style=\"width:100%\"></div>\n        <div style=\"margin-bottom:16px\"><label style=\"display:block;margin-bottom:4px;font-size:14px\">Password</label><input type=\"password\" id=\"smtp-password\" placeholder=\"password\" style=\"width:100%\"></div>\n        <div style=\"margin-bottom:16px\"><label style=\"display:block;margin-bottom:4px;font-size:14px\">From Email</label><input type=\"email\" id=\"smtp-from-email\" placeholder=\"noreply@example.com\" style=\"width:100%\"></div>\n        <div style=\"margin-bottom:16px\"><label style=\"display:block;margin-bottom:4px;font-size:14px\">From Name</label><input type=\"text\" id=\"smtp-from-name\" placeholder=\"TheOneFile_Verse\" style=\"width:100%\"></div>\n        <div style=\"margin-bottom:16px\">\n          <label style=\"display:flex;align-items:center;gap:8px\"><input type=\"checkbox\" id=\"smtp-default\"> Set as default</label>\n          <label style=\"display:flex;align-items:center;gap:8px;margin-top:8px\"><input type=\"checkbox\" id=\"smtp-active\" checked> Active</label>\n        </div>\n        <div style=\"display:flex;gap:8px\">\n          <button class=\"btn btn-secondary\" style=\"flex:1\" onclick=\"demoAlert('Test SMTP')\">Test</button>\n          <button class=\"btn btn-primary\" style=\"flex:1\" onclick=\"demoAlert('Save SMTP')\">Save</button>\n        </div>\n      </div>\n    </div>\n  </div>\n\n\n  <div class=\"modal-overlay\" id=\"template-modal\">\n    <div class=\"modal\" style=\"max-width:900px;max-height:90vh;display:flex;flex-direction:column\">\n      <div class=\"modal-header\"><h3 id=\"template-modal-title\">Edit Email Template</h3><button class=\"modal-close\" onclick=\"closeModal('template-modal')\">&times;</button></div>\n      <div class=\"modal-body\" style=\"flex:1;overflow:auto;display:flex;flex-direction:column\">\n        <div style=\"margin-bottom:16px\"><label style=\"display:block;margin-bottom:4px;font-size:14px\">Template Name</label><input type=\"text\" id=\"tpl-name\" readonly style=\"width:100%;background:var(--bg-alt)\"></div>\n        <div style=\"margin-bottom:16px\"><label style=\"display:block;margin-bottom:4px;font-size:14px\">Subject</label><input type=\"text\" id=\"tpl-subject\" placeholder=\"Email subject with {{variables}}\" style=\"width:100%\"></div>\n        <div style=\"margin-bottom:8px;display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:4px\">\n          <label style=\"font-size:14px\">HTML Body</label>\n          <div style=\"display:flex;gap:4px;flex-wrap:wrap\">\n            <button class=\"btn btn-sm btn-secondary\" onclick=\"demoAlert('Insert {{displayName}}')\">{{displayName}}</button>\n            <button class=\"btn btn-sm btn-secondary\" onclick=\"demoAlert('Insert {{actionUrl}}')\">{{actionUrl}}</button>\n            <button class=\"btn btn-sm btn-secondary\" onclick=\"demoAlert('Insert {{appName}}')\">{{appName}}</button>\n            <button class=\"btn btn-sm btn-secondary\" onclick=\"demoAlert('Toggle HTML/Preview')\">Toggle HTML/Preview</button>\n          </div>\n        </div>\n        <div style=\"flex:1;min-height:300px\">\n          <div style=\"background:var(--bg-alt);border:1px solid var(--border);border-bottom:none;border-radius:8px 8px 0 0;padding:8px;display:flex;gap:4px;flex-wrap:wrap\">\n            <button type=\"button\" class=\"btn btn-sm btn-secondary\" title=\"Bold\"><b>B</b></button>\n            <button type=\"button\" class=\"btn btn-sm btn-secondary\" title=\"Italic\"><i>I</i></button>\n            <button type=\"button\" class=\"btn btn-sm btn-secondary\" title=\"Underline\"><u>U</u></button>\n            <span style=\"border-left:1px solid var(--border);margin:0 4px\"></span>\n            <button type=\"button\" class=\"btn btn-sm btn-secondary\" title=\"Align Left\">&#8676;</button>\n            <button type=\"button\" class=\"btn btn-sm btn-secondary\" title=\"Center\">&#8596;</button>\n            <button type=\"button\" class=\"btn btn-sm btn-secondary\" title=\"Align Right\">&#8677;</button>\n            <span style=\"border-left:1px solid var(--border);margin:0 4px\"></span>\n            <button type=\"button\" class=\"btn btn-sm btn-secondary\" title=\"Bullet List\">&#8226;</button>\n            <button type=\"button\" class=\"btn btn-sm btn-secondary\" title=\"Numbered List\">1.</button>\n            <span style=\"border-left:1px solid var(--border);margin:0 4px\"></span>\n            <select style=\"padding:4px 8px;background:var(--surface);border:1px solid var(--border);border-radius:4px;color:var(--text);font-size:12px;min-width:auto\">\n              <option value=\"\">Heading</option>\n              <option value=\"h1\">Heading 1</option>\n              <option value=\"h2\">Heading 2</option>\n              <option value=\"h3\">Heading 3</option>\n              <option value=\"p\">Paragraph</option>\n            </select>\n            <select style=\"padding:4px 8px;background:var(--surface);border:1px solid var(--border);border-radius:4px;color:var(--text);font-size:12px;min-width:auto\">\n              <option value=\"\">Size</option>\n              <option value=\"1\">Small</option>\n              <option value=\"3\">Normal</option>\n              <option value=\"5\">Large</option>\n              <option value=\"7\">Huge</option>\n            </select>\n            <input type=\"color\" title=\"Text Color\" style=\"width:30px;height:26px;padding:0;border:1px solid var(--border);border-radius:4px;cursor:pointer;min-width:auto\">\n            <span style=\"border-left:1px solid var(--border);margin:0 4px\"></span>\n            <button type=\"button\" class=\"btn btn-sm btn-secondary\" title=\"Insert Link\">&#128279;</button>\n            <button type=\"button\" class=\"btn btn-sm btn-secondary\" title=\"Insert Image\">&#128247;</button>\n            <button type=\"button\" class=\"btn btn-sm btn-secondary\" title=\"Insert Button\">&#9634; Btn</button>\n          </div>\n          <div contenteditable=\"false\" style=\"min-height:250px;background:var(--surface);border:1px solid var(--border);border-radius:0 0 8px 8px;padding:16px;overflow-y:auto;font-family:system-ui;line-height:1.6;color:var(--text-soft)\">\n            <h2 style=\"color:var(--accent)\">Hello {{displayName}},</h2>\n            <p>Thank you for signing up. Please verify your email address by clicking the button below.</p>\n            <p style=\"text-align:center;margin:24px 0\"><a style=\"display:inline-block;padding:12px 24px;background:var(--accent);color:#fff;border-radius:8px;text-decoration:none;font-weight:600\">Verify Email</a></p>\n            <p style=\"font-size:13px;color:var(--text-soft)\">If you didn't create an account, you can safely ignore this email.</p>\n          </div>\n        </div>\n        <div style=\"margin-top:16px\"><label style=\"display:block;margin-bottom:4px;font-size:14px\">Plain Text Body (fallback)</label><textarea placeholder=\"Plain text version for email clients that don't support HTML\" style=\"width:100%;height:100px;background:var(--bg);border:1px solid var(--border);border-radius:8px;padding:12px;font-family:monospace;font-size:13px;color:var(--text);resize:vertical\">Hello {{displayName}},\n\nThank you for signing up. Please verify your email by visiting: {{actionUrl}}\n\nIf you didn't create an account, you can safely ignore this email.</textarea></div>\n        <div style=\"margin-top:16px;display:flex;gap:8px;justify-content:flex-end\">\n          <button class=\"btn btn-secondary\" onclick=\"showModal('template-preview-modal')\">Preview</button>\n          <button class=\"btn btn-primary\" onclick=\"demoAlert('Save Template')\">Save Template</button>\n        </div>\n      </div>\n    </div>\n  </div>\n\n\n  <div class=\"modal-overlay\" id=\"template-preview-modal\">\n    <div class=\"modal\" style=\"max-width:700px;max-height:80vh\">\n      <div class=\"modal-header\"><h3>Email Preview</h3><button class=\"modal-close\" onclick=\"closeModal('template-preview-modal')\">&times;</button></div>\n      <div class=\"modal-body\" style=\"padding:0\">\n        <div style=\"padding:12px 16px;background:var(--bg-alt);border-bottom:1px solid var(--border)\">\n          <div style=\"font-size:12px;color:var(--text-soft)\">Subject:</div>\n          <div style=\"font-weight:500\">Verify your email | TheOneFile_Verse</div>\n        </div>\n        <div style=\"padding:24px;background:#fff;color:#333;min-height:300px\">\n          <h2 style=\"color:#c9a227;margin-bottom:16px\">Hello John Doe,</h2>\n          <p style=\"margin-bottom:16px\">Thank you for signing up. Please verify your email address by clicking the button below.</p>\n          <p style=\"text-align:center;margin:24px 0\"><span style=\"display:inline-block;padding:12px 24px;background:#c9a227;color:#fff;border-radius:8px;font-weight:600\">Verify Email</span></p>\n          <p style=\"font-size:13px;color:#999\">If you didn't create an account, you can safely ignore this email.</p>\n        </div>\n      </div>\n    </div>\n  </div>\n\n  <script>\n    function getTheme() {\n      return localStorage.getItem('demo-theme') || 'dark';\n    }\n\n    function setTheme(theme) {\n      localStorage.setItem('demo-theme', theme);\n      document.documentElement.setAttribute('data-theme', theme);\n      document.getElementById('theme-icon').textContent = theme === 'dark' ? '\\u2600' : '\\u263E';\n    }\n\n    function toggleTheme() {\n      setTheme(getTheme() === 'dark' ? 'light' : 'dark');\n    }\n\n    setTheme(getTheme());\n\n    function showTab(name) {\n      document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));\n      document.querySelectorAll('.tab-content').forEach(t => t.classList.remove('active'));\n      document.querySelector('.tab-content#tab-' + name).classList.add('active');\n      document.querySelector('.tab[onclick*=\"' + name + '\"]').classList.add('active');\n    }\n\n    function demoAlert(action) {\n      alert('Demo Mode: \"' + action + '\" action is disabled.\\n\\nThis is a demonstration of the admin interface with sample data.');\n    }\n\n    function showModal(id) {\n      document.getElementById(id).classList.add('active');\n    }\n\n    function closeModal(id) {\n      document.getElementById(id).classList.remove('active');\n    }\n\n    document.querySelectorAll('.modal-overlay').forEach(function(overlay) {\n      overlay.addEventListener('click', function(e) {\n        if (e.target === overlay) overlay.classList.remove('active');\n      });\n    });\n\n    document.addEventListener('keydown', function(e) {\n      if (e.key === 'Escape') {\n        document.querySelectorAll('.modal-overlay.active').forEach(function(m) { m.classList.remove('active'); });\n      }\n    });\n\n    function showRoomModal(id, created, activity, users, hasPassword, destructMode, destructHours) {\n      document.getElementById('rm-id').textContent = id;\n      document.getElementById('rm-created').textContent = created;\n      document.getElementById('rm-activity').textContent = activity;\n      document.getElementById('rm-users').textContent = users;\n      document.getElementById('rm-password').innerHTML = hasPassword ? '<span class=\"badge badge-yellow\">Yes</span>' : '<span class=\"badge badge-gray\">No</span>';\n      var destruct = destructMode === 'never' ? 'Never' : destructMode === 'empty' ? 'When empty' : 'After ' + destructHours + ' hours';\n      document.getElementById('rm-destruct').textContent = destruct;\n      showModal('room-modal');\n    }\n\n    function showEditUserModal(email, name, role, active, verified) {\n      document.getElementById('eu-email').value = email;\n      document.getElementById('eu-name').value = name;\n      document.getElementById('eu-role').value = role;\n      document.getElementById('eu-active').checked = active;\n      document.getElementById('eu-verified').checked = verified;\n      showModal('edit-user-modal');\n    }\n\n    function showOidcModal(name, type, clientId, clientSecret, issuer, authUrl, tokenUrl, userinfoUrl, scopes, active) {\n      var isEdit = !!name;\n      document.getElementById('oidc-modal-title').textContent = isEdit ? 'Edit OIDC Provider' : 'Add OIDC Provider';\n      document.getElementById('oidc-name').value = name || '';\n      document.getElementById('oidc-type').value = type || 'generic';\n      document.getElementById('oidc-client-id').value = clientId || '';\n      document.getElementById('oidc-client-secret').value = clientSecret || '';\n      document.getElementById('oidc-issuer').value = issuer || '';\n      document.getElementById('oidc-auth-url').value = authUrl || '';\n      document.getElementById('oidc-token-url').value = tokenUrl || '';\n      document.getElementById('oidc-userinfo-url').value = userinfoUrl || '';\n      document.getElementById('oidc-scopes').value = scopes || 'openid email profile';\n      document.getElementById('oidc-active').checked = active !== false;\n      showModal('oidc-modal');\n    }\n\n    function showSmtpModal(name, host, port, secure, username, password, fromEmail, fromName, isDefault, active) {\n      var isEdit = !!name;\n      document.getElementById('smtp-modal-title').textContent = isEdit ? 'Edit SMTP Configuration' : 'Add SMTP Configuration';\n      document.getElementById('smtp-name').value = name || '';\n      document.getElementById('smtp-host').value = host || '';\n      document.getElementById('smtp-port').value = port || 587;\n      document.getElementById('smtp-secure').value = secure || 'starttls';\n      document.getElementById('smtp-username').value = username || '';\n      document.getElementById('smtp-password').value = password || '';\n      document.getElementById('smtp-from-email').value = fromEmail || '';\n      document.getElementById('smtp-from-name').value = fromName || '';\n      document.getElementById('smtp-default').checked = !!isDefault;\n      document.getElementById('smtp-active').checked = active !== false;\n      showModal('smtp-modal');\n    }\n\n    function showTemplateModal(name, subject) {\n      document.getElementById('template-modal-title').textContent = 'Edit Email Template';\n      document.getElementById('tpl-name').value = name;\n      document.getElementById('tpl-subject').value = subject;\n      showModal('template-modal');\n    }\n  </script>\n</body>\n</html>\n"
  },
  {
    "path": "theonefile_verse/docker-compose.yml",
    "content": "services:\n  theonefile_verse:\n    image: ghcr.io/gelatinescreams/theonefile_verse:latest\n    ports:\n      - \"10101:10101\"\n    volumes:\n      - ./data:/app/data\n    environment:\n      - PORT=10101\n      - DATA_DIR=/app/data\n      - UPDATE_INTERVAL=24\n      - REDIS_URL=redis://redis:6379\n    depends_on:\n      redis:\n        condition: service_healthy\n    restart: unless-stopped\n    healthcheck:\n      test: [\"CMD\", \"wget\", \"--quiet\", \"--tries=1\", \"--spider\", \"http://localhost:10101/api/health\"]\n      interval: 30s\n      timeout: 10s\n      retries: 3\n      start_period: 30s\n    deploy:\n      resources:\n        limits:\n          cpus: \"2\"\n          memory: 512M\n          pids: 256\n    logging:\n      driver: json-file\n      options:\n        max-size: \"10m\"\n        max-file: \"3\"\n    security_opt:\n      - no-new-privileges:true\n    cap_drop:\n      - ALL\n    cap_add:\n      - CHOWN\n      - SETUID\n      - SETGID\n      - NET_RAW\n    networks:\n      - internal\n\n  redis:\n    image: redis:7-alpine\n    volumes:\n      - redis_data:/data\n      - ./redis.conf:/usr/local/etc/redis/redis.conf:ro\n    command: redis-server /usr/local/etc/redis/redis.conf\n    restart: unless-stopped\n    healthcheck:\n      test: [\"CMD\", \"redis-cli\", \"ping\"]\n      interval: 10s\n      timeout: 5s\n      retries: 3\n    deploy:\n      resources:\n        limits:\n          cpus: \"2\"\n          memory: 512M\n          pids: 64\n    logging:\n      driver: json-file\n      options:\n        max-size: \"10m\"\n        max-file: \"3\"\n    networks:\n      - internal\n\nvolumes:\n  redis_data:\n\nnetworks:\n  internal:\n    driver: bridge\n"
  },
  {
    "path": "theonefile_verse/entrypoint.sh",
    "content": "#!/bin/sh\nset -e\nchown -R appuser:appgroup /app/data\nif ! su-exec appuser test -w /app/data; then\n  echo \"FATAL: /app/data is not writable by appuser\" >&2\n  exit 1\nfi\nexec su-exec appuser bun run src/index.ts\n"
  },
  {
    "path": "theonefile_verse/package.json",
    "content": "{\n  \"name\": \"theonefile_verse\",\n  \"version\": \"1.9.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"bun run --watch src/index.ts\",\n    \"start\": \"bun run src/index.ts\"\n  },\n  \"dependencies\": {\n    \"redis\": \"^4.6.13\"\n  }\n}\n"
  },
  {
    "path": "theonefile_verse/public/admin-auth.js",
    "content": "(function() {\n  'use strict';\n\n  var forcedTheme = null;\n\n  function getTheme() {\n    if (forcedTheme && forcedTheme !== 'user') return forcedTheme;\n    return localStorage.getItem('theme') || 'dark';\n  }\n\n  function setTheme(t) {\n    document.documentElement.setAttribute('data-theme', t);\n  }\n\n  setTheme(getTheme());\n\n  fetch('/api/theme').then(function(r) {\n    return r.json();\n  }).then(function(d) {\n    if (d.forcedTheme && d.forcedTheme !== 'user') {\n      forcedTheme = d.forcedTheme;\n      setTheme(forcedTheme);\n    }\n  }).catch(function() {});\n\n  window.__authCsrfToken = '';\n  window.__authCsrfRefresh = function() {\n    return fetch('/api/auth/csrf').then(function(r) {\n      return r.json();\n    }).then(function(d) {\n      window.__authCsrfToken = d.token;\n    }).catch(function() {});\n  };\n\n  window.__authRenderOidcProviders = function(containerId, dividerId, redirectSuffix) {\n    fetch('/api/auth/providers').then(function(r) {\n      return r.json();\n    }).then(function(providers) {\n      if (providers.length > 0) {\n        var divider = document.getElementById(dividerId);\n        if (divider) divider.style.display = 'flex';\n        var container = document.getElementById(containerId);\n        providers.forEach(function(p) {\n          var btn = document.createElement('button');\n          btn.type = 'button';\n          btn.className = 'oidc-btn';\n          if (p.iconUrl && (p.iconUrl.startsWith('http://') || p.iconUrl.startsWith('https://'))) {\n            var img = document.createElement('img');\n            img.src = p.iconUrl;\n            img.width = 20;\n            img.height = 20;\n            btn.appendChild(img);\n          }\n          btn.appendChild(document.createTextNode(' Continue with ' + (p.name || '')));\n          btn.addEventListener('click', function() {\n            window.location.href = '/api/auth/oidc/' + p.id + '/login' + (redirectSuffix || '');\n          });\n          container.appendChild(btn);\n        });\n      }\n    }).catch(function() {});\n  };\n})();\n"
  },
  {
    "path": "theonefile_verse/public/admin-dashboard.js",
    "content": "(function() {\n  'use strict';\n  var pageData = JSON.parse((document.getElementById('page-data') || {}).textContent || '{}');\n  var ADMIN_PATH = pageData.adminPath || 'admin';\n  var csrfToken = '';\n  function refreshCsrf() {\n    return fetch('/api/auth/csrf').then(function(r) { return r.json(); }).then(function(d) {\n      if (d.token) csrfToken = d.token;\n    }).catch(function() {});\n  }\n  refreshCsrf();\n  function csrfHeaders(extra) {\n    var h = { 'x-csrf-token': csrfToken };\n    if (extra) { for (var k in extra) { if (extra.hasOwnProperty(k)) h[k] = extra[k]; } }\n    setTimeout(refreshCsrf, 0);\n    return h;\n  }\n  var forcedTheme = null;\n\n  function getTheme() {\n    if (forcedTheme && forcedTheme !== 'user') return forcedTheme;\n    return localStorage.getItem('theme') || 'dark';\n  }\n\n  function setTheme(theme) {\n    if (!forcedTheme || forcedTheme === 'user') {\n      localStorage.setItem('theme', theme);\n    }\n    document.documentElement.setAttribute('data-theme', theme);\n    document.getElementById('theme-icon').textContent = theme === 'dark' ? '\\u2600' : '\\u263E';\n  }\n\n  function toggleTheme() {\n    if (forcedTheme && forcedTheme !== 'user') return;\n    setTheme(getTheme() === 'dark' ? 'light' : 'dark');\n  }\n\n  function updateThemeToggleVisibility() {\n    var btn = document.getElementById('theme-toggle');\n    if (forcedTheme && forcedTheme !== 'user') {\n      btn.style.display = 'none';\n    } else {\n      btn.style.display = 'block';\n    }\n  }\n\n  setTheme(getTheme());\n\n  fetch('/api/theme')\n    .then(function(r) { return r.json(); })\n    .then(function(data) {\n      if (data.forcedTheme && data.forcedTheme !== 'user') {\n        forcedTheme = data.forcedTheme;\n        setTheme(forcedTheme);\n      }\n      updateThemeToggleVisibility();\n    })\n    .catch(function() {\n      updateThemeToggleVisibility();\n    });\n  function h(tag, props) {\n    var node = document.createElement(tag);\n    if (props) {\n      for (var key in props) {\n        if (!props.hasOwnProperty(key)) continue;\n        if (key === 'className') node.className = props[key];\n        else if (key === 'style') node.setAttribute('style', props[key]);\n        else if (key === 'textContent') node.textContent = props[key];\n        else if (key.slice(0, 5) === 'data-') node.setAttribute(key, props[key]);\n        else if (key === 'checked') { if (props[key]) node.checked = true; }\n        else node[key] = props[key];\n      }\n    }\n    for (var i = 2; i < arguments.length; i++) _append(node, arguments[i]);\n    return node;\n  }\n  function _append(parent, child) {\n    if (child == null || child === false) return;\n    if (typeof child === 'string' || typeof child === 'number') {\n      parent.appendChild(document.createTextNode(String(child)));\n    } else if (Array.isArray(child)) {\n      for (var j = 0; j < child.length; j++) _append(parent, child[j]);\n    } else {\n      parent.appendChild(child);\n    }\n  }\n  function clearNode(el) {\n    while (el.firstChild) el.removeChild(el.firstChild);\n  }\n  function setContent(container, children) {\n    clearNode(container);\n    var frag = document.createDocumentFragment();\n    _append(frag, children);\n    container.appendChild(frag);\n  }\n  var rooms = [];\n  var selected = new Set();\n  var settings = {};\n  var totalRooms = 0;\n  var searchTimeout = null;\n  var users = [];\n  var authSettings = {};\n  var oidcProviders = [];\n  var smtpConfigs = [];\n  var emailLogs = [];\n  function showTab(name) {\n    document.querySelectorAll('.tab').forEach(function(t) {\n      t.classList.remove('active');\n    });\n    document.querySelectorAll('.tab-content').forEach(function(t) {\n      t.classList.remove('active');\n    });\n    document.querySelector('.tab-content#tab-' + name).classList.add('active');\n    document.querySelector('.tab[data-tab=\"' + name + '\"]').classList.add('active');\n\n    if (name === 'settings') loadSettings();\n    if (name === 'logs') {\n      loadActivityLogs();\n      loadAuditLogs();\n    }\n    if (name === 'backups') loadBackups();\n    if (name === 'apikeys') loadApiKeys();\n    if (name === 'users') loadUsers();\n    if (name === 'auth') {\n      loadAuthSettings();\n      loadOidcProviders();\n      loadSmtpConfigs();\n      loadEmailTemplates();\n      loadEmailLogs();\n    }\n  }\n\n  async function loadData(query) {\n    query = query || '';\n    try {\n      var url = query\n        ? '/api/admin/rooms?q=' + encodeURIComponent(query)\n        : '/api/admin/rooms';\n      var res = await fetch(url);\n      if (!res.ok) {\n        if (res.status === 401) {\n          window.location.href = '/' + ADMIN_PATH + '/login';\n        }\n        return;\n      }\n      var data = await res.json();\n      rooms = data.rooms || data;\n      totalRooms = data.total || rooms.length;\n      renderStats();\n      renderRooms();\n      updateBulkUI();\n    } catch (e) {\n      console.error(e);\n    }\n  }\n\n  function searchRooms(q) {\n    if (searchTimeout) clearTimeout(searchTimeout);\n    searchTimeout = setTimeout(function() { loadData(q); }, 300);\n  }\n\n  async function loadSettings() {\n    try {\n      var res = await fetch('/api/admin/settings');\n      if (!res.ok) return;\n      settings = await res.json();\n      renderSettings();\n    } catch (e) {\n      console.error(e);\n    }\n  }\n\n  function renderSettings() {\n    document.getElementById('toggle-instance-lock').classList.toggle('active', settings.instancePasswordEnabled);\n    document.getElementById('toggle-public-rooms').classList.toggle('active', settings.allowPublicRoomCreation);\n    document.getElementById('toggle-rate-limit').classList.toggle('active', settings.rateLimitEnabled !== false);\n    document.getElementById('update-interval').value = settings.updateIntervalHours || 0;\n    document.getElementById('default-destruct-mode').value = settings.defaultDestructMode || 'time';\n    document.getElementById('default-destruct-hours').value = settings.defaultDestructHours || 24;\n    document.getElementById('max-rooms').value = settings.maxRoomsPerInstance || 0;\n    document.getElementById('forced-theme').value = settings.forcedTheme || 'user';\n    var themeSelect = document.getElementById('default-room-theme');\n    if (themeSelect) {\n      themeSelect.innerHTML = '<option value=\"\">Default (from file)</option>';\n      if (settings.availableThemes) {\n        settings.availableThemes.forEach(function(t) {\n          var opt = document.createElement('option');\n          opt.value = t.key;\n          opt.textContent = t.label;\n          themeSelect.appendChild(opt);\n        });\n      }\n      themeSelect.value = settings.defaultRoomTheme || '';\n    }\n    document.getElementById('rate-limit-attempts').value = settings.rateLimitMaxAttempts || 10;\n    document.getElementById('rate-limit-window').value = settings.rateLimitWindow || 60;\n    document.getElementById('rate-limit-options').style.display = settings.rateLimitEnabled !== false ? 'flex' : 'none';\n    document.getElementById('instance-pwd-row').style.display = settings.instancePasswordEnabled ? 'flex' : 'none';\n    document.getElementById('instance-pwd-status').textContent = settings.instancePasswordSet ? 'Password is set' : 'No password set';\n\n    if (settings.envAdminPasswordSet) {\n      document.getElementById('toggle-instance-lock').style.opacity = '0.5';\n      document.getElementById('toggle-instance-lock').setAttribute('data-disabled', 'true');\n    }\n\n    document.getElementById('toggle-chat').classList.toggle('active', settings.chatEnabled !== false);\n    document.getElementById('toggle-cursor').classList.toggle('active', settings.cursorSharingEnabled !== false);\n    document.getElementById('toggle-namechange').classList.toggle('active', settings.nameChangeEnabled !== false);\n    document.getElementById('toggle-welcome-modal').classList.toggle('active', settings.forceWelcomeModal);\n    document.getElementById('toggle-probe').classList.toggle('active', settings.probeEnabled !== false);\n    document.getElementById('toggle-discovery').classList.toggle('active', settings.discoveryEnabled !== false);\n    document.getElementById('toggle-discovery-admin').classList.toggle('active', settings.discoveryAdminOnly !== false);\n    document.getElementById('toggle-discovery-public').classList.toggle('active', settings.discoveryAllowPublicRanges);\n    document.getElementById('discovery-max-prefix').value = settings.discoveryMaxPrefix || 20;\n    document.getElementById('toggle-webhook').classList.toggle('active', settings.webhookEnabled);\n    document.getElementById('webhook-url').value = settings.webhookUrl || '';\n    document.getElementById('webhook-url-row').style.display = settings.webhookEnabled ? 'flex' : 'none';\n    document.getElementById('toggle-backup').classList.toggle('active', settings.backupEnabled);\n    document.getElementById('backup-interval').value = settings.backupIntervalHours || 24;\n    document.getElementById('backup-retention').value = settings.backupRetentionCount || 7;\n    document.getElementById('backup-options').style.display = settings.backupEnabled ? 'flex' : 'none';\n    document.getElementById('admin-path').value = settings.adminPath || 'admin';\n    document.getElementById('admin-path-info').style.display = 'flex';\n    document.getElementById('admin-path-current').textContent = 'Current path: /' + (settings.adminPath || 'admin');\n    document.getElementById('toggle-show-admin-link').classList.toggle('active', settings.showAdminLink !== false);\n    updateSourceUI();\n  }\n\n  async function saveAdminPath() {\n    var newPath = document.getElementById('admin-path').value.trim();\n    if (!newPath) {\n      showStatus('Admin path cannot be empty', 'error');\n      return;\n    }\n    if (!/^[a-zA-Z0-9_-]+$/.test(newPath)) {\n      showStatus('Admin path can only contain letters, numbers, hyphens, and underscores', 'error');\n      return;\n    }\n    if (newPath.length < 2) {\n      showStatus('Admin path must be at least 2 characters', 'error');\n      return;\n    }\n    var reserved = ['api', 's', 'ws', 'auth', 'public', 'static', 'assets'];\n    if (reserved.includes(newPath.toLowerCase())) {\n      showStatus('This path is reserved', 'error');\n      return;\n    }\n    try {\n      var res = await fetch('/api/admin/settings', {\n        method: 'POST',\n        headers: csrfHeaders({ 'Content-Type': 'application/json' }),\n        body: JSON.stringify({ adminPath: newPath })\n      });\n      var data = await res.json();\n      if (!res.ok) {\n        showStatus(data.error || 'Failed to save', 'error');\n        return;\n      }\n      document.getElementById('admin-path-current').textContent = 'Current path: /' + newPath;\n      showStatus('Admin path saved! Redirecting to new path...', 'success');\n      setTimeout(function() {\n        window.location.href = '/' + newPath;\n      }, 1500);\n    } catch (e) {\n      showStatus('Error saving admin path', 'error');\n    }\n  }\n\n  async function saveRateLimitSettings() {\n    var attempts = parseInt(document.getElementById('rate-limit-attempts').value) || 10;\n    var windowVal = parseInt(document.getElementById('rate-limit-window').value) || 60;\n    await fetch('/api/admin/settings', {\n      method: 'POST',\n      headers: csrfHeaders({ 'Content-Type': 'application/json' }),\n      body: JSON.stringify({ rateLimitMaxAttempts: attempts, rateLimitWindow: windowVal })\n    });\n    showStatus('Rate limit settings saved', 'success');\n  }\n\n  async function saveForcedTheme() {\n    var val = document.getElementById('forced-theme').value;\n    await fetch('/api/admin/settings', {\n      method: 'POST',\n      headers: csrfHeaders({ 'Content-Type': 'application/json' }),\n      body: JSON.stringify({ forcedTheme: val })\n    });\n    showStatus('Theme setting saved', 'success');\n  }\n\n  async function toggleSetting(key) {\n    if (key === 'instancePasswordEnabled' && settings.envAdminPasswordSet) return;\n    settings[key] = !settings[key];\n    renderSettings();\n    await fetch('/api/admin/settings', {\n      method: 'POST',\n      headers: csrfHeaders({ 'Content-Type': 'application/json' }),\n      body: JSON.stringify({ [key]: settings[key] })\n    });\n    showStatus('Setting updated', 'success');\n  }\n\n  async function setInstancePassword() {\n    var pwd = document.getElementById('instance-password').value;\n    if (pwd.length < 10) {\n      showStatus('Password must be at least 10 characters', 'error');\n      return;\n    }\n    await fetch('/api/admin/settings', {\n      method: 'POST',\n      headers: csrfHeaders({ 'Content-Type': 'application/json' }),\n      body: JSON.stringify({ instancePassword: pwd })\n    });\n    document.getElementById('instance-password').value = '';\n    showStatus('Password updated', 'success');\n    loadSettings();\n  }\n\n  async function saveUpdateInterval() {\n    var val = parseInt(document.getElementById('update-interval').value) || 0;\n    await fetch('/api/admin/settings', {\n      method: 'POST',\n      headers: csrfHeaders({ 'Content-Type': 'application/json' }),\n      body: JSON.stringify({ updateIntervalHours: val })\n    });\n    showStatus('Update interval saved', 'success');\n  }\n\n  async function triggerUpdate() {\n    var btn = document.getElementById('update-btn');\n    btn.disabled = true;\n    btn.textContent = 'Updating...';\n    try {\n      var res = await fetch('/api/admin/update', { method: 'POST', headers: csrfHeaders() });\n      var data = await res.json();\n      if (data.success) {\n        var msg = 'Updated to v' + (data.version || '?') + ' (' + Math.round(data.size / 1024) + 'KB)';\n        if (data.previousVersion && data.previousVersion !== data.version) msg = 'Updated v' + data.previousVersion + ' → v' + data.version + ' (' + Math.round(data.size / 1024) + 'KB)';\n        showStatus(msg, 'success');\n        loadSettings();\n      } else {\n        showStatus(data.error || 'Update failed', 'error');\n      }\n    } catch (e) {\n      showStatus('Update failed', 'error');\n    }\n    btn.disabled = false;\n    btn.textContent = 'Update Now';\n  }\n\n  async function checkForUpdates() {\n    var btn = document.getElementById('check-update-btn');\n    btn.disabled = true;\n    btn.textContent = 'Checking...';\n    try {\n      var res = await fetch('/api/admin/version-check');\n      var data = await res.json();\n      if (data.error) {\n        showStatus(data.error, 'error');\n      } else if (data.updateAvailable) {\n        showStatus('Update available: v' + data.latestVersion + ' (current: v' + data.currentVersion + ')', 'success');\n        var updateBtn = document.getElementById('update-btn');\n        if (updateBtn) updateBtn.style.background = '#22c55e';\n      } else {\n        showStatus('Up to date (v' + data.currentVersion + ')', 'success');\n      }\n      settings.latestGitHubVersion = data.latestVersion;\n      settings.lastVersionCheck = data.lastChecked;\n      updateSourceUI();\n    } catch (e) {\n      showStatus('Version check failed', 'error');\n    }\n    btn.disabled = false;\n    btn.textContent = 'Check for Updates';\n  }\n\n  async function changeSourceMode() {\n    var mode = document.getElementById('source-mode').value;\n    try {\n      var res = await fetch('/api/admin/source-mode', {\n        method: 'POST',\n        headers: csrfHeaders({ 'Content-Type': 'application/json' }),\n        body: JSON.stringify({ mode: mode })\n      });\n      var data = await res.json();\n      if (data.success) {\n        showStatus('Source mode changed to ' + mode, 'success');\n        loadSettings();\n      } else {\n        showStatus(data.error || 'Failed to change mode', 'error');\n      }\n    } catch (e) {\n      showStatus('Failed to change mode', 'error');\n    }\n  }\n\n  async function uploadFile() {\n    var input = document.getElementById('upload-file');\n    if (!input.files || !input.files[0]) return;\n    var file = input.files[0];\n    var formData = new FormData();\n    formData.append('file', file);\n    try {\n      var res = await fetch('/api/admin/upload-html', { method: 'POST', headers: csrfHeaders(), body: formData });\n      var data = await res.json();\n      if (data.success) {\n        showStatus('Uploaded successfully (' + Math.round(data.size / 1024) + 'KB), ' + data.edition + ' edition', 'success');\n        loadSettings();\n      } else {\n        showStatus(data.error || 'Upload failed', 'error');\n      }\n    } catch (e) {\n      showStatus('Upload failed', 'error');\n    }\n    input.value = '';\n  }\n\n  function updateSourceUI() {\n    var isLocal = settings.skipUpdates;\n    document.getElementById('source-mode').value = isLocal ? 'local' : 'github';\n    document.getElementById('github-settings').style.display = isLocal ? 'none' : 'flex';\n    document.getElementById('github-update-row').style.display = isLocal ? 'none' : 'flex';\n    document.getElementById('upload-row').style.display = isLocal ? 'flex' : 'none';\n    var ver = settings.currentFileVersion || 'unknown';\n    var edition = settings.currentFileEdition || 'unknown';\n    var sizeKB = settings.currentFileSize ? Math.round(settings.currentFileSize / 1024) : 0;\n    var versionEl = document.getElementById('current-version-badge');\n    var editionEl = document.getElementById('current-edition-badge');\n    var sizeEl = document.getElementById('file-size-info');\n    var lastUpdateEl = document.getElementById('last-update-info');\n    var statusBadge = document.getElementById('version-status-badge');\n    if (versionEl) versionEl.textContent = 'v' + ver;\n    if (editionEl) editionEl.textContent = edition.charAt(0).toUpperCase() + edition.slice(1) + ' edition';\n    if (sizeEl) sizeEl.textContent = sizeKB + 'KB';\n    if (lastUpdateEl) {\n      if (settings.lastUpdateTimestamp) {\n        var d = new Date(settings.lastUpdateTimestamp);\n        lastUpdateEl.textContent = 'Last updated: ' + d.toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' }) + ' at ' + d.toLocaleTimeString(undefined, { hour: 'numeric', minute: '2-digit' });\n      } else {\n        lastUpdateEl.textContent = 'Never updated from GitHub';\n      }\n    }\n    if (statusBadge) {\n      var latest = settings.latestGitHubVersion;\n      if (latest) latest = latest.replace(/^[\"']+|[\"']+$/g, '');\n      if (latest && latest !== 'unknown' && ver !== 'unknown') {\n        var isNewer = (function(a, b) {\n          var pa = a.replace(/^v/i, '').split('.').map(Number);\n          var pb = b.replace(/^v/i, '').split('.').map(Number);\n          for (var i = 0; i < Math.max(pa.length, pb.length); i++) {\n            if ((pa[i] || 0) > (pb[i] || 0)) return true;\n            if ((pa[i] || 0) < (pb[i] || 0)) return false;\n          }\n          return false;\n        })(latest, ver);\n        if (isNewer) {\n          statusBadge.textContent = 'v' + latest + ' available';\n          statusBadge.style.background = '#854d0e';\n          statusBadge.style.color = '#fbbf24';\n        } else {\n          statusBadge.textContent = 'Up to date';\n          statusBadge.style.background = '#166534';\n          statusBadge.style.color = '#4ade80';\n        }\n      } else {\n        statusBadge.textContent = '';\n      }\n    }\n  }\n\n  async function saveRoomDefaults() {\n    await fetch('/api/admin/settings', {\n      method: 'POST',\n      headers: csrfHeaders({ 'Content-Type': 'application/json' }),\n      body: JSON.stringify({\n        defaultDestructMode: document.getElementById('default-destruct-mode').value,\n        defaultDestructHours: parseInt(document.getElementById('default-destruct-hours').value) || 24,\n        maxRoomsPerInstance: parseInt(document.getElementById('max-rooms').value) || 0,\n        defaultRoomTheme: document.getElementById('default-room-theme').value\n      })\n    });\n    showStatus('Room defaults saved', 'success');\n  }\n\n  function showStatus(msg, type) {\n    var container = document.getElementById('settings-status');\n    setContent(container, h('div', {className: 'status-msg ' + type}, msg));\n    setTimeout(function() { clearNode(container); }, 3000);\n  }\n\n  function showAuthStatus(msg, type) {\n    var container = document.getElementById('auth-status');\n    setContent(container, h('div', {className: 'status-msg ' + type}, msg));\n    setTimeout(function() { clearNode(container); }, 3000);\n  }\n\n  function renderStats() {\n    var active = rooms.filter(function(r) { return r.connectedUsers > 0; }).length;\n    var withPwd = rooms.filter(function(r) { return r.hasPassword; }).length;\n    var totalUsers = rooms.reduce(function(a, r) { return a + r.connectedUsers; }, 0);\n    function statCard(value, label) {\n      return h('div', {className: 'stat-card'},\n        h('div', {className: 'stat-value'}, String(value)),\n        h('div', {className: 'stat-label'}, label)\n      );\n    }\n    setContent(document.getElementById('stats'), [\n      statCard(rooms.length, 'Total Rooms'),\n      statCard(active, 'Active'),\n      statCard(withPwd, 'Protected'),\n      statCard(totalUsers, 'Users Online')\n    ]);\n  }\n\n  function renderRooms() {\n    var container = document.getElementById('room-list');\n    if (rooms.length === 0) {\n      setContent(container, h('div', {className: 'empty-state'},\n        h('h3', null, 'No rooms yet'),\n        h('p', null, 'Rooms will appear here when created')\n      ));\n      return;\n    }\n\n    var header = h('div', {className: 'room-header'},\n      h('input', {type: 'checkbox', className: 'room-checkbox', 'data-action': 'toggleAll', checked: selected.size === rooms.length && rooms.length > 0}),\n      h('span', null, 'Room'), h('span', null, 'Users'), h('span', null, 'Created'), h('span', null, 'Password'), h('span', null, 'Actions')\n    );\n\n    var rows = rooms.map(function(r) {\n      var isSelected = selected.has(r.id);\n      return h('div', {className: 'room-row' + (isSelected ? ' selected' : '')},\n        h('input', {type: 'checkbox', className: 'room-checkbox', checked: isSelected, 'data-action': 'toggleSelect', 'data-id': r.id}),\n        h('div', null,\n          h('div', {className: 'room-name'}, 'Room'),\n          h('div', {className: 'room-id'}, r.id)\n        ),\n        h('div', null, h('span', {className: 'badge badge-' + (r.connectedUsers > 0 ? 'green' : 'gray')}, String(r.connectedUsers))),\n        h('div', null, new Date(r.created).toLocaleDateString()),\n        h('div', null, h('span', {className: 'badge badge-' + (r.hasPassword ? 'yellow' : 'gray')}, r.hasPassword ? 'Yes' : 'No')),\n        h('div', {className: 'room-actions'},\n          h('button', {className: 'btn btn-secondary btn-sm', 'data-action': 'viewRoom', 'data-id': r.id}, 'View'),\n          h('button', {className: 'btn btn-primary btn-sm', 'data-action': 'joinRoom', 'data-id': r.id}, 'Join'),\n          h('button', {className: 'btn btn-danger btn-sm', 'data-action': 'deleteRoom', 'data-id': r.id}, 'Del')\n        )\n      );\n    });\n\n    setContent(container, [header].concat(rows));\n  }\n\n  function toggleSelect(id, checked) {\n    if (checked) {\n      selected.add(id);\n    } else {\n      selected.delete(id);\n    }\n    updateBulkUI();\n    renderRooms();\n  }\n\n  function toggleAll(checked) {\n    if (checked) {\n      rooms.forEach(function(r) { selected.add(r.id); });\n    } else {\n      selected.clear();\n    }\n    updateBulkUI();\n    renderRooms();\n  }\n\n  function clearSelection() {\n    selected.clear();\n    updateBulkUI();\n    renderRooms();\n  }\n\n  function updateBulkUI() {\n    var bulk = document.getElementById('bulk-actions');\n    var count = document.getElementById('selected-count');\n    if (selected.size > 0) {\n      bulk.classList.add('active');\n      count.textContent = selected.size + ' selected';\n    } else {\n      bulk.classList.remove('active');\n    }\n  }\n\n  async function deleteSelected() {\n    if (selected.size === 0) return;\n    if (!confirm('Delete ' + selected.size + ' room(s) permanently?')) return;\n    for (var id of selected) {\n      try {\n        await fetch('/api/admin/rooms/' + id, { method: 'DELETE', headers: csrfHeaders() });\n      } catch (e) {\n      }\n    }\n    selected.clear();\n    loadData();\n  }\n\n  function viewRoom(id) {\n    var room = rooms.find(function(r) { return r.id === id; });\n    if (!room) return;\n\n    var destruct = 'Never';\n    if (room.destruct.mode === 'time') {\n      var hours = room.destruct.value / 3600000;\n      destruct = hours < 1\n        ? Math.round(hours * 60) + ' min'\n        : hours < 24\n          ? hours + ' hours'\n          : Math.round(hours / 24) + ' days';\n    } else if (room.destruct.mode === 'empty') {\n      destruct = 'When empty';\n    }\n\n    document.getElementById('modal-title').textContent = 'Room Details';\n    function infoRow(label, value, extraStyle) {\n      return h('div', {className: 'info-row'},\n        h('span', {className: 'info-label'}, label),\n        h('span', {className: 'info-value', style: extraStyle || ''}, value)\n      );\n    }\n    setContent(document.getElementById('modal-body'), [\n      infoRow('Room ID', room.id, 'font-family:monospace;font-size:12px'),\n      infoRow('Created', new Date(room.created).toLocaleString()),\n      infoRow('Last Activity', new Date(room.lastActivity).toLocaleString()),\n      infoRow('Connected Users', String(room.connectedUsers)),\n      infoRow('Password Protected', room.hasPassword ? 'Yes' : 'No'),\n      infoRow('Self-Destruct', destruct),\n      h('div', {style: 'margin-top:20px;display:flex;gap:12px;flex-wrap:wrap'},\n        h('button', {className: 'btn btn-primary', 'data-action': 'joinRoom', 'data-id': room.id}, 'Join Room'),\n        h('button', {className: 'btn btn-danger', 'data-action': 'deleteRoom', 'data-id': room.id}, 'Delete Room')\n      )\n    ]);\n\n    document.getElementById('room-modal').classList.add('active');\n  }\n\n  function closeModal() {\n    document.getElementById('room-modal').classList.remove('active');\n  }\n\n  function joinRoom(id) {\n    window.open('/s/' + id, '_blank');\n  }\n\n  async function deleteRoom(id) {\n    if (!confirm('Delete this room permanently?')) return;\n    try {\n      var res = await fetch('/api/admin/rooms/' + id, { method: 'DELETE', headers: csrfHeaders() });\n      if (res.ok) {\n        selected.delete(id);\n        loadData();\n      } else {\n        showStatus('Failed to delete room', 'error');\n      }\n    } catch (e) {\n      showStatus('Error deleting room', 'error');\n    }\n  }\n\n  async function logout() {\n    await fetch('/api/logout', { method: 'POST', credentials: 'include', headers: csrfHeaders() });\n    window.location.href = '/';\n  }\n\n  document.getElementById('room-modal').addEventListener('click', function(e) {\n    if (e.target.id === 'room-modal') closeModal();\n  });\n  document.getElementById('apikey-modal').addEventListener('click', function(e) {\n    if (e.target.id === 'apikey-modal') closeApiKeyModal();\n  });\n\n  async function saveWebhookUrl() {\n    var url = document.getElementById('webhook-url').value;\n    await fetch('/api/admin/settings', {\n      method: 'POST',\n      headers: csrfHeaders({ 'Content-Type': 'application/json' }),\n      body: JSON.stringify({ webhookUrl: url })\n    });\n    showStatus('Webhook URL saved', 'success');\n  }\n\n  async function saveDiscoveryPrefix() {\n    var prefix = parseInt(document.getElementById('discovery-max-prefix').value) || 20;\n    await fetch('/api/admin/settings', {\n      method: 'POST',\n      headers: csrfHeaders({ 'Content-Type': 'application/json' }),\n      body: JSON.stringify({ discoveryMaxPrefix: prefix })\n    });\n    showStatus('Discovery prefix saved', 'success');\n  }\n\n  async function saveBackupSettings() {\n    var interval = parseInt(document.getElementById('backup-interval').value) || 24;\n    var retention = parseInt(document.getElementById('backup-retention').value) || 7;\n    await fetch('/api/admin/settings', {\n      method: 'POST',\n      headers: csrfHeaders({ 'Content-Type': 'application/json' }),\n      body: JSON.stringify({ backupIntervalHours: interval, backupRetentionCount: retention })\n    });\n    showStatus('Backup settings saved', 'success');\n  }\n\n  async function loadActivityLogs() {\n    try {\n      var room = document.getElementById('activity-search').value;\n      var url = room\n        ? '/api/admin/activity-logs?room=' + encodeURIComponent(room)\n        : '/api/admin/activity-logs';\n      var res = await fetch(url);\n      if (!res.ok) return;\n      var data = await res.json();\n      renderActivityLogs(data.logs);\n    } catch (e) {\n    }\n  }\n\n  async function loadAuditLogs() {\n    try {\n      var q = document.getElementById('audit-search').value;\n      var url = q\n        ? '/api/admin/audit-logs?q=' + encodeURIComponent(q)\n        : '/api/admin/audit-logs';\n      var res = await fetch(url);\n      if (!res.ok) return;\n      var data = await res.json();\n      renderAuditLogs(data.logs);\n    } catch (e) {\n    }\n  }\n\n  function renderActivityLogs(logs) {\n    var container = document.getElementById('activity-log-list');\n    if (!logs || logs.length === 0) {\n      setContent(container, h('p', {style: 'color:var(--text-soft);padding:12px'}, 'No activity logs'));\n      return;\n    }\n    setContent(container, logs.map(function(l) {\n      return h('div', {style: 'padding:8px 0;border-bottom:1px solid var(--border);font-size:13px'},\n        h('span', {style: 'color:var(--text-soft)'}, new Date(l.timestamp).toLocaleString()), ' ',\n        h('span', {className: 'badge badge-' + (l.eventType === 'join' ? 'green' : 'gray')}, l.eventType), ' ',\n        l.userName || 'Unknown', ' ',\n        h('span', {style: 'color:var(--text-soft);font-size:11px'}, l.roomId.slice(0, 8))\n      );\n    }));\n  }\n\n  function renderAuditLogs(logs) {\n    var container = document.getElementById('audit-log-list');\n    if (!logs || logs.length === 0) {\n      setContent(container, h('p', {style: 'color:var(--text-soft);padding:12px'}, 'No audit logs'));\n      return;\n    }\n    setContent(container, logs.map(function(l) {\n      return h('div', {style: 'padding:8px 0;border-bottom:1px solid var(--border);font-size:13px'},\n        h('span', {style: 'color:var(--text-soft)'}, new Date(l.timestamp).toLocaleString()), ' ',\n        h('span', {className: 'badge badge-yellow'}, l.action), ' ',\n        l.actor || 'system',\n        l.targetId ? [' ', h('span', {style: 'color:var(--text-soft);font-size:11px'}, l.targetId.slice(0, 8))] : null\n      );\n    }));\n  }\n\n  async function loadBackups() {\n    try {\n      var res = await fetch('/api/admin/backups');\n      if (!res.ok) return;\n      var data = await res.json();\n      renderBackups(data.backups);\n    } catch (e) {\n    }\n  }\n\n  function renderBackups(backups) {\n    var container = document.getElementById('backup-list');\n    if (!backups || backups.length === 0) {\n      setContent(container, h('p', {style: 'color:var(--text-soft);padding:12px'}, 'No backups'));\n      return;\n    }\n    setContent(container, backups.map(function(b) {\n      return h('div', {style: 'display:flex;justify-content:space-between;align-items:center;padding:12px 0;border-bottom:1px solid var(--border)'},\n        h('div', null,\n          h('div', {style: 'font-weight:500'}, b.filename),\n          h('div', {style: 'font-size:12px;color:var(--text-soft)'},\n            new Date(b.createdAt).toLocaleString() + ' | ' + Math.round(b.sizeBytes / 1024) + 'KB | ' + b.roomCount + ' rooms' + (b.autoGenerated ? ' | Auto' : '')\n          )\n        ),\n        h('div', {style: 'display:flex;gap:6px'},\n          h('button', {className: 'btn btn-sm btn-secondary', 'data-action': 'downloadBackup', 'data-id': b.id}, 'Download'),\n          h('button', {className: 'btn btn-sm btn-success', 'data-action': 'restoreBackup', 'data-id': b.id}, 'Restore'),\n          h('button', {className: 'btn btn-sm btn-danger', 'data-action': 'deleteBackup', 'data-id': b.id}, 'Delete')\n        )\n      );\n    }));\n  }\n\n  async function createBackup() {\n    try {\n      var res = await fetch('/api/admin/backups', { method: 'POST', headers: csrfHeaders() });\n      var data = await res.json();\n      if (data.success) {\n        showStatus('Backup created', 'success');\n        loadBackups();\n      } else {\n        showStatus(data.error || 'Failed', 'error');\n      }\n    } catch (e) {\n      showStatus('Failed to create backup', 'error');\n    }\n  }\n\n  async function downloadBackup(id) {\n    window.open('/api/admin/backups/' + id + '/download', '_blank');\n  }\n\n  async function restoreBackup(id) {\n    if (!confirm('Restore this backup? This will add missing rooms.')) return;\n    try {\n      var res = await fetch('/api/admin/backups/' + id + '/restore', { method: 'POST', headers: csrfHeaders() });\n      var data = await res.json();\n      if (data.success) {\n        showStatus('Restored ' + data.roomsRestored + ' rooms', 'success');\n        loadData();\n      } else {\n        showStatus(data.error || 'Failed', 'error');\n      }\n    } catch (e) {\n      showStatus('Failed to restore', 'error');\n    }\n  }\n\n  async function deleteBackup(id) {\n    if (!confirm('Delete this backup?')) return;\n    try {\n      await fetch('/api/admin/backups/' + id, { method: 'DELETE', headers: csrfHeaders() });\n      loadBackups();\n    } catch (e) {\n    }\n  }\n\n  async function exportAll() {\n    window.open('/api/admin/export', '_blank');\n  }\n\n  async function loadApiKeys() {\n    try {\n      var res = await fetch('/api/admin/api-keys');\n      if (!res.ok) return;\n      var data = await res.json();\n      renderApiKeys(data.keys);\n    } catch (e) {\n    }\n  }\n\n  function renderApiKeys(keys) {\n    var container = document.getElementById('apikey-list');\n    if (!keys || keys.length === 0) {\n      setContent(container, h('p', {style: 'color:var(--text-soft);padding:12px'}, 'No API keys'));\n      return;\n    }\n    setContent(container, keys.map(function(k) {\n      return h('div', {style: 'display:flex;justify-content:space-between;align-items:center;padding:12px 0;border-bottom:1px solid var(--border)'},\n        h('div', null,\n          h('div', {style: 'font-weight:500'}, k.name),\n          h('div', {style: 'font-size:12px;color:var(--text-soft)'},\n            k.permissions.join(', ') + ' | Created: ' + new Date(k.createdAt).toLocaleDateString() +\n            (k.lastUsed ? ' | Last used: ' + new Date(k.lastUsed).toLocaleDateString() : '') +\n            (k.expiresAt ? ' | Expires: ' + new Date(k.expiresAt).toLocaleDateString() : '')\n          )\n        ),\n        h('button', {className: 'btn btn-sm btn-danger', 'data-action': 'revokeApiKey', 'data-id': k.id}, 'Revoke')\n      );\n    }));\n  }\n\n  function showCreateApiKey() {\n    document.getElementById('apikey-modal').classList.add('active');\n    document.getElementById('new-key-display').style.display = 'none';\n  }\n\n  function closeApiKeyModal() {\n    document.getElementById('apikey-modal').classList.remove('active');\n  }\n\n  async function createApiKey() {\n    var name = document.getElementById('apikey-name').value;\n    if (!name) {\n      showStatus('Name required', 'error');\n      return;\n    }\n    var perms = [];\n    if (document.getElementById('perm-read').checked) perms.push('read');\n    if (document.getElementById('perm-write').checked) perms.push('write');\n    if (document.getElementById('perm-admin').checked) perms.push('admin');\n    var expires = parseInt(document.getElementById('apikey-expires').value) || 0;\n    try {\n      var res = await fetch('/api/admin/api-keys', {\n        method: 'POST',\n        headers: csrfHeaders({ 'Content-Type': 'application/json' }),\n        body: JSON.stringify({ name: name, permissions: perms, expiresInDays: expires || null })\n      });\n      var data = await res.json();\n      if (data.key) {\n        closeApiKeyModal();\n        document.getElementById('new-key-display').style.display = 'block';\n        document.getElementById('new-key-value').textContent = data.key;\n        loadApiKeys();\n      } else {\n        showStatus(data.error || 'Failed to create key', 'error');\n      }\n    } catch (e) {\n      showStatus('Failed to create key', 'error');\n    }\n  }\n\n  async function revokeApiKey(id) {\n    if (!confirm('Revoke this API key?')) return;\n    try {\n      await fetch('/api/admin/api-keys/' + id, { method: 'DELETE', headers: csrfHeaders() });\n      loadApiKeys();\n    } catch (e) {\n    }\n  }\n\n  async function loadUsers(q) {\n    q = q || '';\n    try {\n      var url = q\n        ? '/api/admin/users?q=' + encodeURIComponent(q)\n        : '/api/admin/users';\n      var res = await fetch(url);\n      if (!res.ok) return;\n      var data = await res.json();\n      users = data.users || [];\n      renderUsers();\n    } catch (e) {\n    }\n  }\n\n  function searchUsers(q) {\n    if (searchTimeout) clearTimeout(searchTimeout);\n    searchTimeout = setTimeout(function() { loadUsers(q); }, 300);\n  }\n\n  function renderUsers() {\n    var container = document.getElementById('user-list');\n    if (!users || users.length === 0) {\n      setContent(container, h('div', {className: 'empty-state'},\n        h('h3', null, 'No users yet'),\n        h('p', null, 'Users will appear here when registered')\n      ));\n      return;\n    }\n\n    var header = h('div', {className: 'room-header'},\n      h('span', null, 'User'), h('span', null, 'Role'), h('span', null, 'Status'), h('span', null, 'Created'), h('span', null, 'Actions')\n    );\n\n    var rows = users.map(function(u) {\n      return h('div', {className: 'room-row'},\n        h('div', null,\n          h('div', {className: 'room-name'}, u.displayName || 'No name'),\n          h('div', {className: 'room-id'}, u.email)\n        ),\n        h('div', null, h('span', {className: 'badge badge-' + (u.role === 'admin' ? 'yellow' : 'gray')}, u.role === 'admin' ? 'Admin' : 'User')),\n        h('div', null,\n          h('span', {className: 'badge badge-' + (u.isActive ? 'green' : 'gray')}, u.isActive ? 'Active' : 'Inactive'),\n          u.emailVerified ? null : h('span', {className: 'badge badge-yellow'}, 'Unverified')\n        ),\n        h('div', null, new Date(u.createdAt).toLocaleDateString()),\n        h('div', {className: 'room-actions'},\n          h('button', {className: 'btn btn-secondary btn-sm', 'data-action': 'editUser', 'data-id': u.id}, 'Edit'),\n          h('button', {className: 'btn btn-danger btn-sm', 'data-action': 'deleteUser', 'data-id': u.id}, 'Del')\n        )\n      );\n    });\n\n    setContent(container, [header].concat(rows));\n  }\n\n  function showCreateUser() {\n    document.getElementById('user-modal').classList.add('active');\n  }\n\n  function closeUserModal() {\n    document.getElementById('user-modal').classList.remove('active');\n    document.getElementById('user-email').value = '';\n    document.getElementById('user-displayname').value = '';\n    document.getElementById('user-password').value = '';\n    document.getElementById('user-role').value = 'user';\n  }\n\n  async function createUser() {\n    var email = document.getElementById('user-email').value;\n    var displayName = document.getElementById('user-displayname').value;\n    var password = document.getElementById('user-password').value || null;\n    var role = document.getElementById('user-role').value;\n    if (!email) {\n      showAuthStatus('Email required', 'error');\n      return;\n    }\n    try {\n      var res = await fetch('/api/admin/users', {\n        method: 'POST',\n        headers: csrfHeaders({ 'Content-Type': 'application/json' }),\n        body: JSON.stringify({ email: email, displayName: displayName, password: password, role: role })\n      });\n      var data = await res.json();\n      if (data.success) {\n        closeUserModal();\n        loadUsers();\n        showAuthStatus('User created', 'success');\n      } else {\n        showAuthStatus(data.error || 'Failed to create user', 'error');\n      }\n    } catch (e) {\n      showAuthStatus('Failed to create user', 'error');\n    }\n  }\n\n  async function deleteUser(id) {\n    if (!confirm('Delete this user permanently?')) return;\n    try {\n      await fetch('/api/admin/users/' + id, { method: 'DELETE', headers: csrfHeaders() });\n      loadUsers();\n    } catch (e) {\n      showAuthStatus('Error deleting user', 'error');\n    }\n  }\n\n  function editUser(id) {\n    var u = users.find(function(x) { return x.id === id; });\n    if (!u) return;\n    document.getElementById('edit-user-id').value = id;\n    document.getElementById('edit-user-email').value = u.email;\n    document.getElementById('edit-user-displayname').value = u.displayName || '';\n    document.getElementById('edit-user-password').value = '';\n    document.getElementById('edit-user-role').value = u.role || 'user';\n    document.getElementById('edit-user-active').checked = u.isActive !== false;\n    document.getElementById('edit-user-verified').checked = u.emailVerified === true;\n    document.getElementById('edit-user-modal').classList.add('active');\n  }\n\n  function closeEditUserModal() {\n    document.getElementById('edit-user-modal').classList.remove('active');\n  }\n\n  async function saveUserEdit() {\n    var id = document.getElementById('edit-user-id').value;\n    var data = {\n      displayName: document.getElementById('edit-user-displayname').value,\n      role: document.getElementById('edit-user-role').value,\n      isActive: document.getElementById('edit-user-active').checked,\n      emailVerified: document.getElementById('edit-user-verified').checked\n    };\n    var password = document.getElementById('edit-user-password').value;\n    if (password) data.password = password;\n    try {\n      var res = await fetch('/api/admin/users/' + id, {\n        method: 'PUT',\n        headers: csrfHeaders({ 'Content-Type': 'application/json' }),\n        body: JSON.stringify(data)\n      });\n      var result = await res.json();\n      if (result.success) {\n        closeEditUserModal();\n        loadUsers();\n        showAuthStatus('User updated', 'success');\n      } else {\n        showAuthStatus(result.error || 'Failed to update user', 'error');\n      }\n    } catch (e) {\n      showAuthStatus('Failed to update user', 'error');\n    }\n  }\n\n  async function resetUserPassword() {\n    var id = document.getElementById('edit-user-id').value;\n    var email = document.getElementById('edit-user-email').value;\n    if (!confirm('Send password reset email to ' + email + '?')) return;\n    try {\n      var res = await fetch('/api/admin/users/' + id + '/reset-password', { method: 'POST', headers: csrfHeaders() });\n      var result = await res.json();\n      if (result.success) {\n        showAuthStatus('Password reset email sent!', 'success');\n      } else {\n        showAuthStatus(result.error || 'Failed to send reset email', 'error');\n      }\n    } catch (e) {\n      showAuthStatus('Failed to send reset email', 'error');\n    }\n  }\n\n  document.getElementById('user-modal').addEventListener('click', function(e) {\n    if (e.target.id === 'user-modal') closeUserModal();\n  });\n  document.getElementById('edit-user-modal').addEventListener('click', function(e) {\n    if (e.target.id === 'edit-user-modal') closeEditUserModal();\n  });\n\n  async function loadAuthSettings() {\n    try {\n      var res = await fetch('/api/admin/auth-settings');\n      if (!res.ok) return;\n      authSettings = await res.json();\n      renderAuthSettings();\n    } catch (e) {\n    }\n  }\n\n  function renderAuthSettings() {\n    document.getElementById('auth-mode').value = authSettings.authMode || 'open';\n    document.getElementById('toggle-require-email-verify').classList.toggle('active', authSettings.requireEmailVerification);\n    document.getElementById('toggle-magic-link').classList.toggle('active', authSettings.allowMagicLinkLogin);\n    document.getElementById('toggle-oidc-email-match').classList.toggle('active', authSettings.oidcEmailMatching);\n    document.getElementById('toggle-production-mode').classList.toggle('active', authSettings.productionMode);\n    document.getElementById('toggle-guest-room-create').classList.toggle('active', authSettings.allowGuestRoomCreation !== false);\n    document.getElementById('toggle-guest-room-join').classList.toggle('active', authSettings.allowGuestRoomJoin !== false);\n    document.getElementById('toggle-room-creator-guest').classList.toggle('active', authSettings.allowRoomCreatorGuestSetting !== false);\n    document.getElementById('toggle-share-button').classList.toggle('active', authSettings.shareButtonEnabled !== false);\n    document.getElementById('input-id-token-max-age').value = authSettings.idTokenMaxAgeHours || 2;\n    document.getElementById('input-email-rate-window').value = authSettings.emailRateLimitWindowSeconds || 300;\n    document.getElementById('input-email-rate-max').value = authSettings.emailRateLimitMaxAttempts || 3;\n  }\n\n  async function saveAuthSettings() {\n    var mode = document.getElementById('auth-mode').value;\n    await fetch('/api/admin/auth-settings', {\n      method: 'POST',\n      headers: csrfHeaders({ 'Content-Type': 'application/json' }),\n      body: JSON.stringify({ authMode: mode })\n    });\n    showAuthStatus('Auth mode saved', 'success');\n  }\n\n  async function toggleAuthSetting(key) {\n    authSettings[key] = !authSettings[key];\n    renderAuthSettings();\n    await fetch('/api/admin/auth-settings', {\n      method: 'POST',\n      headers: csrfHeaders({ 'Content-Type': 'application/json' }),\n      body: JSON.stringify({ [key]: authSettings[key] })\n    });\n    showAuthStatus('Setting updated', 'success');\n  }\n\n  async function updateAuthNumber(key, value) {\n    var num = parseInt(value);\n    if (isNaN(num)) return;\n    authSettings[key] = num;\n    await fetch('/api/admin/auth-settings', {\n      method: 'POST',\n      headers: csrfHeaders({ 'Content-Type': 'application/json' }),\n      body: JSON.stringify({ [key]: num })\n    });\n    showAuthStatus('Setting updated', 'success');\n  }\n\n  async function loadOidcProviders() {\n    try {\n      var res = await fetch('/api/admin/oidc-providers');\n      if (!res.ok) return;\n      var data = await res.json();\n      oidcProviders = data.providers || [];\n      renderOidcProviders();\n    } catch (e) {\n    }\n  }\n\n  function renderOidcProviders() {\n    var container = document.getElementById('oidc-provider-list');\n    if (!oidcProviders || oidcProviders.length === 0) {\n      setContent(container, h('p', {style: 'color:var(--text-soft);padding:12px'}, 'No OIDC providers configured'));\n      return;\n    }\n    setContent(container, oidcProviders.map(function(p) {\n      return h('div', {style: 'display:flex;justify-content:space-between;align-items:center;padding:12px 0;border-bottom:1px solid var(--border)'},\n        h('div', null,\n          h('div', {style: 'font-weight:500'}, p.name),\n          h('div', {style: 'font-size:12px;color:var(--text-soft)'},\n            p.providerType, ' | ',\n            h('span', {style: 'color:' + (p.isActive ? '#22c55e' : '#94a3b8')}, p.isActive ? 'Active' : 'Inactive')\n          )\n        ),\n        h('div', {style: 'display:flex;gap:6px'},\n          h('button', {className: 'btn btn-sm btn-secondary', 'data-action': 'editOidcProvider', 'data-id': p.id}, 'Edit'),\n          h('button', {className: 'btn btn-sm btn-danger', 'data-action': 'deleteOidcProvider', 'data-id': p.id}, 'Delete')\n        )\n      );\n    }));\n  }\n\n  function showAddOidcProvider() {\n    document.getElementById('oidc-modal-title').textContent = 'Add OIDC Provider';\n    document.getElementById('oidc-edit-id').value = '';\n    document.getElementById('oidc-name').value = '';\n    document.getElementById('oidc-type').value = 'generic';\n    document.getElementById('oidc-client-id').value = '';\n    document.getElementById('oidc-client-secret').value = '';\n    document.getElementById('oidc-issuer').value = '';\n    document.getElementById('oidc-auth-url').value = '';\n    document.getElementById('oidc-token-url').value = '';\n    document.getElementById('oidc-userinfo-url').value = '';\n    document.getElementById('oidc-scopes').value = 'openid email profile';\n    document.getElementById('oidc-active').checked = true;\n    document.getElementById('oidc-modal').classList.add('active');\n  }\n\n  function editOidcProvider(id) {\n    var p = oidcProviders.find(function(x) { return x.id === id; });\n    if (!p) return;\n    document.getElementById('oidc-modal-title').textContent = 'Edit OIDC Provider';\n    document.getElementById('oidc-edit-id').value = id;\n    document.getElementById('oidc-name').value = p.name;\n    document.getElementById('oidc-type').value = p.providerType;\n    document.getElementById('oidc-client-id').value = p.clientId;\n    document.getElementById('oidc-client-secret').value = '';\n    document.getElementById('oidc-issuer').value = p.issuerUrl || '';\n    document.getElementById('oidc-auth-url').value = p.authorizationUrl || '';\n    document.getElementById('oidc-token-url').value = p.tokenUrl || '';\n    document.getElementById('oidc-userinfo-url').value = p.userinfoUrl || '';\n    document.getElementById('oidc-scopes').value = p.scopes || 'openid email profile';\n    document.getElementById('oidc-active').checked = p.isActive;\n    document.getElementById('oidc-modal').classList.add('active');\n  }\n\n  function closeOidcModal() {\n    document.getElementById('oidc-modal').classList.remove('active');\n  }\n\n  async function saveOidcProvider() {\n    var id = document.getElementById('oidc-edit-id').value;\n    var data = {\n      name: document.getElementById('oidc-name').value,\n      providerType: document.getElementById('oidc-type').value,\n      clientId: document.getElementById('oidc-client-id').value,\n      clientSecret: document.getElementById('oidc-client-secret').value || undefined,\n      issuerUrl: document.getElementById('oidc-issuer').value,\n      authorizationUrl: document.getElementById('oidc-auth-url').value,\n      tokenUrl: document.getElementById('oidc-token-url').value,\n      userinfoUrl: document.getElementById('oidc-userinfo-url').value,\n      scopes: document.getElementById('oidc-scopes').value,\n      isActive: document.getElementById('oidc-active').checked\n    };\n    if (!data.name || !data.clientId) {\n      showAuthStatus('Name and Client ID required', 'error');\n      return;\n    }\n    try {\n      var url = id ? '/api/admin/oidc-providers/' + id : '/api/admin/oidc-providers';\n      var method = id ? 'PUT' : 'POST';\n      var res = await fetch(url, {\n        method: method,\n        headers: csrfHeaders({ 'Content-Type': 'application/json' }),\n        body: JSON.stringify(data)\n      });\n      var result = await res.json();\n      if (result.success) {\n        closeOidcModal();\n        loadOidcProviders();\n        showAuthStatus('Provider saved', 'success');\n      } else {\n        showAuthStatus(result.error || 'Failed to save provider', 'error');\n      }\n    } catch (e) {\n      showAuthStatus('Failed to save provider', 'error');\n    }\n  }\n\n  async function deleteOidcProvider(id) {\n    if (!confirm('Delete this OIDC provider?')) return;\n    try {\n      await fetch('/api/admin/oidc-providers/' + id, { method: 'DELETE', headers: csrfHeaders() });\n      loadOidcProviders();\n    } catch (e) {\n    }\n  }\n\n  document.getElementById('oidc-modal').addEventListener('click', function(e) {\n    if (e.target.id === 'oidc-modal') closeOidcModal();\n  });\n\n  async function loadSmtpConfigs() {\n    try {\n      var res = await fetch('/api/admin/smtp-configs');\n      if (!res.ok) return;\n      var data = await res.json();\n      smtpConfigs = data.configs || [];\n      renderSmtpConfigs();\n    } catch (e) {\n    }\n  }\n\n  function renderSmtpConfigs() {\n    var container = document.getElementById('smtp-config-list');\n    if (!smtpConfigs || smtpConfigs.length === 0) {\n      setContent(container, h('p', {style: 'color:var(--text-soft);padding:12px'}, 'No SMTP configurations'));\n      return;\n    }\n    setContent(container, smtpConfigs.map(function(s) {\n      return h('div', {style: 'display:flex;justify-content:space-between;align-items:center;padding:12px 0;border-bottom:1px solid var(--border)'},\n        h('div', null,\n          h('div', {style: 'font-weight:500'},\n            s.name,\n            s.isDefault ? [' ', h('span', {className: 'badge badge-green'}, 'Default')] : null\n          ),\n          h('div', {style: 'font-size:12px;color:var(--text-soft)'},\n            s.host, ':', String(s.port), ' (', s.secureMode, ') | ',\n            h('span', {style: 'color:' + (s.isActive ? '#22c55e' : '#94a3b8')}, s.isActive ? 'Active' : 'Inactive')\n          )\n        ),\n        h('div', {style: 'display:flex;gap:6px'},\n          h('button', {className: 'btn btn-sm btn-secondary', 'data-action': 'editSmtpConfig', 'data-id': s.id}, 'Edit'),\n          h('button', {className: 'btn btn-sm btn-danger', 'data-action': 'deleteSmtpConfig', 'data-id': s.id}, 'Delete')\n        )\n      );\n    }));\n  }\n\n  function showAddSmtpConfig() {\n    document.getElementById('smtp-modal-title').textContent = 'Add SMTP Configuration';\n    document.getElementById('smtp-edit-id').value = '';\n    document.getElementById('smtp-name').value = '';\n    document.getElementById('smtp-host').value = '';\n    document.getElementById('smtp-port').value = '587';\n    document.getElementById('smtp-secure-mode').value = 'starttls';\n    document.getElementById('smtp-username').value = '';\n    document.getElementById('smtp-password').value = '';\n    document.getElementById('smtp-from-email').value = '';\n    document.getElementById('smtp-from-name').value = '';\n    document.getElementById('smtp-default').checked = false;\n    document.getElementById('smtp-active').checked = true;\n    document.getElementById('smtp-modal').classList.add('active');\n  }\n\n  function editSmtpConfig(id) {\n    var s = smtpConfigs.find(function(x) { return x.id === id; });\n    if (!s) return;\n    document.getElementById('smtp-modal-title').textContent = 'Edit SMTP Configuration';\n    document.getElementById('smtp-edit-id').value = id;\n    document.getElementById('smtp-name').value = s.name;\n    document.getElementById('smtp-host').value = s.host || '';\n    document.getElementById('smtp-port').value = s.port || 587;\n    document.getElementById('smtp-secure-mode').value = s.secureMode || 'starttls';\n    document.getElementById('smtp-username').value = s.username || '';\n    document.getElementById('smtp-password').value = '';\n    document.getElementById('smtp-from-email').value = s.fromEmail || '';\n    document.getElementById('smtp-from-name').value = s.fromName || '';\n    document.getElementById('smtp-default').checked = s.isDefault;\n    document.getElementById('smtp-active').checked = s.isActive;\n    document.getElementById('smtp-modal').classList.add('active');\n  }\n\n  function closeSmtpModal() {\n    document.getElementById('smtp-modal').classList.remove('active');\n  }\n\n  async function saveSmtpConfig() {\n    var id = document.getElementById('smtp-edit-id').value;\n    var data = {\n      name: document.getElementById('smtp-name').value,\n      host: document.getElementById('smtp-host').value,\n      port: parseInt(document.getElementById('smtp-port').value),\n      secureMode: document.getElementById('smtp-secure-mode').value,\n      username: document.getElementById('smtp-username').value,\n      password: document.getElementById('smtp-password').value || undefined,\n      fromEmail: document.getElementById('smtp-from-email').value,\n      fromName: document.getElementById('smtp-from-name').value,\n      isDefault: document.getElementById('smtp-default').checked,\n      isActive: document.getElementById('smtp-active').checked\n    };\n    if (!data.name || !data.host) {\n      showAuthStatus('Name and Host required', 'error');\n      return;\n    }\n    try {\n      var url = id ? '/api/admin/smtp-configs/' + id : '/api/admin/smtp-configs';\n      var method = id ? 'PUT' : 'POST';\n      var res = await fetch(url, {\n        method: method,\n        headers: csrfHeaders({ 'Content-Type': 'application/json' }),\n        body: JSON.stringify(data)\n      });\n      var result = await res.json();\n      if (result.success) {\n        closeSmtpModal();\n        loadSmtpConfigs();\n        showAuthStatus('SMTP config saved', 'success');\n      } else {\n        showAuthStatus(result.error || 'Failed to save config', 'error');\n      }\n    } catch (e) {\n      showAuthStatus('Failed to save config', 'error');\n    }\n  }\n\n  async function testSmtpConfig() {\n    var data = {\n      name: document.getElementById('smtp-name').value,\n      host: document.getElementById('smtp-host').value,\n      port: parseInt(document.getElementById('smtp-port').value),\n      secureMode: document.getElementById('smtp-secure-mode').value,\n      username: document.getElementById('smtp-username').value,\n      password: document.getElementById('smtp-password').value,\n      fromEmail: document.getElementById('smtp-from-email').value,\n      fromName: document.getElementById('smtp-from-name').value\n    };\n    try {\n      var res = await fetch('/api/admin/smtp-configs/test', {\n        method: 'POST',\n        headers: csrfHeaders({ 'Content-Type': 'application/json' }),\n        body: JSON.stringify(data)\n      });\n      var result = await res.json();\n      if (result.success) {\n        showAuthStatus('Test email sent successfully!', 'success');\n      } else {\n        showAuthStatus('Test failed: ' + (result.error || 'Unknown error'), 'error');\n      }\n    } catch (e) {\n      showAuthStatus('Test failed', 'error');\n    }\n  }\n\n  async function deleteSmtpConfig(id) {\n    if (!confirm('Delete this SMTP configuration?')) return;\n    try {\n      await fetch('/api/admin/smtp-configs/' + id, { method: 'DELETE', headers: csrfHeaders() });\n      loadSmtpConfigs();\n    } catch (e) {\n    }\n  }\n\n  document.getElementById('smtp-modal').addEventListener('click', function(e) {\n    if (e.target.id === 'smtp-modal') closeSmtpModal();\n  });\n\n  var emailTemplates = [];\n  var currentEditTemplate = null;\n  var isHtmlSourceView = false;\n\n  async function loadEmailTemplates() {\n    try {\n      var res = await fetch('/api/admin/email-templates');\n      if (!res.ok) return;\n      var data = await res.json();\n      emailTemplates = data.templates || [];\n      renderEmailTemplates(emailTemplates);\n    } catch (e) {\n    }\n  }\n\n  function renderEmailTemplates(templates) {\n    var container = document.getElementById('email-template-list');\n    if (!templates || templates.length === 0) {\n      setContent(container, h('p', {style: 'color:var(--text-soft);padding:12px'}, 'No email templates. Default templates will be used.'));\n      return;\n    }\n    setContent(container, templates.map(function(t) {\n      return h('div', {style: 'display:flex;justify-content:space-between;align-items:center;padding:12px 0;border-bottom:1px solid var(--border)'},\n        h('div', null,\n          h('div', {style: 'font-weight:500'}, t.name),\n          h('div', {style: 'font-size:12px;color:var(--text-soft)'}, 'Subject: ', t.subject)\n        ),\n        h('button', {className: 'btn btn-sm btn-secondary', 'data-action': 'editTemplate', 'data-id': t.id}, 'Edit')\n      );\n    }));\n  }\n\n  function sanitizeTemplateHtml(html) {\n    var doc = new DOMParser().parseFromString(html, 'text/html');\n    doc.querySelectorAll('script,iframe,object,embed,form').forEach(function(el) { el.remove(); });\n    doc.querySelectorAll('*').forEach(function(el) {\n      Array.from(el.attributes).forEach(function(attr) {\n        if (attr.name.startsWith('on') || (typeof attr.value === 'string' && attr.value.trim().toLowerCase().startsWith('javascript:'))) {\n          el.removeAttribute(attr.name);\n        }\n      });\n      if (el.hasAttribute('href') && el.getAttribute('href').trim().toLowerCase().startsWith('javascript:')) {\n        el.removeAttribute('href');\n      }\n      if (el.hasAttribute('src') && el.getAttribute('src').trim().toLowerCase().startsWith('javascript:')) {\n        el.removeAttribute('src');\n      }\n    });\n    return doc.body.innerHTML;\n  }\n\n  function editTemplate(id) {\n    var t = emailTemplates.find(function(x) { return x.id === id; });\n    if (!t) return;\n    currentEditTemplate = t;\n    document.getElementById('template-edit-id').value = id;\n    document.getElementById('template-name').value = t.name;\n    document.getElementById('template-subject').value = t.subject || '';\n    var safeHtml = sanitizeTemplateHtml(t.bodyHtml || '');\n    document.getElementById('template-editor').innerHTML = safeHtml;\n    document.getElementById('template-html-source').value = t.bodyHtml || '';\n    document.getElementById('template-text').value = t.bodyText || '';\n    isHtmlSourceView = false;\n    showWysiwygView();\n    document.getElementById('template-modal').classList.add('active');\n  }\n\n  function closeTemplateModal() {\n    document.getElementById('template-modal').classList.remove('active');\n    currentEditTemplate = null;\n  }\n\n  function showWysiwygView() {\n    document.getElementById('template-editor').style.display = 'block';\n    document.getElementById('template-editor-toolbar').style.display = 'flex';\n    document.getElementById('template-html-source').style.display = 'none';\n  }\n\n  function showHtmlSourceView() {\n    document.getElementById('template-html-source').value = document.getElementById('template-editor').innerHTML;\n    document.getElementById('template-editor').style.display = 'none';\n    document.getElementById('template-editor-toolbar').style.display = 'none';\n    document.getElementById('template-html-source').style.display = 'block';\n  }\n\n  function toggleTemplateView() {\n    if (isHtmlSourceView) {\n      document.getElementById('template-editor').innerHTML = sanitizeTemplateHtml(document.getElementById('template-html-source').value);\n      showWysiwygView();\n    } else {\n      showHtmlSourceView();\n    }\n    isHtmlSourceView = !isHtmlSourceView;\n  }\n\n  function execCmd(cmd) {\n    document.execCommand(cmd, false, null);\n    document.getElementById('template-editor').focus();\n  }\n\n  function execCmdArg(cmd, arg) {\n    if (!arg) return;\n    document.execCommand(cmd, false, arg);\n    document.getElementById('template-editor').focus();\n  }\n\n  function insertTemplateVar(varName) {\n    var editor = document.getElementById('template-editor');\n    editor.focus();\n    document.execCommand('insertText', false, '{{' + varName + '}}');\n  }\n\n  function insertLink() {\n    var url = prompt('Enter URL:');\n    if (url) {\n      document.execCommand('createLink', false, url);\n    }\n  }\n\n  function insertImage() {\n    var url = prompt('Enter image URL:');\n    if (url) {\n      document.execCommand('insertImage', false, url);\n    }\n  }\n\n  function insertButton() {\n    var url = prompt('Enter button URL:');\n    var text = prompt('Enter button text:', 'Click Here');\n    if (url && text) {\n      var safeUrl = url.replace(/&/g, '&amp;').replace(/\"/g, '&quot;').replace(/</g, '&lt;').replace(/>/g, '&gt;');\n      var safeText = text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');\n      if (/^javascript:/i.test(safeUrl.trim())) return;\n      var btn = '<a href=\"' + safeUrl + '\" style=\"display:inline-block;padding:12px 24px;background:#c9a227;color:#fff;text-decoration:none;border-radius:6px;font-weight:600\">' + safeText + '</a>';\n      document.execCommand('insertHTML', false, btn);\n    }\n  }\n\n  async function saveTemplate() {\n    var id = document.getElementById('template-edit-id').value;\n    if (!id) {\n      showAuthStatus('No template selected', 'error');\n      return;\n    }\n    var html = isHtmlSourceView\n      ? document.getElementById('template-html-source').value\n      : document.getElementById('template-editor').innerHTML;\n    var data = {\n      subject: document.getElementById('template-subject').value,\n      bodyHtml: html,\n      bodyText: document.getElementById('template-text').value\n    };\n    try {\n      var res = await fetch('/api/admin/email-templates/' + id, {\n        method: 'PUT',\n        headers: csrfHeaders({ 'Content-Type': 'application/json' }),\n        body: JSON.stringify(data)\n      });\n      var result = await res.json();\n      if (result.success) {\n        closeTemplateModal();\n        loadEmailTemplates();\n        showAuthStatus('Template saved', 'success');\n      } else {\n        showAuthStatus(result.error || 'Failed to save template', 'error');\n      }\n    } catch (e) {\n      showAuthStatus('Failed to save template', 'error');\n    }\n  }\n\n  function previewTemplate() {\n    var subject = document.getElementById('template-subject').value\n      .replace(/\\{\\{displayName\\}\\}/g, 'John Doe')\n      .replace(/\\{\\{actionUrl\\}\\}/g, 'https://example.com/action')\n      .replace(/\\{\\{appName\\}\\}/g, 'TheOneFile_Verse');\n    var html = (isHtmlSourceView\n      ? document.getElementById('template-html-source').value\n      : document.getElementById('template-editor').innerHTML)\n      .replace(/\\{\\{displayName\\}\\}/g, 'John Doe')\n      .replace(/\\{\\{actionUrl\\}\\}/g, 'https://example.com/action')\n      .replace(/\\{\\{appName\\}\\}/g, 'TheOneFile_Verse');\n    document.getElementById('preview-subject').textContent = subject;\n    var frame = document.getElementById('preview-frame');\n    frame.sandbox = '';\n    frame.srcdoc = '<!DOCTYPE html><html><head><style>body{font-family:system-ui,sans-serif;padding:20px;line-height:1.6;color:#333}</style></head><body>' + html + '</body></html>';\n    document.getElementById('template-preview-modal').classList.add('active');\n  }\n\n  function closeTemplatePreview() {\n    document.getElementById('template-preview-modal').classList.remove('active');\n  }\n\n  document.getElementById('template-modal').addEventListener('click', function(e) {\n    if (e.target.id === 'template-modal') closeTemplateModal();\n  });\n  document.getElementById('template-preview-modal').addEventListener('click', function(e) {\n    if (e.target.id === 'template-preview-modal') closeTemplatePreview();\n  });\n\n  async function loadEmailLogs() {\n    try {\n      var email = document.getElementById('email-log-search').value;\n      var url = email\n        ? '/api/admin/email-logs?email=' + encodeURIComponent(email)\n        : '/api/admin/email-logs';\n      var res = await fetch(url);\n      if (!res.ok) return;\n      var data = await res.json();\n      emailLogs = data.logs || [];\n      renderEmailLogs();\n    } catch (e) {\n    }\n  }\n\n  function renderEmailLogs() {\n    var container = document.getElementById('email-log-list');\n    if (!emailLogs || emailLogs.length === 0) {\n      setContent(container, h('p', {style: 'color:var(--text-soft);padding:12px'}, 'No email logs'));\n      return;\n    }\n    setContent(container, emailLogs.map(function(l) {\n      return h('div', {style: 'padding:8px 0;border-bottom:1px solid var(--border);font-size:13px'},\n        h('span', {style: 'color:var(--text-soft)'}, new Date(l.sentAt).toLocaleString()), ' ',\n        h('span', {className: 'badge badge-' + (l.status === 'sent' ? 'green' : 'gray')}, l.status), ' ',\n        l.toEmail, ' ',\n        h('span', {style: 'color:var(--text-soft)'}, l.subject),\n        l.errorMessage ? [' ', h('span', {style: 'color:#ef4444'}, l.errorMessage)] : null\n      );\n    }));\n  }\n\n  async function clearEmailLogs() {\n    if (!confirm('Clear all email logs? This cannot be undone.')) return;\n    try {\n      var res = await fetch('/api/admin/email-logs', { method: 'DELETE', headers: csrfHeaders() });\n      if (res.ok) {\n        loadEmailLogs();\n        showAuthStatus('Email logs cleared', 'success');\n      }\n    } catch (e) {\n    }\n  }\n\n  async function clearActivityLogs() {\n    if (!confirm('Clear all activity logs? This cannot be undone.')) return;\n    try {\n      var res = await fetch('/api/admin/activity-logs', { method: 'DELETE', headers: csrfHeaders() });\n      if (res.ok) {\n        loadActivityLogs();\n        showAuthStatus('Activity logs cleared', 'success');\n      }\n    } catch (e) {\n    }\n  }\n\n  async function clearAuditLogs() {\n    if (!confirm('Clear all audit logs? This cannot be undone.')) return;\n    try {\n      var res = await fetch('/api/admin/audit-logs', { method: 'DELETE', headers: csrfHeaders() });\n      if (res.ok) {\n        loadAuditLogs();\n        showAuthStatus('Audit logs cleared', 'success');\n      }\n    } catch (e) {\n    }\n  }\n\n  document.addEventListener('click', function(e) {\n    var target = e.target.closest('[data-action]');\n    if (!target) return;\n    if (target.getAttribute('data-disabled') === 'true') return;\n    var action = target.dataset.action;\n    var handlers = {\n      toggleTheme: toggleTheme,\n      logout: logout,\n      showTab: function() { showTab(target.dataset.tab); },\n      deleteSelected: deleteSelected,\n      clearSelection: clearSelection,\n      viewRoom: function() { viewRoom(target.dataset.id); },\n      joinRoom: function() { joinRoom(target.dataset.id); },\n      deleteRoom: function() { deleteRoom(target.dataset.id); },\n      toggleSetting: function() { toggleSetting(target.dataset.setting); },\n      toggleAuthSetting: function() { toggleAuthSetting(target.dataset.setting); },\n      showAddOidcProvider: showAddOidcProvider,\n      showAddSmtpConfig: showAddSmtpConfig,\n      editOidcProvider: function() { editOidcProvider(target.dataset.id); },\n      deleteOidcProvider: function() { deleteOidcProvider(target.dataset.id); },\n      editSmtpConfig: function() { editSmtpConfig(target.dataset.id); },\n      deleteSmtpConfig: function() { deleteSmtpConfig(target.dataset.id); },\n      saveOidcProvider: saveOidcProvider,\n      saveSmtpConfig: saveSmtpConfig,\n      testSmtpConfig: testSmtpConfig,\n      closeModal: closeModal,\n      closeApiKeyModal: closeApiKeyModal,\n      closeUserModal: closeUserModal,\n      closeEditUserModal: closeEditUserModal,\n      closeOidcModal: closeOidcModal,\n      closeSmtpModal: closeSmtpModal,\n      closeTemplateModal: closeTemplateModal,\n      closeTemplatePreview: closeTemplatePreview,\n      showCreateApiKey: showCreateApiKey,\n      createApiKey: createApiKey,\n      showCreateUser: showCreateUser,\n      createUser: createUser,\n      editUser: function() { editUser(target.dataset.id); },\n      deleteUser: function() { deleteUser(target.dataset.id); },\n      saveUserEdit: saveUserEdit,\n      resetUserPassword: resetUserPassword,\n      revokeApiKey: function() { revokeApiKey(target.dataset.id); },\n      setInstancePassword: setInstancePassword,\n      saveAdminPath: saveAdminPath,\n      saveUpdateInterval: saveUpdateInterval,\n      triggerUpdate: triggerUpdate,\n      checkForUpdates: checkForUpdates,\n      saveForcedTheme: saveForcedTheme,\n      saveRoomDefaults: saveRoomDefaults,\n      saveRateLimitSettings: saveRateLimitSettings,\n      saveDiscoveryPrefix: saveDiscoveryPrefix,\n      saveWebhookUrl: saveWebhookUrl,\n      saveBackupSettings: saveBackupSettings,\n      createBackup: createBackup,\n      exportAll: exportAll,\n      downloadBackup: function() { downloadBackup(target.dataset.id); },\n      restoreBackup: function() { restoreBackup(target.dataset.id); },\n      deleteBackup: function() { deleteBackup(target.dataset.id); },\n      editTemplate: function() { editTemplate(target.dataset.id); },\n      saveTemplate: saveTemplate,\n      previewTemplate: previewTemplate,\n      insertTemplateVar: function() { insertTemplateVar(target.dataset.varname); },\n      toggleTemplateView: toggleTemplateView,\n      execCmd: function() { execCmd(target.dataset.cmd); },\n      insertLink: insertLink,\n      insertImage: insertImage,\n      insertButton: insertButton,\n      uploadFileClick: function() { document.getElementById('upload-file').click(); },\n      clearEmailLogs: clearEmailLogs,\n      clearActivityLogs: clearActivityLogs,\n      clearAuditLogs: clearAuditLogs\n    };\n    if (handlers[action]) handlers[action]();\n  });\n\n  document.addEventListener('change', function(e) {\n    var target = e.target.closest('[data-action]');\n    if (!target) return;\n    var action = target.dataset.action;\n    if (action === 'saveAuthSettings') saveAuthSettings();\n    else if (action === 'changeSourceMode') changeSourceMode();\n    else if (action === 'saveForcedTheme') saveForcedTheme();\n    else if (action === 'updateAuthNumber') updateAuthNumber(target.dataset.setting, target.value);\n    else if (action === 'execCmdArg') { execCmdArg(target.dataset.cmd, target.value); target.selectedIndex = 0; }\n    else if (action === 'uploadFile') uploadFile();\n    else if (action === 'toggleAll') toggleAll(target.checked);\n    else if (action === 'toggleSelect') toggleSelect(target.dataset.id, target.checked);\n    else if (action === 'templateColor') execCmdArg('foreColor', target.value);\n  });\n\n  document.addEventListener('keyup', function(e) {\n    var target = e.target.closest('[data-action]');\n    if (!target) return;\n    var action = target.dataset.action;\n    if (action === 'searchRooms') searchRooms(target.value);\n    else if (action === 'searchUsers') searchUsers(target.value);\n    else if (action === 'loadActivityLogs') loadActivityLogs();\n    else if (action === 'loadAuditLogs') loadAuditLogs();\n    else if (action === 'loadEmailLogs') loadEmailLogs();\n  });\n\n  loadData();\n  setInterval(loadData, 10000);\n\n})();\n"
  },
  {
    "path": "theonefile_verse/public/admin-pages.js",
    "content": "(function() {\n  'use strict';\n\n  function withFormLoading(form, fn) {\n    return async function(e) {\n      e.preventDefault();\n      var btn = form.querySelector('button[type=\"submit\"], button:not([type])');\n      if (btn) btn.disabled = true;\n      try { await fn.call(this, e); }\n      finally { if (btn) btn.disabled = false; }\n    };\n  }\n\n  var pageData = JSON.parse(\n    (document.getElementById('page-data') || {}).textContent || '{}'\n  );\n  var page = document.body.dataset.page;\n\n  function build2FAForm(form) {\n    while (form.firstChild) form.removeChild(form.firstChild);\n    var label = document.createElement('label');\n    label.setAttribute('for', '2fa-code');\n    label.textContent = 'Authentication Code';\n    var input = document.createElement('input');\n    input.type = 'text';\n    input.id = '2fa-code';\n    input.placeholder = '000000';\n    input.maxLength = 8;\n    input.autocomplete = 'one-time-code';\n    input.inputMode = 'numeric';\n    input.setAttribute('style', 'text-align:center;font-size:24px;letter-spacing:8px');\n    input.autofocus = true;\n    var btn = document.createElement('button');\n    btn.type = 'submit';\n    btn.textContent = 'Verify';\n    form.appendChild(label);\n    form.appendChild(input);\n    form.appendChild(btn);\n  }\n\n  if (page === 'setup') {\n    window.__authRenderOidcProviders('oidc-buttons', 'divider', '');\n    var setupForm = document.getElementById('setup-form');\n    setupForm.addEventListener('submit', withFormLoading(setupForm, async function() {\n      var error = document.getElementById('error');\n      error.classList.remove('active');\n      var email = document.getElementById('email').value;\n      var pwd = document.getElementById('password').value;\n      var confirmVal = document.getElementById('confirm').value;\n      if (!email || !email.includes('@')) { error.textContent = 'Please enter a valid email'; error.setAttribute('role', 'alert'); error.classList.add('active'); return; }\n      if (pwd.length < 8) { error.textContent = 'Password must be at least 8 characters'; error.setAttribute('role', 'alert'); error.classList.add('active'); return; }\n      if (pwd !== confirmVal) { error.textContent = 'Passwords do not match'; error.setAttribute('role', 'alert'); error.classList.add('active'); return; }\n      try {\n        var res = await fetch('/api/setup', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email: email, password: pwd }) });\n        var d = await res.json();\n        if (res.ok && d.success) { window.location.href = '/' + (pageData.adminPath || 'admin'); }\n        else { error.textContent = d.error || 'Setup failed'; error.setAttribute('role', 'alert'); error.classList.add('active'); }\n      } catch(ex) { error.textContent = 'Connection error'; error.setAttribute('role', 'alert'); error.classList.add('active'); }\n    }));\n  }\n\n  else if (page === 'migration') {\n    var migrateForm = document.getElementById('migrate-form');\n    migrateForm.addEventListener('submit', withFormLoading(migrateForm, async function() {\n      var oldPwd = document.getElementById('old-password');\n      var email = document.getElementById('email');\n      var newPwd = document.getElementById('new-password');\n      var error = document.getElementById('error');\n      error.classList.remove('active');\n      if (!oldPwd.value) { error.textContent = 'Please enter your current admin password'; error.setAttribute('role', 'alert'); error.classList.add('active'); return; }\n      if (!email.value || !email.value.includes('@')) { error.textContent = 'Please enter a valid email'; error.setAttribute('role', 'alert'); error.classList.add('active'); return; }\n      try {\n        var res = await fetch('/api/admin/migrate', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ oldPassword: oldPwd.value, email: email.value, newPassword: newPwd.value || null }) });\n        var d = await res.json();\n        if (res.ok && d.success) { window.location.href = '/' + (pageData.adminPath || 'admin'); }\n        else { error.textContent = d.error || 'Migration failed'; error.setAttribute('role', 'alert'); error.classList.add('active'); }\n      } catch(ex) { error.textContent = 'Connection error'; error.setAttribute('role', 'alert'); error.classList.add('active'); }\n    }));\n  }\n\n  else if (page === 'login') {\n    var loginForm = document.getElementById('login-form');\n    loginForm.addEventListener('submit', withFormLoading(loginForm, async function() {\n      var password = document.getElementById('password').value;\n      try {\n        var res = await fetch('/api/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ password: password }) });\n        if (res.ok) window.location.reload();\n        else { document.getElementById('error').setAttribute('role', 'alert'); document.getElementById('error').classList.add('active'); document.getElementById('password').value = ''; }\n      } catch(ex) { document.getElementById('error').textContent = 'Connection error'; document.getElementById('error').setAttribute('role', 'alert'); document.getElementById('error').classList.add('active'); }\n    }));\n  }\n\n  else if (page === 'instance-login') {\n    var instanceForm = document.getElementById('login-form');\n    instanceForm.addEventListener('submit', withFormLoading(instanceForm, async function() {\n      try {\n        var res = await fetch('/api/instance-login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ password: document.getElementById('password').value }) });\n        if (res.ok) window.location.reload();\n        else { document.getElementById('error').setAttribute('role', 'alert'); document.getElementById('error').classList.add('active'); document.getElementById('password').value = ''; }\n      } catch(ex) { document.getElementById('error').textContent = 'Connection error'; document.getElementById('error').setAttribute('role', 'alert'); document.getElementById('error').classList.add('active'); }\n    }));\n  }\n\n  else if (page === 'user-login') {\n    var csrfToken = '';\n    var pendingToken = null;\n    window.__authCsrfRefresh().then(function() { csrfToken = window.__authCsrfToken; });\n    window.__authRenderOidcProviders('oidc-buttons', 'divider', '');\n    var userLoginForm = document.getElementById('login-form');\n    userLoginForm.addEventListener('submit', withFormLoading(userLoginForm, async function() {\n      var error = document.getElementById('error');\n      error.classList.remove('active');\n      if (pendingToken) {\n        var code = document.getElementById('2fa-code').value.trim();\n        if (!code) { error.textContent = 'Please enter your 2FA code'; error.setAttribute('role', 'alert'); error.classList.add('active'); return; }\n        try {\n          var res = await fetch('/api/auth/2fa/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ pendingToken: pendingToken, code: code }) });\n          var d = await res.json();\n          if (res.ok && d.success) { var redirect = new URLSearchParams(window.location.search).get('redirect') || '/'; if (redirect.startsWith('/') && !redirect.startsWith('//')) { window.location.href = redirect; } else { window.location.href = '/'; } }\n          else { error.textContent = d.error || 'Invalid code'; error.setAttribute('role', 'alert'); error.classList.add('active'); }\n        } catch(ex) { error.textContent = 'Connection error'; error.setAttribute('role', 'alert'); error.classList.add('active'); }\n        return;\n      }\n      var email = document.getElementById('email').value;\n      var password = document.getElementById('password').value;\n      if (!email || !email.includes('@')) { error.textContent = 'Please enter a valid email'; error.setAttribute('role', 'alert'); error.classList.add('active'); return; }\n      if (!password) { error.textContent = 'Please enter your password'; error.setAttribute('role', 'alert'); error.classList.add('active'); return; }\n      try {\n        var res = await fetch('/api/auth/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email: email, password: password, csrfToken: csrfToken }) });\n        var d = await res.json();\n        if (d.requires2FA) {\n          pendingToken = d.pendingToken;\n          build2FAForm(userLoginForm);\n          document.querySelector('h1').textContent = 'Two Factor Authentication';\n          document.querySelector('p').textContent = 'Enter the 6 digit code from your authenticator app';\n          return;\n        }\n        if (res.ok && d.success) { var redirect = new URLSearchParams(window.location.search).get('redirect') || '/'; if (redirect.startsWith('/') && !redirect.startsWith('//')) { window.location.href = redirect; } else { window.location.href = '/'; } }\n        else { error.textContent = d.error || 'Invalid credentials'; error.setAttribute('role', 'alert'); error.classList.add('active'); document.getElementById('password').value = ''; fetch('/api/auth/csrf').then(function(r) { return r.json(); }).then(function(c) { csrfToken = c.token; }).catch(function() {}); }\n      } catch(ex) { error.textContent = 'Connection error'; error.setAttribute('role', 'alert'); error.classList.add('active'); }\n    }));\n  }\n\n  else if (page === 'user-register') {\n    var csrfToken = '';\n    window.__authCsrfRefresh().then(function() { csrfToken = window.__authCsrfToken; });\n    window.__authRenderOidcProviders('oidc-buttons', 'divider', '');\n\n    function checkPasswordStrength(pwd) {\n      if (!pwd) return { strength: '', text: '' };\n      var score = 0;\n      if (pwd.length >= 8) score++;\n      if (pwd.length >= 12) score++;\n      if (/[a-z]/.test(pwd) && /[A-Z]/.test(pwd)) score++;\n      if (/[0-9]/.test(pwd)) score++;\n      if (/[^a-zA-Z0-9]/.test(pwd)) score++;\n      if (score <= 2) return { strength: 'weak', text: 'Weak: Add more characters, numbers, or symbols' };\n      if (score <= 3) return { strength: 'medium', text: 'Medium: Getting better, add more variety' };\n      return { strength: 'strong', text: 'Strong password' };\n    }\n\n    document.getElementById('password').addEventListener('input', function(e) {\n      var result = checkPasswordStrength(e.target.value);\n      var bar = document.getElementById('strength-bar');\n      var txt = document.getElementById('strength-text');\n      bar.className = 'password-strength-bar' + (result.strength ? ' ' + result.strength : '');\n      txt.className = 'password-strength-text' + (result.strength ? ' ' + result.strength : '');\n      txt.textContent = result.text;\n    });\n\n    var registerForm = document.getElementById('register-form');\n    registerForm.addEventListener('submit', withFormLoading(registerForm, async function() {\n      var error = document.getElementById('error');\n      var success = document.getElementById('success');\n      error.classList.remove('active'); success.classList.remove('active');\n      var email = document.getElementById('email').value;\n      var displayName = document.getElementById('displayName').value;\n      var password = document.getElementById('password').value;\n      var confirmPassword = document.getElementById('confirmPassword').value;\n      if (!email || !email.includes('@')) { error.textContent = 'Please enter a valid email'; error.setAttribute('role', 'alert'); error.classList.add('active'); return; }\n      if (!password) { error.textContent = 'Please enter a password'; error.setAttribute('role', 'alert'); error.classList.add('active'); return; }\n      if (password !== confirmPassword) { error.textContent = 'Passwords do not match'; error.setAttribute('role', 'alert'); error.classList.add('active'); return; }\n      try {\n        var res = await fetch('/api/auth/register', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email: email, password: password, displayName: displayName || null, csrfToken: csrfToken }) });\n        var d = await res.json();\n        if (res.ok) {\n          if (d.requiresVerification) { success.textContent = 'Account created! Please check your email to verify your account.'; success.classList.add('active'); registerForm.reset(); }\n          else { window.location.href = '/'; }\n        } else { error.textContent = d.error || 'Registration failed'; error.setAttribute('role', 'alert'); error.classList.add('active'); fetch('/api/auth/csrf').then(function(r) { return r.json(); }).then(function(c) { csrfToken = c.token; }).catch(function() {}); }\n      } catch(ex) { error.textContent = 'Connection error'; error.setAttribute('role', 'alert'); error.classList.add('active'); }\n    }));\n  }\n\n  else if (page === 'user-forgot-password') {\n    var forgotForm = document.getElementById('forgot-form');\n    forgotForm.addEventListener('submit', withFormLoading(forgotForm, async function() {\n      var error = document.getElementById('error');\n      var success = document.getElementById('success');\n      error.classList.remove('active'); success.classList.remove('active');\n      var email = document.getElementById('email').value;\n      if (!email || !email.includes('@')) { error.textContent = 'Please enter a valid email'; error.setAttribute('role', 'alert'); error.classList.add('active'); return; }\n      try {\n        var res = await fetch('/api/auth/forgot-password', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email: email }) });\n        var d = await res.json();\n        if (res.ok) { success.textContent = 'If an account exists with this email, a reset link has been sent.'; success.classList.add('active'); document.getElementById('email').value = ''; }\n        else { error.textContent = d.error || 'Failed to send reset email'; error.setAttribute('role', 'alert'); error.classList.add('active'); }\n      } catch(ex) { error.textContent = 'Connection error'; error.setAttribute('role', 'alert'); error.classList.add('active'); }\n    }));\n  }\n\n  else if (page === 'admin-login') {\n    var adminPath = pageData.adminPath || 'admin';\n    window.__authRenderOidcProviders('oidc-buttons', 'divider', '?redirect=/' + adminPath);\n    var pendingToken = null;\n    var adminLoginForm = document.getElementById('login-form');\n    adminLoginForm.addEventListener('submit', withFormLoading(adminLoginForm, async function() {\n      var error = document.getElementById('error');\n      error.classList.remove('active');\n      if (pendingToken) {\n        var code = document.getElementById('2fa-code').value.trim();\n        if (!code) { error.textContent = 'Please enter your 2FA code'; error.setAttribute('role', 'alert'); error.classList.add('active'); return; }\n        try {\n          var res = await fetch('/api/auth/2fa/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ pendingToken: pendingToken, code: code }) });\n          var d = await res.json();\n          if (res.ok && d.success) { window.location.href = '/' + adminPath; }\n          else { error.textContent = d.error || 'Invalid code'; error.setAttribute('role', 'alert'); error.classList.add('active'); }\n        } catch(ex) { error.textContent = 'Connection error'; error.setAttribute('role', 'alert'); error.classList.add('active'); }\n        return;\n      }\n      var email = document.getElementById('email').value;\n      var password = document.getElementById('password').value;\n      if (!email || !email.includes('@')) { error.textContent = 'Please enter a valid email'; error.setAttribute('role', 'alert'); error.classList.add('active'); return; }\n      if (!password) { error.textContent = 'Please enter your password'; error.setAttribute('role', 'alert'); error.classList.add('active'); return; }\n      try {\n        var res = await fetch('/api/admin/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email: email, password: password }) });\n        var d = await res.json();\n        if (d.requires2FA) {\n          pendingToken = d.pendingToken;\n          build2FAForm(adminLoginForm);\n          document.querySelector('h1').textContent = 'Two Factor Authentication';\n          document.querySelector('p').textContent = 'Enter the 6 digit code from your authenticator app';\n          return;\n        }\n        if (res.ok && d.success) { window.location.href = '/' + adminPath; }\n        else { error.textContent = d.error || 'Invalid credentials'; error.setAttribute('role', 'alert'); error.classList.add('active'); document.getElementById('password').value = ''; }\n      } catch(ex) { error.textContent = 'Connection error'; error.setAttribute('role', 'alert'); error.classList.add('active'); }\n    }));\n  }\n\n  else if (page === 'password-reset') {\n    var resetForm = document.getElementById('form');\n    resetForm.addEventListener('submit', withFormLoading(resetForm, async function() {\n      var pw = document.getElementById('password').value;\n      var confirmVal = document.getElementById('confirm').value;\n      var err = document.getElementById('error');\n      err.classList.remove('active');\n      if (pw !== confirmVal) { err.textContent = 'Passwords do not match'; err.setAttribute('role', 'alert'); err.classList.add('active'); return; }\n      if (pw.length < 8) { err.textContent = 'Password must be at least 8 characters'; err.setAttribute('role', 'alert'); err.classList.add('active'); return; }\n      try {\n        var csrfRes = await fetch('/api/auth/csrf');\n        var csrfData = await csrfRes.json();\n        var res = await fetch('/api/auth/reset-password', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ token: pageData.token, password: pw, csrfToken: csrfData.token }) });\n        var data = await res.json();\n        if (data.success) { resetForm.style.display = 'none'; document.getElementById('success').classList.add('active'); }\n        else { err.textContent = data.error || 'Failed to reset password'; err.setAttribute('role', 'alert'); err.classList.add('active'); }\n      } catch(ex) { err.textContent = 'Connection error'; err.setAttribute('role', 'alert'); err.classList.add('active'); }\n    }));\n  }\n})();\n"
  },
  {
    "path": "theonefile_verse/public/collab-init.js",
    "content": "(function(){\n  window.COLLAB_MODE = true;\n\n  var origGetItem = localStorage.getItem.bind(localStorage);\n  var origSetItem = localStorage.setItem.bind(localStorage);\n  var origRemoveItem = localStorage.removeItem.bind(localStorage);\n  var blockedKeys = ['topology', 'autosave', 'savedState', 'nodeData', 'edgeData', 'canvasState', 'lastState', 'PAGE_STATE', 'theonefile'];\n\n  function isBlockedKey(key) {\n    if (!key) return false;\n    var lk = key.toLowerCase();\n    for (var i = 0; i < blockedKeys.length; i++) {\n      if (lk.indexOf(blockedKeys[i].toLowerCase()) !== -1) return true;\n    }\n    return false;\n  }\n\n  localStorage.getItem = function(key) {\n    if (window.COLLAB_MODE && isBlockedKey(key)) {\n      return null;\n    }\n    return origGetItem(key);\n  };\n\n  localStorage.setItem = function(key, value) {\n    if (window.COLLAB_MODE && isBlockedKey(key)) {\n      return;\n    }\n    return origSetItem(key, value);\n  };\n\n  localStorage.removeItem = function(key) {\n    if (window.COLLAB_MODE && isBlockedKey(key)) return;\n    return origRemoveItem(key);\n  };\n\n  var origOpen = indexedDB.open.bind(indexedDB);\n  indexedDB.open = function(name) {\n    if (window.COLLAB_MODE && name && name.toLowerCase().indexOf('theonefile') !== -1) {\n      var fakeRequest = {\n        result: null,\n        error: null,\n        onsuccess: null,\n        onerror: null,\n        onupgradeneeded: null,\n        onblocked: null,\n        readyState: 'done',\n        transaction: null,\n        source: null\n      };\n      setTimeout(function() {\n        if (fakeRequest.onerror) fakeRequest.onerror(new Event('error'));\n      }, 0);\n      return fakeRequest;\n    }\n    return origOpen.apply(indexedDB, arguments);\n  };\n\n  window.__collabSuppressWelcome = true;\n  new MutationObserver(function(mutations) {\n    if (!window.__collabSuppressWelcome) return;\n    for (var i = 0; i < mutations.length; i++) {\n      var el = mutations[i].target;\n      if (el.id === 'welcome-modal' && el.classList.contains('active')) {\n        el.classList.remove('active');\n      }\n    }\n  }).observe(document.documentElement, { subtree: true, attributes: true, attributeFilter: ['class'] });\n})();\n\n(function(){\n  var checkInterval = setInterval(function(){\n    if(typeof NODE_DATA !== 'undefined'){\n      clearInterval(checkInterval);\n      window.__collabGetVar = function(name) {\n        try {\n          switch(name) {\n            case 'NODE_DATA': return NODE_DATA;\n            case 'EDGE_DATA': return EDGE_DATA;\n            case 'RECT_DATA': return RECT_DATA;\n            case 'TEXT_DATA': return TEXT_DATA;\n            case 'EDGE_LEGEND': return EDGE_LEGEND;\n            case 'ZONE_LEGEND': return typeof ZONE_LEGEND !== 'undefined' ? ZONE_LEGEND : {};\n            case 'ZONE_PRESETS': return typeof ZONE_PRESETS !== 'undefined' ? ZONE_PRESETS : {};\n            case 'PAGE_STATE': return PAGE_STATE;\n            case 'savedPositions': return savedPositions;\n            case 'savedSizes': return savedSizes;\n            case 'savedStyles': return savedStyles;\n            case 'savedStyleSets': return typeof savedStyleSets !== 'undefined' ? savedStyleSets : {};\n            case 'canvasState': return canvasState;\n            case 'documentTabs': return documentTabs;\n            case 'currentTabIndex': return currentTabIndex;\n            case 'auditLog': return auditLog;\n            case 'autoPingEnabled': return typeof autoPingEnabled !== 'undefined' ? autoPingEnabled : false;\n            case 'autoPingInterval': return typeof autoPingInterval !== 'undefined' ? autoPingInterval : 5000;\n            case 'savedTopologyView': return typeof savedTopologyView !== 'undefined' ? savedTopologyView : null;\n            case 'encryptedSections': return typeof encryptedSections !== 'undefined' ? encryptedSections : {};\n            case 'iconCache': return typeof IconLibrary !== 'undefined' ? IconLibrary.iconCache : {};\n            case 'ANIM_SETTINGS': return typeof ANIM_SETTINGS !== 'undefined' ? ANIM_SETTINGS : null;\n            case 'rollbackVersions': return typeof rollbackVersions !== 'undefined' ? rollbackVersions : [];\n            case 'CUSTOM_LANG': return typeof CUSTOM_LANG !== 'undefined' ? CUSTOM_LANG : null;\n            case 'IMAGE_DATA': return typeof IMAGE_DATA !== 'undefined' ? IMAGE_DATA : { list: [] };\n            case 'checkNodeStatus': return typeof checkNodeStatus !== 'undefined' ? checkNodeStatus : undefined;\n            case 'checkAllNodesStatus': return typeof checkAllNodesStatus !== 'undefined' ? checkAllNodesStatus : undefined;\n            case 'updatePingIndicator': return typeof updatePingIndicator !== 'undefined' ? updatePingIndicator : undefined;\n            case 'updatePingStatusDisplay': return typeof updatePingStatusDisplay !== 'undefined' ? updatePingStatusDisplay : undefined;\n            case 'forgeTheTopology': return typeof forgeTheTopology !== 'undefined' ? forgeTheTopology : undefined;\n            case 'currentNodeId': return typeof currentNodeId !== 'undefined' ? currentNodeId : undefined;\n            case 'pushUndo': return typeof pushUndo !== 'undefined' ? pushUndo : undefined;\n            default: return undefined;\n          }\n        } catch(e) { return undefined; }\n      };\n      window.__collabSetVar = function(name, value) {\n        try {\n          switch(name) {\n            case 'NODE_DATA': NODE_DATA = value; return true;\n            case 'EDGE_DATA': EDGE_DATA = value; return true;\n            case 'RECT_DATA': RECT_DATA = value; return true;\n            case 'TEXT_DATA': TEXT_DATA = value; return true;\n            case 'EDGE_LEGEND': EDGE_LEGEND = value; return true;\n            case 'ZONE_LEGEND': if(typeof ZONE_LEGEND !== 'undefined') ZONE_LEGEND = value; return true;\n            case 'ZONE_PRESETS': if(typeof ZONE_PRESETS !== 'undefined') ZONE_PRESETS = value; return true;\n            case 'savedPositions': savedPositions = value; return true;\n            case 'savedSizes': savedSizes = value; return true;\n            case 'savedStyles': savedStyles = value; return true;\n            case 'savedStyleSets': if(typeof savedStyleSets !== 'undefined') savedStyleSets = value; return true;\n            case 'auditLog': auditLog = value; return true;\n            case 'documentTabs': documentTabs = value; return true;\n            case 'currentTabIndex': currentTabIndex = value; return true;\n            case 'autoPingEnabled': if(typeof autoPingEnabled !== 'undefined') autoPingEnabled = value; return true;\n            case 'autoPingInterval': if(typeof autoPingInterval !== 'undefined') autoPingInterval = value; return true;\n            case 'savedTopologyView': if(typeof savedTopologyView !== 'undefined') savedTopologyView = value; return true;\n            case 'encryptedSections': if(typeof encryptedSections !== 'undefined') encryptedSections = value; return true;\n            case 'iconCache': if(typeof IconLibrary !== 'undefined') IconLibrary.iconCache = value; return true;\n            case 'ANIM_SETTINGS': if(typeof ANIM_SETTINGS !== 'undefined') { Object.assign(ANIM_SETTINGS, value); return true; } return false;\n            case 'rollbackVersions': if(typeof rollbackVersions !== 'undefined') { rollbackVersions = value; return true; } return false;\n            case 'CUSTOM_LANG': CUSTOM_LANG = value; if(typeof LANG !== 'undefined' && typeof DEFAULT_LANG !== 'undefined' && value) { LANG = deepMerge(DEFAULT_LANG, value); } return true;\n            case 'PAGE_STATE': if(typeof PAGE_STATE !== 'undefined') { Object.assign(PAGE_STATE, value); return true; } return false;\n            case 'IMAGE_DATA': if(typeof IMAGE_DATA !== 'undefined') { IMAGE_DATA = value; if(typeof renderCanvasImages === 'function') renderCanvasImages(); return true; } return false;\n            default: return false;\n          }\n        } catch(e) { return false; }\n      };\n    }\n  }, 50);\n})();\n"
  },
  {
    "path": "theonefile_verse/public/collab-save-hook.js",
    "content": "(function(){\n  var pendingHtmlBlobs = new Map();\n  var origCreateObjectURL = URL.createObjectURL;\n  var origRevokeObjectURL = URL.revokeObjectURL;\n\n  URL.createObjectURL = function(blob) {\n    var url = origCreateObjectURL.apply(URL, arguments);\n    if (blob && blob.type && blob.type.indexOf('text/html') !== -1) {\n      pendingHtmlBlobs.set(url, blob);\n    }\n    return url;\n  };\n\n  URL.revokeObjectURL = function(url) {\n    pendingHtmlBlobs.delete(url);\n    return origRevokeObjectURL.apply(URL, arguments);\n  };\n\n  document.addEventListener('click', function(e) {\n    var anchor = e.target;\n    if (!anchor.download) {\n      anchor = e.target.closest ? e.target.closest('a[download]') : null;\n    }\n    if (!anchor || !anchor.download || !anchor.href) return;\n    if (!anchor.download.endsWith('.html')) return;\n    if (!anchor.href.startsWith('blob:')) return;\n    if (typeof window.__collabStripHTML !== 'function') return;\n\n    var blob = pendingHtmlBlobs.get(anchor.href);\n    if (!blob) return;\n\n    e.preventDefault();\n    e.stopPropagation();\n    e.stopImmediatePropagation();\n\n    var reader = new FileReader();\n    reader.onload = function() {\n      var cleanHtml = window.__collabStripHTML(reader.result);\n      var cleanBlob = new Blob([cleanHtml], {type: 'text/html'});\n      var cleanUrl = origCreateObjectURL.call(URL, cleanBlob);\n      var a = document.createElement('a');\n      a.href = cleanUrl;\n      a.download = anchor.download;\n      a.style.display = 'none';\n      document.body.appendChild(a);\n      a.click();\n      setTimeout(function() {\n        document.body.removeChild(a);\n        origRevokeObjectURL.call(URL, cleanUrl);\n      }, 100);\n    };\n    reader.readAsText(blob);\n  }, true);\n})();\n"
  },
  {
    "path": "theonefile_verse/public/collab.css",
    "content": "#collab-bar {\n  position: fixed;\n  top: 0;\n  left: 0;\n  right: 0;\n  top: env(safe-area-inset-top, 0);\n  height: 48px;\n  background: linear-gradient(to bottom, #0f1419 0%, #0a0e14 100%);\n  border-bottom: 1px solid #1e2530;\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  padding: 0 max(16px, env(safe-area-inset-left, 16px));\n  padding-right: max(16px, env(safe-area-inset-right, 16px));\n  z-index: 99999;\n  font-family: Inter, system-ui, -apple-system, sans-serif;\n  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);\n}\n\n#collab-bar * { box-sizing: border-box; }\n\n.collab-users {\n  display: flex;\n  align-items: center;\n  gap: 8px;\n  flex: 1;\n  overflow-x: auto;\n  padding: 4px 0;\n  -webkit-overflow-scrolling: touch;\n  scrollbar-width: none;\n}\n\n.collab-users::-webkit-scrollbar { display: none; }\n\n.collab-user {\n  display: flex;\n  align-items: center;\n  gap: 6px;\n  padding: 6px 12px;\n  background: rgba(255, 255, 255, 0.05);\n  border-radius: 20px;\n  font-size: 13px;\n  color: #e2e8f0;\n  white-space: nowrap;\n  border: 1px solid transparent;\n}\n\n.collab-user.me {\n  background: rgba(59, 130, 246, 0.15);\n  border-color: rgba(59, 130, 246, 0.3);\n}\n\n.collab-user-dot {\n  width: 10px;\n  height: 10px;\n  border-radius: 50%;\n  flex-shrink: 0;\n}\n\n.collab-user-avatar {\n  width: 24px;\n  height: 24px;\n  border-radius: 50%;\n  flex-shrink: 0;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  font-size: 11px;\n  font-weight: 700;\n  color: white;\n  text-transform: uppercase;\n}\n\n.collab-user-name {\n  max-width: 150px;\n  overflow: hidden;\n  text-overflow: ellipsis;\n}\n\n.collab-user-editing {\n  font-size: 11px;\n  color: #22c55e;\n  margin-left: 4px;\n  padding: 2px 6px;\n  background: rgba(34, 197, 94, 0.15);\n  border-radius: 8px;\n}\n\n.collab-actions {\n  display: flex;\n  align-items: center;\n  gap: 8px;\n}\n\n.collab-btn {\n  padding: 8px 16px;\n  min-height: 44px;\n  min-width: 44px;\n  background: #1e2530;\n  border: 1px solid #2d3748;\n  border-radius: 8px;\n  color: #e2e8f0;\n  font-size: 13px;\n  font-weight: 500;\n  cursor: pointer;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  gap: 6px;\n  transition: all 0.15s;\n  -webkit-tap-highlight-color: transparent;\n}\n\n.collab-btn:hover {\n  background: #2d3748;\n  border-color: #3b82f6;\n}\n\n.collab-btn:active {\n  transform: scale(0.96);\n}\n\n.collab-btn-icon { font-size: 16px; }\n\n.collab-modal-overlay {\n  display: none;\n  position: fixed;\n  inset: 0;\n  background: rgba(0, 0, 0, 0.85);\n  z-index: 999999;\n  align-items: center;\n  justify-content: center;\n  padding: 20px;\n  padding-bottom: max(20px, env(safe-area-inset-bottom, 20px));\n}\n\n.collab-modal-overlay.active { display: flex; }\n#collab-name-modal.collab-modal-overlay { z-index: 99999999; }\n\n.collab-modal {\n  background: #0f1419;\n  border: 1px solid #1e2530;\n  border-radius: 16px;\n  width: 100%;\n  max-width: 420px;\n  font-family: Inter, system-ui, sans-serif;\n  animation: collabModalIn 0.2s ease;\n}\n\n@keyframes collabModalIn {\n  from { opacity: 0; transform: scale(0.95); }\n  to { opacity: 1; transform: scale(1); }\n}\n\n.collab-modal-header {\n  display: flex;\n  justify-content: space-between;\n  align-items: center;\n  padding: 16px 20px;\n  border-bottom: 1px solid #1e2530;\n}\n\n.collab-modal-header h3 {\n  font-size: 18px;\n  font-weight: 600;\n  color: #e2e8f0;\n  margin: 0;\n}\n\n.collab-modal-close {\n  background: none;\n  border: none;\n  color: #8892a0;\n  font-size: 24px;\n  cursor: pointer;\n  padding: 4px;\n  line-height: 1;\n  min-width: 44px;\n  min-height: 44px;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  -webkit-tap-highlight-color: transparent;\n}\n\n.collab-modal-close:hover { color: #e2e8f0; }\n\n.collab-modal-body { padding: 20px; }\n\n.collab-share-url {\n  display: flex;\n  gap: 8px;\n  margin-bottom: 20px;\n}\n\n.collab-share-url input {\n  flex: 1;\n  padding: 12px;\n  background: #0a0e14;\n  border: 1px solid #1e2530;\n  border-radius: 8px;\n  color: #e2e8f0;\n  font-size: 13px;\n  font-family: monospace;\n}\n\n.collab-share-url button {\n  padding: 12px 16px;\n  min-height: 44px;\n  background: #3b82f6;\n  border: none;\n  border-radius: 8px;\n  color: white;\n  font-size: 18px;\n  cursor: pointer;\n  -webkit-tap-highlight-color: transparent;\n}\n\n.collab-share-url button:hover { background: #2563eb; }\n\n.collab-qr {\n  display: flex;\n  justify-content: center;\n  padding: 20px;\n  background: white;\n  border-radius: 12px;\n  margin-bottom: 16px;\n}\n\n.collab-qr svg { max-width: 180px; max-height: 180px; }\n\n.collab-share-note {\n  font-size: 13px;\n  color: #8892a0;\n  text-align: center;\n  margin: 0;\n}\n\n#collab-menu-dropdown {\n  display: none;\n  position: fixed;\n  background: #0f1419;\n  border: 1px solid #1e2530;\n  border-radius: 12px;\n  min-width: 180px;\n  z-index: 999999;\n  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);\n  overflow: hidden;\n}\n\n#collab-menu-dropdown.active { display: block; }\n\n.collab-menu-item {\n  display: flex;\n  align-items: center;\n  gap: 10px;\n  padding: 12px 16px;\n  min-height: 44px;\n  color: #e2e8f0;\n  font-size: 14px;\n  cursor: pointer;\n  border: none;\n  background: none;\n  width: 100%;\n  text-align: left;\n  font-family: inherit;\n  -webkit-tap-highlight-color: transparent;\n}\n\n.collab-menu-item:hover { background: #1e2530; }\n.collab-menu-item.danger { color: #ef4444; }\n.collab-menu-item.danger:hover { background: rgba(239, 68, 68, 0.1); }\n.collab-menu-divider { height: 1px; background: #1e2530; margin: 4px 0; }\n\n.collab-info-row {\n  display: flex;\n  justify-content: space-between;\n  padding: 12px 0;\n  border-bottom: 1px solid #1e2530;\n}\n\n.collab-info-row:last-child { border-bottom: none; }\n.collab-info-label { color: #8892a0; font-size: 14px; }\n.collab-info-value { color: #e2e8f0; font-size: 14px; font-weight: 500; }\n.collab-info-id { font-family: monospace; font-size: 11px; word-break: break-all; }\n\n.collab-input {\n  width: 100%;\n  padding: 14px 16px;\n  background: #0a0e14;\n  border: 1px solid #1e2530;\n  border-radius: 8px;\n  color: #e2e8f0;\n  font-size: 16px;\n  outline: none;\n  font-family: inherit;\n}\n\n.collab-input:focus { border-color: #3b82f6; }\n.collab-input::placeholder { color: #4a5568; }\n\n.collab-name-actions {\n  display: flex;\n  gap: 12px;\n  margin-top: 16px;\n}\n\n.collab-btn-secondary {\n  flex: 1;\n  padding: 12px;\n  min-height: 44px;\n  background: #1e2530;\n  border: 1px solid #2d3748;\n  border-radius: 8px;\n  color: #e2e8f0;\n  font-size: 14px;\n  font-weight: 500;\n  cursor: pointer;\n  font-family: inherit;\n  -webkit-tap-highlight-color: transparent;\n}\n\n.collab-btn-secondary:hover { background: #2d3748; }\n\n.collab-btn-primary {\n  flex: 1;\n  padding: 12px;\n  min-height: 44px;\n  background: #3b82f6;\n  border: none;\n  border-radius: 8px;\n  color: white;\n  font-size: 14px;\n  font-weight: 600;\n  cursor: pointer;\n  font-family: inherit;\n  -webkit-tap-highlight-color: transparent;\n}\n\n.collab-btn-primary:hover { background: #2563eb; }\n\n.collab-pwd-error,\n.collab-name-error {\n  color: #ef4444;\n  font-size: 13px;\n  margin-top: 8px;\n  display: none;\n}\n\n.collab-pwd-error.active,\n.collab-name-error.active { display: block; }\n\n.collab-node-indicator {\n  position: absolute;\n  bottom: -20px;\n  left: 50%;\n  transform: translateX(-50%);\n  padding: 2px 8px;\n  border-radius: 10px;\n  font-size: 10px;\n  font-weight: 500;\n  white-space: nowrap;\n  pointer-events: none;\n  z-index: 1000;\n  font-family: Inter, system-ui, sans-serif;\n}\n\n.collab-selection-ring {\n  position: absolute;\n  inset: -4px;\n  border: 3px solid;\n  border-radius: inherit;\n  pointer-events: none;\n  animation: collab-pulse 2s infinite;\n}\n\n@keyframes collab-pulse {\n  0%, 100% { opacity: 1; }\n  50% { opacity: 0.5; }\n}\n\nbody.collab-active {\n  padding-top: calc(48px + env(safe-area-inset-top, 0px)) !important;\n}\n\n@media (max-width: 640px) {\n  #collab-bar { padding: 0 12px; }\n  .collab-user { padding: 4px 8px; font-size: 12px; }\n  .collab-user-name { max-width: 80px; }\n  .collab-user-editing { display: none; }\n  .collab-btn { padding: 8px 12px; font-size: 12px; min-height: 44px; }\n  .collab-btn span:not(.collab-btn-icon) { display: none; }\n}\n\n#collab-sync-overlay {\n  position: fixed;\n  inset: 0;\n  background: rgba(10, 14, 20, 0.95);\n  z-index: 9999999;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  font-family: Inter, system-ui, -apple-system, sans-serif;\n  animation: syncFadeIn 0.3s ease;\n}\n\n#collab-sync-overlay.fade-out {\n  animation: syncFadeOut 0.3s ease forwards;\n}\n\n@keyframes syncFadeIn {\n  from { opacity: 0; }\n  to { opacity: 1; }\n}\n\n@keyframes syncFadeOut {\n  from { opacity: 1; }\n  to { opacity: 0; }\n}\n\n.collab-sync-content {\n  text-align: center;\n  position: relative;\n}\n\n.collab-sync-sword {\n  width: 80px;\n  height: 200px;\n  margin: 0 auto 30px;\n  position: relative;\n  animation: swordFloat 2s ease-in-out infinite;\n}\n\n.collab-sync-sword::before {\n  content: '';\n  position: absolute;\n  left: 50%;\n  top: 0;\n  transform: translateX(-50%);\n  width: 8px;\n  height: 140px;\n  background: linear-gradient(to bottom, #e2e8f0 0%, #94a3b8 50%, #64748b 100%);\n  border-radius: 2px 2px 0 0;\n  box-shadow: 0 0 20px rgba(226, 232, 240, 0.5);\n}\n\n.collab-sync-sword::after {\n  content: '';\n  position: absolute;\n  left: 50%;\n  top: 130px;\n  transform: translateX(-50%);\n  width: 50px;\n  height: 12px;\n  background: linear-gradient(to bottom, #92400e, #78350f);\n  border-radius: 3px;\n  box-shadow: 0 4px 0 #451a03;\n}\n\n@keyframes swordFloat {\n  0%, 100% { transform: translateY(0); }\n  50% { transform: translateY(-15px); }\n}\n\n.collab-sync-lightning {\n  position: absolute;\n  left: 50%;\n  top: 50%;\n  transform: translate(-50%, -50%);\n  width: 200px;\n  height: 200px;\n  pointer-events: none;\n}\n\n.collab-sync-lightning::before,\n.collab-sync-lightning::after {\n  content: '';\n  position: absolute;\n  background: linear-gradient(180deg, transparent 0%, #3b82f6 50%, transparent 100%);\n  animation: lightning 1.5s ease-in-out infinite;\n}\n\n.collab-sync-lightning::before {\n  left: 20%;\n  top: 0;\n  width: 3px;\n  height: 100%;\n  animation-delay: 0s;\n}\n\n.collab-sync-lightning::after {\n  right: 20%;\n  top: 0;\n  width: 3px;\n  height: 100%;\n  animation-delay: 0.3s;\n}\n\n@keyframes lightning {\n  0%, 100% { opacity: 0; transform: scaleY(0); }\n  10%, 30% { opacity: 1; transform: scaleY(1); }\n  40% { opacity: 0; transform: scaleY(0); }\n}\n\n.collab-sync-text {\n  font-size: 24px;\n  font-weight: 700;\n  color: #e2e8f0;\n  margin-bottom: 12px;\n  text-shadow: 0 0 30px rgba(59, 130, 246, 0.5);\n  animation: textPulse 2s ease-in-out infinite;\n}\n\n@keyframes textPulse {\n  0%, 100% { opacity: 1; }\n  50% { opacity: 0.7; }\n}\n\n.collab-sync-subtext {\n  font-size: 14px;\n  color: #8892a0;\n  animation: dotDotDot 1.5s steps(4, end) infinite;\n}\n\n.collab-sync-subtext::after {\n  content: '';\n  animation: dots 1.5s steps(4, end) infinite;\n}\n\n@keyframes dots {\n  0% { content: ''; }\n  25% { content: '.'; }\n  50% { content: '..'; }\n  75% { content: '...'; }\n  100% { content: ''; }\n}\n\n#collab-chat-panel {\n  display: none;\n  position: fixed;\n  top: 56px;\n  right: 16px;\n  width: 360px;\n  max-width: calc(100vw - 32px);\n  height: 440px;\n  max-height: calc(100vh - 100px);\n  background: #0f1419;\n  border: 1px solid #1e2530;\n  border-radius: 12px;\n  z-index: 999998;\n  flex-direction: column;\n  font-family: Inter, system-ui, -apple-system, sans-serif;\n  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);\n  animation: collabModalIn 0.2s ease;\n}\n\n#collab-chat-panel.active { display: flex; }\n\n.collab-chat-header {\n  display: flex;\n  justify-content: space-between;\n  align-items: center;\n  padding: 12px 16px;\n  border-bottom: 1px solid #1e2530;\n  color: #e2e8f0;\n  font-weight: 600;\n  font-size: 14px;\n  flex-shrink: 0;\n}\n\n.collab-chat-close {\n  background: none;\n  border: none;\n  color: #8892a0;\n  font-size: 20px;\n  cursor: pointer;\n  padding: 0;\n  line-height: 1;\n  min-width: 44px;\n  min-height: 44px;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  -webkit-tap-highlight-color: transparent;\n}\n\n.collab-chat-close:hover { color: #e2e8f0; }\n\n.collab-chat-messages {\n  flex: 1;\n  overflow-y: auto;\n  padding: 12px;\n  display: flex;\n  flex-direction: column;\n  gap: 8px;\n  -webkit-overflow-scrolling: touch;\n}\n\n.collab-chat-msg {\n  padding: 8px 10px;\n  background: rgba(255, 255, 255, 0.03);\n  border-radius: 8px;\n  position: relative;\n}\n\n.collab-chat-msg.mentioned {\n  background: rgba(59, 130, 246, 0.1);\n  border-left: 3px solid #3b82f6;\n}\n\n.collab-chat-reply-ref {\n  font-size: 11px;\n  color: #64748b;\n  padding: 4px 8px;\n  margin-bottom: 4px;\n  border-left: 2px solid #2d3748;\n  overflow: hidden;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n  cursor: pointer;\n}\n\n.collab-chat-reply-ref:hover { color: #8892a0; }\n\n.collab-chat-reply-btn {\n  display: none;\n  position: absolute;\n  right: 6px;\n  top: 6px;\n  background: #1e2530;\n  border: 1px solid #2d3748;\n  border-radius: 4px;\n  color: #8892a0;\n  font-size: 11px;\n  cursor: pointer;\n  padding: 2px 6px;\n  line-height: 1;\n}\n\n.collab-chat-msg:hover .collab-chat-reply-btn { display: block; }\n.collab-chat-reply-btn:hover { color: #e2e8f0; background: #2d3748; }\n\n.collab-chat-name {\n  font-weight: 600;\n  font-size: 12px;\n  margin-right: 8px;\n}\n\n.collab-chat-time {\n  font-size: 10px;\n  color: #64748b;\n}\n\n.collab-chat-text {\n  font-size: 13px;\n  color: #e2e8f0;\n  margin-top: 4px;\n  line-height: 1.4;\n  word-break: break-word;\n}\n\n.collab-chat-mention {\n  color: #3b82f6;\n  font-weight: 600;\n}\n\n.collab-chat-input-area {\n  flex-shrink: 0;\n  border-top: 1px solid #1e2530;\n}\n\n.collab-chat-reply-preview {\n  display: none;\n  padding: 8px 12px;\n  background: rgba(255, 255, 255, 0.02);\n  font-size: 12px;\n  color: #8892a0;\n  border-bottom: 1px solid #1e2530;\n  align-items: center;\n  gap: 8px;\n}\n\n.collab-chat-reply-preview.active { display: flex; }\n\n.collab-chat-reply-preview span { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n\n.collab-chat-reply-cancel {\n  background: none;\n  border: none;\n  color: #8892a0;\n  cursor: pointer;\n  font-size: 16px;\n  padding: 0;\n  line-height: 1;\n  min-width: 28px;\n  min-height: 28px;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n}\n\n.collab-chat-reply-cancel:hover { color: #e2e8f0; }\n\n.collab-chat-input-wrap {\n  display: flex;\n  gap: 8px;\n  padding: 8px 12px;\n  align-items: center;\n}\n\n.collab-chat-input-inner {\n  flex: 1;\n  display: flex;\n  flex-direction: column;\n  gap: 4px;\n}\n\n.collab-chat-input-wrap input {\n  flex: 1;\n  padding: 8px 12px;\n  background: #0a0e14;\n  border: 1px solid #1e2530;\n  border-radius: 8px;\n  color: #e2e8f0;\n  font-size: 13px;\n  outline: none;\n  font-family: inherit;\n  height: 40px;\n  box-sizing: border-box;\n}\n\n.collab-chat-input-wrap input:focus { border-color: #3b82f6; }\n.collab-chat-input-wrap input::placeholder { color: #4a5568; }\n\n.collab-chat-char-count {\n  font-size: 10px;\n  color: #4a5568;\n  text-align: right;\n  padding-right: 4px;\n  height: 14px;\n  transition: color 0.15s;\n}\n\n.collab-chat-char-count.warning { color: #f59e0b; }\n.collab-chat-char-count.danger { color: #ef4444; }\n\n#collab-chat-send {\n  padding: 10px 16px;\n  min-height: 40px;\n  background: #3b82f6;\n  border: none;\n  border-radius: 8px;\n  color: white;\n  font-size: 13px;\n  font-weight: 500;\n  cursor: pointer;\n  font-family: inherit;\n  -webkit-tap-highlight-color: transparent;\n  flex-shrink: 0;\n}\n\n#collab-chat-send:hover { background: #2563eb; }\n\n.collab-typing-indicator {\n  display: none;\n  padding: 4px 12px;\n  font-size: 11px;\n  color: #64748b;\n  font-style: italic;\n  flex-shrink: 0;\n}\n\n.collab-typing-indicator.active { display: block; }\n\n.collab-chat-badge {\n  display: none;\n  align-items: center;\n  justify-content: center;\n  min-width: 18px;\n  height: 18px;\n  padding: 0 5px;\n  background: #ef4444;\n  color: white;\n  font-size: 11px;\n  font-weight: 700;\n  border-radius: 10px;\n  margin-left: 4px;\n}\n\n#collab-chat-btn { position: relative; }\n\n.collab-user-info {\n  display: flex;\n  flex-direction: column;\n  gap: 2px;\n}\n\n.collab-user-tab {\n  font-size: 10px;\n  color: #64748b;\n}\n\n.collab-remote-cursor {\n  position: fixed;\n  pointer-events: none;\n  z-index: 999997;\n  transform: translate(-2px, -2px);\n  transition: left 100ms ease-out, top 100ms ease-out;\n}\n\n.collab-remote-cursor svg {\n  display: block;\n  filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.5));\n}\n\n.collab-cursor-name {\n  position: absolute;\n  left: 14px;\n  top: 12px;\n  padding: 2px 6px;\n  border-radius: 4px;\n  font-size: 10px;\n  font-weight: 500;\n  color: white;\n  white-space: nowrap;\n  font-family: Inter, system-ui, -apple-system, sans-serif;\n}\n\n.collab-conn-status {\n  width: 8px;\n  height: 8px;\n  border-radius: 50%;\n  flex-shrink: 0;\n  margin-right: 4px;\n  transition: background 0.3s;\n}\n\n.collab-conn-status.connected { background: #22c55e; box-shadow: 0 0 6px rgba(34, 197, 94, 0.5); }\n.collab-conn-status.reconnecting { background: #f59e0b; animation: connPulse 1s infinite; }\n.collab-conn-status.disconnected { background: #ef4444; }\n\n@keyframes connPulse {\n  0%, 100% { opacity: 1; }\n  50% { opacity: 0.4; }\n}\n\n.collab-reconnect-banner {\n  display: none;\n  position: fixed;\n  top: 48px;\n  left: 0;\n  right: 0;\n  background: rgba(245, 158, 11, 0.95);\n  color: #000;\n  font-size: 13px;\n  font-weight: 500;\n  text-align: center;\n  padding: 8px 16px;\n  z-index: 99998;\n  font-family: Inter, system-ui, -apple-system, sans-serif;\n  align-items: center;\n  justify-content: center;\n  gap: 12px;\n}\n\n.collab-reconnect-banner.active { display: flex; }\n\n.collab-reconnect-banner.offline { background: rgba(239, 68, 68, 0.95); color: white; }\n\n.collab-reconnect-btn {\n  background: rgba(0, 0, 0, 0.2);\n  border: none;\n  border-radius: 6px;\n  color: inherit;\n  padding: 4px 12px;\n  font-size: 12px;\n  font-weight: 600;\n  cursor: pointer;\n  min-height: 32px;\n}\n\n.collab-reconnect-btn:hover { background: rgba(0, 0, 0, 0.3); }\n\n.collab-toast-stack {\n  position: fixed;\n  bottom: max(80px, calc(80px + env(safe-area-inset-bottom, 0px)));\n  left: 50%;\n  transform: translateX(-50%);\n  display: flex;\n  flex-direction: column-reverse;\n  gap: 8px;\n  z-index: 100001;\n  pointer-events: none;\n}\n\n.collab-toast-item {\n  background: #333;\n  color: #fff;\n  padding: 10px 20px;\n  border-radius: 8px;\n  font-size: 13px;\n  font-family: Inter, system-ui, -apple-system, sans-serif;\n  white-space: nowrap;\n  opacity: 0;\n  transform: translateY(10px);\n  animation: toastIn 0.3s ease forwards;\n  pointer-events: auto;\n}\n\n.collab-toast-item.fade-out {\n  animation: toastOut 0.3s ease forwards;\n}\n\n@keyframes toastIn {\n  from { opacity: 0; transform: translateY(10px); }\n  to { opacity: 1; transform: translateY(0); }\n}\n\n@keyframes toastOut {\n  from { opacity: 1; transform: translateY(0); }\n  to { opacity: 0; transform: translateY(-10px); }\n}\n\n.collab-room-expiry {\n  font-size: 11px;\n  color: #f59e0b;\n  display: flex;\n  align-items: center;\n  gap: 4px;\n  padding: 2px 8px;\n  background: rgba(245, 158, 11, 0.1);\n  border-radius: 12px;\n  white-space: nowrap;\n}\n\n.collab-emoji-picker {\n  display: none;\n  position: absolute;\n  bottom: 100%;\n  left: 0;\n  right: 0;\n  background: #0f1419;\n  border: 1px solid #1e2530;\n  border-radius: 12px 12px 0 0;\n  padding: 8px;\n  max-height: 200px;\n  overflow-y: auto;\n  -webkit-overflow-scrolling: touch;\n}\n\n.collab-emoji-picker.active { display: block; }\n\n.collab-emoji-grid {\n  display: grid;\n  grid-template-columns: repeat(8, 1fr);\n  gap: 2px;\n}\n\n.collab-emoji-btn {\n  background: none;\n  border: none;\n  font-size: 20px;\n  padding: 6px;\n  cursor: pointer;\n  border-radius: 6px;\n  min-width: 36px;\n  min-height: 36px;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  -webkit-tap-highlight-color: transparent;\n}\n\n.collab-emoji-btn:hover { background: rgba(255, 255, 255, 0.1); }\n\n.collab-emoji-toggle {\n  background: none !important;\n  border: none;\n  color: #8892a0;\n  font-size: 20px;\n  cursor: pointer;\n  padding: 4px;\n  width: 36px;\n  height: 40px;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  flex-shrink: 0;\n  -webkit-tap-highlight-color: transparent;\n  border-radius: 8px;\n  transition: background 0.15s;\n}\n\n.collab-emoji-toggle:hover { background: rgba(255, 255, 255, 0.08) !important; color: #e2e8f0; }\n\n@media (max-width: 640px) {\n  #collab-chat-panel {\n    position: fixed;\n    top: 0;\n    right: 0;\n    left: 0;\n    bottom: 0;\n    width: 100%;\n    max-width: none;\n    height: 100%;\n    max-height: none;\n    border-radius: 0;\n    border: none;\n    padding-top: env(safe-area-inset-top, 0);\n    padding-bottom: env(safe-area-inset-bottom, 0);\n  }\n\n  #collab-chat-panel .collab-chat-header {\n    padding-top: max(12px, env(safe-area-inset-top, 12px));\n  }\n\n  .collab-emoji-grid {\n    grid-template-columns: repeat(7, 1fr);\n  }\n}\n"
  },
  {
    "path": "theonefile_verse/public/collab.js",
    "content": "(function() {\n  'use strict';\n\n  const roomConfigEl = document.getElementById('room-config');\n  if (!roomConfigEl) return;\n  let _rc;\n  try { _rc = JSON.parse(roomConfigEl.textContent); } catch { return; }\n  if (!_rc.roomId) return;\n\n  const ROOM_ID = _rc.roomId;\n  const WS_URL = (location.protocol === 'https:' ? 'wss://' : 'ws://') + location.host + '/ws/' + _rc.roomId;\n  const HAS_PASSWORD = _rc.roomHasPassword;\n  const IS_ADMIN = _rc.isAdmin || false;\n  const IS_CREATOR = _rc.isCreator || IS_ADMIN;\n  if (_rc.csrfToken) window.CSRF_TOKEN = _rc.csrfToken;\n  function refreshCsrf() {\n    fetch('/api/auth/csrf').then(function(r) { return r.json(); }).then(function(d) {\n      if (d.token) window.CSRF_TOKEN = d.token;\n    }).catch(function() {});\n  }\n  refreshCsrf();\n  if (_rc.defaultRoomTheme) window.DEFAULT_ROOM_THEME = _rc.defaultRoomTheme;\n\n  let shareButtonEnabled = true;\n\n  function h(tag, props, ...children) {\n    const node = document.createElement(tag);\n    if (props) {\n      for (const key of Object.keys(props)) {\n        if (key === 'className') node.className = props[key];\n        else if (key === 'style') node.setAttribute('style', props[key]);\n        else if (key === 'textContent') node.textContent = props[key];\n        else if (key.startsWith('data-')) node.setAttribute(key, props[key]);\n        else if (key === 'checked') { if (props[key]) node.checked = true; }\n        else if (key === 'readonly') { if (props[key]) node.readOnly = true; }\n        else node[key] = props[key];\n      }\n    }\n    for (const child of children) _append(node, child);\n    return node;\n  }\n  function _append(parent, child) {\n    if (child == null || child === false) return;\n    if (typeof child === 'string' || typeof child === 'number') {\n      parent.appendChild(document.createTextNode(String(child)));\n    } else if (Array.isArray(child)) {\n      for (const c of child) _append(parent, c);\n    } else {\n      parent.appendChild(child);\n    }\n  }\n  function clearNode(el) {\n    while (el.firstChild) el.removeChild(el.firstChild);\n  }\n  function setContent(container, children) {\n    clearNode(container);\n    const frag = document.createDocumentFragment();\n    _append(frag, Array.isArray(children) ? children : [children]);\n    container.appendChild(frag);\n  }\n\n  function generateUUID() {\n    if (typeof crypto !== 'undefined' && crypto.randomUUID) {\n      return crypto.randomUUID();\n    }\n    return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {\n      const r = Math.random() * 16 | 0;\n      const v = c === 'x' ? r : (r & 0x3 | 0x8);\n      return v.toString(16);\n    });\n  }\n\n  function getOrCreateUserId() {\n    const globalKey = 'collab-global-user-id';\n    let userId = localStorage.getItem(globalKey);\n    if (!userId) {\n      userId = generateUUID();\n      localStorage.setItem(globalKey, userId);\n    }\n    return userId;\n  }\n\n  function getStoredUserName() {\n    return localStorage.getItem(`collab-name-${ROOM_ID}`);\n  }\n\n  function setStoredUserName(name) {\n    localStorage.setItem(`collab-name-${ROOM_ID}`, name);\n  }\n\n  const COLORS = [\n    '#e63946', '#f4a261', '#2a9d8f', '#264653',\n    '#e9c46a', '#8338ec', '#ff006e', '#3b82ff',\n    '#06d6a0', '#118ab2', '#ef476f', '#ffd166',\n    '#073b4c', '#06aed5', '#f72585', '#7209b7'\n  ];\n\n  const HIGHLANDER_NAMES = [\n    'Connor MacLeod', 'Duncan MacLeod', 'Ramirez', 'The Kurgan',\n    'Methos', 'Amanda Darieux', 'Richie Ryan', 'Joe Dawson',\n    'Cassandra', 'Kronos', 'Silas', 'Caspian',\n    'Xavier St. Cloud', 'Kalas', 'Fitzcairn', 'Darius',\n    'Kenny', 'Ceirdwyn', 'Rebecca Horne', 'Grace Chandel',\n    'Nakano', 'Kastagir', 'Sean Burns', 'Grayson',\n    'Kern', 'Kell', 'Jacob Kell', 'Faith', 'Kane',\n    'Quentin MacLeod', 'Kortan', 'Arak', 'Asklepios',\n    'Hugh Fitzcairn', 'Carl Robinson', 'Annie Devlin'\n  ];\n\n  const HIGHLANDER_SYNC_QUOTES = [\n    \"There can be only one... state.\",\n    \"I am immortal. Your data is eternal.\",\n    \"The Quickening approaches...\",\n    \"Gathering the power of all Immortals...\",\n    \"From the dawn of time we came...\",\n    \"In the end, there can be only one... source of truth.\",\n    \"I have something to say! Syncing...\",\n    \"It's better to burn out than to fade away... loading.\",\n    \"The Prize awaits... synchronizing.\",\n    \"Feel the Quickening!\"\n  ];\n\n  const EMOJI_LIST = [\n    '😀','😂','🤣','😊','😍','🤔','😎','🙄','😴','🤯',\n    '👍','👎','👏','🙌','🤝','💪','🔥','❤️','💯','⭐',\n    '✅','❌','⚡','💡','🎉','🚀','👀','🤷','😭','🥳'\n  ];\n\n  function getRandomSyncQuote() {\n    return HIGHLANDER_SYNC_QUOTES[Math.floor(Math.random() * HIGHLANDER_SYNC_QUOTES.length)];\n  }\n\n  let syncOverlayTimeout = null;\n\n  function showSyncingOverlay() {\n    if (document.getElementById('collab-sync-overlay')) return;\n    const overlay = document.createElement('div');\n    overlay.id = 'collab-sync-overlay';\n    overlay.appendChild(\n      h('div', {className: 'collab-sync-content'},\n        h('div', {className: 'collab-sync-sword'}),\n        h('div', {className: 'collab-sync-lightning'}),\n        h('div', {className: 'collab-sync-text'}, getRandomSyncQuote()),\n        h('div', {className: 'collab-sync-subtext'}, 'Synchronizing with the realm...')\n      )\n    );\n    document.body.appendChild(overlay);\n    syncOverlayTimeout = setTimeout(() => {\n      hideSyncingOverlay();\n      showToast('Sync timed out. You may need to refresh.');\n    }, 30000);\n  }\n\n  function hideSyncingOverlay() {\n    if (syncOverlayTimeout) {\n      clearTimeout(syncOverlayTimeout);\n      syncOverlayTimeout = null;\n    }\n    const overlay = document.getElementById('collab-sync-overlay');\n    if (overlay) {\n      overlay.classList.add('fade-out');\n      setTimeout(() => overlay.remove(), 300);\n    }\n  }\n\n  function leaveRoom() {\n    if (ws) {\n      sendMessage('leave', { userId: window.COLLAB_USER.id });\n      ws.close();\n      ws = null;\n    }\n\n    localStorage.removeItem(`collab-name-${ROOM_ID}`);\n    localStorage.removeItem(`collab-color-${ROOM_ID}`);\n\n    window.location.href = '/';\n  }\n\n  function generateHighlanderName() {\n    return HIGHLANDER_NAMES[Math.floor(Math.random() * HIGHLANDER_NAMES.length)];\n  }\n\n  function isValidColor(color) {\n    return typeof color === 'string' && /^#[0-9a-fA-F]{6}$/.test(color);\n  }\n\n  function sanitizeColor(color) {\n    return isValidColor(color) ? color : COLORS[0];\n  }\n\n  function getOrCreateUserColor() {\n    const storageKey = `collab-color-${ROOM_ID}`;\n    let color = localStorage.getItem(storageKey);\n    if (!color || !isValidColor(color)) {\n      color = pickUniqueColor();\n      localStorage.setItem(storageKey, color);\n    }\n    return color;\n  }\n\n  function pickUniqueColor() {\n    const usedColors = new Set();\n    users.forEach(u => usedColors.add(u.color));\n    const available = COLORS.filter(c => !usedColors.has(c));\n    if (available.length > 0) {\n      return available[Math.floor(Math.random() * available.length)];\n    }\n    return COLORS[Math.floor(Math.random() * COLORS.length)];\n  }\n\n  function getInitials(name) {\n    if (!name) return '?';\n    const parts = name.trim().split(/\\s+/);\n    if (parts.length >= 2) return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();\n    return name.substring(0, 2).toUpperCase();\n  }\n\n  window.COLLAB_USER = {\n    id: getOrCreateUserId(),\n    name: null,\n    color: null,\n    selectedNodes: [],\n    editingNode: null\n  };\n\n  const users = new Map();\n  let ws = null;\n  let reconnectAttempts = 0;\n  let lastStateHash = null;\n  let syncPaused = false;\n  let hasReceivedInitialState = false;\n  let chatMessages = [];\n  let unreadCount = 0;\n  let chatOpen = false;\n  let replyingTo = null;\n  let typingUsers = new Map();\n  let typingTimeout = null;\n  let lastTypingSent = 0;\n  let connectionState = 'disconnected';\n  let roomExpiryData = null;\n  let expiryInterval = null;\n  let chatSoundEnabled = localStorage.getItem('collab-chat-sound') !== 'false';\n  let emojiPickerOpen = false;\n\n  let currentWsToken = null;\n\n  async function fetchWsToken() {\n    try {\n      const res = await fetch(`/api/room/${ROOM_ID}/ws-token`, {\n        method: 'POST',\n        headers: { 'Content-Type': 'application/json', 'x-csrf-token': window.CSRF_TOKEN || '' },\n        body: JSON.stringify({ collabUserId: window.COLLAB_USER.id })\n      });\n      refreshCsrf();\n      if (!res.ok) return null;\n      const data = await res.json();\n      if (data.collabUserId && data.collabUserId !== window.COLLAB_USER.id) {\n        window.COLLAB_USER.id = data.collabUserId;\n      }\n      return data.wsToken;\n    } catch (e) {\n      console.warn('[Collab] Failed to fetch WS token:', e);\n      return null;\n    }\n  }\n\n  function setConnectionState(state) {\n    connectionState = state;\n    const dot = document.getElementById('collab-conn-dot');\n    if (dot) {\n      dot.className = 'collab-conn-status ' + state;\n    }\n    const banner = document.getElementById('collab-reconnect-banner');\n    if (banner) {\n      if (state === 'connected') {\n        banner.classList.remove('active', 'offline');\n      } else if (state === 'reconnecting') {\n        banner.classList.add('active');\n        banner.classList.remove('offline');\n        banner.querySelector('span').textContent = 'Reconnecting...';\n      } else if (state === 'disconnected') {\n        banner.classList.add('active', 'offline');\n        banner.querySelector('span').textContent = 'Connection lost';\n      }\n    }\n  }\n\n  async function connect() {\n    if (ws && ws.readyState === WebSocket.OPEN) return;\n\n    setConnectionState('reconnecting');\n    currentWsToken = await fetchWsToken();\n\n    ws = new WebSocket(WS_URL);\n\n    ws.onopen = () => {\n      if (currentWsToken) {\n        ws.send(JSON.stringify({ type: 'auth', token: currentWsToken }));\n      } else {\n        reconnectAttempts = 0;\n        setConnectionState('connected');\n        window.COLLAB_USER.color = getOrCreateUserColor();\n        hasReceivedInitialState = false;\n        showSyncingOverlay();\n        sendMessage('join', { user: window.COLLAB_USER });\n      }\n    };\n\n    ws.onmessage = (event) => {\n      try {\n        const msg = JSON.parse(event.data);\n        handleMessage(msg);\n      } catch (e) {}\n    };\n\n    ws.onclose = () => {\n      setConnectionState('reconnecting');\n      scheduleReconnect();\n    };\n    ws.onerror = () => {};\n  }\n\n  function scheduleReconnect() {\n    if (reconnectAttempts >= 10) {\n      setConnectionState('disconnected');\n      return;\n    }\n    const delay = Math.min(1000 * Math.pow(2, reconnectAttempts), 30000);\n    reconnectAttempts++;\n    setTimeout(connect, delay);\n  }\n\n  function sendMessage(type, data) {\n    if (ws && ws.readyState === WebSocket.OPEN) {\n      ws.send(JSON.stringify({ type, ...data }));\n    }\n  }\n\n  function sanitizeUser(user) {\n    if (user && user.color) {\n      user.color = sanitizeColor(user.color);\n    }\n    return user;\n  }\n\n  function handleMessage(msg) {\n    switch (msg.type) {\n      case 'auth-ok':\n        reconnectAttempts = 0;\n        setConnectionState('connected');\n        window.COLLAB_USER.color = getOrCreateUserColor();\n        hasReceivedInitialState = false;\n        showSyncingOverlay();\n        sendMessage('join', { user: window.COLLAB_USER });\n        return;\n      case 'auth-error':\n        console.error('[Collab] Auth failed:', msg.error);\n        return;\n      case 'join':\n        users.set(msg.user.id, sanitizeUser(msg.user));\n        renderUsers();\n        renderUserIndicators();\n        break;\n      case 'leave':\n        users.delete(msg.userId);\n        removeUserIndicators(msg.userId);\n        removeRemoteCursor(msg.userId);\n        typingUsers.delete(msg.userId);\n        updateTypingIndicator();\n        renderUsers();\n        break;\n      case 'users':\n        msg.users.forEach(u => {\n          if (u.id !== window.COLLAB_USER.id) users.set(u.id, sanitizeUser(u));\n        });\n        renderUsers();\n        renderUserIndicators();\n        break;\n      case 'presence':\n        if (msg.userId !== window.COLLAB_USER.id) {\n          const user = users.get(msg.userId);\n          if (user) {\n            user.selectedNodes = msg.selectedNodes || [];\n            user.editingNode = msg.editingNode || null;\n            user.currentTab = msg.currentTab || null;\n            users.set(msg.userId, user);\n            renderUserIndicators();\n            renderUsers();\n          }\n        }\n        break;\n      case 'initial-state':\n        hideSyncingOverlay();\n        hasReceivedInitialState = true;\n        if (msg.state) {\n          applyRemoteState(msg.state);\n          if (!msg.state.themeState && window.DEFAULT_ROOM_THEME) {\n            var dPresets = typeof THEME_PRESETS !== 'undefined' ? THEME_PRESETS : null;\n            if (dPresets && dPresets[window.DEFAULT_ROOM_THEME]) {\n              setGlobal('PAGE_STATE', dPresets[window.DEFAULT_ROOM_THEME]);\n              if (typeof wieldThePower === 'function') try { wieldThePower(); } catch(e) {}\n              var dSel = document.getElementById('welcome-theme-select');\n              if (dSel) dSel.value = window.DEFAULT_ROOM_THEME;\n            }\n          }\n        } else {\n          if (window.DEFAULT_ROOM_THEME) {\n            var dPresets2 = typeof THEME_PRESETS !== 'undefined' ? THEME_PRESETS : null;\n            if (dPresets2 && dPresets2[window.DEFAULT_ROOM_THEME]) {\n              setGlobal('PAGE_STATE', dPresets2[window.DEFAULT_ROOM_THEME]);\n              if (typeof wieldThePower === 'function') try { wieldThePower(); } catch(e) {}\n              var dSel2 = document.getElementById('welcome-theme-select');\n              if (dSel2) dSel2.value = window.DEFAULT_ROOM_THEME;\n            }\n          }\n          sendFullState();\n        }\n        window.__collabSuppressWelcome = false;\n        var _nd = getGlobal('NODE_DATA');\n        var _ed = getGlobal('EDGE_DATA');\n        var _roomEmpty = (!_nd || Object.keys(_nd).length === 0) && (!_ed || !_ed.list || _ed.list.length === 0);\n        if (_roomEmpty || _rc.forceWelcomeModal) {\n          if (typeof showWelcomeModal === 'function') showWelcomeModal();\n        } else {\n          var _wm = document.getElementById('welcome-modal');\n          if (_wm) _wm.classList.remove('active');\n        }\n        break;\n      case 'state':\n        if (!syncPaused && hasReceivedInitialState) applyRemoteState(msg.state);\n        break;\n      case 'patch':\n        if (!syncPaused && hasReceivedInitialState) applyRemoteState(msg.patch);\n        break;\n      case 'chat':\n        addChatMessage(msg);\n        break;\n      case 'chat-history':\n        if (msg.messages && Array.isArray(msg.messages)) {\n          msg.messages.forEach(m => {\n            if (!chatMessages.some(existing => existing.timestamp === m.timestamp && existing.userId === m.userId)) {\n              chatMessages.push(m);\n            }\n          });\n          chatMessages.sort((a, b) => a.timestamp - b.timestamp);\n          if (chatMessages.length > 100) chatMessages = chatMessages.slice(-100);\n          renderChatMessages();\n        }\n        break;\n      case 'typing':\n        if (msg.userId !== window.COLLAB_USER.id) {\n          typingUsers.set(msg.userId, { name: msg.userName, expires: Date.now() + 3000 });\n          updateTypingIndicator();\n        }\n        break;\n      case 'cursor':\n        if (msg.userId !== window.COLLAB_USER.id) {\n          const user = users.get(msg.userId);\n          if (user) {\n            user.cursorX = msg.x;\n            user.cursorY = msg.y;\n            user.isRatio = msg.isRatio || false;\n            user.isCanvasCoords = msg.isCanvasCoords || false;\n            users.set(msg.userId, user);\n            updateRemoteCursor(msg.userId, user);\n            renderUsers();\n          }\n        }\n        break;\n      case 'name-rejected':\n        handleNameRejected(msg.reason);\n        break;\n      case 'discovery-progress':\n        updateDiscoveryProgress(msg.percent, msg.scanned, msg.total, msg.rangeIndex, msg.totalRanges);\n        break;\n      case 'discovery-found':\n        addDiscoveryResult(msg.host);\n        break;\n      case 'discovery-complete':\n        finalizeDiscovery(msg.totalFound);\n        break;\n      case 'deepscan-progress':\n        handleDeepScanProgress(msg.scanId, msg.ip, msg.percent, msg.scanned, msg.total);\n        break;\n      case 'deepscan-update':\n        handleDeepScanUpdate(msg.scanId, msg.ip, msg.newPorts, msg.newServices, msg.containers, msg.newIcons);\n        break;\n      case 'deepscan-complete':\n        handleDeepScanComplete(msg.scanId, msg.ip);\n        break;\n    }\n  }\n\n  let nameRejectedRecovery = false;\n\n  function handleNameRejected(reason) {\n    hideSyncingOverlay();\n    localStorage.removeItem(`collab-name-${ROOM_ID}`);\n    window.COLLAB_USER.name = null;\n    nameRejectedRecovery = true;\n    showNameModal(false, reason);\n  }\n\n  let msgIdCounter = 0;\n\n  function addChatMessage(msg) {\n    const chatMsg = {\n      id: msg.id || (Date.now() + '-' + (msgIdCounter++)),\n      userId: msg.userId,\n      userName: msg.userName,\n      userColor: msg.userColor,\n      text: msg.text,\n      timestamp: msg.timestamp || Date.now(),\n      replyTo: msg.replyTo || null\n    };\n    chatMessages.push(chatMsg);\n    if (chatMessages.length > 100) chatMessages.shift();\n\n    const isMentioned = msg.text && window.COLLAB_USER.name &&\n      msg.text.toLowerCase().includes('@' + window.COLLAB_USER.name.toLowerCase());\n\n    if (!chatOpen && msg.userId !== window.COLLAB_USER.id) {\n      unreadCount++;\n      updateChatBadge();\n      if (chatSoundEnabled) playChatSound(isMentioned);\n    }\n    renderChatMessages();\n  }\n\n  function playChatSound(isMention) {\n    try {\n      const ctx = new (window.AudioContext || window.webkitAudioContext)();\n      const osc = ctx.createOscillator();\n      const gain = ctx.createGain();\n      osc.connect(gain);\n      gain.connect(ctx.destination);\n      osc.type = 'sine';\n      osc.frequency.value = isMention ? 880 : 660;\n      gain.gain.value = 0.08;\n      gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + 0.15);\n      osc.start(ctx.currentTime);\n      osc.stop(ctx.currentTime + 0.15);\n    } catch (e) {}\n  }\n\n  function sendChatMessage(text) {\n    if (!text.trim()) return;\n    let trimmedText = text.trim();\n    if (trimmedText.length > 500) {\n      trimmedText = trimmedText.substring(0, 500);\n      showToast('Message truncated to 500 characters');\n    }\n    const msg = {\n      id: Date.now() + '-' + (msgIdCounter++),\n      userId: window.COLLAB_USER.id,\n      userName: window.COLLAB_USER.name,\n      userColor: window.COLLAB_USER.color,\n      text: trimmedText,\n      timestamp: Date.now(),\n      replyTo: replyingTo ? { id: replyingTo.id, userName: replyingTo.userName, text: replyingTo.text.substring(0, 80) } : null\n    };\n    sendMessage('chat', msg);\n    addChatMessage(msg);\n    clearReply();\n  }\n\n  function setReplyTo(msg) {\n    replyingTo = msg;\n    const preview = document.getElementById('collab-reply-preview');\n    if (preview) {\n      preview.querySelector('span').textContent = `${msg.userName}: ${msg.text.substring(0, 60)}`;\n      preview.classList.add('active');\n    }\n    const input = document.getElementById('collab-chat-input');\n    if (input) input.focus();\n  }\n\n  function clearReply() {\n    replyingTo = null;\n    const preview = document.getElementById('collab-reply-preview');\n    if (preview) preview.classList.remove('active');\n  }\n\n  function sendTypingIndicator() {\n    const now = Date.now();\n    if (now - lastTypingSent < 2000) return;\n    lastTypingSent = now;\n    sendMessage('typing', { userId: window.COLLAB_USER.id, userName: window.COLLAB_USER.name });\n  }\n\n  function updateTypingIndicator() {\n    const now = Date.now();\n    const active = [];\n    typingUsers.forEach((data, userId) => {\n      if (data.expires > now) active.push(data.name);\n      else typingUsers.delete(userId);\n    });\n    const el = document.getElementById('collab-typing');\n    if (!el) return;\n    if (active.length === 0) {\n      el.classList.remove('active');\n    } else {\n      el.classList.add('active');\n      if (active.length === 1) el.textContent = active[0] + ' is typing...';\n      else if (active.length === 2) el.textContent = active[0] + ' and ' + active[1] + ' are typing...';\n      else el.textContent = active.length + ' people are typing...';\n    }\n  }\n\n  setInterval(updateTypingIndicator, 1000);\n\n  function showToast(message) {\n    let stack = document.getElementById('collab-toast-stack');\n    if (!stack) {\n      stack = document.createElement('div');\n      stack.id = 'collab-toast-stack';\n      stack.className = 'collab-toast-stack';\n      document.body.appendChild(stack);\n    }\n    const toast = document.createElement('div');\n    toast.className = 'collab-toast-item';\n    toast.textContent = message;\n    stack.appendChild(toast);\n    setTimeout(() => {\n      toast.classList.add('fade-out');\n      setTimeout(() => toast.remove(), 300);\n    }, 3000);\n    if (stack.children.length > 5) stack.firstChild.remove();\n  }\n\n  function updateChatBadge() {\n    const badge = document.getElementById('collab-chat-badge');\n    if (badge) {\n      if (unreadCount > 0) {\n        badge.textContent = unreadCount > 99 ? '99+' : unreadCount;\n        badge.style.display = 'flex';\n      } else {\n        badge.style.display = 'none';\n      }\n    }\n  }\n\n  function highlightMentions(text) {\n    const allNames = [window.COLLAB_USER.name];\n    users.forEach(u => { if (u.name) allNames.push(u.name); });\n    const validNames = allNames.filter(n => n);\n    if (validNames.length === 0) return [text];\n    const escaped = validNames.map(n => n.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&'));\n    const regex = new RegExp('@(?:' + escaped.join('|') + ')', 'gi');\n    const children = [];\n    let lastIndex = 0;\n    let match;\n    while ((match = regex.exec(text)) !== null) {\n      if (match.index > lastIndex) children.push(text.slice(lastIndex, match.index));\n      children.push(h('span', {className: 'collab-chat-mention'}, match[0]));\n      lastIndex = regex.lastIndex;\n    }\n    if (lastIndex < text.length) children.push(text.slice(lastIndex));\n    return children.length > 0 ? children : [text];\n  }\n\n  function formatTimeAgo(timestamp) {\n    const diff = Date.now() - timestamp;\n    if (diff < 60000) return 'now';\n    if (diff < 3600000) return Math.floor(diff / 60000) + 'm ago';\n    if (diff < 86400000) return Math.floor(diff / 3600000) + 'h ago';\n    return new Date(timestamp).toLocaleDateString([], { month: 'short', day: 'numeric' });\n  }\n\n  function renderChatMessages() {\n    const container = document.getElementById('collab-chat-messages');\n    if (!container) return;\n    clearNode(container);\n    chatMessages.forEach(msg => {\n      const time = formatTimeAgo(msg.timestamp);\n      const safeColor = sanitizeColor(msg.userColor);\n      const isMentioned = msg.text && window.COLLAB_USER.name &&\n        msg.text.toLowerCase().includes('@' + window.COLLAB_USER.name.toLowerCase());\n      const msgDiv = h('div', {className: 'collab-chat-msg' + (isMentioned ? ' mentioned' : ''), 'data-msg-id': msg.id});\n      if (msg.replyTo) {\n        msgDiv.appendChild(h('div', {className: 'collab-chat-reply-ref'}, msg.replyTo.userName + ': ' + msg.replyTo.text));\n      }\n      msgDiv.appendChild(h('span', {className: 'collab-chat-name', style: 'color: ' + safeColor}, msg.userName));\n      msgDiv.appendChild(h('span', {className: 'collab-chat-time'}, time));\n      const replyBtn = h('button', {className: 'collab-chat-reply-btn', 'data-reply-id': msg.id}, 'Reply');\n      replyBtn.addEventListener('click', () => {\n        const m = chatMessages.find(x => x.id === msg.id);\n        if (m) setReplyTo(m);\n      });\n      msgDiv.appendChild(replyBtn);\n      msgDiv.appendChild(h('div', {className: 'collab-chat-text'}, highlightMentions(msg.text)));\n      container.appendChild(msgDiv);\n    });\n    container.scrollTop = container.scrollHeight;\n  }\n\n  const CANVAS_WIDTH = 4000;\n  const CANVAS_HEIGHT = 3000;\n\n  function getCanvasState() {\n    const state = getGlobal('canvasState');\n    return state || { zoom: 1, panX: 0, panY: 0 };\n  }\n\n  function screenToCanvasCoords(screenX, screenY) {\n    const viewport = document.getElementById('canvas-viewport');\n    if (!viewport) return null;\n\n    const rect = viewport.getBoundingClientRect();\n    const cs = getCanvasState();\n\n    const viewportX = screenX - rect.left;\n    const viewportY = screenY - rect.top;\n\n    const viewWidth = CANVAS_WIDTH / cs.zoom;\n    const viewHeight = CANVAS_HEIGHT / cs.zoom;\n\n    const canvasX = cs.panX + (viewportX / rect.width) * viewWidth;\n    const canvasY = cs.panY + (viewportY / rect.height) * viewHeight;\n\n    return { x: canvasX, y: canvasY };\n  }\n\n  function canvasToScreenCoords(canvasX, canvasY) {\n    const viewport = document.getElementById('canvas-viewport');\n    if (!viewport) return null;\n\n    const rect = viewport.getBoundingClientRect();\n    const cs = getCanvasState();\n\n    const viewWidth = CANVAS_WIDTH / cs.zoom;\n    const viewHeight = CANVAS_HEIGHT / cs.zoom;\n\n    const ratioX = (canvasX - cs.panX) / viewWidth;\n    const ratioY = (canvasY - cs.panY) / viewHeight;\n\n    const screenX = rect.left + ratioX * rect.width;\n    const screenY = rect.top + ratioY * rect.height;\n\n    return { x: screenX, y: screenY };\n  }\n\n  function safeElementId(userId) {\n    return typeof CSS !== 'undefined' && CSS.escape ? CSS.escape(userId) : userId.replace(/[^a-zA-Z0-9-_]/g, '');\n  }\n\n  function updateRemoteCursor(userId, user) {\n    const safeId = safeElementId(userId);\n    let cursor = document.getElementById(`collab-cursor-${safeId}`);\n    if (!cursor && user.cursorX !== undefined) {\n      cursor = document.createElement('div');\n      cursor.id = `collab-cursor-${safeId}`;\n      cursor.className = 'collab-remote-cursor';\n      const safeColor = sanitizeColor(user.color);\n      const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');\n      svg.setAttribute('width', '16');\n      svg.setAttribute('height', '16');\n      svg.setAttribute('viewBox', '0 0 16 16');\n      const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');\n      path.setAttribute('d', 'M0 0L16 12L8 12L4 16L0 0Z');\n      path.setAttribute('fill', safeColor);\n      svg.appendChild(path);\n      cursor.appendChild(svg);\n      cursor.appendChild(h('span', {className: 'collab-cursor-name', style: 'background:' + safeColor}, user.name));\n      document.body.appendChild(cursor);\n    }\n    if (cursor && user.cursorX !== undefined) {\n      const viewport = document.getElementById('canvas-viewport');\n      if (!viewport) {\n        cursor.style.display = 'none';\n        return;\n      }\n\n      const rect = viewport.getBoundingClientRect();\n      let screenX, screenY;\n\n      if (user.isCanvasCoords) {\n        const screen = canvasToScreenCoords(user.cursorX, user.cursorY);\n        if (!screen) {\n          cursor.style.display = 'none';\n          return;\n        }\n        screenX = screen.x;\n        screenY = screen.y;\n      } else if (user.isRatio) {\n        screenX = rect.left + user.cursorX * rect.width;\n        screenY = rect.top + user.cursorY * rect.height;\n      } else {\n        screenX = user.cursorX;\n        screenY = user.cursorY;\n      }\n\n      const margin = 100;\n      if (screenX < rect.left - margin || screenX > rect.right + margin ||\n          screenY < rect.top - margin || screenY > rect.bottom + margin) {\n        cursor.style.display = 'none';\n        return;\n      }\n\n      cursor.style.display = 'block';\n      cursor.style.left = screenX + 'px';\n      cursor.style.top = screenY + 'px';\n    }\n  }\n\n  function refreshAllRemoteCursors() {\n    users.forEach((user, userId) => {\n      if (user.cursorX !== undefined) {\n        updateRemoteCursor(userId, user);\n      }\n    });\n  }\n\n  function removeRemoteCursor(userId) {\n    const cursor = document.getElementById(`collab-cursor-${safeElementId(userId)}`);\n    if (cursor) cursor.remove();\n  }\n\n  function trackCursor() {\n    let lastSent = 0;\n    let pendingPos = null;\n    let rafId = null;\n\n    function sendCursorUpdate() {\n      if (!pendingPos) return;\n      const canvas = screenToCanvasCoords(pendingPos.x, pendingPos.y);\n      if (canvas) {\n        sendMessage('cursor', {\n          userId: window.COLLAB_USER.id,\n          x: canvas.x,\n          y: canvas.y,\n          isCanvasCoords: true\n        });\n      }\n      pendingPos = null;\n      rafId = null;\n    }\n\n    document.addEventListener('mousemove', (e) => {\n      const now = Date.now();\n      if (now - lastSent < 25) return;\n      lastSent = now;\n\n      pendingPos = { x: e.clientX, y: e.clientY };\n      if (rafId === null) {\n        rafId = requestAnimationFrame(sendCursorUpdate);\n      }\n    });\n\n    let lastZoom = null;\n    let lastPanX = null;\n    let lastPanY = null;\n    setInterval(() => {\n      const cs = getCanvasState();\n      if (cs.zoom !== lastZoom || cs.panX !== lastPanX || cs.panY !== lastPanY) {\n        lastZoom = cs.zoom;\n        lastPanX = cs.panX;\n        lastPanY = cs.panY;\n        refreshAllRemoteCursors();\n      }\n    }, 100);\n  }\n\n  function getGlobal(name) {\n    if (typeof window.__collabGetVar === 'function') return window.__collabGetVar(name);\n    return undefined;\n  }\n\n  function setGlobal(name, value) {\n    if (typeof window.__collabSetVar === 'function') return window.__collabSetVar(name, value);\n    return false;\n  }\n\n  function captureState() {\n    let state = null;\n\n    if (typeof captureTheQuickening === 'function') {\n      try {\n        state = captureTheQuickening();\n        delete state.canvas;\n        if (state.documentTabs) {\n          state.documentTabs = state.documentTabs.map(tab => {\n            const { pageState, ...rest } = tab;\n            return rest;\n          });\n        }\n      } catch (e) { console.error('captureTheQuickening error:', e); }\n    }\n\n    if (!state) {\n      const nodeData = getGlobal('NODE_DATA');\n      if (!nodeData) return null;\n      state = {\n        nodeData: nodeData,\n        edgeData: getGlobal('EDGE_DATA'),\n        rectData: getGlobal('RECT_DATA'),\n        textData: getGlobal('TEXT_DATA'),\n        imageData: getGlobal('IMAGE_DATA'),\n        nodePositions: getGlobal('savedPositions'),\n        nodeSizes: getGlobal('savedSizes'),\n        nodeStyles: getGlobal('savedStyles'),\n        edgeLegend: getGlobal('EDGE_LEGEND'),\n        zoneLegend: getGlobal('ZONE_LEGEND'),\n        zonePresets: getGlobal('ZONE_PRESETS'),\n        documentTabs: getGlobal('documentTabs'),\n        currentTabIndex: getGlobal('currentTabIndex'),\n        iconCache: getGlobal('iconCache'),\n        auditLog: getGlobal('auditLog'),\n        savedStyleSets: getGlobal('savedStyleSets'),\n        autoPingEnabled: getGlobal('autoPingEnabled'),\n        autoPingInterval: getGlobal('autoPingInterval'),\n        savedTopologyView: getGlobal('savedTopologyView'),\n        encryptedSections: getGlobal('encryptedSections')\n      };\n    }\n\n    state.animSettings = getGlobal('ANIM_SETTINGS');\n    state.rollbackVersions = getGlobal('rollbackVersions');\n    state.customLang = getGlobal('CUSTOM_LANG');\n\n    const pageState = getGlobal('PAGE_STATE');\n    if (pageState) {\n      state.themeState = {};\n      const themeKeys = ['panel','panelAlt','accent','danger','textMain','textSoft',\n        'background','canvasGrid','tagFill','tagText','tagBorder','sidebarBg',\n        'btnBg','btnText','inputBg','inputText','inputBorder','toolbarBg',\n        'toolbarBorder','toolbarText','toolbarBtnBg','toolbarBtnText'];\n      for (const k of themeKeys) {\n        if (pageState[k] !== undefined) state.themeState[k] = pageState[k];\n      }\n    }\n\n    if (window.COLLAB_DEBUG) {\n      console.log('[Collab] Captured state keys:', Object.keys(state));\n      console.log('[Collab] nodeStyles count:', state.nodeStyles ? Object.keys(state.nodeStyles).length : 0);\n      console.log('[Collab] savedStyleSets count:', state.savedStyleSets ? state.savedStyleSets.length : 0);\n    }\n\n    return state;\n  }\n\n  function hashState(state) {\n    const str = JSON.stringify(state);\n    let hash = 5381;\n    for (let i = 0; i < str.length; i++) {\n      hash = ((hash << 5) + hash) + str.charCodeAt(i);\n      hash = hash & hash;\n    }\n    return hash.toString(36);\n  }\n\n  function sendFullState() {\n    const state = captureState();\n    if (!state) return;\n    lastStateHash = hashState(state);\n    sendMessage('state', { state });\n  }\n\n  function applyRemoteState(state) {\n    if (!state) return;\n    syncPaused = true;\n    try {\n      if (window.COLLAB_DEBUG) {\n        console.log('[Collab] Applying remote state, keys:', Object.keys(state));\n        console.log('[Collab] Incoming nodeStyles:', state.nodeStyles ? Object.keys(state.nodeStyles).length : 'none');\n      }\n\n      const localTabIndex = getGlobal('currentTabIndex') || 0;\n      const senderTabIndex = state.currentTabIndex !== undefined ? state.currentTabIndex : 0;\n\n      if (state.documentTabs) {\n        const localTabs = getGlobal('documentTabs') || [];\n        const mergedTabs = state.documentTabs.map((remoteTab, i) => {\n          const localTab = localTabs[i];\n          return {\n            ...remoteTab,\n            pageState: localTab?.pageState || remoteTab.pageState || {}\n          };\n        });\n        setGlobal('documentTabs', mergedTabs);\n      }\n\n      if (state.zoneLegend) setGlobal('ZONE_LEGEND', state.zoneLegend);\n      if (state.zonePresets) setGlobal('ZONE_PRESETS', state.zonePresets);\n\n      const tabs = getGlobal('documentTabs') || [];\n      const myTab = tabs[localTabIndex];\n\n      if (localTabIndex === senderTabIndex) {\n        if (window.COLLAB_DEBUG) console.log('[Collab] Same tab, applying directly');\n        if (state.nodeData) setGlobal('NODE_DATA', state.nodeData);\n        if (state.edgeData) setGlobal('EDGE_DATA', state.edgeData);\n        if (state.rectData) setGlobal('RECT_DATA', state.rectData);\n        if (state.textData) setGlobal('TEXT_DATA', state.textData);\n        if (state.imageData) setGlobal('IMAGE_DATA', state.imageData);\n        if (state.nodePositions) setGlobal('savedPositions', state.nodePositions);\n        if (state.nodeSizes) setGlobal('savedSizes', state.nodeSizes);\n        if (state.nodeStyles) {\n          if (window.COLLAB_DEBUG) console.log('[Collab] Setting savedStyles from nodeStyles');\n          setGlobal('savedStyles', state.nodeStyles);\n        }\n        if (state.edgeLegend) setGlobal('EDGE_LEGEND', state.edgeLegend);\n      } else if (myTab) {\n        if (myTab.nodes) setGlobal('NODE_DATA', myTab.nodes);\n        if (myTab.edges) setGlobal('EDGE_DATA', myTab.edges);\n        if (myTab.positions) setGlobal('savedPositions', myTab.positions);\n        if (myTab.sizes) setGlobal('savedSizes', myTab.sizes);\n        if (myTab.styles) setGlobal('savedStyles', myTab.styles);\n        if (myTab.legend) setGlobal('EDGE_LEGEND', myTab.legend);\n        if (myTab.rects) setGlobal('RECT_DATA', myTab.rects);\n        if (myTab.texts) setGlobal('TEXT_DATA', myTab.texts);\n        if (myTab.images) setGlobal('IMAGE_DATA', myTab.images);\n      }\n\n      if (state.iconCache) setGlobal('iconCache', state.iconCache);\n      if (state.auditLog) setGlobal('auditLog', state.auditLog);\n      if (state.savedStyleSets) setGlobal('savedStyleSets', state.savedStyleSets);\n      if (state.autoPingEnabled !== undefined) setGlobal('autoPingEnabled', state.autoPingEnabled);\n      if (state.autoPingInterval !== undefined) setGlobal('autoPingInterval', state.autoPingInterval);\n      if (state.savedTopologyView) setGlobal('savedTopologyView', state.savedTopologyView);\n      if (state.animSettings) setGlobal('ANIM_SETTINGS', state.animSettings);\n      if (state.rollbackVersions) setGlobal('rollbackVersions', state.rollbackVersions);\n      if (state.customLang) setGlobal('CUSTOM_LANG', state.customLang);\n      if (state.encryptedSections) setGlobal('encryptedSections', state.encryptedSections);\n\n      if (typeof forgeTheTopology === 'function') {\n        try { forgeTheTopology(); } catch (e) {}\n      }\n      if (state.themeState) {\n        setGlobal('PAGE_STATE', state.themeState);\n        if (typeof wieldThePower === 'function') {\n          try { wieldThePower(); } catch (e) {}\n        }\n      }\n    } finally {\n      setTimeout(() => { syncPaused = false; }, 200);\n    }\n  }\n\n  const STATE_SYNC_MIN_INTERVAL = 500;\n  const STATE_SYNC_DEBOUNCE = 300;\n  let lastStateSyncTime = 0;\n  let stateSyncTimeout = null;\n\n  function syncStateIfChanged() {\n    const state = captureState();\n    if (!state) return;\n    const currentHash = hashState(state);\n    if (currentHash === lastStateHash) return;\n    lastStateHash = currentHash;\n    sendMessage('state', { state });\n    lastStateSyncTime = Date.now();\n  }\n\n  function startStatePolling() {\n    setInterval(() => {\n      if (syncPaused || !hasReceivedInitialState) return;\n\n      const now = Date.now();\n      const timeSinceLastSync = now - lastStateSyncTime;\n\n      if (timeSinceLastSync < STATE_SYNC_MIN_INTERVAL) {\n        if (!stateSyncTimeout) {\n          stateSyncTimeout = setTimeout(() => {\n            stateSyncTimeout = null;\n            syncStateIfChanged();\n          }, STATE_SYNC_DEBOUNCE);\n        }\n        return;\n      }\n\n      syncStateIfChanged();\n    }, 250);\n  }\n\n  function sendPresence() {\n    sendMessage('presence', {\n      userId: window.COLLAB_USER.id,\n      selectedNodes: window.COLLAB_USER.selectedNodes || [],\n      editingNode: window.COLLAB_USER.editingNode,\n      currentTab: getCurrentTabName()\n    });\n  }\n\n  function trackSelection() {\n    const map = document.getElementById('map');\n    if (!map) { setTimeout(trackSelection, 500); return; }\n\n    let lastTab = getCurrentTabName();\n    setInterval(() => {\n      const currentTab = getCurrentTabName();\n      if (currentTab !== lastTab) {\n        lastTab = currentTab;\n        sendPresence();\n        renderUsers();\n      }\n    }, 500);\n\n    const observer = new MutationObserver(() => {\n      const selected = [];\n      document.querySelectorAll('.node-group.selected, [data-id].selected').forEach(el => {\n        const id = el.dataset?.id || el.getAttribute('data-id');\n        if (id) selected.push(id);\n      });\n      if (JSON.stringify(selected) !== JSON.stringify(window.COLLAB_USER.selectedNodes)) {\n        window.COLLAB_USER.selectedNodes = selected;\n        sendPresence();\n        renderUsers();\n      }\n    });\n    observer.observe(map, { subtree: true, attributes: true, attributeFilter: ['class'] });\n  }\n\n  function setupAuditLogInjection() {\n    if (typeof window.__collabGetVar !== 'function') return;\n\n    if (typeof window.addAuditEntry === 'function') {\n      window.__collabOriginalAddAudit = window.addAuditEntry;\n      window.addAuditEntry = function(type, description, details) {\n        const entry = {\n          timestamp: Date.now(),\n          type: type,\n          description: description,\n          details: details || {},\n          tab: window.__collabGetVar('documentTabs')?.[window.__collabGetVar('currentTabIndex')]?.name || 'Main',\n          user: window.COLLAB_USER?.name || 'Unknown',\n          userColor: window.COLLAB_USER?.color || '#888'\n        };\n        const auditLog = window.__collabGetVar('auditLog') || [];\n        auditLog.unshift(entry);\n        if (auditLog.length > 1000) auditLog.pop();\n        window.__collabSetVar('auditLog', auditLog);\n        return entry;\n      };\n    }\n  }\n\n  function getCurrentTabName() {\n    try {\n      const tabs = getGlobal('documentTabs');\n      const idx = getGlobal('currentTabIndex') || 0;\n      if (tabs && tabs[idx]) return tabs[idx].name || 'Main';\n      return 'Main';\n    } catch { return 'Main'; }\n  }\n\n  function renderUsers() {\n    const container = document.querySelector('.collab-users');\n    if (!container) return;\n    const myTab = getCurrentTabName();\n    const allUsers = [window.COLLAB_USER, ...users.values()];\n    clearNode(container);\n    allUsers.forEach(user => {\n      const isMe = user.id === window.COLLAB_USER.id;\n      const tabName = isMe ? myTab : (user.currentTab || 'Main');\n      const safeColor = sanitizeColor(user.color);\n      const initials = getInitials(user.name);\n      container.appendChild(\n        h('div', {className: 'collab-user' + (isMe ? ' me' : ''), 'data-user-id': user.id},\n          h('div', {className: 'collab-user-avatar', style: 'background: ' + safeColor}, initials),\n          h('div', {className: 'collab-user-info'},\n            h('span', {className: 'collab-user-name'}, user.name),\n            user.editingNode ? h('span', {className: 'collab-user-editing'}, 'editing') : null,\n            h('span', {className: 'collab-user-tab'}, tabName)\n          )\n        )\n      );\n    });\n  }\n\n  function renderUserIndicators() {\n    document.querySelectorAll('.collab-node-indicator, .collab-selection-ring').forEach(el => el.remove());\n    users.forEach(user => {\n      if (!user.selectedNodes) return;\n      user.selectedNodes.forEach(nodeId => {\n        if (typeof nodeId !== 'string' || !/^[\\w-]+$/.test(nodeId)) return;\n        const nodeEl = document.querySelector(`[data-id=\"${nodeId}\"]`);\n        if (!nodeEl) return;\n        const ring = document.createElement('div');\n        ring.className = 'collab-selection-ring';\n        ring.style.borderColor = user.color;\n        ring.dataset.collabUserId = user.id;\n        const nodeGroup = nodeEl.closest('.node-group') || nodeEl;\n        nodeGroup.style.position = 'relative';\n        nodeGroup.appendChild(ring);\n        const label = document.createElement('div');\n        label.className = 'collab-node-indicator';\n        label.style.background = user.color;\n        label.style.color = '#fff';\n        label.textContent = user.name;\n        label.dataset.collabUserId = user.id;\n        nodeGroup.appendChild(label);\n      });\n    });\n  }\n\n  function removeUserIndicators(userId) {\n    document.querySelectorAll(`[data-collab-user-id=\"${userId}\"]`).forEach(el => el.remove());\n  }\n\n  function updateCharCount() {\n    const input = document.getElementById('collab-chat-input');\n    const counter = document.getElementById('collab-char-count');\n    if (!input || !counter) return;\n    const len = input.value.length;\n    const remaining = 500 - len;\n    if (remaining <= 50) {\n      counter.textContent = remaining;\n      counter.className = 'collab-chat-char-count' + (remaining <= 20 ? ' danger' : ' warning');\n    } else {\n      counter.textContent = '';\n      counter.className = 'collab-chat-char-count';\n    }\n  }\n\n  function toggleEmojiPicker() {\n    emojiPickerOpen = !emojiPickerOpen;\n    const picker = document.getElementById('collab-emoji-picker');\n    if (picker) picker.classList.toggle('active', emojiPickerOpen);\n  }\n\n  function insertEmoji(emoji) {\n    const input = document.getElementById('collab-chat-input');\n    if (!input) return;\n    const start = input.selectionStart || input.value.length;\n    input.value = input.value.substring(0, start) + emoji + input.value.substring(input.selectionEnd || start);\n    input.focus();\n    input.selectionStart = input.selectionEnd = start + emoji.length;\n    updateCharCount();\n    emojiPickerOpen = false;\n    const picker = document.getElementById('collab-emoji-picker');\n    if (picker) picker.classList.remove('active');\n  }\n\n  function updateExpiryCountdown() {\n    const el = document.getElementById('collab-expiry');\n    if (!el || !roomExpiryData) { if (el) el.style.display = 'none'; return; }\n    if (!roomExpiryData.destruct || roomExpiryData.destruct.mode === 'none') { el.style.display = 'none'; return; }\n\n    if (roomExpiryData.destruct.mode === 'empty') {\n      el.style.display = 'flex';\n      el.textContent = 'Destroys when empty';\n      return;\n    }\n\n    if (roomExpiryData.destruct.mode === 'time') {\n      const created = new Date(roomExpiryData.created).getTime();\n      const expiresAt = created + roomExpiryData.destruct.value;\n      const remaining = expiresAt - Date.now();\n      if (remaining <= 0) {\n        el.style.display = 'flex';\n        el.textContent = 'Expiring...';\n        return;\n      }\n      el.style.display = 'flex';\n      const hours = Math.floor(remaining / 3600000);\n      const mins = Math.floor((remaining % 3600000) / 60000);\n      if (hours > 24) el.textContent = Math.ceil(hours / 24) + 'd left';\n      else if (hours > 0) el.textContent = hours + 'h ' + mins + 'm left';\n      else el.textContent = mins + 'm left';\n    }\n  }\n\n  function injectCollabBar() {\n    document.body.classList.add('collab-active');\n\n    const bar = document.createElement('div');\n    bar.id = 'collab-bar';\n    _append(bar, [\n      h('div', {id: 'collab-conn-dot', className: 'collab-conn-status connected'}),\n      h('div', {className: 'collab-users'}),\n      h('div', {id: 'collab-expiry', className: 'collab-room-expiry', style: 'display:none'}),\n      h('div', {className: 'collab-actions'},\n        h('button', {className: 'collab-btn', id: 'collab-chat-btn'},\n          h('span', {className: 'collab-btn-icon'}, '\\u2709'),\n          h('span', null, 'Chat'),\n          h('span', {className: 'collab-chat-badge', id: 'collab-chat-badge'})\n        ),\n        h('button', {className: 'collab-btn', id: 'collab-share-btn', style: shareButtonEnabled ? '' : 'display:none'},\n          h('span', {className: 'collab-btn-icon'}, '+'),\n          h('span', null, 'Share')\n        ),\n        h('button', {className: 'collab-btn', id: 'collab-menu-btn'},\n          h('span', {className: 'collab-btn-icon'}, '=')\n        )\n      )\n    ]);\n    document.body.prepend(bar);\n\n    const reconnectBanner = document.createElement('div');\n    reconnectBanner.id = 'collab-reconnect-banner';\n    reconnectBanner.className = 'collab-reconnect-banner';\n    _append(reconnectBanner, [\n      h('span', null, 'Reconnecting...'),\n      h('button', {className: 'collab-reconnect-btn', id: 'collab-reconnect-btn'}, 'Reconnect')\n    ]);\n    document.body.appendChild(reconnectBanner);\n\n    document.getElementById('collab-reconnect-btn').addEventListener('click', () => {\n      reconnectAttempts = 0;\n      connect();\n    });\n\n    const chatPanel = document.createElement('div');\n    chatPanel.id = 'collab-chat-panel';\n    _append(chatPanel, [\n      h('div', {className: 'collab-chat-header'},\n        h('span', null, 'Chat'),\n        h('button', {className: 'collab-chat-close', id: 'collab-chat-close'}, '\\u00d7')\n      ),\n      h('div', {className: 'collab-chat-messages', id: 'collab-chat-messages'}),\n      h('div', {id: 'collab-typing', className: 'collab-typing-indicator'}),\n      h('div', {className: 'collab-chat-input-area', style: 'position:relative'},\n        h('div', {id: 'collab-reply-preview', className: 'collab-chat-reply-preview'},\n          h('span', null, ''),\n          h('button', {className: 'collab-chat-reply-cancel', id: 'collab-reply-cancel'}, '\\u00d7')\n        ),\n        h('div', {id: 'collab-emoji-picker', className: 'collab-emoji-picker'},\n          h('div', {className: 'collab-emoji-grid'},\n            EMOJI_LIST.map(e => h('button', {className: 'collab-emoji-btn', 'data-emoji': e}, e))\n          )\n        ),\n        h('div', {className: 'collab-chat-input-wrap'},\n          h('button', {className: 'collab-emoji-toggle', id: 'collab-emoji-toggle'}, '\\ud83d\\ude0a'),\n          h('div', {className: 'collab-chat-input-inner'},\n            h('input', {type: 'text', id: 'collab-chat-input', placeholder: 'Type a message...', maxLength: 500, autocomplete: 'off'}),\n            h('div', {className: 'collab-chat-char-count', id: 'collab-char-count'})\n          ),\n          h('button', {id: 'collab-chat-send'}, 'Send')\n        )\n      )\n    ]);\n    document.body.appendChild(chatPanel);\n\n    const shareModal = document.createElement('div');\n    shareModal.id = 'collab-share-modal';\n    shareModal.className = 'collab-modal-overlay';\n    shareModal.appendChild(\n      h('div', {className: 'collab-modal'},\n        h('div', {className: 'collab-modal-header'},\n          h('h3', null, 'Share Room'),\n          h('button', {className: 'collab-modal-close'}, '\\u00d7')\n        ),\n        h('div', {className: 'collab-modal-body'},\n          h('div', {className: 'collab-share-url'},\n            h('input', {type: 'text', readonly: true, value: window.location.href, id: 'collab-share-input'}),\n            h('button', {id: 'collab-copy-btn'}, 'Copy')\n          ),\n          h('div', {className: 'collab-qr', id: 'collab-qr'}),\n          h('p', {className: 'collab-share-note'}, HAS_PASSWORD ? 'Password protected. Share password separately.' : 'Anyone with this link can join.')\n        )\n      )\n    );\n    document.body.appendChild(shareModal);\n\n    const infoModal = document.createElement('div');\n    infoModal.id = 'collab-info-modal';\n    infoModal.className = 'collab-modal-overlay';\n    infoModal.appendChild(\n      h('div', {className: 'collab-modal'},\n        h('div', {className: 'collab-modal-header'},\n          h('h3', null, 'Room Info'),\n          h('button', {className: 'collab-modal-close'}, '\\u00d7')\n        ),\n        h('div', {className: 'collab-modal-body'},\n          h('div', {id: 'collab-info-content'})\n        )\n      )\n    );\n    document.body.appendChild(infoModal);\n\n    const menuDropdown = document.createElement('div');\n    menuDropdown.id = 'collab-menu-dropdown';\n    _append(menuDropdown, [\n      shareButtonEnabled ? h('button', {className: 'collab-menu-item', id: 'collab-menu-copy'}, 'Copy Link') : null,\n      h('button', {className: 'collab-menu-item', id: 'collab-menu-info'}, 'Room Info'),\n      h('button', {className: 'collab-menu-item', id: 'collab-menu-name'}, 'Change Name'),\n      h('button', {className: 'collab-menu-item', id: 'collab-menu-sound'}, chatSoundEnabled ? 'Mute Sounds' : 'Unmute Sounds'),\n      h('div', {className: 'collab-menu-divider'}),\n      h('button', {className: 'collab-menu-item', id: 'collab-menu-leave'}, 'Leave Room'),\n      IS_CREATOR ? h('button', {className: 'collab-menu-item danger', id: 'collab-menu-delete'}, 'Delete Room') : null\n    ]);\n    document.body.appendChild(menuDropdown);\n\n    document.getElementById('collab-chat-btn').addEventListener('click', () => {\n      chatOpen = !chatOpen;\n      chatPanel.classList.toggle('active', chatOpen);\n      if (chatOpen) {\n        unreadCount = 0;\n        updateChatBadge();\n        document.getElementById('collab-chat-input').focus();\n      }\n    });\n\n    document.getElementById('collab-chat-close').addEventListener('click', () => {\n      chatOpen = false;\n      chatPanel.classList.remove('active');\n      emojiPickerOpen = false;\n      const picker = document.getElementById('collab-emoji-picker');\n      if (picker) picker.classList.remove('active');\n    });\n\n    document.getElementById('collab-chat-send').addEventListener('click', () => {\n      const input = document.getElementById('collab-chat-input');\n      sendChatMessage(input.value);\n      input.value = '';\n      updateCharCount();\n    });\n\n    const chatInput = document.getElementById('collab-chat-input');\n    chatInput.addEventListener('keydown', (e) => {\n      if (e.key === 'Enter' && !e.shiftKey) {\n        e.preventDefault();\n        sendChatMessage(e.target.value);\n        e.target.value = '';\n        updateCharCount();\n      }\n    });\n\n    chatInput.addEventListener('input', () => {\n      updateCharCount();\n      sendTypingIndicator();\n    });\n\n    document.getElementById('collab-reply-cancel').addEventListener('click', clearReply);\n\n    document.getElementById('collab-emoji-toggle').addEventListener('click', (e) => {\n      e.stopPropagation();\n      toggleEmojiPicker();\n    });\n\n    document.getElementById('collab-emoji-picker').addEventListener('click', (e) => {\n      e.stopPropagation();\n      const emoji = e.target.dataset?.emoji;\n      if (emoji) insertEmoji(emoji);\n    });\n\n    document.getElementById('collab-share-btn').addEventListener('click', () => {\n      shareModal.classList.add('active');\n      generateQR();\n    });\n\n    shareModal.querySelector('.collab-modal-close').addEventListener('click', () => shareModal.classList.remove('active'));\n    shareModal.addEventListener('click', (e) => { if (e.target === shareModal) shareModal.classList.remove('active'); });\n    infoModal.querySelector('.collab-modal-close').addEventListener('click', () => infoModal.classList.remove('active'));\n    infoModal.addEventListener('click', (e) => { if (e.target === infoModal) infoModal.classList.remove('active'); });\n\n    document.getElementById('collab-copy-btn').addEventListener('click', () => {\n      navigator.clipboard.writeText(document.getElementById('collab-share-input').value);\n      const btn = document.getElementById('collab-copy-btn');\n      btn.textContent = 'OK';\n      setTimeout(() => { btn.textContent = 'Copy'; }, 2000);\n    });\n\n    const menuBtn = document.getElementById('collab-menu-btn');\n    menuBtn.addEventListener('click', (e) => {\n      e.stopPropagation();\n      const rect = menuBtn.getBoundingClientRect();\n      menuDropdown.style.top = `${rect.bottom + 8}px`;\n      menuDropdown.style.right = `${window.innerWidth - rect.right}px`;\n      menuDropdown.classList.toggle('active');\n    });\n    document.addEventListener('click', () => {\n      menuDropdown.classList.remove('active');\n      if (emojiPickerOpen) {\n        emojiPickerOpen = false;\n        const picker = document.getElementById('collab-emoji-picker');\n        if (picker) picker.classList.remove('active');\n      }\n    });\n\n    const copyBtn = document.getElementById('collab-menu-copy');\n    if (copyBtn) {\n      copyBtn.addEventListener('click', () => {\n        navigator.clipboard.writeText(window.location.href);\n        showToast('Link copied');\n      });\n    }\n\n    document.getElementById('collab-menu-sound').addEventListener('click', () => {\n      chatSoundEnabled = !chatSoundEnabled;\n      localStorage.setItem('collab-chat-sound', chatSoundEnabled);\n      document.getElementById('collab-menu-sound').textContent = chatSoundEnabled ? 'Mute Sounds' : 'Unmute Sounds';\n      showToast(chatSoundEnabled ? 'Sound notifications on' : 'Sound notifications off');\n    });\n\n    document.getElementById('collab-menu-info').addEventListener('click', async () => {\n      try {\n        const res = await fetch(`/api/room/${ROOM_ID}/exists`);\n        const data = await res.json();\n        roomExpiryData = data;\n        updateExpiryCountdown();\n        let destructText = 'Never';\n        if (data.destruct) {\n          if (data.destruct.mode === 'time') {\n            const hours = data.destruct.value / 3600000;\n            if (hours < 1) destructText = `${Math.round(hours * 60)} minutes after last activity`;\n            else if (hours < 24) destructText = `${hours} hours after last activity`;\n            else destructText = `${Math.round(hours / 24)} days after last activity`;\n          } else if (data.destruct.mode === 'empty') {\n            destructText = 'When everyone leaves';\n          }\n        }\n        function infoRow(label, value, extraClass) {\n          return h('div', {className: 'collab-info-row'},\n            h('span', {className: 'collab-info-label'}, label),\n            h('span', {className: 'collab-info-value' + (extraClass ? ' ' + extraClass : '')}, value)\n          );\n        }\n        setContent(document.getElementById('collab-info-content'), [\n          infoRow('Room ID', ROOM_ID, 'collab-info-id'),\n          infoRow('Created', new Date(data.created).toLocaleString()),\n          infoRow('Self Destruct', destructText),\n          infoRow('Password', data.hasPassword ? 'Yes' : 'No'),\n          infoRow('Connected', (users.size + 1) + ' users'),\n          infoRow('You are', IS_CREATOR ? 'Room Creator' : 'Participant')\n        ]);\n        infoModal.classList.add('active');\n      } catch {\n        setContent(document.getElementById('collab-info-content'), h('p', null, 'Failed to load room info'));\n        infoModal.classList.add('active');\n      }\n    });\n\n    document.getElementById('collab-menu-name').addEventListener('click', () => showNameModal(true));\n    document.getElementById('collab-menu-leave').addEventListener('click', () => {\n      if (confirm('Leave this room? You will need to enter your name again to rejoin.')) {\n        leaveRoom();\n      }\n    });\n\n    const deleteBtn = document.getElementById('collab-menu-delete');\n    if (deleteBtn) {\n      deleteBtn.addEventListener('click', async () => {\n        if (!confirm('Delete this room permanently?')) return;\n        try {\n          const res = await fetch(`/api/room/${ROOM_ID}`, {\n            method: 'DELETE',\n            headers: { 'Content-Type': 'application/json', 'x-csrf-token': window.CSRF_TOKEN || '' },\n            body: JSON.stringify({ creatorId: localStorage.getItem('collab-user-' + ROOM_ID) })\n          });\n          refreshCsrf();\n          if (res.ok) window.location.href = '/';\n          else showToast((await res.json()).error || 'Failed to delete');\n        } catch { showToast('Failed to delete room'); }\n      });\n    }\n\n    document.addEventListener('keydown', (e) => {\n      if (e.key === 'Escape') {\n        if (chatOpen) {\n          chatOpen = false;\n          chatPanel.classList.remove('active');\n          emojiPickerOpen = false;\n          const picker = document.getElementById('collab-emoji-picker');\n          if (picker) picker.classList.remove('active');\n        }\n        shareModal.classList.remove('active');\n        infoModal.classList.remove('active');\n        menuDropdown.classList.remove('active');\n      }\n    });\n\n    renderUsers();\n\n    fetch(`/api/room/${ROOM_ID}/exists`).then(r => r.json()).then(data => {\n      roomExpiryData = data;\n      updateExpiryCountdown();\n    }).catch(() => {});\n    expiryInterval = setInterval(updateExpiryCountdown, 30000);\n  }\n\n  function generateQR() {\n    const container = document.getElementById('collab-qr');\n    const url = window.location.href;\n\n    if (typeof qrcode === 'undefined') {\n      const script = document.createElement('script');\n      script.src = '/qrcode.min.js';\n      script.onload = () => renderQR(container, url);\n      script.onerror = () => { setContent(container, h('p', {style: 'color:#888;font-size:12px'}, 'QR unavailable')); };\n      document.head.appendChild(script);\n    } else {\n      renderQR(container, url);\n    }\n  }\n\n  function renderQR(container, url) {\n    try {\n      const qr = qrcode(0, 'M');\n      qr.addData(url);\n      qr.make();\n      const svgHtml = qr.createSvgTag({ cellSize: 4, margin: 4 });\n      const parsed = new DOMParser().parseFromString(svgHtml, 'image/svg+xml');\n      clearNode(container);\n      container.appendChild(document.importNode(parsed.documentElement, true));\n    } catch {\n      setContent(container, h('p', {style: 'color:#888;font-size:12px'}, 'QR unavailable'));\n    }\n  }\n\n  function showNameModal(isChange = false, errorMsg = null) {\n    const existing = document.getElementById('collab-name-modal');\n    if (existing) existing.remove();\n\n    const modal = document.createElement('div');\n    modal.id = 'collab-name-modal';\n    modal.className = 'collab-modal-overlay active';\n    modal.appendChild(\n      h('div', {className: 'collab-modal'},\n        h('div', {className: 'collab-modal-header'},\n          h('h3', null, isChange ? 'Change Name' : 'Enter Your Name'),\n          isChange ? h('button', {className: 'collab-modal-close'}, '\\u00d7') : null\n        ),\n        h('div', {className: 'collab-modal-body'},\n          h('input', {type: 'text', id: 'collab-name-input', className: 'collab-input', placeholder: 'Your name', maxLength: 30}),\n          h('div', {className: 'collab-name-error', id: 'collab-name-error'}, errorMsg || ''),\n          h('div', {className: 'collab-name-actions'},\n            h('button', {id: 'collab-name-random', className: 'collab-btn-secondary'}, 'Random'),\n            h('button', {id: 'collab-name-submit', className: 'collab-btn-primary'}, isChange ? 'Update' : 'Join')\n          )\n        )\n      )\n    );\n    document.body.appendChild(modal);\n\n    const errorEl = document.getElementById('collab-name-error');\n    if (errorMsg) errorEl.classList.add('active');\n\n    const input = document.getElementById('collab-name-input');\n    if (isChange && window.COLLAB_USER.name) input.value = window.COLLAB_USER.name;\n    input.focus();\n\n    input.addEventListener('input', () => {\n      errorEl.classList.remove('active');\n    });\n\n    document.getElementById('collab-name-random').addEventListener('click', () => {\n      input.value = generateHighlanderName();\n      errorEl.classList.remove('active');\n    });\n\n    document.getElementById('collab-name-submit').addEventListener('click', () => {\n      const name = input.value.trim() || generateHighlanderName();\n      setStoredUserName(name);\n      window.COLLAB_USER.name = name;\n      modal.remove();\n      if (isChange || nameRejectedRecovery) {\n        nameRejectedRecovery = false;\n        sendMessage('join', { user: window.COLLAB_USER });\n        renderUsers();\n      } else {\n        startCollab();\n      }\n    });\n\n    input.addEventListener('keypress', (e) => {\n      if (e.key === 'Enter') document.getElementById('collab-name-submit').click();\n    });\n\n    const closeBtn = modal.querySelector('.collab-modal-close');\n    if (closeBtn) closeBtn.addEventListener('click', () => modal.remove());\n  }\n\n  async function checkPassword() {\n    if (!HAS_PASSWORD) return true;\n    try {\n      const res = await fetch(`/api/room/${ROOM_ID}/access`, { credentials: 'include' });\n      if (res.ok && (await res.json()).authorized) return true;\n    } catch {}\n    return new Promise((resolve) => {\n      const modal = document.createElement('div');\n      modal.id = 'collab-password-modal';\n      modal.className = 'collab-modal-overlay active';\n      modal.appendChild(\n        h('div', {className: 'collab-modal'},\n          h('div', {className: 'collab-modal-header'},\n            h('h3', null, 'Password Required')\n          ),\n          h('div', {className: 'collab-modal-body'},\n            h('input', {type: 'password', id: 'collab-pwd-input', className: 'collab-input', placeholder: 'Room password'}),\n            h('div', {className: 'collab-pwd-error', id: 'collab-pwd-error'}, 'Invalid password'),\n            h('button', {id: 'collab-pwd-submit', className: 'collab-btn-primary', style: 'width:100%;margin-top:12px'}, 'Enter')\n          )\n        )\n      );\n      document.body.appendChild(modal);\n      const input = document.getElementById('collab-pwd-input');\n      const error = document.getElementById('collab-pwd-error');\n      input.focus();\n      async function tryPwd() {\n        const res = await fetch(`/api/room/${ROOM_ID}/verify`, {\n          method: 'POST',\n          headers: { 'Content-Type': 'application/json' },\n          credentials: 'include',\n          body: JSON.stringify({ password: input.value })\n        });\n        if ((await res.json()).valid) {\n          modal.remove();\n          resolve(true);\n        } else {\n          error.classList.add('active');\n          input.value = '';\n          input.focus();\n        }\n      }\n      document.getElementById('collab-pwd-submit').addEventListener('click', tryPwd);\n      input.addEventListener('keypress', (e) => { if (e.key === 'Enter') tryPwd(); });\n    });\n  }\n\n  async function init() {\n    const authorized = await checkPassword();\n    if (!authorized) { window.location.href = '/'; return; }\n\n    try {\n      const themeRes = await fetch('/api/theme');\n      if (themeRes.ok) {\n        const themeData = await themeRes.json();\n        if (themeData.shareButtonEnabled !== undefined) {\n          shareButtonEnabled = themeData.shareButtonEnabled;\n        }\n      }\n    } catch (e) {\n      console.error('[Collab] Failed to fetch theme settings:', e);\n    }\n\n    function waitForApp() {\n      const hasForge = typeof forgeTheTopology === 'function';\n      const hasHelper = typeof window.__collabGetVar === 'function';\n      const hasNodeData = hasHelper ? window.__collabGetVar('NODE_DATA') !== undefined : false;\n      if (hasForge && hasNodeData) {\n        const storedName = getStoredUserName();\n        if (storedName) {\n          window.COLLAB_USER.name = storedName;\n          startCollab();\n        } else {\n          if (window.DEFAULT_ROOM_THEME) {\n            var drt = typeof THEME_PRESETS !== 'undefined' ? THEME_PRESETS : null;\n            if (drt && drt[window.DEFAULT_ROOM_THEME]) {\n              setGlobal('PAGE_STATE', drt[window.DEFAULT_ROOM_THEME]);\n              if (typeof wieldThePower === 'function') try { wieldThePower(); } catch(e) {}\n              var tsel = document.getElementById('welcome-theme-select');\n              if (tsel) tsel.value = window.DEFAULT_ROOM_THEME;\n            }\n          }\n          showNameModal(false);\n        }\n      } else {\n        setTimeout(waitForApp, 200);\n      }\n    }\n    waitForApp();\n  }\n\n  function stripCollabFromHTML(htmlString) {\n    const parser = new DOMParser();\n    const doc = parser.parseFromString(htmlString, 'text/html');\n\n    const collabElements = [\n      '#collab-bar',\n      '#collab-share-modal',\n      '#collab-info-modal',\n      '#collab-menu-dropdown',\n      '#collab-name-modal',\n      '#collab-password-modal',\n      '#collab-sync-overlay',\n      '#collab-chat-panel',\n      '#collab-reconnect-banner',\n      '#collab-toast-stack',\n      '.collab-node-indicator',\n      '.collab-selection-ring',\n      '.collab-remote-cursor',\n      '#verse-discovery-modal',\n      '#verse-disc-edit-modal',\n      '#verse-discover-btn',\n      '.verse-probe-ui'\n    ];\n    collabElements.forEach(sel => {\n      doc.querySelectorAll(sel).forEach(el => el.remove());\n    });\n\n    const body = doc.querySelector('body');\n    if (body) {\n      body.classList.remove('collab-active');\n    }\n\n    doc.querySelectorAll('script[src*=\"collab\"], link[href*=\"collab.css\"], #room-config').forEach(el => el.remove());\n\n    var topoState = doc.querySelector('#topology-state');\n    if (topoState && topoState.textContent) {\n      try {\n        var topo = JSON.parse(topoState.textContent);\n        function scrubProbeResults(nodeData) {\n          if (!nodeData || typeof nodeData !== 'object') return;\n          Object.keys(nodeData).forEach(function(nid) {\n            if (nodeData[nid] && nodeData[nid].ping) {\n              delete nodeData[nid].ping.probeResults;\n            }\n          });\n        }\n        if (topo.nodeData) scrubProbeResults(topo.nodeData);\n        if (topo.documentTabs && Array.isArray(topo.documentTabs)) {\n          topo.documentTabs.forEach(function(tab) {\n            if (tab && tab.nodes) scrubProbeResults(tab.nodes);\n          });\n        }\n        topoState.textContent = JSON.stringify(topo);\n      } catch(e) {}\n    }\n\n    return '<!DOCTYPE html>\\n' + doc.documentElement.outerHTML;\n  }\n\n  function hookSaveFunction() {\n    window.__collabStripHTML = stripCollabFromHTML;\n  }\n\n  function buildProbeList(ping) {\n    if (ping.probeTypes && Array.isArray(ping.probeTypes) && ping.probeTypes.length > 0) {\n      return ping.probeTypes;\n    }\n    var probes = [{ type: 'icmp' }];\n    if (ping.protocol === 'custom' && ping.customUrl) {\n      probes.push({ type: 'http', url: ping.customUrl });\n    } else if (ping.protocol === 'https') {\n      probes.push({ type: 'http' });\n    } else {\n      probes.push({ type: 'http' });\n    }\n    return probes;\n  }\n\n  function updateProbeResultsPanel(nodeId) {\n    var panel = document.getElementById('verse-probe-results');\n    if (!panel) return;\n    var nd = getGlobal('NODE_DATA');\n    var data = nd[nodeId];\n    if (!data || !data.ping || !data.ping.probeResults || data.ping.probeResults.length === 0) {\n      panel.innerHTML = '';\n      return;\n    }\n    var html = '';\n    data.ping.probeResults.forEach(function(r) {\n      var color = r.status === 'online' ? 'var(--accent)' : r.status === 'offline' ? 'var(--danger)' : 'var(--text-soft)';\n      var label = r.type.toUpperCase();\n      if (r.port) label += ':' + r.port;\n      var detail = '';\n      if (r.responseTime !== null) detail = r.responseTime + 'ms';\n      if (r.detail) detail += (detail ? ' ' : '') + r.detail;\n      html += '<div style=\"display:flex;justify-content:space-between;align-items:center;padding:3px 0;font-size:12px\">';\n      html += '<span style=\"color:var(--text-soft)\">' + label + '</span>';\n      html += '<span style=\"color:' + color + '\">● ' + (detail || r.status) + '</span>';\n      html += '</div>';\n    });\n    panel.innerHTML = html;\n  }\n\n  function overridePingFunctions() {\n    if (!window.COLLAB_MODE || !_rc.probeEnabled) return;\n\n    window.checkNodeStatus = async function(nodeId) {\n      var nd = getGlobal('NODE_DATA');\n      var data = nd[nodeId];\n      if (!data || !data.ping || !data.ping.enabled) return;\n\n      data.ping.status = 'checking';\n      data.ping.lastCheck = new Date().toISOString();\n      data.ping.responseTime = null;\n      var updateIndicator = window.__collabGetVar('updatePingIndicator');\n      var updateDisplay = window.__collabGetVar('updatePingStatusDisplay');\n      if (updateIndicator) updateIndicator(nodeId);\n      var curId = window.__collabGetVar('currentNodeId');\n      if (curId === nodeId && updateDisplay) updateDisplay(nodeId);\n\n      var target = data.ip || '';\n      if (!target && data.ping.protocol === 'custom') target = data.ping.customUrl;\n      if (!target) { data.ping.status = 'unknown'; return; }\n\n      var probes = buildProbeList(data.ping);\n\n      try {\n        var resp = await fetch('/api/probe', {\n          method: 'POST',\n          headers: { 'Content-Type': 'application/json', 'x-csrf-token': window.CSRF_TOKEN },\n          body: JSON.stringify({ target: target, probes: probes, timeout: data.ping.timeout || 3000 })\n        });\n        refreshCsrf();\n        var result = await resp.json();\n        if (resp.ok) {\n          data.ping.status = result.status;\n          data.ping.responseTime = result.rtt || null;\n          data.ping.probeResults = result.results || [];\n        } else {\n          data.ping.status = 'offline';\n          data.ping.responseTime = null;\n        }\n      } catch(e) {\n        data.ping.status = 'offline';\n        data.ping.responseTime = null;\n      }\n\n      data.ping.lastCheck = new Date().toISOString();\n      if (updateIndicator) updateIndicator(nodeId);\n      curId = window.__collabGetVar('currentNodeId');\n      if (curId === nodeId && updateDisplay) updateDisplay(nodeId);\n      updateProbeResultsPanel(nodeId);\n    };\n\n    window.checkAllNodesStatus = async function() {\n      var nd = getGlobal('NODE_DATA');\n      var targets = [];\n      Object.keys(nd).forEach(function(nid) {\n        if (nd[nid] && nd[nid].ping && nd[nid].ping.enabled) {\n          var t = nd[nid].ip || '';\n          if (!t && nd[nid].ping.protocol === 'custom') t = nd[nid].ping.customUrl;\n          if (t) targets.push({ nodeId: nid, target: t, probes: buildProbeList(nd[nid].ping), timeout: nd[nid].ping.timeout || 3000 });\n        }\n      });\n      if (!targets.length) return;\n      var btn = document.getElementById('check-all-ping-btn');\n      if (btn) {\n        btn.disabled = true;\n        btn.textContent = 'Checking...';\n        btn.style.opacity = '0.7';\n      }\n      var checked = 0;\n      try {\n        for (var i = 0; i < targets.length; i += 50) {\n          var batch = targets.slice(i, i + 50);\n          try {\n            var resp = await fetch('/api/probe/batch', {\n              method: 'POST',\n              headers: { 'Content-Type': 'application/json', 'x-csrf-token': window.CSRF_TOKEN },\n              body: JSON.stringify({ targets: batch })\n            });\n            refreshCsrf();\n            if (!resp.ok) { checked += batch.length; continue; }\n            var result = await resp.json();\n            var updateIndicator = window.__collabGetVar('updatePingIndicator');\n            Object.keys(result.results).forEach(function(nid) {\n              var r = result.results[nid];\n              if (!nd[nid] || !nd[nid].ping) return;\n              nd[nid].ping.status = r.status;\n              nd[nid].ping.responseTime = r.rtt || null;\n              nd[nid].ping.probeResults = r.results || [];\n              nd[nid].ping.lastCheck = new Date().toISOString();\n              if (updateIndicator) updateIndicator(nid);\n            });\n            checked += batch.length;\n            if (btn) btn.textContent = 'Checking ' + Math.min(checked, targets.length) + '/' + targets.length;\n          } catch(e) { checked += batch.length; }\n        }\n      } finally {\n        if (btn) {\n          btn.disabled = false;\n          btn.textContent = 'Check Pings';\n          btn.style.opacity = '';\n        }\n      }\n    };\n\n    var checkAllBtn = document.getElementById('check-all-ping-btn');\n    if (checkAllBtn) {\n      var newBtn = checkAllBtn.cloneNode(true);\n      checkAllBtn.parentNode.replaceChild(newBtn, checkAllBtn);\n      newBtn.addEventListener('click', window.checkAllNodesStatus);\n    }\n  }\n\n  function injectProbeUI() {\n    if (!_rc.probeEnabled) return;\n\n    var observer = new MutationObserver(function() {\n      var pingSection = document.getElementById('node-ping-options');\n      if (!pingSection || document.getElementById('verse-probe-type')) return;\n\n      var protocolRow = document.getElementById('node-ping-protocol');\n      if (!protocolRow) return;\n      var insertAfter = protocolRow.closest('.style-row') || protocolRow.parentElement;\n\n      var probeRow = document.createElement('div');\n      probeRow.className = 'style-row verse-probe-ui';\n      probeRow.style.marginTop = '8px';\n      probeRow.innerHTML = '<label style=\"font-size:13px;color:var(--text-soft)\">Probe Type:</label>' +\n        '<select id=\"verse-probe-type\" style=\"flex:1\">' +\n        '<option value=\"auto\">Auto (ICMP + HTTP)</option>' +\n        '<option value=\"icmp\">ICMP Ping Only</option>' +\n        '<option value=\"tcp\">TCP Port Check</option>' +\n        '<option value=\"http\">HTTP/HTTPS Only</option>' +\n        '<option value=\"multi\">Multi Probe</option>' +\n        '</select>';\n      insertAfter.parentNode.insertBefore(probeRow, insertAfter.nextSibling);\n\n      var portsRow = document.createElement('div');\n      portsRow.className = 'verse-probe-ui';\n      portsRow.id = 'verse-tcp-ports-row';\n      portsRow.style.cssText = 'display:none;margin-top:8px';\n      portsRow.innerHTML = '<label style=\"display:block;margin-bottom:4px;font-size:13px;color:var(--text-soft)\">TCP Ports (comma separated):</label>' +\n        '<input type=\"text\" id=\"verse-tcp-ports\" placeholder=\"22, 80, 443, 3389\" style=\"width:100%\">';\n      probeRow.parentNode.insertBefore(portsRow, probeRow.nextSibling);\n\n      var resultsPanel = document.createElement('div');\n      resultsPanel.className = 'verse-probe-ui';\n      resultsPanel.id = 'verse-probe-results';\n      resultsPanel.style.cssText = 'margin-top:8px;padding:8px;background:var(--panel);border-radius:6px;border:1px solid var(--edge-main)';\n      portsRow.parentNode.insertBefore(resultsPanel, portsRow.nextSibling);\n\n      var probeSelect = document.getElementById('verse-probe-type');\n      probeSelect.addEventListener('change', function() {\n        var val = probeSelect.value;\n        document.getElementById('verse-tcp-ports-row').style.display = (val === 'tcp' || val === 'multi') ? 'block' : 'none';\n        saveProbeConfig();\n      });\n\n      document.getElementById('verse-tcp-ports').addEventListener('change', function() {\n        saveProbeConfig();\n      });\n    });\n    observer.observe(document.body, { childList: true, subtree: true, attributes: true });\n\n    var origNodePingable = null;\n    document.addEventListener('change', function(e) {\n      if (e.target && e.target.id === 'node-pingable') {\n        setTimeout(loadProbeConfig, 50);\n      }\n    });\n\n    var lastSelectedNode = null;\n    setInterval(function() {\n      var curId = window.__collabGetVar('currentNodeId');\n      if (curId !== lastSelectedNode) {\n        lastSelectedNode = curId;\n        if (curId) setTimeout(function() { loadProbeConfig(); updateProbeResultsPanel(curId); }, 100);\n      }\n    }, 200);\n  }\n\n  function loadProbeConfig() {\n    var curId = window.__collabGetVar('currentNodeId');\n    if (!curId) return;\n    var nd = getGlobal('NODE_DATA');\n    var data = nd[curId];\n    if (!data || !data.ping) return;\n\n    var probeSelect = document.getElementById('verse-probe-type');\n    var portsInput = document.getElementById('verse-tcp-ports');\n    if (!probeSelect) return;\n\n    if (data.ping.probeTypes && data.ping.probeTypes.length > 0) {\n      var types = data.ping.probeTypes.map(function(p) { return p.type; });\n      var hasTcp = types.indexOf('tcp') !== -1;\n      var hasIcmp = types.indexOf('icmp') !== -1;\n      var hasHttp = types.indexOf('http') !== -1;\n\n      if (hasTcp && hasIcmp && hasHttp) probeSelect.value = 'multi';\n      else if (hasTcp && !hasHttp) probeSelect.value = 'tcp';\n      else if (hasIcmp && !hasHttp && !hasTcp) probeSelect.value = 'icmp';\n      else if (hasHttp && !hasTcp) probeSelect.value = 'http';\n      else probeSelect.value = 'auto';\n\n      if (hasTcp && portsInput) {\n        var ports = data.ping.probeTypes.filter(function(p) { return p.type === 'tcp'; }).map(function(p) { return p.port; });\n        portsInput.value = ports.join(', ');\n      }\n    } else {\n      probeSelect.value = 'auto';\n      if (portsInput) portsInput.value = '';\n    }\n\n    var portsRow = document.getElementById('verse-tcp-ports-row');\n    if (portsRow) portsRow.style.display = (probeSelect.value === 'tcp' || probeSelect.value === 'multi') ? 'block' : 'none';\n  }\n\n  function saveProbeConfig() {\n    var curId = window.__collabGetVar('currentNodeId');\n    if (!curId) return;\n    var nd = getGlobal('NODE_DATA');\n    var data = nd[curId];\n    if (!data || !data.ping) return;\n\n    var probeSelect = document.getElementById('verse-probe-type');\n    var portsInput = document.getElementById('verse-tcp-ports');\n    if (!probeSelect) return;\n\n    var val = probeSelect.value;\n    var probes = [];\n\n    if (val === 'auto') {\n      probes = [];\n    } else if (val === 'icmp') {\n      probes = [{ type: 'icmp' }];\n    } else if (val === 'tcp') {\n      probes = [{ type: 'icmp' }];\n      var ports = parsePorts(portsInput ? portsInput.value : '');\n      ports.forEach(function(p) { probes.push({ type: 'tcp', port: p }); });\n    } else if (val === 'http') {\n      probes = [{ type: 'http' }];\n    } else if (val === 'multi') {\n      probes = [{ type: 'icmp' }, { type: 'http' }];\n      var ports2 = parsePorts(portsInput ? portsInput.value : '');\n      ports2.forEach(function(p) { probes.push({ type: 'tcp', port: p }); });\n      probes.push({ type: 'dns' });\n    }\n\n    data.ping.probeTypes = probes.length > 0 ? probes : undefined;\n  }\n\n  function parsePorts(str) {\n    if (!str) return [];\n    return str.split(',').map(function(s) { return parseInt(s.trim(), 10); }).filter(function(p) { return p >= 1 && p <= 65535; });\n  }\n\n  var discoveryResults = [];\n  var discoveryTaskId = null;\n  var discoveryScanning = false;\n  var discoveryOverrides = {};\n  var _renderEditModal = null;\n\n  var DISC_SHAPES = {\n    basic: ['circle','square','rectangle','triangle','hexagon','diamond','star','stop-sign','octagon','pentagon','cross','rounded-square','pill','parallelogram','trapezoid'],\n    computers: ['server','pc','laptop','phone','printer','pi','sensor'],\n    network: ['router','switch','firewall','access-point','load-balancer','gateway','vpn','nas'],\n    cloud: ['cloud','database','docker','container','vm','kubernetes','api','queue','lambda','bucket'],\n    security: ['shield','camera','monitor'],\n    smarthome: ['thermostat','doorbell','smart-lock','smart-bulb','smart-plug','smart-speaker','smart-tv','hub','smoke-detector','motion-sensor','garage','sprinkler','vacuum'],\n    sports: ['basketball-ball','football-ball','soccer-ball','hockey-puck','baseball-ball','tennis-ball','volleyball','rugby-ball','golf-ball','frisbee','cricket-ball','lacrosse-stick','golf-flag','tactical-x','tactical-o','tactical-star'],\n    rack: ['patch-panel','ups','pdu','rack-shelf','blank-panel','cable-management','kvm']\n  };\n\n  var SHAPE_PREVIEW_SVGS = {\n    'circle': '<circle cx=\"16\" cy=\"16\" r=\"11\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"/>',\n    'square': '<rect x=\"5\" y=\"5\" width=\"22\" height=\"22\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"/>',\n    'rectangle': '<rect x=\"3\" y=\"8\" width=\"26\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"/>',\n    'triangle': '<polygon points=\"16,4 28,28 4,28\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"/>',\n    'hexagon': '<polygon points=\"16,3 27,9 27,22 16,28 5,22 5,9\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"/>',\n    'diamond': '<polygon points=\"16,3 29,16 16,29 3,16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"/>',\n    'star': '<polygon points=\"16,3 19,12 29,12 21,18 24,28 16,22 8,28 11,18 3,12 13,12\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"/>',\n    'stop-sign': '<polygon points=\"11,3 21,3 29,11 29,21 21,29 11,29 3,21 3,11\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"/>',\n    'octagon': '<polygon points=\"11,3 21,3 29,11 29,21 21,29 11,29 3,21 3,11\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"/>',\n    'pentagon': '<polygon points=\"16,3 29,13 24,28 8,28 3,13\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"/>',\n    'cross': '<path d=\"M12,4 h8 v8 h8 v8 h-8 v8 h-8 v-8 h-8 v-8 h8 z\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"/>',\n    'rounded-square': '<rect x=\"5\" y=\"5\" width=\"22\" height=\"22\" rx=\"5\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"/>',\n    'pill': '<rect x=\"4\" y=\"10\" width=\"24\" height=\"12\" rx=\"6\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"/>',\n    'parallelogram': '<polygon points=\"8,6 28,6 24,26 4,26\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"/>',\n    'trapezoid': '<polygon points=\"8,6 24,6 28,26 4,26\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"/>',\n    'server': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"6\" y=\"4\" width=\"20\" height=\"24\" rx=\"2\"/><line x1=\"6\" y1=\"12\" x2=\"26\" y2=\"12\"/><line x1=\"6\" y1=\"20\" x2=\"26\" y2=\"20\"/><circle cx=\"22\" cy=\"8\" r=\"1.5\" fill=\"currentColor\"/><circle cx=\"22\" cy=\"16\" r=\"1.5\" fill=\"currentColor\"/><circle cx=\"22\" cy=\"24\" r=\"1.5\" fill=\"currentColor\"/></g>',\n    'pc': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"5\" y=\"4\" width=\"22\" height=\"16\" rx=\"1\"/><line x1=\"12\" y1=\"24\" x2=\"20\" y2=\"24\"/><line x1=\"16\" y1=\"20\" x2=\"16\" y2=\"24\"/></g>',\n    'laptop': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"6\" y=\"6\" width=\"20\" height=\"14\" rx=\"1\"/><path d=\"M3,22 h26 l-2,4 h-22 z\"/></g>',\n    'phone': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"9\" y=\"3\" width=\"14\" height=\"26\" rx=\"2\"/><line x1=\"13\" y1=\"25\" x2=\"19\" y2=\"25\"/></g>',\n    'printer': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"8\" y=\"12\" width=\"16\" height=\"10\" rx=\"1\"/><rect x=\"10\" y=\"4\" width=\"12\" height=\"8\"/><rect x=\"10\" y=\"22\" width=\"12\" height=\"6\"/></g>',\n    'pi': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"5\" y=\"8\" width=\"22\" height=\"16\" rx=\"2\"/><circle cx=\"10\" cy=\"13\" r=\"1.5\" fill=\"currentColor\"/><rect x=\"18\" y=\"11\" width=\"6\" height=\"4\" rx=\"0.5\"/><line x1=\"14\" y1=\"20\" x2=\"22\" y2=\"20\"/></g>',\n    'sensor': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><circle cx=\"16\" cy=\"16\" r=\"6\"/><circle cx=\"16\" cy=\"16\" r=\"2\" fill=\"currentColor\"/><path d=\"M8,8 Q4,16 8,24\"/><path d=\"M24,8 Q28,16 24,24\"/></g>',\n    'router': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"4\" y=\"14\" width=\"24\" height=\"12\" rx=\"2\"/><line x1=\"10\" y1=\"14\" x2=\"10\" y2=\"8\"/><line x1=\"16\" y1=\"14\" x2=\"16\" y2=\"6\"/><line x1=\"22\" y1=\"14\" x2=\"22\" y2=\"8\"/><circle cx=\"10\" cy=\"7\" r=\"1.5\" fill=\"currentColor\"/><circle cx=\"16\" cy=\"5\" r=\"1.5\" fill=\"currentColor\"/><circle cx=\"22\" cy=\"7\" r=\"1.5\" fill=\"currentColor\"/><circle cx=\"8\" cy=\"22\" r=\"1.5\" fill=\"#4ade80\"/><circle cx=\"13\" cy=\"22\" r=\"1.5\" fill=\"#facc15\"/></g>',\n    'switch': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"3\" y=\"11\" width=\"26\" height=\"10\" rx=\"2\"/><circle cx=\"8\" cy=\"16\" r=\"1.5\" fill=\"currentColor\"/><circle cx=\"13\" cy=\"16\" r=\"1.5\" fill=\"currentColor\"/><circle cx=\"18\" cy=\"16\" r=\"1.5\" fill=\"currentColor\"/><circle cx=\"23\" cy=\"16\" r=\"1.5\" fill=\"currentColor\"/></g>',\n    'firewall': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"5\" y=\"5\" width=\"22\" height=\"22\" rx=\"1\"/><line x1=\"5\" y1=\"11\" x2=\"27\" y2=\"11\"/><line x1=\"5\" y1=\"17\" x2=\"27\" y2=\"17\"/><line x1=\"5\" y1=\"23\" x2=\"27\" y2=\"23\"/><line x1=\"11\" y1=\"5\" x2=\"11\" y2=\"27\"/><line x1=\"17\" y1=\"5\" x2=\"17\" y2=\"27\"/><line x1=\"23\" y1=\"5\" x2=\"23\" y2=\"27\"/></g>',\n    'access-point': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><path d=\"M16,18 L10,26 h12 z\"/><circle cx=\"16\" cy=\"16\" r=\"2\" fill=\"currentColor\"/><path d=\"M9,10 Q16,4 23,10\" stroke-linecap=\"round\"/><path d=\"M6,7 Q16,0 26,7\" stroke-linecap=\"round\"/></g>',\n    'load-balancer': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"6\" y=\"12\" width=\"20\" height=\"8\" rx=\"2\"/><line x1=\"16\" y1=\"8\" x2=\"16\" y2=\"12\"/><line x1=\"16\" y1=\"20\" x2=\"8\" y2=\"26\"/><line x1=\"16\" y1=\"20\" x2=\"16\" y2=\"26\"/><line x1=\"16\" y1=\"20\" x2=\"24\" y2=\"26\"/><circle cx=\"16\" cy=\"7\" r=\"2\" fill=\"currentColor\"/></g>',\n    'gateway': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"4\" y=\"10\" width=\"24\" height=\"12\" rx=\"2\"/><line x1=\"4\" y1=\"16\" x2=\"0\" y2=\"16\"/><line x1=\"28\" y1=\"16\" x2=\"32\" y2=\"16\"/><circle cx=\"16\" cy=\"16\" r=\"3\"/></g>',\n    'vpn': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"5\" y=\"8\" width=\"22\" height=\"16\" rx=\"2\"/><path d=\"M16,12 v6 M13,14 h6\" stroke-width=\"2.5\" stroke-linecap=\"round\"/><circle cx=\"11\" cy=\"20\" r=\"1\" fill=\"currentColor\"/><circle cx=\"21\" cy=\"20\" r=\"1\" fill=\"currentColor\"/></g>',\n    'nas': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"6\" y=\"4\" width=\"20\" height=\"24\" rx=\"2\"/><line x1=\"6\" y1=\"10\" x2=\"26\" y2=\"10\"/><line x1=\"6\" y1=\"16\" x2=\"26\" y2=\"16\"/><line x1=\"6\" y1=\"22\" x2=\"26\" y2=\"22\"/><circle cx=\"22\" cy=\"7\" r=\"1\" fill=\"currentColor\"/><circle cx=\"22\" cy=\"13\" r=\"1\" fill=\"currentColor\"/><circle cx=\"22\" cy=\"19\" r=\"1\" fill=\"currentColor\"/><circle cx=\"22\" cy=\"25\" r=\"1\" fill=\"currentColor\"/></g>',\n    'cloud': '<path d=\"M8,22 Q2,22 2,17 Q2,13 6,12 Q6,7 11,6 Q16,5 19,8 Q21,6 24,7 Q28,8 28,12 Q30,13 30,17 Q30,22 24,22 z\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"/>',\n    'database': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><ellipse cx=\"16\" cy=\"8\" rx=\"10\" ry=\"4\"/><path d=\"M6,8 v16 Q6,28 16,28 Q26,28 26,24 v-16\"/><path d=\"M6,14 Q6,18 16,18 Q26,18 26,14\"/></g>',\n    'docker': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><path d=\"M2,16 h6 v-6 h4 v-4 h4 v4 h4 v-4 h4 v8 Q28,24 20,26 Q10,28 4,22 z\"/><rect x=\"10\" y=\"12\" width=\"3\" height=\"3\"/><rect x=\"14\" y=\"12\" width=\"3\" height=\"3\"/><rect x=\"14\" y=\"8\" width=\"3\" height=\"3\"/></g>',\n    'container': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><path d=\"M4,12 L16,6 L28,12 L16,18 z\"/><path d=\"M4,12 v8 L16,26 v-8\"/><path d=\"M28,12 v8 L16,26 v-8\"/></g>',\n    'vm': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"4\" y=\"4\" width=\"24\" height=\"24\" rx=\"2\"/><rect x=\"7\" y=\"7\" width=\"18\" height=\"14\" rx=\"1\"/><line x1=\"12\" y1=\"24\" x2=\"20\" y2=\"24\"/></g>',\n    'kubernetes': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><polygon points=\"16,2 28,9 28,23 16,30 4,23 4,9\"/><circle cx=\"16\" cy=\"16\" r=\"4\"/><line x1=\"16\" y1=\"12\" x2=\"16\" y2=\"4\"/><line x1=\"19\" y1=\"14\" x2=\"26\" y2=\"10\"/><line x1=\"19\" y1=\"18\" x2=\"26\" y2=\"22\"/><line x1=\"16\" y1=\"20\" x2=\"16\" y2=\"28\"/><line x1=\"13\" y1=\"18\" x2=\"6\" y2=\"22\"/><line x1=\"13\" y1=\"14\" x2=\"6\" y2=\"10\"/></g>',\n    'api': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"4\" y=\"6\" width=\"24\" height=\"20\" rx=\"2\"/><path d=\"M10,16 l2,-6 2,6 M10.5,14 h3\" stroke-linecap=\"round\"/><path d=\"M17,10 h3 Q22,10 22,13 Q22,16 20,16 h-3 M17,16 v6\" stroke-linecap=\"round\"/><line x1=\"25\" y1=\"10\" x2=\"25\" y2=\"22\"/></g>',\n    'queue': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"4\" y=\"8\" width=\"24\" height=\"16\" rx=\"2\"/><line x1=\"12\" y1=\"8\" x2=\"12\" y2=\"24\"/><line x1=\"20\" y1=\"8\" x2=\"20\" y2=\"24\"/><path d=\"M6,16 h3 M14,16 h4 M22,16 h4\" stroke-linecap=\"round\"/></g>',\n    'lambda': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M8,6 L16,26 L24,6\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/><line x1=\"6\" y1=\"16\" x2=\"12\" y2=\"16\" stroke-linecap=\"round\"/></g>',\n    'bucket': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><path d=\"M6,8 h20 l-2,18 h-16 z\"/><ellipse cx=\"16\" cy=\"8\" rx=\"10\" ry=\"3\"/></g>',\n    'shield': '<path d=\"M16,3 L27,8 v10 Q27,26 16,29 Q5,26 5,18 v-10 z\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"/>',\n    'camera': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"4\" y=\"10\" width=\"24\" height=\"16\" rx=\"2\"/><circle cx=\"16\" cy=\"18\" r=\"5\"/><circle cx=\"16\" cy=\"18\" r=\"2\" fill=\"currentColor\"/><rect x=\"10\" y=\"6\" width=\"12\" height=\"4\" rx=\"1\"/></g>',\n    'monitor': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"4\" y=\"4\" width=\"24\" height=\"16\" rx=\"2\"/><line x1=\"12\" y1=\"24\" x2=\"20\" y2=\"24\"/><line x1=\"16\" y1=\"20\" x2=\"16\" y2=\"24\"/><path d=\"M8,8 h4 M8,11 h6 M8,14 h5 M18,8 Q20,8 20,14 Q20,16 18,16\" stroke-width=\"1\"/></g>',\n    'thermostat': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><circle cx=\"16\" cy=\"16\" r=\"11\"/><circle cx=\"16\" cy=\"16\" r=\"7\"/><line x1=\"16\" y1=\"9\" x2=\"16\" y2=\"16\" stroke-width=\"2\" stroke-linecap=\"round\"/></g>',\n    'doorbell': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"9\" y=\"3\" width=\"14\" height=\"26\" rx=\"4\"/><circle cx=\"16\" cy=\"14\" r=\"4\"/><circle cx=\"16\" cy=\"22\" r=\"1.5\" fill=\"currentColor\"/></g>',\n    'smart-lock': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"7\" y=\"14\" width=\"18\" height=\"14\" rx=\"2\"/><path d=\"M11,14 v-4 Q11,4 16,4 Q21,4 21,10 v4\"/><circle cx=\"16\" cy=\"21\" r=\"2\" fill=\"currentColor\"/></g>',\n    'smart-bulb': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><path d=\"M11,18 Q6,12 8,7 Q10,3 16,3 Q22,3 24,7 Q26,12 21,18 z\"/><rect x=\"11\" y=\"18\" width=\"10\" height=\"4\" rx=\"1\"/><line x1=\"12\" y1=\"24\" x2=\"20\" y2=\"24\"/><line x1=\"13\" y1=\"26\" x2=\"19\" y2=\"26\"/></g>',\n    'smart-plug': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"6\" y=\"8\" width=\"20\" height=\"16\" rx=\"3\"/><circle cx=\"12\" cy=\"16\" r=\"2\" fill=\"currentColor\"/><circle cx=\"20\" cy=\"16\" r=\"2\" fill=\"currentColor\"/><line x1=\"16\" y1=\"24\" x2=\"16\" y2=\"28\" stroke-width=\"2\"/></g>',\n    'smart-speaker': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><path d=\"M8,28 Q8,8 16,4 Q24,8 24,28 z\"/><circle cx=\"16\" cy=\"20\" r=\"4\"/><circle cx=\"16\" cy=\"20\" r=\"1.5\" fill=\"currentColor\"/></g>',\n    'smart-tv': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"3\" y=\"5\" width=\"26\" height=\"18\" rx=\"1\"/><line x1=\"10\" y1=\"27\" x2=\"22\" y2=\"27\"/><line x1=\"12\" y1=\"23\" x2=\"12\" y2=\"27\"/><line x1=\"20\" y1=\"23\" x2=\"20\" y2=\"27\"/></g>',\n    'hub': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><circle cx=\"16\" cy=\"16\" r=\"5\"/><circle cx=\"16\" cy=\"16\" r=\"2\" fill=\"currentColor\"/><line x1=\"16\" y1=\"5\" x2=\"16\" y2=\"11\"/><line x1=\"16\" y1=\"21\" x2=\"16\" y2=\"27\"/><line x1=\"5\" y1=\"16\" x2=\"11\" y2=\"16\"/><line x1=\"21\" y1=\"16\" x2=\"27\" y2=\"16\"/><line x1=\"8\" y1=\"8\" x2=\"12.5\" y2=\"12.5\"/><line x1=\"19.5\" y1=\"19.5\" x2=\"24\" y2=\"24\"/><line x1=\"8\" y1=\"24\" x2=\"12.5\" y2=\"19.5\"/><line x1=\"19.5\" y1=\"12.5\" x2=\"24\" y2=\"8\"/></g>',\n    'smoke-detector': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><circle cx=\"16\" cy=\"16\" r=\"11\"/><circle cx=\"16\" cy=\"16\" r=\"3\"/><line x1=\"16\" y1=\"5\" x2=\"16\" y2=\"7\"/><line x1=\"16\" y1=\"25\" x2=\"16\" y2=\"27\"/><line x1=\"5\" y1=\"16\" x2=\"7\" y2=\"16\"/><line x1=\"25\" y1=\"16\" x2=\"27\" y2=\"16\"/></g>',\n    'motion-sensor': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><path d=\"M8,24 Q4,12 16,6 Q28,12 24,24\" /><circle cx=\"16\" cy=\"16\" r=\"3\" fill=\"currentColor\"/><path d=\"M12,20 Q10,16 14,12\"/><path d=\"M20,20 Q22,16 18,12\"/></g>',\n    'garage': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><path d=\"M4,28 v-14 L16,4 L28,14 v14 z\"/><rect x=\"8\" y=\"16\" width=\"16\" height=\"12\"/><line x1=\"8\" y1=\"20\" x2=\"24\" y2=\"20\"/><line x1=\"8\" y1=\"24\" x2=\"24\" y2=\"24\"/></g>',\n    'sprinkler': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><line x1=\"16\" y1=\"4\" x2=\"16\" y2=\"14\"/><path d=\"M10,14 h12 v3 Q16,22 10,17 z\"/><path d=\"M8,20 Q6,24 4,26\" stroke-linecap=\"round\"/><path d=\"M16,22 v6\" stroke-linecap=\"round\"/><path d=\"M24,20 Q26,24 28,26\" stroke-linecap=\"round\"/></g>',\n    'vacuum': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><circle cx=\"16\" cy=\"16\" r=\"11\"/><circle cx=\"16\" cy=\"16\" r=\"6\"/><circle cx=\"16\" cy=\"16\" r=\"2\" fill=\"currentColor\"/><line x1=\"16\" y1=\"5\" x2=\"20\" y2=\"3\" stroke-linecap=\"round\"/></g>',\n    'basketball-ball': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><circle cx=\"16\" cy=\"16\" r=\"12\"/><path d=\"M4,16 h24\"/><path d=\"M16,4 v24\"/><path d=\"M6,7 Q16,14 6,25\"/><path d=\"M26,7 Q16,14 26,25\"/></g>',\n    'football-ball': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><ellipse cx=\"16\" cy=\"16\" rx=\"12\" ry=\"8\" transform=\"rotate(-30,16,16)\"/><path d=\"M10,10 L22,22 M12,8 L24,20\" stroke-linecap=\"round\"/><line x1=\"14\" y1=\"12\" x2=\"18\" y2=\"16\" stroke-linecap=\"round\"/><line x1=\"16\" y1=\"14\" x2=\"20\" y2=\"18\" stroke-linecap=\"round\"/></g>',\n    'soccer-ball': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><circle cx=\"16\" cy=\"16\" r=\"12\"/><polygon points=\"16,8 20,12 18,17 14,17 12,12\" fill=\"currentColor\" stroke=\"none\"/></g>',\n    'hockey-puck': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><ellipse cx=\"16\" cy=\"14\" rx=\"12\" ry=\"5\"/><path d=\"M4,14 v4 Q4,23 16,23 Q28,23 28,18 v-4\"/></g>',\n    'baseball-ball': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><circle cx=\"16\" cy=\"16\" r=\"12\"/><path d=\"M8,6 Q14,12 8,26\"/><path d=\"M24,6 Q18,12 24,26\"/></g>',\n    'tennis-ball': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><circle cx=\"16\" cy=\"16\" r=\"12\"/><path d=\"M6,6 Q16,16 6,26\"/><path d=\"M26,6 Q16,16 26,26\"/></g>',\n    'volleyball': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><circle cx=\"16\" cy=\"16\" r=\"12\"/><path d=\"M16,4 Q12,16 16,28\"/><path d=\"M5,10 Q16,14 27,10\"/><path d=\"M5,22 Q16,18 27,22\"/></g>',\n    'rugby-ball': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><ellipse cx=\"16\" cy=\"16\" rx=\"13\" ry=\"8\" transform=\"rotate(-30,16,16)\"/><path d=\"M10,10 L22,22\"/></g>',\n    'golf-ball': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><circle cx=\"16\" cy=\"16\" r=\"10\"/><circle cx=\"14\" cy=\"14\" r=\"0.8\" fill=\"currentColor\"/><circle cx=\"18\" cy=\"12\" r=\"0.8\" fill=\"currentColor\"/><circle cx=\"16\" cy=\"16\" r=\"0.8\" fill=\"currentColor\"/><circle cx=\"12\" cy=\"17\" r=\"0.8\" fill=\"currentColor\"/><circle cx=\"19\" cy=\"16\" r=\"0.8\" fill=\"currentColor\"/></g>',\n    'frisbee': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><ellipse cx=\"16\" cy=\"16\" rx=\"13\" ry=\"5\"/><ellipse cx=\"16\" cy=\"15\" rx=\"8\" ry=\"3\"/></g>',\n    'cricket-ball': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><circle cx=\"16\" cy=\"16\" r=\"11\"/><path d=\"M10,6 Q16,16 10,26\" stroke-dasharray=\"2,2\"/></g>',\n    'lacrosse-stick': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><line x1=\"8\" y1=\"28\" x2=\"20\" y2=\"6\" stroke-width=\"2\"/><path d=\"M18,8 Q26,4 26,12 Q26,16 20,14\"/><line x1=\"20\" y1=\"9\" x2=\"24\" y2=\"13\"/></g>',\n    'golf-flag': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><line x1=\"10\" y1=\"4\" x2=\"10\" y2=\"28\" stroke-width=\"2\"/><polygon points=\"10,4 26,10 10,16\" fill=\"currentColor\" opacity=\"0.3\" stroke=\"currentColor\"/><ellipse cx=\"16\" cy=\"28\" rx=\"10\" ry=\"2\"/></g>',\n    'tactical-x': '<g stroke=\"currentColor\" stroke-width=\"3\" stroke-linecap=\"round\"><line x1=\"6\" y1=\"6\" x2=\"26\" y2=\"26\"/><line x1=\"26\" y1=\"6\" x2=\"6\" y2=\"26\"/></g>',\n    'tactical-o': '<circle cx=\"16\" cy=\"16\" r=\"10\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"3\"/>',\n    'tactical-star': '<polygon points=\"16,3 19,12 29,12 21,18 24,28 16,22 8,28 11,18 3,12 13,12\" fill=\"currentColor\" opacity=\"0.3\" stroke=\"currentColor\" stroke-width=\"1.5\"/>',\n    'patch-panel': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"3\" y=\"10\" width=\"26\" height=\"12\" rx=\"1\"/><circle cx=\"8\" cy=\"14\" r=\"1.5\"/><circle cx=\"13\" cy=\"14\" r=\"1.5\"/><circle cx=\"18\" cy=\"14\" r=\"1.5\"/><circle cx=\"23\" cy=\"14\" r=\"1.5\"/><circle cx=\"8\" cy=\"19\" r=\"1.5\"/><circle cx=\"13\" cy=\"19\" r=\"1.5\"/><circle cx=\"18\" cy=\"19\" r=\"1.5\"/><circle cx=\"23\" cy=\"19\" r=\"1.5\"/></g>',\n    'ups': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"6\" y=\"4\" width=\"20\" height=\"24\" rx=\"2\"/><rect x=\"10\" y=\"8\" width=\"12\" height=\"6\" rx=\"1\"/><circle cx=\"12\" cy=\"20\" r=\"1.5\" fill=\"currentColor\"/><circle cx=\"16\" cy=\"20\" r=\"1.5\"/><circle cx=\"20\" cy=\"20\" r=\"1.5\"/></g>',\n    'pdu': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"11\" y=\"2\" width=\"10\" height=\"28\" rx=\"1\"/><circle cx=\"16\" cy=\"7\" r=\"2\"/><circle cx=\"16\" cy=\"13\" r=\"2\"/><circle cx=\"16\" cy=\"19\" r=\"2\"/><circle cx=\"16\" cy=\"25\" r=\"2\" fill=\"currentColor\"/></g>',\n    'rack-shelf': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"3\" y=\"12\" width=\"26\" height=\"8\" rx=\"1\"/><line x1=\"3\" y1=\"17\" x2=\"29\" y2=\"17\" stroke-dasharray=\"3,2\"/><circle cx=\"7\" cy=\"14.5\" r=\"1\" fill=\"currentColor\"/></g>',\n    'blank-panel': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"3\" y=\"12\" width=\"26\" height=\"8\" rx=\"1\"/><circle cx=\"6\" cy=\"16\" r=\"1\"/><circle cx=\"26\" cy=\"16\" r=\"1\"/></g>',\n    'cable-management': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"3\" y=\"11\" width=\"26\" height=\"10\" rx=\"1\"/><path d=\"M7,14 Q10,18 13,14 Q16,10 19,14 Q22,18 25,14\" stroke-linecap=\"round\"/></g>',\n    'kvm': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"4\" y=\"8\" width=\"24\" height=\"16\" rx=\"2\"/><rect x=\"8\" y=\"11\" width=\"10\" height=\"7\" rx=\"1\"/><circle cx=\"23\" cy=\"14.5\" r=\"2\"/><line x1=\"12\" y1=\"20\" x2=\"20\" y2=\"20\"/></g>'\n  };\n\n  function getLayerName(val) {\n    var sel = document.getElementById('node-layer');\n    if (sel) {\n      for (var i = 0; i < sel.options.length; i++) {\n        if (sel.options[i].value === val) return sel.options[i].textContent;\n      }\n    }\n    var fallback = { layer1: 'Physical Layer', layer2: 'Logical Layer', layer3: 'Security Layer', layer4: 'Application Layer' };\n    return fallback[val] || val;\n  }\n\n  var _iconCache = {};\n  function fetchDiscoveryIcon(library, name, el, size) {\n    var sz = size || 20;\n    var key = library + '/' + name;\n    if (_iconCache[key] === false) return;\n    var url = '';\n    if (library === 'selfhst') url = 'https://cdn.jsdelivr.net/gh/selfhst/icons@master/png/' + encodeURIComponent(name) + '.png';\n    else if (library === 'mdi') url = 'https://cdn.jsdelivr.net/npm/@mdi/svg@latest/svg/' + encodeURIComponent(name) + '.svg';\n    if (!url) return;\n    if (_iconCache[key]) {\n      el.innerHTML = '<img src=\"' + _iconCache[key] + '\" style=\"width:' + sz + 'px;height:' + sz + 'px;object-fit:contain\" alt=\"' + escapeHtml(name) + '\">';\n      return;\n    }\n    _iconCache[key] = url;\n    el.innerHTML = '<img src=\"' + url + '\" style=\"width:' + sz + 'px;height:' + sz + 'px;object-fit:contain\" alt=\"' + escapeHtml(name) + '\">';\n    var img = el.querySelector('img');\n    if (img) {\n      img.onerror = function() {\n        _iconCache[key] = false;\n        el.innerHTML = '';\n      };\n    }\n  }\n\n  function getShapePreviewSVG(shape) {\n    var svg = SHAPE_PREVIEW_SVGS[shape];\n    if (svg) return '<svg width=\"36\" height=\"36\" viewBox=\"0 0 32 32\" style=\"color:var(--accent)\">' + svg + '</svg>';\n    return '<span style=\"font-size:10px;color:var(--text-soft);text-align:center;line-height:1.2\">' + escapeHtml(shape || 'circle') + '</span>';\n  }\n\n  function updateDiscoveryProgress(percent, scanned, total, rangeIndex, totalRanges) {\n    var bar = document.getElementById('verse-discovery-progress-fill');\n    var text = document.getElementById('verse-discovery-progress-text');\n    if (bar) bar.style.width = percent + '%';\n    if (text) {\n      var label = percent + '% (' + scanned + '/' + total + ')';\n      if (totalRanges > 1) label = 'Range ' + (rangeIndex + 1) + '/' + totalRanges + ' : ' + label;\n      text.textContent = label;\n    }\n  }\n\n  var _scanRenderInterval = null;\n  function addDiscoveryResult(host) {\n    discoveryResults.push(host);\n    var countEl = document.getElementById('verse-discovery-count');\n    if (countEl) countEl.textContent = discoveryResults.length + ' hosts found';\n    if (!_scanRenderInterval) {\n      _scanRenderInterval = setInterval(function() {\n        renderDiscoveryResults();\n      }, 2000);\n    }\n  }\n\n  function finalizeDiscovery(totalFound) {\n    discoveryScanning = false;\n    discoveryTaskId = null;\n    if (_scanRenderInterval) { clearInterval(_scanRenderInterval); _scanRenderInterval = null; }\n    var btn = document.getElementById('verse-discovery-start');\n    var cancelBtn = document.getElementById('verse-discovery-cancel');\n    if (btn) { btn.textContent = 'Start Scan'; btn.disabled = false; }\n    if (cancelBtn) cancelBtn.style.display = 'none';\n    var text = document.getElementById('verse-discovery-progress-text');\n    if (text) text.textContent = 'Complete: ' + totalFound + ' hosts found';\n    renderDiscoveryResults();\n  }\n\n  var activeDeepScans = {};\n\n  function handleDeepScanProgress(scanId, ip, percent) {\n    activeDeepScans[ip] = { scanId: scanId, percent: percent };\n    var cell = document.querySelector('[data-deepscan-ip=\"' + ip + '\"]');\n    if (cell) {\n      var bar = cell.querySelector('.deepscan-bar-fill');\n      var text = cell.querySelector('.deepscan-text');\n      if (bar) bar.style.width = percent + '%';\n      if (text) text.textContent = percent + '%';\n    }\n  }\n\n  function handleDeepScanUpdate(scanId, ip, newPorts, newServices, containers, newIcons) {\n    for (var i = 0; i < discoveryResults.length; i++) {\n      if (discoveryResults[i].ip === ip) {\n        var host = discoveryResults[i];\n        if (!host.ports) host.ports = [];\n        if (!host.services) host.services = {};\n        newPorts.forEach(function(p) {\n          if (host.ports.indexOf(p) === -1) host.ports.push(p);\n        });\n        Object.keys(newServices).forEach(function(portStr) {\n          host.services[parseInt(portStr, 10)] = newServices[portStr];\n        });\n        if (containers) host.dockerContainers = containers;\n        if (!discoveryOverrides[ip]) discoveryOverrides[ip] = {};\n        var ov = discoveryOverrides[ip];\n        var existing = ov.tags ? ov.tags.split(',').map(function(t) { return t.trim(); }).filter(function(t) { return t; }) : [];\n        Object.values(newServices).forEach(function(s) {\n          if (s && s.indexOf('Port ') !== 0 && existing.indexOf(s) === -1) existing.push(s);\n        });\n        if (containers && containers.length > 0) {\n          containers.forEach(function(c) {\n            var label = c.name || '';\n            if (label && existing.indexOf(label) === -1) existing.push(label);\n          });\n        }\n        ov.tags = existing.join(', ');\n        ov._tagsSeeded = true;\n        if (newIcons && newIcons.length > 0) {\n          if (!ov.iconTags) ov.iconTags = [];\n          newIcons.forEach(function(icon) {\n            var exists = ov.iconTags.some(function(t) {\n              return t.library === icon.library && t.name === icon.name;\n            });\n            if (!exists) {\n              ov.iconTags.push({ type: 'icon', library: icon.library, name: icon.name, svg: '' });\n            }\n          });\n        }\n        break;\n      }\n    }\n  }\n\n  function handleDeepScanComplete(scanId, ip) {\n    delete activeDeepScans[ip];\n    renderDiscoveryResults();\n  }\n\n  var DOCKER_TRIGGER_PORTS = [2375, 2376, 9443, 5001];\n\n  function buildDeepScanCell(ip) {\n    var active = activeDeepScans[ip];\n    if (active) {\n      return '<div data-deepscan-ip=\"' + escapeHtml(ip) + '\" style=\"display:inline-flex;align-items:center;gap:4px\">' +\n        '<div style=\"width:48px;height:4px;background:var(--edge-main);border-radius:2px;overflow:hidden\">' +\n          '<div class=\"deepscan-bar-fill\" style=\"width:' + (active.percent || 0) + '%;height:100%;background:var(--accent);border-radius:2px;transition:width 0.3s\"></div>' +\n        '</div>' +\n        '<span class=\"deepscan-text\" style=\"font-size:9px;color:var(--text-soft)\">' + (active.percent || 0) + '%</span>' +\n        '<button class=\"deepscan-cancel-btn\" data-ip=\"' + escapeHtml(ip) + '\" style=\"background:none;border:none;color:var(--danger);cursor:pointer;font-size:11px;padding:0 2px\" title=\"Cancel deep scan\">\\u2715</button>' +\n      '</div>';\n    }\n    return '<button class=\"deepscan-start-btn\" data-ip=\"' + escapeHtml(ip) + '\" style=\"padding:2px 6px;font-size:10px;background:var(--panel);color:var(--text-soft);border:1px solid var(--edge-main);border-radius:4px;cursor:pointer\" title=\"Scan for Docker container ports\">Deep Scan</button>';\n  }\n\n  var discoveryRanges = [];\n\n  function getCurrentCIDR() {\n    var sel = document.getElementById('verse-disc-preset');\n    if (!sel) return '';\n    if (sel.value === 'custom') {\n      var custom = document.getElementById('verse-disc-custom-cidr');\n      return custom ? custom.value.trim() : '';\n    }\n    return sel.value || '';\n  }\n\n  function getDiscoveryCIDRs() {\n    if (discoveryRanges.length > 0) return discoveryRanges.slice();\n    var current = getCurrentCIDR();\n    return current ? [current] : [];\n  }\n\n  function renderRangePills() {\n    var container = document.getElementById('verse-disc-ranges');\n    if (!container) return;\n    container.innerHTML = '';\n    discoveryRanges.forEach(function(cidr, idx) {\n      var pill = document.createElement('span');\n      pill.style.cssText = 'display:inline-flex;align-items:center;gap:4px;padding:4px 10px;background:var(--accent);color:var(--bg);border-radius:16px;font-size:12px;font-family:monospace';\n      pill.textContent = cidr;\n      var x = document.createElement('button');\n      x.style.cssText = 'background:none;border:none;color:var(--bg);font-size:14px;cursor:pointer;padding:0 2px;line-height:1';\n      x.textContent = '\\u2715';\n      x.addEventListener('click', function() {\n        discoveryRanges.splice(idx, 1);\n        renderRangePills();\n      });\n      pill.appendChild(x);\n      container.appendChild(pill);\n    });\n  }\n\n  function updateRackDropdowns() {\n    var nd = getGlobal('NODE_DATA') || {};\n    var canvasRacks = [];\n    Object.keys(nd).forEach(function(nid) {\n      if (nd[nid] && nd[nid].isRack) {\n        canvasRacks.push({ value: 'canvas:' + nid, label: (nd[nid].name || nid) + ' (on canvas)', capacity: parseInt(nd[nid].rackCapacity) || 42 });\n      }\n    });\n    var newRacks = [];\n    discoveryResults.forEach(function(host) {\n      var ov = discoveryOverrides[host.ip] || {};\n      if (ov.typeToggle === 'rack') {\n        var rackName = ov.name || host.hostname || host.ip;\n        var cap = parseInt(ov.rackCapacity) || 42;\n        newRacks.push({ value: 'new:' + host.ip, label: rackName + ' (new)', capacity: cap, ip: host.ip });\n      }\n    });\n\n    document.querySelectorAll('.disc-rack-cell').forEach(function(cell) {\n      var ip = cell.getAttribute('data-ip');\n      var ov = discoveryOverrides[ip] || {};\n      var isRack = ov.typeToggle === 'rack';\n\n      if (isRack) {\n        cell.innerHTML = '';\n        return;\n      }\n\n      var savedRef = ov.assignedRackRef || '';\n      var savedUnit = ov.rackUnit || '';\n      var savedUHeight = ov.uHeight || '1';\n\n      var opts = '<option value=\"\">None</option>';\n      canvasRacks.forEach(function(r) {\n        opts += '<option value=\"' + escapeHtml(r.value) + '\"' + (savedRef === r.value ? ' selected' : '') + '>' + escapeHtml(r.label) + '</option>';\n      });\n      newRacks.forEach(function(r) {\n        if (r.ip === ip) return;\n        opts += '<option value=\"' + escapeHtml(r.value) + '\"' + (savedRef === r.value ? ' selected' : '') + '>' + escapeHtml(r.label) + '</option>';\n      });\n\n      var html = '<select class=\"disc-rack-select\" data-ip=\"' + escapeHtml(ip) + '\" style=\"width:100%;padding:3px 6px;border-radius:4px;border:1px solid var(--edge-main);background:var(--panel);color:var(--text-main);font-size:11px\">' + opts + '</select>';\n\n      if (savedRef) {\n        var capacity = 42;\n        canvasRacks.forEach(function(r) { if (r.value === savedRef) capacity = r.capacity; });\n        newRacks.forEach(function(r) { if (r.value === savedRef) capacity = r.capacity; });\n\n        var unitOpts = '<option value=\"\">U Pos</option>';\n        for (var u = 1; u <= capacity; u++) {\n          unitOpts += '<option value=\"' + u + '\"' + (savedUnit === String(u) ? ' selected' : '') + '>U' + u + '</option>';\n        }\n\n        var uhOpts = '';\n        for (var h = 1; h <= 4; h++) {\n          uhOpts += '<option value=\"' + h + '\"' + (savedUHeight === String(h) ? ' selected' : '') + '>' + h + 'U</option>';\n        }\n\n        html += '<div style=\"display:flex;gap:4px;margin-top:4px\">' +\n          '<select class=\"disc-rack-unit\" data-ip=\"' + escapeHtml(ip) + '\" style=\"flex:1;padding:2px 4px;border-radius:4px;border:1px solid var(--edge-main);background:var(--panel);color:var(--text-main);font-size:10px\">' + unitOpts + '</select>' +\n          '<select class=\"disc-rack-uheight\" data-ip=\"' + escapeHtml(ip) + '\" style=\"width:48px;padding:2px 4px;border-radius:4px;border:1px solid var(--edge-main);background:var(--panel);color:var(--text-main);font-size:10px\">' + uhOpts + '</select>' +\n        '</div>';\n      }\n\n      cell.innerHTML = html;\n    });\n\n    document.querySelectorAll('.disc-rack-select').forEach(function(sel) {\n      sel.addEventListener('change', function() {\n        var ip = sel.getAttribute('data-ip');\n        if (!discoveryOverrides[ip]) discoveryOverrides[ip] = {};\n        if (sel.value) {\n          discoveryOverrides[ip].assignedRackRef = sel.value;\n        } else {\n          delete discoveryOverrides[ip].assignedRackRef;\n          delete discoveryOverrides[ip].rackUnit;\n        }\n        updateRackDropdowns();\n      });\n    });\n\n    document.querySelectorAll('.disc-rack-unit').forEach(function(sel) {\n      sel.addEventListener('change', function() {\n        var ip = sel.getAttribute('data-ip');\n        if (!discoveryOverrides[ip]) discoveryOverrides[ip] = {};\n        discoveryOverrides[ip].rackUnit = sel.value;\n      });\n    });\n\n    document.querySelectorAll('.disc-rack-uheight').forEach(function(sel) {\n      sel.addEventListener('change', function() {\n        var ip = sel.getAttribute('data-ip');\n        if (!discoveryOverrides[ip]) discoveryOverrides[ip] = {};\n        discoveryOverrides[ip].uHeight = sel.value;\n      });\n    });\n  }\n\n  function renderDiscoveryResults() {\n    var tbody = document.getElementById('verse-discovery-tbody');\n    if (!tbody) return;\n    var count = document.getElementById('verse-discovery-count');\n    if (count) count.textContent = discoveryResults.length + ' hosts found';\n    var searchEl = document.getElementById('verse-disc-table-search');\n    var savedSearch = searchEl ? searchEl.value : '';\n    tbody.innerHTML = '';\n\n    var nd = getGlobal('NODE_DATA') || {};\n    var canvasIPs = {};\n    Object.keys(nd).forEach(function(nid) {\n      if (nd[nid] && nd[nid].ip) canvasIPs[nd[nid].ip] = nid;\n    });\n\n    discoveryResults.forEach(function(host) {\n      var nodeId = canvasIPs[host.ip];\n      if (!nodeId) return;\n      var node = nd[nodeId];\n      if (!node) return;\n      if (!discoveryOverrides[host.ip]) discoveryOverrides[host.ip] = {};\n      var ov = discoveryOverrides[host.ip];\n      if (ov.name === undefined && node.name) {\n        ov.name = node.name;\n      }\n      if (ov.layer === undefined && node.layer) {\n        ov.layer = node.layer;\n      }\n      if (ov.assignedRackRef === undefined && node.assignedRack && nd[node.assignedRack]) {\n        ov.assignedRackRef = 'canvas:' + node.assignedRack;\n        if (ov.rackUnit === undefined) ov.rackUnit = node.rackUnit || '';\n        if (ov.uHeight === undefined) ov.uHeight = node.uHeight || '1';\n      }\n      if (ov.typeToggle === undefined && node.isRack) {\n        ov.typeToggle = 'rack';\n      }\n      ov._seeded = true;\n    });\n\n    discoveryResults.forEach(function(host) {\n      if (!host.icon || host.icon.name === 'linux' || host.icon.name === 'terminal') return;\n      if (!discoveryOverrides[host.ip]) discoveryOverrides[host.ip] = {};\n      var ov = discoveryOverrides[host.ip];\n      if (ov.iconData === undefined) {\n        ov.iconData = { library: host.icon.library, name: host.icon.name };\n        if (!ov._seeded) ov._seeded = true;\n      }\n    });\n\n    discoveryResults.forEach(function(host) {\n      if (!host.services) return;\n      if (!discoveryOverrides[host.ip]) discoveryOverrides[host.ip] = {};\n      var ov = discoveryOverrides[host.ip];\n      if (ov._tagsSeeded) return;\n      var svcNames = Object.values(host.services).filter(function(s) { return s && s.indexOf('Port ') !== 0; });\n      if (svcNames.length > 0) {\n        var existing = ov.tags ? ov.tags.split(',').map(function(t) { return t.trim(); }).filter(function(t) { return t; }) : [];\n        svcNames.forEach(function(s) {\n          if (existing.indexOf(s) === -1) existing.push(s);\n        });\n        ov.tags = existing.join(', ');\n        ov._tagsSeeded = true;\n        if (!ov._seeded) ov._seeded = true;\n      }\n    });\n\n    discoveryResults.forEach(function(host) {\n      if (!host.serviceIcons || host.serviceIcons.length === 0) return;\n      if (!discoveryOverrides[host.ip]) discoveryOverrides[host.ip] = {};\n      var ov = discoveryOverrides[host.ip];\n      if (ov._iconTagsSeeded) return;\n      if (!ov.iconTags) ov.iconTags = [];\n      host.serviceIcons.forEach(function(icon) {\n        if (ov.iconData && ov.iconData.library === icon.library && ov.iconData.name === icon.name) return;\n        var exists = ov.iconTags.some(function(t) {\n          return t.library === icon.library && t.name === icon.name;\n        });\n        if (!exists) {\n          ov.iconTags.push({ type: 'icon', library: icon.library, name: icon.name, svg: '' });\n        }\n      });\n      ov._iconTagsSeeded = true;\n      if (!ov._seeded) ov._seeded = true;\n    });\n\n    var editBtn = document.getElementById('verse-disc-edit-btn');\n    if (editBtn) editBtn.style.display = discoveryResults.length > 0 ? 'inline' : 'none';\n\n    var sortedResults = discoveryResults.slice().sort(function(a, b) {\n      var aOnCanvas = canvasIPs[a.ip] ? 1 : 0;\n      var bOnCanvas = canvasIPs[b.ip] ? 1 : 0;\n      return aOnCanvas - bOnCanvas;\n    });\n\n    sortedResults.forEach(function(host) {\n      var idx = discoveryResults.indexOf(host);\n      var svcValues = Object.values(host.services || {});\n      var serviceTags = svcValues.map(function(s) {\n        var isGeneric = s.indexOf('Port ') === 0;\n        var bg = isGeneric ? 'var(--panel)' : 'var(--panel-alt)';\n        var border = isGeneric ? 'var(--edge-main)' : 'var(--accent)';\n        var color = isGeneric ? 'var(--text-soft)' : 'var(--accent)';\n        return '<span style=\"display:inline-block;padding:1px 6px;margin:1px;font-size:10px;border-radius:3px;background:' + bg + ';border:1px solid ' + border + ';color:' + color + ';white-space:nowrap\">' + escapeHtml(s) + '</span>';\n      }).join('');\n      if (host.dockerContainers && host.dockerContainers.length > 0) {\n        host.dockerContainers.forEach(function(c) {\n          var name = c.name || '';\n          if (!name) return;\n          serviceTags += '<span style=\"display:inline-block;padding:1px 6px;margin:1px;font-size:10px;border-radius:3px;background:var(--panel-alt);border:1px solid #2496ed;color:#2496ed;white-space:nowrap\" title=\"Docker container\">' + escapeHtml(name) + '</span>';\n        });\n      }\n      var hostname = host.hostname || '';\n      var nameDetails = [];\n      if (host.dnsName) nameDetails.push('DNS: ' + host.dnsName);\n      if (host.netbiosName) nameDetails.push('NetBIOS: ' + host.netbiosName);\n      if (host.mdnsName) nameDetails.push('mDNS: ' + host.mdnsName);\n      if (host.httpServer) nameDetails.push('HTTP: ' + host.httpServer);\n      if (host.snmpName) nameDetails.push('SNMP: ' + host.snmpName);\n      if (host.snmpDescr) nameDetails.push('Descr: ' + host.snmpDescr);\n      var detailTitle = nameDetails.length > 0 ? nameDetails.join('\\n') : '';\n\n      var onCanvas = canvasIPs[host.ip];\n      var hasOverride = discoveryOverrides[host.ip];\n      var ov = hasOverride || {};\n      var displayName = ov.name || hostname;\n      var userEdited = hasOverride && !ov._seeded;\n      var overrideIndicator = userEdited ? ' <span style=\"color:var(--accent);font-size:11px\" title=\"Edited\">&#9998;</span>' : '';\n      var canvasBadge = onCanvas ? '<br><span style=\"font-size:9px;color:var(--accent);font-style:italic\">on canvas</span>' : '';\n\n      var isRackOverride = ov.typeToggle === 'rack';\n      var isNodeOverride = !ov.typeToggle || ov.typeToggle === 'node';\n      var nodeActive = isNodeOverride ? 'active' : '';\n      var rackActive = isRackOverride ? 'active' : '';\n      var nodeBg = isNodeOverride ? 'var(--accent)' : 'var(--panel)';\n      var nodeColor = isNodeOverride ? 'var(--bg)' : 'var(--text-soft)';\n      var rackBg = isRackOverride ? 'var(--accent)' : 'var(--panel)';\n      var rackColor = isRackOverride ? 'var(--bg)' : 'var(--text-soft)';\n\n      var hasDocker = host.ports && DOCKER_TRIGGER_PORTS.some(function(p) { return host.ports.indexOf(p) !== -1; });\n      var deepScanHtml = hasDocker ? ' ' + buildDeepScanCell(host.ip) : '';\n\n      var tr = document.createElement('tr');\n      tr.style.borderBottom = '1px solid var(--edge-main)';\n      if (onCanvas) tr.style.opacity = '0.5';\n      tr.setAttribute('data-ip', host.ip);\n      tr.innerHTML = '<td style=\"padding:6px 8px\"><label class=\"toggle-switch\"><input type=\"checkbox\" data-idx=\"' + idx + '\" class=\"verse-discovery-check\"' + (onCanvas ? '' : ' checked') + '><span class=\"toggle-slider\"></span></label></td>' +\n        '<td style=\"padding:6px 8px;font-family:monospace;font-size:12px\">' + escapeHtml(host.ip) + canvasBadge + '</td>' +\n        '<td style=\"padding:6px 8px;font-size:12px\" title=\"' + escapeHtml(detailTitle) + '\">' + escapeHtml(displayName) + overrideIndicator + ' <span style=\"display:inline-flex;gap:4px;margin-left:4px;vertical-align:middle\"><button class=\"disc-row-edit-btn\" data-ip=\"' + escapeHtml(host.ip) + '\" style=\"padding:2px 6px;font-size:10px;background:var(--panel);color:var(--text-soft);border:1px solid var(--edge-main);border-radius:4px;cursor:pointer\" title=\"Edit this host\">Editor</button>' + deepScanHtml + '</span></td>' +\n        '<td style=\"padding:6px 8px;font-size:12px\"><div style=\"display:flex;flex-wrap:wrap;gap:0;align-items:center\">' + serviceTags + '</div></td>' +\n        '<td style=\"padding:6px 4px;text-align:center\"><span class=\"disc-icon-cell\" data-icon-ip=\"' + escapeHtml(host.ip) + '\" style=\"display:inline-flex;align-items:center;justify-content:center;width:24px;height:24px\" title=\"' + escapeHtml(ov.iconData && ov.iconData.name ? ov.iconData.library + '/' + ov.iconData.name : '') + '\"></span></td>' +\n        '<td style=\"padding:6px 8px\">' +\n          '<div class=\"verse-type-toggle\" data-idx=\"' + idx + '\" data-ip=\"' + escapeHtml(host.ip) + '\" style=\"display:inline-flex;border-radius:4px;overflow:hidden;border:1px solid var(--edge-main)\">' +\n            '<button data-type=\"node\" class=\"verse-type-btn ' + nodeActive + '\" style=\"padding:3px 10px;font-size:11px;border:none;cursor:pointer;background:' + nodeBg + ';color:' + nodeColor + '\">Node</button>' +\n            '<button data-type=\"rack\" class=\"verse-type-btn ' + rackActive + '\" style=\"padding:3px 10px;font-size:11px;border:none;cursor:pointer;background:' + rackBg + ';color:' + rackColor + '\">Rack</button>' +\n          '</div>' +\n        '</td>' +\n        '<td style=\"padding:6px 8px;min-width:120px\" class=\"disc-rack-cell\" data-ip=\"' + escapeHtml(host.ip) + '\"></td>' +\n        '<td style=\"padding:6px 8px\">' +\n          '<select class=\"disc-layer-select\" data-ip=\"' + escapeHtml(host.ip) + '\" style=\"width:100%;padding:3px 6px;border-radius:4px;border:1px solid var(--edge-main);background:var(--panel);color:var(--text-main);font-size:11px\">' +\n            '<option value=\"layer1\"' + ((ov.layer || 'layer1') === 'layer1' ? ' selected' : '') + '>' + escapeHtml(getLayerName('layer1')) + '</option>' +\n            '<option value=\"layer2\"' + (ov.layer === 'layer2' ? ' selected' : '') + '>' + escapeHtml(getLayerName('layer2')) + '</option>' +\n            '<option value=\"layer3\"' + (ov.layer === 'layer3' ? ' selected' : '') + '>' + escapeHtml(getLayerName('layer3')) + '</option>' +\n            '<option value=\"layer4\"' + (ov.layer === 'layer4' ? ' selected' : '') + '>' + escapeHtml(getLayerName('layer4')) + '</option>' +\n          '</select>' +\n        '</td>';\n      tbody.appendChild(tr);\n    });\n\n    updateRackDropdowns();\n\n    document.querySelectorAll('.disc-icon-cell').forEach(function(el) {\n      var ip = el.getAttribute('data-icon-ip');\n      var ov = discoveryOverrides[ip] || {};\n      if (ov.iconData && ov.iconData.name) {\n        if (ov.iconData.svg) {\n          el.innerHTML = ov.iconData.svg;\n          var svgEl = el.querySelector('svg');\n          if (svgEl) { svgEl.style.width = '20px'; svgEl.style.height = '20px'; }\n        } else {\n          fetchDiscoveryIcon(ov.iconData.library, ov.iconData.name, el);\n        }\n      }\n    });\n\n    document.querySelectorAll('.disc-layer-select').forEach(function(sel) {\n      sel.addEventListener('change', function() {\n        var ip = sel.getAttribute('data-ip');\n        if (!discoveryOverrides[ip]) discoveryOverrides[ip] = {};\n        discoveryOverrides[ip].layer = sel.value;\n      });\n    });\n\n    document.querySelectorAll('.verse-type-toggle').forEach(function(toggle) {\n      toggle.addEventListener('click', function(e) {\n        var btn = e.target.closest('.verse-type-btn');\n        if (!btn) return;\n        var ip = toggle.getAttribute('data-ip');\n        toggle.querySelectorAll('.verse-type-btn').forEach(function(b) {\n          if (b === btn) {\n            b.classList.add('active');\n            b.style.background = 'var(--accent)';\n            b.style.color = 'var(--bg)';\n          } else {\n            b.classList.remove('active');\n            b.style.background = 'var(--panel)';\n            b.style.color = 'var(--text-soft)';\n          }\n        });\n        if (ip) {\n          if (!discoveryOverrides[ip]) discoveryOverrides[ip] = {};\n          var prevType = discoveryOverrides[ip].typeToggle || 'node';\n          discoveryOverrides[ip].typeToggle = btn.dataset.type;\n          if (btn.dataset.type === 'rack') {\n            delete discoveryOverrides[ip].assignedRackRef;\n            delete discoveryOverrides[ip].rackUnit;\n          }\n          if (prevType === 'rack' && btn.dataset.type === 'node') {\n            Object.keys(discoveryOverrides).forEach(function(otherIp) {\n              if (discoveryOverrides[otherIp].assignedRackRef === 'new:' + ip) {\n                delete discoveryOverrides[otherIp].assignedRackRef;\n                delete discoveryOverrides[otherIp].rackUnit;\n              }\n            });\n          }\n          updateRackDropdowns();\n        }\n      });\n    });\n\n    document.querySelectorAll('.disc-row-edit-btn').forEach(function(btn) {\n      btn.addEventListener('click', function(e) {\n        e.stopPropagation();\n        var ip = btn.getAttribute('data-ip');\n        var editModalEl = document.getElementById('verse-disc-edit-modal');\n        if (editModalEl && _renderEditModal) {\n          _renderEditModal(ip);\n          editModalEl.classList.add('active');\n        }\n      });\n    });\n\n    document.querySelectorAll('.deepscan-start-btn').forEach(function(btn) {\n      btn.addEventListener('click', function(e) {\n        e.stopPropagation();\n        var ip = btn.getAttribute('data-ip');\n        var host = null;\n        for (var i = 0; i < discoveryResults.length; i++) {\n          if (discoveryResults[i].ip === ip) { host = discoveryResults[i]; break; }\n        }\n        if (!host) return;\n        btn.disabled = true;\n        btn.textContent = 'Starting...';\n        fetch('/api/discover/deepscan', {\n          method: 'POST',\n          headers: { 'Content-Type': 'application/json', 'x-csrf-token': window.CSRF_TOKEN },\n          body: JSON.stringify({\n            roomId: ROOM_ID,\n            ip: ip,\n            existingPorts: host.ports || []\n          })\n        }).then(function(resp) {\n          refreshCsrf();\n          return resp.json().then(function(result) {\n            if (!resp.ok) {\n              btn.textContent = 'Deep Scan';\n              btn.disabled = false;\n              return;\n            }\n            activeDeepScans[ip] = { scanId: result.scanId, percent: 0 };\n            renderDiscoveryResults();\n          });\n        }).catch(function() {\n          btn.textContent = 'Deep Scan';\n          btn.disabled = false;\n        });\n      });\n    });\n\n    document.querySelectorAll('.deepscan-cancel-btn').forEach(function(btn) {\n      btn.addEventListener('click', function(e) {\n        e.stopPropagation();\n        var ip = btn.getAttribute('data-ip');\n        var active = activeDeepScans[ip];\n        if (!active) return;\n        fetch('/api/discover/deepscan/cancel', {\n          method: 'POST',\n          headers: { 'Content-Type': 'application/json', 'x-csrf-token': window.CSRF_TOKEN },\n          body: JSON.stringify({ scanId: active.scanId })\n        }).then(function() { refreshCsrf(); }).catch(function() { refreshCsrf(); });\n        delete activeDeepScans[ip];\n        renderDiscoveryResults();\n      });\n    });\n\n    var tableSearch = document.getElementById('verse-disc-table-search');\n    if (tableSearch) {\n      var filterRows = function() {\n        var q = tableSearch.value.toLowerCase().trim();\n        var rows = document.querySelectorAll('#verse-discovery-tbody tr');\n        rows.forEach(function(row) {\n          var ip = (row.getAttribute('data-ip') || '').toLowerCase();\n          var text = row.textContent.toLowerCase();\n          row.style.display = (!q || ip.indexOf(q) !== -1 || text.indexOf(q) !== -1) ? '' : 'none';\n        });\n      };\n      tableSearch.addEventListener('input', filterRows);\n      if (savedSearch) {\n        tableSearch.value = savedSearch;\n        filterRows();\n      }\n    }\n  }\n\n  function escapeHtml(str) {\n    if (!str) return '';\n    return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\"/g, '&quot;');\n  }\n\n  function injectDiscoveryUI() {\n    if (!_rc.discoveryAllowed) return;\n\n    var waitForPing = setInterval(function() {\n      var autoPingSection = document.getElementById('auto-ping-settings');\n      var checkAllBtn = document.getElementById('check-all-ping-btn');\n      var target = autoPingSection || checkAllBtn;\n      if (!target) return;\n      clearInterval(waitForPing);\n\n      var btn = document.createElement('button');\n      btn.id = 'verse-discover-btn';\n      btn.style.cssText = 'display:block;width:100%;padding:10px;margin-top:12px;background:var(--accent);color:var(--bg);border:none;border-radius:6px;cursor:pointer;font-size:14px;font-weight:600';\n      btn.textContent = 'Discover Network Hosts';\n      btn.addEventListener('click', function() {\n        var modal = document.getElementById('verse-discovery-modal');\n        if (modal) modal.classList.add('active');\n        var portRef = document.getElementById('verse-disc-port-ref-body');\n        if (portRef && !portRef.dataset.loaded) {\n          fetch('/api/discover/ports').then(function(r) { return r.json(); }).then(function(data) {\n            if (!data.ports) return;\n            portRef.dataset.loaded = '1';\n            var summary = portRef.parentElement.querySelector('summary');\n            if (summary) summary.textContent = 'Default Scan Ports (' + data.ports.length + ')';\n            var html = '<div style=\"display:flex;flex-wrap:wrap;gap:4px\">';\n            data.ports.forEach(function(p) {\n              var iconHtml = p.icon ? '<span class=\"disc-port-ref-icon\" data-icon-name=\"' + escapeHtml(p.icon) + '\" style=\"display:inline-flex;width:14px;height:14px;margin-right:3px;vertical-align:middle\"></span>' : '';\n              html += '<span style=\"display:inline-flex;align-items:center;padding:2px 8px;background:var(--panel);border:1px solid var(--edge-main);border-radius:4px;font-size:11px;color:var(--text-main)\">' + iconHtml + '<strong>' + p.port + '</strong>&nbsp;<span style=\"color:var(--text-soft)\">' + escapeHtml(p.service) + '</span></span>';\n            });\n            html += '</div>';\n            portRef.innerHTML = html;\n            portRef.querySelectorAll('.disc-port-ref-icon').forEach(function(el) {\n              fetchDiscoveryIcon('selfhst', el.dataset.iconName, el, 14);\n            });\n          }).catch(function() {});\n        }\n      });\n      var details = target.closest('details') || target.parentNode;\n      details.parentNode.insertBefore(btn, details.nextSibling);\n    }, 200);\n\n    var modal = document.createElement('div');\n    modal.id = 'verse-discovery-modal';\n    modal.style.cssText = 'position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.7);z-index:10000001;justify-content:center;align-items:center';\n    modal.innerHTML =\n      '<div style=\"background:var(--panel);border-radius:12px;width:80%;max-height:85vh;display:flex;flex-direction:column;border:1px solid var(--edge-main);overflow:hidden\">' +\n        '<div style=\"padding:16px 20px;border-bottom:1px solid var(--edge-main);display:flex;justify-content:space-between;align-items:center\">' +\n          '<h3 style=\"margin:0;font-size:16px;color:var(--text-main)\">Network Discovery</h3>' +\n          '<button id=\"verse-discovery-close\" style=\"background:none;border:none;color:var(--text-soft);font-size:20px;cursor:pointer;padding:0 4px\">\\u2715</button>' +\n        '</div>' +\n        '<div style=\"padding:20px;overflow-y:auto;flex:1\">' +\n          '<div style=\"margin-bottom:12px\">' +\n            '<label style=\"display:block;margin-bottom:4px;font-size:13px;color:var(--text-soft)\">Subnet</label>' +\n            '<div style=\"display:flex;gap:8px;align-items:center\">' +\n              '<select id=\"verse-disc-preset\" style=\"flex:1;padding:8px;border-radius:6px;border:1px solid var(--edge-main);background:var(--panel-alt);color:var(--text-main)\">' +\n                '<option value=\"192.168.1.0/24\">192.168.1.0/24 Home Network (.1 Gateway)</option>' +\n                '<option value=\"192.168.0.0/24\">192.168.0.0/24 Home Network (.0 Gateway)</option>' +\n                '<option value=\"10.0.0.0/24\">10.0.0.0/24 Small Office / VPN</option>' +\n                '<option value=\"10.0.1.0/24\">10.0.1.0/24 Enterprise VLAN 1</option>' +\n                '<option value=\"10.0.2.0/24\">10.0.2.0/24 Enterprise VLAN 2</option>' +\n                '<option value=\"172.16.0.0/24\">172.16.0.0/24 Enterprise Private</option>' +\n                '<option value=\"172.16.1.0/24\">172.16.1.0/24 Enterprise Private 2</option>' +\n                '<option value=\"custom\">Custom...</option>' +\n              '</select>' +\n              '<button id=\"verse-disc-add-range\" style=\"padding:8px 12px;background:var(--accent);color:var(--bg);border:none;border-radius:6px;cursor:pointer;font-weight:600;white-space:nowrap\">+ Add Range</button>' +\n            '</div>' +\n          '</div>' +\n          '<div id=\"verse-disc-custom-row\" style=\"display:none;margin-bottom:12px\">' +\n            '<input type=\"text\" id=\"verse-disc-custom-cidr\" placeholder=\"e.g. 10.10.0.0/24\" style=\"width:100%;padding:8px;border-radius:6px;border:1px solid var(--edge-main);background:var(--panel-alt);color:var(--text-main)\">' +\n          '</div>' +\n          '<div id=\"verse-disc-ranges\" style=\"display:flex;flex-wrap:wrap;gap:6px;margin-bottom:12px\"></div>' +\n          '<div style=\"display:flex;gap:14px;flex-wrap:wrap;margin-bottom:12px;align-items:center\">' +\n            '<div style=\"display:flex;align-items:center;gap:6px\"><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"verse-disc-icmp\" checked><span class=\"toggle-slider\"></span></label><span style=\"font-size:13px;color:var(--text-soft)\">ICMP</span></div>' +\n            '<div style=\"display:flex;align-items:center;gap:6px\"><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"verse-disc-tcp\" checked><span class=\"toggle-slider\"></span></label><span style=\"font-size:13px;color:var(--text-soft)\">TCP Ports</span></div>' +\n            '<div style=\"display:flex;align-items:center;gap:6px\"><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"verse-disc-dns\" checked><span class=\"toggle-slider\"></span></label><span style=\"font-size:13px;color:var(--text-soft)\">DNS</span></div>' +\n            '<div style=\"display:flex;align-items:center;gap:6px\"><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"verse-disc-netbios\" checked><span class=\"toggle-slider\"></span></label><span style=\"font-size:13px;color:var(--text-soft)\">NetBIOS</span></div>' +\n            '<div style=\"display:flex;align-items:center;gap:6px\"><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"verse-disc-mdns\" checked><span class=\"toggle-slider\"></span></label><span style=\"font-size:13px;color:var(--text-soft)\">mDNS</span></div>' +\n            '<div style=\"display:flex;align-items:center;gap:6px\"><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"verse-disc-snmp\"><span class=\"toggle-slider\"></span></label><span style=\"font-size:13px;color:var(--text-soft)\">SNMP</span></div>' +\n          '</div>' +\n          '<div style=\"display:flex;gap:8px;margin-bottom:12px;flex-wrap:wrap\">' +\n            '<div style=\"flex:1;min-width:200px\">' +\n              '<label style=\"display:block;margin-bottom:4px;font-size:12px;color:var(--text-soft)\">TCP Ports</label>' +\n              '<input type=\"text\" id=\"verse-disc-ports\" placeholder=\"Leave empty for all 50+ ports or enter custom\" style=\"width:100%;padding:6px 8px;border-radius:6px;border:1px solid var(--edge-main);background:var(--panel-alt);color:var(--text-main);font-size:12px\">' +\n            '</div>' +\n            '<div id=\"verse-disc-snmp-row\" style=\"display:none;min-width:140px\">' +\n              '<label style=\"display:block;margin-bottom:4px;font-size:12px;color:var(--text-soft)\">SNMP Community</label>' +\n              '<input type=\"text\" id=\"verse-disc-snmp-community\" value=\"public\" style=\"width:100%;padding:6px 8px;border-radius:6px;border:1px solid var(--edge-main);background:var(--panel-alt);color:var(--text-main);font-size:12px\">' +\n            '</div>' +\n          '</div>' +\n          '<details id=\"verse-disc-port-ref\" style=\"margin-bottom:12px\">' +\n            '<summary style=\"cursor:pointer;font-size:12px;color:var(--text-soft);user-select:none;padding:4px 0\">Default Scan Ports (loading...)</summary>' +\n            '<div id=\"verse-disc-port-ref-body\" style=\"max-height:200px;overflow-y:auto;margin-top:6px;padding:8px;background:var(--panel-alt);border-radius:6px;border:1px solid var(--edge-main)\">' +\n            '</div>' +\n          '</details>' +\n          '<div style=\"display:flex;gap:8px;margin-bottom:12px\">' +\n            '<button id=\"verse-discovery-start\" style=\"padding:8px 20px;background:var(--accent);color:var(--bg);border:none;border-radius:6px;cursor:pointer;font-weight:600\">Start Scan</button>' +\n            '<button id=\"verse-discovery-cancel\" style=\"display:none;padding:8px 20px;background:var(--danger);color:#fff;border:none;border-radius:6px;cursor:pointer;font-weight:600\">Cancel</button>' +\n          '</div>' +\n          '<div style=\"margin-bottom:12px;padding:8px 12px;background:var(--panel-alt);border-radius:6px;border:1px solid var(--edge-main)\">' +\n            '<div style=\"display:flex;justify-content:space-between;align-items:center;margin-bottom:6px\">' +\n              '<span id=\"verse-discovery-progress-text\" style=\"font-size:12px;color:var(--text-soft)\">Ready</span>' +\n              '<span id=\"verse-discovery-count\" style=\"font-size:12px;color:var(--text-soft)\"></span>' +\n            '</div>' +\n            '<div style=\"width:100%;height:6px;background:var(--edge-main);border-radius:3px;overflow:hidden\">' +\n              '<div id=\"verse-discovery-progress-fill\" style=\"width:0%;height:100%;background:var(--accent);border-radius:3px;transition:width 0.3s\"></div>' +\n            '</div>' +\n          '</div>' +\n          '<input type=\"text\" id=\"verse-disc-table-search\" placeholder=\"Search by IP or name...\" style=\"width:100%;padding:6px 10px;border-radius:6px;border:1px solid var(--edge-main);background:var(--panel-alt);color:var(--text-main);font-size:12px;margin-bottom:8px;box-sizing:border-box\">' +\n          '<div style=\"overflow-x:auto\">' +\n            '<table style=\"width:100%;border-collapse:collapse;font-size:13px\">' +\n              '<thead><tr style=\"border-bottom:2px solid var(--edge-main)\">' +\n                '<th style=\"padding:8px;text-align:left;width:50px\"><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"verse-discovery-select-all\" checked><span class=\"toggle-slider\"></span></label></th>' +\n                '<th style=\"padding:8px;text-align:left;color:var(--text-soft)\">IP</th>' +\n                '<th style=\"padding:8px;text-align:left;color:var(--text-soft)\">Hostname <button id=\"verse-disc-edit-btn\" title=\"Edit host details before adding\" style=\"padding:2px 6px;font-size:10px;background:var(--panel);color:var(--accent);border:1px solid var(--accent);border-radius:4px;cursor:pointer;vertical-align:middle;display:none\">Edit All</button></th>' +\n                '<th style=\"padding:8px;text-align:left;color:var(--text-soft)\">Services</th>' +\n                '<th style=\"padding:8px;text-align:center;color:var(--text-soft)\">Icon</th>' +\n                '<th style=\"padding:8px;text-align:left;color:var(--text-soft)\">Type</th>' +\n                '<th style=\"padding:8px;text-align:left;color:var(--text-soft)\">Rack</th>' +\n                '<th style=\"padding:8px;text-align:left;color:var(--text-soft)\">Layer</th>' +\n              '</tr></thead>' +\n              '<tbody id=\"verse-discovery-tbody\"></tbody>' +\n            '</table>' +\n          '</div>' +\n        '</div>' +\n        '<div style=\"padding:12px 20px;border-top:1px solid var(--edge-main);display:flex;justify-content:flex-end\">' +\n          '<button id=\"verse-discovery-add\" style=\"padding:8px 20px;background:var(--accent);color:var(--bg);border:none;border-radius:6px;cursor:pointer;font-weight:600\">Add Selected to Canvas</button>' +\n        '</div>' +\n      '</div>';\n\n    document.body.appendChild(modal);\n\n    var editModal = document.createElement('div');\n    editModal.id = 'verse-disc-edit-modal';\n    editModal.style.cssText = 'position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.75);z-index:10000002;justify-content:center;align-items:center';\n    editModal.innerHTML =\n      '<div style=\"background:var(--panel);border-radius:12px;width:80%;max-height:85vh;display:flex;flex-direction:column;border:1px solid var(--edge-main);overflow:hidden\">' +\n        '<div style=\"padding:16px 20px;border-bottom:1px solid var(--edge-main);display:flex;align-items:center;gap:12px\">' +\n          '<h3 style=\"margin:0;font-size:16px;color:var(--text-main);white-space:nowrap\">Edit Host Details</h3>' +\n          '<input type=\"text\" id=\"verse-disc-edit-search\" placeholder=\"Search by IP or name...\" style=\"flex:1;padding:6px 10px;border-radius:6px;border:1px solid var(--edge-main);background:var(--panel-alt);color:var(--text-main);font-size:12px\">' +\n          '<button id=\"verse-disc-edit-close\" style=\"background:none;border:none;color:var(--text-soft);font-size:20px;cursor:pointer;padding:0 4px\">\\u2715</button>' +\n        '</div>' +\n        '<div id=\"verse-disc-edit-body\" style=\"padding:20px;overflow-y:auto;flex:1\"></div>' +\n        '<div style=\"padding:12px 20px;border-top:1px solid var(--edge-main);display:flex;justify-content:flex-end\">' +\n          '<button id=\"verse-disc-edit-done\" style=\"padding:8px 20px;background:var(--accent);color:var(--bg);border:none;border-radius:6px;cursor:pointer;font-weight:600\">Done</button>' +\n        '</div>' +\n      '</div>';\n    document.body.appendChild(editModal);\n    _renderEditModal = renderEditModal;\n\n    function buildShapeOptions(category, selectedShape) {\n      var shapes = DISC_SHAPES[category] || DISC_SHAPES.basic;\n      return shapes.map(function(s) {\n        return '<option value=\"' + s + '\"' + (s === selectedShape ? ' selected' : '') + '>' + s + '</option>';\n      }).join('');\n    }\n\n    function buildCategoryOptions(selected) {\n      var cats = Object.keys(DISC_SHAPES);\n      return cats.map(function(c) {\n        return '<option value=\"' + c + '\"' + (c === selected ? ' selected' : '') + '>' + c + '</option>';\n      }).join('');\n    }\n\n    function renderIconTagBadges(container, ip) {\n      container.innerHTML = '';\n      var tags = (discoveryOverrides[ip] && discoveryOverrides[ip].iconTags) || [];\n      tags.forEach(function(tag, ti) {\n        var badge = document.createElement('span');\n        badge.style.cssText = 'display:inline-flex;align-items:center;gap:4px;padding:2px 8px;background:var(--panel);border:1px solid var(--edge-main);border-radius:12px;font-size:11px;color:var(--text-main)';\n        var svgSpan = '';\n        if (tag.svg) svgSpan = '<span style=\"width:16px;height:16px;display:inline-flex;align-items:center;justify-content:center\">' + tag.svg + '</span>';\n        badge.innerHTML = svgSpan + '<span>' + escapeHtml(tag.name) + '</span>';\n        var removeBtn = document.createElement('button');\n        removeBtn.style.cssText = 'background:none;border:none;color:var(--danger);cursor:pointer;font-size:12px;padding:0 2px;line-height:1';\n        removeBtn.textContent = '\\u2715';\n        removeBtn.addEventListener('click', function() {\n          if (discoveryOverrides[ip] && discoveryOverrides[ip].iconTags) {\n            discoveryOverrides[ip].iconTags.splice(ti, 1);\n            renderIconTagBadges(container, ip);\n          }\n        });\n        badge.appendChild(removeBtn);\n        container.appendChild(badge);\n      });\n    }\n\n    function renderEditModal(scrollToIp) {\n      var body = document.getElementById('verse-disc-edit-body');\n      if (!body) return;\n      body.innerHTML = '';\n      var searchInput = document.getElementById('verse-disc-edit-search');\n      if (searchInput) searchInput.value = '';\n\n      var nd = getGlobal('NODE_DATA') || {};\n      var canvasIPs = {};\n      Object.keys(nd).forEach(function(nid) {\n        if (nd[nid] && nd[nid].ip) canvasIPs[nd[nid].ip] = true;\n      });\n\n      var hasCards = false;\n\n      discoveryResults.forEach(function(host, idx) {\n        if (!host) return;\n\n        hasCards = true;\n        var ov = discoveryOverrides[host.ip] || {};\n        var displayName = ov.name || host.hostname || host.ip;\n        var ipVal = ov.ip || host.ip;\n        var tagsVal = ov.tags || '';\n        var cat = ov.category || 'network';\n        var shp = ov.shape || 'circle';\n        var pingEnabled = ov.pingEnabled !== undefined ? ov.pingEnabled : true;\n        var probeType = ov.probeType || 'auto';\n        var tcpPorts = ov.tcpPorts || (host.ports && host.ports.length > 0 ? host.ports.join(', ') : '22, 80, 443');\n        var pingTimeout = ov.pingTimeout || 3000;\n        var rackCap = ov.rackCapacity || '42';\n        var iconPreview = '';\n        if (ov.iconData && ov.iconData.svg) {\n          iconPreview = '<span style=\"width:24px;height:24px;display:inline-flex;align-items:center;justify-content:center\">' + ov.iconData.svg + '</span>' +\n            '<span style=\"font-size:11px;color:var(--accent)\">' + escapeHtml(ov.iconData.library + '/' + ov.iconData.name) + '</span>';\n        } else if (ov.iconData && ov.iconData.name) {\n          iconPreview = '<span class=\"disc-edit-icon-fetch\" data-lib=\"' + escapeHtml(ov.iconData.library) + '\" data-name=\"' + escapeHtml(ov.iconData.name) + '\" style=\"width:24px;height:24px;display:inline-flex;align-items:center;justify-content:center\"></span>' +\n            '<span style=\"font-size:11px;color:var(--accent)\">' + escapeHtml(ov.iconData.library + '/' + ov.iconData.name) + '</span>';\n        }\n\n        var toggle = document.querySelector('.verse-type-toggle[data-ip=\"' + host.ip + '\"]');\n        var activeBtn = toggle ? toggle.querySelector('.verse-type-btn.active') : null;\n        var isRack = (ov.typeToggle === 'rack') || (activeBtn && activeBtn.dataset.type === 'rack');\n        var rackDisplay = isRack ? 'flex' : 'none';\n        var tcpDisplay = (probeType === 'tcp' || probeType === 'multi') ? 'block' : 'none';\n\n        var onCanvas = canvasIPs[host.ip];\n        var canvasTag = onCanvas ? ' <span style=\"font-size:10px;font-weight:400;color:var(--accent);font-style:italic;margin-left:6px\">on canvas</span>' : '';\n\n        var card = document.createElement('div');\n        card.style.cssText = 'margin-bottom:16px;padding:14px;border:1px solid var(--edge-main);border-radius:8px;background:var(--panel-alt);transition:border-color 0.3s' + (onCanvas ? ';opacity:0.6' : '');\n        card.setAttribute('data-edit-ip', host.ip);\n        card.innerHTML =\n          '<div style=\"margin-bottom:10px;font-weight:600;font-size:13px;color:var(--text-main);font-family:monospace\">' + escapeHtml(host.ip) + canvasTag + '</div>' +\n          '<div style=\"display:grid;grid-template-columns:1fr 1fr;gap:8px\">' +\n            '<div>' +\n              '<label style=\"display:block;font-size:11px;color:var(--text-soft);margin-bottom:2px\">Name</label>' +\n              '<input type=\"text\" class=\"disc-edit-name\" value=\"' + escapeHtml(displayName) + '\" style=\"width:100%;padding:6px 8px;border-radius:4px;border:1px solid var(--edge-main);background:var(--panel);color:var(--text-main);font-size:12px\">' +\n            '</div>' +\n            '<div>' +\n              '<label style=\"display:block;font-size:11px;color:var(--text-soft);margin-bottom:2px\">IP</label>' +\n              '<input type=\"text\" class=\"disc-edit-ip\" value=\"' + escapeHtml(ipVal) + '\" style=\"width:100%;padding:6px 8px;border-radius:4px;border:1px solid var(--edge-main);background:var(--panel);color:var(--text-main);font-size:12px\">' +\n            '</div>' +\n            '<div style=\"grid-column:1/-1\">' +\n              '<label style=\"display:block;font-size:11px;color:var(--text-soft);margin-bottom:2px\">Tags (comma separated)</label>' +\n              '<input type=\"text\" class=\"disc-edit-tags\" value=\"' + escapeHtml(tagsVal) + '\" placeholder=\"e.g. Production, Core, VLAN10\" style=\"width:100%;padding:6px 8px;border-radius:4px;border:1px solid var(--edge-main);background:var(--panel);color:var(--text-main);font-size:12px\">' +\n            '</div>' +\n            '<div style=\"grid-column:1/-1;display:flex;align-items:center;gap:8px;flex-wrap:wrap\">' +\n              '<button class=\"disc-edit-icon-tag-btn\" style=\"padding:4px 10px;background:var(--panel);color:var(--text-main);border:1px solid var(--edge-main);border-radius:4px;cursor:pointer;font-size:11px\">Add Web Icon Tag</button>' +\n              '<div class=\"disc-edit-icon-tags-list\" style=\"display:flex;flex-wrap:wrap;gap:6px\"></div>' +\n            '</div>' +\n            '<div style=\"grid-column:1/-1;display:grid;grid-template-columns:1fr 1fr 56px;gap:8px;align-items:end\">' +\n              '<div>' +\n                '<label style=\"display:block;font-size:11px;color:var(--text-soft);margin-bottom:2px\">Category</label>' +\n                '<select class=\"disc-edit-category\" style=\"width:100%;padding:6px 8px;border-radius:4px;border:1px solid var(--edge-main);background:var(--panel);color:var(--text-main);font-size:12px\">' + buildCategoryOptions(cat) + '</select>' +\n              '</div>' +\n              '<div>' +\n                '<label style=\"display:block;font-size:11px;color:var(--text-soft);margin-bottom:2px\">Shape</label>' +\n                '<select class=\"disc-edit-shape\" style=\"width:100%;padding:6px 8px;border-radius:4px;border:1px solid var(--edge-main);background:var(--panel);color:var(--text-main);font-size:12px\">' + buildShapeOptions(cat, shp) + '</select>' +\n              '</div>' +\n              '<div class=\"disc-edit-shape-preview\" style=\"display:flex;align-items:center;justify-content:center;width:48px;height:48px;border:1px solid var(--edge-main);border-radius:6px;background:var(--panel)\"' + (ov.iconData && ov.iconData.name && !ov.iconData.svg ? ' data-fetch-lib=\"' + escapeHtml(ov.iconData.library) + '\" data-fetch-name=\"' + escapeHtml(ov.iconData.name) + '\"' : '') + '>' +\n                (ov.iconData && ov.iconData.svg ? '<span style=\"width:36px;height:36px;display:inline-flex;align-items:center;justify-content:center\">' + ov.iconData.svg + '</span>' : getShapePreviewSVG(shp || 'circle')) +\n              '</div>' +\n            '</div>' +\n            '<div style=\"grid-column:1/-1;display:flex;align-items:center;gap:8px\">' +\n              '<button class=\"disc-edit-icon-btn\" style=\"padding:6px 12px;background:var(--accent);color:var(--bg);border:none;border-radius:4px;cursor:pointer;font-size:11px;font-weight:600\">Or Search Web Icons</button>' +\n              '<span class=\"disc-edit-icon-preview\" style=\"display:inline-flex;align-items:center;gap:6px\">' + iconPreview + '</span>' +\n            '</div>' +\n            '<div class=\"disc-edit-rack-section\" style=\"grid-column:1/-1;display:' + rackDisplay + ';align-items:center;gap:8px;padding:8px;border:1px solid var(--edge-main);border-radius:4px;background:var(--panel)\">' +\n              '<label style=\"font-size:11px;color:var(--text-soft);white-space:nowrap\">Rack Capacity</label>' +\n              '<select class=\"disc-edit-rack-cap\" style=\"flex:1;padding:6px 8px;border-radius:4px;border:1px solid var(--edge-main);background:var(--panel-alt);color:var(--text-main);font-size:12px\">' +\n                '<option value=\"6\"' + (rackCap === '6' ? ' selected' : '') + '>6U Mini Rack</option>' +\n                '<option value=\"12\"' + (rackCap === '12' ? ' selected' : '') + '>12U Small Wall Mount</option>' +\n                '<option value=\"24\"' + (rackCap === '24' ? ' selected' : '') + '>24U Half Rack</option>' +\n                '<option value=\"42\"' + (rackCap === '42' ? ' selected' : '') + '>42U Standard Full Rack</option>' +\n                '<option value=\"48\"' + (rackCap === '48' ? ' selected' : '') + '>48U Large Rack</option>' +\n              '</select>' +\n            '</div>' +\n            '<div style=\"grid-column:1/-1;border-top:1px solid var(--edge-main);padding-top:8px;margin-top:4px\">' +\n              '<div style=\"display:flex;align-items:center;gap:8px;margin-bottom:6px\">' +\n                '<span style=\"font-size:11px;color:var(--text-soft)\">Probe Enabled</span>' +\n                '<label class=\"toggle-switch\"><input type=\"checkbox\" class=\"disc-edit-ping-enabled\"' + (pingEnabled ? ' checked' : '') + '><span class=\"toggle-slider\"></span></label>' +\n              '</div>' +\n              '<div style=\"display:flex;gap:8px;flex-wrap:wrap\">' +\n                '<div style=\"flex:1;min-width:140px\">' +\n                  '<label style=\"display:block;font-size:11px;color:var(--text-soft);margin-bottom:2px\">Probe Type</label>' +\n                  '<select class=\"disc-edit-probe-type\" style=\"width:100%;padding:6px 8px;border-radius:4px;border:1px solid var(--edge-main);background:var(--panel);color:var(--text-main);font-size:12px\">' +\n                    '<option value=\"auto\"' + (probeType === 'auto' ? ' selected' : '') + '>Auto (ICMP + HTTP)</option>' +\n                    '<option value=\"icmp\"' + (probeType === 'icmp' ? ' selected' : '') + '>ICMP Ping Only</option>' +\n                    '<option value=\"tcp\"' + (probeType === 'tcp' ? ' selected' : '') + '>TCP Port Check</option>' +\n                    '<option value=\"http\"' + (probeType === 'http' ? ' selected' : '') + '>HTTP/HTTPS Only</option>' +\n                    '<option value=\"multi\"' + (probeType === 'multi' ? ' selected' : '') + '>Multi Probe</option>' +\n                  '</select>' +\n                '</div>' +\n                '<div style=\"min-width:80px\">' +\n                  '<label style=\"display:block;font-size:11px;color:var(--text-soft);margin-bottom:2px\">Timeout (ms)</label>' +\n                  '<input type=\"number\" class=\"disc-edit-ping-timeout\" value=\"' + pingTimeout + '\" min=\"1000\" max=\"10000\" step=\"500\" style=\"width:100%;padding:6px 8px;border-radius:4px;border:1px solid var(--edge-main);background:var(--panel);color:var(--text-main);font-size:12px\">' +\n                '</div>' +\n              '</div>' +\n              '<div class=\"disc-edit-tcp-row\" style=\"display:' + tcpDisplay + ';margin-top:6px\">' +\n                '<label style=\"display:block;font-size:11px;color:var(--text-soft);margin-bottom:2px\">TCP Ports</label>' +\n                '<input type=\"text\" class=\"disc-edit-tcp-ports\" value=\"' + escapeHtml(tcpPorts) + '\" placeholder=\"22, 80, 443, 3389\" style=\"width:100%;padding:6px 8px;border-radius:4px;border:1px solid var(--edge-main);background:var(--panel);color:var(--text-main);font-size:12px\">' +\n              '</div>' +\n            '</div>' +\n            '<div style=\"grid-column:1/-1;border-top:1px solid var(--edge-main);padding-top:8px;margin-top:4px\">' +\n              '<div style=\"font-weight:600;font-size:11px;color:var(--text-main);margin-bottom:6px\">Appearance</div>' +\n              '<div style=\"display:flex;gap:8px;align-items:center;flex-wrap:wrap\">' +\n                '<div style=\"display:flex;align-items:center;gap:4px\">' +\n                  '<label style=\"font-size:11px;color:var(--text-soft)\">Fill</label>' +\n                  '<input type=\"color\" class=\"disc-edit-fill-color\" value=\"' + (ov.fillColor || '#1e293b') + '\" style=\"width:30px;height:24px;border:1px solid var(--edge-main);border-radius:4px;cursor:pointer;background:none;padding:0\">' +\n                '</div>' +\n                '<div style=\"display:flex;align-items:center;gap:4px\">' +\n                  '<label style=\"font-size:11px;color:var(--text-soft)\">Border</label>' +\n                  '<input type=\"color\" class=\"disc-edit-border-color\" value=\"' + (ov.borderColor || '#475569') + '\" style=\"width:30px;height:24px;border:1px solid var(--edge-main);border-radius:4px;cursor:pointer;background:none;padding:0\">' +\n                '</div>' +\n                '<div style=\"display:flex;align-items:center;gap:4px;flex:1;min-width:120px\">' +\n                  '<label style=\"font-size:11px;color:var(--text-soft)\">Size</label>' +\n                  '<input type=\"range\" class=\"disc-edit-size\" min=\"20\" max=\"200\" value=\"' + (ov.nodeSize || 50) + '\" style=\"flex:1;accent-color:var(--accent)\">' +\n                  '<span class=\"disc-edit-size-value\" style=\"font-size:11px;color:var(--text-soft);min-width:24px\">' + (ov.nodeSize || 50) + '</span>' +\n                '</div>' +\n              '</div>' +\n            '</div>' +\n            '<div style=\"grid-column:1/-1;border-top:1px solid var(--edge-main);padding-top:8px;margin-top:4px\">' +\n              '<div style=\"font-weight:600;font-size:11px;color:var(--text-main);margin-bottom:6px\">Connections</div>' +\n              '<div style=\"display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin-bottom:6px\">' +\n                '<select class=\"disc-edit-connect-to\" style=\"flex:1;min-width:120px;padding:6px 8px;border-radius:4px;border:1px solid var(--edge-main);background:var(--panel);color:var(--text-main);font-size:12px\">' +\n                  '<option value=\"\">Connect to...</option>' +\n                '</select>' +\n                '<input type=\"text\" class=\"disc-edit-from-port\" placeholder=\"From port\" style=\"width:80px;padding:6px 8px;border-radius:4px;border:1px solid var(--edge-main);background:var(--panel);color:var(--text-main);font-size:11px\">' +\n                '<input type=\"text\" class=\"disc-edit-to-port\" placeholder=\"To port\" style=\"width:80px;padding:6px 8px;border-radius:4px;border:1px solid var(--edge-main);background:var(--panel);color:var(--text-main);font-size:11px\">' +\n                '<button class=\"disc-edit-add-conn-btn\" style=\"padding:4px 10px;background:var(--accent);color:var(--bg);border:none;border-radius:4px;cursor:pointer;font-size:11px;font-weight:600\">Add</button>' +\n              '</div>' +\n              '<div class=\"disc-edit-conn-list\" style=\"display:flex;flex-direction:column;gap:4px\"></div>' +\n            '</div>' +\n            '<div style=\"grid-column:1/-1;border-top:1px solid var(--edge-main);padding-top:8px;margin-top:4px\">' +\n              '<label style=\"display:block;font-size:11px;color:var(--text-soft);margin-bottom:2px\">Notes</label>' +\n              '<textarea class=\"disc-edit-notes\" rows=\"2\" placeholder=\"Add notes...\" style=\"width:100%;padding:6px 8px;border-radius:4px;border:1px solid var(--edge-main);background:var(--panel);color:var(--text-main);font-size:12px;resize:vertical;font-family:inherit\">' + escapeHtml(ov.notes || '') + '</textarea>' +\n            '</div>' +\n          '</div>';\n        body.appendChild(card);\n\n        var iconTagsList = card.querySelector('.disc-edit-icon-tags-list');\n        if (iconTagsList) renderIconTagBadges(iconTagsList, host.ip);\n      });\n\n      if (!hasCards) {\n        body.innerHTML = '<div style=\"text-align:center;padding:40px;color:var(--text-soft)\">No editable hosts selected. Select hosts in the discovery table first, or all selected hosts are already on canvas.</div>';\n      }\n\n      body.querySelectorAll('.disc-edit-category').forEach(function(sel) {\n        sel.addEventListener('change', function() {\n          var card = sel.closest('[data-edit-ip]');\n          var ip = card.getAttribute('data-edit-ip');\n          var shapeSel = card.querySelector('.disc-edit-shape');\n          shapeSel.innerHTML = buildShapeOptions(sel.value, '');\n          var preview = card.querySelector('.disc-edit-shape-preview');\n          if (preview) preview.innerHTML = getShapePreviewSVG(shapeSel.value);\n          if (discoveryOverrides[ip]) delete discoveryOverrides[ip].iconData;\n          var iconPreviewEl = card.querySelector('.disc-edit-icon-preview');\n          if (iconPreviewEl) iconPreviewEl.innerHTML = '';\n          saveCardOverride(card, ip);\n        });\n      });\n\n      body.querySelectorAll('.disc-edit-shape').forEach(function(sel) {\n        sel.addEventListener('change', function() {\n          var card = sel.closest('[data-edit-ip]');\n          var ip = card.getAttribute('data-edit-ip');\n          var preview = card.querySelector('.disc-edit-shape-preview');\n          if (preview) preview.innerHTML = getShapePreviewSVG(sel.value);\n          if (discoveryOverrides[ip]) delete discoveryOverrides[ip].iconData;\n          var iconPreviewEl = card.querySelector('.disc-edit-icon-preview');\n          if (iconPreviewEl) iconPreviewEl.innerHTML = '';\n          saveCardOverride(card, ip);\n        });\n      });\n\n      body.querySelectorAll('.disc-edit-probe-type').forEach(function(sel) {\n        sel.addEventListener('change', function() {\n          var card = sel.closest('[data-edit-ip]');\n          var tcpRow = card.querySelector('.disc-edit-tcp-row');\n          if (tcpRow) tcpRow.style.display = (sel.value === 'tcp' || sel.value === 'multi') ? 'block' : 'none';\n          var ip = card.getAttribute('data-edit-ip');\n          saveCardOverride(card, ip);\n        });\n      });\n\n      body.querySelectorAll('.disc-edit-icon-btn').forEach(function(btn) {\n        btn.addEventListener('click', function() {\n          var card = btn.closest('[data-edit-ip]');\n          var ip = card.getAttribute('data-edit-ip');\n          if (typeof window.openIconPicker === 'function') {\n            window.openIconPicker(function(iconData) {\n              if (!discoveryOverrides[ip]) discoveryOverrides[ip] = {};\n              discoveryOverrides[ip].iconData = iconData;\n              var preview = card.querySelector('.disc-edit-icon-preview');\n              if (preview && iconData && iconData.name) {\n                if (iconData.svg) {\n                  preview.innerHTML = '<span style=\"width:24px;height:24px;display:inline-flex;align-items:center;justify-content:center\">' + iconData.svg + '</span>' +\n                    '<span style=\"font-size:11px;color:var(--accent)\">' + escapeHtml(iconData.library + '/' + iconData.name) + '</span>';\n                } else {\n                  var fetchSpan = document.createElement('span');\n                  fetchSpan.style.cssText = 'width:24px;height:24px;display:inline-flex;align-items:center;justify-content:center';\n                  preview.innerHTML = '';\n                  preview.appendChild(fetchSpan);\n                  fetchDiscoveryIcon(iconData.library, iconData.name, fetchSpan, 24);\n                  var label = document.createElement('span');\n                  label.style.cssText = 'font-size:11px;color:var(--accent)';\n                  label.textContent = iconData.library + '/' + iconData.name;\n                  preview.appendChild(label);\n                }\n              }\n              var shapePreview = card.querySelector('.disc-edit-shape-preview');\n              if (shapePreview && iconData && iconData.name) {\n                if (iconData.svg) {\n                  shapePreview.innerHTML = '<span style=\"width:36px;height:36px;display:inline-flex;align-items:center;justify-content:center\">' + iconData.svg + '</span>';\n                } else {\n                  fetchDiscoveryIcon(iconData.library, iconData.name, shapePreview, 36);\n                }\n              }\n            });\n          }\n        });\n      });\n\n      body.querySelectorAll('.disc-edit-icon-tag-btn').forEach(function(btn) {\n        btn.addEventListener('click', function() {\n          var card = btn.closest('[data-edit-ip]');\n          var ip = card.getAttribute('data-edit-ip');\n          if (typeof window.openIconPicker === 'function') {\n            window.openIconPicker(function(iconData) {\n              if (!discoveryOverrides[ip]) discoveryOverrides[ip] = {};\n              if (!discoveryOverrides[ip].iconTags) discoveryOverrides[ip].iconTags = [];\n              discoveryOverrides[ip].iconTags.push({ type: 'icon', library: iconData.library, name: iconData.name, svg: iconData.svg || '' });\n              var list = card.querySelector('.disc-edit-icon-tags-list');\n              if (list) renderIconTagBadges(list, ip);\n            });\n          }\n        });\n      });\n\n      body.querySelectorAll('.disc-edit-shape-preview[data-fetch-name]').forEach(function(el) {\n        fetchDiscoveryIcon(el.getAttribute('data-fetch-lib'), el.getAttribute('data-fetch-name'), el, 36);\n      });\n\n      body.querySelectorAll('.disc-edit-icon-fetch').forEach(function(el) {\n        fetchDiscoveryIcon(el.getAttribute('data-lib'), el.getAttribute('data-name'), el, 24);\n      });\n\n      body.querySelectorAll('.disc-edit-connect-to').forEach(function(sel) {\n        var card = sel.closest('[data-edit-ip]');\n        var cardIp = card.getAttribute('data-edit-ip');\n        discoveryResults.forEach(function(h) {\n          if (!h || h.ip === cardIp) return;\n          var hov = discoveryOverrides[h.ip] || {};\n          var opt = document.createElement('option');\n          opt.value = h.ip;\n          opt.textContent = (hov.name || h.hostname || h.ip) + ' (' + h.ip + ')';\n          sel.appendChild(opt);\n        });\n        var nd = getGlobal('NODE_DATA') || {};\n        Object.entries(nd).forEach(function(entry) {\n          var opt = document.createElement('option');\n          opt.value = 'canvas:' + entry[0];\n          opt.textContent = entry[1].name + ' (canvas)';\n          sel.appendChild(opt);\n        });\n      });\n\n      body.querySelectorAll('.disc-edit-size').forEach(function(slider) {\n        slider.addEventListener('input', function() {\n          var card = slider.closest('[data-edit-ip]');\n          var valEl = card.querySelector('.disc-edit-size-value');\n          if (valEl) valEl.textContent = slider.value;\n          var ip = card.getAttribute('data-edit-ip');\n          saveCardOverride(card, ip);\n        });\n      });\n\n      body.querySelectorAll('.disc-edit-add-conn-btn').forEach(function(btn) {\n        btn.addEventListener('click', function() {\n          var card = btn.closest('[data-edit-ip]');\n          var ip = card.getAttribute('data-edit-ip');\n          var targetSel = card.querySelector('.disc-edit-connect-to');\n          var fromPort = card.querySelector('.disc-edit-from-port');\n          var toPort = card.querySelector('.disc-edit-to-port');\n          if (!targetSel.value) return;\n          if (!discoveryOverrides[ip]) discoveryOverrides[ip] = {};\n          if (!discoveryOverrides[ip].connections) discoveryOverrides[ip].connections = [];\n          discoveryOverrides[ip].connections.push({\n            target: targetSel.value,\n            fromPort: fromPort.value.trim(),\n            toPort: toPort.value.trim()\n          });\n          fromPort.value = '';\n          toPort.value = '';\n          targetSel.value = '';\n          renderConnList(card, ip);\n        });\n      });\n\n      function renderConnList(card, ip) {\n        var list = card.querySelector('.disc-edit-conn-list');\n        if (!list) return;\n        list.innerHTML = '';\n        var conns = (discoveryOverrides[ip] && discoveryOverrides[ip].connections) || [];\n        conns.forEach(function(conn, ci) {\n          var targetLabel = conn.target;\n          if (conn.target.indexOf('canvas:') === 0) {\n            var nid = conn.target.replace('canvas:', '');\n            var nd = getGlobal('NODE_DATA') || {};\n            if (nd[nid]) targetLabel = nd[nid].name + ' (canvas)';\n          } else {\n            var hov = discoveryOverrides[conn.target] || {};\n            var hst = discoveryResults.find(function(h) { return h && h.ip === conn.target; });\n            targetLabel = (hov.name || (hst && hst.hostname) || conn.target) + ' (' + conn.target + ')';\n          }\n          var row = document.createElement('div');\n          row.style.cssText = 'display:flex;align-items:center;gap:6px;padding:4px 8px;background:var(--panel);border:1px solid var(--edge-main);border-radius:4px;font-size:11px;color:var(--text-main)';\n          row.innerHTML = '<span style=\"flex:1\">' + escapeHtml(ip) + (conn.fromPort ? ':' + escapeHtml(conn.fromPort) : '') + ' \\u2192 ' + escapeHtml(targetLabel) + (conn.toPort ? ':' + escapeHtml(conn.toPort) : '') + '</span>';\n          var removeBtn = document.createElement('button');\n          removeBtn.style.cssText = 'background:none;border:none;color:var(--danger);cursor:pointer;font-size:12px;padding:0 4px';\n          removeBtn.textContent = '\\u2715';\n          removeBtn.addEventListener('click', function() {\n            discoveryOverrides[ip].connections.splice(ci, 1);\n            renderConnList(card, ip);\n          });\n          row.appendChild(removeBtn);\n          list.appendChild(row);\n        });\n      }\n\n      body.querySelectorAll('[data-edit-ip]').forEach(function(card) {\n        var ip = card.getAttribute('data-edit-ip');\n        renderConnList(card, ip);\n      });\n\n      body.querySelectorAll('input, select, textarea').forEach(function(el) {\n        el.addEventListener('change', function() {\n          var card = el.closest('[data-edit-ip]');\n          if (!card) return;\n          var ip = card.getAttribute('data-edit-ip');\n          saveCardOverride(card, ip);\n        });\n      });\n\n      if (scrollToIp) {\n        var target = body.querySelector('[data-edit-ip=\"' + scrollToIp + '\"]');\n        if (target) {\n          setTimeout(function() {\n            target.scrollIntoView({ behavior: 'smooth', block: 'start' });\n            target.style.borderColor = 'var(--accent)';\n            setTimeout(function() { target.style.borderColor = ''; }, 2000);\n          }, 100);\n        }\n      }\n    }\n\n    function saveCardOverride(card, ip) {\n      if (!discoveryOverrides[ip]) discoveryOverrides[ip] = {};\n      var ov = discoveryOverrides[ip];\n      var nameEl = card.querySelector('.disc-edit-name');\n      var ipEl = card.querySelector('.disc-edit-ip');\n      var tagsEl = card.querySelector('.disc-edit-tags');\n      var catEl = card.querySelector('.disc-edit-category');\n      var shapeEl = card.querySelector('.disc-edit-shape');\n      var rackCapEl = card.querySelector('.disc-edit-rack-cap');\n      var pingEnabledEl = card.querySelector('.disc-edit-ping-enabled');\n      var probeTypeEl = card.querySelector('.disc-edit-probe-type');\n      var tcpPortsEl = card.querySelector('.disc-edit-tcp-ports');\n      var pingTimeoutEl = card.querySelector('.disc-edit-ping-timeout');\n      var fillColorEl = card.querySelector('.disc-edit-fill-color');\n      var borderColorEl = card.querySelector('.disc-edit-border-color');\n      var sizeEl = card.querySelector('.disc-edit-size');\n      var notesEl = card.querySelector('.disc-edit-notes');\n      delete ov._seeded;\n      if (nameEl) ov.name = nameEl.value;\n      if (ipEl) ov.ip = ipEl.value;\n      if (tagsEl) ov.tags = tagsEl.value;\n      if (catEl) ov.category = catEl.value;\n      if (shapeEl) ov.shape = shapeEl.value;\n      if (rackCapEl) ov.rackCapacity = rackCapEl.value;\n      if (pingEnabledEl) ov.pingEnabled = pingEnabledEl.checked;\n      if (probeTypeEl) ov.probeType = probeTypeEl.value;\n      if (tcpPortsEl) ov.tcpPorts = tcpPortsEl.value;\n      if (pingTimeoutEl) ov.pingTimeout = parseInt(pingTimeoutEl.value, 10) || 3000;\n      if (fillColorEl) ov.fillColor = fillColorEl.value;\n      if (borderColorEl) ov.borderColor = borderColorEl.value;\n      if (sizeEl) ov.nodeSize = parseInt(sizeEl.value, 10);\n      if (notesEl) ov.notes = notesEl.value;\n    }\n\n    modal.addEventListener('click', function(e) {\n      if (e.target === modal) modal.classList.remove('active');\n    });\n\n    document.getElementById('verse-discovery-close').addEventListener('click', function() {\n      modal.classList.remove('active');\n    });\n\n    editModal.addEventListener('click', function(e) {\n      if (e.target === editModal) {\n        editModal.classList.remove('active');\n        renderDiscoveryResults();\n      }\n    });\n\n    document.getElementById('verse-disc-edit-close').addEventListener('click', function() {\n      editModal.classList.remove('active');\n      renderDiscoveryResults();\n    });\n\n    document.getElementById('verse-disc-edit-done').addEventListener('click', function() {\n      editModal.classList.remove('active');\n      renderDiscoveryResults();\n    });\n\n    document.getElementById('verse-disc-edit-btn').addEventListener('click', function() {\n      renderEditModal();\n      editModal.classList.add('active');\n    });\n\n    document.getElementById('verse-disc-edit-search').addEventListener('input', function() {\n      var q = this.value.toLowerCase().trim();\n      var body = document.getElementById('verse-disc-edit-body');\n      if (!body) return;\n      var cards = body.querySelectorAll('[data-edit-ip]');\n      var visibleCount = 0;\n      var lastVisible = null;\n      cards.forEach(function(card) {\n        var ip = card.getAttribute('data-edit-ip').toLowerCase();\n        var nameInput = card.querySelector('.disc-edit-name');\n        var name = nameInput ? nameInput.value.toLowerCase() : '';\n        if (!q || ip.indexOf(q) !== -1 || name.indexOf(q) !== -1) {\n          card.style.display = '';\n          visibleCount++;\n          lastVisible = card;\n        } else {\n          card.style.display = 'none';\n        }\n      });\n      if (visibleCount === 1 && lastVisible) {\n        lastVisible.scrollIntoView({ behavior: 'smooth', block: 'start' });\n      }\n    });\n\n    document.getElementById('verse-disc-preset').addEventListener('change', function() {\n      document.getElementById('verse-disc-custom-row').style.display = this.value === 'custom' ? 'block' : 'none';\n    });\n\n    document.getElementById('verse-disc-snmp').addEventListener('change', function() {\n      document.getElementById('verse-disc-snmp-row').style.display = this.checked ? 'block' : 'none';\n    });\n\n    document.getElementById('verse-disc-add-range').addEventListener('click', function() {\n      var cidr = getCurrentCIDR();\n      if (cidr && discoveryRanges.indexOf(cidr) === -1) {\n        discoveryRanges.push(cidr);\n        renderRangePills();\n      }\n    });\n\n    document.getElementById('verse-discovery-select-all').addEventListener('change', function(e) {\n      var checks = document.querySelectorAll('.verse-discovery-check');\n      checks.forEach(function(cb) { cb.checked = e.target.checked; });\n    });\n\n    document.getElementById('verse-discovery-start').addEventListener('click', async function() {\n      var cidrs = discoveryRanges.length > 0 ? discoveryRanges.slice() : getDiscoveryCIDRs();\n      if (cidrs.length === 0) return;\n\n      if (discoveryTaskId) {\n        try {\n          await fetch('/api/discover/cancel', {\n            method: 'POST',\n            headers: { 'Content-Type': 'application/json', 'x-csrf-token': window.CSRF_TOKEN },\n            body: JSON.stringify({ taskId: discoveryTaskId })\n          });\n          refreshCsrf();\n        } catch(e) {}\n        discoveryTaskId = null;\n      }\n\n      discoveryResults = [];\n      discoveryScanning = true;\n      renderDiscoveryResults();\n      updateDiscoveryProgress(0, 0, 0);\n\n      var btn = document.getElementById('verse-discovery-start');\n      var cancelBtn = document.getElementById('verse-discovery-cancel');\n      btn.textContent = 'Scanning...';\n      btn.disabled = true;\n      cancelBtn.style.display = 'inline-block';\n\n      var ports = parsePorts(document.getElementById('verse-disc-ports').value);\n\n      try {\n        var resp = await fetch('/api/discover', {\n          method: 'POST',\n          headers: { 'Content-Type': 'application/json', 'x-csrf-token': window.CSRF_TOKEN },\n          body: JSON.stringify({\n            cidrs: cidrs,\n            roomId: ROOM_ID,\n            options: {\n              icmp: document.getElementById('verse-disc-icmp').checked,\n              tcp: document.getElementById('verse-disc-tcp').checked,\n              dns: document.getElementById('verse-disc-dns').checked,\n              netbios: document.getElementById('verse-disc-netbios').checked,\n              mdns: document.getElementById('verse-disc-mdns').checked,\n              snmp: document.getElementById('verse-disc-snmp').checked,\n              snmpCommunity: document.getElementById('verse-disc-snmp-community').value || 'public',\n              ports: ports\n            }\n          })\n        });\n        refreshCsrf();\n        var result = await resp.json();\n        if (!resp.ok) {\n          btn.textContent = 'Start Scan';\n          btn.disabled = false;\n          cancelBtn.style.display = 'none';\n          discoveryScanning = false;\n          var progressText = document.getElementById('verse-discovery-progress-text');\n          if (progressText) progressText.textContent = 'Error: ' + (result.error || 'Scan failed');\n          return;\n        }\n        discoveryTaskId = result.taskId;\n      } catch(e) {\n        btn.textContent = 'Start Scan';\n        btn.disabled = false;\n        cancelBtn.style.display = 'none';\n        discoveryScanning = false;\n      }\n    });\n\n    document.getElementById('verse-discovery-cancel').addEventListener('click', async function() {\n      if (!discoveryTaskId) return;\n      try {\n        await fetch('/api/discover/cancel', {\n          method: 'POST',\n          headers: { 'Content-Type': 'application/json', 'x-csrf-token': window.CSRF_TOKEN },\n          body: JSON.stringify({ taskId: discoveryTaskId })\n        });\n        refreshCsrf();\n      } catch(e) {}\n      finalizeDiscovery(discoveryResults.length);\n    });\n\n    document.getElementById('verse-discovery-add').addEventListener('click', function() {\n      var checks = document.querySelectorAll('.verse-discovery-check:checked');\n      if (checks.length === 0) return;\n\n      var nd = getGlobal('NODE_DATA');\n      var positions = getGlobal('savedPositions');\n      var sizes = getGlobal('savedSizes');\n      var styles = getGlobal('savedStyles');\n      var pushUndo = window.__collabGetVar('pushUndo');\n      if (pushUndo) pushUndo('add discovered nodes');\n\n      var existingIPs = {};\n      Object.keys(nd).forEach(function(nid) {\n        if (nd[nid] && nd[nid].ip) existingIPs[nd[nid].ip] = true;\n      });\n\n      var cols = Math.ceil(Math.sqrt(checks.length));\n      var spacing = 120;\n      var cs = getGlobal('canvasState') || { panX: 0, panY: 0, zoom: 1 };\n      var startX = (-cs.panX / cs.zoom) + 200;\n      var startY = (-cs.panY / cs.zoom) + 200;\n      var added = 0;\n\n      var entries = [];\n      checks.forEach(function(cb) {\n        var idx = parseInt(cb.dataset.idx, 10);\n        var host = discoveryResults[idx];\n        if (!host || existingIPs[host.ip]) return;\n        var ov = discoveryOverrides[host.ip] || {};\n        var toggle = document.querySelector('.verse-type-toggle[data-idx=\"' + idx + '\"]');\n        var activeBtn = toggle ? toggle.querySelector('.verse-type-btn.active') : null;\n        var isRack = (ov.typeToggle === 'rack') || (activeBtn && activeBtn.dataset.type === 'rack');\n        entries.push({ host: host, ov: ov, isRack: isRack, idx: idx });\n      });\n\n      entries.sort(function(a, b) {\n        return (b.isRack ? 1 : 0) - (a.isRack ? 1 : 0);\n      });\n\n      var newRackIpToNodeId = {};\n\n      entries.forEach(function(entry) {\n        var host = entry.host;\n        var ov = entry.ov;\n        var isRack = entry.isRack;\n\n        var nodeName = ov.name || host.hostname || host.ip;\n        var nodeIp = ov.ip || host.ip;\n        var nodeShape = ov.shape || (isRack ? 'server' : 'circle');\n        var nodeTags = [];\n        if (ov.tags) {\n          nodeTags = ov.tags.split(',').map(function(t) { return t.trim(); }).filter(function(t) { return t; });\n        }\n        if (ov.iconTags && ov.iconTags.length > 0) {\n          ov.iconTags.forEach(function(it) {\n            nodeTags.push({ type: 'icon', library: it.library, name: it.name });\n          });\n        }\n        var pingEnabled = ov.pingEnabled !== undefined ? ov.pingEnabled : true;\n        var pingTimeout = ov.pingTimeout || 3000;\n\n        var probeType = ov.probeType || 'auto';\n        var tcpPortsStr = ov.tcpPorts || (host.ports && host.ports.length > 0 ? host.ports.join(', ') : '');\n        var tcpPortsList = tcpPortsStr ? tcpPortsStr.split(',').map(function(s) { return parseInt(s.trim(), 10); }).filter(function(p) { return p >= 1 && p <= 65535; }) : [];\n        var probeTypes;\n        if (probeType === 'icmp') {\n          probeTypes = [{ type: 'icmp' }];\n        } else if (probeType === 'tcp') {\n          probeTypes = [{ type: 'icmp' }];\n          tcpPortsList.forEach(function(p) { probeTypes.push({ type: 'tcp', port: p }); });\n        } else if (probeType === 'http') {\n          probeTypes = [{ type: 'http' }];\n        } else if (probeType === 'multi') {\n          probeTypes = [{ type: 'icmp' }, { type: 'http' }];\n          tcpPortsList.forEach(function(p) { probeTypes.push({ type: 'tcp', port: p }); });\n          probeTypes.push({ type: 'dns' });\n        } else {\n          probeTypes = [{ type: 'icmp' }, { type: 'http' }];\n        }\n\n        var nodeId = generateUUID();\n        var col = added % cols;\n        var row = Math.floor(added / cols);\n\n        var resolvedRack = '';\n        var resolvedUnit = '';\n        var resolvedUHeight = '1';\n        if (!isRack && ov.assignedRackRef) {\n          var parts = ov.assignedRackRef.split(':');\n          var refType = parts[0];\n          var refId = parts.slice(1).join(':');\n          if (refType === 'canvas' && nd[refId]) {\n            resolvedRack = refId;\n          } else if (refType === 'new' && newRackIpToNodeId[refId]) {\n            resolvedRack = newRackIpToNodeId[refId];\n          }\n          if (resolvedRack) {\n            resolvedUnit = ov.rackUnit || '';\n            resolvedUHeight = ov.uHeight || '1';\n          }\n        }\n\n        var nodeNotes = [];\n        if (ov.notes && ov.notes.trim()) nodeNotes.push(ov.notes.trim());\n\n        nd[nodeId] = {\n          shape: nodeShape,\n          name: nodeName,\n          ip: nodeIp,\n          role: isRack ? 'Rack' : '',\n          tags: nodeTags,\n          notes: nodeNotes,\n          mac: '',\n          rackUnit: resolvedUnit,\n          uHeight: resolvedUHeight,\n          layer: ov.layer || 'layer1',\n          assignedRack: resolvedRack,\n          hostedOn: '',\n          locked: false,\n          groupId: null,\n          ping: {\n            enabled: pingEnabled,\n            protocol: 'http',\n            customUrl: '',\n            timeout: pingTimeout,\n            status: 'unknown',\n            lastCheck: null,\n            probeTypes: probeTypes,\n            detectedServices: host.services || {},\n            dnsHostname: host.dnsName || '',\n            netbiosName: host.netbiosName || '',\n            mdnsName: host.mdnsName || '',\n            httpServer: host.httpServer || '',\n            snmpName: host.snmpName || ''\n          }\n        };\n\n        if (isRack) {\n          nd[nodeId].isRack = true;\n          nd[nodeId].rackCapacity = ov.rackCapacity || '42';\n          newRackIpToNodeId[host.ip] = nodeId;\n        }\n\n        positions[nodeId] = { x: startX + col * spacing, y: startY + row * spacing };\n        sizes[nodeId] = ov.nodeSize || 50;\n        if (!styles[nodeId]) styles[nodeId] = {};\n        if (!styles[nodeId]['all']) styles[nodeId]['all'] = {};\n        if (ov.iconData && ov.iconData.name) {\n          styles[nodeId]['all'].icon = { library: ov.iconData.library, name: ov.iconData.name };\n        }\n        if (ov.fillColor && ov.fillColor !== '#1e293b') {\n          styles[nodeId]['all'].circleColor = ov.fillColor;\n        }\n        if (ov.borderColor && ov.borderColor !== '#475569') {\n          styles[nodeId]['all'].circleBorder = ov.borderColor;\n        }\n\n        added++;\n        existingIPs[host.ip] = true;\n      });\n\n      var edgeData = getGlobal('EDGE_DATA');\n      var ipToNodeId = {};\n      Object.keys(nd).forEach(function(nid) {\n        if (nd[nid] && nd[nid].ip) ipToNodeId[nd[nid].ip] = nid;\n      });\n\n      entries.forEach(function(entry) {\n        var host = entry.host;\n        var ov = entry.ov;\n        if (!ov.connections || ov.connections.length === 0) return;\n        var fromNodeId = ipToNodeId[host.ip];\n        if (!fromNodeId) return;\n        ov.connections.forEach(function(conn) {\n          var toNodeId;\n          if (conn.target.indexOf('canvas:') === 0) {\n            toNodeId = conn.target.replace('canvas:', '');\n          } else {\n            toNodeId = ipToNodeId[conn.target];\n          }\n          if (!toNodeId || !nd[toNodeId]) return;\n          edgeData.list.push({\n            id: fromNodeId + '-' + toNodeId + '-' + Date.now() + '-' + Math.random().toString(36).substr(2, 4),\n            from: fromNodeId,\n            to: toNodeId,\n            width: 4,\n            color: '#475569',\n            direction: 'none',\n            routing: 'orthogonal',\n            type: 'main',\n            notes: [],\n            fromPort: conn.fromPort || '',\n            toPort: conn.toPort || '',\n            lineStyle: 'solid',\n            waypoints: []\n          });\n        });\n      });\n\n      entries.forEach(function(entry) {\n        delete discoveryOverrides[entry.host.ip];\n      });\n\n      setGlobal('NODE_DATA', nd);\n      setGlobal('savedPositions', positions);\n      setGlobal('savedSizes', sizes);\n      setGlobal('savedStyles', styles);\n      setGlobal('EDGE_DATA', edgeData);\n      var forge = window.__collabGetVar('forgeTheTopology');\n      if (forge) forge();\n\n      renderDiscoveryResults();\n      sendFullState();\n    });\n\n    var style = document.createElement('style');\n    style.textContent = '#verse-discovery-modal{display:none}#verse-discovery-modal.active{display:flex}#verse-disc-edit-modal{display:none}#verse-disc-edit-modal.active{display:flex}';\n    document.head.appendChild(style);\n  }\n\n  function startCollab() {\n    setupAuditLogInjection();\n    injectCollabBar();\n    hookSaveFunction();\n    overridePingFunctions();\n    injectProbeUI();\n    injectDiscoveryUI();\n    connect();\n    startStatePolling();\n    setTimeout(trackSelection, 1000);\n    trackCursor();\n  }\n\n  init();\n})();\n"
  },
  {
    "path": "theonefile_verse/public/index.html",
    "content": "<!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\">\n  <title>TheOneFile_Verse</title>\n  <style>\n    *{box-sizing:border-box;margin:0;padding:0}\n    button,a,.btn,.user-btn,.theme-toggle,.modal-close,.tab,.oidc-btn{-webkit-tap-highlight-color:transparent}\n\n    :root{\n      --bg:#0d0d0d;\n      --bg-alt:#1a1a1a;\n      --surface:#242424;\n      --border:#333;\n      --text:#e8e8e8;\n      --text-soft:#999;\n      --accent:#c9a227;\n      --accent-hover:#d4b23a;\n      --steel:#71717a;\n      --steel-light:#a1a1aa;\n    }\n\n    [data-theme=\"light\"]{\n      --bg:#f5f3ef;\n      --bg-alt:#eae7e0;\n      --surface:#fff;\n      --border:#d4d0c8;\n      --text:#1a1a1a;\n      --text-soft:#666;\n      --accent:#996b1f;\n      --accent-hover:#7a5518;\n      --steel:#52525b;\n      --steel-light:#71717a;\n    }\n\n    body{\n      font-family:system-ui,-apple-system,sans-serif;\n      background:var(--bg);\n      color:var(--text);\n      min-height:100vh;\n      display:flex;\n      flex-direction:column;\n      align-items:center;\n      justify-content:center;\n      padding:20px;\n    }\n\n    .theme-toggle{\n      position:fixed;\n      top:max(20px,env(safe-area-inset-top,20px));\n      right:max(20px,env(safe-area-inset-right,20px));\n      background:var(--surface);\n      border:1px solid var(--border);\n      color:var(--text);\n      padding:8px 12px;\n      border-radius:6px;\n      cursor:pointer;\n      font-size:14px;\n      min-height:44px;\n      min-width:44px;\n      display:inline-flex;\n      align-items:center;\n      justify-content:center;\n    }\n    .theme-toggle:hover{background:var(--bg-alt)}\n\n    .container{\n      text-align:center;\n      max-width:400px;\n      width:100%;\n    }\n\n    h1{\n      font-size:28px;\n      font-weight:700;\n      margin-bottom:8px;\n      color:var(--text);\n      letter-spacing:-0.5px;\n    }\n\n    .tagline{\n      color:var(--text-soft);\n      font-size:15px;\n      margin-bottom:40px;\n    }\n\n    .actions{\n      display:flex;\n      flex-direction:column;\n      gap:12px;\n    }\n\n    .btn{\n      padding:14px 24px;\n      font-size:15px;\n      font-weight:500;\n      border:none;\n      border-radius:8px;\n      cursor:pointer;\n      width:100%;\n    }\n\n    .btn-primary{\n      background:var(--accent);\n      color:#fff;\n    }\n    .btn-primary:hover{background:var(--accent-hover)}\n\n    .btn-secondary{\n      background:var(--surface);\n      color:var(--text);\n      border:1px solid var(--border);\n    }\n    .btn-secondary:hover{background:var(--bg-alt)}\n\n    .modal-overlay{\n      display:none;\n      position:fixed;\n      inset:0;\n      background:rgba(0,0,0,0.6);\n      align-items:center;\n      justify-content:center;\n      padding:20px;\n      padding-bottom:max(20px,env(safe-area-inset-bottom,20px));\n      z-index:100;\n    }\n    .modal-overlay.active{display:flex}\n\n    .modal{\n      background:var(--surface);\n      border:1px solid var(--border);\n      border-radius:12px;\n      width:100%;\n      max-width:420px;\n    }\n\n    .modal-header{\n      display:flex;\n      justify-content:space-between;\n      align-items:center;\n      padding:16px 20px;\n      border-bottom:1px solid var(--border);\n    }\n    .modal-header h2{font-size:18px;font-weight:600}\n\n    .modal-close{\n      background:none;\n      border:none;\n      color:var(--text-soft);\n      font-size:22px;\n      cursor:pointer;\n      line-height:1;\n      padding:0;\n      min-width:44px;\n      min-height:44px;\n      display:flex;\n      align-items:center;\n      justify-content:center;\n    }\n    .modal-close:hover{color:var(--text)}\n\n    .modal-body{\n      padding:20px;\n      display:flex;\n      flex-direction:column;\n      gap:16px;\n    }\n\n    .form-group{display:flex;flex-direction:column;gap:6px}\n    .form-group label{font-size:13px;font-weight:500;color:var(--text-soft)}\n\n    .form-group input,.form-group select{\n      padding:10px 12px;\n      background:var(--bg);\n      border:1px solid var(--border);\n      border-radius:6px;\n      color:var(--text);\n      font-size:16px;\n    }\n    .form-group input:focus,.form-group select:focus{\n      outline:none;\n      border-color:var(--accent);\n    }\n    .form-group input::placeholder{color:var(--text-soft)}\n\n    .file-drop{\n      border:1px dashed var(--border);\n      border-radius:6px;\n      padding:24px;\n      text-align:center;\n      cursor:pointer;\n    }\n    .file-drop:hover{border-color:var(--steel-light)}\n    .file-drop-text{font-size:14px;color:var(--text-soft)}\n    .file-drop-formats{font-size:12px;color:var(--text-soft);margin-top:4px;opacity:0.7}\n\n    .file-selected{\n      display:none;\n      align-items:center;\n      gap:8px;\n      padding:10px 12px;\n      background:var(--bg);\n      border-radius:6px;\n      font-size:14px;\n    }\n    .file-selected.active{display:flex}\n    .file-selected-name{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n    .file-selected-clear{background:none;border:none;color:var(--steel);cursor:pointer;font-size:16px}\n\n    .modal-footer{\n      padding:16px 20px;\n      border-top:1px solid var(--border);\n    }\n    .modal-footer .btn{width:100%}\n\n    .error-text{\n      color:#c53030;\n      font-size:13px;\n      display:none;\n    }\n    .error-text.active,.error-text:not(:empty){display:block}\n\n    .footer{\n      position:fixed;\n      bottom:max(20px,env(safe-area-inset-bottom,20px));\n      font-size:12px;\n      color:var(--text-soft);\n    }\n    .footer a{color:var(--steel-light);text-decoration:none}\n    .footer a:hover{color:var(--accent)}\n\n    .toast-stack{position:fixed;bottom:max(80px,calc(80px + env(safe-area-inset-bottom,0px)));left:50%;transform:translateX(-50%);z-index:10000;display:flex;flex-direction:column;gap:8px;align-items:center;pointer-events:none}\n    .toast-item{background:#333;color:#fff;padding:10px 20px;border-radius:8px;font-size:14px;animation:toastIn 0.3s ease;pointer-events:auto}\n    .toast-item.error{background:#dc2626}\n    .toast-item.success{background:#16a34a}\n    @keyframes toastIn{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}\n\n    .user-menu{\n      position:fixed;\n      top:max(20px,env(safe-area-inset-top,20px));\n      left:max(20px,env(safe-area-inset-left,20px));\n      display:flex;\n      align-items:center;\n      gap:12px;\n    }\n    .user-avatar{\n      width:36px;\n      height:36px;\n      border-radius:50%;\n      background:var(--accent);\n      display:flex;\n      align-items:center;\n      justify-content:center;\n      color:white;\n      font-weight:600;\n      font-size:14px;\n    }\n    .user-name{font-size:14px;color:var(--text)}\n    .user-btn{\n      background:var(--surface);\n      border:1px solid var(--border);\n      color:var(--text);\n      padding:6px 12px;\n      border-radius:6px;\n      cursor:pointer;\n      font-size:13px;\n      min-height:44px;\n      display:inline-flex;\n      align-items:center;\n    }\n    .user-btn:hover{background:var(--bg-alt)}\n\n    .auth-buttons{\n      display:flex;\n      gap:8px;\n      position:fixed;\n      top:max(20px,env(safe-area-inset-top,20px));\n      left:max(20px,env(safe-area-inset-left,20px));\n    }\n    .auth-buttons .btn{padding:8px 16px;font-size:13px;width:auto;min-height:44px;display:inline-flex;align-items:center}\n\n    .oidc-providers{\n      display:flex;\n      flex-direction:column;\n      gap:8px;\n      margin-bottom:16px;\n    }\n    .oidc-btn{\n      display:flex;\n      align-items:center;\n      gap:12px;\n      padding:12px 16px;\n      background:var(--bg);\n      border:1px solid var(--border);\n      border-radius:8px;\n      color:var(--text);\n      cursor:pointer;\n      font-size:14px;\n      width:100%;\n      min-height:44px;\n    }\n    .oidc-btn:hover{border-color:var(--accent)}\n    .oidc-btn img{width:20px;height:20px}\n\n    .divider{\n      display:flex;\n      align-items:center;\n      gap:12px;\n      margin:16px 0;\n      color:var(--text-soft);\n      font-size:13px;\n    }\n    .divider::before,.divider::after{\n      content:'';\n      flex:1;\n      height:1px;\n      background:var(--border);\n    }\n\n    .tabs{\n      display:flex;\n      border-bottom:1px solid var(--border);\n      margin-bottom:16px;\n    }\n    .tab{\n      flex:1;\n      padding:12px;\n      background:none;\n      border:none;\n      color:var(--text-soft);\n      cursor:pointer;\n      font-size:14px;\n      min-height:44px;\n    }\n    .tab.active{\n      color:var(--accent);\n      border-bottom:2px solid var(--accent);\n      margin-bottom:-1px;\n    }\n\n    .guest-mode-toggle{\n      display:flex;\n      align-items:center;\n      gap:8px;\n      padding:12px;\n      background:var(--bg);\n      border-radius:6px;\n      margin-top:12px;\n    }\n    .guest-mode-toggle input{width:18px;height:18px}\n    .guest-mode-toggle label{font-size:13px;color:var(--text-soft)}\n\n    @media(max-width:640px){\n      h1{font-size:24px}\n      .tagline{font-size:14px;margin-bottom:32px}\n      .auth-buttons,.user-menu{top:max(12px,env(safe-area-inset-top,12px));left:max(12px,env(safe-area-inset-left,12px));gap:6px}\n      .auth-buttons{flex-wrap:wrap}\n      .auth-buttons .btn{padding:8px 12px;font-size:12px;min-height:44px}\n      .user-avatar{width:32px;height:32px;font-size:12px}\n      .user-name{display:none}\n      .user-btn{padding:6px 10px;font-size:12px;min-height:44px}\n      .theme-toggle{top:max(12px,env(safe-area-inset-top,12px));right:max(12px,env(safe-area-inset-right,12px))}\n      .footer{bottom:max(12px,env(safe-area-inset-bottom,12px))}\n      .container{padding:0 4px}\n    }\n    @media(max-width:380px){\n      .container{max-width:100%}\n      .modal-body{padding:16px}\n      .modal-header,.modal-footer{padding:12px 16px}\n    }\n  </style>\n</head>\n<body>\n  <button class=\"theme-toggle\">\n    <span id=\"theme-icon\">&#9728;</span>\n  </button>\n\n  <div class=\"user-menu\" id=\"user-menu\" style=\"display:none\">\n    <div class=\"user-avatar\" id=\"user-avatar\"></div>\n    <span class=\"user-name\" id=\"user-name\"></span>\n    <button class=\"user-btn\" data-action=\"openSettings\">Settings</button>\n    <button class=\"user-btn\" data-action=\"logout\">Logout</button>\n  </div>\n\n  <div class=\"auth-buttons\" id=\"auth-buttons\" style=\"display:none\">\n    <button class=\"btn btn-secondary\" data-action=\"openLogin\">Sign In</button>\n    <button class=\"btn btn-primary\" data-action=\"openRegister\">Register</button>\n  </div>\n\n  <div class=\"container\">\n    <h1>TheOneFile_Verse</h1>\n    <p class=\"tagline\">It turns out there CAN be more than one.</p>\n\n    <div class=\"actions\">\n      <button class=\"btn btn-primary\" data-action=\"openCreate\">Create Room</button>\n      <button class=\"btn btn-secondary\" data-action=\"openJoin\">Join Room</button>\n    </div>\n  </div>\n\n  <div class=\"footer\" id=\"admin-footer\" style=\"display:none\">\n    <a href=\"/admin\" id=\"admin-link\">Admin</a>\n  </div>\n\n  <div class=\"modal-overlay\" id=\"create-modal\">\n    <div class=\"modal\">\n      <div class=\"modal-header\">\n        <h2>Create Room</h2>\n        <button class=\"modal-close\" data-close-modal=\"create\">&times;</button>\n      </div>\n      <div class=\"modal-body\">\n        <div class=\"form-group\">\n          <label>Password (optional)</label>\n          <input type=\"password\" id=\"create-password\" placeholder=\"Leave empty for open access\">\n        </div>\n        <div class=\"form-group\">\n          <label>Room Expiration</label>\n          <select id=\"create-destruct\">\n            <option value=\"3600000\">1 hour after last activity</option>\n            <option value=\"21600000\">6 hours after last activity</option>\n            <option value=\"86400000\" selected>24 hours after last activity</option>\n            <option value=\"604800000\">7 days after last activity</option>\n            <option value=\"empty\">When everyone leaves</option>\n            <option value=\"never\">Never</option>\n          </select>\n        </div>\n        <div class=\"form-group\">\n          <label>Import file (optional)</label>\n          <div class=\"file-drop\" id=\"create-file-drop\">\n            <div class=\"file-drop-text\">Drop file or click to browse</div>\n            <div class=\"file-drop-formats\">.html .json .csv .md</div>\n          </div>\n          <input type=\"file\" id=\"create-file-input\" accept=\".html,.json,.csv,.md,.markdown,.txt\" hidden>\n          <div class=\"file-selected\" id=\"create-file-selected\">\n            <span class=\"file-selected-name\" id=\"create-file-name\"></span>\n            <button class=\"file-selected-clear\" data-action=\"clearFile\">&times;</button>\n          </div>\n        </div>\n        <div class=\"guest-mode-toggle\" id=\"guest-toggle-container\" style=\"display:none\">\n          <input type=\"checkbox\" id=\"create-allow-guests\" checked>\n          <label for=\"create-allow-guests\">Allow unregistered users (guests) to join</label>\n        </div>\n        <div class=\"error-text\" id=\"create-error\"></div>\n      </div>\n      <div class=\"modal-footer\">\n        <button class=\"btn btn-primary\" data-action=\"createRoom\">Create</button>\n      </div>\n    </div>\n  </div>\n\n  <div class=\"modal-overlay\" id=\"join-modal\">\n    <div class=\"modal\">\n      <div class=\"modal-header\">\n        <h2>Join Room</h2>\n        <button class=\"modal-close\" data-close-modal=\"join\">&times;</button>\n      </div>\n      <div class=\"modal-body\">\n        <div class=\"form-group\">\n          <label>Room ID or URL</label>\n          <input type=\"text\" id=\"join-room-id\" placeholder=\"Paste room ID or link\">\n        </div>\n        <div class=\"form-group\" id=\"join-password-group\" style=\"display:none\">\n          <label>Password</label>\n          <input type=\"password\" id=\"join-password\" placeholder=\"Enter room password\">\n        </div>\n        <div class=\"error-text\" id=\"join-error\"></div>\n      </div>\n      <div class=\"modal-footer\">\n        <button class=\"btn btn-primary\" data-action=\"joinRoom\">Join</button>\n      </div>\n    </div>\n  </div>\n\n  <div class=\"modal-overlay\" id=\"login-modal\">\n    <div class=\"modal\">\n      <div class=\"modal-header\">\n        <h2>Sign In</h2>\n        <button class=\"modal-close\" data-close-modal=\"login\">&times;</button>\n      </div>\n      <div class=\"modal-body\">\n        <div class=\"oidc-providers\" id=\"login-oidc-providers\"></div>\n        <div class=\"divider\" id=\"login-divider\" style=\"display:none\">or</div>\n        <div class=\"form-group\">\n          <label>Email</label>\n          <input type=\"email\" id=\"login-email\" placeholder=\"you@example.com\" autocomplete=\"email\">\n        </div>\n        <div class=\"form-group\">\n          <label>Password</label>\n          <input type=\"password\" id=\"login-password\" placeholder=\"Your password\" autocomplete=\"current-password\">\n        </div>\n        <div class=\"error-text\" id=\"login-error\"></div>\n        <div style=\"display:flex;justify-content:space-between;margin-top:8px\">\n          <a href=\"#\" data-action=\"openForgot\" style=\"font-size:13px;color:var(--accent)\">Forgot password?</a>\n          <a href=\"#\" data-action=\"openMagic\" style=\"font-size:13px;color:var(--accent)\">Email me a link</a>\n        </div>\n      </div>\n      <div class=\"modal-footer\">\n        <button class=\"btn btn-primary\" data-action=\"loginWithPassword\">Sign In</button>\n      </div>\n    </div>\n  </div>\n\n  <div class=\"modal-overlay\" id=\"register-modal\">\n    <div class=\"modal\">\n      <div class=\"modal-header\">\n        <h2>Create Account</h2>\n        <button class=\"modal-close\" data-close-modal=\"register\">&times;</button>\n      </div>\n      <div class=\"modal-body\">\n        <div class=\"oidc-providers\" id=\"register-oidc-providers\"></div>\n        <div class=\"divider\" id=\"register-divider\" style=\"display:none\">or</div>\n        <div class=\"form-group\">\n          <label>Display Name</label>\n          <input type=\"text\" id=\"register-name\" placeholder=\"How should we call you?\">\n        </div>\n        <div class=\"form-group\">\n          <label>Email</label>\n          <input type=\"email\" id=\"register-email\" placeholder=\"you@example.com\" autocomplete=\"email\">\n        </div>\n        <div class=\"form-group\">\n          <label>Password</label>\n          <input type=\"password\" id=\"register-password\" placeholder=\"At least 8 characters\" autocomplete=\"new-password\">\n        </div>\n        <div class=\"error-text\" id=\"register-error\"></div>\n      </div>\n      <div class=\"modal-footer\">\n        <button class=\"btn btn-primary\" data-action=\"registerUser\">Create Account</button>\n      </div>\n    </div>\n  </div>\n\n  <div class=\"modal-overlay\" id=\"forgot-modal\">\n    <div class=\"modal\">\n      <div class=\"modal-header\">\n        <h2>Reset Password</h2>\n        <button class=\"modal-close\" data-close-modal=\"forgot\">&times;</button>\n      </div>\n      <div class=\"modal-body\">\n        <p style=\"font-size:14px;color:var(--text-soft);margin-bottom:16px\">Enter your email and we'll send you a link to reset your password.</p>\n        <div class=\"form-group\">\n          <label>Email</label>\n          <input type=\"email\" id=\"forgot-email\" placeholder=\"you@example.com\" autocomplete=\"email\">\n        </div>\n        <div class=\"error-text\" id=\"forgot-error\"></div>\n        <div class=\"error-text\" id=\"forgot-success\" style=\"color:#22c55e\"></div>\n      </div>\n      <div class=\"modal-footer\">\n        <button class=\"btn btn-primary\" data-action=\"requestPasswordReset\">Send Reset Link</button>\n      </div>\n    </div>\n  </div>\n\n  <div class=\"modal-overlay\" id=\"magic-modal\">\n    <div class=\"modal\">\n      <div class=\"modal-header\">\n        <h2>Passwordless Login</h2>\n        <button class=\"modal-close\" data-close-modal=\"magic\">&times;</button>\n      </div>\n      <div class=\"modal-body\">\n        <p style=\"font-size:14px;color:var(--text-soft);margin-bottom:16px\">We'll email you a magic link that logs you in instantly.</p>\n        <div class=\"form-group\">\n          <label>Email</label>\n          <input type=\"email\" id=\"magic-email\" placeholder=\"you@example.com\" autocomplete=\"email\">\n        </div>\n        <div class=\"error-text\" id=\"magic-error\"></div>\n        <div class=\"error-text\" id=\"magic-success\" style=\"color:#22c55e\"></div>\n      </div>\n      <div class=\"modal-footer\">\n        <button class=\"btn btn-primary\" data-action=\"requestMagicLink\">Send Magic Link</button>\n      </div>\n    </div>\n  </div>\n\n  <div class=\"modal-overlay\" id=\"2fa-modal\">\n    <div class=\"modal\">\n      <div class=\"modal-header\">\n        <h2>Two Factor Authentication</h2>\n        <button class=\"modal-close\" data-close-modal=\"2fa\">&times;</button>\n      </div>\n      <div class=\"modal-body\">\n        <p style=\"font-size:14px;color:var(--text-soft);margin-bottom:16px\">Enter the 6 digit code from your authenticator app, or use a backup code.</p>\n        <div class=\"form-group\">\n          <label>Code</label>\n          <input type=\"text\" id=\"2fa-code\" placeholder=\"000000\" maxlength=\"8\" autocomplete=\"one-time-code\" inputmode=\"numeric\" style=\"text-align:center;font-size:24px;letter-spacing:8px\">\n        </div>\n        <div class=\"error-text\" id=\"2fa-error\"></div>\n      </div>\n      <div class=\"modal-footer\">\n        <button class=\"btn btn-primary\" data-action=\"verify2FA\">Verify</button>\n      </div>\n    </div>\n  </div>\n\n  <div class=\"modal-overlay\" id=\"settings-modal\">\n    <div class=\"modal\">\n      <div class=\"modal-header\">\n        <h2>Account Settings</h2>\n        <button class=\"modal-close\" data-close-modal=\"settings\">&times;</button>\n      </div>\n      <div class=\"modal-body\">\n        <div id=\"settings-2fa-section\">\n          <h3 style=\"font-size:16px;margin-bottom:12px\">Two Factor Authentication</h3>\n          <div id=\"2fa-status\"></div>\n        </div>\n        <div style=\"margin-top:24px;padding-top:24px;border-top:1px solid var(--border)\">\n          <h3 style=\"font-size:16px;margin-bottom:12px\">Change Email</h3>\n          <div class=\"form-group\">\n            <label>New Email</label>\n            <input type=\"email\" id=\"settings-new-email\" placeholder=\"new@example.com\">\n          </div>\n          <div class=\"form-group\">\n            <label>Current Password</label>\n            <input type=\"password\" id=\"settings-email-password\" placeholder=\"Confirm your password\" autocomplete=\"current-password\">\n          </div>\n          <div class=\"error-text\" id=\"email-change-error\"></div>\n          <div class=\"error-text\" id=\"email-change-success\" style=\"color:#22c55e\"></div>\n          <button class=\"btn btn-secondary\" data-action=\"requestEmailChange\" style=\"margin-top:8px\">Change Email</button>\n        </div>\n      </div>\n    </div>\n  </div>\n\n  <script src=\"/landing.js\"></script>\n</body>\n</html>\n"
  },
  {
    "path": "theonefile_verse/public/landing.js",
    "content": "(function() {\n  'use strict';\n\n  function withLoading(fn) {\n    return async function() {\n      var btn = this instanceof HTMLElement ? this : null;\n      if (btn) btn.disabled = true;\n      try { await fn.call(this); }\n      finally { if (btn) btn.disabled = false; }\n    };\n  }\n\n  function showToast(message, type) {\n    var stack = document.getElementById('toast-stack');\n    if (!stack) {\n      stack = document.createElement('div');\n      stack.id = 'toast-stack';\n      stack.className = 'toast-stack';\n      document.body.appendChild(stack);\n    }\n    var toast = document.createElement('div');\n    toast.className = 'toast-item' + (type ? ' ' + type : '');\n    toast.textContent = message;\n    stack.appendChild(toast);\n    setTimeout(function() {\n      toast.remove();\n      if (stack.children.length === 0) stack.remove();\n    }, 4000);\n  }\n\n  function h(tag, props) {\n    var node = document.createElement(tag);\n    if (props) {\n      for (var key in props) {\n        if (!props.hasOwnProperty(key)) continue;\n        if (key === 'className') node.className = props[key];\n        else if (key === 'style') node.setAttribute('style', props[key]);\n        else if (key === 'textContent') node.textContent = props[key];\n        else if (key.slice(0, 5) === 'data-') node.setAttribute(key, props[key]);\n        else if (key === 'checked') { if (props[key]) node.checked = true; }\n        else node[key] = props[key];\n      }\n    }\n    for (var i = 2; i < arguments.length; i++) _append(node, arguments[i]);\n    return node;\n  }\n  function _append(parent, child) {\n    if (child == null || child === false) return;\n    if (typeof child === 'string' || typeof child === 'number') {\n      parent.appendChild(document.createTextNode(String(child)));\n    } else if (Array.isArray(child)) {\n      for (var j = 0; j < child.length; j++) _append(parent, child[j]);\n    } else {\n      parent.appendChild(child);\n    }\n  }\n  function clearNode(el) {\n    while (el.firstChild) el.removeChild(el.firstChild);\n  }\n  function setContent(container, children) {\n    clearNode(container);\n    var frag = document.createDocumentFragment();\n    _append(frag, children);\n    container.appendChild(frag);\n  }\n\n  var forcedTheme = null;\n\n  function getTheme() {\n    if (forcedTheme && forcedTheme !== 'user') return forcedTheme;\n    return localStorage.getItem('theme') || 'dark';\n  }\n\n  function setTheme(theme) {\n    if (!forcedTheme || forcedTheme === 'user') {\n      localStorage.setItem('theme', theme);\n    }\n    document.documentElement.setAttribute('data-theme', theme);\n    document.getElementById('theme-icon').textContent = theme === 'dark' ? '\\u2600' : '\\u263E';\n  }\n\n  function toggleTheme() {\n    if (forcedTheme && forcedTheme !== 'user') return;\n    setTheme(getTheme() === 'dark' ? 'light' : 'dark');\n  }\n\n  function updateThemeToggleVisibility() {\n    var btn = document.querySelector('.theme-toggle');\n    if (forcedTheme && forcedTheme !== 'user') {\n      btn.style.display = 'none';\n    } else {\n      btn.style.display = 'block';\n    }\n  }\n\n  setTheme(getTheme());\n\n  fetch('/api/theme').then(function(r) { return r.json(); }).then(function(data) {\n    if (data.forcedTheme && data.forcedTheme !== 'user') {\n      forcedTheme = data.forcedTheme;\n      setTheme(forcedTheme);\n    }\n    updateThemeToggleVisibility();\n    if (data.showAdminLink !== false) {\n      var footer = document.getElementById('admin-footer');\n      if (footer) footer.style.display = '';\n    }\n    if (data.adminPath) {\n      var link = document.getElementById('admin-link');\n      if (link) link.href = '/' + data.adminPath;\n    }\n  }).catch(function() { updateThemeToggleVisibility(); });\n\n  var selectedFile = null;\n  var selectedFileContent = null;\n\n  function generateUUID() {\n    if (crypto.randomUUID) return crypto.randomUUID();\n    return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {\n      var r = Math.random() * 16 | 0;\n      return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16);\n    });\n  }\n\n  function getOrCreateUserId(roomId) {\n    var key = 'collab-user-' + roomId;\n    var id = localStorage.getItem(key);\n    if (!id) { id = generateUUID(); localStorage.setItem(key, id); }\n    return id;\n  }\n\n  function openModal(type) { document.getElementById(type + '-modal').classList.add('active'); }\n\n  function closeModal(type) {\n    var modal = document.getElementById(type + '-modal');\n    modal.classList.remove('active');\n    modal.querySelectorAll('input:not([type=\"checkbox\"]):not([type=\"file\"]):not([type=\"hidden\"])').forEach(function(el) { el.value = ''; });\n    modal.querySelectorAll('select').forEach(function(el) { var sel = el.querySelector('[selected]'); el.selectedIndex = sel ? sel.index : 0; });\n    modal.querySelectorAll('.error-text').forEach(function(el) { el.textContent = ''; el.classList.remove('active'); });\n    if (type === '2fa') { pending2FAToken = null; }\n  }\n\n  function clearAllAuthForms() {\n    ['login-email', 'login-password', 'register-name', 'register-email', 'register-password',\n     'forgot-email', 'magic-email', '2fa-code', 'settings-new-email', 'settings-email-password'\n    ].forEach(function(id) { var el = document.getElementById(id); if (el) el.value = ''; });\n    ['login-error', 'register-error', 'forgot-error', 'forgot-success', 'magic-error',\n     'magic-success', '2fa-error', 'email-change-error', 'email-change-success'\n    ].forEach(function(id) { var el = document.getElementById(id); if (el) { el.textContent = ''; el.classList.remove('active'); } });\n    pending2FAToken = null;\n  }\n\n  function showError(type, msg) {\n    var el = document.getElementById(type + '-error');\n    el.textContent = msg;\n    el.classList.add('active');\n    el.setAttribute('role', 'alert');\n  }\n\n  var fileDrop = document.getElementById('create-file-drop');\n  var fileInput = document.getElementById('create-file-input');\n\n  fileDrop.addEventListener('click', function() { fileInput.click(); });\n  fileDrop.addEventListener('dragover', function(e) { e.preventDefault(); });\n  fileDrop.addEventListener('drop', function(e) { e.preventDefault(); handleFile(e.dataTransfer.files[0]); });\n  fileInput.addEventListener('change', function(e) { handleFile(e.target.files[0]); });\n\n  var MAX_FILE_SIZE_MB = 10;\n  var MAX_FILE_SIZE_BYTES = MAX_FILE_SIZE_MB * 1024 * 1024;\n\n  async function handleFile(file) {\n    if (!file) return;\n    if (file.size > MAX_FILE_SIZE_BYTES) {\n      showError('create', 'File too large. Maximum size is ' + MAX_FILE_SIZE_MB + 'MB');\n      return;\n    }\n    var ext = file.name.split('.').pop().toLowerCase();\n    if (['html', 'json', 'csv', 'md', 'markdown', 'txt'].indexOf(ext) === -1) {\n      showError('create', 'Invalid file type');\n      return;\n    }\n    selectedFile = file;\n    selectedFileContent = await file.text();\n    document.getElementById('create-file-drop').style.display = 'none';\n    document.getElementById('create-file-selected').classList.add('active');\n    document.getElementById('create-file-name').textContent = file.name + ' (' + (file.size / 1024).toFixed(1) + 'KB)';\n  }\n\n  function clearFile() {\n    selectedFile = null;\n    selectedFileContent = null;\n    document.getElementById('create-file-drop').style.display = 'block';\n    document.getElementById('create-file-selected').classList.remove('active');\n    fileInput.value = '';\n  }\n\n  function parseTopologyFile(content, filename) {\n    var ext = filename.split('.').pop().toLowerCase();\n    if (ext === 'json') { try { return JSON.parse(content); } catch(e) { return null; } }\n    if (ext === 'html') {\n      var match = content.match(/<script[^>]*id=\"topology-state\"[^>]*>([\\s\\S]*?)<\\/script>/i);\n      if (match) { try { return JSON.parse(match[1]); } catch(e) {} }\n      return null;\n    }\n    if (ext === 'csv') {\n      var m = content.match(/#THEONEFILE_CONFIG:(.+)/);\n      if (m) { try { return JSON.parse(m[1]); } catch(e) {} }\n      return null;\n    }\n    if (['md', 'markdown', 'txt'].indexOf(ext) !== -1) {\n      var m2 = content.match(/<!--THEONEFILE_CONFIG\\s*([\\s\\S]*?)\\s*THEONEFILE_CONFIG-->/);\n      if (m2) { try { return JSON.parse(m2[1].trim()); } catch(e) {} }\n      return null;\n    }\n    return null;\n  }\n\n  async function createRoom() {\n    var password = document.getElementById('create-password').value;\n    var destructVal = document.getElementById('create-destruct').value;\n    var allowGuestsCheckbox = document.getElementById('create-allow-guests');\n    var allowGuests = allowGuestsCheckbox ? allowGuestsCheckbox.checked : true;\n    var destructMode = 'time', destructMs = parseInt(destructVal);\n    if (destructVal === 'empty') { destructMode = 'empty'; destructMs = 0; }\n    else if (destructVal === 'never') { destructMode = 'never'; destructMs = 0; }\n    var topology = null;\n    if (selectedFileContent) topology = parseTopologyFile(selectedFileContent, selectedFile.name);\n    var tempId = generateUUID();\n    var creatorId = getOrCreateUserId(tempId);\n    try {\n      var res = await fetch('/api/room', {\n        method: 'POST',\n        headers: { 'Content-Type': 'application/json', 'x-csrf-token': csrfToken },\n        body: JSON.stringify({ password: password || null, destructMode: destructMode, destructValue: destructMs, topology: topology, creatorId: creatorId, allowGuests: allowGuests })\n      });\n      var data = await res.json();\n      if (data.error) { showError('create', data.error); return; }\n      localStorage.setItem('collab-user-' + data.id, creatorId);\n      window.location.href = data.url;\n    } catch(e) { showError('create', 'Failed to create room'); }\n  }\n\n  var joinInput = document.getElementById('join-room-id');\n  var checking = false;\n\n  var joinDebounceTimer = null;\n  joinInput.addEventListener('input', function() {\n    var id = joinInput.value.trim();\n    if (id.indexOf('/s/') !== -1) { id = id.split('/s/')[1].split('?')[0]; joinInput.value = id; }\n    if (id.length < 36) return;\n    if (joinDebounceTimer) clearTimeout(joinDebounceTimer);\n    joinDebounceTimer = setTimeout(async function() {\n      if (checking) return;\n      checking = true;\n      try {\n        var res = await fetch('/api/room/' + id + '/exists');\n        var data = await res.json();\n        document.getElementById('join-password-group').style.display = data.hasPassword ? 'block' : 'none';\n        if (!data.exists) showError('join', 'Room not found');\n        else document.getElementById('join-error').classList.remove('active');\n      } catch(e) {}\n      checking = false;\n    }, 300);\n  });\n\n  async function joinRoom() {\n    var id = document.getElementById('join-room-id').value.trim();\n    if (id.indexOf('/s/') !== -1) id = id.split('/s/')[1].split('?')[0];\n    var password = document.getElementById('join-password').value;\n    if (!id) { showError('join', 'Enter a room ID'); return; }\n    try {\n      var existsRes = await fetch('/api/room/' + id + '/exists');\n      var exists = await existsRes.json();\n      if (!exists.exists) { showError('join', 'Room not found'); return; }\n      if (exists.hasPassword) {\n        var verifyRes = await fetch('/api/room/' + id + '/verify', {\n          method: 'POST',\n          headers: { 'Content-Type': 'application/json' },\n          credentials: 'include',\n          body: JSON.stringify({ password: password })\n        });\n        if (!(await verifyRes.json()).valid) { showError('join', 'Invalid password'); return; }\n      }\n      window.location.href = '/s/' + id;\n    } catch(e) { showError('join', 'Failed to join room'); }\n  }\n\n  document.querySelectorAll('.modal-overlay').forEach(function(el) {\n    el.addEventListener('click', function(e) {\n      if (e.target === el) { var type = el.id.replace('-modal', ''); closeModal(type); }\n    });\n  });\n  document.addEventListener('keydown', function(e) {\n    if (e.key === 'Escape') document.querySelectorAll('.modal-overlay.active').forEach(function(m) { var type = m.id.replace('-modal', ''); closeModal(type); });\n  });\n\n  var currentUser = null;\n  var authSettings = null;\n  var oidcProviders = [];\n  var csrfToken = '';\n  var pending2FAToken = null;\n\n  fetch('/api/auth/csrf').then(function(r) { return r.json(); }).then(function(d) { csrfToken = d.token; }).catch(function() {});\n\n  async function loadAuthState() {\n    try {\n      var settingsRes = await fetch('/api/auth/settings', { credentials: 'include' });\n      var settingsData = await settingsRes.json();\n      authSettings = settingsData.settings;\n      oidcProviders = settingsData.providers || [];\n      var userRes = await fetch('/api/auth/me', { credentials: 'include' });\n      var userData = await userRes.json();\n      currentUser = userData.user;\n      updateAuthUI();\n    } catch(e) {\n      console.error('Failed to load auth state:', e);\n    }\n  }\n\n  function updateAuthUI() {\n    var userMenu = document.getElementById('user-menu');\n    var authButtonsEl = document.getElementById('auth-buttons');\n    var guestToggle = document.getElementById('guest-toggle-container');\n\n    if (currentUser) {\n      userMenu.style.display = 'flex';\n      authButtonsEl.style.display = 'none';\n      document.getElementById('user-avatar').textContent = (currentUser.displayName || currentUser.email || 'U')[0].toUpperCase();\n      document.getElementById('user-name').textContent = currentUser.displayName || (currentUser.email ? currentUser.email.split('@')[0] : 'User');\n    } else if (authSettings) {\n      userMenu.style.display = 'none';\n      var hasOidc = oidcProviders && oidcProviders.length > 0;\n      var registrationOpen = authSettings.authMode === 'open' || authSettings.authMode === 'registration';\n      if (registrationOpen || hasOidc) {\n        authButtonsEl.style.display = 'flex';\n      }\n    }\n\n    if (authSettings && authSettings.allowRoomCreatorGuestSetting) {\n      if (currentUser || authSettings.allowGuestRoomCreation) {\n        guestToggle.style.display = 'flex';\n      } else {\n        guestToggle.style.display = 'none';\n      }\n    } else {\n      guestToggle.style.display = 'none';\n    }\n    renderOidcProviders('login-oidc-providers', 'login');\n    renderOidcProviders('register-oidc-providers', 'register');\n  }\n\n  function renderOidcProviders(containerId, mode) {\n    var container = document.getElementById(containerId);\n    var divider = document.getElementById(mode + '-divider');\n    clearNode(container);\n\n    if (oidcProviders.length === 0) {\n      container.style.display = 'none';\n      divider.style.display = 'none';\n      return;\n    }\n\n    container.style.display = 'flex';\n    divider.style.display = 'flex';\n\n    oidcProviders.forEach(function(provider) {\n      var btn = document.createElement('button');\n      btn.className = 'oidc-btn';\n      var safeUrl = provider.iconUrl && /^https?:\\/\\//i.test(provider.iconUrl) ? provider.iconUrl : null;\n      var safeName = document.createElement('span');\n      safeName.textContent = 'Continue with ' + (provider.name || 'SSO');\n      if (safeUrl) {\n        var img = document.createElement('img');\n        img.src = safeUrl;\n        img.alt = provider.name || 'SSO';\n        btn.appendChild(img);\n      }\n      btn.appendChild(safeName);\n      btn.addEventListener('click', function() { startOidcLogin(provider.id); });\n      container.appendChild(btn);\n    });\n  }\n\n  async function startOidcLogin(providerId) {\n    try {\n      var res = await fetch('/api/auth/oidc/' + providerId);\n      var data = await res.json();\n      if (data.url) {\n        window.location.href = data.url;\n      } else {\n        showError('login', data.error || 'Failed to start SSO');\n      }\n    } catch(e) {\n      showError('login', 'Failed to start SSO');\n    }\n  }\n\n  async function loginWithPassword() {\n    var email = document.getElementById('login-email').value.trim();\n    var password = document.getElementById('login-password').value;\n    if (!email || !password) { showError('login', 'Please enter email and password'); return; }\n    try {\n      var res = await fetch('/api/auth/login', {\n        method: 'POST',\n        headers: { 'Content-Type': 'application/json' },\n        credentials: 'include',\n        body: JSON.stringify({ email: email, password: password, csrfToken: csrfToken })\n      });\n      var data = await res.json();\n      if (data.requires2FA) {\n        pending2FAToken = data.pendingToken;\n        closeModal('login');\n        openModal('2fa');\n        setTimeout(function() { document.getElementById('2fa-code').focus(); }, 100);\n        return;\n      }\n      if (res.ok && data.success) {\n        window.location.reload();\n      } else {\n        showError('login', data.error || 'Login failed');\n        fetch('/api/auth/csrf').then(function(r) { return r.json(); }).then(function(d) { csrfToken = d.token; }).catch(function() {});\n      }\n    } catch(e) {\n      showError('login', 'Connection error');\n    }\n  }\n\n  async function verify2FA() {\n    var code = document.getElementById('2fa-code').value.trim();\n    if (!code) { showError('2fa', 'Please enter your code'); return; }\n    try {\n      var res = await fetch('/api/auth/2fa/login', {\n        method: 'POST',\n        headers: { 'Content-Type': 'application/json' },\n        credentials: 'include',\n        body: JSON.stringify({ pendingToken: pending2FAToken, code: code })\n      });\n      var data = await res.json();\n      if (res.ok && data.success) {\n        window.location.reload();\n      } else {\n        showError('2fa', data.error || 'Invalid code');\n      }\n    } catch(e) {\n      showError('2fa', 'Connection error');\n    }\n  }\n\n  async function openSettingsModal() {\n    openModal('settings');\n    await load2FAStatus();\n  }\n\n  async function load2FAStatus() {\n    var container = document.getElementById('2fa-status');\n    try {\n      var res = await fetch('/api/auth/me', { credentials: 'include' });\n      var data = await res.json();\n      if (!data.user) return;\n      currentUser = data.user;\n      if (data.user.totpEnabled) {\n        setContent(container, [\n          h('p', {style: 'color:#22c55e;font-size:14px;margin-bottom:12px'}, '\\u2713 2FA is enabled'),\n          h('div', {className: 'form-group'}, h('label', null, 'Password to Disable'), h('input', {type: 'password', id: '2fa-disable-password', placeholder: 'Enter password'})),\n          h('div', {className: 'error-text', id: '2fa-setup-error'}),\n          h('button', {className: 'btn btn-secondary', 'data-action': 'disable2FA', style: 'margin-top:8px'}, 'Disable 2FA')\n        ]);\n      } else {\n        setContent(container, [\n          h('p', {style: 'color:var(--text-soft);font-size:14px;margin-bottom:12px'}, '2FA is not enabled'),\n          h('div', {className: 'error-text', id: '2fa-setup-error'}),\n          h('button', {className: 'btn btn-secondary', 'data-action': 'setup2FA', style: 'margin-top:8px'}, 'Enable 2FA')\n        ]);\n      }\n    } catch(e) {}\n  }\n\n  async function setup2FA() {\n    var errEl = document.getElementById('2fa-setup-error');\n    if (errEl) errEl.textContent = '';\n    try {\n      var res = await fetch('/api/auth/2fa/setup', { method: 'POST', credentials: 'include', headers: { 'x-csrf-token': csrfToken } });\n      var data = await res.json();\n      if (data.error) {\n        if (errEl) errEl.textContent = data.error;\n        return;\n      }\n      var container = document.getElementById('2fa-status');\n      setContent(container, [\n        h('p', {style: 'font-size:14px;color:var(--text-soft);margin-bottom:12px'}, 'Scan this QR code with your authenticator app, then enter the code below.'),\n        h('div', {style: 'text-align:center;margin-bottom:16px'}, h('div', {id: '2fa-qr', style: 'display:inline-block;background:white;padding:16px;border-radius:8px'})),\n        h('p', {style: 'font-size:12px;color:var(--text-soft);margin-bottom:16px;word-break:break-all;text-align:center'}, 'Manual entry: ', data.secret),\n        h('div', {className: 'form-group'}, h('label', null, 'Verification Code'), h('input', {type: 'text', id: '2fa-setup-code', placeholder: '000000', maxLength: '6', inputMode: 'numeric', style: 'text-align:center;font-size:20px;letter-spacing:6px'})),\n        h('div', {className: 'error-text', id: '2fa-setup-error'}),\n        h('button', {className: 'btn btn-primary', 'data-action': 'verify2FASetup', style: 'margin-top:8px'}, 'Verify & Enable')\n      ]);\n      var renderSetupQR = function() {\n        try {\n          var el = document.getElementById('2fa-qr');\n          var qr = qrcode(0, 'M');\n          qr.addData(data.otpauthUrl);\n          qr.make();\n          var svg = qr.createSvgTag({ cellSize: 4, margin: 4 });\n          var parsed = new DOMParser().parseFromString(svg, 'image/svg+xml');\n          el.appendChild(document.importNode(parsed.documentElement, true));\n        } catch(e) {}\n      };\n      if (typeof qrcode !== 'undefined') {\n        renderSetupQR();\n      } else {\n        var s = document.createElement('script');\n        s.src = '/qrcode.min.js';\n        s.onload = renderSetupQR;\n        document.head.appendChild(s);\n      }\n    } catch(e) {\n      if (errEl) errEl.textContent = 'Connection error';\n    }\n  }\n\n  async function verify2FASetup() {\n    var code = document.getElementById('2fa-setup-code').value.trim();\n    var errEl = document.getElementById('2fa-setup-error');\n    if (!code || code.length !== 6) {\n      if (errEl) errEl.textContent = 'Enter the 6 digit code';\n      return;\n    }\n    try {\n      var res = await fetch('/api/auth/2fa/verify', {\n        method: 'POST',\n        headers: { 'Content-Type': 'application/json', 'x-csrf-token': csrfToken },\n        credentials: 'include',\n        body: JSON.stringify({ code: code })\n      });\n      var data = await res.json();\n      if (data.backupCodes) {\n        var container = document.getElementById('2fa-status');\n        var codeElements = [];\n        data.backupCodes.forEach(function(code, i) {\n          if (i > 0) codeElements.push(h('br', null));\n          codeElements.push(code);\n        });\n        setContent(container, [\n          h('p', {style: 'color:#22c55e;font-size:14px;margin-bottom:12px'}, '\\u2713 2FA has been enabled!'),\n          h('p', {style: 'font-size:14px;color:var(--text-soft);margin-bottom:12px'}, 'Save these backup codes in a safe place. Each can only be used once.'),\n          h('div', {style: 'background:var(--bg);border:1px solid var(--border);border-radius:8px;padding:16px;font-family:monospace;font-size:14px;line-height:2'}, codeElements),\n          h('button', {className: 'btn btn-secondary', 'data-action': 'load2FAStatus', style: 'margin-top:16px'}, 'Done')\n        ]);\n      } else {\n        if (errEl) errEl.textContent = data.error || 'Verification failed';\n      }\n    } catch(e) {\n      if (errEl) errEl.textContent = 'Connection error';\n    }\n  }\n\n  async function disable2FA() {\n    var password = document.getElementById('2fa-disable-password').value;\n    var errEl = document.getElementById('2fa-setup-error');\n    if (!password) { if (errEl) errEl.textContent = 'Password required'; return; }\n    try {\n      var res = await fetch('/api/auth/2fa/disable', {\n        method: 'POST',\n        headers: { 'Content-Type': 'application/json', 'x-csrf-token': csrfToken },\n        credentials: 'include',\n        body: JSON.stringify({ password: password })\n      });\n      var data = await res.json();\n      if (res.ok && data.success) {\n        await load2FAStatus();\n      } else {\n        if (errEl) errEl.textContent = data.error || 'Failed to disable 2FA';\n      }\n    } catch(e) {\n      if (errEl) errEl.textContent = 'Connection error';\n    }\n  }\n\n  async function requestEmailChange() {\n    var newEmail = document.getElementById('settings-new-email').value.trim();\n    var password = document.getElementById('settings-email-password').value;\n    var errEl = document.getElementById('email-change-error');\n    var successEl = document.getElementById('email-change-success');\n    errEl.textContent = '';\n    successEl.textContent = '';\n    if (!newEmail || !password) { errEl.textContent = 'Email and password required'; return; }\n    try {\n      var res = await fetch('/api/auth/email-change', {\n        method: 'POST',\n        headers: { 'Content-Type': 'application/json', 'x-csrf-token': csrfToken },\n        credentials: 'include',\n        body: JSON.stringify({ newEmail: newEmail, password: password })\n      });\n      var data = await res.json();\n      if (res.ok && data.success) {\n        successEl.textContent = 'Verification email sent to ' + newEmail;\n        document.getElementById('settings-new-email').value = '';\n        document.getElementById('settings-email-password').value = '';\n      } else {\n        errEl.textContent = data.error || 'Failed to request email change';\n      }\n    } catch(e) {\n      errEl.textContent = 'Connection error';\n    }\n  }\n\n  async function registerUser() {\n    var displayName = document.getElementById('register-name').value.trim();\n    var email = document.getElementById('register-email').value.trim();\n    var password = document.getElementById('register-password').value;\n    if (!email || !password) { showError('register', 'Please enter email and password'); return; }\n    if (password.length < 8) { showError('register', 'Password must be at least 8 characters'); return; }\n    if (!/[a-zA-Z]/.test(password)) { showError('register', 'Password must contain at least one letter'); return; }\n    if (!/[0-9!@#$%^&*()_+\\-=\\[\\]{};\\':\"\\\\|,.<>\\/?]/.test(password)) { showError('register', 'Password must contain a number or special character'); return; }\n    try {\n      var res = await fetch('/api/auth/register', {\n        method: 'POST',\n        headers: { 'Content-Type': 'application/json' },\n        credentials: 'include',\n        body: JSON.stringify({ email: email, password: password, displayName: displayName, csrfToken: csrfToken })\n      });\n      var data = await res.json();\n      if (data.success) {\n        if (data.requiresVerification) {\n          closeModal('register');\n          showToast('Please check your email to verify your account.', 'success');\n        } else {\n          window.location.reload();\n        }\n      } else {\n        showError('register', data.error || 'Registration failed');\n        fetch('/api/auth/csrf').then(function(r) { return r.json(); }).then(function(d) { csrfToken = d.token; }).catch(function() {});\n      }\n    } catch(e) {\n      showError('register', 'Connection error');\n    }\n  }\n\n  async function requestPasswordReset() {\n    var email = document.getElementById('forgot-email').value.trim();\n    if (!email) { showError('forgot', 'Please enter your email'); return; }\n    try {\n      var res = await fetch('/api/auth/forgot-password', {\n        method: 'POST',\n        headers: { 'Content-Type': 'application/json' },\n        credentials: 'include',\n        body: JSON.stringify({ email: email })\n      });\n      var data = await res.json();\n      document.getElementById('forgot-error').classList.remove('active');\n      var success = document.getElementById('forgot-success');\n      success.textContent = 'If an account exists, we\\'ve sent a reset link.';\n      success.classList.add('active');\n    } catch(e) {\n      showError('forgot', 'Connection error');\n    }\n  }\n\n  async function requestMagicLink() {\n    var email = document.getElementById('magic-email').value.trim();\n    if (!email) { showError('magic', 'Please enter your email'); return; }\n    try {\n      var res = await fetch('/api/auth/magic-link', {\n        method: 'POST',\n        headers: { 'Content-Type': 'application/json' },\n        credentials: 'include',\n        body: JSON.stringify({ email: email })\n      });\n      var data = await res.json();\n      document.getElementById('magic-error').classList.remove('active');\n      var success = document.getElementById('magic-success');\n      success.textContent = 'If an account exists, we\\'ve sent a magic link.';\n      success.classList.add('active');\n    } catch(e) {\n      showError('magic', 'Connection error');\n    }\n  }\n\n  async function logout() {\n    try {\n      await fetch('/api/logout', { method: 'POST', credentials: 'include' });\n    } catch(e) {}\n    localStorage.removeItem('collab_token');\n    localStorage.removeItem('user_token');\n    currentUser = null;\n    clearAllAuthForms();\n    document.querySelectorAll('.modal-overlay.active').forEach(function(m) { m.classList.remove('active'); });\n    window.location.reload();\n  }\n\n  var urlParams = new URLSearchParams(window.location.search);\n  if (urlParams.get('auth_error')) {\n    showToast('Authentication error: ' + urlParams.get('auth_error'), 'error');\n    history.replaceState({}, '', '/');\n  }\n  if (urlParams.get('verified') === 'true') {\n    showToast('Email verified! You can now sign in.', 'success');\n    history.replaceState({}, '', '/');\n  }\n  if (urlParams.get('welcome') === 'true') {\n    showToast('Welcome! Your account has been created.', 'success');\n    history.replaceState({}, '', '/');\n  }\n  if (urlParams.get('error')) {\n    var errorMessages = {\n      'invalid_room': 'That room link is invalid.',\n      'room_not_found': 'That room no longer exists.',\n      'room_unavailable': 'Room is temporarily unavailable.',\n      'not_found': 'Page not found.'\n    };\n    showToast(errorMessages[urlParams.get('error')] || 'Something went wrong.', 'error');\n    history.replaceState({}, '', '/');\n  }\n\n  loadAuthState();\n\n  document.querySelector('.theme-toggle').addEventListener('click', toggleTheme);\n\n  document.querySelector('[data-action=\"openSettings\"]').addEventListener('click', openSettingsModal);\n  document.querySelector('[data-action=\"logout\"]').addEventListener('click', logout);\n\n  document.querySelector('[data-action=\"openLogin\"]').addEventListener('click', function() { openModal('login'); });\n  document.querySelector('[data-action=\"openRegister\"]').addEventListener('click', function() { openModal('register'); });\n\n  document.querySelector('[data-action=\"openCreate\"]').addEventListener('click', function() { openModal('create'); });\n  document.querySelector('[data-action=\"openJoin\"]').addEventListener('click', function() { openModal('join'); });\n\n  document.querySelectorAll('[data-close-modal]').forEach(function(btn) {\n    btn.addEventListener('click', function() { closeModal(btn.dataset.closeModal); });\n  });\n\n  document.querySelector('[data-action=\"createRoom\"]').addEventListener('click', withLoading(createRoom));\n  document.querySelector('[data-action=\"joinRoom\"]').addEventListener('click', withLoading(joinRoom));\n  document.querySelector('[data-action=\"loginWithPassword\"]').addEventListener('click', withLoading(loginWithPassword));\n  document.querySelector('[data-action=\"registerUser\"]').addEventListener('click', withLoading(registerUser));\n  document.querySelector('[data-action=\"requestPasswordReset\"]').addEventListener('click', withLoading(requestPasswordReset));\n  document.querySelector('[data-action=\"requestMagicLink\"]').addEventListener('click', withLoading(requestMagicLink));\n  document.querySelector('[data-action=\"verify2FA\"]').addEventListener('click', withLoading(verify2FA));\n  document.querySelector('[data-action=\"requestEmailChange\"]').addEventListener('click', withLoading(requestEmailChange));\n  document.querySelector('[data-action=\"clearFile\"]').addEventListener('click', clearFile);\n\n  document.querySelector('[data-action=\"openForgot\"]').addEventListener('click', function(e) {\n    e.preventDefault();\n    openModal('forgot');\n    closeModal('login');\n  });\n  document.querySelector('[data-action=\"openMagic\"]').addEventListener('click', function(e) {\n    e.preventDefault();\n    openModal('magic');\n    closeModal('login');\n  });\n\n  document.getElementById('2fa-status').addEventListener('click', async function(e) {\n    var target = e.target.closest('[data-action]');\n    if (!target) return;\n    target.disabled = true;\n    try {\n      var action = target.dataset.action;\n      if (action === 'disable2FA') await disable2FA();\n      else if (action === 'setup2FA') await setup2FA();\n      else if (action === 'verify2FASetup') await verify2FASetup();\n      else if (action === 'load2FAStatus') await load2FAStatus();\n    } finally { target.disabled = false; }\n  });\n\n})();\n"
  },
  {
    "path": "theonefile_verse/public/theonefile.html",
    "content": "<!DOCTYPE html> \n <html lang=\"en\" style=\"--panel: #0b0e13; --panel-alt: #10141b; --accent: #4fd1c5; --danger: #f56565; --text-main: #e2e8f0; --text-soft: #94a3b8; --topbar-bg: rgba(9, 12, 20, 0.9); --topbar-border: #1f2533; --topbar-height: 100px; --sidebar-width: 435px; --mobile-footer-height: 20vh; --draw-toolbar-height: 45px; --sidebar-bg: #10141b; --btn-bg: #0b0e13; --btn-text: #e2e8f0; --tag-fill: #1e293b; --tag-text: #e2e8f0; --tag-border: #475569; --input-bg: #0b0e13; --input-text: #e2e8f0; --input-border: #1f2937; --input-font: Inter, system-ui, sans-serif; --input-font-size: 14px; --toolbar-bg: #0f172a; --toolbar-border: #1f2937; --toolbar-text: #94a3b8; --toolbar-btn-bg: #0b0e13; --toolbar-btn-text: #e2e8f0; --minimap-dots: #94a3b8; --canvas-hint-bg: #0f172a; --canvas-hint-color: #94a3b8; --node-fill: #1e293b; --node-stroke: #475569; --node-title: #e2e8f0; --node-sub: #94a3b8; --node-title-size: 18px; --node-sub-size: 13px; --node-font: Inter, system-ui, sans-serif; --default-edge: #475569; --selection-handle: #f59e0b; --selection-handle-size: 8px; --group-indicator: #4fd1c5;\"><head>\n    <meta charset=\"UTF-8\">\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>The One File: The Networkening</title>\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <!--\n      * ==================================================================================\n      * The One File: The Networkening\n      * !!!!!!!!!!!!!!!!!!!NOTE: THIS IS THE ONLINE VERSION!!!!!!!!!!!!!!!!!!!!!!\n      * Online version uses 3 cdn calls from cdn.jsdelivr.net to display additional icons\n      * Since 3.0 Online version uses http as a form of ping to display uptime\n      * \"There can be only one\". A all in one file topology maker.\n      *\n      * This is your last backup when all others fail. A completely self contained\n      * network topology visualization tool that works as a single HTML file.\n      * Open it anywhere, anytime and the idea lives forever.\n      * ==================================================================================\n      -->\n    <style>\n      :root {\n      color-scheme: dark;\n      --bg: #050608;\n      --panel: #0b0e13;\n      --panel-alt: #10141b;\n      --accent: #4fd1c5;\n      --danger: #f56565;\n      --text-main: #e2e8f0;\n      --text-soft: #94a3b8;\n      --edge-main: #475569;\n      --node-min: 35px;\n      --node-max: 70px;\n      --topbar-bg: rgba(9, 12, 20, 0.9);\n      --topbar-border: #1f2533;\n      }\n\t  html, body, svg, .map-container {\n      touch-action: none;\n      }\n      * {\n      box-sizing: border-box;\n      user-select: none;\n      }\n      input,\n      textarea,\n      [contenteditable=\"true\"] {\n      user-select: text;\n      -webkit-user-select: text;\n      }\n      body {\n      margin: 0;\n      font-family: system-ui, sans-serif;\n      background: radial-gradient(circle at top, #1e2532 0, #050608 70%);\n      color: var(--text-main);\n      display: flex;\n      flex-direction: column;\n      height: 100vh;\n      overflow: hidden;\n      }\n      header {\n      padding: 0 20px;\n      height: var(--topbar-height, 52px);\n      min-height: var(--topbar-height, 52px);\n      background: var(--topbar-bg);\n      backdrop-filter: blur(6px);\n      display: flex;\n      justify-content: space-between;\n      align-items: center;\n      border-bottom: 1px solid var(--topbar-border);\n      gap: 16px;\n      }\n      .title-block {\n      display: flex;\n      flex-direction: column;\n      gap: 6px;\n      min-width: 0;\n      }\n      header h1 {\n      font-size: clamp(22px, 3vw, 32px);\n      margin: 0;\n      white-space: nowrap;\n      text-overflow: ellipsis;\n      overflow: hidden;\n      }\n      .editable-page-title {\n      cursor: pointer;\n      transition: opacity 0.2s;\n      }\n      .editable-page-title:hover {\n      opacity: 0.7;\n      }\n      .save-row {\n      display: flex;\n      align-items: center;\n      gap: 8px;\n      flex-wrap: wrap;\n      }\n\t  .toggle-switch{position:relative;display:inline-block;min-width:44px !important;height:24px;flex-shrink:0;vertical-align:middle;}\n\t\t.toggle-switch input{opacity:0;width:0;height:0;position:absolute;}\n\t\t.toggle-slider{position:absolute;cursor:pointer;top:0;left:0;right:0;bottom:0;background:#475569;transition:.25s;border-radius:24px;}\n\t\t.toggle-slider:before{position:absolute;content:\"\";height:18px;width:18px;left:3px;bottom:3px;background:#e2e8f0;transition:.25s;border-radius:50%;}\n\t\t.toggle-switch input:checked+.toggle-slider{background:var(--accent);}\n\t\t.toggle-switch input:checked+.toggle-slider:before{transform:translateX(20px);}\n\t\t.anim-zone-row{display:flex;justify-content:space-between;align-items:center;padding:6px 0;}\n\t\t.anim-zone-row label{color:var(--text-main);font-size:14px;}\n\t\t.anim-zone-section{font-size:11px;color:var(--text-soft);margin:12px 0 6px;text-transform:uppercase;letter-spacing:0.05em;border-bottom:1px solid var(--edge-main);padding-bottom:4px;}\n\t\t.anim-zone-header{font-size:12px;color:var(--accent);margin-bottom:10px;text-transform:uppercase;letter-spacing:0.05em;font-weight:600;}\n      .save-btn {\n      padding: 6px 12px;\n      background: var(--accent);\n      color: var(--bg);\n      border: none;\n      border-radius: 6px;\n      cursor: pointer;\n      font-size: 14px;\n      font-weight: 600;\n      white-space: nowrap;\n      }\n      .save-btn:hover {\n      opacity: 0.9;\n      }\n      .help-icon {\n      width: 22px;\n      height: 22px;\n      border-radius: 50%;\n      border: 1px solid var(--edge-main);\n      display: inline-flex;\n      align-items: center;\n      justify-content: center;\n      font-size: 14px;\n      cursor: pointer;\n      color: var(--text-soft);\n      background: rgba(15, 23, 42, 0.9);\n      flex-shrink: 0;\n      }\n      .help-icon:hover {\n      color: var(--accent);\n      border-color: var(--accent);\n      }\n      #settings-btn {\n      background: var(--btn-bg, var(--panel));\n      color: var(--btn-text, var(--text-main));\n      border: 1px solid var(--edge-main);\n      padding: 6px 12px;\n      border-radius: 6px;\n      cursor: pointer;\n      font-size: 16px;\n      flex-shrink: 0;\n      }\n      #settings-btn:hover {\n      background: var(--accent);\n      color: var(--bg);\n      }\n      .header-resizer {\n      position: absolute;\n      bottom: 0;\n      left: 0;\n      right: 0;\n      height: 6px;\n      background: transparent;\n      cursor: row-resize;\n      z-index: 1000;\n      transition: background 0.2s;\n      }\n      .header-resizer:hover {\n      background: rgba(79, 209, 197, 0.3);\n      }\n      .header-resizer.resizing {\n      background: rgba(79, 209, 197, 0.5);\n      }\n      .sidebar-resizer {\n      position: absolute;\n      left: 0;\n      top: 0;\n      bottom: 0;\n      width: 6px;\n      background: transparent;\n      cursor: col-resize;\n      z-index: 1000;\n      transition: background 0.2s;\n      }\n      .sidebar-resizer:hover {\n      background: rgba(79, 209, 197, 0.3);\n      }\n      .sidebar-resizer.resizing {\n      background: rgba(79, 209, 197, 0.5);\n      }\n      .mobile-footer-resizer {\n      position: absolute;\n      top: 0;\n      left: 0;\n      right: 0;\n      height: 6px;\n      background: transparent;\n      cursor: row-resize;\n      z-index: 1000;\n      transition: background 0.2s;\n      display: none;\n      }\n      .mobile-footer-resizer:hover {\n      background: rgba(79, 209, 197, 0.3);\n      }\n      .mobile-footer-resizer.resizing {\n      background: rgba(79, 209, 197, 0.5);\n      }\n      @media (max-width: 900px) {\n      .mobile-footer-resizer {\n      display: block;\n      height: 12px;\n      }\n      .sidebar-resizer {\n      display: none;\n      }\n      .header-resizer {\n      height: 12px;\n      }\n      }\n      @media (pointer: coarse) {\n      .header-resizer {\n      height: 16px;\n      }\n      .mobile-footer-resizer {\n      height: 16px;\n      }\n      .sidebar-resizer {\n      width: 16px;\n      }\n      }\n      .resizer-icon {\n      position: absolute;\n      opacity: 0.5;\n      transition: opacity 0.2s, transform 0.2s;\n      pointer-events: none;\n      fill: var(--accent);\n      }\n      .header-resizer .resizer-icon {\n      left: 50%;\n      top: 50%;\n      transform: translate(-50%, -50%);\n      }\n      .sidebar-resizer .resizer-icon {\n      left: 50%;\n      top: 50%;\n      transform: translate(-50%, -50%);\n      }\n      .mobile-footer-resizer .resizer-icon {\n      left: 50%;\n      top: 50%;\n      transform: translate(-50%, -50%);\n      }\n      .header-resizer:hover .resizer-icon,\n      .sidebar-resizer:hover .resizer-icon,\n      .mobile-footer-resizer:hover .resizer-icon {\n      opacity: 1;\n      }\n      .header-resizer.resizing .resizer-icon,\n      .sidebar-resizer.resizing .resizer-icon,\n      .mobile-footer-resizer.resizing .resizer-icon {\n      opacity: 1;\n      transform: translate(-50%, -50%) scale(1.2);\n      }\n      body.resizing {\n      user-select: none;\n      }\n      body.resizing * {\n      cursor: inherit !important;\n      pointer-events: none;\n      }\n      header {\n      position: relative;\n      }\n      .details-panel {\n      position: relative;\n      }\n      main {\n      display: grid;\n      grid-template-columns: 1fr var(--sidebar-width, 350px);\n      flex: 1;\n      }\n      main.sidebar-collapsed {\n      grid-template-columns: 1fr 0;\n      }\n      @media (max-width: 900px) {\n      main {\n      grid-template-columns: 1fr;\n      grid-template-rows: calc(100vh - var(--topbar-height, 52px) - var(--mobile-footer-height, 40vh)) var(--mobile-footer-height, 40vh);\n      }\n      main.sidebar-collapsed {\n      grid-template-rows: 1fr 0;\n      }\n      .details-panel {\n      max-height: var(--mobile-footer-height, 40vh);\n      height: 100%;\n      }\n      }\n      .topology-panel {\n      background: var(--panel);\n      border-right: 1px solid #111827;\n      position: relative;\n      overflow: hidden;\n      }\n      .topology-toolbar {\n      position: absolute;\n      top: 10px;\n      right: 10px;\n      display: none;\n      align-items: center;\n      gap: 8px;\n      padding: 6px 10px;\n      background: var(--toolbar-bg, rgba(15, 23, 42, 0.92));\n      border: 1px solid var(--toolbar-border, #1f2937);\n      border-radius: 6px;\n      z-index: 99;\n      font-size: 13px;\n      color: var(--toolbar-text, #94a3b8);\n      }\n      .topology-toolbar label {\n      color: var(--toolbar-text, var(--text-soft));\n      }\n      .topology-toolbar select {\n      padding: 4px 6px;\n      background: var(--toolbar-btn-bg, var(--panel));\n      border: 1px solid var(--toolbar-border, var(--edge-main));\n      border-radius: 4px;\n      color: var(--toolbar-btn-text, var(--text-main));\n      font-size: 13px;\n      cursor: pointer;\n      }\n      .topology-toolbar button {\n      padding: 4px 10px;\n      background: var(--toolbar-btn-bg, var(--panel));\n      color: var(--toolbar-btn-text, var(--text-main));\n      border: 1px solid var(--toolbar-border, var(--edge-main));\n      border-radius: 4px;\n      cursor: pointer;\n      font-size: 13px;\n      font-weight: 600;\n      }\n      .topology-toolbar button:hover {\n      background: var(--accent);\n      color: var(--bg);\n      }\n      .bulk-toolbar-desktop,\n      .bulk-toolbar-mobile {\n      position: fixed;\n      bottom: 20px;\n      left: 50%;\n      transform: translateX(-50%);\n      z-index: 9999;\n      }\n      .bulk-toolbar-desktop {\n      position: absolute;\n      top: 10px;\n      right: 10px;\n      bottom: auto;\n      left: auto;\n      transform: none;\n      }\n      @media (min-width: 768px) {\n      .bulk-toolbar-mobile {\n      display: none !important;\n      }\n      }\n      .bulk-action-btn {\n      padding: 16px;\n      background: var(--panel-main);\n      border: 1px solid var(--edge-main);\n      border-radius: 8px;\n      color: var(--text-main);\n      font-size: 24px;\n      cursor: pointer;\n      text-align: center;\n      line-height: 1.2;\n      }\n      .bulk-action-btn:active {\n      transform: scale(0.95);\n      background: var(--accent);\n      }\n      .draw-toolbar {\n      position: absolute;\n      top: 10px;\n      left: 10px;\n      display: flex;\n      align-items: center;\n      gap: 6px;\n      padding: 6px 8px;\n      background: var(--toolbar-bg, rgba(15, 23, 42, 0.92));\n      border: 1px solid var(--toolbar-border, #1f2937);\n      border-radius: 6px;\n      z-index: 99;\n      font-size: 13px;\n      pointer-events: auto;\n      color: var(--toolbar-text, #94a3b8);\n      }\n      .draw-toolbar button {\n      padding: 4px 8px;\n      background: var(--toolbar-btn-bg, var(--panel));\n      color: var(--toolbar-btn-text, var(--text-main));\n      border: 1px solid var(--toolbar-border, var(--edge-main));\n      border-radius: 4px;\n      cursor: pointer;\n      font-size: 13px;\n      }\n      .draw-toolbar button:hover {\n      background: var(--accent);\n      color: var(--bg);\n      }\n      .draw-toolbar input[type=\"color\"] {\n      width: 30px;\n      height: 22px;\n      border-radius: 4px;\n      border: 1px solid var(--edge-main);\n      padding: 0;\n      background: transparent;\n      cursor: pointer;\n      }\n      .draw-toolbar select {\n      padding: 4px 6px;\n      background: var(--toolbar-btn-bg, var(--panel));\n      border: 1px solid var(--toolbar-border, var(--edge-main));\n      border-radius: 4px;\n      color: var(--toolbar-btn-text, var(--text-main));\n      font-size: 13px;\n      cursor: pointer;\n      }\n      .legend-container {\n      position: absolute;\n      left: 10px;\n      bottom: 10px;\n      display: flex;\n      flex-direction: column;\n      gap: 6px;\n      padding: 8px 10px;\n      background: var(--toolbar-bg, rgba(15, 23, 42, 0.92));\n      border: 1px solid var(--toolbar-border, #1f2937);\n      border-radius: 6px;\n      font-size: 12px;\n      z-index: 20;\n      max-width: 260px;\n      pointer-events: auto;\n      color: var(--toolbar-text, #94a3b8);\n      }\n      .legend-container {\n      padding-right: 22px;\n      }\n      @media (max-width: 900px) {\n      .legend-container {\n      max-height: calc(100vh - var(--topbar-height, 100px) - 120px);\n      overflow-y: auto;\n      }\n      }\n      @media (max-width: 900px) {\n      .bulk-toolbar-desktop,\n      .topology-toolbar {\n      display: none !important;\n      }\n      }\n      .legend-close-btn {\n      position: absolute;\n      top: 5px;\n      right: 5px;\n      width: 18px;\n      height: 18px;\n      border-radius: 999px;\n      border: 1px solid var(--edge-main);\n      background: var(--panel-alt);\n      color: var(--text-soft);\n      font-size: 11px;\n      display: flex;\n      align-items: center;\n      justify-content: center;\n      cursor: pointer;\n      }\n      .legend-close-btn:hover {\n      background: var(--danger);\n      color: #fff;\n      }\n      .legend-mini-btn {\n      position: absolute;\n      left: 10px;\n      padding: 4px 8px;\n      border-radius: 4px;\n      border: 1px solid var(--toolbar-border, #1f2937);\n      background: var(--toolbar-bg, rgba(15, 23, 42, 0.92));\n      color: var(--toolbar-btn-text, var(--text-main));\n      font-size: 11px;\n      cursor: pointer;\n      z-index: 20;\n      display: none;\n      transition: all 0.2s;\n      }\n      .legend-mini-btn:hover,\n      .legend-mini-btn:active {\n      background: var(--accent);\n      color: var(--bg);\n      border-color: var(--accent);\n      }\n      .legend-title {\n      font-size: 11px;\n      text-transform: uppercase;\n      letter-spacing: 0.06em;\n      color: var(--toolbar-text, var(--text-soft));\n      margin-bottom: 2px;\n      }\n\t  .fov-group {\n        transition: opacity 0.3s ease;\n      }\n      g[data-node-id]:not(:hover) .fov-group {\n        opacity: 0.7;\n      }\n      g[data-node-id]:hover .fov-group {\n        opacity: 1;\n      }\n      .legend-item {\n      display: flex;\n      align-items: center;\n      gap: 8px;\n      }\n      .legend-swatch {\n      width: 14px;\n      height: 14px;\n      border-radius: 3px;\n      border: 1px solid #020617;\n      flex-shrink: 0;\n      }\n      .legend-label {\n      outline: none;\n      cursor: text;\n      flex: 1;\n      min-width: 60px;\n      user-select: text;\n      -webkit-user-select: text;\n      }\n      .legend-label.editing {\n      border-bottom: 1px dashed var(--accent);\n      }\n      .canvas-viewport {\n      position: absolute;\n      top: 0;\n      left: 0;\n      right: 0;\n      bottom: 0;\n      overflow: hidden;\n      }\n      .canvas-viewport.panning {\n      cursor: grabbing !important;\n      }\n      .canvas-viewport.panning * {\n      cursor: grabbing !important;\n      }\n      .canvas-viewport svg {\n      width: 100%;\n      height: 100%;\n      display: block; \n      }\n      .zoom-toolbar {\n      display: flex;\n      gap: 4px;\n      padding: 6px;\n      background: var(--toolbar-bg, rgba(15, 23, 42, 0.92));\n      border: 1px solid var(--toolbar-border, #1f2937);\n      border-radius: 6px;\n      }\n      .zoom-toolbar button {\n      width: 32px;\n      height: 32px;\n      padding: 0;\n      background: var(--toolbar-btn-bg, var(--panel));\n      color: var(--toolbar-btn-text, var(--text-main));\n      border: 1px solid var(--toolbar-border, var(--edge-main));\n      border-radius: 4px;\n      cursor: pointer;\n      font-size: 18px;\n      font-weight: bold;\n      display: flex;\n      align-items: center;\n      justify-content: center;\n      transition: all 0.15s;\n      }\n      .zoom-toolbar button:hover {\n      background: var(--accent);\n      color: var(--bg);\n      }\n      .zoom-toolbar .zoom-level {\n      font-size: 11px;\n      color: var(--toolbar-text, var(--text-soft));\n      text-align: center;\n      padding: 2px 0;\n      min-width: 32px;\n      }\n      .zoom-toolbar .divider {\n      height: 1px;\n      background: var(--toolbar-border, var(--edge-main));\n      margin: 2px 0;\n      }\n      .minimap-zoom-wrapper {\n      position: absolute;\n      bottom: 10px;\n      right: 10px;\n      z-index: 99;\n      }\n      .minimap-container {\n      background: var(--toolbar-bg, rgba(15, 23, 42, 0.92));\n      border: 1px solid var(--toolbar-border, #1f2937);\n      border-radius: 6px;\n      overflow: hidden;\n      margin-bottom: 4px;\n      }\n      .minimap-container svg {\n      width: 100%;\n      height: 100%;\n      }\n\t.minimap-container,\n\t.node-panel,\n\t.edge-panel,\n\t#canvas-viewport {\n\t  contain: layout paint;\n\t}\t\n\t.low-zoom .node-group .node-circle,\n\t.low-zoom .node-group.active .node-circle,\n\t.low-zoom .node-group.selected .node-circle {\n\t  filter: none !important;\n\t  animation: none !important;\n\t}\t\n      .minimap-viewport {\n      fill: rgba(79, 209, 197, 0.2);\n      stroke: var(--accent);\n      stroke-width: 2;\n      cursor: move;\n      }\n      .minimap-node {\n      fill: var(--minimap-dots, var(--text-soft));\n      }\n      .minimap-edge {\n      stroke: var(--edge-main);\n      stroke-width: 8px;\n      fill: none;\n      }\n      .minimap-wall {\n      pointer-events: none;\n      }\n      .minimap-rect {\n      pointer-events: none;\n      }\n\t  .minimap-close-btn {\n      position: absolute;\n      top: 2px;\n      right: 2px;\n      width: 18px;\n      height: 18px;\n      background: var(--toolbar-btn-bg, rgba(15, 23, 42, 0.9));\n      border: 1px solid var(--toolbar-border, var(--edge-main));\n      border-radius: 3px;\n      color: var(--toolbar-text, var(--text-soft));\n      font-size: 11px;\n      cursor: pointer;\n      display: flex;\n      align-items: center;\n      justify-content: center;\n      z-index: 10;\n      padding: 0;\n      line-height: 1;\n      }\n      .minimap-close-btn:hover {\n      background: var(--danger);\n      color: white;\n      border-color: var(--danger);\n      }\n\t  .toolbar-close-btn {\n      width: 24px;\n      height: 24px;\n      background: var(--toolbar-btn-bg, rgba(15, 23, 42, 0.9));\n      border: 1px solid var(--toolbar-border, var(--edge-main));\n      border-radius: 4px;\n      color: var(--toolbar-text, var(--text-soft));\n      font-size: 12px;\n      cursor: pointer;\n      display: flex;\n      align-items: center;\n      justify-content: center;\n      padding: 0;\n      line-height: 1;\n      flex-shrink: 0;\n      }\n      .toolbar-close-btn:hover {\n      background: var(--danger);\n      color: white;\n      border-color: var(--danger);\n      }\n\t  .canvas-hint {\n      position: absolute;\n      top: 50px;\n      left: 50%;\n      transform: translateX(-50%);\n      padding: 8px 16px;\n      background: var(--canvas-hint-bg, rgba(15, 23, 42, 0.92));\n      border: 1px solid var(--toolbar-border, #1f2937);\n      border-radius: 6px;\n      font-size: 12px;\n      color: var(--canvas-hint-color, var(--text-soft));\n      z-index: 18;\n      pointer-events: none;\n      opacity: 0;\n      transition: opacity 0.3s;\n      }\n      .canvas-hint.visible {\n      opacity: 1;\n      pointer-events: auto;\n      }\n      .edge {\n      stroke: var(--edge-main);\n      stroke-width: 4;\n      opacity: 0.75;\n      transition: 0.25s ease-in-out;\n      cursor: pointer;\n      }\n      .edge.backup {\n      stroke: var(--danger);\n      stroke-width: 5;\n      }\n      .edge.active {\n      opacity: 1;\n      stroke-width: 7;\n      filter: drop-shadow(0 0 8px var(--accent, #4fd1c5));\n      }\n      .rect-group.active .rect-shape {\n      filter: drop-shadow(0 0 10px var(--accent, #4fd1c5));\n      animation: pulse 1.2s infinite ease-in-out;\n      }\n      .text-element.active {\n      filter: drop-shadow(0 0 10px var(--accent, #4fd1c5));\n      animation: pulse 1.2s infinite ease-in-out;\n      }\n      .free-preview {\n      fill: none;\n      stroke-dasharray: 4 4;\n      pointer-events: none;\n      }\n@keyframes edge-flow-arrow {\n  0% { offset-distance: 0%; }\n  100% { offset-distance: 100%; }\n}\n@keyframes edge-flow-arrow-reverse {\n  0% { offset-distance: 100%; }\n  100% { offset-distance: 0%; }\n}\n.edge-arrow-forward {\n  offset-rotate: auto;\n  animation: edge-flow-arrow 1.5s linear infinite;\n}\n.edge-arrow-backward {\n  offset-rotate: auto 180deg;\n  animation: edge-flow-arrow-reverse 1.5s linear infinite;\n}\n      .free-point {\n      fill: #e5e7eb;\n      stroke: #0f172a;\n      stroke-width: 1.5;\n      cursor: grab;\n      }\n      .node-circle {\n      fill: var(--node-fill, #1e293b);\n      stroke: var(--node-stroke, #475569);\n      stroke-width: 2;\n      transition: 0.25s ease;\n      transform-origin: center center;\n      }\n      .node-hit-area {\n      cursor: grab;\n      pointer-events: all;\n      }\n      .node-group:hover .node-circle {\n      filter: drop-shadow(0 0 10px rgba(79, 209, 197, 0.45));\n      }\n      .node-group.active .node-circle {\n      stroke: var(--accent);\n      animation: pulse 1.2s infinite ease-in-out;\n      }\n      @keyframes pulse {\n      0% {\n      filter: drop-shadow(0 0 4px #4fd1c5);\n      }\n      50% {\n      filter: drop-shadow(0 0 14px #4fd1c5);\n      }\n      100% {\n      filter: drop-shadow(0 0 4px #4fd1c5);\n      }\n      }\n      @keyframes done-pulse {\n        0%, 100% {\n          transform: scale(1);\n          box-shadow: 0 0 0 0 rgba(79, 209, 197, 0.7);\n        }\n        50% {\n          transform: scale(1.05);\n          box-shadow: 0 0 0 8px rgba(79, 209, 197, 0);\n        }\n      }\n      .done-btn-active {\n        animation: done-pulse 1.5s ease-in-out infinite;\n        background: var(--accent) !important;\n        color: var(--bg) !important;\n      }\n      .node-label {\n      fill: var(--text-main);\n      font-size: 18px;\n      text-anchor: middle;\n      font-weight: 600;\n      }\n      .node-sub {\n      fill: var(--text-soft);\n      font-size: 13px;\n      text-anchor: middle;\n      }\n      .ping-indicator {\n      fill: #6b7280;\n      stroke: #4b5563;\n      stroke-width: 1;\n      }\n      .ping-indicator.online {\n      fill: #10b981;\n      stroke: #059669;\n      }\n      .ping-indicator.offline {\n      fill: #ef4444;\n      stroke: #dc2626;\n      }\n      .ping-indicator.checking {\n      fill: #f59e0b;\n      stroke: #d97706;\n      }\n      @media (max-width: 1024px) {\n      .node-label {\n      font-size: 28px;\n      }\n      .node-sub {\n      font-size: 20px;\n      }\n      }\n      @media (max-width: 768px) {\n      .node-label {\n      font-size: 70px;\n      }\n      .node-sub {\n      font-size: 50px;\n      }\n      }\n      @media (max-width: 380px) {\n      .node-label {\n      font-size: 60px;\n      }\n      .node-sub {\n      font-size: 42px;\n      }\n      }\n      .details-panel {\n      background: var(--sidebar-bg, var(--panel-alt));\n      padding: 22px;\n      padding-bottom: 80px;\n      overflow-y: auto;\n      display: flex;\n      flex-direction: column;\n      gap: 15px;\n      position: relative;\n      transition:\n      width 0.3s ease,\n      min-width 0.3s ease,\n      padding 0.3s ease,\n      opacity 0.3s ease;\n      }\n      .details-panel {\n      min-width: 260px !important;\n      }\n      .details-panel {\n      overflow-y: auto !important;\n      overflow-x: hidden;\n      }\n      .details-panel.collapsed {\n      width: 0 !important;\n      min-width: 0 !important;\n      padding: 0 !important;\n      overflow: hidden;\n      opacity: 0;\n      }\n      body {\n      overflow-x: hidden;\n      overflow-y: hidden;\n      }\n      main {\n      overflow-x: hidden;\n      overflow-y: visible;\n      }\n      .details-panel {\n      overflow-y: auto !important;\n      overflow-x: hidden;\n      }\n      .details-panel.collapsed {\n      min-width: 0 !important;\n      }\n      .sidebar-toggle {\n      position: absolute;\n      left: -16px;\n      top: 50%;\n      transform: translateY(-50%);\n      width: 16px;\n      height: 60px;\n      background: var(--sidebar-bg, var(--panel-alt));\n      border: 1px solid var(--edge-main);\n      border-right: none;\n      border-radius: 8px 0 0 8px;\n      cursor: pointer;\n      display: flex;\n      align-items: center;\n      justify-content: center;\n      color: var(--text-soft);\n      font-size: 10px;\n      z-index: 25;\n      transition:\n      background 0.2s,\n      color 0.2s;\n      }\n      .sidebar-toggle:hover {\n      background: var(--accent);\n      color: var(--bg);\n      }\n      .sidebar-toggle.collapsed {\n      left: 0;\n      border-right: 1px solid var(--edge-main);\n      border-radius: 0 8px 8px 0;\n      position: fixed;\n      right: 0;\n      left: auto;\n      }\n      .details-name {\n      font-size: clamp(22px, 2.5vw, 30px);\n      font-weight: 700;\n      }\n      .details-ip {\n      font-size: clamp(16px, 2vw, 22px);\n      color: var(--text-soft);\n      }\n      .details-role {\n      font-size: clamp(16px, 2vw, 22px);\n      color: var(--accent);\n      }\n      .size-controls {\n      display: flex;\n      gap: 10px;\n      align-items: center;\n      margin-top: 10px;\n      }\n      .size-controls label {\n      font-size: clamp(14px, 1.6vw, 18px);\n      color: var(--text-soft);\n      }\n      .size-controls input[type=\"range\"] {\n      flex: 1;\n      accent-color: var(--accent);\n      }\n      .size-controls button {\n      padding: 6px 12px;\n      background: var(--panel);\n      color: var(--text-main);\n      border: 1px solid var(--edge-main);\n      border-radius: 4px;\n      cursor: pointer;\n      font-size: clamp(12px, 1.4vw, 16px);\n      }\n      .size-controls button:hover {\n      background: var(--accent);\n      color: var(--bg);\n      }\n      .style-section {\n      margin-top: 15px;\n      padding-top: 15px;\n      border-top: 1px solid var(--edge-main);\n      }\n      .style-section summary {\n      cursor: pointer;\n      font-size: clamp(14px, 1.6vw, 20px);\n      text-transform: uppercase;\n      color: var(--text-soft);\n      list-style: none;\n      display: flex;\n      justify-content: space-between;\n      align-items: center;\n      user-select: none;\n      }\n      .style-section summary::-webkit-details-marker {\n      display: none;\n      }\n      .password-overlay {\n      position: fixed;\n      top: 0;\n      left: 0;\n      width: 100%;\n      height: 100%;\n      background: #ffffff;\n      z-index: 9999;\n      }\n      .style-section summary::after {\n      content: \"▼\";\n      transition: transform 0.2s;\n      }\n      .style-section[open] summary::after {\n      transform: rotate(180deg);\n      }\n      .style-content {\n  display: grid;\n  grid-template-columns: 1fr auto;\n  gap: 8px 12px;\n  align-items: center;\n  justify-items: end;\n}\n.style-content label {\n  justify-self: start;\n}\n      .style-row {\n      display: contents;\n      }\n#edge-panel .style-row,\n#rect-panel .style-row,\n#text-panel .style-row {\n  display: flex;\n  align-items: center;\n  gap: 12px;\n  margin-bottom: 8px;\n}\n#edge-panel .style-row label,\n#rect-panel .style-row label,\n#text-panel .style-row label {\n  min-width: 80px;\n}\nbutton:disabled {\n  opacity: 0.5;\n  cursor: not-allowed;\n}\n      .style-row label {\n      font-size: clamp(13px, 1.5vw, 17px);\n      color: var(--text-soft);\n      min-width: 80px;\n      }\n      .style-row input[type=\"color\"] {\n      width: 50px;\n      height: 30px;\n      border: 1px solid var(--edge-main);\n      border-radius: 4px;\n      cursor: pointer;\n      background: transparent;\n      }\n      .style-row input[type=\"number\"] {\n      width: 70px;\n      padding: 4px 8px;\n      background: var(--panel);\n      border: 1px solid var(--edge-main);\n      border-radius: 4px;\n      color: var(--text-main);\n      font-size: clamp(12px, 1.4vw, 16px);\n      }\n      .style-row select {\n      flex: 1;\n      padding: 4px 8px;\n      background: var(--panel);\n      border: 1px solid var(--edge-main);\n      border-radius: 4px;\n      color: var(--text-main);\n      font-size: clamp(12px, 1.4vw, 16px);\n      cursor: pointer;\n      }\n      .editable-text {\n      cursor: pointer;\n      transition: opacity 0.2s;\n      }\n      .editable-text:hover {\n      opacity: 0.7;\n      }\n      .modal {\n      display: none;\n      position: fixed;\n      top: 0;\n      left: 0;\n      width: 100%;\n      height: 100%;\n      z-index: 999999;\n      justify-content: center;\n      align-items: center;\n      overflow: auto;\n      }\n      .modal.active {\n      display: inline-grid;\n      touch-action: pan-y;\n      }\n      #dialog-modal {\n      z-index: 9999999;\n      background: rgba(0, 0, 0, 0.7);\n      }\n      .modal-content {\n      background: var(--panel-alt);\n      padding: 25px;\n      border-radius: 8px;\n      border: 1px solid var(--edge-main);\n      min-width: 300px;\n      max-width: 90%;\n      max-height: 85vh;\n      overflow-y: auto;\n      overscroll-behavior: contain;\n      }\n      .modal-content h3 {\n      margin: 0 0 15px 0;\n      color: var(--text-main);\n      font-size: clamp(18px, 2vw, 24px);\n      }\n      .modal-content p {\n      color: var(--text-soft);\n      font-size: clamp(14px, 1.6vw, 18px);\n      margin: 0 0 20px 0;\n      }\n      .modal-content input:not([type=\"color\"]),\n      .modal-content select {\n      width: 100%;\n      padding: 10px;\n      background: var(--panel);\n      border: 1px solid var(--edge-main);\n      border-radius: 4px;\n      color: var(--text-main);\n      font-size: clamp(14px, 1.6vw, 18px);\n      margin-bottom: 15px;\n      user-select: text;\n      -webkit-user-select: text;\n      }\n      .modal-content input[type=\"color\"] {\n      width: 60px;\n      height: 36px;\n      padding: 2px;\n      border: 2px solid var(--edge-main);\n      border-radius: 6px;\n      cursor: pointer;\n      background: none;\n      -webkit-appearance: none;\n      appearance: none;\n      }\n      .modal-content input[type=\"color\"]::-webkit-color-swatch-wrapper {\n      padding: 0;\n      }\n      .modal-content input[type=\"color\"]::-webkit-color-swatch {\n      border: none;\n      border-radius: 4px;\n      }\n      .modal-buttons {\n      display: flex;\n      gap: 10px;\n      justify-content: flex-end;\n      margin-top: 10px;\n      padding-top: 10px;\n      border-top: 1px solid var(--edge-main);\n      }\n      .modal-buttons button {\n      padding: 8px 16px;\n      border: none;\n      border-radius: 4px;\n      cursor: pointer;\n      font-size: clamp(14px, 1.6vw, 18px);\n      font-weight: 600;\n      }\n      .modal-buttons .btn-cancel {\n      background: var(--panel);\n      color: var(--text-main);\n      border: 1px solid var(--edge-main);\n      }\n      .modal-buttons .btn-save {\n      background: var(--accent);\n      color: var(--bg);\n      }\n      .modal-buttons .btn-delete {\n      background: var(--danger);\n      color: white;\n      }\n      .confirm-modal p {\n      color: var(--text-soft);\n      font-size: clamp(14px, 1.6vw, 18px);\n      margin: 0 0 20px 0;\n      }\n      #note-editor-modal .modal-content {\n      width: 600px;\n      max-width: 95vw;\n      max-height: 90vh;\n      display: flex;\n      flex-direction: column;\n      }\n      .note-editor-toolbar {\n      display: flex;\n      flex-wrap: wrap;\n      gap: 4px;\n      padding: 8px;\n      background: var(--panel);\n      border: 1px solid var(--edge-main);\n      border-bottom: none;\n      border-radius: 6px 6px 0 0;\n      }\n      .note-editor-toolbar button {\n      padding: 6px 10px;\n      background: var(--panel-alt);\n      border: 1px solid var(--edge-main);\n      border-radius: 4px;\n      color: var(--text-main);\n      cursor: pointer;\n      font-size: 14px;\n      min-width: 32px;\n      }\n      .note-editor-toolbar button:hover {\n      background: var(--accent);\n      color: var(--bg);\n      }\n      .note-editor-toolbar button.active {\n      background: var(--accent);\n      color: var(--bg);\n      }\n      .note-editor-toolbar .toolbar-separator {\n      width: 1px;\n      background: var(--edge-main);\n      margin: 0 4px;\n      }\n      .note-editor-toolbar select {\n      padding: 6px 8px;\n      background: var(--panel-alt);\n      border: 1px solid var(--edge-main);\n      border-radius: 4px;\n      color: var(--text-main);\n      cursor: pointer;\n      font-size: 14px;\n      }\n      .note-editor-content {\n      min-height: 200px;\n      max-height: 400px;\n      overflow-y: auto;\n      padding: 12px;\n      background: var(--panel);\n      border: 1px solid var(--edge-main);\n      border-radius: 0 0 6px 6px;\n      color: var(--text-main);\n      font-size: 14px;\n      line-height: 1.6;\n      outline: none;\n      }\n      .note-editor-content:focus {\n      border-color: var(--accent);\n      }\n      .note-editor-content h1 { font-size: 1.8em; margin: 0.5em 0; color: var(--text-main); }\n      .note-editor-content h2 { font-size: 1.5em; margin: 0.5em 0; color: var(--text-main); }\n      .note-editor-content h3 { font-size: 1.2em; margin: 0.5em 0; color: var(--text-main); }\n      .note-editor-content p { margin: 0.5em 0; }\n      .note-editor-content ul, .note-editor-content ol { margin: 0.5em 0; padding-left: 1.5em; }\n      .note-editor-content li { margin: 0.25em 0; }\n      .note-editor-content code {\n      background: var(--panel-alt);\n      padding: 2px 6px;\n      border-radius: 4px;\n      font-family: monospace;\n      font-size: 0.9em;\n      }\n      .note-editor-content pre {\n      background: var(--panel-alt);\n      padding: 12px;\n      border-radius: 6px;\n      overflow-x: auto;\n      margin: 0.5em 0;\n      }\n      .note-editor-content pre code {\n      background: none;\n      padding: 0;\n      }\n      .note-editor-content a { color: var(--accent); }\n      .note-editor-content blockquote {\n      border-left: 3px solid var(--accent);\n      margin: 0.5em 0;\n      padding-left: 12px;\n      color: var(--text-soft);\n      }\n      .notes-feed {\n      max-height: 200px;\n      overflow-y: auto;\n      display: flex;\n      flex-direction: column;\n      gap: 8px;\n      margin-bottom: 10px;\n      }\n      .note-card {\n      background: var(--panel);\n      border: 1px solid var(--edge-main);\n      border-radius: 6px;\n      padding: 10px;\n      cursor: pointer;\n      transition: border-color 0.2s;\n      }\n      .note-card:hover {\n      border-color: var(--accent);\n      }\n      .note-card-content {\n      font-size: 13px;\n      line-height: 1.5;\n      color: var(--text-main);\n      max-height: 60px;\n      overflow: hidden;\n      word-break: break-word;\n      }\n      .note-card-content h1, .note-card-content h2, .note-card-content h3 {\n      font-size: 14px;\n      margin: 0;\n      }\n      .note-card-content p { margin: 0; }\n      .note-card-content pre { margin: 0; font-size: 12px; }\n      .note-card-actions {\n      display: flex;\n      gap: 8px;\n      margin-top: 8px;\n      justify-content: flex-end;\n      }\n      .note-card-actions button {\n      padding: 4px 10px;\n      font-size: 12px;\n      border-radius: 4px;\n      border: 1px solid var(--edge-main);\n      background: var(--panel-alt);\n      color: var(--text-main);\n      cursor: pointer;\n      }\n      .note-card-actions button:hover {\n      background: var(--accent);\n      color: var(--bg);\n      }\n      .note-card-actions button.delete-note:hover {\n      background: var(--danger);\n      color: white;\n      }\n      .context-menu-item {\n      transition: background 0.15s;\n      }\n      .context-menu-item:hover {\n      background: var(--panel);\n      }\n      .details-info-row {\n      display: flex;\n      align-items: center;\n      margin: 6px 0;\n      }\n\t .node-group.selected .node-circle {\n      stroke: var(--selection-handle, #f59e0b);\n      stroke-width: 3;\n      }\n\t  .node-group.layer-faded {\n      pointer-events: none;\n      }\n      .edge.layer-faded {\n      pointer-events: none;\n      }\n      .node-group.search-highlight .node-circle,\n      .node-group.search-highlight rect,\n      .node-group.search-highlight polygon {\n      stroke: #10b981;\n      stroke-width: 3;\n      filter: drop-shadow(0 0 8px #10b981);\n      }\n\t  .node-group.search-faded {\n  opacity: 0.15;\n  pointer-events: none;\n}\n.node-group.search-faded .node-label,\n.node-group.search-faded .node-sub {\n  opacity: 0.3;\n}\n.edge-group.search-faded,\n.edge.search-faded {\n  opacity: 0.1;\n}\n      .badge-row {\n      display: flex;\n      flex-wrap: wrap;\n      gap: 8px;\n      }\n     .badge {\n      background: var(--tag-fill, #1e293b);\n      color: var(--tag-text, #e2e8f0);\n      border: 1px solid var(--tag-border, var(--edge-main));\n      padding: 5px 12px;\n      border-radius: 22px;\n      font-size: clamp(12px, 1.4vw, 18px);\n      }\n\t  .badge.wg {\n      color: var(--accent);\n      border-color: var(--accent);\n      }\n\t  input[type=\"text\"],\n      input[type=\"number\"],\n      input[type=\"password\"],\n      textarea,\n      select {\n      background: var(--input-bg, #0b0e13) !important;\n      color: var(--input-text, #e2e8f0) !important;\n      border-color: var(--input-border, #1f2937) !important;\n      font-family: var(--input-font, Inter, system-ui, sans-serif) !important;\n      font-size: var(--input-font-size, 14px) !important;\n      }\n      .section-label {\n      margin-top: 8px;\n      font-size: clamp(14px, 1.6vw, 20px);\n      text-transform: uppercase;\n      color: var(--text-soft);\n      }\n      .list li {\n      margin: 6px 0;\n      font-size: clamp(14px, 1.6vw, 20px);\n      cursor: pointer;\n      display: flex;\n      justify-content: space-between;\n      align-items: center;\n      gap: 10px;\n      }\n      .list li:hover {\n      color: var(--accent);\n      }\n      .delete-note {\n      color: var(--danger);\n      font-size: 18px;\n      cursor: pointer;\n      opacity: 0;\n      transition: opacity 0.2s;\n      flex-shrink: 0;\n      }\n      .list li:hover .delete-note {\n      opacity: 1;\n      }\n      .editing {\n      outline: 2px solid var(--accent);\n      background: #0d141f;\n      padding: 4px;\n      border-radius: 6px;\n      user-select: text;\n      -webkit-user-select: text;\n      }\n      .mobile-menu-btn {\n      display: none;\n      background: var(--panel);\n      border: 1px solid var(--edge-main);\n      color: var(--text-main);\n      font-size: 22px;\n      padding: 6px 12px;\n      border-radius: 6px;\n      cursor: pointer;\n      }\n      #topbar-menu {\n      gap: 8px;\n      align-items: center;\n      }\n      @media (min-width: 721px) {\n      #topbar-menu {\n      display: flex !important;\n      }\n      }\n      @media (max-width: 720px) {\n      #topbar-menu {\n      display: none;\n      }\n      #topbar-menu.open {\n      display: flex;\n      }\n      }\n      @media (max-width: 720px) {\n      .mobile-menu-btn {\n      display: block;\n      }\n      #topbar-menu {\n      position: absolute;\n      top: var(--topbar-height);\n      right: 0;\n      background: var(--panel-alt);\n      border-left: 1px solid var(--topbar-border);\n      border-bottom: 1px solid var(--topbar-border);\n      padding: 12px;\n      display: none;\n      flex-direction: column;\n      max-height: calc(100vh - var(--topbar-height));\n      overflow-y: auto;\n      overscroll-behavior: contain;\n      z-index: 999;\n      }\n      #topbar-menu.open {\n      display: flex;\n      touch-action: pan-y;\n      }\n      header {\n      position: relative;\n      z-index: 9999;\n      }\n      }\n      @media (max-width: 720px) {\n      .draw-toolbar {\n      position: absolute;\n      top: auto;\n      bottom: auto;\n      left: 10px !important;\n      right: auto !important;\n      width: auto;\n      gap: 6px;\n      padding: 8px;\n      }\n      .topology-toolbar {\n      position: absolute;\n      top: auto;\n      bottom: auto;\n      width: auto;\n      gap: 6px;\n      padding: 8px;\n      }\n      .draw-toolbar {\n      top: 10px !important;\n      }\n      .topology-toolbar {\n      top: calc(10px + var(--draw-toolbar-height, 50px)) !important;\n      }\n      .canvas-hint {\n      top: calc(10px + 120px);\n      }\n      }\n      .icon-picker-modal {\n      display: none;\n      position: fixed;\n      top: 0;\n      left: 0;\n      width: 100%;\n      height: 100%;\n      background: rgba(0, 0, 0, 0.8);\n      z-index: 999999999;\n      justify-content: center;\n      align-items: center;\n      }\n      .icon-picker-modal.active {\n      display: flex;\n      }\n      .icon-picker-content {\n      background: var(--panel);\n      border-radius: 12px;\n      width: 90%;\n      max-width: 800px;\n      max-height: 80vh;\n      display: flex;\n      flex-direction: column;\n      border: 1px solid var(--edge-main);\n      }\n      .icon-picker-header {\n      padding: 20px;\n      border-bottom: 1px solid var(--edge-main);\n      }\n      .icon-picker-header h3 {\n      margin: 0 0 15px 0;\n      color: var(--text-main);\n      }\n      .icon-picker-tabs {\n      display: flex;\n      gap: 10px;\n      margin-bottom: 15px;\n      flex-wrap: wrap;\n      }\n      .icon-picker-tab {\n      padding: 8px 16px;\n      background: var(--panel-alt);\n      border: 1px solid var(--edge-main);\n      border-radius: 6px;\n      cursor: pointer;\n      color: var(--text-soft);\n      font-size: 14px;\n      transition: all 0.2s;\n      }\n      .icon-picker-tab:hover {\n      background: var(--accent);\n      color: var(--bg);\n      }\n      .icon-picker-tab.active {\n      background: var(--accent);\n      color: var(--bg);\n      border-color: var(--accent);\n      }\n      .icon-picker-search {\n      width: 100%;\n      padding: 10px 15px;\n      background: var(--panel-alt);\n      border: 1px solid var(--edge-main);\n      border-radius: 6px;\n      color: var(--text-main);\n      font-size: 14px;\n      }\n      .icon-picker-search::placeholder {\n      color: var(--text-soft);\n      }\n      .icon-picker-body {\n      padding: 20px;\n      overflow-y: auto;\n      flex: 1;\n      }\n      .icon-grid {\n      display: grid;\n      grid-template-columns: repeat(auto-fill, minmax(80px, 1fr));\n      gap: 10px;\n      }\n      .icon-item {\n      display: flex;\n      flex-direction: column;\n      align-items: center;\n      justify-content: center;\n      padding: 15px 10px;\n      background: var(--panel-alt);\n      border: 1px solid var(--edge-main);\n      border-radius: 6px;\n      cursor: pointer;\n      transition: all 0.2s;\n      }\n      .icon-item:hover {\n      background: var(--accent);\n      border-color: var(--accent);\n      transform: scale(1.05);\n      }\n      .icon-item svg {\n      width: 32px;\n      height: 32px;\n      fill: var(--text-main);\n      }\n      .icon-item:hover svg {\n      fill: var(--bg);\n      }\n      .icon-item-name {\n      margin-top: 8px;\n      font-size: 10px;\n      color: var(--text-soft);\n      text-align: center;\n      word-break: break-word;\n      }\n      .icon-item:hover .icon-item-name {\n      color: var(--bg);\n      }\n      .icon-picker-loading {\n      text-align: center;\n      padding: 40px;\n      color: var(--text-soft);\n      }\n      .icon-picker-footer {\n      padding: 15px 20px;\n      border-top: 1px solid var(--edge-main);\n      display: flex;\n      justify-content: flex-end;\n      }\n      .icon-btn-cancel {\n      padding: 8px 20px;\n      background: var(--panel-alt);\n      color: var(--text-main);\n      border: 1px solid var(--edge-main);\n      border-radius: 6px;\n      cursor: pointer;\n      font-size: 14px;\n      }\n      .icon-btn-cancel:hover {\n      background: var(--edge-main);\n      }\n      .icon-badge {\n      display: inline-flex;\n      align-items: center;\n      gap: 5px;\n      padding: 4px 8px;\n      background: var(--panel-alt);\n      border-radius: 4px;\n      font-size: 12px;\n      margin: 2px;\n      }\n      .icon-badge svg {\n      width: 16px;\n      height: 16px;\n      fill: currentColor;\n      }\n      .pick-icon-btn {\n      padding: 6px 12px;\n      background: var(--accent);\n      color: var(--bg);\n      border: none;\n      border-radius: 6px;\n      cursor: pointer;\n      font-size: 13px;\n      font-weight: 600;\n      margin-top: 8px;\n      width: 100%;\n      }\n      .pick-icon-btn:hover {\n      opacity: 0.9;\n      }\n      @media (max-width: 768px) {\n      .icon-picker-content {\n      width: 95%;\n      max-height: 90vh;\n      }\n      .modal-content {\n      background: var(--panel-alt);\n      }\n      #search-input {\n      width: 100%;\n      }\n      }\n      .version-item {\n      display: flex;\n      justify-content: space-between;\n      align-items: center;\n      padding: 12px;\n      background: var(--panel);\n      border: 1px solid var(--edge-main);\n      border-radius: 6px;\n      cursor: pointer;\n      transition: all 0.2s;\n      }\n      .version-item:hover {\n      background: var(--panel-alt);\n      border-color: var(--accent);\n      }\n      .version-item.current {\n      border-color: var(--accent);\n      background: rgba(79, 209, 197, 0.1);\n      }\n      .version-info {\n      flex: 1;\n      }\n      .version-info .timestamp {\n      font-size: 13px;\n      color: var(--text-main);\n      font-weight: 600;\n      }\n      .version-info .details {\n      font-size: 11px;\n      color: var(--text-soft);\n      margin-top: 4px;\n      }\n      .version-actions {\n      display: flex;\n      gap: 6px;\n      }\n      .audit-entry {\n      padding: 8px 12px;\n      background: var(--panel);\n      border-left: 3px solid var(--edge-main);\n      border-radius: 4px;\n      font-size: 12px;\n      }\n      .audit-entry.node { border-left-color: #4fd1c5; }\n      .audit-entry.connection { border-left-color: #9f7aea; }\n      .audit-entry.style { border-left-color: #ed8936; }\n      .audit-entry.rack { border-left-color: #48bb78; }\n      .audit-entry.layer { border-left-color: #4299e1; }\n      .audit-entry .time {\n      color: var(--text-soft);\n      font-size: 10px;\n      }\n      .audit-entry .action {\n      color: var(--text-main);\n      font-weight: 500;\n      }\n      .tab-item {\n      display: flex;\n      justify-content: space-between;\n      align-items: center;\n      padding: 12px;\n      background: var(--panel);\n      border: 1px solid var(--edge-main);\n      border-radius: 6px;\n      cursor: pointer;\n      transition: all 0.2s;\n      }\n      .tab-item:hover {\n      background: var(--panel-alt);\n      }\n      .tab-item.active {\n      border-color: var(--accent);\n      background: rgba(79, 209, 197, 0.1);\n      }\n      .tab-item .tab-name {\n      font-weight: 600;\n      color: var(--text-main);\n      flex: 1;\n      }\n      .tab-item .tab-stats {\n      font-size: 11px;\n      color: var(--text-soft);\n      margin-right: 10px;\n      }\n      .tab-actions {\n      display: flex;\n      gap: 6px;\n      }\n      .secret-item {\n      display: flex;\n      justify-content: space-between;\n      align-items: center;\n      padding: 12px;\n      background: var(--panel);\n      border: 1px solid var(--edge-main);\n      border-radius: 6px;\n      border-left: 3px solid var(--danger);\n      }\n      .secret-item.note-type-global { border-left-color: var(--danger); }\n      .secret-item.note-type-node { border-left-color: var(--accent); }\n      .secret-item.note-type-edge { border-left-color: #22c55e; }\n      .secret-item.note-type-rect { border-left-color: #f97316; }\n      .secret-item.note-type-image { border-left-color: #8b5cf6; }\n      .secret-item .secret-name {\n      font-weight: 600;\n      color: var(--text-main);\n      flex: 1;\n      }\n      .secret-item .secret-status {\n      font-size: 11px;\n      color: var(--text-soft);\n      margin-right: 10px;\n      }\n      .secret-item .note-source {\n      font-size: 11px;\n      color: var(--text-soft);\n      background: var(--panel-alt);\n      padding: 2px 8px;\n      border-radius: 4px;\n      margin-right: 10px;\n      cursor: pointer;\n      }\n      .secret-item .note-source:hover {\n      background: var(--accent);\n      color: var(--bg);\n      }\n      .secret-item .note-preview {\n      font-size: 12px;\n      color: var(--text-soft);\n      max-width: 300px;\n      overflow: hidden;\n      text-overflow: ellipsis;\n      white-space: nowrap;\n      margin-right: 10px;\n      }\n\t  body.view-only-mode:not(.view-only-inspect) #node-panel,\n      body.view-only-mode:not(.view-only-inspect) #edge-panel,\n      body.view-only-mode:not(.view-only-inspect) #text-panel,\n      body.view-only-mode:not(.view-only-inspect) #rect-panel {\n        display: none !important;\n      }\n      body.view-only-mode .node-group,\n      body.view-only-mode .edge,\n      body.view-only-mode .rect-group,\n      body.view-only-mode .text-element {\n        cursor: default !important;\n      }\n      body.view-only-mode .resize-handle,\n      body.view-only-mode .edge-control-point {\n        display: none !important;\n      }\n      body.view-only-mode #bulk-toolbar,\n      body.view-only-mode #bulk-toolbar-mobile,\n      body.view-only-mode #bulk-actions-modal {\n        display: none !important;\n      }\n      body.view-only-mode::after {\n        content: \"VIEW ONLY • click 5× to inspect\";\n        position: fixed;\n        bottom: 20px;\n        left: 50%;\n        transform: translateX(-50%);\n        background: rgba(245, 101, 101, 0.9);\n        color: white;\n        padding: 6px 16px;\n        border-radius: 20px;\n        font-size: 12px;\n        font-weight: 600;\n        letter-spacing: 1px;\n        z-index: 9999;\n        pointer-events: none;\n      }\n\n.dropdown {\n  position: relative;\n  display: inline-block;\n}\n.dropdown-btn {\n  padding: 6px 12px;\n  background: var(--btn-bg, var(--panel));\n  color: var(--btn-text, var(--text-main));\n  border: 1px solid var(--edge-main);\n  border-radius: 6px;\n  cursor: pointer;\n  font-size: 14px;\n  font-weight: 600;\n  white-space: nowrap;\n}\n.dropdown-btn:hover {\n  background: var(--accent);\n  color: var(--bg);\n}\n.dropdown-menu {\n  display: none;\n  position: absolute;\n  top: 100%;\n  left: 0;\n  min-width: 180px;\n  background: var(--panel);\n  border: 1px solid var(--edge-main);\n  border-radius: 6px;\n  box-shadow: 0 4px 20px rgba(0,0,0,0.4);\n  z-index: 100000;\n  margin-top: 4px;\n  overflow: hidden;\n}\nheader {\n  z-index: 99999;\n  position: relative;\n}\n.dropdown-menu.open {\n  display: block;\n}\n.dropdown-menu button {\n  display: block;\n  width: 100%;\n  padding: 10px 14px;\n  background: none;\n  border: none;\n  color: var(--text-main);\n  text-align: left;\n  cursor: pointer;\n  font-size: 14px;\n}\n.dropdown-menu button:hover {\n  background: var(--panel-alt);\n  color: var(--accent);\n}\n.dropdown-divider {\n  height: 1px;\n  background: var(--edge-main);\n  margin: 4px 0;\n}\n#mobile-export-btn,\n#mobile-import-btn {\n  display: none;\n}\n@media (max-width: 900px) {\n  .save-row .dropdown {\n    display: none !important;\n  }\n  #mobile-export-btn,\n  #mobile-import-btn {\n    display: inline-block;\n  }\n}\n\n@media print {\n  @page {\n    size: landscape;\n    margin: 0.5cm;\n  }\n  html, body {\n    width: 100%;\n    height: 100%;\n    margin: 0;\n    padding: 0;\n    overflow: visible !important;\n  }\n  body * {\n    visibility: hidden;\n  }\n  #canvas-viewport,\n  #canvas-viewport *,\n  #map,\n  #map * {\n    visibility: visible;\n  }\n  #canvas-viewport {\n    position: absolute !important;\n    left: 0 !important;\n    top: 0 !important;\n    right: 0 !important;\n    bottom: 0 !important;\n    width: 100vw !important;\n    height: 100vh !important;\n    overflow: visible !important;\n  }\n  #map {\n    position: absolute !important;\n    left: 0 !important;\n    top: 0 !important;\n    width: 100% !important;\n    height: 100% !important;\n    background: white !important;\n    background-image: none !important;\n  }\n  #canvas-grid {\n    display: none !important;\n  }\n  main, .topology-panel {\n    display: block !important;\n    position: static !important;\n    overflow: visible !important;\n  }\n\n  #map .node-hit-area,\n  #map .group-indicator,\n  #map .lock-indicator,\n  #map .ping-indicator,\n  #map .fov-group,\n  #map .edge-arrow-forward,\n  #map .edge-arrow-backward {\n    display: none !important;\n  }\n\n  #map .node-circle,\n  #map .node-shape {\n    fill: white !important;\n    stroke: #000 !important;\n    stroke-width: 2px !important;\n  }\n\n  #map .node-circle svg,\n  #map .node-circle svg path,\n  #map .node-circle svg circle,\n  #map .node-circle svg rect,\n  #map .node-circle svg polygon {\n    fill: #000 !important;\n    stroke: none !important;\n  }\n\n  #map text {\n    fill: #000 !important;\n    stroke: none !important;\n  }\n\n  #map .port-label rect {\n    fill: white !important;\n    stroke: #000 !important;\n    stroke-width: 1px !important;\n    stroke-opacity: 1 !important;\n  }\n\n  #map .edge,\n  #map polyline,\n  #map line:not([class*=\"grid\"]) {\n    stroke: #333 !important;\n  }\n  #map .rect-group rect {\n    stroke: #333 !important;\n  }\n  header, .sidebar, .mobile-footer, .minimap-zoom-wrapper,\n  .draw-toolbar, .topology-toolbar, .legend-container,\n  .bulk-toolbar, #bulk-toolbar-mobile, .dropdown-menu,\n  #canvas-hint, .node-panel, .edge-panel, .text-panel, .rect-panel {\n    display: none !important;\n  }\n}\n\nhtml.rtl {\n  direction: rtl;\n}\nhtml.rtl .sidebar { left: auto; right: 0; }\nhtml.rtl .mobile-footer { direction: rtl; }\nhtml.rtl .modal-content { text-align: right; }\nhtml.rtl .modal-buttons { flex-direction: row-reverse; }\nhtml.rtl .style-row { flex-direction: row-reverse; }\nhtml.rtl .style-row label { text-align: right; }\nhtml.rtl input, html.rtl select, html.rtl textarea { text-align: right; }\nhtml.rtl header { flex-direction: row-reverse; }\nhtml.rtl .title-block { text-align: right; }\nhtml.rtl .save-row { flex-direction: row-reverse; }\nhtml.rtl details summary { text-align: right; }\nhtml.rtl .node-panel-content { direction: rtl; }\nhtml.rtl table { direction: rtl; }\nhtml.rtl td:first-child { text-align: right; }\nhtml.rtl .lang-string-row { flex-direction: row-reverse; }\n    </style>\n  </head>\n  <body style=\"background: radial-gradient(circle at center top, rgb(30, 37, 50) 0px, rgb(5, 6, 8) 70%);\" class=\"\">\n    <div class=\"modal\" id=\"language-editor-modal\" style=\"z-index:9999999999\">\n      <div class=\"modal-content\" style=\"max-width:900px;max-height:85vh;display:flex;flex-direction:column;\">\n        <h3 data-lang=\"ui.modals.languageEditor\">Language Editor</h3>\n        <div style=\"display:flex;gap:8px;flex-wrap:wrap;margin-bottom:12px;\" class=\"lang-editor-tabs\">\n          <button class=\"lang-tab active\" data-filter=\"all\" style=\"padding:6px 12px;border:1px solid var(--edge-main);border-radius:4px;background:var(--accent);color:var(--bg);cursor:pointer;font-size:12px;\" data-lang=\"langEditor.tabs.all\">All</button>\n          <button class=\"lang-tab\" data-filter=\"modes\" style=\"padding:6px 12px;border:1px solid var(--edge-main);border-radius:4px;background:var(--panel);color:var(--text-main);cursor:pointer;font-size:12px;\" data-lang=\"langEditor.tabs.modes\">Modes</button>\n          <button class=\"lang-tab\" data-filter=\"modes.network\" style=\"padding:6px 12px;border:1px solid var(--edge-main);border-radius:4px;background:var(--panel);color:var(--text-main);cursor:pointer;font-size:12px;\" data-lang=\"langEditor.tabs.network\">Network</button>\n          <button class=\"lang-tab\" data-filter=\"modes.mindmap\" style=\"padding:6px 12px;border:1px solid var(--edge-main);border-radius:4px;background:var(--panel);color:var(--text-main);cursor:pointer;font-size:12px;\" data-lang=\"langEditor.tabs.mindMap\">Mind Map</button>\n          <button class=\"lang-tab\" data-filter=\"modes.sports\" style=\"padding:6px 12px;border:1px solid var(--edge-main);border-radius:4px;background:var(--panel);color:var(--text-main);cursor:pointer;font-size:12px;\" data-lang=\"langEditor.tabs.sports\">Sports</button>\n          <button class=\"lang-tab\" data-filter=\"modes.smarthome\" style=\"padding:6px 12px;border:1px solid var(--edge-main);border-radius:4px;background:var(--panel);color:var(--text-main);cursor:pointer;font-size:12px;\" data-lang=\"langEditor.tabs.smartHome\">Smart Home</button>\n          <button class=\"lang-tab\" data-filter=\"modes.floorplan\" style=\"padding:6px 12px;border:1px solid var(--edge-main);border-radius:4px;background:var(--panel);color:var(--text-main);cursor:pointer;font-size:12px;\" data-lang=\"langEditor.tabs.floorPlan\">Floor Plan</button>\n          <button class=\"lang-tab\" data-filter=\"ui\" style=\"padding:6px 12px;border:1px solid var(--edge-main);border-radius:4px;background:var(--panel);color:var(--text-main);cursor:pointer;font-size:12px;\" data-lang=\"langEditor.tabs.ui\">UI</button>\n          <button class=\"lang-tab\" data-filter=\"shapes\" style=\"padding:6px 12px;border:1px solid var(--edge-main);border-radius:4px;background:var(--panel);color:var(--text-main);cursor:pointer;font-size:12px;\" data-lang=\"langEditor.tabs.shapes\">Shapes</button>\n          <button class=\"lang-tab\" data-filter=\"messages\" style=\"padding:6px 12px;border:1px solid var(--edge-main);border-radius:4px;background:var(--panel);color:var(--text-main);cursor:pointer;font-size:12px;\" data-lang=\"langEditor.tabs.messages\">Messages</button>\n        </div>\n        <input type=\"text\" id=\"lang-search\" placeholder=\"Search strings...\" style=\"width:100%;padding:8px 12px;margin-bottom:12px;background:var(--panel-alt);color:var(--text-main);border:1px solid var(--edge-main);border-radius:6px;\" data-lang=\"langEditor.searchPlaceholder\" data-lang-attr=\"placeholder\">\n        <div class=\"lang-editor-list\" style=\"flex:1;overflow-y:auto;border:1px solid var(--edge-main);border-radius:6px;background:var(--panel-alt);\"></div>\n        <div class=\"modal-buttons\" style=\"margin-top:12px;\">\n          <button class=\"btn-cancel\" onclick=\"closeLanguageEditor()\" data-lang=\"ui.buttons.cancel\">Cancel</button>\n          <button class=\"btn-save\" onclick=\"saveLangEdits()\" data-lang=\"ui.buttons.saveChanges\">Save Changes</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"icon-picker-modal\" id=\"icon-picker-modal\">\n      <div class=\"icon-picker-content\">\n        <div class=\"icon-picker-header\">\n          <h3 data-lang=\"ui.modals.selectIcon\">Select Icon</h3>\n          <div class=\"icon-picker-tabs\">\n            <button class=\"icon-picker-tab\" data-library=\"mdi\" data-lang=\"iconPicker.mdi\">MDI</button>\n            <button class=\"icon-picker-tab\" data-library=\"simple\" data-lang=\"iconPicker.simpleIcons\">Simple Icons</button>\n            <button class=\"icon-picker-tab active\" data-library=\"selfhst\" data-lang=\"iconPicker.selfhst\">selfh.st/icons</button>\n          </div>\n          <input type=\"text\" class=\"icon-picker-search\" id=\"icon-search\" data-lang=\"ui.placeholders.searchIcons\" data-lang-attr=\"placeholder\" placeholder=\"Search icons...\" style=\"display: none;\">\n        </div>\n        <div class=\"icon-picker-body\" id=\"icon-picker-body\">\n        </div>\n        <div class=\"icon-picker-footer\">\n          <button class=\"icon-btn-cancel\" id=\"icon-picker-cancel\" data-lang=\"ui.buttons.cancel\">Cancel</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"tabs-modal\">\n      <div class=\"modal-content\" style=\"max-width: 600px;\">\n        <p style=\"font-size: 13px; color: var(--text-soft); margin-bottom: 15px;\" data-lang=\"messages.manageTopologies\">Manage multiple topologies</p>\n        <div id=\"tabs-list\" style=\"display: flex; flex-direction: column; gap: 8px; max-height: 400px; overflow-y: auto; margin-bottom: 15px;\">\n            <div class=\"tab-item active\" onclick=\"switchTab(0)\">\n              <div class=\"tab-name\">Main Topology</div>\n              <div class=\"tab-stats\">0 nodes • 0 connections</div>\n              <div class=\"tab-actions\">\n                <button class=\"btn-cancel\" onclick=\"event.stopPropagation(); renameTab(0)\" title=\"Rename tab\">✏️</button>\n                \n              </div>\n            </div>\n          </div>\n        <div style=\"display: flex; gap: 8px; margin-bottom: 10px;\">\n          <input type=\"text\" id=\"new-tab-name\" data-lang=\"ui.placeholders.newTabName\" data-lang-attr=\"placeholder\" placeholder=\"New tab name...\" style=\"flex: 1; padding: 8px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 6px; color: var(--text-main);\">\n          <button class=\"btn-save\" onclick=\"createNewTab()\" data-lang=\"ui.buttons.addTab\">+ Add Tab</button>\n        </div>\n        <div class=\"modal-buttons\">\n          <button class=\"btn-cancel\" id=\"tabs-modal-close\" data-lang=\"ui.buttons.close\">Close</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"rollback-modal\">\n      <div class=\"modal-content\" style=\"max-width: 700px;\">\n        <h3 data-lang=\"ui.modals.versionHistory\">Version History</h3>\n        <p style=\"font-size: 13px; color: var(--text-soft); margin-bottom: 15px;\" data-lang=\"messages.snapshotLimit\">Limit: Snapshots</p>\n        <div id=\"rollback-list\" style=\"display: flex; flex-direction: column; gap: 8px; max-height: 450px; overflow-y: auto; margin-bottom: 15px;\"></div>\n        <div style=\"display: flex; gap: 8px; margin-bottom: 10px;\">\n          <button class=\"btn-cancel\" onclick=\"clearRollbackHistory()\" data-lang=\"ui.buttons.clearHistory\">Clear History</button>\n          <button class=\"btn-save\" onclick=\"createManualSnapshot()\" data-lang=\"ui.buttons.createSnapshot\">Create Snapshot</button>\n        </div>\n        <div class=\"modal-buttons\">\n          <button class=\"btn-cancel\" id=\"rollback-modal-close\" data-lang=\"ui.buttons.close\">Close</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"audit-log-modal\">\n      <div class=\"modal-content\" style=\"max-width: 800px;\">\n        <h3 data-lang=\"ui.modals.auditLog\">Audit Log</h3>\n        <p style=\"font-size: 13px; color: var(--text-soft); margin-bottom: 15px;\" data-lang=\"messages.trackChanges\">Track all changes made to your topology</p>\n        <div style=\"display: flex; gap: 8px; margin-bottom: 10px;\">\n          <select id=\"audit-filter\" style=\"padding: 8px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 6px; color: var(--text-main);\">\n            <option value=\"all\" data-lang=\"ui.options.allEvents\">All Events</option>\n            <option value=\"node\" data-lang=\"ui.options.nodeOperations\">Node Operations</option>\n            <option value=\"connection\" data-lang=\"ui.sections.connections\">Connections</option>\n            <option value=\"style\" data-lang=\"ui.options.styleChanges\">Style Changes</option>\n            <option value=\"rack\" data-lang=\"ui.options.rackOperations\">Rack Operations</option>\n            <option value=\"layer\" data-lang=\"ui.options.layerChanges\">Layer Changes</option>\n          </select>\n          <button class=\"btn-save\" onclick=\"exportAuditLog()\" data-lang=\"ui.buttons.export\">Export</button>\n        </div>\n        <div id=\"audit-log-list\" style=\"display: flex; flex-direction: column; gap: 4px; max-height: 450px; overflow-y: auto; margin-bottom: 15px; font-family: monospace; font-size: 12px;\"></div>\n        <div style=\"display: flex; gap: 8px;\">\n          <button class=\"btn-cancel\" onclick=\"clearAuditLog()\" data-lang=\"ui.buttons.clearLog\">Clear Log</button>\n          <button class=\"btn-cancel\" id=\"audit-log-modal-close\" data-lang=\"ui.buttons.close\">Close</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"port-map-modal\">\n      <div class=\"modal-content\" style=\"max-width: 900px;\">\n        <h3 data-lang=\"ui.modals.portMap\">Port Map</h3>\n        <p style=\"font-size: 13px; color: var(--text-soft); margin-bottom: 15px;\" data-lang=\"messages.portMapDesc\">All connections with port assignments</p>\n        <div style=\"display: flex; gap: 8px; margin-bottom: 10px;\">\n          <input type=\"text\" id=\"port-map-search\" data-lang=\"ui.placeholders.searchDevices\" data-lang-attr=\"placeholder\" placeholder=\"Search devices or ports...\" style=\"flex: 1; min-width: 200px; padding: 8px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 6px; color: var(--text-main);\">\n          <select id=\"port-map-filter\" style=\"padding: 8px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 6px; color: var(--text-main);\">\n            <option value=\"all\" data-lang=\"ui.options.allConnections\">All Connections</option>\n            <option value=\"with-ports\" data-lang=\"ui.options.withPortsOnly\">With Ports Only</option>\n            <option value=\"without-ports\" data-lang=\"ui.options.missingPorts\">Missing Ports</option>\n          </select>\n        </div>\n        <div id=\"port-map-table\" style=\"max-height: 450px; overflow-y: auto; margin-bottom: 15px;\"><div style=\"text-align: center; padding: 40px; color: var(--text-soft);\" data-lang=\"messages.noConnections\">No connections found</div></div>\n        <div style=\"display: flex; gap: 8px;\">\n          <button class=\"btn-save\" onclick=\"exportPortMap()\" data-lang=\"ui.buttons.csv\">Export CSV</button>\n          <button class=\"btn-cancel\" id=\"port-map-modal-close\" data-lang=\"ui.buttons.close\">Close</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"secrets-modal\">\n      <div class=\"modal-content\" style=\"max-width: 700px;\">\n        <h3 data-lang=\"ui.modals.notesHub\">Notes Hub</h3>\n        <div style=\"display: flex; gap: 8px; margin-bottom: 12px;\">\n          <input type=\"text\" id=\"notes-hub-search\" placeholder=\"Search notes...\" style=\"flex: 1; min-width: 0; padding: 8px 12px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 6px; color: var(--text-main); font-size: 14px;\">\n          <select id=\"notes-hub-filter\" style=\"flex-shrink: 0; width: auto; padding: 8px 12px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 6px; color: var(--text-main); font-size: 14px;\">\n            <option value=\"all\">All Notes</option>\n            <option value=\"global\">Global Notes</option>\n            <option value=\"node\">Node Notes</option>\n            <option value=\"edge\">Line Notes</option>\n            <option value=\"rect\">Zone Notes</option>\n            <option value=\"image\">Image Notes</option>\n          </select>\n        </div>\n        <div id=\"secrets-list\" style=\"display: flex; flex-direction: column; gap: 8px; max-height: 400px; overflow-y: auto; margin-bottom: 15px;\"><div style=\"text-align: center; padding: 40px; color: var(--text-soft);\" data-lang=\"notes.noNotes\">No notes yet</div></div>\n        <div style=\"border-top: 1px solid var(--edge-main); padding-top: 12px; margin-top: 8px;\">\n          <p style=\"font-size: 12px; color: var(--text-soft); margin-bottom: 10px;\" data-lang=\"messages.notesEncryptionNote\">Global notes can be stored with AES 256 encryption</p>\n          <div style=\"display: flex; gap: 8px;\">\n            <input type=\"text\" id=\"new-secret-name\" placeholder=\"New global note name...\" style=\"flex: 1; padding: 8px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 6px; color: var(--text-main);\" data-lang=\"notes.namePlaceholder\" data-lang-attr=\"placeholder\">\n            <button class=\"btn-save\" onclick=\"createNewSecret()\" data-lang=\"ui.buttons.addNote\">+ Add Note</button>\n          </div>\n        </div>\n        <div class=\"modal-buttons\">\n          <button class=\"btn-cancel\" id=\"secrets-modal-close\" data-lang=\"ui.buttons.close\">Close</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"secret-editor-modal\">\n      <div class=\"modal-content\" style=\"max-width: 600px;\">\n        <h3 id=\"secret-editor-title\" data-lang=\"ui.modals.editNote\">Edit Note</h3>\n        <div class=\"note-editor-toolbar\" id=\"secret-editor-toolbar\">\n          <button type=\"button\" data-command=\"bold\" title=\"Bold\"><b>B</b></button>\n          <button type=\"button\" data-command=\"italic\" title=\"Italic\"><i>I</i></button>\n          <button type=\"button\" data-command=\"underline\" title=\"Underline\"><u>U</u></button>\n          <span class=\"toolbar-separator\"></span>\n          <select id=\"secret-heading-select\" title=\"Heading\">\n            <option value=\"\">Normal</option>\n            <option value=\"h1\">H1</option>\n            <option value=\"h2\">H2</option>\n            <option value=\"h3\">H3</option>\n          </select>\n          <span class=\"toolbar-separator\"></span>\n          <button type=\"button\" data-command=\"insertUnorderedList\" title=\"Bullet List\">•</button>\n          <button type=\"button\" data-command=\"insertOrderedList\" title=\"Numbered List\">1.</button>\n          <span class=\"toolbar-separator\"></span>\n          <button type=\"button\" data-command=\"code\" title=\"Inline Code\">&lt;/&gt;</button>\n          <button type=\"button\" data-command=\"codeblock\" title=\"Code Block\">[ ]</button>\n          <button type=\"button\" data-command=\"createLink\" title=\"Link\">🔗</button>\n          <button type=\"button\" data-command=\"blockquote\" title=\"Quote\">\"</button>\n          <span class=\"toolbar-separator\"></span>\n          <button type=\"button\" data-command=\"removeFormat\" title=\"Clear Formatting\">✖</button>\n        </div>\n        <div class=\"note-editor-content\" id=\"secret-editor-content\" contenteditable=\"true\" style=\"height: 200px;\"></div>\n        <div style=\"margin: 15px 0; display: flex; align-items: center; gap: 8px;\">\n          <label class=\"toggle-switch\"><input type=\"checkbox\" id=\"secret-auto-encrypt\" checked><span class=\"toggle-slider\"></span></label>\n          <span data-lang=\"ui.labels.encryptNote\">Encrypt Note</span>\n        </div>\n        <div class=\"modal-buttons\">\n          <button class=\"btn-cancel\" onclick=\"closeSecretEditor()\" data-lang=\"ui.buttons.cancel\">Cancel</button>\n          <button class=\"btn-save\" onclick=\"saveSecret()\" data-lang=\"ui.buttons.save\">Save</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"mobile-export-modal\">\n      <div class=\"modal-content\" style=\"max-width: 320px;\">\n        <h3 style=\"margin: 0 0 20px 0;\" data-lang=\"ui.modals.export\">Export</h3>\n        <div style=\"display: flex; flex-direction: column; gap: 12px;\">\n          <button onclick=\"screenshotCanvas(); document.getElementById('mobile-export-modal').classList.remove('active');\" style=\"padding: 14px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; color: var(--text-main); font-size: 16px; cursor: pointer;\" data-lang=\"ui.buttons.pngImage\">PNG Image</button>\n          <button onclick=\"exportCanvasSVG(); document.getElementById('mobile-export-modal').classList.remove('active');\" style=\"padding: 14px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; color: var(--text-main); font-size: 16px; cursor: pointer;\" data-lang=\"ui.buttons.svgVector\">SVG Vector</button>\n          <button onclick=\"exportJSONFile(); document.getElementById('mobile-export-modal').classList.remove('active');\" style=\"padding: 14px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; color: var(--text-main); font-size: 16px; cursor: pointer;\" data-lang=\"ui.buttons.json\">JSON</button>\n          <button onclick=\"exportMarkdown(); document.getElementById('mobile-export-modal').classList.remove('active');\" style=\"padding: 14px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; color: var(--text-main); font-size: 16px; cursor: pointer;\" data-lang=\"ui.buttons.markdown\">Markdown</button>\n          <button onclick=\"exportCSV(); document.getElementById('mobile-export-modal').classList.remove('active');\" style=\"padding: 14px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; color: var(--text-main); font-size: 16px; cursor: pointer;\" data-lang=\"ui.buttons.csv\">CSV</button>\n          <button onclick=\"printTopology(); document.getElementById('mobile-export-modal').classList.remove('active');\" style=\"padding: 14px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; color: var(--text-main); font-size: 16px; cursor: pointer;\" data-lang=\"ui.buttons.printBW\">Print B&W</button>\n          <button onclick=\"printTopologyColor(); document.getElementById('mobile-export-modal').classList.remove('active');\" style=\"padding: 14px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; color: var(--text-main); font-size: 16px; cursor: pointer;\" data-lang=\"mobileExport.printColorFull\">Print Full Color</button>\n          <button onclick=\"printTopologyColor('#ffffff'); document.getElementById('mobile-export-modal').classList.remove('active');\" style=\"padding: 14px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; color: var(--text-main); font-size: 16px; cursor: pointer;\" data-lang=\"mobileExport.printColorWhite\">Print White BG</button>\n          <button onclick=\"printTopologyColor('#000000'); document.getElementById('mobile-export-modal').classList.remove('active');\" style=\"padding: 14px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; color: var(--text-main); font-size: 16px; cursor: pointer;\" data-lang=\"mobileExport.printColorBlack\">Print Black BG</button>\n\t\t  <button onclick=\"showFormatHelp(); document.getElementById('mobile-import-modal').classList.remove('active');\" style=\"padding: 14px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; color: var(--text-main); font-size: 16px; cursor: pointer;\" data-lang=\"ui.buttons.importHelp\">Import Help</button>\n        </div>\n        <button onclick=\"document.getElementById('mobile-export-modal').classList.remove('active');\" style=\"margin-top: 20px; padding: 12px; background: var(--danger); border: none; border-radius: 8px; color: white; font-size: 16px; cursor: pointer; width: 100%;\" data-lang=\"ui.buttons.cancel\">Cancel</button>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"mobile-import-modal\">\n      <div class=\"modal-content\" style=\"max-width: 320px;\">\n        <h3 style=\"margin: 0 0 20px 0;\" data-lang=\"ui.modals.import\">Import</h3>\n        <div style=\"display: flex; flex-direction: column; gap: 12px;\">\n          <button onclick=\"document.getElementById('import-html-file').click(); document.getElementById('mobile-import-modal').classList.remove('active');\" style=\"padding: 14px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; color: var(--text-main); font-size: 16px; cursor: pointer;\" data-lang=\"ui.buttons.html\">HTML</button>\n          <button onclick=\"document.getElementById('import-json-file').click(); document.getElementById('mobile-import-modal').classList.remove('active');\" style=\"padding: 14px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; color: var(--text-main); font-size: 16px; cursor: pointer;\" data-lang=\"ui.buttons.json\">JSON</button>\n          <button onclick=\"document.getElementById('import-markdown-file').click(); document.getElementById('mobile-import-modal').classList.remove('active');\" style=\"padding: 14px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; color: var(--text-main); font-size: 16px; cursor: pointer;\" data-lang=\"ui.buttons.markdown\">Markdown</button>\n          <button onclick=\"document.getElementById('import-csv-file').click(); document.getElementById('mobile-import-modal').classList.remove('active');\" style=\"padding: 14px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; color: var(--text-main); font-size: 16px; cursor: pointer;\" data-lang=\"ui.buttons.csv\">CSV</button>\n          <button onclick=\"showFormatHelp(); document.getElementById('mobile-import-modal').classList.remove('active');\" style=\"padding: 14px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; color: var(--text-main); font-size: 16px; cursor: pointer;\" data-lang=\"ui.buttons.exportHelp\">Export Help</button>\n        </div>\n        <button onclick=\"document.getElementById('mobile-import-modal').classList.remove('active');\" style=\"margin-top: 20px; padding: 12px; background: var(--danger); border: none; border-radius: 8px; color: white; font-size: 16px; cursor: pointer; width: 100%;\" data-lang=\"ui.buttons.cancel\">Cancel</button>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"edit-modal\">\n      <div class=\"modal-content\">\n        <h3 id=\"modal-title\" data-lang=\"ui.modals.editName\">Edit Name</h3>\n        <input type=\"text\" id=\"modal-input\">\n        <div class=\"modal-buttons\">\n          <button class=\"btn-cancel\" id=\"modal-cancel\" data-lang=\"ui.buttons.cancel\">Cancel</button>\n          <button class=\"btn-save\" id=\"modal-save\" data-lang=\"ui.buttons.save\">Save</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"note-editor-modal\">\n      <div class=\"modal-content\">\n        <h3 id=\"note-editor-title\" data-lang=\"noteEditor.title\">Edit Note</h3>\n        <div class=\"note-editor-toolbar\">\n          <button type=\"button\" data-command=\"bold\" title=\"Bold (Ctrl+B)\"><b>B</b></button>\n          <button type=\"button\" data-command=\"italic\" title=\"Italic (Ctrl+I)\"><i>I</i></button>\n          <button type=\"button\" data-command=\"underline\" title=\"Underline (Ctrl+U)\"><u>U</u></button>\n          <div class=\"toolbar-separator\"></div>\n          <select id=\"note-heading-select\" title=\"Heading\">\n            <option value=\"\">Normal</option>\n            <option value=\"h1\">H1</option>\n            <option value=\"h2\">H2</option>\n            <option value=\"h3\">H3</option>\n          </select>\n          <div class=\"toolbar-separator\"></div>\n          <button type=\"button\" data-command=\"insertUnorderedList\" title=\"Bullet List\">&#8226;</button>\n          <button type=\"button\" data-command=\"insertOrderedList\" title=\"Numbered List\">1.</button>\n          <div class=\"toolbar-separator\"></div>\n          <button type=\"button\" data-command=\"code\" title=\"Inline Code\">&lt;/&gt;</button>\n          <button type=\"button\" data-command=\"codeblock\" title=\"Code Block\">[ ]</button>\n          <div class=\"toolbar-separator\"></div>\n          <button type=\"button\" data-command=\"createLink\" title=\"Insert Link\">&#128279;</button>\n          <button type=\"button\" data-command=\"blockquote\" title=\"Quote\">&#8220;</button>\n          <div class=\"toolbar-separator\"></div>\n          <button type=\"button\" data-command=\"removeFormat\" title=\"Clear Formatting\">&#10006;</button>\n        </div>\n        <div class=\"note-editor-content\" id=\"note-editor-content\" contenteditable=\"true\"></div>\n        <div class=\"modal-buttons\">\n          <button class=\"btn-cancel\" id=\"note-editor-cancel\" data-lang=\"ui.buttons.cancel\">Cancel</button>\n          <button class=\"btn-save\" id=\"note-editor-save\" data-lang=\"ui.buttons.save\">Save</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal confirm-modal\" id=\"confirm-modal\">\n      <div class=\"modal-content\">\n        <h3 data-lang=\"ui.modals.confirm\">Confirm</h3>\n        <p id=\"confirm-message\" data-lang=\"messages.confirmDelete\"> Are you sure you want to delete this line? </p>\n        <div class=\"modal-buttons\">\n          <button class=\"btn-cancel\" id=\"confirm-cancel\" data-lang=\"ui.buttons.cancel\">Cancel</button>\n          <button class=\"btn-delete\" id=\"confirm-delete\" data-lang=\"ui.buttons.delete\">Delete</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"save-info-modal\">\n      <div class=\"modal-content\" style=\"max-width: 700px; max-height: 80vh; overflow-y: auto;\">\n        <h3 style=\"margin-bottom: 16px;\" data-lang=\"help.title\">Help</h3>\n        <div style=\"display: flex; gap: 8px; margin-bottom: 16px; flex-wrap: wrap;\">\n          <button class=\"help-tab active\" data-tab=\"general\" style=\"padding: 8px 16px; border: 1px solid var(--edge-main); background: var(--accent); color: var(--bg); border-radius: 6px; cursor: pointer; font-weight: 600;\" data-lang=\"help.tabs.general\">General</button>\n          <button class=\"help-tab\" data-tab=\"desktop\" style=\"padding: 8px 16px; border: 1px solid var(--edge-main); background: var(--panel); color: var(--text-main); border-radius: 6px; cursor: pointer; font-weight: 600;\" data-lang=\"help.tabs.desktop\">Desktop</button>\n          <button class=\"help-tab\" data-tab=\"mobile\" style=\"padding: 8px 16px; border: 1px solid var(--edge-main); background: var(--panel); color: var(--text-main); border-radius: 6px; cursor: pointer; font-weight: 600;\" data-lang=\"help.tabs.mobile\">Mobile</button>\n\t\t  <button class=\"help-tab\" data-tab=\"formats\" style=\"padding: 8px 16px; border: 1px solid var(--edge-main); background: var(--panel); color: var(--text-main); border-radius: 6px; cursor: pointer; font-weight: 600;\" data-lang=\"help.tabs.formats\">Import/Export</button>\n          <button class=\"help-tab\" data-tab=\"recording\" style=\"padding: 8px 16px; border: 1px solid var(--edge-main); background: var(--panel); color: var(--text-main); border-radius: 6px; cursor: pointer; font-weight: 600;\" data-lang=\"help.tabs.recording\">Recording</button>\n        </div>\n        <div id=\"help-tab-general\" class=\"help-tab-content\" style=\"display: block;\">\n          <ul style=\"margin-left: 20px; margin-bottom: 12px; line-height: 1.8;\">\n            <li><strong data-lang=\"help.general.addNodes\">Add Nodes:</strong> <span data-lang=\"help.general.addNodesDesc\">Click \"+ Node\" or \"+ Rack\" in the top menu</span></li>\n            <li><strong data-lang=\"help.general.connectNodes\">Connect Nodes:</strong> <span data-lang=\"help.general.connectNodesDesc\">Select a node, then use \"Add Connection\" in the panel</span></li>\n            <li><strong data-lang=\"help.general.moveNodes\">Move Nodes:</strong> <span data-lang=\"help.general.moveNodesDesc\">Drag nodes to reposition them</span></li>\n            <li><strong data-lang=\"help.general.enterRackView\">Enter Rack View:</strong> <span data-lang=\"help.general.enterRackViewDesc\">Double click on desktop or Long press on mobile</span></li>\n            <li><strong data-lang=\"help.general.multiSelect\">Multi Select:</strong> <span data-lang=\"help.general.multiSelectDesc\">Right click (desktop) or double tap (mobile)</span></li>\n            <li><strong data-lang=\"help.general.panCanvas\">Pan Canvas:</strong> <span data-lang=\"help.general.panCanvasDesc\">Drag empty space or hold Space + drag</span></li>\n            <li><strong data-lang=\"help.general.zoom\">Zoom:</strong> <span data-lang=\"help.general.zoomDesc\">Scroll wheel or pinch gesture</span></li>\n            <li><strong data-lang=\"help.general.freeDraw\">Free Draw:</strong> <span data-lang=\"help.general.freeDrawDesc\">Use draw toolbar for drawing lines, boxes/zones, and text</span></li>\n          </ul>\n          <h4 style=\"color: var(--accent); margin-bottom: 8px;\" data-lang=\"help.general.savingEncryption\">Saving &amp; Encryption</h4>\n          <p style=\"margin-bottom: 8px;\" data-lang=\"help.general.savingNote\">Browsers cannot overwrite local files. Click Save File to download an updated HTML with all changes. Replace your old file to keep edits. An automatic backup is also saved to your browser's local storage and will prompt for recovery if you close without saving.</p>\n          <p style=\"margin-bottom: 8px;\"><strong data-lang=\"help.general.encryptionNote\">Encryption of data:</strong> <span data-lang=\"help.general.encryptionDesc\">Check \"Encrypt\" before saving to password protect your data. Beware! No recovery possible without password!!</span></p>\n          <p><strong data-lang=\"help.general.decryptionNote\">Decryption of data:</strong> <span data-lang=\"help.general.decryptionDesc\">Export via JSON in top settings menu after successful password validation to decrypt your data. You can then reimport back into the same file and save!</span></p>\n        </div>\n        <div id=\"help-tab-desktop\" class=\"help-tab-content\" style=\"display: none;\">\n          <h4 style=\"color: var(--accent); margin-bottom: 12px;\" data-lang=\"help.desktop.navigation\">Navigation</h4>\n          <table style=\"width: 100%; border-collapse: collapse; margin-bottom: 16px; font-size: 14px;\">\n            <tbody><tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\" data-lang=\"help.desktop.arrowKeys\">Arrow Keys</code></td>\n              <td style=\"padding: 8px;\" data-lang=\"help.desktop.moveSelected1px\">Move selected 1px</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\" data-lang=\"help.desktop.shiftArrows\">Shift + Arrows</code></td>\n              <td style=\"padding: 8px;\" data-lang=\"help.desktop.moveSelected10px\">Move selected 10px</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\" data-lang=\"help.desktop.tabShiftTab\">Tab / Shift+Tab</code></td>\n              <td style=\"padding: 8px;\" data-lang=\"help.desktop.cycleNodes\">Cycle through nodes</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\" data-lang=\"help.desktop.fKey\">F</code></td>\n              <td style=\"padding: 8px;\" data-lang=\"help.desktop.focusSelected\">Focus on selected</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\" data-lang=\"help.desktop.spaceDrag\">Space + Drag</code></td>\n              <td style=\"padding: 8px;\" data-lang=\"help.desktop.panCanvas\">Pan canvas</td>\n            </tr>\n          </tbody></table>\n          <h4 style=\"color: var(--accent); margin-bottom: 12px;\" data-lang=\"help.desktop.management\">Management</h4>\n          <table style=\"width: 100%; border-collapse: collapse; margin-bottom: 16px; font-size: 14px;\">\n            <tbody><tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\" data-lang=\"help.desktop.lKey\">L</code></td>\n              <td style=\"padding: 8px;\" data-lang=\"help.desktop.lockUnlock\">Lock/unlock selected</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\" data-lang=\"help.desktop.gKey\">G</code></td>\n              <td style=\"padding: 8px;\" data-lang=\"help.desktop.groupUngroup\">Group/ungroup selected</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\" data-lang=\"help.desktop.ctrlCV\">Ctrl+C / Ctrl+V</code></td>\n              <td style=\"padding: 8px;\" data-lang=\"help.desktop.copyPaste\">Copy / Paste</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\" data-lang=\"help.desktop.ctrlD\">Ctrl+D</code></td>\n              <td style=\"padding: 8px;\" data-lang=\"help.desktop.duplicate\">Duplicate</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\" data-lang=\"help.desktop.ctrlA\">Ctrl+A</code></td>\n              <td style=\"padding: 8px;\" data-lang=\"help.desktop.selectAll\">Select all</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\" data-lang=\"help.desktop.deleteKey\">Delete</code></td>\n              <td style=\"padding: 8px;\" data-lang=\"help.desktop.deleteSelected\">Delete selected</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\" data-lang=\"help.desktop.escapeKey\">Escape</code></td>\n              <td style=\"padding: 8px;\" data-lang=\"help.desktop.clearSelection\">Clear selection</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\" data-lang=\"help.desktop.ctrlZY\">Ctrl+Z / Ctrl+Y</code></td>\n              <td style=\"padding: 8px;\" data-lang=\"help.desktop.undoRedo\">Undo / Redo</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\" data-lang=\"help.desktop.rightClick\">Right Click</code></td>\n              <td style=\"padding: 8px;\" data-lang=\"help.desktop.multiSelectToggle\">Multi select toggle</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\" data-lang=\"help.desktop.dblClickRack\">Double click rack</code></td>\n              <td style=\"padding: 8px;\" data-lang=\"help.desktop.enterRackView\">Enter rack view</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\" data-lang=\"help.desktop.dblClickConnection\">Double click connection</code></td>\n              <td style=\"padding: 8px;\" data-lang=\"help.desktop.addWaypoint\">Add waypoint</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\" data-lang=\"help.desktop.dblClickEmpty\">Double click empty (in rack)</code></td>\n              <td style=\"padding: 8px;\" data-lang=\"help.desktop.exitRackView\">Exit rack view</td>\n            </tr>\n          </tbody></table>\n          <h4 style=\"color: var(--accent); margin-bottom: 12px;\" data-lang=\"help.desktop.recording\">Recording</h4>\n          <table style=\"width: 100%; border-collapse: collapse; margin-bottom: 16px; font-size: 14px;\">\n            <tbody><tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\" data-lang=\"help.desktop.rKey\">R</code></td>\n              <td style=\"padding: 8px;\" data-lang=\"help.desktop.startStopRecording\">Start/stop real time recording</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\" data-lang=\"help.desktop.shiftR\">Shift+R</code></td>\n              <td style=\"padding: 8px;\" data-lang=\"help.desktop.startStopStepRecording\">Start/stop step by step recording</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\" data-lang=\"help.desktop.spaceKey\">Space</code></td>\n              <td style=\"padding: 8px;\" data-lang=\"help.desktop.addStepOrPlayPause\">Add step (step recording) / Play/Pause (playback)</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><code style=\"background: var(--panel-alt); padding: 2px 6px; border-radius: 4px;\" data-lang=\"help.desktop.pKey\">P</code></td>\n              <td style=\"padding: 8px;\" data-lang=\"help.desktop.playRecording\">Play recording</td>\n            </tr>\n          </tbody></table>\n        </div>\n        <div id=\"help-tab-mobile\" class=\"help-tab-content\" style=\"display: none;\">\n          <h4 style=\"color: var(--accent); margin-bottom: 12px;\" data-lang=\"help.mobile.basicGestures\">Basic Gestures</h4>\n          <table style=\"width: 100%; border-collapse: collapse; margin-bottom: 16px; font-size: 14px;\">\n            <tbody><tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><strong data-lang=\"help.mobile.tapNode\">Tap node</strong></td>\n              <td style=\"padding: 8px;\" data-lang=\"help.mobile.selectOpenProperties\">Select &amp; open properties</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><strong data-lang=\"help.mobile.tapEmpty\">Tap empty</strong></td>\n              <td style=\"padding: 8px;\" data-lang=\"help.mobile.deselectAll\">Deselect all</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><strong data-lang=\"help.mobile.dragNode\">Drag node</strong></td>\n              <td style=\"padding: 8px;\" data-lang=\"help.mobile.moveNode\">Move node</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><strong data-lang=\"help.mobile.dragEmpty\">Drag empty</strong></td>\n              <td style=\"padding: 8px;\" data-lang=\"help.mobile.panCanvas\">Pan canvas</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><strong data-lang=\"help.mobile.pinch\">Pinch</strong></td>\n              <td style=\"padding: 8px;\" data-lang=\"help.mobile.zoomInOut\">Zoom in/out</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><strong data-lang=\"help.mobile.doubleTapNode\">Double tap node</strong></td>\n              <td style=\"padding: 8px;\" data-lang=\"help.mobile.addRemoveSelection\">Add/remove from selection</td>\n            </tr>\t\t\n          </tbody></table>\n          <h4 style=\"color: var(--accent); margin-bottom: 12px;\" data-lang=\"help.mobile.rackView\">Rack View</h4>\n          <table style=\"width: 100%; border-collapse: collapse; margin-bottom: 16px; font-size: 14px;\">\n            <tbody><tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><strong data-lang=\"help.mobile.longPressRack\">Long press rack</strong></td>\n              <td style=\"padding: 8px;\" data-lang=\"help.mobile.enterRackView\">Enter rack view</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><strong data-lang=\"help.mobile.longPressConnection\">Long press connection</strong></td>\n              <td style=\"padding: 8px;\" data-lang=\"help.mobile.addWaypoint\">Add waypoint</td>\n            </tr>\n            <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n              <td style=\"padding: 8px;\"><strong data-lang=\"help.mobile.doubleTapEmpty\">Double tap empty</strong></td>\n              <td style=\"padding: 8px;\" data-lang=\"help.mobile.exitRackView\">Exit rack view</td>\n            </tr>\n          </tbody></table>\n        </div>\n\t\t<div id=\"help-tab-formats\" class=\"help-tab-content\" style=\"display: none;\">\n          <h4 style=\"color: var(--accent); margin-bottom: 12px;\" data-lang=\"help.formats.exportHelp\">Export Help</h4>\n          <table style=\"width: 100%; border-collapse: collapse; margin-bottom: 16px; font-size: 14px;\">\n            <tbody>\n              <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n                <td style=\"padding: 8px;\"><strong data-lang=\"help.formats.html\">HTML</strong></td>\n                <td style=\"padding: 8px;\" data-lang=\"help.formats.htmlDesc\">Complete self contained file and the default save method. When in doubt, choose this. Use the Save HTML button for this</td>\n              </tr>\n              <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n                <td style=\"padding: 8px;\"><strong data-lang=\"help.formats.json\">JSON</strong></td>\n                <td style=\"padding: 8px;\" data-lang=\"help.formats.jsonExportDesc\">Full backup. Editable in your favorite text editor</td>\n              </tr>\n              <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n                <td style=\"padding: 8px;\"><strong data-lang=\"help.formats.markdown\">Markdown</strong></td>\n                <td style=\"padding: 8px;\" data-lang=\"help.formats.markdownExportDesc\">Full backup. Editable in your favorite text editor</td>\n              </tr>\n              <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n                <td style=\"padding: 8px;\"><strong data-lang=\"help.formats.csv\">CSV</strong></td>\n                <td style=\"padding: 8px;\" data-lang=\"help.formats.csvExportDesc\">Full backup in header, nodes in spreadsheet rows</td>\n              </tr>\n              <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n                <td style=\"padding: 8px;\"><strong data-lang=\"help.formats.png\">PNG</strong></td>\n                <td style=\"padding: 8px;\" data-lang=\"help.formats.pngDesc\">Standard image of current canvas tab</td>\n              </tr>\n              <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n                <td style=\"padding: 8px;\"><strong data-lang=\"help.formats.svg\">SVG</strong></td>\n                <td style=\"padding: 8px;\" data-lang=\"help.formats.svgDesc\">Vector image, scalable and editable image of current canvas tab</td>\n              </tr>\n            </tbody>\n          </table>\n          <h4 style=\"color: var(--accent); margin-bottom: 12px;\" data-lang=\"help.formats.importHelp\">Import Help</h4>\n          <table style=\"width: 100%; border-collapse: collapse; margin-bottom: 16px; font-size: 14px;\">\n            <tbody>\n              <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n                <td style=\"padding: 8px;\"><strong data-lang=\"help.formats.json\">JSON</strong></td>\n                <td style=\"padding: 8px;\" data-lang=\"help.formats.jsonImportDesc\">Replaces all data</td>\n              </tr>\n              <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n                <td style=\"padding: 8px;\"><strong data-lang=\"help.formats.markdown\">Markdown</strong></td>\n                <td style=\"padding: 8px;\" data-lang=\"help.formats.markdownImportDesc\">Replaces all data</td>\n              </tr>\n              <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n                <td style=\"padding: 8px;\"><strong data-lang=\"help.formats.csv\">CSV</strong></td>\n                <td style=\"padding: 8px;\" data-lang=\"help.formats.csvImportDesc\">Choose full restore or add nodes only. Allows quickly creating nodes with a spreadsheet and importing into an existing save.</td>\n              </tr>\n              <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n                <td style=\"padding: 8px;\"><strong data-lang=\"help.formats.html\">HTML</strong></td>\n                <td style=\"padding: 8px;\" data-lang=\"help.formats.htmlImportDesc\">Import data from another saved HTML file. Extracts and restores all topology data from a previously exported file.</td>\n              </tr>\n            </tbody>\n          </table>\n        </div>\n        <div id=\"help-tab-recording\" class=\"help-tab-content\" style=\"display: none;\">\n          <h4 style=\"color: var(--accent); margin-bottom: 12px;\" data-lang=\"help.recording.title\">Canvas Recording</h4>\n          <p style=\"margin-bottom: 12px;\" data-lang=\"help.recording.overview\">Record your canvas activity to create visual documentation, tutorials, or demonstrations of your network topology changes.</p>\n          <h4 style=\"color: var(--accent); margin-bottom: 8px; margin-top: 16px;\" data-lang=\"help.recording.twoModes\">Two Recording Modes</h4>\n          <p style=\"margin-bottom: 8px;\"><strong style=\"color: #f56565;\" data-lang=\"help.recording.realtimeTitle\">Real time Recording</strong> <span data-lang=\"help.recording.realtimeDesc\">(red ● button) captures the canvas continuously at 10 frames per second as you work. Good for demonstrating workflows.</span></p>\n          <p style=\"margin-bottom: 12px;\"><strong style=\"color: #48bb78;\" data-lang=\"help.recording.stepByStepTitle\">Step by Step Recording</strong> <span data-lang=\"help.recording.stepByStepDesc\">(green ●+ button) lets you manually capture each frame. Click the green button to start, then press + or Space to capture each step. Each frame plays for 1 second. Good for tutorials where you want precise control over what's shown.</span></p>\n          <ul style=\"margin-left: 20px; margin-bottom: 12px; line-height: 1.8;\">\n            <li data-lang=\"help.recording.savedInFile\">Recordings are saved within your data file for later playback</li>\n            <li data-lang=\"help.recording.exportVideo\">Can be exported as a video file (WebM format) for sharing</li>\n            <li data-lang=\"help.recording.playWhileRecording\">Pressing Play while recording will stop and immediately play the new recording</li>\n          </ul>\n          <h4 style=\"color: var(--accent); margin-bottom: 8px; margin-top: 16px;\" data-lang=\"help.recording.controls\">Controls</h4>\n          <table style=\"width: 100%; border-collapse: collapse; margin-bottom: 16px; font-size: 14px;\">\n            <tbody>\n              <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n                <td style=\"padding: 8px;\"><strong style=\"color: #f56565;\">●</strong> <span data-lang=\"help.recording.recordBtn\">Record</span></td>\n                <td style=\"padding: 8px;\" data-lang=\"help.recording.recordBtnDesc\">Start real time recording (captures 10 frames/sec). Press R</td>\n              </tr>\n              <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n                <td style=\"padding: 8px;\"><strong style=\"color: #48bb78;\">●+</strong> <span data-lang=\"help.recording.stepRecordBtn\">Step Record</span></td>\n                <td style=\"padding: 8px;\" data-lang=\"help.recording.stepRecordBtnDesc\">Start step by step recording. Press Shift+R</td>\n              </tr>\n              <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n                <td style=\"padding: 8px;\"><strong style=\"color: #48bb78;\">+</strong> <span data-lang=\"help.recording.addStepBtn\">Add Step</span></td>\n                <td style=\"padding: 8px;\" data-lang=\"help.recording.addStepBtnDesc\">Capture current state as next frame (during step recording). Press Space</td>\n              </tr>\n              <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n                <td style=\"padding: 8px;\"><strong>▶</strong> <span data-lang=\"help.recording.playBtn\">Play</span></td>\n                <td style=\"padding: 8px;\" data-lang=\"help.recording.playBtnDesc\">Play back a saved recording. Press P</td>\n              </tr>\n              <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n                <td style=\"padding: 8px;\"><strong>■</strong> <span data-lang=\"help.recording.stopBtn\">Stop</span></td>\n                <td style=\"padding: 8px;\" data-lang=\"help.recording.stopBtnDesc\">Stop recording or playback. Press R or Shift+R again</td>\n              </tr>\n              <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n                <td style=\"padding: 8px;\"><strong>❚❚</strong> <span data-lang=\"help.recording.pauseBtn\">Pause</span></td>\n                <td style=\"padding: 8px;\" data-lang=\"help.recording.pauseBtnDesc\">Pause playback. Press Space</td>\n              </tr>\n              <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n                <td style=\"padding: 8px;\"><strong data-lang=\"help.recording.scrubber\">Scrubber</strong></td>\n                <td style=\"padding: 8px;\" data-lang=\"help.recording.scrubberDesc\">Seek to any point in the recording</td>\n              </tr>\n              <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n                <td style=\"padding: 8px;\"><strong data-lang=\"help.recording.speed\">Speed</strong></td>\n                <td style=\"padding: 8px;\" data-lang=\"help.recording.speedDesc\">Adjust playback speed (0.5x, 1x, 2x, 4x)</td>\n              </tr>\n              <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n                <td style=\"padding: 8px;\"><strong data-lang=\"help.recording.loop\">Loop</strong></td>\n                <td style=\"padding: 8px;\" data-lang=\"help.recording.loopDesc\">Toggle continuous loop playback</td>\n              </tr>\n              <tr style=\"border-bottom: 1px solid var(--edge-main);\">\n                <td style=\"padding: 8px;\"><strong>...</strong> <span data-lang=\"help.recording.manage\">Manage</span></td>\n                <td style=\"padding: 8px;\" data-lang=\"help.recording.manageDesc\">View, rename, export, or delete saved recordings</td>\n              </tr>\n            </tbody>\n          </table>\n        </div>\n        <div class=\"modal-buttons\" style=\"margin-top: 16px;\">\n          <button class=\"btn-cancel\" id=\"save-info-close\" data-lang=\"ui.buttons.close\">Close</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"dialog-modal\">\n      <div class=\"modal-content\">\n        <h3 id=\"dialog-modal-title\">Dialog</h3>\n        <p id=\"dialog-modal-message\" style=\"margin: 16px 0; line-height: 1.6; white-space: pre-wrap;\"></p>\n        <input type=\"text\" id=\"dialog-modal-input\" style=\"display: none; width: 100%; padding: 10px; margin-bottom: 16px; background: var(--input-bg); color: var(--input-text); border: 1px solid var(--input-border); border-radius: 6px; font-size: 14px;\">\n        <div class=\"modal-buttons\" id=\"dialog-modal-buttons\">\n          <button class=\"btn-cancel\" id=\"dialog-modal-cancel\" data-lang=\"ui.buttons.cancel\">Cancel</button>\n          <button class=\"btn-save\" id=\"dialog-modal-ok\" data-lang=\"dialogs.ok\">OK</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"settings-modal\">\n      <div class=\"modal-content\">\n        <h2 data-lang=\"ui.modals.settings\">Settings</h2>\n        <details class=\"style-section\" open>\n          <summary data-lang=\"ui.sections.mappingModeTheme\">Mapping Mode & Theme</summary>\n          <div class=\"style-content\">\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.mode\">Mode</label>\n              <select id=\"mapping-mode-select\">\n                <option value=\"network\" data-lang=\"modes.network.name\">Network Topology</option>\n                <option value=\"mindmap\" data-lang=\"modes.mindmap.name\">Mind Map</option>\n                <option value=\"sports\" data-lang=\"modes.sports.name\">Sports Playbook</option>\n                <option value=\"smarthome\" data-lang=\"modes.smarthome.name\">Smart Home</option>\n                <option value=\"floorplan\" data-lang=\"modes.floorplan.name\">Floor Plan / Blueprint</option>\n              </select>\n            </div>\n            <div class=\"style-row\" id=\"canvas-style-row\">\n              <label data-lang=\"ui.labels.canvasStyle\">Canvas Style</label>\n              <select id=\"canvas-style-select\">\n                <option value=\"grid\" data-lang=\"canvas.grid\">Standard Grid</option>\n                <option value=\"dots\" data-lang=\"canvas.dots\">Dot Grid</option>\n                <option value=\"blueprint\" data-lang=\"canvas.blueprint\">Blueprint Grid</option>\n                <option value=\"none\" data-lang=\"canvas.none\">No Grid</option>\n              </select>\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.themePreset\">Theme Preset</label>\n              <div style=\"display:flex;gap:6px;align-items:center;\">\n                <select id=\"theme-preset\" style=\"flex:1;\">\n                  <option value=\"defaulted\" data-lang=\"themes.default\">Default</option>\n                  <option value=\"\" data-lang=\"ui.options.custom\">Custom</option>\n                  <optgroup label=\"Corporate\" data-lang-label=\"themeGroups.corporate\">\n                    <option value=\"slate\" data-lang=\"themes.slate\">Slate</option>\n                    <option value=\"graphite\" data-lang=\"themes.graphite\">Graphite</option>\n                    <option value=\"frost\" data-lang=\"themes.frost\">Frost (Light)</option>\n                  </optgroup>\n                  <optgroup label=\"Homelab\" data-lang-label=\"themeGroups.homelab\">\n                    <option value=\"synthwave\" data-lang=\"themes.synthwave\">Synthwave</option>\n                    <option value=\"terminal\" data-lang=\"themes.terminal\">Terminal</option>\n                  </optgroup>\n                  <optgroup label=\"Dev\" data-lang-label=\"themeGroups.dev\">\n                    <option value=\"dracula\" data-lang=\"themes.dracula\">Dracula</option>\n                    <option value=\"cobalt\" data-lang=\"themes.cobalt\">Cobalt</option>\n                    <option value=\"solarized\" data-lang=\"themes.solarized\">Solarized</option>\n                  </optgroup>\n                  <optgroup label=\"Mind Map\" data-lang-label=\"themeGroups.mindmap\">\n                    <option value=\"brainwave\" data-lang=\"themes.brainwave\">Brainwave</option>\n                                        <option value=\"neonMint\" data-lang=\"themes.neonMint\">Neon Mint</option>\n                    <option value=\"velvetDusk\" data-lang=\"themes.velvetDusk\">Velvet Dusk</option>\n                    <option value=\"sunburst\" data-lang=\"themes.sunburst\">Sunburst</option>\n                  </optgroup>\n                  <optgroup id=\"my-themes-group\" label=\"My Themes\" data-lang-label=\"themeGroups.myThemes\"></optgroup>\n                </select>\n                <button onclick=\"saveCurrentTheme()\" data-lang=\"ui.tooltips.saveTheme\" data-lang-attr=\"title\" title=\"Save current theme\" style=\"padding:4px 8px;background:var(--accent);color:var(--bg);border:none;border-radius:4px;cursor:pointer;font-size:12px;\" data-lang=\"ui.buttons.save\">Save</button>\n                <button id=\"delete-theme-btn\" onclick=\"deleteCurrentTheme()\" data-lang=\"ui.tooltips.deleteTheme\" data-lang-attr=\"title\" title=\"Delete theme\" style=\"padding:4px 8px;background:var(--danger);color:#fff;border:none;border-radius:4px;cursor:pointer;font-size:12px;\" data-lang=\"ui.buttons.del\">Del</button>\n              </div>\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.mainBackground\">Main Background</label>\n              <input type=\"color\" id=\"panel-color\" value=\"#0b0e13\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.accentColor\">Accent Color</label>\n              <input type=\"color\" id=\"accent-color\" value=\"#4fd1c5\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.primaryText\">Primary Text</label>\n              <input type=\"color\" id=\"text-main-color\" value=\"#e2e8f0\">\n            </div>\n          </div>\n        </details>\n        <details class=\"style-section\">\n          <summary data-lang=\"ui.sections.viewOnlyMode\">View Only Mode</summary>\n          <div class=\"style-content\">\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.viewOnly\">View Only (disable editing)</label>\n              <label class=\"toggle-switch\"><input type=\"checkbox\" id=\"view-only-mode\"><span class=\"toggle-slider\"></span></label>\n            </div>\n            <p style=\"margin-top:4px;font-size:12px;color:var(--text-soft);\" data-lang=\"messages.viewOnlyNote\">When enabled, all editing of the canvas is disabled. Pan and zoom still work.</p>\n          </div>\n        </details>\n        <details class=\"style-section\">\n          <summary data-lang=\"ui.sections.moreThemeOptions\">More Theme Options</summary>\n          <div class=\"style-content\">\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.sidebar\">Sidebar / Mobile Footer</label>\n              <input type=\"color\" id=\"sidebar-bg-color\" value=\"#10141b\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.modalBg\">Modal Window Background</label>\n              <input type=\"color\" id=\"panel-alt-color\" value=\"#10141b\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.dangerButtons\">Danger Buttons</label>\n              <input type=\"color\" id=\"danger-color\" value=\"#f56565\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.secondaryText\">Secondary Text</label>\n              <input type=\"color\" id=\"text-soft-color\" value=\"#94a3b8\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.tagFill\">Tag(s) Fill</label>\n              <input type=\"color\" id=\"tag-fill-color\" value=\"#1e293b\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.tagText\">Tag(s) Text</label>\n              <input type=\"color\" id=\"tag-text-color\" value=\"#e2e8f0\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.tagBorder\">Tag(s) Border</label>\n              <input type=\"color\" id=\"tag-border-color\" value=\"#475569\">\n            </div>\n          </div>\n        </details>\n        <details class=\"style-section\">\n          <summary data-lang=\"ui.sections.topBar\">Top Bar</summary>\n          <div class=\"style-content\">\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.background\">Background</label>\n              <input type=\"color\" id=\"topbar-bg-color\" value=\"#0b0e13\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.border\">Border</label>\n              <input type=\"color\" id=\"topbar-border-color\" value=\"#1f2533\">\n            </div>\n          </div>\n\t\t  <div class=\"style-content\">\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.buttonFill\">Button Fill</label>\n              <input type=\"color\" id=\"btn-bg-color\" value=\"#0b0e13\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.buttonText\">Button Text</label>\n              <input type=\"color\" id=\"btn-text-color\" value=\"#e2e8f0\">\n            </div>\n          </div>\n        </details>\n        <details class=\"style-section\">\n          <summary data-lang=\"ui.sections.mainCanvas\">Main Canvas</summary>\n          <div class=\"style-content\">\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.border\">Border</label>\n              <input type=\"color\" id=\"canvas-border-color\" value=\"#475569\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.gridLines\">Grid Lines</label>\n              <input type=\"color\" id=\"canvas-grid-color\" value=\"#475569\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.gridSize\">Grid Size</label>\n              <input type=\"number\" id=\"canvas-grid-size\" value=\"50\" min=\"20\" max=\"200\" style=\"width:60px;\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.showGrid\">Show Grid</label>\n              <label class=\"toggle-switch\"><input type=\"checkbox\" id=\"canvas-grid-enabled\" checked><span class=\"toggle-slider\"></span></label>\n            </div>\n\t\t    <div class=\"style-row\">\n              <label data-lang=\"ui.labels.bgTop\">Background Top</label>\n              <input type=\"color\" id=\"canvas-gradient-top\" value=\"#1e2532\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.bgBottom\">Background Bottom</label>\n              <input type=\"color\" id=\"canvas-gradient-bottom\" value=\"#050608\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.solidBg\">Solid Background</label>\n              <input type=\"color\" id=\"page-bg-color\" value=\"#050608\">\n            </div>\n            <p style=\"margin-top:4px;font-size:12px;color:var(--text-soft);\" data-lang=\"messages.setSolidBgNote\">Set solid background to override gradient.</p>\n          </div>\n        </details>\n        <details class=\"style-section\">\n          <summary data-lang=\"ui.sections.rackCanvas\">Rack Canvas</summary>\n          <div class=\"style-content\">\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.frameFill\">Frame Fill</label>\n              <input type=\"color\" id=\"rack-frame-fill\" value=\"#0f172a\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.frameBorder\">Frame Border</label>\n              <input type=\"color\" id=\"rack-frame-stroke\" value=\"#4fd1c5\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.gridLines\">Grid Lines</label>\n              <input type=\"color\" id=\"rack-line-color\" value=\"#475569\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.showGrid\">Show Grid</label>\n              <label class=\"toggle-switch\"><input type=\"checkbox\" id=\"rack-grid-enabled\" checked><span class=\"toggle-slider\"></span></label>\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.uLabels\">U Labels</label>\n              <input type=\"color\" id=\"rack-text-color\" value=\"#4fd1c5\">\n            </div>\n          </div>\n       </details>\n\t   <details class=\"style-section\">\n          <summary data-lang=\"ui.sections.canvasToolbars\">Canvas Toolbars</summary>\n          <div class=\"style-content\">\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.background\">Background</label>\n              <input type=\"color\" id=\"toolbar-bg-color\" value=\"#0f172a\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.border\">Border</label>\n              <input type=\"color\" id=\"toolbar-border-color\" value=\"#1f2937\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.text\">Text</label>\n              <input type=\"color\" id=\"toolbar-text-color\" value=\"#94a3b8\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.buttonFill\">Button Fill</label>\n              <input type=\"color\" id=\"toolbar-btn-bg-color\" value=\"#0b0e13\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.buttonText\">Button Text</label>\n              <input type=\"color\" id=\"toolbar-btn-text-color\" value=\"#e2e8f0\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.minimapDots\">Minimap Dots</label>\n              <input type=\"color\" id=\"minimap-dots-color\" value=\"#94a3b8\">\n            </div>\n          </div>\n        </details>\n        <details class=\"style-section\">\n          <summary data-lang=\"ui.sections.nodes\">Nodes</summary>\n          <div class=\"style-content\">\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.fill\">Fill</label>\n              <input type=\"color\" id=\"node-fill-color\" value=\"#1e293b\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.border\">Border</label>\n              <input type=\"color\" id=\"node-stroke-color\" value=\"#475569\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.titleColor\">Title Color</label>\n              <input type=\"color\" id=\"node-title-color\" value=\"#e2e8f0\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.titleSize\">Title Size</label>\n              <input type=\"number\" id=\"node-title-size\" value=\"18\" min=\"8\" max=\"48\" style=\"width:60px;\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.subtitleColor\">Subtitle Color</label>\n              <input type=\"color\" id=\"node-sub-color\" value=\"#94a3b8\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.subtitleSize\">Subtitle Size</label>\n              <input type=\"number\" id=\"node-sub-size\" value=\"13\" min=\"8\" max=\"48\" style=\"width:60px;\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.font\">Font</label>\n              <select id=\"node-font-family\" style=\"flex:1;padding:4px;background:var(--panel);color:var(--text-main);border:1px solid var(--topbar-border);border-radius:4px;\">\n                <option value=\"Inter, system-ui, sans-serif\" data-lang=\"ui.fonts.inter\">Inter</option>\n                <option value=\"Arial, sans-serif\" data-lang=\"ui.fonts.arial\">Arial</option>\n                <option value=\"Helvetica, sans-serif\" data-lang=\"ui.fonts.helvetica\">Helvetica</option>\n                <option value=\"Georgia, serif\" data-lang=\"ui.fonts.georgia\">Georgia</option>\n                <option value=\"monospace\" data-lang=\"ui.fonts.monospace\">Monospace</option>\n              </select>\n            </div>\n          </div>\n        </details>\n        <details class=\"style-section\">\n          <summary data-lang=\"ui.sections.connections\">Connections</summary>\n          <div class=\"style-content\">\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.defaultColor\">Default Color</label>\n              <input type=\"color\" id=\"default-edge-color\" value=\"#475569\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.defaultRouting\">Default Routing</label>\n              <select id=\"default-edge-routing\">\n                <option value=\"orthogonal\" data-lang=\"ui.options.orthogonal\">Orthogonal (90°)</option>\n                <option value=\"curved\" data-lang=\"ui.options.curved\">Curved</option>\n                <option value=\"straight\" data-lang=\"ui.options.straight\">Straight</option>\n              </select>\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.animationStyle\">Animation Style</label>\n              <select id=\"animation-style-select\">\n                <option value=\"arrows\" data-lang=\"ui.options.flowingArrows\">Flowing Arrows</option>\n                <option value=\"dots\" data-lang=\"ui.options.dotArrows\">Dot Arrows</option>\n              </select>\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.animateDirections\">Animate Directions</label>\n              <select id=\"animation-direction-select\">\n                <option value=\"all\" data-lang=\"ui.options.allDirections\">All Directions</option>\n                <option value=\"forward\" data-lang=\"ui.options.forwardOnly\">Forward Only</option>\n                <option value=\"backward\" data-lang=\"ui.options.backwardOnly\">Backward Only</option>\n                <option value=\"both\" data-lang=\"ui.options.bidirectionalOnly\">Bidirectional Only</option>\n              </select>\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.animationSpeed\">Animation Speed</label>\n              <select id=\"animation-speed-select\">\n                <option value=\"0.5\" data-lang=\"ui.options.veryFast\">Very Fast</option>\n                <option value=\"1\">Fast</option>\n                <option value=\"1.5\">Normal</option>\n                <option value=\"2.5\">Slow</option>\n                <option value=\"4\">Very Slow</option>\n              </select>\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.showPortLabels\">Show Port Labels</label>\n              <label class=\"toggle-switch\"><input type=\"checkbox\" id=\"show-port-labels-toggle\" checked><span class=\"toggle-slider\"></span></label>\n            </div>\n            <div class=\"style-row\" style=\"grid-column: 1 / -1;\">\n              <button id=\"apply-routing-all\" style=\"width:100%;padding:8px;background:var(--accent);color:var(--bg);border:none;border-radius:6px;cursor:pointer;font-weight:600;\" data-lang=\"ui.buttons.applyRoutingAll\">Apply Routing to All Connections</button>\n            </div>\n          </div>\n        </details>\n\t\t<details class=\"style-section\">\n          <summary data-lang=\"ui.sections.animationsZones\">Animations &amp; Zones</summary>\n          <div class=\"style-content\">\n            <div class=\"style-row\"><label data-lang=\"ui.labels.allAnimations\">All Animations</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"anim-master\" checked><span class=\"toggle-slider\"></span></label></div>\n            <div style=\"grid-column:1/-1;font-size:11px;color:var(--text-soft);text-transform:uppercase;border-bottom:1px solid var(--edge-main);padding-bottom:4px;margin-top:8px;\" data-lang=\"ui.sections.byType\">By Type</div>\n            <div class=\"style-row\"><label data-lang=\"ui.labels.sweepPan\">Sweep (Pan)</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"anim-type-sweep\" checked><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label data-lang=\"ui.labels.pulseBreathe\">Pulse (Breathe)</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"anim-type-pulse\" checked><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label data-lang=\"ui.labels.ringsEmanate\">Rings (Emanate)</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"anim-type-rings\" checked><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label data-lang=\"ui.labels.spinRotate\">Spin (Rotate)</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"anim-type-spin\" checked><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label data-lang=\"ui.labels.connectionsFlow\">Connections (Flow)</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"anim-type-connections\" checked><span class=\"toggle-slider\"></span></label></div>\n            <div style=\"grid-column:1/-1;font-size:11px;color:var(--text-soft);text-transform:uppercase;border-bottom:1px solid var(--edge-main);padding-bottom:4px;margin-top:8px;\" data-lang=\"ui.sections.byCategory\">By Category</div>\n            <div class=\"style-row\"><label data-lang=\"ui.labels.cameras\">Cameras</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"anim-cat-camera\" checked><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label data-lang=\"ui.labels.doorbells\">Doorbells</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"anim-cat-doorbell\" checked><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label data-lang=\"ui.labels.motionSensors\">Motion Sensors</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"anim-cat-motion\" checked><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label data-lang=\"ui.labels.smokeDetectors\">Smoke Detectors</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"anim-cat-smoke\" checked><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label data-lang=\"ui.labels.wifiApRouter\">WiFi/AP/Router</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"anim-cat-wifi\" checked><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label data-lang=\"ui.labels.sensorsIot\">Sensors/IoT</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"anim-cat-sensor\" checked><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label data-lang=\"ui.labels.sprinklers\">Sprinklers</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"anim-cat-sprinkler\" checked><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label data-lang=\"ui.sections.connections\">Connections</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"anim-cat-connections\" checked><span class=\"toggle-slider\"></span></label></div>\n            <div style=\"grid-column:1/-1;font-size:12px;color:var(--accent);text-transform:uppercase;letter-spacing:0.05em;font-weight:600;margin-top:16px;padding-top:12px;border-top:1px solid var(--edge-main);\" data-lang=\"ui.sections.zoneSettings\">Zone (Animation Cone) Settings</div>\n            <div class=\"style-row\"><label data-lang=\"ui.labels.allZones\">All Zones</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"zone-master\" checked><span class=\"toggle-slider\"></span></label></div>\n            <div style=\"grid-column:1/-1;font-size:11px;color:var(--text-soft);text-transform:uppercase;border-bottom:1px solid var(--edge-main);padding-bottom:4px;margin-top:8px;\" data-lang=\"ui.sections.byCategory\">By Category</div>\n            <div class=\"style-row\"><label data-lang=\"ui.labels.cameras\">Cameras</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"zone-cat-camera\" checked><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label data-lang=\"ui.labels.doorbells\">Doorbells</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"zone-cat-doorbell\" checked><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label data-lang=\"ui.labels.motionSensors\">Motion Sensors</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"zone-cat-motion\" checked><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label data-lang=\"ui.labels.smokeDetectors\">Smoke Detectors</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"zone-cat-smoke\" checked><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label data-lang=\"ui.labels.wifiApRouter\">WiFi/AP/Router</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"zone-cat-wifi\" checked><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label data-lang=\"ui.labels.sensorsIot\">Sensors/IoT</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"zone-cat-sensor\" checked><span class=\"toggle-slider\"></span></label></div>\n            <div class=\"style-row\"><label data-lang=\"ui.labels.sprinklers\">Sprinklers</label><label class=\"toggle-switch\"><input type=\"checkbox\" id=\"zone-cat-sprinkler\" checked><span class=\"toggle-slider\"></span></label></div>\n          </div>\n        </details>\n        <details class=\"style-section\">\n          <summary data-lang=\"ui.sections.groupsEditing\">Groups &amp; Editing</summary>\n          <div class=\"style-content\">\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.resizeHandleColor\">Resize Handle Color</label>\n              <input type=\"color\" id=\"selection-handle-color\" value=\"#f59e0b\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.resizeHandleSize\">Resize Handle Size</label>\n              <input type=\"number\" id=\"selection-handle-size\" value=\"8\" min=\"4\" max=\"16\" style=\"width:60px;\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.groupedIconOutline\">Grouped Icon Outline</label>\n              <input type=\"color\" id=\"group-indicator-color\" value=\"#4fd1c5\">\n            </div>\n          </div>\n\t\t  <div class=\"style-content\">\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.multiselectFill\">Multiselect Fill Color (Desktop)</label>\n              <input type=\"color\" id=\"selection-fill-color\" value=\"#4fd1c5\">\n            </div>\n           <div class=\"style-row\">\n              <label data-lang=\"ui.labels.multiselectFillOpacity\">Multiselect Fill Opacity (Desktop)</label>\n              <div style=\"display:flex;align-items:center;gap:8px;\">\n                <input type=\"range\" id=\"selection-fill-opacity\" min=\"0\" max=\"1\" step=\"0.05\" value=\"0.1\">\n                <span id=\"selection-fill-opacity-val\" style=\"min-width:35px;text-align:right;\">10%</span>\n              </div>\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.multiselectBorder\">Multiselect Border Color (Desktop)</label>\n              <input type=\"color\" id=\"selection-stroke-color\" value=\"#4fd1c5\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.multiselectBorderWidth\">Multiselect Border Width (Desktop)</label>\n              <input type=\"number\" id=\"selection-stroke-width\" min=\"1\" max=\"10\" value=\"2\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.multiselectBorderStyle\">Multiselect Border Style (Desktop)</label>\n              <select id=\"selection-stroke-style\">\n                <option value=\"5,5\" data-lang=\"ui.options.dashed\">Dashed</option>\n                <option value=\"2,4\" data-lang=\"ui.options.dotted\">Dotted</option>\n                <option value=\"none\" data-lang=\"ui.options.solid\">Solid</option>\n              </select>\n            </div>\n          </div>\n        </details>\n\t\t<details class=\"style-section\">\n          <summary data-lang=\"ui.sections.inputsDropdowns\">Inputs &amp; Dropdowns</summary>\n          <div class=\"style-content\">\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.background\">Background</label>\n              <input type=\"color\" id=\"input-bg-color\" value=\"#0b0e13\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.text\">Text</label>\n              <input type=\"color\" id=\"input-text-color\" value=\"#e2e8f0\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.border\">Border</label>\n              <input type=\"color\" id=\"input-border-color\" value=\"#1f2937\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.font\">Font</label>\n              <select id=\"input-font-family\" style=\"flex:1;padding:4px;background:var(--panel);color:var(--text-main);border:1px solid var(--topbar-border);border-radius:4px;\">\n                <option value=\"Inter, system-ui, sans-serif\" data-lang=\"ui.fonts.inter\">Inter</option>\n                <option value=\"Arial, sans-serif\" data-lang=\"ui.fonts.arial\">Arial</option>\n                <option value=\"Helvetica, sans-serif\" data-lang=\"ui.fonts.helvetica\">Helvetica</option>\n                <option value=\"Georgia, serif\" data-lang=\"ui.fonts.georgia\">Georgia</option>\n                <option value=\"monospace\" data-lang=\"ui.fonts.monospace\">Monospace</option>\n              </select>\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.fontSize\">Font Size</label>\n              <input type=\"number\" id=\"input-font-size\" value=\"14\" min=\"10\" max=\"24\" style=\"width:60px;\">\n            </div>\n          </div>\n        </details>\n        <details class=\"style-section\">\n          <summary data-lang=\"ui.sections.welcomeMessage\">Welcome Message</summary>\n          <div class=\"style-content\">\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.enabled\">Enabled</label>\n              <label class=\"toggle-switch\"><input type=\"checkbox\" id=\"canvas-hint-enabled\" checked><span class=\"toggle-slider\"></span></label>\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.background\">Background</label>\n              <input type=\"color\" id=\"canvas-hint-bg-color\" value=\"#0f172a\">\n            </div>\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.textColor\">Text Color</label>\n              <input type=\"color\" id=\"canvas-hint-text-color\" value=\"#94a3b8\">\n            </div>\n            <div class=\"style-row\" style=\"flex-direction:column;align-items:stretch;\">\n              <label data-lang=\"ui.labels.customTextHtml\">Custom Text (HTML)</label>\n              <textarea id=\"canvas-hint-text\" rows=\"4\" style=\"width:100%;margin-top:4px;padding:6px;background:var(--panel);color:var(--text-main);border:1px solid var(--topbar-border);border-radius:4px;font-size:12px;resize:vertical;\" placeholder=\"Leave empty for default\" data-lang=\"ui.placeholders.leaveEmpty\" data-lang-attr=\"placeholder\"></textarea>\n            </div>\n          </div>\n        </details>\n         <details class=\"style-section\">\n          <summary data-lang=\"ui.sections.autoStatusChecking\">Auto Status Checking</summary>\n          <div class=\"style-content\">\n            <div style=\"display: flex; align-items: center; gap: 8px; margin-bottom: 16px;\">\n            <label class=\"toggle-switch\"><input type=\"checkbox\" id=\"auto-ping-enabled\"><span class=\"toggle-slider\"></span></label>\n            <span style=\"font-size: 14px; font-weight: 600;\" data-lang=\"ui.labels.enableAutoStatusChecking\">Enable automatic status checking</span>\n            </div>\n            <div id=\"auto-ping-settings\" style=\"display: none; padding-left: 20px; border-left: 2px solid var(--edge-main);\">\n              <div class=\"style-row\" style=\"margin-bottom: 12px;\">\n                <label data-lang=\"ui.labels.checkInterval\">Check Interval (seconds):</label>\n                <input type=\"number\" id=\"auto-ping-interval\" min=\"5\" max=\"3600\" value=\"30\" style=\"width: 80px; padding: 6px; background: var(--panel); border: 1px solid var(--edge-main); border-radius: 4px; color: var(--text-main);\">\n              </div>\n              <div style=\"padding: 10px; background: var(--panel); border-radius: 6px; border: 1px solid var(--edge-main); font-size: 12px; color: var(--text-soft);\">\n                <div style=\"margin-bottom: 4px;\"><span id=\"auto-ping-next-check\" data-lang=\"ui.labels.nextCheckIn\">Next check in: --</span></div>\n                <div><span id=\"auto-ping-last-run\" data-lang=\"ui.labels.lastRun\">Last run: 1:15:07 PM</span></div>\n              </div>\n            </div>\n            <p style=\"margin-top: 12px; font-size: 12px; color: var(--text-soft); font-style: italic;\" data-lang=\"messages.autoStatusCheckingNote\">\n              Automatically checks all ping enabled nodes at the specified interval. I recommend 30 to 60 seconds for local networks.\n            </p>\n\t\t\t</div>\n        </details>\n        <details class=\"style-section\">\n          <summary data-lang=\"ui.labels.language\">Language</summary>\n          <div class=\"style-content\">\n            <div class=\"style-row\">\n              <label data-lang=\"ui.labels.currentLanguage\">Current:</label>\n              <span id=\"current-lang-name\" style=\"color:var(--accent);font-weight:600;\">English</span>\n            </div>\n            <div class=\"style-row\" style=\"flex-direction:column;align-items:stretch;gap:8px;\">\n\t\t    <div class=\"style-row\">\n\t\t\t\t<button onclick=\"openLanguageEditor()\" style=\"width:100%;padding:6px 12px;background:var(--accent);color:var(--bg);border:none;border-radius:6px;cursor:pointer;font-size:13px;font-weight:600;\" data-lang=\"language.editStrings\">Edit Strings</button>\n            </div>\n            <div class=\"style-row\">\n           <button onclick=\"exportLanguageFile()\" style=\"width:100%;padding:6px 12px;background:var(--panel-alt);border:1px solid var(--edge-main);border-radius:6px;color:var(--text-main);cursor:pointer;font-size:13px;\" data-lang=\"language.exportLang\">Export Language File</button>\n            </div>\n\t\t\t    <div class=\"style-row\">\n\t\t\t\t\t<button onclick=\"document.getElementById('import-lang-file').click()\" style=\"width:100%;padding:6px 12px;background:var(--panel-alt);border:1px solid var(--edge-main);border-radius:6px;color:var(--text-main);cursor:pointer;font-size:13px;\" data-lang=\"language.importLang\">Import Language File</button>\n\t\t\t\t\t<input type=\"file\" id=\"import-lang-file\" accept=\".json\" style=\"display:none\" onchange=\"if(this.files[0]) importLanguageFile(this.files[0])\">\n\t\t\t\t<input type=\"file\" id=\"import-lang-file\" accept=\".json\" style=\"display:none\">\n            </div>\n            <div class=\"style-row\">\n        <button onclick=\"resetToDefaultLanguage()\" style=\"width:100%;padding:6px 12px;background:var(--danger);color:var(--text-main);border:none;border-radius:6px;cursor:pointer;font-size:13px;\" data-lang=\"language.resetLang\">Reset to English</button>\n            </div>\t\t          \n            </div>\n          </div>\n        </details>\t\t\t\n        <details class=\"style-section\" open=\"\">\n          <summary data-lang=\"ui.sections.dangerZone\">Danger Zone</summary>\n          <div class=\"style-content\">\n            <p style=\"margin-bottom:12px;font-size:13px;color:var(--text-soft);\" data-lang=\"messages.dangerZoneNote\">Permanently delete everything on the canvas.</p>\n            <button id=\"clear-all-btn\" data-lang=\"tooltips.clearAllNodes\" data-lang-attr=\"title\" title=\"Clear all nodes\" style=\"padding:6px 12px;background:var(--danger);color:var(--text-main);border:none;border-radius:6px;cursor:pointer;font-size:14px;font-weight:600;\" data-lang-text=\"ui.buttons.clearAll\">Clear All</button>\n            <input type=\"file\" id=\"import-data-file\" accept=\".json\" style=\"display:none\">\n          </div>\n        </details>\n        <div style=\"text-align:center;padding:10px 0 2px;font-size:11px;color:var(--text-soft);opacity:0.6;\">The One File: Networkening v<span id=\"version-display\"></span></div>\n        <div class=\"modal-buttons\">\n          <button class=\"btn-cancel\" id=\"settings-close\" data-lang=\"ui.buttons.close\">Close</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"add-node-modal\">\n      <div class=\"modal-content\">\n        <h3 data-lang=\"ui.modals.addNode\">Add New Node</h3>\n        <div style=\"background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; padding: 15px; margin-bottom: 15px;\">\n          <div style=\"font-weight: 600; color: var(--text-main); margin-bottom: 12px; font-size: 14px;\" data-lang=\"ui.sections.basicInfo\">Basic Information</div>\n          <label style=\"display: block;margin-bottom: 4px;color: var(--text-soft);font-size: 13px;\" data-lang=\"ui.labels.name\">Name</label>\n          <input type=\"text\" id=\"new-node-name\" placeholder=\"e.g. web server, jellyfin\">\n          <label id=\"new-node-ip-label\" style=\"display: block;margin-bottom: 4px;margin-top: 12px;color: var(--text-soft);font-size: 13px;\" data-lang=\"ui.labels.ip\">IP / Subtitle</label>\n          <input type=\"text\" id=\"new-node-ip\" placeholder=\"e.g. 192.168.1.100\" data-lang=\"ui.placeholders.nodeIp\" data-lang-attr=\"placeholder\">\n        </div>\n        <div style=\"background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; padding: 15px; margin-bottom: 15px;\">\n          <div style=\"font-weight: 600; color: var(--text-main); margin-bottom: 12px; font-size: 14px;\" data-lang=\"ui.sections.tags\">Tags</div>\n          <label id=\"new-node-tags-label\" style=\"display: block;margin-bottom: 4px;color: var(--text-soft);font-size: 13px;\" data-lang=\"ui.labels.tagsComma\">Tags (comma separated)</label>\n          <input type=\"text\" id=\"new-node-tags\" placeholder=\"e.g. Docker, nginx, WG: vpn\" data-lang=\"ui.placeholders.tags\" data-lang-attr=\"placeholder\">\n          <button class=\"pick-icon-btn\" id=\"pick-new-node-tag-icon-btn\" style=\"margin-top: 10px;\" data-lang=\"ui.labels.addIconTag\">Add Web Icon Tag</button>\n          <div id=\"new-node-icon-tags\" style=\"margin-top: 10px; display: none;\">\n            <label style=\"display: block; margin-bottom: 8px; color: var(--text-soft); font-size: 13px;\" data-lang=\"ui.labels.iconTags\">Icon Tags:</label>\n            <div id=\"new-node-icon-tags-list\" style=\"display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 10px;\"></div>\n          </div>\n        </div>\n        <div style=\"background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; padding: 15px; margin-bottom: 15px;\">\n          <div id=\"new-node-appearance-label\" style=\"font-weight: 600; color: var(--text-main); margin-bottom: 12px; font-size: 14px;\" data-lang=\"ui.sections.nodeAppearance\">Node Appearance</div>\n          <label style=\"display: block;margin-bottom: 4px;color: var(--text-soft);font-size: 13px;\" data-lang=\"ui.labels.category\">Category</label>\n          <select id=\"new-node-category\">\n            <option value=\"basic\" data-lang=\"shapes.categories.basic\">Basic Shapes</option>\n            <option value=\"computers\" data-lang=\"shapes.categories.computers\">Computers & Devices</option>\n            <option value=\"network\" data-lang=\"shapes.categories.network\">Network Equipment</option>\n            <option value=\"cloud\" data-lang=\"shapes.categories.cloud\">Cloud & Services</option>\n            <option value=\"security\" data-lang=\"shapes.categories.security\">Security & Monitoring</option>\n            <option value=\"smarthome\" data-lang=\"shapes.categories.smarthome\">Smart Home</option>\n            <option value=\"sports\" data-lang=\"shapes.categories.sports\">Sports</option>\n            <option value=\"rack\" data-lang=\"shapes.categories.rack\">Rack Equipment</option>\n          </select>\n          <label style=\"display: block;margin-bottom: 4px;margin-top: 12px;color: var(--text-soft);font-size: 13px;\" data-lang=\"ui.labels.shape\">Shape</label>\n          <select id=\"new-node-shape\"></select>\n          <div id=\"new-node-shape-preview\" style=\"display:flex;align-items:center;justify-content:center;width:48px;height:48px;border:1px solid var(--edge-main);border-radius:6px;background:var(--panel);margin:8px 0\"></div>\n          <button class=\"pick-icon-btn\" id=\"pick-node-icon-btn\" style=\"margin-top: 10px;\" data-lang=\"ui.labels.searchWebIcons\"> Or search web icons</button>\n          <div id=\"selected-node-icon\" style=\"margin-top: 10px; display: none;\">\n            <label style=\"display: block; margin-bottom: 8px; color: var(--text-soft); font-size: 13px;\" data-lang=\"ui.labels.selectedIcon\">Selected Icon:</label>\n            <div class=\"icon-badge\" id=\"selected-node-icon-preview\">\n              <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" width=\"24\" height=\"24\">\n                <image href=\"https://cdn.jsdelivr.net/gh/selfhst/icons@master/png/docker.png\" width=\"24\" height=\"24\"></image>\n              </svg>\n              <span>docker</span>\n            </div>\n          </div>\n        </div>\n        <div id=\"new-node-ping-section\" style=\"background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; padding: 15px; margin-bottom: 15px;\">\n          <div style=\"font-weight: 600; color: var(--text-main); margin-bottom: 12px; font-size: 14px;\" data-lang=\"ui.sections.pingStatus\">Ping / Status Monitoring</div>\n          <div style=\"display: flex; align-items: center; gap: 8px; margin-bottom: 12px;\">\n          <label class=\"toggle-switch\"><input type=\"checkbox\" id=\"new-node-pingable\"><span class=\"toggle-slider\"></span></label>\n          <span style=\"color: var(--text-soft); font-size: 13px;\" data-lang=\"ui.labels.enablePing\">Enable ping/status check for this node</span>\n          </div>\n          <div id=\"new-node-ping-options\" style=\"display: block; padding-left: 24px; border-left: 2px solid var(--edge-main);\">\n            <label style=\"display: block; margin-bottom: 4px; color: var(--text-soft); font-size: 13px;\" data-lang=\"ui.labels.protocol\">Protocol</label>\n            <select id=\"new-node-ping-protocol\" style=\"margin-bottom: 12px;\">\n              <option value=\"http\" data-lang=\"ping.httpPort80\">HTTP (port 80) uses IP field</option>\n              <option value=\"https\" data-lang=\"ping.httpsPort443\">HTTPS (port 443) uses IP field</option>\n              <option value=\"custom\" data-lang=\"ping.customUrl\">Custom URL</option>\n            </select>\n            <div id=\"new-node-custom-url-container\" style=\"display: block;\">\n              <label style=\"display: block; margin-bottom: 4px; color: var(--text-soft); font-size: 13px;\" data-lang=\"ui.labels.customUrl\">Custom URL</label>\n              <input type=\"text\" id=\"new-node-custom-url\" placeholder=\"e.g. http://192.168.1.1:8080\" data-lang=\"ui.placeholders.customUrl\" data-lang-attr=\"placeholder\">\n            </div>\n            <label style=\"display: block; margin-bottom: 4px; margin-top: 8px; color: var(--text-soft); font-size: 13px;\" data-lang=\"ui.labels.timeoutMs\">\n            Timeout (ms)\n            </label>\n            <input type=\"number\" id=\"new-node-ping-timeout\" value=\"3000\" min=\"1000\" max=\"10000\" step=\"500\">\n          </div>\n        </div>\n        <details class=\"style-section\" id=\"new-node-more-options\">\n          <summary>More Options</summary>\n          <div style=\"background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; padding: 15px; margin-top: 10px;\">\n            <div style=\"font-weight: 600; color: var(--text-main); margin-bottom: 12px; font-size: 14px;\">Appearance</div>\n            <div class=\"style-content\">\n              <label>Fill Color:</label>\n              <input type=\"color\" id=\"new-node-fill-color\" value=\"#1e293b\">\n              <label>Border Color:</label>\n              <input type=\"color\" id=\"new-node-border-color\" value=\"#475569\">\n            </div>\n            <div class=\"size-controls\" style=\"margin-top: 10px;\">\n              <label>Size:</label>\n              <input type=\"range\" id=\"new-node-size\" min=\"20\" max=\"200\" value=\"55\">\n              <span id=\"new-node-size-value\">55</span>\n            </div>\n          </div>\n          <div style=\"background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; padding: 15px; margin-top: 10px;\">\n            <div style=\"font-weight: 600; color: var(--text-main); margin-bottom: 12px; font-size: 14px;\">Configuration</div>\n            <label style=\"display: block;margin-bottom: 4px;color: var(--text-soft);font-size: 13px;\">Layer</label>\n            <select id=\"new-node-layer\" style=\"margin-bottom: 12px;\">\n              <option value=\"layer1\">Physical</option>\n              <option value=\"layer2\">Logical</option>\n              <option value=\"layer3\">Security</option>\n              <option value=\"layer4\">Application</option>\n            </select>\n            <label style=\"display: block;margin-bottom: 4px;color: var(--text-soft);font-size: 13px;\">Connect to</label>\n            <select id=\"new-node-connect-to\" style=\"margin-bottom: 8px;\">\n              <option value=\"\">None</option>\n            </select>\n            <div style=\"display: flex; gap: 8px; align-items: center; margin-bottom: 12px;\">\n              <input type=\"color\" id=\"new-node-line-color\" value=\"#475569\" style=\"width: 36px; height: 30px;\">\n              <select id=\"new-node-line-direction\" style=\"flex: 1;padding: 6px;background: var(--panel);border: 1px solid var(--edge-main);border-radius: 4px;color: var(--text-main);font-size: 13px;\">\n                <option value=\"none\">No arrows</option>\n                <option value=\"forward\">&rarr; Forward</option>\n                <option value=\"backward\">&larr; Backward</option>\n                <option value=\"both\">&harr; Both</option>\n              </select>\n              <select id=\"new-node-line-routing\" style=\"flex: 1;padding: 6px;background: var(--panel);border: 1px solid var(--edge-main);border-radius: 4px;color: var(--text-main);font-size: 13px;\">\n                <option value=\"orthogonal\">Orthogonal</option>\n                <option value=\"curved\">Curved</option>\n                <option value=\"straight\">Straight</option>\n              </select>\n            </div>\n            <label style=\"display: block;margin-bottom: 4px;color: var(--text-soft);font-size: 13px;\">Notes</label>\n            <textarea id=\"new-node-notes\" rows=\"3\" placeholder=\"Add notes...\" style=\"width: 100%;padding: 10px;background: var(--panel);border: 1px solid var(--edge-main);border-radius: 4px;color: var(--text-main);font-size: 14px;resize: vertical;font-family: inherit;\"></textarea>\n          </div>\n        </details>\n        <div class=\"modal-buttons\">\n          <button class=\"btn-cancel\" id=\"add-node-cancel\" data-lang=\"ui.buttons.cancel\">Cancel</button>\n          <button class=\"btn-save\" id=\"add-node-save\" data-lang=\"ui.buttons.addNode\">Add Node</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"add-rack-modal\">\n      <div class=\"modal-content\">\n        <h3 data-lang=\"ui.modals.addRack\">Add New Rack</h3>\n        <div style=\"background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; padding: 15px; margin-bottom: 15px;\">\n          <div style=\"font-weight: 600; color: var(--text-main); margin-bottom: 12px; font-size: 14px;\" data-lang=\"ui.sections.basicInfo\">Basic Information</div>\n          <label id=\"new-rack-name-label\" style=\"display: block;margin-bottom: 4px;color: var(--text-soft);font-size: 13px;\" data-lang=\"ui.labels.name\">Rack Name</label>\n          <input type=\"text\" id=\"new-rack-name\" placeholder=\"e.g. Rack-A, DC1-R01, Production-01\" data-lang=\"ui.placeholders.rackName\" data-lang-attr=\"placeholder\">\n          <label id=\"new-rack-ip-label\" style=\"display: block;margin-bottom: 4px;margin-top: 12px;color: var(--text-soft);font-size: 13px;\" data-lang=\"ui.labels.networkRange\">IP / Network Range (optional)</label>\n          <input type=\"text\" id=\"new-rack-ip\" placeholder=\"e.g. 192.168.1.0/24\" data-lang=\"ui.placeholders.rackIp\" data-lang-attr=\"placeholder\">\n        </div>\n        <div style=\"background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; padding: 15px; margin-bottom: 15px;\">\n          <div style=\"font-weight: 600; color: var(--text-main); margin-bottom: 12px; font-size: 14px;\" data-lang=\"ui.sections.tags\">Tags</div>\n          <label id=\"new-rack-tags-label\" style=\"display: block;margin-bottom: 4px;color: var(--text-soft);font-size: 13px;\" data-lang=\"ui.labels.tagsComma\">Tags (comma separated)</label>\n          <input type=\"text\" id=\"new-rack-tags\" placeholder=\"e.g. Production, Data Center 1, Row A\" data-lang=\"ui.placeholders.rackTags\" data-lang-attr=\"placeholder\">\n        </div>\n        <div style=\"background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; padding: 15px; margin-bottom: 15px;\">\n          <div id=\"new-rack-appearance-label\" style=\"font-weight: 600; color: var(--text-main); margin-bottom: 12px; font-size: 14px;\" data-lang=\"ui.sections.rackAppearance\">Rack Appearance</div>\n          <label style=\"display: block;margin-bottom: 4px;color: var(--text-soft);font-size: 13px;\" data-lang=\"ui.labels.category\">Category</label>\n          <select id=\"new-rack-category\">\n            <option value=\"basic\" data-lang=\"shapes.categories.basic\">Basic Shapes</option>\n            <option value=\"computers\" data-lang=\"shapes.categories.computers\">Computers & Devices</option>\n            <option value=\"network\" data-lang=\"shapes.categories.network\">Network Equipment</option>\n            <option value=\"cloud\" data-lang=\"shapes.categories.cloud\">Cloud & Services</option>\n            <option value=\"security\" data-lang=\"shapes.categories.security\">Security & Monitoring</option>\n            <option value=\"smarthome\" data-lang=\"shapes.categories.smarthome\">Smart Home</option>\n            <option value=\"sports\" data-lang=\"shapes.categories.sports\">Sports</option>\n            <option value=\"rack\" data-lang=\"shapes.categories.rack\">Rack Equipment</option>\n          </select>\n          <label style=\"display: block;margin-bottom: 4px;margin-top: 12px;color: var(--text-soft);font-size: 13px;\" data-lang=\"ui.labels.shape\">Shape</label>\n          <select id=\"new-rack-shape\"></select>\n          <div id=\"new-rack-shape-preview\" style=\"display:flex;align-items:center;justify-content:center;width:48px;height:48px;border:1px solid var(--edge-main);border-radius:6px;background:var(--panel);margin:8px 0\"></div>\n          <button class=\"pick-icon-btn\" id=\"pick-rack-icon-btn\" style=\"margin-top: 10px;\" data-lang=\"ui.labels.searchWebIcons\">Or Search Web Icons</button>\n          <div id=\"selected-rack-icon\" style=\"margin-top: 10px; display: none;\">\n            <label style=\"display: block; margin-bottom: 8px; color: var(--text-soft); font-size: 13px;\" data-lang=\"ui.labels.selectedIcon\">Selected Icon:</label>\n            <div class=\"icon-badge\" id=\"selected-rack-icon-preview\">\n              <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" width=\"24\" height=\"24\">\n                <image href=\"\" width=\"24\" height=\"24\"></image>\n              </svg>\n              <span></span>\n            </div>\n          </div>\n        </div>\n        <div style=\"background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; padding: 15px; margin-bottom: 15px;\">\n          <div style=\"font-weight: 600; color: var(--text-main); margin-bottom: 12px; font-size: 14px;\" data-lang=\"ui.sections.rackConfiguration\">Rack Configuration</div>\n          <label style=\"display: block;margin-bottom: 4px;color: var(--text-soft);font-size: 13px;\" data-lang=\"ui.labels.rackCapacity\">Rack Capacity</label>\n          <select id=\"new-rack-capacity\">\n            <option value=\"42\" selected=\"\" data-lang=\"ui.labels.u42Standard\">42U (Standard Full Rack)</option>\n            <option value=\"48\" data-lang=\"ui.labels.u48Large\">48U (Large Rack)</option>\n            <option value=\"24\" data-lang=\"ui.labels.u24Half\">24U (Half Rack)</option>\n            <option value=\"12\" data-lang=\"ui.labels.u12Small\">12U (Small/Wall Mount)</option>\n            <option value=\"6\" data-lang=\"ui.labels.u6Mini\">6U (Mini Rack)</option>\n          </select>\n        </div>\n        <details class=\"style-section\" id=\"new-rack-more-options\">\n          <summary>More Options</summary>\n          <div style=\"background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; padding: 15px; margin-top: 10px;\">\n            <div style=\"font-weight: 600; color: var(--text-main); margin-bottom: 12px; font-size: 14px;\">Appearance</div>\n            <div class=\"style-content\">\n              <label>Fill Color:</label>\n              <input type=\"color\" id=\"new-rack-fill-color\" value=\"#1e293b\">\n              <label>Border Color:</label>\n              <input type=\"color\" id=\"new-rack-border-color\" value=\"#475569\">\n            </div>\n            <div class=\"size-controls\" style=\"margin-top: 10px;\">\n              <label>Size:</label>\n              <input type=\"range\" id=\"new-rack-size\" min=\"20\" max=\"200\" value=\"55\">\n              <span id=\"new-rack-size-value\">55</span>\n            </div>\n          </div>\n          <div style=\"background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 8px; padding: 15px; margin-top: 10px;\">\n            <div style=\"font-weight: 600; color: var(--text-main); margin-bottom: 12px; font-size: 14px;\">Configuration</div>\n            <label style=\"display: block;margin-bottom: 4px;color: var(--text-soft);font-size: 13px;\">Layer</label>\n            <select id=\"new-rack-layer\" style=\"margin-bottom: 12px;\">\n              <option value=\"layer1\">Physical</option>\n              <option value=\"layer2\">Logical</option>\n              <option value=\"layer3\">Security</option>\n              <option value=\"layer4\">Application</option>\n            </select>\n            <label style=\"display: block;margin-bottom: 4px;color: var(--text-soft);font-size: 13px;\">Assign Existing Nodes</label>\n            <div id=\"new-rack-assign-nodes\" style=\"max-height: 200px; overflow-y: auto; margin-bottom: 12px;\"></div>\n            <label style=\"display: block;margin-bottom: 4px;color: var(--text-soft);font-size: 13px;\">Notes</label>\n            <textarea id=\"new-rack-notes\" rows=\"3\" placeholder=\"Add notes...\" style=\"width: 100%;padding: 10px;background: var(--panel);border: 1px solid var(--edge-main);border-radius: 4px;color: var(--text-main);font-size: 14px;resize: vertical;font-family: inherit;\"></textarea>\n          </div>\n        </details>\n        <div class=\"modal-buttons\">\n          <button class=\"btn-cancel\" id=\"add-rack-cancel\" data-lang=\"ui.buttons.cancel\">Cancel</button>\n          <button class=\"btn-save\" id=\"add-rack-save\" data-lang=\"ui.buttons.addRack\">Add Rack</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal confirm-modal\" id=\"clear-all-modal\">\n      <div class=\"modal-content\">\n        <h3 data-lang=\"ui.modals.clearAllNodes\">Clear All Nodes</h3>\n        <p data-lang=\"messages.clearAllWarning\"> This will remove ALL nodes and connections. This cannot be undone until you reload without saving. Are you sure? </p>\n        <div class=\"modal-buttons\">\n          <button class=\"btn-cancel\" id=\"clear-all-cancel\" data-lang=\"ui.buttons.cancel\">Cancel</button>\n          <button class=\"btn-delete\" id=\"clear-all-confirm\" data-lang=\"messages.clearEverything\">Clear Everything</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"layer-modal\">\n      <div class=\"modal-content\" style=\"max-width: 400px;\">\n        <h3 data-lang=\"ui.modals.layerVisibility\">Layer Visibility</h3>\n        <p style=\"font-size: 13px; color: var(--text-soft); margin-bottom: 15px;\" data-lang=\"tooltips.toggleLayersDesc\">Toggle which layers are visible on the canvas</p>\n        <div style=\"display: flex; flex-direction: column; gap: 10px;\">\n          <div style=\"display: flex; align-items: center; gap: 8px; padding: 8px; background: var(--panel-alt); border-radius: 6px;\">\n          <label class=\"toggle-switch\"><input type=\"checkbox\" id=\"layer-1\" checked><span class=\"toggle-slider\"></span></label>\n          <span id=\"layer-1-label\" style=\"flex: 1; font-weight: 500;\">Layer 1</span>\n          </div>\n          <div style=\"display: flex; align-items: center; gap: 8px; padding: 8px; background: var(--panel-alt); border-radius: 6px;\">\n          <label class=\"toggle-switch\"><input type=\"checkbox\" id=\"layer-2\" checked><span class=\"toggle-slider\"></span></label>\n          <span id=\"layer-2-label\" style=\"flex: 1; font-weight: 500;\">Layer 2</span>\n          </div>\n          <div style=\"display: flex; align-items: center; gap: 8px; padding: 8px; background: var(--panel-alt); border-radius: 6px;\">\n          <label class=\"toggle-switch\"><input type=\"checkbox\" id=\"layer-3\" checked><span class=\"toggle-slider\"></span></label>\n          <span id=\"layer-3-label\" style=\"flex: 1; font-weight: 500;\">Layer 3</span>\n          </div>\n          <div style=\"display: flex; align-items: center; gap: 8px; padding: 8px; background: var(--panel-alt); border-radius: 6px;\">\n          <label class=\"toggle-switch\"><input type=\"checkbox\" id=\"layer-4\" checked><span class=\"toggle-slider\"></span></label>\n          <span id=\"layer-4-label\" style=\"flex: 1; font-weight: 500;\">Layer 4</span>\n          </div>\n        </div>\n        <div style=\"margin-top: 15px; display: flex; gap: 8px;\">\n          <button class=\"btn-cancel\" style=\"flex: 1;\" onclick=\"document.querySelectorAll('#layer-modal input[type=checkbox]').forEach(cb =&gt; cb.checked = false); applyLayerFilter();\" data-lang=\"ui.buttons.hideAll\">Hide All</button>\n          <button class=\"btn-save\" style=\"flex: 1;\" onclick=\"document.querySelectorAll('#layer-modal input[type=checkbox]').forEach(cb =&gt; cb.checked = true); applyLayerFilter();\" data-lang=\"ui.buttons.showAll\">Show All</button>\n        </div>\n        <div class=\"modal-buttons\" style=\"margin-top: 10px;\">\n          <button class=\"btn-cancel\" id=\"layer-modal-close\" data-lang=\"ui.buttons.close\">Close</button>\n        </div>\n      </div>\n    </div>\n    <div id=\"context-menu\" style=\"display: none !important;\">\n    </div>\n    <div class=\"modal\" id=\"recordings-modal\">\n      <div class=\"modal-content\" style=\"max-width: 600px;\">\n        <h3 data-lang=\"ui.modals.recordings\">Recordings</h3>\n        <p style=\"font-size: 13px; color: var(--text-soft); margin-bottom: 15px;\" data-lang=\"messages.manageRecordings\">Manage recorded movement sequences</p>\n        <div id=\"recordings-list\" style=\"display: flex; flex-direction: column; gap: 8px; max-height: 250px; overflow-y: auto; margin-bottom: 15px;\"><div style=\"text-align: center; padding: 40px; color: var(--text-soft);\" data-lang=\"messages.noRecordingsYet\">No recordings yet. Press Record to start.</div></div>\n        <div style=\"display: flex; gap: 8px; flex-wrap: wrap; justify-content: flex-start; margin-bottom: 15px;\">\n          <button class=\"btn-save\" onclick=\"exportRecordingVideo()\" style=\"flex:0 0 auto;\" data-lang=\"recordings.exportVideo\">Export Video</button>\n          <button class=\"btn-cancel\" onclick=\"deleteSelectedRecording()\" style=\"background:var(--danger);color:white;flex:0 0 auto;\" data-lang=\"ui.buttons.del\">Delete</button>\n        </div>\n        <details style=\"background: var(--panel-alt); border-radius: 6px; padding: 10px; margin-bottom: 15px;\">\n          <summary style=\"color: var(--accent); font-weight: 600; cursor: pointer;\" data-lang=\"trails.title\">Motion Trails</summary>\n          <div style=\"display: flex; flex-direction: column; gap: 10px; margin-top: 12px;\">\n            <div style=\"display: flex; align-items: center; justify-content: space-between;\">\n              <label style=\"color: var(--text-main); font-size: 14px;\" data-lang=\"trails.enableTrails\">Enable Trails:</label>\n              <label class=\"toggle-switch\"><input type=\"checkbox\" id=\"trail-enabled-toggle\" checked><span class=\"toggle-slider\"></span></label>\n            </div>\n            <div style=\"display: flex; align-items: center; justify-content: space-between;\">\n              <label style=\"color: var(--text-main); font-size: 14px;\" data-lang=\"trails.trailLength\">Trail Length:</label>\n              <div style=\"display: flex; align-items: center; gap: 8px;\">\n                <input type=\"range\" id=\"trail-length-slider\" min=\"2\" max=\"20\" value=\"8\" style=\"width: 100px;\">\n                <span id=\"trail-length-value\" style=\"min-width: 30px; color: var(--text-soft);\">8</span>\n              </div>\n            </div>\n            <div style=\"display: flex; align-items: center; justify-content: space-between;\">\n              <label style=\"color: var(--text-main); font-size: 14px;\" data-lang=\"trails.trailStyle\">Trail Style:</label>\n              <select id=\"trail-style-select\" style=\"padding: 4px 8px; background: var(--panel); color: var(--text-main); border: 1px solid var(--edge-main); border-radius: 4px;\">\n                <option value=\"solid\" data-lang=\"trails.styleSolid\">Solid Fade</option>\n                <option value=\"dashed\" data-lang=\"trails.styleDashed\">Dashed</option>\n                <option value=\"dots\" data-lang=\"trails.styleDots\">Dots</option>\n              </select>\n            </div>\n            <div style=\"display: flex; align-items: center; justify-content: space-between;\">\n              <label style=\"color: var(--text-main); font-size: 14px;\" data-lang=\"trails.showArrow\">Show Direction Arrow:</label>\n              <label class=\"toggle-switch\"><input type=\"checkbox\" id=\"trail-arrow-toggle\" checked><span class=\"toggle-slider\"></span></label>\n            </div>\n            <p style=\"font-size: 12px; color: var(--text-soft); margin: 0;\" data-lang=\"trails.perNodeHint\">Tip: Enable/disable trails per node in the node panel</p>\n          </div>\n        </details>\n        <div style=\"display: flex; gap: 8px; justify-content: flex-end;\">\n          <button class=\"btn-cancel\" id=\"recordings-modal-close\" data-lang=\"ui.buttons.close\">Close</button>\n        </div>\n      </div>\n    </div>\n    <div class=\"modal\" id=\"welcome-modal\">\n      <div class=\"modal-content\" style=\"max-width: 700px; max-height: 90vh; overflow-y: auto;\">\n        <h2 style=\"margin-bottom: 8px; text-align: center;\" data-lang=\"welcome.title\">Welcome to The One File</h2>\n        <p style=\"font-size: 14px; color: var(--text-soft); margin-bottom: 16px; text-align: center;\" data-lang=\"welcome.subtitle\">What are you mapping today?</p>\n        <div id=\"welcome-mode-buttons\" style=\"display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-bottom: 20px;\">\n          <button class=\"welcome-mode-btn\" data-mode=\"network\" style=\"padding: 12px 16px; background: var(--panel-alt); border: 2px solid var(--accent); border-radius: 8px; color: var(--text-main); font-size: 14px; cursor: pointer; text-align: left; transition: all 0.2s;\">\n            <strong data-lang=\"welcome.modes.network\">Network Topology</strong>\n            <span style=\"display: block; font-size: 11px; color: var(--text-soft); margin-top: 2px;\" data-lang=\"welcome.modes.networkDesc\">Servers, routers, switches</span>\n          </button>\n          <button class=\"welcome-mode-btn\" data-mode=\"mindmap\" style=\"padding: 12px 16px; background: var(--panel-alt); border: 2px solid var(--edge-main); border-radius: 8px; color: var(--text-main); font-size: 14px; cursor: pointer; text-align: left; transition: all 0.2s;\">\n            <strong data-lang=\"welcome.modes.mindmap\">Mind Map</strong>\n            <span style=\"display: block; font-size: 11px; color: var(--text-soft); margin-top: 2px;\" data-lang=\"welcome.modes.mindmapDesc\">Ideas, concepts, brainstorming</span>\n          </button>\n          <button class=\"welcome-mode-btn\" data-mode=\"sports\" style=\"padding: 12px 16px; background: var(--panel-alt); border: 2px solid var(--edge-main); border-radius: 8px; color: var(--text-main); font-size: 14px; cursor: pointer; text-align: left; transition: all 0.2s;\">\n            <strong data-lang=\"welcome.modes.sports\">Sports Playbook</strong>\n            <span style=\"display: block; font-size: 11px; color: var(--text-soft); margin-top: 2px;\" data-lang=\"welcome.modes.sportsDesc\">Plays, formations, positions</span>\n          </button>\n          <button class=\"welcome-mode-btn\" data-mode=\"smarthome\" style=\"padding: 12px 16px; background: var(--panel-alt); border: 2px solid var(--edge-main); border-radius: 8px; color: var(--text-main); font-size: 14px; cursor: pointer; text-align: left; transition: all 0.2s;\">\n            <strong data-lang=\"welcome.modes.smarthome\">Smart Home</strong>\n            <span style=\"display: block; font-size: 11px; color: var(--text-soft); margin-top: 2px;\" data-lang=\"welcome.modes.smarthomeDesc\">IoT devices, automation</span>\n          </button>\n          <button class=\"welcome-mode-btn\" data-mode=\"floorplan\" style=\"padding: 12px 16px; background: var(--panel-alt); border: 2px solid var(--edge-main); border-radius: 8px; color: var(--text-main); font-size: 14px; cursor: pointer; text-align: left; transition: all 0.2s; grid-column: span 2;\">\n            <strong data-lang=\"welcome.modes.floorplan\">Floor Plan / Blueprint</strong>\n            <span style=\"display: block; font-size: 11px; color: var(--text-soft); margin-top: 2px;\" data-lang=\"welcome.modes.floorplanDesc\">Rooms, layouts, physical spaces</span>\n          </button>\n        </div>\n        <div id=\"welcome-canvas-row\" style=\"display: flex; align-items: center; gap: 10px; margin-bottom: 8px;\">\n          <label style=\"font-size: 13px; color: var(--text-main); min-width: 80px;\" data-lang=\"welcome.canvasStyle\">Canvas Style</label>\n          <select id=\"welcome-canvas-select\" style=\"flex: 1; padding: 6px 10px; background: var(--input-bg); color: var(--input-text); border: 1px solid var(--input-border); border-radius: 4px;\">\n            <option value=\"grid\" data-lang=\"welcome.canvas.grid\">Standard Grid</option>\n            <option value=\"dots\" data-lang=\"welcome.canvas.dots\">Dot Grid</option>\n            <option value=\"blueprint\" data-lang=\"welcome.canvas.blueprint\">Blueprint Grid</option>\n            <option value=\"none\" data-lang=\"welcome.canvas.none\">No Grid</option>\n          </select>\n        </div>\n        <div id=\"welcome-canvas-preview\" style=\"height: 60px; margin-bottom: 12px; border: 1px solid var(--edge-main); border-radius: 4px; background: var(--bg); overflow: hidden;\"></div>\n        <div style=\"display: flex; align-items: center; gap: 10px; margin-bottom: 12px;\">\n          <label style=\"font-size: 13px; color: var(--text-main); min-width: 80px;\" data-lang=\"welcome.theme\">Theme</label>\n          <select id=\"welcome-theme-select\" style=\"flex: 1; padding: 6px 10px; background: var(--input-bg); color: var(--input-text); border: 1px solid var(--input-border); border-radius: 4px;\">\n            <option value=\"defaulted\" selected data-lang=\"themes.default\">Default</option>\n            <option value=\"\" data-lang=\"ui.options.customColors\">Custom Colors</option>\n            <optgroup label=\"Corporate\" data-lang-label=\"themeGroups.corporate\">\n              <option value=\"slate\" data-lang=\"themes.slate\">Slate</option>\n              <option value=\"graphite\" data-lang=\"themes.graphite\">Graphite</option>\n              <option value=\"frost\" data-lang=\"themes.frost\">Frost (Light)</option>\n            </optgroup>\n            <optgroup label=\"Homelab\" data-lang-label=\"themeGroups.homelab\">\n              <option value=\"synthwave\" data-lang=\"themes.synthwave\">Synthwave</option>\n              <option value=\"terminal\" data-lang=\"themes.terminal\">Terminal</option>\n            </optgroup>\n            <optgroup label=\"Dev\" data-lang-label=\"themeGroups.dev\">\n              <option value=\"dracula\" data-lang=\"themes.dracula\">Dracula</option>\n              <option value=\"cobalt\" data-lang=\"themes.cobalt\">Cobalt</option>\n              <option value=\"solarized\" data-lang=\"themes.solarized\">Solarized</option>\n            </optgroup>\n            <optgroup label=\"Mind Map\" data-lang-label=\"themeGroups.mindMap\">\n              <option value=\"brainwave\" data-lang=\"themes.brainwave\">Brainwave</option>\n                            <option value=\"neonMint\" data-lang=\"themes.neonMint\">Neon Mint</option>\n              <option value=\"velvetDusk\" data-lang=\"themes.velvetDusk\">Velvet Dusk</option>\n              <option value=\"sunburst\" data-lang=\"themes.sunburst\">Sunburst</option>\n            </optgroup>\n          </select>\n        </div>\n        <details style=\"margin-bottom: 12px; border: 1px solid var(--edge-main); border-radius: 6px; padding: 8px;\">\n          <summary style=\"cursor: pointer; font-size: 13px; color: var(--text-main); font-weight: 600;\" data-lang=\"welcome.colorCustomization\">Color Customization</summary>\n          <div style=\"display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-top: 10px;\">\n            <div style=\"display: flex; align-items: center; gap: 6px;\">\n              <input type=\"color\" id=\"welcome-bg-color\" value=\"#0b0e13\" style=\"width: 32px; height: 24px; border: 1px solid var(--input-border); border-radius: 3px; cursor: pointer;\">\n              <label style=\"font-size: 11px; color: var(--text-soft);\" data-lang=\"welcome.colors.mainBg\">Top Bar Background</label>\n            </div>\n            <div style=\"display: flex; align-items: center; gap: 6px;\">\n              <input type=\"color\" id=\"welcome-accent-color\" value=\"#4fd1c5\" style=\"width: 32px; height: 24px; border: 1px solid var(--input-border); border-radius: 3px; cursor: pointer;\">\n              <label style=\"font-size: 11px; color: var(--text-soft);\" data-lang=\"welcome.colors.accent\">Accent</label>\n            </div>\n            <div style=\"display: flex; align-items: center; gap: 6px;\">\n              <input type=\"color\" id=\"welcome-text-color\" value=\"#e2e8f0\" style=\"width: 32px; height: 24px; border: 1px solid var(--input-border); border-radius: 3px; cursor: pointer;\">\n              <label style=\"font-size: 11px; color: var(--text-soft);\" data-lang=\"welcome.colors.primaryText\">Primary Text</label>\n            </div>\n            <div style=\"display: flex; align-items: center; gap: 6px;\">\n              <input type=\"color\" id=\"welcome-text-soft-color\" value=\"#94a3b8\" style=\"width: 32px; height: 24px; border: 1px solid var(--input-border); border-radius: 3px; cursor: pointer;\">\n              <label style=\"font-size: 11px; color: var(--text-soft);\" data-lang=\"welcome.colors.secondaryText\">Secondary Text</label>\n            </div>\n            <div style=\"display: flex; align-items: center; gap: 6px;\">\n              <input type=\"color\" id=\"welcome-panel-color\" value=\"#0f1318\" style=\"width: 32px; height: 24px; border: 1px solid var(--input-border); border-radius: 3px; cursor: pointer;\">\n              <label style=\"font-size: 11px; color: var(--text-soft);\" data-lang=\"welcome.colors.sidebarPanel\">Main Background</label>\n            </div>\n            <div style=\"display: flex; align-items: center; gap: 6px;\">\n              <input type=\"color\" id=\"welcome-modal-color\" value=\"#141921\" style=\"width: 32px; height: 24px; border: 1px solid var(--input-border); border-radius: 3px; cursor: pointer;\">\n              <label style=\"font-size: 11px; color: var(--text-soft);\" data-lang=\"welcome.colors.modalWindows\">Modal Background</label>\n            </div>\n            <div style=\"display: flex; align-items: center; gap: 6px;\">\n              <input type=\"color\" id=\"welcome-danger-color\" value=\"#f56565\" style=\"width: 32px; height: 24px; border: 1px solid var(--input-border); border-radius: 3px; cursor: pointer;\">\n              <label style=\"font-size: 11px; color: var(--text-soft);\" data-lang=\"welcome.colors.dangerButtons\">Grid Color</label>\n            </div>\n            <div style=\"display: flex; align-items: center; gap: 6px;\">\n              <input type=\"color\" id=\"welcome-mobile-footer-color\" value=\"#0b0e13\" style=\"width: 32px; height: 24px; border: 1px solid var(--input-border); border-radius: 3px; cursor: pointer;\">\n              <label style=\"font-size: 11px; color: var(--text-soft);\" data-lang=\"welcome.colors.mobileFooter\">Sidebar Background</label>\n            </div>\n          </div>\n\t\t  <div style=\"text-align:center\">\n            <label style=\"font-size: 11px; color: var(--text-soft);\" data-lang=\"welcome.colors.moreStyles\">More styles available in main settings panel</label>\n          </div>\n        </details>\n        <div style=\"display: flex; gap: 10px; justify-content: center;\">\n          <button id=\"welcome-start-btn\" style=\"padding: 10px 24px; background: var(--accent); color: var(--bg); border: none; border-radius: 6px; font-size: 14px; font-weight: 600; cursor: pointer;\" data-lang=\"welcome.startMapping\">Start Mapping</button>\n          <button id=\"welcome-skip-btn\" style=\"padding: 10px 24px; background: transparent; border: 1px solid var(--edge-main); border-radius: 6px; color: var(--text-soft); font-size: 14px; cursor: pointer;\" data-lang=\"welcome.skip\">Skip</button>\n        </div>\n      </div>\n    </div>\n    <header>\n      <div class=\"title-block\">\n        <h1 id=\"page-title\" class=\"editable-page-title\">The One File: The Networkening</h1>\n        <div class=\"save-row\">\n          <button id=\"save-file-btn\" class=\"save-btn\" type=\"button\" data-lang=\"topbar.saveHtml\">Save HTML</button>\n          <div style=\"display: flex;align-items: center;gap: 4px;font-size: 12px;color: var(--text-soft);user-select: none;\">\n            <label class=\"toggle-switch\"><input type=\"checkbox\" id=\"encrypt-toggle\"><span class=\"toggle-slider\"></span></label>\n            <span title=\"Encrypt data with password\">🔒</span>\n          </div>\n          <div class=\"dropdown\" id=\"export-dropdown\">\n            <button class=\"dropdown-btn\" type=\"button\" data-lang=\"topbar.export\">Export ▾</button>\n            <div class=\"dropdown-menu\">\n              <button type=\"button\" onclick=\"screenshotCanvas()\" data-lang=\"help.formats.pngImage\">PNG Image</button>\n              <button type=\"button\" onclick=\"exportCanvasSVG()\" data-lang=\"help.formats.svgVector\">SVG Vector</button>\n              <div class=\"dropdown-divider\"></div>\n              <button type=\"button\" onclick=\"exportJSONFile()\" data-lang=\"help.formats.json\">JSON</button>\n              <button type=\"button\" onclick=\"exportMarkdown()\" data-lang=\"help.formats.markdown\">Markdown</button>\n              <button type=\"button\" onclick=\"exportCSV()\" data-lang=\"help.formats.csv\">CSV</button>\n              <div class=\"dropdown-divider\"></div>\n              <button type=\"button\" onclick=\"printTopology()\" data-lang=\"ui.buttons.printBW\">Print B&W</button>\n              <button type=\"button\" onclick=\"printTopologyColor()\" data-lang=\"ui.buttons.printColorFull\">Print Full Color</button>\n              <button type=\"button\" onclick=\"printTopologyColor('#ffffff')\" data-lang=\"ui.buttons.printColorWhite\">Print White BG</button>\n              <button type=\"button\" onclick=\"printTopologyColor('#000000')\" data-lang=\"ui.buttons.printColorBlack\">Print Black BG</button>\n\t\t\t  <div class=\"dropdown-divider\"></div>\n\t\t\t  <button type=\"button\" onclick=\"showFormatHelp()\" data-lang=\"ui.buttons.exportHelp\">Export Help</button>\n            </div>\n          </div>\n          <div class=\"dropdown\" id=\"import-dropdown\">\n            <button class=\"dropdown-btn\" type=\"button\" data-lang=\"topbar.import\">Import ▾</button>\n            <div class=\"dropdown-menu\">\n              <button type=\"button\" onclick=\"document.getElementById('import-html-file').click()\" data-lang=\"help.formats.html\">HTML</button>\n              <button type=\"button\" onclick=\"document.getElementById('import-data-file').click()\" data-lang=\"help.formats.json\">JSON</button>\n              <button type=\"button\" onclick=\"document.getElementById('import-markdown-file').click()\" data-lang=\"help.formats.markdown\">Markdown</button>\n              <button type=\"button\" onclick=\"document.getElementById('import-csv-file').click()\" data-lang=\"help.formats.csv\">CSV</button>\n              <div class=\"dropdown-divider\"></div>\n              <button type=\"button\" onclick=\"showFormatHelp()\" data-lang=\"ui.buttons.importHelp\">Import Help</button>\n            </div>\n          </div>\n          <input type=\"file\" id=\"import-html-file\" accept=\".html,.htm\" style=\"display:none\">\n          <input type=\"file\" id=\"import-json-file\" accept=\".json\" style=\"display:none\">\n          <input type=\"file\" id=\"import-markdown-file\" accept=\".md,.markdown,.txt\" style=\"display:none\">\n          <input type=\"file\" id=\"import-csv-file\" accept=\".csv,.txt\" style=\"display:none\">\n          <span id=\"save-help-btn\" class=\"help-icon\" title=\"Why save?\">?</span>\n        </div>\n      </div>\n      <div id=\"topbar-menu\">\n        <button id=\"back-to-topology-btn\" title=\"Exit rack view and return to topology\" style=\"padding: 6px 12px;background: var(--accent);color: var(--bg);border: none;border-radius: 6px;cursor: pointer;font-size: 14px;font-weight: 600;display: none;\">← Back to Topology</button>\n        <button id=\"add-node-btn\" class=\"save-btn\" title=\"Add new node\" style=\"background: var(--accent)\">+ Node</button>\n        <button id=\"add-rack-btn\" class=\"save-btn\" title=\"Add new rack\" style=\"background: var(--accent); margin-left: 8px;\">+ Rack</button>\n        <button id=\"undo-btn\" title=\"Undo (Ctrl+Z)\" style=\"padding: 6px 12px; background: var(--btn-bg); color: var(--btn-text); border: 1px solid var(--edge-main); border-radius: 6px; cursor: pointer; font-size: 14px; font-weight: 600; opacity: 0.5;\" disabled=\"\">↶</button>\n        <button id=\"redo-btn\" title=\"Redo (Ctrl+Y)\" style=\"padding: 6px 12px; background: var(--btn-bg); color: var(--btn-text); border: 1px solid var(--edge-main); border-radius: 6px; cursor: pointer; font-size: 14px; font-weight: 600; opacity: 0.5;\" disabled=\"\">↷</button>\n<input type=\"text\" id=\"search-nodes\" placeholder=\"Search nodes...\" title=\"Search by name, IP, MAC, role, or tag\" style=\"padding: 6px 12px;background: var(--input-bg);color: var(--input-text);border: 1px solid var(--input-border);border-radius: 6px;font-family: var(--input-font);font-size: var(--input-font-size);width: 180px;\" data-lang=\"ui.placeholders.searchNodes\" data-lang-attr=\"placeholder\">\n        <button id=\"check-all-ping-btn\" data-lang=\"tooltips.checkAllStatus\" data-lang-attr=\"title\" title=\"Check status of all enabled nodes\" style=\"padding: 6px 12px;background: var(--btn-bg);color: var(--btn-text);border: 1px solid var(--edge-main);border-radius: 6px;cursor: pointer;font-size: 14px;font-weight: 600\" data-lang-text=\"ui.buttons.checkPings\">Check Pings</button>\n        <button id=\"layers-btn\" data-lang=\"ui.tooltips.toggleLayers\" data-lang-attr=\"title\" title=\"Toggle layer visibility\" style=\"padding: 6px 12px;background: var(--btn-bg);color: var(--btn-text);border: 1px solid var(--edge-main);border-radius: 6px;cursor: pointer;font-size: 14px;font-weight: 600;\" data-lang-text=\"ui.buttons.layers\">Layers</button>\n        <button id=\"tabs-btn\" data-lang=\"tooltips.manageTabs\" data-lang-attr=\"title\" title=\"Manage document tabs\" style=\"padding: 6px 12px;background: var(--btn-bg);color: var(--btn-text);border: 1px solid var(--edge-main);border-radius: 6px;cursor: pointer;font-size: 14px;font-weight: 600;\" data-lang-text=\"topbar.tabs\">Tabs</button>\n        <button id=\"rollback-btn\" data-lang=\"tooltips.viewVersionHistory\" data-lang-attr=\"title\" title=\"View version history\" style=\"padding: 6px 12px;background: var(--btn-bg);color: var(--btn-text);border: 1px solid var(--edge-main);border-radius: 6px;cursor: pointer;font-size: 14px;font-weight: 600;\" data-lang-text=\"topbar.snapshots\">Snapshots</button>\n        <button id=\"audit-log-btn\" data-lang=\"ui.tooltips.viewAuditLog\" data-lang-attr=\"title\" title=\"View audit log\" style=\"padding: 6px 12px;background: var(--btn-bg);color: var(--btn-text);border: 1px solid var(--edge-main);border-radius: 6px;cursor: pointer;font-size: 14px;font-weight: 600;\" data-lang-text=\"ui.buttons.auditLog\">Audit</button>\n        <button id=\"port-map-btn\" data-lang=\"ui.tooltips.viewPortMap\" data-lang-attr=\"title\" title=\"View all port connections\" style=\"padding: 6px 12px;background: var(--btn-bg);color: var(--btn-text);border: 1px solid var(--edge-main);border-radius: 6px;cursor: pointer;font-size: 14px;font-weight: 600;\" data-lang-text=\"ui.buttons.portMap\">Ports</button>\n        <button id=\"secrets-btn\" data-lang=\"ui.tooltips.manageNotes\" data-lang-attr=\"title\" title=\"Manage encrypted notes\" style=\"padding: 6px 12px;background: var(--btn-bg);color: var(--btn-text);border: 1px solid var(--edge-main);border-radius: 6px;cursor: pointer;font-size: 14px;font-weight: 600;\" data-lang-text=\"ui.buttons.notes\">Notes</button>\n\t\t<div id=\"recording-controls\" style=\"display:inline-flex;align-items:center;gap:4px;margin-left:8px;padding:4px 8px;background:var(--panel-alt);border:1px solid var(--edge-main);border-radius:6px;\">\n          <button id=\"record-btn\" title=\"Start recording\" style=\"padding:4px 8px;background:var(--btn-bg);color:#f56565;border:1px solid var(--edge-main);border-radius:4px;cursor:pointer;font-size:14px;\">●</button>\n          <button id=\"step-record-btn\" title=\"Step by step recording\" style=\"padding:4px 8px;background:var(--btn-bg);color:#48bb78;border:1px solid var(--edge-main);border-radius:4px;cursor:pointer;font-size:14px;\">●+</button>\n          <button id=\"play-btn\" title=\"Play recording\" style=\"padding:4px 8px;background:var(--btn-bg);color:var(--btn-text);border:1px solid var(--edge-main);border-radius:4px;cursor:pointer;font-size:14px;\">▶</button>\n          <div id=\"recording-expanded\" style=\"display:none;align-items:center;gap:4px;\">\n            <button id=\"stop-btn\" title=\"Stop\" style=\"padding:4px 8px;background:var(--btn-bg);color:var(--btn-text);border:1px solid var(--edge-main);border-radius:4px;cursor:pointer;font-size:14px;\">■</button>\n            <button id=\"pause-btn\" title=\"Pause\" style=\"padding:4px 8px;background:var(--btn-bg);color:var(--btn-text);border:1px solid var(--edge-main);border-radius:4px;cursor:pointer;font-size:14px;display:none;\">❚❚</button>\n            <span id=\"recording-time\" style=\"font-size:11px;color:var(--text-soft);min-width:40px;text-align:center;\">0:00</span>\n            <input type=\"range\" id=\"playback-scrubber\" min=\"0\" max=\"100\" value=\"0\" style=\"width:80px;cursor:pointer;\" title=\"Playback position\">\n            <select id=\"playback-speed\" title=\"Playback speed\" style=\"padding:2px 4px;background:var(--input-bg);color:var(--input-text);border:1px solid var(--input-border);border-radius:4px;font-size:12px;\">\n              <option value=\"0.5\">0.5x</option>\n              <option value=\"1\" selected>1x</option>\n              <option value=\"2\">2x</option>\n              <option value=\"4\">4x</option>\n            </select>\n            <label class=\"toggle-switch\" title=\"Loop playback\"><input type=\"checkbox\" id=\"loop-playback\"><span class=\"toggle-slider\"></span></label>\n            <button id=\"recordings-btn\" title=\"Manage recordings\" style=\"padding:4px 8px;background:var(--btn-bg);color:var(--btn-text);border:1px solid var(--edge-main);border-radius:4px;cursor:pointer;font-size:12px;\">...</button>\n          </div>\n        </div>\n\t\t<button id=\"mobile-export-btn\" data-lang=\"tooltips.exportTopology\" data-lang-attr=\"title\" title=\"Export topology\" style=\"padding: 6px 12px;background: var(--btn-bg);color: var(--btn-text);border: 1px solid var(--edge-main);border-radius: 6px;cursor: pointer;font-size: 14px;font-weight: 600;\" data-lang-text=\"topbar.export\">Export</button>\n        <button id=\"mobile-import-btn\" data-lang=\"tooltips.importTopology\" data-lang-attr=\"title\" title=\"Import topology\" style=\"padding: 6px 12px;background: var(--btn-bg);color: var(--btn-text);border: 1px solid var(--edge-main);border-radius: 6px;cursor: pointer;font-size: 14px;font-weight: 600;\" data-lang-text=\"topbar.import\">Import</button>\n        <button id=\"settings-btn\" title=\"Page settings\">⚙️</button>\n      </div>\n      <button id=\"mobile-menu-toggle\" class=\"mobile-menu-btn\">☰</button>\n      <div class=\"header-resizer\" id=\"header-resizer\">\n        <svg class=\"resizer-icon\" width=\"40\" height=\"6\" viewBox=\"0 0 40 6\" xmlns=\"http://www.w3.org/2000/svg\">\n          <circle cx=\"14\" cy=\"3\" r=\"2\"></circle>\n          <circle cx=\"20\" cy=\"3\" r=\"2\"></circle>\n          <circle cx=\"26\" cy=\"3\" r=\"2\"></circle>\n        </svg>\n      </div>\n    </header>\n    <main>\n      <section class=\"topology-panel\">\n        <div class=\"draw-toolbar\" id=\"draw-toolbar\" style=\"display: flex !important;\">\n          <button type=\"button\" class=\"toolbar-close-btn\" id=\"draw-toolbar-close-btn\" title=\"Hide draw toolbar\">✕</button>\n          <button id=\"draw-toggle\" title=\"Draw custom line\">✏️</button>\n          <button id=\"rect-toggle\" title=\"Draw zone\">▭</button>\n          <button id=\"text-toggle\" title=\"Add text\">T</button>\n          <button id=\"image-toggle\" data-lang=\"tooltips.addImage\" data-lang-attr=\"title\">🖼️</button>\n          <input type=\"file\" id=\"image-file-input\" accept=\"image/*\" style=\"display: none;\">\n          <span style=\"border-left: 1px solid var(--edge-main); height: 20px; margin: 0 4px;\"></span>\n          <select id=\"rect-style\" title=\"zone style\">\n            <option value=\"filled\">Filled</option>\n            <option value=\"outlined\">Outlined</option>\n          </select>\n          <input type=\"color\" id=\"draw-color\" value=\"#f97316\" title=\"Line color\">\n          <select id=\"draw-style\" title=\"Line style\">\n            <option value=\"solid\">Solid</option>\n            <option value=\"dashed\">Dashed</option>\n            <option value=\"dotted\">Dotted</option>\n            <option value=\"wall\">Wall</option>\n          </select>\n          <select id=\"draw-arrow\" title=\"Arrow direction\">\n            <option value=\"none\">No arrows</option>\n            <option value=\"forward\">→ Right</option>\n            <option value=\"backward\">← Left</option>\n            <option value=\"both\">↔ Both</option>\n          </select>\n          <button id=\"draw-undo\" style=\"display: none\" data-lang=\"tooltips.undoLastPoint\" data-lang-attr=\"title\" title=\"Undo last point\" data-lang-text=\"drawToolbar.undoLastPoint\">Undo</button>\n        </div>\n        <div class=\"topology-toolbar\" id=\"topology-toolbar\" style=\"display: flex !important;\">\n          <button type=\"button\" class=\"toolbar-close-btn\" id=\"topology-toolbar-close-btn\" title=\"Hide add line toolbar\">✕</button>\n          <label for=\"add-line-select\" data-lang=\"topologyToolbar.addLineTo\">Add line to:</label>\n          <select id=\"add-line-select\"></select>\n\t      <input type=\"color\" id=\"add-line-color\" value=\"#475569\" title=\"Line color\" style=\"width: 30px;height: 24px;border: 1px solid var(--toolbar-border);border-radius: 4px;cursor: pointer;background: transparent;padding: 0;\">     \n\t\t <select id=\"add-line-direction\" title=\"Line direction\">\n            <option value=\"none\">No arrows</option>\n            <option value=\"forward\">→ Forward</option>\n            <option value=\"backward\">← Backward</option>\n            <option value=\"both\">↔ Both</option>\n          </select>\n          <select id=\"add-line-routing\" title=\"Line routing\">\n            <option value=\"orthogonal\">Orthogonal</option>\n            <option value=\"curved\">Curved</option>\n            <option value=\"straight\">Straight</option>\n          </select>\n          <button id=\"add-line-btn\" data-lang=\"ui.buttons.add\">Add</button>\n        </div>\n        <div class=\"topology-toolbar bulk-toolbar-desktop\" id=\"bulk-toolbar\" style=\"display: none\">\n          <button type=\"button\" class=\"toolbar-close-btn\" id=\"bulk-toolbar-close\" title=\"Clear selection\">✕</button>\n          <label style=\"font-weight: 600; color: var(--accent)\" data-lang=\"bulkToolbar.selected\">Selected: <span id=\"bulk-count\">0</span></label>\n          <button id=\"bulk-align-left\" title=\"Align left\">⬅ Left</button>\n          <button id=\"bulk-align-right\" title=\"Align right\">➡ Right</button>\n          <button id=\"bulk-align-top\" title=\"Align top\">⬆ Top</button>\n          <button id=\"bulk-align-bottom\" title=\"Align bottom\">⬇ Bottom</button>\n          <button id=\"bulk-distribute-h\" title=\"Distribute horizontally\">↔ Distribute H</button>\n          <button id=\"bulk-distribute-v\" title=\"Distribute vertically\">↕ Distribute V</button>\n          <button id=\"bulk-clone\" title=\"Clone selected\">📋 Clone</button>\n          <button id=\"bulk-zone-copy\" title=\"Copy zone style\">📡 Copy Zone</button>\n          <button id=\"bulk-zone-paste\" title=\"Paste zone style\">📡 Paste Zone</button>\n          <button id=\"bulk-zone-toggle\" title=\"Toggle zones on selected\">📡 Toggle Zones</button>\n          <button id=\"bulk-delete\" title=\"Delete selected\" style=\"background: var(--danger); color: white;\" data-lang=\"ui.buttons.delete\">Delete</button>\n        </div>\n        <div class=\"bulk-toolbar-mobile\" id=\"bulk-toolbar-mobile\" style=\"display: none\">\n          <button type=\"button\" id=\"bulk-mobile-btn\" style=\"background: var(--accent);color: white;padding: 12px 20px;border-radius: 25px;font-weight: 600;font-size: 16px;box-shadow: 0 4px 12px rgba(0,0,0,0.3);border: none;cursor: pointer;display: flex;align-items: center;gap: 8px;\">\n          <span id=\"bulk-count-mobile\">0</span>Selected</button>\n        </div>\n        <div id=\"bulk-actions-modal\" style=\"display: none;position: fixed;bottom: 0;left: 0;right: 0;background: var(--panel-alt);border-top-left-radius:20px;border-top-right-radius:20px;padding:20px;padding-bottom:(safe-area-inset-bottom, 20px);box-shadow: 0 -4px 20px rgba(0,0,0,0.5);z-index: 10000;max-height: calc(100vh - 80px);overflow-y: auto;\">\n          <div style=\"display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;\">\n            <h3 style=\"margin: 0; color: var(--accent); font-size: 20px;\">\n              <span id=\"bulk-count-modal\">0</span> Nodes Selected\n            </h3>\n            <button id=\"bulk-modal-close\" style=\"background: none;border: none;font-size: 24px;cursor: pointer;color: var(--text-main);padding: 0;width: 32px;height: 32px;\">✕</button>\n          </div>\n          <div style=\"display: grid; grid-template-columns: 1fr 1fr; gap: 12px;\">\n            <button id=\"bulk-align-left-mobile\" class=\"bulk-action-btn\">⬅<br><span style=\"font-size: 12px;\">Align Left</span></button>\n            <button id=\"bulk-align-right-mobile\" class=\"bulk-action-btn\">➡<br><span style=\"font-size: 12px;\">Align Right</span></button>\n            <button id=\"bulk-align-top-mobile\" class=\"bulk-action-btn\">⬆<br><span style=\"font-size: 12px;\">Align Top</span></button>\n            <button id=\"bulk-align-bottom-mobile\" class=\"bulk-action-btn\">⬇<br><span style=\"font-size: 12px;\">Align Bottom</span></button>\n            <button id=\"bulk-distribute-h-mobile\" class=\"bulk-action-btn\">↔<br><span style=\"font-size: 12px;\">Distribute H</span></button>\n            <button id=\"bulk-distribute-v-mobile\" class=\"bulk-action-btn\">↕<br><span style=\"font-size: 12px;\">Distribute V</span></button>\n            <button id=\"bulk-lock-mobile\" class=\"bulk-action-btn\">🔒<br><span style=\"font-size: 12px;\">Lock Toggle</span></button>\n            <button id=\"bulk-group-mobile\" class=\"bulk-action-btn\">⭕<br><span style=\"font-size: 12px;\">Group Toggle</span></button>\n            <button id=\"bulk-clone-mobile\" class=\"bulk-action-btn\">📋<br><span style=\"font-size: 12px;\">Clone All</span></button>\n            <button id=\"bulk-delete-mobile\" class=\"bulk-action-btn\" style=\"background: var(--danger); color: white;\">🗑<br><span style=\"font-size: 12px;\">Delete All</span></button>\n          </div>\n          <div style=\"margin-top: 16px; padding-top: 16px; border-top: 1px solid var(--edge-main);\">\n            <div style=\"font-size: 12px; color: var(--text-soft); margin-bottom: 12px; text-transform: uppercase; letter-spacing: 0.05em;\">Coverage Zones</div>\n            <div style=\"display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 12px;\">\n              <button id=\"bulk-zone-copy-mobile\" class=\"bulk-action-btn\">📡<br><span style=\"font-size: 12px;\">Copy Zone</span></button>\n              <button id=\"bulk-zone-paste-mobile\" class=\"bulk-action-btn\">📡<br><span style=\"font-size: 12px;\">Paste Zone</span></button>\n              <button id=\"bulk-zone-toggle-mobile\" class=\"bulk-action-btn\">📡<br><span style=\"font-size: 12px;\">Toggle Zones</span></button>\n            </div>\n          </div>\n          <button id=\"bulk-clear-mobile\" style=\"width: 100%;margin-top: 16px;padding: 14px;background: var(--panel-main);border: 1px solid var(--edge-main);border-radius: 8px;color: var(--text-main);font-size: 16px;cursor: pointer;\" data-lang=\"bulkToolbar.clearSelection\">Clear Selection</button>\n        </div>\n        <div class=\"canvas-hint visible\" id=\"canvas-hint\"></div>\n        <div class=\"legend-container\" id=\"edge-legend\" style=\"display: none;\"><div class=\"legend-title\">Connection Legend</div><button type=\"button\" class=\"legend-close-btn\">✕</button></div>\n        <div class=\"canvas-viewport\" id=\"canvas-viewport\">\n          <svg id=\"map\" viewBox=\"0 0 4000 3000\" style=\"\"></svg>\n        </div>\n        <div class=\"minimap-zoom-wrapper\" id=\"minimap-zoom-wrapper\" style=\"display: block;\">\n          <button type=\"button\" class=\"minimap-close-btn\" id=\"minimap-close-btn\" title=\"Hide map &amp; zoom\">✕</button>\n          <div class=\"minimap-container\" id=\"minimap-container\">\n            <svg id=\"minimap\" viewBox=\"0 0 4000 3000\">\n              <rect class=\"minimap-viewport\" id=\"minimap-viewport\" x=\"0\" y=\"0\" width=\"4000\" height=\"3000\"></rect>\n            </svg>\n          </div>\n          <div class=\"zoom-toolbar\" id=\"zoom-toolbar\">\n            <button id=\"zoom-in-btn\" title=\"Zoom in\">+</button>\n            <div class=\"zoom-level\" id=\"zoom-level\">100%</div>\n            <button id=\"zoom-out-btn\" title=\"Zoom out\">-</button>\n            <div class=\"divider\"></div>\n            <button id=\"zoom-fit-btn\" title=\"Fit to view\">[ ]</button>\n            <button id=\"zoom-reset-btn\" title=\"Reset view\">R</button>\n          </div>\n        </div>\n        <button type=\"button\" id=\"edge-legend-mini\" class=\"legend-mini-btn\" style=\"bottom: 10px; display: none;\" data-lang=\"toolbar.legendMini\">Connection & Zone Legend</button>\n        <button type=\"button\" id=\"minimap-mini\" class=\"legend-mini-btn\" style=\"right: 10px; left: auto; bottom: 10px; display: none;\" data-lang=\"toolbar.minimapMini\">Map</button>\n        <button type=\"button\" id=\"draw-toolbar-mini\" class=\"legend-mini-btn\" style=\"top: 10px; left: 10px; right: auto; display: none;\" data-lang=\"toolbar.drawMini\">Draw</button>\n        <button type=\"button\" id=\"topology-toolbar-mini\" class=\"legend-mini-btn\" style=\"top: 10px; left: auto; right: 40px; display: none;\" data-lang=\"toolbar.topologyMini\">Add Connection</button>\n     </section>\n      <aside class=\"details-panel\" id=\"details-panel\">\n        <div class=\"sidebar-resizer\" id=\"sidebar-resizer\">\n          <svg class=\"resizer-icon\" width=\"6\" height=\"40\" viewBox=\"0 0 6 40\" xmlns=\"http://www.w3.org/2000/svg\">\n            <circle cx=\"3\" cy=\"14\" r=\"2\"></circle>\n            <circle cx=\"3\" cy=\"20\" r=\"2\"></circle>\n            <circle cx=\"3\" cy=\"26\" r=\"2\"></circle>\n          </svg>\n        </div>\n        <div class=\"mobile-footer-resizer\" id=\"mobile-footer-resizer\">\n          <svg class=\"resizer-icon\" width=\"40\" height=\"6\" viewBox=\"0 0 40 6\" xmlns=\"http://www.w3.org/2000/svg\">\n            <circle cx=\"14\" cy=\"3\" r=\"2\"></circle>\n            <circle cx=\"20\" cy=\"3\" r=\"2\"></circle>\n            <circle cx=\"26\" cy=\"3\" r=\"2\"></circle>\n          </svg>\n        </div>\n        <div id=\"node-panel\" style=\"display: none;\">\n          <div class=\"details-name editable-text\" id=\"node-name\">OPNSENSE</div>\n          <div class=\"details-ip editable-text\" id=\"node-ip\">192.168.100.1</div>\n          <div class=\"details-info-row\" id=\"mac-row\">\n            <span style=\"color: var(--text-soft); font-size: 14px; width: 80px;\">MAC:</span>\n            <span class=\"editable-text\" id=\"node-mac\" style=\"flex: 1; cursor: pointer; color: var(--text-main);\">--</span>\n          </div>\n          <div class=\"details-info-row\" id=\"rack-unit-row\">\n            <span style=\"color: var(--text-soft); font-size: 14px; width: 80px;\">Rack Unit:</span>\n            <span class=\"editable-text\" id=\"node-rack\" style=\"flex: 1; cursor: pointer; color: var(--text-main);\">--</span>\n          </div>\n          <div class=\"details-info-row\" id=\"uheight-row\" style=\"display: none;\">\n            <span style=\"color: var(--text-soft); font-size: 14px; width: 80px;\">U Height:</span>\n            <span class=\"editable-text\" id=\"node-uheight\" style=\"flex: 1; cursor: pointer; color: var(--text-main);\">1U</span>\n          </div>\n          <div class=\"details-info-row\" id=\"assigned-rack-row\" style=\"display: flex;\">\n            <span id=\"assigned-rack-label\" style=\"color: var(--text-soft); font-size: 14px; width: 80px;\">Assigned Rack:</span>\n            <select id=\"node-assigned-rack\" style=\"flex: 1; padding: 4px 8px; background: var(--panel); color: var(--text-main); border: 1px solid var(--edge-main); border-radius: 4px; cursor: pointer; font-size: 14px;\"><option value=\"\">None</option></select>\n          </div>\n          <div class=\"details-info-row\" id=\"hosted-on-row\" style=\"display: flex;\">\n            <span style=\"color: var(--text-soft); font-size: 14px;\" data-lang=\"nodePanel.hostedOn\">Hosted On:</span>\n            <select id=\"node-hosted-on\" style=\"margin-left: 8px; padding: 4px 8px; background: var(--panel); color: var(--text-main); border: 1px solid var(--edge-main); border-radius: 4px; cursor: pointer; font-size: 14px;\"><option value=\"\" data-lang=\"ui.options.none\">None</option></select>\n          </div>\n          <div class=\"details-info-row\" id=\"rack-capacity-row\" style=\"display: none;\">\n            <span style=\"color: var(--text-soft); font-size: 14px; width: 80px;\">Rack Capacity:</span>\n            <select id=\"node-rack-capacity\" style=\"flex: 1; padding: 4px 8px; background: var(--panel); color: var(--text-main); border: 1px solid var(--edge-main); border-radius: 4px; cursor: pointer; font-size: 14px;\">\n              <option value=\"42\">42U</option>\n              <option value=\"48\">48U</option>\n              <option value=\"24\">24U</option>\n              <option value=\"12\">12U</option>\n              <option value=\"6\">6U</option>\n            </select>\n          </div>\n          <div class=\"details-info-row\">\n            <span style=\"color: var(--text-soft); font-size: 14px; width: 80px;\">Layer:</span>\n            <select id=\"node-layer\" style=\"flex: 1; padding: 4px 8px; background: var(--panel); color: var(--text-main); border: 1px solid var(--edge-main); border-radius: 4px; cursor: pointer; font-size: 14px;\">\n              <option id=\"node-layer-opt1\" value=\"layer1\">Layer 1</option>\n              <option id=\"node-layer-opt2\" value=\"layer2\">Layer 2</option>\n              <option id=\"node-layer-opt3\" value=\"layer3\">Layer 3</option>\n              <option id=\"node-layer-opt4\" value=\"layer4\">Layer 4</option>\n            </select>\n          </div>\n          <div class=\"details-info-row\" id=\"node-trail-row\">\n            <span style=\"color: var(--text-soft); font-size: 14px;\" data-lang=\"nodePanel.motionTrail\">Motion Trail:</span>\n            <label class=\"toggle-switch\" style=\"margin-left: 8px;\"><input type=\"checkbox\" id=\"node-trail-enabled\" checked><span class=\"toggle-slider\"></span></label>\n          </div>\n          <div class=\"details-role\" id=\"node-role\">WAN</div>\n\t\t  <details id=\"rack-contents-section\" style=\"display: none; margin-top: 12px; margin-bottom: 12px; padding: 10px; background: var(--panel-alt); border-radius: 6px;\" open=\"\">\n            <summary style=\"color: var(--accent); font-weight: 600; font-size: 14px; cursor: pointer;\">Nodes in Rack (<span id=\"rack-contents-count\">0</span>)</summary>\n            <div id=\"rack-contents-list\" style=\"max-height: 200px; overflow-y: auto; margin-top: 8px;\"></div>\n          </details>\n          <details id=\"fov-section\" style=\"display: none; margin-top: 12px; margin-bottom: 12px; padding: 10px; background: var(--panel-alt); border-radius: 6px;\">\n            <summary style=\"color: var(--accent); font-weight: 600; font-size: 14px; cursor: pointer;\">Coverage Zone</summary>\n            <div style=\"padding: 10px 0;\">\n              <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px; gap: 8px;\">\n                <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Preset:</label>\n                <select id=\"fov-preset\" style=\"flex: 1; padding: 4px 8px; background: var(--panel); border: 1px solid var(--edge-main); border-radius: 4px; color: var(--text-main);\">\n                  <option value=\"\">-- Apply Preset --</option>\n                  <option value=\"security-cam\">Security Camera</option>\n                  <option value=\"ptz-cam\">PTZ Camera</option>\n                  <option value=\"motion-detect\">Motion Detector</option>\n                  <option value=\"wifi-strong\">WiFi</option>\n                  <option value=\"wifi-extended\">WiFi Extender</option>\n                  <option value=\"smoke-alarm\">Smoke Alarm</option>\n                  <option value=\"sprinkler-arc\">Sprinkler Arc</option>\n                </select>\n                <button id=\"fov-save-preset\" title=\"Save as preset\" style=\"padding: 4px 8px; background: var(--panel); border: 1px solid var(--edge-main); border-radius: 4px; color: var(--text-main); cursor: pointer;\">💾</button>\n                <button id=\"fov-copy-style\" title=\"Copy zone style\" style=\"padding: 4px 8px; background: var(--panel); border: 1px solid var(--edge-main); border-radius: 4px; color: var(--text-main); cursor: pointer;\">📋</button>\n                <button id=\"fov-paste-style\" title=\"Paste zone style\" style=\"padding: 4px 8px; background: var(--panel); border: 1px solid var(--edge-main); border-radius: 4px; color: var(--text-main); cursor: pointer;\">📥</button>\n              </div>\n              <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Show Zone:</label>\n                <label class=\"toggle-switch\"><input type=\"checkbox\" id=\"fov-enabled\"><span class=\"toggle-slider\"></span></label>\n              </div>\n              <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Angle:</label>\n                <input type=\"range\" id=\"fov-angle\" min=\"10\" max=\"360\" value=\"90\" style=\"flex: 1;\">\n                <span id=\"fov-angle-value\" style=\"min-width: 40px; text-align: right; color: var(--text-main);\">90°</span>\n              </div>\n              <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Distance:</label>\n                <input type=\"range\" id=\"fov-distance\" min=\"50\" max=\"500\" value=\"150\" style=\"flex: 1;\">\n                <span id=\"fov-distance-value\" style=\"min-width: 40px; text-align: right; color: var(--text-main);\">150</span>\n              </div>\n              <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Inner Radius:</label>\n                <input type=\"range\" id=\"fov-inner-radius\" min=\"0\" max=\"200\" value=\"0\" style=\"flex: 1;\">\n                <span id=\"fov-inner-radius-value\" style=\"min-width: 40px; text-align: right; color: var(--text-main);\">0</span>\n              </div>\n              <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Rotation:</label>\n                <input type=\"range\" id=\"fov-rotation\" min=\"0\" max=\"360\" value=\"0\" style=\"flex: 1;\">\n                <span id=\"fov-rotation-value\" style=\"min-width: 40px; text-align: right; color: var(--text-main);\">0°</span>\n              </div>\n              <div style=\"border-top: 1px solid var(--edge-main); margin-top: 10px; padding-top: 10px;\">\n                <div style=\"font-size: 12px; color: var(--text-soft); margin-bottom: 8px; text-transform: uppercase; letter-spacing: 0.05em;\">Fill</div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Color:</label>\n                  <input type=\"color\" id=\"fov-color\" value=\"#f59e0b\" style=\"width: 50px; height: 30px; border: 1px solid var(--edge-main); border-radius: 4px; cursor: pointer;\">\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Opacity:</label>\n                  <input type=\"range\" id=\"fov-opacity\" min=\"5\" max=\"80\" value=\"20\" style=\"flex: 1;\">\n                  <span id=\"fov-opacity-value\" style=\"min-width: 40px; text-align: right; color: var(--text-main);\">20%</span>\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Gradient:</label>\n                  <label class=\"toggle-switch\"><input type=\"checkbox\" id=\"fov-gradient\"><span class=\"toggle-slider\"></span></label>\n                  <span style=\"color: var(--text-soft); font-size: 12px; margin-left: 8px;\">Fade toward edge</span>\n                </div>\n              </div>\n              <div style=\"border-top: 1px solid var(--edge-main); margin-top: 10px; padding-top: 10px;\">\n                <div style=\"font-size: 12px; color: var(--text-soft); margin-bottom: 8px; text-transform: uppercase; letter-spacing: 0.05em;\">Border</div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Color:</label>\n                  <input type=\"color\" id=\"fov-border-color\" value=\"#f59e0b\" style=\"width: 50px; height: 30px; border: 1px solid var(--edge-main); border-radius: 4px; cursor: pointer;\">\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Width:</label>\n                  <input type=\"range\" id=\"fov-border-width\" min=\"0\" max=\"10\" value=\"2\" style=\"flex: 1;\">\n                  <span id=\"fov-border-width-value\" style=\"min-width: 40px; text-align: right; color: var(--text-main);\">2</span>\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Style:</label>\n                  <select id=\"fov-border-style\" style=\"flex: 1; padding: 4px 8px; background: var(--panel); border: 1px solid var(--edge-main); border-radius: 4px; color: var(--text-main);\">\n                    <option value=\"solid\">Solid</option>\n                    <option value=\"dashed\">Dashed</option>\n                    <option value=\"dotted\">Dotted</option>\n                  </select>\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Opacity:</label>\n                  <input type=\"range\" id=\"fov-border-opacity\" min=\"0\" max=\"100\" value=\"100\" style=\"flex: 1;\">\n                  <span id=\"fov-border-opacity-value\" style=\"min-width: 40px; text-align: right; color: var(--text-main);\">100%</span>\n                </div>\n              </div>\n              <div style=\"border-top: 1px solid var(--edge-main); margin-top: 10px; padding-top: 10px;\">\n                <div style=\"font-size: 12px; color: var(--text-soft); margin-bottom: 8px; text-transform: uppercase; letter-spacing: 0.05em;\">Label</div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Text:</label>\n                  <input type=\"text\" id=\"fov-label\" placeholder=\"e.g. Detection Zone\" style=\"flex: 1; padding: 4px 8px; background: var(--panel); border: 1px solid var(--edge-main); border-radius: 4px; color: var(--text-main);\">\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Position:</label>\n                  <select id=\"fov-label-position\" style=\"flex: 1; padding: 4px 8px; background: var(--panel); border: 1px solid var(--edge-main); border-radius: 4px; color: var(--text-main);\">\n                    <option value=\"center\">Center</option>\n                    <option value=\"edge\">Edge</option>\n                    <option value=\"outside\">Outside</option>\n                  </select>\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Font Size:</label>\n                  <input type=\"range\" id=\"fov-label-size\" min=\"8\" max=\"32\" value=\"14\" style=\"flex: 1;\">\n                  <span id=\"fov-label-size-value\" style=\"min-width: 40px; text-align: right; color: var(--text-main);\">14px</span>\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Font Color:</label>\n                  <input type=\"color\" id=\"fov-label-color\" value=\"#ffffff\" style=\"width: 50px; height: 30px; border: 1px solid var(--edge-main); border-radius: 4px; cursor: pointer;\">\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Bold:</label>\n                  <label class=\"toggle-switch\"><input type=\"checkbox\" id=\"fov-label-bold\"><span class=\"toggle-slider\"></span></label>\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Background:</label>\n                  <label class=\"toggle-switch\"><input type=\"checkbox\" id=\"fov-label-bg\"><span class=\"toggle-slider\"></span></label>\n                  <input type=\"color\" id=\"fov-label-bg-color\" value=\"#000000\" style=\"width: 50px; height: 30px; border: 1px solid var(--edge-main); border-radius: 4px; cursor: pointer; margin-left: 8px;\">\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Offset X:</label>\n                  <input type=\"range\" id=\"fov-label-offset-x\" min=\"-200\" max=\"200\" value=\"0\" style=\"flex: 1;\">\n                  <span id=\"fov-label-offset-x-value\" style=\"min-width: 40px; text-align: right; color: var(--text-main);\">0</span>\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Offset Y:</label>\n                  <input type=\"range\" id=\"fov-label-offset-y\" min=\"-200\" max=\"200\" value=\"0\" style=\"flex: 1;\">\n                  <span id=\"fov-label-offset-y-value\" style=\"min-width: 40px; text-align: right; color: var(--text-main);\">0</span>\n                </div>\n              </div>\n              <div style=\"border-top: 1px solid var(--edge-main); margin-top: 10px; padding-top: 10px;\">\n                <div style=\"font-size: 12px; color: var(--text-soft); margin-bottom: 8px; text-transform: uppercase; letter-spacing: 0.05em;\">Animation</div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Animate:</label>\n                  <label class=\"toggle-switch\"><input type=\"checkbox\" id=\"fov-animate\"><span class=\"toggle-slider\"></span></label>\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Type:</label>\n                  <select id=\"fov-animation-type\" style=\"flex: 1; padding: 4px 8px; background: var(--panel); border: 1px solid var(--edge-main); border-radius: 4px; color: var(--text-main);\">\n                    <option value=\"sweep\">Sweep (Pan)</option>\n                    <option value=\"pulse\">Pulse (Breathe)</option>\n                    <option value=\"rings\">Rings (Emanate)</option>\n                    <option value=\"spin\">Spin (Rotate)</option>\n                  </select>\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Sweep:</label>\n                  <input type=\"range\" id=\"fov-sweep\" min=\"30\" max=\"360\" value=\"120\" style=\"flex: 1;\">\n                  <span id=\"fov-sweep-value\" style=\"min-width: 40px; text-align: right; color: var(--text-main);\">120°</span>\n                </div>\n                <div class=\"style-row\" style=\"display: flex; align-items: center; margin-bottom: 8px;\">\n                  <label style=\"color: var(--text-soft); font-size: 14px; min-width: 100px;\">Speed:</label>\n                  <input type=\"range\" id=\"fov-speed\" min=\"1\" max=\"60\" value=\"4\" style=\"flex: 1;\">\n                  <span id=\"fov-speed-value\" style=\"min-width: 40px; text-align: right; color: var(--text-main);\">4s</span>\n                </div>\n              </div>\n            </div>\n          </details>\n          <details id=\"node-connections-section\" style=\"margin-top: 12px; margin-bottom: 12px; padding: 10px; background: var(--panel-alt); border-radius: 6px;\">\n            <summary style=\"color: var(--accent); font-weight: 600; font-size: 14px; cursor: pointer;\">Connections (<span id=\"node-connections-count\">0</span>)</summary>\n            <div id=\"node-connections-list\" style=\"max-height: 200px; overflow-y: auto; margin-top: 8px;\"></div>\n          </details>\n          <div class=\"badge-row\" id=\"node-tags\"></div>\n          <div style=\"margin-top: 10px; display: flex; gap: 8px\">\n            <input type=\"text\" id=\"new-tag-input\" placeholder=\"Add tag...\" style=\"flex: 1;padding: 8px 12px;background: var(--panel);border: 1px solid var(--edge-main);border-radius: 6px;color: var(--text-main);font-size: clamp(14px, 1.6vw, 18px);\">\n            <button id=\"add-tag-btn\" style=\"padding: 8px 16px;background: var(--accent);color: var(--bg);border: none;border-radius: 6px;cursor: pointer;font-size: clamp(14px, 1.6vw, 18px);font-weight: 600;\" data-lang=\"ui.buttons.add\">Add</button>\n          </div>\n          <button class=\"pick-icon-btn\" id=\"pick-tag-icon-btn\" data-lang=\"ui.labels.addIconTag\">Add Web Icon Tag</button>\n          <div class=\"section-label\" data-lang=\"ui.labels.size\">Size</div>\n          <div class=\"size-controls\">\n            <label data-lang=\"ui.labels.size\">Size:</label>\n            <input type=\"range\" id=\"size-slider\" min=\"20\" max=\"200\" value=\"55\">\n            <span id=\"size-value\">127</span>\n            <button id=\"reset-size\" data-lang=\"ui.buttons.reset\">Reset</button>\n          </div>\n          <div class=\"size-controls\">\n            <label data-lang=\"ui.labels.rotation\">Rotation:</label>\n            <input type=\"range\" id=\"rotation-slider\" min=\"-360\" max=\"360\" value=\"0\">\n            <input type=\"number\" id=\"rotation-value\" value=\"0\" style=\"width: 60px; padding: 4px; background: var(--panel); border: 1px solid var(--edge-main); border-radius: 4px; color: var(--text-main); text-align: center;\">\n            <span style=\"color: var(--text-soft);\">°</span>\n            <button id=\"reset-rotation\" data-lang=\"ui.buttons.reset\">Reset</button>\n          </div>\n          <details class=\"style-section\">\n            <summary data-lang=\"nodePanel.styling\">Styling</summary>\n            <div class=\"style-content\">\n              <div class=\"style-row\">\n                <label data-lang=\"nodePanel.screen\">Screen:</label>\n                <select id=\"style-scope\">\n                  <option value=\"all\" data-lang=\"ui.tabs.all\">All</option>\n                  <option value=\"desktop\" data-lang=\"help.tabs.desktop\">Desktop</option>\n                  <option value=\"tablet\" data-lang=\"nodePanel.tablet\">Tablet</option>\n                  <option value=\"mobile\" data-lang=\"help.tabs.mobile\">Mobile</option>\n                  <option value=\"fold\" data-lang=\"nodePanel.fold\">Fold</option>\n                </select>\n              </div>\n              <div class=\"style-row\">\n                <label data-lang=\"ui.labels.category\">Category:</label>\n                <select id=\"shape-category-select\">\n                  <option value=\"basic\" data-lang=\"shapes.categories.basic\">Basic Shapes</option>\n                  <option value=\"computers\" data-lang=\"shapes.categories.computers\">Computers & Devices</option>\n                  <option value=\"network\" data-lang=\"shapes.categories.network\">Network Equipment</option>\n                  <option value=\"cloud\" data-lang=\"shapes.categories.cloud\">Cloud & Services</option>\n                  <option value=\"security\" data-lang=\"shapes.categories.security\">Security & Monitoring</option>\n                  <option value=\"smarthome\" data-lang=\"shapes.categories.smarthome\">Smart Home</option>\n                  <option value=\"sports\" data-lang=\"shapes.categories.sports\">Sports</option>\n                  <option value=\"rack\" data-lang=\"shapes.categories.rack\">Rack Equipment</option>\n                </select>\n              </div>\n              <div class=\"style-row\">\n                <label data-lang=\"ui.labels.shape\">Shape:</label>\n                <select id=\"shape-select\"></select>\n              </div>\n              <div class=\"style-row\">\n               <button style=\"width:100%\" class=\"pick-icon-btn\" id=\"pick-shape-icon-btn\" data-lang=\"nodePanel.searchWebIcons\">Or Search Web Icons</button>\n              </div>\t\n              <div class=\"style-row\" style=\"display:unset !important;\">\n               \n              </div>\t\t\t\t  \n              <div class=\"style-row\">\n                <label data-lang=\"nodePanel.fillColor\">Fill Color:</label>\n                <input type=\"color\" id=\"circle-color\" value=\"#1e293b\">\n              </div>\n              <div class=\"style-row\">\n                <label data-lang=\"nodePanel.borderColor\">Border Color:</label>\n                <input type=\"color\" id=\"circle-border\" value=\"#475569\">\n              </div>\n              <div class=\"style-row\">\n                <label data-lang=\"ui.labels.titleColor\">Title Color:</label>\n                <input type=\"color\" id=\"title-color\" value=\"#e2e8f0\">\n              </div>\n              <div class=\"style-row\">\n                <label data-lang=\"nodePanel.titleFont\">Title Font:</label>\n                <select id=\"title-font\">\n                  <option value=\"system-ui, sans-serif\" data-lang=\"nodePanel.systemUI\">System UI</option>\n                  <option value=\"monospace\" data-lang=\"ui.fonts.monospace\">Monospace</option>\n                  <option value=\"serif\" data-lang=\"nodePanel.serif\">Serif</option>\n                  <option value=\"cursive\" data-lang=\"nodePanel.cursive\">Cursive</option>\n                  <option value=\"'Courier New', monospace\" data-lang=\"nodePanel.courier\">Courier</option>\n                  <option value=\"Arial, sans-serif\" data-lang=\"ui.fonts.arial\">Arial</option>\n                  <option value=\"'Times New Roman', serif\" data-lang=\"nodePanel.times\">Times</option>\n                </select>\n              </div>\n              <div class=\"style-row\">\n                <label data-lang=\"ui.labels.titleSize\">Title Size:</label>\n                <input type=\"number\" id=\"title-size\" min=\"8\" max=\"100\" value=\"18\">\n              </div>\n              <div class=\"style-row\">\n                <label data-lang=\"nodePanel.subColor\">Sub Color:</label>\n                <input type=\"color\" id=\"sub-color\" value=\"#94a3b8\">\n              </div>\n              <div class=\"style-row\">\n                <label data-lang=\"nodePanel.subFont\">Sub Font:</label>\n                <select id=\"sub-font\">\n                  <option value=\"system-ui, sans-serif\" data-lang=\"nodePanel.systemUI\">System UI</option>\n                  <option value=\"monospace\" data-lang=\"ui.fonts.monospace\">Monospace</option>\n                  <option value=\"serif\" data-lang=\"nodePanel.serif\">Serif</option>\n                  <option value=\"cursive\" data-lang=\"nodePanel.cursive\">Cursive</option>\n                  <option value=\"'Courier New', monospace\" data-lang=\"nodePanel.courier\">Courier</option>\n                  <option value=\"Arial, sans-serif\" data-lang=\"ui.fonts.arial\">Arial</option>\n                  <option value=\"'Times New Roman', serif\" data-lang=\"nodePanel.times\">Times</option>\n                </select>\n              </div>\n              <div class=\"style-row\">\n                <label data-lang=\"nodePanel.subSize\">Sub Size:</label>\n                <input type=\"number\" id=\"sub-size\" min=\"6\" max=\"80\" value=\"13\">\n              </div>\n              \n                <div class=\"style-row\">\n                  <label data-lang=\"nodePanel.nameY\">Name Y:</label>\n                  <input type=\"number\" id=\"title-offset-y\" min=\"-100\" max=\"100\" value=\"0\" style=\"width: 60px\">\n                </div>\n                <div class=\"style-row\">\n                  <label data-lang=\"nodePanel.nameX\">Name X:</label>\n                  <input type=\"number\" id=\"title-offset-x\" min=\"-100\" max=\"100\" value=\"0\" style=\"width: 60px\">\n                </div>\n                <div class=\"style-row\">\n                  <label data-lang=\"nodePanel.ipY\">IP Y:</label>\n                  <input type=\"number\" id=\"sub-offset-y\" min=\"-100\" max=\"100\" value=\"0\" style=\"width: 60px\">\n                </div>\n                <div class=\"style-row\">\n                  <label data-lang=\"nodePanel.ipX\">IP X:</label>\n                  <input type=\"number\" id=\"sub-offset-x\" min=\"-100\" max=\"100\" value=\"0\" style=\"width: 60px\">\n                </div>\n                <div class=\"style-row\">\n                  <label data-lang=\"nodePanel.pingX\">Ping X:</label>\n                  <input type=\"number\" id=\"ping-offset-x\" min=\"-200\" max=\"200\" value=\"0\" style=\"width: 60px;\">\n                </div>\n                <div class=\"style-row\">\n                  <label data-lang=\"nodePanel.pingY\">Ping Y:</label>\n                  <input type=\"number\" id=\"ping-offset-y\" min=\"-200\" max=\"200\" value=\"0\" style=\"width: 60px;\">\n                </div>\n            \n              <button id=\"reset-styles\" style=\"width: 100%;margin-top: 10px;padding: 8px;background: var(--panel);color: var(--text-main);border: 1px solid var(--edge-main);border-radius: 6px;cursor: pointer;font-size: clamp(13px, 1.5vw, 17px);\" data-lang=\"nodePanel.resetStyles\">Reset Styles</button>\n            </div>\n          </details>\n          <details id=\"node-ping-section\" class=\"style-section\">\n            <summary data-lang=\"nodePanel.pingStatusMonitoring\">Ping / Status Monitoring</summary>\n            <div class=\"style-content\">\n              <div style=\"display: flex; align-items: center; gap: 8px; margin-bottom: 12px;\">\n              <label class=\"toggle-switch\"><input type=\"checkbox\" id=\"node-pingable\"><span class=\"toggle-slider\"></span></label>\n              <span style=\"font-size: 14px;\" data-lang=\"nodePanel.enableStatusCheck\">Enable status check for this node</span>\n              </div>\n              <div id=\"node-ping-options\" style=\"display: block;\">\n                <div class=\"style-row\">\n                  <label data-lang=\"nodePanel.protocol\">Protocol:</label>\n                  <select id=\"node-ping-protocol\">\n                    <option value=\"http\" data-lang=\"nodePanel.httpPort80\">HTTP (port 80)</option>\n                    <option value=\"https\" data-lang=\"nodePanel.httpsPort443\">HTTPS (port 443)</option>\n                    <option value=\"custom\" data-lang=\"nodePanel.customUrl\">Custom URL</option>\n                  </select>\n                </div>\n                <div id=\"node-custom-url-container\" style=\"display: block; margin-top: 8px;\">\n                  <label style=\"display: block; margin-bottom: 4px; font-size: 13px;\" data-lang=\"nodePanel.customUrlLabel\">Custom URL:</label>\n                  <input type=\"text\" id=\"node-custom-url\" placeholder=\"e.g. http://192.168.1.1:8080\" style=\"width: 100%;padding: 8px 12px;background: var(--panel);border: 1px solid var(--edge-main);border-radius: 6px;color: var(--text-main);font-size: 14px;\">\n                </div>\n                <div class=\"style-row\" style=\"margin-top: 8px;\">\n                  <label data-lang=\"nodePanel.timeoutMs\">Timeout (ms):</label>\n                  <input type=\"number\" id=\"node-ping-timeout\" value=\"3000\" min=\"1000\" max=\"10000\" step=\"500\" style=\"width: 100px;\">\n                </div>\n                <div style=\"margin-top: 12px; padding: 10px; background: var(--panel); border-radius: 6px; border: 1px solid var(--edge-main);\">\n                  <div style=\"font-size: 12px; color: var(--text-soft); margin-bottom: 8px;\" data-lang=\"nodePanel.currentStatus\">Current Status:</div>\n                  <div id=\"node-ping-status\" style=\"font-size: 14px; font-weight: 600; color: var(--accent);\">● Online</div>\n                  <div id=\"node-ping-last-check\" style=\"font-size: 11px; color: var(--text-soft); margin-top: 4px;\" data-lang=\"nodePanel.lastChecked\">Last checked: 2:25:19 PM</div>\n                </div>\n                <button id=\"check-ping-now\" style=\"width: 100%;margin-top: 10px;padding: 8px;background: var(--accent);color: var(--bg);border: none;border-radius: 6px;cursor: pointer;font-size: 14px;font-weight: 600;\" data-lang=\"nodePanel.checkStatusNow\">Check Status Now</button>\n              </div>\n            </div>\n          </details>\n          <div class=\"section-label\" data-lang=\"ui.sections.notes\">Notes</div>\n          <div class=\"notes-feed\" id=\"node-notes\"></div>\n          <button id=\"add-note-btn\" style=\"margin-top: 10px;padding: 8px 16px;background: var(--accent);color: var(--bg);border: none;border-radius: 6px;cursor: pointer;font-size: clamp(14px, 1.6vw, 18px);font-weight: 600;width: 100%;\" data-lang=\"ui.buttons.addNote\">+ Add Note</button>\n          <button id=\"delete-node-btn\" style=\"margin-top: 15px;padding: 10px 16px;background: var(--danger);color: rgb(255, 255, 255);border: none;border-radius: 6px;cursor: pointer;font-size: clamp(14px, 1.6vw, 18px);font-weight: 600;width: 100%;\" data-lang=\"ui.buttons.deleteNode\">Delete Node</button>\n        </div>\n        <div id=\"edge-panel\" style=\"display: none\">\n          <div class=\"details-name\" id=\"edge-title\" data-lang=\"edgePanel.customLine\">Custom line</div>\n          <div class=\"style-row\" style=\"margin-top: 10px\">\n            <label data-lang=\"ui.labels.width\">Width:</label>\n            <input type=\"number\" id=\"edge-width\" min=\"1\" max=\"20\" value=\"4\">\n          </div>\n          <div class=\"style-row\">\n            <label data-lang=\"ui.labels.color\">Color:</label>\n            <input type=\"color\" id=\"edge-color\" value=\"#475569\">\n          </div>\n          <div class=\"style-row\">\n            <label data-lang=\"ui.labels.style\">Line Style:</label>\n            <select id=\"edge-line-style\">\n              <option value=\"solid\" data-lang=\"ui.options.solid\">Solid</option>\n              <option value=\"dashed\" data-lang=\"ui.options.dashed\">Dashed</option>\n              <option value=\"dotted\" data-lang=\"ui.options.dotted\">Dotted</option>\n              <option value=\"wall\" data-lang=\"drawToolbar.wall\">Wall</option>\n            </select>\n          </div>\n          <div class=\"style-row\">\n            <label data-lang=\"ui.labels.routing\">Routing:</label>\n            <select id=\"edge-routing\">\n              <option value=\"orthogonal\" data-lang=\"ui.options.orthogonal\">Orthogonal (90°)</option>\n              <option value=\"curved\" data-lang=\"ui.options.curved\">Curved</option>\n              <option value=\"straight\" data-lang=\"ui.options.straight\">Straight</option>\n            </select>\n          </div>\n          <div class=\"style-row\" id=\"waypoints-row\" style=\"display: none;\">\n            <label id=\"waypoints-label\" data-lang=\"ui.labels.waypoints\">Waypoints</label>\n            <button id=\"clear-waypoints-btn\" style=\"padding: 6px 12px;background: var(--panel);border: 1px solid var(--edge-main);border-radius: 6px;cursor: pointer;font-size: 14px;color: var(--text-main);\" data-lang=\"ui.buttons.clearAll\">Clear All</button>\n          </div>\n          <div class=\"style-row\">\n            <label data-lang=\"ui.labels.direction\">Direction:</label>\n            <select id=\"edge-direction\">\n              <option value=\"none\" data-lang=\"topologyToolbar.noArrows\">No arrows</option>\n              <option value=\"forward\" data-lang=\"topologyToolbar.forward\">→ Forward</option>\n              <option value=\"backward\" data-lang=\"topologyToolbar.backward\">← Backward</option>\n              <option value=\"both\" data-lang=\"topologyToolbar.bidirectional\">↔ Bidirectional</option>\n            </select>\n          </div>\n          <div class=\"style-row\">\n            <label data-lang=\"edgePanel.animate\">Animate:</label>\n            <label class=\"toggle-switch\"><input type=\"checkbox\" id=\"edge-animate\"><span class=\"toggle-slider\"></span></label>\n          </div>\n          <div class=\"style-row\">\n            <label data-lang=\"ui.labels.style\">Style:</label>\n            <select id=\"edge-animation-style\">\n              <option value=\"\" data-lang=\"ui.options.default\">Default</option>\n              <option value=\"arrows\" data-lang=\"ui.options.flowingArrows\">Flowing Arrows</option>\n              <option value=\"dots\" data-lang=\"ui.options.dotArrows\">Dot Arrows</option>\n            </select>\n          </div>\n          <div class=\"style-row\">\n            <label data-lang=\"ui.labels.animationSpeed\">Speed:</label>\n            <select id=\"edge-animation-speed\">\n              <option value=\"\" data-lang=\"ui.options.default\">Default</option>\n              <option value=\"0.5\" data-lang=\"ui.options.veryFast\">Very Fast</option>\n              <option value=\"1\" data-lang=\"ui.options.fast\">Fast</option>\n              <option value=\"1.5\" data-lang=\"ui.options.normal\">Normal</option>\n              <option value=\"2.5\" data-lang=\"ui.options.slow\">Slow</option>\n              <option value=\"4\" data-lang=\"ui.options.verySlow\">Very Slow</option>\n            </select>\n          </div>\n          <div class=\"style-row\" id=\"edge-port-fields\">\n            <label data-lang=\"ui.labels.fromPort\">From Port:</label>\n            <input type=\"text\" id=\"edge-from-port\" placeholder=\"e.g. eth0, gi0/1\" data-lang=\"ui.placeholders.fromPort\" data-lang-attr=\"placeholder\" style=\"flex: 1;padding: 6px 10px;background: var(--panel);border: 1px solid var(--edge-main);border-radius: 4px;color: var(--text-main);font-size: 14px;\">\n          </div>\n          <div class=\"style-row\" id=\"edge-port-fields-to\">\n            <label data-lang=\"ui.labels.toPort\">To Port:</label>\n            <input type=\"text\" id=\"edge-to-port\" placeholder=\"e.g. eth1, gi0/2\" data-lang=\"ui.placeholders.toPort\" data-lang-attr=\"placeholder\" style=\"flex: 1;padding: 6px 10px;background: var(--panel);border: 1px solid var(--edge-main);border-radius: 4px;color: var(--text-main);font-size: 14px;\">\n          </div>\n          <div class=\"section-label\" data-lang=\"ui.labels.lineNotes\">Line Notes</div>\n          <div class=\"notes-feed\" id=\"edge-notes\"></div>\n          <button id=\"add-edge-note\" style=\"margin-top: 10px;padding: 8px 16px;background: var(--accent);color: var(--bg);border: none;border-radius: 6px;cursor: pointer;font-size: clamp(14px, 1.6vw, 18px);font-weight: 600;width: 100%;\" data-lang=\"ui.buttons.addNote\">+ Add Note</button>\n          <button id=\"delete-edge\" style=\"margin-top: 15px;padding: 10px 16px;background: var(--danger);color: #fff;border: none;border-radius: 6px;cursor: pointer;font-size: clamp(14px, 1.6vw, 18px);font-weight: 600;\" data-lang=\"ui.buttons.delete\">Delete Line</button>\n        </div>\n        <div id=\"rect-panel\" style=\"display: none\">\n          <div class=\"details-name\" id=\"rect-title\" data-lang=\"rectPanel.zone\">Zone</div>\n          <div class=\"style-row\" id=\"rect-fill-row\" style=\"margin-top: 10px\">\n            <label data-lang=\"rectPanel.fillColor\">Fill Color:</label>\n            <input type=\"color\" id=\"rect-color\" value=\"#f97316\">\n          </div>\n          <div class=\"style-row\" id=\"rect-opacity-row\">\n            <label data-lang=\"rectPanel.fillOpacity\">Fill Opacity:</label>\n            <input type=\"range\" id=\"rect-fill-opacity\" min=\"0\" max=\"100\" value=\"30\" style=\"flex: 1;\">\n            <span id=\"rect-fill-opacity-value\" style=\"min-width: 40px; text-align: right; color: var(--text-soft);\">30%</span>\n          </div>\n          <div class=\"style-row\">\n            <label data-lang=\"rectPanel.borderColor\">Border Color:</label>\n            <input type=\"color\" id=\"rect-border-color\" value=\"#f97316\">\n          </div>\n          <div class=\"style-row\">\n            <label data-lang=\"rectPanel.borderWidth\">Border Width:</label>\n            <input type=\"number\" id=\"rect-border-width\" min=\"0\" max=\"20\" value=\"2\">\n          </div>\n          <div class=\"style-row\">\n            <label data-lang=\"ui.labels.style\">Style:</label>\n            <select id=\"rect-style-select\">\n              <option value=\"filled\" data-lang=\"drawToolbar.filled\">Filled</option>\n              <option value=\"outlined\" data-lang=\"drawToolbar.outlined\">Outlined</option>\n            </select>\n          </div>\n\t\t  <div class=\"style-row\">\n            <label data-lang=\"rectPanel.lineStyle\">Line Style:</label>\n            <select id=\"rect-line-style\">\n              <option value=\"solid\" data-lang=\"ui.options.solid\">Solid</option>\n              <option value=\"dashed\" data-lang=\"ui.options.dashed\">Dashed</option>\n              <option value=\"dotted\" data-lang=\"ui.options.dotted\">Dotted</option>\n              <option value=\"wall\" data-lang=\"drawToolbar.wall\">Wall</option>\n            </select>\n          </div>\n          <div class=\"style-row\">\n            <label data-lang=\"ui.labels.rotation\">Rotation:</label>\n            <input type=\"range\" id=\"rect-rotation\" min=\"-360\" max=\"360\" value=\"0\" style=\"flex: 1;\">\n            <input type=\"number\" id=\"rect-rotation-value\" value=\"0\" style=\"width: 60px; padding: 4px; background: var(--panel); border: 1px solid var(--edge-main); border-radius: 4px; color: var(--text-main); text-align: center;\">\n            <span style=\"color: var(--text-soft);\">°</span>\n          </div>\n          <div class=\"section-label\" data-lang=\"ui.labels.zoneNotes\">Zone Notes</div>\n          <div class=\"notes-feed\" id=\"rect-notes\"></div>\n          <button id=\"add-rect-note\" style=\"margin-top: 10px;padding: 8px 16px;background: var(--accent);color: var(--bg);border: none;border-radius: 6px;cursor: pointer;font-size: clamp(14px, 1.6vw, 18px);font-weight: 600;width: 100%;\" data-lang=\"ui.buttons.addNote\">+ Add Note</button>\n          <button id=\"delete-rect\" style=\"margin-top: 15px;padding: 10px 16px;background: var(--danger);color: #fff;border: none;border-radius: 6px;cursor: pointer;font-size: clamp(14px, 1.6vw, 18px);font-weight: 600;\" data-lang=\"ui.buttons.delete\">Delete Zone</button>\n        </div>\n        <div id=\"text-panel\" style=\"display: none\">\n          <div class=\"details-name\" id=\"text-title\" data-lang=\"textPanel.textElement\">Text Element</div>\n          <div class=\"style-row\" style=\"margin-top: 10px\">\n            <label data-lang=\"textPanel.content\">Content:</label>\n            <textarea id=\"text-content\" placeholder=\"Enter text...\" data-lang=\"ui.placeholders.enterText\" data-lang-attr=\"placeholder\" style=\"width: 100%;padding: 8px 12px;background: var(--panel);border: 1px solid var(--edge-main);border-radius: 6px;color: var(--text-main);font-size: 14px;min-height: 80px;resize: vertical;font-family: inherit;\"></textarea>\n          </div>\n          <div class=\"style-row\">\n            <label data-lang=\"textPanel.fontSizeLabel\">Font Size:</label>\n            <input type=\"number\" id=\"text-font-size\" min=\"8\" max=\"200\" value=\"18\" style=\"flex: 1;padding: 6px 10px;background: var(--panel);border: 1px solid var(--edge-main);border-radius: 4px;color: var(--text-main);\">\n          </div>\n          <div class=\"style-row\">\n            <label data-lang=\"textPanel.colorLabel\">Color:</label>\n            <input type=\"color\" id=\"text-color\" value=\"#e2e8f0\">\n          </div>\n          <div class=\"style-row\">\n            <label data-lang=\"textPanel.fontWeight\">Font Weight:</label>\n            <select id=\"text-font-weight\" style=\"flex: 1;padding: 6px 10px;background: var(--panel);border: 1px solid var(--edge-main);border-radius: 4px;color: var(--text-main);cursor: pointer;\">\n              <option value=\"normal\" data-lang=\"ui.options.normal\">Normal</option>\n              <option value=\"bold\" data-lang=\"textPanel.bold\">Bold</option>\n              <option value=\"600\" data-lang=\"textPanel.semiBold\">Semi-Bold</option>\n              <option value=\"300\" data-lang=\"textPanel.light\">Light</option>\n            </select>\n          </div>\n          <div class=\"style-row\">\n            <label data-lang=\"textPanel.fontStyle\">Font Style:</label>\n            <select id=\"text-font-style\" style=\"flex: 1;padding: 6px 10px;background: var(--panel);border: 1px solid var(--edge-main);border-radius: 4px;color: var(--text-main);cursor: pointer;\">\n              <option value=\"normal\" data-lang=\"ui.options.normal\">Normal</option>\n              <option value=\"italic\" data-lang=\"textPanel.italic\">Italic</option>\n            </select>\n          </div>\n          <div class=\"style-row\">\n            <label data-lang=\"textPanel.textAlign\">Text Align:</label>\n            <select id=\"text-align\" style=\"flex: 1;padding: 6px 10px;background: var(--panel);border: 1px solid var(--edge-main);border-radius: 4px;color: var(--text-main);cursor: pointer;\">\n              <option value=\"start\" data-lang=\"ui.options.left\">Left</option>\n              <option value=\"middle\" data-lang=\"ui.options.center\">Center</option>\n              <option value=\"end\" data-lang=\"ui.options.right\">Right</option>\n            </select>\n          </div>\n          <div class=\"style-row\">\n            <label data-lang=\"textPanel.textDecoration\">Text Decoration:</label>\n            <select id=\"text-decoration\" style=\"flex: 1;padding: 6px 10px;background: var(--panel);border: 1px solid var(--edge-main);border-radius: 4px;color: var(--text-main);cursor: pointer;\">\n              <option value=\"none\" data-lang=\"ui.options.none\">None</option>\n              <option value=\"underline\" data-lang=\"textPanel.underline\">Underline</option>\n              <option value=\"line-through\" data-lang=\"textPanel.strikeThrough\">Strike Through</option>\n            </select>\n          </div>\n          <div class=\"style-row\">\n            <label data-lang=\"ui.labels.background\">Background:</label>\n            <input type=\"color\" id=\"text-bg-color\" value=\"#000000\">\n            <div style=\"margin-left: 10px; display: flex; align-items: center; gap: 6px;\">\n            <label class=\"toggle-switch\"><input type=\"checkbox\" id=\"text-bg-enabled\"><span class=\"toggle-slider\"></span></label>\n            <span style=\"font-size: 13px;\" data-lang=\"textPanel.enable\">Enable</span>\n            </div>\n          </div>\n          <div class=\"style-row\">\n            <label data-lang=\"ui.labels.opacity\">Opacity:</label>\n            <input type=\"range\" id=\"text-opacity\" min=\"0\" max=\"1\" step=\"0.1\" value=\"1\" style=\"flex: 1\">\n            <span id=\"text-opacity-val\" style=\"min-width: 35px; text-align: right\">100%</span>\n          </div>\n          <div class=\"style-row\">\n            <label data-lang=\"ui.labels.rotation\">Rotation:</label>\n            <input type=\"range\" id=\"text-rotation\" min=\"-360\" max=\"360\" value=\"0\" style=\"flex: 1\">\n            <input type=\"number\" id=\"text-rotation-val\" value=\"0\" style=\"width: 60px; padding: 4px; background: var(--panel); border: 1px solid var(--edge-main); border-radius: 4px; color: var(--text-main); text-align: center;\">\n            <span style=\"color: var(--text-soft);\">°</span>\n          </div>\n          <button id=\"delete-text\" style=\"margin-top: 15px;padding: 10px 16px;background: var(--danger);color: #fff;border: none;border-radius: 6px;cursor: pointer;font-size: clamp(14px, 1.6vw, 18px);font-weight: 600;width: 100%;\" data-lang=\"textPanel.deleteText\">Delete Text</button>\n        </div>\n        <div id=\"image-panel\" style=\"display: none\">\n          <div class=\"details-name editable-text\" id=\"image-title\">Image</div>\n          <div class=\"style-row\" style=\"margin-top: 10px\">\n            <label data-lang=\"images.opacity\">Opacity:</label>\n            <input type=\"range\" id=\"image-panel-opacity\" min=\"10\" max=\"100\" value=\"100\" style=\"flex: 1\">\n            <span id=\"image-panel-opacity-val\" style=\"min-width: 40px; text-align: right\">100%</span>\n          </div>\n          <div class=\"style-row\">\n            <label data-lang=\"images.size\">Size:</label>\n            <input type=\"range\" id=\"image-panel-size\" min=\"20\" max=\"200\" value=\"100\" style=\"flex: 1\">\n            <span id=\"image-panel-size-val\" style=\"min-width: 40px; text-align: right\">100%</span>\n          </div>\n          <div class=\"style-row\">\n            <label data-lang=\"images.border\">Border:</label>\n            <select id=\"image-panel-border\" style=\"flex: 1;padding: 6px 10px;background: var(--panel);border: 1px solid var(--edge-main);border-radius: 4px;color: var(--text-main);cursor: pointer;\">\n              <option value=\"none\" data-lang=\"images.none\">None</option>\n              <option value=\"thin\" data-lang=\"images.thin\">Thin</option>\n              <option value=\"medium\" data-lang=\"images.medium\">Medium</option>\n              <option value=\"thick\" data-lang=\"images.thick\">Thick</option>\n            </select>\n          </div>\n          <div class=\"style-row\">\n            <label data-lang=\"images.shadow\">Shadow:</label>\n            <select id=\"image-panel-shadow\" style=\"flex: 1;padding: 6px 10px;background: var(--panel);border: 1px solid var(--edge-main);border-radius: 4px;color: var(--text-main);cursor: pointer;\">\n              <option value=\"none\" data-lang=\"images.none\">None</option>\n              <option value=\"small\" data-lang=\"images.small\">Small</option>\n              <option value=\"medium\" data-lang=\"images.medium\">Medium</option>\n              <option value=\"large\" data-lang=\"images.large\">Large</option>\n            </select>\n          </div>\n          <div class=\"section-label\" data-lang=\"images.imageNotes\">Image Notes</div>\n          <div class=\"notes-feed\" id=\"image-notes\"></div>\n          <button id=\"add-image-note\" style=\"margin-top: 10px;padding: 8px 16px;background: var(--accent);color: var(--bg);border: none;border-radius: 6px;cursor: pointer;font-size: clamp(14px, 1.6vw, 18px);font-weight: 600;width: 100%;\" data-lang=\"ui.buttons.addNote\">+ Add Note</button>\n          <button id=\"delete-image-panel\" style=\"margin-top: 15px;padding: 10px 16px;background: var(--danger);color: #fff;border: none;border-radius: 6px;cursor: pointer;font-size: clamp(14px, 1.6vw, 18px);font-weight: 600;\" data-lang=\"images.delete\">Delete Image</button>\n        </div>\n     <details class=\"style-section\" open=\"\">\n          <summary data-lang=\"ui.sections.pageLayout\">Page Layout</summary>\n          <div class=\"style-content\">\n            <div style=\"padding: 12px; background: var(--panel-alt); border-radius: 6px; color: var(--text-soft); font-size: 13px; line-height: 1.6;\">\n              <strong style=\"color: var(--accent); display: block; margin-bottom: 8px;\" data-lang=\"pageLayoutHelp.dragToResize\">Drag to Resize:</strong>\n              • <strong data-lang=\"pageLayoutHelp.header\">Header:</strong> <span data-lang=\"pageLayoutHelp.headerDesc\">Drag the bottom edge of the header bar</span><br>\n              • <strong data-lang=\"pageLayoutHelp.sidebar\">Sidebar:</strong> <span data-lang=\"pageLayoutHelp.sidebarDesc\">Drag the left edge of the sidebar panel</span><br>\n              • <strong data-lang=\"pageLayoutHelp.mobile\">Mobile:</strong> <span data-lang=\"pageLayoutHelp.mobileDesc\">Drag the top edge of the footer panel</span><br>\n              <div style=\"margin-top: 8px; padding-top: 8px; border-top: 1px solid var(--edge-main); font-size: 12px;\" data-lang=\"pageLayoutHelp.hoverNote\">\n                Hover over panel edges to see resize handles\n              </div>\n            </div>\n          </div>\n        </details>\n      </aside>\n    </main>\n    <script id=\"nodes-json\" type=\"application/json\">{}</script>\n    <script id=\"labels-json\" type=\"application/json\">{\n  \"mappingModes\": {\n    \"network\": { \"name\": \"Network Topology\", \"node\": \"Node\", \"nodes\": \"Nodes\", \"connection\": \"Connection\", \"connections\": \"Connections\", \"add\": \"Add Node\", \"addRack\": \"Add Rack\" },\n    \"mindmap\": { \"name\": \"Mind Map\", \"node\": \"Idea\", \"nodes\": \"Ideas\", \"connection\": \"Link\", \"connections\": \"Links\", \"add\": \"Add Idea\", \"addRack\": \"Add Group\" },\n    \"sports\": { \"name\": \"Sports Playbook\", \"node\": \"Player\", \"nodes\": \"Players\", \"connection\": \"Route\", \"connections\": \"Routes\", \"add\": \"Add Player\", \"addRack\": \"Add Formation\" },\n    \"smarthome\": { \"name\": \"Smart Home\", \"node\": \"Device\", \"nodes\": \"Devices\", \"connection\": \"Connection\", \"connections\": \"Connections\", \"add\": \"Add Device\", \"addRack\": \"Add Zone\" },\n    \"floorplan\": { \"name\": \"Floor Plan\", \"node\": \"Element\", \"nodes\": \"Elements\", \"connection\": \"Connection\", \"connections\": \"Connections\", \"add\": \"Add Element\", \"addRack\": \"Add Room\" }\n  },\n  \"canvasTemplates\": {\n    \"grid\": { \"name\": \"Standard Grid\" },\n    \"dots\": { \"name\": \"Dot Grid\" },\n    \"blueprint\": { \"name\": \"Blueprint Grid\" },\n    \"basketball\": { \"name\": \"Basketball Court\" },\n    \"football\": { \"name\": \"Football Field\" },\n    \"soccer\": { \"name\": \"Soccer Pitch\" },\n    \"hockey\": { \"name\": \"Hockey Rink\" },\n    \"baseball\": { \"name\": \"Baseball Diamond\" },\n    \"tennis\": { \"name\": \"Tennis Court\" },\n    \"none\": { \"name\": \"No Grid\" }\n  },\n  \"defaultShapeCategories\": {\n    \"network\": \"Network Equipment\",\n    \"mindmap\": \"Basic Shapes\",\n    \"sports\": \"Basic Shapes\",\n    \"smarthome\": \"Smart Home\",\n    \"floorplan\": \"Basic Shapes\"\n  }\n}</script>\n    <script id=\"lang-json\" type=\"application/json\">{\n  \"_meta\": {\n    \"name\": \"English\",\n    \"code\": \"en\",\n    \"version\": \"1.0\",\n    \"rtl\": false,\n    \"edition\": \"networkening\"\n  },\n  \"modes\": {\n    \"network\": { \"name\": \"Network Topology\", \"node\": \"Node\", \"nodes\": \"Nodes\", \"connection\": \"Connection\", \"connections\": \"Connections\", \"add\": \"Add Node\", \"addRack\": \"Add Rack\", \"rack\": \"Rack\", \"racks\": \"Racks\", \"subtitle\": \"IP / Subtitle\", \"subtitlePlaceholder\": \"e.g. 192.168.1.100\", \"tags\": \"Tags\", \"tagsPlaceholder\": \"e.g. Docker, nginx, WG: vpn\", \"rackSubtitle\": \"IP / Network Range\", \"rackSubtitlePlaceholder\": \"e.g. 192.168.1.0/24\", \"rackTags\": \"Tags\", \"rackTagsPlaceholder\": \"e.g. Production, Data Center 1\" },\n    \"mindmap\": { \"name\": \"Mind Map\", \"node\": \"Idea\", \"nodes\": \"Ideas\", \"connection\": \"Link\", \"connections\": \"Links\", \"add\": \"Add Idea\", \"addRack\": \"Add Folder\", \"rack\": \"Folder\", \"racks\": \"Folders\", \"subtitle\": \"Description\", \"subtitlePlaceholder\": \"e.g. Main concept\", \"tags\": \"Keywords\", \"tagsPlaceholder\": \"e.g. important, research, todo\", \"rackSubtitle\": \"Description\", \"rackSubtitlePlaceholder\": \"e.g. Folder description\", \"rackTags\": \"Keywords\", \"rackTagsPlaceholder\": \"e.g. category, priority\" },\n    \"sports\": { \"name\": \"Sports Playbook\", \"node\": \"Player\", \"nodes\": \"Players\", \"connection\": \"Route\", \"connections\": \"Routes\", \"add\": \"Add Player\", \"addRack\": \"Add Folder\", \"rack\": \"Folder\", \"racks\": \"Folders\", \"subtitle\": \"Position / Number\", \"subtitlePlaceholder\": \"e.g. QB, #12\", \"tags\": \"Attributes\", \"tagsPlaceholder\": \"e.g. offense, starter, captain\", \"rackSubtitle\": \"Description\", \"rackSubtitlePlaceholder\": \"e.g. Formation name\", \"rackTags\": \"Attributes\", \"rackTagsPlaceholder\": \"e.g. offense, defense\" },\n    \"smarthome\": { \"name\": \"Smart Home\", \"node\": \"Device\", \"nodes\": \"Devices\", \"connection\": \"Connection\", \"connections\": \"Connections\", \"add\": \"Add Device\", \"addRack\": \"Add Room View\", \"rack\": \"Room View\", \"racks\": \"Room Views\", \"subtitle\": \"Model / Location\", \"subtitlePlaceholder\": \"e.g. Nest Thermostat, Living Room\", \"tags\": \"Groups\", \"tagsPlaceholder\": \"e.g. lighting, security, automation\", \"rackSubtitle\": \"Description\", \"rackSubtitlePlaceholder\": \"e.g. Room description\", \"rackTags\": \"Groups\", \"rackTagsPlaceholder\": \"e.g. upstairs, main floor\" },\n    \"floorplan\": { \"name\": \"Floor Plan\", \"node\": \"Element\", \"nodes\": \"Elements\", \"connection\": \"Connection\", \"connections\": \"Connections\", \"add\": \"Add Element\", \"addRack\": \"Add Room View\", \"rack\": \"Room View\", \"racks\": \"Room Views\", \"subtitle\": \"Label / Dimensions\", \"subtitlePlaceholder\": \"e.g. 12x14 ft\", \"tags\": \"Categories\", \"tagsPlaceholder\": \"e.g. furniture, fixture, wall\", \"rackSubtitle\": \"Description\", \"rackSubtitlePlaceholder\": \"e.g. Room dimensions\", \"rackTags\": \"Categories\", \"rackTagsPlaceholder\": \"e.g. bedroom, bathroom\" }\n  },\n  \"canvas\": {\n    \"grid\": \"Standard Grid\",\n    \"dots\": \"Dot Grid\",\n    \"blueprint\": \"Blueprint Grid\",\n    \"basketball\": \"Basketball Court\",\n    \"football\": \"Football Field\",\n    \"soccer\": \"Soccer Pitch\",\n    \"hockey\": \"Hockey Rink\",\n    \"baseball\": \"Baseball Diamond\",\n    \"tennis\": \"Tennis Court\",\n    \"none\": \"No Grid\"\n  },\n  \"ping\": {\n    \"httpPort80\": \"HTTP (port 80) uses IP field\",\n    \"httpsPort443\": \"HTTPS (port 443) uses IP field\",\n    \"customUrl\": \"Custom URL\"\n  },\n  \"shapes\": {\n    \"categories\": {\n      \"basic\": \"Basic Shapes\",\n      \"computers\": \"Computers & Devices\",\n      \"network\": \"Network Equipment\",\n      \"cloud\": \"Cloud & Services\",\n      \"security\": \"Security & Monitoring\",\n      \"smarthome\": \"Smart Home\",\n      \"sports\": \"Sports\",\n      \"rack\": \"Rack Equipment\"\n    },\n    \"items\": {\n      \"circle\": \"Circle\", \"square\": \"Square\", \"rectangle\": \"Rectangle\", \"triangle\": \"Triangle\",\n      \"hexagon\": \"Hexagon\", \"diamond\": \"Diamond\", \"star\": \"Star\", \"stop-sign\": \"Stop Sign\",\n      \"octagon\": \"Octagon\", \"pentagon\": \"Pentagon\", \"cross\": \"Cross\", \"rounded-square\": \"Rounded Square\",\n      \"pill\": \"Pill\", \"parallelogram\": \"Parallelogram\", \"trapezoid\": \"Trapezoid\",\n      \"server\": \"Server\", \"pc\": \"PC / Desktop\", \"laptop\": \"Laptop\", \"phone\": \"Phone / Mobile\",\n      \"printer\": \"Printer\", \"pi\": \"Raspberry Pi\", \"sensor\": \"Sensor / IoT\",\n      \"router\": \"Router\", \"switch\": \"Switch\", \"firewall\": \"Firewall\", \"access-point\": \"Access Point\",\n      \"load-balancer\": \"Load Balancer\", \"gateway\": \"Gateway\", \"vpn\": \"VPN / Tunnel\", \"nas\": \"NAS / Storage\",\n      \"cloud\": \"Cloud\", \"database\": \"Database\", \"docker\": \"Docker\", \"container\": \"Container\",\n      \"vm\": \"Virtual Machine\", \"kubernetes\": \"Kubernetes\", \"api\": \"API / Endpoint\",\n      \"queue\": \"Queue / Message\", \"lambda\": \"Lambda / Function\", \"bucket\": \"Bucket / S3\",\n      \"shield\": \"Shield\", \"camera\": \"Camera / CCTV\", \"monitor\": \"Monitor / Dashboard\",\n      \"thermostat\": \"Thermostat\", \"doorbell\": \"Video Doorbell\", \"smart-lock\": \"Smart Lock\",\n      \"smart-bulb\": \"Smart Bulb\", \"smart-plug\": \"Smart Plug\", \"smart-speaker\": \"Smart Speaker\",\n      \"smart-tv\": \"Smart TV\", \"hub\": \"Smart Hub\", \"smoke-detector\": \"Smoke Detector\",\n      \"motion-sensor\": \"Motion Sensor\", \"garage\": \"Garage Door\", \"sprinkler\": \"Sprinkler\", \"vacuum\": \"Robot Vacuum\",\n      \"basketball-ball\": \"Basketball\", \"football-ball\": \"Football\", \"soccer-ball\": \"Soccer Ball\",\n      \"hockey-puck\": \"Hockey Puck\", \"baseball-ball\": \"Baseball\", \"tennis-ball\": \"Tennis Ball\",\n      \"volleyball\": \"Volleyball\", \"rugby-ball\": \"Rugby Ball\", \"golf-ball\": \"Golf Ball\",\n      \"frisbee\": \"Frisbee\", \"cricket-ball\": \"Cricket Ball\", \"lacrosse-stick\": \"Lacrosse Stick\",\n      \"golf-flag\": \"Golf Hole Flag\",\n      \"tactical-x\": \"X (Defender)\", \"tactical-o\": \"O (Offensive)\",\n      \"tactical-star\": \"Star (Key Player)\",\n      \"patch-panel\": \"Patch Panel\", \"ups\": \"UPS\", \"pdu\": \"PDU\", \"rack-shelf\": \"Shelf\",\n      \"blank-panel\": \"Blank Panel\", \"cable-management\": \"Cable Management\", \"kvm\": \"KVM\"\n    }\n  },\n  \"ui\": {\n    \"defaults\": {\n      \"newNode\": \"New {node}\",\n      \"newRack\": \"New {rack}\",\n      \"newText\": \"New Text\"\n    },\n    \"buttons\": {\n      \"save\": \"Save\", \"cancel\": \"Cancel\", \"delete\": \"Delete\", \"close\": \"Close\",\n      \"add\": \"Add\", \"edit\": \"Edit\", \"undo\": \"Undo\", \"redo\": \"Redo\",\n      \"import\": \"Import\", \"export\": \"Export\", \"settings\": \"Settings\",\n      \"startMapping\": \"Start Mapping\", \"skip\": \"Skip\", \"apply\": \"Apply\",\n      \"clearAll\": \"Clear All\", \"reset\": \"Reset\", \"saveChanges\": \"Save Changes\",\n      \"addNode\": \"Add {node}\", \"addRack\": \"Add {rack}\", \"editNode\": \"Edit {node}\",\n      \"deleteNode\": \"Delete {node}\", \"exportLang\": \"Export Language File\",\n      \"importLang\": \"Import Language File\", \"resetLang\": \"Reset to English\",\n      \"editStrings\": \"Edit Strings\", \"applyRoutingAll\": \"Apply Routing to All Connections\",\n      \"saveHtml\": \"Save HTML\", \"saveFile\": \"Save File\", \"printBW\": \"Print B&W\",\n      \"printColorFull\": \"Print Full Color\", \"printColorWhite\": \"Print White BG\", \"printColorBlack\": \"Print Black BG\",\n      \"exportJson\": \"Export JSON\", \"exportCsv\": \"Export CSV\", \"exportMd\": \"Export Markdown\",\n      \"exportPng\": \"Export PNG\", \"exportSvg\": \"Export SVG\", \"exportVideo\": \"Export Video\",\n      \"importJson\": \"Import JSON\", \"importMd\": \"Import Markdown\",\n      \"exportHelp\": \"Export Help\", \"importHelp\": \"Import Help\",\n      \"layers\": \"Layers\", \"notes\": \"Notes\", \"auditLog\": \"Audit Log\", \"portMap\": \"Port Map\",\n      \"hideAll\": \"Hide All\", \"showAll\": \"Show All\", \"clearLog\": \"Clear Log\",\n      \"addNote\": \"+ Add Note\", \"addTab\": \"+ Add Tab\", \"addTag\": \"+ Add Tag\", \"encrypt\": \"Encrypt\", \"decrypt\": \"Decrypt\",\n      \"recording\": \"Recording\", \"play\": \"Play\", \"pause\": \"Pause\", \"stop\": \"Stop\",\n      \"drawMode\": \"Draw\", \"textMode\": \"Text\", \"rectMode\": \"Rect\", \"freeDrawMode\": \"Draw\",\n      \"zoomIn\": \"Zoom In\", \"zoomOut\": \"Zoom Out\", \"fitView\": \"Fit View\", \"resetView\": \"Reset View\",\n      \"group\": \"Group\", \"ungroup\": \"Ungroup\", \"bringToFront\": \"Bring to Front\", \"sendToBack\": \"Send to Back\",\n      \"duplicate\": \"Duplicate\", \"selectAll\": \"Select All\", \"deselectAll\": \"Deselect All\",\n      \"copyStyle\": \"Copy Style\", \"pasteStyle\": \"Paste Style\",\n      \"clearHistory\": \"Clear History\", \"createSnapshot\": \"Create Snapshot\",\n      \"del\": \"Del\", \"done\": \"Done\", \"pngImage\": \"PNG Image\", \"svgVector\": \"SVG Vector\",\n      \"html\": \"HTML\", \"json\": \"JSON\", \"markdown\": \"Markdown\", \"csv\": \"CSV\"\n    },\n    \"labels\": {\n      \"name\": \"Name\", \"ip\": \"IP / Subtitle\", \"tags\": \"Tags\", \"category\": \"Category\",\n      \"addIconTag\": \"Add Web Icon Tag\", \"iconTags\": \"Icon Tags:\", \"searchWebIcons\": \"Or search web icons\",\n      \"selectedIcon\": \"Selected Icon:\", \"enablePing\": \"Enable ping/status check for this {node}\",\n      \"protocol\": \"Protocol\", \"customUrl\": \"Custom URL\", \"timeoutMs\": \"Timeout (ms)\",\n      \"rackCapacity\": \"Rack Capacity\", \"u42Standard\": \"42U (Standard Full Rack)\",\n      \"u48Large\": \"48U (Large Rack)\", \"u24Half\": \"24U (Half Rack)\",\n      \"u12Small\": \"12U (Small/Wall Mount)\", \"u6Mini\": \"6U (Mini Rack)\",\n      \"shape\": \"Shape\", \"icon\": \"Icon / Shape\", \"theme\": \"Theme\", \"mode\": \"Mode\",\n      \"canvasStyle\": \"Canvas Style\", \"themePreset\": \"Theme Preset\",\n      \"mainBackground\": \"Main Background\", \"accentColor\": \"Accent Color\",\n      \"primaryText\": \"Primary Text\", \"secondaryText\": \"Secondary Text\",\n      \"viewOnly\": \"View Only (disable editing)\", \"sidebar\": \"Sidebar / Mobile Footer\",\n      \"modalBg\": \"Modal Window Background\", \"dangerButtons\": \"Danger Buttons\",\n      \"tagFill\": \"Tag(s) Fill\", \"tagText\": \"Tag(s) Text\", \"tagBorder\": \"Tag(s) Border\",\n      \"background\": \"Background\", \"border\": \"Border\", \"buttonFill\": \"Button Fill\",\n      \"buttonText\": \"Button Text\", \"gridLines\": \"Grid Lines\", \"gridSize\": \"Grid Size\",\n      \"showGrid\": \"Show Grid\", \"bgTop\": \"Background Top\", \"bgBottom\": \"Background Bottom\",\n      \"solidBg\": \"Solid Background\", \"frameFill\": \"Frame Fill\", \"frameBorder\": \"Frame Border\",\n      \"uLabels\": \"U Labels\", \"text\": \"Text\", \"minimapDots\": \"Minimap Dots\",\n      \"fill\": \"Fill\", \"titleColor\": \"Title Color\", \"titleSize\": \"Title Size\",\n      \"subtitleColor\": \"Subtitle Color\", \"subtitleSize\": \"Subtitle Size\", \"font\": \"Font\",\n      \"defaultColor\": \"Default Color\", \"defaultRouting\": \"Default Routing\",\n      \"animationStyle\": \"Animation Style\", \"animateDirections\": \"Animate Directions\",\n      \"animationSpeed\": \"Animation Speed\", \"showPortLabels\": \"Show Port Labels\", \"allAnimations\": \"All Animations\",\n      \"sweepPan\": \"Sweep (Pan)\", \"pulseBreathe\": \"Pulse (Breathe)\", \"ringsEmanate\": \"Rings (Emanate)\",\n      \"spinRotate\": \"Spin (Rotate)\", \"connectionsFlow\": \"Connections (Flow)\",\n      \"cameras\": \"Cameras\", \"doorbells\": \"Doorbells\", \"motionSensors\": \"Motion Sensors\",\n      \"smokeDetectors\": \"Smoke Detectors\", \"wifiApRouter\": \"WiFi/AP/Router\",\n      \"sensorsIot\": \"Sensors/IoT\", \"sprinklers\": \"Sprinklers\",\n      \"allZones\": \"All Zones\", \"resizeHandleColor\": \"Resize Handle Color\",\n      \"resizeHandleSize\": \"Resize Handle Size\", \"groupedIconOutline\": \"Grouped Icon Outline\",\n      \"multiselectFill\": \"Multiselect Fill Color (Desktop)\",\n      \"multiselectFillOpacity\": \"Multiselect Fill Opacity (Desktop)\",\n      \"multiselectBorder\": \"Multiselect Border Color (Desktop)\",\n      \"multiselectBorderWidth\": \"Multiselect Border Width (Desktop)\",\n      \"multiselectBorderStyle\": \"Multiselect Border Style (Desktop)\",\n      \"fontSize\": \"Font Size\", \"enabled\": \"Enabled\", \"textColor\": \"Text Color\",\n      \"customText\": \"Custom Text (HTML)\", \"language\": \"Language\", \"currentLanguage\": \"Current:\",\n      \"tagsComma\": \"Tags (comma separated)\", \"networkRange\": \"IP / Network Range (optional)\",\n      \"layer\": \"Layer\", \"mac\": \"MAC Address\", \"vendor\": \"Vendor\", \"model\": \"Model\",\n      \"serialNumber\": \"Serial Number\", \"firmware\": \"Firmware\", \"location\": \"Location\",\n      \"owner\": \"Owner\", \"purchaseDate\": \"Purchase Date\", \"warranty\": \"Warranty\",\n      \"notes\": \"Notes\", \"lineNotes\": \"Line Notes\", \"zoneNotes\": \"Zone Notes\",\n      \"color\": \"Color\", \"width\": \"Width\", \"style\": \"Style\", \"routing\": \"Routing\",\n      \"direction\": \"Direction\", \"label\": \"Label\", \"labelPosition\": \"Label Position\",\n      \"fromPort\": \"From Port\", \"toPort\": \"To Port\", \"waypoints\": \"Waypoints\", \"bandwidth\": \"Bandwidth\",\n      \"latency\": \"Latency\", \"protocol\": \"Protocol\", \"vlan\": \"VLAN\",\n      \"rackCapacity\": \"Rack Capacity\", \"rackPosition\": \"Rack Position (U)\",\n      \"size\": \"Size\", \"sizeHeight\": \"Height (U)\", \"opacity\": \"Opacity\",\n      \"rotation\": \"Rotation\", \"zIndex\": \"Z-Index\", \"locked\": \"Locked\",\n      \"visible\": \"Visible\", \"animated\": \"Animated\", \"animationType\": \"Animation Type\",\n      \"zoneAngle\": \"Zone Angle\", \"zoneRange\": \"Zone Range\", \"zoneColor\": \"Zone Color\",\n      \"zoneOpacity\": \"Zone Opacity\", \"showZone\": \"Show Zone\",\n      \"pageLayout\": \"Page Layout\", \"pageWidth\": \"Page Width\", \"pageHeight\": \"Page Height\",\n      \"orientation\": \"Orientation\", \"landscape\": \"Landscape\", \"portrait\": \"Portrait\",\n      \"margin\": \"Margin\", \"padding\": \"Padding\", \"scale\": \"Scale\",\n      \"timestamp\": \"Timestamp\", \"action\": \"Action\", \"details\": \"Details\",\n      \"fromDevice\": \"From Device\", \"toDevice\": \"To Device\", \"description\": \"Description\",\n      \"encryptNote\": \"Encrypt Note\",\n      \"enableAutoStatusChecking\": \"Enable automatic status checking\",\n      \"checkInterval\": \"Check Interval (seconds):\",\n      \"nextCheckIn\": \"Next check in:\",\n      \"lastRun\": \"Last run:\"\n    },\n    \"sections\": {\n      \"basicInfo\": \"Basic Information\", \"tags\": \"Tags\", \"nodeAppearance\": \"{node} Appearance\",\n      \"rackAppearance\": \"{rack} Appearance\", \"pingStatus\": \"Ping / Status Monitoring\",\n      \"rackConfiguration\": \"Rack Configuration\",\n      \"mappingModeTheme\": \"Mapping Mode & Theme\", \"viewOnlyMode\": \"View Only Mode\",\n      \"moreThemeOptions\": \"More Theme Options\", \"topBar\": \"Top Bar\",\n      \"mainCanvas\": \"Main Canvas\", \"rackCanvas\": \"Rack Canvas\", \"canvasToolbars\": \"Canvas Toolbars\",\n      \"nodes\": \"Nodes\", \"connections\": \"Connections\", \"animationsZones\": \"Animations & Zones\",\n      \"groupsEditing\": \"Groups & Editing\", \"inputsDropdowns\": \"Inputs & Dropdowns\",\n      \"welcomeMessage\": \"Welcome Message\", \"dangerZone\": \"Danger Zone\",\n      \"byType\": \"By Type\", \"byCategory\": \"By Category\", \"zoneSettings\": \"Zone (Animation Cone) Settings\",\n      \"pageLayout\": \"Page Layout\", \"nodeDetails\": \"Node Details\", \"connectionDetails\": \"Connection Details\",\n      \"appearance\": \"Appearance\", \"position\": \"Position\", \"advanced\": \"Advanced\",\n      \"metadata\": \"Metadata\", \"networkInfo\": \"Network Info\", \"deviceInfo\": \"Device Info\",\n      \"inputsFont\": \"Inputs & Font\", \"exportOptions\": \"Export Options\", \"language\": \"Language\",\n      \"autoStatusChecking\": \"Auto Status Checking\", \"notes\": \"Notes\"\n    },\n    \"modals\": {\n      \"settings\": \"Settings\", \"addNode\": \"Add New {node}\", \"addRack\": \"Add New {rack}\",\n      \"editNode\": \"Edit {node}\", \"editRack\": \"Edit {rack}\", \"confirmDelete\": \"Confirm Delete\",\n      \"languageEditor\": \"Language Editor\", \"welcome\": \"Welcome\",\n      \"layerVisibility\": \"Layer Visibility\", \"notes\": \"Notes\", \"notesHub\": \"Notes Hub\", \"auditLog\": \"Audit Log\",\n      \"portMap\": \"Port Map\", \"export\": \"Export\", \"import\": \"Import\",\n      \"clearAllNodes\": \"Clear All Nodes\", \"recording\": \"Recording\", \"recordings\": \"Recordings\",\n      \"saveInfo\": \"Save Info\", \"howToSave\": \"How to Save\", \"keyboardShortcuts\": \"Keyboard Shortcuts\",\n      \"importExport\": \"Import/Export\", \"help\": \"Help\", \"tabs\": \"Tabs\", \"versionHistory\": \"Version History\",\n      \"selectIcon\": \"Select Icon\", \"confirm\": \"Confirm\", \"editName\": \"Edit Name\", \"editNote\": \"Edit Note\",\n      \"layers\": \"Layers\"\n    },\n    \"placeholders\": {\n      \"nodeName\": \"e.g. web-server\", \"nodeIp\": \"e.g. 192.168.1.100\", \"customUrl\": \"e.g. http://192.168.1.1:8080\",\n      \"tags\": \"e.g. Docker, nginx, WG: vpn\", \"rackName\": \"e.g. Rack-A, DC1-R01\",\n      \"rackIp\": \"e.g. 192.168.1.0/24\", \"rackTags\": \"e.g. Production, Data Center 1\",\n      \"search\": \"Search...\", \"searchStrings\": \"Search strings...\", \"searchNodes\": \"Search nodes...\",\n      \"leaveEmpty\": \"Leave empty for default\", \"enterNote\": \"Enter note...\",\n      \"filterAudit\": \"Filter audit log...\", \"filterNodes\": \"Filter nodes...\",\n      \"newTabName\": \"New tab name...\", \"searchIcons\": \"Search icons...\",\n      \"sectionName\": \"Section name (e.g., 'Root Passwords')...\",\n      \"searchDevices\": \"Search devices or ports...\",\n      \"enterInfo\": \"Enter sensitive information here...\",\n      \"fromPort\": \"e.g. eth0, gi0/1\", \"toPort\": \"e.g. eth1, gi0/2\", \"enterText\": \"Enter text...\"\n    },\n    \"tooltips\": {\n      \"addNode\": \"Add new {node}\", \"addRack\": \"Add new {rack}\",\n      \"undo\": \"Undo (Ctrl+Z)\", \"redo\": \"Redo (Ctrl+Y)\",\n      \"settings\": \"Settings\", \"saveTheme\": \"Save current theme\", \"deleteTheme\": \"Delete theme\",\n      \"toggleLayers\": \"Toggle layer visibility\", \"toggleLayersDesc\": \"Toggle which layers are visible on the canvas\",\n      \"manageNotes\": \"Manage encrypted notes\",\n      \"viewAuditLog\": \"View audit log\", \"viewPortMap\": \"View port map\",\n      \"zoomIn\": \"Zoom in\", \"zoomOut\": \"Zoom out\", \"fitView\": \"Fit all to view\",\n      \"resetView\": \"Reset zoom and pan\", \"toggleMinimap\": \"Toggle minimap\",\n      \"toggleLegend\": \"Toggle legend\", \"toggleToolbar\": \"Toggle toolbar\",\n      \"renameTab\": \"Rename tab\"\n    },\n    \"stats\": {\n      \"nodesConnections\": \"{nodeCount} {nodes} • {edgeCount} {connections}\"\n    },\n    \"options\": {\n      \"orthogonal\": \"Orthogonal (90°)\", \"curved\": \"Curved\", \"straight\": \"Straight\",\n      \"flowingArrows\": \"Flowing Arrows\", \"dotArrows\": \"Dot Arrows\",\n      \"allDirections\": \"All Directions\", \"forwardOnly\": \"Forward Only\",\n      \"backwardOnly\": \"Backward Only\", \"bidirectionalOnly\": \"Bidirectional Only\",\n      \"veryFast\": \"Very Fast\", \"fast\": \"Fast\", \"normal\": \"Normal\", \"slow\": \"Slow\", \"verySlow\": \"Very Slow\",\n      \"dashed\": \"Dashed\", \"dotted\": \"Dotted\", \"solid\": \"Solid\",\n      \"default\": \"Default\", \"custom\": \"Custom\", \"customColors\": \"Custom Colors\",\n      \"none\": \"None\", \"auto\": \"Auto\", \"manual\": \"Manual\",\n      \"top\": \"Top\", \"bottom\": \"Bottom\", \"left\": \"Left\", \"right\": \"Right\", \"center\": \"Center\",\n      \"forward\": \"Forward\", \"backward\": \"Backward\", \"both\": \"Both\",\n      \"allEvents\": \"All Events\", \"nodeOperations\": \"Node Operations\",\n      \"styleChanges\": \"Style Changes\", \"rackOperations\": \"Rack Operations\",\n      \"layerChanges\": \"Layer Changes\", \"allConnections\": \"All Connections\",\n      \"withPortsOnly\": \"With Ports Only\", \"missingPorts\": \"Missing Ports\"\n    },\n    \"tabs\": {\n      \"all\": \"All\", \"uiGeneral\": \"UI/General\", \"howToSave\": \"How to Save\",\n      \"keyboardShortcuts\": \"Keyboard Shortcuts\", \"importExport\": \"Import/Export\"\n    },\n    \"layers\": {\n      \"network\": {\n        \"layer1\": \"Physical Layer\", \"layer2\": \"Logical Layer\",\n        \"layer3\": \"Security Layer\", \"layer4\": \"Application Layer\"\n      },\n      \"sports\": {\n        \"layer1\": \"Players\", \"layer2\": \"Equipment\",\n        \"layer3\": \"Coaching/Strategy\", \"layer4\": \"Officials\"\n      },\n      \"smarthome\": {\n        \"layer1\": \"Devices\", \"layer2\": \"Security\",\n        \"layer3\": \"Climate\", \"layer4\": \"Entertainment\"\n      },\n      \"floorplan\": {\n        \"layer1\": \"Furniture\", \"layer2\": \"Fixtures\",\n        \"layer3\": \"Utilities\", \"layer4\": \"People/Workstations\"\n      },\n      \"mindmap\": {\n        \"layer1\": \"Ideas\", \"layer2\": \"Tasks\",\n        \"layer3\": \"Notes\", \"layer4\": \"Links\"\n      }\n    },\n    \"rackSizes\": {\n      \"42u\": \"42U (Standard Full Rack)\", \"48u\": \"48U (Large Rack)\",\n      \"24u\": \"24U (Half Rack)\", \"12u\": \"12U (Small/Wall Mount)\", \"6u\": \"6U (Mini Rack)\"\n    },\n    \"auditTypes\": {\n      \"all\": \"All Events\", \"create\": \"Create\", \"update\": \"Update\", \"delete\": \"Delete\",\n      \"import\": \"Import\", \"export\": \"Export\", \"layer\": \"Layer Changes\"\n    },\n    \"fonts\": {\n      \"inter\": \"Inter\", \"arial\": \"Arial\", \"helvetica\": \"Helvetica\",\n      \"georgia\": \"Georgia\", \"monospace\": \"Monospace\"\n    }\n  },\n  \"messages\": {\n    \"confirmClearAll\": \"This will permanently delete everything on the canvas. Continue?\",\n    \"confirmDelete\": \"Are you sure you want to delete this {node}?\",\n    \"confirmDeleteMultiple\": \"Delete {count} selected items?\",\n    \"confirmImport\": \"This will replace all current data. Continue?\",\n    \"importSuccess\": \"Imported successfully\",\n    \"exportSuccess\": \"File exported successfully\",\n    \"invalidFile\": \"Invalid file format\",\n    \"noSelection\": \"Select at least one {node} first\",\n    \"langImportSuccess\": \"Language file imported successfully\",\n    \"langResetSuccess\": \"Language reset to English\",\n    \"langExportSuccess\": \"Language file exported\",\n    \"invalidLangFile\": \"Invalid language file structure\",\n    \"setSolidBgNote\": \"Set solid background to override gradient.\",\n    \"dangerZoneNote\": \"Permanently delete everything on the canvas.\",\n    \"viewOnlyNote\": \"When enabled, all editing of the canvas is disabled. Pan and zoom still work.\",\n    \"welcomeHint\": \"Double click canvas to add {nodes}.<br>Drag from {node} edge to connect.<br>Right-click for more options.\",\n    \"clearAllWarning\": \"This will remove ALL nodes and connections. This cannot be undone until you reload without saving. Are you sure?\",\n    \"clearEverything\": \"Clear Everything\",\n    \"layerToggleNote\": \"Toggle which layers are visible on the canvas\",\n    \"notesEncryptionNote\": \"Notes can also be stored with AES 256 encryption\",\n    \"howToSaveNote\": \"Browsers cannot overwrite local files. Click Save File to download an updated HTML with all changes. Replace your old file to keep edits. An automatic backup is also saved to your browser's local storage and will prompt for recovery if you close without saving.\",\n    \"decryptionNote\": \"Export via JSON in top settings menu after successful password validation to decrypt your data. You can then reimport back into the same file and save!\",\n    \"noNodesYet\": \"No {nodes} yet. Double click canvas or use sidebar to add.\",\n    \"selectRecording\": \"Select a recording to export.\",\n    \"pleaseWait\": \"This may take a moment, please wait...\",\n    \"recoverWork\": \"Recover unsaved work from {date}?\",\n    \"replaceCurrentData\": \"This will replace current data. Continue?\",\n    \"invalidDataFile\": \"Invalid data file. Missing required fields.\",\n    \"noStringsFound\": \"No strings found\",\n    \"manageTopologies\": \"Manage multiple topologies\",\n    \"snapshotLimit\": \"Limit: Snapshots\",\n    \"trackChanges\": \"Track all changes made to your topology\",\n    \"portMapDesc\": \"All connections with port assignments\",\n    \"manageRecordings\": \"Manage recorded movement sequences\",\n    \"noConnections\": \"No connections found\",\n    \"rackViewHint\": \"Viewing: {name} | Double click empty space to exit\",\n    \"slotCollisionWarning\": \"Warning: {count} slot collision(s) detected\",\n    \"autoStatusCheckingNote\": \"Automatically checks all ping enabled nodes at the specified interval. I recommend 30 to 60 seconds for local networks.\"\n  },\n  \"dialogs\": {\n    \"recoverWork\": \"Recover unsaved work from {date}?\\n({nodeCount} nodes{tabInfo})\\n\\nClick OK to recover, Cancel to start fresh.\",\n    \"replaceData\": \"This will replace current data. Continue?\",\n    \"enableZoneFirst\": \"Enable the zone first before saving as preset\",\n    \"enterPresetName\": \"Enter preset name:\",\n    \"presetSaved\": \"Preset saved: {name}\",\n    \"selectNodeFirst\": \"Select at least one node first\",\n    \"zoneStyleCopied\": \"Zone style copied from first selected node\",\n    \"selectNodesFirst\": \"Select nodes first\",\n    \"copyZoneStyleFirst\": \"Copy a zone style first\",\n    \"zoneStylePasted\": \"Zone style pasted to {count} node(s)\",\n    \"zonesToggled\": \"Toggled zones on {count} node(s) to {state}\",\n    \"enterDecryptPassword\": \"This file is encrypted. Enter password to decrypt:\\n(Attempt {attempt} of {max})\",\n    \"decryptionCancelled\": \"Decryption cancelled. The file will not be loaded.\",\n    \"incorrectPassword\": \"Incorrect password. Please try again.\",\n    \"maxAttemptsReached\": \"Maximum attempts reached. The file will not be loaded.\",\n    \"editLegendLabel\": \"Edit legend label:\",\n    \"enterPort\": \"Port on {nodeName}:\",\n    \"portAlreadyUsed\": \"Warning: Port \\\"{port}\\\" is already used on {nodeName}. Use anyway?\",\n    \"zoneStyleCopiedShort\": \"Zone style copied!\",\n    \"drawingDisabledInRack\": \"Drawing tools are disabled in rack view. Exit to topology view to use drawing tools.\",\n    \"applyRoutingToAll\": \"Apply \\\"{routing}\\\" routing to all {count} connections?\",\n    \"invalidDataFile\": \"Invalid data file. Missing required fields.\",\n    \"enterEncryptPassword\": \"Enter a password to encrypt your data:\\n(Remember this password! You will need it to open this file and it's not recoverable!)\",\n    \"encryptionCancelled\": \"Encryption cancelled. File not saved.\",\n    \"confirmEncryptPassword\": \"Confirm your password:\",\n    \"passwordsDoNotMatch\": \"Passwords do not match. File not saved.\",\n    \"encryptionFailed\": \"Encryption failed: {error}\",\n    \"restoreVersion\": \"Restore version from {date}?\\n\\nYour current work will be lost unless you save first.\",\n    \"deleteVersionConfirm\": \"Delete this version from history?\",\n    \"clearVersionHistory\": \"Clear all version history?\\n\\nThis cannot be undone.\",\n    \"enterSnapshotDescription\": \"Enter a description for this snapshot:\",\n    \"enterTabName\": \"Please enter a tab name\",\n    \"noAuditEntries\": \"No audit entries to export\",\n    \"enterDecryptPasswordFor\": \"Enter password to decrypt \\\"{name}\\\":\",\n    \"decryptionFailed\": \"Failed to decrypt. Wrong password?\",\n    \"on\": \"ON\",\n    \"off\": \"OFF\",\n    \"ok\": \"OK\",\n    \"cancel\": \"Cancel\",\n    \"yes\": \"Yes\",\n    \"no\": \"No\",\n    \"confirm\": \"Confirm\",\n    \"alert\": \"Alert\",\n    \"prompt\": \"Input Required\",\n    \"nodeTypeNoZones\": \"This {node} type doesn't support coverage zones\",\n    \"noZoneStyleCopied\": \"No zone style copied. Copy a zone style first.\",\n    \"dataImportSuccess\": \"Data imported successfully!\",\n    \"importDataFailed\": \"Failed to import data: {error}\",\n    \"encryptionCancelledNotSaved\": \"Encryption cancelled. File not saved.\",\n    \"passwordsMismatchNotSaved\": \"Passwords do not match. File not saved.\",\n    \"encryptionFailedError\": \"Encryption failed: {error}\",\n    \"enterRackName\": \"Please enter a {rack} name.\",\n    \"enterNodeName\": \"Please enter a {node} name.\",\n    \"cannotDeleteLastTab\": \"Cannot delete the last tab\",\n    \"noAuditEntriesToExport\": \"No audit entries to export\",\n    \"enterNoteName\": \"Please enter a note name\",\n    \"noteAlreadyExists\": \"A note with this name already exists\",\n    \"passwordsMismatch\": \"Passwords do not match\",\n    \"screenshotFailed\": \"Screenshot failed. Please try again.\",\n    \"csvNoDataRows\": \"CSV file has no data rows\",\n    \"csvNeedsNameColumn\": \"CSV must have a \\\"name\\\" column\",\n    \"confirmImportCsvFull\": \"This CSV contains a full backup.\\n\\n- {nodeCount} nodes in CSV data\\n- {tabCount} tab(s) in backup\\n- {edgeCount} connections in backup\\n\\nClick OK for FULL RESTORE (replace all data)\\nClick Cancel to just ADD the {csvNodeCount} nodes\",\n    \"confirmImportCsvAdd\": \"Import {count} nodes from CSV?\\n\\n{settingsNote}\\n\\nNote: CSV does not include connections, zones, or text labels.\",\n    \"csvSettingsRestore\": \"This will ADD nodes and RESTORE settings/theme.\",\n    \"csvAddOnly\": \"This will ADD nodes to your existing topology.\",\n    \"fullRestoreComplete\": \"Full restore complete from CSV backup\",\n    \"importedNodesCount\": \"Successfully imported {count} {nodes}\",\n    \"importCsvFailed\": \"Failed to import CSV: {error}\",\n    \"confirmImportMarkdown\": \"Import Markdown topology?\\n\\n- {nodeCount} nodes\\n- {edgeCount} connections\\n- {rectCount} zones\\n- {textCount} text labels\\n\\nThis will REPLACE all current data.\",\n    \"noValidTopologyInMarkdown\": \"No valid topology data found in Markdown file.\\n\\nMake sure the file was exported from The One File.\",\n    \"markdownImportSuccess\": \"Successfully imported:\\n- {nodeCount} {nodes}\\n- {edgeCount} {connections}\\n- {rectCount} zones\\n- {textCount} text labels\",\n    \"jsonImportSuccess\": \"Successfully imported:\\n- {nodeCount} nodes\\n- {edgeCount} connections\\n- {rectCount} zones\\n- {textCount} text labels\",\n    \"importMarkdownFailed\": \"Failed to import Markdown: {error}\",\n    \"noRecordingAvailable\": \"No recording available. Press Record to create one.\",\n    \"selectRecordingToDelete\": \"Select a recording to delete.\",\n    \"deleteRecording\": \"Delete \\\"{name}\\\"?\",\n    \"selectRecordingToExport\": \"Select a recording to export.\",\n    \"recordingImported\": \"Imported: {name}\",\n    \"importRecordingFailed\": \"Failed to import recording: {error}\",\n    \"selectRecordingForVideo\": \"Select a recording to export as video.\",\n    \"deleteTab\": \"Delete tab \\\"{name}\\\"?\",\n    \"themeExists\": \"A theme named \\\"{name}\\\" already exists. Replace it?\",\n    \"deleteTheme\": \"Delete theme \\\"{name}\\\"?\",\n    \"clearAuditLog\": \"Clear all audit log entries?\\n\\nThis cannot be undone.\",\n    \"deleteNote\": \"Delete note \\\"{name}\\\"?\",\n    \"confirmImportJson\": \"This will replace all current data with the imported data.\\n\\nImporting:\\n- {nodeCount} nodes\\n- {edgeCount} connections\\n- {tabCount} tab(s)\\n\\nContinue?\",\n    \"confirmImportHtml\": \"Import data from HTML file?\\n\\n- {nodeCount} nodes\\n- {edgeCount} connections\\n- {tabCount} tab(s)\\n\\nThis will REPLACE all current data.\",\n    \"invalidHtmlFile\": \"This HTML file does not contain valid topology data.\",\n    \"htmlImportSuccess\": \"Successfully imported {nodeCount} nodes and {edgeCount} connections from HTML.\",\n    \"enterNewTabName\": \"Enter new name:\",\n    \"saveThemeName\": \"Enter a name for this theme:\",\n    \"defaultThemeName\": \"My Theme {number}\",\n    \"enterEncryptPasswordFor\": \"Enter password to encrypt \\\"{name}\\\":\",\n    \"confirmPasswordFor\": \"Confirm password:\",\n    \"deleteNodeConfirm\": \"Delete {node} \\\"{name}\\\"?\",\n    \"deleteLineConfirm\": \"Delete this line?\",\n    \"deleteLineNote\": \"Delete this line note?\",\n    \"deleteRackWithNodes\": \"Delete {rack} \\\"{name}\\\"?\\n\\nThis will also delete {count} {node}(s) inside:\\n• {nodeList}\",\n    \"deleteEmptyRack\": \"Delete {rack} \\\"{name}\\\"? (empty {rack})\",\n    \"deleteNodeWithConnections\": \"Delete {node} \\\"{name}\\\" and all its {connections}?\",\n    \"rackCapacityExceeded\": \"Device requires U{startU}-U{endU} but {rack} only has {capacity}U capacity. Device would exceed {rack} by {excess}U.\",\n    \"invalidRackPosition\": \"Invalid {rack} position. U position must be at least 1.\"\n  },\n  \"emptyStates\": {\n    \"noStringsFound\": \"No strings found\",\n    \"noNodesAssigned\": \"No nodes assigned\",\n    \"noConnections\": \"No connections\",\n    \"noVersionHistory\": \"No version history yet. Versions are saved automatically when you save the file.\",\n    \"noAuditEntries\": \"No audit entries yet\",\n    \"noNotesYet\": \"No notes yet\",\n    \"noConnectionsFound\": \"No connections found\",\n    \"noRecordingsYet\": \"No recordings yet. Press Record to start.\",\n    \"generatingVideo\": \"Generating Video...\"\n  },\n  \"legends\": {\n    \"legend\": \"Legend\",\n    \"zoneLegend\": \"Zone Legend\",\n    \"lineLegend\": \"Line Legend\",\n    \"coverageZone\": \"Coverage Zone\",\n    \"defaultLineLabel\": \"you can edit me too\"\n  },\n  \"editModal\": {\n    \"editTitle\": \"Edit Title\",\n    \"editName\": \"Edit Name\",\n    \"editIpSubtitle\": \"Edit IP/Subtitle\",\n    \"editTag\": \"Edit Tag\",\n    \"editMacAddress\": \"Edit MAC Address\",\n    \"addTags\": \"Add Tag(s) : comma separated\",\n    \"editRackUnit\": \"Edit Rack Unit\",\n    \"editUHeight\": \"Edit U Height\",\n    \"rackPlacementError\": \"Rack Placement Error\"\n  },\n  \"noteEditor\": {\n    \"title\": \"Edit Note\",\n    \"addNote\": \"Add Note\",\n    \"editNote\": \"Edit Note\",\n    \"viewNote\": \"View Note\"\n  },\n  \"notes\": {\n    \"noNotes\": \"No notes yet\",\n    \"namePlaceholder\": \"Note name (e.g., 'Root Passwords')...\",\n    \"editNote\": \"Edit Note\",\n    \"contentPlaceholder\": \"Enter sensitive information here...\",\n    \"encryptNote\": \"Encrypt Note\",\n    \"newNoteTitle\": \"New note: {name}\",\n    \"editNoteTitle\": \"Edit note: {name}\"\n  },\n  \"themes\": {\n    \"default\": \"Default\", \"slate\": \"Slate\", \"graphite\": \"Graphite\", \"frost\": \"Frost (Light)\",\n    \"synthwave\": \"Synthwave\", \"terminal\": \"Terminal\", \"dracula\": \"Dracula\",\n    \"cobalt\": \"Cobalt\", \"solarized\": \"Solarized\", \"brainwave\": \"Brainwave\",\n    \"neonMint\": \"Neon Mint\", \"velvetDusk\": \"Velvet Dusk\", \"sunburst\": \"Sunburst\"\n  },\n  \"themeGroups\": {\n    \"corporate\": \"Corporate\", \"homelab\": \"Homelab\", \"dev\": \"Dev\", \"mindMap\": \"Mind Map\", \"myThemes\": \"My Themes\"\n  },\n  \"sidebar\": {\n    \"addNode\": \"+ {node}\", \"addRack\": \"+ {rack}\",\n    \"nodePanel\": \"{node} Details\", \"edgePanel\": \"{connection} Details\",\n    \"properties\": \"Properties\", \"style\": \"Style\", \"actions\": \"Actions\"\n  },\n  \"topbar\": {\n    \"untitledTopology\": \"Untitled Topology\", \"saveHtml\": \"Save HTML\",\n    \"export\": \"Export\", \"import\": \"Import\",\n    \"backToTopology\": \"← Back to Topology\", \"tabs\": \"Tabs\", \"snapshots\": \"Snapshots\",\n    \"audit\": \"Audit\", \"ports\": \"Ports\"\n  },\n  \"toolbar\": {\n    \"legend\": \"Legend\", \"legendMini\": \"Connection & Zone Legend\", \"minimap\": \"Minimap\", \"minimapMini\": \"Map\", \"draw\": \"Draw Tools\", \"drawMini\": \"Draw\",\n    \"topology\": \"Topology Tools\", \"topologyMini\": \"Add Connection\",\n    \"addNode\": \"+ {node}\", \"addRack\": \"+ {rack}\",\n    \"undo\": \"Undo\", \"redo\": \"Redo\"\n  },\n  \"help\": {\n    \"title\": \"Help\",\n    \"tabs\": {\n      \"general\": \"General\", \"desktop\": \"Desktop\", \"mobile\": \"Mobile\", \"formats\": \"Import/Export\", \"recording\": \"Recording\"\n    },\n    \"general\": {\n      \"addNodes\": \"Add Nodes:\", \"addNodesDesc\": \"Click \\\"+ Node\\\" or \\\"+ Rack\\\" in the top menu\",\n      \"connectNodes\": \"Connect Nodes:\", \"connectNodesDesc\": \"Select a node, then use \\\"Add Connection\\\" in the panel\",\n      \"moveNodes\": \"Move Nodes:\", \"moveNodesDesc\": \"Drag nodes to reposition them\",\n      \"enterRackView\": \"Enter Rack View:\", \"enterRackViewDesc\": \"Double click on desktop or Long press on mobile\",\n      \"multiSelect\": \"Multi Select:\", \"multiSelectDesc\": \"Right click (desktop) or double tap (mobile)\",\n      \"panCanvas\": \"Pan Canvas:\", \"panCanvasDesc\": \"Drag empty space or hold Space + drag\",\n      \"zoom\": \"Zoom:\", \"zoomDesc\": \"Scroll wheel or pinch gesture\",\n      \"freeDraw\": \"Free Draw:\", \"freeDrawDesc\": \"Use draw toolbar for drawing lines, boxes/zones, and text\",\n      \"savingEncryption\": \"Saving & Encryption\",\n      \"savingNote\": \"Browsers cannot overwrite local files. Click Save File to download an updated HTML with all changes. Replace your old file to keep edits. An automatic backup is also saved to your browser's local storage and will prompt for recovery if you close without saving.\",\n      \"savingDesc\": \"Browsers cannot overwrite local files. Click Save File to download an updated HTML with all changes. Replace your old file to keep edits. An automatic backup is also saved to your browser's local storage and will prompt for recovery if you close without saving.\",\n      \"encryptionNote\": \"Encryption of data:\", \"encryptionDesc\": \"Check \\\"Encrypt\\\" before saving to password protect your data. Beware! No recovery possible without password!!\",\n      \"decryptionNote\": \"Decryption of data:\", \"decryptionDesc\": \"Export via JSON in top settings menu after successful password validation to decrypt your data. You can then reimport back into the same file and save!\"\n    },\n    \"desktop\": {\n      \"navigation\": \"Navigation\", \"management\": \"Management\",\n      \"arrowKeys\": \"Arrow Keys\", \"moveSelected1px\": \"Move selected 1px\",\n      \"shiftArrows\": \"Shift + Arrows\", \"moveSelected10px\": \"Move selected 10px\",\n      \"tabShiftTab\": \"Tab / Shift+Tab\", \"cycleNodes\": \"Cycle through nodes\",\n      \"fKey\": \"F\", \"focusSelected\": \"Focus on selected\",\n      \"spaceDrag\": \"Space + Drag\", \"panCanvas\": \"Pan canvas\",\n      \"lKey\": \"L\", \"lockUnlock\": \"Lock/unlock selected\",\n      \"gKey\": \"G\", \"groupUngroup\": \"Group/ungroup selected\",\n      \"ctrlCV\": \"Ctrl+C / Ctrl+V\", \"copyPaste\": \"Copy / Paste\",\n      \"ctrlD\": \"Ctrl+D\", \"duplicate\": \"Duplicate\",\n      \"ctrlA\": \"Ctrl+A\", \"selectAll\": \"Select all\",\n      \"deleteKey\": \"Delete\", \"deleteSelected\": \"Delete selected\",\n      \"escapeKey\": \"Escape\", \"clearSelection\": \"Clear selection\",\n      \"ctrlZY\": \"Ctrl+Z / Ctrl+Y\", \"undoRedo\": \"Undo / Redo\",\n      \"rightClick\": \"Right Click\", \"multiSelectToggle\": \"Multi select toggle\",\n      \"dblClickRack\": \"Double click rack\", \"enterRackView\": \"Enter rack view\",\n      \"dblClickConnection\": \"Double click connection\", \"addWaypoint\": \"Add waypoint\",\n      \"dblClickEmpty\": \"Double click empty (in rack)\", \"exitRackView\": \"Exit rack view\",\n      \"recording\": \"Recording\",\n      \"rKey\": \"R\", \"startStopRecording\": \"Start/stop real time recording\",\n      \"shiftR\": \"Shift+R\", \"startStopStepRecording\": \"Start/stop step by step recording\",\n      \"spaceKey\": \"Space\", \"addStepOrPlayPause\": \"Add step (step recording) / Play/Pause (playback)\",\n      \"pKey\": \"P\", \"playRecording\": \"Play recording\"\n    },\n    \"mobile\": {\n      \"basicGestures\": \"Basic Gestures\", \"rackView\": \"Rack View\",\n      \"tapNode\": \"Tap node\", \"selectOpenProperties\": \"Select & open properties\",\n      \"tapEmpty\": \"Tap empty\", \"deselectAll\": \"Deselect all\",\n      \"dragNode\": \"Drag node\", \"moveNode\": \"Move node\",\n      \"dragEmpty\": \"Drag empty\", \"panCanvas\": \"Pan canvas\",\n      \"pinch\": \"Pinch\", \"zoomInOut\": \"Zoom in/out\",\n      \"doubleTapNode\": \"Double tap node\", \"addRemoveSelection\": \"Add/remove from selection\",\n      \"longPressRack\": \"Long press rack\", \"enterRackView\": \"Enter rack view\",\n      \"longPressConnection\": \"Long press connection\", \"addWaypoint\": \"Add waypoint\",\n      \"doubleTapEmpty\": \"Double tap empty\", \"exitRackView\": \"Exit rack view\"\n    },\n    \"formats\": {\n      \"exportHelp\": \"Export Help\", \"importHelp\": \"Import Help\",\n      \"exportFormats\": \"Export Formats\", \"importFormats\": \"Import Formats\",\n      \"html\": \"HTML\", \"htmlDesc\": \"Complete self contained file and the default save method. When in doubt, choose this. Use the Save HTML button for this\", \"htmlImportDesc\": \"Import data from another saved HTML file. Extracts and restores all topology data from a previously exported file.\",\n      \"json\": \"JSON\", \"jsonExportDesc\": \"Full backup. Editable in your favorite text editor\", \"jsonImportDesc\": \"Replaces all data\",\n      \"markdown\": \"Markdown\", \"markdownExportDesc\": \"Full backup. Editable in your favorite text editor\", \"markdownImportDesc\": \"Replaces all data\",\n      \"csv\": \"CSV\", \"csvExportDesc\": \"Full backup in header, nodes in spreadsheet rows\", \"csvImportDesc\": \"Choose full restore or add nodes only. Allows quickly creating nodes with a spreadsheet and importing into an existing save.\",\n      \"png\": \"PNG\", \"pngDesc\": \"Standard image of current canvas tab\", \"pngImage\": \"PNG Image\",\n      \"svg\": \"SVG\", \"svgDesc\": \"Vector image, scalable and editable image of current canvas tab\", \"svgVector\": \"SVG Vector\"\n    },\n    \"recording\": {\n      \"title\": \"Canvas Recording\",\n      \"overview\": \"Record your canvas activity to create visual documentation, tutorials, or demonstrations of your network topology changes.\",\n      \"twoModes\": \"Two Recording Modes\",\n      \"realtimeTitle\": \"Real time Recording\",\n      \"realtimeDesc\": \"(red ● button) captures the canvas continuously at 10 frames per second as you work. Good for demonstrating workflows.\",\n      \"stepByStepTitle\": \"Step by Step Recording\",\n      \"stepByStepDesc\": \"(green ●+ button) lets you manually capture each frame. Click the green button to start, then press + or Space to capture each step. Each frame plays for 1 second. Good for tutorials where you want precise control over what's shown.\",\n      \"savedInFile\": \"Recordings are saved within your data file for later playback\",\n      \"exportVideo\": \"Can be exported as a video file (WebM format) for sharing\",\n      \"playWhileRecording\": \"Pressing Play while recording will stop and immediately play the new recording\",\n      \"controls\": \"Controls\",\n      \"recordBtn\": \"Record\", \"recordBtnDesc\": \"Start real time recording (captures 10 frames/sec). Press R\",\n      \"stepRecordBtn\": \"Step Record\", \"stepRecordBtnDesc\": \"Start step by step recording. Press Shift+R\",\n      \"addStepBtn\": \"Add Step\", \"addStepBtnDesc\": \"Capture current state as next frame (during step recording). Press Space\",\n      \"playBtn\": \"Play\", \"playBtnDesc\": \"Play back a saved recording. Press P\",\n      \"stopBtn\": \"Stop\", \"stopBtnDesc\": \"Stop recording or playback. Press R or Shift+R again\",\n      \"pauseBtn\": \"Pause\", \"pauseBtnDesc\": \"Pause playback. Press Space\",\n      \"scrubber\": \"Scrubber\", \"scrubberDesc\": \"Seek to any point in the recording\",\n      \"speed\": \"Speed\", \"speedDesc\": \"Adjust playback speed (0.5x, 1x, 2x, 4x)\",\n      \"loop\": \"Loop\", \"loopDesc\": \"Toggle continuous loop playback\",\n      \"manage\": \"Manage\", \"manageDesc\": \"View, rename, export, or delete saved recordings\"\n    }\n  },\n  \"pageLayoutHelp\": {\n    \"dragToResize\": \"Drag to Resize:\",\n    \"header\": \"Header:\", \"headerDesc\": \"Drag the bottom edge of the header bar\",\n    \"sidebar\": \"Sidebar:\", \"sidebarDesc\": \"Drag the left edge of the sidebar panel\",\n    \"mobile\": \"Mobile:\", \"mobileDesc\": \"Drag the top edge of the footer panel\",\n    \"hoverNote\": \"Hover over panel edges to see resize handles\"\n  },\n  \"confirmModal\": {\n    \"confirm\": \"Confirm\",\n    \"deleteLineQuestion\": \"Are you sure you want to delete this line?\"\n  },\n  \"drawToolbar\": {\n    \"filled\": \"Filled\", \"outlined\": \"Outlined\",\n    \"solid\": \"Solid\", \"dashed\": \"Dashed\", \"dotted\": \"Dotted\", \"wall\": \"Wall\",\n    \"noArrows\": \"No arrows\", \"arrowRight\": \"→ Right\", \"arrowLeft\": \"← Left\", \"arrowBoth\": \"↔ Both\",\n    \"undoLastPoint\": \"Undo last point\"\n  },\n  \"topologyToolbar\": {\n    \"addLineTo\": \"Add line to:\",\n    \"noArrows\": \"No arrows\", \"forward\": \"→ Forward\", \"backward\": \"← Backward\", \"bidirectional\": \"↔ Bidirectional\"\n  },\n  \"edgePanel\": {\n    \"customLine\": \"Custom line\",\n    \"animate\": \"Animate\"\n  },\n  \"rectPanel\": {\n    \"zone\": \"Zone\", \"fillColor\": \"Fill Color:\", \"fillOpacity\": \"Fill Opacity:\", \"borderColor\": \"Border Color:\", \"borderWidth\": \"Border Width:\",\n    \"lineStyle\": \"Line Style:\", \"deleteZone\": \"Delete Zone\"\n  },\n  \"textPanel\": {\n    \"textElement\": \"Text Element\", \"content\": \"Content:\", \"fontSizeLabel\": \"Font Size:\", \"colorLabel\": \"Color:\",\n    \"fontWeight\": \"Font Weight:\", \"bold\": \"Bold\", \"semiBold\": \"Semi-Bold\", \"light\": \"Light\",\n    \"fontStyle\": \"Font Style:\", \"italic\": \"Italic\",\n    \"textAlign\": \"Text Align:\", \"textDecoration\": \"Text Decoration:\", \"underline\": \"Underline\", \"strikeThrough\": \"Strike Through\",\n    \"enable\": \"Enable\", \"deleteText\": \"Delete Text\"\n  },\n  \"tabsModal\": {\n    \"manageTopologies\": \"Manage multiple topologies\",\n    \"mainTopology\": \"Main Topology\",\n    \"nodes\": \"nodes\", \"connections\": \"connections\",\n    \"newTabPlaceholder\": \"New tab name...\",\n    \"addTab\": \"+ Add Tab\"\n  },\n  \"auditModal\": {\n    \"auditLogTitle\": \"Audit Log\",\n    \"allEvents\": \"All Events\", \"create\": \"Create\", \"update\": \"Update\", \"delete\": \"Delete\",\n    \"importEvent\": \"Import\", \"exportEvent\": \"Export\", \"layerChanges\": \"Layer Changes\",\n    \"noEvents\": \"No audit events recorded yet\"\n  },\n  \"portMapModal\": {\n    \"portMapTitle\": \"Port Map\",\n    \"exportCsv\": \"Export CSV\"\n  },\n  \"secretEditor\": {\n    \"editNote\": \"Edit Note\",\n    \"enterSensitiveInfo\": \"Enter sensitive information here...\",\n    \"encryptNote\": \"Encrypt Note\",\n    \"noNotesYet\": \"No notes yet\"\n  },\n  \"mobileExport\": {\n    \"pngImage\": \"PNG Image\", \"svgVector\": \"SVG Vector\", \"jsonFullBackup\": \"JSON (Full Backup)\",\n    \"markdownExport\": \"Markdown\", \"csvExport\": \"CSV\",\n    \"jsonBackup\": \"JSON (Full Backup)\", \"markdown\": \"Markdown\",\n    \"csv\": \"CSV\", \"printBW\": \"Print B&W\", \"printColorFull\": \"Print Full Color\", \"printColorWhite\": \"Print White BG\", \"printColorBlack\": \"Print Black BG\", \"exportHelp\": \"Export Help\"\n  },\n  \"mobileImport\": {\n    \"jsonImport\": \"JSON\", \"markdownImport\": \"Markdown\",\n    \"json\": \"JSON\", \"markdown\": \"Markdown\", \"csv\": \"CSV\", \"importHelp\": \"Import Help\"\n  },\n  \"recording\": {\n    \"startRecording\": \"Start recording\", \"stop\": \"Stop\", \"playRecording\": \"Play recording\", \"pause\": \"Pause\",\n    \"playbackSpeed\": \"Playback speed\", \"loop\": \"Loop\", \"manageRecordings\": \"Manage recordings\",\n    \"playbackPosition\": \"Playback position\", \"exportVideo\": \"Export Video\",\n    \"selectOrCreate\": \"Select a recording or create a new one\",\n    \"newRecordingPlaceholder\": \"New recording name...\",\n    \"recording\": \"Recording\"\n  },\n  \"welcome\": {\n    \"title\": \"Welcome to The One File\",\n    \"subtitle\": \"What are you mapping today?\",\n    \"chooseMode\": \"Choose your mode:\",\n    \"chooseCanvas\": \"Choose canvas style:\",\n    \"chooseTheme\": \"Choose a theme:\",\n    \"customColors\": \"Customize colors:\",\n    \"startMapping\": \"Start Mapping\",\n    \"skip\": \"Skip\",\n    \"canvasStyle\": \"Canvas Style\",\n    \"theme\": \"Theme\",\n    \"colorCustomization\": \"Color Customization\",\n    \"canvas\": {\n      \"grid\": \"Standard Grid\", \"dots\": \"Dot Grid\",\n      \"blueprint\": \"Blueprint Grid\", \"none\": \"No Grid\"\n    },\n    \"modes\": {\n      \"network\": \"Network Topology\", \"networkDesc\": \"Servers, routers, switches\",\n      \"mindmap\": \"Mind Map\", \"mindmapDesc\": \"Ideas, concepts, brainstorming\",\n      \"sports\": \"Sports Playbook\", \"sportsDesc\": \"Plays, formations, positions\",\n      \"smarthome\": \"Smart Home\", \"smarthomeDesc\": \"IoT devices, automation\",\n      \"floorplan\": \"Floor Plan / Blueprint\", \"floorplanDesc\": \"Rooms, layouts, physical spaces\"\n    },\n    \"colors\": {\n      \"mainBg\": \"Top Bar Background\", \"accent\": \"Accent\",\n      \"primaryText\": \"Primary Text\", \"secondaryText\": \"Secondary Text\",\n      \"sidebarPanel\": \"Main Background\", \"modalWindows\": \"Modal Background\",\n      \"dangerButtons\": \"Grid Color\", \"mobileFooter\": \"Sidebar Background\",\n      \"moreStyles\": \"More styles available in main settings panel\"\n    }\n  },\n  \"bulkToolbar\": {\n    \"selected\": \"Selected:\",\n    \"alignLeft\": \"⬅ Left\", \"alignRight\": \"➡ Right\", \"alignTop\": \"⬆ Top\", \"alignBottom\": \"⬇ Bottom\",\n    \"alignLeftShort\": \"Align Left\", \"alignRightShort\": \"Align Right\", \"alignTopShort\": \"Align Top\", \"alignBottomShort\": \"Align Bottom\",\n    \"distributeH\": \"↔ Distribute H\", \"distributeV\": \"↕ Distribute V\",\n    \"distributeHShort\": \"Distribute H\", \"distributeVShort\": \"Distribute V\",\n    \"clone\": \"📋 Clone\", \"cloneAll\": \"Clone All\",\n    \"copyZone\": \"📡 Copy Zone\", \"pasteZone\": \"📡 Paste Zone\", \"toggleZones\": \"📡 Toggle Zones\",\n    \"copyZoneShort\": \"Copy Zone\", \"pasteZoneShort\": \"Paste Zone\", \"toggleZonesShort\": \"Toggle Zones\",\n    \"lockToggle\": \"Lock Toggle\", \"groupToggle\": \"Group Toggle\",\n    \"deleteAll\": \"Delete All\", \"clearSelection\": \"Clear Selection\",\n    \"coverageZones\": \"Coverage Zones\",\n    \"nodesSelected\": \"Nodes Selected\"\n  },\n  \"canvasHints\": {\n    \"scrollZoom\": \"Scroll to zoom\",\n    \"dragPan\": \"Drag to pan\",\n    \"rightClickClone\": \"Right click to clone and align\",\n    \"rightClickMulti\": \"Right click to select multiple\",\n    \"shiftMarquee\": \"Hold Shift + drag mouse for marquee selection\",\n    \"power\": \"You have the power\",\n    \"timeNow\": \"Your time is NOW!\"\n  },\n  \"legend\": {\n    \"connectionLegend\": \"Connection Legend\"\n  },\n  \"nodePanel\": {\n    \"connections\": \"Connections\",\n    \"styling\": \"Styling\",\n    \"screen\": \"Screen:\",\n    \"tablet\": \"Tablet\", \"fold\": \"Fold\",\n    \"fillColor\": \"Fill Color:\", \"borderColor\": \"Border Color:\",\n    \"titleFont\": \"Title Font:\", \"subColor\": \"Sub Color:\", \"subFont\": \"Sub Font:\", \"subSize\": \"Sub Size:\",\n    \"systemUI\": \"System UI\", \"serif\": \"Serif\", \"cursive\": \"Cursive\", \"courier\": \"Courier\", \"times\": \"Times\",\n    \"textPosition\": \"Text Position\",\n    \"nameY\": \"Name Y:\", \"nameX\": \"Name X:\", \"ipY\": \"IP Y:\", \"ipX\": \"IP X:\",\n    \"resetStyles\": \"Reset Styles\",\n    \"nodesInRack\": \"Nodes in Rack\",\n    \"uHeight\": \"U Height:\",\n    \"layer\": \"Layer:\",\n    \"assignedRack\": \"Assigned Rack:\",\n    \"hostedOn\": \"Hosted On:\",\n    \"rackCapacity\": \"Rack Capacity:\",\n    \"physical\": \"Physical\",\n    \"logical\": \"Logical\",\n    \"security\": \"Security\",\n    \"application\": \"Application\",\n    \"mac\": \"MAC:\",\n    \"rackUnit\": \"Rack Unit:\",\n    \"searchWebIcons\": \"Or Search Web Icons\",\n    \"monitorUrl\": \"Monitor URL:\",\n    \"status\": \"Status:\",\n    \"pingStatusMonitoring\": \"Ping / Status Monitoring\",\n    \"pingIndicatorPosition\": \"Ping Indicator Position\",\n    \"pingX\": \"Ping X:\",\n    \"pingY\": \"Ping Y:\",\n    \"enableStatusCheck\": \"Enable status check for this node\",\n    \"protocol\": \"Protocol:\",\n    \"httpPort80\": \"HTTP (port 80)\",\n    \"httpsPort443\": \"HTTPS (port 443)\",\n    \"customUrl\": \"Custom URL\",\n    \"customUrlLabel\": \"Custom URL:\",\n    \"timeoutMs\": \"Timeout (ms):\",\n    \"currentStatus\": \"Current Status:\",\n    \"lastChecked\": \"Last checked:\",\n    \"checkStatusNow\": \"Check Status Now\",\n    \"motionTrail\": \"Motion Trail:\"\n  },\n  \"langEditor\": {\n    \"tabs\": {\n      \"all\": \"All\", \"modes\": \"Modes\", \"network\": \"Network\", \"mindMap\": \"Mind Map\",\n      \"sports\": \"Sports\", \"smartHome\": \"Smart Home\", \"floorPlan\": \"Floor Plan\",\n      \"ui\": \"UI\", \"shapes\": \"Shapes\", \"messages\": \"Messages\"\n    },\n    \"searchPlaceholder\": \"Search strings...\"\n  },\n  \"auditLog\": {\n    \"allEvents\": \"All Events\", \"nodeOperations\": \"Node Operations\",\n    \"connections\": \"Connections\", \"styleChanges\": \"Style Changes\",\n    \"rackOperations\": \"Rack Operations\", \"layerChanges\": \"Layer Changes\"\n  },\n  \"portMap\": {\n    \"searchPlaceholder\": \"Search devices or ports...\",\n    \"allConnections\": \"All Connections\", \"withPorts\": \"With Ports Only\",\n    \"missingPorts\": \"Missing Ports\", \"noConnections\": \"No connections found\"\n  },\n  \"notes\": {\n    \"noNotes\": \"No notes yet\",\n    \"namePlaceholder\": \"Note name (e.g., 'Root Passwords')...\",\n    \"editNote\": \"Edit Note\",\n    \"contentPlaceholder\": \"Enter sensitive information here...\",\n    \"encryptNote\": \"Encrypt Note\"\n  },\n  \"recordings\": {\n    \"noRecordings\": \"No recordings yet. Press Record to start.\",\n    \"exportVideo\": \"Export Video\"\n  },\n  \"trails\": {\n    \"title\": \"Motion Trails\",\n    \"enableTrails\": \"Enable Trails:\",\n    \"trailLength\": \"Trail Length:\",\n    \"trailStyle\": \"Trail Style:\",\n    \"styleSolid\": \"Solid Fade\",\n    \"styleDashed\": \"Dashed\",\n    \"styleDots\": \"Dots\",\n    \"showArrow\": \"Show Direction Arrow:\",\n    \"perNodeHint\": \"Tip: Enable/disable trails per node in the node panel\"\n  },\n  \"images\": {\n    \"title\": \"Canvas Image\",\n    \"name\": \"Name:\",\n    \"addImage\": \"Add Image\",\n    \"dropHint\": \"Drop image here or click to browse\",\n    \"maxImages\": \"Maximum images reached (40)\",\n    \"opacity\": \"Opacity:\",\n    \"border\": \"Border:\",\n    \"shadow\": \"Shadow:\",\n    \"size\": \"Size:\",\n    \"delete\": \"Delete Image\",\n    \"none\": \"None\",\n    \"thin\": \"Thin\",\n    \"medium\": \"Medium\",\n    \"thick\": \"Thick\",\n    \"small\": \"Small\",\n    \"large\": \"Large\",\n    \"imageNotes\": \"Image Notes\"\n  },\n  \"rack\": {\n    \"capacity42\": \"42U (Standard Full Rack)\",\n    \"capacity48\": \"48U (Large Rack)\",\n    \"capacity24\": \"24U (Half Rack)\",\n    \"capacity12\": \"12U (Small/Wall Mount)\",\n    \"capacity6\": \"6U (Mini Rack)\"\n  },\n  \"zone\": {\n    \"coverageZone\": \"Coverage Zone\",\n    \"preset\": \"Preset:\",\n    \"applyPreset\": \"-- Apply Preset --\",\n    \"presets\": {\n      \"securityCam\": \"Security Camera\",\n      \"ptzCam\": \"PTZ Camera\",\n      \"motionDetector\": \"Motion Detector\",\n      \"wifi\": \"WiFi\",\n      \"wifiExtender\": \"WiFi Extender\",\n      \"smokeAlarm\": \"Smoke Alarm\",\n      \"sprinklerArc\": \"Sprinkler Arc\"\n    },\n    \"showZone\": \"Show Zone:\",\n    \"angle\": \"Angle:\",\n    \"distance\": \"Distance:\",\n    \"innerRadius\": \"Inner Radius:\",\n    \"rotation\": \"Rotation:\",\n    \"fill\": \"Fill\",\n    \"border\": \"Border\",\n    \"color\": \"Color:\",\n    \"opacity\": \"Opacity:\",\n    \"gradient\": \"Gradient:\",\n    \"fadeTowardEdge\": \"Fade toward edge\",\n    \"width\": \"Width:\",\n    \"style\": \"Style:\",\n    \"label\": \"Label\",\n    \"text\": \"Text:\",\n    \"labelPlaceholder\": \"e.g. Detection Zone\",\n    \"position\": \"Position:\",\n    \"edge\": \"Edge\",\n    \"outside\": \"Outside\",\n    \"fontSize\": \"Font Size:\",\n    \"fontColor\": \"Font Color:\",\n    \"bold\": \"Bold:\",\n    \"background\": \"Background:\",\n    \"offsetX\": \"Offset X:\",\n    \"offsetY\": \"Offset Y:\",\n    \"animation\": \"Animation\",\n    \"animate\": \"Animate:\",\n    \"type\": \"Type:\",\n    \"sweep\": \"Sweep:\",\n    \"speed\": \"Speed:\"\n  },\n  \"iconPicker\": {\n    \"mdi\": \"MDI\",\n    \"simpleIcons\": \"Simple Icons\",\n    \"selfhst\": \"selfh.st/icons\"\n  },\n  \"language\": {\n    \"exportLang\": \"Export Language File\",\n    \"importLang\": \"Import Language File\",\n    \"resetLang\": \"Reset to English\",\n    \"editStrings\": \"Edit Strings\",\n    \"editorTitle\": \"Language Editor\",\n    \"searchStrings\": \"Search strings...\",\n    \"saveChanges\": \"Save Changes\",\n    \"all\": \"All\",\n    \"uiGeneral\": \"UI/General\"\n  }\n}</script>\n    <script id=\"topology-state\" type=\"application/json\">{\n  \"nodeData\": {},\n  \"edgeData\": {\n    \"list\": []\n  },\n  \"rectData\": {\n    \"list\": []\n  },\n  \"textData\": {\n    \"list\": []\n  },\n  \"edgeLegend\": {},\n  \"zoneLegend\": {},\n  \"zonePresets\": {},\n  \"nodePositions\": {},\n  \"nodeSizes\": {},\n  \"nodeStyles\": {},\n  \"iconCache\": {},\n  \"page\": {\n    \"title\": \"The One File: The Networkening\",\n    \"background\": \"\",\n    \"topbarBg\": \"rgba(9, 12, 20, 0.9)\",\n    \"topbarBorder\": \"#1f2533\",\n    \"panel\": \"#0b0e13\",\n    \"panelAlt\": \"#10141b\",\n    \"accent\": \"#4fd1c5\",\n    \"sidebarBg\": \"#10141b\",\n    \"btnBg\": \"#0b0e13\",\n    \"btnText\": \"#e2e8f0\",\n    \"tagFill\": \"#1e293b\",\n    \"tagText\": \"#e2e8f0\",\n    \"tagBorder\": \"#475569\",\n    \"inputBg\": \"#0b0e13\",\n    \"inputText\": \"#e2e8f0\",\n    \"inputBorder\": \"#1f2937\",\n    \"inputFont\": \"Inter, system-ui, sans-serif\",\n    \"inputFontSize\": 14,\n    \"toolbarBg\": \"#0f172a\",\n    \"toolbarBorder\": \"#1f2937\",\n    \"toolbarText\": \"#94a3b8\",\n    \"toolbarBtnBg\": \"#0b0e13\",\n    \"toolbarBtnText\": \"#e2e8f0\",\n    \"minimapDots\": \"#94a3b8\",\n    \"canvasHintEnabled\": true,\n    \"canvasHintText\": \"\",\n    \"canvasHintBg\": \"#0f172a\",\n    \"canvasHintColor\": \"#94a3b8\",\n    \"danger\": \"#f56565\",\n    \"textMain\": \"#e2e8f0\",\n    \"textSoft\": \"#94a3b8\",\n    \"topbarHeight\": 100,\n    \"sidebarWidth\": 435,\n    \"mobileFooterHeight\": 20,\n    \"sidebarCollapsed\": false,\n    \"nodeFill\": \"#1e293b\",\n    \"nodeStroke\": \"#475569\",\n    \"nodeTitle\": \"#e2e8f0\",\n    \"nodeSub\": \"#94a3b8\",\n    \"nodeTitleSize\": 18,\n    \"nodeSubSize\": 13,\n    \"nodeFont\": \"Inter, system-ui, sans-serif\",\n    \"defaultEdge\": \"#475569\",\n    \"selectionHandle\": \"#f59e0b\",\n    \"selectionHandleSize\": 8,\n    \"groupIndicator\": \"#4fd1c5\",\n    \"canvasGradientTop\": \"#1e2532\",\n    \"canvasGradientBottom\": \"#050608\",\n    \"canvasBorder\": \"#475569\",\n    \"canvasGrid\": \"#475569\",\n    \"canvasGridSize\": 50,\n    \"rackFrameFill\": \"#0f172a\",\n    \"rackFrameStroke\": \"#4fd1c5\",\n    \"rackLineColor\": \"#475569\",\n    \"rackTextColor\": \"#4fd1c5\",\n    \"viewOnly\": false,\n    \"autoPingEnabled\": false,\n    \"autoPingInterval\": 30\n  },\n  \"autoPingEnabled\": false,\n  \"autoPingInterval\": 30,\n  \"canvas\": {\n    \"zoom\": 1,\n    \"panX\": 0,\n    \"panY\": 0\n  },\n  \"savedTopologyView\": null,\n  \"documentTabs\": [\n    {\n      \"id\": \"main\",\n      \"name\": \"Main Topology\",\n      \"nodes\": {},\n      \"edges\": {\n        \"list\": []\n      },\n      \"positions\": {},\n      \"sizes\": {},\n      \"styles\": {},\n      \"legend\": {},\n      \"rects\": {\n        \"list\": []\n      },\n      \"texts\": {\n        \"list\": []\n      },\n      \"pageState\": {\n        \"title\": \"The One File: The Networkening\",\n        \"background\": \"\",\n        \"topbarBg\": \"rgba(9, 12, 20, 0.9)\",\n        \"topbarBorder\": \"#1f2533\",\n        \"panel\": \"#0b0e13\",\n        \"panelAlt\": \"#10141b\",\n        \"accent\": \"#4fd1c5\",\n        \"sidebarBg\": \"#10141b\",\n        \"btnBg\": \"#0b0e13\",\n        \"btnText\": \"#e2e8f0\",\n        \"tagFill\": \"#1e293b\",\n        \"tagText\": \"#e2e8f0\",\n        \"tagBorder\": \"#475569\",\n        \"inputBg\": \"#0b0e13\",\n        \"inputText\": \"#e2e8f0\",\n        \"inputBorder\": \"#1f2937\",\n        \"inputFont\": \"Inter, system-ui, sans-serif\",\n        \"inputFontSize\": 14,\n        \"toolbarBg\": \"#0f172a\",\n        \"toolbarBorder\": \"#1f2937\",\n        \"toolbarText\": \"#94a3b8\",\n        \"toolbarBtnBg\": \"#0b0e13\",\n        \"toolbarBtnText\": \"#e2e8f0\",\n        \"minimapDots\": \"#94a3b8\",\n        \"canvasHintEnabled\": true,\n        \"canvasHintText\": \"\",\n        \"canvasHintBg\": \"#0f172a\",\n        \"canvasHintColor\": \"#94a3b8\",\n        \"danger\": \"#f56565\",\n        \"textMain\": \"#e2e8f0\",\n        \"textSoft\": \"#94a3b8\",\n        \"topbarHeight\": 100,\n        \"sidebarWidth\": 435,\n        \"mobileFooterHeight\": 20,\n        \"sidebarCollapsed\": false,\n        \"nodeFill\": \"#1e293b\",\n        \"nodeStroke\": \"#475569\",\n        \"nodeTitle\": \"#e2e8f0\",\n        \"nodeSub\": \"#94a3b8\",\n        \"nodeTitleSize\": 18,\n        \"nodeSubSize\": 13,\n        \"nodeFont\": \"Inter, system-ui, sans-serif\",\n        \"defaultEdge\": \"#475569\",\n        \"selectionHandle\": \"#f59e0b\",\n        \"selectionHandleSize\": 8,\n        \"groupIndicator\": \"#4fd1c5\",\n        \"canvasGradientTop\": \"#1e2532\",\n        \"canvasGradientBottom\": \"#050608\",\n        \"canvasBorder\": \"#475569\",\n        \"canvasGrid\": \"#475569\",\n        \"canvasGridSize\": 50,\n        \"rackFrameFill\": \"#0f172a\",\n        \"rackFrameStroke\": \"#4fd1c5\",\n        \"rackLineColor\": \"#475569\",\n        \"rackTextColor\": \"#4fd1c5\",\n        \"viewOnly\": false,\n        \"autoPingEnabled\": false,\n        \"autoPingInterval\": 30\n      }\n    }\n  ],\n  \"currentTabIndex\": 0,\n  \"encryptedSections\": {},\n  \"auditLog\": []\n}</script>\n    <script>\n\tObject.keys(localStorage).forEach(key => {\n        if (key.startsWith('icon-')) {\n          localStorage.removeItem(key);\n        }\n      });\n      const IconLibrary = {\n       libraries: {\n        mdi: {\n         name: 'Material Design Icons',\n         cdnBase: 'https://cdn.jsdelivr.net/npm/@mdi/svg@latest/svg/',\n         metaUrl: 'https://cdn.jsdelivr.net/npm/@mdi/svg@latest/meta.json',\n         icons: []\n        },\n        simple: {\n         name: 'Simple Icons',\n         cdnBase: 'https://cdn.jsdelivr.net/npm/simple-icons@latest/icons/',\n         indexUrl: 'https://cdn.jsdelivr.net/npm/@iconify-json/simple-icons@latest/icons.json',\n         icons: []\n        },\n        selfhst: {\n         name: 'selfh.st/icons',\n         cdnBase: 'https://cdn.jsdelivr.net/gh/selfhst/icons@master/svg/',\n         indexUrl: 'https://raw.githubusercontent.com/selfhst/icons/refs/heads/main/index.json',\n         icons: []\n        }\n       },\n       currentLibrary: 'selfhst',\n       iconCache: {},\n       indexCache: {},\n       indexLoading: {},\n       async loadLibraryIndex(library) {\n        if (this.indexCache[library]) {\n         return this.indexCache[library];\n        }\n        if (this.indexLoading[library]) {\n         return this.indexLoading[library];\n        }\n        const lib = this.libraries[library];\n        this.indexLoading[library] = (async () => {\n         try {\n          if (library === 'selfhst') {\n           const response = await fetch(lib.indexUrl);\n           const data = await response.json();\n           const icons = data.filter(item => item.SVG === \"Yes\").map(item => ({\n            name: item.Reference,\n            displayName: item.Name,\n            tags: item.Tags ? item.Tags.split(',').map(t => t.trim()).filter(t => t) : [],\n            category: item.Category\n           }));\n           this.indexCache[library] = icons;\n           lib.icons = icons;\n           return icons;\n          } else if (library === 'simple') {\n           const response = await fetch('https://cdn.jsdelivr.net/npm/@iconify-json/simple-icons@latest/icons.json');\n           const data = await response.json();\n           const icons = Object.keys(data.icons).map(slug => ({\n            name: slug,\n            displayName: data.icons[slug].title || slug,\n            tags: data.aliases && data.aliases[slug] ? [data.aliases[slug].parent] : [],\n            hex: data.icons[slug].hex\n           }));\n           this.indexCache[library] = icons;\n           lib.icons = icons;\n           return icons;\n          } else if (library === 'mdi') {\n           const response = await fetch(lib.metaUrl);\n           const data = await response.json();\n           const icons = data.map(item => ({\n            name: item.name,\n            displayName: item.name,\n            tags: item.tags || [],\n            author: item.author\n           }));\n           this.indexCache[library] = icons;\n           lib.icons = icons;\n           return icons;\n          }\n         } catch (error) {\n          console.error(`Failed to load index for ${library}:`, error);\n          this.indexCache[library] = [];\n          lib.icons = [];\n          return [];\n         } finally {\n          delete this.indexLoading[library];\n         }\n        })();\n        return this.indexLoading[library];\n       },\n       async getIcon(library, name) {\n        const cacheKey = `${library}-${name}`;\n        if (this.iconCache[cacheKey]) {\n         return this.iconCache[cacheKey];\n        }\n        const lib = this.libraries[library];\n        const url = `${lib.cdnBase}${name}.svg`;\n        try {\n         const response = await fetch(url);\n         if (!response.ok) {\n          throw new Error(`HTTP ${response.status}`);\n         }\n         const svg = await response.text();\n         this.iconCache[cacheKey] = svg;\n         return svg;\n        } catch (error) {\n         console.error(`Failed to fetch icon ${cacheKey}:`, error);\n         return null;\n        }\n       },\n       searchIcons(library, query) {\n        const lib = this.libraries[library];\n        if (!lib.icons.length) return [];\n        const q = query.toLowerCase();\n        return lib.icons.filter(icon => {\n         const nameMatch = icon.name.toLowerCase().includes(q);\n         const displayMatch = icon.displayName && icon.displayName.toLowerCase().includes(q);\n         const tagMatch = icon.tags && icon.tags.some(t => t.toLowerCase().includes(q));\n         const categoryMatch = icon.category && icon.category.toLowerCase().includes(q);\n         return nameMatch || displayMatch || tagMatch || categoryMatch;\n        }).slice(0, 50);\n       }\n      };\n      let iconPickerCallback = null;\n      let selectedNodeIconData = null;\n      let selectedRackIconData = null;\n      let newNodeIconTags = [];\n      let freeDrawMode = false;\n      async function checkNodeStatus(nodeId) {\n       const data = NODE_DATA[nodeId];\n       if (!data || !data.ping || !data.ping.enabled) return;\n       data.ping.status = 'checking';\n       data.ping.lastCheck = new Date().toISOString();\n       data.ping.responseTime = null;\n       updatePingIndicator(nodeId);\n       if (currentNodeId === nodeId) {\n        updatePingStatusDisplay(nodeId);\n       }\n       let url;\n       if (data.ping.protocol === 'custom') {\n        url = data.ping.customUrl;\n       } else {\n        const ip = data.ip || '0.0.0.0';\n        const protocol = data.ping.protocol || 'http';\n        url = `${protocol}://${ip}`;\n       }\n       if (!url) {\n        data.ping.status = 'unknown';\n        updatePingIndicator(nodeId);\n        if (currentNodeId === nodeId) {\n         updatePingStatusDisplay(nodeId);\n        }\n        return;\n       }\n       const timeout = data.ping.timeout || 3000;\n       const startTime = performance.now();\n       try {\n        const result = await Promise.race([\n         new Promise((resolve, reject) => {\n          const img = new Image();\n          img.onload = () => resolve('online');\n          img.onerror = () => resolve('check-fetch');\n          img.src = `${url}/favicon.ico?_=${Date.now()}`;\n         }),\n         new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), timeout))\n        ]);\n        if (result === 'check-fetch') {\n         await Promise.race([\n          fetch(url, { method: 'HEAD', mode: 'no-cors' }),\n          new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), timeout))\n         ]);\n        }\n        data.ping.status = 'online';\n        data.ping.responseTime = Math.round(performance.now() - startTime);\n       } catch (error) {\n        data.ping.status = 'offline';\n        data.ping.responseTime = null;\n       }\n       data.ping.lastCheck = new Date().toISOString();\n       updatePingIndicator(nodeId);\n       if (currentNodeId === nodeId) {\n        updatePingStatusDisplay(nodeId);\n       }\n      }\n      function rgbaToHex(val) {\n      if (!val) return \"#000000\";\n      if (val.startsWith(\"#\")) return val;\n      const m = val.match(/rgba?\\((\\d+),\\s*(\\d+),\\s*(\\d+)/);\n      if (!m) return \"#000000\";\n      const r = Number(m[1]).toString(16).padStart(2, \"0\");\n      const g = Number(m[2]).toString(16).padStart(2, \"0\");\n      const b = Number(m[3]).toString(16).padStart(2, \"0\");\n      return `#${r}${g}${b}`;\n      }\n      function updatePingIndicator(nodeId) {\n      const nodeGroup = document.querySelector(`g[data-node-id=\"${nodeId}\"]`);\n      if (!nodeGroup) return;\n      const data = NODE_DATA[nodeId];\n      if (!data || !data.ping || !data.ping.enabled) {\n       const existingIndicator = nodeGroup.querySelector('.ping-indicator');\n       if (existingIndicator) existingIndicator.remove();\n       return;\n      }\n      let indicator = nodeGroup.querySelector('.ping-indicator');\n      const label = nodeGroup.querySelector('.node-label');\n      if (!indicator && label) {\n       indicator = document.createElementNS('http://www.w3.org/2000/svg', 'circle');\n       indicator.classList.add('ping-indicator');\n       nodeGroup.appendChild(indicator);\n      }\n      if (indicator && label) {\n       const size = savedSizes[nodeId] || getDefaultSize();\n       const radius = Math.max(4, size * 0.06);\n       indicator.setAttribute('r', radius);\n       const labelBBox = label.getBBox();\n       const labelX = parseFloat(label.getAttribute('x') || 0);\n       const labelY = parseFloat(label.getAttribute('y') || 0);\n       const styles = resolveStylesForNode(nodeId);\n      const offX = styles.pingOffsetX || 0;\n      const offY = styles.pingOffsetY || 0;\n      indicator.setAttribute('cx', (labelX - labelBBox.width / 2 - radius * 1.1) + offX);\n      indicator.setAttribute('cy', (labelY - radius * 0.7) + offY);\n      }\n      if (indicator) {\n       indicator.classList.remove('online', 'offline', 'checking');\n       if (data.ping.status) indicator.classList.add(data.ping.status);\n      }\n      }\n      async function checkAllNodesStatus() {\n       const nodesToCheck = Object.keys(NODE_DATA).filter(nodeId => {\n        const data = NODE_DATA[nodeId];\n        return data && data.ping && data.ping.enabled;\n       });\n       const batchSize = 5;\n       for (let i = 0; i < nodesToCheck.length; i += batchSize) {\n        const batch = nodesToCheck.slice(i, i + batchSize);\n        await Promise.all(batch.map(nodeId => checkNodeStatus(nodeId)));\n       }\n      }\n      function startAutoPing() {\n       stopAutoPing();\n       checkAllNodesStatus();\n       updateAutoPingLastRun();\n       autoPingSecondsRemaining = autoPingInterval;\n       autoPingTimer = setInterval(() => {\n        checkAllNodesStatus();\n        updateAutoPingLastRun();\n        autoPingSecondsRemaining = autoPingInterval;\n       }, autoPingInterval * 1000);\n       autoPingCountdown = setInterval(() => {\n        autoPingSecondsRemaining--;\n        updateAutoPingCountdown();\n        if (autoPingSecondsRemaining <= 0) {\n         autoPingSecondsRemaining = autoPingInterval;\n        }\n       }, 1000);\n       updateAutoPingCountdown();\n      }\n      function stopAutoPing() {\n       if (autoPingTimer) {\n        clearInterval(autoPingTimer);\n        autoPingTimer = null;\n       }\n       if (autoPingCountdown) {\n        clearInterval(autoPingCountdown);\n        autoPingCountdown = null;\n       }\n       autoPingSecondsRemaining = 0;\n       updateAutoPingCountdown();\n      }\n      function updateAutoPingCountdown() {\n       const nextCheckEl = document.getElementById('auto-ping-next-check');\n       if (nextCheckEl) {\n        if (autoPingSecondsRemaining > 0 && autoPingEnabled) {\n         const mins = Math.floor(autoPingSecondsRemaining / 60);\n         const secs = autoPingSecondsRemaining % 60;\n         if (mins > 0) {\n          nextCheckEl.textContent = `Next check in: ${mins}m ${secs}s`;\n         } else {\n          nextCheckEl.textContent = `Next check in: ${secs}s`;\n         }\n        } else {\n         nextCheckEl.textContent = 'Next check in: --';\n        }\n       }\n      }\n      function updateAutoPingLastRun() {\n       const lastRunEl = document.getElementById('auto-ping-last-run');\n       if (lastRunEl) {\n        const now = new Date();\n        lastRunEl.textContent = `Last run: ${now.toLocaleTimeString()}`;\n       }\n      }\n      function openIconPicker(callback) {\n       iconPickerCallback = callback;\n       const modal = document.getElementById('icon-picker-modal');\n       modal.classList.add('active');\n       const searchInput = document.getElementById('icon-search');\n       searchInput.style.display = 'none';\n       loadIconsForCurrentLibrary();\n      }\n      function closeIconPicker() {\n       const modal = document.getElementById('icon-picker-modal');\n       modal.classList.remove('active');\n       iconPickerCallback = null;\n      }\n      async function loadIconsForCurrentLibrary() {\n       const body = document.getElementById('icon-picker-body');\n       const libNames = {\n        mdi: 'MDI (Material Design Icons)',\n        simple: 'Simple Icons',\n        selfhst: 'selfh.st/icons'\n       };\n       body.innerHTML = `<div style=\"padding: 20px;\"><p style=\"color: var(--text-soft); margin-bottom: 15px; text-align: center;\">Search ${libNames[IconLibrary.currentLibrary]}:</p><input type=\"text\" id=\"icon-search-field\" placeholder=\"Search icons...\" style=\"width: 100%; padding: 12px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 6px; color: var(--text-main); font-size: 16px; margin-bottom: 20px;\"><div id=\"icon-grid-container\" style=\"max-height: 400px; overflow-y: auto;\"><div style=\"text-align: center; color: var(--text-soft); padding: 40px;\">Loading icons...</div></div></div>`;\n       const searchField = document.getElementById('icon-search-field');\n       const gridContainer = document.getElementById('icon-grid-container');\n       await IconLibrary.loadLibraryIndex(IconLibrary.currentLibrary);\n       const renderIcons = (icons) => {\n        if (!icons || icons.length === 0) {\n         gridContainer.innerHTML = '<div style=\"text-align: center; color: var(--text-soft); padding: 40px;\">No icons found</div>';\n         return;\n        }\n        const grid = document.createElement('div');\n        grid.style.cssText = 'display: grid; grid-template-columns: repeat(auto-fill, minmax(100px, 1fr)); gap: 15px; padding: 10px;';\n        icons.forEach(icon => {\n         const item = document.createElement('div');\n         item.style.cssText = 'display: flex; flex-direction: column; align-items: center; gap: 8px; padding: 15px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 6px; cursor: pointer; transition: all 0.2s;';\n         item.onmouseover = () => {\n          item.style.background = 'var(--panel)';\n          item.style.borderColor = 'var(--accent)';\n         };\n         item.onmouseout = () => {\n          item.style.background = 'var(--panel-alt)';\n          item.style.borderColor = 'var(--edge-main)';\n         };\n         const iconPreview = document.createElement('div');\n         iconPreview.style.cssText = 'width: 48px; height: 48px; display: flex; align-items: center; justify-content: center;';\n         iconPreview.innerHTML = '<div style=\"color: var(--text-soft); font-size: 12px;\">...</div>';\n         IconLibrary.getIcon(IconLibrary.currentLibrary, icon.name).then(svg => {\n          if (svg) {\n           const parser = new DOMParser();\n           const doc = parser.parseFromString(svg, 'image/svg+xml');\n           const svgEl = doc.querySelector('svg');\n           if (svgEl) {\n            svgEl.setAttribute('width', '48');\n            svgEl.setAttribute('height', '48');\n            svgEl.style.fill = 'var(--text-main)';\n            iconPreview.innerHTML = '';\n            iconPreview.appendChild(svgEl);\n           }\n          }\n         });\n         const name = document.createElement('div');\n         name.textContent = icon.displayName || icon.name;\n         name.style.cssText = 'font-size: 11px; color: var(--text-soft); text-align: center; word-break: break-word; max-width: 100%;';\n         item.appendChild(iconPreview);\n         item.appendChild(name);\n         item.addEventListener('click', async () => {\n          const svg = await IconLibrary.getIcon(IconLibrary.currentLibrary, icon.name);\n          if (iconPickerCallback && svg) {\n           iconPickerCallback({\n            library: IconLibrary.currentLibrary,\n            name: icon.name,\n            svg: svg\n           });\n          }\n          closeIconPicker();\n         });\n         grid.appendChild(item);\n        });\n        gridContainer.innerHTML = '';\n        gridContainer.appendChild(grid);\n       };\n       gridContainer.innerHTML = '<div style=\"text-align: center; color: var(--text-soft); padding: 40px;\">Type to search icons</div>';\n       let searchTimeout;\n       searchField.addEventListener('input', (e) => {\n        clearTimeout(searchTimeout);\n        const query = e.target.value.trim();\n        searchTimeout = setTimeout(() => {\n         if (!query) {\n          gridContainer.innerHTML = '<div style=\"text-align: center; color: var(--text-soft); padding: 40px;\">Type to search icons</div>';\n          return;\n         }\n         const results = IconLibrary.searchIcons(IconLibrary.currentLibrary, query);\n         renderIcons(results);\n        }, 300);\n       });\n       searchField.focus();\n      }\n      async function displayIcons(icons) {\n       const body = document.getElementById('icon-picker-body');\n       const grid = document.createElement('div');\n       grid.className = 'icon-grid';\n       for (const icon of icons) {\n        const item = document.createElement('div');\n        item.className = 'icon-item';\n        const svg = await IconLibrary.getIcon(icon.library, icon.name);\n        if (svg) {\n         const parser = new DOMParser();\n         const doc = parser.parseFromString(svg, 'image/svg+xml');\n         const svgEl = doc.querySelector('svg');\n         if (svgEl) {\n          item.innerHTML = svgEl.outerHTML;\n         }\n        } else {\n         item.innerHTML = '<svg width = \"32\" height = \"32\"><rect width = \"32\" height = \"32\" fill = \"currentColor\"/> </svg>';\n        }\n        const name = document.createElement('div');\n        name.className = 'icon-item-name';\n        name.textContent = icon.name;\n        item.appendChild(name);\n        item.addEventListener('click', () => {\n         if (iconPickerCallback) {\n          iconPickerCallback({\n           library: icon.library,\n           name: icon.name,\n           svg: svg\n          });\n         }\n         closeIconPicker();\n        });\n        grid.appendChild(item);\n       }\n       body.innerHTML = '';\n       body.appendChild(grid);\n      }\n      window.addEventListener('DOMContentLoaded', () => {\n       document.querySelectorAll('.icon-picker-tab').forEach(tab => {\n        tab.addEventListener('click', () => {\n         document.querySelectorAll('.icon-picker-tab').forEach(t => t.classList.remove('active'));\n         tab.classList.add('active');\n         IconLibrary.currentLibrary = tab.dataset.library;\n         loadIconsForCurrentLibrary();\n        });\n       });\n       document.getElementById('icon-picker-cancel').addEventListener('click', closeIconPicker);\n       document.getElementById('icon-picker-modal').addEventListener('click', (e) => {\n        if (e.target.id === 'icon-picker-modal') {\n         closeIconPicker();\n        }\n       });\n      });\n      let textDrawMode = false;\n      const BASE_NODE_DATA = JSON.parse(document.getElementById(\"nodes-json\").textContent);\n      const LABELS = JSON.parse(document.getElementById(\"labels-json\").textContent);\n\n      const DEFAULT_LANG = JSON.parse(document.getElementById(\"lang-json\").textContent);\n      let LANG = JSON.parse(JSON.stringify(DEFAULT_LANG));\n      let CUSTOM_LANG = null;\n\n      function deepMerge(target, source) {\n        const result = { ...target };\n        for (const key in source) {\n          if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {\n            result[key] = deepMerge(target[key] || {}, source[key]);\n          } else {\n            result[key] = source[key];\n          }\n        }\n        return result;\n      }\n\n      function t(key, replacements = {}) {\n        const mode = PAGE_STATE?.mappingMode || 'network';\n        const modeLabels = LANG.modes?.[mode] || LANG.modes?.network || {};\n\n        let text = key.split('.').reduce((obj, k) => obj?.[k], LANG);\n        if (text === undefined || text === null) return key;\n        if (typeof text === 'object') return key;\n\n        text = text.replace(/\\{node\\}/g, modeLabels.node || 'Node');\n        text = text.replace(/\\{nodes\\}/g, modeLabels.nodes || 'Nodes');\n        text = text.replace(/\\{connection\\}/g, modeLabels.connection || 'Connection');\n        text = text.replace(/\\{connections\\}/g, modeLabels.connections || 'Connections');\n        text = text.replace(/\\{rack\\}/g, modeLabels.rack || 'Rack');\n        text = text.replace(/\\{racks\\}/g, modeLabels.racks || 'Racks');\n\n        for (const [k, v] of Object.entries(replacements)) {\n          text = text.replace(new RegExp(`\\\\{${k}\\\\}`, 'g'), v);\n        }\n\n        return text;\n      }\n\n      function applyLanguage() {\n        const isRTL = LANG._meta?.rtl === true;\n        document.documentElement.dir = isRTL ? 'rtl' : 'ltr';\n        document.documentElement.classList.toggle('rtl', isRTL);\n\n        document.querySelectorAll('[data-lang]').forEach(el => {\n          const key = el.dataset.lang;\n          const attr = el.dataset.langAttr || 'textContent';\n          const translated = t(key);\n          if (attr === 'placeholder') {\n            el.placeholder = translated;\n          } else if (attr === 'title') {\n            el.title = translated;\n          } else if (attr === 'innerHTML') {\n            el.innerHTML = translated;\n          } else if (attr === 'label') {\n            el.label = translated;\n          } else {\n            el.textContent = translated;\n          }\n        });\n        document.querySelectorAll('[data-lang-label]').forEach(el => {\n          el.label = t(el.dataset.langLabel);\n        });\n        if (typeof applyMappingModeLabels === 'function') {\n          applyMappingModeLabels();\n        }\n        if (typeof updateLayerLabels === 'function') {\n          updateLayerLabels();\n        }\n      }\n\n      function saveLanguageToStorage() {\n        if (CUSTOM_LANG) {\n          localStorage.setItem('theNetworkeningCustomLang', JSON.stringify(CUSTOM_LANG));\n        } else {\n          localStorage.removeItem('theNetworkeningCustomLang');\n        }\n      }\n\n      function loadLanguageFromStorage() {\n        const stored = localStorage.getItem('theNetworkeningCustomLang');\n        if (stored) {\n          try {\n            CUSTOM_LANG = JSON.parse(stored);\n            LANG = deepMerge(DEFAULT_LANG, CUSTOM_LANG);\n          } catch (e) {\n            console.warn('Failed to load custom language:', e);\n          }\n        }\n      }\n\n      function exportLanguageFile() {\n        const langToExport = CUSTOM_LANG || LANG;\n        const blob = new Blob([JSON.stringify(langToExport, null, 2)], { type: 'application/json' });\n        const url = URL.createObjectURL(blob);\n        const a = document.createElement('a');\n        a.href = url;\n        a.download = `language-${langToExport._meta?.code || 'custom'}.json`;\n        a.click();\n        URL.revokeObjectURL(url);\n      }\n\n      function importLanguageFile(file) {\n        const reader = new FileReader();\n        reader.onload = (e) => {\n          try {\n            const imported = JSON.parse(e.target.result);\n            if (!imported._meta || !imported.modes) {\n              throw new Error('Invalid language file structure');\n            }\n            const currentEdition = DEFAULT_LANG._meta?.edition || 'networkening';\n            const importedEdition = imported._meta?.edition;\n            if (importedEdition && importedEdition !== currentEdition) {\n              throw new Error(`Edition mismatch: This language file is for \"${importedEdition}\" edition but you are using \"${currentEdition}\" edition`);\n            }\n            CUSTOM_LANG = imported;\n            LANG = deepMerge(DEFAULT_LANG, CUSTOM_LANG);\n            applyLanguage();\n            saveLanguageToStorage();\n            updateCurrentLangDisplay();\n            showAlert(t('messages.langImportSuccess'));\n          } catch (err) {\n            showAlert(t('messages.invalidLangFile') + ': ' + err.message);\n          }\n        };\n        reader.readAsText(file);\n      }\n\n      function resetToDefaultLanguage() {\n        LANG = JSON.parse(JSON.stringify(DEFAULT_LANG));\n        CUSTOM_LANG = null;\n        localStorage.removeItem('theNetworkeningCustomLang');\n        applyLanguage();\n        updateCurrentLangDisplay();\n        showAlert(t('messages.langResetSuccess'));\n      }\n\n      function updateCurrentLangDisplay() {\n        const langNameEl = document.getElementById('current-lang-name');\n        if (langNameEl) {\n          langNameEl.textContent = LANG._meta?.name || 'English';\n          if (CUSTOM_LANG) {\n            langNameEl.textContent += ' (Custom)';\n          }\n        }\n      }\n\n      function escapeHtml(str) {\n        const div = document.createElement('div');\n        div.textContent = str;\n        return div.innerHTML;\n      }\n\n      function flattenLangObject(obj, prefix = '') {\n        const result = [];\n        for (const [key, value] of Object.entries(obj)) {\n          if (key === '_meta') continue;\n          const fullKey = prefix ? `${prefix}.${key}` : key;\n          if (typeof value === 'object' && value !== null) {\n            result.push(...flattenLangObject(value, fullKey));\n          } else {\n            result.push({ key: fullKey, value: String(value), category: fullKey.split('.')[0] });\n          }\n        }\n        return result;\n      }\n\n      function setNestedValue(obj, path, value) {\n        const keys = path.split('.');\n        let current = obj;\n        for (let i = 0; i < keys.length - 1; i++) {\n          if (!current[keys[i]]) current[keys[i]] = {};\n          current = current[keys[i]];\n        }\n        current[keys[keys.length - 1]] = value;\n      }\n\n      let langEditorStrings = [];\n      let langEditorFilter = 'all';\n      let langEditorSearch = '';\n\n      function openLanguageEditor() {\n        langEditorStrings = flattenLangObject(LANG);\n        langEditorFilter = 'all';\n        langEditorSearch = '';\n        renderLangEditorList();\n\n        document.querySelectorAll('.lang-editor-tabs .lang-tab').forEach(tab => {\n          tab.onclick = () => {\n            document.querySelectorAll('.lang-editor-tabs .lang-tab').forEach(t => {\n              t.style.background = 'var(--panel)';\n              t.style.color = 'var(--text-main)';\n              t.classList.remove('active');\n            });\n            tab.style.background = 'var(--accent)';\n            tab.style.color = 'var(--bg)';\n            tab.classList.add('active');\n            langEditorFilter = tab.dataset.filter;\n            renderLangEditorList();\n          };\n        });\n        const searchInput = document.getElementById('lang-search');\n        if (searchInput) {\n          searchInput.value = '';\n          searchInput.oninput = () => {\n            langEditorSearch = searchInput.value.toLowerCase();\n            renderLangEditorList();\n          };\n        }\n        document.getElementById('language-editor-modal').style.display = 'flex';\n      }\n\n      function closeLanguageEditor() {\n        document.getElementById('language-editor-modal').style.display = 'none';\n      }\n\n      function renderLangEditorList() {\n        const list = document.querySelector('.lang-editor-list');\n        if (!list) return;\n\n        const filtered = langEditorStrings.filter(s => {\n          if (langEditorFilter !== 'all') {\n            if (!s.key.startsWith(langEditorFilter)) return false;\n          }\n          if (langEditorSearch) {\n            const searchLower = langEditorSearch.toLowerCase();\n            if (!s.key.toLowerCase().includes(searchLower) &&\n                !s.value.toLowerCase().includes(searchLower)) {\n              return false;\n            }\n          }\n          return true;\n        });\n\n        if (filtered.length === 0) {\n          list.innerHTML = '<div style=\"padding:20px;text-align:center;color:var(--text-soft);\">' + t(\"emptyStates.noStringsFound\") + '</div>';\n          return;\n        }\n\n        list.innerHTML = filtered.map(s => `\n          <div style=\"display:flex;gap:12px;padding:8px 12px;border-bottom:1px solid var(--edge-main);align-items:center;\">\n            <span style=\"flex:0 0 280px;font-size:11px;color:var(--text-soft);font-family:monospace;word-break:break-all;\">${escapeHtml(s.key)}</span>\n            <input type=\"text\" class=\"lang-value\" data-key=\"${escapeHtml(s.key)}\" value=\"${escapeHtml(s.value)}\"\n              style=\"flex:1;padding:6px 10px;background:var(--panel-alt);color:var(--text-main);border:1px solid var(--edge-main);border-radius:4px;font-size:13px;\">\n          </div>\n        `).join('');\n      }\n\n      function saveLangEdits() {\n        const inputs = document.querySelectorAll('.lang-editor-list .lang-value');\n        let hasChanges = false;\n\n        inputs.forEach(input => {\n          const key = input.dataset.key;\n          const newValue = input.value;\n\n          const original = langEditorStrings.find(s => s.key === key);\n          if (original && original.value !== newValue) {\n            setNestedValue(LANG, key, newValue);\n            original.value = newValue;\n            hasChanges = true;\n          }\n        });\n\n        if (hasChanges) {\n          CUSTOM_LANG = JSON.parse(JSON.stringify(LANG));\n          saveLanguageToStorage();\n          applyLanguage();\n          updateCurrentLangDisplay();\n        }\n\n        closeLanguageEditor();\n      }\n\n      const SHAPE_CATEGORIES = {\n        basic: [\n          { value: 'circle', label: 'Circle' },\n          { value: 'square', label: 'Square' },\n          { value: 'rectangle', label: 'Rectangle' },\n          { value: 'triangle', label: 'Triangle' },\n          { value: 'hexagon', label: 'Hexagon' },\n          { value: 'diamond', label: 'Diamond' },\n          { value: 'star', label: 'Star' },\n          { value: 'stop-sign', label: 'Stop Sign' },\n          { value: 'octagon', label: 'Octagon' },\n          { value: 'pentagon', label: 'Pentagon' },\n          { value: 'cross', label: 'Cross' },\n          { value: 'rounded-square', label: 'Rounded Square' },\n          { value: 'pill', label: 'Pill' },\n          { value: 'parallelogram', label: 'Parallelogram' },\n          { value: 'trapezoid', label: 'Trapezoid' }\n        ],\n        computers: [\n          { value: 'server', label: 'Server' },\n          { value: 'pc', label: 'PC / Desktop' },\n          { value: 'laptop', label: 'Laptop' },\n          { value: 'phone', label: 'Phone / Mobile' },\n          { value: 'printer', label: 'Printer' },\n          { value: 'pi', label: 'Raspberry Pi' },\n          { value: 'sensor', label: 'Sensor / IoT' }\n        ],\n        network: [\n          { value: 'router', label: 'Router' },\n          { value: 'switch', label: 'Switch' },\n          { value: 'firewall', label: 'Firewall' },\n          { value: 'access-point', label: 'Access Point' },\n          { value: 'load-balancer', label: 'Load Balancer' },\n          { value: 'gateway', label: 'Gateway' },\n          { value: 'vpn', label: 'VPN / Tunnel' },\n          { value: 'nas', label: 'NAS / Storage' }\n        ],\n        cloud: [\n          { value: 'cloud', label: 'Cloud' },\n          { value: 'database', label: 'Database' },\n          { value: 'docker', label: 'Docker' },\n          { value: 'container', label: 'Container' },\n          { value: 'vm', label: 'Virtual Machine' },\n          { value: 'kubernetes', label: 'Kubernetes' },\n          { value: 'api', label: 'API / Endpoint' },\n          { value: 'queue', label: 'Queue / Message' },\n          { value: 'lambda', label: 'Lambda / Function' },\n          { value: 'bucket', label: 'Bucket / S3' }\n        ],\n        security: [\n          { value: 'shield', label: 'Shield' },\n          { value: 'camera', label: 'Camera / CCTV' },\n          { value: 'monitor', label: 'Monitor / Dashboard' }\n        ],\n        smarthome: [\n          { value: 'thermostat', label: 'Thermostat' },\n          { value: 'doorbell', label: 'Video Doorbell' },\n          { value: 'smart-lock', label: 'Smart Lock' },\n          { value: 'smart-bulb', label: 'Smart Bulb' },\n          { value: 'smart-plug', label: 'Smart Plug' },\n          { value: 'smart-speaker', label: 'Smart Speaker' },\n          { value: 'smart-tv', label: 'Smart TV' },\n          { value: 'hub', label: 'Smart Hub' },\n          { value: 'smoke-detector', label: 'Smoke Detector' },\n          { value: 'motion-sensor', label: 'Motion Sensor' },\n          { value: 'garage', label: 'Garage Door' },\n          { value: 'sprinkler', label: 'Sprinkler' },\n          { value: 'vacuum', label: 'Robot Vacuum' }\n        ],\n        sports: [\n          { value: 'basketball-ball', label: 'Basketball' },\n          { value: 'football-ball', label: 'Football' },\n          { value: 'soccer-ball', label: 'Soccer Ball' },\n          { value: 'hockey-puck', label: 'Hockey Puck' },\n          { value: 'baseball-ball', label: 'Baseball' },\n          { value: 'tennis-ball', label: 'Tennis Ball' },\n          { value: 'volleyball', label: 'Volleyball' },\n          { value: 'rugby-ball', label: 'Rugby Ball' },\n          { value: 'golf-ball', label: 'Golf Ball' },\n          { value: 'frisbee', label: 'Frisbee' },\n          { value: 'cricket-ball', label: 'Cricket Ball' },\n          { value: 'lacrosse-stick', label: 'Lacrosse Stick' },\n          { value: 'golf-flag', label: 'Golf Flag' },\n          { value: 'tactical-x', label: 'Tactical X' },\n          { value: 'tactical-o', label: 'Tactical O' },\n          { value: 'tactical-star', label: 'Tactical Star' }\n        ],\n        rack: [\n          { value: 'patch-panel', label: 'Patch Panel' },\n          { value: 'ups', label: 'UPS' },\n          { value: 'pdu', label: 'PDU' },\n          { value: 'rack-shelf', label: 'Rack Shelf' },\n          { value: 'blank-panel', label: 'Blank Panel' },\n          { value: 'cable-management', label: 'Cable Management' },\n          { value: 'kvm', label: 'KVM Switch' }\n        ]\n      };\n\n      const CANVAS_OPTIONS = {\n        network: [\n          { value: 'grid', label: 'Standard Grid' },\n          { value: 'dots', label: 'Dot Grid' },\n          { value: 'blueprint', label: 'Blueprint Grid' },\n          { value: 'none', label: 'No Grid' }\n        ],\n        mindmap: [],\n        sports: [\n          { value: 'basketball', label: 'Basketball Court' },\n          { value: 'football', label: 'Football Field' },\n          { value: 'soccer', label: 'Soccer Pitch' },\n          { value: 'hockey', label: 'Hockey Rink' },\n          { value: 'baseball', label: 'Baseball Diamond' },\n          { value: 'tennis', label: 'Tennis Court' }\n        ],\n        smarthome: [\n          { value: 'blueprint', label: 'Blueprint Grid' },\n          { value: 'grid', label: 'Standard Grid' },\n          { value: 'dots', label: 'Dot Grid' },\n          { value: 'none', label: 'No Grid' }\n        ],\n        floorplan: []\n      };\n\n      const MODE_DEFAULT_CATEGORIES = {\n        network: 'network',\n        mindmap: 'basic',\n        sports: 'sports',\n        smarthome: 'smarthome',\n        floorplan: 'basic'\n      };\n\n      function findCategoryForShape(shape) {\n        if (!shape) return MODE_DEFAULT_CATEGORIES[PAGE_STATE.mappingMode || 'network'] || 'basic';\n        for (const [category, shapes] of Object.entries(SHAPE_CATEGORIES)) {\n          if (shapes.some(s => s.value === shape)) {\n            return category;\n          }\n        }\n        return MODE_DEFAULT_CATEGORIES[PAGE_STATE.mappingMode || 'network'] || 'basic';\n      }\n\n      let currentNodeId = null;\n      let currentEdgeId = null;\n      let currentStyleScope = \"all\";\n      let NODE_DATA = {};\n      let EDGE_DATA = {\n       list: []\n      };\n      let currentRectId = null;\n      let currentTextId = null;\n      let currentImageId = null;\n      let rectDrawMode = false;\n      let RECT_DATA = {\n       list: []\n      };\n      let TEXT_DATA = {\n       list: []\n      };\n      let IMAGE_DATA = {\n       list: []\n      };\n      const MAX_CANVAS_IMAGES = 40;\n      const MAX_IMAGE_SIZE = 250;\n      let EDGE_LEGEND = {};\n      let savedPositions = {};\n      let savedSizes = {};\n      let savedStyles = {};\n      let legendCollapsed = false;\n      let minimapCollapsed = false;\n      let drawToolbarCollapsed = false;\n      let currentView = {\n       mode: \"topology\",\n       rackId: null\n      };\n      let savedTopologyView = null;\n      let activeLayers = new Set([\"layer1\", \"layer2\", \"layer3\", \"layer4\"]);\n      let topologyToolbarCollapsed = false;\n      let legendMiniBtn = null;\n      let minimapMiniBtn = null;\n      let drawToolbarMiniBtn = null;\n      let topologyToolbarMiniBtn = null;\n      let autoPingEnabled = false;\n      let autoPingInterval = 30;\n      let autoPingTimer = null;\n      let autoPingCountdown = null;\n      let autoPingSecondsRemaining = 0;\n      const ROLLBACK_STORAGE_KEY = \"theonefile_rollback_history\";\n      let rollbackVersions = [];\n      const MAX_ROLLBACK_VERSIONS = 50;\n      let currentRollbackIndex = -1;\n      const AUDIT_STORAGE_KEY = \"theonefile_audit_log\";\n      let auditLog = [];\n      const MAX_AUDIT_ENTRIES = 1000;\n      let savedStyleSets = [];\n      let documentTabs = [{\n        id: \"main\",\n        name: \"Main Topology\",\n        nodes: {},\n        edges: { list: [] },\n        positions: {},\n        sizes: {},\n        styles: {},\n        legend: {},\n        rects: { list: [] },\n        texts: { list: [] },\n      pageState: null\n      }];\n      let currentTabIndex = 0;\n      let encryptedSections = {};\n      let undoStack = [];\n      let redoStack = [];\n      const MAX_UNDO_STACK = 50;\n      let forgeDebounceTimer = null;\n      let forgeImmediate = false;\n      let currentSearchQuery = \"\";\n      let currentSearchResults = [];\n      const AUTOSAVE_DB_NAME = \"TheOneFileNetworkeningAutosave\";\n      const AUTOSAVE_STORE_NAME = \"drafts\";\n      const AUTOSAVE_KEY = \"currentDraft\";\n      const AUTOSAVE_INTERVAL = 30000;\n      let autosaveTimer = null;\n      let lastAutosaveTime = 0;\n      let selectedNodes = new Set();\n      let selectedEdges = new Set();\n      let selectedRects = new Set();\n      let selectedTexts = new Set();\n      let isSelecting = false;\n      let selectionStart = null;\n      let preDragSelectedNodes = new Set();\n      let preDragSelectedEdges = new Set();\n      let preDragSelectedRects = new Set();\n      let preDragSelectedTexts = new Set();\n      let selectionRect = null;\n      let isDraggingSelection = false;\n      let dragSelectionStart = null;\n      let selectionBoxStyle = {\n       fillColor: \"#4fd1c5\",\n       fillOpacity: 0.1,\n       strokeColor: \"#4fd1c5\",\n       strokeWidth: 2,\n       strokeDasharray: \"5,5\"\n      };\n      let clipboard = null;\n\t  function escapeHtml(str) {\n       if (!str) return '';\n       return String(str).replace(/[&<>\"']/g, c => ({\n        '&': '&amp;', '<': '&lt;', '>': '&gt;', '\"': '&quot;', \"'\": '&#39;'\n       }[c]));\n      }\n      function showAlert(message, title) {\n       return new Promise((resolve) => {\n        const modal = document.getElementById(\"dialog-modal\");\n        const titleEl = document.getElementById(\"dialog-modal-title\");\n        const messageEl = document.getElementById(\"dialog-modal-message\");\n        const inputEl = document.getElementById(\"dialog-modal-input\");\n        const okBtn = document.getElementById(\"dialog-modal-ok\");\n        const cancelBtn = document.getElementById(\"dialog-modal-cancel\");\n        titleEl.textContent = title || t(\"dialogs.alert\");\n        messageEl.textContent = message;\n        inputEl.style.display = \"none\";\n        cancelBtn.style.display = \"none\";\n        okBtn.textContent = t(\"dialogs.ok\");\n        modal.classList.add(\"active\");\n        const cleanup = () => {\n         modal.classList.remove(\"active\");\n         okBtn.removeEventListener(\"click\", handleOk);\n         modal.removeEventListener(\"click\", bgHandler);\n        };\n        const handleOk = () => { cleanup(); resolve(true); };\n        const bgHandler = (e) => { if (e.target === modal) { cleanup(); resolve(true); } };\n        okBtn.addEventListener(\"click\", handleOk);\n        modal.addEventListener(\"click\", bgHandler);\n       });\n      }\n      function showConfirm(message, title) {\n       return new Promise((resolve) => {\n        const modal = document.getElementById(\"dialog-modal\");\n        const titleEl = document.getElementById(\"dialog-modal-title\");\n        const messageEl = document.getElementById(\"dialog-modal-message\");\n        const inputEl = document.getElementById(\"dialog-modal-input\");\n        const okBtn = document.getElementById(\"dialog-modal-ok\");\n        const cancelBtn = document.getElementById(\"dialog-modal-cancel\");\n        titleEl.textContent = title || t(\"dialogs.confirm\");\n        messageEl.textContent = message;\n        inputEl.style.display = \"none\";\n        cancelBtn.style.display = \"\";\n        okBtn.textContent = t(\"dialogs.ok\");\n        cancelBtn.textContent = t(\"dialogs.cancel\");\n        modal.classList.add(\"active\");\n        const cleanup = () => {\n         modal.classList.remove(\"active\");\n         okBtn.removeEventListener(\"click\", handleOk);\n         cancelBtn.removeEventListener(\"click\", handleCancel);\n         modal.removeEventListener(\"click\", bgHandler);\n        };\n        const handleOk = () => { cleanup(); resolve(true); };\n        const handleCancel = () => { cleanup(); resolve(false); };\n        const bgHandler = (e) => { if (e.target === modal) handleCancel(); };\n        okBtn.addEventListener(\"click\", handleOk);\n        cancelBtn.addEventListener(\"click\", handleCancel);\n        modal.addEventListener(\"click\", bgHandler);\n       });\n      }\n      function showPrompt(message, defaultValue, title) {\n       return new Promise((resolve) => {\n        const modal = document.getElementById(\"dialog-modal\");\n        const titleEl = document.getElementById(\"dialog-modal-title\");\n        const messageEl = document.getElementById(\"dialog-modal-message\");\n        const inputEl = document.getElementById(\"dialog-modal-input\");\n        const okBtn = document.getElementById(\"dialog-modal-ok\");\n        const cancelBtn = document.getElementById(\"dialog-modal-cancel\");\n        titleEl.textContent = title || t(\"dialogs.prompt\");\n        messageEl.textContent = message;\n        inputEl.style.display = \"\";\n        inputEl.value = defaultValue || \"\";\n        cancelBtn.style.display = \"\";\n        okBtn.textContent = t(\"dialogs.ok\");\n        cancelBtn.textContent = t(\"dialogs.cancel\");\n        modal.classList.add(\"active\");\n        inputEl.focus();\n        inputEl.select();\n        const cleanup = () => {\n         modal.classList.remove(\"active\");\n         okBtn.removeEventListener(\"click\", handleOk);\n         cancelBtn.removeEventListener(\"click\", handleCancel);\n         inputEl.removeEventListener(\"keypress\", handleEnter);\n         modal.removeEventListener(\"click\", bgHandler);\n        };\n        const handleOk = () => { cleanup(); resolve(inputEl.value); };\n        const handleCancel = () => { cleanup(); resolve(null); };\n        const handleEnter = (e) => { if (e.key === \"Enter\") handleOk(); };\n        const bgHandler = (e) => { if (e.target === modal) handleCancel(); };\n        okBtn.addEventListener(\"click\", handleOk);\n        cancelBtn.addEventListener(\"click\", handleCancel);\n        inputEl.addEventListener(\"keypress\", handleEnter);\n        modal.addEventListener(\"click\", bgHandler);\n       });\n      }\n      const MobileManager = {\n        isMobile: false,\n        detect() {\n          if (navigator.userAgentData?.mobile === true) {\n            this.isMobile = true;\n            return true;\n          }\n          const coarse = matchMedia(\"(pointer: coarse)\").matches;\n          const width = window.innerWidth <= 900;\n          const portrait = matchMedia(\"(orientation: portrait)\").matches;\n          this.isMobile = coarse;\n          return this.isMobile;\n        },\n        applyInitialCollapse() {\n          if (!this.isMobile) return;\n          legendCollapsed = true;\n          minimapCollapsed = true;\n          drawToolbarCollapsed = true;\n          topologyToolbarCollapsed = true;\n          if (typeof updateLegendVisibility === \"function\") updateLegendVisibility();\n          if (typeof updateMinimapVisibility === \"function\") updateMinimapVisibility();\n          if (typeof updateDrawToolbarVisibility === \"function\") updateDrawToolbarVisibility();\n          if (typeof updateTopologyToolbarVisibility === \"function\") updateTopologyToolbarVisibility();\n        },\n        updateBulkToolbar() {\n          const desktop = document.getElementById(\"bulk-toolbar\");\n          const mobile = document.getElementById(\"bulk-toolbar-mobile\");\n          if (!desktop || !mobile) return;\n          if (typeof isViewOnly === 'function' && isViewOnly()) {\n           desktop.style.display = \"none\";\n           mobile.style.display = \"none\";\n           return;\n          }\n          if (typeof selectedNodes === 'undefined' || selectedNodes.size === 0) {\n           desktop.style.display = \"none\";\n           mobile.style.display = \"none\";\n           return;\n          }\n          const isVisible = desktop.style.display !== \"none\" || mobile.style.display !== \"none\";\n          if (this.isMobile) {\n            desktop.style.display = \"none\";\n            if (isVisible) {\n              mobile.style.display = \"flex\";\n            }\n          } else {\n            mobile.style.display = \"none\";\n            if (isVisible) {\n              desktop.style.display = \"flex\";\n            }\n          }\n        },\n        updateCanvasHint() {\n      const hint = document.getElementById(\"canvas-hint\");\n      if (!hint) return;\n      if (PAGE_STATE.canvasHintText !== undefined && PAGE_STATE.canvasHintText !== \"\") {\n        hint.textContent = PAGE_STATE.canvasHintText;\n        return;\n      }\n      const items = this.isMobile\n      ? [\n        \"Pinch to zoom\",\n        \"Drag to pan\",\n        \"Add node from top menu\",\n        \"Double tap to clone and align\",\n        \"Double tap to select multiple\",\n        \"You have the power\",\n        \"Your time is NOW!\",\n      ]\n      : [\n        \"Scroll to zoom\",\n        \"Drag to pan\",\n        \"Right click to clone and align\",\n        \"Right click to select multiple\",\n        \"You have the power\",\n        \"Your time is NOW!\",\n      ];\n      const list = document.createElement(\"ul\");\n      for (const item of items) {\n      const li = document.createElement(\"li\");\n      li.textContent = item;\n      list.appendChild(li);\n      }\n      hint.replaceChildren(list);\n      },\n        autoSelectStyleScope() {\n          const scope = document.getElementById(\"style-scope\");\n          if (!scope) return;\n          if (this.isMobile) scope.value = \"mobile\";\n        },\n        updateToolbarStack() {\n          if (!this.isMobile) return;\n          const draw = document.getElementById(\"draw-toolbar\");\n          const topo = document.getElementById(\"topology-toolbar\");\n          if (!draw || !topo) return;\n          const h = draw.getBoundingClientRect().height;\n          document.documentElement.style.setProperty(\"--draw-toolbar-height\", h + \"px\");\n        },\n        updateLayoutControls() {\n          const sidebarRow = document.getElementById(\"sidebar-width-row\");\n          const footerRow = document.getElementById(\"mobile-footer-row\");\n          if (sidebarRow && footerRow) {\n            if (this.isMobile) {\n              sidebarRow.style.display = \"none\";\n              footerRow.style.display = \"flex\";\n            } else {\n              sidebarRow.style.display = \"flex\";\n              footerRow.style.display = \"none\";\n            }\n          }\n        },\n        applyAll() {\n          this.detect();\n          this.applyInitialCollapse();\n          this.updateBulkToolbar();\n          this.updateCanvasHint();\n          this.autoSelectStyleScope();\n          this.updateToolbarStack();\n          this.updateLayoutControls();\n        }\n      };\n      function isMobileDevice() {\n        return MobileManager.isMobile;\n      }\n      function ensureLegendMiniButton() {\n\t\tif (legendMiniBtn) return legendMiniBtn;\n\t\tconst handleClick = (e) => {\n\t\t\te.stopPropagation();\n\t\t\te.preventDefault();\n\t\t\tlegendCollapsed = false;\n\t\t\tupdateLegendVisibility();\n\t\t};\n\t\tconst preventTouch = (e) => { e.preventDefault(); };\n\t\tconst existing = document.getElementById(\"edge-legend-mini\");\n\t\tif (existing) {\n\t\t\texisting.addEventListener(\"click\", handleClick);\n\t\t\texisting.addEventListener(\"touchstart\", preventTouch, { passive: false });\n\t\t\texisting.addEventListener(\"touchend\", handleClick);\n\t\t\tlegendMiniBtn = existing;\n\t\t\treturn existing;\n\t\t}\n\t\tconst panel = document.querySelector(\".topology-panel\");\n\t\tif (!panel) return null;\n\t\t   const btn = document.createElement(\"button\");\n\t\t   btn.type = \"button\";\n\t\t   btn.id = \"edge-legend-mini\";\n\t\t   btn.className = \"legend-mini-btn\";\n\t\t   btn.textContent = t(\"toolbar.legendMini\");\n\t\t   btn.style.bottom = \"10px\";\n\t\t   btn.addEventListener(\"click\", handleClick);\n\t\t   btn.addEventListener(\"touchstart\", preventTouch, { passive: false });\n\t\t   btn.addEventListener(\"touchend\", handleClick);\n\t\t   panel.appendChild(btn);\n\t\t   legendMiniBtn = btn;\n\t\t   return btn;\n\t\t}\n\t\t\t function ensureMinimapMiniButton() {\n\t\t   if (minimapMiniBtn) return minimapMiniBtn;\n\t\t   const handleClick = (e) => {\n\t\t\te.stopPropagation();\n\t\t\te.preventDefault();\n\t\t\tminimapCollapsed = false;\n\t\t\tupdateMinimapVisibility();\n\t\t   };\n\t\t   const preventTouch = (e) => { e.preventDefault(); };\n\t\t   const existing = document.getElementById(\"minimap-mini\");\n\t\t   if (existing) {\n\t\t\texisting.addEventListener(\"click\", handleClick);\n\t\t\texisting.addEventListener(\"touchstart\", preventTouch, { passive: false });\n\t\t\texisting.addEventListener(\"touchend\", handleClick);\n\t\t\tminimapMiniBtn = existing;\n\t\t\treturn existing;\n\t\t   }\n\t\t   const panel = document.querySelector(\".topology-panel\");\n\t\t   if (!panel) return null;\n\t\t   const btn = document.createElement(\"button\");\n\t\t   btn.type = \"button\";\n\t\t   btn.id = \"minimap-mini\";\n\t\t   btn.className = \"legend-mini-btn\";\n\t\t   btn.textContent = t(\"toolbar.minimapMini\");\n\t\t   btn.style.right = \"10px\";\n\t\t   btn.style.left = \"auto\";\n\t\t   btn.style.bottom = \"10px\";\n\t\t   btn.addEventListener(\"click\", handleClick);\n\t\t   btn.addEventListener(\"touchstart\", preventTouch, { passive: false });\n\t\t   btn.addEventListener(\"touchend\", handleClick);\n\t\t   panel.appendChild(btn);\n\t\t   minimapMiniBtn = btn;\n\t\t   return btn;\n\t\t}\n\t\t\t  function ensureDrawToolbarMiniButton() {\n\t\t   if (drawToolbarMiniBtn) return drawToolbarMiniBtn;\n\t\t   const handleClick = (e) => {\n\t\t\te.stopPropagation();\n\t\t\te.preventDefault();\n\t\t\tdrawToolbarCollapsed = false;\n\t\t\tupdateDrawToolbarVisibility();\n\t\t   };\n\t\t   const preventTouch = (e) => { e.preventDefault(); };\n\t\t   const existing = document.getElementById(\"draw-toolbar-mini\");\n\t\t   if (existing) {\n\t\t\texisting.addEventListener(\"click\", handleClick);\n\t\t\texisting.addEventListener(\"touchstart\", preventTouch, { passive: false });\n\t\t\texisting.addEventListener(\"touchend\", handleClick);\n\t\t\tdrawToolbarMiniBtn = existing;\n\t\t\treturn existing;\n\t\t   }\n\t\t   const panel = document.querySelector(\".topology-panel\");\n\t\t   if (!panel) return null;\n\t\t   const btn = document.createElement(\"button\");\n\t\t   btn.type = \"button\";\n\t\t   btn.id = \"draw-toolbar-mini\";\n\t\t   btn.className = \"legend-mini-btn\";\n\t\t   btn.textContent = t(\"toolbar.drawMini\");\n\t\t   btn.style.top = \"10px\";\n\t\t   btn.style.left = \"10px\";\n\t\t   btn.style.right = \"auto\";\n\t\t   btn.addEventListener(\"click\", handleClick);\n\t\t   btn.addEventListener(\"touchstart\", preventTouch, { passive: false });\n\t\t   btn.addEventListener(\"touchend\", handleClick);\n\t\t   panel.appendChild(btn);\n\t\t   drawToolbarMiniBtn = btn;\n\t\t   return btn;\n\t\t}\n\t\t  function ensureTopologyToolbarMiniButton() {\n\t   if (topologyToolbarMiniBtn) return topologyToolbarMiniBtn;\n\t   const handleClick = (e) => {\n\t\te.stopPropagation();\n\t\te.preventDefault();\n\t\ttopologyToolbarCollapsed = false;\n\t\tupdateTopologyToolbarVisibility();\n\t   };\n\t   const preventTouch = (e) => { e.preventDefault(); };\n\t   const existing = document.getElementById(\"topology-toolbar-mini\");\n\t   if (existing) {\n\t\texisting.addEventListener(\"click\", handleClick);\n\t\texisting.addEventListener(\"touchstart\", preventTouch, { passive: false });\n\t\texisting.addEventListener(\"touchend\", handleClick);\n\t\ttopologyToolbarMiniBtn = existing;\n\t\treturn existing;\n\t   }\n\t   const panel = document.querySelector(\".topology-panel\");\n\t   if (!panel) return null;\n\t   const btn = document.createElement(\"button\");\n\t   btn.type = \"button\";\n\t   btn.id = \"topology-toolbar-mini\";\n\t   btn.className = \"legend-mini-btn\";\n\t   btn.textContent = t(\"toolbar.topologyMini\");\n\t   btn.style.top = \"10px\";\n\t   btn.style.left = \"auto\";\n\t   btn.style.right = \"40px\";\n\t   btn.addEventListener(\"click\", handleClick);\n\t   btn.addEventListener(\"touchstart\", preventTouch, { passive: false });\n\t   btn.addEventListener(\"touchend\", handleClick);\n\t   panel.appendChild(btn);\n\t   topologyToolbarMiniBtn = btn;\n\t   return btn;\n\t}\n      function updateToolbarStack() {\n       if (!isMobileDevice()) return;\n       const draw = document.getElementById(\"draw-toolbar\");\n       const topo = document.getElementById(\"topology-toolbar\");\n       if (!draw || !topo) return;\n       const h = draw.getBoundingClientRect().height;\n       document.documentElement.style.setProperty(\"--draw-toolbar-height\", h + \"px\");\n      }\n      window.addEventListener(\"resize\", updateToolbarStack);\n      window.addEventListener(\"DOMContentLoaded\", updateToolbarStack);\n      updateToolbarStack();\n      function updateLegendVisibility() {\n       const legend = document.getElementById(\"edge-legend\");\n       const mini = ensureLegendMiniButton();\n       if (!legend || !mini) return;\n       const hasItems = legend.querySelectorAll(\".legend-item\").length > 0;\n       if (!hasItems) {\n        legend.style.display = \"none\";\n        mini.style.display = \"none\";\n        return;\n       }\n       if (legendCollapsed) {\n        legend.style.display = \"none\";\n        mini.style.display = \"inline-flex\";\n       } else {\n        legend.style.display = \"flex\";\n        mini.style.display = \"none\";\n       }\n      }\n      function updateMinimapVisibility() {\n       const wrapper = document.getElementById(\"minimap-zoom-wrapper\");\n       const mini = ensureMinimapMiniButton();\n       if (!wrapper || !mini) return;\n       if (minimapCollapsed) {\n        wrapper.style.display = \"none\";\n        mini.style.display = \"inline-flex\";\n       } else {\n        wrapper.style.display = \"block\";\n        mini.style.display = \"none\";\n       }\n      }\n      function updateDrawToolbarVisibility() {\n       const toolbar = document.getElementById(\"draw-toolbar\");\n       const mini = ensureDrawToolbarMiniButton();\n       if (!toolbar || !mini) return;\n       if (typeof isViewOnly === 'function' && isViewOnly()) {\n        toolbar.style.setProperty('display', 'none', 'important');\n        mini.style.display = \"none\";\n        return;\n       }\n       if (drawToolbarCollapsed) {\n        toolbar.style.setProperty('display', 'none', 'important');\n        mini.style.display = \"inline-flex\";\n       } else {\n      if (isMobileDevice()) {\n        toolbar.style.setProperty('display', 'grid', 'important');\n        mini.style.display = \"none\";\n        } else {\n        toolbar.style.setProperty('display', 'flex', 'important');\n        mini.style.display = \"none\";\n      }\n       }\n      }\n      function u8ToBase64(u8) {\n      let binary = \"\";\n      for (let i = 0; i < u8.length; i++) binary += String.fromCharCode(u8[i]);\n      return btoa(binary);\n      }\n      function base64ToU8(base64) {\n      const binary = atob(base64);\n      const bytes = new Uint8Array(binary.length);\n      for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);\n      return bytes;\n      }\n      function openAutosaveDB() {\n       return new Promise((resolve, reject) => {\n        const request = indexedDB.open(AUTOSAVE_DB_NAME, 1);\n        request.onerror = () => reject(request.error);\n        request.onsuccess = () => resolve(request.result);\n        request.onupgradeneeded = (e) => {\n         const db = e.target.result;\n         if (!db.objectStoreNames.contains(AUTOSAVE_STORE_NAME)) {\n          db.createObjectStore(AUTOSAVE_STORE_NAME, { keyPath: \"id\" });\n         }\n        };\n       });\n      }\n      async function saveToIndexedDB() {\n       if (window.DEMO_MODE) return;\n       try {\n        const db = await openAutosaveDB();\n        const currentTab = documentTabs[currentTabIndex];\n        currentTab.nodes = JSON.parse(JSON.stringify(NODE_DATA));\n        currentTab.edges = JSON.parse(JSON.stringify(EDGE_DATA));\n        currentTab.positions = JSON.parse(JSON.stringify(savedPositions));\n        currentTab.sizes = JSON.parse(JSON.stringify(savedSizes));\n        currentTab.styles = JSON.parse(JSON.stringify(savedStyles));\n        currentTab.legend = JSON.parse(JSON.stringify(EDGE_LEGEND));\n        currentTab.rects = JSON.parse(JSON.stringify(RECT_DATA));\n        currentTab.texts = JSON.parse(JSON.stringify(TEXT_DATA));\n        currentTab.pageState = JSON.parse(JSON.stringify(PAGE_STATE));\n        const state = {\n         id: AUTOSAVE_KEY,\n         timestamp: Date.now(),\n         pageTitle: document.getElementById(\"page-title\")?.textContent || \"The One File\",\n         nodeData: NODE_DATA,\n         edgeData: EDGE_DATA,\n         rectData: RECT_DATA,\n         textData: TEXT_DATA,\n         imageData: IMAGE_DATA,\n         edgeLegend: EDGE_LEGEND,\n         zoneLegend: ZONE_LEGEND,\n         zonePresets: ZONE_PRESETS,\n         nodePositions: savedPositions,\n         nodeSizes: savedSizes,\n         nodeStyles: savedStyles,\n         page: PAGE_STATE,\n         canvas: {\n          zoom: canvasState.zoom,\n          panX: canvasState.panX,\n          panY: canvasState.panY,\n         },\n         savedTopologyView: savedTopologyView,\n         documentTabs: documentTabs,\n         currentTabIndex: currentTabIndex,\n         encryptedSections: encryptedSections,\n         auditLog: auditLog,\n\t\t savedStyleSets: savedStyleSets,\n\t\t selectedTheme: document.getElementById(\"theme-preset\")?.value || \"defaulted\",\n         recordings: RECORDING_STATE.recordings,\n        };\n        const tx = db.transaction(AUTOSAVE_STORE_NAME, \"readwrite\");\n        const store = tx.objectStore(AUTOSAVE_STORE_NAME);\n        store.put(state);\n        await new Promise((resolve, reject) => {\n         tx.oncomplete = resolve;\n         tx.onerror = () => reject(tx.error);\n        });\n        lastAutosaveTime = Date.now();\n        console.log(\"Autosaved to IndexedDB at\", new Date().toLocaleTimeString());\n       } catch (e) {\n        console.warn(\"Auto-save failed:\", e);\n       }\n      }\n      async function loadFromIndexedDB() {\n       if (window.DEMO_MODE) return null;\n       try {\n        const db = await openAutosaveDB();\n        const tx = db.transaction(AUTOSAVE_STORE_NAME, \"readonly\");\n        const store = tx.objectStore(AUTOSAVE_STORE_NAME);\n        const request = store.get(AUTOSAVE_KEY);\n        return new Promise((resolve, reject) => {\n         request.onsuccess = () => resolve(request.result);\n         request.onerror = () => reject(request.error);\n        });\n       } catch (e) {\n        console.warn(\"Load from IndexedDB failed:\", e);\n        return null;\n       }\n      }\n      async function clearAutosave() {\n       try {\n        const db = await openAutosaveDB();\n        const tx = db.transaction(AUTOSAVE_STORE_NAME, \"readwrite\");\n        const store = tx.objectStore(AUTOSAVE_STORE_NAME);\n        store.delete(AUTOSAVE_KEY);\n       } catch (e) {\n        console.warn(\"Clear autosave failed:\", e);\n       }\n      }\n      function startAutosave() {\n       if (autosaveTimer) clearInterval(autosaveTimer);\n       autosaveTimer = setInterval(() => {\n        if (Object.keys(NODE_DATA).length > 0 || EDGE_DATA.list.length > 0 || documentTabs.length > 1) {\n         saveToIndexedDB();\n        }\n       }, AUTOSAVE_INTERVAL);\n      }\n      async function checkForAutosaveRecovery() {\n       if (window.DEMO_MODE) return;\n       try {\n        const saved = await loadFromIndexedDB();\n        if (saved && saved.timestamp) {\n         const age = Date.now() - saved.timestamp;\n         const hasCurrentData = Object.keys(NODE_DATA).length > 0 || EDGE_DATA.list.length > 0;\n         const savedNodeCount = saved.nodeData ? Object.keys(saved.nodeData).length : 0;\n         const savedTabCount = saved.documentTabs ? saved.documentTabs.length : 1;\n         const hasPageState = saved.page && Object.keys(saved.page).length > 0;\n         if (age < 300000 && (savedNodeCount > 0 || savedTabCount > 1 || hasPageState)) {\n          const savedDate = new Date(saved.timestamp).toLocaleString();\n          const tabInfo = savedTabCount > 1 ? ` across ${savedTabCount} tabs` : \"\";\n          if (!hasCurrentData || await showConfirm(t(\"dialogs.recoverWork\", { date: savedDate, nodeCount: savedNodeCount, tabInfo: tabInfo }))) {\n           if (!hasCurrentData || await showConfirm(t(\"dialogs.replaceData\"))) {\n            NODE_DATA = saved.nodeData || {};\n            EDGE_DATA = saved.edgeData || { list: [] };\n            RECT_DATA = saved.rectData || { list: [] };\n            TEXT_DATA = saved.textData || { list: [] };\n            IMAGE_DATA = saved.imageData || { list: [] };\n            EDGE_LEGEND = saved.edgeLegend || {};\n            ZONE_LEGEND = saved.zoneLegend || {};\n            ZONE_PRESETS = saved.zonePresets || {};\n            loadCustomPresets();\n            savedPositions = saved.nodePositions || {};\n            savedSizes = saved.nodeSizes || {};\n            savedStyles = saved.nodeStyles || {};\n            if (saved.page) PAGE_STATE = Object.assign({}, DEFAULT_PAGE_STATE, saved.page);\n            if (saved.canvas) {\n             canvasState.zoom = saved.canvas.zoom || 1;\n             canvasState.panX = saved.canvas.panX || 0;\n             canvasState.panY = saved.canvas.panY || 0;\n            }\n            if (saved.savedTopologyView) savedTopologyView = saved.savedTopologyView;\n            if (saved.savedStyleSets) savedStyleSets = saved.savedStyleSets;\n            if (saved.selectedTheme) {\n              rebuildThemeDropdown();\n              document.getElementById(\"theme-preset\").value = saved.selectedTheme;\n              updateDeleteButton();\n            }\n            if (saved.documentTabs) documentTabs = saved.documentTabs;\n            if (saved.currentTabIndex !== undefined) currentTabIndex = saved.currentTabIndex;\n            if (saved.encryptedSections) encryptedSections = saved.encryptedSections;\n            if (saved.auditLog) auditLog = saved.auditLog;\n            if (saved.recordings) RECORDING_STATE.recordings = saved.recordings;\n            if (saved.pageTitle) {\n             const titleEl = document.getElementById(\"page-title\");\n             if (titleEl) titleEl.textContent = saved.pageTitle;\n            }\n            const layerMap = { \"physical\": \"layer1\", \"logical\": \"layer2\", \"security\": \"layer3\", \"application\": \"layer4\" };\n            Object.values(NODE_DATA).forEach(node => {\n              if (!node.tags) node.tags = [];\n              if (!node.notes) node.notes = [];\n              if (!node.layer || layerMap[node.layer]) node.layer = layerMap[node.layer] || 'layer1';\n            });\n            wieldThePower();\n            forgeTheTopology();\n            updateViewBox();\n            console.log(\"Recovered from autosave:\", savedNodeCount, \"nodes,\", savedTabCount, \"tabs\");\n            return true;\n           }\n          }\n         }\n        }\n       } catch (e) {\n        console.warn(\"Autosave recovery check failed:\", e);\n       }\n       return false;\n      }\n      function updateTopologyToolbarVisibility() {\n       const toolbar = document.getElementById(\"topology-toolbar\");\n       const mini = ensureTopologyToolbarMiniButton();\n       if (!toolbar || !mini) return;\n       if (typeof isViewOnly === 'function' && isViewOnly()) {\n        toolbar.style.setProperty('display', 'none', 'important');\n        mini.style.display = \"none\";\n        return;\n       }\n       const hasSelectedNode = currentNodeId !== null;\n       if (!hasSelectedNode) {\n        toolbar.style.setProperty('display', 'none', 'important');\n        mini.style.display = \"none\";\n       } else if (topologyToolbarCollapsed) {\n        toolbar.style.setProperty('display', 'none', 'important');\n        mini.style.display = \"inline-flex\";\n       } else {\n      if (isMobileDevice()) {\n        toolbar.style.setProperty('display', 'grid', 'important');\n        mini.style.display = \"none\";\n        } else {\n        toolbar.style.setProperty('display', 'flex', 'important');\n        mini.style.display = \"none\";\n      }\n       }\n      }\n      const THE_ONE_FILE_VERSION = \"4.1.5\";\n      const DEFAULT_CANVAS_HINT = document.getElementById(\"canvas-hint\")?.innerHTML || \"\";\n      const DEFAULT_PAGE_STATE = {\n       title: \"The One File: The Networkening\",\n       background: \"\",\n       topbarBg: \"rgba(9, 12, 20, 0.9)\",\n       topbarBorder: \"#1f2533\",\n       panel: \"#0b0e13\",\n       panelAlt: \"#10141b\",\n       accent: \"#4fd1c5\",\n       sidebarBg: \"#10141b\",\n       btnBg: \"#0b0e13\",\n       btnText: \"#e2e8f0\",\n       tagFill: \"#1e293b\",\n       tagText: \"#e2e8f0\",\n       tagBorder: \"#475569\",\n       inputBg: \"#0b0e13\",\n       inputText: \"#e2e8f0\",\n       inputBorder: \"#1f2937\",\n       inputFont: \"Inter, system-ui, sans-serif\",\n       inputFontSize: 14,\n       toolbarBg: \"#0f172a\",\n       toolbarBorder: \"#1f2937\",\n       toolbarText: \"#94a3b8\",\n       toolbarBtnBg: \"#0b0e13\",\n       toolbarBtnText: \"#e2e8f0\",\n       minimapDots: \"#94a3b8\",\n       canvasHintEnabled: true,\n       canvasHintText: \"\",\n       canvasHintBg: \"#0f172a\",\n       canvasHintColor: \"#94a3b8\",\n       danger: \"#f56565\",\n       textMain: \"#e2e8f0\",\n       textSoft: \"#94a3b8\",\n       topbarHeight: 52,\n       sidebarWidth: 350,\n       mobileFooterHeight: 40,\n       sidebarCollapsed: false,\n       nodeFill: \"#1e293b\",\n       nodeStroke: \"#475569\",\n       nodeTitle: \"#e2e8f0\",\n       nodeSub: \"#94a3b8\",\n       nodeTitleSize: 18,\n       nodeSubSize: 13,\n       nodeFont: \"Inter, system-ui, sans-serif\",\n       defaultEdge: \"#475569\",\n       selectionHandle: \"#f59e0b\",\n       selectionHandleSize: 8,\n       groupIndicator: \"#4fd1c5\",\n       canvasGradientTop: \"#1e2532\",\n       canvasGradientBottom: \"#050608\",\n       canvasBorder: \"#475569\",\n       canvasGrid: \"#475569\",\n       canvasGridSize: 50,\n       canvasGridEnabled: true,\n       rackFrameFill: \"#0f172a\",\n       rackFrameStroke: \"#4fd1c5\",\n       rackLineColor: \"#475569\",\n       rackTextColor: \"#4fd1c5\",\n       rackGridEnabled: true,\n\t   viewOnly: false,\n\t   defaultEdgeRouting: \"curved\",\n\t   animateConnections: false,\n\t   animationStyle: \"arrows\",\n\t   animationDirection: \"all\",\n\t   animationSpeed: 1.5,\n\t   motionTrailsEnabled: true,\n\t   motionTrailLength: 8,\n\t   motionTrailStyle: \"solid\",\n\t   motionTrailArrow: true,\n\t   showPortLabels: true,\n      };\n      let PAGE_STATE = Object.assign({}, DEFAULT_PAGE_STATE);\n\t  const FOV_ANIMATION_START = Date.now();\n\t  let ZONES_VISIBLE = true;\n\t  let RECORDING_STATE = {\n\t    isRecording: false,\n\t    isPlaying: false,\n\t    isPaused: false,\n\t    currentRecording: null,\n\t    recordings: [],\n\t    startTime: 0,\n\t    frames: [],\n\t    playbackIndex: 0,\n\t    playbackSpeed: 1,\n\t    loopPlayback: false,\n\t    captureInterval: null,\n\t    playbackTimer: null,\n\t    CAPTURE_FPS: 10,\n\t    videoRecorder: null,\n\t    videoChunks: [],\n\t    videoCanvas: null,\n\t    videoCtx: null,\n\t    videoAnimFrame: null,\n\t    isVideoRecording: false,\n\t    isStepRecording: false,\n\t    stepFrames: [],\n\t    stepCount: 0,\n\t    trailHistory: {}\n\t  };\nconst ANIM_SETTINGS = {\n  masterAnim: true,\n  masterZones: true,\n  animTypes: { sweep: true, pulse: true, rings: true, spin: true, connections: true },\n  animCategories: { camera: true, doorbell: true, motion: true, smoke: true, wifi: true, sensor: true, sprinkler: true, connections: true },\n  zoneCategories: { camera: true, doorbell: true, motion: true, smoke: true, wifi: true, sensor: true, sprinkler: true }\n};\nfunction getShapeCategory(shape) {\n  const map = {\n    \"camera\": \"camera\", \"cctv\": \"camera\", \"ptz-cam\": \"camera\",\n    \"doorbell\": \"doorbell\",\n    \"motion-sensor\": \"motion\", \"motion-detect\": \"motion\",\n    \"smoke-detector\": \"smoke\", \"smoke-alarm\": \"smoke\",\n    \"access-point\": \"wifi\", \"wifi\": \"wifi\", \"router\": \"wifi\", \"wifi-strong\": \"wifi\", \"wifi-weak\": \"wifi\",\n    \"sensor\": \"sensor\", \"iot\": \"sensor\",\n    \"sprinkler\": \"sprinkler\", \"sprinkler-arc\": \"sprinkler\"\n  };\n  return map[shape] || null;\n}\nfunction isAnimationAllowed(shape, animType) {\n  if (!ANIM_SETTINGS.masterAnim) return false;\n  if (!ANIM_SETTINGS.animTypes[animType]) return false;\n  const cat = getShapeCategory(shape);\n  if (cat && !ANIM_SETTINGS.animCategories[cat]) return false;\n  return true;\n}\nfunction isZoneAllowed(shape) {\n  if (!ANIM_SETTINGS.masterZones) return false;\n  const cat = getShapeCategory(shape);\n  if (cat && !ANIM_SETTINGS.zoneCategories[cat]) return false;\n  return true;\n}\nfunction applyAnimZoneSettings() {\n  document.querySelectorAll(\".fov-group\").forEach(g => {\n    const nodeEl = g.closest(\"g[data-node-id]\");\n    if (!nodeEl) return;\n    const nodeId = nodeEl.dataset.nodeId;\n    const node = NODE_DATA[nodeId];\n    if (!node) return;\n    const cat = getShapeCategory(node.shape);\n    const zoneVisible = ANIM_SETTINGS.masterZones && (!cat || ANIM_SETTINGS.zoneCategories[cat]);\n    g.style.display = zoneVisible ? \"\" : \"none\";\n    if (zoneVisible && node.fovAnimate) {\n      const animType = node.fovAnimationType || \"sweep\";\n      const animAllowed = ANIM_SETTINGS.masterAnim && ANIM_SETTINGS.animTypes[animType] && (!cat || ANIM_SETTINGS.animCategories[cat]);\n      g.style.animationPlayState = animAllowed ? \"running\" : \"paused\";\n      g.querySelectorAll(\"circle\").forEach(c => c.style.animationPlayState = animAllowed ? \"running\" : \"paused\");\n    }\n  });\n  const connAnimAllowed = ANIM_SETTINGS.masterAnim && ANIM_SETTINGS.animTypes.connections && ANIM_SETTINGS.animCategories.connections;\n  document.querySelectorAll(\".edge-arrow-forward, .edge-arrow-backward\").forEach(a => {\n    a.style.animationPlayState = connAnimAllowed ? \"running\" : \"paused\";\n  });\n}\nlet ZONE_LEGEND = {};\nlet ZONE_PRESETS = {};\nlet copiedZoneStyle = null;\n\nfunction hasCoverageZone(shape) {\n  const supportedShapes = [\n    \"camera\", \"cctv\", \"doorbell\",\n    \"motion-sensor\", \"smoke-detector\",\n    \"access-point\", \"wifi\", \"router\",\n    \"sensor\", \"iot\", \"sprinkler\"\n  ];\n  return supportedShapes.includes(shape);\n}\n\nfunction getCoverageDefaults(shape) {\n  const defaults = {\n    \"camera\": { angle: 90, distance: 150, animationType: \"sweep\" },\n    \"cctv\": { angle: 90, distance: 150, animationType: \"sweep\" },\n    \"doorbell\": { angle: 120, distance: 100, animationType: \"sweep\" },\n    \"motion-sensor\": { angle: 120, distance: 100, animationType: \"pulse\" },\n    \"smoke-detector\": { angle: 360, distance: 80, animationType: \"pulse\" },\n    \"access-point\": { angle: 360, distance: 200, animationType: \"rings\" },\n    \"wifi\": { angle: 360, distance: 200, animationType: \"rings\" },\n    \"router\": { angle: 360, distance: 200, animationType: \"rings\" },\n    \"sensor\": { angle: 90, distance: 100, animationType: \"pulse\" },\n    \"iot\": { angle: 90, distance: 100, animationType: \"pulse\" },\n    \"sprinkler\": { angle: 90, distance: 120, animationType: \"spin\" }\n  };\n  return defaults[shape] || { angle: 90, distance: 150, animationType: \"sweep\" };\n}\n\nfunction toggleAllZones() {\n  ANIM_SETTINGS.masterZones = !ANIM_SETTINGS.masterZones;\n  const masterCheckbox = document.getElementById(\"zone-master\");\n  if (masterCheckbox) masterCheckbox.checked = ANIM_SETTINGS.masterZones;\n  applyAnimZoneSettings();\n}\n\nfunction copyZoneStyle(nodeId) {\n  const node = NODE_DATA[nodeId];\n  if (!node) return false;\n  if (!hasCoverageZone(node.shape)) {\n    showAlert(t(\"dialogs.nodeTypeNoZones\"));\n    return false;\n  }\n  copiedZoneStyle = {\n    fovEnabled: node.fovEnabled,\n    fovAngle: node.fovAngle,\n    fovDistance: node.fovDistance,\n    fovInnerRadius: node.fovInnerRadius,\n    fovRotation: node.fovRotation,\n    fovColor: node.fovColor,\n    fovOpacity: node.fovOpacity,\n    fovGradient: node.fovGradient,\n    fovBorderColor: node.fovBorderColor,\n    fovBorderWidth: node.fovBorderWidth,\n    fovBorderStyle: node.fovBorderStyle,\n    fovBorderOpacity: node.fovBorderOpacity,\n    fovLabel: node.fovLabel,\n    fovLabelPosition: node.fovLabelPosition,\n    fovLabelSize: node.fovLabelSize,\n    fovLabelColor: node.fovLabelColor,\n    fovLabelBold: node.fovLabelBold,\n    fovLabelBg: node.fovLabelBg,\n    fovLabelBgColor: node.fovLabelBgColor,\n    fovLabelOffsetX: node.fovLabelOffsetX,\n    fovLabelOffsetY: node.fovLabelOffsetY,\n    fovAnimate: node.fovAnimate,\n    fovAnimationType: node.fovAnimationType,\n    fovSweep: node.fovSweep,\n    fovSpeed: node.fovSpeed\n  };\n  return true;\n}\n\nfunction pasteZoneStyle(nodeId) {\n  if (!copiedZoneStyle) {\n    showAlert(t(\"dialogs.noZoneStyleCopied\"));\n    return false;\n  }\n  const node = NODE_DATA[nodeId];\n  if (!node) return false;\n  if (!hasCoverageZone(node.shape)) {\n    showAlert(t(\"dialogs.nodeTypeNoZones\"));\n    return false;\n  }\n  pushUndo(\"paste zone style\");\n  Object.assign(node, copiedZoneStyle);\n  updateFovCone(nodeId);\n  return true;\n}\n\nfunction applyZonePreset(preset) {\n  if (!currentNodeId) return;\n  const presets = {\n    \"security-cam\": { fovEnabled: true, fovAngle: 90, fovDistance: 150, fovColor: \"#f59e0b\", fovOpacity: 20, fovAnimationType: \"sweep\", fovAnimate: false },\n    \"ptz-cam\": { fovEnabled: true, fovAngle: 60, fovDistance: 200, fovColor: \"#f59e0b\", fovOpacity: 25, fovAnimationType: \"sweep\", fovAnimate: true, fovSweep: 180, fovSpeed: 8 },\n    \"motion-detect\": { fovEnabled: true, fovAngle: 120, fovDistance: 100, fovColor: \"#10b981\", fovOpacity: 15, fovAnimationType: \"pulse\", fovAnimate: true, fovSpeed: 3 },\n    \"wifi-strong\": { fovEnabled: true, fovAngle: 360, fovDistance: 150, fovColor: \"#3b82f6\", fovOpacity: 10, fovAnimationType: \"rings\", fovAnimate: true, fovSpeed: 4 },\n    \"wifi-extended\": { fovEnabled: true, fovAngle: 360, fovDistance: 250, fovColor: \"#3b82f6\", fovOpacity: 8, fovGradient: true, fovAnimationType: \"rings\", fovAnimate: true, fovSpeed: 5 },\n    \"smoke-alarm\": { fovEnabled: true, fovAngle: 360, fovDistance: 80, fovColor: \"#ef4444\", fovOpacity: 15, fovAnimationType: \"pulse\", fovAnimate: false },\n    \"sprinkler-arc\": { fovEnabled: true, fovAngle: 90, fovDistance: 120, fovColor: \"#06b6d4\", fovOpacity: 20, fovAnimationType: \"spin\", fovAnimate: true, fovSpeed: 6 }\n  };\n  const allPresets = { ...presets, ...ZONE_PRESETS };\n  const settings = allPresets[preset];\n  if (!settings) return;\n  pushUndo(\"apply zone preset\");\n  Object.assign(NODE_DATA[currentNodeId], settings);\n  updateFovCone(currentNodeId);\n  claimTheImmortal(currentNodeId);\n}\n\nasync function saveCustomZonePreset() {\n  if (!currentNodeId) return;\n  const node = NODE_DATA[currentNodeId];\n  if (!node.fovEnabled) {\n    showAlert(t(\"dialogs.enableZoneFirst\"));\n    return;\n  }\n  const name = await showPrompt(t(\"dialogs.enterPresetName\"), \"\");\n  if (!name || !name.trim()) return;\n  const presetId = \"custom-\" + name.toLowerCase().replace(/[^a-z0-9]+/g, \"-\");\n  ZONE_PRESETS[presetId] = {\n    fovEnabled: true,\n    fovAngle: node.fovAngle,\n    fovDistance: node.fovDistance,\n    fovInnerRadius: node.fovInnerRadius,\n    fovColor: node.fovColor,\n    fovOpacity: node.fovOpacity,\n    fovGradient: node.fovGradient,\n    fovBorderColor: node.fovBorderColor,\n    fovBorderWidth: node.fovBorderWidth,\n    fovBorderStyle: node.fovBorderStyle,\n    fovBorderOpacity: node.fovBorderOpacity,\n    fovAnimationType: node.fovAnimationType,\n    fovAnimate: node.fovAnimate,\n    fovSweep: node.fovSweep,\n    fovSpeed: node.fovSpeed\n  };\n  const select = document.getElementById(\"fov-preset\");\n  if (select && !select.querySelector(`option[value=\"${presetId}\"]`)) {\n    const opt = document.createElement(\"option\");\n    opt.value = presetId;\n    opt.textContent = name.trim() + \" ★\";\n    select.appendChild(opt);\n  }\n  showAlert(t(\"dialogs.presetSaved\", { name: name.trim() }));\n}\n\nfunction bulkCopyZoneStyle() {\n  if (selectedNodes.size === 0) {\n    showAlert(t(\"dialogs.selectNodeFirst\"));\n    return;\n  }\n  const firstId = [...selectedNodes][0];\n  if (copyZoneStyle(firstId)) {\n    showAlert(t(\"dialogs.zoneStyleCopied\"));\n  }\n}\n\nfunction bulkPasteZoneStyle() {\n  if (selectedNodes.size === 0) {\n    showAlert(t(\"dialogs.selectNodesFirst\"));\n    return;\n  }\n  if (!copiedZoneStyle) {\n    showAlert(t(\"dialogs.copyZoneStyleFirst\"));\n    return;\n  }\n  pushUndo(\"bulk paste zone style\");\n  let count = 0;\n  selectedNodes.forEach(id => {\n    const node = NODE_DATA[id];\n    if (node && hasCoverageZone(node.shape)) {\n      Object.assign(node, copiedZoneStyle);\n      updateFovCone(id);\n      count++;\n    }\n  });\n  forgeTheTopology();\n  showAlert(t(\"dialogs.zoneStylePasted\", { count }));\n}\n\nfunction bulkToggleZones() {\n  if (selectedNodes.size === 0) {\n    showAlert(t(\"dialogs.selectNodesFirst\"));\n    return;\n  }\n  pushUndo(\"bulk toggle zones\");\n  const firstNode = NODE_DATA[[...selectedNodes][0]];\n  const newState = !(firstNode?.fovEnabled);\n  let count = 0;\n  selectedNodes.forEach(id => {\n    const node = NODE_DATA[id];\n    if (node && hasCoverageZone(node.shape)) {\n      node.fovEnabled = newState;\n      updateFovCone(id);\n      count++;\n    }\n  });\n  forgeTheTopology();\n  showAlert(t(\"dialogs.zonesToggled\", { count, state: newState ? t(\"dialogs.on\") : t(\"dialogs.off\") }));\n}\n\nfunction loadCustomPresets() {\n  const select = document.getElementById(\"fov-preset\");\n  if (!select) return;\n  Object.keys(ZONE_PRESETS).forEach(id => {\n    if (!select.querySelector(`option[value=\"${id}\"]`)) {\n      const opt = document.createElement(\"option\");\n      opt.value = id;\n      opt.textContent = id.replace(/-/g, \" \").replace(\"custom \", \"\") + \" ★\";\n      select.appendChild(opt);\n    }\n  });\n}\n\nfunction updateZoneLegend() {\n  const container = document.getElementById(\"edge-legend\");\n  if (!container) return;\n  container.querySelectorAll(\".zone-legend-item\").forEach(el => el.remove());\n  container.querySelectorAll(\".zone-legend-title\").forEach(el => el.remove());\n  const zoneColors = new Set();\n  Object.values(NODE_DATA).forEach(node => {\n    if (hasCoverageZone(node.shape) && node.fovEnabled && node.fovColor) {\n      zoneColors.add(node.fovColor);\n    }\n  });\n  \n  if (zoneColors.size === 0) return;\n  const zoneTitle = document.createElement(\"div\");\n  zoneTitle.className = \"legend-title zone-legend-title\";\n  zoneTitle.textContent = t(\"legends.zoneLegend\");\n  zoneTitle.style.marginTop = \"12px\";\n  zoneTitle.style.paddingTop = \"8px\";\n  zoneTitle.style.borderTop = \"1px solid var(--edge-main)\";\n  container.appendChild(zoneTitle);\n  zoneColors.forEach(color => {\n    if (!ZONE_LEGEND[color]) {\n      ZONE_LEGEND[color] = t(\"legends.coverageZone\");\n    }\n    const item = document.createElement(\"div\");\n    item.className = \"legend-item zone-legend-item\";\n    item.addEventListener(\"mousedown\", (e) => e.stopPropagation());\n    item.addEventListener(\"click\", (e) => e.stopPropagation());\n    \n    const swatch = document.createElement(\"span\");\n    swatch.className = \"legend-swatch\";\n    swatch.style.backgroundColor = color;\n    swatch.style.opacity = \"0.5\";\n    swatch.style.cursor = \"pointer\";\n    swatch.addEventListener(\"click\", (e) => {\n      e.stopPropagation();\n      const nodeWithColor = Object.entries(NODE_DATA).find(([id, n]) => \n        hasCoverageZone(n.shape) && n.fovEnabled && n.fovColor === color\n      );\n      if (nodeWithColor) {\n        claimTheImmortal(nodeWithColor[0]);\n      }\n    });\n    \n    const label = document.createElement(\"span\");\n    label.className = \"legend-label\";\n    label.textContent = ZONE_LEGEND[color];\n    label.contentEditable = true;\n    label.addEventListener(\"focus\", () => label.classList.add(\"editing\"));\n    label.addEventListener(\"blur\", () => {\n      label.classList.remove(\"editing\");\n      ZONE_LEGEND[color] = label.textContent.trim() || \"Coverage Zone\";\n    });\n    label.addEventListener(\"keydown\", (e) => {\n      if (e.key === \"Enter\") {\n        e.preventDefault();\n        label.blur();\n      }\n    });\n    \n    item.append(swatch, label);\n    container.appendChild(item);\n  });\n  \n  updateLegendVisibility();\n}\n\t  function isViewOnly() {\n       return PAGE_STATE.viewOnly === true;\n      }\n      let viewOnlyClickCount = 0;\n      let viewOnlyClickTimer = null;\n      let viewOnlyClickTarget = null;\n      function handleViewOnlyClick(id, type) {\n       if (!isViewOnly()) return false;\n       if (viewOnlyClickTarget !== id) {\n        viewOnlyClickCount = 0;\n        viewOnlyClickTarget = id;\n       }\n       viewOnlyClickCount++;\n       clearTimeout(viewOnlyClickTimer);\n       viewOnlyClickTimer = setTimeout(() => {\n        viewOnlyClickCount = 0;\n        viewOnlyClickTarget = null;\n       }, 2000);\n       if (viewOnlyClickCount >= 5) {\n        viewOnlyClickCount = 0;\n        viewOnlyClickTarget = null;\n        document.body.classList.add(\"view-only-inspect\");\n        if (type === 'node') {\n         currentNodeId = id;\n         const data = NODE_DATA[id];\n         if (data) {\n          document.querySelectorAll(\".node-group\").forEach((n) => {\n           n.classList.toggle(\"active\", n.dataset.nodeId === id);\n          });\n          document.getElementById(\"node-panel\").style.display = \"block\";\n          document.getElementById(\"edge-panel\").style.display = \"none\";\n          document.getElementById(\"node-name\").textContent = data.name;\n          const nodeIpEl = document.getElementById(\"node-ip\");\n          const modeLabels = LANG.modes[PAGE_STATE.mappingMode] || LANG.modes.network;\n          if (data.ip) {\n            nodeIpEl.textContent = data.ip;\n            nodeIpEl.style.opacity = \"1\";\n          } else {\n            nodeIpEl.textContent = \"Click to add \" + modeLabels.subtitle;\n            nodeIpEl.style.opacity = \"0.5\";\n          }\n          const fovSection = document.getElementById(\"fov-section\");\n          if (fovSection) {\n            if (hasCoverageZone(data.shape)) {\n              fovSection.style.display = \"block\";\n              const defaults = getCoverageDefaults(data.shape);\n              document.getElementById(\"fov-enabled\").checked = data.fovEnabled || false;\n              document.getElementById(\"fov-angle\").value = data.fovAngle || defaults.angle;\n              document.getElementById(\"fov-angle-value\").textContent = (data.fovAngle || defaults.angle) + \"°\";\n              document.getElementById(\"fov-distance\").value = data.fovDistance || defaults.distance;\n              document.getElementById(\"fov-distance-value\").textContent = data.fovDistance || defaults.distance;\n              document.getElementById(\"fov-inner-radius\").value = data.fovInnerRadius || 0;\n              document.getElementById(\"fov-inner-radius-value\").textContent = data.fovInnerRadius || 0;\n              document.getElementById(\"fov-rotation\").value = data.fovRotation || 0;\n              document.getElementById(\"fov-rotation-value\").textContent = (data.fovRotation || 0) + \"°\";\n              document.getElementById(\"fov-color\").value = data.fovColor || \"#f59e0b\";\n              document.getElementById(\"fov-opacity\").value = data.fovOpacity || 20;\n              document.getElementById(\"fov-opacity-value\").textContent = (data.fovOpacity || 20) + \"%\";\n              document.getElementById(\"fov-gradient\").checked = data.fovGradient || false;\n              document.getElementById(\"fov-border-color\").value = data.fovBorderColor || \"#f59e0b\";\n              document.getElementById(\"fov-border-width\").value = data.fovBorderWidth !== undefined ? data.fovBorderWidth : 2;\n              document.getElementById(\"fov-border-width-value\").textContent = data.fovBorderWidth !== undefined ? data.fovBorderWidth : 2;\n              document.getElementById(\"fov-border-style\").value = data.fovBorderStyle || \"solid\";\n              document.getElementById(\"fov-border-opacity\").value = data.fovBorderOpacity !== undefined ? data.fovBorderOpacity : 100;\n              document.getElementById(\"fov-border-opacity-value\").textContent = (data.fovBorderOpacity !== undefined ? data.fovBorderOpacity : 100) + \"%\";\n              document.getElementById(\"fov-label\").value = data.fovLabel || \"\";\n              document.getElementById(\"fov-label-position\").value = data.fovLabelPosition || \"center\";\n              document.getElementById(\"fov-label-size\").value = data.fovLabelSize || 14;\n              document.getElementById(\"fov-label-size-value\").textContent = (data.fovLabelSize || 14) + \"px\";\n              document.getElementById(\"fov-label-color\").value = data.fovLabelColor || \"#ffffff\";\n              document.getElementById(\"fov-label-bold\").checked = data.fovLabelBold || false;\n              document.getElementById(\"fov-label-bg\").checked = data.fovLabelBg || false;\n              document.getElementById(\"fov-label-bg-color\").value = data.fovLabelBgColor || \"#000000\";\n              document.getElementById(\"fov-label-offset-x\").value = data.fovLabelOffsetX || 0;\n              document.getElementById(\"fov-label-offset-x-value\").textContent = data.fovLabelOffsetX || 0;\n              document.getElementById(\"fov-label-offset-y\").value = data.fovLabelOffsetY || 0;\n              document.getElementById(\"fov-label-offset-y-value\").textContent = data.fovLabelOffsetY || 0;\n              document.getElementById(\"fov-animate\").checked = data.fovAnimate || false;\n              document.getElementById(\"fov-animation-type\").value = data.fovAnimationType || defaults.animationType;\n              document.getElementById(\"fov-sweep\").value = data.fovSweep || 120;\n              document.getElementById(\"fov-sweep-value\").textContent = (data.fovSweep || 120) + \"°\";\n              document.getElementById(\"fov-speed\").value = data.fovSpeed || 4;\n              document.getElementById(\"fov-speed-value\").textContent = (data.fovSpeed || 4) + \"s\";\n            } else {\n              fovSection.style.display = \"none\";\n            }\n          }\n          document.getElementById(\"node-role\").textContent = data.role;\n          document.getElementById(\"node-mac\").textContent = data.mac || \"--\";\n          document.getElementById(\"node-rack\").textContent = data.rackUnit || \"--\";\n          document.getElementById(\"node-uheight\").textContent = (data.uHeight || \"1\") + \"U\";\n          const tagList = document.getElementById(\"node-tags\");\n          if (tagList) {\n           tagList.innerHTML = \"\";\n           if (data.tags && data.tags.length > 0) {\n            data.tags.forEach((tag) => {\n             const li = document.createElement(\"li\");\n             li.textContent = tag;\n             tagList.appendChild(li);\n            });\n           }\n          }\n          const noteList = document.getElementById(\"node-notes\");\n          if (noteList) {\n           noteList.innerHTML = \"\";\n           if (data.notes && data.notes.length > 0) {\n            data.notes.forEach((note) => {\n             const li = document.createElement(\"li\");\n             li.textContent = note;\n             noteList.appendChild(li);\n            });\n           }\n          }\n         }\n        } else if (type === 'edge') {\n         currentEdgeId = id;\n         const edge = EDGE_DATA.list.find(e => e.id === id);\n         if (edge) {\n          document.querySelectorAll(\".edge\").forEach((e) => {\n           e.classList.toggle(\"active\", e.dataset.edgeId === id);\n          });\n          document.getElementById(\"edge-panel\").style.display = \"block\";\n          document.getElementById(\"node-panel\").style.display = \"none\";\n          document.getElementById(\"edge-from\").textContent = NODE_DATA[edge.from]?.name || edge.from;\n          document.getElementById(\"edge-to\").textContent = NODE_DATA[edge.to]?.name || edge.to;\n          document.getElementById(\"edge-label\").value = edge.label || \"\";\n         }\n        }\n        return true;\n       }\n       return false;\n      }\n      const CANVAS_WIDTH = 4000;\n      let CANVAS_HEIGHT = 3000;\n      const BASE_CANVAS_HEIGHT = 3000;\n      const CANVAS_PADDING = 100;\n      const RACK_U_HEIGHT = 70;\n      const RACK_WIDTH = 600;\n      const RACK_START_X = CANVAS_WIDTH / 2;\n      const RACK_START_Y = CANVAS_PADDING + 100;\n      function getRackUHeight(rackId) {\n      const capacity = getRackCapacity(rackId);\n      const availableHeight = CANVAS_HEIGHT - RACK_START_Y - 200;\n      return Math.floor(availableHeight / capacity);\n      }\n      let canvasState = {\n       zoom: 1,\n       panX: 0,\n       panY: 0,\n       minZoom: 0.25,\n       maxZoom: 4,\n       isPanning: false,\n       panStartX: 0,\n       panStartY: 0,\n       spacePressed: false,\n      };\n      function getViewBox() {\n       const viewWidth = CANVAS_WIDTH / canvasState.zoom;\n       const viewHeight = CANVAS_HEIGHT / canvasState.zoom;\n       return {\n        x: canvasState.panX,\n        y: canvasState.panY,\n        width: viewWidth,\n        height: viewHeight,\n       };\n      }\n\t  \n\tfunction updateViewBox() {\n\t  const svg = document.getElementById(\"map\");\n\t  const vb = getViewBox();\n\t  svg.setAttribute(\"viewBox\", `${vb.x} ${vb.y} ${vb.width} ${vb.height}`);\n\t  const zoomLevel = document.getElementById(\"zoom-level\");\n\t  if (zoomLevel) {\n\t\tzoomLevel.textContent = Math.round(canvasState.zoom * 100) + \"%\";\n\t  }\n\t  if (canvasState.zoom < 0.5) {\n\t\tsvg.classList.add(\"low-zoom\");\n\t  } else {\n\t\tsvg.classList.remove(\"low-zoom\");\n\t  }\n\t  updateMinimap();\n\t  populateRackDropdown();\n\t}\n\t  \n\tlet lastMinimapRender = 0;\n\tconst MINIMAP_THROTTLE = 100;\n\n\tfunction updateMinimap() {\n  const now = performance.now();\n  if (now - lastMinimapRender < MINIMAP_THROTTLE) return;\n  lastMinimapRender = now;\n  \n  const minimapViewport = document.getElementById(\"minimap-viewport\");\n  const minimapSvg = document.getElementById(\"minimap\");\n  if (!minimapViewport || !minimapSvg) return;\n  const vb = getViewBox();\n  minimapViewport.setAttribute(\"x\", vb.x);\n  minimapViewport.setAttribute(\"y\", vb.y);\n  minimapViewport.setAttribute(\"width\", vb.width);\n  minimapViewport.setAttribute(\"height\", vb.height);\n  minimapSvg.querySelectorAll(\".minimap-node, .minimap-edge, .minimap-wall, .minimap-rect\").forEach(el => el.remove());\n  const frag = document.createDocumentFragment();\n  const ns = \"http://www.w3.org/2000/svg\";\n  const dotColor = PAGE_STATE.minimapDots || \"#94a3b8\";\n\n  if (RECT_DATA && RECT_DATA.list && currentView.mode !== \"rack\") {\n    RECT_DATA.list.forEach((rect) => {\n      if (rect.lineStyle === \"wall\") {\n        const wallRect = document.createElementNS(ns, \"rect\");\n        wallRect.setAttribute(\"x\", rect.x);\n        wallRect.setAttribute(\"y\", rect.y);\n        wallRect.setAttribute(\"width\", rect.width);\n        wallRect.setAttribute(\"height\", rect.height);\n        wallRect.style.fill = rect.color || \"#666\";\n        wallRect.style.fillOpacity = \"0.6\";\n        wallRect.style.stroke = rect.borderColor || rect.color || \"#666\";\n        wallRect.style.strokeWidth = \"4\";\n        wallRect.classList.add(\"minimap-wall\");\n        frag.appendChild(wallRect);\n      }\n    });\n  }\n\n  EDGE_DATA.list.forEach((edge) => {\n    if (edge.type === \"custom\") {\n      if (Array.isArray(edge.points) && edge.points.length >= 2) {\n        const polyline = document.createElementNS(ns, \"polyline\");\n        polyline.setAttribute(\"points\", edge.points.map(p => `${p.x},${p.y}`).join(\" \"));\n        polyline.classList.add(\"minimap-edge\");\n        frag.appendChild(polyline);\n      }\n      return;\n    }\n    const fromNode = NODE_DATA[edge.from];\n    const toNode = NODE_DATA[edge.to];\n    if (!fromNode || !toNode) return;\n    if (currentView.mode === \"rack\") {\n      if (fromNode.assignedRack !== currentView.rackId || toNode.assignedRack !== currentView.rackId) return;\n    } else {\n      if (fromNode.assignedRack || toNode.assignedRack) return;\n    }\n    const p1 = savedPositions[edge.from];\n    const p2 = savedPositions[edge.to];\n    if (!p1 || !p2) return;\n    const routing = edge.routing || PAGE_STATE.defaultEdgeRouting || \"curved\";\n    \n    if (routing === \"orthogonal\") {\n      const dx = p2.x - p1.x;\n      const dy = p2.y - p1.y;\n      const polyline = document.createElementNS(ns, \"polyline\");\n      let points;\n      if (Math.abs(dx) > Math.abs(dy)) {\n        const midX = p1.x + dx / 2;\n        points = `${p1.x},${p1.y} ${midX},${p1.y} ${midX},${p2.y} ${p2.x},${p2.y}`;\n      } else {\n        const midY = p1.y + dy / 2;\n        points = `${p1.x},${p1.y} ${p1.x},${midY} ${p2.x},${midY} ${p2.x},${p2.y}`;\n      }\n      polyline.setAttribute(\"points\", points);\n      polyline.classList.add(\"minimap-edge\");\n      frag.appendChild(polyline);\n    } else {\n      const line = document.createElementNS(ns, \"line\");\n      line.setAttribute(\"x1\", p1.x);\n      line.setAttribute(\"y1\", p1.y);\n      line.setAttribute(\"x2\", p2.x);\n      line.setAttribute(\"y2\", p2.y);\n      line.classList.add(\"minimap-edge\");\n      frag.appendChild(line);\n    }\n  });\n\n  Object.entries(savedPositions).forEach(([id, pos]) => {\n    const node = NODE_DATA[id];\n    if (!node) return;\n    if (currentView.mode === \"rack\") {\n      if (node.assignedRack !== currentView.rackId) return;\n    } else {\n      if (node.assignedRack) return;\n    }\n    const nodeSize = savedSizes[id] || 55;\n    const s = nodeSize * 0.5;\n    \n    if (node.isRack) {\n      const rect = document.createElementNS(ns, \"rect\");\n      rect.setAttribute(\"x\", pos.x - s);\n      rect.setAttribute(\"y\", pos.y - s);\n      rect.setAttribute(\"width\", s * 2);\n      rect.setAttribute(\"height\", s * 2);\n      rect.style.fill = dotColor;\n      rect.classList.add(\"minimap-node\");\n      frag.appendChild(rect);\n    } else if (hasCoverageZone(node.shape)) {\n      const diamond = document.createElementNS(ns, \"polygon\");\n      const points = `${pos.x},${pos.y - s} ${pos.x + s},${pos.y} ${pos.x},${pos.y + s} ${pos.x - s},${pos.y}`;\n      diamond.setAttribute(\"points\", points);\n      diamond.style.fill = \"none\";\n      diamond.style.stroke = dotColor;\n      diamond.style.strokeWidth = \"6\";\n      diamond.classList.add(\"minimap-node\");\n      frag.appendChild(diamond);\n    } else {\n      const circle = document.createElementNS(ns, \"circle\");\n      circle.setAttribute(\"cx\", pos.x);\n      circle.setAttribute(\"cy\", pos.y);\n      circle.setAttribute(\"r\", s);\n      circle.style.fill = dotColor;\n      circle.classList.add(\"minimap-node\");\n      frag.appendChild(circle);\n    }\n  });\n\n  minimapSvg.insertBefore(frag, minimapViewport);\n}\n\t  \n\t  \n      function zoomTo(newZoom, centerX, centerY) {\n       const oldZoom = canvasState.zoom;\n       newZoom = Math.max(canvasState.minZoom, Math.min(canvasState.maxZoom, newZoom));\n       if (centerX !== undefined && centerY !== undefined) {\n        const oldWidth = CANVAS_WIDTH / oldZoom;\n        const oldHeight = CANVAS_HEIGHT / oldZoom;\n        const newWidth = CANVAS_WIDTH / newZoom;\n        const newHeight = CANVAS_HEIGHT / newZoom;\n        const pointX = canvasState.panX + centerX * oldWidth;\n        const pointY = canvasState.panY + centerY * oldHeight;\n        canvasState.panX = pointX - centerX * newWidth;\n        canvasState.panY = pointY - centerY * newHeight;\n       }\n       canvasState.zoom = newZoom;\n       constrainPan();\n       updateViewBox();\n      }\n      function constrainPan() {\n       const viewWidth = CANVAS_WIDTH / canvasState.zoom;\n       const viewHeight = CANVAS_HEIGHT / canvasState.zoom;\n       const minVisiblePortion = 0.1;\n       const maxPanX = CANVAS_WIDTH - viewWidth * minVisiblePortion;\n       const maxPanY = CANVAS_HEIGHT - viewHeight * minVisiblePortion;\n       const minPanX = -viewWidth * (1 - minVisiblePortion);\n       const minPanY = -viewHeight * (1 - minVisiblePortion);\n       canvasState.panX = Math.max(minPanX, Math.min(maxPanX, canvasState.panX));\n       canvasState.panY = Math.max(minPanY, Math.min(maxPanY, canvasState.panY));\n      }\n      function fitToContent() {\n       const positions = Object.values(savedPositions);\n       if (positions.length === 0) {\n        resetView();\n        return;\n       }\n       let minX = Infinity,\n        minY = Infinity,\n        maxX = -Infinity,\n        maxY = -Infinity;\n       positions.forEach((pos) => {\n        minX = Math.min(minX, pos.x - 100);\n        minY = Math.min(minY, pos.y - 100);\n        maxX = Math.max(maxX, pos.x + 100);\n        maxY = Math.max(maxY, pos.y + 100);\n       });\n       const contentWidth = maxX - minX + 200;\n       const contentHeight = maxY - minY + 200;\n       const viewport = document.getElementById(\"canvas-viewport\");\n       const vpRect = viewport.getBoundingClientRect();\n       const aspectRatio = vpRect.width / vpRect.height;\n       const contentAspect = contentWidth / contentHeight;\n       let newZoom;\n       if (contentAspect > aspectRatio) {\n        newZoom = (vpRect.width / contentWidth) * (CANVAS_WIDTH / vpRect.width);\n       } else {\n        newZoom = (vpRect.height / contentHeight) * (CANVAS_HEIGHT / vpRect.height);\n       }\n       newZoom = Math.max(canvasState.minZoom, Math.min(2, newZoom));\n       const viewWidth = CANVAS_WIDTH / newZoom;\n       const viewHeight = CANVAS_HEIGHT / newZoom;\n       canvasState.zoom = newZoom;\n       canvasState.panX = minX - 100 - (viewWidth - contentWidth) / 2;\n       canvasState.panY = minY - 100 - (viewHeight - contentHeight) / 2;\n       constrainPan();\n       updateViewBox();\n      }\n      function resetView() {\n       canvasState.zoom = 1;\n       canvasState.panX = 0;\n       canvasState.panY = 0;\n       updateViewBox();\n      }\n      function focusOnNodes(nodeIds) {\n       if (!nodeIds || nodeIds.length === 0) return;\n       const positions = nodeIds.map(id => savedPositions[id]).filter(Boolean);\n       if (positions.length === 0) return;\n       let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;\n       positions.forEach(pos => {\n        minX = Math.min(minX, pos.x);\n        minY = Math.min(minY, pos.y);\n        maxX = Math.max(maxX, pos.x);\n        maxY = Math.max(maxY, pos.y);\n       });\n       const padding = 150;\n       const contentWidth = Math.max(maxX - minX + padding * 2, 300);\n       const contentHeight = Math.max(maxY - minY + padding * 2, 300);\n       const centerX = (minX + maxX) / 2;\n       const centerY = (minY + maxY) / 2;\n       const viewport = document.getElementById(\"canvas-viewport\");\n       const vpRect = viewport.getBoundingClientRect();\n       const aspectRatio = vpRect.width / vpRect.height;\n       const contentAspect = contentWidth / contentHeight;\n       let newZoom;\n       if (contentAspect > aspectRatio) {\n        newZoom = (vpRect.width / contentWidth) * (CANVAS_WIDTH / vpRect.width);\n       } else {\n        newZoom = (vpRect.height / contentHeight) * (CANVAS_HEIGHT / vpRect.height);\n       }\n       newZoom = Math.max(canvasState.minZoom, Math.min(1.5, newZoom));\n       const viewWidth = CANVAS_WIDTH / newZoom;\n       const viewHeight = CANVAS_HEIGHT / newZoom;\n       canvasState.zoom = newZoom;\n       canvasState.panX = centerX - viewWidth / 2;\n       canvasState.panY = centerY - viewHeight / 2;\n       constrainPan();\n       updateViewBox();\n      }\n      window.addEventListener(\"DOMContentLoaded\", () => {\n       const toggle = document.getElementById(\"mobile-menu-toggle\");\n       const menu = document.getElementById(\"topbar-menu\");\n       if (!toggle || !menu) return;\n       toggle.addEventListener(\"click\", () => {\n        menu.classList.toggle(\"open\");\n       });\n       menu.addEventListener(\"click\", (e) => {\n        const target = e.target.closest(\"a, button\");\n        if (!target) return;\n        if (isMobileDevice()) {\n       menu.classList.remove(\"open\");\n      }\n       });\n       const minimapCloseBtn = document.getElementById(\"minimap-close-btn\");\n       if (minimapCloseBtn) {\n        const handleMinimapClose = (e) => {\n         e.stopPropagation();\n         e.preventDefault();\n         minimapCollapsed = true;\n         updateMinimapVisibility();\n        };\n        minimapCloseBtn.addEventListener(\"click\", handleMinimapClose);\n        minimapCloseBtn.addEventListener(\"touchend\", handleMinimapClose);\n       }\n       const drawToolbarCloseBtn = document.getElementById(\"draw-toolbar-close-btn\");\n       if (drawToolbarCloseBtn) {\n        const handleDrawClose = (e) => {\n         e.stopPropagation();\n         e.preventDefault();\n         drawToolbarCollapsed = true;\n         updateDrawToolbarVisibility();\n        };\n        drawToolbarCloseBtn.addEventListener(\"click\", handleDrawClose);\n        drawToolbarCloseBtn.addEventListener(\"touchend\", handleDrawClose);\n       }\n       const topologyToolbarCloseBtn = document.getElementById(\"topology-toolbar-close-btn\");\n       if (topologyToolbarCloseBtn) {\n        const handleTopologyClose = (e) => {\n         e.stopPropagation();\n         e.preventDefault();\n         topologyToolbarCollapsed = true;\n         updateTopologyToolbarVisibility();\n        };\n        topologyToolbarCloseBtn.addEventListener(\"click\", handleTopologyClose);\n        topologyToolbarCloseBtn.addEventListener(\"touchstart\", (e) => e.preventDefault(), {\n         passive: false\n        });\n        topologyToolbarCloseBtn.addEventListener(\"touchend\", handleTopologyClose);\n       }\n       updateMinimapVisibility();\n       updateDrawToolbarVisibility();\n       updateTopologyToolbarVisibility();\n      });\n      function applyLayerFilter() {\n       activeLayers.clear();\n       if (document.getElementById(\"layer-1\").checked) activeLayers.add(\"layer1\");\n       if (document.getElementById(\"layer-2\").checked) activeLayers.add(\"layer2\");\n       if (document.getElementById(\"layer-3\").checked) activeLayers.add(\"layer3\");\n       if (document.getElementById(\"layer-4\").checked) activeLayers.add(\"layer4\");\n       forgeTheTopology();\n      }\n      function isNodeVisible(nodeId) {\n       const node = NODE_DATA[nodeId];\n       if (!node) return false;\n       let nodeLayer = node.layer || \"layer1\";\n       const layerMap = { \"physical\": \"layer1\", \"logical\": \"layer2\", \"security\": \"layer3\", \"application\": \"layer4\" };\n       if (layerMap[nodeLayer]) nodeLayer = layerMap[nodeLayer];\n       return activeLayers.has(nodeLayer);\n      }\n      function enterRack(rackId) {\n      if (!NODE_DATA[rackId] || !NODE_DATA[rackId].isRack) return;\n      const rackCapacity = getRackCapacity(rackId);\n      const neededHeight = RACK_START_Y + (rackCapacity * RACK_U_HEIGHT) + 200;\n      if (neededHeight > BASE_CANVAS_HEIGHT) {\n      CANVAS_HEIGHT = neededHeight;\n      }\n       currentView.mode = \"rack\";\n       currentView.rackId = rackId;\n       const backBtn = document.getElementById(\"back-to-topology-btn\");\n       if (backBtn) {\n        backBtn.style.display = \"inline-block\";\n        const modeLabels = {\n         'network': 'Topology',\n         'sports': 'Sports',\n         'smart-home': 'Smart Home',\n         'floor-plan': 'Floor Plan'\n        };\n        const currentMode = PAGE_STATE.mappingMode || 'network';\n        backBtn.textContent = '← Back to ' + (modeLabels[currentMode] || 'Topology');\n       }\n       const hint = document.getElementById(\"canvas-hint\");\n       if (hint) {\n        hint.textContent = `Viewing: ${NODE_DATA[rackId]?.name || 'Rack'} | Double click empty space to exit`;\n        hint.classList.add(\"visible\");\n       }\n      const rackUHeight = getRackUHeight(rackId);\n      const rackHeight = rackCapacity * rackUHeight;\n      const rackCenterX = RACK_START_X;\n      const rackCenterY = RACK_START_Y + (rackHeight / 2);\n       const viewWidth = CANVAS_WIDTH / canvasState.zoom;\n       const viewHeight = CANVAS_HEIGHT / canvasState.zoom;\n       canvasState.panX = rackCenterX - (viewWidth / 2);\n       canvasState.panY = rackCenterY - (viewHeight / 2);\n       constrainPan();\n       updateViewBox();\n       forgeTheTopology();\n      }\n      function exitRack() {\n      CANVAS_HEIGHT = BASE_CANVAS_HEIGHT;\n      currentView.mode = \"topology\";\n      currentView.rackId = null;\n       const backBtn = document.getElementById(\"back-to-topology-btn\");\n       if (backBtn) backBtn.style.display = \"none\";\n       const hint = document.getElementById(\"canvas-hint\");\n       if (hint) {\n        hint.classList.remove(\"visible\");\n       }\n       if (savedTopologyView) {\n        canvasState.zoom = savedTopologyView.zoom;\n        canvasState.panX = savedTopologyView.panX;\n        canvasState.panY = savedTopologyView.panY;\n        updateViewBox();\n       }\n       forgeTheTopology();\n      }\n      function getRackCapacity(rackId) {\n       const node = NODE_DATA[rackId];\n       return node && node.rackCapacity ? parseInt(node.rackCapacity) : 42;\n      }\n      function populateRackDropdown() {\n       const dropdown = document.getElementById(\"node-assigned-rack\");\n       if (!dropdown) return;\n       const prev = dropdown.value;\n       dropdown.innerHTML = '<option value=\"\">None</option>';\n       Object.keys(NODE_DATA).forEach(id => {\n        if (NODE_DATA[id].isRack) {\n         const option = document.createElement(\"option\");\n         option.value = id;\n         option.textContent = NODE_DATA[id].name;\n         dropdown.appendChild(option);\n        }\n       });\n       if (prev) dropdown.value = prev;\n      }\n      function populateHostedOnDropdown() {\n       const dropdown = document.getElementById(\"node-hosted-on\");\n       if (!dropdown) return;\n       dropdown.innerHTML = '<option value=\"\">None</option>';\n       const currentRack = currentNodeId ? NODE_DATA[currentNodeId]?.assignedRack : null;\n       Object.keys(NODE_DATA).forEach(id => {\n        if (id === currentNodeId) return;\n        const node = NODE_DATA[id];\n        if (node.isRack) return;\n        if (currentRack && node.assignedRack !== currentRack) return;\n        const option = document.createElement(\"option\");\n        option.value = id;\n        option.textContent = node.name + (node.ip ? ` (${node.ip})` : '');\n        dropdown.appendChild(option);\n       });\n      }\n      function wieldThePower() {\n       const root = document.documentElement;\n       root.style.setProperty(\"--panel\", PAGE_STATE.panel);\n       root.style.setProperty(\"--panel-alt\", PAGE_STATE.panelAlt);\n       root.style.setProperty(\"--sidebar-bg\", PAGE_STATE.sidebarBg || PAGE_STATE.panelAlt);\n       root.style.setProperty(\"--btn-bg\", PAGE_STATE.btnBg || PAGE_STATE.panel);\n       root.style.setProperty(\"--btn-text\", PAGE_STATE.btnText || \"#e2e8f0\");\n       root.style.setProperty(\"--tag-fill\", PAGE_STATE.tagFill || \"#1e293b\");\n       root.style.setProperty(\"--tag-text\", PAGE_STATE.tagText || \"#e2e8f0\");\n       root.style.setProperty(\"--tag-border\", PAGE_STATE.tagBorder || \"#475569\");\n       root.style.setProperty(\"--input-bg\", PAGE_STATE.inputBg || \"#0b0e13\");\n       root.style.setProperty(\"--input-text\", PAGE_STATE.inputText || \"#e2e8f0\");\n       root.style.setProperty(\"--input-border\", PAGE_STATE.inputBorder || \"#1f2937\");\n       root.style.setProperty(\"--input-font\", PAGE_STATE.inputFont || \"Inter, system-ui, sans-serif\");\n       root.style.setProperty(\"--input-font-size\", (PAGE_STATE.inputFontSize || 14) + \"px\");\n       root.style.setProperty(\"--toolbar-bg\", PAGE_STATE.toolbarBg || \"#0f172a\");\n       root.style.setProperty(\"--toolbar-border\", PAGE_STATE.toolbarBorder || \"#1f2937\");\n       root.style.setProperty(\"--toolbar-text\", PAGE_STATE.toolbarText || \"#94a3b8\");\n       root.style.setProperty(\"--toolbar-btn-bg\", PAGE_STATE.toolbarBtnBg || \"#0b0e13\");\n       root.style.setProperty(\"--toolbar-btn-text\", PAGE_STATE.toolbarBtnText || \"#e2e8f0\");\n       root.style.setProperty(\"--minimap-dots\", PAGE_STATE.minimapDots || \"#94a3b8\");\n       root.style.setProperty(\"--canvas-hint-bg\", PAGE_STATE.canvasHintBg || \"#0f172a\");\n       root.style.setProperty(\"--canvas-hint-color\", PAGE_STATE.canvasHintColor || \"#94a3b8\");\n       const canvasHint = document.getElementById(\"canvas-hint\");\n       if (canvasHint) {\n        canvasHint.style.display = PAGE_STATE.canvasHintEnabled === false ? \"none\" : \"\";\n        if (PAGE_STATE.canvasHintText !== undefined && PAGE_STATE.canvasHintText !== \"\") {\n         canvasHint.textContent = PAGE_STATE.canvasHintText;\n        } else {\n         MobileManager.updateCanvasHint();\n        }\n       }\n       root.style.setProperty(\"--accent\", PAGE_STATE.accent);\n       root.style.setProperty(\"--danger\", PAGE_STATE.danger);\n       root.style.setProperty(\"--text-main\", PAGE_STATE.textMain);\n       root.style.setProperty(\"--text-soft\", PAGE_STATE.textSoft);\n       root.style.setProperty(\"--topbar-bg\", PAGE_STATE.topbarBg);\n       root.style.setProperty(\"--topbar-border\", PAGE_STATE.topbarBorder);\n       root.style.setProperty(\"--node-fill\", PAGE_STATE.nodeFill || \"#1e293b\");\n       root.style.setProperty(\"--node-stroke\", PAGE_STATE.nodeStroke || \"#475569\");\n       root.style.setProperty(\"--node-title\", PAGE_STATE.nodeTitle || \"#e2e8f0\");\n       root.style.setProperty(\"--node-sub\", PAGE_STATE.nodeSub || \"#94a3b8\");\n       root.style.setProperty(\"--node-title-size\", (PAGE_STATE.nodeTitleSize || 18) + \"px\");\n       root.style.setProperty(\"--node-sub-size\", (PAGE_STATE.nodeSubSize || 13) + \"px\");\n       root.style.setProperty(\"--node-font\", PAGE_STATE.nodeFont || \"Inter, system-ui, sans-serif\");\n       root.style.setProperty(\"--default-edge\", PAGE_STATE.defaultEdge || \"#475569\");\n       root.style.setProperty(\"--selection-handle\", PAGE_STATE.selectionHandle || \"#f59e0b\");\n       root.style.setProperty(\"--selection-handle-size\", (PAGE_STATE.selectionHandleSize || 8) + \"px\");\n       root.style.setProperty(\"--group-indicator\", PAGE_STATE.groupIndicator || \"#4fd1c5\");\n       const topbarHeight = PAGE_STATE.topbarHeight || 52;\n       const sidebarWidth = PAGE_STATE.sidebarWidth || 350;\n       const mobileFooterHeight = PAGE_STATE.mobileFooterHeight || 40;\n       root.style.setProperty(\"--topbar-height\", topbarHeight + \"px\");\n       root.style.setProperty(\"--sidebar-width\", sidebarWidth + \"px\");\n       root.style.setProperty(\"--mobile-footer-height\", mobileFooterHeight + \"vh\");\n       const mainEl = document.querySelector(\"main\");\n       const detailsPanel = document.getElementById(\"details-panel\");\n       const sidebarToggle = document.getElementById(\"sidebar-toggle\");\n       if (PAGE_STATE.sidebarCollapsed) {\n        if (mainEl) mainEl.classList.add(\"sidebar-collapsed\");\n        if (detailsPanel) detailsPanel.classList.add(\"collapsed\");\n        if (sidebarToggle) {\n         sidebarToggle.classList.add(\"collapsed\");\n         sidebarToggle.textContent = \"▶\";\n        }\n       } else {\n        if (mainEl) mainEl.classList.remove(\"sidebar-collapsed\");\n        if (detailsPanel) detailsPanel.classList.remove(\"collapsed\");\n        if (sidebarToggle) {\n         sidebarToggle.classList.remove(\"collapsed\");\n         sidebarToggle.textContent = \"◀\";\n        }\n       }\n       if (PAGE_STATE.background) {\n        document.body.style.background = PAGE_STATE.background;\n       } else {\n        document.body.style.background = `radial-gradient(circle at top, ${PAGE_STATE.canvasGradientTop || \"#1e2532\"} 0, ${PAGE_STATE.canvasGradientBottom || \"#050608\"} 70%)`;\n       }\n       document.title = PAGE_STATE.title || DEFAULT_PAGE_STATE.title;\n       const titleEl = document.getElementById(\"page-title\");\n       if (titleEl) {\n        titleEl.textContent = PAGE_STATE.title || DEFAULT_PAGE_STATE.title;\n       }\n       const viewOnlyMode = PAGE_STATE.viewOnly === true;\n       const addNodeBtn = document.getElementById(\"add-node-btn\");\n       const addRackBtn = document.getElementById(\"add-rack-btn\");\n       const bulkToolbar = document.getElementById(\"bulk-toolbar\");\n       const bulkToolbarMobile = document.getElementById(\"bulk-toolbar-mobile\");\n       if (viewOnlyMode) {\n        if (addNodeBtn) addNodeBtn.style.display = \"none\";\n        if (addRackBtn) addRackBtn.style.display = \"none\";\n        if (bulkToolbar) bulkToolbar.style.display = \"none\";\n        if (bulkToolbarMobile) bulkToolbarMobile.style.display = \"none\";\n        [\"draw-toolbar-mini\", \"topology-toolbar-mini\"].forEach(id => {\n         const el = document.getElementById(id);\n         if (el) el.style.display = \"none\";\n        });\n        document.body.classList.remove(\"view-only-inspect\");\n       } else {\n        if (addNodeBtn) addNodeBtn.style.display = \"\";\n        if (addRackBtn) addRackBtn.style.display = \"\";\n       }\n       document.body.classList.toggle(\"view-only-mode\", viewOnlyMode);\n       if (!viewOnlyMode) {\n        if (typeof updateDrawToolbarVisibility === 'function') updateDrawToolbarVisibility();\n        if (typeof updateTopologyToolbarVisibility === 'function') updateTopologyToolbarVisibility();\n       }\n       const mappingModeSelect = document.getElementById(\"mapping-mode-select\");\n       if (mappingModeSelect) mappingModeSelect.value = PAGE_STATE.mappingMode || \"network\";\n       const canvasStyleSelect = document.getElementById(\"canvas-style-select\");\n       if (canvasStyleSelect && PAGE_STATE.canvasTemplate) {\n         canvasStyleSelect.value = PAGE_STATE.canvasTemplate;\n       }\n       if (typeof applyMappingModeLabels === \"function\") applyMappingModeLabels();\n       if (typeof applyLanguage === \"function\") applyLanguage();\n       if (typeof updateCanvasStyleOptions === \"function\") updateCanvasStyleOptions();\n      }\n      (async function awakeTheImmortal() {\n       let initialState = {};\n       let decryptionCancelled = false;\n       const stateEl = document.getElementById(\"topology-state\");\n       if (stateEl && stateEl.textContent.trim()) {\n        try {\n         let stateText = stateEl.textContent.trim();\n         if (isEncrypted(stateText)) {\n          let decrypted = false;\n          let attempts = 0;\n          const maxAttempts = 3;\n          while (!decrypted && attempts < maxAttempts) {\n           const password = await showPrompt(t(\"dialogs.enterDecryptPassword\", { attempt: attempts + 1, max: maxAttempts }));\n           if (!password) {\n            showAlert(t(\"dialogs.decryptionCancelled\"));\n            decryptionCancelled = true;\n            break;\n           }\n           try {\n            stateText = await decryptData(stateText, password);\n            decrypted = true;\n           } catch (e) {\n            attempts++;\n            if (attempts < maxAttempts) {\n             showAlert(t(\"dialogs.incorrectPassword\"));\n            } else {\n             showAlert(t(\"dialogs.maxAttemptsReached\"));\n             decryptionCancelled = true;\n            }\n           }\n          }\n          if (!decrypted) {\n           stateText = \"{}\";\n          }\n         }\n         initialState = JSON.parse(stateText);\n        } catch (e) {\n         console.error(\"Failed to load state:\", e);\n         initialState = {};\n        }\n       }\n       if (decryptionCancelled) {\n        NODE_DATA = {};\n        EDGE_DATA = {\n         list: []\n        };\n        RECT_DATA = { list: [] };\n        TEXT_DATA = { list: [] };\n        IMAGE_DATA = { list: [] };\n        EDGE_LEGEND = {};\n        savedPositions = {};\n        savedSizes = {};\n        savedStyles = {};\n       } else {\n        NODE_DATA = initialState.nodeData ? initialState.nodeData : BASE_NODE_DATA;\n        Object.keys(NODE_DATA).forEach(nodeId => {\n         if (!NODE_DATA[nodeId].ping) {\n          NODE_DATA[nodeId].ping = {\n           enabled: false,\n           protocol: 'http',\n           customUrl: '',\n           timeout: 3000,\n           status: 'unknown',\n           lastCheck: null\n          };\n         }\n        });\n        EDGE_DATA = initialState.edgeData ? initialState.edgeData : {\n         list: [],\n        };\n        RECT_DATA = initialState.rectData ? initialState.rectData : { list: [] };\n        TEXT_DATA = initialState.textData ? initialState.textData : { list: [] };\n        IMAGE_DATA = initialState.imageData ? initialState.imageData : { list: [] };\n        EDGE_LEGEND = initialState.edgeLegend || {};\n        savedPositions = initialState.nodePositions || {};\n        savedSizes = initialState.nodeSizes || {};\n        savedStyles = initialState.nodeStyles || {};\n        if (initialState.iconCache) {\n         IconLibrary.iconCache = initialState.iconCache;\n        }\n       }\n       if (initialState.page) {\n        PAGE_STATE = Object.assign({}, DEFAULT_PAGE_STATE, initialState.page);\n       }\n       if (PAGE_STATE.autoPingEnabled !== undefined) {\n        autoPingEnabled = PAGE_STATE.autoPingEnabled;\n       }\n       if (PAGE_STATE.autoPingInterval !== undefined) {\n        autoPingInterval = PAGE_STATE.autoPingInterval;\n       }\n       if (initialState.canvas) {\n        canvasState.zoom = initialState.canvas.zoom || 1;\n        canvasState.panX = initialState.canvas.panX || 0;\n        canvasState.panY = initialState.canvas.panY || 0;\n       }\n       if (initialState.savedTopologyView) {\n        savedTopologyView = initialState.savedTopologyView;\n       }\n       if (initialState.documentTabs) {\n        documentTabs = initialState.documentTabs;\n        if (initialState.currentTabIndex !== undefined) {\n          currentTabIndex = initialState.currentTabIndex;\n          const currentTab = documentTabs[currentTabIndex];\n          if (currentTab) {\n            NODE_DATA = currentTab.nodes || NODE_DATA;\n            EDGE_DATA = currentTab.edges || EDGE_DATA;\n            savedPositions = currentTab.positions || savedPositions;\n            savedSizes = currentTab.sizes || savedSizes;\n            savedStyles = currentTab.styles || savedStyles;\n            EDGE_LEGEND = currentTab.legend || EDGE_LEGEND;\n            RECT_DATA = currentTab.rects || RECT_DATA;\n            TEXT_DATA = currentTab.texts || TEXT_DATA;\n            if (currentTab.pageState) PAGE_STATE = Object.assign({}, DEFAULT_PAGE_STATE, currentTab.pageState);\n          }\n        }\n       }\n       if (initialState.encryptedSections) {\n        encryptedSections = initialState.encryptedSections;\n       }\n       if (initialState.recordings && Array.isArray(initialState.recordings)) {\n        RECORDING_STATE.recordings = initialState.recordings;\n        RECORDING_STATE.currentRecording = initialState.recordings[0] || null;\n       }\n       if (initialState.customLanguage) {\n        CUSTOM_LANG = initialState.customLanguage;\n        applyLanguage();\n       }\n       const layerMap = { \"physical\": \"layer1\", \"logical\": \"layer2\", \"security\": \"layer3\", \"application\": \"layer4\" };\n       Object.values(NODE_DATA).forEach(node => {\n         if (!node.tags) node.tags = [];\n         if (!node.notes) node.notes = [];\n         if (!node.layer || layerMap[node.layer]) node.layer = layerMap[node.layer] || 'layer1';\n       });\n       wieldThePower();\n       forgeTheTopology();\n       updateViewBox();\n       MobileManager.applyAll();\n       if (autoPingEnabled) {\n        startAutoPing();\n       }\n       const initialNodes = Object.keys(NODE_DATA);\n       if (initialNodes.length > 0) {\n        claimTheImmortal(initialNodes.includes(\"host\") ? \"host\" : initialNodes[0]);\n       } else {\n        document.getElementById(\"node-panel\").style.display = \"none\";\n        document.getElementById(\"topology-toolbar\").style.display = \"none\";\n       }\n       if (Object.keys(NODE_DATA).length === 0 && EDGE_DATA.list.length === 0) {\n        checkForAutosaveRecovery().then(recovered => {\n          if (recovered) return;\n          if (Object.keys(NODE_DATA).length > 0 || EDGE_DATA.list.length > 0) return;\n          if (PAGE_STATE.background && PAGE_STATE.background !== \"\") return;\n          if (typeof showWelcomeModal === 'function') showWelcomeModal();\n        });\n       }\n       startAutosave();\n      })();\n      let resizeDebounceTimer = null;\n      window.addEventListener(\"resize\", () => {\n        clearTimeout(resizeDebounceTimer);\n        resizeDebounceTimer = setTimeout(() => {\n          MobileManager.applyAll();\n          if (typeof updateDrawToolbarVisibility === 'function') updateDrawToolbarVisibility();\n          if (typeof updateTopologyToolbarVisibility === 'function') updateTopologyToolbarVisibility();\n        }, 100);\n      });\n      function getBreakpointKey() {\n       const w = window.innerWidth;\n       if (w <= 380) return \"fold\";\n       if (w <= 768) return \"mobile\";\n       if (w <= 1024) return \"tablet\";\n       return \"desktop\";\n      }\n      function resolveStylesEntry(styleEntry) {\n       if (!styleEntry) return {};\n       if (styleEntry.circleColor || styleEntry.titleColor || styleEntry.titleFont || styleEntry.titleSize || styleEntry.subColor || styleEntry.subFont || styleEntry.subSize) {\n        return styleEntry;\n       }\n       const bp = getBreakpointKey();\n       const base = styleEntry.all || {};\n       const bpStyles = styleEntry[bp] || {};\n       return Object.assign({}, base, bpStyles);\n      }\n      function resolveStylesForNode(id) {\n       const styleEntry = savedStyles[id];\n       if (!styleEntry) return {};\n       return resolveStylesEntry(styleEntry);\n      }\n      function ensureStyleEntry(id) {\n       if (!savedStyles[id]) savedStyles[id] = {};\n       const entry = savedStyles[id];\n       const flatProps = [\"circleColor\", \"circleBorder\", \"titleColor\", \"titleFont\", \"titleSize\", \"subColor\", \"subFont\", \"subSize\", ];\n       const isFlat = flatProps.some((p) => Object.prototype.hasOwnProperty.call(entry, p));\n       if (isFlat) {\n        const all = entry.all || {};\n        flatProps.forEach((p) => {\n         if (entry[p] !== undefined) {\n          all[p] = entry[p];\n          delete entry[p];\n         }\n        });\n        entry.all = all;\n       }\n       return entry;\n      }\n      function getDefaultSize() {\n       if (window.innerWidth <= 380) return 120;\n       if (window.innerWidth <= 768) return 140;\n       if (window.innerWidth <= 1024) return 70;\n       return 55;\n      }\n      function createShapeElement(shape, size) {\n       const ns = \"http://www.w3.org/2000/svg\";\n       if (shape === \"circle\") {\n        const c = document.createElementNS(ns, \"circle\");\n        c.setAttribute(\"r\", size);\n        return c;\n       }\n       if (shape === \"square\") {\n        const r = document.createElementNS(ns, \"rect\");\n        const s = size * 2;\n        r.setAttribute(\"x\", -size);\n        r.setAttribute(\"y\", -size);\n        r.setAttribute(\"width\", s);\n        r.setAttribute(\"height\", s);\n        r.setAttribute(\"rx\", 4);\n        return r;\n       }\n       if (shape === \"rectangle\") {\n        const r = document.createElementNS(ns, \"rect\");\n        r.setAttribute(\"x\", -size * 1.4);\n        r.setAttribute(\"y\", -size * 0.8);\n        r.setAttribute(\"width\", size * 2.8);\n        r.setAttribute(\"height\", size * 1.6);\n        r.setAttribute(\"rx\", 6);\n        return r;\n       }\n       if (shape === \"triangle\") {\n        const p = document.createElementNS(ns, \"polygon\");\n        const h = size * 1.8;\n        const w = size * 2;\n        p.setAttribute(\"points\", `0,${-h} ${w / 2},${h / 2} ${-w / 2},${h / 2}`);\n        return p;\n       }\n       if (shape === \"hexagon\") {\n        const p = document.createElementNS(ns, \"polygon\");\n        const s = size;\n        const pts = [\n         [0, -s],\n         [s * 0.86, -s * 0.5],\n         [s * 0.86, s * 0.5],\n         [0, s],\n         [-s * 0.86, s * 0.5],\n         [-s * 0.86, -s * 0.5],\n        ].map((pt) => pt.join(\",\")).join(\" \");\n        p.setAttribute(\"points\", pts);\n        return p;\n       }\n       if (shape === \"stop-sign\") {\n        const p = document.createElementNS(ns, \"polygon\");\n        const r = size;\n        const ptsArr = [];\n        for (let i = 0; i < 8; i++) {\n         const a = (Math.PI / 4) * i + Math.PI / 8;\n         ptsArr.push([Math.cos(a) * r, Math.sin(a) * r]);\n        }\n        const pts = ptsArr.map((pt) => pt.join(\",\")).join(\" \");\n        p.setAttribute(\"points\", pts);\n        return p;\n       }\n       if (shape === \"star\") {\n        const p = document.createElementNS(ns, \"polygon\");\n        const outer = size;\n        const inner = size * 0.45;\n        let pts = \"\";\n        for (let i = 0; i < 10; i++) {\n         const a = (Math.PI / 5) * i - Math.PI / 2;\n         const r = i % 2 === 0 ? outer : inner;\n         pts += `${Math.cos(a) * r},${Math.sin(a) * r} `;\n        }\n        p.setAttribute(\"points\", pts.trim());\n        return p;\n       }\n       if (shape === \"diamond\") {\n        const p = document.createElementNS(ns, \"polygon\");\n        const s = size;\n        p.setAttribute(\"points\", `0,${-s} ${s},0 0,${s} ${-s},0`);\n        return p;\n       }\n       if (shape === \"server\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 1.2);\n        body.setAttribute(\"y\", -size * 0.6);\n        body.setAttribute(\"width\", size * 2.4);\n        body.setAttribute(\"height\", size * 1.2);\n        body.setAttribute(\"rx\", 4);\n        g.appendChild(body);\n        for (let i = 0; i < 3; i++) {\n         const line = document.createElementNS(ns, \"line\");\n         line.setAttribute(\"x1\", -size * 0.9 + i * size * 0.5);\n         line.setAttribute(\"y1\", -size * 0.3);\n         line.setAttribute(\"x2\", -size * 0.9 + i * size * 0.5);\n         line.setAttribute(\"y2\", size * 0.3);\n         line.style.stroke = \"currentColor\";\n         line.style.strokeWidth = \"2\";\n         line.style.opacity = \"0.5\";\n         g.appendChild(line);\n        }\n        const led = document.createElementNS(ns, \"circle\");\n        led.setAttribute(\"cx\", size * 0.9);\n        led.setAttribute(\"cy\", 0);\n        led.setAttribute(\"r\", size * 0.1);\n        led.style.fill = \"#4ade80\";\n        g.appendChild(led);\n        return g;\n       }\n       if (shape === \"pc\" || shape === \"desktop\") {\n        const g = document.createElementNS(ns, \"g\");\n        const monitor = document.createElementNS(ns, \"rect\");\n        monitor.setAttribute(\"x\", -size * 0.9);\n        monitor.setAttribute(\"y\", -size * 0.8);\n        monitor.setAttribute(\"width\", size * 1.8);\n        monitor.setAttribute(\"height\", size * 1.2);\n        monitor.setAttribute(\"rx\", 4);\n        g.appendChild(monitor);\n        const screen = document.createElementNS(ns, \"rect\");\n        screen.setAttribute(\"x\", -size * 0.75);\n        screen.setAttribute(\"y\", -size * 0.65);\n        screen.setAttribute(\"width\", size * 1.5);\n        screen.setAttribute(\"height\", size * 0.9);\n        screen.setAttribute(\"rx\", 2);\n        screen.style.fill = \"#1e293b\";\n        g.appendChild(screen);\n        const neck = document.createElementNS(ns, \"rect\");\n        neck.setAttribute(\"x\", -size * 0.15);\n        neck.setAttribute(\"y\", size * 0.4);\n        neck.setAttribute(\"width\", size * 0.3);\n        neck.setAttribute(\"height\", size * 0.3);\n        g.appendChild(neck);\n        const base = document.createElementNS(ns, \"rect\");\n        base.setAttribute(\"x\", -size * 0.5);\n        base.setAttribute(\"y\", size * 0.7);\n        base.setAttribute(\"width\", size * 1);\n        base.setAttribute(\"height\", size * 0.15);\n        base.setAttribute(\"rx\", 2);\n        g.appendChild(base);\n        return g;\n       }\n       if (shape === \"laptop\") {\n        const g = document.createElementNS(ns, \"g\");\n        const screen = document.createElementNS(ns, \"rect\");\n        screen.setAttribute(\"x\", -size * 0.8);\n        screen.setAttribute(\"y\", -size * 0.9);\n        screen.setAttribute(\"width\", size * 1.6);\n        screen.setAttribute(\"height\", size * 1.1);\n        screen.setAttribute(\"rx\", 4);\n        g.appendChild(screen);\n        const base = document.createElementNS(ns, \"rect\");\n        base.setAttribute(\"x\", -size * 0.9);\n        base.setAttribute(\"y\", size * 0.25);\n        base.setAttribute(\"width\", size * 1.8);\n        base.setAttribute(\"height\", size * 0.6);\n        base.setAttribute(\"rx\", 4);\n        g.appendChild(base);\n        const pad = document.createElementNS(ns, \"rect\");\n        pad.setAttribute(\"x\", -size * 0.25);\n        pad.setAttribute(\"y\", size * 0.45);\n        pad.setAttribute(\"width\", size * 0.5);\n        pad.setAttribute(\"height\", size * 0.25);\n        pad.setAttribute(\"rx\", 2);\n        pad.style.fill = \"#1e293b\";\n        g.appendChild(pad);\n        return g;\n       }\n       if (shape === \"phone\" || shape === \"mobile\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 0.45);\n        body.setAttribute(\"y\", -size);\n        body.setAttribute(\"width\", size * 0.9);\n        body.setAttribute(\"height\", size * 2);\n        body.setAttribute(\"rx\", 8);\n        g.appendChild(body);\n        const screen = document.createElementNS(ns, \"rect\");\n        screen.setAttribute(\"x\", -size * 0.38);\n        screen.setAttribute(\"y\", -size * 0.85);\n        screen.setAttribute(\"width\", size * 0.76);\n        screen.setAttribute(\"height\", size * 1.6);\n        screen.setAttribute(\"rx\", 4);\n        screen.style.fill = \"#1e293b\";\n        g.appendChild(screen);\n        const btn = document.createElementNS(ns, \"rect\");\n        btn.setAttribute(\"x\", -size * 0.15);\n        btn.setAttribute(\"y\", size * 0.82);\n        btn.setAttribute(\"width\", size * 0.3);\n        btn.setAttribute(\"height\", size * 0.06);\n        btn.setAttribute(\"rx\", 2);\n        btn.style.fill = \"#475569\";\n        g.appendChild(btn);\n        return g;\n       }\n       if (shape === \"router\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 1.1);\n        body.setAttribute(\"y\", -size * 0.3);\n        body.setAttribute(\"width\", size * 2.2);\n        body.setAttribute(\"height\", size * 0.8);\n        body.setAttribute(\"rx\", 6);\n        g.appendChild(body);\n        for (let i = -1; i <= 1; i++) {\n         const ant = document.createElementNS(ns, \"rect\");\n         ant.setAttribute(\"x\", i * size * 0.6 - size * 0.05);\n         ant.setAttribute(\"y\", -size * 0.9);\n         ant.setAttribute(\"width\", size * 0.1);\n         ant.setAttribute(\"height\", size * 0.6);\n         ant.setAttribute(\"rx\", 2);\n         g.appendChild(ant);\n         const tip = document.createElementNS(ns, \"circle\");\n         tip.setAttribute(\"cx\", i * size * 0.6);\n         tip.setAttribute(\"cy\", -size * 0.95);\n         tip.setAttribute(\"r\", size * 0.08);\n         g.appendChild(tip);\n        }\n        for (let i = 0; i < 4; i++) {\n         const led = document.createElementNS(ns, \"circle\");\n         led.setAttribute(\"cx\", -size * 0.7 + i * size * 0.35);\n         led.setAttribute(\"cy\", size * 0.1);\n         led.setAttribute(\"r\", size * 0.06);\n         led.style.fill = i < 2 ? \"#4ade80\" : \"#facc15\";\n         g.appendChild(led);\n        }\n        return g;\n       }\n       if (shape === \"switch\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 1.4);\n        body.setAttribute(\"y\", -size * 0.4);\n        body.setAttribute(\"width\", size * 2.8);\n        body.setAttribute(\"height\", size * 0.8);\n        body.setAttribute(\"rx\", 4);\n        g.appendChild(body);\n        for (let i = 0; i < 8; i++) {\n         const port = document.createElementNS(ns, \"rect\");\n         port.setAttribute(\"x\", -size * 1.2 + i * size * 0.32);\n         port.setAttribute(\"y\", -size * 0.15);\n         port.setAttribute(\"width\", size * 0.22);\n         port.setAttribute(\"height\", size * 0.3);\n         port.setAttribute(\"rx\", 1);\n         port.style.fill = \"#1e293b\";\n         g.appendChild(port);\n        }\n        return g;\n       }\n       if (shape === \"firewall\") {\n        const g = document.createElementNS(ns, \"g\");\n        const wall = document.createElementNS(ns, \"rect\");\n        wall.setAttribute(\"x\", -size);\n        wall.setAttribute(\"y\", -size * 0.8);\n        wall.setAttribute(\"width\", size * 2);\n        wall.setAttribute(\"height\", size * 1.6);\n        wall.setAttribute(\"rx\", 4);\n        g.appendChild(wall);\n        for (let row = 0; row < 3; row++) {\n         const line = document.createElementNS(ns, \"line\");\n         line.setAttribute(\"x1\", -size * 0.85);\n         line.setAttribute(\"y1\", -size * 0.5 + row * size * 0.45);\n         line.setAttribute(\"x2\", size * 0.85);\n         line.setAttribute(\"y2\", -size * 0.5 + row * size * 0.45);\n         line.style.stroke = \"#475569\";\n         line.style.strokeWidth = \"2\";\n         g.appendChild(line);\n        }\n        for (let row = 0; row < 4; row++) {\n         const offset = row % 2 === 0 ? 0 : size * 0.35;\n         for (let col = 0; col < 3; col++) {\n          const line = document.createElementNS(ns, \"line\");\n          const x = -size * 0.5 + col * size * 0.7 + offset;\n          if (x > -size * 0.85 && x < size * 0.85) {\n           line.setAttribute(\"x1\", x);\n           line.setAttribute(\"y1\", -size * 0.8 + row * size * 0.45);\n           line.setAttribute(\"x2\", x);\n           line.setAttribute(\"y2\", -size * 0.8 + row * size * 0.45 + size * 0.45);\n           line.style.stroke = \"#475569\";\n           line.style.strokeWidth = \"2\";\n           g.appendChild(line);\n          }\n         }\n        }\n        return g;\n       }\n       if (shape === \"cloud\") {\n        const p = document.createElementNS(ns, \"path\");\n        const s = size;\n        p.setAttribute(\"d\", `\n             M ${-s * 0.8} ${s * 0.2}\n             Q ${-s * 1.1} ${s * 0.2} ${-s * 1.1} ${-s * 0.1}\n             Q ${-s * 1.1} ${-s * 0.5} ${-s * 0.7} ${-s * 0.5}\n             Q ${-s * 0.7} ${-s * 0.9} ${-s * 0.2} ${-s * 0.9}\n             Q ${s * 0.1} ${-s * 1.1} ${s * 0.5} ${-s * 0.8}\n             Q ${s * 1} ${-s * 0.8} ${s * 1.1} ${-s * 0.3}\n             Q ${s * 1.3} ${-s * 0.1} ${s * 1.1} ${s * 0.2}\n             Q ${s * 1.1} ${s * 0.5} ${s * 0.7} ${s * 0.5}\n             L ${-s * 0.5} ${s * 0.5}\n             Q ${-s * 0.9} ${s * 0.5} ${-s * 0.9} ${s * 0.2}\n             Z\n            `);\n        return p;\n       }\n       if (shape === \"database\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 0.7);\n        body.setAttribute(\"y\", -size * 0.6);\n        body.setAttribute(\"width\", size * 1.4);\n        body.setAttribute(\"height\", size * 1.4);\n        g.appendChild(body);\n        const top = document.createElementNS(ns, \"ellipse\");\n        top.setAttribute(\"cx\", 0);\n        top.setAttribute(\"cy\", -size * 0.6);\n        top.setAttribute(\"rx\", size * 0.7);\n        top.setAttribute(\"ry\", size * 0.25);\n        g.appendChild(top);\n        const bottom = document.createElementNS(ns, \"ellipse\");\n        bottom.setAttribute(\"cx\", 0);\n        bottom.setAttribute(\"cy\", size * 0.8);\n        bottom.setAttribute(\"rx\", size * 0.7);\n        bottom.setAttribute(\"ry\", size * 0.25);\n        g.appendChild(bottom);\n        const mid1 = document.createElementNS(ns, \"ellipse\");\n        mid1.setAttribute(\"cx\", 0);\n        mid1.setAttribute(\"cy\", -size * 0.15);\n        mid1.setAttribute(\"rx\", size * 0.7);\n        mid1.setAttribute(\"ry\", size * 0.2);\n        mid1.style.fill = \"none\";\n        mid1.style.stroke = \"#475569\";\n        mid1.style.strokeWidth = \"2\";\n        g.appendChild(mid1);\n        const mid2 = document.createElementNS(ns, \"ellipse\");\n        mid2.setAttribute(\"cx\", 0);\n        mid2.setAttribute(\"cy\", size * 0.35);\n        mid2.setAttribute(\"rx\", size * 0.7);\n        mid2.setAttribute(\"ry\", size * 0.2);\n        mid2.style.fill = \"none\";\n        mid2.style.stroke = \"#475569\";\n        mid2.style.strokeWidth = \"2\";\n        g.appendChild(mid2);\n        return g;\n       }\n       if (shape === \"printer\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size);\n        body.setAttribute(\"y\", -size * 0.4);\n        body.setAttribute(\"width\", size * 2);\n        body.setAttribute(\"height\", size * 0.9);\n        body.setAttribute(\"rx\", 4);\n        g.appendChild(body);\n        const trayTop = document.createElementNS(ns, \"rect\");\n        trayTop.setAttribute(\"x\", -size * 0.7);\n        trayTop.setAttribute(\"y\", -size * 0.8);\n        trayTop.setAttribute(\"width\", size * 1.4);\n        trayTop.setAttribute(\"height\", size * 0.4);\n        trayTop.setAttribute(\"rx\", 2);\n        trayTop.style.fill = \"#1e293b\";\n        g.appendChild(trayTop);\n        const trayOut = document.createElementNS(ns, \"rect\");\n        trayOut.setAttribute(\"x\", -size * 0.6);\n        trayOut.setAttribute(\"y\", size * 0.5);\n        trayOut.setAttribute(\"width\", size * 1.2);\n        trayOut.setAttribute(\"height\", size * 0.35);\n        trayOut.setAttribute(\"rx\", 2);\n        g.appendChild(trayOut);\n        const paper = document.createElementNS(ns, \"rect\");\n        paper.setAttribute(\"x\", -size * 0.5);\n        paper.setAttribute(\"y\", size * 0.3);\n        paper.setAttribute(\"width\", size * 1);\n        paper.setAttribute(\"height\", size * 0.5);\n        paper.style.fill = \"#e2e8f0\";\n        g.appendChild(paper);\n        return g;\n       }\n       if (shape === \"access-point\" || shape === \"wifi\") {\n        const g = document.createElementNS(ns, \"g\");\n        const base = document.createElementNS(ns, \"rect\");\n        base.setAttribute(\"x\", -size * 0.3);\n        base.setAttribute(\"y\", size * 0.2);\n        base.setAttribute(\"width\", size * 0.6);\n        base.setAttribute(\"height\", size * 0.3);\n        base.setAttribute(\"rx\", 3);\n        g.appendChild(base);\n        for (let i = 1; i <= 3; i++) {\n         const arc = document.createElementNS(ns, \"path\");\n         const r = size * 0.3 * i;\n         arc.setAttribute(\"d\", `M ${-r * 0.7} ${size * 0.1 - r * 0.5} A ${r} ${r} 0 0 1 ${r * 0.7} ${size * 0.1 - r * 0.5}`);\n         arc.style.fill = \"none\";\n         arc.style.stroke = \"currentColor\";\n         arc.style.strokeWidth = \"2\";\n         arc.style.opacity = 1 - (i * 0.2);\n         g.appendChild(arc);\n        }\n        return g;\n       }\n       if (shape === \"load-balancer\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size);\n        body.setAttribute(\"y\", -size * 0.5);\n        body.setAttribute(\"width\", size * 2);\n        body.setAttribute(\"height\", size);\n        body.setAttribute(\"rx\", 6);\n        g.appendChild(body);\n        const bar = document.createElementNS(ns, \"line\");\n        bar.setAttribute(\"x1\", -size * 0.6);\n        bar.setAttribute(\"y1\", 0);\n        bar.setAttribute(\"x2\", size * 0.6);\n        bar.setAttribute(\"y2\", 0);\n        bar.style.stroke = \"#4ade80\";\n        bar.style.strokeWidth = \"3\";\n        g.appendChild(bar);\n        [-1, 1].forEach(dir => {\n         const arrow = document.createElementNS(ns, \"path\");\n         arrow.setAttribute(\"d\", `M ${dir * size * 0.3} ${-size * 0.2} L ${dir * size * 0.6} 0 L ${dir * size * 0.3} ${size * 0.2}`);\n         arrow.style.fill = \"none\";\n         arrow.style.stroke = \"#4ade80\";\n         arrow.style.strokeWidth = \"2\";\n         g.appendChild(arrow);\n        });\n        return g;\n       }\n       if (shape === \"nas\" || shape === \"storage\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 0.8);\n        body.setAttribute(\"y\", -size * 0.9);\n        body.setAttribute(\"width\", size * 1.6);\n        body.setAttribute(\"height\", size * 1.8);\n        body.setAttribute(\"rx\", 4);\n        g.appendChild(body);\n        for (let i = 0; i < 4; i++) {\n         const bay = document.createElementNS(ns, \"rect\");\n         bay.setAttribute(\"x\", -size * 0.6);\n         bay.setAttribute(\"y\", -size * 0.7 + i * size * 0.4);\n         bay.setAttribute(\"width\", size * 1.2);\n         bay.setAttribute(\"height\", size * 0.3);\n         bay.setAttribute(\"rx\", 2);\n         bay.style.fill = \"#1e293b\";\n         g.appendChild(bay);\n        }\n        return g;\n       }\n       if (shape === \"gateway\" || shape === \"modem\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 0.5);\n        body.setAttribute(\"y\", -size * 0.9);\n        body.setAttribute(\"width\", size);\n        body.setAttribute(\"height\", size * 1.8);\n        body.setAttribute(\"rx\", 4);\n        g.appendChild(body);\n        for (let i = 0; i < 4; i++) {\n         const led = document.createElementNS(ns, \"circle\");\n         led.setAttribute(\"cx\", 0);\n         led.setAttribute(\"cy\", -size * 0.6 + i * size * 0.35);\n         led.setAttribute(\"r\", size * 0.08);\n         led.style.fill = [\"#4ade80\", \"#4ade80\", \"#facc15\", \"#60a5fa\"][i];\n         g.appendChild(led);\n        }\n        return g;\n       }\n       if (shape === \"vpn\" || shape === \"tunnel\") {\n        const g = document.createElementNS(ns, \"g\");\n        const outer = document.createElementNS(ns, \"ellipse\");\n        outer.setAttribute(\"cx\", 0);\n        outer.setAttribute(\"cy\", 0);\n        outer.setAttribute(\"rx\", size);\n        outer.setAttribute(\"ry\", size * 0.6);\n        g.appendChild(outer);\n        const inner = document.createElementNS(ns, \"ellipse\");\n        inner.setAttribute(\"cx\", 0);\n        inner.setAttribute(\"cy\", 0);\n        inner.setAttribute(\"rx\", size * 0.5);\n        inner.setAttribute(\"ry\", size * 0.3);\n        inner.style.fill = \"#1e293b\";\n        g.appendChild(inner);\n        const lock = document.createElementNS(ns, \"rect\");\n        lock.setAttribute(\"x\", -size * 0.15);\n        lock.setAttribute(\"y\", -size * 0.1);\n        lock.setAttribute(\"width\", size * 0.3);\n        lock.setAttribute(\"height\", size * 0.25);\n        lock.setAttribute(\"rx\", 2);\n        lock.style.fill = \"#4ade80\";\n        g.appendChild(lock);\n        return g;\n       }\n       if (shape === \"container\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size);\n        body.setAttribute(\"y\", -size * 0.5);\n        body.setAttribute(\"width\", size * 2);\n        body.setAttribute(\"height\", size);\n        body.setAttribute(\"rx\", 4);\n        g.appendChild(body);\n        for (let i = 0; i < 3; i++) {\n         const block = document.createElementNS(ns, \"rect\");\n         block.setAttribute(\"x\", -size * 0.8 + i * size * 0.55);\n         block.setAttribute(\"y\", -size * 0.3);\n         block.setAttribute(\"width\", size * 0.45);\n         block.setAttribute(\"height\", size * 0.6);\n         block.setAttribute(\"rx\", 2);\n         block.style.fill = \"#1e293b\";\n         g.appendChild(block);\n        }\n        return g;\n       }\n       if (shape === \"vm\" || shape === \"virtual\") {\n        const g = document.createElementNS(ns, \"g\");\n        const shadow = document.createElementNS(ns, \"rect\");\n        shadow.setAttribute(\"x\", -size * 0.85 + 4);\n        shadow.setAttribute(\"y\", -size * 0.65 + 4);\n        shadow.setAttribute(\"width\", size * 1.7);\n        shadow.setAttribute(\"height\", size * 1.3);\n        shadow.setAttribute(\"rx\", 4);\n        shadow.style.opacity = \"0.3\";\n        g.appendChild(shadow);\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 0.85);\n        body.setAttribute(\"y\", -size * 0.65);\n        body.setAttribute(\"width\", size * 1.7);\n        body.setAttribute(\"height\", size * 1.3);\n        body.setAttribute(\"rx\", 4);\n        g.appendChild(body);\n        const screen = document.createElementNS(ns, \"rect\");\n        screen.setAttribute(\"x\", -size * 0.65);\n        screen.setAttribute(\"y\", -size * 0.45);\n        screen.setAttribute(\"width\", size * 1.3);\n        screen.setAttribute(\"height\", size * 0.7);\n        screen.setAttribute(\"rx\", 2);\n        screen.style.fill = \"#1e293b\";\n        g.appendChild(screen);\n        return g;\n       }\n       if (shape === \"kubernetes\" || shape === \"k8s\") {\n        const g = document.createElementNS(ns, \"g\");\n        const outer = document.createElementNS(ns, \"circle\");\n        outer.setAttribute(\"cx\", 0);\n        outer.setAttribute(\"cy\", 0);\n        outer.setAttribute(\"r\", size);\n        g.appendChild(outer);\n        const inner = document.createElementNS(ns, \"circle\");\n        inner.setAttribute(\"cx\", 0);\n        inner.setAttribute(\"cy\", 0);\n        inner.setAttribute(\"r\", size * 0.4);\n        inner.style.fill = \"#1e293b\";\n        g.appendChild(inner);\n        for (let i = 0; i < 7; i++) {\n         const angle = (Math.PI * 2 / 7) * i - Math.PI / 2;\n         const spoke = document.createElementNS(ns, \"line\");\n         spoke.setAttribute(\"x1\", Math.cos(angle) * size * 0.4);\n         spoke.setAttribute(\"y1\", Math.sin(angle) * size * 0.4);\n         spoke.setAttribute(\"x2\", Math.cos(angle) * size * 0.85);\n         spoke.setAttribute(\"y2\", Math.sin(angle) * size * 0.85);\n         spoke.style.stroke = \"#326ce5\";\n         spoke.style.strokeWidth = \"3\";\n         g.appendChild(spoke);\n        }\n        return g;\n       }\n       if (shape === \"shield\" || shape === \"security\") {\n        const p = document.createElementNS(ns, \"path\");\n        const s = size;\n        p.setAttribute(\"d\", `M 0 ${-s} L ${s * 0.85} ${-s * 0.5} L ${s * 0.85} ${s * 0.2} Q ${s * 0.7} ${s * 0.9} 0 ${s} Q ${-s * 0.7} ${s * 0.9} ${-s * 0.85} ${s * 0.2} L ${-s * 0.85} ${-s * 0.5} Z`);\n        return p;\n       }\n       if (shape === \"camera\" || shape === \"cctv\") {\n        const g = document.createElementNS(ns, \"g\");\n        const mount = document.createElementNS(ns, \"rect\");\n        mount.setAttribute(\"x\", -size * 1.1);\n        mount.setAttribute(\"y\", -size * 0.5);\n        mount.setAttribute(\"width\", size * 0.25);\n        mount.setAttribute(\"height\", size * 0.6);\n        mount.setAttribute(\"rx\", 2);\n        g.appendChild(mount);\n        const arm = document.createElementNS(ns, \"rect\");\n        arm.setAttribute(\"x\", -size * 0.9);\n        arm.setAttribute(\"y\", -size * 0.15);\n        arm.setAttribute(\"width\", size * 0.5);\n        arm.setAttribute(\"height\", size * 0.15);\n        g.appendChild(arm);\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 0.45);\n        body.setAttribute(\"y\", -size * 0.35);\n        body.setAttribute(\"width\", size * 1.1);\n        body.setAttribute(\"height\", size * 0.7);\n        body.setAttribute(\"rx\", 4);\n        g.appendChild(body);\n        const lensHousing = document.createElementNS(ns, \"circle\");\n        lensHousing.setAttribute(\"cx\", size * 0.85);\n        lensHousing.setAttribute(\"cy\", 0);\n        lensHousing.setAttribute(\"r\", size * 0.4);\n        g.appendChild(lensHousing);\n        const lensOuter = document.createElementNS(ns, \"circle\");\n        lensOuter.setAttribute(\"cx\", size * 0.85);\n        lensOuter.setAttribute(\"cy\", 0);\n        lensOuter.setAttribute(\"r\", size * 0.28);\n        lensOuter.style.fill = \"#1e293b\";\n        g.appendChild(lensOuter);\n        const lensInner = document.createElementNS(ns, \"circle\");\n        lensInner.setAttribute(\"cx\", size * 0.85);\n        lensInner.setAttribute(\"cy\", 0);\n        lensInner.setAttribute(\"r\", size * 0.15);\n        lensInner.style.fill = \"#3b82f6\";\n        g.appendChild(lensInner);\n        const reflection = document.createElementNS(ns, \"circle\");\n        reflection.setAttribute(\"cx\", size * 0.8);\n        reflection.setAttribute(\"cy\", -size * 0.05);\n        reflection.setAttribute(\"r\", size * 0.05);\n        reflection.style.fill = \"#93c5fd\";\n        g.appendChild(reflection);\n        const led = document.createElementNS(ns, \"circle\");\n        led.setAttribute(\"cx\", -size * 0.2);\n        led.setAttribute(\"cy\", -size * 0.15);\n        led.setAttribute(\"r\", size * 0.06);\n        led.style.fill = \"#ef4444\";\n        g.appendChild(led);\n        return g;\n       }\n       if (shape === \"monitor\" || shape === \"dashboard\") {\n        const g = document.createElementNS(ns, \"g\");\n        const screen = document.createElementNS(ns, \"rect\");\n        screen.setAttribute(\"x\", -size);\n        screen.setAttribute(\"y\", -size * 0.7);\n        screen.setAttribute(\"width\", size * 2);\n        screen.setAttribute(\"height\", size * 1.2);\n        screen.setAttribute(\"rx\", 4);\n        g.appendChild(screen);\n        const graph = document.createElementNS(ns, \"polyline\");\n        graph.setAttribute(\"points\", `${-size * 0.8},${size * 0.2} ${-size * 0.3},${-size * 0.2} ${size * 0.2},${size * 0.1} ${size * 0.7},${-size * 0.3}`);\n        graph.style.fill = \"none\";\n        graph.style.stroke = \"#4ade80\";\n        graph.style.strokeWidth = \"3\";\n        g.appendChild(graph);\n        return g;\n       }\n       if (shape === \"docker\" || shape === \"whale\") {\n        const g = document.createElementNS(ns, \"g\");\n        const s = size;\n        const body = document.createElementNS(ns, \"path\");\n        body.setAttribute(\"d\", `\n         M ${-s * 0.9} ${s * 0.2}\n         Q ${-s * 1.1} ${s * 0.5} ${-s * 0.7} ${s * 0.7}\n         Q ${-s * 0.3} ${s * 0.85} ${s * 0.3} ${s * 0.75}\n         Q ${s * 0.8} ${s * 0.6} ${s * 1.0} ${s * 0.3}\n         Q ${s * 1.1} ${s * 0.1} ${s * 1.0} ${-s * 0.1}\n         L ${s * 0.85} ${-s * 0.1}\n         Q ${s * 0.7} ${-s * 0.15} ${s * 0.4} ${-s * 0.2}\n         L ${-s * 0.5} ${-s * 0.2}\n         Q ${-s * 0.8} ${-s * 0.1} ${-s * 0.9} ${s * 0.2}\n         Z\n        `);\n        body.style.fill = \"#0db7ed\";\n        g.appendChild(body);\n        const tail = document.createElementNS(ns, \"path\");\n        tail.setAttribute(\"d\", `\n         M ${-s * 0.85} ${s * 0.1}\n         Q ${-s * 1.2} ${-s * 0.2} ${-s * 1.0} ${-s * 0.55}\n         Q ${-s * 0.95} ${-s * 0.35} ${-s * 0.8} ${-s * 0.15}\n         Z\n        `);\n        tail.style.fill = \"#0db7ed\";\n        g.appendChild(tail);\n        const belly = document.createElementNS(ns, \"path\");\n        belly.setAttribute(\"d\", `\n         M ${-s * 0.5} ${s * 0.65}\n         Q ${s * 0.1} ${s * 0.75} ${s * 0.6} ${s * 0.55}\n         Q ${s * 0.8} ${s * 0.45} ${s * 0.9} ${s * 0.25}\n         Q ${s * 0.7} ${s * 0.5} ${s * 0.3} ${s * 0.6}\n         Q ${-s * 0.1} ${s * 0.7} ${-s * 0.5} ${s * 0.65}\n         Z\n        `);\n        belly.style.fill = \"#ffffff\";\n        belly.style.opacity = \"0.3\";\n        g.appendChild(belly);\n        for (let row = 0; row < 2; row++) {\n         for (let col = 0; col < 3; col++) {\n          const container = document.createElementNS(ns, \"rect\");\n          container.setAttribute(\"x\", -s * 0.45 + col * s * 0.35);\n          container.setAttribute(\"y\", -s * 0.7 + row * s * 0.28);\n          container.setAttribute(\"width\", s * 0.3);\n          container.setAttribute(\"height\", s * 0.23);\n          container.setAttribute(\"rx\", 2);\n          container.style.fill = \"#0db7ed\";\n          container.style.stroke = \"#0a9ed8\";\n          container.style.strokeWidth = \"1.5\";\n          g.appendChild(container);\n         }\n        }\n        const topContainer = document.createElementNS(ns, \"rect\");\n        topContainer.setAttribute(\"x\", -s * 0.1);\n        topContainer.setAttribute(\"y\", -s * 0.95);\n        topContainer.setAttribute(\"width\", s * 0.3);\n        topContainer.setAttribute(\"height\", s * 0.23);\n        topContainer.setAttribute(\"rx\", 2);\n        topContainer.style.fill = \"#0db7ed\";\n        topContainer.style.stroke = \"#0a9ed8\";\n        topContainer.style.strokeWidth = \"1.5\";\n        g.appendChild(topContainer);\n        const eye = document.createElementNS(ns, \"circle\");\n        eye.setAttribute(\"cx\", s * 0.65);\n        eye.setAttribute(\"cy\", s * 0.25);\n        eye.setAttribute(\"r\", s * 0.08);\n        eye.style.fill = \"#0a5f7a\";\n        g.appendChild(eye);\n        return g;\n       }\n       if (shape === \"rounded-square\") {\n        const r = document.createElementNS(ns, \"rect\");\n        const s = size * 1.6;\n        r.setAttribute(\"x\", -s / 2);\n        r.setAttribute(\"y\", -s / 2);\n        r.setAttribute(\"width\", s);\n        r.setAttribute(\"height\", s);\n        r.setAttribute(\"rx\", size * 0.4);\n        return r;\n       }\n       if (shape === \"pill\" || shape === \"capsule\") {\n        const r = document.createElementNS(ns, \"rect\");\n        r.setAttribute(\"x\", -size * 1.2);\n        r.setAttribute(\"y\", -size * 0.5);\n        r.setAttribute(\"width\", size * 2.4);\n        r.setAttribute(\"height\", size);\n        r.setAttribute(\"rx\", size * 0.5);\n        return r;\n       }\n       if (shape === \"octagon\") {\n        const p = document.createElementNS(ns, \"polygon\");\n        const r = size;\n        const pts = [];\n        for (let i = 0; i < 8; i++) {\n         const a = (Math.PI / 4) * i - Math.PI / 8;\n         pts.push([Math.cos(a) * r, Math.sin(a) * r]);\n        }\n        p.setAttribute(\"points\", pts.map(pt => pt.join(\",\")).join(\" \"));\n        return p;\n       }\n       if (shape === \"pentagon\") {\n        const p = document.createElementNS(ns, \"polygon\");\n        const pts = [];\n        for (let i = 0; i < 5; i++) {\n         const a = (Math.PI * 2 / 5) * i - Math.PI / 2;\n         pts.push([Math.cos(a) * size, Math.sin(a) * size]);\n        }\n        p.setAttribute(\"points\", pts.map(pt => pt.join(\",\")).join(\" \"));\n        return p;\n       }\n       if (shape === \"cross\" || shape === \"plus\") {\n        const p = document.createElementNS(ns, \"polygon\");\n        const s = size;\n        const t = size * 0.35;\n        p.setAttribute(\"points\", `${-t},${-s} ${t},${-s} ${t},${-t} ${s},${-t} ${s},${t} ${t},${t} ${t},${s} ${-t},${s} ${-t},${t} ${-s},${t} ${-s},${-t} ${-t},${-t}`);\n        return p;\n       }\n       if (shape === \"parallelogram\") {\n        const p = document.createElementNS(ns, \"polygon\");\n        const s = size;\n        p.setAttribute(\"points\", `${-s * 0.6},${-s * 0.6} ${s * 1.2},${-s * 0.6} ${s * 0.6},${s * 0.6} ${-s * 1.2},${s * 0.6}`);\n        return p;\n       }\n       if (shape === \"trapezoid\") {\n        const p = document.createElementNS(ns, \"polygon\");\n        const s = size;\n        p.setAttribute(\"points\", `${-s * 0.6},${-s * 0.6} ${s * 0.6},${-s * 0.6} ${s},${s * 0.6} ${-s},${s * 0.6}`);\n        return p;\n       }\n       if (shape === \"sensor\" || shape === \"iot\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"circle\");\n        body.setAttribute(\"cx\", 0);\n        body.setAttribute(\"cy\", 0);\n        body.setAttribute(\"r\", size * 0.7);\n        g.appendChild(body);\n        const ant = document.createElementNS(ns, \"line\");\n        ant.setAttribute(\"x1\", 0);\n        ant.setAttribute(\"y1\", -size * 0.7);\n        ant.setAttribute(\"x2\", 0);\n        ant.setAttribute(\"y2\", -size);\n        ant.style.stroke = \"currentColor\";\n        ant.style.strokeWidth = \"2\";\n        g.appendChild(ant);\n        const tip = document.createElementNS(ns, \"circle\");\n        tip.setAttribute(\"cx\", 0);\n        tip.setAttribute(\"cy\", -size);\n        tip.setAttribute(\"r\", size * 0.1);\n        tip.style.fill = \"#4ade80\";\n        g.appendChild(tip);\n        return g;\n       }\n       if (shape === \"pi\" || shape === \"sbc\" || shape === \"raspberry\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size);\n        body.setAttribute(\"y\", -size * 0.65);\n        body.setAttribute(\"width\", size * 2);\n        body.setAttribute(\"height\", size * 1.3);\n        body.setAttribute(\"rx\", 4);\n        g.appendChild(body);\n        for (let i = 0; i < 10; i++) {\n         const pin = document.createElementNS(ns, \"rect\");\n         pin.setAttribute(\"x\", -size * 0.85 + i * size * 0.19);\n         pin.setAttribute(\"y\", -size * 0.8);\n         pin.setAttribute(\"width\", size * 0.08);\n         pin.setAttribute(\"height\", size * 0.15);\n         pin.style.fill = \"#facc15\";\n         g.appendChild(pin);\n        }\n        const port = document.createElementNS(ns, \"rect\");\n        port.setAttribute(\"x\", size * 0.6);\n        port.setAttribute(\"y\", -size * 0.2);\n        port.setAttribute(\"width\", size * 0.35);\n        port.setAttribute(\"height\", size * 0.4);\n        port.style.fill = \"#1e293b\";\n        g.appendChild(port);\n        return g;\n       }\n       if (shape === \"api\" || shape === \"endpoint\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 0.9);\n        body.setAttribute(\"y\", -size * 0.7);\n        body.setAttribute(\"width\", size * 1.8);\n        body.setAttribute(\"height\", size * 1.4);\n        body.setAttribute(\"rx\", 6);\n        g.appendChild(body);\n        const left = document.createElementNS(ns, \"text\");\n        left.setAttribute(\"x\", -size * 0.5);\n        left.setAttribute(\"y\", size * 0.15);\n        left.setAttribute(\"font-size\", size * 0.9);\n        left.setAttribute(\"fill\", \"#4ade80\");\n        left.setAttribute(\"font-family\", \"monospace\");\n        left.textContent = \"{\";\n        g.appendChild(left);\n        const right = document.createElementNS(ns, \"text\");\n        right.setAttribute(\"x\", size * 0.15);\n        right.setAttribute(\"y\", size * 0.15);\n        right.setAttribute(\"font-size\", size * 0.9);\n        right.setAttribute(\"fill\", \"#4ade80\");\n        right.setAttribute(\"font-family\", \"monospace\");\n        right.textContent = \"}\";\n        g.appendChild(right);\n        return g;\n       }\n       if (shape === \"queue\" || shape === \"message\") {\n        const g = document.createElementNS(ns, \"g\");\n        for (let i = 2; i >= 0; i--) {\n         const card = document.createElementNS(ns, \"rect\");\n         card.setAttribute(\"x\", -size * 0.7 + i * 4);\n         card.setAttribute(\"y\", -size * 0.5 + i * 4);\n         card.setAttribute(\"width\", size * 1.4);\n         card.setAttribute(\"height\", size * 0.8);\n         card.setAttribute(\"rx\", 3);\n         card.style.opacity = 1 - i * 0.25;\n         g.appendChild(card);\n        }\n        return g;\n       }\n       if (shape === \"lambda\" || shape === \"function\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 0.8);\n        body.setAttribute(\"y\", -size * 0.8);\n        body.setAttribute(\"width\", size * 1.6);\n        body.setAttribute(\"height\", size * 1.6);\n        body.setAttribute(\"rx\", 4);\n        g.appendChild(body);\n        const lambda = document.createElementNS(ns, \"text\");\n        lambda.setAttribute(\"x\", 0);\n        lambda.setAttribute(\"y\", size * 0.2);\n        lambda.setAttribute(\"font-size\", size * 1.2);\n        lambda.setAttribute(\"fill\", \"#f59e0b\");\n        lambda.setAttribute(\"text-anchor\", \"middle\");\n        lambda.setAttribute(\"font-family\", \"serif\");\n        lambda.textContent = \"λ\";\n        g.appendChild(lambda);\n        return g;\n       }\n       if (shape === \"bucket\" || shape === \"s3\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"path\");\n        body.setAttribute(\"d\", `M ${-size * 0.8} ${-size * 0.7} L ${-size * 0.6} ${size * 0.8} Q ${-size * 0.5} ${size} 0 ${size} Q ${size * 0.5} ${size} ${size * 0.6} ${size * 0.8} L ${size * 0.8} ${-size * 0.7} Z`);\n        g.appendChild(body);\n        const rim = document.createElementNS(ns, \"ellipse\");\n        rim.setAttribute(\"cx\", 0);\n        rim.setAttribute(\"cy\", -size * 0.7);\n        rim.setAttribute(\"rx\", size * 0.8);\n        rim.setAttribute(\"ry\", size * 0.25);\n        g.appendChild(rim);\n        return g;\n       }\n       if (shape === \"thermostat\") {\n        const g = document.createElementNS(ns, \"g\");\n        const outer = document.createElementNS(ns, \"circle\");\n        outer.setAttribute(\"r\", size);\n        g.appendChild(outer);\n        const inner = document.createElementNS(ns, \"circle\");\n        inner.setAttribute(\"r\", size * 0.75);\n        inner.style.fill = \"#1e293b\";\n        g.appendChild(inner);\n        const temp = document.createElementNS(ns, \"text\");\n        temp.setAttribute(\"x\", 0);\n        temp.setAttribute(\"y\", size * 0.15);\n        temp.setAttribute(\"font-size\", size * 0.5);\n        temp.setAttribute(\"fill\", \"#4ade80\");\n        temp.setAttribute(\"text-anchor\", \"middle\");\n        temp.setAttribute(\"font-family\", \"monospace\");\n        temp.textContent = \"72°\";\n        g.appendChild(temp);\n        return g;\n       }\n       if (shape === \"doorbell\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 0.5);\n        body.setAttribute(\"y\", -size);\n        body.setAttribute(\"width\", size);\n        body.setAttribute(\"height\", size * 2);\n        body.setAttribute(\"rx\", size * 0.25);\n        g.appendChild(body);\n        const lens = document.createElementNS(ns, \"circle\");\n        lens.setAttribute(\"cx\", 0);\n        lens.setAttribute(\"cy\", -size * 0.35);\n        lens.setAttribute(\"r\", size * 0.3);\n        lens.style.fill = \"#1e293b\";\n        g.appendChild(lens);\n        const lensDot = document.createElementNS(ns, \"circle\");\n        lensDot.setAttribute(\"cx\", 0);\n        lensDot.setAttribute(\"cy\", -size * 0.35);\n        lensDot.setAttribute(\"r\", size * 0.12);\n        lensDot.style.fill = \"#3b82f6\";\n        g.appendChild(lensDot);\n        const btn = document.createElementNS(ns, \"circle\");\n        btn.setAttribute(\"cx\", 0);\n        btn.setAttribute(\"cy\", size * 0.5);\n        btn.setAttribute(\"r\", size * 0.2);\n        btn.style.fill = \"#f59e0b\";\n        g.appendChild(btn);\n        return g;\n       }\n       if (shape === \"smart-lock\") {\n        const g = document.createElementNS(ns, \"g\");\n        const shackle = document.createElementNS(ns, \"path\");\n        shackle.setAttribute(\"d\", `M ${-size * 0.4} ${-size * 0.1} L ${-size * 0.4} ${-size * 0.6} A ${size * 0.4} ${size * 0.4} 0 1 1 ${size * 0.4} ${-size * 0.6} L ${size * 0.4} ${-size * 0.1}`);\n        shackle.style.fill = \"none\";\n        shackle.style.strokeWidth = size * 0.2;\n        g.appendChild(shackle);\n        const lockBody = document.createElementNS(ns, \"rect\");\n        lockBody.setAttribute(\"x\", -size * 0.6);\n        lockBody.setAttribute(\"y\", -size * 0.15);\n        lockBody.setAttribute(\"width\", size * 1.2);\n        lockBody.setAttribute(\"height\", size * 1);\n        lockBody.setAttribute(\"rx\", 4);\n        g.appendChild(lockBody);\n        const keyhole = document.createElementNS(ns, \"circle\");\n        keyhole.setAttribute(\"cx\", 0);\n        keyhole.setAttribute(\"cy\", size * 0.3);\n        keyhole.setAttribute(\"r\", size * 0.15);\n        keyhole.style.fill = \"#4ade80\";\n        g.appendChild(keyhole);\n        return g;\n       }\n       if (shape === \"smart-bulb\") {\n        const g = document.createElementNS(ns, \"g\");\n        const bulb = document.createElementNS(ns, \"path\");\n        bulb.setAttribute(\"d\", `M ${-size * 0.5} ${size * 0.2} Q ${-size * 0.8} ${-size * 0.3} ${-size * 0.5} ${-size * 0.7} Q 0 ${-size * 1.1} ${size * 0.5} ${-size * 0.7} Q ${size * 0.8} ${-size * 0.3} ${size * 0.5} ${size * 0.2} Z`);\n        g.appendChild(bulb);\n        const base1 = document.createElementNS(ns, \"rect\");\n        base1.setAttribute(\"x\", -size * 0.35);\n        base1.setAttribute(\"y\", size * 0.2);\n        base1.setAttribute(\"width\", size * 0.7);\n        base1.setAttribute(\"height\", size * 0.15);\n        base1.style.fill = \"#94a3b8\";\n        g.appendChild(base1);\n        const base2 = document.createElementNS(ns, \"rect\");\n        base2.setAttribute(\"x\", -size * 0.3);\n        base2.setAttribute(\"y\", size * 0.35);\n        base2.setAttribute(\"width\", size * 0.6);\n        base2.setAttribute(\"height\", size * 0.15);\n        base2.style.fill = \"#64748b\";\n        g.appendChild(base2);\n        const base3 = document.createElementNS(ns, \"rect\");\n        base3.setAttribute(\"x\", -size * 0.25);\n        base3.setAttribute(\"y\", size * 0.5);\n        base3.setAttribute(\"width\", size * 0.5);\n        base3.setAttribute(\"height\", size * 0.2);\n        base3.setAttribute(\"rx\", 2);\n        base3.style.fill = \"#475569\";\n        g.appendChild(base3);\n        return g;\n       }\n       if (shape === \"smart-plug\") {\n        const g = document.createElementNS(ns, \"g\");\n        const plugBody = document.createElementNS(ns, \"rect\");\n        plugBody.setAttribute(\"x\", -size * 0.7);\n        plugBody.setAttribute(\"y\", -size * 0.6);\n        plugBody.setAttribute(\"width\", size * 1.4);\n        plugBody.setAttribute(\"height\", size * 1.2);\n        plugBody.setAttribute(\"rx\", 6);\n        g.appendChild(plugBody);\n        const hole1 = document.createElementNS(ns, \"rect\");\n        hole1.setAttribute(\"x\", -size * 0.35);\n        hole1.setAttribute(\"y\", -size * 0.3);\n        hole1.setAttribute(\"width\", size * 0.15);\n        hole1.setAttribute(\"height\", size * 0.4);\n        hole1.setAttribute(\"rx\", 2);\n        hole1.style.fill = \"#1e293b\";\n        g.appendChild(hole1);\n        const hole2 = document.createElementNS(ns, \"rect\");\n        hole2.setAttribute(\"x\", size * 0.2);\n        hole2.setAttribute(\"y\", -size * 0.3);\n        hole2.setAttribute(\"width\", size * 0.15);\n        hole2.setAttribute(\"height\", size * 0.4);\n        hole2.setAttribute(\"rx\", 2);\n        hole2.style.fill = \"#1e293b\";\n        g.appendChild(hole2);\n        const led = document.createElementNS(ns, \"circle\");\n        led.setAttribute(\"cx\", 0);\n        led.setAttribute(\"cy\", size * 0.35);\n        led.setAttribute(\"r\", size * 0.1);\n        led.style.fill = \"#4ade80\";\n        g.appendChild(led);\n        return g;\n       }\n       if (shape === \"smart-speaker\") {\n        const g = document.createElementNS(ns, \"g\");\n        const speakerBody = document.createElementNS(ns, \"rect\");\n        speakerBody.setAttribute(\"x\", -size * 0.6);\n        speakerBody.setAttribute(\"y\", -size);\n        speakerBody.setAttribute(\"width\", size * 1.2);\n        speakerBody.setAttribute(\"height\", size * 2);\n        speakerBody.setAttribute(\"rx\", size * 0.3);\n        g.appendChild(speakerBody);\n        const mesh = document.createElementNS(ns, \"rect\");\n        mesh.setAttribute(\"x\", -size * 0.5);\n        mesh.setAttribute(\"y\", -size * 0.3);\n        mesh.setAttribute(\"width\", size);\n        mesh.setAttribute(\"height\", size * 1.1);\n        mesh.setAttribute(\"rx\", 4);\n        mesh.style.fill = \"#1e293b\";\n        g.appendChild(mesh);\n        const ring = document.createElementNS(ns, \"circle\");\n        ring.setAttribute(\"cx\", 0);\n        ring.setAttribute(\"cy\", -size * 0.65);\n        ring.setAttribute(\"r\", size * 0.2);\n        ring.style.fill = \"#3b82f6\";\n        g.appendChild(ring);\n        return g;\n       }\n       if (shape === \"smart-tv\") {\n        const g = document.createElementNS(ns, \"g\");\n        const frame = document.createElementNS(ns, \"rect\");\n        frame.setAttribute(\"x\", -size * 1.4);\n        frame.setAttribute(\"y\", -size * 0.85);\n        frame.setAttribute(\"width\", size * 2.8);\n        frame.setAttribute(\"height\", size * 1.6);\n        frame.setAttribute(\"rx\", 4);\n        g.appendChild(frame);\n        const screen = document.createElementNS(ns, \"rect\");\n        screen.setAttribute(\"x\", -size * 1.3);\n        screen.setAttribute(\"y\", -size * 0.75);\n        screen.setAttribute(\"width\", size * 2.6);\n        screen.setAttribute(\"height\", size * 1.4);\n        screen.setAttribute(\"rx\", 2);\n        screen.style.fill = \"#1e293b\";\n        g.appendChild(screen);\n        const stand = document.createElementNS(ns, \"rect\");\n        stand.setAttribute(\"x\", -size * 0.8);\n        stand.setAttribute(\"y\", size * 0.75);\n        stand.setAttribute(\"width\", size * 1.6);\n        stand.setAttribute(\"height\", size * 0.12);\n        stand.setAttribute(\"rx\", 2);\n        g.appendChild(stand);\n        return g;\n       }\n       if (shape === \"hub\") {\n        const g = document.createElementNS(ns, \"g\");\n        const hubBody = document.createElementNS(ns, \"rect\");\n        hubBody.setAttribute(\"x\", -size * 0.9);\n        hubBody.setAttribute(\"y\", -size * 0.5);\n        hubBody.setAttribute(\"width\", size * 1.8);\n        hubBody.setAttribute(\"height\", size);\n        hubBody.setAttribute(\"rx\", 8);\n        g.appendChild(hubBody);\n        for (let i = 0; i < 4; i++) {\n         const led = document.createElementNS(ns, \"circle\");\n         led.setAttribute(\"cx\", -size * 0.5 + i * size * 0.35);\n         led.setAttribute(\"cy\", 0);\n         led.setAttribute(\"r\", size * 0.08);\n         led.style.fill = [\"#4ade80\", \"#3b82f6\", \"#f59e0b\", \"#ef4444\"][i];\n         g.appendChild(led);\n        }\n        return g;\n       }\n       if (shape === \"smoke-detector\") {\n        const g = document.createElementNS(ns, \"g\");\n        const outer = document.createElementNS(ns, \"circle\");\n        outer.setAttribute(\"r\", size);\n        g.appendChild(outer);\n        const inner = document.createElementNS(ns, \"circle\");\n        inner.setAttribute(\"r\", size * 0.6);\n        inner.style.fill = \"#1e293b\";\n        g.appendChild(inner);\n        for (let i = 0; i < 6; i++) {\n         const slot = document.createElementNS(ns, \"rect\");\n         const angle = (i * 60 - 90) * Math.PI / 180;\n         slot.setAttribute(\"x\", Math.cos(angle) * size * 0.35 - size * 0.08);\n         slot.setAttribute(\"y\", Math.sin(angle) * size * 0.35 - size * 0.03);\n         slot.setAttribute(\"width\", size * 0.16);\n         slot.setAttribute(\"height\", size * 0.06);\n         slot.setAttribute(\"rx\", 1);\n         slot.style.fill = \"#475569\";\n         slot.setAttribute(\"transform\", `rotate(${i * 60}, ${Math.cos(angle) * size * 0.35}, ${Math.sin(angle) * size * 0.35})`);\n         g.appendChild(slot);\n        }\n        const led = document.createElementNS(ns, \"circle\");\n        led.setAttribute(\"cx\", 0);\n        led.setAttribute(\"cy\", 0);\n        led.setAttribute(\"r\", size * 0.1);\n        led.style.fill = \"#ef4444\";\n        g.appendChild(led);\n        return g;\n       }\n       if (shape === \"motion-sensor\") {\n        const g = document.createElementNS(ns, \"g\");\n        const dome = document.createElementNS(ns, \"path\");\n        dome.setAttribute(\"d\", `M ${-size * 0.8} ${size * 0.3} Q ${-size * 0.8} ${-size * 0.8} 0 ${-size * 0.8} Q ${size * 0.8} ${-size * 0.8} ${size * 0.8} ${size * 0.3} Z`);\n        g.appendChild(dome);\n        const base = document.createElementNS(ns, \"rect\");\n        base.setAttribute(\"x\", -size * 0.8);\n        base.setAttribute(\"y\", size * 0.3);\n        base.setAttribute(\"width\", size * 1.6);\n        base.setAttribute(\"height\", size * 0.35);\n        base.setAttribute(\"rx\", 3);\n        g.appendChild(base);\n        const lens = document.createElementNS(ns, \"ellipse\");\n        lens.setAttribute(\"cx\", 0);\n        lens.setAttribute(\"cy\", -size * 0.15);\n        lens.setAttribute(\"rx\", size * 0.35);\n        lens.setAttribute(\"ry\", size * 0.25);\n        lens.style.fill = \"#1e293b\";\n        g.appendChild(lens);\n        const led = document.createElementNS(ns, \"circle\");\n        led.setAttribute(\"cx\", 0);\n        led.setAttribute(\"cy\", size * 0.45);\n        led.setAttribute(\"r\", size * 0.08);\n        led.style.fill = \"#4ade80\";\n        g.appendChild(led);\n        return g;\n       }\n       if (shape === \"garage\") {\n        const g = document.createElementNS(ns, \"g\");\n        const frame = document.createElementNS(ns, \"rect\");\n        frame.setAttribute(\"x\", -size);\n        frame.setAttribute(\"y\", -size * 0.9);\n        frame.setAttribute(\"width\", size * 2);\n        frame.setAttribute(\"height\", size * 1.8);\n        frame.setAttribute(\"rx\", 4);\n        g.appendChild(frame);\n        for (let i = 0; i < 4; i++) {\n         const panel = document.createElementNS(ns, \"rect\");\n         panel.setAttribute(\"x\", -size * 0.9);\n         panel.setAttribute(\"y\", -size * 0.8 + i * size * 0.42);\n         panel.setAttribute(\"width\", size * 1.8);\n         panel.setAttribute(\"height\", size * 0.35);\n         panel.setAttribute(\"rx\", 2);\n         panel.style.fill = \"#1e293b\";\n         g.appendChild(panel);\n        }\n        return g;\n       }\n       if (shape === \"sprinkler\") {\n        const g = document.createElementNS(ns, \"g\");\n        const head = document.createElementNS(ns, \"circle\");\n        head.setAttribute(\"r\", size * 0.5);\n        g.appendChild(head);\n        const nozzle = document.createElementNS(ns, \"rect\");\n        nozzle.setAttribute(\"x\", -size * 0.15);\n        nozzle.setAttribute(\"y\", size * 0.3);\n        nozzle.setAttribute(\"width\", size * 0.3);\n        nozzle.setAttribute(\"height\", size * 0.5);\n        g.appendChild(nozzle);\n        for (let i = 0; i < 5; i++) {\n         const spray = document.createElementNS(ns, \"line\");\n         const angle = (-60 + i * 30) * Math.PI / 180;\n         spray.setAttribute(\"x1\", 0);\n         spray.setAttribute(\"y1\", -size * 0.3);\n         spray.setAttribute(\"x2\", Math.cos(angle) * size * 0.8);\n         spray.setAttribute(\"y2\", Math.sin(angle) * size * 0.8 - size * 0.3);\n         spray.style.stroke = \"#3b82f6\";\n         spray.style.strokeWidth = \"2\";\n         spray.style.strokeDasharray = \"3,3\";\n         g.appendChild(spray);\n        }\n        return g;\n       }\n       if (shape === \"vacuum\") {\n        const g = document.createElementNS(ns, \"g\");\n        const vacBody = document.createElementNS(ns, \"circle\");\n        vacBody.setAttribute(\"r\", size);\n        g.appendChild(vacBody);\n        const top = document.createElementNS(ns, \"circle\");\n        top.setAttribute(\"r\", size * 0.7);\n        top.style.fill = \"#1e293b\";\n        g.appendChild(top);\n        const bumper = document.createElementNS(ns, \"path\");\n        bumper.setAttribute(\"d\", `M ${-size * 0.7} ${-size * 0.4} A ${size * 0.8} ${size * 0.8} 0 0 1 ${size * 0.7} ${-size * 0.4}`);\n        bumper.style.fill = \"none\";\n        bumper.style.strokeWidth = size * 0.15;\n        g.appendChild(bumper);\n        const btn = document.createElementNS(ns, \"circle\");\n        btn.setAttribute(\"cx\", 0);\n        btn.setAttribute(\"cy\", size * 0.1);\n        btn.setAttribute(\"r\", size * 0.2);\n        btn.style.fill = \"#4ade80\";\n        g.appendChild(btn);\n        return g;\n       }\n       if (shape === \"basketball-ball\") {\n        const g = document.createElementNS(ns, \"g\");\n        const ball = document.createElementNS(ns, \"circle\");\n        ball.setAttribute(\"r\", size);\n        g.appendChild(ball);\n        const hLine = document.createElementNS(ns, \"line\");\n        hLine.setAttribute(\"x1\", -size);\n        hLine.setAttribute(\"y1\", 0);\n        hLine.setAttribute(\"x2\", size);\n        hLine.setAttribute(\"y2\", 0);\n        hLine.style.stroke = \"currentColor\";\n        hLine.style.strokeWidth = \"2\";\n        hLine.style.opacity = \"0.5\";\n        g.appendChild(hLine);\n        const vLine = document.createElementNS(ns, \"line\");\n        vLine.setAttribute(\"x1\", 0);\n        vLine.setAttribute(\"y1\", -size);\n        vLine.setAttribute(\"x2\", 0);\n        vLine.setAttribute(\"y2\", size);\n        vLine.style.stroke = \"currentColor\";\n        vLine.style.strokeWidth = \"2\";\n        vLine.style.opacity = \"0.5\";\n        g.appendChild(vLine);\n        const curve1 = document.createElementNS(ns, \"path\");\n        curve1.setAttribute(\"d\", `M ${-size * 0.5} ${-size} Q ${-size * 0.8} 0 ${-size * 0.5} ${size}`);\n        curve1.style.fill = \"none\";\n        curve1.style.stroke = \"currentColor\";\n        curve1.style.strokeWidth = \"2\";\n        curve1.style.opacity = \"0.5\";\n        g.appendChild(curve1);\n        const curve2 = document.createElementNS(ns, \"path\");\n        curve2.setAttribute(\"d\", `M ${size * 0.5} ${-size} Q ${size * 0.8} 0 ${size * 0.5} ${size}`);\n        curve2.style.fill = \"none\";\n        curve2.style.stroke = \"currentColor\";\n        curve2.style.strokeWidth = \"2\";\n        curve2.style.opacity = \"0.5\";\n        g.appendChild(curve2);\n        return g;\n       }\n       if (shape === \"football-ball\") {\n        const g = document.createElementNS(ns, \"g\");\n        const ball = document.createElementNS(ns, \"ellipse\");\n        ball.setAttribute(\"rx\", size * 1.3);\n        ball.setAttribute(\"ry\", size * 0.7);\n        g.appendChild(ball);\n        const laces = document.createElementNS(ns, \"line\");\n        laces.setAttribute(\"x1\", -size * 0.4);\n        laces.setAttribute(\"y1\", 0);\n        laces.setAttribute(\"x2\", size * 0.4);\n        laces.setAttribute(\"y2\", 0);\n        laces.style.stroke = \"currentColor\";\n        laces.style.strokeWidth = \"3\";\n        laces.style.opacity = \"0.5\";\n        g.appendChild(laces);\n        for (let i = -2; i <= 2; i++) {\n         const stitch = document.createElementNS(ns, \"line\");\n         stitch.setAttribute(\"x1\", i * size * 0.15);\n         stitch.setAttribute(\"y1\", -size * 0.15);\n         stitch.setAttribute(\"x2\", i * size * 0.15);\n         stitch.setAttribute(\"y2\", size * 0.15);\n         stitch.style.stroke = \"currentColor\";\n         stitch.style.strokeWidth = \"2\";\n         stitch.style.opacity = \"0.5\";\n         g.appendChild(stitch);\n        }\n        return g;\n       }\n       if (shape === \"soccer-ball\") {\n        const g = document.createElementNS(ns, \"g\");\n        const ball = document.createElementNS(ns, \"circle\");\n        ball.setAttribute(\"r\", size);\n        g.appendChild(ball);\n        const pentagon = document.createElementNS(ns, \"polygon\");\n        const pts = [];\n        for (let i = 0; i < 5; i++) {\n         const a = (Math.PI * 2 / 5) * i - Math.PI / 2;\n         pts.push([Math.cos(a) * size * 0.4, Math.sin(a) * size * 0.4]);\n        }\n        pentagon.setAttribute(\"points\", pts.map(pt => pt.join(\",\")).join(\" \"));\n        pentagon.style.fill = \"currentColor\";\n        pentagon.style.opacity = \"0.5\";\n        g.appendChild(pentagon);\n        return g;\n       }\n       if (shape === \"hockey-puck\") {\n        const g = document.createElementNS(ns, \"g\");\n        const side = document.createElementNS(ns, \"ellipse\");\n        side.setAttribute(\"rx\", size);\n        side.setAttribute(\"ry\", size * 0.3);\n        side.setAttribute(\"cy\", size * 0.15);\n        side.style.opacity = \"0.7\";\n        g.appendChild(side);\n        const top = document.createElementNS(ns, \"ellipse\");\n        top.setAttribute(\"rx\", size);\n        top.setAttribute(\"ry\", size * 0.3);\n        top.setAttribute(\"cy\", -size * 0.15);\n        g.appendChild(top);\n        return g;\n       }\n       if (shape === \"baseball-ball\") {\n        const g = document.createElementNS(ns, \"g\");\n        const ball = document.createElementNS(ns, \"circle\");\n        ball.setAttribute(\"r\", size);\n        g.appendChild(ball);\n        const stitch1 = document.createElementNS(ns, \"path\");\n        stitch1.setAttribute(\"d\", `M ${-size * 0.7} ${-size * 0.5} Q ${-size * 0.3} ${-size * 0.8} 0 ${-size * 0.9} Q ${size * 0.3} ${-size * 0.8} ${size * 0.7} ${-size * 0.5}`);\n        stitch1.style.fill = \"none\";\n        stitch1.style.stroke = \"currentColor\";\n        stitch1.style.strokeWidth = \"2\";\n        stitch1.style.opacity = \"0.5\";\n        g.appendChild(stitch1);\n        const stitch2 = document.createElementNS(ns, \"path\");\n        stitch2.setAttribute(\"d\", `M ${-size * 0.7} ${size * 0.5} Q ${-size * 0.3} ${size * 0.8} 0 ${size * 0.9} Q ${size * 0.3} ${size * 0.8} ${size * 0.7} ${size * 0.5}`);\n        stitch2.style.fill = \"none\";\n        stitch2.style.stroke = \"currentColor\";\n        stitch2.style.strokeWidth = \"2\";\n        stitch2.style.opacity = \"0.5\";\n        g.appendChild(stitch2);\n        return g;\n       }\n       if (shape === \"tennis-ball\") {\n        const g = document.createElementNS(ns, \"g\");\n        const ball = document.createElementNS(ns, \"circle\");\n        ball.setAttribute(\"r\", size);\n        g.appendChild(ball);\n        const curve1 = document.createElementNS(ns, \"path\");\n        curve1.setAttribute(\"d\", `M ${-size * 0.2} ${-size} Q ${-size * 0.8} 0 ${-size * 0.2} ${size}`);\n        curve1.style.fill = \"none\";\n        curve1.style.stroke = \"currentColor\";\n        curve1.style.strokeWidth = \"3\";\n        curve1.style.opacity = \"0.5\";\n        g.appendChild(curve1);\n        const curve2 = document.createElementNS(ns, \"path\");\n        curve2.setAttribute(\"d\", `M ${size * 0.2} ${-size} Q ${size * 0.8} 0 ${size * 0.2} ${size}`);\n        curve2.style.fill = \"none\";\n        curve2.style.stroke = \"currentColor\";\n        curve2.style.strokeWidth = \"3\";\n        curve2.style.opacity = \"0.5\";\n        g.appendChild(curve2);\n        return g;\n       }\n       if (shape === \"volleyball\") {\n        const g = document.createElementNS(ns, \"g\");\n        const ball = document.createElementNS(ns, \"circle\");\n        ball.setAttribute(\"r\", size);\n        g.appendChild(ball);\n        for (let i = 0; i < 3; i++) {\n         const curve = document.createElementNS(ns, \"path\");\n         const angle = (Math.PI * 2 / 3) * i;\n         const x1 = Math.cos(angle) * size;\n         const y1 = Math.sin(angle) * size;\n         const x2 = Math.cos(angle + Math.PI) * size;\n         const y2 = Math.sin(angle + Math.PI) * size;\n         curve.setAttribute(\"d\", `M ${x1} ${y1} Q 0 0 ${x2} ${y2}`);\n         curve.style.fill = \"none\";\n         curve.style.stroke = \"currentColor\";\n         curve.style.strokeWidth = \"2\";\n         curve.style.opacity = \"0.5\";\n         g.appendChild(curve);\n        }\n        return g;\n       }\n       if (shape === \"rugby-ball\") {\n        const g = document.createElementNS(ns, \"g\");\n        const ball = document.createElementNS(ns, \"ellipse\");\n        ball.setAttribute(\"rx\", size * 1.4);\n        ball.setAttribute(\"ry\", size * 0.6);\n        g.appendChild(ball);\n        const stripe = document.createElementNS(ns, \"line\");\n        stripe.setAttribute(\"x1\", 0);\n        stripe.setAttribute(\"y1\", -size * 0.6);\n        stripe.setAttribute(\"x2\", 0);\n        stripe.setAttribute(\"y2\", size * 0.6);\n        stripe.style.stroke = \"currentColor\";\n        stripe.style.strokeWidth = \"3\";\n        stripe.style.opacity = \"0.5\";\n        g.appendChild(stripe);\n        return g;\n       }\n       if (shape === \"golf-ball\") {\n        const g = document.createElementNS(ns, \"g\");\n        const ball = document.createElementNS(ns, \"circle\");\n        ball.setAttribute(\"r\", size);\n        g.appendChild(ball);\n        for (let row = -2; row <= 2; row++) {\n         for (let col = -2; col <= 2; col++) {\n          if (Math.abs(row) + Math.abs(col) <= 2) {\n           const dimple = document.createElementNS(ns, \"circle\");\n           dimple.setAttribute(\"cx\", col * size * 0.3);\n           dimple.setAttribute(\"cy\", row * size * 0.3);\n           dimple.setAttribute(\"r\", size * 0.08);\n           dimple.style.fill = \"currentColor\";\n           dimple.style.opacity = \"0.3\";\n           g.appendChild(dimple);\n          }\n         }\n        }\n        return g;\n       }\n       if (shape === \"frisbee\") {\n        const g = document.createElementNS(ns, \"g\");\n        const outer = document.createElementNS(ns, \"ellipse\");\n        outer.setAttribute(\"rx\", size * 1.2);\n        outer.setAttribute(\"ry\", size * 0.4);\n        g.appendChild(outer);\n        const inner = document.createElementNS(ns, \"ellipse\");\n        inner.setAttribute(\"rx\", size * 0.7);\n        inner.setAttribute(\"ry\", size * 0.25);\n        inner.style.fill = \"currentColor\";\n        inner.style.opacity = \"0.5\";\n        g.appendChild(inner);\n        return g;\n       }\n       if (shape === \"cricket-ball\") {\n        const g = document.createElementNS(ns, \"g\");\n        const ball = document.createElementNS(ns, \"circle\");\n        ball.setAttribute(\"r\", size);\n        g.appendChild(ball);\n        const seam = document.createElementNS(ns, \"ellipse\");\n        seam.setAttribute(\"rx\", size * 0.9);\n        seam.setAttribute(\"ry\", size * 0.15);\n        seam.style.fill = \"none\";\n        seam.style.stroke = \"currentColor\";\n        seam.style.strokeWidth = \"3\";\n        seam.style.opacity = \"0.5\";\n        g.appendChild(seam);\n        return g;\n       }\n       if (shape === \"lacrosse-stick\") {\n        const g = document.createElementNS(ns, \"g\");\n        const handle = document.createElementNS(ns, \"rect\");\n        handle.setAttribute(\"x\", -size * 0.1);\n        handle.setAttribute(\"y\", -size * 0.2);\n        handle.setAttribute(\"width\", size * 0.2);\n        handle.setAttribute(\"height\", size * 1.5);\n        handle.setAttribute(\"rx\", 2);\n        g.appendChild(handle);\n        const head = document.createElementNS(ns, \"ellipse\");\n        head.setAttribute(\"cy\", -size * 0.6);\n        head.setAttribute(\"rx\", size * 0.5);\n        head.setAttribute(\"ry\", size * 0.7);\n        head.style.fill = \"none\";\n        head.style.stroke = \"currentColor\";\n        head.style.strokeWidth = size * 0.15;\n        head.style.opacity = \"0.7\";\n        g.appendChild(head);\n        const net = document.createElementNS(ns, \"ellipse\");\n        net.setAttribute(\"cy\", -size * 0.5);\n        net.setAttribute(\"rx\", size * 0.35);\n        net.setAttribute(\"ry\", size * 0.5);\n        net.style.fill = \"currentColor\";\n        net.style.opacity = \"0.3\";\n        g.appendChild(net);\n        return g;\n       }\n       if (shape === \"golf-flag\") {\n        const g = document.createElementNS(ns, \"g\");\n        const hole = document.createElementNS(ns, \"ellipse\");\n        hole.setAttribute(\"cy\", size * 0.7);\n        hole.setAttribute(\"rx\", size * 0.5);\n        hole.setAttribute(\"ry\", size * 0.15);\n        hole.style.opacity = \"0.5\";\n        g.appendChild(hole);\n        const pole = document.createElementNS(ns, \"line\");\n        pole.setAttribute(\"x1\", 0);\n        pole.setAttribute(\"y1\", size * 0.7);\n        pole.setAttribute(\"x2\", 0);\n        pole.setAttribute(\"y2\", -size);\n        pole.style.stroke = \"currentColor\";\n        pole.style.strokeWidth = \"3\";\n        pole.style.opacity = \"0.6\";\n        g.appendChild(pole);\n        const flag = document.createElementNS(ns, \"polygon\");\n        flag.setAttribute(\"points\", `0,${-size} ${size * 0.8},${-size * 0.7} 0,${-size * 0.4}`);\n        g.appendChild(flag);\n        return g;\n       }\n       if (shape === \"tactical-x\") {\n        const g = document.createElementNS(ns, \"g\");\n        const line1 = document.createElementNS(ns, \"line\");\n        line1.setAttribute(\"x1\", -size * 0.7);\n        line1.setAttribute(\"y1\", -size * 0.7);\n        line1.setAttribute(\"x2\", size * 0.7);\n        line1.setAttribute(\"y2\", size * 0.7);\n        line1.style.stroke = \"currentColor\";\n        line1.style.strokeWidth = size * 0.3;\n        line1.style.strokeLinecap = \"round\";\n        g.appendChild(line1);\n        const line2 = document.createElementNS(ns, \"line\");\n        line2.setAttribute(\"x1\", size * 0.7);\n        line2.setAttribute(\"y1\", -size * 0.7);\n        line2.setAttribute(\"x2\", -size * 0.7);\n        line2.setAttribute(\"y2\", size * 0.7);\n        line2.style.stroke = \"currentColor\";\n        line2.style.strokeWidth = size * 0.3;\n        line2.style.strokeLinecap = \"round\";\n        g.appendChild(line2);\n        return g;\n       }\n       if (shape === \"tactical-o\") {\n        const g = document.createElementNS(ns, \"g\");\n        const circle = document.createElementNS(ns, \"circle\");\n        circle.setAttribute(\"r\", size * 0.7);\n        circle.style.fill = \"none\";\n        circle.style.stroke = \"currentColor\";\n        circle.style.strokeWidth = size * 0.3;\n        g.appendChild(circle);\n        return g;\n       }\n       if (shape === \"tactical-star\") {\n        const g = document.createElementNS(ns, \"g\");\n        const outer = size * 0.9;\n        const inner = size * 0.4;\n        let pts = \"\";\n        for (let i = 0; i < 10; i++) {\n         const a = (Math.PI / 5) * i - Math.PI / 2;\n         const r = i % 2 === 0 ? outer : inner;\n         pts += `${Math.cos(a) * r},${Math.sin(a) * r} `;\n        }\n        const star = document.createElementNS(ns, \"polygon\");\n        star.setAttribute(\"points\", pts.trim());\n        g.appendChild(star);\n        return g;\n       }\n       if (shape === \"patch-panel\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 1.4);\n        body.setAttribute(\"y\", -size * 0.35);\n        body.setAttribute(\"width\", size * 2.8);\n        body.setAttribute(\"height\", size * 0.7);\n        body.setAttribute(\"rx\", 2);\n        g.appendChild(body);\n        for (let i = 0; i < 12; i++) {\n         const port = document.createElementNS(ns, \"rect\");\n         port.setAttribute(\"x\", -size * 1.25 + i * size * 0.22);\n         port.setAttribute(\"y\", -size * 0.15);\n         port.setAttribute(\"width\", size * 0.18);\n         port.setAttribute(\"height\", size * 0.3);\n         port.setAttribute(\"rx\", 1);\n         port.style.fill = \"currentColor\";\n         port.style.opacity = \"0.4\";\n         g.appendChild(port);\n        }\n        return g;\n       }\n       if (shape === \"ups\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size);\n        body.setAttribute(\"y\", -size);\n        body.setAttribute(\"width\", size * 2);\n        body.setAttribute(\"height\", size * 2);\n        body.setAttribute(\"rx\", 4);\n        g.appendChild(body);\n        const screen = document.createElementNS(ns, \"rect\");\n        screen.setAttribute(\"x\", -size * 0.6);\n        screen.setAttribute(\"y\", -size * 0.7);\n        screen.setAttribute(\"width\", size * 1.2);\n        screen.setAttribute(\"height\", size * 0.5);\n        screen.setAttribute(\"rx\", 2);\n        screen.style.fill = \"currentColor\";\n        screen.style.opacity = \"0.4\";\n        g.appendChild(screen);\n        const battery = document.createElementNS(ns, \"rect\");\n        battery.setAttribute(\"x\", -size * 0.4);\n        battery.setAttribute(\"y\", -size * 0.55);\n        battery.setAttribute(\"width\", size * 0.8);\n        battery.setAttribute(\"height\", size * 0.2);\n        battery.style.fill = \"#4ade80\";\n        g.appendChild(battery);\n        for (let i = 0; i < 3; i++) {\n         const led = document.createElementNS(ns, \"circle\");\n         led.setAttribute(\"cx\", -size * 0.4 + i * size * 0.4);\n         led.setAttribute(\"cy\", size * 0.2);\n         led.setAttribute(\"r\", size * 0.08);\n         led.style.fill = i < 2 ? \"#4ade80\" : \"#facc15\";\n         g.appendChild(led);\n        }\n        const outlet = document.createElementNS(ns, \"rect\");\n        outlet.setAttribute(\"x\", -size * 0.7);\n        outlet.setAttribute(\"y\", size * 0.5);\n        outlet.setAttribute(\"width\", size * 1.4);\n        outlet.setAttribute(\"height\", size * 0.3);\n        outlet.setAttribute(\"rx\", 2);\n        outlet.style.fill = \"currentColor\";\n        outlet.style.opacity = \"0.4\";\n        g.appendChild(outlet);\n        return g;\n       }\n       if (shape === \"pdu\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 0.4);\n        body.setAttribute(\"y\", -size * 1.2);\n        body.setAttribute(\"width\", size * 0.8);\n        body.setAttribute(\"height\", size * 2.4);\n        body.setAttribute(\"rx\", 3);\n        g.appendChild(body);\n        for (let i = 0; i < 6; i++) {\n         const outlet = document.createElementNS(ns, \"rect\");\n         outlet.setAttribute(\"x\", -size * 0.25);\n         outlet.setAttribute(\"y\", -size + i * size * 0.35);\n         outlet.setAttribute(\"width\", size * 0.5);\n         outlet.setAttribute(\"height\", size * 0.25);\n         outlet.setAttribute(\"rx\", 2);\n         outlet.style.fill = \"currentColor\";\n         outlet.style.opacity = \"0.4\";\n         g.appendChild(outlet);\n        }\n        const led = document.createElementNS(ns, \"circle\");\n        led.setAttribute(\"cy\", -size * 1.05);\n        led.setAttribute(\"r\", size * 0.08);\n        led.style.fill = \"#4ade80\";\n        g.appendChild(led);\n        return g;\n       }\n       if (shape === \"rack-shelf\") {\n        const g = document.createElementNS(ns, \"g\");\n        const shelf = document.createElementNS(ns, \"rect\");\n        shelf.setAttribute(\"x\", -size * 1.3);\n        shelf.setAttribute(\"y\", -size * 0.15);\n        shelf.setAttribute(\"width\", size * 2.6);\n        shelf.setAttribute(\"height\", size * 0.3);\n        shelf.setAttribute(\"rx\", 2);\n        g.appendChild(shelf);\n        const lip = document.createElementNS(ns, \"rect\");\n        lip.setAttribute(\"x\", -size * 1.3);\n        lip.setAttribute(\"y\", size * 0.15);\n        lip.setAttribute(\"width\", size * 2.6);\n        lip.setAttribute(\"height\", size * 0.15);\n        lip.style.opacity = \"0.7\";\n        g.appendChild(lip);\n        const ear1 = document.createElementNS(ns, \"rect\");\n        ear1.setAttribute(\"x\", -size * 1.4);\n        ear1.setAttribute(\"y\", -size * 0.2);\n        ear1.setAttribute(\"width\", size * 0.1);\n        ear1.setAttribute(\"height\", size * 0.4);\n        g.appendChild(ear1);\n        const ear2 = document.createElementNS(ns, \"rect\");\n        ear2.setAttribute(\"x\", size * 1.3);\n        ear2.setAttribute(\"y\", -size * 0.2);\n        ear2.setAttribute(\"width\", size * 0.1);\n        ear2.setAttribute(\"height\", size * 0.4);\n        g.appendChild(ear2);\n        return g;\n       }\n       if (shape === \"blank-panel\") {\n        const g = document.createElementNS(ns, \"g\");\n        const panel = document.createElementNS(ns, \"rect\");\n        panel.setAttribute(\"x\", -size * 1.4);\n        panel.setAttribute(\"y\", -size * 0.25);\n        panel.setAttribute(\"width\", size * 2.8);\n        panel.setAttribute(\"height\", size * 0.5);\n        panel.setAttribute(\"rx\", 2);\n        g.appendChild(panel);\n        const vent1 = document.createElementNS(ns, \"rect\");\n        vent1.setAttribute(\"x\", -size * 0.8);\n        vent1.setAttribute(\"y\", -size * 0.1);\n        vent1.setAttribute(\"width\", size * 0.5);\n        vent1.setAttribute(\"height\", size * 0.04);\n        vent1.style.fill = \"currentColor\";\n        vent1.style.opacity = \"0.4\";\n        g.appendChild(vent1);\n        const vent2 = document.createElementNS(ns, \"rect\");\n        vent2.setAttribute(\"x\", -size * 0.8);\n        vent2.setAttribute(\"y\", size * 0.02);\n        vent2.setAttribute(\"width\", size * 0.5);\n        vent2.setAttribute(\"height\", size * 0.04);\n        vent2.style.fill = \"currentColor\";\n        vent2.style.opacity = \"0.4\";\n        g.appendChild(vent2);\n        const vent3 = document.createElementNS(ns, \"rect\");\n        vent3.setAttribute(\"x\", size * 0.3);\n        vent3.setAttribute(\"y\", -size * 0.1);\n        vent3.setAttribute(\"width\", size * 0.5);\n        vent3.setAttribute(\"height\", size * 0.04);\n        vent3.style.fill = \"currentColor\";\n        vent3.style.opacity = \"0.4\";\n        g.appendChild(vent3);\n        const vent4 = document.createElementNS(ns, \"rect\");\n        vent4.setAttribute(\"x\", size * 0.3);\n        vent4.setAttribute(\"y\", size * 0.02);\n        vent4.setAttribute(\"width\", size * 0.5);\n        vent4.setAttribute(\"height\", size * 0.04);\n        vent4.style.fill = \"currentColor\";\n        vent4.style.opacity = \"0.4\";\n        g.appendChild(vent4);\n        return g;\n       }\n       if (shape === \"cable-management\") {\n        const g = document.createElementNS(ns, \"g\");\n        const panel = document.createElementNS(ns, \"rect\");\n        panel.setAttribute(\"x\", -size * 1.4);\n        panel.setAttribute(\"y\", -size * 0.3);\n        panel.setAttribute(\"width\", size * 2.8);\n        panel.setAttribute(\"height\", size * 0.6);\n        panel.setAttribute(\"rx\", 2);\n        g.appendChild(panel);\n        for (let i = 0; i < 5; i++) {\n         const ring = document.createElementNS(ns, \"ellipse\");\n         ring.setAttribute(\"cx\", -size + i * size * 0.5);\n         ring.setAttribute(\"rx\", size * 0.12);\n         ring.setAttribute(\"ry\", size * 0.18);\n         ring.style.fill = \"none\";\n         ring.style.stroke = \"currentColor\";\n         ring.style.strokeWidth = size * 0.08;\n         ring.style.opacity = \"0.5\";\n         g.appendChild(ring);\n        }\n        return g;\n       }\n       if (shape === \"kvm\") {\n        const g = document.createElementNS(ns, \"g\");\n        const body = document.createElementNS(ns, \"rect\");\n        body.setAttribute(\"x\", -size * 1.2);\n        body.setAttribute(\"y\", -size * 0.4);\n        body.setAttribute(\"width\", size * 2.4);\n        body.setAttribute(\"height\", size * 0.8);\n        body.setAttribute(\"rx\", 3);\n        g.appendChild(body);\n        const screen = document.createElementNS(ns, \"rect\");\n        screen.setAttribute(\"x\", -size * 0.9);\n        screen.setAttribute(\"y\", -size * 0.25);\n        screen.setAttribute(\"width\", size * 0.8);\n        screen.setAttribute(\"height\", size * 0.5);\n        screen.setAttribute(\"rx\", 2);\n        screen.style.fill = \"currentColor\";\n        screen.style.opacity = \"0.4\";\n        g.appendChild(screen);\n        for (let i = 0; i < 4; i++) {\n         const btn = document.createElementNS(ns, \"rect\");\n         btn.setAttribute(\"x\", size * 0.1 + i * size * 0.25);\n         btn.setAttribute(\"y\", -size * 0.15);\n         btn.setAttribute(\"width\", size * 0.18);\n         btn.setAttribute(\"height\", size * 0.3);\n         btn.setAttribute(\"rx\", 2);\n         btn.style.fill = \"currentColor\";\n         btn.style.opacity = \"0.5\";\n         g.appendChild(btn);\n         const led = document.createElementNS(ns, \"circle\");\n         led.setAttribute(\"cx\", size * 0.19 + i * size * 0.25);\n         led.setAttribute(\"cy\", -size * 0.25);\n         led.setAttribute(\"r\", size * 0.04);\n         led.style.fill = i === 0 ? \"#4ade80\" : \"currentColor\";\n         led.style.opacity = i === 0 ? \"1\" : \"0.3\";\n         g.appendChild(led);\n        }\n        return g;\n       }\n       const c = document.createElementNS(ns, \"circle\");\n       c.setAttribute(\"r\", size);\n       return c;\n      }\n      function createNodeShape(id, size) {\n       const styles = resolveStylesForNode(id);\n       if (styles.icon && styles.icon.library && styles.icon.name) {\n        const g = document.createElementNS(\"http://www.w3.org/2000/svg\", \"g\");\n        g.classList.add(\"node-circle\");\n        IconLibrary.getIcon(styles.icon.library, styles.icon.name).then(svgText => {\n         if (svgText) {\n          const parser = new DOMParser();\n          const doc = parser.parseFromString(svgText, 'image/svg+xml');\n          const svgEl = doc.querySelector('svg');\n          if (svgEl) {\n           svgEl.setAttribute('width', size * 2);\n           svgEl.setAttribute('height', size * 2);\n           svgEl.setAttribute('x', -size);\n           svgEl.setAttribute('y', -size);\n           if (styles.circleColor) {\n            svgEl.style.fill = styles.circleColor;\n            svgEl.querySelectorAll('path, circle, rect, polygon, ellipse').forEach(el => {\n             el.style.fill = styles.circleColor;\n            });\n           }\n           if (styles.circleBorder) {\n            svgEl.style.stroke = styles.circleBorder;\n            svgEl.querySelectorAll('path, circle, rect, polygon, ellipse').forEach(el => {\n             el.style.stroke = styles.circleBorder;\n            });\n           }\n           g.innerHTML = svgEl.outerHTML;\n          }\n         }\n        });\n        return g;\n       }\n       const shapeType = (NODE_DATA[id] && NODE_DATA[id].shape) || \"circle\";\n       const shapeEl = createShapeElement(shapeType, size);\n       shapeEl.classList.add(\"node-circle\");\n       shapeEl.style.fill = styles.circleColor || PAGE_STATE.nodeFill || \"#1e293b\";\n       shapeEl.style.stroke = styles.circleBorder || PAGE_STATE.nodeStroke || \"#475569\";\n       return shapeEl;\n      }\n      function forgeTheLegend() {\n       const container = document.getElementById(\"edge-legend\");\n       if (!container) return;\n       container.innerHTML = \"\";\n       const title = document.createElement(\"div\");\n       title.className = \"legend-title\";\n       title.textContent = t(\"legends.lineLegend\");\n       container.appendChild(title);\n       const closeBtn = document.createElement(\"button\");\n       closeBtn.type = \"button\";\n       closeBtn.className = \"legend-close-btn\";\n       closeBtn.textContent = \"✕\";\n       closeBtn.addEventListener(\"click\", (e) => {\n        e.stopPropagation();\n        legendCollapsed = true;\n        updateLegendVisibility();\n       });\n       container.appendChild(closeBtn);\n       const colors = [...new Set(EDGE_DATA.list.map((e) => e.color).filter(Boolean))];\n       if (colors.length === 0) {\n        updateLegendVisibility();\n        return;\n       }\n       colors.forEach((color) => {\n         if (!EDGE_LEGEND[color]) {\n          EDGE_LEGEND[color] = t(\"legends.defaultLineLabel\");\n         }\n         const item = document.createElement(\"div\");\n         item.className = \"legend-item\";\n         item.addEventListener(\"mousedown\", (e) => e.stopPropagation());\n         item.addEventListener(\"click\", (e) => e.stopPropagation());\n         const swatch = document.createElement(\"span\");\n         swatch.className = \"legend-swatch\";\n         swatch.style.backgroundColor = color;\n         swatch.style.cursor = \"pointer\";\n         swatch.addEventListener(\"click\", (e) => {\n          e.stopPropagation();\n          const edgeWithColor = EDGE_DATA.list.find(edge => edge.color === color);\n          if (edgeWithColor) {\n           selectTheConnection(edgeWithColor.id);\n          }\n         });\n         let swatchTouchStart = null;\n         let swatchTouchMoved = false;\n         swatch.addEventListener(\"touchstart\", (e) => {\n         swatchTouchStart = Date.now();\n         swatchTouchMoved = false;\n         if (e.touches[0]) {\n          swatchTouchStartX = e.touches[0].clientX;\n          swatchTouchStartY = e.touches[0].clientY;\n         }\n        }, {\n          passive: false\n         });\n         let swatchTouchStartX = 0, swatchTouchStartY = 0;\n        swatch.addEventListener(\"touchmove\", (e) => {\n         if (e.touches[0]) {\n          const dx = Math.abs(e.touches[0].clientX - swatchTouchStartX);\n          const dy = Math.abs(e.touches[0].clientY - swatchTouchStartY);\n          if (dx > 10 || dy > 10) swatchTouchMoved = true;\n         }\n        }, {\n          passive: false\n         });\n         swatch.addEventListener(\"touchend\", (e) => {\n          if (swatchTouchStart && !swatchTouchMoved && Date.now() - swatchTouchStart < 400) {\n           e.stopPropagation();\n           e.preventDefault();\n           const edgeWithColor = EDGE_DATA.list.find(edge => edge.color === color);\n           if (edgeWithColor) {\n            selectTheConnection(edgeWithColor.id);\n           }\n          }\n          swatchTouchStart = null;\n          swatchTouchMoved = false;\n         }, {\n          passive: false\n         });\n        const label = document.createElement(\"span\");\n        label.className = \"legend-label\";\n        label.textContent = EDGE_LEGEND[color];\n        if (isMobileDevice()) {\n         label.style.cursor = \"pointer\";\n         let labelTapStart = null;\n         let labelTapMoved = false;\n         label.addEventListener(\"touchstart\", (e) => {\n          labelTapStart = Date.now();\n          labelTapMoved = false;\n          if (e.touches[0]) {\n           labelTapStartX = e.touches[0].clientX;\n           labelTapStartY = e.touches[0].clientY;\n          }\n          e.stopPropagation();\n         });\n         let labelTapStartX = 0, labelTapStartY = 0;\n         label.addEventListener(\"touchmove\", (e) => {\n          if (e.touches[0]) {\n           const dx = Math.abs(e.touches[0].clientX - labelTapStartX);\n           const dy = Math.abs(e.touches[0].clientY - labelTapStartY);\n           if (dx > 10 || dy > 10) labelTapMoved = true;\n          }\n         });\n         label.addEventListener(\"touchend\", async (e) => {\n          if (labelTapStart && !labelTapMoved && Date.now() - labelTapStart < 400) {\n           e.preventDefault();\n           e.stopPropagation();\n           const currentText = label.textContent;\n           const newText = await showPrompt(t(\"dialogs.editLegendLabel\"), currentText);\n           if (newText !== null && newText.trim()) {\n            label.textContent = newText.trim();\n            EDGE_LEGEND[color] = newText.trim();\n           }\n          }\n          labelTapStart = null;\n          labelTapMoved = false;\n         });\n        } else {\n           label.contentEditable = true;\n           label.addEventListener(\"focus\", () => {\n            label.classList.add(\"editing\");\n           });\n           label.addEventListener(\"blur\", () => {\n            label.classList.remove(\"editing\");\n            const text = label.textContent.trim() || t(\"legends.defaultLineLabel\");\n            EDGE_LEGEND[color] = text;\n           });\n           label.addEventListener(\"keydown\", (e) => {\n            if (e.key === \"Enter\") {\n             e.preventDefault();\n             label.blur();\n            }\n           });\n          }\n          item.append(swatch, label); container.appendChild(item);\n         }); updateLegendVisibility();\n       }\n       function deleteRectangle(rectId) {\n      pushUndo(\"delete zone\");\n        RECT_DATA.list = RECT_DATA.list.filter(r => r.id !== rectId);\n        forgeTheTopology();\n       }\n       function updateRectangleDeleteButtons() {\n        const deleteButtons = document.querySelectorAll('.rect-delete-btn');\n        deleteButtons.forEach(btn => {\n         btn.style.display = rectDrawMode ? 'block' : 'none';\n        });\n       }\n       function updateFovCone(nodeId) {\n        const node = NODE_DATA[nodeId];\n        if (!node) return;\n        \n        const nodeGroup = document.querySelector(`g[data-node-id=\"${nodeId}\"]`);\n        if (!nodeGroup) return;\n        const existingFov = nodeGroup.querySelector(\".fov-group\");\n        if (existingFov) existingFov.remove();\n        if (!hasCoverageZone(node.shape) || !node.fovEnabled) return;\n        \n        const ns = \"http://www.w3.org/2000/svg\";\n        const defaults = getCoverageDefaults(node.shape);\n        const fovAngle = node.fovAngle || defaults.angle;\n        const fovDistance = node.fovDistance || defaults.distance;\n        const fovInnerRadius = node.fovInnerRadius || 0;\n        const fovRotation = node.fovRotation || 0;\n        const fovColor = node.fovColor || \"#f59e0b\";\n        const fovOpacity = node.fovOpacity || 20;\n        const fovGradient = node.fovGradient || false;\n        const fovBorderColor = node.fovBorderColor || \"#f59e0b\";\n        const fovBorderWidth = node.fovBorderWidth ?? 2;\n        const fovBorderStyle = node.fovBorderStyle || \"solid\";\n        const fovBorderOpacity = node.fovBorderOpacity ?? 100;\n        const fovLabel = node.fovLabel || \"\";\n        const fovAnimate = node.fovAnimate || false;\n        const fovAnimationType = node.fovAnimationType || defaults.animationType;\n        const fovSweep = node.fovSweep || 120;\n        const fovSpeed = node.fovSpeed || 4;\n        \n        const fovGroup = document.createElementNS(ns, \"g\");\n        fovGroup.classList.add(\"fov-group\");\n        if (!ZONES_VISIBLE) fovGroup.style.display = \"none\";\n        \n        if (fovGradient) {\n          const gradientId = `fov-gradient-${nodeId}`;\n          const defs = document.createElementNS(ns, \"defs\");\n          const gradient = document.createElementNS(ns, \"radialGradient\");\n          gradient.id = gradientId;\n          gradient.setAttribute(\"cx\", \"0\");\n          gradient.setAttribute(\"cy\", \"0\");\n          gradient.setAttribute(\"r\", fovDistance);\n          gradient.setAttribute(\"gradientUnits\", \"userSpaceOnUse\");\n          const stop1 = document.createElementNS(ns, \"stop\");\n          stop1.setAttribute(\"offset\", fovInnerRadius / fovDistance);\n          stop1.setAttribute(\"stop-color\", fovColor);\n          stop1.setAttribute(\"stop-opacity\", fovOpacity / 100);\n          const stop2 = document.createElementNS(ns, \"stop\");\n          stop2.setAttribute(\"offset\", \"1\");\n          stop2.setAttribute(\"stop-color\", fovColor);\n          stop2.setAttribute(\"stop-opacity\", \"0\");\n          gradient.appendChild(stop1);\n          gradient.appendChild(stop2);\n          defs.appendChild(gradient);\n          fovGroup.appendChild(defs);\n        }\n        \n        const fovPath = document.createElementNS(ns, \"path\");\n        \n        if (fovAngle >= 360) {\n          if (fovInnerRadius > 0) {\n            fovPath.setAttribute(\"d\", `M ${fovDistance},0 A ${fovDistance},${fovDistance} 0 1,1 ${-fovDistance},0 A ${fovDistance},${fovDistance} 0 1,1 ${fovDistance},0 M ${fovInnerRadius},0 A ${fovInnerRadius},${fovInnerRadius} 0 1,0 ${-fovInnerRadius},0 A ${fovInnerRadius},${fovInnerRadius} 0 1,0 ${fovInnerRadius},0`);\n            fovPath.setAttribute(\"fill-rule\", \"evenodd\");\n          } else {\n            fovPath.setAttribute(\"d\", `M 0,0 m -${fovDistance},0 a ${fovDistance},${fovDistance} 0 1,0 ${fovDistance * 2},0 a ${fovDistance},${fovDistance} 0 1,0 -${fovDistance * 2},0`);\n          }\n        } else {\n          const angleRad = (fovAngle * Math.PI) / 180;\n          const rotationRad = ((fovRotation - 90) * Math.PI) / 180;\n          const startAngle = rotationRad - angleRad / 2;\n          const endAngle = rotationRad + angleRad / 2;\n          const x1 = Math.cos(startAngle) * fovDistance;\n          const y1 = Math.sin(startAngle) * fovDistance;\n          const x2 = Math.cos(endAngle) * fovDistance;\n          const y2 = Math.sin(endAngle) * fovDistance;\n          const largeArc = fovAngle > 180 ? 1 : 0;\n          if (fovInnerRadius > 0) {\n            const ix1 = Math.cos(startAngle) * fovInnerRadius;\n            const iy1 = Math.sin(startAngle) * fovInnerRadius;\n            const ix2 = Math.cos(endAngle) * fovInnerRadius;\n            const iy2 = Math.sin(endAngle) * fovInnerRadius;\n            fovPath.setAttribute(\"d\", `M ${ix1},${iy1} L ${x1},${y1} A ${fovDistance},${fovDistance} 0 ${largeArc},1 ${x2},${y2} L ${ix2},${iy2} A ${fovInnerRadius},${fovInnerRadius} 0 ${largeArc},0 ${ix1},${iy1} Z`);\n          } else {\n            fovPath.setAttribute(\"d\", `M 0,0 L ${x1},${y1} A ${fovDistance},${fovDistance} 0 ${largeArc},1 ${x2},${y2} Z`);\n          }\n        }\n        \n        if (fovGradient) {\n          fovPath.style.fill = `url(#fov-gradient-${nodeId})`;\n        } else {\n          const opacityHex = Math.round((fovOpacity / 100) * 255).toString(16).padStart(2, '0');\n          fovPath.style.fill = fovColor + opacityHex;\n        }\n        \n        const borderOpacityHex = Math.round((fovBorderOpacity / 100) * 255).toString(16).padStart(2, '0');\n        fovPath.style.stroke = fovBorderColor + borderOpacityHex;\n        fovPath.style.strokeWidth = fovBorderWidth;\n        if (fovBorderStyle === \"dashed\") {\n          fovPath.style.strokeDasharray = \"10,5\";\n        } else if (fovBorderStyle === \"dotted\") {\n          fovPath.style.strokeDasharray = \"3,3\";\n        }\n        fovPath.style.pointerEvents = \"none\";\n        fovPath.classList.add(\"fov-cone\");\n        \n        fovGroup.appendChild(fovPath);\n        \n        if (fovLabel) {\n          const fovLabelPosition = node.fovLabelPosition || \"center\";\n          const fovLabelSize = node.fovLabelSize || 14;\n          const fovLabelColor = node.fovLabelColor || \"#ffffff\";\n          const fovLabelBold = node.fovLabelBold || false;\n          const fovLabelBg = node.fovLabelBg || false;\n          const fovLabelBgColor = node.fovLabelBgColor || \"#000000\";\n          let labelDistance;\n          if (fovLabelPosition === \"center\") {\n            labelDistance = fovAngle >= 360 ? 0 : fovDistance * 0.4;\n          } else if (fovLabelPosition === \"edge\") {\n            labelDistance = fovDistance * 0.75;\n          } else {\n            labelDistance = fovDistance + fovLabelSize + 8;\n          }\n          const labelAngle = ((fovRotation - 90) * Math.PI) / 180;\n          const fovLabelOffsetX = node.fovLabelOffsetX || 0;\n          const fovLabelOffsetY = node.fovLabelOffsetY || 0;\n          const labelX = (fovAngle >= 360 && fovLabelPosition === \"center\" ? 0 : Math.cos(labelAngle) * labelDistance) + fovLabelOffsetX;\n          const labelY = (fovAngle >= 360 && fovLabelPosition === \"center\" ? 0 : Math.sin(labelAngle) * labelDistance) + fovLabelOffsetY;\n          if (fovLabelBg) {\n            const bgRect = document.createElementNS(ns, \"rect\");\n            const textWidth = fovLabel.length * fovLabelSize * 0.6;\n            const textHeight = fovLabelSize * 1.4;\n            bgRect.setAttribute(\"x\", labelX - textWidth / 2 - 6);\n            bgRect.setAttribute(\"y\", labelY - textHeight / 2);\n            bgRect.setAttribute(\"width\", textWidth + 12);\n            bgRect.setAttribute(\"height\", textHeight);\n            bgRect.setAttribute(\"rx\", \"4\");\n            bgRect.style.fill = fovLabelBgColor;\n            bgRect.style.opacity = \"0.8\";\n            bgRect.style.pointerEvents = \"none\";\n            fovGroup.appendChild(bgRect);\n          }\n          const labelEl = document.createElementNS(ns, \"text\");\n          labelEl.setAttribute(\"x\", labelX);\n          labelEl.setAttribute(\"y\", labelY);\n          labelEl.setAttribute(\"text-anchor\", \"middle\");\n          labelEl.setAttribute(\"dominant-baseline\", \"middle\");\n          labelEl.style.fill = fovLabelColor;\n          labelEl.style.fontSize = fovLabelSize + \"px\";\n          labelEl.style.fontWeight = fovLabelBold ? \"bold\" : \"normal\";\n          labelEl.style.fontFamily = \"system-ui, sans-serif\";\n          labelEl.style.pointerEvents = \"none\";\n          labelEl.textContent = fovLabel;\n          fovGroup.appendChild(labelEl);\n        }\n        \n        if (fovAnimate) {\n          const animationName = `fov-anim-${nodeId}`;\n          const styleEl = document.createElementNS(\"http://www.w3.org/1999/xhtml\", \"style\");\n          \n          if (fovAnimationType === \"sweep\" && fovAngle < 360) {\n            styleEl.textContent = `\n              @keyframes ${animationName} {\n                0%, 100% { transform: rotate(0deg); }\n                50% { transform: rotate(${fovSweep}deg); }\n              }\n            `;\n            fovGroup.style.animation = `${animationName} ${fovSpeed}s ease-in-out infinite`;\n            fovGroup.style.transformOrigin = \"0 0\";\n          } else if (fovAnimationType === \"pulse\") {\n            styleEl.textContent = `\n              @keyframes ${animationName} {\n                0%, 100% { transform: scale(1); opacity: 1; }\n                50% { transform: scale(1.1); opacity: 0.7; }\n              }\n            `;\n            fovGroup.style.animation = `${animationName} ${fovSpeed}s ease-in-out infinite`;\n            fovGroup.style.transformOrigin = \"0 0\";\n          } else if (fovAnimationType === \"rings\") {\n            for (let i = 1; i <= 3; i++) {\n              const ring = document.createElementNS(ns, \"circle\");\n              ring.setAttribute(\"cx\", \"0\");\n              ring.setAttribute(\"cy\", \"0\");\n              ring.setAttribute(\"r\", fovDistance * 0.3 * i);\n              ring.style.fill = \"none\";\n              ring.style.stroke = fovBorderColor;\n              ring.style.strokeWidth = \"2\";\n              ring.style.opacity = \"0\";\n              const ringAnimName = `${animationName}-ring-${i}`;\n              const ringStyle = document.createElementNS(\"http://www.w3.org/1999/xhtml\", \"style\");\n              ringStyle.textContent = `\n                @keyframes ${ringAnimName} {\n                  0% { r: ${fovInnerRadius || 10}; opacity: 0.8; }\n                  100% { r: ${fovDistance}; opacity: 0; }\n                }\n              `;\n              ring.style.animation = `${ringAnimName} ${fovSpeed}s ease-out infinite`;\n              ring.style.animationDelay = `${(i - 1) * (fovSpeed / 3)}s`;\n              fovGroup.appendChild(ringStyle);\n              fovGroup.appendChild(ring);\n            }\n          } else if (fovAnimationType === \"spin\") {\n            styleEl.textContent = `\n              @keyframes ${animationName} {\n                0% { transform: rotate(0deg); }\n                100% { transform: rotate(360deg); }\n              }\n            `;\n            fovGroup.style.animation = `${animationName} ${fovSpeed}s linear infinite`;\n            fovGroup.style.transformOrigin = \"0 0\";\n          }\n          \n          if (fovAnimationType !== \"rings\") {\n            fovGroup.appendChild(styleEl);\n            const elapsedSeconds = (Date.now() - FOV_ANIMATION_START) / 1000;\n            const animationOffset = elapsedSeconds % fovSpeed;\n            fovGroup.style.animationDelay = `-${animationOffset}s`;\n          }\n        }\n        \n        nodeGroup.insertBefore(fovGroup, nodeGroup.firstChild);\n      }\n       \n      function forgeTheTopology() {\n        if (!NODE_DATA || !EDGE_DATA) {\n         console.warn(\"forgeTheTopology called before data initialized\");\n         return;\n        }\n        const svg = document.getElementById(\"map\");\n        svg.innerHTML = \"\";\n        const ns = \"http://www.w3.org/2000/svg\";\n        const defs = document.createElementNS(ns, \"defs\");\n        const flowArrowBig = document.createElementNS(ns, \"path\");\n        flowArrowBig.id = \"flow-arrow-big\";\n        flowArrowBig.setAttribute(\"d\", \"M-6,-4 L6,0 L-6,4 L-3,0 Z\");\n        defs.appendChild(flowArrowBig);\n        const flowArrowSmall = document.createElementNS(ns, \"path\");\n        flowArrowSmall.id = \"flow-arrow-small\";\n        flowArrowSmall.setAttribute(\"d\", \"M-4,-3 L4,0 L-4,3 Z\");\n        defs.appendChild(flowArrowSmall);\n        const markerForward = document.createElementNS(ns, \"marker\");\n        markerForward.id = \"arrow-forward\";\n        markerForward.setAttribute(\"markerWidth\", \"10\");\n        markerForward.setAttribute(\"markerHeight\", \"10\");\n        markerForward.setAttribute(\"refX\", \"9\");\n        markerForward.setAttribute(\"refY\", \"3\");\n        markerForward.setAttribute(\"orient\", \"auto\");\n        markerForward.setAttribute(\"markerUnits\", \"strokeWidth\");\n        const pathForward = document.createElementNS(ns, \"path\");\n        pathForward.setAttribute(\"d\", \"M0,0 L0,6 L9,3 z\");\n        pathForward.setAttribute(\"fill\", \"context-stroke\");\n        markerForward.appendChild(pathForward);\n        defs.appendChild(markerForward);\n        const markerBackward = document.createElementNS(ns, \"marker\");\n        markerBackward.id = \"arrow-backward\";\n        markerBackward.setAttribute(\"markerWidth\", \"10\");\n        markerBackward.setAttribute(\"markerHeight\", \"10\");\n        markerBackward.setAttribute(\"refX\", \"0\");\n        markerBackward.setAttribute(\"refY\", \"3\");\n        markerBackward.setAttribute(\"orient\", \"auto\");\n        markerBackward.setAttribute(\"markerUnits\", \"strokeWidth\");\n        const pathBackward = document.createElementNS(ns, \"path\");\n        pathBackward.setAttribute(\"d\", \"M9,0 L9,6 L0,3 z\");\n        pathBackward.setAttribute(\"fill\", \"context-stroke\");\n        markerBackward.appendChild(pathBackward);\n        defs.appendChild(markerBackward);\n\n        const wallPattern = document.createElementNS(ns, \"pattern\");\n        wallPattern.id = \"wall-hatch\";\n        wallPattern.setAttribute(\"patternUnits\", \"userSpaceOnUse\");\n        wallPattern.setAttribute(\"width\", \"8\");\n        wallPattern.setAttribute(\"height\", \"8\");\n        wallPattern.setAttribute(\"patternTransform\", \"rotate(45)\");\n        const wallLine = document.createElementNS(ns, \"line\");\n        wallLine.setAttribute(\"x1\", \"0\");\n        wallLine.setAttribute(\"y1\", \"0\");\n        wallLine.setAttribute(\"x2\", \"0\");\n        wallLine.setAttribute(\"y2\", \"8\");\n        wallLine.setAttribute(\"stroke\", \"#666\");\n        wallLine.setAttribute(\"stroke-width\", \"2\");\n        wallPattern.appendChild(wallLine);\n        defs.appendChild(wallPattern);\n\n        svg.appendChild(defs);\n        const boundary = document.createElementNS(ns, \"rect\");\n        boundary.setAttribute(\"x\", CANVAS_PADDING);\n        boundary.setAttribute(\"y\", CANVAS_PADDING);\n        boundary.setAttribute(\"width\", CANVAS_WIDTH - CANVAS_PADDING * 2);\n        boundary.setAttribute(\"height\", CANVAS_HEIGHT - CANVAS_PADDING * 2);\n        boundary.setAttribute(\"fill\", \"none\");\n        boundary.setAttribute(\"stroke\", (PAGE_STATE.canvasBorder || \"#475569\") + \"4D\");\n        boundary.setAttribute(\"stroke-width\", \"20\");\n        boundary.setAttribute(\"stroke-dasharray\", \"10 5\");\n        boundary.setAttribute(\"rx\", \"8\");\n        svg.appendChild(boundary);\n\t\tconst currentMappingModeForCanvas = PAGE_STATE.mappingMode || 'network';\n\t\tconst shouldDrawCanvasTemplate = currentView.mode !== \"rack\" || currentMappingModeForCanvas !== 'network';\n\t\tif (shouldDrawCanvasTemplate) {\n\t\t const template = PAGE_STATE.canvasTemplate || 'grid';\n\t\t if (template !== 'none' && template !== 'grid' && typeof renderCanvasTemplate === 'function') {\n\t\t  const templateGroup = renderCanvasTemplate(svg, ns, template, CANVAS_WIDTH, CANVAS_HEIGHT, CANVAS_PADDING, PAGE_STATE.canvasGrid);\n\t\t  if (templateGroup) svg.appendChild(templateGroup);\n\t\t } else if (PAGE_STATE.canvasGridEnabled !== false && template !== 'none') {\n\t\t  const gridGroup = document.createElementNS(ns, \"g\");\n\t\t  gridGroup.id = \"canvas-grid\";\n\t\t  const gridSize = PAGE_STATE.canvasGridSize || 50;\n\t\t  const gridColor = (PAGE_STATE.canvasGrid || \"#475569\") + \"33\";\n\t\t  const majorGridColor = (PAGE_STATE.canvasGrid || \"#475569\") + \"66\";\n\t\t  for (let x = CANVAS_PADDING; x <= CANVAS_WIDTH - CANVAS_PADDING; x += gridSize) {\n\t\t   const line = document.createElementNS(ns, \"line\");\n\t\t   line.setAttribute(\"x1\", x);\n\t\t   line.setAttribute(\"y1\", CANVAS_PADDING);\n\t\t   line.setAttribute(\"x2\", x);\n\t\t   line.setAttribute(\"y2\", CANVAS_HEIGHT - CANVAS_PADDING);\n\t\t   line.setAttribute(\"stroke\", (x - CANVAS_PADDING) % (gridSize * 5) === 0 ? majorGridColor : gridColor);\n\t\t   line.setAttribute(\"stroke-width\", (x - CANVAS_PADDING) % (gridSize * 5) === 0 ? \"2\" : \"1\");\n\t\t   gridGroup.appendChild(line);\n\t\t  }\n\t\t  for (let y = CANVAS_PADDING; y <= CANVAS_HEIGHT - CANVAS_PADDING; y += gridSize) {\n\t\t   const line = document.createElementNS(ns, \"line\");\n\t\t   line.setAttribute(\"x1\", CANVAS_PADDING);\n\t\t   line.setAttribute(\"y1\", y);\n\t\t   line.setAttribute(\"x2\", CANVAS_WIDTH - CANVAS_PADDING);\n\t\t   line.setAttribute(\"y2\", y);\n\t\t   line.setAttribute(\"stroke\", (y - CANVAS_PADDING) % (gridSize * 5) === 0 ? majorGridColor : gridColor);\n\t\t   line.setAttribute(\"stroke-width\", (y - CANVAS_PADDING) % (gridSize * 5) === 0 ? \"2\" : \"1\");\n\t\t   gridGroup.appendChild(line);\n\t\t  }\n\t\t  svg.appendChild(gridGroup);\n\t\t }\n\t\t}\n       if (currentView.mode === \"rack\" && currentView.rackId) {\n         const rackCapacity = getRackCapacity(currentView.rackId);\n      const rackUHeight = getRackUHeight(currentView.rackId);\n         const rackGroup = document.createElementNS(ns, \"g\");\n         rackGroup.id = \"rack-visualization\";\n         const showUSlotRack = (PAGE_STATE.mappingMode || 'network') === 'network';\n         if (showUSlotRack) {\n         const rackFrame = document.createElementNS(ns, \"rect\");\n         rackFrame.setAttribute(\"x\", RACK_START_X - RACK_WIDTH / 2);\n         rackFrame.setAttribute(\"y\", RACK_START_Y);\n         rackFrame.setAttribute(\"width\", RACK_WIDTH);\n         rackFrame.setAttribute(\"height\", rackCapacity * rackUHeight);\n         rackFrame.setAttribute(\"fill\", (PAGE_STATE.rackFrameFill || \"#0f172a\") + \"4D\");\n         rackFrame.setAttribute(\"stroke\", PAGE_STATE.rackFrameStroke || \"#4fd1c5\");\n         rackFrame.setAttribute(\"stroke-width\", \"3\");\n         rackFrame.setAttribute(\"rx\", \"4\");\n         rackGroup.appendChild(rackFrame);\n         if (PAGE_STATE.rackGridEnabled !== false) {\n          for (let u = 0; u <= rackCapacity; u++) {\n           const y = RACK_START_Y + u * rackUHeight;\n           const line = document.createElementNS(ns, \"line\");\n           line.setAttribute(\"x1\", RACK_START_X - RACK_WIDTH / 2);\n           line.setAttribute(\"y1\", y);\n           line.setAttribute(\"x2\", RACK_START_X + RACK_WIDTH / 2);\n           line.setAttribute(\"y2\", y);\n           line.setAttribute(\"stroke\", (PAGE_STATE.rackLineColor || \"#475569\") + \"66\");\n           line.setAttribute(\"stroke-width\", u % 5 === 0 ? \"2\" : \"1\");\n           line.setAttribute(\"stroke-dasharray\", u % 5 === 0 ? \"none\" : \"5,5\");\n           rackGroup.appendChild(line);\n           if (u < rackCapacity) {\n            const uNumber = rackCapacity - u;\n            const text = document.createElementNS(ns, \"text\");\n            text.setAttribute(\"x\", RACK_START_X - RACK_WIDTH / 2 - 30);\n            text.setAttribute(\"y\", y + rackUHeight / 2);\n            text.setAttribute(\"text-anchor\", \"middle\");\n            text.setAttribute(\"dominant-baseline\", \"middle\");\n            text.style.fill = PAGE_STATE.rackTextColor || \"#4fd1c5\";\n            text.style.fontSize = \"14px\";\n            text.style.fontWeight = \"bold\";\n            text.textContent = `U${uNumber}`;\n            rackGroup.appendChild(text);\n            const textRight = document.createElementNS(ns, \"text\");\n            textRight.setAttribute(\"x\", RACK_START_X + RACK_WIDTH / 2 + 30);\n            textRight.setAttribute(\"y\", y + rackUHeight / 2);\n            textRight.setAttribute(\"text-anchor\", \"middle\");\n            textRight.setAttribute(\"dominant-baseline\", \"middle\");\n            textRight.style.fill = PAGE_STATE.rackTextColor || \"#4fd1c5\";\n            textRight.style.fontSize = \"14px\";\n            textRight.style.fontWeight = \"bold\";\n            textRight.textContent = `U${uNumber}`;\n            rackGroup.appendChild(textRight);\n           }\n          }\n         }\n         }\n         svg.appendChild(rackGroup);\n        }\n        const centerX = CANVAS_WIDTH / 2;\n        if (RECT_DATA && RECT_DATA.list) {\n         RECT_DATA.list.forEach((rect) => {\n          if (currentView.mode === \"rack\") return;\n          if (rect.style === \"filled\" || rect.style === \"outlined\") {\n           const g = document.createElementNS(ns, \"g\");\n           g.classList.add(\"rect-group\");\n           g.dataset.rectId = rect.id;\n           const rectCenterX = rect.x + rect.width / 2;\n           const rectCenterY = rect.y + rect.height / 2;\n           const rectRotation = rect.rotation || 0;\n           if (rectRotation !== 0) {\n             g.setAttribute(\"transform\", `rotate(${rectRotation}, ${rectCenterX}, ${rectCenterY})`);\n           }\n           const rectEl = document.createElementNS(ns, \"rect\");\n           rectEl.classList.add(\"rect-shape\");\n           rectEl.setAttribute(\"x\", rect.x);\n           rectEl.setAttribute(\"y\", rect.y);\n           rectEl.setAttribute(\"width\", rect.width);\n           rectEl.setAttribute(\"height\", rect.height);\n           if (rect.style === \"filled\") {\n             rectEl.style.fill = rect.color;\n             rectEl.style.fillOpacity = rect.fillOpacity !== undefined ? rect.fillOpacity : 0.3;\n           } else {\n             rectEl.style.fill = \"none\";\n           }\n           rectEl.style.stroke = rect.borderColor || rect.color;\n           rectEl.style.strokeWidth = (rect.borderWidth !== undefined ? rect.borderWidth : 2) + \"px\";\n           if (rect.lineStyle === \"dashed\") { rectEl.style.strokeDasharray = \"10,5\"; }\n           else if (rect.lineStyle === \"dotted\") { rectEl.style.strokeDasharray = \"2,4\"; }\n           else if (rect.lineStyle === \"wall\") {\n             rectEl.style.fill = rect.color;\n             rectEl.style.fillOpacity = rect.fillOpacity !== undefined ? rect.fillOpacity : 0.5;\n             rectEl.style.stroke = rect.borderColor || rect.color;\n             rectEl.style.strokeWidth = (rect.borderWidth !== undefined ? rect.borderWidth : 2) + \"px\";\n             const hatchGroup = document.createElementNS(ns, \"g\");\n             hatchGroup.classList.add(\"wall-hatch-lines\");\n             hatchGroup.style.pointerEvents = \"none\";\n             const spacing = 12;\n             const hatchColor = rect.borderColor || rect.color;\n             for (let i = -rect.height; i < rect.width + rect.height; i += spacing) {\n               const line = document.createElementNS(ns, \"line\");\n               line.setAttribute(\"x1\", rect.x + i);\n               line.setAttribute(\"y1\", rect.y);\n               line.setAttribute(\"x2\", rect.x + i - rect.height);\n               line.setAttribute(\"y2\", rect.y + rect.height);\n               line.style.stroke = hatchColor;\n               line.style.strokeWidth = \"2\";\n               hatchGroup.appendChild(line);\n             }\n             const clipId = \"clip-\" + rect.id;\n             const clipPath = document.createElementNS(ns, \"clipPath\");\n             clipPath.id = clipId;\n             const clipRect = document.createElementNS(ns, \"rect\");\n             clipRect.setAttribute(\"x\", rect.x);\n             clipRect.setAttribute(\"y\", rect.y);\n             clipRect.setAttribute(\"width\", rect.width);\n             clipRect.setAttribute(\"height\", rect.height);\n             clipPath.appendChild(clipRect);\n             defs.appendChild(clipPath);\n             hatchGroup.setAttribute(\"clip-path\", \"url(#\" + clipId + \")\");\n             g.appendChild(hatchGroup);\n           }\n           else { rectEl.style.strokeDasharray = \"none\"; }\n           rectEl.style.cursor = \"move\";\n           rectEl.addEventListener(\"click\", (e) => {\n\t\t   if (isViewOnly()) return;\n             if (rectDrawMode) return;\n             e.stopPropagation();\n             currentRectId = rect.id;\n             selectTheRect(rect.id);\n           });\n           rectEl.addEventListener(\"contextmenu\", (e) => {\n\t\t    if (isViewOnly()) return;\n             e.preventDefault();\n             e.stopPropagation();\n             if (selectedRects.has(rect.id)) {\n               selectedRects.delete(rect.id);\n             } else {\n               selectedRects.add(rect.id);\n             }\n             updateAllSelections();\n           });\n           let rectLastTap = 0;\n           rectEl.addEventListener(\"touchend\", (e) => {\n             const now = Date.now();\n             if (now - rectLastTap < 300) {\n               e.preventDefault();\n               if (selectedRects.has(rect.id)) {\n                 selectedRects.delete(rect.id);\n               } else {\n                 selectedRects.add(rect.id);\n               }\n               updateAllSelections();\n               if (navigator.vibrate) navigator.vibrate(50);\n               rectLastTap = 0;\n             } else {\n               rectLastTap = now;\n             }\n           }, { passive: false });\n           const deleteBtn = document.createElementNS(ns, \"g\");\n           deleteBtn.classList.add(\"rect-delete-btn\");\n           deleteBtn.style.cursor = \"pointer\";\n           deleteBtn.style.display = rectDrawMode ? \"block\" : \"none\";\n           const deleteBg = document.createElementNS(ns, \"circle\");\n           deleteBg.setAttribute(\"cx\", rect.x + rect.width - 10);\n           deleteBg.setAttribute(\"cy\", rect.y + 10);\n           deleteBg.setAttribute(\"r\", 12);\n           deleteBg.style.fill = \"#f56565\";\n           deleteBg.style.stroke = \"white\";\n           deleteBg.style.strokeWidth = \"2\";\n           const deleteX = document.createElementNS(ns, \"text\");\n           deleteX.setAttribute(\"x\", rect.x + rect.width - 10);\n           deleteX.setAttribute(\"y\", rect.y + 10);\n           deleteX.setAttribute(\"text-anchor\", \"middle\");\n           deleteX.setAttribute(\"dominant-baseline\", \"middle\");\n           deleteX.style.fill = \"white\";\n           deleteX.style.fontSize = \"16px\";\n           deleteX.style.fontWeight = \"bold\";\n           deleteX.style.pointerEvents = \"none\";\n           deleteX.textContent = \"×\";\n           deleteBtn.appendChild(deleteBg);\n           deleteBtn.appendChild(deleteX);\n           deleteBtn.addEventListener(\"click\", (e) => {\n            e.stopPropagation();\n            e.preventDefault();\n            deleteRectangle(rect.id);\n           });\n           deleteBtn.addEventListener(\"touchend\", (e) => {\n      e.stopPropagation();\n           e.preventDefault();\n           deleteRectangle(rect.id);\n           });\n           let isDragging = false;\n           let dragStartX, dragStartY;\n           let rectStartX, rectStartY;\n           rectEl.addEventListener(\"mousedown\", (e) => {\n\t\t   if (isViewOnly()) return;\n      if (rectDrawMode) return;\n      e.preventDefault();\n      e.stopPropagation();\n      isDragging = true;\n      dragStartX = e.clientX;\n      dragStartY = e.clientY;\n      rectStartX = rect.x;\n      rectStartY = rect.y;\n      rectEl.style.cursor = \"grabbing\";\n      if (selectedRects.has(rect.id)) {\n      initialPositions = {};\n      selectedNodes.forEach(nodeId => {\n      const nodePos = savedPositions[nodeId];\n      if (nodePos) initialPositions[nodeId] = { x: nodePos.x, y: nodePos.y };\n      });\n      selectedRects.forEach(rectId => {\n      const r = RECT_DATA.list.find(x => x.id === rectId);\n      if (r) { r._dragStartX = r.x; r._dragStartY = r.y; }\n      });\n      selectedTexts.forEach(textId => {\n      const t = TEXT_DATA.list.find(x => x.id === textId);\n      if (t) { t._dragStartX = t.x; t._dragStartY = t.y; }\n      });\n      selectedEdges.forEach(edgeId => {\n      const ed = EDGE_DATA.list.find(x => x.id === edgeId);\n      if (ed && ed.points) { ed._dragStartPoints = ed.points.map(p => ({x: p.x, y: p.y})); }\n      });\n      }\n      });\n      const moveHandler = (e) => {\n      if (!isDragging || rectDrawMode) return;\n      const svgEl = svg;\n      const pt1 = svgEl.createSVGPoint();\n      pt1.x = dragStartX;\n      pt1.y = dragStartY;\n      const svgP1 = pt1.matrixTransform(svgEl.getScreenCTM().inverse());\n      const pt2 = svgEl.createSVGPoint();\n      pt2.x = e.clientX;\n      pt2.y = e.clientY;\n      const svgP2 = pt2.matrixTransform(svgEl.getScreenCTM().inverse());\n      const dx = svgP2.x - svgP1.x;\n      const dy = svgP2.y - svgP1.y;\n      rect.x = rectStartX + dx;\n      rect.y = rectStartY + dy;\n      if (selectedRects.has(rect.id)) {\n      selectedNodes.forEach(nodeId => { if (initialPositions[nodeId]) { savedPositions[nodeId] = { x: initialPositions[nodeId].x + dx, y: initialPositions[nodeId].y + dy }; } });\n      selectedRects.forEach(rectId => { if (rectId === rect.id) return; const r = RECT_DATA.list.find(x => x.id === rectId); if (r && r._dragStartX !== undefined) { r.x = r._dragStartX + dx; r.y = r._dragStartY + dy; } });\n      selectedTexts.forEach(textId => { const t = TEXT_DATA.list.find(x => x.id === textId); if (t && t._dragStartX !== undefined) { t.x = t._dragStartX + dx; t.y = t._dragStartY + dy; } });\n      selectedEdges.forEach(edgeId => { const ed = EDGE_DATA.list.find(x => x.id === edgeId); if (ed && ed._dragStartPoints) { ed.points.forEach((p, i) => { p.x = ed._dragStartPoints[i].x + dx; p.y = ed._dragStartPoints[i].y + dy; }); } });\n      }\n      forgeTheTopology();\n      };\n           const upHandler = () => {\n            if (isDragging) {\n             isDragging = false;\n             rectEl.style.cursor = \"move\";\n            }\n           };\n           document.addEventListener(\"mousemove\", moveHandler);\n           document.addEventListener(\"mouseup\", upHandler);\n           let touchStartX, touchStartY;\n           let touchRectStartX, touchRectStartY;\n           rectEl.addEventListener(\"touchstart\", (e) => {\n\t\t   if (isViewOnly()) return;\n            if (rectDrawMode) return;\n            e.preventDefault();\n            e.stopPropagation();\n            isDragging = true;\n            const touch = e.touches[0];\n            touchStartX = touch.clientX;\n            touchStartY = touch.clientY;\n            touchRectStartX = rect.x;\n            touchRectStartY = rect.y;\n            rectEl.style.cursor = \"grabbing\";\n           }, { passive: false });\n           rectEl.addEventListener(\"touchmove\", (e) => {\n\t\t   if (isViewOnly()) return;\n            if (!isDragging || rectDrawMode) return;\n            if (!e.touches[0]) return;\n            e.preventDefault();\n            e.stopPropagation();\n            const svgEl = svg;\n            const pt1 = svgEl.createSVGPoint();\n            pt1.x = touchStartX;\n            pt1.y = touchStartY;\n            const svgP1 = pt1.matrixTransform(svgEl.getScreenCTM().inverse());\n            const touch = e.touches[0];\n            const pt2 = svgEl.createSVGPoint();\n            pt2.x = touch.clientX;\n            pt2.y = touch.clientY;\n            const svgP2 = pt2.matrixTransform(svgEl.getScreenCTM().inverse());\n            const dx = svgP2.x - svgP1.x;\n            const dy = svgP2.y - svgP1.y;\n            rect.x = touchRectStartX + dx;\n            rect.y = touchRectStartY + dy;\n            forgeTheTopology();\n           }, { passive: false });\n           rectEl.addEventListener(\"touchend\", (e) => {\n            if (isDragging) {\n             e.preventDefault();\n             isDragging = false;\n             rectEl.style.cursor = \"move\";\n            }\n           }, { passive: false });\n            if (currentRectId === rect.id) {\n      const corners = [\n      { cx: rect.x, cy: rect.y, cursor: 'nwse-resize', dx: -1, dy: -1 },\n      { cx: rect.x + rect.width, cy: rect.y, cursor: 'nesw-resize', dx: 1, dy: -1 },\n      { cx: rect.x, cy: rect.y + rect.height, cursor: 'nesw-resize', dx: -1, dy: 1 },\n      { cx: rect.x + rect.width, cy: rect.y + rect.height, cursor: 'nwse-resize', dx: 1, dy: 1 }\n      ];\n      corners.forEach((corner, idx) => {\n      const handle = document.createElementNS(ns, \"circle\");\n      handle.setAttribute(\"cx\", corner.cx);\n      handle.setAttribute(\"cy\", corner.cy);\n      const borderW = rect.borderWidth !== undefined ? rect.borderWidth : 2;\n      const handleSize = Math.max(PAGE_STATE.selectionHandleSize || 8, borderW + 4);\n      handle.setAttribute(\"r\", handleSize);\n      handle.style.fill = PAGE_STATE.selectionHandle || \"#f59e0b\";\n      handle.style.stroke = \"#fff\";\n      handle.style.strokeWidth = \"2\";\n      handle.style.cursor = corner.cursor;\n      handle.addEventListener(\"mousedown\", (e) => {\n\t  if (isViewOnly()) return;\n      e.preventDefault();\n      e.stopPropagation();\n\t  pushUndo(\"resize zone\");\n      let dragging = true;\n      const startX = e.clientX, startY = e.clientY;\n      const origX = rect.x, origY = rect.y, origW = rect.width, origH = rect.height;\n      const moveHandler = (ev) => {\n        if (!dragging) return;\n        const pt1 = svg.createSVGPoint(); pt1.x = startX; pt1.y = startY;\n        const pt2 = svg.createSVGPoint(); pt2.x = ev.clientX; pt2.y = ev.clientY;\n        const svgP1 = pt1.matrixTransform(svg.getScreenCTM().inverse());\n        const svgP2 = pt2.matrixTransform(svg.getScreenCTM().inverse());\n        const dx = svgP2.x - svgP1.x, dy = svgP2.y - svgP1.y;\n        if (corner.dx < 0) { rect.x = origX + dx; rect.width = origW - dx; }\n        else { rect.width = origW + dx; }\n        if (corner.dy < 0) { rect.y = origY + dy; rect.height = origH - dy; }\n        else { rect.height = origH + dy; }\n        if (rect.width < 20) rect.width = 20;\n        if (rect.height < 20) rect.height = 20;\n        forgeTheTopology();\n      };\n      const upHandler = () => { dragging = false; document.removeEventListener(\"mousemove\", moveHandler); document.removeEventListener(\"mouseup\", upHandler); };\n      document.addEventListener(\"mousemove\", moveHandler);\n      document.addEventListener(\"mouseup\", upHandler);\n      });\n      g.appendChild(handle);\n      });\n      }\n            if (rect.groupId) {\n             const groupIndicator = document.createElementNS(ns, \"rect\");\n             groupIndicator.setAttribute(\"x\", rect.x - 4);\n             groupIndicator.setAttribute(\"y\", rect.y - 4);\n             groupIndicator.setAttribute(\"width\", rect.width + 8);\n             groupIndicator.setAttribute(\"height\", rect.height + 8);\n             groupIndicator.style.fill = \"none\";\n        groupIndicator.style.stroke = PAGE_STATE.groupIndicator || \"#4fd1c5\";\n             groupIndicator.style.strokeWidth = \"3\";\n             groupIndicator.style.strokeDasharray = \"5,5\";\n             groupIndicator.style.pointerEvents = \"none\";\n             g.insertBefore(groupIndicator, g.firstChild);\n           }\n           g.appendChild(rectEl);\n           g.appendChild(deleteBtn);\n           svg.appendChild(g);\n          }\n         });\n        }\n        const centerY = CANVAS_HEIGHT / 2;\n        let positions = {};\n        Object.keys(NODE_DATA).forEach((id) => {\n         if (currentView.mode === \"rack\") {\n          const node = NODE_DATA[id];\n          if (!node || node.assignedRack !== currentView.rackId) {\n           return;\n          }\n         }\n         positions[id] = savedPositions[id] || {\n          x: centerX,\n          y: centerY\n         };\n        });\n        if (Object.keys(savedPositions).length === 0) {\n         const nodeIds = Object.keys(NODE_DATA).filter(id => {\n          if (currentView.mode === \"rack\") {\n           const node = NODE_DATA[id];\n           return node && node.assignedRack === currentView.rackId;\n          }\n          return true;\n         });\n         const baseY = centerY - 300;\n         if (nodeIds.length > 0) {\n          positions[nodeIds[0]] = {\n           x: centerX,\n           y: baseY\n          };\n          const remaining = nodeIds.slice(1);\n          const radius = 350;\n          const startAngle = Math.PI * 0.3;\n          const endAngle = Math.PI * 0.7;\n          remaining.forEach((id, i) => {\n           const angle = startAngle + (endAngle - startAngle) * (i / Math.max(1, remaining.length - 1));\n           positions[id] = {\n            x: centerX + Math.cos(angle) * radius * (i % 2 === 0 ? 1 : 1.3),\n            y: baseY + 200 + Math.sin(angle) * radius * 0.8 + i * 80,\n           };\n          });\n         }\n        }\n        Object.keys(positions).forEach((id) => {\n         let pos = savedPositions[id] || positions[id];\n         const nodeSize = savedSizes[id] || 55;\n         pos.x = Math.max(nodeSize + CANVAS_PADDING, Math.min(CANVAS_WIDTH - nodeSize - CANVAS_PADDING, pos.x));\n         pos.y = Math.max(nodeSize + CANVAS_PADDING, Math.min(CANVAS_HEIGHT - nodeSize - CANVAS_PADDING, pos.y));\n         positions[id] = {\n          x: pos.x,\n          y: pos.y\n         };\n         savedPositions[id] = {\n          x: pos.x,\n          y: pos.y\n         };\n        });\n        const edgePairCount = {};\n        const edgePairIndex = {};\n        EDGE_DATA.list.forEach((edge) => {\n         if (edge.type === \"custom\") return;\n         const key = [edge.from, edge.to].sort().join(\"||\");\n         edgePairCount[key] = (edgePairCount[key] || 0) + 1;\n        });\n        EDGE_DATA.list.forEach((edge) => {\n         if (edge.type === \"custom\") return;\n         const key = [edge.from, edge.to].sort().join(\"||\");\n         if (!edgePairIndex[key]) edgePairIndex[key] = 0;\n         edge._pairIndex = edgePairIndex[key];\n         edge._pairTotal = edgePairCount[key];\n         edgePairIndex[key]++;\n       });\n       \n       const orthoGaps = (function() {\n         const segments = [];\n         const GAP_SIZE = 12;\n         EDGE_DATA.list.forEach((edge, edgeIndex) => {\n           if ((edge.routing || PAGE_STATE.defaultEdgeRouting || \"curved\") !== \"orthogonal\") return;\n           if (edge.type === \"custom\") return;\n           const p1 = positions[edge.from];\n           const p2 = positions[edge.to];\n           if (!p1 || !p2) return;\n           const waypoints = edge.waypoints || [];\n           const pairIndex = edge._pairIndex || 0;\n           const pairTotal = edge._pairTotal || 1;\n           const offset = (pairIndex - (pairTotal - 1) / 2) * 40;\n           if (waypoints.length > 0) {\n             const allPoints = [p1, ...waypoints, p2];\n             for (let i = 1; i < allPoints.length; i++) {\n               const prev = allPoints[i - 1];\n               const curr = allPoints[i];\n               const dx = curr.x - prev.x;\n               const dy = curr.y - prev.y;\n               if (Math.abs(dx) > Math.abs(dy)) {\n                 const midX = prev.x + dx / 2;\n                 segments.push({ edgeId: edge.id, x1: prev.x, y1: prev.y, x2: midX, y2: prev.y, idx: edgeIndex });\n                 segments.push({ edgeId: edge.id, x1: midX, y1: prev.y, x2: midX, y2: curr.y, idx: edgeIndex });\n                 segments.push({ edgeId: edge.id, x1: midX, y1: curr.y, x2: curr.x, y2: curr.y, idx: edgeIndex });\n               } else {\n                 const midY = prev.y + dy / 2;\n                 segments.push({ edgeId: edge.id, x1: prev.x, y1: prev.y, x2: prev.x, y2: midY, idx: edgeIndex });\n                 segments.push({ edgeId: edge.id, x1: prev.x, y1: midY, x2: curr.x, y2: midY, idx: edgeIndex });\n                 segments.push({ edgeId: edge.id, x1: curr.x, y1: midY, x2: curr.x, y2: curr.y, idx: edgeIndex });\n               }\n             }\n           } else {\n             const dx = p2.x - p1.x;\n             const dy = p2.y - p1.y;\n             if (Math.abs(dx) > Math.abs(dy)) {\n               const midX = p1.x + dx / 2 + offset;\n               segments.push({ edgeId: edge.id, x1: p1.x, y1: p1.y, x2: midX, y2: p1.y, idx: edgeIndex });\n               segments.push({ edgeId: edge.id, x1: midX, y1: p1.y, x2: midX, y2: p2.y, idx: edgeIndex });\n               segments.push({ edgeId: edge.id, x1: midX, y1: p2.y, x2: p2.x, y2: p2.y, idx: edgeIndex });\n             } else {\n               const midY = p1.y + dy / 2 + offset;\n               segments.push({ edgeId: edge.id, x1: p1.x, y1: p1.y, x2: p1.x, y2: midY, idx: edgeIndex });\n               segments.push({ edgeId: edge.id, x1: p1.x, y1: midY, x2: p2.x, y2: midY, idx: edgeIndex });\n               segments.push({ edgeId: edge.id, x1: p2.x, y1: midY, x2: p2.x, y2: p2.y, idx: edgeIndex });\n             }\n           }\n         });\n         const gaps = {};\n         for (let i = 0; i < segments.length; i++) {\n           for (let j = i + 1; j < segments.length; j++) {\n             const s1 = segments[i];\n             const s2 = segments[j];\n             if (s1.edgeId === s2.edgeId) continue;\n             const s1Horiz = Math.abs(s1.y1 - s1.y2) < 1;\n             const s2Horiz = Math.abs(s2.y1 - s2.y2) < 1;\n             if (s1Horiz === s2Horiz) continue;\n             const horiz = s1Horiz ? s1 : s2;\n             const vert = s1Horiz ? s2 : s1;\n             const hY = horiz.y1;\n             const vX = vert.x1;\n             const hMinX = Math.min(horiz.x1, horiz.x2);\n             const hMaxX = Math.max(horiz.x1, horiz.x2);\n             const vMinY = Math.min(vert.y1, vert.y2);\n             const vMaxY = Math.max(vert.y1, vert.y2);\n             if (vX > hMinX && vX < hMaxX && hY > vMinY && hY < vMaxY) {\n               const gapEdge = s1.idx > s2.idx ? s1.edgeId : s2.edgeId;\n               if (!gaps[gapEdge]) gaps[gapEdge] = [];\n               gaps[gapEdge].push({ x: vX, y: hY });\n             }\n           }\n         }\n         return gaps;\n       })();\n\n       const _placedPortLabels = [];\n\n       EDGE_DATA.list.forEach((edge) => {\n        const fromNode = NODE_DATA[edge.from];\n        const toNode = NODE_DATA[edge.to];\n        if (currentView.mode === \"rack\") {\n         if (!fromNode || !toNode || fromNode.assignedRack !== currentView.rackId || toNode.assignedRack !== currentView.rackId) return;\n        } else {\n         if (fromNode?.assignedRack || toNode?.assignedRack) return;\n        }\n        if (edge.type === \"custom\" && Array.isArray(edge.points) && edge.points.length >= 2) {\n          const customEdgeFaded = currentView.mode !== \"rack\" && edge.from && edge.to && (!isNodeVisible(edge.from) || !isNodeVisible(edge.to));\n          const poly = document.createElementNS(ns, \"polyline\");\n          poly.classList.add(\"edge\");\n          if (customEdgeFaded) {\n           poly.style.opacity = \"0.25\";\n           poly.classList.add(\"layer-faded\");\n          }\n          poly.dataset.edgeId = edge.id;\n          poly.style.stroke = edge.color || PAGE_STATE.defaultEdge || \"#475569\";\n          poly.style.strokeWidth = edge.width || 4;\n          poly.setAttribute(\"fill\", \"none\");\n          const lineStyle = edge.lineStyle || \"solid\";\n          if (lineStyle === \"dashed\") {\n           poly.style.strokeDasharray = \"10,5\";\n          } else if (lineStyle === \"dotted\") {\n           poly.style.strokeDasharray = \"2,4\";\n          } else if (lineStyle === \"wall\") {\n           poly.style.stroke = \"url(#wall-hatch)\";\n           poly.style.strokeWidth = (edge.width || 4) * 3;\n           poly.style.strokeDasharray = \"none\";\n          } else {\n           poly.style.strokeDasharray = \"none\";\n          }\n          const direction = edge.direction || \"none\";\n          if (direction === \"forward\") {\n           poly.setAttribute(\"marker-end\", \"url(#arrow-forward)\");\n          } else if (direction === \"backward\") {\n           poly.setAttribute(\"marker-start\", \"url(#arrow-backward)\");\n          } else if (direction === \"both\") {\n           poly.setAttribute(\"marker-start\", \"url(#arrow-backward)\");\n           poly.setAttribute(\"marker-end\", \"url(#arrow-forward)\");\n          }\n          const ptsStr = edge.points.map((p) => `${p.x},${p.y}`).join(\" \");\n          poly.setAttribute(\"points\", ptsStr);\n          const animDir = PAGE_STATE.animationDirection || \"all\";\n          const shouldAnimatePoly = (PAGE_STATE.animateConnections && edge.animate !== false || edge.animate === true) && direction !== \"none\" && edge.points.length >= 2 && (animDir === \"all\" || animDir === direction);\n          if (shouldAnimatePoly) {\n           poly.style.opacity = \"0.25\";\n           const polyPathD = \"M \" + edge.points.map(p => `${p.x} ${p.y}`).join(\" L \");\n           const edgeStyle = edge.animationStyle || PAGE_STATE.animationStyle || \"arrows\";\n           const arrowId = edgeStyle === \"dots\" ? \"#flow-arrow-small\" : \"#flow-arrow-big\";\n           const arrowColor = edge.color || PAGE_STATE.defaultEdge || \"#475569\";\n           const arrowCount = 3;\n           const animDuration = edge.animationSpeed ? parseFloat(edge.animationSpeed) : (PAGE_STATE.animationSpeed || 1.5);\n           if (direction === \"forward\" || direction === \"both\") {\n            for (let i = 0; i < arrowCount; i++) {\n             const arrow = document.createElementNS(ns, \"use\");\n             arrow.setAttribute(\"href\", arrowId);\n             arrow.style.fill = arrowColor;\n             arrow.style.offsetPath = `path('${polyPathD}')`;\n             arrow.style.animationDuration = animDuration + \"s\";\n             arrow.style.animationDelay = (i * (animDuration / arrowCount)) + \"s\";\n             arrow.classList.add(\"edge-arrow-forward\");\n             svg.appendChild(arrow);\n            }\n           }\n           if (direction === \"backward\" || direction === \"both\") {\n            for (let i = 0; i < arrowCount; i++) {\n             const arrow = document.createElementNS(ns, \"use\");\n             arrow.setAttribute(\"href\", arrowId);\n             arrow.style.fill = arrowColor;\n             arrow.style.offsetPath = `path('${polyPathD}')`;\n             arrow.style.animationDuration = animDuration + \"s\";\n             arrow.style.animationDelay = ((i * (animDuration / arrowCount)) + (direction === \"both\" ? animDuration / (arrowCount * 2) : 0)) + \"s\";\n             arrow.classList.add(\"edge-arrow-backward\");\n             svg.appendChild(arrow);\n            }\n           }\n          }\n          const polyHit = document.createElementNS(ns, \"polyline\");\n          polyHit.setAttribute(\"points\", ptsStr);\n          polyHit.style.fill = \"none\";\n          polyHit.style.stroke = \"transparent\";\n          polyHit.style.strokeWidth = \"20\";\n          polyHit.style.cursor = \"pointer\";\n          polyHit.dataset.edgeId = edge.id;\n          polyHit.addEventListener(\"click\", (e) => {\n           e.stopPropagation();\n           selectTheConnection(edge.id);\n          });\n          let edgeTouchStart = null;\n          let edgeTouchMoved = false;\n          polyHit.addEventListener(\"touchstart\", (e) => {\n          edgeTouchStart = Date.now();\n          edgeTouchMoved = false;\n          if (e.touches[0]) {\n           edgeTouchStartX = e.touches[0].clientX;\n           edgeTouchStartY = e.touches[0].clientY;\n          }\n         }, {\n           passive: false\n          });\n          let edgeTouchStartX = 0, edgeTouchStartY = 0;\n         polyHit.addEventListener(\"touchmove\", (e) => {\n          if (e.touches[0]) {\n           const dx = Math.abs(e.touches[0].clientX - edgeTouchStartX);\n           const dy = Math.abs(e.touches[0].clientY - edgeTouchStartY);\n           if (dx > 10 || dy > 10) edgeTouchMoved = true;\n          }\n         }, {\n           passive: false\n          });\n          polyHit.addEventListener(\"touchend\", (e) => {\n           if (edgeTouchStart && !edgeTouchMoved && Date.now() - edgeTouchStart < 400) {\n            e.stopPropagation();\n            e.preventDefault();\n            selectTheConnection(edge.id);\n           }\n           edgeTouchStart = null;\n           edgeTouchMoved = false;\n          }, {\n           passive: false\n          });\n          poly.addEventListener(\"click\", (e) => {\n          e.stopPropagation();\n          selectTheConnection(edge.id);\n         });\n         polyHit.addEventListener(\"contextmenu\", (e) => {\n          e.preventDefault();\n          e.stopPropagation();\n          if (selectedEdges.has(edge.id)) {\n           selectedEdges.delete(edge.id);\n          } else {\n           selectedEdges.add(edge.id);\n          }\n          updateAllSelections();\n         });\n         let customEdgeLastTap = 0;\n         polyHit.addEventListener(\"touchend\", (e) => {\n          const now = Date.now();\n          if (now - customEdgeLastTap < 300) {\n           e.preventDefault();\n           if (selectedEdges.has(edge.id)) {\n            selectedEdges.delete(edge.id);\n           } else {\n            selectedEdges.add(edge.id);\n           }\n           updateAllSelections();\n           if (navigator.vibrate) navigator.vibrate(50);\n           customEdgeLastTap = 0;\n          } else {\n           customEdgeLastTap = now;\n          }\n         }, { passive: false });\n         poly.addEventListener(\"contextmenu\", (e) => {\n          e.preventDefault();\n          e.stopPropagation();\n          if (selectedEdges.has(edge.id)) {\n           selectedEdges.delete(edge.id);\n          } else {\n           selectedEdges.add(edge.id);\n          }\n          updateAllSelections();\n         });\n         let edgeLastTap = 0;\n         poly.addEventListener(\"touchend\", (e) => {\n          const now = Date.now();\n          if (now - edgeLastTap < 300) {\n           e.preventDefault();\n           if (selectedEdges.has(edge.id)) {\n            selectedEdges.delete(edge.id);\n           } else {\n            selectedEdges.add(edge.id);\n           }\n           updateAllSelections();\n           if (navigator.vibrate) navigator.vibrate(50);\n           edgeLastTap = 0;\n          } else {\n           edgeLastTap = now;\n          }\n         });\n          if (currentView.mode === \"rack\") {\n           return;\n          }\n          if (edge.groupId) {\n      const bounds = edge.points.reduce((acc, p) => ({ minX: Math.min(acc.minX, p.x), minY: Math.min(acc.minY, p.y), maxX: Math.max(acc.maxX, p.x), maxY: Math.max(acc.maxY, p.y) }), { minX: Infinity, minY: Infinity, maxX: -Infinity, maxY: -Infinity });\n      const groupIndicator = document.createElementNS(ns, \"rect\");\n      groupIndicator.setAttribute(\"x\", bounds.minX - 8);\n      groupIndicator.setAttribute(\"y\", bounds.minY - 8);\n      groupIndicator.setAttribute(\"width\", bounds.maxX - bounds.minX + 16);\n      groupIndicator.setAttribute(\"height\", bounds.maxY - bounds.minY + 16);\n      groupIndicator.setAttribute(\"rx\", \"4\");\n      groupIndicator.style.fill = \"none\";\n  groupIndicator.style.stroke = PAGE_STATE.groupIndicator || \"#4fd1c5\";\n      groupIndicator.style.strokeWidth = \"3\";\n      groupIndicator.style.strokeDasharray = \"5,5\";\n      groupIndicator.style.pointerEvents = \"none\";\n      svg.appendChild(groupIndicator);\n      }\n         let lineDragging = false;\n      let lineDragStartX, lineDragStartY;\n      let linePointsStart = [];\n      polyHit.addEventListener(\"mousedown\", (e) => {\n\t  if (isViewOnly()) return;\n      if (freeDrawMode) return;\n      e.preventDefault();\n      e.stopPropagation();\n      lineDragging = true;\n      lineDragStartX = e.clientX;\n      lineDragStartY = e.clientY;\n      linePointsStart = edge.points.map(p => ({x: p.x, y: p.y}));\n      if (selectedEdges.has(edge.id)) {\n      initialPositions = {};\n      selectedNodes.forEach(nodeId => {\n      const nodePos = savedPositions[nodeId];\n      if (nodePos) initialPositions[nodeId] = { x: nodePos.x, y: nodePos.y };\n      });\n      selectedRects.forEach(rectId => {\n      const r = RECT_DATA.list.find(x => x.id === rectId);\n      if (r) { r._dragStartX = r.x; r._dragStartY = r.y; }\n      });\n      selectedTexts.forEach(textId => {\n      const t = TEXT_DATA.list.find(x => x.id === textId);\n      if (t) { t._dragStartX = t.x; t._dragStartY = t.y; }\n      });\n      selectedEdges.forEach(edgeId => {\n      const ed = EDGE_DATA.list.find(x => x.id === edgeId);\n      if (ed && ed.points) { ed._dragStartPoints = ed.points.map(p => ({x: p.x, y: p.y})); }\n      });\n      }\n      });\n      const lineMoveHandler = (e) => {\n      if (!lineDragging || freeDrawMode) return;\n      const svgEl = svg;\n      const pt1 = svgEl.createSVGPoint();\n      pt1.x = lineDragStartX;\n      pt1.y = lineDragStartY;\n      const svgP1 = pt1.matrixTransform(svgEl.getScreenCTM().inverse());\n      const pt2 = svgEl.createSVGPoint();\n      pt2.x = e.clientX;\n      pt2.y = e.clientY;\n      const svgP2 = pt2.matrixTransform(svgEl.getScreenCTM().inverse());\n      const dx = svgP2.x - svgP1.x;\n      const dy = svgP2.y - svgP1.y;\n      edge.points.forEach((p, i) => { p.x = linePointsStart[i].x + dx; p.y = linePointsStart[i].y + dy; });\n      if (selectedEdges.has(edge.id)) {\n      selectedNodes.forEach(nodeId => {\n      if (initialPositions[nodeId]) {\n       savedPositions[nodeId] = { x: initialPositions[nodeId].x + dx, y: initialPositions[nodeId].y + dy };\n      }\n      });\n      selectedRects.forEach(rectId => {\n      const r = RECT_DATA.list.find(x => x.id === rectId);\n      if (r && r._dragStartX !== undefined) { r.x = r._dragStartX + dx; r.y = r._dragStartY + dy; }\n      });\n      selectedTexts.forEach(textId => {\n      const t = TEXT_DATA.list.find(x => x.id === textId);\n      if (t && t._dragStartX !== undefined) { t.x = t._dragStartX + dx; t.y = t._dragStartY + dy; }\n      });\n      selectedEdges.forEach(edgeId => {\n      if (edgeId === edge.id) return;\n      const ed = EDGE_DATA.list.find(x => x.id === edgeId);\n      if (ed && ed._dragStartPoints) { ed.points.forEach((p, i) => { p.x = ed._dragStartPoints[i].x + dx; p.y = ed._dragStartPoints[i].y + dy; }); }\n      });\n      }\n      forgeTheTopology();\n      };\n      const lineUpHandler = () => {\n        lineDragging = false;\n        document.removeEventListener(\"mousemove\", lineMoveHandler);\n        document.removeEventListener(\"mouseup\", lineUpHandler);\n      };\n      document.addEventListener(\"mousemove\", lineMoveHandler);\n      document.addEventListener(\"mouseup\", lineUpHandler);\n      svg.appendChild(poly);\n      svg.appendChild(polyHit);\n         if (currentEdgeId === edge.id) {\n          edge.points.forEach((p, idx) => {\n           const c = document.createElementNS(ns, \"circle\");\n           c.classList.add(\"edge-edit-point\");\n           c.setAttribute(\"cx\", p.x);\n           c.setAttribute(\"cy\", p.y);\n\t\t   c.setAttribute(\"r\", PAGE_STATE.selectionHandleSize || 8);\n           c.style.fill = PAGE_STATE.selectionHandle || \"#f59e0b\";\n           c.style.stroke = \"#fff\";\n           c.style.strokeWidth = \"2\";\n           c.style.cursor = \"move\";\n           c.dataset.edgeId = edge.id;\n           c.dataset.pointIndex = String(idx);\n           c.addEventListener(\"mousedown\", (e) => {\n\t\t   if (isViewOnly()) return;\n            e.preventDefault();\n            e.stopPropagation();\n            pushUndo(\"edit edge point\");\n            let dragging = true;\n            const svgEl = svg;\n            const moveHandler = (ev) => {\n             if (!dragging) return;\n             const pt = svgEl.createSVGPoint();\n             pt.x = ev.clientX;\n             pt.y = ev.clientY;\n             const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n             edge.points[idx].x = svgP.x;\n             edge.points[idx].y = svgP.y;\n             forgeTheTopology();\n            };\n            const upHandler = () => {\n             dragging = false;\n             document.removeEventListener(\"mousemove\", moveHandler);\n             document.removeEventListener(\"mouseup\", upHandler);\n            };\n            document.addEventListener(\"mousemove\", moveHandler);\n            document.addEventListener(\"mouseup\", upHandler);\n           });\n           c.addEventListener(\"touchstart\", (e) => {\n\t\t   if (isViewOnly()) return;\n            e.preventDefault();\n            e.stopPropagation();\n            pushUndo(\"edit edge point\");\n            let dragging = true;\n            const svgEl = svg;\n            const touchMoveHandler = (ev) => {\n             if (!dragging || !ev.touches[0]) return;\n             const pt = svgEl.createSVGPoint();\n             pt.x = ev.touches[0].clientX;\n             pt.y = ev.touches[0].clientY;\n             const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n             edge.points[idx].x = svgP.x;\n             edge.points[idx].y = svgP.y;\n             forgeTheTopology();\n            };\n            const touchUpHandler = () => {\n             dragging = false;\n             document.removeEventListener(\"touchmove\", touchMoveHandler);\n             document.removeEventListener(\"touchend\", touchUpHandler);\n            };\n            document.addEventListener(\"touchmove\", touchMoveHandler);\n            document.addEventListener(\"touchend\", touchUpHandler);\n           }, { passive: false });\n           svg.appendChild(c);\n          });\n         }\n         return;\n         }\n         const p1 = positions[edge.from];\n         const p2 = positions[edge.to];\n         if (!p1 || !p2) return;\n         const edgeFaded = currentView.mode !== \"rack\" && (!isNodeVisible(edge.from) || !isNodeVisible(edge.to));\n         const pairTotal = edge._pairTotal || 1;\n         const pairIndex = edge._pairIndex || 0;\n         const routing = edge.routing || \"curved\";\n         const waypoints = edge.waypoints || [];\n         let pathD;\n         if (waypoints.length > 0) {\n          const allPoints = [p1, ...waypoints, p2];\n          if (routing === \"straight\") {\n           pathD = `M ${allPoints[0].x} ${allPoints[0].y}`;\n           for (let i = 1; i < allPoints.length; i++) {\n            pathD += ` L ${allPoints[i].x} ${allPoints[i].y}`;\n           }\n          } else if (routing === \"orthogonal\") {\n          const GAP = 10;\n          const edgeGaps = orthoGaps[edge.id] || [];\n          const segs = [];\n          for (let i = 1; i < allPoints.length; i++) {\n           const prev = allPoints[i - 1];\n           const curr = allPoints[i];\n           const dx = curr.x - prev.x;\n           const dy = curr.y - prev.y;\n           if (Math.abs(dx) > Math.abs(dy)) {\n            const midX = prev.x + dx / 2;\n            segs.push({ x1: prev.x, y1: prev.y, x2: midX, y2: prev.y });\n            segs.push({ x1: midX, y1: prev.y, x2: midX, y2: curr.y });\n            segs.push({ x1: midX, y1: curr.y, x2: curr.x, y2: curr.y });\n           } else {\n            const midY = prev.y + dy / 2;\n            segs.push({ x1: prev.x, y1: prev.y, x2: prev.x, y2: midY });\n            segs.push({ x1: prev.x, y1: midY, x2: curr.x, y2: midY });\n            segs.push({ x1: curr.x, y1: midY, x2: curr.x, y2: curr.y });\n           }\n          }\n          pathD = `M ${allPoints[0].x} ${allPoints[0].y}`;\n          segs.forEach(seg => {\n           const isHoriz = Math.abs(seg.y1 - seg.y2) < 1;\n           const segGaps = edgeGaps.filter(g => {\n            if (isHoriz) {\n             const minX = Math.min(seg.x1, seg.x2), maxX = Math.max(seg.x1, seg.x2);\n             return Math.abs(g.y - seg.y1) < 1 && g.x > minX + GAP && g.x < maxX - GAP;\n            } else {\n             const minY = Math.min(seg.y1, seg.y2), maxY = Math.max(seg.y1, seg.y2);\n             return Math.abs(g.x - seg.x1) < 1 && g.y > minY + GAP && g.y < maxY - GAP;\n            }\n           }).sort((a, b) => isHoriz ? (seg.x2 > seg.x1 ? a.x - b.x : b.x - a.x) : (seg.y2 > seg.y1 ? a.y - b.y : b.y - a.y));\n           let cx = seg.x1, cy = seg.y1;\n           segGaps.forEach(g => {\n            if (isHoriz) {\n             const gx = g.x;\n             pathD += ` L ${gx - GAP} ${cy} M ${gx + GAP} ${cy}`;\n             cx = gx + GAP;\n            } else {\n             const gy = g.y;\n             pathD += ` L ${cx} ${gy - GAP} M ${cx} ${gy + GAP}`;\n             cy = gy + GAP;\n            }\n           });\n           pathD += ` L ${seg.x2} ${seg.y2}`;\n          });\n         } else {\n           if (allPoints.length === 2) {\n            pathD = `M ${allPoints[0].x} ${allPoints[0].y} L ${allPoints[1].x} ${allPoints[1].y}`;\n           } else if (allPoints.length === 3) {\n            pathD = `M ${allPoints[0].x} ${allPoints[0].y} Q ${allPoints[1].x} ${allPoints[1].y} ${allPoints[2].x} ${allPoints[2].y}`;\n           } else {\n            pathD = `M ${allPoints[0].x} ${allPoints[0].y}`;\n            for (let i = 1; i < allPoints.length - 1; i++) {\n             const curr = allPoints[i];\n             const next = allPoints[i + 1];\n             const cpX = curr.x;\n             const cpY = curr.y;\n             const endX = (curr.x + next.x) / 2;\n             const endY = (curr.y + next.y) / 2;\n             if (i === 1) {\n              pathD += ` Q ${cpX} ${cpY} ${endX} ${endY}`;\n             } else {\n              pathD += ` T ${endX} ${endY}`;\n             }\n            }\n            const last = allPoints[allPoints.length - 1];\n            const secondLast = allPoints[allPoints.length - 2];\n            pathD += ` Q ${secondLast.x} ${secondLast.y} ${last.x} ${last.y}`;\n           }\n          }\n         } else if (routing === \"straight\") {\n          pathD = `M ${p1.x} ${p1.y} L ${p2.x} ${p2.y}`;\n         } else if (routing === \"orthogonal\") {\n         const dx = p2.x - p1.x;\n         const dy = p2.y - p1.y;\n         const offset = (pairIndex - (pairTotal - 1) / 2) * 40;\n         const GAP = 10;\n         const edgeGaps = orthoGaps[edge.id] || [];\n\n         if (Math.abs(dx) > Math.abs(dy)) {\n          const midX = p1.x + dx / 2 + offset;\n          const segs = [\n           { x1: p1.x, y1: p1.y, x2: midX, y2: p1.y },\n           { x1: midX, y1: p1.y, x2: midX, y2: p2.y },\n           { x1: midX, y1: p2.y, x2: p2.x, y2: p2.y }\n          ];\n          pathD = `M ${p1.x} ${p1.y}`;\n          segs.forEach(seg => {\n           const isHoriz = Math.abs(seg.y1 - seg.y2) < 1;\n           const segGaps = edgeGaps.filter(g => {\n            if (isHoriz) {\n             const minX = Math.min(seg.x1, seg.x2), maxX = Math.max(seg.x1, seg.x2);\n             return Math.abs(g.y - seg.y1) < 1 && g.x > minX + GAP && g.x < maxX - GAP;\n            } else {\n             const minY = Math.min(seg.y1, seg.y2), maxY = Math.max(seg.y1, seg.y2);\n             return Math.abs(g.x - seg.x1) < 1 && g.y > minY + GAP && g.y < maxY - GAP;\n            }\n           }).sort((a, b) => isHoriz ? (seg.x2 > seg.x1 ? a.x - b.x : b.x - a.x) : (seg.y2 > seg.y1 ? a.y - b.y : b.y - a.y));\n           let cx = seg.x1, cy = seg.y1;\n           segGaps.forEach(g => {\n            if (isHoriz) {\n             const gx = g.x;\n             pathD += ` L ${gx - GAP} ${cy} M ${gx + GAP} ${cy}`;\n             cx = gx + GAP;\n            } else {\n             const gy = g.y;\n             pathD += ` L ${cx} ${gy - GAP} M ${cx} ${gy + GAP}`;\n             cy = gy + GAP;\n            }\n           });\n           pathD += ` L ${seg.x2} ${seg.y2}`;\n          });\n         } else {\n          const midY = p1.y + dy / 2 + offset;\n          const segs = [\n           { x1: p1.x, y1: p1.y, x2: p1.x, y2: midY },\n           { x1: p1.x, y1: midY, x2: p2.x, y2: midY },\n           { x1: p2.x, y1: midY, x2: p2.x, y2: p2.y }\n          ];\n          pathD = `M ${p1.x} ${p1.y}`;\n          segs.forEach(seg => {\n           const isHoriz = Math.abs(seg.y1 - seg.y2) < 1;\n           const segGaps = edgeGaps.filter(g => {\n            if (isHoriz) {\n             const minX = Math.min(seg.x1, seg.x2), maxX = Math.max(seg.x1, seg.x2);\n             return Math.abs(g.y - seg.y1) < 1 && g.x > minX + GAP && g.x < maxX - GAP;\n            } else {\n             const minY = Math.min(seg.y1, seg.y2), maxY = Math.max(seg.y1, seg.y2);\n             return Math.abs(g.x - seg.x1) < 1 && g.y > minY + GAP && g.y < maxY - GAP;\n            }\n           }).sort((a, b) => isHoriz ? (seg.x2 > seg.x1 ? a.x - b.x : b.x - a.x) : (seg.y2 > seg.y1 ? a.y - b.y : b.y - a.y));\n           let cx = seg.x1, cy = seg.y1;\n           segGaps.forEach(g => {\n            if (isHoriz) {\n             const gx = g.x;\n             pathD += ` L ${gx - GAP} ${cy} M ${gx + GAP} ${cy}`;\n             cx = gx + GAP;\n            } else {\n             const gy = g.y;\n             pathD += ` L ${cx} ${gy - GAP} M ${cx} ${gy + GAP}`;\n             cy = gy + GAP;\n            }\n           });\n           pathD += ` L ${seg.x2} ${seg.y2}`;\n          });\n         }\n        } else {\n          const midX = (p1.x + p2.x) / 2;\n          const midY = (p1.y + p2.y) / 2;\n          const dx = p2.x - p1.x;\n          const dy = p2.y - p1.y;\n          const len = Math.sqrt(dx * dx + dy * dy) || 1;\n          const perpX = -dy / len;\n          const perpY = dx / len;\n          let offsetAmount = 0;\n          if (pairTotal > 1) {\n           offsetAmount = (pairIndex - (pairTotal - 1) / 2) * 40;\n          }\n          const ctrlX = midX + perpX * offsetAmount;\n          const ctrlY = midY + perpY * offsetAmount;\n          pathD = `M ${p1.x} ${p1.y} Q ${ctrlX} ${ctrlY} ${p2.x} ${p2.y}`;\n         }\n         const path = document.createElementNS(ns, \"path\");\n         path.setAttribute(\"d\", pathD);\n         path.setAttribute(\"fill\", \"none\");\n         path.classList.add(\"edge\");\n         if (edgeFaded) {\n          path.style.opacity = \"0.25\";\n          path.classList.add(\"layer-faded\");\n         }\n         if (edge.type === \"backup\") path.classList.add(\"backup\");\n         path.dataset.edgeId = edge.id;\n         path.dataset.from = edge.from;\n         path.dataset.to = edge.to;\n         path.style.stroke = edge.color;\n         path.style.strokeWidth = edge.width;\n         const edgeDirection = edge.direction || \"none\";\n         const edgeLineStyle = edge.lineStyle || \"solid\";\n         if (edgeLineStyle === \"dashed\") { path.style.strokeDasharray = \"10,5\"; }\n         else if (edgeLineStyle === \"dotted\") { path.style.strokeDasharray = \"2,4\"; }\n         if (edgeDirection === \"forward\") {\n          path.setAttribute(\"marker-end\", \"url(#arrow-forward)\");\n         } else if (edgeDirection === \"backward\") {\n          path.setAttribute(\"marker-start\", \"url(#arrow-backward)\");\n         } else if (edgeDirection === \"both\") {\n          path.setAttribute(\"marker-start\", \"url(#arrow-backward)\");\n          path.setAttribute(\"marker-end\", \"url(#arrow-forward)\");\n         }\n         const animDir = PAGE_STATE.animationDirection || \"all\";\n         const shouldAnimate = (PAGE_STATE.animateConnections && edge.animate !== false || edge.animate === true) && edgeDirection !== \"none\" && (animDir === \"all\" || animDir === edgeDirection);\n         if (shouldAnimate && !edgeFaded) {\n          path.style.opacity = \"0.25\";\n          const edgeStyle = edge.animationStyle || PAGE_STATE.animationStyle || \"arrows\";\n          const arrowId = edgeStyle === \"dots\" ? \"#flow-arrow-small\" : \"#flow-arrow-big\";\n          const arrowColor = edge.color || PAGE_STATE.defaultEdge || \"#475569\";\n          const arrowCount = 3;\n          const animDuration = edge.animationSpeed ? parseFloat(edge.animationSpeed) : (PAGE_STATE.animationSpeed || 1.5);\n          if (edgeDirection === \"forward\" || edgeDirection === \"both\") {\n           for (let i = 0; i < arrowCount; i++) {\n            const arrow = document.createElementNS(ns, \"use\");\n            arrow.setAttribute(\"href\", arrowId);\n            arrow.style.fill = arrowColor;\n            arrow.style.offsetPath = `path('${pathD}')`;\n            arrow.style.animationDuration = animDuration + \"s\";\n            arrow.style.animationDelay = (i * (animDuration / arrowCount)) + \"s\";\n            arrow.classList.add(\"edge-arrow-forward\");\n            svg.appendChild(arrow);\n           }\n          }\n          if (edgeDirection === \"backward\" || edgeDirection === \"both\") {\n           for (let i = 0; i < arrowCount; i++) {\n            const arrow = document.createElementNS(ns, \"use\");\n            arrow.setAttribute(\"href\", arrowId);\n            arrow.style.fill = arrowColor;\n            arrow.style.offsetPath = `path('${pathD}')`;\n            arrow.style.animationDuration = animDuration + \"s\";\n            arrow.style.animationDelay = ((i * (animDuration / arrowCount)) + (edgeDirection === \"both\" ? animDuration / (arrowCount * 2) : 0)) + \"s\";\n            arrow.classList.add(\"edge-arrow-backward\");\n            svg.appendChild(arrow);\n           }\n          }\n         }\n         const pathHit = document.createElementNS(ns, \"path\");\n         pathHit.setAttribute(\"d\", pathD);\n         pathHit.setAttribute(\"fill\", \"none\");\n         pathHit.style.stroke = \"transparent\";\n         pathHit.style.strokeWidth = \"20\";\n         pathHit.style.cursor = \"pointer\";\n         pathHit.dataset.edgeId = edge.id;\n         pathHit.addEventListener(\"click\", (e) => {\n          e.stopPropagation();\n          selectTheConnection(edge.id);\n         });\n         let pathTouchStart = null;\n         let pathTouchMoved = false;\n         pathHit.addEventListener(\"touchstart\", (e) => {\n         pathTouchStart = Date.now();\n         pathTouchMoved = false;\n         if (e.touches[0]) {\n          pathTouchStartX = e.touches[0].clientX;\n          pathTouchStartY = e.touches[0].clientY;\n         }\n        }, {\n          passive: false\n         });\n         let pathTouchStartX = 0, pathTouchStartY = 0;\n        pathHit.addEventListener(\"touchmove\", (e) => {\n         if (e.touches[0]) {\n          const dx = Math.abs(e.touches[0].clientX - pathTouchStartX);\n          const dy = Math.abs(e.touches[0].clientY - pathTouchStartY);\n          if (dx > 10 || dy > 10) pathTouchMoved = true;\n         }\n        }, {\n          passive: false\n         });\n         pathHit.addEventListener(\"touchend\", (e) => {\n          if (pathTouchStart && !pathTouchMoved && Date.now() - pathTouchStart < 400) {\n           e.stopPropagation();\n           e.preventDefault();\n           selectTheConnection(edge.id);\n          }\n          pathTouchStart = null;\n          pathTouchMoved = false;\n         }, {\n          passive: false\n         });\n         path.addEventListener(\"click\", (e) => {\n          e.stopPropagation();\n          selectTheConnection(edge.id);\n         });\n         svg.appendChild(path);\n         svg.appendChild(pathHit);\n         if (PAGE_STATE.showPortLabels && (edge.fromPort || edge.toPort)) {\n          const plNs = \"http://www.w3.org/2000/svg\";\n          const plWaypoints = edge.waypoints || [];\n          const plPad = { x: 5, y: 3 };\n          function plHitsAny(px, py, halfW, halfH) {\n           for (const nid in positions) {\n            const np = positions[nid];\n            if (!np) continue;\n            const nr = (savedSizes[nid] || 55) + 8;\n            const nd = NODE_DATA[nid];\n            const tFs = PAGE_STATE.nodeTitleSize || 18;\n            const sFs = PAGE_STATE.nodeSubSize || 13;\n            if (px + halfW > np.x - nr && px - halfW < np.x + nr && py + halfH > np.y - nr && py - halfH < np.y + nr) return true;\n            if (nd && nd.name) {\n             const tw = (nd.name.length * tFs * 0.38) + 6;\n             const ty = np.y - nr - 5 - tFs / 2;\n             const th = tFs / 2 + 6;\n             if (px + halfW > np.x - tw && px - halfW < np.x + tw && py + halfH > ty - th && py - halfH < ty + th) return true;\n            }\n            if (nd && nd.subtitle) {\n             const sw = (nd.subtitle.length * sFs * 0.38) + 6;\n             const sy = np.y + nr + 5 + sFs / 2;\n             const sh = sFs / 2 + 6;\n             if (px + halfW > np.x - sw && px - halfW < np.x + sw && py + halfH > sy - sh && py - halfH < sy + sh) return true;\n            }\n           }\n           for (const pl of _placedPortLabels) {\n            if (Math.abs(px - pl.x) < (halfW + pl.hw + 4) && Math.abs(py - pl.y) < (halfH + pl.hh + 4)) return true;\n           }\n           return false;\n          }\n          function plFindClear(origin, target, nodeId, fromEnd) {\n           const wps = fromEnd ? plWaypoints : [...plWaypoints].reverse();\n           const pts = [origin, ...wps, target];\n           const estW = 40, estH = 12;\n           for (let t = 0.12; t <= 0.5; t += 0.04) {\n            const totalLen = pts.reduce((s, pt, i) => i === 0 ? 0 : s + Math.sqrt((pt.x - pts[i-1].x)**2 + (pt.y - pts[i-1].y)**2), 0);\n            let target_d = totalLen * t, accum = 0;\n            for (let i = 1; i < pts.length; i++) {\n             const dx = pts[i].x - pts[i-1].x, dy = pts[i].y - pts[i-1].y;\n             const segLen = Math.sqrt(dx*dx + dy*dy);\n             if (segLen === 0) continue;\n             if (accum + segLen >= target_d) {\n              const frac = (target_d - accum) / segLen;\n              const cx = pts[i-1].x + dx * frac, cy = pts[i-1].y + dy * frac;\n              const fHitsOnPath = plHitsAny(cx, cy, estW, estH);\n              if (!fHitsOnPath) return { x: cx, y: cy };\n              const nx = -dy / segLen, ny = dx / segLen;\n              for (const off of [22, -22, 36, -36]) {\n               const ox = cx + nx * off, oy = cy + ny * off;\n               if (!plHitsAny(ox, oy, estW, estH)) return { x: ox, y: oy };\n              }\n             }\n             accum += segLen;\n            }\n           }\n           const mx = (origin.x + target.x) / 2, my = (origin.y + target.y) / 2;\n           const dx = target.x - origin.x, dy = target.y - origin.y;\n           const fLen = Math.sqrt(dx*dx + dy*dy) || 1;\n           const perpX = -dy / fLen, perpY = dx / fLen;\n           for (const fo of [0, 20, -20, 40, -40]) {\n            const fx = mx + perpX * fo, fy = my - 15 + perpY * fo;\n            if (!plHitsAny(fx, fy, 40, 12)) return { x: fx, y: fy };\n           }\n           return { x: mx, y: my - 15 };\n          }\n          function plRenderLabel(text, pos) {\n           const g = document.createElementNS(plNs, \"g\");\n           g.classList.add(\"port-label\");\n           g.style.pointerEvents = \"none\";\n           const tmp = document.createElementNS(plNs, \"text\");\n           tmp.textContent = text;\n           tmp.setAttribute(\"x\", pos.x);\n           tmp.setAttribute(\"y\", pos.y);\n           tmp.setAttribute(\"text-anchor\", \"middle\");\n           tmp.setAttribute(\"dominant-baseline\", \"central\");\n           tmp.style.fontSize = \"13px\";\n           tmp.style.fontFamily = \"Inter, system-ui, sans-serif\";\n           tmp.style.fontWeight = \"600\";\n           svg.appendChild(tmp);\n           const bbox = tmp.getBBox();\n           tmp.remove();\n           const bg = document.createElementNS(plNs, \"rect\");\n           bg.setAttribute(\"x\", bbox.x - plPad.x);\n           bg.setAttribute(\"y\", bbox.y - plPad.y);\n           bg.setAttribute(\"width\", bbox.width + plPad.x * 2);\n           bg.setAttribute(\"height\", bbox.height + plPad.y * 2);\n           bg.setAttribute(\"rx\", 4);\n           bg.setAttribute(\"ry\", 4);\n           bg.style.fill = \"rgba(15, 23, 42, 0.88)\";\n           bg.style.stroke = edge.color || PAGE_STATE.defaultEdge || \"#475569\";\n           bg.style.strokeWidth = \"1\";\n           bg.style.strokeOpacity = \"0.5\";\n           g.appendChild(bg);\n           const label = document.createElementNS(plNs, \"text\");\n           label.textContent = text;\n           label.setAttribute(\"x\", pos.x);\n           label.setAttribute(\"y\", pos.y);\n           label.setAttribute(\"text-anchor\", \"middle\");\n           label.setAttribute(\"dominant-baseline\", \"central\");\n           label.style.fill = \"#e2e8f0\";\n           label.style.fontSize = \"13px\";\n           label.style.fontFamily = \"Inter, system-ui, sans-serif\";\n           label.style.fontWeight = \"600\";\n           label.style.pointerEvents = \"none\";\n           g.appendChild(label);\n           svg.appendChild(g);\n           _placedPortLabels.push({ x: pos.x, y: pos.y, hw: (bbox.width / 2) + plPad.x, hh: (bbox.height / 2) + plPad.y });\n          }\n          if (edge.fromPort) {\n           const fromPos = plFindClear(p1, p2, edge.from, true);\n           plRenderLabel(edge.fromPort, fromPos);\n          }\n          if (edge.toPort) {\n           const toPos = plFindClear(p2, p1, edge.to, false);\n           plRenderLabel(edge.toPort, toPos);\n          }\n         }\n         if (currentEdgeId === edge.id && !isViewOnly()) {\n          const waypoints = edge.waypoints || [];\n          waypoints.forEach((wp, idx) => {\n           const c = document.createElementNS(ns, \"circle\");\n           c.classList.add(\"waypoint-handle\");\n           c.setAttribute(\"cx\", wp.x);\n           c.setAttribute(\"cy\", wp.y);\n           c.setAttribute(\"r\", 8);\n           c.style.fill = \"#3b82f6\";\n           c.style.stroke = \"#fff\";\n           c.style.strokeWidth = \"2\";\n           c.style.cursor = \"grab\";\n           c.addEventListener(\"mousedown\", (e) => {\n            if (isViewOnly()) return;\n            e.preventDefault();\n            e.stopPropagation();\n            pushUndo(\"move waypoint\");\n            let dragging = true;\n            const svgEl = svg;\n            c.style.cursor = \"grabbing\";\n            const moveHandler = (ev) => {\n             if (!dragging) return;\n             const pt = svgEl.createSVGPoint();\n             pt.x = ev.clientX;\n             pt.y = ev.clientY;\n             const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n             edge.waypoints[idx].x = svgP.x;\n             edge.waypoints[idx].y = svgP.y;\n             forgeTheTopology();\n            };\n            const upHandler = () => {\n             dragging = false;\n             c.style.cursor = \"grab\";\n             document.removeEventListener(\"mousemove\", moveHandler);\n             document.removeEventListener(\"mouseup\", upHandler);\n            };\n            document.addEventListener(\"mousemove\", moveHandler);\n            document.addEventListener(\"mouseup\", upHandler);\n           });\n           let waypointMoved = false;\n           c.addEventListener(\"touchstart\", (e) => {\n            if (isViewOnly()) return;\n            e.preventDefault();\n            e.stopPropagation();\n            waypointMoved = false;\n            pushUndo(\"move waypoint\");\n            let dragging = true;\n            const svgEl = svg;\n            const touchMoveHandler = (ev) => {\n             if (!dragging || !ev.touches[0]) return;\n             waypointMoved = true;\n             const pt = svgEl.createSVGPoint();\n             pt.x = ev.touches[0].clientX;\n             pt.y = ev.touches[0].clientY;\n             const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n             edge.waypoints[idx].x = svgP.x;\n             edge.waypoints[idx].y = svgP.y;\n             forgeTheTopology();\n            };\n            const touchUpHandler = () => {\n             dragging = false;\n             document.removeEventListener(\"touchmove\", touchMoveHandler);\n             document.removeEventListener(\"touchend\", touchUpHandler);\n            };\n            document.addEventListener(\"touchmove\", touchMoveHandler);\n            document.addEventListener(\"touchend\", touchUpHandler);\n           }, { passive: false });\n           c.addEventListener(\"contextmenu\", (e) => {\n            if (isViewOnly()) return;\n            e.preventDefault();\n            e.stopPropagation();\n            pushUndo(\"delete waypoint\");\n            edge.waypoints.splice(idx, 1);\n            forgeTheTopology();\n           });\n           let lastTap = 0;\n           c.addEventListener(\"touchend\", (e) => {\n            if (waypointMoved) {\n             waypointMoved = false;\n             lastTap = 0;\n             return;\n            }\n            const now = Date.now();\n            if (now - lastTap < 300) {\n             if (isViewOnly()) return;\n             e.preventDefault();\n             pushUndo(\"delete waypoint\");\n             edge.waypoints.splice(idx, 1);\n             forgeTheTopology();\n            }\n            lastTap = now;\n           });\n           svg.appendChild(c);\n          });\n         }\n         pathHit.addEventListener(\"dblclick\", (e) => {\n          if (isViewOnly()) return;\n          e.stopPropagation();\n          const svgEl = svg;\n          const pt = svgEl.createSVGPoint();\n          pt.x = e.clientX;\n          pt.y = e.clientY;\n          const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n          pushUndo(\"add waypoint\");\n          if (!edge.waypoints) edge.waypoints = [];\n          const newWp = { x: svgP.x, y: svgP.y };\n          if (edge.waypoints.length === 0) {\n           edge.waypoints.push(newWp);\n          } else {\n           const allPts = [p1, ...edge.waypoints, p2];\n           let bestIdx = 0;\n           let bestDist = Infinity;\n           for (let i = 0; i < allPts.length - 1; i++) {\n            const segMidX = (allPts[i].x + allPts[i + 1].x) / 2;\n            const segMidY = (allPts[i].y + allPts[i + 1].y) / 2;\n            const dist = Math.hypot(svgP.x - segMidX, svgP.y - segMidY);\n            if (dist < bestDist) {\n             bestDist = dist;\n             bestIdx = i;\n            }\n           }\n           edge.waypoints.splice(bestIdx, 0, newWp);\n          }\n          selectTheConnection(edge.id);\n          forgeTheTopology();\n         });\n         let longPressTimer = null;\n         let longPressTriggered = false;\n         pathHit.addEventListener(\"touchstart\", (e) => {\n          if (isViewOnly() || !e.touches[0]) return;\n          longPressTriggered = false;\n          const touch = e.touches[0];\n          const startX = touch.clientX;\n          const startY = touch.clientY;\n          longPressTimer = setTimeout(() => {\n           longPressTriggered = true;\n           if (navigator.vibrate) navigator.vibrate(50);\n           const svgEl = svg;\n           const pt = svgEl.createSVGPoint();\n           pt.x = startX;\n           pt.y = startY;\n           const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n           pushUndo(\"add waypoint\");\n           if (!edge.waypoints) edge.waypoints = [];\n           const newWp = { x: svgP.x, y: svgP.y };\n           if (edge.waypoints.length === 0) {\n            edge.waypoints.push(newWp);\n           } else {\n            const allPts = [p1, ...edge.waypoints, p2];\n            let bestIdx = 0;\n            let bestDist = Infinity;\n            for (let i = 0; i < allPts.length - 1; i++) {\n             const segMidX = (allPts[i].x + allPts[i + 1].x) / 2;\n             const segMidY = (allPts[i].y + allPts[i + 1].y) / 2;\n             const dist = Math.hypot(svgP.x - segMidX, svgP.y - segMidY);\n             if (dist < bestDist) {\n              bestDist = dist;\n              bestIdx = i;\n             }\n            }\n            edge.waypoints.splice(bestIdx, 0, newWp);\n           }\n           selectTheConnection(edge.id);\n           forgeTheTopology();\n          }, 500);\n         }, { passive: true });\n         pathHit.addEventListener(\"touchmove\", () => {\n          if (longPressTimer) {\n           clearTimeout(longPressTimer);\n           longPressTimer = null;\n          }\n         }, { passive: true });\n         pathHit.addEventListener(\"touchend\", () => {\n          if (longPressTimer) {\n           clearTimeout(longPressTimer);\n           longPressTimer = null;\n          }\n         }, { passive: true });\n        });\n        Object.entries(positions).forEach(([id, pos]) => {\n         const node = NODE_DATA[id];\n         if (!node) return;\n         if (currentView.mode === \"rack\") {\n          if (node.assignedRack !== currentView.rackId) return;\n         } else {\n          if (node.assignedRack) return;\n         }\n         const g = document.createElementNS(\"http://www.w3.org/2000/svg\", \"g\");\n         g.classList.add(\"node-group\");\n         g.dataset.nodeId = id;\n         const nodeRotation = NODE_DATA[id].rotation || 0;\n         g.setAttribute(\"transform\", `translate(${pos.x},${pos.y}) rotate(${nodeRotation})`);\n         if (currentView.mode !== \"rack\" && !isNodeVisible(id)) {\n          g.style.opacity = \"0.25\";\n          g.classList.add(\"layer-faded\");\n         }\n         let r = savedSizes[id] || 55;\n\t\tif (!savedSizes[id]) {\n\t\t if (window.innerWidth <= 480) r = 45;\n\t\t else if (window.innerWidth <= 768) r = 50;\n\t\t}\n         const styles = resolveStylesForNode(id);\n         const ns = \"http://www.w3.org/2000/svg\";\n         const hitArea = document.createElementNS(ns, \"circle\");\n         hitArea.setAttribute(\"r\", r * 1.5);\n         hitArea.style.fill = \"transparent\";\n         hitArea.style.stroke = \"none\";\n         hitArea.style.cursor = \"grab\";\n         hitArea.classList.add(\"node-hit-area\");\n         const shapeEl = createNodeShape(id, r);\n         const titleOffsetX = styles.titleOffsetX || 0;\n         const titleOffsetY = styles.titleOffsetY || 0;\n         const subOffsetX = styles.subOffsetX || 0;\n         const subOffsetY = styles.subOffsetY || 0;\n         const label = document.createElementNS(\"http://www.w3.org/2000/svg\", \"text\");\n         label.classList.add(\"node-label\");\n         label.setAttribute(\"x\", titleOffsetX);\n         label.setAttribute(\"y\", -r - 5 + titleOffsetY);\n         const labelFontSize = styles.titleSize || PAGE_STATE.nodeTitleSize || r * 0.33;\n        label.style.fontSize = labelFontSize + \"px\";\n         label.textContent = NODE_DATA[id].name;\n\t\tlabel.style.fill = styles.titleColor || PAGE_STATE.nodeTitle || \"#e2e8f0\";\n        label.style.fontFamily = styles.titleFont || PAGE_STATE.nodeFont || \"Inter, system-ui, sans-serif\";\n         label.style.pointerEvents = \"none\";\n         const sub = document.createElementNS(\"http://www.w3.org/2000/svg\", \"text\");\n         sub.classList.add(\"node-sub\");\n         sub.setAttribute(\"x\", subOffsetX);\n         sub.setAttribute(\"y\", r + 20 + subOffsetY);\n         const subFontSize = styles.subSize || PAGE_STATE.nodeSubSize || r * 0.24;\n        sub.style.fontSize = subFontSize + \"px\";\n         sub.textContent = NODE_DATA[id].ip;\n\t\tsub.style.fill = styles.subColor || PAGE_STATE.nodeSub || \"#94a3b8\";\n        sub.style.fontFamily = styles.subFont || PAGE_STATE.nodeFont || \"Inter, system-ui, sans-serif\";\n         sub.style.pointerEvents = \"none\";\n         if (hasCoverageZone(node.shape) && node.fovEnabled) {\n           const defaults = getCoverageDefaults(node.shape);\n           const fovAngle = node.fovAngle || defaults.angle;\n           const fovDistance = node.fovDistance || defaults.distance;\n           const fovInnerRadius = node.fovInnerRadius || 0;\n           const fovRotation = node.fovRotation || 0;\n           const fovColor = node.fovColor || \"#f59e0b\";\n           const fovOpacity = node.fovOpacity || 20;\n           const fovGradient = node.fovGradient || false;\n           const fovBorderColor = node.fovBorderColor || \"#f59e0b\";\n           const fovBorderWidth = node.fovBorderWidth ?? 2;\n           const fovBorderStyle = node.fovBorderStyle || \"solid\";\n           const fovBorderOpacity = node.fovBorderOpacity ?? 100;\n           const fovLabel = node.fovLabel || \"\";\n           const fovAnimate = node.fovAnimate || false;\n           const fovAnimationType = node.fovAnimationType || defaults.animationType;\n           const fovSweep = node.fovSweep || 120;\n           const fovSpeed = node.fovSpeed || 4;\n           \n           const fovGroup = document.createElementNS(ns, \"g\");\n           fovGroup.classList.add(\"fov-group\");\n           if (!ZONES_VISIBLE) fovGroup.style.display = \"none\";\n           \n           if (fovGradient) {\n             const gradientId = `fov-gradient-${id}`;\n             const defs = document.createElementNS(ns, \"defs\");\n             const gradient = document.createElementNS(ns, \"radialGradient\");\n             gradient.id = gradientId;\n             gradient.setAttribute(\"cx\", \"0\");\n             gradient.setAttribute(\"cy\", \"0\");\n             gradient.setAttribute(\"r\", fovDistance);\n             gradient.setAttribute(\"gradientUnits\", \"userSpaceOnUse\");\n             const stop1 = document.createElementNS(ns, \"stop\");\n             stop1.setAttribute(\"offset\", fovInnerRadius / fovDistance);\n             stop1.setAttribute(\"stop-color\", fovColor);\n             stop1.setAttribute(\"stop-opacity\", fovOpacity / 100);\n             const stop2 = document.createElementNS(ns, \"stop\");\n             stop2.setAttribute(\"offset\", \"1\");\n             stop2.setAttribute(\"stop-color\", fovColor);\n             stop2.setAttribute(\"stop-opacity\", \"0\");\n             gradient.appendChild(stop1);\n             gradient.appendChild(stop2);\n             defs.appendChild(gradient);\n             fovGroup.appendChild(defs);\n           }\n           \n           const fovPath = document.createElementNS(ns, \"path\");\n           \n           if (fovAngle >= 360) {\n             if (fovInnerRadius > 0) {\n               fovPath.setAttribute(\"d\", `M ${fovDistance},0 A ${fovDistance},${fovDistance} 0 1,1 ${-fovDistance},0 A ${fovDistance},${fovDistance} 0 1,1 ${fovDistance},0 M ${fovInnerRadius},0 A ${fovInnerRadius},${fovInnerRadius} 0 1,0 ${-fovInnerRadius},0 A ${fovInnerRadius},${fovInnerRadius} 0 1,0 ${fovInnerRadius},0`);\n               fovPath.setAttribute(\"fill-rule\", \"evenodd\");\n             } else {\n               fovPath.setAttribute(\"d\", `M 0,0 m -${fovDistance},0 a ${fovDistance},${fovDistance} 0 1,0 ${fovDistance * 2},0 a ${fovDistance},${fovDistance} 0 1,0 -${fovDistance * 2},0`);\n             }\n           } else {\n             const angleRad = (fovAngle * Math.PI) / 180;\n             const rotationRad = ((fovRotation - 90) * Math.PI) / 180;\n             const startAngle = rotationRad - angleRad / 2;\n             const endAngle = rotationRad + angleRad / 2;\n             const x1 = Math.cos(startAngle) * fovDistance;\n             const y1 = Math.sin(startAngle) * fovDistance;\n             const x2 = Math.cos(endAngle) * fovDistance;\n             const y2 = Math.sin(endAngle) * fovDistance;\n             const largeArc = fovAngle > 180 ? 1 : 0;\n             if (fovInnerRadius > 0) {\n               const ix1 = Math.cos(startAngle) * fovInnerRadius;\n               const iy1 = Math.sin(startAngle) * fovInnerRadius;\n               const ix2 = Math.cos(endAngle) * fovInnerRadius;\n               const iy2 = Math.sin(endAngle) * fovInnerRadius;\n               fovPath.setAttribute(\"d\", `M ${ix1},${iy1} L ${x1},${y1} A ${fovDistance},${fovDistance} 0 ${largeArc},1 ${x2},${y2} L ${ix2},${iy2} A ${fovInnerRadius},${fovInnerRadius} 0 ${largeArc},0 ${ix1},${iy1} Z`);\n             } else {\n               fovPath.setAttribute(\"d\", `M 0,0 L ${x1},${y1} A ${fovDistance},${fovDistance} 0 ${largeArc},1 ${x2},${y2} Z`);\n             }\n           }\n           \n           if (fovGradient) {\n             fovPath.style.fill = `url(#fov-gradient-${id})`;\n           } else {\n             const opacityHex = Math.round((fovOpacity / 100) * 255).toString(16).padStart(2, '0');\n             fovPath.style.fill = fovColor + opacityHex;\n           }\n           \n           const borderOpacityHex = Math.round((fovBorderOpacity / 100) * 255).toString(16).padStart(2, '0');\n           fovPath.style.stroke = fovBorderColor + borderOpacityHex;\n           fovPath.style.strokeWidth = fovBorderWidth;\n           if (fovBorderStyle === \"dashed\") {\n             fovPath.style.strokeDasharray = \"10,5\";\n           } else if (fovBorderStyle === \"dotted\") {\n             fovPath.style.strokeDasharray = \"3,3\";\n           }\n           fovPath.style.pointerEvents = \"none\";\n           fovPath.classList.add(\"fov-cone\");\n           \n           fovGroup.appendChild(fovPath);\n           \n           if (fovLabel) {\n             const fovLabelPosition = node.fovLabelPosition || \"center\";\n             const fovLabelSize = node.fovLabelSize || 14;\n             const fovLabelColor = node.fovLabelColor || \"#ffffff\";\n             const fovLabelBold = node.fovLabelBold || false;\n             const fovLabelBg = node.fovLabelBg || false;\n             const fovLabelBgColor = node.fovLabelBgColor || \"#000000\";\n             let labelDistance;\n             if (fovLabelPosition === \"center\") {\n               labelDistance = fovAngle >= 360 ? 0 : fovDistance * 0.4;\n             } else if (fovLabelPosition === \"edge\") {\n               labelDistance = fovDistance * 0.75;\n             } else {\n               labelDistance = fovDistance + fovLabelSize + 8;\n             }\n             const labelAngle = ((fovRotation - 90) * Math.PI) / 180;\n             const fovLabelOffsetX = node.fovLabelOffsetX || 0;\n             const fovLabelOffsetY = node.fovLabelOffsetY || 0;\n             const labelX = (fovAngle >= 360 && fovLabelPosition === \"center\" ? 0 : Math.cos(labelAngle) * labelDistance) + fovLabelOffsetX;\n             const labelY = (fovAngle >= 360 && fovLabelPosition === \"center\" ? 0 : Math.sin(labelAngle) * labelDistance) + fovLabelOffsetY;\n             if (fovLabelBg) {\n               const bgRect = document.createElementNS(ns, \"rect\");\n               const textWidth = fovLabel.length * fovLabelSize * 0.6;\n               const textHeight = fovLabelSize * 1.4;\n               bgRect.setAttribute(\"x\", labelX - textWidth / 2 - 6);\n               bgRect.setAttribute(\"y\", labelY - textHeight / 2);\n               bgRect.setAttribute(\"width\", textWidth + 12);\n               bgRect.setAttribute(\"height\", textHeight);\n               bgRect.setAttribute(\"rx\", \"4\");\n               bgRect.style.fill = fovLabelBgColor;\n               bgRect.style.opacity = \"0.8\";\n               bgRect.style.pointerEvents = \"none\";\n               fovGroup.appendChild(bgRect);\n             }\n             const labelEl = document.createElementNS(ns, \"text\");\n             labelEl.setAttribute(\"x\", labelX);\n             labelEl.setAttribute(\"y\", labelY);\n             labelEl.setAttribute(\"text-anchor\", \"middle\");\n             labelEl.setAttribute(\"dominant-baseline\", \"middle\");\n             labelEl.style.fill = fovLabelColor;\n             labelEl.style.fontSize = fovLabelSize + \"px\";\n             labelEl.style.fontWeight = fovLabelBold ? \"bold\" : \"normal\";\n             labelEl.style.fontFamily = \"system-ui, sans-serif\";\n             labelEl.style.pointerEvents = \"none\";\n             labelEl.textContent = fovLabel;\n             fovGroup.appendChild(labelEl);\n           }\n           \n           if (fovAnimate) {\n             const animationName = `fov-anim-${id}`;\n             const styleEl = document.createElementNS(\"http://www.w3.org/1999/xhtml\", \"style\");\n             \n             if (fovAnimationType === \"sweep\" && fovAngle < 360) {\n               styleEl.textContent = `\n                 @keyframes ${animationName} {\n                   0%, 100% { transform: rotate(0deg); }\n                   50% { transform: rotate(${fovSweep}deg); }\n                 }\n               `;\n               fovGroup.style.animation = `${animationName} ${fovSpeed}s ease-in-out infinite`;\n               fovGroup.style.transformOrigin = \"0 0\";\n             } else if (fovAnimationType === \"pulse\") {\n               styleEl.textContent = `\n                 @keyframes ${animationName} {\n                   0%, 100% { transform: scale(1); opacity: 1; }\n                   50% { transform: scale(1.1); opacity: 0.7; }\n                 }\n               `;\n               fovGroup.style.animation = `${animationName} ${fovSpeed}s ease-in-out infinite`;\n               fovGroup.style.transformOrigin = \"0 0\";\n             } else if (fovAnimationType === \"rings\") {\n               for (let i = 1; i <= 3; i++) {\n                 const ring = document.createElementNS(ns, \"circle\");\n                 ring.setAttribute(\"cx\", \"0\");\n                 ring.setAttribute(\"cy\", \"0\");\n                 ring.setAttribute(\"r\", fovDistance * 0.3 * i);\n                 ring.style.fill = \"none\";\n                 ring.style.stroke = fovBorderColor;\n                 ring.style.strokeWidth = \"2\";\n                 ring.style.opacity = \"0\";\n                 const ringAnimName = `${animationName}-ring-${i}`;\n                 const ringStyle = document.createElementNS(\"http://www.w3.org/1999/xhtml\", \"style\");\n                 ringStyle.textContent = `\n                   @keyframes ${ringAnimName} {\n                     0% { r: ${fovInnerRadius || 10}; opacity: 0.8; }\n                     100% { r: ${fovDistance}; opacity: 0; }\n                   }\n                 `;\n                 ring.style.animation = `${ringAnimName} ${fovSpeed}s ease-out infinite`;\n                 ring.style.animationDelay = `${(i - 1) * (fovSpeed / 3)}s`;\n                 fovGroup.appendChild(ringStyle);\n                 fovGroup.appendChild(ring);\n               }\n             } else if (fovAnimationType === \"spin\") {\n               styleEl.textContent = `\n                 @keyframes ${animationName} {\n                   0% { transform: rotate(0deg); }\n                   100% { transform: rotate(360deg); }\n                 }\n               `;\n               fovGroup.style.animation = `${animationName} ${fovSpeed}s linear infinite`;\n               fovGroup.style.transformOrigin = \"0 0\";\n             }\n             \n             if (fovAnimationType !== \"rings\") {\n               fovGroup.appendChild(styleEl);\n               const elapsedSeconds = (Date.now() - FOV_ANIMATION_START) / 1000;\n               const animationOffset = elapsedSeconds % fovSpeed;\n               fovGroup.style.animationDelay = `-${animationOffset}s`;\n             }\n           }\n           \n           g.appendChild(fovGroup);\n         }\n        g.append(hitArea, shapeEl, label);\n        if (NODE_DATA[id].ip && NODE_DATA[id].ip !== \"0.0.0.0\") g.appendChild(sub);\n         if (NODE_DATA[id]?.locked) {\n           const lockIndicator = document.createElementNS(ns, \"text\");\n           lockIndicator.textContent = \"🔒\";\n           lockIndicator.setAttribute(\"x\", r * 0.7);\n           lockIndicator.setAttribute(\"y\", -r * 0.7);\n           lockIndicator.style.fontSize = (r * 0.3) + \"px\";\n           lockIndicator.style.pointerEvents = \"none\";\n           lockIndicator.classList.add(\"lock-indicator\");\n           g.appendChild(lockIndicator);\n         }\n         if (NODE_DATA[id]?.groupId) {\n           const groupIndicator = document.createElementNS(ns, \"circle\");\n           groupIndicator.setAttribute(\"r\", r + 4);\n           groupIndicator.style.fill = \"none\";\n         groupIndicator.style.stroke = PAGE_STATE.groupIndicator || \"#4fd1c5\";\n           groupIndicator.style.strokeWidth = \"3\";\n           groupIndicator.style.strokeDasharray = \"5,5\";\n           groupIndicator.style.pointerEvents = \"none\";\n           groupIndicator.classList.add(\"group-indicator\");\n           g.insertBefore(groupIndicator, g.firstChild);\n         }\n         let isDragging = false;\n         let startX, startY;\n         let initialPositions = {};\n         let longPressTimer = null;\n         let longPressTriggered = false;\n         g.addEventListener(\"contextmenu\", (e) => {\n          e.preventDefault();\n          e.stopPropagation();\n          if (isViewOnly()) return;\n          if (selectedNodes.has(id)) {\n           selectedNodes.delete(id);\n          } else {\n           selectedNodes.add(id);\n          }\n          updateNodeSelection();\n          return false;\n         });\n        g.addEventListener(\"touchstart\", (e) => {\n         if (NODE_DATA[id].isRack) {\n          const touch = e.touches[0];\n          longPressStartX = touch.clientX;\n          longPressStartY = touch.clientY;\n          longPressTimer = setTimeout(() => {\n           longPressTriggered = true;\n           if (navigator.vibrate) {\n            navigator.vibrate(100);\n           }\n           enterRack(id);\n          }, 500);\n         }\n        }, { passive: true });\n        g.addEventListener(\"dblclick\", (e) => {\n         e.preventDefault();\n         e.stopPropagation();\n         if (NODE_DATA[id].isRack) {\n          enterRack(id);\n         }\n        });\n        let lastTapTime = 0;\n        let lastTapNode = null;\n        g.addEventListener(\"touchend\", (e) => {\n         const currentTime = new Date().getTime();\n         const tapLength = currentTime - lastTapTime;\n         if (tapLength < 300 && tapLength > 0 && lastTapNode === id) {\n          e.preventDefault();\n          e.stopPropagation();\n          if (isViewOnly()) return;\n          if (selectedNodes.has(id)) {\n           selectedNodes.delete(id);\n          } else {\n           selectedNodes.add(id);\n          }\n          updateNodeSelection();\n          if (navigator.vibrate) {\n           navigator.vibrate(50);\n          }\n          lastTapTime = 0;\n          lastTapNode = null;\n         } else {\n          lastTapTime = currentTime;\n          lastTapNode = id;\n         }\n        });\n         g.addEventListener(\"touchend\", (e) => {\n          if (longPressTimer) {\n           clearTimeout(longPressTimer);\n           longPressTimer = null;\n          }\n          if (longPressTriggered) {\n           e.preventDefault();\n           e.stopPropagation();\n           longPressTriggered = false;\n          }\n         });\n         let longPressStartX = 0;\n        let longPressStartY = 0;\n        g.addEventListener(\"touchmove\", (e) => {\n         if (longPressTimer) {\n          const touch = e.touches[0];\n          const dx = Math.abs(touch.clientX - longPressStartX);\n          const dy = Math.abs(touch.clientY - longPressStartY);\n          if (dx > 15 || dy > 15) {\n           clearTimeout(longPressTimer);\n           longPressTimer = null;\n           longPressTriggered = false;\n          }\n         }\n        }, { passive: true });\n         g.addEventListener(\"mousedown\", (e) => {\n          if (isViewOnly()) return;\n          if (e.button === 2) {\n           return;\n          }\n          if (NODE_DATA[id]?.locked) {\n           return;\n          }\n          e.preventDefault();\n          isDragging = true;\n\t\t  pushUndo(\"move nodes\");\n          const svgEl = document.getElementById(\"map\");\n          const pt = svgEl.createSVGPoint();\n          pt.x = e.clientX;\n          pt.y = e.clientY;\n          const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n          startX = svgP.x;\n          startY = svgP.y;\n          let nodesToCollect = [];\n      if (selectedNodes.has(id)) {\n      initialPositions = {};\n      const allSelectedRects = Array.from(selectedRects);\n      const allSelectedTexts = Array.from(selectedTexts);\n      const allSelectedEdges = Array.from(selectedEdges).map(eid => EDGE_DATA.list.find(e => e.id === eid)).filter(Boolean);\n      selectedNodes.forEach(nodeId => {\n      const nodePos = savedPositions[nodeId];\n      if (nodePos) {\n      initialPositions[nodeId] = { x: nodePos.x, y: nodePos.y };\n      }\n      });\n      Array.from(selectedRects).forEach(rectId => {\n        const rect = RECT_DATA.list.find(r => r.id === rectId);\n        if (rect) { rect._dragStartX = rect.x; rect._dragStartY = rect.y; }\n      });\n      Array.from(selectedTexts).forEach(textId => {\n        const text = TEXT_DATA.list.find(t => t.id === textId);\n        if (text) { text._dragStartX = text.x; text._dragStartY = text.y; }\n      });\n      Array.from(selectedEdges).forEach(edgeId => {\n        const edge = EDGE_DATA.list.find(e => e.id === edgeId);\n        if (edge && edge.points) { edge._dragStartPoints = edge.points.map(p => ({x: p.x, y: p.y})); }\n      });\n      } else {\n      initialPositions = { [id]: { x: pos.x, y: pos.y } };\n      }\n          const groupIds = new Set();\n          nodesToCollect.forEach(nodeId => {\n           const groupId = NODE_DATA[nodeId]?.groupId;\n           if (groupId) {\n             groupIds.add(groupId);\n           }\n          });\n          if (groupIds.size > 0) {\n           Object.keys(NODE_DATA).forEach(nodeId => {\n             const nodeGroupId = NODE_DATA[nodeId]?.groupId;\n             if (nodeGroupId && groupIds.has(nodeGroupId)) {\n               if (!nodesToCollect.includes(nodeId)) {\n                 nodesToCollect.push(nodeId);\n               }\n             }\n           });\n          }\n          nodesToCollect = nodesToCollect.filter(nodeId => !NODE_DATA[nodeId]?.locked);\n          if (nodesToCollect.length === 0) {\n           return;\n          }\n          initialPositions = {};\n          nodesToCollect.forEach(nodeId => {\n           const nodePos = savedPositions[nodeId];\n           if (nodePos) {\n            initialPositions[nodeId] = { x: nodePos.x, y: nodePos.y };\n           }\n          });\n          g.style.cursor = \"grabbing\";\n          hitArea.style.cursor = \"grabbing\";\n          e.stopPropagation();\n         });\n         const handleMouseMove = (e) => {\n          if (!isDragging) return;\n          e.preventDefault();\n          const svgEl = document.getElementById(\"map\");\n          const pt = svgEl.createSVGPoint();\n          pt.x = e.clientX;\n          pt.y = e.clientY;\n          const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n          const dx = svgP.x - startX;\n          const dy = svgP.y - startY;\n          const nodesToMove = selectedNodes.has(id) ? Array.from(selectedNodes) : [id];\n          nodesToMove.forEach(nodeId => {\n           if (!initialPositions[nodeId]) return;\n           const initialPos = initialPositions[nodeId];\n           let newX = initialPos.x + dx;\n           let newY = initialPos.y + dy;\n           const nodeSize = savedSizes[nodeId] || 55;\n           newX = Math.max(nodeSize + CANVAS_PADDING, Math.min(CANVAS_WIDTH - nodeSize - CANVAS_PADDING, newX));\n           newY = Math.max(nodeSize + CANVAS_PADDING, Math.min(CANVAS_HEIGHT - nodeSize - CANVAS_PADDING, newY));\n           savedPositions[nodeId] = { x: newX, y: newY };\n           positions[nodeId] = { x: newX, y: newY };\n           if (nodeId === id) {\n            pos.x = newX;\n            pos.y = newY;\n           }\n           const nodeGroup = document.querySelector(`g[data-node-id=\"${nodeId}\"]`);\n          if (nodeGroup) {\n           nodeGroup.setAttribute(\"transform\", `translate(${newX},${newY})`);\n          }\n         });\n         Array.from(selectedRects).forEach(rectId => {\n           const rect = RECT_DATA.list.find(r => r.id === rectId);\n           if (rect && rect._dragStartX !== undefined) {\n             rect.x = rect._dragStartX + dx;\n             rect.y = rect._dragStartY + dy;\n           }\n         });\n         Array.from(selectedTexts).forEach(textId => {\n           const text = TEXT_DATA.list.find(t => t.id === textId);\n           if (text && text._dragStartX !== undefined) {\n             text.x = text._dragStartX + dx;\n             text.y = text._dragStartY + dy;\n           }\n         });\n         Array.from(selectedEdges).forEach(edgeId => {\n           const edge = EDGE_DATA.list.find(e => e.id === edgeId);\n           if (edge && edge._dragStartPoints) {\n             edge.points.forEach((p, i) => {\n               p.x = edge._dragStartPoints[i].x + dx;\n               p.y = edge._dragStartPoints[i].y + dy;\n             });\n           }\n         });\n         forgeTheTopology();\n         updateMinimap();\n          document.querySelectorAll(\".edge\").forEach((edgeEl) => {\n           const fromId = edgeEl.dataset.from;\n           const toId = edgeEl.dataset.to;\n           if (nodesToMove.includes(fromId) || nodesToMove.includes(toId)) {\n            const p1 = savedPositions[fromId] || positions[fromId] || {\n             x: 600,\n             y: 350\n            };\n            const p2 = savedPositions[toId] || positions[toId] || {\n             x: 600,\n             y: 350\n            };\n            if (edgeEl.tagName === \"line\") {\n             edgeEl.setAttribute(\"x1\", p1.x);\n             edgeEl.setAttribute(\"y1\", p1.y);\n             edgeEl.setAttribute(\"x2\", p2.x);\n             edgeEl.setAttribute(\"y2\", p2.y);\n            } else if (edgeEl.tagName === \"path\") {\n             const edgeId = edgeEl.dataset.edgeId;\n             const edge = EDGE_DATA.list.find(\n              (e) => e.id === edgeId);\n             if (edge) {\n              const pairTotal = edge._pairTotal || 1;\n              const pairIndex = edge._pairIndex || 0;\n              const midX = (p1.x + p2.x) / 2;\n              const midY = (p1.y + p2.y) / 2;\n              const dx = p2.x - p1.x;\n              const dy = p2.y - p1.y;\n              const len = Math.sqrt(dx * dx + dy * dy) || 1;\n              const perpX = -dy / len;\n              const perpY = dx / len;\n              let offsetAmount = 0;\n              if (pairTotal > 1) {\n               offsetAmount = (pairIndex - (pairTotal - 1) / 2) * 40;\n              }\n              const ctrlX = midX + perpX * offsetAmount;\n              const ctrlY = midY + perpY * offsetAmount;\n              edgeEl.setAttribute(\"d\", `M ${p1.x} ${p1.y} Q ${ctrlX} ${ctrlY} ${p2.x} ${p2.y}`);\n             }\n            }\n           }\n          });\n         };\n      const handleMouseUp = () => {\n      if (isDragging) {\n      isDragging = false;\n      g.style.cursor = \"grab\";\n      hitArea.style.cursor = \"grab\";\n      if (currentView.mode === \"rack\" && NODE_DATA[id]?.assignedRack === currentView.rackId) {\n      const draggedX = savedPositions[id]?.x || pos.x;\n      const draggedY = savedPositions[id]?.y || pos.y;\n      const rackCapacity = getRackCapacity(currentView.rackId);\n      const rackUHeight = getRackUHeight(currentView.rackId);\n      const rackLeft = RACK_START_X - RACK_WIDTH / 2;\n      const rackRight = RACK_START_X + RACK_WIDTH / 2;\n      const rackTop = RACK_START_Y;\n      const rackBottom = RACK_START_Y + rackCapacity * rackUHeight;\n      if (draggedX >= rackLeft && draggedX <= rackRight && draggedY >= rackTop && draggedY <= rackBottom) {\n        let newUnit = rackCapacity - Math.round((draggedY - RACK_START_Y) / rackUHeight);\n        newUnit = Math.max(1, Math.min(newUnit, rackCapacity));\n        NODE_DATA[id].rackUnit = newUnit;\n      }\n      forgeTheTopology();\n      claimTheImmortal(id);\n      }\n      }\n      };\n         document.addEventListener(\"mousemove\", handleMouseMove);\n         document.addEventListener(\"mouseup\", handleMouseUp);\n         let touchStartTime = 0;\n         let touchStartX = 0;\n         let touchStartY = 0;\n         let touchMoved = false;\n       g.addEventListener(\"touchstart\",\n         (e) => {\n          if (NODE_DATA[id]?.locked) {\n           return;\n          }\n          e.preventDefault();\n          touchStartTime = Date.now();\n          touchMoved = false;\n          const svgEl = document.getElementById(\"map\");\n          const pt = svgEl.createSVGPoint();\n          const touch = e.touches[0];\n          pt.x = touch.clientX;\n          pt.y = touch.clientY;\n          const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n          touchStartX = svgP.x;\n          touchStartY = svgP.y;\n          startX = svgP.x;\n          startY = svgP.y;\n          let nodesToCollect = [];\n          if (selectedNodes.has(id)) {\n           nodesToCollect = Array.from(selectedNodes);\n          } else {\n           nodesToCollect = [id];\n          }\n          const groupIds = new Set();\n          nodesToCollect.forEach(nodeId => {\n           const groupId = NODE_DATA[nodeId]?.groupId;\n           if (groupId) {\n             groupIds.add(groupId);\n           }\n          });\n          if (groupIds.size > 0) {\n           Object.keys(NODE_DATA).forEach(nodeId => {\n             const nodeGroupId = NODE_DATA[nodeId]?.groupId;\n             if (nodeGroupId && groupIds.has(nodeGroupId)) {\n               if (!nodesToCollect.includes(nodeId)) {\n                 nodesToCollect.push(nodeId);\n               }\n             }\n           });\n          }\n          nodesToCollect = nodesToCollect.filter(nodeId => !NODE_DATA[nodeId]?.locked);\n          if (nodesToCollect.length === 0) {\n           return;\n          }\n          initialPositions = {};\n          nodesToCollect.forEach(nodeId => {\n           const nodePos = savedPositions[nodeId];\n           if (nodePos) {\n            initialPositions[nodeId] = { x: nodePos.x, y: nodePos.y };\n           }\n          });\n          e.stopPropagation();\n         }, {\n          passive: false\n         });\n         g.addEventListener(\"touchmove\", (e) => {\n\t\t  if (isViewOnly()) return;\n         e.preventDefault();\n         const svgEl = document.getElementById(\"map\");\n         const pt = svgEl.createSVGPoint();\n         const touch = e.touches[0];\n         pt.x = touch.clientX;\n         pt.y = touch.clientY;\n         const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n         const dx = Math.abs(svgP.x - touchStartX);\n         const dy = Math.abs(svgP.y - touchStartY);\n         if (dx > (isMobileDevice() ? 4 : 10) || dy > (isMobileDevice() ? 4 : 10)) {\n      touchMoved = true;\n      isDragging = true;\n      }\n         if (!isDragging) return;\n         const deltaX = svgP.x - startX;\n         const deltaY = svgP.y - startY;\n         const nodesToMove = selectedNodes.has(id) ? Array.from(selectedNodes) : [id];\n         nodesToMove.forEach(nodeId => {\n          if (!initialPositions[nodeId]) return;\n          const initialPos = initialPositions[nodeId];\n          let newX = initialPos.x + deltaX;\n          let newY = initialPos.y + deltaY;\n          const nodeSize = savedSizes[nodeId] || 55;\n          newX = Math.max(nodeSize + CANVAS_PADDING, Math.min(CANVAS_WIDTH - nodeSize - CANVAS_PADDING, newX));\n          newY = Math.max(nodeSize + CANVAS_PADDING, Math.min(CANVAS_HEIGHT - nodeSize - CANVAS_PADDING, newY));\n          savedPositions[nodeId] = { x: newX, y: newY };\n          positions[nodeId] = { x: newX, y: newY };\n          if (nodeId === id) {\n           pos.x = newX;\n           pos.y = newY;\n          }\n          const nodeGroup = document.querySelector(`g[data-node-id=\"${nodeId}\"]`);\n          if (nodeGroup) {\n           nodeGroup.setAttribute(\"transform\", `translate(${newX},${newY})`);\n          }\n         });\n         updateMinimap();\n         document.querySelectorAll(\".edge\").forEach((edgeEl) => {\n          const fromId = edgeEl.dataset.from;\n          const toId = edgeEl.dataset.to;\n          if (nodesToMove.includes(fromId) || nodesToMove.includes(toId)) {\n            const p1 = savedPositions[fromId] || positions[fromId] || {\n             x: 600,\n             y: 350\n            };\n            const p2 = savedPositions[toId] || positions[toId] || {\n             x: 600,\n             y: 350\n            };\n            if (edgeEl.tagName === \"line\") {\n             edgeEl.setAttribute(\"x1\", p1.x);\n             edgeEl.setAttribute(\"y1\", p1.y);\n             edgeEl.setAttribute(\"x2\", p2.x);\n             edgeEl.setAttribute(\"y2\", p2.y);\n            } else if (edgeEl.tagName === \"path\") {\n             const edgeId = edgeEl.dataset.edgeId;\n             const edge = EDGE_DATA.list.find(\n              (e) => e.id === edgeId);\n             if (edge) {\n              const pairTotal = edge._pairTotal || 1;\n              const pairIndex = edge._pairIndex || 0;\n              const midX = (p1.x + p2.x) / 2;\n              const midY = (p1.y + p2.y) / 2;\n              const dx = p2.x - p1.x;\n              const dy = p2.y - p1.y;\n              const len = Math.sqrt(dx * dx + dy * dy) || 1;\n              const perpX = -dy / len;\n              const perpY = dx / len;\n              let offsetAmount = 0;\n              if (pairTotal > 1) {\n               offsetAmount = (pairIndex - (pairTotal - 1) / 2) * 40;\n              }\n              const ctrlX = midX + perpX * offsetAmount;\n              const ctrlY = midY + perpY * offsetAmount;\n              edgeEl.setAttribute(\"d\", `M ${p1.x} ${p1.y} Q ${ctrlX} ${ctrlY} ${p2.x} ${p2.y}`);\n             }\n            }\n           }\n          });\n         }, {\n          passive: false\n         });\n      g.addEventListener(\"touchend\", (e) => {\n      const touchDuration = Date.now() - touchStartTime;\n      if (!touchMoved && touchDuration < 400) {\n       if (isViewOnly()) {\n        handleViewOnlyClick(id, 'node');\n        return;\n       }\n       claimTheImmortal(id);\n      }\n      if (isDragging) {\n      const draggedX = pos.x;\n      const draggedY = pos.y;\n      isDragging = false;\n      savedPositions[id] = {\n      x: pos.x,\n      y: pos.y\n      };\n      if (currentView.mode === \"rack\" && NODE_DATA[id]?.assignedRack === currentView.rackId) {\n      const rackCapacity = getRackCapacity(currentView.rackId);\n      const rackUHeight = getRackUHeight(currentView.rackId);\n      const rackLeft = RACK_START_X - RACK_WIDTH / 2;\n      const rackRight = RACK_START_X + RACK_WIDTH / 2;\n      const rackTop = RACK_START_Y;\n      const rackBottom = RACK_START_Y + rackCapacity * rackUHeight;\n      if (draggedX >= rackLeft && draggedX <= rackRight && draggedY >= rackTop && draggedY <= rackBottom) {\n        let newUnit = rackCapacity - Math.round((draggedY - RACK_START_Y) / rackUHeight);\n        newUnit = Math.max(1, Math.min(newUnit, rackCapacity));\n        NODE_DATA[id].rackUnit = newUnit;\n      }\n      forgeTheTopology();\n      claimTheImmortal(id);\n      }\n      }\n      touchMoved = false;\n      });\n         g.style.cursor = \"grab\";\n         g.addEventListener(\"click\", (e) => {\n          if (!isDragging) {\n           if (isViewOnly()) {\n            handleViewOnlyClick(id, 'node');\n            return;\n           }\n           claimTheImmortal(id);\n          }\n         });\n         svg.appendChild(g);\n        });\n        if (TEXT_DATA && TEXT_DATA.list) {\n         TEXT_DATA.list.forEach((textItem) => {\n          if (currentView.mode === \"rack\") return;\n          const g = document.createElementNS(ns, \"g\");\n          g.classList.add(\"text-group\");\n          g.dataset.textId = textItem.id;\n          const textRotation = textItem.rotation || 0;\n          if (textRotation !== 0) {\n            g.setAttribute(\"transform\", `rotate(${textRotation}, ${textItem.x}, ${textItem.y})`);\n          }\n          if (textItem.bgEnabled) {\n           const bgRect = document.createElementNS(ns, \"rect\");\n           bgRect.classList.add(\"text-bg\");\n           bgRect.setAttribute(\"x\", textItem.x - 5);\n           bgRect.setAttribute(\"y\", textItem.y - textItem.fontSize - 2);\n           bgRect.setAttribute(\"width\", 100);\n           bgRect.setAttribute(\"height\", textItem.fontSize + 10);\n           bgRect.style.fill = textItem.bgColor;\n           bgRect.style.opacity = \"0.7\";\n           bgRect.style.rx = \"4\";\n           g.appendChild(bgRect);\n          }\n          const textEl = document.createElementNS(ns, \"text\");\n          textEl.classList.add(\"text-element\");\n          textEl.setAttribute(\"x\", textItem.x);\n          textEl.setAttribute(\"y\", textItem.y);\n          textEl.style.fill = textItem.color;\n          textEl.style.fontSize = textItem.fontSize + \"px\";\n          textEl.style.fontWeight = textItem.fontWeight;\n          textEl.style.fontStyle = textItem.fontStyle;\n          textEl.style.textAnchor = textItem.textAlign;\n          textEl.style.textDecoration = textItem.textDecoration;\n          textEl.style.opacity = textItem.opacity;\n          textEl.style.cursor = \"move\";\n          textEl.style.userSelect = \"none\";\n          textEl.setAttribute(\"dominant-baseline\", \"middle\");\n          const lines = textItem.content.split('\\n');\n          if (lines.length === 1) {\n           textEl.textContent = textItem.content;\n          } else {\n           lines.forEach((line, i) => {\n            const tspan = document.createElementNS(ns, \"tspan\");\n            tspan.textContent = line;\n            tspan.setAttribute(\"x\", textItem.x);\n            tspan.setAttribute(\"dy\", i === 0 ? 0 : textItem.fontSize * 1.2);\n            textEl.appendChild(tspan);\n           });\n          }\n          g.appendChild(textEl);\n          if (textItem.bgEnabled) {\n           setTimeout(() => {\n            try {\n             const bbox = textEl.getBBox();\n             const bgRect = g.querySelector('.text-bg');\n             if (bgRect && bbox) {\n              bgRect.setAttribute(\"x\", bbox.x - 5);\n              bgRect.setAttribute(\"y\", bbox.y - 2);\n              bgRect.setAttribute(\"width\", bbox.width + 10);\n              bgRect.setAttribute(\"height\", bbox.height + 4);\n             }\n            } catch (e) {\n            }\n           }, 0);\n          }\n          const deleteBtn = document.createElementNS(ns, \"g\");\n          deleteBtn.classList.add(\"text-delete-btn\");\n          deleteBtn.style.cursor = \"pointer\";\n          deleteBtn.style.display = textDrawMode ? \"block\" : \"none\";\n          const deleteBg = document.createElementNS(ns, \"circle\");\n          deleteBg.setAttribute(\"cx\", textItem.x + 20);\n          deleteBg.setAttribute(\"cy\", textItem.y - textItem.fontSize);\n          deleteBg.setAttribute(\"r\", 12);\n          deleteBg.style.fill = \"#f56565\";\n          deleteBg.style.stroke = \"white\";\n          deleteBg.style.strokeWidth = \"2\";\n          const deleteX = document.createElementNS(ns, \"text\");\n          deleteX.setAttribute(\"x\", textItem.x + 20);\n          deleteX.setAttribute(\"y\", textItem.y - textItem.fontSize);\n          deleteX.setAttribute(\"text-anchor\", \"middle\");\n          deleteX.setAttribute(\"dominant-baseline\", \"middle\");\n          deleteX.style.fill = \"white\";\n          deleteX.style.fontSize = \"16px\";\n          deleteX.style.fontWeight = \"bold\";\n          deleteX.style.pointerEvents = \"none\";\n          deleteX.textContent = \"×\";\n          deleteBtn.appendChild(deleteBg);\n          deleteBtn.appendChild(deleteX);\n          deleteBtn.addEventListener(\"click\", (e) => {\n           e.stopPropagation();\n           e.preventDefault();\n           deleteText(textItem.id);\n          });\n          let isDragging = false;\n          let dragStartX, dragStartY;\n          let textStartX, textStartY;\n          textEl.addEventListener(\"mousedown\", (e) => {\n\t\t  if (isViewOnly()) return;\n      if (textDrawMode) return;\n      e.preventDefault();\n      e.stopPropagation();\n      isDragging = true;\n      dragStartX = e.clientX;\n      dragStartY = e.clientY;\n      textStartX = textItem.x;\n      textStartY = textItem.y;\n      textEl.style.cursor = \"grabbing\";\n      showTextPanel(textItem.id);\n      if (selectedTexts.has(textItem.id)) {\n      initialPositions = {};\n      selectedNodes.forEach(nodeId => {\n      const nodePos = savedPositions[nodeId];\n      if (nodePos) initialPositions[nodeId] = { x: nodePos.x, y: nodePos.y };\n      });\n      selectedRects.forEach(rectId => {\n      const r = RECT_DATA.list.find(x => x.id === rectId);\n      if (r) { r._dragStartX = r.x; r._dragStartY = r.y; }\n      });\n      selectedTexts.forEach(textId => {\n      const t = TEXT_DATA.list.find(x => x.id === textId);\n      if (t) { t._dragStartX = t.x; t._dragStartY = t.y; }\n      });\n      selectedEdges.forEach(edgeId => {\n      const ed = EDGE_DATA.list.find(x => x.id === edgeId);\n      if (ed && ed.points) { ed._dragStartPoints = ed.points.map(p => ({x: p.x, y: p.y})); }\n      });\n      }\n      });\n          const moveHandler = (e) => {\n      if (!isDragging || textDrawMode) return;\n      const svgEl = svg;\n      const pt1 = svgEl.createSVGPoint();\n      pt1.x = dragStartX;\n      pt1.y = dragStartY;\n      const svgP1 = pt1.matrixTransform(svgEl.getScreenCTM().inverse());\n      const pt2 = svgEl.createSVGPoint();\n      pt2.x = e.clientX;\n      pt2.y = e.clientY;\n      const svgP2 = pt2.matrixTransform(svgEl.getScreenCTM().inverse());\n      const dx = svgP2.x - svgP1.x;\n      const dy = svgP2.y - svgP1.y;\n      textItem.x = textStartX + dx;\n      textItem.y = textStartY + dy;\n      if (selectedTexts.has(textItem.id)) {\n      selectedNodes.forEach(nodeId => {\n      if (initialPositions[nodeId]) {\n       const newX = initialPositions[nodeId].x + dx;\n       const newY = initialPositions[nodeId].y + dy;\n       savedPositions[nodeId] = { x: newX, y: newY };\n      }\n      });\n      selectedRects.forEach(rectId => {\n      const r = RECT_DATA.list.find(x => x.id === rectId);\n      if (r && r._dragStartX !== undefined) { r.x = r._dragStartX + dx; r.y = r._dragStartY + dy; }\n      });\n      selectedTexts.forEach(textId => {\n      if (textId === textItem.id) return;\n      const t = TEXT_DATA.list.find(x => x.id === textId);\n      if (t && t._dragStartX !== undefined) { t.x = t._dragStartX + dx; t.y = t._dragStartY + dy; }\n      });\n      selectedEdges.forEach(edgeId => {\n      const ed = EDGE_DATA.list.find(x => x.id === edgeId);\n      if (ed && ed._dragStartPoints) { ed.points.forEach((p, i) => { p.x = ed._dragStartPoints[i].x + dx; p.y = ed._dragStartPoints[i].y + dy; }); }\n      });\n      }\n      forgeTheTopology();\n      };\n          const upHandler = () => {\n           if (isDragging) {\n            isDragging = false;\n            textEl.style.cursor = \"move\";\n           }\n          };\n          document.addEventListener(\"mousemove\", moveHandler);\n          document.addEventListener(\"mouseup\", upHandler);\n          let touchStartX = 0, touchStartY = 0;\n          let textTouchStartX = 0, textTouchStartY = 0;\n          textEl.addEventListener(\"touchstart\", (e) => {\n\t\t   if (isViewOnly()) return;\n           if (textDrawMode) return;\n           if (e.touches.length !== 1) return;\n           e.preventDefault();\n           e.stopPropagation();\n           const touch = e.touches[0];\n           isDragging = true;\n           touchStartX = touch.clientX;\n           touchStartY = touch.clientY;\n           textTouchStartX = textItem.x;\n           textTouchStartY = textItem.y;\n           showTextPanel(textItem.id);\n          }, { passive: false });\n          textEl.addEventListener(\"touchmove\", (e) => {\n           if (!isDragging || textDrawMode) return;\n           if (e.touches.length !== 1) return;\n           e.preventDefault();\n           const touch = e.touches[0];\n           const svgEl = svg;\n           const pt1 = svgEl.createSVGPoint();\n           pt1.x = touchStartX;\n           pt1.y = touchStartY;\n           const svgP1 = pt1.matrixTransform(svgEl.getScreenCTM().inverse());\n           const pt2 = svgEl.createSVGPoint();\n           pt2.x = touch.clientX;\n           pt2.y = touch.clientY;\n           const svgP2 = pt2.matrixTransform(svgEl.getScreenCTM().inverse());\n           const dx = svgP2.x - svgP1.x;\n           const dy = svgP2.y - svgP1.y;\n           textItem.x = textTouchStartX + dx;\n           textItem.y = textTouchStartY + dy;\n           forgeTheTopology();\n          }, { passive: false });\n          textEl.addEventListener(\"touchend\", () => {\n           if (isDragging) {\n            isDragging = false;\n           }\n          }, { passive: false });\n          textEl.addEventListener(\"contextmenu\", (e) => {\n           e.preventDefault();\n           e.stopPropagation();\n           if (selectedTexts.has(textItem.id)) {\n            selectedTexts.delete(textItem.id);\n           } else {\n            selectedTexts.add(textItem.id);\n           }\n           updateAllSelections();\n          });\n          let textLastTap = 0;\n          g.addEventListener(\"touchend\", (e) => {\n           const now = Date.now();\n           if (now - textLastTap < 300) {\n            e.preventDefault();\n            if (selectedTexts.has(textItem.id)) {\n             selectedTexts.delete(textItem.id);\n            } else {\n             selectedTexts.add(textItem.id);\n            }\n            updateAllSelections();\n            if (navigator.vibrate) navigator.vibrate(50);\n            textLastTap = 0;\n           } else {\n            textLastTap = now;\n           }\n          }, { passive: false });\n          if (textItem.groupId) {\n            const groupIndicator = document.createElementNS(ns, \"rect\");\n            groupIndicator.setAttribute(\"x\", textItem.x - 54);\n            groupIndicator.setAttribute(\"y\", textItem.y - 24);\n            groupIndicator.setAttribute(\"width\", 108);\n            groupIndicator.setAttribute(\"height\", 48);\n            groupIndicator.setAttribute(\"rx\", \"8\");\n            groupIndicator.style.fill = \"none\";\n       groupIndicator.style.stroke = PAGE_STATE.groupIndicator || \"#4fd1c5\";\n            groupIndicator.style.strokeWidth = \"3\";\n            groupIndicator.style.strokeDasharray = \"5,5\";\n            groupIndicator.style.pointerEvents = \"none\";\n            g.insertBefore(groupIndicator, g.firstChild);\n          }\n          g.appendChild(deleteBtn);\n          svg.appendChild(g);\n         });\n        }\n        if (IMAGE_DATA && IMAGE_DATA.list) {\n         IMAGE_DATA.list.forEach((img) => {\n          if (currentView.mode === \"rack\") return;\n          const g = document.createElementNS(ns, \"g\");\n          g.classList.add(\"image-group\");\n          g.dataset.imageId = img.id;\n          const imgEl = document.createElementNS(ns, \"image\");\n          imgEl.setAttribute(\"x\", img.x);\n          imgEl.setAttribute(\"y\", img.y);\n          imgEl.setAttribute(\"width\", img.width);\n          imgEl.setAttribute(\"height\", img.height);\n          imgEl.setAttribute(\"href\", img.src);\n          imgEl.setAttribute(\"preserveAspectRatio\", \"xMidYMid meet\");\n          imgEl.style.opacity = img.opacity || 1;\n          imgEl.style.cursor = \"move\";\n          if (img.border && img.border !== \"none\") {\n           const borderWidth = img.border === \"thin\" ? 1 : img.border === \"medium\" ? 2 : 4;\n           const borderRect = document.createElementNS(ns, \"rect\");\n           borderRect.setAttribute(\"x\", img.x);\n           borderRect.setAttribute(\"y\", img.y);\n           borderRect.setAttribute(\"width\", img.width);\n           borderRect.setAttribute(\"height\", img.height);\n           borderRect.style.fill = \"none\";\n           borderRect.style.stroke = \"#fff\";\n           borderRect.style.strokeWidth = borderWidth;\n           g.appendChild(borderRect);\n          }\n          if (img.shadow && img.shadow !== \"none\") {\n           const shadowSize = img.shadow === \"small\" ? 4 : img.shadow === \"medium\" ? 8 : 16;\n           imgEl.style.filter = `drop-shadow(${shadowSize/2}px ${shadowSize/2}px ${shadowSize}px rgba(0,0,0,0.5))`;\n          }\n          g.appendChild(imgEl);\n          let isDragging = false;\n          let dragStartX, dragStartY;\n          let imgStartX, imgStartY;\n          imgEl.addEventListener(\"mousedown\", (e) => {\n           if (isViewOnly()) return;\n           e.preventDefault();\n           e.stopPropagation();\n           isDragging = true;\n           dragStartX = e.clientX;\n           dragStartY = e.clientY;\n           imgStartX = img.x;\n           imgStartY = img.y;\n           imgEl.style.cursor = \"grabbing\";\n           showImagePanel(img.id);\n          });\n          const moveHandler = (e) => {\n           if (!isDragging) return;\n           const svgEl = svg;\n           const pt1 = svgEl.createSVGPoint();\n           pt1.x = dragStartX;\n           pt1.y = dragStartY;\n           const svgP1 = pt1.matrixTransform(svgEl.getScreenCTM().inverse());\n           const pt2 = svgEl.createSVGPoint();\n           pt2.x = e.clientX;\n           pt2.y = e.clientY;\n           const svgP2 = pt2.matrixTransform(svgEl.getScreenCTM().inverse());\n           const dx = svgP2.x - svgP1.x;\n           const dy = svgP2.y - svgP1.y;\n           img.x = imgStartX + dx;\n           img.y = imgStartY + dy;\n           forgeTheTopology();\n          };\n          const upHandler = () => {\n           if (isDragging) {\n            isDragging = false;\n            imgEl.style.cursor = \"move\";\n           }\n          };\n          document.addEventListener(\"mousemove\", moveHandler);\n          document.addEventListener(\"mouseup\", upHandler);\n          let touchStartX = 0, touchStartY = 0;\n          let imgTouchStartX = 0, imgTouchStartY = 0;\n          imgEl.addEventListener(\"touchstart\", (e) => {\n           if (isViewOnly()) return;\n           if (e.touches.length !== 1) return;\n           e.preventDefault();\n           e.stopPropagation();\n           const touch = e.touches[0];\n           isDragging = true;\n           touchStartX = touch.clientX;\n           touchStartY = touch.clientY;\n           imgTouchStartX = img.x;\n           imgTouchStartY = img.y;\n           showImagePanel(img.id);\n          }, { passive: false });\n          imgEl.addEventListener(\"touchmove\", (e) => {\n           if (!isDragging) return;\n           if (e.touches.length !== 1) return;\n           e.preventDefault();\n           const touch = e.touches[0];\n           const svgEl = svg;\n           const pt1 = svgEl.createSVGPoint();\n           pt1.x = touchStartX;\n           pt1.y = touchStartY;\n           const svgP1 = pt1.matrixTransform(svgEl.getScreenCTM().inverse());\n           const pt2 = svgEl.createSVGPoint();\n           pt2.x = touch.clientX;\n           pt2.y = touch.clientY;\n           const svgP2 = pt2.matrixTransform(svgEl.getScreenCTM().inverse());\n           img.x = imgTouchStartX + (svgP2.x - svgP1.x);\n           img.y = imgTouchStartY + (svgP2.y - svgP1.y);\n           forgeTheTopology();\n          }, { passive: false });\n          imgEl.addEventListener(\"touchend\", () => {\n           isDragging = false;\n          }, { passive: false });\n          svg.appendChild(g);\n         });\n        }\n        Object.keys(NODE_DATA).forEach(nodeId => {\n         updatePingIndicator(nodeId);\n        });\n        forgeTheLegend();\n\t\tupdateZoneLegend();\n        updateMinimap();\n        if (currentSearchQuery && currentSearchResults.length > 0) {\n         highlightSearchResults(currentSearchResults, true);\n        }\n        if (typeof renderMotionTrails === 'function') {\n         renderMotionTrails(svg);\n        }\n       }\n       const _forgeTheTopologyImpl = forgeTheTopology;\n       forgeTheTopology = function(immediate = false) {\n        if (immediate || forgeImmediate) {\n         forgeImmediate = false;\n         clearTimeout(forgeDebounceTimer);\n         _forgeTheTopologyImpl();\n         return;\n        }\n        clearTimeout(forgeDebounceTimer);\n        forgeDebounceTimer = setTimeout(() => {\n         _forgeTheTopologyImpl();\n        }, 16);\n       };\n       function forgeTheTopologyImmediate() {\n        forgeImmediate = true;\n        forgeTheTopology();\n       }\n       function showEditModal(title, currentValue, onSave) {\n        const modal = document.getElementById(\"edit-modal\");\n        const input = document.getElementById(\"modal-input\");\n        const titleEl = document.getElementById(\"modal-title\");\n        const saveBtn = document.getElementById(\"modal-save\");\n        const cancelBtn = document.getElementById(\"modal-cancel\");\n        titleEl.textContent = title;\n        input.value = currentValue;\n        modal.classList.add(\"active\");\n        input.focus();\n        input.select();\n        const cleanup = () => {\n         modal.classList.remove(\"active\");\n         saveBtn.removeEventListener(\"click\", handleSave);\n         cancelBtn.removeEventListener(\"click\", handleCancel);\n         input.removeEventListener(\"keypress\", handleEnter);\n         modal.removeEventListener(\"click\", bgHandler);\n        };\n        const handleSave = () => {\n         if (input.value.trim()) {\n          onSave(input.value.trim());\n         }\n         cleanup();\n        };\n        const handleCancel = () => {\n         cleanup();\n        };\n        const handleEnter = (e) => {\n         if (e.key === \"Enter\") handleSave();\n        };\n        const bgHandler = (e) => {\n         if (e.target === modal) handleCancel();\n        };\n        saveBtn.addEventListener(\"click\", handleSave);\n        cancelBtn.addEventListener(\"click\", handleCancel);\n        input.addEventListener(\"keypress\", handleEnter);\n        modal.addEventListener(\"click\", bgHandler);\n       }\n\n      let noteEditorCallback = null;\n      let noteEditorEditIndex = null;\n      function showNoteEditor(title, currentContent, onSave, editIndex = null) {\n       const modal = document.getElementById(\"note-editor-modal\");\n       const content = document.getElementById(\"note-editor-content\");\n       const titleEl = document.getElementById(\"note-editor-title\");\n       const saveBtn = document.getElementById(\"note-editor-save\");\n       const cancelBtn = document.getElementById(\"note-editor-cancel\");\n       titleEl.textContent = title || t(\"noteEditor.title\");\n       content.innerHTML = currentContent || \"<p><br></p>\";\n       noteEditorCallback = onSave;\n       noteEditorEditIndex = editIndex;\n       modal.classList.add(\"active\");\n       content.focus();\n       const range = document.createRange();\n       const sel = window.getSelection();\n       range.selectNodeContents(content);\n       range.collapse(false);\n       sel.removeAllRanges();\n       sel.addRange(range);\n      }\n      function closeNoteEditor() {\n       const modal = document.getElementById(\"note-editor-modal\");\n       modal.classList.remove(\"active\");\n       noteEditorCallback = null;\n       noteEditorEditIndex = null;\n       document.getElementById(\"note-heading-select\").value = \"\";\n      }\n      function saveNoteEditor() {\n       const content = document.getElementById(\"note-editor-content\");\n       let html = content.innerHTML.trim();\n       if (html === \"<p><br></p>\" || html === \"<br>\" || html === \"\") {\n        closeNoteEditor();\n        return;\n       }\n       if (noteEditorCallback) {\n        noteEditorCallback(html, noteEditorEditIndex);\n       }\n       closeNoteEditor();\n      }\n      function execNoteCommand(command, value = null) {\n       const content = document.getElementById(\"note-editor-content\");\n       content.focus();\n       if (command === \"code\") {\n        const sel = window.getSelection();\n        if (sel.rangeCount > 0) {\n         const range = sel.getRangeAt(0);\n         const selectedText = range.toString();\n         if (selectedText) {\n          const code = document.createElement(\"code\");\n          code.textContent = selectedText;\n          range.deleteContents();\n          range.insertNode(code);\n          range.setStartAfter(code);\n          range.collapse(true);\n          sel.removeAllRanges();\n          sel.addRange(range);\n         }\n        }\n       } else if (command === \"codeblock\") {\n        const sel = window.getSelection();\n        if (sel.rangeCount > 0) {\n         const range = sel.getRangeAt(0);\n         const selectedText = range.toString() || \"code here\";\n         const pre = document.createElement(\"pre\");\n         const code = document.createElement(\"code\");\n         code.textContent = selectedText;\n         pre.appendChild(code);\n         range.deleteContents();\n         range.insertNode(pre);\n         const p = document.createElement(\"p\");\n         p.innerHTML = \"<br>\";\n         pre.parentNode.insertBefore(p, pre.nextSibling);\n         range.setStart(p, 0);\n         range.collapse(true);\n         sel.removeAllRanges();\n         sel.addRange(range);\n        }\n       } else if (command === \"blockquote\") {\n        document.execCommand(\"formatBlock\", false, \"blockquote\");\n       } else if (command === \"createLink\") {\n        const url = prompt(\"Enter URL:\", \"https://\");\n        if (url) {\n         document.execCommand(\"createLink\", false, url);\n        }\n       } else if (command === \"heading\") {\n        if (value) {\n         document.execCommand(\"formatBlock\", false, value);\n        } else {\n         document.execCommand(\"formatBlock\", false, \"p\");\n        }\n       } else {\n        document.execCommand(command, false, value);\n       }\n      }\n      document.addEventListener(\"DOMContentLoaded\", () => {\n       const modal = document.getElementById(\"note-editor-modal\");\n       const saveBtn = document.getElementById(\"note-editor-save\");\n       const cancelBtn = document.getElementById(\"note-editor-cancel\");\n       const headingSelect = document.getElementById(\"note-heading-select\");\n       const content = document.getElementById(\"note-editor-content\");\n       saveBtn?.addEventListener(\"click\", saveNoteEditor);\n       cancelBtn?.addEventListener(\"click\", closeNoteEditor);\n       modal?.addEventListener(\"click\", (e) => {\n        if (e.target === modal) closeNoteEditor();\n       });\n\n       document.querySelectorAll(\".note-editor-toolbar:not(#secret-editor-toolbar) button[data-command]\").forEach(btn => {\n        btn.addEventListener(\"click\", (e) => {\n         e.preventDefault();\n         execNoteCommand(btn.dataset.command);\n        });\n       });\n       headingSelect?.addEventListener(\"change\", (e) => {\n        execNoteCommand(\"heading\", e.target.value);\n        e.target.value = \"\";\n       });\n       content?.addEventListener(\"keydown\", (e) => {\n        if (e.ctrlKey || e.metaKey) {\n         if (e.key === \"b\") { e.preventDefault(); execNoteCommand(\"bold\"); }\n         if (e.key === \"i\") { e.preventDefault(); execNoteCommand(\"italic\"); }\n         if (e.key === \"u\") { e.preventDefault(); execNoteCommand(\"underline\"); }\n        }\n       });\n       content?.addEventListener(\"paste\", (e) => {\n        const sel = window.getSelection();\n        if (sel.anchorNode) {\n         const parent = sel.anchorNode.parentElement;\n         if (parent && (parent.tagName === \"CODE\" || parent.tagName === \"PRE\" || parent.closest(\"pre\"))) {\n          e.preventDefault();\n          const text = e.clipboardData.getData(\"text/plain\");\n          document.execCommand(\"insertText\", false, text);\n         }\n        }\n       });\n      });\n      function showNoteViewer(content) {\n       const modal = document.getElementById(\"note-editor-modal\");\n       const titleEl = document.getElementById(\"note-editor-title\");\n       const contentEl = document.getElementById(\"note-editor-content\");\n       const toolbar = modal.querySelector(\".note-editor-toolbar\");\n       const saveBtn = document.getElementById(\"note-editor-save\");\n       const cancelBtn = document.getElementById(\"note-editor-cancel\");\n       titleEl.textContent = t(\"noteEditor.viewNote\");\n       contentEl.innerHTML = content;\n       contentEl.contentEditable = \"false\";\n       toolbar.style.display = \"none\";\n       saveBtn.style.display = \"none\";\n       cancelBtn.textContent = t(\"ui.buttons.close\");\n       modal.classList.add(\"active\");\n       const closeViewer = () => {\n        modal.classList.remove(\"active\");\n        contentEl.contentEditable = \"true\";\n        toolbar.style.display = \"\";\n        saveBtn.style.display = \"\";\n        cancelBtn.textContent = t(\"ui.buttons.cancel\");\n        cancelBtn.removeEventListener(\"click\", closeViewer);\n        modal.removeEventListener(\"click\", bgClose);\n       };\n       const bgClose = (e) => { if (e.target === modal) closeViewer(); };\n       cancelBtn.addEventListener(\"click\", closeViewer);\n       modal.addEventListener(\"click\", bgClose);\n      }\n      function renderNotesFeed(notes, containerId, elementType, elementId, onUpdate) {\n       const container = document.getElementById(containerId);\n       if (!container) return;\n       container.innerHTML = \"\";\n       container.className = \"notes-feed\";\n       if (!notes || notes.length === 0) {\n        container.innerHTML = '<div style=\"color: var(--text-soft); font-size: 13px; text-align: center; padding: 10px;\">No notes yet</div>';\n        return;\n       }\n       notes.forEach((note, idx) => {\n        const noteContent = typeof note === \"string\" ? note : (note.content || \"\");\n        const card = document.createElement(\"div\");\n        card.className = \"note-card\";\n        card.innerHTML = `\n         <div class=\"note-card-content\">${noteContent}</div>\n         <div class=\"note-card-actions\">\n          <button class=\"view-note\" data-index=\"${idx}\">View</button>\n          <button class=\"edit-note\" data-index=\"${idx}\">Edit</button>\n          <button class=\"delete-note\" data-index=\"${idx}\">Delete</button>\n         </div>\n        `;\n\n        card.querySelector(\".view-note\").addEventListener(\"click\", (e) => {\n         e.stopPropagation();\n         showNoteViewer(noteContent);\n        });\n\n        card.querySelector(\".edit-note\").addEventListener(\"click\", (e) => {\n         e.stopPropagation();\n         showNoteEditor(t(\"noteEditor.editNote\"), noteContent, (newContent, editIdx) => {\n          pushUndo(\"edit note\");\n          if (typeof notes[editIdx] === \"string\") {\n           notes[editIdx] = newContent;\n          } else {\n           notes[editIdx].content = newContent;\n          }\n          renderNotesFeed(notes, containerId, elementType, elementId, onUpdate);\n          if (onUpdate) onUpdate();\n         }, idx);\n        });\n\n        card.querySelector(\".delete-note\").addEventListener(\"click\", (e) => {\n         e.stopPropagation();\n         pushUndo(\"delete note\");\n         notes.splice(idx, 1);\n         renderNotesFeed(notes, containerId, elementType, elementId, onUpdate);\n         if (onUpdate) onUpdate();\n        });\n        container.appendChild(card);\n       });\n      }\n       function challengeTheImmortal(message, onConfirm) {\n        const modal = document.getElementById(\"confirm-modal\");\n        const messageEl = document.getElementById(\"confirm-message\");\n        const deleteBtn = document.getElementById(\"confirm-delete\");\n        const cancelBtn = document.getElementById(\"confirm-cancel\");\n        messageEl.textContent = message;\n        modal.classList.add(\"active\");\n\t    const cleanup = () => {\n         modal.classList.remove(\"active\");\n         deleteBtn.removeEventListener(\"click\", handleConfirm);\n         cancelBtn.removeEventListener(\"click\", handleCancel);\n         modal.removeEventListener(\"click\", bgHandler);\n        };\n        const handleConfirm = () => {\n         onConfirm();\n         cleanup();\n        };\n        const handleCancel = () => {\n         cleanup();\n        };\n        const bgHandler = (e) => {\n         if (e.target === modal) handleCancel();\n        };\n        deleteBtn.addEventListener(\"click\", handleConfirm);\n        cancelBtn.addEventListener(\"click\", handleCancel);\n        modal.addEventListener(\"click\", bgHandler);\n       }\n       const pageTitleEl = document.getElementById(\"page-title\");\n       if (pageTitleEl) {\n        pageTitleEl.addEventListener(\"click\", () => {\n         showEditModal(t(\"editModal.editTitle\"), PAGE_STATE.title || DEFAULT_PAGE_STATE.title,\n          (newTitle) => {\n           PAGE_STATE.title = newTitle;\n           wieldThePower();\n          });\n        });\n       }\n       function editNodeName(id) {\n        if (!NODE_DATA[id]) return;\n        showEditModal(t(\"editModal.editName\"), NODE_DATA[id].name, (newName) => {\n         if (!NODE_DATA[id]) return;\n         pushUndo(\"edit node name\");\n         NODE_DATA[id].name = newName;\n         const nodeGroup = document.querySelector(`g[data-node-id=\"${id}\"]`);\n         if (nodeGroup) {\n          const label = nodeGroup.querySelector(\".node-label\");\n          if (label) label.textContent = newName;\n         }\n         if (currentNodeId === id) {\n          document.getElementById(\"node-name\").textContent = newName;\n         }\n        });\n       }\n       function editNodeIp(id) {\n        if (!NODE_DATA[id]) return;\n        const labels = LANG.modes[PAGE_STATE.mappingMode] || LANG.modes.network;\n        showEditModal(\"Edit \" + labels.subtitle, NODE_DATA[id].ip, (newIp) => {\n         if (!NODE_DATA[id]) return;\n         pushUndo(\"edit node ip\");\n         NODE_DATA[id].ip = newIp;\n         const nodeGroup = document.querySelector(`g[data-node-id=\"${id}\"]`);\n         if (nodeGroup) {\n          const sub = nodeGroup.querySelector(\".node-sub\");\n          if (sub) sub.textContent = newIp;\n         }\n         if (currentNodeId === id) {\n          const nodeIpEl = document.getElementById(\"node-ip\");\n          if (newIp) {\n            nodeIpEl.textContent = newIp;\n            nodeIpEl.style.opacity = \"1\";\n          } else {\n            nodeIpEl.textContent = \"Click to add \" + labels.subtitle;\n            nodeIpEl.style.opacity = \"0.5\";\n          }\n         }\n        });\n       }\n       function claimTheImmortal(id) {\n\t   if (isViewOnly()) return;\n        if (!NODE_DATA[id]) return;\n        currentNodeId = id;\n        currentEdgeId = null;\n        currentRectId = null;\n        currentTextId = null;\n        const data = NODE_DATA[id];\n        document.querySelectorAll(\".node-group\").forEach((n) => {\n         n.classList.toggle(\"active\", n.dataset.nodeId === id);\n        });\n        document.querySelectorAll(\".edge\").forEach((e) => {\n        const active = e.dataset.from === id || e.dataset.to === id;\n        e.classList.toggle(\"active\", active);\n       });\n       document.querySelectorAll(\".rect-group\").forEach((r) => r.classList.remove(\"active\"));\n       document.querySelectorAll(\".text-element\").forEach((t) => t.classList.remove(\"active\"));\n       document.getElementById(\"node-panel\").style.display = \"block\";\n        document.getElementById(\"edge-panel\").style.display = \"none\";\n        document.getElementById(\"text-panel\").style.display = \"none\";\n        const toolbar = document.getElementById(\"topology-toolbar\");\n        if (!topologyToolbarCollapsed) {\n         toolbar.style.display = \"flex\";\n        }\n        updateTopologyToolbarVisibility();\n        document.getElementById(\"node-name\").textContent = data.name;\n        const nodeIpEl2 = document.getElementById(\"node-ip\");\n        const modeLabels2 = LANG.modes[PAGE_STATE.mappingMode] || LANG.modes.network;\n        if (data.ip) {\n          nodeIpEl2.textContent = data.ip;\n          nodeIpEl2.style.opacity = \"1\";\n        } else {\n          nodeIpEl2.textContent = \"Click to add \" + modeLabels2.subtitle;\n          nodeIpEl2.style.opacity = \"0.5\";\n        }\n        const fovSection = document.getElementById(\"fov-section\");\n        if (fovSection) {\n            if (hasCoverageZone(data.shape)) {\n              fovSection.style.display = \"block\";\n              const defaults = getCoverageDefaults(data.shape);\n              document.getElementById(\"fov-enabled\").checked = data.fovEnabled || false;\n              document.getElementById(\"fov-angle\").value = data.fovAngle || defaults.angle;\n              document.getElementById(\"fov-angle-value\").textContent = (data.fovAngle || defaults.angle) + \"°\";\n              document.getElementById(\"fov-distance\").value = data.fovDistance || defaults.distance;\n              document.getElementById(\"fov-distance-value\").textContent = data.fovDistance || defaults.distance;\n              document.getElementById(\"fov-inner-radius\").value = data.fovInnerRadius || 0;\n              document.getElementById(\"fov-inner-radius-value\").textContent = data.fovInnerRadius || 0;\n              document.getElementById(\"fov-rotation\").value = data.fovRotation || 0;\n              document.getElementById(\"fov-rotation-value\").textContent = (data.fovRotation || 0) + \"°\";\n              document.getElementById(\"fov-color\").value = data.fovColor || \"#f59e0b\";\n              document.getElementById(\"fov-opacity\").value = data.fovOpacity || 20;\n              document.getElementById(\"fov-opacity-value\").textContent = (data.fovOpacity || 20) + \"%\";\n              document.getElementById(\"fov-gradient\").checked = data.fovGradient || false;\n              document.getElementById(\"fov-border-color\").value = data.fovBorderColor || \"#f59e0b\";\n              document.getElementById(\"fov-border-width\").value = data.fovBorderWidth !== undefined ? data.fovBorderWidth : 2;\n              document.getElementById(\"fov-border-width-value\").textContent = data.fovBorderWidth !== undefined ? data.fovBorderWidth : 2;\n              document.getElementById(\"fov-border-style\").value = data.fovBorderStyle || \"solid\";\n              document.getElementById(\"fov-border-opacity\").value = data.fovBorderOpacity !== undefined ? data.fovBorderOpacity : 100;\n              document.getElementById(\"fov-border-opacity-value\").textContent = (data.fovBorderOpacity !== undefined ? data.fovBorderOpacity : 100) + \"%\";\n              document.getElementById(\"fov-label\").value = data.fovLabel || \"\";\n              document.getElementById(\"fov-label-position\").value = data.fovLabelPosition || \"center\";\n              document.getElementById(\"fov-label-size\").value = data.fovLabelSize || 14;\n              document.getElementById(\"fov-label-size-value\").textContent = (data.fovLabelSize || 14) + \"px\";\n              document.getElementById(\"fov-label-color\").value = data.fovLabelColor || \"#ffffff\";\n              document.getElementById(\"fov-label-bold\").checked = data.fovLabelBold || false;\n              document.getElementById(\"fov-label-bg\").checked = data.fovLabelBg || false;\n              document.getElementById(\"fov-label-bg-color\").value = data.fovLabelBgColor || \"#000000\";\n              document.getElementById(\"fov-label-offset-x\").value = data.fovLabelOffsetX || 0;\n              document.getElementById(\"fov-label-offset-x-value\").textContent = data.fovLabelOffsetX || 0;\n              document.getElementById(\"fov-label-offset-y\").value = data.fovLabelOffsetY || 0;\n              document.getElementById(\"fov-label-offset-y-value\").textContent = data.fovLabelOffsetY || 0;\n              document.getElementById(\"fov-animate\").checked = data.fovAnimate || false;\n              document.getElementById(\"fov-animation-type\").value = data.fovAnimationType || defaults.animationType;\n              document.getElementById(\"fov-sweep\").value = data.fovSweep || 120;\n              document.getElementById(\"fov-sweep-value\").textContent = (data.fovSweep || 120) + \"°\";\n              document.getElementById(\"fov-speed\").value = data.fovSpeed || 4;\n              document.getElementById(\"fov-speed-value\").textContent = (data.fovSpeed || 4) + \"s\";\n            } else {\n              fovSection.style.display = \"none\";\n            }\n          }\n        document.getElementById(\"node-mac\").textContent = data.mac || \"--\";\n        document.getElementById(\"node-rack\").textContent = data.rackUnit || \"--\";\n        document.getElementById(\"node-uheight\").textContent = (data.uHeight || \"1\") + \"U\";\n        document.getElementById(\"node-role\").textContent = data.role;\n        populateRackDropdown();\n        populateHostedOnDropdown();\n        const assignedRackSelect = document.getElementById(\"node-assigned-rack\");\n        if (assignedRackSelect) {\n         assignedRackSelect.value = data.assignedRack || \"\";\n        }\n        const hostedOnSelect = document.getElementById(\"node-hosted-on\");\n        if (hostedOnSelect) {\n         hostedOnSelect.value = data.hostedOn || \"\";\n        }\n        const rackCapacitySelect = document.getElementById(\"node-rack-capacity\");\n        if (rackCapacitySelect) {\n         rackCapacitySelect.value = data.rackCapacity || \"42\";\n        }\n        const isRack = data.isRack === true;\n        const isAssignedToRack = !!data.assignedRack;\n        const assignedRackRow = document.getElementById(\"assigned-rack-row\");\n        const hostedOnRow = document.getElementById(\"hosted-on-row\");\n        const rackCapacityRow = document.getElementById(\"rack-capacity-row\");\n        const uheightRow = document.getElementById(\"uheight-row\");\n        if (assignedRackRow) assignedRackRow.style.display = isRack ? \"none\" : \"flex\";\n        if (hostedOnRow) hostedOnRow.style.display = isRack ? \"none\" : (isAssignedToRack ? \"flex\" : \"none\");\n        if (rackCapacityRow) rackCapacityRow.style.display = isRack ? \"flex\" : \"none\";\n        if (uheightRow) uheightRow.style.display = isAssignedToRack ? \"flex\" : \"none\";\n\t\tconst delBtn = document.getElementById(\"delete-node-btn\");\n        if (delBtn) delBtn.textContent = isRack ? \"Delete Rack\" : \"Delete Node\";\n\t\tconst rackContentsSection = document.getElementById(\"rack-contents-section\");\n        const rackContentsList = document.getElementById(\"rack-contents-list\");\n        const rackContentsCount = document.getElementById(\"rack-contents-count\");\n        if (rackContentsSection && rackContentsList) {\n        if (isRack) {\n         const nodesInRack = Object.entries(NODE_DATA).filter(([nid, n]) => n.assignedRack === id);\n         rackContentsCount.textContent = nodesInRack.length;\n         if (nodesInRack.length > 0) {\n          rackContentsList.innerHTML = '';\n          nodesInRack.forEach(([nid, n]) => {\n           const div = document.createElement('div');\n           div.style.cssText = 'padding: 8px 4px; border-bottom: 1px solid var(--edge-main); cursor: pointer;';\n           div.onclick = () => claimTheImmortal(nid);\n           const nameSpan = document.createElement('span');\n           nameSpan.style.color = 'var(--text-main)';\n           nameSpan.textContent = n.name;\n           div.appendChild(nameSpan);\n           (n.tags || []).forEach(t => {\n            const tagSpan = document.createElement('span');\n            tagSpan.style.cssText = 'background: var(--accent); color: var(--bg); padding: 2px 6px; border-radius: 4px; font-size: 11px; margin-left: 4px;';\n            tagSpan.textContent = t;\n            div.appendChild(tagSpan);\n           });\n           rackContentsList.appendChild(div);\n          });\n         } else {\n          rackContentsList.innerHTML = '<div style=\"color: var(--text-soft); font-style: italic; padding: 8px 4px;\">' + t(\"emptyStates.noNodesAssigned\") + '</div>';\n         }\n         rackContentsSection.style.display = \"block\";\n         } else {\n         rackContentsSection.style.display = \"none\";\n         }\n        }\n       const connectionsSection = document.getElementById(\"node-connections-section\");\n       const connectionsList = document.getElementById(\"node-connections-list\");\n       const connectionsCount = document.getElementById(\"node-connections-count\");\n       if (connectionsSection && connectionsList && connectionsCount) {\n        const connectedEdges = (EDGE_DATA.list || []).filter(e => e.from === id || e.to === id);\n        connectionsCount.textContent = connectedEdges.length;\n        if (connectedEdges.length > 0) {\n         connectionsList.innerHTML = '';\n         connectedEdges.forEach(e => {\n          const isFrom = e.from === id;\n          const localPort = isFrom ? e.fromPort : e.toPort;\n          const remoteNodeId = isFrom ? e.to : e.from;\n          const remotePort = isFrom ? e.toPort : e.fromPort;\n          const remoteName = NODE_DATA[remoteNodeId]?.name || remoteNodeId;\n          const div = document.createElement('div');\n          div.style.cssText = 'padding: 6px 0; border-bottom: 1px solid var(--edge-main); display: flex; align-items: center; font-size: 13px;';\n          const localSpan = document.createElement('span');\n          localSpan.style.cssText = localPort ? 'color: var(--accent); font-family: monospace; cursor: pointer;' : 'color: var(--text-soft); cursor: pointer;';\n          localSpan.textContent = localPort || '-';\n          localSpan.title = localPort ? 'Click to view connection' : 'Click to set port';\n          localSpan.onclick = async () => { if (localPort) { selectTheConnection(e.id); } else { const nodeName = NODE_DATA[id]?.name || id; const newVal = await showPrompt(t(\"dialogs.enterPort\", { nodeName: nodeName }), ''); if (newVal !== null && newVal !== '') { const isDupe = (EDGE_DATA.list || []).some(x => x.id !== e.id && ((x.from === id && x.fromPort === newVal) || (x.to === id && x.toPort === newVal))); if (isDupe && !await showConfirm(t(\"dialogs.portAlreadyUsed\", { port: newVal, nodeName: nodeName }))) return; if (isFrom) e.fromPort = newVal; else e.toPort = newVal; claimTheImmortal(id); } } };\n          const arrow = document.createElement('span');\n          arrow.style.cssText = 'color: var(--text-soft); margin: 0 8px;';\n          arrow.textContent = '↔';\n          const remoteSpan = document.createElement('span');\n          remoteSpan.style.cssText = 'color: var(--text-main); cursor: pointer;';\n          remoteSpan.textContent = remoteName;\n          remoteSpan.title = 'Click to view connection';\n          remoteSpan.onclick = () => { claimTheImmortal(remoteNodeId); focusOnSelected(); };\n          const remotePortSpan = document.createElement('span');\n          remotePortSpan.style.cssText = remotePort ? 'color: var(--accent); font-family: monospace; margin-left: 6px; cursor: pointer;' : 'color: var(--text-soft); margin-left: 6px; cursor: pointer;';\n          remotePortSpan.textContent = '(' + (remotePort || '-') + ')';\n          remotePortSpan.title = remotePort ? 'Click to view connection' : 'Click to set port';\n          remotePortSpan.onclick = async () => { if (remotePort) { selectTheConnection(e.id); } else { const newVal = await showPrompt(t(\"dialogs.enterPort\", { nodeName: remoteName }), ''); if (newVal !== null && newVal !== '') { const isDupe = (EDGE_DATA.list || []).some(x => x.id !== e.id && ((x.from === remoteNodeId && x.fromPort === newVal) || (x.to === remoteNodeId && x.toPort === newVal))); if (isDupe && !await showConfirm(t(\"dialogs.portAlreadyUsed\", { port: newVal, nodeName: remoteName }))) return; if (isFrom) e.toPort = newVal; else e.fromPort = newVal; claimTheImmortal(id); } } };\n          div.appendChild(localSpan);\n          div.appendChild(arrow);\n          div.appendChild(remoteSpan);\n          div.appendChild(remotePortSpan);\n          connectionsList.appendChild(div);\n         });\n         connectionsSection.style.display = \"block\";\n        } else {\n         connectionsList.innerHTML = '<div style=\"color: var(--text-soft); font-style: italic;\">' + t(\"emptyStates.noConnections\") + '</div>';\n         connectionsSection.style.display = \"block\";\n        }\n       }\n       document.getElementById(\"node-name\").onclick = () => editNodeName(id);\n        document.getElementById(\"node-ip\").onclick = () => editNodeIp(id);\n        document.getElementById(\"node-mac\").onclick = () => editNodeMac(id);\n        document.getElementById(\"node-rack\").onclick = () => editNodeRack(id);\n        document.getElementById(\"node-uheight\").onclick = () => editNodeUHeight(id);\n        document.getElementById(\"fov-enabled\").onchange = function() {\n  if (!currentNodeId) return;\n  pushUndo(\"toggle coverage zone\");\n  NODE_DATA[currentNodeId].fovEnabled = this.checked;\n  if (this.checked && !NODE_DATA[currentNodeId].fovColor) {\n    NODE_DATA[currentNodeId].fovColor = document.getElementById(\"fov-color\").value || \"#f59e0b\";\n  }\n  updateFovCone(currentNodeId);\n  updateZoneLegend();\n};\n        document.getElementById(\"fov-angle\").oninput = function() {\n          document.getElementById(\"fov-angle-value\").textContent = this.value + \"°\";\n          if (!currentNodeId) return;\n          NODE_DATA[currentNodeId].fovAngle = parseInt(this.value);\n          updateFovCone(currentNodeId);\n        };\n        document.getElementById(\"fov-distance\").oninput = function() {\n          document.getElementById(\"fov-distance-value\").textContent = this.value;\n          if (!currentNodeId) return;\n          NODE_DATA[currentNodeId].fovDistance = parseInt(this.value);\n          updateFovCone(currentNodeId);\n        };\n        document.getElementById(\"fov-rotation\").oninput = function() {\n          document.getElementById(\"fov-rotation-value\").textContent = this.value + \"°\";\n          if (!currentNodeId) return;\n          NODE_DATA[currentNodeId].fovRotation = parseInt(this.value);\n          updateFovCone(currentNodeId);\n        };\n        document.getElementById(\"fov-color\").oninput = function() {\n          if (!currentNodeId) return;\n          NODE_DATA[currentNodeId].fovColor = this.value;\n          updateFovCone(currentNodeId);\n          updateZoneLegend();\n        };\n        document.getElementById(\"fov-animate\").onchange = function() {\n          if (!currentNodeId) return;\n          pushUndo(\"toggle fov animation\");\n          NODE_DATA[currentNodeId].fovAnimate = this.checked;\n          updateFovCone(currentNodeId);\n        };\n        document.getElementById(\"fov-sweep\").oninput = function() {\n          document.getElementById(\"fov-sweep-value\").textContent = this.value + \"°\";\n          if (!currentNodeId) return;\n          NODE_DATA[currentNodeId].fovSweep = parseInt(this.value);\n          updateFovCone(currentNodeId);\n        };\n        document.getElementById(\"fov-speed\").oninput = function() {\n          document.getElementById(\"fov-speed-value\").textContent = this.value + \"s\";\n          if (!currentNodeId) return;\n          NODE_DATA[currentNodeId].fovSpeed = parseInt(this.value);\n          updateFovCone(currentNodeId);\n        };\n        document.getElementById(\"fov-inner-radius\").oninput = function() {\n          document.getElementById(\"fov-inner-radius-value\").textContent = this.value;\n          if (!currentNodeId) return;\n          NODE_DATA[currentNodeId].fovInnerRadius = parseInt(this.value);\n          updateFovCone(currentNodeId);\n        };\n        document.getElementById(\"fov-opacity\").oninput = function() {\n          document.getElementById(\"fov-opacity-value\").textContent = this.value + \"%\";\n          if (!currentNodeId) return;\n          NODE_DATA[currentNodeId].fovOpacity = parseInt(this.value);\n          updateFovCone(currentNodeId);\n        };\n        document.getElementById(\"fov-gradient\").onchange = function() {\n          if (!currentNodeId) return;\n          NODE_DATA[currentNodeId].fovGradient = this.checked;\n          updateFovCone(currentNodeId);\n        };\n        document.getElementById(\"fov-border-color\").oninput = function() {\n          if (!currentNodeId) return;\n          NODE_DATA[currentNodeId].fovBorderColor = this.value;\n          updateFovCone(currentNodeId);\n        };\n        document.getElementById(\"fov-border-width\").oninput = function() {\n          document.getElementById(\"fov-border-width-value\").textContent = this.value;\n          if (!currentNodeId) return;\n          NODE_DATA[currentNodeId].fovBorderWidth = parseInt(this.value);\n          updateFovCone(currentNodeId);\n        };\n        document.getElementById(\"fov-border-style\").onchange = function() {\n          if (!currentNodeId) return;\n          NODE_DATA[currentNodeId].fovBorderStyle = this.value;\n          updateFovCone(currentNodeId);\n        };\n        document.getElementById(\"fov-border-opacity\").oninput = function() {\n          document.getElementById(\"fov-border-opacity-value\").textContent = this.value + \"%\";\n          if (!currentNodeId) return;\n          NODE_DATA[currentNodeId].fovBorderOpacity = parseInt(this.value);\n          updateFovCone(currentNodeId);\n        };\n        document.getElementById(\"fov-label\").oninput = function() {\n          if (!currentNodeId) return;\n          NODE_DATA[currentNodeId].fovLabel = this.value;\n          updateFovCone(currentNodeId);\n        };\n        document.getElementById(\"fov-label-position\").onchange = function() {\n          if (!currentNodeId) return;\n          NODE_DATA[currentNodeId].fovLabelPosition = this.value;\n          updateFovCone(currentNodeId);\n        };\n        document.getElementById(\"fov-label-size\").oninput = function() {\n          document.getElementById(\"fov-label-size-value\").textContent = this.value + \"px\";\n          if (!currentNodeId) return;\n          NODE_DATA[currentNodeId].fovLabelSize = parseInt(this.value);\n          updateFovCone(currentNodeId);\n        };\n        document.getElementById(\"fov-label-color\").oninput = function() {\n          if (!currentNodeId) return;\n          NODE_DATA[currentNodeId].fovLabelColor = this.value;\n          updateFovCone(currentNodeId);\n        };\n        document.getElementById(\"fov-label-bold\").onchange = function() {\n          if (!currentNodeId) return;\n          NODE_DATA[currentNodeId].fovLabelBold = this.checked;\n          updateFovCone(currentNodeId);\n        };\n        document.getElementById(\"fov-label-bg\").onchange = function() {\n          if (!currentNodeId) return;\n          NODE_DATA[currentNodeId].fovLabelBg = this.checked;\n          updateFovCone(currentNodeId);\n        };\n        document.getElementById(\"fov-label-bg-color\").oninput = function() {\n          if (!currentNodeId) return;\n          NODE_DATA[currentNodeId].fovLabelBgColor = this.value;\n          updateFovCone(currentNodeId);\n        };\n        document.getElementById(\"fov-label-offset-x\").oninput = function() {\n          document.getElementById(\"fov-label-offset-x-value\").textContent = this.value;\n          if (!currentNodeId) return;\n          NODE_DATA[currentNodeId].fovLabelOffsetX = parseInt(this.value);\n          updateFovCone(currentNodeId);\n        };\n        document.getElementById(\"fov-label-offset-y\").oninput = function() {\n          document.getElementById(\"fov-label-offset-y-value\").textContent = this.value;\n          if (!currentNodeId) return;\n          NODE_DATA[currentNodeId].fovLabelOffsetY = parseInt(this.value);\n          updateFovCone(currentNodeId);\n        };\n        document.getElementById(\"fov-animation-type\").onchange = function() {\n          if (!currentNodeId) return;\n          NODE_DATA[currentNodeId].fovAnimationType = this.value;\n          updateFovCone(currentNodeId);\n        };\n        document.getElementById(\"fov-preset\").addEventListener(\"change\", function() {\n          if (this.value) {\n            applyZonePreset(this.value);\n            this.value = \"\";\n          }\n        });\n        document.getElementById(\"fov-save-preset\").addEventListener(\"click\", saveCustomZonePreset);\n        document.getElementById(\"fov-copy-style\").addEventListener(\"click\", function() {\n          if (currentNodeId && copyZoneStyle(currentNodeId)) {\n            showAlert(t(\"dialogs.zoneStyleCopiedShort\"));\n          }\n        });\n        document.getElementById(\"fov-paste-style\").addEventListener(\"click\", function() {\n          if (currentNodeId && pasteZoneStyle(currentNodeId)) {\n            claimTheImmortal(currentNodeId);\n          }\n        });\n        document.getElementById(\"bulk-zone-copy\").addEventListener(\"click\", bulkCopyZoneStyle);\n        document.getElementById(\"bulk-zone-paste\").addEventListener(\"click\", bulkPasteZoneStyle);\n        document.getElementById(\"bulk-zone-toggle\").addEventListener(\"click\", bulkToggleZones);\n        document.getElementById(\"bulk-zone-copy-mobile\").addEventListener(\"click\", bulkCopyZoneStyle);\n        document.getElementById(\"bulk-zone-paste-mobile\").addEventListener(\"click\", bulkPasteZoneStyle);\n        document.getElementById(\"bulk-zone-toggle-mobile\").addEventListener(\"click\", bulkToggleZones);\n        const currentSize = savedSizes[id] || getDefaultSize();\n        document.getElementById(\"size-slider\").value = currentSize;\n        const currentRotation = NODE_DATA[id].rotation || 0;\n        document.getElementById(\"rotation-slider\").value = Math.max(-360, Math.min(360, currentRotation));\n        document.getElementById(\"rotation-value\").value = currentRotation;\n        const styleEntry = savedStyles[id] || {};\n        const resolvedStyles = resolveStylesEntry(styleEntry);\n        const scopeKey = currentStyleScope || \"all\";\n        const flatProps = [\"circleColor\", \"circleBorder\", \"titleColor\", \"titleFont\", \"titleSize\", \"subColor\", \"subFont\", \"subSize\", ];\n        const isFlat = flatProps.some((p) => Object.prototype.hasOwnProperty.call(styleEntry, p));\n        const scopedStyles = isFlat ? styleEntry : styleEntry[scopeKey] || {};\n        const circleColorInput = document.getElementById(\"circle-color\");\n        const titleColorInput = document.getElementById(\"title-color\");\n        const titleFontSelect = document.getElementById(\"title-font\");\n        const titleSizeInput = document.getElementById(\"title-size\");\n        const subColorInput = document.getElementById(\"sub-color\");\n        const subFontSelect = document.getElementById(\"sub-font\");\n        const subSizeInput = document.getElementById(\"sub-size\");\n        const shapeSelect = document.getElementById(\"shape-select\");\n        const scopeSelect = document.getElementById(\"style-scope\");\n        const catSelect = document.getElementById(\"shape-category-select\");\n        const nodeShape = data.shape || \"circle\";\n        const shapeCategory = findCategoryForShape(nodeShape);\n        if (catSelect) {\n          catSelect.value = shapeCategory;\n          populateShapeSelect(nodeShape);\n        }\n        circleColorInput.value = scopedStyles.circleColor || resolvedStyles.circleColor || PAGE_STATE.nodeFill || \"#1e293b\";\n       const circleBorderInput = document.getElementById(\"circle-border\");\n       circleBorderInput.value = scopedStyles.circleBorder || resolvedStyles.circleBorder || PAGE_STATE.nodeStroke || \"#475569\";\n        titleColorInput.value = scopedStyles.titleColor || resolvedStyles.titleColor || PAGE_STATE.textMain || \"#e2e8f0\";\n        titleFontSelect.value = scopedStyles.titleFont || resolvedStyles.titleFont || \"system-ui, sans-serif\";\n        titleSizeInput.value = scopedStyles.titleSize || resolvedStyles.titleSize || 18;\n        subColorInput.value = scopedStyles.subColor || resolvedStyles.subColor || PAGE_STATE.textSoft || \"#94a3b8\";\n        subFontSelect.value = scopedStyles.subFont || resolvedStyles.subFont || \"system-ui, sans-serif\";\n        subSizeInput.value = scopedStyles.subSize || resolvedStyles.subSize || 13;\n        const hasWebIcon = resolvedStyles.icon && resolvedStyles.icon.library;\n        const customOpt = shapeSelect.querySelector('option[value=\"custom-icon\"]');\n        if (hasWebIcon) {\n         if (!customOpt) {\n          const opt = document.createElement('option');\n          opt.value = 'custom-icon';\n          opt.textContent = 'Custom Icon';\n          shapeSelect.insertBefore(opt, shapeSelect.firstChild);\n         }\n         shapeSelect.value = 'custom-icon';\n        } else {\n         if (customOpt) customOpt.remove();\n        }\n        const layerSelect = document.getElementById(\"node-layer\");\n        if (layerSelect) {\n         layerSelect.value = data.layer || \"layer1\";\n        }\n        const trailToggle = document.getElementById(\"node-trail-enabled\");\n        if (trailToggle) {\n         trailToggle.checked = data.trailEnabled !== false;\n        }\n        scopeSelect.value = currentStyleScope || \"all\";\n        document.getElementById(\"title-offset-y\").value = scopedStyles.titleOffsetY || resolvedStyles.titleOffsetY || 0;\n        document.getElementById(\"title-offset-x\").value = scopedStyles.titleOffsetX || resolvedStyles.titleOffsetX || 0;\n        document.getElementById(\"sub-offset-y\").value = scopedStyles.subOffsetY || resolvedStyles.subOffsetY || 0;\n        document.getElementById(\"sub-offset-x\").value = scopedStyles.subOffsetX || resolvedStyles.subOffsetX || 0;\n      const pingOffsetXInput = document.getElementById(\"ping-offset-x\");\n      const pingOffsetYInput = document.getElementById(\"ping-offset-y\");\n      if (pingOffsetXInput && pingOffsetYInput) {\n      pingOffsetXInput.value =\n       (scopedStyles.pingOffsetX !== undefined\n         ? scopedStyles.pingOffsetX\n         : (resolvedStyles.pingOffsetX !== undefined\n             ? resolvedStyles.pingOffsetX\n             : 0));\n      pingOffsetYInput.value =\n       (scopedStyles.pingOffsetY !== undefined\n         ? scopedStyles.pingOffsetY\n         : (resolvedStyles.pingOffsetY !== undefined\n             ? resolvedStyles.pingOffsetY\n             : 0));\n      }\n        const tagEl = document.getElementById(\"node-tags\");\n        tagEl.innerHTML = \"\";\n        data.tags.forEach((tag, i) => {\n         const b = document.createElement(\"span\");\n         b.className = \"badge\";\n         const isIconTag = typeof tag === 'object' && tag.type === 'icon';\n         if (!isIconTag && typeof tag === 'string' && tag.toLowerCase().includes(\"wg\")) b.classList.add(\"wg\");\n         b.style.cursor = \"pointer\";\n         b.style.position = \"relative\";\n         const tagContent = document.createElement(\"span\");\n         if (isIconTag) {\n          b.classList.add(\"icon-badge\");\n          IconLibrary.getIcon(tag.library, tag.name).then(svgText => {\n           if (svgText) {\n            const parser = new DOMParser();\n            const doc = parser.parseFromString(svgText, 'image/svg+xml');\n            const svgEl = doc.querySelector('svg');\n            if (svgEl) {\n             svgEl.setAttribute('width', '16');\n             svgEl.setAttribute('height', '16');\n             tagContent.innerHTML = '';\n             tagContent.appendChild(svgEl);\n             const nameSpan = document.createElement('span');\n             nameSpan.textContent = tag.name;\n             nameSpan.style.marginLeft = '4px';\n             tagContent.appendChild(nameSpan);\n            }\n           }\n          });\n         } else {\n          tagContent.textContent = tag;\n          tagContent.addEventListener(\"click\", (e) => {\n           e.stopPropagation();\n           showEditModal(t(\"editModal.editTag\"), tag, (newTag) => {\n            if (newTag) {\n             pushUndo(\"edit tag\");\n             data.tags[i] = newTag;\n             claimTheImmortal(id);\n            }\n           });\n          });\n         }\n         const deleteTag = document.createElement(\"span\");\n         deleteTag.textContent = \" ✕\";\n         deleteTag.style.opacity = \"0.6\";\n         deleteTag.style.marginLeft = \"4px\";\n         deleteTag.style.fontSize = \"10px\";\n         deleteTag.addEventListener(\"click\", (e) => {\n          e.stopPropagation();\n          pushUndo(\"delete tag\");\n          data.tags.splice(i, 1);\n          claimTheImmortal(id);\n         });\n         b.append(tagContent, deleteTag);\n         tagEl.append(b);\n        });\n        const addTagBtn = document.createElement(\"span\");\n        addTagBtn.className = \"badge\";\n        addTagBtn.style.cursor = \"pointer\";\n        addTagBtn.style.opacity = \"0.6\";\n        addTagBtn.style.borderStyle = \"dashed\";\n        addTagBtn.textContent = t(\"ui.buttons.addTag\");\n        addTagBtn.addEventListener(\"click\", () => {\n         showEditModal(t(\"editModal.addTags\"), \"\",\n          (newTagStr) => {\n           if (newTagStr) {\n            pushUndo(\"add tags\");\n            const newTags = newTagStr.split(\",\").map((t) => t.trim()).filter((t) => t);\n            newTags.forEach((t) => data.tags.push(t));\n            claimTheImmortal(id);\n           }\n          });\n        });\n        tagEl.append(addTagBtn);\n        renderNotesFeed(data.notes, \"node-notes\", \"node\", id);\n        const addLineSelect = document.getElementById(\"add-line-select\");\n      addLineSelect.innerHTML = \"\";\n      const currentRack = data.assignedRack || \"\";\n      Object.entries(NODE_DATA).forEach(([nodeId, node]) => {\n      if (nodeId !== id) {\n      const nodeRack = node.assignedRack || \"\";\n      if (currentRack === nodeRack) {\n      const opt = document.createElement(\"option\");\n      opt.value = nodeId;\n      opt.textContent = node.name;\n      addLineSelect.appendChild(opt);\n      }\n      }\n      });\n        const pingEnabled = data.ping && data.ping.enabled;\n        document.getElementById('node-pingable').checked = pingEnabled;\n        document.getElementById('node-ping-options').style.display = pingEnabled ? 'block' : 'none';\n        if (data.ping) {\n         document.getElementById('node-ping-protocol').value = data.ping.protocol || 'http';\n         document.getElementById('node-custom-url').value = data.ping.customUrl || '';\n         document.getElementById('node-ping-timeout').value = data.ping.timeout || 3000;\n         document.getElementById('node-custom-url-container').style.display =\n          data.ping.protocol === 'custom' ? 'block' : 'none';\n         updatePingStatusDisplay(id);\n        }\n       }\n       function updatePingStatusDisplay(nodeId) {\n        const data = NODE_DATA[nodeId];\n        if (!data || !data.ping) return;\n        const statusEl = document.getElementById('node-ping-status');\n        const lastCheckEl = document.getElementById('node-ping-last-check');\n        const statusColors = {\n         online: 'var(--accent)',\n         offline: 'var(--danger)',\n         checking: '#f59e0b',\n         unknown: 'var(--text-soft)'\n        };\n        const statusTexts = {\n         online: '● Online',\n         offline: '● Offline',\n         checking: '● Checking...',\n         unknown: '● Unknown'\n        };\n        const statusText = statusTexts[data.ping.status] || statusTexts.unknown;\n\t\tstatusEl.textContent = data.ping.responseTime ? `${statusText} (${data.ping.responseTime}ms)` : statusText;\n        statusEl.style.color = statusColors[data.ping.status] || statusColors.unknown;\n        if (data.ping.lastCheck) {\n         const checkTime = new Date(data.ping.lastCheck);\n         lastCheckEl.textContent = `Last checked: ${checkTime.toLocaleTimeString()}`;\n        } else {\n         lastCheckEl.textContent = 'Never checked';\n        }\n       }\n       function selectTheConnection(id) {\n\t   if (isViewOnly()) return;\n        currentEdgeId = id;\n        currentNodeId = null;\n        currentRectId = null;\n        currentTextId = null;\n\t\tif (isMobileDevice()) {\n        const currentHeight = PAGE_STATE.mobileFooterHeight || 40;\n        if (currentHeight <= 20) {\n         PAGE_STATE.mobileFooterHeight = 80;\n         const root = document.documentElement;\n         root.style.setProperty(\"--mobile-footer-height\", \"80vh\");\n         const mobileFooterHeightInput = document.getElementById(\"mobile-footer-height\");\n         const mobileFooterHeightVal = document.getElementById(\"mobile-footer-height-val\");\n         if (mobileFooterHeightInput) mobileFooterHeightInput.value = 80;\n         if (mobileFooterHeightVal) mobileFooterHeightVal.textContent = \"80%\";\n        }\n       }\n        document.getElementById(\"node-panel\").style.display = \"none\";\n        document.getElementById(\"edge-panel\").style.display = \"block\";\n        document.getElementById(\"text-panel\").style.display = \"none\";\n       document.getElementById(\"rect-panel\").style.display = \"none\";\n       document.getElementById(\"topology-toolbar\").style.display = \"none\";\n       document.querySelectorAll(\".node-group\").forEach((n) => n.classList.remove(\"active\"));\n       document.querySelectorAll(\".rect-group\").forEach((r) => r.classList.remove(\"active\"));\n       document.querySelectorAll(\".text-element\").forEach((t) => t.classList.remove(\"active\"));\n       document.querySelectorAll(\".edge\").forEach((e) => {\n        e.classList.toggle(\"active\", e.dataset.edgeId === id);\n       });\n        const edge = EDGE_DATA.list.find((e) => e.id === id);\n        if (!edge) return;\n        const directionSymbols = {\n         none: \"⇄\",\n         forward: \"→\",\n         backward: \"←\",\n         both: \"↔\",\n        };\n        const dirSymbol = directionSymbols[edge.direction] || \"⇄\";\n        let titleText = \"Custom line\";\n        if (edge.from || edge.to) {\n         const fromName = edge.from ? NODE_DATA[edge.from]?.name || edge.from : \"\";\n         const toName = edge.to ? NODE_DATA[edge.to]?.name || edge.to : \"\";\n         titleText = `${fromName || \"?\"} ${dirSymbol} ${toName || \"?\"}`;\n        }\n        document.getElementById(\"edge-title\").textContent = titleText;\n        const widthInput = document.getElementById(\"edge-width\");\n        const colorInput = document.getElementById(\"edge-color\");\n        const directionSelect = document.getElementById(\"edge-direction\");\n        const lineStyleSelect = document.getElementById(\"edge-line-style\");\n        const routingSelect = document.getElementById(\"edge-routing\");\n        widthInput.value = edge.width;\n        colorInput.value = edge.color;\n        directionSelect.value = edge.direction || \"none\";\n        lineStyleSelect.value = edge.lineStyle || \"solid\";\n        routingSelect.value = edge.routing || \"curved\";\n        const waypointsRow = document.getElementById(\"waypoints-row\");\n        const waypoints = edge.waypoints || [];\n        if (edge.type !== \"custom\" && waypoints.length > 0) {\n         waypointsRow.style.display = \"flex\";\n         document.getElementById(\"waypoints-label\").textContent = `${t(\"ui.labels.waypoints\")} (${waypoints.length}):`;\n        } else {\n         waypointsRow.style.display = \"none\";\n        }\n        document.getElementById(\"edge-animate\").checked = edge.animate === true;\n        document.getElementById(\"edge-animation-style\").value = edge.animationStyle || \"\";\n        document.getElementById(\"edge-animation-speed\").value = edge.animationSpeed || \"\";\n        const fromPortInput = document.getElementById(\"edge-from-port\");\n        const toPortInput = document.getElementById(\"edge-to-port\");\n        const portFieldsFrom = document.getElementById(\"edge-port-fields\");\n        const portFieldsTo = document.getElementById(\"edge-port-fields-to\");\n        if (edge.type === \"custom\") {\n         if (portFieldsFrom) portFieldsFrom.style.display = \"none\";\n         if (portFieldsTo) portFieldsTo.style.display = \"none\";\n        } else {\n         if (portFieldsFrom) portFieldsFrom.style.display = \"flex\";\n         if (portFieldsTo) portFieldsTo.style.display = \"flex\";\n         if (fromPortInput) {\n          fromPortInput.value = edge.fromPort || \"\";\n          fromPortInput.onchange = () => updateEdgePortLabels(id);\n         }\n         if (toPortInput) {\n          toPortInput.value = edge.toPort || \"\";\n          toPortInput.onchange = () => updateEdgePortLabels(id);\n         }\n        }\n        renderNotesFeed(edge.notes, \"edge-notes\", \"edge\", id);\n       if (edge.type === \"custom\" && Array.isArray(edge.points)) {\n        forgeTheTopology();\n       }\n      }\n       window.addEventListener(\"resize\", () => {\n        forgeTheTopology();\n        if (currentEdgeId) {\n         selectTheConnection(currentEdgeId);\n        } else if (currentNodeId && NODE_DATA[currentNodeId]) {\n         claimTheImmortal(currentNodeId);\n        } else {\n         const availableNodes = Object.keys(NODE_DATA);\n         if (availableNodes.length > 0) {\n          claimTheImmortal(availableNodes[0]);\n         }\n        }\n       });\n       (function initZoomPan() {\n       const viewport = document.getElementById(\"canvas-viewport\");\n       const svg = document.getElementById(\"map\");\n       const hint = document.getElementById(\"canvas-hint\");\n       hint.style.cursor = \"pointer\";\n       let hintDismissed = false;\n       const dismissHint = () => { hintDismissed = true; hint.classList.remove(\"visible\"); };\n       hint.addEventListener(\"click\", dismissHint);\n       hint.addEventListener(\"touchend\", (e) => { e.preventDefault(); dismissHint(); });\n       setTimeout(() => {\n        if (hintDismissed) return;\n        hint.classList.add(\"visible\");\n        setTimeout(() => { if (!hintDismissed) hint.classList.remove(\"visible\"); }, 4000);\n       }, 1000);\n        viewport.addEventListener(\"wheel\",\n         (e) => {\n          e.preventDefault();\n          const rect = viewport.getBoundingClientRect();\n          const mouseX = (e.clientX - rect.left) / rect.width;\n          const mouseY = (e.clientY - rect.top) / rect.height;\n          const delta = e.deltaY > 0 ? 0.9 : 1.1;\n          zoomTo(canvasState.zoom * delta, mouseX, mouseY);\n         }, {\n          passive: false\n         });\n        let initialPinchDistance = 0;\n        let initialPinchZoom = 1;\n        let pinchCenter = {\n         x: 0.5,\n         y: 0.5\n        };\n        let threeFingerTapStart = 0;\n        viewport.addEventListener(\"touchstart\",\n         (e) => {\n          if (e.touches.length === 3) {\n           e.preventDefault();\n           threeFingerTapStart = Date.now();\n          }\n          if (e.touches.length === 2) {\n           e.preventDefault();\n           const touch1 = e.touches[0];\n           const touch2 = e.touches[1];\n           initialPinchDistance = Math.hypot(touch2.clientX - touch1.clientX, touch2.clientY - touch1.clientY);\n           initialPinchZoom = canvasState.zoom;\n           const rect = viewport.getBoundingClientRect();\n           const centerX = (touch1.clientX + touch2.clientX) / 2;\n           const centerY = (touch1.clientY + touch2.clientY) / 2;\n           pinchCenter.x = (centerX - rect.left) / rect.width;\n           pinchCenter.y = (centerY - rect.top) / rect.height;\n          }\n         }, {\n          passive: false\n         });\n        viewport.addEventListener(\"touchend\", (e) => {\n         if (e.touches.length === 0 && threeFingerTapStart > 0) {\n          const duration = Date.now() - threeFingerTapStart;\n          if (duration < 500) {\n           e.preventDefault();\n           undo();\n           if (navigator.vibrate) navigator.vibrate([50, 30, 50]);\n          }\n          threeFingerTapStart = 0;\n         }\n        }, { passive: false });\n        viewport.addEventListener(\"touchmove\",\n         (e) => {\n          if (e.touches.length === 2) {\n           e.preventDefault();\n           const touch1 = e.touches[0];\n           const touch2 = e.touches[1];\n           const currentDistance = Math.hypot(touch2.clientX - touch1.clientX, touch2.clientY - touch1.clientY);\n           if (initialPinchDistance > 0) {\n            const scale = currentDistance / initialPinchDistance;\n            const newZoom = initialPinchZoom * scale;\n            zoomTo(newZoom, pinchCenter.x, pinchCenter.y);\n           }\n          }\n         }, {\n          passive: false\n         });\n        let panStartViewX = 0;\n        let panStartViewY = 0;\n        let lastEmptyTapTime = 0;\n        let emptyTapTimeout = null;\n        let emptyTapMoved = false;\n        let emptyTapStartX = 0;\n        let emptyTapStartY = 0;\n        viewport.addEventListener(\"touchend\", (e) => {\n          if (currentView.mode !== \"rack\") return;\n          if (e.changedTouches.length !== 1) return;\n      const isNodeOrEdge = e.target.closest(\".node-group\") || e.target.closest(\".edge-group\");\n         if (isNodeOrEdge) return;\n          if (emptyTapMoved) {\n            emptyTapMoved = false;\n            return;\n          }\n          const currentTime = new Date().getTime();\n          const tapGap = currentTime - lastEmptyTapTime;\n          if (tapGap < 300 && tapGap > 0) {\n            e.preventDefault();\n            exitRack();\n            if (navigator.vibrate) {\n              navigator.vibrate(50);\n            }\n            lastEmptyTapTime = 0;\n            if (emptyTapTimeout) {\n              clearTimeout(emptyTapTimeout);\n              emptyTapTimeout = null;\n            }\n          } else {\n            lastEmptyTapTime = currentTime;\n            if (emptyTapTimeout) clearTimeout(emptyTapTimeout);\n            emptyTapTimeout = setTimeout(() => {\n              lastEmptyTapTime = 0;\n            }, 300);\n          }\n        }, { passive: false });\n        viewport.addEventListener(\"mousedown\", (e) => {\n         if (e.target.closest(\".draw-toolbar\") || e.target.closest(\".topology-toolbar\") || e.target.closest(\".legend-container\")) {\n          return;\n         }\n         if (freeDrawMode || rectDrawMode) {\n          return;\n         }\n      const isEmptySpace = e.target === svg || e.target === viewport || e.target.tagName === \"svg\";\n      if (isEmptySpace) {\n        if (currentNodeId) {\n          currentNodeId = null;\n          document.getElementById(\"node-panel\").style.display = \"none\";\n          document.querySelectorAll(\".node-group\").forEach(n => n.classList.remove(\"active\"));\n        }\n        if (currentEdgeId) {\n          currentEdgeId = null;\n          document.getElementById(\"edge-panel\").style.display = \"none\";\n        }\n        if (currentRectId) {\n          currentRectId = null;\n          document.getElementById(\"rect-panel\").style.display = \"none\";\n        }\n        if (currentTextId) {\n          currentTextId = null;\n          document.getElementById(\"text-panel\").style.display = \"none\";\n        }\n        if (currentImageId) {\n          currentImageId = null;\n          document.getElementById(\"image-panel\").style.display = \"none\";\n        }\n        forgeTheTopology();\n        updateTopologyToolbarVisibility();\n      }\n         if (isViewOnly()) {\n         document.body.classList.remove(\"view-only-inspect\");\n         document.getElementById(\"node-panel\").style.display = \"none\";\n         document.getElementById(\"edge-panel\").style.display = \"none\";\n        }\n         if (isEmptySpace && e.shiftKey && e.button === 0) {\n         if (isViewOnly()) return;\n         e.preventDefault();\n         startSelection(e);\n         return;\n        }\n        if (isEmptySpace || e.button === 2 || e.button === 1) {\n          e.preventDefault();\n          canvasState.isPanning = true;\n          canvasState.panStartX = e.clientX;\n          canvasState.panStartY = e.clientY;\n          panStartViewX = canvasState.panX;\n          panStartViewY = canvasState.panY;\n          viewport.classList.add(\"panning\");\n         }\n        });\n        viewport.addEventListener(\"touchstart\",\n         (e) => {\n          if (e.target.closest(\".draw-toolbar\") || e.target.closest(\".topology-toolbar\") || e.target.closest(\".legend-container\")) {\n           return;\n          }\n          if (freeDrawMode || rectDrawMode) {\n           return;\n          }\n      const isEmptySpace = e.target === svg || e.target === viewport || e.target.tagName === \"svg\";\n      if (isEmptySpace) {\n        if (currentNodeId) {\n          currentNodeId = null;\n          document.getElementById(\"node-panel\").style.display = \"none\";\n          document.querySelectorAll(\".node-group\").forEach(n => n.classList.remove(\"active\"));\n        }\n        if (currentEdgeId) {\n          currentEdgeId = null;\n          document.getElementById(\"edge-panel\").style.display = \"none\";\n        }\n        if (currentRectId) {\n          currentRectId = null;\n          document.getElementById(\"rect-panel\").style.display = \"none\";\n        }\n        if (currentTextId) {\n          currentTextId = null;\n          document.getElementById(\"text-panel\").style.display = \"none\";\n        }\n        if (currentImageId) {\n          currentImageId = null;\n          document.getElementById(\"image-panel\").style.display = \"none\";\n        }\n        forgeTheTopology();\n        updateTopologyToolbarVisibility();\n      }\n          if (isEmptySpace && e.touches.length === 1) {\n           e.preventDefault();\n           emptyTapMoved = false;\n           emptyTapStartX = e.touches[0].clientX;\n           emptyTapStartY = e.touches[0].clientY;\n           canvasState.isPanning = true;\n           canvasState.panStartX = e.touches[0].clientX;\n           canvasState.panStartY = e.touches[0].clientY;\n           panStartViewX = canvasState.panX;\n           panStartViewY = canvasState.panY;\n           viewport.classList.add(\"panning\");\n          }\n         }, {\n          passive: false\n         });\n        let panRAFPending = false;\n       let lastPanEvent = null;\n       document.addEventListener(\"mousemove\", (e) => {\n        if (isSelecting) {\n         updateSelection(e);\n         return;\n        }\n        if (!canvasState.isPanning) return;\n        lastPanEvent = e;\n        if (!panRAFPending) {\n         panRAFPending = true;\n         requestAnimationFrame(() => {\n          if (lastPanEvent && canvasState.isPanning) {\n           const dx = lastPanEvent.clientX - canvasState.panStartX;\n           const dy = lastPanEvent.clientY - canvasState.panStartY;\n           const rect = viewport.getBoundingClientRect();\n           const viewWidth = CANVAS_WIDTH / canvasState.zoom;\n           const viewHeight = CANVAS_HEIGHT / canvasState.zoom;\n           const canvasDx = (dx / rect.width) * viewWidth;\n           const canvasDy = (dy / rect.height) * viewHeight;\n           canvasState.panX = panStartViewX - canvasDx;\n           canvasState.panY = panStartViewY - canvasDy;\n           constrainPan();\n           updateViewBox();\n          }\n          panRAFPending = false;\n         });\n        }\n       });\n       document.addEventListener(\"touchmove\", (e) => {\n        if (!canvasState.isPanning || !e.touches[0]) return;\n        const touchX = e.touches[0].clientX;\n        const touchY = e.touches[0].clientY;\n        const moveDx = Math.abs(touchX - emptyTapStartX);\n        const moveDy = Math.abs(touchY - emptyTapStartY);\n        if (moveDx > 15 || moveDy > 15) {\n         emptyTapMoved = true;\n        }\n        if (!panRAFPending) {\n         panRAFPending = true;\n         requestAnimationFrame(() => {\n          if (canvasState.isPanning) {\n           const dx = touchX - canvasState.panStartX;\n           const dy = touchY - canvasState.panStartY;\n           const rect = viewport.getBoundingClientRect();\n           const viewWidth = CANVAS_WIDTH / canvasState.zoom;\n           const viewHeight = CANVAS_HEIGHT / canvasState.zoom;\n           const canvasDx = (dx / rect.width) * viewWidth;\n           const canvasDy = (dy / rect.height) * viewHeight;\n           canvasState.panX = panStartViewX - canvasDx;\n           canvasState.panY = panStartViewY - canvasDy;\n           constrainPan();\n           updateViewBox();\n          }\n          panRAFPending = false;\n         });\n        }\n       });\n        document.addEventListener(\"mouseup\", () => {\n         if (isSelecting) {\n          endSelection();\n         }\n         if (canvasState.isPanning) {\n          canvasState.isPanning = false;\n          viewport.classList.remove(\"panning\");\n         }\n        });\n        document.addEventListener(\"touchend\", () => {\n         if (canvasState.isPanning) {\n          canvasState.isPanning = false;\n          viewport.classList.remove(\"panning\");\n         }\n        });\n        document.addEventListener(\"keydown\", (e) => {\n         const isEditing = document.activeElement.tagName === \"INPUT\" || document.activeElement.tagName === \"TEXTAREA\" || document.activeElement.isContentEditable;\n         if (e.code === \"Space\" && !e.repeat && !isEditing) {\n          e.preventDefault();\n          canvasState.spacePressed = true;\n          viewport.style.cursor = \"grab\";\n         }\n        });\n        document.addEventListener(\"keyup\", (e) => {\n         if (e.code === \"Space\") {\n          canvasState.spacePressed = false;\n          viewport.style.cursor = \"\";\n         }\n        });\n        document.getElementById(\"zoom-in-btn\").addEventListener(\"click\", () => {\n         zoomTo(canvasState.zoom * 1.25, 0.5, 0.5);\n        });\n        document.getElementById(\"zoom-out-btn\").addEventListener(\"click\", () => {\n         zoomTo(canvasState.zoom * 0.8, 0.5, 0.5);\n        });\n        document.getElementById(\"zoom-fit-btn\").addEventListener(\"click\", fitToContent);\n        document.getElementById(\"zoom-reset-btn\").addEventListener(\"click\", resetView);\n        const minimapContainer = document.getElementById(\"minimap-container\");\n        const minimapSvg = document.getElementById(\"minimap\");\n        let minimapDragging = false;\n        minimapContainer.addEventListener(\"mousedown\", (e) => {\n         e.preventDefault();\n         minimapDragging = true;\n         updatePanFromMinimap(e);\n        });\n        minimapContainer.addEventListener(\"touchstart\",\n         (e) => {\n          e.preventDefault();\n          minimapDragging = true;\n          updatePanFromMinimapTouch(e);\n         }, {\n          passive: false\n         });\n        document.addEventListener(\"mousemove\", (e) => {\n         if (minimapDragging) {\n          updatePanFromMinimap(e);\n         }\n        });\n        document.addEventListener(\"touchmove\", (e) => {\n         if (minimapDragging && e.touches[0]) {\n          updatePanFromMinimapTouch(e);\n         }\n        });\n        document.addEventListener(\"mouseup\", () => {\n         minimapDragging = false;\n        });\n        document.addEventListener(\"touchend\", () => {\n         minimapDragging = false;\n        });\n        function updatePanFromMinimap(e) {\n         const rect = minimapContainer.getBoundingClientRect();\n         const x = (e.clientX - rect.left) / rect.width;\n         const y = (e.clientY - rect.top) / rect.height;\n         const viewWidth = CANVAS_WIDTH / canvasState.zoom;\n         const viewHeight = CANVAS_HEIGHT / canvasState.zoom;\n         canvasState.panX = x * CANVAS_WIDTH - viewWidth / 2;\n         canvasState.panY = y * CANVAS_HEIGHT - viewHeight / 2;\n         constrainPan();\n         updateViewBox();\n        }\n        function updatePanFromMinimapTouch(e) {\n         const rect = minimapContainer.getBoundingClientRect();\n         const touch = e.touches[0];\n         const x = (touch.clientX - rect.left) / rect.width;\n         const y = (touch.clientY - rect.top) / rect.height;\n         const viewWidth = CANVAS_WIDTH / canvasState.zoom;\n         const viewHeight = CANVAS_HEIGHT / canvasState.zoom;\n         canvasState.panX = x * CANVAS_WIDTH - viewWidth / 2;\n         canvasState.panY = y * CANVAS_HEIGHT - viewHeight / 2;\n         constrainPan();\n         updateViewBox();\n        }\n        document.addEventListener(\"keydown\", (e) => {\n         if (document.activeElement.tagName === \"INPUT\" || document.activeElement.tagName === \"TEXTAREA\") return;\n         if (\n          (e.key === \"+\" || e.key === \"=\") && (e.ctrlKey || e.metaKey)) {\n          e.preventDefault();\n          zoomTo(canvasState.zoom * 1.25, 0.5, 0.5);\n         } else if (e.key === \"-\" && (e.ctrlKey || e.metaKey)) {\n          e.preventDefault();\n          zoomTo(canvasState.zoom * 0.8, 0.5, 0.5);\n         } else if (e.key === \"0\" && (e.ctrlKey || e.metaKey)) {\n          e.preventDefault();\n          resetView();\n         }\n        });\n        setTimeout(() => {\n         fitToContent();\n        }, 100);\n       })();\n       const sizeSlider = document.getElementById(\"size-slider\");\n       const sizeValue = document.getElementById(\"size-value\");\n       const resetSizeBtn = document.getElementById(\"reset-size\");\n       sizeSlider.addEventListener(\"input\", () => {\n        const newSize = parseInt(sizeSlider.value, 10);\n        sizeValue.textContent = newSize;\n        pushUndo(\"resize node\");\n        savedSizes[currentNodeId] = newSize;\n        const nodeGroup = document.querySelector(`g[data-node-id=\"${currentNodeId}\"]`);\n        if (nodeGroup) {\n         const oldShape = nodeGroup.querySelector(\".node-circle\");\n         if (oldShape) oldShape.remove();\n         const newShape = createNodeShape(currentNodeId, newSize);\n         nodeGroup.insertBefore(newShape, nodeGroup.firstChild);\n         const styles = resolveStylesForNode(currentNodeId);\n         if (styles.circleColor) newShape.style.fill = styles.circleColor;\n      if (styles.circleBorder) newShape.style.stroke = styles.circleBorder;\n         const label = nodeGroup.querySelector(\".node-label\");\n         const sub = nodeGroup.querySelector(\".node-sub\");\n         if (label) {\n          label.setAttribute(\"y\", -newSize - 5);\n          const labelSize = styles.titleSize || newSize * 0.33;\n          label.style.fontSize = labelSize + \"px\";\n         }\n         if (sub) {\n          sub.setAttribute(\"y\", newSize + 20);\n          const subSize = styles.subSize || newSize * 0.24;\n          sub.style.fontSize = subSize + \"px\";\n         }\n       updatePingIndicator(currentNodeId);\n        }\n\t\tupdateMinimap();\n       });\n       resetSizeBtn.addEventListener(\"click\", () => {\n        pushUndo(\"reset size\");\n        delete savedSizes[currentNodeId];\n        const defaultSize = getDefaultSize();\n        sizeSlider.value = defaultSize;\n        sizeValue.textContent = defaultSize;\n        const nodeGroup = document.querySelector(`g[data-node-id=\"${currentNodeId}\"]`);\n        if (nodeGroup) {\n         const oldShape = nodeGroup.querySelector(\".node-circle\");\n         if (oldShape) oldShape.remove();\n         const shapeType = (NODE_DATA[currentNodeId] && NODE_DATA[currentNodeId].shape) || \"circle\";\n         const newShape = createNodeShape(currentNodeId, defaultSize);\n         nodeGroup.insertBefore(newShape, nodeGroup.firstChild);\n         const styles = resolveStylesForNode(currentNodeId);\n         if (styles.circleColor) newShape.style.fill = styles.circleColor;\n      if (styles.circleBorder) newShape.style.stroke = styles.circleBorder;\n         const label = nodeGroup.querySelector(\".node-label\");\n         const sub = nodeGroup.querySelector(\".node-sub\");\n         if (label) {\n          label.setAttribute(\"y\", -defaultSize - 5);\n          const labelSize = styles.titleSize || defaultSize * 0.33;\n          label.style.fontSize = labelSize + \"px\";\n         }\n         if (sub) {\n          sub.setAttribute(\"y\", defaultSize + 20);\n          const subSize = styles.subSize || defaultSize * 0.24;\n          sub.style.fontSize = subSize + \"px\";\n         }\n          updatePingIndicator(currentNodeId);\n        }\n\t\tupdateMinimap();\n       });\n       const rotationSlider = document.getElementById(\"rotation-slider\");\n       const rotationInput = document.getElementById(\"rotation-value\");\n       const resetRotationBtn = document.getElementById(\"reset-rotation\");\n       rotationSlider.addEventListener(\"input\", () => {\n         const newRotation = parseInt(rotationSlider.value, 10);\n         rotationInput.value = newRotation;\n         pushUndo(\"rotate node\");\n         NODE_DATA[currentNodeId].rotation = newRotation;\n         const nodeGroup = document.querySelector(`g[data-node-id=\"${currentNodeId}\"]`);\n         if (nodeGroup) {\n           const pos = savedPositions[currentNodeId] || { x: 0, y: 0 };\n           nodeGroup.setAttribute(\"transform\", `translate(${pos.x},${pos.y}) rotate(${newRotation})`);\n         }\n       });\n       rotationInput.addEventListener(\"input\", () => {\n         const newRotation = parseInt(rotationInput.value, 10) || 0;\n         rotationSlider.value = Math.max(-360, Math.min(360, newRotation));\n         pushUndo(\"rotate node\");\n         NODE_DATA[currentNodeId].rotation = newRotation;\n         const nodeGroup = document.querySelector(`g[data-node-id=\"${currentNodeId}\"]`);\n         if (nodeGroup) {\n           const pos = savedPositions[currentNodeId] || { x: 0, y: 0 };\n           nodeGroup.setAttribute(\"transform\", `translate(${pos.x},${pos.y}) rotate(${newRotation})`);\n         }\n       });\n       resetRotationBtn.addEventListener(\"click\", () => {\n         pushUndo(\"reset rotation\");\n         NODE_DATA[currentNodeId].rotation = 0;\n         rotationSlider.value = 0;\n         rotationInput.value = 0;\n         const nodeGroup = document.querySelector(`g[data-node-id=\"${currentNodeId}\"]`);\n         if (nodeGroup) {\n           const pos = savedPositions[currentNodeId] || { x: 0, y: 0 };\n           nodeGroup.setAttribute(\"transform\", `translate(${pos.x},${pos.y}) rotate(0)`);\n         }\n       });\n       const applyStyle = (property, value) => {\n        pushUndo(\"style change\");\n        const styleEntry = ensureStyleEntry(currentNodeId);\n        const scopeKey = currentStyleScope || \"all\";\n        if (!styleEntry[scopeKey]) styleEntry[scopeKey] = {};\n        styleEntry[scopeKey][property] = value;\n        const nodeGroup = document.querySelector(`g[data-node-id=\"${currentNodeId}\"]`);\n        if (!nodeGroup) return;\n        const shapeEl = nodeGroup.querySelector(\".node-circle\");\n        const label = nodeGroup.querySelector(\".node-label\");\n        const sub = nodeGroup.querySelector(\".node-sub\");\n        const isWebIcon = shapeEl && shapeEl.querySelector('svg');\n        if (property === \"circleColor\" && shapeEl) {\n         if (isWebIcon) {\n          shapeEl.querySelectorAll('svg, path, circle, rect, polygon, ellipse').forEach(el => el.style.fill = value);\n         } else {\n          shapeEl.style.fill = value;\n         }\n        } else if (property === \"circleBorder\" && shapeEl) {\n         if (isWebIcon) {\n          shapeEl.querySelectorAll('svg, path, circle, rect, polygon, ellipse').forEach(el => el.style.stroke = value);\n         } else {\n          shapeEl.style.stroke = value;\n         }\n        } else if (property === \"titleColor\" && label) label.style.fill = value;\n        else if (property === \"titleFont\" && label) label.style.fontFamily = value;\n        else if (property === \"titleSize\" && label) label.style.fontSize = value + \"px\";\n        else if (property === \"subColor\" && sub) sub.style.fill = value;\n        else if (property === \"subFont\" && sub) sub.style.fontFamily = value;\n        else if (property === \"subSize\" && sub) sub.style.fontSize = value + \"px\";\n       };\n\n       document.getElementById(\"circle-color\").addEventListener(\"input\", (e) => applyStyle(\"circleColor\", e.target.value));\n      document.getElementById(\"circle-border\").addEventListener(\"input\", (e) => applyStyle(\"circleBorder\", e.target.value));\n       document.getElementById(\"title-color\").addEventListener(\"input\", (e) => applyStyle(\"titleColor\", e.target.value));\n       document.getElementById(\"title-font\").addEventListener(\"change\", (e) => applyStyle(\"titleFont\", e.target.value));\n       document.getElementById(\"title-size\").addEventListener(\"input\", (e) => applyStyle(\"titleSize\", parseInt(e.target.value, 10)));\n       document.getElementById(\"sub-color\").addEventListener(\"input\", (e) => applyStyle(\"subColor\", e.target.value));\n       document.getElementById(\"sub-font\").addEventListener(\"change\", (e) => applyStyle(\"subFont\", e.target.value));\n       document.getElementById(\"sub-size\").addEventListener(\"input\", (e) => applyStyle(\"subSize\", parseInt(e.target.value, 10)));\n       document.getElementById(\"title-offset-y\").addEventListener(\"input\", (e) => {\n        applyStyle(\"titleOffsetY\", parseInt(e.target.value, 10) || 0);\n        forgeTheTopology();\n        if (currentNodeId) claimTheImmortal(currentNodeId);\n       });\n       document.getElementById(\"title-offset-x\").addEventListener(\"input\", (e) => {\n        applyStyle(\"titleOffsetX\", parseInt(e.target.value, 10) || 0);\n        forgeTheTopology();\n        if (currentNodeId) claimTheImmortal(currentNodeId);\n       });\n       document.getElementById(\"sub-offset-y\").addEventListener(\"input\", (e) => {\n        applyStyle(\"subOffsetY\", parseInt(e.target.value, 10) || 0);\n        forgeTheTopology();\n        if (currentNodeId) claimTheImmortal(currentNodeId);\n       });\n       document.getElementById(\"sub-offset-x\").addEventListener(\"input\", (e) => {\n        applyStyle(\"subOffsetX\", parseInt(e.target.value, 10) || 0);\n        forgeTheTopology();\n        if (currentNodeId) claimTheImmortal(currentNodeId);\n       });\n      document.getElementById(\"ping-offset-x\").addEventListener(\"input\", (e) => {\n      applyStyle(\"pingOffsetX\", parseInt(e.target.value, 10) || 0);\n      updatePingIndicator(currentNodeId);\n      if (currentNodeId) claimTheImmortal(currentNodeId);\n      });\n      document.getElementById(\"ping-offset-y\").addEventListener(\"input\", (e) => {\n      applyStyle(\"pingOffsetY\", parseInt(e.target.value, 10) || 0);\n      updatePingIndicator(currentNodeId);\n      if (currentNodeId) claimTheImmortal(currentNodeId);\n      });\n       document.getElementById(\"reset-styles\").addEventListener(\"click\", () => {\n        delete savedStyles[currentNodeId];\n        forgeTheTopology();\n        claimTheImmortal(currentNodeId);\n       });\n       document.getElementById(\"style-scope\").addEventListener(\"change\", (e) => {\n        currentStyleScope = e.target.value || \"all\";\n        claimTheImmortal(currentNodeId);\n       });\n       document.getElementById(\"shape-select\").addEventListener(\"change\", (e) => {\n        if (!currentNodeId || !NODE_DATA[currentNodeId]) return;\n        const shape = e.target.value || \"circle\";\n        if (shape === 'custom-icon') return;\n        pushUndo(\"change shape\");\n        NODE_DATA[currentNodeId].shape = shape;\n        const fovSection = document.getElementById(\"fov-section\");\n        if (fovSection) {\n          const oldShape = NODE_DATA[currentNodeId].shape;\n          fovSection.style.display = hasCoverageZone(shape) ? \"block\" : \"none\";\n          if (hasCoverageZone(shape) && !hasCoverageZone(oldShape)) {\n            const defaults = getCoverageDefaults(shape);\n            document.getElementById(\"fov-angle\").value = defaults.angle;\n            document.getElementById(\"fov-angle-value\").textContent = defaults.angle + \"°\";\n            document.getElementById(\"fov-distance\").value = defaults.distance;\n            document.getElementById(\"fov-distance-value\").textContent = defaults.distance;\n            document.getElementById(\"fov-animation-type\").value = defaults.animationType;\n            NODE_DATA[currentNodeId].fovAngle = defaults.angle;\n            NODE_DATA[currentNodeId].fovDistance = defaults.distance;\n            NODE_DATA[currentNodeId].fovAnimationType = defaults.animationType;\n          }\n        }\n        if (savedStyles[currentNodeId]) {\n         Object.keys(savedStyles[currentNodeId]).forEach(scope => {\n          if (savedStyles[currentNodeId][scope] && savedStyles[currentNodeId][scope].icon) {\n           delete savedStyles[currentNodeId][scope].icon;\n          }\n         });\n        }\n        const customOpt = e.target.querySelector('option[value=\"custom-icon\"]');\n        if (customOpt) customOpt.remove();\n        forgeTheTopology();\n       });\n       document.getElementById(\"add-note-btn\")?.addEventListener(\"click\", () => {\n        if (!currentNodeId) return;\n        showNoteEditor(t(\"noteEditor.addNote\"), \"\", (content) => {\n         pushUndo(\"add note\");\n         NODE_DATA[currentNodeId].notes.push(content);\n         renderNotesFeed(NODE_DATA[currentNodeId].notes, \"node-notes\", \"node\", currentNodeId);\n        });\n       });\n       document.getElementById('node-pingable').addEventListener('change', (e) => {\n        if (!currentNodeId || !NODE_DATA[currentNodeId]) return;\n        if (!NODE_DATA[currentNodeId].ping) {\n         NODE_DATA[currentNodeId].ping = {\n          enabled: false,\n          protocol: 'http',\n          customUrl: '',\n          timeout: 3000,\n          status: 'unknown',\n          lastCheck: null\n         };\n        }\n        NODE_DATA[currentNodeId].ping.enabled = e.target.checked;\n        document.getElementById('node-ping-options').style.display = e.target.checked ? 'block' : 'none';\n        forgeTheTopology();\n       });\n       document.getElementById('node-ping-protocol').addEventListener('change', (e) => {\n        if (!currentNodeId || !NODE_DATA[currentNodeId].ping) return;\n        NODE_DATA[currentNodeId].ping.protocol = e.target.value;\n        document.getElementById('node-custom-url-container').style.display =\n         e.target.value === 'custom' ? 'block' : 'none';\n       });\n       document.getElementById('node-custom-url').addEventListener('change', (e) => {\n        if (!currentNodeId || !NODE_DATA[currentNodeId].ping) return;\n        NODE_DATA[currentNodeId].ping.customUrl = e.target.value.trim();\n       });\n       document.getElementById('node-ping-timeout').addEventListener('change', (e) => {\n        if (!currentNodeId || !NODE_DATA[currentNodeId].ping) return;\n        NODE_DATA[currentNodeId].ping.timeout = parseInt(e.target.value) || 3000;\n       });\n       document.getElementById('check-ping-now').addEventListener('click', () => {\n        if (!currentNodeId) return;\n        checkNodeStatus(currentNodeId);\n       });\n       document.getElementById(\"edge-width\").addEventListener(\"input\", () => {\n        if (!currentEdgeId) return;\n        const edge = EDGE_DATA.list.find((e) => e.id === currentEdgeId);\n        if (!edge) return;\n        const v = parseInt(document.getElementById(\"edge-width\").value, 10);\n        if (Number.isNaN(v) || v <= 0) return;\n        pushUndo(\"edit edge\");\n        edge.width = v;\n        const el = document.querySelector(`.edge[data-edge-id=\"${currentEdgeId}\"]`);\n        if (el) el.style.strokeWidth = v;\n       });\n       document.getElementById(\"edge-color\").addEventListener(\"input\", () => {\n        if (!currentEdgeId) return;\n        const edge = EDGE_DATA.list.find((e) => e.id === currentEdgeId);\n        if (!edge) return;\n        const color = document.getElementById(\"edge-color\").value;\n        pushUndo(\"edit edge\");\n        edge.color = color;\n        const el = document.querySelector(`.edge[data-edge-id=\"${currentEdgeId}\"]`);\n        if (el) el.style.stroke = color;\n        forgeTheLegend();\n\t\tupdateZoneLegend();\n       });\n       document.getElementById(\"edge-direction\").addEventListener(\"change\", () => {\n        if (!currentEdgeId) return;\n        const edge = EDGE_DATA.list.find((e) => e.id === currentEdgeId);\n        if (!edge) return;\n        pushUndo(\"edit edge\");\n        edge.direction = document.getElementById(\"edge-direction\").value;\n        forgeTheTopology();\n        selectTheConnection(currentEdgeId);\n       });\n       document.getElementById(\"edge-line-style\").addEventListener(\"change\", () => {\n        if (!currentEdgeId) return;\n        const edge = EDGE_DATA.list.find((e) => e.id === currentEdgeId);\n        if (!edge) return;\n        pushUndo(\"edit edge\");\n        edge.lineStyle = document.getElementById(\"edge-line-style\").value;\n        forgeTheTopology();\n        selectTheConnection(currentEdgeId);\n       });\n       document.getElementById(\"edge-routing\").addEventListener(\"change\", () => {\n        if (!currentEdgeId) return;\n        const edge = EDGE_DATA.list.find((e) => e.id === currentEdgeId);\n        if (!edge) return;\n        pushUndo(\"edit edge routing\");\n        edge.routing = document.getElementById(\"edge-routing\").value;\n        forgeTheTopology();\n        selectTheConnection(currentEdgeId);\n       });\n       document.getElementById(\"edge-animate\").addEventListener(\"change\", (e) => {\n        if (!currentEdgeId) return;\n        const edge = EDGE_DATA.list.find((ed) => ed.id === currentEdgeId);\n        if (!edge) return;\n        pushUndo(\"edit edge animate\");\n        edge.animate = e.target.checked;\n        forgeTheTopology();\n        selectTheConnection(currentEdgeId);\n       });\n       document.getElementById(\"animation-style-select\").addEventListener(\"change\", (e) => {\n        PAGE_STATE.animationStyle = e.target.value;\n        if (PAGE_STATE.animateConnections) forgeTheTopology();\n       });\n       document.getElementById(\"animation-direction-select\").addEventListener(\"change\", (e) => {\n        PAGE_STATE.animationDirection = e.target.value;\n        if (PAGE_STATE.animateConnections) forgeTheTopology();\n       });\n       document.getElementById(\"animation-speed-select\").addEventListener(\"change\", (e) => {\n        PAGE_STATE.animationSpeed = parseFloat(e.target.value);\n        if (PAGE_STATE.animateConnections) forgeTheTopology();\n       });\n       document.getElementById(\"edge-animation-style\").addEventListener(\"change\", (e) => {\n        if (!currentEdgeId) return;\n        const edge = EDGE_DATA.list.find((ed) => ed.id === currentEdgeId);\n        if (!edge) return;\n        pushUndo(\"edit edge animation style\");\n        edge.animationStyle = e.target.value || \"\";\n        if (PAGE_STATE.animateConnections || edge.animate === true) forgeTheTopology();\n        selectTheConnection(currentEdgeId);\n       });\n       document.getElementById(\"edge-animation-speed\").addEventListener(\"change\", (e) => {\n        if (!currentEdgeId) return;\n        const edge = EDGE_DATA.list.find((ed) => ed.id === currentEdgeId);\n        if (!edge) return;\n        pushUndo(\"edit edge animation speed\");\n        edge.animationSpeed = e.target.value || \"\";\n        if (PAGE_STATE.animateConnections || edge.animate === true) forgeTheTopology();\n        selectTheConnection(currentEdgeId);\n       });\n       document.getElementById(\"add-edge-note\")?.addEventListener(\"click\", () => {\n        if (!currentEdgeId) return;\n        const edge = EDGE_DATA.list.find((e) => e.id === currentEdgeId);\n        if (!edge) return;\n        showNoteEditor(t(\"noteEditor.addNote\"), \"\", (content) => {\n         pushUndo(\"add edge note\");\n         edge.notes.push(content);\n         renderNotesFeed(edge.notes, \"edge-notes\", \"edge\", currentEdgeId);\n        });\n       });\n      function selectTheRect(id) {\n\t  if (isViewOnly()) return;\n      currentRectId = id;\n      currentNodeId = null;\n      currentEdgeId = null;\n      currentTextId = null;\n      document.getElementById(\"node-panel\").style.display = \"none\";\n      document.getElementById(\"edge-panel\").style.display = \"none\";\n      document.getElementById(\"text-panel\").style.display = \"none\";\n      document.getElementById(\"rect-panel\").style.display = \"block\";\n      document.getElementById(\"topology-toolbar\").style.display = \"none\";\n      document.querySelectorAll(\".node-group\").forEach(n => n.classList.remove(\"active\"));\n      document.querySelectorAll(\".edge\").forEach(e => e.classList.remove(\"active\"));\n      document.querySelectorAll(\".text-element\").forEach(t => t.classList.remove(\"active\"));\n      document.querySelectorAll(\".rect-group\").forEach(r => r.classList.toggle(\"active\", r.dataset.rectId === id));\n      const rect = RECT_DATA.list.find(r => r.id === id);\n      if (!rect) return;\n      document.getElementById(\"rect-title\").textContent = \"\";\n      document.getElementById(\"rect-color\").value = rect.color || \"#f97316\";\n      document.getElementById(\"rect-border-color\").value = rect.borderColor || rect.color || \"#f97316\";\n      document.getElementById(\"rect-border-width\").value = rect.borderWidth !== undefined ? rect.borderWidth : 2;\n      document.getElementById(\"rect-style-select\").value = rect.style || \"filled\";\n      document.getElementById(\"rect-line-style\").value = rect.lineStyle || \"solid\";\n      document.getElementById(\"rect-rotation\").value = Math.max(-360, Math.min(360, rect.rotation || 0));\n      document.getElementById(\"rect-rotation-value\").value = rect.rotation || 0;\n      const opacityVal = rect.fillOpacity !== undefined ? Math.round(rect.fillOpacity * 100) : 30;\n      document.getElementById(\"rect-fill-opacity\").value = opacityVal;\n      document.getElementById(\"rect-fill-opacity-value\").textContent = opacityVal + \"%\";\n      document.getElementById(\"rect-fill-row\").style.display = (rect.style === \"outlined\") ? \"none\" : \"flex\";\n      document.getElementById(\"rect-opacity-row\").style.display = (rect.style === \"outlined\") ? \"none\" : \"flex\";\n      renderNotesFeed(rect.notes || [], \"rect-notes\", \"rect\", id);\n      forgeTheTopology();\n      }\n      document.getElementById(\"rect-color\").addEventListener(\"input\", () => {\n      if (!currentRectId) return;\n      const rect = RECT_DATA.list.find(r => r.id === currentRectId);\n      if (!rect) return;\n      pushUndo(\"edit zone\");\n      rect.color = document.getElementById(\"rect-color\").value;\n      forgeTheTopology();\n      });\n      document.getElementById(\"rect-border-color\").addEventListener(\"input\", () => {\n      if (!currentRectId) return;\n      const rect = RECT_DATA.list.find(r => r.id === currentRectId);\n      if (!rect) return;\n      pushUndo(\"edit zone\");\n      rect.borderColor = document.getElementById(\"rect-border-color\").value;\n      forgeTheTopology();\n      });\n      document.getElementById(\"rect-border-width\").addEventListener(\"input\", () => {\n      if (!currentRectId) return;\n      const rect = RECT_DATA.list.find(r => r.id === currentRectId);\n      if (!rect) return;\n      pushUndo(\"edit zone\");\n      rect.borderWidth = parseInt(document.getElementById(\"rect-border-width\").value) || 2;\n      forgeTheTopology();\n      });\n      document.getElementById(\"rect-style-select\").addEventListener(\"change\", () => {\n      if (!currentRectId) return;\n      const rect = RECT_DATA.list.find(r => r.id === currentRectId);\n      if (!rect) return;\n      pushUndo(\"edit zone\");\n      rect.style = document.getElementById(\"rect-style-select\").value;\n      forgeTheTopology();\n      selectTheRect(currentRectId);\n      });\n\t  document.getElementById(\"rect-rotation\").addEventListener(\"input\", () => {\n        if (!currentRectId) return;\n        const rect = RECT_DATA.list.find(r => r.id === currentRectId);\n        if (!rect) return;\n        pushUndo(\"rotate zone\");\n        rect.rotation = parseInt(document.getElementById(\"rect-rotation\").value) || 0;\n        document.getElementById(\"rect-rotation-value\").value = rect.rotation;\n        forgeTheTopology();\n      });\n      document.getElementById(\"rect-rotation-value\").addEventListener(\"input\", () => {\n        if (!currentRectId) return;\n        const rect = RECT_DATA.list.find(r => r.id === currentRectId);\n        if (!rect) return;\n        pushUndo(\"rotate zone\");\n        const val = parseInt(document.getElementById(\"rect-rotation-value\").value) || 0;\n        rect.rotation = val;\n        document.getElementById(\"rect-rotation\").value = Math.max(-360, Math.min(360, val));\n        forgeTheTopology();\n      });\n      document.getElementById(\"rect-line-style\").addEventListener(\"change\", () => {\n      if (!currentRectId) return;\n      pushUndo(\"change zone line style\");\n      const rect = RECT_DATA.list.find(r => r.id === currentRectId);\n      if (!rect) return;\n      rect.lineStyle = document.getElementById(\"rect-line-style\").value;\n      forgeTheTopology();\n      });\n      document.getElementById(\"rect-fill-opacity\").addEventListener(\"input\", () => {\n      if (!currentRectId) return;\n      const rect = RECT_DATA.list.find(r => r.id === currentRectId);\n      if (!rect) return;\n      pushUndo(\"change zone fill opacity\");\n      const val = parseInt(document.getElementById(\"rect-fill-opacity\").value);\n      rect.fillOpacity = val / 100;\n      document.getElementById(\"rect-fill-opacity-value\").textContent = val + \"%\";\n      forgeTheTopology();\n      });\n      document.getElementById(\"add-rect-note\").addEventListener(\"click\", () => {\n        if (!currentRectId) return;\n        const rect = RECT_DATA.list.find(r => r.id === currentRectId);\n        if (!rect) return;\n        if (!rect.notes) rect.notes = [];\n        showNoteEditor(t(\"noteEditor.addNote\"), \"\", (content) => {\n          pushUndo(\"add zone note\");\n          rect.notes.push(content);\n          renderNotesFeed(rect.notes, \"rect-notes\", \"rect\", currentRectId);\n        });\n      });\n      document.getElementById(\"delete-rect\").addEventListener(\"click\", () => {\n      if (!currentRectId) return;\n      challengeTheImmortal(\"Delete this box?\", () => {\n      pushUndo(\"delete zone\");\n      RECT_DATA.list = RECT_DATA.list.filter(r => r.id !== currentRectId);\n      currentRectId = null;\n      document.getElementById(\"rect-panel\").style.display = \"none\";\n      forgeTheTopology();\n      });\n      });\n       document.getElementById(\"delete-edge\").addEventListener(\"click\", () => {\n        if (!currentEdgeId) return;\n        challengeTheImmortal(\"Are you sure you want to delete this line?\",\n         () => {\n          pushUndo(\"delete edge\");\n          EDGE_DATA.list = EDGE_DATA.list.filter(\n           (e) => e.id !== currentEdgeId);\n          currentEdgeId = null;\n          forgeTheTopology();\n          const availableNodes = Object.keys(NODE_DATA);\n          if (availableNodes.length > 0) {\n           claimTheImmortal(availableNodes[0]);\n          } else {\n           document.getElementById(\"node-panel\").style.display = \"none\";\n           document.getElementById(\"edge-panel\").style.display = \"none\";\n           document.getElementById(\"topology-toolbar\", ).style.display = \"none\";\n          }\n         });\n       });\n       document.getElementById(\"clear-waypoints-btn\").addEventListener(\"click\", () => {\n        if (!currentEdgeId) return;\n        const edge = EDGE_DATA.list.find(e => e.id === currentEdgeId);\n        if (!edge || !edge.waypoints || edge.waypoints.length === 0) return;\n        pushUndo(\"clear waypoints\");\n        edge.waypoints = [];\n        selectTheConnection(currentEdgeId);\n        forgeTheTopology();\n       });\n       document.getElementById(\"add-line-btn\").addEventListener(\"click\", () => {\n        if (!currentNodeId) return;\n        const select = document.getElementById(\"add-line-select\");\n        const directionSelect = document.getElementById(\"add-line-direction\");\n        const colorInput = document.getElementById(\"add-line-color\");\n        const routingSelect = document.getElementById(\"add-line-routing\");\n        const targetId = select.value;\n        if (!targetId || targetId === currentNodeId) return;\n        const direction = directionSelect.value || \"none\";\n        const lineColor = colorInput.value || \"#475569\";\n        const routing = routingSelect.value || PAGE_STATE.defaultEdgeRouting || \"orthogonal\";\n        const newId = `${currentNodeId}-${targetId}-${Date.now()}`;\n        const newEdge = {\n         id: newId,\n         from: currentNodeId,\n         to: targetId,\n         width: 4,\n         color: lineColor,\n         direction: direction,\n         routing: routing,\n         type: \"main\",\n         notes: [],\n         fromPort: \"\",\n         toPort: \"\",\n         lineStyle: \"solid\",\n         waypoints: [],\n        };\n        pushUndo(\"add edge\");\n        EDGE_DATA.list.push(newEdge);\n        forgeTheTopology();\n        claimTheImmortal(currentNodeId);\n       });\n       let rectStartPoint = null;\n       let rectPreviewEl = null;\n       let rectStyle = \"filled\";\n       let freeDrawPoints = [];\n       let freeDrawPolylineEl = null;\n       let freeDrawPointEls = [];\n       const drawToggleBtn = document.getElementById(\"draw-toggle\");\n       const drawUndoBtn = document.getElementById(\"draw-undo\");\n       const drawColorInput = document.getElementById(\"draw-color\");\n       const drawStyleSelect = document.getElementById(\"draw-style\");\n       const drawArrowSelect = document.getElementById(\"draw-arrow\");\n       const svgMap = document.getElementById(\"map\");\n       function updateFreeDrawGraphics() {\n        const ns = \"http://www.w3.org/2000/svg\";\n        const svg = svgMap;\n        if (!freeDrawPolylineEl && freeDrawPoints.length > 0) {\n         freeDrawPolylineEl = document.createElementNS(ns, \"polyline\");\n         freeDrawPolylineEl.classList.add(\"edge\", \"free-preview\");\n         freeDrawPolylineEl.setAttribute(\"fill\", \"none\");\n         svg.appendChild(freeDrawPolylineEl);\n        }\n        if (freeDrawPolylineEl) {\n         if (freeDrawPoints.length === 0) {\n          freeDrawPolylineEl.remove();\n          freeDrawPolylineEl = null;\n         } else {\n          const ptsStr = freeDrawPoints.map((p) => `${p.x},${p.y}`).join(\" \");\n          freeDrawPolylineEl.setAttribute(\"points\", ptsStr);\n          freeDrawPolylineEl.style.stroke = drawColorInput.value || \"#475569\";\n          freeDrawPolylineEl.style.strokeWidth = 3;\n          const lineStyle = drawStyleSelect.value || \"solid\";\n          if (lineStyle === \"dashed\") {\n           freeDrawPolylineEl.style.strokeDasharray = \"10,5\";\n          } else if (lineStyle === \"dotted\") {\n           freeDrawPolylineEl.style.strokeDasharray = \"2,4\";\n          } else {\n           freeDrawPolylineEl.style.strokeDasharray = \"none\";\n          }\n         }\n        }\n        freeDrawPointEls.forEach((el) => el.remove());\n        freeDrawPointEls = [];\n        freeDrawPoints.forEach((p, idx) => {\n         const c = document.createElementNS(ns, \"circle\");\n         c.classList.add(\"free-point\");\n         c.setAttribute(\"cx\", p.x);\n         c.setAttribute(\"cy\", p.y);\n         c.setAttribute(\"r\", 5);\n         c.dataset.index = String(idx);\n         c.addEventListener(\"mousedown\", (e) => {\n          if (!freeDrawMode) return;\n          e.preventDefault();\n          e.stopPropagation();\n          let dragging = true;\n          const svgEl = svgMap;\n          const moveHandler = (ev) => {\n           if (!dragging) return;\n           const pt = svgEl.createSVGPoint();\n           pt.x = ev.clientX;\n           pt.y = ev.clientY;\n           const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n           const i = parseInt(c.dataset.index, 10);\n           if (Number.isNaN(i) || !freeDrawPoints[i]) return;\n           freeDrawPoints[i].x = svgP.x;\n           freeDrawPoints[i].y = svgP.y;\n           updateFreeDrawGraphics();\n          };\n          const upHandler = () => {\n           dragging = false;\n           document.removeEventListener(\"mousemove\", moveHandler);\n           document.removeEventListener(\"mouseup\", upHandler);\n          };\n          document.addEventListener(\"mousemove\", moveHandler);\n          document.addEventListener(\"mouseup\", upHandler);\n         });\n         c.addEventListener(\"touchstart\",\n          (e) => {\n           if (!freeDrawMode) return;\n           e.preventDefault();\n           e.stopPropagation();\n           let dragging = true;\n           const svgEl = svgMap;\n           const touchMoveHandler = (ev) => {\n            if (!dragging || !ev.touches[0]) return;\n            const pt = svgEl.createSVGPoint();\n            pt.x = ev.touches[0].clientX;\n            pt.y = ev.touches[0].clientY;\n            const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n            const i = parseInt(c.dataset.index, 10);\n            if (Number.isNaN(i) || !freeDrawPoints[i]) return;\n            freeDrawPoints[i].x = svgP.x;\n            freeDrawPoints[i].y = svgP.y;\n            updateFreeDrawGraphics();\n           };\n           const touchUpHandler = () => {\n            dragging = false;\n            document.removeEventListener(\"touchmove\", touchMoveHandler);\n            document.removeEventListener(\"touchend\", touchUpHandler);\n           };\n           document.addEventListener(\"touchmove\", touchMoveHandler);\n           document.addEventListener(\"touchend\", touchUpHandler);\n          }, {\n           passive: false\n          });\n         svg.appendChild(c);\n         freeDrawPointEls.push(c);\n        });\n        drawUndoBtn.style.display = freeDrawPoints.length ? \"inline-block\" : \"none\";\n       }\n       function addFreeDrawPoint(x, y) {\n        freeDrawPoints.push({\n         x,\n         y\n        });\n        updateFreeDrawGraphics();\n       }\n       function startFreeDraw() {\n        freeDrawMode = true;\n        freeDrawPoints = [];\n        if (freeDrawPolylineEl) {\n         freeDrawPolylineEl.remove();\n         freeDrawPolylineEl = null;\n        }\n        freeDrawPointEls.forEach((el) => el.remove());\n        freeDrawPointEls = [];\n        svgMap.style.cursor = \"crosshair\";\n        drawToggleBtn.textContent = t(\"ui.buttons.done\");\n        drawToggleBtn.classList.add(\"done-btn-active\");\n        drawUndoBtn.style.display = \"none\";\n       }\n       function finishFreeDraw() {\n        freeDrawMode = false;\n        svgMap.style.cursor = \"\";\n        drawToggleBtn.textContent = \"✏️\";\n        drawToggleBtn.classList.remove(\"done-btn-active\");\n        if (freeDrawPoints.length >= 2) {\n         const color = drawColorInput.value || \"#475569\";\n         const lineStyle = drawStyleSelect.value || \"solid\";\n         const arrowDir = drawArrowSelect.value || \"none\";\n         const newId = \"custom-\" + Date.now();\n         const pointsCopy = freeDrawPoints.map((p) => ({\n          x: p.x,\n          y: p.y,\n         }));\n         EDGE_DATA.list.push({\n          id: newId,\n          type: \"custom\",\n          color,\n          width: 4,\n          lineStyle: lineStyle,\n          direction: arrowDir,\n          points: pointsCopy,\n          notes: [],\n         });\n         freeDrawPoints = [];\n         if (freeDrawPolylineEl) {\n          freeDrawPolylineEl.remove();\n          freeDrawPolylineEl = null;\n         }\n         freeDrawPointEls.forEach((el) => el.remove());\n         freeDrawPointEls = [];\n         forgeTheTopology();\n         selectTheConnection(newId);\n        } else {\n         freeDrawPoints = [];\n         if (freeDrawPolylineEl) {\n          freeDrawPolylineEl.remove();\n          freeDrawPolylineEl = null;\n         }\n         freeDrawPointEls.forEach((el) => el.remove());\n         freeDrawPointEls = [];\n         forgeTheLegend();\n\t\t updateZoneLegend();\n        }\n        drawUndoBtn.style.display = \"none\";\n       }\n       drawToggleBtn.addEventListener(\"click\", () => {\n        if (currentView.mode === \"rack\") {\n         showAlert(t(\"dialogs.drawingDisabledInRack\"));\n         return;\n        }\n        if (freeDrawMode) {\n         finishFreeDraw();\n        } else {\n         startFreeDraw();\n        }\n       });\n       drawUndoBtn.addEventListener(\"click\", () => {\n        if (!freeDrawMode || !freeDrawPoints.length) return;\n        freeDrawPoints.pop();\n        updateFreeDrawGraphics();\n       });\n       const drawToolbar = document.getElementById(\"draw-toolbar\");\n       drawToolbar.addEventListener(\"mousedown\", (e) => {\n        e.stopPropagation();\n       });\n       drawToolbar.addEventListener(\"click\", (e) => {\n        if (e.target !== drawToggleBtn && e.target !== drawUndoBtn) {\n         e.stopPropagation();\n        }\n       });\n       drawStyleSelect.addEventListener(\"change\", () => {\n        if (freeDrawMode) {\n         updateFreeDrawGraphics();\n        }\n       });\n       drawArrowSelect.addEventListener(\"change\", () => {\n        if (freeDrawMode) {\n         updateFreeDrawGraphics();\n        }\n       });\n       drawColorInput.addEventListener(\"input\", () => {\n        if (freeDrawMode) {\n         updateFreeDrawGraphics();\n        }\n       });\n       drawStyleSelect.addEventListener(\"mousedown\", (e) => {\n        e.stopPropagation();\n       });\n       drawStyleSelect.addEventListener(\"click\", (e) => {\n        e.stopPropagation();\n       });\n       drawArrowSelect.addEventListener(\"mousedown\", (e) => {\n        e.stopPropagation();\n       });\n       drawArrowSelect.addEventListener(\"click\", (e) => {\n        e.stopPropagation();\n       });\n       drawColorInput.addEventListener(\"mousedown\", (e) => {\n        e.stopPropagation();\n       });\n       drawColorInput.addEventListener(\"click\", (e) => {\n        e.stopPropagation();\n       });\n       svgMap.addEventListener(\"click\", (e) => {\n        if (!freeDrawMode) return;\n        if (e.button !== 0) return;\n        const target = e.target;\n        if (target && target.classList && target.classList.contains(\"free-point\")) return;\n        const svgEl = svgMap;\n        const pt = svgEl.createSVGPoint();\n        pt.x = e.clientX;\n        pt.y = e.clientY;\n        const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n        addFreeDrawPoint(svgP.x, svgP.y);\n       });\n       svgMap.addEventListener(\"touchend\",\n        (e) => {\n         if (!freeDrawMode) return;\n         const target = e.target;\n         if (target && target.classList && target.classList.contains(\"free-point\")) return;\n         if (e.changedTouches && e.changedTouches[0]) {\n          e.preventDefault();\n          const svgEl = svgMap;\n          const pt = svgEl.createSVGPoint();\n          pt.x = e.changedTouches[0].clientX;\n          pt.y = e.changedTouches[0].clientY;\n          const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n          addFreeDrawPoint(svgP.x, svgP.y);\n         }\n        }, {\n         passive: false\n        });\n       const settingsBtn = document.getElementById(\"settings-btn\");\n       const rectToggleBtn = document.getElementById(\"rect-toggle\");\n       const rectStyleSelect = document.getElementById(\"rect-style\");\n       function updateRectPreview() {\n        if (!rectPreviewEl || !rectStartPoint) return;\n        const ns = \"http://www.w3.org/2000/svg\";\n        const svg = svgMap;\n        if (rectPreviewEl) {\n         rectPreviewEl.remove();\n         rectPreviewEl = null;\n        }\n       }\n       function startRectDraw() {\n        rectDrawMode = true;\n        rectStartPoint = null;\n        rectPreviewEl = null;\n        svgMap.style.cursor = \"crosshair\";\n        rectToggleBtn.textContent = t(\"ui.buttons.done\");\n        rectToggleBtn.classList.add(\"done-btn-active\");\n        rectStyle = rectStyleSelect.value || \"filled\";\n        if (freeDrawMode) {\n         finishFreeDraw();\n        }\n        updateRectangleDeleteButtons();\n       }\n       function finishRectDraw() {\n        rectDrawMode = false;\n        svgMap.style.cursor = \"\";\n        rectToggleBtn.textContent = \"▭\";\n        rectToggleBtn.classList.remove(\"done-btn-active\");\n        if (rectPreviewEl) {\n         rectPreviewEl.remove();\n         rectPreviewEl = null;\n        }\n        rectStartPoint = null;\n\t\tupdateRectangleDeleteButtons();\n       }\n       rectToggleBtn.addEventListener(\"click\", () => {\n        if (currentView.mode === \"rack\") {\n         showAlert(t(\"dialogs.drawingDisabledInRack\"));\n         return;\n        }\n        if (rectDrawMode) {\n         finishRectDraw();\n        } else {\n         startRectDraw();\n        }\n       });\n       rectStyleSelect.addEventListener(\"mousedown\", (e) => {\n        e.stopPropagation();\n       });\n       rectStyleSelect.addEventListener(\"click\", (e) => {\n        e.stopPropagation();\n       });\n       rectStyleSelect.addEventListener(\"change\", () => {\n        if (rectDrawMode) {\n         rectStyle = rectStyleSelect.value || \"filled\";\n        }\n       });\n       svgMap.addEventListener(\"mousedown\", (e) => {\n        if (!rectDrawMode) return;\n        if (e.button !== 0) return;\n        e.preventDefault();\n        e.stopPropagation();\n        const svgEl = svgMap;\n        const pt = svgEl.createSVGPoint();\n        pt.x = e.clientX;\n        pt.y = e.clientY;\n        const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n        rectStartPoint = { x: svgP.x, y: svgP.y };\n        const ns = \"http://www.w3.org/2000/svg\";\n        rectPreviewEl = document.createElementNS(ns, \"rect\");\n        rectPreviewEl.classList.add(\"rect-preview\");\n        rectPreviewEl.setAttribute(\"x\", svgP.x);\n        rectPreviewEl.setAttribute(\"y\", svgP.y);\n        rectPreviewEl.setAttribute(\"width\", 0);\n        rectPreviewEl.setAttribute(\"height\", 0);\n        const color = drawColorInput.value || \"#f97316\";\n        rectPreviewEl.style.stroke = color;\n        rectPreviewEl.style.strokeWidth = 2;\n        rectPreviewEl.style.strokeDasharray = \"5,5\";\n        if (rectStyle === \"filled\") {\n         rectPreviewEl.style.fill = color;\n         rectPreviewEl.style.fillOpacity = \"0.3\";\n        } else {\n         rectPreviewEl.style.fill = \"none\";\n        }\n        svgEl.appendChild(rectPreviewEl);\n       });\n       svgMap.addEventListener(\"mousemove\", (e) => {\n        if (!rectDrawMode || !rectStartPoint || !rectPreviewEl) return;\n        const svgEl = svgMap;\n        const pt = svgEl.createSVGPoint();\n        pt.x = e.clientX;\n        pt.y = e.clientY;\n        const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n        const x = Math.min(rectStartPoint.x, svgP.x);\n        const y = Math.min(rectStartPoint.y, svgP.y);\n        const width = Math.abs(svgP.x - rectStartPoint.x);\n        const height = Math.abs(svgP.y - rectStartPoint.y);\n        rectPreviewEl.setAttribute(\"x\", x);\n        rectPreviewEl.setAttribute(\"y\", y);\n        rectPreviewEl.setAttribute(\"width\", width);\n        rectPreviewEl.setAttribute(\"height\", height);\n       });\n       svgMap.addEventListener(\"mouseup\", (e) => {\n        if (!rectDrawMode || !rectStartPoint || !rectPreviewEl) return;\n        const svgEl = svgMap;\n        const pt = svgEl.createSVGPoint();\n        pt.x = e.clientX;\n        pt.y = e.clientY;\n        const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n        const x = Math.min(rectStartPoint.x, svgP.x);\n        const y = Math.min(rectStartPoint.y, svgP.y);\n        const width = Math.abs(svgP.x - rectStartPoint.x);\n        const height = Math.abs(svgP.y - rectStartPoint.y);\n        if (width > 10 && height > 10) {\n      pushUndo(\"draw zone\");\n         const color = drawColorInput.value || \"#f97316\";\n         const newId = \"rect-\" + Date.now();\n         const lineStyle = document.getElementById(\"draw-style\").value || \"solid\";\n         RECT_DATA.list.push({\n          id: newId,\n          x: x,\n          y: y,\n          width: width,\n          height: height,\n          color: color,\n          style: rectStyle,\n          lineStyle: lineStyle,\n          notes: []\n         });\n         finishRectDraw();\n         forgeTheTopology();\n        }\n        if (rectPreviewEl) {\n         rectPreviewEl.remove();\n         rectPreviewEl = null;\n        }\n        rectStartPoint = null;\n       });\n       let rectTouchStart = null;\n       svgMap.addEventListener(\"touchstart\", (e) => {\n        if (!rectDrawMode) return;\n        if (e.touches.length !== 1) return;\n        e.preventDefault();\n        const touch = e.touches[0];\n        const svgEl = svgMap;\n        const pt = svgEl.createSVGPoint();\n        pt.x = touch.clientX;\n        pt.y = touch.clientY;\n        const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n        rectStartPoint = { x: svgP.x, y: svgP.y };\n        rectTouchStart = { x: svgP.x, y: svgP.y };\n        const ns = \"http://www.w3.org/2000/svg\";\n        rectPreviewEl = document.createElementNS(ns, \"rect\");\n        rectPreviewEl.classList.add(\"rect-preview\");\n        rectPreviewEl.setAttribute(\"x\", svgP.x);\n        rectPreviewEl.setAttribute(\"y\", svgP.y);\n        rectPreviewEl.setAttribute(\"width\", 0);\n        rectPreviewEl.setAttribute(\"height\", 0);\n        const color = drawColorInput.value || \"#f97316\";\n        rectPreviewEl.style.stroke = color;\n        rectPreviewEl.style.strokeWidth = 2;\n        rectPreviewEl.style.strokeDasharray = \"5,5\";\n        if (rectStyle === \"filled\") {\n         rectPreviewEl.style.fill = color;\n         rectPreviewEl.style.fillOpacity = \"0.3\";\n        } else {\n         rectPreviewEl.style.fill = \"none\";\n        }\n        svgEl.appendChild(rectPreviewEl);\n       }, { passive: false });\n       svgMap.addEventListener(\"touchmove\", (e) => {\n        if (!rectDrawMode || !rectStartPoint || !rectPreviewEl) return;\n        if (e.touches.length !== 1) return;\n        e.preventDefault();\n        const touch = e.touches[0];\n        const svgEl = svgMap;\n        const pt = svgEl.createSVGPoint();\n        pt.x = touch.clientX;\n        pt.y = touch.clientY;\n        const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n        const x = Math.min(rectStartPoint.x, svgP.x);\n        const y = Math.min(rectStartPoint.y, svgP.y);\n        const width = Math.abs(svgP.x - rectStartPoint.x);\n        const height = Math.abs(svgP.y - rectStartPoint.y);\n        rectPreviewEl.setAttribute(\"x\", x);\n        rectPreviewEl.setAttribute(\"y\", y);\n        rectPreviewEl.setAttribute(\"width\", width);\n        rectPreviewEl.setAttribute(\"height\", height);\n       }, { passive: false });\n       svgMap.addEventListener(\"touchend\", (e) => {\n        if (!rectDrawMode || !rectStartPoint || !rectPreviewEl) return;\n        if (!rectTouchStart) return;\n        e.preventDefault();\n        const touch = e.changedTouches[0];\n        const svgEl = svgMap;\n        const pt = svgEl.createSVGPoint();\n        pt.x = touch.clientX;\n        pt.y = touch.clientY;\n        const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n        const x = Math.min(rectStartPoint.x, svgP.x);\n        const y = Math.min(rectStartPoint.y, svgP.y);\n        const width = Math.abs(svgP.x - rectStartPoint.x);\n        const height = Math.abs(svgP.y - rectStartPoint.y);\n        if (width > 10 && height > 10) {\n      pushUndo(\"draw zone\");\n         const color = drawColorInput.value || \"#f97316\";\n         const newId = \"rect-\" + Date.now();\n         const lineStyle = document.getElementById(\"draw-style\").value || \"solid\";\n         RECT_DATA.list.push({\n          id: newId,\n          x: x,\n          y: y,\n          width: width,\n          height: height,\n          color: color,\n          style: rectStyle,\n          lineStyle: lineStyle,\n          notes: []\n         });\n         finishRectDraw();\n         forgeTheTopology();\n        }\n        if (rectPreviewEl) {\n         rectPreviewEl.remove();\n         rectPreviewEl = null;\n        }\n        rectStartPoint = null;\n        rectTouchStart = null;\n       }, { passive: false });\n       const textToggleBtn = document.getElementById(\"text-toggle\");\n       function startTextMode() {\n        textDrawMode = true;\n        svgMap.style.cursor = \"crosshair\";\n        textToggleBtn.textContent = t(\"ui.buttons.done\");\n        textToggleBtn.classList.add(\"done-btn-active\");\n        if (freeDrawMode) {\n         finishFreeDraw();\n        }\n        if (rectDrawMode) {\n         finishRectDraw();\n        }\n        updateTextDeleteButtons();\n       }\n       function finishTextMode() {\n        textDrawMode = false;\n        svgMap.style.cursor = \"\";\n        textToggleBtn.textContent = \"T\";\n        textToggleBtn.classList.remove(\"done-btn-active\");\n        updateTextDeleteButtons();\n       }\n       textToggleBtn.addEventListener(\"click\", () => {\n        if (currentView.mode === \"rack\") {\n         showAlert(t(\"dialogs.drawingDisabledInRack\"));\n         return;\n        }\n        if (textDrawMode) {\n         finishTextMode();\n        } else {\n         startTextMode();\n        }\n       });\n       function handleTextPlacement(e) {\n        if (!textDrawMode) return;\n        const svgEl = svgMap;\n        const pt = svgEl.createSVGPoint();\n        pt.x = e.clientX;\n        pt.y = e.clientY;\n        const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n        const newId = \"text-\" + Date.now();\n      pushUndo(\"add text\");\n        TEXT_DATA.list.push({\n         id: newId,\n         x: svgP.x,\n         y: svgP.y,\n         content: t(\"ui.defaults.newText\"),\n         fontSize: 18,\n         color: \"#e2e8f0\",\n         fontWeight: \"normal\",\n         fontStyle: \"normal\",\n         textAlign: \"start\",\n         textDecoration: \"none\",\n         bgColor: \"#000000\",\n         bgEnabled: false,\n         opacity: 1\n        });\n        finishTextMode();\n        forgeTheTopology();\n        showTextPanel(newId);\n        const textInput = document.getElementById(\"text-content\");\n        if (textInput) {\n         textInput.focus();\n         textInput.select();\n        }\n       }\n       svgMap.addEventListener(\"click\", (e) => {\n        if (!textDrawMode) return;\n        if (e.target.closest('.text-delete-btn')) return;\n        if (e.target.closest('.text-group')) return;\n        e.preventDefault();\n        e.stopPropagation();\n        handleTextPlacement(e);\n       });\n       svgMap.addEventListener(\"touchend\", (e) => {\n        if (!textDrawMode) return;\n        if (e.target.closest('.text-delete-btn')) return;\n        if (e.target.closest('.text-group')) return;\n        if (e.touches.length > 0) return;\n        e.preventDefault();\n        const touch = e.changedTouches[0];\n        const fakeEvent = {\n         clientX: touch.clientX,\n         clientY: touch.clientY,\n         preventDefault: () => {},\n         stopPropagation: () => {}\n        };\n        handleTextPlacement(fakeEvent);\n       }, { passive: false });\n       function showTextPanel(textId) {\n\t   if (isViewOnly()) return;\n       currentTextId = textId;\n       currentNodeId = null;\n       currentEdgeId = null;\n       currentRectId = null;\n       const textItem = TEXT_DATA.list.find(t => t.id === textId);\n       if (!textItem) return;\n       document.getElementById(\"node-panel\").style.display = \"none\";\n       document.getElementById(\"edge-panel\").style.display = \"none\";\n       document.getElementById(\"rect-panel\").style.display = \"none\";\n       const textPanel = document.getElementById(\"text-panel\");\n       textPanel.style.display = \"block\";\n       document.querySelectorAll(\".node-group\").forEach(n => n.classList.remove(\"active\"));\n       document.querySelectorAll(\".edge\").forEach(e => e.classList.remove(\"active\"));\n       document.querySelectorAll(\".rect-group\").forEach(r => r.classList.remove(\"active\"));\n       document.querySelectorAll(\".text-element\").forEach(t => t.classList.toggle(\"active\", t.dataset.textId === textId));\n        document.getElementById(\"text-content\").value = textItem.content;\n        document.getElementById(\"text-font-size\").value = textItem.fontSize;\n        document.getElementById(\"text-color\").value = textItem.color;\n        document.getElementById(\"text-font-weight\").value = textItem.fontWeight;\n        document.getElementById(\"text-font-style\").value = textItem.fontStyle;\n        document.getElementById(\"text-align\").value = textItem.textAlign;\n        document.getElementById(\"text-decoration\").value = textItem.textDecoration;\n        document.getElementById(\"text-bg-color\").value = textItem.bgColor;\n        document.getElementById(\"text-bg-enabled\").checked = textItem.bgEnabled;\n        document.getElementById(\"text-opacity\").value = textItem.opacity;\n        document.getElementById(\"text-opacity-val\").textContent = Math.round(textItem.opacity * 100) + \"%\";\n        document.getElementById(\"text-rotation\").value = Math.max(-360, Math.min(360, textItem.rotation || 0));\n        document.getElementById(\"text-rotation-val\").value = textItem.rotation || 0;\n       }\n       function updateTextDeleteButtons() {\n        const deleteButtons = document.querySelectorAll('.text-delete-btn');\n        deleteButtons.forEach(btn => {\n         btn.style.display = textDrawMode ? 'block' : 'none';\n        });\n       }\n       function deleteText(textId) {\n      pushUndo(\"delete text\");\n        TEXT_DATA.list = TEXT_DATA.list.filter(t => t.id !== textId);\n        forgeTheTopology();\n        if (currentTextId === textId) {\n         document.getElementById(\"text-panel\").style.display = \"none\";\n         currentTextId = null;\n        }\n       }\n      function processAndAddImage(file, canvasX, canvasY, filename = null) {\n       if (IMAGE_DATA.list.length >= MAX_CANVAS_IMAGES) {\n        showAlert(t(\"images.maxImages\"));\n        return;\n       }\n       const imageName = filename || file.name.replace(/\\.[^/.]+$/, \"\") || \"Image\";\n       const reader = new FileReader();\n       reader.onload = (e) => {\n        const img = new Image();\n        img.onload = () => {\n         let width = img.width;\n         let height = img.height;\n         if (width > MAX_IMAGE_SIZE || height > MAX_IMAGE_SIZE) {\n          const ratio = Math.min(MAX_IMAGE_SIZE / width, MAX_IMAGE_SIZE / height);\n          width = Math.round(width * ratio);\n          height = Math.round(height * ratio);\n         }\n         const canvas = document.createElement(\"canvas\");\n         canvas.width = width;\n         canvas.height = height;\n         const ctx = canvas.getContext(\"2d\");\n         ctx.drawImage(img, 0, 0, width, height);\n         const imageData = ctx.getImageData(0, 0, width, height);\n         const data = imageData.data;\n         let hasTransparency = false;\n         for (let i = 3; i < data.length; i += 4) {\n          if (data[i] < 255) { hasTransparency = true; break; }\n         }\n         let compressedSrc;\n         const webpTest = canvas.toDataURL(\"image/webp\", 0.8);\n         const supportsWebP = webpTest.startsWith(\"data:image/webp\");\n         if (supportsWebP) {\n          compressedSrc = canvas.toDataURL(\"image/webp\", 0.8);\n         } else if (hasTransparency) {\n          compressedSrc = canvas.toDataURL(\"image/png\");\n         } else {\n          compressedSrc = canvas.toDataURL(\"image/jpeg\", 0.8);\n         }\n         const newId = \"img-\" + Date.now();\n         pushUndo(\"add image\");\n         IMAGE_DATA.list.push({\n          id: newId,\n          name: imageName,\n          x: canvasX - width / 2,\n          y: canvasY - height / 2,\n          width: width,\n          height: height,\n          src: compressedSrc,\n          opacity: 1,\n          border: \"none\",\n          shadow: \"none\",\n          notes: [],\n          baseWidth: width,\n          baseHeight: height\n         });\n         forgeTheTopology();\n        };\n        img.src = e.target.result;\n       };\n       reader.readAsDataURL(file);\n      }\n      function showImagePanel(imageId) {\n       const img = IMAGE_DATA.list.find(i => i.id === imageId);\n       if (!img) return;\n       currentImageId = imageId;\n       currentNodeId = null;\n       currentEdgeId = null;\n       currentRectId = null;\n       currentTextId = null;\n       document.getElementById(\"node-panel\").style.display = \"none\";\n       document.getElementById(\"edge-panel\").style.display = \"none\";\n       document.getElementById(\"rect-panel\").style.display = \"none\";\n       document.getElementById(\"text-panel\").style.display = \"none\";\n       document.getElementById(\"image-panel\").style.display = \"block\";\n       document.querySelectorAll(\".node-group\").forEach(n => n.classList.remove(\"active\"));\n       document.querySelectorAll(\".edge\").forEach(e => e.classList.remove(\"active\"));\n       document.querySelectorAll(\".rect-group\").forEach(r => r.classList.remove(\"active\"));\n       document.querySelectorAll(\".text-element\").forEach(t => t.classList.remove(\"active\"));\n       document.getElementById(\"image-title\").textContent = img.name || \"Image\";\n       document.getElementById(\"image-title\").onclick = () => editImageName(imageId);\n       document.getElementById(\"image-panel-opacity\").value = Math.round((img.opacity || 1) * 100);\n       document.getElementById(\"image-panel-opacity-val\").textContent = Math.round((img.opacity || 1) * 100) + \"%\";\n       document.getElementById(\"image-panel-border\").value = img.border || \"none\";\n       document.getElementById(\"image-panel-shadow\").value = img.shadow || \"none\";\n       const sizePercent = img.baseWidth ? Math.round((img.width / img.baseWidth) * 100) : 100;\n       document.getElementById(\"image-panel-size\").value = sizePercent;\n       document.getElementById(\"image-panel-size-val\").textContent = sizePercent + \"%\";\n       renderNotesFeed(img.notes || [], \"image-notes\", \"image\", imageId);\n      }\n      function editImageName(imageId) {\n       const img = IMAGE_DATA.list.find(i => i.id === imageId);\n       if (!img) return;\n       showEditModal(t(\"editModal.editName\"), img.name || \"\", (newName) => {\n        pushUndo(\"edit image name\");\n        img.name = newName;\n        document.getElementById(\"image-title\").textContent = newName || \"Image\";\n       });\n      }\n      function hideImagePanel() {\n       document.getElementById(\"image-panel\").style.display = \"none\";\n       currentImageId = null;\n      }\n      function deleteImage(imageId) {\n       pushUndo(\"delete image\");\n       IMAGE_DATA.list = IMAGE_DATA.list.filter(i => i.id !== imageId);\n       forgeTheTopology();\n       hideImagePanel();\n      }\n      document.getElementById(\"image-toggle\")?.addEventListener(\"click\", () => {\n       document.getElementById(\"image-file-input\").click();\n      });\n      document.getElementById(\"image-file-input\")?.addEventListener(\"change\", (e) => {\n       const file = e.target.files[0];\n       if (!file) return;\n       const svg = document.getElementById(\"map\");\n       const viewBox = svg.viewBox.baseVal;\n       const centerX = viewBox.x + viewBox.width / 2;\n       const centerY = viewBox.y + viewBox.height / 2;\n       processAndAddImage(file, centerX, centerY);\n       e.target.value = \"\";\n      });\n      document.getElementById(\"image-panel-opacity\")?.addEventListener(\"input\", (e) => {\n       if (!currentImageId) return;\n       const img = IMAGE_DATA.list.find(i => i.id === currentImageId);\n       if (img) {\n        img.opacity = parseInt(e.target.value) / 100;\n        document.getElementById(\"image-panel-opacity-val\").textContent = e.target.value + \"%\";\n        forgeTheTopology();\n       }\n      });\n      document.getElementById(\"image-panel-border\")?.addEventListener(\"change\", (e) => {\n       if (!currentImageId) return;\n       const img = IMAGE_DATA.list.find(i => i.id === currentImageId);\n       if (img) {\n        img.border = e.target.value;\n        forgeTheTopology();\n       }\n      });\n      document.getElementById(\"image-panel-shadow\")?.addEventListener(\"change\", (e) => {\n       if (!currentImageId) return;\n       const img = IMAGE_DATA.list.find(i => i.id === currentImageId);\n       if (img) {\n        img.shadow = e.target.value;\n        forgeTheTopology();\n       }\n      });\n      document.getElementById(\"image-panel-size\")?.addEventListener(\"input\", (e) => {\n       if (!currentImageId) return;\n       const img = IMAGE_DATA.list.find(i => i.id === currentImageId);\n       if (img) {\n        const scale = parseInt(e.target.value) / 100;\n        const baseWidth = img.baseWidth || img.width;\n        const baseHeight = img.baseHeight || img.height;\n        if (!img.baseWidth) img.baseWidth = img.width;\n        if (!img.baseHeight) img.baseHeight = img.height;\n        img.width = Math.round(baseWidth * scale);\n        img.height = Math.round(baseHeight * scale);\n        document.getElementById(\"image-panel-size-val\").textContent = e.target.value + \"%\";\n        forgeTheTopology();\n       }\n      });\n      document.getElementById(\"add-image-note\")?.addEventListener(\"click\", () => {\n        if (!currentImageId) return;\n        const img = IMAGE_DATA.list.find(i => i.id === currentImageId);\n        if (!img) return;\n        if (!img.notes) img.notes = [];\n        showNoteEditor(t(\"noteEditor.addNote\"), \"\", (content) => {\n          pushUndo(\"add image note\");\n          img.notes.push(content);\n          renderNotesFeed(img.notes, \"image-notes\", \"image\", currentImageId);\n        });\n      });\n      document.getElementById(\"delete-image-panel\")?.addEventListener(\"click\", () => {\n       if (!currentImageId) return;\n       deleteImage(currentImageId);\n      });\n      (function() {\n       const viewport = document.getElementById(\"canvas-viewport\");\n       viewport?.addEventListener(\"dragover\", (e) => {\n        e.preventDefault();\n        e.dataTransfer.dropEffect = \"copy\";\n       });\n       viewport?.addEventListener(\"drop\", (e) => {\n        e.preventDefault();\n        if (isViewOnly()) return;\n        const files = e.dataTransfer.files;\n        if (files.length === 0) return;\n        const file = files[0];\n        if (!file.type.startsWith(\"image/\")) return;\n        const svg = document.getElementById(\"map\");\n        const pt = svg.createSVGPoint();\n        pt.x = e.clientX;\n        pt.y = e.clientY;\n        const svgP = pt.matrixTransform(svg.getScreenCTM().inverse());\n        processAndAddImage(file, svgP.x, svgP.y);\n       });\n      })();\n       document.getElementById(\"text-content\").addEventListener(\"input\", (e) => {\n        if (!currentTextId) return;\n        const textItem = TEXT_DATA.list.find(t => t.id === currentTextId);\n        if (textItem) {\n      pushUndo(\"edit text\");\n         textItem.content = e.target.value;\n         forgeTheTopology();\n        }\n       });\n       document.getElementById(\"text-font-size\").addEventListener(\"input\", (e) => {\n        if (!currentTextId) return;\n        const textItem = TEXT_DATA.list.find(t => t.id === currentTextId);\n        if (textItem) {\n         pushUndo(\"edit text\");\n         textItem.fontSize = parseInt(e.target.value);\n         forgeTheTopology();\n        }\n       });\n       document.getElementById(\"text-color\").addEventListener(\"input\", (e) => {\n        if (!currentTextId) return;\n        const textItem = TEXT_DATA.list.find(t => t.id === currentTextId);\n        if (textItem) {\n         pushUndo(\"edit text\");\n         textItem.color = e.target.value;\n         forgeTheTopology();\n        }\n       });\n       document.getElementById(\"text-font-weight\").addEventListener(\"change\", (e) => {\n        if (!currentTextId) return;\n        const textItem = TEXT_DATA.list.find(t => t.id === currentTextId);\n        if (textItem) {\n         pushUndo(\"edit text\");\n         textItem.fontWeight = e.target.value;\n         forgeTheTopology();\n        }\n       });\n       document.getElementById(\"text-font-style\").addEventListener(\"change\", (e) => {\n        if (!currentTextId) return;\n        const textItem = TEXT_DATA.list.find(t => t.id === currentTextId);\n        if (textItem) {\n         pushUndo(\"edit text\");\n         textItem.fontStyle = e.target.value;\n         forgeTheTopology();\n        }\n       });\n       document.getElementById(\"text-align\").addEventListener(\"change\", (e) => {\n        if (!currentTextId) return;\n        const textItem = TEXT_DATA.list.find(t => t.id === currentTextId);\n        if (textItem) {\n         pushUndo(\"edit text\");\n         textItem.textAlign = e.target.value;\n         forgeTheTopology();\n        }\n       });\n        document.getElementById(\"text-decoration\").addEventListener(\"change\", (e) => {\n        if (!currentTextId) return;\n        const textItem = TEXT_DATA.list.find(t => t.id === currentTextId);\n        if (textItem) {\n         pushUndo(\"edit text\");\n         textItem.textDecoration = e.target.value;\n         forgeTheTopology();\n        }\n       });\n       document.getElementById(\"text-bg-color\").addEventListener(\"input\", (e) => {\n        if (!currentTextId) return;\n        const textItem = TEXT_DATA.list.find(t => t.id === currentTextId);\n        if (textItem) {\n         pushUndo(\"edit text\");\n         textItem.bgColor = e.target.value;\n         forgeTheTopology();\n        }\n       });\n       document.getElementById(\"text-bg-enabled\").addEventListener(\"change\", (e) => {\n        if (!currentTextId) return;\n        const textItem = TEXT_DATA.list.find(t => t.id === currentTextId);\n        if (textItem) {\n         pushUndo(\"edit text\");\n         textItem.bgEnabled = e.target.checked;\n         forgeTheTopology();\n        }\n       });\n       document.getElementById(\"text-opacity\").addEventListener(\"input\", (e) => {\n        if (!currentTextId) return;\n        const textItem = TEXT_DATA.list.find(t => t.id === currentTextId);\n        if (textItem) {\n         pushUndo(\"edit text\");\n         textItem.opacity = parseFloat(e.target.value);\n         document.getElementById(\"text-opacity-val\").textContent = Math.round(textItem.opacity * 100) + \"%\";\n         forgeTheTopology();\n        }\n       });\n       document.getElementById(\"text-rotation\").addEventListener(\"input\", (e) => {\n         if (!currentTextId) return;\n         const textItem = TEXT_DATA.list.find(t => t.id === currentTextId);\n         if (textItem) {\n           pushUndo(\"rotate text\");\n           textItem.rotation = parseInt(e.target.value) || 0;\n           document.getElementById(\"text-rotation-val\").value = textItem.rotation;\n           forgeTheTopology();\n         }\n       });\n       document.getElementById(\"text-rotation-val\").addEventListener(\"input\", (e) => {\n         if (!currentTextId) return;\n         const textItem = TEXT_DATA.list.find(t => t.id === currentTextId);\n         if (textItem) {\n           pushUndo(\"rotate text\");\n           const val = parseInt(e.target.value) || 0;\n           textItem.rotation = val;\n           document.getElementById(\"text-rotation\").value = Math.max(-360, Math.min(360, val));\n           forgeTheTopology();\n         }\n       });\n       document.getElementById(\"delete-text\").addEventListener(\"click\", () => {\n        if (!currentTextId) return;\n        const textItem = TEXT_DATA.list.find(t => t.id === currentTextId);\n        if (textItem) {\n         challengeTheImmortal(`Delete text \"${(textItem.content || '').substring(0, 30)}...\"?`, () => {\n          deleteText(currentTextId);\n         });\n        }\n       });\n       const settingsModal = document.getElementById(\"settings-modal\");\n       const settingsClose = document.getElementById(\"settings-close\");\n       settingsBtn.addEventListener(\"click\", () => {\n       document.getElementById(\"page-bg-color\").value = rgbaToHex(PAGE_STATE.background) || \"#050608\";\n       document.getElementById(\"topbar-bg-color\").value = rgbaToHex(PAGE_STATE.topbarBg) || \"#0b0e13\";\n       document.getElementById(\"topbar-border-color\").value = rgbaToHex(PAGE_STATE.topbarBorder) || \"#1f2533\";\n       document.getElementById(\"panel-color\").value = rgbaToHex(PAGE_STATE.panel) || \"#0b0e13\";\n       document.getElementById(\"panel-alt-color\").value = PAGE_STATE.panelAlt || \"#10141b\";\n       document.getElementById(\"sidebar-bg-color\").value = PAGE_STATE.sidebarBg || \"#10141b\";\n       document.getElementById(\"btn-bg-color\").value = PAGE_STATE.btnBg || \"#0b0e13\";\n       document.getElementById(\"btn-text-color\").value = PAGE_STATE.btnText || \"#e2e8f0\";\n       document.getElementById(\"tag-fill-color\").value = PAGE_STATE.tagFill || \"#1e293b\";\n       document.getElementById(\"tag-text-color\").value = PAGE_STATE.tagText || \"#e2e8f0\";\n       document.getElementById(\"tag-border-color\").value = PAGE_STATE.tagBorder || \"#475569\";\n       document.getElementById(\"input-bg-color\").value = PAGE_STATE.inputBg || \"#0b0e13\";\n       document.getElementById(\"input-text-color\").value = PAGE_STATE.inputText || \"#e2e8f0\";\n\t   document.getElementById(\"input-border-color\").value = PAGE_STATE.inputBorder || \"#1f2937\";\n       document.getElementById(\"input-font-family\").value = PAGE_STATE.inputFont || \"Inter, system-ui, sans-serif\";\n       document.getElementById(\"input-font-size\").value = PAGE_STATE.inputFontSize || 14;\n       document.getElementById(\"toolbar-bg-color\").value = PAGE_STATE.toolbarBg || \"#0f172a\";\n       document.getElementById(\"toolbar-border-color\").value = PAGE_STATE.toolbarBorder || \"#1f2937\";\n       document.getElementById(\"toolbar-text-color\").value = PAGE_STATE.toolbarText || \"#94a3b8\";\n       document.getElementById(\"toolbar-btn-bg-color\").value = PAGE_STATE.toolbarBtnBg || \"#0b0e13\";\n       document.getElementById(\"toolbar-btn-text-color\").value = PAGE_STATE.toolbarBtnText || \"#e2e8f0\";\n       document.getElementById(\"minimap-dots-color\").value = PAGE_STATE.minimapDots || \"#94a3b8\";\n       document.getElementById(\"canvas-hint-enabled\").checked = PAGE_STATE.canvasHintEnabled !== false;\n       document.getElementById(\"canvas-hint-bg-color\").value = PAGE_STATE.canvasHintBg || \"#0f172a\";\n       document.getElementById(\"canvas-hint-text-color\").value = PAGE_STATE.canvasHintColor || \"#94a3b8\";\n       document.getElementById(\"canvas-hint-text\").value = PAGE_STATE.canvasHintText || \"\";\n       document.getElementById(\"accent-color\").value = rgbaToHex(PAGE_STATE.accent) || \"#4fd1c5\";\n       document.getElementById(\"danger-color\").value = rgbaToHex(PAGE_STATE.danger) || \"#f56565\";\n       document.getElementById(\"text-main-color\").value = rgbaToHex(PAGE_STATE.textMain) || \"#e2e8f0\";\n       document.getElementById(\"text-soft-color\").value = PAGE_STATE.textSoft || \"#94a3b8\";\n       document.getElementById(\"node-fill-color\").value = PAGE_STATE.nodeFill || \"#1e293b\";\n       document.getElementById(\"node-stroke-color\").value = PAGE_STATE.nodeStroke || \"#475569\";\n       document.getElementById(\"node-title-color\").value = PAGE_STATE.nodeTitle || \"#e2e8f0\";\n       document.getElementById(\"node-sub-color\").value = PAGE_STATE.nodeSub || \"#94a3b8\";\n       document.getElementById(\"node-title-size\").value = PAGE_STATE.nodeTitleSize || 18;\n       document.getElementById(\"node-sub-size\").value = PAGE_STATE.nodeSubSize || 13;\n       document.getElementById(\"node-font-family\").value = PAGE_STATE.nodeFont || \"Inter, system-ui, sans-serif\";\n       document.getElementById(\"default-edge-color\").value = PAGE_STATE.defaultEdge || \"#475569\";\n       document.getElementById(\"add-line-color\").value = PAGE_STATE.defaultEdge || \"#475569\";\n       document.getElementById(\"default-edge-routing\").value = PAGE_STATE.defaultEdgeRouting || \"curved\";\n\t   document.getElementById(\"anim-master\").addEventListener(\"change\", e => { ANIM_SETTINGS.masterAnim = e.target.checked; applyAnimZoneSettings(); });\n\t\tdocument.getElementById(\"anim-type-sweep\").addEventListener(\"change\", e => { ANIM_SETTINGS.animTypes.sweep = e.target.checked; applyAnimZoneSettings(); });\n\t\tdocument.getElementById(\"anim-type-pulse\").addEventListener(\"change\", e => { ANIM_SETTINGS.animTypes.pulse = e.target.checked; applyAnimZoneSettings(); });\n\t\tdocument.getElementById(\"anim-type-rings\").addEventListener(\"change\", e => { ANIM_SETTINGS.animTypes.rings = e.target.checked; applyAnimZoneSettings(); });\n\t\tdocument.getElementById(\"anim-type-spin\").addEventListener(\"change\", e => { ANIM_SETTINGS.animTypes.spin = e.target.checked; applyAnimZoneSettings(); });\n\t\tdocument.getElementById(\"anim-type-connections\").addEventListener(\"change\", e => { ANIM_SETTINGS.animTypes.connections = e.target.checked; applyAnimZoneSettings(); });\n\t\tdocument.getElementById(\"anim-cat-camera\").addEventListener(\"change\", e => { ANIM_SETTINGS.animCategories.camera = e.target.checked; applyAnimZoneSettings(); });\n\t\tdocument.getElementById(\"anim-cat-doorbell\").addEventListener(\"change\", e => { ANIM_SETTINGS.animCategories.doorbell = e.target.checked; applyAnimZoneSettings(); });\n\t\tdocument.getElementById(\"anim-cat-motion\").addEventListener(\"change\", e => { ANIM_SETTINGS.animCategories.motion = e.target.checked; applyAnimZoneSettings(); });\n\t\tdocument.getElementById(\"anim-cat-smoke\").addEventListener(\"change\", e => { ANIM_SETTINGS.animCategories.smoke = e.target.checked; applyAnimZoneSettings(); });\n\t\tdocument.getElementById(\"anim-cat-wifi\").addEventListener(\"change\", e => { ANIM_SETTINGS.animCategories.wifi = e.target.checked; applyAnimZoneSettings(); });\n\t\tdocument.getElementById(\"anim-cat-sensor\").addEventListener(\"change\", e => { ANIM_SETTINGS.animCategories.sensor = e.target.checked; applyAnimZoneSettings(); });\n\t\tdocument.getElementById(\"anim-cat-sprinkler\").addEventListener(\"change\", e => { ANIM_SETTINGS.animCategories.sprinkler = e.target.checked; applyAnimZoneSettings(); });\n\t\tdocument.getElementById(\"anim-cat-connections\").addEventListener(\"change\", e => { ANIM_SETTINGS.animCategories.connections = e.target.checked; applyAnimZoneSettings(); });\n\t\tdocument.getElementById(\"zone-master\").addEventListener(\"change\", e => { ANIM_SETTINGS.masterZones = e.target.checked; applyAnimZoneSettings(); });\n\t\tdocument.getElementById(\"zone-cat-camera\").addEventListener(\"change\", e => { ANIM_SETTINGS.zoneCategories.camera = e.target.checked; applyAnimZoneSettings(); });\n\t\tdocument.getElementById(\"zone-cat-doorbell\").addEventListener(\"change\", e => { ANIM_SETTINGS.zoneCategories.doorbell = e.target.checked; applyAnimZoneSettings(); });\n\t\tdocument.getElementById(\"zone-cat-motion\").addEventListener(\"change\", e => { ANIM_SETTINGS.zoneCategories.motion = e.target.checked; applyAnimZoneSettings(); });\n\t\tdocument.getElementById(\"zone-cat-smoke\").addEventListener(\"change\", e => { ANIM_SETTINGS.zoneCategories.smoke = e.target.checked; applyAnimZoneSettings(); });\n\t\tdocument.getElementById(\"zone-cat-wifi\").addEventListener(\"change\", e => { ANIM_SETTINGS.zoneCategories.wifi = e.target.checked; applyAnimZoneSettings(); });\n\t\tdocument.getElementById(\"zone-cat-sensor\").addEventListener(\"change\", e => { ANIM_SETTINGS.zoneCategories.sensor = e.target.checked; applyAnimZoneSettings(); });\n\t\tdocument.getElementById(\"zone-cat-sprinkler\").addEventListener(\"change\", e => { ANIM_SETTINGS.zoneCategories.sprinkler = e.target.checked; applyAnimZoneSettings(); });\n       document.getElementById(\"animation-style-select\").value = PAGE_STATE.animationStyle || \"arrows\";\n       document.getElementById(\"animation-direction-select\").value = PAGE_STATE.animationDirection || \"all\";\n       document.getElementById(\"animation-speed-select\").value = PAGE_STATE.animationSpeed || 1.5;\n       document.getElementById(\"add-line-routing\").value = PAGE_STATE.defaultEdgeRouting || \"orthogonal\";\n       document.getElementById(\"selection-handle-color\").value = PAGE_STATE.selectionHandle || \"#f59e0b\";\n       document.getElementById(\"selection-handle-size\").value = PAGE_STATE.selectionHandleSize || 8;\n       document.getElementById(\"group-indicator-color\").value = PAGE_STATE.groupIndicator || \"#4fd1c5\";\n       document.getElementById(\"canvas-gradient-top\").value = PAGE_STATE.canvasGradientTop || \"#1e2532\";\n       document.getElementById(\"canvas-gradient-bottom\").value = PAGE_STATE.canvasGradientBottom || \"#050608\";\n       document.getElementById(\"canvas-border-color\").value = PAGE_STATE.canvasBorder || \"#475569\";\n       document.getElementById(\"canvas-grid-color\").value = PAGE_STATE.canvasGrid || \"#475569\";\n       document.getElementById(\"canvas-grid-size\").value = PAGE_STATE.canvasGridSize || 50;\n       document.getElementById(\"canvas-grid-enabled\").checked = PAGE_STATE.canvasGridEnabled !== false;\n       document.getElementById(\"rack-frame-fill\").value = PAGE_STATE.rackFrameFill || \"#0f172a\";\n       document.getElementById(\"rack-frame-stroke\").value = PAGE_STATE.rackFrameStroke || \"#4fd1c5\";\n       document.getElementById(\"rack-line-color\").value = PAGE_STATE.rackLineColor || \"#475569\";\n       document.getElementById(\"rack-grid-enabled\").checked = PAGE_STATE.rackGridEnabled !== false;\n       document.getElementById(\"rack-text-color\").value = PAGE_STATE.rackTextColor || \"#4fd1c5\";\n        document.getElementById(\"auto-ping-enabled\").checked = autoPingEnabled;\n        document.getElementById(\"auto-ping-interval\").value = autoPingInterval;\n        document.getElementById(\"auto-ping-settings\").style.display = autoPingEnabled ? 'block' : 'none';\n        document.getElementById(\"view-only-mode\").checked = PAGE_STATE.viewOnly === true;\n        document.getElementById(\"show-port-labels-toggle\").checked = PAGE_STATE.showPortLabels !== false;\n        document.getElementById(\"version-display\").textContent = THE_ONE_FILE_VERSION;\n        rebuildThemeDropdown();\n        updateDeleteButton();\n        settingsModal.classList.add(\"active\");\n       });\n       settingsClose.addEventListener(\"click\", () => {\n        settingsModal.classList.remove(\"active\");\n       });\n\t   document.getElementById(\"view-only-mode\").addEventListener(\"change\", (e) => {\n       PAGE_STATE.viewOnly = e.target.checked;\n       if (e.target.checked) {\n        document.getElementById(\"node-panel\").style.display = \"none\";\n        document.getElementById(\"edge-panel\").style.display = \"none\";\n        document.getElementById(\"text-panel\").style.display = \"none\";\n        document.getElementById(\"rect-panel\").style.display = \"none\";\n        document.getElementById(\"topology-toolbar\").style.setProperty('display', 'none', 'important');\n        document.getElementById(\"draw-toolbar\").style.setProperty('display', 'none', 'important');\n        currentNodeId = null;\n        currentEdgeId = null;\n        currentRectId = null;\n        currentTextId = null;\n        selectedNodes.clear();\n        selectedRects.clear();\n        selectedTexts.clear();\n        selectedEdges.clear();\n        const bulkToolbar = document.getElementById(\"bulk-toolbar\");\n        const bulkToolbarMobile = document.getElementById(\"bulk-toolbar-mobile\");\n        if (bulkToolbar) bulkToolbar.style.display = \"none\";\n        if (bulkToolbarMobile) bulkToolbarMobile.style.display = \"none\";\n        document.querySelectorAll(\".node-group\").forEach(n => n.classList.remove(\"active\", \"selected\"));\n        document.querySelectorAll(\".edge\").forEach(e => e.classList.remove(\"active\"));\n        document.querySelectorAll(\".rect-group\").forEach(r => r.classList.remove(\"active\"));\n        document.querySelectorAll(\".text-element\").forEach(t => t.classList.remove(\"active\"));\n       }\n       wieldThePower();\n       if (!e.target.checked) {\n        updateDrawToolbarVisibility();\n        updateTopologyToolbarVisibility();\n       }\n       forgeTheTopology();\n      });\n       settingsModal.addEventListener(\"click\", (e) => {\n        if (e.target === settingsModal) {\n         settingsModal.classList.remove(\"active\");\n        }\n       });\n       document.getElementById(\"page-bg-color\").addEventListener(\"input\", (e) => {\n        PAGE_STATE.background = e.target.value;\n        wieldThePower();\n       });\n       document.getElementById(\"topbar-bg-color\").addEventListener(\"input\", (e) => {\n        PAGE_STATE.topbarBg = e.target.value;\n        wieldThePower();\n       });\n       document.getElementById(\"topbar-border-color\").addEventListener(\"input\", (e) => {\n        PAGE_STATE.topbarBorder = e.target.value;\n        wieldThePower();\n       });\nconst THEME_PRESETS = {\n  defaulted: { panel:\"#0b0e13\",panelAlt:\"#10141b\",sidebarBg:\"#10141b\",btnBg:\"#0b0e13\",btnText:\"#e2e8f0\",accent:\"#4fd1c5\",danger:\"#f56565\",textMain:\"#e2e8f0\",textSoft:\"#94a3b8\",topbarBg:\"rgba(9,12,20,0.9)\",topbarBorder:\"#1f2533\",nodeFill:\"#1e293b\",nodeStroke:\"#475569\",nodeTitle:\"#e2e8f0\",nodeSub:\"#94a3b8\",defaultEdge:\"#475569\",canvasGradientTop:\"#1e2532\",canvasGradientBottom:\"#050608\",tagFill:\"#1e293b\",tagText:\"#e2e8f0\",tagBorder:\"#475569\",inputBg:\"#0b0e13\",inputText:\"#e2e8f0\",inputBorder:\"#1f2937\",toolbarBg:\"#0f172a\",toolbarBorder:\"#1f2937\",toolbarText:\"#94a3b8\",toolbarBtnBg:\"#0b0e13\",toolbarBtnText:\"#e2e8f0\",selectionHandle:\"#f59e0b\",groupIndicator:\"#4fd1c5\",minimapDots:\"#94a3b8\",canvasHintBg:\"#0f172a\",canvasHintColor:\"#94a3b8\",canvasBorder:\"#475569\",canvasGrid:\"#475569\",rackFrameFill:\"#0f172a\",rackFrameStroke:\"#4fd1c5\",rackLineColor:\"#475569\",rackTextColor:\"#4fd1c5\" },\n  slate: { panel:\"#1e293b\",panelAlt:\"#334155\",sidebarBg:\"#1e293b\",btnBg:\"#334155\",btnText:\"#f1f5f9\",accent:\"#3b82f6\",danger:\"#ef4444\",textMain:\"#f1f5f9\",textSoft:\"#94a3b8\",topbarBg:\"#0f172a\",topbarBorder:\"#334155\",nodeFill:\"#334155\",nodeStroke:\"#3b82f6\",nodeTitle:\"#f1f5f9\",nodeSub:\"#94a3b8\",defaultEdge:\"#64748b\",canvasGradientTop:\"#1e293b\",canvasGradientBottom:\"#0f172a\",tagFill:\"#1e3a5f\",tagText:\"#93c5fd\",tagBorder:\"#3b82f6\",inputBg:\"#0f172a\",inputText:\"#f1f5f9\",inputBorder:\"#475569\",toolbarBg:\"#2563eb\",toolbarBorder:\"#3b82f6\",toolbarText:\"#ffffff\",toolbarBtnBg:\"#3b82f6\",toolbarBtnText:\"#ffffff\",selectionHandle:\"#f59e0b\",groupIndicator:\"#22d3ee\",minimapDots:\"#64748b\",canvasHintBg:\"#1e293b\",canvasHintColor:\"#94a3b8\",canvasBorder:\"#334155\",canvasGrid:\"#334155\",rackFrameFill:\"#1e293b\",rackFrameStroke:\"#3b82f6\",rackLineColor:\"#475569\",rackTextColor:\"#3b82f6\" },\n  graphite: { panel:\"#1f2937\",panelAlt:\"#374151\",sidebarBg:\"#111827\",btnBg:\"#374151\",btnText:\"#f9fafb\",accent:\"#f59e0b\",danger:\"#ef4444\",textMain:\"#f9fafb\",textSoft:\"#9ca3af\",topbarBg:\"#111827\",topbarBorder:\"#4b5563\",nodeFill:\"#374151\",nodeStroke:\"#f59e0b\",nodeTitle:\"#f9fafb\",nodeSub:\"#9ca3af\",defaultEdge:\"#6b7280\",canvasGradientTop:\"#1f2937\",canvasGradientBottom:\"#111827\",tagFill:\"#44403c\",tagText:\"#fbbf24\",tagBorder:\"#f59e0b\",inputBg:\"#111827\",inputText:\"#f9fafb\",inputBorder:\"#4b5563\",toolbarBg:\"#b45309\",toolbarBorder:\"#f59e0b\",toolbarText:\"#ffffff\",toolbarBtnBg:\"#f59e0b\",toolbarBtnText:\"#000000\",selectionHandle:\"#10b981\",groupIndicator:\"#06b6d4\",minimapDots:\"#6b7280\",canvasHintBg:\"#1f2937\",canvasHintColor:\"#9ca3af\",canvasBorder:\"#4b5563\",canvasGrid:\"#374151\",rackFrameFill:\"#1f2937\",rackFrameStroke:\"#f59e0b\",rackLineColor:\"#4b5563\",rackTextColor:\"#fbbf24\" },\n  frost: { panel:\"#f8fafc\",panelAlt:\"#e2e8f0\",sidebarBg:\"#f1f5f9\",btnBg:\"#e2e8f0\",btnText:\"#1e293b\",accent:\"#1e40af\",danger:\"#dc2626\",textMain:\"#0f172a\",textSoft:\"#475569\",topbarBg:\"#1e40af\",topbarBorder:\"#1e3a8a\",nodeFill:\"#ffffff\",nodeStroke:\"#1e40af\",nodeTitle:\"#0f172a\",nodeSub:\"#475569\",defaultEdge:\"#64748b\",canvasGradientTop:\"#e0e7ef\",canvasGradientBottom:\"#f8fafc\",tagFill:\"#dbeafe\",tagText:\"#1e40af\",tagBorder:\"#3b82f6\",inputBg:\"#ffffff\",inputText:\"#0f172a\",inputBorder:\"#cbd5e1\",toolbarBg:\"#1e40af\",toolbarBorder:\"#1e3a8a\",toolbarText:\"#ffffff\",toolbarBtnBg:\"#3b82f6\",toolbarBtnText:\"#ffffff\",selectionHandle:\"#ea580c\",groupIndicator:\"#059669\",minimapDots:\"#64748b\",canvasHintBg:\"#e2e8f0\",canvasHintColor:\"#475569\",canvasBorder:\"#cbd5e1\",canvasGrid:\"#cbd5e1\",rackFrameFill:\"#f1f5f9\",rackFrameStroke:\"#1e40af\",rackLineColor:\"#94a3b8\",rackTextColor:\"#1e40af\" },\n  synthwave: { panel:\"#87366d\",panelAlt:\"#10141b\",sidebarBg:\"#340934\",btnBg:\"#0b0e13\",btnText:\"#e2e8f0\",accent:\"#4fd1c5\",danger:\"#f56565\",textMain:\"#e2e8f0\",textSoft:\"#94a3b8\",topbarBg:\"#781c67\",topbarBorder:\"#1f2533\",nodeFill:\"#1e293b\",nodeStroke:\"#475569\",nodeTitle:\"#e2e8f0\",nodeSub:\"#94a3b8\",defaultEdge:\"#475569\",canvasGradientTop:\"#1e2532\",canvasGradientBottom:\"#050608\",tagFill:\"#1e293b\",tagText:\"#e2e8f0\",tagBorder:\"#475569\",inputBg:\"#0b0e13\",inputText:\"#e2e8f0\",inputBorder:\"#1f2937\",toolbarBg:\"#b95aed\",toolbarBorder:\"#b800eb\",toolbarText:\"#000000\",toolbarBtnBg:\"#ed01fe\",toolbarBtnText:\"#000000\",selectionHandle:\"#f59e0b\",groupIndicator:\"#4fd1c5\",minimapDots:\"#000000\",canvasHintBg:\"#0f172a\",canvasHintColor:\"#94a3b8\",canvasBorder:\"#000000\",canvasGrid:\"#000000\",rackFrameFill:\"#0f172a\",rackFrameStroke:\"#4fd1c5\",rackLineColor:\"#475569\",rackTextColor:\"#4fd1c5\" },\n  terminal: { panel:\"#000000\",panelAlt:\"#0a0a0a\",sidebarBg:\"#050505\",btnBg:\"#0a0a0a\",btnText:\"#33ff33\",accent:\"#33ff33\",danger:\"#ffaa00\",textMain:\"#33ff33\",textSoft:\"#1a9a1a\",topbarBg:\"#000000\",topbarBorder:\"#33ff33\",nodeFill:\"#0a0a0a\",nodeStroke:\"#33ff33\",nodeTitle:\"#33ff33\",nodeSub:\"#1a9a1a\",defaultEdge:\"#1a9a1a\",canvasGradientTop:\"#0a0f0a\",canvasGradientBottom:\"#000000\",tagFill:\"#0a1a0a\",tagText:\"#33ff33\",tagBorder:\"#33ff33\",inputBg:\"#000000\",inputText:\"#33ff33\",inputBorder:\"#1a9a1a\",toolbarBg:\"#1a9a1a\",toolbarBorder:\"#33ff33\",toolbarText:\"#000000\",toolbarBtnBg:\"#33ff33\",toolbarBtnText:\"#000000\",selectionHandle:\"#ffaa00\",groupIndicator:\"#00ffff\",minimapDots:\"#33ff33\",canvasHintBg:\"#0a0a0a\",canvasHintColor:\"#1a9a1a\",canvasBorder:\"#1a9a1a\",canvasGrid:\"#0f1f0f\",rackFrameFill:\"#050505\",rackFrameStroke:\"#33ff33\",rackLineColor:\"#1a9a1a\",rackTextColor:\"#33ff33\" },\n  dracula: { panel:\"#282a36\",panelAlt:\"#44475a\",sidebarBg:\"#21222c\",btnBg:\"#44475a\",btnText:\"#f8f8f2\",accent:\"#bd93f9\",danger:\"#ff5555\",textMain:\"#f8f8f2\",textSoft:\"#6272a4\",topbarBg:\"#21222c\",topbarBorder:\"#6272a4\",nodeFill:\"#44475a\",nodeStroke:\"#ff79c6\",nodeTitle:\"#f8f8f2\",nodeSub:\"#8be9fd\",defaultEdge:\"#bd93f9\",canvasGradientTop:\"#282a36\",canvasGradientBottom:\"#1a1b23\",tagFill:\"#3d3f4a\",tagText:\"#50fa7b\",tagBorder:\"#50fa7b\",inputBg:\"#21222c\",inputText:\"#f8f8f2\",inputBorder:\"#6272a4\",toolbarBg:\"#6272a4\",toolbarBorder:\"#bd93f9\",toolbarText:\"#f8f8f2\",toolbarBtnBg:\"#bd93f9\",toolbarBtnText:\"#282a36\",selectionHandle:\"#f1fa8c\",groupIndicator:\"#ff79c6\",minimapDots:\"#bd93f9\",canvasHintBg:\"#282a36\",canvasHintColor:\"#6272a4\",canvasBorder:\"#44475a\",canvasGrid:\"#44475a\",rackFrameFill:\"#282a36\",rackFrameStroke:\"#ff79c6\",rackLineColor:\"#6272a4\",rackTextColor:\"#8be9fd\" },\n  cobalt: { panel:\"#002240\",panelAlt:\"#003366\",sidebarBg:\"#001b33\",btnBg:\"#003366\",btnText:\"#ffffff\",accent:\"#ffc600\",danger:\"#ff628c\",textMain:\"#ffffff\",textSoft:\"#8090a0\",topbarBg:\"#001525\",topbarBorder:\"#0088ff\",nodeFill:\"#003366\",nodeStroke:\"#0088ff\",nodeTitle:\"#ffffff\",nodeSub:\"#80ffbb\",defaultEdge:\"#0088ff\",canvasGradientTop:\"#002240\",canvasGradientBottom:\"#00111f\",tagFill:\"#004080\",tagText:\"#ffc600\",tagBorder:\"#0088ff\",inputBg:\"#001525\",inputText:\"#ffffff\",inputBorder:\"#0066cc\",toolbarBg:\"#0066cc\",toolbarBorder:\"#0088ff\",toolbarText:\"#ffffff\",toolbarBtnBg:\"#0088ff\",toolbarBtnText:\"#ffffff\",selectionHandle:\"#ffc600\",groupIndicator:\"#3ad900\",minimapDots:\"#0088ff\",canvasHintBg:\"#002240\",canvasHintColor:\"#8090a0\",canvasBorder:\"#0066cc\",canvasGrid:\"#003366\",rackFrameFill:\"#002240\",rackFrameStroke:\"#0088ff\",rackLineColor:\"#0066cc\",rackTextColor:\"#80ffbb\" },\n  solarized: { panel:\"#073642\",panelAlt:\"#002b36\",sidebarBg:\"#002b36\",btnBg:\"#073642\",btnText:\"#93a1a1\",accent:\"#268bd2\",danger:\"#dc322f\",textMain:\"#93a1a1\",textSoft:\"#657b83\",topbarBg:\"#002b36\",topbarBorder:\"#586e75\",nodeFill:\"#073642\",nodeStroke:\"#2aa198\",nodeTitle:\"#93a1a1\",nodeSub:\"#839496\",defaultEdge:\"#657b83\",canvasGradientTop:\"#073642\",canvasGradientBottom:\"#002b36\",tagFill:\"#0a4050\",tagText:\"#b58900\",tagBorder:\"#b58900\",inputBg:\"#002b36\",inputText:\"#93a1a1\",inputBorder:\"#586e75\",toolbarBg:\"#268bd2\",toolbarBorder:\"#2aa198\",toolbarText:\"#fdf6e3\",toolbarBtnBg:\"#2aa198\",toolbarBtnText:\"#002b36\",selectionHandle:\"#cb4b16\",groupIndicator:\"#d33682\",minimapDots:\"#657b83\",canvasHintBg:\"#073642\",canvasHintColor:\"#657b83\",canvasBorder:\"#586e75\",canvasGrid:\"#094050\",rackFrameFill:\"#002b36\",rackFrameStroke:\"#2aa198\",rackLineColor:\"#586e75\",rackTextColor:\"#859900\" },\n  brainwave: { panel:\"#1a1625\",panelAlt:\"#2d2640\",sidebarBg:\"#12101c\",btnBg:\"#2d2640\",btnText:\"#e8dff5\",accent:\"#9d4edd\",danger:\"#ff6b9d\",textMain:\"#e8dff5\",textSoft:\"#a89cc8\",topbarBg:\"#12101c\",topbarBorder:\"#5a189a\",nodeFill:\"#240046\",nodeStroke:\"#7b2cbf\",nodeTitle:\"#e0aaff\",nodeSub:\"#c77dff\",defaultEdge:\"#9d4edd\",canvasGradientTop:\"#1a1625\",canvasGradientBottom:\"#0d0a14\",tagFill:\"#3c096c\",tagText:\"#e0aaff\",tagBorder:\"#9d4edd\",inputBg:\"#12101c\",inputText:\"#e8dff5\",inputBorder:\"#5a189a\",toolbarBg:\"#7b2cbf\",toolbarBorder:\"#9d4edd\",toolbarText:\"#ffffff\",toolbarBtnBg:\"#9d4edd\",toolbarBtnText:\"#ffffff\",selectionHandle:\"#ff9e00\",groupIndicator:\"#00f5d4\",minimapDots:\"#9d4edd\",canvasHintBg:\"#1a1625\",canvasHintColor:\"#a89cc8\",canvasBorder:\"#5a189a\",canvasGrid:\"#2d2640\",rackFrameFill:\"#1a1625\",rackFrameStroke:\"#9d4edd\",rackLineColor:\"#5a189a\",rackTextColor:\"#e0aaff\" },\n  neonMint: { panel:\"#0d1117\",panelAlt:\"#161b22\",sidebarBg:\"#010409\",btnBg:\"#161b22\",btnText:\"#7ee8c7\",accent:\"#00ffc8\",danger:\"#ff6b6b\",textMain:\"#7ee8c7\",textSoft:\"#3fb68a\",topbarBg:\"#010409\",topbarBorder:\"#00ffc8\",nodeFill:\"#0d1f17\",nodeStroke:\"#00ffc8\",nodeTitle:\"#7ee8c7\",nodeSub:\"#3fb68a\",defaultEdge:\"#00d9a7\",canvasGradientTop:\"#0d1117\",canvasGradientBottom:\"#010409\",tagFill:\"#0d2818\",tagText:\"#00ffc8\",tagBorder:\"#00d9a7\",inputBg:\"#010409\",inputText:\"#7ee8c7\",inputBorder:\"#1a4d3a\",toolbarBg:\"#00a67e\",toolbarBorder:\"#00ffc8\",toolbarText:\"#010409\",toolbarBtnBg:\"#00ffc8\",toolbarBtnText:\"#010409\",selectionHandle:\"#ffd000\",groupIndicator:\"#ff79c6\",minimapDots:\"#00ffc8\",canvasHintBg:\"#0d1117\",canvasHintColor:\"#3fb68a\",canvasBorder:\"#1a4d3a\",canvasGrid:\"#0a2920\",rackFrameFill:\"#0d1117\",rackFrameStroke:\"#00ffc8\",rackLineColor:\"#1a4d3a\",rackTextColor:\"#00ffc8\" },\n  velvetDusk: { panel:\"#1f1d2e\",panelAlt:\"#26233a\",sidebarBg:\"#191724\",btnBg:\"#26233a\",btnText:\"#e0def4\",accent:\"#eb6f92\",danger:\"#eb6f92\",textMain:\"#e0def4\",textSoft:\"#908caa\",topbarBg:\"#191724\",topbarBorder:\"#524f67\",nodeFill:\"#26233a\",nodeStroke:\"#c4a7e7\",nodeTitle:\"#e0def4\",nodeSub:\"#9ccfd8\",defaultEdge:\"#ebbcba\",canvasGradientTop:\"#1f1d2e\",canvasGradientBottom:\"#191724\",tagFill:\"#31283d\",tagText:\"#f6c177\",tagBorder:\"#c4a7e7\",inputBg:\"#191724\",inputText:\"#e0def4\",inputBorder:\"#524f67\",toolbarBg:\"#c4a7e7\",toolbarBorder:\"#eb6f92\",toolbarText:\"#191724\",toolbarBtnBg:\"#eb6f92\",toolbarBtnText:\"#191724\",selectionHandle:\"#f6c177\",groupIndicator:\"#31748f\",minimapDots:\"#c4a7e7\",canvasHintBg:\"#1f1d2e\",canvasHintColor:\"#908caa\",canvasBorder:\"#524f67\",canvasGrid:\"#312e45\",rackFrameFill:\"#1f1d2e\",rackFrameStroke:\"#c4a7e7\",rackLineColor:\"#524f67\",rackTextColor:\"#9ccfd8\" },\n  sunburst: { panel:\"#fff7ed\",panelAlt:\"#ffedd5\",sidebarBg:\"#fffbf7\",btnBg:\"#ffedd5\",btnText:\"#9a3412\",accent:\"#f97316\",danger:\"#dc2626\",textMain:\"#431407\",textSoft:\"#9a3412\",topbarBg:\"#ea580c\",topbarBorder:\"#c2410c\",nodeFill:\"#fff7ed\",nodeStroke:\"#fb923c\",nodeTitle:\"#431407\",nodeSub:\"#9a3412\",defaultEdge:\"#fdba74\",canvasGradientTop:\"#fff7ed\",canvasGradientBottom:\"#fffbf7\",tagFill:\"#fed7aa\",tagText:\"#c2410c\",tagBorder:\"#f97316\",inputBg:\"#fffbf7\",inputText:\"#431407\",inputBorder:\"#fed7aa\",toolbarBg:\"#f97316\",toolbarBorder:\"#ea580c\",toolbarText:\"#ffffff\",toolbarBtnBg:\"#fb923c\",toolbarBtnText:\"#431407\",selectionHandle:\"#0891b2\",groupIndicator:\"#059669\",minimapDots:\"#f97316\",canvasHintBg:\"#fff7ed\",canvasHintColor:\"#9a3412\",canvasBorder:\"#fed7aa\",canvasGrid:\"#fdba74\",rackFrameFill:\"#fff7ed\",rackFrameStroke:\"#f97316\",rackLineColor:\"#fed7aa\",rackTextColor:\"#c2410c\" }\n};\ndocument.getElementById(\"theme-preset\").addEventListener(\"change\", function() {\n  updateDeleteButton();\n  var p = THEME_PRESETS[this.value];\n  if (!p && this.value.startsWith(\"mytheme-\")) {\n    var found = savedStyleSets.find(function(s) { return s.id === document.getElementById(\"theme-preset\").value; });\n    if (found) p = found.styles;\n  }\n  if (!p) return;\n  Object.assign(PAGE_STATE, p);\n  document.getElementById(\"panel-color\").value = p.panel;\n  document.getElementById(\"panel-alt-color\").value = p.panelAlt;\n  document.getElementById(\"sidebar-bg-color\").value = p.sidebarBg;\n  document.getElementById(\"btn-bg-color\").value = p.btnBg;\n  document.getElementById(\"btn-text-color\").value = p.btnText;\n  document.getElementById(\"accent-color\").value = p.accent;\n  document.getElementById(\"danger-color\").value = p.danger;\n  document.getElementById(\"text-main-color\").value = p.textMain;\n  document.getElementById(\"text-soft-color\").value = p.textSoft;\n  document.getElementById(\"topbar-border-color\").value = p.topbarBorder;\n  document.getElementById(\"node-fill-color\").value = p.nodeFill;\n  document.getElementById(\"node-stroke-color\").value = p.nodeStroke;\n  document.getElementById(\"node-title-color\").value = p.nodeTitle;\n  document.getElementById(\"node-sub-color\").value = p.nodeSub;\n  document.getElementById(\"default-edge-color\").value = p.defaultEdge;\n  document.getElementById(\"canvas-gradient-top\").value = p.canvasGradientTop;\n  document.getElementById(\"canvas-gradient-bottom\").value = p.canvasGradientBottom;\n  document.getElementById(\"tag-fill-color\").value = p.tagFill;\n  document.getElementById(\"tag-text-color\").value = p.tagText;\n  document.getElementById(\"tag-border-color\").value = p.tagBorder;\n  document.getElementById(\"input-bg-color\").value = p.inputBg;\n  document.getElementById(\"input-text-color\").value = p.inputText;\n  document.getElementById(\"input-border-color\").value = p.inputBorder;\n  document.getElementById(\"toolbar-bg-color\").value = p.toolbarBg;\n  document.getElementById(\"toolbar-border-color\").value = p.toolbarBorder;\n  document.getElementById(\"toolbar-text-color\").value = p.toolbarText;\n  document.getElementById(\"toolbar-btn-bg-color\").value = p.toolbarBtnBg;\n  document.getElementById(\"toolbar-btn-text-color\").value = p.toolbarBtnText;\n  document.getElementById(\"selection-handle-color\").value = p.selectionHandle;\n  document.getElementById(\"group-indicator-color\").value = p.groupIndicator;\n  document.getElementById(\"minimap-dots-color\").value = p.minimapDots;\n  document.getElementById(\"canvas-hint-bg-color\").value = p.canvasHintBg;\n  document.getElementById(\"canvas-hint-text-color\").value = p.canvasHintColor;\n  document.getElementById(\"canvas-border-color\").value = p.canvasBorder;\n  document.getElementById(\"canvas-grid-color\").value = p.canvasGrid;\n  document.getElementById(\"rack-frame-fill\").value = p.rackFrameFill;\n  document.getElementById(\"rack-frame-stroke\").value = p.rackFrameStroke;\n  document.getElementById(\"rack-line-color\").value = p.rackLineColor;\n  document.getElementById(\"rack-text-color\").value = p.rackTextColor;\n  wieldThePower();\n  forgeTheTopology();\n});\ndocument.querySelectorAll('#settings-modal .style-content input, #settings-modal .style-content select').forEach(el => {\n  if (el.id === 'theme-preset') return;\n  el.addEventListener('input', () => document.getElementById('theme-preset').value = '');\n  el.addEventListener('change', () => document.getElementById('theme-preset').value = '');\n});\n       document.getElementById(\"panel-color\").addEventListener(\"input\", (e) => {\n        PAGE_STATE.panel = e.target.value;\n        wieldThePower();\n       });\n       document.getElementById(\"panel-alt-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.panelAlt = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"sidebar-bg-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.sidebarBg = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"btn-bg-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.btnBg = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"btn-text-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.btnText = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"tag-fill-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.tagFill = e.target.value;\n       wieldThePower();\n       forgeTheTopology();\n      });\n      document.getElementById(\"tag-text-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.tagText = e.target.value;\n       wieldThePower();\n       forgeTheTopology();\n      });\n      document.getElementById(\"tag-border-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.tagBorder = e.target.value;\n       wieldThePower();\n       forgeTheTopology();\n      });\n      document.getElementById(\"selection-fill-color\").addEventListener(\"input\", function() {\n       selectionBoxStyle.fillColor = this.value;\n      });\n      document.getElementById(\"selection-fill-opacity\").addEventListener(\"input\", function() {\n       selectionBoxStyle.fillOpacity = parseFloat(this.value);\n       document.getElementById(\"selection-fill-opacity-val\").textContent = Math.round(this.value * 100) + \"%\";\n      });\n      document.getElementById(\"selection-stroke-color\").addEventListener(\"input\", function() {\n       selectionBoxStyle.strokeColor = this.value;\n      });\n      document.getElementById(\"selection-stroke-width\").addEventListener(\"input\", function() {\n       selectionBoxStyle.strokeWidth = parseInt(this.value) || 2;\n      });\n      document.getElementById(\"selection-stroke-style\").addEventListener(\"change\", function() {\n       selectionBoxStyle.strokeDasharray = this.value;\n      });\n      document.getElementById(\"input-bg-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.inputBg = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"input-text-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.inputText = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"input-border-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.inputBorder = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"input-font-family\").addEventListener(\"change\", (e) => {\n       PAGE_STATE.inputFont = e.target.value;\n       wieldThePower();\n      });\n\t  document.getElementById(\"input-font-size\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.inputFontSize = parseInt(e.target.value) || 14;\n       wieldThePower();\n      });\n      document.getElementById(\"toolbar-bg-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.toolbarBg = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"toolbar-border-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.toolbarBorder = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"toolbar-text-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.toolbarText = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"toolbar-btn-bg-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.toolbarBtnBg = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"toolbar-btn-text-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.toolbarBtnText = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"minimap-dots-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.minimapDots = e.target.value;\n       wieldThePower();\n       forgeTheTopology();\n      });\n      document.getElementById(\"canvas-hint-enabled\").addEventListener(\"change\", (e) => {\n       PAGE_STATE.canvasHintEnabled = e.target.checked;\n       wieldThePower();\n      });\n      document.getElementById(\"canvas-hint-bg-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.canvasHintBg = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"canvas-hint-text-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.canvasHintColor = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"canvas-hint-text\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.canvasHintText = e.target.value;\n       wieldThePower();\n      });\n       document.getElementById(\"accent-color\").addEventListener(\"input\", (e) => {\n        PAGE_STATE.accent = e.target.value;\n        wieldThePower();\n       });\n       document.getElementById(\"danger-color\").addEventListener(\"input\", (e) => {\n        PAGE_STATE.danger = e.target.value;\n        wieldThePower();\n       });\n       document.getElementById(\"text-main-color\").addEventListener(\"input\", (e) => {\n        PAGE_STATE.textMain = e.target.value;\n        wieldThePower();\n       });\n       document.getElementById(\"text-soft-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.textSoft = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"node-fill-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.nodeFill = e.target.value;\n       forgeTheTopology();\n      });\n      document.getElementById(\"node-stroke-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.nodeStroke = e.target.value;\n       forgeTheTopology();\n      });\n      document.getElementById(\"node-title-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.nodeTitle = e.target.value;\n       forgeTheTopology();\n      });\n      document.getElementById(\"node-sub-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.nodeSub = e.target.value;\n       forgeTheTopology();\n      });\n      document.getElementById(\"node-title-size\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.nodeTitleSize = parseInt(e.target.value) || 18;\n       forgeTheTopology();\n      });\n      document.getElementById(\"node-sub-size\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.nodeSubSize = parseInt(e.target.value) || 13;\n       forgeTheTopology();\n      });\n      document.getElementById(\"node-font-family\").addEventListener(\"change\", (e) => {\n       PAGE_STATE.nodeFont = e.target.value;\n       forgeTheTopology();\n      });\n      document.getElementById(\"default-edge-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.defaultEdge = e.target.value;\n       document.getElementById(\"add-line-color\").value = e.target.value;\n       forgeTheTopology();\n      });\n      document.getElementById(\"show-port-labels-toggle\").addEventListener(\"change\", (e) => {\n       PAGE_STATE.showPortLabels = e.target.checked;\n       forgeTheTopology();\n      });\n      document.getElementById(\"default-edge-routing\").addEventListener(\"change\", (e) => {\n       PAGE_STATE.defaultEdgeRouting = e.target.value;\n       document.getElementById(\"add-line-routing\").value = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"apply-routing-all\").addEventListener(\"click\", async () => {\n       const routing = document.getElementById(\"default-edge-routing\").value;\n       if (!await showConfirm(t(\"dialogs.applyRoutingToAll\", { routing: routing, count: EDGE_DATA.list.length }))) return;\n       pushUndo(\"apply routing to all\");\n       EDGE_DATA.list.forEach(edge => {\n        edge.routing = routing;\n       });\n       forgeTheTopology();\n      });\n      document.getElementById(\"selection-handle-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.selectionHandle = e.target.value;\n       forgeTheTopology();\n      });\n      document.getElementById(\"selection-handle-size\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.selectionHandleSize = parseInt(e.target.value) || 8;\n       forgeTheTopology();\n      });\n      document.getElementById(\"group-indicator-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.groupIndicator = e.target.value;\n       forgeTheTopology();\n      });\n      document.getElementById(\"canvas-gradient-top\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.canvasGradientTop = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"canvas-gradient-bottom\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.canvasGradientBottom = e.target.value;\n       wieldThePower();\n      });\n      document.getElementById(\"canvas-border-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.canvasBorder = e.target.value;\n       forgeTheTopology();\n      });\n      document.getElementById(\"canvas-grid-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.canvasGrid = e.target.value;\n       forgeTheTopology();\n      });\n      document.getElementById(\"canvas-grid-size\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.canvasGridSize = parseInt(e.target.value) || 50;\n       forgeTheTopology();\n      });\n      document.getElementById(\"canvas-grid-enabled\").addEventListener(\"change\", (e) => {\n       PAGE_STATE.canvasGridEnabled = e.target.checked;\n       forgeTheTopology();\n      });\n      document.getElementById(\"rack-frame-fill\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.rackFrameFill = e.target.value;\n       forgeTheTopology();\n      });\n      document.getElementById(\"rack-frame-stroke\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.rackFrameStroke = e.target.value;\n       forgeTheTopology();\n      });\n      document.getElementById(\"rack-line-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.rackLineColor = e.target.value;\n       forgeTheTopology();\n      });\n      document.getElementById(\"rack-grid-enabled\").addEventListener(\"change\", (e) => {\n       PAGE_STATE.rackGridEnabled = e.target.checked;\n       forgeTheTopology();\n      });\n      document.getElementById(\"rack-text-color\").addEventListener(\"input\", (e) => {\n       PAGE_STATE.rackTextColor = e.target.value;\n       forgeTheTopology();\n      });\n      (function initializeResizers() {\n         const headerResizer = document.getElementById('header-resizer');\n         const sidebarResizer = document.getElementById('sidebar-resizer');\n         const mobileFooterResizer = document.getElementById('mobile-footer-resizer');\n         let isResizing = false;\n         let currentResizer = null;\n         let startY = 0;\n         let startX = 0;\n         let startHeight = 0;\n         let startWidth = 0;\n         function getClientPos(e) {\n           if (e.touches && e.touches.length > 0) {\n             return { x: e.touches[0].clientX, y: e.touches[0].clientY };\n           }\n           return { x: e.clientX, y: e.clientY };\n         }\n         function startResize(resizer, type, e) {\n           isResizing = true;\n           currentResizer = type;\n           const pos = getClientPos(e);\n           if (type === 'header') {\n             startY = pos.y;\n             startHeight = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--topbar-height'));\n           } else if (type === 'sidebar') {\n             startX = pos.x;\n             startWidth = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--sidebar-width'));\n           } else if (type === 'mobile-footer') {\n             startY = pos.y;\n             const currentVh = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--mobile-footer-height'));\n             startHeight = (currentVh / 100) * window.innerHeight;\n           }\n           resizer.classList.add('resizing');\n           document.body.classList.add('resizing');\n           document.body.style.cursor = (type === 'sidebar') ? 'col-resize' : 'row-resize';\n           e.preventDefault();\n         }\n         if (headerResizer) {\n           headerResizer.addEventListener('mousedown', (e) => startResize(headerResizer, 'header', e));\n           headerResizer.addEventListener('touchstart', (e) => startResize(headerResizer, 'header', e), { passive: false });\n         }\n         if (sidebarResizer) {\n           sidebarResizer.addEventListener('mousedown', (e) => startResize(sidebarResizer, 'sidebar', e));\n           sidebarResizer.addEventListener('touchstart', (e) => startResize(sidebarResizer, 'sidebar', e), { passive: false });\n         }\n         if (mobileFooterResizer) {\n           mobileFooterResizer.addEventListener('mousedown', (e) => startResize(mobileFooterResizer, 'mobile-footer', e));\n           mobileFooterResizer.addEventListener('touchstart', (e) => startResize(mobileFooterResizer, 'mobile-footer', e), { passive: false });\n         }\n         function handleMove(e) {\n           if (!isResizing) return;\n           const pos = getClientPos(e);\n           if (currentResizer === 'header') {\n             const deltaY = pos.y - startY;\n             const newHeight = Math.max(40, Math.min(150, startHeight + deltaY));\n             document.documentElement.style.setProperty('--topbar-height', newHeight + 'px');\n           } else if (currentResizer === 'sidebar') {\n             const deltaX = startX - pos.x;\n             const newWidth = Math.max(200, Math.min(800, startWidth + deltaX));\n             document.documentElement.style.setProperty('--sidebar-width', newWidth + 'px');\n           } else if (currentResizer === 'mobile-footer') {\n             const deltaY = startY - pos.y;\n             const newHeight = startHeight + deltaY;\n             const newVh = Math.max(15, Math.min(80, (newHeight / window.innerHeight) * 100));\n             document.documentElement.style.setProperty('--mobile-footer-height', newVh + 'vh');\n           }\n           e.preventDefault();\n         }\n         document.addEventListener('mousemove', handleMove);\n         document.addEventListener('touchmove', handleMove, { passive: false });\n         function handleEnd() {\n           if (isResizing) {\n             isResizing = false;\n             if (currentResizer === 'header') {\n               PAGE_STATE.topbarHeight = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--topbar-height'));\n               headerResizer.classList.remove('resizing');\n             } else if (currentResizer === 'sidebar') {\n               PAGE_STATE.sidebarWidth = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--sidebar-width'));\n               sidebarResizer.classList.remove('resizing');\n             } else if (currentResizer === 'mobile-footer') {\n               PAGE_STATE.mobileFooterHeight = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--mobile-footer-height'));\n               mobileFooterResizer.classList.remove('resizing');\n             }\n             document.body.classList.remove('resizing');\n             document.body.style.cursor = '';\n             currentResizer = null;\n           }\n         }\n         document.addEventListener('mouseup', handleEnd);\n         document.addEventListener('touchend', handleEnd);\n         document.addEventListener('touchcancel', handleEnd);\n       })();\n       document.getElementById(\"auto-ping-enabled\").addEventListener(\"change\", (e) => {\n        autoPingEnabled = e.target.checked;\n        PAGE_STATE.autoPingEnabled = autoPingEnabled;\n        document.getElementById(\"auto-ping-settings\").style.display = autoPingEnabled ? 'block' : 'none';\n        if (autoPingEnabled) {\n         startAutoPing();\n        } else {\n         stopAutoPing();\n        }\n       });\n       document.getElementById(\"auto-ping-interval\").addEventListener(\"change\", (e) => {\n        const newInterval = parseInt(e.target.value, 10);\n        if (newInterval >= 5 && newInterval <= 3600) {\n         autoPingInterval = newInterval;\n         PAGE_STATE.autoPingInterval = autoPingInterval;\n         if (autoPingEnabled) {\n          startAutoPing();\n         }\n        }\n       });\n       document.getElementById(\"import-data-file\").addEventListener(\"change\", async (e) => {\n        const file = e.target.files[0];\n        if (!file) return;\n        try {\n         const text = await file.text();\n         const data = JSON.parse(text);\n         if (!data.nodeData || !data.edgeData) {\n          showAlert(t(\"dialogs.invalidDataFile\"));\n          return;\n         }\n         if (!await showConfirm(t(\"dialogs.confirmImportJson\", { nodeCount: Object.keys(data.nodeData).length, edgeCount: data.edgeData.list?.length || 0, tabCount: data.documentTabs?.length || 1 }))) {\n          e.target.value = \"\";\n          return;\n         }\n\t\t pushUndo('import json');\n         NODE_DATA = data.nodeData || {};\n         EDGE_DATA = data.edgeData || {\n          list: []\n         };\n         EDGE_LEGEND = data.edgeLegend || {};\n         RECT_DATA = data.rectData || { list: [] };\n         TEXT_DATA = data.textData || { list: [] };\n         IMAGE_DATA = data.imageData || { list: [] };\n         savedPositions = data.nodePositions || {};\n         savedSizes = data.nodeSizes || {};\n         savedStyles = data.nodeStyles || {};\n         if (data.page) {\n          PAGE_STATE = Object.assign({}, DEFAULT_PAGE_STATE, data.page);\n          wieldThePower();\n         }\n      if (data.autoPingEnabled !== undefined) {\n      autoPingEnabled = data.autoPingEnabled;\n      PAGE_STATE.autoPingEnabled = autoPingEnabled;\n      document.getElementById(\"auto-ping-enabled\").checked = autoPingEnabled;\n      }\n      if (data.autoPingInterval !== undefined) {\n      autoPingInterval = data.autoPingInterval;\n      PAGE_STATE.autoPingInterval = autoPingInterval;\n      document.getElementById(\"auto-ping-interval\").value = autoPingInterval;\n      }\n         if (data.canvas) {\n          canvasState.zoom = data.canvas.zoom || 1;\n          canvasState.panX = data.canvas.panX || 0;\n          canvasState.panY = data.canvas.panY || 0;\n         }\n         if (data.page?.title) {\n         document.title = data.page.title;\n         document.querySelector(\".editable-page-title\", ).textContent = data.page.title;\n        }\n        if (data.savedStyleSets) {\n          savedStyleSets = data.savedStyleSets;\n        }\n        if (data.documentTabs) {\n         documentTabs = data.documentTabs;\n         currentTabIndex = data.currentTabIndex || 0;\n        }\n        if (data.savedTopologyView) {\n         savedTopologyView = data.savedTopologyView;\n        }\n        if (data.encryptedSections) {\n         encryptedSections = data.encryptedSections;\n        }\n        if (data.auditLog && Array.isArray(data.auditLog)) {\n         auditLog = data.auditLog;\n         try {\n           localStorage.setItem(AUDIT_STORAGE_KEY, JSON.stringify(auditLog));\n         } catch (e) {\n           console.warn(\"Failed to sync audit log to localStorage:\", e);\n         }\n        }\n        if (data.recordings && Array.isArray(data.recordings)) {\n          RECORDING_STATE.recordings = data.recordings;\n          RECORDING_STATE.currentRecording = data.recordings[0] || null;\n        }\n        const layerMap = { \"physical\": \"layer1\", \"logical\": \"layer2\", \"security\": \"layer3\", \"application\": \"layer4\" };\n        Object.values(NODE_DATA).forEach(node => {\n          if (!node.tags) node.tags = [];\n          if (!node.notes) node.notes = [];\n          if (!node.layer || layerMap[node.layer]) node.layer = layerMap[node.layer] || 'layer1';\n        });\n        EDGE_DATA.list.forEach(edge => {\n          if (!edge.notes) edge.notes = [];\n        });\n        forgeTheTopology();\n        if (typeof forgeTheLegend === 'function') forgeTheLegend();\n        if (typeof updateZoneLegend === 'function') updateZoneLegend();\n        const nodeCount = Object.keys(data.nodeData).length;\n        const edgeCount = data.edgeData.list?.length || 0;\n        const rectCount = data.rectData?.list?.length || 0;\n        const textCount = data.textData?.list?.length || 0;\n        logAuditEvent(\"import\", `Imported JSON: ${file.name} (${nodeCount} nodes, ${edgeCount} connections)`);\n        updateViewBox();\n      if (autoPingEnabled) {\n      startAutoPing();\n      } else {\n      stopAutoPing();\n      }\n         const nodeIds = Object.keys(NODE_DATA);\n         if (nodeIds.length > 0) {\n          claimTheImmortal(nodeIds[0]);\n         } else {\n          document.getElementById(\"node-panel\").style.display = \"none\";\n          document.getElementById(\"edge-panel\").style.display = \"none\";\n          document.getElementById(\"topology-toolbar\").style.display = \"none\";\n         }\n         updateDrawToolbarVisibility();\n         showAlert(t(\"dialogs.jsonImportSuccess\", { nodeCount, edgeCount, rectCount, textCount }));\n         e.target.value = \"\";\n        } catch (err) {\n         console.error(\"Import error:\", err);\n         showAlert(t(\"dialogs.importDataFailed\", { error: err.message }));\n         e.target.value = \"\";\n        }\n       });\n       const saveHelpBtn = document.getElementById(\"save-help-btn\");\n       const saveInfoModal = document.getElementById(\"save-info-modal\");\n       const saveInfoClose = document.getElementById(\"save-info-close\");\n       saveHelpBtn.addEventListener(\"click\", () => {\n        saveInfoModal.classList.add(\"active\");\n       });\n       saveInfoClose.addEventListener(\"click\", () => {\n        saveInfoModal.classList.remove(\"active\");\n       });\n       saveInfoModal.addEventListener(\"click\", (e) => {\n       if (e.target === saveInfoModal) {\n        saveInfoModal.classList.remove(\"active\");\n       }\n      });\n      document.querySelectorAll(\".help-tab\").forEach(tab => {\n        tab.addEventListener(\"click\", () => {\n          document.querySelectorAll(\".help-tab\").forEach(t => { t.style.background = \"var(--panel)\"; t.style.color = \"var(--text-main)\"; });\n          tab.style.background = \"var(--accent)\"; tab.style.color = \"var(--bg)\";\n          document.querySelectorAll(\".help-tab-content\").forEach(c => c.style.display = \"none\");\n          document.getElementById(\"help-tab-\" + tab.dataset.tab).style.display = \"block\";\n        });\n      });\n      async function deriveKey(password, salt) {\n      const encoder = new TextEncoder();\n      const keyMaterial = await crypto.subtle.importKey(\n      \"raw\",\n      encoder.encode(password),\n      \"PBKDF2\",\n      false,\n      [\"deriveKey\"]\n      );\n      return crypto.subtle.deriveKey(\n      {\n      name: \"PBKDF2\",\n      salt: salt,\n      iterations: 200000,\n      hash: \"SHA-256\"\n      },\n      keyMaterial,\n      {\n      name: \"AES-GCM\",\n      length: 256\n      },\n      false,\n      [\"encrypt\", \"decrypt\"]\n      );\n      }\n       async function encryptData(data, password) {\n      const encoder = new TextEncoder();\n      const salt = crypto.getRandomValues(new Uint8Array(16));\n      const iv   = crypto.getRandomValues(new Uint8Array(12));\n      const key = await deriveKey(password, salt);\n      const encrypted = await crypto.subtle.encrypt(\n      { name: \"AES-GCM\", iv },\n      key,\n      encoder.encode(data)\n      );\n      const encryptedU8 = new Uint8Array(encrypted);\n      const result = new Uint8Array(salt.length + iv.length + encryptedU8.length);\n      result.set(salt, 0);\n      result.set(iv, salt.length);\n      result.set(encryptedU8, salt.length + iv.length);\n      return \"ENCRYPTED:\" + u8ToBase64(result);\n      }\n       async function decryptData(encryptedData, password) {\n      const base64Data = encryptedData.replace(\"ENCRYPTED:\", \"\");\n      const fullData   = base64ToU8(base64Data);\n      const salt      = fullData.slice(0, 16);\n      const iv        = fullData.slice(16, 28);\n      const encrypted = fullData.slice(28);\n      const key = await deriveKey(password, salt);\n      const decrypted = await crypto.subtle.decrypt(\n      { name: \"AES-GCM\", iv },\n      key,\n      encrypted\n      );\n      const decoder = new TextDecoder();\n      return decoder.decode(decrypted);\n      }\n       function isEncrypted(data) {\n        return typeof data === \"string\" && data.startsWith(\"ENCRYPTED:\");\n       }\n       function captureTheQuickening() {\n      const currentTab = documentTabs[currentTabIndex];\n      currentTab.nodes = JSON.parse(JSON.stringify(NODE_DATA));\n      currentTab.edges = JSON.parse(JSON.stringify(EDGE_DATA));\n      currentTab.positions = JSON.parse(JSON.stringify(savedPositions));\n      currentTab.sizes = JSON.parse(JSON.stringify(savedSizes));\n      currentTab.styles = JSON.parse(JSON.stringify(savedStyles));\n      currentTab.legend = JSON.parse(JSON.stringify(EDGE_LEGEND));\n      currentTab.rects = JSON.parse(JSON.stringify(RECT_DATA));\n      currentTab.texts = JSON.parse(JSON.stringify(TEXT_DATA));\n      currentTab.images = JSON.parse(JSON.stringify(IMAGE_DATA));\n      currentTab.pageState = JSON.parse(JSON.stringify(PAGE_STATE));\n      return {\n      nodeData: NODE_DATA,\n       edgeData: EDGE_DATA,\n       rectData: RECT_DATA,\n       textData: TEXT_DATA,\n       imageData: IMAGE_DATA,\n       edgeLegend: EDGE_LEGEND,\n       zoneLegend: ZONE_LEGEND,\n       zonePresets: ZONE_PRESETS,\n       nodePositions: savedPositions,\n       nodeSizes: savedSizes,\n       nodeStyles: savedStyles,\n       iconCache: IconLibrary.iconCache,\n       page: PAGE_STATE,\n       autoPingEnabled: autoPingEnabled,\n       autoPingInterval: autoPingInterval,\n       canvas: {\n         zoom: canvasState.zoom,\n         panX: canvasState.panX,\n         panY: canvasState.panY,\n       },\n       savedTopologyView: savedTopologyView,\n       documentTabs: documentTabs,\n       currentTabIndex: currentTabIndex,\n       encryptedSections: encryptedSections,\n\t   auditLog: auditLog,\n       savedStyleSets: savedStyleSets,\n       recordings: RECORDING_STATE.recordings,\n       customLanguage: CUSTOM_LANG || null,\n       };\n      }\n       function assembleTheImmortalForm() {\n\t   const canvasHintEl = document.getElementById(\"canvas-hint\");\n       const savedHintHTML = canvasHintEl ? canvasHintEl.innerHTML : \"\";\n       if (canvasHintEl) canvasHintEl.innerHTML = DEFAULT_CANVAS_HINT;\n       const clone = document.documentElement.cloneNode(true);\n       [\"edge-legend-mini\", \"minimap-mini\", \"draw-toolbar-mini\", \"topology-toolbar-mini\"].forEach(id => {\n        const all = clone.querySelectorAll(\"#\" + id);\n        for (let i = 1; i < all.length; i++) {\n         all[i].remove();\n        }\n       });\n       const addLineSelect = clone.querySelector(\"#add-line-select\");\n       if (addLineSelect) addLineSelect.innerHTML = \"\";\n       const minimapSvg = clone.querySelector(\"#minimap\");\n       if (minimapSvg) {\n        minimapSvg.querySelectorAll(\".minimap-node, .minimap-edge\").forEach(el => el.remove());\n       }\n       const canvasGrid = clone.querySelector(\"#canvas-grid\");\n       if (canvasGrid) canvasGrid.innerHTML = \"\";\n       const mainSvg = clone.querySelector(\"#map\");\n       if (mainSvg) mainSvg.innerHTML = \"\";\n       const nodeScript = clone.querySelector(\"#nodes-json\");\n        if (nodeScript) {\n         nodeScript.textContent = JSON.stringify(NODE_DATA, null, 2);\n        }\n        let stateScript = clone.querySelector(\"#topology-state\");\n        if (!stateScript) {\n         stateScript = document.createElement(\"script\");\n         stateScript.id = \"topology-state\";\n         stateScript.type = \"application/json\";\n         const body = clone.querySelector(\"body\") || clone;\n         body.appendChild(stateScript);\n        }\n        stateScript.textContent = JSON.stringify(captureTheQuickening(), null, 2);\n        return \" <!DOCTYPE html> \\n \" + clone.outerHTML;\n       }\n       async function becomeImmortal() {\n        if (RECORDING_STATE.isRecording) stopRecording();\n        if (RECORDING_STATE.isStepRecording) stopStepRecording();\n        const encryptEnabled = document.getElementById(\"encrypt-toggle\").checked;\n        let stateData = JSON.stringify(captureTheQuickening(), null, 2);\n        if (encryptEnabled) {\n         const password = await showPrompt(t(\"dialogs.enterEncryptPassword\"));\n         if (!password) {\n          showAlert(t(\"dialogs.encryptionCancelledNotSaved\"));\n          return;\n         }\n         const confirmPassword = await showPrompt(t(\"dialogs.confirmEncryptPassword\"));\n         if (password !== confirmPassword) {\n          showAlert(t(\"dialogs.passwordsMismatchNotSaved\"));\n          return;\n         }\n         try {\n          stateData = await encryptData(stateData, password);\n         } catch (e) {\n          showAlert(t(\"dialogs.encryptionFailedError\", { error: e.message }));\n          return;\n         }\n        }\n       const canvasHintEl = document.getElementById(\"canvas-hint\");\n       const savedHintHTML = canvasHintEl ? canvasHintEl.innerHTML : \"\";\n       if (canvasHintEl) canvasHintEl.innerHTML = DEFAULT_CANVAS_HINT;\n       const clone = document.documentElement.cloneNode(true);\n       [\"edge-legend-mini\", \"minimap-mini\", \"draw-toolbar-mini\", \"topology-toolbar-mini\"].forEach(id => {\n        const all = clone.querySelectorAll(\"#\" + id);\n        for (let i = 1; i < all.length; i++) {\n         all[i].remove();\n        }\n       });\n       const selectsToClear = [\"#add-line-select\", \"#new-edge-from\", \"#new-edge-to\", \"#assigned-rack-select\"];\n       selectsToClear.forEach(sel => {\n        const el = clone.querySelector(sel);\n        if (el) el.innerHTML = \"\";\n       });\n       const minimapSvg = clone.querySelector(\"#minimap\");\n       if (minimapSvg) minimapSvg.querySelectorAll(\".minimap-node, .minimap-edge\").forEach(el => el.remove());\n       const mainSvg = clone.querySelector(\"#map\");\n       if (mainSvg) mainSvg.innerHTML = \"\";\n       const edgeLegendContent = clone.querySelector(\"#edge-legend-content\");\n       if (edgeLegendContent) edgeLegendContent.innerHTML = \"\";\n       const nodeTags = clone.querySelector(\"#node-tags\");\n       if (nodeTags) nodeTags.innerHTML = \"\";\n       const nodeNotes = clone.querySelector(\"#node-notes\");\n       if (nodeNotes) nodeNotes.innerHTML = \"\";\n       const edgeNotes = clone.querySelector(\"#edge-notes\");\n       if (edgeNotes) edgeNotes.innerHTML = \"\";\n       const tabBar = clone.querySelector(\"#tab-bar\");\n       if (tabBar) tabBar.innerHTML = \"\";\n       const rollbackList = clone.querySelector(\"#rollback-list\");\n       if (rollbackList) rollbackList.innerHTML = \"\";\n       const auditList = clone.querySelector(\"#audit-log-list\");\n       if (auditList) auditList.innerHTML = \"\";\n       clone.querySelectorAll(\".ping-indicator\").forEach(el => el.remove());\n       const nodeScript = clone.querySelector(\"#nodes-json\");\n        if (nodeScript) {\n         if (encryptEnabled) {\n          nodeScript.textContent = JSON.stringify({}, null, 2);\n         } else {\n          nodeScript.textContent = JSON.stringify(NODE_DATA, null, 2);\n         }\n        }\n        let stateScript = clone.querySelector(\"#topology-state\");\n        if (!stateScript) {\n         stateScript = document.createElement(\"script\");\n         stateScript.id = \"topology-state\";\n         stateScript.type = \"application/json\";\n         const body = clone.querySelector(\"body\") || clone;\n         body.appendChild(stateScript);\n        }\n        stateScript.textContent = stateData;\n        const html = \"<!DOCTYPE html> \\n \" + clone.outerHTML;\n        const blob = new Blob([html], {\n         type: \"text/html\"\n        });\n        const url = URL.createObjectURL(blob);\n        const a = document.createElement(\"a\");\n        a.href = url;\n        const safeTitle = (PAGE_STATE.title || document.title || \"network-topology\").toLowerCase().replace(/[^a-z0-9\\-]+/g, \"-\");\n        a.download = safeTitle + \".html\";\n        document.body.appendChild(a);\n        a.click();\n        document.body.removeChild(a);\n        URL.revokeObjectURL(url);\n\t\tif (canvasHintEl) canvasHintEl.innerHTML = savedHintHTML;\n        saveRollbackVersion(\"Manual save\");\n\t\tlogAuditEvent(\"save\", `File saved: ${safeTitle}.html`);\n       }\n      function captureState() {\n\t\t const clone = typeof structuredClone === 'function' \n\t\t   ? (o) => structuredClone(o)\n\t\t   : (o) => JSON.parse(JSON.stringify(o));\n\t\t return {\n\t\t  nodes: clone(NODE_DATA),\n\t\t  edges: clone(EDGE_DATA),\n\t\t  positions: clone(savedPositions),\n\t\t  sizes: clone(savedSizes),\n\t\t  styles: clone(savedStyles),\n\t\t  legend: clone(EDGE_LEGEND),\n\t\t  rects: clone(RECT_DATA),\n\t\t  texts: clone(TEXT_DATA)\n\t\t };\n\t\t}\n      let lastUndoPush = 0;\n\t   function pushUndo(action = \"\") {\n\t   const now = Date.now();\n\t   if (now - lastUndoPush < 100 && undoStack.length > 0) {\n\t    return;\n\t   }\n\t   lastUndoPush = now;\n\t   const state = captureState();\n\t   undoStack.push(state);\n       if (undoStack.length > MAX_UNDO_STACK) {\n        undoStack.shift();\n       }\n       redoStack = [];\n       updateUndoButtons();\n       if (action) {\n        const actionTypeMap = {\n          \"create node\": \"node\",\n          \"delete node\": \"node\",\n          \"add node\": \"node\",\n          \"edit\": \"node\",\n          \"clone node\": \"node\",\n          \"paste node\": \"node\",\n          \"move nodes\": \"node\",\n          \"nudge\": \"node\",\n          \"nudge nodes\": \"node\",\n          \"align nodes\": \"node\",\n          \"distribute nodes\": \"node\",\n          \"snap to grid\": \"node\",\n          \"toggle group\": \"node\",\n          \"toggle lock\": \"node\",\n          \"create rack\": \"rack\",\n          \"add rack\": \"rack\",\n          \"edit rack\": \"rack\",\n          \"edit mac\": \"rack\",\n          \"edit U height\": \"rack\",\n          \"change rack capacity\": \"rack\",\n          \"change assigned rack\": \"rack\",\n          \"add connection\": \"connection\",\n          \"delete connection\": \"connection\",\n          \"delete edge\": \"connection\",\n          \"clone edge\": \"connection\",\n          \"paste edge\": \"connection\",\n          \"style change\": \"style\",\n          \"change layer\": \"layer\",\n          \"add text\": \"text\",\n          \"edit text\": \"text\",\n          \"delete text\": \"text\",\n          \"clone text\": \"text\",\n          \"paste text\": \"text\",\n          \"draw zone\": \"zone\",\n          \"delete zone\": \"zone\",\n          \"delete rect\": \"zone\",\n          \"clone rect\": \"zone\",\n          \"paste rect\": \"zone\",\n          \"change zone line style\": \"zone\",\n          \"delete selected\": \"bulk\",\n          \"clone selected\": \"bulk\",\n        };\n        const type = actionTypeMap[action] || \"edit\";\n        logAuditEvent(type, action);\n       }\n      }\n      function undo() {\n       if (undoStack.length === 0) return;\n       const currentState = captureState();\n       redoStack.push(currentState);\n       const previousState = undoStack.pop();\n       restoreState(previousState);\n       updateUndoButtons();\n       logAuditEvent(\"undo\", \"Undo action performed\");\n      }\n      function redo() {\n       if (redoStack.length === 0) return;\n       logAuditEvent(\"redo\", \"Redo action performed\");\n       const currentState = captureState();\n       undoStack.push(currentState);\n       const nextState = redoStack.pop();\n       restoreState(nextState);\n       updateUndoButtons();\n      }\n      function restoreState(state) {\n      NODE_DATA = state.nodes;\n       EDGE_DATA = state.edges;\n       savedPositions = state.positions;\n       savedSizes = state.sizes;\n       savedStyles = state.styles;\n       EDGE_LEGEND = state.legend;\n       RECT_DATA = state.rects || { list: [] };\n       TEXT_DATA = state.texts || { list: [] };\n       forgeTheTopology();\n       if (currentNodeId && NODE_DATA[currentNodeId]) {\n       claimTheImmortal(currentNodeId);\n       } else if (currentEdgeId) {\n       selectTheConnection(currentEdgeId);\n       }\n      }\n      function updateUndoButtons() {\n       const undoBtn = document.getElementById(\"undo-btn\");\n       const redoBtn = document.getElementById(\"redo-btn\");\n       if (undoBtn) {\n        undoBtn.disabled = undoStack.length === 0;\n        undoBtn.style.opacity = undoStack.length === 0 ? \"0.5\" : \"1\";\n       }\n       if (redoBtn) {\n        redoBtn.disabled = redoStack.length === 0;\n        redoBtn.style.opacity = redoStack.length === 0 ? \"0.5\" : \"1\";\n       }\n      }\n      function clearSelection() {\n       selectedNodes.clear();\n       selectedEdges.clear();\n       selectedRects.clear();\n       selectedTexts.clear();\n       updateAllSelections();\n      }\n      function updateAllSelections() {\n      updateNodeSelection();\n      clearSearchHighlight();\n      document.querySelectorAll(\".edge\").forEach(el => {\n      const edgeId = el.dataset.edgeId;\n      if (selectedEdges.has(edgeId)) {\n      el.style.filter = \"drop-shadow(0 0 8px #4fd1c5)\";\n      } else {\n      el.style.filter = \"\";\n      }\n      });\n      document.querySelectorAll(\".rect-group\").forEach(el => {\n      const rectId = el.dataset.rectId;\n      if (selectedRects.has(rectId)) {\n      el.style.filter = \"drop-shadow(0 0 8px #4fd1c5)\";\n      } else {\n      el.style.filter = \"\";\n      }\n      });\n      document.querySelectorAll(\".text-group\").forEach(el => {\n      const textId = el.dataset.textId;\n      if (selectedTexts.has(textId)) {\n      el.style.filter = \"drop-shadow(0 0 8px #4fd1c5)\";\n      } else {\n      el.style.filter = \"\";\n      }\n      });\n      const total = selectedNodes.size + selectedEdges.size + selectedRects.size + selectedTexts.size;\n      const bulkToolbar = document.getElementById(\"bulk-toolbar\");\n      const bulkToolbarMobile = document.getElementById(\"bulk-toolbar-mobile\");\n      const bulkCount = document.getElementById(\"bulk-count\");\n      const bulkCountMobile = document.getElementById(\"bulk-count-mobile\");\n      if (total > 0) {\n      if (bulkToolbar) bulkToolbar.style.display = \"flex\";\n      if (bulkToolbarMobile) bulkToolbarMobile.style.display = \"flex\";\n      if (bulkCount) bulkCount.textContent = total;\n      if (bulkCountMobile) bulkCountMobile.textContent = total;\n      } else {\n      if (bulkToolbar) bulkToolbar.style.display = \"none\";\n      if (bulkToolbarMobile) bulkToolbarMobile.style.display = \"none\";\n      }\n      }\n      function updateNodeSelection() {\n       if (isViewOnly()) {\n        selectedNodes.clear();\n        selectedRects.clear();\n        selectedTexts.clear();\n        const bulkToolbar = document.getElementById(\"bulk-toolbar\");\n        const bulkToolbarMobile = document.getElementById(\"bulk-toolbar-mobile\");\n        if (bulkToolbar) bulkToolbar.style.display = \"none\";\n        if (bulkToolbarMobile) bulkToolbarMobile.style.display = \"none\";\n        return;\n       }\n       document.querySelectorAll(\".node-group\").forEach(node => {\n        const nodeId = node.dataset.nodeId;\n        if (selectedNodes.has(nodeId)) {\n         node.classList.add(\"selected\");\n        } else {\n         node.classList.remove(\"selected\");\n        }\n       });\n       const bulkToolbar = document.getElementById(\"bulk-toolbar\");\n       const bulkToolbarMobile = document.getElementById(\"bulk-toolbar-mobile\");\n       const bulkCount = document.getElementById(\"bulk-count\");\n       const bulkCountMobile = document.getElementById(\"bulk-count-mobile\");\n       const bulkCountModal = document.getElementById(\"bulk-count-modal\");\n       if (selectedNodes.size > 0) {\n        if (bulkToolbar) bulkToolbar.style.display = \"flex\";\n        if (bulkToolbarMobile) bulkToolbarMobile.style.display = \"flex\";\n        if (bulkCount) bulkCount.textContent = selectedNodes.size;\n        if (bulkCountMobile) bulkCountMobile.textContent = selectedNodes.size;\n        if (bulkCountModal) bulkCountModal.textContent = selectedNodes.size;\n       } else {\n        if (bulkToolbar) bulkToolbar.style.display = \"none\";\n        if (bulkToolbarMobile) bulkToolbarMobile.style.display = \"none\";\n        const modal = document.getElementById(\"bulk-actions-modal\");\n        if (modal) modal.style.display = \"none\";\n       }\n      }\n      function deleteSelected() {\n       const total = selectedNodes.size + selectedEdges.size + selectedRects.size + selectedTexts.size;\n       if (total === 0) return;\n       let nodesInsideRacks = [];\n       selectedNodes.forEach(nodeId => {\n        if (NODE_DATA[nodeId]?.isRack) {\n         Object.entries(NODE_DATA).forEach(([id, n]) => {\n          if (n.assignedRack === nodeId) nodesInsideRacks.push(n.name || id);\n         });\n        }\n       });\n       let message = `Delete ${total} selected item(s)?`;\n       if (nodesInsideRacks.length > 0) {\n        message += `\\n\\nThis will also delete ${nodesInsideRacks.length} node(s) inside rack(s):\\n• ${nodesInsideRacks.join('\\n• ')}`;\n       }\n       challengeTheImmortal(message, () => {\n        pushUndo(\"delete selected\");\n        selectedNodes.forEach(nodeId => {\n         if (NODE_DATA[nodeId]?.isRack) {\n          Object.keys(NODE_DATA).forEach(id => {\n           if (NODE_DATA[id]?.assignedRack === nodeId) {\n            EDGE_DATA.list = EDGE_DATA.list.filter(e => e.from !== id && e.to !== id);\n            delete NODE_DATA[id];\n            delete savedPositions[id];\n            delete savedSizes[id];\n            delete savedStyles[id];\n           }\n          });\n         }\n         delete NODE_DATA[nodeId];\n         delete savedPositions[nodeId];\n         delete savedSizes[nodeId];\n         delete savedStyles[nodeId];\n         EDGE_DATA.list = EDGE_DATA.list.filter(e => e.from !== nodeId && e.to !== nodeId);\n        });\n        selectedEdges.forEach(edgeId => {\n         EDGE_DATA.list = EDGE_DATA.list.filter(e => e.id !== edgeId);\n        });\n        selectedRects.forEach(rectId => {\n         RECT_DATA.list = RECT_DATA.list.filter(r => r.id !== rectId);\n        });\n        selectedTexts.forEach(textId => {\n         TEXT_DATA.list = TEXT_DATA.list.filter(t => t.id !== textId);\n        });\n        clearSelection();\n        forgeTheTopology();\n       });\n      }\n      function startSelection(event) {\n       const svgEl = document.getElementById(\"map\");\n       const pt = svgEl.createSVGPoint();\n       pt.x = event.clientX;\n       pt.y = event.clientY;\n       const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n       isSelecting = true;\n       selectionStart = { x: svgP.x, y: svgP.y };\n       if (!selectionRect || !selectionRect.parentNode) {\n        selectionRect = document.createElementNS(\"http://www.w3.org/2000/svg\", \"rect\");\n        selectionRect.setAttribute(\"fill\", selectionBoxStyle.fillColor);\n        selectionRect.setAttribute(\"fill-opacity\", selectionBoxStyle.fillOpacity);\n        selectionRect.setAttribute(\"stroke\", selectionBoxStyle.strokeColor);\n        selectionRect.setAttribute(\"stroke-width\", selectionBoxStyle.strokeWidth);\n        selectionRect.setAttribute(\"stroke-dasharray\", selectionBoxStyle.strokeDasharray);\n        selectionRect.style.pointerEvents = \"none\";\n        svgEl.appendChild(selectionRect);\n       }\n       clearSelection();\n       preDragSelectedNodes = new Set(selectedNodes);\n       preDragSelectedEdges = new Set(selectedEdges);\n       preDragSelectedRects = new Set(selectedRects);\n       preDragSelectedTexts = new Set(selectedTexts);\n      }\n      function updateSelection(event) {\n       if (!isSelecting || !selectionStart) return;\n       const svgEl = document.getElementById(\"map\");\n       const pt = svgEl.createSVGPoint();\n       pt.x = event.clientX;\n       pt.y = event.clientY;\n       const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n       const x = Math.min(selectionStart.x, svgP.x);\n       const y = Math.min(selectionStart.y, svgP.y);\n       const width = Math.abs(svgP.x - selectionStart.x);\n       const height = Math.abs(svgP.y - selectionStart.y);\n       selectionRect.setAttribute(\"x\", x);\n       selectionRect.setAttribute(\"y\", y);\n       selectionRect.setAttribute(\"width\", width);\n       selectionRect.setAttribute(\"height\", height);\n       selectionRect.style.display = \"block\";\n       const box = { x, y, width, height };\n       Object.entries(savedPositions).forEach(([nodeId, pos]) => {\n        const size = savedSizes[nodeId] || 50;\n        const halfSize = size / 2;\n        const nodeBox = { x: pos.x - halfSize, y: pos.y - halfSize, width: size, height: size };\n        if (boxesIntersect(box, nodeBox)) {\n         selectedNodes.add(nodeId);\n        } else if (!preDragSelectedNodes.has(nodeId)) {\n         selectedNodes.delete(nodeId);\n        }\n       });\n       EDGE_DATA.list.forEach(edge => {\n        if (!edge.points || edge.points.length === 0) return;\n        for (let i = 0; i < edge.points.length - 1; i++) {\n         const p1 = edge.points[i];\n         const p2 = edge.points[i + 1];\n         if (lineIntersectsBox(p1.x, p1.y, p2.x, p2.y, box)) {\n          selectedEdges.add(edge.id);\n          return;\n         }\n        }\n        if (!preDragSelectedEdges.has(edge.id)) {\n         selectedEdges.delete(edge.id);\n        }\n       });\n       RECT_DATA.list.forEach(rect => {\n        const rectBox = { x: rect.x, y: rect.y, width: rect.width, height: rect.height };\n        if (boxesIntersect(box, rectBox)) {\n         selectedRects.add(rect.id);\n        } else if (!preDragSelectedRects.has(rect.id)) {\n         selectedRects.delete(rect.id);\n        }\n       });\n       TEXT_DATA.list.forEach(text => {\n        const fontSize = text.fontSize || 18;\n        const textBox = { x: text.x - 50, y: text.y - fontSize, width: 100, height: fontSize * 1.5 };\n        if (boxesIntersect(box, textBox)) {\n         selectedTexts.add(text.id);\n        } else if (!preDragSelectedTexts.has(text.id)) {\n         selectedTexts.delete(text.id);\n        }\n       });\n       updateAllSelectionVisuals();\n      }\n      function endSelection() {\n       isSelecting = false;\n       selectionStart = null;\n       if (selectionRect) {\n        selectionRect.style.display = \"none\";\n       }\n      }\n      function boxesIntersect(a, b) {\n       return !(a.x + a.width < b.x || b.x + b.width < a.x || a.y + a.height < b.y || b.y + b.height < a.y);\n      }\n      function lineIntersectsBox(x1, y1, x2, y2, box) {\n       if (pointInBox(x1, y1, box) || pointInBox(x2, y2, box)) return true;\n       const lines = [\n        [box.x, box.y, box.x + box.width, box.y],\n        [box.x + box.width, box.y, box.x + box.width, box.y + box.height],\n        [box.x + box.width, box.y + box.height, box.x, box.y + box.height],\n        [box.x, box.y + box.height, box.x, box.y]\n       ];\n       for (const [bx1, by1, bx2, by2] of lines) {\n        if (linesIntersect(x1, y1, x2, y2, bx1, by1, bx2, by2)) return true;\n       }\n       return false;\n      }\n      function pointInBox(x, y, box) {\n       return x >= box.x && x <= box.x + box.width && y >= box.y && y <= box.y + box.height;\n      }\n      function linesIntersect(x1, y1, x2, y2, x3, y3, x4, y4) {\n       const denom = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1);\n       if (Math.abs(denom) < 0.0001) return false;\n       const ua = ((x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)) / denom;\n       const ub = ((x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)) / denom;\n       return ua >= 0 && ua <= 1 && ub >= 0 && ub <= 1;\n      }\n      function updateAllSelectionVisuals() {\n       document.querySelectorAll(\".node-group\").forEach(node => {\n        const nodeId = node.dataset.nodeId;\n        if (selectedNodes.has(nodeId)) {\n         node.classList.add(\"selected\");\n        } else {\n         node.classList.remove(\"selected\");\n        }\n       });\n       document.querySelectorAll(\".edge\").forEach(edge => {\n        const edgeId = edge.dataset.edgeId;\n        if (selectedEdges.has(edgeId)) {\n         edge.style.filter = \"drop-shadow(0 0 6px #4fd1c5)\";\n        } else {\n         edge.style.filter = \"\";\n        }\n       });\n       document.querySelectorAll(\".rect-group rect\").forEach(el => {\n        const rectId = el.closest(\".rect-group\")?.dataset?.rectId;\n        if (selectedRects.has(rectId)) {\n         el.style.filter = \"drop-shadow(0 0 8px #4fd1c5)\";\n        } else {\n         el.style.filter = \"\";\n        }\n       });\n       document.querySelectorAll(\".text-group, .text-element\").forEach(el => {\n        const textId = el.dataset?.textId;\n        if (selectedTexts.has(textId)) {\n         el.style.filter = \"drop-shadow(0 0 8px #4fd1c5)\";\n        } else {\n         el.style.filter = \"\";\n        }\n       });\n       const total = selectedNodes.size + selectedEdges.size + selectedRects.size + selectedTexts.size;\n       const bulkToolbar = document.getElementById(\"bulk-toolbar\");\n       const bulkToolbarMobile = document.getElementById(\"bulk-toolbar-mobile\");\n       const bulkCount = document.getElementById(\"bulk-count\");\n       const bulkCountMobile = document.getElementById(\"bulk-count-mobile\");\n       if (total > 0) {\n        if (bulkToolbar) bulkToolbar.style.display = \"flex\";\n        if (bulkToolbarMobile) bulkToolbarMobile.style.display = \"flex\";\n        if (bulkCount) bulkCount.textContent = total;\n        if (bulkCountMobile) bulkCountMobile.textContent = total;\n       } else {\n        if (bulkToolbar) bulkToolbar.style.display = \"none\";\n        if (bulkToolbarMobile) bulkToolbarMobile.style.display = \"none\";\n       }\n      }\n      function cloneNode(sourceId, skipUndo) {\n       const source = NODE_DATA[sourceId];\n       if (!source) return;\n       if (!skipUndo) pushUndo(\"clone node\");\n       let baseName = source.name;\n       let copyNum = 0;\n       let newName = baseName + \" copy\";\n       while (Object.values(NODE_DATA).some(n => n.name === newName)) {\n        copyNum++;\n        newName = baseName + \" copy \" + copyNum;\n       }\n       const newId = newName.toLowerCase().replace(/[^a-z0-9]+/g, \"-\");\n       NODE_DATA[newId] = {\n        shape: source.shape,\n        name: newName,\n        ip: source.ip,\n        role: source.role,\n        tags: [...source.tags],\n        notes: [...source.notes],\n        mac: source.mac || \"\",\n        rackUnit: source.rackUnit || \"\",\n        uHeight: source.uHeight || \"1\",\n        ping: source.ping ? JSON.parse(JSON.stringify(source.ping)) : {\n         enabled: false,\n         protocol: 'http',\n         customUrl: '',\n         timeout: 3000,\n         status: 'unknown',\n         lastCheck: null\n        },\n        layer: source.layer || \"layer1\",\n        assignedRack: source.assignedRack || \"\",\n        hostedOn: source.hostedOn || \"\",\n        rackCapacity: source.rackCapacity || \"42\",\n        isRack: source.isRack || false,\n        fovEnabled: source.fovEnabled || false,\n        fovAngle: source.fovAngle || 90,\n        fovDistance: source.fovDistance || 150,\n        fovInnerRadius: source.fovInnerRadius || 0,\n        fovRotation: source.fovRotation || 0,\n        fovColor: source.fovColor || \"#f59e0b\",\n        fovOpacity: source.fovOpacity || 20,\n        fovGradient: source.fovGradient || false,\n        fovBorderColor: source.fovBorderColor || \"#f59e0b\",\n        fovBorderWidth: source.fovBorderWidth !== undefined ? source.fovBorderWidth : 2,\n        fovBorderStyle: source.fovBorderStyle || \"solid\",\n        fovBorderOpacity: source.fovBorderOpacity !== undefined ? source.fovBorderOpacity : 100,\n        fovLabel: source.fovLabel || \"\",\n        fovLabelPosition: source.fovLabelPosition || \"center\",\n        fovLabelSize: source.fovLabelSize || 14,\n        fovLabelColor: source.fovLabelColor || \"#ffffff\",\n        fovLabelBold: source.fovLabelBold || false,\n        fovLabelBg: source.fovLabelBg || false,\n        fovLabelBgColor: source.fovLabelBgColor || \"#000000\",\n        fovLabelOffsetX: source.fovLabelOffsetX || 0,\n        fovLabelOffsetY: source.fovLabelOffsetY || 0,\n        fovAnimate: source.fovAnimate || false,\n        fovAnimationType: source.fovAnimationType || \"sweep\",\n        fovSweep: source.fovSweep || 120,\n        fovSpeed: source.fovSpeed || 4\n       };\n       if (source.isRack) {\n        const childNodes = Object.entries(NODE_DATA).filter(([id, n]) =>\n         id !== newId && n.assignedRack === sourceId\n        );\n        childNodes.forEach(([childId, childNode]) => {\n         let childBaseName = childNode.name;\n         let c = 0;\n         let childNewName = childBaseName + \" copy\";\n         while (Object.values(NODE_DATA).some(n => n.name === childNewName)) {\n          c++;\n          childNewName = childBaseName + \" copy \" + c;\n         }\n         const childNewId = childNewName.toLowerCase().replace(/[^a-z0-9]+/g, \"-\");\n         NODE_DATA[childNewId] = {\n          ...JSON.parse(JSON.stringify(childNode)),\n          name: childNewName,\n          assignedRack: newId\n         };\n         if (savedPositions[childId]) {\n          savedPositions[childNewId] = { ...savedPositions[childId] };\n         }\n         if (savedSizes[childId]) {\n          savedSizes[childNewId] = savedSizes[childId];\n         }\n         if (savedStyles[childId]) {\n          savedStyles[childNewId] = JSON.parse(JSON.stringify(savedStyles[childId]));\n         }\n        });\n       }\n       if (savedSizes[sourceId]) {\n        savedSizes[newId] = savedSizes[sourceId];\n       }\n       if (savedStyles[sourceId]) {\n        savedStyles[newId] = JSON.parse(JSON.stringify(savedStyles[sourceId]));\n       }\n      if (currentView.mode === \"rack\" && currentView.rackId) {\n        NODE_DATA[newId].assignedRack = currentView.rackId;\n       }\n       const sourcePos = savedPositions[sourceId];\n       savedPositions[newId] = {\n        x: sourcePos.x + 100,\n        y: sourcePos.y + 100\n       };\n       forgeTheTopology();\n       claimTheImmortal(newId);\n      }\n      function alignSelectedNodes(direction) {\n       if (selectedNodes.size < 2) return;\n       pushUndo(\"align nodes\");\n       const nodeIds = Array.from(selectedNodes);\n       const positions = nodeIds.map(id => ({ id, pos: savedPositions[id] }));\n       switch(direction) {\n        case \"left\":\n         const minX = Math.min(...positions.map(p => p.pos.x));\n         positions.forEach(p => savedPositions[p.id].x = minX);\n         break;\n        case \"right\":\n         const maxX = Math.max(...positions.map(p => p.pos.x));\n         positions.forEach(p => savedPositions[p.id].x = maxX);\n         break;\n        case \"top\":\n         const minY = Math.min(...positions.map(p => p.pos.y));\n         positions.forEach(p => savedPositions[p.id].y = minY);\n         break;\n        case \"bottom\":\n         const maxY = Math.max(...positions.map(p => p.pos.y));\n         positions.forEach(p => savedPositions[p.id].y = maxY);\n         break;\n        case \"center-h\":\n         const avgX = positions.reduce((sum, p) => sum + p.pos.x, 0) / positions.length;\n         positions.forEach(p => savedPositions[p.id].x = avgX);\n         break;\n        case \"center-v\":\n         const avgY = positions.reduce((sum, p) => sum + p.pos.y, 0) / positions.length;\n         positions.forEach(p => savedPositions[p.id].y = avgY);\n         break;\n       }\n       forgeTheTopology();\n      }\n      function distributeSelectedNodes(direction) {\n       if (selectedNodes.size < 3) return;\n       pushUndo(\"distribute nodes\");\n       const nodeIds = Array.from(selectedNodes);\n       const positions = nodeIds.map(id => ({ id, pos: savedPositions[id] }));\n       if (direction === \"horizontal\") {\n        positions.sort((a, b) => a.pos.x - b.pos.x);\n        const minX = positions[0].pos.x;\n        const maxX = positions[positions.length - 1].pos.x;\n        const gap = (maxX - minX) / (positions.length - 1);\n        positions.forEach((p, i) => {\n         savedPositions[p.id].x = minX + (gap * i);\n        });\n       } else if (direction === \"vertical\") {\n        positions.sort((a, b) => a.pos.y - b.pos.y);\n        const minY = positions[0].pos.y;\n        const maxY = positions[positions.length - 1].pos.y;\n        const gap = (maxY - minY) / (positions.length - 1);\n        positions.forEach((p, i) => {\n         savedPositions[p.id].y = minY + (gap * i);\n        });\n       }\n       forgeTheTopology();\n      }\n      function snapToGrid(nodeId, gridSize = 50) {\n       if (!savedPositions[nodeId]) return;\n       pushUndo(\"snap to grid\");\n       const pos = savedPositions[nodeId];\n       pos.x = Math.round(pos.x / gridSize) * gridSize;\n       pos.y = Math.round(pos.y / gridSize) * gridSize;\n       forgeTheTopology();\n      }\n      function nudgeSelectedNodes(direction, distance) {\n        const nodesToNudge = selectedNodes.size > 0\n          ? Array.from(selectedNodes)\n          : (currentNodeId ? [currentNodeId] : []);\n        if (nodesToNudge.length === 0) return;\n        const unlockedNodes = nodesToNudge.filter(id => !NODE_DATA[id]?.locked);\n        if (unlockedNodes.length === 0) {\n          return;\n        }\n        pushUndo(\"nudge nodes\");\n        unlockedNodes.forEach(id => {\n          if (!savedPositions[id]) {\n            savedPositions[id] = { x: 0, y: 0 };\n          }\n          switch(direction) {\n            case \"ArrowUp\":\n              savedPositions[id].y -= distance;\n              break;\n            case \"ArrowDown\":\n              savedPositions[id].y += distance;\n              break;\n            case \"ArrowLeft\":\n              savedPositions[id].x -= distance;\n              break;\n            case \"ArrowRight\":\n              savedPositions[id].x += distance;\n              break;\n          }\n        });\n        forgeTheTopology();\n      }\n      function cycleNodes(reverse = false) {\n        const nodeIds = Object.keys(NODE_DATA).filter(id => {\n          if (currentView.mode === \"rack\") {\n           const node = NODE_DATA[id];\n           return node && node.assignedRack === currentView.rackId;\n          }\n          return isNodeVisible(id);\n         });\n        if (nodeIds.length === 0) return;\n        let currentIndex = nodeIds.indexOf(currentNodeId);\n        if (reverse) {\n          currentIndex = currentIndex <= 0 ? nodeIds.length - 1 : currentIndex - 1;\n        } else {\n          currentIndex = currentIndex >= nodeIds.length - 1 ? 0 : currentIndex + 1;\n        }\n        const nextNodeId = nodeIds[currentIndex];\n        claimTheImmortal(nextNodeId);\n        selectedNodes.clear();\n        updateNodeSelection();\n      }\n      function focusOnSelected() {\n        let minX = Infinity, minY = Infinity;\n        let maxX = -Infinity, maxY = -Infinity;\n        let hasItems = false;\n        const nodesToFocus = selectedNodes.size > 0\n          ? Array.from(selectedNodes)\n          : (currentNodeId ? [currentNodeId] : []);\n        nodesToFocus.forEach(id => {\n          const pos = savedPositions[id];\n          if (pos) {\n            hasItems = true;\n            const size = savedSizes[id] || 50;\n            minX = Math.min(minX, pos.x - size/2);\n            minY = Math.min(minY, pos.y - size/2);\n            maxX = Math.max(maxX, pos.x + size/2);\n            maxY = Math.max(maxY, pos.y + size/2);\n          }\n        });\n        const rectsToFocus = selectedRects.size > 0 ? Array.from(selectedRects) : (currentRectId ? [currentRectId] : []);\n        rectsToFocus.forEach(id => {\n          const r = RECT_DATA.list.find(x => x.id === id);\n          if (r) {\n            hasItems = true;\n            minX = Math.min(minX, r.x);\n            minY = Math.min(minY, r.y);\n            maxX = Math.max(maxX, r.x + r.width);\n            maxY = Math.max(maxY, r.y + r.height);\n          }\n        });\n        const textsToFocus = selectedTexts.size > 0 ? Array.from(selectedTexts) : (currentTextId ? [currentTextId] : []);\n        textsToFocus.forEach(id => {\n          const t = TEXT_DATA.list.find(x => x.id === id);\n          if (t) {\n            hasItems = true;\n            minX = Math.min(minX, t.x - 50);\n            minY = Math.min(minY, t.y - 20);\n            maxX = Math.max(maxX, t.x + 50);\n            maxY = Math.max(maxY, t.y + 20);\n          }\n        });\n        const edgesToFocus = selectedEdges.size > 0 ? Array.from(selectedEdges) : (currentEdgeId ? [currentEdgeId] : []);\n        edgesToFocus.forEach(id => {\n          const e = EDGE_DATA.list.find(x => x.id === id);\n          if (e) {\n            const fromPos = savedPositions[e.from];\n            const toPos = savedPositions[e.to];\n            if (fromPos && toPos) {\n              hasItems = true;\n              minX = Math.min(minX, fromPos.x, toPos.x);\n              minY = Math.min(minY, fromPos.y, toPos.y);\n              maxX = Math.max(maxX, fromPos.x, toPos.x);\n              maxY = Math.max(maxY, fromPos.y, toPos.y);\n            }\n          }\n        });\n        if (!hasItems || !isFinite(minX)) return;\n        const padding = 100;\n        const centerX = (minX + maxX) / 2;\n        const centerY = (minY + maxY) / 2;\n        const width = maxX - minX + padding * 2;\n        const height = maxY - minY + padding * 2;\n        const zoomX = CANVAS_WIDTH / width;\n        const zoomY = CANVAS_HEIGHT / height;\n        const targetZoom = Math.min(zoomX, zoomY, 2);\n        canvasState.zoom = targetZoom;\n        canvasState.panX = centerX - (CANVAS_WIDTH / targetZoom) / 2;\n        canvasState.panY = centerY - (CANVAS_HEIGHT / targetZoom) / 2;\n        updateViewBox();\n      }\n      function toggleLockSelected() {\n        const nodesToToggle = selectedNodes.size > 0\n          ? Array.from(selectedNodes)\n          : (currentNodeId ? [currentNodeId] : []);\n        const rectsToToggle = selectedRects.size > 0\n          ? Array.from(selectedRects)\n          : (currentRectId ? [currentRectId] : []);\n        const textsToToggle = selectedTexts.size > 0\n          ? Array.from(selectedTexts)\n          : (currentTextId ? [currentTextId] : []);\n        if (nodesToToggle.length === 0 && rectsToToggle.length === 0 && textsToToggle.length === 0) return;\n        pushUndo(\"toggle lock\");\n        let hasUnlocked = nodesToToggle.some(id => !NODE_DATA[id]?.locked);\n        hasUnlocked = hasUnlocked || rectsToToggle.some(id => { const r = RECT_DATA.list.find(x => x.id === id); return r && !r.locked; });\n        hasUnlocked = hasUnlocked || textsToToggle.some(id => { const t = TEXT_DATA.list.find(x => x.id === id); return t && !t.locked; });\n        nodesToToggle.forEach(id => {\n          if (NODE_DATA[id]) NODE_DATA[id].locked = hasUnlocked;\n        });\n        rectsToToggle.forEach(id => {\n          const r = RECT_DATA.list.find(x => x.id === id);\n          if (r) r.locked = hasUnlocked;\n        });\n        textsToToggle.forEach(id => {\n          const t = TEXT_DATA.list.find(x => x.id === id);\n          if (t) t.locked = hasUnlocked;\n        });\n        forgeTheTopology();\n      }\n      function toggleGroupSelected() {\n      const nodesToGroup = Array.from(selectedNodes);\n      const rectsToGroup = Array.from(selectedRects);\n      const textsToGroup = Array.from(selectedTexts);\n      const edgesToGroup = Array.from(selectedEdges);\n      const totalItems = nodesToGroup.length + rectsToGroup.length + textsToGroup.length + edgesToGroup.length;\n      if (totalItems < 2) return;\n      pushUndo(\"toggle group\");\n      const allGroupIds = [];\n      nodesToGroup.forEach(id => { if (NODE_DATA[id]?.groupId) allGroupIds.push(NODE_DATA[id].groupId); });\n      rectsToGroup.forEach(id => { const r = RECT_DATA.list.find(x => x.id === id); if (r?.groupId) allGroupIds.push(r.groupId); });\n      textsToGroup.forEach(id => { const t = TEXT_DATA.list.find(x => x.id === id); if (t?.groupId) allGroupIds.push(t.groupId); });\n      edgesToGroup.forEach(id => { const e = EDGE_DATA.list.find(x => x.id === id); if (e?.groupId) allGroupIds.push(e.groupId); });\n      const uniqueGroups = [...new Set(allGroupIds)];\n      if (uniqueGroups.length === 1 && allGroupIds.length === totalItems) {\n      nodesToGroup.forEach(id => { if (NODE_DATA[id]) NODE_DATA[id].groupId = null; });\n      rectsToGroup.forEach(id => { const r = RECT_DATA.list.find(x => x.id === id); if (r) r.groupId = null; });\n      textsToGroup.forEach(id => { const t = TEXT_DATA.list.find(x => x.id === id); if (t) t.groupId = null; });\n      edgesToGroup.forEach(id => { const e = EDGE_DATA.list.find(x => x.id === id); if (e) e.groupId = null; });\n      } else {\n      const newGroupId = \"group-\" + Date.now();\n      nodesToGroup.forEach(id => { if (NODE_DATA[id]) NODE_DATA[id].groupId = newGroupId; });\n      rectsToGroup.forEach(id => { const r = RECT_DATA.list.find(x => x.id === id); if (r) r.groupId = newGroupId; });\n      textsToGroup.forEach(id => { const t = TEXT_DATA.list.find(x => x.id === id); if (t) t.groupId = newGroupId; });\n      edgesToGroup.forEach(id => { const e = EDGE_DATA.list.find(x => x.id === id); if (e) e.groupId = newGroupId; });\n      }\n      forgeTheTopology();\n      }\n      function handleKeyDown(event) {\n       if (event.metaKey && !event.ctrlKey) event.ctrlKey = true;\n       if (event.target.tagName === \"INPUT\" || event.target.tagName === \"TEXTAREA\" || event.target.isContentEditable) {\n        return;\n       }\n       if ([\"ArrowUp\", \"ArrowDown\", \"ArrowLeft\", \"ArrowRight\"].includes(event.key)) {\n        event.preventDefault();\n        const distance = event.shiftKey ? 10 : 1;\n        nudgeSelectedNodes(event.key, distance);\n       }\n       if (event.key === \"Tab\") {\n        event.preventDefault();\n        cycleNodes(event.shiftKey);\n       }\n       if (event.key === \"f\" || event.key === \"F\") {\n        event.preventDefault();\n        focusOnSelected();\n       }\n       if (event.key === \"l\" || event.key === \"L\") {\n        event.preventDefault();\n        toggleLockSelected();\n       }\n       if (event.key === \"g\" || event.key === \"G\") {\n        event.preventDefault();\n        toggleGroupSelected();\n       }\n       if (event.ctrlKey && event.key === \"z\" && !event.shiftKey) {\n        event.preventDefault();\n        undo();\n       }\n       if ((event.ctrlKey && event.key === \"y\") || (event.ctrlKey && event.shiftKey && event.key === \"z\")) {\n        event.preventDefault();\n        redo();\n       }\n       if (event.ctrlKey && event.key === \"c\") {\n        event.preventDefault();\n        clipboard = null;\n        if (currentNodeId && NODE_DATA[currentNodeId]) {\n         clipboard = {\n          type: \"node\",\n          data: JSON.parse(JSON.stringify(NODE_DATA[currentNodeId])),\n          size: savedSizes[currentNodeId],\n          style: savedStyles[currentNodeId] ? JSON.parse(JSON.stringify(savedStyles[currentNodeId])) : null\n         };\n        } else if (currentEdgeId) {\n         const edge = EDGE_DATA.list.find(e => e.id === currentEdgeId);\n         if (edge) clipboard = { type: \"edge\", data: JSON.parse(JSON.stringify(edge)) };\n        } else if (currentRectId) {\n         const rect = RECT_DATA.list.find(r => r.id === currentRectId);\n         if (rect) clipboard = { type: \"rect\", data: JSON.parse(JSON.stringify(rect)) };\n        } else if (currentTextId) {\n         const text = TEXT_DATA.list.find(t => t.id === currentTextId);\n         if (text) clipboard = { type: \"text\", data: JSON.parse(JSON.stringify(text)) };\n        }\n       }\n       if (event.ctrlKey && event.key === \"v\") {\n        event.preventDefault();\n        if (!clipboard) return;\n        const svgEl = document.getElementById(\"map\");\n        const rect = svgEl.getBoundingClientRect();\n        const pt = svgEl.createSVGPoint();\n        pt.x = rect.left + rect.width / 2;\n        pt.y = rect.top + rect.height / 2;\n        const svgP = pt.matrixTransform(svgEl.getScreenCTM().inverse());\n        const centerX = svgP.x;\n        const centerY = svgP.y;\n        if (clipboard.type === \"node\") {\n         let newName = clipboard.data.name + \" copy\";\n         let counter = 1;\n         while (Object.values(NODE_DATA).some(n => n.name === newName)) {\n          newName = clipboard.data.name + \" copy \" + counter;\n          counter++;\n         }\n         let newId = newName.toLowerCase().replace(/[^a-z0-9]+/g, \"-\");\n         counter = 1;\n         while (NODE_DATA[newId]) {\n          newId = newName.toLowerCase().replace(/[^a-z0-9]+/g, \"-\") + \"-\" + counter;\n          counter++;\n         }\n         pushUndo(\"paste node\");\n         NODE_DATA[newId] = { ...JSON.parse(JSON.stringify(clipboard.data)), name: newName };\n         savedPositions[newId] = { x: centerX, y: centerY };\n         if (clipboard.size) savedSizes[newId] = clipboard.size;\n         if (clipboard.style) savedStyles[newId] = JSON.parse(JSON.stringify(clipboard.style));\n         forgeTheTopology();\n         claimTheImmortal(newId);\n        } else if (clipboard.type === \"edge\") {\n         pushUndo(\"paste edge\");\n         const newEdge = { ...clipboard.data, id: \"edge-\" + Date.now() };\n         EDGE_DATA.list.push(newEdge);\n         forgeTheTopology();\n         selectTheConnection(newEdge.id);\n        } else if (clipboard.type === \"rect\") {\n         pushUndo(\"paste rect\");\n         const newRect = { ...clipboard.data, id: \"rect-\" + Date.now(), x: centerX - (clipboard.data.width || 100) / 2, y: centerY - (clipboard.data.height || 100) / 2 };\n         RECT_DATA.list.push(newRect);\n         forgeTheTopology();\n         selectTheRect(newRect.id);\n        } else if (clipboard.type === \"text\") {\n         pushUndo(\"paste text\");\n         const newText = { ...clipboard.data, id: \"text-\" + Date.now(), x: centerX, y: centerY };\n         TEXT_DATA.list.push(newText);\n         forgeTheTopology();\n         showTextPanel(newText.id);\n        }\n       }\n       if (event.ctrlKey && event.key === \"d\") {\n        event.preventDefault();\n        if (currentNodeId && NODE_DATA[currentNodeId]) {\n         cloneNode(currentNodeId);\n        } else if (currentEdgeId) {\n         const edge = EDGE_DATA.list.find(e => e.id === currentEdgeId);\n         if (edge) {\n          pushUndo(\"clone edge\");\n          const newEdge = { ...JSON.parse(JSON.stringify(edge)), id: \"edge-\" + Date.now() };\n          EDGE_DATA.list.push(newEdge);\n          forgeTheTopology();\n          selectTheConnection(newEdge.id);\n         }\n        } else if (currentRectId) {\n         const rect = RECT_DATA.list.find(r => r.id === currentRectId);\n         if (rect) {\n          pushUndo(\"clone rect\");\n          const newRect = { ...JSON.parse(JSON.stringify(rect)), id: \"rect-\" + Date.now(), x: rect.x + 50, y: rect.y + 50 };\n          RECT_DATA.list.push(newRect);\n          forgeTheTopology();\n          selectTheRect(newRect.id);\n         }\n        } else if (currentTextId) {\n         const text = TEXT_DATA.list.find(t => t.id === currentTextId);\n         if (text) {\n          pushUndo(\"clone text\");\n          const newText = { ...JSON.parse(JSON.stringify(text)), id: \"text-\" + Date.now(), x: text.x + 50, y: text.y + 50 };\n          TEXT_DATA.list.push(newText);\n          forgeTheTopology();\n          showTextPanel(newText.id);\n         }\n        }\n       }\n       if (event.key === \"Delete\") {\n        event.preventDefault();\n        const totalSelected = selectedNodes.size + selectedEdges.size + selectedRects.size + selectedTexts.size;\n        if (totalSelected > 0) {\n         deleteSelected();\n        } else if (currentNodeId && NODE_DATA[currentNodeId]) {\n         challengeTheImmortal(`Delete node \"${NODE_DATA[currentNodeId].name}\"?`, () => {\n          pushUndo(\"delete node\");\n          delete NODE_DATA[currentNodeId];\n          delete savedPositions[currentNodeId];\n          delete savedSizes[currentNodeId];\n          delete savedStyles[currentNodeId];\n          EDGE_DATA.list = EDGE_DATA.list.filter(e => e.from !== currentNodeId && e.to !== currentNodeId);\n          currentNodeId = null;\n          forgeTheTopology();\n          document.getElementById(\"node-panel\").style.display = \"none\";\n         });\n        } else if (currentEdgeId) {\n         challengeTheImmortal(\"Delete this line?\", () => {\n          pushUndo(\"delete edge\");\n          EDGE_DATA.list = EDGE_DATA.list.filter(e => e.id !== currentEdgeId);\n          currentEdgeId = null;\n          forgeTheTopology();\n          document.getElementById(\"edge-panel\").style.display = \"none\";\n         });\n        } else if (currentRectId) {\n         challengeTheImmortal(\"Delete this zone?\", () => {\n          pushUndo(\"delete rect\");\n          RECT_DATA.list = RECT_DATA.list.filter(r => r.id !== currentRectId);\n          currentRectId = null;\n          forgeTheTopology();\n          document.getElementById(\"rect-panel\").style.display = \"none\";\n         });\n        } else if (currentTextId) {\n         const textItem = TEXT_DATA.list.find(t => t.id === currentTextId);\n         if (textItem) {\n          challengeTheImmortal(`Delete text \"${(textItem.content || '').substring(0, 30)}...\"?`, () => {\n           pushUndo(\"delete text\");\n           TEXT_DATA.list = TEXT_DATA.list.filter(t => t.id !== currentTextId);\n           currentTextId = null;\n           forgeTheTopology();\n           document.getElementById(\"text-panel\").style.display = \"none\";\n          });\n         }\n        }\n       }\n       if (event.ctrlKey && event.key === \"a\") {\n        event.preventDefault();\n        Object.keys(NODE_DATA).forEach(id => selectedNodes.add(id));\n        EDGE_DATA.list.forEach(e => selectedEdges.add(e.id));\n        RECT_DATA.list.forEach(r => selectedRects.add(r.id));\n        TEXT_DATA.list.forEach(t => selectedTexts.add(t.id));\n        updateAllSelections();\n       }\n       if (event.key === \"Escape\") {\n        clearSelection();\n       }\n       if (event.key === \"r\" && !event.ctrlKey && !event.shiftKey) {\n        event.preventDefault();\n        if (RECORDING_STATE.isRecording) {\n          stopRecording();\n        } else if (!RECORDING_STATE.isStepRecording) {\n          startRecording();\n        }\n       }\n       if (event.key === \"R\" && event.shiftKey && !event.ctrlKey) {\n        event.preventDefault();\n        if (RECORDING_STATE.isStepRecording) {\n          stopStepRecording();\n        } else if (!RECORDING_STATE.isRecording) {\n          startStepRecording();\n        }\n       }\n       if (event.key === \" \" && !event.ctrlKey) {\n        event.preventDefault();\n        if (RECORDING_STATE.isStepRecording) {\n          captureStepFrame();\n        } else if (RECORDING_STATE.isPlaying) {\n          pauseRecording();\n        } else if (RECORDING_STATE.isPaused) {\n          playRecording();\n        }\n       }\n       if ((event.key === \"p\" || event.key === \"P\") && !event.ctrlKey) {\n        event.preventDefault();\n        if (!RECORDING_STATE.isRecording && !RECORDING_STATE.isStepRecording) {\n          playRecording();\n        }\n       }\n      }\n\t\tfunction searchNodes(query) {\n\t\t   if (!query) {\n\t\t\tcurrentSearchQuery = \"\";\n\t\t\tcurrentSearchResults = [];\n\t\t\tclearSearchHighlight();\n\t\t\treturn [];\n\t\t   }\n\t\t   currentSearchQuery = query;\n\t\t   query = query.toLowerCase();\n\t\t   const results = [];\n\t\t   Object.entries(NODE_DATA).forEach(([id, data]) => {\n\t\t\ttry {\n\t\t\t const nameMatch = data.name && data.name.toLowerCase().includes(query);\n\t\t\t const ipMatch = data.ip && data.ip.toLowerCase().includes(query);\n\t\t\t const roleMatch = data.role && data.role.toLowerCase().includes(query);\n\t\t\t const tagsMatch = data.tags && Array.isArray(data.tags) && data.tags.some(tag => tag && tag.toLowerCase().includes(query));\n\t\t\t const macMatch = data.mac && data.mac.toLowerCase().includes(query);\n\t\t\t const rackUnitMatch = data.rackUnit && String(data.rackUnit).toLowerCase().includes(query);\n\t\t\t if (nameMatch || ipMatch || roleMatch || tagsMatch || macMatch || rackUnitMatch) {\n\t\t\t  results.push(id);\n\t\t\t }\n\t\t\t} catch (e) {\n\t\t\t console.warn(\"Search error for node:\", id, e);\n\t\t\t}\n\t\t   });\n\t\t   currentSearchResults = results;\n\t\t   highlightSearchResults(results, true);\n\t\t   if (results.length > 0) {\n\t\t\tfocusOnNodes(results);\n\t\t   }\n\t\t   return results;\n\t\t}\n\t\tfunction highlightSearchResults(nodeIds, hasQuery = true) {\n\t\t   document.querySelectorAll(\".node-group\").forEach(node => {\n\t\t\tconst nodeId = node.dataset.nodeId;\n\t\t\tif (nodeIds.includes(nodeId)) {\n\t\t\t node.classList.add(\"search-highlight\");\n\t\t\t node.classList.remove(\"search-faded\");\n\t\t\t} else {\n\t\t\t node.classList.remove(\"search-highlight\");\n\t\t\t if (hasQuery) {\n\t\t\t  node.classList.add(\"search-faded\");\n\t\t\t } else {\n\t\t\t  node.classList.remove(\"search-faded\");\n\t\t\t }\n\t\t\t}\n\t\t   });\n\t\t   document.querySelectorAll(\".edge-group, .edge\").forEach(edge => {\n\t\t\tif (hasQuery) {\n\t\t\t edge.classList.add(\"search-faded\");\n\t\t\t} else {\n\t\t\t edge.classList.remove(\"search-faded\");\n\t\t\t}\n\t\t   });\n\t\t}\n\t\tfunction clearSearchHighlight() {\n\t\t   document.querySelectorAll(\".search-highlight\").forEach(node => {\n\t\t\tnode.classList.remove(\"search-highlight\");\n\t\t   });\n\t\t   document.querySelectorAll(\".search-faded\").forEach(el => {\n\t\t\tel.classList.remove(\"search-faded\");\n\t\t   });\n\t\t}\n      function editNodeMac(id) {\n       const node = NODE_DATA[id];\n       if (!node) return;\n       document.getElementById(\"modal-title\").textContent = t(\"editModal.editMacAddress\");\n       document.getElementById(\"modal-input\").value = node.mac || \"\";\n       document.getElementById(\"edit-modal\").style.display = \"\";\n\t   document.getElementById(\"edit-modal\").classList.add(\"active\");\n       document.getElementById(\"modal-input\").focus();\n       const modal = document.getElementById(\"edit-modal\");\n       const cleanup = () => {\n        modal.classList.remove(\"active\");\n        document.getElementById(\"modal-save\").removeEventListener(\"click\", saveHandler);\n        document.getElementById(\"modal-cancel\").removeEventListener(\"click\", cancelHandler);\n        modal.removeEventListener(\"click\", bgHandler);\n       };\n       const saveHandler = () => {\n        pushUndo(\"edit mac\");\n        const value = document.getElementById(\"modal-input\").value.trim();\n        node.mac = value;\n        cleanup();\n        claimTheImmortal(id);\n       };\n       const cancelHandler = () => {\n        cleanup();\n       };\n       const bgHandler = (e) => {\n        if (e.target === modal) cancelHandler();\n       };\n       document.getElementById(\"modal-save\").addEventListener(\"click\", saveHandler);\n       document.getElementById(\"modal-cancel\").addEventListener(\"click\", cancelHandler);\n       modal.addEventListener(\"click\", bgHandler);\n      }\n      function editNodeRack(id) {\n       const node = NODE_DATA[id];\n       if (!node) return;\n       document.getElementById(\"modal-title\").textContent = t(\"editModal.editRackUnit\");\n       document.getElementById(\"modal-input\").value = node.rackUnit || \"\";\n       document.getElementById(\"edit-modal\").style.display = \"\";\n       document.getElementById(\"edit-modal\").classList.add(\"active\");\n       document.getElementById(\"modal-input\").focus();\n\t   const modal = document.getElementById(\"edit-modal\");\n       const cleanup = () => {\n        modal.classList.remove(\"active\");\n        document.getElementById(\"modal-save\").removeEventListener(\"click\", saveHandler);\n        document.getElementById(\"modal-cancel\").removeEventListener(\"click\", cancelHandler);\n        modal.removeEventListener(\"click\", bgHandler);\n        document.removeEventListener(\"keydown\", escHandler);\n       };\n       const escHandler = (e) => {\n        if (e.key === \"Escape\") cleanup();\n       };\n       document.addEventListener(\"keydown\", escHandler);\n       const saveHandler = () => {\n        pushUndo(\"edit rack\");\n        const value = document.getElementById(\"modal-input\").value.trim();\n        node.rackUnit = value;\n        cleanup();\n        claimTheImmortal(id);\n       };\n       const cancelHandler = () => {\n        cleanup();\n       };\n       const bgHandler = (e) => {\n        if (e.target === modal) cancelHandler();\n       };\n       document.getElementById(\"modal-save\").addEventListener(\"click\", saveHandler);\n       document.getElementById(\"modal-cancel\").addEventListener(\"click\", cancelHandler);\n       modal.addEventListener(\"click\", bgHandler);\n      }\n      function editNodeUHeight(id) {\n       const node = NODE_DATA[id];\n       if (!node) return;\n       document.getElementById(\"modal-title\").textContent = t(\"editModal.editUHeight\");\n       document.getElementById(\"modal-input\").value = node.uHeight || \"1\";\n       document.getElementById(\"edit-modal\").style.display = \"\";\n       document.getElementById(\"edit-modal\").classList.add(\"active\");\n       document.getElementById(\"modal-input\").focus();\n\t   const modal = document.getElementById(\"edit-modal\");\n       const cleanup = () => {\n        modal.classList.remove(\"active\");\n        document.getElementById(\"modal-save\").removeEventListener(\"click\", saveHandler);\n        document.getElementById(\"modal-cancel\").removeEventListener(\"click\", cancelHandler);\n        modal.removeEventListener(\"click\", bgHandler);\n        document.removeEventListener(\"keydown\", escHandler);\n       };\n       const escHandler = (e) => {\n        if (e.key === \"Escape\") cleanup();\n       };\n       document.addEventListener(\"keydown\", escHandler);\n       const saveHandler = () => {\n        pushUndo(\"edit U height\");\n        const value = document.getElementById(\"modal-input\").value.trim();\n        node.uHeight = value;\n        cleanup();\n        claimTheImmortal(id);\n        forgeTheTopology();\n       };\n       const cancelHandler = () => {\n        cleanup();\n       };\n       const bgHandler = (e) => {\n        if (e.target === modal) cancelHandler();\n       };\n       document.getElementById(\"modal-save\").addEventListener(\"click\", saveHandler);\n       document.getElementById(\"modal-cancel\").addEventListener(\"click\", cancelHandler);\n       modal.addEventListener(\"click\", bgHandler);\n      }\n      function updateEdgePortLabels(edgeId) {\n       const edge = EDGE_DATA.list.find(e => e.id === edgeId);\n       if (!edge) return;\n       const fromPortInput = document.getElementById(\"edge-from-port\");\n       const toPortInput = document.getElementById(\"edge-to-port\");\n       edge.fromPort = fromPortInput ? fromPortInput.value.trim() : \"\";\n       edge.toPort = toPortInput ? toPortInput.value.trim() : \"\";\n       forgeTheTopology();\n      }\n      document.addEventListener(\"keydown\", handleKeyDown);\n      const undoBtn = document.getElementById(\"undo-btn\");\n      const redoBtn = document.getElementById(\"redo-btn\");\n      if (undoBtn) undoBtn.addEventListener(\"click\", undo);\n      if (redoBtn) redoBtn.addEventListener(\"click\", redo);\n      const backToTopologyBtn = document.getElementById(\"back-to-topology-btn\");\n      if (backToTopologyBtn) {\n       backToTopologyBtn.addEventListener(\"click\", exitRack);\n      }\n      const bulkToolbarClose = document.getElementById(\"bulk-toolbar-close\");\n      if (bulkToolbarClose) {\n       bulkToolbarClose.addEventListener(\"click\", clearSelection);\n      }\n      const bulkAlignLeft = document.getElementById(\"bulk-align-left\");\n      if (bulkAlignLeft) {\n       bulkAlignLeft.addEventListener(\"click\", () => alignSelectedNodes(\"left\"));\n      }\n      const bulkAlignRight = document.getElementById(\"bulk-align-right\");\n      if (bulkAlignRight) {\n       bulkAlignRight.addEventListener(\"click\", () => alignSelectedNodes(\"right\"));\n      }\n      const bulkAlignTop = document.getElementById(\"bulk-align-top\");\n      if (bulkAlignTop) {\n       bulkAlignTop.addEventListener(\"click\", () => alignSelectedNodes(\"top\"));\n      }\n      const bulkAlignBottom = document.getElementById(\"bulk-align-bottom\");\n      if (bulkAlignBottom) {\n       bulkAlignBottom.addEventListener(\"click\", () => alignSelectedNodes(\"bottom\"));\n      }\n      const bulkDistributeH = document.getElementById(\"bulk-distribute-h\");\n      if (bulkDistributeH) {\n       bulkDistributeH.addEventListener(\"click\", () => distributeSelectedNodes(\"horizontal\"));\n      }\n      const bulkDistributeV = document.getElementById(\"bulk-distribute-v\");\n      if (bulkDistributeV) {\n       bulkDistributeV.addEventListener(\"click\", () => distributeSelectedNodes(\"vertical\"));\n      }\n      const bulkClone = document.getElementById(\"bulk-clone\");\n      if (bulkClone) {\n       bulkClone.addEventListener(\"click\", () => {\n        if (selectedNodes.size === 0) return;\n        pushUndo(\"clone selected\");\n        const nodesToClone = Array.from(selectedNodes);\n        selectedNodes.clear();\n       nodesToClone.forEach(id => {\n         cloneNode(id, true);\n         selectedNodes.add(currentNodeId);\n        });\n        updateNodeSelection();\n       });\n      }\n      const bulkDelete = document.getElementById(\"bulk-delete\");\n      if (bulkDelete) {\n       bulkDelete.addEventListener(\"click\", deleteSelected);\n      }\n      const bulkMobileBtn = document.getElementById(\"bulk-mobile-btn\");\n      const bulkActionsModal = document.getElementById(\"bulk-actions-modal\");\n      const bulkModalClose = document.getElementById(\"bulk-modal-close\");\n      if (bulkMobileBtn) {\n       bulkMobileBtn.addEventListener(\"click\", () => {\n        if (bulkActionsModal) bulkActionsModal.style.display = \"block\";\n       });\n      }\n      if (bulkModalClose) {\n       bulkModalClose.addEventListener(\"click\", () => {\n        if (bulkActionsModal) bulkActionsModal.style.display = \"none\";\n       });\n      }\n      const closeModalAfterAction = () => {\n       if (bulkActionsModal) bulkActionsModal.style.display = \"none\";\n      };\n      const bulkAlignLeftMobile = document.getElementById(\"bulk-align-left-mobile\");\n      if (bulkAlignLeftMobile) {\n       bulkAlignLeftMobile.addEventListener(\"click\", () => {\n        alignSelectedNodes(\"left\");\n        closeModalAfterAction();\n       });\n      }\n      const bulkAlignRightMobile = document.getElementById(\"bulk-align-right-mobile\");\n      if (bulkAlignRightMobile) {\n       bulkAlignRightMobile.addEventListener(\"click\", () => {\n        alignSelectedNodes(\"right\");\n        closeModalAfterAction();\n       });\n      }\n      const bulkAlignTopMobile = document.getElementById(\"bulk-align-top-mobile\");\n      if (bulkAlignTopMobile) {\n       bulkAlignTopMobile.addEventListener(\"click\", () => {\n        alignSelectedNodes(\"top\");\n        closeModalAfterAction();\n       });\n      }\n      const bulkAlignBottomMobile = document.getElementById(\"bulk-align-bottom-mobile\");\n      if (bulkAlignBottomMobile) {\n       bulkAlignBottomMobile.addEventListener(\"click\", () => {\n        alignSelectedNodes(\"bottom\");\n        closeModalAfterAction();\n       });\n      }\n      const bulkDistributeHMobile = document.getElementById(\"bulk-distribute-h-mobile\");\n      if (bulkDistributeHMobile) {\n       bulkDistributeHMobile.addEventListener(\"click\", () => {\n        distributeSelectedNodes(\"horizontal\");\n        closeModalAfterAction();\n       });\n      }\n      const bulkDistributeVMobile = document.getElementById(\"bulk-distribute-v-mobile\");\n      if (bulkDistributeVMobile) {\n       bulkDistributeVMobile.addEventListener(\"click\", () => {\n        distributeSelectedNodes(\"vertical\");\n        closeModalAfterAction();\n       });\n      }\n      const bulkCloneMobile = document.getElementById(\"bulk-clone-mobile\");\n      if (bulkCloneMobile) {\n       bulkCloneMobile.addEventListener(\"click\", () => {\n        if (selectedNodes.size === 0) return;\n        pushUndo(\"clone selected\");\n        const nodesToClone = Array.from(selectedNodes);\n        selectedNodes.clear();\n        nodesToClone.forEach(id => {\n         cloneNode(id, true);\n         selectedNodes.add(currentNodeId);\n        });\n        updateNodeSelection();\n        closeModalAfterAction();\n       });\n      }\n      const bulkDeleteMobile = document.getElementById(\"bulk-delete-mobile\");\n      if (bulkDeleteMobile) {\n       bulkDeleteMobile.addEventListener(\"click\", () => {\n        deleteSelected();\n        closeModalAfterAction();\n       });\n      }\n      const bulkLockMobile = document.getElementById(\"bulk-lock-mobile\");\n      if (bulkLockMobile) {\n       bulkLockMobile.addEventListener(\"click\", () => {\n        toggleLockSelected();\n        closeModalAfterAction();\n       });\n      }\n      const bulkGroupMobile = document.getElementById(\"bulk-group-mobile\");\n      if (bulkGroupMobile) {\n       bulkGroupMobile.addEventListener(\"click\", () => {\n        toggleGroupSelected();\n        closeModalAfterAction();\n       });\n      }\n      const bulkClearMobile = document.getElementById(\"bulk-clear-mobile\");\n      if (bulkClearMobile) {\n       bulkClearMobile.addEventListener(\"click\", () => {\n        clearSelection();\n        closeModalAfterAction();\n       });\n      }\n      const searchInput = document.getElementById(\"search-nodes\");\n      let searchTimeout = null;\n      if (searchInput) {\n       searchInput.addEventListener(\"input\", (e) => {\n        clearTimeout(searchTimeout);\n        searchTimeout = setTimeout(() => { searchNodes(e.target.value); }, 150);\n\t\t });\n\t\t}\n       document.getElementById(\"save-file-btn\").addEventListener(\"click\", becomeImmortal);\n       document.getElementById(\"check-all-ping-btn\").addEventListener(\"click\", checkAllNodesStatus);\n       const addNodeBtn = document.getElementById(\"add-node-btn\");\n       const addNodeModal = document.getElementById(\"add-node-modal\");\n       const addNodeCancel = document.getElementById(\"add-node-cancel\");\n       const addNodeSave = document.getElementById(\"add-node-save\");\n       addNodeBtn.addEventListener(\"click\", () => {\n\t    if (isViewOnly()) return;\n        document.getElementById(\"new-node-name\").value = \"\";\n        document.getElementById(\"new-node-ip\").value = \"\";\n        document.getElementById(\"new-node-tags\").value = \"\";\n        document.getElementById(\"new-node-category\").value = \"basic\";\n        populateModalShapeSelect(\"new-node-category\", \"new-node-shape\", \"new-node-shape-preview\");\n        selectedNodeIconData = null;\n        document.getElementById('selected-node-icon').style.display = 'none';\n        const nodeShapePreview = document.getElementById('new-node-shape-preview');\n        if (nodeShapePreview) nodeShapePreview.style.display = 'flex';\n        document.getElementById(\"new-node-pingable\").checked = false;\n        document.getElementById(\"new-node-ping-protocol\").value = \"http\";\n        document.getElementById(\"new-node-custom-url\").value = \"\";\n        document.getElementById(\"new-node-ping-timeout\").value = \"3000\";\n        document.getElementById(\"new-node-ping-options\").style.display = \"none\";\n        document.getElementById(\"new-node-custom-url-container\").style.display = \"none\";\n        newNodeIconTags = [];\n        document.getElementById(\"new-node-icon-tags\").style.display = \"none\";\n        document.getElementById(\"new-node-icon-tags-list\").innerHTML = \"\";\n        document.getElementById(\"new-node-fill-color\").value = \"#1e293b\";\n        document.getElementById(\"new-node-border-color\").value = \"#475569\";\n        document.getElementById(\"new-node-size\").value = \"55\";\n        document.getElementById(\"new-node-size-value\").textContent = \"55\";\n        document.getElementById(\"new-node-layer\").value = \"layer1\";\n        document.getElementById(\"new-node-line-color\").value = \"#475569\";\n        document.getElementById(\"new-node-line-direction\").value = \"none\";\n        document.getElementById(\"new-node-line-routing\").value = \"orthogonal\";\n        document.getElementById(\"new-node-notes\").value = \"\";\n        const moreOpts = document.getElementById(\"new-node-more-options\");\n        if (moreOpts) moreOpts.removeAttribute(\"open\");\n        const connectSelect = document.getElementById(\"new-node-connect-to\");\n        connectSelect.innerHTML = '<option value=\"\">None</option>';\n        Object.entries(NODE_DATA).forEach(([nid, n]) => {\n         if (n.assignedRack) return;\n         const opt = document.createElement(\"option\");\n         opt.value = nid;\n         opt.textContent = n.name || nid;\n         connectSelect.appendChild(opt);\n        });\n        addNodeModal.classList.add(\"active\");\n        document.getElementById(\"new-node-name\").focus();\n       });\n       const canvasViewport = document.getElementById(\"canvas-viewport\");\n       if (canvasViewport) {\n        canvasViewport.addEventListener(\"dblclick\", (e) => {\n         if (currentView.mode === \"rack\" && e.target.id === \"map\") {\n          exitRack();\n         }\n        });\n       }\n       const layersBtn = document.getElementById(\"layers-btn\");\n       const layerModal = document.getElementById(\"layer-modal\");\n       const layerModalClose = document.getElementById(\"layer-modal-close\");\n       if (layersBtn && layerModal) {\n        layersBtn.addEventListener(\"click\", () => {\n         layerModal.classList.add(\"active\");\n        });\n       }\n       if (layerModalClose && layerModal) {\n        layerModalClose.addEventListener(\"click\", () => {\n         layerModal.classList.remove(\"active\");\n        });\n       }\n       if (layerModal) {\n        layerModal.addEventListener(\"click\", (e) => {\n         if (e.target === layerModal) {\n          layerModal.classList.remove(\"active\");\n         }\n        });\n       }\n       const tabsBtn = document.getElementById(\"tabs-btn\");\n       const tabsModal = document.getElementById(\"tabs-modal\");\n       const tabsModalClose = document.getElementById(\"tabs-modal-close\");\n       if (tabsBtn && tabsModal) {\n         tabsBtn.addEventListener(\"click\", () => {\n           displayTabs();\n           tabsModal.classList.add(\"active\");\n         });\n       }\n       if (tabsModalClose && tabsModal) {\n         tabsModalClose.addEventListener(\"click\", () => {\n           tabsModal.classList.remove(\"active\");\n         });\n       }\n       if (tabsModal) {\n         tabsModal.addEventListener(\"click\", (e) => {\n           if (e.target === tabsModal) {\n             tabsModal.classList.remove(\"active\");\n           }\n         });\n       }\n       const rollbackBtn = document.getElementById(\"rollback-btn\");\n       const rollbackModal = document.getElementById(\"rollback-modal\");\n       const rollbackModalClose = document.getElementById(\"rollback-modal-close\");\n       if (rollbackBtn && rollbackModal) {\n         rollbackBtn.addEventListener(\"click\", () => {\n           loadRollbackVersions();\n           rollbackModal.classList.add(\"active\");\n         });\n       }\n       if (rollbackModalClose && rollbackModal) {\n         rollbackModalClose.addEventListener(\"click\", () => {\n           rollbackModal.classList.remove(\"active\");\n         });\n       }\n       if (rollbackModal) {\n         rollbackModal.addEventListener(\"click\", (e) => {\n           if (e.target === rollbackModal) {\n             rollbackModal.classList.remove(\"active\");\n           }\n         });\n       }\n       const auditLogBtn = document.getElementById(\"audit-log-btn\");\n       const auditLogModal = document.getElementById(\"audit-log-modal\");\n       const auditLogModalClose = document.getElementById(\"audit-log-modal-close\");\n       if (auditLogBtn && auditLogModal) {\n         auditLogBtn.addEventListener(\"click\", () => {\n           loadAuditLog();\n           displayAuditLog();\n           auditLogModal.classList.add(\"active\");\n         });\n       }\n       if (auditLogModalClose && auditLogModal) {\n         auditLogModalClose.addEventListener(\"click\", () => {\n           auditLogModal.classList.remove(\"active\");\n         });\n       }\n\t   const portMapBtn = document.getElementById(\"port-map-btn\");\n      const portMapModal = document.getElementById(\"port-map-modal\");\n      const portMapClose = document.getElementById(\"port-map-modal-close\");\n      const portMapSearch = document.getElementById(\"port-map-search\");\n      const portMapFilter = document.getElementById(\"port-map-filter\");\n      function renderPortMap() {\n        const container = document.getElementById(\"port-map-table\");\n        const search = (document.getElementById(\"port-map-search\")?.value || \"\").toLowerCase();\n        const filter = document.getElementById(\"port-map-filter\")?.value || \"all\";\n        let edges = EDGE_DATA.list || [];\n        if (filter === \"with-ports\") edges = edges.filter(e => e.fromPort || e.toPort);\n        if (filter === \"without-ports\") edges = edges.filter(e => !e.fromPort && !e.toPort);\n        if (search) {\n          edges = edges.filter(e => {\n            const fromName = (NODE_DATA[e.from]?.name || e.from || \"\").toLowerCase();\n            const toName = (NODE_DATA[e.to]?.name || e.to || \"\").toLowerCase();\n            return fromName.includes(search) || toName.includes(search) || (e.fromPort || \"\").toLowerCase().includes(search) || (e.toPort || \"\").toLowerCase().includes(search) || (e.notes || []).join(\" \").toLowerCase().includes(search);\n          });\n        }\n        if (edges.length === 0) {\n          container.innerHTML = '<div style=\"text-align: center; padding: 40px; color: var(--text-soft);\">' + t(\"emptyStates.noConnectionsFound\") + '</div>';\n          return;\n        }\n        let html = '<table style=\"width: 100%; border-collapse: collapse; font-size: 13px;\">';\n        html += '<thead><tr style=\"background: var(--panel-alt); position: sticky; top: 0;\"><th style=\"padding: 10px; text-align: left; border-bottom: 1px solid var(--edge-main);\">From Device</th><th style=\"padding: 10px; text-align: left; border-bottom: 1px solid var(--edge-main);\">From Port</th><th style=\"padding: 10px; text-align: left; border-bottom: 1px solid var(--edge-main);\">To Device</th><th style=\"padding: 10px; text-align: left; border-bottom: 1px solid var(--edge-main);\">To Port</th><th style=\"padding: 10px; text-align: left; border-bottom: 1px solid var(--edge-main);\">Notes</th></tr></thead><tbody>';\n        edges.forEach(e => {\n          const fromName = NODE_DATA[e.from]?.name || e.from;\n          const toName = NODE_DATA[e.to]?.name || e.to;\n          const notes = (e.notes || []).join(\", \");\n          const edgeColor = e.color || EDGE_LEGEND[e.type]?.color || 'var(--edge-main)';\n          const fromPortDisplay = e.fromPort ? escapeHtml(e.fromPort) : \"-\";\n          const toPortDisplay = e.toPort ? escapeHtml(e.toPort) : \"-\";\n          const goToEdge = `document.getElementById('port-map-modal').classList.remove('active'); selectTheConnection('${escapeHtml(e.id)}'); focusOnSelected()`;\n          html += `<tr style=\"border-bottom: 1px solid var(--edge-main);\"><td style=\"padding: 8px;\"><span style=\"display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: ${edgeColor}; margin-right: 6px;\"></span><button onclick=\"document.getElementById('port-map-modal').classList.remove('active'); claimTheImmortal('${escapeHtml(e.from)}'); focusOnSelected()\" style=\"background: var(--panel-alt); color: var(--text-main); border: 1px solid var(--edge-main); border-radius: 4px; padding: 6px 10px; cursor: pointer; font-size: 13px; min-height: 36px;\">${escapeHtml(fromName)}</button></td><td style=\"padding: 8px;\"><button onclick=\"editPortFromMap('${escapeHtml(e.id)}', 'from')\" style=\"background: ${e.fromPort ? 'var(--accent)' : 'var(--panel)'}; color: ${e.fromPort ? 'var(--bg)' : 'var(--text-soft)'}; border: 1px solid var(--edge-main); border-radius: 4px; padding: 6px 10px; cursor: pointer; font-family: monospace; font-size: 13px; min-height: 36px;\">${fromPortDisplay}</button></td><td style=\"padding: 8px;\"><button onclick=\"document.getElementById('port-map-modal').classList.remove('active'); claimTheImmortal('${escapeHtml(e.to)}'); focusOnSelected()\" style=\"background: var(--panel-alt); color: var(--text-main); border: 1px solid var(--edge-main); border-radius: 4px; padding: 6px 10px; cursor: pointer; font-size: 13px; min-height: 36px;\">${escapeHtml(toName)}</button></td><td style=\"padding: 8px;\"><button onclick=\"editPortFromMap('${escapeHtml(e.id)}', 'to')\" style=\"background: ${e.toPort ? 'var(--accent)' : 'var(--panel)'}; color: ${e.toPort ? 'var(--bg)' : 'var(--text-soft)'}; border: 1px solid var(--edge-main); border-radius: 4px; padding: 6px 10px; cursor: pointer; font-family: monospace; font-size: 13px; min-height: 36px;\">${toPortDisplay}</button></td><td style=\"padding: 8px;\"><button onclick=\"${goToEdge}\" style=\"background: var(--panel); color: var(--text-main); border: 1px solid var(--edge-main); border-radius: 4px; padding: 6px 10px; cursor: pointer; font-size: 13px; min-height: 36px;\" title=\"Go to connection\">Go</button></td></tr>`;\n        });\n        html += '</tbody></table>';\n        container.innerHTML = html;\n      }\n      window.editPortFromMap = async function(edgeId, which) {\n        const edge = EDGE_DATA.list.find(e => e.id === edgeId);\n        if (!edge) return;\n        const fromName = NODE_DATA[edge.from]?.name || edge.from;\n        const toName = NODE_DATA[edge.to]?.name || edge.to;\n        const nodeId = which === 'from' ? edge.from : edge.to;\n        const nodeName = which === 'from' ? fromName : toName;\n        const currentVal = which === 'from' ? (edge.fromPort || '') : (edge.toPort || '');\n        const newVal = await showPrompt(t(\"dialogs.enterPort\", { nodeName: nodeName }), currentVal);\n        if (newVal !== null && newVal !== '') {\n          const isDuplicate = (EDGE_DATA.list || []).some(e => {\n            if (e.id === edgeId) return false;\n            if (e.from === nodeId && e.fromPort === newVal) return true;\n            if (e.to === nodeId && e.toPort === newVal) return true;\n            return false;\n          });\n          if (isDuplicate && !await showConfirm(t(\"dialogs.portAlreadyUsed\", { port: newVal, nodeName: nodeName }))) {\n            return;\n          }\n          if (which === 'from') edge.fromPort = newVal;\n          else edge.toPort = newVal;\n          renderPortMap();\n          if (currentNodeId && (edge.from === currentNodeId || edge.to === currentNodeId)) {\n            claimTheImmortal(currentNodeId);\n          }\n        } else if (newVal === '') {\n          if (which === 'from') edge.fromPort = '';\n          else edge.toPort = '';\n          renderPortMap();\n          if (currentNodeId && (edge.from === currentNodeId || edge.to === currentNodeId)) {\n            claimTheImmortal(currentNodeId);\n          }\n        }\n      };\n      window.exportPortMap = function() {\n        let csv = \"From Device,From Port,To Device,To Port,Notes\\n\";\n        (EDGE_DATA.list || []).forEach(e => {\n          const fromName = NODE_DATA[e.from]?.name || e.from || \"\";\n          const toName = NODE_DATA[e.to]?.name || e.to || \"\";\n          const notes = (e.notes || []).join(\"; \");\n          csv += `${csvEscape(fromName)},${csvEscape(e.fromPort || \"\")},${csvEscape(toName)},${csvEscape(e.toPort || \"\")},${csvEscape(notes)}\\n`;\n        });\n        const blob = new Blob([csv], { type: \"text/csv\" });\n        const url = URL.createObjectURL(blob);\n        const a = document.createElement(\"a\");\n        a.href = url;\n        a.download = \"port-map.csv\";\n        a.click();\n        URL.revokeObjectURL(url);\n      };\n      if (portMapBtn && portMapModal) {\n        portMapBtn.addEventListener(\"click\", () => { renderPortMap(); portMapModal.classList.add(\"active\"); });\n      }\n      if (portMapClose && portMapModal) {\n        portMapClose.addEventListener(\"click\", () => { portMapModal.classList.remove(\"active\"); });\n      }\n      if (portMapSearch) { portMapSearch.addEventListener(\"input\", renderPortMap); }\n      if (portMapFilter) { portMapFilter.addEventListener(\"change\", renderPortMap); }\n      if (portMapModal) {\n        portMapModal.addEventListener(\"click\", (e) => { if (e.target === portMapModal) portMapModal.classList.remove(\"active\"); });\n      }\n       if (auditLogModal) {\n         auditLogModal.addEventListener(\"click\", (e) => {\n           if (e.target === auditLogModal) {\n             auditLogModal.classList.remove(\"active\");\n           }\n         });\n       }\n       const auditFilter = document.getElementById(\"audit-filter\");\n       if (auditFilter) {\n         auditFilter.addEventListener(\"change\", (e) => {\n           displayAuditLog(e.target.value);\n         });\n       }\n       const secretsBtn = document.getElementById(\"secrets-btn\");\n       const secretsModal = document.getElementById(\"secrets-modal\");\n       const secretsModalClose = document.getElementById(\"secrets-modal-close\");\n       if (secretsBtn && secretsModal) {\n         secretsBtn.addEventListener(\"click\", () => {\n           displaySecrets();\n           secretsModal.classList.add(\"active\");\n         });\n       }\n       if (secretsModalClose && secretsModal) {\n         secretsModalClose.addEventListener(\"click\", () => {\n           secretsModal.classList.remove(\"active\");\n         });\n       }\n       if (secretsModal) {\n         secretsModal.addEventListener(\"click\", (e) => {\n           if (e.target === secretsModal) {\n             secretsModal.classList.remove(\"active\");\n           }\n         });\n       }\n       const secretEditorModal = document.getElementById(\"secret-editor-modal\");\n       if (secretEditorModal) {\n         secretEditorModal.addEventListener(\"click\", (e) => {\n           if (e.target === secretEditorModal) {\n             closeSecretEditor();\n           }\n         });\n       }\n\n       function execSecretCommand(command, value = null) {\n         const content = document.getElementById(\"secret-editor-content\");\n         content.focus();\n         if (command === \"code\") {\n           const sel = window.getSelection();\n           if (sel.rangeCount > 0) {\n             const range = sel.getRangeAt(0);\n             const selectedText = range.toString();\n             if (selectedText) {\n               const code = document.createElement(\"code\");\n               code.textContent = selectedText;\n               range.deleteContents();\n               range.insertNode(code);\n               range.setStartAfter(code);\n               range.collapse(true);\n               sel.removeAllRanges();\n               sel.addRange(range);\n             }\n           }\n         } else if (command === \"codeblock\") {\n           const sel = window.getSelection();\n           if (sel.rangeCount > 0) {\n             const range = sel.getRangeAt(0);\n             const selectedText = range.toString() || \"code here\";\n             const pre = document.createElement(\"pre\");\n             const code = document.createElement(\"code\");\n             code.textContent = selectedText;\n             pre.appendChild(code);\n             range.deleteContents();\n             range.insertNode(pre);\n             const p = document.createElement(\"p\");\n             p.innerHTML = \"<br>\";\n             pre.parentNode.insertBefore(p, pre.nextSibling);\n             range.setStart(p, 0);\n             range.collapse(true);\n             sel.removeAllRanges();\n             sel.addRange(range);\n           }\n         } else if (command === \"blockquote\") {\n           document.execCommand(\"formatBlock\", false, \"blockquote\");\n         } else if (command === \"createLink\") {\n           const url = prompt(\"Enter URL:\", \"https://\");\n           if (url) document.execCommand(\"createLink\", false, url);\n         } else if (command === \"heading\") {\n           if (value) {\n             document.execCommand(\"formatBlock\", false, value);\n           } else {\n             document.execCommand(\"formatBlock\", false, \"p\");\n           }\n         } else {\n           document.execCommand(command, false, null);\n         }\n       }\n       document.querySelectorAll(\"#secret-editor-toolbar button[data-command]\").forEach(btn => {\n         btn.addEventListener(\"click\", (e) => {\n           e.preventDefault();\n           execSecretCommand(btn.dataset.command);\n         });\n       });\n       const secretHeadingSelect = document.getElementById(\"secret-heading-select\");\n       if (secretHeadingSelect) {\n         secretHeadingSelect.addEventListener(\"change\", (e) => {\n           execSecretCommand(\"heading\", e.target.value);\n           e.target.value = \"\";\n         });\n       }\n\n       const notesHubSearch = document.getElementById(\"notes-hub-search\");\n       const notesHubFilter = document.getElementById(\"notes-hub-filter\");\n       if (notesHubSearch) {\n         notesHubSearch.addEventListener(\"input\", () => displaySecrets());\n       }\n       if (notesHubFilter) {\n         notesHubFilter.addEventListener(\"change\", () => displaySecrets());\n       }\n       [\"1\", \"2\", \"3\", \"4\"].forEach(num => {\n        const checkbox = document.getElementById(`layer-${num}`);\n        if (checkbox) {\n         checkbox.addEventListener(\"change\", applyLayerFilter);\n        }\n       });\n       const layerSelect = document.getElementById(\"node-layer\");\n       if (layerSelect) {\n        layerSelect.addEventListener(\"change\", (e) => {\n         if (currentNodeId) {\n          pushUndo(\"change layer\");\n          NODE_DATA[currentNodeId].layer = e.target.value;\n          forgeTheTopology();\n         }\n        });\n       }\n       const assignedRackSelect = document.getElementById(\"node-assigned-rack\");\n       if (assignedRackSelect) {\n        assignedRackSelect.addEventListener(\"change\", (e) => {\n      if (currentNodeId) {\n      pushUndo(\"change assigned rack\");\n      EDGE_DATA.list = EDGE_DATA.list.filter(edge => edge.from !== currentNodeId && edge.to !== currentNodeId);\n      NODE_DATA[currentNodeId].assignedRack = e.target.value;\n      forgeTheTopology();\n      claimTheImmortal(currentNodeId);\n      }\n      });\n       }\n       const hostedOnSelect = document.getElementById(\"node-hosted-on\");\n       if (hostedOnSelect) {\n        hostedOnSelect.addEventListener(\"change\", (e) => {\n         if (currentNodeId) {\n          pushUndo(\"change hosted on\");\n          NODE_DATA[currentNodeId].hostedOn = e.target.value;\n          forgeTheTopology();\n         }\n        });\n       }\n       const rackCapacitySelect = document.getElementById(\"node-rack-capacity\");\n       if (rackCapacitySelect) {\n        rackCapacitySelect.addEventListener(\"change\", (e) => {\n         if (currentNodeId) {\n          pushUndo(\"change rack capacity\");\n          NODE_DATA[currentNodeId].rackCapacity = e.target.value;\n          forgeTheTopology();\n         }\n        });\n       }\n       const addRackBtn = document.getElementById(\"add-rack-btn\");\n       const addRackModal = document.getElementById(\"add-rack-modal\");\n       const addRackCancel = document.getElementById(\"add-rack-cancel\");\n       const addRackSave = document.getElementById(\"add-rack-save\");\n       if (addRackBtn && addRackModal) {\n        addRackBtn.addEventListener(\"click\", () => {\n\t\tif (isViewOnly()) return;\n         document.getElementById(\"new-rack-name\").value = \"\";\n         document.getElementById(\"new-rack-ip\").value = \"\";\n         document.getElementById(\"new-rack-tags\").value = \"\";\n         document.getElementById(\"new-rack-shape\").value = \"server\";\n         document.getElementById(\"new-rack-capacity\").value = \"42\";\n         selectedRackIconData = null;\n         document.getElementById('selected-rack-icon').style.display = 'none';\n         const rackShapePreview = document.getElementById('new-rack-shape-preview');\n         if (rackShapePreview) rackShapePreview.style.display = 'flex';\n         document.getElementById(\"new-rack-fill-color\").value = \"#1e293b\";\n         document.getElementById(\"new-rack-border-color\").value = \"#475569\";\n         document.getElementById(\"new-rack-size\").value = \"55\";\n         document.getElementById(\"new-rack-size-value\").textContent = \"55\";\n         document.getElementById(\"new-rack-layer\").value = \"layer1\";\n         document.getElementById(\"new-rack-notes\").value = \"\";\n         const moreOpts = document.getElementById(\"new-rack-more-options\");\n         if (moreOpts) moreOpts.removeAttribute(\"open\");\n         const assignContainer = document.getElementById(\"new-rack-assign-nodes\");\n         assignContainer.innerHTML = \"\";\n         Object.entries(NODE_DATA).forEach(([nid, n]) => {\n          if (n.isRack) return;\n          if (n.assignedRack) return;\n          const row = document.createElement(\"div\");\n          row.style.cssText = \"display:flex;align-items:center;gap:8px;padding:6px 10px;border:1px solid var(--edge-main);border-radius:4px;margin-bottom:4px;background:var(--panel)\";\n          const cb = document.createElement(\"input\");\n          cb.type = \"checkbox\";\n          cb.style.cssText = \"flex-shrink:0;width:16px;height:16px;margin:0;cursor:pointer\";\n          cb.dataset.nodeId = nid;\n          cb.className = \"new-rack-assign-cb\";\n          const lbl = document.createElement(\"span\");\n          lbl.style.cssText = \"color:var(--text-main);font-size:13px;flex:1;text-align:left\";\n          lbl.textContent = n.name || nid;\n          row.appendChild(cb);\n          row.appendChild(lbl);\n          assignContainer.appendChild(row);\n         });\n         if (!assignContainer.children.length) {\n          assignContainer.innerHTML = '<div style=\"color:var(--text-soft);font-size:13px;text-align:center;padding:10px\">No unassigned nodes</div>';\n         }\n         addRackModal.classList.add(\"active\");\n         document.getElementById(\"new-rack-name\").focus();\n        });\n       }\n       if (addRackCancel && addRackModal) {\n        addRackCancel.addEventListener(\"click\", () => {\n         addRackModal.classList.remove(\"active\");\n        });\n       }\n       if (addRackModal) {\n        addRackModal.addEventListener(\"click\", (e) => {\n         if (e.target === addRackModal) {\n          addRackModal.classList.remove(\"active\");\n         }\n        });\n       }\n       document.getElementById(\"new-node-size\").addEventListener(\"input\", (e) => {\n        document.getElementById(\"new-node-size-value\").textContent = e.target.value;\n       });\n       document.getElementById(\"new-rack-size\").addEventListener(\"input\", (e) => {\n        document.getElementById(\"new-rack-size-value\").textContent = e.target.value;\n       });\n       if (addRackSave && addRackModal) {\n        addRackSave.addEventListener(\"click\", () => {\n         const name = document.getElementById(\"new-rack-name\").value.trim();\n         const ip = document.getElementById(\"new-rack-ip\").value.trim();\n         const tagsStr = document.getElementById(\"new-rack-tags\").value.trim();\n         const shape = document.getElementById(\"new-rack-shape\").value;\n         const capacity = document.getElementById(\"new-rack-capacity\").value;\n         if (!name) {\n          showAlert(t(\"dialogs.enterRackName\"));\n          return;\n         }\n         const tags = tagsStr ? tagsStr.split(\",\").map((t) => t.trim()).filter((t) => t) : [];\n         let baseId = name.toLowerCase().replace(/[^a-z0-9]+/g, \"-\").replace(/^-|-$/g, \"\");\n         if (!baseId) baseId = \"rack\";\n         let nodeId = baseId;\n         let counter = 1;\n         while (NODE_DATA[nodeId]) {\n          nodeId = baseId + \"-\" + counter;\n          counter++;\n         }\n         const rackLayer = document.getElementById(\"new-rack-layer\").value;\n         const rackFillColor = document.getElementById(\"new-rack-fill-color\").value;\n         const rackBorderColor = document.getElementById(\"new-rack-border-color\").value;\n         const rackSize = parseInt(document.getElementById(\"new-rack-size\").value, 10);\n         const rackNotesVal = document.getElementById(\"new-rack-notes\").value.trim();\n         pushUndo(\"add rack\");\n         NODE_DATA[nodeId] = {\n          shape: shape,\n          name: name,\n          ip: ip || \"\",\n          role: \"Rack\",\n          tags: tags,\n          notes: rackNotesVal ? [rackNotesVal] : [],\n          mac: \"\",\n          rackUnit: \"\",\n          uHeight: \"1\",\n          layer: rackLayer,\n          assignedRack: \"\",\n          hostedOn: \"\",\n          rackCapacity: capacity,\n          isRack: true,\n          locked: false,\n          groupId: null\n         };\n         const viewWidth = CANVAS_WIDTH / canvasState.zoom;\n         const viewHeight = CANVAS_HEIGHT / canvasState.zoom;\n         const centerX = canvasState.panX + (viewWidth / 2);\n         const centerY = canvasState.panY + (viewHeight / 2);\n         savedPositions[nodeId] = {\n          x: centerX,\n          y: centerY\n         };\n         if (selectedRackIconData) {\n          if (!savedStyles[nodeId]) {\n           savedStyles[nodeId] = {};\n          }\n          if (!savedStyles[nodeId]['all']) {\n           savedStyles[nodeId]['all'] = {};\n          }\n          savedStyles[nodeId]['all'].icon = {\n           library: selectedRackIconData.library,\n           name: selectedRackIconData.name\n          };\n          selectedRackIconData = null;\n          document.getElementById('selected-rack-icon').style.display = 'none';\n         }\n         if (rackFillColor !== \"#1e293b\" || rackBorderColor !== \"#475569\") {\n          const styleEntry = ensureStyleEntry(nodeId);\n          if (!styleEntry[\"all\"]) styleEntry[\"all\"] = {};\n          if (rackFillColor !== \"#1e293b\") styleEntry[\"all\"].circleColor = rackFillColor;\n          if (rackBorderColor !== \"#475569\") styleEntry[\"all\"].circleBorder = rackBorderColor;\n         }\n         if (rackSize !== 55) {\n          savedSizes[nodeId] = rackSize;\n         }\n         document.querySelectorAll(\".new-rack-assign-cb:checked\").forEach((cb) => {\n          const assignId = cb.dataset.nodeId;\n          if (NODE_DATA[assignId]) {\n           NODE_DATA[assignId].assignedRack = nodeId;\n          }\n         });\n         addRackModal.classList.remove(\"active\");\n         forgeTheTopology();\n         claimTheImmortal(nodeId);\n        });\n        [\"new-rack-name\", \"new-rack-ip\", \"new-rack-tags\"].forEach((inputId) => {\n         const input = document.getElementById(inputId);\n         if (input) {\n          input.addEventListener(\"keypress\", (e) => {\n           if (e.key === \"Enter\") {\n            addRackSave.click();\n           }\n          });\n         }\n        });\n       }\n       addNodeCancel.addEventListener(\"click\", () => {\n        addNodeModal.classList.remove(\"active\");\n       });\n       addNodeModal.addEventListener(\"click\", (e) => {\n        if (e.target === addNodeModal) {\n         addNodeModal.classList.remove(\"active\");\n        }\n       });\n       addNodeSave.addEventListener(\"click\", () => {\n        const name = document.getElementById(\"new-node-name\").value.trim();\n        const ip = document.getElementById(\"new-node-ip\").value.trim();\n        const tagsStr = document.getElementById(\"new-node-tags\").value.trim();\n        const shape = document.getElementById(\"new-node-shape\").value;\n        const pingable = document.getElementById(\"new-node-pingable\").checked;\n        const pingProtocol = document.getElementById(\"new-node-ping-protocol\").value;\n        const pingCustomUrl = document.getElementById(\"new-node-custom-url\").value.trim();\n        const pingTimeout = parseInt(document.getElementById(\"new-node-ping-timeout\").value) || 3000;\n        if (!name) {\n         showAlert(t(\"dialogs.enterNodeName\"));\n         return;\n        }\n        const tags = tagsStr ? tagsStr.split(\",\").map((t) => t.trim()).filter((t) => t) : [];\n        if (newNodeIconTags.length > 0) {\n         tags.push(...newNodeIconTags);\n        }\n        let baseId = name.toLowerCase().replace(/[^a-z0-9]+/g, \"-\").replace(/^-|-$/g, \"\");\n        if (!baseId) baseId = \"node\";\n        let nodeId = baseId;\n        let counter = 1;\n        while (NODE_DATA[nodeId]) {\n         nodeId = baseId + \"-\" + counter;\n         counter++;\n        }\n        const nodeLayer = document.getElementById(\"new-node-layer\").value;\n        const nodeFillColor = document.getElementById(\"new-node-fill-color\").value;\n        const nodeBorderColor = document.getElementById(\"new-node-border-color\").value;\n        const nodeSize = parseInt(document.getElementById(\"new-node-size\").value, 10);\n        const nodeConnectTo = document.getElementById(\"new-node-connect-to\").value;\n        const nodeLineColor = document.getElementById(\"new-node-line-color\").value;\n        const nodeLineDirection = document.getElementById(\"new-node-line-direction\").value;\n        const nodeLineRouting = document.getElementById(\"new-node-line-routing\").value;\n        const nodeNotesVal = document.getElementById(\"new-node-notes\").value.trim();\n\t\tpushUndo(\"add node\");\n        NODE_DATA[nodeId] = {\n         shape: shape || \"circle\",\n         name: name,\n         ip: ip || \"\",\n         role: \"\",\n         tags: tags,\n         notes: nodeNotesVal ? [nodeNotesVal] : [],\n         mac: \"\",\n         rackUnit: \"\",\n         uHeight: \"1\",\n         layer: nodeLayer,\n         assignedRack: \"\",\n         hostedOn: \"\",\n         ping: {\n          enabled: pingable,\n          protocol: pingProtocol,\n          customUrl: pingCustomUrl,\n          timeout: pingTimeout,\n          status: 'unknown',\n          lastCheck: null\n         },\n         locked: false,\n         groupId: null\n        };\n        if (currentView.mode === \"rack\" && currentView.rackId) {\n         NODE_DATA[nodeId].assignedRack = currentView.rackId;\n         NODE_DATA[nodeId].layer = \"layer1\";\n         const rackCapacity = getRackCapacity(currentView.rackId);\n         const rackUHeight = getRackUHeight(currentView.rackId);\n         const viewHeight = CANVAS_HEIGHT / canvasState.zoom;\n         const centerY = canvasState.panY + (viewHeight / 2);\n         let unit = rackCapacity - Math.round((centerY - RACK_START_Y) / rackUHeight);\n         unit = Math.max(1, Math.min(rackCapacity, unit));\n         NODE_DATA[nodeId].rackUnit = String(unit);\n        }\n        if (selectedNodeIconData) {\n         if (!savedStyles[nodeId]) savedStyles[nodeId] = {};\n         if (!savedStyles[nodeId]['all']) savedStyles[nodeId]['all'] = {};\n         savedStyles[nodeId]['all'].icon = {\n          library: selectedNodeIconData.library,\n          name: selectedNodeIconData.name\n         };\n         selectedNodeIconData = null;\n         document.getElementById('selected-node-icon').style.display = 'none';\n        }\n        newNodeIconTags = [];\n      const viewWidth = CANVAS_WIDTH / canvasState.zoom;\n      const viewHeight = CANVAS_HEIGHT / canvasState.zoom;\n      const centerX = canvasState.panX + (viewWidth / 2);\n      const centerY = canvasState.panY + (viewHeight / 2);\n      savedPositions[nodeId] = {\n      x: centerX,\n      y: centerY\n      };\n        if (nodeFillColor !== \"#1e293b\" || nodeBorderColor !== \"#475569\") {\n         const styleEntry = ensureStyleEntry(nodeId);\n         if (!styleEntry[\"all\"]) styleEntry[\"all\"] = {};\n         if (nodeFillColor !== \"#1e293b\") styleEntry[\"all\"].circleColor = nodeFillColor;\n         if (nodeBorderColor !== \"#475569\") styleEntry[\"all\"].circleBorder = nodeBorderColor;\n        }\n        if (nodeSize !== 55) {\n         savedSizes[nodeId] = nodeSize;\n        }\n        if (nodeConnectTo && NODE_DATA[nodeConnectTo]) {\n         EDGE_DATA.list.push({\n          id: nodeId + \"-\" + nodeConnectTo + \"-\" + Date.now(),\n          from: nodeId,\n          to: nodeConnectTo,\n          width: 4,\n          color: nodeLineColor || \"#475569\",\n          direction: nodeLineDirection || \"none\",\n          routing: nodeLineRouting || \"orthogonal\",\n          type: \"main\",\n          notes: [],\n          fromPort: \"\",\n          toPort: \"\",\n          lineStyle: \"solid\",\n          waypoints: []\n         });\n        }\n        addNodeModal.classList.remove(\"active\");\n        forgeTheTopology();\n        claimTheImmortal(nodeId);\n       });\n       [\"new-node-name\", \"new-node-ip\", \"new-node-tags\"].forEach(\n        (inputId) => {\n         document.getElementById(inputId).addEventListener(\"keypress\", (e) => {\n          if (e.key === \"Enter\") {\n           addNodeSave.click();\n          }\n         });\n        });\n       document.getElementById('new-node-pingable').addEventListener('change', (e) => {\n        const pingOptions = document.getElementById('new-node-ping-options');\n        pingOptions.style.display = e.target.checked ? 'block' : 'none';\n       });\n       document.getElementById('new-node-ping-protocol').addEventListener('change', (e) => {\n        const customUrlContainer = document.getElementById('new-node-custom-url-container');\n        customUrlContainer.style.display = e.target.value === 'custom' ? 'block' : 'none';\n       });\n       document.getElementById('pick-rack-icon-btn').addEventListener('click', () => {\n        openIconPicker((iconData) => {\n         selectedRackIconData = iconData;\n         const preview = document.getElementById('selected-rack-icon-preview');\n         const container = document.getElementById('selected-rack-icon');\n         const parser = new DOMParser();\n         const doc = parser.parseFromString(iconData.svg, 'image/svg+xml');\n         const svgEl = doc.querySelector('svg');\n         preview.innerHTML = '';\n         if (svgEl) {\n          preview.appendChild(svgEl.cloneNode(true));\n         }\n         const nameSpan = document.createElement('span');\n         nameSpan.textContent = iconData.name;\n         preview.appendChild(nameSpan);\n         container.style.display = 'block';\n         const shapePreview = document.getElementById('new-rack-shape-preview');\n         if (shapePreview) shapePreview.style.display = 'none';\n        });\n       });\n       document.getElementById('pick-node-icon-btn').addEventListener('click', () => {\n        openIconPicker((iconData) => {\n         selectedNodeIconData = iconData;\n         const preview = document.getElementById('selected-node-icon-preview');\n         const container = document.getElementById('selected-node-icon');\n         const parser = new DOMParser();\n         const doc = parser.parseFromString(iconData.svg, 'image/svg+xml');\n         const svgEl = doc.querySelector('svg');\n         preview.innerHTML = '';\n         if (svgEl) {\n          preview.appendChild(svgEl.cloneNode(true));\n         }\n         const nameSpan = document.createElement('span');\n         nameSpan.textContent = iconData.name;\n         preview.appendChild(nameSpan);\n         container.style.display = 'block';\n         const shapePreview = document.getElementById('new-node-shape-preview');\n         if (shapePreview) shapePreview.style.display = 'none';\n        });\n       });\n       document.getElementById('pick-tag-icon-btn').addEventListener('click', () => {\n        if (!currentNodeId) return;\n        openIconPicker((iconData) => {\n         if (!currentNodeId || !NODE_DATA[currentNodeId]) return;\n         if (!NODE_DATA[currentNodeId].tags) {\n          NODE_DATA[currentNodeId].tags = [];\n         }\n         NODE_DATA[currentNodeId].tags.push({\n          type: 'icon',\n          library: iconData.library,\n          name: iconData.name\n         });\n         forgeTheTopology();\n         claimTheImmortal(currentNodeId);\n        });\n       });\n       document.getElementById('pick-new-node-tag-icon-btn').addEventListener('click', () => {\n        openIconPicker((iconData) => {\n         newNodeIconTags.push({\n          type: 'icon',\n          library: iconData.library,\n          name: iconData.name\n         });\n         const container = document.getElementById('new-node-icon-tags');\n         const list = document.getElementById('new-node-icon-tags-list');\n         const badge = document.createElement('div');\n         badge.className = 'icon-badge';\n         badge.style.cssText = 'display: inline-flex; align-items: center; gap: 6px; padding: 6px 10px; background: var(--panel-alt); border: 1px solid var(--edge-main); border-radius: 6px; font-size: 13px;';\n         const iconPreview = document.createElement('div');\n         iconPreview.style.cssText = 'width: 16px; height: 16px; display: flex; align-items: center; justify-content: center;';\n         IconLibrary.getIcon(iconData.library, iconData.name).then(svg => {\n          if (svg) {\n           const parser = new DOMParser();\n           const doc = parser.parseFromString(svg, 'image/svg+xml');\n           const svgEl = doc.querySelector('svg');\n           if (svgEl) {\n            svgEl.setAttribute('width', '16');\n            svgEl.setAttribute('height', '16');\n            svgEl.style.fill = 'var(--text-main)';\n            iconPreview.appendChild(svgEl);\n           }\n          }\n         });\n         const name = document.createElement('span');\n         name.textContent = iconData.name;\n         name.style.color = 'var(--text-soft)';\n         const removeBtn = document.createElement('button');\n         removeBtn.textContent = '×';\n         removeBtn.style.cssText = 'background: none; border: none; color: var(--danger); cursor: pointer; font-size: 18px; line-height: 1; padding: 0 4px;';\n         removeBtn.addEventListener('click', () => {\n          const index = newNodeIconTags.findIndex(t => t.type === 'icon' && t.library === iconData.library && t.name === iconData.name);\n          if (index > -1) {\n           newNodeIconTags.splice(index, 1);\n          }\n          badge.remove();\n          if (list.children.length === 0) {\n           container.style.display = 'none';\n          }\n         });\n         badge.appendChild(iconPreview);\n         badge.appendChild(name);\n         badge.appendChild(removeBtn);\n         list.appendChild(badge);\n         container.style.display = 'block';\n        });\n       });\n       document.getElementById('pick-shape-icon-btn').addEventListener('click', () => {\n        if (!currentNodeId) return;\n        openIconPicker((iconData) => {\n         if (!savedStyles[currentNodeId]) {\n          savedStyles[currentNodeId] = {};\n         }\n         if (!savedStyles[currentNodeId][currentStyleScope]) {\n          savedStyles[currentNodeId][currentStyleScope] = {};\n         }\n         savedStyles[currentNodeId][currentStyleScope].icon = {\n          library: iconData.library,\n          name: iconData.name\n         };\n\t\t delete savedStyles[currentNodeId][currentStyleScope].circleColor;\n         delete savedStyles[currentNodeId][currentStyleScope].circleBorder;\n         const shapeSelect = document.getElementById('shape-select');\n         if (!shapeSelect.querySelector('option[value=\"custom-icon\"]')) {\n          const opt = document.createElement('option');\n          opt.value = 'custom-icon';\n          opt.textContent = 'Custom Icon';\n          shapeSelect.insertBefore(opt, shapeSelect.firstChild);\n         }\n         shapeSelect.value = 'custom-icon';\n         forgeTheTopology();\n        });\n       });\n       document.getElementById('add-tag-btn').addEventListener('click', () => {\n        if (!currentNodeId) return;\n        if (!NODE_DATA[currentNodeId]) return;\n        const input = document.getElementById('new-tag-input');\n        const tagText = input.value.trim();\n        if (!tagText) return;\n        if (!NODE_DATA[currentNodeId].tags) {\n         NODE_DATA[currentNodeId].tags = [];\n        }\n        NODE_DATA[currentNodeId].tags.push(tagText);\n        input.value = '';\n        forgeTheTopology();\n        claimTheImmortal(currentNodeId);\n       });\n      function saveRollbackVersion(description = \"Auto-save\") {\n        const version = {\n          timestamp: Date.now(),\n          description,\n          data: captureTheQuickening()\n        };\n        try {\n          const stored = localStorage.getItem(ROLLBACK_STORAGE_KEY);\n          rollbackVersions = stored ? JSON.parse(stored) : [];\n        } catch (e) {\n          rollbackVersions = [];\n        }\n        rollbackVersions.unshift(version);\n        if (rollbackVersions.length > MAX_ROLLBACK_VERSIONS) {\n          rollbackVersions = rollbackVersions.slice(0, MAX_ROLLBACK_VERSIONS);\n        }\n        try {\n          localStorage.setItem(ROLLBACK_STORAGE_KEY, JSON.stringify(rollbackVersions));\n        } catch (e) {\n          console.warn(\"Failed to save rollback version:\", e);\n        }\n      }\n      function loadRollbackVersions() {\n        try {\n          const stored = localStorage.getItem(ROLLBACK_STORAGE_KEY);\n          rollbackVersions = stored ? JSON.parse(stored) : [];\n          displayRollbackVersions();\n        } catch (e) {\n          console.warn(\"Failed to load rollback versions:\", e);\n          rollbackVersions = [];\n        }\n      }\n      function displayRollbackVersions() {\n        const listEl = document.getElementById(\"rollback-list\");\n        if (!listEl) return;\n        if (rollbackVersions.length === 0) {\n          listEl.innerHTML = '<div style=\"text-align: center; padding: 40px; color: var(--text-soft);\">' + t(\"emptyStates.noVersionHistory\") + '</div>';\n          return;\n        }\n        listEl.innerHTML = rollbackVersions.map((version, index) => {\n          const date = new Date(version.timestamp);\n          const timeStr = date.toLocaleString();\n          const nodeCount = Object.keys(version.data.nodeData || {}).length;\n          const edgeCount = (version.data.edgeData?.list || []).length;\n          return `\n            <div class=\"version-item\" onclick=\"restoreRollbackVersion(${index})\">\n              <div class=\"version-info\">\n                <div class=\"timestamp\">${escapeHtml(timeStr)}</div>\n                <div class=\"details\">${escapeHtml(version.description)} • ${t(\"ui.stats.nodesConnections\", { nodeCount, edgeCount })}</div>\n              </div>\n              <div class=\"version-actions\">\n                <button class=\"btn-cancel\" onclick=\"event.stopPropagation(); deleteRollbackVersion(${index})\" title=\"Delete this version\">🗑️</button>\n              </div>\n            </div>\n          `;\n        }).join('');\n      }\n      async function restoreRollbackVersion(index) {\n        if (!await showConfirm(t(\"dialogs.restoreVersion\", { date: new Date(rollbackVersions[index].timestamp).toLocaleString() }))) {\n          return;\n        }\n        const version = rollbackVersions[index];\n        const data = version.data;\n        NODE_DATA = data.nodeData || {};\n        EDGE_DATA = data.edgeData || { list: [] };\n        RECT_DATA = data.rectData || { list: [] };\n        TEXT_DATA = data.textData || { list: [] };\n        EDGE_LEGEND = data.edgeLegend || {};\n        savedPositions = data.nodePositions || {};\n        savedSizes = data.nodeSizes || {};\n        savedStyles = data.nodeStyles || {};\n        PAGE_STATE = data.page || PAGE_STATE;\n        if (data.canvas) {\n          canvasState.zoom = data.canvas.zoom;\n          canvasState.panX = data.canvas.panX;\n          canvasState.panY = data.canvas.panY;\n        }\n        wieldThePower();\n        forgeTheTopology();\n        document.getElementById(\"rollback-modal\").classList.remove(\"active\");\n        logAuditEvent(\"rollback\", `Restored version from ${new Date(version.timestamp).toLocaleString()}`);\n      }\n      async function deleteRollbackVersion(index) {\n        if (!await showConfirm(t(\"dialogs.deleteVersionConfirm\"))) return;\n        rollbackVersions.splice(index, 1);\n        try {\n          localStorage.setItem(ROLLBACK_STORAGE_KEY, JSON.stringify(rollbackVersions));\n        } catch (e) {\n          console.warn(\"Failed to delete version:\", e);\n        }\n        displayRollbackVersions();\n      }\n      async function clearRollbackHistory() {\n        if (!await showConfirm(t(\"dialogs.clearVersionHistory\"))) return;\n        rollbackVersions = [];\n        localStorage.removeItem(ROLLBACK_STORAGE_KEY);\n        displayRollbackVersions();\n      }\n      async function createManualSnapshot() {\n        const description = await showPrompt(t(\"dialogs.enterSnapshotDescription\"), \"Manual snapshot\");\n        if (!description) return;\n        saveRollbackVersion(description);\n        displayRollbackVersions();\n      }\n      function switchTab(index) {\n        if (index === currentTabIndex) return;\n        const currentTab = documentTabs[currentTabIndex];\n        currentTab.nodes = JSON.parse(JSON.stringify(NODE_DATA));\n        currentTab.edges = JSON.parse(JSON.stringify(EDGE_DATA));\n        currentTab.positions = JSON.parse(JSON.stringify(savedPositions));\n        currentTab.sizes = JSON.parse(JSON.stringify(savedSizes));\n        currentTab.styles = JSON.parse(JSON.stringify(savedStyles));\n        currentTab.legend = JSON.parse(JSON.stringify(EDGE_LEGEND));\n        currentTab.rects = JSON.parse(JSON.stringify(RECT_DATA));\n        currentTab.texts = JSON.parse(JSON.stringify(TEXT_DATA));\n        currentTab.images = JSON.parse(JSON.stringify(IMAGE_DATA));\n        currentTab.pageState = JSON.parse(JSON.stringify(PAGE_STATE));\n        currentTabIndex = index;\n        const newTab = documentTabs[index];\n        NODE_DATA = JSON.parse(JSON.stringify(newTab.nodes));\n        EDGE_DATA = JSON.parse(JSON.stringify(newTab.edges));\n        savedPositions = JSON.parse(JSON.stringify(newTab.positions));\n        savedSizes = JSON.parse(JSON.stringify(newTab.sizes));\n        savedStyles = JSON.parse(JSON.stringify(newTab.styles));\n        EDGE_LEGEND = JSON.parse(JSON.stringify(newTab.legend));\n        RECT_DATA = JSON.parse(JSON.stringify(newTab.rects));\n        TEXT_DATA = JSON.parse(JSON.stringify(newTab.texts));\n        IMAGE_DATA = JSON.parse(JSON.stringify(newTab.images || { list: [] }));\n        PAGE_STATE = Object.assign({}, DEFAULT_PAGE_STATE, newTab.pageState || {});\n        wieldThePower();\n        document.title = PAGE_STATE.title || newTab.name;\n        document.getElementById(\"page-title\").textContent = PAGE_STATE.title || newTab.name;\n        forgeTheTopology();\n        currentNodeId = null;\n        currentEdgeId = null;\n        currentTextId = null;\n        currentRectId = null;\n        currentImageId = null;\n        document.getElementById(\"node-panel\").style.display = \"none\";\n        document.getElementById(\"edge-panel\").style.display = \"none\";\n        document.getElementById(\"text-panel\").style.display = \"none\";\n        document.getElementById(\"rect-panel\").style.display = \"none\";\n        document.getElementById(\"image-panel\").style.display = \"none\";\n        displayTabs();\n        updateDrawToolbarVisibility();\n        updateTopologyToolbarVisibility();\n        logAuditEvent(\"tab\", `Switched to tab: ${newTab.name}`);\n        const hasData = Object.keys(NODE_DATA).length > 0 || EDGE_DATA.list.length > 0;\n        const hasPageState = newTab.pageState !== null && newTab.pageState !== undefined;\n        if (!hasData && !hasPageState) {\n          if (typeof showWelcomeModal === 'function') showWelcomeModal();\n        }\n      }\n      function createNewTab() {\n        const nameInput = document.getElementById(\"new-tab-name\");\n        const name = nameInput.value.trim();\n        if (!name) {\n          showAlert(t(\"dialogs.enterTabName\"));\n          return;\n        }\n        const newTab = {\n          id: `tab-${Date.now()}`,\n          name,\n          nodes: {},\n          edges: { list: [] },\n          positions: {},\n          sizes: {},\n          styles: {},\n          legend: {},\n          rects: { list: [] },\n          texts: { list: [] },\n      pageState: null\n        };\n        documentTabs.push(newTab);\n        nameInput.value = \"\";\n        displayTabs();\n        logAuditEvent(\"tab\", `Created new tab: ${name}`);\n      }\n      async function renameTab(index) {\n        const tab = documentTabs[index];\n        const newName = await showPrompt(t(\"dialogs.enterNewTabName\"), tab.name);\n        if (!newName || newName === tab.name) return;\n        tab.name = newName;\n        displayTabs();\n        logAuditEvent(\"tab\", `Renamed tab to: ${newName}`);\n      }\n      async function deleteTab(index) {\n        if (documentTabs.length === 1) {\n          showAlert(t(\"dialogs.cannotDeleteLastTab\"));\n          return;\n        }\n        if (!await showConfirm(t(\"dialogs.deleteTab\", { name: documentTabs[index].name }))) return;\n        const wasCurrentTab = (index === currentTabIndex);\n        documentTabs.splice(index, 1);\n        if (currentTabIndex >= documentTabs.length) {\n          currentTabIndex = documentTabs.length - 1;\n        } else if (index < currentTabIndex) {\n          currentTabIndex--;\n        }\n        if (wasCurrentTab) {\n          const newTab = documentTabs[currentTabIndex];\n          NODE_DATA = JSON.parse(JSON.stringify(newTab.nodes));\n          EDGE_DATA = JSON.parse(JSON.stringify(newTab.edges));\n          savedPositions = JSON.parse(JSON.stringify(newTab.positions));\n          savedSizes = JSON.parse(JSON.stringify(newTab.sizes));\n          savedStyles = JSON.parse(JSON.stringify(newTab.styles));\n          EDGE_LEGEND = JSON.parse(JSON.stringify(newTab.legend));\n          RECT_DATA = JSON.parse(JSON.stringify(newTab.rects));\n          TEXT_DATA = JSON.parse(JSON.stringify(newTab.texts));\n          IMAGE_DATA = JSON.parse(JSON.stringify(newTab.images || { list: [] }));\n          PAGE_STATE = Object.assign({}, DEFAULT_PAGE_STATE, newTab.pageState || {});\n          wieldThePower();\n          forgeTheTopology();\n          currentNodeId = null;\n          currentEdgeId = null;\n          currentTextId = null;\n          currentRectId = null;\n          currentImageId = null;\n          document.getElementById(\"node-panel\").style.display = \"none\";\n          document.getElementById(\"edge-panel\").style.display = \"none\";\n          document.getElementById(\"text-panel\").style.display = \"none\";\n          document.getElementById(\"rect-panel\").style.display = \"none\";\n          document.getElementById(\"image-panel\").style.display = \"none\";\n        }\n        displayTabs();\n        logAuditEvent(\"tab\", `Deleted tab`);\n      }\n      async function saveCurrentTheme() {\n        const name = await showPrompt(t(\"dialogs.saveThemeName\"), t(\"dialogs.defaultThemeName\", { number: savedStyleSets.length + 1 }));\n        if (!name || !name.trim()) return;\n        const existingIndex = savedStyleSets.findIndex(s => s.name.toLowerCase() === name.trim().toLowerCase());\n        if (existingIndex !== -1) {\n          if (!await showConfirm(t(\"dialogs.themeExists\", { name: name }))) return;\n          savedStyleSets.splice(existingIndex, 1);\n        }\n        const styleSet = {\n          id: \"mytheme-\" + Date.now(),\n          name: name.trim(),\n          styles: JSON.parse(JSON.stringify(PAGE_STATE))\n        };\n        delete styleSet.styles.title;\n        delete styleSet.styles.viewOnly;\n        savedStyleSets.push(styleSet);\n        rebuildThemeDropdown();\n        document.getElementById(\"theme-preset\").value = styleSet.id;\n        updateDeleteButton();\n        logAuditEvent(\"style\", \"Saved theme: \" + name);\n      }\n      async function deleteCurrentTheme() {\n        const select = document.getElementById(\"theme-preset\");\n        const val = select.value;\n        if (!val.startsWith(\"mytheme-\")) return;\n        const index = savedStyleSets.findIndex(s => s.id === val);\n        if (index === -1) return;\n        if (!await showConfirm(t(\"dialogs.deleteTheme\", { name: savedStyleSets[index].name }))) return;\n        savedStyleSets.splice(index, 1);\n        rebuildThemeDropdown();\n        select.value = \"defaulted\";\n        updateDeleteButton();\n        logAuditEvent(\"style\", \"Deleted theme\");\n      }\n      function rebuildThemeDropdown() {\n        const group = document.getElementById(\"my-themes-group\");\n        if (!group) return;\n        const select = document.getElementById(\"theme-preset\");\n        const currentValue = select ? select.value : \"\";\n        group.innerHTML = \"\";\n        savedStyleSets.forEach(function(s) {\n          const opt = document.createElement(\"option\");\n          opt.value = s.id;\n          opt.textContent = s.name;\n          group.appendChild(opt);\n        });\n        if (select && currentValue) {\n          select.value = currentValue;\n        }\n      }\n      function updateDeleteButton() {\n        const select = document.getElementById(\"theme-preset\");\n        const btn = document.getElementById(\"delete-theme-btn\");\n        if (!btn) return;\n\t\tbtn.style.display = \"block\";\n\t\tbtn.disabled = !select.value.startsWith(\"mytheme-\");\n      }\n      function displayTabs() {\n        const listEl = document.getElementById(\"tabs-list\");\n        if (!listEl) return;\n        listEl.innerHTML = documentTabs.map((tab, index) => {\n          const nodeCount = Object.keys(tab.nodes).length;\n          const edgeCount = tab.edges.list.length;\n          const isActive = index === currentTabIndex;\n          const tabMode = tab.pageState?.mappingMode || (isActive ? PAGE_STATE.mappingMode : 'network') || 'network';\n          const modeLabel = LANG.modes[tabMode]?.name || tabMode;\n          return `\n            <div class=\"tab-item ${isActive ? 'active' : ''}\" onclick=\"switchTab(${index})\">\n              <div class=\"tab-name\">${escapeHtml(tab.name)}</div>\n              <div class=\"tab-stats\">${t(\"ui.stats.nodesConnections\", { nodeCount, edgeCount })} · ${modeLabel}</div>\n              <div class=\"tab-actions\">\n                <button class=\"btn-cancel\" onclick=\"event.stopPropagation(); renameTab(${index})\" data-lang=\"tooltips.renameTab\" data-lang-attr=\"title\">✏️</button>\n                ${documentTabs.length > 1 ? '<button class=\"btn-cancel\" onclick=\"event.stopPropagation(); deleteTab(' + index + ')\" data-lang=\"tooltips.deleteTab\" data-lang-attr=\"title\">🗑️</button>' : ''}\n              </div>\n            </div>\n          `;\n        }).join('');\n      }\n      function logAuditEvent(type, description, details = {}) {\n        const event = {\n          timestamp: Date.now(),\n          type,\n          description,\n          details,\n          tab: documentTabs[currentTabIndex]?.name || \"Main\"\n        };\n        auditLog.unshift(event);\n        if (auditLog.length > MAX_AUDIT_ENTRIES) {\n          auditLog = auditLog.slice(0, MAX_AUDIT_ENTRIES);\n        }\n        try {\n          localStorage.setItem(AUDIT_STORAGE_KEY, JSON.stringify(auditLog));\n        } catch (e) {\n          console.warn(\"Failed to save audit log:\", e);\n        }\n      }\n      function loadAuditLog() {\n        let embeddedLog = [];\n        let localLog = [];\n        try {\n          const stateScript = document.getElementById(\"topology-state\");\n          if (stateScript) {\n            const stateData = JSON.parse(stateScript.textContent);\n            if (stateData.auditLog && Array.isArray(stateData.auditLog)) {\n              embeddedLog = stateData.auditLog;\n            }\n          }\n        } catch (e) {\n          console.warn(\"Failed to load embedded audit log:\", e);\n        }\n        try {\n          const stored = localStorage.getItem(AUDIT_STORAGE_KEY);\n          if (stored) {\n            localLog = JSON.parse(stored);\n          }\n        } catch (e) {\n          console.warn(\"Failed to load localStorage audit log:\", e);\n        }\n        const merged = [...embeddedLog, ...localLog];\n        const seen = new Set();\n        auditLog = merged.filter(entry => {\n          const key = entry.timestamp + entry.description;\n          if (seen.has(key)) return false;\n          seen.add(key);\n          return true;\n        }).sort((a, b) => b.timestamp - a.timestamp);\n        if (auditLog.length > MAX_AUDIT_ENTRIES) {\n          auditLog = auditLog.slice(0, MAX_AUDIT_ENTRIES);\n        }\n        try {\n          localStorage.setItem(AUDIT_STORAGE_KEY, JSON.stringify(auditLog));\n        } catch (e) {\n          console.warn(\"Failed to sync audit log:\", e);\n        }\n      }\n      function displayAuditLog(filter = \"all\") {\n        const listEl = document.getElementById(\"audit-log-list\");\n        if (!listEl) return;\n        const filtered = filter === \"all\" ? auditLog : auditLog.filter(e => e.type === filter);\n        if (filtered.length === 0) {\n          listEl.innerHTML = '<div style=\"text-align: center; padding: 40px; color: var(--text-soft);\">' + t(\"emptyStates.noAuditEntries\") + '</div>';\n          return;\n        }\n        listEl.innerHTML = filtered.map(event => {\n          const date = new Date(event.timestamp);\n          const timeStr = date.toLocaleString();\n          return `\n            <div class=\"audit-entry ${escapeHtml(event.type)}\">\n              <div class=\"time\">[${escapeHtml(timeStr)}] ${escapeHtml(event.tab)}</div>\n              <div class=\"action\">[${escapeHtml(event.type.toUpperCase())}] ${escapeHtml(event.description)}</div>\n            </div>\n          `;\n        }).join('');\n      }\n      async function clearAuditLog() {\n        if (!await showConfirm(t(\"dialogs.clearAuditLog\"))) return;\n        auditLog = [];\n        localStorage.removeItem(AUDIT_STORAGE_KEY);\n        displayAuditLog();\n      }\n      function exportAuditLog() {\n        if (auditLog.length === 0) {\n          showAlert(t(\"dialogs.noAuditEntriesToExport\"));\n          return;\n        }\n        const csv = [\n          [\"Timestamp\", \"Tab\", \"Type\", \"Description\"],\n          ...auditLog.map(e => [\n            new Date(e.timestamp).toISOString(),\n            e.tab,\n            e.type,\n            e.description\n          ])\n        ].map(row => row.map(cell => `\"${cell}\"`).join(\",\")).join(\"\\n\");\n        const blob = new Blob([csv], { type: \"text/csv\" });\n        const url = URL.createObjectURL(blob);\n        const a = document.createElement(\"a\");\n        a.href = url;\n        a.download = `audit-log-${Date.now()}.csv`;\n        document.body.appendChild(a);\n        a.click();\n        document.body.removeChild(a);\n        URL.revokeObjectURL(url);\n      }\n      let currentSecretName = null;\n      function createNewSecret() {\n        const nameInput = document.getElementById(\"new-secret-name\");\n        const name = nameInput.value.trim();\n        if (!name) {\n          showAlert(t(\"dialogs.enterNoteName\"));\n          return;\n        }\n        if (encryptedSections[name]) {\n          showAlert(t(\"dialogs.noteAlreadyExists\"));\n          return;\n        }\n        currentSecretName = name;\n        encryptedSections[name] = { encrypted: false, data: \"\" };\n        nameInput.value = \"\";\n        document.getElementById(\"secret-editor-title\").textContent = `New note: ${name}`;\n        document.getElementById(\"secret-editor-content\").value = \"\";\n        document.getElementById(\"secrets-modal\").classList.remove(\"active\");\n        document.getElementById(\"secret-editor-modal\").classList.add(\"active\");\n        displaySecrets();\n      }\n      async function editSecret(name) {\n        currentSecretName = name;\n        const section = encryptedSections[name];\n        if (section.encrypted) {\n          const password = await showPrompt(t(\"dialogs.enterDecryptPasswordFor\", { name: name }));\n          if (!password) return;\n          try {\n            const decrypted = await decryptData(section.data, password);\n            document.getElementById(\"secret-editor-title\").textContent = `Edit Secret: ${name}`;\n            document.getElementById(\"secret-editor-content\").innerHTML = decrypted;\n            document.getElementById(\"secrets-modal\").classList.remove(\"active\");\n            document.getElementById(\"secret-editor-modal\").classList.add(\"active\");\n          } catch (e) {\n            showAlert(t(\"dialogs.decryptionFailed\"));\n          }\n        } else {\n          document.getElementById(\"secret-editor-title\").textContent = `Edit note: ${name}`;\n          document.getElementById(\"secret-editor-content\").innerHTML = section.data;\n          document.getElementById(\"secrets-modal\").classList.remove(\"active\");\n          document.getElementById(\"secret-editor-modal\").classList.add(\"active\");\n        }\n      }\n      async function saveSecret() {\n        if (!currentSecretName) return;\n        const content = document.getElementById(\"secret-editor-content\").innerHTML;\n        const autoEncrypt = document.getElementById(\"secret-auto-encrypt\").checked;\n        if (autoEncrypt) {\n          const password = await showPrompt(t(\"dialogs.enterEncryptPasswordFor\", { name: currentSecretName }));\n          if (!password) return;\n          const confirmPassword = await showPrompt(t(\"dialogs.confirmPasswordFor\"));\n          if (password !== confirmPassword) {\n            showAlert(t(\"dialogs.passwordsMismatch\"));\n            return;\n          }\n          try {\n            const encrypted = await encryptData(content, password);\n            encryptedSections[currentSecretName] = { encrypted: true, data: encrypted };\n          } catch (e) {\n            showAlert(t(\"dialogs.encryptionFailedError\", { error: e.message }));\n            return;\n          }\n        } else {\n          encryptedSections[currentSecretName] = { encrypted: false, data: content };\n        }\n        closeSecretEditor();\n        displaySecrets();\n        logAuditEvent(\"secret\", `Saved note section: ${currentSecretName}`);\n      }\n      function closeSecretEditor() {\n        document.getElementById(\"secret-editor-modal\").classList.remove(\"active\");\n        document.getElementById(\"secrets-modal\").classList.add(\"active\");\n        currentSecretName = null;\n      }\n      async function deleteSecret(name) {\n        if (!await showConfirm(t(\"dialogs.deleteNote\", { name: name }))) return;\n        delete encryptedSections[name];\n        displaySecrets();\n        logAuditEvent(\"secret\", `Deleted note: ${name}`);\n      }\n      function getAllNotesAggregated() {\n        const allNotes = [];\n\n        Object.keys(encryptedSections).forEach(name => {\n          const section = encryptedSections[name];\n          allNotes.push({\n            type: \"global\",\n            name: name,\n            content: section.encrypted ? \"[Encrypted]\" : section.content,\n            encrypted: section.encrypted,\n            elementId: null,\n            elementName: null\n          });\n        });\n\n        Object.entries(NODE_DATA).forEach(([nodeId, node]) => {\n          (node.notes || []).forEach((note, idx) => {\n            const noteContent = typeof note === \"string\" ? note : (note.content || \"\");\n            allNotes.push({\n              type: \"node\",\n              name: `Note ${idx + 1}`,\n              content: noteContent,\n              encrypted: false,\n              elementId: nodeId,\n              elementName: node.name,\n              noteIndex: idx\n            });\n          });\n        });\n\n        (EDGE_DATA.list || []).forEach(edge => {\n          (edge.notes || []).forEach((note, idx) => {\n            const noteContent = typeof note === \"string\" ? note : (note.content || \"\");\n            const fromName = NODE_DATA[edge.from]?.name || edge.from;\n            const toName = NODE_DATA[edge.to]?.name || edge.to;\n            allNotes.push({\n              type: \"edge\",\n              name: `Note ${idx + 1}`,\n              content: noteContent,\n              encrypted: false,\n              elementId: edge.id,\n              elementName: `${fromName} ↔ ${toName}`,\n              noteIndex: idx\n            });\n          });\n        });\n\n        (RECT_DATA.list || []).forEach(rect => {\n          (rect.notes || []).forEach((note, idx) => {\n            const noteContent = typeof note === \"string\" ? note : (note.content || \"\");\n            allNotes.push({\n              type: \"rect\",\n              name: `Note ${idx + 1}`,\n              content: noteContent,\n              encrypted: false,\n              elementId: rect.id,\n              elementName: `Zone`,\n              noteIndex: idx\n            });\n          });\n        });\n\n        (IMAGE_DATA.list || []).forEach(img => {\n          (img.notes || []).forEach((note, idx) => {\n            const noteContent = typeof note === \"string\" ? note : (note.content || \"\");\n            allNotes.push({\n              type: \"image\",\n              name: `Note ${idx + 1}`,\n              content: noteContent,\n              encrypted: false,\n              elementId: img.id,\n              elementName: img.name || \"Image\",\n              noteIndex: idx\n            });\n          });\n        });\n        return allNotes;\n      }\n      function displaySecrets() {\n        const listEl = document.getElementById(\"secrets-list\");\n        if (!listEl) return;\n        const searchInput = document.getElementById(\"notes-hub-search\");\n        const filterSelect = document.getElementById(\"notes-hub-filter\");\n        const searchTerm = (searchInput?.value || \"\").toLowerCase();\n        const filterType = filterSelect?.value || \"all\";\n        let allNotes = getAllNotesAggregated();\n\n        if (filterType !== \"all\") {\n          allNotes = allNotes.filter(n => n.type === filterType);\n        }\n\n        if (searchTerm) {\n          allNotes = allNotes.filter(n =>\n            n.name.toLowerCase().includes(searchTerm) ||\n            n.content.toLowerCase().includes(searchTerm) ||\n            (n.elementName && n.elementName.toLowerCase().includes(searchTerm))\n          );\n        }\n        if (allNotes.length === 0) {\n          listEl.innerHTML = '<div style=\"text-align: center; padding: 40px; color: var(--text-soft);\">' + t(\"emptyStates.noNotesYet\") + '</div>';\n          return;\n        }\n        listEl.innerHTML = allNotes.map((note, i) => {\n          const typeLabels = { global: \"Global\", node: \"Node\", edge: \"Line\", rect: \"Zone\", image: \"Image\" };\n          const typeLabel = typeLabels[note.type] || note.type;\n          if (note.type === \"global\") {\n            const status = note.encrypted ? \"🔒\" : \"🔓\";\n            return `\n              <div class=\"secret-item note-type-${note.type}\">\n                <div class=\"secret-name\">${escapeHtml(note.name)}</div>\n                <div class=\"secret-status\">${status} ${typeLabel}</div>\n                <div class=\"tab-actions\">\n                  <button class=\"btn-cancel\" onclick=\"viewGlobalNote('${escapeHtml(note.name)}')\" title=\"View\">👁️</button>\n                  <button class=\"btn-cancel\" onclick=\"editSecret('${escapeHtml(note.name)}')\" title=\"Edit\">✏️</button>\n                  <button class=\"btn-cancel\" onclick=\"deleteSecret('${escapeHtml(note.name)}')\" title=\"Delete\">🗑️</button>\n                </div>\n              </div>\n            `;\n          } else {\n            const preview = note.content.replace(/<[^>]*>/g, '').substring(0, 60) + (note.content.length > 60 ? '...' : '');\n            return `\n              <div class=\"secret-item note-type-${note.type}\">\n                <div class=\"note-source\" onclick=\"navigateToNoteSource('${note.type}', '${escapeHtml(note.elementId)}')\">${typeLabel}: ${escapeHtml(note.elementName)}</div>\n                <div class=\"note-preview\">${escapeHtml(preview)}</div>\n                <div class=\"tab-actions\">\n                  <button class=\"btn-cancel\" onclick=\"viewElementNote('${note.type}', '${escapeHtml(note.elementId)}', ${note.noteIndex})\" title=\"View\">👁️</button>\n                  <button class=\"btn-cancel\" onclick=\"editElementNote('${note.type}', '${escapeHtml(note.elementId)}', ${note.noteIndex})\" title=\"Edit\">✏️</button>\n                </div>\n              </div>\n            `;\n          }\n        }).join('');\n      }\n      function navigateToNoteSource(type, elementId) {\n        document.getElementById(\"secrets-modal\").classList.remove(\"active\");\n        if (type === \"node\") {\n          claimTheImmortal(elementId);\n          focusOnSelected();\n        } else if (type === \"edge\") {\n          selectTheConnection(elementId);\n        } else if (type === \"rect\") {\n          selectTheRect(elementId);\n        } else if (type === \"image\") {\n          showImagePanel(elementId);\n        }\n      }\n      function viewElementNote(type, elementId, noteIndex) {\n        let notes = [];\n        if (type === \"node\" && NODE_DATA[elementId]) {\n          notes = NODE_DATA[elementId].notes || [];\n        } else if (type === \"edge\") {\n          const edge = EDGE_DATA.list.find(e => e.id === elementId);\n          if (edge) notes = edge.notes || [];\n        } else if (type === \"rect\") {\n          const rect = RECT_DATA.list.find(r => r.id === elementId);\n          if (rect) notes = rect.notes || [];\n        } else if (type === \"image\") {\n          const img = IMAGE_DATA.list.find(i => i.id === elementId);\n          if (img) notes = img.notes || [];\n        }\n        if (notes[noteIndex] !== undefined) {\n          const noteContent = typeof notes[noteIndex] === \"string\" ? notes[noteIndex] : (notes[noteIndex].content || \"\");\n          showNoteViewer(noteContent);\n        }\n      }\n      function viewGlobalNote(name) {\n        const section = encryptedSections[name];\n        if (!section) return;\n        if (section.encrypted) {\n          const pwd = prompt(t(\"notes.enterPassword\"));\n          if (!pwd) return;\n          try {\n            const decrypted = CryptoJS.AES.decrypt(section.data, pwd).toString(CryptoJS.enc.Utf8);\n            if (!decrypted) { alert(t(\"notes.wrongPassword\")); return; }\n            showNoteViewer(decrypted);\n          } catch { alert(t(\"notes.wrongPassword\")); }\n        } else {\n          showNoteViewer(section.data);\n        }\n      }\n      function editElementNote(type, elementId, noteIndex) {\n        let notes = [];\n        let updateFn = null;\n        if (type === \"node\" && NODE_DATA[elementId]) {\n          notes = NODE_DATA[elementId].notes || [];\n          updateFn = () => { if (currentNodeId === elementId) renderNotesFeed(notes, \"node-notes\", \"node\", elementId); };\n        } else if (type === \"edge\") {\n          const edge = EDGE_DATA.list.find(e => e.id === elementId);\n          if (edge) {\n            notes = edge.notes || [];\n            updateFn = () => { if (currentEdgeId === elementId) renderNotesFeed(notes, \"edge-notes\", \"edge\", elementId); };\n          }\n        } else if (type === \"rect\") {\n          const rect = RECT_DATA.list.find(r => r.id === elementId);\n          if (rect) {\n            notes = rect.notes || [];\n            updateFn = () => { if (currentRectId === elementId) renderNotesFeed(notes, \"rect-notes\", \"rect\", elementId); };\n          }\n        } else if (type === \"image\") {\n          const img = IMAGE_DATA.list.find(i => i.id === elementId);\n          if (img) {\n            notes = img.notes || [];\n            updateFn = () => { if (currentImageId === elementId) renderNotesFeed(notes, \"image-notes\", \"image\", elementId); };\n          }\n        }\n        if (notes[noteIndex] !== undefined) {\n          const noteContent = typeof notes[noteIndex] === \"string\" ? notes[noteIndex] : (notes[noteIndex].content || \"\");\n          showNoteEditor(t(\"noteEditor.editNote\"), noteContent, (newContent) => {\n            pushUndo(\"edit note\");\n            if (typeof notes[noteIndex] === \"string\") {\n              notes[noteIndex] = newContent;\n            } else {\n              notes[noteIndex].content = newContent;\n            }\n            displaySecrets();\n            if (updateFn) updateFn();\n          }, noteIndex);\n        }\n      }\n       const clearAllBtn = document.getElementById(\"clear-all-btn\");\n       const clearAllModal = document.getElementById(\"clear-all-modal\");\n       const clearAllCancel = document.getElementById(\"clear-all-cancel\");\n       const clearAllConfirm = document.getElementById(\"clear-all-confirm\");\n       clearAllBtn.addEventListener(\"click\", () => {\n        clearAllModal.classList.add(\"active\");\n       });\n       clearAllCancel.addEventListener(\"click\", () => {\n        clearAllModal.classList.remove(\"active\");\n       });\n       clearAllModal.addEventListener(\"click\", (e) => {\n        if (e.target === clearAllModal) {\n         clearAllModal.classList.remove(\"active\");\n        }\n       });\n       clearAllConfirm.addEventListener(\"click\", () => {\n        NODE_DATA = {};\n        EDGE_DATA = { list: [] };\n        EDGE_LEGEND = {};\n        RECT_DATA = { list: [] };\n        TEXT_DATA = { list: [] };\n        IMAGE_DATA = { list: [] };\n        savedPositions = {};\n        savedSizes = {};\n        savedStyles = {};\n        undoStack = [];\n        redoStack = [];\n        updateUndoButtons();\n        auditLog = [];\n        localStorage.removeItem(AUDIT_STORAGE_KEY);\n        rollbackVersions = [];\n        currentRollbackIndex = -1;\n        localStorage.removeItem(ROLLBACK_STORAGE_KEY);\n        clipboard = null;\n        selectedNodes.clear();\n        selectedEdges.clear();\n        selectedRects.clear();\n        selectedTexts.clear();\n        documentTabs = [{\n          id: \"main\",\n          name: \"Main Topology\",\n          nodes: {},\n          edges: { list: [] },\n          positions: {},\n          sizes: {},\n          styles: {},\n          legend: {},\n          rects: { list: [] },\n          texts: { list: [] },\n          images: { list: [] },\n          pageState: null\n        }];\n        currentTabIndex = 0;\n        displayTabs();\n        clearAutosave();\n        try {\n          Object.keys(localStorage).forEach(key => {\n            if (key.startsWith(\"theonefile\") || key.startsWith(\"theOneFile\")) {\n              localStorage.removeItem(key);\n            }\n          });\n        } catch(e) {}\n        CUSTOM_LANG = null;\n        LANG = JSON.parse(JSON.stringify(DEFAULT_LANG));\n        applyLanguage();\n        const addLineSelect = document.getElementById(\"add-line-select\");\n        if (addLineSelect) addLineSelect.innerHTML = \"\";\n        const edgeLegendContent = document.getElementById(\"edge-legend-content\");\n        if (edgeLegendContent) edgeLegendContent.innerHTML = \"\";\n        clearAllModal.classList.remove(\"active\");\n        forgeTheTopology();\n        document.getElementById(\"node-panel\").style.display = \"none\";\n        document.getElementById(\"edge-panel\").style.display = \"none\";\n        document.getElementById(\"text-panel\").style.display = \"none\";\n        document.getElementById(\"rect-panel\").style.display = \"none\";\n        document.getElementById(\"image-panel\").style.display = \"none\";\n        document.getElementById(\"topology-toolbar\").style.display = \"none\";\n        currentNodeId = null;\n        currentEdgeId = null;\n        currentTextId = null;\n        currentRectId = null;\n        currentImageId = null;\n        if (typeof displayRollbackVersions === \"function\") displayRollbackVersions();\n        if (typeof displayAuditLog === \"function\") displayAuditLog();\n        if (typeof forgeTheLegend === \"function\") forgeTheLegend();\n        if (typeof updateZoneLegend === \"function\") updateZoneLegend();\n      });\n       (function addDeleteNodeButton() {\n       const nodePanel = document.getElementById(\"node-panel\");\n       if (!nodePanel) return;\n       let deleteBtn = document.getElementById(\"delete-node-btn\");\n       if (!deleteBtn) {\n        deleteBtn = document.createElement(\"button\");\n        deleteBtn.id = \"delete-node-btn\";\n        deleteBtn.textContent = t(\"ui.buttons.deleteNode\");\n        deleteBtn.style.cssText = \"margin-top:15px; padding:10px 16px; background:var(--danger); color:#fff; border:none; border-radius:6px; cursor:pointer; font-size:clamp(14px,1.6vw,18px); font-weight:600; width:100%;\";\n        nodePanel.appendChild(deleteBtn);\n       }\n       deleteBtn.addEventListener(\"click\", () => {\n        if (!currentNodeId) return;\n        const nodeData = NODE_DATA[currentNodeId];\n        let confirmMessage;\n        let nodesInsideRack = [];\n        if (nodeData?.isRack) {\n         nodesInsideRack = Object.entries(NODE_DATA)\n          .filter(([id, n]) => n.assignedRack === currentNodeId)\n          .map(([id, n]) => n.name || id);\n         if (nodesInsideRack.length > 0) {\n          confirmMessage = `Delete rack \"${nodeData.name}\"?\\n\\nThis will also delete ${nodesInsideRack.length} node(s) inside:\\n• ${nodesInsideRack.join('\\n• ')}`;\n         } else {\n          confirmMessage = `Delete rack \"${nodeData.name}\"? (empty rack)`;\n         }\n        } else {\n         confirmMessage = `Delete node \"${nodeData?.name || currentNodeId}\" and all its connections?`;\n        }\n        challengeTheImmortal(confirmMessage, () => {\n         pushUndo(\"delete node\");\n         if (nodeData?.isRack) {\n          Object.keys(NODE_DATA).forEach(id => {\n           if (NODE_DATA[id]?.assignedRack === currentNodeId) {\n            EDGE_DATA.list = EDGE_DATA.list.filter(e => e.from !== id && e.to !== id);\n            delete NODE_DATA[id];\n            delete savedPositions[id];\n            delete savedSizes[id];\n            delete savedStyles[id];\n           }\n          });\n         }\n         EDGE_DATA.list = EDGE_DATA.list.filter(e => e.from !== currentNodeId && e.to !== currentNodeId);\n         delete NODE_DATA[currentNodeId];\n         delete savedPositions[currentNodeId];\n         delete savedSizes[currentNodeId];\n         delete savedStyles[currentNodeId];\n         currentNodeId = null;\n         currentEdgeId = null;\n         forgeTheTopology();\n         const remainingNodes = Object.keys(NODE_DATA);\n         if (remainingNodes.length > 0) {\n          claimTheImmortal(remainingNodes[0]);\n         } else {\n          document.getElementById(\"node-panel\").style.display = \"none\";\n          document.getElementById(\"edge-panel\").style.display = \"none\";\n          document.getElementById(\"topology-toolbar\").style.display = \"none\";\n         }\n        });\n       });\n      })();\n       function screenshotCanvas() {\n        const svg = document.getElementById(\"map\");\n        const svgClone = svg.cloneNode(true);\n        const viewBox = svg.getAttribute(\"viewBox\").split(\" \").map(Number);\n        const [x, y, width, height] = viewBox;\n        svgClone.setAttribute(\"width\", width);\n        svgClone.setAttribute(\"height\", height);\n        svgClone.setAttribute(\"viewBox\", `${x} ${y} ${width} ${height}`);\n        const wrapper = document.createElement(\"div\");\n        wrapper.style.position = \"absolute\";\n        wrapper.style.left = \"-9999px\";\n        wrapper.appendChild(svgClone);\n        document.body.appendChild(wrapper);\n        function inlineStyles(original, clone) {\n         const elements = original.querySelectorAll(\"*\");\n         const clonedElements = clone.querySelectorAll(\"*\");\n         const rootStyles = getComputedStyle(document.documentElement);\n         const bgColor = rootStyles.getPropertyValue(\"--bg\").trim() || \"#050608\";\n         const bgRect = document.createElementNS(\"http://www.w3.org/2000/svg\", \"rect\");\n         bgRect.setAttribute(\"x\", x);\n         bgRect.setAttribute(\"y\", y);\n         bgRect.setAttribute(\"width\", width);\n         bgRect.setAttribute(\"height\", height);\n         bgRect.setAttribute(\"fill\", bgColor);\n         clone.insertBefore(bgRect, clone.firstChild);\n         elements.forEach((el, index) => {\n          const clonedEl = clonedElements[index];\n          if (!clonedEl) return;\n          const computedStyle = getComputedStyle(el);\n          const svgProps = [\"fill\", \"stroke\", \"stroke-width\", \"stroke-dasharray\", \"stroke-linecap\", \"stroke-linejoin\", \"opacity\", \"font-family\", \"font-size\", \"font-weight\", \"font-style\", \"text-anchor\", \"dominant-baseline\", \"marker-start\", \"marker-end\", ];\n          svgProps.forEach((prop) => {\n           const value = computedStyle.getPropertyValue(prop);\n           if (value && value !== \"none\" && value !== \"normal\") {\n            clonedEl.style[prop] = value;\n           }\n          });\n          clonedEl.removeAttribute(\"class\");\n         });\n        }\n        inlineStyles(svg, svgClone);\n        const svgData = new XMLSerializer().serializeToString(svgClone);\n        document.body.removeChild(wrapper);\n        const svgBlob = new Blob([svgData], {\n         type: \"image/svg+xml;charset=utf-8\",\n        });\n        const url = URL.createObjectURL(svgBlob);\n        const img = new Image();\n        img.onload = function() {\n         const canvas = document.createElement(\"canvas\");\n         canvas.width = width;\n         canvas.height = height;\n         const ctx = canvas.getContext(\"2d\");\n         ctx.drawImage(img, 0, 0);\n         canvas.toBlob(function(blob) {\n          const link = document.createElement(\"a\");\n          const timestamp = new Date().toISOString().slice(0, 10);\n          link.download = `topology-${timestamp}.png`;\n          link.href = URL.createObjectURL(blob);\n          link.click();\n          URL.revokeObjectURL(url);\n          URL.revokeObjectURL(link.href);\n         }, \"image/png\");\n        };\n        img.onerror = function() {\n         console.error(\"Failed to load SVG image\");\n         showAlert(t(\"dialogs.screenshotFailed\"));\n         URL.revokeObjectURL(url);\n        };\n        img.src = url;\n       }\n       function exportCanvasSVG() {\n        const svg = document.getElementById(\"map\");\n        const svgClone = svg.cloneNode(true);\n        const viewBox = svg.getAttribute(\"viewBox\").split(\" \").map(Number);\n        const [x, y, width, height] = viewBox;\n        svgClone.setAttribute(\"width\", width);\n        svgClone.setAttribute(\"height\", height);\n        const rootStyles = getComputedStyle(document.documentElement);\n        const bgColor = rootStyles.getPropertyValue(\"--bg\").trim() || \"#050608\";\n        const bgRect = document.createElementNS(\"http://www.w3.org/2000/svg\", \"rect\");\n        bgRect.setAttribute(\"x\", x);\n        bgRect.setAttribute(\"y\", y);\n        bgRect.setAttribute(\"width\", width);\n        bgRect.setAttribute(\"height\", height);\n        bgRect.setAttribute(\"fill\", bgColor);\n        svgClone.insertBefore(bgRect, svgClone.firstChild);\n        const wrapper = document.createElement(\"div\");\n        wrapper.style.position = \"absolute\";\n        wrapper.style.left = \"-9999px\";\n        wrapper.appendChild(svgClone);\n        document.body.appendChild(wrapper);\n        const elements = svg.querySelectorAll(\"*\");\n        const clonedElements = svgClone.querySelectorAll(\"*\");\n        elements.forEach((el, index) => {\n         const clonedEl = clonedElements[index];\n         if (!clonedEl) return;\n         const computedStyle = getComputedStyle(el);\n         const svgProps = [\"fill\", \"stroke\", \"stroke-width\", \"stroke-dasharray\", \"stroke-linecap\", \"stroke-linejoin\", \"opacity\", \"font-family\", \"font-size\", \"font-weight\", \"font-style\", \"text-anchor\", \"dominant-baseline\", \"marker-start\", \"marker-end\", ];\n         svgProps.forEach((prop) => {\n          const value = computedStyle.getPropertyValue(prop);\n          if (value && value !== \"none\" && value !== \"normal\") {\n           clonedEl.setAttribute(prop, value);\n          }\n         });\n         clonedEl.removeAttribute(\"class\");\n        });\n        const svgData = new XMLSerializer().serializeToString(svgClone);\n        document.body.removeChild(wrapper);\n        const blob = new Blob([svgData], {\n         type: \"image/svg+xml;charset=utf-8\",\n        });\n        const url = URL.createObjectURL(blob);\n        const link = document.createElement(\"a\");\n        const timestamp = new Date().toISOString().slice(0, 10);\n        link.download = `topology-${timestamp}.svg`;\n        link.href = url;\n        link.click();\n        URL.revokeObjectURL(url);\n       }\n      let resizeTimeout;\n       window.addEventListener('resize', () => {\n       clearTimeout(resizeTimeout);\n       resizeTimeout = setTimeout(() => {\n       updateDrawToolbarVisibility();\n       updateTopologyToolbarVisibility();\n       }, 100);\n      });\ndocument.addEventListener('DOMContentLoaded', () => {\n  loadLanguageFromStorage();\n  applyLanguage();\n  updateCurrentLangDisplay();\n\n  document.querySelectorAll('.dropdown').forEach(dropdown => {\n    const btn = dropdown.querySelector('.dropdown-btn');\n    const menu = dropdown.querySelector('.dropdown-menu');\n    if (!btn || !menu) return;\n    btn.addEventListener('click', (e) => {\n      e.stopPropagation();\n      document.querySelectorAll('.dropdown-menu.open').forEach(m => {\n        if (m !== menu) m.classList.remove('open');\n      });\n      menu.classList.toggle('open');\n    });\n  });\n  document.addEventListener('click', (e) => {\n    if (!e.target.closest('.dropdown')) {\n      document.querySelectorAll('.dropdown-menu.open').forEach(m => m.classList.remove('open'));\n    }\n  });\n  document.querySelectorAll('.dropdown-menu button').forEach(btn => {\n    btn.addEventListener('click', () => {\n      setTimeout(() => {\n        document.querySelectorAll('.dropdown-menu.open').forEach(m => m.classList.remove('open'));\n      }, 100);\n    });\n  });\n  });\nfunction showFormatHelp() {\n  document.getElementById('save-info-modal').classList.add('active');\n  document.querySelectorAll('.help-tab').forEach(t => {\n    t.style.background = 'var(--panel)';\n    t.style.color = 'var(--text-main)';\n  });\n  document.querySelectorAll('.help-tab-content').forEach(c => c.style.display = 'none');\n  const formatsTab = document.querySelector('.help-tab[data-tab=\"formats\"]');\n  if (formatsTab) {\n    formatsTab.style.background = 'var(--accent)';\n    formatsTab.style.color = 'var(--bg)';\n  }\n  document.getElementById('help-tab-formats').style.display = 'block';\n}\nfunction printTopology() {\n  const svg = document.getElementById('map');\n  if (!svg) { window.print(); return; }\n  const originalViewBox = svg.getAttribute('viewBox');\n  let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;\n  let hasContent = false;\n  Object.entries(savedPositions).forEach(([id, pos]) => {\n    const node = NODE_DATA[id];\n    if (!node) return;\n    if (currentView.mode !== 'rack' && node.assignedRack) return;\n    const size = savedSizes[id] || 50;\n    hasContent = true;\n    minX = Math.min(minX, pos.x - size);\n    minY = Math.min(minY, pos.y - size);\n    maxX = Math.max(maxX, pos.x + size);\n    maxY = Math.max(maxY, pos.y + size);\n  });\n  RECT_DATA.list.forEach(rect => {\n    hasContent = true;\n    minX = Math.min(minX, rect.x);\n    minY = Math.min(minY, rect.y);\n    maxX = Math.max(maxX, rect.x + rect.width);\n    maxY = Math.max(maxY, rect.y + rect.height);\n  });\n  TEXT_DATA.list.forEach(text => {\n    hasContent = true;\n    minX = Math.min(minX, text.x - 100);\n    minY = Math.min(minY, text.y - 50);\n    maxX = Math.max(maxX, text.x + 300);\n    maxY = Math.max(maxY, text.y + 50);\n  });\n  EDGE_DATA.list.forEach(edge => {\n    if (edge.points && edge.points.length > 0) {\n      edge.points.forEach(p => {\n        hasContent = true;\n        minX = Math.min(minX, p.x - 10);\n        minY = Math.min(minY, p.y - 10);\n        maxX = Math.max(maxX, p.x + 10);\n        maxY = Math.max(maxY, p.y + 10);\n      });\n    }\n  });\n  if (!hasContent) { window.print(); return; }\n  const padding = 100;\n  minX -= padding; minY -= padding; maxX += padding; maxY += padding;\n  const width = maxX - minX;\n  const height = maxY - minY;\n  const grid = document.getElementById('canvas-grid');\n  const gridDisplay = grid ? grid.style.display : '';\n  if (grid) grid.style.display = 'none';\n  svg.setAttribute('viewBox', `${minX} ${minY} ${width} ${height}`);\n  const originalWidth = svg.style.width;\n  const originalHeight = svg.style.height;\n  svg.style.width = '100%';\n  svg.style.height = '100%';\n  setTimeout(() => {\n    window.print();\n    setTimeout(() => {\n      svg.setAttribute('viewBox', originalViewBox);\n      svg.style.width = originalWidth;\n      svg.style.height = originalHeight;\n      if (grid) grid.style.display = gridDisplay;\n    }, 500);\n  }, 100);\n}\nfunction printTopologyColor(bgOverride) {\n  const svg = document.getElementById(\"map\");\n  if (!svg) { window.print(); return; }\n  const svgClone = svg.cloneNode(true);\n  const viewBox = svg.getAttribute(\"viewBox\").split(\" \").map(Number);\n  const [x, y, width, height] = viewBox;\n  svgClone.setAttribute(\"width\", width);\n  svgClone.setAttribute(\"height\", height);\n  svgClone.setAttribute(\"viewBox\", `${x} ${y} ${width} ${height}`);\n  const wrapper = document.createElement(\"div\");\n  wrapper.style.position = \"absolute\";\n  wrapper.style.left = \"-9999px\";\n  wrapper.appendChild(svgClone);\n  document.body.appendChild(wrapper);\n  function inlineStyles(original, clone) {\n    const elements = original.querySelectorAll(\"*\");\n    const clonedElements = clone.querySelectorAll(\"*\");\n    const rootStyles = getComputedStyle(document.documentElement);\n    const bgColor = bgOverride || rootStyles.getPropertyValue(\"--bg\").trim() || \"#050608\";\n    const bgRect = document.createElementNS(\"http://www.w3.org/2000/svg\", \"rect\");\n    bgRect.setAttribute(\"x\", x);\n    bgRect.setAttribute(\"y\", y);\n    bgRect.setAttribute(\"width\", width);\n    bgRect.setAttribute(\"height\", height);\n    bgRect.setAttribute(\"fill\", bgColor);\n    clone.insertBefore(bgRect, clone.firstChild);\n    elements.forEach((el, index) => {\n      const clonedEl = clonedElements[index];\n      if (!clonedEl) return;\n      const computedStyle = getComputedStyle(el);\n      const svgProps = [\"fill\", \"stroke\", \"stroke-width\", \"stroke-dasharray\", \"stroke-linecap\", \"stroke-linejoin\", \"opacity\", \"font-family\", \"font-size\", \"font-weight\", \"font-style\", \"text-anchor\", \"dominant-baseline\", \"marker-start\", \"marker-end\"];\n      svgProps.forEach((prop) => {\n        const value = computedStyle.getPropertyValue(prop);\n        if (value && value !== \"none\" && value !== \"normal\") {\n          clonedEl.style[prop] = value;\n        }\n      });\n      clonedEl.removeAttribute(\"class\");\n    });\n  }\n  inlineStyles(svg, svgClone);\n  const svgData = new XMLSerializer().serializeToString(svgClone);\n  document.body.removeChild(wrapper);\n  const svgBlob = new Blob([svgData], { type: \"image/svg+xml;charset=utf-8\" });\n  const url = URL.createObjectURL(svgBlob);\n  const img = new Image();\n  img.onload = function() {\n    const canvas = document.createElement(\"canvas\");\n    canvas.width = width;\n    canvas.height = height;\n    const ctx = canvas.getContext(\"2d\");\n    ctx.drawImage(img, 0, 0);\n    const dataUrl = canvas.toDataURL(\"image/png\");\n    URL.revokeObjectURL(url);\n    const printWindow = window.open(\"\", \"_blank\");\n    printWindow.document.write(`\n      <html><head><title>Print Topology</title>\n      <style>@media print { @page { size: landscape; margin: 0; } body { margin: 0; } img { width: 100vw; height: 100vh; object-fit: contain; } }</style>\n      </head><body><img src=\"${dataUrl}\" onload=\"window.print(); window.close();\"></body></html>\n    `);\n    printWindow.document.close();\n  };\n  img.onerror = function() {\n    console.error(\"Failed to load SVG image for print\");\n    showAlert(t(\"dialogs.screenshotFailed\"));\n    URL.revokeObjectURL(url);\n  };\n  img.src = url;\n}\nfunction exportJSONFile() {\n  const data = captureTheQuickening();\n  const jsonStr = JSON.stringify(data, null, 2);\n  const blob = new Blob([jsonStr], { type: \"application/json\" });\n  const url = URL.createObjectURL(blob);\n  const a = document.createElement(\"a\");\n  a.href = url;\n  const safeTitle = (PAGE_STATE.title || \"network-topology\").toLowerCase().replace(/[^a-z0-9\\-]+/g, \"-\");\n  a.download = `${safeTitle}.json`;\n  a.click();\n  URL.revokeObjectURL(url);\n  logAuditEvent(\"export\", `Exported JSON: ${a.download}`);\n}\nfunction exportCSV() {\n  const safeTitle = (PAGE_STATE.title || 'topology').toLowerCase().replace(/[^a-z0-9]+/g, '-');\n  const timestamp = new Date().toISOString();\n  const config = captureTheQuickening();\n  let csv = `#THEONEFILE_CONFIG:${JSON.stringify(config)}\\n`;\n  csv += `#\\n# ${PAGE_STATE.title || 'Network Topology'} - Node List\\n`;\n  csv += `# Exported from The One File on ${timestamp}\\n`;\n  const headers = ['name','ip','role','shape','tags','layer','mac','rackUnit','uHeight','assignedRack','rackCapacity','isRack','locked','groupId','x','y','size','notes','styles'];\n  csv += headers.join(',') + '\\n';\n  Object.entries(NODE_DATA).forEach(([id, node]) => {\n    const pos = savedPositions[id] || { x: 0, y: 0 };\n    const size = savedSizes[id] || 50;\n    const styles = savedStyles[id] ? JSON.stringify(savedStyles[id]) : '';\n    const row = [\n      csvEscape(node.name || ''), csvEscape(node.ip || ''), csvEscape(node.role || ''),\n      node.shape || 'circle', csvEscape((node.tags || []).join(';')), node.layer || 'layer1',\n      csvEscape(node.mac || ''), csvEscape(node.rackUnit || ''), node.uHeight || '1',\n      node.assignedRack || '', node.rackCapacity || '', node.isRack ? 'true' : 'false',\n      node.locked ? 'true' : 'false', node.groupId || '', Math.round(pos.x), Math.round(pos.y),\n      size, csvEscape((node.notes || []).join('|')), csvEscape(styles)\n    ];\n    csv += row.join(',') + '\\n';\n  });\n  const blob = new Blob([csv], { 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 = `${safeTitle}.csv`;\n  a.click();\n  URL.revokeObjectURL(url);\n  logAuditEvent('export', `Exported CSV: ${a.download}`);\n}\nfunction csvEscape(val) {\n  if (val === null || val === undefined) return '';\n  const str = String(val);\n  if (str.includes(',') || str.includes('\"') || str.includes('\\n') || str.includes('\\r')) {\n    return '\"' + str.replace(/\"/g, '\"\"') + '\"';\n  }\n  return str;\n}\ndocument.getElementById('import-csv-file')?.addEventListener('change', async (e) => {\n  const file = e.target.files[0];\n  if (!file) return;\n  e.target.value = '';\n  try {\n    const text = await file.text();\n    const lines = text.split(/\\r?\\n/);\n    let config = null;\n    let dataLines = [];\n    let headers = null;\n    for (const line of lines) {\n      const trimmed = line.trim();\n      if (!trimmed) continue;\n      if (trimmed.startsWith('#THEONEFILE_CONFIG:')) {\n        try { config = JSON.parse(trimmed.substring(19)); } catch (err) { console.warn('Failed to parse CSV config:', err); }\n        continue;\n      }\n      if (trimmed.startsWith('#')) continue;\n      if (!headers) { headers = parseCSVLine(trimmed).map(h => h.toLowerCase().trim()); continue; }\n      dataLines.push(trimmed);\n    }\n    if (!headers || dataLines.length === 0) { showAlert(t(\"dialogs.csvNoDataRows\")); return; }\n    const nameIdx = headers.indexOf('name');\n    if (nameIdx === -1) { showAlert(t(\"dialogs.csvNeedsNameColumn\")); return; }\n    const nodes = dataLines.map(line => {\n      const values = parseCSVLine(line);\n      const node = {};\n      headers.forEach((h, idx) => { node[h] = values[idx] || ''; });\n      return node;\n    });\n    const hasFullBackup = config && config.documentTabs;\n    const hasConfig = config && (config.pageState || config.page);\n    \n    let importMode = 'add';\n\n    if (hasFullBackup) {\n      const choice = await showConfirm(\n        t(\"dialogs.confirmImportCsvFull\", {\n          nodeCount: nodes.length,\n          tabCount: config.documentTabs?.length || 1,\n          edgeCount: config.edgeData?.list?.length || 0,\n          csvNodeCount: nodes.length\n        })\n      );\n      if (choice) {\n        importMode = 'full';\n      }\n    } else {\n      const settingsNote = hasConfig ? t(\"dialogs.csvSettingsRestore\") : t(\"dialogs.csvAddOnly\");\n      if (!await showConfirm(t(\"dialogs.confirmImportCsvAdd\", { count: nodes.length, settingsNote: settingsNote }))) return;\n    }\n    \n    pushUndo('import csv');\n    \n    if (importMode === 'full' && hasFullBackup) {\n      NODE_DATA = config.nodeData || {};\n      EDGE_DATA = config.edgeData || { list: [] };\n      EDGE_LEGEND = config.edgeLegend || {};\n      RECT_DATA = config.rectData || { list: [] };\n      TEXT_DATA = config.textData || { list: [] };\n      savedPositions = config.nodePositions || {};\n      savedSizes = config.nodeSizes || {};\n      savedStyles = config.nodeStyles || {};\n      if (config.page) {\n        PAGE_STATE = Object.assign({}, DEFAULT_PAGE_STATE, config.page);\n        wieldThePower();\n      }\n      if (config.canvas) {\n        canvasState.zoom = config.canvas.zoom || 1;\n        canvasState.panX = config.canvas.panX || 0;\n        canvasState.panY = config.canvas.panY || 0;\n      }\n      if (config.page?.title) {\n        document.title = config.page.title;\n        document.querySelector(\".editable-page-title\").textContent = config.page.title;\n      }\n      if (config.savedStyleSets) {\n        savedStyleSets = config.savedStyleSets;\n      }\n      if (config.documentTabs) {\n        documentTabs = config.documentTabs;\n        currentTabIndex = config.currentTabIndex || 0;\n      }\n      if (config.savedTopologyView) {\n        savedTopologyView = config.savedTopologyView;\n      }\n      if (config.encryptedSections) {\n        encryptedSections = config.encryptedSections;\n      }\n      if (config.auditLog && Array.isArray(config.auditLog)) {\n        auditLog = config.auditLog;\n        try {\n          localStorage.setItem(AUDIT_STORAGE_KEY, JSON.stringify(auditLog));\n        } catch (e) {\n          console.warn(\"Failed to sync audit log to localStorage:\", e);\n        }\n      }\n      if (config.recordings && Array.isArray(config.recordings)) {\n        RECORDING_STATE.recordings = config.recordings;\n        RECORDING_STATE.currentRecording = config.recordings[0] || null;\n      }\n      const layerMap = { \"physical\": \"layer1\", \"logical\": \"layer2\", \"security\": \"layer3\", \"application\": \"layer4\" };\n      Object.values(NODE_DATA).forEach(node => {\n        if (!node.tags) node.tags = [];\n        if (!node.notes) node.notes = [];\n        if (!node.layer || layerMap[node.layer]) node.layer = layerMap[node.layer] || 'layer1';\n      });\n      EDGE_DATA.list.forEach(edge => {\n        if (!edge.notes) edge.notes = [];\n      });\n      forgeTheTopology();\n      if (typeof forgeTheLegend === 'function') forgeTheLegend();\n      if (typeof updateZoneLegend === 'function') updateZoneLegend();\n      updateViewBox();\n      updateDrawToolbarVisibility();\n      updateTopologyToolbarVisibility();\n      logAuditEvent('import', `Imported CSV (full restore): ${file.name}`);\n      showAlert(t(\"dialogs.fullRestoreComplete\"));\n      return;\n    }\n    if (hasConfig) {\n      Object.assign(PAGE_STATE, config.pageState || config.page);\n      if (config.canvasView || config.canvas) {\n        const canvasConfig = config.canvasView || config.canvas;\n        canvasState.zoom = canvasConfig.zoom || 1;\n        canvasState.panX = canvasConfig.panX || 0;\n        canvasState.panY = canvasConfig.panY || 0;\n      }\n      if (config.legend || config.edgeLegend) Object.assign(EDGE_LEGEND, config.legend || config.edgeLegend);\n      wieldThePower();\n    }\n    let gridX = 200, gridY = 200;\n    const spacing = 150;\n    const perRow = Math.ceil(Math.sqrt(nodes.length));\n    let gridIndex = 0;\n    nodes.forEach((n) => {\n      let baseId = (n.name || 'node').toLowerCase().replace(/[^a-z0-9]+/g, '-');\n      if (!baseId) baseId = 'node';\n      let nodeId = baseId;\n      let counter = 1;\n      while (NODE_DATA[nodeId]) { nodeId = `${baseId}-${counter}`; counter++; }\n      NODE_DATA[nodeId] = {\n        name: n.name || 'Unnamed', ip: n.ip || '', role: n.role || '', shape: n.shape || 'circle',\n        tags: n.tags ? n.tags.split(';').map(t => t.trim()).filter(t => t) : [],\n        notes: n.notes ? n.notes.split('|').map(t => t.trim()).filter(t => t) : [],\n        layer: n.layer || 'layer1', mac: n.mac || '', rackUnit: n.rackunit || '',\n        uHeight: n.uheight || '1', assignedRack: n.assignedrack || '', rackCapacity: n.rackcapacity || '',\n        isRack: n.israck === 'true', locked: n.locked === 'true', groupId: n.groupid || null\n      };\n      const hasPosition = n.x && n.y && !isNaN(parseFloat(n.x)) && !isNaN(parseFloat(n.y));\n      if (hasPosition) {\n        savedPositions[nodeId] = { x: parseFloat(n.x), y: parseFloat(n.y) };\n      } else {\n        const row = Math.floor(gridIndex / perRow);\n        const col = gridIndex % perRow;\n        savedPositions[nodeId] = { x: gridX + col * spacing, y: gridY + row * spacing };\n        gridIndex++;\n      }\n      if (n.size && !isNaN(parseFloat(n.size))) savedSizes[nodeId] = parseFloat(n.size);\n      if (n.styles) { try { savedStyles[nodeId] = JSON.parse(n.styles); } catch (err) {} }\n    });\n    forgeTheTopology();\n    updateViewBox();\n    updateDrawToolbarVisibility();\n    updateTopologyToolbarVisibility();\n    logAuditEvent('import', `Imported CSV: ${file.name} (${nodes.length} nodes)`);\n    showAlert(t(\"dialogs.importedNodesCount\", { count: nodes.length }));\n  } catch (err) {\n    console.error('CSV import error:', err);\n    showAlert(t(\"dialogs.importCsvFailed\", { error: err.message }));\n  }\n});\nfunction parseCSVLine(line) {\n  const result = [];\n  let current = '';\n  let inQuotes = false;\n  for (let i = 0; i < line.length; i++) {\n    const char = line[i];\n    if (char === '\"') {\n      if (inQuotes && line[i + 1] === '\"') { current += '\"'; i++; }\n      else { inQuotes = !inQuotes; }\n    } else if (char === ',' && !inQuotes) { result.push(current); current = ''; }\n    else { current += char; }\n  }\n  result.push(current);\n  return result;\n}\n\nfunction exportMarkdown() {\n  const safeTitle = (PAGE_STATE.title || 'topology').toLowerCase().replace(/[^a-z0-9]+/g, '-');\n  const timestamp = new Date().toISOString();\n  const title = PAGE_STATE.title || 'Network Topology';\n  const fullData = captureTheQuickening();\n  const config = fullData;\n  let md = `<!--THEONEFILE_CONFIG\\n${JSON.stringify(config, null, 2)}\\nTHEONEFILE_CONFIG-->\\n\\n`;\n  md += `# ${title}\\n\\n> Exported from The One File on ${timestamp}\\n\\n`;\n  md += `## Legend\\n\\n`;\n  if (Object.keys(EDGE_LEGEND).length > 0) {\n    Object.entries(EDGE_LEGEND).forEach(([color, label]) => { md += `- ${color}: ${label}\\n`; });\n  } else { md += `_No legend entries_\\n`; }\n  md += '\\n## Nodes\\n\\n';\n  Object.entries(NODE_DATA).forEach(([id, node]) => {\n    const pos = savedPositions[id] || { x: 0, y: 0 };\n    const size = savedSizes[id] || 50;\n    const styles = savedStyles[id] || null;\n    md += `### ${id}\\n`;\n    md += `- **Name:** ${node.name || ''}\\n- **IP:** ${node.ip || ''}\\n- **Role:** ${node.role || ''}\\n`;\n    md += `- **Shape:** ${node.shape || 'circle'}\\n- **Tags:** ${(node.tags || []).join('; ') || '_none_'}\\n`;\n    md += `- **Layer:** ${node.layer || 'layer1'}\\n- **MAC:** ${node.mac || ''}\\n`;\n    md += `- **Rack Unit:** ${node.rackUnit || ''}\\n- **U Height:** ${node.uHeight || '1'}\\n`;\n    md += `- **Assigned Rack:** ${node.assignedRack || ''}\\n- **Rack Capacity:** ${node.rackCapacity || ''}\\n`;\n    md += `- **Is Rack:** ${node.isRack ? 'true' : 'false'}\\n- **Locked:** ${node.locked ? 'true' : 'false'}\\n`;\n    md += `- **Group ID:** ${node.groupId || ''}\\n- **Position:** ${Math.round(pos.x)}, ${Math.round(pos.y)}\\n- **Size:** ${size}\\n`;\n    if (node.notes && node.notes.length > 0) { md += `- **Notes:**\\n`; node.notes.forEach(note => { md += `  - ${note.replace(/\\n/g, '<br>')}\\n`; }); }\n    if (styles) { md += `- **Styles:** \\`${JSON.stringify(styles)}\\`\\n`; }\n    md += '\\n';\n  });\n  md += `## Connections\\n\\n`;\n  if (EDGE_DATA.list && EDGE_DATA.list.length > 0) {\n    EDGE_DATA.list.forEach(edge => {\n      const fromPort = edge.fromPort ? ` (${edge.fromPort})` : '';\n      const toPort = edge.toPort ? ` (${edge.toPort})` : '';\n      md += `- ${edge.from}${fromPort} --> ${edge.to}${toPort}\\n`;\n      md += `  - **ID:** ${edge.id}\\n  - **Label:** ${(edge.label || '').replace(/\\n/g, '<br>')}\\n  - **Color:** ${edge.color || ''}\\n`;\n      md += `  - **Width:** ${edge.width || 4}\\n  - **Direction:** ${edge.direction || 'none'}\\n`;\n      md += `  - **Routing:** ${edge.routing || 'curved'}\\n  - **Type:** ${edge.type || 'main'}\\n`;\n      md += `  - **Line Style:** ${edge.lineStyle || 'solid'}\\n  - **Group ID:** ${edge.groupId || ''}\\n`;\n      if (edge.points && edge.points.length > 0) { md += `  - **Points:** ${edge.points.map(p => `${Math.round(p.x)},${Math.round(p.y)}`).join(' ')}\\n`; }\n      if (edge.notes && edge.notes.length > 0) { md += `  - **Notes:**\\n`; edge.notes.forEach(note => { md += `    - ${note.replace(/\\n/g, '<br>')}\\n`; }); }\n      md += '\\n';\n    });\n  } else { md += `_No connections_\\n\\n`; }\n  md += `## Zones\\n\\n`;\n  if (RECT_DATA.list && RECT_DATA.list.length > 0) {\n    RECT_DATA.list.forEach(rect => {\n      md += `### ${rect.id}\\n`;\n      md += `- **Position:** ${Math.round(rect.x)}, ${Math.round(rect.y)}\\n- **Size:** ${Math.round(rect.width)} x ${Math.round(rect.height)}\\n`;\n      md += `- **Color:** ${rect.color || ''}\\n- **Style:** ${rect.style || 'filled'}\\n- **Line Style:** ${rect.lineStyle || 'solid'}\\n`;\n      md += `- **Border Color:** ${rect.borderColor || ''}\\n- **Border Width:** ${rect.borderWidth !== undefined ? rect.borderWidth : 2}\\n`;\n      if (rect.notes && rect.notes.length > 0) { md += `- **Notes:**\\n`; rect.notes.forEach(note => { md += `  - ${note.replace(/\\n/g, '<br>')}\\n`; }); }\n      md += '\\n';\n    });\n  } else { md += `_No zones_\\n\\n`; }\n  md += `## Text Labels\\n\\n`;\n  if (TEXT_DATA.list && TEXT_DATA.list.length > 0) {\n    TEXT_DATA.list.forEach(text => {\n      md += `### ${text.id}\\n`;\n      md += `- **Content:** ${(text.content || '').replace(/\\n/g, '<br>')}\\n- **Position:** ${Math.round(text.x)}, ${Math.round(text.y)}\\n`;\n      md += `- **Font Size:** ${text.fontSize || 18}\\n- **Color:** ${text.color || '#e2e8f0'}\\n`;\n      md += `- **Font Weight:** ${text.fontWeight || 'normal'}\\n- **Font Style:** ${text.fontStyle || 'normal'}\\n`;\n      md += `- **Text Align:** ${text.textAlign || 'start'}\\n- **Text Decoration:** ${text.textDecoration || 'none'}\\n`;\n      md += `- **Background Color:** ${text.bgColor || '#000000'}\\n- **Background Enabled:** ${text.bgEnabled ? 'true' : 'false'}\\n`;\n      md += `- **Opacity:** ${text.opacity !== undefined ? text.opacity : 1}\\n\\n`;\n    });\n  } else { md += `_No text labels_\\n\\n`; }\n  const blob = new Blob([md], { type: 'text/markdown;charset=utf-8;' });\n  const url = URL.createObjectURL(blob);\n  const a = document.createElement('a');\n  a.href = url;\n  a.download = `${safeTitle}.md`;\n  a.click();\n  URL.revokeObjectURL(url);\n  logAuditEvent('export', `Exported Markdown: ${a.download}`);\n}\n\ndocument.getElementById('import-markdown-file')?.addEventListener('change', async (e) => {\n  const file = e.target.files[0];\n  if (!file) return;\n  e.target.value = '';\n  try {\n    const text = (await file.text()).replace(/\\r\\n?/g, '\\n');\n    let config = null;\n    const configMatch = text.match(/<!--THEONEFILE_CONFIG\\s*([\\s\\S]*?)\\s*THEONEFILE_CONFIG-->/);\n    if (configMatch) { try { config = JSON.parse(configMatch[1].trim()); } catch (err) { console.warn('Failed to parse Markdown config:', err); } }\n    const nodes = {}, positions = {}, sizes = {}, styles = {}, edges = [], rects = [], texts = [], legend = {};\n    const sections = text.split(/^## /m);\n    sections.forEach(section => {\n      const lines = section.trim().split('\\n');\n      const sectionTitle = lines[0]?.trim().toLowerCase();\n      if (sectionTitle === 'legend') {\n        lines.slice(1).forEach(line => {\n          const match = line.match(/^- (#[a-fA-F0-9]{3,8}):\\s*(.+)$/);\n          if (match) legend[match[1]] = match[2].trim();\n        });\n      } else if (sectionTitle === 'nodes') {\n        let currentNodeId = null, currentNode = null, inNotes = false;\n        lines.slice(1).forEach(line => {\n          const nodeMatch = line.match(/^### (.+)$/);\n          if (nodeMatch) {\n            if (currentNodeId && currentNode) nodes[currentNodeId] = currentNode;\n            currentNodeId = nodeMatch[1].trim();\n            currentNode = { tags: [], notes: [] };\n            inNotes = false;\n            return;\n          }\n          if (!currentNode) return;\n          if (inNotes && line.match(/^\\s+-\\s+(.+)$/)) {\n            const noteMatch = line.match(/^\\s+-\\s+(.+)$/);\n            if (noteMatch) currentNode.notes.push(noteMatch[1].trim().replace(/<br>/g, '\\n'));\n            return;\n          }\n          inNotes = false;\n          const propMatch = line.match(/^- \\*\\*(.+?):\\*\\*\\s*(.*)$/);\n          if (propMatch) {\n            const key = propMatch[1].trim().toLowerCase(), val = propMatch[2].trim();\n            switch (key) {\n              case 'name': currentNode.name = val; break;\n              case 'ip': currentNode.ip = val; break;\n              case 'role': currentNode.role = val; break;\n              case 'shape': currentNode.shape = val; break;\n              case 'tags': currentNode.tags = val === '_none_' ? [] : val.split(';').map(t => t.trim()).filter(t => t); break;\n              case 'layer': currentNode.layer = val; break;\n              case 'mac': currentNode.mac = val; break;\n              case 'rack unit': currentNode.rackUnit = val; break;\n              case 'u height': currentNode.uHeight = val; break;\n              case 'assigned rack': currentNode.assignedRack = val; break;\n              case 'rack capacity': currentNode.rackCapacity = val; break;\n              case 'is rack': currentNode.isRack = val === 'true'; break;\n              case 'locked': currentNode.locked = val === 'true'; break;\n              case 'group id': currentNode.groupId = val || null; break;\n              case 'position':\n                const posMatch = val.match(/(-?\\d+),\\s*(-?\\d+)/);\n                if (posMatch) positions[currentNodeId] = { x: parseFloat(posMatch[1]), y: parseFloat(posMatch[2]) };\n                break;\n              case 'size': sizes[currentNodeId] = parseFloat(val) || 50; break;\n              case 'notes': inNotes = true; break;\n              case 'styles':\n                const stylesMatch = val.match(/`(.+)`/);\n                if (stylesMatch) { try { styles[currentNodeId] = JSON.parse(stylesMatch[1]); } catch (err) {} }\n                break;\n            }\n          }\n        });\n        if (currentNodeId && currentNode) nodes[currentNodeId] = currentNode;\n      } else if (sectionTitle === 'connections') {\n        let currentEdge = null, inNotes = false;\n        lines.slice(1).forEach(line => {\n          const connMatch = line.match(/^- (.+?)\\s*(?:\\(([^)]*)\\))?\\s*-->\\s*(.+?)\\s*(?:\\(([^)]*)\\))?$/);\n          if (connMatch) {\n            if (currentEdge) edges.push(currentEdge);\n            currentEdge = { from: connMatch[1].trim(), to: connMatch[3].trim(), fromPort: connMatch[2] || '', toPort: connMatch[4] || '', notes: [] };\n            inNotes = false;\n            return;\n          }\n          if (!currentEdge) return;\n          if (inNotes && line.match(/^\\s+-\\s+(.+)$/)) {\n            const noteMatch = line.match(/^\\s+-\\s+(.+)$/);\n            if (noteMatch) currentEdge.notes.push(noteMatch[1].trim().replace(/<br>/g, '\\n'));\n            return;\n          }\n          inNotes = false;\n          const propMatch = line.match(/^\\s+- \\*\\*(.+?):\\*\\*\\s*(.*)$/);\n          if (propMatch) {\n            const key = propMatch[1].trim().toLowerCase(), val = propMatch[2].trim();\n            switch (key) {\n              case 'id': currentEdge.id = val; break;\n              case 'label': currentEdge.label = val.replace(/<br>/g, '\\n'); break;\n              case 'color': currentEdge.color = val; break;\n              case 'width': currentEdge.width = parseFloat(val) || 4; break;\n              case 'direction': currentEdge.direction = val; break;\n              case 'routing': currentEdge.routing = val; break;\n              case 'type': currentEdge.type = val; break;\n              case 'line style': currentEdge.lineStyle = val; break;\n              case 'group id': currentEdge.groupId = val || null; break;\n              case 'points':\n                currentEdge.points = val.split(' ').map(p => { const [x, y] = p.split(',').map(Number); return { x, y }; }).filter(p => !isNaN(p.x) && !isNaN(p.y));\n                break;\n              case 'notes': inNotes = true; break;\n            }\n          }\n        });\n        if (currentEdge) edges.push(currentEdge);\n      } else if (sectionTitle === 'zones') {\n        let currentRect = null, inNotes = false;\n        lines.slice(1).forEach(line => {\n          const rectMatch = line.match(/^### (.+)$/);\n          if (rectMatch) {\n            if (currentRect) rects.push(currentRect);\n            currentRect = { id: rectMatch[1].trim(), notes: [] };\n            inNotes = false;\n            return;\n          }\n          if (!currentRect) return;\n          if (inNotes && line.match(/^\\s+-\\s+(.+)$/)) {\n            const noteMatch = line.match(/^\\s+-\\s+(.+)$/);\n            if (noteMatch) currentRect.notes.push(noteMatch[1].trim().replace(/<br>/g, '\\n'));\n            return;\n          }\n          inNotes = false;\n          const propMatch = line.match(/^- \\*\\*(.+?):\\*\\*\\s*(.*)$/);\n          if (propMatch) {\n            const key = propMatch[1].trim().toLowerCase(), val = propMatch[2].trim();\n            switch (key) {\n              case 'position': const posMatch = val.match(/(-?\\d+),\\s*(-?\\d+)/); if (posMatch) { currentRect.x = parseFloat(posMatch[1]); currentRect.y = parseFloat(posMatch[2]); } break;\n              case 'size': const sizeMatch = val.match(/(\\d+)\\s*x\\s*(\\d+)/); if (sizeMatch) { currentRect.width = parseFloat(sizeMatch[1]); currentRect.height = parseFloat(sizeMatch[2]); } break;\n              case 'color': currentRect.color = val; break;\n              case 'style': currentRect.style = val; break;\n              case 'line style': currentRect.lineStyle = val; break;\n              case 'border color': currentRect.borderColor = val; break;\n              case 'border width': currentRect.borderWidth = parseFloat(val) || 2; break;\n              case 'notes': inNotes = true; break;\n            }\n          }\n        });\n        if (currentRect) rects.push(currentRect);\n      } else if (sectionTitle === 'text labels') {\n        let currentText = null;\n        lines.slice(1).forEach(line => {\n          const textMatch = line.match(/^### (.+)$/);\n          if (textMatch) {\n            if (currentText) texts.push(currentText);\n            currentText = { id: textMatch[1].trim() };\n            return;\n          }\n          if (!currentText) return;\n          const propMatch = line.match(/^- \\*\\*(.+?):\\*\\*\\s*(.*)$/);\n          if (propMatch) {\n            const key = propMatch[1].trim().toLowerCase(), val = propMatch[2].trim();\n            switch (key) {\n              case 'content': currentText.content = val.replace(/<br>/g, '\\n'); break;\n              case 'position': const posMatch = val.match(/(-?\\d+),\\s*(-?\\d+)/); if (posMatch) { currentText.x = parseFloat(posMatch[1]); currentText.y = parseFloat(posMatch[2]); } break;\n              case 'font size': currentText.fontSize = parseFloat(val) || 18; break;\n              case 'color': currentText.color = val; break;\n              case 'font weight': currentText.fontWeight = val; break;\n              case 'font style': currentText.fontStyle = val; break;\n              case 'text align': currentText.textAlign = val; break;\n              case 'text decoration': currentText.textDecoration = val; break;\n              case 'background color': currentText.bgColor = val; break;\n              case 'background enabled': currentText.bgEnabled = val === 'true'; break;\n              case 'opacity': currentText.opacity = parseFloat(val) || 1; break;\n            }\n          }\n        });\n        if (currentText) texts.push(currentText);\n      }\n    });\n    const nodeCount = Object.keys(nodes).length, edgeCount = edges.length, rectCount = rects.length, textCount = texts.length;\n    if (nodeCount === 0 && edgeCount === 0 && rectCount === 0 && textCount === 0) {\n      showAlert(t(\"dialogs.noValidTopologyInMarkdown\"));\n      return;\n    }\n    if (!await showConfirm(t(\"dialogs.confirmImportMarkdown\", { nodeCount: nodeCount, edgeCount: edgeCount, rectCount: rectCount, textCount: textCount }))) return;\n    pushUndo('import markdown');\n    if (config && config.documentTabs) {\n      if (config.page) { Object.assign(PAGE_STATE, config.page); wieldThePower(); }\n      if (config.canvas) {\n        canvasState.zoom = config.canvas.zoom || 1;\n        canvasState.panX = config.canvas.panX || 0;\n        canvasState.panY = config.canvas.panY || 0;\n      }\n      if (config.savedStyleSets) {\n        savedStyleSets = config.savedStyleSets;\n      }\n      if (config.documentTabs) {\n        documentTabs = config.documentTabs;\n        currentTabIndex = config.currentTabIndex || 0;\n      }\n      if (config.savedTopologyView) {\n        savedTopologyView = config.savedTopologyView;\n      }\n      if (config.encryptedSections) {\n        encryptedSections = config.encryptedSections;\n      }\n      if (config.auditLog && Array.isArray(config.auditLog)) {\n        auditLog = config.auditLog;\n        try {\n          localStorage.setItem(AUDIT_STORAGE_KEY, JSON.stringify(auditLog));\n        } catch (e) {\n          console.warn(\"Failed to sync audit log to localStorage:\", e);\n        }\n      }\n      if (config.recordings && Array.isArray(config.recordings)) {\n        RECORDING_STATE.recordings = config.recordings;\n        RECORDING_STATE.currentRecording = config.recordings[0] || null;\n      }\n    } else if (config && config.pageState) {\n      Object.assign(PAGE_STATE, config.pageState);\n      wieldThePower();\n      if (config.canvasView) {\n        canvasState.zoom = config.canvasView.zoom || 1;\n        canvasState.panX = config.canvasView.panX || 0;\n        canvasState.panY = config.canvasView.panY || 0;\n      }\n    }\n    Object.keys(NODE_DATA).forEach(k => delete NODE_DATA[k]);\n    Object.keys(savedPositions).forEach(k => delete savedPositions[k]);\n    Object.keys(savedSizes).forEach(k => delete savedSizes[k]);\n    Object.keys(savedStyles).forEach(k => delete savedStyles[k]);\n    EDGE_DATA.list = [];\n    RECT_DATA.list = [];\n    TEXT_DATA.list = [];\n    Object.keys(EDGE_LEGEND).forEach(k => delete EDGE_LEGEND[k]);\n    Object.assign(EDGE_LEGEND, legend);\n    Object.entries(nodes).forEach(([id, node]) => {\n      NODE_DATA[id] = { name: node.name || '', ip: node.ip || '', role: node.role || '', shape: node.shape || 'circle', tags: node.tags || [], notes: node.notes || [], mac: node.mac || '', rackUnit: node.rackUnit || '', uHeight: node.uHeight || '1', layer: node.layer || 'layer1', assignedRack: node.assignedRack || '', hostedOn: node.hostedOn || '', rackCapacity: node.rackCapacity || '', isRack: node.isRack || false, locked: node.locked || false, groupId: node.groupId || null };\n      savedPositions[id] = positions[id] || { x: 500, y: 300 };\n      if (sizes[id]) savedSizes[id] = sizes[id];\n      if (styles[id]) savedStyles[id] = styles[id];\n    });\n    let skippedEdges = 0;\n    edges.forEach(edge => {\n      if (!NODE_DATA[edge.from] || !NODE_DATA[edge.to]) {\n        console.warn(`Skipping orphan edge: ${edge.from} -> ${edge.to}`);\n        skippedEdges++;\n        return;\n      }\n      EDGE_DATA.list.push({ id: edge.id || `edge-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, from: edge.from, to: edge.to, fromPort: edge.fromPort || '', toPort: edge.toPort || '', label: edge.label || '', color: edge.color || '', width: edge.width || 4, direction: edge.direction || 'none', routing: edge.routing || 'curved', type: edge.type || 'main', lineStyle: edge.lineStyle || 'solid', groupId: edge.groupId || null, points: edge.points || [], notes: edge.notes || [], waypoints: edge.waypoints || [] });\n    });\n    if (skippedEdges > 0) console.warn(`Total skipped orphan edges: ${skippedEdges}`);\n    rects.forEach(rect => {\n      RECT_DATA.list.push({ id: rect.id, x: rect.x || 0, y: rect.y || 0, width: rect.width || 100, height: rect.height || 100, color: rect.color || '#f97316', style: rect.style || 'filled', lineStyle: rect.lineStyle || 'solid', borderColor: rect.borderColor || '', borderWidth: rect.borderWidth !== undefined ? rect.borderWidth : 2, notes: rect.notes || [] });\n    });\n    texts.forEach(text => {\n      TEXT_DATA.list.push({ id: text.id, x: text.x || 0, y: text.y || 0, content: text.content || '', fontSize: text.fontSize || 18, color: text.color || '#e2e8f0', fontWeight: text.fontWeight || 'normal', fontStyle: text.fontStyle || 'normal', textAlign: text.textAlign || 'start', textDecoration: text.textDecoration || 'none', bgColor: text.bgColor || '#000000', bgEnabled: text.bgEnabled || false, opacity: text.opacity !== undefined ? text.opacity : 1 });\n    });\n    forgeTheTopology();\n    if (typeof forgeTheLegend === 'function') forgeTheLegend();\n    if (typeof updateZoneLegend === 'function') updateZoneLegend();\n    updateViewBox();\n    updateDrawToolbarVisibility();\n    updateTopologyToolbarVisibility();\n    logAuditEvent('import', `Imported Markdown: ${file.name} (${nodeCount} nodes, ${edgeCount} connections)`);\n    showAlert(t(\"dialogs.markdownImportSuccess\", { nodeCount, edgeCount, rectCount, textCount }));\n  } catch (err) {\n    console.error('Markdown import error:', err);\n    showAlert(t(\"dialogs.importMarkdownFailed\", { error: err.message }));\n  }\n});\n\ndocument.getElementById('mobile-export-btn')?.addEventListener('click', () => {\n  document.getElementById('mobile-export-modal').classList.add('active');\n  document.getElementById('topbar-menu').classList.remove('open');\n});\ndocument.getElementById('mobile-import-btn')?.addEventListener('click', () => {\n  document.getElementById('mobile-import-modal').classList.add('active');\n  document.getElementById('topbar-menu').classList.remove('open');\n});\ndocument.getElementById('mobile-export-modal')?.addEventListener('click', (e) => {\n  if (e.target.id === 'mobile-export-modal') e.target.classList.remove('active');\n});\ndocument.getElementById('mobile-import-modal')?.addEventListener('click', (e) => {\n  if (e.target.id === 'mobile-import-modal') e.target.classList.remove('active');\n});\ndocument.getElementById('import-html-file')?.addEventListener('change', async (e) => {\n  const file = e.target.files[0];\n  if (!file) return;\n  e.target.value = '';\n  try {\n    const text = await file.text();\n    const parser = new DOMParser();\n    const doc = parser.parseFromString(text, 'text/html');\n    const stateScript = doc.getElementById('topology-state');\n    if (!stateScript) {\n      showAlert(t(\"dialogs.invalidHtmlFile\"));\n      return;\n    }\n    const data = JSON.parse(stateScript.textContent);\n    if (!data.nodeData || !data.edgeData) {\n      showAlert(t(\"dialogs.invalidHtmlFile\"));\n      return;\n    }\n    const nodeCount = Object.keys(data.nodeData).length;\n    const edgeCount = data.edgeData.list?.length || 0;\n    const tabCount = data.documentTabs?.length || 1;\n    if (!await showConfirm(t(\"dialogs.confirmImportHtml\", { nodeCount, edgeCount, tabCount }))) {\n      return;\n    }\n    pushUndo('import html');\n    NODE_DATA = data.nodeData || {};\n    EDGE_DATA = data.edgeData || { list: [] };\n    EDGE_LEGEND = data.edgeLegend || {};\n    RECT_DATA = data.rectData || { list: [] };\n    TEXT_DATA = data.textData || { list: [] };\n    savedPositions = data.nodePositions || {};\n    savedSizes = data.nodeSizes || {};\n    savedStyles = data.nodeStyles || {};\n    if (data.page) {\n      PAGE_STATE = Object.assign({}, DEFAULT_PAGE_STATE, data.page);\n      wieldThePower();\n    }\n    if (data.autoPingEnabled !== undefined) {\n      autoPingEnabled = data.autoPingEnabled;\n      PAGE_STATE.autoPingEnabled = autoPingEnabled;\n      document.getElementById(\"auto-ping-enabled\").checked = autoPingEnabled;\n    }\n    if (data.autoPingInterval !== undefined) {\n      autoPingInterval = data.autoPingInterval;\n      PAGE_STATE.autoPingInterval = autoPingInterval;\n      document.getElementById(\"auto-ping-interval\").value = autoPingInterval;\n    }\n    if (data.canvas) {\n      canvasState.zoom = data.canvas.zoom || 1;\n      canvasState.panX = data.canvas.panX || 0;\n      canvasState.panY = data.canvas.panY || 0;\n    }\n    if (data.page?.title) {\n      document.title = data.page.title;\n      document.querySelector(\".editable-page-title\").textContent = data.page.title;\n    }\n    if (data.savedStyleSets) {\n      savedStyleSets = data.savedStyleSets;\n    }\n    if (data.documentTabs) {\n      documentTabs = data.documentTabs;\n      currentTabIndex = data.currentTabIndex || 0;\n    }\n    if (data.savedTopologyView) {\n      savedTopologyView = data.savedTopologyView;\n    }\n    if (data.encryptedSections) {\n      encryptedSections = data.encryptedSections;\n    }\n    if (data.auditLog && Array.isArray(data.auditLog)) {\n      auditLog = data.auditLog;\n      try {\n        localStorage.setItem(AUDIT_STORAGE_KEY, JSON.stringify(auditLog));\n      } catch (err) {\n        console.warn(\"Failed to sync audit log to localStorage:\", err);\n      }\n    }\n    if (data.recordings && Array.isArray(data.recordings)) {\n      RECORDING_STATE.recordings = data.recordings;\n      RECORDING_STATE.currentRecording = data.recordings[0] || null;\n    }\n    const layerMap = { \"physical\": \"layer1\", \"logical\": \"layer2\", \"security\": \"layer3\", \"application\": \"layer4\" };\n    Object.values(NODE_DATA).forEach(node => {\n      if (!node.tags) node.tags = [];\n      if (!node.notes) node.notes = [];\n      if (!node.layer || layerMap[node.layer]) node.layer = layerMap[node.layer] || 'layer1';\n    });\n    EDGE_DATA.list.forEach(edge => {\n      if (!edge.notes) edge.notes = [];\n    });\n    forgeTheTopology();\n    if (typeof forgeTheLegend === 'function') forgeTheLegend();\n    if (typeof updateZoneLegend === 'function') updateZoneLegend();\n    logAuditEvent(\"import\", `Imported HTML: ${file.name} (${nodeCount} nodes, ${edgeCount} connections)`);\n    updateViewBox();\n    if (autoPingEnabled) {\n      startAutoPing();\n    } else {\n      stopAutoPing();\n    }\n    const nodeIds = Object.keys(NODE_DATA);\n    if (nodeIds.length > 0) {\n      claimTheImmortal(nodeIds[0]);\n    } else {\n      document.getElementById(\"node-panel\").style.display = \"none\";\n      document.getElementById(\"edge-panel\").style.display = \"none\";\n      document.getElementById(\"topology-toolbar\").style.display = \"none\";\n    }\n    updateDrawToolbarVisibility();\n    showAlert(t(\"dialogs.htmlImportSuccess\", { nodeCount, edgeCount }));\n  } catch (err) {\n    console.error(\"HTML import error:\", err);\n    showAlert(t(\"dialogs.invalidHtmlFile\"));\n  }\n});\ndocument.getElementById('import-json-file')?.addEventListener('change', async (e) => {\n  const file = e.target.files[0];\n  if (!file) return;\n  e.target.value = '';\n  const existingInput = document.getElementById('import-data-file');\n  if (existingInput) {\n    const dt = new DataTransfer();\n    dt.items.add(file);\n    existingInput.files = dt.files;\n    existingInput.dispatchEvent(new Event('change'));\n  }\n});\n\nfunction formatRecordingTime(ms) {\n  const seconds = Math.floor(ms / 1000);\n  const minutes = Math.floor(seconds / 60);\n  const secs = seconds % 60;\n  return `${minutes}:${secs.toString().padStart(2, '0')}`;\n}\n\nfunction captureRecordingFrame() {\n  const frame = {\n    time: Date.now() - RECORDING_STATE.startTime,\n    positions: {},\n    waypoints: {},\n    rects: [],\n    texts: []\n  };\n  Object.keys(savedPositions).forEach(id => {\n    frame.positions[id] = { x: savedPositions[id].x, y: savedPositions[id].y };\n  });\n  EDGE_DATA.list.forEach(edge => {\n    if (edge.waypoints && edge.waypoints.length > 0) {\n      frame.waypoints[edge.id] = edge.waypoints.map(wp => ({ x: wp.x, y: wp.y }));\n    }\n  });\n  if (RECT_DATA && RECT_DATA.list) {\n    frame.rects = RECT_DATA.list.map(r => ({ id: r.id, x: r.x, y: r.y, width: r.width, height: r.height, rotation: r.rotation || 0 }));\n  }\n  if (TEXT_DATA && TEXT_DATA.list) {\n    frame.texts = TEXT_DATA.list.map(t => ({ id: t.id, x: t.x, y: t.y }));\n  }\n  RECORDING_STATE.frames.push(frame);\n  document.getElementById('recording-time').textContent = formatRecordingTime(frame.time);\n}\n\nfunction startRecording() {\n  if (RECORDING_STATE.isRecording) return;\n  const expanded = document.getElementById('recording-expanded');\n  if (expanded) expanded.style.display = 'inline-flex';\n  RECORDING_STATE.isRecording = true;\n  RECORDING_STATE.frames = [];\n  RECORDING_STATE.startTime = Date.now();\n  captureRecordingFrame();\n  RECORDING_STATE.captureInterval = setInterval(captureRecordingFrame, 1000 / RECORDING_STATE.CAPTURE_FPS);\n  document.getElementById('record-btn').style.background = '#f56565';\n  document.getElementById('record-btn').style.color = 'white';\n  document.getElementById('stop-btn').style.display = 'inline-block';\n  logAuditEvent('recording', 'Started recording');\n}\n\nfunction stopRecording() {\n  if (!RECORDING_STATE.isRecording && !RECORDING_STATE.isPlaying) return;\n  const wasPlaying = RECORDING_STATE.isPlaying;\n  if (RECORDING_STATE.isRecording) {\n    clearInterval(RECORDING_STATE.captureInterval);\n    RECORDING_STATE.isRecording = false;\n    if (RECORDING_STATE.frames.length > 1) {\n      const recording = {\n        id: 'rec-' + Date.now(),\n        name: 'Recording ' + (RECORDING_STATE.recordings.length + 1),\n        created: new Date().toISOString(),\n        duration: RECORDING_STATE.frames[RECORDING_STATE.frames.length - 1].time,\n        frames: RECORDING_STATE.frames\n      };\n      RECORDING_STATE.recordings.push(recording);\n      RECORDING_STATE.currentRecording = recording;\n      logAuditEvent('recording', `Saved recording: ${recording.name} (${formatRecordingTime(recording.duration)})`);\n    }\n    document.getElementById('record-btn').style.background = 'var(--btn-bg)';\n    document.getElementById('record-btn').style.color = '#f56565';\n  }\n  if (RECORDING_STATE.isPlaying) {\n    clearTimeout(RECORDING_STATE.playbackTimer);\n    RECORDING_STATE.isPlaying = false;\n    RECORDING_STATE.isPaused = false;\n    RECORDING_STATE.trailHistory = {};\n    forgeTheTopology();\n  }\n  document.getElementById('stop-btn').style.display = 'none';\n  document.getElementById('play-btn').style.display = 'inline-block';\n  document.getElementById('pause-btn').style.display = 'none';\n  document.getElementById('recording-time').textContent = '0:00';\n  if (wasPlaying) {\n    const expanded = document.getElementById('recording-expanded');\n    if (expanded) expanded.style.display = 'none';\n  }\n}\n\nfunction playRecording() {\n  if (!RECORDING_STATE.currentRecording || RECORDING_STATE.currentRecording.frames.length < 2) {\n    showAlert(t(\"dialogs.noRecordingAvailable\"));\n    const expanded = document.getElementById('recording-expanded');\n    if (expanded) expanded.style.display = 'none';\n    return;\n  }\n  const expanded = document.getElementById('recording-expanded');\n  if (expanded) expanded.style.display = 'inline-flex';\n  if (RECORDING_STATE.isPaused) {\n    RECORDING_STATE.isPaused = false;\n    RECORDING_STATE.isPlaying = true;\n    document.getElementById('play-btn').style.display = 'none';\n    document.getElementById('pause-btn').style.display = 'inline-block';\n    playNextFrame();\n    return;\n  }\n  RECORDING_STATE.isPlaying = true;\n  RECORDING_STATE.playbackIndex = 0;\n  document.getElementById('play-btn').style.display = 'none';\n  document.getElementById('pause-btn').style.display = 'inline-block';\n  document.getElementById('stop-btn').style.display = 'inline-block';\n  playNextFrame();\n}\n\nfunction pauseRecording() {\n  if (!RECORDING_STATE.isPlaying) return;\n  RECORDING_STATE.isPaused = true;\n  RECORDING_STATE.isPlaying = false;\n  clearTimeout(RECORDING_STATE.playbackTimer);\n  document.getElementById('play-btn').style.display = 'inline-block';\n  document.getElementById('pause-btn').style.display = 'none';\n}\n\nfunction startStepRecording() {\n  if (RECORDING_STATE.isStepRecording || RECORDING_STATE.isRecording) return;\n  RECORDING_STATE.isStepRecording = true;\n  RECORDING_STATE.stepFrames = [];\n  RECORDING_STATE.stepCount = 0;\n  captureStepFrame();\n  const stepBtn = document.getElementById('step-record-btn');\n  stepBtn.textContent = '+';\n  stepBtn.style.background = '#48bb78';\n  stepBtn.style.color = 'white';\n  document.getElementById('recording-expanded').style.display = 'inline-flex';\n  document.getElementById('pause-btn').style.display = 'none';\n  document.getElementById('record-btn').style.display = 'none';\n  updateStepRecordingTime();\n  logAuditEvent('recording', 'Started step by step recording');\n}\n\nfunction captureStepFrame() {\n  const frame = {\n    time: RECORDING_STATE.stepCount * 1000,\n    positions: {},\n    waypoints: {},\n    rects: [],\n    texts: []\n  };\n  Object.keys(savedPositions).forEach(id => {\n    frame.positions[id] = { x: savedPositions[id].x, y: savedPositions[id].y };\n  });\n  EDGE_DATA.list.forEach(edge => {\n    if (edge.waypoints && edge.waypoints.length > 0) {\n      frame.waypoints[edge.id] = edge.waypoints.map(wp => ({ x: wp.x, y: wp.y }));\n    }\n  });\n  if (RECT_DATA && RECT_DATA.list) {\n    frame.rects = RECT_DATA.list.map(r => ({ id: r.id, x: r.x, y: r.y, width: r.width, height: r.height, rotation: r.rotation || 0 }));\n  }\n  if (TEXT_DATA && TEXT_DATA.list) {\n    frame.texts = TEXT_DATA.list.map(t => ({ id: t.id, x: t.x, y: t.y }));\n  }\n  RECORDING_STATE.stepFrames.push(frame);\n  RECORDING_STATE.stepCount++;\n  updateStepRecordingTime();\n}\n\nfunction updateStepRecordingTime() {\n  const timeEl = document.getElementById('recording-time');\n  if (timeEl) {\n    timeEl.textContent = RECORDING_STATE.stepCount + ' frames';\n  }\n}\n\nfunction stopStepRecording() {\n  if (!RECORDING_STATE.isStepRecording) return;\n  RECORDING_STATE.isStepRecording = false;\n  if (RECORDING_STATE.stepFrames.length > 1) {\n    const recording = {\n      id: 'rec-' + Date.now(),\n      name: 'Step Recording ' + (RECORDING_STATE.recordings.length + 1),\n      created: new Date().toISOString(),\n      duration: (RECORDING_STATE.stepCount - 1) * 1000,\n      frames: RECORDING_STATE.stepFrames\n    };\n    RECORDING_STATE.recordings.push(recording);\n    RECORDING_STATE.currentRecording = recording;\n    logAuditEvent('recording', `Saved step recording: ${recording.name} (${RECORDING_STATE.stepCount} frames)`);\n  }\n  const stepBtn = document.getElementById('step-record-btn');\n  stepBtn.textContent = '●+';\n  stepBtn.style.background = 'var(--btn-bg)';\n  stepBtn.style.color = '#48bb78';\n  document.getElementById('record-btn').style.display = 'inline-block';\n  document.getElementById('recording-time').textContent = '0:00';\n}\n\nfunction playNextFrame() {\n  if (!RECORDING_STATE.isPlaying || !RECORDING_STATE.currentRecording) return;\n  const frames = RECORDING_STATE.currentRecording.frames;\n  if (RECORDING_STATE.playbackIndex >= frames.length) {\n    if (RECORDING_STATE.loopPlayback) {\n      RECORDING_STATE.playbackIndex = 0;\n      RECORDING_STATE.trailHistory = {};\n    } else {\n      stopRecording();\n      return;\n    }\n  }\n  const frame = frames[RECORDING_STATE.playbackIndex];\n  const trailLength = PAGE_STATE.motionTrailLength || 8;\n  Object.keys(frame.positions).forEach(id => {\n    if (savedPositions[id]) {\n      if (PAGE_STATE.motionTrailsEnabled) {\n        if (!RECORDING_STATE.trailHistory[id]) RECORDING_STATE.trailHistory[id] = [];\n        RECORDING_STATE.trailHistory[id].push({ x: frame.positions[id].x, y: frame.positions[id].y });\n        if (RECORDING_STATE.trailHistory[id].length > trailLength) {\n          RECORDING_STATE.trailHistory[id].shift();\n        }\n      }\n      savedPositions[id].x = frame.positions[id].x;\n      savedPositions[id].y = frame.positions[id].y;\n    }\n  });\n  if (frame.waypoints) {\n    EDGE_DATA.list.forEach(edge => {\n      if (frame.waypoints[edge.id]) {\n        edge.waypoints = frame.waypoints[edge.id].map(wp => ({ x: wp.x, y: wp.y }));\n      } else {\n        edge.waypoints = [];\n      }\n    });\n  }\n  if (frame.rects && RECT_DATA && RECT_DATA.list) {\n    frame.rects.forEach(frameRect => {\n      const rect = RECT_DATA.list.find(r => r.id === frameRect.id);\n      if (rect) {\n        rect.x = frameRect.x;\n        rect.y = frameRect.y;\n        rect.width = frameRect.width;\n        rect.height = frameRect.height;\n        rect.rotation = frameRect.rotation || 0;\n      }\n    });\n  }\n  if (frame.texts && TEXT_DATA && TEXT_DATA.list) {\n    frame.texts.forEach(frameText => {\n      const text = TEXT_DATA.list.find(t => t.id === frameText.id);\n      if (text) {\n        text.x = frameText.x;\n        text.y = frameText.y;\n      }\n    });\n  }\n  forgeTheTopology();\n  updateMinimap();\n  document.getElementById('recording-time').textContent = formatRecordingTime(frame.time);\n  const progress = (RECORDING_STATE.playbackIndex / (frames.length - 1)) * 100;\n  document.getElementById('playback-scrubber').value = progress;\n  RECORDING_STATE.playbackIndex++;\n  const nextFrame = frames[RECORDING_STATE.playbackIndex];\n  if (nextFrame) {\n    const delay = (nextFrame.time - frame.time) / RECORDING_STATE.playbackSpeed;\n    RECORDING_STATE.playbackTimer = setTimeout(playNextFrame, Math.max(16, delay));\n  } else if (RECORDING_STATE.loopPlayback) {\n    RECORDING_STATE.playbackTimer = setTimeout(playNextFrame, 100);\n  } else {\n    stopRecording();\n  }\n}\n\nfunction renderMotionTrails(svg) {\n  if (!PAGE_STATE.motionTrailsEnabled || !RECORDING_STATE.isPlaying) return;\n  if (Object.keys(RECORDING_STATE.trailHistory).length === 0) return;\n  const ns = \"http://www.w3.org/2000/svg\";\n  const trailGroup = document.createElementNS(ns, \"g\");\n  trailGroup.id = \"motion-trails\";\n  const trailStyle = PAGE_STATE.motionTrailStyle || \"solid\";\n  const showArrow = PAGE_STATE.motionTrailArrow !== false;\n  Object.keys(RECORDING_STATE.trailHistory).forEach(nodeId => {\n    const node = NODE_DATA[nodeId];\n    if (!node) return;\n    if (node.trailEnabled === false) return;\n    const trail = RECORDING_STATE.trailHistory[nodeId];\n    if (!trail || trail.length < 2) return;\n    const nodeSize = (savedSizes[nodeId] && savedSizes[nodeId].size) || 55;\n    const nodeColor = (savedStyles[nodeId] && savedStyles[nodeId].stroke) || PAGE_STATE.nodeStroke || \"#475569\";\n    if (trailStyle === \"dots\") {\n      trail.forEach((point, idx) => {\n        const opacity = (idx + 1) / trail.length * 0.7;\n        const dotSize = 3 + (idx / trail.length) * 4;\n        const dot = document.createElementNS(ns, \"circle\");\n        dot.setAttribute(\"cx\", point.x);\n        dot.setAttribute(\"cy\", point.y);\n        dot.setAttribute(\"r\", dotSize);\n        dot.setAttribute(\"fill\", nodeColor);\n        dot.setAttribute(\"opacity\", opacity);\n        trailGroup.appendChild(dot);\n      });\n    } else {\n      const path = document.createElementNS(ns, \"polyline\");\n      const points = trail.map(p => `${p.x},${p.y}`).join(\" \");\n      path.setAttribute(\"points\", points);\n      path.setAttribute(\"fill\", \"none\");\n      path.setAttribute(\"stroke\", nodeColor);\n      path.setAttribute(\"stroke-width\", \"3\");\n      path.setAttribute(\"stroke-linecap\", \"round\");\n      path.setAttribute(\"stroke-linejoin\", \"round\");\n      path.setAttribute(\"opacity\", \"0.6\");\n      if (trailStyle === \"dashed\") {\n        path.setAttribute(\"stroke-dasharray\", \"8,4\");\n      }\n      trailGroup.appendChild(path);\n    }\n    if (showArrow && trail.length >= 2) {\n      const last = trail[trail.length - 1];\n      const prev = trail[trail.length - 2];\n      const angle = Math.atan2(last.y - prev.y, last.x - prev.x);\n      const arrowSize = 10;\n      const arrowX = last.x + Math.cos(angle) * (nodeSize / 2 + 5);\n      const arrowY = last.y + Math.sin(angle) * (nodeSize / 2 + 5);\n      const arrow = document.createElementNS(ns, \"polygon\");\n      const p1x = arrowX + Math.cos(angle) * arrowSize;\n      const p1y = arrowY + Math.sin(angle) * arrowSize;\n      const p2x = arrowX + Math.cos(angle + 2.5) * arrowSize * 0.7;\n      const p2y = arrowY + Math.sin(angle + 2.5) * arrowSize * 0.7;\n      const p3x = arrowX + Math.cos(angle - 2.5) * arrowSize * 0.7;\n      const p3y = arrowY + Math.sin(angle - 2.5) * arrowSize * 0.7;\n      arrow.setAttribute(\"points\", `${p1x},${p1y} ${p2x},${p2y} ${p3x},${p3y}`);\n      arrow.setAttribute(\"fill\", nodeColor);\n      arrow.setAttribute(\"opacity\", \"0.8\");\n      trailGroup.appendChild(arrow);\n    }\n  });\n  const firstChild = svg.firstChild;\n  if (firstChild) {\n    svg.insertBefore(trailGroup, firstChild);\n  } else {\n    svg.appendChild(trailGroup);\n  }\n}\n\nfunction seekRecording(percent) {\n  if (!RECORDING_STATE.currentRecording) return;\n  const frames = RECORDING_STATE.currentRecording.frames;\n  const targetIndex = Math.floor((percent / 100) * (frames.length - 1));\n  RECORDING_STATE.playbackIndex = targetIndex;\n  const frame = frames[targetIndex];\n  if (frame) {\n    Object.keys(frame.positions).forEach(id => {\n      if (savedPositions[id]) {\n        savedPositions[id].x = frame.positions[id].x;\n        savedPositions[id].y = frame.positions[id].y;\n      }\n    });\n    if (frame.waypoints) {\n      EDGE_DATA.list.forEach(edge => {\n        if (frame.waypoints[edge.id]) {\n          edge.waypoints = frame.waypoints[edge.id].map(wp => ({ x: wp.x, y: wp.y }));\n        } else {\n          edge.waypoints = [];\n        }\n      });\n    }\n    if (frame.rects && RECT_DATA && RECT_DATA.list) {\n      frame.rects.forEach(frameRect => {\n        const rect = RECT_DATA.list.find(r => r.id === frameRect.id);\n        if (rect) {\n          rect.x = frameRect.x;\n          rect.y = frameRect.y;\n          rect.width = frameRect.width;\n          rect.height = frameRect.height;\n          rect.rotation = frameRect.rotation || 0;\n        }\n      });\n    }\n    if (frame.texts && TEXT_DATA && TEXT_DATA.list) {\n      frame.texts.forEach(frameText => {\n        const text = TEXT_DATA.list.find(t => t.id === frameText.id);\n        if (text) {\n          text.x = frameText.x;\n          text.y = frameText.y;\n        }\n      });\n    }\n    forgeTheTopology();\n    updateMinimap();\n    document.getElementById('recording-time').textContent = formatRecordingTime(frame.time);\n  }\n}\n\nfunction renderRecordingsList() {\n  const list = document.getElementById('recordings-list');\n  if (RECORDING_STATE.recordings.length === 0) {\n    list.innerHTML = '<div style=\"text-align: center; padding: 40px; color: var(--text-soft);\">' + t(\"emptyStates.noRecordingsYet\") + '</div>';\n    return;\n  }\n  list.innerHTML = RECORDING_STATE.recordings.map((rec, i) => `\n    <div style=\"display:flex;align-items:center;gap:10px;padding:10px;background:var(--panel);border-radius:6px;border:1px solid ${rec.id === RECORDING_STATE.currentRecording?.id ? 'var(--accent)' : 'var(--edge-main)'};\">\n      <input type=\"radio\" name=\"recording-select\" value=\"${rec.id}\" ${rec.id === RECORDING_STATE.currentRecording?.id ? 'checked' : ''} onchange=\"selectRecording('${rec.id}')\">\n      <div style=\"flex:1;\">\n        <input type=\"text\" value=\"${rec.name}\" style=\"background:transparent;border:none;color:var(--text-main);font-weight:600;width:100%;\" onchange=\"renameRecording('${rec.id}', this.value)\">\n        <div style=\"font-size:11px;color:var(--text-soft);\">${formatRecordingTime(rec.duration)} | ${rec.frames.length} frames | ${new Date(rec.created).toLocaleDateString()}</div>\n      </div>\n      <button onclick=\"playSelectedRecording('${rec.id}')\" style=\"padding:4px 8px;background:var(--btn-bg);border:1px solid var(--edge-main);border-radius:4px;cursor:pointer;\">▶</button>\n    </div>\n  `).join('');\n}\n\nfunction selectRecording(id) {\n  RECORDING_STATE.currentRecording = RECORDING_STATE.recordings.find(r => r.id === id) || null;\n  renderRecordingsList();\n}\n\nfunction playSelectedRecording(id) {\n  selectRecording(id);\n  document.getElementById('recordings-modal').classList.remove('active');\n  playRecording();\n}\n\nfunction renameRecording(id, newName) {\n  const rec = RECORDING_STATE.recordings.find(r => r.id === id);\n  if (rec) rec.name = newName;\n}\n\nasync function deleteSelectedRecording() {\n  if (!RECORDING_STATE.currentRecording) {\n    showAlert(t(\"dialogs.selectRecordingToDelete\"));\n    return;\n  }\n  if (!await showConfirm(t(\"dialogs.deleteRecording\", { name: RECORDING_STATE.currentRecording.name }))) return;\n  RECORDING_STATE.recordings = RECORDING_STATE.recordings.filter(r => r.id !== RECORDING_STATE.currentRecording.id);\n  RECORDING_STATE.currentRecording = RECORDING_STATE.recordings[0] || null;\n  renderRecordingsList();\n  logAuditEvent('recording', 'Deleted recording');\n}\n\nfunction exportRecordingJSON() {\n  if (!RECORDING_STATE.currentRecording) {\n    showAlert(t(\"dialogs.selectRecordingToExport\"));\n    return;\n  }\n  const json = JSON.stringify(RECORDING_STATE.currentRecording, null, 2);\n  const blob = new Blob([json], { type: 'application/json' });\n  const url = URL.createObjectURL(blob);\n  const a = document.createElement('a');\n  a.href = url;\n  a.download = (RECORDING_STATE.currentRecording.name || 'recording').replace(/[^a-z0-9]/gi, '-') + '.json';\n  a.click();\n  URL.revokeObjectURL(url);\n  logAuditEvent('export', `Exported recording: ${RECORDING_STATE.currentRecording.name}`);\n}\n\nfunction importRecordingJSON(input) {\n  const file = input.files[0];\n  if (!file) return;\n  const reader = new FileReader();\n  reader.onload = (e) => {\n    try {\n      const rec = JSON.parse(e.target.result);\n      if (!rec.frames || !Array.isArray(rec.frames)) {\n        throw new Error('Invalid recording format');\n      }\n      rec.id = 'rec-' + Date.now();\n      rec.name = rec.name || 'Imported Recording';\n      RECORDING_STATE.recordings.push(rec);\n      RECORDING_STATE.currentRecording = rec;\n      renderRecordingsList();\n      logAuditEvent('import', `Imported recording: ${rec.name}`);\n      showAlert(t(\"dialogs.recordingImported\", { name: rec.name }));\n    } catch (err) {\n      showAlert(t(\"dialogs.importRecordingFailed\", { error: err.message }));\n    }\n  };\n  reader.readAsText(file);\n  input.value = '';\n}\n\nfunction startVideoRecording() {\n  if (RECORDING_STATE.isVideoRecording) return;\n\n  const svg = document.getElementById('map');\n  const svgRect = svg.getBoundingClientRect();\n  const width = Math.min(1280, svgRect.width);\n  const height = Math.round(width * svgRect.height / svgRect.width);\n\n  const canvas = document.createElement('canvas');\n  canvas.width = width;\n  canvas.height = height;\n  const ctx = canvas.getContext('2d');\n\n  RECORDING_STATE.videoCanvas = canvas;\n  RECORDING_STATE.videoCtx = ctx;\n  RECORDING_STATE.videoChunks = [];\n\n  const stream = canvas.captureStream(30);\n  const mimeType = MediaRecorder.isTypeSupported('video/webm;codecs=vp9') ? 'video/webm;codecs=vp9' :\n                   MediaRecorder.isTypeSupported('video/webm;codecs=vp8') ? 'video/webm;codecs=vp8' :\n                   MediaRecorder.isTypeSupported('video/webm') ? 'video/webm' : 'video/mp4';\n\n  const recorder = new MediaRecorder(stream, { mimeType, videoBitsPerSecond: 10000000 });\n\n  recorder.ondataavailable = (e) => {\n    if (e.data.size > 0) {\n      RECORDING_STATE.videoChunks.push(e.data);\n    }\n  };\n\n  recorder.onstop = () => {\n    const blob = new Blob(RECORDING_STATE.videoChunks, { type: mimeType });\n    const url = URL.createObjectURL(blob);\n    const a = document.createElement('a');\n    a.href = url;\n    const ext = mimeType.includes('webm') ? 'webm' : 'mp4';\n    a.download = `topology-recording-${Date.now()}.${ext}`;\n    a.click();\n    URL.revokeObjectURL(url);\n    logAuditEvent('export', `Exported video recording`);\n  };\n\n  RECORDING_STATE.videoRecorder = recorder;\n  RECORDING_STATE.isVideoRecording = true;\n  recorder.start(100);\n\n  function renderFrame() {\n    if (!RECORDING_STATE.isVideoRecording) return;\n\n    const svg = document.getElementById('map');\n    const svgData = new XMLSerializer().serializeToString(svg);\n    const svgBlob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' });\n    const url = URL.createObjectURL(svgBlob);\n\n    const img = new Image();\n    img.onload = () => {\n      ctx.fillStyle = PAGE_STATE.background || '#050608';\n      ctx.fillRect(0, 0, width, height);\n      ctx.drawImage(img, 0, 0, width, height);\n      URL.revokeObjectURL(url);\n      if (RECORDING_STATE.isVideoRecording) {\n        RECORDING_STATE.videoAnimFrame = requestAnimationFrame(renderFrame);\n      }\n    };\n    img.onerror = () => {\n      URL.revokeObjectURL(url);\n      if (RECORDING_STATE.isVideoRecording) {\n        RECORDING_STATE.videoAnimFrame = requestAnimationFrame(renderFrame);\n      }\n    };\n    img.src = url;\n  }\n\n  renderFrame();\n\n  document.getElementById('record-btn').style.background = '#f56565';\n  document.getElementById('record-btn').style.color = 'white';\n  document.getElementById('stop-btn').style.display = 'inline-block';\n  logAuditEvent('recording', 'Started video recording');\n}\n\nfunction stopVideoRecording() {\n  if (!RECORDING_STATE.isVideoRecording) return;\n\n  RECORDING_STATE.isVideoRecording = false;\n  if (RECORDING_STATE.videoAnimFrame) {\n    cancelAnimationFrame(RECORDING_STATE.videoAnimFrame);\n  }\n  if (RECORDING_STATE.videoRecorder && RECORDING_STATE.videoRecorder.state !== 'inactive') {\n    RECORDING_STATE.videoRecorder.stop();\n  }\n\n  document.getElementById('record-btn').style.background = 'var(--btn-bg)';\n  document.getElementById('record-btn').style.color = '#f56565';\n  document.getElementById('stop-btn').style.display = 'none';\n  logAuditEvent('recording', 'Stopped video recording');\n}\n\nasync function exportRecordingVideo() {\n  if (!RECORDING_STATE.currentRecording || RECORDING_STATE.currentRecording.frames.length < 2) {\n    showAlert(t(\"dialogs.selectRecordingForVideo\"));\n    return;\n  }\n\n  const rec = RECORDING_STATE.currentRecording;\n  const frames = rec.frames;\n  const svg = document.getElementById('map');\n  const viewBox = svg.getAttribute('viewBox').split(' ').map(Number);\n  const width = 1920;\n  const height = Math.round(width * viewBox[3] / viewBox[2]);\n\n  const canvas = document.createElement('canvas');\n  canvas.width = width;\n  canvas.height = height;\n  const ctx = canvas.getContext('2d');\n\n  const progressDiv = document.createElement('div');\n  progressDiv.style.cssText = 'position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);background:var(--panel);padding:20px 40px;border-radius:8px;border:1px solid var(--edge-main);z-index:99999999;text-align:center;';\n  progressDiv.innerHTML = '<div style=\"color:var(--text-main);margin-bottom:10px;\">' + t(\"emptyStates.generatingVideo\") + '</div><div id=\"video-progress\" style=\"color:var(--accent);margin-bottom:8px;\">0%</div><div style=\"color:var(--text-soft);font-size:12px;\">' + t(\"messages.pleaseWait\") + '</div>';\n  document.body.appendChild(progressDiv);\n\n  const stream = canvas.captureStream(30);\n  const mimeType = MediaRecorder.isTypeSupported('video/webm;codecs=vp9') ? 'video/webm;codecs=vp9' :\n                   MediaRecorder.isTypeSupported('video/webm') ? 'video/webm' : 'video/mp4';\n  const chunks = [];\n  const recorder = new MediaRecorder(stream, { mimeType, videoBitsPerSecond: 10000000 });\n\n  recorder.ondataavailable = (e) => {\n    if (e.data.size > 0) chunks.push(e.data);\n  };\n\n  const recorderReady = new Promise(resolve => {\n    recorder.onstop = resolve;\n  });\n\n  recorder.start(100);\n\n  const originalPositions = JSON.parse(JSON.stringify(savedPositions));\n  const originalWaypoints = {};\n  EDGE_DATA.list.forEach(edge => {\n    originalWaypoints[edge.id] = edge.waypoints ? edge.waypoints.map(wp => ({ x: wp.x, y: wp.y })) : [];\n  });\n\n  for (let i = 0; i < frames.length; i++) {\n    const frame = frames[i];\n    Object.keys(frame.positions).forEach(id => {\n      if (savedPositions[id]) {\n        savedPositions[id].x = frame.positions[id].x;\n        savedPositions[id].y = frame.positions[id].y;\n      }\n    });\n    if (frame.waypoints) {\n      EDGE_DATA.list.forEach(edge => {\n        if (frame.waypoints[edge.id]) {\n          edge.waypoints = frame.waypoints[edge.id].map(wp => ({ x: wp.x, y: wp.y }));\n        } else {\n          edge.waypoints = [];\n        }\n      });\n    }\n    forgeTheTopology();\n\n    const svgData = new XMLSerializer().serializeToString(svg);\n    const svgBlob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' });\n    const url = URL.createObjectURL(svgBlob);\n\n    await new Promise((resolve) => {\n      const img = new Image();\n      img.onload = () => {\n        ctx.fillStyle = PAGE_STATE.panel || '#0b0e13';\n        ctx.fillRect(0, 0, width, height);\n        ctx.drawImage(img, 0, 0, width, height);\n        URL.revokeObjectURL(url);\n        document.getElementById('video-progress').textContent = Math.round((i / frames.length) * 100) + '%';\n        resolve();\n      };\n      img.onerror = () => {\n        URL.revokeObjectURL(url);\n        resolve();\n      };\n      img.src = url;\n    });\n\n    const frameDelay = i < frames.length - 1 ? (frames[i + 1].time - frame.time) : 100;\n    await new Promise(r => setTimeout(r, Math.max(33, frameDelay / 2)));\n  }\n\n  recorder.stop();\n  await recorderReady;\n\n  Object.keys(originalPositions).forEach(id => {\n    if (savedPositions[id]) {\n      savedPositions[id].x = originalPositions[id].x;\n      savedPositions[id].y = originalPositions[id].y;\n    }\n  });\n  EDGE_DATA.list.forEach(edge => {\n    if (originalWaypoints[edge.id]) {\n      edge.waypoints = originalWaypoints[edge.id].map(wp => ({ x: wp.x, y: wp.y }));\n    }\n  });\n  forgeTheTopology();\n\n  const blob = new Blob(chunks, { type: mimeType });\n  const url = URL.createObjectURL(blob);\n  const a = document.createElement('a');\n  a.href = url;\n  const ext = mimeType.includes('webm') ? 'webm' : 'mp4';\n  a.download = (rec.name || 'recording').replace(/[^a-z0-9]/gi, '-') + '.' + ext;\n  a.click();\n  URL.revokeObjectURL(url);\n\n  progressDiv.remove();\n  logAuditEvent('export', `Exported video: ${rec.name}`);\n}\n\nfunction exportRecordingGIF() {\n  exportRecordingVideo();\n}\n\ndocument.getElementById('record-btn')?.addEventListener('click', () => {\n  const expanded = document.getElementById('recording-expanded');\n  if (expanded.style.display === 'none') {\n    expanded.style.display = 'inline-flex';\n  } else {\n    startRecording();\n  }\n});\ndocument.getElementById('step-record-btn')?.addEventListener('click', () => {\n  if (RECORDING_STATE.isStepRecording) {\n    captureStepFrame();\n  } else {\n    startStepRecording();\n  }\n});\ndocument.getElementById('stop-btn')?.addEventListener('click', () => {\n  if (RECORDING_STATE.isStepRecording) {\n    stopStepRecording();\n  } else {\n    stopRecording();\n  }\n});\ndocument.getElementById('play-btn')?.addEventListener('click', () => {\n  if (RECORDING_STATE.isRecording) {\n    stopRecording();\n    playRecording();\n    return;\n  }\n  if (RECORDING_STATE.isStepRecording) {\n    stopStepRecording();\n    playRecording();\n    return;\n  }\n  const expanded = document.getElementById('recording-expanded');\n  if (expanded.style.display === 'none') {\n    expanded.style.display = 'inline-flex';\n  } else {\n    playRecording();\n  }\n});\ndocument.getElementById('pause-btn')?.addEventListener('click', pauseRecording);\ndocument.getElementById('playback-scrubber')?.addEventListener('input', (e) => {\n  seekRecording(parseFloat(e.target.value));\n});\ndocument.getElementById('playback-speed')?.addEventListener('change', (e) => {\n  RECORDING_STATE.playbackSpeed = parseFloat(e.target.value);\n});\ndocument.getElementById('loop-playback')?.addEventListener('change', (e) => {\n  RECORDING_STATE.loopPlayback = e.target.checked;\n});\ndocument.getElementById('recordings-btn')?.addEventListener('click', () => {\n  renderRecordingsList();\n  document.getElementById('trail-enabled-toggle').checked = PAGE_STATE.motionTrailsEnabled !== false;\n  document.getElementById('trail-length-slider').value = PAGE_STATE.motionTrailLength || 8;\n  document.getElementById('trail-length-value').textContent = PAGE_STATE.motionTrailLength || 8;\n  document.getElementById('trail-style-select').value = PAGE_STATE.motionTrailStyle || 'solid';\n  document.getElementById('trail-arrow-toggle').checked = PAGE_STATE.motionTrailArrow !== false;\n  document.getElementById('recordings-modal').classList.add('active');\n});\ndocument.getElementById('recordings-modal-close')?.addEventListener('click', () => {\n  document.getElementById('recordings-modal').classList.remove('active');\n});\ndocument.getElementById('recordings-modal')?.addEventListener('click', (e) => {\n  if (e.target.id === 'recordings-modal') e.target.classList.remove('active');\n});\n\ndocument.getElementById('trail-enabled-toggle')?.addEventListener('change', (e) => {\n  PAGE_STATE.motionTrailsEnabled = e.target.checked;\n  if (!e.target.checked) {\n    RECORDING_STATE.trailHistory = {};\n    forgeTheTopology();\n  }\n});\ndocument.getElementById('trail-length-slider')?.addEventListener('input', (e) => {\n  PAGE_STATE.motionTrailLength = parseInt(e.target.value);\n  document.getElementById('trail-length-value').textContent = e.target.value;\n});\ndocument.getElementById('trail-style-select')?.addEventListener('change', (e) => {\n  PAGE_STATE.motionTrailStyle = e.target.value;\n  forgeTheTopology();\n});\ndocument.getElementById('trail-arrow-toggle')?.addEventListener('change', (e) => {\n  PAGE_STATE.motionTrailArrow = e.target.checked;\n  forgeTheTopology();\n});\n\ndocument.getElementById('node-trail-enabled')?.addEventListener('change', (e) => {\n  if (currentNodeId && NODE_DATA[currentNodeId]) {\n    NODE_DATA[currentNodeId].trailEnabled = e.target.checked;\n  }\n});\n\nfunction renderCanvasTemplate(svg, ns, template, width, height, padding, templateColor) {\n  const color = (templateColor || PAGE_STATE.canvasGrid || \"#475569\") + \"66\";\n  const majorColor = (templateColor || PAGE_STATE.canvasGrid || \"#475569\") + \"99\";\n  const w = width - padding * 2;\n  const h = height - padding * 2;\n  const cx = padding + w / 2;\n  const cy = padding + h / 2;\n  const templateGroup = document.createElementNS(ns, \"g\");\n  templateGroup.id = \"canvas-template\";\n\n  switch (template) {\n    case \"dots\": {\n      const spacing = PAGE_STATE.canvasGridSize || 50;\n      for (let x = padding; x <= width - padding; x += spacing) {\n        for (let y = padding; y <= height - padding; y += spacing) {\n          const dot = document.createElementNS(ns, \"circle\");\n          dot.setAttribute(\"cx\", x);\n          dot.setAttribute(\"cy\", y);\n          dot.setAttribute(\"r\", 2);\n          dot.setAttribute(\"fill\", color);\n          templateGroup.appendChild(dot);\n        }\n      }\n      break;\n    }\n    case \"blueprint\": {\n      const spacing = 25;\n      const majorSpacing = 100;\n      for (let x = padding; x <= width - padding; x += spacing) {\n        const line = document.createElementNS(ns, \"line\");\n        line.setAttribute(\"x1\", x);\n        line.setAttribute(\"y1\", padding);\n        line.setAttribute(\"x2\", x);\n        line.setAttribute(\"y2\", height - padding);\n        line.setAttribute(\"stroke\", (x - padding) % majorSpacing === 0 ? majorColor : color);\n        line.setAttribute(\"stroke-width\", (x - padding) % majorSpacing === 0 ? \"1.5\" : \"0.5\");\n        templateGroup.appendChild(line);\n      }\n      for (let y = padding; y <= height - padding; y += spacing) {\n        const line = document.createElementNS(ns, \"line\");\n        line.setAttribute(\"x1\", padding);\n        line.setAttribute(\"y1\", y);\n        line.setAttribute(\"x2\", width - padding);\n        line.setAttribute(\"y2\", y);\n        line.setAttribute(\"stroke\", (y - padding) % majorSpacing === 0 ? majorColor : color);\n        line.setAttribute(\"stroke-width\", (y - padding) % majorSpacing === 0 ? \"1.5\" : \"0.5\");\n        templateGroup.appendChild(line);\n      }\n\n      const border = document.createElementNS(ns, \"rect\");\n      border.setAttribute(\"x\", padding + 20);\n      border.setAttribute(\"y\", padding + 20);\n      border.setAttribute(\"width\", w - 40);\n      border.setAttribute(\"height\", h - 40);\n      border.setAttribute(\"fill\", \"none\");\n      border.setAttribute(\"stroke\", majorColor);\n      border.setAttribute(\"stroke-width\", \"2\");\n      templateGroup.appendChild(border);\n      break;\n    }\n    case \"basketball\": {\n\n      const courtW = Math.min(w * 0.9, h * 0.9 * 1.88);\n      const courtH = courtW / 1.88;\n      const courtX = cx - courtW / 2;\n      const courtY = cy - courtH / 2;\n\n      const court = document.createElementNS(ns, \"rect\");\n      court.setAttribute(\"x\", courtX);\n      court.setAttribute(\"y\", courtY);\n      court.setAttribute(\"width\", courtW);\n      court.setAttribute(\"height\", courtH);\n      court.setAttribute(\"fill\", \"none\");\n      court.setAttribute(\"stroke\", majorColor);\n      court.setAttribute(\"stroke-width\", \"3\");\n      templateGroup.appendChild(court);\n\n      const centerCircle = document.createElementNS(ns, \"circle\");\n      centerCircle.setAttribute(\"cx\", cx);\n      centerCircle.setAttribute(\"cy\", cy);\n      centerCircle.setAttribute(\"r\", courtH * 0.12);\n      centerCircle.setAttribute(\"fill\", \"none\");\n      centerCircle.setAttribute(\"stroke\", color);\n      centerCircle.setAttribute(\"stroke-width\", \"2\");\n      templateGroup.appendChild(centerCircle);\n\n      const centerLine = document.createElementNS(ns, \"line\");\n      centerLine.setAttribute(\"x1\", cx);\n      centerLine.setAttribute(\"y1\", courtY);\n      centerLine.setAttribute(\"x2\", cx);\n      centerLine.setAttribute(\"y2\", courtY + courtH);\n      centerLine.setAttribute(\"stroke\", color);\n      centerLine.setAttribute(\"stroke-width\", \"2\");\n      templateGroup.appendChild(centerLine);\n\n      const keyW = courtW * 0.19;\n      const keyH = courtH * 0.38;\n      [courtX, courtX + courtW - keyW].forEach(kx => {\n        const key = document.createElementNS(ns, \"rect\");\n        key.setAttribute(\"x\", kx);\n        key.setAttribute(\"y\", cy - keyH / 2);\n        key.setAttribute(\"width\", keyW);\n        key.setAttribute(\"height\", keyH);\n        key.setAttribute(\"fill\", \"none\");\n        key.setAttribute(\"stroke\", color);\n        key.setAttribute(\"stroke-width\", \"2\");\n        templateGroup.appendChild(key);\n\n        const ftCircle = document.createElementNS(ns, \"circle\");\n        ftCircle.setAttribute(\"cx\", kx === courtX ? kx + keyW : kx);\n        ftCircle.setAttribute(\"cy\", cy);\n        ftCircle.setAttribute(\"r\", keyH * 0.32);\n        ftCircle.setAttribute(\"fill\", \"none\");\n        ftCircle.setAttribute(\"stroke\", color);\n        ftCircle.setAttribute(\"stroke-width\", \"2\");\n        templateGroup.appendChild(ftCircle);\n      });\n\n      const arcRadius = courtH * 0.48;\n\n      const leftArc = document.createElementNS(ns, \"path\");\n      const leftCenterX = courtX + courtW * 0.055;\n      leftArc.setAttribute(\"d\", `M ${courtX} ${courtY + courtH * 0.06} L ${leftCenterX} ${cy - arcRadius} A ${arcRadius} ${arcRadius} 0 0 1 ${leftCenterX} ${cy + arcRadius} L ${courtX} ${courtY + courtH * 0.94}`);\n      leftArc.setAttribute(\"fill\", \"none\");\n      leftArc.setAttribute(\"stroke\", color);\n      leftArc.setAttribute(\"stroke-width\", \"2\");\n      templateGroup.appendChild(leftArc);\n\n      const rightArc = document.createElementNS(ns, \"path\");\n      const rightCenterX = courtX + courtW * 0.945;\n      rightArc.setAttribute(\"d\", `M ${courtX + courtW} ${courtY + courtH * 0.06} L ${rightCenterX} ${cy - arcRadius} A ${arcRadius} ${arcRadius} 0 0 0 ${rightCenterX} ${cy + arcRadius} L ${courtX + courtW} ${courtY + courtH * 0.94}`);\n      rightArc.setAttribute(\"fill\", \"none\");\n      rightArc.setAttribute(\"stroke\", color);\n      rightArc.setAttribute(\"stroke-width\", \"2\");\n      templateGroup.appendChild(rightArc);\n      break;\n    }\n    case \"football\": {\n      const fieldW = Math.min(w * 0.9, h * 0.9 * 2.25);\n      const fieldH = fieldW / 2.25;\n      const fieldX = cx - fieldW / 2;\n      const fieldY = cy - fieldH / 2;\n      const field = document.createElementNS(ns, \"rect\");\n      field.setAttribute(\"x\", fieldX);\n      field.setAttribute(\"y\", fieldY);\n      field.setAttribute(\"width\", fieldW);\n      field.setAttribute(\"height\", fieldH);\n      field.setAttribute(\"fill\", \"none\");\n      field.setAttribute(\"stroke\", majorColor);\n      field.setAttribute(\"stroke-width\", \"3\");\n      templateGroup.appendChild(field);\n      const endZoneW = fieldW / 12;\n      [fieldX, fieldX + fieldW - endZoneW].forEach(ex => {\n        const ez = document.createElementNS(ns, \"rect\");\n        ez.setAttribute(\"x\", ex);\n        ez.setAttribute(\"y\", fieldY);\n        ez.setAttribute(\"width\", endZoneW);\n        ez.setAttribute(\"height\", fieldH);\n        ez.setAttribute(\"fill\", color);\n        ez.setAttribute(\"fill-opacity\", \"0.2\");\n        ez.setAttribute(\"stroke\", color);\n        ez.setAttribute(\"stroke-width\", \"2\");\n        templateGroup.appendChild(ez);\n      });\n\n      const playFieldW = fieldW - 2 * endZoneW;\n      for (let i = 1; i < 10; i++) {\n        const x = fieldX + endZoneW + (playFieldW * i / 10);\n        const line = document.createElementNS(ns, \"line\");\n        line.setAttribute(\"x1\", x);\n        line.setAttribute(\"y1\", fieldY);\n        line.setAttribute(\"x2\", x);\n        line.setAttribute(\"y2\", fieldY + fieldH);\n        line.setAttribute(\"stroke\", color);\n        line.setAttribute(\"stroke-width\", i === 5 ? \"3\" : \"1.5\");\n        templateGroup.appendChild(line);\n      }\n\n      const hashY1 = fieldY + fieldH * 0.3;\n      const hashY2 = fieldY + fieldH * 0.7;\n      for (let i = 0; i <= 100; i += 5) {\n        const x = fieldX + endZoneW + (playFieldW * i / 100);\n        [hashY1, hashY2].forEach(hy => {\n          const hash = document.createElementNS(ns, \"line\");\n          hash.setAttribute(\"x1\", x - 5);\n          hash.setAttribute(\"y1\", hy);\n          hash.setAttribute(\"x2\", x + 5);\n          hash.setAttribute(\"y2\", hy);\n          hash.setAttribute(\"stroke\", color);\n          hash.setAttribute(\"stroke-width\", \"1\");\n          templateGroup.appendChild(hash);\n        });\n      }\n      break;\n    }\n    case \"soccer\": {\n\n      const pitchW = Math.min(w * 0.9, h * 0.9 * 1.54);\n      const pitchH = pitchW / 1.54;\n      const pitchX = cx - pitchW / 2;\n      const pitchY = cy - pitchH / 2;\n\n      const pitch = document.createElementNS(ns, \"rect\");\n      pitch.setAttribute(\"x\", pitchX);\n      pitch.setAttribute(\"y\", pitchY);\n      pitch.setAttribute(\"width\", pitchW);\n      pitch.setAttribute(\"height\", pitchH);\n      pitch.setAttribute(\"fill\", \"none\");\n      pitch.setAttribute(\"stroke\", majorColor);\n      pitch.setAttribute(\"stroke-width\", \"3\");\n      templateGroup.appendChild(pitch);\n\n      const centerLine = document.createElementNS(ns, \"line\");\n      centerLine.setAttribute(\"x1\", cx);\n      centerLine.setAttribute(\"y1\", pitchY);\n      centerLine.setAttribute(\"x2\", cx);\n      centerLine.setAttribute(\"y2\", pitchY + pitchH);\n      centerLine.setAttribute(\"stroke\", color);\n      centerLine.setAttribute(\"stroke-width\", \"2\");\n      templateGroup.appendChild(centerLine);\n\n      const centerCircle = document.createElementNS(ns, \"circle\");\n      centerCircle.setAttribute(\"cx\", cx);\n      centerCircle.setAttribute(\"cy\", cy);\n      centerCircle.setAttribute(\"r\", pitchH * 0.14);\n      centerCircle.setAttribute(\"fill\", \"none\");\n      centerCircle.setAttribute(\"stroke\", color);\n      centerCircle.setAttribute(\"stroke-width\", \"2\");\n      templateGroup.appendChild(centerCircle);\n\n      const penW = pitchW * 0.157;\n      const penH = pitchH * 0.62;\n      [pitchX, pitchX + pitchW - penW].forEach(px => {\n        const pen = document.createElementNS(ns, \"rect\");\n        pen.setAttribute(\"x\", px);\n        pen.setAttribute(\"y\", cy - penH / 2);\n        pen.setAttribute(\"width\", penW);\n        pen.setAttribute(\"height\", penH);\n        pen.setAttribute(\"fill\", \"none\");\n        pen.setAttribute(\"stroke\", color);\n        pen.setAttribute(\"stroke-width\", \"2\");\n        templateGroup.appendChild(pen);\n\n        const goalW = penW * 0.35;\n        const goalH = penH * 0.45;\n        const goal = document.createElementNS(ns, \"rect\");\n        goal.setAttribute(\"x\", px === pitchX ? px : px + penW - goalW);\n        goal.setAttribute(\"y\", cy - goalH / 2);\n        goal.setAttribute(\"width\", goalW);\n        goal.setAttribute(\"height\", goalH);\n        goal.setAttribute(\"fill\", \"none\");\n        goal.setAttribute(\"stroke\", color);\n        goal.setAttribute(\"stroke-width\", \"2\");\n        templateGroup.appendChild(goal);\n      });\n      break;\n    }\n    case \"hockey\": {\n\n      const rinkW = Math.min(w * 0.9, h * 0.9 * 2.35);\n      const rinkH = rinkW / 2.35;\n      const rinkX = cx - rinkW / 2;\n      const rinkY = cy - rinkH / 2;\n      const cornerR = rinkH * 0.33;\n\n      const rink = document.createElementNS(ns, \"rect\");\n      rink.setAttribute(\"x\", rinkX);\n      rink.setAttribute(\"y\", rinkY);\n      rink.setAttribute(\"width\", rinkW);\n      rink.setAttribute(\"height\", rinkH);\n      rink.setAttribute(\"rx\", cornerR);\n      rink.setAttribute(\"fill\", \"none\");\n      rink.setAttribute(\"stroke\", majorColor);\n      rink.setAttribute(\"stroke-width\", \"3\");\n      templateGroup.appendChild(rink);\n\n      const centerLine = document.createElementNS(ns, \"line\");\n      centerLine.setAttribute(\"x1\", cx);\n      centerLine.setAttribute(\"y1\", rinkY);\n      centerLine.setAttribute(\"x2\", cx);\n      centerLine.setAttribute(\"y2\", rinkY + rinkH);\n      centerLine.setAttribute(\"stroke\", \"#cc3333\" + \"99\");\n      centerLine.setAttribute(\"stroke-width\", \"3\");\n      templateGroup.appendChild(centerLine);\n\n      const blueLineOffset = rinkW * 0.25;\n      [cx - blueLineOffset, cx + blueLineOffset].forEach(bx => {\n        const blueLine = document.createElementNS(ns, \"line\");\n        blueLine.setAttribute(\"x1\", bx);\n        blueLine.setAttribute(\"y1\", rinkY);\n        blueLine.setAttribute(\"x2\", bx);\n        blueLine.setAttribute(\"y2\", rinkY + rinkH);\n        blueLine.setAttribute(\"stroke\", \"#3366cc\" + \"99\");\n        blueLine.setAttribute(\"stroke-width\", \"3\");\n        templateGroup.appendChild(blueLine);\n      });\n\n      const centerCircle = document.createElementNS(ns, \"circle\");\n      centerCircle.setAttribute(\"cx\", cx);\n      centerCircle.setAttribute(\"cy\", cy);\n      centerCircle.setAttribute(\"r\", rinkH * 0.18);\n      centerCircle.setAttribute(\"fill\", \"none\");\n      centerCircle.setAttribute(\"stroke\", \"#3366cc\" + \"99\");\n      centerCircle.setAttribute(\"stroke-width\", \"2\");\n      templateGroup.appendChild(centerCircle);\n\n      const faceoffR = rinkH * 0.18;\n      const faceoffPositions = [\n        [rinkX + rinkW * 0.15, cy - rinkH * 0.25],\n        [rinkX + rinkW * 0.15, cy + rinkH * 0.25],\n        [rinkX + rinkW * 0.85, cy - rinkH * 0.25],\n        [rinkX + rinkW * 0.85, cy + rinkH * 0.25]\n      ];\n      faceoffPositions.forEach(([fx, fy]) => {\n        const circle = document.createElementNS(ns, \"circle\");\n        circle.setAttribute(\"cx\", fx);\n        circle.setAttribute(\"cy\", fy);\n        circle.setAttribute(\"r\", faceoffR);\n        circle.setAttribute(\"fill\", \"none\");\n        circle.setAttribute(\"stroke\", \"#cc3333\" + \"99\");\n        circle.setAttribute(\"stroke-width\", \"2\");\n        templateGroup.appendChild(circle);\n      });\n      [rinkX + rinkW * 0.045, rinkX + rinkW * 0.955].forEach((gx, i) => {\n        const crease = document.createElementNS(ns, \"path\");\n        const creaseR = rinkH * 0.08;\n        if (i === 0) {\n          crease.setAttribute(\"d\", `M ${gx} ${cy - creaseR} A ${creaseR} ${creaseR} 0 0 1 ${gx} ${cy + creaseR}`);\n        } else {\n          crease.setAttribute(\"d\", `M ${gx} ${cy - creaseR} A ${creaseR} ${creaseR} 0 0 0 ${gx} ${cy + creaseR}`);\n        }\n        crease.setAttribute(\"fill\", \"#3366cc\" + \"33\");\n        crease.setAttribute(\"stroke\", \"#cc3333\" + \"99\");\n        crease.setAttribute(\"stroke-width\", \"2\");\n        templateGroup.appendChild(crease);\n      });\n      break;\n    }\n    case \"baseball\": {\n      const diamondSize = Math.min(w * 0.7, h * 0.7);\n      const baseX = cx;\n      const baseY = cy + diamondSize * 0.3;\n      const baseDistance = diamondSize * 0.35;\n      const diamond = document.createElementNS(ns, \"polygon\");\n      const homeX = baseX, homeY = baseY;\n      const firstX = baseX + baseDistance, firstY = baseY - baseDistance;\n      const secondX = baseX, secondY = baseY - baseDistance * 2;\n      const thirdX = baseX - baseDistance, thirdY = baseY - baseDistance;\n      diamond.setAttribute(\"points\", `${homeX},${homeY} ${firstX},${firstY} ${secondX},${secondY} ${thirdX},${thirdY}`);\n      diamond.setAttribute(\"fill\", \"none\");\n      diamond.setAttribute(\"stroke\", color);\n      diamond.setAttribute(\"stroke-width\", \"2\");\n      templateGroup.appendChild(diamond);\n      [[homeX, homeY], [firstX, firstY], [secondX, secondY], [thirdX, thirdY]].forEach(([bx, by], i) => {\n        const base = document.createElementNS(ns, \"rect\");\n        const baseSize = i === 0 ? 15 : 12;\n        base.setAttribute(\"x\", bx - baseSize / 2);\n        base.setAttribute(\"y\", by - baseSize / 2);\n        base.setAttribute(\"width\", baseSize);\n        base.setAttribute(\"height\", baseSize);\n        base.setAttribute(\"transform\", `rotate(45 ${bx} ${by})`);\n        base.setAttribute(\"fill\", i === 0 ? majorColor : color);\n        base.setAttribute(\"stroke\", majorColor);\n        base.setAttribute(\"stroke-width\", \"2\");\n        templateGroup.appendChild(base);\n      });\n\n      const moundX = baseX;\n      const moundY = baseY - baseDistance * 0.67;\n      const mound = document.createElementNS(ns, \"circle\");\n      mound.setAttribute(\"cx\", moundX);\n      mound.setAttribute(\"cy\", moundY);\n      mound.setAttribute(\"r\", 20);\n      mound.setAttribute(\"fill\", color);\n      mound.setAttribute(\"fill-opacity\", \"0.3\");\n      mound.setAttribute(\"stroke\", color);\n      mound.setAttribute(\"stroke-width\", \"2\");\n      templateGroup.appendChild(mound);\n      const outfieldR = diamondSize * 0.6;\n      const outfield = document.createElementNS(ns, \"path\");\n      outfield.setAttribute(\"d\", `M ${baseX - outfieldR * 0.95} ${baseY - outfieldR * 0.3} A ${outfieldR} ${outfieldR} 0 0 1 ${baseX + outfieldR * 0.95} ${baseY - outfieldR * 0.3}`);\n      outfield.setAttribute(\"fill\", \"none\");\n      outfield.setAttribute(\"stroke\", color);\n      outfield.setAttribute(\"stroke-width\", \"2\");\n      outfield.setAttribute(\"stroke-dasharray\", \"10,5\");\n      templateGroup.appendChild(outfield);\n      const foulLength = diamondSize * 0.8;\n      [[-1, -1], [1, -1]].forEach(([dx, dy]) => {\n        const foul = document.createElementNS(ns, \"line\");\n        foul.setAttribute(\"x1\", homeX);\n        foul.setAttribute(\"y1\", homeY);\n        foul.setAttribute(\"x2\", homeX + dx * foulLength);\n        foul.setAttribute(\"y2\", homeY + dy * foulLength * 0.7);\n        foul.setAttribute(\"stroke\", color);\n        foul.setAttribute(\"stroke-width\", \"2\");\n        templateGroup.appendChild(foul);\n      });\n      break;\n    }\n    case \"tennis\": {\n      const courtW = Math.min(w * 0.9, h * 0.9 * 2.17);\n      const courtH = courtW / 2.17;\n      const courtX = cx - courtW / 2;\n      const courtY = cy - courtH / 2;\n      const court = document.createElementNS(ns, \"rect\");\n      court.setAttribute(\"x\", courtX);\n      court.setAttribute(\"y\", courtY);\n      court.setAttribute(\"width\", courtW);\n      court.setAttribute(\"height\", courtH);\n      court.setAttribute(\"fill\", \"none\");\n      court.setAttribute(\"stroke\", majorColor);\n      court.setAttribute(\"stroke-width\", \"3\");\n      templateGroup.appendChild(court);\n\n      const singlesInset = courtH * 0.125;\n      const singles = document.createElementNS(ns, \"rect\");\n      singles.setAttribute(\"x\", courtX);\n      singles.setAttribute(\"y\", courtY + singlesInset);\n      singles.setAttribute(\"width\", courtW);\n      singles.setAttribute(\"height\", courtH - singlesInset * 2);\n      singles.setAttribute(\"fill\", \"none\");\n      singles.setAttribute(\"stroke\", color);\n      singles.setAttribute(\"stroke-width\", \"2\");\n      templateGroup.appendChild(singles);\n\n      const net = document.createElementNS(ns, \"line\");\n      net.setAttribute(\"x1\", cx);\n      net.setAttribute(\"y1\", courtY);\n      net.setAttribute(\"x2\", cx);\n      net.setAttribute(\"y2\", courtY + courtH);\n      net.setAttribute(\"stroke\", majorColor);\n      net.setAttribute(\"stroke-width\", \"3\");\n      templateGroup.appendChild(net);\n\n      const serviceW = courtW * 0.27;\n      const serviceH = (courtH - singlesInset * 2) / 2;\n      [courtX, courtX + courtW - serviceW].forEach(sx => {\n\n        const topBox = document.createElementNS(ns, \"rect\");\n        topBox.setAttribute(\"x\", sx === courtX ? cx - serviceW : cx);\n        topBox.setAttribute(\"y\", courtY + singlesInset);\n        topBox.setAttribute(\"width\", serviceW);\n        topBox.setAttribute(\"height\", serviceH);\n        topBox.setAttribute(\"fill\", \"none\");\n        topBox.setAttribute(\"stroke\", color);\n        topBox.setAttribute(\"stroke-width\", \"2\");\n        templateGroup.appendChild(topBox);\n\n        const botBox = document.createElementNS(ns, \"rect\");\n        botBox.setAttribute(\"x\", sx === courtX ? cx - serviceW : cx);\n        botBox.setAttribute(\"y\", cy);\n        botBox.setAttribute(\"width\", serviceW);\n        botBox.setAttribute(\"height\", serviceH);\n        botBox.setAttribute(\"fill\", \"none\");\n        botBox.setAttribute(\"stroke\", color);\n        botBox.setAttribute(\"stroke-width\", \"2\");\n        templateGroup.appendChild(botBox);\n      });\n      const centerService = document.createElementNS(ns, \"line\");\n      centerService.setAttribute(\"x1\", cx - serviceW);\n      centerService.setAttribute(\"y1\", cy);\n      centerService.setAttribute(\"x2\", cx + serviceW);\n      centerService.setAttribute(\"y2\", cy);\n      centerService.setAttribute(\"stroke\", color);\n      centerService.setAttribute(\"stroke-width\", \"2\");\n      templateGroup.appendChild(centerService);\n      [courtX, courtX + courtW].forEach(bx => {\n        const mark = document.createElementNS(ns, \"line\");\n        mark.setAttribute(\"x1\", bx);\n        mark.setAttribute(\"y1\", cy - 10);\n        mark.setAttribute(\"x2\", bx);\n        mark.setAttribute(\"y2\", cy + 10);\n        mark.setAttribute(\"stroke\", color);\n        mark.setAttribute(\"stroke-width\", \"2\");\n        templateGroup.appendChild(mark);\n      });\n      break;\n    }\n    case \"none\":\n    default:\n      break;\n  }\n  return templateGroup;\n}\n\nfunction updateCanvasStyleOptions() {\n  const mode = PAGE_STATE.mappingMode || 'network';\n  const canvasStyleRow = document.getElementById('canvas-style-row');\n  const canvasStyleSelect = document.getElementById('canvas-style-select');\n  const canvasGridCheckbox = document.getElementById('canvas-grid-enabled');\n\n  if (mode === 'mindmap') {\n    if (canvasStyleRow) canvasStyleRow.style.display = 'none';\n    PAGE_STATE.canvasTemplate = 'none';\n    PAGE_STATE.canvasGridEnabled = false;\n    if (canvasGridCheckbox) canvasGridCheckbox.checked = false;\n  } else if (mode === 'floorplan') {\n    if (canvasStyleRow) canvasStyleRow.style.display = 'none';\n    PAGE_STATE.canvasTemplate = 'blueprint';\n    PAGE_STATE.canvasGridEnabled = true;\n    if (canvasGridCheckbox) canvasGridCheckbox.checked = true;\n  } else {\n    if (canvasStyleRow) canvasStyleRow.style.display = 'contents';\n    const options = CANVAS_OPTIONS[mode] || CANVAS_OPTIONS.network;\n    if (canvasStyleSelect && options.length > 0) {\n      canvasStyleSelect.innerHTML = options.map(o =>\n        `<option value=\"${o.value}\">${o.label}</option>`\n      ).join('');\n      const validValues = options.map(o => o.value);\n      if (!validValues.includes(PAGE_STATE.canvasTemplate)) {\n        PAGE_STATE.canvasTemplate = options[0]?.value || 'grid';\n      }\n      canvasStyleSelect.value = PAGE_STATE.canvasTemplate;\n      PAGE_STATE.canvasGridEnabled = PAGE_STATE.canvasTemplate !== 'none';\n      if (canvasGridCheckbox) canvasGridCheckbox.checked = PAGE_STATE.canvasGridEnabled;\n    }\n  }\n  forgeTheTopology();\n}\n\nfunction updateShapeCategoryOptions() {\n  const mode = PAGE_STATE.mappingMode || 'network';\n  const defaultCat = MODE_DEFAULT_CATEGORIES[mode] || 'network';\n  const catSelect = document.getElementById('shape-category-select');\n  if (catSelect && !catSelect.value) {\n    catSelect.value = defaultCat;\n  }\n  populateShapeSelect();\n}\n\nfunction populateShapeSelect(currentValue) {\n  const catSelect = document.getElementById('shape-category-select');\n  const shapeSelect = document.getElementById('shape-select');\n  if (!catSelect || !shapeSelect) return;\n\n  const category = catSelect.value || 'basic';\n  const shapes = SHAPE_CATEGORIES[category] || SHAPE_CATEGORIES.basic;\n\n  shapeSelect.innerHTML = shapes.map(s =>\n    `<option value=\"${s.value}\">${s.label}</option>`\n  ).join('');\n\n  if (currentValue) {\n    const exists = shapes.some(s => s.value === currentValue);\n    if (exists) {\n      shapeSelect.value = currentValue;\n    }\n  }\n}\n\ndocument.getElementById('mapping-mode-select')?.addEventListener('change', (e) => {\n  PAGE_STATE.mappingMode = e.target.value;\n  updateCanvasStyleOptions();\n  applyMappingModeLabels();\n  updateLayerLabels();\n  const catSelect = document.getElementById('shape-category-select');\n  if (catSelect) {\n    catSelect.value = MODE_DEFAULT_CATEGORIES[e.target.value] || 'network';\n    populateShapeSelect();\n  }\n  wieldThePower();\n  displayTabs();\n});\n\ndocument.getElementById('canvas-style-select')?.addEventListener('change', (e) => {\n  PAGE_STATE.canvasTemplate = e.target.value;\n  PAGE_STATE.canvasGridEnabled = e.target.value !== 'none';\n  forgeTheTopology();\n});\n\ndocument.getElementById('shape-category-select')?.addEventListener('change', (e) => {\n  populateShapeSelect();\n});\n\ndocument.getElementById('shape-select')?.addEventListener('change', (e) => {\n  const shape = e.target.value || 'circle';\n  if (!currentNodeId || !NODE_DATA[currentNodeId]) return;\n  pushUndo('change shape');\n  NODE_DATA[currentNodeId].shape = shape;\n  const fovSection = document.getElementById(\"fov-section\");\n  if (fovSection) {\n    if (typeof hasCoverageZone === 'function' && hasCoverageZone(shape)) {\n      const defaults = getCoverageDefaults(shape);\n      fovSection.style.display = \"block\";\n      document.getElementById(\"fov-angle\").value = defaults.angle;\n      document.getElementById(\"fov-angle-value\").textContent = defaults.angle + \"°\";\n      document.getElementById(\"fov-distance\").value = defaults.distance;\n      document.getElementById(\"fov-distance-value\").textContent = defaults.distance;\n      document.getElementById(\"fov-animation-type\").value = defaults.animationType;\n    } else {\n      fovSection.style.display = \"none\";\n    }\n  }\n  const nodeGroup = document.querySelector(`g[data-node-id=\"${currentNodeId}\"]`);\n  if (nodeGroup) {\n    const oldShape = nodeGroup.querySelector(\".node-circle\");\n    if (oldShape) oldShape.remove();\n    const size = savedSizes[currentNodeId] || getDefaultSize();\n    const newShape = createNodeShape(currentNodeId, size);\n    nodeGroup.insertBefore(newShape, nodeGroup.firstChild);\n  }\n});\n\nfunction applyMappingModeLabels() {\n  const mode = PAGE_STATE.mappingMode || 'network';\n  const labels = LANG.modes[mode] || LANG.modes.network;\n\n  const addNodeBtn = document.getElementById('add-node-btn');\n  const addRackBtn = document.getElementById('add-rack-btn');\n  if (addNodeBtn) addNodeBtn.textContent = '+ ' + labels.node;\n  if (addRackBtn) {\n    if (!labels.rack) {\n      addRackBtn.style.display = 'none';\n    } else {\n      addRackBtn.style.display = '';\n      addRackBtn.textContent = '+ ' + labels.rack;\n    }\n  }\n\n  const nodeIpLabel = document.getElementById('new-node-ip-label');\n  const nodeTagsLabel = document.getElementById('new-node-tags-label');\n  const nodeIpInput = document.getElementById('new-node-ip');\n  const nodeTagsInput = document.getElementById('new-node-tags');\n  if (nodeIpLabel && labels.subtitle) nodeIpLabel.textContent = labels.subtitle;\n  if (nodeTagsLabel && labels.tags) nodeTagsLabel.textContent = labels.tags + ' (comma separated)';\n  if (nodeIpInput && labels.subtitlePlaceholder) nodeIpInput.placeholder = labels.subtitlePlaceholder;\n  if (nodeTagsInput && labels.tagsPlaceholder) nodeTagsInput.placeholder = labels.tagsPlaceholder;\n\n  const rackNameLabel = document.getElementById('new-rack-name-label');\n  const rackIpLabel = document.getElementById('new-rack-ip-label');\n  const rackTagsLabel = document.getElementById('new-rack-tags-label');\n  const rackIpInput = document.getElementById('new-rack-ip');\n  const rackTagsInput = document.getElementById('new-rack-tags');\n  if (rackNameLabel && labels.rack) rackNameLabel.textContent = labels.rack + ' Name';\n  if (rackIpLabel && labels.rackSubtitle) rackIpLabel.textContent = labels.rackSubtitle;\n  if (rackTagsLabel && labels.rackTags) rackTagsLabel.textContent = labels.rackTags + ' (comma separated)';\n  if (rackIpInput && labels.rackSubtitlePlaceholder) rackIpInput.placeholder = labels.rackSubtitlePlaceholder;\n  if (rackTagsInput && labels.rackTagsPlaceholder) rackTagsInput.placeholder = labels.rackTagsPlaceholder;\n\n  const nodeCatSelect = document.getElementById('new-node-category');\n  const rackCatSelect = document.getElementById('new-rack-category');\n  const defaultCat = MODE_DEFAULT_CATEGORIES[mode] || 'basic';\n  if (nodeCatSelect) {\n    nodeCatSelect.value = defaultCat;\n    populateModalShapeSelect('new-node-category', 'new-node-shape');\n  }\n  if (rackCatSelect) {\n    rackCatSelect.value = defaultCat;\n    populateModalShapeSelect('new-rack-category', 'new-rack-shape');\n  }\n\n  const nodeAppearanceLabel = document.getElementById('new-node-appearance-label');\n  const rackAppearanceLabel = document.getElementById('new-rack-appearance-label');\n  if (nodeAppearanceLabel) nodeAppearanceLabel.textContent = labels.node + ' Appearance';\n  if (rackAppearanceLabel && labels.rack) rackAppearanceLabel.textContent = labels.rack + ' Appearance';\n\n  const isNetworkMode = mode === 'network';\n  const macRow = document.getElementById('mac-row');\n  const rackUnitRow = document.getElementById('rack-unit-row');\n  const assignedRackLabel = document.getElementById('assigned-rack-label');\n  if (macRow) macRow.style.display = isNetworkMode ? 'flex' : 'none';\n  if (rackUnitRow) rackUnitRow.style.display = isNetworkMode ? 'flex' : 'none';\n  if (assignedRackLabel && labels.rack) assignedRackLabel.textContent = 'Assigned ' + labels.rack + ':';\n\n  const showPing = mode === 'network' || mode === 'smarthome';\n  const newNodePingSection = document.getElementById('new-node-ping-section');\n  const nodePingSection = document.getElementById('node-ping-section');\n  if (newNodePingSection) newNodePingSection.style.display = showPing ? 'block' : 'none';\n  if (nodePingSection) nodePingSection.style.display = showPing ? 'block' : 'none';\n}\n\nfunction updateLayerLabels() {\n  const mode = PAGE_STATE.mappingMode || 'network';\n  const layers = LANG.ui?.layers?.[mode] || LANG.ui?.layers?.network;\n  if (layers) {\n    const label1 = document.getElementById('layer-1-label');\n    const label2 = document.getElementById('layer-2-label');\n    const label3 = document.getElementById('layer-3-label');\n    const label4 = document.getElementById('layer-4-label');\n    if (label1) label1.textContent = layers.layer1 || 'Layer 1';\n    if (label2) label2.textContent = layers.layer2 || 'Layer 2';\n    if (label3) label3.textContent = layers.layer3 || 'Layer 3';\n    if (label4) label4.textContent = layers.layer4 || 'Layer 4';\n    const opt1 = document.getElementById('node-layer-opt1');\n    const opt2 = document.getElementById('node-layer-opt2');\n    const opt3 = document.getElementById('node-layer-opt3');\n    const opt4 = document.getElementById('node-layer-opt4');\n    if (opt1) opt1.textContent = layers.layer1 || 'Layer 1';\n    if (opt2) opt2.textContent = layers.layer2 || 'Layer 2';\n    if (opt3) opt3.textContent = layers.layer3 || 'Layer 3';\n    if (opt4) opt4.textContent = layers.layer4 || 'Layer 4';\n  }\n}\n\nsetTimeout(() => {\n  const catSelect = document.getElementById('shape-category-select');\n  if (catSelect) {\n    catSelect.value = MODE_DEFAULT_CATEGORIES[PAGE_STATE.mappingMode || 'network'] || 'network';\n    populateShapeSelect();\n  }\n  updateCanvasStyleOptions();\n  updateLayerLabels();\n}, 100);\n\nconst MODAL_SHAPE_PREVIEWS = {\n  'circle': '<circle cx=\"16\" cy=\"16\" r=\"11\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"/>',\n  'square': '<rect x=\"5\" y=\"5\" width=\"22\" height=\"22\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"/>',\n  'rectangle': '<rect x=\"3\" y=\"8\" width=\"26\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"/>',\n  'triangle': '<polygon points=\"16,4 28,28 4,28\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"/>',\n  'hexagon': '<polygon points=\"16,3 27,9 27,22 16,28 5,22 5,9\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"/>',\n  'diamond': '<polygon points=\"16,3 29,16 16,29 3,16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"/>',\n  'star': '<polygon points=\"16,3 19,12 29,12 21,18 24,28 16,22 8,28 11,18 3,12 13,12\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"/>',\n  'stop-sign': '<polygon points=\"11,3 21,3 29,11 29,21 21,29 11,29 3,21 3,11\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"/>',\n  'octagon': '<polygon points=\"11,3 21,3 29,11 29,21 21,29 11,29 3,21 3,11\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"/>',\n  'pentagon': '<polygon points=\"16,3 29,13 24,28 8,28 3,13\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"/>',\n  'cross': '<path d=\"M12,4 h8 v8 h8 v8 h-8 v8 h-8 v-8 h-8 v-8 h8 z\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"/>',\n  'rounded-square': '<rect x=\"5\" y=\"5\" width=\"22\" height=\"22\" rx=\"5\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"/>',\n  'pill': '<rect x=\"4\" y=\"10\" width=\"24\" height=\"12\" rx=\"6\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"/>',\n  'parallelogram': '<polygon points=\"8,6 28,6 24,26 4,26\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"/>',\n  'trapezoid': '<polygon points=\"8,6 24,6 28,26 4,26\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"/>',\n  'server': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"6\" y=\"4\" width=\"20\" height=\"24\" rx=\"2\"/><line x1=\"6\" y1=\"12\" x2=\"26\" y2=\"12\"/><line x1=\"6\" y1=\"20\" x2=\"26\" y2=\"20\"/><circle cx=\"22\" cy=\"8\" r=\"1.5\" fill=\"currentColor\"/><circle cx=\"22\" cy=\"16\" r=\"1.5\" fill=\"currentColor\"/><circle cx=\"22\" cy=\"24\" r=\"1.5\" fill=\"currentColor\"/></g>',\n  'pc': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"5\" y=\"4\" width=\"22\" height=\"16\" rx=\"1\"/><line x1=\"12\" y1=\"24\" x2=\"20\" y2=\"24\"/><line x1=\"16\" y1=\"20\" x2=\"16\" y2=\"24\"/></g>',\n  'laptop': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"6\" y=\"6\" width=\"20\" height=\"14\" rx=\"1\"/><path d=\"M3,22 h26 l-2,4 h-22 z\"/></g>',\n  'phone': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"9\" y=\"3\" width=\"14\" height=\"26\" rx=\"2\"/><line x1=\"13\" y1=\"25\" x2=\"19\" y2=\"25\"/></g>',\n  'printer': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"8\" y=\"12\" width=\"16\" height=\"10\" rx=\"1\"/><rect x=\"10\" y=\"4\" width=\"12\" height=\"8\"/><rect x=\"10\" y=\"22\" width=\"12\" height=\"6\"/></g>',\n  'pi': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"5\" y=\"8\" width=\"22\" height=\"16\" rx=\"2\"/><circle cx=\"10\" cy=\"13\" r=\"1.5\" fill=\"currentColor\"/><rect x=\"18\" y=\"11\" width=\"6\" height=\"4\" rx=\"0.5\"/><line x1=\"14\" y1=\"20\" x2=\"22\" y2=\"20\"/></g>',\n  'sensor': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><circle cx=\"16\" cy=\"16\" r=\"6\"/><circle cx=\"16\" cy=\"16\" r=\"2\" fill=\"currentColor\"/><path d=\"M8,8 Q4,16 8,24\"/><path d=\"M24,8 Q28,16 24,24\"/></g>',\n  'router': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"4\" y=\"14\" width=\"24\" height=\"12\" rx=\"2\"/><line x1=\"10\" y1=\"14\" x2=\"10\" y2=\"8\"/><line x1=\"16\" y1=\"14\" x2=\"16\" y2=\"6\"/><line x1=\"22\" y1=\"14\" x2=\"22\" y2=\"8\"/><circle cx=\"10\" cy=\"7\" r=\"1.5\" fill=\"currentColor\"/><circle cx=\"16\" cy=\"5\" r=\"1.5\" fill=\"currentColor\"/><circle cx=\"22\" cy=\"7\" r=\"1.5\" fill=\"currentColor\"/></g>',\n  'switch': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"3\" y=\"11\" width=\"26\" height=\"10\" rx=\"2\"/><circle cx=\"8\" cy=\"16\" r=\"1.5\" fill=\"currentColor\"/><circle cx=\"13\" cy=\"16\" r=\"1.5\" fill=\"currentColor\"/><circle cx=\"18\" cy=\"16\" r=\"1.5\" fill=\"currentColor\"/><circle cx=\"23\" cy=\"16\" r=\"1.5\" fill=\"currentColor\"/></g>',\n  'firewall': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"5\" y=\"5\" width=\"22\" height=\"22\" rx=\"1\"/><line x1=\"5\" y1=\"11\" x2=\"27\" y2=\"11\"/><line x1=\"5\" y1=\"17\" x2=\"27\" y2=\"17\"/><line x1=\"11\" y1=\"5\" x2=\"11\" y2=\"27\"/><line x1=\"17\" y1=\"5\" x2=\"17\" y2=\"27\"/></g>',\n  'access-point': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><path d=\"M16,18 L10,26 h12 z\"/><circle cx=\"16\" cy=\"16\" r=\"2\" fill=\"currentColor\"/><path d=\"M9,10 Q16,4 23,10\" stroke-linecap=\"round\"/><path d=\"M6,7 Q16,0 26,7\" stroke-linecap=\"round\"/></g>',\n  'load-balancer': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"6\" y=\"12\" width=\"20\" height=\"8\" rx=\"2\"/><line x1=\"16\" y1=\"8\" x2=\"16\" y2=\"12\"/><line x1=\"16\" y1=\"20\" x2=\"8\" y2=\"26\"/><line x1=\"16\" y1=\"20\" x2=\"16\" y2=\"26\"/><line x1=\"16\" y1=\"20\" x2=\"24\" y2=\"26\"/><circle cx=\"16\" cy=\"7\" r=\"2\" fill=\"currentColor\"/></g>',\n  'gateway': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"4\" y=\"10\" width=\"24\" height=\"12\" rx=\"2\"/><line x1=\"4\" y1=\"16\" x2=\"0\" y2=\"16\"/><line x1=\"28\" y1=\"16\" x2=\"32\" y2=\"16\"/><circle cx=\"16\" cy=\"16\" r=\"3\"/></g>',\n  'vpn': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"5\" y=\"8\" width=\"22\" height=\"16\" rx=\"2\"/><path d=\"M16,12 v6 M13,14 h6\" stroke-width=\"2.5\" stroke-linecap=\"round\"/><circle cx=\"11\" cy=\"20\" r=\"1\" fill=\"currentColor\"/><circle cx=\"21\" cy=\"20\" r=\"1\" fill=\"currentColor\"/></g>',\n  'nas': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"6\" y=\"4\" width=\"20\" height=\"24\" rx=\"2\"/><line x1=\"6\" y1=\"10\" x2=\"26\" y2=\"10\"/><line x1=\"6\" y1=\"16\" x2=\"26\" y2=\"16\"/><line x1=\"6\" y1=\"22\" x2=\"26\" y2=\"22\"/><circle cx=\"22\" cy=\"7\" r=\"1\" fill=\"currentColor\"/><circle cx=\"22\" cy=\"13\" r=\"1\" fill=\"currentColor\"/><circle cx=\"22\" cy=\"19\" r=\"1\" fill=\"currentColor\"/><circle cx=\"22\" cy=\"25\" r=\"1\" fill=\"currentColor\"/></g>',\n  'cloud': '<path d=\"M8,22 Q2,22 2,17 Q2,13 6,12 Q6,7 11,6 Q16,5 19,8 Q21,6 24,7 Q28,8 28,12 Q30,13 30,17 Q30,22 24,22 z\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"/>',\n  'database': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><ellipse cx=\"16\" cy=\"8\" rx=\"10\" ry=\"4\"/><path d=\"M6,8 v16 Q6,28 16,28 Q26,28 26,24 v-16\"/><path d=\"M6,14 Q6,18 16,18 Q26,18 26,14\"/></g>',\n  'docker': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><path d=\"M2,16 h6 v-6 h4 v-4 h4 v4 h4 v-4 h4 v8 Q28,24 20,26 Q10,28 4,22 z\"/><rect x=\"10\" y=\"12\" width=\"3\" height=\"3\"/><rect x=\"14\" y=\"12\" width=\"3\" height=\"3\"/><rect x=\"14\" y=\"8\" width=\"3\" height=\"3\"/></g>',\n  'container': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><path d=\"M4,12 L16,6 L28,12 L16,18 z\"/><path d=\"M4,12 v8 L16,26 v-8\"/><path d=\"M28,12 v8 L16,26 v-8\"/></g>',\n  'vm': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"4\" y=\"4\" width=\"24\" height=\"24\" rx=\"2\"/><rect x=\"7\" y=\"7\" width=\"18\" height=\"14\" rx=\"1\"/><line x1=\"12\" y1=\"24\" x2=\"20\" y2=\"24\"/></g>',\n  'kubernetes': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><polygon points=\"16,2 28,9 28,23 16,30 4,23 4,9\"/><circle cx=\"16\" cy=\"16\" r=\"4\"/><line x1=\"16\" y1=\"12\" x2=\"16\" y2=\"4\"/><line x1=\"19\" y1=\"14\" x2=\"26\" y2=\"10\"/><line x1=\"19\" y1=\"18\" x2=\"26\" y2=\"22\"/><line x1=\"16\" y1=\"20\" x2=\"16\" y2=\"28\"/><line x1=\"13\" y1=\"18\" x2=\"6\" y2=\"22\"/><line x1=\"13\" y1=\"14\" x2=\"6\" y2=\"10\"/></g>',\n  'api': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"4\" y=\"6\" width=\"24\" height=\"20\" rx=\"2\"/><path d=\"M10,16 l2,-6 2,6 M10.5,14 h3\" stroke-linecap=\"round\"/><path d=\"M17,10 h3 Q22,10 22,13 Q22,16 20,16 h-3 M17,16 v6\" stroke-linecap=\"round\"/><line x1=\"25\" y1=\"10\" x2=\"25\" y2=\"22\"/></g>',\n  'queue': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"4\" y=\"8\" width=\"24\" height=\"16\" rx=\"2\"/><line x1=\"12\" y1=\"8\" x2=\"12\" y2=\"24\"/><line x1=\"20\" y1=\"8\" x2=\"20\" y2=\"24\"/><path d=\"M6,16 h3 M14,16 h4 M22,16 h4\" stroke-linecap=\"round\"/></g>',\n  'lambda': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M8,6 L16,26 L24,6\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/><line x1=\"6\" y1=\"16\" x2=\"12\" y2=\"16\" stroke-linecap=\"round\"/></g>',\n  'bucket': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><path d=\"M6,8 h20 l-2,18 h-16 z\"/><ellipse cx=\"16\" cy=\"8\" rx=\"10\" ry=\"3\"/></g>',\n  'shield': '<path d=\"M16,3 L27,8 v10 Q27,26 16,29 Q5,26 5,18 v-10 z\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"/>',\n  'camera': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"4\" y=\"10\" width=\"24\" height=\"16\" rx=\"2\"/><circle cx=\"16\" cy=\"18\" r=\"5\"/><circle cx=\"16\" cy=\"18\" r=\"2\" fill=\"currentColor\"/><rect x=\"10\" y=\"6\" width=\"12\" height=\"4\" rx=\"1\"/></g>',\n  'monitor': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"4\" y=\"4\" width=\"24\" height=\"16\" rx=\"2\"/><line x1=\"12\" y1=\"24\" x2=\"20\" y2=\"24\"/><line x1=\"16\" y1=\"20\" x2=\"16\" y2=\"24\"/><path d=\"M8,8 h4 M8,11 h6 M8,14 h5 M18,8 Q20,8 20,14 Q20,16 18,16\" stroke-width=\"1\"/></g>',\n  'thermostat': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><circle cx=\"16\" cy=\"16\" r=\"11\"/><circle cx=\"16\" cy=\"16\" r=\"7\"/><line x1=\"16\" y1=\"9\" x2=\"16\" y2=\"16\" stroke-width=\"2\" stroke-linecap=\"round\"/></g>',\n  'doorbell': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"9\" y=\"3\" width=\"14\" height=\"26\" rx=\"4\"/><circle cx=\"16\" cy=\"14\" r=\"4\"/><circle cx=\"16\" cy=\"22\" r=\"1.5\" fill=\"currentColor\"/></g>',\n  'smart-lock': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"7\" y=\"14\" width=\"18\" height=\"14\" rx=\"2\"/><path d=\"M11,14 v-4 Q11,4 16,4 Q21,4 21,10 v4\"/><circle cx=\"16\" cy=\"21\" r=\"2\" fill=\"currentColor\"/></g>',\n  'smart-bulb': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><path d=\"M11,18 Q6,12 8,7 Q10,3 16,3 Q22,3 24,7 Q26,12 21,18 z\"/><rect x=\"11\" y=\"18\" width=\"10\" height=\"4\" rx=\"1\"/><line x1=\"12\" y1=\"24\" x2=\"20\" y2=\"24\"/><line x1=\"13\" y1=\"26\" x2=\"19\" y2=\"26\"/></g>',\n  'smart-plug': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"6\" y=\"8\" width=\"20\" height=\"16\" rx=\"3\"/><circle cx=\"12\" cy=\"16\" r=\"2\" fill=\"currentColor\"/><circle cx=\"20\" cy=\"16\" r=\"2\" fill=\"currentColor\"/><line x1=\"16\" y1=\"24\" x2=\"16\" y2=\"28\" stroke-width=\"2\"/></g>',\n  'smart-speaker': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><path d=\"M8,28 Q8,8 16,4 Q24,8 24,28 z\"/><circle cx=\"16\" cy=\"20\" r=\"4\"/><circle cx=\"16\" cy=\"20\" r=\"1.5\" fill=\"currentColor\"/></g>',\n  'smart-tv': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"3\" y=\"5\" width=\"26\" height=\"18\" rx=\"1\"/><line x1=\"10\" y1=\"27\" x2=\"22\" y2=\"27\"/><line x1=\"12\" y1=\"23\" x2=\"12\" y2=\"27\"/><line x1=\"20\" y1=\"23\" x2=\"20\" y2=\"27\"/></g>',\n  'hub': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><circle cx=\"16\" cy=\"16\" r=\"5\"/><circle cx=\"16\" cy=\"16\" r=\"2\" fill=\"currentColor\"/><line x1=\"16\" y1=\"5\" x2=\"16\" y2=\"11\"/><line x1=\"16\" y1=\"21\" x2=\"16\" y2=\"27\"/><line x1=\"5\" y1=\"16\" x2=\"11\" y2=\"16\"/><line x1=\"21\" y1=\"16\" x2=\"27\" y2=\"16\"/></g>',\n  'smoke-detector': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><circle cx=\"16\" cy=\"16\" r=\"11\"/><circle cx=\"16\" cy=\"16\" r=\"3\"/><line x1=\"16\" y1=\"5\" x2=\"16\" y2=\"7\"/><line x1=\"16\" y1=\"25\" x2=\"16\" y2=\"27\"/><line x1=\"5\" y1=\"16\" x2=\"7\" y2=\"16\"/><line x1=\"25\" y1=\"16\" x2=\"27\" y2=\"16\"/></g>',\n  'motion-sensor': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><path d=\"M8,24 Q4,12 16,6 Q28,12 24,24\"/><circle cx=\"16\" cy=\"16\" r=\"3\" fill=\"currentColor\"/><path d=\"M12,20 Q10,16 14,12\"/><path d=\"M20,20 Q22,16 18,12\"/></g>',\n  'garage': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><path d=\"M4,28 v-14 L16,4 L28,14 v14 z\"/><rect x=\"8\" y=\"16\" width=\"16\" height=\"12\"/><line x1=\"8\" y1=\"20\" x2=\"24\" y2=\"20\"/><line x1=\"8\" y1=\"24\" x2=\"24\" y2=\"24\"/></g>',\n  'sprinkler': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><line x1=\"16\" y1=\"4\" x2=\"16\" y2=\"14\"/><path d=\"M10,14 h12 v3 Q16,22 10,17 z\"/><path d=\"M8,20 Q6,24 4,26\" stroke-linecap=\"round\"/><path d=\"M16,22 v6\" stroke-linecap=\"round\"/><path d=\"M24,20 Q26,24 28,26\" stroke-linecap=\"round\"/></g>',\n  'vacuum': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><circle cx=\"16\" cy=\"16\" r=\"11\"/><circle cx=\"16\" cy=\"16\" r=\"6\"/><circle cx=\"16\" cy=\"16\" r=\"2\" fill=\"currentColor\"/><line x1=\"16\" y1=\"5\" x2=\"20\" y2=\"3\" stroke-linecap=\"round\"/></g>',\n  'basketball-ball': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><circle cx=\"16\" cy=\"16\" r=\"12\"/><path d=\"M4,16 h24\"/><path d=\"M16,4 v24\"/><path d=\"M6,7 Q16,14 6,25\"/><path d=\"M26,7 Q16,14 26,25\"/></g>',\n  'football-ball': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><ellipse cx=\"16\" cy=\"16\" rx=\"12\" ry=\"8\" transform=\"rotate(-30,16,16)\"/><path d=\"M10,10 L22,22 M12,8 L24,20\" stroke-linecap=\"round\"/></g>',\n  'soccer-ball': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><circle cx=\"16\" cy=\"16\" r=\"12\"/><polygon points=\"16,8 20,12 18,17 14,17 12,12\" fill=\"currentColor\" stroke=\"none\"/></g>',\n  'hockey-puck': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><ellipse cx=\"16\" cy=\"14\" rx=\"12\" ry=\"5\"/><path d=\"M4,14 v4 Q4,23 16,23 Q28,23 28,18 v-4\"/></g>',\n  'baseball-ball': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><circle cx=\"16\" cy=\"16\" r=\"12\"/><path d=\"M8,6 Q14,12 8,26\"/><path d=\"M24,6 Q18,12 24,26\"/></g>',\n  'tennis-ball': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><circle cx=\"16\" cy=\"16\" r=\"12\"/><path d=\"M6,6 Q16,16 6,26\"/><path d=\"M26,6 Q16,16 26,26\"/></g>',\n  'volleyball': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><circle cx=\"16\" cy=\"16\" r=\"12\"/><path d=\"M16,4 Q12,16 16,28\"/><path d=\"M5,10 Q16,14 27,10\"/><path d=\"M5,22 Q16,18 27,22\"/></g>',\n  'rugby-ball': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><ellipse cx=\"16\" cy=\"16\" rx=\"13\" ry=\"8\" transform=\"rotate(-30,16,16)\"/><path d=\"M10,10 L22,22\"/></g>',\n  'golf-ball': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><circle cx=\"16\" cy=\"16\" r=\"10\"/><circle cx=\"14\" cy=\"14\" r=\"0.8\" fill=\"currentColor\"/><circle cx=\"18\" cy=\"12\" r=\"0.8\" fill=\"currentColor\"/><circle cx=\"16\" cy=\"16\" r=\"0.8\" fill=\"currentColor\"/></g>',\n  'frisbee': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><ellipse cx=\"16\" cy=\"16\" rx=\"13\" ry=\"5\"/><ellipse cx=\"16\" cy=\"15\" rx=\"8\" ry=\"3\"/></g>',\n  'cricket-ball': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><circle cx=\"16\" cy=\"16\" r=\"11\"/><path d=\"M10,6 Q16,16 10,26\" stroke-dasharray=\"2,2\"/></g>',\n  'lacrosse-stick': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><line x1=\"8\" y1=\"28\" x2=\"20\" y2=\"6\" stroke-width=\"2\"/><path d=\"M18,8 Q26,4 26,12 Q26,16 20,14\"/><line x1=\"20\" y1=\"9\" x2=\"24\" y2=\"13\"/></g>',\n  'golf-flag': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><line x1=\"10\" y1=\"4\" x2=\"10\" y2=\"28\" stroke-width=\"2\"/><polygon points=\"10,4 26,10 10,16\" fill=\"currentColor\" opacity=\"0.3\" stroke=\"currentColor\"/><ellipse cx=\"16\" cy=\"28\" rx=\"10\" ry=\"2\"/></g>',\n  'tactical-x': '<g stroke=\"currentColor\" stroke-width=\"3\" stroke-linecap=\"round\"><line x1=\"6\" y1=\"6\" x2=\"26\" y2=\"26\"/><line x1=\"26\" y1=\"6\" x2=\"6\" y2=\"26\"/></g>',\n  'tactical-o': '<circle cx=\"16\" cy=\"16\" r=\"10\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"3\"/>',\n  'tactical-star': '<polygon points=\"16,3 19,12 29,12 21,18 24,28 16,22 8,28 11,18 3,12 13,12\" fill=\"currentColor\" opacity=\"0.3\" stroke=\"currentColor\" stroke-width=\"1.5\"/>',\n  'patch-panel': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"3\" y=\"10\" width=\"26\" height=\"12\" rx=\"1\"/><circle cx=\"8\" cy=\"14\" r=\"1.5\"/><circle cx=\"13\" cy=\"14\" r=\"1.5\"/><circle cx=\"18\" cy=\"14\" r=\"1.5\"/><circle cx=\"23\" cy=\"14\" r=\"1.5\"/><circle cx=\"8\" cy=\"19\" r=\"1.5\"/><circle cx=\"13\" cy=\"19\" r=\"1.5\"/><circle cx=\"18\" cy=\"19\" r=\"1.5\"/><circle cx=\"23\" cy=\"19\" r=\"1.5\"/></g>',\n  'ups': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"6\" y=\"4\" width=\"20\" height=\"24\" rx=\"2\"/><rect x=\"10\" y=\"8\" width=\"12\" height=\"6\" rx=\"1\"/><circle cx=\"12\" cy=\"20\" r=\"1.5\" fill=\"currentColor\"/><circle cx=\"16\" cy=\"20\" r=\"1.5\"/><circle cx=\"20\" cy=\"20\" r=\"1.5\"/></g>',\n  'pdu': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"11\" y=\"2\" width=\"10\" height=\"28\" rx=\"1\"/><circle cx=\"16\" cy=\"7\" r=\"2\"/><circle cx=\"16\" cy=\"13\" r=\"2\"/><circle cx=\"16\" cy=\"19\" r=\"2\"/><circle cx=\"16\" cy=\"25\" r=\"2\" fill=\"currentColor\"/></g>',\n  'rack-shelf': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"3\" y=\"12\" width=\"26\" height=\"8\" rx=\"1\"/><line x1=\"3\" y1=\"17\" x2=\"29\" y2=\"17\" stroke-dasharray=\"3,2\"/><circle cx=\"7\" cy=\"14.5\" r=\"1\" fill=\"currentColor\"/></g>',\n  'blank-panel': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"3\" y=\"12\" width=\"26\" height=\"8\" rx=\"1\"/><circle cx=\"6\" cy=\"16\" r=\"1\"/><circle cx=\"26\" cy=\"16\" r=\"1\"/></g>',\n  'cable-management': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"3\" y=\"11\" width=\"26\" height=\"10\" rx=\"1\"/><path d=\"M7,14 Q10,18 13,14 Q16,10 19,14 Q22,18 25,14\" stroke-linecap=\"round\"/></g>',\n  'kvm': '<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><rect x=\"4\" y=\"8\" width=\"24\" height=\"16\" rx=\"2\"/><rect x=\"8\" y=\"11\" width=\"10\" height=\"7\" rx=\"1\"/><circle cx=\"23\" cy=\"14.5\" r=\"2\"/><line x1=\"12\" y1=\"20\" x2=\"20\" y2=\"20\"/></g>'\n};\n\nfunction getModalShapePreview(shape) {\n  const svg = MODAL_SHAPE_PREVIEWS[shape];\n  if (svg) return '<svg width=\"36\" height=\"36\" viewBox=\"0 0 32 32\" style=\"color:var(--accent)\">' + svg + '</svg>';\n  return '<span style=\"font-size:10px;color:var(--text-soft);text-align:center;line-height:1.2\">' + (shape || 'circle') + '</span>';\n}\n\nfunction populateModalShapeSelect(categorySelectId, shapeSelectId, previewId) {\n  const catSelect = document.getElementById(categorySelectId);\n  const shapeSelect = document.getElementById(shapeSelectId);\n  if (!catSelect || !shapeSelect) return;\n  const category = catSelect.value || 'basic';\n  const shapes = SHAPE_CATEGORIES[category] || SHAPE_CATEGORIES.basic;\n  shapeSelect.innerHTML = shapes.map(s => `<option value=\"${s.value}\">${s.label}</option>`).join('');\n  if (previewId) {\n    const preview = document.getElementById(previewId);\n    if (preview && shapes.length > 0) preview.innerHTML = getModalShapePreview(shapes[0].value);\n  }\n}\n\ndocument.getElementById('new-node-category')?.addEventListener('change', () => {\n  populateModalShapeSelect('new-node-category', 'new-node-shape', 'new-node-shape-preview');\n  selectedNodeIconData = null;\n  const iconContainer = document.getElementById('selected-node-icon');\n  if (iconContainer) iconContainer.style.display = 'none';\n  const shapePreview = document.getElementById('new-node-shape-preview');\n  if (shapePreview) shapePreview.style.display = 'flex';\n});\ndocument.getElementById('new-rack-category')?.addEventListener('change', () => {\n  populateModalShapeSelect('new-rack-category', 'new-rack-shape', 'new-rack-shape-preview');\n  selectedRackIconData = null;\n  const iconContainer = document.getElementById('selected-rack-icon');\n  if (iconContainer) iconContainer.style.display = 'none';\n  const shapePreview = document.getElementById('new-rack-shape-preview');\n  if (shapePreview) shapePreview.style.display = 'flex';\n});\ndocument.getElementById('new-node-shape')?.addEventListener('change', (e) => {\n  const preview = document.getElementById('new-node-shape-preview');\n  if (preview) {\n    preview.innerHTML = getModalShapePreview(e.target.value);\n    preview.style.display = 'flex';\n  }\n  selectedNodeIconData = null;\n  const iconContainer = document.getElementById('selected-node-icon');\n  if (iconContainer) iconContainer.style.display = 'none';\n});\ndocument.getElementById('new-rack-shape')?.addEventListener('change', (e) => {\n  const preview = document.getElementById('new-rack-shape-preview');\n  if (preview) {\n    preview.innerHTML = getModalShapePreview(e.target.value);\n    preview.style.display = 'flex';\n  }\n  selectedRackIconData = null;\n  const iconContainer = document.getElementById('selected-rack-icon');\n  if (iconContainer) iconContainer.style.display = 'none';\n});\n\npopulateModalShapeSelect('new-node-category', 'new-node-shape', 'new-node-shape-preview');\npopulateModalShapeSelect('new-rack-category', 'new-rack-shape', 'new-rack-shape-preview');\n\nfunction showWelcomeModal() {\n  const modal = document.getElementById('welcome-modal');\n  if (!modal) return;\n  modal.classList.add('active');\n\n  let selectedMode = 'network';\n  const canvasRow = document.getElementById('welcome-canvas-row');\n  const canvasSelect = document.getElementById('welcome-canvas-select');\n  const canvasPreview = document.getElementById('welcome-canvas-preview');\n  const themeSelect = document.getElementById('welcome-theme-select');\n  const bgColor = document.getElementById('welcome-bg-color');\n  const accentColor = document.getElementById('welcome-accent-color');\n  const textColor = document.getElementById('welcome-text-color');\n  const textSoftColor = document.getElementById('welcome-text-soft-color');\n  const panelColor = document.getElementById('welcome-panel-color');\n  const modalColor = document.getElementById('welcome-modal-color');\n  const dangerColor = document.getElementById('welcome-danger-color');\n  const mobileFooterColor = document.getElementById('welcome-mobile-footer-color');\n  const tagFillColor = document.getElementById('welcome-tag-fill-color');\n  const tagTextColor = document.getElementById('welcome-tag-text-color');\n  const tagBorderColor = document.getElementById('welcome-tag-border-color');\n\n  function renderWelcomeCanvasPreview() {\n    if (!canvasPreview) return;\n    const template = canvasSelect ? canvasSelect.value : 'grid';\n    const previewBg = bgColor ? bgColor.value : '#0b0e13';\n    const gridColor = '#475569';\n    canvasPreview.style.background = previewBg;\n    canvasPreview.innerHTML = '';\n\n    if (template === 'none') return;\n\n    const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');\n    svg.setAttribute('width', '100%');\n    svg.setAttribute('height', '100%');\n    svg.setAttribute('viewBox', '0 0 300 60');\n    svg.style.display = 'block';\n\n    if (template === 'grid') {\n      for (let x = 0; x <= 300; x += 20) {\n        const line = document.createElementNS('http://www.w3.org/2000/svg', 'line');\n        line.setAttribute('x1', x); line.setAttribute('y1', 0);\n        line.setAttribute('x2', x); line.setAttribute('y2', 60);\n        line.setAttribute('stroke', gridColor + '33');\n        line.setAttribute('stroke-width', x % 100 === 0 ? '1.5' : '0.5');\n        svg.appendChild(line);\n      }\n      for (let y = 0; y <= 60; y += 20) {\n        const line = document.createElementNS('http://www.w3.org/2000/svg', 'line');\n        line.setAttribute('x1', 0); line.setAttribute('y1', y);\n        line.setAttribute('x2', 300); line.setAttribute('y2', y);\n        line.setAttribute('stroke', gridColor + '33');\n        line.setAttribute('stroke-width', y % 100 === 0 ? '1.5' : '0.5');\n        svg.appendChild(line);\n      }\n    } else if (template === 'dots') {\n      for (let x = 10; x <= 290; x += 20) {\n        for (let y = 10; y <= 50; y += 20) {\n          const dot = document.createElementNS('http://www.w3.org/2000/svg', 'circle');\n          dot.setAttribute('cx', x); dot.setAttribute('cy', y); dot.setAttribute('r', 1.5);\n          dot.setAttribute('fill', gridColor + '66');\n          svg.appendChild(dot);\n        }\n      }\n    } else if (template === 'blueprint') {\n      for (let x = 0; x <= 300; x += 10) {\n        const line = document.createElementNS('http://www.w3.org/2000/svg', 'line');\n        line.setAttribute('x1', x); line.setAttribute('y1', 0);\n        line.setAttribute('x2', x); line.setAttribute('y2', 60);\n        line.setAttribute('stroke', x % 40 === 0 ? gridColor + '66' : gridColor + '33');\n        line.setAttribute('stroke-width', x % 40 === 0 ? '1' : '0.3');\n        svg.appendChild(line);\n      }\n      for (let y = 0; y <= 60; y += 10) {\n        const line = document.createElementNS('http://www.w3.org/2000/svg', 'line');\n        line.setAttribute('x1', 0); line.setAttribute('y1', y);\n        line.setAttribute('x2', 300); line.setAttribute('y2', y);\n        line.setAttribute('stroke', y % 40 === 0 ? gridColor + '66' : gridColor + '33');\n        line.setAttribute('stroke-width', y % 40 === 0 ? '1' : '0.3');\n        svg.appendChild(line);\n      }\n    } else if (template === 'basketball') {\n      const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');\n      rect.setAttribute('x', 20); rect.setAttribute('y', 10);\n      rect.setAttribute('width', 260); rect.setAttribute('height', 40);\n      rect.setAttribute('fill', '#cd853f22'); rect.setAttribute('stroke', gridColor + '66');\n      rect.setAttribute('stroke-width', '2');\n      svg.appendChild(rect);\n      const center = document.createElementNS('http://www.w3.org/2000/svg', 'line');\n      center.setAttribute('x1', 150); center.setAttribute('y1', 10);\n      center.setAttribute('x2', 150); center.setAttribute('y2', 50);\n      center.setAttribute('stroke', gridColor + '66'); center.setAttribute('stroke-width', '1.5');\n      svg.appendChild(center);\n      const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');\n      circle.setAttribute('cx', 150); circle.setAttribute('cy', 30); circle.setAttribute('r', 10);\n      circle.setAttribute('fill', 'none'); circle.setAttribute('stroke', gridColor + '66');\n      svg.appendChild(circle);\n    } else if (template === 'football') {\n      const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');\n      rect.setAttribute('x', 20); rect.setAttribute('y', 10);\n      rect.setAttribute('width', 260); rect.setAttribute('height', 40);\n      rect.setAttribute('fill', '#22803322'); rect.setAttribute('stroke', '#ffffff44');\n      rect.setAttribute('stroke-width', '2');\n      svg.appendChild(rect);\n      for (let x = 46; x <= 254; x += 26) {\n        const line = document.createElementNS('http://www.w3.org/2000/svg', 'line');\n        line.setAttribute('x1', x); line.setAttribute('y1', 10);\n        line.setAttribute('x2', x); line.setAttribute('y2', 50);\n        line.setAttribute('stroke', '#ffffff44'); line.setAttribute('stroke-width', '1');\n        svg.appendChild(line);\n      }\n    } else if (template === 'soccer') {\n      const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');\n      rect.setAttribute('x', 20); rect.setAttribute('y', 10);\n      rect.setAttribute('width', 260); rect.setAttribute('height', 40);\n      rect.setAttribute('fill', '#1a802022'); rect.setAttribute('stroke', '#ffffff55');\n      rect.setAttribute('stroke-width', '2');\n      svg.appendChild(rect);\n      const center = document.createElementNS('http://www.w3.org/2000/svg', 'line');\n      center.setAttribute('x1', 150); center.setAttribute('y1', 10);\n      center.setAttribute('x2', 150); center.setAttribute('y2', 50);\n      center.setAttribute('stroke', '#ffffff44'); center.setAttribute('stroke-width', '1');\n      svg.appendChild(center);\n      const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');\n      circle.setAttribute('cx', 150); circle.setAttribute('cy', 30); circle.setAttribute('r', 8);\n      circle.setAttribute('fill', 'none'); circle.setAttribute('stroke', '#ffffff44');\n      svg.appendChild(circle);\n    } else if (template === 'hockey') {\n      const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');\n      rect.setAttribute('x', 20); rect.setAttribute('y', 10);\n      rect.setAttribute('width', 260); rect.setAttribute('height', 40);\n      rect.setAttribute('rx', 10); rect.setAttribute('ry', 10);\n      rect.setAttribute('fill', '#e8f4f844'); rect.setAttribute('stroke', '#ff000066');\n      rect.setAttribute('stroke-width', '2');\n      svg.appendChild(rect);\n      const center = document.createElementNS('http://www.w3.org/2000/svg', 'line');\n      center.setAttribute('x1', 150); center.setAttribute('y1', 10);\n      center.setAttribute('x2', 150); center.setAttribute('y2', 50);\n      center.setAttribute('stroke', '#ff000055'); center.setAttribute('stroke-width', '2');\n      svg.appendChild(center);\n      const blueL = document.createElementNS('http://www.w3.org/2000/svg', 'line');\n      blueL.setAttribute('x1', 95); blueL.setAttribute('y1', 10);\n      blueL.setAttribute('x2', 95); blueL.setAttribute('y2', 50);\n      blueL.setAttribute('stroke', '#0066ff55'); blueL.setAttribute('stroke-width', '2');\n      svg.appendChild(blueL);\n      const blueR = document.createElementNS('http://www.w3.org/2000/svg', 'line');\n      blueR.setAttribute('x1', 205); blueR.setAttribute('y1', 10);\n      blueR.setAttribute('x2', 205); blueR.setAttribute('y2', 50);\n      blueR.setAttribute('stroke', '#0066ff55'); blueR.setAttribute('stroke-width', '2');\n      svg.appendChild(blueR);\n    } else if (template === 'baseball') {\n      const diamond = document.createElementNS('http://www.w3.org/2000/svg', 'polygon');\n      diamond.setAttribute('points', '150,15 185,30 150,45 115,30');\n      diamond.setAttribute('fill', '#cd853f22'); diamond.setAttribute('stroke', '#ffffff55');\n      diamond.setAttribute('stroke-width', '1.5');\n      svg.appendChild(diamond);\n      const arc = document.createElementNS('http://www.w3.org/2000/svg', 'path');\n      arc.setAttribute('d', 'M 60,50 Q 150,5 240,50');\n      arc.setAttribute('fill', '#22803322'); arc.setAttribute('stroke', '#ffffff44');\n      arc.setAttribute('stroke-width', '1');\n      svg.appendChild(arc);\n    } else if (template === 'tennis') {\n      const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');\n      rect.setAttribute('x', 30); rect.setAttribute('y', 10);\n      rect.setAttribute('width', 240); rect.setAttribute('height', 40);\n      rect.setAttribute('fill', '#0066aa22'); rect.setAttribute('stroke', '#ffffff66');\n      rect.setAttribute('stroke-width', '2');\n      svg.appendChild(rect);\n      const net = document.createElementNS('http://www.w3.org/2000/svg', 'line');\n      net.setAttribute('x1', 150); net.setAttribute('y1', 10);\n      net.setAttribute('x2', 150); net.setAttribute('y2', 50);\n      net.setAttribute('stroke', '#ffffff88'); net.setAttribute('stroke-width', '1');\n      svg.appendChild(net);\n      const svcL = document.createElementNS('http://www.w3.org/2000/svg', 'rect');\n      svcL.setAttribute('x', 70); svcL.setAttribute('y', 18);\n      svcL.setAttribute('width', 80); svcL.setAttribute('height', 24);\n      svcL.setAttribute('fill', 'none'); svcL.setAttribute('stroke', '#ffffff44');\n      svg.appendChild(svcL);\n      const svcR = document.createElementNS('http://www.w3.org/2000/svg', 'rect');\n      svcR.setAttribute('x', 150); svcR.setAttribute('y', 18);\n      svcR.setAttribute('width', 80); svcR.setAttribute('height', 24);\n      svcR.setAttribute('fill', 'none'); svcR.setAttribute('stroke', '#ffffff44');\n      svg.appendChild(svcR);\n    }\n    canvasPreview.appendChild(svg);\n  }\n\n  function updateWelcomeCanvasOptions(mode) {\n    if (!canvasSelect || !canvasRow) return;\n    const options = CANVAS_OPTIONS[mode] || CANVAS_OPTIONS.network;\n    const previewRow = document.getElementById('welcome-canvas-preview');\n    const colorDetails = modal.querySelector('details');\n    if (mode === 'mindmap') {\n      canvasRow.style.display = 'none';\n      if (previewRow) previewRow.style.display = 'none';\n      if (colorDetails) colorDetails.open = true;\n    } else if (mode === 'floorplan') {\n      canvasRow.style.display = 'none';\n      if (previewRow) {\n        previewRow.style.display = 'block';\n        const networkOptions = CANVAS_OPTIONS.network;\n        canvasSelect.innerHTML = networkOptions.map(o => `<option value=\"${o.value}\">${o.label}</option>`).join('');\n        canvasSelect.value = 'blueprint';\n        renderWelcomeCanvasPreview();\n      }\n      if (colorDetails) colorDetails.open = false;\n    } else if (options.length > 0) {\n      canvasRow.style.display = 'flex';\n      if (previewRow) previewRow.style.display = 'block';\n      canvasSelect.innerHTML = options.map(o => `<option value=\"${o.value}\">${o.label}</option>`).join('');\n      renderWelcomeCanvasPreview();\n      if (colorDetails) colorDetails.open = false;\n    }\n  }\n\n  modal.querySelectorAll('.welcome-mode-btn').forEach(btn => {\n    btn.addEventListener('click', () => {\n      selectedMode = btn.dataset.mode;\n      modal.querySelectorAll('.welcome-mode-btn').forEach(b => {\n        b.style.borderColor = 'var(--edge-main)';\n      });\n      btn.style.borderColor = 'var(--accent)';\n      updateWelcomeCanvasOptions(selectedMode);\n      PAGE_STATE.mappingMode = selectedMode;\n      applyMappingModeLabels();\n      updateLayerLabels();\n      applyLanguage();\n      displayTabs();\n      if (selectedMode === 'mindmap') {\n        PAGE_STATE.canvasTemplate = 'none';\n        PAGE_STATE.canvasGridEnabled = false;\n      } else if (selectedMode === 'floorplan') {\n        PAGE_STATE.canvasTemplate = 'blueprint';\n        PAGE_STATE.canvasGridEnabled = true;\n      } else if (canvasSelect) {\n        PAGE_STATE.canvasTemplate = canvasSelect.value;\n        PAGE_STATE.canvasGridEnabled = canvasSelect.value !== 'none';\n      }\n      forgeTheTopology();\n    });\n    btn.addEventListener('mouseenter', () => {\n      if (btn.style.borderColor !== 'var(--accent)') {\n        btn.style.background = 'var(--panel)';\n      }\n    });\n    btn.addEventListener('mouseleave', () => {\n      btn.style.background = 'var(--panel-alt)';\n    });\n  });\n\n  if (canvasSelect) {\n    canvasSelect.addEventListener('change', () => {\n      renderWelcomeCanvasPreview();\n      PAGE_STATE.canvasTemplate = canvasSelect.value;\n      PAGE_STATE.canvasGridEnabled = canvasSelect.value !== 'none';\n      forgeTheTopology();\n    });\n  }\n\n  if (themeSelect) {\n    themeSelect.addEventListener('change', () => {\n      const presetKey = themeSelect.value;\n      const colorDetails = modal.querySelector('details');\n      if (presetKey === '') {\n        if (colorDetails) colorDetails.open = true;\n        return;\n      }\n      if (typeof THEME_PRESETS === 'undefined') return;\n      const p = THEME_PRESETS[presetKey];\n      if (!p) return;\n      Object.assign(PAGE_STATE, p);\n      document.body.style.background = p.panel;\n      document.documentElement.style.setProperty('--bg', p.panel);\n      document.documentElement.style.setProperty('--panel', p.panel);\n      document.documentElement.style.setProperty('--panel-alt', p.panelAlt);\n      document.documentElement.style.setProperty('--accent', p.accent);\n      document.documentElement.style.setProperty('--danger', p.danger);\n      document.documentElement.style.setProperty('--text-main', p.textMain);\n      document.documentElement.style.setProperty('--text-soft', p.textSoft);\n      document.documentElement.style.setProperty('--modal-bg', p.panelAlt);\n      document.documentElement.style.setProperty('--tag-bg', p.tagFill);\n      document.documentElement.style.setProperty('--tag-text', p.tagText);\n      document.documentElement.style.setProperty('--tag-border', p.tagBorder);\n      if (bgColor) bgColor.value = p.panel;\n      if (accentColor) accentColor.value = p.accent;\n      if (textColor) textColor.value = p.textMain;\n      if (textSoftColor) textSoftColor.value = p.textSoft;\n      if (panelColor) panelColor.value = p.panel;\n      if (modalColor) modalColor.value = p.panelAlt;\n      if (dangerColor) dangerColor.value = p.canvasGrid || '#475569';\n      if (tagFillColor) tagFillColor.value = p.tagFill;\n      if (tagTextColor) tagTextColor.value = p.tagText;\n      if (tagBorderColor) tagBorderColor.value = p.tagBorder;\n      PAGE_STATE.background = p.panel;\n      renderWelcomeCanvasPreview();\n      wieldThePower();\n    });\n  }\n  \n  function setupColorPicker(picker, cssVar, pageStateKey) {\n    if (!picker) return;\n    picker.addEventListener('input', (e) => {\n      document.documentElement.style.setProperty(cssVar, e.target.value);\n      if (pageStateKey) PAGE_STATE[pageStateKey] = e.target.value;\n      if (cssVar === '--bg' || pageStateKey === 'background') {\n        document.body.style.background = e.target.value;\n        renderWelcomeCanvasPreview();\n      }\n    });\n  }\n\n  setupColorPicker(bgColor, '--bg', 'background');\n  setupColorPicker(accentColor, '--accent', 'accent');\n  setupColorPicker(textColor, '--text-main', 'textMain');\n  setupColorPicker(textSoftColor, '--text-soft', 'textSoft');\n  setupColorPicker(panelColor, '--panel', 'panel');\n  setupColorPicker(modalColor, '--panel-alt', 'panelAlt');\n  if (dangerColor) {\n    dangerColor.addEventListener('input', (e) => {\n      PAGE_STATE.canvasGrid = e.target.value;\n      if (typeof forgeTheTopology === 'function') forgeTheTopology();\n      renderWelcomeCanvasPreview();\n    });\n  }\n  setupColorPicker(mobileFooterColor, '--sidebar-bg', 'sidebarBg');\n  setupColorPicker(tagFillColor, '--tag-bg', 'tagFill');\n  setupColorPicker(tagTextColor, '--tag-text', 'tagText');\n  setupColorPicker(tagBorderColor, '--tag-border', 'tagBorder');\n\n  renderWelcomeCanvasPreview();\n\n  const startBtn = document.getElementById('welcome-start-btn');\n  if (startBtn) {\n    startBtn.addEventListener('click', () => {\n      PAGE_STATE.mappingMode = selectedMode;\n      const modeSelect = document.getElementById('mapping-mode-select');\n      if (modeSelect) modeSelect.value = selectedMode;\n\n      if (selectedMode === 'mindmap') {\n        PAGE_STATE.canvasTemplate = 'none';\n        PAGE_STATE.canvasGridEnabled = false;\n      } else if (selectedMode === 'floorplan') {\n        PAGE_STATE.canvasTemplate = 'blueprint';\n      } else if (canvasSelect) {\n        PAGE_STATE.canvasTemplate = canvasSelect.value;\n        PAGE_STATE.canvasGridEnabled = canvasSelect.value !== 'none';\n      }\n\n      if (bgColor) {\n        PAGE_STATE.background = bgColor.value;\n        document.body.style.background = bgColor.value;\n        const el = document.getElementById('panel-color');\n        if (el) el.value = bgColor.value;\n      }\n      if (accentColor) {\n        PAGE_STATE.accent = accentColor.value;\n        const el = document.getElementById('accent-color');\n        if (el) el.value = accentColor.value;\n      }\n\n      if (typeof updateCanvasStyleOptions === 'function') updateCanvasStyleOptions();\n      if (typeof applyMappingModeLabels === 'function') applyMappingModeLabels();\n      if (typeof updateLayerLabels === 'function') updateLayerLabels();\n\n      const catSelect = document.getElementById('shape-category-select');\n      if (catSelect) {\n        catSelect.value = MODE_DEFAULT_CATEGORIES[selectedMode] || 'network';\n        if (typeof populateShapeSelect === 'function') populateShapeSelect();\n      }\n\n      modal.classList.remove('active');\n      wieldThePower();\n      forgeTheTopology();\n    });\n  }\n\n  const skipBtn = document.getElementById('welcome-skip-btn');\n  if (skipBtn) {\n    skipBtn.addEventListener('click', () => {\n      modal.classList.remove('active');\n    });\n  }\n\n  modal.addEventListener('click', (e) => {\n    if (e.target === modal) {\n      modal.classList.remove('active');\n    }\n  });\n}\n    </script>\n</body></html>"
  },
  {
    "path": "theonefile_verse/redis.conf",
    "content": "maxmemory 200mb\nmaxmemory-policy volatile-lru\nappendonly yes\n"
  },
  {
    "path": "theonefile_verse/src/auth.ts",
    "content": "import * as db from \"./database\";\nimport * as oidc from \"./oidc\";\nimport * as mailer from \"./mailer\";\nimport { createHmac, randomBytes } from \"crypto\";\n\nexport async function hashPassword(password: string): Promise<string> {\n  return await Bun.password.hash(password, {\n    algorithm: \"argon2id\",\n    memoryCost: 65536,\n    timeCost: 2\n  });\n}\n\nexport async function verifyPassword(password: string, hash: string): Promise<boolean> {\n  if (hash.startsWith('$argon2')) {\n    return await Bun.password.verify(password, hash);\n  }\n  const encoder = new TextEncoder();\n  const data = encoder.encode(password + \"theonefile-collab-salt-v1\");\n  const legacyHash = await crypto.subtle.digest(\"SHA-256\", data);\n  const computed = Buffer.from(Buffer.from(legacyHash).toString(\"hex\"));\n  const stored = Buffer.from(hash);\n  if (computed.length !== stored.length) return false;\n  return crypto.timingSafeEqual(computed, stored);\n}\n\nexport function isLegacyHash(hash: string): boolean {\n  return !hash.startsWith('$argon2');\n}\n\nexport async function upgradePasswordHash(userId: string, password: string): Promise<void> {\n  const user = db.getUserById(userId);\n  if (!user) return;\n\n  const newHash = await hashPassword(password);\n  user.passwordHash = newHash;\n  user.updatedAt = new Date().toISOString();\n  db.updateUser(user);\n  console.log(`[Auth] Upgraded password hash for user ${userId} from legacy SHA-256 to Argon2id`);\n}\n\nexport function validatePassword(password: string): { valid: boolean; error?: string } {\n  if (password.length < 8) {\n    return { valid: false, error: \"Password must be at least 8 characters\" };\n  }\n  if (password.length > 128) {\n    return { valid: false, error: \"Password must be less than 128 characters\" };\n  }\n  if (!/[0-9!@#$%^&*()_+\\-=\\[\\]{};':\"\\\\|,.<>\\/?]/.test(password)) {\n    return { valid: false, error: \"Password must contain at least one number or special character\" };\n  }\n  if (!/[a-zA-Z]/.test(password)) {\n    return { valid: false, error: \"Password must contain at least one letter\" };\n  }\n  return { valid: true };\n}\n\nexport function validateEmail(email: string): { valid: boolean; error?: string } {\n  if (!email || email.length > 254) {\n    return { valid: false, error: \"Invalid email address\" };\n  }\n  const emailRegex = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;\n  if (!emailRegex.test(email)) {\n    return { valid: false, error: \"Invalid email format\" };\n  }\n  return { valid: true };\n}\n\nfunction normalizeEmail(email: string): string {\n  return email.toLowerCase().trim();\n}\n\nexport async function registerUser(\n  email: string,\n  password: string,\n  displayName?: string,\n  baseUrl?: string\n): Promise<{ success: boolean; userId?: string; error?: string; requiresVerification?: boolean }> {\n  const normalizedEmail = normalizeEmail(email);\n  const emailValidation = validateEmail(normalizedEmail);\n  if (!emailValidation.valid) {\n    return { success: false, error: emailValidation.error };\n  }\n\n  const passwordValidation = validatePassword(password);\n  if (!passwordValidation.valid) {\n    return { success: false, error: passwordValidation.error };\n  }\n\n  const existing = db.getUserByEmail(normalizedEmail);\n  if (existing) {\n    return { success: false, error: \"Registration failed. Please try again or use a different email.\" };\n  }\n\n  const authSettings = oidc.getAuthSettings();\n  if (authSettings.authMode === 'closed') {\n    return { success: false, error: \"Registration is closed\" };\n  }\n  if (authSettings.authMode === 'invite_only') {\n    return { success: false, error: \"Registration requires an invitation\" };\n  }\n  if (authSettings.authMode === 'oidc_only') {\n    return { success: false, error: \"Please use SSO to register\" };\n  }\n\n  const userId = crypto.randomUUID();\n  const now = new Date().toISOString();\n  const passwordHash = await hashPassword(password);\n\n  const user: db.User = {\n    id: userId,\n    email: normalizedEmail,\n    emailVerified: !authSettings.requireEmailVerification,\n    displayName: displayName || normalizedEmail.split('@')[0],\n    avatarUrl: null,\n    passwordHash,\n    role: 'user',\n    createdAt: now,\n    updatedAt: now,\n    lastLogin: now,\n    isActive: true,\n    failedLoginAttempts: 0,\n    lockedUntil: null,\n    totpSecret: null,\n    totpEnabled: false,\n    totpBackupCodes: null,\n    pendingEmail: null,\n    pendingEmailToken: null\n  };\n\n  const { wasFirst: isFirstUser } = db.createUserAtomic(user);\n  db.logAuthEvent('register', user.id, null, { role: user.role, isFirstUser });\n\n  if (!isFirstUser && authSettings.requireEmailVerification && baseUrl) {\n    const token = await createVerificationToken(userId);\n    const verifyUrl = `${baseUrl}/auth/verify?token=${token}`;\n    await mailer.sendVerificationEmail(normalizedEmail, user.displayName || 'User', verifyUrl);\n    return { success: true, userId, requiresVerification: true };\n  }\n\n  return { success: true, userId };\n}\n\nconst MAX_FAILED_ATTEMPTS = 5;\nconst LOCKOUT_DURATION_MINUTES = 15;\n\nexport async function loginWithPassword(\n  email: string,\n  password: string,\n  ipAddress: string,\n  userAgent: string\n): Promise<{ success: boolean; userId?: string; sessionToken?: string; error?: string; requires2FA?: boolean; pendingToken?: string }> {\n  const normalizedEmail = normalizeEmail(email);\n  const user = db.getUserByEmail(normalizedEmail);\n  if (!user) {\n    return { success: false, error: \"Invalid email or password\" };\n  }\n\n  if (!user.isActive) {\n    return { success: false, error: \"Invalid email or password\" };\n  }\n\n  if (user.lockedUntil) {\n    const lockExpiry = new Date(user.lockedUntil);\n    if (lockExpiry > new Date()) {\n      const remainingMinutes = Math.ceil((lockExpiry.getTime() - Date.now()) / 60000);\n      return { success: false, error: `Account is temporarily locked. Try again in ${remainingMinutes} minute${remainingMinutes !== 1 ? 's' : ''}.` };\n    }\n    db.resetFailedLogin(user.id);\n  }\n\n  if (!user.passwordHash) {\n    return { success: false, error: \"Please use SSO to login\" };\n  }\n\n  const valid = await verifyPassword(password, user.passwordHash);\n  if (!valid) {\n    db.incrementFailedLogin(user.id);\n    const updatedUser = db.getUserById(user.id);\n    if (updatedUser && updatedUser.failedLoginAttempts >= MAX_FAILED_ATTEMPTS) {\n      const lockUntil = new Date(Date.now() + LOCKOUT_DURATION_MINUTES * 60 * 1000);\n      db.lockUserAccount(user.id, lockUntil);\n      db.logAuthEvent('account_locked', user.id, ipAddress, { reason: 'too_many_failed_attempts' });\n      return { success: false, error: `Too many failed attempts. Account locked for ${LOCKOUT_DURATION_MINUTES} minutes.` };\n    }\n    db.logAuthEvent('login_failed', user.id, ipAddress, { reason: 'invalid_password', attempts: updatedUser?.failedLoginAttempts });\n    const attemptsRemaining = MAX_FAILED_ATTEMPTS - (updatedUser?.failedLoginAttempts || 0);\n    if (attemptsRemaining <= 2 && attemptsRemaining > 0) {\n      return { success: false, error: `Invalid email or password. ${attemptsRemaining} attempt${attemptsRemaining !== 1 ? 's' : ''} remaining.` };\n    }\n    return { success: false, error: \"Invalid email or password\" };\n  }\n\n  db.resetFailedLogin(user.id);\n\n  if (isLegacyHash(user.passwordHash)) {\n    await upgradePasswordHash(user.id, password);\n  }\n\n  const authSettings = oidc.getAuthSettings();\n  if (authSettings.requireEmailVerification && !user.emailVerified) {\n    return { success: false, error: \"Please verify your email first\" };\n  }\n\n  if (user.totpEnabled) {\n    const pendingToken = oidc.generateSecureToken(32);\n    const tokenHash = await oidc.hashToken(pendingToken);\n    db.deleteUserTokensByType(user.id, 'totp_pending');\n    db.createUserToken({\n      id: crypto.randomUUID(),\n      userId: user.id,\n      type: 'totp_pending',\n      tokenHash,\n      expiresAt: new Date(Date.now() + 5 * 60 * 1000).toISOString(),\n      usedAt: null,\n      createdAt: new Date().toISOString()\n    });\n    return { success: false, requires2FA: true, pendingToken, error: undefined };\n  }\n\n  user.lastLogin = new Date().toISOString();\n  user.updatedAt = new Date().toISOString();\n  user.failedLoginAttempts = 0;\n  user.lockedUntil = null;\n  db.updateUser(user);\n\n  const sessionToken = await createSessionToken(user.id, ipAddress, userAgent);\n\n  db.logAuthEvent('login_success', user.id, ipAddress, { method: 'password' });\n\n  return { success: true, userId: user.id, sessionToken };\n}\n\nconst MAX_SESSIONS_PER_USER = 10;\n\nasync function createSessionToken(\n  userId: string,\n  ipAddress: string,\n  userAgent: string\n): Promise<string> {\n  const token = oidc.generateSecureToken(32);\n  const tokenHash = await oidc.hashToken(token);\n  const now = new Date();\n  const expiresAt = new Date(now.getTime() + 30 * 24 * 60 * 60 * 1000);\n\n  const existing = db.getSessionsByUserId(userId);\n  if (existing.length >= MAX_SESSIONS_PER_USER) {\n    const oldest = existing.slice(MAX_SESSIONS_PER_USER - 1);\n    for (const s of oldest) db.deleteSession(s.id);\n  }\n\n  db.createUserSession({\n    id: crypto.randomUUID(),\n    userId,\n    tokenHash,\n    ipAddress,\n    userAgent: userAgent?.substring(0, 500) || null,\n    expiresAt: expiresAt.toISOString(),\n    createdAt: now.toISOString()\n  });\n\n  return token;\n}\n\nexport async function rotateSessionToken(\n  oldToken: string,\n  ipAddress: string,\n  userAgent: string\n): Promise<{ success: boolean; newToken?: string; error?: string }> {\n  const tokenHash = await oidc.hashToken(oldToken);\n  const session = db.getUserSessionByTokenHash(tokenHash);\n\n  if (!session) {\n    return { success: false, error: \"Invalid session\" };\n  }\n\n  if (new Date(session.expiresAt) < new Date()) {\n    db.deleteUserSession(session.id);\n    return { success: false, error: \"Session expired\" };\n  }\n\n  db.deleteUserSession(session.id);\n\n  const newToken = await createSessionToken(session.userId, ipAddress, userAgent);\n  return { success: true, newToken };\n}\n\nasync function createVerificationToken(userId: string): Promise<string> {\n  db.deleteUserTokensByType(userId, 'email_verify');\n\n  const token = oidc.generateSecureToken(32);\n  const tokenHash = await oidc.hashToken(token);\n  const now = new Date();\n  const expiresAt = new Date(now.getTime() + 24 * 60 * 60 * 1000);\n\n  db.createUserToken({\n    id: crypto.randomUUID(),\n    userId,\n    type: 'email_verify',\n    tokenHash,\n    expiresAt: expiresAt.toISOString(),\n    usedAt: null,\n    createdAt: now.toISOString()\n  });\n\n  return token;\n}\n\nexport async function verifyEmail(token: string): Promise<{ success: boolean; error?: string }> {\n  const tokenHash = await oidc.hashToken(token);\n  const userToken = db.getUserTokenByHash(tokenHash);\n\n  if (!userToken) {\n    return { success: false, error: \"Invalid or expired token\" };\n  }\n\n  if (userToken.type !== 'email_verify') {\n    return { success: false, error: \"Invalid token type\" };\n  }\n\n  if (userToken.usedAt) {\n    return { success: false, error: \"Token already used\" };\n  }\n\n  if (new Date(userToken.expiresAt) < new Date()) {\n    return { success: false, error: \"Token expired\" };\n  }\n\n  const user = db.getUserById(userToken.userId);\n  if (!user) {\n    return { success: false, error: \"User not found\" };\n  }\n\n  user.emailVerified = true;\n  user.updatedAt = new Date().toISOString();\n  db.updateUser(user);\n\n  db.markUserTokenUsed(userToken.id);\n\n  return { success: true };\n}\n\nconst MIN_REQUEST_TIME_MS = 200;\n\nasync function normalizeResponseTime(startTime: number): Promise<void> {\n  const elapsed = Date.now() - startTime;\n  if (elapsed < MIN_REQUEST_TIME_MS) {\n    await new Promise(r => setTimeout(r, MIN_REQUEST_TIME_MS - elapsed));\n  }\n}\n\nexport async function requestPasswordReset(\n  email: string,\n  baseUrl: string\n): Promise<{ success: boolean; error?: string }> {\n  const startTime = Date.now();\n  const user = db.getUserByEmail(email);\n\n  if (!user || !user.isActive) {\n    await normalizeResponseTime(startTime);\n    return { success: true };\n  }\n\n  db.deleteUserTokensByType(user.id, 'password_reset');\n\n  const token = oidc.generateSecureToken(32);\n  const tokenHash = await oidc.hashToken(token);\n  const now = new Date();\n  const expiresAt = new Date(now.getTime() + 60 * 60 * 1000);\n\n  db.createUserToken({\n    id: crypto.randomUUID(),\n    userId: user.id,\n    type: 'password_reset',\n    tokenHash,\n    expiresAt: expiresAt.toISOString(),\n    usedAt: null,\n    createdAt: now.toISOString()\n  });\n\n  const resetUrl = `${baseUrl}/auth/reset-password?token=${token}`;\n  await mailer.sendPasswordResetEmail(user.email!, user.displayName || 'User', resetUrl);\n\n  await normalizeResponseTime(startTime);\n  return { success: true };\n}\n\nexport async function resetPassword(\n  token: string,\n  newPassword: string\n): Promise<{ success: boolean; error?: string }> {\n  const passwordValidation = validatePassword(newPassword);\n  if (!passwordValidation.valid) {\n    return { success: false, error: passwordValidation.error };\n  }\n\n  const tokenHash = await oidc.hashToken(token);\n  const userToken = db.getUserTokenByHash(tokenHash);\n\n  if (!userToken) {\n    return { success: false, error: \"Invalid or expired token\" };\n  }\n\n  if (userToken.type !== 'password_reset') {\n    return { success: false, error: \"Invalid token type\" };\n  }\n\n  if (userToken.usedAt) {\n    return { success: false, error: \"Token already used\" };\n  }\n\n  if (new Date(userToken.expiresAt) < new Date()) {\n    return { success: false, error: \"Token expired\" };\n  }\n\n  const user = db.getUserById(userToken.userId);\n  if (!user) {\n    return { success: false, error: \"User not found\" };\n  }\n\n  user.passwordHash = await hashPassword(newPassword);\n  user.updatedAt = new Date().toISOString();\n  db.updateUser(user);\n\n  db.markUserTokenUsed(userToken.id);\n\n  db.deleteAllUserSessions(user.id);\n\n  return { success: true };\n}\n\nexport async function requestMagicLink(\n  email: string,\n  baseUrl: string\n): Promise<{ success: boolean; error?: string }> {\n  const startTime = Date.now();\n  const authSettings = oidc.getAuthSettings();\n  if (!authSettings.allowMagicLinkLogin) {\n    return { success: false, error: \"Magic link login is disabled\" };\n  }\n\n  const user = db.getUserByEmail(email);\n  if (!user || !user.isActive) {\n    await normalizeResponseTime(startTime);\n    return { success: true };\n  }\n\n  db.deleteUserTokensByType(user.id, 'magic_link');\n\n  const token = oidc.generateSecureToken(32);\n  const tokenHash = await oidc.hashToken(token);\n  const now = new Date();\n  const expiresAt = new Date(now.getTime() + 15 * 60 * 1000);\n\n  db.createUserToken({\n    id: crypto.randomUUID(),\n    userId: user.id,\n    type: 'magic_link',\n    tokenHash,\n    expiresAt: expiresAt.toISOString(),\n    usedAt: null,\n    createdAt: now.toISOString()\n  });\n\n  const loginUrl = `${baseUrl}/auth/magic-link?token=${token}`;\n  await mailer.sendMagicLinkEmail(user.email!, user.displayName || 'User', loginUrl);\n\n  await normalizeResponseTime(startTime);\n  return { success: true };\n}\n\nexport async function loginWithMagicLink(\n  token: string,\n  ipAddress: string,\n  userAgent: string\n): Promise<{ success: boolean; userId?: string; sessionToken?: string; error?: string }> {\n  const tokenHash = await oidc.hashToken(token);\n  const userToken = db.getUserTokenByHash(tokenHash);\n\n  if (!userToken) {\n    return { success: false, error: \"Invalid or expired link\" };\n  }\n\n  if (userToken.type !== 'magic_link') {\n    return { success: false, error: \"Invalid link type\" };\n  }\n\n  if (userToken.usedAt) {\n    return { success: false, error: \"Link already used\" };\n  }\n\n  if (new Date(userToken.expiresAt) < new Date()) {\n    return { success: false, error: \"Link expired\" };\n  }\n\n  const user = db.getUserById(userToken.userId);\n  if (!user || !user.isActive) {\n    return { success: false, error: \"User not found\" };\n  }\n\n  db.markUserTokenUsed(userToken.id);\n\n  user.lastLogin = new Date().toISOString();\n  user.updatedAt = new Date().toISOString();\n  user.emailVerified = true;\n  db.updateUser(user);\n\n  const sessionToken = await createSessionToken(user.id, ipAddress, userAgent);\n\n  return { success: true, userId: user.id, sessionToken };\n}\n\nexport async function logout(token: string): Promise<void> {\n  const tokenHash = await oidc.hashToken(token);\n  db.deleteSessionByTokenHash(tokenHash);\n}\n\nexport function logoutAll(userId: string): number {\n  return db.deleteAllUserSessions(userId);\n}\n\nexport async function changePassword(\n  userId: string,\n  currentPassword: string,\n  newPassword: string,\n  currentSessionToken?: string\n): Promise<{ success: boolean; error?: string }> {\n  const user = db.getUserById(userId);\n  if (!user) {\n    return { success: false, error: \"User not found\" };\n  }\n\n  if (!user.passwordHash) {\n    return { success: false, error: \"Account uses SSO only\" };\n  }\n\n  const valid = await verifyPassword(currentPassword, user.passwordHash);\n  if (!valid) {\n    return { success: false, error: \"Current password is incorrect\" };\n  }\n\n  const passwordValidation = validatePassword(newPassword);\n  if (!passwordValidation.valid) {\n    return { success: false, error: passwordValidation.error };\n  }\n\n  user.passwordHash = await hashPassword(newPassword);\n  user.updatedAt = new Date().toISOString();\n  db.updateUser(user);\n\n  db.deleteAllUserSessions(userId);\n\n  return { success: true };\n}\n\nexport function updateProfile(\n  userId: string,\n  updates: { displayName?: string; avatarUrl?: string }\n): { success: boolean; error?: string } {\n  const user = db.getUserById(userId);\n  if (!user) {\n    return { success: false, error: \"User not found\" };\n  }\n\n  if (updates.displayName !== undefined) {\n    if (typeof updates.displayName !== 'string') return { success: false, error: \"Invalid display name\" };\n    user.displayName = updates.displayName.substring(0, 100);\n  }\n  if (updates.avatarUrl !== undefined) {\n    if (typeof updates.avatarUrl !== 'string') return { success: false, error: \"Invalid avatar URL\" };\n    user.avatarUrl = updates.avatarUrl.substring(0, 500);\n  }\n\n  user.updatedAt = new Date().toISOString();\n  db.updateUser(user);\n\n  return { success: true };\n}\n\nexport function deleteAccount(userId: string): { success: boolean; error?: string } {\n  const user = db.getUserById(userId);\n  if (!user) {\n    return { success: false, error: \"User not found\" };\n  }\n\n  if (user.role === 'admin') {\n    const adminCount = db.countUsersByRole('admin');\n    if (adminCount <= 1) {\n      return { success: false, error: \"Cannot delete the last admin\" };\n    }\n  }\n\n  db.deleteUser(userId);\n\n  return { success: true };\n}\n\nexport interface ParsedSession extends db.UserSession {\n  browser: string;\n  os: string;\n  device: string;\n  location: string;\n}\n\nfunction parseUserAgent(ua: string | null): { browser: string; os: string; device: string } {\n  if (!ua) return { browser: 'Unknown', os: 'Unknown', device: 'Unknown' };\n\n  let browser = 'Unknown';\n  let os = 'Unknown';\n  let device = 'Desktop';\n\n  if (ua.includes('Mobile') || ua.includes('Android')) device = 'Mobile';\n  else if (ua.includes('Tablet') || ua.includes('iPad')) device = 'Tablet';\n\n  if (ua.includes('Firefox/')) browser = 'Firefox';\n  else if (ua.includes('Edg/')) browser = 'Edge';\n  else if (ua.includes('Chrome/')) browser = 'Chrome';\n  else if (ua.includes('Safari/') && !ua.includes('Chrome')) browser = 'Safari';\n  else if (ua.includes('Opera') || ua.includes('OPR/')) browser = 'Opera';\n\n  if (ua.includes('Windows')) os = 'Windows';\n  else if (ua.includes('Mac OS X') || ua.includes('macOS')) os = 'macOS';\n  else if (ua.includes('Linux') && !ua.includes('Android')) os = 'Linux';\n  else if (ua.includes('Android')) os = 'Android';\n  else if (ua.includes('iOS') || ua.includes('iPhone') || ua.includes('iPad')) os = 'iOS';\n\n  return { browser, os, device };\n}\n\nfunction formatIPLocation(ip: string | null): string {\n  if (!ip || ip === 'unknown') return 'Unknown location';\n  if (ip.startsWith('127.') || ip === '::1') return 'Localhost';\n  if (ip.startsWith('192.168.') || ip.startsWith('10.') || ip.startsWith('172.')) return 'Local network';\n  return ip;\n}\n\nexport function getUserSessions(userId: string): ParsedSession[] {\n  const sessions = db.getSessionsByUserId(userId);\n  return sessions.map(session => {\n    const parsed = parseUserAgent(session.userAgent);\n    return {\n      ...session,\n      browser: parsed.browser,\n      os: parsed.os,\n      device: parsed.device,\n      location: formatIPLocation(session.ipAddress)\n    };\n  });\n}\n\nexport function getUserOidcLinks(userId: string): db.UserOidcLink[] {\n  return db.getOidcLinksByUser(userId);\n}\n\nexport function unlinkOidcProvider(userId: string, linkId: string): { success: boolean; error?: string } {\n  const links = db.getOidcLinksByUser(userId);\n  const link = links.find(l => l.id === linkId);\n\n  if (!link) {\n    return { success: false, error: \"Link not found\" };\n  }\n\n  const user = db.getUserById(userId);\n  if (!user) {\n    return { success: false, error: \"User not found\" };\n  }\n\n  if (!user.passwordHash && links.length <= 1) {\n    return { success: false, error: \"Cannot unlink last login method\" };\n  }\n\n  db.deleteOidcLink(linkId);\n  return { success: true };\n}\n\nexport async function adminCreateUser(\n  email: string,\n  password: string | null,\n  displayName: string | null,\n  role: 'admin' | 'user'\n): Promise<{ success: boolean; userId?: string; error?: string }> {\n  const emailValidation = validateEmail(email);\n  if (!emailValidation.valid) {\n    return { success: false, error: emailValidation.error };\n  }\n\n  if (password) {\n    const passwordValidation = validatePassword(password);\n    if (!passwordValidation.valid) {\n      return { success: false, error: passwordValidation.error };\n    }\n  }\n\n  const existing = db.getUserByEmail(email);\n  if (existing) {\n    return { success: false, error: \"Registration failed. Please try again or use a different email.\" };\n  }\n\n  const userId = crypto.randomUUID();\n  const now = new Date().toISOString();\n  const sanitizedDisplayName = displayName ? displayName.substring(0, 100) : email.split('@')[0];\n\n  const user: db.User = {\n    id: userId,\n    email,\n    emailVerified: true,\n    displayName: sanitizedDisplayName,\n    avatarUrl: null,\n    passwordHash: password ? await hashPassword(password) : null,\n    role,\n    createdAt: now,\n    updatedAt: now,\n    lastLogin: null,\n    isActive: true,\n    failedLoginAttempts: 0,\n    lockedUntil: null,\n    totpSecret: null,\n    totpEnabled: false,\n    totpBackupCodes: null,\n    pendingEmail: null,\n    pendingEmailToken: null\n  };\n\n  db.createUser(user);\n\n  return { success: true, userId };\n}\n\nexport function adminUpdateUser(\n  userId: string,\n  updates: {\n    displayName?: string;\n    role?: 'admin' | 'user';\n    isActive?: boolean;\n    emailVerified?: boolean;\n  }\n): { success: boolean; error?: string } {\n  const user = db.getUserById(userId);\n  if (!user) {\n    return { success: false, error: \"User not found\" };\n  }\n\n  if (updates.role === 'user' && user.role === 'admin') {\n    const adminCount = db.countUsersByRole('admin');\n    if (adminCount <= 1) {\n      return { success: false, error: \"Cannot demote the last admin\" };\n    }\n  }\n\n  if (updates.isActive === false && user.role === 'admin') {\n    const adminCount = db.countUsersByRole('admin');\n    if (adminCount <= 1) {\n      return { success: false, error: \"Cannot disable the last admin\" };\n    }\n  }\n\n  if (updates.displayName !== undefined) user.displayName = updates.displayName.substring(0, 100);\n  if (updates.role !== undefined) user.role = updates.role;\n  if (updates.isActive !== undefined) user.isActive = updates.isActive;\n  if (updates.emailVerified !== undefined) user.emailVerified = updates.emailVerified;\n\n  user.updatedAt = new Date().toISOString();\n  db.updateUser(user);\n\n  return { success: true };\n}\n\nexport async function adminResetPassword(\n  userId: string,\n  newPassword: string\n): Promise<{ success: boolean; error?: string }> {\n  const user = db.getUserById(userId);\n  if (!user) {\n    return { success: false, error: \"User not found\" };\n  }\n\n  const passwordValidation = validatePassword(newPassword);\n  if (!passwordValidation.valid) {\n    return { success: false, error: passwordValidation.error };\n  }\n\n  user.passwordHash = await hashPassword(newPassword);\n  user.updatedAt = new Date().toISOString();\n  db.updateUser(user);\n\n  db.deleteAllUserSessions(userId);\n\n  return { success: true };\n}\n\nexport function adminDeleteUser(userId: string): { success: boolean; error?: string } {\n  return deleteAccount(userId);\n}\n\nexport function cleanupExpiredTokens(): { sessions: number; tokens: number } {\n  return {\n    sessions: db.cleanupExpiredSessions(),\n    tokens: db.cleanupExpiredUserTokens()\n  };\n}\n\nfunction base32Encode(buffer: Uint8Array): string {\n  const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';\n  let result = '';\n  let bits = 0;\n  let value = 0;\n  for (const byte of buffer) {\n    value = (value << 8) | byte;\n    bits += 8;\n    while (bits >= 5) {\n      result += alphabet[(value >>> (bits - 5)) & 31];\n      bits -= 5;\n    }\n  }\n  if (bits > 0) {\n    result += alphabet[(value << (5 - bits)) & 31];\n  }\n  return result;\n}\n\nfunction base32Decode(encoded: string): Uint8Array {\n  const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';\n  const cleanInput = encoded.toUpperCase().replace(/=+$/, '');\n  const result: number[] = [];\n  let bits = 0;\n  let value = 0;\n  for (const char of cleanInput) {\n    const index = alphabet.indexOf(char);\n    if (index === -1) continue;\n    value = (value << 5) | index;\n    bits += 5;\n    if (bits >= 8) {\n      result.push((value >>> (bits - 8)) & 0xff);\n      bits -= 8;\n    }\n  }\n  return new Uint8Array(result);\n}\n\nfunction generateTOTPCode(secret: Uint8Array, counter: number): string {\n  const buffer = Buffer.alloc(8);\n  buffer.writeUInt32BE(0, 0);\n  buffer.writeUInt32BE(counter, 4);\n\n  const hmac = createHmac('sha1', Buffer.from(secret));\n  hmac.update(buffer);\n  const hash = hmac.digest();\n\n  const offset = hash[hash.length - 1] & 0x0f;\n  const code = ((hash[offset] & 0x7f) << 24) |\n               ((hash[offset + 1] & 0xff) << 16) |\n               ((hash[offset + 2] & 0xff) << 8) |\n               (hash[offset + 3] & 0xff);\n\n  return (code % Math.pow(10, 6)).toString().padStart(6, '0');\n}\n\nexport function verifyTOTPCode(secret: string, code: string): boolean {\n  const secretBytes = base32Decode(secret);\n  const timeStep = Math.floor(Date.now() / 1000 / 30);\n\n  for (let i = -1; i <= 1; i++) {\n    if (generateTOTPCode(secretBytes, timeStep + i) === code) {\n      return true;\n    }\n  }\n  return false;\n}\n\nexport async function setupTOTP(userId: string): Promise<{ success: boolean; secret?: string; otpauthUrl?: string; error?: string }> {\n  const user = db.getUserById(userId);\n  if (!user) {\n    return { success: false, error: \"User not found\" };\n  }\n\n  if (user.totpEnabled) {\n    return { success: false, error: \"2FA is already enabled\" };\n  }\n\n  const secretBytes = randomBytes(20);\n  const base32secret = base32Encode(new Uint8Array(secretBytes));\n\n  const encryptedSecret = await oidc.encryptSecret(base32secret);\n  user.totpSecret = encryptedSecret;\n  user.updatedAt = new Date().toISOString();\n  db.updateUser(user);\n\n  const otpauthUrl = `otpauth://totp/TheOneFileVerse:${user.email}?secret=${base32secret}&issuer=TheOneFileVerse&algorithm=SHA1&digits=6&period=30`;\n\n  db.logAuthEvent('totp_setup_started', userId, null, {});\n\n  return { success: true, secret: base32secret, otpauthUrl };\n}\n\nexport async function verifyAndEnableTOTP(userId: string, code: string): Promise<{ success: boolean; backupCodes?: string[]; error?: string }> {\n  const user = db.getUserById(userId);\n  if (!user) {\n    return { success: false, error: \"User not found\" };\n  }\n\n  if (user.totpEnabled) {\n    return { success: false, error: \"2FA is already enabled\" };\n  }\n\n  if (!user.totpSecret) {\n    return { success: false, error: \"2FA setup not started\" };\n  }\n\n  const decryptedSecret = await oidc.decryptSecret(user.totpSecret);\n\n  if (!verifyTOTPCode(decryptedSecret, code)) {\n    return { success: false, error: \"Invalid verification code\" };\n  }\n\n  const backupCodes: string[] = [];\n  for (let i = 0; i < 10; i++) {\n    backupCodes.push(randomBytes(8).toString('hex'));\n  }\n\n  const encryptedBackupCodes = await oidc.encryptSecret(JSON.stringify(backupCodes));\n\n  user.totpEnabled = true;\n  user.totpBackupCodes = encryptedBackupCodes;\n  user.updatedAt = new Date().toISOString();\n  db.updateUser(user);\n\n  db.logAuthEvent('totp_enabled', userId, null, {});\n\n  return { success: true, backupCodes };\n}\n\nexport async function disableTOTP(userId: string, password: string): Promise<{ success: boolean; error?: string }> {\n  const user = db.getUserById(userId);\n  if (!user) {\n    return { success: false, error: \"User not found\" };\n  }\n\n  if (!user.totpEnabled) {\n    return { success: false, error: \"2FA is not enabled\" };\n  }\n\n  if (!user.passwordHash) {\n    return { success: false, error: \"Account uses SSO only\" };\n  }\n\n  const valid = await verifyPassword(password, user.passwordHash);\n  if (!valid) {\n    return { success: false, error: \"Invalid password\" };\n  }\n\n  user.totpSecret = null;\n  user.totpEnabled = false;\n  user.totpBackupCodes = null;\n  user.updatedAt = new Date().toISOString();\n  db.updateUser(user);\n\n  db.logAuthEvent('totp_disabled', userId, null, {});\n\n  return { success: true };\n}\n\nexport async function verifyBackupCode(userId: string, code: string): Promise<boolean> {\n  const user = db.getUserById(userId);\n  if (!user || !user.totpBackupCodes) {\n    return false;\n  }\n\n  let backupCodes: string[];\n  try {\n    const decrypted = await oidc.decryptSecret(user.totpBackupCodes);\n    backupCodes = JSON.parse(decrypted);\n  } catch {\n    return false;\n  }\n\n  const normalizedCode = code.toLowerCase().trim();\n  const index = backupCodes.findIndex(c => c.toLowerCase() === normalizedCode);\n  if (index === -1) {\n    return false;\n  }\n\n  backupCodes.splice(index, 1);\n  user.totpBackupCodes = await oidc.encryptSecret(JSON.stringify(backupCodes));\n  user.updatedAt = new Date().toISOString();\n  db.updateUser(user);\n\n  db.logAuthEvent('backup_code_used', userId, null, { remainingCodes: backupCodes.length });\n\n  return true;\n}\n\nexport async function loginWith2FA(\n  pendingToken: string,\n  totpCode: string,\n  ipAddress: string,\n  userAgent: string\n): Promise<{ success: boolean; userId?: string; sessionToken?: string; error?: string }> {\n  const tokenHash = await oidc.hashToken(pendingToken);\n  const userToken = db.getUserTokenByHash(tokenHash);\n\n  if (!userToken) {\n    return { success: false, error: \"Invalid or expired 2FA session\" };\n  }\n\n  if (userToken.type !== 'totp_pending') {\n    return { success: false, error: \"Invalid token type\" };\n  }\n\n  if (userToken.usedAt) {\n    return { success: false, error: \"Token already used\" };\n  }\n\n  if (new Date(userToken.expiresAt) < new Date()) {\n    db.deleteUserToken(userToken.id);\n    return { success: false, error: \"2FA session expired\" };\n  }\n\n  if (userToken.failedAttempts >= 3) {\n    db.deleteUserToken(userToken.id);\n    return { success: false, error: \"Too many failed attempts. Please log in again.\" };\n  }\n\n  const user = db.getUserById(userToken.userId);\n  if (!user || !user.isActive) {\n    return { success: false, error: \"User not found\" };\n  }\n\n  if (!user.totpSecret) {\n    return { success: false, error: \"2FA is not configured\" };\n  }\n\n  const decryptedSecret = await oidc.decryptSecret(user.totpSecret);\n  let codeValid = verifyTOTPCode(decryptedSecret, totpCode);\n\n  if (!codeValid) {\n    codeValid = await verifyBackupCode(user.id, totpCode);\n  }\n\n  if (!codeValid) {\n    const attempts = db.incrementTokenFailedAttempts(userToken.id);\n    db.logAuthEvent('login_failed', user.id, ipAddress, { reason: 'invalid_2fa_code', attempts });\n    if (attempts >= 3) {\n      db.deleteUserToken(userToken.id);\n      return { success: false, error: \"Too many failed attempts. Please log in again.\" };\n    }\n    return { success: false, error: \"Invalid 2FA code\" };\n  }\n\n  db.markUserTokenUsed(userToken.id);\n\n  user.lastLogin = new Date().toISOString();\n  user.updatedAt = new Date().toISOString();\n  user.failedLoginAttempts = 0;\n  user.lockedUntil = null;\n  db.updateUser(user);\n\n  const sessionToken = await createSessionToken(user.id, ipAddress, userAgent);\n\n  db.logAuthEvent('login_success', user.id, ipAddress, { method: 'password_2fa' });\n\n  return { success: true, userId: user.id, sessionToken };\n}\n\nexport async function requestEmailChange(\n  userId: string,\n  newEmail: string,\n  password: string,\n  baseUrl: string\n): Promise<{ success: boolean; error?: string }> {\n  const user = db.getUserById(userId);\n  if (!user) {\n    return { success: false, error: \"User not found\" };\n  }\n\n  const emailValidation = validateEmail(newEmail);\n  if (!emailValidation.valid) {\n    return { success: false, error: emailValidation.error };\n  }\n\n  if (!user.passwordHash) {\n    return { success: false, error: \"Account uses SSO only\" };\n  }\n\n  const valid = await verifyPassword(password, user.passwordHash);\n  if (!valid) {\n    return { success: false, error: \"Invalid password\" };\n  }\n\n  const normalizedNewEmail = newEmail.toLowerCase().trim();\n  const existing = db.getUserByEmail(normalizedNewEmail);\n  if (existing) {\n    return { success: false, error: \"Email is already in use\" };\n  }\n\n  const token = oidc.generateSecureToken(32);\n  const tokenHash = await oidc.hashToken(token);\n\n  db.deleteUserTokensByType(user.id, 'email_change');\n\n  db.createUserToken({\n    id: crypto.randomUUID(),\n    userId: user.id,\n    type: 'email_change',\n    tokenHash,\n    expiresAt: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(),\n    usedAt: null,\n    createdAt: new Date().toISOString()\n  });\n\n  user.pendingEmail = normalizedNewEmail;\n  user.pendingEmailToken = tokenHash;\n  user.updatedAt = new Date().toISOString();\n  db.updateUser(user);\n\n  const verifyUrl = `${baseUrl}/auth/verify-email-change?token=${token}`;\n  await mailer.sendVerificationEmail(normalizedNewEmail, user.displayName || 'User', verifyUrl);\n\n  db.logAuthEvent('email_change_requested', userId, null, { newEmail: normalizedNewEmail });\n\n  return { success: true };\n}\n\nexport async function confirmEmailChange(token: string): Promise<{ success: boolean; error?: string }> {\n  const tokenHash = await oidc.hashToken(token);\n  const userToken = db.getUserTokenByHash(tokenHash);\n\n  if (!userToken) {\n    return { success: false, error: \"Invalid or expired token\" };\n  }\n\n  if (userToken.type !== 'email_change') {\n    return { success: false, error: \"Invalid token type\" };\n  }\n\n  if (userToken.usedAt) {\n    return { success: false, error: \"Token already used\" };\n  }\n\n  if (new Date(userToken.expiresAt) < new Date()) {\n    return { success: false, error: \"Token expired\" };\n  }\n\n  const user = db.getUserById(userToken.userId);\n  if (!user) {\n    return { success: false, error: \"User not found\" };\n  }\n\n  if (!user.pendingEmail) {\n    return { success: false, error: \"No email change pending\" };\n  }\n\n  const existing = db.getUserByEmail(user.pendingEmail);\n  if (existing) {\n    return { success: false, error: \"Email is already in use\" };\n  }\n\n  const oldEmail = user.email;\n  user.email = user.pendingEmail;\n  user.pendingEmail = null;\n  user.pendingEmailToken = null;\n  user.updatedAt = new Date().toISOString();\n  db.updateUser(user);\n\n  db.markUserTokenUsed(userToken.id);\n\n  db.logAuthEvent('email_changed', user.id, null, { oldEmail, newEmail: user.email });\n\n  return { success: true };\n}\n\nsetInterval(() => {\n  cleanupExpiredTokens();\n}, 60 * 60 * 1000);\n"
  },
  {
    "path": "theonefile_verse/src/config.ts",
    "content": "import { readFileSync, existsSync } from \"fs\";\nimport { join } from \"path\";\nimport * as db from \"./database\";\nimport * as auth from \"./auth\";\nimport pkg from \"../package.json\";\n\nexport const APP_VERSION = pkg.version || \"unknown\";\nexport const PORT = parseInt(process.env.PORT || \"10101\");\nexport const DATA_DIR = process.env.DATA_DIR || \"./data\";\nexport const ROOMS_DIR = join(DATA_DIR, \"rooms\");\nexport const ADMIN_CONFIG_PATH = join(DATA_DIR, \"admin.json\");\nexport const SETTINGS_PATH = join(DATA_DIR, \"settings.json\");\n\nexport const ENV_UPDATE_INTERVAL = parseInt(process.env.UPDATE_INTERVAL || \"0\");\nexport const ENV_SKIP_UPDATE = process.env.SKIP_UPDATE === \"true\";\nexport const ENV_ADMIN_PASSWORD = process.env.ADMIN_PASSWORD || \"\";\nexport const ENV_TRUSTED_PROXY_COUNT = process.env.TRUSTED_PROXY_COUNT ? parseInt(process.env.TRUSTED_PROXY_COUNT) : null;\nexport const ENV_TRUSTED_PROXIES = process.env.TRUSTED_PROXIES ? process.env.TRUSTED_PROXIES.split(\",\").map(s => s.trim()).filter(Boolean) : null;\n\nexport const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;\n\nexport function isValidUUID(id: string): boolean {\n  return UUID_REGEX.test(id);\n}\n\nexport interface AdminConfig {\n  passwordHash: string;\n  createdAt: string;\n}\n\nexport interface InstanceSettings {\n  instancePasswordEnabled: boolean;\n  instancePasswordHash: string | null;\n  updateIntervalHours: number;\n  skipUpdates: boolean;\n  allowPublicRoomCreation: boolean;\n  maxRoomsPerInstance: number;\n  defaultDestructMode: \"time\" | \"empty\" | \"never\";\n  defaultDestructHours: number;\n  forcedTheme: \"user\" | \"dark\" | \"light\";\n  rateLimitEnabled: boolean;\n  rateLimitWindow: number;\n  rateLimitMaxAttempts: number;\n  chatEnabled: boolean;\n  cursorSharingEnabled: boolean;\n  nameChangeEnabled: boolean;\n  webhookUrl: string | null;\n  webhookEnabled: boolean;\n  backupEnabled: boolean;\n  backupIntervalHours: number;\n  backupRetentionCount: number;\n  adminPath: string;\n  showAdminLink: boolean;\n  trustedProxyCount: number;\n  trustedProxies: string[];\n  defaultRoomTheme: string;\n  forceWelcomeModal: boolean;\n  probeEnabled: boolean;\n  discoveryEnabled: boolean;\n  discoveryAdminOnly: boolean;\n  discoveryAllowPublicRanges: boolean;\n  discoveryMaxPrefix: number;\n}\n\nexport const defaultSettings: InstanceSettings = {\n  instancePasswordEnabled: false,\n  instancePasswordHash: null,\n  updateIntervalHours: 0,\n  skipUpdates: false,\n  allowPublicRoomCreation: true,\n  maxRoomsPerInstance: 0,\n  defaultDestructMode: \"time\",\n  defaultDestructHours: 24,\n  forcedTheme: \"user\",\n  rateLimitEnabled: true,\n  rateLimitWindow: 60,\n  rateLimitMaxAttempts: 10,\n  chatEnabled: true,\n  cursorSharingEnabled: true,\n  nameChangeEnabled: true,\n  webhookUrl: null,\n  webhookEnabled: false,\n  backupEnabled: false,\n  backupIntervalHours: 24,\n  backupRetentionCount: 7,\n  adminPath: \"admin\",\n  showAdminLink: true,\n  trustedProxyCount: 1,\n  trustedProxies: [],\n  defaultRoomTheme: \"\",\n  forceWelcomeModal: false,\n  probeEnabled: true,\n  discoveryEnabled: true,\n  discoveryAdminOnly: true,\n  discoveryAllowPublicRanges: false,\n  discoveryMaxPrefix: 20\n};\n\nexport function loadSettings(): InstanceSettings {\n  const allSettings = db.getAllSettings();\n  if (Object.keys(allSettings).length === 0) {\n    if (existsSync(SETTINGS_PATH)) {\n      try {\n        const saved = JSON.parse(readFileSync(SETTINGS_PATH, \"utf-8\"));\n        return { ...defaultSettings, ...saved };\n      } catch (e: any) { console.error(\"[Settings]\", e.message); return { ...defaultSettings }; }\n    }\n    return { ...defaultSettings };\n  }\n  const result = { ...defaultSettings };\n  for (const [key, value] of Object.entries(allSettings)) {\n    try {\n      (result as any)[key] = JSON.parse(value);\n    } catch {\n      (result as any)[key] = value;\n    }\n  }\n  return result;\n}\n\nexport function saveSettings(settings: InstanceSettings): void {\n  for (const [key, value] of Object.entries(settings)) {\n    db.setSetting(key, JSON.stringify(value));\n  }\n}\n\nlet instanceSettings = loadSettings();\n\nif (ENV_SKIP_UPDATE) instanceSettings.skipUpdates = true;\nif (ENV_UPDATE_INTERVAL > 0) instanceSettings.updateIntervalHours = ENV_UPDATE_INTERVAL;\nif (ENV_TRUSTED_PROXY_COUNT !== null) instanceSettings.trustedProxyCount = ENV_TRUSTED_PROXY_COUNT;\nif (ENV_TRUSTED_PROXIES !== null) instanceSettings.trustedProxies = ENV_TRUSTED_PROXIES;\n\nexport function getSettings(): InstanceSettings { return instanceSettings; }\nexport function updateSettings(s: InstanceSettings): void { instanceSettings = s; }\nexport function reloadSettings(): void { instanceSettings = loadSettings(); }\n\nexport function loadAdminConfig(): AdminConfig | null {\n  const hash = db.getSetting(\"admin_password_hash\");\n  const createdAt = db.getSetting(\"admin_created_at\");\n  if (!hash) {\n    if (existsSync(ADMIN_CONFIG_PATH)) {\n      try { return JSON.parse(readFileSync(ADMIN_CONFIG_PATH, \"utf-8\")); } catch (e: any) { console.error(\"[Config]\", e.message); return null; }\n    }\n    return null;\n  }\n  return { passwordHash: hash, createdAt: createdAt || new Date().toISOString() };\n}\n\nexport function saveAdminConfig(config: AdminConfig): void {\n  db.setSetting(\"admin_password_hash\", config.passwordHash);\n  db.setSetting(\"admin_created_at\", config.createdAt);\n}\n\nexport function isAdminConfigured(): boolean {\n  return !!ENV_ADMIN_PASSWORD || hasAdminUser() || getOldAdminPasswordHash() !== null;\n}\n\nexport function needsAdminMigration(): boolean {\n  return !hasAdminUser() && getOldAdminPasswordHash() !== null;\n}\n\nexport function hasAdminUser(): boolean {\n  return db.countUsersByRole('admin') > 0;\n}\n\nexport function getOldAdminPasswordHash(): string | null {\n  return db.getSetting('admin_password_hash');\n}\n\nexport async function verifyAdminPassword(password: string): Promise<boolean> {\n  if (ENV_ADMIN_PASSWORD) {\n    const maxLen = Math.max(password.length, ENV_ADMIN_PASSWORD.length) + 4;\n    const padded = Buffer.alloc(maxLen, 0);\n    const expected = Buffer.alloc(maxLen, 0);\n    const lenBuf1 = Buffer.alloc(4);\n    const lenBuf2 = Buffer.alloc(4);\n    lenBuf1.writeUInt32BE(password.length);\n    lenBuf2.writeUInt32BE(ENV_ADMIN_PASSWORD.length);\n    Buffer.from(password).copy(padded);\n    lenBuf1.copy(padded, maxLen - 4);\n    Buffer.from(ENV_ADMIN_PASSWORD).copy(expected);\n    lenBuf2.copy(expected, maxLen - 4);\n    return crypto.timingSafeEqual(padded, expected);\n  }\n  const config = loadAdminConfig();\n  if (!config) return false;\n  return await auth.verifyPassword(password, config.passwordHash);\n}\n\nexport async function verifyInstancePassword(password: string): Promise<boolean> {\n  if (!instanceSettings.instancePasswordEnabled || !instanceSettings.instancePasswordHash) return true;\n  return await auth.verifyPassword(password, instanceSettings.instancePasswordHash);\n}\n\nexport function isInstanceLocked(): boolean {\n  return instanceSettings.instancePasswordEnabled && !!instanceSettings.instancePasswordHash;\n}\n\nexport function getAdminPath(): string {\n  const path = instanceSettings.adminPath || \"admin\";\n  const sanitized = path.replace(/[^a-zA-Z0-9_-]/g, '');\n  return sanitized || \"admin\";\n}\n\nexport function isCustomAdminPath(path: string): boolean {\n  const adminPath = getAdminPath();\n  return path === `/${adminPath}` || path.startsWith(`/${adminPath}/`);\n}\n\nexport function validateAdminPath(newPath: string): { valid: boolean; error?: string } {\n  if (!newPath || newPath.length < 2) {\n    return { valid: false, error: \"Admin path must be at least 2 characters\" };\n  }\n  if (newPath.length > 50) {\n    return { valid: false, error: \"Admin path must be less than 50 characters\" };\n  }\n  if (!/^[a-zA-Z0-9_-]+$/.test(newPath)) {\n    return { valid: false, error: \"Admin path can only contain letters, numbers, hyphens, and underscores\" };\n  }\n  const reserved = [\"api\", \"s\", \"ws\", \"auth\", \"public\", \"static\", \"assets\"];\n  if (reserved.includes(newPath.toLowerCase())) {\n    return { valid: false, error: \"This path is reserved\" };\n  }\n  return { valid: true };\n}\n\nexport function isAdminRoute(path: string): boolean {\n  return path === \"/\" || path === \"/index.html\" || path.startsWith(\"/s/\") ||\n         path.startsWith(\"/ws/\") || path.startsWith(\"/api/room\");\n}\n\nexport function getExternalOrigin(req: Request): string {\n  const fwdProto = req.headers.get(\"x-forwarded-proto\");\n  const fwdHost = req.headers.get(\"x-forwarded-host\");\n  if (fwdProto && fwdHost) {\n    return `${fwdProto.split(\",\")[0].trim()}://${fwdHost.split(\",\")[0].trim()}`;\n  }\n  return new URL(req.url).origin;\n}\n"
  },
  {
    "path": "theonefile_verse/src/database.ts",
    "content": "import { Database } from \"bun:sqlite\";\nimport { existsSync, mkdirSync, readdirSync, readFileSync, unlinkSync } from \"fs\";\nimport { join } from \"path\";\n\nfunction sanitizeSearchQuery(query: string): string | null {\n  if (query.length > 100) return null;\n  return query.replace(/[%_\\\\]/g, '\\\\$&');\n}\n\nconst DATA_DIR = process.env.DATA_DIR || \"./data\";\nconst DB_PATH = join(DATA_DIR, \"theonefile.db\");\nconst ROOMS_DIR = join(DATA_DIR, \"rooms\");\nconst ADMIN_CONFIG_PATH = join(DATA_DIR, \"admin.json\");\nconst SETTINGS_PATH = join(DATA_DIR, \"settings.json\");\n\nif (!existsSync(DATA_DIR)) mkdirSync(DATA_DIR, { recursive: true });\n\nconst db = new Database(DB_PATH);\ndb.exec(\"PRAGMA journal_mode = WAL\");\ndb.exec(\"PRAGMA foreign_keys = ON\");\n\nfunction migrateAddColumn(table: string, column: string, definition: string) {\n  try {\n    const cols = db.prepare(`PRAGMA table_info(${table})`).all() as any[];\n    if (!cols.some((c: any) => c.name === column)) {\n      db.exec(`ALTER TABLE ${table} ADD COLUMN ${column} ${definition}`);\n    }\n  } catch (e: any) {\n    console.error(`Migration error (${table}.${column}):`, e.message);\n  }\n}\n\ndb.exec(`\n  CREATE TABLE IF NOT EXISTS rooms (\n    id TEXT PRIMARY KEY,\n    created TEXT NOT NULL,\n    last_activity TEXT NOT NULL,\n    creator_id TEXT NOT NULL,\n    password_hash TEXT,\n    destruct_mode TEXT NOT NULL DEFAULT 'time',\n    destruct_value INTEGER NOT NULL DEFAULT 86400000,\n    topology TEXT,\n    owner_user_id TEXT,\n    allow_guests INTEGER NOT NULL DEFAULT 1\n  )\n`);\n\nmigrateAddColumn(\"rooms\", \"owner_user_id\", \"TEXT\");\nmigrateAddColumn(\"rooms\", \"allow_guests\", \"INTEGER NOT NULL DEFAULT 1\");\n\ndb.exec(`\n  CREATE TABLE IF NOT EXISTS admin_settings (\n    key TEXT PRIMARY KEY,\n    value TEXT NOT NULL\n  )\n`);\n\ndb.exec(`\n  CREATE TABLE IF NOT EXISTS audit_logs (\n    id INTEGER PRIMARY KEY AUTOINCREMENT,\n    timestamp TEXT NOT NULL,\n    action TEXT NOT NULL,\n    actor TEXT,\n    actor_ip TEXT,\n    target_type TEXT,\n    target_id TEXT,\n    details TEXT,\n    created_at TEXT DEFAULT CURRENT_TIMESTAMP\n  )\n`);\n\ndb.exec(`\n  CREATE INDEX IF NOT EXISTS idx_audit_logs_timestamp ON audit_logs(timestamp)\n`);\n\ndb.exec(`\n  CREATE INDEX IF NOT EXISTS idx_audit_logs_action ON audit_logs(action)\n`);\n\ndb.exec(`\n  CREATE INDEX IF NOT EXISTS idx_audit_logs_target ON audit_logs(target_type, target_id)\n`);\n\ndb.exec(`\n  CREATE TABLE IF NOT EXISTS activity_logs (\n    id INTEGER PRIMARY KEY AUTOINCREMENT,\n    timestamp TEXT NOT NULL,\n    room_id TEXT NOT NULL,\n    user_id TEXT,\n    user_name TEXT,\n    event_type TEXT NOT NULL,\n    details TEXT,\n    ip_address TEXT\n  )\n`);\n\ndb.exec(`\n  CREATE INDEX IF NOT EXISTS idx_activity_logs_room ON activity_logs(room_id)\n`);\n\ndb.exec(`\n  CREATE INDEX IF NOT EXISTS idx_activity_logs_timestamp ON activity_logs(timestamp)\n`);\n\ndb.exec(`\n  CREATE INDEX IF NOT EXISTS idx_activity_logs_room_timestamp ON activity_logs(room_id, timestamp DESC)\n`);\n\ndb.exec(`\n  CREATE INDEX IF NOT EXISTS idx_rooms_last_activity ON rooms(last_activity)\n`);\n\ndb.exec(`\n  CREATE INDEX IF NOT EXISTS idx_rooms_creator ON rooms(creator_id)\n`);\n\ndb.exec(`\n  CREATE TABLE IF NOT EXISTS api_keys (\n    id TEXT PRIMARY KEY,\n    name TEXT NOT NULL,\n    key_hash TEXT NOT NULL,\n    permissions TEXT NOT NULL,\n    created_at TEXT NOT NULL,\n    last_used TEXT,\n    expires_at TEXT,\n    active INTEGER NOT NULL DEFAULT 1\n  )\n`);\ndb.exec(`CREATE INDEX IF NOT EXISTS idx_api_keys_hash ON api_keys(key_hash)`);\n\ndb.exec(`\n  CREATE TABLE IF NOT EXISTS backups (\n    id TEXT PRIMARY KEY,\n    filename TEXT NOT NULL,\n    created_at TEXT NOT NULL,\n    size_bytes INTEGER NOT NULL,\n    room_count INTEGER NOT NULL,\n    auto_generated INTEGER NOT NULL DEFAULT 0\n  )\n`);\n\ndb.exec(`\n  CREATE TABLE IF NOT EXISTS users (\n    id TEXT PRIMARY KEY,\n    email TEXT UNIQUE,\n    email_verified INTEGER NOT NULL DEFAULT 0,\n    display_name TEXT,\n    avatar_url TEXT,\n    password_hash TEXT,\n    role TEXT NOT NULL DEFAULT 'user',\n    created_at TEXT NOT NULL,\n    updated_at TEXT NOT NULL,\n    last_login TEXT,\n    is_active INTEGER NOT NULL DEFAULT 1,\n    failed_login_attempts INTEGER NOT NULL DEFAULT 0,\n    locked_until TEXT\n  )\n`);\n\ndb.exec(`CREATE INDEX IF NOT EXISTS idx_users_email ON users(email)`);\ndb.exec(`CREATE INDEX IF NOT EXISTS idx_users_role ON users(role)`);\n\nmigrateAddColumn(\"users\", \"failed_login_attempts\", \"INTEGER NOT NULL DEFAULT 0\");\nmigrateAddColumn(\"users\", \"locked_until\", \"TEXT\");\nmigrateAddColumn(\"users\", \"totp_secret\", \"TEXT\");\nmigrateAddColumn(\"users\", \"totp_enabled\", \"INTEGER NOT NULL DEFAULT 0\");\nmigrateAddColumn(\"users\", \"totp_backup_codes\", \"TEXT\");\nmigrateAddColumn(\"users\", \"pending_email\", \"TEXT\");\nmigrateAddColumn(\"users\", \"pending_email_token\", \"TEXT\");\n\ndb.exec(`\n  CREATE TABLE IF NOT EXISTS user_oidc_links (\n    id TEXT PRIMARY KEY,\n    user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,\n    provider TEXT NOT NULL,\n    provider_user_id TEXT NOT NULL,\n    provider_email TEXT,\n    access_token_encrypted TEXT,\n    refresh_token_encrypted TEXT,\n    token_expires_at TEXT,\n    created_at TEXT NOT NULL,\n    UNIQUE(provider, provider_user_id)\n  )\n`);\n\ndb.exec(`CREATE INDEX IF NOT EXISTS idx_oidc_links_user ON user_oidc_links(user_id)`);\ndb.exec(`CREATE INDEX IF NOT EXISTS idx_oidc_links_provider ON user_oidc_links(provider, provider_user_id)`);\n\ndb.exec(`\n  CREATE TABLE IF NOT EXISTS user_sessions (\n    id TEXT PRIMARY KEY,\n    user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,\n    token_hash TEXT UNIQUE NOT NULL,\n    ip_address TEXT,\n    user_agent TEXT,\n    expires_at TEXT NOT NULL,\n    created_at TEXT NOT NULL\n  )\n`);\n\ndb.exec(`CREATE INDEX IF NOT EXISTS idx_sessions_user ON user_sessions(user_id)`);\ndb.exec(`CREATE INDEX IF NOT EXISTS idx_sessions_token ON user_sessions(token_hash)`);\ndb.exec(`CREATE INDEX IF NOT EXISTS idx_sessions_expires ON user_sessions(expires_at)`);\n\ndb.exec(`\n  CREATE TABLE IF NOT EXISTS user_tokens (\n    id TEXT PRIMARY KEY,\n    user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,\n    type TEXT NOT NULL,\n    token_hash TEXT UNIQUE NOT NULL,\n    expires_at TEXT NOT NULL,\n    used_at TEXT,\n    created_at TEXT NOT NULL\n  )\n`);\n\ndb.exec(`CREATE INDEX IF NOT EXISTS idx_user_tokens_hash ON user_tokens(token_hash)`);\ndb.exec(`CREATE INDEX IF NOT EXISTS idx_user_tokens_user ON user_tokens(user_id)`);\nmigrateAddColumn(\"user_tokens\", \"failed_attempts\", \"INTEGER NOT NULL DEFAULT 0\");\n\ndb.exec(`\n  CREATE TABLE IF NOT EXISTS smtp_configs (\n    id TEXT PRIMARY KEY,\n    name TEXT NOT NULL,\n    provider_type TEXT NOT NULL,\n    host TEXT,\n    port INTEGER,\n    secure_mode TEXT NOT NULL DEFAULT 'tls',\n    username TEXT,\n    password_encrypted TEXT,\n    api_key_encrypted TEXT,\n    from_email TEXT NOT NULL,\n    from_name TEXT,\n    is_default INTEGER NOT NULL DEFAULT 0,\n    is_active INTEGER NOT NULL DEFAULT 1,\n    allow_insecure_tls INTEGER NOT NULL DEFAULT 0,\n    created_at TEXT NOT NULL,\n    updated_at TEXT NOT NULL\n  )\n`);\n\nmigrateAddColumn(\"smtp_configs\", \"allow_insecure_tls\", \"INTEGER NOT NULL DEFAULT 0\");\n\ndb.exec(`\n  CREATE TABLE IF NOT EXISTS oidc_providers (\n    id TEXT PRIMARY KEY,\n    name TEXT NOT NULL,\n    provider_type TEXT NOT NULL,\n    client_id TEXT NOT NULL,\n    client_secret_encrypted TEXT NOT NULL,\n    issuer_url TEXT,\n    authorization_url TEXT,\n    token_url TEXT,\n    userinfo_url TEXT,\n    jwks_uri TEXT,\n    scopes TEXT NOT NULL DEFAULT 'openid email profile',\n    is_active INTEGER NOT NULL DEFAULT 1,\n    display_order INTEGER NOT NULL DEFAULT 0,\n    icon_url TEXT,\n    created_at TEXT NOT NULL,\n    updated_at TEXT NOT NULL\n  )\n`);\n\ndb.exec(`\n  CREATE TABLE IF NOT EXISTS user_preferences (\n    user_id TEXT PRIMARY KEY REFERENCES users(id) ON DELETE CASCADE,\n    theme TEXT DEFAULT 'system',\n    email_notifications INTEGER NOT NULL DEFAULT 1,\n    default_room_settings TEXT\n  )\n`);\n\ndb.exec(`\n  CREATE TABLE IF NOT EXISTS email_templates (\n    id TEXT PRIMARY KEY,\n    name TEXT NOT NULL UNIQUE,\n    subject TEXT NOT NULL,\n    body_html TEXT NOT NULL,\n    body_text TEXT NOT NULL,\n    created_at TEXT NOT NULL,\n    updated_at TEXT NOT NULL\n  )\n`);\n\ndb.exec(`\n  CREATE TABLE IF NOT EXISTS email_logs (\n    id TEXT PRIMARY KEY,\n    to_email TEXT NOT NULL,\n    template_name TEXT,\n    subject TEXT NOT NULL,\n    status TEXT NOT NULL,\n    error_message TEXT,\n    smtp_config_id TEXT,\n    sent_at TEXT NOT NULL\n  )\n`);\n\ndb.exec(`CREATE INDEX IF NOT EXISTS idx_email_logs_date ON email_logs(sent_at)`);\ndb.exec(`CREATE INDEX IF NOT EXISTS idx_email_logs_status ON email_logs(status)`);\n\ndb.exec(`\n  CREATE TABLE IF NOT EXISTS oidc_states (\n    state TEXT PRIMARY KEY,\n    provider_id TEXT NOT NULL,\n    code_verifier TEXT NOT NULL,\n    nonce TEXT NOT NULL,\n    redirect_uri TEXT NOT NULL,\n    link_user_id TEXT,\n    post_login_redirect TEXT,\n    created_at TEXT NOT NULL,\n    expires_at TEXT NOT NULL\n  )\n`);\n\ndb.exec(`CREATE INDEX IF NOT EXISTS idx_oidc_states_expires ON oidc_states(expires_at)`);\n\nmigrateAddColumn(\"oidc_states\", \"post_login_redirect\", \"TEXT\");\n\ndb.exec(`\n  CREATE TABLE IF NOT EXISTS csrf_tokens (\n    token TEXT PRIMARY KEY,\n    used INTEGER NOT NULL DEFAULT 0,\n    created_at TEXT NOT NULL,\n    expires_at TEXT NOT NULL\n  )\n`);\n\ndb.exec(`CREATE INDEX IF NOT EXISTS idx_csrf_tokens_expires ON csrf_tokens(expires_at)`);\n\ndb.exec(`\n  CREATE TABLE IF NOT EXISTS email_rate_limits (\n    id INTEGER PRIMARY KEY AUTOINCREMENT,\n    email TEXT NOT NULL,\n    action TEXT NOT NULL,\n    created_at TEXT NOT NULL\n  )\n`);\n\ndb.exec(`CREATE INDEX IF NOT EXISTS idx_email_rate_limits_lookup ON email_rate_limits(email, action, created_at)`);\n\nexport interface OidcState {\n  state: string;\n  providerId: string;\n  codeVerifier: string;\n  nonce: string;\n  redirectUri: string;\n  linkUserId: string | null;\n  postLoginRedirect: string | null;\n  createdAt: string;\n  expiresAt: string;\n}\n\nexport interface CsrfToken {\n  token: string;\n  used: boolean;\n  createdAt: string;\n  expiresAt: string;\n}\n\nexport interface Room {\n  id: string;\n  created: string;\n  lastActivity: string;\n  creatorId: string;\n  passwordHash: string | null;\n  destruct: { mode: \"time\" | \"empty\" | \"never\"; value: number };\n  topology: any;\n  ownerUserId: string | null;\n  allowGuests: boolean;\n}\n\nexport interface AuditLog {\n  id?: number;\n  timestamp: string;\n  action: string;\n  actor?: string;\n  actorIp?: string;\n  targetType?: string;\n  targetId?: string;\n  details?: any;\n}\n\nexport interface ActivityLog {\n  id?: number;\n  timestamp: string;\n  roomId: string;\n  userId?: string;\n  userName?: string;\n  eventType: string;\n  details?: any;\n  ipAddress?: string;\n}\n\nexport interface ApiKey {\n  id: string;\n  name: string;\n  keyHash: string;\n  permissions: string[];\n  createdAt: string;\n  lastUsed?: string;\n  expiresAt?: string;\n  active: boolean;\n}\n\nexport interface Backup {\n  id: string;\n  filename: string;\n  createdAt: string;\n  sizeBytes: number;\n  roomCount: number;\n  autoGenerated: boolean;\n}\n\nexport interface User {\n  id: string;\n  email: string | null;\n  emailVerified: boolean;\n  displayName: string | null;\n  avatarUrl: string | null;\n  passwordHash: string | null;\n  role: 'admin' | 'user' | 'guest';\n  createdAt: string;\n  updatedAt: string;\n  lastLogin: string | null;\n  isActive: boolean;\n  failedLoginAttempts: number;\n  lockedUntil: string | null;\n  totpSecret: string | null;\n  totpEnabled: boolean;\n  totpBackupCodes: string | null;\n  pendingEmail: string | null;\n  pendingEmailToken: string | null;\n}\n\nexport interface UserOidcLink {\n  id: string;\n  userId: string;\n  provider: string;\n  providerUserId: string;\n  providerEmail: string | null;\n  accessTokenEncrypted: string | null;\n  refreshTokenEncrypted: string | null;\n  tokenExpiresAt: string | null;\n  createdAt: string;\n}\n\nexport interface UserSession {\n  id: string;\n  userId: string;\n  tokenHash: string;\n  ipAddress: string | null;\n  userAgent: string | null;\n  expiresAt: string;\n  createdAt: string;\n}\n\nexport interface UserToken {\n  id: string;\n  userId: string;\n  type: 'email_verify' | 'password_reset' | 'magic_link' | 'totp_pending' | 'email_change';\n  tokenHash: string;\n  expiresAt: string;\n  usedAt: string | null;\n  createdAt: string;\n  failedAttempts: number;\n}\n\nexport interface SmtpConfig {\n  id: string;\n  name: string;\n  providerType: string;\n  host: string | null;\n  port: number | null;\n  secureMode: 'none' | 'tls' | 'starttls';\n  username: string | null;\n  passwordEncrypted: string | null;\n  apiKeyEncrypted: string | null;\n  fromEmail: string;\n  fromName: string | null;\n  isDefault: boolean;\n  isActive: boolean;\n  allowInsecureTls: boolean;\n  createdAt: string;\n  updatedAt: string;\n}\n\nexport interface OidcProvider {\n  id: string;\n  name: string;\n  providerType: string;\n  clientId: string;\n  clientSecretEncrypted: string;\n  issuerUrl: string | null;\n  authorizationUrl: string | null;\n  tokenUrl: string | null;\n  userinfoUrl: string | null;\n  jwksUri: string | null;\n  scopes: string;\n  isActive: boolean;\n  displayOrder: number;\n  iconUrl: string | null;\n  createdAt: string;\n  updatedAt: string;\n}\n\nexport interface UserPreferences {\n  userId: string;\n  theme: string;\n  emailNotifications: boolean;\n  defaultRoomSettings: any;\n}\n\nexport interface EmailTemplate {\n  id: string;\n  name: string;\n  subject: string;\n  bodyHtml: string;\n  bodyText: string;\n  createdAt: string;\n  updatedAt: string;\n}\n\nexport interface EmailLog {\n  id: string;\n  toEmail: string;\n  templateName: string | null;\n  subject: string;\n  status: 'sent' | 'failed' | 'pending';\n  errorMessage: string | null;\n  smtpConfigId: string | null;\n  sentAt: string;\n}\n\nconst stmtInsertRoom = db.prepare(`\n  INSERT INTO rooms (id, created, last_activity, creator_id, password_hash, destruct_mode, destruct_value, topology, owner_user_id, allow_guests)\n  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n`);\n\nconst stmtUpdateRoom = db.prepare(`\n  UPDATE rooms SET last_activity = ?, topology = ? WHERE id = ?\n`);\n\nconst stmtUpdateRoomSettings = db.prepare(`\n  UPDATE rooms SET allow_guests = ?, owner_user_id = ? WHERE id = ?\n`);\n\nconst stmtGetRoom = db.prepare(`\n  SELECT * FROM rooms WHERE id = ?\n`);\n\nconst stmtDeleteRoom = db.prepare(`\n  DELETE FROM rooms WHERE id = ?\n`);\n\nconst stmtListRooms = db.prepare(`\n  SELECT * FROM rooms ORDER BY created DESC\n`);\n\nconst stmtListRoomsPaginated = db.prepare(`\n  SELECT * FROM rooms ORDER BY created DESC LIMIT ? OFFSET ?\n`);\n\nconst stmtSearchRooms = db.prepare(`\n  SELECT * FROM rooms WHERE id LIKE ? ESCAPE '\\\\' OR creator_id LIKE ? ESCAPE '\\\\' ORDER BY created DESC LIMIT ? OFFSET ?\n`);\n\nconst stmtCountRooms = db.prepare(`\n  SELECT COUNT(*) as count FROM rooms\n`);\n\nconst stmtGetSetting = db.prepare(`\n  SELECT value FROM admin_settings WHERE key = ?\n`);\n\nconst stmtSetSetting = db.prepare(`\n  INSERT OR REPLACE INTO admin_settings (key, value) VALUES (?, ?)\n`);\n\nconst stmtDeleteSetting = db.prepare(`\n  DELETE FROM admin_settings WHERE key = ?\n`);\n\nconst stmtGetAllSettings = db.prepare(`\n  SELECT key, value FROM admin_settings\n`);\n\nconst stmtInsertAuditLog = db.prepare(`\n  INSERT INTO audit_logs (timestamp, action, actor, actor_ip, target_type, target_id, details)\n  VALUES (?, ?, ?, ?, ?, ?, ?)\n`);\n\nconst stmtGetAuditLogs = db.prepare(`\n  SELECT * FROM audit_logs ORDER BY timestamp DESC LIMIT ? OFFSET ?\n`);\n\nconst stmtSearchAuditLogs = db.prepare(`\n  SELECT * FROM audit_logs WHERE action LIKE ? ESCAPE '\\\\' OR actor LIKE ? ESCAPE '\\\\' OR target_id LIKE ? ESCAPE '\\\\' ORDER BY timestamp DESC LIMIT ? OFFSET ?\n`);\n\nconst stmtInsertActivityLog = db.prepare(`\n  INSERT INTO activity_logs (timestamp, room_id, user_id, user_name, event_type, details, ip_address)\n  VALUES (?, ?, ?, ?, ?, ?, ?)\n`);\n\nconst stmtGetActivityLogs = db.prepare(`\n  SELECT * FROM activity_logs WHERE room_id = ? ORDER BY timestamp DESC LIMIT ? OFFSET ?\n`);\n\nconst stmtGetAllActivityLogs = db.prepare(`\n  SELECT * FROM activity_logs ORDER BY timestamp DESC LIMIT ? OFFSET ?\n`);\n\nconst stmtInsertApiKey = db.prepare(`\n  INSERT INTO api_keys (id, name, key_hash, permissions, created_at, expires_at, active)\n  VALUES (?, ?, ?, ?, ?, ?, ?)\n`);\n\nconst stmtGetApiKey = db.prepare(`\n  SELECT * FROM api_keys WHERE id = ? AND active = 1\n`);\n\nconst stmtGetApiKeyByHash = db.prepare(`\n  SELECT * FROM api_keys WHERE key_hash = ? AND active = 1\n`);\n\nconst stmtUpdateApiKeyLastUsed = db.prepare(`\n  UPDATE api_keys SET last_used = ? WHERE id = ?\n`);\n\nconst stmtListApiKeys = db.prepare(`\n  SELECT id, name, permissions, created_at, last_used, expires_at, active FROM api_keys ORDER BY created_at DESC\n`);\n\nconst stmtDeactivateApiKey = db.prepare(`\n  UPDATE api_keys SET active = 0 WHERE id = ?\n`);\n\nconst stmtInsertBackup = db.prepare(`\n  INSERT INTO backups (id, filename, created_at, size_bytes, room_count, auto_generated)\n  VALUES (?, ?, ?, ?, ?, ?)\n`);\n\nconst stmtListBackups = db.prepare(`\n  SELECT * FROM backups ORDER BY created_at DESC\n`);\n\nconst stmtDeleteBackup = db.prepare(`\n  DELETE FROM backups WHERE id = ?\n`);\n\nconst stmtGetOldAutoBackups = db.prepare(`\n  SELECT * FROM backups WHERE auto_generated = 1 ORDER BY created_at DESC LIMIT -1 OFFSET ?\n`);\n\nfunction rowToRoom(row: any): Room | null {\n  if (!row) return null;\n  return {\n    id: row.id,\n    created: row.created,\n    lastActivity: row.last_activity,\n    creatorId: row.creator_id,\n    passwordHash: row.password_hash,\n    destruct: { mode: row.destruct_mode, value: row.destruct_value },\n    topology: row.topology ? JSON.parse(row.topology) : null,\n    ownerUserId: row.owner_user_id || null,\n    allowGuests: row.allow_guests !== 0\n  };\n}\n\nexport function createRoom(room: Room): void {\n  stmtInsertRoom.run(\n    room.id,\n    room.created,\n    room.lastActivity,\n    room.creatorId,\n    room.passwordHash,\n    room.destruct.mode,\n    room.destruct.value,\n    room.topology ? JSON.stringify(room.topology) : null,\n    room.ownerUserId || null,\n    room.allowGuests ? 1 : 0\n  );\n}\n\nexport function getRoom(id: string): Room | null {\n  return rowToRoom(stmtGetRoom.get(id));\n}\n\nexport function updateRoom(id: string, lastActivity: string, topology: any): void {\n  stmtUpdateRoom.run(lastActivity, topology ? JSON.stringify(topology) : null, id);\n}\n\nexport function updateRoomSettings(id: string, allowGuests: boolean, ownerUserId: string | null): void {\n  stmtUpdateRoomSettings.run(allowGuests ? 1 : 0, ownerUserId, id);\n}\n\nexport function deleteRoom(id: string): boolean {\n  const result = stmtDeleteRoom.run(id);\n  return result.changes > 0;\n}\n\nexport function listRooms(limit?: number, offset?: number): Room[] {\n  if (limit !== undefined) {\n    return (stmtListRoomsPaginated.all(limit, offset || 0) as any[]).map(rowToRoom).filter(r => r !== null) as Room[];\n  }\n  return (stmtListRooms.all() as any[]).map(rowToRoom).filter(r => r !== null) as Room[];\n}\n\nexport function searchRooms(query: string, limit: number = 50, offset: number = 0): Room[] {\n  const sanitized = sanitizeSearchQuery(query);\n  if (!sanitized) return [];\n  const searchPattern = `%${sanitized}%`;\n  return (stmtSearchRooms.all(searchPattern, searchPattern, limit, offset) as any[])\n    .map(rowToRoom)\n    .filter(r => r !== null) as Room[];\n}\n\nexport function countRooms(): number {\n  const result = stmtCountRooms.get() as { count: number };\n  return result.count;\n}\n\nexport function getSetting(key: string): string | null {\n  const row = stmtGetSetting.get(key) as { value: string } | undefined;\n  return row?.value ?? null;\n}\n\nexport function setSetting(key: string, value: string): void {\n  stmtSetSetting.run(key, value);\n}\n\nexport function deleteSetting(key: string): void {\n  stmtDeleteSetting.run(key);\n}\n\nexport function getAllSettings(): Record<string, string> {\n  const rows = stmtGetAllSettings.all() as { key: string; value: string }[];\n  const result: Record<string, string> = {};\n  for (const row of rows) {\n    result[row.key] = row.value;\n  }\n  return result;\n}\n\nexport function addAuditLog(log: AuditLog): void {\n  stmtInsertAuditLog.run(\n    log.timestamp,\n    log.action,\n    log.actor || null,\n    log.actorIp || null,\n    log.targetType || null,\n    log.targetId || null,\n    log.details ? JSON.stringify(log.details) : null\n  );\n}\n\nexport function getAuditLogs(limit: number = 100, offset: number = 0): AuditLog[] {\n  return (stmtGetAuditLogs.all(limit, offset) as any[]).map(row => ({\n    id: row.id,\n    timestamp: row.timestamp,\n    action: row.action,\n    actor: row.actor,\n    actorIp: row.actor_ip,\n    targetType: row.target_type,\n    targetId: row.target_id,\n    details: row.details ? JSON.parse(row.details) : null\n  }));\n}\n\nexport function searchAuditLogs(query: string, limit: number = 100, offset: number = 0): AuditLog[] {\n  const sanitized = sanitizeSearchQuery(query);\n  if (!sanitized) return [];\n  const searchPattern = `%${sanitized}%`;\n  return (stmtSearchAuditLogs.all(searchPattern, searchPattern, searchPattern, limit, offset) as any[]).map(row => ({\n    id: row.id,\n    timestamp: row.timestamp,\n    action: row.action,\n    actor: row.actor,\n    actorIp: row.actor_ip,\n    targetType: row.target_type,\n    targetId: row.target_id,\n    details: row.details ? JSON.parse(row.details) : null\n  }));\n}\n\nexport function addActivityLog(log: ActivityLog): void {\n  stmtInsertActivityLog.run(\n    log.timestamp,\n    log.roomId,\n    log.userId || null,\n    log.userName || null,\n    log.eventType,\n    log.details ? JSON.stringify(log.details) : null,\n    log.ipAddress || null\n  );\n}\n\nexport function getActivityLogs(roomId: string, limit: number = 100, offset: number = 0): ActivityLog[] {\n  return (stmtGetActivityLogs.all(roomId, limit, offset) as any[]).map(row => ({\n    id: row.id,\n    timestamp: row.timestamp,\n    roomId: row.room_id,\n    userId: row.user_id,\n    userName: row.user_name,\n    eventType: row.event_type,\n    details: row.details ? JSON.parse(row.details) : null,\n    ipAddress: row.ip_address\n  }));\n}\n\nexport function getAllActivityLogs(limit: number = 100, offset: number = 0): ActivityLog[] {\n  return (stmtGetAllActivityLogs.all(limit, offset) as any[]).map(row => ({\n    id: row.id,\n    timestamp: row.timestamp,\n    roomId: row.room_id,\n    userId: row.user_id,\n    userName: row.user_name,\n    eventType: row.event_type,\n    details: row.details ? JSON.parse(row.details) : null,\n    ipAddress: row.ip_address\n  }));\n}\n\nexport function createApiKey(apiKey: ApiKey): void {\n  stmtInsertApiKey.run(\n    apiKey.id,\n    apiKey.name,\n    apiKey.keyHash,\n    JSON.stringify(apiKey.permissions),\n    apiKey.createdAt,\n    apiKey.expiresAt || null,\n    apiKey.active ? 1 : 0\n  );\n}\n\nexport function getApiKeyById(id: string): ApiKey | null {\n  const row = stmtGetApiKey.get(id) as any;\n  if (!row) return null;\n  return {\n    id: row.id,\n    name: row.name,\n    keyHash: row.key_hash,\n    permissions: (() => { try { return row.permissions ? JSON.parse(row.permissions) : []; } catch { return []; } })(),\n    createdAt: row.created_at,\n    lastUsed: row.last_used,\n    expiresAt: row.expires_at,\n    active: row.active === 1\n  };\n}\n\nexport function getApiKeyByHash(hash: string): ApiKey | null {\n  const row = stmtGetApiKeyByHash.get(hash) as any;\n  if (!row) return null;\n  return {\n    id: row.id,\n    name: row.name,\n    keyHash: row.key_hash,\n    permissions: (() => { try { return row.permissions ? JSON.parse(row.permissions) : []; } catch { return []; } })(),\n    createdAt: row.created_at,\n    lastUsed: row.last_used,\n    expiresAt: row.expires_at,\n    active: row.active === 1\n  };\n}\n\nexport function updateApiKeyLastUsed(id: string): void {\n  stmtUpdateApiKeyLastUsed.run(new Date().toISOString(), id);\n}\n\nexport function listApiKeys(): Omit<ApiKey, \"keyHash\">[] {\n  return (stmtListApiKeys.all() as any[]).map(row => ({\n    id: row.id,\n    name: row.name,\n    permissions: (() => { try { return row.permissions ? JSON.parse(row.permissions) : []; } catch { return []; } })(),\n    createdAt: row.created_at,\n    lastUsed: row.last_used,\n    expiresAt: row.expires_at,\n    active: row.active === 1\n  }));\n}\n\nexport function deactivateApiKey(id: string): boolean {\n  const result = stmtDeactivateApiKey.run(id);\n  return result.changes > 0;\n}\n\nexport function createBackupRecord(backup: Backup): void {\n  stmtInsertBackup.run(\n    backup.id,\n    backup.filename,\n    backup.createdAt,\n    backup.sizeBytes,\n    backup.roomCount,\n    backup.autoGenerated ? 1 : 0\n  );\n}\n\nexport function listBackups(): Backup[] {\n  return (stmtListBackups.all() as any[]).map(row => ({\n    id: row.id,\n    filename: row.filename,\n    createdAt: row.created_at,\n    sizeBytes: row.size_bytes,\n    roomCount: row.room_count,\n    autoGenerated: row.auto_generated === 1\n  }));\n}\n\nexport function deleteBackupRecord(id: string): boolean {\n  const result = stmtDeleteBackup.run(id);\n  return result.changes > 0;\n}\n\nexport function getOldAutoBackups(keepCount: number): Backup[] {\n  return (stmtGetOldAutoBackups.all(keepCount) as any[]).map(row => ({\n    id: row.id,\n    filename: row.filename,\n    createdAt: row.created_at,\n    sizeBytes: row.size_bytes,\n    roomCount: row.room_count,\n    autoGenerated: row.auto_generated === 1\n  }));\n}\n\nexport function migrateFromFlatFiles(): { rooms: number; settings: boolean; admin: boolean } {\n  let roomsMigrated = 0;\n  let settingsMigrated = false;\n  let adminMigrated = false;\n\n  if (existsSync(ROOMS_DIR)) {\n    const files = readdirSync(ROOMS_DIR).filter(f => f.endsWith(\".json\"));\n    for (const file of files) {\n      try {\n        const roomPath = join(ROOMS_DIR, file);\n        const roomData = JSON.parse(readFileSync(roomPath, \"utf-8\"));\n        const existing = getRoom(roomData.id);\n        if (!existing) {\n          createRoom({\n            id: roomData.id,\n            created: roomData.created,\n            lastActivity: roomData.lastActivity,\n            creatorId: roomData.creatorId,\n            passwordHash: roomData.passwordHash,\n            destruct: roomData.destruct,\n            topology: roomData.topology\n          });\n          roomsMigrated++;\n        }\n        unlinkSync(roomPath);\n      } catch (e) {\n        console.error(`Failed to migrate room ${file}:`, e);\n      }\n    }\n  }\n\n  if (existsSync(SETTINGS_PATH)) {\n    try {\n      const settings = JSON.parse(readFileSync(SETTINGS_PATH, \"utf-8\"));\n      for (const [key, value] of Object.entries(settings)) {\n        setSetting(key, JSON.stringify(value));\n      }\n      unlinkSync(SETTINGS_PATH);\n      settingsMigrated = true;\n    } catch (e) {\n      console.error(\"Failed to migrate settings:\", e);\n    }\n  }\n\n  if (existsSync(ADMIN_CONFIG_PATH)) {\n    try {\n      const adminConfig = JSON.parse(readFileSync(ADMIN_CONFIG_PATH, \"utf-8\"));\n      setSetting(\"admin_password_hash\", adminConfig.passwordHash);\n      setSetting(\"admin_created_at\", adminConfig.createdAt);\n      unlinkSync(ADMIN_CONFIG_PATH);\n      adminMigrated = true;\n    } catch (e) {\n      console.error(\"Failed to migrate admin config:\", e);\n    }\n  }\n\n  return { rooms: roomsMigrated, settings: settingsMigrated, admin: adminMigrated };\n}\n\nexport function getDatabase(): Database {\n  return db;\n}\n\nexport function closeDatabase(): void {\n  db.close();\n}\n\nexport function healthCheck(): boolean {\n  try {\n    db.prepare(\"SELECT 1\").get();\n    return true;\n  } catch {\n    return false;\n  }\n}\n\nexport function logAuthEvent(\n  action: string,\n  userId: string | null,\n  ipAddress: string | null,\n  details?: Record<string, any>\n): void {\n  addAuditLog({\n    timestamp: new Date().toISOString(),\n    action: `auth:${action}`,\n    actor: userId,\n    actorIp: ipAddress,\n    targetType: 'user',\n    targetId: userId,\n    details: details\n  });\n}\n\nconst stmtInsertUser = db.prepare(`\n  INSERT INTO users (id, email, email_verified, display_name, avatar_url, password_hash, role, created_at, updated_at, last_login, is_active)\n  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n`);\n\nconst stmtGetUserById = db.prepare(`SELECT * FROM users WHERE id = ?`);\nconst stmtGetUserByEmail = db.prepare(`SELECT * FROM users WHERE email = ? COLLATE NOCASE`);\nconst stmtUpdateUser = db.prepare(`\n  UPDATE users SET email = ?, email_verified = ?, display_name = ?, avatar_url = ?, password_hash = ?, role = ?, updated_at = ?, last_login = ?, is_active = ?, failed_login_attempts = ?, locked_until = ?, totp_secret = ?, totp_enabled = ?, totp_backup_codes = ?, pending_email = ?, pending_email_token = ?\n  WHERE id = ?\n`);\nconst stmtIncrementFailedLogin = db.prepare(`UPDATE users SET failed_login_attempts = failed_login_attempts + 1, updated_at = ? WHERE id = ?`);\nconst stmtResetFailedLogin = db.prepare(`UPDATE users SET failed_login_attempts = 0, locked_until = NULL, updated_at = ? WHERE id = ?`);\nconst stmtLockUser = db.prepare(`UPDATE users SET locked_until = ?, updated_at = ? WHERE id = ?`);\nconst stmtDeleteUser = db.prepare(`DELETE FROM users WHERE id = ?`);\nconst stmtListUsers = db.prepare(`SELECT * FROM users ORDER BY created_at DESC LIMIT ? OFFSET ?`);\nconst stmtListUsersByRole = db.prepare(`SELECT * FROM users WHERE role = ? ORDER BY created_at DESC`);\nconst stmtSearchUsers = db.prepare(`SELECT * FROM users WHERE email LIKE ? ESCAPE '\\\\' OR display_name LIKE ? ESCAPE '\\\\' ORDER BY created_at DESC LIMIT ? OFFSET ?`);\nconst stmtCountUsers = db.prepare(`SELECT COUNT(*) as count FROM users`);\nconst stmtCountUsersByRole = db.prepare(`SELECT COUNT(*) as count FROM users WHERE role = ?`);\n\nfunction rowToUser(row: any): User | null {\n  if (!row) return null;\n  return {\n    id: row.id,\n    email: row.email,\n    emailVerified: row.email_verified === 1,\n    displayName: row.display_name,\n    avatarUrl: row.avatar_url,\n    passwordHash: row.password_hash,\n    role: row.role,\n    createdAt: row.created_at,\n    updatedAt: row.updated_at,\n    lastLogin: row.last_login,\n    isActive: row.is_active === 1,\n    failedLoginAttempts: row.failed_login_attempts || 0,\n    lockedUntil: row.locked_until,\n    totpSecret: row.totp_secret || null,\n    totpEnabled: row.totp_enabled === 1,\n    totpBackupCodes: row.totp_backup_codes || null,\n    pendingEmail: row.pending_email || null,\n    pendingEmailToken: row.pending_email_token || null\n  };\n}\n\nexport function createUser(user: User): void {\n  stmtInsertUser.run(\n    user.id, user.email, user.emailVerified ? 1 : 0, user.displayName, user.avatarUrl,\n    user.passwordHash, user.role, user.createdAt, user.updatedAt, user.lastLogin, user.isActive ? 1 : 0\n  );\n}\n\nexport function createUserAtomic(user: User): { created: boolean; wasFirst: boolean } {\n  db.run('BEGIN IMMEDIATE');\n  try {\n    const count = countUsers();\n    const wasFirst = count === 0;\n    if (wasFirst) {\n      user.role = 'admin';\n      user.emailVerified = true;\n    }\n    createUser(user);\n    db.run('COMMIT');\n    return { created: true, wasFirst };\n  } catch (e) {\n    db.run('ROLLBACK');\n    throw e;\n  }\n}\n\nexport function getUserById(id: string): User | null {\n  return rowToUser(stmtGetUserById.get(id));\n}\n\nexport function getUserByEmail(email: string): User | null {\n  return rowToUser(stmtGetUserByEmail.get(email));\n}\n\nexport function updateUser(user: User): void {\n  stmtUpdateUser.run(\n    user.email, user.emailVerified ? 1 : 0, user.displayName, user.avatarUrl, user.passwordHash,\n    user.role, user.updatedAt, user.lastLogin, user.isActive ? 1 : 0,\n    user.failedLoginAttempts, user.lockedUntil,\n    user.totpSecret, user.totpEnabled ? 1 : 0, user.totpBackupCodes,\n    user.pendingEmail, user.pendingEmailToken,\n    user.id\n  );\n}\n\nexport function incrementFailedLogin(userId: string): void {\n  stmtIncrementFailedLogin.run(new Date().toISOString(), userId);\n}\n\nexport function resetFailedLogin(userId: string): void {\n  stmtResetFailedLogin.run(new Date().toISOString(), userId);\n}\n\nexport function lockUserAccount(userId: string, until: Date): void {\n  stmtLockUser.run(until.toISOString(), new Date().toISOString(), userId);\n}\n\nexport function deleteUser(id: string): boolean {\n  return stmtDeleteUser.run(id).changes > 0;\n}\n\nexport function listUsers(limit: number = 100, offset: number = 0): User[] {\n  return (stmtListUsers.all(limit, offset) as any[]).map(rowToUser).filter(u => u !== null) as User[];\n}\n\nexport function searchUsers(query: string, limit: number = 100, offset: number = 0): User[] {\n  const sanitized = sanitizeSearchQuery(query);\n  if (!sanitized) return [];\n  const pattern = `%${sanitized}%`;\n  return (stmtSearchUsers.all(pattern, pattern, limit, offset) as any[]).map(rowToUser).filter(u => u !== null) as User[];\n}\n\nexport function countUsers(): number {\n  return (stmtCountUsers.get() as { count: number }).count;\n}\n\nexport function countUsersByRole(role: string): number {\n  return (stmtCountUsersByRole.get(role) as { count: number }).count;\n}\n\nexport function listUsersByRole(role: string): User[] {\n  return (stmtListUsersByRole.all(role) as any[]).map(rowToUser).filter(u => u !== null) as User[];\n}\n\nconst stmtVerifyAdminEmails = db.prepare(`UPDATE users SET email_verified = 1, updated_at = ? WHERE role = 'admin' AND email_verified = 0`);\n\nexport function verifyAllAdminEmails(): number {\n  return stmtVerifyAdminEmails.run(new Date().toISOString()).changes;\n}\n\n\nconst stmtInsertSession = db.prepare(`\n  INSERT INTO user_sessions (id, user_id, token_hash, ip_address, user_agent, expires_at, created_at)\n  VALUES (?, ?, ?, ?, ?, ?, ?)\n`);\nconst stmtGetSessionByToken = db.prepare(`SELECT * FROM user_sessions WHERE token_hash = ?`);\nconst stmtGetSessionsByUser = db.prepare(`SELECT * FROM user_sessions WHERE user_id = ? ORDER BY created_at DESC`);\nconst stmtDeleteSession = db.prepare(`DELETE FROM user_sessions WHERE id = ?`);\nconst stmtDeleteSessionByToken = db.prepare(`DELETE FROM user_sessions WHERE token_hash = ?`);\nconst stmtDeleteSessionsByUser = db.prepare(`DELETE FROM user_sessions WHERE user_id = ?`);\nconst stmtDeleteExpiredSessions = db.prepare(`DELETE FROM user_sessions WHERE expires_at < ?`);\n\nfunction rowToSession(row: any): UserSession | null {\n  if (!row) return null;\n  return {\n    id: row.id,\n    userId: row.user_id,\n    tokenHash: row.token_hash,\n    ipAddress: row.ip_address,\n    userAgent: row.user_agent,\n    expiresAt: row.expires_at,\n    createdAt: row.created_at\n  };\n}\n\nexport function createUserSession(session: UserSession): void {\n  stmtInsertSession.run(session.id, session.userId, session.tokenHash, session.ipAddress, session.userAgent, session.expiresAt, session.createdAt);\n}\n\nexport function getSessionByTokenHash(tokenHash: string): UserSession | null {\n  return rowToSession(stmtGetSessionByToken.get(tokenHash));\n}\n\nexport function getSessionsByUserId(userId: string): UserSession[] {\n  return (stmtGetSessionsByUser.all(userId) as any[]).map(rowToSession).filter(s => s !== null) as UserSession[];\n}\n\nexport function deleteSession(id: string): boolean {\n  return stmtDeleteSession.run(id).changes > 0;\n}\n\nexport function deleteSessionByTokenHash(tokenHash: string): boolean {\n  return stmtDeleteSessionByToken.run(tokenHash).changes > 0;\n}\n\nexport function deleteAllUserSessions(userId: string): number {\n  return stmtDeleteSessionsByUser.run(userId).changes;\n}\n\nexport function cleanupExpiredSessions(): number {\n  return stmtDeleteExpiredSessions.run(new Date().toISOString()).changes;\n}\n\nexport const getUserSessionByTokenHash = getSessionByTokenHash;\nexport const deleteUserSession = deleteSession;\n\n\nconst stmtInsertUserToken = db.prepare(`\n  INSERT INTO user_tokens (id, user_id, type, token_hash, expires_at, used_at, created_at)\n  VALUES (?, ?, ?, ?, ?, ?, ?)\n`);\nconst stmtGetUserTokenByHash = db.prepare(`SELECT * FROM user_tokens WHERE token_hash = ?`);\nconst stmtMarkUserTokenUsed = db.prepare(`UPDATE user_tokens SET used_at = ? WHERE id = ?`);\nconst stmtDeleteUserToken = db.prepare(`DELETE FROM user_tokens WHERE id = ?`);\nconst stmtDeleteExpiredUserTokens = db.prepare(`DELETE FROM user_tokens WHERE expires_at < ?`);\nconst stmtDeleteUserTokensByUser = db.prepare(`DELETE FROM user_tokens WHERE user_id = ? AND type = ?`);\nconst stmtIncrementTokenFailedAttempts = db.prepare(`UPDATE user_tokens SET failed_attempts = failed_attempts + 1 WHERE id = ?`);\nconst stmtGetTokenFailedAttempts = db.prepare(`SELECT failed_attempts FROM user_tokens WHERE id = ?`);\n\nfunction rowToUserToken(row: any): UserToken | null {\n  if (!row) return null;\n  return {\n    id: row.id,\n    userId: row.user_id,\n    type: row.type,\n    tokenHash: row.token_hash,\n    expiresAt: row.expires_at,\n    usedAt: row.used_at,\n    createdAt: row.created_at,\n    failedAttempts: row.failed_attempts || 0\n  };\n}\n\nexport function createUserToken(token: UserToken): void {\n  stmtInsertUserToken.run(token.id, token.userId, token.type, token.tokenHash, token.expiresAt, token.usedAt, token.createdAt);\n}\n\nexport function getUserTokenByHash(tokenHash: string): UserToken | null {\n  return rowToUserToken(stmtGetUserTokenByHash.get(tokenHash));\n}\n\nexport function markUserTokenUsed(id: string): void {\n  stmtMarkUserTokenUsed.run(new Date().toISOString(), id);\n}\n\nexport function incrementTokenFailedAttempts(id: string): number {\n  stmtIncrementTokenFailedAttempts.run(id);\n  const row = stmtGetTokenFailedAttempts.get(id) as any;\n  return row ? row.failed_attempts : 0;\n}\n\nexport function deleteUserToken(id: string): boolean {\n  return stmtDeleteUserToken.run(id).changes > 0;\n}\n\nexport function deleteUserTokensByType(userId: string, type: string): number {\n  return stmtDeleteUserTokensByUser.run(userId, type).changes;\n}\n\nexport function cleanupExpiredUserTokens(): number {\n  return stmtDeleteExpiredUserTokens.run(new Date().toISOString()).changes;\n}\n\n\nconst stmtInsertOidcLink = db.prepare(`\n  INSERT INTO user_oidc_links (id, user_id, provider, provider_user_id, provider_email, access_token_encrypted, refresh_token_encrypted, token_expires_at, created_at)\n  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)\n`);\nconst stmtGetOidcLinkById = db.prepare(`SELECT * FROM user_oidc_links WHERE id = ?`);\nconst stmtGetOidcLinkByProvider = db.prepare(`SELECT * FROM user_oidc_links WHERE provider = ? AND provider_user_id = ?`);\nconst stmtGetOidcLinksByUser = db.prepare(`SELECT * FROM user_oidc_links WHERE user_id = ?`);\nconst stmtDeleteOidcLink = db.prepare(`DELETE FROM user_oidc_links WHERE id = ?`);\nconst stmtUpdateOidcLinkTokens = db.prepare(`UPDATE user_oidc_links SET access_token_encrypted = ?, refresh_token_encrypted = ?, token_expires_at = ? WHERE id = ?`);\n\nfunction rowToOidcLink(row: any): UserOidcLink | null {\n  if (!row) return null;\n  return {\n    id: row.id,\n    userId: row.user_id,\n    provider: row.provider,\n    providerUserId: row.provider_user_id,\n    providerEmail: row.provider_email,\n    accessTokenEncrypted: row.access_token_encrypted,\n    refreshTokenEncrypted: row.refresh_token_encrypted,\n    tokenExpiresAt: row.token_expires_at,\n    createdAt: row.created_at\n  };\n}\n\nexport function createOidcLink(link: UserOidcLink): void {\n  stmtInsertOidcLink.run(link.id, link.userId, link.provider, link.providerUserId, link.providerEmail, link.accessTokenEncrypted, link.refreshTokenEncrypted, link.tokenExpiresAt, link.createdAt);\n}\n\nexport function getOidcLinkById(id: string): UserOidcLink | null {\n  return rowToOidcLink(stmtGetOidcLinkById.get(id));\n}\n\nexport function getOidcLinkByProvider(provider: string, providerUserId: string): UserOidcLink | null {\n  return rowToOidcLink(stmtGetOidcLinkByProvider.get(provider, providerUserId));\n}\n\nexport function getOidcLinksByUser(userId: string): UserOidcLink[] {\n  return (stmtGetOidcLinksByUser.all(userId) as any[]).map(rowToOidcLink).filter(l => l !== null) as UserOidcLink[];\n}\n\nexport function deleteOidcLink(id: string): boolean {\n  return stmtDeleteOidcLink.run(id).changes > 0;\n}\n\nexport function updateOidcLinkTokens(id: string, accessToken: string | null, refreshToken: string | null, expiresAt: string | null): void {\n  stmtUpdateOidcLinkTokens.run(accessToken, refreshToken, expiresAt, id);\n}\n\n\nconst stmtInsertOidcProvider = db.prepare(`\n  INSERT INTO oidc_providers (id, name, provider_type, client_id, client_secret_encrypted, issuer_url, authorization_url, token_url, userinfo_url, jwks_uri, scopes, is_active, display_order, icon_url, created_at, updated_at)\n  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n`);\nconst stmtGetOidcProvider = db.prepare(`SELECT * FROM oidc_providers WHERE id = ?`);\nconst stmtGetOidcProviderByName = db.prepare(`SELECT * FROM oidc_providers WHERE name = ?`);\nconst stmtListOidcProviders = db.prepare(`SELECT * FROM oidc_providers ORDER BY display_order ASC, name ASC`);\nconst stmtListActiveOidcProviders = db.prepare(`SELECT * FROM oidc_providers WHERE is_active = 1 ORDER BY display_order ASC, name ASC`);\nconst stmtUpdateOidcProvider = db.prepare(`\n  UPDATE oidc_providers SET name = ?, provider_type = ?, client_id = ?, client_secret_encrypted = ?, issuer_url = ?, authorization_url = ?, token_url = ?, userinfo_url = ?, jwks_uri = ?, scopes = ?, is_active = ?, display_order = ?, icon_url = ?, updated_at = ?\n  WHERE id = ?\n`);\nconst stmtDeleteOidcProvider = db.prepare(`DELETE FROM oidc_providers WHERE id = ?`);\n\nfunction rowToOidcProvider(row: any): OidcProvider | null {\n  if (!row) return null;\n  return {\n    id: row.id,\n    name: row.name,\n    providerType: row.provider_type,\n    clientId: row.client_id,\n    clientSecretEncrypted: row.client_secret_encrypted,\n    issuerUrl: row.issuer_url,\n    authorizationUrl: row.authorization_url,\n    tokenUrl: row.token_url,\n    userinfoUrl: row.userinfo_url,\n    jwksUri: row.jwks_uri,\n    scopes: row.scopes,\n    isActive: row.is_active === 1,\n    displayOrder: row.display_order,\n    iconUrl: row.icon_url,\n    createdAt: row.created_at,\n    updatedAt: row.updated_at\n  };\n}\n\nexport function createOidcProvider(provider: OidcProvider): void {\n  stmtInsertOidcProvider.run(\n    provider.id, provider.name, provider.providerType, provider.clientId, provider.clientSecretEncrypted,\n    provider.issuerUrl, provider.authorizationUrl, provider.tokenUrl, provider.userinfoUrl, provider.jwksUri,\n    provider.scopes, provider.isActive ? 1 : 0, provider.displayOrder, provider.iconUrl, provider.createdAt, provider.updatedAt\n  );\n}\n\nexport function getOidcProvider(id: string): OidcProvider | null {\n  return rowToOidcProvider(stmtGetOidcProvider.get(id));\n}\n\nexport function getOidcProviderByName(name: string): OidcProvider | null {\n  return rowToOidcProvider(stmtGetOidcProviderByName.get(name));\n}\n\nexport function listOidcProviders(): OidcProvider[] {\n  return (stmtListOidcProviders.all() as any[]).map(rowToOidcProvider).filter(p => p !== null) as OidcProvider[];\n}\n\nexport function listActiveOidcProviders(): OidcProvider[] {\n  return (stmtListActiveOidcProviders.all() as any[]).map(rowToOidcProvider).filter(p => p !== null) as OidcProvider[];\n}\n\nexport function updateOidcProvider(provider: OidcProvider): void {\n  stmtUpdateOidcProvider.run(\n    provider.name, provider.providerType, provider.clientId, provider.clientSecretEncrypted,\n    provider.issuerUrl, provider.authorizationUrl, provider.tokenUrl, provider.userinfoUrl, provider.jwksUri,\n    provider.scopes, provider.isActive ? 1 : 0, provider.displayOrder, provider.iconUrl, provider.updatedAt, provider.id\n  );\n}\n\nexport function deleteOidcProvider(id: string): boolean {\n  return stmtDeleteOidcProvider.run(id).changes > 0;\n}\n\n\nconst stmtInsertSmtpConfig = db.prepare(`\n  INSERT INTO smtp_configs (id, name, provider_type, host, port, secure_mode, username, password_encrypted, api_key_encrypted, from_email, from_name, is_default, is_active, allow_insecure_tls, created_at, updated_at)\n  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n`);\nconst stmtGetSmtpConfig = db.prepare(`SELECT * FROM smtp_configs WHERE id = ?`);\nconst stmtGetDefaultSmtpConfig = db.prepare(`SELECT * FROM smtp_configs WHERE is_default = 1 AND is_active = 1 LIMIT 1`);\nconst stmtListSmtpConfigs = db.prepare(`SELECT * FROM smtp_configs ORDER BY is_default DESC, name ASC`);\nconst stmtUpdateSmtpConfig = db.prepare(`\n  UPDATE smtp_configs SET name = ?, provider_type = ?, host = ?, port = ?, secure_mode = ?, username = ?, password_encrypted = ?, api_key_encrypted = ?, from_email = ?, from_name = ?, is_default = ?, is_active = ?, allow_insecure_tls = ?, updated_at = ?\n  WHERE id = ?\n`);\nconst stmtDeleteSmtpConfig = db.prepare(`DELETE FROM smtp_configs WHERE id = ?`);\nconst stmtClearDefaultSmtp = db.prepare(`UPDATE smtp_configs SET is_default = 0`);\n\nfunction rowToSmtpConfig(row: any): SmtpConfig | null {\n  if (!row) return null;\n  return {\n    id: row.id,\n    name: row.name,\n    providerType: row.provider_type,\n    host: row.host,\n    port: row.port,\n    secureMode: row.secure_mode,\n    username: row.username,\n    passwordEncrypted: row.password_encrypted,\n    apiKeyEncrypted: row.api_key_encrypted,\n    fromEmail: row.from_email,\n    fromName: row.from_name,\n    isDefault: row.is_default === 1,\n    isActive: row.is_active === 1,\n    allowInsecureTls: row.allow_insecure_tls === 1,\n    createdAt: row.created_at,\n    updatedAt: row.updated_at\n  };\n}\n\nexport function createSmtpConfig(config: SmtpConfig): void {\n  if (config.isDefault) stmtClearDefaultSmtp.run();\n  stmtInsertSmtpConfig.run(\n    config.id, config.name, config.providerType, config.host, config.port, config.secureMode,\n    config.username, config.passwordEncrypted, config.apiKeyEncrypted, config.fromEmail, config.fromName,\n    config.isDefault ? 1 : 0, config.isActive ? 1 : 0, config.allowInsecureTls ? 1 : 0, config.createdAt, config.updatedAt\n  );\n}\n\nexport function getSmtpConfig(id: string): SmtpConfig | null {\n  return rowToSmtpConfig(stmtGetSmtpConfig.get(id));\n}\n\nexport function getDefaultSmtpConfig(): SmtpConfig | null {\n  return rowToSmtpConfig(stmtGetDefaultSmtpConfig.get());\n}\n\nexport function listSmtpConfigs(): SmtpConfig[] {\n  return (stmtListSmtpConfigs.all() as any[]).map(rowToSmtpConfig).filter(c => c !== null) as SmtpConfig[];\n}\n\nexport function updateSmtpConfig(config: SmtpConfig): void {\n  if (config.isDefault) stmtClearDefaultSmtp.run();\n  stmtUpdateSmtpConfig.run(\n    config.name, config.providerType, config.host, config.port, config.secureMode,\n    config.username, config.passwordEncrypted, config.apiKeyEncrypted, config.fromEmail, config.fromName,\n    config.isDefault ? 1 : 0, config.isActive ? 1 : 0, config.allowInsecureTls ? 1 : 0, config.updatedAt, config.id\n  );\n}\n\nexport function deleteSmtpConfig(id: string): boolean {\n  return stmtDeleteSmtpConfig.run(id).changes > 0;\n}\n\n\nconst stmtInsertEmailTemplate = db.prepare(`\n  INSERT INTO email_templates (id, name, subject, body_html, body_text, created_at, updated_at)\n  VALUES (?, ?, ?, ?, ?, ?, ?)\n`);\nconst stmtGetEmailTemplate = db.prepare(`SELECT * FROM email_templates WHERE id = ?`);\nconst stmtGetEmailTemplateByName = db.prepare(`SELECT * FROM email_templates WHERE name = ?`);\nconst stmtListEmailTemplates = db.prepare(`SELECT * FROM email_templates ORDER BY name ASC`);\nconst stmtUpdateEmailTemplate = db.prepare(`UPDATE email_templates SET name = ?, subject = ?, body_html = ?, body_text = ?, updated_at = ? WHERE id = ?`);\nconst stmtDeleteEmailTemplate = db.prepare(`DELETE FROM email_templates WHERE id = ?`);\n\nfunction rowToEmailTemplate(row: any): EmailTemplate | null {\n  if (!row) return null;\n  return {\n    id: row.id,\n    name: row.name,\n    subject: row.subject,\n    bodyHtml: row.body_html,\n    bodyText: row.body_text,\n    createdAt: row.created_at,\n    updatedAt: row.updated_at\n  };\n}\n\nexport function createEmailTemplate(template: EmailTemplate): void {\n  stmtInsertEmailTemplate.run(template.id, template.name, template.subject, template.bodyHtml, template.bodyText, template.createdAt, template.updatedAt);\n}\n\nexport function getEmailTemplate(id: string): EmailTemplate | null {\n  return rowToEmailTemplate(stmtGetEmailTemplate.get(id));\n}\n\nexport function getEmailTemplateByName(name: string): EmailTemplate | null {\n  return rowToEmailTemplate(stmtGetEmailTemplateByName.get(name));\n}\n\nexport function listEmailTemplates(): EmailTemplate[] {\n  return (stmtListEmailTemplates.all() as any[]).map(rowToEmailTemplate).filter(t => t !== null) as EmailTemplate[];\n}\n\nexport function updateEmailTemplate(template: EmailTemplate): void {\n  stmtUpdateEmailTemplate.run(template.name, template.subject, template.bodyHtml, template.bodyText, template.updatedAt, template.id);\n}\n\nexport function deleteEmailTemplate(id: string): boolean {\n  return stmtDeleteEmailTemplate.run(id).changes > 0;\n}\n\n\nconst stmtInsertEmailLog = db.prepare(`\n  INSERT INTO email_logs (id, to_email, template_name, subject, status, error_message, smtp_config_id, sent_at)\n  VALUES (?, ?, ?, ?, ?, ?, ?, ?)\n`);\nconst stmtListEmailLogs = db.prepare(`SELECT * FROM email_logs ORDER BY sent_at DESC LIMIT ? OFFSET ?`);\nconst stmtCountEmailLogs = db.prepare(`SELECT COUNT(*) as count FROM email_logs`);\nconst stmtCountEmailLogsByStatus = db.prepare(`SELECT COUNT(*) as count FROM email_logs WHERE status = ?`);\n\nfunction rowToEmailLog(row: any): EmailLog | null {\n  if (!row) return null;\n  return {\n    id: row.id,\n    toEmail: row.to_email,\n    templateName: row.template_name,\n    subject: row.subject,\n    status: row.status,\n    errorMessage: row.error_message,\n    smtpConfigId: row.smtp_config_id,\n    sentAt: row.sent_at\n  };\n}\n\nexport function createEmailLog(log: EmailLog): void {\n  stmtInsertEmailLog.run(log.id, log.toEmail, log.templateName, log.subject, log.status, log.errorMessage, log.smtpConfigId, log.sentAt);\n}\n\nexport function listEmailLogs(limit: number = 100, offset: number = 0): EmailLog[] {\n  return (stmtListEmailLogs.all(limit, offset) as any[]).map(rowToEmailLog).filter(l => l !== null) as EmailLog[];\n}\n\nexport function countEmailLogs(): number {\n  return (stmtCountEmailLogs.get() as { count: number }).count;\n}\n\nexport function countEmailLogsByStatus(status: string): number {\n  return (stmtCountEmailLogsByStatus.get(status) as { count: number }).count;\n}\n\nexport function clearEmailLogs(): number {\n  return db.exec(`DELETE FROM email_logs`).changes;\n}\n\nexport function clearAuditLogs(): number {\n  return db.exec(`DELETE FROM audit_logs`).changes;\n}\n\nexport function clearActivityLogs(): number {\n  return db.exec(`DELETE FROM activity_logs`).changes;\n}\n\n\nconst stmtUpsertUserPreferences = db.prepare(`\n  INSERT INTO user_preferences (user_id, theme, email_notifications, default_room_settings)\n  VALUES (?, ?, ?, ?)\n  ON CONFLICT(user_id) DO UPDATE SET theme = excluded.theme, email_notifications = excluded.email_notifications, default_room_settings = excluded.default_room_settings\n`);\nconst stmtGetUserPreferences = db.prepare(`SELECT * FROM user_preferences WHERE user_id = ?`);\n\nfunction rowToUserPreferences(row: any): UserPreferences | null {\n  if (!row) return null;\n  return {\n    userId: row.user_id,\n    theme: row.theme,\n    emailNotifications: row.email_notifications === 1,\n    defaultRoomSettings: row.default_room_settings ? JSON.parse(row.default_room_settings) : null\n  };\n}\n\nexport function saveUserPreferences(prefs: UserPreferences): void {\n  stmtUpsertUserPreferences.run(prefs.userId, prefs.theme, prefs.emailNotifications ? 1 : 0, prefs.defaultRoomSettings ? JSON.stringify(prefs.defaultRoomSettings) : null);\n}\n\nexport function getUserPreferences(userId: string): UserPreferences | null {\n  return rowToUserPreferences(stmtGetUserPreferences.get(userId));\n}\n\n\nexport function initializeDefaultEmailTemplates(): void {\n  const now = new Date().toISOString();\n  const defaults = [\n    {\n      id: 'emailVerify',\n      name: 'Email Verification',\n      subject: 'Verify your email | TheOneFile_Verse',\n      bodyHtml: `<!DOCTYPE html><html><body style=\"font-family:system-ui,sans-serif;max-width:600px;margin:0 auto;padding:20px;\">\n        <h1 style=\"color:#c9a227;\">Verify Your Email</h1>\n        <p>Hello {{displayName}},</p>\n        <p>Please click the button below to verify your email address:</p>\n        <p style=\"text-align:center;margin:30px 0;\">\n          <a href=\"{{verifyUrl}}\" style=\"background:#c9a227;color:#fff;padding:12px 24px;border-radius:6px;text-decoration:none;font-weight:bold;\">Verify Email</a>\n        </p>\n        <p>Or copy and paste this link: <a href=\"{{verifyUrl}}\">{{verifyUrl}}</a></p>\n        <p>This link expires in 24 hours.</p>\n        <p style=\"color:#666;font-size:12px;margin-top:40px;\">If you didn't create an account, you can ignore this email.</p>\n      </body></html>`,\n      bodyText: `Verify Your Email\\n\\nHello {{displayName}},\\n\\nPlease visit this link to verify your email:\\n{{verifyUrl}}\\n\\nThis link expires in 24 hours.\\n\\nIf you didn't create an account, you can ignore this email.`\n    },\n    {\n      id: 'passwordReset',\n      name: 'Password Reset',\n      subject: 'Reset your password | TheOneFile_Verse',\n      bodyHtml: `<!DOCTYPE html><html><body style=\"font-family:system-ui,sans-serif;max-width:600px;margin:0 auto;padding:20px;\">\n        <h1 style=\"color:#c9a227;\">Reset Your Password</h1>\n        <p>Hello {{displayName}},</p>\n        <p>We received a request to reset your password. Click the button below to create a new password:</p>\n        <p style=\"text-align:center;margin:30px 0;\">\n          <a href=\"{{resetUrl}}\" style=\"background:#c9a227;color:#fff;padding:12px 24px;border-radius:6px;text-decoration:none;font-weight:bold;\">Reset Password</a>\n        </p>\n        <p>Or copy and paste this link: <a href=\"{{resetUrl}}\">{{resetUrl}}</a></p>\n        <p>This link expires in 1 hour.</p>\n        <p style=\"color:#666;font-size:12px;margin-top:40px;\">If you didn't request a password reset, you can ignore this email.</p>\n      </body></html>`,\n      bodyText: `Reset Your Password\\n\\nHello {{displayName}},\\n\\nWe received a request to reset your password.\\n\\nVisit this link to create a new password:\\n{{resetUrl}}\\n\\nThis link expires in 1 hour.\\n\\nIf you didn't request a password reset, you can ignore this email.`\n    },\n    {\n      id: 'magicLink',\n      name: 'Magic Link',\n      subject: 'Your login link | TheOneFile_Verse',\n      bodyHtml: `<!DOCTYPE html><html><body style=\"font-family:system-ui,sans-serif;max-width:600px;margin:0 auto;padding:20px;\">\n        <h1 style=\"color:#c9a227;\">Your Login Link</h1>\n        <p>Hello {{displayName}},</p>\n        <p>Click the button below to sign in to TheOneFile_Verse:</p>\n        <p style=\"text-align:center;margin:30px 0;\">\n          <a href=\"{{loginUrl}}\" style=\"background:#c9a227;color:#fff;padding:12px 24px;border-radius:6px;text-decoration:none;font-weight:bold;\">Sign In</a>\n        </p>\n        <p>Or copy and paste this link: <a href=\"{{loginUrl}}\">{{loginUrl}}</a></p>\n        <p>This link expires in 15 minutes and can only be used once.</p>\n      </body></html>`,\n      bodyText: `Your Login Link\\n\\nHello {{displayName}},\\n\\nVisit this link to sign in:\\n{{loginUrl}}\\n\\nThis link expires in 15 minutes and can only be used once.`\n    },\n    {\n      id: 'roomInvite',\n      name: 'Room Invitation',\n      subject: 'You\\'ve been invited to collaborate | TheOneFile_Verse',\n      bodyHtml: `<!DOCTYPE html><html><body style=\"font-family:system-ui,sans-serif;max-width:600px;margin:0 auto;padding:20px;\">\n        <h1 style=\"color:#c9a227;\">You're Invited!</h1>\n        <p>Hello,</p>\n        <p>{{inviterName}} has invited you to collaborate on a room in TheOneFile_Verse.</p>\n        <p style=\"text-align:center;margin:30px 0;\">\n          <a href=\"{{roomUrl}}\" style=\"background:#c9a227;color:#fff;padding:12px 24px;border-radius:6px;text-decoration:none;font-weight:bold;\">Join Room</a>\n        </p>\n        <p>Or copy and paste this link: <a href=\"{{roomUrl}}\">{{roomUrl}}</a></p>\n      </body></html>`,\n      bodyText: `You're Invited!\\n\\nHello,\\n\\n{{inviterName}} has invited you to collaborate on a room in TheOneFile_Verse.\\n\\nJoin here: {{roomUrl}}`\n    }\n  ];\n\n  const legacyNames = ['email_verification', 'password_reset', 'magic_link', 'room_invitation'];\n  for (const legacyName of legacyNames) {\n    const legacy = getEmailTemplateByName(legacyName);\n    if (legacy) {\n      deleteEmailTemplate(legacy.id);\n    }\n  }\n\n  for (const template of defaults) {\n    const existing = getEmailTemplateByName(template.name);\n    if (!existing) {\n      createEmailTemplate({ ...template, createdAt: now, updatedAt: now });\n    }\n  }\n}\n\n\nconst stmtInsertOidcState = db.prepare(`\n  INSERT INTO oidc_states (state, provider_id, code_verifier, nonce, redirect_uri, link_user_id, post_login_redirect, created_at, expires_at)\n  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)\n`);\nconst stmtGetOidcState = db.prepare(`SELECT * FROM oidc_states WHERE state = ?`);\nconst stmtDeleteOidcState = db.prepare(`DELETE FROM oidc_states WHERE state = ?`);\nconst stmtDeleteExpiredOidcStates = db.prepare(`DELETE FROM oidc_states WHERE expires_at < ?`);\nconst stmtCountOidcStates = db.prepare(`SELECT COUNT(*) as count FROM oidc_states`);\nconst stmtDeleteOldestOidcStates = db.prepare(`DELETE FROM oidc_states WHERE state IN (SELECT state FROM oidc_states ORDER BY created_at ASC LIMIT ?)`);\n\nfunction rowToOidcState(row: any): OidcState | null {\n  if (!row) return null;\n  return {\n    state: row.state,\n    providerId: row.provider_id,\n    codeVerifier: row.code_verifier,\n    nonce: row.nonce,\n    redirectUri: row.redirect_uri,\n    linkUserId: row.link_user_id,\n    postLoginRedirect: row.post_login_redirect || null,\n    createdAt: row.created_at,\n    expiresAt: row.expires_at\n  };\n}\n\nexport function createOidcState(oidcState: OidcState): void {\n  stmtInsertOidcState.run(\n    oidcState.state,\n    oidcState.providerId,\n    oidcState.codeVerifier,\n    oidcState.nonce,\n    oidcState.redirectUri,\n    oidcState.linkUserId,\n    oidcState.postLoginRedirect,\n    oidcState.createdAt,\n    oidcState.expiresAt\n  );\n}\n\nexport function getOidcState(state: string): OidcState | null {\n  return rowToOidcState(stmtGetOidcState.get(state));\n}\n\nexport function deleteOidcState(state: string): boolean {\n  return stmtDeleteOidcState.run(state).changes > 0;\n}\n\nexport function cleanupExpiredOidcStates(): number {\n  return stmtDeleteExpiredOidcStates.run(new Date().toISOString()).changes;\n}\n\nexport function countOidcStates(): number {\n  return (stmtCountOidcStates.get() as { count: number }).count;\n}\n\nexport function deleteOldestOidcStates(count: number): number {\n  return stmtDeleteOldestOidcStates.run(count).changes;\n}\n\n\nconst stmtInsertCsrfToken = db.prepare(`\n  INSERT INTO csrf_tokens (token, used, created_at, expires_at)\n  VALUES (?, ?, ?, ?)\n`);\nconst stmtGetCsrfToken = db.prepare(`SELECT * FROM csrf_tokens WHERE token = ?`);\nconst stmtMarkCsrfTokenUsed = db.prepare(`UPDATE csrf_tokens SET used = 1 WHERE token = ?`);\nconst stmtDeleteCsrfToken = db.prepare(`DELETE FROM csrf_tokens WHERE token = ?`);\nconst stmtDeleteExpiredCsrfTokens = db.prepare(`DELETE FROM csrf_tokens WHERE expires_at < ?`);\nconst stmtCountCsrfTokens = db.prepare(`SELECT COUNT(*) as count FROM csrf_tokens`);\nconst stmtDeleteOldestCsrfTokens = db.prepare(`DELETE FROM csrf_tokens WHERE token IN (SELECT token FROM csrf_tokens ORDER BY created_at ASC LIMIT ?)`);\n\nfunction rowToCsrfToken(row: any): CsrfToken | null {\n  if (!row) return null;\n  return {\n    token: row.token,\n    used: row.used === 1,\n    createdAt: row.created_at,\n    expiresAt: row.expires_at\n  };\n}\n\nexport function createCsrfToken(csrfToken: CsrfToken): void {\n  stmtInsertCsrfToken.run(\n    csrfToken.token,\n    csrfToken.used ? 1 : 0,\n    csrfToken.createdAt,\n    csrfToken.expiresAt\n  );\n}\n\nexport function getCsrfToken(token: string): CsrfToken | null {\n  return rowToCsrfToken(stmtGetCsrfToken.get(token));\n}\n\nexport function markCsrfTokenUsed(token: string): boolean {\n  return stmtMarkCsrfTokenUsed.run(token).changes > 0;\n}\n\nexport function deleteCsrfToken(token: string): boolean {\n  return stmtDeleteCsrfToken.run(token).changes > 0;\n}\n\nexport function cleanupExpiredCsrfTokens(): number {\n  return stmtDeleteExpiredCsrfTokens.run(new Date().toISOString()).changes;\n}\n\nexport function countCsrfTokens(): number {\n  return (stmtCountCsrfTokens.get() as { count: number }).count;\n}\n\nexport function deleteOldestCsrfTokens(count: number): number {\n  return stmtDeleteOldestCsrfTokens.run(count).changes;\n}\n\n\nconst stmtInsertEmailRateLimit = db.prepare(`INSERT INTO email_rate_limits (email, action, created_at) VALUES (?, ?, ?)`);\nconst stmtCountEmailRateLimits = db.prepare(`SELECT COUNT(*) as count FROM email_rate_limits WHERE email = ? AND action = ? AND created_at > ?`);\nconst stmtCleanupEmailRateLimits = db.prepare(`DELETE FROM email_rate_limits WHERE created_at < ?`);\n\nexport function recordEmailRateLimit(email: string, action: string): void {\n  stmtInsertEmailRateLimit.run(email.toLowerCase().trim(), action, new Date().toISOString());\n}\n\nexport function countEmailRateLimitAttempts(email: string, action: string, windowSeconds: number): number {\n  const cutoff = new Date(Date.now() - windowSeconds * 1000).toISOString();\n  return (stmtCountEmailRateLimits.get(email.toLowerCase().trim(), action, cutoff) as { count: number }).count;\n}\n\nexport function cleanupEmailRateLimits(maxAgeSeconds: number = 3600): number {\n  const cutoff = new Date(Date.now() - maxAgeSeconds * 1000).toISOString();\n  return stmtCleanupEmailRateLimits.run(cutoff).changes;\n}\n"
  },
  {
    "path": "theonefile_verse/src/index.ts",
    "content": "import * as db from \"./database\";\nimport * as redis from \"./redis\";\nimport * as oidc from \"./oidc\";\nimport { APP_VERSION, PORT, ENV_ADMIN_PASSWORD, getSettings, isAdminConfigured, isInstanceLocked, isValidUUID } from \"./config\";\nimport { securityHeaders, getClientIP, getTokenFromRequest, relativeRedirect } from \"./security\";\nimport { startTokenCleanupIntervals, validateInstanceToken } from \"./tokens\";\nimport { startRateLimitCleanupIntervals, checkWsConnectionLimit } from \"./rate-limit\";\nimport { loadRoom, restartBackupTimer, clearBackupTimer, clearUpdateTimer, deleteRoomData, scheduleDestruction, initializeTheOneFile } from \"./rooms\";\nimport { websocketHandlers, type WsData } from \"./websocket\";\n\nconst CONFIGURED_ORIGINS = process.env.CORS_ORIGIN?.split(\",\").map(o => o.trim()).filter(Boolean) || null;\n\nimport * as setupRoutes from \"./routes/setup\";\nimport * as adminAuthRoutes from \"./routes/admin-auth\";\nimport * as adminRoomsRoutes from \"./routes/admin-rooms\";\nimport * as userAuthRoutes from \"./routes/user-auth\";\nimport * as adminUsersRoutes from \"./routes/admin-users\";\nimport * as adminAuthSettingsRoutes from \"./routes/admin-auth-settings\";\nimport * as instanceAccessRoutes from \"./routes/instance-access\";\nimport * as adminSettingsRoutes from \"./routes/admin-settings\";\nimport * as adminLogsRoutes from \"./routes/admin-logs\";\nimport * as adminBackupsRoutes from \"./routes/admin-backups\";\nimport * as adminApikeysRoutes from \"./routes/admin-apikeys\";\nimport * as roomRoutes from \"./routes/room\";\nimport * as networkRoutes from \"./routes/network-routes\";\nimport * as publicRoutes from \"./routes/public\";\n\nasync function handleRequest(req: Request, server: any): Promise<Response | undefined> {\n  const url = new URL(req.url);\n  const path = url.pathname;\n\n  if (req.method === 'POST' || req.method === 'PUT') {\n    if (path !== '/api/admin/upload-html') {\n      const cl = parseInt(req.headers.get('content-length') || '0');\n      if (cl > 5 * 1024 * 1024) {\n        return new Response('Payload too large', { status: 413 });\n      }\n    }\n  }\n\n  if (path.match(/^\\/ws\\/[\\w-]+$/)) {\n    const clientIp = getClientIP(req);\n    if (!checkWsConnectionLimit(clientIp)) {\n      return new Response(\"Too many WebSocket connections\", { status: 429 });\n    }\n\n    const roomId = path.split(\"/\")[2];\n    if (!isValidUUID(roomId)) {\n      return new Response(\"Invalid room ID\", { status: 400 });\n    }\n    const room = loadRoom(roomId);\n    if (!room) return new Response(\"Room not found\", { status: 404 });\n\n    if (ENV_ADMIN_PASSWORD || isInstanceLocked()) {\n      const token = getTokenFromRequest(req);\n      if (!token || !validateInstanceToken(token)) return new Response(\"Unauthorized\", { status: 401 });\n    }\n\n    const authSettings = oidc.getAuthSettings();\n    const requireWsToken = authSettings.productionMode || process.env.REQUIRE_WS_TOKEN === 'true';\n\n    const upgraded = server.upgrade(req, { data: { roomId, authenticated: !requireWsToken } });\n    if (upgraded) return undefined;\n    return new Response(\"WebSocket upgrade failed\", { status: 400 });\n  }\n\n  const requestOrigin = req.headers.get(\"origin\") || new URL(req.url).origin;\n\n  let allowedOrigin: string;\n  if (CONFIGURED_ORIGINS && CONFIGURED_ORIGINS.length > 0) {\n    allowedOrigin = CONFIGURED_ORIGINS.includes(requestOrigin) ? requestOrigin : CONFIGURED_ORIGINS[0];\n  } else {\n    allowedOrigin = new URL(req.url).origin;\n  }\n\n  const corsHeaders = {\n    \"Access-Control-Allow-Origin\": allowedOrigin,\n    \"Access-Control-Allow-Methods\": \"GET, POST, PUT, DELETE, OPTIONS\",\n    \"Access-Control-Allow-Headers\": \"Content-Type, Authorization, x-csrf-token\",\n    \"Access-Control-Allow-Credentials\": \"true\",\n    ...securityHeaders\n  };\n\n  if (req.method === \"OPTIONS\") return new Response(null, { headers: corsHeaders });\n\n  let response: Response | null;\n\n  response = await setupRoutes.handle(req, path, url, corsHeaders);\n  if (response) return response;\n\n  response = await adminAuthRoutes.handle(req, path, url, corsHeaders);\n  if (response) return response;\n\n  response = await adminRoomsRoutes.handle(req, path, url, corsHeaders);\n  if (response) return response;\n\n  response = await userAuthRoutes.handle(req, path, url, corsHeaders);\n  if (response) return response;\n\n  response = await adminUsersRoutes.handle(req, path, url, corsHeaders);\n  if (response) return response;\n\n  response = await adminAuthSettingsRoutes.handle(req, path, url, corsHeaders);\n  if (response) return response;\n\n  response = await instanceAccessRoutes.handle(req, path, url, corsHeaders);\n  if (response) return response;\n\n  response = await adminSettingsRoutes.handle(req, path, url, corsHeaders);\n  if (response) return response;\n\n  response = await adminLogsRoutes.handle(req, path, url, corsHeaders);\n  if (response) return response;\n\n  response = await adminBackupsRoutes.handle(req, path, url, corsHeaders);\n  if (response) return response;\n\n  response = await adminApikeysRoutes.handle(req, path, url, corsHeaders);\n  if (response) return response;\n\n  response = await roomRoutes.handle(req, path, url, corsHeaders);\n  if (response) return response;\n\n  response = await networkRoutes.handle(req, path, url, corsHeaders);\n  if (response) return response;\n\n  response = await publicRoutes.handle(req, path, url, corsHeaders);\n  if (response) return response;\n\n  return relativeRedirect(\"/?error=not_found\");\n}\n\nconst server = Bun.serve({\n  port: PORT,\n  async fetch(req, server) {\n    const start = performance.now();\n    const response = await handleRequest(req, server);\n    if (response) {\n      const path = new URL(req.url).pathname;\n      if (path !== '/api/health' && !path.startsWith('/ws/')) {\n        console.log(`[HTTP] ${req.method} ${path} ${response.status} ${(performance.now() - start).toFixed(1)}ms`);\n      }\n    }\n    return response;\n  },\n  websocket: {\n    perMessageDeflate: true,\n    maxPayloadLength: 5 * 1024 * 1024,\n    ...websocketHandlers\n  }\n});\n\nconst migrationResult = db.migrateFromFlatFiles();\nif (migrationResult.rooms > 0 || migrationResult.settings || migrationResult.admin) {\n  console.log(`[Migration] Migrated: ${migrationResult.rooms} rooms, settings: ${migrationResult.settings}, admin: ${migrationResult.admin}`);\n}\n\nredis.connectRedis().then(connected => {\n  if (connected) console.log(\"[Redis] Connected successfully\");\n  else console.log(\"[Redis] Not available, using in-memory fallback\");\n});\n\nrestartBackupTimer();\ndb.initializeDefaultEmailTemplates();\ndb.verifyAllAdminEmails();\nawait initializeTheOneFile();\n\nstartTokenCleanupIntervals();\nstartRateLimitCleanupIntervals();\n\nconst settings = getSettings();\nconsole.log(`TheOneFile Verse v${APP_VERSION} | http://localhost:${PORT}`);\nif (ENV_ADMIN_PASSWORD) console.log(`Instance password lock: ENV`);\nelse if (isInstanceLocked()) console.log(`Instance password lock: Settings`);\nif (settings.skipUpdates) console.log(`Auto updates: Disabled`);\nelse if (settings.updateIntervalHours > 0) console.log(`Auto updates: Every ${settings.updateIntervalHours}h`);\nif (settings.backupEnabled) console.log(`Auto backups: Every ${settings.backupIntervalHours}h, keep ${settings.backupRetentionCount}`);\nconsole.log(`Admin: ${isAdminConfigured() ? 'Configured' : 'Not set up'} | Rooms: ${db.countRooms()}`);\n\nconst allRooms = db.listRooms();\nfor (const room of allRooms) {\n  if (room.destruct.mode === \"time\") {\n    const elapsed = Date.now() - new Date(room.lastActivity).getTime();\n    const remaining = room.destruct.value - elapsed;\n    if (remaining <= 0) deleteRoomData(room.id);\n    else scheduleDestruction(room.id, remaining);\n  }\n}\n\nfunction shutdown() {\n  console.log(\"[Shutdown] Shutting down...\");\n  server.stop();\n  clearUpdateTimer();\n  clearBackupTimer();\n  redis.disconnectRedis();\n  db.closeDatabase();\n  process.exit(0);\n}\nprocess.on(\"SIGTERM\", shutdown);\nprocess.on(\"SIGINT\", shutdown);\nprocess.on(\"uncaughtException\", (err) => {\n  console.error(\"[Fatal]\", err);\n  shutdown();\n});\nprocess.on(\"unhandledRejection\", (err) => {\n  console.error(\"[UnhandledRejection]\", err);\n});\n"
  },
  {
    "path": "theonefile_verse/src/mailer.ts",
    "content": "import * as db from \"./database\";\nimport { decryptSecret } from \"./oidc\";\nimport { connect as tlsConnect, TLSSocket } from \"tls\";\nimport { Socket } from \"net\";\n\nfunction escapeHtml(str: string): string {\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\ninterface EmailMessage {\n  to: string;\n  subject: string;\n  html: string;\n  text: string;\n}\n\ninterface SendResult {\n  success: boolean;\n  messageId?: string;\n  error?: string;\n}\n\ninterface SmtpResponse {\n  code: number;\n  message: string;\n  lines: string[];\n}\n\nexport async function sendEmail(message: EmailMessage): Promise<SendResult> {\n  const config = db.getDefaultSmtpConfig();\n  if (!config) {\n    return { success: false, error: \"No SMTP configuration found\" };\n  }\n\n  return sendEmailWithConfig(message, config);\n}\n\nexport async function sendEmailWithConfig(\n  message: EmailMessage,\n  config: db.SmtpConfig\n): Promise<SendResult> {\n  const logId = crypto.randomUUID();\n\n  if (config.allowInsecureTls) {\n    console.warn(`[Mailer] WARNING: Insecure TLS is enabled for SMTP config \"${config.name}\". This disables certificate verification and is NOT recommended for production use.`);\n  }\n\n  try {\n    let password: string | null = null;\n    if (config.passwordEncrypted) {\n      try {\n        password = await decryptSecret(config.passwordEncrypted);\n      } catch (e) {\n        console.error(\"[Mailer] Failed to decrypt password\");\n        return logAndReturn(logId, message, config, false, \"Failed to decrypt SMTP password\");\n      }\n    }\n\n    const result = await sendViaSMTP(message, config, password);\n    return logAndReturn(logId, message, config, result.success, result.error);\n  } catch (e: any) {\n    console.error(\"[Mailer] Send error:\", e);\n    return logAndReturn(logId, message, config, false, e.message || \"Unknown error\");\n  }\n}\n\nfunction logAndReturn(\n  id: string,\n  message: EmailMessage,\n  config: db.SmtpConfig,\n  success: boolean,\n  error?: string\n): SendResult {\n  db.createEmailLog({\n    id,\n    toEmail: message.to,\n    templateName: null,\n    subject: message.subject,\n    status: success ? 'sent' : 'failed',\n    errorMessage: error || null,\n    smtpConfigId: config.id,\n    sentAt: new Date().toISOString()\n  });\n\n  return { success, messageId: success ? id : undefined, error };\n}\n\nclass SmtpClient {\n  private socket: any = null;\n  private buffer = \"\";\n  private responsePromise: { resolve: (r: SmtpResponse) => void; reject: (e: Error) => void } | null = null;\n  private isSecure = false;\n  private config: db.SmtpConfig;\n  private debug = true;\n\n  constructor(config: db.SmtpConfig) {\n    this.config = config;\n  }\n\n  private log(...args: any[]) {\n    if (this.debug) console.log(\"[SMTP]\", ...args);\n  }\n\n  async connect(): Promise<void> {\n    const { host, port, secureMode } = this.config;\n    if (!host || !port) {\n      throw new Error(\"SMTP host and port are required\");\n    }\n\n    this.log(`Connecting to ${host}:${port} (${secureMode})`);\n\n    return new Promise((resolve, reject) => {\n      const timeout = setTimeout(() => {\n        reject(new Error(\"Connection timeout\"));\n      }, 30000);\n\n      if (secureMode === 'tls') {\n        const tlsSocket = tlsConnect({\n          host: host,\n          port: port,\n          rejectUnauthorized: !this.config.allowInsecureTls,\n          servername: host\n        });\n\n        tlsSocket.once('secureConnect', () => {\n          clearTimeout(timeout);\n          this.isSecure = true;\n          this.socket = {\n            write: (data: string) => tlsSocket.write(data),\n            end: () => tlsSocket.end(),\n            rawSocket: tlsSocket\n          };\n\n          tlsSocket.on('data', (data) => this.handleData(data));\n          tlsSocket.on('error', (err) => this.handleError(err));\n          tlsSocket.on('close', () => this.handleClose());\n\n          this.readResponse().then(greeting => {\n            if (greeting.code !== 220) {\n              reject(new Error(`Server greeting failed: ${greeting.message}`));\n            } else {\n              this.log(\"Connected (TLS), greeting:\", greeting.code);\n              resolve();\n            }\n          }).catch(reject);\n        });\n\n        tlsSocket.once('error', (err) => {\n          clearTimeout(timeout);\n          reject(err);\n        });\n\n      } else {\n        const netSocket = new Socket();\n\n        netSocket.connect(port, host, () => {\n          clearTimeout(timeout);\n          this.socket = {\n            write: (data: string) => netSocket.write(data),\n            end: () => netSocket.end(),\n            rawSocket: netSocket\n          };\n\n          netSocket.on('data', (data) => this.handleData(data));\n          netSocket.on('error', (err) => this.handleError(err));\n          netSocket.on('close', () => this.handleClose());\n\n          this.readResponse().then(greeting => {\n            if (greeting.code !== 220) {\n              reject(new Error(`Server greeting failed: ${greeting.message}`));\n            } else {\n              this.log(\"Connected, greeting:\", greeting.code);\n              resolve();\n            }\n          }).catch(reject);\n        });\n\n        netSocket.once('error', (err) => {\n          clearTimeout(timeout);\n          reject(err);\n        });\n      }\n    });\n  }\n\n  private handleData(data: Buffer) {\n    this.buffer += data.toString();\n    this.processBuffer();\n  }\n\n  private handleError(error: Error) {\n    this.log(\"Socket error:\", error.message);\n    if (this.responsePromise) {\n      this.responsePromise.reject(error);\n      this.responsePromise = null;\n    }\n  }\n\n  private handleClose() {\n    this.log(\"Socket closed\");\n    if (this.responsePromise) {\n      this.responsePromise.reject(new Error(\"Connection closed unexpectedly\"));\n      this.responsePromise = null;\n    }\n  }\n\n  private processBuffer() {\n    const lines = this.buffer.split('\\r\\n');\n\n    for (let i = 0; i < lines.length - 1; i++) {\n      const line = lines[i];\n      if (line.length < 3) continue;\n\n      const code = parseInt(line.substring(0, 3), 10);\n      const isContinuation = line.charAt(3) === '-';\n\n      if (!isContinuation && this.responsePromise) {\n        const allLines = lines.slice(0, i + 1);\n        this.buffer = lines.slice(i + 1).join('\\r\\n');\n\n        this.responsePromise.resolve({\n          code,\n          message: line.substring(4),\n          lines: allLines\n        });\n        this.responsePromise = null;\n        return;\n      }\n    }\n  }\n\n  private readResponse(): Promise<SmtpResponse> {\n    return new Promise((resolve, reject) => {\n      const timeout = setTimeout(() => {\n        this.responsePromise = null;\n        reject(new Error(\"Response timeout\"));\n      }, 30000);\n\n      this.responsePromise = {\n        resolve: (r) => {\n          clearTimeout(timeout);\n          resolve(r);\n        },\n        reject: (e) => {\n          clearTimeout(timeout);\n          reject(e);\n        }\n      };\n\n      this.processBuffer();\n    });\n  }\n\n  private async command(cmd: string, expectedCodes: number[] = [250]): Promise<SmtpResponse> {\n    this.log(\">>>\", cmd.startsWith(\"AUTH\") || cmd.includes(\"@\") ? cmd.split(\" \")[0] + \" ***\" : cmd);\n\n    this.socket.write(cmd + \"\\r\\n\");\n    const response = await this.readResponse();\n\n    this.log(\"<<<\", response.code, response.message);\n\n    if (!expectedCodes.includes(response.code)) {\n      throw new Error(`SMTP error ${response.code}: ${response.message}`);\n    }\n\n    return response;\n  }\n\n  async ehlo(): Promise<string[]> {\n    const response = await this.command(`EHLO ${this.config.host}`, [250]);\n    return response.lines;\n  }\n\n  async startTls(): Promise<string[]> {\n    if (this.isSecure) return [];\n\n    await this.command(\"STARTTLS\", [220]);\n\n    this.log(\"Upgrading to TLS...\");\n\n    return new Promise((resolve, reject) => {\n      const timeout = setTimeout(() => {\n        reject(new Error(\"TLS upgrade timeout\"));\n      }, 15000);\n\n      try {\n        const rawSocket = this.socket.rawSocket as Socket;\n        if (!rawSocket) {\n          clearTimeout(timeout);\n          reject(new Error(\"Cannot upgrade to TLS: no raw socket available\"));\n          return;\n        }\n\n        rawSocket.removeAllListeners('data');\n        rawSocket.removeAllListeners('error');\n        rawSocket.removeAllListeners('close');\n\n        const tlsSocket = tlsConnect({\n          socket: rawSocket,\n          host: this.config.host!,\n          rejectUnauthorized: !this.config.allowInsecureTls,\n          servername: this.config.host!\n        });\n\n        tlsSocket.once('secureConnect', () => {\n          clearTimeout(timeout);\n          this.log(\"TLS handshake successful\");\n          this.isSecure = true;\n          this.buffer = \"\";\n\n          this.socket = {\n            write: (data: string) => tlsSocket.write(data),\n            end: () => tlsSocket.end(),\n            rawSocket: tlsSocket\n          };\n\n          tlsSocket.on('data', (data) => this.handleData(data));\n          tlsSocket.on('error', (err) => this.handleError(err));\n          tlsSocket.on('close', () => this.handleClose());\n\n          this.ehlo().then(caps => resolve(caps)).catch(reject);\n        });\n\n        tlsSocket.once('error', (err) => {\n          clearTimeout(timeout);\n          reject(err);\n        });\n\n      } catch (e: any) {\n        clearTimeout(timeout);\n        reject(e);\n      }\n    });\n  }\n\n  async authenticate(username: string, password: string, capabilities: string[]): Promise<void> {\n    const supportsLogin = capabilities.some(l => l.toUpperCase().includes('AUTH') && l.toUpperCase().includes('LOGIN'));\n    const supportsPlain = capabilities.some(l => l.toUpperCase().includes('AUTH') && l.toUpperCase().includes('PLAIN'));\n\n    if (supportsLogin) {\n      await this.command(\"AUTH LOGIN\", [334]);\n      await this.command(Buffer.from(username).toString('base64'), [334]);\n      await this.command(Buffer.from(password).toString('base64'), [235]);\n    } else if (supportsPlain) {\n      const credentials = Buffer.from(`\\0${username}\\0${password}`).toString('base64');\n      await this.command(`AUTH PLAIN ${credentials}`, [235]);\n    } else {\n      throw new Error(\"Server does not support LOGIN or PLAIN authentication\");\n    }\n    this.log(\"Authenticated successfully\");\n  }\n\n  async mailFrom(email: string): Promise<void> {\n    await this.command(`MAIL FROM:<${email}>`, [250]);\n  }\n\n  async rcptTo(email: string): Promise<void> {\n    await this.command(`RCPT TO:<${email}>`, [250, 251]);\n  }\n\n  async data(content: string): Promise<void> {\n    await this.command(\"DATA\", [354]);\n\n    const escapedContent = content.replace(/^\\./gm, '..');\n    const finalContent = escapedContent.endsWith('\\r\\n') ? escapedContent : escapedContent + '\\r\\n';\n\n    this.socket.write(finalContent + \".\\r\\n\");\n    const response = await this.readResponse();\n\n    if (response.code !== 250) {\n      throw new Error(`DATA failed: ${response.code} ${response.message}`);\n    }\n    this.log(\"Message accepted\");\n  }\n\n  async quit(): Promise<void> {\n    try {\n      await this.command(\"QUIT\", [221]);\n    } catch {}\n    this.close();\n  }\n\n  close() {\n    if (this.socket) {\n      try {\n        this.socket.end();\n      } catch {}\n      this.socket = null;\n    }\n  }\n}\n\nasync function sendViaSMTP(\n  message: EmailMessage,\n  config: db.SmtpConfig,\n  password: string | null\n): Promise<SendResult> {\n  if (!config.host || !config.port) {\n    return { success: false, error: \"SMTP host and port are required\" };\n  }\n\n  const client = new SmtpClient(config);\n\n  try {\n    console.log(`[Mailer] Connecting to ${config.host}:${config.port} (${config.secureMode})`);\n    await client.connect();\n    console.log(\"[Mailer] Connected successfully\");\n\n    let capabilities = await client.ehlo();\n    console.log(\"[Mailer] EHLO capabilities:\", capabilities.join(\", \"));\n\n    if (config.secureMode === 'starttls') {\n      const supportsTls = capabilities.some(line =>\n        line.toUpperCase().includes('STARTTLS')\n      );\n\n      if (supportsTls) {\n        console.log(\"[Mailer] Starting TLS upgrade...\");\n        const newCaps = await client.startTls();\n        if (newCaps.length > 0) {\n          capabilities = newCaps;\n          console.log(\"[Mailer] Post-TLS capabilities:\", capabilities.join(\", \"));\n        }\n      } else {\n        client.close();\n        throw new Error(\"STARTTLS required but server does not support it. Refusing to send credentials over plaintext.\");\n      }\n    }\n\n    if (config.username && password) {\n      console.log(\"[Mailer] Authenticating as:\", config.username);\n      await client.authenticate(config.username, password, capabilities);\n      console.log(\"[Mailer] Authentication successful\");\n    }\n\n    console.log(\"[Mailer] Sending MAIL FROM:\", config.fromEmail);\n    await client.mailFrom(config.fromEmail);\n\n    console.log(\"[Mailer] Sending RCPT TO:\", message.to);\n    await client.rcptTo(message.to);\n\n    console.log(\"[Mailer] Sending message data...\");\n    const messageContent = buildMessage(message, config);\n    await client.data(messageContent);\n\n    console.log(\"[Mailer] Message sent, closing connection\");\n    await client.quit();\n\n    return { success: true };\n  } catch (e: any) {\n    client.close();\n    console.error(\"[Mailer] SMTP error:\", e.message);\n    console.error(\"[Mailer] Stack:\", e.stack);\n    return { success: false, error: e.message };\n  }\n}\n\nfunction stripCRLF(str: string): string {\n  return str.replace(/[\\r\\n]/g, '');\n}\n\nfunction buildMessage(message: EmailMessage, config: db.SmtpConfig): string {\n  const boundary = `----=_Part_${Date.now()}_${crypto.randomUUID().replace(/-/g, '')}`;\n  const messageId = `<${crypto.randomUUID()}@${config.host}>`;\n  const date = new Date().toUTCString();\n\n  const safeName = config.fromName ? stripCRLF(config.fromName) : '';\n  const safeFromEmail = stripCRLF(config.fromEmail);\n  const fromHeader = safeName\n    ? `\"${safeName.replace(/\"/g, '\\\\\"')}\" <${safeFromEmail}>`\n    : safeFromEmail;\n\n  const subjectEncoded = encodeSubject(stripCRLF(message.subject));\n\n  const headers = [\n    `Message-ID: ${messageId}`,\n    `Date: ${date}`,\n    `From: ${fromHeader}`,\n    `To: ${stripCRLF(message.to)}`,\n    `Subject: ${subjectEncoded}`,\n    `MIME-Version: 1.0`,\n    `Content-Type: multipart/alternative; boundary=\"${boundary}\"`,\n    `X-Mailer: TheOneFile_Verse`,\n    ``\n  ];\n\n  const textPart = [\n    `--${boundary}`,\n    `Content-Type: text/plain; charset=\"utf-8\"`,\n    `Content-Transfer-Encoding: quoted-printable`,\n    ``,\n    encodeQuotedPrintable(message.text),\n    ``\n  ];\n\n  const htmlPart = [\n    `--${boundary}`,\n    `Content-Type: text/html; charset=\"utf-8\"`,\n    `Content-Transfer-Encoding: quoted-printable`,\n    ``,\n    encodeQuotedPrintable(message.html),\n    ``\n  ];\n\n  const ending = [`--${boundary}--`, ``];\n\n  return [...headers, ...textPart, ...htmlPart, ...ending].join('\\r\\n');\n}\n\nfunction encodeSubject(subject: string): string {\n  if (/^[\\x20-\\x7E]*$/.test(subject)) {\n    return subject;\n  }\n  const encoded = Buffer.from(subject, 'utf-8').toString('base64');\n  return `=?UTF-8?B?${encoded}?=`;\n}\n\nfunction encodeQuotedPrintable(text: string): string {\n  const lines: string[] = [];\n  let currentLine = \"\";\n\n  for (let i = 0; i < text.length; i++) {\n    const char = text[i];\n    const code = char.charCodeAt(0);\n\n    let encoded: string;\n    if (char === '\\r' && text[i + 1] === '\\n') {\n      if (currentLine) {\n        lines.push(currentLine);\n        currentLine = \"\";\n      }\n      lines.push(\"\");\n      i++;\n      continue;\n    } else if (char === '\\n') {\n      if (currentLine) {\n        lines.push(currentLine);\n        currentLine = \"\";\n      }\n      lines.push(\"\");\n      continue;\n    } else if (code >= 33 && code <= 126 && char !== '=') {\n      encoded = char;\n    } else if (char === ' ' || char === '\\t') {\n      encoded = char;\n    } else {\n      const bytes = Buffer.from(char, 'utf-8');\n      encoded = Array.from(bytes)\n        .map(b => '=' + b.toString(16).toUpperCase().padStart(2, '0'))\n        .join('');\n    }\n\n    if (currentLine.length + encoded.length > 73) {\n      lines.push(currentLine + \"=\");\n      currentLine = encoded;\n    } else {\n      currentLine += encoded;\n    }\n  }\n\n  if (currentLine) {\n    lines.push(currentLine);\n  }\n\n  return lines.join('\\r\\n');\n}\n\nexport async function sendTemplatedEmail(\n  to: string,\n  templateName: string,\n  variables: Record<string, string>\n): Promise<SendResult> {\n  const template = db.getEmailTemplateByName(templateName);\n  if (!template) {\n    return { success: false, error: `Template '${templateName}' not found` };\n  }\n\n  let subject = template.subject;\n  let html = template.bodyHtml;\n  let text = template.bodyText;\n\n  for (const [key, value] of Object.entries(variables)) {\n    const pattern = new RegExp(`\\\\{\\\\{${key}\\\\}\\\\}`, 'g');\n    const escapedValueForHtml = escapeHtml(value);\n    const safeValue = value.replace(/[\\r\\n]/g, '');\n    subject = subject.replace(pattern, safeValue);\n    html = html.replace(pattern, escapedValueForHtml);\n    text = text.replace(pattern, value);\n  }\n\n  return sendEmail({ to, subject, html, text });\n}\n\nexport async function sendVerificationEmail(\n  to: string,\n  displayName: string,\n  verifyUrl: string\n): Promise<SendResult> {\n  return sendTemplatedEmail(to, 'Email Verification', {\n    displayName,\n    verifyUrl\n  });\n}\n\nexport async function sendPasswordResetEmail(\n  to: string,\n  displayName: string,\n  resetUrl: string\n): Promise<SendResult> {\n  return sendTemplatedEmail(to, 'Password Reset', {\n    displayName,\n    resetUrl\n  });\n}\n\nexport async function sendMagicLinkEmail(\n  to: string,\n  displayName: string,\n  loginUrl: string\n): Promise<SendResult> {\n  return sendTemplatedEmail(to, 'Magic Link', {\n    displayName,\n    loginUrl\n  });\n}\n\nexport async function sendRoomInvitationEmail(\n  to: string,\n  inviterName: string,\n  roomUrl: string\n): Promise<SendResult> {\n  return sendTemplatedEmail(to, 'Room Invitation', {\n    inviterName,\n    roomUrl\n  });\n}\n\nexport async function testSmtpConfig(config: db.SmtpConfig): Promise<SendResult> {\n  return sendEmailWithConfig({\n    to: config.fromEmail,\n    subject: 'TheOneFile_Verse - SMTP Test',\n    html: '<h1>SMTP Test Successful</h1><p>Your SMTP configuration is working correctly.</p>',\n    text: 'SMTP Test Successful\\n\\nYour SMTP configuration is working correctly.'\n  }, config);\n}\n\nexport function getEmailStats(): {\n  total: number;\n  sent: number;\n  failed: number;\n} {\n  return {\n    total: db.countEmailLogs(),\n    sent: db.countEmailLogsByStatus('sent'),\n    failed: db.countEmailLogsByStatus('failed')\n  };\n}\n"
  },
  {
    "path": "theonefile_verse/src/network.ts",
    "content": "const IPV4_RE = /^(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$/;\nconst HOSTNAME_RE = /^[a-zA-Z0-9]([a-zA-Z0-9\\-]{0,61}[a-zA-Z0-9])?(\\.[a-zA-Z0-9]([a-zA-Z0-9\\-]{0,61}[a-zA-Z0-9])?)*$/;\nconst CIDR_RE = /^(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3})\\/(\\d{1,2})$/;\n\nexport interface ProbeConfig {\n  type: \"icmp\" | \"tcp\" | \"http\" | \"dns\";\n  port?: number;\n  path?: string;\n  url?: string;\n}\n\nexport interface ProbeResult {\n  type: string;\n  port?: number;\n  status: \"online\" | \"offline\" | \"error\";\n  responseTime: number | null;\n  detail?: string;\n}\n\nexport interface BatchTarget {\n  nodeId: string;\n  target: string;\n  probes: ProbeConfig[];\n  timeout: number;\n}\n\nexport interface BatchResult {\n  status: \"online\" | \"offline\" | \"unknown\";\n  rtt: number | null;\n  results: ProbeResult[];\n}\n\nexport interface DiscoveryOptions {\n  icmp: boolean;\n  tcp: boolean;\n  dns: boolean;\n  netbios: boolean;\n  mdns: boolean;\n  snmp: boolean;\n  snmpCommunity: string;\n  ports: number[];\n}\n\nexport interface DiscoveredHost {\n  ip: string;\n  hostname: string;\n  ports: number[];\n  services: Record<number, string>;\n  icon: { library: string; name: string };\n  serviceIcons: { library: string; name: string }[];\n  dnsName: string;\n  netbiosName: string;\n  mdnsName: string;\n  httpServer: string;\n  snmpName: string;\n  snmpDescr: string;\n}\n\ninterface DiscoveryHandle {\n  taskId: string;\n  cancelled: boolean;\n  cancel: () => void;\n}\n\nconst activeDiscoveries = new Map<string, DiscoveryHandle>();\n\nconst PORT_SERVICE_MAP: Record<number, string> = {\n  21: \"FTP\",\n  22: \"SSH\",\n  23: \"Telnet\",\n  25: \"SMTP\",\n  53: \"DNS\",\n  80: \"HTTP\",\n  110: \"POP3\",\n  111: \"RPC\",\n  135: \"MSRPC\",\n  139: \"NetBIOS\",\n  143: \"IMAP\",\n  161: \"SNMP\",\n  443: \"HTTPS\",\n  445: \"SMB\",\n  465: \"SMTPS\",\n  514: \"Syslog\",\n  515: \"LPD\",\n  587: \"Submission\",\n  631: \"IPP\",\n  993: \"IMAPS\",\n  995: \"POP3S\",\n  1194: \"OpenVPN\",\n  1433: \"MSSQL\",\n  1521: \"Oracle\",\n  1880: \"Node RED\",\n  1883: \"MQTT\",\n  2283: \"Immich\",\n  2342: \"PhotoPrism\",\n  2368: \"Ghost\",\n  2375: \"Docker API\",\n  2376: \"Docker TLS\",\n  3000: \"Grafana\",\n  3001: \"Uptime Kuma\",\n  3100: \"Loki\",\n  3306: \"MySQL\",\n  3389: \"RDP\",\n  3456: \"Vikunja\",\n  3579: \"Ombi\",\n  4533: \"Navidrome\",\n  4646: \"Nomad\",\n  4822: \"Guacamole\",\n  5001: \"Dockge\",\n  5055: \"Overseerr\",\n  5060: \"SIP\",\n  5380: \"Technitium\",\n  5432: \"PostgreSQL\",\n  5601: \"Kibana\",\n  5672: \"AMQP\",\n  5678: \"n8n\",\n  5900: \"VNC\",\n  6052: \"ESPHome\",\n  6379: \"Redis\",\n  6767: \"Bazarr\",\n  6789: \"NZBGet\",\n  7575: \"Homarr\",\n  7878: \"Radarr\",\n  8000: \"Paperless ngx\",\n  8006: \"Proxmox VE\",\n  8007: \"Proxmox BS\",\n  8065: \"Mattermost\",\n  8080: \"HTTP Alt\",\n  8083: \"Calibre Web\",\n  8086: \"InfluxDB\",\n  8096: \"Jellyfin\",\n  8112: \"Deluge\",\n  8123: \"Home Assistant\",\n  8181: \"Tautulli\",\n  8200: \"Vault\",\n  8384: \"Syncthing\",\n  8443: \"HTTPS Alt\",\n  8500: \"Consul\",\n  8686: \"Lidarr\",\n  8787: \"Readarr\",\n  8883: \"MQTTS\",\n  8920: \"Emby\",\n  8971: \"Frigate\",\n  8989: \"Sonarr\",\n  9001: \"MinIO\",\n  9090: \"Prometheus\",\n  9091: \"Authelia\",\n  9100: \"JetDirect\",\n  9117: \"Jackett\",\n  9200: \"Elasticsearch\",\n  9443: \"Portainer\",\n  9696: \"Prowlarr\",\n  10000: \"Webmin\",\n  11434: \"Ollama\",\n  13378: \"Audiobookshelf\",\n  19999: \"Netdata\",\n  25600: \"Komga\",\n  27017: \"MongoDB\",\n  32400: \"Plex\",\n  51820: \"WireGuard\",\n  61208: \"Glances\",\n};\n\nconst DEFAULT_SCAN_PORTS = [22, 23, 25, 53, 80, 110, 135, 139, 143, 161, 443, 445, 515, 587, 631, 993, 1433, 1880, 2283, 2342, 2375, 2376, 3000, 3001, 3100, 3306, 3389, 4646, 4822, 5001, 5055, 5380, 5432, 5601, 5678, 5900, 6379, 6767, 7575, 7878, 8000, 8006, 8007, 8065, 8080, 8083, 8086, 8096, 8112, 8123, 8181, 8200, 8384, 8443, 8500, 8686, 8787, 8920, 8971, 8989, 9001, 9090, 9091, 9100, 9117, 9443, 9696, 10000, 11434, 13378, 19999, 25600, 27017, 32400, 61208];\n\ninterface ServiceIconEntry {\n  ports: number[];\n  library: string;\n  name: string;\n}\n\nconst SERVICE_ICON_MAP: ServiceIconEntry[] = [\n  { ports: [8006, 8007], library: \"selfhst\", name: \"proxmox\" },\n  { ports: [32400], library: \"selfhst\", name: \"plex\" },\n  { ports: [8096], library: \"selfhst\", name: \"jellyfin\" },\n  { ports: [8920], library: \"selfhst\", name: \"emby\" },\n  { ports: [8989], library: \"selfhst\", name: \"sonarr\" },\n  { ports: [7878], library: \"selfhst\", name: \"radarr\" },\n  { ports: [8686], library: \"selfhst\", name: \"lidarr\" },\n  { ports: [8787], library: \"selfhst\", name: \"readarr\" },\n  { ports: [9696], library: \"selfhst\", name: \"prowlarr\" },\n  { ports: [6767], library: \"selfhst\", name: \"bazarr\" },\n  { ports: [5055], library: \"selfhst\", name: \"overseerr\" },\n  { ports: [8181], library: \"selfhst\", name: \"tautulli\" },\n  { ports: [3579], library: \"selfhst\", name: \"ombi\" },\n  { ports: [4533], library: \"selfhst\", name: \"navidrome\" },\n  { ports: [13378], library: \"selfhst\", name: \"audiobookshelf\" },\n  { ports: [25600], library: \"selfhst\", name: \"komga\" },\n  { ports: [9117], library: \"selfhst\", name: \"jackett\" },\n  { ports: [8083], library: \"selfhst\", name: \"calibre-web\" },\n  { ports: [8971], library: \"selfhst\", name: \"frigate\" },\n  { ports: [8123], library: \"selfhst\", name: \"home-assistant\" },\n  { ports: [1880], library: \"selfhst\", name: \"node-red\" },\n  { ports: [6052], library: \"selfhst\", name: \"esphome\" },\n  { ports: [5678], library: \"selfhst\", name: \"n8n\" },\n  { ports: [3001], library: \"selfhst\", name: \"uptime-kuma\" },\n  { ports: [19999], library: \"selfhst\", name: \"netdata\" },\n  { ports: [3000], library: \"selfhst\", name: \"grafana\" },\n  { ports: [9090], library: \"selfhst\", name: \"prometheus\" },\n  { ports: [5601], library: \"selfhst\", name: \"kibana\" },\n  { ports: [3100], library: \"selfhst\", name: \"loki\" },\n  { ports: [61208], library: \"selfhst\", name: \"glances\" },\n  { ports: [2283], library: \"selfhst\", name: \"immich\" },\n  { ports: [2342], library: \"selfhst\", name: \"photoprism\" },\n  { ports: [9443], library: \"selfhst\", name: \"portainer\" },\n  { ports: [5001], library: \"selfhst\", name: \"dockge\" },\n  { ports: [2375, 2376], library: \"selfhst\", name: \"docker\" },\n  { ports: [9091], library: \"selfhst\", name: \"authelia\" },\n  { ports: [8384], library: \"selfhst\", name: \"syncthing\" },\n  { ports: [5380], library: \"selfhst\", name: \"technitium-dns\" },\n  { ports: [8000], library: \"selfhst\", name: \"paperless-ngx\" },\n  { ports: [8112], library: \"selfhst\", name: \"deluge\" },\n  { ports: [6789], library: \"selfhst\", name: \"nzbget\" },\n  { ports: [7575], library: \"selfhst\", name: \"homarr\" },\n  { ports: [8065], library: \"selfhst\", name: \"mattermost\" },\n  { ports: [8086], library: \"selfhst\", name: \"influxdb\" },\n  { ports: [2368], library: \"selfhst\", name: \"ghost\" },\n  { ports: [3456], library: \"selfhst\", name: \"vikunja\" },\n  { ports: [11434], library: \"selfhst\", name: \"ollama\" },\n  { ports: [4822], library: \"selfhst\", name: \"guacamole\" },\n  { ports: [8500], library: \"selfhst\", name: \"hashicorp-consul\" },\n  { ports: [8200], library: \"selfhst\", name: \"hashicorp-vault\" },\n  { ports: [4646], library: \"selfhst\", name: \"hashicorp-nomad\" },\n  { ports: [9001], library: \"selfhst\", name: \"minio\" },\n  { ports: [1194], library: \"selfhst\", name: \"openvpn\" },\n  { ports: [51820], library: \"selfhst\", name: \"wireguard\" },\n  { ports: [10000], library: \"selfhst\", name: \"webmin\" },\n  { ports: [3306], library: \"selfhst\", name: \"mysql\" },\n  { ports: [5432], library: \"selfhst\", name: \"postgresql\" },\n  { ports: [6379], library: \"selfhst\", name: \"redis\" },\n  { ports: [27017], library: \"selfhst\", name: \"mongodb\" },\n  { ports: [9200], library: \"selfhst\", name: \"elasticsearch\" },\n  { ports: [1883, 8883], library: \"selfhst\", name: \"mosquitto\" },\n  { ports: [5060], library: \"selfhst\", name: \"asterisk\" },\n  { ports: [5672], library: \"selfhst\", name: \"rabbitmq\" },\n  { ports: [9100, 515, 631], library: \"selfhst\", name: \"cups\" },\n  { ports: [25, 465, 587], library: \"selfhst\", name: \"mailcow\" },\n  { ports: [179], library: \"selfhst\", name: \"openwrt\" },\n  { ports: [3389], library: \"selfhst\", name: \"windows\" },\n  { ports: [5900], library: \"selfhst\", name: \"vnc\" },\n  { ports: [22], library: \"selfhst\", name: \"terminal\" },\n];\n\nconst DEFAULT_ICON = { library: \"selfhst\", name: \"linux\" };\n\nexport function validateIPv4(ip: string): boolean {\n  const match = IPV4_RE.exec(ip);\n  if (!match) return false;\n  for (let i = 1; i <= 4; i++) {\n    const octet = parseInt(match[i], 10);\n    if (octet < 0 || octet > 255) return false;\n  }\n  return true;\n}\n\nexport function validateHostname(host: string): boolean {\n  if (!host || host.length > 253) return false;\n  return HOSTNAME_RE.test(host);\n}\n\nexport function validateTarget(target: string): boolean {\n  return validateIPv4(target) || validateHostname(target);\n}\n\nexport function isRFC1918(ip: string): boolean {\n  const match = IPV4_RE.exec(ip);\n  if (!match) return false;\n  const a = parseInt(match[1], 10);\n  const b = parseInt(match[2], 10);\n  if (a === 10) return true;\n  if (a === 172 && b >= 16 && b <= 31) return true;\n  if (a === 192 && b === 168) return true;\n  if (a === 127) return true;\n  if (a === 169 && b === 254) return true;\n  return false;\n}\n\nexport function validatePort(port: number): boolean {\n  return Number.isInteger(port) && port >= 1 && port <= 65535;\n}\n\nfunction ipToInt(ip: string): number {\n  const parts = ip.split(\".\").map(Number);\n  return ((parts[0] << 24) | (parts[1] << 16) | (parts[2] << 8) | parts[3]) >>> 0;\n}\n\nfunction intToIp(n: number): string {\n  return [(n >>> 24) & 255, (n >>> 16) & 255, (n >>> 8) & 255, n & 255].join(\".\");\n}\n\nexport function validateCIDR(cidr: string, allowPublic: boolean, maxPrefix: number): { valid: boolean; error?: string; firstIP?: string; lastIP?: string; count?: number } {\n  const match = CIDR_RE.exec(cidr);\n  if (!match) return { valid: false, error: \"Invalid CIDR format\" };\n\n  const ip = match[1];\n  const prefix = parseInt(match[2], 10);\n\n  if (!validateIPv4(ip)) return { valid: false, error: \"Invalid IP address\" };\n  if (prefix < 0 || prefix > 32) return { valid: false, error: \"Prefix must be 0-32\" };\n  if (prefix < maxPrefix) return { valid: false, error: `Prefix must be /${maxPrefix} or larger (smaller range)` };\n\n  if (!allowPublic && !isRFC1918(ip)) {\n    return { valid: false, error: \"Only private (RFC1918) ranges allowed\" };\n  }\n\n  const ipInt = ipToInt(ip);\n  const mask = prefix === 0 ? 0 : (~0 << (32 - prefix)) >>> 0;\n  const network = (ipInt & mask) >>> 0;\n  const broadcast = (network | ~mask) >>> 0;\n  const count = broadcast - network + 1;\n\n  return {\n    valid: true,\n    firstIP: intToIp(network),\n    lastIP: intToIp(broadcast),\n    count,\n  };\n}\n\nasync function probeICMP(target: string, timeoutMs: number): Promise<ProbeResult> {\n  const timeoutSec = Math.max(1, Math.ceil(timeoutMs / 1000));\n  const start = performance.now();\n  try {\n    const proc = Bun.spawn([\"ping\", \"-c\", \"1\", \"-W\", String(timeoutSec), target], {\n      stdout: \"pipe\",\n      stderr: \"ignore\",\n    });\n    const exitCode = await proc.exited;\n    const elapsed = Math.round(performance.now() - start);\n\n    if (exitCode === 0) {\n      const output = await new Response(proc.stdout).text();\n      const rttMatch = /time[=<](\\d+\\.?\\d*)\\s*ms/i.exec(output);\n      const rtt = rttMatch ? Math.round(parseFloat(rttMatch[1])) : elapsed;\n      return { type: \"icmp\", status: \"online\", responseTime: rtt };\n    }\n    return { type: \"icmp\", status: \"offline\", responseTime: null };\n  } catch {\n    return { type: \"icmp\", status: \"error\", responseTime: null, detail: \"ping failed\" };\n  }\n}\n\nasync function probeTCP(target: string, port: number, timeoutMs: number): Promise<ProbeResult> {\n  const start = performance.now();\n  try {\n    const socket = await Promise.race([\n      Bun.connect({\n        hostname: target,\n        port,\n        socket: {\n          data() {},\n          open(socket) { socket.end(); },\n          error() {},\n          close() {},\n        },\n      }),\n      new Promise<never>((_, reject) => setTimeout(() => reject(new Error(\"timeout\")), timeoutMs)),\n    ]);\n    const elapsed = Math.round(performance.now() - start);\n    const service = PORT_SERVICE_MAP[port] || \"\";\n    return { type: \"tcp\", port, status: \"online\", responseTime: elapsed, detail: service || undefined };\n  } catch {\n    return { type: \"tcp\", port, status: \"offline\", responseTime: null };\n  }\n}\n\nasync function probeHTTP(target: string, config: ProbeConfig, timeoutMs: number): Promise<ProbeResult> {\n  const url = config.url || `http://${target}${config.path || \"/\"}`;\n  const start = performance.now();\n  try {\n    const resp = await fetch(url, {\n      method: \"HEAD\",\n      signal: AbortSignal.timeout(timeoutMs),\n      redirect: \"manual\",\n    });\n    const elapsed = Math.round(performance.now() - start);\n    return {\n      type: \"http\",\n      status: resp.status < 500 ? \"online\" : \"error\",\n      responseTime: elapsed,\n      detail: `${resp.status} ${resp.statusText}`,\n    };\n  } catch {\n    return { type: \"http\", status: \"offline\", responseTime: null };\n  }\n}\n\nasync function probeDNS(target: string, timeoutMs: number): Promise<ProbeResult> {\n  const start = performance.now();\n  try {\n    if (validateIPv4(target)) {\n      const proc = Bun.spawn([\"nslookup\", target], {\n        stdout: \"pipe\",\n        stderr: \"ignore\",\n      });\n      const raceResult = await Promise.race([\n        proc.exited,\n        new Promise<never>((_, reject) => setTimeout(() => reject(new Error(\"timeout\")), timeoutMs)),\n      ]);\n      const elapsed = Math.round(performance.now() - start);\n      const output = await new Response(proc.stdout).text();\n      const nameMatch = /name\\s*=\\s*(\\S+)/i.exec(output);\n      const hostname = nameMatch ? nameMatch[1].replace(/\\.$/, \"\") : \"\";\n      return { type: \"dns\", status: raceResult === 0 ? \"online\" : \"offline\", responseTime: elapsed, detail: hostname || undefined };\n    } else {\n      const results = await Bun.dns.resolve(target);\n      const elapsed = Math.round(performance.now() - start);\n      const ip = results.length > 0 ? (results[0] as any).address || String(results[0]) : \"\";\n      return { type: \"dns\", status: \"online\", responseTime: elapsed, detail: ip || undefined };\n    }\n  } catch {\n    return { type: \"dns\", status: \"offline\", responseTime: null };\n  }\n}\n\nexport async function probeTarget(target: string, probes: ProbeConfig[], timeout: number): Promise<{ status: \"online\" | \"offline\" | \"unknown\"; rtt: number | null; results: ProbeResult[] }> {\n  const cappedTimeout = Math.min(timeout, 10000);\n  const results: ProbeResult[] = [];\n\n  for (const probe of probes) {\n    let result: ProbeResult;\n    switch (probe.type) {\n      case \"icmp\":\n        result = await probeICMP(target, cappedTimeout);\n        break;\n      case \"tcp\":\n        if (!probe.port || !validatePort(probe.port)) continue;\n        result = await probeTCP(target, probe.port, cappedTimeout);\n        break;\n      case \"http\":\n        result = await probeHTTP(target, probe, cappedTimeout);\n        break;\n      case \"dns\":\n        result = await probeDNS(target, cappedTimeout);\n        break;\n      default:\n        continue;\n    }\n    results.push(result);\n  }\n\n  const onlineResults = results.filter((r) => r.status === \"online\");\n  let status: \"online\" | \"offline\" | \"unknown\" = \"unknown\";\n  if (results.length === 0) status = \"unknown\";\n  else if (onlineResults.length > 0) status = \"online\";\n  else status = \"offline\";\n\n  const rtts = onlineResults.map((r) => r.responseTime).filter((t): t is number => t !== null);\n  const rtt = rtts.length > 0 ? Math.min(...rtts) : null;\n\n  return { status, rtt, results };\n}\n\nexport async function probeBatch(targets: BatchTarget[]): Promise<Record<string, BatchResult>> {\n  const results: Record<string, BatchResult> = {};\n  const batchSize = 10;\n\n  for (let i = 0; i < targets.length; i += batchSize) {\n    const batch = targets.slice(i, i + batchSize);\n    const promises = batch.map(async (t) => {\n      const result = await probeTarget(t.target, t.probes, t.timeout);\n      results[t.nodeId] = result;\n    });\n    await Promise.all(promises);\n  }\n\n  return results;\n}\n\nexport function getServiceIcon(ports: number[]): { library: string; name: string } {\n  for (const entry of SERVICE_ICON_MAP) {\n    for (const port of ports) {\n      if (entry.ports.includes(port)) return { library: entry.library, name: entry.name };\n    }\n  }\n  return { ...DEFAULT_ICON };\n}\n\nconst GENERIC_ICON_NAMES = new Set([\"terminal\", \"windows\", \"vnc\", \"cups\", \"mailcow\", \"openwrt\"]);\n\nexport function getServiceIcons(ports: number[]): { library: string; name: string }[] {\n  const icons: { library: string; name: string }[] = [];\n  const seen = new Set<string>();\n  for (const entry of SERVICE_ICON_MAP) {\n    if (GENERIC_ICON_NAMES.has(entry.name)) continue;\n    for (const port of ports) {\n      if (entry.ports.includes(port)) {\n        const key = entry.library + \"/\" + entry.name;\n        if (!seen.has(key)) {\n          seen.add(key);\n          icons.push({ library: entry.library, name: entry.name });\n        }\n        break;\n      }\n    }\n  }\n  return icons;\n}\n\nexport function getDefaultPortList(): { port: number; service: string; icon: string | null }[] {\n  return DEFAULT_SCAN_PORTS.map(port => {\n    const service = PORT_SERVICE_MAP[port] || `Port ${port}`;\n    let icon: string | null = null;\n    for (const entry of SERVICE_ICON_MAP) {\n      if (entry.ports.includes(port)) { icon = entry.name; break; }\n    }\n    return { port, service, icon };\n  });\n}\n\nfunction getServicesForPorts(ports: number[]): Record<number, string> {\n  const services: Record<number, string> = {};\n  for (const port of ports) {\n    if (PORT_SERVICE_MAP[port]) services[port] = PORT_SERVICE_MAP[port];\n    else services[port] = `Port ${port}`;\n  }\n  return services;\n}\n\nasync function reverseDNS(ip: string, timeoutMs: number): Promise<string> {\n  try {\n    const proc = Bun.spawn([\"nslookup\", ip], { stdout: \"pipe\", stderr: \"ignore\" });\n    const raceResult = await Promise.race([\n      proc.exited,\n      new Promise<never>((_, reject) => setTimeout(() => reject(new Error(\"timeout\")), timeoutMs)),\n    ]);\n    if (raceResult !== 0) return \"\";\n    const output = await new Response(proc.stdout).text();\n    const nameMatch = /name\\s*=\\s*(\\S+)/i.exec(output);\n    return nameMatch ? nameMatch[1].replace(/\\.$/, \"\") : \"\";\n  } catch {\n    return \"\";\n  }\n}\n\nasync function resolveNetBIOS(ip: string, timeoutMs: number): Promise<string> {\n  try {\n    const proc = Bun.spawn([\"nmblookup\", \"-A\", ip], { stdout: \"pipe\", stderr: \"ignore\" });\n    const raceResult = await Promise.race([\n      proc.exited,\n      new Promise<never>((_, reject) => setTimeout(() => reject(new Error(\"timeout\")), timeoutMs)),\n    ]);\n    if (raceResult !== 0) {\n      const output = await new Response(proc.stdout).text();\n      const nameMatch = /(\\S+)\\s+<00>\\s+-\\s+/i.exec(output);\n      if (nameMatch) return nameMatch[1];\n    }\n    const output = await new Response(proc.stdout).text();\n    const nameMatch = /(\\S+)\\s+<00>\\s+-\\s+/i.exec(output);\n    return nameMatch ? nameMatch[1] : \"\";\n  } catch {\n    return \"\";\n  }\n}\n\nasync function resolveMDNS(ip: string, timeoutMs: number): Promise<string> {\n  try {\n    const proc = Bun.spawn([\"avahi-resolve\", \"-a\", ip], { stdout: \"pipe\", stderr: \"ignore\" });\n    const raceResult = await Promise.race([\n      proc.exited,\n      new Promise<never>((_, reject) => setTimeout(() => reject(new Error(\"timeout\")), timeoutMs)),\n    ]);\n    if (raceResult !== 0) return \"\";\n    const output = await new Response(proc.stdout).text();\n    const parts = output.trim().split(/\\s+/);\n    return parts.length >= 2 ? parts[1] : \"\";\n  } catch {\n    return \"\";\n  }\n}\n\nasync function resolveHTTPServer(ip: string, timeoutMs: number): Promise<string> {\n  try {\n    const resp = await fetch(`http://${ip}/`, {\n      method: \"HEAD\",\n      signal: AbortSignal.timeout(timeoutMs),\n      redirect: \"manual\",\n    });\n    const server = resp.headers.get(\"server\") || \"\";\n    if (server) return server;\n    const xPowered = resp.headers.get(\"x-powered-by\") || \"\";\n    return xPowered;\n  } catch {\n    return \"\";\n  }\n}\n\ninterface DNSFingerprint {\n  serviceName: string;\n  icon?: { library: string; name: string };\n  extraServices?: Record<number, string>;\n}\n\nasync function fingerprintDNS(ip: string, openPorts: number[], timeoutMs: number): Promise<DNSFingerprint> {\n  if (openPorts.includes(5380)) {\n    return { serviceName: \"Technitium DNS\", icon: { library: \"selfhst\", name: \"technitium-dns\" } };\n  }\n\n  const adguardPorts = [80, 3000, 443].filter(p => openPorts.includes(p));\n  for (const port of adguardPorts) {\n    try {\n      const protocol = port === 443 ? \"https\" : \"http\";\n      const portSuffix = (port === 80 || port === 443) ? \"\" : `:${port}`;\n      const resp = await fetch(`${protocol}://${ip}${portSuffix}/control/status`, {\n        signal: AbortSignal.timeout(timeoutMs),\n        redirect: \"manual\",\n      });\n      if (resp.status === 401 || resp.status === 403 || resp.ok) {\n        const isAdGuard = resp.status === 401 || resp.status === 403;\n        if (!isAdGuard && resp.ok) {\n          const text = await resp.text();\n          if (!text.includes(\"running\") && !text.includes(\"version\")) continue;\n        }\n        const extra: Record<number, string> = {};\n        if (port !== 80 && port !== 443) extra[port] = \"AdGuard Home\";\n        return {\n          serviceName: \"AdGuard Home\",\n          icon: { library: \"selfhst\", name: \"adguard-home\" },\n          extraServices: Object.keys(extra).length > 0 ? extra : undefined,\n        };\n      }\n    } catch {}\n  }\n\n  const piholePorts = [80, 443].filter(p => openPorts.includes(p));\n  for (const port of piholePorts) {\n    const protocol = port === 443 ? \"https\" : \"http\";\n    try {\n      const resp = await fetch(`${protocol}://${ip}/admin/api.php`, {\n        signal: AbortSignal.timeout(timeoutMs),\n        redirect: \"manual\",\n      });\n      if (resp.ok || resp.status === 401 || resp.status === 403) {\n        return { serviceName: \"Pi-hole\", icon: { library: \"selfhst\", name: \"pihole\" } };\n      }\n    } catch {}\n    try {\n      const resp = await fetch(`${protocol}://${ip}/api/`, {\n        signal: AbortSignal.timeout(timeoutMs),\n        redirect: \"manual\",\n      });\n      if (resp.ok || resp.status === 401 || resp.status === 403) {\n        return { serviceName: \"Pi-hole\", icon: { library: \"selfhst\", name: \"pihole\" } };\n      }\n    } catch {}\n  }\n\n  return { serviceName: \"DNS\" };\n}\n\nasync function resolveSNMP(ip: string, community: string, timeoutMs: number): Promise<{ sysName: string; sysDescr: string }> {\n  const timeoutSec = Math.max(1, Math.ceil(timeoutMs / 1000));\n  try {\n    const proc = Bun.spawn(\n      [\"snmpget\", \"-v2c\", \"-c\", community, \"-t\", String(timeoutSec), \"-r\", \"0\", ip, \"1.3.6.1.2.1.1.5.0\", \"1.3.6.1.2.1.1.1.0\"],\n      { stdout: \"pipe\", stderr: \"ignore\" },\n    );\n    const raceResult = await Promise.race([\n      proc.exited,\n      new Promise<never>((_, reject) => setTimeout(() => reject(new Error(\"timeout\")), timeoutMs)),\n    ]);\n    if (raceResult !== 0) return { sysName: \"\", sysDescr: \"\" };\n    const output = await new Response(proc.stdout).text();\n    const lines = output.split(\"\\n\");\n    let sysName = \"\";\n    let sysDescr = \"\";\n    for (const line of lines) {\n      const valMatch = /STRING:\\s*\"?([^\"]*)\"?/i.exec(line);\n      if (!valMatch) continue;\n      if (line.includes(\"1.3.6.1.2.1.1.5.0\")) sysName = valMatch[1].trim();\n      else if (line.includes(\"1.3.6.1.2.1.1.1.0\")) sysDescr = valMatch[1].trim();\n    }\n    return { sysName, sysDescr };\n  } catch {\n    return { sysName: \"\", sysDescr: \"\" };\n  }\n}\n\nfunction bestHostname(host: DiscoveredHost): string {\n  return host.netbiosName || host.mdnsName || host.dnsName || host.ip;\n}\n\nfunction cidrToIPs(cidr: string): string[] {\n  const validation = validateCIDR(cidr, true, 20);\n  if (!validation.valid || !validation.firstIP || !validation.count) return [];\n  const firstInt = ipToInt(validation.firstIP);\n  const ips: string[] = [];\n  for (let i = 0; i < validation.count; i++) {\n    const ip = intToIp(firstInt + i);\n    if (!ip.endsWith(\".0\") && !ip.endsWith(\".255\")) {\n      ips.push(ip);\n    }\n  }\n  return ips;\n}\n\nexport async function startDiscovery(\n  taskId: string,\n  roomId: string,\n  cidrs: string[],\n  options: DiscoveryOptions,\n  onProgress: (percent: number, scanned: number, total: number, rangeIndex: number, totalRanges: number) => void,\n  onFound: (host: DiscoveredHost) => void,\n  onComplete: (totalFound: number) => void,\n): Promise<DiscoveryHandle> {\n  const allIPs: { ip: string; rangeIndex: number }[] = [];\n  for (let r = 0; r < cidrs.length; r++) {\n    const ips = cidrToIPs(cidrs[r]);\n    for (const ip of ips) {\n      allIPs.push({ ip, rangeIndex: r });\n    }\n  }\n\n  if (allIPs.length === 0) throw new Error(\"No valid IPs in ranges\");\n\n  const handle: DiscoveryHandle = {\n    taskId,\n    cancelled: false,\n    cancel() { this.cancelled = true; },\n  };\n  activeDiscoveries.set(taskId, handle);\n\n  const userPorts = options.ports && options.ports.length > 0 ? options.ports.filter(validatePort) : [];\n  const scanPorts = [...new Set([...DEFAULT_SCAN_PORTS, ...userPorts])].sort((a, b) => a - b);\n  const totalRanges = cidrs.length;\n\n  (async () => {\n    let scanned = 0;\n    let found = 0;\n    const total = allIPs.length;\n    const pingBatchSize = 50;\n\n    try {\n      for (let i = 0; i < allIPs.length; i += pingBatchSize) {\n        if (handle.cancelled) break;\n\n        const batch = allIPs.slice(i, i + pingBatchSize);\n        const pingResults = await Promise.all(\n          batch.map(async (entry) => {\n            if (handle.cancelled) return null;\n            const result = await probeICMP(entry.ip, 1500);\n            return result.status === \"online\" ? entry : null;\n          }),\n        );\n\n        const aliveEntries = pingResults.filter((e): e is { ip: string; rangeIndex: number } => e !== null);\n\n        for (const entry of aliveEntries) {\n          if (handle.cancelled) break;\n\n          const host: DiscoveredHost = {\n            ip: entry.ip,\n            hostname: \"\",\n            ports: [],\n            services: {},\n            icon: { ...DEFAULT_ICON },\n            serviceIcons: [],\n            dnsName: \"\",\n            netbiosName: \"\",\n            mdnsName: \"\",\n            httpServer: \"\",\n            snmpName: \"\",\n            snmpDescr: \"\",\n          };\n\n          if (options.tcp) {\n            const portBatchSize = 10;\n            for (let p = 0; p < scanPorts.length; p += portBatchSize) {\n              if (handle.cancelled) break;\n              const portBatch = scanPorts.slice(p, p + portBatchSize);\n              const portResults = await Promise.all(\n                portBatch.map(async (port) => {\n                  const result = await probeTCP(entry.ip, port, 1500);\n                  return result.status === \"online\" ? port : null;\n                }),\n              );\n              const openPorts = portResults.filter((p): p is number => p !== null);\n              host.ports.push(...openPorts);\n            }\n            host.services = getServicesForPorts(host.ports);\n            if (host.ports.includes(53)) {\n              const dnsResult = await fingerprintDNS(entry.ip, host.ports, 2000);\n              host.services[53] = dnsResult.serviceName;\n              if (dnsResult.icon) host.icon = dnsResult.icon;\n              if (dnsResult.extraServices) {\n                for (const [portStr, svcName] of Object.entries(dnsResult.extraServices)) {\n                  host.services[parseInt(portStr, 10)] = svcName;\n                }\n              }\n              if (!dnsResult.icon) host.icon = getServiceIcon(host.ports);\n            } else {\n              host.icon = getServiceIcon(host.ports);\n            }\n            host.serviceIcons = getServiceIcons(host.ports);\n          }\n\n          const namePromises: Promise<void>[] = [];\n\n          if (options.dns) {\n            namePromises.push(reverseDNS(entry.ip, 2000).then((name) => { host.dnsName = name; }));\n          }\n          if (options.netbios) {\n            namePromises.push(resolveNetBIOS(entry.ip, 2000).then((name) => { host.netbiosName = name; }));\n          }\n          if (options.mdns) {\n            namePromises.push(resolveMDNS(entry.ip, 2000).then((name) => { host.mdnsName = name; }));\n          }\n          if (host.ports.includes(80) || host.ports.includes(443) || host.ports.includes(8080)) {\n            namePromises.push(resolveHTTPServer(entry.ip, 2000).then((server) => { host.httpServer = server; }));\n          }\n          if (options.snmp && options.snmpCommunity && (host.ports.includes(161) || !options.tcp)) {\n            namePromises.push(\n              resolveSNMP(entry.ip, options.snmpCommunity, 3000).then((result) => {\n                host.snmpName = result.sysName;\n                host.snmpDescr = result.sysDescr;\n              }),\n            );\n          }\n\n          await Promise.all(namePromises);\n          host.hostname = bestHostname(host);\n\n          found++;\n          onFound(host);\n        }\n\n        scanned += batch.length;\n        const percent = Math.round((scanned / total) * 100);\n        const currentRange = batch.length > 0 ? batch[batch.length - 1].rangeIndex : 0;\n        onProgress(percent, scanned, total, currentRange, totalRanges);\n      }\n\n      onComplete(found);\n    } finally {\n      activeDiscoveries.delete(taskId);\n    }\n  })();\n\n  return handle;\n}\n\nexport function cancelDiscovery(taskId: string): boolean {\n  const handle = activeDiscoveries.get(taskId);\n  if (!handle) return false;\n  handle.cancel();\n  activeDiscoveries.delete(taskId);\n  return true;\n}\n\nexport function hasActiveDiscovery(roomId: string): boolean {\n  for (const [, handle] of activeDiscoveries) {\n    if (!handle.cancelled) return true;\n  }\n  return false;\n}\n\nexport function hasActiveDiscoveryForRoom(roomId: string, taskPrefix: string): boolean {\n  for (const [taskId, handle] of activeDiscoveries) {\n    if (taskId.startsWith(taskPrefix) && !handle.cancelled) return true;\n  }\n  return false;\n}\n\nexport function validateProbeConfig(probes: unknown): probes is ProbeConfig[] {\n  if (!Array.isArray(probes)) return false;\n  if (probes.length === 0 || probes.length > 20) return false;\n  const validTypes = [\"icmp\", \"tcp\", \"http\", \"dns\"];\n  for (const probe of probes) {\n    if (!probe || typeof probe !== \"object\") return false;\n    if (!validTypes.includes(probe.type)) return false;\n    if (probe.type === \"tcp\" && (!probe.port || !validatePort(probe.port))) return false;\n  }\n  return true;\n}\n\nexport const DOCKER_TRIGGER_PORTS = [2375, 2376, 9443, 5001];\n\nconst DOCKER_DEEP_SCAN_PORTS: number[] = [];\nfor (let p = 1000; p <= 10000; p++) DOCKER_DEEP_SCAN_PORTS.push(p);\nfor (let p = 30000; p <= 33000; p++) DOCKER_DEEP_SCAN_PORTS.push(p);\n\nexport interface DockerContainer {\n  name: string;\n  image: string;\n  ports: { hostPort: number; containerPort: number; protocol: string }[];\n  state: string;\n}\n\ninterface DeepScanHandle {\n  scanId: string;\n  ip: string;\n  cancelled: boolean;\n  cancel: () => void;\n}\n\nconst activeDeepScans = new Map<string, DeepScanHandle>();\n\nasync function queryDockerAPI(ip: string, timeoutMs: number): Promise<DockerContainer[] | null> {\n  for (const port of [2375, 2376]) {\n    try {\n      const protocol = port === 2376 ? \"https\" : \"http\";\n      const resp = await fetch(`${protocol}://${ip}:${port}/containers/json?all=false`, {\n        signal: AbortSignal.timeout(timeoutMs),\n      });\n      if (!resp.ok) continue;\n      const containers = await resp.json();\n      if (!Array.isArray(containers)) continue;\n      return containers.map((c: any) => ({\n        name: Array.isArray(c.Names) && c.Names.length > 0 ? c.Names[0].replace(/^\\//, \"\") : \"unknown\",\n        image: c.Image || \"\",\n        ports: Array.isArray(c.Ports)\n          ? c.Ports.filter((p: any) => p.PublicPort)\n                   .map((p: any) => ({\n                     hostPort: p.PublicPort,\n                     containerPort: p.PrivatePort || p.PublicPort,\n                     protocol: p.Type || \"tcp\",\n                   }))\n          : [],\n        state: c.State || \"\",\n      }));\n    } catch {\n      continue;\n    }\n  }\n  return null;\n}\n\nexport async function startDeepScan(\n  scanId: string,\n  ip: string,\n  existingPorts: number[],\n  onProgress: (percent: number, scanned: number, total: number) => void,\n  onUpdate: (newPorts: number[], newServices: Record<number, string>, containers: DockerContainer[] | null) => void,\n  onComplete: () => void,\n): Promise<DeepScanHandle> {\n  const handle: DeepScanHandle = {\n    scanId,\n    ip,\n    cancelled: false,\n    cancel() { this.cancelled = true; },\n  };\n  activeDeepScans.set(scanId, handle);\n\n  (async () => {\n    try {\n      const hasDockerAPI = existingPorts.includes(2375) || existingPorts.includes(2376);\n      let containers: DockerContainer[] | null = null;\n      const discoveredPorts: number[] = [];\n      const discoveredServices: Record<number, string> = {};\n\n      if (hasDockerAPI) {\n        containers = await queryDockerAPI(ip, 5000);\n        if (containers) {\n          for (const container of containers) {\n            for (const pm of container.ports) {\n              if (!existingPorts.includes(pm.hostPort) && !discoveredPorts.includes(pm.hostPort)) {\n                discoveredPorts.push(pm.hostPort);\n              }\n              const imageName = container.image.split(\":\")[0].split(\"/\").pop() || container.image;\n              discoveredServices[pm.hostPort] = `${imageName} (${container.name})`;\n            }\n          }\n          onProgress(100, 1, 1);\n          onUpdate(discoveredPorts, discoveredServices, containers);\n          onComplete();\n          return;\n        }\n      }\n\n      const existingSet = new Set(existingPorts);\n      const portsToScan = DOCKER_DEEP_SCAN_PORTS.filter(p => !existingSet.has(p));\n      const total = portsToScan.length;\n      let scanned = 0;\n      const deepBatchSize = 20;\n\n      for (let i = 0; i < portsToScan.length; i += deepBatchSize) {\n        if (handle.cancelled) break;\n        const batch = portsToScan.slice(i, i + deepBatchSize);\n        const results = await Promise.allSettled(\n          batch.map(async (port) => {\n            const result = await probeTCP(ip, port, 1500);\n            return result.status === \"online\" ? port : null;\n          }),\n        );\n        for (const r of results) {\n          if (r.status === \"fulfilled\" && r.value !== null) {\n            const port = r.value;\n            if (!discoveredPorts.includes(port)) {\n              discoveredPorts.push(port);\n              discoveredServices[port] = PORT_SERVICE_MAP[port] || `Port ${port}`;\n            }\n          }\n        }\n        scanned += batch.length;\n        const percent = Math.round((scanned / total) * 100);\n        onProgress(percent, scanned, total);\n      }\n\n      onUpdate(discoveredPorts, discoveredServices, null);\n      onComplete();\n    } finally {\n      activeDeepScans.delete(scanId);\n    }\n  })();\n\n  return handle;\n}\n\nexport function cancelDeepScan(scanId: string): boolean {\n  const handle = activeDeepScans.get(scanId);\n  if (!handle) return false;\n  handle.cancel();\n  activeDeepScans.delete(scanId);\n  return true;\n}\n\nexport function hasActiveDeepScan(ip: string): boolean {\n  for (const [, handle] of activeDeepScans) {\n    if (handle.ip === ip && !handle.cancelled) return true;\n  }\n  return false;\n}\n"
  },
  {
    "path": "theonefile_verse/src/oidc.ts",
    "content": "import * as db from \"./database\";\n\nconst DEBUG_OIDC = process.env.DEBUG_OIDC === 'true';\n\nif (DEBUG_OIDC) {\n  const authSettings = db.getSetting('authSettings');\n  const isProduction = authSettings ? JSON.parse(authSettings).productionMode : false;\n  if (isProduction) {\n    console.warn('[Security] WARNING: DEBUG_OIDC is enabled in production mode!');\n    console.warn('[Security] This may expose sensitive information in logs. Disable DEBUG_OIDC for production.');\n  }\n}\n\nfunction logOidcError(message: string, ...args: any[]): void {\n  if (DEBUG_OIDC) {\n    console.error('[OIDC]', message, ...args);\n  } else {\n    console.error('[OIDC]', message);\n  }\n}\n\nfunction logOidcDebug(message: string, ...args: any[]): void {\n  if (DEBUG_OIDC) {\n    console.log('[OIDC]', message, ...args);\n  }\n}\n\nconst MAX_OIDC_STATES = 10000;\nconst OIDC_STATE_TTL_MS = 10 * 60 * 1000;\nconst ID_TOKEN_CLOCK_SKEW_MS = parseInt(process.env.OIDC_CLOCK_SKEW_SECONDS || '120') * 1000;\nconst CSRF_TOKEN_TTL_MS = 60 * 60 * 1000;\nconst MAX_CSRF_TOKENS = 10000;\n\nfunction cleanupOldestOidcStates(): void {\n  db.cleanupExpiredOidcStates();\n  const count = db.countOidcStates();\n  if (count <= MAX_OIDC_STATES) return;\n  db.deleteOldestOidcStates(count - MAX_OIDC_STATES + 100);\n}\n\nsetInterval(() => {\n  db.cleanupExpiredOidcStates();\n  db.cleanupExpiredCsrfTokens();\n}, 60 * 1000);\n\nfunction generateRandomString(length: number): string {\n  const array = new Uint8Array(length);\n  crypto.getRandomValues(array);\n  return base64UrlEncode(array);\n}\n\nfunction generateCodeVerifier(): string {\n  const array = new Uint8Array(32);\n  crypto.getRandomValues(array);\n  return base64UrlEncode(array);\n}\n\nasync function generateCodeChallenge(verifier: string): Promise<string> {\n  const encoder = new TextEncoder();\n  const data = encoder.encode(verifier);\n  const hash = await crypto.subtle.digest('SHA-256', data);\n  return base64UrlEncode(new Uint8Array(hash));\n}\n\nfunction base64UrlEncode(buffer: Uint8Array): string {\n  let str = '';\n  for (const byte of buffer) {\n    str += String.fromCharCode(byte);\n  }\n  return btoa(str).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=/g, '');\n}\n\nconst ENCRYPTION_KEY_LENGTH = 32;\nconst PBKDF2_ITERATIONS = 600000;\nconst PBKDF2_SALT_LENGTH = 16;\nlet encryptionKey: CryptoKey | null = null;\n\nasync function deriveKeyFromSecret(secret: string, salt: Uint8Array): Promise<CryptoKey> {\n  const encoder = new TextEncoder();\n  const keyMaterial = await crypto.subtle.importKey(\n    'raw',\n    encoder.encode(secret),\n    'PBKDF2',\n    false,\n    ['deriveBits', 'deriveKey']\n  );\n\n  return await crypto.subtle.deriveKey(\n    {\n      name: 'PBKDF2',\n      salt: salt,\n      iterations: PBKDF2_ITERATIONS,\n      hash: 'SHA-256'\n    },\n    keyMaterial,\n    { name: 'AES-GCM', length: 256 },\n    false,\n    ['encrypt', 'decrypt']\n  );\n}\n\nasync function getEncryptionKeyMaterial(): Promise<Uint8Array> {\n  const envKey = process.env.ENCRYPTION_KEY;\n\n  if (envKey) {\n    if (envKey.length < 32 || !/^[0-9a-fA-F]+$/.test(envKey)) {\n      console.error('[FATAL] ENCRYPTION_KEY must be a hex string of at least 32 characters.');\n      console.error('[FATAL] Generate one with: openssl rand -hex 32');\n      process.exit(1);\n    }\n    const encoder = new TextEncoder();\n    const hash = await crypto.subtle.digest('SHA-256', encoder.encode(envKey));\n    return new Uint8Array(hash);\n  }\n\n  const authSettings = db.getSetting('authSettings');\n  const isProduction = authSettings ? JSON.parse(authSettings).productionMode : false;\n\n  if (isProduction) {\n    console.error('='.repeat(70));\n    console.error('[FATAL] ENCRYPTION_KEY environment variable is required in production mode.');\n    console.error('[FATAL] Set ENCRYPTION_KEY in your environment before starting the server.');\n    console.error('[FATAL] Generate one with: openssl rand -hex 32');\n    console.error('='.repeat(70));\n    process.exit(1);\n  }\n\n  let keyHex = db.getSetting('encryption_key');\n\n  if (!keyHex) {\n    const keyBytes = new Uint8Array(ENCRYPTION_KEY_LENGTH);\n    crypto.getRandomValues(keyBytes);\n    keyHex = Array.from(keyBytes, b => b.toString(16).padStart(2, '0')).join('');\n    db.setSetting('encryption_key', keyHex);\n    console.warn('='.repeat(70));\n    console.warn('[SECURITY WARNING] ENCRYPTION_KEY environment variable not set.');\n    console.warn('[SECURITY WARNING] A random key has been generated and stored in the database.');\n    console.warn('[SECURITY WARNING] This is acceptable for development but NOT for production.');\n    console.warn('[SECURITY WARNING] Enable productionMode and set ENCRYPTION_KEY for production.');\n    console.warn('='.repeat(70));\n  }\n\n  return new Uint8Array(keyHex.match(/.{1,2}/g)!.map(byte => parseInt(byte, 16)));\n}\n\nasync function getEncryptionKey(salt: Uint8Array): Promise<CryptoKey> {\n  const keyMaterial = await getEncryptionKeyMaterial();\n\n  const importedKey = await crypto.subtle.importKey(\n    'raw',\n    keyMaterial,\n    'PBKDF2',\n    false,\n    ['deriveBits', 'deriveKey']\n  );\n\n  return await crypto.subtle.deriveKey(\n    {\n      name: 'PBKDF2',\n      salt: salt,\n      iterations: PBKDF2_ITERATIONS,\n      hash: 'SHA-256'\n    },\n    importedKey,\n    { name: 'AES-GCM', length: 256 },\n    false,\n    ['encrypt', 'decrypt']\n  );\n}\n\nexport async function encryptSecret(plaintext: string): Promise<string> {\n  const salt = crypto.getRandomValues(new Uint8Array(PBKDF2_SALT_LENGTH));\n  const key = await getEncryptionKey(salt);\n  const iv = crypto.getRandomValues(new Uint8Array(12));\n  const encoder = new TextEncoder();\n  const encrypted = await crypto.subtle.encrypt(\n    { name: 'AES-GCM', iv },\n    key,\n    encoder.encode(plaintext)\n  );\n\n  const combined = new Uint8Array(salt.length + iv.length + encrypted.byteLength);\n  combined.set(salt);\n  combined.set(iv, salt.length);\n  combined.set(new Uint8Array(encrypted), salt.length + iv.length);\n\n  return base64UrlEncode(combined);\n}\n\nexport async function decryptSecret(ciphertext: string): Promise<string> {\n  const combined = Uint8Array.from(atob(ciphertext.replace(/-/g, '+').replace(/_/g, '/')), c => c.charCodeAt(0));\n\n  const isNewFormat = combined.length >= 44;\n\n  if (isNewFormat) {\n    const salt = combined.slice(0, PBKDF2_SALT_LENGTH);\n    const iv = combined.slice(PBKDF2_SALT_LENGTH, PBKDF2_SALT_LENGTH + 12);\n    const encrypted = combined.slice(PBKDF2_SALT_LENGTH + 12);\n\n    const key = await getEncryptionKey(salt);\n\n    const decrypted = await crypto.subtle.decrypt(\n      { name: 'AES-GCM', iv },\n      key,\n      encrypted\n    );\n\n    return new TextDecoder().decode(decrypted);\n  } else {\n    const legacySalt = new TextEncoder().encode('theonefile-verse-encryption-v1');\n    const key = await getEncryptionKey(legacySalt);\n\n    const iv = combined.slice(0, 12);\n    const encrypted = combined.slice(12);\n\n    const decrypted = await crypto.subtle.decrypt(\n      { name: 'AES-GCM', iv },\n      key,\n      encrypted\n    );\n\n    return new TextDecoder().decode(decrypted);\n  }\n}\n\ninterface OidcDiscovery {\n  issuer: string;\n  authorization_endpoint: string;\n  token_endpoint: string;\n  userinfo_endpoint: string;\n  jwks_uri: string;\n  scopes_supported?: string[];\n  revocation_endpoint?: string;\n}\n\nasync function discoverOidcEndpoints(issuerUrl: string): Promise<OidcDiscovery | null> {\n  try {\n    const wellKnownUrl = issuerUrl.replace(/\\/$/, '') + '/.well-known/openid-configuration';\n    const res = await fetch(wellKnownUrl);\n    if (!res.ok) return null;\n    const disc = await res.json();\n    if (!disc.authorization_endpoint || !disc.token_endpoint) return null;\n    const normExpected = issuerUrl.replace(/\\/$/, '');\n    const normActual = (disc.issuer || '').replace(/\\/$/, '');\n    if (normActual && normActual !== normExpected) {\n      logOidcError('Discovery issuer mismatch', { expected: normExpected, got: normActual });\n      return null;\n    }\n    return disc;\n  } catch {\n    return null;\n  }\n}\n\nexport interface OidcAuthUrl {\n  url: string;\n  state: string;\n}\n\nexport interface OidcUserInfo {\n  sub: string;\n  email?: string;\n  email_verified?: boolean;\n  name?: string;\n  preferred_username?: string;\n  picture?: string;\n}\n\nexport interface OidcTokenResponse {\n  access_token: string;\n  token_type: string;\n  expires_in?: number;\n  refresh_token?: string;\n  id_token?: string;\n}\n\nexport async function generateAuthorizationUrl(\n  providerId: string,\n  baseUrl: string,\n  linkUserId?: string,\n  postLoginRedirect?: string\n): Promise<OidcAuthUrl | null> {\n  const provider = db.getOidcProvider(providerId);\n  if (!provider || !provider.isActive) return null;\n\n  const codeVerifier = generateCodeVerifier();\n  const codeChallenge = await generateCodeChallenge(codeVerifier);\n  const state = generateRandomString(32);\n  const nonce = generateRandomString(32);\n  const redirectUri = `${baseUrl}/auth/callback/${providerId}`;\n\n  cleanupOldestOidcStates();\n\n  const now = new Date();\n  const expiresAt = new Date(now.getTime() + OIDC_STATE_TTL_MS);\n\n  db.createOidcState({\n    state,\n    providerId,\n    codeVerifier,\n    nonce,\n    redirectUri,\n    linkUserId: linkUserId || null,\n    postLoginRedirect: postLoginRedirect || null,\n    createdAt: now.toISOString(),\n    expiresAt: expiresAt.toISOString()\n  });\n\n  let authUrl = provider.authorizationUrl;\n\n  if (provider.issuerUrl && !authUrl) {\n    const discovery = await discoverOidcEndpoints(provider.issuerUrl);\n    if (discovery) {\n      authUrl = discovery.authorization_endpoint;\n    }\n  }\n\n  if (!authUrl) return null;\n\n  let scopes = provider.scopes || 'openid email profile';\n  if (!scopes.split(' ').includes('openid')) scopes = 'openid ' + scopes;\n\n  const params = new URLSearchParams({\n    response_type: 'code',\n    client_id: provider.clientId,\n    redirect_uri: redirectUri,\n    scope: scopes,\n    state,\n    nonce,\n    code_challenge: codeChallenge,\n    code_challenge_method: 'S256'\n  });\n\n  return {\n    url: `${authUrl}?${params.toString()}`,\n    state\n  };\n}\n\nexport async function exchangeCodeForTokens(\n  providerId: string,\n  code: string,\n  state: string\n): Promise<{ tokens: OidcTokenResponse; userInfo: OidcUserInfo; linkUserId?: string; postLoginRedirect?: string } | null> {\n  const storedState = db.getOidcState(state);\n  if (!storedState || storedState.providerId !== providerId) {\n    return null;\n  }\n\n  if (new Date(storedState.expiresAt) < new Date()) {\n    db.deleteOidcState(state);\n    return null;\n  }\n\n  db.deleteOidcState(state);\n\n  const provider = db.getOidcProvider(providerId);\n  if (!provider) return null;\n\n  let tokenUrl = provider.tokenUrl;\n  let userinfoUrl = provider.userinfoUrl;\n  let jwksUri = provider.jwksUri;\n\n  if (provider.issuerUrl && (!tokenUrl || !userinfoUrl || !jwksUri)) {\n    const discovery = await discoverOidcEndpoints(provider.issuerUrl);\n    if (discovery) {\n      tokenUrl = tokenUrl || discovery.token_endpoint;\n      userinfoUrl = userinfoUrl || discovery.userinfo_endpoint;\n      jwksUri = jwksUri || discovery.jwks_uri;\n    }\n  }\n\n  if (!tokenUrl) return null;\n\n  let clientSecret: string;\n  try {\n    clientSecret = await decryptSecret(provider.clientSecretEncrypted);\n  } catch {\n    return null;\n  }\n\n  const tokenParams = new URLSearchParams({\n    grant_type: 'authorization_code',\n    client_id: provider.clientId,\n    client_secret: clientSecret,\n    code,\n    redirect_uri: storedState.redirectUri,\n    code_verifier: storedState.codeVerifier\n  });\n  const storedNonce = storedState.nonce;\n  const storedLinkUserId = storedState.linkUserId;\n  const storedPostLoginRedirect = storedState.postLoginRedirect;\n\n  try {\n    const tokenRes = await fetch(tokenUrl, {\n      method: 'POST',\n      headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n      body: tokenParams.toString()\n    });\n\n    if (!tokenRes.ok) {\n      const errText = await tokenRes.text();\n      logOidcError('Token exchange failed', errText.substring(0, 200).replace(/access_token[^&]*/gi, 'access_token=[REDACTED]').replace(/refresh_token[^&]*/gi, 'refresh_token=[REDACTED]'));\n      return null;\n    }\n\n    const tokens: OidcTokenResponse = await tokenRes.json();\n\n    if (tokens.token_type && tokens.token_type.toLowerCase() !== 'bearer') {\n      logOidcError('Unsupported token type', tokens.token_type);\n      return null;\n    }\n\n    let userInfo: OidcUserInfo | null = null;\n\n    if (tokens.id_token) {\n      userInfo = await parseIdToken(\n        tokens.id_token,\n        storedNonce,\n        jwksUri,\n        provider.issuerUrl,\n        provider.clientId\n      );\n    }\n\n    if (!userInfo && userinfoUrl) {\n      const userInfoRes = await fetch(userinfoUrl, {\n        headers: { 'Authorization': `Bearer ${tokens.access_token}` }\n      });\n\n      if (userInfoRes.ok) {\n        userInfo = await userInfoRes.json();\n      }\n    }\n\n    if (!userInfo || !userInfo.sub) {\n      logOidcError('Could not get user info');\n      return null;\n    }\n\n    return { tokens, userInfo, linkUserId: storedLinkUserId || undefined, postLoginRedirect: storedPostLoginRedirect || undefined };\n  } catch (e) {\n    logOidcError('Token exchange error', e);\n    return null;\n  }\n}\n\nconst MAX_JWKS_CACHE_ENTRIES = 100;\nconst jwksCache = new Map<string, { keys: JsonWebKey[]; fetchedAt: number }>();\n\nfunction cleanupJwksCache(): void {\n  if (jwksCache.size <= MAX_JWKS_CACHE_ENTRIES) return;\n\n  const entries = Array.from(jwksCache.entries())\n    .sort((a, b) => a[1].fetchedAt - b[1].fetchedAt);\n\n  const toRemove = entries.slice(0, jwksCache.size - MAX_JWKS_CACHE_ENTRIES + 10);\n  for (const [key] of toRemove) {\n    jwksCache.delete(key);\n  }\n}\n\nasync function sleep(ms: number): Promise<void> {\n  return new Promise(resolve => setTimeout(resolve, ms));\n}\n\nasync function fetchJwks(jwksUri: string): Promise<JsonWebKey[]> {\n  const cached = jwksCache.get(jwksUri);\n  if (cached && Date.now() - cached.fetchedAt < 60 * 60 * 1000) {\n    return cached.keys;\n  }\n\n  const maxRetries = 3;\n  const baseDelayMs = 100;\n\n  for (let attempt = 0; attempt < maxRetries; attempt++) {\n    try {\n      const res = await fetch(jwksUri);\n      if (!res.ok) {\n        if (attempt < maxRetries - 1) {\n          const jitter = new Uint32Array(1); crypto.getRandomValues(jitter);\n          const delay = baseDelayMs * Math.pow(2, attempt) + (jitter[0] % 100);\n          await sleep(delay);\n          continue;\n        }\n        return [];\n      }\n      const data = await res.json();\n      const keys = data.keys || [];\n      cleanupJwksCache();\n      jwksCache.set(jwksUri, { keys, fetchedAt: Date.now() });\n      return keys;\n    } catch {\n      if (attempt < maxRetries - 1) {\n        const jitter = new Uint32Array(1); crypto.getRandomValues(jitter);\n        const delay = baseDelayMs * Math.pow(2, attempt) + (jitter[0] % 100);\n        await sleep(delay);\n        continue;\n      }\n      return [];\n    }\n  }\n  return [];\n}\n\nfunction getHashAlgorithm(alg: string): string {\n  if (alg.endsWith('384')) return 'SHA-384';\n  if (alg.endsWith('512')) return 'SHA-512';\n  return 'SHA-256';\n}\n\nasync function importJwkForVerify(jwk: JsonWebKey, headerAlg?: string): Promise<CryptoKey | null> {\n  try {\n    const alg = jwk.alg || headerAlg;\n    if (!alg) return null;\n    let algorithm: RsaHashedImportParams | EcKeyImportParams;\n\n    if (alg.startsWith('RS')) {\n      algorithm = { name: 'RSASSA-PKCS1-v1_5', hash: { name: getHashAlgorithm(alg) } };\n    } else if (alg.startsWith('ES')) {\n      algorithm = { name: 'ECDSA', namedCurve: jwk.crv || 'P-256' };\n    } else {\n      return null;\n    }\n\n    return await crypto.subtle.importKey('jwk', jwk, algorithm, false, ['verify']);\n  } catch {\n    return null;\n  }\n}\n\nasync function verifyJwtSignature(token: string, jwks: JsonWebKey[]): Promise<boolean> {\n  const parts = token.split('.');\n  if (parts.length !== 3) return false;\n\n  try {\n    const header = JSON.parse(atob(parts[0].replace(/-/g, '+').replace(/_/g, '/')));\n    const signature = Uint8Array.from(atob(parts[2].replace(/-/g, '+').replace(/_/g, '/')), c => c.charCodeAt(0));\n    const signedData = new TextEncoder().encode(parts[0] + '.' + parts[1]);\n\n    const matchingKeys = header.kid\n      ? jwks.filter(k => k.kid === header.kid)\n      : jwks;\n\n    for (const jwk of matchingKeys) {\n      const key = await importJwkForVerify(jwk, header.alg);\n      if (!key) continue;\n\n      const alg = jwk.alg || header.alg;\n      if (!alg) continue;\n      let verifyAlg: AlgorithmIdentifier | RsaPssParams | EcdsaParams;\n\n      if (alg.startsWith('RS')) {\n        verifyAlg = { name: 'RSASSA-PKCS1-v1_5' };\n      } else if (alg.startsWith('ES')) {\n        verifyAlg = { name: 'ECDSA', hash: { name: getHashAlgorithm(alg) } };\n      } else {\n        continue;\n      }\n\n      const valid = await crypto.subtle.verify(verifyAlg, key, signature, signedData);\n      if (valid) return true;\n    }\n\n    return false;\n  } catch (e) {\n    logOidcError('JWT signature verification error', e);\n    return false;\n  }\n}\n\nasync function parseIdToken(\n  idToken: string,\n  expectedNonce: string,\n  jwksUri?: string | null,\n  expectedIssuer?: string | null,\n  expectedAudience?: string | null\n): Promise<OidcUserInfo | null> {\n  try {\n    const parts = idToken.split('.');\n    if (parts.length !== 3) return null;\n\n    if (jwksUri) {\n      const jwks = await fetchJwks(jwksUri);\n      if (jwks.length === 0) {\n        logOidcError('Failed to fetch JWKS for signature verification');\n        return null;\n      }\n      const signatureValid = await verifyJwtSignature(idToken, jwks);\n      if (!signatureValid) {\n        logOidcError('ID token signature verification failed');\n        return null;\n      }\n    }\n\n    const payload = JSON.parse(atob(parts[1].replace(/-/g, '+').replace(/_/g, '/')));\n\n    if (expectedIssuer) {\n      const normalizedExpected = expectedIssuer.replace(/\\/$/, '');\n      const normalizedActual = (payload.iss || '').replace(/\\/$/, '');\n      if (normalizedActual !== normalizedExpected) {\n        logOidcError('Issuer mismatch', { expected: normalizedExpected, got: normalizedActual });\n        return null;\n      }\n    }\n\n    if (expectedAudience) {\n      const aud = payload.aud;\n      const audArray = Array.isArray(aud) ? aud : [aud];\n      if (!audArray.includes(expectedAudience)) {\n        logOidcError('Audience mismatch', { expected: expectedAudience, got: aud });\n        return null;\n      }\n\n      if (audArray.length > 1 && payload.azp !== expectedAudience) {\n        logOidcError('Authorized party (azp) mismatch for multi-audience token');\n        return null;\n      }\n    }\n\n    if (typeof payload.nonce !== 'string' || typeof expectedNonce !== 'string' ||\n        payload.nonce.length !== expectedNonce.length ||\n        !crypto.timingSafeEqual(Buffer.from(payload.nonce), Buffer.from(expectedNonce))) {\n      logOidcError('Nonce mismatch');\n      return null;\n    }\n\n    if (payload.exp && payload.exp * 1000 < Date.now() - ID_TOKEN_CLOCK_SKEW_MS) {\n      logOidcError('ID token expired');\n      return null;\n    }\n\n    if (payload.nbf && payload.nbf * 1000 > Date.now() + ID_TOKEN_CLOCK_SKEW_MS) {\n      logOidcError('ID token not yet valid (nbf)');\n      return null;\n    }\n\n    if (payload.iat && payload.iat * 1000 < Date.now() - getIdTokenMaxAgeMs()) {\n      logOidcError('ID token too old');\n      return null;\n    }\n\n    if (payload.iat && payload.iat * 1000 > Date.now() + ID_TOKEN_CLOCK_SKEW_MS) {\n      logOidcError('ID token issued in the future');\n      return null;\n    }\n\n    if (!payload.sub) {\n      logOidcError('ID token missing required sub claim');\n      return null;\n    }\n\n    return {\n      sub: payload.sub,\n      email: payload.email,\n      email_verified: payload.email_verified,\n      name: payload.name || payload.preferred_username,\n      preferred_username: payload.preferred_username,\n      picture: payload.picture\n    };\n  } catch {\n    return null;\n  }\n}\n\nexport async function processOidcCallback(\n  providerId: string,\n  code: string,\n  state: string,\n  ipAddress: string,\n  userAgent: string,\n  currentUserToken?: string | null\n): Promise<{\n  success: boolean;\n  userId?: string;\n  sessionToken?: string;\n  error?: string;\n  isNewUser?: boolean;\n  isLinked?: boolean;\n  message?: string;\n  postLoginRedirect?: string;\n}> {\n  const result = await exchangeCodeForTokens(providerId, code, state);\n  if (!result) {\n    return { success: false, error: 'Failed to exchange code for tokens' };\n  }\n\n  const { tokens, userInfo, linkUserId, postLoginRedirect } = result;\n  const provider = db.getOidcProvider(providerId);\n  if (!provider) {\n    return { success: false, error: 'Provider not found' };\n  }\n\n  const existingLink = db.getOidcLinkByProvider(provider.name, userInfo.sub);\n\n  if (existingLink) {\n    const encryptedAccess = tokens.access_token ? await encryptSecret(tokens.access_token) : null;\n    const encryptedRefresh = tokens.refresh_token ? await encryptSecret(tokens.refresh_token) : null;\n    const expiresAt = tokens.expires_in\n      ? new Date(Date.now() + tokens.expires_in * 1000).toISOString()\n      : null;\n\n    db.updateOidcLinkTokens(existingLink.id, encryptedAccess, encryptedRefresh, expiresAt);\n\n    const user = db.getUserById(existingLink.userId);\n    if (user) {\n      user.lastLogin = new Date().toISOString();\n      user.updatedAt = new Date().toISOString();\n      db.updateUser(user);\n    }\n\n    const sessionToken = await createUserSessionToken(existingLink.userId, ipAddress, userAgent);\n\n    db.logAuthEvent('oidc_login', existingLink.userId, ipAddress, { provider: provider.name, providerUserId: userInfo.sub });\n    return { success: true, userId: existingLink.userId, sessionToken, isNewUser: false, postLoginRedirect };\n  }\n\n  if (linkUserId) {\n    if (currentUserToken) {\n      const currentUser = await validateUserSessionToken(currentUserToken);\n      if (!currentUser || currentUser.id !== linkUserId) {\n        return { success: false, error: 'Session expired or invalid for account linking' };\n      }\n    } else {\n      return { success: false, error: 'Authentication required for account linking' };\n    }\n\n    const user = db.getUserById(linkUserId);\n    if (!user) {\n      return { success: false, error: 'User not found for linking' };\n    }\n\n    const encryptedAccess = tokens.access_token ? await encryptSecret(tokens.access_token) : null;\n    const encryptedRefresh = tokens.refresh_token ? await encryptSecret(tokens.refresh_token) : null;\n    const expiresAt = tokens.expires_in\n      ? new Date(Date.now() + tokens.expires_in * 1000).toISOString()\n      : null;\n\n    db.createOidcLink({\n      id: crypto.randomUUID(),\n      userId: linkUserId,\n      provider: provider.name,\n      providerUserId: userInfo.sub,\n      providerEmail: userInfo.email || null,\n      accessTokenEncrypted: encryptedAccess,\n      refreshTokenEncrypted: encryptedRefresh,\n      tokenExpiresAt: expiresAt,\n      createdAt: new Date().toISOString()\n    });\n\n    db.logAuthEvent('oidc_link', linkUserId, ipAddress, { provider: provider.name, providerUserId: userInfo.sub });\n    return { success: true, userId: linkUserId, isNewUser: false, isLinked: true, message: `Successfully linked ${provider.name} account`, postLoginRedirect };\n  }\n\n  const authSettings = getAuthSettings();\n  let existingUser: db.User | null = null;\n\n  if (authSettings.oidcEmailMatching && userInfo.email && userInfo.email_verified) {\n    const candidate = db.getUserByEmail(userInfo.email.toLowerCase().trim());\n    if (candidate && candidate.emailVerified) existingUser = candidate;\n  }\n\n  if (existingUser) {\n    const encryptedAccess = tokens.access_token ? await encryptSecret(tokens.access_token) : null;\n    const encryptedRefresh = tokens.refresh_token ? await encryptSecret(tokens.refresh_token) : null;\n    const expiresAt = tokens.expires_in\n      ? new Date(Date.now() + tokens.expires_in * 1000).toISOString()\n      : null;\n\n    db.createOidcLink({\n      id: crypto.randomUUID(),\n      userId: existingUser.id,\n      provider: provider.name,\n      providerUserId: userInfo.sub,\n      providerEmail: userInfo.email || null,\n      accessTokenEncrypted: encryptedAccess,\n      refreshTokenEncrypted: encryptedRefresh,\n      tokenExpiresAt: expiresAt,\n      createdAt: new Date().toISOString()\n    });\n\n    existingUser.lastLogin = new Date().toISOString();\n    existingUser.updatedAt = new Date().toISOString();\n    db.updateUser(existingUser);\n\n    const sessionToken = await createUserSessionToken(existingUser.id, ipAddress, userAgent);\n    db.logAuthEvent('oidc_login_email_match', existingUser.id, ipAddress, { provider: provider.name, providerUserId: userInfo.sub, matchedEmail: userInfo.email });\n    return { success: true, userId: existingUser.id, sessionToken, isNewUser: false, postLoginRedirect };\n  }\n\n  if (authSettings.authMode === 'closed') {\n    return { success: false, error: 'Registration is currently closed', postLoginRedirect };\n  }\n  if (authSettings.authMode === 'invite_only') {\n    return { success: false, error: 'Registration requires an invitation', postLoginRedirect };\n  }\n\n  const userId = crypto.randomUUID();\n  const now = new Date().toISOString();\n\n  const newUser: db.User = {\n    id: userId,\n    email: userInfo.email || null,\n    emailVerified: userInfo.email_verified || false,\n    displayName: userInfo.name || userInfo.preferred_username || null,\n    avatarUrl: userInfo.picture || null,\n    passwordHash: null,\n    role: 'user',\n    createdAt: now,\n    updatedAt: now,\n    lastLogin: now,\n    isActive: true,\n    failedLoginAttempts: 0,\n    lockedUntil: null,\n    totpSecret: null,\n    totpEnabled: false,\n    totpBackupCodes: null,\n    pendingEmail: null,\n    pendingEmailToken: null\n  };\n\n  const { wasFirst } = db.createUserAtomic(newUser);\n  if (wasFirst) {\n    newUser.role = 'admin';\n    newUser.emailVerified = true;\n    db.updateUser(newUser);\n  }\n\n  const encryptedAccess = tokens.access_token ? await encryptSecret(tokens.access_token) : null;\n  const encryptedRefresh = tokens.refresh_token ? await encryptSecret(tokens.refresh_token) : null;\n  const expiresAt = tokens.expires_in\n    ? new Date(Date.now() + tokens.expires_in * 1000).toISOString()\n    : null;\n\n  db.createOidcLink({\n    id: crypto.randomUUID(),\n    userId,\n    provider: provider.name,\n    providerUserId: userInfo.sub,\n    providerEmail: userInfo.email || null,\n    accessTokenEncrypted: encryptedAccess,\n    refreshTokenEncrypted: encryptedRefresh,\n    tokenExpiresAt: expiresAt,\n    createdAt: now\n  });\n\n  const sessionToken = await createUserSessionToken(userId, ipAddress, userAgent);\n\n  db.logAuthEvent('oidc_register', userId, ipAddress, { provider: provider.name, providerUserId: userInfo.sub, role: newUser.role, isFirstUser: wasFirst });\n  return { success: true, userId, sessionToken, isNewUser: true, postLoginRedirect };\n}\n\nconst MAX_SESSIONS_PER_USER = 10;\n\nasync function createUserSessionToken(\n  userId: string,\n  ipAddress: string,\n  userAgent: string\n): Promise<string> {\n  const token = generateSecureToken(32);\n  const tokenHash = await hashToken(token);\n  const now = new Date();\n  const expiresAt = new Date(now.getTime() + 30 * 24 * 60 * 60 * 1000);\n\n  const existing = db.getSessionsByUserId(userId);\n  if (existing.length >= MAX_SESSIONS_PER_USER) {\n    const oldest = existing.slice(MAX_SESSIONS_PER_USER - 1);\n    for (const s of oldest) db.deleteSession(s.id);\n  }\n\n  db.createUserSession({\n    id: crypto.randomUUID(),\n    userId,\n    tokenHash,\n    ipAddress,\n    userAgent: userAgent?.substring(0, 500) || null,\n    expiresAt: expiresAt.toISOString(),\n    createdAt: now.toISOString()\n  });\n\n  return token;\n}\n\nexport async function hashToken(token: string): Promise<string> {\n  const encoder = new TextEncoder();\n  const data = encoder.encode(token);\n  const hash = await crypto.subtle.digest('SHA-256', data);\n  return Array.from(new Uint8Array(hash), b => b.toString(16).padStart(2, '0')).join('');\n}\n\nexport function generateSecureToken(bytes: number = 32): string {\n  const array = new Uint8Array(bytes);\n  crypto.getRandomValues(array);\n  return Array.from(array, b => b.toString(16).padStart(2, '0')).join('');\n}\n\nexport async function validateUserSessionToken(\n  token: string,\n  currentIP?: string,\n  currentUserAgent?: string\n): Promise<db.User | null> {\n  const tokenHash = await hashToken(token);\n  const session = db.getSessionByTokenHash(tokenHash);\n\n  if (!session) return null;\n\n  if (new Date(session.expiresAt) < new Date()) {\n    db.deleteSession(session.id);\n    return null;\n  }\n\n  const user = db.getUserById(session.userId);\n  if (!user || !user.isActive) return null;\n\n  if (currentIP && session.ipAddress && session.ipAddress !== currentIP) {\n    db.logAuthEvent('session_ip_change', session.userId, currentIP, {\n      originalIP: session.ipAddress,\n      newIP: currentIP,\n      sessionId: session.id\n    });\n  }\n\n  return user;\n}\n\nexport async function refreshOidcTokens(linkId: string): Promise<{\n  success: boolean;\n  accessToken?: string;\n  error?: string;\n}> {\n  const link = db.getOidcLinkById(linkId);\n  if (!link || !link.refreshTokenEncrypted) {\n    return { success: false, error: 'No refresh token available' };\n  }\n\n  const provider = db.getOidcProviderByName(link.provider);\n  if (!provider) {\n    return { success: false, error: 'Provider not found' };\n  }\n\n  let tokenUrl = provider.tokenUrl;\n  if (!tokenUrl && provider.issuerUrl) {\n    const discovery = await discoverOidcEndpoints(provider.issuerUrl);\n    if (discovery) {\n      tokenUrl = discovery.token_endpoint;\n    }\n  }\n\n  if (!tokenUrl) {\n    return { success: false, error: 'No token endpoint configured for provider' };\n  }\n\n  let refreshToken: string;\n  try {\n    refreshToken = await decryptSecret(link.refreshTokenEncrypted);\n  } catch {\n    return { success: false, error: 'Failed to decrypt refresh token' };\n  }\n\n  let clientSecret: string | null = null;\n  if (provider.clientSecretEncrypted) {\n    try {\n      clientSecret = await decryptSecret(provider.clientSecretEncrypted);\n    } catch {\n      return { success: false, error: 'Failed to decrypt client secret' };\n    }\n  }\n\n  try {\n    const params = new URLSearchParams({\n      grant_type: 'refresh_token',\n      refresh_token: refreshToken,\n      client_id: provider.clientId\n    });\n\n    if (clientSecret) {\n      params.set('client_secret', clientSecret);\n    }\n\n    const tokenRes = await fetch(tokenUrl, {\n      method: 'POST',\n      headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n      body: params.toString()\n    });\n\n    if (!tokenRes.ok) {\n      const errText = await tokenRes.text();\n      logOidcError('Token refresh failed', errText.substring(0, 200).replace(/access_token[^&]*/gi, 'access_token=[REDACTED]').replace(/refresh_token[^&]*/gi, 'refresh_token=[REDACTED]'));\n      return { success: false, error: 'Token refresh failed' };\n    }\n\n    const tokens = await tokenRes.json();\n\n    const encryptedAccess = tokens.access_token ? await encryptSecret(tokens.access_token) : null;\n    const encryptedRefresh = tokens.refresh_token ? await encryptSecret(tokens.refresh_token) : link.refreshTokenEncrypted;\n    const expiresAt = tokens.expires_in\n      ? new Date(Date.now() + tokens.expires_in * 1000).toISOString()\n      : null;\n\n    db.updateOidcLinkTokens(link.id, encryptedAccess, encryptedRefresh, expiresAt);\n\n    return { success: true, accessToken: tokens.access_token };\n  } catch (e: any) {\n    logOidcError('Token refresh error', e);\n    return { success: false, error: 'Token refresh error' };\n  }\n}\n\nexport async function revokeUserOidcTokens(userId: string): Promise<void> {\n  const links = db.getOidcLinksByUser(userId);\n  for (const link of links) {\n    try {\n      const provider = db.getOidcProviderByName(link.provider);\n      if (!provider) continue;\n\n      let revocationUrl: string | undefined;\n      if (provider.issuerUrl) {\n        const discovery = await discoverOidcEndpoints(provider.issuerUrl);\n        revocationUrl = discovery?.revocation_endpoint ?? undefined;\n      }\n      if (!revocationUrl) continue;\n\n      let clientSecret: string | null = null;\n      if (provider.clientSecretEncrypted) {\n        try { clientSecret = await decryptSecret(provider.clientSecretEncrypted); } catch { continue; }\n      }\n\n      const tokensToRevoke: { token: string; hint: string }[] = [];\n\n      if (link.accessTokenEncrypted) {\n        try {\n          const accessToken = await decryptSecret(link.accessTokenEncrypted);\n          tokensToRevoke.push({ token: accessToken, hint: 'access_token' });\n        } catch {}\n      }\n      if (link.refreshTokenEncrypted) {\n        try {\n          const refreshToken = await decryptSecret(link.refreshTokenEncrypted);\n          tokensToRevoke.push({ token: refreshToken, hint: 'refresh_token' });\n        } catch {}\n      }\n\n      for (const { token, hint } of tokensToRevoke) {\n        try {\n          const params = new URLSearchParams({\n            token,\n            token_type_hint: hint,\n            client_id: provider.clientId\n          });\n          if (clientSecret) params.set('client_secret', clientSecret);\n\n          await fetch(revocationUrl, {\n            method: 'POST',\n            headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n            body: params.toString()\n          });\n        } catch (e: any) {\n          logOidcError(`Token revocation failed for provider ${link.provider}`, e);\n        }\n      }\n\n      db.updateOidcLinkTokens(link.id, null, null, null);\n    } catch (e: any) {\n      logOidcError(`Failed to revoke tokens for OIDC link ${link.id}`, e);\n    }\n  }\n}\n\nexport interface AuthSettings {\n  authMode: 'open' | 'registration' | 'oidc_only' | 'invite_only' | 'closed';\n  allowGuestRoomCreation: boolean;\n  allowGuestRoomJoin: boolean;\n  allowRoomCreatorGuestSetting: boolean;\n  oidcEmailMatching: boolean;\n  requireEmailVerification: boolean;\n  allowMagicLinkLogin: boolean;\n  shareButtonEnabled: boolean;\n  productionMode: boolean;\n  idTokenMaxAgeHours: number;\n  emailRateLimitWindowSeconds: number;\n  emailRateLimitMaxAttempts: number;\n}\n\nexport function getAuthSettings(): AuthSettings {\n  const defaults: AuthSettings = {\n    authMode: 'open',\n    allowGuestRoomCreation: true,\n    allowGuestRoomJoin: true,\n    allowRoomCreatorGuestSetting: true,\n    oidcEmailMatching: false,\n    requireEmailVerification: false,\n    allowMagicLinkLogin: true,\n    shareButtonEnabled: true,\n    productionMode: false,\n    idTokenMaxAgeHours: 0.167,\n    emailRateLimitWindowSeconds: 300,\n    emailRateLimitMaxAttempts: 3\n  };\n\n  try {\n    const stored = db.getSetting('authSettings');\n    if (stored) {\n      return { ...defaults, ...JSON.parse(stored) };\n    }\n  } catch (e: any) {\n    console.error('[OIDC] Failed to parse auth settings:', e.message);\n  }\n\n  return defaults;\n}\n\nexport function saveAuthSettings(settings: Partial<AuthSettings>): void {\n  const current = getAuthSettings();\n  const merged = { ...current, ...settings };\n  db.setSetting('authSettings', JSON.stringify(merged));\n}\n\nfunction getIdTokenMaxAgeMs(): number {\n  const settings = getAuthSettings();\n  return settings.idTokenMaxAgeHours * 60 * 60 * 1000;\n}\n\nfunction sanitizeIconUrl(url: string | null): string | null {\n  if (!url) return null;\n  try {\n    const parsed = new URL(url);\n    if (parsed.protocol === 'https:' || parsed.protocol === 'http:') return url;\n    return null;\n  } catch {\n    return null;\n  }\n}\n\nexport function getActiveProviders(): { id: string; name: string; iconUrl: string | null }[] {\n  return db.listActiveOidcProviders().map(p => ({\n    id: p.id,\n    name: p.name,\n    iconUrl: sanitizeIconUrl(p.iconUrl)\n  }));\n}\n\nexport function getSessionCookie(name: string, value: string, maxAge: number = 2592000): string {\n  const settings = getAuthSettings();\n  if (settings.productionMode) {\n    return `__Host-${name}=${value}; Path=/; HttpOnly; SameSite=Strict; Secure; Partitioned; Max-Age=${maxAge}`;\n  }\n  return `${name}=${value}; Path=/; HttpOnly; SameSite=Strict; Max-Age=${maxAge}`;\n}\n\nexport function getClearCookie(name: string): string {\n  const settings = getAuthSettings();\n  if (settings.productionMode) {\n    return `__Host-${name}=; Path=/; HttpOnly; SameSite=Strict; Secure; Partitioned; Max-Age=0`;\n  }\n  return `${name}=; Path=/; HttpOnly; SameSite=Strict; Max-Age=0`;\n}\n\nexport function getSessionCookieName(baseName: string): string {\n  const settings = getAuthSettings();\n  return settings.productionMode ? `__Host-${baseName}` : baseName;\n}\n\nfunction cleanupOldestCsrfTokens(): void {\n  const count = db.countCsrfTokens();\n  if (count <= MAX_CSRF_TOKENS) return;\n  db.deleteOldestCsrfTokens(count - MAX_CSRF_TOKENS + 100);\n}\n\nexport function generateCsrfToken(): string {\n  cleanupOldestCsrfTokens();\n\n  const token = generateSecureToken(32);\n  const now = new Date();\n  const expiresAt = new Date(now.getTime() + CSRF_TOKEN_TTL_MS);\n\n  db.createCsrfToken({\n    token,\n    used: false,\n    createdAt: now.toISOString(),\n    expiresAt: expiresAt.toISOString()\n  });\n\n  return token;\n}\n\nexport function validateCsrfToken(token: string): boolean {\n  if (!token) return false;\n\n  const data = db.getCsrfToken(token);\n  if (!data) return false;\n\n  if (new Date(data.expiresAt) < new Date()) {\n    db.deleteCsrfToken(token);\n    return false;\n  }\n\n  if (data.used) {\n    db.deleteCsrfToken(token);\n    return false;\n  }\n\n  db.markCsrfTokenUsed(token);\n  return true;\n}\n\nexport function getCsrfCookie(token: string): string {\n  const settings = getAuthSettings();\n  const secure = settings.productionMode ? '; Secure' : '';\n  return `csrf_token=${token}; Path=/; HttpOnly; SameSite=Strict${secure}; Max-Age=3600`;\n}\n\nexport function validateRedirectUrl(redirectUrl: string | null, baseUrl: string): string {\n  if (!redirectUrl) {\n    return '/';\n  }\n\n  const sanitized = redirectUrl.trim().toLowerCase();\n\n  const dangerousSchemes = ['javascript:', 'data:', 'vbscript:', 'file:', 'blob:'];\n  for (const scheme of dangerousSchemes) {\n    if (sanitized.startsWith(scheme)) {\n      return '/';\n    }\n  }\n\n  const original = redirectUrl.trim();\n\n  if (original.startsWith('/') && !original.startsWith('//')) {\n    if (original.includes('..') || original.includes('\\\\')) {\n      return '/';\n    }\n    return original;\n  }\n\n  try {\n    const redirectOrigin = new URL(original).origin;\n    const baseOrigin = new URL(baseUrl).origin;\n\n    if (redirectOrigin === baseOrigin) {\n      const url = new URL(original);\n      return url.pathname + url.search + url.hash;\n    }\n  } catch {\n  }\n\n  return '/';\n}\n"
  },
  {
    "path": "theonefile_verse/src/rate-limit.ts",
    "content": "import * as redis from \"./redis\";\nimport * as db from \"./database\";\nimport * as oidc from \"./oidc\";\nimport { type InstanceSettings } from \"./config\";\n\ninterface RateLimitEntry {\n  count: number;\n  resetAt: number;\n}\n\nconst MAX_RATE_LIMIT_ENTRIES = 10000;\nconst rateLimitStore = new Map<string, RateLimitEntry>();\n\ninterface WsTokenBucket {\n  tokens: number;\n  lastRefill: number;\n}\n\nconst WS_RATE_LIMITS = {\n  state: { bucketSize: 10, refillRate: 2 },\n  chat: { bucketSize: 5, refillRate: 1 },\n  cursor: { bucketSize: 30, refillRate: 15 },\n  typing: { bucketSize: 5, refillRate: 1 },\n  presence: { bucketSize: 20, refillRate: 5 },\n  default: { bucketSize: 20, refillRate: 5 }\n};\n\nconst MAX_WS_RATE_LIMIT_BUCKETS = 50000;\nconst wsRateLimitBuckets = new Map<string, WsTokenBucket>();\n\nexport function checkWsRateLimit(connectionId: string, messageType: string): boolean {\n  const limits = WS_RATE_LIMITS[messageType as keyof typeof WS_RATE_LIMITS] || WS_RATE_LIMITS.default;\n  const key = `${connectionId}:${messageType}`;\n  const now = Date.now();\n  let bucket = wsRateLimitBuckets.get(key);\n  if (!bucket) {\n    bucket = { tokens: limits.bucketSize, lastRefill: now };\n    wsRateLimitBuckets.set(key, bucket);\n  }\n  const elapsed = (now - bucket.lastRefill) / 1000;\n  bucket.tokens = Math.min(limits.bucketSize, bucket.tokens + elapsed * limits.refillRate);\n  bucket.lastRefill = now;\n  if (bucket.tokens >= 1) {\n    bucket.tokens -= 1;\n    return true;\n  }\n  return false;\n}\n\nexport async function checkRateLimit(ip: string, endpoint: string, settings: InstanceSettings, maxOverride?: number, windowOverride?: number): Promise<boolean> {\n  if (!settings.rateLimitEnabled) return true;\n  const key = `${ip}:${endpoint}`;\n  const maxAttempts = maxOverride ?? settings.rateLimitMaxAttempts;\n  const windowSeconds = windowOverride ?? settings.rateLimitWindow;\n  if (redis.isRedisConnected()) {\n    return await redis.checkRateLimitRedis(key, maxAttempts, windowSeconds);\n  }\n  if (rateLimitStore.size > MAX_RATE_LIMIT_ENTRIES) {\n    const now = Date.now();\n    for (const [k, v] of rateLimitStore.entries()) {\n      if (now > v.resetAt) rateLimitStore.delete(k);\n    }\n    if (rateLimitStore.size > MAX_RATE_LIMIT_ENTRIES) {\n      const entries = [...rateLimitStore.entries()].sort((a, b) => a[1].resetAt - b[1].resetAt);\n      const toRemove = entries.length - Math.floor(MAX_RATE_LIMIT_ENTRIES * 0.75);\n      for (let i = 0; i < toRemove; i++) rateLimitStore.delete(entries[i][0]);\n    }\n  }\n  const now = Date.now();\n  const entry = rateLimitStore.get(key);\n  const window = windowSeconds * 1000;\n  if (!entry || now > entry.resetAt) {\n    rateLimitStore.set(key, { count: 1, resetAt: now + window });\n    return true;\n  }\n  if (entry.count >= maxAttempts) {\n    return false;\n  }\n  entry.count++;\n  return true;\n}\n\nexport async function checkEmailRateLimit(email: string, action: string, settings: InstanceSettings): Promise<boolean> {\n  if (!settings.rateLimitEnabled) return true;\n  const authSettings = oidc.getAuthSettings();\n  const windowSeconds = authSettings.emailRateLimitWindowSeconds;\n  const maxAttempts = authSettings.emailRateLimitMaxAttempts;\n  const normalizedEmail = email.toLowerCase().trim();\n  if (redis.isRedisConnected()) {\n    const key = `email:${normalizedEmail}:${action}`;\n    return await redis.checkRateLimitRedis(key, maxAttempts, windowSeconds);\n  }\n  const currentCount = db.countEmailRateLimitAttempts(normalizedEmail, action, windowSeconds);\n  if (currentCount >= maxAttempts) {\n    return false;\n  }\n  db.recordEmailRateLimit(normalizedEmail, action);\n  return true;\n}\n\nconst wsConnectionCounts = new Map<string, { count: number; resetAt: number }>();\nconst MAX_WS_CONNECTIONS_PER_IP = 50;\nconst WS_CONNECTION_WINDOW = 3600 * 1000;\n\nexport function checkWsConnectionLimit(clientIp: string): boolean {\n  const now = Date.now();\n  const wsEntry = wsConnectionCounts.get(clientIp);\n  if (wsEntry && now < wsEntry.resetAt && wsEntry.count >= MAX_WS_CONNECTIONS_PER_IP) {\n    return false;\n  }\n  if (!wsEntry || now > wsEntry.resetAt) {\n    wsConnectionCounts.set(clientIp, { count: 1, resetAt: now + WS_CONNECTION_WINDOW });\n  } else {\n    wsEntry.count++;\n  }\n  return true;\n}\n\nexport function startRateLimitCleanupIntervals(): void {\n  setInterval(() => {\n    const now = Date.now();\n    const staleThreshold = 60 * 1000;\n    for (const [key, bucket] of wsRateLimitBuckets.entries()) {\n      if (now - bucket.lastRefill > staleThreshold) {\n        wsRateLimitBuckets.delete(key);\n      }\n    }\n    if (wsRateLimitBuckets.size > MAX_WS_RATE_LIMIT_BUCKETS) {\n      const entries = [...wsRateLimitBuckets.entries()].sort((a, b) => a[1].lastRefill - b[1].lastRefill);\n      const toRemove = entries.length - Math.floor(MAX_WS_RATE_LIMIT_BUCKETS * 0.75);\n      for (let i = 0; i < toRemove; i++) wsRateLimitBuckets.delete(entries[i][0]);\n    }\n  }, 30 * 1000);\n\n  setInterval(() => {\n    const now = Date.now();\n    for (const [key, entry] of rateLimitStore.entries()) {\n      if (now > entry.resetAt) {\n        rateLimitStore.delete(key);\n      }\n    }\n    db.cleanupEmailRateLimits(3600);\n  }, 60 * 1000);\n\n  setInterval(() => {\n    const now = Date.now();\n    for (const [ip, entry] of wsConnectionCounts.entries()) {\n      if (now > entry.resetAt) wsConnectionCounts.delete(ip);\n    }\n  }, 5 * 60 * 1000);\n}\n"
  },
  {
    "path": "theonefile_verse/src/redis.ts",
    "content": "import { createClient, RedisClientType } from \"redis\";\n\nconst REDIS_URL = process.env.REDIS_URL || \"redis://localhost:6379\";\n\nlet client: RedisClientType | null = null;\nlet subscriber: RedisClientType | null = null;\nlet isConnected = false;\n\nconst messageHandlers = new Map<string, Set<(message: string, channel: string) => void>>();\n\nexport async function connectRedis(): Promise<boolean> {\n  try {\n    client = createClient({ url: REDIS_URL });\n    subscriber = client.duplicate();\n\n    client.on(\"error\", (err) => {\n      console.error(\"[Redis] Client error:\", err.message);\n      isConnected = false;\n    });\n\n    subscriber.on(\"error\", (err) => {\n      console.error(\"[Redis] Subscriber error:\", err.message);\n    });\n\n    await client.connect();\n    await subscriber.connect();\n\n    isConnected = true;\n    console.log(\"[Redis] Connected to\", REDIS_URL);\n\n    subscriber.on(\"message\", (channel, message) => {\n      const handlers = messageHandlers.get(channel);\n      if (handlers) {\n        handlers.forEach((handler) => handler(message, channel));\n      }\n    });\n\n    return true;\n  } catch (err: any) {\n    console.error(\"[Redis] Connection failed:\", err.message);\n    isConnected = false;\n    return false;\n  }\n}\n\nexport function isRedisConnected(): boolean {\n  return isConnected && client !== null;\n}\n\nconst RATE_LIMIT_LUA = `\nlocal c = redis.call('INCR', KEYS[1])\nif c == 1 then redis.call('EXPIRE', KEYS[1], ARGV[1]) end\nreturn c\n`;\n\nexport async function checkRateLimitRedis(\n  key: string,\n  maxAttempts: number,\n  windowSeconds: number\n): Promise<boolean> {\n  if (!client || !isConnected) return false;\n\n  try {\n    const redisKey = `ratelimit:${key}`;\n    const current = await client.eval(RATE_LIMIT_LUA, { keys: [redisKey], arguments: [String(windowSeconds)] }) as number;\n    return current <= maxAttempts;\n  } catch (err: any) {\n    console.error(\"[Redis] Rate limit check failed, denying request (fail-closed):\", err.message);\n    return false;\n  }\n}\n\nexport async function setSessionToken(\n  token: string,\n  data: { type: string; createdAt: number } | Record<string, any>,\n  ttlSeconds: number = 604800\n): Promise<boolean> {\n  if (!client || !isConnected) return false;\n\n  try {\n    await client.setEx(`session:${token}`, ttlSeconds, JSON.stringify(data));\n    return true;\n  } catch (err: any) {\n    console.error(\"[Redis] Set session token failed:\", err.message);\n    return false;\n  }\n}\n\nexport async function getSessionToken(token: string): Promise<{ type: string; createdAt: number } | null> {\n  if (!client || !isConnected) return null;\n\n  try {\n    const data = await client.get(`session:${token}`);\n    return data ? JSON.parse(data) : null;\n  } catch (err: any) {\n    console.error(\"[Redis] Get session token failed:\", err.message);\n    return null;\n  }\n}\n\nexport async function deleteSessionToken(token: string): Promise<boolean> {\n  if (!client || !isConnected) return false;\n\n  try {\n    await client.del(`session:${token}`);\n    return true;\n  } catch (err: any) {\n    console.error(\"[Redis] Delete session token failed:\", err.message);\n    return false;\n  }\n}\n\nexport async function setUserPresence(\n  roomId: string,\n  userId: string,\n  userData: any,\n  ttlSeconds: number = 300\n): Promise<boolean> {\n  if (!client || !isConnected) return false;\n\n  try {\n    const userKey = `presence:${roomId}:${userId}`;\n    await client.setEx(userKey, ttlSeconds, JSON.stringify(userData));\n    return true;\n  } catch (err: any) {\n    console.error(\"[Redis] Set user presence failed:\", err.message);\n    return false;\n  }\n}\n\nexport async function getUserPresence(roomId: string, userId: string): Promise<any | null> {\n  if (!client || !isConnected) return null;\n\n  try {\n    const data = await client.get(`presence:${roomId}:${userId}`);\n    return data ? JSON.parse(data) : null;\n  } catch (err: any) {\n    console.error(\"[Redis] Get user presence failed:\", err.message);\n    return null;\n  }\n}\n\nexport async function getAllUserPresence(roomId: string): Promise<Map<string, any>> {\n  const result = new Map<string, any>();\n  if (!client || !isConnected) return result;\n\n  try {\n    const pattern = `presence:${roomId}:*`;\n    let cursor = 0;\n    const keys: string[] = [];\n    do {\n      const reply = await client.scan(cursor, { MATCH: pattern, COUNT: 100 });\n      cursor = reply.cursor;\n      keys.push(...reply.keys);\n    } while (cursor !== 0);\n    if (keys.length === 0) return result;\n    const values = await client.mGet(keys);\n    for (let i = 0; i < keys.length; i++) {\n      const userId = keys[i].split(':').slice(2).join(':');\n      if (values[i]) {\n        try { result.set(userId, JSON.parse(values[i])); } catch (e: any) { console.error(\"[Redis] Presence JSON parse failed:\", e.message); }\n      }\n    }\n  } catch (err: any) {\n    console.error(\"[Redis] Get all user presence failed:\", err.message);\n  }\n\n  return result;\n}\n\nexport async function removeUserPresence(roomId: string, userId: string): Promise<boolean> {\n  if (!client || !isConnected) return false;\n\n  try {\n    await client.del(`presence:${roomId}:${userId}`);\n    return true;\n  } catch (err: any) {\n    console.error(\"[Redis] Remove user presence failed:\", err.message);\n    return false;\n  }\n}\n\nexport async function setRoomStateCache(roomId: string, state: any, ttlSeconds: number = 3600): Promise<boolean> {\n  if (!client || !isConnected) return false;\n\n  try {\n    await client.setEx(`roomstate:${roomId}`, ttlSeconds, JSON.stringify(state));\n    return true;\n  } catch (err: any) {\n    console.error(\"[Redis] Set room state cache failed:\", err.message);\n    return false;\n  }\n}\n\nexport async function getRoomStateCache(roomId: string): Promise<any | null> {\n  if (!client || !isConnected) return null;\n\n  try {\n    const data = await client.get(`roomstate:${roomId}`);\n    return data ? JSON.parse(data) : null;\n  } catch (err: any) {\n    console.error(\"[Redis] Get room state cache failed:\", err.message);\n    return null;\n  }\n}\n\nexport async function deleteRoomStateCache(roomId: string): Promise<boolean> {\n  if (!client || !isConnected) return false;\n\n  try {\n    await client.del(`roomstate:${roomId}`);\n    return true;\n  } catch (err: any) {\n    console.error(\"[Redis] Delete room state cache failed:\", err.message);\n    return false;\n  }\n}\n\nexport async function publishMessage(channel: string, message: string): Promise<boolean> {\n  if (!client || !isConnected) return false;\n\n  try {\n    await client.publish(channel, message);\n    return true;\n  } catch (err: any) {\n    console.error(\"[Redis] Publish message failed:\", err.message);\n    return false;\n  }\n}\n\nexport async function subscribeToChannel(\n  channel: string,\n  handler: (message: string, channel: string) => void\n): Promise<void> {\n  if (!subscriber || !isConnected) return;\n\n  try {\n    if (!messageHandlers.has(channel)) {\n      messageHandlers.set(channel, new Set());\n      await subscriber.subscribe(channel, (message, ch) => {\n        const handlers = messageHandlers.get(ch);\n        if (handlers) {\n          handlers.forEach((h) => h(message, ch));\n        }\n      });\n    }\n    messageHandlers.get(channel)!.add(handler);\n  } catch (err: any) {\n    console.error(\"[Redis] Subscribe to channel failed:\", err.message);\n  }\n}\n\nexport async function unsubscribeFromChannel(\n  channel: string,\n  handler?: (message: string, channel: string) => void\n): Promise<void> {\n  if (!subscriber || !isConnected) return;\n\n  try {\n    if (handler) {\n      const handlers = messageHandlers.get(channel);\n      if (handlers) {\n        handlers.delete(handler);\n        if (handlers.size === 0) {\n          messageHandlers.delete(channel);\n          await subscriber.unsubscribe(channel);\n        }\n      }\n    } else {\n      messageHandlers.delete(channel);\n      await subscriber.unsubscribe(channel);\n    }\n  } catch (err: any) {\n    console.error(\"[Redis] Unsubscribe from channel failed:\", err.message);\n  }\n}\n\nexport async function publishToRoom(roomId: string, message: any): Promise<void> {\n  await publishMessage(`room:${roomId}`, JSON.stringify(message));\n}\n\nexport async function subscribeToRoom(\n  roomId: string,\n  handler: (message: any) => void\n): Promise<() => Promise<void>> {\n  const wrappedHandler = (msg: string) => {\n    try {\n      handler(JSON.parse(msg));\n    } catch (err: any) {\n      console.error(\"[Redis] Room message parse failed:\", err.message);\n    }\n  };\n\n  await subscribeToChannel(`room:${roomId}`, wrappedHandler);\n\n  return async () => {\n    await unsubscribeFromChannel(`room:${roomId}`, wrappedHandler);\n  };\n}\n\nexport async function incrementCounter(key: string): Promise<number> {\n  if (!client || !isConnected) return 0;\n\n  try {\n    return await client.incr(`counter:${key}`);\n  } catch (err: any) {\n    console.error(\"[Redis] Increment counter failed:\", err.message);\n    return 0;\n  }\n}\n\nexport async function getCounter(key: string): Promise<number> {\n  if (!client || !isConnected) return 0;\n\n  try {\n    const value = await client.get(`counter:${key}`);\n    return value ? parseInt(value, 10) : 0;\n  } catch (err: any) {\n    console.error(\"[Redis] Get counter failed:\", err.message);\n    return 0;\n  }\n}\n\nexport async function setWithExpiry(key: string, value: string, ttlSeconds: number): Promise<boolean> {\n  if (!client || !isConnected) return false;\n\n  try {\n    await client.setEx(key, ttlSeconds, value);\n    return true;\n  } catch (err: any) {\n    console.error(\"[Redis] Set with expiry failed:\", err.message);\n    return false;\n  }\n}\n\nexport async function getValue(key: string): Promise<string | null> {\n  if (!client || !isConnected) return null;\n\n  try {\n    return await client.get(key);\n  } catch (err: any) {\n    console.error(\"[Redis] Get value failed:\", err.message);\n    return null;\n  }\n}\n\nexport async function deleteKey(key: string): Promise<boolean> {\n  if (!client || !isConnected) return false;\n\n  try {\n    await client.del(key);\n    return true;\n  } catch (err: any) {\n    console.error(\"[Redis] Delete key failed:\", err.message);\n    return false;\n  }\n}\n\nexport async function ping(): Promise<boolean> {\n  if (!client || !isConnected) return false;\n  try {\n    const result = await client.ping();\n    return result === \"PONG\";\n  } catch {\n    return false;\n  }\n}\n\nexport async function disconnectRedis(): Promise<void> {\n  try {\n    if (subscriber) {\n      await subscriber.quit();\n      subscriber = null;\n    }\n    if (client) {\n      await client.quit();\n      client = null;\n    }\n    isConnected = false;\n    console.log(\"[Redis] Disconnected\");\n  } catch (err: any) {\n    console.error(\"[Redis] Disconnect failed:\", err.message);\n  }\n}\n\nexport function getRedisClient(): RedisClientType | null {\n  return client;\n}\n"
  },
  {
    "path": "theonefile_verse/src/rooms.ts",
    "content": "import { existsSync, mkdirSync } from \"fs\";\nimport { unlink } from \"fs/promises\";\nimport { join } from \"path\";\nimport * as db from \"./database\";\nimport * as redis from \"./redis\";\nimport * as auth from \"./auth\";\nimport { DATA_DIR, ROOMS_DIR, getSettings, updateSettings, isValidUUID, type InstanceSettings } from \"./config\";\n\nif (!existsSync(DATA_DIR)) mkdirSync(DATA_DIR, { recursive: true });\nif (!existsSync(ROOMS_DIR)) mkdirSync(ROOMS_DIR, { recursive: true });\n\nexport interface Room {\n  id: string;\n  created: string;\n  lastActivity: string;\n  creatorId: string;\n  passwordHash: string | null;\n  destruct: { mode: \"time\" | \"empty\" | \"never\"; value: number };\n  topology: any;\n  ownerUserId: string | null;\n  allowGuests: boolean;\n}\n\nexport interface RoomMeta {\n  connectedUsers: number;\n  destructTimer?: Timer;\n}\n\nexport const roomMeta: Map<string, RoomMeta> = new Map();\nexport const roomConnections: Map<string, Set<any>> = new Map();\nexport const roomUsers: Map<string, Map<string, any>> = new Map();\nexport const roomUsedNames: Map<string, Map<string, string>> = new Map();\nexport const roomChatHistory: Map<string, any[]> = new Map();\n\nexport async function hashPassword(password: string): Promise<string> {\n  return await auth.hashPassword(password);\n}\n\nexport async function verifyPassword(password: string, hash: string): Promise<boolean> {\n  return await auth.verifyPassword(password, hash);\n}\n\nexport function loadRoom(id: string): Room | null {\n  if (!isValidUUID(id)) return null;\n  return db.getRoom(id);\n}\n\nexport function saveRoom(room: Room): void {\n  if (!isValidUUID(room.id)) return;\n  const existing = db.getRoom(room.id);\n  if (existing) {\n    db.updateRoom(room.id, room.lastActivity, room.topology);\n  } else {\n    db.createRoom(room);\n  }\n  if (redis.isRedisConnected() && room.topology) {\n    redis.setRoomStateCache(room.id, room.topology, 3600);\n  }\n}\n\nexport function deleteRoomData(id: string): boolean {\n  const result = db.deleteRoom(id);\n  if (result) {\n    roomMeta.delete(id);\n    roomUsedNames.delete(id);\n    roomChatHistory.delete(id);\n    if (redis.isRedisConnected()) {\n      redis.deleteRoomStateCache(id);\n    }\n  }\n  return result;\n}\n\nexport function scheduleDestruction(roomId: string, delayMs: number): void {\n  const meta = roomMeta.get(roomId) || { connectedUsers: 0 };\n  if (meta.destructTimer) clearTimeout(meta.destructTimer);\n  meta.destructTimer = setTimeout(() => {\n    const room = loadRoom(roomId);\n    if (room && room.destruct.mode === \"time\") {\n      console.log(`[Room] ${roomId} self destructed`);\n      deleteRoomData(roomId);\n    }\n  }, delayMs);\n  roomMeta.set(roomId, meta);\n}\n\nexport function resetDestructionTimer(roomId: string): void {\n  const room = loadRoom(roomId);\n  if (room && room.destruct.mode === \"time\") {\n    scheduleDestruction(roomId, room.destruct.value);\n  }\n}\n\nexport let theOneFileHtml = \"\";\nconst theOneFilePath = join(process.cwd(), \"public\", \"theonefile.html\");\n\nexport function extractThemePresets(): Array<{key: string, label: string}> {\n  if (!theOneFileHtml) return [];\n  const themes: Array<{key: string, label: string}> = [];\n  const selectMatch = theOneFileHtml.match(/id=\"welcome-theme-select\"[^>]*>([\\s\\S]*?)<\\/select>/);\n  if (selectMatch) {\n    const optionRegex = /<option\\s+value=\"(\\w+)\"[^>]*>([^<]+)<\\/option>/g;\n    let m;\n    while ((m = optionRegex.exec(selectMatch[1])) !== null) {\n      if (m[1]) themes.push({ key: m[1], label: m[2] });\n    }\n  }\n  if (themes.length === 0) {\n    const presetsMatch = theOneFileHtml.match(/THEME_PRESETS\\s*=\\s*\\{([\\s\\S]*?)\\n\\};/);\n    if (presetsMatch) {\n      const keyRegex = /^\\s+(\\w+)\\s*:/gm;\n      let km;\n      while ((km = keyRegex.exec(presetsMatch[1])) !== null) {\n        themes.push({ key: km[1], label: km[1] });\n      }\n    }\n  }\n  return themes;\n}\n\nexport const GITHUB_RAW_URL = \"https://raw.githubusercontent.com/gelatinescreams/The-One-File/main/theonefile-networkening.html\";\n\nexport let currentFileVersion = \"\";\n\nexport function getExpectedTheOneFileHash(): string | null {\n  return db.getSetting(\"theOneFileHash\");\n}\n\nexport function setExpectedTheOneFileHash(hash: string): void {\n  db.setSetting(\"theOneFileHash\", hash);\n}\n\nexport function extractVersionFromHtml(html: string): string {\n  const match = html.match(/THE_ONE_FILE_VERSION\\s*=\\s*[\"']([^\"']+)[\"']/);\n  return match ? match[1].replace(/^[\"']+|[\"']+$/g, '') : \"unknown\";\n}\n\nexport function isNewerVersion(latest: string, current: string): boolean {\n  const parse = (v: string) => v.replace(/^v/i, '').split('.').map(n => parseInt(n) || 0);\n  const l = parse(latest);\n  const c = parse(current);\n  for (let i = 0; i < Math.max(l.length, c.length); i++) {\n    const lv = l[i] || 0;\n    const cv = c[i] || 0;\n    if (lv > cv) return true;\n    if (lv < cv) return false;\n  }\n  return false;\n}\n\nexport async function computeSha256Hash(content: string): Promise<string> {\n  const encoder = new TextEncoder();\n  const data = encoder.encode(content);\n  const hashBuffer = await crypto.subtle.digest(\"SHA-256\", data);\n  const hashArray = Array.from(new Uint8Array(hashBuffer));\n  return hashArray.map(b => b.toString(16).padStart(2, \"0\")).join(\"\");\n}\n\nexport async function fetchLatestFromGitHub(): Promise<boolean> {\n  try {\n    console.log(\"[Update] Fetching from GitHub...\");\n    const res = await fetch(GITHUB_RAW_URL);\n    if (!res.ok) return false;\n    const html = await res.text();\n    const downloadedHash = await computeSha256Hash(html);\n    const expectedHash = getExpectedTheOneFileHash();\n    if (expectedHash) {\n      if (downloadedHash !== expectedHash) {\n        console.error(`[Update] INTEGRITY CHECK FAILED!`);\n        console.error(`[Update] Expected: ${expectedHash}`);\n        console.error(`[Update] Got:      ${downloadedHash}`);\n        console.error(`[Update] File rejected - possible tampering or update. Admin must update the expected hash.`);\n        return false;\n      }\n      console.log(`[Update] Integrity verified (SHA-256: ${downloadedHash.substring(0, 16)}...)`);\n    } else {\n      console.log(`[Update] No integrity hash set. Current file hash: ${downloadedHash}`);\n      console.log(`[Update] Admin can set this hash in settings to enable integrity checking.`);\n    }\n    const previousVersion = currentFileVersion;\n    await Bun.write(theOneFilePath, html);\n    theOneFileHtml = html;\n    currentFileVersion = extractVersionFromHtml(html);\n    db.setSetting(\"lastUpdateTimestamp\", new Date().toISOString());\n    db.setSetting(\"latestFetchedVersion\", currentFileVersion);\n    console.log(`[Update] Downloaded v${currentFileVersion} (${(html.length / 1024).toFixed(1)}KB)${previousVersion && previousVersion !== currentFileVersion ? ` from v${previousVersion}` : ''}`);\n    return true;\n  } catch (e: any) { console.error(\"[Update]\", e.message); return false; }\n}\n\nexport function setTheOneFileHtml(html: string): void {\n  theOneFileHtml = html;\n  currentFileVersion = extractVersionFromHtml(html);\n}\n\nexport function getTheOneFilePath(): string {\n  return theOneFilePath;\n}\n\nlet updateTimer: Timer | null = null;\n\nexport function restartUpdateTimer(): void {\n  const settings = getSettings();\n  if (updateTimer) clearInterval(updateTimer);\n  updateTimer = null;\n  if (settings.updateIntervalHours > 0 && !settings.skipUpdates) {\n    updateTimer = setInterval(() => { fetchLatestFromGitHub(); }, settings.updateIntervalHours * 60 * 60 * 1000);\n    console.log(`[Update] Auto update every ${settings.updateIntervalHours} hours`);\n  }\n}\n\nexport function clearUpdateTimer(): void {\n  if (updateTimer) clearInterval(updateTimer);\n  updateTimer = null;\n}\n\nexport async function initializeTheOneFile(): Promise<void> {\n  const settings = getSettings();\n  if (settings.skipUpdates) {\n    const localFile = Bun.file(theOneFilePath);\n    if (await localFile.exists()) {\n      theOneFileHtml = await localFile.text();\n      console.log(\"[Update] Using local file (updates disabled)\");\n    }\n  } else {\n    await fetchLatestFromGitHub();\n    if (!theOneFileHtml) {\n      const cachedFile = Bun.file(theOneFilePath);\n      if (await cachedFile.exists()) {\n        theOneFileHtml = await cachedFile.text();\n        console.log(\"[Update] Using cached file\");\n      }\n    }\n  }\n  if (theOneFileHtml) {\n    currentFileVersion = extractVersionFromHtml(theOneFileHtml);\n    if (currentFileVersion !== \"unknown\") console.log(`[Update] Current version: v${currentFileVersion}`);\n  }\n  restartUpdateTimer();\n}\n\nexport interface ValidationResult {\n  valid: boolean;\n  error?: string;\n  edition?: string;\n}\n\nexport interface TopologyValidationResult {\n  valid: boolean;\n  error?: string;\n  sanitized?: any;\n}\n\nconst TOPOLOGY_LIMITS = {\n  maxSizeBytes: 5 * 1024 * 1024,\n  maxDepth: 20,\n  maxArrayLength: 10000,\n  maxObjectKeys: 10000,\n  maxStringLength: 1 * 1024 * 1024\n};\n\nfunction checkJsonDepthAndSize(obj: any, currentDepth: number = 0): { valid: boolean; error?: string } {\n  if (currentDepth > TOPOLOGY_LIMITS.maxDepth) {\n    return { valid: false, error: `Topology exceeds maximum nesting depth of ${TOPOLOGY_LIMITS.maxDepth}` };\n  }\n  if (obj === null || obj === undefined) {\n    return { valid: true };\n  }\n  if (typeof obj === 'string') {\n    if (obj.length > TOPOLOGY_LIMITS.maxStringLength) {\n      return { valid: false, error: `String value exceeds maximum length of ${TOPOLOGY_LIMITS.maxStringLength / 1024}KB` };\n    }\n    return { valid: true };\n  }\n  if (Array.isArray(obj)) {\n    if (obj.length > TOPOLOGY_LIMITS.maxArrayLength) {\n      return { valid: false, error: `Array exceeds maximum length of ${TOPOLOGY_LIMITS.maxArrayLength}` };\n    }\n    for (const item of obj) {\n      const result = checkJsonDepthAndSize(item, currentDepth + 1);\n      if (!result.valid) return result;\n    }\n    return { valid: true };\n  }\n  if (typeof obj === 'object') {\n    const keys = Object.keys(obj);\n    if (keys.length > TOPOLOGY_LIMITS.maxObjectKeys) {\n      return { valid: false, error: `Object exceeds maximum keys of ${TOPOLOGY_LIMITS.maxObjectKeys}` };\n    }\n    for (const key of keys) {\n      const result = checkJsonDepthAndSize(obj[key], currentDepth + 1);\n      if (!result.valid) return result;\n    }\n    return { valid: true };\n  }\n  return { valid: true };\n}\n\nconst ALLOWED_TAGS = new Set([\n  'a', 'abbr', 'address', 'article', 'aside', 'b', 'bdi', 'bdo', 'blockquote',\n  'br', 'caption', 'cite', 'code', 'col', 'colgroup', 'dd', 'del', 'details',\n  'dfn', 'div', 'dl', 'dt', 'em', 'figcaption', 'figure', 'footer', 'h1', 'h2',\n  'h3', 'h4', 'h5', 'h6', 'header', 'hr', 'i', 'img', 'ins', 'kbd', 'li',\n  'main', 'mark', 'nav', 'ol', 'p', 'picture', 'pre', 'q', 'rp', 'rt', 'ruby',\n  's', 'samp', 'section', 'small', 'source', 'span', 'strong', 'sub', 'summary',\n  'sup', 'table', 'tbody', 'td', 'tfoot', 'th', 'thead', 'time', 'tr', 'u',\n  'ul', 'var', 'wbr', 'input', 'label', 'select', 'option', 'textarea', 'button',\n  'canvas', 'audio', 'video'\n]);\n\nconst ALLOWED_ATTRS = new Set([\n  'id', 'class', 'title', 'lang', 'dir', 'role', 'tabindex', 'aria-label',\n  'aria-hidden', 'aria-expanded', 'aria-controls', 'aria-describedby',\n  'data-id', 'data-type', 'data-value', 'data-node-id', 'data-edge-id',\n  'data-tab-id', 'data-tab', 'data-color', 'data-size', 'data-x', 'data-y',\n  'data-width', 'data-height', 'data-source', 'data-target', 'data-label',\n  'data-index', 'data-selected', 'data-locked', 'data-visible', 'data-active',\n  'data-state', 'data-mode', 'data-theme', 'data-collapsed', 'data-position',\n  'data-group', 'data-parent', 'data-layer', 'data-order', 'data-shape',\n  'data-font-size', 'data-font-family', 'data-text-align', 'data-stroke',\n  'data-fill', 'data-opacity', 'data-rotation', 'data-style', 'data-content',\n  'href', 'src', 'alt', 'width', 'height', 'colspan', 'rowspan', 'target',\n  'rel', 'type', 'value', 'placeholder', 'name', 'for', 'checked', 'disabled',\n  'readonly', 'maxlength', 'min', 'max', 'step', 'rows', 'cols', 'wrap',\n  'autoplay', 'controls', 'loop', 'muted', 'preload', 'poster',\n  'contenteditable', 'spellcheck', 'draggable', 'hidden', 'open', 'start',\n  'reversed', 'datetime', 'cite', 'loading', 'decoding', 'crossorigin',\n  'referrerpolicy', 'sizes', 'srcset', 'media'\n]);\n\nfunction isAllowedDataAttr(name: string): boolean {\n  if (!name.startsWith('data-')) return false;\n  return /^data-[a-z][a-z0-9-]*$/.test(name);\n}\n\nfunction sanitizeUrl(url: string): string {\n  let decoded = url\n    .replace(/&#x([0-9a-f]+);?/gi, (_: string, h: string) => String.fromCharCode(parseInt(h, 16)))\n    .replace(/&#(\\d+);?/gi, (_: string, d: string) => String.fromCharCode(parseInt(d, 10)))\n    .replace(/[\\t\\n\\r\\x00]/g, '');\n  const clean = decoded.replace(/\\s/g, '').toLowerCase();\n  if (clean.startsWith('javascript:') || clean.startsWith('vbscript:')) return '';\n  if (clean.startsWith('data:') && !/^data:image\\/(png|jpe?g|gif|webp|bmp|ico)/i.test(clean)) return 'data:blocked';\n  return url;\n}\n\nexport function sanitizeHtmlString(str: string): string {\n  if (typeof str !== 'string') return str;\n  return str.replace(/<\\/?([a-zA-Z][a-zA-Z0-9]*)\\b([^>]*)?\\/?>/g, (match, tagName, attrsStr) => {\n    const tag = tagName.toLowerCase();\n    const isClosing = match.startsWith('</');\n    if (!ALLOWED_TAGS.has(tag)) return '';\n    if (isClosing) return `</${tag}>`;\n    const selfClosing = match.endsWith('/>') || ['br', 'hr', 'img', 'input', 'col', 'source', 'wbr'].includes(tag);\n    let safeAttrs = '';\n    if (attrsStr) {\n      const attrRegex = /([a-zA-Z][a-zA-Z0-9-]*)\\s*(?:=\\s*(?:\"([^\"]*)\"|'([^']*)'|([^\\s>\"']+)))?/g;\n      let attrMatch;\n      while ((attrMatch = attrRegex.exec(attrsStr)) !== null) {\n        const attrName = attrMatch[1].toLowerCase();\n        const attrVal = attrMatch[2] ?? attrMatch[3] ?? attrMatch[4] ?? '';\n        if (/^on/i.test(attrName)) continue;\n        if (attrName === 'style') continue;\n        if (attrName === 'srcdoc') continue;\n        if (attrName === 'formaction') continue;\n        if (!ALLOWED_ATTRS.has(attrName) && !isAllowedDataAttr(attrName)) continue;\n        let safeVal = attrVal;\n        if (attrName === 'href' || attrName === 'src' || attrName === 'action' || attrName === 'poster') {\n          safeVal = sanitizeUrl(attrVal);\n        }\n        if (attrName === 'target') {\n          safeVal = '_blank';\n        }\n        safeAttrs += ` ${attrName}=\"${safeVal.replace(/\"/g, '&quot;')}\"`;\n        if (attrName === 'target') {\n          safeAttrs += ' rel=\"noopener noreferrer\"';\n        }\n      }\n    }\n    return selfClosing ? `<${tag}${safeAttrs} />` : `<${tag}${safeAttrs}>`;\n  });\n}\n\nexport function sanitizeTopologyStrings(obj: any): any {\n  if (obj === null || obj === undefined) return obj;\n  if (typeof obj === 'string') return sanitizeHtmlString(obj);\n  if (Array.isArray(obj)) return obj.map(sanitizeTopologyStrings);\n  if (typeof obj === 'object') {\n    const result: any = {};\n    for (const key of Object.keys(obj)) {\n      result[key] = sanitizeTopologyStrings(obj[key]);\n    }\n    return result;\n  }\n  return obj;\n}\n\nexport function validateTopology(topology: any): TopologyValidationResult {\n  if (topology === null || topology === undefined) {\n    return { valid: true, sanitized: null };\n  }\n  let jsonStr: string;\n  try {\n    jsonStr = JSON.stringify(topology);\n  } catch {\n    return { valid: false, error: \"Topology is not valid JSON\" };\n  }\n  if (jsonStr.length > TOPOLOGY_LIMITS.maxSizeBytes) {\n    return { valid: false, error: `Topology exceeds maximum size of ${TOPOLOGY_LIMITS.maxSizeBytes / (1024 * 1024)}MB` };\n  }\n  const depthCheck = checkJsonDepthAndSize(topology);\n  if (!depthCheck.valid) {\n    return { valid: false, error: depthCheck.error };\n  }\n  if (typeof topology !== 'object' || Array.isArray(topology)) {\n    return { valid: false, error: \"Topology must be an object\" };\n  }\n  const typeChecks: Array<{ key: string; expectedType: string; isArray?: boolean }> = [\n    { key: 'nodeData', expectedType: 'object' },\n    { key: 'edgeData', expectedType: 'object' },\n    { key: 'nodePositions', expectedType: 'object' },\n    { key: 'nodeSizes', expectedType: 'object' },\n    { key: 'nodeStyles', expectedType: 'object' },\n    { key: 'rectData', expectedType: 'object' },\n    { key: 'textData', expectedType: 'object' },\n    { key: 'imageData', expectedType: 'object' },\n    { key: 'documentTabs', expectedType: 'object', isArray: true },\n    { key: 'edgeLegend', expectedType: 'object' },\n    { key: 'zoneLegend', expectedType: 'object' },\n    { key: 'currentTabIndex', expectedType: 'number' },\n  ];\n  for (const check of typeChecks) {\n    if (topology[check.key] !== undefined && topology[check.key] !== null) {\n      const actualType = typeof topology[check.key];\n      const isArray = Array.isArray(topology[check.key]);\n      if (check.isArray) {\n        if (!isArray) {\n          return { valid: false, error: `Topology field '${check.key}' must be an array` };\n        }\n      } else if (check.expectedType === 'object') {\n        if (actualType !== 'object' || isArray) {\n          return { valid: false, error: `Topology field '${check.key}' must be an object` };\n        }\n      } else if (actualType !== check.expectedType) {\n        return { valid: false, error: `Topology field '${check.key}' must be a ${check.expectedType}` };\n      }\n    }\n  }\n  return { valid: true, sanitized: sanitizeTopologyStrings(topology) };\n}\n\nexport function validateTheOneFileHtml(html: string): ValidationResult {\n  if (!html || html.length < 1000) {\n    return { valid: false, error: \"File too small to be valid\" };\n  }\n  if (!html.trim().startsWith(\"<!DOCTYPE html>\") && !html.trim().startsWith(\"<html\")) {\n    return { valid: false, error: \"Not a valid HTML file\" };\n  }\n  const hasHeaderComment = html.includes(\"The One File\") && html.includes(\"There can be only one\");\n  if (!hasHeaderComment) {\n    return { valid: false, error: \"Missing The One File header comment\" };\n  }\n  const hasLangJson = html.includes('id=\"lang-json\"');\n  if (!hasLangJson) {\n    return { valid: false, error: \"Missing language system (lang json)\" };\n  }\n  const hasTopologyState = html.includes('id=\"topology-state\"');\n  if (!hasTopologyState) {\n    return { valid: false, error: \"Missing topology state element\" };\n  }\n  let edition = \"core\";\n  if (html.includes(\"The Networkening\") || html.includes(\"networkening\")) {\n    edition = \"networkening\";\n  }\n  const hasCoreVars = html.includes(\"NODE_DATA\") && html.includes(\"EDGE_DATA\") && html.includes(\"savedPositions\");\n  if (!hasCoreVars) {\n    return { valid: false, error: \"Missing core topology variables\" };\n  }\n  return { valid: true, edition };\n}\n\nexport function isValidWebhookUrl(url: string): boolean {\n  try {\n    const parsed = new URL(url);\n    if (!['http:', 'https:'].includes(parsed.protocol)) return false;\n    let hostname = parsed.hostname.toLowerCase().replace(/^\\[|\\]$/g, '');\n    if (hostname === 'localhost' || hostname.endsWith('.localhost')) return false;\n    if (hostname === 'metadata.google.internal' || hostname.endsWith('.internal')) return false;\n    const mappedMatch = hostname.match(/^::ffff:(\\d+\\.\\d+\\.\\d+\\.\\d+)$/);\n    if (mappedMatch) hostname = mappedMatch[1];\n    if (/^(0x[0-9a-f]+|\\d{8,})$/i.test(hostname)) return false;\n    if (/^\\d+\\.\\d+\\.\\d+\\.\\d+$/.test(hostname)) {\n      const parts = hostname.split('.').map(Number);\n      if (parts.some(p => p < 0 || p > 255)) return false;\n      if (parts[0] === 127 || parts[0] === 10 || parts[0] === 0) return false;\n      if (parts[0] === 172 && parts[1] >= 16 && parts[1] <= 31) return false;\n      if (parts[0] === 192 && parts[1] === 168) return false;\n      if (parts[0] === 169 && parts[1] === 254) return false;\n      if (parts.every(p => p === 255)) return false;\n    }\n    if (hostname.includes(':')) {\n      if (hostname === '::1' || hostname === '::') return false;\n      if (/^fe80:/i.test(hostname)) return false;\n      if (/^f[cd][0-9a-f]{2}:/i.test(hostname)) return false;\n      if (/^::ffff:/i.test(hostname)) return false;\n      if (/^100::/i.test(hostname)) return false;\n    }\n    return true;\n  } catch { return false; }\n}\n\nexport async function sendWebhook(event: string, data: any): Promise<void> {\n  const settings = getSettings();\n  if (!settings.webhookEnabled || !settings.webhookUrl) return;\n  if (!isValidWebhookUrl(settings.webhookUrl)) {\n    console.error('[Webhook] Blocked request to disallowed URL:', settings.webhookUrl);\n    return;\n  }\n  try {\n    await fetch(settings.webhookUrl, {\n      method: \"POST\",\n      headers: { \"Content-Type\": \"application/json\" },\n      body: JSON.stringify({ event, timestamp: new Date().toISOString(), data })\n    });\n  } catch (e: any) {\n    console.error('[Webhook] Failed to send webhook:', e.message);\n  }\n}\n\nconst BACKUPS_DIR = join(DATA_DIR, \"backups\");\nif (!existsSync(BACKUPS_DIR)) mkdirSync(BACKUPS_DIR, { recursive: true });\n\nexport async function createBackup(autoGenerated: boolean = false): Promise<{ id: string; filename: string; size: number } | null> {\n  try {\n    const settings = getSettings();\n    const id = crypto.randomUUID();\n    const timestamp = new Date().toISOString().replace(/[:.]/g, \"\").slice(0, 15);\n    const filename = `backup_${timestamp}_${id.slice(0, 8)}.json`;\n    const rooms = db.listRooms();\n    const dbSettings = db.getAllSettings();\n    const backupData = { version: 1, timestamp: new Date().toISOString(), rooms, settings: dbSettings };\n    const content = JSON.stringify(backupData, null, 2);\n    await Bun.write(join(BACKUPS_DIR, filename), content);\n    const size = content.length;\n    db.createBackupRecord({ id, filename, createdAt: new Date().toISOString(), sizeBytes: size, roomCount: rooms.length, autoGenerated });\n    if (autoGenerated && settings.backupRetentionCount > 0) {\n      const oldBackups = db.getOldAutoBackups(settings.backupRetentionCount);\n      for (const backup of oldBackups) {\n        const backupPath = join(BACKUPS_DIR, backup.filename);\n        try { await unlink(backupPath); } catch {}\n        db.deleteBackupRecord(backup.id);\n      }\n    }\n    return { id, filename, size };\n  } catch (e: any) { console.error(\"[Backup]\", e.message); return null; }\n}\n\nexport async function restoreBackup(backupId: string): Promise<{ success: boolean; error?: string; roomsRestored?: number }> {\n  const backups = db.listBackups();\n  const backup = backups.find(b => b.id === backupId);\n  if (!backup) return { success: false, error: \"Backup not found\" };\n  const backupPath = join(BACKUPS_DIR, backup.filename);\n  const backupFile = Bun.file(backupPath);\n  if (!await backupFile.exists()) return { success: false, error: \"Backup file missing\" };\n  try {\n    const content = await backupFile.text();\n    const data = JSON.parse(content);\n    if (!data.rooms || !Array.isArray(data.rooms)) return { success: false, error: \"Invalid backup format\" };\n    let roomsRestored = 0;\n    for (const room of data.rooms) {\n      const existing = db.getRoom(room.id);\n      if (!existing) {\n        if (room.topology) room.topology = sanitizeTopologyStrings(room.topology);\n        db.createRoom(room);\n        roomsRestored++;\n      }\n    }\n    return { success: true, roomsRestored };\n  } catch (e: any) { console.error(\"[Backup]\", e.message); return { success: false, error: \"Failed to parse backup\" }; }\n}\n\nexport function getBackupsDir(): string {\n  return BACKUPS_DIR;\n}\n\nlet backupTimer: Timer | null = null;\n\nexport function restartBackupTimer(): void {\n  const settings = getSettings();\n  if (backupTimer) clearInterval(backupTimer);\n  backupTimer = null;\n  if (settings.backupEnabled && settings.backupIntervalHours > 0) {\n    backupTimer = setInterval(() => { createBackup(true); }, settings.backupIntervalHours * 60 * 60 * 1000);\n    console.log(`[Backup] Auto backup every ${settings.backupIntervalHours} hours`);\n  }\n}\n\nexport function clearBackupTimer(): void {\n  if (backupTimer) clearInterval(backupTimer);\n  backupTimer = null;\n}\n"
  },
  {
    "path": "theonefile_verse/src/routes/admin-apikeys.ts",
    "content": "import * as db from \"../database\";\nimport { getClientIP, apiError, validateAdminOrApiKey } from \"../security\";\nimport { securityHeaders } from \"../security\";\nimport { hashApiKey } from \"../tokens\";\n\nexport async function handle(req: Request, path: string, url: URL, corsHeaders: Record<string, string>): Promise<Response | null> {\n  if (path === \"/api/admin/api-keys\" && req.method === \"GET\") {\n    const { user, apiKey } = await validateAdminOrApiKey(req, \"admin\");\n    if (!user && !apiKey) {\n      return Response.json({ error: \"Unauthorized\" }, { status: 401, headers: corsHeaders });\n    }\n    const keys = db.listApiKeys();\n    return Response.json({ keys }, { headers: corsHeaders });\n  }\n\n  if (path === \"/api/admin/api-keys\" && req.method === \"POST\") {\n    const { user, apiKey } = await validateAdminOrApiKey(req, \"admin\");\n    if (!user && !apiKey) {\n      return Response.json({ error: \"Unauthorized\" }, { status: 401, headers: corsHeaders });\n    }\n    try {\n      const body = await req.json();\n      if (!body.name) return Response.json({ error: \"Name required\" }, { status: 400, headers: corsHeaders });\n      const id = crypto.randomUUID();\n      const rawKey = `tof_${crypto.randomUUID().replace(/-/g, \"\")}`;\n      const keyHash = await hashApiKey(rawKey);\n      const permissions = body.permissions || [\"read\"];\n      const expiresAt = body.expiresInDays ? new Date(Date.now() + body.expiresInDays * 24 * 60 * 60 * 1000).toISOString() : null;\n      db.createApiKey({ id, name: body.name, keyHash, permissions, createdAt: new Date().toISOString(), expiresAt, active: true });\n      const actor = user ? user.id : `apikey:${apiKey!.name}`;\n      db.addAuditLog({ timestamp: new Date().toISOString(), action: \"api_key_created\", actor, actorIp: getClientIP(req), targetType: \"api_key\", targetId: id, details: { name: body.name } });\n      return Response.json({ id, key: rawKey, name: body.name, permissions, expiresAt }, { headers: corsHeaders });\n    } catch (e: any) { return apiError(e, corsHeaders); }\n  }\n\n  if (path.match(/^\\/api\\/admin\\/api-keys\\/[\\w-]+$/) && req.method === \"DELETE\") {\n    const { user, apiKey } = await validateAdminOrApiKey(req, \"admin\");\n    if (!user && !apiKey) {\n      return Response.json({ error: \"Unauthorized\" }, { status: 401, headers: corsHeaders });\n    }\n    const keyId = path.split(\"/\")[4];\n    if (db.deactivateApiKey(keyId)) {\n      const actor = user ? user.id : `apikey:${apiKey!.name}`;\n      db.addAuditLog({ timestamp: new Date().toISOString(), action: \"api_key_revoked\", actor, actorIp: getClientIP(req), targetType: \"api_key\", targetId: keyId });\n      return Response.json({ revoked: true }, { headers: corsHeaders });\n    }\n    return Response.json({ error: \"API key not found\" }, { status: 404, headers: corsHeaders });\n  }\n\n  if (path === \"/api/admin/export\" && req.method === \"GET\") {\n    const { user, apiKey } = await validateAdminOrApiKey(req, \"read\");\n    if (!user && !apiKey) {\n      return Response.json({ error: \"Unauthorized\" }, { status: 401, headers: corsHeaders });\n    }\n    const rooms = db.listRooms();\n    const settings = db.getAllSettings();\n    const exportData = { version: 1, exportedAt: new Date().toISOString(), rooms, settings };\n    const actor = user ? user.id : `apikey:${apiKey!.name}`;\n    db.addAuditLog({ timestamp: new Date().toISOString(), action: \"data_exported\", actor, actorIp: getClientIP(req), details: { roomCount: rooms.length } });\n    return new Response(JSON.stringify(exportData, null, 2), { headers: { \"Content-Type\": \"application/json\", \"Content-Disposition\": `attachment; filename=\"theonefile_export_${new Date().toISOString().slice(0, 10)}.json\"`, ...securityHeaders } });\n  }\n\n  return null;\n}\n"
  },
  {
    "path": "theonefile_verse/src/routes/admin-auth-settings.ts",
    "content": "import * as oidc from \"../oidc\";\nimport * as db from \"../database\";\nimport * as mailer from \"../mailer\";\nimport { getClientIP, apiError, validateAdminUser } from \"../security\";\n\nexport async function handle(req: Request, path: string, url: URL, corsHeaders: Record<string, string>): Promise<Response | null> {\n  if (path === \"/api/admin/auth-settings\" && req.method === \"GET\") {\n    const adminUser = await validateAdminUser(req);\n    if (!adminUser) {\n      return Response.json({ error: \"Unauthorized\" }, { status: 401, headers: corsHeaders });\n    }\n    return Response.json(oidc.getAuthSettings(), { headers: corsHeaders });\n  }\n\n  if (path === \"/api/admin/auth-settings\" && req.method === \"POST\") {\n    const adminUser = await validateAdminUser(req);\n    if (!adminUser) {\n      return Response.json({ error: \"Unauthorized\" }, { status: 401, headers: corsHeaders });\n    }\n    try {\n      const body = await req.json();\n      oidc.saveAuthSettings(body);\n      db.addAuditLog({ timestamp: new Date().toISOString(), action: \"auth_settings_changed\", actor: adminUser.id, actorIp: getClientIP(req), details: body });\n      return Response.json({ success: true }, { headers: corsHeaders });\n    } catch (e: any) { return apiError(e, corsHeaders); }\n  }\n\n  if (path === \"/api/admin/oidc-providers\" && req.method === \"GET\") {\n    const adminUser = await validateAdminUser(req);\n    if (!adminUser) {\n      return Response.json({ error: \"Unauthorized\" }, { status: 401, headers: corsHeaders });\n    }\n    const providers = db.listOidcProviders().map(p => ({\n      id: p.id, name: p.name, providerType: p.providerType, clientId: p.clientId,\n      issuerUrl: p.issuerUrl, authorizationUrl: p.authorizationUrl, tokenUrl: p.tokenUrl,\n      userinfoUrl: p.userinfoUrl, scopes: p.scopes, isActive: p.isActive, displayOrder: p.displayOrder,\n      iconUrl: p.iconUrl, createdAt: p.createdAt\n    }));\n    return Response.json({ providers }, { headers: corsHeaders });\n  }\n\n  if (path === \"/api/admin/oidc-providers\" && req.method === \"POST\") {\n    const adminUser = await validateAdminUser(req);\n    if (!adminUser) {\n      return Response.json({ error: \"Unauthorized\" }, { status: 401, headers: corsHeaders });\n    }\n    try {\n      const body = await req.json();\n      if (!body.name || !body.clientId || !body.clientSecret) {\n        return Response.json({ error: \"Name, client ID, and client secret are required\" }, { status: 400, headers: corsHeaders });\n      }\n      const encryptedSecret = await oidc.encryptSecret(body.clientSecret);\n      const now = new Date().toISOString();\n      const provider: db.OidcProvider = {\n        id: crypto.randomUUID(),\n        name: body.name,\n        providerType: body.providerType || 'generic',\n        clientId: body.clientId,\n        clientSecretEncrypted: encryptedSecret,\n        issuerUrl: body.issuerUrl || null,\n        authorizationUrl: body.authorizationUrl || null,\n        tokenUrl: body.tokenUrl || null,\n        userinfoUrl: body.userinfoUrl || null,\n        jwksUri: body.jwksUri || null,\n        scopes: body.scopes || 'openid email profile',\n        isActive: body.isActive !== false,\n        displayOrder: body.displayOrder || 0,\n        iconUrl: body.iconUrl || null,\n        createdAt: now,\n        updatedAt: now\n      };\n      db.createOidcProvider(provider);\n      db.addAuditLog({ timestamp: now, action: \"oidc_provider_created\", actor: adminUser.id, actorIp: getClientIP(req), targetType: \"oidc_provider\", targetId: provider.id, details: { name: provider.name } });\n      return Response.json({ success: true, id: provider.id }, { headers: corsHeaders });\n    } catch (e: any) { return apiError(e, corsHeaders); }\n  }\n\n  if (path.match(/^\\/api\\/admin\\/oidc-providers\\/[\\w-]+$/) && req.method === \"PUT\") {\n    const providerId = path.split(\"/\")[4];\n    const adminUser = await validateAdminUser(req);\n    if (!adminUser) {\n      return Response.json({ error: \"Unauthorized\" }, { status: 401, headers: corsHeaders });\n    }\n    try {\n      const body = await req.json();\n      const existing = db.getOidcProvider(providerId);\n      if (!existing) {\n        return Response.json({ error: \"Provider not found\" }, { status: 404, headers: corsHeaders });\n      }\n      const now = new Date().toISOString();\n      const updated: db.OidcProvider = {\n        ...existing,\n        name: body.name ?? existing.name,\n        providerType: body.providerType ?? existing.providerType,\n        clientId: body.clientId ?? existing.clientId,\n        clientSecretEncrypted: body.clientSecret ? await oidc.encryptSecret(body.clientSecret) : existing.clientSecretEncrypted,\n        issuerUrl: body.issuerUrl !== undefined ? body.issuerUrl : existing.issuerUrl,\n        authorizationUrl: body.authorizationUrl !== undefined ? body.authorizationUrl : existing.authorizationUrl,\n        tokenUrl: body.tokenUrl !== undefined ? body.tokenUrl : existing.tokenUrl,\n        userinfoUrl: body.userinfoUrl !== undefined ? body.userinfoUrl : existing.userinfoUrl,\n        jwksUri: body.jwksUri !== undefined ? body.jwksUri : existing.jwksUri,\n        scopes: body.scopes ?? existing.scopes,\n        isActive: body.isActive ?? existing.isActive,\n        displayOrder: body.displayOrder ?? existing.displayOrder,\n        iconUrl: body.iconUrl !== undefined ? body.iconUrl : existing.iconUrl,\n        updatedAt: now\n      };\n      db.updateOidcProvider(updated);\n      db.addAuditLog({ timestamp: now, action: \"oidc_provider_updated\", actor: adminUser.id, actorIp: getClientIP(req), targetType: \"oidc_provider\", targetId: providerId });\n      return Response.json({ success: true }, { headers: corsHeaders });\n    } catch (e: any) { return apiError(e, corsHeaders); }\n  }\n\n  if (path.match(/^\\/api\\/admin\\/oidc-providers\\/[\\w-]+$/) && req.method === \"DELETE\") {\n    const providerId = path.split(\"/\")[4];\n    const adminUser = await validateAdminUser(req);\n    if (!adminUser) {\n      return Response.json({ error: \"Unauthorized\" }, { status: 401, headers: corsHeaders });\n    }\n    if (db.deleteOidcProvider(providerId)) {\n      db.addAuditLog({ timestamp: new Date().toISOString(), action: \"oidc_provider_deleted\", actor: adminUser.id, actorIp: getClientIP(req), targetType: \"oidc_provider\", targetId: providerId });\n      return Response.json({ success: true }, { headers: corsHeaders });\n    }\n    return Response.json({ error: \"Provider not found\" }, { status: 404, headers: corsHeaders });\n  }\n\n  if (path === \"/api/admin/smtp-configs\" && req.method === \"GET\") {\n    const adminUser = await validateAdminUser(req);\n    if (!adminUser) {\n      return Response.json({ error: \"Unauthorized\" }, { status: 401, headers: corsHeaders });\n    }\n    const configs = db.listSmtpConfigs().map(c => ({\n      id: c.id, name: c.name, providerType: c.providerType, host: c.host, port: c.port,\n      secureMode: c.secureMode, username: c.username, fromEmail: c.fromEmail, fromName: c.fromName,\n      isDefault: c.isDefault, isActive: c.isActive, createdAt: c.createdAt\n    }));\n    return Response.json({ configs }, { headers: corsHeaders });\n  }\n\n  if (path === \"/api/admin/smtp-configs\" && req.method === \"POST\") {\n    const adminUser = await validateAdminUser(req);\n    if (!adminUser) {\n      return Response.json({ error: \"Unauthorized\" }, { status: 401, headers: corsHeaders });\n    }\n    try {\n      const body = await req.json();\n      if (!body.name || !body.fromEmail) {\n        return Response.json({ error: \"Name and from email are required\" }, { status: 400, headers: corsHeaders });\n      }\n      const encryptedPassword = body.password ? await oidc.encryptSecret(body.password) : null;\n      const now = new Date().toISOString();\n      const config: db.SmtpConfig = {\n        id: crypto.randomUUID(),\n        name: body.name,\n        providerType: body.providerType || 'smtp',\n        host: body.host || null,\n        port: body.port || null,\n        secureMode: body.secureMode || 'tls',\n        username: body.username || null,\n        passwordEncrypted: encryptedPassword,\n        apiKeyEncrypted: null,\n        fromEmail: body.fromEmail,\n        fromName: body.fromName || null,\n        isDefault: body.isDefault || false,\n        isActive: body.isActive !== false,\n        createdAt: now,\n        updatedAt: now\n      };\n      db.createSmtpConfig(config);\n      db.addAuditLog({ timestamp: now, action: \"smtp_config_created\", actor: adminUser.id, actorIp: getClientIP(req), targetType: \"smtp_config\", targetId: config.id, details: { name: config.name } });\n      return Response.json({ success: true, id: config.id }, { headers: corsHeaders });\n    } catch (e: any) { return apiError(e, corsHeaders); }\n  }\n\n  if (path.match(/^\\/api\\/admin\\/smtp-configs\\/[\\w-]+$/) && req.method === \"PUT\") {\n    const configId = path.split(\"/\")[4];\n    const adminUser = await validateAdminUser(req);\n    if (!adminUser) {\n      return Response.json({ error: \"Unauthorized\" }, { status: 401, headers: corsHeaders });\n    }\n    try {\n      const body = await req.json();\n      const existing = db.getSmtpConfig(configId);\n      if (!existing) {\n        return Response.json({ error: \"Config not found\" }, { status: 404, headers: corsHeaders });\n      }\n      const now = new Date().toISOString();\n      const updated: db.SmtpConfig = {\n        ...existing,\n        name: body.name ?? existing.name,\n        providerType: body.providerType ?? existing.providerType,\n        host: body.host !== undefined ? body.host : existing.host,\n        port: body.port !== undefined ? body.port : existing.port,\n        secureMode: body.secureMode ?? existing.secureMode,\n        username: body.username !== undefined ? body.username : existing.username,\n        passwordEncrypted: body.password ? await oidc.encryptSecret(body.password) : existing.passwordEncrypted,\n        fromEmail: body.fromEmail ?? existing.fromEmail,\n        fromName: body.fromName !== undefined ? body.fromName : existing.fromName,\n        isDefault: body.isDefault ?? existing.isDefault,\n        isActive: body.isActive ?? existing.isActive,\n        updatedAt: now\n      };\n      db.updateSmtpConfig(updated);\n      db.addAuditLog({ timestamp: now, action: \"smtp_config_updated\", actor: adminUser.id, actorIp: getClientIP(req), targetType: \"smtp_config\", targetId: configId });\n      return Response.json({ success: true }, { headers: corsHeaders });\n    } catch (e: any) { return apiError(e, corsHeaders); }\n  }\n\n  if (path === \"/api/admin/smtp-configs/test\" && req.method === \"POST\") {\n    const adminUser = await validateAdminUser(req);\n    if (!adminUser) {\n      return Response.json({ error: \"Unauthorized\" }, { status: 401, headers: corsHeaders });\n    }\n    try {\n      const body = await req.json();\n      const now = new Date().toISOString();\n      let passwordEncrypted: string | null = null;\n      if (body.password) {\n        passwordEncrypted = await oidc.encryptSecret(body.password);\n      }\n      const testConfig: db.SmtpConfig = {\n        id: \"test-\" + crypto.randomUUID(),\n        name: body.name || \"Test\",\n        providerType: \"smtp\",\n        host: body.host,\n        port: body.port || 587,\n        secureMode: body.secureMode || \"starttls\",\n        username: body.username || null,\n        passwordEncrypted,\n        apiKeyEncrypted: null,\n        fromEmail: body.fromEmail,\n        fromName: body.fromName || null,\n        isDefault: false,\n        isActive: true,\n        createdAt: now,\n        updatedAt: now\n      };\n      const result = await mailer.testSmtpConfig(testConfig);\n      return Response.json(result, { headers: corsHeaders });\n    } catch (e: any) {\n      return Response.json({ success: false, error: e.message || \"Test failed\" }, { headers: corsHeaders });\n    }\n  }\n\n  if (path.match(/^\\/api\\/admin\\/smtp-configs\\/[\\w-]+\\/test$/) && req.method === \"POST\") {\n    const configId = path.split(\"/\")[4];\n    const adminUser = await validateAdminUser(req);\n    if (!adminUser) {\n      return Response.json({ error: \"Unauthorized\" }, { status: 401, headers: corsHeaders });\n    }\n    const config = db.getSmtpConfig(configId);\n    if (!config) {\n      return Response.json({ error: \"Config not found\" }, { status: 404, headers: corsHeaders });\n    }\n    const result = await mailer.testSmtpConfig(config);\n    return Response.json(result, { headers: corsHeaders });\n  }\n\n  if (path.match(/^\\/api\\/admin\\/smtp-configs\\/[\\w-]+$/) && req.method === \"DELETE\") {\n    const configId = path.split(\"/\")[4];\n    const adminUser = await validateAdminUser(req);\n    if (!adminUser) {\n      return Response.json({ error: \"Unauthorized\" }, { status: 401, headers: corsHeaders });\n    }\n    if (db.deleteSmtpConfig(configId)) {\n      db.addAuditLog({ timestamp: new Date().toISOString(), action: \"smtp_config_deleted\", actor: adminUser.id, actorIp: getClientIP(req), targetType: \"smtp_config\", targetId: configId });\n      return Response.json({ success: true }, { headers: corsHeaders });\n    }\n    return Response.json({ error: \"Config not found\" }, { status: 404, headers: corsHeaders });\n  }\n\n  if (path === \"/api/admin/email-logs\" && req.method === \"GET\") {\n    const adminUser = await validateAdminUser(req);\n    if (!adminUser) {\n      return Response.json({ error: \"Unauthorized\" }, { status: 401, headers: corsHeaders });\n    }\n    const limit = Math.min(parseInt(url.searchParams.get(\"limit\") || \"100\") || 100, 1000);\n    const offset = parseInt(url.searchParams.get(\"offset\") || \"0\");\n    const logs = db.listEmailLogs(limit, offset);\n    const stats = mailer.getEmailStats();\n    return Response.json({ logs, stats }, { headers: corsHeaders });\n  }\n\n  if (path === \"/api/admin/email-logs\" && req.method === \"DELETE\") {\n    const adminUser = await validateAdminUser(req);\n    if (!adminUser) {\n      return Response.json({ error: \"Unauthorized\" }, { status: 401, headers: corsHeaders });\n    }\n    db.clearEmailLogs();\n    db.addAuditLog({ timestamp: new Date().toISOString(), action: \"email_logs_cleared\", actor: adminUser.email || \"admin\", actorIp: getClientIP(req) });\n    return Response.json({ success: true }, { headers: corsHeaders });\n  }\n\n  if (path === \"/api/admin/email-templates\" && req.method === \"GET\") {\n    const adminUser = await validateAdminUser(req);\n    if (!adminUser) {\n      return Response.json({ error: \"Unauthorized\" }, { status: 401, headers: corsHeaders });\n    }\n    return Response.json({ templates: db.listEmailTemplates() }, { headers: corsHeaders });\n  }\n\n  if (path.match(/^\\/api\\/admin\\/email-templates\\/[\\w-]+$/) && req.method === \"PUT\") {\n    const templateId = path.split(\"/\")[4];\n    const adminUser = await validateAdminUser(req);\n    if (!adminUser) {\n      return Response.json({ error: \"Unauthorized\" }, { status: 401, headers: corsHeaders });\n    }\n    try {\n      const body = await req.json();\n      const existing = db.getEmailTemplate(templateId);\n      if (!existing) {\n        return Response.json({ error: \"Template not found\" }, { status: 404, headers: corsHeaders });\n      }\n      const updated: db.EmailTemplate = {\n        ...existing,\n        subject: body.subject ?? existing.subject,\n        bodyHtml: body.bodyHtml ?? existing.bodyHtml,\n        bodyText: body.bodyText ?? existing.bodyText,\n        updatedAt: new Date().toISOString()\n      };\n      db.updateEmailTemplate(updated);\n      return Response.json({ success: true }, { headers: corsHeaders });\n    } catch (e: any) { return apiError(e, corsHeaders); }\n  }\n\n  return null;\n}\n"
  },
  {
    "path": "theonefile_verse/src/routes/admin-auth.ts",
    "content": "import * as auth from \"../auth\";\nimport * as oidc from \"../oidc\";\nimport * as db from \"../database\";\nimport * as redis from \"../redis\";\nimport { getSettings, getAdminPath, needsAdminMigration, hasAdminUser, verifyAdminPassword, getExternalOrigin } from \"../config\";\nimport { getClientIP, serveHtml, apiError, validateAdminUser, getTokenFromRequest, getUserTokenFromRequest, relativeRedirect } from \"../security\";\nimport { checkRateLimit } from \"../rate-limit\";\nimport { removeAdminToken, removeInstanceToken } from \"../tokens\";\nimport { adminDashboardHtml, adminLoginHtml, migrationPageHtml } from \"../templates\";\n\nexport async function handle(req: Request, path: string, url: URL, corsHeaders: Record<string, string>): Promise<Response | null> {\n  const adminPath = getAdminPath();\n  const settings = getSettings();\n\n  if (path === `/${adminPath}`) {\n    if (needsAdminMigration()) {\n      const migrationWithPath = migrationPageHtml.replace(/\\/admin\\b(?!-)/g, `/${adminPath}`);\n      return serveHtml(migrationWithPath, 'admin', req);\n    }\n    if (!hasAdminUser()) {\n      return relativeRedirect(\"/\");\n    }\n    const adminUser = await validateAdminUser(req);\n    if (!adminUser) {\n      return relativeRedirect(`/${adminPath}/login`);\n    }\n    const dashboardWithPath = adminDashboardHtml\n      .replace('ADMIN_PATH_PLACEHOLDER', adminPath)\n      .replace(/\\/admin\\b(?!-)/g, `/${adminPath}`);\n    return serveHtml(dashboardWithPath, 'admin', req);\n  }\n\n  if (path === `/${adminPath}/login`) {\n    if (needsAdminMigration()) {\n      return relativeRedirect(`/${adminPath}`);\n    }\n    if (!hasAdminUser()) {\n      return relativeRedirect(\"/\");\n    }\n    const loginWithPath = adminLoginHtml.replace('ADMIN_PATH_PLACEHOLDER', adminPath).replace(/\\/admin\\b(?!-)/g, `/${adminPath}`);\n    return serveHtml(loginWithPath, 'admin', req);\n  }\n\n  if (path === \"/api/admin/login\" && req.method === \"POST\") {\n    if (!hasAdminUser()) {\n      return Response.json({ error: \"No admin user configured\" }, { status: 400, headers: corsHeaders });\n    }\n    const clientIP = getClientIP(req);\n    if (!(await checkRateLimit(clientIP, \"admin-login\", settings))) {\n      return Response.json({ error: \"Too many attempts. Try again later.\" }, { status: 429, headers: corsHeaders });\n    }\n    try {\n      const body = await req.json();\n      if (!body.email || !body.password) {\n        return Response.json({ error: \"Email and password required\" }, { status: 400, headers: corsHeaders });\n      }\n      const result = await auth.loginWithPassword(body.email, body.password, clientIP, req.headers.get(\"user-agent\") || \"\");\n      if (result.requires2FA) {\n        const user = db.getUserByEmail(body.email);\n        if (!user || user.role !== 'admin') {\n          return Response.json({ error: \"Not authorized as admin\" }, { status: 403, headers: corsHeaders });\n        }\n        return Response.json({ requires2FA: true, pendingToken: result.pendingToken }, { headers: corsHeaders });\n      }\n      if (!result.success || !result.sessionToken) {\n        return Response.json({ error: result.error || \"Invalid credentials\" }, { status: 403, headers: corsHeaders });\n      }\n      const user = db.getUserByEmail(body.email);\n      if (!user || user.role !== 'admin') {\n        return Response.json({ error: \"Not authorized as admin\" }, { status: 403, headers: corsHeaders });\n      }\n      return new Response(JSON.stringify({ success: true }), {\n        headers: { ...corsHeaders, \"Content-Type\": \"application/json\",\n          \"Set-Cookie\": oidc.getSessionCookie(\"user_token\", result.sessionToken) }\n      });\n    } catch (e: any) { return apiError(e, corsHeaders); }\n  }\n\n  if (path === \"/api/admin/migrate\" && req.method === \"POST\") {\n    if (!needsAdminMigration()) {\n      return Response.json({ error: \"Migration not needed\" }, { status: 400, headers: corsHeaders });\n    }\n    const clientIP = getClientIP(req);\n    if (!(await checkRateLimit(clientIP, \"admin-migrate\", settings))) {\n      return Response.json({ error: \"Too many attempts. Try again later.\" }, { status: 429, headers: corsHeaders });\n    }\n    try {\n      const body = await req.json();\n      if (!body.oldPassword || !body.email) {\n        return Response.json({ error: \"Current password and email required\" }, { status: 400, headers: corsHeaders });\n      }\n      if (!(await verifyAdminPassword(body.oldPassword))) {\n        return Response.json({ error: \"Invalid current password\" }, { status: 403, headers: corsHeaders });\n      }\n      const emailValidation = auth.validateEmail(body.email);\n      if (!emailValidation.valid) {\n        return Response.json({ error: emailValidation.error }, { status: 400, headers: corsHeaders });\n      }\n      const newPassword = body.newPassword || body.oldPassword;\n      const passwordValidation = auth.validatePassword(newPassword);\n      if (!passwordValidation.valid) {\n        return Response.json({ error: passwordValidation.error }, { status: 400, headers: corsHeaders });\n      }\n      const baseUrl = getExternalOrigin(req);\n      const result = await auth.registerUser(body.email, newPassword, body.email.split('@')[0], baseUrl);\n      if (!result.success) {\n        return Response.json({ error: result.error || \"Migration failed\" }, { status: 400, headers: corsHeaders });\n      }\n      db.deleteSetting('admin_password_hash');\n      db.deleteSetting('admin_created_at');\n      const loginResult = await auth.loginWithPassword(body.email, newPassword, clientIP, req.headers.get(\"user-agent\") || \"\");\n      if (!loginResult.success || !loginResult.sessionToken) {\n        return Response.json({ error: \"Migration succeeded but login failed\" }, { status: 400, headers: corsHeaders });\n      }\n      return new Response(JSON.stringify({ success: true }), {\n        headers: { ...corsHeaders, \"Content-Type\": \"application/json\",\n          \"Set-Cookie\": oidc.getSessionCookie(\"user_token\", loginResult.sessionToken) }\n      });\n    } catch (e: any) { return apiError(e, corsHeaders); }\n  }\n\n  if (path === \"/api/logout\" && req.method === \"POST\") {\n    const collabToken = getTokenFromRequest(req);\n    if (collabToken) {\n      await removeAdminToken(collabToken);\n      removeInstanceToken(collabToken);\n      if (redis.isRedisConnected()) await redis.deleteSessionToken(collabToken);\n    }\n\n    const userToken = getUserTokenFromRequest(req);\n    if (userToken) {\n      const user = await oidc.validateUserSessionToken(userToken);\n      if (user) {\n        oidc.revokeUserOidcTokens(user.id).catch(e => console.error('[OIDC] Token revocation error:', e));\n      }\n      await auth.logout(userToken);\n      if (redis.isRedisConnected()) await redis.deleteSessionToken(userToken);\n    }\n\n    const clearCookies = [\n      oidc.getClearCookie(\"collab_token\"),\n      oidc.getClearCookie(\"user_token\")\n    ].join(\", \");\n\n    return new Response(JSON.stringify({ success: true }), {\n      headers: { ...corsHeaders, \"Content-Type\": \"application/json\", \"Set-Cookie\": clearCookies }\n    });\n  }\n\n  return null;\n}\n"
  },
  {
    "path": "theonefile_verse/src/routes/admin-backups.ts",
    "content": "import { join } from \"path\";\nimport { unlink } from \"fs/promises\";\nimport * as db from \"../database\";\nimport { getClientIP, validateAdminOrApiKey } from \"../security\";\nimport { securityHeaders } from \"../security\";\nimport { createBackup, restoreBackup, getBackupsDir } from \"../rooms\";\n\nexport async function handle(req: Request, path: string, url: URL, corsHeaders: Record<string, string>): Promise<Response | null> {\n  if (path === \"/api/admin/backups\" && req.method === \"GET\") {\n    const { user, apiKey } = await validateAdminOrApiKey(req, \"read\");\n    if (!user && !apiKey) {\n      return Response.json({ error: \"Unauthorized\" }, { status: 401, headers: corsHeaders });\n    }\n    const backups = db.listBackups();\n    return Response.json({ backups }, { headers: corsHeaders });\n  }\n\n  if (path === \"/api/admin/backups\" && req.method === \"POST\") {\n    const { user, apiKey } = await validateAdminOrApiKey(req, \"write\");\n    if (!user && !apiKey) {\n      return Response.json({ error: \"Unauthorized\" }, { status: 401, headers: corsHeaders });\n    }\n    const result = await createBackup(false);\n    if (result) {\n      const actor = user ? user.id : `apikey:${apiKey!.name}`;\n      db.addAuditLog({ timestamp: new Date().toISOString(), action: \"backup_created\", actor, actorIp: getClientIP(req), targetType: \"backup\", targetId: result.id });\n      return Response.json({ success: true, backup: result }, { headers: corsHeaders });\n    }\n    return Response.json({ error: \"Failed to create backup\" }, { status: 500, headers: corsHeaders });\n  }\n\n  if (path.match(/^\\/api\\/admin\\/backups\\/[\\w-]+\\/restore$/) && req.method === \"POST\") {\n    const { user, apiKey } = await validateAdminOrApiKey(req, \"admin\");\n    if (!user && !apiKey) {\n      return Response.json({ error: \"Unauthorized\" }, { status: 401, headers: corsHeaders });\n    }\n    const backupId = path.split(\"/\")[4];\n    const result = await restoreBackup(backupId);\n    if (result.success) {\n      const actor = user ? user.id : `apikey:${apiKey!.name}`;\n      db.addAuditLog({ timestamp: new Date().toISOString(), action: \"backup_restored\", actor, actorIp: getClientIP(req), targetType: \"backup\", targetId: backupId, details: { roomsRestored: result.roomsRestored } });\n    }\n    return Response.json(result, { headers: corsHeaders });\n  }\n\n  if (path.match(/^\\/api\\/admin\\/backups\\/[\\w-]+\\/download$/) && req.method === \"GET\") {\n    const { user, apiKey } = await validateAdminOrApiKey(req, \"read\");\n    if (!user && !apiKey) {\n      return Response.json({ error: \"Unauthorized\" }, { status: 401, headers: corsHeaders });\n    }\n    const backupId = path.split(\"/\")[4];\n    const backups = db.listBackups();\n    const backup = backups.find(b => b.id === backupId);\n    if (!backup) return Response.json({ error: \"Backup not found\" }, { status: 404, headers: corsHeaders });\n    const BACKUPS_DIR = getBackupsDir();\n    const backupPath = join(BACKUPS_DIR, backup.filename);\n    const backupFile = Bun.file(backupPath);\n    if (!await backupFile.exists()) return Response.json({ error: \"Backup file missing\" }, { status: 404, headers: corsHeaders });\n    return new Response(backupFile, { headers: { \"Content-Type\": \"application/json\", \"Content-Disposition\": `attachment; filename=\"${backup.filename}\"`, ...securityHeaders } });\n  }\n\n  if (path.match(/^\\/api\\/admin\\/backups\\/[\\w-]+$/) && req.method === \"DELETE\") {\n    const { user, apiKey } = await validateAdminOrApiKey(req, \"admin\");\n    if (!user && !apiKey) {\n      return Response.json({ error: \"Unauthorized\" }, { status: 401, headers: corsHeaders });\n    }\n    const backupId = path.split(\"/\")[4];\n    const backups = db.listBackups();\n    const backup = backups.find(b => b.id === backupId);\n    if (!backup) return Response.json({ error: \"Backup not found\" }, { status: 404, headers: corsHeaders });\n    const BACKUPS_DIR = getBackupsDir();\n    const backupPath = join(BACKUPS_DIR, backup.filename);\n    try { await unlink(backupPath); } catch {}\n    db.deleteBackupRecord(backupId);\n    const actor = user ? user.id : `apikey:${apiKey!.name}`;\n    db.addAuditLog({ timestamp: new Date().toISOString(), action: \"backup_deleted\", actor, actorIp: getClientIP(req), targetType: \"backup\", targetId: backupId });\n    return Response.json({ deleted: true }, { headers: corsHeaders });\n  }\n\n  return null;\n}\n"
  },
  {
    "path": "theonefile_verse/src/routes/admin-logs.ts",
    "content": "import * as db from \"../database\";\nimport { getClientIP, validateAdminOrApiKey } from \"../security\";\n\nexport async function handle(req: Request, path: string, url: URL, corsHeaders: Record<string, string>): Promise<Response | null> {\n  if (path === \"/api/admin/audit-logs\" && req.method === \"GET\") {\n    const { user, apiKey } = await validateAdminOrApiKey(req, \"read\");\n    if (!user && !apiKey) {\n      return Response.json({ error: \"Unauthorized\" }, { status: 401, headers: corsHeaders });\n    }\n    const query = url.searchParams.get(\"q\") || \"\";\n    const limit = Math.min(parseInt(url.searchParams.get(\"limit\") || \"100\") || 100, 1000);\n    const offset = parseInt(url.searchParams.get(\"offset\") || \"0\");\n    const logs = query ? db.searchAuditLogs(query, limit, offset) : db.getAuditLogs(limit, offset);\n    return Response.json({ logs }, { headers: corsHeaders });\n  }\n\n  if (path === \"/api/admin/audit-logs\" && req.method === \"DELETE\") {\n    const { user, apiKey } = await validateAdminOrApiKey(req, \"admin\");\n    if (!user && !apiKey) {\n      return Response.json({ error: \"Unauthorized\" }, { status: 401, headers: corsHeaders });\n    }\n    db.clearAuditLogs();\n    return Response.json({ success: true }, { headers: corsHeaders });\n  }\n\n  if (path === \"/api/admin/activity-logs\" && req.method === \"GET\") {\n    const { user, apiKey } = await validateAdminOrApiKey(req, \"read\");\n    if (!user && !apiKey) {\n      return Response.json({ error: \"Unauthorized\" }, { status: 401, headers: corsHeaders });\n    }\n    const roomId = url.searchParams.get(\"room\") || \"\";\n    const limit = Math.min(parseInt(url.searchParams.get(\"limit\") || \"100\") || 100, 1000);\n    const offset = parseInt(url.searchParams.get(\"offset\") || \"0\");\n    const logs = roomId ? db.getActivityLogs(roomId, limit, offset) : db.getAllActivityLogs(limit, offset);\n    return Response.json({ logs }, { headers: corsHeaders });\n  }\n\n  if (path === \"/api/admin/activity-logs\" && req.method === \"DELETE\") {\n    const { user, apiKey } = await validateAdminOrApiKey(req, \"admin\");\n    if (!user && !apiKey) {\n      return Response.json({ error: \"Unauthorized\" }, { status: 401, headers: corsHeaders });\n    }\n    const actor = user ? (user.email || \"admin\") : `apikey:${apiKey!.name}`;\n    db.clearActivityLogs();\n    db.addAuditLog({ timestamp: new Date().toISOString(), action: \"activity_logs_cleared\", actor, actorIp: getClientIP(req) });\n    return Response.json({ success: true }, { headers: corsHeaders });\n  }\n\n  return null;\n}\n"
  },
  {
    "path": "theonefile_verse/src/routes/admin-rooms.ts",
    "content": "import * as db from \"../database\";\nimport { isValidUUID } from \"../config\";\nimport { getClientIP, validateAdminOrApiKey } from \"../security\";\nimport { roomMeta, deleteRoomData } from \"../rooms\";\n\nexport async function handle(req: Request, path: string, url: URL, corsHeaders: Record<string, string>): Promise<Response | null> {\n  if (path === \"/api/admin/rooms\" && req.method === \"GET\") {\n    const { user, apiKey } = await validateAdminOrApiKey(req, \"read\");\n    if (!user && !apiKey) {\n      return Response.json({ error: \"Unauthorized\" }, { status: 401, headers: corsHeaders });\n    }\n    const searchQuery = url.searchParams.get(\"q\") || \"\";\n    const limit = Math.min(parseInt(url.searchParams.get(\"limit\") || \"100\") || 100, 1000);\n    const offset = parseInt(url.searchParams.get(\"offset\") || \"0\");\n    const dbRooms = searchQuery ? db.searchRooms(searchQuery, limit, offset) : db.listRooms(limit, offset);\n    const rooms = dbRooms.map(room => {\n      const meta = roomMeta.get(room.id);\n      return {\n        id: room.id,\n        created: room.created,\n        lastActivity: room.lastActivity,\n        hasPassword: !!room.passwordHash,\n        destruct: room.destruct,\n        connectedUsers: meta?.connectedUsers || 0\n      };\n    });\n    return Response.json({ rooms, total: db.countRooms() }, { headers: corsHeaders });\n  }\n\n  if (path.match(/^\\/api\\/admin\\/rooms\\/[\\w-]+$/) && req.method === \"DELETE\") {\n    const { user, apiKey } = await validateAdminOrApiKey(req, \"admin\");\n    if (!user && !apiKey) {\n      return Response.json({ error: \"Unauthorized\" }, { status: 401, headers: corsHeaders });\n    }\n    const id = path.split(\"/\")[4];\n    if (!isValidUUID(id)) {\n      return Response.json({ error: \"Invalid room ID\" }, { status: 400, headers: corsHeaders });\n    }\n    if (deleteRoomData(id)) {\n      const actor = user ? user.id : `apikey:${apiKey!.name}`;\n      db.addAuditLog({ timestamp: new Date().toISOString(), action: \"room_deleted\", actor, actorIp: getClientIP(req), targetType: \"room\", targetId: id });\n      return Response.json({ deleted: true }, { headers: corsHeaders });\n    }\n    return Response.json({ error: \"Room not found\" }, { status: 404, headers: corsHeaders });\n  }\n\n  return null;\n}\n"
  },
  {
    "path": "theonefile_verse/src/routes/admin-settings.ts",
    "content": "import * as db from \"../database\";\nimport { getSettings, updateSettings, saveSettings, validateAdminPath, ENV_ADMIN_PASSWORD, APP_VERSION } from \"../config\";\nimport { getClientIP, apiError, validateAdminUser, validateAdminOrApiKey } from \"../security\";\nimport { hashPassword, theOneFileHtml, currentFileVersion, extractThemePresets, computeSha256Hash, validateTheOneFileHtml, fetchLatestFromGitHub, getExpectedTheOneFileHash, setExpectedTheOneFileHash, setTheOneFileHtml, getTheOneFilePath, restartUpdateTimer, clearUpdateTimer, isValidWebhookUrl, restartBackupTimer, extractVersionFromHtml, isNewerVersion } from \"../rooms\";\n\nconst GITHUB_RAW_URL = \"https://raw.githubusercontent.com/gelatinescreams/The-One-File/main/theonefile-networkening.html\";\n\nexport async function handle(req: Request, path: string, url: URL, corsHeaders: Record<string, string>): Promise<Response | null> {\n  if (path === \"/api/admin/settings\" && req.method === \"GET\") {\n    const { user: adminUser, apiKey } = await validateAdminOrApiKey(req, \"read\");\n    if (!adminUser && !apiKey) {\n      return Response.json({ error: \"Unauthorized\" }, { status: 401, headers: corsHeaders });\n    }\n    const settings = getSettings();\n    const { instancePasswordHash, ...safeSettings } = settings;\n    const fileValidation = validateTheOneFileHtml(theOneFileHtml);\n    const currentFileHash = theOneFileHtml ? await computeSha256Hash(theOneFileHtml) : null;\n    const expectedFileHash = getExpectedTheOneFileHash();\n    return Response.json({\n      ...safeSettings,\n      instancePasswordSet: !!instancePasswordHash,\n      envAdminPasswordSet: !!ENV_ADMIN_PASSWORD,\n      currentFileSize: theOneFileHtml.length,\n      currentFileEdition: fileValidation.valid ? fileValidation.edition : \"invalid\",\n      currentFileHash,\n      expectedFileHash,\n      integrityCheckEnabled: !!expectedFileHash,\n      integrityCheckPassed: expectedFileHash ? currentFileHash === expectedFileHash : null,\n      availableThemes: extractThemePresets(),\n      currentFileVersion: currentFileVersion || \"unknown\",\n      lastUpdateTimestamp: db.getSetting(\"lastUpdateTimestamp\") || null,\n      latestGitHubVersion: db.getSetting(\"latestGitHubVersion\") || null,\n      lastVersionCheck: db.getSetting(\"lastVersionCheck\") || null\n    }, { headers: corsHeaders });\n  }\n\n  if (path === \"/api/admin/settings\" && req.method === \"POST\") {\n    const { user: adminUser, apiKey } = await validateAdminOrApiKey(req, \"admin\");\n    if (!adminUser && !apiKey) {\n      return Response.json({ error: \"Unauthorized\" }, { status: 401, headers: corsHeaders });\n    }\n    try {\n      const body = await req.json();\n\n      if (body.instancePassword !== undefined && body.instancePassword !== \"\" && body.instancePassword.length < 10) {\n        return Response.json({ error: \"Password must be at least 10 characters\" }, { status: 400, headers: corsHeaders });\n      }\n      if (body.webhookUrl && !isValidWebhookUrl(body.webhookUrl)) {\n        return Response.json({ error: \"Invalid webhook URL: must be http(s) and not point to private/internal addresses\" }, { status: 400, headers: corsHeaders });\n      }\n      if (body.adminPath !== undefined) {\n        const validation = validateAdminPath(body.adminPath);\n        if (!validation.valid) {\n          return Response.json({ error: validation.error }, { status: 400, headers: corsHeaders });\n        }\n      }\n      if (body.theOneFileHash !== undefined && body.theOneFileHash !== \"\" && body.theOneFileHash !== null && body.theOneFileHash !== \"current\") {\n        if (typeof body.theOneFileHash !== \"string\" || !/^[a-f0-9]{64}$/i.test(body.theOneFileHash)) {\n          return Response.json({ error: \"Invalid hash format. Must be 64 hex characters, 'current', or empty to disable.\" }, { status: 400, headers: corsHeaders });\n        }\n      }\n\n      const settings = { ...getSettings() };\n\n      if (typeof body.instancePasswordEnabled === \"boolean\") {\n        settings.instancePasswordEnabled = body.instancePasswordEnabled;\n      }\n      if (body.instancePassword !== undefined) {\n        if (body.instancePassword === \"\") {\n          settings.instancePasswordHash = null;\n        } else if (body.instancePassword.length >= 10) {\n          settings.instancePasswordHash = await hashPassword(body.instancePassword);\n        }\n      }\n      if (typeof body.updateIntervalHours === \"number\") {\n        settings.updateIntervalHours = Math.max(0, body.updateIntervalHours);\n      }\n      if (typeof body.skipUpdates === \"boolean\") {\n        settings.skipUpdates = body.skipUpdates;\n      }\n      if (typeof body.allowPublicRoomCreation === \"boolean\") {\n        settings.allowPublicRoomCreation = body.allowPublicRoomCreation;\n      }\n      if (typeof body.maxRoomsPerInstance === \"number\") {\n        settings.maxRoomsPerInstance = Math.max(0, body.maxRoomsPerInstance);\n      }\n      if (body.defaultDestructMode && [\"time\", \"empty\", \"never\"].includes(body.defaultDestructMode)) {\n        settings.defaultDestructMode = body.defaultDestructMode;\n      }\n      if (typeof body.defaultDestructHours === \"number\") {\n        settings.defaultDestructHours = Math.max(1, body.defaultDestructHours);\n      }\n      if (body.forcedTheme && [\"user\", \"dark\", \"light\"].includes(body.forcedTheme)) {\n        settings.forcedTheme = body.forcedTheme;\n      }\n      if (typeof body.defaultRoomTheme === 'string') {\n        const validKeys = extractThemePresets().map(t => t.key);\n        if (body.defaultRoomTheme === '' || validKeys.includes(body.defaultRoomTheme)) {\n          settings.defaultRoomTheme = body.defaultRoomTheme;\n        }\n      }\n      if (typeof body.rateLimitEnabled === \"boolean\") {\n        settings.rateLimitEnabled = body.rateLimitEnabled;\n      }\n      if (typeof body.rateLimitWindow === \"number\") {\n        settings.rateLimitWindow = Math.max(10, Math.min(3600, body.rateLimitWindow));\n      }\n      if (typeof body.rateLimitMaxAttempts === \"number\") {\n        settings.rateLimitMaxAttempts = Math.max(1, Math.min(100, body.rateLimitMaxAttempts));\n      }\n      if (typeof body.chatEnabled === \"boolean\") {\n        settings.chatEnabled = body.chatEnabled;\n      }\n      if (typeof body.cursorSharingEnabled === \"boolean\") {\n        settings.cursorSharingEnabled = body.cursorSharingEnabled;\n      }\n      if (typeof body.nameChangeEnabled === \"boolean\") {\n        settings.nameChangeEnabled = body.nameChangeEnabled;\n      }\n      if (typeof body.probeEnabled === \"boolean\") {\n        settings.probeEnabled = body.probeEnabled;\n      }\n      if (typeof body.discoveryEnabled === \"boolean\") {\n        settings.discoveryEnabled = body.discoveryEnabled;\n      }\n      if (typeof body.discoveryAdminOnly === \"boolean\") {\n        settings.discoveryAdminOnly = body.discoveryAdminOnly;\n      }\n      if (typeof body.discoveryAllowPublicRanges === \"boolean\") {\n        settings.discoveryAllowPublicRanges = body.discoveryAllowPublicRanges;\n      }\n      if (typeof body.discoveryMaxPrefix === \"number\") {\n        settings.discoveryMaxPrefix = Math.max(20, Math.min(32, body.discoveryMaxPrefix));\n      }\n      if (typeof body.webhookEnabled === \"boolean\") {\n        settings.webhookEnabled = body.webhookEnabled;\n      }\n      if (body.webhookUrl !== undefined) {\n        settings.webhookUrl = body.webhookUrl || null;\n      }\n      if (typeof body.backupEnabled === \"boolean\") {\n        settings.backupEnabled = body.backupEnabled;\n      }\n      if (typeof body.backupIntervalHours === \"number\") {\n        settings.backupIntervalHours = Math.max(1, Math.min(168, body.backupIntervalHours));\n      }\n      if (typeof body.backupRetentionCount === \"number\") {\n        settings.backupRetentionCount = Math.max(1, Math.min(100, body.backupRetentionCount));\n      }\n      if (body.adminPath !== undefined) {\n        settings.adminPath = body.adminPath;\n      }\n      if (typeof body.showAdminLink === \"boolean\") {\n        settings.showAdminLink = body.showAdminLink;\n      }\n      if (typeof body.forceWelcomeModal === \"boolean\") {\n        settings.forceWelcomeModal = body.forceWelcomeModal;\n      }\n\n      if (body.theOneFileHash !== undefined) {\n        if (body.theOneFileHash === \"\" || body.theOneFileHash === null) {\n          db.deleteSetting(\"theOneFileHash\");\n          console.log(\"[Security] TheOneFile integrity checking disabled by admin\");\n        } else if (body.theOneFileHash === \"current\") {\n          if (theOneFileHtml) {\n            const currentHash = await computeSha256Hash(theOneFileHtml);\n            setExpectedTheOneFileHash(currentHash);\n            console.log(`[Security] TheOneFile integrity hash set to current file: ${currentHash.substring(0, 16)}...`);\n          }\n        } else {\n          setExpectedTheOneFileHash(body.theOneFileHash.toLowerCase());\n          console.log(`[Security] TheOneFile integrity hash set: ${body.theOneFileHash.substring(0, 16)}...`);\n        }\n      }\n\n      updateSettings(settings);\n      saveSettings(settings);\n\n      if (typeof body.updateIntervalHours === \"number\" || typeof body.skipUpdates === \"boolean\") {\n        restartUpdateTimer();\n      }\n      if (typeof body.backupEnabled === \"boolean\" || typeof body.backupIntervalHours === \"number\") {\n        restartBackupTimer();\n      }\n\n      const actor = adminUser ? adminUser.id : `apikey:${apiKey!.name}`;\n      db.addAuditLog({ timestamp: new Date().toISOString(), action: \"settings_changed\", actor, actorIp: getClientIP(req), details: body });\n      return Response.json({ success: true }, { headers: corsHeaders });\n    } catch (e: any) { return apiError(e, corsHeaders); }\n  }\n\n  if (path === \"/api/admin/update\" && req.method === \"POST\") {\n    const { user: adminUser, apiKey } = await validateAdminOrApiKey(req, \"admin\");\n    if (!adminUser && !apiKey) {\n      return Response.json({ error: \"Unauthorized\" }, { status: 401, headers: corsHeaders });\n    }\n    const settings = getSettings();\n    if (settings.skipUpdates) {\n      return Response.json({ error: \"Updates disabled\" }, { status: 400, headers: corsHeaders });\n    }\n    const previousVersion = currentFileVersion;\n    const success = await fetchLatestFromGitHub();\n    return Response.json({ success, size: theOneFileHtml.length, version: currentFileVersion, previousVersion }, { headers: corsHeaders });\n  }\n\n  if (path === \"/api/admin/version-check\" && req.method === \"GET\") {\n    const { user: adminUser, apiKey } = await validateAdminOrApiKey(req, \"read\");\n    if (!adminUser && !apiKey) {\n      return Response.json({ error: \"Unauthorized\" }, { status: 401, headers: corsHeaders });\n    }\n    try {\n      const res = await fetch(GITHUB_RAW_URL, { signal: AbortSignal.timeout(15000) });\n      if (!res.ok) {\n        return Response.json({ error: \"GitHub unreachable\" }, { status: 502, headers: corsHeaders });\n      }\n      const html = await res.text();\n      const latestVersion = extractVersionFromHtml(html);\n      const now = new Date().toISOString();\n      db.setSetting(\"latestGitHubVersion\", latestVersion);\n      db.setSetting(\"lastVersionCheck\", now);\n      return Response.json({\n        currentVersion: currentFileVersion || \"unknown\",\n        latestVersion,\n        updateAvailable: latestVersion !== \"unknown\" && currentFileVersion !== \"unknown\" && isNewerVersion(latestVersion, currentFileVersion),\n        lastChecked: now\n      }, { headers: corsHeaders });\n    } catch (e: any) {\n      return Response.json({ error: e.message || \"Version check failed\" }, { status: 500, headers: corsHeaders });\n    }\n  }\n\n  if (path === \"/api/admin/upload-html\" && req.method === \"POST\") {\n    const { user: adminUser, apiKey } = await validateAdminOrApiKey(req, \"admin\");\n    if (!adminUser && !apiKey) {\n      return Response.json({ error: \"Unauthorized\" }, { status: 401, headers: corsHeaders });\n    }\n    try {\n      const formData = await req.formData();\n      const file = formData.get(\"file\") as File | null;\n      if (!file) {\n        return Response.json({ error: \"No file provided\" }, { status: 400, headers: corsHeaders });\n      }\n      if (file.size > 50 * 1024 * 1024) {\n        return Response.json({ error: \"File too large. Maximum size is 50MB.\" }, { status: 400, headers: corsHeaders });\n      }\n      const html = await file.text();\n      const validationResult = validateTheOneFileHtml(html);\n      if (!validationResult.valid) {\n        return Response.json({ error: validationResult.error }, { status: 400, headers: corsHeaders });\n      }\n      const theOneFilePath = getTheOneFilePath();\n      await Bun.write(theOneFilePath, html);\n      setTheOneFileHtml(html);\n      const settings = getSettings();\n      settings.skipUpdates = true;\n      updateSettings(settings);\n      saveSettings(settings);\n      clearUpdateTimer();\n      console.log(`[Upload] Admin uploaded local file (${(html.length / 1024).toFixed(1)}KB) - ${validationResult.edition}`);\n      return Response.json({ success: true, size: html.length, edition: validationResult.edition }, { headers: corsHeaders });\n    } catch (e) {\n      return Response.json({ error: \"Failed to process upload\" }, { status: 500, headers: corsHeaders });\n    }\n  }\n\n  if (path === \"/api/admin/source-mode\" && req.method === \"POST\") {\n    const { user: adminUser, apiKey } = await validateAdminOrApiKey(req, \"admin\");\n    if (!adminUser && !apiKey) {\n      return Response.json({ error: \"Unauthorized\" }, { status: 401, headers: corsHeaders });\n    }\n    try {\n      const body = await req.json();\n      const settings = getSettings();\n      if (body.mode === \"github\") {\n        settings.skipUpdates = false;\n        updateSettings(settings);\n        saveSettings(settings);\n        await fetchLatestFromGitHub();\n        restartUpdateTimer();\n        return Response.json({ success: true, mode: \"github\", size: theOneFileHtml.length }, { headers: corsHeaders });\n      } else if (body.mode === \"local\") {\n        settings.skipUpdates = true;\n        updateSettings(settings);\n        saveSettings(settings);\n        clearUpdateTimer();\n        return Response.json({ success: true, mode: \"local\" }, { headers: corsHeaders });\n      }\n      return Response.json({ error: \"Invalid mode\" }, { status: 400, headers: corsHeaders });\n    } catch (e: any) {\n      console.error(\"[API]\", e.message); return Response.json({ error: \"Invalid request\" }, { status: 400, headers: corsHeaders });\n    }\n  }\n\n  return null;\n}\n"
  },
  {
    "path": "theonefile_verse/src/routes/admin-users.ts",
    "content": "import * as auth from \"../auth\";\nimport * as db from \"../database\";\nimport { getClientIP, apiError, validateAdminUser } from \"../security\";\nimport { getExternalOrigin } from \"../config\";\n\nexport async function handle(req: Request, path: string, url: URL, corsHeaders: Record<string, string>): Promise<Response | null> {\n  if (path === \"/api/admin/users\" && req.method === \"GET\") {\n    const adminUser = await validateAdminUser(req);\n    if (!adminUser) {\n      return Response.json({ error: \"Unauthorized\" }, { status: 401, headers: corsHeaders });\n    }\n    const searchQuery = url.searchParams.get(\"q\") || \"\";\n    const limit = Math.min(parseInt(url.searchParams.get(\"limit\") || \"100\") || 100, 1000);\n    const offset = parseInt(url.searchParams.get(\"offset\") || \"0\");\n    const users = searchQuery ? db.searchUsers(searchQuery, limit, offset) : db.listUsers(limit, offset);\n    const safeUsers = users.map(u => {\n      const { passwordHash, totpSecret, totpBackupCodes, pendingEmailToken, ...safe } = u;\n      return safe;\n    });\n    return Response.json({ users: safeUsers, total: db.countUsers() }, { headers: corsHeaders });\n  }\n\n  if (path === \"/api/admin/users\" && req.method === \"POST\") {\n    const adminUser = await validateAdminUser(req);\n    if (!adminUser) {\n      return Response.json({ error: \"Unauthorized\" }, { status: 401, headers: corsHeaders });\n    }\n    try {\n      const body = await req.json();\n      const result = await auth.adminCreateUser(body.email, body.password, body.displayName, body.role || 'user');\n      if (!result.success) {\n        return Response.json({ error: result.error }, { status: 400, headers: corsHeaders });\n      }\n      db.addAuditLog({ timestamp: new Date().toISOString(), action: \"user_created\", actor: adminUser.id, actorIp: getClientIP(req), targetType: \"user\", targetId: result.userId });\n      return Response.json({ success: true, userId: result.userId }, { headers: corsHeaders });\n    } catch (e: any) { return apiError(e, corsHeaders); }\n  }\n\n  if (path.match(/^\\/api\\/admin\\/users\\/[\\w-]+$/) && req.method === \"PUT\") {\n    const userId = path.split(\"/\")[4];\n    const adminUser = await validateAdminUser(req);\n    if (!adminUser) {\n      return Response.json({ error: \"Unauthorized\" }, { status: 401, headers: corsHeaders });\n    }\n    try {\n      const body = await req.json();\n      const result = auth.adminUpdateUser(userId, body);\n      if (!result.success) {\n        return Response.json({ error: result.error }, { status: 400, headers: corsHeaders });\n      }\n      db.addAuditLog({ timestamp: new Date().toISOString(), action: \"user_updated\", actor: adminUser.id, actorIp: getClientIP(req), targetType: \"user\", targetId: userId, details: body });\n      return Response.json({ success: true }, { headers: corsHeaders });\n    } catch (e: any) { return apiError(e, corsHeaders); }\n  }\n\n  if (path.match(/^\\/api\\/admin\\/users\\/[\\w-]+\\/reset-password$/) && req.method === \"POST\") {\n    const userId = path.split(\"/\")[4];\n    const adminUser = await validateAdminUser(req);\n    if (!adminUser) {\n      return Response.json({ error: \"Unauthorized\" }, { status: 401, headers: corsHeaders });\n    }\n    try {\n      const user = db.getUserById(userId);\n      if (!user) {\n        return Response.json({ error: \"User not found\" }, { status: 404, headers: corsHeaders });\n      }\n      if (!user.email) {\n        return Response.json({ error: \"User has no email address\" }, { status: 400, headers: corsHeaders });\n      }\n      const baseUrl = getExternalOrigin(req);\n      await auth.requestPasswordReset(user.email, baseUrl);\n      db.addAuditLog({ timestamp: new Date().toISOString(), action: \"user_password_reset_sent\", actor: adminUser.id, actorIp: getClientIP(req), targetType: \"user\", targetId: userId });\n      return Response.json({ success: true }, { headers: corsHeaders });\n    } catch (e: any) { return apiError(e, corsHeaders, \"Failed to send reset email\", 500); }\n  }\n\n  if (path.match(/^\\/api\\/admin\\/users\\/[\\w-]+\\/set-password$/) && req.method === \"POST\") {\n    const userId = path.split(\"/\")[4];\n    const adminUser = await validateAdminUser(req);\n    if (!adminUser) {\n      return Response.json({ error: \"Unauthorized\" }, { status: 401, headers: corsHeaders });\n    }\n    try {\n      const body = await req.json();\n      if (!body.password || typeof body.password !== 'string') {\n        return Response.json({ error: \"Password is required\" }, { status: 400, headers: corsHeaders });\n      }\n      const result = await auth.adminResetPassword(userId, body.password);\n      if (!result.success) {\n        return Response.json({ error: result.error }, { status: 400, headers: corsHeaders });\n      }\n      db.addAuditLog({ timestamp: new Date().toISOString(), action: \"user_password_set\", actor: adminUser.id, actorIp: getClientIP(req), targetType: \"user\", targetId: userId });\n      return Response.json({ success: true }, { headers: corsHeaders });\n    } catch (e: any) { return apiError(e, corsHeaders); }\n  }\n\n  if (path.match(/^\\/api\\/admin\\/users\\/[\\w-]+$/) && req.method === \"DELETE\") {\n    const userId = path.split(\"/\")[4];\n    const adminUser = await validateAdminUser(req);\n    if (!adminUser) {\n      return Response.json({ error: \"Unauthorized\" }, { status: 401, headers: corsHeaders });\n    }\n    const result = auth.adminDeleteUser(userId);\n    if (!result.success) {\n      return Response.json({ error: result.error }, { status: 400, headers: corsHeaders });\n    }\n    db.addAuditLog({ timestamp: new Date().toISOString(), action: \"user_deleted\", actor: adminUser.id, actorIp: getClientIP(req), targetType: \"user\", targetId: userId });\n    return Response.json({ success: true }, { headers: corsHeaders });\n  }\n\n  return null;\n}\n"
  },
  {
    "path": "theonefile_verse/src/routes/instance-access.ts",
    "content": "import * as oidc from \"../oidc\";\nimport { ENV_ADMIN_PASSWORD, isInstanceLocked, isAdminRoute, getSettings, verifyAdminPassword, verifyInstancePassword } from \"../config\";\nimport { getClientIP, apiError, serveHtml, getTokenFromRequest, validateAdminUser } from \"../security\";\nimport { checkRateLimit } from \"../rate-limit\";\nimport { validateInstanceToken, storeInstanceToken } from \"../tokens\";\nimport { instanceLoginPageHtml } from \"../templates\";\n\nexport async function handle(req: Request, path: string, url: URL, corsHeaders: Record<string, string>): Promise<Response | null> {\n  if ((ENV_ADMIN_PASSWORD || isInstanceLocked()) && isAdminRoute(path)) {\n    const token = getTokenFromRequest(req);\n    const adminUser = await validateAdminUser(req);\n    const hasInstanceAccess = (token && validateInstanceToken(token)) || adminUser;\n    if (!hasInstanceAccess) {\n      if (path === \"/\" || path === \"/index.html\" || path.startsWith(\"/s/\")) {\n        return serveHtml(instanceLoginPageHtml, 'public', req);\n      }\n      return Response.json({ error: \"Unauthorized\" }, { status: 401, headers: corsHeaders });\n    }\n  }\n\n  if (path === \"/api/instance-login\" && req.method === \"POST\") {\n    if (!ENV_ADMIN_PASSWORD && !isInstanceLocked()) {\n      return Response.json({ error: \"Instance not locked\" }, { status: 400, headers: corsHeaders });\n    }\n    const clientIP = getClientIP(req);\n    const settings = getSettings();\n    if (!(await checkRateLimit(clientIP, \"instance-login\", settings))) {\n      return Response.json({ error: \"Too many attempts. Try again later.\" }, { status: 429, headers: corsHeaders });\n    }\n    try {\n      const body = await req.json();\n      let valid = false;\n      if (ENV_ADMIN_PASSWORD) {\n        valid = await verifyAdminPassword(body.password);\n      } else {\n        valid = await verifyInstancePassword(body.password);\n      }\n      if (valid) {\n        const token = crypto.randomUUID();\n        storeInstanceToken(token);\n        return new Response(JSON.stringify({ success: true }), {\n          headers: { ...corsHeaders, \"Content-Type\": \"application/json\",\n            \"Set-Cookie\": oidc.getSessionCookie(\"collab_token\", token, 604800) }\n        });\n      }\n      return Response.json({ error: \"Invalid password\" }, { status: 403, headers: corsHeaders });\n    } catch (e: any) { return apiError(e, corsHeaders); }\n  }\n\n  return null;\n}\n"
  },
  {
    "path": "theonefile_verse/src/routes/network-routes.ts",
    "content": "import * as network from \"../network\";\nimport * as db from \"../database\";\nimport { getSettings, isValidUUID } from \"../config\";\nimport { getClientIP, apiError, validateRequestCsrf, csrfReject, validateAdminUser } from \"../security\";\nimport { checkRateLimit } from \"../rate-limit\";\nimport { roomConnections } from \"../rooms\";\nimport { fetchLatestFromGitHub, theOneFileHtml } from \"../rooms\";\n\nexport async function handle(req: Request, path: string, url: URL, corsHeaders: Record<string, string>): Promise<Response | null> {\n  const settings = getSettings();\n\n  if (path === \"/api/probe\" && req.method === \"POST\") {\n    if (!validateRequestCsrf(req)) return csrfReject(corsHeaders);\n    if (!settings.probeEnabled) {\n      return Response.json({ error: \"Probing is disabled\" }, { status: 403, headers: corsHeaders });\n    }\n    const clientIP = getClientIP(req);\n    if (!(await checkRateLimit(clientIP, \"probe\", settings, 60))) {\n      return Response.json({ error: \"Too many probe requests. Try again later.\" }, { status: 429, headers: corsHeaders });\n    }\n    try {\n      const body = await req.json();\n      const target = body.target;\n      const probes = body.probes;\n      const timeout = Math.min(Math.max(parseInt(body.timeout) || 3000, 1000), 10000);\n      if (!target || typeof target !== \"string\" || !network.validateTarget(target)) {\n        return Response.json({ error: \"Invalid target\" }, { status: 400, headers: corsHeaders });\n      }\n      if (!network.validateProbeConfig(probes)) {\n        return Response.json({ error: \"Invalid probe configuration\" }, { status: 400, headers: corsHeaders });\n      }\n      const result = await network.probeTarget(target, probes, timeout);\n      return Response.json(result, { headers: corsHeaders });\n    } catch (e: any) { return apiError(e, corsHeaders); }\n  }\n\n  if (path === \"/api/probe/batch\" && req.method === \"POST\") {\n    if (!validateRequestCsrf(req)) return csrfReject(corsHeaders);\n    if (!settings.probeEnabled) {\n      return Response.json({ error: \"Probing is disabled\" }, { status: 403, headers: corsHeaders });\n    }\n    const clientIP = getClientIP(req);\n    if (!(await checkRateLimit(clientIP, \"probe-batch\", settings, 30))) {\n      return Response.json({ error: \"Too many batch probe requests. Try again later.\" }, { status: 429, headers: corsHeaders });\n    }\n    try {\n      const body = await req.json();\n      if (!body.targets || !Array.isArray(body.targets) || body.targets.length === 0 || body.targets.length > 50) {\n        return Response.json({ error: \"Invalid targets (1-50 required)\" }, { status: 400, headers: corsHeaders });\n      }\n      const validTargets: network.BatchTarget[] = [];\n      for (const t of body.targets) {\n        if (!t.nodeId || typeof t.nodeId !== \"string\") continue;\n        if (!t.target || !network.validateTarget(t.target)) continue;\n        if (!network.validateProbeConfig(t.probes)) continue;\n        const timeout = Math.min(Math.max(parseInt(t.timeout) || 3000, 1000), 10000);\n        validTargets.push({ nodeId: t.nodeId, target: t.target, probes: t.probes, timeout });\n      }\n      if (validTargets.length === 0) {\n        return Response.json({ error: \"No valid targets\" }, { status: 400, headers: corsHeaders });\n      }\n      const results = await network.probeBatch(validTargets);\n      return Response.json({ results }, { headers: corsHeaders });\n    } catch (e: any) { return apiError(e, corsHeaders); }\n  }\n\n  if (path === \"/api/discover/ports\" && req.method === \"GET\") {\n    return Response.json({ ports: network.getDefaultPortList() }, { headers: corsHeaders });\n  }\n\n  if (path === \"/api/discover\" && req.method === \"POST\") {\n    if (!validateRequestCsrf(req)) return csrfReject(corsHeaders);\n    if (!settings.discoveryEnabled) {\n      return Response.json({ error: \"Discovery is disabled\" }, { status: 403, headers: corsHeaders });\n    }\n    if (settings.discoveryAdminOnly) {\n      const adminUser = await validateAdminUser(req);\n      if (!adminUser) {\n        return Response.json({ error: \"Discovery requires admin access\" }, { status: 401, headers: corsHeaders });\n      }\n    }\n    const clientIP = getClientIP(req);\n    if (!(await checkRateLimit(clientIP, \"discover\", settings))) {\n      return Response.json({ error: \"Too many discovery requests. Try again later.\" }, { status: 429, headers: corsHeaders });\n    }\n    try {\n      const body = await req.json();\n      const roomId = body.roomId;\n      if (!roomId || !isValidUUID(roomId)) {\n        return Response.json({ error: \"Valid room ID required\" }, { status: 400, headers: corsHeaders });\n      }\n      let cidrs: string[] = [];\n      if (Array.isArray(body.cidrs) && body.cidrs.length > 0) {\n        cidrs = body.cidrs.filter((c: unknown) => typeof c === \"string\" && c.length > 0);\n      } else if (body.cidr && typeof body.cidr === \"string\") {\n        cidrs = [body.cidr];\n      }\n      if (cidrs.length === 0) {\n        return Response.json({ error: \"At least one CIDR range required\" }, { status: 400, headers: corsHeaders });\n      }\n      if (cidrs.length > 10) {\n        return Response.json({ error: \"Maximum 10 ranges per scan\" }, { status: 400, headers: corsHeaders });\n      }\n      let totalCount = 0;\n      for (const cidr of cidrs) {\n        const validation = network.validateCIDR(cidr, settings.discoveryAllowPublicRanges, settings.discoveryMaxPrefix);\n        if (!validation.valid) {\n          return Response.json({ error: `${cidr}: ${validation.error}` }, { status: 400, headers: corsHeaders });\n        }\n        totalCount += validation.count || 0;\n      }\n      const taskPrefix = `disc-${roomId}`;\n      if (network.hasActiveDiscoveryForRoom(roomId, taskPrefix)) {\n        return Response.json({ error: \"A scan is already running for this room\" }, { status: 409, headers: corsHeaders });\n      }\n      const taskId = `${taskPrefix}-${Date.now()}`;\n      const options: network.DiscoveryOptions = {\n        icmp: body.options?.icmp !== false,\n        tcp: body.options?.tcp !== false,\n        dns: body.options?.dns !== false,\n        netbios: body.options?.netbios !== false,\n        mdns: body.options?.mdns !== false,\n        snmp: body.options?.snmp === true,\n        snmpCommunity: (typeof body.options?.snmpCommunity === \"string\" && body.options.snmpCommunity.length <= 64) ? body.options.snmpCommunity : \"public\",\n        ports: Array.isArray(body.options?.ports) ? body.options.ports.filter((p: unknown) => typeof p === \"number\" && network.validatePort(p)) : [],\n      };\n      const connections = roomConnections.get(roomId);\n      await network.startDiscovery(\n        taskId,\n        roomId,\n        cidrs,\n        options,\n        (percent, scanned, total, rangeIndex, totalRanges) => {\n          if (connections) {\n            const msg = JSON.stringify({ type: \"discovery-progress\", taskId, percent, scanned, total, rangeIndex, totalRanges });\n            for (const ws of connections) ws.send(msg);\n          }\n        },\n        (host) => {\n          if (connections) {\n            const msg = JSON.stringify({ type: \"discovery-found\", taskId, host });\n            for (const ws of connections) ws.send(msg);\n          }\n        },\n        (totalFound) => {\n          if (connections) {\n            const msg = JSON.stringify({ type: \"discovery-complete\", taskId, totalFound });\n            for (const ws of connections) ws.send(msg);\n          }\n        },\n      );\n      return Response.json({ taskId, count: totalCount, ranges: cidrs.length }, { headers: corsHeaders });\n    } catch (e: any) { return apiError(e, corsHeaders); }\n  }\n\n  if (path === \"/api/discover/cancel\" && req.method === \"POST\") {\n    if (!validateRequestCsrf(req)) return csrfReject(corsHeaders);\n    try {\n      const body = await req.json();\n      if (!body.taskId || typeof body.taskId !== \"string\") {\n        return Response.json({ error: \"Task ID required\" }, { status: 400, headers: corsHeaders });\n      }\n      const cancelled = network.cancelDiscovery(body.taskId);\n      return Response.json({ cancelled }, { headers: corsHeaders });\n    } catch (e: any) { return apiError(e, corsHeaders); }\n  }\n\n  if (path === \"/api/discover/deepscan\" && req.method === \"POST\") {\n    if (!validateRequestCsrf(req)) return csrfReject(corsHeaders);\n    if (!settings.discoveryEnabled) {\n      return Response.json({ error: \"Discovery is disabled\" }, { status: 403, headers: corsHeaders });\n    }\n    if (settings.discoveryAdminOnly) {\n      const adminUser = await validateAdminUser(req);\n      if (!adminUser) {\n        return Response.json({ error: \"Discovery requires admin access\" }, { status: 401, headers: corsHeaders });\n      }\n    }\n    const clientIP = getClientIP(req);\n    if (!(await checkRateLimit(clientIP, \"deepscan\", settings, 10, 120))) {\n      return Response.json({ error: \"Too many deep scan requests. Try again later.\" }, { status: 429, headers: corsHeaders });\n    }\n    try {\n      const body = await req.json();\n      const roomId = body.roomId;\n      if (!roomId || !isValidUUID(roomId)) {\n        return Response.json({ error: \"Valid room ID required\" }, { status: 400, headers: corsHeaders });\n      }\n      const ip = body.ip;\n      if (!ip || typeof ip !== \"string\" || !network.validateIPv4(ip)) {\n        return Response.json({ error: \"Valid IP address required\" }, { status: 400, headers: corsHeaders });\n      }\n      if (!network.isRFC1918(ip)) {\n        return Response.json({ error: \"Only private IP addresses allowed for deep scan\" }, { status: 400, headers: corsHeaders });\n      }\n      if (network.hasActiveDeepScan(ip)) {\n        return Response.json({ error: \"A deep scan is already running for this host\" }, { status: 409, headers: corsHeaders });\n      }\n      const existingPorts: number[] = Array.isArray(body.existingPorts)\n        ? body.existingPorts.filter((p: unknown) => typeof p === \"number\" && network.validatePort(p))\n        : [];\n      const scanId = `deepscan-${ip}-${Date.now()}`;\n      const connections = roomConnections.get(roomId);\n      await network.startDeepScan(\n        scanId,\n        ip,\n        existingPorts,\n        (percent, scanned, total) => {\n          if (connections) {\n            const msg = JSON.stringify({ type: \"deepscan-progress\", scanId, ip, percent, scanned, total });\n            for (const ws of connections) ws.send(msg);\n          }\n        },\n        (newPorts, newServices, containers) => {\n          if (connections) {\n            const newIcons = network.getServiceIcons(newPorts);\n            const msg = JSON.stringify({ type: \"deepscan-update\", scanId, ip, newPorts, newServices, containers, newIcons });\n            for (const ws of connections) ws.send(msg);\n          }\n        },\n        () => {\n          if (connections) {\n            const msg = JSON.stringify({ type: \"deepscan-complete\", scanId, ip });\n            for (const ws of connections) ws.send(msg);\n          }\n        },\n      );\n      return Response.json({ scanId, ip }, { headers: corsHeaders });\n    } catch (e: any) { return apiError(e, corsHeaders); }\n  }\n\n  if (path === \"/api/discover/deepscan/cancel\" && req.method === \"POST\") {\n    if (!validateRequestCsrf(req)) return csrfReject(corsHeaders);\n    try {\n      const body = await req.json();\n      if (!body.scanId || typeof body.scanId !== \"string\") {\n        return Response.json({ error: \"Scan ID required\" }, { status: 400, headers: corsHeaders });\n      }\n      const cancelled = network.cancelDeepScan(body.scanId);\n      return Response.json({ cancelled }, { headers: corsHeaders });\n    } catch (e: any) { return apiError(e, corsHeaders); }\n  }\n\n  if (path === \"/api/refresh\" && req.method === \"POST\") {\n    const adminUser = await validateAdminUser(req);\n    if (!adminUser) {\n      return Response.json({ error: \"Unauthorized\" }, { status: 401, headers: corsHeaders });\n    }\n    if (settings.skipUpdates) return Response.json({ error: \"Updates disabled\" }, { status: 400, headers: corsHeaders });\n    const success = await fetchLatestFromGitHub();\n    return Response.json({ success, size: theOneFileHtml.length }, { headers: corsHeaders });\n  }\n\n  return null;\n}\n"
  },
  {
    "path": "theonefile_verse/src/routes/public.ts",
    "content": "import { join } from \"path\";\nimport * as oidc from \"../oidc\";\nimport * as db from \"../database\";\nimport * as redis from \"../redis\";\nimport { APP_VERSION, getSettings, isValidUUID, getAdminPath } from \"../config\";\nimport { serveHtml, securityHeaders, getUserTokenFromRequest, validateAdminUser, relativeRedirect } from \"../security\";\nimport { theOneFileHtml, loadRoom } from \"../rooms\";\n\nconst STATIC_FILES: Record<string, { path: string; type: string; cache: string }> = {\n  \"/collab.js\": { path: \"public/collab.js\", type: \"application/javascript\", cache: \"no-cache\" },\n  \"/collab-init.js\": { path: \"public/collab-init.js\", type: \"application/javascript\", cache: \"no-cache\" },\n  \"/collab-save-hook.js\": { path: \"public/collab-save-hook.js\", type: \"application/javascript\", cache: \"no-cache\" },\n  \"/collab.css\": { path: \"public/collab.css\", type: \"text/css\", cache: \"no-cache\" },\n  \"/landing.js\": { path: \"public/landing.js\", type: \"application/javascript\", cache: \"public, max-age=3600\" },\n  \"/admin-dashboard.js\": { path: \"public/admin-dashboard.js\", type: \"application/javascript\", cache: \"public, max-age=3600\" },\n  \"/admin-auth.js\": { path: \"public/admin-auth.js\", type: \"application/javascript\", cache: \"public, max-age=3600\" },\n  \"/admin-pages.js\": { path: \"public/admin-pages.js\", type: \"application/javascript\", cache: \"public, max-age=3600\" },\n  \"/qrcode.min.js\": { path: \"public/qrcode.min.js\", type: \"application/javascript\", cache: \"public, max-age=86400\" }\n};\n\nexport async function handle(req: Request, path: string, url: URL, corsHeaders: Record<string, string>): Promise<Response | null> {\n  const settings = getSettings();\n\n  if (path === \"/api/version\" && req.method === \"GET\") {\n    return Response.json({ version: APP_VERSION }, { headers: corsHeaders });\n  }\n\n  if (path === \"/api/health\" && req.method === \"GET\") {\n    const uptime = process.uptime();\n    let dbOk = false;\n    try { dbOk = db.healthCheck(); } catch {}\n    let redisOk = false;\n    try { redisOk = await redis.ping(); } catch {}\n    const status = dbOk ? \"ok\" : \"degraded\";\n    return Response.json({\n      status,\n      version: APP_VERSION,\n      uptime: Math.floor(uptime),\n      components: { database: dbOk ? \"ok\" : \"error\", redis: redisOk ? \"ok\" : \"unavailable\" },\n      rooms: db.countRooms()\n    }, { headers: corsHeaders });\n  }\n\n  if (path === \"/api/theme\" && req.method === \"GET\") {\n    const authSettings = oidc.getAuthSettings();\n    return Response.json({\n      forcedTheme: settings.forcedTheme,\n      chatEnabled: settings.chatEnabled,\n      cursorSharingEnabled: settings.cursorSharingEnabled,\n      nameChangeEnabled: settings.nameChangeEnabled,\n      shareButtonEnabled: authSettings.shareButtonEnabled,\n      showAdminLink: settings.showAdminLink,\n      ...(settings.showAdminLink ? { adminPath: getAdminPath() } : {})\n    }, { headers: corsHeaders });\n  }\n\n  if (path.match(/^\\/s\\/[\\w-]+$/)) {\n    const id = path.split(\"/\")[2];\n    if (!isValidUUID(id)) {\n      return relativeRedirect(\"/?error=invalid_room\");\n    }\n    const room = loadRoom(id);\n    if (!room) return relativeRedirect(\"/?error=room_not_found\");\n    if (!theOneFileHtml) return relativeRedirect(\"/?error=room_unavailable\");\n\n    let injectedHtml = theOneFileHtml.replace('<head>', '<head><script src=\"/collab-init.js\"></script>');\n\n    const adminUser = await validateAdminUser(req);\n    const isAdmin = !!adminUser;\n\n    let isOwner = false;\n    if (room.ownerUserId) {\n      const userToken = getUserTokenFromRequest(req);\n      if (userToken) {\n        const user = await oidc.validateUserSessionToken(userToken);\n        if (user && user.id === room.ownerUserId) isOwner = true;\n      }\n    }\n\n    const roomCsrfToken = oidc.generateCsrfToken();\n    const safeRoomConfig = JSON.stringify({\n      roomId: id,\n      roomHasPassword: !!room.passwordHash && !isAdmin,\n      isAdmin,\n      csrfToken: roomCsrfToken,\n      isCreator: isOwner || isAdmin,\n      defaultRoomTheme: settings.defaultRoomTheme || '',\n      forceWelcomeModal: settings.forceWelcomeModal || false,\n      probeEnabled: settings.probeEnabled,\n      discoveryEnabled: settings.discoveryEnabled,\n      discoveryAllowed: settings.discoveryEnabled && (isAdmin || !settings.discoveryAdminOnly)\n    });\n\n    const configBlock = `<script type=\"application/json\" id=\"room-config\">${safeRoomConfig}</script>\n<link rel=\"stylesheet\" href=\"/collab.css\">\n<script src=\"/collab.js\"></script>\n<script src=\"/collab-save-hook.js\"></script>\n</body>`;\n\n    injectedHtml = injectedHtml.replace(/<\\/body>\\s*<\\/html>\\s*$/i, configBlock + \"\\n</html>\");\n\n    return serveHtml(injectedHtml, 'room', req);\n  }\n\n  if (path === \"/\" || path === \"/index.html\") {\n    const file = Bun.file(join(process.cwd(), \"public\", \"index.html\"));\n    if (await file.exists()) return serveHtml(await file.text(), 'public', req);\n  }\n\n  const staticEntry = STATIC_FILES[path];\n  if (staticEntry) {\n    const file = Bun.file(join(process.cwd(), staticEntry.path));\n    if (await file.exists()) {\n      return new Response(file, {\n        headers: { \"Content-Type\": staticEntry.type, ...securityHeaders, \"Cache-Control\": staticEntry.cache }\n      });\n    }\n  }\n\n  if (path === \"/favicon.ico\") {\n    return new Response(null, { status: 204 });\n  }\n\n  return null;\n}\n"
  },
  {
    "path": "theonefile_verse/src/routes/room.ts",
    "content": "import * as oidc from \"../oidc\";\nimport * as db from \"../database\";\nimport { getSettings, isValidUUID } from \"../config\";\nimport { getClientIP, apiError, validateRequestCsrf, csrfReject, getUserTokenFromRequest, validateAdminUser } from \"../security\";\nimport { checkRateLimit } from \"../rate-limit\";\nimport { generateWsSessionToken, generateRoomAccessToken, validateRoomAccessToken, WS_TOKEN_EXPIRY } from \"../tokens\";\nimport { loadRoom, saveRoom, deleteRoomData, scheduleDestruction, hashPassword, verifyPassword, validateTopology, sendWebhook, type Room } from \"../rooms\";\n\nconst ROOM_ACCESS_COOKIE_REGEX = /(?:^|;\\s*)room_access=([^;]+)/;\nconst HOST_ROOM_ACCESS_COOKIE_REGEX = /(?:^|;\\s*)__Host-room_access=([^;]+)/;\n\nfunction extractRoomAccessCookie(cookies: string, prodMode: boolean): string {\n  const match = cookies.match(prodMode ? HOST_ROOM_ACCESS_COOKIE_REGEX : ROOM_ACCESS_COOKIE_REGEX);\n  return match ? match[1] : '';\n}\n\nexport async function handle(req: Request, path: string, url: URL, corsHeaders: Record<string, string>): Promise<Response | null> {\n  const settings = getSettings();\n\n  if (path === \"/api/room\" && req.method === \"POST\") {\n    if (!validateRequestCsrf(req)) return csrfReject(corsHeaders);\n    const clientIP = getClientIP(req);\n    if (!(await checkRateLimit(clientIP, \"room-create\", settings))) {\n      return Response.json({ error: \"Too many rooms created. Try again later.\" }, { status: 429, headers: corsHeaders });\n    }\n    try {\n      const body = await req.json();\n      const creatorId = body.creatorId && isValidUUID(body.creatorId) ? body.creatorId : crypto.randomUUID();\n      const validDestructModes = [\"time\", \"empty\", \"never\"];\n      const destructMode = validDestructModes.includes(body.destructMode) ? body.destructMode : \"time\";\n      const maxDestructValue = 30 * 24 * 60 * 60 * 1000;\n      const destructValue = typeof body.destructValue === \"number\"\n        ? Math.min(Math.max(0, body.destructValue), maxDestructValue)\n        : 86400000;\n      if (body.password && body.password.length > 0 && body.password.length < 8) {\n        return Response.json({ error: \"Room password must be at least 8 characters\" }, { status: 400, headers: corsHeaders });\n      }\n      const passwordHash = body.password && body.password.length >= 8\n        ? await hashPassword(body.password)\n        : null;\n\n      const id = crypto.randomUUID();\n\n      const userToken = getUserTokenFromRequest(req);\n      let ownerUserId: string | null = null;\n      if (userToken) {\n        const user = await oidc.validateUserSessionToken(userToken);\n        if (user) ownerUserId = user.id;\n      }\n\n      const authSettings = oidc.getAuthSettings();\n      if (!authSettings.allowGuestRoomCreation && !ownerUserId) {\n        return Response.json({ error: \"Please sign in to create a room\" }, { status: 401, headers: corsHeaders });\n      }\n\n      if (settings.maxRoomsPerInstance > 0 && db.countRooms() >= settings.maxRoomsPerInstance) {\n        return Response.json({ error: \"Maximum number of rooms reached\" }, { status: 403, headers: corsHeaders });\n      }\n\n      const allowGuests = body.allowGuests !== false && authSettings.allowGuestRoomJoin;\n\n      let validatedTopology = null;\n      if (body.topology) {\n        const topologyValidation = validateTopology(body.topology);\n        if (!topologyValidation.valid) {\n          return Response.json({ error: topologyValidation.error || \"Invalid topology data\" }, { status: 400, headers: corsHeaders });\n        }\n        validatedTopology = topologyValidation.sanitized;\n      }\n\n      const room: Room = {\n        id,\n        created: new Date().toISOString(),\n        lastActivity: new Date().toISOString(),\n        creatorId,\n        passwordHash,\n        destruct: { mode: destructMode, value: destructValue },\n        topology: validatedTopology,\n        ownerUserId,\n        allowGuests\n      };\n      saveRoom(room);\n      if (room.destruct.mode === \"time\") scheduleDestruction(id, room.destruct.value);\n      sendWebhook(\"room_created\", { roomId: id, hasPassword: !!room.passwordHash, destructMode, creatorId });\n      return Response.json({ id, url: `/s/${id}`, hasPassword: !!room.passwordHash, allowGuests: room.allowGuests }, { headers: corsHeaders });\n    } catch (e: any) { return apiError(e, corsHeaders); }\n  }\n\n  if (path.match(/^\\/api\\/room\\/[\\w-]+\\/verify$/) && req.method === \"POST\") {\n    const id = path.split(\"/\")[3];\n    if (!isValidUUID(id)) {\n      return Response.json({ error: \"Invalid room ID\" }, { status: 400, headers: corsHeaders });\n    }\n    const room = loadRoom(id);\n    if (!room) return Response.json({ error: \"Room not found\" }, { status: 404, headers: corsHeaders });\n    if (!room.passwordHash) return Response.json({ valid: true }, { headers: corsHeaders });\n    const clientIP = getClientIP(req);\n    if (!(await checkRateLimit(clientIP, `room-verify-${id}`, settings))) {\n      return Response.json({ error: \"Too many attempts. Try again later.\" }, { status: 429, headers: corsHeaders });\n    }\n    try {\n      const body = await req.json();\n      const valid = await verifyPassword(body.password || \"\", room.passwordHash);\n      if (valid) {\n        const accessToken = generateRoomAccessToken(id);\n        const prodMode = oidc.getAuthSettings().productionMode;\n        const cookieName = prodMode ? '__Host-room_access' : 'room_access';\n        const cookieFlags = prodMode ? '; HttpOnly; Secure; SameSite=Strict; Path=/; Partitioned' : '; HttpOnly; SameSite=Strict; Path=/';\n        const responseHeaders = { ...corsHeaders, 'Set-Cookie': `${cookieName}=${accessToken}; Max-Age=86400${cookieFlags}` };\n        return Response.json({ valid: true }, { headers: responseHeaders });\n      }\n      return Response.json({ valid: false }, { headers: corsHeaders });\n    } catch (e: any) { console.error(\"[API]\", e.message); return Response.json({ valid: false }, { headers: corsHeaders }); }\n  }\n\n  if (path.match(/^\\/api\\/room\\/[\\w-]+\\/access$/) && req.method === \"GET\") {\n    const id = path.split(\"/\")[3];\n    if (!isValidUUID(id)) return Response.json({ authorized: false }, { status: 400, headers: corsHeaders });\n    const room = loadRoom(id);\n    if (!room) return Response.json({ authorized: false }, { status: 404, headers: corsHeaders });\n    if (!room.passwordHash) return Response.json({ authorized: true }, { headers: corsHeaders });\n    const prodMode = oidc.getAuthSettings().productionMode;\n    const cookies = req.headers.get('cookie') || '';\n    const token = extractRoomAccessCookie(cookies, prodMode);\n    return Response.json({ authorized: validateRoomAccessToken(token, id) }, { headers: corsHeaders });\n  }\n\n  if (path.match(/^\\/api\\/room\\/[\\w-]+$/) && req.method === \"DELETE\") {\n    if (!validateRequestCsrf(req)) return csrfReject(corsHeaders);\n    const id = path.split(\"/\")[3];\n    if (!isValidUUID(id)) {\n      return Response.json({ error: \"Invalid room ID\" }, { status: 400, headers: corsHeaders });\n    }\n    const room = loadRoom(id);\n    if (!room) return Response.json({ error: \"Room not found\" }, { status: 404, headers: corsHeaders });\n\n    const userToken = getUserTokenFromRequest(req);\n    if (userToken) {\n      const user = await oidc.validateUserSessionToken(userToken);\n      if (user) {\n        if (user.role === 'admin' || (room.ownerUserId && user.id === room.ownerUserId)) {\n          deleteRoomData(id);\n          db.addAuditLog({ timestamp: new Date().toISOString(), action: \"room_deleted\", actor: user.id, actorIp: getClientIP(req), targetType: \"room\", targetId: id });\n          return Response.json({ deleted: true }, { headers: corsHeaders });\n        }\n      }\n    }\n\n    return Response.json({ error: \"Only room owner or admin can delete\" }, { status: 403, headers: corsHeaders });\n  }\n\n  if (path.match(/^\\/api\\/room\\/[\\w-]+\\/exists$/) && req.method === \"GET\") {\n    const id = path.split(\"/\")[3];\n    if (!isValidUUID(id)) {\n      return Response.json({ exists: false }, { headers: corsHeaders });\n    }\n    const clientIP = getClientIP(req);\n    if (!(await checkRateLimit(clientIP, \"room-exists\", settings))) {\n      return Response.json({ error: \"Too many requests. Try again later.\" }, { status: 429, headers: corsHeaders });\n    }\n    const room = loadRoom(id);\n    if (!room) return Response.json({ exists: false }, { headers: corsHeaders });\n    return Response.json({\n      exists: true, hasPassword: !!room.passwordHash\n    }, { headers: corsHeaders });\n  }\n\n  if (path.match(/^\\/api\\/room\\/[\\w-]+\\/ws-token$/) && req.method === \"POST\") {\n    if (!validateRequestCsrf(req)) return csrfReject(corsHeaders);\n    const id = path.split(\"/\")[3];\n    if (!isValidUUID(id)) {\n      return Response.json({ error: \"Invalid room ID\" }, { status: 400, headers: corsHeaders });\n    }\n    const room = loadRoom(id);\n    if (!room) return Response.json({ error: \"Room not found\" }, { status: 404, headers: corsHeaders });\n\n    if (room.passwordHash) {\n      const prodMode = oidc.getAuthSettings().productionMode;\n      const cookies = req.headers.get('cookie') || '';\n      const accessToken = extractRoomAccessCookie(cookies, prodMode);\n      if (!validateRoomAccessToken(accessToken, id)) {\n        return Response.json({ error: \"Room password required\" }, { status: 403, headers: corsHeaders });\n      }\n    }\n\n    if (room.allowGuests === false) {\n      const userToken = getUserTokenFromRequest(req);\n      if (!userToken) return Response.json({ error: \"Authentication required\" }, { status: 401, headers: corsHeaders });\n      const user = await oidc.validateUserSessionToken(userToken);\n      if (!user) return Response.json({ error: \"Authentication required\" }, { status: 401, headers: corsHeaders });\n    }\n\n    const clientIP = getClientIP(req);\n    if (!(await checkRateLimit(clientIP, `ws-token-${id}`, settings))) {\n      return Response.json({ error: \"Too many attempts. Try again later.\" }, { status: 429, headers: corsHeaders });\n    }\n\n    try {\n      const body = await req.json();\n      let collabUserId: string;\n      const userToken = getUserTokenFromRequest(req);\n      if (userToken) {\n        const user = await oidc.validateUserSessionToken(userToken);\n        collabUserId = user ? user.id : crypto.randomUUID();\n      } else {\n        collabUserId = crypto.randomUUID();\n      }\n\n      const wsToken = await generateWsSessionToken(id, collabUserId);\n\n      return Response.json({ wsToken, expiresIn: WS_TOKEN_EXPIRY / 1000, collabUserId }, { headers: corsHeaders });\n    } catch (e: any) { return apiError(e, corsHeaders); }\n  }\n\n  return null;\n}\n"
  },
  {
    "path": "theonefile_verse/src/routes/setup.ts",
    "content": "import * as auth from \"../auth\";\nimport * as oidc from \"../oidc\";\nimport * as db from \"../database\";\nimport { isAdminConfigured, hasAdminUser, getSettings, getExternalOrigin } from \"../config\";\nimport { getClientIP, serveHtml, apiError } from \"../security\";\nimport { checkRateLimit } from \"../rate-limit\";\nimport { setupPageHtml } from \"../templates\";\n\nexport async function handle(req: Request, path: string, url: URL, corsHeaders: Record<string, string>): Promise<Response | null> {\n  if (!isAdminConfigured() && (path === \"/\" || path === \"/index.html\")) {\n    return serveHtml(setupPageHtml, 'public', req);\n  }\n\n  if (path === \"/api/setup\" && req.method === \"POST\") {\n    if (hasAdminUser()) {\n      return Response.json({ error: \"Admin already configured\" }, { status: 400, headers: corsHeaders });\n    }\n    const clientIP = getClientIP(req);\n    const settings = getSettings();\n    if (!(await checkRateLimit(clientIP, \"setup\", settings))) {\n      return Response.json({ error: \"Too many attempts. Try again later.\" }, { status: 429, headers: corsHeaders });\n    }\n    try {\n      const body = await req.json();\n      if (!body.email || !body.email.includes('@')) {\n        return Response.json({ error: \"Valid email required\" }, { status: 400, headers: corsHeaders });\n      }\n      if (!body.password || body.password.length < 8) {\n        return Response.json({ error: \"Password must be at least 8 characters\" }, { status: 400, headers: corsHeaders });\n      }\n      const emailValidation = auth.validateEmail(body.email);\n      if (!emailValidation.valid) {\n        return Response.json({ error: emailValidation.error }, { status: 400, headers: corsHeaders });\n      }\n      const passwordValidation = auth.validatePassword(body.password);\n      if (!passwordValidation.valid) {\n        return Response.json({ error: passwordValidation.error }, { status: 400, headers: corsHeaders });\n      }\n      const baseUrl = getExternalOrigin(req);\n      const result = await auth.registerUser(body.email, body.password, body.email.split('@')[0], baseUrl);\n      if (!result.success) {\n        return Response.json({ error: result.error || \"Registration failed\" }, { status: 400, headers: corsHeaders });\n      }\n      const loginResult = await auth.loginWithPassword(body.email, body.password, clientIP, req.headers.get(\"user-agent\") || \"\");\n      if (!loginResult.success || !loginResult.sessionToken) {\n        return Response.json({ error: \"Account created but login failed\" }, { status: 400, headers: corsHeaders });\n      }\n      return new Response(JSON.stringify({ success: true }), {\n        headers: { ...corsHeaders, \"Content-Type\": \"application/json\",\n          \"Set-Cookie\": oidc.getSessionCookie(\"user_token\", loginResult.sessionToken) }\n      });\n    } catch (e: any) { return apiError(e, corsHeaders); }\n  }\n\n  return null;\n}\n"
  },
  {
    "path": "theonefile_verse/src/routes/user-auth.ts",
    "content": "import * as auth from \"../auth\";\nimport * as oidc from \"../oidc\";\nimport * as redis from \"../redis\";\nimport { getSettings, getExternalOrigin } from \"../config\";\nimport { getClientIP, apiError, validateRequestCsrf, csrfReject, getUserTokenFromRequest, serveHtml, relativeRedirect } from \"../security\";\nimport { checkRateLimit, checkEmailRateLimit } from \"../rate-limit\";\nimport { userLoginHtml, userRegisterHtml, userForgotPasswordHtml, getPasswordResetHtml } from \"../templates\";\n\nexport async function handle(req: Request, path: string, url: URL, corsHeaders: Record<string, string>): Promise<Response | null> {\n  const settings = getSettings();\n\n  if (path === \"/api/auth/settings\" && req.method === \"GET\") {\n    const authSettings = oidc.getAuthSettings();\n    const providers = oidc.getActiveProviders();\n    return Response.json({ settings: authSettings, providers }, { headers: corsHeaders });\n  }\n\n  if (path === \"/api/auth/providers\" && req.method === \"GET\") {\n    const providers = oidc.getActiveProviders();\n    return Response.json(providers, { headers: corsHeaders });\n  }\n\n  if (path === \"/api/auth/csrf\" && req.method === \"GET\") {\n    const csrfToken = oidc.generateCsrfToken();\n    return new Response(JSON.stringify({ token: csrfToken }), {\n      headers: { ...corsHeaders, \"Content-Type\": \"application/json\", \"Set-Cookie\": oidc.getCsrfCookie(csrfToken) }\n    });\n  }\n\n  if (path === \"/api/auth/register\" && req.method === \"POST\") {\n    const clientIP = getClientIP(req);\n    if (!(await checkRateLimit(clientIP, \"register\", settings))) {\n      return Response.json({ error: \"Too many attempts. Try again later.\" }, { status: 429, headers: corsHeaders });\n    }\n    try {\n      const body = await req.json();\n      if (typeof body.email !== \"string\" || typeof body.password !== \"string\" || (body.displayName !== undefined && body.displayName !== null && typeof body.displayName !== \"string\")) {\n        return Response.json({ error: \"Invalid input types\" }, { status: 400, headers: corsHeaders });\n      }\n      const csrfToken = body.csrfToken || req.headers.get(\"x-csrf-token\");\n      if (!oidc.validateCsrfToken(csrfToken)) {\n        return Response.json({ error: \"Invalid security token. Please refresh and try again.\" }, { status: 403, headers: corsHeaders });\n      }\n      if (body.email && !(await checkEmailRateLimit(body.email, \"register\", settings))) {\n        return Response.json({ error: \"Too many registration attempts for this email. Try again later.\" }, { status: 429, headers: corsHeaders });\n      }\n      const baseUrl = getExternalOrigin(req);\n      const result = await auth.registerUser(body.email, body.password, body.displayName, baseUrl);\n      if (!result.success) {\n        return Response.json({ error: result.error }, { status: 400, headers: corsHeaders });\n      }\n      if (result.requiresVerification) {\n        return Response.json({ success: true, requiresVerification: true }, { headers: corsHeaders });\n      }\n      const loginResult = await auth.loginWithPassword(body.email, body.password, clientIP, req.headers.get(\"user-agent\") || \"\");\n      if (loginResult.success) {\n        return new Response(JSON.stringify({ success: true, userId: loginResult.userId }), {\n          headers: { ...corsHeaders, \"Content-Type\": \"application/json\",\n            \"Set-Cookie\": oidc.getSessionCookie(\"user_token\", loginResult.sessionToken!) }\n        });\n      }\n      return Response.json({ success: true, userId: result.userId }, { headers: corsHeaders });\n    } catch (e: any) { return apiError(e, corsHeaders); }\n  }\n\n  if (path === \"/api/auth/login\" && req.method === \"POST\") {\n    const clientIP = getClientIP(req);\n    if (!(await checkRateLimit(clientIP, \"user-login\", settings))) {\n      return Response.json({ error: \"Too many attempts. Try again later.\" }, { status: 429, headers: corsHeaders });\n    }\n    try {\n      const body = await req.json();\n      if (typeof body.email !== \"string\" || typeof body.password !== \"string\") {\n        return Response.json({ error: \"Invalid input types\" }, { status: 400, headers: corsHeaders });\n      }\n      const csrfToken = body.csrfToken || req.headers.get(\"x-csrf-token\");\n      if (!oidc.validateCsrfToken(csrfToken)) {\n        return Response.json({ error: \"Invalid security token. Please refresh and try again.\" }, { status: 403, headers: corsHeaders });\n      }\n      const result = await auth.loginWithPassword(body.email, body.password, clientIP, req.headers.get(\"user-agent\") || \"\");\n      if (result.requires2FA) {\n        return Response.json({ requires2FA: true, pendingToken: result.pendingToken }, { headers: corsHeaders });\n      }\n      if (!result.success) {\n        return Response.json({ error: result.error }, { status: 401, headers: corsHeaders });\n      }\n      return new Response(JSON.stringify({ success: true, userId: result.userId }), {\n        headers: { ...corsHeaders, \"Content-Type\": \"application/json\",\n          \"Set-Cookie\": oidc.getSessionCookie(\"user_token\", result.sessionToken!) }\n      });\n    } catch (e: any) { return apiError(e, corsHeaders); }\n  }\n\n  if (path === \"/api/auth/2fa/login\" && req.method === \"POST\") {\n    const clientIP = getClientIP(req);\n    if (!(await checkRateLimit(clientIP, \"2fa-login\", settings))) {\n      return Response.json({ error: \"Too many attempts. Try again later.\" }, { status: 429, headers: corsHeaders });\n    }\n    try {\n      const body = await req.json();\n      if (!body.pendingToken || !body.code || typeof body.pendingToken !== \"string\" || typeof body.code !== \"string\") {\n        return Response.json({ error: \"Token and code required\" }, { status: 400, headers: corsHeaders });\n      }\n      const result = await auth.loginWith2FA(body.pendingToken, body.code, clientIP, req.headers.get(\"user-agent\") || \"\");\n      if (!result.success) {\n        return Response.json({ error: result.error }, { status: 401, headers: corsHeaders });\n      }\n      return new Response(JSON.stringify({ success: true, userId: result.userId }), {\n        headers: { ...corsHeaders, \"Content-Type\": \"application/json\",\n          \"Set-Cookie\": oidc.getSessionCookie(\"user_token\", result.sessionToken!) }\n      });\n    } catch (e: any) { return apiError(e, corsHeaders); }\n  }\n\n  if (path === \"/api/auth/me\" && req.method === \"GET\") {\n    const token = getUserTokenFromRequest(req);\n    if (!token) {\n      return Response.json({ user: null }, { headers: corsHeaders });\n    }\n    const user = await oidc.validateUserSessionToken(token);\n    if (!user) {\n      return Response.json({ user: null }, { headers: corsHeaders });\n    }\n    const { passwordHash, totpSecret, totpBackupCodes, pendingEmailToken, ...safeUser } = user;\n    return Response.json({ user: safeUser }, { headers: corsHeaders });\n  }\n\n  if (path === \"/api/auth/logout\" && req.method === \"POST\") {\n    const token = getUserTokenFromRequest(req);\n    if (token) {\n      const user = await oidc.validateUserSessionToken(token);\n      if (user) {\n        oidc.revokeUserOidcTokens(user.id).catch(e => console.error('[OIDC] Token revocation error:', e));\n      }\n      await auth.logout(token);\n      if (redis.isRedisConnected()) await redis.deleteSessionToken(token);\n    }\n    const clearCookies = [\n      oidc.getClearCookie(\"collab_token\"),\n      oidc.getClearCookie(\"user_token\")\n    ].join(\", \");\n    return new Response(JSON.stringify({ success: true }), {\n      headers: { ...corsHeaders, \"Content-Type\": \"application/json\", \"Set-Cookie\": clearCookies }\n    });\n  }\n\n  if (path.match(/^\\/api\\/auth\\/oidc\\/[\\w-]+\\/login$/) && req.method === \"GET\") {\n    const clientIP = getClientIP(req);\n    if (!(await checkRateLimit(clientIP, \"oidc-init\", settings))) {\n      return relativeRedirect(\"/?auth_error=rate_limited\");\n    }\n    const providerId = path.split(\"/\")[4];\n    const baseUrl = getExternalOrigin(req);\n    const redirectParam = url.searchParams.get(\"redirect\");\n    const validatedRedirect = oidc.validateRedirectUrl(redirectParam, baseUrl);\n    const result = await oidc.generateAuthorizationUrl(providerId, baseUrl, undefined, validatedRedirect);\n    if (!result) {\n      return relativeRedirect(\"/?auth_error=provider_unavailable\");\n    }\n    return Response.redirect(result.url, 302);\n  }\n\n  if (path.match(/^\\/api\\/auth\\/oidc\\/[\\w-]+$/) && req.method === \"GET\") {\n    const clientIP = getClientIP(req);\n    if (!(await checkRateLimit(clientIP, \"oidc-init\", settings))) {\n      return Response.json({ error: \"Too many attempts. Try again later.\" }, { status: 429, headers: corsHeaders });\n    }\n    const providerId = path.split(\"/\")[4];\n    const baseUrl = getExternalOrigin(req);\n    let linkUserId: string | undefined = undefined;\n    const linkParam = url.searchParams.get(\"link\");\n    if (linkParam) {\n      const token = getUserTokenFromRequest(req);\n      if (!token) {\n        return Response.json({ error: \"Authentication required for account linking\" }, { status: 401, headers: corsHeaders });\n      }\n      const user = await oidc.validateUserSessionToken(token);\n      if (!user) {\n        return Response.json({ error: \"Authentication required for account linking\" }, { status: 401, headers: corsHeaders });\n      }\n      if (user.id !== linkParam) {\n        return Response.json({ error: \"Cannot link to another user's account\" }, { status: 403, headers: corsHeaders });\n      }\n      linkUserId = linkParam;\n    }\n    const result = await oidc.generateAuthorizationUrl(providerId, baseUrl, linkUserId);\n    if (!result) {\n      return Response.json({ error: \"Provider not available\" }, { status: 400, headers: corsHeaders });\n    }\n    return Response.json({ url: result.url, state: result.state }, { headers: corsHeaders });\n  }\n\n  if (path.match(/^\\/auth\\/callback\\/[\\w-]+$/) && req.method === \"GET\") {\n    const clientIP = getClientIP(req);\n    if (!(await checkRateLimit(clientIP, \"oidc-callback\", settings))) {\n      return relativeRedirect(\"/?auth_error=rate_limited\");\n    }\n    const providerId = path.split(\"/\")[3];\n    const code = url.searchParams.get(\"code\");\n    const state = url.searchParams.get(\"state\");\n    const error = url.searchParams.get(\"error\");\n\n    if (error) {\n      return relativeRedirect(`/?auth_error=${encodeURIComponent(error)}`);\n    }\n\n    if (!code || !state) {\n      return relativeRedirect(\"/?auth_error=missing_params\");\n    }\n\n    const currentUserToken = getUserTokenFromRequest(req);\n    const result = await oidc.processOidcCallback(providerId, code, state, clientIP, req.headers.get(\"user-agent\") || \"\", currentUserToken);\n\n    if (!result.success) {\n      return relativeRedirect(`/?auth_error=${encodeURIComponent(result.error || \"unknown\")}`);\n    }\n\n    let redirectTo = \"/\";\n    if (result.isNewUser) {\n      redirectTo = \"/?welcome=true\";\n    } else if (result.postLoginRedirect && result.postLoginRedirect !== \"/\") {\n      redirectTo = result.postLoginRedirect;\n    }\n\n    return new Response(null, {\n      status: 302,\n      headers: {\n        \"Location\": redirectTo,\n        \"Set-Cookie\": oidc.getSessionCookie(\"user_token\", result.sessionToken!),\n        \"X-Content-Type-Options\": \"nosniff\",\n        \"Cache-Control\": \"no-store\"\n      }\n    });\n  }\n\n  if (path === \"/auth/login\" && req.method === \"GET\") {\n    return serveHtml(userLoginHtml, 'public', req);\n  }\n\n  if (path === \"/auth/register\" && req.method === \"GET\") {\n    return serveHtml(userRegisterHtml, 'public', req);\n  }\n\n  if (path === \"/auth/forgot-password\" && req.method === \"GET\") {\n    return serveHtml(userForgotPasswordHtml, 'public', req);\n  }\n\n  if (path === \"/auth/verify\" && req.method === \"GET\") {\n    const token = url.searchParams.get(\"token\");\n    if (!token) {\n      return relativeRedirect(\"/?verify_error=missing_token\");\n    }\n    const result = await auth.verifyEmail(token);\n    if (!result.success) {\n      return relativeRedirect(`/?verify_error=${encodeURIComponent(result.error || \"unknown\")}`);\n    }\n    return relativeRedirect(\"/?verified=true\");\n  }\n\n  if (path === \"/api/auth/forgot-password\" && req.method === \"POST\") {\n    const clientIP = getClientIP(req);\n    if (!(await checkRateLimit(clientIP, \"forgot-password\", settings))) {\n      return Response.json({ error: \"Too many attempts. Try again later.\" }, { status: 429, headers: corsHeaders });\n    }\n    try {\n      const body = await req.json();\n      if (typeof body.email !== \"string\") {\n        return Response.json({ error: \"Invalid input types\" }, { status: 400, headers: corsHeaders });\n      }\n      if (body.email && !(await checkEmailRateLimit(body.email, \"password-reset\", settings))) {\n        return Response.json({ success: true }, { headers: corsHeaders });\n      }\n      const baseUrl = getExternalOrigin(req);\n      await auth.requestPasswordReset(body.email, baseUrl);\n      return Response.json({ success: true }, { headers: corsHeaders });\n    } catch (e: any) { return apiError(e, corsHeaders); }\n  }\n\n  if (path === \"/auth/reset-password\" && req.method === \"GET\") {\n    const token = url.searchParams.get(\"token\");\n    if (!token) {\n      return relativeRedirect(\"/?reset_error=missing_token\");\n    }\n    return serveHtml(getPasswordResetHtml(token), 'public', req);\n  }\n\n  if (path === \"/api/auth/reset-password\" && req.method === \"POST\") {\n    const clientIP = getClientIP(req);\n    if (!(await checkRateLimit(clientIP, \"reset-password\", settings))) {\n      return Response.json({ error: \"Too many attempts. Try again later.\" }, { status: 429, headers: corsHeaders });\n    }\n    try {\n      const body = await req.json();\n      if (typeof body.token !== \"string\" || typeof body.password !== \"string\") {\n        return Response.json({ error: \"Invalid input types\" }, { status: 400, headers: corsHeaders });\n      }\n      const csrfToken = body.csrfToken || req.headers.get(\"x-csrf-token\");\n      if (!oidc.validateCsrfToken(csrfToken)) {\n        return Response.json({ error: \"Invalid security token. Please refresh and try again.\" }, { status: 403, headers: corsHeaders });\n      }\n      const result = await auth.resetPassword(body.token, body.password);\n      if (!result.success) {\n        return Response.json({ error: result.error }, { status: 400, headers: corsHeaders });\n      }\n      return Response.json({ success: true }, { headers: corsHeaders });\n    } catch (e: any) { return apiError(e, corsHeaders); }\n  }\n\n  if (path === \"/api/auth/magic-link\" && req.method === \"POST\") {\n    const clientIP = getClientIP(req);\n    if (!(await checkRateLimit(clientIP, \"magic-link\", settings))) {\n      return Response.json({ error: \"Too many attempts. Try again later.\" }, { status: 429, headers: corsHeaders });\n    }\n    try {\n      const body = await req.json();\n      if (typeof body.email !== \"string\") {\n        return Response.json({ error: \"Invalid input types\" }, { status: 400, headers: corsHeaders });\n      }\n      if (body.email && !(await checkEmailRateLimit(body.email, \"magic-link\", settings))) {\n        return Response.json({ success: true }, { headers: corsHeaders });\n      }\n      const baseUrl = getExternalOrigin(req);\n      await auth.requestMagicLink(body.email, baseUrl);\n      return Response.json({ success: true }, { headers: corsHeaders });\n    } catch (e: any) { return apiError(e, corsHeaders); }\n  }\n\n  if (path === \"/auth/magic-link\" && req.method === \"GET\") {\n    const token = url.searchParams.get(\"token\");\n    if (!token) {\n      return relativeRedirect(\"/?magic_error=missing_token\");\n    }\n    const clientIP = getClientIP(req);\n    if (!(await checkRateLimit(clientIP, \"magic-link-verify\", settings))) {\n      return relativeRedirect(\"/?magic_error=rate_limited\");\n    }\n    const result = await auth.loginWithMagicLink(token, clientIP, req.headers.get(\"user-agent\") || \"\");\n    if (!result.success) {\n      return relativeRedirect(`/?magic_error=${encodeURIComponent(result.error || \"unknown\")}`);\n    }\n    return new Response(null, {\n      status: 302,\n      headers: {\n        \"Location\": \"/\",\n        \"Set-Cookie\": oidc.getSessionCookie(\"user_token\", result.sessionToken!),\n        \"X-Content-Type-Options\": \"nosniff\",\n        \"Cache-Control\": \"no-store\"\n      }\n    });\n  }\n\n  if (path === \"/api/auth/profile\" && req.method === \"PUT\") {\n    const clientIP = getClientIP(req);\n    if (!(await checkRateLimit(clientIP, \"profile-update\", settings))) {\n      return Response.json({ error: \"Too many attempts. Try again later.\" }, { status: 429, headers: corsHeaders });\n    }\n    const token = getUserTokenFromRequest(req);\n    if (!token) {\n      return Response.json({ error: \"Unauthorized\" }, { status: 401, headers: corsHeaders });\n    }\n    const user = await oidc.validateUserSessionToken(token);\n    if (!user) {\n      return Response.json({ error: \"Unauthorized\" }, { status: 401, headers: corsHeaders });\n    }\n    try {\n      const body = await req.json();\n      if (!validateRequestCsrf(req, body)) return csrfReject(corsHeaders);\n      if (typeof body !== \"object\" || body === null || Array.isArray(body)) {\n        return Response.json({ error: \"Invalid input\" }, { status: 400, headers: corsHeaders });\n      }\n      const result = auth.updateProfile(user.id, body);\n      if (!result.success) {\n        return Response.json({ error: result.error }, { status: 400, headers: corsHeaders });\n      }\n      return Response.json({ success: true }, { headers: corsHeaders });\n    } catch (e: any) { return apiError(e, corsHeaders); }\n  }\n\n  if (path === \"/api/auth/change-password\" && req.method === \"POST\") {\n    const clientIP = getClientIP(req);\n    if (!(await checkRateLimit(clientIP, \"change-password\", settings))) {\n      return Response.json({ error: \"Too many attempts. Try again later.\" }, { status: 429, headers: corsHeaders });\n    }\n    const token = getUserTokenFromRequest(req);\n    if (!token) {\n      return Response.json({ error: \"Unauthorized\" }, { status: 401, headers: corsHeaders });\n    }\n    const user = await oidc.validateUserSessionToken(token);\n    if (!user) {\n      return Response.json({ error: \"Unauthorized\" }, { status: 401, headers: corsHeaders });\n    }\n    try {\n      const body = await req.json();\n      if (!validateRequestCsrf(req, body)) return csrfReject(corsHeaders);\n      if (typeof body.currentPassword !== \"string\" || typeof body.newPassword !== \"string\") {\n        return Response.json({ error: \"Invalid input types\" }, { status: 400, headers: corsHeaders });\n      }\n      const result = await auth.changePassword(user.id, body.currentPassword, body.newPassword);\n      if (!result.success) {\n        return Response.json({ error: result.error }, { status: 400, headers: corsHeaders });\n      }\n      return Response.json({ success: true }, { headers: corsHeaders });\n    } catch (e: any) { return apiError(e, corsHeaders); }\n  }\n\n  if (path === \"/api/auth/sessions\" && req.method === \"GET\") {\n    const token = getUserTokenFromRequest(req);\n    if (!token) {\n      return Response.json({ error: \"Unauthorized\" }, { status: 401, headers: corsHeaders });\n    }\n    const user = await oidc.validateUserSessionToken(token);\n    if (!user) {\n      return Response.json({ error: \"Unauthorized\" }, { status: 401, headers: corsHeaders });\n    }\n    const sessions = auth.getUserSessions(user.id);\n    return Response.json({ sessions }, { headers: corsHeaders });\n  }\n\n  if (path === \"/api/auth/sessions\" && req.method === \"DELETE\") {\n    if (!validateRequestCsrf(req)) return csrfReject(corsHeaders);\n    const token = getUserTokenFromRequest(req);\n    if (!token) {\n      return Response.json({ error: \"Unauthorized\" }, { status: 401, headers: corsHeaders });\n    }\n    const user = await oidc.validateUserSessionToken(token);\n    if (!user) {\n      return Response.json({ error: \"Unauthorized\" }, { status: 401, headers: corsHeaders });\n    }\n    const count = auth.logoutAll(user.id);\n    return Response.json({ success: true, loggedOut: count }, { headers: corsHeaders });\n  }\n\n  if (path === \"/api/auth/oidc-links\" && req.method === \"GET\") {\n    const token = getUserTokenFromRequest(req);\n    if (!token) {\n      return Response.json({ error: \"Unauthorized\" }, { status: 401, headers: corsHeaders });\n    }\n    const user = await oidc.validateUserSessionToken(token);\n    if (!user) {\n      return Response.json({ error: \"Unauthorized\" }, { status: 401, headers: corsHeaders });\n    }\n    const links = auth.getUserOidcLinks(user.id).map(l => ({\n      id: l.id,\n      provider: l.provider,\n      providerEmail: l.providerEmail,\n      createdAt: l.createdAt\n    }));\n    return Response.json({ links }, { headers: corsHeaders });\n  }\n\n  if (path.match(/^\\/api\\/auth\\/oidc-links\\/[\\w-]+$/) && req.method === \"DELETE\") {\n    if (!validateRequestCsrf(req)) return csrfReject(corsHeaders);\n    const linkId = path.split(\"/\")[4];\n    const token = getUserTokenFromRequest(req);\n    if (!token) {\n      return Response.json({ error: \"Unauthorized\" }, { status: 401, headers: corsHeaders });\n    }\n    const user = await oidc.validateUserSessionToken(token);\n    if (!user) {\n      return Response.json({ error: \"Unauthorized\" }, { status: 401, headers: corsHeaders });\n    }\n    const result = auth.unlinkOidcProvider(user.id, linkId);\n    if (!result.success) {\n      return Response.json({ error: result.error }, { status: 400, headers: corsHeaders });\n    }\n    return Response.json({ success: true }, { headers: corsHeaders });\n  }\n\n  if (path === \"/api/auth/2fa/setup\" && req.method === \"POST\") {\n    if (!validateRequestCsrf(req)) return csrfReject(corsHeaders);\n    const clientIP = getClientIP(req);\n    if (!(await checkRateLimit(clientIP, \"2fa-setup\", settings))) {\n      return Response.json({ error: \"Too many attempts. Try again later.\" }, { status: 429, headers: corsHeaders });\n    }\n    const token = getUserTokenFromRequest(req);\n    if (!token) {\n      return Response.json({ error: \"Unauthorized\" }, { status: 401, headers: corsHeaders });\n    }\n    const user = await oidc.validateUserSessionToken(token);\n    if (!user) {\n      return Response.json({ error: \"Unauthorized\" }, { status: 401, headers: corsHeaders });\n    }\n    try {\n      const result = await auth.setupTOTP(user.id);\n      if (!result.success) {\n        return Response.json({ error: result.error }, { status: 400, headers: corsHeaders });\n      }\n      return Response.json({ secret: result.secret, otpauthUrl: result.otpauthUrl }, { headers: corsHeaders });\n    } catch (e: any) { return apiError(e, corsHeaders); }\n  }\n\n  if (path === \"/api/auth/2fa/verify\" && req.method === \"POST\") {\n    if (!validateRequestCsrf(req)) return csrfReject(corsHeaders);\n    const clientIP = getClientIP(req);\n    if (!(await checkRateLimit(clientIP, \"2fa-verify\", settings))) {\n      return Response.json({ error: \"Too many attempts. Try again later.\" }, { status: 429, headers: corsHeaders });\n    }\n    const token = getUserTokenFromRequest(req);\n    if (!token) {\n      return Response.json({ error: \"Unauthorized\" }, { status: 401, headers: corsHeaders });\n    }\n    const user = await oidc.validateUserSessionToken(token);\n    if (!user) {\n      return Response.json({ error: \"Unauthorized\" }, { status: 401, headers: corsHeaders });\n    }\n    try {\n      const body = await req.json();\n      if (!body.code || typeof body.code !== \"string\" || body.code.length !== 6) {\n        return Response.json({ error: \"Valid 6 digit code required\" }, { status: 400, headers: corsHeaders });\n      }\n      const result = await auth.verifyAndEnableTOTP(user.id, body.code);\n      if (!result.success) {\n        return Response.json({ error: result.error }, { status: 400, headers: corsHeaders });\n      }\n      return Response.json({ success: true, backupCodes: result.backupCodes }, { headers: corsHeaders });\n    } catch (e: any) { return apiError(e, corsHeaders); }\n  }\n\n  if (path === \"/api/auth/2fa/disable\" && req.method === \"POST\") {\n    if (!validateRequestCsrf(req)) return csrfReject(corsHeaders);\n    const clientIP = getClientIP(req);\n    if (!(await checkRateLimit(clientIP, \"2fa-disable\", settings))) {\n      return Response.json({ error: \"Too many attempts. Try again later.\" }, { status: 429, headers: corsHeaders });\n    }\n    const token = getUserTokenFromRequest(req);\n    if (!token) {\n      return Response.json({ error: \"Unauthorized\" }, { status: 401, headers: corsHeaders });\n    }\n    const user = await oidc.validateUserSessionToken(token);\n    if (!user) {\n      return Response.json({ error: \"Unauthorized\" }, { status: 401, headers: corsHeaders });\n    }\n    try {\n      const body = await req.json();\n      if (!body.password || typeof body.password !== \"string\") {\n        return Response.json({ error: \"Password required\" }, { status: 400, headers: corsHeaders });\n      }\n      const result = await auth.disableTOTP(user.id, body.password);\n      if (!result.success) {\n        return Response.json({ error: result.error }, { status: 400, headers: corsHeaders });\n      }\n      return Response.json({ success: true }, { headers: corsHeaders });\n    } catch (e: any) { return apiError(e, corsHeaders); }\n  }\n\n  if (path === \"/api/auth/email-change\" && req.method === \"POST\") {\n    if (!validateRequestCsrf(req)) return csrfReject(corsHeaders);\n    const clientIP = getClientIP(req);\n    if (!(await checkRateLimit(clientIP, \"email-change\", settings))) {\n      return Response.json({ error: \"Too many attempts. Try again later.\" }, { status: 429, headers: corsHeaders });\n    }\n    const token = getUserTokenFromRequest(req);\n    if (!token) {\n      return Response.json({ error: \"Unauthorized\" }, { status: 401, headers: corsHeaders });\n    }\n    const user = await oidc.validateUserSessionToken(token);\n    if (!user) {\n      return Response.json({ error: \"Unauthorized\" }, { status: 401, headers: corsHeaders });\n    }\n    try {\n      const body = await req.json();\n      if (!body.newEmail || !body.password || typeof body.newEmail !== \"string\" || typeof body.password !== \"string\") {\n        return Response.json({ error: \"New email and password required\" }, { status: 400, headers: corsHeaders });\n      }\n      const baseUrl = getExternalOrigin(req);\n      const result = await auth.requestEmailChange(user.id, body.newEmail, body.password, baseUrl);\n      if (!result.success) {\n        return Response.json({ error: result.error }, { status: 400, headers: corsHeaders });\n      }\n      return Response.json({ success: true }, { headers: corsHeaders });\n    } catch (e: any) { return apiError(e, corsHeaders); }\n  }\n\n  if (path === \"/auth/verify-email-change\" && req.method === \"GET\") {\n    const token = url.searchParams.get(\"token\");\n    if (!token) {\n      return relativeRedirect(\"/?email_change_error=missing_token\");\n    }\n    const clientIP = getClientIP(req);\n    if (!(await checkRateLimit(clientIP, \"email-change-verify\", settings))) {\n      return relativeRedirect(\"/?email_change_error=rate_limited\");\n    }\n    const result = await auth.confirmEmailChange(token);\n    if (!result.success) {\n      return relativeRedirect(`/?email_change_error=${encodeURIComponent(result.error || \"unknown\")}`);\n    }\n    return relativeRedirect(\"/?email_changed=true\");\n  }\n\n  return null;\n}\n"
  },
  {
    "path": "theonefile_verse/src/security.ts",
    "content": "import * as oidc from \"./oidc\";\nimport * as db from \"./database\";\nimport { getSettings, getExternalOrigin } from \"./config\";\nimport { validateApiKey } from \"./tokens\";\n\nconst IPV4_REGEX = /^(\\d{1,3}\\.){3}\\d{1,3}$/;\nconst IPV6_REGEX = /^([\\da-fA-F]{0,4}:){2,7}[\\da-fA-F]{0,4}$/;\n\nexport function isValidIP(ip: string): boolean {\n  return IPV4_REGEX.test(ip) || IPV6_REGEX.test(ip) || ip === '::1';\n}\n\nexport function getClientIP(req: Request): string {\n  const xForwardedFor = req.headers.get(\"x-forwarded-for\");\n  if (xForwardedFor) {\n    const ips = xForwardedFor.split(\",\").map(ip => ip.trim()).filter(isValidIP);\n    if (ips.length === 0) return \"unknown\";\n    const settings = getSettings();\n    if (settings.trustedProxies.length > 0) {\n      for (let i = ips.length - 1; i >= 0; i--) {\n        if (!settings.trustedProxies.includes(ips[i])) {\n          return ips[i];\n        }\n      }\n    }\n    if (settings.trustedProxyCount > 0) {\n      const index = Math.max(0, ips.length - settings.trustedProxyCount);\n      return ips[index] || ips[0];\n    }\n    return ips[0];\n  }\n  const realIp = req.headers.get(\"x-real-ip\");\n  if (realIp && isValidIP(realIp.trim())) return realIp.trim();\n  return \"unknown\";\n}\n\nexport function getSecurityHeaders(pageType: 'admin' | 'public' | 'room' | 'api' = 'public'): Record<string, string> {\n  const headers: Record<string, string> = {\n    \"X-Content-Type-Options\": \"nosniff\",\n    \"X-Frame-Options\": \"DENY\",\n    \"X-XSS-Protection\": \"1; mode=block\",\n    \"Referrer-Policy\": \"strict-origin-when-cross-origin\"\n  };\n  headers[\"Permissions-Policy\"] = \"camera=(), microphone=(), geolocation=(), payment=(), usb=()\";\n  headers[\"Strict-Transport-Security\"] = \"max-age=31536000; includeSubDomains; preload\";\n  if (pageType === 'api') {\n    headers[\"Cache-Control\"] = \"no-store\";\n  } else if (pageType === 'admin') {\n    headers[\"Content-Security-Policy\"] = `default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self'; connect-src 'self'; frame-ancestors 'none'; base-uri 'self'; form-action 'self'`;\n  } else if (pageType === 'room') {\n    headers[\"Content-Security-Policy\"] = `default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self'; connect-src 'self' wss: ws: https://cdn.jsdelivr.net https://raw.githubusercontent.com; frame-ancestors 'none'; base-uri 'self'`;\n  } else {\n    headers[\"Content-Security-Policy\"] = `default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self'; connect-src 'self' wss: ws:; frame-ancestors 'none'; base-uri 'self'`;\n  }\n  return headers;\n}\n\nexport const securityHeaders: Record<string, string> = {\n  \"X-Content-Type-Options\": \"nosniff\",\n  \"X-Frame-Options\": \"DENY\",\n  \"X-XSS-Protection\": \"1; mode=block\",\n  \"Referrer-Policy\": \"strict-origin-when-cross-origin\",\n  \"Permissions-Policy\": \"camera=(), microphone=(), geolocation=(), payment=(), usb=()\",\n  \"Cache-Control\": \"no-store\"\n};\n\nexport function serveHtml(html: string, pageType: 'admin' | 'public' | 'room' = 'public', req?: Request): Response {\n  const headers: Record<string, string> = { \"Content-Type\": \"text/html; charset=utf-8\", ...getSecurityHeaders(pageType), \"Vary\": \"Accept-Encoding\" };\n  if (req && html.length > 1024 && (req.headers.get('accept-encoding') || '').includes('gzip')) {\n    headers[\"Content-Encoding\"] = \"gzip\";\n    return new Response(Bun.gzipSync(Buffer.from(html)), { headers });\n  }\n  return new Response(html, { headers });\n}\n\nexport function apiError(e: any, corsHeaders: Record<string, string>, message = \"Invalid request\", status = 400): Response {\n  console.error(\"[API]\", e.message);\n  return Response.json({ error: message }, { status, headers: corsHeaders });\n}\n\nexport function validateRequestCsrf(req: Request, body?: any): boolean {\n  const headerToken = req.headers.get(\"x-csrf-token\");\n  const bodyToken = body?.csrfToken;\n  const token = headerToken || bodyToken;\n  if (token && oidc.validateCsrfToken(token)) return true;\n  const origin = req.headers.get(\"origin\");\n  const externalOrigin = getExternalOrigin(req);\n  if (origin) return origin === externalOrigin;\n  const referer = req.headers.get(\"referer\");\n  if (referer) {\n    try { return new URL(referer).origin === externalOrigin; } catch { return false; }\n  }\n  return false;\n}\n\nexport function csrfReject(corsHeaders: Record<string, string>): Response {\n  return Response.json({ error: \"Invalid security token. Please refresh and try again.\" }, { status: 403, headers: corsHeaders });\n}\n\nexport function getTokenFromRequest(req: Request): string | null {\n  const cookie = req.headers.get(\"cookie\") || \"\";\n  const match = cookie.match(/(?:^|;\\s*)collab_token=([^;]+)/);\n  if (match) return match[1];\n  const auth = req.headers.get(\"authorization\") || \"\";\n  if (auth.startsWith(\"Bearer \")) return auth.slice(7);\n  return null;\n}\n\nexport function getUserTokenFromRequest(req: Request): string | null {\n  const cookie = req.headers.get(\"cookie\") || \"\";\n  const hostMatch = cookie.match(/__Host-user_token=([^;]+)/);\n  if (hostMatch) return hostMatch[1];\n  const match = cookie.match(/user_token=([^;]+)/);\n  if (match) return match[1];\n  const auth = req.headers.get(\"authorization\") || \"\";\n  if (auth.startsWith(\"Bearer \")) return auth.slice(7);\n  return null;\n}\n\nexport function relativeRedirect(path: string, status: number = 302): Response {\n  return new Response(null, { status, headers: { Location: path } });\n}\n\nexport async function validateAdminUser(req: Request): Promise<db.User | null> {\n  const method = req.method;\n  if (method === \"POST\" || method === \"PUT\" || method === \"DELETE\") {\n    if (!validateRequestCsrf(req)) return null;\n  }\n  const token = getUserTokenFromRequest(req);\n  if (!token) return null;\n  const user = await oidc.validateUserSessionToken(token);\n  if (!user || user.role !== 'admin') return null;\n  return user;\n}\n\nexport async function validateAdminOrApiKey(\n  req: Request,\n  requiredPermission: string = \"read\"\n): Promise<{ user: db.User | null; apiKey: db.ApiKey | null }> {\n  const auth = req.headers.get(\"authorization\") || \"\";\n  if (auth.startsWith(\"Bearer tof_\")) {\n    const key = auth.slice(7);\n    const apiKey = await validateApiKey(key);\n    if (!apiKey || !apiKey.active) return { user: null, apiKey: null };\n    if (!apiKey.permissions.includes(requiredPermission) && !apiKey.permissions.includes(\"admin\")) {\n      return { user: null, apiKey: null };\n    }\n    return { user: null, apiKey };\n  }\n  const user = await validateAdminUser(req);\n  return { user, apiKey: null };\n}\n"
  },
  {
    "path": "theonefile_verse/src/templates.ts",
    "content": "export const setupPageHtml = `<!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\">\n  <title>Setup - The One File Collab</title>\n  <style>\n    *{box-sizing:border-box;margin:0;padding:0}\n    button,a{-webkit-tap-highlight-color:transparent}\n    :root{--bg:#0d0d0d;--bg-alt:#1a1a1a;--surface:#242424;--border:#333;--text:#e8e8e8;--text-soft:#999;--accent:#c9a227;--accent-hover:#d4b23a}\n    [data-theme=\"light\"]{--bg:#f5f3ef;--bg-alt:#eae7e0;--surface:#fff;--border:#d4d0c8;--text:#1a1a1a;--text-soft:#666;--accent:#996b1f;--accent-hover:#7a5518}\n    body{font-family:Inter,system-ui,sans-serif;background:var(--bg);color:var(--text);min-height:100vh;display:flex;align-items:center;justify-content:center;padding:20px;padding-left:max(20px,env(safe-area-inset-left,20px));padding-right:max(20px,env(safe-area-inset-right,20px));padding-bottom:max(20px,env(safe-area-inset-bottom,20px))}\n    .setup-box{background:var(--surface);border:1px solid var(--border);border-radius:16px;padding:40px;width:100%;max-width:450px}\n    h1{font-size:24px;margin-bottom:8px;text-align:center}\n    .subtitle{color:var(--text-soft);font-size:14px;text-align:center;margin-bottom:32px}\n    .info{background:rgba(201,162,39,0.1);border:1px solid rgba(201,162,39,0.3);border-radius:8px;padding:12px;margin-bottom:24px;font-size:13px;color:#c9a227}\n    label{display:block;font-size:14px;color:var(--text-soft);margin-bottom:6px}\n    input{width:100%;padding:14px 16px;background:var(--bg);border:1px solid var(--border);border-radius:8px;color:var(--text);font-size:16px;margin-bottom:16px;outline:none}\n    input:focus{border-color:var(--accent)}\n    button{width:100%;padding:14px;background:var(--accent);border:none;border-radius:8px;color:white;font-size:16px;font-weight:600;cursor:pointer;margin-top:8px}\n    button:hover{background:var(--accent-hover)}\n    button:disabled{background:var(--border);cursor:not-allowed}\n    .error{color:#ef4444;font-size:14px;text-align:center;margin-bottom:16px;display:none}\n    .error.active{display:block}\n    .oidc-btn{display:flex;align-items:center;justify-content:center;gap:8px;background:var(--bg);border:1px solid var(--border);margin-bottom:12px}\n    .oidc-btn:hover{background:var(--bg-alt)}\n    .divider{display:flex;align-items:center;gap:12px;margin:24px 0;color:var(--text-soft);font-size:12px}\n    .divider::before,.divider::after{content:'';flex:1;height:1px;background:var(--border)}\n    @media(max-width:640px){.setup-box,.login-box,.box{padding:24px}}\n    @media(max-width:380px){.setup-box,.login-box,.box{padding:20px 16px}}\n  </style>\n</head>\n<body data-page=\"setup\">\n  <div class=\"setup-box\">\n    <h1>Welcome to The One File Collab</h1>\n    <p class=\"subtitle\">Create your admin account to get started</p>\n    <div class=\"info\">The first user created becomes the administrator.</div>\n    <div class=\"error\" id=\"error\"></div>\n    <div id=\"oidc-buttons\"></div>\n    <div class=\"divider\" id=\"divider\" style=\"display:none\">or continue with email</div>\n    <form id=\"setup-form\" novalidate>\n      <label for=\"email\">Email</label>\n      <input type=\"email\" id=\"email\" placeholder=\"admin@example.com\" autocomplete=\"email\" autofocus>\n      <label for=\"password\">Password</label>\n      <input type=\"password\" id=\"password\" placeholder=\"At least 8 characters\" autocomplete=\"new-password\">\n      <label for=\"confirm\">Confirm Password</label>\n      <input type=\"password\" id=\"confirm\" placeholder=\"Confirm your password\" autocomplete=\"new-password\">\n      <button type=\"submit\" id=\"submit-btn\">Create Admin Account</button>\n    </form>\n  </div>\n  <script src=\"/admin-auth.js\"></script>\n  <script src=\"/admin-pages.js\"></script>\n</body>\n</html>`;\n\nexport const migrationPageHtml = `<!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\">\n  <title>Migrate Admin - The One File Collab</title>\n  <style>\n    *{box-sizing:border-box;margin:0;padding:0}\n    button,a{-webkit-tap-highlight-color:transparent}\n    :root{--bg:#0d0d0d;--bg-alt:#1a1a1a;--surface:#242424;--border:#333;--text:#e8e8e8;--text-soft:#999;--accent:#c9a227;--accent-hover:#d4b23a}\n    [data-theme=\"light\"]{--bg:#f5f3ef;--bg-alt:#eae7e0;--surface:#fff;--border:#d4d0c8;--text:#1a1a1a;--text-soft:#666;--accent:#996b1f;--accent-hover:#7a5518}\n    body{font-family:Inter,system-ui,sans-serif;background:var(--bg);color:var(--text);min-height:100vh;display:flex;align-items:center;justify-content:center;padding:20px;padding-left:max(20px,env(safe-area-inset-left,20px));padding-right:max(20px,env(safe-area-inset-right,20px));padding-bottom:max(20px,env(safe-area-inset-bottom,20px))}\n    .setup-box{background:var(--surface);border:1px solid var(--border);border-radius:16px;padding:40px;width:100%;max-width:450px}\n    h1{font-size:24px;margin-bottom:8px;text-align:center}\n    .subtitle{color:var(--text-soft);font-size:14px;text-align:center;margin-bottom:32px}\n    .info{background:rgba(201,162,39,0.1);border:1px solid rgba(201,162,39,0.3);border-radius:8px;padding:12px;margin-bottom:24px;font-size:13px;color:#c9a227}\n    label{display:block;font-size:14px;color:var(--text-soft);margin-bottom:6px}\n    input{width:100%;padding:14px 16px;background:var(--bg);border:1px solid var(--border);border-radius:8px;color:var(--text);font-size:16px;margin-bottom:16px;outline:none}\n    input:focus{border-color:var(--accent)}\n    button{width:100%;padding:14px;background:var(--accent);border:none;border-radius:8px;color:white;font-size:16px;font-weight:600;cursor:pointer;margin-top:8px}\n    button:hover{background:var(--accent-hover)}\n    .error{color:#ef4444;font-size:14px;text-align:center;margin-bottom:16px;display:none}\n    .error.active{display:block}\n    @media(max-width:640px){.setup-box,.login-box,.box{padding:24px}}\n    @media(max-width:380px){.setup-box,.login-box,.box{padding:20px 16px}}\n  </style>\n</head>\n<body data-page=\"migration\">\n  <div class=\"setup-box\">\n    <h1>Migrate to User Account</h1>\n    <p class=\"subtitle\">Convert your admin password to a user account</p>\n    <div class=\"info\">Enter your current admin password and a new email to create your admin user account. This is a one time migration.</div>\n    <div class=\"error\" id=\"error\"></div>\n    <form id=\"migrate-form\" novalidate>\n      <label for=\"old-password\">Current Admin Password</label>\n      <input type=\"password\" id=\"old-password\" placeholder=\"Your existing admin password\" autocomplete=\"current-password\" autofocus>\n      <label for=\"email\">Email for Admin Account</label>\n      <input type=\"email\" id=\"email\" placeholder=\"admin@example.com\" autocomplete=\"email\">\n      <label for=\"new-password\">New Password (optional)</label>\n      <input type=\"password\" id=\"new-password\" placeholder=\"Leave blank to keep current password\" autocomplete=\"new-password\">\n      <button type=\"submit\">Migrate Account</button>\n    </form>\n  </div>\n  <script src=\"/admin-auth.js\"></script>\n  <script src=\"/admin-pages.js\"></script>\n</body>\n</html>`;\n\nexport const loginPageHtml = `<!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\">\n  <title>Login - The One File Collab</title>\n  <style>\n    *{box-sizing:border-box;margin:0;padding:0}\n    button,a{-webkit-tap-highlight-color:transparent}\n    :root{--bg:#0d0d0d;--bg-alt:#1a1a1a;--surface:#242424;--border:#333;--text:#e8e8e8;--text-soft:#999;--accent:#c9a227;--accent-hover:#d4b23a}\n    [data-theme=\"light\"]{--bg:#f5f3ef;--bg-alt:#eae7e0;--surface:#fff;--border:#d4d0c8;--text:#1a1a1a;--text-soft:#666;--accent:#996b1f;--accent-hover:#7a5518}\n    body{font-family:Inter,system-ui,sans-serif;background:var(--bg);color:var(--text);min-height:100vh;display:flex;align-items:center;justify-content:center;padding:20px;padding-left:max(20px,env(safe-area-inset-left,20px));padding-right:max(20px,env(safe-area-inset-right,20px));padding-bottom:max(20px,env(safe-area-inset-bottom,20px))}\n    .login-box{background:var(--surface);border:1px solid var(--border);border-radius:16px;padding:40px;width:100%;max-width:400px}\n    h1{font-size:24px;margin-bottom:8px;text-align:center}\n    p{color:var(--text-soft);font-size:14px;text-align:center;margin-bottom:32px}\n    input{width:100%;padding:14px 16px;background:var(--bg);border:1px solid var(--border);border-radius:8px;color:var(--text);font-size:16px;margin-bottom:16px;outline:none}\n    input:focus{border-color:var(--accent)}\n    button{width:100%;padding:14px;background:var(--accent);border:none;border-radius:8px;color:white;font-size:16px;font-weight:600;cursor:pointer}\n    button:hover{background:var(--accent-hover)}\n    .error{color:#ef4444;font-size:14px;text-align:center;margin-bottom:16px;display:none}\n    .error.active{display:block}\n    @media(max-width:640px){.setup-box,.login-box,.box{padding:24px}}\n    @media(max-width:380px){.setup-box,.login-box,.box{padding:20px 16px}}\n  </style>\n</head>\n<body data-page=\"login\">\n  <div class=\"login-box\">\n    <h1>The One File Collab</h1>\n    <p>This instance requires a password</p>\n    <div class=\"error\" id=\"error\">Invalid password</div>\n    <form id=\"login-form\" novalidate>\n      <input type=\"password\" id=\"password\" placeholder=\"Enter password\" autofocus>\n      <button type=\"submit\">Login</button>\n    </form>\n  </div>\n  <script src=\"/admin-auth.js\"></script>\n  <script src=\"/admin-pages.js\"></script>\n</body>\n</html>`;\n\nexport const instanceLoginPageHtml = `<!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\">\n  <title>Access - The One File Collab</title>\n  <style>\n    *{box-sizing:border-box;margin:0;padding:0}\n    button,a{-webkit-tap-highlight-color:transparent}\n    :root{--bg:#0d0d0d;--bg-alt:#1a1a1a;--surface:#242424;--border:#333;--text:#e8e8e8;--text-soft:#999;--accent:#c9a227;--accent-hover:#d4b23a}\n    [data-theme=\"light\"]{--bg:#f5f3ef;--bg-alt:#eae7e0;--surface:#fff;--border:#d4d0c8;--text:#1a1a1a;--text-soft:#666;--accent:#996b1f;--accent-hover:#7a5518}\n    body{font-family:Inter,system-ui,sans-serif;background:var(--bg);color:var(--text);min-height:100vh;display:flex;align-items:center;justify-content:center;padding:20px;padding-left:max(20px,env(safe-area-inset-left,20px));padding-right:max(20px,env(safe-area-inset-right,20px));padding-bottom:max(20px,env(safe-area-inset-bottom,20px))}\n    .login-box{background:var(--surface);border:1px solid var(--border);border-radius:16px;padding:40px;width:100%;max-width:400px}\n    h1{font-size:24px;margin-bottom:8px;text-align:center}\n    p{color:var(--text-soft);font-size:14px;text-align:center;margin-bottom:32px}\n    input{width:100%;padding:14px 16px;background:var(--bg);border:1px solid var(--border);border-radius:8px;color:var(--text);font-size:16px;margin-bottom:16px;outline:none}\n    input:focus{border-color:var(--accent)}\n    button{width:100%;padding:14px;background:var(--accent);border:none;border-radius:8px;color:white;font-size:16px;font-weight:600;cursor:pointer}\n    button:hover{background:var(--accent-hover)}\n    .error{color:#ef4444;font-size:14px;text-align:center;margin-bottom:16px;display:none}\n    .error.active{display:block}\n    @media(max-width:640px){.setup-box,.login-box,.box{padding:24px}}\n    @media(max-width:380px){.setup-box,.login-box,.box{padding:20px 16px}}\n  </style>\n</head>\n<body data-page=\"instance-login\">\n  <div class=\"login-box\">\n    <h1>The One File Collab</h1>\n    <p>This instance requires a password to access</p>\n    <div class=\"error\" id=\"error\">Invalid password</div>\n    <form id=\"login-form\" novalidate>\n      <input type=\"password\" id=\"password\" placeholder=\"Enter password\" autofocus>\n      <button type=\"submit\">Access</button>\n    </form>\n  </div>\n  <script src=\"/admin-auth.js\"></script>\n  <script src=\"/admin-pages.js\"></script>\n</body>\n</html>`;\n\nexport const userLoginHtml = `<!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\">\n  <title>Login - The One File Collab</title>\n  <style>\n    *{box-sizing:border-box;margin:0;padding:0}\n    button,a{-webkit-tap-highlight-color:transparent}\n    :root{--bg:#0d0d0d;--bg-alt:#1a1a1a;--surface:#242424;--border:#333;--text:#e8e8e8;--text-soft:#999;--accent:#c9a227;--accent-hover:#d4b23a}\n    [data-theme=\"light\"]{--bg:#f5f3ef;--bg-alt:#eae7e0;--surface:#fff;--border:#d4d0c8;--text:#1a1a1a;--text-soft:#666;--accent:#996b1f;--accent-hover:#7a5518}\n    body{font-family:Inter,system-ui,sans-serif;background:var(--bg);color:var(--text);min-height:100vh;display:flex;align-items:center;justify-content:center;padding:20px;padding-left:max(20px,env(safe-area-inset-left,20px));padding-right:max(20px,env(safe-area-inset-right,20px));padding-bottom:max(20px,env(safe-area-inset-bottom,20px))}\n    .login-box{background:var(--surface);border:1px solid var(--border);border-radius:16px;padding:40px;width:100%;max-width:400px}\n    h1{font-size:24px;margin-bottom:8px;text-align:center}\n    p{color:var(--text-soft);font-size:14px;text-align:center;margin-bottom:32px}\n    label{display:block;font-size:14px;color:var(--text-soft);margin-bottom:6px}\n    input{width:100%;padding:14px 16px;background:var(--bg);border:1px solid var(--border);border-radius:8px;color:var(--text);font-size:16px;margin-bottom:16px;outline:none}\n    input:focus{border-color:var(--accent)}\n    button{width:100%;padding:14px;background:var(--accent);border:none;border-radius:8px;color:white;font-size:16px;font-weight:600;cursor:pointer;margin-top:8px}\n    button:hover{background:var(--accent-hover)}\n    .error{color:#ef4444;font-size:14px;text-align:center;margin-bottom:16px;display:none}\n    .error.active{display:block}\n    .success{color:#22c55e;font-size:14px;text-align:center;margin-bottom:16px;display:none}\n    .success.active{display:block}\n    .links{text-align:center;margin-top:20px;font-size:14px}\n    .links a{color:var(--accent);text-decoration:none}\n    .links a:hover{text-decoration:underline}\n    .oidc-btn{display:flex;align-items:center;justify-content:center;gap:8px;background:var(--bg);border:1px solid var(--border);margin-bottom:12px}\n    .oidc-btn:hover{background:var(--bg-alt)}\n    .divider{display:flex;align-items:center;gap:12px;margin:24px 0;color:var(--text-soft);font-size:12px}\n    .divider::before,.divider::after{content:'';flex:1;height:1px;background:var(--border)}\n    @media(max-width:640px){.setup-box,.login-box,.box{padding:24px}}\n    @media(max-width:380px){.setup-box,.login-box,.box{padding:20px 16px}}\n  </style>\n</head>\n<body data-page=\"user-login\">\n  <div class=\"login-box\">\n    <h1>Welcome Back</h1>\n    <p>Sign in to your account</p>\n    <div class=\"error\" id=\"error\"></div>\n    <div id=\"oidc-buttons\"></div>\n    <div class=\"divider\" id=\"divider\" style=\"display:none\">or continue with email</div>\n    <form id=\"login-form\" novalidate>\n      <label for=\"email\">Email</label>\n      <input type=\"email\" id=\"email\" placeholder=\"you@example.com\" autocomplete=\"email\" autofocus>\n      <label for=\"password\">Password</label>\n      <input type=\"password\" id=\"password\" placeholder=\"Your password\" autocomplete=\"current-password\">\n      <button type=\"submit\">Sign In</button>\n    </form>\n    <div class=\"links\">\n      <a href=\"/auth/forgot-password\">Forgot password?</a>\n      <span style=\"margin:0 8px;color:var(--text-soft)\">|</span>\n      <a href=\"/auth/register\">Create account</a>\n    </div>\n  </div>\n  <script src=\"/admin-auth.js\"></script>\n  <script src=\"/admin-pages.js\"></script>\n</body>\n</html>`;\n\nexport const userRegisterHtml = `<!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\">\n  <title>Register - The One File Collab</title>\n  <style>\n    *{box-sizing:border-box;margin:0;padding:0}\n    button,a{-webkit-tap-highlight-color:transparent}\n    :root{--bg:#0d0d0d;--bg-alt:#1a1a1a;--surface:#242424;--border:#333;--text:#e8e8e8;--text-soft:#999;--accent:#c9a227;--accent-hover:#d4b23a}\n    [data-theme=\"light\"]{--bg:#f5f3ef;--bg-alt:#eae7e0;--surface:#fff;--border:#d4d0c8;--text:#1a1a1a;--text-soft:#666;--accent:#996b1f;--accent-hover:#7a5518}\n    body{font-family:Inter,system-ui,sans-serif;background:var(--bg);color:var(--text);min-height:100vh;display:flex;align-items:center;justify-content:center;padding:20px;padding-left:max(20px,env(safe-area-inset-left,20px));padding-right:max(20px,env(safe-area-inset-right,20px));padding-bottom:max(20px,env(safe-area-inset-bottom,20px))}\n    .login-box{background:var(--surface);border:1px solid var(--border);border-radius:16px;padding:40px;width:100%;max-width:400px}\n    h1{font-size:24px;margin-bottom:8px;text-align:center}\n    p{color:var(--text-soft);font-size:14px;text-align:center;margin-bottom:32px}\n    label{display:block;font-size:14px;color:var(--text-soft);margin-bottom:6px}\n    input{width:100%;padding:14px 16px;background:var(--bg);border:1px solid var(--border);border-radius:8px;color:var(--text);font-size:16px;margin-bottom:16px;outline:none}\n    input:focus{border-color:var(--accent)}\n    button{width:100%;padding:14px;background:var(--accent);border:none;border-radius:8px;color:white;font-size:16px;font-weight:600;cursor:pointer;margin-top:8px}\n    button:hover{background:var(--accent-hover)}\n    .error{color:#ef4444;font-size:14px;text-align:center;margin-bottom:16px;display:none}\n    .error.active{display:block}\n    .success{color:#22c55e;font-size:14px;text-align:center;margin-bottom:16px;display:none}\n    .success.active{display:block}\n    .links{text-align:center;margin-top:20px;font-size:14px}\n    .links a{color:var(--accent);text-decoration:none}\n    .links a:hover{text-decoration:underline}\n    .oidc-btn{display:flex;align-items:center;justify-content:center;gap:8px;background:var(--bg);border:1px solid var(--border);margin-bottom:12px}\n    .oidc-btn:hover{background:var(--bg-alt)}\n    .divider{display:flex;align-items:center;gap:12px;margin:24px 0;color:var(--text-soft);font-size:12px}\n    .divider::before,.divider::after{content:'';flex:1;height:1px;background:var(--border)}\n    .password-hint{font-size:12px;color:var(--text-soft);margin-top:-12px;margin-bottom:16px}\n    .password-strength{height:4px;border-radius:2px;margin-top:-12px;margin-bottom:8px;background:var(--border);overflow:hidden}\n    .password-strength-bar{height:100%;transition:all 0.3s;width:0%}\n    .password-strength-bar.weak{width:33%;background:#ef4444}\n    .password-strength-bar.medium{width:66%;background:#f59e0b}\n    .password-strength-bar.strong{width:100%;background:#22c55e}\n    .password-strength-text{font-size:11px;margin-top:-4px;margin-bottom:12px;transition:color 0.3s}\n    .password-strength-text.weak{color:#ef4444}\n    .password-strength-text.medium{color:#f59e0b}\n    .password-strength-text.strong{color:#22c55e}\n    @media(max-width:640px){.setup-box,.login-box,.box{padding:24px}}\n    @media(max-width:380px){.setup-box,.login-box,.box{padding:20px 16px}}\n  </style>\n</head>\n<body data-page=\"user-register\">\n  <div class=\"login-box\">\n    <h1>Create Account</h1>\n    <p>Join The One File Collab</p>\n    <div class=\"error\" id=\"error\"></div>\n    <div class=\"success\" id=\"success\"></div>\n    <div id=\"oidc-buttons\"></div>\n    <div class=\"divider\" id=\"divider\" style=\"display:none\">or register with email</div>\n    <form id=\"register-form\" novalidate>\n      <label for=\"email\">Email</label>\n      <input type=\"email\" id=\"email\" placeholder=\"you@example.com\" autocomplete=\"email\" autofocus>\n      <label for=\"displayName\">Display Name</label>\n      <input type=\"text\" id=\"displayName\" placeholder=\"Your name\" autocomplete=\"name\">\n      <label for=\"password\">Password</label>\n      <input type=\"password\" id=\"password\" placeholder=\"Create a password\" autocomplete=\"new-password\">\n      <div class=\"password-strength\"><div class=\"password-strength-bar\" id=\"strength-bar\"></div></div>\n      <div class=\"password-strength-text\" id=\"strength-text\"></div>\n      <label for=\"confirmPassword\">Confirm Password</label>\n      <input type=\"password\" id=\"confirmPassword\" placeholder=\"Confirm your password\" autocomplete=\"new-password\">\n      <button type=\"submit\">Create Account</button>\n    </form>\n    <div class=\"links\">\n      Already have an account? <a href=\"/auth/login\">Sign in</a>\n    </div>\n  </div>\n  <script src=\"/admin-auth.js\"></script>\n  <script src=\"/admin-pages.js\"></script>\n</body>\n</html>`;\n\nexport const userForgotPasswordHtml = `<!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\">\n  <title>Forgot Password - The One File Collab</title>\n  <style>\n    *{box-sizing:border-box;margin:0;padding:0}\n    button,a{-webkit-tap-highlight-color:transparent}\n    :root{--bg:#0d0d0d;--bg-alt:#1a1a1a;--surface:#242424;--border:#333;--text:#e8e8e8;--text-soft:#999;--accent:#c9a227;--accent-hover:#d4b23a}\n    [data-theme=\"light\"]{--bg:#f5f3ef;--bg-alt:#eae7e0;--surface:#fff;--border:#d4d0c8;--text:#1a1a1a;--text-soft:#666;--accent:#996b1f;--accent-hover:#7a5518}\n    body{font-family:Inter,system-ui,sans-serif;background:var(--bg);color:var(--text);min-height:100vh;display:flex;align-items:center;justify-content:center;padding:20px;padding-left:max(20px,env(safe-area-inset-left,20px));padding-right:max(20px,env(safe-area-inset-right,20px));padding-bottom:max(20px,env(safe-area-inset-bottom,20px))}\n    .login-box{background:var(--surface);border:1px solid var(--border);border-radius:16px;padding:40px;width:100%;max-width:400px}\n    h1{font-size:24px;margin-bottom:8px;text-align:center}\n    p{color:var(--text-soft);font-size:14px;text-align:center;margin-bottom:32px}\n    label{display:block;font-size:14px;color:var(--text-soft);margin-bottom:6px}\n    input{width:100%;padding:14px 16px;background:var(--bg);border:1px solid var(--border);border-radius:8px;color:var(--text);font-size:16px;margin-bottom:16px;outline:none}\n    input:focus{border-color:var(--accent)}\n    button{width:100%;padding:14px;background:var(--accent);border:none;border-radius:8px;color:white;font-size:16px;font-weight:600;cursor:pointer;margin-top:8px}\n    button:hover{background:var(--accent-hover)}\n    .error{color:#ef4444;font-size:14px;text-align:center;margin-bottom:16px;display:none}\n    .error.active{display:block}\n    .success{color:#22c55e;font-size:14px;text-align:center;margin-bottom:16px;display:none}\n    .success.active{display:block}\n    .links{text-align:center;margin-top:20px;font-size:14px}\n    .links a{color:var(--accent);text-decoration:none}\n    .links a:hover{text-decoration:underline}\n    @media(max-width:640px){.setup-box,.login-box,.box{padding:24px}}\n    @media(max-width:380px){.setup-box,.login-box,.box{padding:20px 16px}}\n  </style>\n</head>\n<body data-page=\"user-forgot-password\">\n  <div class=\"login-box\">\n    <h1>Reset Password</h1>\n    <p>Enter your email to receive a reset link</p>\n    <div class=\"error\" id=\"error\"></div>\n    <div class=\"success\" id=\"success\"></div>\n    <form id=\"forgot-form\" novalidate>\n      <label for=\"email\">Email</label>\n      <input type=\"email\" id=\"email\" placeholder=\"you@example.com\" autocomplete=\"email\" autofocus>\n      <button type=\"submit\">Send Reset Link</button>\n    </form>\n    <div class=\"links\">\n      <a href=\"/auth/login\">Back to login</a>\n    </div>\n  </div>\n  <script src=\"/admin-auth.js\"></script>\n  <script src=\"/admin-pages.js\"></script>\n</body>\n</html>`;\n\nexport const adminDashboardHtml = `<!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\">\n  <title>Admin - The One File Collab</title>\n  <style>\n    *{box-sizing:border-box;margin:0;padding:0}\n    button,a,.btn,.tab,.toggle,.room-checkbox,.modal-close{-webkit-tap-highlight-color:transparent}\n    :root{\n      --bg:#0d0d0d;\n      --bg-alt:#1a1a1a;\n      --surface:#242424;\n      --border:#333;\n      --text:#e8e8e8;\n      --text-soft:#999;\n      --accent:#c9a227;\n      --accent-hover:#d4b23a;\n      --accent-bg:rgba(201,162,39,0.1);\n      --accent-bg-hover:rgba(201,162,39,0.15);\n    }\n    [data-theme=\"light\"]{\n      --bg:#f5f3ef;\n      --bg-alt:#eae7e0;\n      --surface:#fff;\n      --border:#d4d0c8;\n      --text:#1a1a1a;\n      --text-soft:#666;\n      --accent:#996b1f;\n      --accent-hover:#7a5518;\n      --accent-bg:rgba(153,107,31,0.1);\n      --accent-bg-hover:rgba(153,107,31,0.15);\n    }\n    body{font-family:Inter,system-ui,sans-serif;background:var(--bg);color:var(--text);min-height:100vh;padding:20px;padding-left:max(20px,env(safe-area-inset-left,20px));padding-right:max(20px,env(safe-area-inset-right,20px));padding-bottom:max(80px,calc(80px + env(safe-area-inset-bottom,0px)))}\n    .container{max-width:1200px;margin:0 auto}\n    header{display:flex;justify-content:space-between;align-items:center;margin-bottom:24px;padding-bottom:20px;border-bottom:1px solid var(--border);flex-wrap:wrap;gap:12px}\n    h1{font-size:24px;display:flex;align-items:center;gap:12px}\n    .header-actions{display:flex;gap:8px;flex-wrap:wrap}\n    .theme-toggle{background:var(--surface);border:1px solid var(--border);color:var(--text);padding:8px 12px;border-radius:6px;cursor:pointer;font-size:14px}\n    .theme-toggle:hover{background:var(--bg-alt)}\n    .btn{padding:10px 20px;border-radius:8px;font-size:14px;font-weight:500;cursor:pointer;border:none;transition:all 0.15s;text-decoration:none;display:inline-flex;align-items:center;justify-content:center;gap:6px;min-height:44px}\n    .btn-primary{background:var(--accent);color:#fff}.btn-primary:hover{background:var(--accent-hover)}\n    .btn-secondary{background:var(--surface);color:var(--text);border:1px solid var(--border)}.btn-secondary:hover{background:var(--bg-alt)}\n    .btn-danger{background:#dc2626;color:white}.btn-danger:hover{background:#b91c1c}\n    .btn-success{background:#22c55e;color:white}.btn-success:hover{background:#16a34a}\n    .btn-sm{padding:8px 12px;font-size:12px;min-height:44px}\n    .btn:disabled{opacity:0.5;cursor:not-allowed}\n    .tabs{display:flex;gap:4px;margin-bottom:24px;border-bottom:1px solid var(--border);padding-bottom:0}\n    .tab{padding:12px 20px;background:transparent;border:none;color:var(--text-soft);font-size:14px;font-weight:500;cursor:pointer;border-bottom:2px solid transparent;margin-bottom:-1px;min-height:44px}\n    .tab:hover{color:var(--text)}\n    .tab.active{color:var(--accent);border-bottom-color:var(--accent)}\n    .tab-content{display:none}\n    .tab-content.active{display:block}\n    .stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:12px;margin-bottom:24px}\n    .stat-card{background:var(--surface);border:1px solid var(--border);border-radius:12px;padding:16px}\n    .stat-value{font-size:28px;font-weight:700;color:var(--accent)}\n    .stat-label{font-size:13px;color:var(--text-soft);margin-top:4px}\n    .section-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:16px;flex-wrap:wrap;gap:12px}\n    .section-title{font-size:18px;font-weight:600;display:flex;align-items:center;gap:8px}\n    .bulk-actions{display:none;gap:8px;align-items:center;flex-wrap:wrap}\n    .bulk-actions.active{display:flex}\n    .selected-count{font-size:13px;color:var(--text-soft);padding:6px 12px;background:var(--surface);border-radius:6px}\n    .room-list{background:var(--surface);border:1px solid var(--border);border-radius:12px;overflow:hidden}\n    .room-header{display:grid;grid-template-columns:40px 1fr 80px 100px 80px 200px;padding:12px 16px;background:var(--bg-alt);font-size:12px;font-weight:600;color:var(--text-soft);text-transform:uppercase;gap:8px;align-items:center}\n    .room-row{display:grid;grid-template-columns:40px 1fr 80px 100px 80px 200px;padding:12px 16px;border-bottom:1px solid var(--border);align-items:center;gap:8px;transition:background 0.15s}\n    .room-row:last-child{border-bottom:none}\n    .room-row:hover{background:var(--accent-bg)}\n    .room-row.selected{background:var(--accent-bg-hover)}\n    .room-checkbox{width:20px;height:20px;cursor:pointer;accent-color:var(--accent)}\n    .room-id{font-family:monospace;font-size:11px;color:var(--text-soft);word-break:break-all}\n    .room-name{font-weight:500;font-size:14px}\n    .badge{display:inline-block;padding:4px 8px;border-radius:12px;font-size:11px;font-weight:500}\n    .badge-green{background:rgba(34,197,94,0.15);color:#22c55e}\n    .badge-yellow{background:rgba(201,162,39,0.15);color:#c9a227}\n    .badge-gray{background:rgba(100,116,139,0.15);color:#94a3b8}\n    .room-actions{display:flex;gap:6px;flex-wrap:nowrap}\n    #user-list .room-header,#user-list .room-row{grid-template-columns:1fr 100px 120px 100px 140px}\n    .empty-state{text-align:center;padding:60px 20px;color:var(--text-soft)}\n    .empty-state h3{font-size:18px;margin-bottom:8px;color:var(--text)}\n    .settings-section{background:var(--surface);border:1px solid var(--border);border-radius:12px;padding:20px;margin-bottom:16px}\n    .settings-section h3{font-size:16px;font-weight:600;margin-bottom:16px;padding-bottom:12px;border-bottom:1px solid var(--border)}\n    .setting-row{display:flex;justify-content:space-between;align-items:center;padding:12px 0;border-bottom:1px solid var(--border);flex-wrap:wrap;gap:12px}\n    .setting-row:last-child{border-bottom:none}\n    .setting-info{flex:1;min-width:200px}\n    .setting-label{font-size:14px;font-weight:500;margin-bottom:4px}\n    .setting-desc{font-size:12px;color:var(--text-soft)}\n    .setting-control{display:flex;align-items:center;gap:8px}\n    .toggle{position:relative;width:48px;height:26px;background:var(--border);border-radius:13px;cursor:pointer;transition:background 0.2s}\n    .toggle.active{background:var(--accent)}\n    .toggle::after{content:'';position:absolute;top:3px;left:3px;width:20px;height:20px;background:var(--text);border-radius:50%;transition:transform 0.2s}\n    .toggle.active::after{transform:translateX(22px)}\n    .toggle::before{content:'';position:absolute;top:-9px;left:-4px;right:-4px;bottom:-9px}\n    input[type=\"text\"],input[type=\"password\"],input[type=\"number\"],input[type=\"email\"],select{padding:10px 12px;background:var(--bg);border:1px solid var(--border);border-radius:8px;color:var(--text);font-size:16px;min-width:120px;min-height:44px}\n    input:focus,select:focus{outline:none;border-color:var(--accent)}\n    .modal-overlay{display:none;position:fixed;inset:0;background:rgba(0,0,0,0.85);z-index:1000;align-items:center;justify-content:center;padding:20px;padding-bottom:max(20px,env(safe-area-inset-bottom,20px))}\n    .modal-overlay.active{display:flex}\n    .modal{background:var(--surface);border:1px solid var(--border);border-radius:16px;width:100%;max-width:500px}\n    .modal-header{display:flex;justify-content:space-between;align-items:center;padding:16px 20px;border-bottom:1px solid var(--border)}\n    .modal-header h3{font-size:18px}\n    .modal-close{background:none;border:none;color:var(--text-soft);font-size:24px;cursor:pointer;padding:4px;min-width:44px;min-height:44px;display:flex;align-items:center;justify-content:center}\n    .modal-body{padding:20px}\n    .info-row{display:flex;justify-content:space-between;padding:12px 0;border-bottom:1px solid var(--border);flex-wrap:wrap;gap:8px}\n    .info-row:last-child{border-bottom:none}\n    .info-label{color:var(--text-soft);font-size:14px}\n    .info-value{font-weight:500;font-size:14px;word-break:break-all}\n    .status-msg{padding:12px;border-radius:8px;margin-bottom:16px;font-size:14px}\n    .status-msg.success{background:rgba(34,197,94,0.15);color:#22c55e}\n    .status-msg.error{background:rgba(239,68,68,0.15);color:#ef4444}\n    .tabs,#email-log-list,#activity-log-list,#audit-log-list{-webkit-overflow-scrolling:touch;scrollbar-width:none}\n    .tabs::-webkit-scrollbar{display:none}\n    @media(max-width:900px){.tabs{overflow-x:auto;-webkit-overflow-scrolling:touch}.room-header,.room-row{grid-template-columns:32px 1fr 80px 170px}.room-header>*:nth-child(4),.room-header>*:nth-child(5),.room-row>*:nth-child(4),.room-row>*:nth-child(5){display:none}#user-list .room-header,#user-list .room-row{grid-template-columns:1fr 80px 100px}#user-list .room-header>*:nth-child(3),#user-list .room-header>*:nth-child(4),#user-list .room-row>*:nth-child(3),#user-list .room-row>*:nth-child(4){display:none}}\n    @media(max-width:640px){body{padding:12px;padding-left:max(12px,env(safe-area-inset-left,12px));padding-right:max(12px,env(safe-area-inset-right,12px));padding-bottom:max(80px,calc(80px + env(safe-area-inset-bottom,0px)))}header{flex-direction:column;align-items:flex-start}h1{font-size:20px}.stats{grid-template-columns:repeat(2,1fr);gap:8px}.stat-card{padding:12px}.stat-value{font-size:22px}.stat-label{font-size:11px}.room-header,.room-row{grid-template-columns:32px 1fr 140px}.room-header>*:nth-child(3),.room-header>*:nth-child(4),.room-header>*:nth-child(5),.room-row>*:nth-child(3),.room-row>*:nth-child(4),.room-row>*:nth-child(5){display:none}#user-list .room-header,#user-list .room-row{grid-template-columns:1fr 100px}#user-list .room-header>*:nth-child(2),#user-list .room-header>*:nth-child(3),#user-list .room-header>*:nth-child(4),#user-list .room-row>*:nth-child(2),#user-list .room-row>*:nth-child(3),#user-list .room-row>*:nth-child(4){display:none}.room-actions{justify-content:flex-end}.btn{padding:8px 12px;font-size:12px;min-height:44px}.btn-sm{padding:6px 10px;font-size:11px;min-height:44px}.section-header{flex-direction:column;align-items:flex-start}.bulk-actions{width:100%;justify-content:flex-start}.tabs{overflow-x:auto;-webkit-overflow-scrolling:touch;scrollbar-width:none}.tabs::-webkit-scrollbar{display:none}.tab{padding:12px 16px;white-space:nowrap;min-height:44px}.setting-row{flex-direction:column;align-items:flex-start}.setting-control{width:100%}.setting-control input,.setting-control select{width:100%;max-width:none}}\n  </style>\n</head>\n<body>\n  <div class=\"container\">\n    <header>\n      <h1>Admin Dashboard</h1>\n      <div class=\"header-actions\">\n        <button class=\"theme-toggle\" id=\"theme-toggle\" data-action=\"toggleTheme\"><span id=\"theme-icon\"></span></button>\n        <a href=\"/\" class=\"btn btn-secondary\">Back to App</a>\n        <button class=\"btn btn-secondary\" data-action=\"logout\">Logout</button>\n      </div>\n    </header>\n    <div class=\"tabs\">\n      <button class=\"tab active\" data-action=\"showTab\" data-tab=\"rooms\">Rooms</button>\n      <button class=\"tab\" data-action=\"showTab\" data-tab=\"users\">Users</button>\n      <button class=\"tab\" data-action=\"showTab\" data-tab=\"auth\">Authentication</button>\n      <button class=\"tab\" data-action=\"showTab\" data-tab=\"settings\">Settings</button>\n      <button class=\"tab\" data-action=\"showTab\" data-tab=\"logs\">Logs</button>\n      <button class=\"tab\" data-action=\"showTab\" data-tab=\"backups\">Backups</button>\n      <button class=\"tab\" data-action=\"showTab\" data-tab=\"apikeys\">API Keys</button>\n    </div>\n    <div id=\"tab-rooms\" class=\"tab-content active\">\n      <div class=\"stats\" id=\"stats\"></div>\n      <div class=\"section-header\">\n        <div style=\"display:flex;align-items:center;gap:12px;flex-wrap:wrap\">\n          <h2 class=\"section-title\">All Rooms</h2>\n          <input type=\"text\" id=\"room-search\" placeholder=\"Search rooms...\" style=\"padding:8px 12px;width:100%;max-width:200px\" data-action=\"searchRooms\">\n        </div>\n        <div class=\"bulk-actions\" id=\"bulk-actions\">\n          <span class=\"selected-count\" id=\"selected-count\">0 selected</span>\n          <button class=\"btn btn-danger btn-sm\" data-action=\"deleteSelected\">Delete Selected</button>\n          <button class=\"btn btn-secondary btn-sm\" data-action=\"clearSelection\">Clear</button>\n        </div>\n      </div>\n      <div class=\"room-list\" id=\"room-list\"></div>\n    </div>\n    <div id=\"tab-users\" class=\"tab-content\">\n      <div class=\"section-header\">\n        <div style=\"display:flex;align-items:center;gap:12px;flex-wrap:wrap\">\n          <h2 class=\"section-title\">User Management</h2>\n          <input type=\"text\" id=\"user-search\" placeholder=\"Search users...\" style=\"padding:8px 12px;width:100%;max-width:200px\" data-action=\"searchUsers\">\n        </div>\n        <button class=\"btn btn-primary\" data-action=\"showCreateUser\">Create User</button>\n      </div>\n      <div class=\"room-list\" id=\"user-list\"></div>\n    </div>\n    <div id=\"tab-auth\" class=\"tab-content\">\n      <div id=\"auth-status\"></div>\n      <div class=\"settings-section\">\n        <h3>Authentication Mode</h3>\n        <div class=\"setting-row\">\n          <div class=\"setting-info\"><div class=\"setting-label\">Default Auth Mode</div><div class=\"setting-desc\">How users can access the system</div></div>\n          <div class=\"setting-control\">\n            <select id=\"auth-mode\" data-action=\"saveAuthSettings\">\n              <option value=\"open\">Open (Anyone can register)</option>\n              <option value=\"registration\">Registration Required</option>\n              <option value=\"oidc_only\">OIDC Only</option>\n              <option value=\"invite_only\">Invite Only</option>\n              <option value=\"closed\">Closed (No new users)</option>\n            </select>\n          </div>\n        </div>\n        <div class=\"setting-row\">\n          <div class=\"setting-info\"><div class=\"setting-label\">Require Email Verification</div><div class=\"setting-desc\">Users must verify email before accessing</div></div>\n          <div class=\"setting-control\"><div class=\"toggle\" id=\"toggle-require-email-verify\" data-action=\"toggleAuthSetting\" data-setting=\"requireEmailVerification\"></div></div>\n        </div>\n        <div class=\"setting-row\">\n          <div class=\"setting-info\"><div class=\"setting-label\">Allow Magic Link Login</div><div class=\"setting-desc\">Users can login via email link</div></div>\n          <div class=\"setting-control\"><div class=\"toggle\" id=\"toggle-magic-link\" data-action=\"toggleAuthSetting\" data-setting=\"allowMagicLinkLogin\"></div></div>\n        </div>\n        <div class=\"setting-row\">\n          <div class=\"setting-info\"><div class=\"setting-label\">Match OIDC Emails</div><div class=\"setting-desc\">Auto link OIDC accounts with matching email. <span style=\"color:#f59e0b\">Only enable with trusted providers</span></div></div>\n          <div class=\"setting-control\"><div class=\"toggle\" id=\"toggle-oidc-email-match\" data-action=\"toggleAuthSetting\" data-setting=\"oidcEmailMatching\"></div></div>\n        </div>\n      </div>\n      <div class=\"settings-section\">\n        <h3>Security</h3>\n        <div class=\"setting-row\">\n          <div class=\"setting-info\"><div class=\"setting-label\">Production Mode</div><div class=\"setting-desc\">Enable HTTPS secure cookies. <span style=\"color:#ef4444\">Required for production</span></div></div>\n          <div class=\"setting-control\"><div class=\"toggle\" id=\"toggle-production-mode\" data-action=\"toggleAuthSetting\" data-setting=\"productionMode\"></div></div>\n        </div>\n        <div class=\"setting-row\">\n          <div class=\"setting-info\"><div class=\"setting-label\">ID Token Max Age (hours)</div><div class=\"setting-desc\">Maximum age for OIDC ID tokens before they are considered too old</div></div>\n          <div class=\"setting-control\"><input type=\"number\" id=\"input-id-token-max-age\" min=\"1\" max=\"168\" style=\"width:80px;padding:8px;border:1px solid #374151;border-radius:4px;background:#1f2937;color:#fff\" data-action=\"updateAuthNumber\" data-setting=\"idTokenMaxAgeHours\"></div>\n        </div>\n        <div class=\"setting-row\">\n          <div class=\"setting-info\"><div class=\"setting-label\">Email Rate Limit Window (seconds)</div><div class=\"setting-desc\">Time window for email rate limiting</div></div>\n          <div class=\"setting-control\"><input type=\"number\" id=\"input-email-rate-window\" min=\"60\" max=\"3600\" style=\"width:80px;padding:8px;border:1px solid #374151;border-radius:4px;background:#1f2937;color:#fff\" data-action=\"updateAuthNumber\" data-setting=\"emailRateLimitWindowSeconds\"></div>\n        </div>\n        <div class=\"setting-row\">\n          <div class=\"setting-info\"><div class=\"setting-label\">Email Rate Limit Max Attempts</div><div class=\"setting-desc\">Maximum email requests per address within window</div></div>\n          <div class=\"setting-control\"><input type=\"number\" id=\"input-email-rate-max\" min=\"1\" max=\"20\" style=\"width:80px;padding:8px;border:1px solid #374151;border-radius:4px;background:#1f2937;color:#fff\" data-action=\"updateAuthNumber\" data-setting=\"emailRateLimitMaxAttempts\"></div>\n        </div>\n      </div>\n      <div class=\"settings-section\">\n        <h3>Guest Access</h3>\n        <div class=\"setting-row\">\n          <div class=\"setting-info\"><div class=\"setting-label\">Allow Guest Room Creation</div><div class=\"setting-desc\">Unregistered users can create rooms</div></div>\n          <div class=\"setting-control\"><div class=\"toggle\" id=\"toggle-guest-room-create\" data-action=\"toggleAuthSetting\" data-setting=\"allowGuestRoomCreation\"></div></div>\n        </div>\n        <div class=\"setting-row\">\n          <div class=\"setting-info\"><div class=\"setting-label\">Allow Guest Room Join</div><div class=\"setting-desc\">Unregistered users can join rooms</div></div>\n          <div class=\"setting-control\"><div class=\"toggle\" id=\"toggle-guest-room-join\" data-action=\"toggleAuthSetting\" data-setting=\"allowGuestRoomJoin\"></div></div>\n        </div>\n        <div class=\"setting-row\">\n          <div class=\"setting-info\"><div class=\"setting-label\">Room Creator Guest Setting</div><div class=\"setting-desc\">Let room creators choose guest access per room</div></div>\n          <div class=\"setting-control\"><div class=\"toggle\" id=\"toggle-room-creator-guest\" data-action=\"toggleAuthSetting\" data-setting=\"allowRoomCreatorGuestSetting\"></div></div>\n        </div>\n        <div class=\"setting-row\">\n          <div class=\"setting-info\"><div class=\"setting-label\">Share Button</div><div class=\"setting-desc\">Show share button in rooms</div></div>\n          <div class=\"setting-control\"><div class=\"toggle\" id=\"toggle-share-button\" data-action=\"toggleAuthSetting\" data-setting=\"shareButtonEnabled\"></div></div>\n        </div>\n      </div>\n      <div class=\"settings-section\">\n        <h3>OIDC Providers</h3>\n        <div style=\"display:flex;justify-content:space-between;align-items:center;margin-bottom:16px\">\n          <p style=\"color:var(--text-soft);font-size:13px\">Configure OpenID Connect providers for SSO</p>\n          <button class=\"btn btn-primary btn-sm\" data-action=\"showAddOidcProvider\">Add Provider</button>\n        </div>\n        <div id=\"oidc-provider-list\"></div>\n      </div>\n      <div class=\"settings-section\">\n        <h3>SMTP Configuration</h3>\n        <div style=\"display:flex;justify-content:space-between;align-items:center;margin-bottom:16px\">\n          <p style=\"color:var(--text-soft);font-size:13px\">Configure email delivery for verification and notifications</p>\n          <button class=\"btn btn-primary btn-sm\" data-action=\"showAddSmtpConfig\">Add SMTP</button>\n        </div>\n        <div id=\"smtp-config-list\"></div>\n      </div>\n      <div class=\"settings-section\">\n        <h3>Email Templates</h3>\n        <div id=\"email-template-list\"></div>\n      </div>\n      <div class=\"settings-section\">\n        <h3>Email Logs</h3>\n        <div style=\"margin-bottom:12px;display:flex;gap:12px;align-items:center\"><input type=\"text\" id=\"email-log-search\" placeholder=\"Filter by email...\" style=\"width:100%;max-width:250px\" data-action=\"loadEmailLogs\"><button class=\"btn btn-sm btn-secondary\" data-action=\"clearEmailLogs\">Clear All</button></div>\n        <div id=\"email-log-list\" style=\"max-height:300px;overflow-y:auto\"></div>\n      </div>\n    </div>\n    <div id=\"tab-settings\" class=\"tab-content\">\n      <div id=\"settings-status\"></div>\n      <div class=\"settings-section\">\n        <h3>Instance Access</h3>\n        <div class=\"setting-row\">\n          <div class=\"setting-info\"><div class=\"setting-label\">Password Lock</div><div class=\"setting-desc\">Require password to access the entire instance</div></div>\n          <div class=\"setting-control\"><div class=\"toggle\" id=\"toggle-instance-lock\" data-action=\"toggleSetting\" data-setting=\"instancePasswordEnabled\"></div></div>\n        </div>\n        <div class=\"setting-row\" id=\"instance-pwd-row\" style=\"display:none\">\n          <div class=\"setting-info\"><div class=\"setting-label\">Instance Password</div><div class=\"setting-desc\" id=\"instance-pwd-status\">Set a password for instance access</div></div>\n          <div class=\"setting-control\"><input type=\"password\" id=\"instance-password\" placeholder=\"New password\"><button class=\"btn btn-sm btn-primary\" data-action=\"setInstancePassword\">Set</button></div>\n        </div>\n        <div class=\"setting-row\">\n          <div class=\"setting-info\"><div class=\"setting-label\">Admin Panel Path</div><div class=\"setting-desc\">Custom URL path for the admin panel (default: admin)</div></div>\n          <div class=\"setting-control\"><span style=\"color:#8892a0;font-size:12px\">/</span><input type=\"text\" id=\"admin-path\" placeholder=\"admin\" style=\"width:100%;max-width:150px\" pattern=\"[a-zA-Z0-9_-]+\"><button class=\"btn btn-sm btn-primary\" data-action=\"saveAdminPath\">Save</button></div>\n        </div>\n        <div class=\"setting-row\" id=\"admin-path-info\" style=\"display:none\">\n          <div class=\"setting-info\"><div class=\"setting-label\"></div><div class=\"setting-desc\" id=\"admin-path-current\" style=\"color:#4ade80\">Current path: /admin</div></div>\n        </div>\n        <div class=\"setting-row\">\n          <div class=\"setting-info\"><div class=\"setting-label\">Show Admin Link</div><div class=\"setting-desc\">Show admin panel link in the landing page footer</div></div>\n          <div class=\"setting-control\"><div class=\"toggle\" id=\"toggle-show-admin-link\" data-action=\"toggleSetting\" data-setting=\"showAdminLink\"></div></div>\n        </div>\n      </div>\n      <div class=\"settings-section\">\n        <h3>TheOneFile Source</h3>\n        <div class=\"setting-row\">\n          <div class=\"setting-info\"><div class=\"setting-label\">Source Mode</div><div class=\"setting-desc\">Choose where to load TheOneFile from</div></div>\n          <div class=\"setting-control\">\n            <select id=\"source-mode\" data-action=\"changeSourceMode\"><option value=\"github\">GitHub (Auto Update)</option><option value=\"local\">Local (Manual Upload)</option></select>\n          </div>\n        </div>\n        <div class=\"setting-row\" id=\"github-settings\">\n          <div class=\"setting-info\"><div class=\"setting-label\">Update Interval</div><div class=\"setting-desc\">Hours between auto updates (0 = manual only)</div></div>\n          <div class=\"setting-control\"><input type=\"number\" id=\"update-interval\" min=\"0\" max=\"168\" style=\"width:80px\"><button class=\"btn btn-sm btn-primary\" data-action=\"saveUpdateInterval\">Save</button></div>\n        </div>\n        <div class=\"setting-row\" id=\"upload-row\" style=\"display:none\">\n          <div class=\"setting-info\"><div class=\"setting-label\">Upload Local File</div><div class=\"setting-desc\" id=\"upload-status\">Upload your own TheOneFile HTML</div></div>\n          <div class=\"setting-control\">\n            <input type=\"file\" id=\"upload-file\" accept=\".html\" style=\"display:none\" data-action=\"uploadFile\">\n            <button class=\"btn btn-sm btn-primary\" data-action=\"uploadFileClick\">Choose File</button>\n          </div>\n        </div>\n        <div style=\"padding:12px;background:var(--card-bg,#1a1f2e);border-radius:8px;margin-top:8px;\">\n          <div style=\"display:flex;align-items:center;justify-content:space-between;margin-bottom:8px;\">\n            <div>\n              <span id=\"current-version-badge\" style=\"font-size:18px;font-weight:700;color:var(--accent,#f0b429);\"></span>\n              <span id=\"current-edition-badge\" style=\"font-size:12px;color:#8892a0;margin-left:8px;\"></span>\n            </div>\n            <span id=\"version-status-badge\" style=\"font-size:11px;padding:3px 8px;border-radius:4px;font-weight:600;\"></span>\n          </div>\n          <div style=\"font-size:12px;color:#8892a0;margin-bottom:10px;\">\n            <span id=\"last-update-info\"></span>\n            <span id=\"file-size-info\" style=\"margin-left:12px;\"></span>\n          </div>\n          <div id=\"github-update-row\" style=\"display:flex;gap:8px;\">\n            <button class=\"btn btn-sm btn-success\" id=\"update-btn\" data-action=\"triggerUpdate\">Update Now</button>\n            <button class=\"btn btn-sm\" id=\"check-update-btn\" data-action=\"checkForUpdates\" style=\"background:#2a3040;color:#c8cdd5;\">Check for Updates</button>\n            <a href=\"https://github.com/gelatinescreams/The-One-File/releases\" target=\"_blank\" rel=\"noopener\" style=\"font-size:12px;color:var(--accent,#f0b429);align-self:center;margin-left:auto;text-decoration:none;\">Changelog</a>\n          </div>\n        </div>\n      </div>\n      <div class=\"settings-section\">\n        <h3>Appearance</h3>\n        <div class=\"setting-row\">\n          <div class=\"setting-info\"><div class=\"setting-label\">Theme</div><div class=\"setting-desc\">Force a theme for all users or let them choose</div></div>\n          <div class=\"setting-control\">\n            <select id=\"forced-theme\" data-action=\"saveForcedTheme\"><option value=\"user\">User Choice</option><option value=\"dark\">Force Dark</option><option value=\"light\">Force Light</option></select>\n          </div>\n        </div>\n      </div>\n      <div class=\"settings-section\">\n        <h3>Room Defaults</h3>\n        <div class=\"setting-row\">\n          <div class=\"setting-info\"><div class=\"setting-label\">Default Self Destruct</div><div class=\"setting-desc\">Default expiration for new rooms</div></div>\n          <div class=\"setting-control\">\n            <select id=\"default-destruct-mode\"><option value=\"time\">After time</option><option value=\"empty\">When empty</option><option value=\"never\">Never</option></select>\n            <input type=\"number\" id=\"default-destruct-hours\" min=\"1\" max=\"720\" style=\"width:70px\">\n            <span style=\"color:#8892a0;font-size:12px\">hours</span>\n          </div>\n        </div>\n        <div class=\"setting-row\">\n          <div class=\"setting-info\"><div class=\"setting-label\">Max Rooms</div><div class=\"setting-desc\">Maximum rooms allowed (0 = unlimited)</div></div>\n          <div class=\"setting-control\"><input type=\"number\" id=\"max-rooms\" min=\"0\" max=\"1000\" style=\"width:80px\"></div>\n        </div>\n        <div class=\"setting-row\">\n          <div class=\"setting-info\"><div class=\"setting-label\">Default Room Theme</div><div class=\"setting-desc\">Theme preset for new rooms (from loaded file)</div></div>\n          <div class=\"setting-control\"><select id=\"default-room-theme\"><option value=\"\">Default (from file)</option></select></div>\n        </div>\n        <div class=\"setting-row\">\n          <div class=\"setting-info\"><div class=\"setting-label\">Public Room Creation</div><div class=\"setting-desc\">Allow anyone to create rooms</div></div>\n          <div class=\"setting-control\"><div class=\"toggle\" id=\"toggle-public-rooms\" data-action=\"toggleSetting\" data-setting=\"allowPublicRoomCreation\"></div></div>\n        </div>\n        <div style=\"margin-top:16px\"><button class=\"btn btn-primary\" data-action=\"saveRoomDefaults\">Save Room Settings</button></div>\n      </div>\n      <div class=\"settings-section\">\n        <h3>Rate Limiting</h3>\n        <div class=\"setting-row\">\n          <div class=\"setting-info\"><div class=\"setting-label\">Enable Rate Limiting</div><div class=\"setting-desc\">Protect against brute force attacks</div></div>\n          <div class=\"setting-control\"><div class=\"toggle\" id=\"toggle-rate-limit\" data-action=\"toggleSetting\" data-setting=\"rateLimitEnabled\"></div></div>\n        </div>\n        <div class=\"setting-row\" id=\"rate-limit-options\">\n          <div class=\"setting-info\"><div class=\"setting-label\">Limit Settings</div><div class=\"setting-desc\">Max attempts per time window</div></div>\n          <div class=\"setting-control\">\n            <input type=\"number\" id=\"rate-limit-attempts\" min=\"1\" max=\"100\" style=\"width:60px\">\n            <span style=\"color:#8892a0;font-size:12px\">attempts per</span>\n            <input type=\"number\" id=\"rate-limit-window\" min=\"10\" max=\"3600\" style=\"width:70px\">\n            <span style=\"color:#8892a0;font-size:12px\">seconds</span>\n          </div>\n        </div>\n        <div style=\"margin-top:16px\"><button class=\"btn btn-primary\" data-action=\"saveRateLimitSettings\">Save Rate Limit Settings</button></div>\n      </div>\n      <div class=\"settings-section\">\n        <h3>Collaboration Features</h3>\n        <div class=\"setting-row\">\n          <div class=\"setting-info\"><div class=\"setting-label\">Chat</div><div class=\"setting-desc\">Enable chat in rooms</div></div>\n          <div class=\"setting-control\"><div class=\"toggle\" id=\"toggle-chat\" data-action=\"toggleSetting\" data-setting=\"chatEnabled\"></div></div>\n        </div>\n        <div class=\"setting-row\">\n          <div class=\"setting-info\"><div class=\"setting-label\">Cursor Sharing</div><div class=\"setting-desc\">Show other users cursors</div></div>\n          <div class=\"setting-control\"><div class=\"toggle\" id=\"toggle-cursor\" data-action=\"toggleSetting\" data-setting=\"cursorSharingEnabled\"></div></div>\n        </div>\n        <div class=\"setting-row\">\n          <div class=\"setting-info\"><div class=\"setting-label\">Name Changes</div><div class=\"setting-desc\">Allow users to change name after joining</div></div>\n          <div class=\"setting-control\"><div class=\"toggle\" id=\"toggle-namechange\" data-action=\"toggleSetting\" data-setting=\"nameChangeEnabled\"></div></div>\n        </div>\n        <div class=\"setting-row\">\n          <div class=\"setting-info\"><div class=\"setting-label\">Welcome Modal</div><div class=\"setting-desc\">Always show welcome setup when joining rooms</div></div>\n          <div class=\"setting-control\"><div class=\"toggle\" id=\"toggle-welcome-modal\" data-action=\"toggleSetting\" data-setting=\"forceWelcomeModal\"></div></div>\n        </div>\n      </div>\n      <div class=\"settings-section\">\n        <h3>Network Probing</h3>\n        <div class=\"setting-row\">\n          <div class=\"setting-info\"><div class=\"setting-label\">Server-Side Probing</div><div class=\"setting-desc\">Enable real ICMP/TCP/HTTP/DNS probes from server</div></div>\n          <div class=\"setting-control\"><div class=\"toggle\" id=\"toggle-probe\" data-action=\"toggleSetting\" data-setting=\"probeEnabled\"></div></div>\n        </div>\n        <div class=\"setting-row\">\n          <div class=\"setting-info\"><div class=\"setting-label\">Network Discovery</div><div class=\"setting-desc\">Allow subnet scanning to discover hosts</div></div>\n          <div class=\"setting-control\"><div class=\"toggle\" id=\"toggle-discovery\" data-action=\"toggleSetting\" data-setting=\"discoveryEnabled\"></div></div>\n        </div>\n        <div class=\"setting-row\">\n          <div class=\"setting-info\"><div class=\"setting-label\">Discovery Admin Only</div><div class=\"setting-desc\">Restrict network discovery to admin users</div></div>\n          <div class=\"setting-control\"><div class=\"toggle\" id=\"toggle-discovery-admin\" data-action=\"toggleSetting\" data-setting=\"discoveryAdminOnly\"></div></div>\n        </div>\n        <div class=\"setting-row\">\n          <div class=\"setting-info\"><div class=\"setting-label\">Allow Public Ranges</div><div class=\"setting-desc\">Allow scanning non-private (public) IP ranges</div></div>\n          <div class=\"setting-control\"><div class=\"toggle\" id=\"toggle-discovery-public\" data-action=\"toggleSetting\" data-setting=\"discoveryAllowPublicRanges\"></div></div>\n        </div>\n        <div class=\"setting-row\">\n          <div class=\"setting-info\"><div class=\"setting-label\">Max Scan Size</div><div class=\"setting-desc\">Minimum CIDR prefix (larger = smaller range)</div></div>\n          <div class=\"setting-control\">\n            <span style=\"color:#8892a0;font-size:12px\">/</span>\n            <input type=\"number\" id=\"discovery-max-prefix\" min=\"20\" max=\"32\" style=\"width:60px\">\n            <button class=\"btn btn-sm btn-primary\" data-action=\"saveDiscoveryPrefix\">Save</button>\n          </div>\n        </div>\n      </div>\n      <div class=\"settings-section\">\n        <h3>Webhooks</h3>\n        <div class=\"setting-row\">\n          <div class=\"setting-info\"><div class=\"setting-label\">Enable Webhooks</div><div class=\"setting-desc\">Send notifications for events</div></div>\n          <div class=\"setting-control\"><div class=\"toggle\" id=\"toggle-webhook\" data-action=\"toggleSetting\" data-setting=\"webhookEnabled\"></div></div>\n        </div>\n        <div class=\"setting-row\" id=\"webhook-url-row\">\n          <div class=\"setting-info\"><div class=\"setting-label\">Webhook URL</div><div class=\"setting-desc\">POST endpoint for notifications</div></div>\n          <div class=\"setting-control\"><input type=\"text\" id=\"webhook-url\" placeholder=\"https://...\" style=\"width:100%;max-width:250px\"><button class=\"btn btn-sm btn-primary\" data-action=\"saveWebhookUrl\">Save</button></div>\n        </div>\n      </div>\n      <div class=\"settings-section\">\n        <h3>Automatic Backups</h3>\n        <div class=\"setting-row\">\n          <div class=\"setting-info\"><div class=\"setting-label\">Enable Auto Backup</div><div class=\"setting-desc\">Automatically backup data</div></div>\n          <div class=\"setting-control\"><div class=\"toggle\" id=\"toggle-backup\" data-action=\"toggleSetting\" data-setting=\"backupEnabled\"></div></div>\n        </div>\n        <div class=\"setting-row\" id=\"backup-options\">\n          <div class=\"setting-info\"><div class=\"setting-label\">Backup Settings</div><div class=\"setting-desc\">Interval and retention</div></div>\n          <div class=\"setting-control\">\n            <span style=\"color:#8892a0;font-size:12px\">Every</span>\n            <input type=\"number\" id=\"backup-interval\" min=\"1\" max=\"168\" style=\"width:60px\">\n            <span style=\"color:#8892a0;font-size:12px\">hours, keep</span>\n            <input type=\"number\" id=\"backup-retention\" min=\"1\" max=\"100\" style=\"width:60px\">\n            <span style=\"color:#8892a0;font-size:12px\">backups</span>\n          </div>\n        </div>\n        <div style=\"margin-top:16px\"><button class=\"btn btn-primary\" data-action=\"saveBackupSettings\">Save Backup Settings</button></div>\n      </div>\n    </div>\n    <div id=\"tab-logs\" class=\"tab-content\">\n      <div class=\"settings-section\">\n        <h3>Activity Log</h3>\n        <div style=\"margin-bottom:12px;display:flex;gap:12px;align-items:center\"><input type=\"text\" id=\"activity-search\" placeholder=\"Filter by room ID...\" style=\"width:100%;max-width:250px\" data-action=\"loadActivityLogs\"><button class=\"btn btn-sm btn-secondary\" data-action=\"clearActivityLogs\">Clear All</button></div>\n        <div id=\"activity-log-list\" style=\"max-height:400px;overflow-y:auto\"></div>\n      </div>\n      <div class=\"settings-section\">\n        <h3>Audit Log</h3>\n        <div style=\"margin-bottom:12px;display:flex;gap:12px;align-items:center\"><input type=\"text\" id=\"audit-search\" placeholder=\"Search...\" style=\"width:100%;max-width:250px\" data-action=\"loadAuditLogs\"><button class=\"btn btn-sm btn-secondary\" data-action=\"clearAuditLogs\">Clear All</button></div>\n        <div id=\"audit-log-list\" style=\"max-height:400px;overflow-y:auto\"></div>\n      </div>\n    </div>\n    <div id=\"tab-backups\" class=\"tab-content\">\n      <div class=\"settings-section\">\n        <div style=\"display:flex;justify-content:space-between;align-items:center;margin-bottom:16px\">\n          <h3 style=\"margin:0;border:none;padding:0\">Backups</h3>\n          <div style=\"display:flex;gap:8px\">\n            <button class=\"btn btn-primary btn-sm\" data-action=\"createBackup\">Create Backup</button>\n            <button class=\"btn btn-secondary btn-sm\" data-action=\"exportAll\">Export All</button>\n          </div>\n        </div>\n        <div id=\"backup-list\"></div>\n      </div>\n    </div>\n    <div id=\"tab-apikeys\" class=\"tab-content\">\n      <div class=\"settings-section\">\n        <div style=\"display:flex;justify-content:space-between;align-items:center;margin-bottom:16px\">\n          <h3 style=\"margin:0;border:none;padding:0\">API Keys</h3>\n          <button class=\"btn btn-primary btn-sm\" data-action=\"showCreateApiKey\">Create API Key</button>\n        </div>\n        <div id=\"apikey-list\"></div>\n        <div id=\"new-key-display\" style=\"display:none;margin-top:16px;padding:16px;background:var(--bg);border-radius:8px\">\n          <p style=\"margin-bottom:8px;font-weight:500\">New API Key Created</p>\n          <p style=\"font-size:12px;color:var(--text-soft);margin-bottom:8px\">Copy this key now. It will not be shown again.</p>\n          <code id=\"new-key-value\" style=\"display:block;padding:12px;background:var(--surface);border-radius:4px;word-break:break-all;font-size:12px\"></code>\n        </div>\n      </div>\n    </div>\n  </div>\n  <div class=\"modal-overlay\" id=\"room-modal\">\n    <div class=\"modal\">\n      <div class=\"modal-header\"><h3 id=\"modal-title\">Room Details</h3><button class=\"modal-close\" data-action=\"closeModal\">&times;</button></div>\n      <div class=\"modal-body\" id=\"modal-body\"></div>\n    </div>\n  </div>\n  <div class=\"modal-overlay\" id=\"apikey-modal\">\n    <div class=\"modal\">\n      <div class=\"modal-header\"><h3>Create API Key</h3><button class=\"modal-close\" data-action=\"closeApiKeyModal\">&times;</button></div>\n      <div class=\"modal-body\">\n        <div style=\"margin-bottom:16px\"><label style=\"display:block;margin-bottom:4px;font-size:14px\">Name</label><input type=\"text\" id=\"apikey-name\" placeholder=\"My API Key\" style=\"width:100%\"></div>\n        <div style=\"margin-bottom:16px\"><label style=\"display:block;margin-bottom:4px;font-size:14px\">Expires In (days, 0=never)</label><input type=\"number\" id=\"apikey-expires\" value=\"0\" min=\"0\" style=\"width:100%\"></div>\n        <div style=\"margin-bottom:16px\"><label style=\"display:block;margin-bottom:4px;font-size:14px\">Permissions</label>\n          <label style=\"display:flex;align-items:center;gap:8px;margin:8px 0\"><input type=\"checkbox\" id=\"perm-read\" checked> Read rooms</label>\n          <label style=\"display:flex;align-items:center;gap:8px;margin:8px 0\"><input type=\"checkbox\" id=\"perm-write\"> Write rooms</label>\n          <label style=\"display:flex;align-items:center;gap:8px;margin:8px 0\"><input type=\"checkbox\" id=\"perm-admin\"> Admin access</label>\n        </div>\n        <button class=\"btn btn-primary\" data-action=\"createApiKey\" style=\"width:100%\">Create Key</button>\n      </div>\n    </div>\n  </div>\n  <div class=\"modal-overlay\" id=\"user-modal\">\n    <div class=\"modal\">\n      <div class=\"modal-header\"><h3>Create User</h3><button class=\"modal-close\" data-action=\"closeUserModal\">&times;</button></div>\n      <div class=\"modal-body\">\n        <div style=\"margin-bottom:16px\"><label style=\"display:block;margin-bottom:4px;font-size:14px\">Email</label><input type=\"email\" id=\"user-email\" placeholder=\"user@example.com\" style=\"width:100%\"></div>\n        <div style=\"margin-bottom:16px\"><label style=\"display:block;margin-bottom:4px;font-size:14px\">Display Name</label><input type=\"text\" id=\"user-displayname\" placeholder=\"John Doe\" style=\"width:100%\"></div>\n        <div style=\"margin-bottom:16px\"><label style=\"display:block;margin-bottom:4px;font-size:14px\">Password (leave blank for invite email)</label><input type=\"password\" id=\"user-password\" placeholder=\"Optional\" style=\"width:100%\"></div>\n        <div style=\"margin-bottom:16px\"><label style=\"display:block;margin-bottom:4px;font-size:14px\">Role</label>\n          <select id=\"user-role\" style=\"width:100%\"><option value=\"user\">User</option><option value=\"admin\">Admin</option></select>\n        </div>\n        <button class=\"btn btn-primary\" data-action=\"createUser\" style=\"width:100%\">Create User</button>\n      </div>\n    </div>\n  </div>\n  <div class=\"modal-overlay\" id=\"edit-user-modal\">\n    <div class=\"modal\">\n      <div class=\"modal-header\"><h3>Edit User</h3><button class=\"modal-close\" data-action=\"closeEditUserModal\">&times;</button></div>\n      <div class=\"modal-body\">\n        <input type=\"hidden\" id=\"edit-user-id\">\n        <div style=\"margin-bottom:16px\"><label style=\"display:block;margin-bottom:4px;font-size:14px\">Email</label><input type=\"email\" id=\"edit-user-email\" style=\"width:100%;background:var(--bg-alt)\" readonly></div>\n        <div style=\"margin-bottom:16px\"><label style=\"display:block;margin-bottom:4px;font-size:14px\">Display Name</label><input type=\"text\" id=\"edit-user-displayname\" placeholder=\"Display Name\" style=\"width:100%\"></div>\n        <div style=\"margin-bottom:16px\"><label style=\"display:block;margin-bottom:4px;font-size:14px\">New Password (leave blank to keep current)</label><input type=\"password\" id=\"edit-user-password\" placeholder=\"Optional\" style=\"width:100%\"></div>\n        <div style=\"margin-bottom:16px\"><label style=\"display:block;margin-bottom:4px;font-size:14px\">Role</label>\n          <select id=\"edit-user-role\" style=\"width:100%\"><option value=\"user\">User</option><option value=\"admin\">Admin</option></select>\n        </div>\n        <div style=\"margin-bottom:16px\">\n          <label style=\"display:flex;align-items:center;gap:8px\"><input type=\"checkbox\" id=\"edit-user-active\"> Active</label>\n          <label style=\"display:flex;align-items:center;gap:8px;margin-top:8px\"><input type=\"checkbox\" id=\"edit-user-verified\"> Email Verified</label>\n        </div>\n        <div style=\"display:flex;gap:8px\">\n          <button class=\"btn btn-secondary\" data-action=\"resetUserPassword\" style=\"flex:1\">Send Password Reset</button>\n          <button class=\"btn btn-primary\" data-action=\"saveUserEdit\" style=\"flex:1\">Save</button>\n        </div>\n      </div>\n    </div>\n  </div>\n  <div class=\"modal-overlay\" id=\"oidc-modal\">\n    <div class=\"modal\" style=\"max-width:550px\">\n      <div class=\"modal-header\"><h3 id=\"oidc-modal-title\">Add OIDC Provider</h3><button class=\"modal-close\" data-action=\"closeOidcModal\">&times;</button></div>\n      <div class=\"modal-body\">\n        <input type=\"hidden\" id=\"oidc-edit-id\">\n        <div style=\"margin-bottom:16px\"><label style=\"display:block;margin-bottom:4px;font-size:14px\">Name</label><input type=\"text\" id=\"oidc-name\" placeholder=\"My Provider\" style=\"width:100%\"></div>\n        <div style=\"margin-bottom:16px\"><label style=\"display:block;margin-bottom:4px;font-size:14px\">Provider Type</label>\n          <select id=\"oidc-type\" style=\"width:100%\"><option value=\"generic\">Generic OIDC</option><option value=\"authentik\">Authentik</option><option value=\"keycloak\">Keycloak</option><option value=\"auth0\">Auth0</option><option value=\"okta\">Okta</option></select>\n        </div>\n        <div style=\"margin-bottom:16px\"><label style=\"display:block;margin-bottom:4px;font-size:14px\">Client ID</label><input type=\"text\" id=\"oidc-client-id\" placeholder=\"client-id\" style=\"width:100%\"></div>\n        <div style=\"margin-bottom:16px\"><label style=\"display:block;margin-bottom:4px;font-size:14px\">Client Secret</label><input type=\"password\" id=\"oidc-client-secret\" placeholder=\"client-secret\" style=\"width:100%\"></div>\n        <div style=\"margin-bottom:16px\"><label style=\"display:block;margin-bottom:4px;font-size:14px\">Issuer URL</label><input type=\"text\" id=\"oidc-issuer\" placeholder=\"https://auth.example.com\" style=\"width:100%\"></div>\n        <div style=\"margin-bottom:16px\"><label style=\"display:block;margin-bottom:4px;font-size:14px\">Authorization URL</label><input type=\"text\" id=\"oidc-auth-url\" placeholder=\"https://auth.example.com/authorize\" style=\"width:100%\"></div>\n        <div style=\"margin-bottom:16px\"><label style=\"display:block;margin-bottom:4px;font-size:14px\">Token URL</label><input type=\"text\" id=\"oidc-token-url\" placeholder=\"https://auth.example.com/token\" style=\"width:100%\"></div>\n        <div style=\"margin-bottom:16px\"><label style=\"display:block;margin-bottom:4px;font-size:14px\">Userinfo URL</label><input type=\"text\" id=\"oidc-userinfo-url\" placeholder=\"https://auth.example.com/userinfo\" style=\"width:100%\"></div>\n        <div style=\"margin-bottom:16px\"><label style=\"display:block;margin-bottom:4px;font-size:14px\">Scopes</label><input type=\"text\" id=\"oidc-scopes\" value=\"openid email profile\" style=\"width:100%\"></div>\n        <div style=\"margin-bottom:16px\"><label style=\"display:flex;align-items:center;gap:8px\"><input type=\"checkbox\" id=\"oidc-active\" checked> Active</label></div>\n        <button class=\"btn btn-primary\" data-action=\"saveOidcProvider\" style=\"width:100%\">Save Provider</button>\n      </div>\n    </div>\n  </div>\n  <div class=\"modal-overlay\" id=\"smtp-modal\">\n    <div class=\"modal\" style=\"max-width:550px\">\n      <div class=\"modal-header\"><h3 id=\"smtp-modal-title\">Add SMTP Configuration</h3><button class=\"modal-close\" data-action=\"closeSmtpModal\">&times;</button></div>\n      <div class=\"modal-body\">\n        <input type=\"hidden\" id=\"smtp-edit-id\">\n        <div style=\"margin-bottom:16px\"><label style=\"display:block;margin-bottom:4px;font-size:14px\">Name</label><input type=\"text\" id=\"smtp-name\" placeholder=\"Primary SMTP\" style=\"width:100%\"></div>\n        <div style=\"margin-bottom:16px\"><label style=\"display:block;margin-bottom:4px;font-size:14px\">Host</label><input type=\"text\" id=\"smtp-host\" placeholder=\"smtp.example.com\" style=\"width:100%\"></div>\n        <div style=\"margin-bottom:16px\"><label style=\"display:block;margin-bottom:4px;font-size:14px\">Port</label><input type=\"number\" id=\"smtp-port\" value=\"587\" style=\"width:100%\"></div>\n        <div style=\"margin-bottom:16px\"><label style=\"display:block;margin-bottom:4px;font-size:14px\">Security Mode</label>\n          <select id=\"smtp-secure-mode\" style=\"width:100%\"><option value=\"starttls\">STARTTLS (587)</option><option value=\"tls\">TLS/SSL (465)</option><option value=\"none\">None (25)</option></select>\n        </div>\n        <div style=\"margin-bottom:16px\"><label style=\"display:block;margin-bottom:4px;font-size:14px\">Username</label><input type=\"text\" id=\"smtp-username\" placeholder=\"user@example.com\" style=\"width:100%\"></div>\n        <div style=\"margin-bottom:16px\"><label style=\"display:block;margin-bottom:4px;font-size:14px\">Password</label><input type=\"password\" id=\"smtp-password\" placeholder=\"password\" style=\"width:100%\"></div>\n        <div style=\"margin-bottom:16px\"><label style=\"display:block;margin-bottom:4px;font-size:14px\">From Email</label><input type=\"email\" id=\"smtp-from-email\" placeholder=\"noreply@example.com\" style=\"width:100%\"></div>\n        <div style=\"margin-bottom:16px\"><label style=\"display:block;margin-bottom:4px;font-size:14px\">From Name</label><input type=\"text\" id=\"smtp-from-name\" placeholder=\"TheOneFile_Verse\" style=\"width:100%\"></div>\n        <div style=\"margin-bottom:16px\">\n          <label style=\"display:flex;align-items:center;gap:8px\"><input type=\"checkbox\" id=\"smtp-default\"> Set as default</label>\n          <label style=\"display:flex;align-items:center;gap:8px;margin-top:8px\"><input type=\"checkbox\" id=\"smtp-active\" checked> Active</label>\n        </div>\n        <div style=\"display:flex;gap:8px\">\n          <button class=\"btn btn-secondary\" data-action=\"testSmtpConfig\" style=\"flex:1\">Test</button>\n          <button class=\"btn btn-primary\" data-action=\"saveSmtpConfig\" style=\"flex:1\">Save</button>\n        </div>\n      </div>\n    </div>\n  </div>\n  <div class=\"modal-overlay\" id=\"template-modal\">\n    <div class=\"modal\" style=\"max-width:900px;max-height:90vh;display:flex;flex-direction:column\">\n      <div class=\"modal-header\"><h3 id=\"template-modal-title\">Edit Email Template</h3><button class=\"modal-close\" data-action=\"closeTemplateModal\">&times;</button></div>\n      <div class=\"modal-body\" style=\"flex:1;overflow:auto;display:flex;flex-direction:column\">\n        <input type=\"hidden\" id=\"template-edit-id\">\n        <div style=\"margin-bottom:16px\"><label style=\"display:block;margin-bottom:4px;font-size:14px\">Template Name</label><input type=\"text\" id=\"template-name\" readonly style=\"width:100%;background:var(--bg-alt)\"></div>\n        <div style=\"margin-bottom:16px\"><label style=\"display:block;margin-bottom:4px;font-size:14px\">Subject</label><input type=\"text\" id=\"template-subject\" placeholder=\"Email subject with {{variables}}\" style=\"width:100%\"></div>\n        <div style=\"margin-bottom:8px;display:flex;justify-content:space-between;align-items:center\">\n          <label style=\"font-size:14px\">HTML Body</label>\n          <div style=\"display:flex;gap:4px\">\n            <button class=\"btn btn-sm btn-secondary\" data-action=\"insertTemplateVar\" data-varname=\"displayName\">{{displayName}}</button>\n            <button class=\"btn btn-sm btn-secondary\" data-action=\"insertTemplateVar\" data-varname=\"actionUrl\">{{actionUrl}}</button>\n            <button class=\"btn btn-sm btn-secondary\" data-action=\"insertTemplateVar\" data-varname=\"appName\">{{appName}}</button>\n            <button class=\"btn btn-sm btn-secondary\" data-action=\"toggleTemplateView\">Toggle HTML/Preview</button>\n          </div>\n        </div>\n        <div style=\"flex:1;min-height:300px;position:relative\">\n          <div id=\"template-editor-toolbar\" style=\"background:var(--bg-alt);border:1px solid var(--border);border-bottom:none;border-radius:8px 8px 0 0;padding:8px;display:flex;gap:4px;flex-wrap:wrap\">\n            <button type=\"button\" class=\"btn btn-sm btn-secondary\" data-action=\"execCmd\" data-cmd=\"bold\" title=\"Bold\"><b>B</b></button>\n            <button type=\"button\" class=\"btn btn-sm btn-secondary\" data-action=\"execCmd\" data-cmd=\"italic\" title=\"Italic\"><i>I</i></button>\n            <button type=\"button\" class=\"btn btn-sm btn-secondary\" data-action=\"execCmd\" data-cmd=\"underline\" title=\"Underline\"><u>U</u></button>\n            <span style=\"border-left:1px solid var(--border);margin:0 4px\"></span>\n            <button type=\"button\" class=\"btn btn-sm btn-secondary\" data-action=\"execCmd\" data-cmd=\"justifyLeft\" title=\"Align Left\">&#8676;</button>\n            <button type=\"button\" class=\"btn btn-sm btn-secondary\" data-action=\"execCmd\" data-cmd=\"justifyCenter\" title=\"Center\">&#8596;</button>\n            <button type=\"button\" class=\"btn btn-sm btn-secondary\" data-action=\"execCmd\" data-cmd=\"justifyRight\" title=\"Align Right\">&#8677;</button>\n            <span style=\"border-left:1px solid var(--border);margin:0 4px\"></span>\n            <button type=\"button\" class=\"btn btn-sm btn-secondary\" data-action=\"execCmd\" data-cmd=\"insertUnorderedList\" title=\"Bullet List\">&#8226;</button>\n            <button type=\"button\" class=\"btn btn-sm btn-secondary\" data-action=\"execCmd\" data-cmd=\"insertOrderedList\" title=\"Numbered List\">1.</button>\n            <span style=\"border-left:1px solid var(--border);margin:0 4px\"></span>\n            <select data-action=\"execCmdArg\" data-cmd=\"formatBlock\" style=\"padding:4px 8px;background:var(--surface);border:1px solid var(--border);border-radius:4px;color:var(--text);font-size:12px\">\n              <option value=\"\">Heading</option>\n              <option value=\"h1\">Heading 1</option>\n              <option value=\"h2\">Heading 2</option>\n              <option value=\"h3\">Heading 3</option>\n              <option value=\"p\">Paragraph</option>\n            </select>\n            <select data-action=\"execCmdArg\" data-cmd=\"fontSize\" style=\"padding:4px 8px;background:var(--surface);border:1px solid var(--border);border-radius:4px;color:var(--text);font-size:12px\">\n              <option value=\"\">Size</option>\n              <option value=\"1\">Small</option>\n              <option value=\"3\">Normal</option>\n              <option value=\"5\">Large</option>\n              <option value=\"7\">Huge</option>\n            </select>\n            <input type=\"color\" id=\"template-color\" data-action=\"templateColor\" title=\"Text Color\" style=\"width:30px;height:26px;padding:0;border:1px solid var(--border);border-radius:4px;cursor:pointer\">\n            <span style=\"border-left:1px solid var(--border);margin:0 4px\"></span>\n            <button type=\"button\" class=\"btn btn-sm btn-secondary\" data-action=\"insertLink\" title=\"Insert Link\">&#128279;</button>\n            <button type=\"button\" class=\"btn btn-sm btn-secondary\" data-action=\"insertImage\" title=\"Insert Image\">&#128247;</button>\n            <button type=\"button\" class=\"btn btn-sm btn-secondary\" data-action=\"insertButton\" title=\"Insert Button\">&#9634; Btn</button>\n          </div>\n          <div id=\"template-editor\" contenteditable=\"true\" style=\"flex:1;min-height:250px;background:var(--surface);border:1px solid var(--border);border-radius:0 0 8px 8px;padding:16px;overflow-y:auto;font-family:system-ui;line-height:1.6\"></div>\n          <textarea id=\"template-html-source\" style=\"display:none;width:100%;min-height:300px;background:var(--surface);border:1px solid var(--border);border-radius:8px;padding:16px;font-family:monospace;font-size:13px;color:var(--text);resize:vertical\"></textarea>\n        </div>\n        <div style=\"margin-top:16px\"><label style=\"display:block;margin-bottom:4px;font-size:14px\">Plain Text Body (fallback)</label><textarea id=\"template-text\" placeholder=\"Plain text version for email clients that don't support HTML\" style=\"width:100%;height:100px;background:var(--bg);border:1px solid var(--border);border-radius:8px;padding:12px;font-family:monospace;font-size:13px;color:var(--text);resize:vertical\"></textarea></div>\n        <div style=\"margin-top:16px;display:flex;gap:8px;justify-content:flex-end\">\n          <button class=\"btn btn-secondary\" data-action=\"previewTemplate\">Preview</button>\n          <button class=\"btn btn-primary\" data-action=\"saveTemplate\">Save Template</button>\n        </div>\n      </div>\n    </div>\n  </div>\n  <div class=\"modal-overlay\" id=\"template-preview-modal\">\n    <div class=\"modal\" style=\"max-width:700px;max-height:80vh\">\n      <div class=\"modal-header\"><h3>Email Preview</h3><button class=\"modal-close\" data-action=\"closeTemplatePreview\">&times;</button></div>\n      <div class=\"modal-body\" style=\"padding:0\">\n        <div style=\"padding:12px 16px;background:var(--bg-alt);border-bottom:1px solid var(--border)\">\n          <div style=\"font-size:12px;color:var(--text-soft)\">Subject:</div>\n          <div id=\"preview-subject\" style=\"font-weight:500\"></div>\n        </div>\n        <iframe id=\"preview-frame\" style=\"width:100%;height:400px;border:none;background:#fff\"></iframe>\n      </div>\n    </div>\n  </div>\n  <script type=\"application/json\" id=\"page-data\">{\"adminPath\":\"ADMIN_PATH_PLACEHOLDER\"}</script>\n  <script src=\"/admin-dashboard.js\"></script>\n</body>\n</html>`;\n\nexport const adminLoginHtml = `<!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\">\n  <title>Admin Login - The One File Collab</title>\n  <style>\n    *{box-sizing:border-box;margin:0;padding:0}\n    button,a{-webkit-tap-highlight-color:transparent}\n    :root{--bg:#0d0d0d;--bg-alt:#1a1a1a;--surface:#242424;--border:#333;--text:#e8e8e8;--text-soft:#999;--accent:#c9a227;--accent-hover:#d4b23a}\n    [data-theme=\"light\"]{--bg:#f5f3ef;--bg-alt:#eae7e0;--surface:#fff;--border:#d4d0c8;--text:#1a1a1a;--text-soft:#666;--accent:#996b1f;--accent-hover:#7a5518}\n    body{font-family:Inter,system-ui,sans-serif;background:var(--bg);color:var(--text);min-height:100vh;display:flex;align-items:center;justify-content:center;padding:20px;padding-left:max(20px,env(safe-area-inset-left,20px));padding-right:max(20px,env(safe-area-inset-right,20px));padding-bottom:max(20px,env(safe-area-inset-bottom,20px))}\n    .login-box{background:var(--surface);border:1px solid var(--border);border-radius:16px;padding:40px;width:100%;max-width:400px}\n    h1{font-size:24px;margin-bottom:8px;text-align:center}\n    p{color:var(--text-soft);font-size:14px;text-align:center;margin-bottom:32px}\n    label{display:block;font-size:14px;color:var(--text-soft);margin-bottom:6px}\n    input{width:100%;padding:14px 16px;background:var(--bg);border:1px solid var(--border);border-radius:8px;color:var(--text);font-size:16px;margin-bottom:16px;outline:none}\n    input:focus{border-color:var(--accent)}\n    button{width:100%;padding:14px;background:var(--accent);border:none;border-radius:8px;color:white;font-size:16px;font-weight:600;cursor:pointer;margin-top:8px}\n    button:hover{background:var(--accent-hover)}\n    .error{color:#ef4444;font-size:14px;text-align:center;margin-bottom:16px;display:none}\n    .error.active{display:block}\n    .back-link{text-align:center;margin-top:20px}\n    .back-link a{color:var(--accent);text-decoration:none;font-size:14px}\n    .oidc-btn{display:flex;align-items:center;justify-content:center;gap:8px;background:var(--bg);border:1px solid var(--border);margin-bottom:12px}\n    .oidc-btn:hover{background:var(--bg-alt)}\n    .divider{display:flex;align-items:center;gap:12px;margin:24px 0;color:var(--text-soft);font-size:12px}\n    .divider::before,.divider::after{content:'';flex:1;height:1px;background:var(--border)}\n    @media(max-width:640px){.setup-box,.login-box,.box{padding:24px}}\n    @media(max-width:380px){.setup-box,.login-box,.box{padding:20px 16px}}\n  </style>\n</head>\n<body data-page=\"admin-login\">\n  <div class=\"login-box\">\n    <h1>Admin Login</h1>\n    <p>Sign in with your admin account</p>\n    <div class=\"error\" id=\"error\"></div>\n    <div id=\"oidc-buttons\"></div>\n    <div class=\"divider\" id=\"divider\" style=\"display:none\">or continue with email</div>\n    <form id=\"login-form\" novalidate>\n      <label for=\"email\">Email</label>\n      <input type=\"email\" id=\"email\" placeholder=\"admin@example.com\" autocomplete=\"email\" autofocus>\n      <label for=\"password\">Password</label>\n      <input type=\"password\" id=\"password\" placeholder=\"Your password\" autocomplete=\"current-password\">\n      <button type=\"submit\">Login</button>\n    </form>\n    <div class=\"back-link\"><a href=\"/\">Back to App</a></div>\n  </div>\n  <script type=\"application/json\" id=\"page-data\">{\"adminPath\":\"ADMIN_PATH_PLACEHOLDER\"}</script>\n  <script src=\"/admin-auth.js\"></script>\n  <script src=\"/admin-pages.js\"></script>\n</body>\n</html>`;\n\nexport function getPasswordResetHtml(token: string): string {\n  const safePageData = JSON.stringify({ token }).replace(/</g, '\\\\u003c');\n  return `<!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\">\n  <title>Reset Password - TheOneFile_Verse</title>\n  <style>\n    *{box-sizing:border-box;margin:0;padding:0}\n    button,a{-webkit-tap-highlight-color:transparent}\n    :root{--bg:#0d0d0d;--surface:#242424;--border:#333;--text:#e8e8e8;--text-soft:#999;--accent:#c9a227;--accent-hover:#d4b23a}\n    body{font-family:system-ui,sans-serif;background:var(--bg);color:var(--text);min-height:100vh;display:flex;align-items:center;justify-content:center;padding:20px;padding-left:max(20px,env(safe-area-inset-left,20px));padding-right:max(20px,env(safe-area-inset-right,20px));padding-bottom:max(20px,env(safe-area-inset-bottom,20px))}\n    .box{background:var(--surface);border:1px solid var(--border);border-radius:16px;padding:40px;width:100%;max-width:400px}\n    h1{font-size:24px;margin-bottom:8px;text-align:center}\n    p{color:var(--text-soft);font-size:14px;text-align:center;margin-bottom:32px}\n    input{width:100%;padding:14px 16px;background:var(--bg);border:1px solid var(--border);border-radius:8px;color:var(--text);font-size:16px;margin-bottom:16px;outline:none}\n    input:focus{border-color:var(--accent)}\n    button{width:100%;padding:14px;background:var(--accent);border:none;border-radius:8px;color:white;font-size:16px;font-weight:600;cursor:pointer}\n    button:hover{background:var(--accent-hover)}\n    .error{color:#ef4444;font-size:14px;text-align:center;margin-bottom:16px;display:none}\n    .error.active{display:block}\n    .success{color:#22c55e;font-size:14px;text-align:center;margin-bottom:16px;display:none}\n    .success.active{display:block}\n    @media(max-width:640px){.setup-box,.login-box,.box{padding:24px}}\n    @media(max-width:380px){.setup-box,.login-box,.box{padding:20px 16px}}\n  </style>\n</head>\n<body data-page=\"password-reset\">\n  <div class=\"box\">\n    <h1>Reset Password</h1>\n    <p>Enter your new password</p>\n    <div class=\"error\" id=\"error\"></div>\n    <div class=\"success\" id=\"success\">Password reset successfully! <a href=\"/\" style=\"color:var(--accent)\">Go to home</a></div>\n    <form id=\"form\" novalidate>\n      <input type=\"password\" id=\"password\" placeholder=\"New password (min 8 characters)\" minlength=\"8\" required autocomplete=\"new-password\">\n      <input type=\"password\" id=\"confirm\" placeholder=\"Confirm password\" required autocomplete=\"new-password\">\n      <button type=\"submit\">Reset Password</button>\n    </form>\n  </div>\n  <script type=\"application/json\" id=\"page-data\">${safePageData}</script>\n  <script src=\"/admin-auth.js\"></script>\n  <script src=\"/admin-pages.js\"></script>\n</body>\n</html>`;\n}\n"
  },
  {
    "path": "theonefile_verse/src/tokens.ts",
    "content": "import * as redis from \"./redis\";\nimport * as oidc from \"./oidc\";\nimport * as db from \"./database\";\n\ninterface TokenEntry {\n  token: string;\n  createdAt: number;\n}\n\nconst ADMIN_TOKENS = new Map<string, TokenEntry>();\nconst INSTANCE_TOKENS = new Map<string, number>();\nconst TOKEN_EXPIRY = 7 * 24 * 60 * 60 * 1000;\n\nconst ROOM_ACCESS_TOKENS = new Map<string, { roomId: string; expiresAt: number }>();\nconst ROOM_ACCESS_EXPIRY = 24 * 60 * 60 * 1000;\n\ninterface WsSessionToken {\n  roomId: string;\n  collabUserId: string;\n  createdAt: number;\n  expiresAt: number;\n}\n\nconst WS_SESSION_TOKENS = new Map<string, WsSessionToken>();\nexport const WS_TOKEN_EXPIRY = 5 * 60 * 1000;\n\nexport async function generateWsSessionToken(roomId: string, collabUserId: string): Promise<string> {\n  const token = oidc.generateSecureToken(32);\n  const now = Date.now();\n  const tokenData: WsSessionToken = {\n    roomId,\n    collabUserId,\n    createdAt: now,\n    expiresAt: now + WS_TOKEN_EXPIRY\n  };\n  if (redis.isRedisConnected()) {\n    await redis.setSessionToken(`ws:${token}`, tokenData, Math.ceil(WS_TOKEN_EXPIRY / 1000));\n  } else {\n    WS_SESSION_TOKENS.set(token, tokenData);\n  }\n  return token;\n}\n\nexport async function validateWsSessionToken(token: string, roomId: string): Promise<{ valid: boolean; collabUserId?: string }> {\n  if (!token) return { valid: false };\n  let tokenData: WsSessionToken | null = null;\n  if (redis.isRedisConnected()) {\n    tokenData = await redis.getSessionToken(`ws:${token}`) as WsSessionToken | null;\n    if (tokenData) {\n      await redis.deleteSessionToken(`ws:${token}`);\n    }\n  } else {\n    tokenData = WS_SESSION_TOKENS.get(token) || null;\n    if (tokenData) {\n      WS_SESSION_TOKENS.delete(token);\n    }\n  }\n  if (!tokenData) return { valid: false };\n  if (Date.now() > tokenData.expiresAt) return { valid: false };\n  if (tokenData.roomId !== roomId) return { valid: false };\n  return { valid: true, collabUserId: tokenData.collabUserId };\n}\n\nconst MAX_WS_SESSION_TOKENS = 50000;\n\nexport function generateRoomAccessToken(roomId: string): string {\n  const token = oidc.generateSecureToken(32);\n  ROOM_ACCESS_TOKENS.set(token, { roomId, expiresAt: Date.now() + ROOM_ACCESS_EXPIRY });\n  if (ROOM_ACCESS_TOKENS.size > 10000) {\n    const now = Date.now();\n    for (const [k, v] of ROOM_ACCESS_TOKENS) {\n      if (now > v.expiresAt) ROOM_ACCESS_TOKENS.delete(k);\n    }\n  }\n  return token;\n}\n\nexport function validateRoomAccessToken(token: string, roomId: string): boolean {\n  const data = ROOM_ACCESS_TOKENS.get(token);\n  if (!data) return false;\n  if (Date.now() > data.expiresAt) { ROOM_ACCESS_TOKENS.delete(token); return false; }\n  return data.roomId === roomId;\n}\n\nexport async function generateAdminToken(): Promise<string> {\n  const token = oidc.generateSecureToken(32);\n  const data = { type: \"admin\", createdAt: Date.now() };\n  if (redis.isRedisConnected()) {\n    await redis.setSessionToken(token, data, 604800);\n  } else {\n    ADMIN_TOKENS.set(token, { token, createdAt: Date.now() });\n  }\n  return token;\n}\n\nexport async function validateAdminToken(token: string): Promise<boolean> {\n  if (redis.isRedisConnected()) {\n    const data = await redis.getSessionToken(token);\n    if (!data || data.type !== \"admin\") return false;\n    if (Date.now() - data.createdAt > TOKEN_EXPIRY) {\n      await redis.deleteSessionToken(token);\n      return false;\n    }\n    return true;\n  }\n  const entry = ADMIN_TOKENS.get(token);\n  if (!entry) return false;\n  if (Date.now() - entry.createdAt > TOKEN_EXPIRY) {\n    ADMIN_TOKENS.delete(token);\n    return false;\n  }\n  return true;\n}\n\nexport function generateInstanceToken(): string {\n  const token = oidc.generateSecureToken(32);\n  INSTANCE_TOKENS.set(token, Date.now());\n  return token;\n}\n\nexport function validateInstanceToken(token: string): boolean {\n  const createdAt = INSTANCE_TOKENS.get(token);\n  if (!createdAt) return false;\n  if (Date.now() - createdAt > TOKEN_EXPIRY) {\n    INSTANCE_TOKENS.delete(token);\n    return false;\n  }\n  return true;\n}\n\nexport function storeInstanceToken(token: string): void {\n  INSTANCE_TOKENS.set(token, Date.now());\n}\n\nexport function removeInstanceToken(token: string): void {\n  INSTANCE_TOKENS.delete(token);\n}\n\nexport async function removeAdminToken(token: string): Promise<void> {\n  ADMIN_TOKENS.delete(token);\n}\n\nexport async function hashApiKey(key: string): Promise<string> {\n  const encoder = new TextEncoder();\n  const data = encoder.encode(key);\n  const hash = await crypto.subtle.digest(\"SHA-256\", data);\n  return Buffer.from(hash).toString(\"hex\");\n}\n\nexport async function validateApiKey(key: string): Promise<db.ApiKey | null> {\n  const hash = await hashApiKey(key);\n  const apiKey = db.getApiKeyByHash(hash);\n  if (!apiKey) return null;\n  if (apiKey.expiresAt && new Date(apiKey.expiresAt) < new Date()) return null;\n  db.updateApiKeyLastUsed(apiKey.id);\n  return apiKey;\n}\n\nexport function startTokenCleanupIntervals(): void {\n  setInterval(() => {\n    const now = Date.now();\n    for (const [token, data] of WS_SESSION_TOKENS.entries()) {\n      if (now > data.expiresAt) {\n        WS_SESSION_TOKENS.delete(token);\n      }\n    }\n    if (WS_SESSION_TOKENS.size > MAX_WS_SESSION_TOKENS) {\n      const entries = [...WS_SESSION_TOKENS.entries()].sort((a, b) => a[1].createdAt - b[1].createdAt);\n      const toRemove = entries.length - Math.floor(MAX_WS_SESSION_TOKENS * 0.75);\n      for (let i = 0; i < toRemove; i++) WS_SESSION_TOKENS.delete(entries[i][0]);\n    }\n  }, 60 * 1000);\n\n  setInterval(() => {\n    const now = Date.now();\n    for (const [token, entry] of ADMIN_TOKENS.entries()) {\n      if (now - entry.createdAt > TOKEN_EXPIRY) {\n        ADMIN_TOKENS.delete(token);\n      }\n    }\n    if (ADMIN_TOKENS.size > 10000) {\n      const sorted = [...ADMIN_TOKENS.entries()].sort((a, b) => a[1].createdAt - b[1].createdAt);\n      for (let i = 0; i < sorted.length - 5000; i++) {\n        ADMIN_TOKENS.delete(sorted[i][0]);\n      }\n    }\n  }, 10 * 60 * 1000);\n\n  setInterval(() => {\n    const now = Date.now();\n    for (const [token, createdAt] of INSTANCE_TOKENS.entries()) {\n      if (now - createdAt > TOKEN_EXPIRY) INSTANCE_TOKENS.delete(token);\n    }\n    if (INSTANCE_TOKENS.size > 1000) {\n      const sorted = [...INSTANCE_TOKENS.entries()].sort((a, b) => a[1] - b[1]);\n      for (let i = 0; i < sorted.length - 500; i++) INSTANCE_TOKENS.delete(sorted[i][0]);\n    }\n  }, 10 * 60 * 1000);\n}\n\nexport function cleanupExpiredRoomTokens(): void {\n  const now = Date.now();\n  for (const [k, v] of ROOM_ACCESS_TOKENS) {\n    if (now > v.expiresAt) ROOM_ACCESS_TOKENS.delete(k);\n  }\n}\n"
  },
  {
    "path": "theonefile_verse/src/websocket.ts",
    "content": "import * as redis from \"./redis\";\nimport * as oidc from \"./oidc\";\nimport * as db from \"./database\";\nimport * as auth from \"./auth\";\nimport { roomConnections, roomUsers, roomMeta, roomUsedNames, roomChatHistory, loadRoom, saveRoom, deleteRoomData, scheduleDestruction, resetDestructionTimer, validateTopology, sanitizeTopologyStrings, type Room, type RoomMeta } from \"./rooms\";\nimport { validateWsSessionToken } from \"./tokens\";\nimport { checkWsRateLimit } from \"./rate-limit\";\nimport { getSettings, isValidUUID } from \"./config\";\n\nexport interface WsData {\n  roomId?: string;\n  connectionId?: string;\n  userId?: string;\n  verifiedUserId?: string;\n  authenticated?: boolean;\n}\n\nexport const websocketHandlers = {\n  open(ws: any) {\n    const roomId = (ws.data as WsData)?.roomId;\n    if (!roomId) return;\n    const connectionId = crypto.randomUUID();\n    (ws.data as WsData).connectionId = connectionId;\n    if (!roomConnections.has(roomId)) roomConnections.set(roomId, new Set());\n    roomConnections.get(roomId)!.add(ws);\n    if (!roomUsers.has(roomId)) roomUsers.set(roomId, new Map());\n    ws.subscribe(roomId);\n    const meta = roomMeta.get(roomId) || { connectedUsers: 0 };\n    meta.connectedUsers++;\n    if (meta.destructTimer) { clearTimeout(meta.destructTimer); meta.destructTimer = undefined; }\n    roomMeta.set(roomId, meta);\n  },\n\n  async message(ws: any, message: any) {\n    const roomId = (ws.data as WsData)?.roomId;\n    const connectionId = (ws.data as WsData)?.connectionId;\n    if (!roomId || !connectionId) return;\n    let msg;\n    try { msg = JSON.parse(message.toString()); } catch { return; }\n\n    if (msg.type === 'auth') {\n      if ((ws.data as WsData).authenticated) {\n        ws.send(JSON.stringify({ type: 'auth-ok' }));\n        return;\n      }\n      const token = msg.token;\n      if (!token || typeof token !== 'string') {\n        ws.send(JSON.stringify({ type: 'auth-error', error: 'Token required' }));\n        ws.close(4001, 'Authentication failed');\n        return;\n      }\n      const tokenValidation = await validateWsSessionToken(token, roomId);\n      if (!tokenValidation.valid) {\n        ws.send(JSON.stringify({ type: 'auth-error', error: 'Invalid or expired token' }));\n        ws.close(4001, 'Authentication failed');\n        return;\n      }\n      (ws.data as WsData).authenticated = true;\n      (ws.data as WsData).verifiedUserId = tokenValidation.collabUserId;\n      ws.send(JSON.stringify({ type: 'auth-ok' }));\n      return;\n    }\n\n    if (!(ws.data as WsData).authenticated) {\n      ws.send(JSON.stringify({ type: 'auth-error', error: 'Authenticate first' }));\n      ws.close(4001, 'Not authenticated');\n      return;\n    }\n\n    const validTypes = ['join', 'leave', 'presence', 'state', 'patch', 'chat', 'cursor', 'typing'];\n    if (!msg.type || !validTypes.includes(msg.type)) return;\n\n    if (!checkWsRateLimit(connectionId, msg.type)) {\n      ws.send(JSON.stringify({ type: 'rate-limited', messageType: msg.type }));\n      return;\n    }\n\n    const messageStr = message.toString();\n    const maxSize = msg.type === 'state' ? 5 * 1024 * 1024 : 1024;\n    if (messageStr.length > maxSize) return;\n\n    if (msg.type === 'chat') {\n      if (!msg.text || typeof msg.text !== 'string') return;\n      if (msg.text.length > 500) msg.text = msg.text.substring(0, 500);\n      msg.text = msg.text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\"/g, '&quot;').replace(/'/g, '&#039;');\n      if (!roomChatHistory.has(roomId)) roomChatHistory.set(roomId, []);\n      const history = roomChatHistory.get(roomId)!;\n      history.push({ ...msg, timestamp: Date.now() });\n      if (history.length > 100) history.splice(0, history.length - 100);\n      ws.publish(roomId, JSON.stringify(msg));\n      resetDestructionTimer(roomId);\n      return;\n    }\n\n    if (msg.type === 'join' && msg.user) {\n      let userId = msg.user.id;\n      if (!userId || !isValidUUID(userId)) return;\n\n      const verifiedUserId = (ws.data as WsData)?.verifiedUserId;\n      if (verifiedUserId) {\n        if (userId !== verifiedUserId) {\n          ws.send(JSON.stringify({ type: 'error', error: 'User ID mismatch with session token' }));\n          return;\n        }\n      }\n\n      let rawName = msg.user.name;\n      if (rawName && typeof rawName === 'string') {\n        rawName = rawName.substring(0, 30).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\"/g, '&quot;').replace(/'/g, '&#039;').trim();\n      }\n      msg.user = { id: userId, name: rawName || '', color: msg.user.color || '' };\n      const userName = rawName?.toLowerCase().trim();\n      const users = roomUsers.get(roomId)!;\n\n      if (!roomUsedNames.has(roomId)) roomUsedNames.set(roomId, new Map());\n      const usedNames = roomUsedNames.get(roomId)!;\n\n      const existingUser = users.get(userId);\n      const isNameChange = existingUser && existingUser.name?.toLowerCase().trim() !== userName;\n      const isNewUser = !existingUser;\n\n      if (userName && (isNewUser || isNameChange)) {\n        const nameOwner = usedNames.get(userName);\n        if (nameOwner && nameOwner !== userId) {\n          ws.send(JSON.stringify({ type: 'name-rejected', reason: 'Name already taken in this room' }));\n          return;\n        }\n        if (isNameChange && existingUser?.name) {\n          usedNames.delete(existingUser.name.toLowerCase().trim());\n        }\n        usedNames.set(userName, userId);\n      }\n\n      (ws.data as WsData).userId = userId;\n      users.delete(userId);\n      users.set(userId, msg.user);\n      const existingUsers = Array.from(users.values()).filter(u => u.id !== userId);\n      if (existingUsers.length > 0) ws.send(JSON.stringify({ type: 'users', users: existingUsers }));\n\n      if (isNewUser) {\n        const room = loadRoom(roomId);\n        if (room && room.topology) {\n          ws.send(JSON.stringify({ type: 'initial-state', state: sanitizeTopologyStrings(room.topology) }));\n        } else {\n          ws.send(JSON.stringify({ type: 'initial-state', state: null }));\n        }\n        const chatHistory = roomChatHistory.get(roomId);\n        if (chatHistory && chatHistory.length > 0) {\n          ws.send(JSON.stringify({ type: 'chat-history', messages: chatHistory }));\n        }\n        db.addActivityLog({ timestamp: new Date().toISOString(), roomId, userId, userName: rawName, eventType: \"join\" });\n        if (redis.isRedisConnected()) {\n          redis.setUserPresence(roomId, userId, msg.user, 300);\n        }\n      }\n\n      const connections = roomConnections.get(roomId);\n      if (connections) {\n        const joinMsg = JSON.stringify(msg);\n        connections.forEach(client => { if (client !== ws && client.readyState === 1) client.send(joinMsg); });\n      }\n    } else if (msg.type === 'presence') {\n      const wsUserId = (ws.data as WsData)?.userId;\n      if (!wsUserId) return;\n      msg.userId = wsUserId;\n      const users = roomUsers.get(roomId);\n      if (users) {\n        const user = users.get(wsUserId);\n        if (user) {\n          if (Array.isArray(msg.selectedNodes)) {\n            user.selectedNodes = msg.selectedNodes.filter((id: unknown) => typeof id === 'string' && /^[\\w-]+$/.test(id)).slice(0, 50);\n            msg.selectedNodes = user.selectedNodes;\n          }\n          if (typeof msg.editingNode === 'string' && /^[\\w-]+$/.test(msg.editingNode)) {\n            user.editingNode = msg.editingNode;\n          } else if (msg.editingNode === null) {\n            user.editingNode = null;\n          }\n        }\n      }\n      ws.publish(roomId, JSON.stringify(msg));\n    } else if (msg.type === 'state') {\n      if (msg.state) {\n        const topologyValidation = validateTopology(msg.state);\n        if (!topologyValidation.valid) {\n          ws.send(JSON.stringify({ type: 'error', error: topologyValidation.error || 'Invalid state data' }));\n          return;\n        }\n        ws.publish(roomId, JSON.stringify({ type: 'state', state: topologyValidation.sanitized }));\n        const room = loadRoom(roomId);\n        if (room) { room.topology = topologyValidation.sanitized; room.lastActivity = new Date().toISOString(); saveRoom(room); }\n      } else {\n        ws.publish(roomId, message);\n      }\n    } else if (msg.type === 'patch') {\n      if (msg.patch) {\n        msg.patch = sanitizeTopologyStrings(msg.patch);\n      }\n      ws.publish(roomId, JSON.stringify(msg));\n    } else {\n      const wsUserId = (ws.data as WsData)?.userId;\n      if (wsUserId && msg.userId) msg.userId = wsUserId;\n      ws.publish(roomId, JSON.stringify(msg));\n    }\n    resetDestructionTimer(roomId);\n  },\n\n  close(ws: any) {\n    const roomId = (ws.data as WsData)?.roomId;\n    const userId = (ws.data as WsData)?.userId;\n    if (!roomId) return;\n    const connections = roomConnections.get(roomId);\n    if (connections) {\n      connections.delete(ws);\n      if (connections.size === 0) roomConnections.delete(roomId);\n    }\n    if (userId) {\n      const users = roomUsers.get(roomId);\n      if (users) {\n        const user = users.get(userId);\n        users.delete(userId);\n        if (connections && connections.size > 0) {\n          const leaveMsg = JSON.stringify({ type: 'leave', userId });\n          connections.forEach(client => { if (client.readyState === 1) client.send(leaveMsg); });\n        }\n        if (users.size === 0) roomUsers.delete(roomId);\n        db.addActivityLog({ timestamp: new Date().toISOString(), roomId, userId, userName: user?.name, eventType: \"leave\" });\n        if (redis.isRedisConnected()) {\n          redis.removeUserPresence(roomId, userId);\n        }\n      }\n    }\n    ws.unsubscribe(roomId);\n    const meta = roomMeta.get(roomId);\n    if (meta) {\n      meta.connectedUsers = Math.max(0, meta.connectedUsers - 1);\n      if (meta.connectedUsers === 0) {\n        const room = loadRoom(roomId);\n        if (room) {\n          if (room.destruct.mode === \"empty\") { deleteRoomData(roomId); }\n          else if (room.destruct.mode === \"time\") { scheduleDestruction(roomId, room.destruct.value); }\n        }\n      }\n    }\n  }\n};\n"
  }
]